diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 00000000000000..3d3dbdb1785c79 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,95 @@ +name: "Bug report" +description: Create a report to help us fix your issue +body: +- type: markdown + attributes: + value: | + **Is this the right place for my bug report?** + This repository contains the Linux kernel used on the Raspberry Pi. + If you believe that the issue you are seeing is kernel-related, this is the right place. + If not, we have other repositories for the GPU firmware at [github.com/raspberrypi/firmware](https://github.com/raspberrypi/firmware) and Raspberry Pi userland applications at [github.com/raspberrypi/userland](https://github.com/raspberrypi/userland). + + If you have problems with the Raspbian distribution packages, report them in the [github.com/RPi-Distro/repo](https://github.com/RPi-Distro/repo). + If you simply have a question, then [the Raspberry Pi forums](https://www.raspberrypi.org/forums) are the best place to ask it. + +- type: textarea + id: description + attributes: + label: Describe the bug + description: | + Add a clear and concise description of what you think the bug is. + validations: + required: true + +- type: textarea + id: reproduce + attributes: + label: Steps to reproduce the behaviour + description: | + List the steps required to reproduce the issue. + validations: + required: true + +- type: dropdown + id: model + attributes: + label: Device (s) + description: On which device you are facing the bug? + multiple: true + options: + - Raspberry Pi Zero + - Raspberry Pi Zero W / WH + - Raspberry Pi Zero 2 W + - Raspberry Pi 1 Mod. A + - Raspberry Pi 1 Mod. A+ + - Raspberry Pi 1 Mod. B + - Raspberry Pi 1 Mod. B+ + - Raspberry Pi 2 Mod. B + - Raspberry Pi 2 Mod. B v1.2 + - Raspberry Pi 3 Mod. A+ + - Raspberry Pi 3 Mod. B + - Raspberry Pi 3 Mod. B+ + - Raspberry Pi 4 Mod. B + - Raspberry Pi 400 + - Raspberry Pi 5 + - Raspberry Pi 500 + - Raspberry Pi CM1 + - Raspberry Pi CM3 + - Raspberry Pi CM3 Lite + - Raspberry Pi CM3+ + - Raspberry Pi CM3+ Lite + - Raspberry Pi CM4 + - Raspberry Pi CM4 Lite + - Raspberry Pi CM5 + - Raspberry Pi CM5 Lite + - Other + validations: + required: true + +- type: textarea + id: system + attributes: + label: System + description: | + Copy and paste the results of the raspinfo command in to this section. + Alternatively, copy and paste a pastebin link, or add answers to the following questions: + * Which OS and version (`cat /etc/rpi-issue`)? + * Which firmware version (`vcgencmd version`)? + * Which kernel version (`uname -a`)? + validations: + required: true + +- type: textarea + id: logs + attributes: + label: Logs + description: | + If applicable, add the relevant output from `dmesg` or similar. + +- type: textarea + id: additional + attributes: + label: Additional context + description: | + Add any other relevant context for the problem. + diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000000000..a2dc0b737233e4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,9 @@ +blank_issues_enabled: false +contact_links: + - name: "⛔ Question" + url: https://forums.raspberrypi.com + about: "Please do not use GitHub for asking questions. If you simply have a question, then the Raspberry Pi forums are the best place to ask it. Thanks in advance for helping us keep the issue tracker clean!" + - name: "⛔ Problems with Raspberry Pi OS packages" + url: https://github.com/RPi-Distro/repo + about: "If you have problems with a Raspberry Pi OS package, please report them at https://github.com/RPi-Distro/repo." + diff --git a/.github/workflows/checkpatch.yml b/.github/workflows/checkpatch.yml new file mode 100644 index 00000000000000..c214226654dc1b --- /dev/null +++ b/.github/workflows/checkpatch.yml @@ -0,0 +1,18 @@ +name: Advisory checkpatch review +on: [pull_request] + +jobs: + review: + name: checkpatch review + runs-on: ubuntu-latest + steps: + - name: 'Calculate PR commits + 1' + run: echo "PR_FETCH_DEPTH=$(( ${{ github.event.pull_request.commits }} + 1 ))" >> $GITHUB_ENV + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: ${{ env.PR_FETCH_DEPTH }} + - name: Copy checkpatch.conf + run: cp ${{github.workspace}}/.github/workflows/ci_checkpatch.conf ${{github.workspace}}/.checkpatch.conf + - name: Run checkpatch review + uses: webispy/checkpatch-action@v9 diff --git a/.github/workflows/ci_checkpatch.conf b/.github/workflows/ci_checkpatch.conf new file mode 100644 index 00000000000000..5d79c37b1d8c40 --- /dev/null +++ b/.github/workflows/ci_checkpatch.conf @@ -0,0 +1,4 @@ +--no-tree +--ignore FILE_PATH_CHANGES +--ignore GIT_COMMIT_ID +--ignore SPDX_LICENSE_TAG diff --git a/.github/workflows/dtoverlaycheck.yml b/.github/workflows/dtoverlaycheck.yml new file mode 100644 index 00000000000000..3d96814fe19f21 --- /dev/null +++ b/.github/workflows/dtoverlaycheck.yml @@ -0,0 +1,48 @@ +name: Pi dtoverlay checks + +on: + pull_request: + paths-ignore: + - '.github/**' + branches: [ "rpi-*" ] + push: + paths-ignore: + - '.github/**' + branches: [ "rpi-*" ] + workflow_dispatch: + +env: + UTILS_DIR: "${{github.workspace}}/utils" + +jobs: + dtoverlaycheck: + runs-on: ubuntu-latest + + steps: + - name: Install toolchain + run: | + sudo apt update + sudo apt-get install gcc-arm-linux-gnueabihf libfdt-dev device-tree-compiler + timeout-minutes: 10 + + - uses: actions/checkout@v4 + with: + fetch-depth: 1 + clean: true + + - name: overlaycheck + run: | + git clone https://github.com/raspberrypi/utils ${{env.UTILS_DIR}} + cd ${{env.UTILS_DIR}} + pwd + mkdir build + cd build + pwd + cmake .. + make -j4 + sudo make install + cd ${{github.workspace}} + pwd + make ARCH=arm KERNEL=kernel CROSS_COMPILE=arm-linux-gnueabihf- bcm2711_defconfig + make ARCH=arm KERNEL=kernel CROSS_COMPILE=arm-linux-gnueabihf- dtbs + ${{env.UTILS_DIR}}/overlaycheck/overlaycheck diff --git a/.github/workflows/kernel-build.yml b/.github/workflows/kernel-build.yml new file mode 100644 index 00000000000000..bc16fb8420f9db --- /dev/null +++ b/.github/workflows/kernel-build.yml @@ -0,0 +1,108 @@ +name: Pi kernel build tests + +on: + pull_request: + paths-ignore: + - '.github/**' + branches: [ "rpi-*" ] + push: + paths-ignore: + - '.github/**' + branches: [ "rpi-*" ] + workflow_dispatch: + +env: + NUM_JOBS: 6 + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + include: + - name: bcm2835 + arch: arm + defconfig: bcm2835_defconfig + kernel: kernel + + - name: arm64 + arch: arm64 + defconfig: defconfig + kernel: kernel8 + + - name: bcmrpi + arch: arm + defconfig: bcmrpi_defconfig + kernel: kernel + + - name: bcm2709 + arch: arm + defconfig: bcm2709_defconfig + kernel: kernel7 + + - name: bcm2711 + arch: arm + defconfig: bcm2711_defconfig + kernel: kernel7l + + - name: bcm2711_arm64 + arch: arm64 + defconfig: bcm2711_defconfig + kernel: kernel8 + + - name: bcm2712 + arch: arm64 + defconfig: bcm2712_defconfig + kernel: kernel_2712 + + steps: + - name: Update install + run: + sudo apt-get update + + - name: Install toolchain + run: + if [[ "${{matrix.arch}}" == "arm64" ]]; then + sudo apt-get install gcc-aarch64-linux-gnu; + else + sudo apt-get install gcc-arm-linux-gnueabihf; + fi + timeout-minutes: 5 + + - uses: actions/checkout@v4 + with: + fetch-depth: 1 + clean: true + + - name: Build kernel ${{matrix.name}} + run: | + mkdir ${{github.workspace}}/build + export ARCH=${{matrix.arch}} + if [[ "$ARCH" == "arm64" ]]; then + export CROSS_COMPILE=aarch64-linux-gnu- + export DTS_SUBDIR=broadcom + export IMAGE=Image.gz + else + export CROSS_COMPILE=arm-linux-gnueabihf- + export DTS_SUBDIR=broadcom + export IMAGE=zImage + fi + make O=${{github.workspace}}/build ${{matrix.defconfig}} + scripts/config --file ${{github.workspace}}/build/.config --set-val CONFIG_WERROR y + make O=${{github.workspace}}/build -j ${{env.NUM_JOBS}} $IMAGE modules dtbs + mkdir -p ${{github.workspace}}/install/boot/overlays + make O=${{github.workspace}}/build INSTALL_MOD_PATH=${{github.workspace}}/install modules_install + cp ${{github.workspace}}/build/arch/${ARCH}/boot/dts/${DTS_SUBDIR}/*.dtb ${{github.workspace}}/install/boot/ + cp ${{github.workspace}}/build/arch/${ARCH}/boot/dts/overlays/*.dtb* ${{github.workspace}}/install/boot/overlays/ + cp ${{github.workspace}}/arch/${ARCH}/boot/dts/overlays/README ${{github.workspace}}/install/boot/overlays/ + cp ${{github.workspace}}/build/arch/${ARCH}/boot/$IMAGE ${{github.workspace}}/install/boot/${{matrix.kernel}}.img + + - name: Tar build + run: tar -cvf ${{matrix.name}}_build.tar -C ${{github.workspace}}/install . + + - name: Upload results + uses: actions/upload-artifact@v4 + with: + name: ${{matrix.name}}_build + path: ${{matrix.name}}_build.tar + retention-days: 90 diff --git a/.github/workflows/kunit.yml b/.github/workflows/kunit.yml new file mode 100644 index 00000000000000..5713297d757141 --- /dev/null +++ b/.github/workflows/kunit.yml @@ -0,0 +1,57 @@ +name: KUnit Tests + +on: + pull_request: + branches: [ "rpi-*"] + + push: + branches: [ "rpi-*"] + +jobs: + core: + name: Generic DRM/KMS Unit Tests + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Run Generic DRM Tests + run: | + echo Skipping ./tools/testing/kunit/kunit.py run \ + --kunitconfig=drivers/gpu/drm/tests + + vc4-arm: + name: VC4 Unit Tests on ARM + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install Dependencies + run: | + sudo apt-get update + sudo apt-get install -y gcc-arm-linux-gnueabihf qemu-system-arm + + - name: Run VC4 Tests + run: | + ./tools/testing/kunit/kunit.py run \ + --kunitconfig=drivers/gpu/drm/vc4/tests \ + --cross_compile=arm-linux-gnueabihf- --arch=arm + + vc4-arm64: + name: VC4 Unit Tests on ARM64 + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install Dependencies + run: | + sudo apt-get update + sudo apt-get install -y gcc-aarch64-linux-gnu qemu-system-arm + + - name: Run VC4 Tests + run: | + ./tools/testing/kunit/kunit.py run \ + --kunitconfig=drivers/gpu/drm/vc4/tests \ + --cross_compile=aarch64-linux-gnu- --arch=arm64 diff --git a/Documentation/admin-guide/media/bcm2835-isp.rst b/Documentation/admin-guide/media/bcm2835-isp.rst new file mode 100644 index 00000000000000..e1c19f78435e6b --- /dev/null +++ b/Documentation/admin-guide/media/bcm2835-isp.rst @@ -0,0 +1,127 @@ +.. SPDX-License-Identifier: GPL-2.0 + +BCM2835 ISP Driver +================== + +Introduction +------------ + +The BCM2835 Image Sensor Pipeline (ISP) is a fixed function hardware pipeline +for performing image processing operations. Images are fed to the input +of the ISP through memory frame buffers. These images may be in various YUV, +RGB, or Bayer formats. A typical use case would have Bayer images obtained from +an image sensor by the BCM2835 Unicam peripheral, written to a memory +frame buffer, and finally fed into the input of the ISP. Two concurrent output +images may be generated in YUV or RGB format at different resolutions. +Statistics output is also generated for Bayer input images. + +The bcm2835-isp driver exposes the following media pads as V4L2 device nodes: + +.. tabularcolumns:: |l|l|l|l| + +.. cssclass: longtable + +.. flat-table:: + + * - *Pad* + - *Direction* + - *Purpose* + - *Formats* + + * - "bcm2835-isp0-output0" + - sink + - Accepts Bayer, RGB or YUV format frame buffers as input to the ISP HW + pipeline. + - :ref:`RAW8 <V4L2-PIX-FMT-SRGGB8>`, + :ref:`RAW10P <V4L2-PIX-FMT-SRGGB10P>`, + :ref:`RAW12P <V4L2-PIX-FMT-SRGGB12P>`, + :ref:`RAW14P <V4L2-PIX-FMT-SRGGB14P>`, + :ref:`RAW16 <V4L2-PIX-FMT-SRGGB16>`, + :ref:`RGB24/BGR24 <V4L2-PIX-FMT-RGB24>`, + :ref:`YUYV <V4L2-PIX-FMT-YUYV>`, + :ref:`YVYU <V4L2-PIX-FMT-YVYU>`, + :ref:`UYVY <V4L2-PIX-FMT-UYVY>`, + :ref:`VYUY <V4L2-PIX-FMT-VYUY>`, + :ref:`YUV420/YVU420 <V4L2-PIX-FMT-YUV420>` + + * - "bcm2835-isp0-capture1" + - source + - High resolution YUV or RGB processed output from the ISP. + - :ref:`RGB565 <V4L2-PIX-FMT-RGB565>`, + :ref:`RGB24/BGR24 <V4L2-PIX-FMT-RGB24>`, + :ref:`ABGR32 <V4L2-PIX-FMT-ABGR32>`, + :ref:`YUYV <V4L2-PIX-FMT-YUYV>`, + :ref:`YVYU <V4L2-PIX-FMT-YVYU>`, + :ref:`UYVY <V4L2-PIX-FMT-UYVY>`, + :ref:`VYUY <V4L2-PIX-FMT-VYUY>`. + :ref:`YUV420/YVU420 <V4L2-PIX-FMT-YUV420>`, + :ref:`NV12/NV21 <V4L2-PIX-FMT-NV12>`, + + * - "bcm2835-isp0-capture2" + - source + - Low resolution YUV processed output from the ISP. The output of + this pad cannot have a resolution larger than the "bcm2835-isp0-capture1" pad in any dimension. + - :ref:`YUYV <V4L2-PIX-FMT-YUYV>`, + :ref:`YVYU <V4L2-PIX-FMT-YVYU>`, + :ref:`UYVY <V4L2-PIX-FMT-UYVY>`, + :ref:`VYUY <V4L2-PIX-FMT-VYUY>`. + :ref:`YUV420/YVU420 <V4L2-PIX-FMT-YUV420>`, + :ref:`NV12/NV21 <V4L2-PIX-FMT-NV12>`, + + * - "bcm2835-isp0-capture1" + - source + - Image statistics calculated from the input image provided on the + "bcm2835-isp0-output0" pad. Statistics are only available for Bayer + format input images. + - :ref:`v4l2-meta-fmt-bcm2835-isp-stats`. + +Pipeline Configuration +---------------------- + +The ISP pipeline can be configure through user-space by calling +:ref:`VIDIOC_S_EXT_CTRLS <VIDIOC_G_EXT_CTRLS>` on the “bcm2835-isp0-output0” +node with the appropriate parameters as shown in the table below. + +.. tabularcolumns:: |p{2cm}|p{5.0cm}| + +.. cssclass: longtable + +.. flat-table:: + + * - *id* + - *Parameter* + + * - ``V4L2_CID_USER_BCM2835_ISP_CC_MATRIX`` + - struct :c:type:`bcm2835_isp_custom_ccm` + + * - ``V4L2_CID_USER_BCM2835_ISP_LENS_SHADING`` + - struct :c:type:`bcm2835_isp_lens_shading` + + * - ``V4L2_CID_USER_BCM2835_ISP_BLACK_LEVEL`` + - struct :c:type:`bcm2835_isp_black_level` + + * - ``V4L2_CID_USER_BCM2835_ISP_GEQ`` + - struct :c:type:`bcm2835_isp_geq` + + * - ``V4L2_CID_USER_BCM2835_ISP_GAMMA`` + - struct :c:type:`bcm2835_isp_gamma` + + * - ``V4L2_CID_USER_BCM2835_ISP_DENOISE`` + - struct :c:type:`bcm2835_isp_denoise` + + * - ``V4L2_CID_USER_BCM2835_ISP_SHARPEN`` + - struct :c:type:`bcm2835_isp_sharpen` + + * - ``V4L2_CID_USER_BCM2835_ISP_DPC`` + - struct :c:type:`bcm2835_isp_dpc` + +++++++++++++++++++++++++ +Configuration Parameters +++++++++++++++++++++++++ + +.. kernel-doc:: include/uapi/linux/bcm2835-isp.h + :functions: bcm2835_isp_rational bcm2835_isp_ccm bcm2835_isp_custom_ccm + bcm2835_isp_gain_format bcm2835_isp_lens_shading + bcm2835_isp_black_level bcm2835_isp_geq bcm2835_isp_gamma + bcm2835_isp_denoise bcm2835_isp_sharpen + bcm2835_isp_dpc_mode bcm2835_isp_dpc diff --git a/Documentation/devicetree/bindings/display/brcm,bcm2711-hdmi.yaml b/Documentation/devicetree/bindings/display/brcm,bcm2711-hdmi.yaml index 5b35adf34c7bd0..6d11f5955b51a3 100644 --- a/Documentation/devicetree/bindings/display/brcm,bcm2711-hdmi.yaml +++ b/Documentation/devicetree/bindings/display/brcm,bcm2711-hdmi.yaml @@ -14,6 +14,8 @@ properties: enum: - brcm,bcm2711-hdmi0 - brcm,bcm2711-hdmi1 + - brcm,bcm2712-hdmi0 + - brcm,bcm2712-hdmi1 reg: items: diff --git a/Documentation/devicetree/bindings/display/brcm,bcm2835-dsi0.yaml b/Documentation/devicetree/bindings/display/brcm,bcm2835-dsi0.yaml index c8b2459d64f666..af638b22461923 100644 --- a/Documentation/devicetree/bindings/display/brcm,bcm2835-dsi0.yaml +++ b/Documentation/devicetree/bindings/display/brcm,bcm2835-dsi0.yaml @@ -21,6 +21,7 @@ properties: - brcm,bcm2711-dsi1 - brcm,bcm2835-dsi0 - brcm,bcm2835-dsi1 + - brcm,bcm2711-dsi1 reg: maxItems: 1 diff --git a/Documentation/devicetree/bindings/display/brcm,bcm2835-hvs.yaml b/Documentation/devicetree/bindings/display/brcm,bcm2835-hvs.yaml index 2e8566f47e63c0..f91c9dce2a44d3 100644 --- a/Documentation/devicetree/bindings/display/brcm,bcm2835-hvs.yaml +++ b/Documentation/devicetree/bindings/display/brcm,bcm2835-hvs.yaml @@ -13,6 +13,7 @@ properties: compatible: enum: - brcm,bcm2711-hvs + - brcm,bcm2712-hvs - brcm,bcm2835-hvs reg: @@ -36,7 +37,9 @@ if: properties: compatible: contains: - const: brcm,bcm2711-hvs + enum: + - brcm,bcm2711-hvs + - brcm,bcm2712-hvs then: required: diff --git a/Documentation/devicetree/bindings/display/brcm,bcm2835-pixelvalve0.yaml b/Documentation/devicetree/bindings/display/brcm,bcm2835-pixelvalve0.yaml index 4e1ba03f6477f4..6b5b1d3fbc0beb 100644 --- a/Documentation/devicetree/bindings/display/brcm,bcm2835-pixelvalve0.yaml +++ b/Documentation/devicetree/bindings/display/brcm,bcm2835-pixelvalve0.yaml @@ -20,6 +20,9 @@ properties: - brcm,bcm2711-pixelvalve2 - brcm,bcm2711-pixelvalve3 - brcm,bcm2711-pixelvalve4 + - brcm,bcm2712-pixelvalve0 + - brcm,bcm2712-pixelvalve1 + - brcm,bcm2712-pixelvalve2 reg: maxItems: 1 diff --git a/Documentation/devicetree/bindings/display/brcm,bcm2835-txp.yaml b/Documentation/devicetree/bindings/display/brcm,bcm2835-txp.yaml index bb186197e471eb..16f45afd2badce 100644 --- a/Documentation/devicetree/bindings/display/brcm,bcm2835-txp.yaml +++ b/Documentation/devicetree/bindings/display/brcm,bcm2835-txp.yaml @@ -11,7 +11,10 @@ maintainers: properties: compatible: - const: brcm,bcm2835-txp + enum: + - brcm,bcm2712-mop + - brcm,bcm2712-moplet + - brcm,bcm2835-txp reg: maxItems: 1 diff --git a/Documentation/devicetree/bindings/display/brcm,bcm2835-vc4.yaml b/Documentation/devicetree/bindings/display/brcm,bcm2835-vc4.yaml index 49a5e041aa4936..2aa9d5d2afff04 100644 --- a/Documentation/devicetree/bindings/display/brcm,bcm2835-vc4.yaml +++ b/Documentation/devicetree/bindings/display/brcm,bcm2835-vc4.yaml @@ -18,6 +18,7 @@ properties: compatible: enum: - brcm,bcm2711-vc5 + - brcm,bcm2712-vc6 - brcm,bcm2835-vc4 - brcm,cygnus-vc4 diff --git a/Documentation/devicetree/bindings/display/panel/ilitek,ili9881c.yaml b/Documentation/devicetree/bindings/display/panel/ilitek,ili9881c.yaml index baf5dfe5f5ebdd..9edb85103c867f 100644 --- a/Documentation/devicetree/bindings/display/panel/ilitek,ili9881c.yaml +++ b/Documentation/devicetree/bindings/display/panel/ilitek,ili9881c.yaml @@ -22,6 +22,8 @@ properties: - startek,kd050hdfia020 - tdo,tl050hdv35 - wanchanglong,w552946aba + - raspberrypi,dsi-5inch + - raspberrypi,dsi-7inch - const: ilitek,ili9881c reg: diff --git a/Documentation/devicetree/bindings/display/panel/panel-dsi.yaml b/Documentation/devicetree/bindings/display/panel/panel-dsi.yaml new file mode 100644 index 00000000000000..0576541d956797 --- /dev/null +++ b/Documentation/devicetree/bindings/display/panel/panel-dsi.yaml @@ -0,0 +1,118 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/display/panel/panel-dsi.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Generic MIPI DSI Panel + +maintainers: + - Timon Skerutsch <kernel@diodes-delight.com> + +allOf: + - $ref: panel-common.yaml# + +properties: + compatible: + description: + Shall contain a panel specific compatible and "panel-dsi" + in that order. + items: + - {} + - const: panel-dsi + + dsi-color-format: + description: | + The color format used by the panel. Only DSI supported formats are allowed. + enum: + - RGB888 + - RGB666 + - RGB666_PACKED + - RGB565 + + port: + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + description: + Panel MIPI DSI input + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + properties: + data-lanes: true + + required: + - data-lanes + + mode: + description: | + DSI mode flags. See DSI Specs for details. + These are driver independent features of the DSI bus. + items: + - const: MODE_VIDEO + - const: MODE_VIDEO_BURST + - const: MODE_VIDEO_SYNC_PULSE + - const: MODE_VIDEO_AUTO_VERT + - const: MODE_VIDEO_HSE + - const: MODE_VIDEO_NO_HFP + - const: MODE_VIDEO_NO_HBP + - const: MODE_VIDEO_NO_HSA + - const: MODE_VSYNC_FLUSH + - const: MODE_NO_EOT_PACKET + - const: CLOCK_NON_CONTINUOUS + - const: MODE_LPM + - const: HS_PKT_END_ALIGNED + + reg: true + backlight: true + enable-gpios: true + width-mm: true + height-mm: true + panel-timing: true + power-supply: true + reset-gpios: true + ddc-i2c-bus: true + +required: + - panel-timing + - reg + - power-supply + - dsi-color-format + - port + +additionalProperties: false + +examples: + - | + panel { + compatible = "panel-mfgr,generic-dsi-panel","panel-dsi"; + power-supply = <&vcc_supply>; + backlight = <&backlight>; + dsi-color-format = "RGB888"; + reg = <0>; + mode = "MODE_VIDEO", "MODE_VIDEO_BURST", "MODE_NO_EOT_PACKET"; + + port { + panel_dsi_port: endpoint { + data-lanes = <1 2>; + remote-endpoint = <&dsi_out>; + }; + }; + + panel-timing { + clock-frequency = <9200000>; + hactive = <800>; + vactive = <480>; + hfront-porch = <8>; + hback-porch = <4>; + hsync-len = <41>; + vback-porch = <2>; + vfront-porch = <4>; + vsync-len = <10>; + }; + }; + +... diff --git a/Documentation/devicetree/bindings/display/panel/panel-simple.yaml b/Documentation/devicetree/bindings/display/panel/panel-simple.yaml index b89e3979057911..2f1c39e3afdd6d 100644 --- a/Documentation/devicetree/bindings/display/panel/panel-simple.yaml +++ b/Documentation/devicetree/bindings/display/panel/panel-simple.yaml @@ -142,6 +142,8 @@ properties: - frida,frd350h54004 # FriendlyELEC HD702E 800x1280 LCD panel - friendlyarm,hd702e + # Geekworm MZP280 2.8" 480x640 LCD panel with capacitive touch + - geekworm,mzp280 # GiantPlus GPG48273QS5 4.3" (480x272) WQVGA TFT LCD panel - giantplus,gpg48273qs5 # GiantPlus GPM940B0 3.0" QVGA TFT LCD panel @@ -154,6 +156,8 @@ properties: - hit,tx23d38vm0caa # Innolux AT043TN24 4.3" WQVGA TFT LCD panel - innolux,at043tn24 + # Innolux AT056tN53V1 5.6" VGA (640x480) TFT LCD panel + - innolux,at056tn53v1 # Innolux AT070TN92 7.0" WQVGA TFT LCD panel - innolux,at070tn92 # Innolux G070ACE-L01 7" WVGA (800x480) TFT LCD panel diff --git a/Documentation/devicetree/bindings/gpu/brcm,bcm-v3d.yaml b/Documentation/devicetree/bindings/gpu/brcm,bcm-v3d.yaml index dc078ceeca9ac3..deae587bfb4483 100644 --- a/Documentation/devicetree/bindings/gpu/brcm,bcm-v3d.yaml +++ b/Documentation/devicetree/bindings/gpu/brcm,bcm-v3d.yaml @@ -16,6 +16,7 @@ properties: compatible: enum: + - brcm,2712-v3d - brcm,2711-v3d - brcm,2712-v3d - brcm,7268-v3d diff --git a/Documentation/devicetree/bindings/hwmon/microchip,emc2305.yaml b/Documentation/devicetree/bindings/hwmon/microchip,emc2305.yaml new file mode 100644 index 00000000000000..efdc3cecb03dbb --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/microchip,emc2305.yaml @@ -0,0 +1,54 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- + +$id: http://devicetree.org/schemas/hwmon/emc2305.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Microchip EMC230[1|2|3|5] RPM-based PWM Fan Speed Controller + +properties: + compatible: + enum: + - microchip,emc2305 + - microchip,emc2301 + emc2305,pwm-min: + description: + Min pwm of emc2305 + maxItems: 1 + emc2305,pwm-max: + description: + Max pwm of emc2305 + maxItems: 1 + emc2305,pwm-channel: + description: + Max number of pwm channels + maxItems: 1 + emcs205,max-state: + description: + maxItems: 1 + emc2305,cooling-levels: + description: + Quantity of cooling level state. + maxItems: 1 + +required: + - compatible + +optional: + - emc2305,min-pwm + - emc2305,max-pwm + - emc2305,pwm-channels + - emc2305,cooling-levels + +additionalProperties: false + +examples: + - | + fan { + compatible = "microchip,emc2305"; + emc2305,pwm-min = <0>; + emc2305,pwm-max = <255>; + emc2305,pwm-channel = <5> + emc2305,cooling-levels = <10>; + }; diff --git a/Documentation/devicetree/bindings/media/bcm2835-unicam.txt b/Documentation/devicetree/bindings/media/bcm2835-unicam.txt new file mode 100644 index 00000000000000..164d0377dcd249 --- /dev/null +++ b/Documentation/devicetree/bindings/media/bcm2835-unicam.txt @@ -0,0 +1,85 @@ +Broadcom BCM283x Camera Interface (Unicam) +------------------------------------------ + +The Unicam block on BCM283x SoCs is the receiver for either +CSI-2 or CCP2 data from image sensors or similar devices. + +The main platform using this SoC is the Raspberry Pi family of boards. +On the Pi the VideoCore firmware can also control this hardware block, +and driving it from two different processors will cause issues. +To avoid this, the firmware checks the device tree configuration +during boot. If it finds device tree nodes called csi0 or csi1 then +it will stop the firmware accessing the block, and it can then +safely be used via the device tree binding. + +Required properties: +=================== +- compatible : must be "brcm,bcm2835-unicam". +- reg : physical base address and length of the register sets for the + device. +- interrupts : should contain the IRQ line for this Unicam instance. +- clocks : list of clock specifiers, corresponding to entries in + clock-names property. +- clock-names : must contain "lp" and "vpu" entries, matching entries in the + clocks property. + +Unicam supports a single port node. It should contain one 'port' child node +with child 'endpoint' node. Please refer to the bindings defined in +Documentation/devicetree/bindings/media/video-interfaces.txt. + +Within the endpoint node the "remote-endpoint" and "data-lanes" properties +are mandatory. +Data lane reordering is not supported so the data lanes must be in order, +starting at 1. The number of data lanes should represent the number of +usable lanes for the hardware block. That may be limited by either the SoC or +how the platform presents the interface, and the lower value must be used. + +Lane reordering is not supported on the clock lane either, so the optional +property "clock-lane" will implicitly be <0>. +Similarly lane inversion is not supported, therefore "lane-polarities" will +implicitly be <0 0 0 0 0>. +Neither of these values will be checked. + +Example: + csi1: csi1@7e801000 { + compatible = "brcm,bcm2835-unicam"; + reg = <0x7e801000 0x800>, + <0x7e802004 0x4>; + interrupts = <2 7>; + clocks = <&clocks BCM2835_CLOCK_CAM1>, + <&firmware_clocks 4>; + clock-names = "lp", "vpu"; + port { + csi1_ep: endpoint { + remote-endpoint = <&tc358743_0>; + data-lanes = <1 2>; + }; + }; + }; + + i2c0: i2c@7e205000 { + tc358743: csi-hdmi-bridge@0f { + compatible = "toshiba,tc358743"; + reg = <0x0f>; + + clocks = <&tc358743_clk>; + clock-names = "refclk"; + + tc358743_clk: bridge-clk { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <27000000>; + }; + + port { + tc358743_0: endpoint { + remote-endpoint = <&csi1_ep>; + clock-lanes = <0>; + data-lanes = <1 2>; + clock-noncontinuous; + link-frequencies = + /bits/ 64 <297000000>; + }; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/media/i2c/ad5398.txt b/Documentation/devicetree/bindings/media/i2c/ad5398.txt new file mode 100644 index 00000000000000..446ac9717598f0 --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/ad5398.txt @@ -0,0 +1,20 @@ +* Analog Devices AD5398 autofocus coil + +Required Properties: + + - compatible: Must contain one of: + - "adi,ad5398" + + - reg: I2C slave address + + - VANA-supply: supply of voltage for VANA pin + +Example: + + ad5398: coil@c { + compatible = "adi,ad5398"; + reg = <0x0c>; + + VANA-supply = <&vaux4>; + }; + diff --git a/Documentation/devicetree/bindings/media/i2c/arducam,64mp.yaml b/Documentation/devicetree/bindings/media/i2c/arducam,64mp.yaml new file mode 100644 index 00000000000000..b71a19782f97f2 --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/arducam,64mp.yaml @@ -0,0 +1,115 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/arducam,64mp.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Arducam 1/1.7-Inch 64Mpixel CMOS Digital Image Sensor + +maintainers: + - Lee Jackson <info@arducam.com> + +description: |- + The Arducam 1/1.7-Inch 64Mpixel CMOS active pixel digital image sensor + with an active array size of 9248 x 6944. It is programmable through + I2C interface. The I2C address is fixed to 0x1A as per sensor data sheet. + Image data is sent through MIPI CSI-2, which can be configured for operation + with either 2 or 4 data lanes. + +properties: + compatible: + const: arducam,64mp + + reg: + description: I2C device address + maxItems: 1 + + clocks: + maxItems: 1 + + VDIG-supply: + description: + Digital I/O voltage supply, 1.05 volts + + VANA-supply: + description: + Analog voltage supply, 2.8 volts + + VDDL-supply: + description: + Digital core voltage supply, 1.8 volts + + reset-gpios: + description: |- + Reference to the GPIO connected to the xclr pin, if any. + Must be released (set high) after all supplies and INCK are applied. + + # See ../video-interfaces.txt for more details + port: + type: object + properties: + endpoint: + type: object + properties: + data-lanes: + description: |- + The sensor supports either two-lane, or four-lane operation. + For two-lane operation the property must be set to <1 2>. + anyOf: + - items: + - const: 1 + - const: 2 + - items: + - const: 1 + - const: 2 + - const: 3 + - const: 4 + + clock-noncontinuous: true + + link-frequencies: + allOf: + - $ref: /schemas/types.yaml#/definitions/uint64-array + description: + Allowed data bus frequencies. + + required: + - link-frequencies + +required: + - compatible + - reg + - clocks + - VANA-supply + - VDIG-supply + - VDDL-supply + - port + +additionalProperties: false + +examples: + - | + i2c0 { + #address-cells = <1>; + #size-cells = <0>; + + arducam_64mp: sensor@1a { + compatible = "arducam,64mp"; + reg = <0x1a>; + clocks = <&arducam_64mp_clk>; + VANA-supply = <&arducam_64mp_vana>; /* 2.8v */ + VDIG-supply = <&arducam_64mp_vdig>; /* 1.05v */ + VDDL-supply = <&arducam_64mp_vddl>; /* 1.8v */ + + port { + arducam_64mp_0: endpoint { + remote-endpoint = <&csi1_ep>; + data-lanes = <1 2>; + clock-noncontinuous; + link-frequencies = /bits/ 64 <456000000>; + }; + }; + }; + }; + +... diff --git a/Documentation/devicetree/bindings/media/i2c/arducam-pivariety.yaml b/Documentation/devicetree/bindings/media/i2c/arducam-pivariety.yaml new file mode 100644 index 00000000000000..92bf4ff32eb4cc --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/arducam-pivariety.yaml @@ -0,0 +1,112 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/arducam-pivariety.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Arducam Pivariety Series CMOS Digital Image Sensor + +maintainers: + - Lee Jackson <info@arducam.com> + +description: |- + Arducam Pivariety series cameras make compatibility layers for various CMOS + sensors and provide a unified command interface. It is programmable through + I2C interface. The I2C address is fixed to 0x0C. Image data is sent through + MIPI CSI-2, which is configured as either 1, 2 or 4 data lanes. + +properties: + compatible: + const: arducam,arducam-pivariety + + reg: + description: I2C device address + maxItems: 1 + + clocks: + maxItems: 1 + + VDIG-supply: + description: + Digital I/O voltage supply, 1.05 volts + + VANA-supply: + description: + Analog voltage supply, 2.8 volts + + VDDL-supply: + description: + Digital core voltage supply, 1.8 volts + + reset-gpios: + description: |- + Reference to the GPIO connected to the xclr pin, if any. + Must be released (set high) after all supplies and INCK are applied. + + # See ../video-interfaces.txt for more details + port: + type: object + properties: + endpoint: + type: object + properties: + data-lanes: + description: |- + The sensor supports either two-lane, or four-lane operation. + For two-lane operation the property must be set to <1 2>. + items: + - const: 1 + - const: 2 + + clock-noncontinuous: + type: boolean + description: |- + MIPI CSI-2 clock is non-continuous if this property is present, + otherwise it's continuous. + + link-frequencies: + allOf: + - $ref: /schemas/types.yaml#/definitions/uint64-array + description: + Allowed data bus frequencies. + + required: + - link-frequencies + +required: + - compatible + - reg + - clocks + - VANA-supply + - VDIG-supply + - VDDL-supply + - port + +additionalProperties: false + +examples: + - | + i2c0 { + #address-cells = <1>; + #size-cells = <0>; + + arducam_pivariety: sensor@0c { + compatible = "arducam,arducam-pivariety"; + reg = <0x0c>; + clocks = <&arducam_pivariety_clk>; + VANA-supply = <&arducam_pivariety_vana>; /* 2.8v */ + VDIG-supply = <&arducam_pivariety_vdig>; /* 1.05v */ + VDDL-supply = <&arducam_pivariety_vddl>; /* 1.8v */ + + port { + arducam_pivariety_0: endpoint { + remote-endpoint = <&csi1_ep>; + data-lanes = <1 2>; + clock-noncontinuous; + link-frequencies = /bits/ 64 <493500000>; + }; + }; + }; + }; + +... diff --git a/Documentation/devicetree/bindings/media/i2c/dongwoon,dw9807-vcm.yaml b/Documentation/devicetree/bindings/media/i2c/dongwoon,dw9807-vcm.yaml index aae246ca3fcf53..6de07543e9731d 100644 --- a/Documentation/devicetree/bindings/media/i2c/dongwoon,dw9807-vcm.yaml +++ b/Documentation/devicetree/bindings/media/i2c/dongwoon,dw9807-vcm.yaml @@ -5,22 +5,32 @@ $id: http://devicetree.org/schemas/media/i2c/dongwoon,dw9807-vcm.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: Dongwoon Anatech DW9807 voice coil lens driver +title: Dongwoon Anatech DW9807 and DW9817 voice coil lens driver maintainers: - Sakari Ailus <sakari.ailus@linux.intel.com> description: | DW9807 is a 10-bit DAC with current sink capability. It is intended for - controlling voice coil lenses. + controlling voice coil lenses. The output drive is 0-100mA. + DW9817 is very similar as a 10-bit DAC with current sink capability, + however the output drive is a bidirection -100 to +100mA. + properties: compatible: - const: dongwoon,dw9807-vcm + items: + - enum: + - dongwoon,dw9807-vcm + - dongwoon,dw9817-vcm reg: maxItems: 1 + VDD-supply: + description: + Definition of the regulator used as VDD power supply to the driver. + required: - compatible - reg diff --git a/Documentation/devicetree/bindings/media/i2c/imx378.yaml b/Documentation/devicetree/bindings/media/i2c/imx378.yaml new file mode 100644 index 00000000000000..f832b4bfab9366 --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/imx378.yaml @@ -0,0 +1,113 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/imx378.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Sony 1/2.3-Inch 12Mpixel CMOS Digital Image Sensor + +maintainers: + - Naushir Patuck <naush@raspberypi.com> + +description: |- + The Sony IMX378 is a 1/2.3-inch CMOS active pixel digital image sensor + with an active array size of 4056H x 3040V. It is programmable through + I2C interface. The I2C address is fixed to 0x1A as per sensor data sheet. + Image data is sent through MIPI CSI-2, which is configured as either 2 or + 4 data lanes. + +properties: + compatible: + const: sony,imx378 + + reg: + description: I2C device address + maxItems: 1 + + clocks: + maxItems: 1 + + VDIG-supply: + description: + Digital I/O voltage supply, 1.05 volts + + VANA-supply: + description: + Analog voltage supply, 2.8 volts + + VDDL-supply: + description: + Digital core voltage supply, 1.8 volts + + reset-gpios: + description: |- + Reference to the GPIO connected to the xclr pin, if any. + Must be released (set high) after all supplies and INCK are applied. + + # See ../video-interfaces.txt for more details + port: + type: object + properties: + endpoint: + type: object + properties: + data-lanes: + description: |- + The sensor supports either two-lane, or four-lane operation. + For two-lane operation the property must be set to <1 2>. + items: + - const: 1 + - const: 2 + + clock-noncontinuous: + type: boolean + description: |- + MIPI CSI-2 clock is non-continuous if this property is present, + otherwise it's continuous. + + link-frequencies: + allOf: + - $ref: /schemas/types.yaml#/definitions/uint64-array + description: + Allowed data bus frequencies. + + required: + - link-frequencies + +required: + - compatible + - reg + - clocks + - VANA-supply + - VDIG-supply + - VDDL-supply + - port + +additionalProperties: false + +examples: + - | + i2c0 { + #address-cells = <1>; + #size-cells = <0>; + + imx378: sensor@10 { + compatible = "sony,imx378"; + reg = <0x1a>; + clocks = <&imx378_clk>; + VANA-supply = <&imx378_vana>; /* 2.8v */ + VDIG-supply = <&imx378_vdig>; /* 1.05v */ + VDDL-supply = <&imx378_vddl>; /* 1.8v */ + + port { + imx378_0: endpoint { + remote-endpoint = <&csi1_ep>; + data-lanes = <1 2>; + clock-noncontinuous; + link-frequencies = /bits/ 64 <450000000>; + }; + }; + }; + }; + +... diff --git a/Documentation/devicetree/bindings/media/i2c/imx477.yaml b/Documentation/devicetree/bindings/media/i2c/imx477.yaml new file mode 100644 index 00000000000000..0994e13e67f68c --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/imx477.yaml @@ -0,0 +1,113 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/imx477.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Sony 1/2.3-Inch 12Mpixel CMOS Digital Image Sensor + +maintainers: + - Naushir Patuck <naush@raspberypi.com> + +description: |- + The Sony IMX477 is a 1/2.3-inch CMOS active pixel digital image sensor + with an active array size of 4056H x 3040V. It is programmable through + I2C interface. The I2C address is fixed to 0x1A as per sensor data sheet. + Image data is sent through MIPI CSI-2, which is configured as either 2 or + 4 data lanes. + +properties: + compatible: + const: sony,imx477 + + reg: + description: I2C device address + maxItems: 1 + + clocks: + maxItems: 1 + + VDIG-supply: + description: + Digital I/O voltage supply, 1.05 volts + + VANA-supply: + description: + Analog voltage supply, 2.8 volts + + VDDL-supply: + description: + Digital core voltage supply, 1.8 volts + + reset-gpios: + description: |- + Reference to the GPIO connected to the xclr pin, if any. + Must be released (set high) after all all supplies and INCK are applied. + + # See ../video-interfaces.txt for more details + port: + type: object + properties: + endpoint: + type: object + properties: + data-lanes: + description: |- + The sensor supports either two-lane, or four-lane operation. + For two-lane operation the property must be set to <1 2>. + items: + - const: 1 + - const: 2 + + clock-noncontinuous: + type: boolean + description: |- + MIPI CSI-2 clock is non-continuous if this property is present, + otherwise it's continuous. + + link-frequencies: + allOf: + - $ref: /schemas/types.yaml#/definitions/uint64-array + description: + Allowed data bus frequencies. + + required: + - link-frequencies + +required: + - compatible + - reg + - clocks + - VANA-supply + - VDIG-supply + - VDDL-supply + - port + +additionalProperties: false + +examples: + - | + i2c0 { + #address-cells = <1>; + #size-cells = <0>; + + imx477: sensor@10 { + compatible = "sony,imx477"; + reg = <0x1a>; + clocks = <&imx477_clk>; + VANA-supply = <&imx477_vana>; /* 2.8v */ + VDIG-supply = <&imx477_vdig>; /* 1.05v */ + VDDL-supply = <&imx477_vddl>; /* 1.8v */ + + port { + imx477_0: endpoint { + remote-endpoint = <&csi1_ep>; + data-lanes = <1 2>; + clock-noncontinuous; + link-frequencies = /bits/ 64 <450000000>; + }; + }; + }; + }; + +... diff --git a/Documentation/devicetree/bindings/media/i2c/imx519.yaml b/Documentation/devicetree/bindings/media/i2c/imx519.yaml new file mode 100644 index 00000000000000..717230a21764c3 --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/imx519.yaml @@ -0,0 +1,113 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/imx519.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Sony 1/2.5-Inch 16Mpixel CMOS Digital Image Sensor + +maintainers: + - Lee Jackson <info@arducam.com> + +description: |- + The Sony IMX519 is a 1/2.5-inch CMOS active pixel digital image sensor + with an active array size of 4656H x 3496V. It is programmable through + I2C interface. The I2C address is fixed to 0x1A as per sensor data sheet. + Image data is sent through MIPI CSI-2, which is configured as either 2 or + 4 data lanes. + +properties: + compatible: + const: sony,imx519 + + reg: + description: I2C device address + maxItems: 1 + + clocks: + maxItems: 1 + + VDIG-supply: + description: + Digital I/O voltage supply, 1.05 volts + + VANA-supply: + description: + Analog voltage supply, 2.8 volts + + VDDL-supply: + description: + Digital core voltage supply, 1.8 volts + + reset-gpios: + description: |- + Reference to the GPIO connected to the xclr pin, if any. + Must be released (set high) after all supplies and INCK are applied. + + # See ../video-interfaces.txt for more details + port: + type: object + properties: + endpoint: + type: object + properties: + data-lanes: + description: |- + The sensor supports either two-lane, or four-lane operation. + For two-lane operation the property must be set to <1 2>. + items: + - const: 1 + - const: 2 + + clock-noncontinuous: + type: boolean + description: |- + MIPI CSI-2 clock is non-continuous if this property is present, + otherwise it's continuous. + + link-frequencies: + allOf: + - $ref: /schemas/types.yaml#/definitions/uint64-array + description: + Allowed data bus frequencies. + + required: + - link-frequencies + +required: + - compatible + - reg + - clocks + - VANA-supply + - VDIG-supply + - VDDL-supply + - port + +additionalProperties: false + +examples: + - | + i2c0 { + #address-cells = <1>; + #size-cells = <0>; + + imx519: sensor@1a { + compatible = "sony,imx519"; + reg = <0x1a>; + clocks = <&imx519_clk>; + VANA-supply = <&imx519_vana>; /* 2.8v */ + VDIG-supply = <&imx519_vdig>; /* 1.05v */ + VDDL-supply = <&imx519_vddl>; /* 1.8v */ + + port { + imx519_0: endpoint { + remote-endpoint = <&csi1_ep>; + data-lanes = <1 2>; + clock-noncontinuous; + link-frequencies = /bits/ 64 <493500000>; + }; + }; + }; + }; + +... diff --git a/Documentation/devicetree/bindings/media/i2c/irs1125.txt b/Documentation/devicetree/bindings/media/i2c/irs1125.txt new file mode 100644 index 00000000000000..25a48028c9577a --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/irs1125.txt @@ -0,0 +1,48 @@ +* Infineon irs1125 time of flight sensor + +The Infineon irs1125 is a time of flight digital image sensor with +an active array size of 352H x 286V. It is programmable through I2C +interface. The I2C address defaults to 0x3D, but can be reconfigured +to address 0x3C or 0x41 via I2C commands. Image data is sent through +MIPI CSI-2, which is configured as either 1 or 2 data lanes. + +Required Properties: +- compatible: value should be "infineon,irs1125" for irs1125 sensor +- reg: I2C bus address of the device +- clocks: reference to the xclk input clock. +- pwdn-gpios: reference to the GPIO connected to the reset pin. + This is an active low signal to the iirs1125. + +The irs1125 device node should contain one 'port' child node with +an 'endpoint' subnode. For further reading on port node refer to +Documentation/devicetree/bindings/media/video-interfaces.txt. + +Endpoint node required properties for CSI-2 connection are: +- remote-endpoint: a phandle to the bus receiver's endpoint node. +- clock-lanes: should be set to <0> (clock lane on hardware lane 0) +- data-lanes: should be set to <1> or <1 2> (one or two lane CSI-2 + supported) + +Example: + sensor@10 { + compatible = "infineon,irs1125"; + reg = <0x3D>; + #address-cells = <1>; + #size-cells = <0>; + clocks = <&irs1125_clk>; + pwdn-gpios = <&gpio 5 0>; + + irs1125_clk: camera-clk { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <26000000>; + }; + + port { + sensor_out: endpoint { + remote-endpoint = <&csiss_in>; + clock-lanes = <0>; + data-lanes = <1 2>; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/media/i2c/rohm,bu64754.yaml b/Documentation/devicetree/bindings/media/i2c/rohm,bu64754.yaml new file mode 100644 index 00000000000000..22da4a46bb0c0e --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/rohm,bu64754.yaml @@ -0,0 +1,48 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# Copyright (C) 2023 Ideas on Board Oy. +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/rohm,bu64754.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: ROHM BU64754 Actuator Driver for Camera Autofocus + +maintainers: + - Kieran Bingham <kieran.bingham@ideasonboard.com> + +description: | + The BU64754GWZ is an actuator driver IC which can control the actuator + position precisely using an internal Hall Sensor. + +properties: + compatible: + items: + - enum: + - rohm,bu64754 + + reg: + maxItems: 1 + + vdd-supply: + description: + Definition of the regulator used as VDD power supply to the driver. + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + lens@76 { + compatible = "rohm,bu64754"; + reg = <0x76>; + vdd-supply = <&cam1_reg>; + }; + }; +... diff --git a/Documentation/devicetree/bindings/media/i2c/sony,imx290.yaml b/Documentation/devicetree/bindings/media/i2c/sony,imx290.yaml index bf05ca48601abd..fa69bd21c8da40 100644 --- a/Documentation/devicetree/bindings/media/i2c/sony,imx290.yaml +++ b/Documentation/devicetree/bindings/media/i2c/sony,imx290.yaml @@ -33,6 +33,8 @@ properties: - sony,imx290lqr # Colour - sony,imx290llr # Monochrome - sony,imx327lqr # Colour + - sony,imx462lqr # Colour + - sony,imx462llr # Monochrome - const: sony,imx290 deprecated: true diff --git a/Documentation/devicetree/bindings/media/i2c/sony,imx500.yaml b/Documentation/devicetree/bindings/media/i2c/sony,imx500.yaml new file mode 100644 index 00000000000000..b8538ae6c80f28 --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/sony,imx500.yaml @@ -0,0 +1,132 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/sony,imx500.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Sony CMOS Digital Image Sensor and CNN + +maintainers: + - Raspberry Pi <kernel-list@raspberrypi.com> + +description: |- + The Sony IMX500 is a stacked 1/2.3-inch CMOS digital image sensor and inbuilt + AI processor with an active array CNN (Convolutional Neural Network) inference + engine. The native sensor size is 4056H x 3040V, and the module also contains + an in-built ISP for the CNN. The module is programmable through an I2C + interface with firmware and neural network uploads being made over SPI. The + default I2C address is 0x1A, with an address of 0x10 being selectable via + SLASEL. The module also has a second I2C interface available with a fixed + address of 0x36. Image data is sent through MIPI CSI-2, which is configured + as either 2 or 4 data lanes. + +properties: + compatible: + const: sony,imx500 + + reg: + description: I2C device address + maxItems: 1 + + clocks: + maxItems: 1 + + clock-names: + description: |- + Input clock (12 to 27 MHz) + items: + - const: inck + + interrupts: + maxItems: 1 + + vana-supply: + description: Supply voltage (analog) - 2.7 V + + vdig-supply: + description: Supply voltage (digital) - 0.84 V + + vif-supply: + description: Supply voltage (interface) - 1.8 V + + reset-gpios: + description: |- + Sensor reset (XCLR) GPIO + + Chip clear in lieu of built-in power on reset. To be set 'High' after + power supplies are brought up and INCK supplied. + + port: + $ref: /schemas/graph.yaml#/$defs/port-base + additionalProperties: false + description: | + Video output port + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + type: object + unevaluatedProperties: false + properties: + data-lanes: + items: + - const: 2 + - const: 4 + clock-noncontinuous: true + link-frequencies: true + required: + - link-frequencies + - data-lanes + + spi: + $ref: /schemas/types.yaml#/definitions/phandle + description: |- + SPI peripheral + + Optional SPI peripheral for uploading firmware and network weights to AI + processor. + +required: + - compatible + - reg + - clocks + - clock-names + - vana-supply + - vdig-supply + - vif-supply + - port + +examples: + - | + #include <dt-bindings/gpio/gpio.h> + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + imx500: sensor@1a { + compatible = "sony,imx500"; + reg = <0x1a>; + + clocks = <&imx500_clk>; + clock-names = "inck"; + + vana-supply = <&imx500_vana>; /* 2.7 +/- 0.1 V */ + vdig-supply = <&imx500_vdig>; /* 0.84 +/- 0.04 V */ + vif-supply = <&imx500_vif>; /* 1.8 +/- 0.1 V */ + + reset-gpios = <&gpio_sensor 0 GPIO_ACTIVE_LOW>; + + port { + imx500_0: endpoint { + remote-endpoint = <&csi1_ep>; + data-lanes = <1 2>; + clock-noncontinuous; + link-frequencies = /bits/ 64 <499500000>; + }; + }; + }; + }; + +... + diff --git a/Documentation/devicetree/bindings/media/i2c/sony,imx708.yaml b/Documentation/devicetree/bindings/media/i2c/sony,imx708.yaml new file mode 100644 index 00000000000000..286aad2e8c697c --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/sony,imx708.yaml @@ -0,0 +1,128 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/sony,imx708.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Sony 1/2.3-Inch 12Mpixel CMOS Digital Image Sensor + +maintainers: + - Raspberry Pi Kernel Maintenance <kernel-list@raspberrypi.com> + +description: |- + The Sony IMX708 is a 1/2.3-inch CMOS active pixel digital image sensor + with an active array size of 4608H x 2592V. It is programmable through + I2C interface. The I2C address is fixed to 0x1A as per sensor data sheet. + Image data is sent through MIPI CSI-2, which is configured as either 2 or + 4 data lanes. + +properties: + compatible: + const: sony,imx708 + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + clock-names: + description: Input clock (6 to 27 MHz) + items: + - const: inck + + vdig-supply: + description: + Digital I/O voltage supply, 1.1 volts + + vana1-supply: + description: + Analog1 voltage supply, 2.8 volts + + vana2-supply: + description: + Analog2 voltage supply, 1.8 volts + + vddl-supply: + description: + Digital core voltage supply, 1.8 volts + + reset-gpios: + description: Sensor reset (XCLR) GPIO + maxItems: 1 + + port: + $ref: /schemas/graph.yaml#/$defs/port-base + description: | + Video output port + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + properties: + data-lanes: + anyOf: + - items: + - const: 1 + - const: 2 + - items: + - const: 1 + - const: 2 + - const: 3 + - const: 4 + + link-frequencies: true + + required: + - data-lanes + - link-frequencies + + additionalProperties: false + +required: + - compatible + - reg + - clocks + - clock-names + - vdig-supply + - vana1-supply + - vana2-supply + - vddl-supply + - port + +additionalProperties: false + +examples: + - | + #include <dt-bindings/gpio/gpio.h> + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + imx708: camera-sensor@1a { + compatible = "sony,imx708"; + reg = <0x1a>; + + clocks = <&clk 90>; + clock-names = "inck"; + + vdig-supply = <&camera_vdig>; + vana1-supply = <&camera_vana1>; + vana2-supply = <&camera_vana2>; + vddl-supply = <&camera_vddl>; + + reset-gpios = <&gpio 35 GPIO_ACTIVE_LOW>; + + port { + imx708_ep: endpoint { + data-lanes = <1 2>; + link-frequencies = /bits/ 64 <450000000>; + remote-endpoint = <&csi_ep>; + }; + }; + }; + }; +... diff --git a/Documentation/devicetree/bindings/media/rpivid_hevc.yaml b/Documentation/devicetree/bindings/media/rpivid_hevc.yaml new file mode 100644 index 00000000000000..ce6b81a103030e --- /dev/null +++ b/Documentation/devicetree/bindings/media/rpivid_hevc.yaml @@ -0,0 +1,72 @@ +# SPDX-License-Identifier: GPL-2.0-only +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/rpivid_hevc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Raspberry Pi HEVC Decoder + +maintainers: + - Raspberry Pi <kernel-list@raspberrypi.com> + +description: |- + The Camera Adaptation Layer (CAL) is a key component for image capture + applications. The capture module provides the system interface and the + processing capability to connect CSI2 image-sensor modules to the + DRA72x device. + +properties: + compatible: + enum: + - raspberrypi,rpivid-vid-decoder + + reg: + minItems: 2 + items: + - description: The HEVC main register region + - description: The Interrupt controller register region + + reg-names: + minItems: 2 + items: + - const: hevc + - const: intc + + interrupts: + maxItems: 1 + + clocks: + items: + - description: The HEVC block clock + + clock-names: + items: + - const: hevc + +required: + - compatible + - reg + - reg-names + - interrupts + - clocks + +additionalProperties: false + +examples: + - | + #include <dt-bindings/interrupt-controller/arm-gic.h> + + video-codec@7eb10000 { + compatible = "raspberrypi,rpivid-vid-decoder"; + reg = <0x0 0x7eb10000 0x1000>, /* INTC */ + <0x0 0x7eb00000 0x10000>; /* HEVC */ + reg-names = "intc", + "hevc"; + + interrupts = <GIC_SPI 98 IRQ_TYPE_LEVEL_HIGH>; + + clocks = <&clk 0>; + clock-names = "hevc"; + }; + +... diff --git a/Documentation/devicetree/bindings/misc/brcm,bcm2835-smi-dev.txt b/Documentation/devicetree/bindings/misc/brcm,bcm2835-smi-dev.txt new file mode 100644 index 00000000000000..68cc8ebc3392d4 --- /dev/null +++ b/Documentation/devicetree/bindings/misc/brcm,bcm2835-smi-dev.txt @@ -0,0 +1,17 @@ +* Broadcom BCM2835 SMI character device driver. + +SMI or secondary memory interface is a peripheral specific to certain Broadcom +SOCs, and is helpful for talking to things like parallel-interface displays +and NAND flashes (in fact, most things with a parallel register interface). + +This driver adds a character device which provides a user-space interface to +an instance of the SMI driver. + +Required properties: +- compatible: "brcm,bcm2835-smi-dev" +- smi_handle: a phandle to the smi node. + +Optional properties: +- None. + + diff --git a/Documentation/devicetree/bindings/misc/brcm,bcm2835-smi.txt b/Documentation/devicetree/bindings/misc/brcm,bcm2835-smi.txt new file mode 100644 index 00000000000000..b76dc694f1ac0b --- /dev/null +++ b/Documentation/devicetree/bindings/misc/brcm,bcm2835-smi.txt @@ -0,0 +1,48 @@ +* Broadcom BCM2835 SMI driver. + +SMI or secondary memory interface is a peripheral specific to certain Broadcom +SOCs, and is helpful for talking to things like parallel-interface displays +and NAND flashes (in fact, most things with a parallel register interface). + +Required properties: +- compatible: "brcm,bcm2835-smi" +- reg: Should contain location and length of SMI registers and SMI clkman regs +- interrupts: *the* SMI interrupt. +- pinctrl-names: should be "default". +- pinctrl-0: the phandle of the gpio pin node. +- brcm,smi-clock-source: the clock source for clkman +- brcm,smi-clock-divisor: the integer clock divisor for clkman +- dmas: the dma controller phandle and the DREQ number (4 on a 2835) +- dma-names: the name used by the driver to request its channel. + Should be "rx-tx". + +Optional properties: +- None. + +Examples: + +8 data pin configuration: + +smi: smi@7e600000 { + compatible = "brcm,bcm2835-smi"; + reg = <0x7e600000 0x44>, <0x7e1010b0 0x8>; + interrupts = <2 16>; + pinctrl-names = "default"; + pinctrl-0 = <&smi_pins>; + brcm,smi-clock-source = <6>; + brcm,smi-clock-divisor = <4>; + dmas = <&dma 4>; + dma-names = "rx-tx"; + + status = "okay"; +}; + +smi_pins: smi_pins { + brcm,pins = <2 3 4 5 6 7 8 9 10 11 12 13 14 15>; + /* Alt 1: SMI */ + brcm,function = <5 5 5 5 5 5 5 5 5 5 5 5 5 5>; + /* /CS, /WE and /OE are pulled high, as they are + generally active low signals */ + brcm,pull = <2 2 2 2 2 2 0 0 0 0 0 0 0 0>; +}; + diff --git a/Documentation/devicetree/bindings/mmc/snps,dwcmshc-sdhci.yaml b/Documentation/devicetree/bindings/mmc/snps,dwcmshc-sdhci.yaml index c3d5e0230af1a6..3be3a53ef79459 100644 --- a/Documentation/devicetree/bindings/mmc/snps,dwcmshc-sdhci.yaml +++ b/Documentation/devicetree/bindings/mmc/snps,dwcmshc-sdhci.yaml @@ -17,6 +17,7 @@ properties: - const: rockchip,rk3576-dwcmshc - const: rockchip,rk3588-dwcmshc - enum: + - raspberrypi,rp1-dwcmshc - rockchip,rk3568-dwcmshc - rockchip,rk3588-dwcmshc - snps,dwcmshc-sdhci @@ -95,6 +96,8 @@ allOf: - description: axi clock for rockchip specified - description: block clock for rockchip specified - description: timer clock for rockchip specified + - description: timeout clock for rp1 specified + - description: sdio clock generator for rp1 specified clock-names: minItems: 1 items: @@ -103,6 +106,8 @@ allOf: - const: axi - const: block - const: timer + - const: timeout + - const: sdio - if: properties: diff --git a/Documentation/devicetree/bindings/net/cdns,macb.yaml b/Documentation/devicetree/bindings/net/cdns,macb.yaml index 3c30dd23cd4efa..7c1dc7a7351af8 100644 --- a/Documentation/devicetree/bindings/net/cdns,macb.yaml +++ b/Documentation/devicetree/bindings/net/cdns,macb.yaml @@ -54,6 +54,7 @@ properties: - cdns,np4-macb # NP4 SoC devices - microchip,sama7g5-emac # Microchip SAMA7G5 ethernet interface - microchip,sama7g5-gem # Microchip SAMA7G5 gigabit ethernet interface + - raspberrypi,rp1-gem # Raspberry Pi RP1 gigabit ethernet interface - sifive,fu540-c000-gem # SiFive FU540-C000 SoC - cdns,emac # Generic - cdns,gem # Generic @@ -136,6 +137,22 @@ properties: Node containing PHY children. If this node is not present, then PHYs will be direct children. + cdns,aw2w-max-pipe: + $ref: /schemas/types.yaml#/definitions/uint32 + description: + Maximum number of outstanding AXI write requests + + cdns,ar2r-max-pipe: + $ref: /schemas/types.yaml#/definitions/uint32 + description: + Maximum number of outstanding AXI read requests + + cdns,use-aw2b-fill: + type: boolean + description: + If set, the maximum number of outstanding write transactions operates + between the AW to B AXI channel, instead of the AW to W AXI channel. + patternProperties: "^ethernet-phy@[0-9a-f]$": type: object diff --git a/Documentation/devicetree/bindings/net/microchip,lan78xx.txt b/Documentation/devicetree/bindings/net/microchip,lan78xx.txt index 11a679530ae65a..104768b85bbc57 100644 --- a/Documentation/devicetree/bindings/net/microchip,lan78xx.txt +++ b/Documentation/devicetree/bindings/net/microchip,lan78xx.txt @@ -14,6 +14,9 @@ Optional properties of the embedded PHY: - microchip,led-modes: a 0..4 element vector, with each element configuring the operating mode of an LED. Omitted LEDs are turned off. Allowed values are defined in "include/dt-bindings/net/microchip-lan78xx.h". +- microchip,downshift-after: sets the number of failed auto-negotiation + attempts after which the link is downgraded from 1000BASE-T. Should be one of + 2, 3, 4, 5 or 0, where 0 means never downshift. Example: diff --git a/Documentation/devicetree/bindings/pci/brcm,stb-pcie.yaml b/Documentation/devicetree/bindings/pci/brcm,stb-pcie.yaml index 0925c520195ae3..be9d8bc92e7643 100644 --- a/Documentation/devicetree/bindings/pci/brcm,stb-pcie.yaml +++ b/Documentation/devicetree/bindings/pci/brcm,stb-pcie.yaml @@ -104,6 +104,14 @@ properties: minItems: 1 maxItems: 3 + brcm,tperst-clk-ms: + category: optional + type: int + description: u32 giving the number of milliseconds to extend + the time between internal release of fundamental reset and + the deassertion of the external PERST# pin. This has the + effect of increasing the Tperst_clk phase of link init. + required: - compatible - reg diff --git a/Documentation/devicetree/bindings/pci/brcmstb-pcie.txt b/Documentation/devicetree/bindings/pci/brcmstb-pcie.txt new file mode 100644 index 00000000000000..a1a9ad5e70cabd --- /dev/null +++ b/Documentation/devicetree/bindings/pci/brcmstb-pcie.txt @@ -0,0 +1,59 @@ +Brcmstb PCIe Host Controller Device Tree Bindings + +Required Properties: +- compatible + "brcm,bcm7425-pcie" -- for 7425 family MIPS-based SOCs. + "brcm,bcm7435-pcie" -- for 7435 family MIPS-based SOCs. + "brcm,bcm7445-pcie" -- for 7445 and later ARM based SOCs (not including + the 7278). + "brcm,bcm7278-pcie" -- for 7278 family ARM-based SOCs. + +- reg -- the register start address and length for the PCIe reg block. +- interrupts -- two interrupts are specified; the first interrupt is for + the PCI host controller and the second is for MSI if the built-in + MSI controller is to be used. +- interrupt-names -- names of the interrupts (above): "pcie" and "msi". +- #address-cells -- set to <3>. +- #size-cells -- set to <2>. +- #interrupt-cells: set to <1>. +- interrupt-map-mask and interrupt-map, standard PCI properties to define the + mapping of the PCIe interface to interrupt numbers. +- ranges: ranges for the PCI memory and I/O regions. +- linux,pci-domain -- should be unique per host controller. + +Optional Properties: +- clocks -- phandle of pcie clock. +- clock-names -- set to "sw_pcie" if clocks is used. +- dma-ranges -- Specifies the inbound memory mapping regions when + an "identity map" is not possible. +- msi-controller -- this property is typically specified to have the + PCIe controller use its internal MSI controller. +- msi-parent -- set to use an external MSI interrupt controller. +- brcm,enable-ssc -- (boolean) indicates usage of spread-spectrum clocking. +- max-link-speed -- (integer) indicates desired generation of link: + 1 => 2.5 Gbps (gen1), 2 => 5.0 Gbps (gen2), 3 => 8.0 Gbps (gen3). + +Example Node: + +pcie0: pcie@f0460000 { + reg = <0x0 0xf0460000 0x0 0x9310>; + interrupts = <0x0 0x0 0x4>; + compatible = "brcm,bcm7445-pcie"; + #address-cells = <3>; + #size-cells = <2>; + ranges = <0x02000000 0x00000000 0x00000000 0x00000000 0xc0000000 0x00000000 0x08000000 + 0x02000000 0x00000000 0x08000000 0x00000000 0xc8000000 0x00000000 0x08000000>; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &intc 0 47 3 + 0 0 0 2 &intc 0 48 3 + 0 0 0 3 &intc 0 49 3 + 0 0 0 4 &intc 0 50 3>; + clocks = <&sw_pcie0>; + clock-names = "sw_pcie"; + msi-parent = <&pcie0>; /* use PCIe's internal MSI controller */ + msi-controller; /* use PCIe's internal MSI controller */ + brcm,ssc; + max-link-speed = <1>; + linux,pci-domain = <0>; + }; diff --git a/Documentation/devicetree/bindings/power/reset/gpio-poweroff.yaml b/Documentation/devicetree/bindings/power/reset/gpio-poweroff.yaml index a4b437fce37cf4..61df8d05e0bf6b 100644 --- a/Documentation/devicetree/bindings/power/reset/gpio-poweroff.yaml +++ b/Documentation/devicetree/bindings/power/reset/gpio-poweroff.yaml @@ -50,6 +50,10 @@ properties: default: 3000 description: Time to wait before assuming the power off sequence failed. + export: + type: boolean + description: Export the GPIO line to the sysfs system + required: - compatible - gpios diff --git a/Documentation/devicetree/bindings/pwm/pwm-rp1.yaml b/Documentation/devicetree/bindings/pwm/pwm-rp1.yaml new file mode 100644 index 00000000000000..db9d7085f1c3b5 --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/pwm-rp1.yaml @@ -0,0 +1,38 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/pwm/pwm-rp1.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Raspberry Pi RP1 PWM controller + +maintainers: + - Naushir Patuck <naush@raspberrypi.com> + +properties: + compatible: + enum: + - raspberrypi,rp1-pwm + + reg: + maxItems: 1 + + "#pwm-cells": + const: 3 + +required: + - compatible + - reg + - clocks + - "#pwm-cells" + +additionalProperties: false + +examples: + - | + pwm0: pwm@98000 { + compatible = "raspberrypi,rp1-pwm"; + reg = <0x0 0x98000 0x0 0x100>; + clocks = <&rp1_sys>; + #pwm-cells = <3>; + }; diff --git a/Documentation/devicetree/bindings/rtc/rtc-rpi.txt b/Documentation/devicetree/bindings/rtc/rtc-rpi.txt new file mode 100644 index 00000000000000..ed0d0d0a846490 --- /dev/null +++ b/Documentation/devicetree/bindings/rtc/rtc-rpi.txt @@ -0,0 +1,22 @@ +* Raspberry Pi RTC + +This is a Linux interface to an RTC managed by firmware, hence it's +virtual from a Linux perspective. + +The interface uses the firmware mailbox api to access the RTC registers. + +Required properties: +compatible: should be "raspberrypi,rpi-rtc" +firmware: Reference to the RPi firmware device node. + +Optional property: +trickle-charge-microvolt: specify a trickle charge voltage for the backup + battery in microvolts. + +Example: + + rpi_rtc: rpi_rtc { + compatible = "raspberrypi,rpi-rtc"; + firmware = <&firmware>; + trickle-charge-microvolt = <3000000>; + }; diff --git a/Documentation/devicetree/bindings/serial/pl011.yaml b/Documentation/devicetree/bindings/serial/pl011.yaml index 9571041030b78d..f34e2f66d1a375 100644 --- a/Documentation/devicetree/bindings/serial/pl011.yaml +++ b/Documentation/devicetree/bindings/serial/pl011.yaml @@ -101,6 +101,12 @@ properties: on the device. enum: [1, 4] + cts-event-workaround: + description: + Enables the (otherwise vendor-specific) workaround for the + CTS-induced TX lockup. + type: boolean + required: - compatible - reg diff --git a/Documentation/devicetree/bindings/sound/snps,designware-i2s.yaml b/Documentation/devicetree/bindings/sound/snps,designware-i2s.yaml index a48d040b0a4fbd..6e50851ff70f46 100644 --- a/Documentation/devicetree/bindings/sound/snps,designware-i2s.yaml +++ b/Documentation/devicetree/bindings/sound/snps,designware-i2s.yaml @@ -69,6 +69,10 @@ properties: - description: RX DMA Channel minItems: 1 + dma-maxburst: + description: FIFO DMA burst threshold limit + maxItems: 1 + dma-names: items: - const: tx diff --git a/Documentation/devicetree/bindings/sound/ti,pcm3168a.yaml b/Documentation/devicetree/bindings/sound/ti,pcm3168a.yaml index 0b4f003989a461..d8ffcdd3828167 100644 --- a/Documentation/devicetree/bindings/sound/ti,pcm3168a.yaml +++ b/Documentation/devicetree/bindings/sound/ti,pcm3168a.yaml @@ -58,6 +58,14 @@ properties: VCCDA2-supply: description: DAC power supply regulator 2 (+5V) + adc-force-cons: + description: Force ADC to operate in consumer mode. Useful if ADC and DAC + clock pins are tied together with DAC as producer. + + dac-force-cons: + description: Force DAC to operate in consumer mode. Useful if ADC and DAC + clock pins are tied together with ADC as producer. + ports: $ref: audio-graph-port.yaml#/definitions/port-base unevaluatedProperties: false diff --git a/Documentation/devicetree/bindings/spi/raspberrypi,rp2040-gpio-bridge.yaml b/Documentation/devicetree/bindings/spi/raspberrypi,rp2040-gpio-bridge.yaml new file mode 100644 index 00000000000000..d6af6db169d514 --- /dev/null +++ b/Documentation/devicetree/bindings/spi/raspberrypi,rp2040-gpio-bridge.yaml @@ -0,0 +1,77 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/spi/raspberrypi,rp2040-gpio-bridge.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Raspberry Pi RP2040 GPIO Bridge + +maintainers: + - Raspberry Pi <kernel-list@raspberrypi.com> + +description: |- + The Raspberry Pi PR2040 GPIO bridge can be used as a GPIO expander and + Tx-only SPI master. + +properties: + reg: + description: I2C slave address + const: 0x40 + + compatible: + const: raspberrypi,rp2040-gpio-bridge + + power-supply: + description: Phandle to the regulator that powers the RP2040. + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + + '#gpio-cells': + const: 2 + + gpio-controller: true + + fast_xfer_requires_i2c_lock: + description: Set if I2C bus should be locked during fast transfer. + + fast_xfer_recv_gpio_base: + description: RP2040 GPIO base for fast transfer pair. + + fast_xfer-gpios: + description: RP1 GPIOs to use for fast transfer clock and data. + +required: + - reg + - compatible + - power-supply + - '#gpio-cells' + - gpio-controller + +additionalProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + spi@40 { + reg = <0x40>; + compatible = "raspberrypi,rp2040-gpio-bridge"; + status = "disabled"; + #address-cells = <1>; + #size-cells = <0>; + + power-supply = <&cam_dummy_reg>; + + #gpio-cells = <2>; + gpio-controller; + }; + }; + +... + diff --git a/Documentation/devicetree/bindings/spi/spi-gpio.yaml b/Documentation/devicetree/bindings/spi/spi-gpio.yaml index 9ce1df93d4c30d..d911c203fa4598 100644 --- a/Documentation/devicetree/bindings/spi/spi-gpio.yaml +++ b/Documentation/devicetree/bindings/spi/spi-gpio.yaml @@ -43,6 +43,10 @@ properties: with no chip select is connected. $ref: /schemas/types.yaml#/definitions/uint32 + sck-idle-input: + description: Make SCK an input when inactive. + type: boolean + # Deprecated properties gpio-sck: false gpio-miso: false diff --git a/Documentation/devicetree/bindings/usb/snps,dwc3.yaml b/Documentation/devicetree/bindings/usb/snps,dwc3.yaml index 1cd0ca90127d91..272896bc233c26 100644 --- a/Documentation/devicetree/bindings/usb/snps,dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/snps,dwc3.yaml @@ -232,14 +232,29 @@ properties: description: When set, disable u2mac linestate check during HS transmit type: boolean + snps,enhanced-nak-fs-quirk: + description: + When set, the controller schedules many more handshakes to Async FS + endpoints, improving throughput when they frequently respond with NAKs. + + snps,enhanced-nak-hs-quirk: + description: + When set, the controller schedules many more handshakes to Async HS + endpoints, improving throughput when they frequently respond with NAKs. + snps,parkmode-disable-ss-quirk: description: - When set, all SuperSpeed bus instances in park mode are disabled. + When set, disable park mode for all Superspeed bus instances. type: boolean snps,parkmode-disable-hs-quirk: description: - When set, all HighSpeed bus instances in park mode are disabled. + When set, disable park mode for all Highspeed bus instances. + type: boolean + + snps,parkmode-disable-fsls-quirk: + description: + When set, disable park mode for all Full/Lowspeed bus instances. type: boolean snps,dis_metastability_quirk: diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt new file mode 100644 index 00000000000000..f8d32547195b3a --- /dev/null +++ b/Documentation/devicetree/bindings/vendor-prefixes.txt @@ -0,0 +1,463 @@ +Device tree binding vendor prefix registry. Keep list in alphabetical order. + +This isn't an exhaustive list, but you should add new prefixes to it before +using them to avoid name-space collisions. + +abilis Abilis Systems +abracon Abracon Corporation +actions Actions Semiconductor Co., Ltd. +active-semi Active-Semi International Inc +ad Avionic Design GmbH +adafruit Adafruit Industries, LLC +adapteva Adapteva, Inc. +adaptrum Adaptrum, Inc. +adh AD Holdings Plc. +adi Analog Devices, Inc. +advantech Advantech Corporation +aeroflexgaisler Aeroflex Gaisler AB +al Annapurna Labs +allo Allo.com +allwinner Allwinner Technology Co., Ltd. +alphascale AlphaScale Integrated Circuits Systems, Inc. +altr Altera Corp. +amarula Amarula Solutions +amazon Amazon.com, Inc. +amcc Applied Micro Circuits Corporation (APM, formally AMCC) +amd Advanced Micro Devices (AMD), Inc. +amediatech Shenzhen Amediatech Technology Co., Ltd +amlogic Amlogic, Inc. +ampire Ampire Co., Ltd. +ams AMS AG +amstaos AMS-Taos Inc. +analogix Analogix Semiconductor, Inc. +andestech Andes Technology Corporation +apm Applied Micro Circuits Corporation (APM) +aptina Aptina Imaging +arasan Arasan Chip Systems +archermind ArcherMind Technology (Nanjing) Co., Ltd. +arctic Arctic Sand +aries Aries Embedded GmbH +arm ARM Ltd. +armadeus ARMadeus Systems SARL +arrow Arrow Electronics +artesyn Artesyn Embedded Technologies Inc. +asahi-kasei Asahi Kasei Corp. +aspeed ASPEED Technology Inc. +asus AsusTek Computer Inc. +atlas Atlas Scientific LLC +atmel Atmel Corporation +auo AU Optronics Corporation +auvidea Auvidea GmbH +avago Avago Technologies +avia avia semiconductor +avic Shanghai AVIC Optoelectronics Co., Ltd. +avnet Avnet, Inc. +axentia Axentia Technologies AB +axis Axis Communications AB +bananapi BIPAI KEJI LIMITED +bhf Beckhoff Automation GmbH & Co. KG +bitmain Bitmain Technologies +blokaslabs Vilniaus Blokas UAB +boe BOE Technology Group Co., Ltd. +bosch Bosch Sensortec GmbH +boundary Boundary Devices Inc. +brcm Broadcom Corporation +buffalo Buffalo, Inc. +bticino Bticino International +calxeda Calxeda +capella Capella Microsystems, Inc +cascoda Cascoda, Ltd. +catalyst Catalyst Semiconductor, Inc. +cavium Cavium, Inc. +cdns Cadence Design Systems Inc. +cdtech CDTech(H.K.) Electronics Limited +ceva Ceva, Inc. +chipidea Chipidea, Inc +chipone ChipOne +chipspark ChipSPARK +chrp Common Hardware Reference Platform +chunghwa Chunghwa Picture Tubes Ltd. +ciaa Computadora Industrial Abierta Argentina +cirrus Cirrus Logic, Inc. +cloudengines Cloud Engines, Inc. +cnm Chips&Media, Inc. +cnxt Conexant Systems, Inc. +compulab CompuLab Ltd. +cortina Cortina Systems, Inc. +cosmic Cosmic Circuits +crane Crane Connectivity Solutions +creative Creative Technology Ltd +crystalfontz Crystalfontz America, Inc. +csky Hangzhou C-SKY Microsystems Co., Ltd +cubietech Cubietech, Ltd. +cypress Cypress Semiconductor Corporation +cznic CZ.NIC, z.s.p.o. +dallas Maxim Integrated Products (formerly Dallas Semiconductor) +dataimage DataImage, Inc. +davicom DAVICOM Semiconductor, Inc. +delta Delta Electronics, Inc. +denx Denx Software Engineering +devantech Devantech, Ltd. +dh DH electronics GmbH +digi Digi International Inc. +digilent Diglent, Inc. +dioo Dioo Microcircuit Co., Ltd +dlc DLC Display Co., Ltd. +dlg Dialog Semiconductor +dlink D-Link Corporation +dmo Data Modul AG +domintech Domintech Co., Ltd. +dongwoon Dongwoon Anatech +dptechnics DPTechnics +dragino Dragino Technology Co., Limited +ea Embedded Artists AB +ebs-systart EBS-SYSTART GmbH +ebv EBV Elektronik +eckelmann Eckelmann AG +edt Emerging Display Technologies +eeti eGalax_eMPIA Technology Inc +elan Elan Microelectronic Corp. +elgin Elgin S/A. +embest Shenzhen Embest Technology Co., Ltd. +emlid Emlid, Ltd. +emmicro EM Microelectronic +emtrion emtrion GmbH +endless Endless Mobile, Inc. +energymicro Silicon Laboratories (formerly Energy Micro AS) +engicam Engicam S.r.l. +epcos EPCOS AG +epfl Ecole Polytechnique Fédérale de Lausanne +epson Seiko Epson Corp. +est ESTeem Wireless Modems +ettus NI Ettus Research +eukrea Eukréa Electromatique +everest Everest Semiconductor Co. Ltd. +everspin Everspin Technologies, Inc. +exar Exar Corporation +excito Excito +ezchip EZchip Semiconductor +facebook Facebook +fairphone Fairphone B.V. +faraday Faraday Technology Corporation +fastrax Fastrax Oy +fcs Fairchild Semiconductor +feiyang Shenzhen Fly Young Technology Co.,LTD. +firefly Firefly +focaltech FocalTech Systems Co.,Ltd +friendlyarm Guangzhou FriendlyARM Computer Tech Co., Ltd +fsl Freescale Semiconductor +fujitsu Fujitsu Ltd. +gateworks Gateworks Corporation +gcw Game Consoles Worldwide +ge General Electric Company +geekbuying GeekBuying +gef GE Fanuc Intelligent Platforms Embedded Systems, Inc. +GEFanuc GE Fanuc Intelligent Platforms Embedded Systems, Inc. +geniatech Geniatech, Inc. +giantec Giantec Semiconductor, Inc. +giantplus Giantplus Technology Co., Ltd. +globalscale Globalscale Technologies, Inc. +globaltop GlobalTop Technology, Inc. +gmt Global Mixed-mode Technology, Inc. +goodix Shenzhen Huiding Technology Co., Ltd. +google Google, Inc. +grinn Grinn +grmn Garmin Limited +gumstix Gumstix, Inc. +gw Gateworks Corporation +hannstar HannStar Display Corporation +haoyu Haoyu Microelectronic Co. Ltd. +hardkernel Hardkernel Co., Ltd +hideep HiDeep Inc. +himax Himax Technologies, Inc. +hisilicon Hisilicon Limited. +hit Hitachi Ltd. +hitex Hitex Development Tools +holt Holt Integrated Circuits, Inc. +honeywell Honeywell +hp Hewlett Packard +holtek Holtek Semiconductor, Inc. +hwacom HwaCom Systems Inc. +i2se I2SE GmbH +ibm International Business Machines (IBM) +icplus IC Plus Corp. +idt Integrated Device Technologies, Inc. +ifi Ingenieurburo Fur Ic-Technologie (I/F/I) +ilitek ILI Technology Corporation (ILITEK) +img Imagination Technologies Ltd. +infineon Infineon Technologies +inforce Inforce Computing +ingenic Ingenic Semiconductor +innolux Innolux Corporation +inside-secure INSIDE Secure +intel Intel Corporation +intercontrol Inter Control Group +invensense InvenSense Inc. +inversepath Inverse Path +iom Iomega Corporation +isee ISEE 2007 S.L. +isil Intersil +issi Integrated Silicon Solutions Inc. +itead ITEAD Intelligent Systems Co.Ltd +iwave iWave Systems Technologies Pvt. Ltd. +jdi Japan Display Inc. +jedec JEDEC Solid State Technology Association +jianda Jiandangjing Technology Co., Ltd. +karo Ka-Ro electronics GmbH +keithkoep Keith & Koep GmbH +keymile Keymile GmbH +khadas Khadas +kiebackpeter Kieback & Peter GmbH +kinetic Kinetic Technologies +kingdisplay King & Display Technology Co., Ltd. +kingnovel Kingnovel Technology Co., Ltd. +koe Kaohsiung Opto-Electronics Inc. +kosagi Sutajio Ko-Usagi PTE Ltd. +kyo Kyocera Corporation +lacie LaCie +laird Laird PLC +lantiq Lantiq Semiconductor +lattice Lattice Semiconductor +lego LEGO Systems A/S +lemaker Shenzhen LeMaker Technology Co., Ltd. +lenovo Lenovo Group Ltd. +lg LG Corporation +libretech Shenzhen Libre Technology Co., Ltd +licheepi Lichee Pi +linaro Linaro Limited +linksys Belkin International, Inc. (Linksys) +linux Linux-specific binding +linx Linx Technologies +lltc Linear Technology Corporation +logicpd Logic PD, Inc. +lsi LSI Corp. (LSI Logic) +lwn Liebherr-Werk Nenzing GmbH +macnica Macnica Americas +marvell Marvell Technology Group Ltd. +maxim Maxim Integrated Products +mbvl Mobiveil Inc. +mcube mCube +meas Measurement Specialties +mediatek MediaTek Inc. +megachips MegaChips +mele Shenzhen MeLE Digital Technology Ltd. +melexis Melexis N.V. +melfas MELFAS Inc. +mellanox Mellanox Technologies +memsic MEMSIC Inc. +merrii Merrii Technology Co., Ltd. +micrel Micrel Inc. +microchip Microchip Technology Inc. +microcrystal Micro Crystal AG +micron Micron Technology Inc. +mikroe MikroElektronika d.o.o. +minix MINIX Technology Ltd. +miramems MiraMEMS Sensing Technology Co., Ltd. +mitsubishi Mitsubishi Electric Corporation +mosaixtech Mosaix Technologies, Inc. +motorola Motorola, Inc. +moxa Moxa Inc. +mpl MPL AG +mqmaker mqmaker Inc. +mscc Microsemi Corporation +msi Micro-Star International Co. Ltd. +mti Imagination Technologies Ltd. (formerly MIPS Technologies Inc.) +multi-inno Multi-Inno Technology Co.,Ltd +mundoreader Mundo Reader S.L. +murata Murata Manufacturing Co., Ltd. +mxicy Macronix International Co., Ltd. +myir MYIR Tech Limited +national National Semiconductor +nec NEC LCD Technologies, Ltd. +neonode Neonode Inc. +netgear NETGEAR +netlogic Broadcom Corporation (formerly NetLogic Microsystems) +netron-dy Netron DY +netxeon Shenzhen Netxeon Technology CO., LTD +nexbox Nexbox +nextthing Next Thing Co. +newhaven Newhaven Display International +ni National Instruments +nintendo Nintendo +nlt NLT Technologies, Ltd. +nokia Nokia +nordic Nordic Semiconductor +novtech NovTech, Inc. +nutsboard NutsBoard +nuvoton Nuvoton Technology Corporation +nvd New Vision Display +nvidia NVIDIA +nxp NXP Semiconductors +okaya Okaya Electric America, Inc. +oki Oki Electric Industry Co., Ltd. +olimex OLIMEX Ltd. +olpc One Laptop Per Child +onion Onion Corporation +onnn ON Semiconductor Corp. +ontat On Tat Industrial Company +opalkelly Opal Kelly Incorporated +opencores OpenCores.org +openrisc OpenRISC.io +option Option NV +oranth Shenzhen Oranth Technology Co., Ltd. +ORCL Oracle Corporation +orisetech Orise Technology +ortustech Ortus Technology Co., Ltd. +ovti OmniVision Technologies +oxsemi Oxford Semiconductor, Ltd. +panasonic Panasonic Corporation +parade Parade Technologies Inc. +pda Precision Design Associates, Inc. +pericom Pericom Technology Inc. +pervasive Pervasive Displays, Inc. +phicomm PHICOMM Co., Ltd. +phytec PHYTEC Messtechnik GmbH +picochip Picochip Ltd +pine64 Pine64 +pixcir PIXCIR MICROELECTRONICS Co., Ltd +plantower Plantower Co., Ltd +plathome Plat'Home Co., Ltd. +plda PLDA +plx Broadcom Corporation (formerly PLX Technology) +pni PNI Sensor Corporation +portwell Portwell Inc. +poslab Poslab Technology Co., Ltd. +powervr PowerVR (deprecated, use img) +probox2 PROBOX2 (by W2COMP Co., Ltd.) +pulsedlight PulsedLight, Inc +qca Qualcomm Atheros, Inc. +qcom Qualcomm Technologies, Inc +qemu QEMU, a generic and open source machine emulator and virtualizer +qi Qi Hardware +qiaodian QiaoDian XianShi Corporation +qnap QNAP Systems, Inc. +radxa Radxa +raidsonic RaidSonic Technology GmbH +ralink Mediatek/Ralink Technology Corp. +ramtron Ramtron International +raspberrypi Raspberry Pi Foundation +raydium Raydium Semiconductor Corp. +rda Unisoc Communications, Inc. +realtek Realtek Semiconductor Corp. +renesas Renesas Electronics Corporation +richtek Richtek Technology Corporation +ricoh Ricoh Co. Ltd. +rikomagic Rikomagic Tech Corp. Ltd +riscv RISC-V Foundation +rockchip Fuzhou Rockchip Electronics Co., Ltd +rohm ROHM Semiconductor Co., Ltd +roofull Shenzhen Roofull Technology Co, Ltd +samsung Samsung Semiconductor +samtec Samtec/Softing company +sancloud Sancloud Ltd +sandisk Sandisk Corporation +sbs Smart Battery System +schindler Schindler +seagate Seagate Technology PLC +semtech Semtech Corporation +sensirion Sensirion AG +sff Small Form Factor Committee +sgd Solomon Goldentek Display Corporation +sgx SGX Sensortech +sharp Sharp Corporation +shimafuji Shimafuji Electric, Inc. +si-en Si-En Technology Ltd. +sifive SiFive, Inc. +sigma Sigma Designs, Inc. +sii Seiko Instruments, Inc. +sil Silicon Image +silabs Silicon Laboratories +silead Silead Inc. +silergy Silergy Corp. +siliconmitus Silicon Mitus, Inc. +simtek +sirf SiRF Technology, Inc. +sis Silicon Integrated Systems Corp. +sitronix Sitronix Technology Corporation +skyworks Skyworks Solutions, Inc. +smsc Standard Microsystems Corporation +snps Synopsys, Inc. +socionext Socionext Inc. +solidrun SolidRun +solomon Solomon Systech Limited +sony Sony Corporation +spansion Spansion Inc. +sprd Spreadtrum Communications Inc. +sst Silicon Storage Technology, Inc. +st STMicroelectronics +starry Starry Electronic Technology (ShenZhen) Co., LTD +startek Startek +ste ST-Ericsson +stericsson ST-Ericsson +summit Summit microelectronics +sunchip Shenzhen Sunchip Technology Co., Ltd +SUNW Sun Microsystems, Inc +swir Sierra Wireless +syna Synaptics Inc. +synology Synology, Inc. +tbs TBS Technologies +tbs-biometrics Touchless Biometric Systems AG +tcg Trusted Computing Group +tcl Toby Churchill Ltd. +technexion TechNexion +technologic Technologic Systems +tempo Tempo Semiconductor +techstar Shenzhen Techstar Electronics Co., Ltd. +terasic Terasic Inc. +thine THine Electronics, Inc. +ti Texas Instruments +tianma Tianma Micro-electronics Co., Ltd. +tlm Trusted Logic Mobility +tmt Tecon Microprocessor Technologies, LLC. +topeet Topeet +toradex Toradex AG +toshiba Toshiba Corporation +toumaz Toumaz +tpk TPK U.S.A. LLC +tplink TP-LINK Technologies Co., Ltd. +tpo TPO +tronfy Tronfy +tronsmart Tronsmart +truly Truly Semiconductors Limited +tsd Theobroma Systems Design und Consulting GmbH +tyan Tyan Computer Corporation +u-blox u-blox +ucrobotics uCRobotics +ubnt Ubiquiti Networks +udoo Udoo +uniwest United Western Technologies Corp (UniWest) +upisemi uPI Semiconductor Corp. +urt United Radiant Technology Corporation +usi Universal Scientific Industrial Co., Ltd. +v3 V3 Semiconductor +vamrs Vamrs Ltd. +variscite Variscite Ltd. +via VIA Technologies, Inc. +virtio Virtual I/O Device Specification, developed by the OASIS consortium +vishay Vishay Intertechnology, Inc +vitesse Vitesse Semiconductor Corporation +vivante Vivante Corporation +vocore VoCore Studio +voipac Voipac Technologies s.r.o. +vot Vision Optical Technology Co., Ltd. +wd Western Digital Corp. +wetek WeTek Electronics, limited. +wexler Wexler +whwave Shenzhen whwave Electronics, Inc. +wi2wi Wi2Wi, Inc. +winbond Winbond Electronics corp. +winstar Winstar Display Corp. +wlf Wolfson Microelectronics +wm Wondermedia Technologies, Inc. +x-powers X-Powers +xes Extreme Engineering Solutions (X-ES) +xillybus Xillybus Ltd. +xlnx Xilinx +xunlong Shenzhen Xunlong Software CO.,Limited +ysoft Y Soft Corporation a.s. +zarlink Zarlink Semiconductor +zeitec ZEITEC Semiconductor Co., LTD. +zidoo Shenzhen Zidoo Technology Co., Ltd. +zii Zodiac Inflight Innovations +zte ZTE Corp. +zyxel ZyXEL Communications Corp. diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml index fbfce9b4ae6b8e..c9d93e698d39d8 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -147,6 +147,8 @@ patternProperties: description: arcx Inc. / Archronix Inc. "^aries,.*": description: Aries Embedded GmbH + "^arducam,.*": + description: Arducam Technology co., Ltd. "^arm,.*": description: ARM Ltd. "^armadeus,.*": @@ -216,6 +218,8 @@ patternProperties: description: Shenzhen BigTree Tech Co., LTD "^bitmain,.*": description: Bitmain Technologies + "^blokaslabs,.*": + description: Vilniaus Blokas UAB "^blutek,.*": description: BluTek Power "^boe,.*": @@ -557,6 +561,8 @@ patternProperties: description: General Electric Company "^geekbuying,.*": description: GeekBuying + "^geekworm,.*": + description: Geekworm "^gef,.*": description: GE Fanuc Intelligent Platforms Embedded Systems, Inc. "^GEFanuc,.*": diff --git a/Documentation/devicetree/configfs-overlays.txt b/Documentation/devicetree/configfs-overlays.txt new file mode 100644 index 00000000000000..5fa43e0643072c --- /dev/null +++ b/Documentation/devicetree/configfs-overlays.txt @@ -0,0 +1,31 @@ +Howto use the configfs overlay interface. + +A device-tree configfs entry is created in /config/device-tree/overlays +and and it is manipulated using standard file system I/O. +Note that this is a debug level interface, for use by developers and +not necessarily something accessed by normal users due to the +security implications of having direct access to the kernel's device tree. + +* To create an overlay you mkdir the directory: + + # mkdir /config/device-tree/overlays/foo + +* Either you echo the overlay firmware file to the path property file. + + # echo foo.dtbo >/config/device-tree/overlays/foo/path + +* Or you cat the contents of the overlay to the dtbo file + + # cat foo.dtbo >/config/device-tree/overlays/foo/dtbo + +The overlay file will be applied, and devices will be created/destroyed +as required. + +To remove it simply rmdir the directory. + + # rmdir /config/device-tree/overlays/foo + +The rationalle of the dual interface (firmware & direct copy) is that each is +better suited to different use patterns. The firmware interface is what's +intended to be used by hardware managers in the kernel, while the copy interface +make sense for developers (since it avoids problems with namespaces). diff --git a/Documentation/userspace-api/media/drivers/index.rst b/Documentation/userspace-api/media/drivers/index.rst index d706cb47b1122b..65bcd99cb96f5d 100644 --- a/Documentation/userspace-api/media/drivers/index.rst +++ b/Documentation/userspace-api/media/drivers/index.rst @@ -32,6 +32,7 @@ For more details see the file COPYING in the source distribution of Linux. cx2341x-uapi dw100 imx-uapi + bcm2835-isp max2175 npcm-video omap3isp-uapi diff --git a/Documentation/userspace-api/media/v4l/meta-formats.rst b/Documentation/userspace-api/media/v4l/meta-formats.rst index c6e56b5888bc9d..69e09857d55ea9 100644 --- a/Documentation/userspace-api/media/v4l/meta-formats.rst +++ b/Documentation/userspace-api/media/v4l/meta-formats.rst @@ -12,11 +12,13 @@ These formats are used for the :ref:`metadata` interface only. .. toctree:: :maxdepth: 1 + metafmt-bcm2835-isp-stats metafmt-d4xx metafmt-generic metafmt-intel-ipu3 metafmt-pisp-be metafmt-rkisp1 + metafmt-sensor-data metafmt-uvc metafmt-vivid metafmt-vsp1-hgo diff --git a/Documentation/userspace-api/media/v4l/pixfmt-meta-bcm2835-isp-stats.rst b/Documentation/userspace-api/media/v4l/pixfmt-meta-bcm2835-isp-stats.rst new file mode 100644 index 00000000000000..f974774c825277 --- /dev/null +++ b/Documentation/userspace-api/media/v4l/pixfmt-meta-bcm2835-isp-stats.rst @@ -0,0 +1,41 @@ +.. Permission is granted to copy, distribute and/or modify this +.. document under the terms of the GNU Free Documentation License, +.. Version 1.1 or any later version published by the Free Software +.. Foundation, with no Invariant Sections, no Front-Cover Texts +.. and no Back-Cover Texts. A copy of the license is included at +.. Documentation/media/uapi/fdl-appendix.rst. +.. +.. TODO: replace it to GFDL-1.1-or-later WITH no-invariant-sections + +.. _v4l2-meta-fmt-bcm2835-isp-stats: + +***************************************** +V4L2_META_FMT_BCM2835_ISP_STATS ('BSTA') +***************************************** + +BCM2835 ISP Statistics + +Description +=========== + +The BCM2835 ISP hardware calculate image statistics for an input Bayer frame. +These statistics are obtained from the "bcm2835-isp0-capture3" device node +using the :c:type:`v4l2_meta_format` interface. They are formatted as described +by the :c:type:`bcm2835_isp_stats` structure below. + +.. code-block:: c + + #define DEFAULT_AWB_REGIONS_X 16 + #define DEFAULT_AWB_REGIONS_Y 12 + + #define NUM_HISTOGRAMS 2 + #define NUM_HISTOGRAM_BINS 128 + #define AWB_REGIONS (DEFAULT_AWB_REGIONS_X * DEFAULT_AWB_REGIONS_Y) + #define FLOATING_REGIONS 16 + #define AGC_REGIONS 16 + #define FOCUS_REGIONS 12 + +.. kernel-doc:: include/uapi/linux/bcm2835-isp.h + :functions: bcm2835_isp_stats_hist bcm2835_isp_stats_region + bcm2835_isp_stats_focus bcm2835_isp_stats + diff --git a/Documentation/userspace-api/media/v4l/pixfmt-meta-sensor-data.rst b/Documentation/userspace-api/media/v4l/pixfmt-meta-sensor-data.rst new file mode 100644 index 00000000000000..4a67e204d08a30 --- /dev/null +++ b/Documentation/userspace-api/media/v4l/pixfmt-meta-sensor-data.rst @@ -0,0 +1,32 @@ +.. Permission is granted to copy, distribute and/or modify this +.. document under the terms of the GNU Free Documentation License, +.. Version 1.1 or any later version published by the Free Software +.. Foundation, with no Invariant Sections, no Front-Cover Texts +.. and no Back-Cover Texts. A copy of the license is included at +.. Documentation/media/uapi/fdl-appendix.rst. +.. +.. TODO: replace it to GFDL-1.1-or-later WITH no-invariant-sections + +.. _v4l2-meta-fmt-sensor-data: + +*********************************** +V4L2_META_FMT_SENSOR_DATA ('SENS') +*********************************** + +Sensor Ancillary Metadata + +Description +=========== + +This format describes ancillary data generated by a camera sensor and +transmitted over a stream on the camera bus. Sensor vendors generally have their +own custom format for this ancillary data. Some vendors follow a generic +CSI-2/SMIA embedded data format as described in the `CSI-2 specification. +<https://mipi.org/specifications/csi-2>`_ + +The size of the embedded buffer is defined as a single line with a pixel width +width specified in bytes. This is obtained by a call to the +:c:type:`VIDIOC_SUBDEV_G_FMT` ioctl on the sensor subdevice where the ``pad`` +field in :c:type:`v4l2_subdev_format` is set to 1. Note that this size is fixed +and cannot be modified with a call to :c:type:`VIDIOC_SUBDEV_S_FMT`. + diff --git a/Documentation/userspace-api/media/v4l/pixfmt-nv12-col128.rst b/Documentation/userspace-api/media/v4l/pixfmt-nv12-col128.rst new file mode 100644 index 00000000000000..196ca33a5dff85 --- /dev/null +++ b/Documentation/userspace-api/media/v4l/pixfmt-nv12-col128.rst @@ -0,0 +1,215 @@ +.. Permission is granted to copy, distribute and/or modify this +.. document under the terms of the GNU Free Documentation License, +.. Version 1.1 or any later version published by the Free Software +.. Foundation, with no Invariant Sections, no Front-Cover Texts +.. and no Back-Cover Texts. A copy of the license is included at +.. Documentation/media/uapi/fdl-appendix.rst. +.. +.. TODO: replace it to GFDL-1.1-or-later WITH no-invariant-sections + +.. _V4L2_PIX_FMT_NV12_COL128: +.. _V4L2_PIX_FMT_NV12_10_COL128: + +******************************************************************************** +V4L2_PIX_FMT_NV12_COL128, V4L2_PIX_FMT_NV12_10_COL128 +******************************************************************************** + + +V4L2_PIX_FMT_NV21_COL128 +Formats with ½ horizontal and vertical chroma resolution. This format +has two planes - one for luminance and one for chrominance. Chroma +samples are interleaved. The difference to ``V4L2_PIX_FMT_NV12`` is the +memory layout. The image is split into columns of 128 bytes wide rather than +being in raster order. + +V4L2_PIX_FMT_NV12_10_COL128 +Follows the same pattern as ``V4L2_PIX_FMT_NV21_COL128`` with 128 byte, but is +a 10bit format with 3 10-bit samples being packed into 4 bytes. Each 128 byte +wide column therefore contains 96 samples. + + +Description +=========== + +This is the two-plane versions of the YUV 4:2:0 format where data is +grouped into 128 byte wide columns. The three components are separated into +two sub-images or planes. The Y plane has one byte per pixel and pixels +are grouped into 128 byte wide columns. The CbCr plane has the same width, +in bytes, as the Y plane (and the image), but is half as tall in pixels. +The chroma plane is also in 128 byte columns, reflecting 64 Cb and 64 Cr +samples. + +The chroma samples for a column follow the luma samples. If there is any +paddding, then that will be reflected via the selection API. +The luma height must be a multiple of 2 lines. + +The normal bytesperline is effectively fixed at 128. However the format +requires knowledge of the stride between columns, therefore the bytesperline +value has been repurposed to denote the number of 128 byte long lines between +the start of each column. + +**Byte Order.** + + +.. flat-table:: + :header-rows: 0 + :stub-columns: 0 + :widths: 12 12 12 12 12 4 12 12 12 12 + + * - start + 0: + - Y'\ :sub:`0,0` + - Y'\ :sub:`0,1` + - Y'\ :sub:`0,2` + - Y'\ :sub:`0,3` + - ... + - Y'\ :sub:`0,124` + - Y'\ :sub:`0,125` + - Y'\ :sub:`0,126` + - Y'\ :sub:`0,127` + * - start + 128: + - Y'\ :sub:`1,0` + - Y'\ :sub:`1,1` + - Y'\ :sub:`1,2` + - Y'\ :sub:`1,3` + - ... + - Y'\ :sub:`1,124` + - Y'\ :sub:`1,125` + - Y'\ :sub:`1,126` + - Y'\ :sub:`1,127` + * - start + 256: + - Y'\ :sub:`2,0` + - Y'\ :sub:`2,1` + - Y'\ :sub:`2,2` + - Y'\ :sub:`2,3` + - ... + - Y'\ :sub:`2,124` + - Y'\ :sub:`2,125` + - Y'\ :sub:`2,126` + - Y'\ :sub:`2,127` + * - ... + - ... + - ... + - ... + - ... + - ... + - ... + - ... + * - start + ((height-1) * 128): + - Y'\ :sub:`height-1,0` + - Y'\ :sub:`height-1,1` + - Y'\ :sub:`height-1,2` + - Y'\ :sub:`height-1,3` + - ... + - Y'\ :sub:`height-1,124` + - Y'\ :sub:`height-1,125` + - Y'\ :sub:`height-1,126` + - Y'\ :sub:`height-1,127` + * - start + ((height) * 128): + - Cb\ :sub:`0,0` + - Cr\ :sub:`0,0` + - Cb\ :sub:`0,1` + - Cr\ :sub:`0,1` + - ... + - Cb\ :sub:`0,62` + - Cr\ :sub:`0,62` + - Cb\ :sub:`0,63` + - Cr\ :sub:`0,63` + * - start + ((height+1) * 128): + - Cb\ :sub:`1,0` + - Cr\ :sub:`1,0` + - Cb\ :sub:`1,1` + - Cr\ :sub:`1,1` + - ... + - Cb\ :sub:`1,62` + - Cr\ :sub:`1,62` + - Cb\ :sub:`1,63` + - Cr\ :sub:`1,63` + * - ... + - ... + - ... + - ... + - ... + - ... + - ... + - ... + * - start + ((height+(height/2)-1) * 128): + - Cb\ :sub:`(height/2)-1,0` + - Cr\ :sub:`(height/2)-1,0` + - Cb\ :sub:`(height/2)-1,1` + - Cr\ :sub:`(height/2)-1,1` + - ... + - Cb\ :sub:`(height/2)-1,62` + - Cr\ :sub:`(height/2)-1,62` + - Cb\ :sub:`(height/2)-1,63` + - Cr\ :sub:`(height/2)-1,63` + * - start + (bytesperline * 128): + - Y'\ :sub:`0,128` + - Y'\ :sub:`0,129` + - Y'\ :sub:`0,130` + - Y'\ :sub:`0,131` + - ... + - Y'\ :sub:`0,252` + - Y'\ :sub:`0,253` + - Y'\ :sub:`0,254` + - Y'\ :sub:`0,255` + * - ... + - ... + - ... + - ... + - ... + - ... + - ... + - ... + +V4L2_PIX_FMT_NV12_10_COL128 uses the same 128 byte column structure, but +encodes 10-bit YUV. +3 10-bit values are packed into 4 bytes as bits 9:0, 19:10, and 29:20, with +bits 30 & 31 unused. For the luma plane, bits 9:0 are Y0, 19:10 are Y1, and +29:20 are Y2. For the chroma plane the samples always come in pairs of Cr +and Cb, so it needs to be considered 6 values packed in 8 bytes. + +Bit-packed representation. + +.. raw:: latex + + \small + +.. tabularcolumns:: |p{1.2cm}||p{1.2cm}||p{1.2cm}||p{1.2cm}|p{3.2cm}|p{3.2cm}| + +.. flat-table:: + :header-rows: 0 + :stub-columns: 0 + :widths: 8 8 8 8 + + * - Y'\ :sub:`00[7:0]` + - Y'\ :sub:`01[5:0] (bits 7--2)` Y'\ :sub:`00[9:8]`\ (bits 1--0) + - Y'\ :sub:`02[3:0] (bits 7--4)` Y'\ :sub:`01[9:6]`\ (bits 3--0) + - unused (bits 7--6)` Y'\ :sub:`02[9:4]`\ (bits 5--0) + +.. raw:: latex + + \small + +.. tabularcolumns:: |p{1.2cm}||p{1.2cm}||p{1.2cm}||p{1.2cm}|p{3.2cm}|p{3.2cm}| + +.. flat-table:: + :header-rows: 0 + :stub-columns: 0 + :widths: 12 12 12 12 12 12 12 12 + + * - Cb\ :sub:`00[7:0]` + - Cr\ :sub:`00[5:0]`\ (bits 7--2) Cb\ :sub:`00[9:8]`\ (bits 1--0) + - Cb\ :sub:`01[3:0]`\ (bits 7--4) Cr\ :sub:`00[9:6]`\ (bits 3--0) + - unused (bits 7--6) Cb\ :sub:`02[9:4]`\ (bits 5--0) + - Cr\ :sub:`01[7:0]` + - Cb\ :sub:`02[5:0]`\ (bits 7--2) Cr\ :sub:`01[9:8]`\ (bits 1--0) + - Cr\ :sub:`02[3:0]`\ (bits 7--4) Cb\ :sub:`02[9:6]`\ (bits 3--0) + - unused (bits 7--6) Cr\ :sub:`02[9:4]`\ (bits 5--0) + +.. raw:: latex + + \normalsize + + + + diff --git a/Documentation/userspace-api/media/v4l/pixfmt-yuv-planar.rst b/Documentation/userspace-api/media/v4l/pixfmt-yuv-planar.rst index b788f693385541..7f92b071e6ed08 100644 --- a/Documentation/userspace-api/media/v4l/pixfmt-yuv-planar.rst +++ b/Documentation/userspace-api/media/v4l/pixfmt-yuv-planar.rst @@ -828,6 +828,18 @@ Data in the 12 high bits, zeros in the 4 low bits, arranged in little endian ord - Cr\ :sub:`11` +V4L2_PIX_FMT_NV12_COL128 +------------------------ + +``V4L2_PIX_FMT_NV12_COL128`` is the tiled version of +``V4L2_PIX_FMT_NV12`` with the image broken down into 128 pixel wide columns of +Y followed by the associated combined CbCr plane. +The normal bytesperline is effectively fixed at 128. However the format +requires knowledge of the stride between columns, therefore the bytesperline +value has been repurposed to denote the number of 128 byte long lines between +the start of each column. + + Fully Planar YUV Formats ======================== diff --git a/Documentation/userspace-api/media/v4l/subdev-formats.rst b/Documentation/userspace-api/media/v4l/subdev-formats.rst index d2a6cd2e1eb2ce..7ae8f4526e5e92 100644 --- a/Documentation/userspace-api/media/v4l/subdev-formats.rst +++ b/Documentation/userspace-api/media/v4l/subdev-formats.rst @@ -625,6 +625,43 @@ The following tables list existing packed RGB formats. - b\ :sub:`2` - b\ :sub:`1` - b\ :sub:`0` + * .. _MEDIA_BUS_FMT_RGB565_1X24_CPADHI: + + - MEDIA_BUS_FMT_RGB565_1X24_CPADHI + - 0x1022 + - + - + - + - + - + - + - + - + - + - 0 + - 0 + - 0 + - r\ :sub:`4` + - r\ :sub:`3` + - r\ :sub:`2` + - r\ :sub:`1` + - r\ :sub:`0` + - 0 + - 0 + - g\ :sub:`5` + - g\ :sub:`4` + - g\ :sub:`3` + - g\ :sub:`2` + - g\ :sub:`1` + - g\ :sub:`0` + - 0 + - 0 + - 0 + - b\ :sub:`4` + - b\ :sub:`3` + - b\ :sub:`2` + - b\ :sub:`1` + - b\ :sub:`0` * .. _MEDIA-BUS-FMT-BGR565-2X8-BE: - MEDIA_BUS_FMT_BGR565_2X8_BE @@ -913,6 +950,43 @@ The following tables list existing packed RGB formats. - g\ :sub:`5` - g\ :sub:`4` - g\ :sub:`3` + * .. _MEDIA-BUS-FMT-BGR666-1X18: + + - MEDIA_BUS_FMT-BGR666_1X18 + - 0x1023 + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - b\ :sub:`5` + - b\ :sub:`4` + - b\ :sub:`3` + - b\ :sub:`2` + - b\ :sub:`1` + - b\ :sub:`0` + - g\ :sub:`5` + - g\ :sub:`4` + - g\ :sub:`3` + - g\ :sub:`2` + - g\ :sub:`1` + - g\ :sub:`0` + - r\ :sub:`5` + - r\ :sub:`4` + - r\ :sub:`3` + - r\ :sub:`2` + - r\ :sub:`1` + - r\ :sub:`0` * .. _MEDIA-BUS-FMT-RGB666-1X18: - MEDIA_BUS_FMT_RGB666_1X18 @@ -1096,6 +1170,43 @@ The following tables list existing packed RGB formats. - g\ :sub:`2` - g\ :sub:`1` - g\ :sub:`0` + * .. _MEDIA-BUS-FMT-BGR666-1X24_CPADHI: + + - MEDIA_BUS_FMT_BGR666_1X24_CPADHI + - 0x1024 + - + - + - + - + - + - + - + - + - + - 0 + - 0 + - b\ :sub:`5` + - b\ :sub:`4` + - b\ :sub:`3` + - b\ :sub:`2` + - b\ :sub:`1` + - b\ :sub:`0` + - 0 + - 0 + - g\ :sub:`5` + - g\ :sub:`4` + - g\ :sub:`3` + - g\ :sub:`2` + - g\ :sub:`1` + - g\ :sub:`0` + - 0 + - 0 + - r\ :sub:`5` + - r\ :sub:`4` + - r\ :sub:`3` + - r\ :sub:`2` + - r\ :sub:`1` + - r\ :sub:`0` * .. _MEDIA-BUS-FMT-RGB666-1X24_CPADHI: - MEDIA_BUS_FMT_RGB666_1X24_CPADHI @@ -8561,3 +8672,33 @@ and finally the bit number in subscript. "x" indicates a padding bit. - x - x - x + +.. _v4l2-mbus-sensor-data: + +Sensor Ancillary Metadata Formats +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This section lists ancillary data generated by a camera sensor and +transmitted over a stream on the camera bus. + +The following table lists the existing sensor ancillary metadata formats: + + +.. _v4l2-mbus-pixelcode-sensor-metadata: + +.. tabularcolumns:: |p{8.0cm}|p{1.4cm}|p{7.7cm}| + +.. flat-table:: Sensor ancillary metadata formats + :header-rows: 1 + :stub-columns: 0 + + * - Identifier + - Code + - Comments + * .. _MEDIA_BUS_FMT_SENSOR_DATA: + + - MEDIA_BUS_FMT_SENSOR_DATA + - 0x7001 + - Sensor vendor specific ancillary metadata. Some vendors follow a generic + CSI-2/SMIA embedded data format as described in the `CSI-2 specification. + <https://mipi.org/specifications/csi-2>`_ diff --git a/Documentation/userspace-api/media/v4l/yuv-formats.rst b/Documentation/userspace-api/media/v4l/yuv-formats.rst index 24b34cdfa6fea6..458e07782c8d80 100644 --- a/Documentation/userspace-api/media/v4l/yuv-formats.rst +++ b/Documentation/userspace-api/media/v4l/yuv-formats.rst @@ -270,4 +270,23 @@ image. pixfmt-y8i pixfmt-y12i pixfmt-uv8 + pixfmt-yuyv + pixfmt-uyvy + pixfmt-yvyu + pixfmt-vyuy + pixfmt-y41p + pixfmt-yuv420 + pixfmt-yuv420m + pixfmt-yuv422m + pixfmt-yuv444m + pixfmt-yuv410 + pixfmt-yuv422p + pixfmt-yuv411p + pixfmt-nv12 + pixfmt-nv12m + pixfmt-nv12mt + pixfmt-nv12-col128 + pixfmt-nv16 + pixfmt-nv16m + pixfmt-nv24 pixfmt-m420 diff --git a/MAINTAINERS b/MAINTAINERS index 6bb4ec0c162a53..0ca82e50fe372a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1752,6 +1752,22 @@ S: Maintained F: drivers/net/arcnet/ F: include/uapi/linux/if_arcnet.h +ARDUCAM 64MP SENSOR DRIVER +M: Arducam Kernel Maintenance <info@arducam.com> +L: linux-media@vger.kernel.org +S: Maintained +T: git git://linuxtv.org/media_tree.git +F: Documentation/devicetree/bindings/media/i2c/arducam,64mp.yaml +F: drivers/media/i2c/arducam_64mp.c + +ARDUCAM PIVARIETY SENSOR DRIVER +M: Arducam Kernel Maintenance <info@arducam.com> +L: linux-media@vger.kernel.org +S: Maintained +T: git git://linuxtv.org/media_tree.git +F: Documentation/devicetree/bindings/media/i2c/arducam-pivariety.yaml +F: drivers/media/i2c/arducam-pivariety.c + ARM AND ARM64 SoC SUB-ARCHITECTURES (COMMON PARTS) M: Arnd Bergmann <arnd@arndb.de> M: Olof Johansson <olof@lixom.net> @@ -4374,6 +4390,22 @@ S: Maintained F: Documentation/devicetree/bindings/media/brcm,bcm2835-unicam.yaml F: drivers/media/platform/broadcom/bcm2835-unicam* +BROADCOM BCM2835 ISP DRIVER +M: Raspberry Pi Kernel Maintenance <kernel-list@raspberrypi.com> +L: linux-media@vger.kernel.org +S: Maintained +F: Documentation/media/uapi/v4l/pixfmt-meta-bcm2835-isp-stats.rst +F: Documentation/media/v4l-drivers/bcm2835-isp.rst +F: drivers/staging/vc04_services/bcm2835-isp +F: include/uapi/linux/bcm2835-isp.h + +BROADCOM BCM2711 HEVC DECODER +M: Raspberry Pi Kernel Maintenance <kernel-list@raspberrypi.com> +L: linux-media@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/media/rpivid_hevc.jaml +F: drivers/staging/media/rpivid + BROADCOM BCM47XX MIPS ARCHITECTURE M: Hauke Mehrtens <hauke@hauke-m.de> M: Rafał Miłecki <zajec5@gmail.com> @@ -19319,6 +19351,12 @@ L: linux-edac@vger.kernel.org S: Maintained F: drivers/ras/amd/fmpm.c +RASPBERRY PI RP2040 GPIO BRIDGE DRIVER +M: Raspberry Pi Kernel Maintenance <kernel-list@raspberrypi.com> +S: Maintained +F: Documentation/devicetree/bindings/spi/raspberrypi,rp2040-gpio-bridge.yaml +F: drivers/spi/spi-rp2040-gpio-bridge.c + RASPBERRY PI PISP BACK END M: Jacopo Mondi <jacopo.mondi@ideasonboard.com> L: Raspberry Pi Kernel Maintenance <kernel-list@raspberrypi.com> @@ -20026,6 +20064,13 @@ S: Supported F: drivers/iio/light/rohm-bu27008.c F: drivers/iio/light/rohm-bu27034.c +ROHM BU64754 MOTOR DRIVER FOR CAMERA AUTOFOCUS +M: Kieran Bingham <kieran.bingham@ideasonboard.com> +L: linux-media@vger.kernel.org +S: Maintained +T: git git://linuxtv.org/media_tree.git +F: Documentation/devicetree/bindings/media/i2c/rohm,bu64754.yaml + ROHM MULTIFUNCTION BD9571MWV-M PMIC DEVICE DRIVERS M: Marek Vasut <marek.vasut+renesas@gmail.com> L: linux-kernel@vger.kernel.org @@ -21538,6 +21583,39 @@ T: git git://linuxtv.org/media.git F: Documentation/devicetree/bindings/media/i2c/sony,imx415.yaml F: drivers/media/i2c/imx415.c +SONY IMX477 SENSOR DRIVER +M: Raspberry Pi Kernel Maintenance <kernel-list@raspberrypi.com> +L: linux-media@vger.kernel.org +S: Maintained +T: git git://linuxtv.org/media_tree.git +F: Documentation/devicetree/bindings/media/i2c/imx378.yaml +F: Documentation/devicetree/bindings/media/i2c/imx477.yaml +F: drivers/media/i2c/imx477.c + +SONY IMX500 SENSOR DRIVER +M: Raspberry Pi Kernel Maintenance <kernel-list@raspberrypi.com> +L: linux-media@vger.kernel.org +S: Maintained +T: git git://linuxtv.org/media_tree.git +F: Documentation/devicetree/bindings/media/i2c/sony,imx500.yaml +F: drivers/media/i2c/imx500.c + +SONY IMX519 SENSOR DRIVER +M: Arducam Kernel Maintenance <info@arducam.com> +L: linux-media@vger.kernel.org +S: Maintained +T: git git://linuxtv.org/media_tree.git +F: Documentation/devicetree/bindings/media/i2c/imx519.yaml +F: drivers/media/i2c/imx519.c + +SONY IMX708 SENSOR DRIVER +M: Raspberry Pi Kernel Maintenance <kernel-list@raspberrypi.com> +L: linux-media@vger.kernel.org +S: Maintained +T: git git://linuxtv.org/media_tree.git +F: Documentation/devicetree/bindings/media/i2c/sony,imx708.yaml +F: drivers/media/i2c/imx708.c + SONY MEMORYSTICK SUBSYSTEM M: Maxim Levitsky <maximlevitsky@gmail.com> M: Alex Dubov <oakad@yahoo.com> diff --git a/README.md b/README.md new file mode 100644 index 00000000000000..e26e19b0262727 --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +Linux kernel +============ + +There are several guides for kernel developers and users. These guides can +be rendered in a number of formats, like HTML and PDF. Please read +Documentation/admin-guide/README.rst first. + +In order to build the documentation, use ``make htmldocs`` or +``make pdfdocs``. The formatted documentation can also be read online at: + + https://www.kernel.org/doc/html/latest/ + +There are various text files in the Documentation/ subdirectory, +several of them using the Restructured Text markup notation. + +Please read the Documentation/process/changes.rst file, as it contains the +requirements for building and running the kernel, and information about +the problems which may result by upgrading your kernel. + +Build status for rpi-6.1.y: +[](https://github.com/raspberrypi/linux/actions/workflows/kernel-build.yml) +[](https://github.com/raspberrypi/linux/actions/workflows/dtoverlaycheck.yml) + +Build status for rpi-6.6.y: +[](https://github.com/raspberrypi/linux/actions/workflows/kernel-build.yml) +[](https://github.com/raspberrypi/linux/actions/workflows/dtoverlaycheck.yml) + +Build status for rpi-6.12.y: +[](https://github.com/raspberrypi/linux/actions/workflows/kernel-build.yml) +[](https://github.com/raspberrypi/linux/actions/workflows/dtoverlaycheck.yml) diff --git a/arch/arm/boot/dts/Makefile b/arch/arm/boot/dts/Makefile index efe38eb2530164..a2a407fb5b281c 100644 --- a/arch/arm/boot/dts/Makefile +++ b/arch/arm/boot/dts/Makefile @@ -39,3 +39,8 @@ subdir-y += unisoc subdir-y += vt8500 subdir-y += xen subdir-y += xilinx + +targets += dtbs dtbs_install +targets += $(dtb-y) + +subdir-y += overlays diff --git a/arch/arm/boot/dts/broadcom/Makefile b/arch/arm/boot/dts/broadcom/Makefile index 5881bcc95eba6b..79d83e8a214df4 100644 --- a/arch/arm/boot/dts/broadcom/Makefile +++ b/arch/arm/boot/dts/broadcom/Makefile @@ -35,6 +35,41 @@ dtb-$(CONFIG_ARCH_BCM2835) += \ bcm2711-rpi-cm4-io.dtb \ bcm2835-rpi-zero.dtb \ bcm2835-rpi-zero-w.dtb + +DTC_FLAGS_bcm2708-rpi-b := -@ +DTC_FLAGS_bcm2708-rpi-b-rev1 := -@ +DTC_FLAGS_bcm2708-rpi-b-plus := -@ +DTC_FLAGS_bcm2708-rpi-cm := -@ +DTC_FLAGS_bcm2708-rpi-zero := -@ +DTC_FLAGS_bcm2708-rpi-zero-w := -@ +DTC_FLAGS_bcm2710-rpi-zero-2 := -@ +DTC_FLAGS_bcm2710-rpi-zero-2-w := -@ +DTC_FLAGS_bcm2709-rpi-2-b := -@ +DTC_FLAGS_bcm2710-rpi-2-b := -@ +DTC_FLAGS_bcm2710-rpi-3-b := -@ +DTC_FLAGS_bcm2710-rpi-3-b-plus := -@ +DTC_FLAGS_bcm2709-rpi-cm2 := -@ +DTC_FLAGS_bcm2710-rpi-cm3 := -@ +DTC_FLAGS_bcm2711-rpi-cm4 := -@ +DTC_FLAGS_bcm2711-rpi-cm4s := -@ +dtb-$(CONFIG_ARCH_BCM2835) += \ + bcm2708-rpi-b.dtb \ + bcm2708-rpi-b-rev1.dtb \ + bcm2708-rpi-b-plus.dtb \ + bcm2708-rpi-cm.dtb \ + bcm2708-rpi-zero.dtb \ + bcm2708-rpi-zero-w.dtb \ + bcm2710-rpi-zero-2.dtb \ + bcm2710-rpi-zero-2-w.dtb \ + bcm2709-rpi-2-b.dtb \ + bcm2710-rpi-2-b.dtb \ + bcm2710-rpi-3-b.dtb \ + bcm2710-rpi-3-b-plus.dtb \ + bcm2709-rpi-cm2.dtb \ + bcm2710-rpi-cm3.dtb \ + bcm2711-rpi-cm4.dtb \ + bcm2711-rpi-cm4s.dtb + dtb-$(CONFIG_ARCH_BCMBCA) += \ bcm947622.dtb \ bcm963138.dtb \ diff --git a/arch/arm/boot/dts/broadcom/bcm2708-rpi-b-plus.dts b/arch/arm/boot/dts/broadcom/bcm2708-rpi-b-plus.dts new file mode 100644 index 00000000000000..ee72fdac666368 --- /dev/null +++ b/arch/arm/boot/dts/broadcom/bcm2708-rpi-b-plus.dts @@ -0,0 +1,210 @@ +/dts-v1/; + +#include "bcm2708.dtsi" +#include "bcm2708-rpi.dtsi" +#include "bcm283x-rpi-smsc9514.dtsi" +#include "bcm283x-rpi-csi1-2lane.dtsi" +#include "bcm283x-rpi-i2c0mux_0_28.dtsi" +#include "bcm283x-rpi-led-deprecated.dtsi" + +/ { + compatible = "raspberrypi,model-b-plus", "brcm,bcm2835"; + model = "Raspberry Pi Model B+"; +}; + +&gpio { + /* + * Taken from Raspberry-Pi-B-Plus-V1.2-Schematics.pdf + * RPI-BPLUS sheet 1 + * + * Legend: + * "NC" = not connected (no rail from the SoC) + * "FOO" = GPIO line named "FOO" on the schematic + * "FOO_N" = GPIO line named "FOO" on schematic, active low + */ + gpio-line-names = "ID_SDA", + "ID_SCL", + "GPIO2", + "GPIO3", + "GPIO4", + "GPIO5", + "GPIO6", + "GPIO7", + "GPIO8", + "GPIO9", + "GPIO10", + "GPIO11", + "GPIO12", + "GPIO13", + "GPIO14", + "GPIO15", + "GPIO16", + "GPIO17", + "GPIO18", + "GPIO19", + "GPIO20", + "GPIO21", + "GPIO22", + "GPIO23", + "GPIO24", + "GPIO25", + "GPIO26", + "GPIO27", + "SDA0", + "SCL0", + "NC", /* GPIO30 */ + "LAN_RUN", /* GPIO31 */ + "CAM_GPIO1", /* GPIO32 */ + "NC", /* GPIO33 */ + "NC", /* GPIO34 */ + "PWR_LOW_N", /* GPIO35 */ + "NC", /* GPIO36 */ + "NC", /* GPIO37 */ + "USB_LIMIT", /* GPIO38 */ + "NC", /* GPIO39 */ + "PWM0_OUT", /* GPIO40 */ + "CAM_GPIO0", /* GPIO41 */ + "NC", /* GPIO42 */ + "NC", /* GPIO43 */ + "ETH_CLK", /* GPIO44 */ + "PWM1_OUT", /* GPIO45 */ + "HDMI_HPD_N", + "STATUS_LED", + /* Used by SD Card */ + "SD_CLK_R", + "SD_CMD_R", + "SD_DATA0_R", + "SD_DATA1_R", + "SD_DATA2_R", + "SD_DATA3_R"; + + spi0_pins: spi0_pins { + brcm,pins = <9 10 11>; + brcm,function = <4>; /* alt0 */ + }; + + spi0_cs_pins: spi0_cs_pins { + brcm,pins = <8 7>; + brcm,function = <1>; /* output */ + }; + + i2c0_pins: i2c0 { + brcm,pins = <0 1>; + brcm,function = <4>; + }; + + i2c1_pins: i2c1 { + brcm,pins = <2 3>; + brcm,function = <4>; + }; + + i2s_pins: i2s { + brcm,pins = <18 19 20 21>; + brcm,function = <4>; /* alt0 */ + }; + + audio_pins: audio_pins { + brcm,pins = <40 45>; + brcm,function = <4>; + brcm,pull = <0>; + }; +}; + +&uart0 { + status = "okay"; +}; + +&spi0 { + pinctrl-names = "default"; + pinctrl-0 = <&spi0_pins &spi0_cs_pins>; + cs-gpios = <&gpio 8 1>, <&gpio 7 1>; + + spidev0: spidev@0{ + compatible = "spidev"; + reg = <0>; /* CE0 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + }; + + spidev1: spidev@1{ + compatible = "spidev"; + reg = <1>; /* CE1 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + }; +}; + +&i2c0if { + clock-frequency = <100000>; +}; + +&i2c1 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c1_pins>; + clock-frequency = <100000>; +}; + +&i2c2 { + clock-frequency = <100000>; +}; + +&i2s { + pinctrl-names = "default"; + pinctrl-0 = <&i2s_pins>; +}; + +&led_act { + gpios = <&gpio 47 GPIO_ACTIVE_HIGH>; + default-state = "off"; + linux,default-trigger = "mmc0"; +}; + +&leds { + led_pwr: led-pwr { + label = "PWR"; + gpios = <&gpio 35 GPIO_ACTIVE_HIGH>; + default-state = "off"; + linux,default-trigger = "input"; + }; +}; + +&hdmi { + hpd-gpios = <&gpio 46 GPIO_ACTIVE_LOW>; +}; + +&vchiq { + pinctrl-names = "default"; + pinctrl-0 = <&audio_pins>; +}; + +&cam1_reg { + gpio = <&gpio 41 GPIO_ACTIVE_HIGH>; +}; + +cam0_reg: &cam_dummy_reg { +}; + +i2c_arm: &i2c1 { +}; + +i2c_vc: &i2c0 { +}; + +i2c_csi_dsi0: &i2c0 { +}; + +/ { + __overrides__ { + audio = <&chosen>,"bootargs{on='snd_bcm2835.enable_headphones=1 snd_bcm2835.enable_hdmi=1',off='snd_bcm2835.enable_headphones=0 snd_bcm2835.enable_hdmi=0'}"; + + act_led_gpio = <&led_act>,"gpios:4"; + act_led_activelow = <&led_act>,"gpios:8"; + act_led_trigger = <&led_act>,"linux,default-trigger"; + + pwr_led_gpio = <&led_pwr>,"gpios:4"; + pwr_led_activelow = <&led_pwr>,"gpios:8"; + pwr_led_trigger = <&led_pwr>,"linux,default-trigger"; + }; +}; diff --git a/arch/arm/boot/dts/broadcom/bcm2708-rpi-b-rev1.dts b/arch/arm/boot/dts/broadcom/bcm2708-rpi-b-rev1.dts new file mode 100644 index 00000000000000..9301e345aea222 --- /dev/null +++ b/arch/arm/boot/dts/broadcom/bcm2708-rpi-b-rev1.dts @@ -0,0 +1,223 @@ +/dts-v1/; + +#include "bcm2708.dtsi" +#include "bcm2708-rpi.dtsi" +#include "bcm283x-rpi-smsc9512.dtsi" +#include "bcm283x-rpi-csi1-2lane.dtsi" +#include "bcm283x-rpi-led-deprecated.dtsi" + +/ { + compatible = "raspberrypi,model-b", "brcm,bcm2835"; + model = "Raspberry Pi Model B"; +}; + +&gpio { + /* + * Taken from Raspberry-Pi-Rev-1.0-Model-AB-Schematics.pdf + * RPI00021 sheet 02 + * + * Legend: + * "NC" = not connected (no rail from the SoC) + * "FOO" = GPIO line named "FOO" on the schematic + * "FOO_N" = GPIO line named "FOO" on schematic, active low + */ + gpio-line-names = "SDA0", + "SCL0", + "SDA1", + "SCL1", + "GPIO_GCLK", + "CAM_GPIO1", + "LAN_RUN", + "SPI_CE1_N", + "SPI_CE0_N", + "SPI_MISO", + "SPI_MOSI", + "SPI_SCLK", + "NC", /* GPIO12 */ + "NC", /* GPIO13 */ + /* Serial port */ + "TXD0", + "RXD0", + "STATUS_LED_N", + "GPIO17", + "GPIO18", + "NC", /* GPIO19 */ + "NC", /* GPIO20 */ + "GPIO21", + "GPIO22", + "GPIO23", + "GPIO24", + "GPIO25", + "NC", /* GPIO26 */ + "CAM_GPIO0", + /* Binary number representing build/revision */ + "CONFIG0", + "CONFIG1", + "CONFIG2", + "CONFIG3", + "NC", /* GPIO32 */ + "NC", /* GPIO33 */ + "NC", /* GPIO34 */ + "NC", /* GPIO35 */ + "NC", /* GPIO36 */ + "NC", /* GPIO37 */ + "NC", /* GPIO38 */ + "NC", /* GPIO39 */ + "PWM0_OUT", + "NC", /* GPIO41 */ + "NC", /* GPIO42 */ + "NC", /* GPIO43 */ + "NC", /* GPIO44 */ + "PWM1_OUT", + "HDMI_HPD_P", + "SD_CARD_DET", + /* Used by SD Card */ + "SD_CLK_R", + "SD_CMD_R", + "SD_DATA0_R", + "SD_DATA1_R", + "SD_DATA2_R", + "SD_DATA3_R"; + + spi0_pins: spi0_pins { + brcm,pins = <9 10 11>; + brcm,function = <4>; /* alt0 */ + }; + + spi0_cs_pins: spi0_cs_pins { + brcm,pins = <8 7>; + brcm,function = <1>; /* output */ + }; + + i2c0_pins: i2c0 { + brcm,pins = <0 1>; + brcm,function = <4>; + }; + + i2c1_pins: i2c1 { + brcm,pins = <2 3>; + brcm,function = <4>; + }; + + i2s_pins: i2s { + brcm,pins = <28 29 30 31>; + brcm,function = <6>; /* alt2 */ + }; + + audio_pins: audio_pins { + brcm,pins = <40 45>; + brcm,function = <4>; + brcm,pull = <0>; + }; +}; + +&uart0 { + status = "okay"; +}; + +&spi0 { + pinctrl-names = "default"; + pinctrl-0 = <&spi0_pins &spi0_cs_pins>; + cs-gpios = <&gpio 8 1>, <&gpio 7 1>; + + spidev0: spidev@0{ + compatible = "spidev"; + reg = <0>; /* CE0 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + }; + + spidev1: spidev@1{ + compatible = "spidev"; + reg = <1>; /* CE1 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + }; +}; + +/delete-node/ &i2c0mux; + +i2c0: &i2c0if { + pinctrl-names = "default"; + pinctrl-0 = <&i2c0_pins>; + clock-frequency = <100000>; +}; + +i2c_csi_dsi: &i2c1 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c1_pins>; + clock-frequency = <100000>; +}; + +/ { + aliases { + i2c0 = &i2c0; + }; + + /* Provide an i2c0mux label to avoid undefined symbols in overlays */ + i2c0mux: i2c0mux { + }; + + __overrides__ { + i2c0 = <&i2c0>, "status"; + }; +}; + +&i2c2 { + clock-frequency = <100000>; +}; + +&i2s { + pinctrl-names = "default"; + pinctrl-0 = <&i2s_pins>; +}; + +&led_act { + gpios = <&gpio 16 GPIO_ACTIVE_LOW>; + default-state = "off"; + linux,default-trigger = "mmc0"; +}; + +&hdmi { + hpd-gpios = <&gpio 46 GPIO_ACTIVE_HIGH>; +}; + +&vchiq { + pinctrl-names = "default"; + pinctrl-0 = <&audio_pins>; +}; + +&cam1_reg { + gpio = <&gpio 27 GPIO_ACTIVE_HIGH>; +}; + +cam0_reg: &cam_dummy_reg { +}; + +i2c_arm: &i2c0 { +}; + +i2c_vc: &i2c1 { +}; + +i2c_csi_dsi0: &i2c0 { +}; + +/ { + __overrides__ { + audio = <&chosen>,"bootargs{on='snd_bcm2835.enable_headphones=1 snd_bcm2835.enable_hdmi=1',off='snd_bcm2835.enable_headphones=0 snd_bcm2835.enable_hdmi=0'}"; + + act_led_gpio = <&led_act>,"gpios:4"; + act_led_activelow = <&led_act>,"gpios:8"; + act_led_trigger = <&led_act>,"linux,default-trigger"; + + i2c = <&i2c0>,"status"; + i2c_arm = <&i2c0>,"status"; + i2c_vc = <&i2c1>,"status"; + i2c_baudrate = <&i2c0>,"clock-frequency:0"; + i2c_arm_baudrate = <&i2c0>,"clock-frequency:0"; + i2c_vc_baudrate = <&i2c1>,"clock-frequency:0"; + }; +}; diff --git a/arch/arm/boot/dts/broadcom/bcm2708-rpi-b.dts b/arch/arm/boot/dts/broadcom/bcm2708-rpi-b.dts new file mode 100644 index 00000000000000..b8459fd0f49703 --- /dev/null +++ b/arch/arm/boot/dts/broadcom/bcm2708-rpi-b.dts @@ -0,0 +1,198 @@ +/dts-v1/; + +#include "bcm2708.dtsi" +#include "bcm2708-rpi.dtsi" +#include "bcm283x-rpi-smsc9512.dtsi" +#include "bcm283x-rpi-csi1-2lane.dtsi" +#include "bcm283x-rpi-i2c0mux_0_28.dtsi" +#include "bcm283x-rpi-led-deprecated.dtsi" + +/ { + compatible = "raspberrypi,model-b", "brcm,bcm2835"; + model = "Raspberry Pi Model B"; +}; + +&gpio { + /* + * Taken from Raspberry-Pi-Rev-2.0-Model-AB-Schematics.pdf + * RPI00022 sheet 02 + * + * Legend: + * "NC" = not connected (no rail from the SoC) + * "FOO" = GPIO line named "FOO" on the schematic + * "FOO_N" = GPIO line named "FOO" on schematic, active low + */ + gpio-line-names = "SDA0", + "SCL0", + "SDA1", + "SCL1", + "GPIO_GCLK", + "CAM_GPIO1", + "LAN_RUN", + "SPI_CE1_N", + "SPI_CE0_N", + "SPI_MISO", + "SPI_MOSI", + "SPI_SCLK", + "NC", /* GPIO12 */ + "NC", /* GPIO13 */ + /* Serial port */ + "TXD0", + "RXD0", + "STATUS_LED_N", + "GPIO17", + "GPIO18", + "NC", /* GPIO19 */ + "NC", /* GPIO20 */ + "CAM_GPIO0", + "GPIO22", + "GPIO23", + "GPIO24", + "GPIO25", + "NC", /* GPIO26 */ + "GPIO27", + "GPIO28", + "GPIO29", + "GPIO30", + "GPIO31", + "NC", /* GPIO32 */ + "NC", /* GPIO33 */ + "NC", /* GPIO34 */ + "NC", /* GPIO35 */ + "NC", /* GPIO36 */ + "NC", /* GPIO37 */ + "NC", /* GPIO38 */ + "NC", /* GPIO39 */ + "PWM0_OUT", + "NC", /* GPIO41 */ + "NC", /* GPIO42 */ + "NC", /* GPIO43 */ + "NC", /* GPIO44 */ + "PWM1_OUT", + "HDMI_HPD_P", + "SD_CARD_DET", + /* Used by SD Card */ + "SD_CLK_R", + "SD_CMD_R", + "SD_DATA0_R", + "SD_DATA1_R", + "SD_DATA2_R", + "SD_DATA3_R"; + + spi0_pins: spi0_pins { + brcm,pins = <9 10 11>; + brcm,function = <4>; /* alt0 */ + }; + + spi0_cs_pins: spi0_cs_pins { + brcm,pins = <8 7>; + brcm,function = <1>; /* output */ + }; + + i2c0_pins: i2c0 { + brcm,pins = <0 1>; + brcm,function = <4>; + }; + + i2c1_pins: i2c1 { + brcm,pins = <2 3>; + brcm,function = <4>; + }; + + i2s_pins: i2s { + brcm,pins = <28 29 30 31>; + brcm,function = <6>; /* alt2 */ + }; + + audio_pins: audio_pins { + brcm,pins = <40 45>; + brcm,function = <4>; + brcm,pull = <0>; + }; +}; + +&uart0 { + status = "okay"; +}; + +&spi0 { + pinctrl-names = "default"; + pinctrl-0 = <&spi0_pins &spi0_cs_pins>; + cs-gpios = <&gpio 8 1>, <&gpio 7 1>; + + spidev0: spidev@0{ + compatible = "spidev"; + reg = <0>; /* CE0 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + }; + + spidev1: spidev@1{ + compatible = "spidev"; + reg = <1>; /* CE1 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + }; +}; + +&i2c0if { + clock-frequency = <100000>; +}; + +&i2c1 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c1_pins>; + clock-frequency = <100000>; +}; + +&i2c2 { + clock-frequency = <100000>; +}; + +&i2s { + pinctrl-names = "default"; + pinctrl-0 = <&i2s_pins>; +}; + +&led_act { + gpios = <&gpio 16 GPIO_ACTIVE_LOW>; + default-state = "off"; + linux,default-trigger = "mmc0"; +}; + +&hdmi { + hpd-gpios = <&gpio 46 GPIO_ACTIVE_HIGH>; +}; + +&vchiq { + pinctrl-names = "default"; + pinctrl-0 = <&audio_pins>; +}; + +&cam1_reg { + gpio = <&gpio 21 GPIO_ACTIVE_HIGH>; +}; + +cam0_reg: &cam_dummy_reg { +}; + +i2c_arm: &i2c1 { +}; + +i2c_vc: &i2c0 { +}; + +i2c_csi_dsi0: &i2c0 { +}; + +/ { + __overrides__ { + audio = <&chosen>,"bootargs{on='snd_bcm2835.enable_headphones=1 snd_bcm2835.enable_hdmi=1',off='snd_bcm2835.enable_headphones=0 snd_bcm2835.enable_hdmi=0'}"; + + act_led_gpio = <&led_act>,"gpios:4"; + act_led_activelow = <&led_act>,"gpios:8"; + act_led_trigger = <&led_act>,"linux,default-trigger"; + }; +}; diff --git a/arch/arm/boot/dts/broadcom/bcm2708-rpi-bt.dtsi b/arch/arm/boot/dts/broadcom/bcm2708-rpi-bt.dtsi new file mode 100644 index 00000000000000..87a6c00bd0562f --- /dev/null +++ b/arch/arm/boot/dts/broadcom/bcm2708-rpi-bt.dtsi @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-2.0 + +&uart0 { + bt: bluetooth { + compatible = "brcm,bcm43438-bt"; + max-speed = <3000000>; + shutdown-gpios = <&gpio 45 GPIO_ACTIVE_HIGH>; + local-bd-address = [ 00 00 00 00 00 00 ]; + fallback-bd-address; // Don't override a valid address + status = "okay"; + }; +}; + +&uart1 { + minibt: bluetooth { + compatible = "brcm,bcm43438-bt"; + max-speed = <230400>; + shutdown-gpios = <&gpio 45 GPIO_ACTIVE_HIGH>; + local-bd-address = [ 00 00 00 00 00 00 ]; + fallback-bd-address; // Don't override a valid address + status = "disabled"; + }; +}; + +/ { + chosen { + bootargs = "coherent_pool=1M 8250.nr_uarts=1 snd_bcm2835.enable_headphones=0 cgroup_disable=memory"; + }; + + aliases { + bluetooth = &bt; + }; + + __overrides__ { + bdaddr = <&bt>,"local-bd-address[", + <&bt>,"fallback-bd-address?=0", + <&minibt>,"local-bd-address[", + <&minibt>,"fallback-bd-address?=0"; + krnbt = <&bt>,"status"; + krnbt_baudrate = <&bt>,"max-speed:0", <&minibt>,"max-speed:0"; + }; +}; diff --git a/arch/arm/boot/dts/broadcom/bcm2708-rpi-cm.dts b/arch/arm/boot/dts/broadcom/bcm2708-rpi-cm.dts new file mode 100644 index 00000000000000..fde85c8c7dca22 --- /dev/null +++ b/arch/arm/boot/dts/broadcom/bcm2708-rpi-cm.dts @@ -0,0 +1,174 @@ +/dts-v1/; + +#include "bcm2708-rpi-cm.dtsi" +#include "bcm283x-rpi-csi0-2lane.dtsi" +#include "bcm283x-rpi-csi1-4lane.dtsi" +#include "bcm283x-rpi-i2c0mux_0_28.dtsi" + +/ { + compatible = "raspberrypi,compute-module", "brcm,bcm2835"; + model = "Raspberry Pi Compute Module"; +}; + +&cam1_reg { + gpio = <&gpio 3 GPIO_ACTIVE_HIGH>; + status = "disabled"; +}; + +cam0_reg: &cam0_regulator { + gpio = <&gpio 31 GPIO_ACTIVE_HIGH>; +}; + +i2c_csi_dsi0: &i2c0 { +}; + +&uart0 { + status = "okay"; +}; + +&gpio { + /* + * This is based on the official GPU firmware DT blob. + * + * Legend: + * "NC" = not connected (no rail from the SoC) + * "FOO" = GPIO line named "FOO" on the schematic + * "FOO_N" = GPIO line named "FOO" on schematic, active low + */ + gpio-line-names = "GPIO0", + "GPIO1", + "GPIO2", + "GPIO3", + "GPIO4", + "GPIO5", + "GPIO6", + "GPIO7", + "GPIO8", + "GPIO9", + "GPIO10", + "GPIO11", + "GPIO12", + "GPIO13", + "GPIO14", + "GPIO15", + "GPIO16", + "GPIO17", + "GPIO18", + "GPIO19", + "GPIO20", + "GPIO21", + "GPIO22", + "GPIO23", + "GPIO24", + "GPIO25", + "GPIO26", + "GPIO27", + "GPIO28", + "GPIO29", + "GPIO30", + "GPIO31", + "GPIO32", + "GPIO33", + "GPIO34", + "GPIO35", + "GPIO36", + "GPIO37", + "GPIO38", + "GPIO39", + "GPIO40", + "GPIO41", + "GPIO42", + "GPIO43", + "GPIO44", + "GPIO45", + "HDMI_HPD_N", + /* Also used as ACT LED */ + "EMMC_EN_N", + /* Used by eMMC */ + "SD_CLK_R", + "SD_CMD_R", + "SD_DATA0_R", + "SD_DATA1_R", + "SD_DATA2_R", + "SD_DATA3_R"; + + spi0_pins: spi0_pins { + brcm,pins = <9 10 11>; + brcm,function = <4>; /* alt0 */ + }; + + spi0_cs_pins: spi0_cs_pins { + brcm,pins = <8 7>; + brcm,function = <1>; /* output */ + }; + + i2c0_pins: i2c0 { + brcm,pins = <0 1>; + brcm,function = <4>; + }; + + i2c1_pins: i2c1 { + brcm,pins = <2 3>; + brcm,function = <4>; + }; + + i2s_pins: i2s { + brcm,pins = <18 19 20 21>; + brcm,function = <4>; /* alt0 */ + }; + + audio_pins: audio_pins { + brcm,pins; + brcm,function; + }; +}; + +&spi0 { + pinctrl-names = "default"; + pinctrl-0 = <&spi0_pins &spi0_cs_pins>; + cs-gpios = <&gpio 8 1>, <&gpio 7 1>; + + spidev0: spidev@0{ + compatible = "spidev"; + reg = <0>; /* CE0 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + }; + + spidev1: spidev@1{ + compatible = "spidev"; + reg = <1>; /* CE1 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + }; +}; + +&i2c0if { + clock-frequency = <100000>; +}; + +&i2c1 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c1_pins>; + clock-frequency = <100000>; +}; + +&i2c2 { + clock-frequency = <100000>; +}; + +&i2s { + pinctrl-names = "default"; + pinctrl-0 = <&i2s_pins>; +}; + +&vchiq { + pinctrl-names = "default"; + pinctrl-0 = <&audio_pins>; +}; + +&hdmi { + hpd-gpios = <&gpio 46 GPIO_ACTIVE_HIGH>; +}; diff --git a/arch/arm/boot/dts/broadcom/bcm2708-rpi-cm.dtsi b/arch/arm/boot/dts/broadcom/bcm2708-rpi-cm.dtsi new file mode 100644 index 00000000000000..8d3e42bfe4f08e --- /dev/null +++ b/arch/arm/boot/dts/broadcom/bcm2708-rpi-cm.dtsi @@ -0,0 +1,23 @@ +#include "bcm2708.dtsi" +#include "bcm2708-rpi.dtsi" +#include "bcm283x-rpi-led-deprecated.dtsi" + +&led_act { + gpios = <&gpio 47 GPIO_ACTIVE_HIGH>; + default-state = "off"; + linux,default-trigger = "mmc0"; +}; + +i2c_arm: &i2c1 { +}; + +i2c_vc: &i2c0 { +}; + +/ { + __overrides__ { + act_led_gpio = <&led_act>,"gpios:4"; + act_led_activelow = <&led_act>,"gpios:8"; + act_led_trigger = <&led_act>,"linux,default-trigger"; + }; +}; diff --git a/arch/arm/boot/dts/broadcom/bcm2708-rpi-zero-w.dts b/arch/arm/boot/dts/broadcom/bcm2708-rpi-zero-w.dts new file mode 100644 index 00000000000000..f6d4e2c73df986 --- /dev/null +++ b/arch/arm/boot/dts/broadcom/bcm2708-rpi-zero-w.dts @@ -0,0 +1,250 @@ +/dts-v1/; + +#include "bcm2708.dtsi" +#include "bcm2708-rpi.dtsi" +#include "bcm283x-rpi-csi1-2lane.dtsi" +#include "bcm283x-rpi-i2c0mux_0_28.dtsi" +#include "bcm2708-rpi-bt.dtsi" +#include "bcm283x-rpi-led-deprecated.dtsi" + +/ { + compatible = "raspberrypi,model-zero-w", "brcm,bcm2835"; + model = "Raspberry Pi Zero W"; + + aliases { + serial0 = &uart1; + serial1 = &uart0; + mmc1 = &mmcnr; + }; +}; + +&gpio { + /* + * This is based on the official GPU firmware DT blob. + * + * Legend: + * "NC" = not connected (no rail from the SoC) + * "FOO" = GPIO line named "FOO" on the schematic + * "FOO_N" = GPIO line named "FOO" on schematic, active low + */ + gpio-line-names = "ID_SDA", + "ID_SCL", + "GPIO2", + "GPIO3", + "GPIO4", + "GPIO5", + "GPIO6", + "GPIO7", + "GPIO8", + "GPIO9", + "GPIO10", + "GPIO11", + "GPIO12", + "GPIO13", + "GPIO14", + "GPIO15", + "GPIO16", + "GPIO17", + "GPIO18", + "GPIO19", + "GPIO20", + "GPIO21", + "GPIO22", + "GPIO23", + "GPIO24", + "GPIO25", + "GPIO26", + "GPIO27", + "SDA0", + "SCL0", + /* Used by BT module */ + "CTS0", + "RTS0", + "TXD0", + "RXD0", + /* Used by Wifi */ + "SD1_CLK", + "SD1_CMD", + "SD1_DATA0", + "SD1_DATA1", + "SD1_DATA2", + "SD1_DATA3", + "CAM_GPIO1", /* GPIO40 */ + "WL_ON", /* GPIO41 */ + "NC", /* GPIO42 */ + "WIFI_CLK", /* GPIO43 */ + "CAM_GPIO0", /* GPIO44 */ + "BT_ON", /* GPIO45 */ + "HDMI_HPD_N", + "STATUS_LED_N", + /* Used by SD Card */ + "SD_CLK_R", + "SD_CMD_R", + "SD_DATA0_R", + "SD_DATA1_R", + "SD_DATA2_R", + "SD_DATA3_R"; + + spi0_pins: spi0_pins { + brcm,pins = <9 10 11>; + brcm,function = <4>; /* alt0 */ + }; + + spi0_cs_pins: spi0_cs_pins { + brcm,pins = <8 7>; + brcm,function = <1>; /* output */ + }; + + i2c0_pins: i2c0 { + brcm,pins = <0 1>; + brcm,function = <4>; + }; + + i2c1_pins: i2c1 { + brcm,pins = <2 3>; + brcm,function = <4>; + }; + + i2s_pins: i2s { + brcm,pins = <18 19 20 21>; + brcm,function = <4>; /* alt0 */ + }; + + sdio_pins: sdio_pins { + brcm,pins = <34 35 36 37 38 39>; + brcm,function = <7>; /* ALT3 = SD1 */ + brcm,pull = <0 2 2 2 2 2>; + }; + + bt_pins: bt_pins { + brcm,pins = <43>; + brcm,function = <4>; /* alt0:GPCLK2 */ + brcm,pull = <0>; /* none */ + }; + + uart0_pins: uart0_pins { + brcm,pins = <30 31 32 33>; + brcm,function = <7>; /* alt3=UART0 */ + brcm,pull = <2 0 0 2>; /* up none none up */ + }; + + uart1_pins: uart1_pins { + brcm,pins; + brcm,function; + brcm,pull; + }; + + uart1_bt_pins: uart1_bt_pins { + brcm,pins = <32 33 30 31>; + brcm,function = <BCM2835_FSEL_ALT5>; /* alt5=UART1 */ + brcm,pull = <0 2 2 0>; + }; + + audio_pins: audio_pins { + brcm,pins = <>; + brcm,function = <>; + }; +}; + +&mmcnr { + pinctrl-names = "default"; + pinctrl-0 = <&sdio_pins>; + bus-width = <4>; + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + brcmf: wifi@1 { + reg = <1>; + compatible = "brcm,bcm4329-fmac"; + }; +}; + +&uart0 { + pinctrl-names = "default"; + pinctrl-0 = <&uart0_pins &bt_pins>; + status = "okay"; +}; + +&uart1 { + pinctrl-names = "default"; + pinctrl-0 = <&uart1_pins>; + status = "okay"; +}; + +&spi0 { + pinctrl-names = "default"; + pinctrl-0 = <&spi0_pins &spi0_cs_pins>; + cs-gpios = <&gpio 8 1>, <&gpio 7 1>; + + spidev0: spidev@0{ + compatible = "spidev"; + reg = <0>; /* CE0 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + }; + + spidev1: spidev@1{ + compatible = "spidev"; + reg = <1>; /* CE1 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + }; +}; + +&i2c0if { + clock-frequency = <100000>; +}; + +&i2c1 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c1_pins>; + clock-frequency = <100000>; +}; + +&i2c2 { + clock-frequency = <100000>; +}; + +&i2s { + pinctrl-names = "default"; + pinctrl-0 = <&i2s_pins>; +}; + +&led_act { + gpios = <&gpio 47 GPIO_ACTIVE_LOW>; + default-state = "off"; + linux,default-trigger = "actpwr"; +}; + +&hdmi { + hpd-gpios = <&gpio 46 GPIO_ACTIVE_LOW>; +}; + +&vchiq { + pinctrl-names = "default"; + pinctrl-0 = <&audio_pins>; +}; + +&cam1_reg { + gpio = <&gpio 44 GPIO_ACTIVE_HIGH>; +}; + +cam0_reg: &cam_dummy_reg { +}; + +i2c_arm: &i2c1 {}; +i2c_vc: &i2c0 {}; +i2c_csi_dsi0: &i2c0 {}; + +/ { + __overrides__ { + audio = <&chosen>,"bootargs{on='snd_bcm2835.enable_hdmi=1',off='snd_bcm2835.enable_hdmi=0'}"; + + act_led_gpio = <&led_act>,"gpios:4"; + act_led_activelow = <&led_act>,"gpios:8"; + act_led_trigger = <&led_act>,"linux,default-trigger"; + }; +}; diff --git a/arch/arm/boot/dts/broadcom/bcm2708-rpi-zero.dts b/arch/arm/boot/dts/broadcom/bcm2708-rpi-zero.dts new file mode 100644 index 00000000000000..1721be8dbe20a6 --- /dev/null +++ b/arch/arm/boot/dts/broadcom/bcm2708-rpi-zero.dts @@ -0,0 +1,189 @@ +/dts-v1/; + +#include "bcm2708.dtsi" +#include "bcm2708-rpi.dtsi" +#include "bcm283x-rpi-csi1-2lane.dtsi" +#include "bcm283x-rpi-i2c0mux_0_28.dtsi" +#include "bcm283x-rpi-led-deprecated.dtsi" + +/ { + compatible = "raspberrypi,model-zero", "brcm,bcm2835"; + model = "Raspberry Pi Zero"; +}; + +&gpio { + /* + * This is based on the official GPU firmware DT blob. + * + * Legend: + * "NC" = not connected (no rail from the SoC) + * "FOO" = GPIO line named "FOO" on the schematic + * "FOO_N" = GPIO line named "FOO" on schematic, active low + */ + gpio-line-names = "ID_SDA", + "ID_SCL", + "GPIO2", + "GPIO3", + "GPIO4", + "GPIO5", + "GPIO6", + "GPIO7", + "GPIO8", + "GPIO9", + "GPIO10", + "GPIO11", + "GPIO12", + "GPIO13", + "GPIO14", + "GPIO15", + "GPIO16", + "GPIO17", + "GPIO18", + "GPIO19", + "GPIO20", + "GPIO21", + "GPIO22", + "GPIO23", + "GPIO24", + "GPIO25", + "GPIO26", + "GPIO27", + "SDA0", + "SCL0", + "NC", /* GPIO30 */ + "NC", /* GPIO31 */ + "CAM_GPIO1", /* GPIO32 */ + "NC", /* GPIO33 */ + "NC", /* GPIO34 */ + "NC", /* GPIO35 */ + "NC", /* GPIO36 */ + "NC", /* GPIO37 */ + "NC", /* GPIO38 */ + "NC", /* GPIO39 */ + "NC", /* GPIO40 */ + "CAM_GPIO0", /* GPIO41 */ + "NC", /* GPIO42 */ + "NC", /* GPIO43 */ + "NC", /* GPIO44 */ + "NC", /* GPIO45 */ + "HDMI_HPD_N", + "STATUS_LED_N", + /* Used by SD Card */ + "SD_CLK_R", + "SD_CMD_R", + "SD_DATA0_R", + "SD_DATA1_R", + "SD_DATA2_R", + "SD_DATA3_R"; + + spi0_pins: spi0_pins { + brcm,pins = <9 10 11>; + brcm,function = <4>; /* alt0 */ + }; + + spi0_cs_pins: spi0_cs_pins { + brcm,pins = <8 7>; + brcm,function = <1>; /* output */ + }; + + i2c0_pins: i2c0 { + brcm,pins = <0 1>; + brcm,function = <4>; + }; + + i2c1_pins: i2c1 { + brcm,pins = <2 3>; + brcm,function = <4>; + }; + + i2s_pins: i2s { + brcm,pins = <18 19 20 21>; + brcm,function = <4>; /* alt0 */ + }; + + audio_pins: audio_pins { + brcm,pins = <>; + brcm,function = <>; + }; +}; + +&uart0 { + status = "okay"; +}; + +&spi0 { + pinctrl-names = "default"; + pinctrl-0 = <&spi0_pins &spi0_cs_pins>; + cs-gpios = <&gpio 8 1>, <&gpio 7 1>; + + spidev0: spidev@0{ + compatible = "spidev"; + reg = <0>; /* CE0 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + }; + + spidev1: spidev@1{ + compatible = "spidev"; + reg = <1>; /* CE1 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + }; +}; + +&i2c0if { + clock-frequency = <100000>; +}; + +&i2c1 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c1_pins>; + clock-frequency = <100000>; +}; + +&i2c2 { + clock-frequency = <100000>; +}; + +&i2s { + pinctrl-names = "default"; + pinctrl-0 = <&i2s_pins>; +}; + +&led_act { + gpios = <&gpio 47 GPIO_ACTIVE_LOW>; + default-state = "off"; + linux,default-trigger = "actpwr"; +}; + +&hdmi { + hpd-gpios = <&gpio 46 GPIO_ACTIVE_LOW>; +}; + +&vchiq { + pinctrl-names = "default"; + pinctrl-0 = <&audio_pins>; +}; + +&cam1_reg { + gpio = <&gpio 41 GPIO_ACTIVE_HIGH>; +}; + +cam0_reg: &cam_dummy_reg { +}; + +i2c_arm: &i2c1 {}; +i2c_vc: &i2c0 {}; +i2c_csi_dsi0: &i2c0 {}; + +/ { + __overrides__ { + audio = <&chosen>,"bootargs{on='snd_bcm2835.enable_hdmi=1',off='snd_bcm2835.enable_hdmi=0'}"; + + act_led_gpio = <&led_act>,"gpios:4"; + act_led_activelow = <&led_act>,"gpios:8"; + act_led_trigger = <&led_act>,"linux,default-trigger"; + }; +}; diff --git a/arch/arm/boot/dts/broadcom/bcm2708-rpi.dtsi b/arch/arm/boot/dts/broadcom/bcm2708-rpi.dtsi new file mode 100644 index 00000000000000..f4aedb5c532b51 --- /dev/null +++ b/arch/arm/boot/dts/broadcom/bcm2708-rpi.dtsi @@ -0,0 +1,63 @@ +/* Downstream modifications common to bcm2835, bcm2836, bcm2837 */ + +#define i2c0 i2c0mux +#include "bcm2835-rpi.dtsi" +#undef i2c0 +#include "bcm270x-rpi.dtsi" + +/ { + memory@0 { + device_type = "memory"; + reg = <0x0 0x0>; + }; + + aliases { + i2c2 = &i2c2; + }; + + __overrides__ { + hdmi = <&hdmi>,"status"; + i2c2_iknowwhatimdoing = <&i2c2>,"status"; + i2c2_baudrate = <&i2c2>,"clock-frequency:0"; + nvmem_cust_rw = <&nvmem_cust>,"rw?"; + sd = <&sdhost>,"status"; + sd_poll_once = <&sdhost>,"non-removable?"; + }; +}; + +&soc { + nvmem { + compatible = "simple-bus"; + #address-cells = <1>; + #size-cells = <1>; + + nvmem_otp: nvmem_otp { + compatible = "raspberrypi,rpi-otp"; + firmware = <&firmware>; + reg = <0 192>; + status = "okay"; + }; + + nvmem_cust: nvmem_cust { + compatible = "raspberrypi,rpi-otp"; + firmware = <&firmware>; + reg = <1 8>; + status = "okay"; + }; + }; +}; + +&sdhost { + pinctrl-names = "default"; + pinctrl-0 = <&sdhost_gpio48>; + status = "okay"; +}; + +&hdmi { + power-domains = <&power RPI_POWER_DOMAIN_HDMI>; + status = "disabled"; +}; + +&i2c2 { + status = "disabled"; +}; diff --git a/arch/arm/boot/dts/broadcom/bcm2708.dtsi b/arch/arm/boot/dts/broadcom/bcm2708.dtsi new file mode 100644 index 00000000000000..fdc7f2423bbe62 --- /dev/null +++ b/arch/arm/boot/dts/broadcom/bcm2708.dtsi @@ -0,0 +1,19 @@ +#define i2c0 i2c0if +#include "bcm2835.dtsi" +#undef i2c0 +#include "bcm270x.dtsi" + +/ { + __overrides__ { + arm_freq; + }; +}; + +&soc { + dma-ranges = <0x80000000 0x00000000 0x20000000>, + <0x7e000000 0x20000000 0x02000000>; +}; + +&vc4 { + status = "disabled"; +}; diff --git a/arch/arm/boot/dts/broadcom/bcm2709-rpi-2-b.dts b/arch/arm/boot/dts/broadcom/bcm2709-rpi-2-b.dts new file mode 100644 index 00000000000000..7796e545da43fb --- /dev/null +++ b/arch/arm/boot/dts/broadcom/bcm2709-rpi-2-b.dts @@ -0,0 +1,204 @@ +/dts-v1/; + +#include "bcm2709.dtsi" +#include "bcm2709-rpi.dtsi" +#include "bcm283x-rpi-smsc9514.dtsi" +#include "bcm283x-rpi-csi1-2lane.dtsi" +#include "bcm283x-rpi-i2c0mux_0_28.dtsi" +#include "bcm283x-rpi-led-deprecated.dtsi" + +/ { + compatible = "raspberrypi,2-model-b", "brcm,bcm2836"; + model = "Raspberry Pi 2 Model B"; +}; + +&gpio { + /* + * Taken from rpi_SCH_2b_1p2_reduced.pdf and + * the official GPU firmware DT blob. + * + * Legend: + * "NC" = not connected (no rail from the SoC) + * "FOO" = GPIO line named "FOO" on the schematic + * "FOO_N" = GPIO line named "FOO" on schematic, active low + */ + gpio-line-names = "ID_SDA", + "ID_SCL", + "GPIO2", + "GPIO3", + "GPIO4", + "GPIO5", + "GPIO6", + "GPIO7", + "GPIO8", + "GPIO9", + "GPIO10", + "GPIO11", + "GPIO12", + "GPIO13", + "GPIO14", + "GPIO15", + "GPIO16", + "GPIO17", + "GPIO18", + "GPIO19", + "GPIO20", + "GPIO21", + "GPIO22", + "GPIO23", + "GPIO24", + "GPIO25", + "GPIO26", + "GPIO27", + "SDA0", + "SCL0", + "NC", /* GPIO30 */ + "LAN_RUN", + "CAM_GPIO1", + "NC", /* GPIO33 */ + "NC", /* GPIO34 */ + "PWR_LOW_N", + "NC", /* GPIO36 */ + "NC", /* GPIO37 */ + "USB_LIMIT", + "NC", /* GPIO39 */ + "PWM0_OUT", + "CAM_GPIO0", + "SMPS_SCL", + "SMPS_SDA", + "ETH_CLK", + "PWM1_OUT", + "HDMI_HPD_N", + "STATUS_LED", + /* Used by SD Card */ + "SD_CLK_R", + "SD_CMD_R", + "SD_DATA0_R", + "SD_DATA1_R", + "SD_DATA2_R", + "SD_DATA3_R"; + + spi0_pins: spi0_pins { + brcm,pins = <9 10 11>; + brcm,function = <4>; /* alt0 */ + }; + + spi0_cs_pins: spi0_cs_pins { + brcm,pins = <8 7>; + brcm,function = <1>; /* output */ + }; + + i2c0_pins: i2c0 { + brcm,pins = <0 1>; + brcm,function = <4>; + }; + + i2c1_pins: i2c1 { + brcm,pins = <2 3>; + brcm,function = <4>; + }; + + i2s_pins: i2s { + brcm,pins = <18 19 20 21>; + brcm,function = <4>; /* alt0 */ + }; + + audio_pins: audio_pins { + brcm,pins = <40 45>; + brcm,function = <4>; + brcm,pull = <0>; + }; +}; + +&uart0 { + status = "okay"; +}; + +&spi0 { + pinctrl-names = "default"; + pinctrl-0 = <&spi0_pins &spi0_cs_pins>; + cs-gpios = <&gpio 8 1>, <&gpio 7 1>; + + spidev0: spidev@0{ + compatible = "spidev"; + reg = <0>; /* CE0 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + }; + + spidev1: spidev@1{ + compatible = "spidev"; + reg = <1>; /* CE1 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + }; +}; + +&i2c0if { + clock-frequency = <100000>; +}; + +&i2c1 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c1_pins>; + clock-frequency = <100000>; +}; + +&i2c2 { + clock-frequency = <100000>; +}; + +&i2s { + pinctrl-names = "default"; + pinctrl-0 = <&i2s_pins>; +}; + +&led_act { + gpios = <&gpio 47 GPIO_ACTIVE_HIGH>; + default-state = "off"; + linux,default-trigger = "mmc0"; +}; + +&leds { + led_pwr: led-pwr { + label = "PWR"; + gpios = <&gpio 35 GPIO_ACTIVE_HIGH>; + default-state = "off"; + linux,default-trigger = "input"; + }; +}; + +&hdmi { + hpd-gpios = <&gpio 46 GPIO_ACTIVE_LOW>; +}; + +&vchiq { + pinctrl-names = "default"; + pinctrl-0 = <&audio_pins>; +}; + +&cam1_reg { + gpio = <&gpio 41 GPIO_ACTIVE_HIGH>; +}; + +cam0_reg: &cam_dummy_reg { +}; + +i2c_csi_dsi0: &i2c0 { +}; + +/ { + __overrides__ { + audio = <&chosen>,"bootargs{on='snd_bcm2835.enable_headphones=1 snd_bcm2835.enable_hdmi=1',off='snd_bcm2835.enable_headphones=0 snd_bcm2835.enable_hdmi=0'}"; + + act_led_gpio = <&led_act>,"gpios:4"; + act_led_activelow = <&led_act>,"gpios:8"; + act_led_trigger = <&led_act>,"linux,default-trigger"; + + pwr_led_gpio = <&led_pwr>,"gpios:4"; + pwr_led_activelow = <&led_pwr>,"gpios:8"; + pwr_led_trigger = <&led_pwr>,"linux,default-trigger"; + }; +}; diff --git a/arch/arm/boot/dts/broadcom/bcm2709-rpi-cm2.dts b/arch/arm/boot/dts/broadcom/bcm2709-rpi-cm2.dts new file mode 100644 index 00000000000000..36d00aa889a39a --- /dev/null +++ b/arch/arm/boot/dts/broadcom/bcm2709-rpi-cm2.dts @@ -0,0 +1,215 @@ +/dts-v1/; + +#include "bcm2709.dtsi" +#include "bcm2709-rpi.dtsi" +#include "bcm283x-rpi-csi0-2lane.dtsi" +#include "bcm283x-rpi-csi1-4lane.dtsi" +#include "bcm283x-rpi-i2c0mux_0_28.dtsi" +#include "bcm283x-rpi-led-deprecated.dtsi" + +/ { + compatible = "raspberrypi,2-compute-module", "brcm,bcm2836"; + model = "Raspberry Pi Compute Module 2"; +}; + +&cam1_reg { + gpio = <&gpio 2 GPIO_ACTIVE_HIGH>; + status = "disabled"; +}; + +cam0_reg: &cam0_regulator { + gpio = <&gpio 30 GPIO_ACTIVE_HIGH>; +}; + +i2c_csi_dsi0: &i2c0 { +}; + +&uart0 { + status = "okay"; +}; + +&gpio { + /* + * This is based on the official GPU firmware DT blob. + * + * Legend: + * "NC" = not connected (no rail from the SoC) + * "FOO" = GPIO line named "FOO" on the schematic + * "FOO_N" = GPIO line named "FOO" on schematic, active low + */ + gpio-line-names = "GPIO0", + "GPIO1", + "GPIO2", + "GPIO3", + "GPIO4", + "GPIO5", + "GPIO6", + "GPIO7", + "GPIO8", + "GPIO9", + "GPIO10", + "GPIO11", + "GPIO12", + "GPIO13", + "GPIO14", + "GPIO15", + "GPIO16", + "GPIO17", + "GPIO18", + "GPIO19", + "GPIO20", + "GPIO21", + "GPIO22", + "GPIO23", + "GPIO24", + "GPIO25", + "GPIO26", + "GPIO27", + "GPIO28", + "GPIO29", + "GPIO30", + "GPIO31", + "GPIO32", + "GPIO33", + "GPIO34", + "GPIO35", + "GPIO36", + "GPIO37", + "GPIO38", + "GPIO39", + "GPIO40", + "GPIO41", + "GPIO42", + "GPIO43", + "GPIO44", + "GPIO45", + "SMPS_SCL", + "SMPS_SDA", + /* Used by eMMC */ + "SD_CLK_R", + "SD_CMD_R", + "SD_DATA0_R", + "SD_DATA1_R", + "SD_DATA2_R", + "SD_DATA3_R"; + + spi0_pins: spi0_pins { + brcm,pins = <9 10 11>; + brcm,function = <4>; /* alt0 */ + }; + + spi0_cs_pins: spi0_cs_pins { + brcm,pins = <8 7>; + brcm,function = <1>; /* output */ + }; + + i2c0_pins: i2c0 { + brcm,pins = <0 1>; + brcm,function = <4>; + }; + + i2c1_pins: i2c1 { + brcm,pins = <2 3>; + brcm,function = <4>; + }; + + i2s_pins: i2s { + brcm,pins = <18 19 20 21>; + brcm,function = <4>; /* alt0 */ + }; + + audio_pins: audio_pins { + brcm,pins; + brcm,function; + }; +}; + +&firmware { + expgpio: expgpio { + compatible = "raspberrypi,firmware-gpio"; + gpio-controller; + #gpio-cells = <2>; + gpio-line-names = "HDMI_HPD_N", + "EMMC_EN_N", + "NC", + "NC", + "NC", + "NC", + "NC", + "NC"; + status = "okay"; + }; + + virtgpio: virtgpio { + compatible = "brcm,bcm2835-virtgpio"; + gpio-controller; + #gpio-cells = <2>; + status = "okay"; + }; +}; + +&spi0 { + pinctrl-names = "default"; + pinctrl-0 = <&spi0_pins &spi0_cs_pins>; + cs-gpios = <&gpio 8 1>, <&gpio 7 1>; + + spidev0: spidev@0{ + compatible = "spidev"; + reg = <0>; /* CE0 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + }; + + spidev1: spidev@1{ + compatible = "spidev"; + reg = <1>; /* CE1 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + }; +}; + +&i2c0if { + clock-frequency = <100000>; +}; + +&i2c1 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c1_pins>; + clock-frequency = <100000>; +}; + +&i2c2 { + clock-frequency = <100000>; +}; + +&i2s { + pinctrl-names = "default"; + pinctrl-0 = <&i2s_pins>; +}; + +&led_act { + gpios = <&virtgpio 0 GPIO_ACTIVE_HIGH>; + default-state = "off"; + linux,default-trigger = "mmc0"; +}; + +&hdmi { + hpd-gpios = <&expgpio 0 GPIO_ACTIVE_LOW>; +}; + +&vchiq { + pinctrl-names = "default"; + pinctrl-0 = <&audio_pins>; +}; + +/ { + __overrides__ { + audio = <&chosen>,"bootargs{on='snd_bcm2835.enable_hdmi=1',off='snd_bcm2835.enable_hdmi=0'}"; + + act_led_gpio = <&led_act>,"gpios:4"; + act_led_activelow = <&led_act>,"gpios:8"; + act_led_trigger = <&led_act>,"linux,default-trigger"; + }; +}; diff --git a/arch/arm/boot/dts/broadcom/bcm2709-rpi.dtsi b/arch/arm/boot/dts/broadcom/bcm2709-rpi.dtsi new file mode 100644 index 00000000000000..7335e7fbcb7144 --- /dev/null +++ b/arch/arm/boot/dts/broadcom/bcm2709-rpi.dtsi @@ -0,0 +1,8 @@ +#include "bcm2708-rpi.dtsi" + +&vchiq { + compatible = "brcm,bcm2836-vchiq", "brcm,bcm2835-vchiq"; +}; + +i2c_arm: &i2c1 {}; +i2c_vc: &i2c0 {}; diff --git a/arch/arm/boot/dts/broadcom/bcm2709.dtsi b/arch/arm/boot/dts/broadcom/bcm2709.dtsi new file mode 100644 index 00000000000000..868f65f922ff4d --- /dev/null +++ b/arch/arm/boot/dts/broadcom/bcm2709.dtsi @@ -0,0 +1,29 @@ +#define i2c0 i2c0if +#include "bcm2836.dtsi" +#undef i2c0 +#include "bcm270x.dtsi" + +/ { + soc { + ranges = <0x7e000000 0x3f000000 0x01000000>, + <0x40000000 0x40000000 0x00040000>; + + dma-ranges = <0xc0000000 0x00000000 0x3f000000>, + <0x7e000000 0x3f000000 0x01000000>; + }; + + __overrides__ { + arm_freq = <&v7_cpu0>, "clock-frequency:0", + <&v7_cpu1>, "clock-frequency:0", + <&v7_cpu2>, "clock-frequency:0", + <&v7_cpu3>, "clock-frequency:0"; + }; +}; + +&system_timer { + status = "disabled"; +}; + +&vc4 { + status = "disabled"; +}; diff --git a/arch/arm/boot/dts/broadcom/bcm270x-rpi.dtsi b/arch/arm/boot/dts/broadcom/bcm270x-rpi.dtsi new file mode 100644 index 00000000000000..eeef9788d64929 --- /dev/null +++ b/arch/arm/boot/dts/broadcom/bcm270x-rpi.dtsi @@ -0,0 +1,201 @@ +/* Downstream modifications to bcm2835-rpi.dtsi */ + +/ { + aliases: aliases { + aux = &aux; + sound = &sound; + soc = &soc; + dma = &dma; + intc = &intc; + watchdog = &watchdog; + random = &random; + mailbox = &mailbox; + gpio = &gpio; + uart0 = &uart0; + uart1 = &uart1; + sdhost = &sdhost; + mmc = &mmc; + mmc1 = &mmc; + mmc0 = &sdhost; + i2s = &i2s; + i2c0 = &i2c0; + i2c1 = &i2c1; + i2c10 = &i2c_csi_dsi; + i2c = &i2c_arm; + spi0 = &spi0; + spi1 = &spi1; + spi2 = &spi2; + usb = &usb; + leds = &leds; + fb = &fb; + thermal = &thermal; + axiperf = &axiperf; + }; + + /* Define these notional regulators for use by overlays */ + vdd_3v3_reg: fixedregulator_3v3 { + compatible = "regulator-fixed"; + regulator-always-on; + regulator-max-microvolt = <3300000>; + regulator-min-microvolt = <3300000>; + regulator-name = "3v3"; + }; + + vdd_5v0_reg: fixedregulator_5v0 { + compatible = "regulator-fixed"; + regulator-always-on; + regulator-max-microvolt = <5000000>; + regulator-min-microvolt = <5000000>; + regulator-name = "5v0"; + }; + + soc { + gpiomem { + compatible = "brcm,bcm2835-gpiomem"; + reg = <0x7e200000 0x1000>; + }; + + fb: fb { + compatible = "brcm,bcm2708-fb"; + firmware = <&firmware>; + status = "okay"; + }; + + /* External sound card */ + sound: sound { + status = "disabled"; + }; + }; + + __overrides__ { + cache_line_size; + + uart0 = <&uart0>,"status"; + uart1 = <&uart1>,"status"; + i2s = <&i2s>,"status"; + spi = <&spi0>,"status"; + i2c0 = <&i2c0if>,"status",<&i2c0mux>,"status"; + i2c1 = <&i2c1>,"status"; + i2c = <&i2c1>,"status"; + i2c_arm = <&i2c1>,"status"; + i2c_vc = <&i2c0if>,"status",<&i2c0mux>,"status"; + i2c0_baudrate = <&i2c0if>,"clock-frequency:0"; + i2c1_baudrate = <&i2c1>,"clock-frequency:0"; + i2c_baudrate = <&i2c1>,"clock-frequency:0"; + i2c_arm_baudrate = <&i2c1>,"clock-frequency:0"; + i2c_vc_baudrate = <&i2c0if>,"clock-frequency:0"; + + watchdog = <&watchdog>,"status"; + random = <&random>,"status"; + sd_overclock = <&sdhost>,"brcm,overclock-50:0"; + sd_force_pio = <&sdhost>,"brcm,force-pio?"; + sd_pio_limit = <&sdhost>,"brcm,pio-limit:0"; + sd_debug = <&sdhost>,"brcm,debug"; + sdio_overclock = <&mmc>,"brcm,overclock-50:0", + <&mmcnr>,"brcm,overclock-50:0"; + axiperf = <&axiperf>,"status"; + drm_fb0_vc4 = <&aliases>, "drm-fb0=",&vc4; + drm_fb1_vc4 = <&aliases>, "drm-fb1=",&vc4; + drm_fb2_vc4 = <&aliases>, "drm-fb2=",&vc4; + + cam1_sync = <&csi1>, "sync-gpios:0=", <&gpio>, + <&csi1>, "sync-gpios:4", + <&csi1>, "sync-gpios:8=", <GPIO_ACTIVE_HIGH>; + cam1_sync_inverted = <&csi1>, "sync-gpios:0=", <&gpio>, + <&csi1>, "sync-gpios:4", + <&csi1>, "sync-gpios:8=", <GPIO_ACTIVE_LOW>; + cam0_sync = <&csi0>, "sync-gpios:0=", <&gpio>, + <&csi0>, "sync-gpios:4", + <&csi0>, "sync-gpios:8=", <GPIO_ACTIVE_HIGH>; + cam0_sync_inverted = <&csi0>, "sync-gpios:0=", <&gpio>, + <&csi0>, "sync-gpios:4", + <&csi0>, "sync-gpios:8=", <GPIO_ACTIVE_LOW>; + + cam0_reg = <&cam0_reg>,"status"; + cam0_reg_gpio = <&cam0_reg>,"gpio:4", + <&cam0_reg>,"gpio:0=", <&gpio>; + cam1_reg = <&cam1_reg>,"status"; + cam1_reg_gpio = <&cam1_reg>,"gpio:4", + <&cam1_reg>,"gpio:0=", <&gpio>; + + strict_gpiod = <&chosen>, "bootargs=pinctrl_bcm2835.persist_gpio_outputs=n"; + }; +}; + +&uart0 { + skip-init; +}; + +&uart1 { + skip-init; +}; + +&txp { + status = "disabled"; +}; + +&i2c0if { + status = "disabled"; +}; + +&i2c0mux { + pinctrl-names = "i2c0", "i2c_csi_dsi"; + /delete-property/ clock-frequency; + status = "disabled"; +}; + +&i2c1 { + status = "disabled"; +}; + +i2s_clk_producer: &i2s {}; +i2s_clk_consumer: &i2s {}; + +&clocks { + firmware = <&firmware>; +}; + +&sdhci { + pinctrl-names = "default"; + pinctrl-0 = <&emmc_gpio48>; + bus-width = <4>; +}; + +&cpu_thermal { + // Add some labels + thermal_trips: trips { + cpu-crit { + // Raise upstream limit of 90C + temperature = <110000>; + }; + }; + cooling_maps: cooling-maps { + }; +}; + +&vec { + clocks = <&firmware_clocks 15>; + status = "disabled"; +}; + +&firmware { + vcio: vcio { + compatible = "raspberrypi,vcio"; + }; +}; + +&vc4 { + raspberrypi,firmware = <&firmware>; +}; + +#ifndef BCM2711 + +&hdmi { + reg-names = "hdmi", + "hd"; + clocks = <&firmware_clocks 9>, + <&firmware_clocks 13>; + dmas = <&dma (17|(1<<27)|(1<<24))>; +}; + +#endif diff --git a/arch/arm/boot/dts/broadcom/bcm270x.dtsi b/arch/arm/boot/dts/broadcom/bcm270x.dtsi new file mode 100644 index 00000000000000..678bee7d96e7c2 --- /dev/null +++ b/arch/arm/boot/dts/broadcom/bcm270x.dtsi @@ -0,0 +1,264 @@ +/* Downstream bcm283x.dtsi diff */ +#include <dt-bindings/power/raspberrypi-power.h> + +/ { + chosen: chosen { + // Disable audio by default + bootargs = "coherent_pool=1M snd_bcm2835.enable_headphones=0 cgroup_disable=memory"; + stdout-path = "serial0:115200n8"; + }; + + soc: soc { + watchdog: watchdog@7e100000 { + /* Add label */ + }; + + random: rng@7e104000 { + /* Add label */ + }; + + spi0: spi@7e204000 { + /* Add label */ + }; + +#ifndef BCM2711 + pixelvalve0: pixelvalve@7e206000 { + /* Add label */ + status = "disabled"; + }; + + pixelvalve1: pixelvalve@7e207000 { + /* Add label */ + status = "disabled"; + }; +#endif + + /delete-node/ mmc@7e300000; + + sdhci: mmc: mmc@7e300000 { + compatible = "brcm,bcm2835-mmc", "brcm,bcm2835-sdhci"; + reg = <0x7e300000 0x100>; + interrupts = <2 30>; + clocks = <&clocks BCM2835_CLOCK_EMMC>; + dmas = <&dma 11>; + dma-names = "rx-tx"; + brcm,overclock-50 = <0>; + status = "disabled"; + }; + + /* A clone of mmc but with non-removable set */ + mmcnr: mmcnr@7e300000 { + compatible = "brcm,bcm2835-mmc", "brcm,bcm2835-sdhci"; + reg = <0x7e300000 0x100>; + interrupts = <2 30>; + clocks = <&clocks BCM2835_CLOCK_EMMC>; + dmas = <&dma 11>; + dma-names = "rx-tx"; + brcm,overclock-50 = <0>; + non-removable; + status = "disabled"; + }; + + hvs: hvs@7e400000 { + /* Add label */ + status = "disabled"; + }; + + firmwarekms: firmwarekms@7e600000 { + compatible = "raspberrypi,rpi-firmware-kms"; + /* SMI interrupt reg */ + reg = <0x7e600000 0x100>; + interrupts = <2 16>; + brcm,firmware = <&firmware>; + status = "disabled"; + }; + + smi: smi@7e600000 { + compatible = "brcm,bcm2835-smi"; + reg = <0x7e600000 0x100>; + interrupts = <2 16>; + clocks = <&clocks BCM2835_CLOCK_SMI>; + assigned-clocks = <&clocks BCM2835_CLOCK_SMI>; + assigned-clock-rates = <125000000>; + dmas = <&dma 4>; + dma-names = "rx-tx"; + status = "disabled"; + }; + +#ifndef BCM2711 + pixelvalve2: pixelvalve@7e807000 { + /* Add label */ + status = "disabled"; + }; +#endif + + hdmi@7e902000 { /* hdmi */ + status = "disabled"; + }; + + usb@7e980000 { /* usb */ + compatible = "brcm,bcm2708-usb"; + reg = <0x7e980000 0x10000>, + <0x7e006000 0x1000>; + interrupt-names = "usb", + "soft"; + interrupts = <1 9>, + <2 0>; + }; + +#ifndef BCM2711 + v3d@7ec00000 { /* vd3 */ + compatible = "brcm,vc4-v3d"; + power-domains = <&power RPI_POWER_DOMAIN_V3D>; + status = "disabled"; + }; +#endif + + axiperf: axiperf { + compatible = "brcm,bcm2835-axiperf"; + reg = <0x7e009800 0x100>, + <0x7ee08000 0x100>; + firmware = <&firmware>; + status = "disabled"; + }; + + i2c0mux: i2c0mux { + compatible = "i2c-mux-pinctrl"; + #address-cells = <1>; + #size-cells = <0>; + + i2c-parent = <&i2c0if>; + + status = "disabled"; + + i2c0: i2c@0 { + reg = <0>; + #address-cells = <1>; + #size-cells = <0>; + }; + + i2c_csi_dsi: i2c@1 { + reg = <1>; + #address-cells = <1>; + #size-cells = <0>; + }; + }; + }; + + cam1_reg: cam1_regulator { + compatible = "regulator-fixed"; + regulator-name = "cam1-reg"; + enable-active-high; + /* Needs to be enabled, as removing a regulator is very unsafe */ + status = "okay"; + }; + + cam1_clk: cam1_clk { + compatible = "fixed-clock"; + #clock-cells = <0>; + status = "disabled"; + }; + + cam0_regulator: cam0_regulator { + compatible = "regulator-fixed"; + regulator-name = "cam0-reg"; + enable-active-high; + status = "disabled"; + }; + + cam0_clk: cam0_clk { + compatible = "fixed-clock"; + #clock-cells = <0>; + status = "disabled"; + }; + + cam_dummy_reg: cam_dummy_reg { + compatible = "regulator-fixed"; + regulator-name = "cam-dummy-reg"; + status = "okay"; + }; + + __overrides__ { + cam0-pwdn-ctrl; + cam0-pwdn; + cam0-led-ctrl; + cam0-led; + }; +}; + +&gpio { + interrupts = <2 17>, <2 18>; + + dpi_18bit_cpadhi_gpio0: dpi_18bit_cpadhi_gpio0 { + brcm,pins = <0 1 2 3 4 5 6 7 8 9 + 12 13 14 15 16 17 + 20 21 22 23 24 25>; + brcm,function = <BCM2835_FSEL_ALT2>; + brcm,pull = <0>; /* no pull */ + }; + dpi_18bit_cpadhi_gpio2: dpi_18bit_cpadhi_gpio2 { + brcm,pins = <2 3 4 5 6 7 8 9 + 12 13 14 15 16 17 + 20 21 22 23 24 25>; + brcm,function = <BCM2835_FSEL_ALT2>; + }; + dpi_18bit_gpio0: dpi_18bit_gpio0 { + brcm,pins = <0 1 2 3 4 5 6 7 8 9 10 11 + 12 13 14 15 16 17 18 19 + 20 21>; + brcm,function = <BCM2835_FSEL_ALT2>; + }; + dpi_18bit_gpio2: dpi_18bit_gpio2 { + brcm,pins = <2 3 4 5 6 7 8 9 10 11 + 12 13 14 15 16 17 18 19 + 20 21>; + brcm,function = <BCM2835_FSEL_ALT2>; + }; + dpi_16bit_gpio0: dpi_16bit_gpio0 { + brcm,pins = <0 1 2 3 4 5 6 7 8 9 10 11 + 12 13 14 15 16 17 18 19>; + brcm,function = <BCM2835_FSEL_ALT2>; + }; + dpi_16bit_gpio2: dpi_16bit_gpio2 { + brcm,pins = <2 3 4 5 6 7 8 9 10 11 + 12 13 14 15 16 17 18 19>; + brcm,function = <BCM2835_FSEL_ALT2>; + }; + dpi_16bit_cpadhi_gpio0: dpi_16bit_cpadhi_gpio0 { + brcm,pins = <0 1 2 3 4 5 6 7 8 + 12 13 14 15 16 17 + 20 21 22 23 24>; + brcm,function = <BCM2835_FSEL_ALT2>; + }; + dpi_16bit_cpadhi_gpio2: dpi_16bit_cpadhi_gpio2 { + brcm,pins = <2 3 4 5 6 7 8 + 12 13 14 15 16 17 + 20 21 22 23 24>; + brcm,function = <BCM2835_FSEL_ALT2>; + }; +}; + +&uart0 { + /* Enable CTS bug workaround */ + cts-event-workaround; +}; + +&i2s { + #sound-dai-cells = <0>; + dmas = <&dma 2>, <&dma 3>; + dma-names = "tx", "rx"; +}; + +&sdhost { + dmas = <&dma (13|(1<<29))>; + dma-names = "rx-tx"; + bus-width = <4>; + brcm,overclock-50 = <0>; + brcm,pio-limit = <1>; + firmware = <&firmware>; +}; + +&spi0 { + dmas = <&dma 6>, <&dma 7>; + dma-names = "tx", "rx"; +}; diff --git a/arch/arm/boot/dts/broadcom/bcm2710-rpi-2-b.dts b/arch/arm/boot/dts/broadcom/bcm2710-rpi-2-b.dts new file mode 100644 index 00000000000000..ce48eb6073f0cd --- /dev/null +++ b/arch/arm/boot/dts/broadcom/bcm2710-rpi-2-b.dts @@ -0,0 +1,204 @@ +/dts-v1/; + +#include "bcm2710.dtsi" +#include "bcm2709-rpi.dtsi" +#include "bcm283x-rpi-smsc9514.dtsi" +#include "bcm283x-rpi-csi1-2lane.dtsi" +#include "bcm283x-rpi-i2c0mux_0_28.dtsi" +#include "bcm283x-rpi-led-deprecated.dtsi" + +/ { + compatible = "raspberrypi,2-model-b-rev2", "brcm,bcm2837"; + model = "Raspberry Pi 2 Model B rev 1.2"; +}; + +&gpio { + /* + * Taken from rpi_SCH_2b_1p2_reduced.pdf and + * the official GPU firmware DT blob. + * + * Legend: + * "NC" = not connected (no rail from the SoC) + * "FOO" = GPIO line named "FOO" on the schematic + * "FOO_N" = GPIO line named "FOO" on schematic, active low + */ + gpio-line-names = "ID_SDA", + "ID_SCL", + "GPIO2", + "GPIO3", + "GPIO4", + "GPIO5", + "GPIO6", + "GPIO7", + "GPIO8", + "GPIO9", + "GPIO10", + "GPIO11", + "GPIO12", + "GPIO13", + "GPIO14", + "GPIO15", + "GPIO16", + "GPIO17", + "GPIO18", + "GPIO19", + "GPIO20", + "GPIO21", + "GPIO22", + "GPIO23", + "GPIO24", + "GPIO25", + "GPIO26", + "GPIO27", + "SDA0", + "SCL0", + "NC", /* GPIO30 */ + "LAN_RUN", + "CAM_GPIO1", + "NC", /* GPIO33 */ + "NC", /* GPIO34 */ + "PWR_LOW_N", + "NC", /* GPIO36 */ + "NC", /* GPIO37 */ + "USB_LIMIT", + "NC", /* GPIO39 */ + "PWM0_OUT", + "CAM_GPIO0", + "SMPS_SCL", + "SMPS_SDA", + "ETH_CLK", + "PWM1_OUT", + "HDMI_HPD_N", + "STATUS_LED", + /* Used by SD Card */ + "SD_CLK_R", + "SD_CMD_R", + "SD_DATA0_R", + "SD_DATA1_R", + "SD_DATA2_R", + "SD_DATA3_R"; + + spi0_pins: spi0_pins { + brcm,pins = <9 10 11>; + brcm,function = <4>; /* alt0 */ + }; + + spi0_cs_pins: spi0_cs_pins { + brcm,pins = <8 7>; + brcm,function = <1>; /* output */ + }; + + i2c0_pins: i2c0 { + brcm,pins = <0 1>; + brcm,function = <4>; + }; + + i2c1_pins: i2c1 { + brcm,pins = <2 3>; + brcm,function = <4>; + }; + + i2s_pins: i2s { + brcm,pins = <18 19 20 21>; + brcm,function = <4>; /* alt0 */ + }; + + audio_pins: audio_pins { + brcm,pins = <40 45>; + brcm,function = <4>; + brcm,pull = <0>; + }; +}; + +&uart0 { + status = "okay"; +}; + +&spi0 { + pinctrl-names = "default"; + pinctrl-0 = <&spi0_pins &spi0_cs_pins>; + cs-gpios = <&gpio 8 1>, <&gpio 7 1>; + + spidev0: spidev@0{ + compatible = "spidev"; + reg = <0>; /* CE0 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + }; + + spidev1: spidev@1{ + compatible = "spidev"; + reg = <1>; /* CE1 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + }; +}; + +&i2c0if { + clock-frequency = <100000>; +}; + +&i2c1 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c1_pins>; + clock-frequency = <100000>; +}; + +&i2c2 { + clock-frequency = <100000>; +}; + +&i2s { + pinctrl-names = "default"; + pinctrl-0 = <&i2s_pins>; +}; + +&led_act { + gpios = <&gpio 47 GPIO_ACTIVE_HIGH>; + default-state = "off"; + linux,default-trigger = "mmc0"; +}; + +&leds { + led_pwr: led-pwr { + label = "PWR"; + gpios = <&gpio 35 GPIO_ACTIVE_HIGH>; + default-state = "off"; + linux,default-trigger = "input"; + }; +}; + +&hdmi { + hpd-gpios = <&gpio 46 GPIO_ACTIVE_LOW>; +}; + +&vchiq { + pinctrl-names = "default"; + pinctrl-0 = <&audio_pins>; +}; + +&cam1_reg { + gpio = <&gpio 41 GPIO_ACTIVE_HIGH>; +}; + +cam0_reg: &cam_dummy_reg { +}; + +i2c_csi_dsi0: &i2c0 { +}; + +/ { + __overrides__ { + audio = <&chosen>,"bootargs{on='snd_bcm2835.enable_headphones=1 snd_bcm2835.enable_hdmi=1',off='snd_bcm2835.enable_headphones=0 snd_bcm2835.enable_hdmi=0'}"; + + act_led_gpio = <&led_act>,"gpios:4"; + act_led_activelow = <&led_act>,"gpios:8"; + act_led_trigger = <&led_act>,"linux,default-trigger"; + + pwr_led_gpio = <&led_pwr>,"gpios:4"; + pwr_led_activelow = <&led_pwr>,"gpios:8"; + pwr_led_trigger = <&led_pwr>,"linux,default-trigger"; + }; +}; diff --git a/arch/arm/boot/dts/broadcom/bcm2710-rpi-3-b-plus.dts b/arch/arm/boot/dts/broadcom/bcm2710-rpi-3-b-plus.dts new file mode 100644 index 00000000000000..8973985e99028c --- /dev/null +++ b/arch/arm/boot/dts/broadcom/bcm2710-rpi-3-b-plus.dts @@ -0,0 +1,295 @@ +/dts-v1/; + +#include "bcm2710.dtsi" +#include "bcm2709-rpi.dtsi" +#include "bcm283x-rpi-lan7515.dtsi" +#include "bcm283x-rpi-csi1-2lane.dtsi" +#include "bcm283x-rpi-i2c0mux_0_44.dtsi" +#include "bcm271x-rpi-bt.dtsi" +#include "bcm283x-rpi-led-deprecated.dtsi" + +/ { + compatible = "raspberrypi,3-model-b-plus", "brcm,bcm2837"; + model = "Raspberry Pi 3 Model B+"; + + aliases { + serial0 = &uart1; + serial1 = &uart0; + mmc1 = &mmcnr; + }; +}; + +&gpio { + /* + * Taken from rpi_SCH_3bplus_1p0_reduced.pdf and + * the official GPU firmware DT blob. + * + * Legend: + * "NC" = not connected (no rail from the SoC) + * "FOO" = GPIO line named "FOO" on the schematic + * "FOO_N" = GPIO line named "FOO" on schematic, active low + */ + gpio-line-names = "ID_SDA", + "ID_SCL", + "GPIO2", + "GPIO3", + "GPIO4", + "GPIO5", + "GPIO6", + "GPIO7", + "GPIO8", + "GPIO9", + "GPIO10", + "GPIO11", + "GPIO12", + "GPIO13", + "GPIO14", + "GPIO15", + "GPIO16", + "GPIO17", + "GPIO18", + "GPIO19", + "GPIO20", + "GPIO21", + "GPIO22", + "GPIO23", + "GPIO24", + "GPIO25", + "GPIO26", + "GPIO27", + "HDMI_HPD_N", + "STATUS_LED_G", + /* Used by BT module */ + "CTS0", + "RTS0", + "TXD0", + "RXD0", + /* Used by Wifi */ + "SD1_CLK", + "SD1_CMD", + "SD1_DATA0", + "SD1_DATA1", + "SD1_DATA2", + "SD1_DATA3", + "PWM0_OUT", + "PWM1_OUT", + "ETH_CLK", + "WIFI_CLK", + "SDA0", + "SCL0", + "SMPS_SCL", + "SMPS_SDA", + /* Used by SD Card */ + "SD_CLK_R", + "SD_CMD_R", + "SD_DATA0_R", + "SD_DATA1_R", + "SD_DATA2_R", + "SD_DATA3_R"; + + spi0_pins: spi0_pins { + brcm,pins = <9 10 11>; + brcm,function = <4>; /* alt0 */ + }; + + spi0_cs_pins: spi0_cs_pins { + brcm,pins = <8 7>; + brcm,function = <1>; /* output */ + }; + + i2c0_pins: i2c0 { + brcm,pins = <0 1>; + brcm,function = <4>; + }; + + i2c1_pins: i2c1 { + brcm,pins = <2 3>; + brcm,function = <4>; + }; + + i2s_pins: i2s { + brcm,pins = <18 19 20 21>; + brcm,function = <4>; /* alt0 */ + }; + + sdio_pins: sdio_pins { + brcm,pins = <34 35 36 37 38 39>; + brcm,function = <7>; // alt3 = SD1 + brcm,pull = <0 2 2 2 2 2>; + }; + + bt_pins: bt_pins { + brcm,pins = <43>; + brcm,function = <4>; /* alt0:GPCLK2 */ + brcm,pull = <0>; + }; + + uart0_pins: uart0_pins { + brcm,pins = <32 33>; + brcm,function = <7>; /* alt3=UART0 */ + brcm,pull = <0 2>; + }; + + uart1_pins: uart1_pins { + brcm,pins; + brcm,function; + brcm,pull; + }; + + uart1_bt_pins: uart1_bt_pins { + brcm,pins = <32 33 30 31>; + brcm,function = <BCM2835_FSEL_ALT5>; /* alt5=UART1 */ + brcm,pull = <0 2 2 0>; + }; + + audio_pins: audio_pins { + brcm,pins = <40 41>; + brcm,function = <4>; + brcm,pull = <0>; + }; +}; + +&mmcnr { + pinctrl-names = "default"; + pinctrl-0 = <&sdio_pins>; + bus-width = <4>; + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + brcmf: wifi@1 { + reg = <1>; + compatible = "brcm,bcm4329-fmac"; + }; +}; + +&firmware { + expgpio: expgpio { + compatible = "raspberrypi,firmware-gpio"; + gpio-controller; + #gpio-cells = <2>; + gpio-line-names = "BT_ON", + "WL_ON", + "PWR_LED_R", + "LAN_RUN", + "NC", + "CAM_GPIO0", + "CAM_GPIO1", + "NC"; + status = "okay"; + }; +}; + +&uart0 { + pinctrl-names = "default"; + pinctrl-0 = <&uart0_pins &bt_pins>; + status = "okay"; +}; + +&uart1 { + pinctrl-names = "default"; + pinctrl-0 = <&uart1_pins>; + status = "okay"; +}; + +&spi0 { + pinctrl-names = "default"; + pinctrl-0 = <&spi0_pins &spi0_cs_pins>; + cs-gpios = <&gpio 8 1>, <&gpio 7 1>; + + spidev0: spidev@0{ + compatible = "spidev"; + reg = <0>; /* CE0 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + }; + + spidev1: spidev@1{ + compatible = "spidev"; + reg = <1>; /* CE1 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + }; +}; + +&i2c0if { + clock-frequency = <100000>; +}; + +&i2c1 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c1_pins>; + clock-frequency = <100000>; +}; + +&i2c2 { + clock-frequency = <100000>; +}; + +&i2s { + pinctrl-names = "default"; + pinctrl-0 = <&i2s_pins>; +}; + +&led_act { + gpios = <&gpio 29 GPIO_ACTIVE_HIGH>; + default-state = "off"; + linux,default-trigger = "mmc0"; +}; + +&leds { + led_pwr: led-pwr { + label = "PWR"; + gpios = <&expgpio 2 GPIO_ACTIVE_LOW>; + default-state = "off"; + linux,default-trigger = "default-on"; + }; +}; + +&hdmi { + hpd-gpios = <&gpio 28 GPIO_ACTIVE_LOW>; +}; + +&vchiq { + pinctrl-names = "default"; + pinctrl-0 = <&audio_pins>; +}; + +ð_phy { + microchip,eee-enabled; + microchip,tx-lpi-timer = <600>; /* non-aggressive*/ + microchip,downshift-after = <2>; +}; + +&cam1_reg { + gpio = <&expgpio 5 GPIO_ACTIVE_HIGH>; +}; + +cam0_reg: &cam_dummy_reg { +}; + +i2c_csi_dsi0: &i2c0 { +}; + +/ { + __overrides__ { + audio = <&chosen>,"bootargs{on='snd_bcm2835.enable_headphones=1 snd_bcm2835.enable_hdmi=1',off='snd_bcm2835.enable_headphones=0 snd_bcm2835.enable_hdmi=0'}"; + + act_led_gpio = <&led_act>,"gpios:4"; + act_led_activelow = <&led_act>,"gpios:8"; + act_led_trigger = <&led_act>,"linux,default-trigger"; + + pwr_led_gpio = <&led_pwr>,"gpios:4"; + pwr_led_activelow = <&led_pwr>,"gpios:8"; + pwr_led_trigger = <&led_pwr>,"linux,default-trigger"; + + eee = <ð_phy>,"microchip,eee-enabled?"; + tx_lpi_timer = <ð_phy>,"microchip,tx-lpi-timer:0"; + eth_led0 = <ð_phy>,"microchip,led-modes:0"; + eth_led1 = <ð_phy>,"microchip,led-modes:4"; + eth_downshift_after = <ð_phy>,"microchip,downshift-after:0"; + eth_max_speed = <ð_phy>,"max-speed:0"; + }; +}; diff --git a/arch/arm/boot/dts/broadcom/bcm2710-rpi-3-b.dts b/arch/arm/boot/dts/broadcom/bcm2710-rpi-3-b.dts new file mode 100644 index 00000000000000..35e6e99000834a --- /dev/null +++ b/arch/arm/boot/dts/broadcom/bcm2710-rpi-3-b.dts @@ -0,0 +1,293 @@ +/dts-v1/; + +#include "bcm2710.dtsi" +#include "bcm2709-rpi.dtsi" +#include "bcm283x-rpi-smsc9514.dtsi" +#include "bcm283x-rpi-csi1-2lane.dtsi" +#include "bcm283x-rpi-i2c0mux_0_44.dtsi" +#include "bcm271x-rpi-bt.dtsi" +#include "bcm283x-rpi-led-deprecated.dtsi" + +/ { + compatible = "raspberrypi,3-model-b", "brcm,bcm2837"; + model = "Raspberry Pi 3 Model B"; + + aliases { + serial0 = &uart1; + serial1 = &uart0; + mmc1 = &mmcnr; + }; +}; + +&gpio { + /* + * Taken from rpi_SCH_3b_1p2_reduced.pdf and + * the official GPU firmware DT blob. + * + * Legend: + * "NC" = not connected (no rail from the SoC) + * "FOO" = GPIO line named "FOO" on the schematic + * "FOO_N" = GPIO line named "FOO" on schematic, active low + */ + gpio-line-names = "ID_SDA", + "ID_SCL", + "GPIO2", + "GPIO3", + "GPIO4", + "GPIO5", + "GPIO6", + "GPIO7", + "GPIO8", + "GPIO9", + "GPIO10", + "GPIO11", + "GPIO12", + "GPIO13", + "GPIO14", + "GPIO15", + "GPIO16", + "GPIO17", + "GPIO18", + "GPIO19", + "GPIO20", + "GPIO21", + "GPIO22", + "GPIO23", + "GPIO24", + "GPIO25", + "GPIO26", + "GPIO27", + "NC", /* GPIO 28 */ + "LAN_RUN_BOOT", + /* Used by BT module */ + "CTS0", + "RTS0", + "TXD0", + "RXD0", + /* Used by Wifi */ + "SD1_CLK", + "SD1_CMD", + "SD1_DATA0", + "SD1_DATA1", + "SD1_DATA2", + "SD1_DATA3", + "PWM0_OUT", + "PWM1_OUT", + "ETH_CLK", + "WIFI_CLK", + "SDA0", + "SCL0", + "SMPS_SCL", + "SMPS_SDA", + /* Used by SD Card */ + "SD_CLK_R", + "SD_CMD_R", + "SD_DATA0_R", + "SD_DATA1_R", + "SD_DATA2_R", + "SD_DATA3_R"; + + spi0_pins: spi0_pins { + brcm,pins = <9 10 11>; + brcm,function = <4>; /* alt0 */ + }; + + spi0_cs_pins: spi0_cs_pins { + brcm,pins = <8 7>; + brcm,function = <1>; /* output */ + }; + + i2c0_pins: i2c0 { + brcm,pins = <0 1>; + brcm,function = <4>; + }; + + i2c1_pins: i2c1 { + brcm,pins = <2 3>; + brcm,function = <4>; + }; + + i2s_pins: i2s { + brcm,pins = <18 19 20 21>; + brcm,function = <4>; /* alt0 */ + }; + + sdio_pins: sdio_pins { + brcm,pins = <34 35 36 37 38 39>; + brcm,function = <7>; // alt3 = SD1 + brcm,pull = <0 2 2 2 2 2>; + }; + + bt_pins: bt_pins { + brcm,pins = <43>; + brcm,function = <4>; /* alt0:GPCLK2 */ + brcm,pull = <0>; + }; + + uart0_pins: uart0_pins { + brcm,pins = <32 33>; + brcm,function = <7>; /* alt3=UART0 */ + brcm,pull = <0 2>; + }; + + uart1_pins: uart1_pins { + brcm,pins; + brcm,function; + brcm,pull; + }; + + uart1_bt_pins: uart1_bt_pins { + brcm,pins = <32 33>; + brcm,function = <BCM2835_FSEL_ALT5>; /* alt5=UART1 */ + brcm,pull = <0 2>; + }; + + audio_pins: audio_pins { + brcm,pins = <40 41>; + brcm,function = <4>; + brcm,pull = <0>; + }; +}; + +&mmcnr { + pinctrl-names = "default"; + pinctrl-0 = <&sdio_pins>; + bus-width = <4>; + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + brcmf: wifi@1 { + reg = <1>; + compatible = "brcm,bcm4329-fmac"; + }; +}; + +&firmware { + expgpio: expgpio { + compatible = "raspberrypi,firmware-gpio"; + gpio-controller; + #gpio-cells = <2>; + gpio-line-names = "BT_ON", + "WL_ON", + "STATUS_LED", + "LAN_RUN", + "HDMI_HPD_N", + "CAM_GPIO0", + "CAM_GPIO1", + "PWR_LOW_N"; + status = "okay"; + }; + + virtgpio: virtgpio { + compatible = "brcm,bcm2835-virtgpio"; + gpio-controller; + #gpio-cells = <2>; + status = "okay"; + }; +}; + +&uart0 { + pinctrl-names = "default"; + pinctrl-0 = <&uart0_pins &bt_pins>; + status = "okay"; +}; + +&uart1 { + pinctrl-names = "default"; + pinctrl-0 = <&uart1_pins>; + status = "okay"; +}; + +&bt { + max-speed = <921600>; +}; + +&spi0 { + pinctrl-names = "default"; + pinctrl-0 = <&spi0_pins &spi0_cs_pins>; + cs-gpios = <&gpio 8 1>, <&gpio 7 1>; + + spidev0: spidev@0{ + compatible = "spidev"; + reg = <0>; /* CE0 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + }; + + spidev1: spidev@1{ + compatible = "spidev"; + reg = <1>; /* CE1 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + }; +}; + +&i2c0if { + clock-frequency = <100000>; +}; + +&i2c1 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c1_pins>; + clock-frequency = <100000>; +}; + +&i2c2 { + clock-frequency = <100000>; +}; + +&i2s { + pinctrl-names = "default"; + pinctrl-0 = <&i2s_pins>; +}; + +&led_act { + gpios = <&virtgpio 0 GPIO_ACTIVE_HIGH>; + default-state = "off"; + linux,default-trigger = "mmc0"; +}; + +&leds { + led_pwr: led-pwr { + label = "PWR"; + gpios = <&expgpio 7 GPIO_ACTIVE_HIGH>; + default-state = "off"; + linux,default-trigger = "input"; + }; +}; + +&hdmi { + hpd-gpios = <&expgpio 4 GPIO_ACTIVE_LOW>; +}; + +&vchiq { + pinctrl-names = "default"; + pinctrl-0 = <&audio_pins>; +}; + +&cam1_reg { + gpio = <&expgpio 5 GPIO_ACTIVE_HIGH>; +}; + +cam0_reg: &cam_dummy_reg { +}; + +i2c_csi_dsi0: &i2c0 { +}; + +/ { + __overrides__ { + audio = <&chosen>,"bootargs{on='snd_bcm2835.enable_headphones=1 snd_bcm2835.enable_hdmi=1',off='snd_bcm2835.enable_headphones=0 snd_bcm2835.enable_hdmi=0'}"; + + act_led_gpio = <&led_act>,"gpios:4"; + act_led_activelow = <&led_act>,"gpios:8"; + act_led_trigger = <&led_act>,"linux,default-trigger"; + + pwr_led_gpio = <&led_pwr>,"gpios:4"; + pwr_led_activelow = <&led_pwr>,"gpios:8"; + pwr_led_trigger = <&led_pwr>,"linux,default-trigger"; + }; +}; diff --git a/arch/arm/boot/dts/broadcom/bcm2710-rpi-cm3.dts b/arch/arm/boot/dts/broadcom/bcm2710-rpi-cm3.dts new file mode 100644 index 00000000000000..0d6e9e61f87751 --- /dev/null +++ b/arch/arm/boot/dts/broadcom/bcm2710-rpi-cm3.dts @@ -0,0 +1,215 @@ +/dts-v1/; + +#include "bcm2710.dtsi" +#include "bcm2709-rpi.dtsi" +#include "bcm283x-rpi-csi0-2lane.dtsi" +#include "bcm283x-rpi-csi1-4lane.dtsi" +#include "bcm283x-rpi-i2c0mux_0_28.dtsi" +#include "bcm283x-rpi-led-deprecated.dtsi" + +/ { + compatible = "raspberrypi,3-compute-module", "brcm,bcm2837"; + model = "Raspberry Pi Compute Module 3"; +}; + +&cam1_reg { + gpio = <&gpio 3 GPIO_ACTIVE_HIGH>; + status = "disabled"; +}; + +cam0_reg: &cam0_regulator { + gpio = <&gpio 31 GPIO_ACTIVE_HIGH>; +}; + +i2c_csi_dsi0: &i2c0 { +}; + +&uart0 { + status = "okay"; +}; + +&gpio { + /* + * This is based on the official GPU firmware DT blob. + * + * Legend: + * "NC" = not connected (no rail from the SoC) + * "FOO" = GPIO line named "FOO" on the schematic + * "FOO_N" = GPIO line named "FOO" on schematic, active low + */ + gpio-line-names = "GPIO0", + "GPIO1", + "GPIO2", + "GPIO3", + "GPIO4", + "GPIO5", + "GPIO6", + "GPIO7", + "GPIO8", + "GPIO9", + "GPIO10", + "GPIO11", + "GPIO12", + "GPIO13", + "GPIO14", + "GPIO15", + "GPIO16", + "GPIO17", + "GPIO18", + "GPIO19", + "GPIO20", + "GPIO21", + "GPIO22", + "GPIO23", + "GPIO24", + "GPIO25", + "GPIO26", + "GPIO27", + "GPIO28", + "GPIO29", + "GPIO30", + "GPIO31", + "GPIO32", + "GPIO33", + "GPIO34", + "GPIO35", + "GPIO36", + "GPIO37", + "GPIO38", + "GPIO39", + "GPIO40", + "GPIO41", + "GPIO42", + "GPIO43", + "GPIO44", + "GPIO45", + "SMPS_SCL", + "SMPS_SDA", + /* Used by eMMC */ + "SD_CLK_R", + "SD_CMD_R", + "SD_DATA0_R", + "SD_DATA1_R", + "SD_DATA2_R", + "SD_DATA3_R"; + + spi0_pins: spi0_pins { + brcm,pins = <9 10 11>; + brcm,function = <4>; /* alt0 */ + }; + + spi0_cs_pins: spi0_cs_pins { + brcm,pins = <8 7>; + brcm,function = <1>; /* output */ + }; + + i2c0_pins: i2c0 { + brcm,pins = <0 1>; + brcm,function = <4>; + }; + + i2c1_pins: i2c1 { + brcm,pins = <2 3>; + brcm,function = <4>; + }; + + i2s_pins: i2s { + brcm,pins = <18 19 20 21>; + brcm,function = <4>; /* alt0 */ + }; + + audio_pins: audio_pins { + brcm,pins; + brcm,function; + }; +}; + +&firmware { + expgpio: expgpio { + compatible = "raspberrypi,firmware-gpio"; + gpio-controller; + #gpio-cells = <2>; + gpio-line-names = "HDMI_HPD_N", + "EMMC_EN_N", + "NC", + "NC", + "NC", + "NC", + "NC", + "NC"; + status = "okay"; + }; + + virtgpio: virtgpio { + compatible = "brcm,bcm2835-virtgpio"; + gpio-controller; + #gpio-cells = <2>; + status = "okay"; + }; +}; + +&spi0 { + pinctrl-names = "default"; + pinctrl-0 = <&spi0_pins &spi0_cs_pins>; + cs-gpios = <&gpio 8 1>, <&gpio 7 1>; + + spidev0: spidev@0{ + compatible = "spidev"; + reg = <0>; /* CE0 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + }; + + spidev1: spidev@1{ + compatible = "spidev"; + reg = <1>; /* CE1 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + }; +}; + +&i2c0if { + clock-frequency = <100000>; +}; + +&i2c1 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c1_pins>; + clock-frequency = <100000>; +}; + +&i2c2 { + clock-frequency = <100000>; +}; + +&i2s { + pinctrl-names = "default"; + pinctrl-0 = <&i2s_pins>; +}; + +&led_act { + gpios = <&virtgpio 0 GPIO_ACTIVE_HIGH>; + default-state = "off"; + linux,default-trigger = "mmc0"; +}; + +&hdmi { + hpd-gpios = <&expgpio 0 GPIO_ACTIVE_LOW>; +}; + +&vchiq { + pinctrl-names = "default"; + pinctrl-0 = <&audio_pins>; +}; + +/ { + __overrides__ { + audio = <&chosen>,"bootargs{on='snd_bcm2835.enable_hdmi=1',off='snd_bcm2835.enable_hdmi=0'}"; + + act_led_gpio = <&led_act>,"gpios:4"; + act_led_activelow = <&led_act>,"gpios:8"; + act_led_trigger = <&led_act>,"linux,default-trigger"; + }; +}; diff --git a/arch/arm/boot/dts/broadcom/bcm2710-rpi-zero-2-w.dts b/arch/arm/boot/dts/broadcom/bcm2710-rpi-zero-2-w.dts new file mode 100644 index 00000000000000..16971e50229f0a --- /dev/null +++ b/arch/arm/boot/dts/broadcom/bcm2710-rpi-zero-2-w.dts @@ -0,0 +1,257 @@ +/dts-v1/; + +#include "bcm2710.dtsi" +#include "bcm2709-rpi.dtsi" +#include "bcm283x-rpi-csi1-2lane.dtsi" +#include "bcm283x-rpi-i2c0mux_0_44.dtsi" +#include "bcm2708-rpi-bt.dtsi" +#include "bcm283x-rpi-led-deprecated.dtsi" + +/ { + compatible = "raspberrypi,model-zero-2-w", "brcm,bcm2837"; + model = "Raspberry Pi Zero 2 W"; + + aliases { + serial0 = &uart1; + serial1 = &uart0; + mmc1 = &mmcnr; + }; +}; + +&gpio { + /* + * This is based on the official GPU firmware DT blob. + * + * Legend: + * "NC" = not connected (no rail from the SoC) + * "FOO" = GPIO line named "FOO" on the schematic + * "FOO_N" = GPIO line named "FOO" on schematic, active low + */ + gpio-line-names = "ID_SDA", + "ID_SCL", + "GPIO2", + "GPIO3", + "GPIO4", + "GPIO5", + "GPIO6", + "GPIO7", + "GPIO8", + "GPIO9", + "GPIO10", + "GPIO11", + "GPIO12", + "GPIO13", + "GPIO14", + "GPIO15", + "GPIO16", + "GPIO17", + "GPIO18", + "GPIO19", + "GPIO20", + "GPIO21", + "GPIO22", + "GPIO23", + "GPIO24", + "GPIO25", + "GPIO26", + "GPIO27", + "HDMI_HPD_N", + "STATUS_LED_N", + /* Used by BT module */ + "CTS0", + "RTS0", + "TXD0", + "RXD0", + /* Used by Wifi */ + "SD1_CLK", + "SD1_CMD", + "SD1_DATA0", + "SD1_DATA1", + "SD1_DATA2", + "SD1_DATA3", + "CAM_GPIO1", /* GPIO40 */ + "WL_ON", /* GPIO41 */ + "BT_ON", /* GPIO42 */ + "WIFI_CLK", /* GPIO43 */ + "SDA0", /* GPIO44 */ + "SCL0", /* GPIO45 */ + "SMPS_SCL", /* GPIO46 */ + "SMPS_SDA", /* GPIO47 */ + /* Used by SD Card */ + "SD_CLK_R", + "SD_CMD_R", + "SD_DATA0_R", + "SD_DATA1_R", + "SD_DATA2_R", + "SD_DATA3_R"; + + spi0_pins: spi0_pins { + brcm,pins = <9 10 11>; + brcm,function = <4>; /* alt0 */ + }; + + spi0_cs_pins: spi0_cs_pins { + brcm,pins = <8 7>; + brcm,function = <1>; /* output */ + }; + + i2c0_pins: i2c0 { + brcm,pins = <0 1>; + brcm,function = <4>; + }; + + i2c1_pins: i2c1 { + brcm,pins = <2 3>; + brcm,function = <4>; + }; + + i2s_pins: i2s { + brcm,pins = <18 19 20 21>; + brcm,function = <4>; /* alt0 */ + }; + + sdio_pins: sdio_pins { + brcm,pins = <34 35 36 37 38 39>; + brcm,function = <7>; // alt3 = SD1 + brcm,pull = <0 2 2 2 2 2>; + }; + + bt_pins: bt_pins { + brcm,pins = <43>; + brcm,function = <4>; /* alt0:GPCLK2 */ + brcm,pull = <0>; + }; + + uart0_pins: uart0_pins { + brcm,pins = <30 31 32 33>; + brcm,function = <7>; /* alt3=UART0 */ + brcm,pull = <2 0 0 2>; /* up none none up */ + }; + + uart1_pins: uart1_pins { + brcm,pins; + brcm,function; + brcm,pull; + }; + + uart1_bt_pins: uart1_bt_pins { + brcm,pins = <32 33 30 31>; + brcm,function = <BCM2835_FSEL_ALT5>; /* alt5=UART1 */ + brcm,pull = <0 2 2 0>; + }; + + audio_pins: audio_pins { + brcm,pins = <>; + brcm,function = <>; + }; +}; + +&mmcnr { + pinctrl-names = "default"; + pinctrl-0 = <&sdio_pins>; + bus-width = <4>; + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + brcmf: wifi@1 { + reg = <1>; + compatible = "brcm,bcm4329-fmac"; + }; +}; + +&uart0 { + pinctrl-names = "default"; + pinctrl-0 = <&uart0_pins &bt_pins>; + status = "okay"; +}; + +&uart1 { + pinctrl-names = "default"; + pinctrl-0 = <&uart1_pins>; + status = "okay"; +}; + +&spi0 { + pinctrl-names = "default"; + pinctrl-0 = <&spi0_pins &spi0_cs_pins>; + cs-gpios = <&gpio 8 1>, <&gpio 7 1>; + + spidev0: spidev@0{ + compatible = "spidev"; + reg = <0>; /* CE0 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + }; + + spidev1: spidev@1{ + compatible = "spidev"; + reg = <1>; /* CE1 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + }; +}; + +&i2c0if { + clock-frequency = <100000>; +}; + +&i2c1 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c1_pins>; + clock-frequency = <100000>; +}; + +&i2c2 { + clock-frequency = <100000>; +}; + +&i2s { + pinctrl-names = "default"; + pinctrl-0 = <&i2s_pins>; +}; + +&led_act { + gpios = <&gpio 29 GPIO_ACTIVE_LOW>; + default-state = "off"; + linux,default-trigger = "actpwr"; +}; + +&hdmi { + hpd-gpios = <&gpio 28 GPIO_ACTIVE_LOW>; +}; + +&vchiq { + pinctrl-names = "default"; + pinctrl-0 = <&audio_pins>; +}; + +&bt { + shutdown-gpios = <&gpio 42 GPIO_ACTIVE_HIGH>; +}; + +&minibt { + shutdown-gpios = <&gpio 42 GPIO_ACTIVE_HIGH>; +}; + +&cam1_reg { + gpio = <&gpio 40 GPIO_ACTIVE_HIGH>; +}; + +cam0_reg: &cam_dummy_reg { +}; + +i2c_csi_dsi0: &i2c0 { +}; + +/ { + __overrides__ { + audio = <&chosen>,"bootargs{on='snd_bcm2835.enable_hdmi=1',off='snd_bcm2835.enable_hdmi=0'}"; + + act_led_gpio = <&led_act>,"gpios:4"; + act_led_activelow = <&led_act>,"gpios:8"; + act_led_trigger = <&led_act>,"linux,default-trigger"; + }; +}; diff --git a/arch/arm/boot/dts/broadcom/bcm2710-rpi-zero-2.dts b/arch/arm/boot/dts/broadcom/bcm2710-rpi-zero-2.dts new file mode 100644 index 00000000000000..daa12bd30d6b6e --- /dev/null +++ b/arch/arm/boot/dts/broadcom/bcm2710-rpi-zero-2.dts @@ -0,0 +1 @@ +#include "bcm2710-rpi-zero-2-w.dts" diff --git a/arch/arm/boot/dts/broadcom/bcm2710.dtsi b/arch/arm/boot/dts/broadcom/bcm2710.dtsi new file mode 100644 index 00000000000000..bdcdbb51fab834 --- /dev/null +++ b/arch/arm/boot/dts/broadcom/bcm2710.dtsi @@ -0,0 +1,32 @@ +#define i2c0 i2c0if +#include "bcm2837.dtsi" +#undef i2c0 +#include "bcm270x.dtsi" + +/ { + compatible = "brcm,bcm2837", "brcm,bcm2836"; + + arm-pmu { + compatible = "arm,cortex-a53-pmu", "arm,cortex-a7-pmu"; + }; + + soc { + dma-ranges = <0xc0000000 0x00000000 0x3f000000>, + <0x7e000000 0x3f000000 0x01000000>; + }; + + __overrides__ { + arm_freq = <&cpu0>, "clock-frequency:0", + <&cpu1>, "clock-frequency:0", + <&cpu2>, "clock-frequency:0", + <&cpu3>, "clock-frequency:0"; + }; +}; + +&system_timer { + status = "disabled"; +}; + +&vc4 { + status = "disabled"; +}; diff --git a/arch/arm/boot/dts/broadcom/bcm2711-rpi-4-b.dts b/arch/arm/boot/dts/broadcom/bcm2711-rpi-4-b.dts index 353bb50ce5425c..a4aae12775dc55 100644 --- a/arch/arm/boot/dts/broadcom/bcm2711-rpi-4-b.dts +++ b/arch/arm/boot/dts/broadcom/bcm2711-rpi-4-b.dts @@ -1,11 +1,19 @@ // SPDX-License-Identifier: GPL-2.0 /dts-v1/; +#define BCM2711 +#define i2c0 i2c0if #include "bcm2711.dtsi" #include "bcm2711-rpi.dtsi" +/delete-node/&i2c0mux; #include "bcm283x-rpi-led-deprecated.dtsi" -#include "bcm283x-rpi-usb-peripheral.dtsi" #include "bcm283x-rpi-wifi-bt.dtsi" #include <dt-bindings/leds/common.h> +#undef i2c0 +#include "bcm270x.dtsi" +#define i2c0 i2c0mux +#undef i2c0 + +/delete-node/ &cam1_reg; / { compatible = "raspberrypi,4-model-b", "brcm,bcm2711"; @@ -68,7 +76,7 @@ "VDD_SD_IO_SEL", "CAM_GPIO", /* 5 */ "SD_PWR_ON", - ""; + "SD_OC_N"; }; &gpio { @@ -82,21 +90,21 @@ */ gpio-line-names = "ID_SDA", /* 0 */ "ID_SCL", - "SDA1", - "SCL1", - "GPIO_GCLK", + "GPIO2", + "GPIO3", + "GPIO4", "GPIO5", /* 5 */ "GPIO6", - "SPI_CE1_N", - "SPI_CE0_N", - "SPI_MISO", - "SPI_MOSI", /* 10 */ - "SPI_SCLK", + "GPIO7", + "GPIO8", + "GPIO9", + "GPIO10", /* 10 */ + "GPIO11", "GPIO12", "GPIO13", /* Serial port */ - "TXD1", - "RXD1", /* 15 */ + "GPIO14", + "GPIO15", /* 15 */ "GPIO16", "GPIO17", "GPIO18", @@ -214,7 +222,7 @@ led@0 { reg = <0>; color = <LED_COLOR_ID_GREEN>; - function = LED_FUNCTION_LAN; + function = "lan";//LED_FUNCTION_LAN; default-state = "keep"; }; @@ -222,7 +230,7 @@ led@1 { reg = <1>; color = <LED_COLOR_ID_AMBER>; - function = LED_FUNCTION_LAN; + function = "lan";//LED_FUNCTION_LAN; default-state = "keep"; }; }; @@ -270,3 +278,233 @@ &wifi_pwrseq { reset-gpios = <&expgpio 1 GPIO_ACTIVE_LOW>; }; + +// ============================================= +// Downstream rpi- changes + +#include "bcm271x-rpi-bt.dtsi" + +/ { + soc { + /delete-node/ pixelvalve@7e807000; + /delete-node/ hdmi@7e902000; + }; +}; + +&phy1 { + /delete-node/ leds; +}; + +#include "bcm2711-rpi-ds.dtsi" +#include "bcm283x-rpi-csi1-2lane.dtsi" +#include "bcm283x-rpi-i2c0mux_0_44.dtsi" + +/ { + /delete-node/ wifi-pwrseq; +}; + +&mmcnr { + pinctrl-names = "default"; + pinctrl-0 = <&sdio_pins>; + bus-width = <4>; + status = "okay"; +}; + +&uart0 { + pinctrl-0 = <&uart0_pins &bt_pins>; + status = "okay"; +}; + +&uart1 { + pinctrl-0 = <&uart1_pins>; +}; + +&spi0 { + pinctrl-names = "default"; + pinctrl-0 = <&spi0_pins &spi0_cs_pins>; + cs-gpios = <&gpio 8 1>, <&gpio 7 1>; + + spidev0: spidev@0{ + compatible = "spidev"; + reg = <0>; /* CE0 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + }; + + spidev1: spidev@1{ + compatible = "spidev"; + reg = <1>; /* CE1 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + }; +}; + +&gpio { + gpio-line-names = "ID_SDA", + "ID_SCL", + "GPIO2", + "GPIO3", + "GPIO4", + "GPIO5", + "GPIO6", + "GPIO7", + "GPIO8", + "GPIO9", + "GPIO10", + "GPIO11", + "GPIO12", + "GPIO13", + "GPIO14", + "GPIO15", + "GPIO16", + "GPIO17", + "GPIO18", + "GPIO19", + "GPIO20", + "GPIO21", + "GPIO22", + "GPIO23", + "GPIO24", + "GPIO25", + "GPIO26", + "GPIO27", + "RGMII_MDIO", + "RGMIO_MDC", + /* Used by BT module */ + "CTS0", /* 30 */ + "RTS0", + "TXD0", + "RXD0", + /* Used by Wifi */ + "SD1_CLK", + "SD1_CMD", /* 35 */ + "SD1_DATA0", + "SD1_DATA1", + "SD1_DATA2", + "SD1_DATA3", + /* Shared with SPI flash */ + "PWM0_MISO", /* 40 */ + "PWM1_MOSI", + "STATUS_LED_G_CLK", + "SPIFLASH_CE_N", + "SDA0", + "SCL0", /* 45 */ + "RGMII_RXCLK", + "RGMII_RXCTL", + "RGMII_RXD0", + "RGMII_RXD1", + "RGMII_RXD2", /* 50 */ + "RGMII_RXD3", + "RGMII_TXCLK", + "RGMII_TXCTL", + "RGMII_TXD0", + "RGMII_TXD1", /* 55 */ + "RGMII_TXD2", + "RGMII_TXD3"; + + bt_pins: bt_pins { + brcm,pins = "-"; // non-empty to keep btuart happy, //4 = 0 + // to fool pinctrl + brcm,function = <0>; + brcm,pull = <2>; + }; + + uart0_pins: uart0_pins { + brcm,pins = <32 33>; + brcm,function = <BCM2835_FSEL_ALT3>; + brcm,pull = <0 2>; + }; + + uart1_pins: uart1_pins { + brcm,pins; + brcm,function; + brcm,pull; + }; + + uart1_bt_pins: uart1_bt_pins { + brcm,pins = <32 33 30 31>; + brcm,function = <BCM2835_FSEL_ALT5>; /* alt5=UART1 */ + brcm,pull = <0 2 2 0>; + }; +}; + +&i2c0if { + clock-frequency = <100000>; +}; + +&i2c1 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c1_pins>; + clock-frequency = <100000>; +}; + +&i2s { + pinctrl-names = "default"; + pinctrl-0 = <&i2s_pins>; +}; + +// ============================================= +// Board specific stuff here + +&sdhost { + status = "disabled"; +}; + +&phy1 { + led-modes = <0x00 0x08>; /* link/activity link */ +}; + +&gpio { + audio_pins: audio_pins { + brcm,pins = <40 41>; + brcm,function = <4>; + brcm,pull = <0>; + }; +}; + +&led_act { + default-state = "off"; + linux,default-trigger = "mmc0"; +}; + +&led_pwr { + default-state = "off"; +}; + +&pwm1 { + status = "disabled"; +}; + +&vchiq { + pinctrl-names = "default"; + pinctrl-0 = <&audio_pins>; +}; + +&cam1_reg { + gpio = <&expgpio 5 GPIO_ACTIVE_HIGH>; +}; + +cam0_reg: &cam_dummy_reg { +}; + +i2c_csi_dsi0: &i2c0 { +}; + +/ { + __overrides__ { + audio = <&chosen>,"bootargs{on='snd_bcm2835.enable_headphones=1 snd_bcm2835.enable_hdmi=1',off='snd_bcm2835.enable_headphones=0 snd_bcm2835.enable_hdmi=0'}"; + + act_led_gpio = <&led_act>,"gpios:4"; + act_led_activelow = <&led_act>,"gpios:8"; + act_led_trigger = <&led_act>,"linux,default-trigger"; + + pwr_led_gpio = <&led_pwr>,"gpios:4"; + pwr_led_activelow = <&led_pwr>,"gpios:8"; + pwr_led_trigger = <&led_pwr>,"linux,default-trigger"; + + eth_led0 = <&phy1>,"led-modes:0"; + eth_led1 = <&phy1>,"led-modes:4"; + }; +}; diff --git a/arch/arm/boot/dts/broadcom/bcm2711-rpi-400.dts b/arch/arm/boot/dts/broadcom/bcm2711-rpi-400.dts index ca9be91b4f3654..dec5743d3fb558 100644 --- a/arch/arm/boot/dts/broadcom/bcm2711-rpi-400.dts +++ b/arch/arm/boot/dts/broadcom/bcm2711-rpi-400.dts @@ -1,5 +1,4 @@ // SPDX-License-Identifier: GPL-2.0 -/dts-v1/; #include "bcm2711-rpi-4-b.dts" / { @@ -37,8 +36,53 @@ gpios = <&gpio 42 GPIO_ACTIVE_HIGH>; }; -/delete-node/ &led_act; - &pm { /delete-property/ system-power-controller; }; + +// ============================================= +// Downstream rpi- changes + +/ { + chosen { + stdout-path = "serial0:115200n8"; + }; +}; + +&audio_pins { + brcm,pins = <>; + brcm,function = <>; +}; + +// Declare the LED but leave it disabled, in case a user wants to map it +// to a GPIO on the header +&led_act { + default-state = "off"; + gpios = <&gpio 0 GPIO_ACTIVE_HIGH>; + status = "disabled"; +}; + +&led_pwr { + default-state = "off"; +}; + +&cam1_reg { + /delete-property/ gpio; +}; + +cam0_reg: &cam_dummy_reg { +}; + +/ { + __overrides__ { + audio = <&chosen>,"bootargs{on='snd_bcm2835.enable_hdmi=1',off='snd_bcm2835.enable_hdmi=0'}"; + + act_led_gpio = <&led_act>,"gpios:4", + <&led_act>,"status=okay"; + act_led_activelow = <&led_act>,"gpios:8"; + act_led_trigger = <&led_act>,"linux,default-trigger"; + pwr_led_gpio = <&led_pwr>,"gpios:4"; + pwr_led_activelow = <&led_pwr>,"gpios:8"; + pwr_led_trigger = <&led_pwr>,"linux,default-trigger"; + }; +}; diff --git a/arch/arm/boot/dts/broadcom/bcm2711-rpi-cm4.dts b/arch/arm/boot/dts/broadcom/bcm2711-rpi-cm4.dts new file mode 100644 index 00000000000000..ddc33bbe872fb5 --- /dev/null +++ b/arch/arm/boot/dts/broadcom/bcm2711-rpi-cm4.dts @@ -0,0 +1,501 @@ +// SPDX-License-Identifier: GPL-2.0 +/dts-v1/; +#define BCM2711 +#define i2c0 i2c0if +#include "bcm2711.dtsi" +#include "bcm2711-rpi.dtsi" +/delete-node/&i2c0mux; +#include "bcm283x-rpi-led-deprecated.dtsi" +#include "bcm283x-rpi-wifi-bt.dtsi" +#undef i2c0 +#include "bcm270x.dtsi" +#define i2c0 i2c0mux +#undef i2c0 + +/ { + compatible = "raspberrypi,4-compute-module", "brcm,bcm2711"; + model = "Raspberry Pi Compute Module 4"; + + chosen { + /* 8250 auxiliary UART instead of pl011 */ + stdout-path = "serial1:115200n8"; + }; + + sd_io_1v8_reg: sd_io_1v8_reg { + compatible = "regulator-gpio"; + regulator-name = "vdd-sd-io"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <3300000>; + regulator-boot-on; + regulator-always-on; + regulator-settling-time-us = <5000>; + gpios = <&expgpio 4 GPIO_ACTIVE_HIGH>; + states = <1800000 0x1>, + <3300000 0x0>; + status = "okay"; + }; + + sd_vcc_reg: sd_vcc_reg { + compatible = "regulator-fixed"; + regulator-name = "vcc-sd"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-boot-on; + enable-active-high; + gpio = <&expgpio 6 GPIO_ACTIVE_HIGH>; + }; +}; + +&bt { + shutdown-gpios = <&expgpio 0 GPIO_ACTIVE_HIGH>; +}; + +&ddc0 { + status = "okay"; +}; + +&ddc1 { + status = "okay"; +}; + +&expgpio { + gpio-line-names = "BT_ON", + "WL_ON", + "PWR_LED_OFF", + "ANT1", + "VDD_SD_IO_SEL", + "CAM_GPIO", + "SD_PWR_ON", + "ANT2"; + + ant1: ant1 { + gpio-hog; + gpios = <3 GPIO_ACTIVE_HIGH>; + output-high; + }; + + ant2: ant2 { + gpio-hog; + gpios = <7 GPIO_ACTIVE_HIGH>; + output-low; + }; +}; + +&gpio { + /* + * Parts taken from rpi_SCH_4b_4p0_reduced.pdf and + * the official GPU firmware DT blob. + * + * Legend: + * "FOO" = GPIO line named "FOO" on the schematic + * "FOO_N" = GPIO line named "FOO" on schematic, active low + */ + gpio-line-names = "ID_SDA", + "ID_SCL", + "SDA1", + "SCL1", + "GPIO_GCLK", + "GPIO5", + "GPIO6", + "SPI_CE1_N", + "SPI_CE0_N", + "SPI_MISO", + "SPI_MOSI", + "SPI_SCLK", + "GPIO12", + "GPIO13", + /* Serial port */ + "TXD1", + "RXD1", + "GPIO16", + "GPIO17", + "GPIO18", + "GPIO19", + "GPIO20", + "GPIO21", + "GPIO22", + "GPIO23", + "GPIO24", + "GPIO25", + "GPIO26", + "GPIO27", + "RGMII_MDIO", + "RGMIO_MDC", + /* Used by BT module */ + "CTS0", + "RTS0", + "TXD0", + "RXD0", + /* Used by Wifi */ + "SD1_CLK", + "SD1_CMD", + "SD1_DATA0", + "SD1_DATA1", + "SD1_DATA2", + "SD1_DATA3", + /* Shared with SPI flash */ + "PWM0_MISO", + "PWM1_MOSI", + "STATUS_LED_G_CLK", + "SPIFLASH_CE_N", + "SDA0", + "SCL0", + "RGMII_RXCLK", + "RGMII_RXCTL", + "RGMII_RXD0", + "RGMII_RXD1", + "RGMII_RXD2", + "RGMII_RXD3", + "RGMII_TXCLK", + "RGMII_TXCTL", + "RGMII_TXD0", + "RGMII_TXD1", + "RGMII_TXD2", + "RGMII_TXD3"; +}; + +&hdmi0 { + status = "okay"; +}; + +&hdmi1 { + status = "okay"; +}; + +&led_act { + gpios = <&gpio 42 GPIO_ACTIVE_HIGH>; +}; + +&leds { + led_pwr: led-pwr { + label = "PWR"; + gpios = <&expgpio 2 GPIO_ACTIVE_LOW>; + default-state = "keep"; + linux,default-trigger = "default-on"; + }; +}; + +&pixelvalve0 { + status = "okay"; +}; + +&pixelvalve1 { + status = "okay"; +}; + +&pixelvalve2 { + status = "okay"; +}; + +&pixelvalve4 { + status = "okay"; +}; + +&pwm1 { + pinctrl-names = "default"; + pinctrl-0 = <&pwm1_0_gpio40 &pwm1_1_gpio41>; + status = "okay"; +}; + +/* EMMC2 is used to drive the EMMC card */ +&emmc2 { + bus-width = <8>; + vqmmc-supply = <&sd_io_1v8_reg>; + vmmc-supply = <&sd_vcc_reg>; + broken-cd; + status = "okay"; +}; + +&genet { + phy-handle = <&phy1>; + phy-mode = "rgmii-rxid"; + status = "okay"; +}; + +&genet_mdio { + phy1: ethernet-phy@0 { + /* No PHY interrupt */ + reg = <0x0>; + }; +}; + +&pcie0 { + pci@0,0 { + device_type = "pci"; + #address-cells = <3>; + #size-cells = <2>; + ranges; + + reg = <0 0 0 0 0>; + }; +}; + +/* uart0 communicates with the BT module */ +&uart0 { + pinctrl-names = "default"; + pinctrl-0 = <&uart0_ctsrts_gpio30 &uart0_gpio32>; + uart-has-rtscts; +}; + +/* uart1 is mapped to the pin header */ +&uart1 { + pinctrl-names = "default"; + pinctrl-0 = <&uart1_gpio14>; + status = "okay"; +}; + +&vc4 { + status = "okay"; +}; + +&vec { + status = "disabled"; +}; + +&wifi_pwrseq { + reset-gpios = <&expgpio 1 GPIO_ACTIVE_LOW>; +}; + +// ============================================= +// Downstream rpi- changes + +#include "bcm271x-rpi-bt.dtsi" + +/ { + soc { + /delete-node/ pixelvalve@7e807000; + /delete-node/ hdmi@7e902000; + }; +}; + +#include "bcm2711-rpi-ds.dtsi" +#include "bcm283x-rpi-csi0-2lane.dtsi" +#include "bcm283x-rpi-csi1-4lane.dtsi" +#include "bcm283x-rpi-i2c0mux_0_44.dtsi" + +/ { + /delete-node/ wifi-pwrseq; +}; + +&mmcnr { + pinctrl-names = "default"; + pinctrl-0 = <&sdio_pins>; + bus-width = <4>; + status = "okay"; +}; + +&uart0 { + pinctrl-0 = <&uart0_pins &bt_pins>; + status = "okay"; +}; + +&uart1 { + pinctrl-0 = <&uart1_pins>; +}; + +&spi0 { + pinctrl-names = "default"; + pinctrl-0 = <&spi0_pins &spi0_cs_pins>; + cs-gpios = <&gpio 8 1>, <&gpio 7 1>; + + spidev0: spidev@0{ + compatible = "spidev"; + reg = <0>; /* CE0 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + }; + + spidev1: spidev@1{ + compatible = "spidev"; + reg = <1>; /* CE1 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + }; +}; + +&gpio { + gpio-line-names = "ID_SDA", + "ID_SCL", + "GPIO2", + "GPIO3", + "GPIO4", + "GPIO5", + "GPIO6", + "GPIO7", + "GPIO8", + "GPIO9", + "GPIO10", + "GPIO11", + "GPIO12", + "GPIO13", + "GPIO14", + "GPIO15", + "GPIO16", + "GPIO17", + "GPIO18", + "GPIO19", + "GPIO20", + "GPIO21", + "GPIO22", + "GPIO23", + "GPIO24", + "GPIO25", + "GPIO26", + "GPIO27", + "RGMII_MDIO", + "RGMIO_MDC", + /* Used by BT module */ + "CTS0", + "RTS0", + "TXD0", + "RXD0", + /* Used by Wifi */ + "SD1_CLK", + "SD1_CMD", + "SD1_DATA0", + "SD1_DATA1", + "SD1_DATA2", + "SD1_DATA3", + /* Shared with SPI flash */ + "PWM0_MISO", + "PWM1_MOSI", + "STATUS_LED_G_CLK", + "SPIFLASH_CE_N", + "SDA0", + "SCL0", + "RGMII_RXCLK", + "RGMII_RXCTL", + "RGMII_RXD0", + "RGMII_RXD1", + "RGMII_RXD2", + "RGMII_RXD3", + "RGMII_TXCLK", + "RGMII_TXCTL", + "RGMII_TXD0", + "RGMII_TXD1", + "RGMII_TXD2", + "RGMII_TXD3"; + + bt_pins: bt_pins { + brcm,pins = "-"; // non-empty to keep btuart happy, //4 = 0 + // to fool pinctrl + brcm,function = <0>; + brcm,pull = <2>; + }; + + uart0_pins: uart0_pins { + brcm,pins = <32 33>; + brcm,function = <BCM2835_FSEL_ALT3>; + brcm,pull = <0 2>; + }; + + uart1_pins: uart1_pins { + brcm,pins; + brcm,function; + brcm,pull; + }; + + uart1_bt_pins: uart1_bt_pins { + brcm,pins = <32 33 30 31>; + brcm,function = <BCM2835_FSEL_ALT5>; /* alt5=UART1 */ + brcm,pull = <0 2 2 0>; + }; +}; + +&i2c0if { + clock-frequency = <100000>; +}; + +&i2c1 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c1_pins>; + clock-frequency = <100000>; +}; + +&i2s { + pinctrl-names = "default"; + pinctrl-0 = <&i2s_pins>; +}; + +// ============================================= +// Board specific stuff here + +&pcie0 { + brcm,enable-l1ss; +}; + +&sdhost { + status = "disabled"; +}; + +&phy1 { + led-modes = <0x00 0x08>; /* link/activity link */ +}; + +&gpio { + audio_pins: audio_pins { + brcm,pins = <>; + brcm,function = <>; + }; +}; + +&led_act { + default-state = "off"; + linux,default-trigger = "mmc0"; +}; + +&led_pwr { + default-state = "off"; +}; + +&pwm1 { + status = "disabled"; +}; + +&vchiq { + pinctrl-names = "default"; + pinctrl-0 = <&audio_pins>; +}; + +cam0_reg: &cam1_reg { + gpio = <&expgpio 5 GPIO_ACTIVE_HIGH>; +}; + +i2c_csi_dsi0: &i2c0 { +}; + +/ { + __overrides__ { + audio = <&chosen>,"bootargs{on='snd_bcm2835.enable_hdmi=1',off='snd_bcm2835.enable_hdmi=0'}"; + + act_led_gpio = <&led_act>,"gpios:4"; + act_led_activelow = <&led_act>,"gpios:8"; + act_led_trigger = <&led_act>,"linux,default-trigger"; + + pwr_led_gpio = <&led_pwr>,"gpios:4"; + pwr_led_activelow = <&led_pwr>,"gpios:8"; + pwr_led_trigger = <&led_pwr>,"linux,default-trigger"; + + eth_led0 = <&phy1>,"led-modes:0"; + eth_led1 = <&phy1>,"led-modes:4"; + + ant1 = <&ant1>,"output-high?=on", + <&ant1>, "output-low?=off", + <&ant2>, "output-high?=off", + <&ant2>, "output-low?=on"; + ant2 = <&ant1>,"output-high?=off", + <&ant1>, "output-low?=on", + <&ant2>, "output-high?=on", + <&ant2>, "output-low?=off"; + noant = <&ant1>,"output-high?=off", + <&ant1>, "output-low?=on", + <&ant2>, "output-high?=off", + <&ant2>, "output-low?=on"; + noanthogs = <&ant1>,"status=disabled", + <&ant2>, "status=disabled"; + + pcie_tperst_clk_ms = <&pcie0>,"brcm,tperst-clk-ms:0"; + }; +}; diff --git a/arch/arm/boot/dts/broadcom/bcm2711-rpi-cm4s.dts b/arch/arm/boot/dts/broadcom/bcm2711-rpi-cm4s.dts new file mode 100644 index 00000000000000..badf2a2fc3e9c7 --- /dev/null +++ b/arch/arm/boot/dts/broadcom/bcm2711-rpi-cm4s.dts @@ -0,0 +1,293 @@ +// SPDX-License-Identifier: GPL-2.0 +/dts-v1/; +#define BCM2711 +#define i2c0 i2c0if +#include "bcm2711.dtsi" +#include "bcm2711-rpi.dtsi" +/delete-node/&i2c0mux; +#include "bcm283x-rpi-led-deprecated.dtsi" +#undef i2c0 +#include "bcm270x.dtsi" +#define i2c0 i2c0mux +#undef i2c0 + +/ { + compatible = "raspberrypi,4-compute-module-s", "brcm,bcm2711"; + model = "Raspberry Pi Compute Module 4S"; +}; + +&ddc0 { + status = "okay"; +}; + +&gpio { + /* + * Parts taken from rpi_SCH_4b_4p0_reduced.pdf and + * the official GPU firmware DT blob. + * + * Legend: + * "FOO" = GPIO line named "FOO" on the schematic + * "FOO_N" = GPIO line named "FOO" on schematic, active low + */ + gpio-line-names = "ID_SDA", + "ID_SCL", + "GPIO2", + "GPIO3", + "GPIO4", + "GPIO5", + "GPIO6", + "GPIO7", + "GPIO8", + "GPIO9", + "GPIO10", + "GPIO11", + "GPIO12", + "GPIO13", + "GPIO14", + "GPIO15", + "GPIO16", + "GPIO17", + "GPIO18", + "GPIO19", + "GPIO20", + "GPIO21", + "GPIO22", + "GPIO23", + "GPIO24", + "GPIO25", + "GPIO26", + "GPIO27", + "GPIO28", + "GPIO29", + "GPIO30", + "GPIO31", + "GPIO32", + "GPIO33", + "GPIO34", + "GPIO35", + "GPIO36", + "GPIO37", + "GPIO38", + "GPIO39", + "PWM0_MISO", + "PWM1_MOSI", + "GPIO42", + "GPIO43", + "GPIO44", + "GPIO45"; +}; + +&hdmi0 { + status = "okay"; +}; + +&led_act { + gpios = <&virtgpio 0 GPIO_ACTIVE_HIGH>; +}; + +&pixelvalve0 { + status = "okay"; +}; + +&pixelvalve1 { + status = "okay"; +}; + +&pixelvalve2 { + status = "okay"; +}; + +&pixelvalve4 { + status = "okay"; +}; + +&pwm1 { + pinctrl-names = "default"; + pinctrl-0 = <&pwm1_0_gpio40 &pwm1_1_gpio41>; + status = "okay"; +}; + +/* EMMC2 is used to drive the EMMC card */ +&emmc2 { + bus-width = <8>; + broken-cd; + status = "okay"; +}; + +&pcie0 { + status = "disabled"; +}; + +&vchiq { + interrupts = <GIC_SPI 34 IRQ_TYPE_LEVEL_HIGH>; +}; + +&vc4 { + status = "okay"; +}; + +&vec { + status = "disabled"; +}; + +// ============================================= +// Downstream rpi- changes + +#include "bcm2711-rpi-ds.dtsi" + +/ { + soc { + /delete-node/ pixelvalve@7e807000; + /delete-node/ hdmi@7e902000; + }; +}; + +#include "bcm283x-rpi-csi0-2lane.dtsi" +#include "bcm283x-rpi-csi1-4lane.dtsi" +#include "bcm283x-rpi-i2c0mux_0_28.dtsi" + +/ { + chosen { + bootargs = "coherent_pool=1M snd_bcm2835.enable_headphones=0 cgroup_disable=memory numa_policy=interleave"; + }; + + aliases { + serial0 = &uart0; + serial1 = &uart1; + /delete-property/ i2c20; + /delete-property/ i2c21; + }; + + /delete-node/ wifi-pwrseq; +}; + +&firmware { + virtgpio: virtgpio { + compatible = "brcm,bcm2835-virtgpio"; + gpio-controller; + #gpio-cells = <2>; + status = "okay"; + }; +}; + +&uart0 { + pinctrl-names = "default"; + pinctrl-0 = <&uart0_pins>; + status = "okay"; +}; + +&spi0 { + pinctrl-names = "default"; + pinctrl-0 = <&spi0_pins &spi0_cs_pins>; + cs-gpios = <&gpio 8 1>, <&gpio 7 1>; + + spidev0: spidev@0{ + compatible = "spidev"; + reg = <0>; /* CE0 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + }; + + spidev1: spidev@1{ + compatible = "spidev"; + reg = <1>; /* CE1 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + }; +}; + +&gpio { + uart0_pins: uart0_pins { + brcm,pins; + brcm,function; + brcm,pull; + }; +}; + +&i2c0if { + clock-frequency = <100000>; +}; + +&i2c1 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c1_pins>; + clock-frequency = <100000>; +}; + +&i2s { + pinctrl-names = "default"; + pinctrl-0 = <&i2s_pins>; +}; + +// ============================================= +// Board specific stuff here + +/* Enable USB in OTG-aware mode */ +&usb { + compatible = "brcm,bcm2835-usb"; + dr_mode = "otg"; + g-np-tx-fifo-size = <32>; + g-rx-fifo-size = <558>; + g-tx-fifo-size = <512 512 512 512 512 256 256>; + status = "okay"; +}; + +&sdhost { + status = "disabled"; +}; + +&gpio { + audio_pins: audio_pins { + brcm,pins = <>; + brcm,function = <>; + }; +}; + +/* Permanently disable HDMI1 */ +&hdmi1 { + compatible = "disabled"; +}; + +/* Permanently disable DDC1 */ +&ddc1 { + compatible = "disabled"; +}; + +&led_act { + default-state = "off"; + linux,default-trigger = "mmc0"; +}; + +&pwm1 { + status = "disabled"; +}; + +&vchiq { + pinctrl-names = "default"; + pinctrl-0 = <&audio_pins>; +}; + +&cam1_reg { + gpio = <&gpio 3 GPIO_ACTIVE_HIGH>; + status = "disabled"; +}; + +cam0_reg: &cam0_regulator { + gpio = <&gpio 31 GPIO_ACTIVE_HIGH>; + status = "disabled"; +}; + +i2c_csi_dsi0: &i2c0 { +}; + +/ { + __overrides__ { + audio = <&chosen>,"bootargs{on='snd_bcm2835.enable_hdmi=1',off='snd_bcm2835.enable_hdmi=0'}"; + + act_led_gpio = <&led_act>,"gpios:4"; + act_led_activelow = <&led_act>,"gpios:8"; + act_led_trigger = <&led_act>,"linux,default-trigger"; + }; +}; diff --git a/arch/arm/boot/dts/broadcom/bcm2711-rpi-ds.dtsi b/arch/arm/boot/dts/broadcom/bcm2711-rpi-ds.dtsi new file mode 100644 index 00000000000000..fd70dea32e3a54 --- /dev/null +++ b/arch/arm/boot/dts/broadcom/bcm2711-rpi-ds.dtsi @@ -0,0 +1,562 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "bcm270x-rpi.dtsi" + +/ { + chosen { + bootargs = "coherent_pool=1M 8250.nr_uarts=1 snd_bcm2835.enable_headphones=0 cgroup_disable=memory numa_policy=interleave"; + }; + + __overrides__ { + arm_freq; + eee = <&chosen>,"bootargs{on='',off='genet.eee=N'}"; + hdmi = <&hdmi0>,"status", + <&hdmi1>,"status"; + nvmem_cust_rw = <&nvmem_cust>,"rw?"; + nvmem_priv_rw = <&nvmem_priv>,"rw?"; + pcie = <&pcie0>,"status"; + sd = <&emmc2>,"status"; + + sd_poll_once = <&emmc2>, "non-removable?"; + spi_dma4 = <&spi0>, "dmas:0=", <&dma40>, + <&spi0>, "dmas:8=", <&dma40>; + i2s_dma4 = <&i2s>, "dmas:0=", <&dma40>, + <&i2s>, "dmas:8=", <&dma40>; + }; + + scb: scb { + /* Add a label */ + }; + + soc: soc { + /* Add a label */ + }; + + chosen { + stdout-path = "serial0:115200n8"; + }; + + aliases { + uart2 = &uart2; + uart3 = &uart3; + uart4 = &uart4; + uart5 = &uart5; + serial0 = &uart1; + serial1 = &uart0; + serial2 = &uart2; + serial3 = &uart3; + serial4 = &uart4; + serial5 = &uart5; + mmc0 = &emmc2; + mmc1 = &mmcnr; + mmc2 = &sdhost; + i2c3 = &i2c3; + i2c4 = &i2c4; + i2c5 = &i2c5; + i2c6 = &i2c6; + i2c20 = &ddc0; + i2c21 = &ddc1; + spi3 = &spi3; + spi4 = &spi4; + spi5 = &spi5; + spi6 = &spi6; + /delete-property/ intc; + }; + + /* + * Add a node with a dma-ranges value that exists only to be found + * by of_dma_get_max_cpu_address, and hence limit the DMA zone. + */ + zone_dma { + #address-cells = <1>; + #size-cells = <1>; + dma-ranges = <0x0 0x0 0x0 0x40000000>; + }; +}; + +&vc4 { + raspberrypi,firmware = <&firmware>; +}; + +&cma { + /* Limit cma to the lower 768MB to allow room for HIGHMEM on 32-bit */ + alloc-ranges = <0x0 0x00000000 0x30000000>; +}; + +&soc { + /* Add the physical <-> DMA mapping for the I/O space */ + dma-ranges = <0xc0000000 0x0 0x00000000 0x40000000>, + <0x7c000000 0x0 0xfc000000 0x03800000>; + nvmem { + compatible = "simple-bus"; + #address-cells = <1>; + #size-cells = <1>; + + nvmem_otp: nvmem_otp { + compatible = "raspberrypi,rpi-otp"; + firmware = <&firmware>; + reg = <0 166>; + status = "okay"; + }; + + nvmem_cust: nvmem_cust { + compatible = "raspberrypi,rpi-otp"; + firmware = <&firmware>; + reg = <1 8>; + status = "okay"; + }; + + nvmem_priv: nvmem_priv { + compatible = "raspberrypi,rpi-otp"; + firmware = <&firmware>; + reg = <3 8>; + status = "okay"; + }; + }; +}; + +&scb { + #size-cells = <2>; + + ranges = <0x0 0x7c000000 0x0 0xfc000000 0x0 0x03800000>, + <0x0 0x40000000 0x0 0xff800000 0x0 0x00800000>, + <0x6 0x00000000 0x6 0x00000000 0x0 0x40000000>, + <0x0 0x00000000 0x0 0x00000000 0x0 0xfc000000>; + dma-ranges = <0x4 0x7c000000 0x0 0xfc000000 0x0 0x03800000>, + <0x0 0x00000000 0x0 0x00000000 0x4 0x00000000>; + + dma40: dma@7e007b00 { + compatible = "brcm,bcm2711-dma"; + reg = <0x0 0x7e007b00 0x0 0x400>; + interrupts = + <GIC_SPI 89 IRQ_TYPE_LEVEL_HIGH>, /* dma4 11 */ + <GIC_SPI 90 IRQ_TYPE_LEVEL_HIGH>, /* dma4 12 */ + <GIC_SPI 91 IRQ_TYPE_LEVEL_HIGH>, /* dma4 13 */ + <GIC_SPI 92 IRQ_TYPE_LEVEL_HIGH>; /* dma4 14 */ + interrupt-names = "dma11", + "dma12", + "dma13", + "dma14"; + #dma-cells = <1>; + brcm,dma-channel-mask = <0x7800>; + }; + + xhci: xhci@7e9c0000 { + compatible = "generic-xhci"; + status = "disabled"; + reg = <0x0 0x7e9c0000 0x0 0x100000>; + interrupts = <GIC_SPI 176 IRQ_TYPE_LEVEL_HIGH>; + power-domains = <&power RPI_POWER_DOMAIN_USB>; + }; + + codec@7eb10000 { + compatible = "raspberrypi,rpivid-vid-decoder"; + reg = <0x0 0x7eb10000 0x0 0x1000>, /* INTC */ + <0x0 0x7eb00000 0x0 0x10000>; /* HEVC */ + reg-names = "intc", + "hevc"; + interrupts = <GIC_SPI 98 IRQ_TYPE_LEVEL_HIGH>; + + clocks = <&firmware_clocks 11>; + clock-names = "hevc"; + }; +}; + +&pcie0 { + reg = <0x0 0x7d500000 0x0 0x9310>; + ranges = <0x02000000 0x0 0xc0000000 0x6 0x00000000 + 0x0 0x40000000>; +}; + +&genet { + reg = <0x0 0x7d580000 0x0 0x10000>; +}; + +&dma40 { + /* The VPU firmware uses DMA channel 11 for VCHIQ */ + brcm,dma-channel-mask = <0x7000>; +}; + +&vchiq { + compatible = "brcm,bcm2711-vchiq"; +}; + +&firmwarekms { + compatible = "raspberrypi,rpi-firmware-kms-2711"; + interrupts = <GIC_SPI 112 IRQ_TYPE_LEVEL_HIGH>; +}; + +&smi { + interrupts = <GIC_SPI 112 IRQ_TYPE_LEVEL_HIGH>; +}; + +&mmc { + interrupts = <GIC_SPI 126 IRQ_TYPE_LEVEL_HIGH>; +}; + +&mmcnr { + interrupts = <GIC_SPI 126 IRQ_TYPE_LEVEL_HIGH>; +}; + +&csi0 { + interrupts = <GIC_SPI 102 IRQ_TYPE_LEVEL_HIGH>; +}; + +&csi1 { + interrupts = <GIC_SPI 103 IRQ_TYPE_LEVEL_HIGH>; +}; + +&random { + compatible = "brcm,bcm2711-rng200"; + status = "okay"; +}; + +&usb { + /* Enable the FIQ support */ + reg = <0x7e980000 0x10000>, + <0x7e00b200 0x200>; + interrupts = <GIC_SPI 73 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 40 IRQ_TYPE_LEVEL_HIGH>; + status = "disabled"; +}; + +&gpio { + interrupts = <GIC_SPI 113 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 114 IRQ_TYPE_LEVEL_HIGH>; + + spi0_pins: spi0_pins { + brcm,pins = <9 10 11>; + brcm,function = <BCM2835_FSEL_ALT0>; + }; + + spi0_cs_pins: spi0_cs_pins { + brcm,pins = <8 7>; + brcm,function = <BCM2835_FSEL_GPIO_OUT>; + }; + + spi3_pins: spi3_pins { + brcm,pins = <1 2 3>; + brcm,function = <BCM2835_FSEL_ALT3>; + }; + + spi3_cs_pins: spi3_cs_pins { + brcm,pins = <0 24>; + brcm,function = <BCM2835_FSEL_GPIO_OUT>; + }; + + spi4_pins: spi4_pins { + brcm,pins = <5 6 7>; + brcm,function = <BCM2835_FSEL_ALT3>; + }; + + spi4_cs_pins: spi4_cs_pins { + brcm,pins = <4 25>; + brcm,function = <BCM2835_FSEL_GPIO_OUT>; + }; + + spi5_pins: spi5_pins { + brcm,pins = <13 14 15>; + brcm,function = <BCM2835_FSEL_ALT3>; + }; + + spi5_cs_pins: spi5_cs_pins { + brcm,pins = <12 26>; + brcm,function = <BCM2835_FSEL_GPIO_OUT>; + }; + + spi6_pins: spi6_pins { + brcm,pins = <19 20 21>; + brcm,function = <BCM2835_FSEL_ALT3>; + }; + + spi6_cs_pins: spi6_cs_pins { + brcm,pins = <18 27>; + brcm,function = <BCM2835_FSEL_GPIO_OUT>; + }; + + i2c0_pins: i2c0 { + brcm,pins = <0 1>; + brcm,function = <BCM2835_FSEL_ALT0>; + brcm,pull = <BCM2835_PUD_UP>; + }; + + i2c1_pins: i2c1 { + brcm,pins = <2 3>; + brcm,function = <BCM2835_FSEL_ALT0>; + brcm,pull = <BCM2835_PUD_UP>; + }; + + i2c3_pins: i2c3 { + brcm,pins = <4 5>; + brcm,function = <BCM2835_FSEL_ALT5>; + brcm,pull = <BCM2835_PUD_UP>; + }; + + i2c4_pins: i2c4 { + brcm,pins = <8 9>; + brcm,function = <BCM2835_FSEL_ALT5>; + brcm,pull = <BCM2835_PUD_UP>; + }; + + i2c5_pins: i2c5 { + brcm,pins = <12 13>; + brcm,function = <BCM2835_FSEL_ALT5>; + brcm,pull = <BCM2835_PUD_UP>; + }; + + i2c6_pins: i2c6 { + brcm,pins = <22 23>; + brcm,function = <BCM2835_FSEL_ALT5>; + brcm,pull = <BCM2835_PUD_UP>; + }; + + i2s_pins: i2s { + brcm,pins = <18 19 20 21>; + brcm,function = <BCM2835_FSEL_ALT0>; + }; + + sdio_pins: sdio_pins { + brcm,pins = <34 35 36 37 38 39>; + brcm,function = <BCM2835_FSEL_ALT3>; // alt3 = SD1 + brcm,pull = <0 2 2 2 2 2>; + }; + + uart2_pins: uart2_pins { + brcm,pins = <0 1>; + brcm,function = <BCM2835_FSEL_ALT4>; + brcm,pull = <0 2>; + }; + + uart3_pins: uart3_pins { + brcm,pins = <4 5>; + brcm,function = <BCM2835_FSEL_ALT4>; + brcm,pull = <0 2>; + }; + + uart4_pins: uart4_pins { + brcm,pins = <8 9>; + brcm,function = <BCM2835_FSEL_ALT4>; + brcm,pull = <0 2>; + }; + + uart5_pins: uart5_pins { + brcm,pins = <12 13>; + brcm,function = <BCM2835_FSEL_ALT4>; + brcm,pull = <0 2>; + }; +}; + +&emmc2 { + mmc-ddr-3_3v; +}; + +&vc4 { + status = "disabled"; +}; + +&pixelvalve0 { + status = "disabled"; +}; + +&pixelvalve1 { + status = "disabled"; +}; + +&pixelvalve2 { + status = "disabled"; +}; + +&pixelvalve3 { + status = "disabled"; +}; + +&pixelvalve4 { + status = "disabled"; +}; + +&hdmi0 { + reg = <0x7ef00700 0x300>, + <0x7ef00300 0x200>, + <0x7ef00f00 0x80>, + <0x7ef00f80 0x80>, + <0x7ef01b00 0x200>, + <0x7ef01f00 0x400>, + <0x7ef00200 0x80>, + <0x7ef04300 0x100>, + <0x7ef20000 0x100>, + <0x7ef00100 0x30>; + reg-names = "hdmi", + "dvp", + "phy", + "rm", + "packet", + "metadata", + "csc", + "cec", + "hd", + "intr2"; + clocks = <&firmware_clocks 13>, + <&firmware_clocks 14>, + <&dvp 0>, + <&clk_27MHz>; + dmas = <&dma40 (10|(1<<30)|(1<<24)|(10<<16)|(15<<20))>; + status = "disabled"; +}; + +&ddc0 { + status = "disabled"; +}; + +&hdmi1 { + reg = <0x7ef05700 0x300>, + <0x7ef05300 0x200>, + <0x7ef05f00 0x80>, + <0x7ef05f80 0x80>, + <0x7ef06b00 0x200>, + <0x7ef06f00 0x400>, + <0x7ef00280 0x80>, + <0x7ef09300 0x100>, + <0x7ef20000 0x100>, + <0x7ef00100 0x30>; + reg-names = "hdmi", + "dvp", + "phy", + "rm", + "packet", + "metadata", + "csc", + "cec", + "hd", + "intr2"; + clocks = <&firmware_clocks 13>, + <&firmware_clocks 14>, + <&dvp 1>, + <&clk_27MHz>; + dmas = <&dma40 (17|(1<<30)|(1<<24)|(10<<16)|(15<<20))>; + status = "disabled"; +}; + +&ddc1 { + status = "disabled"; +}; + +&dvp { + status = "disabled"; +}; + +&vec { + clocks = <&firmware_clocks 15>; +}; + +&aon_intr { + interrupts = <GIC_SPI 96 IRQ_TYPE_EDGE_RISING>; + status = "disabled"; +}; + +&system_timer { + status = "disabled"; +}; + +&i2c0 { + /delete-property/ compatible; + /delete-property/ interrupts; +}; + +&i2c0if { + compatible = "brcm,bcm2711-i2c", "brcm,bcm2835-i2c"; + interrupts = <GIC_SPI 117 IRQ_TYPE_LEVEL_HIGH>; +}; + +i2c_arm: &i2c1 {}; +i2c_vc: &i2c0 {}; + +&i2c3 { + pinctrl-0 = <&i2c3_pins>; + pinctrl-names = "default"; +}; + +&i2c4 { + pinctrl-0 = <&i2c4_pins>; + pinctrl-names = "default"; +}; + +&i2c5 { + pinctrl-0 = <&i2c5_pins>; + pinctrl-names = "default"; +}; + +&i2c6 { + pinctrl-0 = <&i2c6_pins>; + pinctrl-names = "default"; +}; + +&spi3 { + pinctrl-0 = <&spi3_pins &spi3_cs_pins>; + pinctrl-names = "default"; +}; + +&spi4 { + pinctrl-0 = <&spi4_pins &spi4_cs_pins>; + pinctrl-names = "default"; +}; + +&spi5 { + pinctrl-0 = <&spi5_pins &spi5_cs_pins>; + pinctrl-names = "default"; +}; + +&spi6 { + pinctrl-0 = <&spi6_pins &spi6_cs_pins>; + pinctrl-names = "default"; +}; + +&uart2 { + pinctrl-0 = <&uart2_pins>; + pinctrl-names = "default"; +}; + +&uart3 { + pinctrl-0 = <&uart3_pins>; + pinctrl-names = "default"; +}; + +&uart4 { + pinctrl-0 = <&uart4_pins>; + pinctrl-names = "default"; +}; + +&uart5 { + pinctrl-0 = <&uart5_pins>; + pinctrl-names = "default"; +}; + +&axiperf { + compatible = "brcm,bcm2711-axiperf"; +}; + +/delete-node/ &v3d; + +/ { + v3dbus: v3dbus { + compatible = "simple-bus"; + #address-cells = <1>; + #size-cells = <2>; + ranges = <0x7c500000 0x0 0xfc500000 0x0 0x03300000>, + <0x40000000 0x0 0xff800000 0x0 0x00800000>; + dma-ranges = <0x00000000 0x0 0x00000000 0x4 0x00000000>; + + v3d: v3d@7ec04000 { + compatible = "brcm,2711-v3d"; + reg = + <0x7ec00000 0x0 0x4000>, + <0x7ec04000 0x0 0x4000>; + reg-names = "hub", "core0"; + + power-domains = <&pm BCM2835_POWER_DOMAIN_GRAFX_V3D>; + resets = <&pm BCM2835_RESET_V3D>; + clocks = <&firmware_clocks 5>; + clocks-names = "v3d"; + interrupts = <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>; + status = "disabled"; + }; + }; +}; diff --git a/arch/arm/boot/dts/broadcom/bcm2711-rpi.dtsi b/arch/arm/boot/dts/broadcom/bcm2711-rpi.dtsi index 6bf4241fe3b737..da5f54e7dd2447 100644 --- a/arch/arm/boot/dts/broadcom/bcm2711-rpi.dtsi +++ b/arch/arm/boot/dts/broadcom/bcm2711-rpi.dtsi @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 #include "bcm2835-rpi.dtsi" -#include <dt-bindings/power/raspberrypi-power.h> #include <dt-bindings/reset/raspberrypi,firmware-reset.h> / { @@ -16,6 +15,7 @@ ethernet0 = &genet; pcie0 = &pcie0; blconfig = &blconfig; + blpubkey = &blpubkey; }; i2c0mux: i2c-mux0 { @@ -92,6 +92,18 @@ no-map; status = "disabled"; }; + /* + * RPi4 will copy the binary public key blob (if present) from the bootloader + * into memory for use by the OS. + */ + blpubkey: nvram@1 { + compatible = "raspberrypi,bootloader-public-key", "nvmem-rmem"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0x0 0x0 0x0>; + no-map; + status = "disabled"; + }; }; &v3d { @@ -101,7 +113,3 @@ &vchiq { interrupts = <GIC_SPI 34 IRQ_TYPE_LEVEL_HIGH>; }; - -&xhci { - power-domains = <&power RPI_POWER_DOMAIN_USB>; -}; diff --git a/arch/arm/boot/dts/broadcom/bcm2711.dtsi b/arch/arm/boot/dts/broadcom/bcm2711.dtsi index e4e42af21ef3a4..21c628c55b5808 100644 --- a/arch/arm/boot/dts/broadcom/bcm2711.dtsi +++ b/arch/arm/boot/dts/broadcom/bcm2711.dtsi @@ -134,7 +134,7 @@ clocks = <&clocks BCM2835_CLOCK_UART>, <&clocks BCM2835_CLOCK_VPU>; clock-names = "uartclk", "apb_pclk"; - arm,primecell-periphid = <0x00241011>; + arm,primecell-periphid = <0x00341011>; status = "disabled"; }; @@ -145,7 +145,7 @@ clocks = <&clocks BCM2835_CLOCK_UART>, <&clocks BCM2835_CLOCK_VPU>; clock-names = "uartclk", "apb_pclk"; - arm,primecell-periphid = <0x00241011>; + arm,primecell-periphid = <0x00341011>; status = "disabled"; }; @@ -156,7 +156,7 @@ clocks = <&clocks BCM2835_CLOCK_UART>, <&clocks BCM2835_CLOCK_VPU>; clock-names = "uartclk", "apb_pclk"; - arm,primecell-periphid = <0x00241011>; + arm,primecell-periphid = <0x00341011>; status = "disabled"; }; @@ -167,7 +167,7 @@ clocks = <&clocks BCM2835_CLOCK_UART>, <&clocks BCM2835_CLOCK_VPU>; clock-names = "uartclk", "apb_pclk"; - arm,primecell-periphid = <0x00241011>; + arm,primecell-periphid = <0x00341011>; status = "disabled"; }; @@ -277,7 +277,7 @@ reg = <0x7e20c800 0x28>; clocks = <&clocks BCM2835_CLOCK_PWM>; assigned-clocks = <&clocks BCM2835_CLOCK_PWM>; - assigned-clock-rates = <10000000>; + assigned-clock-rates = <50000000>; #pwm-cells = <3>; status = "disabled"; }; @@ -451,8 +451,6 @@ IRQ_TYPE_LEVEL_LOW)>, <GIC_PPI 10 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>; - /* This only applies to the ARMv7 stub */ - arm,cpu-registers-not-fw-configured; }; cpus: cpus { @@ -604,20 +602,6 @@ }; }; - xhci: usb@7e9c0000 { - compatible = "brcm,bcm2711-xhci", "brcm,xhci-brcm-v2"; - reg = <0x0 0x7e9c0000 0x100000>; - #address-cells = <1>; - #size-cells = <0>; - interrupts = <GIC_SPI 176 IRQ_TYPE_LEVEL_HIGH>; - /* DWC2 and this IP block share the same USB PHY, - * enabling both at the same time results in lockups. - * So keep this node disabled and let the bootloader - * decide which interface should be enabled. - */ - status = "disabled"; - }; - v3d: gpu@7ec00000 { compatible = "brcm,2711-v3d"; reg = <0x0 0x7ec00000 0x4000>, @@ -1177,6 +1161,7 @@ }; &uart0 { + arm,primecell-periphid = <0x00341011>; interrupts = <GIC_SPI 121 IRQ_TYPE_LEVEL_HIGH>; }; diff --git a/arch/arm/boot/dts/broadcom/bcm271x-rpi-bt.dtsi b/arch/arm/boot/dts/broadcom/bcm271x-rpi-bt.dtsi new file mode 100644 index 00000000000000..c77e280ccd163e --- /dev/null +++ b/arch/arm/boot/dts/broadcom/bcm271x-rpi-bt.dtsi @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-2.0 + +&uart0 { + bt: bluetooth { + compatible = "brcm,bcm43438-bt"; + max-speed = <3000000>; + shutdown-gpios = <&expgpio 0 GPIO_ACTIVE_HIGH>; + local-bd-address = [ 00 00 00 00 00 00 ]; + fallback-bd-address; // Don't override a valid address + status = "okay"; + }; +}; + +&uart1 { + minibt: bluetooth { + compatible = "brcm,bcm43438-bt"; + max-speed = <230400>; + shutdown-gpios = <&expgpio 0 GPIO_ACTIVE_HIGH>; + local-bd-address = [ 00 00 00 00 00 00 ]; + fallback-bd-address; // Don't override a valid address + status = "disabled"; + }; +}; + +/ { + chosen { + bootargs = "coherent_pool=1M 8250.nr_uarts=1 snd_bcm2835.enable_headphones=0 cgroup_disable=memory"; + }; + + aliases { + bluetooth = &bt; + }; + + __overrides__ { + bdaddr = <&bt>,"local-bd-address[", + <&bt>,"fallback-bd-address?=0", + <&minibt>,"local-bd-address[", + <&minibt>,"fallback-bd-address?=0"; + krnbt = <&bt>,"status"; + krnbt_baudrate = <&bt>,"max-speed:0", <&minibt>,"max-speed:0"; + }; +}; diff --git a/arch/arm/boot/dts/broadcom/bcm283x-rpi-csi0-2lane.dtsi b/arch/arm/boot/dts/broadcom/bcm283x-rpi-csi0-2lane.dtsi new file mode 100644 index 00000000000000..6e4ce8622b4774 --- /dev/null +++ b/arch/arm/boot/dts/broadcom/bcm283x-rpi-csi0-2lane.dtsi @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only +&csi0 { + brcm,num-data-lanes = <2>; +}; diff --git a/arch/arm/boot/dts/broadcom/bcm283x-rpi-csi1-2lane.dtsi b/arch/arm/boot/dts/broadcom/bcm283x-rpi-csi1-2lane.dtsi new file mode 100644 index 00000000000000..6938f4daacdc20 --- /dev/null +++ b/arch/arm/boot/dts/broadcom/bcm283x-rpi-csi1-2lane.dtsi @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only +&csi1 { + brcm,num-data-lanes = <2>; +}; diff --git a/arch/arm/boot/dts/broadcom/bcm283x-rpi-csi1-4lane.dtsi b/arch/arm/boot/dts/broadcom/bcm283x-rpi-csi1-4lane.dtsi new file mode 100644 index 00000000000000..b37037437beed2 --- /dev/null +++ b/arch/arm/boot/dts/broadcom/bcm283x-rpi-csi1-4lane.dtsi @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only +&csi1 { + brcm,num-data-lanes = <4>; +}; diff --git a/arch/arm/boot/dts/broadcom/bcm283x-rpi-i2c0mux_0_28.dtsi b/arch/arm/boot/dts/broadcom/bcm283x-rpi-i2c0mux_0_28.dtsi new file mode 100644 index 00000000000000..38f0074bce3ff9 --- /dev/null +++ b/arch/arm/boot/dts/broadcom/bcm283x-rpi-i2c0mux_0_28.dtsi @@ -0,0 +1,4 @@ +&i2c0mux { + pinctrl-0 = <&i2c0_gpio0>; + pinctrl-1 = <&i2c0_gpio28>; +}; diff --git a/arch/arm/boot/dts/broadcom/bcm283x-rpi-i2c0mux_0_44.dtsi b/arch/arm/boot/dts/broadcom/bcm283x-rpi-i2c0mux_0_44.dtsi new file mode 100644 index 00000000000000..119946d878dbf2 --- /dev/null +++ b/arch/arm/boot/dts/broadcom/bcm283x-rpi-i2c0mux_0_44.dtsi @@ -0,0 +1,4 @@ +&i2c0mux { + pinctrl-0 = <&i2c0_gpio0>; + pinctrl-1 = <&i2c0_gpio44>; +}; diff --git a/arch/arm/boot/dts/broadcom/bcm283x.dtsi b/arch/arm/boot/dts/broadcom/bcm283x.dtsi index 69b0919f1324ab..562c4e9d08cc0a 100644 --- a/arch/arm/boot/dts/broadcom/bcm283x.dtsi +++ b/arch/arm/boot/dts/broadcom/bcm283x.dtsi @@ -363,7 +363,7 @@ #size-cells = <0>; #clock-cells = <1>; - clocks = <&clocks BCM2835_PLLA_DSI0>, + clocks = <&clocks BCM2835_PLLD_DSI0>, <&clocks BCM2835_CLOCK_DSI0E>, <&clocks BCM2835_CLOCK_DSI0P>; clock-names = "phy", "escape", "pixel"; @@ -415,7 +415,7 @@ reg = <0x7e20c000 0x28>; clocks = <&clocks BCM2835_CLOCK_PWM>; assigned-clocks = <&clocks BCM2835_CLOCK_PWM>; - assigned-clock-rates = <10000000>; + assigned-clock-rates = <50000000>; #pwm-cells = <3>; status = "disabled"; }; @@ -502,6 +502,10 @@ }; clocks { + compatible = "simple-bus"; + #address-cells = <1>; + #size-cells = <0>; + /* The oscillator is the root of the clock tree. */ clk_osc: clk-osc { compatible = "fixed-clock"; diff --git a/arch/arm/boot/dts/overlays/Makefile b/arch/arm/boot/dts/overlays/Makefile new file mode 100644 index 00000000000000..92ff31f6406be6 --- /dev/null +++ b/arch/arm/boot/dts/overlays/Makefile @@ -0,0 +1,359 @@ +# Overlays for the Raspberry Pi platform + +dtb-$(CONFIG_ARCH_BCM2835) += overlay_map.dtb hat_map.dtb README + +dtbo-$(CONFIG_ARCH_BCM2835) += \ + act-led.dtbo \ + adafruit-st7735r.dtbo \ + adafruit18.dtbo \ + adau1977-adc.dtbo \ + adau7002-simple.dtbo \ + ads1015.dtbo \ + ads1115.dtbo \ + ads7846.dtbo \ + adv7282m.dtbo \ + adv728x-m.dtbo \ + akkordion-iqdacplus.dtbo \ + allo-boss-dac-pcm512x-audio.dtbo \ + allo-boss2-dac-audio.dtbo \ + allo-digione.dtbo \ + allo-katana-dac-audio.dtbo \ + allo-piano-dac-pcm512x-audio.dtbo \ + allo-piano-dac-plus-pcm512x-audio.dtbo \ + anyspi.dtbo \ + apds9960.dtbo \ + applepi-dac.dtbo \ + arducam-64mp.dtbo \ + arducam-pivariety.dtbo \ + at86rf233.dtbo \ + audioinjector-addons.dtbo \ + audioinjector-bare-i2s.dtbo \ + audioinjector-isolated-soundcard.dtbo \ + audioinjector-ultra.dtbo \ + audioinjector-wm8731-audio.dtbo \ + audiosense-pi.dtbo \ + audremap.dtbo \ + audremap-pi5.dtbo \ + balena-fin.dtbo \ + bcm2712d0.dtbo \ + camera-mux-2port.dtbo \ + camera-mux-4port.dtbo \ + cap1106.dtbo \ + chipcap2.dtbo \ + chipdip-dac.dtbo \ + cirrus-wm5102.dtbo \ + cm-swap-i2c0.dtbo \ + cma.dtbo \ + crystalfontz-cfa050_pi_m.dtbo \ + cutiepi-panel.dtbo \ + dacberry400.dtbo \ + dht11.dtbo \ + dionaudio-kiwi.dtbo \ + dionaudio-loco.dtbo \ + dionaudio-loco-v2.dtbo \ + disable-bt.dtbo \ + disable-bt-pi5.dtbo \ + disable-emmc2.dtbo \ + disable-wifi.dtbo \ + disable-wifi-pi5.dtbo \ + dpi18.dtbo \ + dpi18cpadhi.dtbo \ + dpi24.dtbo \ + draws.dtbo \ + dwc-otg.dtbo \ + dwc2.dtbo \ + edt-ft5406.dtbo \ + enc28j60.dtbo \ + enc28j60-spi2.dtbo \ + exc3000.dtbo \ + ezsound-6x8iso.dtbo \ + fbtft.dtbo \ + fe-pi-audio.dtbo \ + fsm-demo.dtbo \ + gc9a01.dtbo \ + ghost-amp.dtbo \ + goodix.dtbo \ + googlevoicehat-soundcard.dtbo \ + gpio-charger.dtbo \ + gpio-fan.dtbo \ + gpio-hog.dtbo \ + gpio-ir.dtbo \ + gpio-ir-tx.dtbo \ + gpio-key.dtbo \ + gpio-led.dtbo \ + gpio-no-bank0-irq.dtbo \ + gpio-no-irq.dtbo \ + gpio-poweroff.dtbo \ + gpio-shutdown.dtbo \ + hd44780-i2c-lcd.dtbo \ + hd44780-lcd.dtbo \ + hdmi-backlight-hwhack-gpio.dtbo \ + hifiberry-adc.dtbo \ + hifiberry-adc8x.dtbo \ + hifiberry-amp.dtbo \ + hifiberry-amp100.dtbo \ + hifiberry-amp3.dtbo \ + hifiberry-amp4pro.dtbo \ + hifiberry-dac.dtbo \ + hifiberry-dac8x.dtbo \ + hifiberry-dacplus.dtbo \ + hifiberry-dacplus-pro.dtbo \ + hifiberry-dacplus-std.dtbo \ + hifiberry-dacplusadc.dtbo \ + hifiberry-dacplusadcpro.dtbo \ + hifiberry-dacplusdsp.dtbo \ + hifiberry-dacplushd.dtbo \ + hifiberry-digi.dtbo \ + hifiberry-digi-pro.dtbo \ + highperi.dtbo \ + hy28a.dtbo \ + hy28b.dtbo \ + hy28b-2017.dtbo \ + i-sabre-q2m.dtbo \ + i2c-bcm2708.dtbo \ + i2c-fan.dtbo \ + i2c-gpio.dtbo \ + i2c-mux.dtbo \ + i2c-pwm-pca9685a.dtbo \ + i2c-rtc.dtbo \ + i2c-rtc-gpio.dtbo \ + i2c-sensor.dtbo \ + i2c0.dtbo \ + i2c0-pi5.dtbo \ + i2c1.dtbo \ + i2c1-pi5.dtbo \ + i2c2-pi5.dtbo \ + i2c3.dtbo \ + i2c3-pi5.dtbo \ + i2c4.dtbo \ + i2c5.dtbo \ + i2c6.dtbo \ + i2s-dac.dtbo \ + i2s-gpio28-31.dtbo \ + i2s-master-dac.dtbo \ + ilitek251x.dtbo \ + imx219.dtbo \ + imx258.dtbo \ + imx290.dtbo \ + imx296.dtbo \ + imx327.dtbo \ + imx378.dtbo \ + imx415.dtbo \ + imx462.dtbo \ + imx477.dtbo \ + imx500.dtbo \ + imx500-pi5.dtbo \ + imx519.dtbo \ + imx708.dtbo \ + interludeaudio-analog.dtbo \ + interludeaudio-digital.dtbo \ + iqaudio-codec.dtbo \ + iqaudio-dac.dtbo \ + iqaudio-dacplus.dtbo \ + iqaudio-digi-wm8804-audio.dtbo \ + iqs550.dtbo \ + irs1125.dtbo \ + jedec-spi-nor.dtbo \ + justboom-both.dtbo \ + justboom-dac.dtbo \ + justboom-digi.dtbo \ + ltc294x.dtbo \ + max98357a.dtbo \ + maxtherm.dtbo \ + mbed-dac.dtbo \ + mcp23017.dtbo \ + mcp23s17.dtbo \ + mcp2515.dtbo \ + mcp2515-can0.dtbo \ + mcp2515-can1.dtbo \ + mcp251xfd.dtbo \ + mcp3008.dtbo \ + mcp3202.dtbo \ + mcp342x.dtbo \ + media-center.dtbo \ + merus-amp.dtbo \ + midi-uart0.dtbo \ + midi-uart0-pi5.dtbo \ + midi-uart1.dtbo \ + midi-uart1-pi5.dtbo \ + midi-uart2.dtbo \ + midi-uart2-pi5.dtbo \ + midi-uart3.dtbo \ + midi-uart3-pi5.dtbo \ + midi-uart4.dtbo \ + midi-uart4-pi5.dtbo \ + midi-uart5.dtbo \ + minipitft13.dtbo \ + miniuart-bt.dtbo \ + mipi-dbi-spi.dtbo \ + mlx90640.dtbo \ + mmc.dtbo \ + mz61581.dtbo \ + ov2311.dtbo \ + ov5647.dtbo \ + ov64a40.dtbo \ + ov7251.dtbo \ + ov9281.dtbo \ + papirus.dtbo \ + pca953x.dtbo \ + pcf857x.dtbo \ + pcie-32bit-dma.dtbo \ + pcie-32bit-dma-pi5.dtbo \ + pciex1-compat-pi5.dtbo \ + pibell.dtbo \ + pifacedigital.dtbo \ + pifi-40.dtbo \ + pifi-dac-hd.dtbo \ + pifi-dac-zero.dtbo \ + pifi-mini-210.dtbo \ + piglow.dtbo \ + pimidi.dtbo \ + pineboards-hat-ai.dtbo \ + pineboards-hatdrive-poe-plus.dtbo \ + piscreen.dtbo \ + piscreen2r.dtbo \ + pisound.dtbo \ + pisound-pi5.dtbo \ + pitft22.dtbo \ + pitft28-capacitive.dtbo \ + pitft28-resistive.dtbo \ + pitft35-resistive.dtbo \ + pps-gpio.dtbo \ + proto-codec.dtbo \ + pwm.dtbo \ + pwm-2chan.dtbo \ + pwm-gpio.dtbo \ + pwm-gpio-fan.dtbo \ + pwm-ir-tx.dtbo \ + pwm-pio.dtbo \ + pwm1.dtbo \ + qca7000.dtbo \ + qca7000-uart0.dtbo \ + ramoops.dtbo \ + ramoops-pi4.dtbo \ + rootmaster.dtbo \ + rotary-encoder.dtbo \ + rpi-backlight.dtbo \ + rpi-codeczero.dtbo \ + rpi-dacplus.dtbo \ + rpi-dacpro.dtbo \ + rpi-digiampplus.dtbo \ + rpi-ft5406.dtbo \ + rpi-fw-uart.dtbo \ + rpi-poe.dtbo \ + rpi-poe-plus.dtbo \ + rpi-sense.dtbo \ + rpi-sense-v2.dtbo \ + rpi-tv.dtbo \ + rra-digidac1-wm8741-audio.dtbo \ + sainsmart18.dtbo \ + sc16is750-i2c.dtbo \ + sc16is750-spi0.dtbo \ + sc16is752-i2c.dtbo \ + sc16is752-spi0.dtbo \ + sc16is752-spi1.dtbo \ + sdhost.dtbo \ + sdio.dtbo \ + sdio-pi5.dtbo \ + seeed-can-fd-hat-v1.dtbo \ + seeed-can-fd-hat-v2.dtbo \ + sh1106-spi.dtbo \ + si446x-spi0.dtbo \ + smi.dtbo \ + smi-dev.dtbo \ + smi-nand.dtbo \ + spi-gpio35-39.dtbo \ + spi-gpio40-45.dtbo \ + spi-rtc.dtbo \ + spi0-0cs.dtbo \ + spi0-1cs.dtbo \ + spi0-1cs-inverted.dtbo \ + spi0-2cs.dtbo \ + spi1-1cs.dtbo \ + spi1-2cs.dtbo \ + spi1-3cs.dtbo \ + spi2-1cs.dtbo \ + spi2-1cs-pi5.dtbo \ + spi2-2cs.dtbo \ + spi2-2cs-pi5.dtbo \ + spi2-3cs.dtbo \ + spi3-1cs.dtbo \ + spi3-1cs-pi5.dtbo \ + spi3-2cs.dtbo \ + spi3-2cs-pi5.dtbo \ + spi4-1cs.dtbo \ + spi4-2cs.dtbo \ + spi5-1cs.dtbo \ + spi5-1cs-pi5.dtbo \ + spi5-2cs.dtbo \ + spi5-2cs-pi5.dtbo \ + spi6-1cs.dtbo \ + spi6-2cs.dtbo \ + ssd1306.dtbo \ + ssd1306-spi.dtbo \ + ssd1327-spi.dtbo \ + ssd1331-spi.dtbo \ + ssd1351-spi.dtbo \ + sunfounder-pipower3.dtbo \ + sunfounder-pironman5.dtbo \ + superaudioboard.dtbo \ + sx150x.dtbo \ + tc358743.dtbo \ + tc358743-audio.dtbo \ + tinylcd35.dtbo \ + tpm-slb9670.dtbo \ + tpm-slb9673.dtbo \ + uart0.dtbo \ + uart0-pi5.dtbo \ + uart1.dtbo \ + uart1-pi5.dtbo \ + uart2.dtbo \ + uart2-pi5.dtbo \ + uart3.dtbo \ + uart3-pi5.dtbo \ + uart4.dtbo \ + uart4-pi5.dtbo \ + uart5.dtbo \ + udrc.dtbo \ + ugreen-dabboard.dtbo \ + upstream.dtbo \ + upstream-pi4.dtbo \ + vc4-fkms-v3d.dtbo \ + vc4-fkms-v3d-pi4.dtbo \ + vc4-kms-dpi-generic.dtbo \ + vc4-kms-dpi-hyperpixel2r.dtbo \ + vc4-kms-dpi-hyperpixel4.dtbo \ + vc4-kms-dpi-hyperpixel4sq.dtbo \ + vc4-kms-dpi-panel.dtbo \ + vc4-kms-dsi-7inch.dtbo \ + vc4-kms-dsi-generic.dtbo \ + vc4-kms-dsi-ili9881-5inch.dtbo \ + vc4-kms-dsi-ili9881-7inch.dtbo \ + vc4-kms-dsi-lt070me05000.dtbo \ + vc4-kms-dsi-lt070me05000-v2.dtbo \ + vc4-kms-dsi-waveshare-800x480.dtbo \ + vc4-kms-dsi-waveshare-panel.dtbo \ + vc4-kms-kippah-7inch.dtbo \ + vc4-kms-v3d.dtbo \ + vc4-kms-v3d-pi4.dtbo \ + vc4-kms-v3d-pi5.dtbo \ + vc4-kms-vga666.dtbo \ + vga666.dtbo \ + vl805.dtbo \ + w1-gpio.dtbo \ + w1-gpio-pi5.dtbo \ + w1-gpio-pullup.dtbo \ + w1-gpio-pullup-pi5.dtbo \ + w5500.dtbo \ + watterott-display.dtbo \ + waveshare-can-fd-hat-mode-a.dtbo \ + waveshare-can-fd-hat-mode-b.dtbo \ + wittypi.dtbo \ + wm8960-soundcard.dtbo \ + ws2812-pio.dtbo + +targets += dtbs dtbs_install +targets += $(dtbo-y) + +always-y := $(dtbo-y) +clean-files := *.dtbo diff --git a/arch/arm/boot/dts/overlays/README b/arch/arm/boot/dts/overlays/README new file mode 100644 index 00000000000000..2736e1fac3f3fa --- /dev/null +++ b/arch/arm/boot/dts/overlays/README @@ -0,0 +1,5728 @@ +Introduction +============ + +This directory contains Device Tree overlays. Device Tree makes it possible +to support many hardware configurations with a single kernel and without the +need to explicitly load or blacklist kernel modules. Note that this isn't a +"pure" Device Tree configuration (c.f. MACH_BCM2835) - some on-board devices +are still configured by the board support code, but the intention is to +eventually reach that goal. + +On Raspberry Pi, Device Tree usage is controlled from /boot/config.txt. By +default, the Raspberry Pi kernel boots with device tree enabled. You can +completely disable DT usage (for now) by adding: + + device_tree= + +to your config.txt, which should cause your Pi to revert to the old way of +doing things after a reboot. + +In /boot you will find a .dtb for each base platform. This describes the +hardware that is part of the Raspberry Pi board. The loader (start.elf and its +siblings) selects the .dtb file appropriate for the platform by name, and reads +it into memory. At this point, all of the optional interfaces (i2c, i2s, spi) +are disabled, but they can be enabled using Device Tree parameters: + + dtparam=i2c=on,i2s=on,spi=on + +However, this shouldn't be necessary in many use cases because loading an +overlay that requires one of those interfaces will cause it to be enabled +automatically, and it is advisable to only enable interfaces if they are +needed. + +Configuring additional, optional hardware is done using Device Tree overlays +(see below). + +GPIO numbering uses the hardware pin numbering scheme (aka BCM scheme) and +not the physical pin numbers. + +raspi-config +============ + +The Advanced Options section of the raspi-config utility can enable and disable +Device Tree use, as well as toggling the I2C and SPI interfaces. Note that it +is possible to both enable an interface and blacklist the driver, if for some +reason you should want to defer the loading. + +Modules +======= + +As well as describing the hardware, Device Tree also gives enough information +to allow suitable driver modules to be located and loaded, with the corollary +that unneeded modules are not loaded. As a result it should be possible to +remove lines from /etc/modules, and /etc/modprobe.d/raspi-blacklist.conf can +have its contents deleted (or commented out). + +Using Overlays +============== + +Overlays are loaded using the "dtoverlay" config.txt setting. As an example, +consider I2C Real Time Clock drivers. In the pre-DT world these would be loaded +by writing a magic string comprising a device identifier and an I2C address to +a special file in /sys/class/i2c-adapter, having first loaded the driver for +the I2C interface and the RTC device - something like this: + + modprobe i2c-bcm2835 + modprobe rtc-ds1307 + echo ds1307 0x68 > /sys/class/i2c-adapter/i2c-1/new_device + +With DT enabled, this becomes a line in config.txt: + + dtoverlay=i2c-rtc,ds1307 + +This causes the file /boot/overlays/i2c-rtc.dtbo to be loaded and a "node" +describing the DS1307 I2C device to be added to the Device Tree for the Pi. By +default it usees address 0x68, but this can be modified with an additional DT +parameter: + + dtoverlay=i2c-rtc,ds1307,addr=0x68 + +Parameters usually have default values, although certain parameters are +mandatory. See the list of overlays below for a description of the parameters +and their defaults. + +Making new Overlays based on existing Overlays +============================================== + +Recent overlays have been designed in a more general way, so that they can be +adapted to hardware by changing their parameters. When you have additional +hardware with more than one device of a kind, you end up using the same overlay +multiple times with other parameters, e.g. + + # 2 CAN FD interfaces on spi but with different pins + dtoverlay=mcp251xfd,spi0-0,interrupt=25 + dtoverlay=mcp251xfd,spi0-1,interrupt=24 + + # a realtime clock on i2c + dtoverlay=i2c-rtc,pcf85063 + +While this approach does work, it requires knowledge about the hardware design. +It is more feasible to simplify things for the end user by providing a single +overlay as it is done the traditional way. + +A new overlay can be generated by using ovmerge utility. +https://github.com/raspberrypi/utils/blob/master/ovmerge/ovmerge + +To generate an overlay for the above configuration we pass the configuration +to ovmerge and add the -c flag. + + ovmerge -c mcp251xfd-overlay.dts,spi0-0,interrupt=25 \ + mcp251xfd-overlay.dts,spi0-1,interrupt=24 \ + i2c-rtc-overlay.dts,pcf85063 \ + >> merged-overlay.dts + +The -c option writes the command above as a comment into the overlay as +a marker that this overlay is generated and how it was generated. +After compiling the overlay it can be loaded in a single line. + + dtoverlay=merged + +It does the same as the original configuration but without parameters. + +The Overlay and Parameter Reference +=================================== + +N.B. When editing this file, please preserve the indentation levels to make it +simple to parse programmatically. NO HARD TABS. + + +Name: <The base DTB> +Info: Configures the base Raspberry Pi hardware +Load: <loaded automatically> +Params: + act_led_trigger Choose which activity the LED tracks. + Use "heartbeat" for a nice load indicator. + (default "mmc") + + act_led_activelow Set to "on" to invert the sense of the LED + (default "off") + N.B. For Pi 3B, 3B+, 3A+ and 4B, use the act-led + overlay. + + act_led_gpio Set which GPIO to use for the activity LED + (in case you want to connect it to an external + device) + (default "16" on a non-Plus board, "47" on a + Plus or Pi 2) + N.B. For Pi 3B, 3B+, 3A+ and 4B, use the act-led + overlay. + + ant1 Select antenna 1 (default). CM4/5 only. + + ant2 Select antenna 2. CM4/5 only. + + noant Disable both antennas. CM4/5 only. + + noanthogs Disable the GPIO hogs on the antenna controls + so they can be controlled at runtime. Note that + using this parameter without suitable OS + support will result in attenuated WiFi and + Bluetooth signals. CM4/5 only. + + audio Set to "on" to enable the onboard ALSA audio + interface (default "off") + + axiperf Set to "on" to enable the AXI bus performance + monitors. + See /sys/kernel/debug/raspberrypi_axi_monitor + for the results. + + bdaddr Set an alternative Bluetooth address (BDADDR). + The value should be a 6-byte hexadecimal value, + with or without colon separators, written least- + significant-byte first. For example, + bdaddr=06:05:04:03:02:01 + will set the BDADDR to 01:02:03:04:05:06. + + button_debounce Set the debounce delay (in ms) on the power/ + shutdown button (default 50ms) + + cam0_reg Controls CAM 0 regulator. + Disabled by default on CM1 & 3. + Enabled by default on all other boards. + + cam0_reg_gpio Set GPIO for CAM 0 regulator. + NB override switches to the normal GPIO driver, + even if the original was on the GPIO expander. + + cam1_reg Controls CAM 1 regulator. + Disabled by default on CM1 & 3. + Enabled by default on all other boards. + + cam1_reg_gpio Set GPIO for CAM 1 regulator. + NB override switches to the normal GPIO driver, + even if the original was on the GPIO expander. + + cam0_sync Enable a GPIO to reflect frame sync from CSI0, + going high on frame start, and low on frame end. + + cam0_sync_inverted Enable a GPIO to reflect frame sync from CSI0 + going low on frame start, and high on frame end. + + cam1_sync Enable a GPIO to reflect frame sync from CSI1, + going high on frame start, and low on frame end. + + cam1_sync_inverted Enable a GPIO to reflect frame sync from CSI1 + going low on frame start, and high on frame end. + + cooling_fan Enables the Pi 5 cooling fan (enabled + automatically by the firmware) + + drm_fb0_rp1_dpi Assign /dev/fb0 to the RP1 DPI output + + drm_fb0_rp1_dsi0 Assign /dev/fb0 to the RP1 DSI0 output + + drm_fb0_rp1_dsi1 Assign /dev/fb0 to the RP1 DSI1 output + + drm_fb0_vc4 Assign /dev/fb0 to the vc4 outputs + + drm_fb1_rp1_dpi Assign /dev/fb1 to the RP1 DPI output + + drm_fb1_rp1_dsi0 Assign /dev/fb1 to the RP1 DSI0 output + + drm_fb1_rp1_dsi1 Assign /dev/fb1 to the RP1 DSI1 output + + drm_fb1_vc4 Assign /dev/fb1 to the vc4 outputs + + drm_fb2_rp1_dpi Assign /dev/fb2 to the RP1 DPI output + + drm_fb2_rp1_dsi0 Assign /dev/fb2 to the RP1 DSI0 output + + drm_fb2_rp1_dsi1 Assign /dev/fb2 to the RP1 DSI1 output + + drm_fb2_vc4 Assign /dev/fb2 to the vc4 outputs + + eee Enable Energy Efficient Ethernet support for + compatible devices (default "on"). See also + "tx_lpi_timer". Pi3B+ only. + + eth_downshift_after Set the number of auto-negotiation failures + after which the 1000Mbps modes are disabled. + Legal values are 2, 3, 4, 5 and 0, where + 0 means never downshift (default 2). Pi3B+ only. + + eth_led0 Set mode of LED0 - amber on Pi3B+ (default "1"), + green on Pi4/5 (default "0"). + The legal values are: + + Pi3B+ + + 0=link/activity 1=link1000/activity + 2=link100/activity 3=link10/activity + 4=link100/1000/activity 5=link10/1000/activity + 6=link10/100/activity 14=off 15=on + + Pi4/5 + + 0=Speed/Activity 1=Speed + 2=Flash activity 3=FDX + 4=Off 5=On + 6=Alt 7=Speed/Flash + 8=Link 9=Activity + + eth_led1 Set mode of LED1 - green on Pi3B+ (default "6"), + amber on Pi4/5 (default "8"). See eth_led0 for + legal values. + + eth_max_speed Set the maximum speed a link is allowed + to negotiate. Legal values are 10, 100 and + 1000 (default 1000). Pi3B+ only. + + fan_temp0 Temperature threshold (in millicelcius) for + 1st cooling level (default 50000). Pi5 only. + fan_temp0_hyst Temperature hysteresis (in millicelcius) for + 1st cooling level (default 5000). Pi5 only. + fan_temp0_speed Fan PWM setting for 1st cooling level (0-255, + default 75). Pi5 only. + fan_temp1 Temperature threshold (in millicelcius) for + 2nd cooling level (default 60000). Pi5 only. + fan_temp1_hyst Temperature hysteresis (in millicelcius) for + 2nd cooling level (default 5000). Pi5 only. + fan_temp1_speed Fan PWM setting for 2nd cooling level (0-255, + default 125). Pi5 only. + fan_temp2 Temperature threshold (in millicelcius) for + 3rd cooling level (default 67500). Pi5 only. + fan_temp2_hyst Temperature hysteresis (in millicelcius) for + 3rd cooling level (default 5000). Pi5 only. + fan_temp2_speed Fan PWM setting for 3rd cooling level (0-255, + default 175). Pi5 only. + fan_temp3 Temperature threshold (in millicelcius) for + 4th cooling level (default 75000). Pi5 only. + fan_temp3_hyst Temperature hysteresis (in millicelcius) for + 4th cooling level (default 5000). Pi5 only. + fan_temp3_speed Fan PWM setting for 4th cooling level (0-255, + default 250). Pi5 only. + + hdmi Set to "off" to disable the HDMI interface + (default "on") + + i2c An alias for i2c_arm + + i2c_arm Set to "on" to enable the ARM's i2c interface + (default "off") + + i2c_arm_baudrate Set the baudrate of the ARM's i2c interface + (default "100000") + + i2c_baudrate An alias for i2c_arm_baudrate + + i2c_csi_dsi Set to "on" to enable the i2c_csi_dsi interface + The I2C bus and GPIOs are platform specific: + B rev 1: + i2c-1 on 2 & 3 + B rev 2, B+, CM, Zero, Zero W, 2B, CM2, CM3, + CM4S: + i2c-0 on 28 & 29 + 3B, 3B+, Zero 2W, 4B, 400, CM4: + i2c-0 on 44 & 45 + 5, 500: + i2c-11/i2c-4 on 40 & 41 + CM5 on CM5IO: + i2c-0 on 0 & 1 + CM5 on CM4IO: + i2c-10/i2c-6 on 38 & 39 + + i2c_csi_dsi0 Set to "on" to enable the i2c_csi_dsi0 interface + The I2C bus and GPIOs are platform specific: + B rev 1 & 2, B+, CM, Zero, Zero W, 2B, CM2, + CM3, CM4S, 3B, 3B+, Zero 2W, 4B, 400, CM4, + CM5 on CM4IO: + i2c-0 on 0 & 1 + 5, 500, CM5 on CM5IO: + i2c-10/i2c-6 on 38 & 39 + + i2c_csi_dsi1 A Pi 5 family-specific alias for i2c_csi_dsi. + + i2c_vc Set to "on" to enable the i2c interface + usually reserved for the VideoCore processor + (default "off") + + i2c_vc_baudrate Set the baudrate of the VideoCore i2c interface + (default "100000") + + i2s Set to "on" to enable the i2s interface + (default "off") + + i2s_dma4 Use to enable 40-bit DMA on the i2s interface + (the assigned value doesn't matter) + (2711 only) + + krnbt Set to "off" to disable autoprobing of Bluetooth + driver without need of hciattach/btattach + (default "on") + + krnbt_baudrate Set the baudrate of the PL011 UART when used + with krnbt=on + + nvme Alias for "pciex1" (2712 only) + + nvmem_cust_rw Allow read/write access to customer otp + + nvmem_mac_rw Allow read/write access to mac addresses otp + + nvmem_priv_rw Allow read/write access to customer private otp + + pcie Set to "off" to disable the PCIe interface + (default "on") + (2711 only, but not applicable on CM4S) + N.B. USB-A ports on 4B are subsequently disabled + + pcie_tperst_clk_ms Add N milliseconds between PCIe reference clock + activation and PERST# deassertion + (CM4 and 2712, default "0") + + pciex1 Set to "on" to enable the external PCIe link + (2712 only, default "off") + + pciex1_gen Sets the PCIe "GEN"/speed for the external PCIe + link (2712 only, default "2") + + pciex1_no_l0s Set to "on" to disable ASPM L0s on the external + PCIe link for devices that have broken + implementations (2712 only, default "off") + + pciex1_tperst_clk_ms Alias for pcie_tperst_clk_ms + (2712 only, default "0") + + pwr_led_trigger + pwr_led_activelow + pwr_led_gpio + As for act_led_*, but using the PWR LED. + Not available on Model A/B boards. + + random Set to "on" to enable the hardware random + number generator (default "on") + + rtc Set to "off" to disable the onboard Real Time + Clock (2712 only, default "on") + + rtc_bbat_vchg Set the RTC backup battery charging voltage in + microvolts. If set to 0 or not specified, the + trickle charger is disabled. + (2712 only, default "0") + + sd Set to "off" to disable the SD card (or eMMC on + non-lite SKU of CM4). + (default "on") + + sd_cqe Modify Command Queuing behaviour on the main SD + interface. Legal values are: + 0: disable CQ + 1: allow CQ for known-good SD A2 cards, and all + eMMC cards + 2: allow CQ for all SD A2 cards that aren't + known-bad, and all eMMC cards. + (2712 only, default "1") + + sd_overclock Clock (in MHz) to use when the MMC framework + requests 50MHz + + sd_poll_once Looks for a card once after booting. Useful + for network booting scenarios to avoid the + overhead of continuous polling. N.B. Using + this option restricts the system to using a + single card per boot (or none at all). + (default off) + + sd_force_pio Disable DMA support for SD driver (default off) + + sd_pio_limit Number of blocks above which to use DMA for + SD card (default 1) + + sd_debug Enable debug output from SD driver (default off) + + sdio_overclock Clock (in MHz) to use when the MMC framework + requests 50MHz for the SDIO/WLAN interface. + + spi Set to "on" to enable the spi interfaces + (default "off") + + spi_dma4 Use to enable 40-bit DMA on spi interfaces + (the assigned value doesn't matter) + (2711 only) + + strict_gpiod Return GPIOs to inputs when they are released. + If using the gpiod utilities, it is necessary + to keep a gpioset running (e.g. with + --mode=wait) in order for an output value to + persist. + + suspend Make the power button trigger a suspend rather + than a power-off (2712 only, default "off") + + tx_lpi_timer Set the delay in microseconds between going idle + and entering the low power state (default 600). + Requires EEE to be enabled - see "eee". + + uart0 Set to "off" to disable uart0 (default "on") + + uart0_console Move the kernel boot console to UART0 on pins + 6, 8 and 10 of the 40-way header (2712 only, + default "off") + + uart1 Set to "on" or "off" to enable or disable uart1 + (default varies) + + watchdog Set to "on" to enable the hardware watchdog + (default "off") + + wifiaddr Set an alternative WiFi MAC address. + The value should be a 6-byte hexadecimal value, + with or without colon separators, written in the + natural (big-endian) order. + + N.B. It is recommended to only enable those interfaces that are needed. + Leaving all interfaces enabled can lead to unwanted behaviour (i2c_vc + interfering with Pi Camera, I2S and SPI hogging GPIO pins, etc.) + Note also that i2c, i2c_arm and i2c_vc are aliases for the physical + interfaces i2c0 and i2c1. Use of the numeric variants is still possible + but deprecated because the ARM/VC assignments differ between board + revisions. The same board-specific mapping applies to i2c_baudrate, + and the other i2c baudrate parameters. + + +Name: act-led +Info: Pi 3B, 3B+, 3A+ and 4B use a GPIO expander to drive the LEDs which can + only be accessed from the VPU. There is a special driver for this with a + separate DT node, which has the unfortunate consequence of breaking the + act_led_gpio and act_led_activelow dtparams. + This overlay changes the GPIO controller back to the standard one and + restores the dtparams. +Load: dtoverlay=act-led,<param>=<val> +Params: activelow Set to "on" to invert the sense of the LED + (default "off") + + gpio Set which GPIO to use for the activity LED + (in case you want to connect it to an external + device) + REQUIRED + + +Name: adafruit-st7735r +Info: Overlay for the SPI-connected Adafruit 1.8" 160x128 or 128x128 displays, + based on the ST7735R chip. + This overlay uses the newer DRM/KMS "Tiny" driver. +Load: dtoverlay=adafruit-st7735r,<param>=<val> +Params: 128x128 Select the 128x128 driver (default 160x128) + rotate Display rotation {0,90,180,270} (default 90) + speed SPI bus speed in Hz (default 4000000) + dc_pin GPIO pin for D/C (default 24) + reset_pin GPIO pin for RESET (default 25) + led_pin GPIO used to control backlight (default 18) + + +Name: adafruit18 +Info: Overlay for the SPI-connected Adafruit 1.8" display (based on the + ST7735R chip). It includes support for the "green tab" version. + This overlay uses the older fbtft driver. +Load: dtoverlay=adafruit18,<param>=<val> +Params: green Use the adafruit18_green variant. + rotate Display rotation {0,90,180,270} + speed SPI bus speed in Hz (default 4000000) + fps Display frame rate in Hz + bgr Enable BGR mode (default off) + debug Debug output level {0-7} + dc_pin GPIO pin for D/C (default 24) + reset_pin GPIO pin for RESET (default 25) + led_pin GPIO used to control backlight (default 18) + + +Name: adau1977-adc +Info: Overlay for activation of ADAU1977 ADC codec over I2C for control + and I2S for data. +Load: dtoverlay=adau1977-adc +Params: <None> + + +Name: adau7002-simple +Info: Overlay for the activation of ADAU7002 stereo PDM to I2S converter. +Load: dtoverlay=adau7002-simple,<param>=<val> +Params: card-name Override the default, "adau7002", card name. + + +Name: ads1015 +Info: Overlay for activation of Texas Instruments ADS1015 ADC over I2C +Load: dtoverlay=ads1015,<param>=<val> +Params: addr I2C bus address of device. Set based on how the + addr pin is wired. (default=0x48 assumes addr + is pulled to GND) + cha_enable Enable virtual channel a. (default=true) + cha_cfg Set the configuration for virtual channel a. + (default=4 configures this channel for the + voltage at A0 with respect to GND) + cha_datarate Set the datarate (samples/sec) for this channel. + (default=4 sets 1600 sps) + cha_gain Set the gain of the Programmable Gain + Amplifier for this channel. (default=2 sets the + full scale of the channel to 2.048 Volts) + + Channel (ch) parameters can be set for each enabled channel. + A maximum of 4 channels can be enabled (letters a thru d). + For more information refer to the device datasheet at: + http://www.ti.com/lit/ds/symlink/ads1015.pdf + + +Name: ads1115 +Info: Texas Instruments ADS1115 ADC +Load: dtoverlay=ads1115,<param>[=<val>] +Params: addr I2C bus address of device. Set based on how the + addr pin is wired. (default=0x48 assumes addr + is pulled to GND) + i2c-bus Supports all the standard I2C bus selection + parameters - see "dtoverlay -h i2c-bus" + cha_enable Enable virtual channel a. + cha_cfg Set the configuration for virtual channel a. + (default=4 configures this channel for the + voltage at A0 with respect to GND) + cha_datarate Set the datarate (samples/sec) for this channel. + (default=7 sets 860 sps) + cha_gain Set the gain of the Programmable Gain + Amplifier for this channel. (Default 1 sets the + full scale of the channel to 4.096 Volts) + + Channel parameters can be set for each enabled channel. + A maximum of 4 channels can be enabled (letters a thru d). + For more information refer to the device datasheet at: + http://www.ti.com/lit/ds/symlink/ads1115.pdf + + +Name: ads7846 +Info: ADS7846 Touch controller +Load: dtoverlay=ads7846,<param>=<val> +Params: cs SPI bus Chip Select (default 1) + speed SPI bus speed (default 2MHz, max 3.25MHz) + penirq GPIO used for PENIRQ. REQUIRED + penirq_pull Set GPIO pull (default 0=none, 2=pullup) + swapxy Swap x and y axis + xmin Minimum value on the X axis (default 0) + ymin Minimum value on the Y axis (default 0) + xmax Maximum value on the X axis (default 4095) + ymax Maximum value on the Y axis (default 4095) + pmin Minimum reported pressure value (default 0) + pmax Maximum reported pressure value (default 65535) + xohms Touchpanel sensitivity (X-plate resistance) + (default 400) + + penirq is required and usually xohms (60-100) has to be set as well. + Apart from that, pmax (255) and swapxy are also common. + The rest of the calibration can be done with xinput-calibrator. + See: github.com/notro/fbtft/wiki/FBTFT-on-Raspian + Device Tree binding document: + www.kernel.org/doc/Documentation/devicetree/bindings/input/ads7846.txt + + +Name: adv7282m +Info: Analog Devices ADV7282M analogue video to CSI2 bridge. + Uses Unicam1, which is the standard camera connector on most Pi + variants. +Load: dtoverlay=adv7282m,<param>=<val> +Params: addr Overrides the I2C address (default 0x21) + media-controller Configure use of Media Controller API for + configuring the sensor (default off) + + +Name: adv728x-m +Info: Analog Devices ADV728[0|1|2]-M analogue video to CSI2 bridges. + This is a wrapper for adv7282m, and defaults to ADV7282M. +Load: dtoverlay=adv728x-m,<param>=<val> +Params: addr Overrides the I2C address (default 0x21) + adv7280m Select ADV7280-M. + adv7281m Select ADV7281-M. + adv7281ma Select ADV7281-MA. + media-controller Configure use of Media Controller API for + configuring the sensor (default off) + + +Name: akkordion-iqdacplus +Info: Configures the Digital Dreamtime Akkordion Music Player (based on the + OEM IQAudIO DAC+ or DAC Zero module). +Load: dtoverlay=akkordion-iqdacplus,<param>=<val> +Params: 24db_digital_gain Allow gain to be applied via the PCM512x codec + Digital volume control. Enable with + dtoverlay=akkordion-iqdacplus,24db_digital_gain + (The default behaviour is that the Digital + volume control is limited to a maximum of + 0dB. ie. it can attenuate but not provide + gain. For most users, this will be desired + as it will prevent clipping. By appending + the 24db_digital_gain parameter, the Digital + volume control will allow up to 24dB of + gain. If this parameter is enabled, it is the + responsibility of the user to ensure that + the Digital volume control is set to a value + that does not result in clipping/distortion!) + + +Name: allo-boss-dac-pcm512x-audio +Info: Configures the Allo Boss DAC audio cards. +Load: dtoverlay=allo-boss-dac-pcm512x-audio,<param> +Params: 24db_digital_gain Allow gain to be applied via the PCM512x codec + Digital volume control. Enable with + "dtoverlay=allo-boss-dac-pcm512x-audio, + 24db_digital_gain" + (The default behaviour is that the Digital + volume control is limited to a maximum of + 0dB. ie. it can attenuate but not provide + gain. For most users, this will be desired + as it will prevent clipping. By appending + the 24db_digital_gain parameter, the Digital + volume control will allow up to 24dB of + gain. If this parameter is enabled, it is the + responsibility of the user to ensure that + the Digital volume control is set to a value + that does not result in clipping/distortion!) + slave Force Boss DAC into slave mode, using Pi a + master for bit clock and frame clock. Enable + with "dtoverlay=allo-boss-dac-pcm512x-audio, + slave" + + +Name: allo-boss2-dac-audio +Info: Configures the Allo Boss2 DAC audio card +Load: dtoverlay=allo-boss2-dac-audio +Params: <None> + + +Name: allo-digione +Info: Configures the Allo Digione audio card +Load: dtoverlay=allo-digione +Params: <None> + + +Name: allo-katana-dac-audio +Info: Configures the Allo Katana DAC audio card +Load: dtoverlay=allo-katana-dac-audio +Params: <None> + + +Name: allo-piano-dac-pcm512x-audio +Info: Configures the Allo Piano DAC (2.0/2.1) audio cards. + (NB. This initial support is for 2.0 channel audio ONLY! ie. stereo. + The subwoofer outputs on the Piano 2.1 are not currently supported!) +Load: dtoverlay=allo-piano-dac-pcm512x-audio,<param> +Params: 24db_digital_gain Allow gain to be applied via the PCM512x codec + Digital volume control. + (The default behaviour is that the Digital + volume control is limited to a maximum of + 0dB. ie. it can attenuate but not provide + gain. For most users, this will be desired + as it will prevent clipping. By appending + the 24db_digital_gain parameter, the Digital + volume control will allow up to 24dB of + gain. If this parameter is enabled, it is the + responsibility of the user to ensure that + the Digital volume control is set to a value + that does not result in clipping/distortion!) + + +Name: allo-piano-dac-plus-pcm512x-audio +Info: Configures the Allo Piano DAC (2.1) audio cards. +Load: dtoverlay=allo-piano-dac-plus-pcm512x-audio,<param> +Params: 24db_digital_gain Allow gain to be applied via the PCM512x codec + Digital volume control. + (The default behaviour is that the Digital + volume control is limited to a maximum of + 0dB. ie. it can attenuate but not provide + gain. For most users, this will be desired + as it will prevent clipping. By appending + the 24db_digital_gain parameter, the Digital + volume control will allow up to 24dB of + gain. If this parameter is enabled, it is the + responsibility of the user to ensure that + the Digital volume control is set to a value + that does not result in clipping/distortion!) + glb_mclk This option is only with Kali board. If enabled, + MCLK for Kali is used and PLL is disabled for + better voice quality. (default Off) + + +Name: anyspi +Info: Universal device tree overlay for SPI devices + + Just specify the SPI address and device name ("compatible" property). + This overlay lacks any device-specific parameter support! + + For devices on spi1 or spi2, the interfaces should be enabled + with one of the spi1-1/2/3cs and/or spi2-1/2/3cs overlays. + + Examples: + 1. SPI NOR flash on spi0.1, maximum SPI clock frequency 45MHz: + dtoverlay=anyspi:spi0-1,dev="jedec,spi-nor",speed=45000000 + 2. MCP3204 ADC on spi1.2, maximum SPI clock frequency 500kHz: + dtoverlay=anyspi:spi1-2,dev="microchip,mcp3204" +Load: dtoverlay=anyspi,<param>=<val> +Params: spi<n>-<m> Configure device at spi<n>, cs<m> + (boolean, required) + dev Set device name to search compatible module + (string, required) + speed Set SPI clock frequency in Hz + (integer, optional, default 500000) + + +Name: apds9960 +Info: Configures the AVAGO APDS9960 digital proximity, ambient light, RGB and + gesture sensor +Load: dtoverlay=apds9960,<param>=<val> +Params: gpiopin GPIO used for INT (default 4) + noints Disable the interrupt GPIO line. + + +Name: applepi-dac +Info: Configures the Orchard Audio ApplePi-DAC audio card +Load: dtoverlay=applepi-dac +Params: <None> + + +Name: arducam-64mp +Info: Arducam 64MP camera module. + Uses Unicam 1, which is the standard camera connector on most Pi + variants. +Load: dtoverlay=arducam-64mp,<param>=<val> +Params: rotation Mounting rotation of the camera sensor (0 or + 180, default 0) + orientation Sensor orientation (0 = front, 1 = rear, + 2 = external, default external) + media-controller Configure use of Media Controller API for + configuring the sensor (default on) + cam0 Adopt the default configuration for CAM0 on a + Compute Module (CSI0, i2c_vc, and cam0_reg). + vcm Select lens driver state. Default is enabled, + but vcm=off will disable. + + +Name: arducam-pivariety +Info: Arducam Pivariety camera module. + Uses Unicam 1, which is the standard camera connector on most Pi + variants. +Load: dtoverlay=arducam-pivariety,<param>=<val> +Params: rotation Mounting rotation of the camera sensor (0 or + 180, default 0) + orientation Sensor orientation (0 = front, 1 = rear, + 2 = external, default external) + media-controller Configure use of Media Controller API for + configuring the sensor (default on) + cam0 Adopt the default configuration for CAM0 on a + Compute Module (CSI0, i2c_vc, and cam0_reg). + + +Name: at86rf233 +Info: Configures the Atmel AT86RF233 802.15.4 low-power WPAN transceiver, + connected to spi0.0 +Load: dtoverlay=at86rf233,<param>=<val> +Params: interrupt GPIO used for INT (default 23) + reset GPIO used for Reset (default 24) + sleep GPIO used for Sleep (default 25) + speed SPI bus speed in Hz (default 3000000) + trim Fine tuning of the internal capacitance + arrays (0=+0pF, 15=+4.5pF, default 15) + + +Name: audioinjector-addons +Info: Configures the audioinjector.net audio add on soundcards +Load: dtoverlay=audioinjector-addons,<param>=<val> +Params: non-stop-clocks Keeps the clocks running even when the stream + is paused or stopped (default off) + + +Name: audioinjector-bare-i2s +Info: Configures the audioinjector.net audio bare i2s soundcard +Load: dtoverlay=audioinjector-bare-i2s +Params: <None> + + +Name: audioinjector-isolated-soundcard +Info: Configures the audioinjector.net isolated soundcard +Load: dtoverlay=audioinjector-isolated-soundcard +Params: <None> + + +Name: audioinjector-ultra +Info: Configures the audioinjector.net ultra soundcard +Load: dtoverlay=audioinjector-ultra +Params: <None> + + +Name: audioinjector-wm8731-audio +Info: Configures the audioinjector.net audio add on soundcard +Load: dtoverlay=audioinjector-wm8731-audio +Params: <None> + + +Name: audiosense-pi +Info: Configures the audiosense-pi add on soundcard + For more information refer to + https://gitlab.com/kakar0t/audiosense-pi +Load: dtoverlay=audiosense-pi +Params: <None> + + +Name: audremap +Info: Switches PWM sound output to GPIOs on the 40-pin header +Load: dtoverlay=audremap,<param>=<val> +Params: swap_lr Reverse the channel allocation, which will also + swap the audio jack outputs (default off) + enable_jack Don't switch off the audio jack output. Does + nothing on BCM2711 (default off) + pins_12_13 Select GPIOs 12 & 13 (default) + pins_18_19 Select GPIOs 18 & 19 + pins_40_41 Select GPIOs 40 & 41 (not available on CM4, used + for other purposes) + pins_40_45 Select GPIOs 40 & 45 (don't use on BCM2711 - the + pins are on different controllers) + + +Name: audremap-pi5 +Info: On Raspberry Pi 5 / 500 /CM5, enable digital audio output + and route it to GPIOs 12 & 13 on the 40-pin header +Load: dtoverlay=audremap-pi5,<param>=<val> +Params: swap_lr Reverse the channel allocation (default off) + pins_12_13 Select GPIOs 12 & 13 (default) + pins_18_19 Not available; this will not enable audio out + pins_40_41 Not available; this will not enable audio out + pins_40_45 Not available; this will not enable audio out + + +Name: balena-fin +Info: Overlay that enables WLAN, Bluetooth and the GPIO expander on the + balenaFin carrier board for the Raspberry Pi Compute Module 3/3+ Lite. +Load: dtoverlay=balena-fin +Params: <None> + + +Name: bcm2712d0 +Info: Overlay encapsulating the BCM2712 C0->D0 differences +Load: dtoverlay=bcm2712d0 +Params: <None> + + +Name: bmp085_i2c-sensor +Info: This overlay is now deprecated - see i2c-sensor +Load: <Deprecated> + + +Name: camera-mux-2port +Info: Configures a 2 port camera multiplexer + Note that currently ALL IMX290 modules share a common clock, therefore + all modules will need to have the same clock frequency. +Load: dtoverlay=camera-mux-2port,<param>=<val> +Params: cam0-arducam-64mp Select Arducam64MP for camera on port 0 + cam0-imx219 Select IMX219 for camera on port 0 + cam0-imx258 Select IMX258 for camera on port 0 + cam0-imx290 Select IMX290 for camera on port 0 + cam0-imx477 Select IMX477 for camera on port 0 + cam0-imx519 Select IMX519 for camera on port 0 + cam0-imx708 Select IMX708 for camera on port 0 + cam0-ov2311 Select OV2311 for camera on port 0 + cam0-ov5647 Select OV5647 for camera on port 0 + cam0-ov64a40 Select OV64A40 for camera on port 0 + cam0-ov7251 Select OV7251 for camera on port 0 + cam0-ov9281 Select OV9281 for camera on port 0 + cam0-imx290-clk-freq Set clock frequency for an IMX290 on port 0 + cam1-arducam-64mp Select Arducam64MP for camera on port 1 + cam1-imx219 Select IMX219 for camera on port 1 + cam1-imx258 Select IMX258 for camera on port 1 + cam1-imx290 Select IMX290 for camera on port 1 + cam1-imx477 Select IMX477 for camera on port 1 + cam1-imx519 Select IMX519 for camera on port 1 + cam1-imx708 Select IMX708 for camera on port 1 + cam1-ov2311 Select OV2311 for camera on port 1 + cam1-ov5647 Select OV5647 for camera on port 1 + cam1-ov64a40 Select OV64A40 for camera on port 1 + cam1-ov7251 Select OV7251 for camera on port 1 + cam1-ov9281 Select OV9281 for camera on port 1 + cam1-imx290-clk-freq Set clock frequency for an IMX290 on port 1 + cam0-sync-source Set camera on port 0 as vsync source + cam0-sync-sink Set camera on port 0 as vsync sink + cam1-sync-source Set camera on port 1 as vsync source + cam1-sync-sink Set camera on port 1 as vsync sink + + cam0 Connect the mux to CAM0 port (default is CAM1) + + +Name: camera-mux-4port +Info: Configures a 4 port camera multiplexer + Note that currently ALL IMX290 modules share a common clock, therefore + all modules will need to have the same clock frequency. +Load: dtoverlay=camera-mux-4port,<param>=<val> +Params: cam0-arducam-64mp Select Arducam64MP for camera on port 0 + cam0-imx219 Select IMX219 for camera on port 0 + cam0-imx258 Select IMX258 for camera on port 0 + cam0-imx290 Select IMX290 for camera on port 0 + cam0-imx477 Select IMX477 for camera on port 0 + cam0-imx519 Select IMX519 for camera on port 0 + cam0-imx708 Select IMX708 for camera on port 0 + cam0-ov2311 Select OV2311 for camera on port 0 + cam0-ov5647 Select OV5647 for camera on port 0 + cam0-ov64a40 Select OV64A40 for camera on port 0 + cam0-ov7251 Select OV7251 for camera on port 0 + cam0-ov9281 Select OV9281 for camera on port 0 + cam0-imx290-clk-freq Set clock frequency for an IMX290 on port 0 + cam1-arducam-64mp Select Arducam64MP for camera on port 1 + cam1-imx219 Select IMX219 for camera on port 1 + cam1-imx258 Select IMX258 for camera on port 1 + cam1-imx290 Select IMX290 for camera on port 1 + cam1-imx477 Select IMX477 for camera on port 1 + cam1-imx519 Select IMX519 for camera on port 1 + cam1-imx708 Select IMX708 for camera on port 1 + cam1-ov2311 Select OV2311 for camera on port 1 + cam1-ov5647 Select OV5647 for camera on port 1 + cam1-ov64a40 Select OV64A40 for camera on port 1 + cam1-ov7251 Select OV7251 for camera on port 1 + cam1-ov9281 Select OV9281 for camera on port 1 + cam1-imx290-clk-freq Set clock frequency for an IMX290 on port 1 + cam2-arducam-64mp Select Arducam64MP for camera on port 2 + cam2-imx219 Select IMX219 for camera on port 2 + cam2-imx258 Select IMX258 for camera on port 2 + cam2-imx290 Select IMX290 for camera on port 2 + cam2-imx477 Select IMX477 for camera on port 2 + cam2-imx519 Select IMX519 for camera on port 2 + cam2-imx708 Select IMX708 for camera on port 2 + cam2-ov2311 Select OV2311 for camera on port 2 + cam2-ov5647 Select OV5647 for camera on port 2 + cam2-ov64a40 Select OV64A40 for camera on port 2 + cam2-ov7251 Select OV7251 for camera on port 2 + cam2-ov9281 Select OV9281 for camera on port 2 + cam2-imx290-clk-freq Set clock frequency for an IMX290 on port 2 + cam3-arducam-64mp Select Arducam64MP for camera on port 3 + cam3-imx219 Select IMX219 for camera on port 3 + cam3-imx258 Select IMX258 for camera on port 3 + cam3-imx290 Select IMX290 for camera on port 3 + cam3-imx477 Select IMX477 for camera on port 3 + cam3-imx519 Select IMX519 for camera on port 3 + cam3-imx708 Select IMX708 for camera on port 3 + cam3-ov2311 Select OV2311 for camera on port 3 + cam3-ov5647 Select OV5647 for camera on port 3 + cam3-ov64a40 Select OV64A40 for camera on port 3 + cam3-ov7251 Select OV7251 for camera on port 3 + cam3-ov9281 Select OV9281 for camera on port 3 + cam3-imx290-clk-freq Set clock frequency for an IMX290 on port 3 + cam0-sync-source Set camera on port 0 as vsync source + cam0-sync-sink Set camera on port 0 as vsync sink + cam1-sync-source Set camera on port 1 as vsync source + cam1-sync-sink Set camera on port 1 as vsync sink + cam2-sync-source Set camera on port 2 as vsync source + cam2-sync-sink Set camera on port 2 as vsync sink + cam3-sync-source Set camera on port 3 as vsync source + cam3-sync-sink Set camera on port 3 as vsync sink + + cam0 Connect the mux to CAM0 port (default is CAM1) + + +Name: cap1106 +Info: Enables the ability to use the cap1106 touch sensor as a keyboard +Load: dtoverlay=cap1106,<param>=<val> +Params: int_pin GPIO pin for interrupt signal (default 23) + + +Name: chipcap2 +Info: Enables the Chipcap 2 humidity and temperature sensor. This device + provides low and high humidity alarms and requires a 3V3 dedicated + regulator to operate. +Load: dtoverlay=chipcap2,<param>=<val> +Params: ready_pin GPIO pin for ready signal (default 4) + + low_pin GPIO pin for low humidity signal (default 5) + + high_pin GPIO pin for high humidity signal (default 6) + + reg_pin GPIO pin to control the dedicated regulator + that powers the device (default 26) + + +Name: chipdip-dac +Info: Configures Chip Dip audio cards. +Load: dtoverlay=chipdip-dac +Params: <None> + + +Name: cirrus-wm5102 +Info: Configures the Cirrus Logic Audio Card +Load: dtoverlay=cirrus-wm5102 +Params: <None> + + +Name: cm-swap-i2c0 +Info: Largely for Compute Modules 1&3 where the original instructions for + adding a camera used GPIOs 0&1 for CAM1 and 28&29 for CAM0, whilst all + other platforms use 28&29 (or 44&45) for CAM1. + The default assignment through using this overlay is for + i2c0 to use 28&29, and i2c10 (aka i2c_csi_dsi) to use 28&29, but the + overrides allow this to be changed. +Load: dtoverlay=cm-swap-i2c0,<param>=<val> +Params: i2c0-gpio0 Use GPIOs 0&1 for i2c0 + i2c0-gpio28 Use GPIOs 28&29 for i2c0 (default) + i2c0-gpio44 Use GPIOs 44&45 for i2c0 + i2c10-gpio0 Use GPIOs 0&1 for i2c0 (default) + i2c10-gpio28 Use GPIOs 28&29 for i2c0 + i2c10-gpio44 Use GPIOs 44&45 for i2c0 + + +Name: cma +Info: Set custom CMA sizes, only use if you know what you are doing, might + clash with other overlays like vc4-fkms-v3d and vc4-kms-v3d. +Load: dtoverlay=cma,<param>=<val> +Params: cma-512 CMA is 512MB (needs 1GB) + cma-448 CMA is 448MB (needs 1GB) + cma-384 CMA is 384MB (needs 1GB) + cma-320 CMA is 320MB (needs 1GB) + cma-256 CMA is 256MB (needs 1GB) + cma-192 CMA is 192MB (needs 1GB) + cma-128 CMA is 128MB + cma-96 CMA is 96MB + cma-64 CMA is 64MB + cma-size CMA size in bytes, 4MB aligned + cma-default Use upstream's default value + + +Name: crystalfontz-cfa050_pi_m +Info: Configures the Crystalfontz CFA050-PI-M series of Raspberry Pi CM4 + based modules using the CFA7201280A0_050Tx 7" TFT LCD displays, + with or without capacitive touch screen. + Requires use of vc4-kms-v3d. +Load: dtoverlay=crystalfontz-cfa050_pi_m,<param>=<val> +Params: captouch Enable capacitive touch display + + +Name: cutiepi-panel +Info: 8" TFT LCD display and touch panel used by cutiepi.io +Load: dtoverlay=cutiepi-panel +Params: <None> + + +Name: dacberry400 +Info: Configures the dacberry400 add on soundcard +Load: dtoverlay=dacberry400 +Params: <None> + + +Name: dht11 +Info: Overlay for the DHT11/DHT21/DHT22 humidity/temperature sensors + Also sometimes found with the part number(s) AM230x. +Load: dtoverlay=dht11,<param>=<val> +Params: gpiopin GPIO connected to the sensor's DATA output. + (default 4) + + +Name: dionaudio-kiwi +Info: Configures the Dion Audio KIWI STREAMER +Load: dtoverlay=dionaudio-kiwi +Params: <None> + + +Name: dionaudio-loco +Info: Configures the Dion Audio LOCO DAC-AMP +Load: dtoverlay=dionaudio-loco +Params: <None> + + +Name: dionaudio-loco-v2 +Info: Configures the Dion Audio LOCO-V2 DAC-AMP +Load: dtoverlay=dionaudio-loco-v2,<param>=<val> +Params: 24db_digital_gain Allow gain to be applied via the PCM512x codec + Digital volume control. Enable with + "dtoverlay=hifiberry-dacplus,24db_digital_gain" + (The default behaviour is that the Digital + volume control is limited to a maximum of + 0dB. ie. it can attenuate but not provide + gain. For most users, this will be desired + as it will prevent clipping. By appending + the 24dB_digital_gain parameter, the Digital + volume control will allow up to 24dB of + gain. If this parameter is enabled, it is the + responsibility of the user to ensure that + the Digital volume control is set to a value + that does not result in clipping/distortion!) + + +Name: disable-bt +Info: Disable onboard Bluetooth on Bluetooth-capable Raspberry Pis. On Pis + prior to Pi 5 this restores UART0/ttyAMA0 over GPIOs 14 & 15. +Load: dtoverlay=disable-bt +Params: <None> + + +Name: disable-bt-pi5 +Info: See disable-bt + + +Name: disable-emmc2 +Info: Disable EMMC2 controller on BCM2711. + The allows the onboard EMMC storage on Compute Module 4 to be disabled + e.g. if a fault has occurred. +Load: dtoverlay=disable-emmc2 +Params: <None> + + +Name: disable-wifi +Info: Disable onboard WLAN on WiFi-capable Raspberry Pis. +Load: dtoverlay=disable-wifi +Params: <None> + + +Name: disable-wifi-pi5 +Info: See disable-wifi + + +Name: dpi18 +Info: Overlay for a generic 18-bit DPI display + This uses GPIOs 0-21 (so no I2C, uart etc.), and activates the output + 2-3 seconds after the kernel has started. +Load: dtoverlay=dpi18 +Params: <None> + + +Name: dpi18cpadhi +Info: Overlay for a generic 18-bit DPI display (in 'mode 6' connection scheme) + This uses GPIOs 0-9,12-17,20-25 (so no I2C, uart etc.), and activates + the output 3-3 seconds after the kernel has started. +Load: dtoverlay=dpi18cpadhi +Params: <None> + + +Name: dpi24 +Info: Overlay for a generic 24-bit DPI display + This uses GPIOs 0-27 (so no I2C, uart etc.), and activates the output + 2-3 seconds after the kernel has started. +Load: dtoverlay=dpi24 +Params: <None> + + +Name: draws +Info: Configures the NW Digital Radio DRAWS Hat + + The board includes an ADC to measure various board values and also + provides two analog user inputs on the expansion header. The ADC + can be configured for various sample rates and gain values to adjust + the input range. Tables describing the two parameters follow. + + ADC Gain Values: + 0 = +/- 6.144V + 1 = +/- 4.096V + 2 = +/- 2.048V + 3 = +/- 1.024V + 4 = +/- 0.512V + 5 = +/- 0.256V + 6 = +/- 0.256V + 7 = +/- 0.256V + + ADC Datarate Values: + 0 = 128sps + 1 = 250sps + 2 = 490sps + 3 = 920sps + 4 = 1600sps (default) + 5 = 2400sps + 6 = 3300sps + 7 = 3300sps +Load: dtoverlay=draws,<param>=<val> +Params: draws_adc_ch4_gain Sets the full scale resolution of the ADCs + input voltage sensor (default 1) + + draws_adc_ch4_datarate Sets the datarate of the ADCs input voltage + sensor + + draws_adc_ch5_gain Sets the full scale resolution of the ADCs + 5V rail voltage sensor (default 1) + + draws_adc_ch5_datarate Sets the datarate of the ADCs 4V rail voltage + sensor + + draws_adc_ch6_gain Sets the full scale resolution of the ADCs + AIN2 input (default 2) + + draws_adc_ch6_datarate Sets the datarate of the ADCs AIN2 input + + draws_adc_ch7_gain Sets the full scale resolution of the ADCs + AIN3 input (default 2) + + draws_adc_ch7_datarate Sets the datarate of the ADCs AIN3 input + + alsaname Name of the ALSA audio device (default "draws") + + +Name: dwc-otg +Info: Selects the dwc_otg USB controller driver which has fiq support. This + is the default on all except the Pi Zero which defaults to dwc2. +Load: dtoverlay=dwc-otg +Params: <None> + + +Name: dwc2 +Info: Selects the dwc2 USB controller driver +Load: dtoverlay=dwc2,<param>=<val> +Params: dr_mode Dual role mode: "host", "peripheral" or "otg" + + g-rx-fifo-size Size of rx fifo size in gadget mode + + g-np-tx-fifo-size Size of non-periodic tx fifo size in gadget + mode + + +[ The ds1307-rtc overlay has been deleted. See i2c-rtc. ] + + +Name: edt-ft5406 +Info: Overlay for the EDT FT5406 touchscreen. + This works with the Raspberry Pi 7" touchscreen when not being polled + by the firmware. + By default the overlay uses the i2c_csi_dsi I2C interface, but this + can be overridden + You MUST use either "disable_touchscreen=1" or "ignore_lcd=1" in + config.txt to stop the firmware polling the touchscreen. +Load: dtoverlay=edt-ft5406,<param>=<val> +Params: sizex Touchscreen size x (default 800) + sizey Touchscreen size y (default 480) + invx Touchscreen inverted x axis + invy Touchscreen inverted y axis + swapxy Touchscreen swapped x y axis + addr Sets the address for the touch controller. Note + that the device must be configured to use the + specified address. + i2c-bus Supports all the standard I2C bus selection + parameters - see "dtoverlay -h i2c-bus" + + +Name: enc28j60 +Info: Overlay for the Microchip ENC28J60 Ethernet Controller on SPI0 +Load: dtoverlay=enc28j60,<param>=<val> +Params: int_pin GPIO used for INT (default 25) + + speed SPI bus speed (default 12000000) + + +Name: enc28j60-spi2 +Info: Overlay for the Microchip ENC28J60 Ethernet Controller on SPI2 +Load: dtoverlay=enc28j60-spi2,<param>=<val> +Params: int_pin GPIO used for INT (default 39) + + speed SPI bus speed (default 12000000) + + +Name: exc3000 +Info: Enables I2C connected EETI EXC3000 multiple touch controller using + GPIO 4 (pin 7 on GPIO header) for interrupt. +Load: dtoverlay=exc3000,<param>=<val> +Params: interrupt GPIO used for interrupt (default 4) + sizex Touchscreen size x (default 4096) + sizey Touchscreen size y (default 4096) + invx Touchscreen inverted x axis + invy Touchscreen inverted y axis + swapxy Touchscreen swapped x y axis + + +Name: ezsound-6x8iso +Info: Overlay for the ezsound 6x8 isolated soundcard. +Load: dtoverlay=ezsound-6x8iso +Params: <None> + + +Name: fbtft +Info: Overlay for SPI-connected displays using the fbtft drivers. + + This overlay seeks to replace the functionality provided by fbtft_device + which is now gone from the kernel. + + Most displays from fbtft_device have been ported over. + Example: + dtoverlay=fbtft,spi0-0,rpi-display,reset_pin=23,dc_pin=24,led_pin=18,rotate=270 + + It is also possible to specify the controller (this will use the default + init sequence in the driver). + Example: + dtoverlay=fbtft,spi0-0,ili9341,bgr,reset_pin=23,dc_pin=24,led_pin=18,rotate=270 + + For devices on spi1 or spi2, the interfaces should be enabled + with one of the spi1-1/2/3cs and/or spi2-1/2/3cs overlays. + + The following features of fbtft_device have not been ported over: + - parallel bus is not supported + - the init property which overrides the controller initialization + sequence is not supported as a parameter due to memory limitations in + the bootloader responsible for applying the overlay. + + See https://github.com/notro/fbtft/wiki/FBTFT-RPI-overlays for how to + create an overlay. + +Load: dtoverlay=fbtft,<param>=<val> +Params: + spi<n>-<m> Configure device at spi<n>, cs<m> + (boolean, required) + speed SPI bus speed in Hz (default 32000000) + cpha Shifted clock phase (CPHA) mode + cpol Inverse clock polarity (CPOL) mode + + adafruit18 Adafruit 1.8 + adafruit22 Adafruit 2.2 (old) + adafruit22a Adafruit 2.2 + adafruit28 Adafruit 2.8 + adafruit13m Adafruit 1.3 OLED + admatec_c-berry28 C-Berry28 + dogs102 EA DOGS102 + er_tftm050_2 ER-TFTM070-2 + er_tftm070_5 ER-TFTM070-5 + ew24ha0 EW24HA0 + ew24ha0_9bit EW24HA0 in 9-bit mode + freetronicsoled128 Freetronics OLED128 + hy28a HY28A + hy28b HY28B + itdb28_spi ITDB02-2.8 with SPI interface circuit + mi0283qt-2 Watterott MI0283QT-2 + mi0283qt-9a Watterott MI0283QT-9A + nokia3310 Nokia 3310 + nokia3310a Nokia 3310a + nokia5110 Nokia 5110 + piscreen PiScreen + pitft Adafruit PiTFT 2.8 + pioled ILSoft OLED + rpi-display Watterott rpi-display + sainsmart18 Sainsmart 1.8 + sainsmart32_spi Sainsmart 3.2 with SPI interfce circuit + tinylcd35 TinyLCD 3.5 + tm022hdh26 Tianma TM022HDH26 + tontec35_9481 Tontect 3.5 with ILI9481 controller + tontec35_9486 Tontect 3.5 with ILI9486 controller + waveshare32b Waveshare 3.2 + waveshare22 Waveshare 2.2 + + bd663474 BD663474 display controller + hx8340bn HX8340BN display controller + hx8347d HX8347D display controller + hx8353d HX8353D display controller + hx8357d HX8357D display controller + ili9163 ILI9163 display controller + ili9320 ILI9320 display controller + ili9325 ILI9325 display controller + ili9340 ILI9340 display controller + ili9341 ILI9341 display controller + ili9481 ILI9481 display controller + ili9486 ILI9486 display controller + pcd8544 PCD8544 display controller + ra8875 RA8875 display controller + s6d02a1 S6D02A1 display controller + s6d1121 S6D1121 display controller + seps525 SEPS525 display controller + sh1106 SH1106 display controller + ssd1289 SSD1289 display controller + ssd1305 SSD1305 display controller + ssd1306 SSD1306 display controller + ssd1325 SSD1325 display controller + ssd1331 SSD1331 display controller + ssd1351 SSD1351 display controller + st7735r ST7735R display controller + st7789v ST7789V display controller + tls8204 TLS8204 display controller + uc1611 UC1611 display controller + uc1701 UC1701 display controller + upd161704 UPD161704 display controller + + width Display width in pixels + height Display height in pixels + regwidth Display controller register width (default is + driver specific) + buswidth Display bus interface width (default 8) + debug Debug output level {0-7} + rotate Display rotation {0, 90, 180, 270} (counter + clockwise). Not supported by all drivers. + bgr Enable BGR mode (default off). Use if Red and + Blue are swapped. Not supported by all drivers. + fps Frames per second (default 30). In effect this + states how long the driver will wait after video + memory has been changed until display update + transfer is started. + txbuflen Length of the FBTFT transmit buffer + (default 4096) + startbyte Sets the Start byte used by fb_ili9320, + fb_ili9325 and fb_hx8347d. Common value is 0x70. + gamma String representation of Gamma Curve(s). Driver + specific. Not supported by all drivers. + reset_pin GPIO pin for RESET + dc_pin GPIO pin for D/C + led_pin GPIO pin for LED backlight + + +Name: fe-pi-audio +Info: Configures the Fe-Pi Audio Sound Card +Load: dtoverlay=fe-pi-audio +Params: <None> + + +Name: fsm-demo +Info: A demonstration of the gpio-fsm driver. The GPIOs are chosen to work + nicely with a "traffic-light" display of red, amber and green LEDs on + GPIOs 7, 8 and 25 respectively. +Load: dtoverlay=fsm-demo,<param>=<val> +Params: fsm_debug Enable debug logging (default off) + + +Name: gc9a01 +Info: Enables GalaxyCore's GC9A01 single chip driver based displays on + SPI0 as fb1, using GPIOs DC=25, RST=27 and BL=18 (physical + GPIO header pins 22, 13 and 12 respectively) in addition to the + SPI0 pins DIN=10, CLK=11 and CS=8 (physical GPIO header pins 19, + 23 and 24 respectively). +Load: dtoverlay=gc9a01,<param>=<val> +Params: speed Display SPI bus speed + + rotate Display rotation {0,90,180,270} + + width Width of the display + + height Height of the display + + fps Delay between frame updates + + debug Debug output level {0-7} + + +Name: ghost-amp +Info: An overlay for the Ghost amplifier. +Load: dtoverlay=ghost-amp,<param>=<val> +Params: fsm_debug Enable debug logging of the GPIO FSM (default + off) + + +Name: goodix +Info: Enables I2C connected Goodix gt9271 multiple touch controller using + GPIOs 4 and 17 (pins 7 and 11 on GPIO header) for interrupt and reset. +Load: dtoverlay=goodix,<param>=<val> +Params: addr I2C address (default 0x14) + interrupt GPIO used for interrupt (default 4) + reset GPIO used for reset (default 17) + i2c-path Override I2C path to allow for i2c-gpio buses + + +Name: googlevoicehat-soundcard +Info: Configures the Google voiceHAT soundcard +Load: dtoverlay=googlevoicehat-soundcard +Params: <None> + + +Name: gpio-charger +Info: This is a generic overlay for detecting charger with GPIO. +Load: dtoverlay=gpio-charger,<param>=<val> +Params: gpio GPIO pin to trigger on (default 4) + active_low When this is 1 (active low), a falling + edge generates a charging event and a + rising edge generates a discharging event. + When this is 0 (active high), this is + reversed. The default is 0 (active high) + gpio_pull Desired pull-up/down state (off, down, up) + Default is "down". + type Set a charger type for the pin. (Default: mains) + + +Name: gpio-fan +Info: Configure a GPIO pin to control a cooling fan. +Load: dtoverlay=gpio-fan,<param>=<val> +Params: gpiopin GPIO used to control the fan (default 12) + temp Temperature at which the fan switches on, in + millicelcius (default 55000) + hyst Temperature delta (in millicelcius) below + temp at which the fan will drop to minrpm + (default 10000) + + +Name: gpio-hog +Info: Activate a "hog" for a GPIO - request that the kernel configures it as + an output, driven low or high as indicated by the presence or absence + of the active_low parameter. Note that a hogged GPIO is not available + to other drivers or for gpioset/gpioget. +Load: dtoverlay=gpio-hog,<param>=<val> +Params: gpio GPIO pin to hog (default 26) + active_low If set, the hog drives the GPIO low (defaults + to off - the GPIO is driven high) + + +Name: gpio-ir +Info: Use GPIO pin as rc-core style infrared receiver input. The rc-core- + based gpio_ir_recv driver maps received keys directly to a + /dev/input/event* device, all decoding is done by the kernel - LIRC is + not required! The key mapping and other decoding parameters can be + configured by "ir-keytable" tool. +Load: dtoverlay=gpio-ir,<param>=<val> +Params: gpio_pin Input pin number. Default is 18. + + gpio_pull Desired pull-up/down state (off, down, up) + Default is "up". + + invert "1" = invert the input (active-low signalling). + "0" = non-inverted input (active-high + signalling). Default is "1". + + rc-map-name Default rc keymap (can also be changed by + ir-keytable), defaults to "rc-rc6-mce" + + +Name: gpio-ir-tx +Info: Use GPIO pin as bit-banged infrared transmitter output. + This is an alternative to "pwm-ir-tx". gpio-ir-tx doesn't require + a PWM so it can be used together with onboard analog audio. +Load: dtoverlay=gpio-ir-tx,<param>=<val> +Params: gpio_pin Output GPIO (default 18) + + invert "1" = invert the output (make it active-low). + Default is "0" (active-high). + + +Name: gpio-key +Info: This is a generic overlay for activating GPIO keypresses using + the gpio-keys library and this dtoverlay. Multiple keys can be + set up using multiple calls to the overlay for configuring + additional buttons or joysticks. You can see available keycodes + at https://github.com/torvalds/linux/blob/v4.12/include/uapi/ + linux/input-event-codes.h#L64 +Load: dtoverlay=gpio-key,<param>=<val> +Params: gpio GPIO pin to trigger on (default 3) + active_low When this is 1 (active low), a falling + edge generates a key down event and a + rising edge generates a key up event. + When this is 0 (active high), this is + reversed. The default is 1 (active low) + gpio_pull Desired pull-up/down state (off, down, up) + Default is "up". Note that the default pin + (GPIO3) has an external pullup + label Set a label for the key + keycode Set the key code for the button + + + +Name: gpio-led +Info: This is a generic overlay for activating LEDs (or any other component) + by a GPIO pin. Multiple LEDs can be set up using multiple calls to the + overlay. While there are many existing methods to activate LEDs on the + RPi, this method offers some advantages: + 1) Does not require any userspace programs. + 2) LEDs can be connected to the kernel's led-trigger framework, + and drive the LED based on triggers such as cpu load, heartbeat, + kernel panic, key input, timers and others. + 3) LED can be tied to the input state of another GPIO pin. + 4) The LED is setup early during the kernel boot process (useful + for cpu/heartbeat/panic triggers). + + Typical electrical connection is: + RPI-GPIO.19 -> LED -> 300ohm resister -> RPI-GND + The GPIO pin number can be changed with the 'gpio=' parameter. + + To control an LED from userspace, write a 0 or 1 value: + echo 1 > /sys/class/leds/myled1/brightness + The 'myled1' name can be changed with the 'label=' parameter. + + To connect the LED to a kernel trigger from userspace: + echo cpu > /sys/class/leds/myled1/trigger + echo heartbeat > /sys/class/leds/myled1/trigger + echo none > /sys/class/leds/myled1/trigger + To connect the LED to GPIO.26 pin (physical pin 37): + echo gpio > /sys/class/leds/myled1/trigger + echo 26 > /sys/class/leds/myled1/gpio + Available triggers: + cat /sys/class/leds/myled1/trigger + + More information about the Linux kernel LED/Trigger system: + https://www.kernel.org/doc/Documentation/leds/leds-class.rst + https://www.kernel.org/doc/Documentation/leds/ledtrig-oneshot.rst +Load: dtoverlay=gpio-led,<param>=<val> +Params: gpio GPIO pin connected to the LED (default 19) + label The label for this LED. It will appear under + /sys/class/leds/<label> . Default 'myled1'. + trigger Set the led-trigger to connect to this LED. + default 'none' (LED is user-controlled). + Some possible triggers: + cpu - CPU load (all CPUs) + cpu0 - CPU load of first CPU. + mmc - disk activity (all disks) + panic - turn on on kernel panic + heartbeat - indicate system health + gpio - connect to a GPIO input pin (note: + currently the GPIO PIN can not be set + using overlay parameters, must be + done in userspace, see examples above. + active_low Set to 1 to turn invert the LED control + (writing 0 to /sys/class/leds/XXX/brightness + will turn on the GPIO/LED). Default '0'. + + +Name: gpio-no-bank0-irq +Info: Use this overlay to disable GPIO interrupts for GPIOs in bank 0 (0-27), + which can be useful for UIO drivers. + N.B. Using this overlay will trigger a kernel WARN during booting, but + this can safely be ignored - the system should work as expected. +Load: dtoverlay=gpio-no-bank0-irq +Params: <None> + + +Name: gpio-no-irq +Info: Use this overlay to disable all GPIO interrupts, which can be useful + for user-space GPIO edge detection systems. +Load: dtoverlay=gpio-no-irq +Params: <None> + + +Name: gpio-poweroff +Info: Drives a GPIO high or low on poweroff (including halt). Using this + overlay interferes with the normal power-down sequence, preventing the + kernel from resetting the SoC (a necessary step in a normal power-off + or reboot). This also disables the ability to trigger a boot by driving + GPIO3 low. + + The GPIO starts in an inactive state. At poweroff time it is driven + active for 100ms, then inactive for 100ms, then active again. It is + safe to remove the power at any point after the initial activation of + the GPIO. + + Users of this overlay are required to provide an external mechanism to + switch off the power supply when signalled - failure to do so results + in a kernel BUG, increased power consumption and undefined behaviour. +Load: dtoverlay=gpio-poweroff,<param>=<val> +Params: gpiopin GPIO for signalling (default 26) + + active_low Set if the power control device requires a + high->low transition to trigger a power-down. + Note that this will require the support of a + custom dt-blob.bin to prevent a power-down + during the boot process, and that a reboot + will also cause the pin to go low. + input Set if the gpio pin should be configured as + an input. + export Set to export the configured pin to sysfs + active_delay_ms Initial GPIO active period (default 100) + inactive_delay_ms Subsequent GPIO inactive period (default 100) + timeout_ms Specify (in ms) how long the kernel waits for + power-down before issuing a WARN (default 3000). + + +Name: gpio-shutdown +Info: Initiates a shutdown when GPIO pin changes. The given GPIO pin + is configured as an input key that generates KEY_POWER events. + + This event is handled by systemd-logind by initiating a + shutdown. Systemd versions older than 225 need an udev rule + enable listening to the input device: + + ACTION!="REMOVE", SUBSYSTEM=="input", KERNEL=="event*", \ + SUBSYSTEMS=="platform", DRIVERS=="gpio-keys", \ + ATTRS{keys}=="116", TAG+="power-switch" + + Alternatively this event can be handled also on systems without + systemd, just by traditional SysV init daemon. KEY_POWER event + (keycode 116) needs to be mapped to KeyboardSignal on console + and then kb::kbrequest inittab action which is triggered by + KeyboardSignal from console can be configured to issue system + shutdown. Steps for this configuration are: + + Add following lines to the /etc/console-setup/remap.inc file: + + # Key Power as special keypress + keycode 116 = KeyboardSignal + + Then add following lines to /etc/inittab file: + + # Action on special keypress (Key Power) + kb::kbrequest:/sbin/shutdown -t1 -a -h -P now + + And finally reload configuration by calling following commands: + + # dpkg-reconfigure console-setup + # service console-setup reload + # init q + + This overlay only handles shutdown. After shutdown, the system + can be powered up again by driving GPIO3 low. The default + configuration uses GPIO3 with a pullup, so if you connect a + button between GPIO3 and GND (pin 5 and 6 on the 40-pin header), + you get a shutdown and power-up button. Please note that + Raspberry Pi 1 Model B rev 1 uses GPIO1 instead of GPIO3. +Load: dtoverlay=gpio-shutdown,<param>=<val> +Params: gpio_pin GPIO pin to trigger on (default 3) + For Raspberry Pi 1 Model B rev 1 set this + explicitly to value 1, e.g.: + + dtoverlay=gpio-shutdown,gpio_pin=1 + + active_low When this is 1 (active low), a falling + edge generates a key down event and a + rising edge generates a key up event. + When this is 0 (active high), this is + reversed. The default is 1 (active low). + + gpio_pull Desired pull-up/down state (off, down, up) + Default is "up". + + Note that the default pin (GPIO3) has an + external pullup. Same applies for GPIO1 + on Raspberry Pi 1 Model B rev 1. + + debounce Specify the debounce interval in milliseconds + (default 100) + + +Name: hd44780-i2c-lcd +Info: Configures an HD44780 compatible LCD display connected via a PCF8574 as + is often found as a backpack interface for these displays. +Load: dtoverlay=hd44780-i2c-lcd,<param>=<val> +Params: addr I2C address of PCF8574 + pin_d4 GPIO pin for data pin D4 (default 4) + + pin_d5 GPIO pin for data pin D5 (default 5) + + pin_d6 GPIO pin for data pin D6 (default 6) + + pin_d7 GPIO pin for data pin D7 (default 7) + + pin_en GPIO pin for "Enable" (default 2) + + pin_rs GPIO pin for "Register Select" (default 0) + + pin_rw GPIO pin for R/W select (default 1) + + pin_bl GPIO pin for enabling/disabling the display + backlight. (default 3) + + display_height Height of the display in characters (default 2) + + display_width Width of the display in characters (default 16) + i2c-path Override I2C path to allow for i2c-gpio buses + + +Name: hd44780-lcd +Info: Configures an HD44780 compatible LCD display. Uses 4 gpio pins for + data, 2 gpio pins for enable and register select and 1 optional pin + for enabling/disabling the backlight display. +Load: dtoverlay=hd44780-lcd,<param>=<val> +Params: pin_d4 GPIO pin for data pin D4 (default 6) + + pin_d5 GPIO pin for data pin D5 (default 13) + + pin_d6 GPIO pin for data pin D6 (default 19) + + pin_d7 GPIO pin for data pin D7 (default 26) + + pin_en GPIO pin for "Enable" (default 21) + + pin_rs GPIO pin for "Register Select" (default 20) + + pin_bl Optional pin for enabling/disabling the + display backlight. (default disabled) + + display_height Height of the display in characters (default 2) + + display_width Width of the display in characters (default 16) + + +Name: hdmi-backlight-hwhack-gpio +Info: Devicetree overlay for GPIO based backlight on/off capability. + Use this if you have one of those HDMI displays whose backlight cannot + be controlled via DPMS over HDMI and plan to do a little soldering to + use an RPi gpio pin for on/off switching. See: + https://www.waveshare.com/wiki/7inch_HDMI_LCD_(C)#Backlight_Control +Load: dtoverlay=hdmi-backlight-hwhack-gpio,<param>=<val> +Params: gpio_pin GPIO pin used (default 17) + active_low Set this to 1 if the display backlight is + switched on when the wire goes low. + Leave the default (value 0) if the backlight + expects a high to switch it on. + + +Name: hifiberry-adc +Info: Configures the HifiBerry ADC audio card +Load: dtoverlay=hifiberry-adc,<param>=<val> +Params: leds_off If set to 'true' the onboard indicator LED + is switched off at all times. + + +Name: hifiberry-adc8x +Info: Configures the HifiBerry ADC8X audio card (only on Pi5) +Load: dtoverlay=hifiberry-adc8x +Params: <None> + + +Name: hifiberry-amp +Info: Configures the HifiBerry Amp and Amp+ audio cards +Load: dtoverlay=hifiberry-amp +Params: <None> + + +Name: hifiberry-amp100 +Info: Configures the HifiBerry AMP100 audio card +Load: dtoverlay=hifiberry-amp100,<param>=<val> +Params: 24db_digital_gain Allow gain to be applied via the PCM512x codec + Digital volume control. Enable with + "dtoverlay=hifiberry-amp100,24db_digital_gain" + (The default behaviour is that the Digital + volume control is limited to a maximum of + 0dB. ie. it can attenuate but not provide + gain. For most users, this will be desired + as it will prevent clipping. By appending + the 24dB_digital_gain parameter, the Digital + volume control will allow up to 24dB of + gain. If this parameter is enabled, it is the + responsibility of the user to ensure that + the Digital volume control is set to a value + that does not result in clipping/distortion!) + slave Force AMP100 into slave mode, using Pi as + master for bit clock and frame clock. + leds_off If set to 'true' the onboard indicator LEDs + are switched off at all times. + auto_mute If set to 'true' the amplifier is automatically + muted when the DAC is not playing. + mute_ext_ctl The amplifier's HW mute control is enabled + in ALSA mixer and set to <val>. + Will be overwritten by ALSA user settings. + + +Name: hifiberry-amp3 +Info: Configures the HifiBerry Amp3 audio card +Load: dtoverlay=hifiberry-amp3 +Params: <None> + + +Name: hifiberry-amp4pro +Info: Configures the HifiBerry AMP4 Pro audio card +Load: dtoverlay=hifiberry-amp4pro,<param>=<val> +Params: 24db_digital_gain Allow gain to be applied via the TAS5756 + Digital volume control. Enable with + "dtoverlay=hifiberry-amp4pro,24db_digital_gain" + (The default behaviour is that the Digital + volume control is limited to a maximum of + 0dB. ie. it can attenuate but not provide + gain. For most users, this will be desired + as it will prevent clipping. By appending + the 24dB_digital_gain parameter, the Digital + volume control will allow up to 24dB of + gain. If this parameter is enabled, it is the + responsibility of the user to ensure that + the Digital volume control is set to a value + that does not result in clipping/distortion!) + slave Force the amp into slave mode, using Pi as + master for bit clock and frame clock. + leds_off If set to 'true' the onboard indicator LEDs + are switched off at all times. + auto_mute If set to 'true' the amplifier is automatically + muted when it is not playing. + mute_ext_ctl The amplifier's HW mute control is enabled + in ALSA mixer and set to <val>. + Will be overwritten by ALSA user settings. + + +Name: hifiberry-dac +Info: Configures the HifiBerry DAC audio cards +Load: dtoverlay=hifiberry-dac +Params: <None> + + +Name: hifiberry-dac8x +Info: Configures the HifiBerry DAC8X audio cards (only on Pi5) + This driver also detects a stacked ADC8x and activates the + capture capabilities. + Note: for standalone use of the ADC8x activate the ADC8x module. +Load: dtoverlay=hifiberry-dac8x +Params: <None> + + +Name: hifiberry-dacplus +Info: Configures the HifiBerry DAC+ audio card +Load: dtoverlay=hifiberry-dacplus,<param>=<val> +Params: 24db_digital_gain Allow gain to be applied via the PCM512x codec + Digital volume control. Enable with + "dtoverlay=hifiberry-dacplus,24db_digital_gain" + (The default behaviour is that the Digital + volume control is limited to a maximum of + 0dB. ie. it can attenuate but not provide + gain. For most users, this will be desired + as it will prevent clipping. By appending + the 24dB_digital_gain parameter, the Digital + volume control will allow up to 24dB of + gain. If this parameter is enabled, it is the + responsibility of the user to ensure that + the Digital volume control is set to a value + that does not result in clipping/distortion!) + slave Force DAC+ into slave mode, using Pi as + master for bit clock and frame clock. + leds_off If set to 'true' the onboard indicator LEDs + are switched off at all times. + + +Name: hifiberry-dacplus-pro +Info: Configures the HifiBerry DAC+ PRO audio card (onboard clocks) +Load: dtoverlay=hifiberry-dacplus-pro,<param>=<val> +Params: 24db_digital_gain Allow gain to be applied via the PCM512x codec + Digital volume control. Enable with + "dtoverlay=hifiberry-dacplus,24db_digital_gain" + (The default behaviour is that the Digital + volume control is limited to a maximum of + 0dB. ie. it can attenuate but not provide + gain. For most users, this will be desired + as it will prevent clipping. By appending + the 24dB_digital_gain parameter, the Digital + volume control will allow up to 24dB of + gain. If this parameter is enabled, it is the + responsibility of the user to ensure that + the Digital volume control is set to a value + that does not result in clipping/distortion!) + leds_off If set to 'true' the onboard indicator LEDs + are switched off at all times. + + +Name: hifiberry-dacplus-std +Info: Configures the HifiBerry DAC+ standard audio card (no onboard clocks) +Load: dtoverlay=hifiberry-dacplus-std,<param>=<val> +Params: 24db_digital_gain Allow gain to be applied via the PCM512x codec + Digital volume control. Enable with + "dtoverlay=hifiberry-dacplus,24db_digital_gain" + (The default behaviour is that the Digital + volume control is limited to a maximum of + 0dB. ie. it can attenuate but not provide + gain. For most users, this will be desired + as it will prevent clipping. By appending + the 24dB_digital_gain parameter, the Digital + volume control will allow up to 24dB of + gain. If this parameter is enabled, it is the + responsibility of the user to ensure that + the Digital volume control is set to a value + that does not result in clipping/distortion!) + leds_off If set to 'true' the onboard indicator LEDs + are switched off at all times. + + +Name: hifiberry-dacplusadc +Info: Configures the HifiBerry DAC+ADC audio card +Load: dtoverlay=hifiberry-dacplusadc,<param>=<val> +Params: 24db_digital_gain Allow gain to be applied via the PCM512x codec + Digital volume control. Enable with + "dtoverlay=hifiberry-dacplus,24db_digital_gain" + (The default behaviour is that the Digital + volume control is limited to a maximum of + 0dB. ie. it can attenuate but not provide + gain. For most users, this will be desired + as it will prevent clipping. By appending + the 24dB_digital_gain parameter, the Digital + volume control will allow up to 24dB of + gain. If this parameter is enabled, it is the + responsibility of the user to ensure that + the Digital volume control is set to a value + that does not result in clipping/distortion!) + leds_off If set to 'true' the onboard indicator LEDs + are switched off at all times. + + +Name: hifiberry-dacplusadcpro +Info: Configures the HifiBerry DAC+ADC PRO audio card +Load: dtoverlay=hifiberry-dacplusadcpro,<param>=<val> +Params: 24db_digital_gain Allow gain to be applied via the PCM512x codec + Digital volume control. Enable with + "dtoverlay=hifiberry-dacplusadcpro,24db_digital_gain" + (The default behaviour is that the Digital + volume control is limited to a maximum of + 0dB. ie. it can attenuate but not provide + gain. For most users, this will be desired + as it will prevent clipping. By appending + the 24dB_digital_gain parameter, the Digital + volume control will allow up to 24dB of + gain. If this parameter is enabled, it is the + responsibility of the user to ensure that + the Digital volume control is set to a value + that does not result in clipping/distortion!) + slave Force DAC+ADC Pro into slave mode, using Pi as + master for bit clock and frame clock. + leds_off If set to 'true' the onboard indicator LEDs + are switched off at all times. + + +Name: hifiberry-dacplusdsp +Info: Configures the HifiBerry DAC+DSP audio card +Load: dtoverlay=hifiberry-dacplusdsp +Params: <None> + + +Name: hifiberry-dacplushd +Info: Configures the HifiBerry DAC+ HD audio card +Load: dtoverlay=hifiberry-dacplushd +Params: <None> + + +Name: hifiberry-digi +Info: Configures the HifiBerry Digi and Digi+ audio card +Load: dtoverlay=hifiberry-digi +Params: <None> + + +Name: hifiberry-digi-pro +Info: Configures the HifiBerry Digi+ Pro and Digi2 Pro audio card +Load: dtoverlay=hifiberry-digi-pro +Params: <None> + + +Name: highperi +Info: Enables "High Peripheral" mode +Load: dtoverlay=highperi +Params: <None> + + +Name: hy28a +Info: HY28A - 2.8" TFT LCD Display Module by HAOYU Electronics + Default values match Texy's display shield +Load: dtoverlay=hy28a,<param>=<val> +Params: speed Display SPI bus speed + + rotate Display rotation {0,90,180,270} + + fps Delay between frame updates + + debug Debug output level {0-7} + + xohms Touchpanel sensitivity (X-plate resistance) + + resetgpio GPIO used to reset controller + + ledgpio GPIO used to control backlight + + +Name: hy28b +Info: HY28B - 2.8" TFT LCD Display Module by HAOYU Electronics + Default values match Texy's display shield +Load: dtoverlay=hy28b,<param>=<val> +Params: speed Display SPI bus speed + + rotate Display rotation {0,90,180,270} + + fps Delay between frame updates + + debug Debug output level {0-7} + + xohms Touchpanel sensitivity (X-plate resistance) + + resetgpio GPIO used to reset controller + + ledgpio GPIO used to control backlight + + +Name: hy28b-2017 +Info: HY28B 2017 version - 2.8" TFT LCD Display Module by HAOYU Electronics + Default values match Texy's display shield +Load: dtoverlay=hy28b-2017,<param>=<val> +Params: speed Display SPI bus speed + + rotate Display rotation {0,90,180,270} + + fps Delay between frame updates + + debug Debug output level {0-7} + + xohms Touchpanel sensitivity (X-plate resistance) + + resetgpio GPIO used to reset controller + + ledgpio GPIO used to control backlight + + +Name: i-sabre-q2m +Info: Configures the Audiophonics I-SABRE Q2M DAC +Load: dtoverlay=i-sabre-q2m +Params: <None> + + +Name: i2c-bcm2708 +Info: Fall back to the i2c_bcm2708 driver for the i2c_arm bus. +Load: dtoverlay=i2c-bcm2708 +Params: <None> + + +Name: i2c-bus +Info: This is not a real overlay. Many overlays support the use of a variety + of I2C buses, and this is where the relevant parameters are documented. +Load: <Documentation> +Params: i2c0 Choose the I2C0 bus on GPIOs 0&1 + i2c1 Choose the I2C1 bus on GPIOs 2&3 + i2c2 Choose the I2C2 bus (configure with the i2c2 + overlay - BCM2711 only) + i2c3 Choose the I2C3 bus (configure with the i2c3 + overlay - BCM2711 only) + i2c4 Choose the I2C4 bus (configure with the i2c4 + overlay - BCM2711 only) + i2c5 Choose the I2C5 bus (configure with the i2c5 + overlay - BCM2711 only) + i2c6 Choose the I2C6 bus (configure with the i2c6 + overlay - BCM2711 only) + i2c_csi_dsi Choose the I2C bus connected to the main + camera/display connector. + See "dtparam -h i2c_csi_dsi" for details. + i2c_csi_dsi0 Choose the I2C bus connected to the second + camera/display connector, if present. + See "dtparam -h i2c_csi_dsi0" for details. + i2c-path Override I2C path to allow for i2c-gpio buses + + +Name: i2c-fan +Info: Adds support for a number of I2C fan controllers +Load: dtoverlay=i2c-fan,<param>=<val> +Params: addr Sets the address for the fan controller. Note + that the device must be configured to use the + specified address. + + i2c-bus Supports all the standard I2C bus selection + parameters - see "dtoverlay -h i2c-bus" + + minpwm PWM setting for the fan when the SoC is below + mintemp (range 0-255. default 0) + maxpwm PWM setting for the fan when the SoC is above + maxtemp (range 0-255. default 255) + midtemp Temperature (in millicelcius) at which the fan + begins to speed up (default 50000) + + midtemp_hyst Temperature delta (in millicelcius) below + mintemp at which the fan will drop to minrpm + (default 2000) + + maxtemp Temperature (in millicelcius) at which the fan + will be held at maxrpm (default 70000) + + maxtemp_hyst Temperature delta (in millicelcius) below + maxtemp at which the fan begins to slow down + (default 2000) + + emc2301 Select the Microchip EMC230x controller family + - EMC2301, EMC2302, EMC2303, EMC2305. + + +Name: i2c-gpio +Info: Adds support for software i2c controller on gpio pins +Load: dtoverlay=i2c-gpio,<param>=<val> +Params: i2c_gpio_sda GPIO used for I2C data (default "23") + + i2c_gpio_scl GPIO used for I2C clock (default "24") + + i2c_gpio_delay_us Clock delay in microseconds + (default "2" = ~100kHz) + + bus Set to a unique, non-zero value if wanting + multiple i2c-gpio busses. If set, will be used + as the preferred bus number (/dev/i2c-<n>). If + not set, the default value is 0, but the bus + number will be dynamically assigned - probably + 3. + + +Name: i2c-mux +Info: Adds support for a number of I2C bus multiplexers on i2c_arm +Load: dtoverlay=i2c-mux,<param>=<val> +Params: pca9542 Select the NXP PCA9542 device + + pca9545 Select the NXP PCA9545 device + + pca9548 Select the NXP PCA9548 device + + addr Change I2C address of the device (default 0x70) + + i2c-bus Supports all the standard I2C bus selection + parameters - see "dtoverlay -h i2c-bus" + + base Set an explicit base value for the channel bus + numbers + + disconnect_on_idle Force the mux to disconnect all child buses + after every transaction. + + +[ The i2c-mux-pca9548a overlay has been deleted. See i2c-mux. ] + + +Name: i2c-pwm-pca9685a +Info: Adds support for an NXP PCA9685A I2C PWM controller on i2c_arm +Load: dtoverlay=i2c-pwm-pca9685a,<param>=<val> +Params: addr I2C address of PCA9685A (default 0x40) + i2c-bus Supports all the standard I2C bus selection + parameters - see "dtoverlay -h i2c-bus" + + +Name: i2c-rtc +Info: Adds support for a number of I2C Real Time Clock devices +Load: dtoverlay=i2c-rtc,<param>=<val> +Params: abx80x Select one of the ABx80x family: + AB0801, AB0803, AB0804, AB0805, + AB1801, AB1803, AB1804, AB1805 + + bq32000 Select the TI BQ32000 device + + ds1307 Select the DS1307 device + + ds1339 Select the DS1339 device + + ds1340 Select the DS1340 device + + ds3231 Select the DS3231 device + + m41t62 Select the M41T62 device + + mcp7940x Select the MCP7940x device + + mcp7941x Select the MCP7941x device + + pcf2127 Select the PCF2127 device + + pcf2129 Select the PCF2129 device + + pcf2131 Select the PCF2131 device + + pcf85063 Select the PCF85063 device + + pcf85063a Select the PCF85063A device + + pcf8523 Select the PCF8523 device + + pcf85363 Select the PCF85363 device + + pcf8563 Select the PCF8563 device + + rv1805 Select the Micro Crystal RV1805 device + + rv3028 Select the Micro Crystal RV3028 device + + rv3032 Select the Micro Crystal RV3032 device + + rv8803 Select the Micro Crystal RV8803 device + + sd3078 Select the ZXW Shenzhen whwave SD3078 device + + s35390a Select the ABLIC S35390A device + + i2c-bus Supports all the standard I2C bus selection + parameters - see "dtoverlay -h i2c-bus" + + addr Sets the address for the RTC. Note that the + device must be configured to use the specified + address. + + trickle-diode-disable Do not use the internal trickle charger diode + (BQ32000 only) + + trickle-diode-type Diode type for trickle charge - "standard" or + "schottky" (ABx80x and RV1805 only) + + trickle-resistor-ohms Resistor value for trickle charge (DS1339, + ABx80x, BQ32000, RV1805, RV3028, RV3032) + + trickle-voltage-mv Charge pump voltage for trickle charge (RV3032) + + wakeup-source Specify that the RTC can be used as a wakeup + source + + backup-switchover-mode Backup power supply switch mode. Must be 0 for + "Switchover disabled", 1 for "Direct Switching" + (if Vdd < VBackup), 2 for "Standby + Mode" (if Vdd < Vbackup, + does not draw current) or 3 for + "Level Switching" (if Vdd < Vbackup + and Vdd < Vddsw and Vbackup > Vddsw) + (RV3028, RV3032) + + +Name: i2c-rtc-gpio +Info: Adds support for a number of I2C Real Time Clock devices + using the software i2c controller +Load: dtoverlay=i2c-rtc-gpio,<param>=<val> +Params: abx80x Select one of the ABx80x family: + AB0801, AB0803, AB0804, AB0805, + AB1801, AB1803, AB1804, AB1805 + + bq32000 Select the TI BQ32000 device + + ds1307 Select the DS1307 device + + ds1339 Select the DS1339 device + + ds1340 Select the DS1340 device + + ds3231 Select the DS3231 device + + m41t62 Select the M41T62 device + + mcp7940x Select the MCP7940x device + + mcp7941x Select the MCP7941x device + + pcf2127 Select the PCF2127 device + + pcf2129 Select the PCF2129 device + + pcf2131 Select the PCF2131 device + + pcf85063 Select the PCF85063 device + + pcf85063a Select the PCF85063A device + + pcf8523 Select the PCF8523 device + + pcf85363 Select the PCF85363 device + + pcf8563 Select the PCF8563 device + + rv1805 Select the Micro Crystal RV1805 device + + rv3028 Select the Micro Crystal RV3028 device + + rv3032 Select the Micro Crystal RV3032 device + + rv8803 Select the Micro Crystal RV8803 device + + sd3078 Select the ZXW Shenzhen whwave SD3078 device + + s35390a Select the ABLIC S35390A device + + addr Sets the address for the RTC. Note that the + device must be configured to use the specified + address. + + trickle-diode-disable Do not use the internal trickle charger diode + (BQ32000 only) + + trickle-diode-type Diode type for trickle charge - "standard" or + "schottky" (ABx80x and RV1805 only) + + trickle-resistor-ohms Resistor value for trickle charge (DS1339, + ABx80x, BQ32000, RV1805, RV3028, RV3032) + + trickle-voltage-mv Charge pump voltage for trickle charge (RV3032) + + wakeup-source Specify that the RTC can be used as a wakeup + source + + backup-switchover-mode Backup power supply switch mode. Must be 0 for + "Switchover disabled", 1 for "Direct Switching" + (if Vdd < VBackup), 2 for "Standby + Mode" (if Vdd < Vbackup, + does not draw current) or 3 for + "Level Switching" (if Vdd < Vbackup + and Vdd < Vddsw and Vbackup > Vddsw) + (RV3028, RV3032) + + i2c_gpio_sda GPIO used for I2C data (default "23") + + i2c_gpio_scl GPIO used for I2C clock (default "24") + + i2c_gpio_delay_us Clock delay in microseconds + (default "2" = ~100kHz) + + +Name: i2c-sensor +Info: Adds support for a number of I2C barometric pressure, temperature, + light level and chemical sensors on i2c_arm +Load: dtoverlay=i2c-sensor,<param>=<val> +Params: addr Set the address for the ADT7410, AS73211, + AS7331, BH1750, BME280, BME680, BMP280, BMP380, + CCS811, DS1621, HDC100X, HDC3020, JC42, LM75, + MCP980x, MPU6050, MPU9250, MS5637, MS5803, + MS5805, MS5837, MS8607, SHT3x or TMP102 + + i2c-bus Supports all the standard I2C bus selection + parameters - see "dtoverlay -h i2c-bus" + + adt7410 Select the Analog Devices ADT7410 and ADT7420 + temperature sensors + Valid address 0x48-0x4b, default 0x48 + + aht10 Select the Aosong AHT10 temperature and humidity + sensor + + as73211 Select the AMS AS73211 XYZ true color sensor + Valid addresses 0x74-0x77, default 0x74 + + as7331 Select the AMS AS7331 spectral UV sensor + Valid addresses 0x74-0x77, default 0x74 + + bh1750 Select the Rohm BH1750 ambient light sensor + Valid addresses 0x23 or 0x5c, default 0x23 + + bme280 Select the Bosch Sensortronic BME280 + Valid addresses 0x76-0x77, default 0x76 + + bme680 Select the Bosch Sensortronic BME680 + Valid addresses 0x76-0x77, default 0x76 + + bmp085 Select the Bosch Sensortronic BMP085 + + bmp180 Select the Bosch Sensortronic BMP180 + + bmp280 Select the Bosch Sensortronic BMP280 + Valid addresses 0x76-0x77, default 0x76 + + bmp380 Select the Bosch Sensortronic BMP380 + Valid addresses 0x76-0x77, default 0x76 + + bno055 Select the Bosch Sensortronic BNO055 IMU + Valid address 0x28-0x29, default 0x29 + + ccs811 Select the AMS CCS811 digital gas sensor + Valid addresses 0x5a-0x5b, default 0x5b + + ds1621 Select the Dallas Semiconductors DS1621 temp + sensor. Valid addresses 0x48-0x4f, default 0x48 + + gain Gain used for measuring shunt resistor current. + Valid values 1 or 4, default 1. (ina238 only, + disabled by default) + + hdc100x Select the Texas Instruments HDC100x temp sensor + Valid addresses 0x40-0x43, default 0x40 + + hdc3020 Select the Texas Instruments HDC3020 temp sensor + Valid addresses 0x44-0x47, default 0x44 + + hts221 Select the HTS221 temperature and humidity + sensor + + htu21 Select the HTU21 temperature and humidity sensor + + ina238 Select the TI INA238 power monitor. Valid + addresses 0x40-0x4F, default 0x40. + Uses parameters shunt-resistor and + ti,shunt-gain for configuration + + int_pin Set the GPIO to use for interrupts (as73211, + as7331, hdc3020, hts221, max30102, mpu6050 and + mpu9250 only) + + jc42 Select any of the many JEDEC JC42.4-compliant + temperature sensors, including: + ADT7408, AT30TS00, CAT34TS02, CAT6095, + MAX6604, MCP9804, MCP9805, MCP9808, + MCP98242, MCP98243, MCP98244, MCP9843, + SE97, SE98, STTS424(E), STTS2002, STTS3000, + TSE2002, TSE2004, TS3000, and TS3001. + The default address is 0x18. + + lm75 Select the Maxim LM75 temperature sensor + Valid addresses 0x48-0x4f, default 0x4f + + lm75addr Deprecated - use addr parameter instead + + max17040 Select the Maxim Integrated MAX17040 battery + monitor + + max30102 Select the Maxim Integrated MAX30102 heart-rate + and blood-oxygen sensor + + mcp980x Select the Maxim MCP980x range of temperature + sensors (i.e. MCP9800, MCP9801, MCP9802 and + MCP9803). N.B. For MCP9804, MCP9805 and MCP9808, + use the "jc42" option. + Valid addresses are 0x18-0x1f (default 0x18) + + mpu6050 Select the InvenSense MPU6050 IMU. Valid + valid addresses are 0x68 and 0x69 (default 0x68) + + mpu9250 Select the InvenSense MPU9250 IMU. Valid + valid addresses are 0x68 and 0x69 (default 0x68) + + ms5637 Select the Measurement Specialities MS5637 + pressure and temperature sensor. + + ms5803 Select the Measurement Specialities MS5803 + pressure and temperature sensor. + + ms5805 Select the Measurement Specialities MS5805 + pressure and temperature sensor. + + ms5837 Select the Measurement Specialities MS5837 + pressure and temperature sensor. + + ms8607 Select the Measurement Specialities MS8607 + pressure and temperature sensor. + + no_timeout Disable the SMBUS timeout. N.B. Only supported + by some jc42 devices - using with an + incompatible device can stop it from being + activated. + + reset_pin GPIO to be used to reset the device (bno055 + only, disabled by default) + + shunt_resistor Value of shunt resistor used for current + measurement in uOhms. (ina238 only, disabled + by default) + + sht3x Select the Sensirion SHT3x temperature and + humidity sensors. Valid addresses 0x44-0x45, + default 0x44 + + sht4x Select the Sensirion SHT4x temperature and + humidity sensors. Valid addresses 0x44-0x45, + default 0x44 + + shtc3 Select the Sensirion SHTC3 temperature and + humidity sensors. + + si7020 Select the Silicon Labs Si7013/20/21 humidity/ + temperature sensor + + sps30 Select the Sensirion SPS30 particulate matter + sensor. Fixed address 0x69. + + sgp30 Select the Sensirion SGP30 VOC sensor. + Fixed address 0x58. + + tmp102 Select the Texas Instruments TMP102 temp sensor + Valid addresses 0x48-0x4b, default 0x48 + + tsl4531 Select the AMS TSL4531 digital ambient light + sensor + + veml6070 Select the Vishay VEML6070 ultraviolet light + sensor + + veml6075 Select the Vishay VEML6075 UVA and UVB light + sensor + + +Name: i2c0 +Info: Change i2c0 pin usage. Not all pin combinations are usable on all + platforms - platforms other then Compute Modules can only use this + to disable transaction combining. + Do NOT use in conjunction with dtparam=i2c_vc=on. From the 5.4 kernel + onwards the base DT includes the use of i2c_mux_pinctrl to expose two + muxings of BSC0 - GPIOs 0&1, and whichever combination is used for the + camera and display connectors. This overlay disables that mux and + configures /dev/i2c0 to point at whichever set of pins is requested. + dtparam=i2c_vc=on will try and enable the mux, so combining the two + will cause conflicts. +Load: dtoverlay=i2c0,<param>=<val> +Params: pins_0_1 Use pins 0 and 1 (default) + pins_28_29 Use pins 28 and 29 + pins_44_45 Use pins 44 and 45 + pins_46_47 Use pins 46 and 47 + combine Allow transactions to be combined (default + "yes") + + +Name: i2c0-bcm2708 +Info: Deprecated, legacy version of i2c0. +Load: <Deprecated> + + +Name: i2c0-pi5 +Info: Enable i2c0 (Pi 5 only) +Load: dtoverlay=i2c0-pi5,<param>=<val> +Params: pins_0_1 Use GPIOs 0 and 1 (default) + pins_8_9 Use GPIOs 8 and 9 + baudrate Set the baudrate for the interface (default + "100000") + + +Name: i2c1 +Info: Change i2c1 pin usage. Not all pin combinations are usable on all + platforms - platforms other then Compute Modules can only use this + to disable transaction combining. +Load: dtoverlay=i2c1,<param>=<val> +Params: pins_2_3 Use pins 2 and 3 (default) + pins_44_45 Use pins 44 and 45 + combine Allow transactions to be combined (default + "yes") + + +Name: i2c1-bcm2708 +Info: Deprecated, legacy version of i2c1. +Load: <Deprecated> + + +Name: i2c1-pi5 +Info: Enable i2c1 (Pi 5 only) +Load: dtoverlay=i2c1-pi5,<param>=<val> +Params: pins_2_3 Use GPIOs 2 and 3 (default) + pins_10_11 Use GPIOs 10 and 11 + baudrate Set the baudrate for the interface (default + "100000") + + +Name: i2c2-pi5 +Info: Enable i2c2 (Pi 5 only) +Load: dtoverlay=i2c2-pi5,<param>=<val> +Params: pins_4_5 Use GPIOs 4 and 5 (default) + pins_12_13 Use GPIOs 12 and 13 + baudrate Set the baudrate for the interface (default + "100000") + + +Name: i2c3 +Info: Enable the i2c3 bus. BCM2711 only. +Load: dtoverlay=i2c3,<param> +Params: pins_2_3 Use GPIOs 2 and 3 + pins_4_5 Use GPIOs 4 and 5 (default) + baudrate Set the baudrate for the interface (default + "100000") + + +Name: i2c3-pi5 +Info: Enable i2c3 (Pi 5 only) +Load: dtoverlay=i2c3-pi5,<param>=<val> +Params: pins_6_7 Use GPIOs 6 and 7 (default) + pins_14_15 Use GPIOs 14 and 15 + pins_22_23 Use GPIOs 22 and 23 + baudrate Set the baudrate for the interface (default + "100000") + + +Name: i2c4 +Info: Enable the i2c4 bus. BCM2711 only. +Load: dtoverlay=i2c4,<param> +Params: pins_6_7 Use GPIOs 6 and 7 + pins_8_9 Use GPIOs 8 and 9 (default) + baudrate Set the baudrate for the interface (default + "100000") + + +Name: i2c5 +Info: Enable the i2c5 bus. BCM2711 only. +Load: dtoverlay=i2c5,<param> +Params: pins_10_11 Use GPIOs 10 and 11 + pins_12_13 Use GPIOs 12 and 13 (default) + baudrate Set the baudrate for the interface (default + "100000") + + +Name: i2c6 +Info: Enable the i2c6 bus. BCM2711 only. +Load: dtoverlay=i2c6,<param> +Params: pins_0_1 Use GPIOs 0 and 1 + pins_22_23 Use GPIOs 22 and 23 (default) + baudrate Set the baudrate for the interface (default + "100000") + + +Name: i2s-dac +Info: Configures any passive I2S DAC soundcard. +Load: dtoverlay=i2s-dac +Params: <None> + + +Name: i2s-gpio28-31 +Info: move I2S function block to GPIO 28 to 31 +Load: dtoverlay=i2s-gpio28-31 +Params: <None> + + +Name: i2s-master-dac +Info: Configures a generic I2S DAC soundcard that acts as a clock master. +Load: dtoverlay=i2s-master-dac +Params: <None> + + +Name: ilitek251x +Info: Enables I2C connected Ilitek 251x multiple touch controller using + GPIO 4 (pin 7 on GPIO header) for interrupt. +Load: dtoverlay=ilitek251x,<param>=<val> +Params: interrupt GPIO used for interrupt (default 4) + sizex Touchscreen size x, horizontal resolution of + touchscreen (in pixels) + sizey Touchscreen size y, vertical resolution of + touchscreen (in pixels) + i2c-path Override I2C path to allow for i2c-gpio buses + + +Name: imx219 +Info: Sony IMX219 camera module. + Uses Unicam 1, which is the standard camera connector on most Pi + variants. +Load: dtoverlay=imx219,<param>=<val> +Params: rotation Mounting rotation of the camera sensor (0 or + 180, default 180) + orientation Sensor orientation (0 = front, 1 = rear, + 2 = external, default external) + media-controller Configure use of Media Controller API for + configuring the sensor (default on) + cam0 Adopt the default configuration for CAM0 on a + Compute Module (CSI0, i2c_vc, and cam0_reg). + vcm Configure a VCM focus drive on the sensor. + 4lane Enable 4 CSI2 lanes. This requires a Compute + Module (1, 3, 4, or 5) or Pi 5. + + +Name: imx258 +Info: Sony IMX258 camera module. + Uses Unicam 1, which is the standard camera connector on most Pi + variants. +Load: dtoverlay=imx258,<param>=<val> +Params: rotation Mounting rotation of the camera sensor (0 or + 180, default 180) + orientation Sensor orientation (0 = front, 1 = rear, + 2 = external, default external) + media-controller Configure use of Media Controller API for + configuring the sensor (default on) + cam0 Adopt the default configuration for CAM0 on a + Compute Module (CSI0, i2c_vc, and cam0_reg). + vcm Configure a VCM focus drive on the sensor. + 4lane Enable 4 CSI2 lanes. This requires a Compute + Module (1, 3, or 4). + + +Name: imx290 +Info: Sony IMX290 camera module. + Uses Unicam 1, which is the standard camera connector on most Pi + variants. +Load: dtoverlay=imx290,<param> +Params: 4lane Enable 4 CSI2 lanes. This requires a Compute + Module (1, 3, or 4). + clock-frequency Sets the clock frequency to match that used on + the board. + Modules from Vision Components use 37.125MHz + (the default), whilst those from Innomaker use + 74.25MHz. + mono Denote that the module is a mono sensor. + orientation Sensor orientation (0 = front, 1 = rear, + 2 = external, default external) + rotation Mounting rotation of the camera sensor (0 or + 180, default 0) + media-controller Configure use of Media Controller API for + configuring the sensor (default on) + cam0 Adopt the default configuration for CAM0 on a + Compute Module (CSI0, i2c_vc, and cam0_reg). + + +Name: imx296 +Info: Sony IMX296 camera module. + Uses Unicam 1, which is the standard camera connector on most Pi + variants. +Load: dtoverlay=imx296,<param>=<val> +Params: rotation Mounting rotation of the camera sensor (0 or + 180, default 180) + orientation Sensor orientation (0 = front, 1 = rear, + 2 = external, default external) + media-controller Configure use of Media Controller API for + configuring the sensor (default on) + cam0 Adopt the default configuration for CAM0 on a + Compute Module (CSI0, i2c_vc, and cam0_reg). + clock-frequency Sets the clock frequency to match that used on + the board, which should be one of 54000000 + (the default), 37125000 or 74250000. + always-on Leave the regulator powered up, to stop the + camera clamping I/Os such as XTRIG to 0V. + + +Name: imx327 +Info: Sony IMX327 camera module. + Uses Unicam 1, which is the standard camera connector on most Pi + variants. +Load: dtoverlay=imx327,<param> +Params: 4lane Enable 4 CSI2 lanes. This requires a Compute + Module (1, 3, or 4). + clock-frequency Sets the clock frequency to match that used on + the board. + Modules from Vision Components use 37.125MHz + (the default), whilst those from Innomaker use + 74.25MHz. + mono Denote that the module is a mono sensor. + orientation Sensor orientation (0 = front, 1 = rear, + 2 = external, default external) + rotation Mounting rotation of the camera sensor (0 or + 180, default 0) + media-controller Configure use of Media Controller API for + configuring the sensor (default on) + cam0 Adopt the default configuration for CAM0 on a + Compute Module (CSI0, i2c_vc, and cam0_reg). + + +Name: imx378 +Info: Sony IMX378 camera module. + Uses Unicam 1, which is the standard camera connector on most Pi + variants. +Load: dtoverlay=imx378,<param>=<val> +Params: rotation Mounting rotation of the camera sensor (0 or + 180, default 180) + orientation Sensor orientation (0 = front, 1 = rear, + 2 = external, default external) + media-controller Configure use of Media Controller API for + configuring the sensor (default on) + cam0 Adopt the default configuration for CAM0 on a + Compute Module (CSI0, i2c_vc, and cam0_reg). + always-on Leave the regulator powered up, to stop the + camera clamping I/Os such as XVS to 0V. + sync-source Configure as vsync source + sync-sink Configure as vsync sink + link-frequency Allowable link frequency values to use in Hz: + 450000000 (default), 453000000, 456000000. + + +Name: imx415 +Info: Sony IMX415 camera module. + Uses Unicam 1, which is the standard camera connector on most Pi + variants. By default this uses 4 CSI2 data lanes, so requires a + Compute Module or Pi5. +Load: dtoverlay=imx415,<param> +Params: addr Set I2C address of sensor. Valid values are + 0x10, 0x1a, 0x36 and 0x37. Default is 0x37. + 4lane Enable 4 CSI2 data lanes. + clock-frequency Sets the clock frequency to match that used on + the board. + Valid values are 24, 27, 37.125, 72, or + 74.25MHz. + The default is 24MHz. + Note that the link frequencies permitted vary + based on the oscillator used. + link-frequency Confgures the link frequency to be used. Note + that the permitted values vary based on + clock-frequency and number of lanes. + The default is 360MHz for 720Mbit/s. + orientation Sensor orientation (0 = front, 1 = rear, + 2 = external, default external) + rotation Mounting rotation of the camera sensor (0 or + 180, default 0) + media-controller Configure use of Media Controller API for + configuring the sensor (default on) + cam0 Adopt the default configuration for CAM0 on a + Compute Module (CSI0, i2c_vc, and cam0_reg). + vcm Enable ad5398 VCM associated with the sensor. + + +Name: imx462 +Info: Sony IMX462 camera module. + Uses Unicam 1, which is the standard camera connector on most Pi + variants. +Load: dtoverlay=imx462,<param> +Params: 4lane Enable 4 CSI2 lanes. This requires a Compute + Module (1, 3, or 4). + clock-frequency Sets the clock frequency to match that used on + the board. + Modules from Vision Components use 37.125MHz + (the default), whilst those from Innomaker use + 74.25MHz. + mono Denote that the module is a mono sensor. + orientation Sensor orientation (0 = front, 1 = rear, + 2 = external, default external) + rotation Mounting rotation of the camera sensor (0 or + 180, default 0) + media-controller Configure use of Media Controller API for + configuring the sensor (default on) + cam0 Adopt the default configuration for CAM0 on a + Compute Module (CSI0, i2c_vc, and cam0_reg). + + +Name: imx477 +Info: Sony IMX477 camera module. + Uses Unicam 1, which is the standard camera connector on most Pi + variants. +Load: dtoverlay=imx477,<param>=<val> +Params: rotation Mounting rotation of the camera sensor (0 or + 180, default 180) + orientation Sensor orientation (0 = front, 1 = rear, + 2 = external, default external) + media-controller Configure use of Media Controller API for + configuring the sensor (default on) + cam0 Adopt the default configuration for CAM0 on a + Compute Module (CSI0, i2c_vc, and cam0_reg). + always-on Leave the regulator powered up, to stop the + camera clamping I/Os such as XVS to 0V. + sync-source Configure as vsync source + sync-sink Configure as vsync sink + link-frequency Allowable link frequency values to use in Hz: + 450000000 (default), 453000000, 456000000. + + +Name: imx500 +Info: Sony IMX500 camera module. + Uses Unicam 1, which is the standard camera connector on most Pi + variants. +Load: dtoverlay=imx500,<param>=<val> +Params: rotation Mounting rotation of the camera sensor (0 or + 180, default 0) + orientation Sensor orientation (0 = front, 1 = rear, + 2 = external, default external) + media-controller Configure use of Media Controller API for + configuring the sensor (default on) + cam0 Adopt the default configuration for CAM0 on a + Compute Module (CSI0, i2c_vc, and cam0_reg). + bypass-cache Do save blocks of data to flash when using + rp2040-gpio-bridge for SPI transfers. + + +Name: imx500-pi5 +Info: See imx500 (this is the Pi 5 version) + + +Name: imx519 +Info: Sony IMX519 camera module. + Uses Unicam 1, which is the standard camera connector on most Pi + variants. +Load: dtoverlay=imx519,<param>=<val> +Params: rotation Mounting rotation of the camera sensor (0 or + 180, default 0) + orientation Sensor orientation (0 = front, 1 = rear, + 2 = external, default external) + media-controller Configure use of Media Controller API for + configuring the sensor (default on) + cam0 Adopt the default configuration for CAM0 on a + Compute Module (CSI0, i2c_vc, and cam0_reg). + vcm Select lens driver state. Default is enabled, + but vcm=off will disable. + + +Name: imx708 +Info: Sony IMX708 camera module. + Uses Unicam 1, which is the standard camera connector on most Pi + variants. +Load: dtoverlay=imx708,<param>=<val> +Params: rotation Mounting rotation of the camera sensor (0 or + 180, default 180) + orientation Sensor orientation (0 = front, 1 = rear, + 2 = external, default external) + vcm Select lens driver state. Default is enabled, + but vcm=off will disable. + media-controller Configure use of Media Controller API for + configuring the sensor (default on) + cam0 Adopt the default configuration for CAM0 on a + Compute Module (CSI0, i2c_vc, and cam0_reg). + link-frequency Allowable link frequency values to use in Hz: + 450000000 (default), 447000000, 453000000. + + +Name: interludeaudio-analog +Info: Configures Interlude Audio Analog Hat audio card +Load: dtoverlay=interludeaudio-analog,<param>=<val> +Params: gpiopin GPIO pin for codec reset + + +Name: interludeaudio-digital +Info: Configures Interlude Audio Digital Hat audio card +Load: dtoverlay=interludeaudio-digital +Params: <None> + + +Name: iqaudio-codec +Info: Configures the IQaudio Codec audio card +Load: dtoverlay=iqaudio-codec +Params: <None> + + +Name: iqaudio-dac +Info: Configures the IQaudio DAC audio card +Load: dtoverlay=iqaudio-dac,<param> +Params: 24db_digital_gain Allow gain to be applied via the PCM512x codec + Digital volume control. Enable with + "dtoverlay=iqaudio-dac,24db_digital_gain" + (The default behaviour is that the Digital + volume control is limited to a maximum of + 0dB. ie. it can attenuate but not provide + gain. For most users, this will be desired + as it will prevent clipping. By appending + the 24db_digital_gain parameter, the Digital + volume control will allow up to 24dB of + gain. If this parameter is enabled, it is the + responsibility of the user to ensure that + the Digital volume control is set to a value + that does not result in clipping/distortion!) + + +Name: iqaudio-dacplus +Info: Configures the IQaudio DAC+ audio card +Load: dtoverlay=iqaudio-dacplus,<param>=<val> +Params: 24db_digital_gain Allow gain to be applied via the PCM512x codec + Digital volume control. Enable with + "dtoverlay=iqaudio-dacplus,24db_digital_gain" + (The default behaviour is that the Digital + volume control is limited to a maximum of + 0dB. ie. it can attenuate but not provide + gain. For most users, this will be desired + as it will prevent clipping. By appending + the 24db_digital_gain parameter, the Digital + volume control will allow up to 24dB of + gain. If this parameter is enabled, it is the + responsibility of the user to ensure that + the Digital volume control is set to a value + that does not result in clipping/distortion!) + auto_mute_amp If specified, unmute/mute the IQaudIO amp when + starting/stopping audio playback. + unmute_amp If specified, unmute the IQaudIO amp once when + the DAC driver module loads. + + +Name: iqaudio-digi-wm8804-audio +Info: Configures the IQAudIO Digi WM8804 audio card +Load: dtoverlay=iqaudio-digi-wm8804-audio,<param>=<val> +Params: card_name Override the default, "IQAudIODigi", card name. + dai_name Override the default, "IQAudIO Digi", dai name. + dai_stream_name Override the default, "IQAudIO Digi HiFi", + dai stream name. + + +Name: iqs550 +Info: Enables I2C connected Azoteq IQS550 trackpad/touchscreen controller + using GPIO 4 (pin 7 on GPIO header) for interrupt. +Load: dtoverlay=iqs550,<param>=<val> +Params: interrupt GPIO used for interrupt (default 4) + reset GPIO used for reset (optional) + sizex Touchscreen size x (default 800) + sizey Touchscreen size y (default 480) + invx Touchscreen inverted x axis + invy Touchscreen inverted y axis + swapxy Touchscreen swapped x y axis + + +Name: irs1125 +Info: Infineon irs1125 TOF camera module. + Uses Unicam 1, which is the standard camera connector on most Pi + variants. +Load: dtoverlay=irs1125,<param>=<val> +Params: media-controller Configure use of Media Controller API for + configuring the sensor (default off) + cam0 Adopt the default configuration for CAM0 on a + Compute Module (CSI0, i2c_vc, and cam0_reg). + + +Name: jedec-spi-nor +Info: Adds support for JEDEC-compliant SPI NOR flash devices. (Note: The + "jedec,spi-nor" kernel driver was formerly known as "m25p80".) +Load: dtoverlay=jedec-spi-nor,<param>=<val> +Params: spi<n>-<m> Enable flash device on SPI<n>, CS#<m> + fastr Add fast read capability to the flash device + speed Maximum SPI frequency (Hz) + flash-spi<n>-<m> Same as spi<n>-<m> (deprecated) + flash-fastr-spi<n>-<m> Same as spi<n>->m>,fastr (deprecated) + + +Name: justboom-both +Info: Simultaneous usage of an justboom-dac and justboom-digi based + card +Load: dtoverlay=justboom-both,<param>=<val> +Params: 24db_digital_gain Allow gain to be applied via the PCM512x codec + Digital volume control. Enable with + "dtoverlay=justboom-dac,24db_digital_gain" + (The default behaviour is that the Digital + volume control is limited to a maximum of + 0dB. ie. it can attenuate but not provide + gain. For most users, this will be desired + as it will prevent clipping. By appending + the 24dB_digital_gain parameter, the Digital + volume control will allow up to 24dB of + gain. If this parameter is enabled, it is the + responsibility of the user to ensure that + the Digital volume control is set to a value + that does not result in clipping/distortion!) + + +Name: justboom-dac +Info: Configures the JustBoom DAC HAT, Amp HAT, DAC Zero and Amp Zero audio + cards +Load: dtoverlay=justboom-dac,<param>=<val> +Params: 24db_digital_gain Allow gain to be applied via the PCM512x codec + Digital volume control. Enable with + "dtoverlay=justboom-dac,24db_digital_gain" + (The default behaviour is that the Digital + volume control is limited to a maximum of + 0dB. ie. it can attenuate but not provide + gain. For most users, this will be desired + as it will prevent clipping. By appending + the 24dB_digital_gain parameter, the Digital + volume control will allow up to 24dB of + gain. If this parameter is enabled, it is the + responsibility of the user to ensure that + the Digital volume control is set to a value + that does not result in clipping/distortion!) + + +Name: justboom-digi +Info: Configures the JustBoom Digi HAT and Digi Zero audio cards +Load: dtoverlay=justboom-digi +Params: <None> + + +Name: lirc-rpi +Info: This overlay has been deprecated and removed - see gpio-ir +Load: <Deprecated> + + +Name: ltc294x +Info: Adds support for the ltc294x family of battery gauges +Load: dtoverlay=ltc294x,<param>=<val> +Params: ltc2941 Select the ltc2941 device + + ltc2942 Select the ltc2942 device + + ltc2943 Select the ltc2943 device + + ltc2944 Select the ltc2944 device + + resistor-sense The sense resistor value in milli-ohms. + Can be a 32-bit negative value when the battery + has been connected to the wrong end of the + resistor. + + prescaler-exponent Range and accuracy of the gauge. The value is + programmed into the chip only if it differs + from the current setting. + For LTC2941 only: + - Default value is 128 + - the exponent is in the range 0-7 (default 7) + See the datasheet for more information. + + +Name: max98357a +Info: Configures the Maxim MAX98357A I2S DAC +Load: dtoverlay=max98357a,<param>=<val> +Params: no-sdmode Driver does not manage the state of the DAC's + SD_MODE pin (i.e. chip is always on). + sdmode-pin integer, GPIO pin connected to the SD_MODE input + of the DAC (default GPIO4 if parameter omitted). + + +Name: maxtherm +Info: Configure a MAX6675, MAX31855 or MAX31856 thermocouple as an IIO device. + + For devices on spi1 or spi2, the interfaces should be enabled + with one of the spi1-1/2/3cs and/or spi2-1/2/3cs overlays. + The overlay expects to disable the relevant spidev node, so also using + e.g. cs0_spidev=off is unnecessary. + + Example: + MAX31855 on /dev/spidev0.0 + dtoverlay=maxtherm,spi0-0,max31855 + MAX31856 using a type J thermocouple on /dev/spidev2.1 + dtoverlay=spi2-2cs + dtoverlay=maxtherm,spi2-1,max31856,type_j + +Load: dtoverlay=maxtherm,<param>=<val> +Params: spi<n>-<m> Configure device at spi<n>, cs<m> + (boolean, required) + max6675 Enable support for the MAX6675 (default) + max31855 Enable support for the MAX31855 + max31855e Enable support for the MAX31855E + max31855j Enable support for the MAX31855J + max31855k Enable support for the MAX31855K + max31855n Enable support for the MAX31855N + max31855r Enable support for the MAX31855R + max31855s Enable support for the MAX31855S + max31855t Enable support for the MAX31855T + max31856 Enable support for the MAX31856 (with type K) + type_b Select a type B sensor for max31856 + type_e Select a type E sensor for max31856 + type_j Select a type J sensor for max31856 + type_k Select a type K sensor for max31856 + type_n Select a type N sensor for max31856 + type_r Select a type R sensor for max31856 + type_s Select a type S sensor for max31856 + type_t Select a type T sensor for max31856 + + +Name: mbed-dac +Info: Configures the mbed AudioCODEC (TLV320AIC23B) +Load: dtoverlay=mbed-dac +Params: <None> + + +Name: mcp23017 +Info: Configures the MCP23017 I2C GPIO expander +Load: dtoverlay=mcp23017,<param>=<val> +Params: gpiopin Gpio pin connected to the INTA output of the + MCP23017 (default: 4) + + addr I2C address of the MCP23017 (default: 0x20) + + i2c-bus Supports all the standard I2C bus selection + parameters - see "dtoverlay -h i2c-bus" + + mcp23008 Configure an MCP23008 instead. + noints Disable the interrupt GPIO line. + + +Name: mcp23s17 +Info: Configures the MCP23S08/17 SPI GPIO expanders. + If devices are present on SPI1 or SPI2, those interfaces must be enabled + with one of the spi1-1/2/3cs and/or spi2-1/2/3cs overlays. + If interrupts are enabled for a device on a given CS# on a SPI bus, that + device must be the only one present on that SPI bus/CS#. +Load: dtoverlay=mcp23s17,<param>=<val> +Params: s08-spi<n>-<m>-present 4-bit integer, bitmap indicating MCP23S08 + devices present on SPI<n>, CS#<m> + + s17-spi<n>-<m>-present 8-bit integer, bitmap indicating MCP23S17 + devices present on SPI<n>, CS#<m> + + s08-spi<n>-<m>-int-gpio integer, enables interrupts on a single + MCP23S08 device on SPI<n>, CS#<m>, specifies + the GPIO pin to which INT output of MCP23S08 + is connected. + + s17-spi<n>-<m>-int-gpio integer, enables mirrored interrupts on a + single MCP23S17 device on SPI<n>, CS#<m>, + specifies the GPIO pin to which either INTA + or INTB output of MCP23S17 is connected. + + +Name: mcp2515 +Info: Configures the MCP2515 CAN controller on spi0/1/2 + For devices on spi1 or spi2, the interfaces should be enabled + with one of the spi1-1/2/3cs and/or spi2-1/2/3cs overlays. +Load: dtoverlay=mcp2515,<param>=<val> +Params: spi<n>-<m> Configure device at spi<n>, cs<m> + (boolean, required) + + oscillator Clock frequency for the CAN controller (Hz) + + speed Maximum SPI frequence (Hz) + + interrupt GPIO for interrupt signal + + +Name: mcp2515-can0 +Info: Configures the MCP2515 CAN controller on spi0.0 +Load: dtoverlay=mcp2515-can0,<param>=<val> +Params: oscillator Clock frequency for the CAN controller (Hz) + + spimaxfrequency Maximum SPI frequence (Hz) + + interrupt GPIO for interrupt signal + + +Name: mcp2515-can1 +Info: Configures the MCP2515 CAN controller on spi0.1 +Load: dtoverlay=mcp2515-can1,<param>=<val> +Params: oscillator Clock frequency for the CAN controller (Hz) + + spimaxfrequency Maximum SPI frequence (Hz) + + interrupt GPIO for interrupt signal + + +Name: mcp251xfd +Info: Configures the MCP251XFD CAN controller family + For devices on spi1 or spi2, the interfaces should be enabled + with one of the spi1-1/2/3cs and/or spi2-1/2/3cs overlays. +Load: dtoverlay=mcp251xfd,<param>=<val> +Params: spi<n>-<m> Configure device at spi<n>, cs<m> + (boolean, required) + + oscillator Clock frequency for the CAN controller (Hz) + + speed Maximum SPI frequence (Hz) + + interrupt GPIO for interrupt signal + + rx_interrupt GPIO for RX interrupt signal (nINT1) (optional) + + xceiver_enable GPIO for CAN transceiver enable (optional) + + xceiver_active_high specifiy if CAN transceiver enable pin is + active high (optional, default: active low) + + +Name: mcp3008 +Info: Configures MCP3008 A/D converters + For devices on spi1 or spi2, the interfaces should be enabled + with one of the spi1-1/2/3cs and/or spi2-1/2/3cs overlays. +Load: dtoverlay=mcp3008,<param>[=<val>] +Params: spi<n>-<m>-present boolean, configure device at spi<n>, cs<m> + spi<n>-<m>-speed integer, set the spi bus speed for this device + + +Name: mcp3202 +Info: Configures MCP3202 A/D converters + For devices on spi1 or spi2, the interfaces should be enabled + with one of the spi1-1/2/3cs and/or spi2-1/2/3cs overlays. +Load: dtoverlay=mcp3202,<param>[=<val>] +Params: spi<n>-<m>-present boolean, configure device at spi<n>, cs<m> + spi<n>-<m>-speed integer, set the spi bus speed for this device + + +Name: mcp342x +Info: Overlay for activation of Microchip MCP3421-3428 ADCs over I2C +Load: dtoverlay=mcp342x,<param>=<val> +Params: addr I2C bus address of device, for devices with + addresses that are configurable, e.g. by + hardware links (default=0x68) + mcp3421 The device is an MCP3421 + mcp3422 The device is an MCP3422 + mcp3423 The device is an MCP3423 + mcp3424 The device is an MCP3424 + mcp3425 The device is an MCP3425 + mcp3426 The device is an MCP3426 + mcp3427 The device is an MCP3427 + mcp3428 The device is an MCP3428 + + +Name: media-center +Info: Media Center HAT - 2.83" Touch Display + extras by Pi Supply +Load: dtoverlay=media-center,<param>=<val> +Params: speed Display SPI bus speed + rotate Display rotation {0,90,180,270} + fps Delay between frame updates + xohms Touchpanel sensitivity (X-plate resistance) + swapxy Swap x and y axis + backlight Change backlight GPIO pin {e.g. 12, 18} + debug "on" = enable additional debug messages + (default "off") + + +Name: merus-amp +Info: Configures the merus-amp audio card +Load: dtoverlay=merus-amp +Params: <None> + + +Name: midi-uart0 +Info: Configures UART0 (ttyAMA0) so that a requested 38.4kbaud actually gets + 31.25kbaud, the frequency required for MIDI +Load: dtoverlay=midi-uart0 +Params: <None> + + +Name: midi-uart0-pi5 +Info: See midi-uart0 (this is the Pi 5 version) + + +Name: midi-uart1 +Info: Configures UART1 (ttyS0) so that a requested 38.4kbaud actually gets + 31.25kbaud, the frequency required for MIDI +Load: dtoverlay=midi-uart1 +Params: <None> + + +Name: midi-uart1-pi5 +Info: See midi-uart1 (this is the Pi 5 version) + + +Name: midi-uart2 +Info: Configures UART2 (ttyAMA2) so that a requested 38.4kbaud actually gets + 31.25kbaud, the frequency required for MIDI +Load: dtoverlay=midi-uart2 +Params: <None> + + +Name: midi-uart2-pi5 +Info: See midi-uart2 (this is the Pi 5 version) + + +Name: midi-uart3 +Info: Configures UART3 (ttyAMA3) so that a requested 38.4kbaud actually gets + 31.25kbaud, the frequency required for MIDI +Load: dtoverlay=midi-uart3 +Params: <None> + + +Name: midi-uart3-pi5 +Info: See midi-uart3 (this is the Pi 5 version) + + +Name: midi-uart4 +Info: Configures UART4 (ttyAMA4) so that a requested 38.4kbaud actually gets + 31.25kbaud, the frequency required for MIDI +Load: dtoverlay=midi-uart4 +Params: <None> + + +Name: midi-uart4-pi5 +Info: See midi-uart4 (this is the Pi 5 version) + + +Name: midi-uart5 +Info: Configures UART5 (ttyAMA5) so that a requested 38.4kbaud actually gets + 31.25kbaud, the frequency required for MIDI +Load: dtoverlay=midi-uart5 +Params: <None> + + +Name: minipitft13 +Info: Overlay for AdaFruit Mini Pi 1.3" TFT via SPI using fbtft driver. +Load: dtoverlay=minipitft13,<param>=<val> +Params: speed SPI bus speed (default 32000000) + rotate Display rotation (0, 90, 180 or 270; default 0) + width Display width (default 240) + height Display height (default 240) + fps Delay between frame updates (default 25) + debug Debug output level (0-7; default 0) + + +Name: miniuart-bt +Info: Switch the onboard Bluetooth function of a BT-equipped Raspberry Pi + to use the mini-UART (ttyS0) and restore UART0/ttyAMA0 over GPIOs 14 & + 15. Note that this option uses a lower baudrate, and should only be used + with low-bandwidth peripherals. +Load: dtoverlay=miniuart-bt,<param>=<val> +Params: krnbt Set to "off" to disable autoprobing of Bluetooth + driver without need of hciattach/btattach + + +Name: mipi-dbi-spi +Info: Overlay for SPI-connected MIPI DBI displays using the panel-mipi-dbi + driver. The driver will load a file /lib/firmware/panel.bin containing + the initialisation commands. + + Example: + dtoverlay=mipi-dbi-spi,spi0-0,speed=70000000 + dtparam=width=320,height=240 + dtparam=reset-gpio=23,dc-gpio=24 + dtparam=backlight-gpio=18 + + Compared to fbtft panel-mipi-dbi runs pixel data at spi-max-frequency + and init commands at 10MHz. This makes it possible to push the envelope + without messing up the controller configuration due to command + transmission errors. + + For devices on spi1 or spi2, the interfaces should be enabled + with one of the spi1-1/2/3cs and/or spi2-1/2/3cs overlays. + + See https://github.com/notro/panel-mipi-dbi/wiki for more info. + +Load: dtoverlay=mipi-dbi-spi,<param>=<val> +Params: + compatible Set the compatible string to load a different + firmware file. Both the panel compatible value + used to load the firmware file and the value + used to load the driver has to be set having a + NUL (\0) separator between them. + Example: + dtparam=compatible=mypanel\0panel-mipi-dbi-spi + spi<n>-<m> Configure device at spi<n>, cs<m> + (boolean, required) + speed SPI bus speed in Hz (default 32000000) + cpha Shifted SPI clock phase (CPHA) mode + cpol Inverse SPI clock polarity (CPOL) mode + write-only Controller is not readable + (ie. MISO is not wired up). + + width Panel width in pixels (required) + height Panel height in pixels (required) + width-mm Panel width in mm + height-mm Panel height in mm + x-offset Panel x-offset in controller RAM + y-offset Panel y-offset in controller RAM + + clock-frequency Panel clock frequency in Hz + (optional, just informational). + + reset-gpio GPIO pin to be used for RESET + dc-gpio GPIO pin to be used for D/C + + backlight-gpio GPIO pin to be used for backlight control + (default of none). + backlight-pwm PWM channel to be used for backlight control + (default of none). NB Disables audio headphone + output as that also uses PWM. + backlight-pwm-chan Choose channel on &pwm node for backlight + control (default 0). + backlight-pwm-gpio GPIO pin to be used for the PWM backlight. See + pwm-2chan for valid options (default 18). + backlight-pwm-func Pin function of GPIO used for the PWM backlight. + See pwm-2chan for valid options (default 2). + backlight-def-brightness + Set the default brightness. Normal range 1-16. + (default 16). + + +Name: mlx90640 +Info: Overlay for i2c connected mlx90640 thermal camera +Load: dtoverlay=mlx90640 +Params: <None> + + +Name: mmc +Info: Selects the bcm2835-mmc SD/MMC driver, optionally with overclock +Load: dtoverlay=mmc,<param>=<val> +Params: overclock_50 Clock (in MHz) to use when the MMC framework + requests 50MHz + + +Name: mpu6050 +Info: This overlay has been deprecated - use "dtoverlay=i2c-sensor,mpu6050" + instead. Note that "int_pin" is the new name for the "interrupt" + parameter. +Load: <Deprecated> + + +Name: mz61581 +Info: MZ61581 display by Tontec +Load: dtoverlay=mz61581,<param>=<val> +Params: speed Display SPI bus speed + + rotate Display rotation {0,90,180,270} + + fps Delay between frame updates + + txbuflen Transmit buffer length (default 32768) + + debug Debug output level {0-7} + + xohms Touchpanel sensitivity (X-plate resistance) + + +Name: ov2311 +Info: Omnivision OV2311 camera module. + Uses Unicam 1, which is the standard camera connector on most Pi + variants. +Load: dtoverlay=ov2311,<param>=<val> +Params: rotation Mounting rotation of the camera sensor (0 or + 180, default 0) + orientation Sensor orientation (0 = front, 1 = rear, + 2 = external, default external) + media-controller Configure use of Media Controller API for + configuring the sensor (default on) + cam0 Adopt the default configuration for CAM0 on a + Compute Module (CSI0, i2c_vc, and cam0_reg). + + +Name: ov5647 +Info: Omnivision OV5647 camera module. + Uses Unicam 1, which is the standard camera connector on most Pi + variants. +Load: dtoverlay=ov5647,<param>=<val> +Params: rotation Mounting rotation of the camera sensor (0 or + 180, default 0) + orientation Sensor orientation (0 = front, 1 = rear, + 2 = external, default external) + media-controller Configure use of Media Controller API for + configuring the sensor (default on) + cam0 Adopt the default configuration for CAM0 on a + Compute Module (CSI0, i2c_vc, and cam0_reg). + vcm Configure a VCM focus drive on the sensor. + + +Name: ov64a40 +Info: Arducam OV64A40 camera module. + Uses Unicam 1, which is the standard camera connector on most Pi + variants. +Load: dtoverlay=ov64a40,<param>=<val> +Params: rotation Mounting rotation of the camera sensor (0 or + 180, default 0) + orientation Sensor orientation (0 = front, 1 = rear, + 2 = external, default external) + media-controller Configure use of Media Controller API for + configuring the sensor (default on) + cam0 Adopt the default configuration for CAM0 on a + Compute Module (CSI0, i2c_vc, and cam0_reg). + vcm Select lens driver state. Default is enabled, + but vcm=off will disable. + link-frequency Allowable link frequency values to use in Hz: + 456000000 (default), 360000000 + + +Name: ov7251 +Info: Omnivision OV7251 camera module. + Uses Unicam 1, which is the standard camera connector on most Pi + variants. +Load: dtoverlay=ov7251,<param>=<val> +Params: rotation Mounting rotation of the camera sensor (0 or + 180, default 0) + orientation Sensor orientation (0 = front, 1 = rear, + 2 = external, default external) + media-controller Configure use of Media Controller API for + configuring the sensor (default off) + cam0 Adopt the default configuration for CAM0 on a + Compute Module (CSI0, i2c_vc, and cam0_reg). + + +Name: ov9281 +Info: Omnivision OV9281 camera module. + Uses Unicam 1, which is the standard camera connector on most Pi + variants. +Load: dtoverlay=ov9281,<param>=<val> +Params: rotation Mounting rotation of the camera sensor (0 or + 180, default 0) + orientation Sensor orientation (0 = front, 1 = rear, + 2 = external, default external) + media-controller Configure use of Media Controller API for + configuring the sensor (default on) + cam0 Adopt the default configuration for CAM0 on a + Compute Module (CSI0, i2c_vc, and cam0_reg). + arducam Slow down the regulator for slow Arducam + modules. + + +Name: papirus +Info: PaPiRus ePaper Screen by Pi Supply (both HAT and pHAT) +Load: dtoverlay=papirus,<param>=<val> +Params: panel Display panel (required): + 1.44": e1144cs021 + 2.0": e2200cs021 + 2.7": e2271cs021 + + speed Display SPI bus speed + + +Name: pca953x +Info: TI PCA953x family of I2C GPIO expanders. Default is for NXP PCA9534. +Load: dtoverlay=pca953x,<param>=<val> +Params: addr I2C address of expander. Default 0x20. + i2c-bus Supports all the standard I2C bus selection + parameters - see "dtoverlay -h i2c-bus" + pca6416 Select the NXP PCA6416 (16 bit) + pca9505 Select the NXP PCA9505 (40 bit) + pca9535 Select the NXP PCA9535 (16 bit) + pca9536 Select the NXP PCA9536 or TI PCA9536 (4 bit) + pca9537 Select the NXP PCA9537 (4 bit) + pca9538 Select the NXP PCA9538 (8 bit) + pca9539 Select the NXP PCA9539 (16 bit) + pca9554 Select the NXP PCA9554 (8 bit) + pca9555 Select the NXP PCA9555 (16 bit) + pca9556 Select the NXP PCA9556 (8 bit) + pca9557 Select the NXP PCA9557 (8 bit) + pca9574 Select the NXP PCA9574 (8 bit) + pca9575 Select the NXP PCA9575 (16 bit) + pca9698 Select the NXP PCA9698 (40 bit) + pcal6416 Select the NXP PCAL6416 (16 bit) + pcal6524 Select the NXP PCAL6524 (24 bit) + pcal9555a Select the NXP PCAL9555A (16 bit) + max7310 Select the Maxim MAX7310 (8 bit) + max7312 Select the Maxim MAX7312 (16 bit) + max7313 Select the Maxim MAX7313 (16 bit) + max7315 Select the Maxim MAX7315 (8 bit) + pca6107 Select the TI PCA6107 (8 bit) + tca6408 Select the TI TCA6408 (8 bit) + tca6416 Select the TI TCA6416 (16 bit) + tca6424 Select the TI TCA6424 (24 bit) + tca9539 Select the TI TCA9539 (16 bit) + tca9554 Select the TI TCA9554 (8 bit) + cat9554 Select the Onnn CAT9554 (8 bit) + pca9654 Select the Onnn PCA9654 (8 bit) + xra1202 Select the Exar XRA1202 (8 bit) + + +Name: pcf857x +Info: NXP PCF857x family of I2C GPIO expanders. +Load: dtoverlay=pcf857x,<param>=<val> +Params: addr I2C address of expander. Default + depends on model selected. + i2c-bus Supports all the standard I2C bus selection + parameters - see "dtoverlay -h i2c-bus" + pcf8574 Select the NXP PCF8574 (8 bit) + pcf8574a Select the NXP PCF8574A (8 bit) + pcf8575 Select the NXP PCF8575 (16 bit) + pca8574 Select the NXP PCA8574 (8 bit) + + +Name: pcie-32bit-dma +Info: Force PCIe config to support 32bit DMA addresses at the expense of + having to bounce buffers. +Load: dtoverlay=pcie-32bit-dma +Params: <None> + + +Name: pcie-32bit-dma-pi5 +Info: Force PCIe config to support 32bit DMA addresses at the expense of + having to bounce buffers (on the Pi 5). +Load: dtoverlay=pcie-32bit-dma-pi5 +Params: <None> + + +Name: pciex1-compat-pi5 +Info: Compatibility features for pciex1 on Pi 5. +Load: dtoverlay=pciex1-compat-pi5,<param>=<val> +Params: l1ss Enable ASPM L1 sub-state support + no-l0s Disable ASPM L0s + no-mip Revert to the MSI target in the RC, instead of + the MSI-MIP peripheral. Use if a) more than 8 + interrupt vectors are required or b) the EP + requires DMA and MSI addresses to be 32bit. + mmio-hi Move the start of outbound 32bit addresses to + 2GB and expand 64bit outbound space to 14GB. + + +[ The pcf2127-rtc overlay has been deleted. See i2c-rtc. ] + + +[ The pcf8523-rtc overlay has been deleted. See i2c-rtc. ] + + +[ The pcf8563-rtc overlay has been deleted. See i2c-rtc. ] + + +Name: pi3-act-led +Info: This overlay has been renamed act-led, keeping pi3-act-led as an alias + for backwards compatibility. +Load: <Deprecated> + + +Name: pi3-disable-bt +Info: This overlay has been renamed disable-bt, keeping pi3-disable-bt as an + alias for backwards compatibility. +Load: <Deprecated> + + +Name: pi3-disable-wifi +Info: This overlay has been renamed disable-wifi, keeping pi3-disable-wifi as + an alias for backwards compatibility. +Load: <Deprecated> + + +Name: pi3-miniuart-bt +Info: This overlay has been renamed miniuart-bt, keeping pi3-miniuart-bt as + an alias for backwards compatibility. +Load: <Deprecated> + + +Name: pibell +Info: Configures the pibell audio card. +Load: dtoverlay=pibell,<param>=<val> +Params: alsaname Set the name as it appears in ALSA (default + "PiBell") + + +Name: pifacedigital +Info: Configures the PiFace Digital mcp23s17 GPIO port expander. +Load: dtoverlay=pifacedigital,<param>=<val> +Params: spi-present-mask 8-bit integer, bitmap indicating MCP23S17 SPI0 + CS0 address. PiFace Digital supports addresses + 0-3, which can be configured with JP1 and JP2. + + +Name: pifi-40 +Info: Configures the PiFi 40W stereo amplifier +Load: dtoverlay=pifi-40 +Params: <None> + + +Name: pifi-dac-hd +Info: Configures the PiFi DAC HD +Load: dtoverlay=pifi-dac-hd +Params: <None> + + +Name: pifi-dac-zero +Info: Configures the PiFi DAC Zero +Load: dtoverlay=pifi-dac-zero +Params: <None> + + +Name: pifi-mini-210 +Info: Configures the PiFi Mini stereo amplifier +Load: dtoverlay=pifi-mini-210 +Params: <None> + + +Name: piglow +Info: Configures the PiGlow by pimoroni.com +Load: dtoverlay=piglow +Params: <None> + + +Name: pimidi +Info: Configures the Blokas Labs Pimidi card +Load: dtoverlay=pimidi,<param>=<val> +Params: sel The position used for the sel rotary switch. + Each unit in the stack must be set on a unique + position. If param is omitted, sel=0 is assumed. + + +Name: pineboards-hat-ai +Info: Pineboards Hat Ai! overlay for the Google Coral Edge TPU +Load: dtoverlay=pineboards-hat-ai +Params: <None> + + +Name: pineboards-hatdrive-poe-plus +Info: Configures the Pineboards HatDrive! PoE+ +Load: dtoverlay=pineboards-hatdrive-poe-plus +Params: <None> + + +Name: piscreen +Info: PiScreen display by OzzMaker.com +Load: dtoverlay=piscreen,<param>=<val> +Params: speed Display SPI bus speed + + rotate Display rotation {0,90,180,270} + + fps Delay between frame updates + + debug Debug output level {0-7} + + xohms Touchpanel sensitivity (X-plate resistance) + + drm Select the DRM/KMS driver instead of the FBTFT + one + + invx Touchscreen inverted x axis + + invy Touchscreen inverted y axis + + swapxy Touchscreen swapped x y axis + + +Name: piscreen2r +Info: PiScreen 2 with resistive TP display by OzzMaker.com +Load: dtoverlay=piscreen2r,<param>=<val> +Params: speed Display SPI bus speed + + rotate Display rotation {0,90,180,270} + + fps Delay between frame updates + + debug Debug output level {0-7} + + xohms Touchpanel sensitivity (X-plate resistance) + + +Name: pisound +Info: Configures the Blokas Labs Pisound card +Load: dtoverlay=pisound +Params: <None> + + +Name: pisound-pi5 +Info: Pi 5 specific overlay override for Blokas Labs Pisound card, see pisound +Load: dtoverlay=pisound-pi5 +Params: <None> + + +Name: pitft22 +Info: Adafruit PiTFT 2.2" screen +Load: dtoverlay=pitft22,<param>=<val> +Params: speed Display SPI bus speed + + rotate Display rotation {0,90,180,270} + + fps Delay between frame updates + + debug Debug output level {0-7} + + drm Force the use of the mi0283qt DRM driver (by + default the ili9340 framebuffer driver will + be used in preference if available) + + +Name: pitft28-capacitive +Info: Adafruit PiTFT 2.8" capacitive touch screen +Load: dtoverlay=pitft28-capacitive,<param>=<val> +Params: speed Display SPI bus speed + + rotate Display rotation {0,90,180,270} + + fps Delay between frame updates + + debug Debug output level {0-7} + + drm Force the use of the mi0283qt DRM driver (by + default the ili9340 framebuffer driver will + be used in preference if available) + + touch-sizex Touchscreen size x (default 240) + + touch-sizey Touchscreen size y (default 320) + + touch-invx Touchscreen inverted x axis + + touch-invy Touchscreen inverted y axis + + touch-swapxy Touchscreen swapped x y axis + + +Name: pitft28-resistive +Info: Adafruit PiTFT 2.8" resistive touch screen +Load: dtoverlay=pitft28-resistive,<param>=<val> +Params: speed Display SPI bus speed + + rotate Display rotation {0,90,180,270} + + fps Delay between frame updates + + debug Debug output level {0-7} + + drm Force the use of the mi0283qt DRM driver (by + default the ili9340 framebuffer driver will + be used in preference if available) + + touch-invx Touchscreen inverted x axis + + touch-invy Touchscreen inverted y axis + + touch-swapxy Touchscreen swapped x y axis + + +Name: pitft35-resistive +Info: Adafruit PiTFT 3.5" resistive touch screen +Load: dtoverlay=pitft35-resistive,<param>=<val> +Params: speed Display SPI bus speed + + rotate Display rotation {0,90,180,270} + + fps Delay between frame updates + + debug Debug output level {0-7} + + drm Force the use of the hx8357d DRM driver (by + default the fb_hx8357d framebuffer driver will + be used in preference if available) + + touch-invx Touchscreen inverted x axis + + touch-invy Touchscreen inverted y axis + + touch-swapxy Touchscreen swapped x y axis + + +Name: pps-gpio +Info: Configures the pps-gpio (pulse-per-second time signal via GPIO). +Load: dtoverlay=pps-gpio,<param>=<val> +Params: gpiopin Input GPIO (default "18") + assert_falling_edge When present, assert is indicated by a falling + edge, rather than by a rising edge (default + off) + capture_clear Generate clear events on the trailing edge + (default off) + pull Desired pull-up/down state (off, down, up) + Default is "off". + + +Name: proto-codec +Info: Configures the PROTO Audio Codec card +Load: dtoverlay=proto-codec +Params: <None> + + +Name: pwm +Info: Configures a single PWM channel + Legal pin,function combinations for each channel: + PWM0: 12,4(Alt0) 18,2(Alt5) 40,4(Alt0) 52,5(Alt1) + PWM1: 13,4(Alt0) 19,2(Alt5) 41,4(Alt0) 45,4(Alt0) 53,5(Alt1) + N.B.: + 1) Pin 18 is the only one available on all platforms, and + it is the one used by the I2S audio interface. + Pins 12 and 13 might be better choices on an A+, B+ or Pi2. + 2) The onboard analogue audio output uses both PWM channels. + 3) So be careful mixing audio and PWM. + 4) Currently the clock must have been enabled and configured + by other means. +Load: dtoverlay=pwm,<param>=<val> +Params: pin Output pin (default 18) - see table + func Pin function (default 2 = Alt5) - see above + clock PWM clock frequency (informational) + + +Name: pwm-2chan +Info: Configures both PWM channels + Legal pin,function combinations for each channel: + PWM0: 12,4(Alt0) 18,2(Alt5) 40,4(Alt0) 52,5(Alt1) + PWM1: 13,4(Alt0) 19,2(Alt5) 41,4(Alt0) 45,4(Alt0) 53,5(Alt1) + N.B.: + 1) Pin 18 is the only one available on all platforms, and + it is the one used by the I2S audio interface. + Pins 12 and 13 might be better choices on an A+, B+ or Pi2. + 2) The onboard analogue audio output uses both PWM channels. + 3) So be careful mixing audio and PWM. + 4) Currently the clock must have been enabled and configured + by other means. +Load: dtoverlay=pwm-2chan,<param>=<val> +Params: pin Output pin (default 18) - see table + pin2 Output pin for other channel (default 19) + func Pin function (default 2 = Alt5) - see above + func2 Function for pin2 (default 2 = Alt5) + clock PWM clock frequency (informational) + + +Name: pwm-gpio +Info: Configures the software PWM GPIO driver +Load: dtoverlay=pwm-gpio,<param>=<val> +Params: gpio Output pin (default 4) + + +Name: pwm-gpio-fan +Info: Configure a GPIO connected PWM cooling fan controlled by the + software-based GPIO PWM kernel module +Load: dtoverlay=pwm-gpio-fan,<param>=<val> +Params: fan_gpio BCM number of the pin driving the fan, + default 18 (GPIO 18) + fan_temp0 CPU temperature at which fan is started with + low speed in millicelsius, + default 55000 (55 °C) + fan_temp1 CPU temperature at which fan is switched + to medium speed in millicelsius, + default 60000 (60 °C) + fan_temp2 CPU temperature at which fan is switched + to high speed in millicelsius, + default 67500 (67.5 °C) + fan_temp3 CPU temperature at which fan is switched + to max speed in millicelsius, + default 75000 (75 °C) + fan_temp0_hyst Temperature hysteris at which fan is stopped + in millicelsius,default 5000 (resulting + in 50 °C) + fan_temp1_hyst Temperature hysteris at which fan is switched + back to low speed in millicelsius, + default 5000 (resulting in 55 °C) + fan_temp2_hyst Temperature hysteris at which fan is switched + back to medium speed in millicelsius, + default 5000 (resulting in 62.5 °C) + fan_temp3_hyst Temperature hysteris at which fan is switched + back to high speed in millicelsius, + default 5000 (resulting in 70 °C) + fan_temp0_speed Fan speed for low cooling state in range + 0 to 255, default 114 (45% PWM duty cycle) + fan_temp1_speed Fan speed for medium cooling state in range + 0 to 255, default 152 (60% PWM duty cycle) + fan_temp2_speed Fan speed for high cooling state in range + 0 to 255, default 204 (80% PWM duty cycle) + fan_temp3_speed Fan speed for max cooling state in range + 0 to 255, default 255 (100% PWM duty cycle) + + +Name: pwm-ir-tx +Info: Use GPIO pin as pwm-assisted infrared transmitter output. + This is an alternative to "gpio-ir-tx". pwm-ir-tx makes use + of PWM0 to reduce the CPU load during transmission compared to + gpio-ir-tx which uses bit-banging. + Legal pin,function combinations are: + 12,4(Alt0) 18,2(Alt5) 40,4(Alt0) 52,5(Alt1) +Load: dtoverlay=pwm-ir-tx,<param>=<val> +Params: gpio_pin Output GPIO (default 18) + + func Pin function (default 2 = Alt5) + + +Name: pwm-pio +Info: Configures a GPIO pin as PIO-assisted PWM output. Unlike hardware PWM, + this can be used on any RP1 GPIO in bank 0 (0-27). Up to 4 are + supported, assuming nothing else is using PIO. Pi 5 only. +Load: dtoverlay=pwm-pio,<param>=<val> +Params: gpio Output GPIO (0-27, default 4) + + +Name: pwm1 +Info: Configures one or two PWM channel on PWM1 (BCM2711 only) + N.B.: + 1) The onboard analogue audio output uses both PWM channels. + 2) So be careful mixing audio and PWM. + Note that even when only one pin is enabled, both channels are available + from the PWM driver, so be careful to use the correct one. +Load: dtoverlay=pwm1,<param>=<val> +Params: clock PWM clock frequency (informational) + pins_40 Enable channel 0 (PWM1_0) on GPIO 40 + pins_41 Enable channel 1 (PWM1_1) on GPIO 41 + pins_40_41 Enable channels 0 (PWM1_0) and 1 (PW1_1) on + GPIOs 40 and 41 (default) + pull_up Enable pull-ups on the PWM pins (default) + pull_down Enable pull-downs on the PWM pins + pull_off Disable pulls on the PWM pins + + +Name: qca7000 +Info: Evaluation Board for PLC Stamp micro + This uses spi0 and a separate GPIO interrupt to connect the QCA7000. +Load: dtoverlay=qca7000,<param>=<val> +Params: int_pin GPIO pin for interrupt signal (default 23) + + speed SPI bus speed (default 12 MHz) + + +Name: qca7000-uart0 +Info: Evaluation Board for PLC Stamp micro (UART) + This uses uart0/ttyAMA0 over GPIOs 14 & 15 to connect the QCA7000. + But it requires disabling of onboard Bluetooth on + Pi 3B, 3B+, 3A+, 4B and Zero W. +Load: dtoverlay=qca7000-uart0,<param>=<val> +Params: baudrate Set the baudrate for the UART (default + "115200") + + +Name: ramoops +Info: Enable the preservation of crash logs across a reboot. With + systemd-pstore enabled (as it is on Raspberry Pi OS) the crash logs + are moved to /var/lib/systemd/pstore/ on reboot. +Load: dtoverlay=ramoops,<param>=<val> +Params: base-addr Where to place the capture buffer (default + 0x0b000000) + total-size How much memory to allocate altogether (in + bytes - default 64kB) + record-size How much space to use for each capture, i.e. + total-size / record-size = number of captures + (default 16kB) + console-size Size of non-panic dmesg captures (default 0) + + +Name: ramoops-pi4 +Info: The version of the ramoops overlay for the Pi 4 family. It should be + loaded automatically if dtoverlay=ramoops is specified on a Pi 4. +Load: dtoverlay=ramoops-pi4,<param>=<val> +Params: base-addr Where to place the capture buffer (default + 0x0b000000) + total-size How much memory to allocate altogether (in + bytes - default 64kB) + record-size How much space to use for each capture, i.e. + total-size / record-size = number of captures + (default 16kB) + console-size Size of non-panic dmesg captures (default 0) + + +Name: rootmaster +Info: Overlay for OpenHydroponics RootMaster board. + https://openhydroponics.com/hw/rootmaster +Load: dtoverlay=rootmaster +Params: <None> + + +Name: rotary-encoder +Info: Overlay for GPIO connected rotary encoder. +Load: dtoverlay=rotary-encoder,<param>=<val> +Params: pin_a GPIO connected to rotary encoder channel A + (default 4). + pin_b GPIO connected to rotary encoder channel B + (default 17). + relative_axis register a relative axis rather than an + absolute one. Relative axis will only + generate +1/-1 events on the input device, + hence no steps need to be passed. + linux_axis the input subsystem axis to map to this + rotary encoder. Defaults to 0 (ABS_X / REL_X) + rollover Automatic rollover when the rotary value + becomes greater than the specified steps or + smaller than 0. For absolute axis only. + steps-per-period Number of steps (stable states) per period. + The values have the following meaning: + 1: Full-period mode (default) + 2: Half-period mode + 4: Quarter-period mode + steps Number of steps in a full turnaround of the + encoder. Only relevant for absolute axis. + Defaults to 24 which is a typical value for + such devices. + wakeup Boolean, rotary encoder can wake up the + system. + encoding String, the method used to encode steps. + Supported are "gray" (the default and more + common) and "binary". + + +Name: rpi-backlight +Info: Raspberry Pi official display backlight driver +Load: dtoverlay=rpi-backlight +Params: <None> + + +Name: rpi-cirrus-wm5102 +Info: This overlay has been renamed to cirrus-wm5102 +Load: <Deprecated> + + +Name: rpi-codeczero +Info: Configures the Raspberry Pi Codec Zero sound card +Load: dtoverlay=rpi-codeczero +Params: <None> + + +Name: rpi-dac +Info: This overlay has been renamed to i2s-dac. +Load: <Deprecated> + + +Name: rpi-dacplus +Info: Configures the Raspberry Pi DAC+ card +Load: dtoverlay=rpi-dacplus,<param>=<val> +Params: 24db_digital_gain Allow gain to be applied via the PCM512x codec + digital volume control. Enable by adding + "dtparam=24db_digital_gain" to config.txt + before any "dtoverlay" lines. + The default behaviour is that the digital + volume control is limited to a maximum of + 0dB. ie. it can attenuate but not provide + gain. For most users, this will be desired + as it will prevent clipping. By appending + the 24db_digital_gain parameter, the digital + volume control will allow up to 24dB of + gain. If this parameter is enabled, it is the + responsibility of the user to ensure that + the digital volume control is set to a value + that does not result in clipping/distortion! + + +Name: rpi-dacpro +Info: Configures the Raspberry Pi DAC Pro sound card +Load: dtoverlay=rpi-dacpro,<param>=<val> +Params: 24db_digital_gain Allow gain to be applied via the PCM512x codec + digital volume control. Enable by adding + "dtparam=24db_digital_gain" to config.txt + before any "dtoverlay" lines. + The default behaviour is that the digital + volume control is limited to a maximum of + 0dB. ie. it can attenuate but not provide + gain. For most users, this will be desired + as it will prevent clipping. By appending + the 24db_digital_gain parameter, the digital + volume control will allow up to 24dB of + gain. If this parameter is enabled, it is the + responsibility of the user to ensure that + the digital volume control is set to a value + that does not result in clipping/distortion! + + +Name: rpi-digiampplus +Info: Configures the Raspberry Pi DigiAMP+ sound card +Load: dtoverlay=rpi-digiampplus,<param>=<val> +Params: 24db_digital_gain Allow gain to be applied via the PCM512x codec + digital volume control. Enable by adding + "dtparam=24db_digital_gain" to config.txt + before any "dtoverlay" lines. + The default behaviour is that the digital + volume control is limited to a maximum of + 0dB. ie. it can attenuate but not provide + gain. For most users, this will be desired + as it will prevent clipping. By appending + the 24db_digital_gain parameter, the digital + volume control will allow up to 24dB of + gain. If this parameter is enabled, it is the + responsibility of the user to ensure that + the digital volume control is set to a value + that does not result in clipping/distortion! + auto_mute_amp If specified, unmute/mute the DigiAMP+ when + starting/stopping audio playback (default "on"). + unmute_amp If specified, unmute the DigiAMP+ amp once when + the DAC driver module loads (default "off"). + + +Name: rpi-display +Info: This overlay has been renamed to watterott-display +Load: <Deprecated> + + +Name: rpi-ft5406 +Info: Official Raspberry Pi display touchscreen +Load: dtoverlay=rpi-ft5406,<param>=<val> +Params: touchscreen-size-x Touchscreen X resolution (default 800) + touchscreen-size-y Touchscreen Y resolution (default 480); + touchscreen-inverted-x Invert touchscreen X coordinates (default 0); + touchscreen-inverted-y Invert touchscreen Y coordinates (default 0); + touchscreen-swapped-x-y Swap X and Y cordinates (default 0); + + +Name: rpi-fw-uart +Info: Configures the firmware software UART driver. + This driver requires exclusive usage of the second VPU core. The + following config.txt entries should be set when this driver is used. + dtparam=audio=off + isp_use_vpu0=1 +Load: dtoverlay=rpi-fw-uart,<param>[=<val>] +Params: txd0_pin GPIO pin for TXD0 (any free - default 20) + + rxd0_pin GPIO pin for RXD0 (any free - default 21) + + +Name: rpi-poe +Info: Raspberry Pi PoE HAT fan +Load: dtoverlay=rpi-poe,<param>[=<val>] +Params: poe_fan_temp0 Temperature (in millicelcius) at which the fan + turns on (default 40000) + poe_fan_temp0_hyst Temperature delta (in millicelcius) at which + the fan turns off (default 2000) + poe_fan_temp1 Temperature (in millicelcius) at which the fan + speeds up (default 45000) + poe_fan_temp1_hyst Temperature delta (in millicelcius) at which + the fan slows down (default 2000) + poe_fan_temp2 Temperature (in millicelcius) at which the fan + speeds up (default 50000) + poe_fan_temp2_hyst Temperature delta (in millicelcius) at which + the fan slows down (default 2000) + poe_fan_temp3 Temperature (in millicelcius) at which the fan + speeds up (default 55000) + poe_fan_temp3_hyst Temperature delta (in millicelcius) at which + the fan slows down (default 5000) + i2c Control the fan via Linux I2C drivers instead of + the firmware. + + +Name: rpi-poe-plus +Info: Raspberry Pi PoE+ HAT fan +Load: dtoverlay=rpi-poe-plus,<param>[=<val>] +Params: poe_fan_temp0 Temperature (in millicelcius) at which the fan + turns on (default 40000) + poe_fan_temp0_hyst Temperature delta (in millicelcius) at which + the fan turns off (default 2000) + poe_fan_temp1 Temperature (in millicelcius) at which the fan + speeds up (default 45000) + poe_fan_temp1_hyst Temperature delta (in millicelcius) at which + the fan slows down (default 2000) + poe_fan_temp2 Temperature (in millicelcius) at which the fan + speeds up (default 50000) + poe_fan_temp2_hyst Temperature delta (in millicelcius) at which + the fan slows down (default 2000) + poe_fan_temp3 Temperature (in millicelcius) at which the fan + speeds up (default 55000) + poe_fan_temp3_hyst Temperature delta (in millicelcius) at which + the fan slows down (default 5000) + i2c Control the fan via Linux I2C drivers instead of + the firmware. + + +Name: rpi-proto +Info: This overlay has been renamed to proto-codec. +Load: <Deprecated> + + +Name: rpi-sense +Info: Raspberry Pi Sense HAT +Load: dtoverlay=rpi-sense +Params: <None> + + +Name: rpi-sense-v2 +Info: Raspberry Pi Sense HAT v2 +Load: dtoverlay=rpi-sense-v2 +Params: <None> + + +Name: rpi-tv +Info: Raspberry Pi TV HAT +Load: dtoverlay=rpi-tv +Params: <None> + + +Name: rpivid-v4l2 +Info: This overlay has been deprecated and deleted as the V4L2 stateless + video decoder driver is enabled by default. +Load: <Deprecated> + + +Name: rra-digidac1-wm8741-audio +Info: Configures the Red Rocks Audio DigiDAC1 soundcard +Load: dtoverlay=rra-digidac1-wm8741-audio +Params: <None> + + +Name: sainsmart18 +Info: Overlay for the SPI-connected Sainsmart 1.8" display (based on the + ST7735R chip). +Load: dtoverlay=sainsmart18,<param>=<val> +Params: rotate Display rotation {0,90,180,270} + speed SPI bus speed in Hz (default 4000000) + fps Display frame rate in Hz + bgr Enable BGR mode (default off) + debug Debug output level {0-7} + dc_pin GPIO pin for D/C (default 24) + reset_pin GPIO pin for RESET (default 25) + + +Name: sc16is750-i2c +Info: Overlay for the NXP SC16IS750 UART with I2C Interface + Enables the chip on I2C1 at 0x48 (or the "addr" parameter value). To + select another address, please refer to table 10 in reference manual. +Load: dtoverlay=sc16is750-i2c,<param>=<val> +Params: int_pin GPIO used for IRQ (default 24) + addr Address (default 0x48) + i2c-bus Supports all the standard I2C bus selection + parameters - see "dtoverlay -h i2c-bus" + xtal On-board crystal frequency (default 14745600) + + +Name: sc16is750-spi0 +Info: Overlay for the NXP SC16IS750 UART with SPI Interface + Enables the chip on SPI0. +Load: dtoverlay=sc16is750-spi0,<param>=<val> +Params: int_pin GPIO used for IRQ (default 24) + xtal On-board crystal frequency (default 14745600) + + +Name: sc16is752-i2c +Info: Overlay for the NXP SC16IS752 dual UART with I2C Interface + Enables the chip on I2C1 at 0x48 (or the "addr" parameter value). To + select another address, please refer to table 10 in reference manual. +Load: dtoverlay=sc16is752-i2c,<param>=<val> +Params: int_pin GPIO used for IRQ (default 24) + addr Address (default 0x48) + i2c-bus Supports all the standard I2C bus selection + parameters - see "dtoverlay -h i2c-bus" + xtal On-board crystal frequency (default 14745600) + + +Name: sc16is752-spi0 +Info: Overlay for the NXP SC16IS752 Dual UART with SPI Interface + Enables the chip on SPI0. +Load: dtoverlay=sc16is752-spi0,<param>=<val> +Params: int_pin GPIO used for IRQ (default 24) + xtal On-board crystal frequency (default 14745600) + + +Name: sc16is752-spi1 +Info: Overlay for the NXP SC16IS752 Dual UART with SPI Interface + Enables the chip on SPI1. + N.B.: spi1 is only accessible on devices with a 40pin header, eg: + A+, B+, Zero and PI2 B; as well as the Compute Module. + +Load: dtoverlay=sc16is752-spi1,<param>=<val> +Params: int_pin GPIO used for IRQ (default 24) + xtal On-board crystal frequency (default 14745600) + + +Name: sdhost +Info: Selects the bcm2835-sdhost SD/MMC driver, optionally with overclock. + N.B. This overlay is designed for situations where the mmc driver is + the default, so it disables the other (mmc) interface - this will kill + WLAN on a Pi3. If this isn't what you want, either use the sdtweak + overlay or the new sd_* dtparams of the base DTBs. +Load: dtoverlay=sdhost,<param>=<val> +Params: overclock_50 Clock (in MHz) to use when the MMC framework + requests 50MHz + + force_pio Disable DMA support (default off) + + pio_limit Number of blocks above which to use DMA + (default 1) + + debug Enable debug output (default off) + + +Name: sdio +Info: Selects the bcm2835-sdhost SD/MMC driver, optionally with overclock, + and enables SDIO via GPIOs 22-27. An example of use in 1-bit mode is + "dtoverlay=sdio,bus_width=1,gpios_22_25" +Load: dtoverlay=sdio,<param>=<val> +Params: sdio_overclock SDIO Clock (in MHz) to use when the MMC + framework requests 50MHz + + poll_once Disable SDIO-device polling every second + (default on: polling once at boot-time) + + bus_width Set the SDIO host bus width (default 4 bits) + + gpios_22_25 Select GPIOs 22-25 for 1-bit mode. Must be used + with bus_width=1. This replaces the sdio-1bit + overlay, which is now deprecated. + + gpios_34_37 Select GPIOs 34-37 for 1-bit mode. Must be used + with bus_width=1. + + gpios_34_39 Select GPIOs 34-39 for 4-bit mode. Must be used + with bus_width=4 (the default). + + +Name: sdio-1bit +Info: This overlay is now deprecated. Use + "dtoverlay=sdio,bus_width=1,gpios_22_25" instead. +Load: <Deprecated> + + +Name: sdio-pi5 +Info: Selects the rp1_mmc0 interface and enables it on GPIOs 22-27. + Pi 5 only. +Load: dtoverlay=sdio-pi5 +Params: <None> + + +Name: sdtweak +Info: This overlay is now deprecated. Use the sd_* dtparams in the + base DTB, e.g. "dtoverlay=sdtweak,poll_once" becomes + "dtparam=sd_poll_once". +Load: <Deprecated> + + +Name: seeed-can-fd-hat-v1 +Info: Overlay for Seeed Studio CAN BUS FD HAT with two CAN FD + channels without RTC. Use this overlay if your HAT has no + battery holder. + https://www.seeedstudio.com/2-Channel-CAN-BUS-FD-Shield-for-Raspberry-Pi-p-4072.html +Load: dtoverlay=seeed-can-fd-hat-v1 +Params: <None> + + +Name: seeed-can-fd-hat-v2 +Info: Overlay for Seeed Studio CAN BUS FD HAT with two CAN FD + channels and an RTC. Use this overlay if your HAT has a + battery holder. + https://www.seeedstudio.com/CAN-BUS-FD-HAT-for-Raspberry-Pi-p-4742.html +Load: dtoverlay=seeed-can-fd-hat-v2 +Params: <None> + + +Name: sh1106-spi +Info: Overlay for SH1106 OLED via SPI using fbtft staging driver. +Load: dtoverlay=sh1106-spi,<param>=<val> +Params: speed SPI bus speed (default 4000000) + rotate Display rotation (0, 90, 180 or 270; default 0) + fps Delay between frame updates (default 25) + debug Debug output level (0-7; default 0) + dc_pin GPIO pin for D/C (default 24) + reset_pin GPIO pin for RESET (default 25) + height Display height (32 or 64; default 64) + + +Name: si446x-spi0 +Info: Overlay for Si446x UHF Transceiver via SPI using si446x driver. + The driver is currently out-of-tree at + https://github.com/sunipkmukherjee/silabs.git +Load: dtoverlay=si446x-spi0,<param>=<val> +Params: speed SPI bus speed (default 4000000) + int_pin GPIO pin for interrupts (default 17) + reset_pin GPIO pin for RESET (default 27) + + +Name: smi +Info: Enables the Secondary Memory Interface peripheral. Uses GPIOs 2-25! +Load: dtoverlay=smi +Params: <None> + + +Name: smi-dev +Info: Enables the userspace interface for the SMI driver +Load: dtoverlay=smi-dev +Params: <None> + + +Name: smi-nand +Info: Enables access to NAND flash via the SMI interface +Load: dtoverlay=smi-nand +Params: <None> + + +Name: spi-gpio35-39 +Info: Move SPI function block to GPIO 35 to 39 +Load: dtoverlay=spi-gpio35-39 +Params: <None> + + +Name: spi-gpio40-45 +Info: Move SPI function block to GPIOs 40 to 45 +Load: dtoverlay=spi-gpio40-45 +Params: <None> + + +Name: spi-rtc +Info: Adds support for a number of SPI Real Time Clock devices +Load: dtoverlay=spi-rtc,<param>=<val> +Params: ds3232 Select the DS3232 device + ds3234 Select the DS3234 device + pcf2123 Select the PCF2123 device + + spi0_0 Use spi0.0 (default) + spi0_1 Use spi0.1 + spi1_0 Use spi1.0 + spi1_1 Use spi1.1 + spi2_0 Use spi2.0 + spi2_1 Use spi2.1 + cs_high This device requires an active-high CS + + +Name: spi0-0cs +Info: Don't claim any CS pins for SPI0. Requires a device with its chip + select permanently enabled, but frees a GPIO for e.g. a DPI display. +Load: dtoverlay=spi0-0cs,<param>=<val> +Params: no_miso Don't claim and use the MISO pin (9), freeing + it for other uses. + + +Name: spi0-1cs +Info: Only use one CS pin for SPI0 +Load: dtoverlay=spi0-1cs,<param>=<val> +Params: cs0_pin GPIO pin for CS0 (default 8) + no_miso Don't claim and use the MISO pin (9), freeing + it for other uses. + + +Name: spi0-1cs-inverted +Info: Only use one CS pin for SPI0 and set to active-high +Load: dtoverlay=spi0-1cs-inverted,<param>=<val> +Params: cs0_pin GPIO pin for CS0 (default 8) + no_miso Don't claim and use the MISO pin (9), freeing + it for other uses. + + +Name: spi0-2cs +Info: Change the CS pins for SPI0 +Load: dtoverlay=spi0-2cs,<param>=<val> +Params: cs0_pin GPIO pin for CS0 (default 8) + cs1_pin GPIO pin for CS1 (default 7) + no_miso Don't claim and use the MISO pin (9), freeing + it for other uses. + + +Name: spi0-cs +Info: This overlay has been renamed spi0-2cs, keeping spi0-cs as an + alias for backwards compatibility. +Load: <Deprecated> + + +Name: spi0-hw-cs +Info: This overlay has been deprecated and removed because it is no longer + necessary and has been seen to prevent spi0 from working. +Load: <Deprecated> + + +Name: spi1-1cs +Info: Enables spi1 with a single chip select (CS) line and associated spidev + dev node. The gpio pin number for the CS line and spidev device node + creation are configurable. + N.B.: spi1 is not accessible on old Pis without a 40-pin header. +Load: dtoverlay=spi1-1cs,<param>=<val> +Params: cs0_pin GPIO pin for CS0 (default 18 - BCM SPI1_CE0). + cs0_spidev Set to 'off' to stop the creation of a + userspace device node /dev/spidev1.0 (default + is 'on' or enabled). + + +Name: spi1-2cs +Info: Enables spi1 with two chip select (CS) lines and associated spidev + dev nodes. The gpio pin numbers for the CS lines and spidev device node + creation are configurable. + N.B.: spi1 is not accessible on old Pis without a 40-pin header. +Load: dtoverlay=spi1-2cs,<param>=<val> +Params: cs0_pin GPIO pin for CS0 (default 18 - BCM SPI1_CE0). + cs1_pin GPIO pin for CS1 (default 17 - BCM SPI1_CE1). + cs0_spidev Set to 'off' to stop the creation of a + userspace device node /dev/spidev1.0 (default + is 'on' or enabled). + cs1_spidev Set to 'off' to stop the creation of a + userspace device node /dev/spidev1.1 (default + is 'on' or enabled). + + +Name: spi1-3cs +Info: Enables spi1 with three chip select (CS) lines and associated spidev + dev nodes. The gpio pin numbers for the CS lines and spidev device node + creation are configurable. + N.B.: spi1 is not accessible on old Pis without a 40-pin header. +Load: dtoverlay=spi1-3cs,<param>=<val> +Params: cs0_pin GPIO pin for CS0 (default 18 - BCM SPI1_CE0). + cs1_pin GPIO pin for CS1 (default 17 - BCM SPI1_CE1). + cs2_pin GPIO pin for CS2 (default 16 - BCM SPI1_CE2). + cs0_spidev Set to 'off' to stop the creation of a + userspace device node /dev/spidev1.0 (default + is 'on' or enabled). + cs1_spidev Set to 'off' to stop the creation of a + userspace device node /dev/spidev1.1 (default + is 'on' or enabled). + cs2_spidev Set to 'off' to stop the creation of a + userspace device node /dev/spidev1.2 (default + is 'on' or enabled). + + +Name: spi2-1cs +Info: Enables spi2 on GPIOs 40-42 with a single chip select (CS) line and + associated spidev dev node. The gpio pin number for the CS line and + spidev device node creation are configurable. spi2-2cs-pi5 is + substituted on a Pi 5. + N.B.: spi2 is only accessible with the Compute Module or Pi 5. +Load: dtoverlay=spi2-1cs,<param>=<val> +Params: cs0_pin GPIO pin for CS0 (default 43 - BCM SPI2_CE0). + cs0_spidev Set to 'off' to stop the creation of a + userspace device node /dev/spidev2.0 (default + is 'on' or enabled). + + +Name: spi2-1cs-pi5 +Info: Enables spi2 on GPIOs 1-3 with a single chip select (CS) line and + associated spidev dev node. The gpio pin number for the CS line and + spidev device node creation are configurable. Pi 5 only. +Load: dtoverlay=spi2-1cs-pi5,<param>=<val> +Params: cs0_pin GPIO pin for CS0 (default 0). + cs0_spidev Set to 'off' to stop the creation of a + userspace device node /dev/spidev2.0 (default + is 'on' or enabled). + + +Name: spi2-2cs +Info: Enables spi2 on GPIOs 40-42 with two chip select (CS) lines and + associated spidev dev nodes. The gpio pin numbers for the CS lines and + spidev device node creation are configurable. spi2-2cs-pi5 is + substituted on a Pi 5. + N.B.: spi2 is only accessible with the Compute Module or Pi 5. +Load: dtoverlay=spi2-2cs,<param>=<val> +Params: cs0_pin GPIO pin for CS0 (default 43 - BCM SPI2_CE0). + cs1_pin GPIO pin for CS1 (default 44 - BCM SPI2_CE1). + cs0_spidev Set to 'off' to stop the creation of a + userspace device node /dev/spidev2.0 (default + is 'on' or enabled). + cs1_spidev Set to 'off' to stop the creation of a + userspace device node /dev/spidev2.1 (default + is 'on' or enabled). + + +Name: spi2-2cs-pi5 +Info: Enables spi2 on GPIOs 1-3 with two chip select (CS) lines and + associated spidev dev nodes. The gpio pin numbers for the CS lines and + spidev device node creation are configurable. Pi 5 only. +Load: dtoverlay=spi2-2cs-pi5,<param>=<val> +Params: cs0_pin GPIO pin for CS0 (default 0). + cs1_pin GPIO pin for CS1 (default 24). + cs0_spidev Set to 'off' to stop the creation of a + userspace device node /dev/spidev2.0 (default + is 'on' or enabled). + cs1_spidev Set to 'off' to stop the creation of a + userspace device node /dev/spidev2.1 (default + is 'on' or enabled). + + +Name: spi2-3cs +Info: Enables spi2 on GPIOs 40-42 with three chip select (CS) lines and + associated spidev dev nodes. The gpio pin numbers for the CS lines and + spidev device node creation are configurable. + N.B.: spi2 is only accessible with the Compute Module or Pi 5. +Load: dtoverlay=spi2-3cs,<param>=<val> +Params: cs0_pin GPIO pin for CS0 (default 43 - BCM SPI2_CE0). + cs1_pin GPIO pin for CS1 (default 44 - BCM SPI2_CE1). + cs2_pin GPIO pin for CS2 (default 45 - BCM SPI2_CE2). + cs0_spidev Set to 'off' to stop the creation of a + userspace device node /dev/spidev2.0 (default + is 'on' or enabled). + cs1_spidev Set to 'off' to stop the creation of a + userspace device node /dev/spidev2.1 (default + is 'on' or enabled). + cs2_spidev Set to 'off' to stop the creation of a + userspace device node /dev/spidev2.2 (default + is 'on' or enabled). + + +Name: spi3-1cs +Info: Enables spi3 on GPIOs 1-3 with a single chip select (CS) line and + associated spidev dev node. The gpio pin number for the CS line and + spidev device node creation are configurable. BCM2711 only, + spi3-1cs-pi5 is substituted on Pi 5. +Load: dtoverlay=spi3-1cs,<param>=<val> +Params: cs0_pin GPIO pin for CS0 (default 0 - BCM SPI3_CE0). + cs0_spidev Set to 'off' to prevent the creation of a + userspace device node /dev/spidev3.0 (default + is 'on' or enabled). + + +Name: spi3-1cs-pi5 +Info: Enables spi3 on GPIOs 5-7 with a single chip select (CS) line and + associated spidev dev node. The gpio pin number for the CS line and + spidev device node creation are configurable. Pi 5 only. +Load: dtoverlay=spi3-1cs-pi5,<param>=<val> +Params: cs0_pin GPIO pin for CS0 (default 4). + cs0_spidev Set to 'off' to prevent the creation of a + userspace device node /dev/spidev3.0 (default + is 'on' or enabled). + + +Name: spi3-2cs +Info: Enables spi3 on GPIO2 1-3 with two chip select (CS) lines and + associated spidev dev nodes. The gpio pin numbers for the CS lines and + spidev device node creation are configurable. BCM2711 only, + spi3-2cs-pi5 is substituted on Pi 5. +Load: dtoverlay=spi3-2cs,<param>=<val> +Params: cs0_pin GPIO pin for CS0 (default 0 - BCM SPI3_CE0). + cs1_pin GPIO pin for CS1 (default 24 - BCM SPI3_CE1). + cs0_spidev Set to 'off' to prevent the creation of a + userspace device node /dev/spidev3.0 (default + is 'on' or enabled). + cs1_spidev Set to 'off' to prevent the creation of a + userspace device node /dev/spidev3.1 (default + is 'on' or enabled). + + +Name: spi3-2cs-pi5 +Info: Enables spi3 on GPIOs 5-7 with two chip select (CS) lines and + associated spidev dev nodes. The gpio pin numbers for the CS lines and + spidev device node creation are configurable. Pi 5 only. +Load: dtoverlay=spi3-2cs-pi5,<param>=<val> +Params: cs0_pin GPIO pin for CS0 (default 4). + cs1_pin GPIO pin for CS1 (default 25). + cs0_spidev Set to 'off' to prevent the creation of a + userspace device node /dev/spidev3.0 (default + is 'on' or enabled). + cs1_spidev Set to 'off' to prevent the creation of a + userspace device node /dev/spidev3.1 (default + is 'on' or enabled). + + +Name: spi4-1cs +Info: Enables spi4 on GPIOs 5-7 with a single chip select (CS) line and + associated spidev dev node. The gpio pin number for the CS line and + spidev device node creation are configurable. BCM2711 only. +Load: dtoverlay=spi4-1cs,<param>=<val> +Params: cs0_pin GPIO pin for CS0 (default 4 - BCM SPI4_CE0). + cs0_spidev Set to 'off' to prevent the creation of a + userspace device node /dev/spidev4.0 (default + is 'on' or enabled). + + +Name: spi4-2cs +Info: Enables spi4 on GPIOs 5-6 with two chip select (CS) lines and + associated spidev dev nodes. The gpio pin numbers for the CS lines and + spidev device node creation are configurable. BCM2711 only. +Load: dtoverlay=spi4-2cs,<param>=<val> +Params: cs0_pin GPIO pin for CS0 (default 4 - BCM SPI4_CE0). + cs1_pin GPIO pin for CS1 (default 25 - BCM SPI4_CE1). + cs0_spidev Set to 'off' to prevent the creation of a + userspace device node /dev/spidev4.0 (default + is 'on' or enabled). + cs1_spidev Set to 'off' to prevent the creation of a + userspace device node /dev/spidev4.1 (default + is 'on' or enabled). + + +Name: spi5-1cs +Info: Enables spi5 on GPIOs 13-15 with a single chip select (CS) line and + associated spidev dev node. The gpio pin numbers for the CS lines and + spidev device node creation are configurable. BCM2711 and Pi 5. +Load: dtoverlay=spi5-1cs,<param>=<val> +Params: cs0_pin GPIO pin for CS0 (default 12). + cs0_spidev Set to 'off' to prevent the creation of a + userspace device node /dev/spidev5.0 (default + is 'on' or enabled). + + +Name: spi5-1cs-pi5 +Info: See spi5-1cs + + +Name: spi5-2cs +Info: Enables spi5 on GPIOs 13-15 with two chip select (CS) lines and + associated spidev dev nodes. The gpio pin numbers for the CS lines and + spidev device node creation are configurable. BCM2711 and Pi 5. +Load: dtoverlay=spi5-2cs,<param>=<val> +Params: cs0_pin GPIO pin for CS0 (default 12). + cs1_pin GPIO pin for CS1 (default 26). + cs0_spidev Set to 'off' to prevent the creation of a + userspace device node /dev/spidev5.0 (default + is 'on' or enabled). + cs1_spidev Set to 'off' to prevent the creation of a + userspace device node /dev/spidev5.1 (default + is 'on' or enabled). + + +Name: spi5-2cs-pi5 +Info: See spi5-2cs + + +Name: spi6-1cs +Info: Enables spi6 with a single chip select (CS) line and associated spidev + dev node. The gpio pin number for the CS line and spidev device node + creation are configurable. BCM2711 only. +Load: dtoverlay=spi6-1cs,<param>=<val> +Params: cs0_pin GPIO pin for CS0 (default 18 - BCM SPI6_CE0). + cs0_spidev Set to 'off' to prevent the creation of a + userspace device node /dev/spidev6.0 (default + is 'on' or enabled). + + +Name: spi6-2cs +Info: Enables spi6 with two chip select (CS) lines and associated spidev + dev nodes. The gpio pin numbers for the CS lines and spidev device node + creation are configurable. BCM2711 only. +Load: dtoverlay=spi6-2cs,<param>=<val> +Params: cs0_pin GPIO pin for CS0 (default 18 - BCM SPI6_CE0). + cs1_pin GPIO pin for CS1 (default 27 - BCM SPI6_CE1). + cs0_spidev Set to 'off' to prevent the creation of a + userspace device node /dev/spidev6.0 (default + is 'on' or enabled). + cs1_spidev Set to 'off' to prevent the creation of a + userspace device node /dev/spidev6.1 (default + is 'on' or enabled). + + +Name: ssd1306 +Info: Overlay for activation of SSD1306 over I2C OLED display framebuffer. +Load: dtoverlay=ssd1306,<param>=<val> +Params: address Location in display memory of first character. + (default=0) + width Width of display. (default=128) + height Height of display. (default=64) + offset virtual channel a. (default=0) + normal Has no effect on displays tested. (default=not + set) + sequential Set this if every other scan line is missing. + (default=not set) + remapped Set this if display is garbled. (default=not + set) + inverted Set this if display is inverted and mirrored. + (default=not set) + + Examples: + Typical usage for 128x64 display: dtoverlay=ssd1306,inverted + + Typical usage for 128x32 display: dtoverlay=ssd1306,inverted,sequential + + i2c_baudrate=400000 will speed up the display. + + i2c_baudrate=1000000 seems to work even though it's not officially + supported by the hardware, and is faster still. + + For more information refer to the device datasheet at: + https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf + + +Name: ssd1306-spi +Info: Overlay for SSD1306 OLED via SPI using fbtft staging driver. +Load: dtoverlay=ssd1306-spi,<param>=<val> +Params: speed SPI bus speed (default 10000000) + rotate Display rotation (0, 90, 180 or 270; default 0) + fps Delay between frame updates (default 25) + debug Debug output level (0-7; default 0) + dc_pin GPIO pin for D/C (default 24) + reset_pin GPIO pin for RESET (default 25) + height Display height (32 or 64; default 64) + inverted Set this if display is inverted and mirrored. + (default=not set) + + +Name: ssd1327-spi +Info: Overlay for SSD1327 OLED via SPI using the DRM ssd130x driver. +Load: dtoverlay=ssd1327-spi,<param>=<val> +Params: speed SPI bus speed (default 4500000) + dc_pin GPIO pin for D/C (default 24) + reset_pin GPIO pin for RESET (default 25) + + +Name: ssd1331-spi +Info: Overlay for SSD1331 OLED via SPI using fbtft staging driver. +Load: dtoverlay=ssd1331-spi,<param>=<val> +Params: speed SPI bus speed (default 4500000) + rotate Display rotation (0, 90, 180 or 270; default 0) + fps Delay between frame updates (default 25) + debug Debug output level (0-7; default 0) + dc_pin GPIO pin for D/C (default 24) + reset_pin GPIO pin for RESET (default 25) + + +Name: ssd1351-spi +Info: Overlay for SSD1351 OLED via SPI using fbtft staging driver. +Load: dtoverlay=ssd1351-spi,<param>=<val> +Params: speed SPI bus speed (default 4500000) + rotate Display rotation (0, 90, 180 or 270; default 0) + fps Delay between frame updates (default 25) + debug Debug output level (0-7; default 0) + dc_pin GPIO pin for D/C (default 24) + reset_pin GPIO pin for RESET (default 25) + + +Name: sunfounder-pipower3 +Info: Overlay for SunFounder PiPower 3 +Load: dtoverlay=sunfounder-pipower3,<param>=<val> +Params: poweroff_pin Change poweroff pin (default 26) + + +Name: sunfounder-pironman5 +Info: Overlay for SunFounder Pironman 5 +Load: dtoverlay=sunfounder-pironman5,<param>=<val> +Params: ir Enable IR or not (on or off, default on) + ir_pins Change IR receiver pin (default 13) + + +Name: superaudioboard +Info: Configures the SuperAudioBoard sound card +Load: dtoverlay=superaudioboard,<param>=<val> +Params: gpiopin GPIO pin for codec reset + + +Name: sx150x +Info: Configures the Semtech SX150X I2C GPIO expanders. +Load: dtoverlay=sx150x,<param>=<val> +Params: sx150<x>-<n>-<m> Enables SX150X device on I2C#<n> with slave + address <m>. <x> may be 1-9. <n> may be 0 or 1. + Permissible values of <m> (which is denoted in + hex) depend on the device variant. For SX1501, + SX1502, SX1504 and SX1505, <m> may be 20 or 21. + For SX1503 and SX1506, <m> may be 20. For + SX1507 and SX1509, <m> may be 3E, 3F, 70 or 71. + For SX1508, <m> may be 20, 21, 22 or 23. + + sx150<x>-<n>-<m>-int-gpio + Integer, enables interrupts on SX150X device on + I2C#<n> with slave address <m>, specifies + the GPIO pin to which NINT output of SX150X is + connected. + + +Name: tc358743 +Info: Toshiba TC358743 HDMI to CSI-2 bridge chip. + Uses Unicam 1, which is the standard camera connector on most Pi + variants. +Load: dtoverlay=tc358743,<param>=<val> +Params: 4lane Use 4 lanes (only applicable to Compute Modules + CAM1 connector). + + link-frequency Set the link frequency. Only values of 297000000 + (574Mbit/s) and 486000000 (972Mbit/s - default) + are supported by the driver. + media-controller Configure use of Media Controller API for + configuring the sensor (default off) + cam0 Adopt the default configuration for CAM0 on a + Compute Module (CSI0, i2c_vc, and cam0_reg). + + +Name: tc358743-audio +Info: Used in combination with the tc358743-fast overlay to route the audio + from the TC358743 over I2S to the Pi. + Wiring is LRCK/WFS to GPIO 19, BCK/SCK to GPIO 18, and DATA/SD to GPIO + 20. +Load: dtoverlay=tc358743-audio,<param>=<val> +Params: card-name Override the default, "tc358743", card name. + + +Name: tinylcd35 +Info: 3.5" Color TFT Display by www.tinylcd.com + Options: Touch, RTC, keypad +Load: dtoverlay=tinylcd35,<param>=<val> +Params: speed Display SPI bus speed + + rotate Display rotation {0,90,180,270} + + fps Delay between frame updates + + debug Debug output level {0-7} + + touch Enable touch panel + + touchgpio Touch controller IRQ GPIO + + xohms Touchpanel: Resistance of X-plate in ohms + + rtc-pcf PCF8563 Real Time Clock + + rtc-ds DS1307 Real Time Clock + + keypad Enable keypad + + Examples: + Display with touchpanel, PCF8563 RTC and keypad: + dtoverlay=tinylcd35,touch,rtc-pcf,keypad + Old touch display: + dtoverlay=tinylcd35,touch,touchgpio=3 + + +Name: tpm-slb9670 +Info: Enables support for Infineon SLB9670 Trusted Platform Module add-on + boards, which can be used as a secure key storage and hwrng, + available as "Iridium SLB9670" by Infineon and "LetsTrust TPM" by pi3g. +Load: dtoverlay=tpm-slb9670 +Params: <None> + + +Name: tpm-slb9673 +Info: Enables support for Infineon SLB9673 Trusted Platform Module add-on + boards, which can be used as a secure key storage and hwrng + via the I2C protocol. +Load: dtoverlay=tpm-slb9673 +Params: <None> + + +Name: uart0 +Info: Change the pin usage of uart0 +Load: dtoverlay=uart0,<param>=<val> +Params: txd0_pin GPIO pin for TXD0 (14, 32 or 36 - default 14) + + rxd0_pin GPIO pin for RXD0 (15, 33 or 37 - default 15) + + pin_func Alternative pin function - 4(Alt0) for 14&15, + 7(Alt3) for 32&33, 6(Alt2) for 36&37 + + +Name: uart0-pi5 +Info: Enable uart 0 on GPIOs 14-15. Pi 5 only. +Load: dtoverlay=uart0-pi5,<param> +Params: ctsrts Enable CTS/RTS on GPIOs 16-17 (default off) + + +Name: uart1 +Info: Change the pin usage of uart1 +Load: dtoverlay=uart1,<param>=<val> +Params: txd1_pin GPIO pin for TXD1 (14, 32 or 40 - default 14) + + rxd1_pin GPIO pin for RXD1 (15, 33 or 41 - default 15) + + +Name: uart1-pi5 +Info: Enable uart 1 on GPIOs 0-1. Pi 5 only. +Load: dtoverlay=uart1-pi5,<param> +Params: ctsrts Enable CTS/RTS on GPIOs 2-3 (default off) + + +Name: uart2 +Info: Enable uart 2 on GPIOs 0-3. BCM2711 only. +Load: dtoverlay=uart2,<param> +Params: ctsrts Enable CTS/RTS on GPIOs 2-3 (default off) + + +Name: uart2-pi5 +Info: Enable uart 2 on GPIOs 4-5. Pi 5 only. +Load: dtoverlay=uart2-pi5,<param> +Params: ctsrts Enable CTS/RTS on GPIOs 6-7 (default off) + + +Name: uart3 +Info: Enable uart 3 on GPIOs 4-7. BCM2711 only. +Load: dtoverlay=uart3,<param> +Params: ctsrts Enable CTS/RTS on GPIOs 6-7 (default off) + + +Name: uart3-pi5 +Info: Enable uart 3 on GPIOs 8-9. Pi 5 only. +Load: dtoverlay=uart3-pi5,<param> +Params: ctsrts Enable CTS/RTS on GPIOs 10-11 (default off) + + +Name: uart4 +Info: Enable uart 4 on GPIOs 8-11. BCM2711 only. +Load: dtoverlay=uart4,<param> +Params: ctsrts Enable CTS/RTS on GPIOs 10-11 (default off) + + +Name: uart4-pi5 +Info: Enable uart 4 on GPIOs 12-13. Pi 5 only. +Load: dtoverlay=uart4-pi5,<param> +Params: ctsrts Enable CTS/RTS on GPIOs 14-15 (default off) + + +Name: uart5 +Info: Enable uart 5 on GPIOs 12-15. BCM2711 only. +Load: dtoverlay=uart5,<param> +Params: ctsrts Enable CTS/RTS on GPIOs 14-15 (default off) + + +Name: udrc +Info: Configures the NW Digital Radio UDRC Hat +Load: dtoverlay=udrc,<param>=<val> +Params: alsaname Name of the ALSA audio device (default "udrc") + + +Name: ugreen-dabboard +Info: Configures the ugreen-dabboard I2S overlay + This is a simple overlay based on the simple-audio-card and the dmic + codec. It has the speciality that it is configured to use the codec + as a master I2S device. It works for example with the Si468x DAB + receiver on the uGreen DABBoard. +Load: dtoverlay=ugreen-dabboard,<param>=<val> +Params: card-name Override the default, "dabboard", card name. + + +Name: upstream +Info: Allow usage of downstream .dtb with upstream kernel. Comprises the + vc4-kms-v3d and dwc2 overlays. +Load: dtoverlay=upstream +Params: <None> + + +Name: upstream-aux-interrupt +Info: This overlay has been deprecated and removed because it is no longer + necessary. +Load: <Deprecated> + + +Name: upstream-pi4 +Info: Allow usage of downstream .dtb with upstream kernel on Pi 4. Comprises + the vc4-kms-v3d-pi4 and dwc2 overlays. +Load: dtoverlay=upstream-pi4 +Params: <None> + + +Name: vc4-fkms-v3d +Info: Enable the kernel DRM VC4 V3D driver on top of the dispmanx + display stack. + NB The firmware will not allow this overlay to load on a Pi with less + than 512MB as memory is too tight. +Load: dtoverlay=vc4-fkms-v3d,<param> +Params: cma-512 CMA is 512MB (needs 1GB) + cma-448 CMA is 448MB (needs 1GB) + cma-384 CMA is 384MB (needs 1GB) + cma-320 CMA is 320MB (needs 1GB) + cma-256 CMA is 256MB (needs 1GB) + cma-192 CMA is 192MB (needs 1GB) + cma-128 CMA is 128MB + cma-96 CMA is 96MB + cma-64 CMA is 64MB + cma-size CMA size in bytes, 4MB aligned + cma-default Use upstream's default value + + +Name: vc4-fkms-v3d-pi4 +Info: Enable the kernel DRM VC4 V3D driver on top of the dispmanx + display stack. +Load: dtoverlay=vc4-fkms-v3d-pi4,<param> +Params: cma-512 CMA is 512MB (needs 1GB) + cma-448 CMA is 448MB (needs 1GB) + cma-384 CMA is 384MB (needs 1GB) + cma-320 CMA is 320MB (needs 1GB) + cma-256 CMA is 256MB (needs 1GB) + cma-192 CMA is 192MB (needs 1GB) + cma-128 CMA is 128MB + cma-96 CMA is 96MB + cma-64 CMA is 64MB + cma-size CMA size in bytes, 4MB aligned + cma-default Use upstream's default value + + +Name: vc4-kms-dpi-at056tn53v1 +Info: This overlay is now deprecated - see vc4-kms-dpi-panel,at056tn53v1 +Load: <Deprecated> + + +Name: vc4-kms-dpi-generic +Info: Enable a generic DPI display under KMS. Default timings are for the + Adafruit Kippah with 800x480 panel and RGB666 (GPIOs 0-21) + Requires vc4-kms-v3d to be loaded. +Load: dtoverlay=vc4-kms-dpi-generic,<param>=<val> +Params: clock-frequency Display clock frequency (Hz) + hactive Horizontal active pixels + hfp Horizontal front porch + hsync Horizontal sync pulse width + hbp Horizontal back porch + vactive Vertical active lines + vfp Vertical front porch + vsync Vertical sync pulse width + vbp Vertical back porch + hsync-invert Horizontal sync active low + vsync-invert Vertical sync active low + de-invert Data Enable active low + pixclk-invert Negative edge pixel clock + interlaced Use an interlaced mode (where supported) + width-mm Define the screen width in mm + height-mm Define the screen height in mm + rgb565 Change to RGB565 output on GPIOs 0-19 + rgb565-padhi Change to RGB565 output on GPIOs 0-8, 12-17, and + 20-24 + bgr666 Change to BGR666 output on GPIOs 0-21. + bgr666-padhi Change to BGR666 output on GPIOs 0-9, 12-17, and + 20-25 + rgb666-padhi Change to RGB666 output on GPIOs 0-9, 12-17, and + 20-25 + bgr888 Change to BGR888 output on GPIOs 0-27 + rgb888 Change to RGB888 output on GPIOs 0-27 + bus-format Override the bus format for a MEDIA_BUS_FMT_* + value. NB also overridden by rgbXXX overrides. + backlight-gpio Defines a GPIO to be used for backlight control + (default of none). + backlight-pwm Defines a PWM channel to be used for backlight + control (default of none). NB Disables audio + headphone output as that also uses PWM. + backlight-pwm-chan Choose channel on &pwm node for backlight + control. + (default 0). + backlight-pwm-gpio GPIO pin to be used for the PWM backlight. See + pwm-2chan for valid options. + (default 18 - note this can only work with + rgb666-padhi). + backlight-pwm-func Pin function of GPIO used for the PWM + backlight. + See pwm-2chan for valid options. + (default 2). + backlight-def-brightness + Set the default brightness. Normal range 1-16. + (default 16). + rotate Display rotation {0,90,180,270} (default 0) + rgb-order Allow override of RGB order from DPI. + Options for vc4 are "rgb", "bgr", "grb", and + "brg". Other values will be ignored. + + + +Name: vc4-kms-dpi-hyperpixel2r +Info: Enable the KMS drivers for the Pimoroni HyperPixel2 Round DPI display. + Requires vc4-kms-v3d to be loaded. +Load: dtoverlay=vc4-kms-dpi-hyperpixel2r,<param>=<val> +Params: disable-touch Disables the touch controller + touchscreen-inverted-x Inverts X direction of touch controller + touchscreen-inverted-y Inverts Y direction of touch controller + touchscreen-swapped-x-y Swaps X & Y axes of touch controller + rotate Display rotation {0,90,180,270} (default 0) + + +Name: vc4-kms-dpi-hyperpixel4 +Info: Enable the KMS drivers for the Pimoroni HyperPixel4 DPI display. + Requires vc4-kms-v3d to be loaded. +Load: dtoverlay=vc4-kms-dpi-hyperpixel4,<param>=<val> +Params: disable-touch Disables the touch controller + touchscreen-inverted-x Inverts X direction of touch controller + touchscreen-inverted-y Inverts Y direction of touch controller + touchscreen-swapped-x-y Swaps X & Y axes of touch controller + rotate Display rotation {0,90,180,270} (default 0) + + +Name: vc4-kms-dpi-hyperpixel4sq +Info: Enable the KMS drivers for the Pimoroni HyperPixel4 Square DPI display. + Requires vc4-kms-v3d to be loaded. +Load: dtoverlay=vc4-kms-dpi-hyperpixel4sq,<param>=<val> +Params: disable-touch Disables the touch controller + touchscreen-inverted-x Inverts X direction of touch controller + touchscreen-inverted-y Inverts Y direction of touch controller + touchscreen-swapped-x-y Swaps X & Y axes of touch controller + rotate Display rotation {0,90,180,270} (default 0) + + +Name: vc4-kms-dpi-panel +Info: Enable a preconfigured KMS DPI panel. + Requires vc4-kms-v3d to be loaded. +Load: dtoverlay=vc4-kms-dpi-panel,<param>=<val> +Params: at056tn53v1 Enable an Innolux 5.6in VGA TFT + kippah-7inch Enable an Adafruit Kippah with 7inch panel. + mzp280 Enable a Geekworm MZP280 panel. + backlight-gpio Defines a GPIO to be used for backlight control + (default of none). + backlight-pwm Defines a PWM channel to be used for backlight + control (default of none). NB Disables audio + headphone output as that also uses PWM. + backlight-pwm-chan Choose channel on &pwm node for backlight + control. + (default 0). + backlight-pwm-gpio GPIO pin to be used for the PWM backlight. See + pwm-2chan for valid options. + (default 18 - note this can only work with + rgb666-padhi). + backlight-pwm-func Pin function of GPIO used for the PWM + backlight. + See pwm-2chan for valid options. + (default 2). + backlight-def-brightness + Set the default brightness. Normal range 1-16. + (default 16). + rotate Display rotation {0,90,180,270} (default 0) + + +Name: vc4-kms-dsi-7inch +Info: Enable the Raspberry Pi DSI 7" screen. + Includes the edt-ft5406 for the touchscreen element. + Requires vc4-kms-v3d to be loaded. +Load: dtoverlay=vc4-kms-dsi-7inch,<param>=<val> +Params: sizex Touchscreen size x (default 800) + sizey Touchscreen size y (default 480) + invx Touchscreen inverted x axis + invy Touchscreen inverted y axis + swapxy Touchscreen swapped x y axis + disable_touch Disables the touch screen overlay driver + dsi0 Use DSI0 and i2c_csi_dsi0 (rather than + the default DSI1 and i2c_csi_dsi). + + +Name: vc4-kms-dsi-generic +Info: Enable a generic DSI display under KMS. + Default timings are for a 840x480 RGB888 panel. + Requires vc4-kms-v3d to be loaded. +Load: dtoverlay=vc4-kms-dsi-generic,<param>=<val> +Params: clock-frequency Display clock frequency (Hz) + hactive Horizontal active pixels + hfp Horizontal front porch + hsync Horizontal sync pulse width + hbp Horizontal back porch + vactive Vertical active lines + vfp Vertical front porch + vsync Vertical sync pulse width + vbp Vertical back porch + width-mm Define the screen width in mm + height-mm Define the screen height in mm + rgb565 Change to RGB565 output + rgb666 Change to RGB666 output + rgb666p Change to RGB666 output with pixel packing + rgb888 Change to RGB888 output, this is the default + one-lane Use one DSI lane for data transmission + This is the default + two-lane Use two DSI lanes for data transmission + three-lane Use three DSI lanes for data transmission + Only supported on Pi5 and CM + four-lane Use four DSI lanes for data transmission + Only supported on Pi5 and CM + dsi0 Switch DSI port to DSI0 + Only supported on Pi5 and CM + + +Name: vc4-kms-dsi-ili9881-5inch +Info: Enable the Raspberry Pi 5" ILI9881 based touchscreen panel. + Requires vc4-kms-v3d to be loaded. +Load: dtoverlay=vc4-kms-dsi-ili9881-5inch,<param> +Params: sizex Touchscreen size x (default 720) + sizey Touchscreen size y (default 1280) + invx Touchscreen inverted x axis + invy Touchscreen inverted y axis + swapxy Touchscreen swapped x y axis + disable_touch Disables the touch screen overlay driver + dsi0 Use DSI0 and i2c_csi_dsi0 (rather than + the default DSI1 and i2c_csi_dsi). + + +Name: vc4-kms-dsi-ili9881-7inch +Info: Enable the Raspberry Pi 7" ILI9881 based touchscreen panel. + Requires vc4-kms-v3d to be loaded. +Load: dtoverlay=vc4-kms-dsi-ili9881-7inch,<param> +Params: sizex Touchscreen size x (default 720) + sizey Touchscreen size y (default 1280) + invx Touchscreen inverted x axis + invy Touchscreen inverted y axis + swapxy Touchscreen swapped x y axis + disable_touch Disables the touch screen overlay driver + rotation Display rotation {0,90,180,270} (default 0) + dsi0 Use DSI0 and i2c_csi_dsi0 (rather than + the default DSI1 and i2c_csi_dsi). + + +Name: vc4-kms-dsi-lt070me05000 +Info: Enable a JDI LT070ME05000 DSI display on DSI1. + Note that this is a 4 lane DSI device, so it will only work on a Compute + Module. + Requires vc4-kms-v3d to be loaded. +Load: dtoverlay=vc4-kms-dsi-lt070me05000,<param> +Params: reset GPIO for the reset signal (default 17) + enable GPIO for the enable signal (default 4) + dcdc-en GPIO for the DC-DC converter enable (default 5) + + +Name: vc4-kms-dsi-lt070me05000-v2 +Info: Enable a JDI LT070ME05000 DSI display on DSI1 using Harlab's V2 + interface board. + Note that this is a 4 lane DSI device, so it will only work on a Compute + Module. + Requires vc4-kms-v3d to be loaded. +Load: dtoverlay=vc4-kms-dsi-lt070me05000-v2 +Params: <None> + + +Name: vc4-kms-dsi-waveshare-800x480 +Info: Enable the Waveshare 4.3" 800x480 DSI screen. + It tries to look like the Pi 7" display, but won't accept some of the + timings. + Includes the edt-ft5406 for the touchscreen element. + Requires vc4-kms-v3d to be loaded. +Load: dtoverlay=vc4-kms-dsi-waveshare-800x480,<param>=<val> +Params: sizex Touchscreen size x (default 800) + sizey Touchscreen size y (default 480) + invx Touchscreen inverted x axis + invy Touchscreen inverted y axis + swapxy Touchscreen swapped x y axis + disable_touch Disables the touch screen overlay driver + dsi0 Use DSI0 and i2c_csi_dsi0 (rather than + the default DSI1 and i2c_csi_dsi). + + +Name: vc4-kms-dsi-waveshare-panel +Info: Enable a Waveshare DSI touchscreen + Includes the Goodix driver for the touchscreen element. + The default is for the display to be using the I2C0 option for control. + Use the i2c1 override if using the I2C1 wiring with jumper wires from + GPIOs 2&3 (pins 3&5). + invx/invy/swapxy should be used with caution as the panel specifier will + set the default inversions for that panel. Always use them after the + panel specifier, and be aware that you may need to set them as =0, not + just adding it. + Requires vc4-kms-v3d to be loaded. +Load: dtoverlay=vc4-kms-dsi-waveshare-panel,<param>=<val> +Params: 2_8_inch 2.8" 480x640 + 3_4_inch 3.4" 800x800 round + 4_0_inch 4.0" 480x800 + 4_0_inchC 4.0" 720x720 + 5_0_inch 5.0" 720x1280 + 6_25_inch 6.25" 720x1560 + 8_8_inch 8.8" 480x1920 + 7_0_inchC 7.0" C 1024x600 + 7_9_inch 7.9" 400x1280 + 8_0_inch 8.0" 1280x800 + 10_1_inch 10.1" 1280x800 + 11_9_inch 11.9" 320x1480 + 13_3_inch_4lane 13.3" 1920x1080 4lane + 13_3_inch_2lane 13.3" 1920x1080 2lane + i2c1 Use i2c-1 with jumper wires from GPIOs 2&3 + disable_touch Disable the touch controller + rotation Set the panel orientation property + invx Touchscreen inverted x axis + invy Touchscreen inverted y axis + swapxy Touchscreen swapped x y axis + dsi0 Use DSI0 and i2c_csi_dsi0 (rather than + the default DSI1 and i2c_csi_dsi). + + +Name: vc4-kms-kippah-7inch +Info: This overlay is now deprecated - see vc4-kms-dpi-panel,kippah-7inch +Load: <Deprecated> + + +Name: vc4-kms-v3d +Info: Enable the kernel DRM VC4 HDMI/HVS/V3D driver. + NB The firmware will not allow this overlay to load on a Pi with less + than 512MB as memory is too tight. +Load: dtoverlay=vc4-kms-v3d,<param> +Params: cma-512 CMA is 512MB (needs 1GB) + cma-448 CMA is 448MB (needs 1GB) + cma-384 CMA is 384MB (needs 1GB) + cma-320 CMA is 320MB (needs 1GB) + cma-256 CMA is 256MB (needs 1GB) + cma-192 CMA is 192MB (needs 1GB) + cma-128 CMA is 128MB + cma-96 CMA is 96MB + cma-64 CMA is 64MB + cma-size CMA size in bytes, 4MB aligned + cma-default Use upstream's default value + audio Enable or disable audio over HDMI (default "on") + noaudio Disable all HDMI audio (default "off") + composite Enable the composite output (default "off") + N.B. Disables all other outputs on a Pi 4. + nohdmi Disable HDMI output + + +Name: vc4-kms-v3d-pi4 +Info: Enable the kernel DRM VC4 HDMI/HVS/V3D driver for Pi4. +Load: dtoverlay=vc4-kms-v3d-pi4,<param> +Params: cma-512 CMA is 512MB + cma-448 CMA is 448MB + cma-384 CMA is 384MB + cma-320 CMA is 320MB + cma-256 CMA is 256MB + cma-192 CMA is 192MB + cma-128 CMA is 128MB + cma-96 CMA is 96MB + cma-64 CMA is 64MB + cma-size CMA size in bytes, 4MB aligned + cma-default Use upstream's default value + audio Enable or disable audio over HDMI0 (default + "on") + audio1 Enable or disable audio over HDMI1 (default + "on") + noaudio Disable all HDMI audio (default "off") + composite Enable the composite output (disables all other + outputs) + nohdmi Disable both HDMI 0 & 1 outputs + nohdmi0 Disable HDMI 0 output + nohdmi1 Disable HDMI 1 output + + +Name: vc4-kms-v3d-pi5 +Info: See vc4-kms-v3d-pi4 (this is the Pi 5 version) + + +Name: vc4-kms-vga666 +Info: Enable the VGA666 (resistor ladder ADC) for the vc4-kms-v3d driver. + Requires vc4-kms-v3d to be loaded. +Load: dtoverlay=vc4-kms-vga666,<param> +Params: ddc Enables GPIOs 0&1 as the I2C to read the EDID + from the display. NB These are NOT 5V tolerant + GPIOs, therefore level shifters are required. + + +Name: vga666 +Info: Overlay for the Fen Logic VGA666 board + This uses GPIOs 2-21 (so no I2C), and activates the output 2-3 seconds + after the kernel has started. + NOT for use with vc4-kms-v3d. +Load: dtoverlay=vga666 +Params: <None> + + +Name: vl805 +Info: Overlay to enable a VIA VL805 USB3 controller on CM4 carriers + Will be loaded automatically by up-to-date firmware if "VL805=1" is + set in the EEPROM config. +Load: dtoverlay=vl805 +Params: <None> + + +Name: w1-gpio +Info: Configures the w1-gpio Onewire interface module. + Use this overlay if you *don't* need a GPIO to drive an external pullup. +Load: dtoverlay=w1-gpio,<param>=<val> +Params: gpiopin GPIO for I/O (default "4") + pullup Now enabled by default (ignored) + + +Name: w1-gpio-pi5 +Info: See w1-gpio (this is the Pi 5 version) + + +Name: w1-gpio-pullup +Info: Configures the w1-gpio Onewire interface module. + Use this overlay if you *do* need a GPIO to drive an external pullup. +Load: dtoverlay=w1-gpio-pullup,<param>=<val> +Params: gpiopin GPIO for I/O (default "4") + extpullup GPIO for external pullup (default "5") + pullup Now enabled by default (ignored) + + +Name: w1-gpio-pullup-pi5 +Info: See w1-gpio-pullup (this is the Pi 5 version) + + +Name: w5500 +Info: Overlay for the Wiznet W5500 Ethernet Controller on SPI0 +Load: dtoverlay=w5500,<param>=<val> +Params: int_pin GPIO used for INT (default 25) + + speed SPI bus speed (default 30000000) + + cs SPI bus Chip Select (default 0) + + +Name: watterott-display +Info: Watterott RPi-Display - 2.8" Touch Display + Linux has 2 drivers that support this display and this overlay supports + both. + + Examples: + fbtft/fb_ili9341: dtoverlay=watterott-display + drm/mi0283qt: dtoverlay=watterott-display,drm,backlight-pwm,rotate=180 + + Some notable differences with the DRM driver compared to fbtft: + - The display is turned on when it's first used and not on driver load + as with fbtft. So if nothing uses the display it stays off. + - Can run with a higher SPI clock increasing framerate. This is possible + since the driver avoids messing up the controller configuration due to + transmission errors by running config commands at 10MHz and only pixel + data at full speed (occasional pixel glitch might occur). + - PWM backlight is supported. + +Load: dtoverlay=watterott-display,<param>=<val> +Params: speed Display SPI bus speed + rotate Display rotation {0,90,180,270} + fps Delay between frame updates (fbtft only) + debug Debug output level {0-7} (fbtft only) + xohms Touchpanel sensitivity (X-plate resistance) + swapxy Swap x and y axis + backlight Change backlight GPIO pin {e.g. 12, 18} + (fbtft only) + drm Use DRM/KMS driver mi0283qt instead of fbtft. + Set the SPI clock to 70MHz. + This has to be the first parameter. + backlight-pwm Use pwm for backlight (drm only). NB: Disables + audio headphone output as that also uses PWM. + + +Name: waveshare-can-fd-hat-mode-a +Info: Overlay for the Waveshare 2-Channel Isolated CAN FD Expansion HAT + for Raspberry Pi, Multi Protections. Use this overlay when the + HAT is configured in Mode A (Default), with can0 on spi0.0 + and can1 on spi1.0. + https://www.waveshare.com/2-ch-can-fd-hat.htm +Load: dtoverlay=waveshare-can-fd-hat-mode-a +Params: <None> + + +Name: waveshare-can-fd-hat-mode-b +Info: Overlay for the Waveshare 2-Channel Isolated CAN FD Expansion HAT + for Raspberry Pi, Multi Protections. Use this overlay when the + HAT is configured in Mode B (requires hardware modification), with + can0 on spi0.0 and can1 on spi0.1. + https://www.waveshare.com/2-ch-can-fd-hat.htm +Load: dtoverlay=waveshare-can-fd-hat-mode-b +Params: <None> + + +Name: wittypi +Info: Configures the wittypi RTC module. +Load: dtoverlay=wittypi,<param>=<val> +Params: led_gpio GPIO for LED (default "17") + led_trigger Choose which activity the LED tracks (default + "default-on") + + +Name: wm8960-soundcard +Info: Overlay for the Waveshare wm8960 soundcard +Load: dtoverlay=wm8960-soundcard,<param>=<val> +Params: alsaname Changes the card name in ALSA + compatible Changes the codec compatibility + + +Name: ws2812-pio +Info: Configures a GPIO pin to drive a string of WS2812 LEDS using pio. It + can be enabled on any RP1 GPIO in bank 0 (0-27). Up to 4 are supported, + assuming nothing else is using PIO. Pi 5 only. +Load: dtoverlay=ws2812-pio,<param>=<val> +Params: brightness Set the initial brightness for the LEDs. The + brightness can be changed at runtime by writing + a single byte to offset 0 of the device. Note + that brightness is a multiplier for the pixel + values, and only white pixels can reach the + maximum visible brightness. (range 0-255, + default 255) + dev_name The name for the /dev/ device entry. Note that + if the name includes '%d' it will be replaced + by the instance number. (default 'leds%d') + gpio Output GPIO (0-27, default 4) + num_leds Number of LEDs (default 60) + rgbw 'rgbw=on' (or 'rgbw') indicates that each pixel + includes a white LED as well as the usual red, + green and blue. (default 'off') + + +Troubleshooting +=============== + +If you are experiencing problems that you think are DT-related, enable DT +diagnostic output by adding this to /boot/config.txt: + + dtdebug=on + +and rebooting. Then run: + + sudo vcdbg log msg + +and look for relevant messages. + +Further reading +=============== + +This is only meant to be a quick introduction to the subject of Device Tree on +Raspberry Pi. There is a more complete explanation here: + +http://www.raspberrypi.org/documentation/configuration/device-tree.md diff --git a/arch/arm/boot/dts/overlays/act-led-overlay.dts b/arch/arm/boot/dts/overlays/act-led-overlay.dts new file mode 100644 index 00000000000000..685e354923a0a5 --- /dev/null +++ b/arch/arm/boot/dts/overlays/act-led-overlay.dts @@ -0,0 +1,28 @@ +/dts-v1/; +/plugin/; + +/* Pi3 uses a GPIO expander to drive the LEDs which can only be accessed + from the VPU. There is a special driver for this with a separate DT node, + which has the unfortunate consequence of breaking the act_led_gpio and + act_led_activelow dtparams. + + This overlay changes the GPIO controller back to the standard one and + restores the dtparams. +*/ + +/{ + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&led_act>; + frag0: __overlay__ { + gpios = <&gpio 0 0>; + }; + }; + + __overrides__ { + gpio = <&frag0>,"gpios:4", + <&frag0>,"status=okay"; + activelow = <&frag0>,"gpios:8"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/adafruit-st7735r-overlay.dts b/arch/arm/boot/dts/overlays/adafruit-st7735r-overlay.dts new file mode 100644 index 00000000000000..6e69bd7fa0318d --- /dev/null +++ b/arch/arm/boot/dts/overlays/adafruit-st7735r-overlay.dts @@ -0,0 +1,83 @@ +/* + * adafruit-st7735r-overlay.dts + * + * ST7735R based SPI LCD displays. Either + * Adafruit 1.8" 160x128 + * or + * Okaya 1.44" 128x128 + */ + +/dts-v1/; +/plugin/; + +#include <dt-bindings/gpio/gpio.h> + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@1 { + target = <&gpio>; + __overlay__ { + adafruit_pins: adafruit_pins { + brcm,pins = <25 24>; + brcm,function = <1>; /* out */ + }; + backlight_pins: backlight_pins { + brcm,pins = <18>; + brcm,function = <1>; /* out */ + }; + }; + }; + + fragment@2 { + target-path = "/"; + __overlay__ { + af18_backlight: backlight { + compatible = "gpio-backlight"; + gpios = <&gpio 18 GPIO_ACTIVE_HIGH>; + pinctrl-names = "default"; + pinctrl-0 = <&backlight_pins>; + }; + }; + }; + + fragment@3 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + af18: adafruit18@0 { + compatible = "jianda,jd-t18003-t01"; + reg = <0>; + spi-max-frequency = <32000000>; + dc-gpios = <&gpio 24 GPIO_ACTIVE_HIGH>; + reset-gpios = <&gpio 25 GPIO_ACTIVE_HIGH>; + rotation = <90>; + pinctrl-names = "default"; + pinctrl-0 = <&adafruit_pins>; + backlight = <&af18_backlight>; + }; + }; + }; + + __overrides__ { + 128x128 = <&af18>, "compatible=okaya,rh128128t"; + speed = <&af18>,"spi-max-frequency:0"; + rotate = <&af18>,"rotation:0"; + dc_pin = <&af18>,"dc-gpios:4", <&adafruit_pins>,"brcm,pins:4"; + reset_pin = <&af18>,"reset-gpios:4", + <&adafruit_pins>,"brcm,pins:0"; + led_pin = <&af18_backlight>,"gpios:4", + <&backlight_pins>,"brcm,pins:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/adafruit18-overlay.dts b/arch/arm/boot/dts/overlays/adafruit18-overlay.dts new file mode 100644 index 00000000000000..e1ce94a8cd3e2a --- /dev/null +++ b/arch/arm/boot/dts/overlays/adafruit18-overlay.dts @@ -0,0 +1,55 @@ +/* + * Device Tree overlay for Adafruit 1.8" TFT LCD with ST7735R chip 160x128 + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@1 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + af18: adafruit18@0 { + compatible = "fbtft,adafruit18"; + reg = <0>; + pinctrl-names = "default"; + spi-max-frequency = <40000000>; + rotate = <90>; + buswidth = <8>; + fps = <50>; + height = <160>; + width = <128>; + reset-gpios = <&gpio 25 1>; + dc-gpios = <&gpio 24 0>; + led-gpios = <&gpio 18 0>; + debug = <0>; + }; + }; + }; + + __overrides__ { + green = <&af18>, "compatible=fbtft,adafruit18_green"; + speed = <&af18>,"spi-max-frequency:0"; + rotate = <&af18>,"rotate:0"; + fps = <&af18>,"fps:0"; + bgr = <&af18>,"bgr?"; + debug = <&af18>,"debug:0"; + dc_pin = <&af18>,"dc-gpios:4"; + reset_pin = <&af18>,"reset-gpios:4"; + led_pin = <&af18>,"led-gpios:4"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/adau1977-adc-overlay.dts b/arch/arm/boot/dts/overlays/adau1977-adc-overlay.dts new file mode 100644 index 00000000000000..cf6d1ef3bfffbc --- /dev/null +++ b/arch/arm/boot/dts/overlays/adau1977-adc-overlay.dts @@ -0,0 +1,40 @@ +// Definitions for ADAU1977 ADC +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2c1>; + + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + adau1977: codec@11 { + compatible = "adi,adau1977"; + reg = <0x11>; + reset-gpios = <&gpio 5 0>; + AVDD-supply = <&vdd_3v3_reg>; + }; + }; + }; + + fragment@1 { + target = <&i2s_clk_consumer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@2 { + target = <&sound>; + __overlay__ { + compatible = "adi,adau1977-adc"; + i2s-controller = <&i2s_clk_consumer>; + status = "okay"; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/adau7002-simple-overlay.dts b/arch/arm/boot/dts/overlays/adau7002-simple-overlay.dts new file mode 100644 index 00000000000000..62e92bd8f9525e --- /dev/null +++ b/arch/arm/boot/dts/overlays/adau7002-simple-overlay.dts @@ -0,0 +1,52 @@ +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_producer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target-path = "/"; + __overlay__ { + adau7002_codec: adau7002-codec { + #sound-dai-cells = <0>; + compatible = "adi,adau7002"; +/* IOVDD-supply = <&supply>;*/ + status = "okay"; + }; + }; + }; + + fragment@2 { + target = <&sound>; + sound_overlay: __overlay__ { + compatible = "simple-audio-card"; + simple-audio-card,format = "i2s"; + simple-audio-card,name = "adau7002"; + simple-audio-card,bitclock-slave = <&dailink0_slave>; + simple-audio-card,frame-slave = <&dailink0_slave>; + simple-audio-card,widgets = + "Microphone", "Microphone Jack"; + simple-audio-card,routing = + "PDM_DAT", "Microphone Jack"; + status = "okay"; + simple-audio-card,cpu { + sound-dai = <&i2s_clk_producer>; + }; + dailink0_slave: simple-audio-card,codec { + sound-dai = <&adau7002_codec>; + }; + }; + }; + + + __overrides__ { + card-name = <&sound_overlay>,"simple-audio-card,name"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/ads1015-overlay.dts b/arch/arm/boot/dts/overlays/ads1015-overlay.dts new file mode 100644 index 00000000000000..dc1764613a8b04 --- /dev/null +++ b/arch/arm/boot/dts/overlays/ads1015-overlay.dts @@ -0,0 +1,98 @@ +/* + * 2016 - Erik Sejr + */ +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + /* ----------- ADS1015 ------------ */ + fragment@0 { + target = <&i2c_arm>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + ads1015: ads1015@48 { + compatible = "ti,ads1015"; + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + reg = <0x48>; + }; + }; + }; + + fragment@1 { + target = <&ads1015>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + channel_a: channel_a { + reg = <4>; + ti,gain = <2>; + ti,datarate = <4>; + }; + }; + }; + + fragment@2 { + target = <&ads1015>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + channel_b: channel_b { + reg = <5>; + ti,gain = <2>; + ti,datarate = <4>; + }; + }; + }; + + fragment@3 { + target = <&ads1015>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + channel_c: channel_c { + reg = <6>; + ti,gain = <2>; + ti,datarate = <4>; + }; + }; + }; + + fragment@4 { + target = <&ads1015>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + channel_d: channel_d { + reg = <7>; + ti,gain = <2>; + ti,datarate = <4>; + }; + }; + }; + + __overrides__ { + addr = <&ads1015>,"reg:0"; + cha_enable = <0>,"=1"; + cha_cfg = <&channel_a>,"reg:0"; + cha_gain = <&channel_a>,"ti,gain:0"; + cha_datarate = <&channel_a>,"ti,datarate:0"; + chb_enable = <0>,"=2"; + chb_cfg = <&channel_b>,"reg:0"; + chb_gain = <&channel_b>,"ti,gain:0"; + chb_datarate = <&channel_b>,"ti,datarate:0"; + chc_enable = <0>,"=3"; + chc_cfg = <&channel_c>,"reg:0"; + chc_gain = <&channel_c>,"ti,gain:0"; + chc_datarate = <&channel_c>,"ti,datarate:0"; + chd_enable = <0>,"=4"; + chd_cfg = <&channel_d>,"reg:0"; + chd_gain = <&channel_d>,"ti,gain:0"; + chd_datarate = <&channel_d>,"ti,datarate:0"; + }; + +}; diff --git a/arch/arm/boot/dts/overlays/ads1115-overlay.dts b/arch/arm/boot/dts/overlays/ads1115-overlay.dts new file mode 100644 index 00000000000000..6a1d86929added --- /dev/null +++ b/arch/arm/boot/dts/overlays/ads1115-overlay.dts @@ -0,0 +1,105 @@ +/* + * TI ADS1115 multi-channel ADC overlay + */ + +/dts-v1/; +/plugin/; + +#include "i2c-buses.dtsi" + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&ads1115>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + channel_a: channel_a { + reg = <4>; + ti,gain = <1>; + ti,datarate = <7>; + }; + }; + }; + + fragment@1 { + target = <&ads1115>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + channel_b: channel_b { + reg = <5>; + ti,gain = <1>; + ti,datarate = <7>; + }; + }; + }; + + fragment@2 { + target = <&ads1115>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + channel_c: channel_c { + reg = <6>; + ti,gain = <1>; + ti,datarate = <7>; + }; + }; + }; + + fragment@3 { + target = <&ads1115>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + channel_d: channel_d { + reg = <7>; + ti,gain = <1>; + ti,datarate = <7>; + }; + }; + }; + + fragment@4 { + target = <&i2cbus>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + ads1115: ads1115@48 { + compatible = "ti,ads1115"; + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + reg = <0x48>; + }; + }; + }; + + __overrides__ { + addr = <&ads1115>,"reg:0"; + cha_enable = <0>,"=0"; + cha_cfg = <&channel_a>,"reg:0"; + cha_gain = <&channel_a>,"ti,gain:0"; + cha_datarate = <&channel_a>,"ti,datarate:0"; + chb_enable = <0>,"=1"; + chb_cfg = <&channel_b>,"reg:0"; + chb_gain = <&channel_b>,"ti,gain:0"; + chb_datarate = <&channel_b>,"ti,datarate:0"; + chc_enable = <0>,"=2"; + chc_cfg = <&channel_c>,"reg:0"; + chc_gain = <&channel_c>,"ti,gain:0"; + chc_datarate = <&channel_c>,"ti,datarate:0"; + chd_enable = <0>,"=3"; + chd_cfg = <&channel_d>,"reg:0"; + chd_gain = <&channel_d>,"ti,gain:0"; + chd_datarate = <&channel_d>,"ti,datarate:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/ads7846-overlay.dts b/arch/arm/boot/dts/overlays/ads7846-overlay.dts new file mode 100644 index 00000000000000..211a002c0b3447 --- /dev/null +++ b/arch/arm/boot/dts/overlays/ads7846-overlay.dts @@ -0,0 +1,89 @@ +/* + * Generic Device Tree overlay for the ADS7846 touch controller + * + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spi0>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@2 { + target = <&spidev1>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@3 { + target = <&gpio>; + __overlay__ { + ads7846_pins: ads7846_pins { + brcm,pins = <255>; /* illegal default value */ + brcm,function = <0>; /* in */ + brcm,pull = <0>; /* none */ + }; + }; + }; + + fragment@4 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + ads7846: ads7846@1 { + compatible = "ti,ads7846"; + reg = <1>; + pinctrl-names = "default"; + pinctrl-0 = <&ads7846_pins>; + + spi-max-frequency = <2000000>; + interrupts = <255 2>; /* high-to-low edge triggered */ + interrupt-parent = <&gpio>; + pendown-gpio = <&gpio 255 1>; + + /* driver defaults */ + ti,x-min = /bits/ 16 <0>; + ti,y-min = /bits/ 16 <0>; + ti,x-max = /bits/ 16 <0x0FFF>; + ti,y-max = /bits/ 16 <0x0FFF>; + ti,pressure-min = /bits/ 16 <0>; + ti,pressure-max = /bits/ 16 <0xFFFF>; + ti,x-plate-ohms = /bits/ 16 <400>; + }; + }; + }; + __overrides__ { + cs = <&ads7846>,"reg:0"; + speed = <&ads7846>,"spi-max-frequency:0"; + penirq = <&ads7846_pins>,"brcm,pins:0", /* REQUIRED */ + <&ads7846>,"interrupts:0", + <&ads7846>,"pendown-gpio:4"; + penirq_pull = <&ads7846_pins>,"brcm,pull:0"; + swapxy = <&ads7846>,"ti,swap-xy?"; + xmin = <&ads7846>,"ti,x-min;0"; + ymin = <&ads7846>,"ti,y-min;0"; + xmax = <&ads7846>,"ti,x-max;0"; + ymax = <&ads7846>,"ti,y-max;0"; + pmin = <&ads7846>,"ti,pressure-min;0"; + pmax = <&ads7846>,"ti,pressure-max;0"; + xohms = <&ads7846>,"ti,x-plate-ohms;0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/adv7282m-overlay.dts b/arch/arm/boot/dts/overlays/adv7282m-overlay.dts new file mode 100644 index 00000000000000..a9eb75a30825fe --- /dev/null +++ b/arch/arm/boot/dts/overlays/adv7282m-overlay.dts @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Definitions for Analog Devices ADV7282-M video to CSI2 bridge on VC I2C bus +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2c_csi_dsi>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + adv728x: adv728x@21 { + compatible = "adi,adv7282-m"; + reg = <0x21>; + status = "okay"; + clock-frequency = <24000000>; + port { + adv728x_0: endpoint { + remote-endpoint = <&csi1_ep>; + clock-lanes = <0>; + data-lanes = <1>; + link-frequencies = + /bits/ 64 <297000000>; + + mclk-frequency = <12000000>; + }; + }; + }; + }; + }; + fragment@1 { + target = <&csi1>; + __overlay__ { + status = "okay"; + + port { + csi1_ep: endpoint { + remote-endpoint = <&adv728x_0>; + data-lanes = <1>; + }; + }; + }; + }; + fragment@2 { + target = <&i2c0if>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@3 { + target = <&i2c0mux>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@4 { + target = <&csi1>; + __overlay__ { + compatible = "brcm,bcm2835-unicam-legacy"; + }; + }; + + __overrides__ { + addr = <&adv728x>,"reg:0"; + media-controller = <0>,"!4"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/adv728x-m-overlay.dts b/arch/arm/boot/dts/overlays/adv728x-m-overlay.dts new file mode 100644 index 00000000000000..ea392e886984be --- /dev/null +++ b/arch/arm/boot/dts/overlays/adv728x-m-overlay.dts @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Definitions for Analog Devices ADV728[0|1|2]-M video to CSI2 bridges on VC +// I2C bus + +#include "adv7282m-overlay.dts" + +/{ + compatible = "brcm,bcm2835"; + + // Fragment numbers deliberately high to avoid conflicts with the + // included adv7282m overlay file. + + fragment@101 { + target = <&adv728x>; + __dormant__ { + compatible = "adi,adv7280-m"; + }; + }; + fragment@102 { + target = <&adv728x>; + __dormant__ { + compatible = "adi,adv7281-m"; + }; + }; + fragment@103 { + target = <&adv728x>; + __dormant__ { + compatible = "adi,adv7281-ma"; + }; + }; + + __overrides__ { + adv7280m = <0>, "+101"; + adv7281m = <0>, "+102"; + adv7281ma = <0>, "+103"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/akkordion-iqdacplus-overlay.dts b/arch/arm/boot/dts/overlays/akkordion-iqdacplus-overlay.dts new file mode 100644 index 00000000000000..d867146bcb8ff7 --- /dev/null +++ b/arch/arm/boot/dts/overlays/akkordion-iqdacplus-overlay.dts @@ -0,0 +1,49 @@ +// Definitions for Digital Dreamtime Akkordion using IQaudIO DAC+ or DACZero +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_producer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + pcm5122@4c { + #sound-dai-cells = <0>; + compatible = "ti,pcm5122"; + reg = <0x4c>; + AVDD-supply = <&vdd_3v3_reg>; + DVDD-supply = <&vdd_3v3_reg>; + CPVDD-supply = <&vdd_3v3_reg>; + status = "okay"; + }; + }; + }; + + fragment@2 { + target = <&sound>; + frag2: __overlay__ { + compatible = "iqaudio,iqaudio-dac"; + card_name = "Akkordion"; + dai_name = "IQaudIO DAC"; + dai_stream_name = "IQaudIO DAC HiFi"; + i2s-controller = <&i2s_clk_producer>; + status = "okay"; + }; + }; + + __overrides__ { + 24db_digital_gain = <&frag2>,"iqaudio,24db_digital_gain?"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/allo-boss-dac-pcm512x-audio-overlay.dts b/arch/arm/boot/dts/overlays/allo-boss-dac-pcm512x-audio-overlay.dts new file mode 100644 index 00000000000000..16806945890ba5 --- /dev/null +++ b/arch/arm/boot/dts/overlays/allo-boss-dac-pcm512x-audio-overlay.dts @@ -0,0 +1,61 @@ +/* + * Definitions for Allo Boss DAC board + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target-path = "/"; + __overlay__ { + boss_osc: boss_osc { + compatible = "allo,dac-clk"; + #clock-cells = <0>; + }; + }; + }; + + frag1: fragment@1 { + target = <&i2s_clk_consumer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@2 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + pcm5122@4d { + #sound-dai-cells = <0>; + compatible = "ti,pcm5122"; + clocks = <&boss_osc>; + reg = <0x4d>; + status = "okay"; + }; + }; + }; + + fragment@3 { + target = <&sound>; + boss_dac: __overlay__ { + compatible = "allo,boss-dac"; + i2s-controller = <&i2s_clk_consumer>; + mute-gpios = <&gpio 6 1>; + status = "okay"; + }; + }; + + __overrides__ { + 24db_digital_gain = <&boss_dac>,"allo,24db_digital_gain?"; + slave = <&boss_dac>,"allo,slave?", + <&frag1>,"target:0=",<&i2s_clk_producer>, + <&boss_dac>,"i2s-controller:0=",<&i2s_clk_producer>; + }; +}; diff --git a/arch/arm/boot/dts/overlays/allo-boss2-dac-audio-overlay.dts b/arch/arm/boot/dts/overlays/allo-boss2-dac-audio-overlay.dts new file mode 100644 index 00000000000000..feac2b091b3650 --- /dev/null +++ b/arch/arm/boot/dts/overlays/allo-boss2-dac-audio-overlay.dts @@ -0,0 +1,57 @@ +/* * Definitions for Allo Boss2 DAC boards + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_consumer>; + __overlay__ { + #sound-dai-cells = <0>; + status = "okay"; + cpu_port: port { + cpu_endpoint: endpoint { + remote-endpoint = <&codec_endpoint>; + bitclock-master = <&codec_endpoint>; + frame-master = <&codec_endpoint>; + dai-format = "i2s"; + }; + }; + }; + }; + + fragment@1 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + allo-cs43130@30 { + #sound-dai-cells = <0>; + compatible = "allo,allo-cs43198"; + clock44-gpio = <&gpio 5 0>; + clock48-gpio = <&gpio 6 0>; + reg = <0x30>; + port { + codec_endpoint: endpoint { + remote-endpoint = <&cpu_endpoint>; + }; + }; + }; + }; + }; + + fragment@2 { + target = <&sound>; + boss2_dac: __overlay__ { + compatible = "audio-graph-card"; + label = "Allo Boss2"; + dais = <&cpu_port>; + status = "okay"; + }; + }; +}; + diff --git a/arch/arm/boot/dts/overlays/allo-digione-overlay.dts b/arch/arm/boot/dts/overlays/allo-digione-overlay.dts new file mode 100644 index 00000000000000..61c3c2e9fbd83f --- /dev/null +++ b/arch/arm/boot/dts/overlays/allo-digione-overlay.dts @@ -0,0 +1,44 @@ +// Definitions for Allo DigiOne +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_consumer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + wm8804@3b { + #sound-dai-cells = <0>; + compatible = "wlf,wm8804"; + reg = <0x3b>; + PVDD-supply = <&vdd_3v3_reg>; + DVDD-supply = <&vdd_3v3_reg>; + status = "okay"; + wlf,reset-gpio = <&gpio 17 0>; + }; + }; + }; + + fragment@2 { + target = <&sound>; + __overlay__ { + compatible = "allo,allo-digione"; + i2s-controller = <&i2s_clk_consumer>; + status = "okay"; + clock44-gpio = <&gpio 5 0>; + clock48-gpio = <&gpio 6 0>; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/allo-katana-dac-audio-overlay.dts b/arch/arm/boot/dts/overlays/allo-katana-dac-audio-overlay.dts new file mode 100644 index 00000000000000..1ebb6bc6b90730 --- /dev/null +++ b/arch/arm/boot/dts/overlays/allo-katana-dac-audio-overlay.dts @@ -0,0 +1,58 @@ +/* + * Definitions for Allo Katana DAC boards + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_consumer>; + __overlay__ { + #sound-dai-cells = <0>; + status = "okay"; + cpu_port: port { + cpu_endpoint: endpoint { + remote-endpoint = <&codec_endpoint>; + bitclock-master = <&codec_endpoint>; + frame-master = <&codec_endpoint>; + dai-format = "i2s"; + }; + }; + }; + }; + + fragment@1 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + clock-frequency = <50000>; + + allo-katana-codec@30 { + #sound-dai-cells = <0>; + compatible = "allo,allo-katana-codec"; + reg = <0x30>; + port { + codec_endpoint: endpoint { + remote-endpoint = <&cpu_endpoint>; + }; + }; + }; + }; + }; + + fragment@2 { + target = <&sound>; + katana_dac: __overlay__ { + compatible = "audio-graph-card"; + label = "Allo Katana"; + dais = <&cpu_port>; + status = "okay"; + }; + }; +}; + diff --git a/arch/arm/boot/dts/overlays/allo-piano-dac-pcm512x-audio-overlay.dts b/arch/arm/boot/dts/overlays/allo-piano-dac-pcm512x-audio-overlay.dts new file mode 100644 index 00000000000000..1b79ef1df2a1d9 --- /dev/null +++ b/arch/arm/boot/dts/overlays/allo-piano-dac-pcm512x-audio-overlay.dts @@ -0,0 +1,54 @@ +/* + * Definitions for Allo Piano DAC (2.0/2.1) boards + * + * NB. The Piano DAC 2.1 board contains 2x TI PCM5142 DAC's. One DAC is stereo + * (left/right) and the other provides a subwoofer output, using DSP on the + * chip for digital high/low pass crossover. + * The initial support for this hardware, that doesn't require any codec driver + * modifications, uses only one DAC chip for stereo (left/right) output, the + * chip with 0x4c slave address. The other chip at 0x4d is currently ignored! + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_producer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + pcm5142@4c { + #sound-dai-cells = <0>; + compatible = "ti,pcm5142"; + reg = <0x4c>; + status = "okay"; + }; + }; + }; + + fragment@2 { + target = <&sound>; + piano_dac: __overlay__ { + compatible = "allo,piano-dac"; + i2s-controller = <&i2s_clk_producer>; + status = "okay"; + }; + }; + + __overrides__ { + 24db_digital_gain = + <&piano_dac>,"allo,24db_digital_gain?"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/allo-piano-dac-plus-pcm512x-audio-overlay.dts b/arch/arm/boot/dts/overlays/allo-piano-dac-plus-pcm512x-audio-overlay.dts new file mode 100644 index 00000000000000..d17c9c10df3981 --- /dev/null +++ b/arch/arm/boot/dts/overlays/allo-piano-dac-plus-pcm512x-audio-overlay.dts @@ -0,0 +1,57 @@ +// Definitions for Piano DAC +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_producer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + allo_pcm5122_4c: pcm5122@4c { + #sound-dai-cells = <0>; + compatible = "ti,pcm5122"; + reg = <0x4c>; + sound-name-prefix = "Main"; + status = "okay"; + }; + allo_pcm5122_4d: pcm5122@4d { + #sound-dai-cells = <0>; + compatible = "ti,pcm5122"; + reg = <0x4d>; + sound-name-prefix = "Sub"; + status = "okay"; + }; + }; + }; + + fragment@2 { + target = <&sound>; + piano_dac: __overlay__ { + compatible = "allo,piano-dac-plus"; + audio-codec = <&allo_pcm5122_4c &allo_pcm5122_4d>; + i2s-controller = <&i2s_clk_producer>; + mute1-gpios = <&gpio 6 1>; + mute2-gpios = <&gpio 25 1>; + status = "okay"; + }; + }; + + __overrides__ { + 24db_digital_gain = + <&piano_dac>,"allo,24db_digital_gain?"; + glb_mclk = + <&piano_dac>,"allo,glb_mclk?"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/anyspi-overlay.dts b/arch/arm/boot/dts/overlays/anyspi-overlay.dts new file mode 100755 index 00000000000000..87523dcca318cf --- /dev/null +++ b/arch/arm/boot/dts/overlays/anyspi-overlay.dts @@ -0,0 +1,205 @@ +/* + * Universal device tree overlay for SPI devices + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spidev0>; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@1 { + target = <&spidev1>; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@2 { + target-path = "spi1/spidev@0"; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@3 { + target-path = "spi1/spidev@1"; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@4 { + target-path = "spi1/spidev@2"; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@5 { + target-path = "spi2/spidev@0"; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@6 { + target-path = "spi2/spidev@1"; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@7 { + target-path = "spi2/spidev@2"; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@8 { + target = <&spi0>; + __dormant__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + anyspi_00: anyspi@0 { + reg = <0>; + spi-max-frequency = <500000>; + }; + }; + }; + + fragment@9 { + target = <&spi0>; + __dormant__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + anyspi_01: anyspi@1 { + reg = <1>; + spi-max-frequency = <500000>; + }; + }; + }; + + fragment@10 { + target = <&spi1>; + __dormant__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + anyspi_10: anyspi@0 { + reg = <0>; + spi-max-frequency = <500000>; + }; + }; + }; + + fragment@11 { + target = <&spi1>; + __dormant__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + anyspi_11: anyspi@1 { + reg = <1>; + spi-max-frequency = <500000>; + }; + }; + }; + + fragment@12 { + target = <&spi1>; + __dormant__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + anyspi_12: anyspi@2 { + reg = <2>; + spi-max-frequency = <500000>; + }; + }; + }; + + fragment@13 { + target = <&spi2>; + __dormant__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + anyspi_20: anyspi@0 { + reg = <0>; + spi-max-frequency = <500000>; + }; + }; + }; + + fragment@14 { + target = <&spi2>; + __dormant__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + anyspi_21: anyspi@1 { + reg = <1>; + spi-max-frequency = <500000>; + }; + }; + }; + + fragment@15 { + target = <&spi2>; + __dormant__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + anyspi_22: anyspi@2 { + reg = <2>; + spi-max-frequency = <500000>; + }; + }; + }; + + __overrides__ { + spi0-0 = <0>, "+0+8"; + spi0-1 = <0>, "+1+9"; + spi1-0 = <0>, "+2+10"; + spi1-1 = <0>, "+3+11"; + spi1-2 = <0>, "+4+12"; + spi2-0 = <0>, "+5+13"; + spi2-1 = <0>, "+6+14"; + spi2-2 = <0>, "+7+15"; + dev = <&anyspi_00>,"compatible", + <&anyspi_01>,"compatible", + <&anyspi_10>,"compatible", + <&anyspi_11>,"compatible", + <&anyspi_12>,"compatible", + <&anyspi_20>,"compatible", + <&anyspi_21>,"compatible", + <&anyspi_22>,"compatible"; + speed = <&anyspi_00>, "spi-max-frequency:0", + <&anyspi_01>, "spi-max-frequency:0", + <&anyspi_10>, "spi-max-frequency:0", + <&anyspi_11>, "spi-max-frequency:0", + <&anyspi_12>, "spi-max-frequency:0", + <&anyspi_20>, "spi-max-frequency:0", + <&anyspi_21>, "spi-max-frequency:0", + <&anyspi_22>, "spi-max-frequency:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/apds9960-overlay.dts b/arch/arm/boot/dts/overlays/apds9960-overlay.dts new file mode 100644 index 00000000000000..bb18cca1ac664f --- /dev/null +++ b/arch/arm/boot/dts/overlays/apds9960-overlay.dts @@ -0,0 +1,55 @@ +// Definitions for APDS-9960 ambient light and gesture sensor + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2c1>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&gpio>; + __overlay__ { + apds9960_pins: apds9960_pins@39 { + brcm,pins = <4>; + brcm,function = <0>; + }; + }; + }; + + fragment@2 { + target = <&apds9960>; + apds9960_irq: __overlay__ { + #interrupt-cells = <2>; + interrupt-parent = <&gpio>; + interrupts = <4 1>; + }; + }; + + fragment@3 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + + apds9960: apds@39 { + compatible = "avago,apds9960"; + reg = <0x39>; + status = "okay"; + }; + }; + }; + + __overrides__ { + gpiopin = <&apds9960_pins>,"brcm,pins:0", + <&apds9960_irq>,"interrupts:0"; + noints = <0>,"!1!2"; + }; +}; + diff --git a/arch/arm/boot/dts/overlays/applepi-dac-overlay.dts b/arch/arm/boot/dts/overlays/applepi-dac-overlay.dts new file mode 100644 index 00000000000000..cb7649d3a6133a --- /dev/null +++ b/arch/arm/boot/dts/overlays/applepi-dac-overlay.dts @@ -0,0 +1,57 @@ +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&sound>; + __overlay__ { + compatible = "simple-audio-card"; + simple-audio-card,name = "ApplePi-DAC"; + + status = "okay"; + + playback_link: simple-audio-card,dai-link@1 { + format = "i2s"; + + p_cpu_dai: cpu { + sound-dai = <&i2s_clk_producer>; + dai-tdm-slot-num = <2>; + dai-tdm-slot-width = <32>; + }; + + p_codec_dai: codec { + sound-dai = <&codec_out>; + }; + }; + }; + }; + + fragment@1 { + target-path = "/"; + __overlay__ { + codec_out: pcm1794a-codec { + #sound-dai-cells = <0>; + compatible = "ti,pcm1794a"; + status = "okay"; + }; + }; + }; + + fragment@2 { + target = <&i2s_clk_producer>; + __overlay__ { + #sound-dai-cells = <0>; + status = "okay"; + }; + }; +}; + +/* + Written by: Leonid Ayzenshtat + Company: Orchard Audio (www.orchardaudio.com) + + compile with: + dtc -@ -H epapr -O dtb -o ApplePi-DAC.dtbo -W no-unit_address_vs_reg ApplePi-DAC.dts +*/ diff --git a/arch/arm/boot/dts/overlays/arducam-64mp-overlay.dts b/arch/arm/boot/dts/overlays/arducam-64mp-overlay.dts new file mode 100644 index 00000000000000..3f3d7858f2fdc7 --- /dev/null +++ b/arch/arm/boot/dts/overlays/arducam-64mp-overlay.dts @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Definitions for Arducam 64MP camera module on VC I2C bus +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2835"; + + i2c_frag: fragment@0 { + target = <&i2c_csi_dsi>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + #include "arducam-64mp.dtsi" + }; + }; + + csi_frag: fragment@1 { + target = <&csi1>; + csi: __overlay__ { + status = "okay"; + + port{ + csi_ep: endpoint{ + remote-endpoint = <&cam_endpoint>; + clock-lanes = <0>; + data-lanes = <1 2>; + clock-noncontinuous; + }; + }; + }; + }; + + fragment@2 { + target = <&i2c0if>; + __overlay__ { + status = "okay"; + }; + }; + + clk_frag: fragment@3 { + target = <&cam1_clk>; + __overlay__ { + clock-frequency = <24000000>; + status = "okay"; + }; + }; + + fragment@4 { + target = <&i2c0mux>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@5 { + target = <&cam_node>; + __overlay__ { + lens-focus = <&vcm_node>; + }; + }; + + fragment@6 { + target = <&csi1>; + __dormant__ { + compatible = "brcm,bcm2835-unicam-legacy"; + }; + }; + + __overrides__ { + rotation = <&cam_node>,"rotation:0"; + orientation = <&cam_node>,"orientation:0"; + media-controller = <0>,"!6"; + cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>, + <&csi_frag>, "target:0=",<&csi0>, + <&clk_frag>, "target:0=",<&cam0_clk>, + <&cam_node>, "clocks:0=",<&cam0_clk>, + <&cam_node>, "VANA-supply:0=",<&cam0_reg>, + <&vcm_node>, "VDD-supply:0=", <&cam0_reg>; + vcm = <&vcm_node>, "status", + <0>, "=5"; + }; +}; + +&cam_node { + status = "okay"; +}; + +&cam_endpoint { + remote-endpoint = <&csi_ep>; +}; + +&vcm_node { + status = "okay"; +}; diff --git a/arch/arm/boot/dts/overlays/arducam-64mp.dtsi b/arch/arm/boot/dts/overlays/arducam-64mp.dtsi new file mode 100644 index 00000000000000..ed9f2e50c287c1 --- /dev/null +++ b/arch/arm/boot/dts/overlays/arducam-64mp.dtsi @@ -0,0 +1,34 @@ +// Fragment that configures a Arducam64MP + +cam_node: arducam_64mp@1a { + compatible = "arducam,64mp"; + reg = <0x1a>; + status = "disabled"; + + clocks = <&cam1_clk>; + clock-names = "xclk"; + + VANA-supply = <&cam1_reg>; /* 2.8v */ + VDIG-supply = <&cam_dummy_reg>; /* 1.8v */ + VDDL-supply = <&cam_dummy_reg>; /* 1.2v */ + + rotation = <0>; + orientation = <2>; + + port { + cam_endpoint: endpoint { + clock-lanes = <0>; + data-lanes = <1 2>; + clock-noncontinuous; + link-frequencies = + /bits/ 64 <456000000>; + }; + }; +}; + +vcm_node: dw9817_arducam64mp@c { + compatible = "dongwoon,dw9817-vcm"; + reg = <0x0c>; + status = "disabled"; + VDD-supply = <&cam1_reg>; +}; diff --git a/arch/arm/boot/dts/overlays/arducam-pivariety-overlay.dts b/arch/arm/boot/dts/overlays/arducam-pivariety-overlay.dts new file mode 100644 index 00000000000000..752022786f7077 --- /dev/null +++ b/arch/arm/boot/dts/overlays/arducam-pivariety-overlay.dts @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Definitions for Arducam Pivariety camera module on VC I2C bus +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2835"; + + i2c_frag: fragment@0 { + target = <&i2c_csi_dsi>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + arducam_pivariety: arducam_pivariety@c { + compatible = "arducam,arducam-pivariety"; + reg = <0x0c>; + status = "okay"; + + clocks = <&cam1_clk>; + clock-names = "xclk"; + + VANA-supply = <&cam1_reg>; /* 2.8v */ + VDIG-supply = <&cam_dummy_reg>; /* 1.8v */ + VDDL-supply = <&cam_dummy_reg>; /* 1.2v */ + + rotation = <0>; + orientation = <2>; + + port { + arducam_pivariety_0: endpoint { + remote-endpoint = <&csi1_ep>; + clock-lanes = <0>; + data-lanes = <1 2>; + clock-noncontinuous; + link-frequencies = + /bits/ 64 <493500000>; + }; + }; + }; + }; + }; + + csi_frag: fragment@1 { + target = <&csi1>; + csi: __overlay__ { + status = "okay"; + + port{ + csi1_ep: endpoint{ + remote-endpoint = <&arducam_pivariety_0>; + clock-lanes = <0>; + data-lanes = <1 2>; + clock-noncontinuous; + }; + }; + }; + }; + + fragment@2 { + target = <&i2c0if>; + __overlay__ { + status = "okay"; + }; + }; + + clk_frag: fragment@3 { + target = <&cam1_clk>; + __overlay__ { + clock-frequency = <24000000>; + status = "okay"; + }; + }; + + fragment@4 { + target = <&i2c0mux>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@5 { + target = <&csi1>; + __dormant__ { + compatible = "brcm,bcm2835-unicam-legacy"; + }; + }; + + __overrides__ { + rotation = <&arducam_pivariety>,"rotation:0"; + orientation = <&arducam_pivariety>,"orientation:0"; + media-controller = <0>,"!5"; + cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>, + <&csi_frag>, "target:0=",<&csi0>, + <&clk_frag>, "target:0=",<&cam0_clk>, + <&arducam_pivariety>, "clocks:0=",<&cam0_clk>, + <&arducam_pivariety>, "VANA-supply:0=",<&cam0_reg>; + }; +}; diff --git a/arch/arm/boot/dts/overlays/at86rf233-overlay.dts b/arch/arm/boot/dts/overlays/at86rf233-overlay.dts new file mode 100644 index 00000000000000..5a3f4571ee789f --- /dev/null +++ b/arch/arm/boot/dts/overlays/at86rf233-overlay.dts @@ -0,0 +1,57 @@ +/dts-v1/; +/plugin/; + +/* Overlay for Atmel AT86RF233 IEEE 802.15.4 WPAN transceiver on spi0.0 */ + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spi0>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + + status = "okay"; + + lowpan0: at86rf233@0 { + compatible = "atmel,at86rf233"; + reg = <0>; + interrupt-parent = <&gpio>; + interrupts = <23 4>; /* active high */ + reset-gpio = <&gpio 24 1>; + sleep-gpio = <&gpio 25 1>; + spi-max-frequency = <3000000>; + xtal-trim = /bits/ 8 <0xf>; + }; + }; + }; + + fragment@1 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@2 { + target = <&gpio>; + __overlay__ { + lowpan0_pins: lowpan0_pins { + brcm,pins = <23 24 25>; + brcm,function = <0 1 1>; /* in out out */ + }; + }; + }; + + __overrides__ { + interrupt = <&lowpan0>, "interrupts:0", + <&lowpan0_pins>, "brcm,pins:0"; + reset = <&lowpan0>, "reset-gpio:4", + <&lowpan0_pins>, "brcm,pins:4"; + sleep = <&lowpan0>, "sleep-gpio:4", + <&lowpan0_pins>, "brcm,pins:8"; + speed = <&lowpan0>, "spi-max-frequency:0"; + trim = <&lowpan0>, "xtal-trim.0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/audioinjector-addons-overlay.dts b/arch/arm/boot/dts/overlays/audioinjector-addons-overlay.dts new file mode 100644 index 00000000000000..af72ea0b706afb --- /dev/null +++ b/arch/arm/boot/dts/overlays/audioinjector-addons-overlay.dts @@ -0,0 +1,60 @@ +// Definitions for audioinjector.net audio add on soundcard +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_producer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target-path = "/"; + __overlay__ { + cs42448_mclk: codec-mclk { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <49152000>; + }; + }; + }; + + fragment@2 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + cs42448: cs42448@48 { + #sound-dai-cells = <0>; + compatible = "cirrus,cs42448"; + reg = <0x48>; + clocks = <&cs42448_mclk>; + clock-names = "mclk"; + status = "okay"; + }; + }; + }; + + fragment@3 { + target = <&sound>; + snd: __overlay__ { + compatible = "ai,audioinjector-octo-soundcard"; + mult-gpios = <&gpio 27 0>, <&gpio 22 0>, <&gpio 23 0>, + <&gpio 24 0>; + reset-gpios = <&gpio 5 0>; + i2s-controller = <&i2s_clk_producer>; + codec = <&cs42448>; + status = "okay"; + }; + }; + + __overrides__ { + non-stop-clocks = <&snd>, "non-stop-clocks?"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/audioinjector-bare-i2s-overlay.dts b/arch/arm/boot/dts/overlays/audioinjector-bare-i2s-overlay.dts new file mode 100644 index 00000000000000..a536fbb1a985cf --- /dev/null +++ b/arch/arm/boot/dts/overlays/audioinjector-bare-i2s-overlay.dts @@ -0,0 +1,50 @@ +// Definitions for audioinjector.net audio soundcard +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_producer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target-path = "/"; + __overlay__ { + codec_bare: codec_bare { + compatible = "linux,spdif-dit"; + #sound-dai-cells = <0>; + status = "okay"; + }; + }; + }; + + fragment@2 { + target = <&sound>; + __overlay__ { + compatible = "simple-audio-card"; + i2s-controller = <&i2s_clk_producer>; + status = "okay"; + + simple-audio-card,name = "audioinjector-bare"; + simple-audio-card,format = "i2s"; + + simple-audio-card,bitclock-master = <&dailink0_master>; + simple-audio-card,frame-master = <&dailink0_master>; + + dailink0_master: simple-audio-card,cpu { + sound-dai = <&i2s_clk_producer>; + dai-tdm-slot-num = <2>; + dai-tdm-slot-width = <32>; + }; + + snd_codec: simple-audio-card,codec { + sound-dai = <&codec_bare>; + }; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/audioinjector-isolated-soundcard-overlay.dts b/arch/arm/boot/dts/overlays/audioinjector-isolated-soundcard-overlay.dts new file mode 100644 index 00000000000000..89faed778fcb20 --- /dev/null +++ b/arch/arm/boot/dts/overlays/audioinjector-isolated-soundcard-overlay.dts @@ -0,0 +1,55 @@ +// Definitions for audioinjector.net audio isolated soundcard +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_consumer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target-path = "/"; + __overlay__ { + cs4272_mclk: codec-mclk { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <24576000>; + }; + }; + }; + + fragment@2 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + cs4272: cs4271@10 { + #sound-dai-cells = <0>; + compatible = "cirrus,cs4271"; + reg = <0x10>; + reset-gpio = <&gpio 5 0>; + clocks = <&cs4272_mclk>; + clock-names = "mclk"; + status = "okay"; + }; + }; + }; + + fragment@3 { + target = <&sound>; + snd: __overlay__ { + compatible = "ai,audioinjector-isolated-soundcard"; + mute-gpios = <&gpio 17 0>; + i2s-controller = <&i2s_clk_consumer>; + codec = <&cs4272>; + status = "okay"; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/audioinjector-ultra-overlay.dts b/arch/arm/boot/dts/overlays/audioinjector-ultra-overlay.dts new file mode 100644 index 00000000000000..ee79441187bd56 --- /dev/null +++ b/arch/arm/boot/dts/overlays/audioinjector-ultra-overlay.dts @@ -0,0 +1,71 @@ +// Definitions for audioinjector.net audio add on soundcard +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_consumer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + cs4265: cs4265@4e { + #sound-dai-cells = <0>; + compatible = "cirrus,cs4265"; + reg = <0x4e>; + reset-gpios = <&gpio 5 0>; + status = "okay"; + }; + }; + }; + + fragment@2 { + target = <&sound>; + __overlay__ { + compatible = "simple-audio-card"; + i2s-controller = <&i2s_clk_consumer>; + status = "okay"; + + simple-audio-card,name = "audioinjector-ultra"; + + simple-audio-card,widgets = + "Line", "OUTPUTS", + "Line", "INPUTS"; + + simple-audio-card,routing = + "OUTPUTS","LINEOUTL", + "OUTPUTS","LINEOUTR", + "OUTPUTS","SPDIFOUT", + "LINEINL","INPUTS", + "LINEINR","INPUTS", + "MICL","INPUTS", + "MICR","INPUTS"; + + simple-audio-card,format = "i2s"; + + simple-audio-card,bitclock-master = <&sound_master>; + simple-audio-card,frame-master = <&sound_master>; + + simple-audio-card,cpu { + sound-dai = <&i2s_clk_consumer>; + dai-tdm-slot-num = <2>; + dai-tdm-slot-width = <32>; + }; + + sound_master: simple-audio-card,codec { + sound-dai = <&cs4265>; + system-clock-frequency = <12288000>; + }; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/audioinjector-wm8731-audio-overlay.dts b/arch/arm/boot/dts/overlays/audioinjector-wm8731-audio-overlay.dts new file mode 100644 index 00000000000000..417353b2798e77 --- /dev/null +++ b/arch/arm/boot/dts/overlays/audioinjector-wm8731-audio-overlay.dts @@ -0,0 +1,39 @@ +// Definitions for audioinjector.net audio add on soundcard +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_consumer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + wm8731@1a { + #sound-dai-cells = <0>; + compatible = "wlf,wm8731"; + reg = <0x1a>; + status = "okay"; + }; + }; + }; + + fragment@2 { + target = <&sound>; + __overlay__ { + compatible = "ai,audioinjector-pi-soundcard"; + i2s-controller = <&i2s_clk_consumer>; + status = "okay"; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/audiosense-pi-overlay.dts b/arch/arm/boot/dts/overlays/audiosense-pi-overlay.dts new file mode 100644 index 00000000000000..a89d38b2fe197f --- /dev/null +++ b/arch/arm/boot/dts/overlays/audiosense-pi-overlay.dts @@ -0,0 +1,82 @@ +// Definitions for audiosense add on soundcard +/dts-v1/; +/plugin/; +#include <dt-bindings/pinctrl/bcm2835.h> +#include <dt-bindings/gpio/gpio.h> + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_consumer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target-path = "/"; + __overlay__ { + codec_reg_1v8: codec-reg-1v8 { + compatible = "regulator-fixed"; + regulator-name = "tlv320aic3204_1v8"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; + }; + + /* audio external oscillator */ + codec_osc: codec_osc { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <12000000>; /* 12 MHz */ + }; + }; + }; + + fragment@2 { + target = <&gpio>; + __overlay__ { + codec_rst: codec-rst { + brcm,pins = <26>; + brcm,function = <BCM2835_FSEL_GPIO_OUT>; + }; + }; + }; + + fragment@3 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + codec: tlv320aic32x4@18 { + #sound-dai-cells = <0>; + compatible = "ti,tlv320aic32x4"; + reg = <0x18>; + + clocks = <&codec_osc>; + clock-names = "mclk"; + + iov-supply = <&vdd_3v3_reg>; + ldoin-supply = <&vdd_3v3_reg>; + + gpio-controller; + #gpio-cells = <2>; + reset-gpios = <&gpio 26 GPIO_ACTIVE_HIGH>; + + status = "okay"; + }; + }; + }; + + fragment@4 { + target = <&sound>; + __overlay__ { + compatible = "as,audiosense-pi"; + i2s-controller = <&i2s_clk_consumer>; + status = "okay"; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/audremap-overlay.dts b/arch/arm/boot/dts/overlays/audremap-overlay.dts new file mode 100644 index 00000000000000..95027c5c8f9e75 --- /dev/null +++ b/arch/arm/boot/dts/overlays/audremap-overlay.dts @@ -0,0 +1,38 @@ +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&audio_pins>; + frag0: __overlay__ { + brcm,pins = <12 13>; + brcm,function = <4>; /* alt0 alt0 */ + }; + }; + + fragment@1 { + target = <&chosen>; + __overlay__ { + bootargs = "snd_bcm2835.enable_headphones=1"; + }; + }; + + __overrides__ { + swap_lr = <&frag0>, "swap_lr?"; + enable_jack = <&frag0>, "enable_jack?"; + pins_12_13 = <&frag0>,"brcm,pins:0=12", + <&frag0>,"brcm,pins:4=13", + <&frag0>,"brcm,function:0=4"; + pins_18_19 = <&frag0>,"brcm,pins:0=18", + <&frag0>,"brcm,pins:4=19", + <&frag0>,"brcm,function:0=2"; + pins_40_41 = <&frag0>,"brcm,pins:0=40", + <&frag0>,"brcm,pins:4=41", + <&frag0>,"brcm,function:0=4"; + pins_40_45 = <&frag0>,"brcm,pins:0=40", + <&frag0>,"brcm,pins:4=45", + <&frag0>,"brcm,function:0=4"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/audremap-pi5-overlay.dts b/arch/arm/boot/dts/overlays/audremap-pi5-overlay.dts new file mode 100644 index 00000000000000..aa74ecd26f15ff --- /dev/null +++ b/arch/arm/boot/dts/overlays/audremap-pi5-overlay.dts @@ -0,0 +1,65 @@ +/dts-v1/; +/plugin/; + +/* + * Raspberry Pi 5 has a different Audio Out hardware from earlier Raspberry Pis. + * It can output only to GPIOs 12 and 13. We therefore enable it *only* when + * that particular GPIO mapping is requested here. To make it work with ASOC + * we must also define a "dummy" codec and a generic audio card. + */ + +/ { + compatible = "brcm,bcm2712"; + + fragment@0 { + target = <&rp1_audio_out>; + frag0: __overlay__ { + pinctrl-0 = <&rp1_audio_out_12_13>; + pinctrl-names = "default"; + status = "ok"; + }; + }; + + fragment@1 { + target-path = "/"; + __overlay__ { + + rp1_audio_out_codec: rp1_audio_out_codec@0 { + compatible = "linux,spdif-dit"; + #sound-dai-cells = <0>; + status = "ok"; + }; + + rp1_audio_out_simple_card@0 { + compatible = "simple-audio-card"; + simple-audio-card,name = "RP1-Audio-Out"; + #address-cells = <1>; + #size-cells = <0>; + status = "ok"; + + simple-audio-card,dai-link@0 { + reg = <0>; + format = "left_j"; + bitclock-master = <&sndcpu0>; + frame-master = <&sndcpu0>; + + sndcpu0: cpu { + sound-dai = <&rp1_audio_out>; + }; + + codec { + sound-dai = <&rp1_audio_out_codec>; + }; + }; + }; + }; + }; + + __overrides__ { + swap_lr = <&frag0>, "swap_lr?"; + pins_12_13 = <0>, "+0+1"; /* this is the default */ + pins_18_19 = <0>, "-0-1"; /* sorry not supported */ + pins_40_41 = <0>, "-0-1"; /* sorry not supported */ + pins_40_45 = <0>, "-0-1"; /* sorry not supported */ + }; +}; diff --git a/arch/arm/boot/dts/overlays/balena-fin-overlay.dts b/arch/arm/boot/dts/overlays/balena-fin-overlay.dts new file mode 100644 index 00000000000000..8fc22587e69cdf --- /dev/null +++ b/arch/arm/boot/dts/overlays/balena-fin-overlay.dts @@ -0,0 +1,125 @@ +/dts-v1/; +/plugin/; + +#include <dt-bindings/gpio/gpio.h> + +/{ + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&mmcnr>; + __overlay__ { + pinctrl-names = "default"; + pinctrl-0 = <&sdio_pins>; + bus-width = <4>; + brcm,overclock-50 = <35>; + status = "okay"; + }; + }; + + fragment@1 { + target = <&gpio>; + __overlay__ { + sdio_pins: sdio_ovl_pins { + brcm,pins = <34 35 36 37 38 39>; + brcm,function = <7>; /* ALT3 = SD1 */ + brcm,pull = <0 2 2 2 2 2>; + }; + + power_ctrl_pins: power_ctrl_pins { + brcm,pins = <40>; + brcm,function = <1>; // out + }; + }; + }; + + fragment@2 { + target-path = "/"; + __overlay__ { + // We should switch to mmc-pwrseq-sd8787 after making it + // compatible with sd8887 + // Currently that module requires two GPIOs to function since it + // targets a slightly different chip + power_ctrl: power_ctrl { + compatible = "gpio-poweroff"; + gpios = <&gpio 40 1>; + force; + pinctrl-names = "default"; + pinctrl-0 = <&power_ctrl_pins>; + }; + + i2c_soft: i2c@0 { + compatible = "i2c-gpio"; + gpios = <&gpio 43 (GPIO_ACTIVE_HIGH|GPIO_OPEN_DRAIN) /* sda */ + &gpio 42 (GPIO_ACTIVE_HIGH|GPIO_OPEN_DRAIN) /* scl */>; + i2c-gpio,delay-us = <5>; + i2c-gpio,scl-open-drain; + i2c-gpio,sda-open-drain; + #address-cells = <1>; + #size-cells = <0>; + }; + + sd8xxx-wlan { + drvdbg = <0x6>; + drv_mode = <0x1>; + cfg80211_wext = <0xf>; + sta_name = "wlan"; + wfd_name = "p2p"; + cal_data_cfg = "none"; + }; + }; + }; + + fragment@3 { + target = <&i2c_soft>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + gpio_expander: gpio_expander@20 { + compatible = "nxp,pca9554"; + gpio-controller; + #gpio-cells = <2>; + reg = <0x20>; + status = "okay"; + }; + + // rtc clock + ds1307: ds1307@68 { + compatible = "dallas,ds1307"; + reg = <0x68>; + status = "okay"; + }; + + // RGB LEDs (>= v1.1.0) + pca9633: pca9633@62 { + compatible = "nxp,pca9633"; + reg = <0x62>; + #address-cells = <1>; + #size-cells = <0>; + + red@0 { + label = "red"; + reg = <0>; + linux,default-trigger = "none"; + }; + green@1 { + label = "green"; + reg = <1>; + linux,default-trigger = "none"; + }; + blue@2 { + label = "blue"; + reg = <2>; + linux,default-trigger = "none"; + }; + unused@3 { + label = "unused"; + reg = <3>; + linux,default-trigger = "none"; + }; + }; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/bcm2712d0-overlay.dts b/arch/arm/boot/dts/overlays/bcm2712d0-overlay.dts new file mode 100644 index 00000000000000..1ee74234e80545 --- /dev/null +++ b/arch/arm/boot/dts/overlays/bcm2712d0-overlay.dts @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: GPL-2.0 +/dts-v1/; +/plugin/; + +#include <dt-bindings/interrupt-controller/irq.h> +#include <dt-bindings/interrupt-controller/arm-gic.h> + +/ { + compatible = "brcm,bcm2712"; + + fragment@0 { + target = <&gio>; + __overlay__ { + brcm,gpio-bank-widths = <32 4>; + }; + }; + + fragment@1 { + target = <&gio_aon>; + __overlay__ { + brcm,gpio-bank-widths = <15 6>; + }; + }; + + fragment@2 { + target = <&pinctrl>; + __overlay__ { + compatible = "brcm,bcm2712d0-pinctrl"; + reg = <0x7d504100 0x20>; + }; + }; + + fragment@3 { + target = <&pinctrl_aon>; + __overlay__ { + compatible = "brcm,bcm2712d0-aon-pinctrl"; + reg = <0x7d510700 0x1c>; + }; + }; + + fragment@4 { + target = <&uart10>; + __overlay__ { + interrupts = <GIC_SPI 120 IRQ_TYPE_LEVEL_HIGH>; + }; + }; + + fragment@5 { + target = <&spi10>; + __overlay__ { + dmas = <&dma40 3>, <&dma40 4>; + }; + }; + + fragment@6 { + target = <&hdmi0>; + __overlay__ { + dmas = <&dma40 (12|(1<<30)|(1<<24)|(10<<16)|(15<<20))>; + }; + }; + + fragment@7 { + target = <&hdmi1>; + __overlay__ { + dmas = <&dma40 (13|(1<<30)|(1<<24)|(10<<16)|(15<<20))>; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/camera-mux-2port-overlay.dts b/arch/arm/boot/dts/overlays/camera-mux-2port-overlay.dts new file mode 100644 index 00000000000000..97d1988dd98418 --- /dev/null +++ b/arch/arm/boot/dts/overlays/camera-mux-2port-overlay.dts @@ -0,0 +1,545 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Overlay to configure a 2 port camera multiplexer +// +// Configuration is based on the Arducam Doubleplexer +// which uses a PCA9543 I2C multiplexer to handle the +// I2C, and GPIO 4 to control the MIPI mux, and GPIO 17 +// to enable the CSI-2 mux output (gpio-hog). + +/dts-v1/; +/plugin/; + +#include <dt-bindings/gpio/gpio.h> + +/{ + compatible = "brcm,bcm2835"; + + /* Fragments that complete the individual sensor fragments */ + /* IMX290 */ + fragment@0 { + target = <&imx290_0_ep>; + __overlay__ { + data-lanes = <1 2>; + link-frequencies = + /bits/ 64 <445500000 297000000>; + }; + }; + + fragment@1 { + target = <&imx290_1_ep>; + __overlay__ { + data-lanes = <1 2>; + link-frequencies = + /bits/ 64 <445500000 297000000>; + }; + }; + + /* IMX477 */ + fragment@10 { + target = <&imx477_0>; + __overlay__ { + compatible = "sony,imx477"; + }; + }; + + fragment@11 { + target = <&imx477_1>; + __overlay__ { + compatible = "sony,imx477"; + }; + }; + + /* Additional fragments affecting the mux nodes */ + fragment@100 { + target = <&mux_in0>; + __dormant__ { + data-lanes = <1>; + }; + }; + fragment@101 { + target = <&mux_in0>; + __overlay__ { + data-lanes = <1 2>; + }; + }; + + fragment@102 { + target = <&mux_in1>; + __dormant__ { + data-lanes = <1>; + }; + }; + fragment@103 { + target = <&mux_in1>; + __overlay__ { + data-lanes = <1 2>; + }; + }; + + /* Mux define */ + i2c_frag: fragment@200 { + target = <&i2c_csi_dsi>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + pca@70 { + reg = <0x70>; + compatible = "nxp,pca9543"; + + #address-cells = <1>; + #size-cells = <0>; + + i2c@0 { + reg = <0>; + #address-cells = <1>; + #size-cells = <0>; + + #define cam_node arducam_64mp_0 + #define cam_endpoint arducam_64mp_0_ep + #define vcm_node arducam_64mp_0_vcm + #define cam1_clk clk_24mhz + #include "arducam-64mp.dtsi" + #undef cam_node + #undef cam_endpoint + #undef vcm_node + #undef cam1_clk + + #define cam_node imx219_0 + #define cam_endpoint imx219_0_ep + #define cam1_clk clk_24mhz + #include "imx219.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node imx477_0 + #define cam_endpoint imx477_0_ep + #define cam1_clk clk_24mhz + #include "imx477_378.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node imx519_0 + #define cam_endpoint imx519_0_ep + #define vcm_node imx519_0_vcm + #define cam1_clk clk_24mhz + #include "imx519.dtsi" + #undef cam_node + #undef cam_endpoint + #undef vcm_node + #undef cam1_clk + + #define cam_node imx708_0 + #define cam_endpoint imx708_0_ep + #define vcm_node imx708_0_vcm + #define cam1_clk clk_24mhz + #include "imx708.dtsi" + #undef cam_node + #undef cam_endpoint + #undef vcm_node + #undef cam1_clk + + #define cam_node ov5647_0 + #define cam_endpoint ov5647_0_ep + #define cam1_clk clk_25mhz + #include "ov5647.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node ov7251_0 + #define cam_endpoint ov7251_0_ep + #define cam1_clk clk_24mhz + #include "ov7251.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node ov9281_0 + #define cam_endpoint ov9281_0_ep + #define cam1_clk clk_24mhz + #include "ov9281.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node imx258_0 + #define cam_endpoint imx258_0_ep + #define cam1_clk clk_24mhz + #include "imx258.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node imx290_0 + #define cam_endpoint imx290_0_ep + #define cam1_clk clk_imx290 + #include "imx290_327.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node ov2311_0 + #define cam_endpoint ov2311_0_ep + #define cam1_clk clk_24mhz + #include "ov2311.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node ov64a40_0 + #define cam_endpoint ov64a40_0_ep + #define vcm_node ov64a40_0_vcm + #define cam1_clk clk_24mhz + #include "ov64a40.dtsi" + #undef cam_node + #undef cam_endpoint + #undef vcm_node + #undef cam1_clk + }; + + i2c@1 { + reg = <1>; + #address-cells = <1>; + #size-cells = <0>; + + #define cam_node arducam_64mp_1 + #define cam_endpoint arducam_64mp_1_ep + #define vcm_node arducam_64mp_1_vcm + #define cam1_clk clk_24mhz + #include "arducam-64mp.dtsi" + #undef cam_node + #undef cam_endpoint + #undef vcm_node + #undef cam1_clk + + #define cam_node imx219_1 + #define cam_endpoint imx219_1_ep + #define cam1_clk clk_24mhz + #include "imx219.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node imx477_1 + #define cam_endpoint imx477_1_ep + #define cam1_clk clk_24mhz + #include "imx477_378.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node imx519_1 + #define cam_endpoint imx519_1_ep + #define vcm_node imx519_1_vcm + #define cam1_clk clk_24mhz + #include "imx519.dtsi" + #undef cam_node + #undef cam_endpoint + #undef vcm_node + #undef cam1_clk + + #define cam_node imx708_1 + #define cam_endpoint imx708_1_ep + #define vcm_node imx708_1_vcm + #define cam1_clk clk_24mhz + #include "imx708.dtsi" + #undef cam_node + #undef cam_endpoint + #undef vcm_node + #undef cam1_clk + + #define cam_node ov5647_1 + #define cam_endpoint ov5647_1_ep + #define cam1_clk clk_25mhz + #include "ov5647.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node ov7251_1 + #define cam_endpoint ov7251_1_ep + #define cam1_clk clk_24mhz + #include "ov7251.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node ov9281_1 + #define cam_endpoint ov9281_1_ep + #define cam1_clk clk_24mhz + #include "ov9281.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node imx258_1 + #define cam_endpoint imx258_1_ep + #define cam1_clk clk_24mhz + #include "imx258.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node imx290_1 + #define cam_endpoint imx290_1_ep + #define cam1_clk clk_imx290 + #include "imx290_327.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node ov2311_1 + #define cam_endpoint ov2311_1_ep + #define cam1_clk clk_24mhz + #include "ov2311.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node ov64a40_1 + #define cam_endpoint ov64a40_1_ep + #define vcm_node ov64a40_1_vcm + #define cam1_clk clk_24mhz + #include "ov64a40.dtsi" + #undef cam_node + #undef cam_endpoint + #undef vcm_node + #undef cam1_clk + }; + }; + }; + }; + + csi_frag: fragment@201 { + target = <&csi1>; + __overlay__ { + status = "okay"; + + port { + csi1_ep: endpoint { + remote-endpoint = <&mux_out>; + clock-lanes = <0>; + data-lanes = <1 2>; + }; + }; + }; + }; + + fragment@202 { + target = <&i2c0if>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@203 { + target-path="/"; + __overlay__ { + mux: mux-controller { + compatible = "gpio-mux"; + #mux-control-cells = <0>; + + mux-gpios = <&gpio 4 GPIO_ACTIVE_HIGH>; + }; + + video-mux { + compatible = "video-mux"; + mux-controls = <&mux>; + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + mux_in0: endpoint { + clock-lanes = <0>; + }; + }; + + port@1 { + reg = <1>; + + mux_in1: endpoint { + clock-lanes = <0>; + }; + }; + + port@2 { + reg = <2>; + + mux_out: endpoint { + remote-endpoint = <&csi1_ep>; + clock-lanes = <0>; + }; + }; + }; + + clk_24mhz: clk_24mhz { + compatible = "fixed-clock"; + #clock-cells = <0>; + + clock-frequency = <24000000>; + status = "okay"; + }; + + clk_25mhz: clk_25mhz { + compatible = "fixed-clock"; + #clock-cells = <0>; + + clock-frequency = <25000000>; + status = "okay"; + }; + + clk_imx290: clk_imx290 { + compatible = "fixed-clock"; + #clock-cells = <0>; + + clock-frequency = <37125000>; + status = "okay"; + }; + }; + }; + + fragment@204 { + target = <&i2c0mux>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@205 { + target = <&gpio>; + __overlay__ { + mipi_sw_oe_hog { + gpio-hog; + gpios = <17 GPIO_ACTIVE_LOW>; + output-high; + }; + }; + }; + + __overrides__ { + cam0-arducam-64mp = <&mux_in0>, "remote-endpoint:0=",<&arducam_64mp_0_ep>, + <&arducam_64mp_0_ep>, "remote-endpoint:0=",<&mux_in0>, + <&mux_in0>, "clock-noncontinuous?", + <&arducam_64mp_0>, "status=okay", + <&arducam_64mp_0_vcm>, "status=okay", + <&arducam_64mp_0>,"lens-focus:0=", <&arducam_64mp_0_vcm>; + cam0-imx219 = <&mux_in0>, "remote-endpoint:0=",<&imx219_0_ep>, + <&imx219_0_ep>, "remote-endpoint:0=",<&mux_in0>, + <&mux_in0>, "clock-noncontinuous?", + <&imx219_0>, "status=okay"; + cam0-imx477 = <&mux_in0>, "remote-endpoint:0=",<&imx477_0_ep>, + <&imx477_0_ep>, "remote-endpoint:0=",<&mux_in0>, + <&mux_in0>, "clock-noncontinuous?", + <&imx477_0>, "status=okay"; + cam0-imx519 = <&mux_in0>, "remote-endpoint:0=",<&imx519_0_ep>, + <&imx519_0_ep>, "remote-endpoint:0=",<&mux_in0>, + <&mux_in0>, "clock-noncontinuous?", + <&imx519_0>, "status=okay", + <&imx519_0_vcm>, "status=okay", + <&imx519_0>,"lens-focus:0=", <&imx519_0_vcm>; + cam0-imx708 = <&mux_in0>, "remote-endpoint:0=",<&imx708_0_ep>, + <&imx708_0_ep>, "remote-endpoint:0=",<&mux_in0>, + <&mux_in0>, "clock-noncontinuous?", + <&imx708_0>, "status=okay", + <&imx708_0_vcm>, "status=okay", + <&imx708_0>,"lens-focus:0=", <&imx708_0_vcm>; + cam0-ov5647 = <&mux_in0>, "remote-endpoint:0=",<&ov5647_0_ep>, + <&ov5647_0_ep>, "remote-endpoint:0=",<&mux_in0>, + <&mux_in0>, "clock-noncontinuous?", + <&ov5647_0>, "status=okay"; + cam0-ov7251 = <&mux_in0>, "remote-endpoint:0=",<&ov7251_0_ep>, + <&ov7251_0_ep>, "remote-endpoint:0=",<&mux_in0>, + <&ov7251_0>, "status=okay", + <0>,"+100-101"; + cam0-ov9281 = <&mux_in0>, "remote-endpoint:0=",<&ov9281_0_ep>, + <&ov9281_0_ep>, "remote-endpoint:0=",<&mux_in0>, + <&ov9281_0>, "status=okay"; + cam0-imx258 = <&mux_in0>, "remote-endpoint:0=",<&imx258_0_ep>, + <&imx258_0_ep>, "remote-endpoint:0=",<&mux_in0>, + <&imx258_0>, "status=okay"; + cam0-imx290 = <&mux_in0>, "remote-endpoint:0=",<&imx290_0_ep>, + <&imx290_0_ep>, "remote-endpoint:0=",<&mux_in0>, + <&imx290_0>, "status=okay"; + cam0-ov2311 = <&mux_in0>, "remote-endpoint:0=",<&ov2311_0_ep>, + <&ov2311_0_ep>, "remote-endpoint:0=",<&mux_in0>, + <&ov2311_0>, "status=okay"; + cam0-ov64a40 = <&mux_in0>, "remote-endpoint:0=",<&ov64a40_0_ep>, + <&ov64a40_0_ep>, "remote-endpoint:0=",<&mux_in0>, + <&mux_in0>, "clock-noncontinuous?", + <&ov64a40_0>, "status=okay", + <&ov64a40_0_vcm>, "status=okay", + <&ov64a40_0>,"lens-focus:0=", <&ov64a40_0_vcm>; + + cam1-arducam-64mp = <&mux_in1>, "remote-endpoint:0=",<&arducam_64mp_1_ep>, + <&arducam_64mp_1_ep>, "remote-endpoint:0=",<&mux_in1>, + <&mux_in1>, "clock-noncontinuous?", + <&arducam_64mp_1>, "status=okay", + <&arducam_64mp_1_vcm>, "status=okay", + <&arducam_64mp_1>,"lens-focus:0=", <&arducam_64mp_1_vcm>; + cam1-imx219 = <&mux_in1>, "remote-endpoint:0=",<&imx219_1_ep>, + <&imx219_1_ep>, "remote-endpoint:0=",<&mux_in1>, + <&mux_in1>, "clock-noncontinuous?", + <&imx219_1>, "status=okay"; + cam1-imx477 = <&mux_in1>, "remote-endpoint:0=",<&imx477_1_ep>, + <&imx477_1_ep>, "remote-endpoint:0=",<&mux_in1>, + <&mux_in1>, "clock-noncontinuous?", + <&imx477_1>, "status=okay"; + cam1-imx519 = <&mux_in1>, "remote-endpoint:0=",<&imx519_1_ep>, + <&imx519_1_ep>, "remote-endpoint:0=",<&mux_in1>, + <&mux_in1>, "clock-noncontinuous?", + <&imx519_1>, "status=okay", + <&imx519_1_vcm>, "status=okay", + <&imx519_1>,"lens-focus:0=", <&imx519_1_vcm>; + cam1-imx708 = <&mux_in1>, "remote-endpoint:0=",<&imx708_1_ep>, + <&imx708_1_ep>, "remote-endpoint:0=",<&mux_in1>, + <&mux_in1>, "clock-noncontinuous?", + <&imx708_1>, "status=okay", + <&imx708_1_vcm>, "status=okay", + <&imx708_1>,"lens-focus:0=", <&imx708_1_vcm>; + cam1-ov5647 = <&mux_in1>, "remote-endpoint:0=",<&ov5647_1_ep>, + <&ov5647_1_ep>, "remote-endpoint:0=",<&mux_in1>, + <&mux_in1>, "clock-noncontinuous?", + <&ov5647_1>, "status=okay"; + cam1-ov7251 = <&mux_in1>, "remote-endpoint:0=",<&ov7251_1_ep>, + <&ov7251_1_ep>, "remote-endpoint:0=",<&mux_in1>, + <&ov7251_1>, "status=okay", + <0>,"+102-103"; + cam1-ov9281 = <&mux_in1>, "remote-endpoint:0=",<&ov9281_1_ep>, + <&ov9281_1_ep>, "remote-endpoint:0=",<&mux_in1>, + <&ov9281_1>, "status=okay"; + cam1-imx258 = <&mux_in1>, "remote-endpoint:0=",<&imx258_1_ep>, + <&imx258_1_ep>, "remote-endpoint:0=",<&mux_in1>, + <&imx258_1>, "status=okay"; + cam1-imx290 = <&mux_in1>, "remote-endpoint:0=",<&imx290_1_ep>, + <&imx290_1_ep>, "remote-endpoint:0=",<&mux_in1>, + <&imx290_1>, "status=okay"; + cam1-ov2311 = <&mux_in1>, "remote-endpoint:0=",<&ov2311_1_ep>, + <&ov2311_1_ep>, "remote-endpoint:0=",<&mux_in1>, + <&ov2311_1>, "status=okay"; + cam1-ov64a40 = <&mux_in1>, "remote-endpoint:0=",<&ov64a40_1_ep>, + <&ov64a40_1_ep>, "remote-endpoint:0=",<&mux_in1>, + <&mux_in1>, "clock-noncontinuous?", + <&ov64a40_1>, "status=okay", + <&ov64a40_1_vcm>, "status=okay", + <&ov64a40_1>,"lens-focus:0=", <&ov64a40_1_vcm>; + + cam0-imx290-clk-freq = <&clk_imx290>,"clock-frequency:0", + <&imx290_0>,"clock-frequency:0"; + cam1-imx290-clk-freq = <&clk_imx290>,"clock-frequency:0", + <&imx290_1>,"clock-frequency:0"; + + cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>, + <&csi_frag>, "target:0=",<&csi0>; + + cam0-sync-source = <&imx477_0>, "trigger-mode:0=1"; + cam0-sync-sink = <&imx477_0>, "trigger-mode:0=2"; + cam1-sync-source = <&imx477_1>, "trigger-mode:0=1"; + cam1-sync-sink = <&imx477_1>, "trigger-mode:0=2"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/camera-mux-4port-overlay.dts b/arch/arm/boot/dts/overlays/camera-mux-4port-overlay.dts new file mode 100644 index 00000000000000..dbbb476f51e7fa --- /dev/null +++ b/arch/arm/boot/dts/overlays/camera-mux-4port-overlay.dts @@ -0,0 +1,954 @@ +// SPDX-License-Identifier: GPL-2.0-only + +// Overlay to configure a 4 port camera multiplexer +// +// Configuration is based on the Arducam 4 channel multiplexer +// which uses a PCA9543 I2C multiplexer to handle the +// I2C, and GPIOs 4, 17, and 18 to control the MIPI muxes. + +/dts-v1/; +/plugin/; + +#include <dt-bindings/gpio/gpio.h> + +/{ + compatible = "brcm,bcm2835"; + + /* Fragments that complete the individual sensor fragments */ + /* IMX290 */ + fragment@0 { + target = <&imx290_0_ep>; + __overlay__ { + data-lanes = <1 2>; + link-frequencies = + /bits/ 64 <445500000 297000000>; + }; + }; + + fragment@1 { + target = <&imx290_1_ep>; + __overlay__ { + data-lanes = <1 2>; + link-frequencies = + /bits/ 64 <445500000 297000000>; + }; + }; + + fragment@2 { + target = <&imx290_2_ep>; + __overlay__ { + data-lanes = <1 2>; + link-frequencies = + /bits/ 64 <445500000 297000000>; + }; + }; + + fragment@3 { + target = <&imx290_3_ep>; + __overlay__ { + data-lanes = <1 2>; + link-frequencies = + /bits/ 64 <445500000 297000000>; + }; + }; + + /* IMX477 */ + fragment@10 { + target = <&imx477_0>; + __overlay__ { + compatible = "sony,imx477"; + }; + }; + + fragment@11 { + target = <&imx477_1>; + __overlay__ { + compatible = "sony,imx477"; + }; + }; + + fragment@12 { + target = <&imx477_2>; + __overlay__ { + compatible = "sony,imx477"; + }; + }; + + fragment@13 { + target = <&imx477_3>; + __overlay__ { + compatible = "sony,imx477"; + }; + }; + + /* Additional fragments affecting the mux nodes */ + fragment@100 { + target = <&mux_in0>; + __dormant__ { + data-lanes = <1>; + }; + }; + fragment@101 { + target = <&mux_in0>; + __overlay__ { + data-lanes = <1 2>; + }; + }; + + fragment@102 { + target = <&mux_in1>; + __dormant__ { + data-lanes = <1>; + }; + }; + fragment@103 { + target = <&mux_in1>; + __overlay__ { + data-lanes = <1 2>; + }; + }; + + fragment@104 { + target = <&mux_in2>; + __dormant__ { + data-lanes = <1>; + }; + }; + fragment@105 { + target = <&mux_in2>; + __overlay__ { + data-lanes = <1 2>; + }; + }; + + fragment@106 { + target = <&mux_in3>; + __dormant__ { + data-lanes = <1>; + }; + }; + fragment@107 { + target = <&mux_in3>; + __overlay__ { + data-lanes = <1 2>; + }; + }; + + /* Mux define */ + i2c_frag: fragment@200 { + target = <&i2c_csi_dsi>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + pca@70 { + reg = <0x70>; + compatible = "nxp,pca9544"; + + #address-cells = <1>; + #size-cells = <0>; + + i2c@0 { + reg = <0>; + #address-cells = <1>; + #size-cells = <0>; + + #define cam_node arducam_64mp_0 + #define cam_endpoint arducam_64mp_0_ep + #define vcm_node arducam_64mp_0_vcm + #define cam1_clk clk_24mhz + #include "arducam-64mp.dtsi" + #undef cam_node + #undef cam_endpoint + #undef vcm_node + #undef cam1_clk + + #define cam_node imx219_0 + #define cam_endpoint imx219_0_ep + #define cam1_clk clk_24mhz + #include "imx219.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node imx477_0 + #define cam_endpoint imx477_0_ep + #define cam1_clk clk_24mhz + #include "imx477_378.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node imx519_0 + #define cam_endpoint imx519_0_ep + #define vcm_node imx519_0_vcm + #define cam1_clk clk_24mhz + #include "imx519.dtsi" + #undef cam_node + #undef cam_endpoint + #undef vcm_node + #undef cam1_clk + + #define cam_node imx708_0 + #define cam_endpoint imx708_0_ep + #define vcm_node imx708_0_vcm + #define cam1_clk clk_24mhz + #include "imx708.dtsi" + #undef cam_node + #undef cam_endpoint + #undef vcm_node + #undef cam1_clk + + #define cam_node ov5647_0 + #define cam_endpoint ov5647_0_ep + #define cam1_clk clk_25mhz + #include "ov5647.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node ov7251_0 + #define cam_endpoint ov7251_0_ep + #define cam1_clk clk_24mhz + #include "ov7251.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node ov9281_0 + #define cam_endpoint ov9281_0_ep + #define cam1_clk clk_24mhz + #include "ov9281.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node imx258_0 + #define cam_endpoint imx258_0_ep + #define cam1_clk clk_24mhz + #include "imx258.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node imx290_0 + #define cam_endpoint imx290_0_ep + #define cam1_clk clk_imx290 + #include "imx290_327.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node ov2311_0 + #define cam_endpoint ov2311_0_ep + #define cam1_clk clk_24mhz + #include "ov2311.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node ov64a40_0 + #define cam_endpoint ov64a40_0_ep + #define vcm_node ov64a40_0_vcm + #define cam1_clk clk_24mhz + #include "ov64a40.dtsi" + #undef cam_node + #undef cam_endpoint + #undef vcm_node + #undef cam1_clk + }; + + i2c@1 { + reg = <1>; + #address-cells = <1>; + #size-cells = <0>; + + #define cam_node arducam_64mp_1 + #define cam_endpoint arducam_64mp_1_ep + #define vcm_node arducam_64mp_1_vcm + #define cam1_clk clk_24mhz + #include "arducam-64mp.dtsi" + #undef cam_node + #undef cam_endpoint + #undef vcm_node + #undef cam1_clk + + #define cam_node imx219_1 + #define cam_endpoint imx219_1_ep + #define cam1_clk clk_24mhz + #include "imx219.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node imx477_1 + #define cam_endpoint imx477_1_ep + #define cam1_clk clk_24mhz + #include "imx477_378.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node imx519_1 + #define cam_endpoint imx519_1_ep + #define vcm_node imx519_1_vcm + #define cam1_clk clk_24mhz + #include "imx519.dtsi" + #undef cam_node + #undef cam_endpoint + #undef vcm_node + #undef cam1_clk + + #define cam_node imx708_1 + #define cam_endpoint imx708_1_ep + #define vcm_node imx708_1_vcm + #define cam1_clk clk_24mhz + #include "imx708.dtsi" + #undef cam_node + #undef cam_endpoint + #undef vcm_node + #undef cam1_clk + + #define cam_node ov5647_1 + #define cam_endpoint ov5647_1_ep + #define cam1_clk clk_25mhz + #include "ov5647.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node ov7251_1 + #define cam_endpoint ov7251_1_ep + #define cam1_clk clk_24mhz + #include "ov7251.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node ov9281_1 + #define cam_endpoint ov9281_1_ep + #define cam1_clk clk_24mhz + #include "ov9281.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node imx258_1 + #define cam_endpoint imx258_1_ep + #define cam1_clk clk_24mhz + #include "imx258.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node imx290_1 + #define cam_endpoint imx290_1_ep + #define cam1_clk clk_imx290 + #include "imx290_327.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node ov2311_1 + #define cam_endpoint ov2311_1_ep + #define cam1_clk clk_24mhz + #include "ov2311.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node ov64a40_1 + #define cam_endpoint ov64a40_1_ep + #define vcm_node ov64a40_1_vcm + #define cam1_clk clk_24mhz + #include "ov64a40.dtsi" + #undef cam_node + #undef cam_endpoint + #undef vcm_node + #undef cam1_clk + }; + + i2c@2 { + reg = <2>; + #address-cells = <1>; + #size-cells = <0>; + + #define cam_node arducam_64mp_2 + #define cam_endpoint arducam_64mp_2_ep + #define vcm_node arducam_64mp_2_vcm + #define cam1_clk clk_24mhz + #include "arducam-64mp.dtsi" + #undef cam_node + #undef cam_endpoint + #undef vcm_node + #undef cam1_clk + + #define cam_node imx219_2 + #define cam_endpoint imx219_2_ep + #define cam1_clk clk_24mhz + #include "imx219.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node imx477_2 + #define cam_endpoint imx477_2_ep + #define cam1_clk clk_24mhz + #include "imx477_378.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node imx519_2 + #define cam_endpoint imx519_2_ep + #define vcm_node imx519_2_vcm + #define cam1_clk clk_24mhz + #include "imx519.dtsi" + #undef cam_node + #undef cam_endpoint + #undef vcm_node + #undef cam1_clk + + #define cam_node imx708_2 + #define cam_endpoint imx708_2_ep + #define vcm_node imx708_2_vcm + #define cam1_clk clk_24mhz + #include "imx708.dtsi" + #undef cam_node + #undef cam_endpoint + #undef vcm_node + #undef cam1_clk + + #define cam_node ov5647_2 + #define cam_endpoint ov5647_2_ep + #define cam1_clk clk_25mhz + #include "ov5647.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node ov7251_2 + #define cam_endpoint ov7251_2_ep + #define cam1_clk clk_24mhz + #include "ov7251.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node ov9281_2 + #define cam_endpoint ov9281_2_ep + #define cam1_clk clk_24mhz + #include "ov9281.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node imx258_2 + #define cam_endpoint imx258_2_ep + #define cam1_clk clk_24mhz + #include "imx258.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node imx290_2 + #define cam_endpoint imx290_2_ep + #define cam1_clk clk_imx290 + #include "imx290_327.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node ov2311_2 + #define cam_endpoint ov2311_2_ep + #define cam1_clk clk_24mhz + #include "ov2311.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node ov64a40_2 + #define cam_endpoint ov64a40_2_ep + #define vcm_node ov64a40_2_vcm + #define cam1_clk clk_24mhz + #include "ov64a40.dtsi" + #undef cam_node + #undef cam_endpoint + #undef vcm_node + #undef cam1_clk + }; + + i2c@3 { + reg = <3>; + #address-cells = <1>; + #size-cells = <0>; + + #define cam_node arducam_64mp_3 + #define cam_endpoint arducam_64mp_3_ep + #define vcm_node arducam_64mp_3_vcm + #define cam1_clk clk_24mhz + #include "arducam-64mp.dtsi" + #undef cam_node + #undef cam_endpoint + #undef vcm_node + #undef cam1_clk + + #define cam_node imx219_3 + #define cam_endpoint imx219_3_ep + #define cam1_clk clk_24mhz + #include "imx219.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node imx477_3 + #define cam_endpoint imx477_3_ep + #define cam1_clk clk_24mhz + #include "imx477_378.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node imx519_3 + #define cam_endpoint imx519_3_ep + #define vcm_node imx519_3_vcm + #define cam1_clk clk_24mhz + #include "imx519.dtsi" + #undef cam_node + #undef cam_endpoint + #undef vcm_node + #undef cam1_clk + + #define cam_node imx708_3 + #define cam_endpoint imx708_3_ep + #define vcm_node imx708_3_vcm + #define cam1_clk clk_24mhz + #include "imx708.dtsi" + #undef cam_node + #undef cam_endpoint + #undef vcm_node + #undef cam1_clk + + #define cam_node ov5647_3 + #define cam_endpoint ov5647_3_ep + #define cam1_clk clk_25mhz + #include "ov5647.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node ov7251_3 + #define cam_endpoint ov7251_3_ep + #define cam1_clk clk_24mhz + #include "ov7251.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node ov9281_3 + #define cam_endpoint ov9281_3_ep + #define cam1_clk clk_24mhz + #include "ov9281.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node imx258_3 + #define cam_endpoint imx258_3_ep + #define cam1_clk clk_24mhz + #include "imx258.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node imx290_3 + #define cam_endpoint imx290_3_ep + #define cam1_clk clk_imx290 + #include "imx290_327.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node ov2311_3 + #define cam_endpoint ov2311_3_ep + #define cam1_clk clk_24mhz + #include "ov2311.dtsi" + #undef cam_node + #undef cam_endpoint + #undef cam1_clk + + #define cam_node ov64a40_3 + #define cam_endpoint ov64a40_3_ep + #define vcm_node ov64a40_3_vcm + #define cam1_clk clk_24mhz + #include "ov64a40.dtsi" + #undef cam_node + #undef cam_endpoint + #undef vcm_node + #undef cam1_clk + }; + }; + }; + }; + + csi_frag: fragment@201 { + target = <&csi1>; + __overlay__ { + status = "okay"; + + port { + csi1_ep: endpoint { + remote-endpoint = <&mux_out>; + clock-lanes = <0>; + data-lanes = <1 2>; + }; + }; + }; + }; + + fragment@202 { + target = <&i2c0if>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@203 { + target-path="/"; + __overlay__ { + mux: mux-controller { + compatible = "gpio-mux"; + #mux-control-cells = <0>; + + /* SEL, En2, En1 */ + mux-gpios = <&gpio 4 GPIO_ACTIVE_HIGH>, + <&gpio 18 GPIO_ACTIVE_HIGH>, + <&gpio 17 GPIO_ACTIVE_HIGH>; + }; + + video-mux { + compatible = "video-mux"; + mux-controls = <&mux>; + #address-cells = <1>; + #size-cells = <0>; + + /* GPIO mappings settings for selecting the different + * camera connectors are not direct, hence port@ values + * are not straight forward. + */ + port@2 { + /* Port A - GPIO 17 = 0, GPIO 18 = 1,GPIO 4 = 0 */ + reg = <2>; + + mux_in0: endpoint { + clock-lanes = <0>; + }; + }; + + port@3 { + /* Port B - GPIO 17 = 0, GPIO 18 = 1,GPIO 4 = 1 */ + reg = <3>; + + mux_in1: endpoint { + clock-lanes = <0>; + }; + }; + + port@4 { + /* Port C - GPIO 17 = 1, GPIO 18 = 0, GPIO 4 = 0 */ + reg = <4>; + + mux_in2: endpoint { + clock-lanes = <0>; + }; + }; + + port@5 { + /* Port D - GPIO 17 = 1, GPIO 18 = 0, GPIO 4 = 1 */ + reg = <5>; + + mux_in3: endpoint { + clock-lanes = <0>; + }; + }; + + port@6 { + /* Output port needs to be the highest port number */ + reg = <6>; + + mux_out: endpoint { + remote-endpoint = <&csi1_ep>; + clock-lanes = <0>; + }; + }; + }; + + clk_24mhz: clk_24mhz { + compatible = "fixed-clock"; + #clock-cells = <0>; + + clock-frequency = <24000000>; + status = "okay"; + }; + + clk_25mhz: clk_25mhz { + compatible = "fixed-clock"; + #clock-cells = <0>; + + clock-frequency = <25000000>; + status = "okay"; + }; + + clk_imx290: clk_imx290 { + compatible = "fixed-clock"; + #clock-cells = <0>; + + clock-frequency = <37125000>; + status = "okay"; + }; + }; + }; + + fragment@204 { + target = <&i2c0mux>; + __overlay__ { + status = "okay"; + }; + }; + + __overrides__ { + cam0-arducam-64mp = <&mux_in0>, "remote-endpoint:0=",<&arducam_64mp_0_ep>, + <&arducam_64mp_0_ep>, "remote-endpoint:0=",<&mux_in0>, + <&mux_in0>, "clock-noncontinuous?", + <&arducam_64mp_0>, "status=okay", + <&arducam_64mp_0_vcm>, "status=okay", + <&arducam_64mp_0>,"lens-focus:0=", <&arducam_64mp_0_vcm>; + cam0-imx219 = <&mux_in0>, "remote-endpoint:0=",<&imx219_0_ep>, + <&imx219_0_ep>, "remote-endpoint:0=",<&mux_in0>, + <&mux_in0>, "clock-noncontinuous?", + <&imx219_0>, "status=okay"; + cam0-imx477 = <&mux_in0>, "remote-endpoint:0=",<&imx477_0_ep>, + <&imx477_0_ep>, "remote-endpoint:0=",<&mux_in0>, + <&mux_in0>, "clock-noncontinuous?", + <&imx477_0>, "status=okay"; + cam0-imx519 = <&mux_in0>, "remote-endpoint:0=",<&imx519_0_ep>, + <&imx519_0_ep>, "remote-endpoint:0=",<&mux_in0>, + <&mux_in0>, "clock-noncontinuous?", + <&imx519_0>, "status=okay", + <&imx519_0_vcm>, "status=okay", + <&imx519_0>,"lens-focus:0=", <&imx519_0_vcm>; + cam0-imx708 = <&mux_in0>, "remote-endpoint:0=",<&imx708_0_ep>, + <&imx708_0_ep>, "remote-endpoint:0=",<&mux_in0>, + <&mux_in0>, "clock-noncontinuous?", + <&imx708_0>, "status=okay", + <&imx708_0_vcm>, "status=okay", + <&imx708_0>,"lens-focus:0=", <&imx708_0_vcm>; + cam0-ov5647 = <&mux_in0>, "remote-endpoint:0=",<&ov5647_0_ep>, + <&ov5647_0_ep>, "remote-endpoint:0=",<&mux_in0>, + <&mux_in0>, "clock-noncontinuous?", + <&ov5647_0>, "status=okay"; + cam0-ov7251 = <&mux_in0>, "remote-endpoint:0=",<&ov7251_0_ep>, + <&ov7251_0_ep>, "remote-endpoint:0=",<&mux_in0>, + <&ov7251_0>, "status=okay", + <0>,"+100-101"; + cam0-ov9281 = <&mux_in0>, "remote-endpoint:0=",<&ov9281_0_ep>, + <&ov9281_0_ep>, "remote-endpoint:0=",<&mux_in0>, + <&ov9281_0>, "status=okay"; + cam0-imx258 = <&mux_in0>, "remote-endpoint:0=",<&imx258_0_ep>, + <&imx258_0_ep>, "remote-endpoint:0=",<&mux_in0>, + <&imx258_0>, "status=okay"; + cam0-imx290 = <&mux_in0>, "remote-endpoint:0=",<&imx290_0_ep>, + <&imx290_0_ep>, "remote-endpoint:0=",<&mux_in0>, + <&imx290_0>, "status=okay"; + cam0-ov2311 = <&mux_in0>, "remote-endpoint:0=",<&ov2311_0_ep>, + <&ov2311_0_ep>, "remote-endpoint:0=",<&mux_in0>, + <&ov2311_0>, "status=okay"; + cam0-ov64a40 = <&mux_in0>, "remote-endpoint:0=",<&ov64a40_0_ep>, + <&ov64a40_0_ep>, "remote-endpoint:0=",<&mux_in0>, + <&mux_in0>, "clock-noncontinuous?", + <&ov64a40_0>, "status=okay", + <&ov64a40_0_vcm>, "status=okay", + <&ov64a40_0>,"lens-focus:0=", <&ov64a40_0_vcm>; + + cam1-arducam-64mp = <&mux_in1>, "remote-endpoint:0=",<&arducam_64mp_1_ep>, + <&arducam_64mp_1_ep>, "remote-endpoint:0=",<&mux_in1>, + <&mux_in1>, "clock-noncontinuous?", + <&arducam_64mp_1>, "status=okay", + <&arducam_64mp_1_vcm>, "status=okay", + <&arducam_64mp_1>,"lens-focus:0=", <&arducam_64mp_1_vcm>; + cam1-imx219 = <&mux_in1>, "remote-endpoint:0=",<&imx219_1_ep>, + <&imx219_1_ep>, "remote-endpoint:0=",<&mux_in1>, + <&mux_in1>, "clock-noncontinuous?", + <&imx219_1>, "status=okay"; + cam1-imx477 = <&mux_in1>, "remote-endpoint:0=",<&imx477_1_ep>, + <&imx477_1_ep>, "remote-endpoint:0=",<&mux_in1>, + <&mux_in1>, "clock-noncontinuous?", + <&imx477_1>, "status=okay"; + cam1-imx519 = <&mux_in1>, "remote-endpoint:0=",<&imx519_1_ep>, + <&imx519_1_ep>, "remote-endpoint:0=",<&mux_in1>, + <&mux_in1>, "clock-noncontinuous?", + <&imx519_1>, "status=okay", + <&imx519_1_vcm>, "status=okay", + <&imx519_1>,"lens-focus:0=", <&imx519_1_vcm>; + cam1-imx708 = <&mux_in1>, "remote-endpoint:0=",<&imx708_1_ep>, + <&imx708_1_ep>, "remote-endpoint:0=",<&mux_in1>, + <&mux_in1>, "clock-noncontinuous?", + <&imx708_1>, "status=okay", + <&imx708_1_vcm>, "status=okay", + <&imx708_1>,"lens-focus:0=", <&imx708_1_vcm>; + cam1-ov5647 = <&mux_in1>, "remote-endpoint:0=",<&ov5647_1_ep>, + <&ov5647_1_ep>, "remote-endpoint:0=",<&mux_in1>, + <&mux_in1>, "clock-noncontinuous?", + <&ov5647_1>, "status=okay"; + cam1-ov7251 = <&mux_in1>, "remote-endpoint:0=",<&ov7251_1_ep>, + <&ov7251_1_ep>, "remote-endpoint:0=",<&mux_in1>, + <&ov7251_1>, "status=okay", + <0>,"+102-103"; + cam1-ov9281 = <&mux_in1>, "remote-endpoint:0=",<&ov9281_1_ep>, + <&ov9281_1_ep>, "remote-endpoint:0=",<&mux_in1>, + <&ov9281_1>, "status=okay"; + cam1-imx258 = <&mux_in1>, "remote-endpoint:0=",<&imx258_1_ep>, + <&imx258_1_ep>, "remote-endpoint:0=",<&mux_in1>, + <&imx258_1>, "status=okay"; + cam1-imx290 = <&mux_in1>, "remote-endpoint:0=",<&imx290_1_ep>, + <&imx290_1_ep>, "remote-endpoint:0=",<&mux_in1>, + <&imx290_1>, "status=okay"; + cam1-ov2311 = <&mux_in1>, "remote-endpoint:0=",<&ov2311_1_ep>, + <&ov2311_1_ep>, "remote-endpoint:0=",<&mux_in1>, + <&ov2311_1>, "status=okay"; + cam1-ov64a40 = <&mux_in1>, "remote-endpoint:0=",<&ov64a40_1_ep>, + <&ov64a40_1_ep>, "remote-endpoint:0=",<&mux_in1>, + <&mux_in1>, "clock-noncontinuous?", + <&ov64a40_1>, "status=okay", + <&ov64a40_1_vcm>, "status=okay", + <&ov64a40_1>,"lens-focus:0=", <&ov64a40_1_vcm>; + + cam2-arducam-64mp = <&mux_in2>, "remote-endpoint:0=",<&arducam_64mp_2_ep>, + <&arducam_64mp_2_ep>, "remote-endpoint:0=",<&mux_in2>, + <&mux_in2>, "clock-noncontinuous?", + <&arducam_64mp_2>, "status=okay", + <&arducam_64mp_2_vcm>, "status=okay", + <&arducam_64mp_2>,"lens-focus:0=", <&arducam_64mp_2_vcm>; + cam2-imx219 = <&mux_in2>, "remote-endpoint:0=",<&imx219_2_ep>, + <&imx219_2_ep>, "remote-endpoint:0=",<&mux_in2>, + <&mux_in2>, "clock-noncontinuous?", + <&imx219_2>, "status=okay"; + cam2-imx477 = <&mux_in2>, "remote-endpoint:0=",<&imx477_2_ep>, + <&imx477_2_ep>, "remote-endpoint:0=",<&mux_in2>, + <&mux_in2>, "clock-noncontinuous?", + <&imx477_2>, "status=okay"; + cam2-imx519 = <&mux_in2>, "remote-endpoint:0=",<&imx519_2_ep>, + <&imx519_2_ep>, "remote-endpoint:0=",<&mux_in2>, + <&mux_in2>, "clock-noncontinuous?", + <&imx519_2>, "status=okay", + <&imx519_2_vcm>, "status=okay", + <&imx519_2>,"lens-focus:0=", <&imx519_2_vcm>; + cam2-imx708 = <&mux_in2>, "remote-endpoint:0=",<&imx708_2_ep>, + <&imx708_2_ep>, "remote-endpoint:0=",<&mux_in2>, + <&mux_in2>, "clock-noncontinuous?", + <&imx708_2>, "status=okay", + <&imx708_2_vcm>, "status=okay", + <&imx708_2>,"lens-focus:0=", <&imx708_2_vcm>; + cam2-ov5647 = <&mux_in2>, "remote-endpoint:0=",<&ov5647_2_ep>, + <&ov5647_2_ep>, "remote-endpoint:0=",<&mux_in2>, + <&mux_in2>, "clock-noncontinuous?", + <&ov5647_2>, "status=okay"; + cam2-ov7251 = <&mux_in2>, "remote-endpoint:0=",<&ov7251_2_ep>, + <&ov7251_2_ep>, "remote-endpoint:0=",<&mux_in2>, + <&ov7251_2>, "status=okay", + <0>,"+104-105"; + cam2-ov9281 = <&mux_in2>, "remote-endpoint:0=",<&ov9281_2_ep>, + <&ov9281_2_ep>, "remote-endpoint:0=",<&mux_in2>, + <&ov9281_2>, "status=okay"; + cam2-imx258 = <&mux_in2>, "remote-endpoint:0=",<&imx258_2_ep>, + <&imx258_2>, "status=okay", + <&imx258_2>, "remote-endpoint:0=",<&mux_in2>; + cam2-imx290 = <&mux_in2>, "remote-endpoint:0=",<&imx290_2_ep>, + <&imx290_2_ep>, "remote-endpoint:0=",<&mux_in2>, + <&imx290_2>, "status=okay"; + cam2-ov2311 = <&mux_in2>, "remote-endpoint:0=",<&ov2311_2_ep>, + <&ov2311_2_ep>, "remote-endpoint:0=",<&mux_in2>, + <&ov2311_2>, "status=okay"; + cam2-ov64a40 = <&mux_in2>, "remote-endpoint:0=",<&ov64a40_2_ep>, + <&ov64a40_2_ep>, "remote-endpoint:0=",<&mux_in2>, + <&mux_in2>, "clock-noncontinuous?", + <&ov64a40_2>, "status=okay", + <&ov64a40_2_vcm>, "status=okay", + <&ov64a40_2>,"lens-focus:0=", <&ov64a40_2_vcm>; + + cam3-arducam-64mp = <&mux_in3>, "remote-endpoint:0=",<&arducam_64mp_3_ep>, + <&arducam_64mp_3_ep>, "remote-endpoint:0=",<&mux_in3>, + <&mux_in3>, "clock-noncontinuous?", + <&arducam_64mp_3>, "status=okay", + <&arducam_64mp_3_vcm>, "status=okay", + <&arducam_64mp_3>,"lens-focus:0=", <&arducam_64mp_3_vcm>; + cam3-imx219 = <&mux_in3>, "remote-endpoint:0=",<&imx219_3_ep>, + <&imx219_3_ep>, "remote-endpoint:0=",<&mux_in3>, + <&mux_in3>, "clock-noncontinuous?", + <&imx219_3>, "status=okay"; + cam3-imx477 = <&mux_in3>, "remote-endpoint:0=",<&imx477_3_ep>, + <&imx477_3_ep>, "remote-endpoint:0=",<&mux_in3>, + <&mux_in3>, "clock-noncontinuous?", + <&imx477_3>, "status=okay"; + cam3-imx519 = <&mux_in3>, "remote-endpoint:0=",<&imx519_3_ep>, + <&imx519_3_ep>, "remote-endpoint:0=",<&mux_in3>, + <&mux_in3>, "clock-noncontinuous?", + <&imx519_3>, "status=okay", + <&imx519_3_vcm>, "status=okay", + <&imx519_3>,"lens-focus:0=", <&imx519_3_vcm>; + cam3-imx708 = <&mux_in3>, "remote-endpoint:0=",<&imx708_3_ep>, + <&imx708_3_ep>, "remote-endpoint:0=",<&mux_in3>, + <&mux_in3>, "clock-noncontinuous?", + <&imx708_3>, "status=okay", + <&imx708_3_vcm>, "status=okay", + <&imx708_3>,"lens-focus:0=", <&imx708_3_vcm>; + cam3-ov5647 = <&mux_in3>, "remote-endpoint:0=",<&ov5647_3_ep>, + <&ov5647_3_ep>, "remote-endpoint:0=",<&mux_in3>, + <&mux_in3>, "clock-noncontinuous?", + <&ov5647_3>, "status=okay"; + cam3-ov7251 = <&mux_in3>, "remote-endpoint:0=",<&ov7251_3_ep>, + <&ov7251_3_ep>, "remote-endpoint:0=",<&mux_in3>, + <&ov7251_3>, "status=okay", + <0>,"+106-107"; + cam3-ov9281 = <&mux_in3>, "remote-endpoint:0=",<&ov9281_3_ep>, + <&ov9281_3_ep>, "remote-endpoint:0=",<&mux_in3>, + <&ov9281_3>, "status=okay"; + cam3-imx258 = <&mux_in3>, "remote-endpoint:0=",<&imx258_3_ep>, + <&imx258_3_ep>, "remote-endpoint:0=",<&mux_in3>, + <&imx258_3>, "status=okay"; + cam3-imx290 = <&mux_in3>, "remote-endpoint:0=",<&imx290_3_ep>, + <&imx290_3_ep>, "remote-endpoint:0=",<&mux_in3>, + <&imx290_3>, "status=okay"; + cam3-ov2311 = <&mux_in3>, "remote-endpoint:0=",<&ov2311_3_ep>, + <&ov2311_3_ep>, "remote-endpoint:0=",<&mux_in3>, + <&ov2311_3>, "status=okay"; + cam3-ov64a40 = <&mux_in3>, "remote-endpoint:0=",<&ov64a40_3_ep>, + <&ov64a40_3_ep>, "remote-endpoint:0=",<&mux_in3>, + <&mux_in3>, "clock-noncontinuous?", + <&ov64a40_3>, "status=okay", + <&ov64a40_3_vcm>, "status=okay", + <&ov64a40_3>,"lens-focus:0=", <&ov64a40_3_vcm>; + + cam0-imx290-clk-freq = <&clk_imx290>,"clock-frequency:0", + <&imx290_0>,"clock-frequency:0"; + cam1-imx290-clk-freq = <&clk_imx290>,"clock-frequency:0", + <&imx290_1>,"clock-frequency:0"; + cam2-imx290-clk-freq = <&clk_imx290>,"clock-frequency:0", + <&imx290_2>,"clock-frequency:0"; + cam3-imx290-clk-freq = <&clk_imx290>,"clock-frequency:0", + <&imx290_3>,"clock-frequency:0"; + + cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>, + <&csi_frag>, "target:0=",<&csi0>; + + cam0-sync-source = <&imx477_0>, "trigger-mode:0=1"; + cam0-sync-sink = <&imx477_0>, "trigger-mode:0=2"; + cam1-sync-source = <&imx477_1>, "trigger-mode:0=1"; + cam1-sync-sink = <&imx477_1>, "trigger-mode:0=2"; + cam2-sync-source = <&imx477_2>, "trigger-mode:0=1"; + cam2-sync-sink = <&imx477_2>, "trigger-mode:0=2"; + cam3-sync-source = <&imx477_3>, "trigger-mode:0=1"; + cam3-sync-sink = <&imx477_3>, "trigger-mode:0=2"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/cap1106-overlay.dts b/arch/arm/boot/dts/overlays/cap1106-overlay.dts new file mode 100644 index 00000000000000..0a585e725f8429 --- /dev/null +++ b/arch/arm/boot/dts/overlays/cap1106-overlay.dts @@ -0,0 +1,52 @@ +// Overlay for cap1106 from Microchip Semiconductor +// add CONFIG_KEYBOARD_CAP11XX=y + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + fragment@0 { + target = <&i2c1>; + __overlay__{ + status = "okay"; + cap1106: cap1106@28 { + compatible = "microchip,cap1106"; + pinctrl-0 = <&cap1106_pins>; + pinctrl-names = "default"; + interrupt-parent = <&gpio>; + interrupts = <4 2>; + reg = <0x28>; + autorepeat; + microchip,sensor-gain = <2>; + + linux,keycodes = <2>, /* KEY_1 */ + <3>, /* KEY_2 */ + <4>, /* KEY_3 */ + <5>, /* KEY_4 */ + <6>, /* KEY_5 */ + <7>; /* KEY_6 */ + + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + }; + }; + }; + fragment@1 { + target = <&gpio>; + __overlay__ { + cap1106_pins: cap1106_pins { + brcm,pins = <4>; + brcm,function = <0>; /* in */ + brcm,pull = <0>; /* none */ + }; + }; + }; + + __overrides__ { + int_pin = <&cap1106>, "interrupts:0", + <&cap1106_pins>, "brcm,pins:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/chipcap2-overlay.dts b/arch/arm/boot/dts/overlays/chipcap2-overlay.dts new file mode 100644 index 00000000000000..e0b627e036cd8e --- /dev/null +++ b/arch/arm/boot/dts/overlays/chipcap2-overlay.dts @@ -0,0 +1,66 @@ +/dts-v1/; +/plugin/; + +/* Overlay for ChipCap 2 on i2c */ +#include <dt-bindings/gpio/gpio.h> +#include <dt-bindings/interrupt-controller/irq.h> + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + chipcap2: chipcap2@28 { + compatible = "amphenol,cc2d23s"; + reg = <0x28>; + interrupt-parent = <&gpio>; + interrupts = <4 IRQ_TYPE_EDGE_RISING>, + <5 IRQ_TYPE_EDGE_RISING>, + <6 IRQ_TYPE_EDGE_RISING>; + interrupt-names = "ready", "low", "high"; + vdd-supply = <&chipcap2_reg>; + status = "okay"; + }; + }; + }; + + fragment@1 { + target = <&gpio>; + __overlay__ { + chipcap2_pins: chipcap2_pins { + brcm,pins = <4 5 6 26>; + brcm,function = <0 0 0 1>; + }; + }; + }; + + fragment@2 { + target-path = "/"; + __overlay__ { + chipcap2_reg: chipcap2_reg { + compatible = "regulator-fixed"; + regulator-name = "chipcap2_reg"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + gpio = <&gpio 26 GPIO_ACTIVE_HIGH>; + enable-active-high; + }; + }; + }; + + __overrides__ { + ready_pin = <&chipcap2>, "interrupts:0", + <&chipcap2_pins>, "brcm,pins:0"; + low_pin = <&chipcap2>, "interrupts:8", + <&chipcap2_pins>, "brcm,pins:4"; + high_pin = <&chipcap2>, "interrupts:16", + <&chipcap2_pins>, "brcm,pins:8"; + reg_pin = <&chipcap2_reg>, "gpio:4", + <&chipcap2_pins>, "brcm,pins:12"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/chipdip-dac-overlay.dts b/arch/arm/boot/dts/overlays/chipdip-dac-overlay.dts new file mode 100644 index 00000000000000..3ef7565a931264 --- /dev/null +++ b/arch/arm/boot/dts/overlays/chipdip-dac-overlay.dts @@ -0,0 +1,46 @@ +/* + * Device Tree overlay for ChipDip DAC + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_consumer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target-path = "/"; + __overlay__ { + spdif-transmitter { + #address-cells = <0>; + #size-cells = <0>; + #sound-dai-cells = <0>; + compatible = "linux,spdif-dit"; + status = "okay"; + }; + }; + }; + + fragment@2 { + target = <&sound>; + __overlay__ { + compatible = "chipdip,chipdip-dac"; + i2s-controller = <&i2s_clk_consumer>; + sr0-gpios = <&gpio 5 0>; + sr1-gpios = <&gpio 6 0>; + sr2-gpios = <&gpio 12 0>; + res0-gpios = <&gpio 24 0>; + res1-gpios = <&gpio 27 0>; + mute-gpios = <&gpio 4 0>; + sdwn-gpios = <&gpio 13 0>; + status = "okay"; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/cirrus-wm5102-overlay.dts b/arch/arm/boot/dts/overlays/cirrus-wm5102-overlay.dts new file mode 100644 index 00000000000000..a82b422ba16eda --- /dev/null +++ b/arch/arm/boot/dts/overlays/cirrus-wm5102-overlay.dts @@ -0,0 +1,172 @@ +// Definitions for the Cirrus Logic Audio Card +/dts-v1/; +/plugin/; +#include <dt-bindings/pinctrl/bcm2835.h> +#include <dt-bindings/gpio/gpio.h> +#include <dt-bindings/mfd/arizona.h> + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_consumer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&gpio>; + __overlay__ { + wlf_5102_pins: wlf_5102_pins { + brcm,pins = <17 22 27>; + brcm,function = < + BCM2835_FSEL_GPIO_OUT + BCM2835_FSEL_GPIO_OUT + BCM2835_FSEL_GPIO_IN + >; + }; + wlf_8804_pins: wlf_8804_pins { + brcm,pins = <8>; + brcm,function = <BCM2835_FSEL_GPIO_OUT>; + }; + }; + }; + + fragment@2 { + target = <&spi0_cs_pins>; + __overlay__ { + brcm,pins = <7>; + brcm,function = <BCM2835_FSEL_GPIO_OUT>; + }; + }; + + + fragment@3 { + target-path = "/"; + __overlay__ { + rpi_cirrus_reg_1v8: rpi_cirrus_reg_1v8 { + compatible = "regulator-fixed"; + regulator-name = "RPi-Cirrus 1v8"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; + }; + }; + }; + + fragment@4 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@5 { + target = <&spidev1>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@6 { + target = <&spi0>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + cs-gpios = <&gpio 7 GPIO_ACTIVE_LOW>; + + wm5102@0{ + compatible = "wlf,wm5102"; + reg = <0>; + + pinctrl-names = "default"; + pinctrl-0 = <&wlf_5102_pins>; + + spi-max-frequency = <500000>; + + interrupt-parent = <&gpio>; + interrupts = <27 8>; + interrupt-controller; + #interrupt-cells = <2>; + + gpio-controller; + #gpio-cells = <2>; + + LDOVDD-supply = <&rpi_cirrus_reg_1v8>; + AVDD-supply = <&rpi_cirrus_reg_1v8>; + DBVDD1-supply = <&rpi_cirrus_reg_1v8>; + DBVDD2-supply = <&vdd_3v3_reg>; + DBVDD3-supply = <&vdd_3v3_reg>; + CPVDD-supply = <&rpi_cirrus_reg_1v8>; + SPKVDDL-supply = <&vdd_5v0_reg>; + SPKVDDR-supply = <&vdd_5v0_reg>; + DCVDD-supply = <&arizona_ldo1>; + + reset-gpios = <&gpio 17 GPIO_ACTIVE_HIGH>; + wlf,ldoena = <&gpio 22 GPIO_ACTIVE_HIGH>; + wlf,gpio-defaults = < + ARIZONA_GP_DEFAULT + ARIZONA_GP_DEFAULT + ARIZONA_GP_DEFAULT + ARIZONA_GP_DEFAULT + ARIZONA_GP_DEFAULT + >; + wlf,micd-configs = <0 1 0>; + wlf,dmic-ref = < + ARIZONA_DMIC_MICVDD + ARIZONA_DMIC_MICBIAS2 + ARIZONA_DMIC_MICVDD + ARIZONA_DMIC_MICVDD + >; + wlf,inmode = < + ARIZONA_INMODE_DIFF + ARIZONA_INMODE_DMIC + ARIZONA_INMODE_SE + ARIZONA_INMODE_DIFF + >; + status = "okay"; + + arizona_ldo1: ldo1 { + regulator-name = "LDO1"; + // default constraints as in + // arizona-ldo1.c + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1800000>; + }; + }; + }; + }; + + fragment@7 { + target = <&i2c1>; + __overlay__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + wm8804@3b { + compatible = "wlf,wm8804"; + reg = <0x3b>; + status = "okay"; + + pinctrl-names = "default"; + pinctrl-0 = <&wlf_8804_pins>; + + PVDD-supply = <&vdd_3v3_reg>; + DVDD-supply = <&vdd_3v3_reg>; + wlf,reset-gpio = <&gpio 8 GPIO_ACTIVE_HIGH>; + }; + }; + }; + + fragment@8 { + target = <&sound>; + __overlay__ { + compatible = "wlf,rpi-cirrus"; + i2s-controller = <&i2s_clk_consumer>; + status = "okay"; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/cm-swap-i2c0-overlay.dts b/arch/arm/boot/dts/overlays/cm-swap-i2c0-overlay.dts new file mode 100644 index 00000000000000..6b7f599f761156 --- /dev/null +++ b/arch/arm/boot/dts/overlays/cm-swap-i2c0-overlay.dts @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Definitions for IMX708 camera module on VC I2C bus +/dts-v1/; +/plugin/; + +#include <dt-bindings/gpio/gpio.h> + +/{ + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2c0mux>; + i2c0mux_frag: __overlay__ { + pinctrl-0 = <&i2c0_gpio28>; + pinctrl-1 = <&i2c0_gpio0>; + }; + }; + + __overrides__ { + i2c0-gpio0 = <&i2c0mux_frag>, "pinctrl-0:0=",<&i2c0_gpio0>; + i2c0-gpio28 = <&i2c0mux_frag>, "pinctrl-0:0=",<&i2c0_gpio28>; + i2c0-gpio44 = <&i2c0mux_frag>, "pinctrl-0:0=",<&i2c0_gpio44>; + i2c10-gpio0 = <&i2c0mux_frag>, "pinctrl-1:0=",<&i2c0_gpio0>; + i2c10-gpio28 = <&i2c0mux_frag>, "pinctrl-1:0=",<&i2c0_gpio28>; + i2c10-gpio44 = <&i2c0mux_frag>, "pinctrl-1:0=",<&i2c0_gpio44>; + }; +}; diff --git a/arch/arm/boot/dts/overlays/cma-overlay.dts b/arch/arm/boot/dts/overlays/cma-overlay.dts new file mode 100644 index 00000000000000..1d87c599f909dc --- /dev/null +++ b/arch/arm/boot/dts/overlays/cma-overlay.dts @@ -0,0 +1,36 @@ +/* + * cma.dts + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&cma>; + frag0: __overlay__ { + /* + * The default size when using this overlay is 256 MB + * and should be kept as is for backwards + * compatibility. + */ + size = <0x10000000>; + }; + }; + + __overrides__ { + cma-512 = <&frag0>,"size:0=",<0x20000000>; + cma-448 = <&frag0>,"size:0=",<0x1c000000>; + cma-384 = <&frag0>,"size:0=",<0x18000000>; + cma-320 = <&frag0>,"size:0=",<0x14000000>; + cma-256 = <&frag0>,"size:0=",<0x10000000>; + cma-192 = <&frag0>,"size:0=",<0xC000000>; + cma-128 = <&frag0>,"size:0=",<0x8000000>; + cma-96 = <&frag0>,"size:0=",<0x6000000>; + cma-64 = <&frag0>,"size:0=",<0x4000000>; + cma-size = <&frag0>,"size:0"; /* in bytes, 4MB aligned */ + cma-default = <0>,"-0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/crystalfontz-cfa050_pi_m-overlay.dts b/arch/arm/boot/dts/overlays/crystalfontz-cfa050_pi_m-overlay.dts new file mode 100644 index 00000000000000..544036589b667e --- /dev/null +++ b/arch/arm/boot/dts/overlays/crystalfontz-cfa050_pi_m-overlay.dts @@ -0,0 +1,124 @@ +/* + * crystalfontz-cfa050_pi_m-overlay.dts + * Configures the Crystalfontz CFA050-PI-M series of modules + * using CFAF7201280A0-050TC/TN panels with RaspberryPi CM4 DSI1 + */ +/dts-v1/; +/plugin/; +/{ +// RaspberryPi CM4 + compatible = "brcm,bcm2835"; +// PCF8574 I2C GPIO EXPANDER + fragment@0 { + target = <&i2c_csi_dsi>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + pcf8574a: pcf8574a@38 { + reg = <0x38>; + compatible = "nxp,pcf8574"; + gpio-controller; + #gpio-cells = <2>; + ngpios = <8>; + gpio-line-names = "TFT_RESET", "TOUCH_RESET", "EXT_P2", "EXT_P3", + "EXT_P4", "EXT_P5", "EXT_P6", "EXT_P7"; + }; + }; + }; +// LM3630a BACKLIGHT LED CONTROLLER + fragment@1 { + target = <&i2c_csi_dsi>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + lm3630a: backlight@36 { + reg = <0x36>; + compatible = "ti,lm3630a"; + #address-cells = <1>; + #size-cells = <0>; + led@0 { + reg = <0>; + led-sources = <0 1>; + label = "lcd-backlight"; + default-brightness = <128>; + max-brightness = <255>; + }; + }; + }; + }; +// CFAF7201280A0_050Tx TFT DSI PANEL + fragment@2 { + target = <&dsi1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + port { + dsi_out: endpoint { + remote-endpoint = <&panel_in>; + }; + }; + dsi_panel: dsi_panel@0 { + compatible = "crystalfontz,cfaf7201280a0_050tx"; + reg = <0>; + reset-gpios = <&pcf8574a 0 1>; + backlight = <&lm3630a>; + fps = <60>; + port { + panel_in: endpoint { + remote-endpoint = <&dsi_out>; + }; + }; + }; + }; + }; +// rPI GPIO INPUT FOR TOUCH IC IRQ + fragment@3 { + target = <&gpio>; + __dormant__ { + gt928intpins: gt928intpins { + brcm,pins = <26>; + brcm,function = <0>; + brcm,pull = <1>; + }; + }; + }; +// GT928 TOUCH CONTROLLER IC + fragment@4 { + target = <&i2c_csi_dsi>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + gt928@5d { + compatible = "goodix,gt928"; + reg = <0x5d>; + interrupt-parent = <&gpio>; + interrupts = <26 2>; + irq-gpios = <&gpio 26 0>; + reset-gpios = <&pcf8574a 1 1>; + touchscreen-inverted-x; + touchscreen-inverted-y; + }; + }; + }; +// PCF85063A RTC on I2C + fragment@5 { + target = <&i2c_csi_dsi>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + pcf85063a@51 { + compatible = "nxp,pcf85063a"; + reg = <0x51>; + }; + }; + }; +// CAPACITIVE TOUCH OPTION FOR TFT PANEL + __overrides__ { + captouch = <0>,"+3+4"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/cutiepi-panel-overlay.dts b/arch/arm/boot/dts/overlays/cutiepi-panel-overlay.dts new file mode 100644 index 00000000000000..d14c3698eb752f --- /dev/null +++ b/arch/arm/boot/dts/overlays/cutiepi-panel-overlay.dts @@ -0,0 +1,117 @@ +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2711"; + + fragment@0 { + target=<&dsi1>; + + __overlay__ { + status = "okay"; + + #address-cells = <1>; + #size-cells = <0>; + + port { + dsi1_out_port: endpoint { + remote-endpoint = <&panel_dsi_in1>; + }; + }; + + display1: panel@0 { + compatible = "nwe,nwe080"; + reg=<0>; + backlight = <&rpi_backlight>; + reset-gpios = <&gpio 20 0>; + port { + panel_dsi_in1: endpoint { + remote-endpoint = <&dsi1_out_port>; + }; + }; + }; + }; + }; + + fragment@1 { + target = <&gpio>; + __overlay__ { + pwm_pins: pwm_pins { + brcm,pins = <12>; + brcm,function = <4>; // ALT0 + }; + }; + }; + + fragment@2 { + target = <&pwm>; + frag1: __overlay__ { + pinctrl-names = "default"; + pinctrl-0 = <&pwm_pins>; + assigned-clock-rates = <1000000>; + status = "okay"; + }; + }; + + fragment@3 { + target-path = "/"; + __overlay__ { + rpi_backlight: rpi_backlight { + compatible = "pwm-backlight"; + brightness-levels = <0 6 8 12 16 24 32 40 48 64 96 128 160 192 224 255>; + default-brightness-level = <6>; + pwms = <&pwm 0 200000 0>; + power-supply = <&vdd_3v3_reg>; + status = "okay"; + }; + }; + }; + + fragment@4 { + target = <&i2c6>; + frag0: __overlay__ { + status = "okay"; + pinctrl-names = "default"; + pinctrl-0 = <&i2c6_pins>; + clock-frequency = <100000>; + }; + }; + + fragment@5 { + target = <&i2c6_pins>; + __overlay__ { + brcm,pins = <22 23>; + }; + }; + + fragment@6 { + target = <&gpio>; + __overlay__ { + goodix_pins: goodix_pins { + brcm,pins = <21 26>; // interrupt and reset + brcm,function = <0 0>; // in + brcm,pull = <2 2>; // pull-up + }; + }; + }; + + fragment@7 { + target = <&i2c6>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + gt9xx: gt9xx@5d { + compatible = "goodix,gt9271"; + reg = <0x5D>; + pinctrl-names = "default"; + pinctrl-0 = <&goodix_pins>; + interrupt-parent = <&gpio>; + interrupts = <21 2>; // high-to-low edge triggered + irq-gpios = <&gpio 21 0>; + reset-gpios = <&gpio 26 0>; + }; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/dacberry400-overlay.dts b/arch/arm/boot/dts/overlays/dacberry400-overlay.dts new file mode 100644 index 00000000000000..c9ac11db20de79 --- /dev/null +++ b/arch/arm/boot/dts/overlays/dacberry400-overlay.dts @@ -0,0 +1,71 @@ +// Definitions for DACberry400 +/dts-v1/; +/plugin/; +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_producer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target-path = "/"; + __overlay__ { + codec_1v8_reg: codec-1v8-reg { + compatible = "regulator-fixed"; + regulator-name = "tlv320aic3104_1v8"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; + }; + }; + }; + + fragment@2 { + target = <&gpio>; + __overlay__ { + codec_rst: codec-rst { + brcm,pins = <26>; + brcm,function = <1>; + }; + }; + }; + + fragment@3 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + tlv320aic3104@18 { + #sound-dai-cells = <0>; + reg = <0x18>; + + compatible = "ti,tlv320aic3x"; + AVDD-supply = <&vdd_3v3_reg>; + DRVDD-supply = <&vdd_3v3_reg>; + DVDD-supply = <&codec_1v8_reg>; + IOVDD-supply = <&codec_1v8_reg>; + + gpio-controller; + reset-gpios = <&gpio 26 1>; + status = "okay"; + }; + }; + }; + + fragment@4 { + target = <&sound>; + __overlay__ { + compatible = "osaelectronics,dacberry400"; + i2s-controller = <&i2s_clk_producer>; + status = "okay"; + }; + }; +}; + + diff --git a/arch/arm/boot/dts/overlays/dht11-overlay.dts b/arch/arm/boot/dts/overlays/dht11-overlay.dts new file mode 100644 index 00000000000000..8b0fc6b7a3cb6a --- /dev/null +++ b/arch/arm/boot/dts/overlays/dht11-overlay.dts @@ -0,0 +1,48 @@ +/* + * Overlay for the DHT11/21/22 humidity/temperature sensor modules. + */ +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target-path = "/"; + __overlay__ { + dht11: dht11@4 { + compatible = "dht11"; + pinctrl-names = "default"; + pinctrl-0 = <&dht11_pins>; + gpios = <&gpio 4 0>; + status = "okay"; + #io-channel-cells = <1>; + }; + + iio: iio-hwmon@4 { + compatible = "iio-hwmon"; + status = "okay"; + io-channels = <&dht11 0>, <&dht11 1>; + }; + }; + }; + + fragment@1 { + target = <&gpio>; + __overlay__ { + dht11_pins: dht11_pins@4 { + brcm,pins = <4>; + brcm,function = <0>; // in + brcm,pull = <0>; // off + }; + }; + }; + + __overrides__ { + gpiopin = <&dht11_pins>,"brcm,pins:0", + <&dht11_pins>, "reg:0", + <&dht11>,"gpios:4", + <&dht11>,"reg:0", + <&iio>,"reg:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/dionaudio-kiwi-overlay.dts b/arch/arm/boot/dts/overlays/dionaudio-kiwi-overlay.dts new file mode 100644 index 00000000000000..ab0144cd17dc26 --- /dev/null +++ b/arch/arm/boot/dts/overlays/dionaudio-kiwi-overlay.dts @@ -0,0 +1,39 @@ +// Definitions for Dion Audio KIWI streamer + +/* + * PCM1794 DAC (in hardware mode). + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_producer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target-path = "/"; + __overlay__ { + pcm1794a-codec { + #sound-dai-cells = <0>; + compatible = "ti,pcm1794a"; + status = "okay"; + }; + }; + }; + + fragment@2 { + target = <&sound>; + __overlay__ { + compatible = "dionaudio,dionaudio-kiwi"; + i2s-controller = <&i2s_clk_producer>; + status = "okay"; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/dionaudio-loco-overlay.dts b/arch/arm/boot/dts/overlays/dionaudio-loco-overlay.dts new file mode 100644 index 00000000000000..6f4a9c1a824342 --- /dev/null +++ b/arch/arm/boot/dts/overlays/dionaudio-loco-overlay.dts @@ -0,0 +1,39 @@ +// Definitions for Dion Audio LOCO DAC-AMP + +/* + * PCM5242 DAC (in hardware mode) and TPA3118 AMP. + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_producer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target-path = "/"; + __overlay__ { + pcm5102a-codec { + #sound-dai-cells = <0>; + compatible = "ti,pcm5102a"; + status = "okay"; + }; + }; + }; + + fragment@2 { + target = <&sound>; + __overlay__ { + compatible = "dionaudio,loco-pcm5242-tpa3118"; + i2s-controller = <&i2s_clk_producer>; + status = "okay"; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/dionaudio-loco-v2-overlay.dts b/arch/arm/boot/dts/overlays/dionaudio-loco-v2-overlay.dts new file mode 100644 index 00000000000000..975a844eb27211 --- /dev/null +++ b/arch/arm/boot/dts/overlays/dionaudio-loco-v2-overlay.dts @@ -0,0 +1,49 @@ +/* + * Definitions for Dion Audio LOCO-V2 DAC-AMP + * eg. dtoverlay=dionaudio-loco-v2 + * + * PCM5242 DAC (in software mode) and TPA3255 AMP. + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&sound>; + frag0: __overlay__ { + compatible = "dionaudio,dionaudio-loco-v2"; + i2s-controller = <&i2s_clk_producer>; + status = "okay"; + }; + }; + + fragment@1 { + target = <&i2s_clk_producer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@2 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + pcm5122@4c { + #sound-dai-cells = <0>; + compatible = "ti,pcm5122"; + reg = <0x4d>; + status = "okay"; + }; + }; + }; + + __overrides__ { + 24db_digital_gain = <&frag0>,"dionaudio,24db_digital_gain?"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/disable-bt-overlay.dts b/arch/arm/boot/dts/overlays/disable-bt-overlay.dts new file mode 100644 index 00000000000000..f3a8af1375f06e --- /dev/null +++ b/arch/arm/boot/dts/overlays/disable-bt-overlay.dts @@ -0,0 +1,59 @@ +/dts-v1/; +/plugin/; + +/* Disable Bluetooth and restore UART0/ttyAMA0 over GPIOs 14 & 15. */ + +#include <dt-bindings/gpio/gpio.h> + +/{ + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&uart1>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@1 { + target = <&uart0>; + __overlay__ { + pinctrl-names = "default"; + pinctrl-0 = <&uart0_pins>; + status = "okay"; + }; + }; + + fragment@2 { + target = <&bt>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@3 { + target = <&uart0_pins>; + __overlay__ { + brcm,pins; + brcm,function; + brcm,pull; + }; + }; + + fragment@4 { + target = <&bt_pins>; + __overlay__ { + brcm,pins; + brcm,function; + brcm,pull; + }; + }; + + fragment@5 { + target-path = "/aliases"; + __overlay__ { + serial0 = "/soc/serial@7e201000"; + serial1 = "/soc/serial@7e215040"; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/disable-bt-pi5-overlay.dts b/arch/arm/boot/dts/overlays/disable-bt-pi5-overlay.dts new file mode 100644 index 00000000000000..6e23b64d44e72f --- /dev/null +++ b/arch/arm/boot/dts/overlays/disable-bt-pi5-overlay.dts @@ -0,0 +1,17 @@ +/dts-v1/; +/plugin/; + +/* Disable Bluetooth */ + +#include <dt-bindings/gpio/gpio.h> + +/{ + compatible = "brcm,bcm2712"; + + fragment@0 { + target = <&bluetooth>; + __overlay__ { + status = "disabled"; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/disable-emmc2-overlay.dts b/arch/arm/boot/dts/overlays/disable-emmc2-overlay.dts new file mode 100644 index 00000000000000..8cd1d7fa4a90ac --- /dev/null +++ b/arch/arm/boot/dts/overlays/disable-emmc2-overlay.dts @@ -0,0 +1,13 @@ +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2711"; + + fragment@0 { + target = <&emmc2>; + __overlay__ { + status = "disabled"; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/disable-wifi-overlay.dts b/arch/arm/boot/dts/overlays/disable-wifi-overlay.dts new file mode 100644 index 00000000000000..75e04646390002 --- /dev/null +++ b/arch/arm/boot/dts/overlays/disable-wifi-overlay.dts @@ -0,0 +1,20 @@ +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&mmc>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@1 { + target = <&mmcnr>; + __overlay__ { + status = "disabled"; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/disable-wifi-pi5-overlay.dts b/arch/arm/boot/dts/overlays/disable-wifi-pi5-overlay.dts new file mode 100644 index 00000000000000..d5389c5dbb69aa --- /dev/null +++ b/arch/arm/boot/dts/overlays/disable-wifi-pi5-overlay.dts @@ -0,0 +1,13 @@ +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2712"; + + fragment@0 { + target = <&sdio2>; + __overlay__ { + status = "disabled"; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/dpi18-overlay.dts b/arch/arm/boot/dts/overlays/dpi18-overlay.dts new file mode 100644 index 00000000000000..4abe5be744db7a --- /dev/null +++ b/arch/arm/boot/dts/overlays/dpi18-overlay.dts @@ -0,0 +1,39 @@ +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2835"; + + // There is no DPI driver module, but we need a platform device + // node (that doesn't already use pinctrl) to hang the pinctrl + // reference on - leds will do + + fragment@0 { + target = <&fb>; + __overlay__ { + pinctrl-names = "default"; + pinctrl-0 = <&dpi18_pins>; + }; + }; + + fragment@1 { + target = <&vc4>; + __overlay__ { + pinctrl-names = "default"; + pinctrl-0 = <&dpi18_pins>; + }; + }; + + fragment@2 { + target = <&gpio>; + __overlay__ { + dpi18_pins: dpi18_pins { + brcm,pins = <0 1 2 3 4 5 6 7 8 9 10 11 + 12 13 14 15 16 17 18 19 20 + 21>; + brcm,function = <6>; /* alt2 */ + brcm,pull = <0>; /* no pull */ + }; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/dpi18cpadhi-overlay.dts b/arch/arm/boot/dts/overlays/dpi18cpadhi-overlay.dts new file mode 100644 index 00000000000000..50c88a1ed299b6 --- /dev/null +++ b/arch/arm/boot/dts/overlays/dpi18cpadhi-overlay.dts @@ -0,0 +1,26 @@ +/* + * dpi18cpadhi-overlay.dts + */ + +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&fb>; + __overlay__ { + pinctrl-names = "default"; + pinctrl-0 = <&dpi_18bit_cpadhi_gpio0>; + }; + }; + + fragment@1 { + target = <&vc4>; + __overlay__ { + pinctrl-names = "default"; + pinctrl-0 = <&dpi_18bit_cpadhi_gpio0>; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/dpi24-overlay.dts b/arch/arm/boot/dts/overlays/dpi24-overlay.dts new file mode 100644 index 00000000000000..44335cc812770b --- /dev/null +++ b/arch/arm/boot/dts/overlays/dpi24-overlay.dts @@ -0,0 +1,39 @@ +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2835"; + + // There is no DPI driver module, but we need a platform device + // node (that doesn't already use pinctrl) to hang the pinctrl + // reference on - leds will do + + fragment@0 { + target = <&fb>; + __overlay__ { + pinctrl-names = "default"; + pinctrl-0 = <&dpi24_pins>; + }; + }; + + fragment@1 { + target = <&vc4>; + __overlay__ { + pinctrl-names = "default"; + pinctrl-0 = <&dpi24_pins>; + }; + }; + + fragment@2 { + target = <&gpio>; + __overlay__ { + dpi24_pins: dpi24_pins { + brcm,pins = <0 1 2 3 4 5 6 7 8 9 10 11 + 12 13 14 15 16 17 18 19 20 + 21 22 23 24 25 26 27>; + brcm,function = <6>; /* alt2 */ + brcm,pull = <0>; /* no pull */ + }; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/draws-overlay.dts b/arch/arm/boot/dts/overlays/draws-overlay.dts new file mode 100644 index 00000000000000..b8801f583369d7 --- /dev/null +++ b/arch/arm/boot/dts/overlays/draws-overlay.dts @@ -0,0 +1,208 @@ +#include <dt-bindings/clock/bcm2835.h> +/* + * Device tree overlay for the DRAWS Hardware + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + fragment@0 { + target = <&i2s_clk_producer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target-path = "/"; + __overlay__ { + regulators { + compatible = "simple-bus"; + #address-cells = <1>; + #size-cells = <0>; + + udrc0_ldoin: udrc0_ldoin { + compatible = "regulator-fixed"; + regulator-name = "ldoin"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + }; + + sc16is752_clk: sc16is752_draws_clk { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <1843200>; + }; + }; + + pps: pps { + compatible = "pps-gpio"; + pinctrl-names = "default"; + pinctrl-0 = <&pps_pins>; + gpios = <&gpio 7 0>; + status = "okay"; + }; + + iio-hwmon { + compatible = "iio-hwmon"; + status = "okay"; + io-channels = <&tla2024 4>, <&tla2024 5>, <&tla2024 6>, + <&tla2024 7>; + }; + }; + }; + + fragment@2 { + target = <&i2c_arm>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + tlv320aic32x4: tlv320aic32x4@18 { + compatible = "ti,tlv320aic32x4"; + reg = <0x18>; + #sound-dai-cells = <0>; + status = "okay"; + + clocks = <&clocks BCM2835_CLOCK_GP0>; + clock-names = "mclk"; + assigned-clocks = <&clocks BCM2835_CLOCK_GP0>; + assigned-clock-rates = <25000000>; + + pinctrl-names = "default"; + pinctrl-0 = <&gpclk0_pin &aic3204_reset>; + + reset-gpios = <&gpio 13 0>; + + iov-supply = <&udrc0_ldoin>; + ldoin-supply = <&udrc0_ldoin>; + }; + + sc16is752: sc16is752@50 { + compatible = "nxp,sc16is752"; + reg = <0x50>; + clocks = <&sc16is752_clk>; + interrupt-parent = <&gpio>; + interrupts = <17 2>; /* IRQ_TYPE_EDGE_FALLING */ + + pinctrl-names = "default"; + pinctrl-0 = <&sc16is752_irq>; + }; + + tla2024: tla2024@48 { + compatible = "ti,ads1015"; + reg = <0x48>; + #address-cells = <1>; + #size-cells = <0>; + #io-channel-cells = <1>; + + adc_ch4: channel@4 { + reg = <4>; + ti,gain = <1>; + ti,datarate = <4>; + }; + + adc_ch5: channel@5 { + reg = <5>; + ti,gain = <1>; + ti,datarate = <4>; + }; + + adc_ch6: channel@6 { + reg = <6>; + ti,gain = <2>; + ti,datarate = <4>; + }; + + adc_ch7: channel@7 { + reg = <7>; + ti,gain = <2>; + ti,datarate = <4>; + }; + }; + }; + }; + + fragment@3 { + target = <&sound>; + snd: __overlay__ { + compatible = "simple-audio-card"; + i2s-controller = <&i2s_clk_producer>; + status = "okay"; + + simple-audio-card,name = "draws"; + simple-audio-card,format = "i2s"; + + simple-audio-card,bitclock-master = <&dailink0_master>; + simple-audio-card,frame-master = <&dailink0_master>; + + simple-audio-card,widgets = + "Line", "Line In", + "Line", "Line Out"; + + simple-audio-card,routing = + "IN1_R", "Line In", + "IN1_L", "Line In", + "CM_L", "Line In", + "CM_R", "Line In", + "Line Out", "LOR", + "Line Out", "LOL"; + + dailink0_master: simple-audio-card,cpu { + sound-dai = <&i2s_clk_producer>; + }; + + simple-audio-card,codec { + sound-dai = <&tlv320aic32x4>; + }; + }; + }; + + fragment@4 { + target = <&gpio>; + __overlay__ { + gpclk0_pin: gpclk0_pin { + brcm,pins = <4>; + brcm,function = <4>; + }; + + aic3204_reset: aic3204_reset { + brcm,pins = <13>; + brcm,function = <1>; + brcm,pull = <1>; + }; + + aic3204_gpio: aic3204_gpio { + brcm,pins = <26>; + }; + + sc16is752_irq: sc16is752_irq { + brcm,pins = <17>; + brcm,function = <0>; + brcm,pull = <2>; + }; + + pps_pins: pps_pins { + brcm,pins = <7>; + brcm,function = <0>; + brcm,pull = <0>; + }; + }; + }; + + __overrides__ { + draws_adc_ch4_gain = <&adc_ch4>,"ti,gain:0"; + draws_adc_ch4_datarate = <&adc_ch4>,"ti,datarate:0"; + draws_adc_ch5_gain = <&adc_ch5>,"ti,gain:0"; + draws_adc_ch5_datarate = <&adc_ch5>,"ti,datarate:0"; + draws_adc_ch6_gain = <&adc_ch6>,"ti,gain:0"; + draws_adc_ch6_datarate = <&adc_ch6>,"ti,datarate:0"; + draws_adc_ch7_gain = <&adc_ch7>,"ti,gain:0"; + draws_adc_ch7_datarate = <&adc_ch7>,"ti,datarate:0"; + alsaname = <&snd>, "simple-audio-card,name"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/dwc-otg-overlay.dts b/arch/arm/boot/dts/overlays/dwc-otg-overlay.dts new file mode 100644 index 00000000000000..78c5e9f850484b --- /dev/null +++ b/arch/arm/boot/dts/overlays/dwc-otg-overlay.dts @@ -0,0 +1,14 @@ +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&usb>; + __overlay__ { + compatible = "brcm,bcm2708-usb"; + status = "okay"; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/dwc2-overlay.dts b/arch/arm/boot/dts/overlays/dwc2-overlay.dts new file mode 100644 index 00000000000000..0d83e344ad9735 --- /dev/null +++ b/arch/arm/boot/dts/overlays/dwc2-overlay.dts @@ -0,0 +1,26 @@ +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&usb>; + #address-cells = <1>; + #size-cells = <1>; + dwc2_usb: __overlay__ { + compatible = "brcm,bcm2835-usb"; + dr_mode = "otg"; + g-np-tx-fifo-size = <32>; + g-rx-fifo-size = <558>; + g-tx-fifo-size = <512 512 512 512 512 256 256>; + status = "okay"; + }; + }; + + __overrides__ { + dr_mode = <&dwc2_usb>, "dr_mode"; + g-np-tx-fifo-size = <&dwc2_usb>,"g-np-tx-fifo-size:0"; + g-rx-fifo-size = <&dwc2_usb>,"g-rx-fifo-size:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/edt-ft5406-overlay.dts b/arch/arm/boot/dts/overlays/edt-ft5406-overlay.dts new file mode 100644 index 00000000000000..7cbc3fa673a307 --- /dev/null +++ b/arch/arm/boot/dts/overlays/edt-ft5406-overlay.dts @@ -0,0 +1,22 @@ +/* + * Device Tree overlay for EDT 5406 touchscreen controller, as used on the + * Raspberry Pi 7" panel + * + */ + +/dts-v1/; +/plugin/; + +#define ENABLE_I2C0_MUX +#include "i2c-buses.dtsi" +#include "edt-ft5406.dtsi" + +&busfrag { + target = <&i2c_csi_dsi>; +}; + +/ { + __overrides__ { + addr = <&ft5406>,"reg:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/edt-ft5406.dtsi b/arch/arm/boot/dts/overlays/edt-ft5406.dtsi new file mode 100644 index 00000000000000..3be2f18e898f2b --- /dev/null +++ b/arch/arm/boot/dts/overlays/edt-ft5406.dtsi @@ -0,0 +1,48 @@ +/* + * Device Tree overlay for an EDT FT5406 touchscreen + * + * Note that this is included from vc4-kms-dsi-7inch, hence the + * fragment numbers not starting at 0. + */ + +/ { + compatible = "brcm,bcm2835"; + + fragment@10 { + target = <&ft5406>; + __overlay__ { + touchscreen-inverted-x; + }; + }; + + fragment@11 { + target = <&ft5406>; + __overlay__ { + touchscreen-inverted-y; + }; + }; + + fragment@12 { + target = <&i2cbus>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + + ft5406: ts@38 { + compatible = "edt,edt-ft5506"; + reg = <0x38>; + + touchscreen-size-x = < 800 >; + touchscreen-size-y = < 480 >; + }; + }; + }; + + __overrides__ { + sizex = <&ft5406>,"touchscreen-size-x:0"; + sizey = <&ft5406>,"touchscreen-size-y:0"; + invx = <0>, "-10"; + invy = <0>, "-11"; + swapxy = <&ft5406>,"touchscreen-swapped-x-y?"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/enc28j60-overlay.dts b/arch/arm/boot/dts/overlays/enc28j60-overlay.dts new file mode 100644 index 00000000000000..7af5c2e607ea0b --- /dev/null +++ b/arch/arm/boot/dts/overlays/enc28j60-overlay.dts @@ -0,0 +1,53 @@ +// Overlay for the Microchip ENC28J60 Ethernet Controller +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + status = "okay"; + + eth1: enc28j60@0{ + compatible = "microchip,enc28j60"; + reg = <0>; /* CE0 */ + pinctrl-names = "default"; + pinctrl-0 = <ð1_pins>; + interrupt-parent = <&gpio>; + interrupts = <25 0x2>; /* falling edge */ + spi-max-frequency = <12000000>; + status = "okay"; + }; + }; + }; + + fragment@1 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@2 { + target = <&gpio>; + __overlay__ { + eth1_pins: eth1_pins { + brcm,pins = <25>; + brcm,function = <0>; /* in */ + brcm,pull = <0>; /* none */ + }; + }; + }; + + __overrides__ { + int_pin = <ð1>, "interrupts:0", + <ð1_pins>, "brcm,pins:0"; + speed = <ð1>, "spi-max-frequency:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/enc28j60-spi2-overlay.dts b/arch/arm/boot/dts/overlays/enc28j60-spi2-overlay.dts new file mode 100644 index 00000000000000..17cb5b8fa4852c --- /dev/null +++ b/arch/arm/boot/dts/overlays/enc28j60-spi2-overlay.dts @@ -0,0 +1,47 @@ +// Overlay for the Microchip ENC28J60 Ethernet Controller - SPI2 Compute Module +// Interrupt pin: 39 +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spi2>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + status = "okay"; + + eth1: enc28j60@0{ + compatible = "microchip,enc28j60"; + reg = <0>; /* CE0 */ + pinctrl-names = "default"; + pinctrl-0 = <ð1_pins>; + interrupt-parent = <&gpio>; + interrupts = <39 0x2>; /* falling edge */ + spi-max-frequency = <12000000>; + status = "okay"; + }; + }; + }; + + fragment@1 { + target = <&gpio>; + __overlay__ { + eth1_pins: eth1_pins { + brcm,pins = <39>; + brcm,function = <0>; /* in */ + brcm,pull = <0>; /* none */ + }; + }; + }; + + __overrides__ { + int_pin = <ð1>, "interrupts:0", + <ð1_pins>, "brcm,pins:0"; + speed = <ð1>, "spi-max-frequency:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/exc3000-overlay.dts b/arch/arm/boot/dts/overlays/exc3000-overlay.dts new file mode 100644 index 00000000000000..6f087fb206618f --- /dev/null +++ b/arch/arm/boot/dts/overlays/exc3000-overlay.dts @@ -0,0 +1,48 @@ +// Device tree overlay for I2C connected EETI EXC3000 multiple touch controller +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&gpio>; + __overlay__ { + exc3000_pins: exc3000_pins { + brcm,pins = <4>; // interrupt + brcm,function = <0>; // in + brcm,pull = <2>; // pull-up + }; + }; + }; + + fragment@1 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + exc3000: exc3000@2a { + compatible = "eeti,exc3000"; + reg = <0x2a>; + pinctrl-names = "default"; + pinctrl-0 = <&exc3000_pins>; + interrupt-parent = <&gpio>; + interrupts = <4 8>; // active low level-sensitive + touchscreen-size-x = <4096>; + touchscreen-size-y = <4096>; + }; + }; + }; + + __overrides__ { + interrupt = <&exc3000_pins>,"brcm,pins:0", + <&exc3000>,"interrupts:0"; + sizex = <&exc3000>,"touchscreen-size-x:0"; + sizey = <&exc3000>,"touchscreen-size-y:0"; + invx = <&exc3000>,"touchscreen-inverted-x?"; + invy = <&exc3000>,"touchscreen-inverted-y?"; + swapxy = <&exc3000>,"touchscreen-swapped-x-y?"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/ezsound-6x8iso-overlay.dts b/arch/arm/boot/dts/overlays/ezsound-6x8iso-overlay.dts new file mode 100644 index 00000000000000..764ad9a56edc29 --- /dev/null +++ b/arch/arm/boot/dts/overlays/ezsound-6x8iso-overlay.dts @@ -0,0 +1,117 @@ +//Device tree overlay for ezsound 6x8 isolated card +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2712"; + + fragment@0 { + target = <&rp1_i2s0_18_21>; + __overlay__ { + pins = "gpio18", "gpio19", "gpio20", "gpio22", "gpio24", + "gpio26", "gpio21", "gpio23", "gpio25", "gpio27"; + }; + }; + + fragment@1 { + target = <&rp1_i2s1_18_21>; + __overlay__ { + pins = "gpio18", "gpio19", "gpio20", "gpio22", "gpio24", + "gpio26", "gpio21", "gpio23", "gpio25", "gpio27"; + }; + }; + + fragment@2 { + target = <&sound>; + __overlay__ { + compatible = "simple-audio-card"; + #address-cells = <1>; + #size-cells = <0>; + i2s-controller = <&i2s_clk_consumer>; + status="okay"; + + simple-audio-card,name = "ezsound-6x8"; + + dailink_out_master: simple-audio-card,dai-link@0 { + reg = <0>; + format = "i2s"; + bitclock-master = <&pcm3168_playback>; + frame-master = <&pcm3168_playback>; + cpu { + sound-dai = <&i2s_clk_consumer>; + dai-tdm-slot-num = <2>; + dai-tdm-slot-width = <32>; + }; + pcm3168_playback: codec { + system-clock-fixed; + mclk-fs = <256>; + sound-dai = <&pcm3168a 0>; + dai-tdm-slot-num = <2>; + dai-tdm-slot-width = <32>; + }; + }; + dailink_in_slave: simple-audio-card,dai-link@1 { + reg = <1>; + format = "i2s"; + bitclock-master = <&pcm3168_capture>; + frame-master = <&pcm3168_capture>; + cpu { + sound-dai = <&i2s_clk_consumer>; + dai-tdm-slot-num = <2>; + dai-tdm-slot-width = <32>; + }; + pcm3168_capture: codec { + system-clock-fixed; + mclk-fs = <256>; + sound-dai = <&pcm3168a 1>; + dai-tdm-slot-num = <2>; + dai-tdm-slot-width = <32>; + }; + }; + }; + }; + + fragment@3 { + target-path = "/"; + __overlay__ { + scki_clk: scki-clock { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <24576000>; + }; + }; + }; + + // Bring the I2S clock consumer block up + fragment@4 { + target = <&i2s_clk_consumer>; + __overlay__ { + #sound-dai-cells = <0>; + status = "okay"; + }; + }; + + fragment@5 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + + pcm3168a: audio-codec@45 { + #sound-dai-cells = <1>; + compatible = "ti,pcm3168a"; + status = "okay"; + reg = <0x45>; + clocks = <&scki_clk>; + clock-names = "scki"; + dac-force-cons; + VDD1-supply = <&vdd_3v3_reg>; + VDD2-supply = <&vdd_3v3_reg>; + VCCAD1-supply = <&vdd_5v0_reg>; + VCCAD2-supply = <&vdd_5v0_reg>; + VCCDA1-supply = <&vdd_5v0_reg>; + VCCDA2-supply = <&vdd_5v0_reg>; + }; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/fbtft-overlay.dts b/arch/arm/boot/dts/overlays/fbtft-overlay.dts new file mode 100644 index 00000000000000..db45f8c53bcc65 --- /dev/null +++ b/arch/arm/boot/dts/overlays/fbtft-overlay.dts @@ -0,0 +1,611 @@ +/* + * Device Tree overlay for fbtft drivers + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + /* adafruit18 */ + fragment@0 { + target = <&display>; + __dormant__ { + compatible = "sitronix,st7735r"; + spi-max-frequency = <32000000>; + gamma = "02 1c 07 12 37 32 29 2d 29 25 2B 39 00 01 03 10\n03 1d 07 06 2E 2C 29 2D 2E 2E 37 3F 00 00 02 10"; + }; + }; + + /* adafruit22 */ + fragment@1 { + target = <&display>; + __dormant__ { + compatible = "himax,hx8340bn"; + spi-max-frequency = <32000000>; + buswidth = <9>; + bgr; + }; + }; + + /* adafruit22a */ + fragment@2 { + target = <&display>; + __dormant__ { + compatible = "ilitek,ili9340"; + spi-max-frequency = <32000000>; + bgr; + }; + }; + + /* adafruit28 */ + fragment@3 { + target = <&display>; + __dormant__ { + compatible = "ilitek,ili9341"; + spi-max-frequency = <32000000>; + bgr; + }; + }; + + /* adafruit13m */ + fragment@4 { + target = <&display>; + __dormant__ { + compatible = "solomon,ssd1306"; + spi-max-frequency = <16000000>; + }; + }; + + /* admatec_c-berry28 */ + fragment@5 { + target = <&display>; + __dormant__ { + compatible = "sitronix,st7789v"; + spi-max-frequency = <48000000>; + init = <0x01000011 + 0x02000078 + 0x0100003A 0x05 + 0x010000B2 0x0C 0x0C 0x00 0x33 0x33 + 0x010000B7 0x35 + 0x010000C2 0x01 0xFF + 0x010000C3 0x17 + 0x010000C4 0x20 + 0x010000BB 0x17 + 0x010000C5 0x20 + 0x010000D0 0xA4 0xA1 + 0x01000029>; + gamma = "D0 00 14 15 13 2C 42 43 4E 09 16 14 18 21\nD0 00 14 15 13 0B 43 55 53 0C 17 14 23 20"; + }; + }; + + /* dogs102 */ + fragment@6 { + target = <&display>; + __dormant__ { + compatible = "UltraChip,uc1701"; + spi-max-frequency = <8000000>; + bgr; + }; + }; + + /* er_tftm050_2 */ + fragment@7 { + target = <&display>; + __dormant__ { + compatible = "raio,ra8875"; + spi-max-frequency = <5000000>; + spi-cpha; + spi-cpol; + width = <480>; + height = <272>; + bgr; + }; + }; + + /* er_tftm070_5 */ + fragment@8 { + target = <&display>; + __dormant__ { + compatible = "raio,ra8875"; + spi-max-frequency = <5000000>; + spi-cpha; + spi-cpol; + width = <800>; + height = <480>; + bgr; + }; + }; + + /* ew24ha0 */ + fragment@9 { + target = <&display>; + __dormant__ { + compatible = "ultrachip,uc1611"; + spi-max-frequency = <32000000>; + spi-cpha; + spi-cpol; + }; + }; + + /* ew24ha0_9bit */ + fragment@10 { + target = <&display>; + __dormant__ { + compatible = "ultrachip,uc1611"; + spi-max-frequency = <32000000>; + spi-cpha; + spi-cpol; + buswidth = <9>; + }; + }; + + /* freetronicsoled128 */ + fragment@11 { + target = <&display>; + __dormant__ { + compatible = "solomon,ssd1351"; + spi-max-frequency = <20000000>; + backlight = <2>; /* FBTFT_ONBOARD_BACKLIGHT */ + bgr; + }; + }; + + /* hy28a */ + fragment@12 { + target = <&display>; + __dormant__ { + compatible = "ilitek,ili9320"; + spi-max-frequency = <32000000>; + spi-cpha; + spi-cpol; + startbyte = <0x70>; + bgr; + }; + }; + + /* hy28b */ + fragment@13 { + target = <&display>; + __dormant__ { + compatible = "ilitek,ili9325"; + spi-max-frequency = <48000000>; + spi-cpha; + spi-cpol; + init = <0x010000e7 0x0010 + 0x01000000 0x0001 + 0x01000001 0x0100 + 0x01000002 0x0700 + 0x01000003 0x1030 + 0x01000004 0x0000 + 0x01000008 0x0207 + 0x01000009 0x0000 + 0x0100000a 0x0000 + 0x0100000c 0x0001 + 0x0100000d 0x0000 + 0x0100000f 0x0000 + 0x01000010 0x0000 + 0x01000011 0x0007 + 0x01000012 0x0000 + 0x01000013 0x0000 + 0x02000032 + 0x01000010 0x1590 + 0x01000011 0x0227 + 0x02000032 + 0x01000012 0x009c + 0x02000032 + 0x01000013 0x1900 + 0x01000029 0x0023 + 0x0100002b 0x000e + 0x02000032 + 0x01000020 0x0000 + 0x01000021 0x0000 + 0x02000032 + 0x01000050 0x0000 + 0x01000051 0x00ef + 0x01000052 0x0000 + 0x01000053 0x013f + 0x01000060 0xa700 + 0x01000061 0x0001 + 0x0100006a 0x0000 + 0x01000080 0x0000 + 0x01000081 0x0000 + 0x01000082 0x0000 + 0x01000083 0x0000 + 0x01000084 0x0000 + 0x01000085 0x0000 + 0x01000090 0x0010 + 0x01000092 0x0000 + 0x01000093 0x0003 + 0x01000095 0x0110 + 0x01000097 0x0000 + 0x01000098 0x0000 + 0x01000007 0x0133 + 0x01000020 0x0000 + 0x01000021 0x0000 + 0x02000064>; + startbyte = <0x70>; + bgr; + fps = <50>; + gamma = "04 1F 4 7 7 0 7 7 6 0\n0F 00 1 7 4 0 0 0 6 7"; + }; + }; + + /* itdb28_spi */ + fragment@14 { + target = <&display>; + __dormant__ { + compatible = "ilitek,ili9325"; + spi-max-frequency = <32000000>; + bgr; + }; + }; + + /* mi0283qt-2 */ + fragment@15 { + target = <&display>; + __dormant__ { + compatible = "himax,hx8347d"; + spi-max-frequency = <32000000>; + startbyte = <0x70>; + bgr; + }; + }; + + /* mi0283qt-9a */ + fragment@16 { + target = <&display>; + __dormant__ { + compatible = "ilitek,ili9341"; + spi-max-frequency = <32000000>; + buswidth = <9>; + bgr; + }; + }; + + /* nokia3310 */ + fragment@17 { + target = <&display>; + __dormant__ { + compatible = "philips,pcd8544"; + spi-max-frequency = <400000>; + }; + }; + + /* nokia3310a */ + fragment@18 { + target = <&display>; + __dormant__ { + compatible = "teralane,tls8204"; + spi-max-frequency = <1000000>; + }; + }; + + /* nokia5110 */ + fragment@19 { + target = <&display>; + __dormant__ { + compatible = "ilitek,ili9163"; + spi-max-frequency = <12000000>; + bgr; + }; + }; + + /* piscreen */ + fragment@20 { + target = <&display>; + __dormant__ { + compatible = "ilitek,ili9486"; + spi-max-frequency = <32000000>; + regwidth = <16>; + bgr; + }; + }; + + /* pitft */ + fragment@21 { + target = <&display>; + __dormant__ { + compatible = "ilitek,ili9340"; + spi-max-frequency = <32000000>; + init = <0x01000001 + 0x02000005 + 0x01000028 + 0x010000EF 0x03 0x80 0x02 + 0x010000CF 0x00 0xC1 0x30 + 0x010000ED 0x64 0x03 0x12 0x81 + 0x010000E8 0x85 0x00 0x78 + 0x010000CB 0x39 0x2C 0x00 0x34 0x02 + 0x010000F7 0x20 + 0x010000EA 0x00 0x00 + 0x010000C0 0x23 + 0x010000C1 0x10 + 0x010000C5 0x3E 0x28 + 0x010000C7 0x86 + 0x0100003A 0x55 + 0x010000B1 0x00 0x18 + 0x010000B6 0x08 0x82 0x27 + 0x010000F2 0x00 + 0x01000026 0x01 + 0x010000E0 0x0F 0x31 0x2B 0x0C 0x0E 0x08 0x4E 0xF1 0x37 0x07 0x10 0x03 0x0E 0x09 0x00 + 0x010000E1 0x00 0x0E 0x14 0x03 0x11 0x07 0x31 0xC1 0x48 0x08 0x0F 0x0C 0x31 0x36 0x0F + 0x01000011 + 0x02000064 + 0x01000029 + 0x02000014>; + bgr; + }; + }; + + /* pioled */ + fragment@22 { + target = <&display>; + __dormant__ { + compatible = "solomon,ssd1351"; + spi-max-frequency = <20000000>; + bgr; + gamma = "0 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4"; + }; + }; + + /* rpi-display */ + fragment@23 { + target = <&display>; + __dormant__ { + compatible = "ilitek,ili9341"; + spi-max-frequency = <32000000>; + bgr; + }; + }; + + /* sainsmart18 */ + fragment@24 { + target = <&display>; + __dormant__ { + compatible = "sitronix,st7735r"; + spi-max-frequency = <32000000>; + }; + }; + + /* sainsmart32_spi */ + fragment@25 { + target = <&display>; + __dormant__ { + compatible = "solomon,ssd1289"; + spi-max-frequency = <16000000>; + bgr; + }; + }; + + /* tinylcd35 */ + fragment@26 { + target = <&display>; + __dormant__ { + compatible = "neosec,tinylcd"; + spi-max-frequency = <32000000>; + bgr; + }; + }; + + /* tm022hdh26 */ + fragment@27 { + target = <&display>; + __dormant__ { + compatible = "ilitek,ili9341"; + spi-max-frequency = <32000000>; + bgr; + }; + }; + + /* tontec35_9481 - boards before 02 July 2014 */ + fragment@28 { + target = <&display>; + __dormant__ { + compatible = "ilitek,ili9481"; + spi-max-frequency = <128000000>; + spi-cpha; + spi-cpol; + bgr; + }; + }; + + /* tontec35_9486 - boards after 02 July 2014 */ + fragment@29 { + target = <&display>; + __dormant__ { + compatible = "ilitek,ili9486"; + spi-max-frequency = <128000000>; + spi-cpha; + spi-cpol; + bgr; + }; + }; + + /* waveshare32b */ + fragment@30 { + target = <&display>; + __dormant__ { + compatible = "ilitek,ili9340"; + spi-max-frequency = <48000000>; + init = <0x010000CB 0x39 0x2C 0x00 0x34 0x02 + 0x010000CF 0x00 0xC1 0x30 + 0x010000E8 0x85 0x00 0x78 + 0x010000EA 0x00 0x00 + 0x010000ED 0x64 0x03 0x12 0x81 + 0x010000F7 0x20 + 0x010000C0 0x23 + 0x010000C1 0x10 + 0x010000C5 0x3E 0x28 + 0x010000C7 0x86 + 0x01000036 0x28 + 0x0100003A 0x55 + 0x010000B1 0x00 0x18 + 0x010000B6 0x08 0x82 0x27 + 0x010000F2 0x00 + 0x01000026 0x01 + 0x010000E0 0x0F 0x31 0x2B 0x0C 0x0E 0x08 0x4E 0xF1 0x37 0x07 0x10 0x03 0x0E 0x09 0x00 + 0x010000E1 0x00 0x0E 0x14 0x03 0x11 0x07 0x31 0xC1 0x48 0x08 0x0F 0x0C 0x31 0x36 0x0F + 0x01000011 + 0x02000078 + 0x01000029 + 0x0100002C>; + bgr; + }; + }; + + /* waveshare22 */ + fragment@31 { + target = <&display>; + __dormant__ { + compatible = "hitachi,bd663474"; + spi-max-frequency = <32000000>; + spi-cpha; + spi-cpol; + }; + }; + + spidev_fragment: fragment@100 { + target-path = "spi0/spidev@0"; + __overlay__ { + status = "disabled"; + }; + }; + + display_fragment: fragment@101 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + status = "okay"; + + display: display@0{ + reg = <0>; + spi-max-frequency = <32000000>; + fps = <30>; + buswidth = <8>; + }; + }; + }; + + __overrides__ { + spi0-0 = <&display_fragment>, "target:0=",<&spi0>, + <&spidev_fragment>, "target-path=spi0/spidev@0", + <&display>, "reg:0=0"; + spi0-1 = <&display_fragment>, "target:0=",<&spi0>, + <&spidev_fragment>, "target-path=spi0/spidev@1", + <&display>, "reg:0=1"; + spi1-0 = <&display_fragment>, "target:0=",<&spi1>, + <&spidev_fragment>, "target-path=spi1/spidev@0", + <&display>, "reg:0=0"; + spi1-1 = <&display_fragment>, "target:0=",<&spi1>, + <&spidev_fragment>, "target-path=spi1/spidev@1", + <&display>, "reg:0=1"; + spi1-2 = <&display_fragment>, "target:0=",<&spi1>, + <&spidev_fragment>, "target-path=spi1/spidev@2", + <&display>, "reg:0=2"; + spi2-0 = <&display_fragment>, "target:0=",<&spi2>, + <&spidev_fragment>, "target-path=spi2/spidev@0", + <&display>, "reg:0=0"; + spi2-1 = <&display_fragment>, "target:0=",<&spi2>, + <&spidev_fragment>, "target-path=spi2/spidev@1", + <&display>, "reg:0=1"; + spi2-2 = <&display_fragment>, "target:0=",<&spi2>, + <&spidev_fragment>, "target-path=spi2/spidev@2", + <&display>, "reg:0=2"; + + speed = <&display>, "spi-max-frequency:0"; + cpha = <&display>, "spi-cpha?"; + cpol = <&display>, "spi-cpol?"; + + /* Displays */ + adafruit18 = <0>, "+0"; + adafruit22 = <0>, "+1"; + adafruit22a = <0>, "+2"; + adafruit28 = <0>, "+3"; + adafruit13m = <0>, "+4"; + admatec_c-berry28 = <0>, "+5"; + dogs102 = <0>, "+6"; + er_tftm050_2 = <0>, "+7"; + er_tftm070_5 = <0>, "+8"; + ew24ha0 = <0>, "+9"; + ew24ha0_9bit = <0>, "+10"; + freetronicsoled128 = <0>, "+11"; + hy28a = <0>, "+12"; + hy28b = <0>, "+13"; + itdb28_spi = <0>, "+14"; + mi0283qt-2 = <0>, "+15"; + mi0283qt-9a = <0>, "+16"; + nokia3310 = <0>, "+17"; + nokia3310a = <0>, "+18"; + nokia5110 = <0>, "+19"; + piscreen = <0>, "+20"; + pitft = <0>, "+21"; + pioled = <0>, "+22"; + rpi-display = <0>, "+23"; + sainsmart18 = <0>, "+24"; + sainsmart32_spi = <0>, "+25"; + tinylcd35 = <0>, "+26"; + tm022hdh26 = <0>, "+27"; + tontec35_9481 = <0>, "+28"; + tontec35_9486 = <0>, "+29"; + waveshare32b = <0>, "+30"; + waveshare22 = <0>, "+31"; + + /* Controllers */ + bd663474 = <&display>, "compatible=hitachi,bd663474"; + hx8340bn = <&display>, "compatible=himax,hx8340bn"; + hx8347d = <&display>, "compatible=himax,hx8347d"; + hx8353d = <&display>, "compatible=himax,hx8353d"; + hx8357d = <&display>, "compatible=himax,hx8357d"; + ili9163 = <&display>, "compatible=ilitek,ili9163"; + ili9320 = <&display>, "compatible=ilitek,ili9320"; + ili9325 = <&display>, "compatible=ilitek,ili9325"; + ili9340 = <&display>, "compatible=ilitek,ili9340"; + ili9341 = <&display>, "compatible=ilitek,ili9341"; + ili9481 = <&display>, "compatible=ilitek,ili9481"; + ili9486 = <&display>, "compatible=ilitek,ili9486"; + pcd8544 = <&display>, "compatible=philips,pcd8544"; + ra8875 = <&display>, "compatible=raio,ra8875"; + s6d02a1 = <&display>, "compatible=samsung,s6d02a1"; + s6d1121 = <&display>, "compatible=samsung,s6d1121"; + seps525 = <&display>, "compatible=syncoam,seps525"; + sh1106 = <&display>, "compatible=sinowealth,sh1106"; + ssd1289 = <&display>, "compatible=solomon,ssd1289"; + ssd1305 = <&display>, "compatible=solomon,ssd1305"; + ssd1306 = <&display>, "compatible=solomon,ssd1306"; + ssd1325 = <&display>, "compatible=solomon,ssd1325"; + ssd1331 = <&display>, "compatible=solomon,ssd1331"; + ssd1351 = <&display>, "compatible=solomon,ssd1351"; + st7735r = <&display>, "compatible=sitronix,st7735r"; + st7789v = <&display>, "compatible=sitronix,st7789v"; + tls8204 = <&display>, "compatible=teralane,tls8204"; + uc1611 = <&display>, "compatible=ultrachip,uc1611"; + uc1701 = <&display>, "compatible=UltraChip,uc1701"; + upd161704 = <&display>, "compatible=nec,upd161704"; + + width = <&display>, "width:0"; + height = <&display>, "height:0"; + regwidth = <&display>, "regwidth:0"; + buswidth = <&display>, "buswidth:0"; + debug = <&display>, "debug:0"; + rotate = <&display>, "rotate:0"; + bgr = <&display>, "bgr?"; + fps = <&display>, "fps:0"; + txbuflen = <&display>, "txbuflen:0"; + startbyte = <&display>, "startbyte:0"; + gamma = <&display>, "gamma"; + + reset_pin = <&display>, "reset-gpios:0=", <&gpio>, + <&display>, "reset-gpios:4", + <&display>, "reset-gpios:8=1"; /* GPIO_ACTIVE_LOW */ + dc_pin = <&display>, "dc-gpios:0=", <&gpio>, + <&display>, "dc-gpios:4", + <&display>, "dc-gpios:8=0"; /* GPIO_ACTIVE_HIGH */ + led_pin = <&display>, "led-gpios:0=", <&gpio>, + <&display>, "led-gpios:4", + <&display>, "led-gpios:8=0"; /* GPIO_ACTIVE_HIGH */ + }; +}; diff --git a/arch/arm/boot/dts/overlays/fe-pi-audio-overlay.dts b/arch/arm/boot/dts/overlays/fe-pi-audio-overlay.dts new file mode 100644 index 00000000000000..10624fe4f5ac17 --- /dev/null +++ b/arch/arm/boot/dts/overlays/fe-pi-audio-overlay.dts @@ -0,0 +1,70 @@ +// Definitions for Fe-Pi Audio +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target-path = "/"; + __overlay__ { + sgtl5000_mclk: sgtl5000_mclk { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <12288000>; + clock-output-names = "sgtl5000-mclk"; + }; + }; + }; + + fragment@1 { + target = <&soc>; + __overlay__ { + reg_1v8: reg_1v8@0 { + compatible = "regulator-fixed"; + regulator-name = "1V8"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; + }; + }; + }; + + fragment@2 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + sgtl5000@a { + #sound-dai-cells = <0>; + compatible = "fsl,sgtl5000"; + reg = <0x0a>; + clocks = <&sgtl5000_mclk>; + micbias-resistor-k-ohms = <2>; + micbias-voltage-m-volts = <3000>; + VDDA-supply = <&vdd_3v3_reg>; + VDDIO-supply = <&vdd_3v3_reg>; + VDDD-supply = <®_1v8>; + status = "okay"; + }; + }; + }; + + fragment@3 { + target = <&i2s_clk_consumer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@4 { + target = <&sound>; + __overlay__ { + compatible = "fe-pi,fe-pi-audio"; + i2s-controller = <&i2s_clk_consumer>; + status = "okay"; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/fsm-demo-overlay.dts b/arch/arm/boot/dts/overlays/fsm-demo-overlay.dts new file mode 100644 index 00000000000000..e9944f5cd25815 --- /dev/null +++ b/arch/arm/boot/dts/overlays/fsm-demo-overlay.dts @@ -0,0 +1,104 @@ +// Demo overlay for the gpio-fsm driver +/dts-v1/; +/plugin/; + +#include <dt-bindings/gpio/gpio-fsm.h> + +#define BUTTON1 GF_IP(0) +#define BUTTON2 GF_SW(0) +#define RED GF_OP(0) // GPIO7 +#define AMBER GF_OP(1) // GPIO8 +#define GREEN GF_OP(2) // GPIO25 + +/{ + compatible = "brcm,bcm2835"; + + fragment@0 { + target-path = "/"; + __overlay__ { + fsm_demo: fsm-demo { + compatible = "rpi,gpio-fsm"; + + debug = <0>; + gpio-controller; + #gpio-cells = <2>; + num-swgpios = <1>; + gpio-line-names = "button2"; + input-gpios = <&gpio 6 1>; // BUTTON1 (active-low) + output-gpios = <&gpio 7 0>, // RED + <&gpio 8 0>, // AMBER + <&gpio 25 0>; // GREEN + shutdown-timeout-ms = <2000>; + + start { + start_state; + set = <RED 1>, <AMBER 0>, <GREEN 0>; + start2 = <GF_DELAY 250>; + }; + + start2 { + set = <RED 0>, <AMBER 1>; + go = <GF_DELAY 250>; + }; + + go { + set = <RED 0>, <AMBER 0>, <GREEN 1>; + ready_wait = <BUTTON1 0>; + shutdown1 = <GF_SHUTDOWN 0>; + }; + + ready_wait { + // Clear the soft GPIO + set = <BUTTON2 0>; + ready = <GF_DELAY 1000>; + shutdown1 = <GF_SHUTDOWN 0>; + }; + + ready { + stopping = <BUTTON1 1>, <BUTTON2 1>; + shutdown1 = <GF_SHUTDOWN 0>; + }; + + stopping { + set = <GREEN 0>, <AMBER 1>; + stopped = <GF_DELAY 1000>; + }; + + stopped { + set = <AMBER 0>, <RED 1>; + get_set = <GF_DELAY 3000>; + shutdown1 = <GF_SHUTDOWN 0>; + }; + + get_set { + set = <AMBER 1>; + go = <GF_DELAY 1000>; + }; + + shutdown1 { + set = <RED 0>, <AMBER 0>, <GREEN 1>; + shutdown2 = <GF_SHUTDOWN 250>; + }; + + shutdown2 { + set = <AMBER 1>, <GREEN 0>; + shutdown3 = <GF_SHUTDOWN 250>; + }; + + shutdown3 { + set = <RED 1>, <AMBER 0>; + shutdown4 = <GF_SHUTDOWN 250>; + }; + + shutdown4 { + shutdown_state; + set = <RED 0>, <AMBER 0>, <GREEN 0>; + }; + }; + }; + }; + + __overrides__ { + fsm_debug = <&fsm_demo>,"debug:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/gc9a01-overlay.dts b/arch/arm/boot/dts/overlays/gc9a01-overlay.dts new file mode 100644 index 00000000000000..3d31030c5564e8 --- /dev/null +++ b/arch/arm/boot/dts/overlays/gc9a01-overlay.dts @@ -0,0 +1,151 @@ +/* + Device Tree overlay for Galaxycore GC9A01A single chip driver + for use on SPI TFT LCD, 240x240 65K RGB + Based on Galaxycore's GC9A01A datasheet Rev.1.0 (2019/07/02) + Copyright (C) 2022, Julianno F. C. Silva (@juliannojungle) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <https://www.gnu.org/licenses/agpl-3.0.html>. + + Init sequence partially based on Waveshare team's Arduino LCD_Driver V1.0 (2020/12/09). + + Permission is hereby granted, free of UBYTEge, to any person obtaining a copy + of this software and associated documnetation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@1 { + target = <&gpio>; + __overlay__ { + gc9a01_pins: gc9a01_pins { + brcm,pins = <25 27>; + brcm,function = <1 1>; /* out */ + brcm,pull = <0 0>; /* none */ + }; + }; + }; + + fragment@2 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + gc9a01: gc9a01@0 { + compatible = "ilitek,ili9340"; + reg = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&gc9a01_pins>; + reset-gpios = <&gpio 27 1>; + dc-gpios = <&gpio 25 0>; + led-gpios = <&gpio 18 0>; + spi-max-frequency = <40000000>; + buswidth = <8>; + width = <240>; + height = <240>; + rotate = <0>; + fps = <50>; + bgr; + debug = <0>; + init = < + 0x01000011 /* Sleep mode OFF */ + 0x02000078 /* Delay 120ms */ + 0x010000EF /* Inter register enable 2 */ + 0x010000EB 0x14 + /* BEGIN set inter_command HIGH */ + 0x010000FE /* Inter register enable 1 */ + 0x010000EF /* Inter register enable 2 */ + /* END set inter_command HIGH */ + 0x010000EB 0x14 + 0x01000084 0x40 + 0x01000085 0xFF + 0x01000086 0xFF + 0x01000087 0xFF + 0x01000088 0x0A + 0x01000089 0x21 + 0x0100008A 0x00 + 0x0100008B 0x80 + 0x0100008C 0x01 + 0x0100008D 0x01 + 0x0100008E 0xFF + 0x0100008F 0xFF + 0x010000B6 0x00 0x00 /* Display function control */ + 0x01000036 0x08 /* Memory access control */ + 0x0100003A 0x05 /* Pixel format */ + 0x01000090 0x08 0x08 0x08 0x08 + 0x010000BD 0x06 + 0x010000BC 0x00 + 0x010000FF 0x60 0x01 0x04 + 0x010000C3 0x13 /* Voltage regulator 1a */ + 0x010000C4 0x13 /* Voltage regulator 1b */ + 0x010000C9 0x22 /* Voltage regulator 2a */ + 0x010000BE 0x11 + 0x010000E1 0x10 0x0E + 0x010000DF 0x21 0x0c 0x02 + 0x010000F0 0x45 0x09 0x08 0x08 0x26 0x2A /* Set gamma1 */ + 0x010000F1 0x43 0x70 0x72 0x36 0x37 0x6F /* Set gamma2 */ + 0x010000F2 0x45 0x09 0x08 0x08 0x26 0x2A /* Set gamma3 */ + 0x010000F3 0x43 0x70 0x72 0x36 0x37 0x6F /* Set gamma4 */ + 0x010000ED 0x1B 0x0B + 0x010000AE 0x77 + 0x010000CD 0x63 + 0x01000070 0x07 0x07 0x04 0x0E 0x0F 0x09 0x07 0x08 0x03 + 0x010000E8 0x34 /* Frame rate */ + 0x01000062 0x18 0x0D 0x71 0xED 0x70 0x70 0x18 0x0F 0x71 0xEF 0x70 0x70 + 0x01000063 0x18 0x11 0x71 0xF1 0x70 0x70 0x18 0x13 0x71 0xF3 0x70 0x70 + 0x01000064 0x28 0x29 0xF1 0x01 0xF1 0x00 0x07 + 0x01000066 0x3C 0x00 0xCD 0x67 0x45 0x45 0x10 0x00 0x00 0x00 + 0x01000067 0x00 0x3C 0x00 0x00 0x00 0x01 0x54 0x10 0x32 0x98 + 0x01000074 0x10 0x85 0x80 0x00 0x00 0x4E 0x00 + 0x01000098 0x3e 0x07 + 0x01000035 /* Tearing effect ON */ + 0x01000021 /* Display inversion ON */ + 0x01000011 /* Sleep mode OFF */ + 0x0200000C /* Delay 12ms */ + 0x01000029 /* Display ON */ + 0x02000014 /* Delay 20ms */ + >; + }; + }; + }; + + __overrides__ { + speed = <&gc9a01>,"spi-max-frequency:0"; + rotate = <&gc9a01>,"rotate:0"; + width = <&gc9a01>,"width:0"; + height = <&gc9a01>,"height:0"; + fps = <&gc9a01>,"fps:0"; + debug = <&gc9a01>,"debug:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/ghost-amp-overlay.dts b/arch/arm/boot/dts/overlays/ghost-amp-overlay.dts new file mode 100644 index 00000000000000..d2f1e9a888e0b9 --- /dev/null +++ b/arch/arm/boot/dts/overlays/ghost-amp-overlay.dts @@ -0,0 +1,145 @@ +// Overlay for the PCM5122-based Ghost amplifier using gpio-fsm +/dts-v1/; +/plugin/; + +#include <dt-bindings/gpio/gpio-fsm.h> + +#define ENABLE GF_SW(0) +#define FAULT GF_IP(0) // GPIO5 +#define RELAY1 GF_OP(0) // GPIO22 +#define RELAY2 GF_OP(1) // GPIO23 +#define RELAYSSR GF_OP(2) // GPIO24 + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_producer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + pcm5122@4c { + #sound-dai-cells = <0>; + compatible = "ti,pcm5122"; + reg = <0x4c>; + AVDD-supply = <&vdd_3v3_reg>; + DVDD-supply = <&vdd_3v3_reg>; + CPVDD-supply = <&vdd_3v3_reg>; + status = "okay"; + }; + }; + }; + + fragment@2 { + target = <&sound>; + iqaudio_dac: __overlay__ { + compatible = "iqaudio,iqaudio-dac"; + i2s-controller = <&i2s_clk_producer>; + mute-gpios = <& 0 0>; + iqaudio-dac,auto-mute-amp; + status = "okay"; + }; + }; + + fragment@3 { + target-path = "/"; + __overlay__ { + amp: ghost-amp { + compatible = "rpi,gpio-fsm"; + pinctrl-names = "default"; + pinctrl-0 = <&ghost_amp_pins>; + + debug = <0>; + gpio-controller; + #gpio-cells = <2>; + num-swgpios = <1>; + gpio-line-names = "enable"; + input-gpios = <&gpio 5 1>; // FAULT (active low) + output-gpios = <&gpio 22 0>, // RELAY1 + <&gpio 23 0>, // RELAY2 + <&gpio 24 0>; // RELAYSSR + shutdown-timeout-ms = <1000>; + + amp_off { + start_state; + shutdown_state; + + set = <RELAYSSR 0>, + <RELAY2 0>, + <RELAY1 0>; + amp_on_1 = <ENABLE 1>; + fault = <FAULT 1>; + }; + + amp_on_1 { + set = <RELAY1 1>; + amp_on_2 = <GF_DELAY 1000>; + amp_off = <GF_SHUTDOWN 0>; + fault = <FAULT 1>; + }; + + amp_on_2 { + set = <RELAY2 1>; + amp_on_wait = <ENABLE 0>; + amp_on = <GF_DELAY 1>; + fault = <FAULT 1>; + }; + + amp_on { + set = <RELAYSSR 1>; + amp_on_wait = <ENABLE 0>; + fault = <FAULT 1>; + }; + + amp_on_wait { + set = <RELAYSSR 0>; + amp_off_1 = <GF_DELAY (30*60*1000)>, + <GF_SHUTDOWN 0>; + amp_on = <ENABLE 1>; + fault = <FAULT 1>; + }; + + amp_off_1 { + set = <RELAY2 0>; + amp_on = <ENABLE 1>; + amp_off = <GF_DELAY 100>; + fault = <FAULT 1>; + }; + + // Keep this a distinct state to prevent + // changes and for the diagnostic output + fault { + set = <RELAYSSR 0>, + <RELAY2 0>, + <RELAY1 0>; + amp_off = <FAULT 0>; + shutdown_state; + }; + }; + }; + }; + + fragment@4 { + target = <&gpio>; + __overlay__ { + ghost_amp_pins: ghost_amp_pins { + brcm,pins = <5 22 23 24>; + brcm,function = <0 1 1 1>; /* in out out out */ + brcm,pull = <2 0 0 0>; /* up none none none */ + }; + }; + }; + + __overrides__ { + fsm_debug = <&>,"debug:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/goodix-overlay.dts b/arch/arm/boot/dts/overlays/goodix-overlay.dts new file mode 100644 index 00000000000000..8a5035df9daa6a --- /dev/null +++ b/arch/arm/boot/dts/overlays/goodix-overlay.dts @@ -0,0 +1,49 @@ +// Device tree overlay for I2C connected Goodix gt9271 multiple touch controller +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&gpio>; + __overlay__ { + goodix_pins: goodix_pins { + brcm,pins = <4 17>; // interrupt and reset + brcm,function = <0 0>; // in + brcm,pull = <2 2>; // pull-up + }; + }; + }; + + i2c_frag: fragment@1 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + gt9271: gt9271@14 { + compatible = "goodix,gt9271"; + reg = <0x14>; + pinctrl-names = "default"; + pinctrl-0 = <&goodix_pins>; + interrupt-parent = <&gpio>; + interrupts = <4 2>; // high-to-low edge triggered + irq-gpios = <&gpio 4 0>; // Pin7 on GPIO header + reset-gpios = <&gpio 17 0>; // Pin11 on GPIO header + }; + }; + }; + + __overrides__ { + addr = <>9271>,"reg:0"; + interrupt = <&goodix_pins>,"brcm,pins:0", + <>9271>,"interrupts:0", + <>9271>,"irq-gpios:4"; + reset = <&goodix_pins>,"brcm,pins:4", + <>9271>,"reset-gpios:4"; + i2c-path = <&i2c_frag>, "target?=0", + <&i2c_frag>, "target-path"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/googlevoicehat-soundcard-overlay.dts b/arch/arm/boot/dts/overlays/googlevoicehat-soundcard-overlay.dts new file mode 100644 index 00000000000000..1063f189856288 --- /dev/null +++ b/arch/arm/boot/dts/overlays/googlevoicehat-soundcard-overlay.dts @@ -0,0 +1,49 @@ +// Definitions for Google voiceHAT v1 soundcard overlay +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_producer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&gpio>; + __overlay__ { + googlevoicehat_pins: googlevoicehat_pins { + brcm,pins = <16>; + brcm,function = <1>; /* out */ + brcm,pull = <0>; /* up */ + }; + }; + }; + + + fragment@2 { + target-path = "/"; + __overlay__ { + voicehat-codec { + #sound-dai-cells = <0>; + compatible = "google,voicehat"; + pinctrl-names = "default"; + pinctrl-0 = <&googlevoicehat_pins>; + sdmode-gpios= <&gpio 16 0>; + status = "okay"; + }; + }; + }; + + fragment@3 { + target = <&sound>; + __overlay__ { + compatible = "googlevoicehat,googlevoicehat-soundcard"; + i2s-controller = <&i2s_clk_producer>; + status = "okay"; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/gpio-charger-overlay.dts b/arch/arm/boot/dts/overlays/gpio-charger-overlay.dts new file mode 100644 index 00000000000000..2868aa06dd6d3d --- /dev/null +++ b/arch/arm/boot/dts/overlays/gpio-charger-overlay.dts @@ -0,0 +1,42 @@ +// Definitions for gpio-charger module +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + // Configure the gpio pin controller + target = <&gpio>; + __overlay__ { + pin_state: charger_pins@0 { + brcm,pins = <4>; // gpio number + brcm,function = <0>; // 0 = input, 1 = output + brcm,pull = <1>; // 0 = none, 1 = pull down, 2 = pull up + }; + }; + }; + fragment@1 { + target-path = "/"; + __overlay__ { + charger: charger@0 { + compatible = "gpio-charger"; + pinctrl-0 = <&pin_state>; + status = "okay"; + gpios = <&gpio 4 0>; + charger-type = "mains"; + }; + }; + }; + + __overrides__ { + gpio = <&charger>,"reg:0", + <&charger>,"gpios:4", + <&pin_state>,"reg:0", + <&pin_state>,"brcm,pins:0"; + type = <&charger>,"charger-type"; + gpio_pull = <&pin_state>,"brcm,pull:0"; + active_low = <&charger>,"gpios:8"; + }; + +}; diff --git a/arch/arm/boot/dts/overlays/gpio-fan-overlay.dts b/arch/arm/boot/dts/overlays/gpio-fan-overlay.dts new file mode 100644 index 00000000000000..17b77bb2793175 --- /dev/null +++ b/arch/arm/boot/dts/overlays/gpio-fan-overlay.dts @@ -0,0 +1,89 @@ +/* + * Overlay for the Raspberry Pi GPIO Fan @ BCM GPIO12. + * References: + * - https://www.raspberrypi.org/forums/viewtopic.php?f=107&p=1367135#p1365084 + * + * Optional parameters: + * - "gpiopin" - BCM number of the pin driving the fan, default 12 (GPIO12); + * - "temp" - CPU temperature at which fan is started in millicelsius, default 55000; + * + * Requires: + * - kernel configurations: CONFIG_SENSORS_GPIO_FAN=m; + * - kernel rebuild; + * - N-MOSFET connected to gpiopin, 2N7002-[https://en.wikipedia.org/wiki/2N7000]; + * - DC Fan connected to N-MOSFET Drain terminal, a 12V fan is working fine and quite silently; + * [https://www.tme.eu/en/details/ee40101s1-999-a/dc12v-fans/sunon/ee40101s1-1000u-999/] + * + * ┌─────────────────────┐ + * │Fan negative terminal│ + * └┬────────────────────┘ + * │D + * G │──┘ + * [GPIO12]──────┤ │<─┐ 2N7002 + * │──┤ + * │S + * ─┴─ + * GND + * + * Build: + * - `sudo dtc -W no-unit_address_vs_reg -@ -I dts -O dtb -o /boot/overlays/gpio-fan.dtbo gpio-fan-overlay.dts` + * Activate: + * - sudo nano /boot/config.txt add "dtoverlay=gpio-fan" or "dtoverlay=gpio-fan,gpiopin=12,temp=45000" + * or + * - sudo sh -c 'printf "\n# Enable PI GPIO-Fan Default\ndtoverlay=gpio-fan\n" >> /boot/config.txt' + * - sudo sh -c 'printf "\n# Enable PI GPIO-Fan Custom\ndtoverlay=gpio-fan,gpiopin=12,temp=45000\n" >> /boot/config.txt' + * + */ +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target-path = "/"; + __overlay__ { + fan0: gpio-fan@0 { + compatible = "gpio-fan"; + gpios = <&gpio 12 0>; + gpio-fan,speed-map = <0 0>, + <5000 1>; + #cooling-cells = <2>; + }; + }; + }; + + fragment@1 { + target = <&cpu_thermal>; + __overlay__ { + polling-delay = <2000>; /* milliseconds */ + }; + }; + + fragment@2 { + target = <&thermal_trips>; + __overlay__ { + cpu_hot: trip-point@0 { + temperature = <55000>; /* (millicelsius) Fan started at 55°C */ + hysteresis = <10000>; /* (millicelsius) Fan stopped at 45°C */ + type = "active"; + }; + }; + }; + + fragment@3 { + target = <&cooling_maps>; + __overlay__ { + map0 { + trip = <&cpu_hot>; + cooling-device = <&fan0 1 1>; + }; + }; + }; + + __overrides__ { + gpiopin = <&fan0>,"gpios:4", <&fan0>,"brcm,pins:0"; + temp = <&cpu_hot>,"temperature:0"; + hyst = <&cpu_hot>,"hysteresis:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/gpio-hog-overlay.dts b/arch/arm/boot/dts/overlays/gpio-hog-overlay.dts new file mode 100644 index 00000000000000..c9e39046fed961 --- /dev/null +++ b/arch/arm/boot/dts/overlays/gpio-hog-overlay.dts @@ -0,0 +1,27 @@ +// Configure a "hog" on the specified GPIO +/dts-v1/; +/plugin/; + +#include <dt-bindings/gpio/gpio.h> + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&gpio>; + __overlay__ { + hog: hog@1a { + gpio-hog; + gpios = <26 GPIO_ACTIVE_HIGH>; + output-high; + }; + }; + }; + + __overrides__ { + gpio = <&hog>,"reg:0", + <&hog>,"gpios:0"; + active_low = <&hog>,"output-high!", + <&hog>,"output-low?"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/gpio-ir-overlay.dts b/arch/arm/boot/dts/overlays/gpio-ir-overlay.dts new file mode 100644 index 00000000000000..162b6ce07dc91f --- /dev/null +++ b/arch/arm/boot/dts/overlays/gpio-ir-overlay.dts @@ -0,0 +1,49 @@ +// Definitions for ir-gpio module +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target-path = "/"; + __overlay__ { + gpio_ir: ir-receiver@12 { + compatible = "gpio-ir-receiver"; + pinctrl-names = "default"; + pinctrl-0 = <&gpio_ir_pins>; + + // pin number, high or low + gpios = <&gpio 18 1>; + + // parameter for keymap name + linux,rc-map-name = "rc-rc6-mce"; + + status = "okay"; + }; + }; + }; + + fragment@1 { + target = <&gpio>; + __overlay__ { + gpio_ir_pins: gpio_ir_pins@12 { + brcm,pins = <18>; // pin 18 + brcm,function = <0>; // in + brcm,pull = <2>; // up + }; + }; + }; + + __overrides__ { + // parameters + gpio_pin = <&gpio_ir>,"gpios:4", // pin number + <&gpio_ir>,"reg:0", + <&gpio_ir_pins>,"brcm,pins:0", + <&gpio_ir_pins>,"reg:0"; + gpio_pull = <&gpio_ir_pins>,"brcm,pull:0"; // pull-up/down state + invert = <&gpio_ir>,"gpios:8"; // 0 = active high input + + rc-map-name = <&gpio_ir>,"linux,rc-map-name"; // default rc map + }; +}; diff --git a/arch/arm/boot/dts/overlays/gpio-ir-tx-overlay.dts b/arch/arm/boot/dts/overlays/gpio-ir-tx-overlay.dts new file mode 100644 index 00000000000000..3625431b756048 --- /dev/null +++ b/arch/arm/boot/dts/overlays/gpio-ir-tx-overlay.dts @@ -0,0 +1,36 @@ +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&gpio>; + __overlay__ { + gpio_ir_tx_pins: gpio_ir_tx_pins@12 { + brcm,pins = <18>; + brcm,function = <1>; // out + }; + }; + }; + + fragment@1 { + target-path = "/"; + __overlay__ { + gpio_ir_tx: gpio-ir-transmitter@12 { + compatible = "gpio-ir-tx"; + pinctrl-names = "default"; + pinctrl-0 = <&gpio_ir_tx_pins>; + gpios = <&gpio 18 0>; + }; + }; + }; + + __overrides__ { + gpio_pin = <&gpio_ir_tx>, "gpios:4", // pin number + <&gpio_ir_tx>, "reg:0", + <&gpio_ir_tx_pins>, "brcm,pins:0", + <&gpio_ir_tx_pins>, "reg:0"; + invert = <&gpio_ir_tx>, "gpios:8"; // 1 = active low + }; +}; diff --git a/arch/arm/boot/dts/overlays/gpio-key-overlay.dts b/arch/arm/boot/dts/overlays/gpio-key-overlay.dts new file mode 100644 index 00000000000000..2e7253d1d0abf1 --- /dev/null +++ b/arch/arm/boot/dts/overlays/gpio-key-overlay.dts @@ -0,0 +1,48 @@ +// Definitions for gpio-key module +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + // Configure the gpio pin controller + target = <&gpio>; + __overlay__ { + pin_state: button_pins@0 { + brcm,pins = <3>; // gpio number + brcm,function = <0>; // 0 = input, 1 = output + brcm,pull = <2>; // 0 = none, 1 = pull down, 2 = pull up + }; + }; + }; + fragment@1 { + target-path = "/"; + __overlay__ { + button: button@0 { + compatible = "gpio-keys"; + pinctrl-names = "default"; + pinctrl-0 = <&pin_state>; + status = "okay"; + + key: key { + linux,code = <116>; + gpios = <&gpio 3 1>; + label = "KEY_POWER"; + }; + }; + }; + }; + + __overrides__ { + gpio = <&key>,"gpios:4", + <&button>,"reg:0", + <&pin_state>,"brcm,pins:0", + <&pin_state>,"reg:0"; + label = <&key>,"label"; + keycode = <&key>,"linux,code:0"; + gpio_pull = <&pin_state>,"brcm,pull:0"; + active_low = <&key>,"gpios:8"; + }; + +}; diff --git a/arch/arm/boot/dts/overlays/gpio-led-overlay.dts b/arch/arm/boot/dts/overlays/gpio-led-overlay.dts new file mode 100755 index 00000000000000..d8e9d53f1b6191 --- /dev/null +++ b/arch/arm/boot/dts/overlays/gpio-led-overlay.dts @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * gpio-led - generic connection of kernel's LED framework to the RPI's GPIO. + * Copyright (C) 2021 House Gordon Software Company Ltd. <assafgordon@gmail.com> + * + * Based on information from: + * https://mjoldfield.com/atelier/2017/03/rpi-devicetree.html + * https://www.raspberrypi.org/documentation/configuration/device-tree.md + * https://www.kernel.org/doc/html/latest/leds/index.html + * + * compile with: + * dtc -@ -Hepapr -I dts -O dtb -o gpio-led.dtbo gpio-led-overlay.dts + * + * There will be some warnings (can be ignored): + * Warning (label_is_string): /__overrides__:label: property is not a string + * Warning (unit_address_vs_reg): /fragment@0/__overlay__/led_pins@0: + * node has a unit name, but no reg property + * Warning (unit_address_vs_reg): /fragment@1/__overlay__/leds@0: + * node has a unit name, but no reg property + * Warning (gpios_property): /__overrides__: Missing property + * '#gpio-cells' in node /fragment@1/__overlay__/leds@0/led + * or bad phandle (referred from gpio[0]) + * + * Typical electrical connection is: + * RPI-GPIO.19 -> LED -> 300ohm resister -> RPI-GND + * The GPIO pin number can be changed with the 'gpio=' parameter. + * + * Test from user-space with: + * # if nothing is shown, the overlay file isn't found in /boot/overlays + * dtoverlay -a | grep gpio-led + * + * # Load the overlay + * dtoverlay gpio-led label=moo gpio=19 + * + * # if nothing is shown, the overlay wasn't loaded successfully + * dtoverlay -l | grep gpio-led + * + * echo 1 > /sys/class/leds/moo/brightness + * echo 0 > /sys/class/leds/moo/brightness + * echo cpu > /sys/class/leds/moo/trigger + * echo heartbeat > /sys/class/leds/moo/trigger + * + * # unload the overlay + * dtoverlay -r gpio-led + * + * To load in /boot/config.txt add lines such as: + * dtoverlay=gpio-led,gpio=19,label=heart,trigger=heartbeat + * dtoverlay=gpio-led,gpio=26,label=brain,trigger=cpu + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + // Configure the gpio pin controller + target = <&gpio>; + __overlay__ { + led_pin: led_pins@19 { + brcm,pins = <19>; // gpio number + brcm,function = <1>; // 0 = input, 1 = output + brcm,pull = <0>; // 0 = none, 1 = pull down, 2 = pull up + }; + }; + }; + fragment@1 { + target-path = "/"; + __overlay__ { + leds: leds@0 { + compatible = "gpio-leds"; + pinctrl-names = "default"; + pinctrl-0 = <&led_pin>; + status = "okay"; + + led: led { + label = "myled1"; + gpios = <&gpio 19 0>; + linux,default-trigger = "none"; + }; + }; + }; + }; + + __overrides__ { + gpio = <&led>,"gpios:4", + <&leds>,"reg:0", + <&led_pin>,"brcm,pins:0", + <&led_pin>,"reg:0"; + label = <&led>,"label"; + active_low = <&led>,"gpios:8"; + trigger = <&led>,"linux,default-trigger"; + }; + +}; + diff --git a/arch/arm/boot/dts/overlays/gpio-no-bank0-irq-overlay.dts b/arch/arm/boot/dts/overlays/gpio-no-bank0-irq-overlay.dts new file mode 100755 index 00000000000000..96cbe80820b72a --- /dev/null +++ b/arch/arm/boot/dts/overlays/gpio-no-bank0-irq-overlay.dts @@ -0,0 +1,14 @@ +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + // Configure the gpio pin controller + target = <&gpio>; + __overlay__ { + interrupts = <255 255>, <2 18>; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/gpio-no-irq-overlay.dts b/arch/arm/boot/dts/overlays/gpio-no-irq-overlay.dts new file mode 100644 index 00000000000000..55f9bff3a8f622 --- /dev/null +++ b/arch/arm/boot/dts/overlays/gpio-no-irq-overlay.dts @@ -0,0 +1,14 @@ +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + // Configure the gpio pin controller + target = <&gpio>; + __overlay__ { + interrupts; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/gpio-poweroff-overlay.dts b/arch/arm/boot/dts/overlays/gpio-poweroff-overlay.dts new file mode 100644 index 00000000000000..8153f83f04270c --- /dev/null +++ b/arch/arm/boot/dts/overlays/gpio-poweroff-overlay.dts @@ -0,0 +1,39 @@ +// Definitions for gpio-poweroff module +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target-path = "/"; + __overlay__ { + power_ctrl: power_ctrl { + compatible = "gpio-poweroff"; + gpios = <&gpio 26 0>; + force; + }; + }; + }; + + fragment@1 { + target = <&gpio>; + __overlay__ { + power_ctrl_pins: power_ctrl_pins { + brcm,pins = <26>; + brcm,function = <1>; // out + }; + }; + }; + + __overrides__ { + gpiopin = <&power_ctrl>,"gpios:4", + <&power_ctrl_pins>,"brcm,pins:0"; + active_low = <&power_ctrl>,"gpios:8"; + input = <&power_ctrl>,"input?"; + export = <&power_ctrl>,"export?"; + timeout_ms = <&power_ctrl>,"timeout-ms:0"; + active_delay_ms = <&power_ctrl>,"active-delay-ms:0"; + inactive_delay_ms = <&power_ctrl>,"inactive-delay-ms:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/gpio-shutdown-overlay.dts b/arch/arm/boot/dts/overlays/gpio-shutdown-overlay.dts new file mode 100644 index 00000000000000..da148064aedd15 --- /dev/null +++ b/arch/arm/boot/dts/overlays/gpio-shutdown-overlay.dts @@ -0,0 +1,86 @@ +// Definitions for gpio-poweroff module +/dts-v1/; +/plugin/; + +// This overlay sets up an input device that generates KEY_POWER events +// when a given GPIO pin changes. It defaults to using GPIO3, which can +// also be used to wake up (start) the Rpi again after shutdown. +// Raspberry Pi 1 Model B rev 1 can be wake up only by GPIO1 pin, so for +// these boards change default GPIO pin to 1 via gpio_pin parameter. Since +// wakeup is active-low, this defaults to active-low with a pullup +// enabled, but all of this can be changed using overlay parameters (but +// note that GPIO3 has an external pullup on at least some boards). + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + // Configure the gpio pin controller + target = <&gpio>; + __overlay__ { + // Define a pinctrl state, that sets up the gpio + // as an input with a pullup enabled. This does + // not take effect by itself, only when referenced + // by a "pinctrl client", as is done below. See: + // https://www.kernel.org/doc/Documentation/devicetree/bindings/pinctrl/pinctrl-bindings.txt + // https://www.kernel.org/doc/Documentation/devicetree/bindings/pinctrl/brcm,bcm2835-gpio.txt + pin_state: shutdown_button_pins@3 { + brcm,pins = <3>; // gpio number + brcm,function = <0>; // 0 = input, 1 = output + brcm,pull = <2>; // 0 = none, 1 = pull down, 2 = pull up + }; + }; + }; + fragment@1 { + // Add a new device to the /soc devicetree node + target-path = "/soc"; + __overlay__ { + shutdown_button: shutdown_button@3 { + // Let the gpio-keys driver handle this device. See: + // https://www.kernel.org/doc/Documentation/devicetree/bindings/input/gpio-keys.txt + compatible = "gpio-keys"; + + // Declare a single pinctrl state (referencing the one declared above) and name it + // default, so it is activated automatically. + pinctrl-names = "default"; + pinctrl-0 = <&pin_state>; + + // Enable this device + status = "okay"; + + // Define a single key, called "shutdown" that monitors the gpio and sends KEY_POWER + // (keycode 116, see + // https://github.com/torvalds/linux/blob/v4.12/include/uapi/linux/input-event-codes.h#L190) + button: shutdown { + label = "shutdown"; + linux,code = <116>; // KEY_POWER + gpios = <&gpio 3 1>; + debounce-interval = <100>; // ms + }; + }; + }; + }; + + // This defines parameters that can be specified when loading + // the overlay. Each foo = line specifies one parameter, named + // foo. The rest of the specification gives properties where the + // parameter value is inserted into (changing the values above + // or adding new ones). + __overrides__ { + // Allow overriding the GPIO number. + gpio_pin = <&button>,"gpios:4", + <&shutdown_button>,"reg:0", + <&pin_state>,"reg:0", + <&pin_state>,"brcm,pins:0"; + + // Allow changing the internal pullup/down state. 0 = none, 1 = pulldown, 2 = pullup + // Note that GPIO3 and GPIO2 are the I2c pins and have an external pullup (at least + // on some boards). Same applies for GPIO1 on Raspberry Pi 1 Model B rev 1. + gpio_pull = <&pin_state>,"brcm,pull:0"; + + // Allow setting the active_low flag. 0 = active high, 1 = active low + active_low = <&button>,"gpios:8"; + debounce = <&button>,"debounce-interval:0"; + }; + +}; diff --git a/arch/arm/boot/dts/overlays/hat_map.dts b/arch/arm/boot/dts/overlays/hat_map.dts new file mode 100644 index 00000000000000..0b5d902e85b8f4 --- /dev/null +++ b/arch/arm/boot/dts/overlays/hat_map.dts @@ -0,0 +1,124 @@ +/dts-v1/; + +/ { + hailo-8 { + product = "Raspberry Pi AI Hat, Model Hailo-8"; + vendor = "Hailo Technologies"; + overlay = "none,pciex1,pciex1_gen=3"; + }; + + hailo-8l { + product = "Raspberry Pi AI Hat, Model Hailo-8L"; + vendor = "Hailo Technologies"; + overlay = "none,pciex1,pciex1_gen=3"; + }; + + hifiberry-amp100-1 { + uuid = [ 5eb863b8 12f9 41ad 978f 4cee1b3eca62 ]; + overlay = "hifiberry-amp100"; + }; + + hifiberry-amp100-2 { + uuid = [ b1a57dbe 8b52 447f 939e 1baf72157d79 ]; + overlay = "hifiberry-amp100"; + }; + + hifiberry-amp4pro { + uuid = [ 3619722a c92d 4092 95bd 493a2903e933 ]; + overlay = "hifiberry-amp4pro"; + }; + + hifiberry-amp4 { + uuid = [ fcb6ec42 a182 419d a314 7eeae416f608 ]; + overlay = "hifiberry-dacplus-std"; + }; + + hifiberry-dac2proadc { + uuid = [ 30660215 dbb2 4c57 953f 099370b63e2e ]; + overlay = "hifiberry-dacplusadcpro"; + }; + + hifiberry-dac2hd { + uuid = [ 482ad277 5586 480c 88e7 85ae89c4e501 ]; + overlay = "hifiberry-dacplushd"; + }; + + hifiberry-dac2pro { + uuid = [ ebf9cfc4 6d77 4880 89fd 353690467dfc ]; + overlay = "hifiberry-dacplus-pro"; + }; + + hifiberry-dac8x { + uuid = [ f65985f9 5354 4457 ae3b 3da39ba2cf6d ]; + overlay = "hifiberry-dac8x"; + }; + + hifiberry-dacplus-amp2-1 { + uuid = [ 81cac43d 27c6 4a1e a0b2 c70b4e608ab6 ]; + overlay = "hifiberry-dacplus-std"; + }; + + hifiberry-dacplus-amp2-2 { + uuid = [ ef586afc 2efa 47a0 be2e 95a7d952fe98 ]; + overlay = "hifiberry-dacplus-std"; + }; + + hifiberry-digiplus-pro { + uuid = [ 2154f80b 0f92 45e4 96db c1643ec2b46b ]; + overlay = "hifiberry-digi-pro"; + }; + + hifiberry-dacplusadcpro { + uuid = [ 36e3d3da 1ed9 468b aea3 cd165f6820f0 ]; + overlay = "hifiberry-dacplusadcpro"; + }; + + hifiberry-digi2pro { + uuid = [ 5af941bb 4dcf 4eac 82a8 e36e84fcabef ]; + overlay = "hifiberry-digi-pro"; + }; + + hifiberry-digi2standard { + uuid = [ 7c980a0e 9d15 40af 9f40 bddfbd3aee8c ]; + overlay = "hifiberry-digi"; + }; + + hifiberry-dsp2x4 { + uuid = [ 8f287583 429d 4206 a751 862264bbda63 ]; + overlay = "hifiberry-dacplus-dsp"; + }; + + iqaudio-pi-codecplus { + uuid = [ dc1c9594 c1ab 4c6c acda a88dc59a3c5b ]; + overlay = "iqaudio-codec"; + }; + + iqaudio-pi-codeczero { + uuid = [ e15c739c 877d 4e29 ab36 4dc73c21127c ]; + overlay = "iqaudio-codec"; + }; + + pisound { + uuid = [ a7ee5d28 da03 41f5 bbd7 20438a4bec5d ]; + overlay = "pisound"; + }; + + recalbox-rgbdual { + uuid = [ 1c955808 681f 4bbc a2ef b7ea47cd388e ]; + overlay = "recalboxrgbdual"; + }; + + sensehat-v1 { + product = "Sense HAT"; + vendor = "Raspberry Pi"; + pver = < 0x0001 >; + overlay = "rpi-sense"; + }; + + sensehat-v2 { + product = "Sense HAT"; + vendor = "Raspberry Pi"; + pver = < 0x0002 >; + overlay = "rpi-sense-v2"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/hd44780-i2c-lcd-overlay.dts b/arch/arm/boot/dts/overlays/hd44780-i2c-lcd-overlay.dts new file mode 100644 index 00000000000000..d5bfd762fe2eb5 --- /dev/null +++ b/arch/arm/boot/dts/overlays/hd44780-i2c-lcd-overlay.dts @@ -0,0 +1,59 @@ +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + i2c_frag: fragment@0 { + target = <&i2c_arm>; + __overlay__ { + status = "okay"; + + pcf857x: pcf857x@27 { + compatible = "nxp,pcf8574"; + reg = <0x27>; + gpio-controller; + #gpio-cells = <2>; + status = "okay"; + }; + }; + }; + + fragment@1 { + target-path = "/"; + __overlay__ { + lcd_screen: auxdisplay { + compatible = "hit,hd44780"; + + data-gpios = <&pcf857x 4 0>, + <&pcf857x 5 0>, + <&pcf857x 6 0>, + <&pcf857x 7 0>; + enable-gpios = <&pcf857x 2 0>; + rs-gpios = <&pcf857x 0 0>; + rw-gpios = <&pcf857x 1 0>; + backlight-gpios = <&pcf857x 3 0>; + + display-width-chars = <16>; + display-height-chars = <2>; + }; + }; + }; + + __overrides__ { + pin_d4 = <&lcd_screen>,"data-gpios:4"; + pin_d5 = <&lcd_screen>,"data-gpios:16"; + pin_d6 = <&lcd_screen>,"data-gpios:28"; + pin_d7 = <&lcd_screen>,"data-gpios:40"; + pin_en = <&lcd_screen>,"enable-gpios:4"; + pin_rs = <&lcd_screen>,"rs-gpios:4"; + pin_rw = <&lcd_screen>,"rw-gpios:4"; + pin_bl = <&lcd_screen>,"backlight-gpios:4"; + display_height = <&lcd_screen>,"display-height-chars:0"; + display_width = <&lcd_screen>,"display-width-chars:0"; + addr = <&pcf857x>,"reg:0"; + i2c-path = <&i2c_frag>, "target?=0", + <&i2c_frag>, "target-path"; + }; + +}; diff --git a/arch/arm/boot/dts/overlays/hd44780-lcd-overlay.dts b/arch/arm/boot/dts/overlays/hd44780-lcd-overlay.dts new file mode 100644 index 00000000000000..ee726669ff5112 --- /dev/null +++ b/arch/arm/boot/dts/overlays/hd44780-lcd-overlay.dts @@ -0,0 +1,46 @@ +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target-path = "/"; + __overlay__ { + lcd_screen: auxdisplay { + compatible = "hit,hd44780"; + + data-gpios = <&gpio 6 0>, + <&gpio 13 0>, + <&gpio 19 0>, + <&gpio 26 0>; + enable-gpios = <&gpio 21 0>; + rs-gpios = <&gpio 20 0>; + + display-height-chars = <2>; + display-width-chars = <16>; + }; + + }; + }; + + fragment@1 { + target = <&lcd_screen>; + __dormant__ { + backlight-gpios = <&gpio 12 0>; + }; + }; + + __overrides__ { + pin_d4 = <&lcd_screen>,"data-gpios:4"; + pin_d5 = <&lcd_screen>,"data-gpios:16"; + pin_d6 = <&lcd_screen>,"data-gpios:28"; + pin_d7 = <&lcd_screen>,"data-gpios:40"; + pin_en = <&lcd_screen>,"enable-gpios:4"; + pin_rs = <&lcd_screen>,"rs-gpios:4"; + pin_bl = <0>,"+1", <&lcd_screen>,"backlight-gpios:4"; + display_height = <&lcd_screen>,"display-height-chars:0"; + display_width = <&lcd_screen>,"display-width-chars:0"; + }; + +}; diff --git a/arch/arm/boot/dts/overlays/hdmi-backlight-hwhack-gpio-overlay.dts b/arch/arm/boot/dts/overlays/hdmi-backlight-hwhack-gpio-overlay.dts new file mode 100644 index 00000000000000..50b9a2665c80bc --- /dev/null +++ b/arch/arm/boot/dts/overlays/hdmi-backlight-hwhack-gpio-overlay.dts @@ -0,0 +1,47 @@ +/* + * Devicetree overlay for GPIO based backlight on/off capability. + * + * Use this if you have one of those HDMI displays whose backlight cannot be + * controlled via DPMS over HDMI and plan to do a little soldering to use an + * RPi gpio pin for on/off switching. + * + * See: https://www.waveshare.com/wiki/7inch_HDMI_LCD_(C)#Backlight_Control + * + */ +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@1 { + target = <&gpio>; + __overlay__ { + hdmi_backlight_hwhack_gpio_pins: hdmi_backlight_hwhack_gpio_pins { + brcm,pins = <17>; + brcm,function = <1>; /* out */ + }; + }; + }; + + fragment@2 { + target-path = "/"; + __overlay__ { + hdmi_backlight_hwhack_gpio: hdmi_backlight_hwhack_gpio { + compatible = "gpio-backlight"; + + pinctrl-names = "default"; + pinctrl-0 = <&hdmi_backlight_hwhack_gpio_pins>; + + gpios = <&gpio 17 0>; + default-on; + }; + }; + }; + + __overrides__ { + gpio_pin = <&hdmi_backlight_hwhack_gpio>,"gpios:4", + <&hdmi_backlight_hwhack_gpio_pins>,"brcm,pins:0"; + active_low = <&hdmi_backlight_hwhack_gpio>,"gpios:8"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/hifiberry-adc-overlay.dts b/arch/arm/boot/dts/overlays/hifiberry-adc-overlay.dts new file mode 100644 index 00000000000000..2658f324255614 --- /dev/null +++ b/arch/arm/boot/dts/overlays/hifiberry-adc-overlay.dts @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0 +// Definitions for HiFiBerry ADC, no onboard clocks +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_producer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + hb_adc: pcm186x@4a { + #sound-dai-cells = <0>; + compatible = "ti,pcm1863"; + reg = <0x4a>; + status = "okay"; + }; + }; + }; + + fragment@2 { + target = <&sound>; + hifiberry_adc: __overlay__ { + compatible = "hifiberry,hifiberry-adc"; + audio-codec = <&hb_adc>; + i2s-controller = <&i2s_clk_producer>; + status = "okay"; + }; + }; + + __overrides__ { + leds_off = <&hifiberry_adc>,"hifiberry-adc,leds_off?"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/hifiberry-adc8x-overlay.dts b/arch/arm/boot/dts/overlays/hifiberry-adc8x-overlay.dts new file mode 100644 index 00000000000000..e0432115dc3924 --- /dev/null +++ b/arch/arm/boot/dts/overlays/hifiberry-adc8x-overlay.dts @@ -0,0 +1,50 @@ +// Definitions for HiFiBerry ADC8x +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2712"; + + fragment@0 { + target = <&gpio>; + __overlay__ { + rp1_i2s0_adc8x: rp1_i2s0_adc8x { + function = "i2s0"; + pins = "gpio18", "gpio19", "gpio20", + "gpio22", "gpio24", "gpio26"; + bias-disable; + status = "okay"; + }; + }; + }; + + fragment@1 { + target = <&i2s_clk_producer>; + __overlay__ { + pinctrl-names = "default"; + pinctrl-0 = <&rp1_i2s0_adc8x>; + status = "okay"; + }; + }; + + fragment@2 { + target-path = "/"; + __overlay__ { + dummy-codec { + #sound-dai-cells = <0>; + compatible = "snd-soc-dummy"; + status = "okay"; + }; + }; + }; + + fragment@3 { + target = <&sound>; + __overlay__ { + compatible = "hifiberry,hifiberry-adc8x"; + i2s-controller = <&i2s_clk_producer>; + status = "okay"; + }; + }; + +}; diff --git a/arch/arm/boot/dts/overlays/hifiberry-amp-overlay.dts b/arch/arm/boot/dts/overlays/hifiberry-amp-overlay.dts new file mode 100644 index 00000000000000..667cd260180647 --- /dev/null +++ b/arch/arm/boot/dts/overlays/hifiberry-amp-overlay.dts @@ -0,0 +1,39 @@ +// Definitions for HiFiBerry Amp/Amp+ +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_producer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + tas5713@1b { + #sound-dai-cells = <0>; + compatible = "ti,tas5713"; + reg = <0x1b>; + status = "okay"; + }; + }; + }; + + fragment@2 { + target = <&sound>; + __overlay__ { + compatible = "hifiberry,hifiberry-amp"; + i2s-controller = <&i2s_clk_producer>; + status = "okay"; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/hifiberry-amp100-overlay.dts b/arch/arm/boot/dts/overlays/hifiberry-amp100-overlay.dts new file mode 100644 index 00000000000000..b38e6631a57252 --- /dev/null +++ b/arch/arm/boot/dts/overlays/hifiberry-amp100-overlay.dts @@ -0,0 +1,67 @@ +// Definitions for HiFiBerry AMP100 +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target-path = "/"; + __overlay__ { + dacpro_osc: dacpro_osc { + compatible = "hifiberry,dacpro-clk"; + #clock-cells = <0>; + }; + }; + }; + + frag1: fragment@1 { + target = <&i2s_clk_consumer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@2 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + pcm5122@4d { + #sound-dai-cells = <0>; + compatible = "ti,pcm5122"; + reg = <0x4d>; + clocks = <&dacpro_osc>; + AVDD-supply = <&vdd_3v3_reg>; + DVDD-supply = <&vdd_3v3_reg>; + CPVDD-supply = <&vdd_3v3_reg>; + status = "okay"; + }; + }; + }; + + fragment@3 { + target = <&sound>; + hifiberry_dacplus: __overlay__ { + compatible = "hifiberry,hifiberry-dacplus"; + i2s-controller = <&i2s_clk_consumer>; + status = "okay"; + mute-gpio = <&gpio 4 0>; + reset-gpio = <&gpio 17 0x11>; + }; + }; + + __overrides__ { + 24db_digital_gain = + <&hifiberry_dacplus>,"hifiberry,24db_digital_gain?"; + slave = <&hifiberry_dacplus>,"hifiberry-dacplus,slave?", + <&frag1>,"target:0=",<&i2s_clk_producer>, + <&hifiberry_dacplus>,"i2s-controller:0=",<&i2s_clk_producer>; + + leds_off = <&hifiberry_dacplus>,"hifiberry-dacplus,leds_off?"; + mute_ext_ctl = <&hifiberry_dacplus>,"hifiberry-dacplus,mute_ext_ctl:0"; + auto_mute = <&hifiberry_dacplus>,"hifiberry-dacplus,auto_mute?"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/hifiberry-amp3-overlay.dts b/arch/arm/boot/dts/overlays/hifiberry-amp3-overlay.dts new file mode 100644 index 00000000000000..fc8f11b6294e60 --- /dev/null +++ b/arch/arm/boot/dts/overlays/hifiberry-amp3-overlay.dts @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Definitions for HiFiBerry's Amp3 +/dts-v1/; +/plugin/; +#include <dt-bindings/pinctrl/bcm2835.h> +#include <dt-bindings/gpio/gpio.h> + + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_producer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&gpio>; + __overlay__ { + hifiberry_amp3_pins: hifiberry_amp3_pins { + brcm,pins = <23 17>; + brcm,function = <0 1>; + brcm,pull = <2 1>; + }; + }; + }; + + fragment@2 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + hifiberry_amp2: ma120x0p@20 { + #sound-dai-cells = <0>; + compatible = "ma,ma120x0p"; + reg = <0x20>; + status = "okay"; + pinctrl-names = "default"; + pinctrl-0 = <&hifiberry_amp3_pins>; + error_gp-gpios = <&gpio 23 GPIO_ACTIVE_HIGH>; + }; + }; + }; + + fragment@3 { + target = <&sound>; + __overlay__ { + compatible = "hifiberry,hifiberry-amp3"; + i2s-controller = <&i2s_clk_producer>; + status = "okay"; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/hifiberry-amp4pro-overlay.dts b/arch/arm/boot/dts/overlays/hifiberry-amp4pro-overlay.dts new file mode 100644 index 00000000000000..6b211c2932dd15 --- /dev/null +++ b/arch/arm/boot/dts/overlays/hifiberry-amp4pro-overlay.dts @@ -0,0 +1,63 @@ +// Definitions for HiFiBerry AMP4PRO +/dts-v1/; +/plugin/; +#include <dt-bindings/gpio/gpio.h> + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target-path = "/"; + __overlay__ { + dacpro_osc: dacpro_osc { + compatible = "hifiberry,dacpro-clk"; + #clock-cells = <0>; + }; + }; + }; + + frag1: fragment@1 { + target = <&i2s_clk_consumer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@2 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + tas5756@4d { + #sound-dai-cells = <0>; + compatible = "ti,tas5756"; + reg = <0x4d>; + clocks = <&dacpro_osc>; + status = "okay"; + }; + }; + }; + + fragment@3 { + target = <&sound>; + hifiberry_dacplus: __overlay__ { + compatible = "hifiberry,hifiberry-dacplus"; + i2s-controller = <&i2s_clk_consumer>; + status = "okay"; + mute-gpio = <&gpio 4 GPIO_ACTIVE_LOW>; + }; + }; + + __overrides__ { + 24db_digital_gain = + <&hifiberry_dacplus>,"hifiberry-amp4,24db_digital_gain?"; + leds_off = <&hifiberry_dacplus>,"hifiberry-amp4,leds_off?"; + mute_ext_ctl = <&hifiberry_dacplus>,"hifiberry-amp4,mute_ext_ctl:0"; + auto_mute = <&hifiberry_dacplus>,"hifiberry-amp4,auto_mute?"; + slave = <&hifiberry_dacplus>,"hifiberry-dacplus,slave?", + <&frag1>,"target:0=",<&i2s_clk_producer>, + <&hifiberry_dacplus>,"i2s-controller:0=",<&i2s_clk_producer>; + }; +}; diff --git a/arch/arm/boot/dts/overlays/hifiberry-dac-overlay.dts b/arch/arm/boot/dts/overlays/hifiberry-dac-overlay.dts new file mode 100644 index 00000000000000..efb0e18dbdc4a1 --- /dev/null +++ b/arch/arm/boot/dts/overlays/hifiberry-dac-overlay.dts @@ -0,0 +1,34 @@ +// Definitions for HiFiBerry DAC +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_producer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target-path = "/"; + __overlay__ { + pcm5102a-codec { + #sound-dai-cells = <0>; + compatible = "ti,pcm5102a"; + status = "okay"; + }; + }; + }; + + fragment@2 { + target = <&sound>; + __overlay__ { + compatible = "hifiberry,hifiberry-dac"; + i2s-controller = <&i2s_clk_producer>; + status = "okay"; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/hifiberry-dac8x-overlay.dts b/arch/arm/boot/dts/overlays/hifiberry-dac8x-overlay.dts new file mode 100644 index 00000000000000..1f4bcfc2328e3c --- /dev/null +++ b/arch/arm/boot/dts/overlays/hifiberry-dac8x-overlay.dts @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0 +// Definitions for HiFiBerry DAC8x +/dts-v1/; +/plugin/; + +#include <dt-bindings/gpio/gpio.h> + +/ { + compatible = "brcm,bcm2712"; + + fragment@0 { + target = <&gpio>; + __overlay__ { + rp1_i2s0_dac8x: rp1_i2s0_dac8x { + function = "i2s0"; + pins = "gpio18", "gpio19", "gpio20", + "gpio21", "gpio22", "gpio23", + "gpio24", "gpio25", "gpio26", + "gpio27"; + bias-disable; + status = "okay"; + }; + }; + }; + + fragment@1 { + target = <&i2s_clk_producer>; + __overlay__ { + pinctrl-names = "default"; + pinctrl-0 = <&rp1_i2s0_dac8x>; + status = "okay"; + }; + }; + + fragment@2 { + target-path = "/"; + __overlay__ { + dummy-codec { + #sound-dai-cells = <0>; + compatible = "snd-soc-dummy"; + status = "okay"; + }; + }; + }; + + fragment@3 { + target = <&sound>; + __overlay__ { + compatible = "hifiberry,hifiberry-dac8x"; + i2s-controller = <&i2s_clk_producer>; + hasadc-gpio = <&gpio 5 GPIO_ACTIVE_LOW>; + status = "okay"; + }; + }; + +}; diff --git a/arch/arm/boot/dts/overlays/hifiberry-dacplus-overlay.dts b/arch/arm/boot/dts/overlays/hifiberry-dacplus-overlay.dts new file mode 100644 index 00000000000000..0d0ab068112fad --- /dev/null +++ b/arch/arm/boot/dts/overlays/hifiberry-dacplus-overlay.dts @@ -0,0 +1,68 @@ +// Definitions for HiFiBerry DAC+ +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target-path = "/"; + __overlay__ { + dacpro_osc: dacpro_osc { + compatible = "hifiberry,dacpro-clk"; + #clock-cells = <0>; + }; + }; + }; + + frag1: fragment@1 { + target = <&i2s_clk_consumer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@2 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + pcm5122@4d { + #sound-dai-cells = <0>; + compatible = "ti,pcm5122"; + reg = <0x4d>; + clocks = <&dacpro_osc>; + AVDD-supply = <&vdd_3v3_reg>; + DVDD-supply = <&vdd_3v3_reg>; + CPVDD-supply = <&vdd_3v3_reg>; + status = "okay"; + }; + hpamp: hpamp@60 { + compatible = "ti,tpa6130a2"; + reg = <0x60>; + status = "disabled"; + }; + }; + }; + + fragment@3 { + target = <&sound>; + hifiberry_dacplus: __overlay__ { + compatible = "hifiberry,hifiberry-dacplus"; + i2s-controller = <&i2s_clk_consumer>; + status = "okay"; + }; + }; + + __overrides__ { + 24db_digital_gain = + <&hifiberry_dacplus>,"hifiberry,24db_digital_gain?"; + slave = <&hifiberry_dacplus>,"hifiberry-dacplus,slave?", + <&frag1>,"target:0=",<&i2s_clk_producer>, + <&hifiberry_dacplus>,"i2s-controller:0=",<&i2s_clk_producer>; + + leds_off = <&hifiberry_dacplus>,"hifiberry-dacplus,leds_off?"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/hifiberry-dacplus-pro-overlay.dts b/arch/arm/boot/dts/overlays/hifiberry-dacplus-pro-overlay.dts new file mode 100644 index 00000000000000..28b1c2f2f1a8b5 --- /dev/null +++ b/arch/arm/boot/dts/overlays/hifiberry-dacplus-pro-overlay.dts @@ -0,0 +1,64 @@ +// Definitions for HiFiBerry DAC+ PRO, with onboard clocks +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target-path = "/"; + __overlay__ { + dacpro_osc: dacpro_osc { + compatible = "hifiberry,dacpro-clk"; + #clock-cells = <0>; + }; + }; + }; + + frag1: fragment@1 { + target = <&i2s_clk_consumer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@2 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + pcm5122@4d { + #sound-dai-cells = <0>; + compatible = "ti,pcm5122"; + reg = <0x4d>; + clocks = <&dacpro_osc>; + AVDD-supply = <&vdd_3v3_reg>; + DVDD-supply = <&vdd_3v3_reg>; + CPVDD-supply = <&vdd_3v3_reg>; + status = "okay"; + }; + hpamp: hpamp@60 { + compatible = "ti,tpa6130a2"; + reg = <0x60>; + status = "disabled"; + }; + }; + }; + + fragment@3 { + target = <&sound>; + hifiberry_dacplus: __overlay__ { + compatible = "hifiberry,hifiberry-dacplus"; + i2s-controller = <&i2s_clk_consumer>; + status = "okay"; + }; + }; + + __overrides__ { + 24db_digital_gain = + <&hifiberry_dacplus>,"hifiberry,24db_digital_gain?"; + leds_off = <&hifiberry_dacplus>,"hifiberry-dacplus,leds_off?"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/hifiberry-dacplus-std-overlay.dts b/arch/arm/boot/dts/overlays/hifiberry-dacplus-std-overlay.dts new file mode 100644 index 00000000000000..8872e3aa348d5e --- /dev/null +++ b/arch/arm/boot/dts/overlays/hifiberry-dacplus-std-overlay.dts @@ -0,0 +1,65 @@ +// Definitions for HiFiBerry DAC+ Standard w/o onboard clocks +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target-path = "/"; + __overlay__ { + dacpro_osc: dacpro_osc { + compatible = "hifiberry,dacpro-clk"; + #clock-cells = <0>; + }; + }; + }; + + fragment@1 { + target = <&i2s_clk_producer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@2 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + pcm5122@4d { + #sound-dai-cells = <0>; + compatible = "ti,pcm5122"; + reg = <0x4d>; + clocks = <&dacpro_osc>; + AVDD-supply = <&vdd_3v3_reg>; + DVDD-supply = <&vdd_3v3_reg>; + CPVDD-supply = <&vdd_3v3_reg>; + status = "okay"; + }; + hpamp: hpamp@60 { + compatible = "ti,tpa6130a2"; + reg = <0x60>; + status = "disabled"; + }; + }; + }; + + fragment@3 { + target = <&sound>; + hifiberry_dacplus: __overlay__ { + compatible = "hifiberry,hifiberry-dacplus"; + i2s-controller = <&i2s_clk_producer>; + hifiberry-dacplus,slave; + status = "okay"; + }; + }; + + __overrides__ { + 24db_digital_gain = + <&hifiberry_dacplus>,"hifiberry,24db_digital_gain?"; + leds_off = <&hifiberry_dacplus>,"hifiberry-dacplus,leds_off?"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/hifiberry-dacplusadc-overlay.dts b/arch/arm/boot/dts/overlays/hifiberry-dacplusadc-overlay.dts new file mode 100644 index 00000000000000..ea4c3572826f82 --- /dev/null +++ b/arch/arm/boot/dts/overlays/hifiberry-dacplusadc-overlay.dts @@ -0,0 +1,72 @@ +// Definitions for HiFiBerry DAC+ADC, no onboard clocks +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target-path = "/"; + __overlay__ { + dacpro_osc: dacpro_osc { + compatible = "hifiberry,dacpro-clk"; + #clock-cells = <0>; + }; + }; + }; + + fragment@1 { + target = <&i2s_clk_producer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@2 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + pcm_codec: pcm5122@4d { + #sound-dai-cells = <0>; + compatible = "ti,pcm5122"; + reg = <0x4d>; + clocks = <&dacpro_osc>; + AVDD-supply = <&vdd_3v3_reg>; + DVDD-supply = <&vdd_3v3_reg>; + CPVDD-supply = <&vdd_3v3_reg>; + status = "okay"; + }; + }; + }; + + fragment@3 { + target-path = "/"; + __overlay__ { + dmic { + #sound-dai-cells = <0>; + compatible = "dmic-codec"; + num-channels = <2>; + status = "okay"; + }; + }; + }; + + fragment@4 { + target = <&sound>; + hifiberry_dacplusadc: __overlay__ { + compatible = "hifiberry,hifiberry-dacplusadc"; + i2s-controller = <&i2s_clk_producer>; + hifiberry-dacplusadc,slave; + status = "okay"; + }; + }; + + __overrides__ { + 24db_digital_gain = + <&hifiberry_dacplusadc>,"hifiberry,24db_digital_gain?"; + leds_off = <&hifiberry_dacplusadc>,"hifiberry-dacplusadc,leds_off?"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/hifiberry-dacplusadcpro-overlay.dts b/arch/arm/boot/dts/overlays/hifiberry-dacplusadcpro-overlay.dts new file mode 100644 index 00000000000000..a4268bd72477f0 --- /dev/null +++ b/arch/arm/boot/dts/overlays/hifiberry-dacplusadcpro-overlay.dts @@ -0,0 +1,72 @@ +// Definitions for HiFiBerry DAC+ADC PRO +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target-path = "/"; + __overlay__ { + dacpro_osc: dacpro_osc { + compatible = "hifiberry,dacpro-clk"; + #clock-cells = <0>; + }; + }; + }; + + frag1: fragment@1 { + target = <&i2s_clk_consumer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@2 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + hb_dac: pcm5122@4d { + #sound-dai-cells = <0>; + compatible = "ti,pcm5122"; + reg = <0x4d>; + clocks = <&dacpro_osc>; + status = "okay"; + }; + hb_adc: pcm186x@4a { + #sound-dai-cells = <0>; + compatible = "ti,pcm1863"; + reg = <0x4a>; + clocks = <&dacpro_osc>; + status = "okay"; + }; + hpamp: hpamp@60 { + compatible = "ti,tpa6130a2"; + reg = <0x60>; + status = "disabled"; + }; + }; + }; + + fragment@3 { + target = <&sound>; + hifiberry_dacplusadcpro: __overlay__ { + compatible = "hifiberry,hifiberry-dacplusadcpro"; + audio-codec = <&hb_dac &hb_adc>; + i2s-controller = <&i2s_clk_consumer>; + status = "okay"; + }; + }; + + __overrides__ { + 24db_digital_gain = + <&hifiberry_dacplusadcpro>,"hifiberry-dacplusadcpro,24db_digital_gain?"; + slave = <&hifiberry_dacplusadcpro>,"hifiberry-dacplusadcpro,slave?", + <&frag1>,"target:0=",<&i2s_clk_producer>, + <&hifiberry_dacplusadcpro>,"i2s-controller:0=",<&i2s_clk_producer>; + leds_off = <&hifiberry_dacplusadcpro>,"hifiberry-dacplusadcpro,leds_off?"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/hifiberry-dacplusdsp-overlay.dts b/arch/arm/boot/dts/overlays/hifiberry-dacplusdsp-overlay.dts new file mode 100644 index 00000000000000..e916485f737e8e --- /dev/null +++ b/arch/arm/boot/dts/overlays/hifiberry-dacplusdsp-overlay.dts @@ -0,0 +1,34 @@ +// Definitions for hifiberry DAC+DSP soundcard overlay +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_producer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target-path = "/"; + __overlay__ { + dacplusdsp-codec { + #sound-dai-cells = <0>; + compatible = "hifiberry,dacplusdsp"; + status = "okay"; + }; + }; + }; + + fragment@2 { + target = <&sound>; + __overlay__ { + compatible = "hifiberrydacplusdsp,hifiberrydacplusdsp-soundcard"; + i2s-controller = <&i2s_clk_producer>; + status = "okay"; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/hifiberry-dacplushd-overlay.dts b/arch/arm/boot/dts/overlays/hifiberry-dacplushd-overlay.dts new file mode 100644 index 00000000000000..1856ac19793b36 --- /dev/null +++ b/arch/arm/boot/dts/overlays/hifiberry-dacplushd-overlay.dts @@ -0,0 +1,94 @@ +// Definitions for HiFiBerry DAC+ HD +/dts-v1/; +/plugin/; + +#include <dt-bindings/gpio/gpio.h> + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_consumer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + pcm1792a@4c { + compatible = "ti,pcm1792a"; + #sound-dai-cells = <0>; + #clock-cells = <0>; + reg = <0x4c>; + status = "okay"; + }; + pll: pll@62 { + compatible = "hifiberry,dachd-clk"; + #clock-cells = <0>; + reg = <0x62>; + status = "okay"; + common_pll_regs = [ + 02 53 03 00 07 20 0F 00 + 10 0D 11 1D 12 0D 13 8C + 14 8C 15 8C 16 8C 17 8C + 18 2A 1C 00 1D 0F 1F 00 + 2A 00 2C 00 2F 00 30 00 + 31 00 32 00 34 00 37 00 + 38 00 39 00 3A 00 3B 01 + 3E 00 3F 00 40 00 41 00 + 5A 00 5B 00 95 00 96 00 + 97 00 98 00 99 00 9A 00 + 9B 00 A2 00 A3 00 A4 00 + B7 92 ]; + 192k_pll_regs = [ + 1A 0C 1B 35 1E F0 20 09 + 21 50 2B 02 2D 10 2E 40 + 33 01 35 22 36 80 3C 22 + 3D 46 ]; + 96k_pll_regs = [ + 1A 0C 1B 35 1E F0 20 09 + 21 50 2B 02 2D 10 2E 40 + 33 01 35 47 36 00 3C 32 + 3D 46 ]; + 48k_pll_regs = [ + 1A 0C 1B 35 1E F0 20 09 + 21 50 2B 02 2D 10 2E 40 + 33 01 35 90 36 00 3C 42 + 3D 46 ]; + 176k4_pll_regs = [ + 1A 3D 1B 09 1E F3 20 13 + 21 75 2B 04 2D 11 2E E0 + 33 02 35 25 36 C0 3C 22 + 3D 7A ]; + 88k2_pll_regs = [ + 1A 3D 1B 09 1E F3 20 13 + 21 75 2B 04 2D 11 2E E0 + 33 01 35 4D 36 80 3C 32 + 3D 7A ]; + 44k1_pll_regs = [ + 1A 3D 1B 09 1E F3 20 13 + 21 75 2B 04 2D 11 2E E0 + 33 01 35 9D 36 00 3C 42 + 3D 7A ]; + }; + }; + }; + + fragment@2 { + target = <&sound>; + __overlay__ { + compatible = "hifiberry,hifiberry-dacplushd"; + i2s-controller = <&i2s_clk_consumer>; + clocks = <&pll 0>; + reset-gpio = <&gpio 16 GPIO_ACTIVE_LOW>; + status = "okay"; + }; + }; + +}; diff --git a/arch/arm/boot/dts/overlays/hifiberry-digi-overlay.dts b/arch/arm/boot/dts/overlays/hifiberry-digi-overlay.dts new file mode 100644 index 00000000000000..eb68f117a92af8 --- /dev/null +++ b/arch/arm/boot/dts/overlays/hifiberry-digi-overlay.dts @@ -0,0 +1,41 @@ +// Definitions for HiFiBerry Digi +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_consumer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + wm8804@3b { + #sound-dai-cells = <0>; + compatible = "wlf,wm8804"; + reg = <0x3b>; + PVDD-supply = <&vdd_3v3_reg>; + DVDD-supply = <&vdd_3v3_reg>; + status = "okay"; + }; + }; + }; + + fragment@2 { + target = <&sound>; + __overlay__ { + compatible = "hifiberry,hifiberry-digi"; + i2s-controller = <&i2s_clk_consumer>; + status = "okay"; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/hifiberry-digi-pro-overlay.dts b/arch/arm/boot/dts/overlays/hifiberry-digi-pro-overlay.dts new file mode 100644 index 00000000000000..18d16276e120df --- /dev/null +++ b/arch/arm/boot/dts/overlays/hifiberry-digi-pro-overlay.dts @@ -0,0 +1,43 @@ +// Definitions for HiFiBerry Digi Pro +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_consumer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + wm8804@3b { + #sound-dai-cells = <0>; + compatible = "wlf,wm8804"; + reg = <0x3b>; + PVDD-supply = <&vdd_3v3_reg>; + DVDD-supply = <&vdd_3v3_reg>; + status = "okay"; + }; + }; + }; + + fragment@2 { + target = <&sound>; + __overlay__ { + compatible = "hifiberry,hifiberry-digi"; + i2s-controller = <&i2s_clk_consumer>; + status = "okay"; + clock44-gpio = <&gpio 5 0>; + clock48-gpio = <&gpio 6 0>; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/highperi-overlay.dts b/arch/arm/boot/dts/overlays/highperi-overlay.dts new file mode 100644 index 00000000000000..46cb76c2d34ffd --- /dev/null +++ b/arch/arm/boot/dts/overlays/highperi-overlay.dts @@ -0,0 +1,63 @@ +/* + * highperi.dts + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2711"; + + fragment@0 { + target = <&soc>; + #address-cells = <2>; + #size-cells = <1>; + + __overlay__ { + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x7c000000 0x4 0x7c000000 0x04000000>, + <0x40000000 0x4 0xc0000000 0x00800000>; + }; + }; + + fragment@1 { + target = <&scb>; + #address-cells = <2>; + #size-cells = <1>; + + __overlay__ { + #address-cells = <2>; + #size-cells = <2>; + ranges = <0x0 0x7c000000 0x4 0x7c000000 0x0 0x04000000>, + <0x0 0x40000000 0x4 0xc0000000 0x0 0x00800000>, + <0x6 0x00000000 0x6 0x00000000 0x0 0x40000000>; + dma-ranges = <0x0 0x00000000 0x0 0x00000000 0x2 0x00000000>; + }; + }; + + fragment@2 { + target = <&v3dbus>; + #address-cells = <2>; + #size-cells = <1>; + + __overlay__ { + #address-cells = <1>; + #size-cells = <2>; + ranges = <0x7c500000 0x4 0x7c500000 0x0 0x03300000>, + <0x40000000 0x4 0xc0000000 0x0 0x00800000>; + }; + }; + + fragment@3 { + target = <&emmc2bus>; + #address-cells = <2>; + #size-cells = <1>; + + __overlay__ { + #address-cells = <2>; + #size-cells = <1>; + ranges = <0x0 0x7e000000 0x4 0x7e000000 0x01800000>; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/hy28a-overlay.dts b/arch/arm/boot/dts/overlays/hy28a-overlay.dts new file mode 100644 index 00000000000000..d0d52ebd9bd542 --- /dev/null +++ b/arch/arm/boot/dts/overlays/hy28a-overlay.dts @@ -0,0 +1,93 @@ +/* + * Device Tree overlay for HY28A display + * + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spi0>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@2 { + target = <&spidev1>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@3 { + target = <&gpio>; + __overlay__ { + hy28a_pins: hy28a_pins { + brcm,pins = <17 25 18>; + brcm,function = <0 1 1>; /* in out out */ + }; + }; + }; + + fragment@4 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + hy28a: hy28a@0{ + compatible = "ilitek,ili9320"; + reg = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&hy28a_pins>; + + spi-max-frequency = <32000000>; + spi-cpol; + spi-cpha; + rotate = <270>; + bgr; + fps = <50>; + buswidth = <8>; + startbyte = <0x70>; + reset-gpios = <&gpio 25 1>; + led-gpios = <&gpio 18 1>; + debug = <0>; + }; + + hy28a_ts: hy28a-ts@1 { + compatible = "ti,ads7846"; + reg = <1>; + + spi-max-frequency = <2000000>; + interrupts = <17 2>; /* high-to-low edge triggered */ + interrupt-parent = <&gpio>; + pendown-gpio = <&gpio 17 1>; + ti,x-plate-ohms = /bits/ 16 <100>; + ti,pressure-max = /bits/ 16 <255>; + }; + }; + }; + __overrides__ { + speed = <&hy28a>,"spi-max-frequency:0"; + rotate = <&hy28a>,"rotate:0"; + fps = <&hy28a>,"fps:0"; + debug = <&hy28a>,"debug:0"; + xohms = <&hy28a_ts>,"ti,x-plate-ohms;0"; + resetgpio = <&hy28a>,"reset-gpios:4", + <&hy28a_pins>, "brcm,pins:4"; + ledgpio = <&hy28a>,"led-gpios:4", + <&hy28a_pins>, "brcm,pins:8"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/hy28b-2017-overlay.dts b/arch/arm/boot/dts/overlays/hy28b-2017-overlay.dts new file mode 100644 index 00000000000000..9df33c5d95bbc9 --- /dev/null +++ b/arch/arm/boot/dts/overlays/hy28b-2017-overlay.dts @@ -0,0 +1,152 @@ +/* + * Device Tree overlay for HY28b display shield by Texy. + * Modified for 2017 version with ILI9325 D chip + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spi0>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@2 { + target = <&spidev1>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@3 { + target = <&gpio>; + __overlay__ { + hy28b_pins: hy28b_pins { + brcm,pins = <17 25 18>; + brcm,function = <0 1 1>; /* in out out */ + }; + }; + }; + + fragment@4 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + hy28b: hy28b@0{ + compatible = "ilitek,ili9325"; + reg = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&hy28b_pins>; + + spi-max-frequency = <48000000>; + spi-cpol; + spi-cpha; + rotate = <270>; + bgr; + fps = <50>; + buswidth = <8>; + startbyte = <0x70>; + reset-gpios = <&gpio 25 1>; + led-gpios = <&gpio 18 1>; + + init = <0x10000e5 0x78F0 + 0x1000001 0x0100 + 0x1000002 0x0700 + 0x1000003 0x1030 + 0x1000004 0x0000 + 0x1000008 0x0207 + 0x1000009 0x0000 + 0x100000a 0x0000 + 0x100000c 0x0000 + 0x100000d 0x0000 + 0x100000f 0x0000 + 0x1000010 0x0000 + 0x1000011 0x0007 + 0x1000012 0x0000 + 0x1000013 0x0000 + 0x1000007 0x0001 + 0x2000032 + 0x2000032 + 0x2000032 + 0x2000032 + 0x1000010 0x1090 + 0x1000011 0x0227 + 0x2000032 + 0x1000012 0x001f + 0x2000032 + 0x1000013 0x1500 + 0x1000029 0x0027 + 0x100002b 0x000d + 0x2000032 + 0x1000020 0x0000 + 0x1000021 0x0000 + 0x2000032 + 0x1000030 0x0000 + 0x1000031 0x0707 + 0x1000032 0x0307 + 0x1000035 0x0200 + 0x1000036 0x0008 + 0x1000037 0x0004 + 0x1000038 0x0000 + 0x1000039 0x0707 + 0x100003c 0x0002 + 0x100003d 0x1d04 + 0x1000050 0x0000 + 0x1000051 0x00ef + 0x1000052 0x0000 + 0x1000053 0x013f + 0x1000060 0xa700 + 0x1000061 0x0001 + 0x100006a 0x0000 + 0x1000080 0x0000 + 0x1000081 0x0000 + 0x1000082 0x0000 + 0x1000083 0x0000 + 0x1000084 0x0000 + 0x1000085 0x0000 + 0x1000090 0x0010 + 0x1000092 0x0600 + 0x1000007 0x0133>; + debug = <0>; + }; + + hy28b_ts: hy28b-ts@1 { + compatible = "ti,ads7846"; + reg = <1>; + + spi-max-frequency = <2000000>; + interrupts = <17 2>; /* high-to-low edge triggered */ + interrupt-parent = <&gpio>; + pendown-gpio = <&gpio 17 1>; + ti,x-plate-ohms = /bits/ 16 <100>; + ti,pressure-max = /bits/ 16 <255>; + }; + }; + }; + __overrides__ { + speed = <&hy28b>,"spi-max-frequency:0"; + rotate = <&hy28b>,"rotate:0"; + fps = <&hy28b>,"fps:0"; + debug = <&hy28b>,"debug:0"; + xohms = <&hy28b_ts>,"ti,x-plate-ohms;0"; + resetgpio = <&hy28b>,"reset-gpios:4", + <&hy28b_pins>, "brcm,pins:4"; + ledgpio = <&hy28b>,"led-gpios:4", + <&hy28b_pins>, "brcm,pins:8"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/hy28b-overlay.dts b/arch/arm/boot/dts/overlays/hy28b-overlay.dts new file mode 100644 index 00000000000000..421bde94a4a0c3 --- /dev/null +++ b/arch/arm/boot/dts/overlays/hy28b-overlay.dts @@ -0,0 +1,148 @@ +/* + * Device Tree overlay for HY28b display shield by Texy + * + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spi0>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@2 { + target = <&spidev1>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@3 { + target = <&gpio>; + __overlay__ { + hy28b_pins: hy28b_pins { + brcm,pins = <17 25 18>; + brcm,function = <0 1 1>; /* in out out */ + }; + }; + }; + + fragment@4 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + hy28b: hy28b@0{ + compatible = "ilitek,ili9325"; + reg = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&hy28b_pins>; + + spi-max-frequency = <48000000>; + spi-cpol; + spi-cpha; + rotate = <270>; + bgr; + fps = <50>; + buswidth = <8>; + startbyte = <0x70>; + reset-gpios = <&gpio 25 1>; + led-gpios = <&gpio 18 1>; + + gamma = "04 1F 4 7 7 0 7 7 6 0\n0F 00 1 7 4 0 0 0 6 7"; + + init = <0x10000e7 0x0010 + 0x1000000 0x0001 + 0x1000001 0x0100 + 0x1000002 0x0700 + 0x1000003 0x1030 + 0x1000004 0x0000 + 0x1000008 0x0207 + 0x1000009 0x0000 + 0x100000a 0x0000 + 0x100000c 0x0001 + 0x100000d 0x0000 + 0x100000f 0x0000 + 0x1000010 0x0000 + 0x1000011 0x0007 + 0x1000012 0x0000 + 0x1000013 0x0000 + 0x2000032 + 0x1000010 0x1590 + 0x1000011 0x0227 + 0x2000032 + 0x1000012 0x009c + 0x2000032 + 0x1000013 0x1900 + 0x1000029 0x0023 + 0x100002b 0x000e + 0x2000032 + 0x1000020 0x0000 + 0x1000021 0x0000 + 0x2000032 + 0x1000050 0x0000 + 0x1000051 0x00ef + 0x1000052 0x0000 + 0x1000053 0x013f + 0x1000060 0xa700 + 0x1000061 0x0001 + 0x100006a 0x0000 + 0x1000080 0x0000 + 0x1000081 0x0000 + 0x1000082 0x0000 + 0x1000083 0x0000 + 0x1000084 0x0000 + 0x1000085 0x0000 + 0x1000090 0x0010 + 0x1000092 0x0000 + 0x1000093 0x0003 + 0x1000095 0x0110 + 0x1000097 0x0000 + 0x1000098 0x0000 + 0x1000007 0x0133 + 0x1000020 0x0000 + 0x1000021 0x0000 + 0x2000064>; + debug = <0>; + }; + + hy28b_ts: hy28b-ts@1 { + compatible = "ti,ads7846"; + reg = <1>; + + spi-max-frequency = <2000000>; + interrupts = <17 2>; /* high-to-low edge triggered */ + interrupt-parent = <&gpio>; + pendown-gpio = <&gpio 17 1>; + ti,x-plate-ohms = /bits/ 16 <100>; + ti,pressure-max = /bits/ 16 <255>; + }; + }; + }; + __overrides__ { + speed = <&hy28b>,"spi-max-frequency:0"; + rotate = <&hy28b>,"rotate:0"; + fps = <&hy28b>,"fps:0"; + debug = <&hy28b>,"debug:0"; + xohms = <&hy28b_ts>,"ti,x-plate-ohms;0"; + resetgpio = <&hy28b>,"reset-gpios:4", + <&hy28b_pins>, "brcm,pins:4"; + ledgpio = <&hy28b>,"led-gpios:4", + <&hy28b_pins>, "brcm,pins:8"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/i-sabre-q2m-overlay.dts b/arch/arm/boot/dts/overlays/i-sabre-q2m-overlay.dts new file mode 100644 index 00000000000000..6db52955a8f80c --- /dev/null +++ b/arch/arm/boot/dts/overlays/i-sabre-q2m-overlay.dts @@ -0,0 +1,39 @@ +// Definitions for I-Sabre Q2M +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&sound>; + frag0: __overlay__ { + compatible = "audiophonics,i-sabre-q2m"; + i2s-controller = <&i2s_clk_producer>; + status = "okay"; + }; + }; + + fragment@1 { + target = <&i2s_clk_producer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@2 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + i-sabre-codec@48 { + #sound-dai-cells = <0>; + compatible = "audiophonics,i-sabre-codec"; + reg = <0x48>; + status = "okay"; + }; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/i2c-bcm2708-overlay.dts b/arch/arm/boot/dts/overlays/i2c-bcm2708-overlay.dts new file mode 100644 index 00000000000000..8204b6b3aef833 --- /dev/null +++ b/arch/arm/boot/dts/overlays/i2c-bcm2708-overlay.dts @@ -0,0 +1,13 @@ +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2c_arm>; + __overlay__ { + compatible = "brcm,bcm2708-i2c"; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/i2c-buses.dtsi b/arch/arm/boot/dts/overlays/i2c-buses.dtsi new file mode 100644 index 00000000000000..5c7699a8ac66cf --- /dev/null +++ b/arch/arm/boot/dts/overlays/i2c-buses.dtsi @@ -0,0 +1,67 @@ +// Common i2c buses, and dtparams to select them + +/ { + compatible = "brcm,bcm2835"; + + busfrag: fragment@100 { + target = <&i2c_arm>; + i2cbus: __overlay__ { + status = "okay"; + }; + }; + + fragment@101 { + target = <&i2c0if>; +#ifdef ENABLE_I2C0_MUX + __overlay__ { +#else + __dormant__ { +#endif + status = "okay"; + }; + }; + + fragment@102 { + target = <&i2c0mux>; +#ifdef ENABLE_I2C0_MUX + __overlay__ { +#else + __dormant__ { +#endif + status = "okay"; + }; + }; + + __overrides__ { + i2c0 = <&busfrag>,"target:0=",<&i2c0>, + <&busfrag>, "target-path?=0", + <0>,"+101+102"; + i2c_csi_dsi = <&busfrag>,"target:0=",<&i2c_csi_dsi>, + <&busfrag>, "target-path?=0", + <0>,"+101+102"; + i2c_csi_dsi0 = <&busfrag>, "target:0=",<&i2c_csi_dsi0>, + <&busfrag>, "target-path?=0", + <0>,"+101+102"; + i2c1 = <&busfrag>,"target:0=",<&i2c1>, + <&busfrag>, "target-path?=0", + <0>,"-101-102"; + i2c2 = <&busfrag>, "target?=0", + <&busfrag>, "target-path=i2c2", + <0>,"-101-102"; + i2c3 = <&busfrag>, "target?=0", + <&busfrag>, "target-path=i2c3", + <0>,"-101-102"; + i2c4 = <&busfrag>, "target?=0", + <&busfrag>, "target-path=i2c4", + <0>,"-101-102"; + i2c5 = <&busfrag>, "target?=0", + <&busfrag>, "target-path=i2c5", + <0>,"-101-102"; + i2c6 = <&busfrag>, "target?=0", + <&busfrag>, "target-path=i2c6", + <0>,"-101-102"; + i2c-path = <&busfrag>, "target?=0", + <&busfrag>, "target-path", + <0>,"-101-102"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/i2c-fan-overlay.dts b/arch/arm/boot/dts/overlays/i2c-fan-overlay.dts new file mode 100644 index 00000000000000..70e204cb81e6d9 --- /dev/null +++ b/arch/arm/boot/dts/overlays/i2c-fan-overlay.dts @@ -0,0 +1,77 @@ +// Definitions for I2C based sensors using the Industrial IO or HWMON interface. +/dts-v1/; +/plugin/; + +#include <dt-bindings/thermal/thermal.h> + +#include "i2c-buses.dtsi" + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + emc2301: emc2301@2f { + compatible = "microchip,emc2301"; + reg = <0x2f>; + #cooling-cells = <0x02>; + }; + }; + }; + + fragment@1 { + target = <&cpu_thermal>; + __overlay__ { + polling-delay = <2000>; /* milliseconds */ + }; + }; + + fragment@2 { + target = <&thermal_trips>; + __overlay__ { + fanmid0: fanmid0 { + temperature = <50000>; + hysteresis = <2000>; + type = "active"; + }; + fanmax0: fanmax0 { + temperature = <75000>; + hysteresis = <2000>; + type = "active"; + }; + }; + }; + + fragment@3 { + target = <&cooling_maps>; + __overlay__ { + map0: map0 { + trip = <&fanmid0>; + cooling-device = <&emc2301 2 6>; + }; + map1: map1 { + trip = <&fanmax0>; + cooling-device = <&emc2301 7 THERMAL_NO_LIMIT>; + }; + }; + }; + + __overrides__ { + addr = <&emc2301>,"reg:0"; + minpwm = <&emc2301>,"emc2305,pwm-min.0"; + maxpwm = <&emc2301>,"emc2305,pwm-max.0"; + midtemp = <&fanmid0>,"temperature:0"; + midtemp_hyst = <&fanmid0>,"hysteresis:0"; + maxtemp = <&fanmax0>,"temperature:0"; + maxtemp_hyst = <&fanmax0>,"hysteresis:0"; + + emc2301 = <0>,"+0", + <&map0>,"cooling-device:0=",<&emc2301>, + <&map1>,"cooling-device:0=",<&emc2301>; + }; +}; diff --git a/arch/arm/boot/dts/overlays/i2c-gpio-overlay.dts b/arch/arm/boot/dts/overlays/i2c-gpio-overlay.dts new file mode 100644 index 00000000000000..63231b5d7c0c11 --- /dev/null +++ b/arch/arm/boot/dts/overlays/i2c-gpio-overlay.dts @@ -0,0 +1,47 @@ +// Overlay for i2c_gpio bitbanging host bus. +/dts-v1/; +/plugin/; + +#include <dt-bindings/gpio/gpio.h> + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target-path = "/"; + + __overlay__ { + i2c_gpio: i2c@0 { + reg = <0xffffffff>; + compatible = "i2c-gpio"; + gpios = <&gpio 23 (GPIO_ACTIVE_HIGH|GPIO_OPEN_DRAIN) /* sda */ + &gpio 24 (GPIO_ACTIVE_HIGH|GPIO_OPEN_DRAIN) /* scl */ + >; + i2c-gpio,delay-us = <2>; /* ~100 kHz */ + #address-cells = <1>; + #size-cells = <0>; + }; + }; + }; + + fragment@1 { + target-path = "/aliases"; + __overlay__ { + i2c_gpio = "/i2c@0"; + }; + }; + + fragment@2 { + target-path = "/__symbols__"; + __overlay__ { + i2c_gpio = "/i2c@0"; + }; + }; + + __overrides__ { + i2c_gpio_sda = <&i2c_gpio>,"gpios:4"; + i2c_gpio_scl = <&i2c_gpio>,"gpios:16"; + i2c_gpio_delay_us = <&i2c_gpio>,"i2c-gpio,delay-us:0"; + bus = <&i2c_gpio>, "reg:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/i2c-mux-overlay.dts b/arch/arm/boot/dts/overlays/i2c-mux-overlay.dts new file mode 100644 index 00000000000000..be79831ea8d2cc --- /dev/null +++ b/arch/arm/boot/dts/overlays/i2c-mux-overlay.dts @@ -0,0 +1,149 @@ +// Umbrella I2C Mux overlay + +/dts-v1/; +/plugin/; + +#include <dt-bindings/mux/mux.h> + +#include "i2c-buses.dtsi" + +/{ + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + pca9542: mux@70 { + compatible = "nxp,pca9542"; + reg = <0x70>; + #address-cells = <1>; + #size-cells = <0>; + + i2c@0 { + #address-cells = <1>; + #size-cells = <0>; + reg = <0>; + }; + i2c@1 { + #address-cells = <1>; + #size-cells = <0>; + reg = <1>; + }; + }; + }; + }; + + fragment@1 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + pca9545: mux@70 { + compatible = "nxp,pca9545"; + reg = <0x70>; + #address-cells = <1>; + #size-cells = <0>; + + i2c@0 { + #address-cells = <1>; + #size-cells = <0>; + reg = <0>; + }; + i2c@1 { + #address-cells = <1>; + #size-cells = <0>; + reg = <1>; + }; + i2c@2 { + #address-cells = <1>; + #size-cells = <0>; + reg = <2>; + }; + i2c@3 { + #address-cells = <1>; + #size-cells = <0>; + reg = <3>; + }; + }; + }; + }; + + fragment@2 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + pca9548: mux@70 { + compatible = "nxp,pca9548"; + reg = <0x70>; + #address-cells = <1>; + #size-cells = <0>; + + i2c@0 { + #address-cells = <1>; + #size-cells = <0>; + reg = <0>; + }; + i2c@1 { + #address-cells = <1>; + #size-cells = <0>; + reg = <1>; + }; + i2c@2 { + #address-cells = <1>; + #size-cells = <0>; + reg = <2>; + }; + i2c@3 { + #address-cells = <1>; + #size-cells = <0>; + reg = <3>; + }; + i2c@4 { + #address-cells = <1>; + #size-cells = <0>; + reg = <4>; + }; + i2c@5 { + #address-cells = <1>; + #size-cells = <0>; + reg = <5>; + }; + i2c@6 { + #address-cells = <1>; + #size-cells = <0>; + reg = <6>; + }; + i2c@7 { + #address-cells = <1>; + #size-cells = <0>; + reg = <7>; + }; + }; + }; + }; + + __overrides__ { + pca9542 = <0>, "+0"; + pca9545 = <0>, "+1"; + pca9548 = <0>, "+2"; + + addr = <&pca9542>,"reg:0", + <&pca9545>,"reg:0", + <&pca9548>,"reg:0"; + + base = <&pca9542>,"base-nr:0", + <&pca9545>,"base-nr:0", + <&pca9548>,"base-nr:0"; + + disconnect_on_idle = + <&pca9542>,"idle-state:0=", <MUX_IDLE_DISCONNECT>, + <&pca9545>,"idle-state:0=", <MUX_IDLE_DISCONNECT>, + <&pca9548>,"idle-state:0=", <MUX_IDLE_DISCONNECT>; + }; +}; diff --git a/arch/arm/boot/dts/overlays/i2c-pwm-pca9685a-overlay.dts b/arch/arm/boot/dts/overlays/i2c-pwm-pca9685a-overlay.dts new file mode 100644 index 00000000000000..4941e78a3d934b --- /dev/null +++ b/arch/arm/boot/dts/overlays/i2c-pwm-pca9685a-overlay.dts @@ -0,0 +1,28 @@ +// Definitions for NXP PCA9685A I2C PWM controller on ARM I2C bus. +/dts-v1/; +/plugin/; + +#include "i2c-buses.dtsi" + +/{ + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2cbus>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + + pca: pca@40 { + compatible = "nxp,pca9685-pwm"; + #pwm-cells = <2>; + reg = <0x40>; + status = "okay"; + }; + }; + }; + + __overrides__ { + addr = <&pca>,"reg:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/i2c-rtc-common.dtsi b/arch/arm/boot/dts/overlays/i2c-rtc-common.dtsi new file mode 100644 index 00000000000000..8638123336baa6 --- /dev/null +++ b/arch/arm/boot/dts/overlays/i2c-rtc-common.dtsi @@ -0,0 +1,367 @@ +// Definitions for several I2C based Real Time Clocks + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + abx80x: abx80x@69 { + compatible = "abracon,abx80x"; + reg = <0x69>; + abracon,tc-diode = "standard"; + abracon,tc-resistor = <0>; + }; + }; + }; + + fragment@1 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + ds1307: ds1307@68 { + compatible = "dallas,ds1307"; + reg = <0x68>; + }; + }; + }; + + fragment@2 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + ds1339: ds1339@68 { + compatible = "dallas,ds1339"; + trickle-resistor-ohms = <0>; + reg = <0x68>; + }; + }; + }; + + fragment@3 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + ds3231: ds3231@68 { + compatible = "maxim,ds3231"; + reg = <0x68>; + }; + }; + }; + + fragment@4 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + mcp7940x: mcp7940x@6f { + compatible = "microchip,mcp7940x"; + reg = <0x6f>; + }; + }; + }; + + fragment@5 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + mcp7941x: mcp7941x@6f { + compatible = "microchip,mcp7941x"; + reg = <0x6f>; + }; + }; + }; + + fragment@6 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + pcf2127@51 { + compatible = "nxp,pcf2127"; + reg = <0x51>; + }; + }; + }; + + fragment@7 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + pcf8523: pcf8523@68 { + compatible = "nxp,pcf8523"; + reg = <0x68>; + }; + }; + }; + + fragment@8 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + pcf8563: pcf8563@51 { + compatible = "nxp,pcf8563"; + reg = <0x51>; + }; + }; + }; + + fragment@9 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + m41t62: m41t62@68 { + compatible = "st,m41t62"; + reg = <0x68>; + }; + }; + }; + + fragment@10 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + rv3028: rv3028@52 { + compatible = "microcrystal,rv3028"; + reg = <0x52>; + }; + }; + }; + + fragment@11 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + pcf2129@51 { + compatible = "nxp,pcf2129"; + reg = <0x51>; + }; + }; + }; + + fragment@12 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + pcf85363@51 { + compatible = "nxp,pcf85363"; + reg = <0x51>; + }; + }; + }; + + fragment@13 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + rv1805: rv1805@69 { + compatible = "microcrystal,rv1805"; + reg = <0x69>; + abracon,tc-diode = "standard"; + abracon,tc-resistor = <0>; + }; + }; + }; + + fragment@14 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sd3078: sd3078@32 { + compatible = "whwave,sd3078"; + reg = <0x32>; + }; + }; + }; + + fragment@15 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + pcf85063@51 { + compatible = "nxp,pcf85063"; + reg = <0x51>; + }; + }; + }; + + fragment@16 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + pcf85063a@51 { + compatible = "nxp,pcf85063a"; + reg = <0x51>; + }; + }; + }; + + fragment@17 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + ds1340: ds1340@68 { + compatible = "dallas,ds1340"; + trickle-resistor-ohms = <0>; + reg = <0x68>; + }; + }; + }; + + fragment@18 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + s35390a: s35390a@30 { + compatible = "sii,s35390a"; + reg = <0x30>; + }; + }; + }; + + fragment@19 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + bq32000: bq32000@68 { + compatible = "ti,bq32000"; + trickle-resistor-ohms = <0>; + reg = <0x68>; + }; + }; + }; + + fragment@20 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + rv8803: rv8803@32 { + compatible = "microcrystal,rv8803"; + reg = <0x32>; + }; + }; + }; + + fragment@21 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + rv3032: rv3032@51 { + compatible = "microcrystal,rv3032"; + reg = <0x51>; + }; + }; + }; + + fragment@22 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + pcf2131@53 { + compatible = "nxp,pcf2131"; + reg = <0x53>; + }; + }; + }; + + __overrides__ { + abx80x = <0>,"+0"; + ds1307 = <0>,"+1"; + ds1339 = <0>,"+2"; + ds1340 = <0>,"+17"; + ds3231 = <0>,"+3"; + mcp7940x = <0>,"+4"; + mcp7941x = <0>,"+5"; + pcf2127 = <0>,"+6"; + pcf8523 = <0>,"+7"; + pcf8563 = <0>,"+8"; + m41t62 = <0>,"+9"; + rv3028 = <0>,"+10"; + pcf2129 = <0>,"+11"; + pcf85363 = <0>,"+12"; + rv1805 = <0>,"+13"; + sd3078 = <0>,"+14"; + pcf85063 = <0>,"+15"; + pcf85063a = <0>,"+16"; + s35390a = <0>,"+18"; + bq32000 = <0>,"+19"; + rv8803 = <0>,"+20"; + rv3032 = <0>,"+21"; + pcf2131 = <0>,"+22"; + + addr = <&abx80x>, "reg:0", + <&ds1307>, "reg:0", + <&ds1339>, "reg:0", + <&ds3231>, "reg:0", + <&mcp7940x>, "reg:0", + <&mcp7941x>, "reg:0", + <&pcf8523>, "reg:0", + <&pcf8563>, "reg:0", + <&m41t62>, "reg:0", + <&rv1805>, "reg:0", + <&s35390a>, "reg:0"; + trickle-diode-disable = <&bq32000>,"trickle-diode-disable?"; + trickle-diode-type = <&abx80x>,"abracon,tc-diode", + <&rv1805>,"abracon,tc-diode"; + trickle-resistor-ohms = <&ds1339>,"trickle-resistor-ohms:0", + <&ds1340>,"trickle-resistor-ohms:0", + <&abx80x>,"abracon,tc-resistor:0", + <&rv3028>,"trickle-resistor-ohms:0", + <&rv3032>,"trickle-resistor-ohms:0", + <&rv1805>,"abracon,tc-resistor:0", + <&bq32000>,"trickle-resistor-ohms:0"; + trickle-voltage-mv = <&rv3032>,"trickle-voltage-millivolts:0"; + backup-switchover-mode = <&rv3028>,"backup-switchover-mode:0"; + wakeup-source = <&ds1339>,"wakeup-source?", + <&ds3231>,"wakeup-source?", + <&mcp7940x>,"wakeup-source?", + <&mcp7941x>,"wakeup-source?", + <&m41t62>,"wakeup-source?", + <&pcf8563>,"wakeup-source?"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/i2c-rtc-gpio-overlay.dts b/arch/arm/boot/dts/overlays/i2c-rtc-gpio-overlay.dts new file mode 100644 index 00000000000000..c83480c1c32793 --- /dev/null +++ b/arch/arm/boot/dts/overlays/i2c-rtc-gpio-overlay.dts @@ -0,0 +1,31 @@ +// Definitions for several I2C based Real Time Clocks +// Available through i2c-gpio +/dts-v1/; +/plugin/; + +#include <dt-bindings/gpio/gpio.h> + +#include "i2c-rtc-common.dtsi" + +/ { + fragment@100 { + target-path = "/"; + __overlay__ { + i2cbus: i2c-gpio-rtc@0 { + compatible = "i2c-gpio"; + gpios = <&gpio 23 (GPIO_ACTIVE_HIGH|GPIO_OPEN_DRAIN) /* sda */ + &gpio 24 (GPIO_ACTIVE_HIGH|GPIO_OPEN_DRAIN) /* scl */ + >; + i2c-gpio,delay-us = <2>; /* ~100 kHz */ + #address-cells = <1>; + #size-cells = <0>; + }; + }; + }; + + __overrides__ { + i2c_gpio_sda = <&i2cbus>,"gpios:4"; + i2c_gpio_scl = <&i2cbus>,"gpios:16"; + i2c_gpio_delay_us = <&i2cbus>,"i2c-gpio,delay-us:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/i2c-rtc-overlay.dts b/arch/arm/boot/dts/overlays/i2c-rtc-overlay.dts new file mode 100644 index 00000000000000..e003244d894c4a --- /dev/null +++ b/arch/arm/boot/dts/overlays/i2c-rtc-overlay.dts @@ -0,0 +1,6 @@ +// Definitions for several I2C based Real Time Clocks +/dts-v1/; +/plugin/; + +#include "i2c-rtc-common.dtsi" +#include "i2c-buses.dtsi" diff --git a/arch/arm/boot/dts/overlays/i2c-sensor-common.dtsi b/arch/arm/boot/dts/overlays/i2c-sensor-common.dtsi new file mode 100755 index 00000000000000..1a1c2c55118016 --- /dev/null +++ b/arch/arm/boot/dts/overlays/i2c-sensor-common.dtsi @@ -0,0 +1,731 @@ +// Definitions for I2C based sensors using the Industrial IO or HWMON interface. +#include <dt-bindings/gpio/gpio.h> +#include <dt-bindings/interrupt-controller/irq.h> + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + bme280: bme280@76 { + compatible = "bosch,bme280"; + reg = <0x76>; + status = "okay"; + }; + }; + }; + + fragment@1 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + bmp085: bmp085@77 { + compatible = "bosch,bmp085"; + reg = <0x77>; + default-oversampling = <3>; + status = "okay"; + }; + }; + }; + + fragment@2 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + bmp180: bmp180@77 { + compatible = "bosch,bmp180"; + reg = <0x77>; + status = "okay"; + }; + }; + }; + + fragment@3 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + bmp280: bmp280@76 { + compatible = "bosch,bmp280"; + reg = <0x76>; + status = "okay"; + }; + }; + }; + + fragment@4 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + htu21: htu21@40 { + compatible = "meas,htu21"; + reg = <0x40>; + status = "okay"; + }; + }; + }; + + fragment@5 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + lm75: lm75@4f { + compatible = "national,lm75"; + reg = <0x4f>; + status = "okay"; + }; + }; + }; + + fragment@6 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + si7020: si7020@40 { + compatible = "silabs,si7020"; + reg = <0x40>; + status = "okay"; + }; + }; + }; + + fragment@7 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + tmp102: tmp102@48 { + compatible = "ti,tmp102"; + reg = <0x48>; + status = "okay"; + }; + }; + }; + + fragment@8 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + hdc100x: hdc100x@40 { + compatible = "ti,hdc1000"; + reg = <0x40>; + status = "okay"; + }; + }; + }; + + fragment@9 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + tsl4531: tsl4531@29 { + compatible = "amstaos,tsl4531"; + reg = <0x29>; + status = "okay"; + }; + }; + }; + + fragment@10 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + veml6070: veml6070@38 { + compatible = "vishay,veml6070"; + reg = <0x38>; + status = "okay"; + }; + }; + }; + + fragment@11 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + sht3x: sht3x@44 { + compatible = "sensirion,sht3x"; + reg = <0x44>; + status = "okay"; + }; + }; + }; + + fragment@12 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + ds1621: ds1621@48 { + compatible = "dallas,ds1621"; + reg = <0x48>; + status = "okay"; + }; + }; + }; + + fragment@13 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + max17040: max17040@36 { + compatible = "maxim,max17040"; + reg = <0x36>; + status = "okay"; + }; + }; + }; + + fragment@14 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + bme680: bme680@76 { + compatible = "bosch,bme680"; + reg = <0x76>; + status = "okay"; + }; + }; + }; + + fragment@15 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + sps30: sps30@69 { + compatible = "sensirion,sps30"; + reg = <0x69>; + status = "okay"; + }; + }; + }; + + fragment@16 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + sgp30: sgp30@58 { + compatible = "sensirion,sgp30"; + reg = <0x58>; + status = "okay"; + }; + }; + }; + + fragment@17 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + ccs811: ccs811@5b { + compatible = "ams,ccs811"; + reg = <0x5b>; + status = "okay"; + }; + }; + }; + + fragment@18 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + bh1750: bh1750@23 { + compatible = "rohm,bh1750"; + reg = <0x23>; + status = "okay"; + }; + }; + }; + + fragment@19 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + max30102: max30102@57 { + compatible = "maxim,max30102"; + reg = <0x57>; + maxim,red-led-current-microamp = <7000>; + maxim,ir-led-current-microamp = <7000>; + interrupt-parent = <&gpio>; + interrupts = <4 IRQ_TYPE_EDGE_FALLING>; + pinctrl-0 = <&int_pins>; + pinctrl-names = "default"; + }; + }; + }; + + fragment@20 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + aht10: aht10@38 { + compatible = "aosong,aht10"; + reg = <0x38>; + }; + }; + }; + + fragment@21 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + mcp980x: mcp980x@18 { + compatible = "maxim,mcp980x"; + reg = <0x18>; + }; + }; + }; + + fragment@22 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + jc42: jc42@18 { + compatible = "jedec,jc-42.4-temp"; + reg = <0x18>; + }; + }; + }; + + fragment@23 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + ms5637: ms5637@76 { + compatible = "meas,ms5637"; + reg = <0x76>; + }; + }; + }; + + fragment@24 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + ms5803: ms5803@76 { + compatible = "meas,ms5803"; + reg = <0x76>; + }; + }; + }; + + fragment@25 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + ms5805: ms5805@76 { + compatible = "meas,ms5805"; + reg = <0x76>; + }; + }; + }; + + fragment@26 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + ms5837: ms5837@76 { + compatible = "meas,ms5837"; + reg = <0x76>; + }; + }; + }; + + fragment@27 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + ms8607: ms8607@76 { + compatible = "meas,ms8607-temppressure"; + reg = <0x76>; + }; + }; + }; + + fragment@28 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + clock-frequency = <400000>; + + mpu6050: mpu6050@68 { + compatible = "invensense,mpu6050"; + reg = <0x68>; + interrupt-parent = <&gpio>; + interrupts = <4 IRQ_TYPE_EDGE_FALLING>; + pinctrl-0 = <&int_pins>; + pinctrl-names = "default"; + }; + }; + }; + + fragment@29 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + clock-frequency = <400000>; + + mpu9250: mpu9250@68 { + compatible = "invensense,mpu9250"; + reg = <0x68>; + interrupt-parent = <&gpio>; + interrupts = <4 IRQ_TYPE_EDGE_FALLING>; + pinctrl-0 = <&int_pins>; + pinctrl-names = "default"; + }; + }; + }; + + fragment@30 { + target = <&bno055>; + __dormant__ { + reset-gpios = <&gpio 5 GPIO_ACTIVE_LOW>; + }; + }; + + fragment@31 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + bno055: bno055@29 { + compatible = "bosch,bno055"; + reg = <0x29>; + }; + }; + }; + + fragment@32 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + sht4x: sht4x@44 { + compatible = "sensirion,sht4x"; + reg = <0x44>; + status = "okay"; + }; + }; + }; + + fragment@33 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + bmp380: bmp380@76 { + compatible = "bosch,bmp380"; + reg = <0x76>; + status = "okay"; + }; + }; + }; + + fragment@34 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + adt7410: adt7410@48 { + compatible = "adi,adt7410", "adi,adt7420"; + reg = <0x48>; + status = "okay"; + }; + }; + }; + + fragment@35 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + ina238: ina238@48 { + compatible = "ti,ina238"; + #address-cells = <1>; + #size-cells = <0>; + reg = <0x40>; + /* uOhms, uint32_t */ + shunt-resistor = <1000>; + /* 1 or 4, (±40.96 mV or ±163.84 mV) */ + ti,shunt-gain = <1>; + }; + }; + }; + + fragment@36 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + shtc3: shtc3@70 { + compatible = "sensirion,shtc3"; + reg = <0x70>; + status = "okay"; + }; + }; + }; + + fragment@37 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + hts221: hts221@5f { + compatible = "st,hts221-humid", "st,hts221"; + reg = <0x5f>; + interrupt-parent = <&gpio>; + interrupts = <4 IRQ_TYPE_EDGE_RISING>; + pinctrl-0 = <&int_pins>; + pinctrl-names = "default"; + }; + }; + }; + + fragment@38 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + veml6075: veml6075@10 { + compatible = "vishay,veml6075"; + reg = <0x10>; + }; + }; + }; + + fragment@39 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + hdc3020: hdc3020@44 { + compatible = "ti,hdc3020"; + reg = <0x44>; + interrupt-parent = <&gpio>; + interrupts = <4 IRQ_TYPE_EDGE_RISING>; + pinctrl-0 = <&int_pins>; + pinctrl-names = "default"; + }; + }; + }; + + fragment@40 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + as73211: as73211@74 { + compatible = "ams,as73211"; + reg = <0x74>; + interrupt-parent = <&gpio>; + interrupts = <4 IRQ_TYPE_EDGE_RISING>; + pinctrl-0 = <&int_pins>; + pinctrl-names = "default"; + }; + }; + }; + + fragment@41 { + target = <&i2cbus>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + as7331: as7331@74 { + compatible = "ams,as7331"; + reg = <0x74>; + interrupt-parent = <&gpio>; + interrupts = <4 IRQ_TYPE_EDGE_RISING>; + pinctrl-0 = <&int_pins>; + pinctrl-names = "default"; + }; + }; + }; + + fragment@99 { + target = <&gpio>; + __dormant__ { + int_pins: int_pins@4 { + brcm,pins = <4>; + brcm,function = <0>; /* in */ + brcm,pull = <0>; /* none */ + }; + }; + }; + + __overrides__ { + bme280 = <0>,"+0"; + bmp085 = <0>,"+1"; + bmp180 = <0>,"+2"; + bmp280 = <0>,"+3"; + bmp380 = <0>,"+33"; + htu21 = <0>,"+4"; + lm75 = <0>,"+5"; + lm75addr = <&lm75>,"reg:0"; + si7020 = <0>,"+6"; + tmp102 = <0>,"+7"; + hdc100x = <0>,"+8"; + tsl4531 = <0>,"+9"; + veml6070 = <0>,"+10"; + sht3x = <0>,"+11"; + ds1621 = <0>,"+12"; + max17040 = <0>,"+13"; + bme680 = <0>,"+14"; + sps30 = <0>,"+15"; + sgp30 = <0>,"+16"; + ccs811 = <0>, "+17"; + bh1750 = <0>, "+18"; + max30102 = <0>,"+19+99"; + aht10 = <0>,"+20"; + mcp980x = <0>,"+21"; + jc42 = <0>,"+22"; + ms5637 = <0>,"+23"; + ms5803 = <0>,"+24"; + ms5805 = <0>,"+25"; + ms5837 = <0>,"+26"; + ms8607 = <0>,"+27"; + mpu6050 = <0>,"+28+99"; + mpu9250 = <0>,"+29+99"; + bno055 = <0>,"+31"; + sht4x = <0>,"+32"; + adt7410 = <0>,"+34"; + ina238 = <0>,"+35"; + shtc3 = <0>,"+36"; + hts221 = <0>,"+37+99"; + veml6075 = <0>,"+38"; + hdc3020 = <0>,"+39+99"; + as73211 = <0>,"+40+99"; + as7331 = <0>,"+41+99"; + + addr = <&bme280>,"reg:0", <&bmp280>,"reg:0", <&tmp102>,"reg:0", + <&lm75>,"reg:0", <&hdc100x>,"reg:0", <&sht3x>,"reg:0", + <&ds1621>,"reg:0", <&bme680>,"reg:0", <&ccs811>,"reg:0", + <&bh1750>,"reg:0", <&mcp980x>,"reg:0", <&jc42>,"reg:0", + <&ms5637>,"reg:0", <&ms5803>,"reg:0", <&ms5805>,"reg:0", + <&ms5837>,"reg:0", <&ms8607>,"reg:0", + <&mpu6050>,"reg:0", <&mpu9250>,"reg:0", + <&bno055>,"reg:0", <&sht4x>,"reg:0", + <&bmp380>,"reg:0", <&adt7410>,"reg:0", <&ina238>,"reg:0", + <&hdc3020>,"reg:0", <&as73211>,"reg:0", + <&as7331>,"reg:0"; + int_pin = <&int_pins>, "brcm,pins:0", + <&int_pins>, "reg:0", + <&max30102>, "interrupts:0", + <&mpu6050>, "interrupts:0", + <&mpu9250>, "interrupts:0", + <&hts221>, "interrupts:0", + <&hdc3020>, "interrupts:0", + <&as73211>, "interrupts:0", + <&as7331>, "interrupts:0"; + no_timeout = <&jc42>, "smbus-timeout-disable?"; + reset_pin = <&bno055>,"reset-gpios:4", <0>,"+30"; + shunt_resistor = <&ina238>,"shunt-resistor:0"; + gain = <&ina238>,"ti,shunt-gain:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/i2c-sensor-overlay.dts b/arch/arm/boot/dts/overlays/i2c-sensor-overlay.dts new file mode 100755 index 00000000000000..29522e666908ba --- /dev/null +++ b/arch/arm/boot/dts/overlays/i2c-sensor-overlay.dts @@ -0,0 +1,6 @@ +// Definitions for I2C based sensors using the Industrial IO or HWMON interface. +/dts-v1/; +/plugin/; + +#include "i2c-sensor-common.dtsi" +#include "i2c-buses.dtsi" diff --git a/arch/arm/boot/dts/overlays/i2c0-overlay.dts b/arch/arm/boot/dts/overlays/i2c0-overlay.dts new file mode 100644 index 00000000000000..46bf1bf2dc5cb6 --- /dev/null +++ b/arch/arm/boot/dts/overlays/i2c0-overlay.dts @@ -0,0 +1,83 @@ +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2c0if>; + __overlay__ { + status = "okay"; + pinctrl-names = "default"; + pinctrl-0 = <&i2c0_pins>; + }; + }; + + fragment@1 { + target = <&i2c0_pins>; + pins1: __overlay__ { + brcm,pins = <0 1>; + brcm,function = <4>; /* alt0 */ + }; + }; + + fragment@2 { + target = <&i2c0_pins>; + pins2: __dormant__ { + brcm,pins = <28 29>; + brcm,function = <4>; /* alt0 */ + }; + }; + + fragment@3 { + target = <&i2c0_pins>; + pins3: __dormant__ { + brcm,pins = <44 45>; + brcm,function = <5>; /* alt1 */ + }; + }; + + fragment@4 { + target = <&i2c0_pins>; + pins4: __dormant__ { + brcm,pins = <46 47>; + brcm,function = <4>; /* alt0 */ + }; + }; + + fragment@5 { + target = <&i2c0>; + __dormant__ { + compatible = "brcm,bcm2708-i2c"; + }; + }; + + fragment@6 { + target = <&i2c0mux>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@7 { + target-path = "/aliases"; + __overlay__ { + i2c0 = "/soc/i2c@7e205000"; + }; + }; + + fragment@8 { + target-path = "/__symbols__"; + __overlay__ { + i2c0 = "/soc/i2c@7e205000"; + }; + }; + + __overrides__ { + pins_0_1 = <0>,"+1-2-3-4"; + pins_28_29 = <0>,"-1+2-3-4"; + pins_44_45 = <0>,"-1-2+3-4"; + pins_46_47 = <0>,"-1-2-3+4"; + combine = <0>, "!5"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/i2c0-pi5-overlay.dts b/arch/arm/boot/dts/overlays/i2c0-pi5-overlay.dts new file mode 100644 index 00000000000000..152794822552f6 --- /dev/null +++ b/arch/arm/boot/dts/overlays/i2c0-pi5-overlay.dts @@ -0,0 +1,34 @@ +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2712"; + + fragment@0 { + target = <&i2c0>; + frag0: __overlay__ { + status = "okay"; + clock-frequency = <100000>; + }; + }; + + fragment@1 { + target = <&frag0>; + __overlay__ { + pinctrl-0 = <&rp1_i2c0_0_1>; + }; + }; + + fragment@2 { + target = <&frag0>; + __dormant__ { + pinctrl-0 = <&rp1_i2c0_8_9>; + }; + }; + + __overrides__ { + pins_0_1 = <0>,"+1-2"; + pins_8_9 = <0>,"-1+2"; + baudrate = <&frag0>, "clock-frequency:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/i2c1-overlay.dts b/arch/arm/boot/dts/overlays/i2c1-overlay.dts new file mode 100644 index 00000000000000..addaed73e66566 --- /dev/null +++ b/arch/arm/boot/dts/overlays/i2c1-overlay.dts @@ -0,0 +1,44 @@ +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2c1>; + __overlay__ { + status = "okay"; + pinctrl-names = "default"; + pinctrl-0 = <&i2c1_pins>; + }; + }; + + fragment@1 { + target = <&i2c1_pins>; + pins1: __overlay__ { + brcm,pins = <2 3>; + brcm,function = <4>; /* alt 0 */ + }; + }; + + fragment@2 { + target = <&i2c1_pins>; + pins2: __dormant__ { + brcm,pins = <44 45>; + brcm,function = <6>; /* alt 2 */ + }; + }; + + fragment@3 { + target = <&i2c1>; + __dormant__ { + compatible = "brcm,bcm2708-i2c"; + }; + }; + + __overrides__ { + pins_2_3 = <0>,"=1!2"; + pins_44_45 = <0>,"!1=2"; + combine = <0>, "!3"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/i2c1-pi5-overlay.dts b/arch/arm/boot/dts/overlays/i2c1-pi5-overlay.dts new file mode 100644 index 00000000000000..719966ceb59af4 --- /dev/null +++ b/arch/arm/boot/dts/overlays/i2c1-pi5-overlay.dts @@ -0,0 +1,34 @@ +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2712"; + + fragment@0 { + target = <&i2c1>; + frag0: __overlay__ { + status = "okay"; + clock-frequency = <100000>; + }; + }; + + fragment@1 { + target = <&frag0>; + __overlay__ { + pinctrl-0 = <&rp1_i2c1_2_3>; + }; + }; + + fragment@2 { + target = <&frag0>; + __dormant__ { + pinctrl-0 = <&rp1_i2c1_10_11>; + }; + }; + + __overrides__ { + pins_2_3 = <0>,"+1-2"; + pins_10_11 = <0>,"-1+2"; + baudrate = <&frag0>, "clock-frequency:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/i2c2-pi5-overlay.dts b/arch/arm/boot/dts/overlays/i2c2-pi5-overlay.dts new file mode 100644 index 00000000000000..324d344052b878 --- /dev/null +++ b/arch/arm/boot/dts/overlays/i2c2-pi5-overlay.dts @@ -0,0 +1,21 @@ +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2712"; + + fragment@0 { + target = <&i2c2>; + frag0: __overlay__ { + status = "okay"; + clock-frequency = <100000>; + pinctrl-0 = <&rp1_i2c2_4_5>; + }; + }; + + __overrides__ { + pins_4_5 = <&frag0>,"pinctrl-0:0=", <&rp1_i2c2_4_5>; + pins_12_13 = <&frag0>,"pinctrl-0:0=", <&rp1_i2c2_12_13>; + baudrate = <&frag0>, "clock-frequency:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/i2c3-overlay.dts b/arch/arm/boot/dts/overlays/i2c3-overlay.dts new file mode 100644 index 00000000000000..663d4f060ee8eb --- /dev/null +++ b/arch/arm/boot/dts/overlays/i2c3-overlay.dts @@ -0,0 +1,34 @@ +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2711"; + + fragment@0 { + target = <&i2c3>; + frag0: __overlay__ { + status = "okay"; + clock-frequency = <100000>; + }; + }; + + fragment@1 { + target = <&i2c3_pins>; + __dormant__ { + brcm,pins = <2 3>; + }; + }; + + fragment@2 { + target = <&i2c3_pins>; + __overlay__ { + brcm,pins = <4 5>; + }; + }; + + __overrides__ { + pins_2_3 = <0>,"=1!2"; + pins_4_5 = <0>,"!1=2"; + baudrate = <&frag0>, "clock-frequency:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/i2c3-pi5-overlay.dts b/arch/arm/boot/dts/overlays/i2c3-pi5-overlay.dts new file mode 100644 index 00000000000000..cbd1f9ff650d7e --- /dev/null +++ b/arch/arm/boot/dts/overlays/i2c3-pi5-overlay.dts @@ -0,0 +1,22 @@ +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2712"; + + fragment@0 { + target = <&i2c3>; + frag0: __overlay__ { + status = "okay"; + clock-frequency = <100000>; + pinctrl-0 = <&rp1_i2c3_6_7>; + }; + }; + + __overrides__ { + pins_6_7 = <&frag0>,"pinctrl-0:0=", <&rp1_i2c3_6_7>; + pins_14_15 = <&frag0>,"pinctrl-0:0=", <&rp1_i2c3_14_15>; + pins_22_23 = <&frag0>,"pinctrl-0:0=", <&rp1_i2c3_22_23>; + baudrate = <&frag0>, "clock-frequency:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/i2c4-overlay.dts b/arch/arm/boot/dts/overlays/i2c4-overlay.dts new file mode 100644 index 00000000000000..495de00f7aa186 --- /dev/null +++ b/arch/arm/boot/dts/overlays/i2c4-overlay.dts @@ -0,0 +1,34 @@ +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2711"; + + fragment@0 { + target = <&i2c4>; + frag0: __overlay__ { + status = "okay"; + clock-frequency = <100000>; + }; + }; + + fragment@1 { + target = <&i2c4_pins>; + __dormant__ { + brcm,pins = <6 7>; + }; + }; + + fragment@2 { + target = <&i2c4_pins>; + __overlay__ { + brcm,pins = <8 9>; + }; + }; + + __overrides__ { + pins_6_7 = <0>,"=1!2"; + pins_8_9 = <0>,"!1=2"; + baudrate = <&frag0>, "clock-frequency:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/i2c5-overlay.dts b/arch/arm/boot/dts/overlays/i2c5-overlay.dts new file mode 100644 index 00000000000000..d498ebc72de6f8 --- /dev/null +++ b/arch/arm/boot/dts/overlays/i2c5-overlay.dts @@ -0,0 +1,34 @@ +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2711"; + + fragment@0 { + target = <&i2c5>; + frag0: __overlay__ { + status = "okay"; + clock-frequency = <100000>; + }; + }; + + fragment@1 { + target = <&i2c5_pins>; + __dormant__ { + brcm,pins = <10 11>; + }; + }; + + fragment@2 { + target = <&i2c5_pins>; + __overlay__ { + brcm,pins = <12 13>; + }; + }; + + __overrides__ { + pins_10_11 = <0>,"=1!2"; + pins_12_13 = <0>,"!1=2"; + baudrate = <&frag0>, "clock-frequency:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/i2c6-overlay.dts b/arch/arm/boot/dts/overlays/i2c6-overlay.dts new file mode 100644 index 00000000000000..4d26178a73ca7c --- /dev/null +++ b/arch/arm/boot/dts/overlays/i2c6-overlay.dts @@ -0,0 +1,34 @@ +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2711"; + + fragment@0 { + target = <&i2c6>; + frag0: __overlay__ { + status = "okay"; + clock-frequency = <100000>; + }; + }; + + fragment@1 { + target = <&i2c6_pins>; + __dormant__ { + brcm,pins = <0 1>; + }; + }; + + fragment@2 { + target = <&i2c6_pins>; + __overlay__ { + brcm,pins = <22 23>; + }; + }; + + __overrides__ { + pins_0_1 = <0>,"=1!2"; + pins_22_23 = <0>,"!1=2"; + baudrate = <&frag0>, "clock-frequency:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/i2s-dac-overlay.dts b/arch/arm/boot/dts/overlays/i2s-dac-overlay.dts new file mode 100644 index 00000000000000..1d8874a1886064 --- /dev/null +++ b/arch/arm/boot/dts/overlays/i2s-dac-overlay.dts @@ -0,0 +1,34 @@ +// Definitions for RPi DAC +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_producer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target-path = "/"; + __overlay__ { + pcm1794a-codec { + #sound-dai-cells = <0>; + compatible = "ti,pcm1794a"; + status = "okay"; + }; + }; + }; + + fragment@2 { + target = <&sound>; + __overlay__ { + compatible = "rpi,rpi-dac"; + i2s-controller = <&i2s_clk_producer>; + status = "okay"; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/i2s-gpio28-31-overlay.dts b/arch/arm/boot/dts/overlays/i2s-gpio28-31-overlay.dts new file mode 100644 index 00000000000000..cf43094c6ff456 --- /dev/null +++ b/arch/arm/boot/dts/overlays/i2s-gpio28-31-overlay.dts @@ -0,0 +1,18 @@ +/* + * Device tree overlay to move i2s to gpio 28 to 31 on CM + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_pins>; + __overlay__ { + brcm,pins = <28 29 30 31>; + brcm,function = <6>; /* alt2 */ + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/i2s-master-dac-overlay.dts b/arch/arm/boot/dts/overlays/i2s-master-dac-overlay.dts new file mode 100644 index 00000000000000..8b46067858d72e --- /dev/null +++ b/arch/arm/boot/dts/overlays/i2s-master-dac-overlay.dts @@ -0,0 +1,50 @@ +// Definitions for a generic I2S DAC that acts as clock master on the bus. +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_consumer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target-path = "/"; + __overlay__ { + codec_bare: codec_bare { + compatible = "linux,spdif-dit"; + #sound-dai-cells = <0>; + status = "okay"; + }; + }; + }; + + fragment@2 { + target = <&sound>; + __overlay__ { + compatible = "simple-audio-card"; + i2s-controller = <&i2s_clk_consumer>; + status = "okay"; + + simple-audio-card,name = "i2s-master-dac"; + simple-audio-card,format = "i2s"; + + simple-audio-card,bitclock-master = <&snd_codec>; + simple-audio-card,frame-master = <&snd_codec>; + + simple-audio-card,cpu { + sound-dai = <&i2s_clk_consumer>; + dai-tdm-slot-num = <2>; + dai-tdm-slot-width = <32>; + }; + + snd_codec: simple-audio-card,codec { + sound-dai = <&codec_bare>; + }; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/ilitek251x-overlay.dts b/arch/arm/boot/dts/overlays/ilitek251x-overlay.dts new file mode 100644 index 00000000000000..226d490ce092fe --- /dev/null +++ b/arch/arm/boot/dts/overlays/ilitek251x-overlay.dts @@ -0,0 +1,47 @@ +// Device tree overlay for I2C connected Ilitek multiple touch controller +/dts-v1/; +/plugin/; + + / { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&gpio>; + __overlay__ { + ili251x_pins: ili251x_pins { + brcm,pins = <4>; // interrupt + brcm,function = <0>; // in + brcm,pull = <2>; // pull-up // + }; + }; + }; + + frag1: fragment@1 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + ili251x: ili251x@41 { + compatible = "ilitek,ili251x"; + reg = <0x41>; + pinctrl-names = "default"; + pinctrl-0 = <&ili251x_pins>; + interrupt-parent = <&gpio>; + interrupts = <4 8>; // high-to-low edge triggered + touchscreen-size-x = <16384>; + touchscreen-size-y = <9600>; + }; + }; + }; + + __overrides__ { + interrupt = <&ili251x_pins>,"brcm,pins:0", + <&ili251x>,"interrupts:0"; + sizex = <&ili251x>,"touchscreen-size-x:0"; + sizey = <&ili251x>,"touchscreen-size-y:0"; + i2c-path = <&frag1>, "target?=0", + <&frag1>, "target-path"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/imx219-overlay.dts b/arch/arm/boot/dts/overlays/imx219-overlay.dts new file mode 100644 index 00000000000000..c855b14ff22045 --- /dev/null +++ b/arch/arm/boot/dts/overlays/imx219-overlay.dts @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Definitions for IMX219 camera module on VC I2C bus +/dts-v1/; +/plugin/; + +#include <dt-bindings/gpio/gpio.h> + +/{ + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2c0if>; + __overlay__ { + status = "okay"; + }; + }; + + clk_frag: fragment@1 { + target = <&cam1_clk>; + __overlay__ { + status = "okay"; + clock-frequency = <24000000>; + }; + }; + + fragment@2 { + target = <&i2c0mux>; + __overlay__ { + status = "okay"; + }; + }; + + i2c_frag: fragment@100 { + target = <&i2c_csi_dsi>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + #include "imx219.dtsi" + + vcm: ad5398@c { + compatible = "adi,ad5398"; + reg = <0x0c>; + status = "disabled"; + VANA-supply = <&cam1_reg>; + }; + }; + }; + + csi_frag: fragment@101 { + target = <&csi1>; + csi: __overlay__ { + status = "okay"; + + port { + csi_ep: endpoint { + remote-endpoint = <&cam_endpoint>; + clock-lanes = <0>; + data-lanes = <1 2>; + clock-noncontinuous; + }; + }; + }; + }; + + fragment@102 { + target = <&csi1>; + __dormant__ { + compatible = "brcm,bcm2835-unicam-legacy"; + }; + }; + + fragment@201 { + target = <&csi_ep>; + __dormant__ { + data-lanes = <1 2 3 4>; + }; + }; + + fragment@202 { + target = <&cam_endpoint>; + __dormant__ { + data-lanes = <1 2 3 4>; + link-frequencies = + /bits/ 64 <363000000>; + }; + }; + + __overrides__ { + rotation = <&cam_node>,"rotation:0"; + orientation = <&cam_node>,"orientation:0"; + media-controller = <0>,"!102"; + cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>, + <&csi_frag>, "target:0=",<&csi0>, + <&clk_frag>, "target:0=",<&cam0_clk>, + <&cam_node>, "clocks:0=",<&cam0_clk>, + <&cam_node>, "VANA-supply:0=",<&cam0_reg>, + <&vcm>, "VANA-supply:0=", <&cam0_reg>; + vcm = <&vcm>, "status=okay", + <&cam_node>,"lens-focus:0=", <&vcm>; + 4lane = <0>, "+201+202"; + }; +}; + +&cam_node { + status = "okay"; +}; + +&cam_endpoint { + remote-endpoint = <&csi_ep>; +}; diff --git a/arch/arm/boot/dts/overlays/imx219.dtsi b/arch/arm/boot/dts/overlays/imx219.dtsi new file mode 100644 index 00000000000000..fa870f77ef074f --- /dev/null +++ b/arch/arm/boot/dts/overlays/imx219.dtsi @@ -0,0 +1,27 @@ +// Fragment that configures an imx219 + +cam_node: imx219@10 { + compatible = "sony,imx219"; + reg = <0x10>; + status = "disabled"; + + clocks = <&cam1_clk>; + clock-names = "xclk"; + + VANA-supply = <&cam1_reg>; /* 2.8v */ + VDIG-supply = <&cam_dummy_reg>; /* 1.8v */ + VDDL-supply = <&cam_dummy_reg>; /* 1.2v */ + + rotation = <180>; + orientation = <2>; + + port { + cam_endpoint: endpoint { + clock-lanes = <0>; + data-lanes = <1 2>; + clock-noncontinuous; + link-frequencies = + /bits/ 64 <456000000>; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/imx258-overlay.dts b/arch/arm/boot/dts/overlays/imx258-overlay.dts new file mode 100644 index 00000000000000..bf06d681d5dc58 --- /dev/null +++ b/arch/arm/boot/dts/overlays/imx258-overlay.dts @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Definitions for IMX258 camera module on VC I2C bus +/dts-v1/; +/plugin/; + +#include <dt-bindings/gpio/gpio.h> + +/{ + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2c0if>; + __overlay__ { + status = "okay"; + }; + }; + + clk_frag: fragment@1 { + target = <&cam1_clk>; + cam_clk: __overlay__ { + clock-frequency = <24000000>; + status = "okay"; + }; + }; + + fragment@2 { + target = <&i2c0mux>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@11 { + target = <&cam_endpoint>; + __overlay__ { + data-lanes = <1 2>; + link-frequencies = /bits/ 64 <633600000 + 320000000>; + }; + }; + + fragment@12 { + target = <&cam_endpoint>; + __dormant__ { + data-lanes = <1 2 3 4>; + link-frequencies = + /bits/ 64 <633600000 320000000>; + }; + }; + + fragment@13 { + target = <&csi_ep>; + __overlay__ { + data-lanes = <1 2>; + }; + }; + + fragment@14 { + target = <&csi_ep>; + __dormant__ { + data-lanes = <1 2 3 4>; + }; + }; + + csi_frag: fragment@101 { + target = <&csi1>; + csi: __overlay__ { + status = "okay"; + + port { + csi_ep: endpoint { + remote-endpoint = <&cam_endpoint>; + clock-lanes = <0>; + clock-noncontinuous; + }; + }; + }; + }; + + reg_frag: fragment@5 { + target = <&cam1_reg>; + cam_reg: __overlay__ { + regulator-name = "imx258_vana"; + startup-delay-us = <300000>; + regulator-min-microvolt = <2700000>; + regulator-max-microvolt = <2700000>; + }; + }; + + i2c_frag: fragment@100 { + target = <&i2c_csi_dsi>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + #include "imx258.dtsi" + + vcm: ad5398@c { + compatible = "adi,ad5398"; + reg = <0x0c>; + status = "disabled"; + VANA-supply = <&cam1_reg>; + }; + }; + }; + + fragment@102 { + target = <&csi1>; + __dormant__ { + compatible = "brcm,bcm2835-unicam-legacy"; + }; + }; + + __overrides__ { + rotation = <&cam_node>,"rotation:0"; + orientation = <&cam_node>,"orientation:0"; + media-controller = <0>,"!102"; + cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>, + <&csi_frag>, "target:0=",<&csi0>, + <&clk_frag>, "target:0=",<&cam0_clk>, + <®_frag>, "target:0=",<&cam0_reg>, + <&cam_node>, "clocks:0=",<&cam0_clk>, + <&cam_node>, "vana-supply:0=",<&cam0_reg>; + vcm = <&vcm>, "status=okay", + <&cam_node>,"lens-focus:0=", <&vcm>; + 4lane = <0>, "-11+12-13+14"; + }; +}; + +&cam_node { + status = "okay"; +}; + +&cam_endpoint { + remote-endpoint = <&csi_ep>; +}; diff --git a/arch/arm/boot/dts/overlays/imx258.dtsi b/arch/arm/boot/dts/overlays/imx258.dtsi new file mode 100644 index 00000000000000..cca81e1aa8b343 --- /dev/null +++ b/arch/arm/boot/dts/overlays/imx258.dtsi @@ -0,0 +1,27 @@ +// Fragment that configures a Sony IMX258 + +cam_node: imx258@10 { + compatible = "sony,imx258"; + reg = <0x10>; + status = "disabled"; + + clocks = <&cam1_clk>; + clock-names = "xclk"; + + vana-supply = <&cam1_reg>; /* 2.8v */ + vdig-supply = <&cam_dummy_reg>; /* 1.05v */ + vif-supply = <&cam_dummy_reg>; /* 1.8v */ + + rotation = <180>; + orientation = <2>; + + port { + cam_endpoint: endpoint { + clock-lanes = <0>; + clock-noncontinuous; + link-frequencies = + /bits/ 64 <633600000 + 320000000>; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/imx290-overlay.dts b/arch/arm/boot/dts/overlays/imx290-overlay.dts new file mode 100644 index 00000000000000..3de3c3910d9076 --- /dev/null +++ b/arch/arm/boot/dts/overlays/imx290-overlay.dts @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Definitions for IMX290 camera module on VC I2C bus +/dts-v1/; +/plugin/; + +#include <dt-bindings/gpio/gpio.h> +#include "imx290_327-overlay.dtsi" + +/{ + compatible = "brcm,bcm2835"; + + // Fragment numbers deliberately high to avoid conflicts with the + // included imx290_327 overlay file. + + fragment@101 { + target = <&cam_node>; + __overlay__ { + compatible = "sony,imx290lqr"; + }; + }; + + fragment@102 { + target = <&cam_node>; + __dormant__ { + compatible = "sony,imx290llr"; + }; + }; + + __overrides__ { + mono = <0>, "-101+102"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/imx290_327-overlay.dtsi b/arch/arm/boot/dts/overlays/imx290_327-overlay.dtsi new file mode 100644 index 00000000000000..55f047e315bcde --- /dev/null +++ b/arch/arm/boot/dts/overlays/imx290_327-overlay.dtsi @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Partial definitions for IMX290 or IMX327 camera module on VC I2C bus +// The compatible string should be set in an overlay that then includes this one + +#include <dt-bindings/gpio/gpio.h> + +/{ + compatible = "brcm,bcm2835"; + + i2c_frag: fragment@0 { + target = <&i2c_csi_dsi>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + #include "imx290_327.dtsi" + }; + }; + + csi_frag: fragment@1 { + target = <&csi1>; + csi: __overlay__ { + status = "okay"; + + port { + csi_ep: endpoint { + remote-endpoint = <&cam_endpoint>; + }; + }; + }; + }; + + fragment@2 { + target = <&i2c0if>; + __overlay__ { + status = "okay"; + }; + }; + + clk_frag: fragment@3 { + target = <&cam1_clk>; + cam_clk: __overlay__ { + status = "okay"; + clock-frequency = <37125000>; + }; + }; + + fragment@4 { + target = <&i2c0mux>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@6 { + target = <&cam_endpoint>; + __overlay__ { + data-lanes = <1 2>; + link-frequencies = + /bits/ 64 <445500000 297000000>; + }; + }; + + fragment@7 { + target = <&cam_endpoint>; + __dormant__ { + data-lanes = <1 2 3 4>; + link-frequencies = + /bits/ 64 <222750000 148500000>; + }; + }; + + fragment@8 { + target = <&csi_ep>; + __overlay__ { + data-lanes = <1 2>; + }; + }; + + fragment@9 { + target = <&csi_ep>; + __dormant__ { + data-lanes = <1 2 3 4>; + }; + }; + + fragment@10 { + target = <&csi1>; + __dormant__ { + compatible = "brcm,bcm2835-unicam-legacy"; + }; + }; + + __overrides__ { + 4lane = <0>, "-6+7-8+9"; + clock-frequency = <&cam_clk>,"clock-frequency:0", + <&cam_node>,"clock-frequency:0"; + rotation = <&cam_node>,"rotation:0"; + orientation = <&cam_node>,"orientation:0"; + media-controller = <0>,"!10"; + cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>, + <&csi_frag>, "target:0=",<&csi0>, + <&clk_frag>, "target:0=",<&cam0_clk>, + <&cam_node>, "clocks:0=",<&cam0_clk>, + <&cam_node>, "vdda-supply:0=",<&cam0_reg>; + }; +}; + +&cam_node { + status = "okay"; +}; + +&cam_endpoint { + remote-endpoint = <&csi_ep>; +}; diff --git a/arch/arm/boot/dts/overlays/imx290_327.dtsi b/arch/arm/boot/dts/overlays/imx290_327.dtsi new file mode 100644 index 00000000000000..14d1f0b95bb34b --- /dev/null +++ b/arch/arm/boot/dts/overlays/imx290_327.dtsi @@ -0,0 +1,24 @@ +// Fragment to configure and IMX290 / IMX327 / IMX462 image sensor + +cam_node: imx290@1a { + compatible = "sony,imx290lqr"; + reg = <0x1a>; + status = "disabled"; + + clocks = <&cam1_clk>; + clock-names = "xclk"; + clock-frequency = <37125000>; + + rotation = <0>; + orientation = <2>; + + vdda-supply = <&cam1_reg>; /* 2.8v */ + vdddo-supply = <&cam_dummy_reg>; /* 1.8v */ + vddd-supply = <&cam_dummy_reg>; /* 1.5v */ + + port { + cam_endpoint: endpoint { + clock-lanes = <0>; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/imx296-overlay.dts b/arch/arm/boot/dts/overlays/imx296-overlay.dts new file mode 100644 index 00000000000000..018beafdfbd74b --- /dev/null +++ b/arch/arm/boot/dts/overlays/imx296-overlay.dts @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Definitions for IMX296 camera module on VC I2C bus +/dts-v1/; +/plugin/; + +#include <dt-bindings/gpio/gpio.h> + +/{ + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2c0if>; + __overlay__ { + status = "okay"; + }; + }; + + clk_frag: fragment@1 { + target = <&cam1_clk>; + clk_over: __overlay__ { + status = "okay"; + clock-frequency = <54000000>; + }; + }; + + fragment@2 { + target = <&i2c0mux>; + __overlay__ { + status = "okay"; + }; + }; + + reg_frag: fragment@5 { + target = <&cam1_reg>; + cam_reg: __overlay__ { + startup-delay-us = <500000>; + }; + }; + + reg_alwayson_frag: fragment@99 { + target = <&cam1_reg>; + __dormant__ { + regulator-always-on; + }; + }; + + i2c_frag: fragment@100 { + target = <&i2c_csi_dsi>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + imx296: imx296@1a { + compatible = "sony,imx296"; + reg = <0x1a>; + status = "okay"; + + clocks = <&cam1_clk>; + clock-names = "inck"; + + avdd-supply = <&cam1_reg>; /* 3.3v */ + dvdd-supply = <&cam_dummy_reg>; /* 1.8v */ + ovdd-supply = <&cam_dummy_reg>; /* 1.2v */ + + rotation = <180>; + orientation = <2>; + + port { + imx296_0: endpoint { + remote-endpoint = <&csi_ep>; + clock-lanes = <0>; + data-lanes = <1>; + clock-noncontinuous; + link-frequencies = + /bits/ 64 <594000000>; + }; + }; + }; + }; + }; + + csi_frag: fragment@101 { + target = <&csi1>; + csi: __overlay__ { + status = "okay"; + + port { + csi_ep: endpoint { + remote-endpoint = <&imx296_0>; + clock-lanes = <0>; + data-lanes = <1>; + clock-noncontinuous; + }; + }; + }; + }; + + fragment@102 { + target = <&csi1>; + __dormant__ { + compatible = "brcm,bcm2835-unicam-legacy"; + }; + }; + + __overrides__ { + rotation = <&imx296>,"rotation:0"; + orientation = <&imx296>,"orientation:0"; + media-controller = <0>,"!102"; + cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>, + <&csi_frag>, "target:0=",<&csi0>, + <&clk_frag>, "target:0=",<&cam0_clk>, + <®_frag>, "target:0=",<&cam0_reg>, + <®_alwayson_frag>, "target:0=",<&cam0_reg>, + <&imx296>, "clocks:0=",<&cam0_clk>, + <&imx296>, "avdd-supply:0=",<&cam0_reg>; + clock-frequency = <&clk_over>, "clock-frequency:0"; + always-on = <0>, "+99"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/imx327-overlay.dts b/arch/arm/boot/dts/overlays/imx327-overlay.dts new file mode 100644 index 00000000000000..0776954bdba266 --- /dev/null +++ b/arch/arm/boot/dts/overlays/imx327-overlay.dts @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Definitions for IMX327 camera module on VC I2C bus +/dts-v1/; +/plugin/; + +#include <dt-bindings/gpio/gpio.h> +#include "imx290_327-overlay.dtsi" + +/{ + compatible = "brcm,bcm2835"; + + // Fragment numbers deliberately high to avoid conflicts with the + // included imx290_327 overlay file. + + fragment@101 { + target = <&cam_node>; + __overlay__ { + compatible = "sony,imx327lqr"; + }; + }; + + fragment@102 { + target = <&cam_node>; + __dormant__ { + // No mono IMX327 is currently defined. Use IMX290. + compatible = "sony,imx290llr"; + }; + }; + + __overrides__ { + mono = <0>, "-101+102"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/imx378-overlay.dts b/arch/arm/boot/dts/overlays/imx378-overlay.dts new file mode 100644 index 00000000000000..4a5072489a3441 --- /dev/null +++ b/arch/arm/boot/dts/overlays/imx378-overlay.dts @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Definitions for IMX378 camera module on VC I2C bus +/dts-v1/; +/plugin/; + +#include "imx477_378-overlay.dtsi" + +&cam_node { + compatible = "sony,imx378"; +}; + +/{ + __overrides__ { + sync-sink = <&cam_node>,"trigger-mode:0=2"; + sync-source = <&cam_node>,"trigger-mode:0=1"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/imx415-overlay.dts b/arch/arm/boot/dts/overlays/imx415-overlay.dts new file mode 100644 index 00000000000000..d420b46abc8cf6 --- /dev/null +++ b/arch/arm/boot/dts/overlays/imx415-overlay.dts @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Definitions for IMX415 camera module on VC I2C bus +/dts-v1/; +/plugin/; + +#include <dt-bindings/gpio/gpio.h> + +/{ + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2c0if>; + __overlay__ { + status = "okay"; + }; + }; + + clk_frag: fragment@1 { + target = <&cam1_clk>; + cam_clk: __overlay__ { + status = "okay"; + clock-frequency = <24000000>; + }; + }; + + fragment@2 { + target = <&i2c0mux>; + __overlay__ { + status = "okay"; + }; + }; + + reg_frag: fragment@3 { + target = <&cam1_reg>; + cam_reg: __overlay__ { + startup-delay-us = <100000>; + }; + }; + + i2c_frag: fragment@100 { + target = <&i2c_csi_dsi>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + #include "imx415.dtsi" + + vcm: ad5398@c { + compatible = "adi,ad5398"; + reg = <0x0c>; + status = "disabled"; + VANA-supply = <&cam1_reg>; + }; + }; + }; + + csi_frag: fragment@101 { + target = <&csi1>; + csi: __overlay__ { + status = "okay"; + brcm,media-controller; + + port { + csi_ep: endpoint { + remote-endpoint = <&cam_endpoint>; + clock-lanes = <0>; + data-lanes = <1 2>; + clock-noncontinuous; + }; + }; + }; + }; + + fragment@201 { + target = <&cam_endpoint>; + __dormant__ { + data-lanes = <1 2 3 4>; + }; + }; + + fragment@202 { + target = <&csi_ep>; + __dormant__ { + data-lanes = <1 2 3 4>; + }; + }; + + + __overrides__ { + addr = <&cam_node>, "reg:0"; + rotation = <&cam_node>,"rotation:0"; + orientation = <&cam_node>,"orientation:0"; + media-controller = <&csi>,"brcm,media-controller?"; + cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>, + <&csi_frag>, "target:0=",<&csi0>, + <®_frag>, "target:0=",<&cam0_reg>, + <&clk_frag>, "target:0=",<&cam0_clk>, + <&cam_node>, "clocks:0=",<&cam0_clk>, + <&cam_node>, "avdd-supply:0=",<&cam0_reg>, + <&vcm>, "VANA-supply:0=", <&cam0_reg>; + vcm = <&vcm>, "status=okay", + <&cam_node>,"lens-focus:0=", <&vcm>; + clock-frequency = <&cam_clk>, "clock-frequency:0"; + link-frequency = <&cam_endpoint>,"link-frequencies#0"; + 4lane = <0>, "+201+202"; + }; +}; + +&cam_node { + status = "okay"; +}; + +&cam_endpoint { + remote-endpoint = <&csi_ep>; +}; diff --git a/arch/arm/boot/dts/overlays/imx415.dtsi b/arch/arm/boot/dts/overlays/imx415.dtsi new file mode 100644 index 00000000000000..ba18550ddec7b6 --- /dev/null +++ b/arch/arm/boot/dts/overlays/imx415.dtsi @@ -0,0 +1,27 @@ +// Fragment that configures an imx415 + +cam_node: imx415@37 { + compatible = "sony,imx415"; + reg = <0x37>; + status = "disabled"; + + clocks = <&cam1_clk>; + clock-names = "inck"; + + avdd-supply = <&cam1_reg>; /* 2.8v */ + dvdd-supply = <&cam_dummy_reg>; /* 1.8v */ + ovdd-supply = <&cam_dummy_reg>; /* 1.2v */ + + rotation = <180>; + orientation = <2>; + + port { + cam_endpoint: endpoint { + clock-lanes = <0>; + data-lanes = <1 2>; + //clock-noncontinuous; + link-frequencies = + /bits/ 64 <360000000>; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/imx462-overlay.dts b/arch/arm/boot/dts/overlays/imx462-overlay.dts new file mode 100644 index 00000000000000..6e4c5aaddff5e9 --- /dev/null +++ b/arch/arm/boot/dts/overlays/imx462-overlay.dts @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Definitions for IMX462 camera module on VC I2C bus + +// IMX462 is the successor to IMX290. + +/dts-v1/; +/plugin/; + +#include <dt-bindings/gpio/gpio.h> +#include "imx290_327-overlay.dtsi" + +/{ + compatible = "brcm,bcm2835"; + + // Fragment numbers deliberately high to avoid conflicts with the + // included imx290_327 overlay file. + + fragment@101 { + target = <&cam_node>; + __overlay__ { + compatible = "sony,imx462lqr"; + }; + }; + + fragment@102 { + target = <&cam_node>; + __dormant__ { + compatible = "sony,imx462llr"; + }; + }; + + __overrides__ { + mono = <0>, "-101+102"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/imx477-overlay.dts b/arch/arm/boot/dts/overlays/imx477-overlay.dts new file mode 100644 index 00000000000000..8645162682f42e --- /dev/null +++ b/arch/arm/boot/dts/overlays/imx477-overlay.dts @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Definitions for IMX477 camera module on VC I2C bus +/dts-v1/; +/plugin/; + +#include "imx477_378-overlay.dtsi" + +&cam_node { + compatible = "sony,imx477"; +}; + +/{ + __overrides__ { + sync-sink = <&cam_node>,"trigger-mode:0=2"; + sync-source = <&cam_node>,"trigger-mode:0=1"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/imx477_378-overlay.dtsi b/arch/arm/boot/dts/overlays/imx477_378-overlay.dtsi new file mode 100644 index 00000000000000..686289f550297c --- /dev/null +++ b/arch/arm/boot/dts/overlays/imx477_378-overlay.dtsi @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Definitions for IMX477 camera module on VC I2C bus + +/{ + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2c0if>; + __overlay__ { + status = "okay"; + }; + }; + + clk_frag: fragment@1 { + target = <&cam1_clk>; + cam_clk: __overlay__ { + clock-frequency = <24000000>; + status = "okay"; + }; + }; + + fragment@2 { + target = <&i2c0mux>; + __overlay__ { + status = "okay"; + }; + }; + + reg_frag: fragment@3 { + target = <&cam1_reg>; + cam_reg: __overlay__ { + startup-delay-us = <300000>; + }; + }; + + reg_alwayson_frag: fragment@99 { + target = <&cam1_reg>; + __dormant__ { + regulator-always-on; + }; + }; + + i2c_frag: fragment@100 { + target = <&i2c_csi_dsi>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + #include "imx477_378.dtsi" + }; + }; + + csi_frag: fragment@101 { + target = <&csi1>; + csi: __overlay__ { + status = "okay"; + + port { + csi_ep: endpoint { + remote-endpoint = <&cam_endpoint>; + clock-lanes = <0>; + data-lanes = <1 2>; + clock-noncontinuous; + }; + }; + }; + }; + + fragment@102 { + target = <&csi1>; + __dormant__ { + compatible = "brcm,bcm2835-unicam-legacy"; + }; + }; + + __overrides__ { + rotation = <&cam_node>,"rotation:0"; + orientation = <&cam_node>,"orientation:0"; + media-controller = <0>,"!102"; + cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>, + <&csi_frag>, "target:0=",<&csi0>, + <&clk_frag>, "target:0=",<&cam0_clk>, + <®_frag>, "target:0=",<&cam0_reg>, + <®_alwayson_frag>, "target:0=",<&cam0_reg>, + <&cam_node>, "clocks:0=",<&cam0_clk>, + <&cam_node>, "VANA-supply:0=",<&cam0_reg>; + always-on = <0>, "+99"; + link-frequency = <&cam_endpoint>,"link-frequencies#0"; + }; +}; + +&cam_node { + status = "okay"; +}; + +&cam_endpoint { + remote-endpoint = <&csi_ep>; +}; diff --git a/arch/arm/boot/dts/overlays/imx477_378.dtsi b/arch/arm/boot/dts/overlays/imx477_378.dtsi new file mode 100644 index 00000000000000..a0c154c2a11fb7 --- /dev/null +++ b/arch/arm/boot/dts/overlays/imx477_378.dtsi @@ -0,0 +1,24 @@ +cam_node: imx477@1a { + reg = <0x1a>; + status = "disabled"; + + clocks = <&cam1_clk>; + clock-names = "xclk"; + + VANA-supply = <&cam1_reg>; /* 2.8v */ + VDIG-supply = <&cam_dummy_reg>; /* 1.05v */ + VDDL-supply = <&cam_dummy_reg>; /* 1.8v */ + + rotation = <180>; + orientation = <2>; + + port { + cam_endpoint: endpoint { + clock-lanes = <0>; + data-lanes = <1 2>; + clock-noncontinuous; + link-frequencies = + /bits/ 64 <450000000>; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/imx500-overlay.dts b/arch/arm/boot/dts/overlays/imx500-overlay.dts new file mode 100644 index 00000000000000..b8d76feb259aab --- /dev/null +++ b/arch/arm/boot/dts/overlays/imx500-overlay.dts @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Definitions for IMX500 camera module on VC I2C bus +/dts-v1/; +/plugin/; + +#include <dt-bindings/gpio/gpio.h> + +/{ + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2c0if>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&i2c0mux>; + __overlay__ { + status = "okay"; + }; + }; + + reg_frag: fragment@2 { + target = <&cam1_reg>; + cam_reg: __overlay__ { + startup-delay-us = <300000>; + }; + }; + + i2c_frag: fragment@100 { + target = <&i2c_csi_dsi>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + #include "imx500.dtsi" + #include "rpi-rp2040-gpio-bridge.dtsi" + }; + }; + + csi_frag: fragment@101 { + target = <&csi1>; + csi: __overlay__ { + status = "okay"; + brcm,media-controller; + + port { + csi_ep: endpoint { + remote-endpoint = <&cam_endpoint>; + clock-lanes = <0>; + data-lanes = <1 2>; + clock-noncontinuous; + }; + }; + }; + }; + + spi_frag: fragment@102 { + target = <&spi_bridgedev0>; + __overlay__ { + compatible = "sony,imx500"; + }; + }; + + chosen_frag: fragment@103 { + target = <&chosen>; + __overlay__ { + core_freq_fixed; + }; + }; + + fragment@104 { + target-path = "/clocks"; + __overlay__ { + clk_aicam: clk-aicam1 { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <24000000>; + }; + + clk_aicam_gated: clk-aicam-gated1 { + compatible = "gpio-gate-clock"; + clocks = <&clk_aicam>; + #clock-cells = <0>; + enable-gpios = <&spi_bridge 21 GPIO_ACTIVE_HIGH>; + }; + }; + }; + + __overrides__ { + rotation = <&cam_node>,"rotation:0"; + orientation = <&cam_node>,"orientation:0"; + media-controller = <&csi>,"brcm,media-controller?"; + cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>, + <&csi_frag>, "target:0=",<&csi0>, + <&spi_bridge>, "power-supply:0=",<&cam0_reg>, + <®_frag>, "target:0=",<&cam0_reg>, + <&cam_node>, "VANA-supply:0=",<&cam0_reg>, + <&clk_aicam>,"name=clk-aicam0", + <&clk_aicam_gated>,"name=clk-aicam-gated0"; + bypass-cache = <&spi_bridge>,"bypass-cache?"; + }; +}; + +&cam_node { + status = "okay"; + led-gpios = <&spi_bridge 19 GPIO_ACTIVE_HIGH>; + reset-gpios = <&spi_bridge 20 GPIO_ACTIVE_HIGH>; + clocks = <&clk_aicam_gated>; + spi = <&spi_bridgedev0>; +}; + +&spi_bridge { + status = "okay"; +}; + +&cam_endpoint { + remote-endpoint = <&csi_ep>; +}; diff --git a/arch/arm/boot/dts/overlays/imx500-pi5-overlay.dts b/arch/arm/boot/dts/overlays/imx500-pi5-overlay.dts new file mode 100644 index 00000000000000..8ad4f0cd1c7b50 --- /dev/null +++ b/arch/arm/boot/dts/overlays/imx500-pi5-overlay.dts @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Definitions for IMX500 camera module on VC I2C bus +/dts-v1/; +/plugin/; + +#include <dt-bindings/gpio/gpio.h> + +/{ + compatible = "brcm,bcm2712"; + + fragment@0 { + target = <&i2c0if>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&i2c0mux>; + __overlay__ { + status = "okay"; + }; + }; + + reg_frag: fragment@2 { + target = <&cam1_reg>; + cam_reg: __overlay__ { + startup-delay-us = <300000>; + }; + }; + + i2c_frag: fragment@100 { + target = <&i2c_csi_dsi>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + #include "imx500.dtsi" + #include "rpi-rp2040-gpio-bridge.dtsi" + }; + }; + + csi_frag: fragment@101 { + target = <&csi1>; + csi: __overlay__ { + status = "okay"; + brcm,media-controller; + + port { + csi_ep: endpoint { + remote-endpoint = <&cam_endpoint>; + clock-lanes = <0>; + data-lanes = <1 2>; + clock-noncontinuous; + }; + }; + }; + }; + + spi_frag: fragment@102 { + target = <&spi_bridge>; + spi_frag_overlay: __overlay__ { + fast_xfer_requires_i2c_lock = <1>; + fast_xfer_recv_gpio_base = <11>; + fast_xfer-gpios = <&rp1_gpio 40 0>, // CD1_SDA (used as data) + <&rp1_gpio 48 0>; // CD1_IO1_MICDAT1 (clock) + }; + }; + + spi_bridge_frag: fragment@103 { + target = <&spi_bridgedev0>; + __overlay__ { + compatible = "sony,imx500"; + }; + }; + + fragment@104 { + target-path = "/clocks"; + __overlay__ { + clk_aicam: clk-aicam1 { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <24000000>; + }; + + clk_aicam_gated: clk-aicam-gated1 { + compatible = "gpio-gate-clock"; + clocks = <&clk_aicam>; + #clock-cells = <0>; + enable-gpios = <&spi_bridge 21 GPIO_ACTIVE_HIGH>; + }; + }; + }; + + __overrides__ { + rotation = <&cam_node>,"rotation:0"; + orientation = <&cam_node>,"orientation:0"; + media-controller = <&csi>,"brcm,media-controller?"; + cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>, + <&csi_frag>, "target:0=",<&csi0>, + <&spi_frag_overlay>, "fast_xfer-gpios:4=38", // CD0_SDA (data) + <&spi_frag_overlay>, "fast_xfer-gpios:16=35", // CD0_IO1_MICDAT0 (clock) + <&spi_bridge>, "power-supply:0=",<&cam0_reg>, + <®_frag>, "target:0=",<&cam0_reg>, + <&cam_node>, "VANA-supply:0=",<&cam0_reg>, + <&clk_aicam>,"name=clk-aicam0", + <&clk_aicam_gated>,"name=clk-aicam-gated0"; + bypass-cache = <&spi_bridge>,"bypass-cache?"; + }; +}; + +&cam_node { + status = "okay"; + led-gpios = <&spi_bridge 19 GPIO_ACTIVE_HIGH>; + reset-gpios = <&spi_bridge 20 GPIO_ACTIVE_HIGH>; + clocks = <&clk_aicam_gated>; + spi = <&spi_bridgedev0>; +}; + +&spi_bridge { + status = "okay"; +}; + +&cam_endpoint { + remote-endpoint = <&csi_ep>; +}; diff --git a/arch/arm/boot/dts/overlays/imx500.dtsi b/arch/arm/boot/dts/overlays/imx500.dtsi new file mode 100644 index 00000000000000..a931aa9941e677 --- /dev/null +++ b/arch/arm/boot/dts/overlays/imx500.dtsi @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-2.0-only +cam_node: imx500@1a { + reg = <0x1a>; + compatible = "sony,imx500"; + status = "disabled"; + + clocks = <&cam1_clk>; + clock-names = "inck"; + + vana-supply = <&cam1_reg>; /* 2.7v */ + vdig-supply = <&cam_dummy_reg>; /* 0.84v */ + vif-supply = <&cam_dummy_reg>; /* 1.8v */ + + reset-gpios = <&gpio 255 GPIO_ACTIVE_HIGH>; + + rotation = <0>; + orientation = <2>; + + port { + cam_endpoint: endpoint { + clock-lanes = <0>; + data-lanes = <1 2>; + clock-noncontinuous; + link-frequencies = + /bits/ 64 <444000000>; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/imx519-overlay.dts b/arch/arm/boot/dts/overlays/imx519-overlay.dts new file mode 100644 index 00000000000000..f1bcd782b99fb8 --- /dev/null +++ b/arch/arm/boot/dts/overlays/imx519-overlay.dts @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Definitions for imx519 camera module on VC I2C bus +/dts-v1/; +/plugin/; + +#include <dt-bindings/gpio/gpio.h> + +/{ + compatible = "brcm,bcm2835"; + + i2c_frag: fragment@0 { + target = <&i2c_csi_dsi>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + #include "imx519.dtsi" + }; + }; + + csi_frag: fragment@1 { + target = <&csi1>; + csi: __overlay__ { + status = "okay"; + + port{ + csi_ep: endpoint{ + remote-endpoint = <&cam_endpoint>; + clock-lanes = <0>; + data-lanes = <1 2>; + clock-noncontinuous; + }; + }; + }; + }; + + fragment@2 { + target = <&i2c0if>; + __overlay__ { + status = "okay"; + }; + }; + + clk_frag: fragment@3 { + target = <&cam1_clk>; + __overlay__ { + clock-frequency = <24000000>; + status = "okay"; + }; + }; + + fragment@4 { + target = <&i2c0mux>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@5 { + target = <&cam_node>; + __overlay__ { + lens-focus = <&vcm_node>; + }; + }; + + fragment@6 { + target = <&csi1>; + __dormant__ { + compatible = "brcm,bcm2835-unicam-legacy"; + }; + }; + + __overrides__ { + rotation = <&cam_node>,"rotation:0"; + orientation = <&cam_node>,"orientation:0"; + media-controller = <0>,"!6"; + cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>, + <&csi_frag>, "target:0=",<&csi0>, + <&clk_frag>, "target:0=",<&cam0_clk>, + <&cam_node>, "clocks:0=",<&cam0_clk>, + <&cam_node>, "VANA-supply:0=",<&cam0_reg>, + <&vcm_node>, "vdd-supply:0=",<&cam0_reg>; + vcm = <&vcm_node>, "status", + <0>, "=5"; + }; +}; + +&cam_node { + status = "okay"; +}; + +&cam_endpoint { + remote-endpoint = <&csi_ep>; +}; + +&vcm_node { + status = "okay"; +}; diff --git a/arch/arm/boot/dts/overlays/imx519.dtsi b/arch/arm/boot/dts/overlays/imx519.dtsi new file mode 100644 index 00000000000000..18cba1781ec4fa --- /dev/null +++ b/arch/arm/boot/dts/overlays/imx519.dtsi @@ -0,0 +1,34 @@ +// Fragment that configures a Sony IMX519 + +cam_node: imx519@1a { + compatible = "sony,imx519"; + reg = <0x1a>; + status = "disabled"; + + clocks = <&cam1_clk>; + clock-names = "xclk"; + + VANA-supply = <&cam1_reg>; /* 2.8v */ + VDIG-supply = <&cam_dummy_reg>; /* 1.8v */ + VDDL-supply = <&cam_dummy_reg>; /* 1.2v */ + + rotation = <0>; + orientation = <2>; + + port { + cam_endpoint: endpoint { + clock-lanes = <0>; + data-lanes = <1 2>; + clock-noncontinuous; + link-frequencies = + /bits/ 64 <408000000>; + }; + }; +}; + +vcm_node: ak7375@c { + compatible = "asahi-kasei,ak7375"; + reg = <0x0c>; + status = "disabled"; + vdd-supply = <&cam1_reg>; +}; diff --git a/arch/arm/boot/dts/overlays/imx708-overlay.dts b/arch/arm/boot/dts/overlays/imx708-overlay.dts new file mode 100644 index 00000000000000..3cbec474ce3e96 --- /dev/null +++ b/arch/arm/boot/dts/overlays/imx708-overlay.dts @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Definitions for IMX708 camera module on VC I2C bus +/dts-v1/; +/plugin/; + +#include <dt-bindings/gpio/gpio.h> + +/{ + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2c0if>; + __overlay__ { + status = "okay"; + }; + }; + + clk_frag: fragment@1 { + target = <&cam1_clk>; + __overlay__ { + status = "okay"; + clock-frequency = <24000000>; + }; + }; + + fragment@2 { + target = <&i2c0mux>; + __overlay__ { + status = "okay"; + }; + }; + + reg_frag: fragment@3 { + target = <&cam1_reg>; + cam_reg: __overlay__ { + startup-delay-us = <70000>; + off-on-delay-us = <30000>; + regulator-min-microvolt = <2700000>; + regulator-max-microvolt = <2700000>; + }; + }; + + fragment@4 { + target = <&cam_node>; + __overlay__ { + lens-focus = <&vcm_node>; + }; + }; + + i2c_frag: fragment@100 { + target = <&i2c_csi_dsi>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + #include "imx708.dtsi" + }; + }; + + csi_frag: fragment@101 { + target = <&csi1>; + csi: __overlay__ { + status = "okay"; + + port { + csi_ep: endpoint { + remote-endpoint = <&cam_endpoint>; + clock-lanes = <0>; + data-lanes = <1 2>; + clock-noncontinuous; + }; + }; + }; + }; + + fragment@102 { + target = <&csi1>; + __dormant__ { + compatible = "brcm,bcm2835-unicam-legacy"; + }; + }; + + __overrides__ { + rotation = <&cam_node>,"rotation:0"; + orientation = <&cam_node>,"orientation:0"; + media-controller = <0>,"!102"; + cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>, + <&csi_frag>, "target:0=",<&csi0>, + <&clk_frag>, "target:0=",<&cam0_clk>, + <®_frag>, "target:0=",<&cam0_reg>, + <&cam_node>, "clocks:0=",<&cam0_clk>, + <&cam_node>, "vana1-supply:0=",<&cam0_reg>, + <&vcm_node>, "VDD-supply:0=",<&cam0_reg>; + vcm = <&vcm_node>, "status", + <0>, "=4"; + link-frequency = <&cam_endpoint>,"link-frequencies#0"; + }; +}; + +&cam_node { + status = "okay"; +}; + +&cam_endpoint { + remote-endpoint = <&csi_ep>; +}; + +&vcm_node { + status = "okay"; +}; diff --git a/arch/arm/boot/dts/overlays/imx708.dtsi b/arch/arm/boot/dts/overlays/imx708.dtsi new file mode 100644 index 00000000000000..1558458d58ecbb --- /dev/null +++ b/arch/arm/boot/dts/overlays/imx708.dtsi @@ -0,0 +1,35 @@ +// Fragment that configures a Sony IMX708 + +cam_node: imx708@1a { + compatible = "sony,imx708"; + reg = <0x1a>; + status = "disabled"; + + clocks = <&cam1_clk>; + clock-names = "inclk"; + + vana1-supply = <&cam1_reg>; /* 2.8v */ + vana2-supply = <&cam_dummy_reg>;/* 1.8v */ + vdig-supply = <&cam_dummy_reg>; /* 1.1v */ + vddl-supply = <&cam_dummy_reg>; /* 1.8v */ + + rotation = <180>; + orientation = <2>; + + port { + cam_endpoint: endpoint { + clock-lanes = <0>; + data-lanes = <1 2>; + clock-noncontinuous; + link-frequencies = + /bits/ 64 <450000000>; + }; + }; +}; + +vcm_node: dw9817@c { + compatible = "dongwoon,dw9817-vcm"; + reg = <0x0c>; + status = "disabled"; + VDD-supply = <&cam1_reg>; +}; diff --git a/arch/arm/boot/dts/overlays/interludeaudio-analog-overlay.dts b/arch/arm/boot/dts/overlays/interludeaudio-analog-overlay.dts new file mode 100644 index 00000000000000..e2590135f91983 --- /dev/null +++ b/arch/arm/boot/dts/overlays/interludeaudio-analog-overlay.dts @@ -0,0 +1,73 @@ +// Definitions for Interlude audio analog hat +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&sound>; + __overlay__ { + compatible = "simple-audio-card"; + i2s-controller = <&i2s_clk_consumer>; + status = "okay"; + + simple-audio-card,name = "snd_IA_Analog_Hat"; + + simple-audio-card,widgets = + "Line", "Line In", + "Line", "Line Out"; + + simple-audio-card,routing = + "Line Out","AOUTA+", + "Line Out","AOUTA-", + "Line Out","AOUTB+", + "Line Out","AOUTB-", + "AINA","Line In", + "AINB","Line In"; + + simple-audio-card,format = "i2s"; + + simple-audio-card,bitclock-master = <&sound_master>; + simple-audio-card,frame-master = <&sound_master>; + + simple-audio-card,cpu { + sound-dai = <&i2s>; + dai-tdm-slot-num = <2>; + dai-tdm-slot-width = <32>; + }; + + sound_master: simple-audio-card,codec { + sound-dai = <&cs4271>; + system-clock-frequency = <24576000>; + }; + }; + }; + + fragment@1 { + target = <&i2s_clk_consumer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@2 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + cs4271: cs4271@10 { + #sound-dai-cells = <0>; + compatible = "cirrus,cs4271"; + reg = <0x10>; + status = "okay"; + reset-gpio = <&gpio 24 0>; /* Pin 26, active high */ + }; + }; + }; + __overrides__ { + gpiopin = <&cs4271>,"reset-gpio:4"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/interludeaudio-digital-overlay.dts b/arch/arm/boot/dts/overlays/interludeaudio-digital-overlay.dts new file mode 100644 index 00000000000000..24be00860310bf --- /dev/null +++ b/arch/arm/boot/dts/overlays/interludeaudio-digital-overlay.dts @@ -0,0 +1,49 @@ +// Definitions for Interlude Audio Digital Hat +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_consumer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + wm8804@3b { + #sound-dai-cells = <0>; + compatible = "wlf,wm8804"; + reg = <0x3b>; + PVDD-supply = <&vdd_3v3_reg>; + DVDD-supply = <&vdd_3v3_reg>; + status = "okay"; + }; + }; + }; + + + fragment@2 { + target = <&sound>; + __overlay__ { + compatible = "interludeaudio,interludeaudio-digital"; + i2s-controller = <&i2s_clk_consumer>; + status = "okay"; + clock44-gpio = <&gpio 22 0>; + clock48-gpio = <&gpio 27 0>; + led1-gpio = <&gpio 13 0>; + led2-gpio = <&gpio 12 0>; + led3-gpio = <&gpio 6 0>; + reset-gpio = <&gpio 23 0>; + }; + }; + +}; diff --git a/arch/arm/boot/dts/overlays/iqaudio-codec-overlay.dts b/arch/arm/boot/dts/overlays/iqaudio-codec-overlay.dts new file mode 100644 index 00000000000000..bffff5a4d64ca4 --- /dev/null +++ b/arch/arm/boot/dts/overlays/iqaudio-codec-overlay.dts @@ -0,0 +1,42 @@ +// Definitions for IQaudIO CODEC +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_consumer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + da2713@1a { + #sound-dai-cells = <0>; + compatible = "dlg,da7213"; + reg = <0x1a>; + status = "okay"; + }; + }; + }; + + fragment@2 { + target = <&sound>; + iqaudio_dac: __overlay__ { + compatible = "iqaudio,iqaudio-codec"; + i2s-controller = <&i2s_clk_consumer>; + status = "okay"; + }; + }; + + __overrides__ { + }; +}; diff --git a/arch/arm/boot/dts/overlays/iqaudio-dac-overlay.dts b/arch/arm/boot/dts/overlays/iqaudio-dac-overlay.dts new file mode 100644 index 00000000000000..05d348f5e58af5 --- /dev/null +++ b/arch/arm/boot/dts/overlays/iqaudio-dac-overlay.dts @@ -0,0 +1,46 @@ +// Definitions for IQaudIO DAC +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_producer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + pcm5122@4c { + #sound-dai-cells = <0>; + compatible = "ti,pcm5122"; + reg = <0x4c>; + AVDD-supply = <&vdd_3v3_reg>; + DVDD-supply = <&vdd_3v3_reg>; + CPVDD-supply = <&vdd_3v3_reg>; + status = "okay"; + }; + }; + }; + + fragment@2 { + target = <&sound>; + frag2: __overlay__ { + compatible = "iqaudio,iqaudio-dac"; + i2s-controller = <&i2s_clk_producer>; + status = "okay"; + }; + }; + + __overrides__ { + 24db_digital_gain = <&frag2>,"iqaudio,24db_digital_gain?"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/iqaudio-dacplus-overlay.dts b/arch/arm/boot/dts/overlays/iqaudio-dacplus-overlay.dts new file mode 100644 index 00000000000000..3993580f7ac1f6 --- /dev/null +++ b/arch/arm/boot/dts/overlays/iqaudio-dacplus-overlay.dts @@ -0,0 +1,49 @@ +// Definitions for IQaudIO DAC+ +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_producer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + pcm5122@4c { + #sound-dai-cells = <0>; + compatible = "ti,pcm5122"; + reg = <0x4c>; + AVDD-supply = <&vdd_3v3_reg>; + DVDD-supply = <&vdd_3v3_reg>; + CPVDD-supply = <&vdd_3v3_reg>; + status = "okay"; + }; + }; + }; + + fragment@2 { + target = <&sound>; + iqaudio_dac: __overlay__ { + compatible = "iqaudio,iqaudio-dac"; + i2s-controller = <&i2s_clk_producer>; + mute-gpios = <&gpio 22 0>; + status = "okay"; + }; + }; + + __overrides__ { + 24db_digital_gain = <&iqaudio_dac>,"iqaudio,24db_digital_gain?"; + auto_mute_amp = <&iqaudio_dac>,"iqaudio-dac,auto-mute-amp?"; + unmute_amp = <&iqaudio_dac>,"iqaudio-dac,unmute-amp?"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/iqaudio-digi-wm8804-audio-overlay.dts b/arch/arm/boot/dts/overlays/iqaudio-digi-wm8804-audio-overlay.dts new file mode 100644 index 00000000000000..f24faf11ecfacf --- /dev/null +++ b/arch/arm/boot/dts/overlays/iqaudio-digi-wm8804-audio-overlay.dts @@ -0,0 +1,47 @@ +// Definitions for IQAudIO Digi WM8804 audio board +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_consumer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + wm8804@3b { + #sound-dai-cells = <0>; + compatible = "wlf,wm8804"; + reg = <0x3b>; + status = "okay"; + DVDD-supply = <&vdd_3v3_reg>; + PVDD-supply = <&vdd_3v3_reg>; + }; + }; + }; + + fragment@2 { + target = <&sound>; + wm8804_digi: __overlay__ { + compatible = "iqaudio,wm8804-digi"; + i2s-controller = <&i2s_clk_consumer>; + status = "okay"; + }; + }; + + __overrides__ { + card_name = <&wm8804_digi>,"wm8804-digi,card-name"; + dai_name = <&wm8804_digi>,"wm8804-digi,dai-name"; + dai_stream_name = <&wm8804_digi>,"wm8804-digi,dai-stream-name"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/iqs550-overlay.dts b/arch/arm/boot/dts/overlays/iqs550-overlay.dts new file mode 100644 index 00000000000000..c3956937055fa1 --- /dev/null +++ b/arch/arm/boot/dts/overlays/iqs550-overlay.dts @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Definitions for Azoteq IQS550 trackpad/touchscreen controller +/dts-v1/; +/plugin/; +#include <dt-bindings/gpio/gpio.h> +#include <dt-bindings/interrupt-controller/irq.h> + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + iqs550: iqs550@74 { + compatible = "azoteq,iqs550"; + reg = <0x74>; + interrupt-parent = <&gpio>; + interrupts = <4 IRQ_TYPE_LEVEL_HIGH>; + pinctrl-names = "default"; + pinctrl-0 = <&iqs550_pins>; + touchscreen-size-x = <800>; + touchscreen-size-y = <480>; + }; + }; + }; + + fragment@1 { + target = <&iqs550>; + iqs550_reset: __dormant__ { + reset-gpios = <&gpio 255 (GPIO_ACTIVE_LOW | + GPIO_PUSH_PULL)>; + }; + }; + + fragment@2 { + target = <&gpio>; + __overlay__ { + iqs550_pins: iqs550_pins { + brcm,pins = <4>; + brcm,pull = <1>; + }; + }; + }; + + __overrides__ { + interrupt = <&iqs550>,"interrupts:0", + <&iqs550_pins>,"brcm,pins:0"; + reset = <0>,"+1", <&iqs550_reset>,"reset-gpios:4"; + sizex = <&iqs550>,"touchscreen-size-x:0"; + sizey = <&iqs550>,"touchscreen-size-y:0"; + invx = <&iqs550>,"touchscreen-inverted-x?"; + invy = <&iqs550>,"touchscreen-inverted-y?"; + swapxy = <&iqs550>,"touchscreen-swapped-x-y?"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/irs1125-overlay.dts b/arch/arm/boot/dts/overlays/irs1125-overlay.dts new file mode 100644 index 00000000000000..07996247a7bea6 --- /dev/null +++ b/arch/arm/boot/dts/overlays/irs1125-overlay.dts @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Definitions for IRS1125 camera module on VC I2C bus +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2835"; + + i2c_frag: fragment@0 { + target = <&i2c_csi_dsi>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + irs1125: irs1125@3d { + compatible = "infineon,irs1125"; + reg = <0x3d>; + status = "okay"; + + pwdn-gpios = <&gpio 5 0>; + clocks = <&cam1_clk>; + + port { + irs1125_0: endpoint { + remote-endpoint = <&csi1_ep>; + clock-lanes = <0>; + data-lanes = <1 2>; + clock-noncontinuous; + link-frequencies = + /bits/ 64 <297000000>; + }; + }; + }; + }; + }; + + csi_frag: fragment@1 { + target = <&csi1>; + csi: __overlay__ { + status = "okay"; + + port { + csi1_ep: endpoint { + remote-endpoint = <&irs1125_0>; + data-lanes = <1 2>; + clock-noncontinuous; + }; + }; + }; + }; + + fragment@2 { + target = <&i2c0if>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@3 { + target = <&i2c0mux>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@4 { + target-path="/__overrides__"; + __overlay__ { + cam0-pwdn-ctrl = <&irs1125>,"pwdn-gpios:0"; + cam0-pwdn = <&irs1125>,"pwdn-gpios:4"; + }; + }; + + clk_frag: fragment@5 { + target = <&cam1_clk>; + __overlay__ { + status = "okay"; + clock-frequency = <26000000>; + }; + }; + + fragment@6 { + target = <&csi1>; + __overlay__ { + compatible = "brcm,bcm2835-unicam-legacy"; + }; + }; + + __overrides__ { + media-controller = <0>,"!6"; + cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>, + <&csi_frag>, "target:0=",<&csi0>, + <&clk_frag>, "target:0=",<&cam0_clk>, + <&irs1125>, "clocks:0=",<&cam0_clk>; + }; +}; diff --git a/arch/arm/boot/dts/overlays/jedec-spi-nor-overlay.dts b/arch/arm/boot/dts/overlays/jedec-spi-nor-overlay.dts new file mode 100644 index 00000000000000..fb6d4bc91bf3cc --- /dev/null +++ b/arch/arm/boot/dts/overlays/jedec-spi-nor-overlay.dts @@ -0,0 +1,136 @@ +// Overlay for JEDEC SPI-NOR Flash Devices (aka m25p80) + +// dtparams: +// flash-spi<n>-<m> - Enables flash device on SPI<n>, CS#<m>. +// flash-fastr-spi<n>-<m> - Enables flash device with fast read capability on SPI<n>, CS#<m>. +// speed - Set the SPI clock speed in Hz +// +// If devices are present on SPI1 or SPI2, those interfaces must be enabled with one of the spi1-1/2/3cs and/or spi2-1/2/3cs overlays. +// +// Example: A single flash device with fast read capability on SPI0, CS#0: +// dtoverlay=jedec-spi-nor:flash-fastr-spi0-0 + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + // disable spi-dev on spi0.0 + fragment@0 { + target = <&spidev0>; + __dormant__ { + status = "disabled"; + }; + }; + + // disable spi-dev on spi0.1 + fragment@1 { + target = <&spidev1>; + __dormant__ { + status = "disabled"; + }; + }; + + // disable spi-dev on spi1.0 + fragment@2 { + target-path = "spi1/spidev@0"; + __dormant__ { + status = "disabled"; + }; + }; + + // disable spi-dev on spi1.1 + fragment@3 { + target-path = "spi1/spidev@1"; + __dormant__ { + status = "disabled"; + }; + }; + + // disable spi-dev on spi1.2 + fragment@4 { + target-path = "spi1/spidev@2"; + __dormant__ { + status = "disabled"; + }; + }; + + // disable spi-dev on spi2.0 + fragment@5 { + target-path = "spi2/spidev@0"; + __dormant__ { + status = "disabled"; + }; + }; + + // disable spi-dev on spi2.1 + fragment@6 { + target-path = "spi2/spidev@1"; + __dormant__ { + status = "disabled"; + }; + }; + + // disable spi-dev on spi2.2 + fragment@7 { + target-path = "spi2/spidev@2"; + __dormant__ { + status = "disabled"; + }; + }; + + // Enable fast read for device + // Use default active low interrupt signalling. + fragment@8 { + target = <&spi_nor>; + __dormant__ { + m25p,fast-read; + }; + }; + + payload: fragment@100 { + target = <&spi0>; + __overlay__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + spi_nor: spi_nor@0 { + compatible = "jedec,spi-nor"; + reg = <0>; + spi-max-frequency = <500000>; + }; + }; + }; + + __overrides__ { + spi0-0 = <0>,"+0", <&payload>,"target:0=",<&spi0>, <&spi_nor>,"reg:0=0"; + spi0-1 = <0>,"+1", <&payload>,"target:0=",<&spi0>, <&spi_nor>,"reg:0=1"; + spi1-0 = <0>,"+2", <&payload>,"target:0=",<&spi1>, <&spi_nor>,"reg:0=0"; + spi1-1 = <0>,"+3", <&payload>,"target:0=",<&spi1>, <&spi_nor>,"reg:0=1"; + spi1-2 = <0>,"+4", <&payload>,"target:0=",<&spi1>, <&spi_nor>,"reg:0=2"; + spi2-0 = <0>,"+5", <&payload>,"target:0=",<&spi2>, <&spi_nor>,"reg:0=0"; + spi2-1 = <0>,"+6", <&payload>,"target:0=",<&spi2>, <&spi_nor>,"reg:0=1"; + spi2-2 = <0>,"+7", <&payload>,"target:0=",<&spi2>, <&spi_nor>,"reg:0=2"; + flash-spi0-0 = <0>,"+0", <&payload>,"target:0=",<&spi0>, <&spi_nor>,"reg:0=0"; + flash-spi0-1 = <0>,"+1", <&payload>,"target:0=",<&spi0>, <&spi_nor>,"reg:0=1"; + flash-spi1-0 = <0>,"+2", <&payload>,"target:0=",<&spi1>, <&spi_nor>,"reg:0=0"; + flash-spi1-1 = <0>,"+3", <&payload>,"target:0=",<&spi1>, <&spi_nor>,"reg:0=1"; + flash-spi1-2 = <0>,"+4", <&payload>,"target:0=",<&spi1>, <&spi_nor>,"reg:0=2"; + flash-spi2-0 = <0>,"+5", <&payload>,"target:0=",<&spi2>, <&spi_nor>,"reg:0=0"; + flash-spi2-1 = <0>,"+6", <&payload>,"target:0=",<&spi2>, <&spi_nor>,"reg:0=1"; + flash-spi2-2 = <0>,"+7", <&payload>,"target:0=",<&spi2>, <&spi_nor>,"reg:0=2"; + flash-fastr-spi0-0 = <0>,"+0+8", <&payload>,"target:0=",<&spi0>, <&spi_nor>,"reg:0=0"; + flash-fastr-spi0-1 = <0>,"+1+8", <&payload>,"target:0=",<&spi0>, <&spi_nor>,"reg:0=1"; + flash-fastr-spi1-0 = <0>,"+2+8", <&payload>,"target:0=",<&spi1>, <&spi_nor>,"reg:0=0"; + flash-fastr-spi1-1 = <0>,"+3+8", <&payload>,"target:0=",<&spi1>, <&spi_nor>,"reg:0=1"; + flash-fastr-spi1-2 = <0>,"+4+8", <&payload>,"target:0=",<&spi1>, <&spi_nor>,"reg:0=2"; + flash-fastr-spi2-0 = <0>,"+5+8", <&payload>,"target:0=",<&spi2>, <&spi_nor>,"reg:0=0"; + flash-fastr-spi2-1 = <0>,"+6+8", <&payload>,"target:0=",<&spi2>, <&spi_nor>,"reg:0=1"; + flash-fastr-spi2-2 = <0>,"+7+8", <&payload>,"target:0=",<&spi2>, <&spi_nor>,"reg:0=2"; + fastr = <0>,"+8"; + speed = <&spi_nor>, "spi-max-frequency:0"; + }; +}; + diff --git a/arch/arm/boot/dts/overlays/justboom-both-overlay.dts b/arch/arm/boot/dts/overlays/justboom-both-overlay.dts new file mode 100644 index 00000000000000..9185d668d1d5be --- /dev/null +++ b/arch/arm/boot/dts/overlays/justboom-both-overlay.dts @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: GPL-2.0 +// Definitions for JustBoom Both (Digi+DAC) +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_consumer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + wm8804@3b { + #sound-dai-cells = <0>; + compatible = "wlf,wm8804"; + reg = <0x3b>; + PVDD-supply = <&vdd_3v3_reg>; + DVDD-supply = <&vdd_3v3_reg>; + status = "okay"; + }; + }; + }; + + fragment@2 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + pcm5122@4d { + #sound-dai-cells = <0>; + compatible = "ti,pcm5122"; + reg = <0x4d>; + AVDD-supply = <&vdd_3v3_reg>; + DVDD-supply = <&vdd_3v3_reg>; + CPVDD-supply = <&vdd_3v3_reg>; + status = "okay"; + }; + }; + }; + + fragment@3 { + target = <&sound>; + frag3: __overlay__ { + compatible = "justboom,justboom-both"; + i2s-controller = <&i2s_clk_consumer>; + status = "okay"; + }; + }; + + __overrides__ { + 24db_digital_gain = <&frag3>,"justboom,24db_digital_gain?"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/justboom-dac-overlay.dts b/arch/arm/boot/dts/overlays/justboom-dac-overlay.dts new file mode 100644 index 00000000000000..901a6aaba4bcd2 --- /dev/null +++ b/arch/arm/boot/dts/overlays/justboom-dac-overlay.dts @@ -0,0 +1,46 @@ +// Definitions for JustBoom DAC +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_producer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + pcm5122@4d { + #sound-dai-cells = <0>; + compatible = "ti,pcm5122"; + reg = <0x4d>; + AVDD-supply = <&vdd_3v3_reg>; + DVDD-supply = <&vdd_3v3_reg>; + CPVDD-supply = <&vdd_3v3_reg>; + status = "okay"; + }; + }; + }; + + fragment@2 { + target = <&sound>; + frag2: __overlay__ { + compatible = "justboom,justboom-dac"; + i2s-controller = <&i2s_clk_producer>; + status = "okay"; + }; + }; + + __overrides__ { + 24db_digital_gain = <&frag2>,"justboom,24db_digital_gain?"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/justboom-digi-overlay.dts b/arch/arm/boot/dts/overlays/justboom-digi-overlay.dts new file mode 100644 index 00000000000000..c4c968200a4cdc --- /dev/null +++ b/arch/arm/boot/dts/overlays/justboom-digi-overlay.dts @@ -0,0 +1,41 @@ +// Definitions for JustBoom Digi +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_consumer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + wm8804@3b { + #sound-dai-cells = <0>; + compatible = "wlf,wm8804"; + reg = <0x3b>; + PVDD-supply = <&vdd_3v3_reg>; + DVDD-supply = <&vdd_3v3_reg>; + status = "okay"; + }; + }; + }; + + fragment@2 { + target = <&sound>; + __overlay__ { + compatible = "justboom,justboom-digi"; + i2s-controller = <&i2s_clk_consumer>; + status = "okay"; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/ltc294x-overlay.dts b/arch/arm/boot/dts/overlays/ltc294x-overlay.dts new file mode 100644 index 00000000000000..6d971f3649ca5b --- /dev/null +++ b/arch/arm/boot/dts/overlays/ltc294x-overlay.dts @@ -0,0 +1,86 @@ +/dts-v1/; +/plugin/; + + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2c_arm>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + ltc2941: ltc2941@64 { + compatible = "lltc,ltc2941"; + reg = <0x64>; + lltc,resistor-sense = <50>; + lltc,prescaler-exponent = <7>; + }; + }; + }; + + fragment@1 { + target = <&i2c_arm>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + ltc2942: ltc2942@64 { + compatible = "lltc,ltc2942"; + reg = <0x64>; + lltc,resistor-sense = <50>; + lltc,prescaler-exponent = <7>; + }; + }; + }; + + fragment@2 { + target = <&i2c_arm>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + ltc2943: ltc2943@64 { + compatible = "lltc,ltc2943"; + reg = <0x64>; + lltc,resistor-sense = <50>; + lltc,prescaler-exponent = <7>; + }; + }; + }; + + fragment@3 { + target = <&i2c_arm>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + ltc2944: ltc2944@64 { + compatible = "lltc,ltc2944"; + reg = <0x64>; + lltc,resistor-sense = <50>; + lltc,prescaler-exponent = <7>; + }; + }; + }; + + __overrides__ { + ltc2941 = <0>,"+0"; + ltc2942 = <0>,"+1"; + ltc2943 = <0>,"+2"; + ltc2944 = <0>,"+3"; + resistor-sense = <<c2941>, "lltc,resistor-sense:0", + <<c2942>, "lltc,resistor-sense:0", + <<c2943>, "lltc,resistor-sense:0", + <<c2944>, "lltc,resistor-sense:0"; + prescaler-exponent = <<c2941>, "lltc,prescaler-exponent:0", + <<c2942>, "lltc,prescaler-exponent:0", + <<c2943>, "lltc,prescaler-exponent:0", + <<c2944>, "lltc,prescaler-exponent:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/max98357a-overlay.dts b/arch/arm/boot/dts/overlays/max98357a-overlay.dts new file mode 100644 index 00000000000000..263d071fe97722 --- /dev/null +++ b/arch/arm/boot/dts/overlays/max98357a-overlay.dts @@ -0,0 +1,84 @@ +// Overlay for Maxim MAX98357A audio DAC + +// dtparams: +// no-sdmode - SD_MODE pin not managed by driver. +// sdmode-pin - Specify GPIO pin to which SD_MODE is connected (default 4). + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + /* Enable I2S */ + fragment@0 { + target = <&i2s_clk_producer>; + __overlay__ { + status = "okay"; + }; + }; + + /* DAC whose SD_MODE pin is managed by driver (via GPIO pin) */ + fragment@1 { + target-path = "/"; + __overlay__ { + max98357a_dac: max98357a { + compatible = "maxim,max98357a"; + #sound-dai-cells = <0>; + sdmode-gpios = <&gpio 4 0>; /* 2nd word overwritten by sdmode-pin parameter */ + status = "okay"; + }; + }; + }; + + /* DAC whose SD_MODE pin is not managed by driver */ + fragment@2 { + target-path = "/"; + __dormant__ { + max98357a_nsd: max98357a { + compatible = "maxim,max98357a"; + #sound-dai-cells = <0>; + status = "okay"; + }; + }; + }; + + /* Soundcard connecting I2S to DAC with SD_MODE */ + fragment@3 { + target = <&sound>; + __overlay__ { + compatible = "simple-audio-card"; + simple-audio-card,format = "i2s"; + simple-audio-card,name = "MAX98357A"; + status = "okay"; + simple-audio-card,cpu { + sound-dai = <&i2s_clk_producer>; + }; + simple-audio-card,codec { + sound-dai = <&max98357a_dac>; + }; + }; + }; + + /* Soundcard connecting I2S to DAC without SD_MODE */ + fragment@4 { + target = <&sound>; + __dormant__ { + compatible = "simple-audio-card"; + simple-audio-card,format = "i2s"; + simple-audio-card,name = "MAX98357A"; + status = "okay"; + simple-audio-card,cpu { + sound-dai = <&i2s_clk_producer>; + }; + simple-audio-card,codec { + sound-dai = <&max98357a_nsd>; + }; + }; + }; + + __overrides__ { + no-sdmode = <0>,"-1+2-3+4"; + sdmode-pin = <&max98357a_dac>,"sdmode-gpios:4"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/maxtherm-overlay.dts b/arch/arm/boot/dts/overlays/maxtherm-overlay.dts new file mode 100644 index 00000000000000..9964e246c14f6e --- /dev/null +++ b/arch/arm/boot/dts/overlays/maxtherm-overlay.dts @@ -0,0 +1,186 @@ +/* + * Universal device tree overlay for SPI devices + */ + +/dts-v1/; +/plugin/; + +#include <dt-bindings/iio/temperature/thermocouple.h> + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spidev0>; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@1 { + target = <&spidev1>; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@2 { + target-path = "spi1/spidev@0"; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@3 { + target-path = "spi1/spidev@1"; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@4 { + target-path = "spi1/spidev@2"; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@5 { + target-path = "spi2/spidev@0"; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@6 { + target-path = "spi2/spidev@1"; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@7 { + target-path = "spi2/spidev@2"; + __dormant__ { + status = "disabled"; + }; + }; + + maxfrag: fragment@8 { + target = <&spi0>; + __overlay__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + max: maxtherm@0 { + compatible = "maxim,max6675"; + reg = <0>; + spi-max-frequency = <500000>; + }; + }; + }; + + fragment@9 { + target = <&max>; + __dormant__ { + compatible = "maxim,max31855e", "maxim,max31855"; + }; + }; + + fragment@10 { + target = <&max>; + __dormant__ { + compatible = "maxim,max31855j", "maxim,max31855"; + }; + }; + + fragment@11 { + target = <&max>; + __dormant__ { + compatible = "maxim,max31855k", "maxim,max31855"; + }; + }; + + fragment@12 { + target = <&max>; + __dormant__ { + compatible = "maxim,max31855n", "maxim,max31855"; + }; + }; + + fragment@13 { + target = <&max>; + __dormant__ { + compatible = "maxim,max31855r", "maxim,max31855"; + }; + }; + + fragment@14 { + target = <&max>; + __dormant__ { + compatible = "maxim,max31855s", "maxim,max31855"; + }; + }; + + fragment@15 { + target = <&max>; + __dormant__ { + compatible = "maxim,max31855t", "maxim,max31855"; + }; + }; + + fragment@16 { + target = <&max>; + __dormant__ { + compatible = "maxim,max31856"; + spi-cpha; + thermocouple-type = <THERMOCOUPLE_TYPE_K>; + }; + }; + + __overrides__ { + spi0-0 = <0>, "+0", + <&maxfrag>,"target:0=",<&spi0>, + <&max>,"reg:0=0"; + spi0-1 = <0>, "+1", + <&maxfrag>,"target:0=",<&spi0>, + <&max>,"reg:0=1"; + spi1-0 = <0>, "+2", + <&maxfrag>,"target:0=",<&spi1>, + <&max>,"reg:0=0"; + spi1-1 = <0>, "+3", + <&maxfrag>,"target:0=",<&spi1>, + <&max>,"reg:0=1"; + spi1-2 = <0>, "+4", + <&maxfrag>,"target:0=",<&spi1>, + <&max>,"reg:0=2"; + spi2-0 = <0>, "+5", + <&maxfrag>,"target:0=",<&spi2>, + <&max>,"reg:0=0"; + spi2-1 = <0>, "+6", + <&maxfrag>,"target:0=",<&spi2>, + <&max>,"reg:0=1"; + spi2-2 = <0>, "+7", + <&maxfrag>,"target:0=",<&spi2>, + <&max>,"reg:0=2"; + max6675 = <&max>,"compatible=maxim,max6675"; + max31855 = <&max>,"compatible=maxim,max31855"; + max31855e = <0>,"+9"; + max31855j = <0>,"+10"; + max31855k = <0>,"+11"; + max31855n = <0>,"+12"; + max31855r = <0>,"+13"; + max31855s = <0>,"+14"; + max31855t = <0>,"+15"; + max31856 = <0>,"+16"; + type_b = <&max>,"thermocouple-type:0=",<THERMOCOUPLE_TYPE_B>; + type_e = <&max>,"thermocouple-type:0=",<THERMOCOUPLE_TYPE_E>; + type_j = <&max>,"thermocouple-type:0=",<THERMOCOUPLE_TYPE_J>; + type_k = <&max>,"thermocouple-type:0=",<THERMOCOUPLE_TYPE_K>; + type_n = <&max>,"thermocouple-type:0=",<THERMOCOUPLE_TYPE_N>; + type_r = <&max>,"thermocouple-type:0=",<THERMOCOUPLE_TYPE_R>; + type_s = <&max>,"thermocouple-type:0=",<THERMOCOUPLE_TYPE_S>; + type_t = <&max>,"thermocouple-type:0=",<THERMOCOUPLE_TYPE_T>; + }; +}; diff --git a/arch/arm/boot/dts/overlays/mbed-dac-overlay.dts b/arch/arm/boot/dts/overlays/mbed-dac-overlay.dts new file mode 100644 index 00000000000000..e3f56608c643a9 --- /dev/null +++ b/arch/arm/boot/dts/overlays/mbed-dac-overlay.dts @@ -0,0 +1,64 @@ +// Definitions for mbed DAC +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_producer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + tlv320aic23: codec@1a { + #sound-dai-cells = <0>; + reg = <0x1a>; + compatible = "ti,tlv320aic23"; + status = "okay"; + }; + }; + }; + + fragment@2 { + target = <&sound>; + __overlay__ { + compatible = "simple-audio-card"; + i2s-controller = <&i2s_clk_producer>; + status = "okay"; + + simple-audio-card,name = "mbed-DAC"; + + simple-audio-card,widgets = + "Microphone", "Mic Jack", + "Line", "Line In", + "Headphone", "Headphone Jack"; + + simple-audio-card,routing = + "Headphone Jack", "LHPOUT", + "Headphone Jack", "RHPOUT", + "LLINEIN", "Line In", + "RLINEIN", "Line In", + "MICIN", "Mic Jack"; + + simple-audio-card,format = "i2s"; + + simple-audio-card,cpu { + sound-dai = <&i2s_clk_producer>; + }; + + sound_master: simple-audio-card,codec { + sound-dai = <&tlv320aic23>; + system-clock-frequency = <12288000>; + }; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/mcp23017-overlay.dts b/arch/arm/boot/dts/overlays/mcp23017-overlay.dts new file mode 100644 index 00000000000000..e23780b985a3a1 --- /dev/null +++ b/arch/arm/boot/dts/overlays/mcp23017-overlay.dts @@ -0,0 +1,65 @@ +// Definitions for MCP23017 Gpio Extender from Microchip Semiconductor + +/dts-v1/; +/plugin/; + +#include "i2c-buses.dtsi" + +/ { + compatible = "brcm,bcm2835"; + + fragment@1 { + target = <&gpio>; + __overlay__ { + mcp23017_pins: mcp23017_pins@20 { + brcm,pins = <4>; + brcm,function = <0>; + }; + }; + }; + + fragment@2 { + target = <&mcp23017>; + __dormant__ { + compatible = "microchip,mcp23008"; + }; + }; + + fragment@3 { + target = <&mcp23017>; + mcp23017_irq: __overlay__ { + #interrupt-cells=<2>; + pinctrl-names = "default"; + pinctrl-0 = <&mcp23017_pins>; + interrupt-parent = <&gpio>; + interrupts = <4 2>; + interrupt-controller; + microchip,irq-mirror; + }; + }; + + fragment@4 { + target = <&i2cbus>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + + mcp23017: mcp@20 { + compatible = "microchip,mcp23017"; + reg = <0x20>; + gpio-controller; + #gpio-cells = <2>; + + status = "okay"; + }; + }; + }; + + __overrides__ { + gpiopin = <&mcp23017_pins>,"brcm,pins:0", + <&mcp23017_irq>,"interrupts:0"; + addr = <&mcp23017>,"reg:0", <&mcp23017_pins>,"reg:0"; + mcp23008 = <0>,"=2"; + noints = <0>,"!1!3"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/mcp23s17-overlay.dts b/arch/arm/boot/dts/overlays/mcp23s17-overlay.dts new file mode 100644 index 00000000000000..484d64b225fb8b --- /dev/null +++ b/arch/arm/boot/dts/overlays/mcp23s17-overlay.dts @@ -0,0 +1,732 @@ +// Overlay for MCP23S08/17 GPIO Extenders from Microchip Semiconductor + +// dtparams: +// s08-spi<n>-<m>-present - 4-bit integer, bitmap indicating MCP23S08 devices present on SPI<n>, CS#<m>. +// s17-spi<n>-<m>-present - 8-bit integer, bitmap indicating MCP23S17 devices present on SPI<n>, CS#<m>. +// s08-spi<n>-<m>-int-gpio - integer, enables interrupts on a single MCP23S08 device on SPI<n>, CS#<m>, specifies the GPIO pin to which INT output is connected. +// s17-spi<n>-<m>-int-gpio - integer, enables mirrored interrupts on a single MCP23S17 device on SPI<n>, CS#<m>, specifies the GPIO pin to which either INTA or INTB output is connected. +// +// If devices are present on SPI1 or SPI2, those interfaces must be enabled with one of the spi1-1/2/3cs and/or spi2-1/2/3cs overlays. +// If interrupts are enabled for a device on a given CS# on a SPI bus, that device must be the only one present on that SPI bus/CS#. +// +// Example 1: A single MCP23S17 device on SPI0, CS#0 with its SPI addr set to 0 and INTA output connected to GPIO25: +// dtoverlay=mcp23s17:s17-spi0-0-present=1,s17-spi0-0-int-gpio=25 +// +// Example 2: Two MCP23S08 devices on SPI1, CS#0 with their addrs set to 2 and 3. Three MCP23S17 devices on SPI1, CS#1 with their addrs set to 0, 1 and 7: +// dtoverlay=spi1-2cs +// dtoverlay=mcp23s17:s08-spi1-0-present=12,s17-spi1-1-present=131 + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + // disable spi-dev on spi0.0 + fragment@0 { + target = <&spidev0>; + __dormant__ { + status = "disabled"; + }; + }; + + // disable spi-dev on spi0.1 + fragment@1 { + target = <&spidev1>; + __dormant__ { + status = "disabled"; + }; + }; + + // disable spi-dev on spi1.0 + fragment@2 { + target-path = "spi1/spidev@0"; + __dormant__ { + status = "disabled"; + }; + }; + + // disable spi-dev on spi1.1 + fragment@3 { + target-path = "spi1/spidev@1"; + __dormant__ { + status = "disabled"; + }; + }; + + // disable spi-dev on spi1.2 + fragment@4 { + target-path = "spi1/spidev@2"; + __dormant__ { + status = "disabled"; + }; + }; + + // disable spi-dev on spi2.0 + fragment@5 { + target-path = "spi2/spidev@0"; + __dormant__ { + status = "disabled"; + }; + }; + + // disable spi-dev on spi2.1 + fragment@6 { + target-path = "spi2/spidev@1"; + __dormant__ { + status = "disabled"; + }; + }; + + // disable spi-dev on spi2.2 + fragment@7 { + target-path = "spi2/spidev@2"; + __dormant__ { + status = "disabled"; + }; + }; + + // enable one or more mcp23s08s on spi0.0 + fragment@8 { + target = <&spi0>; + __dormant__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + mcp23s08_00: mcp23s08@0 { + compatible = "microchip,mcp23s08"; + gpio-controller; + #gpio-cells = <2>; + microchip,spi-present-mask = <0x00>; /* overwritten by mcp23s08-spi0-0-present parameter */ + reg = <0>; + spi-max-frequency = <500000>; + status = "okay"; + #interrupt-cells=<2>; + interrupts = <0 2>; /* 1st word overwritten by mcp23s08-spi0-0-int-gpio parameter */ + }; + }; + }; + + // enable one or more mcp23s08s on spi0.1 + fragment@9 { + target = <&spi0>; + __dormant__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + mcp23s08_01: mcp23s08@1 { + compatible = "microchip,mcp23s08"; + gpio-controller; + #gpio-cells = <2>; + microchip,spi-present-mask = <0x00>; /* overwritten by mcp23s08-spi0-1-present parameter */ + reg = <1>; + spi-max-frequency = <500000>; + status = "okay"; + #interrupt-cells=<2>; + interrupts = <0 2>; /* 1st word overwritten by mcp23s08-spi0-1-int-gpio parameter */ + }; + }; + }; + + // enable one or more mcp23s08s on spi1.0 + fragment@10 { + target = <&spi1>; + __dormant__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + mcp23s08_10: mcp23s08@0 { + compatible = "microchip,mcp23s08"; + gpio-controller; + #gpio-cells = <2>; + microchip,spi-present-mask = <0x00>; /* overwritten by mcp23s08-spi1-0-present parameter */ + reg = <0>; + spi-max-frequency = <500000>; + status = "okay"; + #interrupt-cells=<2>; + interrupts = <0 2>; /* 1st word overwritten by mcp23s08-spi1-0-int-gpio parameter */ + }; + }; + }; + + // enable one or more mcp23s08s on spi1.1 + fragment@11 { + target = <&spi1>; + __dormant__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + mcp23s08_11: mcp23s08@1 { + compatible = "microchip,mcp23s08"; + gpio-controller; + #gpio-cells = <2>; + microchip,spi-present-mask = <0x00>; /* overwritten by mcp23s08-spi1-1-present parameter */ + reg = <1>; + spi-max-frequency = <500000>; + status = "okay"; + #interrupt-cells=<2>; + interrupts = <0 2>; /* 1st word overwritten by mcp23s08-spi1-1-int-gpio parameter */ + }; + }; + }; + + // enable one or more mcp23s08s on spi1.2 + fragment@12 { + target = <&spi1>; + __dormant__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + mcp23s08_12: mcp23s08@2 { + compatible = "microchip,mcp23s08"; + gpio-controller; + #gpio-cells = <2>; + microchip,spi-present-mask = <0x00>; /* overwritten by mcp23s08-spi1-2-present parameter */ + reg = <2>; + spi-max-frequency = <500000>; + status = "okay"; + #interrupt-cells=<2>; + interrupts = <0 2>; /* 1st word overwritten by mcp23s08-spi1-2-int-gpio parameter */ + }; + }; + }; + + // enable one or more mcp23s08s on spi2.0 + fragment@13 { + target = <&spi2>; + __dormant__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + mcp23s08_20: mcp23s08@0 { + compatible = "microchip,mcp23s08"; + gpio-controller; + #gpio-cells = <2>; + microchip,spi-present-mask = <0x00>; /* overwritten by mcp23s08-spi2-0-present parameter */ + reg = <0>; + spi-max-frequency = <500000>; + status = "okay"; + #interrupt-cells=<2>; + interrupts = <0 2>; /* 1st word overwritten by mcp23s08-spi2-0-int-gpio parameter */ + }; + }; + }; + + // enable one or more mcp23s08s on spi2.1 + fragment@14 { + target = <&spi2>; + __dormant__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + mcp23s08_21: mcp23s08@1 { + compatible = "microchip,mcp23s08"; + gpio-controller; + #gpio-cells = <2>; + microchip,spi-present-mask = <0x00>; /* overwritten by mcp23s08-spi2-1-present parameter */ + reg = <1>; + spi-max-frequency = <500000>; + status = "okay"; + #interrupt-cells=<2>; + interrupts = <0 2>; /* 1st word overwritten by mcp23s08-spi2-1-int-gpio parameter */ + }; + }; + }; + + // enable one or more mcp23s08s on spi2.2 + fragment@15 { + target = <&spi2>; + __dormant__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + mcp23s08_22: mcp23s08@2 { + compatible = "microchip,mcp23s08"; + gpio-controller; + #gpio-cells = <2>; + microchip,spi-present-mask = <0x00>; /* overwritten by mcp23s08-spi2-2-present parameter */ + reg = <2>; + spi-max-frequency = <500000>; + status = "okay"; + #interrupt-cells=<2>; + interrupts = <0 2>; /* 1st word overwritten by mcp23s08-spi2-2-int-gpio parameter */ + }; + }; + }; + + // enable one or more mcp23s17s on spi0.0 + fragment@16 { + target = <&spi0>; + __dormant__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + mcp23s17_00: mcp23s17@0 { + compatible = "microchip,mcp23s17"; + gpio-controller; + #gpio-cells = <2>; + microchip,spi-present-mask = <0x00>; /* overwritten by mcp23s17-spi0-0-present parameter */ + reg = <0>; + spi-max-frequency = <500000>; + status = "okay"; + #interrupt-cells=<2>; + interrupts = <0 2>; /* 1st word overwritten by mcp23s17-spi0-0-int-gpio parameter */ + }; + }; + }; + + // enable one or more mcp23s17s on spi0.1 + fragment@17 { + target = <&spi0>; + __dormant__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + mcp23s17_01: mcp23s17@1 { + compatible = "microchip,mcp23s17"; + gpio-controller; + #gpio-cells = <2>; + microchip,spi-present-mask = <0x00>; /* overwritten by mcp23s17-spi0-1-present parameter */ + reg = <1>; + spi-max-frequency = <500000>; + status = "okay"; + #interrupt-cells=<2>; + interrupts = <0 2>; /* 1st word overwritten by mcp23s17-spi0-1-int-gpio parameter */ + }; + }; + }; + + // enable one or more mcp23s17s on spi1.0 + fragment@18 { + target = <&spi1>; + __dormant__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + mcp23s17_10: mcp23s17@0 { + compatible = "microchip,mcp23s17"; + gpio-controller; + #gpio-cells = <2>; + microchip,spi-present-mask = <0x00>; /* overwritten by mcp23s17-spi1-0-present parameter */ + reg = <0>; + spi-max-frequency = <500000>; + status = "okay"; + #interrupt-cells=<2>; + interrupts = <0 2>; /* 1st word overwritten by mcp23s17-spi1-0-int-gpio parameter */ + }; + }; + }; + + // enable one or more mcp23s17s on spi1.1 + fragment@19 { + target = <&spi1>; + __dormant__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + mcp23s17_11: mcp23s17@1 { + compatible = "microchip,mcp23s17"; + gpio-controller; + #gpio-cells = <2>; + microchip,spi-present-mask = <0x00>; /* overwritten by mcp23s17-spi1-1-present parameter */ + reg = <1>; + spi-max-frequency = <500000>; + status = "okay"; + #interrupt-cells=<2>; + interrupts = <0 2>; /* 1st word overwritten by mcp23s17-spi1-1-int-gpio parameter */ + }; + }; + }; + + // enable one or more mcp23s17s on spi1.2 + fragment@20 { + target = <&spi1>; + __dormant__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + mcp23s17_12: mcp23s17@2 { + compatible = "microchip,mcp23s17"; + gpio-controller; + #gpio-cells = <2>; + microchip,spi-present-mask = <0x00>; /* overwritten by mcp23s17-spi1-2-present parameter */ + reg = <2>; + spi-max-frequency = <500000>; + status = "okay"; + #interrupt-cells=<2>; + interrupts = <0 2>; /* 1st word overwritten by mcp23s17-spi1-2-int-gpio parameter */ + }; + }; + }; + + // enable one or more mcp23s17s on spi2.0 + fragment@21 { + target = <&spi2>; + __dormant__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + mcp23s17_20: mcp23s17@0 { + compatible = "microchip,mcp23s17"; + gpio-controller; + #gpio-cells = <2>; + microchip,spi-present-mask = <0x00>; /* overwritten by mcp23s17-spi2-0-present parameter */ + reg = <0>; + spi-max-frequency = <500000>; + status = "okay"; + #interrupt-cells=<2>; + interrupts = <0 2>; /* 1st word overwritten by mcp23s17-spi2-0-int-gpio parameter */ + }; + }; + }; + + // enable one or more mcp23s17s on spi2.1 + fragment@22 { + target = <&spi2>; + __dormant__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + mcp23s17_21: mcp23s17@1 { + compatible = "microchip,mcp23s17"; + gpio-controller; + #gpio-cells = <2>; + microchip,spi-present-mask = <0x00>; /* overwritten by mcp23s17-spi2-1-present parameter */ + reg = <1>; + spi-max-frequency = <500000>; + status = "okay"; + #interrupt-cells=<2>; + interrupts = <0 2>; /* 1st word overwritten by mcp23s17-spi2-1-int-gpio parameter */ + }; + }; + }; + + // enable one or more mcp23s17s on spi2.2 + fragment@23 { + target = <&spi2>; + __dormant__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + mcp23s17_22: mcp23s17@2 { + compatible = "microchip,mcp23s17"; + gpio-controller; + #gpio-cells = <2>; + microchip,spi-present-mask = <0x00>; /* overwritten by mcp23s17-spi2-2-present parameter */ + reg = <2>; + spi-max-frequency = <500000>; + status = "okay"; + #interrupt-cells=<2>; + interrupts = <0 2>; /* 1st word overwritten by mcp23s17-spi2-2-int-gpio parameter */ + }; + }; + }; + + // Configure GPIO pin connected to INT(A/B) output of mcp23s08/17 on spi0.0 as a input with no pull-up/down + fragment@24 { + target = <&gpio>; + __dormant__ { + spi0_0_int_pins: spi0_0_int_pins { + brcm,pins = <0>; /* overwritten by mcp23s08/17-spi0-0-int-gpio parameter */ + brcm,function = <0>; + brcm,pull = <0>; + }; + }; + }; + + // Configure GPIO pin connected to INT(A/B) output of mcp23s08/17 on spi0.1 as a input with no pull-up/down + fragment@25 { + target = <&gpio>; + __dormant__ { + spi0_1_int_pins: spi0_1_int_pins { + brcm,pins = <0>; /* overwritten by mcp23s08/17-spi0-1-int-gpio parameter */ + brcm,function = <0>; + brcm,pull = <0>; + }; + }; + }; + + // Configure GPIO pin connected to INT(A/B) output of mcp23s08/17 on spi1.0 as a input with no pull-up/down + fragment@26 { + target = <&gpio>; + __dormant__ { + spi1_0_int_pins: spi1_0_int_pins { + brcm,pins = <0>; /* overwritten by mcp23s08/17-spi1-0-int-gpio parameter */ + brcm,function = <0>; + brcm,pull = <0>; + }; + }; + }; + + // Configure GPIO pin connected to INT(A/B) output of mcp23s08/17 on spi1.1 as a input with no pull-up/down + fragment@27 { + target = <&gpio>; + __dormant__ { + spi1_1_int_pins: spi1_1_int_pins { + brcm,pins = <0>; /* overwritten by mcp23s08/17-spi1-1-int-gpio parameter */ + brcm,function = <0>; + brcm,pull = <0>; + }; + }; + }; + + // Configure GPIO pin connected to INT(A/B) output of mcp23s08/17 on spi1.2 as a input with no pull-up/down + fragment@28 { + target = <&gpio>; + __dormant__ { + spi1_2_int_pins: spi1_2_int_pins { + brcm,pins = <0>; /* overwritten by mcp23s08/17-spi1-2-int-gpio parameter */ + brcm,function = <0>; + brcm,pull = <0>; + }; + }; + }; + + // Configure GPIO pin connected to INT(A/B) output of mcp23s08/17 on spi2.0 as a input with no pull-up/down + fragment@29 { + target = <&gpio>; + __dormant__ { + spi2_0_int_pins: spi2_0_int_pins { + brcm,pins = <0>; /* overwritten by mcp23s08/17-spi2-0-int-gpio parameter */ + brcm,function = <0>; + brcm,pull = <0>; + }; + }; + }; + + // Configure GPIO pin connected to INT(A/B) output of mcp23s08/17 on spi2.1 as a input with no pull-up/down + fragment@30 { + target = <&gpio>; + __dormant__ { + spi2_1_int_pins: spi2_1_int_pins { + brcm,pins = <0>; /* overwritten by mcp23s08/17-spi2-1-int-gpio parameter */ + brcm,function = <0>; + brcm,pull = <0>; + }; + }; + }; + + // Configure GPIO pin connected to INT(A/B) output of mcp23s08/17 on spi2.2 as a input with no pull-up/down + fragment@31 { + target = <&gpio>; + __dormant__ { + spi2_2_int_pins: spi2_2_int_pins { + brcm,pins = <0>; /* overwritten by mcp23s08/17-spi2-2-int-gpio parameter */ + brcm,function = <0>; + brcm,pull = <0>; + }; + }; + }; + + // Enable interrupts for a mcp23s08 on spi0.0. + // Use default active low interrupt signalling. + fragment@32 { + target = <&mcp23s08_00>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + }; + }; + + // Enable interrupts for a mcp23s08 on spi0.1. + // Use default active low interrupt signalling. + fragment@33 { + target = <&mcp23s08_01>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + }; + }; + + // Enable interrupts for a mcp23s08 on spi1.0. + // Use default active low interrupt signalling. + fragment@34 { + target = <&mcp23s08_10>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + }; + }; + + // Enable interrupts for a mcp23s08 on spi1.1. + // Use default active low interrupt signalling. + fragment@35 { + target = <&mcp23s08_11>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + }; + }; + + // Enable interrupts for a mcp23s08 on spi1.2. + // Use default active low interrupt signalling. + fragment@36 { + target = <&mcp23s08_12>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + }; + }; + + // Enable interrupts for a mcp23s08 on spi2.0. + // Use default active low interrupt signalling. + fragment@37 { + target = <&mcp23s08_20>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + }; + }; + + // Enable interrupts for a mcp23s08 on spi2.1. + // Use default active low interrupt signalling. + fragment@38 { + target = <&mcp23s08_21>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + }; + }; + + // Enable interrupts for a mcp23s08 on spi2.2. + // Use default active low interrupt signalling. + fragment@39 { + target = <&mcp23s08_22>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + }; + }; + + // Enable interrupts for a mcp23s17 on spi0.0. + // Enable mirroring so that either INTA or INTB output of mcp23s17 can be connected to the GPIO pin. + // Use default active low interrupt signalling. + fragment@40 { + target = <&mcp23s17_00>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + microchip,irq-mirror; + }; + }; + + // Enable interrupts for a mcp23s17 on spi0.1. + // Enable mirroring so that either INTA or INTB output of mcp23s17 can be connected to the GPIO pin. + // Configure INTA/B outputs of mcp23s08/17 as active low. + fragment@41 { + target = <&mcp23s17_01>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + microchip,irq-mirror; + }; + }; + + // Enable interrupts for a mcp23s17 on spi1.0. + // Enable mirroring so that either INTA or INTB output of mcp23s17 can be connected to the GPIO pin. + // Configure INTA/B outputs of mcp23s08/17 as active low. + fragment@42 { + target = <&mcp23s17_10>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + microchip,irq-mirror; + }; + }; + + // Enable interrupts for a mcp23s17 on spi1.1. + // Enable mirroring so that either INTA or INTB output of mcp23s17 can be connected to the GPIO pin. + // Configure INTA/B outputs of mcp23s08/17 as active low. + fragment@43 { + target = <&mcp23s17_11>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + microchip,irq-mirror; + }; + }; + + // Enable interrupts for a mcp23s17 on spi1.2. + // Enable mirroring so that either INTA or INTB output of mcp23s17 can be connected to the GPIO pin. + // Configure INTA/B outputs of mcp23s08/17 as active low. + fragment@44 { + target = <&mcp23s17_12>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + microchip,irq-mirror; + }; + }; + + // Enable interrupts for a mcp23s17 on spi2.0. + // Enable mirroring so that either INTA or INTB output of mcp23s17 can be connected to the GPIO pin. + // Configure INTA/B outputs of mcp23s08/17 as active low. + fragment@45 { + target = <&mcp23s17_20>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + microchip,irq-mirror; + }; + }; + + // Enable interrupts for a mcp23s17 on spi2.1. + // Enable mirroring so that either INTA or INTB output of mcp23s17 can be connected to the GPIO pin. + // Configure INTA/B outputs of mcp23s08/17 as active low. + fragment@46 { + target = <&mcp23s17_21>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + microchip,irq-mirror; + }; + }; + + // Enable interrupts for a mcp23s17 on spi2.2. + // Enable mirroring so that either INTA or INTB output of mcp23s17 can be connected to the GPIO pin. + // Configure INTA/B outputs of mcp23s08/17 as active low. + fragment@47 { + target = <&mcp23s17_22>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + microchip,irq-mirror; + }; + }; + + __overrides__ { + s08-spi0-0-present = <0>,"+0+8", <&mcp23s08_00>,"microchip,spi-present-mask:0"; + s08-spi0-1-present = <0>,"+1+9", <&mcp23s08_01>,"microchip,spi-present-mask:0"; + s08-spi1-0-present = <0>,"+2+10", <&mcp23s08_10>,"microchip,spi-present-mask:0"; + s08-spi1-1-present = <0>,"+3+11", <&mcp23s08_11>,"microchip,spi-present-mask:0"; + s08-spi1-2-present = <0>,"+4+12", <&mcp23s08_12>,"microchip,spi-present-mask:0"; + s08-spi2-0-present = <0>,"+5+13", <&mcp23s08_20>,"microchip,spi-present-mask:0"; + s08-spi2-1-present = <0>,"+6+14", <&mcp23s08_21>,"microchip,spi-present-mask:0"; + s08-spi2-2-present = <0>,"+7+15", <&mcp23s08_22>,"microchip,spi-present-mask:0"; + s17-spi0-0-present = <0>,"+0+16", <&mcp23s17_00>,"microchip,spi-present-mask:0"; + s17-spi0-1-present = <0>,"+1+17", <&mcp23s17_01>,"microchip,spi-present-mask:0"; + s17-spi1-0-present = <0>,"+2+18", <&mcp23s17_10>,"microchip,spi-present-mask:0"; + s17-spi1-1-present = <0>,"+3+19", <&mcp23s17_11>,"microchip,spi-present-mask:0"; + s17-spi1-2-present = <0>,"+4+20", <&mcp23s17_12>,"microchip,spi-present-mask:0"; + s17-spi2-0-present = <0>,"+5+21", <&mcp23s17_20>,"microchip,spi-present-mask:0"; + s17-spi2-1-present = <0>,"+6+22", <&mcp23s17_21>,"microchip,spi-present-mask:0"; + s17-spi2-2-present = <0>,"+7+23", <&mcp23s17_22>,"microchip,spi-present-mask:0"; + s08-spi0-0-int-gpio = <0>,"+24+32", <&spi0_0_int_pins>,"brcm,pins:0", <&mcp23s08_00>,"interrupts:0"; + s08-spi0-1-int-gpio = <0>,"+25+33", <&spi0_1_int_pins>,"brcm,pins:0", <&mcp23s08_01>,"interrupts:0"; + s08-spi1-0-int-gpio = <0>,"+26+34", <&spi1_0_int_pins>,"brcm,pins:0", <&mcp23s08_10>,"interrupts:0"; + s08-spi1-1-int-gpio = <0>,"+27+35", <&spi1_1_int_pins>,"brcm,pins:0", <&mcp23s08_11>,"interrupts:0"; + s08-spi1-2-int-gpio = <0>,"+28+36", <&spi1_2_int_pins>,"brcm,pins:0", <&mcp23s08_12>,"interrupts:0"; + s08-spi2-0-int-gpio = <0>,"+29+37", <&spi2_0_int_pins>,"brcm,pins:0", <&mcp23s08_20>,"interrupts:0"; + s08-spi2-1-int-gpio = <0>,"+30+38", <&spi2_1_int_pins>,"brcm,pins:0", <&mcp23s08_21>,"interrupts:0"; + s08-spi2-2-int-gpio = <0>,"+31+39", <&spi2_2_int_pins>,"brcm,pins:0", <&mcp23s08_22>,"interrupts:0"; + s17-spi0-0-int-gpio = <0>,"+24+40", <&spi0_0_int_pins>,"brcm,pins:0", <&mcp23s17_00>,"interrupts:0"; + s17-spi0-1-int-gpio = <0>,"+25+41", <&spi0_1_int_pins>,"brcm,pins:0", <&mcp23s17_01>,"interrupts:0"; + s17-spi1-0-int-gpio = <0>,"+26+42", <&spi1_0_int_pins>,"brcm,pins:0", <&mcp23s17_10>,"interrupts:0"; + s17-spi1-1-int-gpio = <0>,"+27+43", <&spi1_1_int_pins>,"brcm,pins:0", <&mcp23s17_11>,"interrupts:0"; + s17-spi1-2-int-gpio = <0>,"+28+44", <&spi1_2_int_pins>,"brcm,pins:0", <&mcp23s17_12>,"interrupts:0"; + s17-spi2-0-int-gpio = <0>,"+29+45", <&spi2_0_int_pins>,"brcm,pins:0", <&mcp23s17_20>,"interrupts:0"; + s17-spi2-1-int-gpio = <0>,"+30+46", <&spi2_1_int_pins>,"brcm,pins:0", <&mcp23s17_21>,"interrupts:0"; + s17-spi2-2-int-gpio = <0>,"+31+47", <&spi2_2_int_pins>,"brcm,pins:0", <&mcp23s17_22>,"interrupts:0"; + }; +}; + diff --git a/arch/arm/boot/dts/overlays/mcp2515-can0-overlay.dts b/arch/arm/boot/dts/overlays/mcp2515-can0-overlay.dts new file mode 100755 index 00000000000000..46f143d809cc86 --- /dev/null +++ b/arch/arm/boot/dts/overlays/mcp2515-can0-overlay.dts @@ -0,0 +1,73 @@ +/* + * Device tree overlay for mcp251x/can0 on spi0.0 + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + /* disable spi-dev for spi0.0 */ + fragment@0 { + target = <&spi0>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + + /* the interrupt pin of the can-controller */ + fragment@2 { + target = <&gpio>; + __overlay__ { + can0_pins: can0_pins { + brcm,pins = <25>; + brcm,function = <0>; /* input */ + }; + }; + }; + + /* the clock/oscillator of the can-controller */ + fragment@3 { + target-path = "/"; + __overlay__ { + /* external oscillator of mcp2515 on SPI0.0 */ + can0_osc: can0_osc { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <16000000>; + }; + }; + }; + + /* the spi config of the can-controller itself binding everything together */ + fragment@4 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + can0: mcp2515@0 { + reg = <0>; + compatible = "microchip,mcp2515"; + pinctrl-names = "default"; + pinctrl-0 = <&can0_pins>; + spi-max-frequency = <10000000>; + interrupt-parent = <&gpio>; + interrupts = <25 8>; /* IRQ_TYPE_LEVEL_LOW */ + clocks = <&can0_osc>; + }; + }; + }; + __overrides__ { + oscillator = <&can0_osc>,"clock-frequency:0"; + spimaxfrequency = <&can0>,"spi-max-frequency:0"; + interrupt = <&can0_pins>,"brcm,pins:0",<&can0>,"interrupts:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/mcp2515-can1-overlay.dts b/arch/arm/boot/dts/overlays/mcp2515-can1-overlay.dts new file mode 100644 index 00000000000000..0a8dd576818e9b --- /dev/null +++ b/arch/arm/boot/dts/overlays/mcp2515-can1-overlay.dts @@ -0,0 +1,73 @@ +/* + * Device tree overlay for mcp251x/can1 on spi0.1 edited by petit_miner + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + /* disable spi-dev for spi0.1 */ + fragment@0 { + target = <&spi0>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&spidev1>; + __overlay__ { + status = "disabled"; + }; + }; + + /* the interrupt pin of the can-controller */ + fragment@2 { + target = <&gpio>; + __overlay__ { + can1_pins: can1_pins { + brcm,pins = <25>; + brcm,function = <0>; /* input */ + }; + }; + }; + + /* the clock/oscillator of the can-controller */ + fragment@3 { + target-path = "/"; + __overlay__ { + /* external oscillator of mcp2515 on spi0.1 */ + can1_osc: can1_osc { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <16000000>; + }; + }; + }; + + /* the spi config of the can-controller itself binding everything together */ + fragment@4 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + can1: mcp2515@1 { + reg = <1>; + compatible = "microchip,mcp2515"; + pinctrl-names = "default"; + pinctrl-0 = <&can1_pins>; + spi-max-frequency = <10000000>; + interrupt-parent = <&gpio>; + interrupts = <25 8>; /* IRQ_TYPE_LEVEL_LOW */ + clocks = <&can1_osc>; + }; + }; + }; + __overrides__ { + oscillator = <&can1_osc>,"clock-frequency:0"; + spimaxfrequency = <&can1>,"spi-max-frequency:0"; + interrupt = <&can1_pins>,"brcm,pins:0",<&can1>,"interrupts:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/mcp2515-overlay.dts b/arch/arm/boot/dts/overlays/mcp2515-overlay.dts new file mode 100644 index 00000000000000..cda1fb0b119923 --- /dev/null +++ b/arch/arm/boot/dts/overlays/mcp2515-overlay.dts @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: (GPL-2.0 OR MIT) + +/dts-v1/; +/plugin/; + +#include <dt-bindings/gpio/gpio.h> +#include <dt-bindings/interrupt-controller/irq.h> +#include <dt-bindings/pinctrl/bcm2835.h> + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spidev0>; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@1 { + target = <&spidev1>; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@2 { + target-path = "spi1/spidev@0"; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@3 { + target-path = "spi1/spidev@1"; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@4 { + target-path = "spi1/spidev@2"; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@5 { + target-path = "spi2/spidev@0"; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@6 { + target-path = "spi2/spidev@1"; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@7 { + target-path = "spi2/spidev@2"; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@8 { + target = <&gpio>; + __overlay__ { + mcp2515_pins: mcp2515_pins { + brcm,pins = <25>; + brcm,function = <BCM2835_FSEL_GPIO_IN>; + }; + }; + }; + + fragment@9 { + target-path = "/clocks"; + __overlay__ { + clk_mcp2515_osc: mcp2515-osc { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <16000000>; + }; + }; + }; + + mcp2515_frag: fragment@10 { + target = <&spi0>; + __overlay__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + mcp2515: mcp2515@0 { + compatible = "microchip,mcp2515"; + reg = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&mcp2515_pins>; + spi-max-frequency = <10000000>; + interrupt-parent = <&gpio>; + interrupts = <25 IRQ_TYPE_LEVEL_LOW>; + clocks = <&clk_mcp2515_osc>; + }; + }; + }; + + __overrides__ { + spi0-0 = <0>, "+0", + <&mcp2515_frag>, "target:0=", <&spi0>, + <&mcp2515>, "reg:0=0", + <&mcp2515_pins>, "name=mcp2515_spi0_0_pins", + <&clk_mcp2515_osc>, "name=mcp2515-spi0-0-osc"; + spi0-1 = <0>, "+1", + <&mcp2515_frag>, "target:0=", <&spi0>, + <&mcp2515>, "reg:0=1", + <&mcp2515_pins>, "name=mcp2515_spi0_1_pins", + <&clk_mcp2515_osc>, "name=mcp2515-spi0-1-osc"; + spi1-0 = <0>, "+2", + <&mcp2515_frag>, "target:0=", <&spi1>, + <&mcp2515>, "reg:0=0", + <&mcp2515_pins>, "name=mcp2515_spi1_0_pins", + <&clk_mcp2515_osc>, "name=mcp2515-spi1-0-osc"; + spi1-1 = <0>, "+3", + <&mcp2515_frag>, "target:0=", <&spi1>, + <&mcp2515>, "reg:0=1", + <&mcp2515_pins>, "name=mcp2515_spi1_1_pins", + <&clk_mcp2515_osc>, "name=mcp2515-spi1-1-osc"; + spi1-2 = <0>, "+4", + <&mcp2515_frag>, "target:0=", <&spi1>, + <&mcp2515>, "reg:0=2", + <&mcp2515_pins>, "name=mcp2515_spi1_2_pins", + <&clk_mcp2515_osc>, "name=mcp2515-spi1-2-osc"; + spi2-0 = <0>, "+5", + <&mcp2515_frag>, "target:0=", <&spi2>, + <&mcp2515>, "reg:0=0", + <&mcp2515_pins>, "name=mcp2515_spi2_0_pins", + <&clk_mcp2515_osc>, "name=mcp2515-spi2-0-osc"; + spi2-1 = <0>, "+6", + <&mcp2515_frag>, "target:0=", <&spi2>, + <&mcp2515>, "reg:0=1", + <&mcp2515_pins>, "name=mcp2515_spi2_1_pins", + <&clk_mcp2515_osc>, "name=mcp2515-spi2-1-osc"; + spi2-2 = <0>, "+7", + <&mcp2515_frag>, "target:0=", <&spi2>, + <&mcp2515>, "reg:0=2", + <&mcp2515_pins>, "name=mcp2515_spi2_2_pins", + <&clk_mcp2515_osc>, "name=mcp2515-spi2-2-osc"; + oscillator = <&clk_mcp2515_osc>, "clock-frequency:0"; + speed = <&mcp2515>, "spi-max-frequency:0"; + interrupt = <&mcp2515_pins>, "brcm,pins:0", + <&mcp2515>, "interrupts:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/mcp251xfd-overlay.dts b/arch/arm/boot/dts/overlays/mcp251xfd-overlay.dts new file mode 100644 index 00000000000000..65c861bbd34010 --- /dev/null +++ b/arch/arm/boot/dts/overlays/mcp251xfd-overlay.dts @@ -0,0 +1,226 @@ +// SPDX-License-Identifier: (GPL-2.0 OR MIT) + +/dts-v1/; +/plugin/; + +#include <dt-bindings/gpio/gpio.h> +#include <dt-bindings/interrupt-controller/irq.h> +#include <dt-bindings/pinctrl/bcm2835.h> + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spidev0>; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@1 { + target = <&spidev1>; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@2 { + target-path = "spi1/spidev@0"; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@3 { + target-path = "spi1/spidev@1"; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@4 { + target-path = "spi1/spidev@2"; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@5 { + target-path = "spi2/spidev@0"; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@6 { + target-path = "spi2/spidev@1"; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@7 { + target-path = "spi2/spidev@2"; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@8 { + target = <&gpio>; + __overlay__ { + mcp251xfd_pins: mcp251xfd_pins { + brcm,pins = <25>; + brcm,function = <BCM2835_FSEL_GPIO_IN>; + }; + }; + }; + + fragment@9 { + target-path = "/clocks"; + __overlay__ { + clk_mcp251xfd_osc: mcp251xfd-osc { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <40000000>; + }; + }; + }; + + mcp251xfd_frag: fragment@10 { + target = <&spi0>; + __overlay__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + mcp251xfd: mcp251xfd@0 { + compatible = "microchip,mcp251xfd"; + reg = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&mcp251xfd_pins>; + spi-max-frequency = <20000000>; + interrupt-parent = <&gpio>; + interrupts = <25 IRQ_TYPE_LEVEL_LOW>; + clocks = <&clk_mcp251xfd_osc>; + }; + }; + }; + + fragment@11 { + target = <&mcp251xfd>; + mcp251xfd_rx_int_gpios: __dormant__ { + microchip,rx-int-gpios = <&gpio 255 GPIO_ACTIVE_LOW>; + }; + }; + + fragment@12 { + target = <&gpio>; + __dormant__ { + mcp251xfd_xceiver_pins: mcp251xfd_xceiver_pins { + brcm,pins = <255>; + brcm,function = <BCM2835_FSEL_GPIO_OUT>; + }; + }; + }; + + fragment@13 { + target-path = "/"; + __dormant__ { + reg_mcp251xfd_xceiver: reg_mcp251xfd_xceiver { + compatible = "regulator-fixed"; + regulator-name = "mcp251xfd_xceiver"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + gpio = <&gpio 4 GPIO_ACTIVE_HIGH>; + pinctrl-names = "default"; + pinctrl-0 = <&mcp251xfd_xceiver_pins>; + }; + }; + }; + + fragment@14 { + target = <&mcp251xfd>; + __dormant__ { + xceiver-supply = <®_mcp251xfd_xceiver>; + }; + }; + + __overrides__ { + spi0-0 = <0>, "+0", + <&mcp251xfd_frag>, "target:0=", <&spi0>, + <&mcp251xfd>, "reg:0=0", + <&mcp251xfd_pins>, "name=mcp251xfd_spi0_0_pins", + <&clk_mcp251xfd_osc>, "name=mcp251xfd-spi0-0-osc", + <&mcp251xfd_xceiver_pins>, "name=mcp251xfd_spi0_0_xceiver_pins", + <®_mcp251xfd_xceiver>, "name=reg-mcp251xfd-spi0-0-xceiver", + <®_mcp251xfd_xceiver>, "regulator-name=mcp251xfd-spi0-0-xceiver"; + spi0-1 = <0>, "+1", + <&mcp251xfd_frag>, "target:0=", <&spi0>, + <&mcp251xfd>, "reg:0=1", + <&mcp251xfd_pins>, "name=mcp251xfd_spi0_1_pins", + <&clk_mcp251xfd_osc>, "name=mcp251xfd-spi0-1-osc", + <&mcp251xfd_xceiver_pins>, "name=mcp251xfd_spi0_1_xceiver_pins", + <®_mcp251xfd_xceiver>, "name=reg-mcp251xfd-spi0-1-xceiver", + <®_mcp251xfd_xceiver>, "regulator-name=mcp251xfd-spi0-1-xceiver"; + spi1-0 = <0>, "+2", + <&mcp251xfd_frag>, "target:0=", <&spi1>, + <&mcp251xfd>, "reg:0=0", + <&mcp251xfd_pins>, "name=mcp251xfd_spi1_0_pins", + <&clk_mcp251xfd_osc>, "name=mcp251xfd-spi1-0-osc", + <&mcp251xfd_xceiver_pins>, "name=mcp251xfd_spi1_0_xceiver_pins", + <®_mcp251xfd_xceiver>, "name=reg-mcp251xfd-spi1-0-xceiver", + <®_mcp251xfd_xceiver>, "regulator-name=mcp251xfd-spi1-0-xceiver"; + spi1-1 = <0>, "+3", + <&mcp251xfd_frag>, "target:0=", <&spi1>, + <&mcp251xfd>, "reg:0=1", + <&mcp251xfd_pins>, "name=mcp251xfd_spi1_1_pins", + <&clk_mcp251xfd_osc>, "name=mcp251xfd-spi1-1-osc", + <&mcp251xfd_xceiver_pins>, "name=mcp251xfd_spi1_1_xceiver_pins", + <®_mcp251xfd_xceiver>, "name=reg-mcp251xfd-spi1-1-xceiver", + <®_mcp251xfd_xceiver>, "regulator-name=mcp251xfd-spi1-1-xceiver"; + spi1-2 = <0>, "+4", + <&mcp251xfd_frag>, "target:0=", <&spi1>, + <&mcp251xfd>, "reg:0=2", + <&mcp251xfd_pins>, "name=mcp251xfd_spi1_2_pins", + <&clk_mcp251xfd_osc>, "name=mcp251xfd-spi1-2-osc", + <&mcp251xfd_xceiver_pins>, "name=mcp251xfd_spi1_2_xceiver_pins", + <®_mcp251xfd_xceiver>, "name=reg-mcp251xfd-spi1-2-xceiver", + <®_mcp251xfd_xceiver>, "regulator-name=mcp251xfd-spi1-2-xceiver"; + spi2-0 = <0>, "+5", + <&mcp251xfd_frag>, "target:0=", <&spi2>, + <&mcp251xfd>, "reg:0=0", + <&mcp251xfd_pins>, "name=mcp251xfd_spi2_0_pins", + <&clk_mcp251xfd_osc>, "name=mcp251xfd-spi2-0-osc", + <&mcp251xfd_xceiver_pins>, "name=mcp251xfd_spi2_0_xceiver_pins", + <®_mcp251xfd_xceiver>, "name=reg-mcp251xfd-spi2-0-xceiver", + <®_mcp251xfd_xceiver>, "regulator-name=mcp251xfd-spi2-0-xceiver"; + spi2-1 = <0>, "+6", + <&mcp251xfd_frag>, "target:0=", <&spi2>, + <&mcp251xfd>, "reg:0=1", + <&mcp251xfd_pins>, "name=mcp251xfd_spi2_1_pins", + <&clk_mcp251xfd_osc>, "name=mcp251xfd-spi2-1-osc", + <&mcp251xfd_xceiver_pins>, "name=mcp251xfd_spi2_1_xceiver_pins", + <®_mcp251xfd_xceiver>, "name=reg-mcp251xfd-spi2-1-xceiver", + <®_mcp251xfd_xceiver>, "regulator-name=mcp251xfd-spi2-1-xceiver"; + spi2-2 = <0>, "+7", + <&mcp251xfd_frag>, "target:0=", <&spi2>, + <&mcp251xfd>, "reg:0=2", + <&mcp251xfd_pins>, "name=mcp251xfd_spi2_2_pins", + <&clk_mcp251xfd_osc>, "name=mcp251xfd-spi2-2-osc", + <&mcp251xfd_xceiver_pins>, "name=mcp251xfd_spi2_2_xceiver_pins", + <®_mcp251xfd_xceiver>, "name=reg-mcp251xfd-spi2-2-xceiver", + <®_mcp251xfd_xceiver>, "regulator-name=mcp251xfd-spi2-2-xceiver"; + oscillator = <&clk_mcp251xfd_osc>, "clock-frequency:0"; + speed = <&mcp251xfd>, "spi-max-frequency:0"; + interrupt = <&mcp251xfd_pins>, "brcm,pins:0", + <&mcp251xfd>, "interrupts:0"; + rx_interrupt = <0>, "+11", + <&mcp251xfd_pins>, "brcm,pins:4", + <&mcp251xfd_rx_int_gpios>, "microchip,rx-int-gpios:4"; + xceiver_enable = <0>, "+12+13+14", + <&mcp251xfd_xceiver_pins>, "brcm,pins:0", + <®_mcp251xfd_xceiver>, "gpio:4"; + xceiver_active_high = <®_mcp251xfd_xceiver>, "enable-active-high?"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/mcp3008-overlay.dts b/arch/arm/boot/dts/overlays/mcp3008-overlay.dts new file mode 100755 index 00000000000000..957fdb9310af42 --- /dev/null +++ b/arch/arm/boot/dts/overlays/mcp3008-overlay.dts @@ -0,0 +1,205 @@ +/* + * Device tree overlay for Microchip mcp3008 10-Bit A/D Converters + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spidev0>; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@1 { + target = <&spidev1>; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@2 { + target-path = "spi1/spidev@0"; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@3 { + target-path = "spi1/spidev@1"; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@4 { + target-path = "spi1/spidev@2"; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@5 { + target-path = "spi2/spidev@0"; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@6 { + target-path = "spi2/spidev@1"; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@7 { + target-path = "spi2/spidev@2"; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@8 { + target = <&spi0>; + __dormant__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + mcp3008_00: mcp3008@0 { + compatible = "microchip,mcp3008"; + reg = <0>; + spi-max-frequency = <1600000>; + }; + }; + }; + + fragment@9 { + target = <&spi0>; + __dormant__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + mcp3008_01: mcp3008@1 { + compatible = "microchip,mcp3008"; + reg = <1>; + spi-max-frequency = <1600000>; + }; + }; + }; + + fragment@10 { + target = <&spi1>; + __dormant__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + mcp3008_10: mcp3008@0 { + compatible = "microchip,mcp3008"; + reg = <0>; + spi-max-frequency = <1600000>; + }; + }; + }; + + fragment@11 { + target = <&spi1>; + __dormant__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + mcp3008_11: mcp3008@1 { + compatible = "microchip,mcp3008"; + reg = <1>; + spi-max-frequency = <1600000>; + }; + }; + }; + + fragment@12 { + target = <&spi1>; + __dormant__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + mcp3008_12: mcp3008@2 { + compatible = "microchip,mcp3008"; + reg = <2>; + spi-max-frequency = <1600000>; + }; + }; + }; + + fragment@13 { + target = <&spi2>; + __dormant__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + mcp3008_20: mcp3008@0 { + compatible = "microchip,mcp3008"; + reg = <0>; + spi-max-frequency = <1600000>; + }; + }; + }; + + fragment@14 { + target = <&spi2>; + __dormant__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + mcp3008_21: mcp3008@1 { + compatible = "microchip,mcp3008"; + reg = <1>; + spi-max-frequency = <1600000>; + }; + }; + }; + + fragment@15 { + target = <&spi2>; + __dormant__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + mcp3008_22: mcp3008@2 { + compatible = "microchip,mcp3008"; + reg = <2>; + spi-max-frequency = <1600000>; + }; + }; + }; + + __overrides__ { + spi0-0-present = <0>, "+0+8"; + spi0-1-present = <0>, "+1+9"; + spi1-0-present = <0>, "+2+10"; + spi1-1-present = <0>, "+3+11"; + spi1-2-present = <0>, "+4+12"; + spi2-0-present = <0>, "+5+13"; + spi2-1-present = <0>, "+6+14"; + spi2-2-present = <0>, "+7+15"; + spi0-0-speed = <&mcp3008_00>, "spi-max-frequency:0"; + spi0-1-speed = <&mcp3008_01>, "spi-max-frequency:0"; + spi1-0-speed = <&mcp3008_10>, "spi-max-frequency:0"; + spi1-1-speed = <&mcp3008_11>, "spi-max-frequency:0"; + spi1-2-speed = <&mcp3008_12>, "spi-max-frequency:0"; + spi2-0-speed = <&mcp3008_20>, "spi-max-frequency:0"; + spi2-1-speed = <&mcp3008_21>, "spi-max-frequency:0"; + spi2-2-speed = <&mcp3008_22>, "spi-max-frequency:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/mcp3202-overlay.dts b/arch/arm/boot/dts/overlays/mcp3202-overlay.dts new file mode 100755 index 00000000000000..8e4e9f60f285fa --- /dev/null +++ b/arch/arm/boot/dts/overlays/mcp3202-overlay.dts @@ -0,0 +1,205 @@ +/* + * Device tree overlay for Microchip mcp3202 12-Bit A/D Converters + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spidev0>; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@1 { + target = <&spidev1>; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@2 { + target-path = "spi1/spidev@0"; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@3 { + target-path = "spi1/spidev@1"; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@4 { + target-path = "spi1/spidev@2"; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@5 { + target-path = "spi2/spidev@0"; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@6 { + target-path = "spi2/spidev@1"; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@7 { + target-path = "spi2/spidev@2"; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@8 { + target = <&spi0>; + __dormant__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + mcp3202_00: mcp3202@0 { + compatible = "mcp3202"; + reg = <0>; + spi-max-frequency = <1600000>; + }; + }; + }; + + fragment@9 { + target = <&spi0>; + __dormant__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + mcp3202_01: mcp3202@1 { + compatible = "mcp3202"; + reg = <1>; + spi-max-frequency = <1600000>; + }; + }; + }; + + fragment@10 { + target = <&spi1>; + __dormant__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + mcp3202_10: mcp3202@0 { + compatible = "mcp3202"; + reg = <0>; + spi-max-frequency = <1600000>; + }; + }; + }; + + fragment@11 { + target = <&spi1>; + __dormant__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + mcp3202_11: mcp3202@1 { + compatible = "mcp3202"; + reg = <1>; + spi-max-frequency = <1600000>; + }; + }; + }; + + fragment@12 { + target = <&spi1>; + __dormant__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + mcp3202_12: mcp3202@2 { + compatible = "mcp3202"; + reg = <2>; + spi-max-frequency = <1600000>; + }; + }; + }; + + fragment@13 { + target = <&spi2>; + __dormant__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + mcp3202_20: mcp3202@0 { + compatible = "mcp3202"; + reg = <0>; + spi-max-frequency = <1600000>; + }; + }; + }; + + fragment@14 { + target = <&spi2>; + __dormant__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + mcp3202_21: mcp3202@1 { + compatible = "mcp3202"; + reg = <1>; + spi-max-frequency = <1600000>; + }; + }; + }; + + fragment@15 { + target = <&spi2>; + __dormant__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + mcp3202_22: mcp3202@2 { + compatible = "mcp3202"; + reg = <2>; + spi-max-frequency = <1600000>; + }; + }; + }; + + __overrides__ { + spi0-0-present = <0>, "+0+8"; + spi0-1-present = <0>, "+1+9"; + spi1-0-present = <0>, "+2+10"; + spi1-1-present = <0>, "+3+11"; + spi1-2-present = <0>, "+4+12"; + spi2-0-present = <0>, "+5+13"; + spi2-1-present = <0>, "+6+14"; + spi2-2-present = <0>, "+7+15"; + spi0-0-speed = <&mcp3202_00>, "spi-max-frequency:0"; + spi0-1-speed = <&mcp3202_01>, "spi-max-frequency:0"; + spi1-0-speed = <&mcp3202_10>, "spi-max-frequency:0"; + spi1-1-speed = <&mcp3202_11>, "spi-max-frequency:0"; + spi1-2-speed = <&mcp3202_12>, "spi-max-frequency:0"; + spi2-0-speed = <&mcp3202_20>, "spi-max-frequency:0"; + spi2-1-speed = <&mcp3202_21>, "spi-max-frequency:0"; + spi2-2-speed = <&mcp3202_22>, "spi-max-frequency:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/mcp342x-overlay.dts b/arch/arm/boot/dts/overlays/mcp342x-overlay.dts new file mode 100644 index 00000000000000..714eca5a4b5e0c --- /dev/null +++ b/arch/arm/boot/dts/overlays/mcp342x-overlay.dts @@ -0,0 +1,164 @@ +// Overlay for MCP3421-8 ADCs from Microchip Semiconductor + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2c1>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + status = "okay"; + + mcp3421: mcp@68 { + reg = <0x68>; + compatible = "microchip,mcp3421"; + + status = "okay"; + }; + }; + }; + + fragment@1 { + target = <&i2c1>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + status = "okay"; + + mcp3422: mcp@68 { + reg = <0x68>; + compatible = "microchip,mcp3422"; + + status = "okay"; + }; + }; + }; + + fragment@2 { + target = <&i2c1>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + status = "okay"; + + mcp3423: mcp@68 { + reg = <0x68>; + compatible = "microchip,mcp3423"; + + status = "okay"; + }; + }; + }; + + fragment@3 { + target = <&i2c1>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + status = "okay"; + + mcp3424: mcp@68 { + reg = <0x68>; + compatible = "microchip,mcp3424"; + + status = "okay"; + }; + }; + }; + + fragment@4 { + target = <&i2c1>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + status = "okay"; + + mcp3425: mcp@68 { + reg = <0x68>; + compatible = "microchip,mcp3425","mcp3425"; + + status = "okay"; + }; + }; + }; + + fragment@5 { + target = <&i2c1>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + status = "okay"; + + mcp3426: mcp@68 { + reg = <0x68>; + compatible = "microchip,mcp3426"; + + status = "okay"; + }; + }; + }; + + fragment@6 { + target = <&i2c1>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + status = "okay"; + + mcp3427: mcp@68 { + reg = <0x68>; + compatible = "microchip,mcp3427"; + + status = "okay"; + }; + }; + }; + + fragment@7 { + target = <&i2c1>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + status = "okay"; + + mcp3428: mcp@68 { + reg = <0x68>; + compatible = "microchip,mcp3428"; + + status = "okay"; + }; + }; + }; + + __overrides__ { + addr = <&mcp3421>,"reg:0", + <&mcp3422>,"reg:0", + <&mcp3423>,"reg:0", + <&mcp3424>,"reg:0", + <&mcp3425>,"reg:0", + <&mcp3426>,"reg:0", + <&mcp3427>,"reg:0", + <&mcp3428>,"reg:0"; + mcp3421 = <0>,"=0"; + mcp3422 = <0>,"=1"; + mcp3423 = <0>,"=2"; + mcp3424 = <0>,"=3"; + mcp3425 = <0>,"=4"; + mcp3426 = <0>,"=5"; + mcp3427 = <0>,"=6"; + mcp3428 = <0>,"=7"; + }; +}; + diff --git a/arch/arm/boot/dts/overlays/media-center-overlay.dts b/arch/arm/boot/dts/overlays/media-center-overlay.dts new file mode 100644 index 00000000000000..4bc2eaa1f2153e --- /dev/null +++ b/arch/arm/boot/dts/overlays/media-center-overlay.dts @@ -0,0 +1,86 @@ +/* + * Device Tree overlay for Media Center HAT by Pi Supply + * + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@1 { + target = <&spidev1>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@2 { + target = <&gpio>; + __overlay__ { + rpi_display_pins: rpi_display_pins { + brcm,pins = <12 23 24 25>; + brcm,function = <1 1 1 0>; /* out out out in */ + brcm,pull = <0 0 0 2>; /* - - - up */ + }; + }; + }; + + fragment@3 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + rpidisplay: rpi-display@0{ + compatible = "ilitek,ili9341"; + reg = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&rpi_display_pins>; + + spi-max-frequency = <32000000>; + rotate = <90>; + bgr; + fps = <30>; + buswidth = <8>; + reset-gpios = <&gpio 23 1>; + dc-gpios = <&gpio 24 0>; + led-gpios = <&gpio 12 0>; + debug = <0>; + }; + + rpidisplay_ts: rpi-display-ts@1 { + compatible = "ti,ads7846"; + reg = <1>; + + spi-max-frequency = <2000000>; + interrupts = <25 2>; /* high-to-low edge triggered */ + interrupt-parent = <&gpio>; + pendown-gpio = <&gpio 25 1>; + ti,x-plate-ohms = /bits/ 16 <60>; + ti,pressure-max = /bits/ 16 <255>; + }; + }; + }; + + __overrides__ { + speed = <&rpidisplay>,"spi-max-frequency:0"; + rotate = <&rpidisplay>,"rotate:0"; + fps = <&rpidisplay>,"fps:0"; + debug = <&rpidisplay>,"debug:0"; + xohms = <&rpidisplay_ts>,"ti,x-plate-ohms;0"; + swapxy = <&rpidisplay_ts>,"ti,swap-xy?"; + backlight = <&rpidisplay>,"led-gpios:4", + <&rpi_display_pins>,"brcm,pins:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/merus-amp-overlay.dts b/arch/arm/boot/dts/overlays/merus-amp-overlay.dts new file mode 100644 index 00000000000000..96159a48d33f61 --- /dev/null +++ b/arch/arm/boot/dts/overlays/merus-amp-overlay.dts @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Definitions for Infineon Merus-Amp +/dts-v1/; +/plugin/; +#include <dt-bindings/pinctrl/bcm2835.h> +#include <dt-bindings/gpio/gpio.h> + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_producer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&gpio>; + __overlay__ { + merus_amp_pins: merus_amp_pins { + brcm,pins = <23 8>; + brcm,function = <0 0>; + brcm,pull = <2 0>; + }; + }; + }; + + fragment@2 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + merus_amp: ma120x0p@20 { + #sound-dai-cells = <0>; + compatible = "ma,ma120x0p"; + reg = <0x20>; + status = "okay"; + pinctrl-names = "default"; + pinctrl-0 = <&merus_amp_pins>; + enable_gp-gpios = <&gpio 14 GPIO_ACTIVE_HIGH>; + mute_gp-gpios = <&gpio 15 GPIO_ACTIVE_HIGH>; + booster_gp-gpios = <&gpio 17 GPIO_ACTIVE_HIGH>; + error_gp-gpios = <&gpio 23 GPIO_ACTIVE_HIGH>; + }; + }; + }; + + fragment@3 { + target = <&sound>; + __overlay__ { + compatible = "merus,merus-amp"; + i2s-controller = <&i2s_clk_producer>; + status = "okay"; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/midi-uart0-overlay.dts b/arch/arm/boot/dts/overlays/midi-uart0-overlay.dts new file mode 100644 index 00000000000000..f7e44d29e10102 --- /dev/null +++ b/arch/arm/boot/dts/overlays/midi-uart0-overlay.dts @@ -0,0 +1,36 @@ +/dts-v1/; +/plugin/; + +#include <dt-bindings/clock/bcm2835.h> + +/* + * Fake a higher clock rate to get a larger divisor, and thereby a lower + * baudrate. The real clock is 48MHz, which we scale so that requesting + * 38.4kHz results in an actual 31.25kHz. + * + * 48000000*38400/31250 = 58982400 + */ + +/{ + compatible = "brcm,bcm2835"; + + fragment@0 { + target-path = "/"; + __overlay__ { + midi_clk: midi_clk { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-output-names = "uart0_pclk"; + clock-frequency = <58982400>; + }; + }; + }; + + fragment@1 { + target = <&uart0>; + __overlay__ { + clocks = <&midi_clk>, + <&clocks BCM2835_CLOCK_VPU>; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/midi-uart0-pi5-overlay.dts b/arch/arm/boot/dts/overlays/midi-uart0-pi5-overlay.dts new file mode 100644 index 00000000000000..837d1b014e28a5 --- /dev/null +++ b/arch/arm/boot/dts/overlays/midi-uart0-pi5-overlay.dts @@ -0,0 +1,35 @@ +/dts-v1/; +/plugin/; + +#include <dt-bindings/clock/rp1.h> + +/* + * Fake a higher clock rate to get a larger divisor, and thereby a lower + * baudrate. The real clock is 50MHz, which we scale so that requesting + * 38.4kHz results in an actual 31.25kHz. + * + * 50000000*38400/31250 = 61440000 + */ + +/{ + compatible = "brcm,bcm2712"; + + fragment@0 { + target-path = "/"; + __overlay__ { + midi_clk: midi_clk0 { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-output-names = "uart0_pclk"; + clock-frequency = <61440000>; + }; + }; + }; + + fragment@1 { + target = <&uart0>; + __overlay__ { + clocks = <&midi_clk &rp1_clocks RP1_PLL_SYS_PRI_PH>; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/midi-uart1-overlay.dts b/arch/arm/boot/dts/overlays/midi-uart1-overlay.dts new file mode 100644 index 00000000000000..e0bc410acbff3a --- /dev/null +++ b/arch/arm/boot/dts/overlays/midi-uart1-overlay.dts @@ -0,0 +1,43 @@ +/dts-v1/; +/plugin/; + +#include <dt-bindings/clock/bcm2835-aux.h> + +/* + * Fake a higher clock rate to get a larger divisor, and thereby a lower + * baudrate. The real clock is 48MHz, which we scale so that requesting + * 38.4kHz results in an actual 31.25kHz. + * + * 48000000*38400/31250 = 58982400 + */ + +/{ + compatible = "brcm,bcm2835"; + + fragment@0 { + target-path = "/clocks"; + __overlay__ { + midi_clk: clock@5 { + compatible = "fixed-factor-clock"; + #clock-cells = <0>; + clocks = <&aux BCM2835_AUX_CLOCK_UART>; + clock-mult = <38400>; + clock-div = <31250>; + }; + }; + }; + + fragment@1 { + target = <&uart1>; + __overlay__ { + clocks = <&midi_clk>; + }; + }; + + fragment@2 { + target = <&aux>; + __overlay__ { + clock-output-names = "aux_uart", "aux_spi1", "aux_spi2"; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/midi-uart1-pi5-overlay.dts b/arch/arm/boot/dts/overlays/midi-uart1-pi5-overlay.dts new file mode 100644 index 00000000000000..e803876622a96c --- /dev/null +++ b/arch/arm/boot/dts/overlays/midi-uart1-pi5-overlay.dts @@ -0,0 +1,35 @@ +/dts-v1/; +/plugin/; + +#include <dt-bindings/clock/rp1.h> + +/* + * Fake a higher clock rate to get a larger divisor, and thereby a lower + * baudrate. The real clock is 50MHz, which we scale so that requesting + * 38.4kHz results in an actual 31.25kHz. + * + * 50000000*38400/31250 = 61440000 + */ + +/{ + compatible = "brcm,bcm2712"; + + fragment@0 { + target-path = "/"; + __overlay__ { + midi_clk: midi_clk1 { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-output-names = "uart1_pclk"; + clock-frequency = <61440000>; + }; + }; + }; + + fragment@1 { + target = <&uart1>; + __overlay__ { + clocks = <&midi_clk &rp1_clocks RP1_PLL_SYS_PRI_PH>; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/midi-uart2-overlay.dts b/arch/arm/boot/dts/overlays/midi-uart2-overlay.dts new file mode 100644 index 00000000000000..5c6985f41ea25d --- /dev/null +++ b/arch/arm/boot/dts/overlays/midi-uart2-overlay.dts @@ -0,0 +1,37 @@ +/dts-v1/; +/plugin/; + +#include <dt-bindings/clock/bcm2835.h> + +/* + * Fake a higher clock rate to get a larger divisor, and thereby a lower + * baudrate. The real clock is 48MHz, which we scale so that requesting + * 38.4kHz results in an actual 31.25kHz. + * + * 48000000*38400/31250 = 58982400 + */ + +/{ + compatible = "brcm,bcm2711"; + + fragment@0 { + target-path = "/"; + __overlay__ { + midi_clk: midi_clk2 { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-output-names = "uart2_pclk"; + clock-frequency = <58982400>; + }; + }; + }; + + fragment@1 { + target = <&uart2>; + __overlay__ { + clocks = <&midi_clk>, + <&clocks BCM2835_CLOCK_VPU>; + }; + }; +}; + diff --git a/arch/arm/boot/dts/overlays/midi-uart2-pi5-overlay.dts b/arch/arm/boot/dts/overlays/midi-uart2-pi5-overlay.dts new file mode 100644 index 00000000000000..4f07e7de2df333 --- /dev/null +++ b/arch/arm/boot/dts/overlays/midi-uart2-pi5-overlay.dts @@ -0,0 +1,35 @@ +/dts-v1/; +/plugin/; + +#include <dt-bindings/clock/rp1.h> + +/* + * Fake a higher clock rate to get a larger divisor, and thereby a lower + * baudrate. The real clock is 50MHz, which we scale so that requesting + * 38.4kHz results in an actual 31.25kHz. + * + * 50000000*38400/31250 = 61440000 + */ + +/{ + compatible = "brcm,bcm2712"; + + fragment@0 { + target-path = "/"; + __overlay__ { + midi_clk: midi_clk2 { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-output-names = "uart2_pclk"; + clock-frequency = <61440000>; + }; + }; + }; + + fragment@1 { + target = <&uart2>; + __overlay__ { + clocks = <&midi_clk &rp1_clocks RP1_PLL_SYS_PRI_PH>; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/midi-uart3-overlay.dts b/arch/arm/boot/dts/overlays/midi-uart3-overlay.dts new file mode 100644 index 00000000000000..052027db05648e --- /dev/null +++ b/arch/arm/boot/dts/overlays/midi-uart3-overlay.dts @@ -0,0 +1,38 @@ +/dts-v1/; +/plugin/; + +#include <dt-bindings/clock/bcm2835.h> + +/* + * Fake a higher clock rate to get a larger divisor, and thereby a lower + * baudrate. The real clock is 48MHz, which we scale so that requesting + * 38.4kHz results in an actual 31.25kHz. + * + * 48000000*38400/31250 = 58982400 + */ + +/{ + compatible = "brcm,bcm2711"; + + fragment@0 { + target-path = "/"; + __overlay__ { + midi_clk: midi_clk3 { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-output-names = "uart3_pclk"; + clock-frequency = <58982400>; + }; + }; + }; + + fragment@1 { + target = <&uart3>; + __overlay__ { + clocks = <&midi_clk>, + <&clocks BCM2835_CLOCK_VPU>; + }; + }; +}; + + diff --git a/arch/arm/boot/dts/overlays/midi-uart3-pi5-overlay.dts b/arch/arm/boot/dts/overlays/midi-uart3-pi5-overlay.dts new file mode 100644 index 00000000000000..478220d41edcec --- /dev/null +++ b/arch/arm/boot/dts/overlays/midi-uart3-pi5-overlay.dts @@ -0,0 +1,35 @@ +/dts-v1/; +/plugin/; + +#include <dt-bindings/clock/rp1.h> + +/* + * Fake a higher clock rate to get a larger divisor, and thereby a lower + * baudrate. The real clock is 50MHz, which we scale so that requesting + * 38.4kHz results in an actual 31.25kHz. + * + * 50000000*38400/31250 = 61440000 + */ + +/{ + compatible = "brcm,bcm2712"; + + fragment@0 { + target-path = "/"; + __overlay__ { + midi_clk: midi_clk3 { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-output-names = "uart3_pclk"; + clock-frequency = <61440000>; + }; + }; + }; + + fragment@1 { + target = <&uart3>; + __overlay__ { + clocks = <&midi_clk &rp1_clocks RP1_PLL_SYS_PRI_PH>; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/midi-uart4-overlay.dts b/arch/arm/boot/dts/overlays/midi-uart4-overlay.dts new file mode 100644 index 00000000000000..5f09a7ccd675b3 --- /dev/null +++ b/arch/arm/boot/dts/overlays/midi-uart4-overlay.dts @@ -0,0 +1,38 @@ +/dts-v1/; +/plugin/; + +#include <dt-bindings/clock/bcm2835.h> + +/* + * Fake a higher clock rate to get a larger divisor, and thereby a lower + * baudrate. The real clock is 48MHz, which we scale so that requesting + * 38.4kHz results in an actual 31.25kHz. + * + * 48000000*38400/31250 = 58982400 + */ + +/{ + compatible = "brcm,bcm2711"; + + fragment@0 { + target-path = "/"; + __overlay__ { + midi_clk: midi_clk4 { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-output-names = "uart4_pclk"; + clock-frequency = <58982400>; + }; + }; + }; + + fragment@1 { + target = <&uart4>; + __overlay__ { + clocks = <&midi_clk>, + <&clocks BCM2835_CLOCK_VPU>; + }; + }; +}; + + diff --git a/arch/arm/boot/dts/overlays/midi-uart4-pi5-overlay.dts b/arch/arm/boot/dts/overlays/midi-uart4-pi5-overlay.dts new file mode 100644 index 00000000000000..827bd5e951ba4c --- /dev/null +++ b/arch/arm/boot/dts/overlays/midi-uart4-pi5-overlay.dts @@ -0,0 +1,35 @@ +/dts-v1/; +/plugin/; + +#include <dt-bindings/clock/rp1.h> + +/* + * Fake a higher clock rate to get a larger divisor, and thereby a lower + * baudrate. The real clock is 50MHz, which we scale so that requesting + * 38.4kHz results in an actual 31.25kHz. + * + * 50000000*38400/31250 = 61440000 + */ + +/{ + compatible = "brcm,bcm2712"; + + fragment@0 { + target-path = "/"; + __overlay__ { + midi_clk: midi_clk4 { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-output-names = "uart4_pclk"; + clock-frequency = <61440000>; + }; + }; + }; + + fragment@1 { + target = <&uart4>; + __overlay__ { + clocks = <&midi_clk &rp1_clocks RP1_PLL_SYS_PRI_PH>; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/midi-uart5-overlay.dts b/arch/arm/boot/dts/overlays/midi-uart5-overlay.dts new file mode 100644 index 00000000000000..74551ec2a6721b --- /dev/null +++ b/arch/arm/boot/dts/overlays/midi-uart5-overlay.dts @@ -0,0 +1,38 @@ +/dts-v1/; +/plugin/; + +#include <dt-bindings/clock/bcm2835.h> + +/* + * Fake a higher clock rate to get a larger divisor, and thereby a lower + * baudrate. The real clock is 48MHz, which we scale so that requesting + * 38.4kHz results in an actual 31.25kHz. + * + * 48000000*38400/31250 = 58982400 + */ + +/{ + compatible = "brcm,bcm2711"; + + fragment@0 { + target-path = "/"; + __overlay__ { + midi_clk: midi_clk5 { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-output-names = "uart5_pclk"; + clock-frequency = <58982400>; + }; + }; + }; + + fragment@1 { + target = <&uart5>; + __overlay__ { + clocks = <&midi_clk>, + <&clocks BCM2835_CLOCK_VPU>; + }; + }; +}; + + diff --git a/arch/arm/boot/dts/overlays/minipitft13-overlay.dts b/arch/arm/boot/dts/overlays/minipitft13-overlay.dts new file mode 100644 index 00000000000000..5e0941e8ba540e --- /dev/null +++ b/arch/arm/boot/dts/overlays/minipitft13-overlay.dts @@ -0,0 +1,70 @@ +/* + * Device Tree overlay for Adafruit Mini PiTFT 1.3" and 1.5" 240x240 Display + * + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@1 { + target = <&spidev1>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@2 { + target = <&gpio>; + __overlay__ { + pitft_pins: pitft_pins { + brcm,pins = <25>; + brcm,function = <1>; /* out */ + brcm,pull = <0>; /* none */ + }; + }; + }; + + fragment@3 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + pitft: pitft@0 { + compatible = "fbtft,minipitft13"; + reg = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&pitft_pins>; + spi-max-frequency = <32000000>; + rotate = <0>; + width = <240>; + height = <240>; + buswidth = <8>; + dc-gpios = <&gpio 25 0>; + led-gpios = <&gpio 26 0>; + debug = <0>; + }; + }; + }; + + __overrides__ { + speed = <&pitft>,"spi-max-frequency:0"; + rotate = <&pitft>,"rotate:0"; + width = <&pitft>,"width:0"; + height = <&pitft>,"height:0"; + fps = <&pitft>,"fps:0"; + debug = <&pitft>,"debug:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/miniuart-bt-overlay.dts b/arch/arm/boot/dts/overlays/miniuart-bt-overlay.dts new file mode 100644 index 00000000000000..757e5cd3c4e85a --- /dev/null +++ b/arch/arm/boot/dts/overlays/miniuart-bt-overlay.dts @@ -0,0 +1,83 @@ +/dts-v1/; +/plugin/; + +/* Switch Pi3 Bluetooth function to use the mini-UART (ttyS0) and restore + UART0/ttyAMA0 over GPIOs 14 & 15. Note that this may reduce the maximum + usable baudrate. + + It is also necessary to edit /lib/systemd/system/hciuart.service and + replace ttyAMA0 with ttyS0, unless you have a system with udev rules + that create /dev/serial0 and /dev/serial1, in which case use /dev/serial1 + instead because it will always be correct. + + If cmdline.txt uses the alias serial0 to refer to the user-accessable port + then the firmware will replace with the appropriate port whether or not + this overlay is used. +*/ + +#include <dt-bindings/gpio/gpio.h> + +/{ + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&uart0>; + __overlay__ { + pinctrl-names = "default"; + pinctrl-0 = <&uart0_pins>; + status = "okay"; + }; + }; + + fragment@1 { + target = <&bt>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@2 { + target = <&uart1>; + __overlay__ { + pinctrl-names = "default"; + pinctrl-0 = <&uart1_pins>; + status = "okay"; + }; + }; + + fragment@3 { + target = <&uart0_pins>; + __overlay__ { + brcm,pins; + brcm,function; + brcm,pull; + }; + }; + + fragment@4 { + target = <&uart1>; + __overlay__ { + pinctrl-0 = <&uart1_bt_pins>; + }; + }; + + fragment@5 { + target-path = "/aliases"; + __overlay__ { + serial0 = "/soc/serial@7e201000"; + serial1 = "/soc/serial@7e215040"; + bluetooth = "/soc/serial@7e215040/bluetooth"; + }; + }; + + fragment@6 { + target = <&minibt>; + minibt_frag: __overlay__ { + status = "okay"; + }; + }; + + __overrides__ { + krnbt = <&minibt_frag>,"status"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/mipi-dbi-spi-overlay.dts b/arch/arm/boot/dts/overlays/mipi-dbi-spi-overlay.dts new file mode 100644 index 00000000000000..63fb3a5f23885a --- /dev/null +++ b/arch/arm/boot/dts/overlays/mipi-dbi-spi-overlay.dts @@ -0,0 +1,175 @@ +/* + * mipi-dbi-spi-overlay.dts + */ + +#include <dt-bindings/gpio/gpio.h> + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + spidev_fragment: fragment@0 { + target-path = "spi0/spidev@0"; + __overlay__ { + status = "disabled"; + }; + }; + + panel_fragment: fragment@1 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + status = "okay"; + + panel: panel@0 { + compatible = "panel", "panel-mipi-dbi-spi"; + reg = <0>; + spi-max-frequency = <32000000>; + + width-mm = <0>; + height-mm = <0>; + + timing: panel-timing { + hactive = <320>; + vactive = <240>; + hback-porch = <0>; + vback-porch = <0>; + + clock-frequency = <0>; + hfront-porch = <0>; + hsync-len = <0>; + vfront-porch = <0>; + vsync-len = <0>; + }; + }; + }; + }; + + fragment@10 { + target = <&panel>; + __dormant__ { + backlight = <&backlight_gpio>; + }; + }; + + fragment@11 { + target-path = "/"; + __dormant__ { + backlight_gpio: backlight_gpio { + compatible = "gpio-backlight"; + gpios = <&gpio 255 GPIO_ACTIVE_HIGH>; + }; + }; + }; + + fragment@20 { + target = <&panel>; + __dormant__ { + backlight = <&backlight_pwm>; + }; + }; + + fragment@21 { + target-path = "/"; + __dormant__ { + backlight_pwm: backlight_pwm { + compatible = "pwm-backlight"; + brightness-levels = <0 6 8 12 16 24 32 40 48 64 96 128 160 192 224 255>; + default-brightness-level = <15>; + pwms = <&pwm 0 200000 0>; + }; + }; + }; + + fragment@22 { + target = <&pwm>; + __dormant__ { + pinctrl-names = "default"; + pinctrl-0 = <&pwm_pins>; + assigned-clock-rates = <1000000>; + status = "okay"; + }; + }; + + fragment@23 { + target = <&gpio>; + __dormant__ { + pwm_pins: pwm_pins { + brcm,pins = <18>; + brcm,function = <2>; /* Alt5 */ + }; + }; + }; + + fragment@24 { + target = <&chosen>; + __dormant__ { + bootargs = "snd_bcm2835.enable_headphones=0"; + }; + }; + + __overrides__ { + compatible = <&panel>, "compatible"; + + spi0-0 = <&panel_fragment>, "target:0=",<&spi0>, + <&spidev_fragment>, "target-path=spi0/spidev@0", + <&panel>, "reg:0=0"; + spi0-1 = <&panel_fragment>, "target:0=",<&spi0>, + <&spidev_fragment>, "target-path=spi0/spidev@1", + <&panel>, "reg:0=1"; + spi1-0 = <&panel_fragment>, "target:0=",<&spi1>, + <&spidev_fragment>, "target-path=spi1/spidev@0", + <&panel>, "reg:0=0"; + spi1-1 = <&panel_fragment>, "target:0=",<&spi1>, + <&spidev_fragment>, "target-path=spi1/spidev@1", + <&panel>, "reg:0=1"; + spi1-2 = <&panel_fragment>, "target:0=",<&spi1>, + <&spidev_fragment>, "target-path=spi1/spidev@2", + <&panel>, "reg:0=2"; + spi2-0 = <&panel_fragment>, "target:0=",<&spi2>, + <&spidev_fragment>, "target-path=spi2/spidev@0", + <&panel>, "reg:0=0"; + spi2-1 = <&panel_fragment>, "target:0=",<&spi2>, + <&spidev_fragment>, "target-path=spi2/spidev@1", + <&panel>, "reg:0=1"; + spi2-2 = <&panel_fragment>, "target:0=",<&spi2>, + <&spidev_fragment>, "target-path=spi2/spidev@2", + <&panel>, "reg:0=2"; + + speed = <&panel>, "spi-max-frequency:0"; + cpha = <&panel>, "spi-cpha?"; + cpol = <&panel>, "spi-cpol?"; + + write-only = <&panel>, "write-only?"; + + width = <&timing>, "hactive:0"; + height = <&timing>, "vactive:0"; + x-offset = <&timing>, "hback-porch:0"; + y-offset = <&timing>, "vback-porch:0"; + clock-frequency = <&timing>, "clock-frequency:0"; + + width-mm = <&panel>, "width-mm:0"; + height-mm = <&panel>, "height-mm:0"; + + /* optional gpios */ + reset-gpio = <&panel>, "reset-gpios:0=", <&gpio>, + <&panel>, "reset-gpios:4", + <&panel>, "reset-gpios:8=0"; /* GPIO_ACTIVE_HIGH */ + dc-gpio = <&panel>, "dc-gpios:0=", <&gpio>, + <&panel>, "dc-gpios:4", + <&panel>, "dc-gpios:8=0"; /* GPIO_ACTIVE_HIGH */ + + backlight-gpio = <0>, "+10+11", + <&backlight_gpio>, "gpios:4"; + backlight-pwm = <0>, "+20+21+22+23+24"; + backlight-pwm-chan = <&backlight_pwm>, "pwms:4"; + backlight-pwm-gpio = <&pwm_pins>, "brcm,pins:0"; + backlight-pwm-func = <&pwm_pins>, "brcm,function:0"; + backlight-def-brightness = <&backlight_pwm>, "default-brightness-level:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/mlx90640-overlay.dts b/arch/arm/boot/dts/overlays/mlx90640-overlay.dts new file mode 100644 index 00000000000000..a2655ed825859b --- /dev/null +++ b/arch/arm/boot/dts/overlays/mlx90640-overlay.dts @@ -0,0 +1,22 @@ +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2c_arm>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + clock-frequency = <400000>; + + mlx90640: mlx90640@33 { + compatible = "melexis,mlx90640"; + reg = <0x33>; + status = "okay"; + }; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/mmc-overlay.dts b/arch/arm/boot/dts/overlays/mmc-overlay.dts new file mode 100644 index 00000000000000..c1a2f691aa1e71 --- /dev/null +++ b/arch/arm/boot/dts/overlays/mmc-overlay.dts @@ -0,0 +1,46 @@ +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&mmc>; + frag0: __overlay__ { + pinctrl-names = "default"; + pinctrl-0 = <&mmc_pins>; + bus-width = <4>; + brcm,overclock-50 = <0>; + status = "okay"; + }; + }; + + fragment@1 { + target = <&gpio>; + __overlay__ { + mmc_pins: mmc_pins { + brcm,pins = <48 49 50 51 52 53>; + brcm,function = <7>; /* alt3 */ + brcm,pull = <0 2 2 2 2 2>; + }; + }; + }; + + fragment@2 { + target = <&sdhost>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@3 { + target = <&mmcnr>; + __overlay__ { + status = "disabled"; + }; + }; + + __overrides__ { + overclock_50 = <&frag0>,"brcm,overclock-50:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/mz61581-overlay.dts b/arch/arm/boot/dts/overlays/mz61581-overlay.dts new file mode 100644 index 00000000000000..101ad21d8093b7 --- /dev/null +++ b/arch/arm/boot/dts/overlays/mz61581-overlay.dts @@ -0,0 +1,117 @@ +/* + * Device Tree overlay for MZ61581-PI-EXT 2014.12.28 by Tontec + * + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spi0>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@2 { + target = <&spidev1>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@3 { + target = <&gpio>; + __overlay__ { + mz61581_pins: mz61581_pins { + brcm,pins = <4 15 18 25>; + brcm,function = <0 1 1 1>; /* in out out out */ + }; + }; + }; + + fragment@4 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + mz61581: mz61581@0{ + compatible = "samsung,s6d02a1"; + reg = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&mz61581_pins>; + + spi-max-frequency = <128000000>; + spi-cpol; + spi-cpha; + + width = <320>; + height = <480>; + rotate = <270>; + bgr; + fps = <30>; + buswidth = <8>; + txbuflen = <32768>; + + reset-gpios = <&gpio 15 1>; + dc-gpios = <&gpio 25 0>; + led-gpios = <&gpio 18 0>; + + init = <0x10000b0 00 + 0x1000011 + 0x20000ff + 0x10000b3 0x02 0x00 0x00 0x00 + 0x10000c0 0x13 0x3b 0x00 0x02 0x00 0x01 0x00 0x43 + 0x10000c1 0x08 0x16 0x08 0x08 + 0x10000c4 0x11 0x07 0x03 0x03 + 0x10000c6 0x00 + 0x10000c8 0x03 0x03 0x13 0x5c 0x03 0x07 0x14 0x08 0x00 0x21 0x08 0x14 0x07 0x53 0x0c 0x13 0x03 0x03 0x21 0x00 + 0x1000035 0x00 + 0x1000036 0xa0 + 0x100003a 0x55 + 0x1000044 0x00 0x01 + 0x10000d0 0x07 0x07 0x1d 0x03 + 0x10000d1 0x03 0x30 0x10 + 0x10000d2 0x03 0x14 0x04 + 0x1000029 + 0x100002c>; + + /* This is a workaround to make sure the init sequence slows down and doesn't fail */ + debug = <3>; + }; + + mz61581_ts: mz61581_ts@1 { + compatible = "ti,ads7846"; + reg = <1>; + + spi-max-frequency = <2000000>; + interrupts = <4 2>; /* high-to-low edge triggered */ + interrupt-parent = <&gpio>; + pendown-gpio = <&gpio 4 1>; + + ti,x-plate-ohms = /bits/ 16 <60>; + ti,pressure-max = /bits/ 16 <255>; + }; + }; + }; + __overrides__ { + speed = <&mz61581>, "spi-max-frequency:0"; + rotate = <&mz61581>, "rotate:0"; + fps = <&mz61581>, "fps:0"; + txbuflen = <&mz61581>, "txbuflen:0"; + debug = <&mz61581>, "debug:0"; + xohms = <&mz61581_ts>,"ti,x-plate-ohms;0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/ov2311-overlay.dts b/arch/arm/boot/dts/overlays/ov2311-overlay.dts new file mode 100644 index 00000000000000..4dad303c176892 --- /dev/null +++ b/arch/arm/boot/dts/overlays/ov2311-overlay.dts @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Definitions for OV2311 camera module on VC I2C bus +/dts-v1/; +/plugin/; + +#include <dt-bindings/gpio/gpio.h> + +/{ + compatible = "brcm,bcm2835"; + + i2c_frag: fragment@0 { + target = <&i2c_csi_dsi>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + #include "ov2311.dtsi" + }; + }; + + csi_frag: fragment@1 { + target = <&csi1>; + csi: __overlay__ { + status = "okay"; + + port { + csi_ep: endpoint { + remote-endpoint = <&cam_endpoint>; + data-lanes = <1 2>; + }; + }; + }; + }; + + fragment@2 { + target = <&i2c0if>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@3 { + target = <&i2c0mux>; + __overlay__ { + status = "okay"; + }; + }; + + clk_frag: fragment@4{ + target = <&cam1_clk>; + __overlay__ { + status = "okay"; + clock-frequency = <24000000>; + }; + }; + + fragment@5 { + target = <&csi1>; + __dormant__ { + compatible = "brcm,bcm2835-unicam-legacy"; + }; + }; + + __overrides__ { + rotation = <&cam_node>,"rotation:0"; + orientation = <&cam_node>,"orientation:0"; + media-controller = <0>,"!5"; + cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>, + <&csi_frag>, "target:0=",<&csi0>, + <&clk_frag>, "target:0=",<&cam0_clk>, + <&cam_node>, "clocks:0=",<&cam0_clk>, + <&cam_node>, "avdd-supply:0=",<&cam0_reg>; + }; +}; + +&cam_node { + status = "okay"; +}; + +&cam_endpoint { + remote-endpoint = <&csi_ep>; +}; diff --git a/arch/arm/boot/dts/overlays/ov2311.dtsi b/arch/arm/boot/dts/overlays/ov2311.dtsi new file mode 100644 index 00000000000000..a1714d6941c3a3 --- /dev/null +++ b/arch/arm/boot/dts/overlays/ov2311.dtsi @@ -0,0 +1,26 @@ +// Fragment that configures an ov2311 + +cam_node: ov2311@60 { + compatible = "ovti,ov2311"; + reg = <0x60>; + status = "disabled"; + + clocks = <&cam1_clk>; + clock-names = "xvclk"; + + avdd-supply = <&cam1_reg>; + dovdd-supply = <&cam_dummy_reg>; + dvdd-supply = <&cam_dummy_reg>; + + rotation = <0>; + orientation = <2>; + + port { + cam_endpoint: endpoint { + clock-lanes = <0>; + data-lanes = <1 2>; + link-frequencies = + /bits/ 64 <400000000>; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/ov5647-overlay.dts b/arch/arm/boot/dts/overlays/ov5647-overlay.dts new file mode 100644 index 00000000000000..e2d40dac80c9d7 --- /dev/null +++ b/arch/arm/boot/dts/overlays/ov5647-overlay.dts @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Definitions for OV5647 camera module on VC I2C bus +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2835"; + + i2c_frag: fragment@0 { + target = <&i2c_csi_dsi>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + #include "ov5647.dtsi" + + vcm_node: ad5398@c { + compatible = "adi,ad5398"; + reg = <0x0c>; + status = "disabled"; + VANA-supply = <&cam1_reg>; + }; + }; + }; + + csi_frag: fragment@1 { + target = <&csi1>; + csi: __overlay__ { + status = "okay"; + + port { + csi_ep: endpoint { + remote-endpoint = <&cam_endpoint>; + data-lanes = <1 2>; + clock-noncontinuous; + }; + }; + }; + }; + + fragment@2 { + target = <&i2c0if>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@3 { + target = <&i2c0mux>; + __overlay__ { + status = "okay"; + }; + }; + + reg_frag: fragment@4 { + target = <&cam1_reg>; + __overlay__ { + startup-delay-us = <20000>; + }; + }; + + clk_frag: fragment@5 { + target = <&cam1_clk>; + __overlay__ { + status = "okay"; + clock-frequency = <25000000>; + }; + }; + + fragment@6 { + target = <&csi1>; + __dormant__ { + compatible = "brcm,bcm2835-unicam-legacy"; + }; + }; + + __overrides__ { + rotation = <&cam_node>,"rotation:0"; + orientation = <&cam_node>,"orientation:0"; + media-controller = <0>,"!6"; + cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>, + <&csi_frag>, "target:0=",<&csi0>, + <®_frag>, "target:0=",<&cam0_reg>, + <&clk_frag>, "target:0=",<&cam0_clk>, + <&cam_node>, "clocks:0=",<&cam0_clk>, + <&cam_node>, "avdd-supply:0=",<&cam0_reg>, + <&vcm_node>, "VANA-supply:0=",<&cam0_reg>; + vcm = <&vcm_node>, "status=okay", + <&cam_node>,"lens-focus:0=", <&vcm_node>; + }; +}; + +&cam_node { + status = "okay"; +}; + +&cam_endpoint { + remote-endpoint = <&csi_ep>; +}; diff --git a/arch/arm/boot/dts/overlays/ov5647.dtsi b/arch/arm/boot/dts/overlays/ov5647.dtsi new file mode 100644 index 00000000000000..6455a191a394bf --- /dev/null +++ b/arch/arm/boot/dts/overlays/ov5647.dtsi @@ -0,0 +1,25 @@ +cam_node: ov5647@36 { + compatible = "ovti,ov5647"; + reg = <0x36>; + status = "disabled"; + + clocks = <&cam1_clk>; + + avdd-supply = <&cam1_reg>; + dovdd-supply = <&cam_dummy_reg>; + dvdd-supply = <&cam_dummy_reg>; + + rotation = <0>; + orientation = <2>; + + port { + cam_endpoint: endpoint { + clock-lanes = <0>; + data-lanes = <1 2>; + clock-noncontinuous; + link-frequencies = + /bits/ 64 <297000000>; + }; + }; +}; + diff --git a/arch/arm/boot/dts/overlays/ov64a40-overlay.dts b/arch/arm/boot/dts/overlays/ov64a40-overlay.dts new file mode 100644 index 00000000000000..a765483aaaca8e --- /dev/null +++ b/arch/arm/boot/dts/overlays/ov64a40-overlay.dts @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Definitions for OV64A40 camera module on VC I2C bus +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2835"; + + i2c_frag: fragment@0 { + target = <&i2c_csi_dsi>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + #include "ov64a40.dtsi" + }; + }; + + csi_frag: fragment@1 { + target = <&csi1>; + csi: __overlay__ { + status = "okay"; + + port{ + csi_ep: endpoint{ + remote-endpoint = <&cam_endpoint>; + clock-lanes = <0>; + data-lanes = <1 2>; + }; + }; + }; + }; + + fragment@2 { + target = <&i2c0if>; + __overlay__ { + status = "okay"; + }; + }; + + clk_frag: fragment@3 { + target = <&cam1_clk>; + __overlay__ { + clock-frequency = <24000000>; + status = "okay"; + }; + }; + + fragment@4 { + target = <&i2c0mux>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@5 { + target = <&cam_node>; + __overlay__ { + lens-focus = <&vcm_node>; + }; + }; + + fragment@6 { + target = <&csi1>; + __dormant__ { + compatible = "brcm,bcm2835-unicam-legacy"; + }; + }; + + __overrides__ { + rotation = <&cam_node>,"rotation:0"; + orientation = <&cam_node>,"orientation:0"; + media-controller = <0>,"!6"; + cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>, + <&csi_frag>, "target:0=",<&csi0>, + <&clk_frag>, "target:0=",<&cam0_clk>, + <&cam_node>, "clocks:0=",<&cam0_clk>, + <&cam_node>, "avdd-supply:0=",<&cam0_reg>, + <&vcm_node>, "vdd-supply:0=",<&cam0_reg>; + vcm = <&vcm_node>, "status", + <0>, "=5"; + link-frequency = <&cam_endpoint>,"link-frequencies#0"; + }; +}; + +&cam_node { + status = "okay"; +}; + +&cam_endpoint { + remote-endpoint = <&csi_ep>; +}; + +&vcm_node { + status = "okay"; +}; diff --git a/arch/arm/boot/dts/overlays/ov64a40.dtsi b/arch/arm/boot/dts/overlays/ov64a40.dtsi new file mode 100644 index 00000000000000..471b383fa15127 --- /dev/null +++ b/arch/arm/boot/dts/overlays/ov64a40.dtsi @@ -0,0 +1,34 @@ +// Fragment that configures an OV64A40 + +cam_node: ov64a40@36 { + compatible = "ovti,ov64a40"; + reg = <0x36>; + status = "disabled"; + + clocks = <&cam1_clk>; + clock-names = "xclk"; + + avdd-supply = <&cam1_reg>; /* 2.8v */ + dovdd-supply = <&cam_dummy_reg>;/* 1.8v */ + dvdd-supply = <&cam_dummy_reg>; /* 1.1v */ + + rotation = <180>; + orientation = <2>; + + port { + cam_endpoint: endpoint { + bus-type = <4>; + clock-lanes = <0>; + data-lanes = <1 2>; + link-frequencies = + /bits/ 64 <456000000>; + }; + }; +}; + +vcm_node: bu64754@76 { + compatible = "rohm,bu64754"; + reg = <0x76>; + status = "disabled"; + vdd-supply = <&cam1_reg>; +}; diff --git a/arch/arm/boot/dts/overlays/ov7251-overlay.dts b/arch/arm/boot/dts/overlays/ov7251-overlay.dts new file mode 100644 index 00000000000000..e12953809690c3 --- /dev/null +++ b/arch/arm/boot/dts/overlays/ov7251-overlay.dts @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Definitions for OV7251 camera module on VC I2C bus +/dts-v1/; +/plugin/; + +#include <dt-bindings/gpio/gpio.h> + +/{ + compatible = "brcm,bcm2835"; + + i2c_frag: fragment@0 { + target = <&i2c_csi_dsi>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + #include "ov7251.dtsi" + }; + }; + + csi_frag: fragment@1 { + target = <&csi1>; + csi: __overlay__ { + status = "okay"; + + port { + csi_ep: endpoint { + remote-endpoint = <&cam_endpoint>; + data-lanes = <1>; + }; + }; + }; + }; + + fragment@2 { + target = <&i2c0if>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@3 { + target = <&i2c0mux>; + __overlay__ { + status = "okay"; + }; + }; + + clk_frag: fragment@4 { + target = <&cam1_clk>; + __overlay__ { + status = "okay"; + clock-frequency = <24000000>; + }; + }; + + fragment@5 { + target = <&csi1>; + __dormant__ { + compatible = "brcm,bcm2835-unicam-legacy"; + }; + }; + + __overrides__ { + rotation = <&cam_node>,"rotation:0"; + orientation = <&cam_node>,"orientation:0"; + media-controller = <0>,"!5"; + cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>, + <&csi_frag>, "target:0=",<&csi0>, + <&clk_frag>, "target:0=",<&cam0_clk>, + <&cam_node>, "clocks:0=",<&cam0_clk>, + <&cam_node>, "vdda-supply:0=",<&cam0_reg>; + }; +}; + +&cam_node { + status = "okay"; +}; + +&cam_endpoint { + remote-endpoint = <&csi_ep>; +}; diff --git a/arch/arm/boot/dts/overlays/ov7251.dtsi b/arch/arm/boot/dts/overlays/ov7251.dtsi new file mode 100644 index 00000000000000..561fed1db8370a --- /dev/null +++ b/arch/arm/boot/dts/overlays/ov7251.dtsi @@ -0,0 +1,28 @@ +// Fragment that configures an ov7251 + +cam_node: ov7251@60 { + compatible = "ovti,ov7251"; + reg = <0x60>; + status = "disabled"; + + clocks = <&cam1_clk>; + clock-names = "xclk"; + clock-frequency = <24000000>; + + vdddo-supply = <&cam_dummy_reg>; + vdda-supply = <&cam1_reg>; + vddd-supply = <&cam_dummy_reg>; + + rotation = <0>; + orientation = <2>; + + port { + cam_endpoint: endpoint { + clock-lanes = <0>; + data-lanes = <1>; + clock-noncontinuous; + link-frequencies = + /bits/ 64 <240000000>; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/ov9281-overlay.dts b/arch/arm/boot/dts/overlays/ov9281-overlay.dts new file mode 100644 index 00000000000000..ff70628415f84c --- /dev/null +++ b/arch/arm/boot/dts/overlays/ov9281-overlay.dts @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Definitions for OV9281 camera module on VC I2C bus +/dts-v1/; +/plugin/; + +#include <dt-bindings/gpio/gpio.h> + +/{ + compatible = "brcm,bcm2835"; + + i2c_frag: fragment@0 { + target = <&i2c_csi_dsi>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + #include "ov9281.dtsi" + }; + }; + + csi_frag: fragment@1 { + target = <&csi1>; + csi: __overlay__ { + status = "okay"; + + port { + csi_ep: endpoint { + remote-endpoint = <&cam_endpoint>; + data-lanes = <1 2>; + }; + }; + }; + }; + + fragment@2 { + target = <&i2c0if>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@3 { + target = <&i2c0mux>; + __overlay__ { + status = "okay"; + }; + }; + + clk_frag: fragment@4 { + target = <&cam1_clk>; + __overlay__ { + status = "okay"; + clock-frequency = <24000000>; + }; + }; + + fragment@5 { + target = <&csi1>; + __dormant__ { + compatible = "brcm,bcm2835-unicam-legacy"; + }; + }; + + reg_frag: fragment@6 { + target = <&cam1_reg>; + __dormant__ { + startup-delay-us = <20000>; + off-on-delay-us = <30000>; + }; + }; + + __overrides__ { + rotation = <&cam_node>,"rotation:0"; + orientation = <&cam_node>,"orientation:0"; + media-controller = <0>,"!5"; + cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>, + <&csi_frag>, "target:0=",<&csi0>, + <&clk_frag>, "target:0=",<&cam0_clk>, + <&cam_node>, "clocks:0=",<&cam0_clk>, + <&cam_node>, "avdd-supply:0=",<&cam0_reg>, + <®_frag>, "target:0=",<&cam0_reg>; + arducam = <0>, "+6"; + }; +}; + +&cam_node { + status = "okay"; +}; + +&cam_endpoint { + remote-endpoint = <&csi_ep>; +}; diff --git a/arch/arm/boot/dts/overlays/ov9281.dtsi b/arch/arm/boot/dts/overlays/ov9281.dtsi new file mode 100644 index 00000000000000..321a2eb8c8f2fc --- /dev/null +++ b/arch/arm/boot/dts/overlays/ov9281.dtsi @@ -0,0 +1,26 @@ +// Fragment that configures an ov9281 + +cam_node: ov9281@60 { + compatible = "ovti,ov9281"; + reg = <0x60>; + status = "disabled"; + + clocks = <&cam1_clk>; + clock-names = "xvclk"; + + avdd-supply = <&cam1_reg>; + dovdd-supply = <&cam_dummy_reg>; + dvdd-supply = <&cam_dummy_reg>; + + rotation = <0>; + orientation = <2>; + + port { + cam_endpoint: endpoint { + clock-lanes = <0>; + data-lanes = <1 2>; + link-frequencies = + /bits/ 64 <400000000>; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/overlay_map.dts b/arch/arm/boot/dts/overlays/overlay_map.dts new file mode 100644 index 00000000000000..2639384e8b701e --- /dev/null +++ b/arch/arm/boot/dts/overlays/overlay_map.dts @@ -0,0 +1,523 @@ +/dts-v1/; + +/ { + audremap { + bcm2835; + bcm2711; + bcm2712 = "audremap-pi5"; + }; + + audremap-pi5 { + bcm2712; + }; + + balena-fin { + bcm2835; + bcm2711; + }; + + bmp085_i2c-sensor { + deprecated = "use i2c-sensor,bmp085"; + }; + + cm-swap-i2c0 { + bcm2835; + bcm2711; + }; + + cutiepi-panel { + bcm2711; + }; + + disable-bt { + bcm2835; + bcm2711; + bcm2712 = "disable-bt-pi5"; + }; + + disable-bt-pi5 { + bcm2712; + }; + + disable-emmc2 { + bcm2711; + }; + + disable-wifi { + bcm2835; + bcm2711; + bcm2712 = "disable-wifi-pi5"; + }; + + disable-wifi-pi5 { + bcm2712; + }; + + hifiberry-adc8x { + bcm2712; + }; + + hifiberry-dac8x { + bcm2712; + }; + + highperi { + bcm2711; + }; + + i2c0 { + bcm2835; + bcm2711; + bcm2712 = "i2c0-pi5"; + }; + + i2c0-bcm2708 { + deprecated = "use i2c0"; + }; + + i2c0-pi5 { + bcm2712; + }; + + i2c1 { + bcm2835; + bcm2711; + bcm2712 = "i2c1-pi5"; + }; + + i2c1-bcm2708 { + deprecated = "use i2c1"; + }; + + i2c1-pi5 { + bcm2712; + }; + + i2c2 { + bcm2712 = "i2c2-pi5"; + }; + + i2c2-pi5 { + bcm2712; + }; + + i2c3 { + bcm2711; + bcm2712 = "i2c3-pi5"; + }; + + i2c3-pi5 { + bcm2712; + }; + + i2c4 { + bcm2711; + }; + + i2c5 { + bcm2711; + }; + + i2c6 { + bcm2711; + }; + + i2s-gpio28-31 { + bcm2835; + bcm2711; + }; + + imx500 { + bcm2835; + bcm2711; + bcm2712 = "imx500-pi5"; + }; + + imx500-pi5 { + bcm2712; + }; + + lirc-rpi { + deprecated = "use gpio-ir"; + }; + + midi-uart0 { + bcm2835; + bcm2711; + bcm2712 = "midi-uart0-pi5"; + }; + + midi-uart0-pi5 { + bcm2712; + }; + + midi-uart1 { + bcm2835; + bcm2711; + bcm2712 = "midi-uart1-pi5"; + }; + + midi-uart1-pi5 { + bcm2712; + }; + + midi-uart2 { + bcm2711; + bcm2712 = "midi-uart2-pi5"; + }; + + midi-uart2-pi5 { + bcm2712; + }; + + midi-uart3 { + bcm2711; + bcm2712 = "midi-uart3-pi5"; + }; + + midi-uart3-pi5 { + bcm2712; + }; + + midi-uart4 { + bcm2711; + bcm2712 = "midi-uart4-pi5"; + }; + + midi-uart4-pi5 { + bcm2712; + }; + + midi-uart5 { + bcm2711; + }; + + miniuart-bt { + bcm2835; + bcm2711; + }; + + mmc { + bcm2835; + bcm2711; + }; + + mpu6050 { + deprecated = "use i2c-sensor,mpu6050"; + }; + + pcie-32bit-dma { + bcm2711; + bcm2712 = "pcie-32bit-dma-pi5"; + }; + + pcie-32bit-dma-pi5 { + bcm2712; + }; + + pcie-compat-pi5 { + bcm2712; + }; + + pi3-act-led { + renamed = "act-led"; + }; + + pi3-disable-bt { + renamed = "disable-bt"; + }; + + pi3-disable-wifi { + renamed = "disable-wifi"; + }; + + pi3-miniuart-bt { + renamed = "miniuart-bt"; + }; + + pisound { + bcm2835; + bcm2711; + bcm2712 = "pisound-pi5"; + }; + + pisound-pi5 { + bcm2712; + }; + + pwm-pio { + bcm2712; + }; + + pwm1 { + bcm2711; + }; + + ramoops { + bcm2835; + bcm2711 = "ramoops-pi4"; + bcm2712 = "ramoops-pi4"; + }; + + ramoops-pi4 { + bcm2711; + bcm2712; + }; + + rpi-cirrus-wm5102 { + renamed = "cirrus-wm5102"; + }; + + rpi-dac { + renamed = "i2s-dac"; + }; + + rpi-display { + renamed = "watterott-display"; + }; + + rpi-proto { + renamed = "proto-codec"; + }; + + rpivid-v4l2 { + deprecated = "no longer necessary"; + }; + + sdhost { + bcm2835; + bcm2711; + }; + + sdio { + bcm2835; + bcm2711; + bcm2712 = "sdio-pi5"; + }; + + sdio-1bit { + deprecated = "use sdio,bus_width=1,gpios_22_25"; + }; + + sdio-pi5 { + bcm2712; + }; + + sdtweak { + deprecated = "use 'dtparam=sd_poll_once' etc."; + }; + + smi { + bcm2835; + bcm2711; + }; + + smi-dev { + bcm2835; + bcm2711; + }; + + smi-nand { + bcm2835; + bcm2711; + }; + + spi0-cs { + renamed = "spi0-2cs"; + }; + + spi0-hw-cs { + deprecated = "no longer necessary"; + }; + + spi2-1cs { + bcm2835; + bcm2711; + bcm2712 = "spi2-1cs-pi5"; + }; + + spi2-1cs-pi5 { + bcm2712; + }; + + spi2-2cs { + bcm2835; + bcm2711; + bcm2712 = "spi2-2cs-pi5"; + }; + + spi2-2cs-pi5 { + bcm2712; + }; + + spi3-1cs { + bcm2711; + bcm2712 = "spi3-1cs-pi5"; + }; + + spi3-1cs-pi5 { + bcm2712; + }; + + spi3-2cs { + bcm2711; + bcm2712 = "spi3-2cs-pi5"; + }; + + spi3-2cs-pi5 { + bcm2712; + }; + + spi4-1cs { + bcm2711; + }; + + spi4-2cs { + bcm2711; + }; + + spi5-1cs { + bcm2711; + bcm2712 = "spi5-1cs-pi5"; + }; + + spi5-1cs-pi5 { + bcm2712; + }; + + spi5-2cs { + bcm2711; + bcm2712 = "spi5-2cs-pi5"; + }; + + spi5-2cs-pi5 { + bcm2712; + }; + + spi6-1cs { + bcm2711; + }; + + spi6-2cs { + bcm2711; + }; + + uart0 { + bcm2835; + bcm2711; + bcm2712 = "uart0-pi5"; + }; + + uart0-pi5 { + bcm2712; + }; + + uart1 { + bcm2835; + bcm2711; + bcm2712 = "uart1-pi5"; + }; + + uart1-pi5 { + bcm2712; + }; + + uart2 { + bcm2711; + bcm2712 = "uart2-pi5"; + }; + + uart2-pi5 { + bcm2712; + }; + + uart3 { + bcm2711; + bcm2712 = "uart3-pi5"; + }; + + uart3-pi5 { + bcm2712; + }; + + uart4 { + bcm2711; + bcm2712 = "uart4-pi5"; + }; + + uart4-pi5 { + bcm2712; + }; + + uart5 { + bcm2711; + }; + + upstream { + bcm2835; + bcm2711 = "upstream-pi4"; + }; + + upstream-aux-interrupt { + deprecated = "no longer necessary"; + }; + + upstream-pi4 { + bcm2711; + }; + + vc4-fkms-v3d { + bcm2835; + bcm2711 = "vc4-fkms-v3d-pi4"; + bcm2712 = "vc4-fkms-v3d-pi4"; + }; + + vc4-fkms-v3d-pi4 { + bcm2711; + bcm2712; + }; + + vc4-kms-dpi-at056tn53v1 { + deprecated = "use vc4-kms-dpi-panel,at056tn53v1"; + }; + + vc4-kms-v3d { + bcm2835; + bcm2711 = "vc4-kms-v3d-pi4"; + bcm2712 = "vc4-kms-v3d-pi5"; + }; + + vc4-kms-v3d-pi4 { + bcm2711; + bcm2712 = "vc4-kms-v3d-pi5"; + }; + + vc4-kms-v3d-pi5 { + bcm2712; + }; + + vl805 { + bcm2711; + }; + + w1-gpio { + bcm2835; + bcm2711; + bcm2712 = "w1-gpio-pi5"; + }; + + w1-gpio-pi5 { + bcm2712; + }; + + w1-gpio-pullup { + bcm2835; + bcm2711; + bcm2712 = "w1-gpio-pullup-pi5"; + }; + + w1-gpio-pullup-pi5 { + bcm2712; + }; + +}; diff --git a/arch/arm/boot/dts/overlays/papirus-overlay.dts b/arch/arm/boot/dts/overlays/papirus-overlay.dts new file mode 100644 index 00000000000000..67052b53a59cf7 --- /dev/null +++ b/arch/arm/boot/dts/overlays/papirus-overlay.dts @@ -0,0 +1,84 @@ +/* PaPiRus ePaper Screen by Pi Supply */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2c_arm>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + display_temp: lm75@48 { + compatible = "national,lm75b"; + reg = <0x48>; + status = "okay"; + #thermal-sensor-cells = <0>; + }; + }; + }; + + fragment@1 { + target-path = "/thermal-zones"; + __overlay__ { + display { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&display_temp>; + }; + }; + }; + + fragment@2 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@3 { + target = <&gpio>; + __overlay__ { + repaper_pins: repaper_pins { + brcm,pins = <14 15 23 24 25>; + brcm,function = <1 1 1 1 0>; /* out out out out in */ + }; + }; + }; + + fragment@4 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + repaper: repaper@0{ + compatible = "not_set"; + reg = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&repaper_pins>; + + spi-max-frequency = <8000000>; + + panel-on-gpios = <&gpio 23 0>; + border-gpios = <&gpio 14 0>; + discharge-gpios = <&gpio 15 0>; + reset-gpios = <&gpio 24 0>; + busy-gpios = <&gpio 25 0>; + + repaper-thermal-zone = "display"; + }; + }; + }; + + __overrides__ { + panel = <&repaper>, "compatible"; + speed = <&repaper>, "spi-max-frequency:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/pca953x-overlay.dts b/arch/arm/boot/dts/overlays/pca953x-overlay.dts new file mode 100644 index 00000000000000..50ff90d7c07235 --- /dev/null +++ b/arch/arm/boot/dts/overlays/pca953x-overlay.dts @@ -0,0 +1,59 @@ +// Definitions for NXP PCA953x family of I2C GPIO controllers on ARM I2C bus. +/dts-v1/; +/plugin/; + +#include "i2c-buses.dtsi" + +/{ + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2cbus>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + pca: pca@20 { + compatible = "nxp,pca9534"; + reg = <0x20>; + gpio-controller; + #gpio-cells = <2>; + }; + }; + }; + + __overrides__ { + addr = <&pca>,"reg:0"; + pca6416 = <&pca>,"compatible=nxp,pca6416"; + pca9505 = <&pca>,"compatible=nxp,pca9505"; + pca9535 = <&pca>,"compatible=nxp,pca9535"; + pca9536 = <&pca>,"compatible=nxp,pca9536"; + pca9537 = <&pca>,"compatible=nxp,pca9537"; + pca9538 = <&pca>,"compatible=nxp,pca9538"; + pca9539 = <&pca>,"compatible=nxp,pca9539"; + pca9554 = <&pca>,"compatible=nxp,pca9554"; + pca9555 = <&pca>,"compatible=nxp,pca9555"; + pca9556 = <&pca>,"compatible=nxp,pca9556"; + pca9557 = <&pca>,"compatible=nxp,pca9557"; + pca9574 = <&pca>,"compatible=nxp,pca9574"; + pca9575 = <&pca>,"compatible=nxp,pca9575"; + pca9698 = <&pca>,"compatible=nxp,pca9698"; + pcal6416 = <&pca>,"compatible=nxp,pcal6416"; + pcal6524 = <&pca>,"compatible=nxp,pcal6524"; + pcal9555a = <&pca>,"compatible=nxp,pcal9555a"; + max7310 = <&pca>,"compatible=maxim,max7310"; + max7312 = <&pca>,"compatible=maxim,max7312"; + max7313 = <&pca>,"compatible=maxim,max7313"; + max7315 = <&pca>,"compatible=maxim,max7315"; + pca6107 = <&pca>,"compatible=ti,pca6107"; + tca6408 = <&pca>,"compatible=ti,tca6408"; + tca6416 = <&pca>,"compatible=ti,tca6416"; + tca6424 = <&pca>,"compatible=ti,tca6424"; + tca9539 = <&pca>,"compatible=ti,tca9539"; + tca9554 = <&pca>,"compatible=ti,tca9554"; + cat9554 = <&pca>,"compatible=onnn,cat9554"; + pca9654 = <&pca>,"compatible=onnn,pca9654"; + xra1202 = <&pca>,"compatible=exar,xra1202"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/pcf857x-overlay.dts b/arch/arm/boot/dts/overlays/pcf857x-overlay.dts new file mode 100644 index 00000000000000..6b81d32c3818a1 --- /dev/null +++ b/arch/arm/boot/dts/overlays/pcf857x-overlay.dts @@ -0,0 +1,34 @@ +// Definitions for PCF857X GPIO Extender from NXP + +/dts-v1/; +/plugin/; + +#include "i2c-buses.dtsi" + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2cbus>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + pcf857x: pcf857x@0 { + compatible = ""; + reg = <0x00>; + gpio-controller; + #gpio-cells = <2>; + }; + }; + }; + + __overrides__ { + pcf8574 = <&pcf857x>,"compatible=nxp,pcf8574", <&pcf857x>,"reg:0=0x20"; + pcf8574a = <&pcf857x>,"compatible=nxp,pcf8574a", <&pcf857x>,"reg:0=0x38"; + pcf8575 = <&pcf857x>,"compatible=nxp,pcf8575", <&pcf857x>,"reg:0=0x20"; + pca8574 = <&pcf857x>,"compatible=nxp,pca8574", <&pcf857x>,"reg:0=0x20"; + addr = <&pcf857x>,"reg:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/pcie-32bit-dma-overlay.dts b/arch/arm/boot/dts/overlays/pcie-32bit-dma-overlay.dts new file mode 100644 index 00000000000000..955703563df770 --- /dev/null +++ b/arch/arm/boot/dts/overlays/pcie-32bit-dma-overlay.dts @@ -0,0 +1,38 @@ +/* + * pcie-32bit-dma-overlay.dts + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2711"; + + fragment@0 { + target-path = "/aliases"; + __overlay__ { + /* + * Removing this alias stops the firmware patching the + * PCIE DT dma-ranges based on the detected chip + * revision. + */ + pcie0 = ""; + }; + }; + + fragment@1 { + target = <&pcie0>; + __overlay__ { + /* + * The size of the range is rounded up to a power of 2, + * so the range ends up being 0-4GB, and the MSI vector + * gets pushed beyond 4GB. + */ + #address-cells = <3>; + #size-cells = <2>; + dma-ranges = <0x02000000 0x0 0x00000000 0x0 0x00000000 + 0x0 0x80000000>; + }; + }; + +}; diff --git a/arch/arm/boot/dts/overlays/pcie-32bit-dma-pi5-overlay.dts b/arch/arm/boot/dts/overlays/pcie-32bit-dma-pi5-overlay.dts new file mode 100644 index 00000000000000..f9908494f101f6 --- /dev/null +++ b/arch/arm/boot/dts/overlays/pcie-32bit-dma-pi5-overlay.dts @@ -0,0 +1,26 @@ +/* + * pcie-32bit-dma-pi5-overlay.dts + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2712"; + + fragment@0 { + target = <&pcie1>; + __overlay__ { + /* + * The size of the range is rounded up to a power of 2, + * so the range ends up being 0-4GB, and the MSI vector + * gets pushed beyond 4GB. + */ + #address-cells = <3>; + #size-cells = <2>; + dma-ranges = <0x02000000 0x0 0x00000000 0x0 0x00000000 + 0x0 0x80000000>; + }; + }; + +}; diff --git a/arch/arm/boot/dts/overlays/pciex1-compat-pi5-overlay.dts b/arch/arm/boot/dts/overlays/pciex1-compat-pi5-overlay.dts new file mode 100644 index 00000000000000..77d59bbc86ceea --- /dev/null +++ b/arch/arm/boot/dts/overlays/pciex1-compat-pi5-overlay.dts @@ -0,0 +1,60 @@ +/* + * Various feature switches for the 1-lane PCIe controller on Pi 5 + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2712"; + + /* Enable L1 sub-state support */ + fragment@0 { + target = <&pciex1>; + __dormant__ { + brcm,enable-l1ss; + }; + }; + + /* Disable ASPM L0s */ + fragment@1 { + target = <&pciex1>; + __dormant__ { + aspm-no-l0s; + }; + }; + + /* Use RC MSI target instead of MIP MSIx target */ + fragment@2 { + target = <&pciex1>; + __dormant__ { + msi-parent = <&pciex1>; + }; + }; + + /* + * Shift the start of the 32bit outbound window to 2GB, + * so there are no BARs starting at 0x0. Expand the 64bit + * outbound window to use the spare 2GB. + */ + fragment@3 { + target = <&pciex1>; + __dormant__ { + #address-cells = <3>; + #size-cells = <2>; + ranges = <0x02000000 0x00 0x80000000 + 0x1b 0x80000000 + 0x00 0x7ffffffc>, + <0x43000000 0x04 0x00000000 + 0x18 0x00000000 + 0x03 0x80000000>; + }; + }; + + __overrides__ { + l1ss = <0>, "+0"; + no-l0s = <0>, "+1"; + no-mip = <0>, "+2"; + mmio-hi = <0>, "+3"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/pibell-overlay.dts b/arch/arm/boot/dts/overlays/pibell-overlay.dts new file mode 100644 index 00000000000000..99d4b6d97969ab --- /dev/null +++ b/arch/arm/boot/dts/overlays/pibell-overlay.dts @@ -0,0 +1,81 @@ +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target-path = "/"; + __overlay__ { + codec_out: spdif-transmitter { + #address-cells = <0>; + #size-cells = <0>; + #sound-dai-cells = <0>; + compatible = "linux,spdif-dit"; + status = "okay"; + }; + + codec_in: card-codec { + #sound-dai-cells = <0>; + compatible = "invensense,ics43432"; + status = "okay"; + }; + }; + }; + + fragment@1 { + target = <&i2s_clk_producer>; + __overlay__ { + #sound-dai-cells = <0>; + status = "okay"; + }; + }; + + fragment@2 { + target = <&sound>; + snd: __overlay__ { + compatible = "simple-audio-card"; + simple-audio-card,name = "PiBell"; + + status="okay"; + + capture_link: simple-audio-card,dai-link@0 { + format = "i2s"; + + r_cpu_dai: cpu { + sound-dai = <&i2s_clk_producer>; + +/* example TDM slot configuration + dai-tdm-slot-num = <2>; + dai-tdm-slot-width = <32>; +*/ + }; + + r_codec_dai: codec { + sound-dai = <&codec_in>; + }; + }; + + playback_link: simple-audio-card,dai-link@1 { + format = "i2s"; + + p_cpu_dai: cpu { + sound-dai = <&i2s_clk_producer>; + +/* example TDM slot configuration + dai-tdm-slot-num = <2>; + dai-tdm-slot-width = <32>; +*/ + }; + + p_codec_dai: codec { + sound-dai = <&codec_out>; + }; + }; + }; + }; + + __overrides__ { + alsaname = <&snd>, "simple-audio-card,name"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/pifacedigital-overlay.dts b/arch/arm/boot/dts/overlays/pifacedigital-overlay.dts new file mode 100644 index 00000000000000..532a858683d6f0 --- /dev/null +++ b/arch/arm/boot/dts/overlays/pifacedigital-overlay.dts @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * PiFace Digital, Device Tree Overlay. + * Copyright (C) 2020 Thomas Preston <thomas.preston@codethink.co.uk> + * + * The PiFace Digital is a convenient breakout board for the Microchip mcp23s17 + * SPI GPIO port expander. + * + * The first eight GPIOs 0..7 (bank A) are connected to eight output terminals + * and LEDs, plus two relays on the first two outputs. These output loads are + * active-high. + * + * The next eight GPIOs 8..15 (bank B) are connected to eight input terminals + * with four on-board switches connecting them to ground. Inputs devices are + * therefore expected to bridge terminals to ground, so the mcp23s17 pullups are + * activated for GPIO bank B. + * + * On PiFace Digital, the mcp23s17 is connected to the Raspberry Pi's SPI0 CS0 + * bus. Each SPI bus supports up to eight addressable child devices. The PiFace + * Digital only supports addresses 0-4, which can be configured by jumpers JP1 + * and JP2. + * + * You can tell the driver about these jumper configurations with the + * spi-present-mask bitmask: + * + * | JP1 | JP2 | dtoverlay line in /boot/config.txt | + * | --- | --- | ------------------------------------------ | + * | 0 | 0 | dtoverlay=pifacedigital | + * | 0 | 0 | dtoverlay=pifacedigital:spi-present-mask=1 | + * | 0 | 1 | dtoverlay=pifacedigital:spi-present-mask=2 | + * | 1 | 0 | dtoverlay=pifacedigital:spi-present-mask=4 | + * | 1 | 1 | dtoverlay=pifacedigital:spi-present-mask=8 | + * + * # Example + * Set the dtoverlay config in /boot/config.txt and power off the Raspberry Pi: + * + * $ grep pifacedigital /boot/config.txt + * dtoverlay=pifacedigital + * $ sudo systemctl poweroff + * + * Attach the PiFace Digital and power on the Raspberry Pi. + * Then use the libgpiod tools to query the device: + * + * $ sudo apt install gpiod + * $ gpiodetect | grep mcp23s17 + * gpiochip2 [mcp23s17.0] (16 lines) + * + * Set GPIO outputs 0, 2 and 5: + * + * $ gpioset gpiochip2 0=1 2=1 5=1 + * + * Get GPIO status (input GPIO 8..15 are high, because they are active-low): + * + * $ gpioget gpiochip2 {8..15} + * 1 1 1 1 1 1 1 1 + * + * And even monitor interrupts: + * + * $ gpiomon gpiochip2 {8..15} + * event: FALLING EDGE offset: 11 timestamp: [1597361662.926741667] + * event: RISING EDGE offset: 11 timestamp: [1597361663.062555051] + * + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + /* Disable exposing /dev/spidev0.0 */ + fragment@0 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + + /* Add the PiFace Digital device node to the spi0.0 device. */ + fragment@1 { + target = <&spi0>; + __overlay__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + pfdigital: pifacedigital@0 { + compatible = "microchip,mcp23s17"; + reg = <0>; + + /* Set devices present with 8-bit mask. */ + microchip,spi-present-mask = <0x01>; + spi-max-frequency = <500000>; + + gpio-controller; + #gpio-cells = <2>; + + /* This device can pass through interrupts. */ + interrupt-controller; + #interrupt-cells = <2>; + + /* INTB is connected to GPIO 25. + * 0x8 active-low level-sensitive + */ + interrupts = <25 0x8>; + interrupt-parent = <&gpio>; + + /* Configure pull-ups on bank B GPIOs */ + pinctrl-0 = <&pfdigital_irq &pfdigital_pullups>; + pinctrl-names = "default"; + pfdigital_pullups: pinmux { + pins = + "gpio8", + "gpio9", + "gpio10", + "gpio11", + "gpio12", + "gpio13", + "gpio14", + "gpio15"; + bias-pull-up; + }; + }; + }; + }; + + /* PiFace Digital mcp23s17 INTB pin is connected to GPIO 25. The INTB + * pin is configured active-low (0 on interrupt), so expect to see + * FALLING_EDGE when inputs are bridged to ground (switch is pressed). + */ + fragment@3 { + target = <&gpio>; + __overlay__ { + pfdigital_irq: pifacedigital_irq { + brcm,pins = <25>; + brcm,function = <0>; /* input */ + }; + }; + }; + + __overrides__ { + spi-present-mask = <&pfdigital>, "microchip,spi-present-mask:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/pifi-40-overlay.dts b/arch/arm/boot/dts/overlays/pifi-40-overlay.dts new file mode 100644 index 00000000000000..d9ef4ea4097e19 --- /dev/null +++ b/arch/arm/boot/dts/overlays/pifi-40-overlay.dts @@ -0,0 +1,50 @@ +// Definitions for PiFi-40 Amp +/dts-v1/; +/plugin/; +#include <dt-bindings/gpio/gpio.h> +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_producer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + tas5711l: audio-codec@1a { + compatible = "ti,tas5711"; + reg = <0x1a>; + #sound-dai-cells = <0>; + sound-name-prefix = "Left"; + status = "okay"; + }; + + tas5711r: audio-codec@1b { + compatible = "ti,tas5711"; + reg = <0x1b>; + #sound-dai-cells = <0>; + sound-name-prefix = "Right"; + status = "okay"; + }; + }; + }; + + fragment@2 { + target = <&sound>; + pifi_40: __overlay__ { + compatible = "pifi,pifi-40"; + audio-codec = <&tas5711l &tas5711r>; + i2s-controller = <&i2s_clk_producer>; + pdn-gpios = <&gpio 23 1>; + status = "okay"; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/pifi-dac-hd-overlay.dts b/arch/arm/boot/dts/overlays/pifi-dac-hd-overlay.dts new file mode 100644 index 00000000000000..236098365dc28f --- /dev/null +++ b/arch/arm/boot/dts/overlays/pifi-dac-hd-overlay.dts @@ -0,0 +1,49 @@ +// Overlay for PiFi-DAC-HD +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_producer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&i2c1>; + __overlay__ { + status = "okay"; + #address-cells = <1>; + #size-cells =<0>; + + pcm5142: pcm5142@4c { + #sound-dai-cells = <0>; + compatible = "ti,pcm5142"; + reg = <0x4c>; + status = "okay"; + }; + }; + }; + + fragment@2 { + target = <&sound>; + __overlay__ { + compatible = "simple-audio-card"; + simple-audio-card,name = "PiFi-DAC-HD"; + status = "okay"; + + simple-audio-card,dai-link@1 { + format = "i2s"; + cpu { + sound-dai = <&i2s_clk_producer>; + }; + codec { + sound-dai = <&pcm5142>; + }; + }; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/pifi-dac-zero-overlay.dts b/arch/arm/boot/dts/overlays/pifi-dac-zero-overlay.dts new file mode 100644 index 00000000000000..dd272388779e3d --- /dev/null +++ b/arch/arm/boot/dts/overlays/pifi-dac-zero-overlay.dts @@ -0,0 +1,49 @@ +// Overlay for PiFi-DAC-Zero +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&sound>; + __overlay__ { + compatible = "simple-audio-card"; + simple-audio-card,name = "PiFi-DAC-Zero"; + status = "okay"; + + simple-audio-card,dai-link@1 { + format = "i2s"; + + cpu { + sound-dai = <&i2s_clk_producer>; + dai-tdm-slot-num = <2>; + dai-tdm-slot-width = <32>; + }; + + codec { + sound-dai = <&codec_out>; + }; + }; + }; + }; + + fragment@1 { + target-path = "/"; + __overlay__ { + codec_out: pcm5102a-codec { + #sound-dai-cells = <0>; + compatible = "ti,pcm5102a"; + status = "okay"; + }; + }; + }; + + fragment@2 { + target = <&i2s_clk_producer>; + __overlay__ { + #sound-dai-cells = <0>; + status = "okay"; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/pifi-mini-210-overlay.dts b/arch/arm/boot/dts/overlays/pifi-mini-210-overlay.dts new file mode 100644 index 00000000000000..a7b857144a48d5 --- /dev/null +++ b/arch/arm/boot/dts/overlays/pifi-mini-210-overlay.dts @@ -0,0 +1,42 @@ +// Definitions for PiFi Mini 210 +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_producer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + tas5711@1a { + #sound-dai-cells = <0>; + compatible = "ti,tas5711"; + reg = <0x1a>; + status = "okay"; + pdn-gpios = <&gpio 23 1>; + reset-gpios = <&gpio 24 1>; + }; + }; + }; + + fragment@2 { + target = <&sound>; + __overlay__ { + compatible = "pifi,pifi-mini-210"; + i2s-controller = <&i2s_clk_producer>; + + status = "okay"; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/piglow-overlay.dts b/arch/arm/boot/dts/overlays/piglow-overlay.dts new file mode 100644 index 00000000000000..075bceef158c84 --- /dev/null +++ b/arch/arm/boot/dts/overlays/piglow-overlay.dts @@ -0,0 +1,97 @@ +// Definitions for SN3218 LED driver from Si-En Technology on PiGlow +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2c_arm>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + sn3218@54 { + compatible = "si-en,sn3218"; + reg = <0x54>; + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + led@1 { + reg = <1>; + label = "piglow:red:led1"; + }; + led@2 { + reg = <2>; + label = "piglow:orange:led2"; + }; + led@3 { + reg = <3>; + label = "piglow:yellow:led3"; + }; + led@4 { + reg = <4>; + label = "piglow:green:led4"; + }; + led@5 { + reg = <5>; + label = "piglow:blue:led5"; + }; + led@6 { + reg = <6>; + label = "piglow:green:led6"; + }; + led@7 { + reg = <7>; + label = "piglow:red:led7"; + }; + led@8 { + reg = <8>; + label = "piglow:orange:led8"; + }; + led@9 { + reg = <9>; + label = "piglow:yellow:led9"; + }; + led@10 { + reg = <10>; + label = "piglow:white:led10"; + }; + led@11 { + reg = <11>; + label = "piglow:white:led11"; + }; + led@12 { + reg = <12>; + label = "piglow:blue:led12"; + }; + led@13 { + reg = <13>; + label = "piglow:white:led13"; + }; + led@14 { + reg = <14>; + label = "piglow:green:led14"; + }; + led@15 { + reg = <15>; + label = "piglow:blue:led15"; + }; + led@16 { + reg = <16>; + label = "piglow:yellow:led16"; + }; + led@17 { + reg = <17>; + label = "piglow:orange:led17"; + }; + led@18 { + reg = <18>; + label = "piglow:red:led18"; + }; + }; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/pimidi-overlay.dts b/arch/arm/boot/dts/overlays/pimidi-overlay.dts new file mode 100644 index 00000000000000..183738474afb38 --- /dev/null +++ b/arch/arm/boot/dts/overlays/pimidi-overlay.dts @@ -0,0 +1,53 @@ +/* + * Pimidi Linux kernel module. + * Copyright (C) 2017-2024 Vilniaus Blokas UAB, https://blokas.io/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 of the + * License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/dts-v1/; +/plugin/; + +#include <dt-bindings/gpio/gpio.h> +#include <dt-bindings/interrupt-controller/irq.h> + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2c_arm>; + __overlay__ { + status = "okay"; + + pimidi_ctrl: pimidi_ctrl@20 { + compatible = "blokaslabs,pimidi"; + + reg = <0x20>; + status = "okay"; + + interrupt-parent = <&gpio>; + interrupts = <23 IRQ_TYPE_LEVEL_LOW>; + interrupt-names = "data_ready"; + interrupt-controller; + #interrupt-cells = <2>; + + data-ready-gpios = <&gpio 23 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>; + reset-gpios = <&gpio 22 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>; + }; + }; + }; + + __overrides__ { + sel = <&pimidi_ctrl>,"reg:0{0=0x20,1=0x21,2=0x22,3=0x23}", + <&pimidi_ctrl>,"data-ready-gpios:4{0=23,1=5,2=6,3=27}", + <&pimidi_ctrl>,"interrupts:0{0=23,1=5,2=6,3=27}"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/pineboards-hat-ai-overlay.dts b/arch/arm/boot/dts/overlays/pineboards-hat-ai-overlay.dts new file mode 100644 index 00000000000000..8160272f470581 --- /dev/null +++ b/arch/arm/boot/dts/overlays/pineboards-hat-ai-overlay.dts @@ -0,0 +1,18 @@ +/* + * Device Tree overlay for Pineboards Hat Ai!. + * Compatible with the Google Coral Edge TPU (Single and Dual Edge). + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2712"; + + fragment@0 { + target = <&pcie1>; + __overlay__ { + msi-parent = <&pcie1>; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/pineboards-hatdrive-poe-plus-overlay.dts b/arch/arm/boot/dts/overlays/pineboards-hatdrive-poe-plus-overlay.dts new file mode 100644 index 00000000000000..77b8e0d3be3119 --- /dev/null +++ b/arch/arm/boot/dts/overlays/pineboards-hatdrive-poe-plus-overlay.dts @@ -0,0 +1,19 @@ +/* + * Device Tree overlay for Pineboards HatDrive! PoE+. + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2712"; + + fragment@0 { + target-path = "/chosen"; + __overlay__ { + power: power { + hat_current_supply = <5000>; + }; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/piscreen-overlay.dts b/arch/arm/boot/dts/overlays/piscreen-overlay.dts new file mode 100644 index 00000000000000..bd389c8a5e51f3 --- /dev/null +++ b/arch/arm/boot/dts/overlays/piscreen-overlay.dts @@ -0,0 +1,110 @@ +/* + * Device Tree overlay for PiScreen 3.5" display shield by Ozzmaker + * + */ + +/dts-v1/; +/plugin/; + +#include <dt-bindings/gpio/gpio.h> + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spi0>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@2 { + target = <&spidev1>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@3 { + target = <&gpio>; + __overlay__ { + piscreen_pins: piscreen_pins { + brcm,pins = <17 25 24 22>; + brcm,function = <0 1 1 1>; /* in out out out */ + }; + }; + }; + + fragment@4 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + piscreen: piscreen@0{ + compatible = "ilitek,ili9486"; + reg = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&piscreen_pins>; + + spi-max-frequency = <24000000>; + rotate = <270>; + bgr; + fps = <30>; + buswidth = <8>; + regwidth = <16>; + reset-gpios = <&gpio 25 GPIO_ACTIVE_LOW>; + dc-gpios = <&gpio 24 GPIO_ACTIVE_HIGH>; + led-gpios = <&gpio 22 GPIO_ACTIVE_HIGH>; + debug = <0>; + + init = <0x10000b0 0x00 + 0x1000011 + 0x20000ff + 0x100003a 0x55 + 0x1000036 0x28 + 0x10000c2 0x44 + 0x10000c5 0x00 0x00 0x00 0x00 + 0x10000e0 0x0f 0x1f 0x1c 0x0c 0x0f 0x08 0x48 0x98 0x37 0x0a 0x13 0x04 0x11 0x0d 0x00 + 0x10000e1 0x0f 0x32 0x2e 0x0b 0x0d 0x05 0x47 0x75 0x37 0x06 0x10 0x03 0x24 0x20 0x00 + 0x10000e2 0x0f 0x32 0x2e 0x0b 0x0d 0x05 0x47 0x75 0x37 0x06 0x10 0x03 0x24 0x20 0x00 + 0x1000011 + 0x1000029>; + }; + + piscreen_ts: piscreen-ts@1 { + compatible = "ti,ads7846"; + reg = <1>; + + spi-max-frequency = <2000000>; + interrupts = <17 2>; /* high-to-low edge triggered */ + interrupt-parent = <&gpio>; + pendown-gpio = <&gpio 17 GPIO_ACTIVE_LOW>; + ti,swap-xy; + ti,x-plate-ohms = /bits/ 16 <100>; + ti,pressure-max = /bits/ 16 <255>; + }; + }; + }; + __overrides__ { + speed = <&piscreen>,"spi-max-frequency:0"; + rotate = <&piscreen>,"rotate:0", + <&piscreen>,"rotation:0"; + fps = <&piscreen>,"fps:0"; + debug = <&piscreen>,"debug:0"; + xohms = <&piscreen_ts>,"ti,x-plate-ohms;0"; + drm = <&piscreen>,"compatible=waveshare,rpi-lcd-35", + <&piscreen>,"reset-gpios:8=",<GPIO_ACTIVE_HIGH>; + invx = <&piscreen_ts>,"touchscreen-inverted-x?"; + invy = <&piscreen_ts>,"touchscreen-inverted-y?"; + swapxy = <&piscreen_ts>,"touchscreen-swapped-x-y!"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/piscreen2r-overlay.dts b/arch/arm/boot/dts/overlays/piscreen2r-overlay.dts new file mode 100644 index 00000000000000..4468f4a54bf7a9 --- /dev/null +++ b/arch/arm/boot/dts/overlays/piscreen2r-overlay.dts @@ -0,0 +1,106 @@ + /* + * Device Tree overlay for PiScreen2 3.5" TFT with resistive touch by Ozzmaker.com + * + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spi0>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@2 { + target = <&spidev1>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@3 { + target = <&gpio>; + __overlay__ { + piscreen2_pins: piscreen2_pins { + brcm,pins = <17 25 24 22>; + brcm,function = <0 1 1 1>; /* in out out out */ + }; + }; + }; + + fragment@4 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + piscreen2: piscreen2@0{ + compatible = "ilitek,ili9486"; + reg = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&piscreen2_pins>; + bgr; + spi-max-frequency = <64000000>; + rotate = <90>; + fps = <30>; + buswidth = <8>; + regwidth = <16>; + txbuflen = <32768>; + reset-gpios = <&gpio 25 1>; + dc-gpios = <&gpio 24 0>; + led-gpios = <&gpio 22 0>; + debug = <0>; + + init = <0x10000b0 0x00 + 0x1000011 + 0x20000ff + 0x100003a 0x55 + 0x1000036 0x28 + 0x10000c0 0x11 0x09 + 0x10000c1 0x41 + 0x10000c5 0x00 0x00 0x00 0x00 + 0x10000b6 0x00 0x02 + 0x10000f7 0xa9 0x51 0x2c 0x2 + 0x10000be 0x00 0x04 + 0x10000e9 0x00 + 0x1000011 + 0x1000029>; + + }; + + piscreen2_ts: piscreen2-ts@1 { + compatible = "ti,ads7846"; + reg = <1>; + + spi-max-frequency = <2000000>; + interrupts = <17 2>; /* high-to-low edge triggered */ + interrupt-parent = <&gpio>; + pendown-gpio = <&gpio 17 1>; + ti,swap-xy; + ti,x-plate-ohms = /bits/ 16 <100>; + ti,pressure-max = /bits/ 16 <255>; + }; + }; + }; + __overrides__ { + speed = <&piscreen2>,"spi-max-frequency:0"; + rotate = <&piscreen2>,"rotate:0"; + fps = <&piscreen2>,"fps:0"; + debug = <&piscreen2>,"debug:0"; + xohms = <&piscreen2_ts>,"ti,x-plate-ohms;0"; + }; +}; + diff --git a/arch/arm/boot/dts/overlays/pisound-overlay.dts b/arch/arm/boot/dts/overlays/pisound-overlay.dts new file mode 100644 index 00000000000000..226bcbdf8a0966 --- /dev/null +++ b/arch/arm/boot/dts/overlays/pisound-overlay.dts @@ -0,0 +1,118 @@ +/* + * Pisound Linux kernel module. + * Copyright (C) 2016-2024 Vilniaus Blokas UAB, https://blokas.io/pisound + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 of the + * License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/dts-v1/; +/plugin/; + +#include <dt-bindings/gpio/gpio.h> + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spi0>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@2 { + target = <&spidev1>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@3 { + target = <&spi0>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + + pisound_spi: pisound_spi@0{ + compatible = "blokaslabs,pisound-spi"; + reg = <0>; + spi-max-frequency = <1000000>; + spi-speed-hz = <150000>; + }; + }; + }; + + fragment@4 { + target-path = "/"; + __overlay__ { + pcm5102a-codec { + #sound-dai-cells = <0>; + compatible = "ti,pcm5102a"; + status = "okay"; + }; + }; + }; + + fragment@5 { + target = <&sound>; + __overlay__ { + compatible = "blokaslabs,pisound"; + i2s-controller = <&i2s_clk_consumer>; + spi-controller = <&pisound_spi>; + status = "okay"; + + pinctrl-names = "default"; + pinctrl-0 = <&pisound_button_pins>; + + osr-gpios = + <&gpio 13 GPIO_ACTIVE_HIGH>, + <&gpio 26 GPIO_ACTIVE_HIGH>, + <&gpio 16 GPIO_ACTIVE_HIGH>; + + reset-gpios = + <&gpio 12 GPIO_ACTIVE_HIGH>, + <&gpio 24 GPIO_ACTIVE_HIGH>; + + data_available-gpios = <&gpio 25 GPIO_ACTIVE_HIGH>; + + button-gpios = <&gpio 17 GPIO_ACTIVE_LOW>; + }; + }; + + fragment@6 { + target = <&gpio>; + __overlay__ { + pisound_button_pins: pisound_button_pins { + brcm,pins = <17>; + brcm,function = <0>; // Input + brcm,pull = <2>; // Pull-Up + }; + }; + }; + + fragment@7 { + target = <&i2s_clk_consumer>; + __overlay__ { + status = "okay"; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/pisound-pi5-overlay.dts b/arch/arm/boot/dts/overlays/pisound-pi5-overlay.dts new file mode 100644 index 00000000000000..0b07a6d0369491 --- /dev/null +++ b/arch/arm/boot/dts/overlays/pisound-pi5-overlay.dts @@ -0,0 +1,28 @@ +/* + * Pisound Linux kernel module. + * Copyright (C) 2016-2024 Vilniaus Blokas UAB, https://blokas.io/pisound + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 of the + * License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "pisound-overlay.dts" + +&pisound_spi { + spi-speed-hz = <100000>; +}; + +/ { + compatible = "brcm,bcm2712"; +}; diff --git a/arch/arm/boot/dts/overlays/pitft22-overlay.dts b/arch/arm/boot/dts/overlays/pitft22-overlay.dts new file mode 100644 index 00000000000000..5759d48aed57e0 --- /dev/null +++ b/arch/arm/boot/dts/overlays/pitft22-overlay.dts @@ -0,0 +1,71 @@ +/* + * Device Tree overlay for pitft by Adafruit + * + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@1 { + target = <&spidev1>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@2 { + target = <&gpio>; + __overlay__ { + pitft_pins: pitft_pins { + brcm,pins = <25>; + brcm,function = <1>; /* out */ + brcm,pull = <0>; /* none */ + }; + }; + }; + + fragment@3 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + pitft: pitft@0{ + compatible = "ilitek,ili9340"; + reg = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&pitft_pins>; + + spi-max-frequency = <32000000>; + rotate = <90>; + fps = <25>; + bgr; + buswidth = <8>; + dc-gpios = <&gpio 25 0>; + debug = <0>; + }; + + }; + }; + + __overrides__ { + speed = <&pitft>,"spi-max-frequency:0"; + rotate = <&pitft>,"rotate:0", /* fbtft */ + <&pitft>,"rotation:0"; /* drm */ + fps = <&pitft>,"fps:0"; + debug = <&pitft>,"debug:0"; + drm = <&pitft>,"compatible=adafruit,yx240qv29"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/pitft28-capacitive-overlay.dts b/arch/arm/boot/dts/overlays/pitft28-capacitive-overlay.dts new file mode 100644 index 00000000000000..de98ee7b449679 --- /dev/null +++ b/arch/arm/boot/dts/overlays/pitft28-capacitive-overlay.dts @@ -0,0 +1,93 @@ +/* + * Device Tree overlay for Adafruit PiTFT 2.8" capacitive touch screen + * + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spi0>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@2 { + target = <&gpio>; + __overlay__ { + pitft_pins: pitft_pins { + brcm,pins = <24 25>; + brcm,function = <0 1>; /* in out */ + brcm,pull = <2 0>; /* pullup none */ + }; + }; + }; + + fragment@3 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + pitft: pitft@0{ + compatible = "ilitek,ili9340"; + reg = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&pitft_pins>; + + spi-max-frequency = <32000000>; + rotate = <90>; + fps = <25>; + bgr; + buswidth = <8>; + dc-gpios = <&gpio 25 0>; + debug = <0>; + }; + }; + }; + + fragment@4 { + target = <&i2c1>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + ft6236: ft6236@38 { + compatible = "focaltech,ft6236"; + reg = <0x38>; + + interrupt-parent = <&gpio>; + interrupts = <24 2>; + touchscreen-size-x = <240>; + touchscreen-size-y = <320>; + }; + }; + }; + + __overrides__ { + speed = <&pitft>,"spi-max-frequency:0"; + rotate = <&pitft>,"rotate:0", /* fbtft */ + <&pitft>,"rotation:0"; /* drm */ + fps = <&pitft>,"fps:0"; + debug = <&pitft>,"debug:0"; + drm = <&pitft>,"compatible=adafruit,yx240qv29"; + touch-sizex = <&ft6236>,"touchscreen-size-x:0"; + touch-sizey = <&ft6236>,"touchscreen-size-y:0"; + touch-invx = <&ft6236>,"touchscreen-inverted-x?"; + touch-invy = <&ft6236>,"touchscreen-inverted-y?"; + touch-swapxy = <&ft6236>,"touchscreen-swapped-x-y?"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/pitft28-resistive-overlay.dts b/arch/arm/boot/dts/overlays/pitft28-resistive-overlay.dts new file mode 100644 index 00000000000000..bc2597179b9c84 --- /dev/null +++ b/arch/arm/boot/dts/overlays/pitft28-resistive-overlay.dts @@ -0,0 +1,126 @@ +/* + * Device Tree overlay for Adafruit PiTFT 2.8" resistive touch screen + * + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spi0>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@2 { + target = <&spidev1>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@3 { + target = <&gpio>; + __overlay__ { + pitft_pins: pitft_pins { + brcm,pins = <24 25>; + brcm,function = <0 1>; /* in out */ + brcm,pull = <2 0>; /* pullup none */ + }; + }; + }; + + fragment@4 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + pitft: pitft@0{ + compatible = "ilitek,ili9340"; + reg = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&pitft_pins>; + + spi-max-frequency = <32000000>; + rotate = <90>; + fps = <25>; + bgr; + buswidth = <8>; + dc-gpios = <&gpio 25 0>; + debug = <0>; + }; + + pitft_ts@1 { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #interrupt-cells = <1>; + compatible = "st,stmpe610"; + reg = <1>; + + spi-max-frequency = <500000>; + interrupts = <24 2>; /* high-to-low edge triggered */ + interrupt-parent = <&gpio>; + interrupt-controller; + + stmpe_touchscreen: stmpe_touchscreen { + compatible = "st,stmpe-ts"; + st,sample-time = <4>; + st,mod-12b = <1>; + st,ref-sel = <0>; + st,adc-freq = <2>; + st,ave-ctrl = <3>; + st,touch-det-delay = <4>; + st,settling = <2>; + st,fraction-z = <7>; + st,i-drive = <0>; + }; + + stmpe_gpio: stmpe_gpio { + #gpio-cells = <2>; + compatible = "st,stmpe-gpio"; + /* + * only GPIO2 is wired/available + * and it is wired to the backlight + */ + st,norequest-mask = <0x7b>; + }; + }; + }; + }; + + fragment@5 { + target-path = "/soc"; + __overlay__ { + backlight { + compatible = "gpio-backlight"; + gpios = <&stmpe_gpio 2 0>; + default-on; + }; + }; + }; + + __overrides__ { + speed = <&pitft>,"spi-max-frequency:0"; + rotate = <&pitft>,"rotate:0", /* fbtft */ + <&pitft>,"rotation:0"; /* drm */ + fps = <&pitft>,"fps:0"; + debug = <&pitft>,"debug:0"; + drm = <&pitft>,"compatible=adafruit,yx240qv29"; + touch-invx = <&stmpe_touchscreen>,"touchscreen-inverted-x?"; + touch-invy = <&stmpe_touchscreen>,"touchscreen-inverted-y?"; + touch-swapxy = <&stmpe_touchscreen>,"touchscreen-swapped-x-y?"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/pitft35-resistive-overlay.dts b/arch/arm/boot/dts/overlays/pitft35-resistive-overlay.dts new file mode 100644 index 00000000000000..c3e81ef6003ae1 --- /dev/null +++ b/arch/arm/boot/dts/overlays/pitft35-resistive-overlay.dts @@ -0,0 +1,127 @@ +/* + * Device Tree overlay for Adafruit PiTFT 3.5" resistive touch screen + * + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spi0>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@2 { + target = <&spidev1>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@3 { + target = <&gpio>; + __overlay__ { + pitft_pins: pitft_pins { + brcm,pins = <24 25>; + brcm,function = <0 1>; /* in out */ + brcm,pull = <2 0>; /* pullup none */ + }; + }; + }; + + fragment@4 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + pitft: pitft@0{ + compatible = "himax,hx8357d"; + reg = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&pitft_pins>; + + spi-max-frequency = <32000000>; + rotate = <90>; + fps = <25>; + bgr; + buswidth = <8>; + dc-gpios = <&gpio 25 0>; + debug = <0>; + }; + + pitft_ts@1 { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #interrupt-cells = <1>; + compatible = "st,stmpe610"; + reg = <1>; + + spi-max-frequency = <500000>; + interrupts = <24 2>; /* high-to-low edge triggered */ + interrupt-parent = <&gpio>; + interrupt-controller; + + stmpe_touchscreen: stmpe_touchscreen { + compatible = "st,stmpe-ts"; + st,sample-time = <4>; + st,mod-12b = <1>; + st,ref-sel = <0>; + st,adc-freq = <2>; + st,ave-ctrl = <3>; + st,touch-det-delay = <4>; + st,settling = <2>; + st,fraction-z = <7>; + st,i-drive = <0>; + }; + + stmpe_gpio: stmpe_gpio { + #gpio-cells = <2>; + compatible = "st,stmpe-gpio"; + /* + * only GPIO2 is wired/available + * and it is wired to the backlight + */ + st,norequest-mask = <0x7b>; + }; + }; + }; + }; + + fragment@5 { + target-path = "/soc"; + __overlay__ { + backlight: backlight { + compatible = "gpio-backlight"; + gpios = <&stmpe_gpio 2 0>; + default-on; + }; + }; + }; + + __overrides__ { + speed = <&pitft>,"spi-max-frequency:0"; + rotate = <&pitft>,"rotate:0", /* fbtft */ + <&pitft>,"rotation:0"; /* drm */ + fps = <&pitft>,"fps:0"; + debug = <&pitft>,"debug:0"; + drm = <&pitft>,"compatible=adafruit,yx350hv15", + <&pitft>,"backlight:0=",<&backlight>; + touch-invx = <&stmpe_touchscreen>,"touchscreen-inverted-x?"; + touch-invy = <&stmpe_touchscreen>,"touchscreen-inverted-y?"; + touch-swapxy = <&stmpe_touchscreen>,"touchscreen-swapped-x-y?"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/pps-gpio-overlay.dts b/arch/arm/boot/dts/overlays/pps-gpio-overlay.dts new file mode 100644 index 00000000000000..a4f6b868aad8a4 --- /dev/null +++ b/arch/arm/boot/dts/overlays/pps-gpio-overlay.dts @@ -0,0 +1,39 @@ +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + fragment@0 { + target-path = "/"; + __overlay__ { + pps: pps@12 { + compatible = "pps-gpio"; + pinctrl-names = "default"; + pinctrl-0 = <&pps_pins>; + gpios = <&gpio 18 0>; + status = "okay"; + }; + }; + }; + + fragment@1 { + target = <&gpio>; + __overlay__ { + pps_pins: pps_pins@12 { + brcm,pins = <18>; + brcm,function = <0>; // in + brcm,pull = <0>; // off + }; + }; + }; + + __overrides__ { + gpiopin = <&pps>,"gpios:4", + <&pps>,"reg:0", + <&pps_pins>,"brcm,pins:0", + <&pps_pins>,"reg:0"; + assert_falling_edge = <&pps>,"assert-falling-edge?"; + capture_clear = <&pps>,"capture-clear?"; + pull = <&pps_pins>,"brcm,pull:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/proto-codec-overlay.dts b/arch/arm/boot/dts/overlays/proto-codec-overlay.dts new file mode 100644 index 00000000000000..92f6ed158923cb --- /dev/null +++ b/arch/arm/boot/dts/overlays/proto-codec-overlay.dts @@ -0,0 +1,39 @@ +// Definitions for Rpi-Proto +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_consumer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + wm8731@1a { + #sound-dai-cells = <0>; + compatible = "wlf,wm8731"; + reg = <0x1a>; + status = "okay"; + }; + }; + }; + + fragment@2 { + target = <&sound>; + __overlay__ { + compatible = "rpi,rpi-proto"; + i2s-controller = <&i2s_clk_consumer>; + status = "okay"; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/pwm-2chan-overlay.dts b/arch/arm/boot/dts/overlays/pwm-2chan-overlay.dts new file mode 100644 index 00000000000000..823c8b4126d180 --- /dev/null +++ b/arch/arm/boot/dts/overlays/pwm-2chan-overlay.dts @@ -0,0 +1,48 @@ +/dts-v1/; +/plugin/; + +/* +This is the 2-channel overlay - only use it if you need both channels. + +Legal pin,function combinations for each channel: + PWM0: 12,4(Alt0) 18,2(Alt5) 40,4(Alt0) 52,5(Alt1) + PWM1: 13,4(Alt0) 19,2(Alt5) 41,4(Alt0) 45,4(Alt0) 53,5(Alt1) + +N.B.: + 1) Pin 18 is the only one available on all platforms, and + it is the one used by the I2S audio interface. + Pins 12 and 13 might be better choices on an A+, B+ or Pi2. + 2) The onboard analogue audio output uses both PWM channels. + 3) So be careful mixing audio and PWM. +*/ + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&gpio>; + __overlay__ { + pwm_pins: pwm_pins { + brcm,pins = <18 19>; + brcm,function = <2 2>; /* Alt5 */ + }; + }; + }; + + fragment@1 { + target = <&pwm>; + frag1: __overlay__ { + pinctrl-names = "default"; + pinctrl-0 = <&pwm_pins>; + status = "okay"; + }; + }; + + __overrides__ { + pin = <&pwm_pins>,"brcm,pins:0"; + pin2 = <&pwm_pins>,"brcm,pins:4"; + func = <&pwm_pins>,"brcm,function:0"; + func2 = <&pwm_pins>,"brcm,function:4"; + clock = <&frag1>,"assigned-clock-rates:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/pwm-gpio-fan-overlay.dts b/arch/arm/boot/dts/overlays/pwm-gpio-fan-overlay.dts new file mode 100644 index 00000000000000..dc5e586a3251c6 --- /dev/null +++ b/arch/arm/boot/dts/overlays/pwm-gpio-fan-overlay.dts @@ -0,0 +1,170 @@ +/* + * Overlay for a GPIO connected PWM cooling fan controlled by software GPIO PWM + * + * Optional parameters: + * - "fan_gpio" BCM number of the pin driving the fan, default 18 (GPIO18) + * + * - "fan_temp0" CPU temperature at which fan is started with low speed in millicelsius, + * default 55000 (55 °C) + * - "fan_temp1" CPU temperature at which fan is switched to medium speed in millicelsius, + * default 60000 (60 °C) + * - "fan_temp2" CPU temperature at which fan is switched to high speed in millicelsius, + * default 67500 (67.5 °C) + * - "fan_temp3" CPU temperature at which fan is switched to max speed in millicelsius, + * default 75000 (75 °C) + * - "fan_temp0_hyst" Temperature hysteris at which fan is stopped in millicelsius, + * default 5000 (resulting in 50 °C) + * - "fan_temp1_hyst" Temperature hysteris at which fan is switched back to low speed + * in millicelsius, default 5000 (resulting in 55 °C) + * - "fan_temp2_hyst" Temperature hysteris at which fan is switched back to medium speed + * in millicelsius, default 5000 (resulting in 62.5 °C) + * - "fan_temp3_hyst" Temperature hysteris at which fan is switched back to high speed + * in millicelsius, default 5000 (resulting in 70 °C) + * - "fan_temp0_speed" Fan speed for low cooling state in range 0 to 255, + * default 114 (45% PWM duty cycle) + * - "fan_temp1_speed" Fan speed for medium cooling state in range 0 to 255, + * default 152 (60% PWM duty cycle) + * - "fan_temp2_speed" Fan speed for high cooling state in range 0 to 255, + * default 204 (80% PWM duty cycle) + * - "fan_temp3_speed" Fan speed for max cooling state in range 0 to 255, + * default 255 (100% PWM duty cycle) + * + * N.B. + * - Uses the software GPIO PWM kernel module instead of the Pis hardware PWMs (PWM0/PWM1). + * This will allow for an undisturbed concurrent usage of the Pis analogue audio output. + * + * Requires: + * - A PWM controlled cooling fan connected to the GPIO, such as an + * Argon mini-fan, HighPi Pro Fan or Waveshare FAN-4020-PWM-5V + * - Raspberry Pi OS Bookworm with kernel 6.6.62 or above + * + * Build: + * - sudo dtc -I dts -O dtb -o /boot/firmware/overlays/pwm-gpiofan.dtbo pwm-gpiofan-overlay.dts + * + * Activate: + * - sudo nano /boot/firmware/config.txt add "dtoverlay=pwm-gpiofan" + * + */ +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&gpio>; + __overlay__ { + pwm_gpio_pins: pwm_gpio_pins { + brcm,pins = <18>; /* gpio-pin = 18 */ + brcm,function = <1>; /* gpio function = output */ + brcm,pull = <0>; /* gpio pull up/down = off */ + }; + }; + }; + + fragment@1 { + target-path = "/"; + __overlay__ { + pwm_gpio: pwm_gpio { + compatible="pwm-gpio"; + #pwm-cells = <2>; + pinctrl-names = "default"; + pinctrl-0 = <&pwm_gpio_pins>; + gpios = <&gpio 18 0>; /* gpio-pin = 18 */ + }; + }; + }; + + fragment@2 { + target-path = "/"; + __overlay__ { + fan0: pwm-fan { + compatible = "pwm-fan"; + #cooling-cells = <2>; + /* in ns = 20ms = 50 Hz */ + pwms = <&pwm_gpio 0 20000000 0>; + + cooling-min-state = <0>; + cooling-max-state = <4>; + /* PWM duty cycle values in a range from 0 to 255 */ + /* which correspond to thermal cooling states 0 to 4 */ + cooling-levels = <0 114 152 204 255>; + }; + }; + }; + + fragment@3 { + target = <&cpu_thermal>; + __overlay__ { + /* in ms = poll every 2s */ + polling-delay = <2000>; + }; + }; + + fragment@4 { + target = <&thermal_trips>; + __overlay__ { + /* below temperatures in millicelsius */ + trip0: trip0 { + temperature = <55000>; /* 55 °C */ + hysteresis = <5000>; /* 5 °C */ + type = "active"; + }; + trip1: trip1 { + temperature = <60000>; /* 60 °C */ + hysteresis = <5000>; /* 5 °C */ + type = "active"; + }; + trip2: trip2 { + temperature = <67500>; /* 67.5 °C */ + hysteresis = <5000>; /* 5 °C */ + type = "active"; + }; + trip3: trip3 { + temperature = <75000>; /* 75 °C */ + hysteresis = <5000>; /* 5 °C */ + type = "active"; + }; + }; + }; + + fragment@5 { + target = <&cooling_maps>; + __overlay__ { + map0 { + cooling-device = <&fan0 0 1>; + trip = <&trip0>; + }; + map1 { + cooling-device = <&fan0 1 2>; + trip = <&trip1>; + }; + map2 { + cooling-device = <&fan0 2 3>; + trip = <&trip2>; + }; + map3 { + cooling-device = <&fan0 3 4>; + trip = <&trip3>; + }; + }; + }; + + __overrides__ { + fan_gpio = <&pwm_gpio>,"gpios:4", + <&pwm_gpio_pins>,"brcm,pins:0"; + fan_temp0 = <&trip0>,"temperature:0"; + fan_temp0_hyst = <&trip0>,"hysteresis:0"; + fan_temp0_speed = <&fan0>,"cooling-levels:4"; + fan_temp1 = <&trip1>,"temperature:0"; + fan_temp1_hyst = <&trip1>,"hysteresis:0"; + fan_temp1_speed = <&fan0>,"cooling-levels:8"; + fan_temp2 = <&trip2>,"temperature:0"; + fan_temp2_hyst = <&trip2>,"hysteresis:0"; + fan_temp2_speed = <&fan0>,"cooling-levels:12"; + fan_temp3 = <&trip3>,"temperature:0"; + fan_temp3_hyst = <&trip3>,"hysteresis:0"; + fan_temp3_speed = <&fan0>,"cooling-levels:16"; + }; + +}; diff --git a/arch/arm/boot/dts/overlays/pwm-gpio-overlay.dts b/arch/arm/boot/dts/overlays/pwm-gpio-overlay.dts new file mode 100644 index 00000000000000..f5a1fb38e2578e --- /dev/null +++ b/arch/arm/boot/dts/overlays/pwm-gpio-overlay.dts @@ -0,0 +1,38 @@ +// Device tree overlay for software GPIO PWM. +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&gpio>; + __overlay__ { + pwm_gpio_pins: pwm_gpio_pins@4 { + brcm,pins = <4>; /* gpio 4 */ + brcm,function = <1>; /* output */ + brcm,pull = <0>; /* pull-none */ + }; + }; + }; + + fragment@1 { + target-path = "/"; + __overlay__ { + pwm_gpio: pwm_gpio@4 { + compatible = "pwm-gpio"; + pinctrl-names = "default"; + pinctrl-0 = <&pwm_gpio_pins>; + gpios = <&gpio 4 0>; + }; + }; + }; + + __overrides__ { + gpio = <&pwm_gpio>,"gpios:4", + <&pwm_gpio_pins>,"brcm,pins:0", + /* modify reg values to allow multiple instantiation */ + <&pwm_gpio>,"reg:0", + <&pwm_gpio_pins>,"reg:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/pwm-ir-tx-overlay.dts b/arch/arm/boot/dts/overlays/pwm-ir-tx-overlay.dts new file mode 100644 index 00000000000000..33597eb79729f6 --- /dev/null +++ b/arch/arm/boot/dts/overlays/pwm-ir-tx-overlay.dts @@ -0,0 +1,40 @@ +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&gpio>; + __overlay__ { + pwm0_pins: pwm0_pins { + brcm,pins = <18>; + brcm,function = <2>; /* Alt5 */ + }; + }; + }; + + fragment@1 { + target = <&pwm>; + __overlay__ { + pinctrl-names = "default"; + pinctrl-0 = <&pwm0_pins>; + status = "okay"; + }; + }; + + fragment@2 { + target-path = "/"; + __overlay__ { + pwm-ir-transmitter { + compatible = "pwm-ir-tx"; + pwms = <&pwm 0 100 0>; + }; + }; + }; + + __overrides__ { + gpio_pin = <&pwm0_pins>, "brcm,pins:0"; + func = <&pwm0_pins>,"brcm,function:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/pwm-overlay.dts b/arch/arm/boot/dts/overlays/pwm-overlay.dts new file mode 100644 index 00000000000000..32853492aaea3d --- /dev/null +++ b/arch/arm/boot/dts/overlays/pwm-overlay.dts @@ -0,0 +1,44 @@ +/dts-v1/; +/plugin/; + +/* +Legal pin,function combinations for each channel: + PWM0: 12,4(Alt0) 18,2(Alt5) 40,4(Alt0) 52,5(Alt1) + PWM1: 13,4(Alt0) 19,2(Alt5) 41,4(Alt0) 45,4(Alt0) 53,5(Alt1) + +N.B.: + 1) Pin 18 is the only one available on all platforms, and + it is the one used by the I2S audio interface. + Pins 12 and 13 might be better choices on an A+, B+ or Pi2. + 2) The onboard analogue audio output uses both PWM channels. + 3) So be careful mixing audio and PWM. +*/ + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&gpio>; + __overlay__ { + pwm_pins: pwm_pins { + brcm,pins = <18>; + brcm,function = <2>; /* Alt5 */ + }; + }; + }; + + fragment@1 { + target = <&pwm>; + frag1: __overlay__ { + pinctrl-names = "default"; + pinctrl-0 = <&pwm_pins>; + status = "okay"; + }; + }; + + __overrides__ { + pin = <&pwm_pins>,"brcm,pins:0"; + func = <&pwm_pins>,"brcm,function:0"; + clock = <&frag1>,"assigned-clock-rates:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/pwm-pio-overlay.dts b/arch/arm/boot/dts/overlays/pwm-pio-overlay.dts new file mode 100644 index 00000000000000..3f105a8c21ce6f --- /dev/null +++ b/arch/arm/boot/dts/overlays/pwm-pio-overlay.dts @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-2.0 +// Device tree overlay for RP1 PIO PWM. +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2712"; + + fragment@0 { + target = <&gpio>; + __overlay__ { + pwm_pio_pins: pwm_pio_pins@4 { + brcm,pins = <4>; /* gpio 4 */ + function = "pio"; + bias-disable; + }; + }; + }; + + fragment@1 { + target-path = "/"; + __overlay__ { + pwm_pio: pwm_pio@4 { + compatible = "raspberrypi,pwm-pio-rp1"; + pinctrl-names = "default"; + pinctrl-0 = <&pwm_pio_pins>; + gpios = <&gpio 4 0>; + }; + }; + }; + + __overrides__ { + gpio = <&pwm_pio>,"gpios:4", + <&pwm_pio_pins>,"brcm,pins:0", + /* modify reg values to allow multiple instantiation */ + <&pwm_pio>,"reg:0", + <&pwm_pio_pins>,"reg:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/pwm1-overlay.dts b/arch/arm/boot/dts/overlays/pwm1-overlay.dts new file mode 100644 index 00000000000000..3324d4160653e3 --- /dev/null +++ b/arch/arm/boot/dts/overlays/pwm1-overlay.dts @@ -0,0 +1,59 @@ +/dts-v1/; +/plugin/; + +#include <dt-bindings/pinctrl/bcm2835.h> + +/ { + compatible = "brcm,bcm2711"; + + fragment@0 { + target = <&pins>; + __overlay__ { + brcm,pins = <40 41>; + }; + }; + + fragment@1 { + target = <&pins>; + __dormant__ { + brcm,pins = <40>; + }; + }; + + fragment@2 { + target = <&pins>; + __dormant__ { + brcm,pins = <41>; + }; + }; + + fragment@3 { + target = <&gpio>; + __overlay__ { + pins: pwm1_overlay_pins { + brcm,pins = <40 41>; + brcm,function = <BCM2835_FSEL_ALT0>; + brcm,pull = <BCM2835_PUD_UP>; + }; + }; + }; + + fragment@4 { + target = <&pwm1>; + pwm: __overlay__ { + status = "okay"; + pinctrl-names = "default"; + pinctrl-0 = <&pins>; + }; + }; + + __overrides__ { + clock = <&pwm>, "assigned-clock-rates:0"; + pins_40_41 = <0>,"+0-1-2"; + pins_40 = <0>,"-0+1-2"; + pins_41 = <0>,"-0-1+2"; + pull_up = <&pins>, "brcm,pull:0=", <BCM2835_PUD_UP>; + pull_down = <&pins>, "brcm,pull:0=", <BCM2835_PUD_DOWN>; + pull_off = <&pins>, "brcm,pull:0=", <BCM2835_PUD_OFF>; + }; +}; diff --git a/arch/arm/boot/dts/overlays/qca7000-overlay.dts b/arch/arm/boot/dts/overlays/qca7000-overlay.dts new file mode 100644 index 00000000000000..0184c53801dbd5 --- /dev/null +++ b/arch/arm/boot/dts/overlays/qca7000-overlay.dts @@ -0,0 +1,55 @@ +// Overlay for the Qualcomm Atheros QCA7000 on PLC Stamp micro EVK +// Visit: https://chargebyte.com -> Controllers & Modules -> Evaluation Tools -> PLC Stamp Micro 2 Evaluation Board for details + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@1 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + status = "okay"; + + eth1: qca7000@0 { + compatible = "qca,qca7000"; + reg = <0>; /* CE0 */ + pinctrl-names = "default"; + pinctrl-0 = <ð1_pins>; + interrupt-parent = <&gpio>; + interrupts = <23 0x1>; /* rising edge */ + spi-max-frequency = <12000000>; + status = "okay"; + }; + }; + }; + + fragment@2 { + target = <&gpio>; + __overlay__ { + eth1_pins: eth1_pins { + brcm,pins = <23>; + brcm,function = <0>; /* in */ + brcm,pull = <0>; /* none */ + }; + }; + }; + + __overrides__ { + int_pin = <ð1>, "interrupts:0", + <ð1_pins>, "brcm,pins:0"; + speed = <ð1>, "spi-max-frequency:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/qca7000-uart0-overlay.dts b/arch/arm/boot/dts/overlays/qca7000-uart0-overlay.dts new file mode 100644 index 00000000000000..3e4af02d91e28d --- /dev/null +++ b/arch/arm/boot/dts/overlays/qca7000-uart0-overlay.dts @@ -0,0 +1,46 @@ +// Overlay for the Qualcomm Atheros QCA7000 on PLC Stamp micro EVK +// Visit: https://chargebyte.com -> Controllers & Modules -> Evaluation Tools -> PLC Stamp Micro 2 Evaluation Board for details + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&uart0>; + __overlay__ { + pinctrl-names = "default"; + pinctrl-0 = <&uart0_pins>; + status = "okay"; + + eth2: qca7000 { + compatible = "qca,qca7000"; + current-speed = <115200>; + }; + }; + }; + + fragment@1 { + target = <&gpio>; + __overlay__ { + uart0_pins: uart0_ovl_pins { + brcm,pins = <14 15>; + brcm,function = <4>; /* alt0 */ + brcm,pull = <0 2>; + }; + }; + }; + + fragment@2 { + target-path = "/aliases"; + __overlay__ { + serial0 = "/soc/serial@7e201000"; + serial1 = "/soc/serial@7e215040"; + }; + }; + + __overrides__ { + baudrate = <ð2>, "current-speed:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/ramoops-overlay.dts b/arch/arm/boot/dts/overlays/ramoops-overlay.dts new file mode 100644 index 00000000000000..e5038658138d69 --- /dev/null +++ b/arch/arm/boot/dts/overlays/ramoops-overlay.dts @@ -0,0 +1,25 @@ +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&rmem>; + __overlay__ { + ramoops: ramoops@b000000 { + compatible = "ramoops"; + reg = <0x0b000000 0x10000>; /* 64kB */ + record-size = <0x4000>; /* 16kB */ + console-size = <0>; /* disabled by default */ + }; + }; + }; + + __overrides__ { + base-addr = <&ramoops>,"reg:0"; + total-size = <&ramoops>,"reg:4"; + record-size = <&ramoops>,"record-size:0"; + console-size = <&ramoops>,"console-size:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/ramoops-pi4-overlay.dts b/arch/arm/boot/dts/overlays/ramoops-pi4-overlay.dts new file mode 100644 index 00000000000000..1737e37f5724e8 --- /dev/null +++ b/arch/arm/boot/dts/overlays/ramoops-pi4-overlay.dts @@ -0,0 +1,25 @@ +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2711"; + + fragment@0 { + target = <&rmem>; + __overlay__ { + ramoops: ramoops@b000000 { + compatible = "ramoops"; + reg = <0x0 0x0b000000 0x10000>; /* 64kB */ + record-size = <0x4000>; /* 16kB */ + console-size = <0>; /* disabled by default */ + }; + }; + }; + + __overrides__ { + base-addr = <&ramoops>,"reg#0"; + total-size = <&ramoops>,"reg:8"; + record-size = <&ramoops>,"record-size:0"; + console-size = <&ramoops>,"console-size:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/rootmaster-overlay.dts b/arch/arm/boot/dts/overlays/rootmaster-overlay.dts new file mode 100644 index 00000000000000..f5ebaea2abb5b7 --- /dev/null +++ b/arch/arm/boot/dts/overlays/rootmaster-overlay.dts @@ -0,0 +1,77 @@ +// redo: ovmerge -c mcp251xfd-overlay.dts,spi0-0,interrupt=25 w1-gpio-overlay.dts,gpiopin=4 + +/dts-v1/; +/plugin/; + +#include <dt-bindings/gpio/gpio.h> +#include <dt-bindings/interrupt-controller/irq.h> +#include <dt-bindings/pinctrl/bcm2835.h> + +/ { + compatible = "brcm,bcm2835"; + fragment@0 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + fragment@1 { + target = <&gpio>; + __overlay__ { + mcp251xfd_pins: mcp251xfd_spi0_0_pins { + brcm,pins = <25>; + brcm,function = <BCM2835_FSEL_GPIO_IN>; + }; + }; + }; + fragment@2 { + target-path = "/clocks"; + __overlay__ { + clk_mcp251xfd_osc: mcp251xfd-spi0-0-osc { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <40000000>; + }; + }; + }; + fragment@3 { + target = <&spi0>; + __overlay__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + mcp251xfd@0 { + compatible = "microchip,mcp251xfd"; + reg = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&mcp251xfd_pins>; + spi-max-frequency = <20000000>; + interrupt-parent = <&gpio>; + interrupts = <25 IRQ_TYPE_LEVEL_LOW>; + clocks = <&clk_mcp251xfd_osc>; + }; + }; + }; + fragment@4 { + target-path = "/"; + __overlay__ { + onewire@4 { + compatible = "w1-gpio"; + pinctrl-names = "default"; + pinctrl-0 = <&w1_pins>; + gpios = <&gpio 4 0>; + status = "okay"; + }; + }; + }; + fragment@5 { + target = <&gpio>; + __overlay__ { + w1_pins: w1_pins@4 { + brcm,pins = <4>; + brcm,function = <0>; + brcm,pull = <0>; + }; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/rotary-encoder-overlay.dts b/arch/arm/boot/dts/overlays/rotary-encoder-overlay.dts new file mode 100644 index 00000000000000..ea1d952734e9f9 --- /dev/null +++ b/arch/arm/boot/dts/overlays/rotary-encoder-overlay.dts @@ -0,0 +1,59 @@ +// Device tree overlay for GPIO connected rotary encoder. +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&gpio>; + __overlay__ { + rotary_pins: rotary_pins@4 { + brcm,pins = <4 17>; /* gpio 4 17 */ + brcm,function = <0 0>; /* input */ + brcm,pull = <2 2>; /* pull-up */ + }; + + }; + }; + + fragment@1 { + target-path = "/"; + __overlay__ { + rotary: rotary@4 { + compatible = "rotary-encoder"; + status = "okay"; + pinctrl-names = "default"; + pinctrl-0 = <&rotary_pins>; + gpios = <&gpio 4 0>, <&gpio 17 0>; + linux,axis = <0>; /* REL_X */ + rotary-encoder,encoding = "gray"; + rotary-encoder,steps = <24>; /* 24 default */ + rotary-encoder,steps-per-period = <1>; /* corresponds to full period mode. See README */ + }; + }; + + }; + + __overrides__ { + pin_a = <&rotary>,"gpios:4", + <&rotary_pins>,"brcm,pins:0", + /* modify reg values to allow multiple instantiation */ + <&rotary>,"reg:0", + <&rotary_pins>,"reg:0"; + pin_b = <&rotary>,"gpios:16", + <&rotary_pins>,"brcm,pins:4"; + relative_axis = <&rotary>,"rotary-encoder,relative-axis?"; + linux_axis = <&rotary>,"linux,axis:0"; + rollover = <&rotary>,"rotary-encoder,rollover?"; + steps-per-period = <&rotary>,"rotary-encoder,steps-per-period:0"; + steps = <&rotary>,"rotary-encoder,steps:0"; + wakeup = <&rotary>,"wakeup-source?"; + encoding = <&rotary>,"rotary-encoder,encoding"; + /* legacy parameters*/ + rotary0_pin_a = <&rotary>,"gpios:4", + <&rotary_pins>,"brcm,pins:0"; + rotary0_pin_b = <&rotary>,"gpios:16", + <&rotary_pins>,"brcm,pins:4"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/rpi-backlight-overlay.dts b/arch/arm/boot/dts/overlays/rpi-backlight-overlay.dts new file mode 100644 index 00000000000000..cac5e44c6ec54a --- /dev/null +++ b/arch/arm/boot/dts/overlays/rpi-backlight-overlay.dts @@ -0,0 +1,21 @@ +/* + * Devicetree overlay for mailbox-driven Raspberry Pi DSI Display + * backlight controller + */ +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target-path = "/"; + __overlay__ { + rpi_backlight: rpi_backlight { + compatible = "raspberrypi,rpi-backlight"; + firmware = <&firmware>; + status = "okay"; + }; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/rpi-codeczero-overlay.dts b/arch/arm/boot/dts/overlays/rpi-codeczero-overlay.dts new file mode 100644 index 00000000000000..c3b0564b2fb2c4 --- /dev/null +++ b/arch/arm/boot/dts/overlays/rpi-codeczero-overlay.dts @@ -0,0 +1,9 @@ +// Overlay for the Raspberry Pi Codec Zero soundcard + +#include "iqaudio-codec-overlay.dts" + +&iqaudio_dac { + card_name = "RPi Codec Zero"; + dai_name = "Raspberry Pi Codec Zero"; + dai_stream_name = "Raspberry Pi Codec Zero HiFi"; +}; diff --git a/arch/arm/boot/dts/overlays/rpi-dacplus-overlay.dts b/arch/arm/boot/dts/overlays/rpi-dacplus-overlay.dts new file mode 100644 index 00000000000000..47557aa17f19bd --- /dev/null +++ b/arch/arm/boot/dts/overlays/rpi-dacplus-overlay.dts @@ -0,0 +1,17 @@ +// Overlay for the Raspberry Pi DAC Plus soundcard + +#include "iqaudio-dacplus-overlay.dts" + +&iqaudio_dac { + card_name = "RPi DAC+"; + dai_name = "Raspberry Pi DAC+"; + dai_stream_name = "Raspberry Pi DAC+ HiFi"; + /delete-property/ mute-gpios; +}; + +/ { + __overrides__ { + /delete-property/ auto_mute_amp; + /delete-property/ unmute_amp; + }; +}; diff --git a/arch/arm/boot/dts/overlays/rpi-dacpro-overlay.dts b/arch/arm/boot/dts/overlays/rpi-dacpro-overlay.dts new file mode 100644 index 00000000000000..412260c64edf2d --- /dev/null +++ b/arch/arm/boot/dts/overlays/rpi-dacpro-overlay.dts @@ -0,0 +1,17 @@ +// Overlay for the Raspberry Pi DAC Pro soundcard + +#include "iqaudio-dacplus-overlay.dts" + +&iqaudio_dac { + card_name = "RPi DAC Pro"; + dai_name = "Raspberry Pi DAC Pro"; + dai_stream_name = "Raspberry Pi DAC Pro HiFi"; + /delete-property/ mute-gpios; +}; + +/ { + __overrides__ { + /delete-property/ auto_mute_amp; + /delete-property/ unmute_amp; + }; +}; diff --git a/arch/arm/boot/dts/overlays/rpi-digiampplus-overlay.dts b/arch/arm/boot/dts/overlays/rpi-digiampplus-overlay.dts new file mode 100644 index 00000000000000..5e73d6c1bf4217 --- /dev/null +++ b/arch/arm/boot/dts/overlays/rpi-digiampplus-overlay.dts @@ -0,0 +1,17 @@ +// Overlay for the Raspberry Pi DAC Plus soundcard + +#include "iqaudio-dacplus-overlay.dts" + +&iqaudio_dac { + card_name = "RPi DigiAMP+"; + dai_name = "Raspberry Pi DigiAMP+"; + dai_stream_name = "Raspberry Pi DigiAMP+ HiFi"; + iqaudio-dac,auto-mute-amp; +}; + +/ { + __overrides__ { + unmute_amp = <&iqaudio_dac>,"iqaudio-dac,unmute-amp?", + <&iqaudio_dac>,"iqaudio-dac,auto-mute-amp!"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/rpi-ft5406-overlay.dts b/arch/arm/boot/dts/overlays/rpi-ft5406-overlay.dts new file mode 100644 index 00000000000000..8483c4f4b2eb26 --- /dev/null +++ b/arch/arm/boot/dts/overlays/rpi-ft5406-overlay.dts @@ -0,0 +1,25 @@ +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target-path = "/soc/firmware"; + __overlay__ { + ts: touchscreen { + compatible = "raspberrypi,firmware-ts"; + touchscreen-size-x = <800>; + touchscreen-size-y = <480>; + }; + }; + }; + + __overrides__ { + touchscreen-size-x = <&ts>,"touchscreen-size-x:0"; + touchscreen-size-y = <&ts>,"touchscreen-size-y:0"; + touchscreen-inverted-x = <&ts>,"touchscreen-inverted-x?"; + touchscreen-inverted-y = <&ts>,"touchscreen-inverted-y?"; + touchscreen-swapped-x-y = <&ts>,"touchscreen-swapped-x-y?"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/rpi-fw-uart-overlay.dts b/arch/arm/boot/dts/overlays/rpi-fw-uart-overlay.dts new file mode 100644 index 00000000000000..c17556e91105d8 --- /dev/null +++ b/arch/arm/boot/dts/overlays/rpi-fw-uart-overlay.dts @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-2.0 +// Overlay for the Raspberry Pi Firmware UART driver +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&gpio>; + __overlay__ { + rpi_fw_uart_pins: rpi_fw_uart_pins@4 { + brcm,pins = <20 21>; + brcm,function = <1 0>; /* output input */ + brcm,pull = <0 2>; /* none pull-up */ + }; + }; + }; + + fragment@1 { + target = <&soc>; + __overlay__ { + rpi_fw_uart: rpi_fw_uart@7e000000 { + compatible = "raspberrypi,firmware-uart"; + reg = <0x7e000000 0x100>; /* VideoCore MS sync regs */ + firmware = <&firmware>; + pinctrl-names = "default"; + pinctrl-0 = <&rpi_fw_uart_pins>; + tx-gpios = <&gpio 20 0>; + rx-gpios = <&gpio 21 0>; + }; + }; + }; + + __overrides__ { + txd0_pin = <&rpi_fw_uart>,"tx-gpios:4", + <&rpi_fw_uart_pins>, "brcm,pins:0"; + rxd0_pin = <&rpi_fw_uart>,"rx-gpios:4", + <&rpi_fw_uart_pins>, "brcm,pins:4"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/rpi-poe-overlay.dts b/arch/arm/boot/dts/overlays/rpi-poe-overlay.dts new file mode 100644 index 00000000000000..cfd9fe37e108ce --- /dev/null +++ b/arch/arm/boot/dts/overlays/rpi-poe-overlay.dts @@ -0,0 +1,154 @@ +/* + * Overlay for the Raspberry Pi POE HAT. + */ +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target-path = "/"; + __overlay__ { + fan: pwm-fan { + compatible = "pwm-fan"; + cooling-levels = <0 1 10 100 255>; + #cooling-cells = <2>; + pwms = <&fwpwm 0 80000 0>; + }; + }; + }; + + fragment@1 { + target = <&cpu_thermal>; + __overlay__ { + polling-delay = <2000>; /* milliseconds */ + }; + }; + + fragment@2 { + target = <&thermal_trips>; + __overlay__ { + trip0: trip0 { + temperature = <40000>; + hysteresis = <2000>; + type = "active"; + }; + trip1: trip1 { + temperature = <45000>; + hysteresis = <2000>; + type = "active"; + }; + trip2: trip2 { + temperature = <50000>; + hysteresis = <2000>; + type = "active"; + }; + trip3: trip3 { + temperature = <55000>; + hysteresis = <5000>; + type = "active"; + }; + }; + }; + + fragment@3 { + target = <&cooling_maps>; + __overlay__ { + map0 { + trip = <&trip0>; + cooling-device = <&fan 0 1>; + }; + map1 { + trip = <&trip1>; + cooling-device = <&fan 1 2>; + }; + map2 { + trip = <&trip2>; + cooling-device = <&fan 2 3>; + }; + map3 { + trip = <&trip3>; + cooling-device = <&fan 3 4>; + }; + }; + }; + + fragment@4 { + target-path = "/__overrides__"; + params: __overlay__ { + poe_fan_temp0 = <&trip0>,"temperature:0"; + poe_fan_temp0_hyst = <&trip0>,"hysteresis:0"; + poe_fan_temp1 = <&trip1>,"temperature:0"; + poe_fan_temp1_hyst = <&trip1>,"hysteresis:0"; + poe_fan_temp2 = <&trip2>,"temperature:0"; + poe_fan_temp2_hyst = <&trip2>,"hysteresis:0"; + poe_fan_temp3 = <&trip3>,"temperature:0"; + poe_fan_temp3_hyst = <&trip3>,"hysteresis:0"; + poe_fan_i2c = <&fwpwm>,"status=disabled", + <&poe_mfd>,"status=okay", + <&fan>,"pwms:0=",<&poe_mfd_pwm>; + }; + }; + + fragment@5 { + target = <&firmware>; + __overlay__ { + fwpwm: pwm { + compatible = "raspberrypi,firmware-poe-pwm"; + #pwm-cells = <2>; + }; + }; + }; + + fragment@6 { + target = <&i2c0>; + i2c_bus: __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + + poe_mfd: poe@51 { + compatible = "raspberrypi,poe-core"; + reg = <0x51>; + status = "disabled"; + + poe_mfd_pwm: poe_pwm@f0 { + compatible = "raspberrypi,poe-pwm"; + reg = <0xf0>; + status = "okay"; + #pwm-cells = <2>; + }; + }; + }; + }; + + fragment@7 { + target = <&i2c0if>; + __dormant__ { + status = "okay"; + }; + }; + + fragment@8 { + target = <&i2c0mux>; + __dormant__ { + status = "okay"; + }; + }; + + __overrides__ { + poe_fan_temp0 = <&trip0>,"temperature:0"; + poe_fan_temp0_hyst = <&trip0>,"hysteresis:0"; + poe_fan_temp1 = <&trip1>,"temperature:0"; + poe_fan_temp1_hyst = <&trip1>,"hysteresis:0"; + poe_fan_temp2 = <&trip2>,"temperature:0"; + poe_fan_temp2_hyst = <&trip2>,"hysteresis:0"; + poe_fan_temp3 = <&trip3>,"temperature:0"; + poe_fan_temp3_hyst = <&trip3>,"hysteresis:0"; + i2c = <0>, "+5+6", + <&fwpwm>,"status=disabled", + <&i2c_bus>,"status=okay", + <&poe_mfd>,"status=okay", + <&fan>,"pwms:0=",<&poe_mfd_pwm>; + }; +}; diff --git a/arch/arm/boot/dts/overlays/rpi-poe-plus-overlay.dts b/arch/arm/boot/dts/overlays/rpi-poe-plus-overlay.dts new file mode 100644 index 00000000000000..54deda2f18c363 --- /dev/null +++ b/arch/arm/boot/dts/overlays/rpi-poe-plus-overlay.dts @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: (GPL-2.0 OR MIT) +// Overlay for the Raspberry Pi PoE+ HAT. + +#include "rpi-poe-overlay.dts" + +/ { + compatible = "brcm,bcm2835"; + + fragment@10 { + target-path = "/"; + __overlay__ { + rpi_poe_power_supply: rpi-poe-power-supply { + compatible = "raspberrypi,rpi-poe-power-supply"; + firmware = <&firmware>; + status = "okay"; + }; + }; + }; + fragment@11 { + target = <&poe_mfd>; + __overlay__ { + rpi-poe-power-supply@f2 { + compatible = "raspberrypi,rpi-poe-power-supply"; + reg = <0xf2>; + status = "okay"; + }; + }; + }; + + __overrides__ { + i2c = <0>, "+5+6", + <&fwpwm>,"status=disabled", + <&rpi_poe_power_supply>,"status=disabled", + <&i2c_bus>,"status=okay", + <&poe_mfd>,"status=okay", + <&fan>,"pwms:0=",<&poe_mfd_pwm>; + }; +}; + +&fan { + cooling-levels = <0 32 64 128 255>; +}; + +¶ms { + poe_fan_i2c = <&fwpwm>,"status=disabled", + <&rpi_poe_power_supply>,"status=disabled", + <&poe_mfd>,"status=okay", + <&fan>,"pwms:0=",<&poe_mfd_pwm>; +}; diff --git a/arch/arm/boot/dts/overlays/rpi-rp2040-gpio-bridge.dtsi b/arch/arm/boot/dts/overlays/rpi-rp2040-gpio-bridge.dtsi new file mode 100644 index 00000000000000..2b7f670a1f6d0c --- /dev/null +++ b/arch/arm/boot/dts/overlays/rpi-rp2040-gpio-bridge.dtsi @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0-only +spi_bridge: spi@40 { + reg = <0x40>; + compatible = "raspberrypi,rp2040-gpio-bridge"; + status = "disabled"; + #address-cells = <1>; + #size-cells = <0>; + + power-supply = <&cam1_reg>; + + #gpio-cells = <2>; + gpio-controller; + + spi_bridgedev0: spidev@0{ + compatible = "spidev"; + reg = <0>; /* CE0 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <35000000>; + }; +}; diff --git a/arch/arm/boot/dts/overlays/rpi-sense-overlay.dts b/arch/arm/boot/dts/overlays/rpi-sense-overlay.dts new file mode 100644 index 00000000000000..32e99b7effc897 --- /dev/null +++ b/arch/arm/boot/dts/overlays/rpi-sense-overlay.dts @@ -0,0 +1,69 @@ +// rpi-sense HAT +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + sensehat@46 { + compatible = "raspberrypi,sensehat"; + reg = <0x46>; + interrupt-parent = <&gpio>; + status = "okay"; + + display { + compatible = "raspberrypi,rpi-sense-fb"; + status = "okay"; + }; + joystick { + compatible = "raspberrypi,sensehat-joystick"; + interrupts = <23 1>; + pinctrl-names = "default"; + pinctrl-0 = <&sensehat_pins>; + status = "okay"; + }; + }; + + lsm9ds1-magn@1c { + compatible = "st,lsm9ds1-magn"; + reg = <0x1c>; + status = "okay"; + }; + + lsm9ds1-accel6a { + compatible = "st,lsm9ds1-accel"; + reg = <0x6a>; + status = "okay"; + }; + + lps25h-press@5c { + compatible = "st,lps25h-press"; + reg = <0x5c>; + status = "okay"; + }; + + hts221-humid@5f { + compatible = "st,hts221-humid", "st,hts221"; + reg = <0x5f>; + status = "okay"; + }; + }; + }; + + fragment@1 { + target = <&gpio>; + __overlay__ { + sensehat_pins: sensehat_pins { + brcm,pins = <23>; + brcm,function = <0>; + }; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/rpi-sense-v2-overlay.dts b/arch/arm/boot/dts/overlays/rpi-sense-v2-overlay.dts new file mode 100644 index 00000000000000..c4fe97db52fbc7 --- /dev/null +++ b/arch/arm/boot/dts/overlays/rpi-sense-v2-overlay.dts @@ -0,0 +1,69 @@ +// rpi-sense HAT +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + sensehat@46 { + compatible = "raspberrypi,sensehat"; + reg = <0x46>; + interrupt-parent = <&gpio>; + status = "okay"; + + display { + compatible = "raspberrypi,rpi-sense-fb"; + status = "okay"; + }; + joystick { + compatible = "raspberrypi,sensehat-joystick"; + interrupts = <23 1>; + pinctrl-names = "default"; + pinctrl-0 = <&sensehat_pins>; + status = "okay"; + }; + }; + + lsm9ds1-magn@1c { + compatible = "st,lsm9ds1-magn"; + reg = <0x1c>; + status = "okay"; + }; + + lps25h-press@5c { + compatible = "st,lps25h-press"; + reg = <0x5c>; + status = "okay"; + }; + + hts221-humid@5f { + compatible = "st,hts221-humid", "st,hts221"; + reg = <0x5f>; + status = "okay"; + }; + + lsm9ds1-accel@6a { + compatible = "st,lsm9ds1-accel"; + reg = <0x6a>; + status = "okay"; + }; + }; + }; + + fragment@1 { + target = <&gpio>; + __overlay__ { + sensehat_pins: sensehat_pins { + brcm,pins = <23>; + brcm,function = <0>; + }; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/rpi-tv-overlay.dts b/arch/arm/boot/dts/overlays/rpi-tv-overlay.dts new file mode 100644 index 00000000000000..3c97a545d8207b --- /dev/null +++ b/arch/arm/boot/dts/overlays/rpi-tv-overlay.dts @@ -0,0 +1,34 @@ +// rpi-tv HAT + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@1 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + status = "okay"; + + cxd2880@0 { + compatible = "sony,cxd2880"; + reg = <0>; /* CE0 */ + spi-max-frequency = <50000000>; + status = "okay"; + }; + }; + }; + +}; diff --git a/arch/arm/boot/dts/overlays/rra-digidac1-wm8741-audio-overlay.dts b/arch/arm/boot/dts/overlays/rra-digidac1-wm8741-audio-overlay.dts new file mode 100644 index 00000000000000..97db53a91fdaa6 --- /dev/null +++ b/arch/arm/boot/dts/overlays/rra-digidac1-wm8741-audio-overlay.dts @@ -0,0 +1,49 @@ +// Definitions for RRA DigiDAC1 Audio card +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_consumer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + wm8804@3b { + #sound-dai-cells = <0>; + compatible = "wlf,wm8804"; + reg = <0x3b>; + status = "okay"; + PVDD-supply = <&vdd_3v3_reg>; + DVDD-supply = <&vdd_3v3_reg>; + }; + + wm8742: wm8741@1a { + compatible = "wlf,wm8741"; + reg = <0x1a>; + status = "okay"; + AVDD-supply = <&vdd_5v0_reg>; + DVDD-supply = <&vdd_3v3_reg>; + }; + }; + }; + + fragment@2 { + target = <&sound>; + __overlay__ { + compatible = "rra,digidac1-soundcard"; + i2s-controller = <&i2s_clk_consumer>; + status = "okay"; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/sainsmart18-overlay.dts b/arch/arm/boot/dts/overlays/sainsmart18-overlay.dts new file mode 100644 index 00000000000000..c51f1c030a557b --- /dev/null +++ b/arch/arm/boot/dts/overlays/sainsmart18-overlay.dts @@ -0,0 +1,52 @@ +/* + * Device Tree overlay for the Sainsmart 1.8" TFT LCD with ST7735R chip 160x128 + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@1 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + ss18: sainsmart18@0 { + compatible = "fbtft,sainsmart18"; + reg = <0>; + pinctrl-names = "default"; + spi-max-frequency = <40000000>; + rotate = <90>; + buswidth = <8>; + fps = <50>; + height = <160>; + width = <128>; + reset-gpios = <&gpio 25 1>; + dc-gpios = <&gpio 24 0>; + debug = <0>; + }; + }; + }; + + __overrides__ { + speed = <&ss18>,"spi-max-frequency:0"; + rotate = <&ss18>,"rotate:0"; + fps = <&ss18>,"fps:0"; + bgr = <&ss18>,"bgr?"; + debug = <&ss18>,"debug:0"; + dc_pin = <&ss18>,"dc-gpios:4"; + reset_pin = <&ss18>,"reset-gpios:4"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/sc16is750-i2c-overlay.dts b/arch/arm/boot/dts/overlays/sc16is750-i2c-overlay.dts new file mode 100644 index 00000000000000..700e8a60f18256 --- /dev/null +++ b/arch/arm/boot/dts/overlays/sc16is750-i2c-overlay.dts @@ -0,0 +1,58 @@ +/dts-v1/; +/plugin/; + +#include "i2c-buses.dtsi" + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2cbus>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + + sc16is750: sc16is750@48 { + compatible = "nxp,sc16is750"; + reg = <0x48>; /* i2c address */ + clocks = <&sc16is750_clk>; + interrupt-parent = <&gpio>; + interrupts = <24 2>; /* IRQ_TYPE_EDGE_FALLING */ + pinctrl-0 = <&int_pins>; + pinctrl-names = "default"; + gpio-controller; + #gpio-cells = <2>; + i2c-max-frequency = <400000>; + }; + }; + }; + + fragment@1 { + target = <&clocks>; + __overlay__ { + sc16is750_clk: sc16is750_i2c_clk@48 { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <14745600>; + }; + }; + }; + + fragment@2 { + target = <&gpio>; + __overlay__ { + int_pins: int_pins@18 { + brcm,pins = <24>; + brcm,function = <0>; /* in */ + brcm,pull = <0>; /* none */ + }; + }; + }; + + __overrides__ { + int_pin = <&sc16is750>,"interrupts:0", <&int_pins>,"brcm,pins:0", + <&int_pins>,"reg:0"; + addr = <&sc16is750>,"reg:0", <&sc16is750_clk>,"name"; + xtal = <&sc16is750_clk>,"clock-frequency:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/sc16is750-spi0-overlay.dts b/arch/arm/boot/dts/overlays/sc16is750-spi0-overlay.dts new file mode 100644 index 00000000000000..b289ee900edfe4 --- /dev/null +++ b/arch/arm/boot/dts/overlays/sc16is750-spi0-overlay.dts @@ -0,0 +1,63 @@ +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spi0>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + sc16is750: sc16is750@0 { + compatible = "nxp,sc16is750"; + reg = <0>; /* CE0 */ + clocks = <&sc16is750_clk>; + interrupt-parent = <&gpio>; + interrupts = <24 2>; /* IRQ_TYPE_EDGE_FALLING */ + pinctrl-0 = <&int_pins>; + pinctrl-names = "default"; + gpio-controller; + #gpio-cells = <2>; + spi-max-frequency = <4000000>; + }; + }; + }; + + fragment@1 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@2 { + target-path = "/"; + __overlay__ { + sc16is750_clk: sc16is750_spi0_0_clk { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <14745600>; + }; + }; + }; + + fragment@3 { + target = <&gpio>; + __overlay__ { + int_pins: int_pins@18 { + brcm,pins = <24>; + brcm,function = <0>; /* in */ + brcm,pull = <0>; /* none */ + }; + }; + }; + + __overrides__ { + int_pin = <&sc16is750>,"interrupts:0", <&int_pins>,"brcm,pins:0", + <&int_pins>,"reg:0"; + xtal = <&sc16is750_clk>,"clock-frequency:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/sc16is752-i2c-overlay.dts b/arch/arm/boot/dts/overlays/sc16is752-i2c-overlay.dts new file mode 100644 index 00000000000000..d481d6aa823198 --- /dev/null +++ b/arch/arm/boot/dts/overlays/sc16is752-i2c-overlay.dts @@ -0,0 +1,58 @@ +/dts-v1/; +/plugin/; + +#include "i2c-buses.dtsi" + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2cbus>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + + sc16is752: sc16is752@48 { + compatible = "nxp,sc16is752"; + reg = <0x48>; /* i2c address */ + clocks = <&sc16is752_clk>; + interrupt-parent = <&gpio>; + interrupts = <24 2>; /* IRQ_TYPE_EDGE_FALLING */ + pinctrl-0 = <&int_pins>; + pinctrl-names = "default"; + gpio-controller; + #gpio-cells = <2>; + i2c-max-frequency = <400000>; + }; + }; + }; + + fragment@1 { + target = <&clocks>; + __overlay__ { + sc16is752_clk: sc16is752_i2c_clk@48 { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <14745600>; + }; + }; + }; + + fragment@2 { + target = <&gpio>; + __overlay__ { + int_pins: int_pins@18 { + brcm,pins = <24>; + brcm,function = <0>; /* in */ + brcm,pull = <0>; /* none */ + }; + }; + }; + + __overrides__ { + int_pin = <&sc16is752>,"interrupts:0", <&int_pins>,"brcm,pins:0", + <&int_pins>,"reg:0"; + addr = <&sc16is752>,"reg:0",<&sc16is752_clk>,"name"; + xtal = <&sc16is752_clk>,"clock-frequency:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/sc16is752-spi0-overlay.dts b/arch/arm/boot/dts/overlays/sc16is752-spi0-overlay.dts new file mode 100644 index 00000000000000..5f89410858317c --- /dev/null +++ b/arch/arm/boot/dts/overlays/sc16is752-spi0-overlay.dts @@ -0,0 +1,63 @@ +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spi0>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + sc16is752: sc16is752@0 { + compatible = "nxp,sc16is752"; + reg = <0>; /* CE0 */ + clocks = <&sc16is752_clk>; + interrupt-parent = <&gpio>; + interrupts = <24 2>; /* IRQ_TYPE_EDGE_FALLING */ + pinctrl-0 = <&int_pins>; + pinctrl-names = "default"; + gpio-controller; + #gpio-cells = <2>; + spi-max-frequency = <4000000>; + }; + }; + }; + + fragment@1 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@2 { + target-path = "/"; + __overlay__ { + sc16is752_clk: sc16is752_spi0_0_clk { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <14745600>; + }; + }; + }; + + fragment@3 { + target = <&gpio>; + __overlay__ { + int_pins: int_pins@18 { + brcm,pins = <24>; + brcm,function = <0>; /* in */ + brcm,pull = <0>; /* none */ + }; + }; + }; + + __overrides__ { + int_pin = <&sc16is752>,"interrupts:0", <&int_pins>,"brcm,pins:0", + <&int_pins>,"reg:0"; + xtal = <&sc16is752_clk>,"clock-frequency:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/sc16is752-spi1-overlay.dts b/arch/arm/boot/dts/overlays/sc16is752-spi1-overlay.dts new file mode 100644 index 00000000000000..a9b64a98c278cd --- /dev/null +++ b/arch/arm/boot/dts/overlays/sc16is752-spi1-overlay.dts @@ -0,0 +1,76 @@ +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&gpio>; + __overlay__ { + spi1_pins: spi1_pins { + brcm,pins = <19 20 21>; + brcm,function = <3>; /* alt4 */ + }; + + spi1_cs_pins: spi1_cs_pins { + brcm,pins = <18>; + brcm,function = <1>; /* output */ + }; + + int_pins: int_pins@18 { + brcm,pins = <24>; + brcm,function = <0>; /* in */ + brcm,pull = <0>; /* none */ + }; + }; + }; + + fragment@1 { + target = <&spi1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&spi1_pins &spi1_cs_pins>; + cs-gpios = <&gpio 18 1>; + status = "okay"; + + sc16is752: sc16is752@0 { + compatible = "nxp,sc16is752"; + reg = <0>; /* CE0 */ + clocks = <&sc16is752_clk>; + interrupt-parent = <&gpio>; + interrupts = <24 2>; /* IRQ_TYPE_EDGE_FALLING */ + pinctrl-0 = <&int_pins>; + pinctrl-names = "default"; + gpio-controller; + #gpio-cells = <2>; + spi-max-frequency = <4000000>; + }; + }; + }; + + fragment@2 { + target = <&aux>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@3 { + target-path = "/"; + __overlay__ { + sc16is752_clk: sc16is752_spi1_0_clk { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <14745600>; + }; + }; + }; + + __overrides__ { + int_pin = <&sc16is752>,"interrupts:0", <&int_pins>,"brcm,pins:0", + <&int_pins>,"reg:0"; + xtal = <&sc16is752_clk>,"clock-frequency:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/sdhost-overlay.dts b/arch/arm/boot/dts/overlays/sdhost-overlay.dts new file mode 100644 index 00000000000000..0b72b4eeac8877 --- /dev/null +++ b/arch/arm/boot/dts/overlays/sdhost-overlay.dts @@ -0,0 +1,38 @@ +/dts-v1/; +/plugin/; + +/* Provide backwards compatible aliases for the old sdhost dtparams. */ + +/{ + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&sdhost>; + frag0: __overlay__ { + brcm,overclock-50 = <0>; + brcm,pio-limit = <1>; + status = "okay"; + }; + }; + + fragment@1 { + target = <&mmc>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@2 { + target = <&mmcnr>; + __overlay__ { + status = "disabled"; + }; + }; + + __overrides__ { + overclock_50 = <&frag0>,"brcm,overclock-50:0"; + force_pio = <&frag0>,"brcm,force-pio?"; + pio_limit = <&frag0>,"brcm,pio-limit:0"; + debug = <&frag0>,"brcm,debug?"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/sdio-overlay.dts b/arch/arm/boot/dts/overlays/sdio-overlay.dts new file mode 100644 index 00000000000000..873e490563797a --- /dev/null +++ b/arch/arm/boot/dts/overlays/sdio-overlay.dts @@ -0,0 +1,77 @@ +/dts-v1/; +/plugin/; + +/* Enable SDIO from MMC interface via various GPIO groups */ + +/{ + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&mmcnr>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@1 { + target = <&mmc>; + sdio_ovl: __overlay__ { + pinctrl-0 = <&sdio_ovl_pins>; + pinctrl-names = "default"; + non-removable; + bus-width = <4>; + status = "okay"; + }; + }; + + fragment@2 { + target = <&gpio>; + __overlay__ { + sdio_ovl_pins: sdio_ovl_pins { + brcm,pins = <22 23 24 25 26 27>; + brcm,function = <7>; /* ALT3 = SD1 */ + brcm,pull = <0 2 2 2 2 2>; + }; + }; + }; + + fragment@3 { + target = <&sdio_ovl_pins>; + __dormant__ { + brcm,pins = <22 23 24 25>; + brcm,pull = <0 2 2 2>; + }; + }; + + fragment@4 { + target = <&sdio_ovl_pins>; + __dormant__ { + brcm,pins = <34 35 36 37>; + brcm,pull = <0 2 2 2>; + }; + }; + + fragment@5 { + target = <&sdio_ovl_pins>; + __dormant__ { + brcm,pins = <34 35 36 37 38 39>; + brcm,pull = <0 2 2 2 2 2>; + }; + }; + + fragment@6 { + target-path = "/aliases"; + __overlay__ { + mmc1 = "/soc/mmc@7e300000"; + }; + }; + + __overrides__ { + poll_once = <&sdio_ovl>,"non-removable?"; + bus_width = <&sdio_ovl>,"bus-width:0"; + sdio_overclock = <&sdio_ovl>,"brcm,overclock-50:0"; + gpios_22_25 = <0>,"=3"; + gpios_34_37 = <0>,"=4"; + gpios_34_39 = <0>,"=5"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/sdio-pi5-overlay.dts b/arch/arm/boot/dts/overlays/sdio-pi5-overlay.dts new file mode 100644 index 00000000000000..4e42cb5c856ebd --- /dev/null +++ b/arch/arm/boot/dts/overlays/sdio-pi5-overlay.dts @@ -0,0 +1,24 @@ +/dts-v1/; +/plugin/; + +/* SDIO/SD/MMC on RP1 bank 0 */ + +/{ + compatible = "brcm,bcm2712"; + + fragment@0 { + target = <&rp1_mmc0>; + frag0: __overlay__ { + status = "okay"; + pinctrl-0 = <&rp1_sdio0_22_27>; + pinctrl-names = "default"; + }; + }; + + fragment@1 { + target = <&rp1_sdio_clk0>; + frag1: __overlay__ { + status = "okay"; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/seeed-can-fd-hat-v1-overlay.dts b/arch/arm/boot/dts/overlays/seeed-can-fd-hat-v1-overlay.dts new file mode 100644 index 00000000000000..210d027a073ee1 --- /dev/null +++ b/arch/arm/boot/dts/overlays/seeed-can-fd-hat-v1-overlay.dts @@ -0,0 +1,138 @@ +// redo: ovmerge -c spi1-1cs-overlay.dts,cs0_pin=18,cs0_spidev=false mcp251xfd-overlay.dts,spi0-0,interrupt=25 mcp251xfd-overlay.dts,spi1-0,interrupt=24 + +// Device tree overlay for https://www.seeedstudio.com/2-Channel-CAN-BUS-FD-Shield-for-Raspberry-Pi-p-4072.html + +/dts-v1/; +/plugin/; + +#include <dt-bindings/gpio/gpio.h> +#include <dt-bindings/interrupt-controller/irq.h> +#include <dt-bindings/pinctrl/bcm2835.h> + +/ { + compatible = "brcm,bcm2835"; + fragment@0 { + target = <&gpio>; + __overlay__ { + spi1_pins: spi1_pins { + brcm,pins = <19 20 21>; + brcm,function = <3>; + }; + spi1_cs_pins: spi1_cs_pins { + brcm,pins = <18>; + brcm,function = <1>; + }; + }; + }; + fragment@1 { + target = <&spi1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&spi1_pins &spi1_cs_pins>; + cs-gpios = <&gpio 18 1>; + status = "okay"; + spidev@0 { + compatible = "spidev"; + reg = <0>; + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + status = "disabled"; + }; + }; + }; + fragment@2 { + target = <&aux>; + __overlay__ { + status = "okay"; + }; + }; + fragment@3 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + fragment@4 { + target = <&gpio>; + __overlay__ { + mcp251xfd_pins: mcp251xfd_spi0_0_pins { + brcm,pins = <25>; + brcm,function = <BCM2835_FSEL_GPIO_IN>; + }; + }; + }; + fragment@5 { + target-path = "/clocks"; + __overlay__ { + clk_mcp251xfd_osc: mcp251xfd-spi0-0-osc { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <40000000>; + }; + }; + }; + fragment@6 { + target = <&spi0>; + __overlay__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + mcp251xfd@0 { + compatible = "microchip,mcp251xfd"; + reg = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&mcp251xfd_pins>; + spi-max-frequency = <20000000>; + interrupt-parent = <&gpio>; + interrupts = <25 IRQ_TYPE_LEVEL_LOW>; + clocks = <&clk_mcp251xfd_osc>; + }; + }; + }; + fragment@7 { + target-path = "spi1/spidev@0"; + __overlay__ { + status = "disabled"; + }; + }; + fragment@8 { + target = <&gpio>; + __overlay__ { + mcp251xfd_pins_1: mcp251xfd_spi1_0_pins { + brcm,pins = <24>; + brcm,function = <BCM2835_FSEL_GPIO_IN>; + }; + }; + }; + fragment@9 { + target-path = "/clocks"; + __overlay__ { + clk_mcp251xfd_osc_1: mcp251xfd-spi1-0-osc { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <40000000>; + }; + }; + }; + fragment@10 { + target = <&spi1>; + __overlay__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + mcp251xfd@0 { + compatible = "microchip,mcp251xfd"; + reg = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&mcp251xfd_pins_1>; + spi-max-frequency = <20000000>; + interrupt-parent = <&gpio>; + interrupts = <24 IRQ_TYPE_LEVEL_LOW>; + clocks = <&clk_mcp251xfd_osc_1>; + }; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/seeed-can-fd-hat-v2-overlay.dts b/arch/arm/boot/dts/overlays/seeed-can-fd-hat-v2-overlay.dts new file mode 100644 index 00000000000000..fc3c7794c28a32 --- /dev/null +++ b/arch/arm/boot/dts/overlays/seeed-can-fd-hat-v2-overlay.dts @@ -0,0 +1,112 @@ +// redo: ovmerge -c mcp251xfd-overlay.dts,spi0-0,interrupt=25 mcp251xfd-overlay.dts,spi0-1,interrupt=24 i2c-rtc-overlay.dts,pcf85063 + +// Device tree overlay for https://www.seeedstudio.com/CAN-BUS-FD-HAT-for-Raspberry-Pi-p-4742.html + +/dts-v1/; +/plugin/; + +#include <dt-bindings/gpio/gpio.h> +#include <dt-bindings/interrupt-controller/irq.h> +#include <dt-bindings/pinctrl/bcm2835.h> + +/ { + compatible = "brcm,bcm2835"; + fragment@0 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + fragment@1 { + target = <&gpio>; + __overlay__ { + mcp251xfd_pins: mcp251xfd_spi0_0_pins { + brcm,pins = <25>; + brcm,function = <BCM2835_FSEL_GPIO_IN>; + }; + }; + }; + fragment@2 { + target-path = "/clocks"; + __overlay__ { + clk_mcp251xfd_osc: mcp251xfd-spi0-0-osc { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <40000000>; + }; + }; + }; + fragment@3 { + target = <&spi0>; + __overlay__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + mcp251xfd@0 { + compatible = "microchip,mcp251xfd"; + reg = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&mcp251xfd_pins>; + spi-max-frequency = <20000000>; + interrupt-parent = <&gpio>; + interrupts = <25 IRQ_TYPE_LEVEL_LOW>; + clocks = <&clk_mcp251xfd_osc>; + }; + }; + }; + fragment@4 { + target = <&spidev1>; + __overlay__ { + status = "disabled"; + }; + }; + fragment@5 { + target = <&gpio>; + __overlay__ { + mcp251xfd_pins_1: mcp251xfd_spi0_1_pins { + brcm,pins = <24>; + brcm,function = <BCM2835_FSEL_GPIO_IN>; + }; + }; + }; + fragment@6 { + target-path = "/clocks"; + __overlay__ { + clk_mcp251xfd_osc_1: mcp251xfd-spi0-1-osc { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <40000000>; + }; + }; + }; + fragment@7 { + target = <&spi0>; + __overlay__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + mcp251xfd@1 { + compatible = "microchip,mcp251xfd"; + reg = <1>; + pinctrl-names = "default"; + pinctrl-0 = <&mcp251xfd_pins_1>; + spi-max-frequency = <20000000>; + interrupt-parent = <&gpio>; + interrupts = <24 IRQ_TYPE_LEVEL_LOW>; + clocks = <&clk_mcp251xfd_osc_1>; + }; + }; + }; + fragment@8 { + target = <&i2c_arm>; + __overlay__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + pcf85063@51 { + compatible = "nxp,pcf85063"; + reg = <0x51>; + }; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/sh1106-spi-overlay.dts b/arch/arm/boot/dts/overlays/sh1106-spi-overlay.dts new file mode 100644 index 00000000000000..57a0cc9b174102 --- /dev/null +++ b/arch/arm/boot/dts/overlays/sh1106-spi-overlay.dts @@ -0,0 +1,84 @@ +/* + * Device Tree overlay for SH1106 based SPI OLED display + * + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spi0>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@2 { + target = <&spidev1>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@3 { + target = <&gpio>; + __overlay__ { + sh1106_pins: sh1106_pins { + brcm,pins = <25 24>; + brcm,function = <1 1>; /* out out */ + }; + }; + }; + + fragment@4 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + sh1106: sh1106@0{ + compatible = "sinowealth,sh1106"; + reg = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&sh1106_pins>; + + spi-max-frequency = <4000000>; + bgr = <0>; + bpp = <1>; + rotate = <0>; + fps = <25>; + buswidth = <8>; + reset-gpios = <&gpio 25 1>; + dc-gpios = <&gpio 24 0>; + debug = <0>; + + sinowealth,height = <64>; + sinowealth,width = <128>; + sinowealth,page-offset = <0>; + }; + }; + }; + + __overrides__ { + speed = <&sh1106>,"spi-max-frequency:0"; + rotate = <&sh1106>,"rotate:0"; + fps = <&sh1106>,"fps:0"; + debug = <&sh1106>,"debug:0"; + dc_pin = <&sh1106>,"dc-gpios:4", + <&sh1106_pins>,"brcm,pins:4"; + reset_pin = <&sh1106>,"reset-gpios:4", + <&sh1106_pins>,"brcm,pins:0"; + height = <&sh1106>,"sinowealth,height:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/si446x-spi0-overlay.dts b/arch/arm/boot/dts/overlays/si446x-spi0-overlay.dts new file mode 100644 index 00000000000000..90495f0941fbb9 --- /dev/null +++ b/arch/arm/boot/dts/overlays/si446x-spi0-overlay.dts @@ -0,0 +1,53 @@ +// Overlay for the SiLabs Si446X Controller - SPI0 +// Default Interrupt Pin: 17 +// Default SDN Pin: 27 +/dts-v1/; +/plugin/; + + / { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spi0>; + __overlay__ { + // needed to avoid dtc warning + #address-cells = <1>; + #size-cells = <0>; + + status = "okay"; + + uhf0: si446x@0{ + compatible = "silabs,si446x"; + reg = <0>; // CE0 + pinctrl-names = "default"; + pinctrl-0 = <&uhf0_pins>; + interrupt-parent = <&gpio>; + interrupts = <17 0x2>; // falling edge + spi-max-frequency = <4000000>; + sdn_pin = <27>; + irq_pin = <17>; + status = "okay"; + }; + }; + }; + + fragment@1 { + target = <&gpio>; + __overlay__ { + uhf0_pins: uhf0_pins { + brcm,pins = <17 27>; + brcm,function = <0 1>; // in, out + brcm,pull = <2 0>; // high, none + }; + }; + }; + + __overrides__ { + int_pin = <&uhf0>, "interrupts:0", + <&uhf0>, "irq_pin:0", + <&uhf0_pins>, "brcm,pins:0"; + reset_pin = <&uhf0>, "sdn_pin:0", + <&uhf0_pins>, "brcm,pins:4"; + speed = <&uhf0>, "spi-max-frequency:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/smi-dev-overlay.dts b/arch/arm/boot/dts/overlays/smi-dev-overlay.dts new file mode 100644 index 00000000000000..bafab6c92506d4 --- /dev/null +++ b/arch/arm/boot/dts/overlays/smi-dev-overlay.dts @@ -0,0 +1,20 @@ +// Description: Overlay to enable character device interface for SMI. +// Author: Luke Wren <luke@raspberrypi.org> + +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&soc>; + __overlay__ { + smi_dev { + compatible = "brcm,bcm2835-smi-dev"; + smi_handle = <&smi>; + status = "okay"; + }; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/smi-nand-overlay.dts b/arch/arm/boot/dts/overlays/smi-nand-overlay.dts new file mode 100644 index 00000000000000..ae1e50329d6602 --- /dev/null +++ b/arch/arm/boot/dts/overlays/smi-nand-overlay.dts @@ -0,0 +1,66 @@ +// Description: Overlay to enable NAND flash through +// the secondary memory interface +// Author: Luke Wren + +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&smi>; + __overlay__ { + pinctrl-names = "default"; + pinctrl-0 = <&smi_pins>; + status = "okay"; + }; + }; + + fragment@1 { + target = <&soc>; + __overlay__ { + nand: flash@0 { + compatible = "brcm,bcm2835-smi-nand"; + smi_handle = <&smi>; + #address-cells = <1>; + #size-cells = <1>; + status = "okay"; + + partition@0 { + label = "stage2"; + // 128k + reg = <0 0x20000>; + read-only; + }; + partition@1 { + label = "firmware"; + // 16M + reg = <0x20000 0x1000000>; + read-only; + }; + partition@2 { + label = "root"; + // 2G (will need to use 64 bit for >=4G) + reg = <0x1020000 0x80000000>; + }; + }; + }; + }; + + fragment@2 { + target = <&gpio>; + __overlay__ { + smi_pins: smi_pins { + brcm,pins = <0 1 2 3 4 5 6 7 8 9 10 11 + 12 13 14 15>; + /* Alt 1: SMI */ + brcm,function = <5 5 5 5 5 5 5 5 5 5 5 + 5 5 5 5 5>; + /* /CS, /WE and /OE are pulled high, as they are + generally active low signals */ + brcm,pull = <2 2 2 2 2 2 2 2 0 0 0 0 0 0 0 0>; + }; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/smi-overlay.dts b/arch/arm/boot/dts/overlays/smi-overlay.dts new file mode 100644 index 00000000000000..bb8c7830df23f3 --- /dev/null +++ b/arch/arm/boot/dts/overlays/smi-overlay.dts @@ -0,0 +1,37 @@ +// Description: Overlay to enable the secondary memory interface peripheral +// Author: Luke Wren + +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&smi>; + __overlay__ { + pinctrl-names = "default"; + pinctrl-0 = <&smi_pins>; + status = "okay"; + }; + }; + + fragment@1 { + target = <&gpio>; + __overlay__ { + smi_pins: smi_pins { + /* Don't configure the top two address bits, as + these are already used as ID_SD and ID_SC */ + brcm,pins = <2 3 4 5 6 7 8 9 10 11 12 13 14 15 + 16 17 18 19 20 21 22 23 24 25>; + /* Alt 1: SMI */ + brcm,function = <5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 + 5 5 5 5 5 5 5 5 5>; + /* /CS, /WE and /OE are pulled high, as they are + generally active low signals */ + brcm,pull = <2 2 2 2 2 2 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0>; + }; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/spi-gpio35-39-overlay.dts b/arch/arm/boot/dts/overlays/spi-gpio35-39-overlay.dts new file mode 100644 index 00000000000000..a132b8637c313e --- /dev/null +++ b/arch/arm/boot/dts/overlays/spi-gpio35-39-overlay.dts @@ -0,0 +1,31 @@ +/* + * Device tree overlay to move spi0 to gpio 35 to 39 on CM + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spi0>; + __overlay__ { + cs-gpios = <&gpio 36 1>, <&gpio 35 1>; + }; + }; + + fragment@1 { + target = <&spi0_cs_pins>; + __overlay__ { + brcm,pins = <36 35>; + }; + }; + + fragment@2 { + target = <&spi0_pins>; + __overlay__ { + brcm,pins = <37 38 39>; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/spi-gpio40-45-overlay.dts b/arch/arm/boot/dts/overlays/spi-gpio40-45-overlay.dts new file mode 100644 index 00000000000000..9ebcaf1b5ea07c --- /dev/null +++ b/arch/arm/boot/dts/overlays/spi-gpio40-45-overlay.dts @@ -0,0 +1,36 @@ +/* + * Boot EEPROM overlay + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spi0>; + __overlay__ { + cs-gpios = <&gpio 43 1>, <&gpio 44 1>, <&gpio 45 1>; + status = "okay"; + }; + }; + + fragment@1 { + target = <&spi0_cs_pins>; + __overlay__ { + brcm,pins = <45 44 43>; + brcm,function = <1>; /* output */ + status = "okay"; + }; + }; + + fragment@2 { + target = <&spi0_pins>; + __overlay__ { + brcm,pins = <40 41 42>; + brcm,function = <3>; /* alt4 */ + status = "okay"; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/spi-rtc-overlay.dts b/arch/arm/boot/dts/overlays/spi-rtc-overlay.dts new file mode 100644 index 00000000000000..df3286929c2e3a --- /dev/null +++ b/arch/arm/boot/dts/overlays/spi-rtc-overlay.dts @@ -0,0 +1,75 @@ +// Definitions for several SPI-based Real Time Clocks +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&rtc>; + __dormant__ { + compatible = "dallas,ds3232"; + }; + }; + + fragment@1 { + target = <&rtc>; + __dormant__ { + compatible = "dallas,ds3234"; + }; + }; + + fragment@2 { + target = <&rtc>; + __dormant__ { + compatible = "nxp,rtc-pcf2123"; + }; + }; + + spidev: fragment@100 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + + frag101: fragment@101 { + target = <&spi0>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + rtc: rtc@0 { + reg = <0>; + spi-max-frequency = <5000000>; + }; + }; + }; + + __overrides__ { + spi0_0 = <&spidev>, "target:0=",<&spidev0>, + <&frag101>, "target:0=",<&spi0>, + <&rtc>, "reg:0=0"; + spi0_1 = <&spidev>, "target:0=",<&spidev1>, + <&frag101>, "target:0=",<&spi0>, + <&rtc>, "reg:0=1"; + spi1_0 = <0>,"-100", + <&frag101>, "target:0=",<&spi1>, + <&rtc>, "reg:0=0"; + spi1_1 = <0>,"-100", + <&frag101>, "target:0=",<&spi1>, + <&rtc>, "reg:0=1"; + spi2_0 = <0>,"-100", + <&frag101>, "target:0=",<&spi2>, + <&rtc>, "reg:0=0"; + spi2_1 = <0>,"-100", + <&frag101>, "target:0=",<&spi2>, + <&rtc>, "reg:0=1"; + cs_high = <&rtc>, "spi-cs-high?"; + + ds3232 = <0>,"+0"; + ds3234 = <0>,"+1"; + pcf2123 = <0>,"+2"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/spi0-0cs-overlay.dts b/arch/arm/boot/dts/overlays/spi0-0cs-overlay.dts new file mode 100644 index 00000000000000..0d2acabf56a469 --- /dev/null +++ b/arch/arm/boot/dts/overlays/spi0-0cs-overlay.dts @@ -0,0 +1,39 @@ +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spi0_cs_pins>; + frag0: __overlay__ { + brcm,pins; + }; + }; + + fragment@1 { + target = <&spi0>; + __overlay__ { + cs-gpios; + status = "okay"; + }; + }; + + fragment@2 { + target = <&spidev1>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@3 { + target = <&spi0_pins>; + __dormant__ { + brcm,pins = <10 11>; + }; + }; + + __overrides__ { + no_miso = <0>,"=3"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/spi0-1cs-inverted-overlay.dts b/arch/arm/boot/dts/overlays/spi0-1cs-inverted-overlay.dts new file mode 100644 index 00000000000000..dc1db2184df4a9 --- /dev/null +++ b/arch/arm/boot/dts/overlays/spi0-1cs-inverted-overlay.dts @@ -0,0 +1,59 @@ +/dts-v1/; +/plugin/; + +/* + * There are some devices that need an inverted Chip Select (CS) to select the + * device signal, as an example the AZDelivery 12864 display. That means that + * the CS polarity is active-high. To invert the CS signal the DT needs to set + * the cs-gpio to GPIO_ACTIVE_HIGH (0) in the controller and set the + * spi-cs-high in the peripheral property. On top of that, since this is a + * display the DT also needs to specify the write-only property. +*/ + +#include <dt-bindings/gpio/gpio.h> + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spi0_cs_pins>; + frag0: __overlay__ { + brcm,pins = <8>; + }; + }; + + fragment@1 { + target = <&spi0>; + frag1: __overlay__ { + cs-gpios = <&gpio 8 GPIO_ACTIVE_HIGH>; + status = "okay"; + }; + }; + + fragment@2 { + target = <&spidev1>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@3 { + target = <&spi0_pins>; + __dormant__ { + brcm,pins = <10 11>; + }; + }; + + fragment@4 { + target = <&spidev0>; + __overlay__ { + spi-cs-high; + }; + }; + + __overrides__ { + cs0_pin = <&frag0>,"brcm,pins:0", + <&frag1>,"cs-gpios:4"; + no_miso = <0>,"=3"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/spi0-1cs-overlay.dts b/arch/arm/boot/dts/overlays/spi0-1cs-overlay.dts new file mode 100644 index 00000000000000..e6eb66e2076aaf --- /dev/null +++ b/arch/arm/boot/dts/overlays/spi0-1cs-overlay.dts @@ -0,0 +1,42 @@ +/dts-v1/; +/plugin/; + + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spi0_cs_pins>; + frag0: __overlay__ { + brcm,pins = <8>; + }; + }; + + fragment@1 { + target = <&spi0>; + frag1: __overlay__ { + cs-gpios = <&gpio 8 1>; + status = "okay"; + }; + }; + + fragment@2 { + target = <&spidev1>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@3 { + target = <&spi0_pins>; + __dormant__ { + brcm,pins = <10 11>; + }; + }; + + __overrides__ { + cs0_pin = <&frag0>,"brcm,pins:0", + <&frag1>,"cs-gpios:4"; + no_miso = <0>,"=3"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/spi0-2cs-overlay.dts b/arch/arm/boot/dts/overlays/spi0-2cs-overlay.dts new file mode 100644 index 00000000000000..df6519537c3a8c --- /dev/null +++ b/arch/arm/boot/dts/overlays/spi0-2cs-overlay.dts @@ -0,0 +1,37 @@ +/dts-v1/; +/plugin/; + + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spi0_cs_pins>; + frag0: __overlay__ { + brcm,pins = <8 7>; + }; + }; + + fragment@1 { + target = <&spi0>; + frag1: __overlay__ { + cs-gpios = <&gpio 8 1>, <&gpio 7 1>; + status = "okay"; + }; + }; + + fragment@2 { + target = <&spi0_pins>; + __dormant__ { + brcm,pins = <10 11>; + }; + }; + + __overrides__ { + cs0_pin = <&frag0>,"brcm,pins:0", + <&frag1>,"cs-gpios:4"; + cs1_pin = <&frag0>,"brcm,pins:4", + <&frag1>,"cs-gpios:16"; + no_miso = <0>,"=2"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/spi1-1cs-overlay.dts b/arch/arm/boot/dts/overlays/spi1-1cs-overlay.dts new file mode 100644 index 00000000000000..ea2794bc5fd5d4 --- /dev/null +++ b/arch/arm/boot/dts/overlays/spi1-1cs-overlay.dts @@ -0,0 +1,57 @@ +/dts-v1/; +/plugin/; + + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&gpio>; + __overlay__ { + spi1_pins: spi1_pins { + brcm,pins = <19 20 21>; + brcm,function = <3>; /* alt4 */ + }; + + spi1_cs_pins: spi1_cs_pins { + brcm,pins = <18>; + brcm,function = <1>; /* output */ + }; + }; + }; + + fragment@1 { + target = <&spi1>; + frag1: __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&spi1_pins &spi1_cs_pins>; + cs-gpios = <&gpio 18 1>; + status = "okay"; + + spidev1_0: spidev@0 { + compatible = "spidev"; + reg = <0>; /* CE0 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + status = "okay"; + }; + }; + }; + + fragment@2 { + target = <&aux>; + __overlay__ { + status = "okay"; + }; + }; + + __overrides__ { + cs0_pin = <&spi1_cs_pins>,"brcm,pins:0", + <&frag1>,"cs-gpios:4"; + cs0_spidev = <&spidev1_0>,"status"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/spi1-2cs-overlay.dts b/arch/arm/boot/dts/overlays/spi1-2cs-overlay.dts new file mode 100644 index 00000000000000..dab34ee79ae283 --- /dev/null +++ b/arch/arm/boot/dts/overlays/spi1-2cs-overlay.dts @@ -0,0 +1,69 @@ +/dts-v1/; +/plugin/; + + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&gpio>; + __overlay__ { + spi1_pins: spi1_pins { + brcm,pins = <19 20 21>; + brcm,function = <3>; /* alt4 */ + }; + + spi1_cs_pins: spi1_cs_pins { + brcm,pins = <18 17>; + brcm,function = <1>; /* output */ + }; + }; + }; + + fragment@1 { + target = <&spi1>; + frag1: __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&spi1_pins &spi1_cs_pins>; + cs-gpios = <&gpio 18 1>, <&gpio 17 1>; + status = "okay"; + + spidev1_0: spidev@0 { + compatible = "spidev"; + reg = <0>; /* CE0 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + status = "okay"; + }; + + spidev1_1: spidev@1 { + compatible = "spidev"; + reg = <1>; /* CE1 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + status = "okay"; + }; + }; + }; + + fragment@2 { + target = <&aux>; + __overlay__ { + status = "okay"; + }; + }; + + __overrides__ { + cs0_pin = <&spi1_cs_pins>,"brcm,pins:0", + <&frag1>,"cs-gpios:4"; + cs1_pin = <&spi1_cs_pins>,"brcm,pins:4", + <&frag1>,"cs-gpios:16"; + cs0_spidev = <&spidev1_0>,"status"; + cs1_spidev = <&spidev1_1>,"status"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/spi1-3cs-overlay.dts b/arch/arm/boot/dts/overlays/spi1-3cs-overlay.dts new file mode 100644 index 00000000000000..bc7e7d04324bdb --- /dev/null +++ b/arch/arm/boot/dts/overlays/spi1-3cs-overlay.dts @@ -0,0 +1,81 @@ +/dts-v1/; +/plugin/; + + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&gpio>; + __overlay__ { + spi1_pins: spi1_pins { + brcm,pins = <19 20 21>; + brcm,function = <3>; /* alt4 */ + }; + + spi1_cs_pins: spi1_cs_pins { + brcm,pins = <18 17 16>; + brcm,function = <1>; /* output */ + }; + }; + }; + + fragment@1 { + target = <&spi1>; + frag1: __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&spi1_pins &spi1_cs_pins>; + cs-gpios = <&gpio 18 1>, <&gpio 17 1>, <&gpio 16 1>; + status = "okay"; + + spidev1_0: spidev@0 { + compatible = "spidev"; + reg = <0>; /* CE0 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + status = "okay"; + }; + + spidev1_1: spidev@1 { + compatible = "spidev"; + reg = <1>; /* CE1 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + status = "okay"; + }; + + spidev1_2: spidev@2 { + compatible = "spidev"; + reg = <2>; /* CE2 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + status = "okay"; + }; + }; + }; + + fragment@2 { + target = <&aux>; + __overlay__ { + status = "okay"; + }; + }; + + __overrides__ { + cs0_pin = <&spi1_cs_pins>,"brcm,pins:0", + <&frag1>,"cs-gpios:4"; + cs1_pin = <&spi1_cs_pins>,"brcm,pins:4", + <&frag1>,"cs-gpios:16"; + cs2_pin = <&spi1_cs_pins>,"brcm,pins:8", + <&frag1>,"cs-gpios:28"; + cs0_spidev = <&spidev1_0>,"status"; + cs1_spidev = <&spidev1_1>,"status"; + cs2_spidev = <&spidev1_2>,"status"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/spi2-1cs-overlay.dts b/arch/arm/boot/dts/overlays/spi2-1cs-overlay.dts new file mode 100644 index 00000000000000..2a29750462af85 --- /dev/null +++ b/arch/arm/boot/dts/overlays/spi2-1cs-overlay.dts @@ -0,0 +1,57 @@ +/dts-v1/; +/plugin/; + + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&gpio>; + __overlay__ { + spi2_pins: spi2_pins { + brcm,pins = <40 41 42>; + brcm,function = <3>; /* alt4 */ + }; + + spi2_cs_pins: spi2_cs_pins { + brcm,pins = <43>; + brcm,function = <1>; /* output */ + }; + }; + }; + + fragment@1 { + target = <&spi2>; + frag1: __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&spi2_pins &spi2_cs_pins>; + cs-gpios = <&gpio 43 1>; + status = "okay"; + + spidev2_0: spidev@0 { + compatible = "spidev"; + reg = <0>; /* CE0 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + status = "okay"; + }; + }; + }; + + fragment@2 { + target = <&aux>; + __overlay__ { + status = "okay"; + }; + }; + + __overrides__ { + cs0_pin = <&spi2_cs_pins>,"brcm,pins:0", + <&frag1>,"cs-gpios:4"; + cs0_spidev = <&spidev2_0>,"status"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/spi2-1cs-pi5-overlay.dts b/arch/arm/boot/dts/overlays/spi2-1cs-pi5-overlay.dts new file mode 100644 index 00000000000000..44382cc5a7c049 --- /dev/null +++ b/arch/arm/boot/dts/overlays/spi2-1cs-pi5-overlay.dts @@ -0,0 +1,33 @@ +/dts-v1/; +/plugin/; + + +/ { + compatible = "brcm,bcm2712"; + + fragment@0 { + target = <&spi2>; + frag1: __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + cs-gpios = <&gpio 0 1>; + status = "okay"; + + spidev2_0: spidev@0 { + compatible = "spidev"; + reg = <0>; /* CE0 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + status = "okay"; + }; + }; + }; + + __overrides__ { + cs0_pin = <&frag1>,"cs-gpios:4"; + cs0_spidev = <&spidev2_0>,"status"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/spi2-2cs-overlay.dts b/arch/arm/boot/dts/overlays/spi2-2cs-overlay.dts new file mode 100644 index 00000000000000..642678fc9ddd5f --- /dev/null +++ b/arch/arm/boot/dts/overlays/spi2-2cs-overlay.dts @@ -0,0 +1,69 @@ +/dts-v1/; +/plugin/; + + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&gpio>; + __overlay__ { + spi2_pins: spi2_pins { + brcm,pins = <40 41 42>; + brcm,function = <3>; /* alt4 */ + }; + + spi2_cs_pins: spi2_cs_pins { + brcm,pins = <43 44>; + brcm,function = <1>; /* output */ + }; + }; + }; + + fragment@1 { + target = <&spi2>; + frag1: __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&spi2_pins &spi2_cs_pins>; + cs-gpios = <&gpio 43 1>, <&gpio 44 1>; + status = "okay"; + + spidev2_0: spidev@0 { + compatible = "spidev"; + reg = <0>; /* CE0 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + status = "okay"; + }; + + spidev2_1: spidev@1 { + compatible = "spidev"; + reg = <1>; /* CE1 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + status = "okay"; + }; + }; + }; + + fragment@2 { + target = <&aux>; + __overlay__ { + status = "okay"; + }; + }; + + __overrides__ { + cs0_pin = <&spi2_cs_pins>,"brcm,pins:0", + <&frag1>,"cs-gpios:4"; + cs1_pin = <&spi2_cs_pins>,"brcm,pins:4", + <&frag1>,"cs-gpios:16"; + cs0_spidev = <&spidev2_0>,"status"; + cs1_spidev = <&spidev2_1>,"status"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/spi2-2cs-pi5-overlay.dts b/arch/arm/boot/dts/overlays/spi2-2cs-pi5-overlay.dts new file mode 100644 index 00000000000000..b37a2c21c7b474 --- /dev/null +++ b/arch/arm/boot/dts/overlays/spi2-2cs-pi5-overlay.dts @@ -0,0 +1,44 @@ +/dts-v1/; +/plugin/; + + +/ { + compatible = "brcm,bcm2712"; + + fragment@0 { + target = <&spi2>; + frag1: __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + cs-gpios = <&gpio 0 1>, <&gpio 24 1>; + status = "okay"; + + spidev2_0: spidev@0 { + compatible = "spidev"; + reg = <0>; /* CE0 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + status = "okay"; + }; + + spidev2_1: spidev@1 { + compatible = "spidev"; + reg = <1>; /* CE1 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + status = "okay"; + }; + }; + }; + + __overrides__ { + cs0_pin = <&frag1>,"cs-gpios:4"; + cs1_pin = <&frag1>,"cs-gpios:16"; + cs0_spidev = <&spidev2_0>,"status"; + cs1_spidev = <&spidev2_1>,"status"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/spi2-3cs-overlay.dts b/arch/arm/boot/dts/overlays/spi2-3cs-overlay.dts new file mode 100644 index 00000000000000..28d40c6c3c379e --- /dev/null +++ b/arch/arm/boot/dts/overlays/spi2-3cs-overlay.dts @@ -0,0 +1,81 @@ +/dts-v1/; +/plugin/; + + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&gpio>; + __overlay__ { + spi2_pins: spi2_pins { + brcm,pins = <40 41 42>; + brcm,function = <3>; /* alt4 */ + }; + + spi2_cs_pins: spi2_cs_pins { + brcm,pins = <43 44 45>; + brcm,function = <1>; /* output */ + }; + }; + }; + + fragment@1 { + target = <&spi2>; + frag1: __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&spi2_pins &spi2_cs_pins>; + cs-gpios = <&gpio 43 1>, <&gpio 44 1>, <&gpio 45 1>; + status = "okay"; + + spidev2_0: spidev@0 { + compatible = "spidev"; + reg = <0>; /* CE0 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + status = "okay"; + }; + + spidev2_1: spidev@1 { + compatible = "spidev"; + reg = <1>; /* CE1 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + status = "okay"; + }; + + spidev2_2: spidev@2 { + compatible = "spidev"; + reg = <2>; /* CE2 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + status = "okay"; + }; + }; + }; + + fragment@2 { + target = <&aux>; + __overlay__ { + status = "okay"; + }; + }; + + __overrides__ { + cs0_pin = <&spi2_cs_pins>,"brcm,pins:0", + <&frag1>,"cs-gpios:4"; + cs1_pin = <&spi2_cs_pins>,"brcm,pins:4", + <&frag1>,"cs-gpios:16"; + cs2_pin = <&spi2_cs_pins>,"brcm,pins:8", + <&frag1>,"cs-gpios:28"; + cs0_spidev = <&spidev2_0>,"status"; + cs1_spidev = <&spidev2_1>,"status"; + cs2_spidev = <&spidev2_2>,"status"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/spi3-1cs-overlay.dts b/arch/arm/boot/dts/overlays/spi3-1cs-overlay.dts new file mode 100644 index 00000000000000..7abea6d86fd09b --- /dev/null +++ b/arch/arm/boot/dts/overlays/spi3-1cs-overlay.dts @@ -0,0 +1,42 @@ +/dts-v1/; +/plugin/; + + +/ { + compatible = "brcm,bcm2711"; + + fragment@0 { + target = <&spi3_cs_pins>; + frag0: __overlay__ { + brcm,pins = <0>; + brcm,function = <1>; /* output */ + }; + }; + + fragment@1 { + target = <&spi3>; + frag1: __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + cs-gpios = <&gpio 0 1>; + status = "okay"; + + spidev3_0: spidev@0 { + compatible = "spidev"; + reg = <0>; /* CE0 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + status = "okay"; + }; + }; + }; + + __overrides__ { + cs0_pin = <&frag0>,"brcm,pins:0", + <&frag1>,"cs-gpios:4"; + cs0_spidev = <&spidev3_0>,"status"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/spi3-1cs-pi5-overlay.dts b/arch/arm/boot/dts/overlays/spi3-1cs-pi5-overlay.dts new file mode 100644 index 00000000000000..a94e3a9f35ce6d --- /dev/null +++ b/arch/arm/boot/dts/overlays/spi3-1cs-pi5-overlay.dts @@ -0,0 +1,33 @@ +/dts-v1/; +/plugin/; + + +/ { + compatible = "brcm,bcm2712"; + + fragment@0 { + target = <&spi3>; + frag1: __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + cs-gpios = <&gpio 4 1>; + status = "okay"; + + spidev3_0: spidev@0 { + compatible = "spidev"; + reg = <0>; /* CE0 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + status = "okay"; + }; + }; + }; + + __overrides__ { + cs0_pin = <&frag1>,"cs-gpios:4"; + cs0_spidev = <&spidev3_0>,"status"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/spi3-2cs-overlay.dts b/arch/arm/boot/dts/overlays/spi3-2cs-overlay.dts new file mode 100644 index 00000000000000..2f474ac769f5a9 --- /dev/null +++ b/arch/arm/boot/dts/overlays/spi3-2cs-overlay.dts @@ -0,0 +1,54 @@ +/dts-v1/; +/plugin/; + + +/ { + compatible = "brcm,bcm2711"; + + fragment@0 { + target = <&spi3_cs_pins>; + frag0: __overlay__ { + brcm,pins = <0 24>; + brcm,function = <1>; /* output */ + }; + }; + + fragment@1 { + target = <&spi3>; + frag1: __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + cs-gpios = <&gpio 0 1>, <&gpio 24 1>; + status = "okay"; + + spidev3_0: spidev@0 { + compatible = "spidev"; + reg = <0>; /* CE0 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + status = "okay"; + }; + + spidev3_1: spidev@1 { + compatible = "spidev"; + reg = <1>; /* CE1 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + status = "okay"; + }; + }; + }; + + __overrides__ { + cs0_pin = <&frag0>,"brcm,pins:0", + <&frag1>,"cs-gpios:4"; + cs1_pin = <&frag0>,"brcm,pins:4", + <&frag1>,"cs-gpios:16"; + cs0_spidev = <&spidev3_0>,"status"; + cs1_spidev = <&spidev3_1>,"status"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/spi3-2cs-pi5-overlay.dts b/arch/arm/boot/dts/overlays/spi3-2cs-pi5-overlay.dts new file mode 100644 index 00000000000000..259548b37d5c05 --- /dev/null +++ b/arch/arm/boot/dts/overlays/spi3-2cs-pi5-overlay.dts @@ -0,0 +1,44 @@ +/dts-v1/; +/plugin/; + + +/ { + compatible = "brcm,bcm2712"; + + fragment@0 { + target = <&spi3>; + frag1: __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + cs-gpios = <&gpio 4 1>, <&gpio 25 1>; + status = "okay"; + + spidev3_0: spidev@0 { + compatible = "spidev"; + reg = <0>; /* CE0 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + status = "okay"; + }; + + spidev3_1: spidev@1 { + compatible = "spidev"; + reg = <1>; /* CE1 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + status = "okay"; + }; + }; + }; + + __overrides__ { + cs0_pin = <&frag1>,"cs-gpios:4"; + cs1_pin = <&frag1>,"cs-gpios:16"; + cs0_spidev = <&spidev3_0>,"status"; + cs1_spidev = <&spidev3_1>,"status"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/spi4-1cs-overlay.dts b/arch/arm/boot/dts/overlays/spi4-1cs-overlay.dts new file mode 100644 index 00000000000000..66d89521124a53 --- /dev/null +++ b/arch/arm/boot/dts/overlays/spi4-1cs-overlay.dts @@ -0,0 +1,42 @@ +/dts-v1/; +/plugin/; + + +/ { + compatible = "brcm,bcm2711"; + + fragment@0 { + target = <&spi4_cs_pins>; + frag0: __overlay__ { + brcm,pins = <4>; + brcm,function = <1>; /* output */ + }; + }; + + fragment@1 { + target = <&spi4>; + frag1: __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + cs-gpios = <&gpio 4 1>; + status = "okay"; + + spidev4_0: spidev@0 { + compatible = "spidev"; + reg = <0>; /* CE0 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + status = "okay"; + }; + }; + }; + + __overrides__ { + cs0_pin = <&frag0>,"brcm,pins:0", + <&frag1>,"cs-gpios:4"; + cs0_spidev = <&spidev4_0>,"status"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/spi4-2cs-overlay.dts b/arch/arm/boot/dts/overlays/spi4-2cs-overlay.dts new file mode 100644 index 00000000000000..83d8cb8b918cd0 --- /dev/null +++ b/arch/arm/boot/dts/overlays/spi4-2cs-overlay.dts @@ -0,0 +1,54 @@ +/dts-v1/; +/plugin/; + + +/ { + compatible = "brcm,bcm2711"; + + fragment@0 { + target = <&spi4_cs_pins>; + frag0: __overlay__ { + brcm,pins = <4 25>; + brcm,function = <1>; /* output */ + }; + }; + + fragment@1 { + target = <&spi4>; + frag1: __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + cs-gpios = <&gpio 4 1>, <&gpio 25 1>; + status = "okay"; + + spidev4_0: spidev@0 { + compatible = "spidev"; + reg = <0>; /* CE0 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + status = "okay"; + }; + + spidev4_1: spidev@1 { + compatible = "spidev"; + reg = <1>; /* CE1 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + status = "okay"; + }; + }; + }; + + __overrides__ { + cs0_pin = <&frag0>,"brcm,pins:0", + <&frag1>,"cs-gpios:4"; + cs1_pin = <&frag0>,"brcm,pins:4", + <&frag1>,"cs-gpios:16"; + cs0_spidev = <&spidev4_0>,"status"; + cs1_spidev = <&spidev4_1>,"status"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/spi5-1cs-overlay.dts b/arch/arm/boot/dts/overlays/spi5-1cs-overlay.dts new file mode 100644 index 00000000000000..168b4825de34fd --- /dev/null +++ b/arch/arm/boot/dts/overlays/spi5-1cs-overlay.dts @@ -0,0 +1,42 @@ +/dts-v1/; +/plugin/; + + +/ { + compatible = "brcm,bcm2711"; + + fragment@0 { + target = <&spi5_cs_pins>; + frag0: __overlay__ { + brcm,pins = <12>; + brcm,function = <1>; /* output */ + }; + }; + + fragment@1 { + target = <&spi5>; + frag1: __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + cs-gpios = <&gpio 12 1>; + status = "okay"; + + spidev5_0: spidev@0 { + compatible = "spidev"; + reg = <0>; /* CE0 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + status = "okay"; + }; + }; + }; + + __overrides__ { + cs0_pin = <&frag0>,"brcm,pins:0", + <&frag1>,"cs-gpios:4"; + cs0_spidev = <&spidev5_0>,"status"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/spi5-1cs-pi5-overlay.dts b/arch/arm/boot/dts/overlays/spi5-1cs-pi5-overlay.dts new file mode 100644 index 00000000000000..bde1837f26c01f --- /dev/null +++ b/arch/arm/boot/dts/overlays/spi5-1cs-pi5-overlay.dts @@ -0,0 +1,33 @@ +/dts-v1/; +/plugin/; + + +/ { + compatible = "brcm,bcm2712"; + + fragment@0 { + target = <&spi5>; + frag1: __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + cs-gpios = <&gpio 12 1>; + status = "okay"; + + spidev5_0: spidev@0 { + compatible = "spidev"; + reg = <0>; /* CE0 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + status = "okay"; + }; + }; + }; + + __overrides__ { + cs0_pin = <&frag1>,"cs-gpios:4"; + cs0_spidev = <&spidev5_0>,"status"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/spi5-2cs-overlay.dts b/arch/arm/boot/dts/overlays/spi5-2cs-overlay.dts new file mode 100644 index 00000000000000..c2a239a34b35d5 --- /dev/null +++ b/arch/arm/boot/dts/overlays/spi5-2cs-overlay.dts @@ -0,0 +1,54 @@ +/dts-v1/; +/plugin/; + + +/ { + compatible = "brcm,bcm2711"; + + fragment@0 { + target = <&spi5_cs_pins>; + frag0: __overlay__ { + brcm,pins = <12 26>; + brcm,function = <1>; /* output */ + }; + }; + + fragment@1 { + target = <&spi5>; + frag1: __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + cs-gpios = <&gpio 12 1>, <&gpio 26 1>; + status = "okay"; + + spidev5_0: spidev@0 { + compatible = "spidev"; + reg = <0>; /* CE0 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + status = "okay"; + }; + + spidev5_1: spidev@1 { + compatible = "spidev"; + reg = <1>; /* CE1 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + status = "okay"; + }; + }; + }; + + __overrides__ { + cs0_pin = <&frag0>,"brcm,pins:0", + <&frag1>,"cs-gpios:4"; + cs1_pin = <&frag0>,"brcm,pins:4", + <&frag1>,"cs-gpios:16"; + cs0_spidev = <&spidev5_0>,"status"; + cs1_spidev = <&spidev5_1>,"status"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/spi5-2cs-pi5-overlay.dts b/arch/arm/boot/dts/overlays/spi5-2cs-pi5-overlay.dts new file mode 100644 index 00000000000000..2c9eee2a9db8a2 --- /dev/null +++ b/arch/arm/boot/dts/overlays/spi5-2cs-pi5-overlay.dts @@ -0,0 +1,44 @@ +/dts-v1/; +/plugin/; + + +/ { + compatible = "brcm,bcm2712"; + + fragment@0 { + target = <&spi5>; + frag1: __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + cs-gpios = <&gpio 12 1>, <&gpio 26 1>; + status = "okay"; + + spidev5_0: spidev@0 { + compatible = "spidev"; + reg = <0>; /* CE0 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + status = "okay"; + }; + + spidev5_1: spidev@1 { + compatible = "spidev"; + reg = <1>; /* CE1 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + status = "okay"; + }; + }; + }; + + __overrides__ { + cs0_pin = <&frag1>,"cs-gpios:4"; + cs1_pin = <&frag1>,"cs-gpios:16"; + cs0_spidev = <&spidev5_0>,"status"; + cs1_spidev = <&spidev5_1>,"status"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/spi6-1cs-overlay.dts b/arch/arm/boot/dts/overlays/spi6-1cs-overlay.dts new file mode 100644 index 00000000000000..a784f8a17d2304 --- /dev/null +++ b/arch/arm/boot/dts/overlays/spi6-1cs-overlay.dts @@ -0,0 +1,42 @@ +/dts-v1/; +/plugin/; + + +/ { + compatible = "brcm,bcm2711"; + + fragment@0 { + target = <&spi6_cs_pins>; + frag0: __overlay__ { + brcm,pins = <18>; + brcm,function = <1>; /* output */ + }; + }; + + fragment@1 { + target = <&spi6>; + frag1: __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + cs-gpios = <&gpio 18 1>; + status = "okay"; + + spidev6_0: spidev@0 { + compatible = "spidev"; + reg = <0>; /* CE0 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + status = "okay"; + }; + }; + }; + + __overrides__ { + cs0_pin = <&frag0>,"brcm,pins:0", + <&frag1>,"cs-gpios:4"; + cs0_spidev = <&spidev6_0>,"status"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/spi6-2cs-overlay.dts b/arch/arm/boot/dts/overlays/spi6-2cs-overlay.dts new file mode 100644 index 00000000000000..8ef513814d2b64 --- /dev/null +++ b/arch/arm/boot/dts/overlays/spi6-2cs-overlay.dts @@ -0,0 +1,54 @@ +/dts-v1/; +/plugin/; + + +/ { + compatible = "brcm,bcm2711"; + + fragment@0 { + target = <&spi6_cs_pins>; + frag0: __overlay__ { + brcm,pins = <18 27>; + brcm,function = <1>; /* output */ + }; + }; + + fragment@1 { + target = <&spi6>; + frag1: __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + cs-gpios = <&gpio 18 1>, <&gpio 27 1>; + status = "okay"; + + spidev6_0: spidev@0 { + compatible = "spidev"; + reg = <0>; /* CE0 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + status = "okay"; + }; + + spidev6_1: spidev@1 { + compatible = "spidev"; + reg = <1>; /* CE1 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + status = "okay"; + }; + }; + }; + + __overrides__ { + cs0_pin = <&frag0>,"brcm,pins:0", + <&frag1>,"cs-gpios:4"; + cs1_pin = <&frag0>,"brcm,pins:4", + <&frag1>,"cs-gpios:16"; + cs0_spidev = <&spidev6_0>,"status"; + cs1_spidev = <&spidev6_1>,"status"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/ssd1306-overlay.dts b/arch/arm/boot/dts/overlays/ssd1306-overlay.dts new file mode 100644 index 00000000000000..84cf10e489d3c6 --- /dev/null +++ b/arch/arm/boot/dts/overlays/ssd1306-overlay.dts @@ -0,0 +1,36 @@ +// Overlay for SSD1306 128x64 and 128x32 OLED displays +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2c1>; + __overlay__ { + status = "okay"; + + #address-cells = <1>; + #size-cells = <0>; + + ssd1306: oled@3c{ + compatible = "solomon,ssd1306fb-i2c"; + reg = <0x3c>; + solomon,width = <128>; + solomon,height = <64>; + solomon,page-offset = <0>; + }; + }; + }; + + __overrides__ { + address = <&ssd1306>,"reg:0"; + width = <&ssd1306>,"solomon,width:0"; + height = <&ssd1306>,"solomon,height:0"; + offset = <&ssd1306>,"solomon,page-offset:0"; + normal = <&ssd1306>,"solomon,segment-no-remap?"; + sequential = <&ssd1306>,"solomon,com-seq?"; + remapped = <&ssd1306>,"solomon,com-lrremap?"; + inverted = <&ssd1306>,"solomon,com-invdir?"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/ssd1306-spi-overlay.dts b/arch/arm/boot/dts/overlays/ssd1306-spi-overlay.dts new file mode 100644 index 00000000000000..679749fc3065c2 --- /dev/null +++ b/arch/arm/boot/dts/overlays/ssd1306-spi-overlay.dts @@ -0,0 +1,85 @@ +/* + * Device Tree overlay for SSD1306 based SPI OLED display + * + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spi0>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@2 { + target = <&spidev1>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@3 { + target = <&gpio>; + __overlay__ { + ssd1306_pins: ssd1306_pins { + brcm,pins = <25 24>; + brcm,function = <1 1>; /* out out */ + }; + }; + }; + + fragment@4 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + ssd1306: ssd1306@0{ + compatible = "solomon,ssd1306"; + reg = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&ssd1306_pins>; + + spi-max-frequency = <10000000>; + bgr = <0>; + bpp = <1>; + rotate = <0>; + fps = <25>; + buswidth = <8>; + reset-gpios = <&gpio 25 1>; + dc-gpios = <&gpio 24 0>; + debug = <0>; + + solomon,height = <64>; + solomon,width = <128>; + solomon,page-offset = <0>; + }; + }; + }; + + __overrides__ { + speed = <&ssd1306>,"spi-max-frequency:0"; + rotate = <&ssd1306>,"rotate:0"; + fps = <&ssd1306>,"fps:0"; + debug = <&ssd1306>,"debug:0"; + dc_pin = <&ssd1306>,"dc-gpios:4", + <&ssd1306_pins>,"brcm,pins:4"; + reset_pin = <&ssd1306>,"reset-gpios:4", + <&ssd1306_pins>,"brcm,pins:0"; + height = <&ssd1306>,"solomon,height:0"; + inverted = <&ssd1306>,"solomon,com-invdir?"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/ssd1327-spi-overlay.dts b/arch/arm/boot/dts/overlays/ssd1327-spi-overlay.dts new file mode 100644 index 00000000000000..c0770738c176c0 --- /dev/null +++ b/arch/arm/boot/dts/overlays/ssd1327-spi-overlay.dts @@ -0,0 +1,70 @@ +/* + * Device Tree overlay for SSD1327 based SPI OLED display + * + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spi0>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@2 { + target = <&spidev1>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@3 { + target = <&gpio>; + __overlay__ { + ssd1327_pins: ssd1327_pins { + brcm,pins = <25 24>; + brcm,function = <1 1>; + }; + }; + }; + + fragment@4 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + ssd1327: ssd1327@0{ + compatible = "solomon,ssd1327"; + reg = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&ssd1327_pins>; + + spi-max-frequency = <4500000>; + reset-gpios = <&gpio 25 1>; + dc-gpios = <&gpio 24 0>; + }; + }; + }; + + __overrides__ { + speed = <&ssd1327>,"spi-max-frequency:0"; + dc_pin = <&ssd1327>,"dc-gpios:4", + <&ssd1327_pins>,"brcm,pins:4"; + reset_pin = <&ssd1327>,"reset-gpios:4", + <&ssd1327_pins>,"brcm,pins:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/ssd1331-spi-overlay.dts b/arch/arm/boot/dts/overlays/ssd1331-spi-overlay.dts new file mode 100644 index 00000000000000..9fd5ebf2fedae4 --- /dev/null +++ b/arch/arm/boot/dts/overlays/ssd1331-spi-overlay.dts @@ -0,0 +1,83 @@ +/* + * Device Tree overlay for SSD1331 based SPI OLED display + * + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spi0>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@2 { + target = <&spidev1>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@3 { + target = <&gpio>; + __overlay__ { + ssd1331_pins: ssd1331_pins { + brcm,pins = <25 24>; + brcm,function = <1 1>; /* out out */ + }; + }; + }; + + fragment@4 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + ssd1331: ssd1331@0{ + compatible = "solomon,ssd1331"; + reg = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&ssd1331_pins>; + + spi-max-frequency = <4500000>; + bgr = <0>; + bpp = <16>; + rotate = <0>; + fps = <25>; + buswidth = <8>; + reset-gpios = <&gpio 25 1>; + dc-gpios = <&gpio 24 0>; + debug = <0>; + + solomon,height = <64>; + solomon,width = <96>; + solomon,page-offset = <0>; + }; + }; + }; + + __overrides__ { + speed = <&ssd1331>,"spi-max-frequency:0"; + rotate = <&ssd1331>,"rotate:0"; + fps = <&ssd1331>,"fps:0"; + debug = <&ssd1331>,"debug:0"; + dc_pin = <&ssd1331>,"dc-gpios:4", + <&ssd1331_pins>,"brcm,pins:4"; + reset_pin = <&ssd1331>,"reset-gpios:4", + <&ssd1331_pins>,"brcm,pins:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/ssd1351-spi-overlay.dts b/arch/arm/boot/dts/overlays/ssd1351-spi-overlay.dts new file mode 100644 index 00000000000000..ffc872c60648fc --- /dev/null +++ b/arch/arm/boot/dts/overlays/ssd1351-spi-overlay.dts @@ -0,0 +1,83 @@ +/* + * Device Tree overlay for SSD1351 based SPI OLED display + * + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spi0>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@2 { + target = <&spidev1>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@3 { + target = <&gpio>; + __overlay__ { + ssd1351_pins: ssd1351_pins { + brcm,pins = <25 24>; + brcm,function = <1 1>; /* out out */ + }; + }; + }; + + fragment@4 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + ssd1351: ssd1351@0{ + compatible = "solomon,ssd1351"; + reg = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&ssd1351_pins>; + + spi-max-frequency = <4500000>; + bgr = <0>; + bpp = <16>; + rotate = <0>; + fps = <25>; + buswidth = <8>; + reset-gpios = <&gpio 25 1>; + dc-gpios = <&gpio 24 0>; + debug = <0>; + + solomon,height = <128>; + solomon,width = <128>; + solomon,page-offset = <0>; + }; + }; + }; + + __overrides__ { + speed = <&ssd1351>,"spi-max-frequency:0"; + rotate = <&ssd1351>,"rotate:0"; + fps = <&ssd1351>,"fps:0"; + debug = <&ssd1351>,"debug:0"; + dc_pin = <&ssd1351>,"dc-gpios:4", + <&ssd1351_pins>,"brcm,pins:4"; + reset_pin = <&ssd1351>,"reset-gpios:4", + <&ssd1351_pins>,"brcm,pins:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/sunfounder-pipower3-overlay.dts b/arch/arm/boot/dts/overlays/sunfounder-pipower3-overlay.dts new file mode 100644 index 00000000000000..cd5e8e68f20de7 --- /dev/null +++ b/arch/arm/boot/dts/overlays/sunfounder-pipower3-overlay.dts @@ -0,0 +1,44 @@ +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target-path = "/chosen"; + __overlay__ { + power: power { + hat_current_supply = <5000>; + }; + }; + }; + fragment@1 { + target = <&i2c1>; + __overlay__ { + status = "okay"; + }; + }; + fragment@2 { + target-path = "/"; + __overlay__ { + power_ctrl: power_ctrl { + compatible = "gpio-poweroff"; + gpios = <&gpio 26 0>; + force; + }; + }; + }; + fragment@3 { + target = <&gpio>; + __overlay__ { + power_ctrl_pins: power_ctrl_pins { + brcm,pins = <26>; + brcm,function = <1>; // out + }; + }; + }; + __overrides__ { + poweroff_pin = <&power_ctrl>,"gpios:4", + <&power_ctrl_pins>,"brcm,pins:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/sunfounder-pironman5-overlay.dts b/arch/arm/boot/dts/overlays/sunfounder-pironman5-overlay.dts new file mode 100644 index 00000000000000..fad68ef1813f4c --- /dev/null +++ b/arch/arm/boot/dts/overlays/sunfounder-pironman5-overlay.dts @@ -0,0 +1,55 @@ +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2c1>; + __overlay__ { + status = "okay"; + }; + }; + fragment@1 { + target = <&spi0>; + __overlay__ { + status = "okay"; + }; + }; + fragment@2 { + target-path = "/"; + __overlay__ { + gpio_ir: ir-receiver@d { + compatible = "gpio-ir-receiver"; + pinctrl-names = "default"; + pinctrl-0 = <&gpio_ir_pins>; + + // pin number, high or low + gpios = <&gpio 13 1>; + + // parameter for keymap name + linux,rc-map-name = "rc-rc6-mce"; + + status = "okay"; + }; + }; + }; + fragment@3 { + target = <&gpio>; + __overlay__ { + gpio_ir_pins: gpio_ir_pins@d { + brcm,pins = <13>; + brcm,function = <0>; + brcm,pull = <2>; + }; + }; + }; + __overrides__ { + ir = <&gpio_ir>,"status"; + ir_pins = + <&gpio_ir>,"gpios:4", + <&gpio_ir>,"reg:0", + <&gpio_ir_pins>,"brcm,pins:0", + <&gpio_ir_pins>,"reg:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/superaudioboard-overlay.dts b/arch/arm/boot/dts/overlays/superaudioboard-overlay.dts new file mode 100755 index 00000000000000..1006d5fe9e066f --- /dev/null +++ b/arch/arm/boot/dts/overlays/superaudioboard-overlay.dts @@ -0,0 +1,73 @@ +// Definitions for SuperAudioBoard +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&sound>; + __overlay__ { + compatible = "simple-audio-card"; + i2s-controller = <&i2s_clk_consumer>; + status = "okay"; + + simple-audio-card,name = "SuperAudioBoard"; + + simple-audio-card,widgets = + "Line", "Line In", + "Line", "Line Out"; + + simple-audio-card,routing = + "Line Out","AOUTA+", + "Line Out","AOUTA-", + "Line Out","AOUTB+", + "Line Out","AOUTB-", + "AINA","Line In", + "AINB","Line In"; + + simple-audio-card,format = "i2s"; + + simple-audio-card,bitclock-master = <&sound_master>; + simple-audio-card,frame-master = <&sound_master>; + + simple-audio-card,cpu { + sound-dai = <&i2s_clk_consumer>; + dai-tdm-slot-num = <2>; + dai-tdm-slot-width = <32>; + }; + + sound_master: simple-audio-card,codec { + sound-dai = <&cs4271>; + system-clock-frequency = <24576000>; + }; + }; + }; + + fragment@1 { + target = <&i2s_clk_consumer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@2 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + cs4271: cs4271@10 { + #sound-dai-cells = <0>; + compatible = "cirrus,cs4271"; + reg = <0x10>; + status = "okay"; + reset-gpio = <&gpio 26 0>; /* Pin 26, active high */ + }; + }; + }; + __overrides__ { + gpiopin = <&cs4271>,"reset-gpio:4"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/sx150x-overlay.dts b/arch/arm/boot/dts/overlays/sx150x-overlay.dts new file mode 100644 index 00000000000000..1d1069345da21e --- /dev/null +++ b/arch/arm/boot/dts/overlays/sx150x-overlay.dts @@ -0,0 +1,1706 @@ +// Definitions for SX150x I2C GPIO Expanders from Semtech + +// dtparams: +// sx150<x>-<n>-<m> - Enables SX150X device on I2C#<n> with slave address <m>. <x> may be 1-9. +// <n> may be 0 or 1. Permissible values of <m> (which is denoted in hex) +// depend on the device variant. +// For SX1501, SX1502, SX1504 and SX1505, <m> may be 20 or 21. +// For SX1503 and SX1506, <m> may be 20. +// For SX1507 and SX1509, <m> may be 3E, 3F, 70 or 71. +// For SX1508, <m> may be 20, 21, 22 or 23. +// sx150<x>-<n>-<m>-int-gpio - Integer, enables interrupts on SX150X device on I2C#<n> with slave address <m>, +// specifies the GPIO pin to which NINT output of SX150X is connected. +// +// +// Example 1: A single SX1505 device on I2C#1 with its slave address set to 0x20 and NINT output connected to GPIO25: +// dtoverlay=sx150x:sx1505-1-20,sx1505-1-20-int-gpio=25 +// +// Example 2: Two SX1507 devices on I2C#0 with their slave addresses set to 0x3E and 0x70 (interrupts not used): +// dtoverlay=sx150x:sx1507-0-3E,sx1507-0-70 + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + // Enable I2C#0 interface + fragment@0 { + target = <&i2c0>; + __dormant__ { + status = "okay"; + }; + }; + + // Enable I2C#1 interface + fragment@1 { + target = <&i2c1>; + __dormant__ { + status = "okay"; + }; + }; + + // Enable a SX1501 on I2C#0 at slave addr 0x20 + fragment@2 { + target = <&i2c0>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sx1501_0_20: sx150x@20 { + compatible = "semtech,sx1501q"; + reg = <0x20>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupts = <25 2>; /* 1st word overwritten by sx1501-0-20-int-gpio parameter + 2nd word is 2 for falling-edge triggered */ + status = "okay"; + }; + }; + }; + + // Enable a SX1501 on I2C#1 at slave addr 0x20 + fragment@3 { + target = <&i2c1>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sx1501_1_20: sx150x@20 { + compatible = "semtech,sx1501q"; + reg = <0x20>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupts = <25 2>; /* 1st word overwritten by sx1501-1-20-int-gpio parameter + 2nd word is 2 for falling-edge triggered */ + status = "okay"; + }; + }; + }; + + // Enable a SX1501 on I2C#0 at slave addr 0x21 + fragment@4 { + target = <&i2c0>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sx1501_0_21: sx150x@21 { + compatible = "semtech,sx1501q"; + reg = <0x21>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupts = <25 2>; /* 1st word overwritten by sx1501-0-21-int-gpio parameter + 2nd word is 2 for falling-edge triggered */ + status = "okay"; + }; + }; + }; + + // Enable a SX1501 on I2C#1 at slave addr 0x21 + fragment@5 { + target = <&i2c1>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sx1501_1_21: sx150x@21 { + compatible = "semtech,sx1501q"; + reg = <0x21>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupts = <25 2>; /* 1st word overwritten by sx1501-1-21-int-gpio parameter + 2nd word is 2 for falling-edge triggered */ + status = "okay"; + }; + }; + }; + + // Enable a SX1502 on I2C#0 at slave addr 0x20 + fragment@6 { + target = <&i2c0>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sx1502_0_20: sx150x@20 { + compatible = "semtech,sx1502q"; + reg = <0x20>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupts = <25 2>; /* 1st word overwritten by sx1502-0-20-int-gpio parameter + 2nd word is 2 for falling-edge triggered */ + status = "okay"; + }; + }; + }; + + // Enable a SX1502 on I2C#1 at slave addr 0x20 + fragment@7 { + target = <&i2c1>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sx1502_1_20: sx150x@20 { + compatible = "semtech,sx1502q"; + reg = <0x20>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupts = <25 2>; /* 1st word overwritten by sx1502-1-20-int-gpio parameter + 2nd word is 2 for falling-edge triggered */ + status = "okay"; + }; + }; + }; + + // Enable a SX1502 on I2C#0 at slave addr 0x21 + fragment@8 { + target = <&i2c0>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sx1502_0_21: sx150x@21 { + compatible = "semtech,sx1502q"; + reg = <0x21>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupts = <25 2>; /* 1st word overwritten by sx1502-0-21-int-gpio parameter + 2nd word is 2 for falling-edge triggered */ + status = "okay"; + }; + }; + }; + + // Enable a SX1502 on I2C#1 at slave addr 0x21 + fragment@9 { + target = <&i2c1>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sx1502_1_21: sx150x@21 { + compatible = "semtech,sx1502q"; + reg = <0x21>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupts = <25 2>; /* 1st word overwritten by sx1501-1-21-int-gpio parameter + 2nd word is 2 for falling-edge triggered */ + status = "okay"; + }; + }; + }; + + // Enable a SX1503 on I2C#0 at slave addr 0x20 + fragment@10 { + target = <&i2c0>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sx1503_0_20: sx150x@20 { + compatible = "semtech,sx1503q"; + reg = <0x20>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupts = <25 2>; /* 1st word overwritten by sx1503-0-20-int-gpio parameter + 2nd word is 2 for falling-edge triggered */ + status = "okay"; + }; + }; + }; + + // Enable a SX1503 on I2C#1 at slave addr 0x20 + fragment@11 { + target = <&i2c1>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sx1503_1_20: sx150x@20 { + compatible = "semtech,sx1503q"; + reg = <0x20>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupts = <25 2>; /* 1st word overwritten by sx1503-1-20-int-gpio parameter + 2nd word is 2 for falling-edge triggered */ + status = "okay"; + }; + }; + }; + + // Enable a SX1504 on I2C#0 at slave addr 0x20 + fragment@12 { + target = <&i2c0>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sx1504_0_20: sx150x@20 { + compatible = "semtech,sx1504q"; + reg = <0x20>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupts = <25 2>; /* 1st word overwritten by sx1504-0-20-int-gpio parameter + 2nd word is 2 for falling-edge triggered */ + status = "okay"; + }; + }; + }; + + // Enable a SX1504 on I2C#1 at slave addr 0x20 + fragment@13 { + target = <&i2c1>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sx1504_1_20: sx150x@20 { + compatible = "semtech,sx1504q"; + reg = <0x20>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupts = <25 2>; /* 1st word overwritten by sx1504-1-20-int-gpio parameter + 2nd word is 2 for falling-edge triggered */ + status = "okay"; + }; + }; + }; + + // Enable a SX1504 on I2C#0 at slave addr 0x21 + fragment@14 { + target = <&i2c0>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sx1504_0_21: sx150x@21 { + compatible = "semtech,sx1504q"; + reg = <0x21>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupts = <25 2>; /* 1st word overwritten by sx1504-0-21-int-gpio parameter + 2nd word is 2 for falling-edge triggered */ + status = "okay"; + }; + }; + }; + + // Enable a SX1504 on I2C#1 at slave addr 0x21 + fragment@15 { + target = <&i2c1>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sx1504_1_21: sx150x@21 { + compatible = "semtech,sx1504q"; + reg = <0x21>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupts = <25 2>; /* 1st word overwritten by sx1504-1-20-int-gpio parameter + 2nd word is 2 for falling-edge triggered */ + status = "okay"; + }; + }; + }; + + // Enable a SX1505 on I2C#0 at slave addr 0x20 + fragment@16 { + target = <&i2c0>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sx1505_0_20: sx150x@20 { + compatible = "semtech,sx1505q"; + reg = <0x20>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupts = <25 2>; /* 1st word overwritten by sx1505-0-20-int-gpio parameter + 2nd word is 2 for falling-edge triggered */ + status = "okay"; + }; + }; + }; + + // Enable a SX1505 on I2C#1 at slave addr 0x20 + fragment@17 { + target = <&i2c1>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sx1505_1_20: sx150x@20 { + compatible = "semtech,sx1505q"; + reg = <0x20>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupts = <25 2>; /* 1st word overwritten by sx1505-1-20-int-gpio parameter + 2nd word is 2 for falling-edge triggered */ + status = "okay"; + }; + }; + }; + + // Enable a SX1505 on I2C#0 at slave addr 0x21 + fragment@18 { + target = <&i2c0>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sx1505_0_21: sx150x@21 { + compatible = "semtech,sx1505q"; + reg = <0x21>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupts = <25 2>; /* 1st word overwritten by sx1505-0-21-int-gpio parameter + 2nd word is 2 for falling-edge triggered */ + status = "okay"; + }; + }; + }; + + // Enable a SX1505 on I2C#1 at slave addr 0x21 + fragment@19 { + target = <&i2c1>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sx1505_1_21: sx150x@21 { + compatible = "semtech,sx1505q"; + reg = <0x21>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupts = <25 2>; /* 1st word overwritten by sx1505-1-21-int-gpio parameter + 2nd word is 2 for falling-edge triggered */ + status = "okay"; + }; + }; + }; + + // Enable a SX1506 on I2C#0 at slave addr 0x20 + fragment@20 { + target = <&i2c0>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sx1506_0_20: sx150x@20 { + compatible = "semtech,sx1506q"; + reg = <0x20>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupts = <25 2>; /* 1st word overwritten by sx1506-0-20-int-gpio parameter + 2nd word is 2 for falling-edge triggered */ + status = "okay"; + }; + }; + }; + + // Enable a SX1506 on I2C#1 at slave addr 0x20 + fragment@21 { + target = <&i2c1>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sx1506_1_20: sx150x@20 { + compatible = "semtech,sx1506q"; + reg = <0x20>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupts = <25 2>; /* 1st word overwritten by sx1506-1-20-int-gpio parameter + 2nd word is 2 for falling-edge triggered */ + status = "okay"; + }; + }; + }; + + // Enable a SX1507 on I2C#0 at slave addr 0x3E + fragment@22 { + target = <&i2c0>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sx1507_0_3E: sx150x@3E { + compatible = "semtech,sx1507q"; + reg = <0x3E>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupts = <25 2>; /* 1st word overwritten by sx1507_0_3E-int-gpio parameter + 2nd word is 2 for falling-edge triggered */ + status = "okay"; + }; + }; + }; + + // Enable a SX1507 on I2C#1 at slave addr 0x3E + fragment@23 { + target = <&i2c1>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sx1507_1_3E: sx150x@3E { + compatible = "semtech,sx1507q"; + reg = <0x3E>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupts = <25 2>; /* 1st word overwritten by sx1507_1_3E-int-gpio parameter + 2nd word is 2 for falling-edge triggered */ + status = "okay"; + }; + }; + }; + + // Enable a SX1507 on I2C#0 at slave addr 0x3F + fragment@24 { + target = <&i2c0>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sx1507_0_3F: sx150x@3F { + compatible = "semtech,sx1507q"; + reg = <0x3F>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupts = <25 2>; /* 1st word overwritten by sx1507_0_3F-int-gpio parameter + 2nd word is 2 for falling-edge triggered */ + status = "okay"; + }; + }; + }; + + // Enable a SX1507 on I2C#1 at slave addr 0x3F + fragment@25 { + target = <&i2c1>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sx1507_1_3F: sx150x@3F { + compatible = "semtech,sx1507q"; + reg = <0x3F>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupts = <25 2>; /* 1st word overwritten by sx1507_1_3F-int-gpio parameter + 2nd word is 2 for falling-edge triggered */ + status = "okay"; + }; + }; + }; + + // Enable a SX1507 on I2C#0 at slave addr 0x70 + fragment@26 { + target = <&i2c0>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sx1507_0_70: sx150x@70 { + compatible = "semtech,sx1507q"; + reg = <0x70>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupts = <25 2>; /* 1st word overwritten by sx1507-0-70-int-gpio parameter + 2nd word is 2 for falling-edge triggered */ + status = "okay"; + }; + }; + }; + + // Enable a SX1507 on I2C#1 at slave addr 0x70 + fragment@27 { + target = <&i2c1>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sx1507_1_70: sx150x@70 { + compatible = "semtech,sx1507q"; + reg = <0x70>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupts = <25 2>; /* 1st word overwritten by sx1507-1-70-int-gpio parameter + 2nd word is 2 for falling-edge triggered */ + status = "okay"; + }; + }; + }; + + // Enable a SX1507 on I2C#0 at slave addr 0x71 + fragment@28 { + target = <&i2c0>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sx1507_0_71: sx150x@71 { + compatible = "semtech,sx1507q"; + reg = <0x71>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupts = <25 2>; /* 1st word overwritten by sx1507-0-71-int-gpio parameter + 2nd word is 2 for falling-edge triggered */ + status = "okay"; + }; + }; + }; + + // Enable a SX1507 on I2C#1 at slave addr 0x71 + fragment@29 { + target = <&i2c1>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sx1507_1_71: sx150x@71 { + compatible = "semtech,sx1507q"; + reg = <0x71>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupts = <25 2>; /* 1st word overwritten by sx1507-1-71-int-gpio parameter + 2nd word is 2 for falling-edge triggered */ + status = "okay"; + }; + }; + }; + + // Enable a SX1508 on I2C#0 at slave addr 0x20 + fragment@30 { + target = <&i2c0>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sx1508_0_20: sx150x@20 { + compatible = "semtech,sx1508q"; + reg = <0x20>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupts = <25 2>; /* 1st word overwritten by sx1508-0-20-int-gpio parameter + 2nd word is 2 for falling-edge triggered */ + status = "okay"; + }; + }; + }; + + // Enable a SX1508 on I2C#1 at slave addr 0x20 + fragment@31 { + target = <&i2c1>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sx1508_1_20: sx150x@20 { + compatible = "semtech,sx1508q"; + reg = <0x20>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupts = <25 2>; /* 1st word overwritten by sx1508-1-20-int-gpio parameter + 2nd word is 2 for falling-edge triggered */ + status = "okay"; + }; + }; + }; + + // Enable a SX1508 on I2C#0 at slave addr 0x21 + fragment@32 { + target = <&i2c0>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sx1508_0_21: sx150x@21 { + compatible = "semtech,sx1508q"; + reg = <0x21>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupts = <25 2>; /* 1st word overwritten by sx1508-0-21-int-gpio parameter + 2nd word is 2 for falling-edge triggered */ + status = "okay"; + }; + }; + }; + + // Enable a SX1508 on I2C#1 at slave addr 0x21 + fragment@33 { + target = <&i2c1>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sx1508_1_21: sx150x@21 { + compatible = "semtech,sx1508q"; + reg = <0x21>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupts = <25 2>; /* 1st word overwritten by sx1508-1-21-int-gpio parameter + 2nd word is 2 for falling-edge triggered */ + status = "okay"; + }; + }; + }; + + // Enable a SX1508 on I2C#0 at slave addr 0x22 + fragment@34 { + target = <&i2c0>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sx1508_0_22: sx150x@22 { + compatible = "semtech,sx1508q"; + reg = <0x22>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupts = <25 2>; /* 1st word overwritten by sx1508-0-22-int-gpio parameter + 2nd word is 2 for falling-edge triggered */ + status = "okay"; + }; + }; + }; + + // Enable a SX1508 on I2C#1 at slave addr 0x22 + fragment@35 { + target = <&i2c1>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sx1508_1_22: sx150x@22 { + compatible = "semtech,sx1508q"; + reg = <0x22>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupts = <25 2>; /* 1st word overwritten by sx1508-1-22-int-gpio parameter + 2nd word is 2 for falling-edge triggered */ + status = "okay"; + }; + }; + }; + + // Enable a SX1508 on I2C#0 at slave addr 0x23 + fragment@36 { + target = <&i2c0>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sx1508_0_23: sx150x@23 { + compatible = "semtech,sx1508q"; + reg = <0x23>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupts = <25 2>; /* 1st word overwritten by sx1508-0-23-int-gpio parameter + 2nd word is 2 for falling-edge triggered */ + status = "okay"; + }; + }; + }; + + // Enable a SX1508 on I2C#1 at slave addr 0x23 + fragment@37 { + target = <&i2c1>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sx1508_1_23: sx150x@23 { + compatible = "semtech,sx1508q"; + reg = <0x23>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupts = <25 2>; /* 1st word overwritten by sx1508-1-23-int-gpio parameter + 2nd word is 2 for falling-edge triggered */ + status = "okay"; + }; + }; + }; + + // Enable a SX1509 on I2C#0 at slave addr 0x3E + fragment@38 { + target = <&i2c0>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sx1509_0_3E: sx150x@3E { + compatible = "semtech,sx1509q"; + reg = <0x3E>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupts = <25 2>; /* 1st word overwritten by sx1509_0_3E-int-gpio parameter + 2nd word is 2 for falling-edge triggered */ + status = "okay"; + }; + }; + }; + + // Enable a SX1509 on I2C#1 at slave addr 0x3E + fragment@39 { + target = <&i2c1>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sx1509_1_3E: sx150x@3E { + compatible = "semtech,sx1509q"; + reg = <0x3E>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupts = <25 2>; /* 1st word overwritten by sx1509_1_3E-int-gpio parameter + 2nd word is 2 for falling-edge triggered */ + status = "okay"; + }; + }; + }; + + // Enable a SX1509 on I2C#0 at slave addr 0x3F + fragment@40 { + target = <&i2c0>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sx1509_0_3F: sx150x@3F { + compatible = "semtech,sx1509q"; + reg = <0x3F>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupts = <25 2>; /* 1st word overwritten by sx1509_0_3F-int-gpio parameter + 2nd word is 2 for falling-edge triggered */ + status = "okay"; + }; + }; + }; + + // Enable a SX1509 on I2C#1 at slave addr 0x3F + fragment@41 { + target = <&i2c1>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sx1509_1_3F: sx150x@3F { + compatible = "semtech,sx1509q"; + reg = <0x3F>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupts = <25 2>; /* 1st word overwritten by sx1509_1_3F-int-gpio parameter + 2nd word is 2 for falling-edge triggered */ + status = "okay"; + }; + }; + }; + + // Enable a SX1509 on I2C#0 at slave addr 0x70 + fragment@42 { + target = <&i2c0>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sx1509_0_70: sx150x@70 { + compatible = "semtech,sx1509q"; + reg = <0x70>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupts = <25 2>; /* 1st word overwritten by sx1509-0-70-int-gpio parameter + 2nd word is 2 for falling-edge triggered */ + status = "okay"; + }; + }; + }; + + // Enable a SX1509 on I2C#1 at slave addr 0x70 + fragment@43 { + target = <&i2c1>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sx1509_1_70: sx150x@70 { + compatible = "semtech,sx1509q"; + reg = <0x70>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupts = <25 2>; /* 1st word overwritten by sx1509-1-70-int-gpio parameter + 2nd word is 2 for falling-edge triggered */ + status = "okay"; + }; + }; + }; + + // Enable a SX1509 on I2C#0 at slave addr 0x71 + fragment@44 { + target = <&i2c0>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sx1509_0_71: sx150x@71 { + compatible = "semtech,sx1509q"; + reg = <0x71>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupts = <25 2>; /* 1st word overwritten by sx1509-0-71-int-gpio parameter + 2nd word is 2 for falling-edge triggered */ + status = "okay"; + }; + }; + }; + + // Enable a SX1509 on I2C#1 at slave addr 0x71 + fragment@45 { + target = <&i2c1>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + sx1509_1_71: sx150x@71 { + compatible = "semtech,sx1509q"; + reg = <0x71>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupts = <25 2>; /* 1st word overwritten by sx1509-1-71-int-gpio parameter + 2nd word is 2 for falling-edge triggered */ + status = "okay"; + }; + }; + }; + + // Enable interrupts for a SX1501 on I2C#0 at slave addr 0x20 + fragment@46 { + target = <&sx1501_0_20>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&sx150x_0_20_pins>; + }; + }; + + // Enable interrupts for a SX1501 on I2C#1 at slave addr 0x20 + fragment@47 { + target = <&sx1501_1_20>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&sx150x_1_20_pins>; + }; + }; + + // Enable interrupts for a SX1501 on I2C#0 at slave addr 0x21 + fragment@48 { + target = <&sx1501_0_21>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&sx150x_0_21_pins>; + }; + }; + + // Enable interrupts for a SX1501 on I2C#1 at slave addr 0x21 + fragment@49 { + target = <&sx1501_1_21>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&sx150x_1_21_pins>; + }; + }; + + // Enable interrupts for a SX1502 on I2C#0 at slave addr 0x20 + fragment@50 { + target = <&sx1502_0_20>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&sx150x_0_20_pins>; + }; + }; + + // Enable interrupts for a SX1502 on I2C#1 at slave addr 0x20 + fragment@51 { + target = <&sx1502_1_20>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&sx150x_1_20_pins>; + }; + }; + + // Enable interrupts for a SX1502 on I2C#0 at slave addr 0x21 + fragment@52 { + target = <&sx1502_0_21>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&sx150x_0_21_pins>; + }; + }; + + // Enable interrupts for a SX1502 on I2C#1 at slave addr 0x21 + fragment@53 { + target = <&sx1502_1_21>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&sx150x_1_21_pins>; + }; + }; + + // Enable interrupts for a SX1503 on I2C#0 at slave addr 0x20 + fragment@54 { + target = <&sx1503_0_20>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&sx150x_0_20_pins>; + }; + }; + + // Enable interrupts for a SX1503 on I2C#1 at slave addr 0x20 + fragment@55 { + target = <&sx1503_1_20>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&sx150x_1_20_pins>; + }; + }; + + // Enable interrupts for a SX1504 on I2C#0 at slave addr 0x20 + fragment@56 { + target = <&sx1504_0_20>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&sx150x_0_20_pins>; + }; + }; + + // Enable interrupts for a SX1504 on I2C#1 at slave addr 0x20 + fragment@57 { + target = <&sx1504_1_20>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&sx150x_1_20_pins>; + }; + }; + + // Enable interrupts for a SX1504 on I2C#0 at slave addr 0x21 + fragment@58 { + target = <&sx1504_0_21>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&sx150x_0_21_pins>; + }; + }; + + // Enable interrupts for a SX1504 on I2C#1 at slave addr 0x21 + fragment@59 { + target = <&sx1504_1_21>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&sx150x_1_21_pins>; + }; + }; + + // Enable interrupts for a SX1505 on I2C#0 at slave addr 0x20 + fragment@60 { + target = <&sx1505_0_20>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&sx150x_0_20_pins>; + }; + }; + + // Enable interrupts for a SX1505 on I2C#1 at slave addr 0x20 + fragment@61 { + target = <&sx1505_1_20>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&sx150x_1_20_pins>; + }; + }; + + // Enable interrupts for a SX1505 on I2C#0 at slave addr 0x21 + fragment@62 { + target = <&sx1505_0_21>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&sx150x_0_21_pins>; + }; + }; + + // Enable interrupts for a SX1505 on I2C#1 at slave addr 0x21 + fragment@63 { + target = <&sx1505_1_21>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&sx150x_1_21_pins>; + }; + }; + + // Enable interrupts for a SX1506 on I2C#0 at slave addr 0x20 + fragment@64 { + target = <&sx1506_0_20>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&sx150x_0_20_pins>; + }; + }; + + // Enable interrupts for a SX1506 on I2C#1 at slave addr 0x20 + fragment@65 { + target = <&sx1506_1_20>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&sx150x_1_20_pins>; + }; + }; + + // Enable interrupts for a SX1507 on I2C#0 at slave addr 0x3E + fragment@66 { + target = <&sx1507_0_3E>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&sx150x_0_3E_pins>; + }; + }; + + // Enable interrupts for a SX1507 on I2C#1 at slave addr 0x3E + fragment@67 { + target = <&sx1507_1_3E>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&sx150x_1_3E_pins>; + }; + }; + + // Enable interrupts for a SX1507 on I2C#0 at slave addr 0x3F + fragment@68 { + target = <&sx1507_0_3F>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&sx150x_0_3F_pins>; + }; + }; + + // Enable interrupts for a SX1507 on I2C#1 at slave addr 0x3F + fragment@69 { + target = <&sx1507_1_3F>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&sx150x_1_3F_pins>; + }; + }; + + // Enable interrupts for a SX1507 on I2C#0 at slave addr 0x70 + fragment@70 { + target = <&sx1507_0_70>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&sx150x_1_70_pins>; + }; + }; + + // Enable interrupts for a SX1507 on I2C#1 at slave addr 0x70 + fragment@71 { + target = <&sx1507_1_70>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&sx150x_1_70_pins>; + }; + }; + + // Enable interrupts for a SX1507 on I2C#0 at slave addr 0x71 + fragment@72 { + target = <&sx1507_0_71>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&sx150x_0_71_pins>; + }; + }; + + // Enable interrupts for a SX1507 on I2C#1 at slave addr 0x71 + fragment@73 { + target = <&sx1507_1_71>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&sx150x_1_71_pins>; + }; + }; + + // Enable interrupts for a SX1508 on I2C#0 at slave addr 0x20 + fragment@74 { + target = <&sx1508_0_20>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&sx150x_0_20_pins>; + }; + }; + + // Enable interrupts for a SX1508 on I2C#1 at slave addr 0x20 + fragment@75 { + target = <&sx1508_1_20>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&sx150x_1_20_pins>; + }; + }; + + // Enable interrupts for a SX1508 on I2C#0 at slave addr 0x21 + fragment@76 { + target = <&sx1508_0_21>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&sx150x_0_21_pins>; + }; + }; + + // Enable interrupts for a SX1508 on I2C#1 at slave addr 0x21 + fragment@77 { + target = <&sx1508_1_21>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&sx150x_1_21_pins>; + }; + }; + + // Enable interrupts for a SX1508 on I2C#0 at slave addr 0x22 + fragment@78 { + target = <&sx1508_0_22>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&sx150x_0_22_pins>; + }; + }; + + // Enable interrupts for a SX1508 on I2C#1 at slave addr 0x22 + fragment@79 { + target = <&sx1508_1_22>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&sx150x_1_22_pins>; + }; + }; + + // Enable interrupts for a SX1508 on I2C#0 at slave addr 0x23 + fragment@80 { + target = <&sx1508_0_23>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&sx150x_0_23_pins>; + }; + }; + + // Enable interrupts for a SX1508 on I2C#1 at slave addr 0x23 + fragment@81 { + target = <&sx1508_1_23>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&sx150x_1_23_pins>; + }; + }; + + // Enable interrupts for a SX1509 on I2C#0 at slave addr 0x3E + fragment@82 { + target = <&sx1509_0_3E>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&sx150x_0_3E_pins>; + }; + }; + + // Enable interrupts for a SX1509 on I2C#1 at slave addr 0x3E + fragment@83 { + target = <&sx1509_1_3E>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&sx150x_1_3E_pins>; + }; + }; + + // Enable interrupts for a SX1509 on I2C#0 at slave addr 0x3F + fragment@84 { + target = <&sx1509_0_3F>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&sx150x_0_3F_pins>; + }; + }; + + // Enable interrupts for a SX1509 on I2C#1 at slave addr 0x3F + fragment@85 { + target = <&sx1509_1_3F>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&sx150x_1_3F_pins>; + }; + }; + + // Enable interrupts for a SX1509 on I2C#0 at slave addr 0x70 + fragment@86 { + target = <&sx1509_0_70>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&sx150x_0_70_pins>; + }; + }; + + // Enable interrupts for a SX1509 on I2C#1 at slave addr 0x70 + fragment@87 { + target = <&sx1509_1_70>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&sx150x_1_70_pins>; + }; + }; + + // Enable interrupts for a SX1509 on I2C#0 at slave addr 0x71 + fragment@88 { + target = <&sx1509_0_71>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&sx150x_0_71_pins>; + }; + }; + + // Enable interrupts for a SX1509 on I2C#1 at slave addr 0x71 + fragment@89 { + target = <&sx1509_1_71>; + __dormant__ { + interrupt-parent = <&gpio>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&sx150x_1_71_pins>; + }; + }; + + // Configure GPIO pin connected to NINT output of a SX150x on I2C#0 interface at slave addr 0x20 + // Configure as a input with no pull-up/down + fragment@90 { + target = <&gpio>; + __dormant__ { + sx150x_0_20_pins: sx150x_0_20_pins { + brcm,pins = <0>; /* overwritten by sx150x-0-20-int-gpio parameter */ + brcm,function = <0>; + brcm,pull = <0>; + }; + }; + }; + + // Configure GPIO pin connected to NINT output of a SX150x on I2C#1 interface at slave addr 0x20 + // Configure as a input with no pull-up/down + fragment@91 { + target = <&gpio>; + __dormant__ { + sx150x_1_20_pins: sx150x_1_20_pins { + brcm,pins = <0>; /* overwritten by sx150x-1-20-int-gpio parameter */ + brcm,function = <0>; + brcm,pull = <0>; + }; + }; + }; + + // Configure GPIO pin connected to NINT output of a SX150x on I2C#0 interface at slave addr 0x21 + // Configure as a input with no pull-up/down + fragment@92 { + target = <&gpio>; + __dormant__ { + sx150x_0_21_pins: sx150x_0_21_pins { + brcm,pins = <0>; /* overwritten by sx150x-0-21-int-gpio parameter */ + brcm,function = <0>; + brcm,pull = <0>; + }; + }; + }; + + // Configure GPIO pin connected to NINT output of a SX150x on I2C#1 interface at slave addr 0x21 + // Configure as a input with no pull-up/down + fragment@93 { + target = <&gpio>; + __dormant__ { + sx150x_1_21_pins: sx150x_1_21_pins { + brcm,pins = <0>; /* overwritten by sx150x-1-21-int-gpio parameter */ + brcm,function = <0>; + brcm,pull = <0>; + }; + }; + }; + + // Configure GPIO pin connected to NINT output of a SX150x on I2C#0 interface at slave addr 0x22 + // Configure as a input with no pull-up/down + fragment@94 { + target = <&gpio>; + __dormant__ { + sx150x_0_22_pins: sx150x_0_22_pins { + brcm,pins = <0>; /* overwritten by sx150x-0-22-int-gpio parameter */ + brcm,function = <0>; + brcm,pull = <0>; + }; + }; + }; + + // Configure GPIO pin connected to NINT output of a SX150x on I2C#1 interface at slave addr 0x22 + // Configure as a input with no pull-up/down + fragment@95 { + target = <&gpio>; + __dormant__ { + sx150x_1_22_pins: sx150x_1_22_pins { + brcm,pins = <0>; /* overwritten by sx150x-1-22-int-gpio parameter */ + brcm,function = <0>; + brcm,pull = <0>; + }; + }; + }; + + // Configure GPIO pin connected to NINT output of a SX150x on I2C#0 interface at slave addr 0x23 + // Configure as a input with no pull-up/down + fragment@96 { + target = <&gpio>; + __dormant__ { + sx150x_0_23_pins: sx150x_0_23_pins { + brcm,pins = <0>; /* overwritten by sx150x-0-23-int-gpio parameter */ + brcm,function = <0>; + brcm,pull = <0>; + }; + }; + }; + + // Configure GPIO pin connected to NINT output of a SX150x on I2C#1 interface at slave addr 0x23 + // Configure as a input with no pull-up/down + fragment@97 { + target = <&gpio>; + __dormant__ { + sx150x_1_23_pins: sx150x_1_23_pins { + brcm,pins = <0>; /* overwritten by sx150x-1-23-int-gpio parameter */ + brcm,function = <0>; + brcm,pull = <0>; + }; + }; + }; + + // Configure GPIO pin connected to NINT output of a SX150x on I2C#0 interface at slave addr 0x3E + // Configure as a input with no pull-up/down + fragment@98 { + target = <&gpio>; + __dormant__ { + sx150x_0_3E_pins: sx150x_0_3E_pins { + brcm,pins = <0>; /* overwritten by sx150x-0-3E-int-gpio parameter */ + brcm,function = <0>; + brcm,pull = <0>; + }; + }; + }; + + // Configure GPIO pin connected to NINT output of a SX150x on I2C#1 interface at slave addr 0x3E + // Configure as a input with no pull-up/down + fragment@99 { + target = <&gpio>; + __dormant__ { + sx150x_1_3E_pins: sx150x_1_3E_pins { + brcm,pins = <0>; /* overwritten by sx150x-1-3E-int-gpio parameter */ + brcm,function = <0>; + brcm,pull = <0>; + }; + }; + }; + + // Configure GPIO pin connected to NINT output of a SX150x on I2C#0 interface at slave addr 0x3F + // Configure as a input with no pull-up/down + fragment@100 { + target = <&gpio>; + __dormant__ { + sx150x_0_3F_pins: sx150x_0_3F_pins { + brcm,pins = <0>; /* overwritten by sx150x-0-3F-int-gpio parameter */ + brcm,function = <0>; + brcm,pull = <0>; + }; + }; + }; + + // Configure GPIO pin connected to NINT output of a SX150x on I2C#1 interface at slave addr 0x3F + // Configure as a input with no pull-up/down + fragment@101 { + target = <&gpio>; + __dormant__ { + sx150x_1_3F_pins: sx150x_1_3F_pins { + brcm,pins = <0>; /* overwritten by sx150x-1-3F-int-gpio parameter */ + brcm,function = <0>; + brcm,pull = <0>; + }; + }; + }; + + // Configure GPIO pin connected to NINT output of a SX150x on I2C#0 interface at slave addr 0x70 + // Configure as a input with no pull-up/down + fragment@102 { + target = <&gpio>; + __dormant__ { + sx150x_0_70_pins: sx150x_0_70_pins { + brcm,pins = <0>; /* overwritten by sx150x-0-70-int-gpio parameter */ + brcm,function = <0>; + brcm,pull = <0>; + }; + }; + }; + + // Configure GPIO pin connected to NINT output of a SX150x on I2C#1 interface at slave addr 0x70 + // Configure as a input with no pull-up/down + fragment@103 { + target = <&gpio>; + __dormant__ { + sx150x_1_70_pins: sx150x_1_70_pins { + brcm,pins = <0>; /* overwritten by sx150x-1-70-int-gpio parameter */ + brcm,function = <0>; + brcm,pull = <0>; + }; + }; + }; + + // Configure GPIO pin connected to NINT output of a SX150x on I2C#0 interface at slave addr 0x71 + // Configure as a input with no pull-up/down + fragment@104 { + target = <&gpio>; + __dormant__ { + sx150x_0_71_pins: sx150x_0_71_pins { + brcm,pins = <0>; /* overwritten by sx150x-0-71-int-gpio parameter */ + brcm,function = <0>; + brcm,pull = <0>; + }; + }; + }; + + // Configure GPIO pin connected to NINT output of a SX150x on I2C#1 interface at slave addr 0x71 + // Configure as a input with no pull-up/down + fragment@105 { + target = <&gpio>; + __dormant__ { + sx150x_1_71_pins: sx150x_1_71_pins { + brcm,pins = <0>; /* overwritten by sx150x-1-71-int-gpio parameter */ + brcm,function = <0>; + brcm,pull = <0>; + }; + }; + }; + + __overrides__ { + sx1501-0-20 = <0>,"+0+2"; + sx1501-1-20 = <0>,"+1+3"; + sx1501-0-21 = <0>,"+0+4"; + sx1501-1-21 = <0>,"+1+5"; + sx1502-0-20 = <0>,"+0+6"; + sx1502-1-20 = <0>,"+1+7"; + sx1502-0-21 = <0>,"+0+8"; + sx1502-1-21 = <0>,"+1+9"; + sx1503-0-20 = <0>,"+0+10"; + sx1503-1-20 = <0>,"+1+11"; + sx1504-0-20 = <0>,"+0+12"; + sx1504-1-20 = <0>,"+1+13"; + sx1504-0-21 = <0>,"+0+14"; + sx1504-1-21 = <0>,"+1+15"; + sx1505-0-20 = <0>,"+0+16"; + sx1505-1-20 = <0>,"+1+17"; + sx1505-0-21 = <0>,"+0+18"; + sx1505-1-21 = <0>,"+1+19"; + sx1506-0-20 = <0>,"+0+20"; + sx1506-1-20 = <0>,"+1+21"; + sx1507-0-3E = <0>,"+0+22"; + sx1507-1-3E = <0>,"+1+23"; + sx1507-0-3F = <0>,"+0+24"; + sx1507-1-3F = <0>,"+1+25"; + sx1507-0-70 = <0>,"+0+26"; + sx1507-1-70 = <0>,"+1+27"; + sx1507-0-71 = <0>,"+0+28"; + sx1507-1-71 = <0>,"+1+29"; + sx1508-0-20 = <0>,"+0+30"; + sx1508-1-20 = <0>,"+1+31"; + sx1508-0-21 = <0>,"+0+32"; + sx1508-1-21 = <0>,"+1+33"; + sx1508-0-22 = <0>,"+0+34"; + sx1508-1-22 = <0>,"+1+35"; + sx1508-0-23 = <0>,"+0+36"; + sx1508-1-23 = <0>,"+1+37"; + sx1509-0-3E = <0>,"+0+38"; + sx1509-1-3E = <0>,"+1+39"; + sx1509-0-3F = <0>,"+0+40"; + sx1509-1-3F = <0>,"+1+41"; + sx1509-0-70 = <0>,"+0+42"; + sx1509-1-70 = <0>,"+1+43"; + sx1509-0-71 = <0>,"+0+44"; + sx1509-1-71 = <0>,"+1+45"; + sx1501-0-20-int-gpio = <0>,"+46+90", <&sx150x_0_20_pins>,"brcm,pins:0", <&sx1501_0_20>,"interrupts:0"; + sx1501-1-20-int-gpio = <0>,"+47+91", <&sx150x_1_20_pins>,"brcm,pins:0", <&sx1501_1_20>,"interrupts:0"; + sx1501-0-21-int-gpio = <0>,"+48+92", <&sx150x_0_21_pins>,"brcm,pins:0", <&sx1501_0_21>,"interrupts:0"; + sx1501-1-21-int-gpio = <0>,"+49+93", <&sx150x_1_21_pins>,"brcm,pins:0", <&sx1501_1_21>,"interrupts:0"; + sx1502-0-20-int-gpio = <0>,"+50+90", <&sx150x_0_20_pins>,"brcm,pins:0", <&sx1502_0_20>,"interrupts:0"; + sx1502-1-20-int-gpio = <0>,"+51+91", <&sx150x_1_20_pins>,"brcm,pins:0", <&sx1502_1_20>,"interrupts:0"; + sx1502-0-21-int-gpio = <0>,"+52+92", <&sx150x_0_21_pins>,"brcm,pins:0", <&sx1502_0_21>,"interrupts:0"; + sx1502-1-21-int-gpio = <0>,"+53+93", <&sx150x_1_21_pins>,"brcm,pins:0", <&sx1502_1_21>,"interrupts:0"; + sx1503-0-20-int-gpio = <0>,"+54+90", <&sx150x_0_20_pins>,"brcm,pins:0", <&sx1503_0_20>,"interrupts:0"; + sx1503-1-20-int-gpio = <0>,"+55+91", <&sx150x_1_20_pins>,"brcm,pins:0", <&sx1503_1_20>,"interrupts:0"; + sx1504-0-20-int-gpio = <0>,"+56+90", <&sx150x_0_20_pins>,"brcm,pins:0", <&sx1504_0_20>,"interrupts:0"; + sx1504-1-20-int-gpio = <0>,"+57+91", <&sx150x_1_20_pins>,"brcm,pins:0", <&sx1504_1_20>,"interrupts:0"; + sx1504-0-21-int-gpio = <0>,"+58+92", <&sx150x_0_21_pins>,"brcm,pins:0", <&sx1504_0_21>,"interrupts:0"; + sx1504-1-21-int-gpio = <0>,"+59+93", <&sx150x_1_21_pins>,"brcm,pins:0", <&sx1504_1_21>,"interrupts:0"; + sx1505-0-20-int-gpio = <0>,"+60+90", <&sx150x_0_20_pins>,"brcm,pins:0", <&sx1505_0_20>,"interrupts:0"; + sx1505-1-20-int-gpio = <0>,"+61+91", <&sx150x_1_20_pins>,"brcm,pins:0", <&sx1505_1_20>,"interrupts:0"; + sx1505-0-21-int-gpio = <0>,"+62+92", <&sx150x_0_21_pins>,"brcm,pins:0", <&sx1505_0_21>,"interrupts:0"; + sx1505-1-21-int-gpio = <0>,"+63+93", <&sx150x_1_21_pins>,"brcm,pins:0", <&sx1505_1_21>,"interrupts:0"; + sx1506-0-20-int-gpio = <0>,"+64+90", <&sx150x_0_20_pins>,"brcm,pins:0", <&sx1506_0_20>,"interrupts:0"; + sx1506-1-20-int-gpio = <0>,"+65+91", <&sx150x_1_20_pins>,"brcm,pins:0", <&sx1506_1_20>,"interrupts:0"; + sx1507-0-3E-int-gpio = <0>,"+66+98", <&sx150x_0_3E_pins>,"brcm,pins:0", <&sx1507_0_3E>,"interrupts:0"; + sx1507-1-3E-int-gpio = <0>,"+67+99", <&sx150x_1_3E_pins>,"brcm,pins:0", <&sx1507_1_3E>,"interrupts:0"; + sx1507-0-3F-int-gpio = <0>,"+68+100", <&sx150x_0_3F_pins>,"brcm,pins:0", <&sx1507_0_3F>,"interrupts:0"; + sx1507-1-3F-int-gpio = <0>,"+69+101", <&sx150x_1_3F_pins>,"brcm,pins:0", <&sx1507_1_3F>,"interrupts:0"; + sx1507-0-70-int-gpio = <0>,"+60+102", <&sx150x_0_70_pins>,"brcm,pins:0", <&sx1507_0_70>,"interrupts:0"; + sx1507-1-70-int-gpio = <0>,"+71+103", <&sx150x_1_70_pins>,"brcm,pins:0", <&sx1507_1_70>,"interrupts:0"; + sx1507-0-71-int-gpio = <0>,"+72+104", <&sx150x_0_71_pins>,"brcm,pins:0", <&sx1507_0_71>,"interrupts:0"; + sx1507-1-71-int-gpio = <0>,"+73+105", <&sx150x_1_71_pins>,"brcm,pins:0", <&sx1507_1_71>,"interrupts:0"; + sx1508-0-20-int-gpio = <0>,"+74+90", <&sx150x_0_20_pins>,"brcm,pins:0", <&sx1508_0_20>,"interrupts:0"; + sx1508-1-20-int-gpio = <0>,"+75+91", <&sx150x_1_20_pins>,"brcm,pins:0", <&sx1508_1_20>,"interrupts:0"; + sx1508-0-21-int-gpio = <0>,"+76+92", <&sx150x_0_21_pins>,"brcm,pins:0", <&sx1508_0_21>,"interrupts:0"; + sx1508-1-21-int-gpio = <0>,"+77+93", <&sx150x_1_21_pins>,"brcm,pins:0", <&sx1508_1_21>,"interrupts:0"; + sx1508-0-22-int-gpio = <0>,"+78+94", <&sx150x_0_22_pins>,"brcm,pins:0", <&sx1508_0_22>,"interrupts:0"; + sx1508-1-22-int-gpio = <0>,"+79+95", <&sx150x_1_22_pins>,"brcm,pins:0", <&sx1508_1_22>,"interrupts:0"; + sx1508-0-23-int-gpio = <0>,"+80+96", <&sx150x_0_23_pins>,"brcm,pins:0", <&sx1508_0_23>,"interrupts:0"; + sx1508-1-23-int-gpio = <0>,"+81+97", <&sx150x_1_23_pins>,"brcm,pins:0", <&sx1508_1_23>,"interrupts:0"; + sx1509-0-3E-int-gpio = <0>,"+82+98", <&sx150x_0_3E_pins>,"brcm,pins:0", <&sx1509_0_3E>,"interrupts:0"; + sx1509-1-3E-int-gpio = <0>,"+83+99", <&sx150x_1_3E_pins>,"brcm,pins:0", <&sx1509_1_3E>,"interrupts:0"; + sx1509-0-3F-int-gpio = <0>,"+84+100", <&sx150x_0_3F_pins>,"brcm,pins:0", <&sx1509_0_3F>,"interrupts:0"; + sx1509-1-3F-int-gpio = <0>,"+85+101", <&sx150x_1_3F_pins>,"brcm,pins:0", <&sx1509_1_3F>,"interrupts:0"; + sx1509-0-70-int-gpio = <0>,"+86+102", <&sx150x_0_70_pins>,"brcm,pins:0", <&sx1509_0_70>,"interrupts:0"; + sx1509-1-70-int-gpio = <0>,"+87+103", <&sx150x_1_70_pins>,"brcm,pins:0", <&sx1509_1_70>,"interrupts:0"; + sx1509-0-71-int-gpio = <0>,"+88+104", <&sx150x_0_71_pins>,"brcm,pins:0", <&sx1509_0_71>,"interrupts:0"; + sx1509-1-71-int-gpio = <0>,"+89+105", <&sx150x_1_71_pins>,"brcm,pins:0", <&sx1509_1_71>,"interrupts:0"; + }; +}; + diff --git a/arch/arm/boot/dts/overlays/tc358743-audio-overlay.dts b/arch/arm/boot/dts/overlays/tc358743-audio-overlay.dts new file mode 100644 index 00000000000000..6bb3dceb0df3b3 --- /dev/null +++ b/arch/arm/boot/dts/overlays/tc358743-audio-overlay.dts @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Definitions to add I2S audio from the Toshiba TC358743 HDMI to CSI2 bridge. +// Requires tc358743 overlay to have been loaded to actually function. +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_consumer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target-path = "/"; + __overlay__ { + tc358743_codec: tc358743-codec { + #sound-dai-cells = <0>; + compatible = "linux,spdif-dir"; + status = "okay"; + }; + }; + }; + + fragment@2 { + target = <&sound>; + sound_overlay: __overlay__ { + compatible = "simple-audio-card"; + simple-audio-card,format = "i2s"; + simple-audio-card,name = "tc358743"; + simple-audio-card,bitclock-master = <&dailink0_master>; + simple-audio-card,frame-master = <&dailink0_master>; + status = "okay"; + + simple-audio-card,cpu { + sound-dai = <&i2s_clk_consumer>; + dai-tdm-slot-num = <2>; + dai-tdm-slot-width = <32>; + }; + dailink0_master: simple-audio-card,codec { + sound-dai = <&tc358743_codec>; + }; + }; + }; + + __overrides__ { + card-name = <&sound_overlay>,"simple-audio-card,name"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/tc358743-overlay.dts b/arch/arm/boot/dts/overlays/tc358743-overlay.dts new file mode 100644 index 00000000000000..44a28ca6eedf55 --- /dev/null +++ b/arch/arm/boot/dts/overlays/tc358743-overlay.dts @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Definitions for Toshiba TC358743 HDMI to CSI2 bridge on VC I2C bus +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2835"; + + i2c_frag: fragment@0 { + target = <&i2c_csi_dsi>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + tc358743: tc358743@f { + compatible = "toshiba,tc358743"; + reg = <0x0f>; + status = "okay"; + + clocks = <&cam1_clk>; + clock-names = "refclk"; + + port { + tc358743_0: endpoint { + remote-endpoint = <&csi1_ep>; + clock-lanes = <0>; + clock-noncontinuous; + link-frequencies = + /bits/ 64 <486000000>; + }; + }; + }; + }; + }; + + csi_frag: fragment@1 { + target = <&csi1>; + csi: __overlay__ { + status = "okay"; + + port { + csi1_ep: endpoint { + remote-endpoint = <&tc358743_0>; + }; + }; + }; + }; + + fragment@2 { + target = <&tc358743_0>; + __overlay__ { + data-lanes = <1 2>; + }; + }; + + fragment@3 { + target = <&tc358743_0>; + __dormant__ { + data-lanes = <1 2 3 4>; + }; + }; + + fragment@4 { + target = <&i2c0if>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@5 { + target = <&i2c0mux>; + __overlay__ { + status = "okay"; + }; + }; + + clk_frag: fragment@6 { + target = <&cam1_clk>; + __overlay__ { + status = "okay"; + clock-frequency = <27000000>; + }; + }; + + fragment@7 { + target = <&csi1_ep>; + __overlay__ { + data-lanes = <1 2>; + }; + }; + + fragment@8 { + target = <&csi1_ep>; + __dormant__ { + data-lanes = <1 2 3 4>; + }; + }; + + fragment@9 { + target = <&csi1>; + __overlay__ { + compatible = "brcm,bcm2835-unicam-legacy"; + }; + }; + + __overrides__ { + 4lane = <0>, "-2+3-7+8"; + link-frequency = <&tc358743_0>,"link-frequencies#0"; + media-controller = <0>,"!9"; + cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>, + <&csi_frag>, "target:0=",<&csi0>, + <&clk_frag>, "target:0=",<&cam0_clk>, + <&tc358743>, "clocks:0=",<&cam0_clk>; + }; +}; diff --git a/arch/arm/boot/dts/overlays/tinylcd35-overlay.dts b/arch/arm/boot/dts/overlays/tinylcd35-overlay.dts new file mode 100644 index 00000000000000..edc5889b6f5f11 --- /dev/null +++ b/arch/arm/boot/dts/overlays/tinylcd35-overlay.dts @@ -0,0 +1,222 @@ +/* + * tinylcd35-overlay.dts + * + * ------------------------------------------------- + * www.tinlylcd.com + * ------------------------------------------------- + * Device---Driver-----BUS GPIO's + * display tinylcd35 spi0.0 25 24 18 + * touch ads7846 spi0.1 5 + * rtc ds1307 i2c1-0068 + * rtc pcf8563 i2c1-0051 + * keypad gpio-keys --------- 17 22 27 23 28 + * + * + * TinyLCD.com 3.5 inch TFT + * + * Version 001 + * 5/3/2015 -- Noralf Trønnes Initial Device tree framework + * 10/3/2015 -- tinylcd@gmail.com added ds1307 support. + * + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spi0>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@2 { + target = <&spidev1>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@3 { + target = <&gpio>; + __overlay__ { + tinylcd35_pins: tinylcd35_pins { + brcm,pins = <25 24 18>; + brcm,function = <1>; /* out */ + }; + tinylcd35_ts_pins: tinylcd35_ts_pins { + brcm,pins = <5>; + brcm,function = <0>; /* in */ + }; + keypad_pins: keypad_pins { + brcm,pins = <4 17 22 23 27>; + brcm,function = <0>; /* in */ + brcm,pull = <1>; /* down */ + }; + }; + }; + + fragment@4 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + tinylcd35: tinylcd35@0{ + compatible = "neosec,tinylcd"; + reg = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&tinylcd35_pins>, + <&tinylcd35_ts_pins>; + + spi-max-frequency = <48000000>; + rotate = <270>; + fps = <20>; + bgr; + buswidth = <8>; + reset-gpios = <&gpio 25 1>; + dc-gpios = <&gpio 24 0>; + led-gpios = <&gpio 18 0>; + debug = <0>; + + init = <0x10000B0 0x80 + 0x10000C0 0x0A 0x0A + 0x10000C1 0x01 0x01 + 0x10000C2 0x33 + 0x10000C5 0x00 0x42 0x80 + 0x10000B1 0xD0 0x11 + 0x10000B4 0x02 + 0x10000B6 0x00 0x22 0x3B + 0x10000B7 0x07 + 0x1000036 0x58 + 0x10000F0 0x36 0xA5 0xD3 + 0x10000E5 0x80 + 0x10000E5 0x01 + 0x10000B3 0x00 + 0x10000E5 0x00 + 0x10000F0 0x36 0xA5 0x53 + 0x10000E0 0x00 0x35 0x33 0x00 0x00 0x00 0x00 0x35 0x33 0x00 0x00 0x00 + 0x100003A 0x55 + 0x1000011 + 0x2000001 + 0x1000029>; + }; + + tinylcd35_ts: tinylcd35_ts@1 { + compatible = "ti,ads7846"; + reg = <1>; + status = "disabled"; + + spi-max-frequency = <2000000>; + interrupts = <5 2>; /* high-to-low edge triggered */ + interrupt-parent = <&gpio>; + pendown-gpio = <&gpio 5 1>; + ti,x-plate-ohms = /bits/ 16 <100>; + ti,pressure-max = /bits/ 16 <255>; + }; + }; + }; + + /* RTC */ + + fragment@5 { + target = <&i2c1>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + status = "okay"; + + pcf8563: pcf8563@51 { + compatible = "nxp,pcf8563"; + reg = <0x51>; + status = "okay"; + }; + }; + }; + + fragment@6 { + target = <&i2c1>; + __dormant__ { + #address-cells = <1>; + #size-cells = <0>; + + status = "okay"; + + ds1307: ds1307@68 { + compatible = "dallas,ds1307"; + reg = <0x68>; + status = "okay"; + }; + }; + }; + + /* + * Values for input event code is found under the + * 'Keys and buttons' heading in include/uapi/linux/input.h + */ + fragment@7 { + target-path = "/soc"; + __overlay__ { + keypad: keypad { + compatible = "gpio-keys"; + pinctrl-names = "default"; + pinctrl-0 = <&keypad_pins>; + status = "disabled"; + autorepeat; + + button@17 { + label = "GPIO KEY_UP"; + linux,code = <103>; + gpios = <&gpio 17 0>; + }; + button@22 { + label = "GPIO KEY_DOWN"; + linux,code = <108>; + gpios = <&gpio 22 0>; + }; + button@27 { + label = "GPIO KEY_LEFT"; + linux,code = <105>; + gpios = <&gpio 27 0>; + }; + button@23 { + label = "GPIO KEY_RIGHT"; + linux,code = <106>; + gpios = <&gpio 23 0>; + }; + button@4 { + label = "GPIO KEY_ENTER"; + linux,code = <28>; + gpios = <&gpio 4 0>; + }; + }; + }; + }; + + __overrides__ { + speed = <&tinylcd35>,"spi-max-frequency:0"; + rotate = <&tinylcd35>,"rotate:0"; + fps = <&tinylcd35>,"fps:0"; + debug = <&tinylcd35>,"debug:0"; + touch = <&tinylcd35_ts>,"status"; + touchgpio = <&tinylcd35_ts_pins>,"brcm,pins:0", + <&tinylcd35_ts>,"interrupts:0", + <&tinylcd35_ts>,"pendown-gpio:4"; + xohms = <&tinylcd35_ts>,"ti,x-plate-ohms;0"; + rtc-pcf = <0>,"=5"; + rtc-ds = <0>,"=6"; + keypad = <&keypad>,"status"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/tpm-slb9670-overlay.dts b/arch/arm/boot/dts/overlays/tpm-slb9670-overlay.dts new file mode 100644 index 00000000000000..e69188503ca330 --- /dev/null +++ b/arch/arm/boot/dts/overlays/tpm-slb9670-overlay.dts @@ -0,0 +1,44 @@ +/* + * Device Tree overlay for the Infineon SLB9670 Trusted Platform Module add-on + * boards, which can be used as a secure key storage and hwrng. + * available as "Iridium SLB9670" by Infineon and "LetsTrust TPM" by pi3g. + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spi0>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&spidev1>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@2 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + slb9670: slb9670@1 { + compatible = "infineon,slb9670"; + reg = <1>; /* CE1 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <32000000>; + status = "okay"; + }; + + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/tpm-slb9673-overlay.dts b/arch/arm/boot/dts/overlays/tpm-slb9673-overlay.dts new file mode 100644 index 00000000000000..cba8c25c30e5e9 --- /dev/null +++ b/arch/arm/boot/dts/overlays/tpm-slb9673-overlay.dts @@ -0,0 +1,50 @@ +/* + * Device Tree overlay for the Infineon SLB9673 Trusted Platform Module add-on + * boards, which can be used as a secure key storage and hwrng. + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + /* Due to issue https://github.com/raspberrypi/linux/issues/4884 the + hardware I2C needs to be disabled and software I2C enabled */ + fragment@0 { + target = <&i2c_arm>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@1 { + target-path = "/"; + __overlay__ { + i2c1: i2c-gpio@1 { + #address-cells = <1>; + #size-cells = <0>; + compatible = "i2c-gpio"; + gpios = <&gpio 2 6>, /* SDA GPIO_OPEN_DRAIN */ + <&gpio 3 6>; /* CLK GPIO_OPEN_DRAIN */ + clock-frequency = <400000>; + status = "okay"; + }; + }; + }; + + /* Add the TPM */ + fragment@2 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + + slb9673: slb9673@2e { + compatible = "infineon,slb9673", "tcg,tpm-tis-i2c"; + reg = <0x2e>; + status = "okay"; + }; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/uart0-overlay.dts b/arch/arm/boot/dts/overlays/uart0-overlay.dts new file mode 100755 index 00000000000000..6bf2e0fd5c6142 --- /dev/null +++ b/arch/arm/boot/dts/overlays/uart0-overlay.dts @@ -0,0 +1,32 @@ +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&uart0>; + __overlay__ { + pinctrl-names = "default"; + pinctrl-0 = <&uart0_pins>; + status = "okay"; + }; + }; + + fragment@1 { + target = <&gpio>; + __overlay__ { + uart0_pins: uart0_ovl_pins { + brcm,pins = <14 15>; + brcm,function = <4>; /* alt0 */ + brcm,pull = <0 2>; + }; + }; + }; + + __overrides__ { + txd0_pin = <&uart0_pins>,"brcm,pins:0"; + rxd0_pin = <&uart0_pins>,"brcm,pins:4"; + pin_func = <&uart0_pins>,"brcm,function:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/uart0-pi5-overlay.dts b/arch/arm/boot/dts/overlays/uart0-pi5-overlay.dts new file mode 100755 index 00000000000000..3cc9843b812dab --- /dev/null +++ b/arch/arm/boot/dts/overlays/uart0-pi5-overlay.dts @@ -0,0 +1,18 @@ +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2712"; + + fragment@0 { + target = <&uart0>; + frag0: __overlay__ { + status = "okay"; + pinctrl-0 = <&uart0_pins>; + }; + }; + + __overrides__ { + ctsrts = <&frag0>,"pinctrl-0:4=",<&uart0_ctsrts_pins>; + }; +}; diff --git a/arch/arm/boot/dts/overlays/uart1-overlay.dts b/arch/arm/boot/dts/overlays/uart1-overlay.dts new file mode 100644 index 00000000000000..64163bf932b707 --- /dev/null +++ b/arch/arm/boot/dts/overlays/uart1-overlay.dts @@ -0,0 +1,38 @@ +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&uart1>; + __overlay__ { + pinctrl-names = "default"; + pinctrl-0 = <&uart1_pins>; + status = "okay"; + }; + }; + + fragment@1 { + target = <&gpio>; + __overlay__ { + uart1_pins: uart1_ovl_pins { + brcm,pins = <14 15>; + brcm,function = <2>; /* alt5 */ + brcm,pull = <0 2>; + }; + }; + }; + + fragment@2 { + target-path = "/chosen"; + __overlay__ { + bootargs = "8250.nr_uarts=1"; + }; + }; + + __overrides__ { + txd1_pin = <&uart1_pins>,"brcm,pins:0"; + rxd1_pin = <&uart1_pins>,"brcm,pins:4"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/uart1-pi5-overlay.dts b/arch/arm/boot/dts/overlays/uart1-pi5-overlay.dts new file mode 100755 index 00000000000000..739f5a941ffabd --- /dev/null +++ b/arch/arm/boot/dts/overlays/uart1-pi5-overlay.dts @@ -0,0 +1,18 @@ +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2712"; + + fragment@0 { + target = <&uart1>; + frag0: __overlay__ { + status = "okay"; + pinctrl-0 = <&uart1_pins>; + }; + }; + + __overrides__ { + ctsrts = <&frag0>,"pinctrl-0:4=",<&uart1_ctsrts_pins>; + }; +}; diff --git a/arch/arm/boot/dts/overlays/uart2-overlay.dts b/arch/arm/boot/dts/overlays/uart2-overlay.dts new file mode 100644 index 00000000000000..d98cb5795f6a62 --- /dev/null +++ b/arch/arm/boot/dts/overlays/uart2-overlay.dts @@ -0,0 +1,25 @@ +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2711"; + + fragment@0 { + target = <&uart2>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&uart2_pins>; + __dormant__ { + brcm,pins = <0 1 2 3>; + brcm,pull = <0 2 2 0>; + }; + }; + + __overrides__ { + ctsrts = <0>,"=1"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/uart2-pi5-overlay.dts b/arch/arm/boot/dts/overlays/uart2-pi5-overlay.dts new file mode 100755 index 00000000000000..1df956425d3a0f --- /dev/null +++ b/arch/arm/boot/dts/overlays/uart2-pi5-overlay.dts @@ -0,0 +1,18 @@ +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2712"; + + fragment@0 { + target = <&uart2>; + frag0: __overlay__ { + status = "okay"; + pinctrl-0 = <&uart2_pins>; + }; + }; + + __overrides__ { + ctsrts = <&frag0>,"pinctrl-0:4=",<&uart2_ctsrts_pins>; + }; +}; diff --git a/arch/arm/boot/dts/overlays/uart3-overlay.dts b/arch/arm/boot/dts/overlays/uart3-overlay.dts new file mode 100644 index 00000000000000..5751d5b1a29e84 --- /dev/null +++ b/arch/arm/boot/dts/overlays/uart3-overlay.dts @@ -0,0 +1,25 @@ +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2711"; + + fragment@0 { + target = <&uart3>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&uart3_pins>; + __dormant__ { + brcm,pins = <4 5 6 7>; + brcm,pull = <0 2 2 0>; + }; + }; + + __overrides__ { + ctsrts = <0>,"=1"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/uart3-pi5-overlay.dts b/arch/arm/boot/dts/overlays/uart3-pi5-overlay.dts new file mode 100755 index 00000000000000..d8ef51b403ddc6 --- /dev/null +++ b/arch/arm/boot/dts/overlays/uart3-pi5-overlay.dts @@ -0,0 +1,18 @@ +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2712"; + + fragment@0 { + target = <&uart3>; + frag0: __overlay__ { + status = "okay"; + pinctrl-0 = <&uart3_pins>; + }; + }; + + __overrides__ { + ctsrts = <&frag0>,"pinctrl-0:4=",<&uart3_ctsrts_pins>; + }; +}; diff --git a/arch/arm/boot/dts/overlays/uart4-overlay.dts b/arch/arm/boot/dts/overlays/uart4-overlay.dts new file mode 100644 index 00000000000000..99def557b779a1 --- /dev/null +++ b/arch/arm/boot/dts/overlays/uart4-overlay.dts @@ -0,0 +1,25 @@ +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2711"; + + fragment@0 { + target = <&uart4>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&uart4_pins>; + __dormant__ { + brcm,pins = <8 9 10 11>; + brcm,pull = <0 2 2 0>; + }; + }; + + __overrides__ { + ctsrts = <0>,"=1"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/uart4-pi5-overlay.dts b/arch/arm/boot/dts/overlays/uart4-pi5-overlay.dts new file mode 100755 index 00000000000000..7ce5be8cc95c0b --- /dev/null +++ b/arch/arm/boot/dts/overlays/uart4-pi5-overlay.dts @@ -0,0 +1,18 @@ +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2712"; + + fragment@0 { + target = <&uart4>; + frag0: __overlay__ { + status = "okay"; + pinctrl-0 = <&uart4_pins>; + }; + }; + + __overrides__ { + ctsrts = <&frag0>,"pinctrl-0:4=",<&uart4_ctsrts_pins>; + }; +}; diff --git a/arch/arm/boot/dts/overlays/uart5-overlay.dts b/arch/arm/boot/dts/overlays/uart5-overlay.dts new file mode 100644 index 00000000000000..649daea52e6b14 --- /dev/null +++ b/arch/arm/boot/dts/overlays/uart5-overlay.dts @@ -0,0 +1,25 @@ +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2711"; + + fragment@0 { + target = <&uart5>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&uart5_pins>; + __dormant__ { + brcm,pins = <12 13 14 15>; + brcm,pull = <0 2 2 0>; + }; + }; + + __overrides__ { + ctsrts = <0>,"=1"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/udrc-overlay.dts b/arch/arm/boot/dts/overlays/udrc-overlay.dts new file mode 100644 index 00000000000000..701f28e811bb70 --- /dev/null +++ b/arch/arm/boot/dts/overlays/udrc-overlay.dts @@ -0,0 +1,128 @@ +#include <dt-bindings/clock/bcm2835.h> +/* + * Device tree overlay for the Universal Digital Radio Controller + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + fragment@0 { + target = <&i2s_clk_producer>; + __overlay__ { + clocks = <&clocks BCM2835_CLOCK_PCM>; + clock-names = "pcm"; + status = "okay"; + }; + }; + + fragment@1 { + target-path = "/"; + __overlay__ { + regulators { + compatible = "simple-bus"; + #address-cells = <1>; + #size-cells = <0>; + + udrc0_ldoin: udrc0_ldoin { + compatible = "regulator-fixed"; + regulator-name = "ldoin"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + }; + }; + }; + }; + + fragment@2 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + clocks = <&clocks BCM2835_CLOCK_VPU>; + clock-frequency = <400000>; + + tlv320aic32x4: tlv320aic32x4@18 { + compatible = "ti,tlv320aic32x4"; + #sound-dai-cells = <0>; + reg = <0x18>; + status = "okay"; + + clocks = <&clocks BCM2835_CLOCK_GP0>; + clock-names = "mclk"; + assigned-clocks = <&clocks BCM2835_CLOCK_GP0>; + assigned-clock-rates = <25000000>; + + pinctrl-names = "default"; + pinctrl-0 = <&gpclk0_pin &aic3204_reset>; + + reset-gpios = <&gpio 13 0>; + + iov-supply = <&udrc0_ldoin>; + ldoin-supply = <&udrc0_ldoin>; + }; + }; + }; + + fragment@3 { + target = <&sound>; + snd: __overlay__ { + compatible = "simple-audio-card"; + i2s-controller = <&i2s_clk_producer>; + status = "okay"; + + simple-audio-card,name = "udrc"; + simple-audio-card,format = "i2s"; + + simple-audio-card,bitclock-master = <&dailink0_master>; + simple-audio-card,frame-master = <&dailink0_master>; + + simple-audio-card,widgets = + "Line", "Line In", + "Line", "Line Out"; + + simple-audio-card,routing = + "IN1_R", "Line In", + "IN1_L", "Line In", + "CM_L", "Line In", + "CM_R", "Line In", + "Line Out", "LOR", + "Line Out", "LOL"; + + dailink0_master: simple-audio-card,cpu { + sound-dai = <&i2s_clk_producer>; + }; + + simple-audio-card,codec { + sound-dai = <&tlv320aic32x4>; + }; + }; + }; + + fragment@4 { + target = <&gpio>; + __overlay__ { + gpclk0_pin: gpclk0_pin { + brcm,pins = <4>; + brcm,function = <4>; + }; + + aic3204_reset: aic3204_reset { + brcm,pins = <13>; + brcm,function = <1>; + brcm,pull = <1>; + }; + + aic3204_gpio: aic3204_gpio { + brcm,pins = <26>; + }; + }; + }; + + __overrides__ { + alsaname = <&snd>, "simple-audio-card,name"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/ugreen-dabboard-overlay.dts b/arch/arm/boot/dts/overlays/ugreen-dabboard-overlay.dts new file mode 100644 index 00000000000000..234f1f38225b98 --- /dev/null +++ b/arch/arm/boot/dts/overlays/ugreen-dabboard-overlay.dts @@ -0,0 +1,49 @@ +// Definitions for the ugreen dabboard I2S +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_consumer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target-path = "/"; + __overlay__ { + dmic_codec: dmic-codec { + #sound-dai-cells = <0>; + compatible = "dmic-codec"; + status = "okay"; + }; + }; + }; + + fragment@2 { + target = <&sound>; + sound_overlay: __overlay__ { + compatible = "simple-audio-card"; + simple-audio-card,format = "i2s"; + simple-audio-card,name = "dabboard"; + simple-audio-card,bitclock-master = <&dailink0_master>; + simple-audio-card,frame-master = <&dailink0_master>; + simple-audio-card,widgets = "Microphone", "Microphone Jack"; + status = "okay"; + simple-audio-card,cpu { + sound-dai = <&i2s_clk_consumer>; + }; + dailink0_master: simple-audio-card,codec { + #sound-dai-cells = <0>; + sound-dai = <&dmic_codec>; + }; + }; + }; + + __overrides__ { + card-name = <&sound_overlay>,"simple-audio-card,name"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/upstream-overlay.dts b/arch/arm/boot/dts/overlays/upstream-overlay.dts new file mode 100644 index 00000000000000..55a99736a33b0e --- /dev/null +++ b/arch/arm/boot/dts/overlays/upstream-overlay.dts @@ -0,0 +1,101 @@ +// redo: ovmerge -c vc4-kms-v3d-overlay.dts,cma-default,composite dwc2-overlay.dts,dr_mode=otg + +/dts-v1/; +/plugin/; + +#include <dt-bindings/clock/bcm2835.h> + +/ { + compatible = "brcm,bcm2835"; + fragment@0 { + target = <&i2c2>; + __overlay__ { + status = "okay"; + }; + }; + fragment@1 { + target = <&fb>; + __overlay__ { + status = "disabled"; + }; + }; + fragment@2 { + target = <&pixelvalve0>; + __overlay__ { + status = "okay"; + }; + }; + fragment@3 { + target = <&pixelvalve1>; + __overlay__ { + status = "okay"; + }; + }; + fragment@4 { + target = <&pixelvalve2>; + __overlay__ { + status = "okay"; + }; + }; + fragment@5 { + target = <&hvs>; + __overlay__ { + status = "okay"; + }; + }; + fragment@6 { + target = <&hdmi>; + __overlay__ { + status = "okay"; + }; + }; + fragment@7 { + target = <&v3d>; + __overlay__ { + status = "okay"; + }; + }; + fragment@8 { + target = <&vc4>; + __overlay__ { + status = "okay"; + }; + }; + fragment@9 { + target = <&clocks>; + __overlay__ { + claim-clocks = <BCM2835_PLLD_DSI0 BCM2835_PLLD_DSI1 BCM2835_PLLH_AUX BCM2835_PLLH_PIX>; + }; + }; + fragment@10 { + target = <&vec>; + __overlay__ { + status = "okay"; + }; + }; + fragment@11 { + target = <&txp>; + __overlay__ { + status = "okay"; + }; + }; + fragment@12 { + target = <&chosen>; + __overlay__ { + bootargs = "snd_bcm2835.enable_hdmi=0"; + }; + }; + fragment@13 { + target = <&usb>; + #address-cells = <1>; + #size-cells = <1>; + __overlay__ { + compatible = "brcm,bcm2835-usb"; + dr_mode = "otg"; + g-np-tx-fifo-size = <32>; + g-rx-fifo-size = <558>; + g-tx-fifo-size = <512 512 512 512 512 256 256>; + status = "okay"; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/upstream-pi4-overlay.dts b/arch/arm/boot/dts/overlays/upstream-pi4-overlay.dts new file mode 100644 index 00000000000000..1dc60ae6d96716 --- /dev/null +++ b/arch/arm/boot/dts/overlays/upstream-pi4-overlay.dts @@ -0,0 +1,137 @@ +// redo: ovmerge -c vc4-kms-v3d-pi4-overlay.dts,cma-default dwc2-overlay.dts,dr_mode=otg + +/dts-v1/; +/plugin/; + +#include <dt-bindings/clock/bcm2835.h> + +/ { + compatible = "brcm,bcm2711"; + fragment@0 { + target = <&ddc0>; + __overlay__ { + status = "okay"; + }; + }; + fragment@1 { + target = <&ddc1>; + __overlay__ { + status = "okay"; + }; + }; + fragment@2 { + target = <&hdmi0>; + __overlay__ { + status = "okay"; + }; + }; + fragment@3 { + target = <&hdmi1>; + __overlay__ { + status = "okay"; + }; + }; + fragment@4 { + target = <&hvs>; + __overlay__ { + status = "okay"; + }; + }; + fragment@5 { + target = <&pixelvalve0>; + __overlay__ { + status = "okay"; + }; + }; + fragment@6 { + target = <&pixelvalve1>; + __overlay__ { + status = "okay"; + }; + }; + fragment@7 { + target = <&pixelvalve2>; + __overlay__ { + status = "okay"; + }; + }; + fragment@8 { + target = <&pixelvalve3>; + __overlay__ { + status = "okay"; + }; + }; + fragment@9 { + target = <&pixelvalve4>; + __overlay__ { + status = "okay"; + }; + }; + fragment@10 { + target = <&v3d>; + __overlay__ { + status = "okay"; + }; + }; + fragment@11 { + target = <&vc4>; + __overlay__ { + status = "okay"; + }; + }; + fragment@12 { + target = <&txp>; + __overlay__ { + status = "okay"; + }; + }; + fragment@13 { + target = <&fb>; + __overlay__ { + status = "disabled"; + }; + }; + fragment@14 { + target = <&firmwarekms>; + __overlay__ { + status = "disabled"; + }; + }; + fragment@15 { + target = <&vec>; + __overlay__ { + status = "disabled"; + }; + }; + fragment@16 { + target-path = "/chosen"; + __overlay__ { + bootargs = "snd_bcm2835.enable_hdmi=0"; + }; + }; + fragment@17 { + target = <&dvp>; + __overlay__ { + status = "okay"; + }; + }; + fragment@18 { + target = <&aon_intr>; + __overlay__ { + status = "okay"; + }; + }; + fragment@19 { + target = <&usb>; + #address-cells = <1>; + #size-cells = <1>; + __overlay__ { + compatible = "brcm,bcm2835-usb"; + dr_mode = "otg"; + g-np-tx-fifo-size = <32>; + g-rx-fifo-size = <558>; + g-tx-fifo-size = <512 512 512 512 512 256 256>; + status = "okay"; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/vc4-fkms-v3d-overlay.dts b/arch/arm/boot/dts/overlays/vc4-fkms-v3d-overlay.dts new file mode 100644 index 00000000000000..847a98f2a10cee --- /dev/null +++ b/arch/arm/boot/dts/overlays/vc4-fkms-v3d-overlay.dts @@ -0,0 +1,43 @@ +/* + * vc4-fkms-v3d-overlay.dts + */ + +#include "cma-overlay.dts" + +/ { + compatible = "brcm,bcm2835"; + + fragment@1 { + target = <&fb>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@2 { + target = <&firmwarekms>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@3 { + target = <&v3d>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@4 { + target = <&vc4>; + __overlay__ { + status = "okay"; + }; + }; + fragment@5 { + target-path = "/chosen"; + __overlay__ { + bootargs = "clk_ignore_unused"; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/vc4-fkms-v3d-pi4-overlay.dts b/arch/arm/boot/dts/overlays/vc4-fkms-v3d-pi4-overlay.dts new file mode 100644 index 00000000000000..39e78280d475c9 --- /dev/null +++ b/arch/arm/boot/dts/overlays/vc4-fkms-v3d-pi4-overlay.dts @@ -0,0 +1,47 @@ +/* + * vc4-fkms-v3d-overlay.dts + */ + +#include "cma-overlay.dts" + +&frag0 { + size = <((512-4)*1024*1024)>; +}; + +/ { + compatible = "brcm,bcm2711"; + + fragment@1 { + target = <&fb>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@2 { + target = <&firmwarekms>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@3 { + target = <&v3d>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@4 { + target = <&vc4>; + __overlay__ { + status = "okay"; + }; + }; + fragment@5 { + target-path = "/chosen"; + __overlay__ { + bootargs = "clk_ignore_unused"; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/vc4-kms-dpi-generic-overlay.dts b/arch/arm/boot/dts/overlays/vc4-kms-dpi-generic-overlay.dts new file mode 100644 index 00000000000000..8cf41167d4cccd --- /dev/null +++ b/arch/arm/boot/dts/overlays/vc4-kms-dpi-generic-overlay.dts @@ -0,0 +1,83 @@ +/* + * vc4-kms-dpi-generic-overlay.dts + */ + +/dts-v1/; +/plugin/; + +#include "vc4-kms-dpi.dtsi" + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&panel>; + panel_generic: __overlay__ { + compatible = "panel-dpi"; + + width-mm = <154>; + height-mm = <83>; + bus-format = <0x1009>; + + timing: panel-timing { + clock-frequency = <29500000>; + hactive = <800>; + hfront-porch = <24>; + hsync-len = <72>; + hback-porch = <96>; + hsync-active = <1>; + vactive = <480>; + vfront-porch = <3>; + vsync-len = <10>; + vback-porch = <7>; + vsync-active = <1>; + + de-active = <1>; + pixelclk-active = <1>; + }; + }; + }; + + fragment@1 { + target = <&dpi>; + dpi_node_generic: __overlay__ { + pinctrl-0 = <&dpi_18bit_gpio0>; + }; + }; + + __overrides__ { + clock-frequency = <&timing>, "clock-frequency:0"; + hactive = <&timing>, "hactive:0"; + hfp = <&timing>, "hfront-porch:0"; + hsync = <&timing>, "hsync-len:0"; + hbp = <&timing>, "hback-porch:0"; + vactive = <&timing>, "vactive:0"; + vfp = <&timing>, "vfront-porch:0"; + vsync = <&timing>, "vsync-len:0"; + vbp = <&timing>, "vback-porch:0"; + hsync-invert = <&timing>, "hsync-active:0=0"; + vsync-invert = <&timing>, "vsync-active:0=0"; + de-invert = <&timing>, "de-active:0=0"; + pixclk-invert = <&timing>, "pixelclk-active:0=0"; + interlaced = <&timing>, "interlaced?"; + + width-mm = <&panel_generic>, "width-mm:0"; + height-mm = <&panel_generic>, "height-mm:0"; + + rgb565 = <&panel_generic>, "bus-format:0=0x1017", + <&dpi_node_generic>, "pinctrl-0:0=",<&dpi_16bit_gpio0>; + rgb565-padhi = <&panel_generic>, "bus-format:0=0x1022", + <&dpi_node_generic>, "pinctrl-0:0=",<&dpi_16bit_cpadhi_gpio0>; + bgr666 = <&panel_generic>, "bus-format:0=0x1023"; + bgr666-padhi = <&panel_generic>, "bus-format:0=0x1024", + <&dpi_node_generic>, "pinctrl-0:0=",<&dpi_18bit_cpadhi_gpio0>; + rgb666-padhi = <&panel_generic>, "bus-format:0=0x1015", + <&dpi_node_generic>, "pinctrl-0:0=",<&dpi_18bit_cpadhi_gpio0>; + bgr888 = <&panel_generic>, "bus-format:0=0x1013", + <&dpi_node_generic>, "pinctrl-0:0=",<&dpi_gpio0>; + rgb888 = <&panel_generic>, "bus-format:0=0x100a", + <&dpi_node_generic>, "pinctrl-0:0=",<&dpi_gpio0>; + bus-format = <&panel_generic>, "bus-format:0"; + rgb-order = <&dpi_node_generic>, "rgb_order"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/vc4-kms-dpi-hyperpixel.dtsi b/arch/arm/boot/dts/overlays/vc4-kms-dpi-hyperpixel.dtsi new file mode 100644 index 00000000000000..585402a3b9b49a --- /dev/null +++ b/arch/arm/boot/dts/overlays/vc4-kms-dpi-hyperpixel.dtsi @@ -0,0 +1,94 @@ +/* + * vc4-kms-dpi-hyperpixel4.dtsi + * Commmon initialisation for HyperPixel DPI displays + */ + +#include <dt-bindings/gpio/gpio.h> +#include <dt-bindings/pinctrl/bcm2835.h> + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target-path = "/"; + __overlay__ { + spi { + compatible = "spi-gpio"; + #address-cells = <1>; + #size-cells = <0>; + pinctrl-0 = <&spi_pins>; + pinctrl-names = "default"; + + sck-gpios = <&gpio 27 GPIO_ACTIVE_HIGH>; + mosi-gpios = <&gpio 26 GPIO_ACTIVE_HIGH>; + cs-gpios = <&gpio 18 GPIO_ACTIVE_LOW>; + num-chipselects = <1>; + sck-idle-input; + + panel: display@0 { + reg = <0>; + /* 100 kHz */ + spi-max-frequency = <100000>; + backlight = <&backlight>; + rotation = <0>; + + port { + panel_in: endpoint { + remote-endpoint = <&dpi_out>; + }; + }; + }; + }; + + backlight: backlight { + compatible = "gpio-backlight"; + gpios = <&gpio 19 0>; + }; + }; + }; + + fragment@1 { + target = <&dpi>; + __overlay__ { + status = "okay"; + + pinctrl-names = "default"; + pinctrl-0 = <&dpi_18bit_cpadhi_gpio0>; + + port { + dpi_out: endpoint { + remote-endpoint = <&panel_in>; + }; + }; + }; + }; + + fragment@2 { + target = <&gpio>; + __overlay__ { + spi_pins: hyperpixel4_spi_pins { + brcm,pins = <27 18 26>; + brcm,pull = <BCM2835_PUD_UP BCM2835_PUD_UP BCM2835_PUD_OFF>; + brcm,function = <0>; + }; + }; + }; + + fragment@3 { + target-path = "/"; + __overlay__ { + i2c_gpio: i2c@0 { + compatible = "i2c-gpio"; + gpios = <&gpio 10 0 /* sda */ + &gpio 11 0>; /* scl */ + i2c-gpio,delay-us = <4>; /* ~100 kHz */ + #address-cells = <1>; + #size-cells = <0>; + }; + }; + }; + + __overrides__ { + rotate = <&panel>, "rotation:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/vc4-kms-dpi-hyperpixel2r-overlay.dts b/arch/arm/boot/dts/overlays/vc4-kms-dpi-hyperpixel2r-overlay.dts new file mode 100644 index 00000000000000..4cd9d6a55c48bd --- /dev/null +++ b/arch/arm/boot/dts/overlays/vc4-kms-dpi-hyperpixel2r-overlay.dts @@ -0,0 +1,114 @@ +/* + * vc4-kms-dpi-hyperpixel2r-overlay.dts + */ + +#include <dt-bindings/gpio/gpio.h> +#include <dt-bindings/pinctrl/bcm2835.h> + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target-path = "/"; + __overlay__ { + spi { + compatible = "spi-gpio"; + #address-cells = <1>; + #size-cells = <0>; + pinctrl-0 = <&spi_pins>; + pinctrl-names = "default"; + + sck-gpios = <&gpio 11 GPIO_ACTIVE_HIGH>; + mosi-gpios = <&gpio 10 GPIO_ACTIVE_HIGH>; + cs-gpios = <&gpio 18 GPIO_ACTIVE_LOW>; + num-chipselects = <1>; + + panel: display@0 { + compatible = "pimoroni,hyperpixel2round"; + reg = <0>; + /* 100 kHz */ + spi-max-frequency = <100000>; + backlight = <&backlight>; + rotation = <0>; + + port { + panel_in: endpoint { + remote-endpoint = <&dpi_out>; + }; + }; + }; + }; + + backlight: backlight { + compatible = "gpio-backlight"; + gpios = <&gpio 19 0>; + }; + }; + }; + + fragment@1 { + target = <&dpi>; + __overlay__ { + status = "okay"; + + pinctrl-names = "default"; + pinctrl-0 = <&dpi_18bit_cpadhi_gpio0>; + + port { + dpi_out: endpoint { + remote-endpoint = <&panel_in>; + }; + }; + }; + }; + + fragment@2 { + target = <&gpio>; + __overlay__ { + spi_pins: hyperpixel4_spi_pins { + brcm,pins = <27 18 26>; + brcm,pull = <BCM2835_PUD_UP BCM2835_PUD_UP BCM2835_PUD_OFF>; + brcm,function = <0>; + }; + }; + }; + + fragment@3 { + target-path = "/"; + __overlay__ { + i2c_gpio: i2c@0 { + compatible = "i2c-gpio"; + status = "disabled"; + + gpios = <&gpio 10 GPIO_ACTIVE_HIGH /* sda */ + &gpio 11 GPIO_ACTIVE_HIGH>; /* scl */ + i2c-gpio,delay-us = <4>; /* ~100 kHz */ + #address-cells = <1>; + #size-cells = <0>; + + polytouch: edt-ft5x06@15 { + #address-cells = <1>; + #size-cells = <0>; + compatible = "edt,edt-ft5406"; + reg = <0x15>; + interrupt-parent = <&gpio>; + interrupts = <27 0x02>; + touchscreen-size-x = <240>; + touchscreen-size-y = <240>; + }; + }; + }; + }; + + __overrides__ { + disable-touch = <0>,"-3"; + touchscreen-inverted-x = <&polytouch>,"touchscreen-inverted-x?"; + touchscreen-inverted-y = <&polytouch>,"touchscreen-inverted-y!"; + touchscreen-swapped-x-y = <&polytouch>,"touchscreen-swapped-x-y!"; + rotate = <&panel>, "rotation:0"; + }; + +}; diff --git a/arch/arm/boot/dts/overlays/vc4-kms-dpi-hyperpixel4-overlay.dts b/arch/arm/boot/dts/overlays/vc4-kms-dpi-hyperpixel4-overlay.dts new file mode 100644 index 00000000000000..eafc25ad79fff7 --- /dev/null +++ b/arch/arm/boot/dts/overlays/vc4-kms-dpi-hyperpixel4-overlay.dts @@ -0,0 +1,57 @@ +/* + * vc4-kms-dpi-hyperpixel4sq-overlay.dts + */ + +/dts-v1/; +/plugin/; + +#include "vc4-kms-dpi-hyperpixel.dtsi" + +&panel { + compatible = "pimoroni,hyperpixel4"; +}; + +/ { + fragment@11 { + target = <&i2c_gpio>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + ft6236_14: ft6236@14 { + compatible = "goodix,gt911"; + reg = <0x14>; + interrupt-parent = <&gpio>; + interrupts = <27 2>; + touchscreen-size-x = <480>; + touchscreen-size-y = <800>; + touchscreen-x-mm = <51>; + touchscreen-y-mm = <85>; + touchscreen-inverted-y; + touchscreen-swapped-x-y; + }; + ft6236_5d: ft6236@5d { + compatible = "goodix,gt911"; + reg = <0x5d>; + interrupt-parent = <&gpio>; + interrupts = <27 2>; + touchscreen-size-x = <480>; + touchscreen-size-y = <800>; + touchscreen-x-mm = <51>; + touchscreen-y-mm = <85>; + touchscreen-inverted-y; + touchscreen-swapped-x-y; + }; + }; + }; + + __overrides__ { + disable-touch = <0>,"-3-11"; + touchscreen-inverted-x = <&ft6236_14>,"touchscreen-inverted-x?", + <&ft6236_5d>,"touchscreen-inverted-x?"; + touchscreen-inverted-y = <&ft6236_14>,"touchscreen-inverted-y!", + <&ft6236_5d>,"touchscreen-inverted-y!"; + touchscreen-swapped-x-y = <&ft6236_14>,"touchscreen-swapped-x-y!", + <&ft6236_5d>,"touchscreen-swapped-x-y!"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/vc4-kms-dpi-hyperpixel4sq-overlay.dts b/arch/arm/boot/dts/overlays/vc4-kms-dpi-hyperpixel4sq-overlay.dts new file mode 100644 index 00000000000000..700046348ecf0d --- /dev/null +++ b/arch/arm/boot/dts/overlays/vc4-kms-dpi-hyperpixel4sq-overlay.dts @@ -0,0 +1,36 @@ +/* + * vc4-kms-dpi-hyperpixel4-overlay.dts + */ + +/dts-v1/; +/plugin/; + +#include "vc4-kms-dpi-hyperpixel.dtsi" + +&panel { + compatible = "pimoroni,hyperpixel4square"; +}; + +/ { + fragment@11 { + target = <&i2c_gpio>; + __overlay__ { + polytouch: edt-ft5x06@48 { + #address-cells = <1>; + #size-cells = <0>; + compatible = "edt,edt-ft5406"; + reg = <0x48>; + interrupt-parent = <&gpio>; + interrupts = <27 0x02>; + touchscreen-size-x = <720>; + touchscreen-size-y = <720>; + }; + }; + }; + __overrides__ { + disable-touch = <0>,"-3-11"; + touchscreen-inverted-x = <&polytouch>,"touchscreen-inverted-x?"; + touchscreen-inverted-y = <&polytouch>,"touchscreen-inverted-y!"; + touchscreen-swapped-x-y = <&polytouch>,"touchscreen-swapped-x-y!"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/vc4-kms-dpi-panel-overlay.dts b/arch/arm/boot/dts/overlays/vc4-kms-dpi-panel-overlay.dts new file mode 100644 index 00000000000000..ee9e2e8fd2468c --- /dev/null +++ b/arch/arm/boot/dts/overlays/vc4-kms-dpi-panel-overlay.dts @@ -0,0 +1,69 @@ +/* + * vc4-kms-dpi-panel-overlay.dts + * Support for any predefined DPI panel. + */ + +/dts-v1/; +/plugin/; + +#include "vc4-kms-dpi.dtsi" + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&panel>; + __dormant__ { + compatible = "innolux,at056tn53v1", "simple-panel"; + }; + }; + fragment@1 { + target = <&panel>; + __dormant__ { + compatible = "ontat,yx700wv03", "simple-panel"; + }; + }; + fragment@2 { + target = <&panel>; + __dormant__ { + compatible = "geekworm,mzp280", "simple-panel"; + }; + }; + + fragment@90 { + target = <&dpi>; + __dormant__ { + pinctrl-0 = <&dpi_18bit_cpadhi_gpio0>; + }; + }; + fragment@91 { + target = <&dpi>; + __dormant__ { + pinctrl-0 = <&dpi_18bit_gpio0>; + }; + }; + fragment@92 { + target = <&dpi>; + __dormant__ { + pinctrl-0 = <&dpi_gpio0>; + }; + }; + fragment@93 { + target = <&dpi>; + __dormant__ { + pinctrl-0 = <&dpi_16bit_cpadhi_gpio0>; + }; + }; + fragment@94 { + target = <&dpi>; + __dormant__ { + pinctrl-0 = <&dpi_16bit_gpio0>; + }; + }; + + __overrides__ { + at056tn53v1 = <0>, "+0+90"; + kippah-7inch = <0>, "+1+91"; + mzp280 = <0>, "+2+93"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/vc4-kms-dpi.dtsi b/arch/arm/boot/dts/overlays/vc4-kms-dpi.dtsi new file mode 100644 index 00000000000000..67c884de2a8db1 --- /dev/null +++ b/arch/arm/boot/dts/overlays/vc4-kms-dpi.dtsi @@ -0,0 +1,111 @@ +/* + * vc4-kms-dpi.dtsi + */ + +#include <dt-bindings/gpio/gpio.h> +#include <dt-bindings/pinctrl/bcm2835.h> + +/ { + fragment@100 { + target-path = "/"; + __overlay__ { + panel: panel { + rotation = <0>; + port { + panel_in: endpoint { + remote-endpoint = <&dpi_out>; + }; + }; + }; + }; + }; + + fragment@101 { + target = <&dpi>; + dpi_node: __overlay__ { + status = "okay"; + + pinctrl-names = "default"; + + port { + dpi_out: endpoint { + remote-endpoint = <&panel_in>; + }; + }; + }; + }; + + fragment@102 { + target = <&panel>; + __dormant__ { + backlight = <&backlight>; + }; + }; + + fragment@103 { + target-path = "/"; + __dormant__ { + backlight: backlight { + compatible = "gpio-backlight"; + gpios = <&gpio 255 GPIO_ACTIVE_HIGH>; + }; + }; + }; + + fragment@104 { + target = <&panel>; + __dormant__ { + backlight = <&backlight_pwm>; + }; + }; + + fragment@105 { + target-path = "/"; + __dormant__ { + backlight_pwm: backlight_pwm { + compatible = "pwm-backlight"; + brightness-levels = <0 6 8 12 16 24 32 40 48 64 96 128 160 192 224 255>; + default-brightness-level = <16>; + pwms = <&pwm 0 200000 0>; + }; + }; + }; + + fragment@106 { + target = <&pwm>; + __dormant__ { + pinctrl-names = "default"; + pinctrl-0 = <&pwm_pins>; + assigned-clock-rates = <1000000>; + status = "okay"; + }; + }; + + fragment@107 { + target = <&gpio>; + __dormant__ { + pwm_pins: pwm_pins { + brcm,pins = <18>; + brcm,function = <2>; /* Alt5 */ + }; + }; + }; + + fragment@108 { + target = <&chosen>; + __dormant__ { + bootargs = "snd_bcm2835.enable_headphones=0"; + }; + }; + + __overrides__ { + backlight-gpio = <0>, "+102+103", + <&backlight>, "gpios:4"; + backlight-pwm = <0>, "+104+105+106+107+108"; + backlight-pwm-chan = <&backlight_pwm>, "pwms:4"; + backlight-pwm-gpio = <&pwm_pins>, "brcm,pins:0"; + backlight-pwm-func = <&pwm_pins>, "brcm,function:0"; + backlight-def-brightness = <&backlight_pwm>, "default-brightness-level:0"; + rotate = <&panel>, "rotation:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/vc4-kms-dsi-7inch-overlay.dts b/arch/arm/boot/dts/overlays/vc4-kms-dsi-7inch-overlay.dts new file mode 100644 index 00000000000000..8d58d5e6067fc0 --- /dev/null +++ b/arch/arm/boot/dts/overlays/vc4-kms-dsi-7inch-overlay.dts @@ -0,0 +1,121 @@ + /* + * Device Tree overlay for RaspberryPi 7" Touchscreen panel + * + */ + +/dts-v1/; +/plugin/; + +#include "edt-ft5406.dtsi" + +&ft5406 { + vcc-supply = <®_display>; + reset-gpio = <®_display 1 1>; +}; + +/ { + /* No compatible as it will have come from edt-ft5406.dtsi */ + + dsi_frag: fragment@0 { + target = <&dsi1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + port { + dsi_out: endpoint { + remote-endpoint = <&bridge_in>; + }; + }; + bridge@0 { + reg = <0>; + compatible = "toshiba,tc358762"; + vddc-supply = <®_bridge>; + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + bridge_in: endpoint { + remote-endpoint = <&dsi_out>; + }; + }; + + port@1 { + reg = <1>; + bridge_out: endpoint { + remote-endpoint = <&panel_in>; + }; + }; + }; + }; + }; + }; + + fragment@1 { + target-path = "/"; + __overlay__ { + panel_disp: panel_disp@1 { + reg = <1>; + compatible = "raspberrypi,7inch-dsi", "simple-panel"; + backlight = <®_display>; + power-supply = <®_display>; + + port { + panel_in: endpoint { + remote-endpoint = <&bridge_out>; + }; + }; + }; + + reg_bridge: reg_bridge@1 { + reg = <1>; + compatible = "regulator-fixed"; + regulator-name = "bridge_reg"; + gpio = <®_display 0 0>; + vin-supply = <®_display>; + enable-active-high; + }; + }; + }; + + i2c_frag: fragment@2 { + target = <&i2c_csi_dsi>; + i2cbus: __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + reg_display: reg_display@45 { + compatible = "raspberrypi,7inch-touchscreen-panel-regulator"; + reg = <0x45>; + gpio-controller; + #gpio-cells = <2>; + }; + }; + }; + + fragment@3 { + target = <&i2c0if>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@4 { + target = <&i2c0mux>; + __overlay__ { + status = "okay"; + }; + }; + + __overrides__ { + dsi0 = <&dsi_frag>, "target:0=",<&dsi0>, + <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>, + <&panel_disp>, "reg:0=0", + <®_bridge>, "reg:0=0", + <®_bridge>, "regulator-name=bridge_reg_0"; + disable_touch = <&ft5406>, "status=disabled"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/vc4-kms-dsi-generic-overlay.dts b/arch/arm/boot/dts/overlays/vc4-kms-dsi-generic-overlay.dts new file mode 100644 index 00000000000000..cf4ca5b6c75f61 --- /dev/null +++ b/arch/arm/boot/dts/overlays/vc4-kms-dsi-generic-overlay.dts @@ -0,0 +1,106 @@ +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + dsi_frag: fragment@0 { + target = <&dsi1>; + __overlay__{ + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + port { + dsi_out:endpoint { + remote-endpoint = <&panel_dsi_port>; + }; + }; + panel: panel-dsi-generic@0 { + // See panel-dsi.yaml binding + // Using dummy name for panel model + compatible = "Generic,panel-dsi","panel-dsi"; + reg = <0>; + power-supply = <0>; + backlight = <0>; + dsi-color-format = "RGB888"; + mode = "MODE_VIDEO"; + width-mm = <0>; + height-mm = <0>; + + port { + panel_dsi_port: endpoint { + data-lanes = <1>; + remote-endpoint = <&dsi_out>; + }; + }; + + timing: panel-timing { + clock-frequency = <30000000>; + hactive = <840>; + vactive = <480>; + hback-porch = <44>; + hfront-porch = <46>; + hsync-len = <2>; + vback-porch = <18>; + vfront-porch = <16>; + vsync-len = <2>; + }; + }; + }; + }; + + fragment@1 { + target = <&panel_dsi_port>; + __dormant__ { + data-lanes = <1>; + }; + }; + + fragment@2 { + target = <&panel_dsi_port>; + __dormant__ { + data-lanes = <1 2>; + }; + }; + + fragment@3 { + target = <&panel_dsi_port>; + __dormant__ { + data-lanes = <1 2 3>; + }; + }; + + fragment@4 { + target = <&panel_dsi_port>; + __dormant__ { + data-lanes = <1 2 3 4>; + }; + }; + + __overrides__ { + dsi0 = <&dsi_frag>, "target:0=",<&dsi0>; + + clock-frequency = <&timing>, "clock-frequency:0"; + hactive = <&timing>, "hactive:0"; + hfp = <&timing>, "hfront-porch:0"; + hsync = <&timing>, "hsync-len:0"; + hbp = <&timing>, "hback-porch:0"; + vactive = <&timing>, "vactive:0"; + vfp = <&timing>, "vfront-porch:0"; + vsync = <&timing>, "vsync-len:0"; + vbp = <&timing>, "vback-porch:0"; + + width-mm = <&panel>, "width-mm:0"; + height-mm = <&panel>, "height-mm:0"; + + rgb565 = <&panel>, "dsi-color-format=RGB565"; + rgb666p = <&panel>, "dsi-color-format=RGB666_PACKED"; + rgb666 = <&panel>, "dsi-color-format=RGB666"; + rgb888 = <&panel>, "dsi-color-format=RGB888"; + one-lane = <0>,"+1"; + two-lane = <0>,"+2"; + three-lane = <0>,"+3"; + four-lane = <0>,"+4"; + }; + +}; diff --git a/arch/arm/boot/dts/overlays/vc4-kms-dsi-ili9881-5inch-overlay.dts b/arch/arm/boot/dts/overlays/vc4-kms-dsi-ili9881-5inch-overlay.dts new file mode 100644 index 00000000000000..1985766c0e679d --- /dev/null +++ b/arch/arm/boot/dts/overlays/vc4-kms-dsi-ili9881-5inch-overlay.dts @@ -0,0 +1,122 @@ +/* + * vc4-kms-dsi-ili9881-5inch-overlay.dts + */ + +/dts-v1/; +/plugin/; + +#include <dt-bindings/gpio/gpio.h> + +/ { + compatible = "brcm,bcm2835"; + + i2c_frag: fragment@0 { + target = <&i2c_csi_dsi>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + display_mcu: display_mcu@45 + { + compatible = "raspberrypi,v2-touchscreen-panel-regulator"; + reg = <0x45>; + gpio-controller; + #gpio-cells = <2>; + }; + + gt911: gt911@5d { + compatible = "goodix,gt911"; + reg = <0x5d>; + AVDD28-supply = <&touch_reg>; + touchscreen-size-x = <720>; + touchscreen-size-y = <1280>; + touchscreen-x-mm = <62>; + touchscreen-y-mm = <110>; + }; + }; + }; + + dsi_frag: fragment@1 { + target = <&dsi1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + port { + dsi_out: endpoint { + remote-endpoint = <&panel_in>; + }; + }; + + dsi_panel: dsi_panel@0 { + reg = <0>; + compatible = "raspberrypi,dsi-5inch"; + reset-gpio = <&display_mcu 0 GPIO_ACTIVE_LOW>; + backlight = <&display_mcu>; + + port { + panel_in: endpoint { + remote-endpoint = <&dsi_out>; + }; + }; + }; + }; + }; + + fragment@2 { + target = <&i2c0if>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@3 { + target = <&i2c0mux>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@4 { + target-path = "/"; + __overlay__ { + touch_reg: touch_reg@1 { + reg = <1>; + compatible = "regulator-fixed"; + regulator-name = "touch_reg_1"; + gpio = <&display_mcu 1 GPIO_ACTIVE_HIGH>; + startup-delay-us = <50000>; + enable-active-high; + }; + }; + }; + + fragment@10 { + target = <>911>; + __dormant__ { + touchscreen-inverted-x; + }; + }; + + fragment@11 { + target = <>911>; + __dormant__ { + touchscreen-inverted-y; + }; + }; + + __overrides__ { + dsi0 = <&dsi_frag>, "target:0=",<&dsi0>, + <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>, + <&touch_reg>, "reg:0=0", + <&touch_reg>, "regulator-name=touch_reg_0"; + sizex = <>911>,"touchscreen-size-x:0"; + sizey = <>911>,"touchscreen-size-y:0"; + invx = <0>, "+10"; + invy = <0>, "+11"; + swapxy = <>911>,"touchscreen-swapped-x-y?"; + disable_touch = <>911>, "status=disabled"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/vc4-kms-dsi-ili9881-7inch-overlay.dts b/arch/arm/boot/dts/overlays/vc4-kms-dsi-ili9881-7inch-overlay.dts new file mode 100644 index 00000000000000..2c8eef6475f10f --- /dev/null +++ b/arch/arm/boot/dts/overlays/vc4-kms-dsi-ili9881-7inch-overlay.dts @@ -0,0 +1,123 @@ +/* + * vc4-kms-dsi-ili9881-5inch-overlay.dts + */ + +/dts-v1/; +/plugin/; + +#include <dt-bindings/gpio/gpio.h> + +/ { + compatible = "brcm,bcm2835"; + + i2c_frag: fragment@0 { + target = <&i2c_csi_dsi>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + display_mcu: display_mcu@45 + { + compatible = "raspberrypi,v2-touchscreen-panel-regulator"; + reg = <0x45>; + gpio-controller; + #gpio-cells = <2>; + }; + + gt911: gt911@5d { + compatible = "goodix,gt911"; + reg = <0x5d>; + AVDD28-supply = <&touch_reg>; + touchscreen-size-x = <720>; + touchscreen-size-y = <1280>; + touchscreen-x-mm = <90>; + touchscreen-y-mm = <151>; + }; + }; + }; + + dsi_frag: fragment@1 { + target = <&dsi1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + port { + dsi_out: endpoint { + remote-endpoint = <&panel_in>; + }; + }; + + dsi_panel: dsi_panel@0 { + reg = <0>; + compatible = "raspberrypi,dsi-7inch"; + reset-gpio = <&display_mcu 0 GPIO_ACTIVE_LOW>; + backlight = <&display_mcu>; + + port { + panel_in: endpoint { + remote-endpoint = <&dsi_out>; + }; + }; + }; + }; + }; + + fragment@2 { + target = <&i2c0if>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@3 { + target = <&i2c0mux>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@4 { + target-path = "/"; + __overlay__ { + touch_reg: touch_reg@1 { + reg = <1>; + compatible = "regulator-fixed"; + regulator-name = "touch_reg_1"; + gpio = <&display_mcu 1 GPIO_ACTIVE_HIGH>; + startup-delay-us = <50000>; + enable-active-high; + }; + }; + }; + + fragment@10 { + target = <>911>; + __dormant__ { + touchscreen-inverted-x; + }; + }; + + fragment@11 { + target = <>911>; + __dormant__ { + touchscreen-inverted-y; + }; + }; + + __overrides__ { + dsi0 = <&dsi_frag>, "target:0=",<&dsi0>, + <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>, + <&touch_reg>, "reg:0=0", + <&touch_reg>, "regulator-name=touch_reg_0"; + sizex = <>911>,"touchscreen-size-x:0"; + sizey = <>911>,"touchscreen-size-y:0"; + invx = <0>, "+10"; + invy = <0>, "+11"; + swapxy = <>911>,"touchscreen-swapped-x-y?"; + disable_touch = <>911>, "status=disabled"; + rotation = <&dsi_panel>, "rotation:0"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/vc4-kms-dsi-lt070me05000-overlay.dts b/arch/arm/boot/dts/overlays/vc4-kms-dsi-lt070me05000-overlay.dts new file mode 100644 index 00000000000000..d7b8f671380418 --- /dev/null +++ b/arch/arm/boot/dts/overlays/vc4-kms-dsi-lt070me05000-overlay.dts @@ -0,0 +1,69 @@ +/* + * Device Tree overlay to connect a JDI LT070ME05000 DSI panel to DSI1. + * This uses 4 DSI data lanes, so can only be used with a Compute Module. + * + * Credit to forum user gizmomouse on + * https://www.raspberrypi.org/forums/viewtopic.php?f=98&t=253912 and + * Andrey Vostrukhin of Harlab for the overlay. + * + * Refer to https://github.com/harlab/CM4_LCD_LT070ME05000 for schematics and + * other documentation. + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&dsi1>; + __overlay__{ + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + port { + dsi_out_port:endpoint { + remote-endpoint = <&panel_dsi_port>; + }; + }; + + lt070me05000:lt070me05000@0 { + compatible = "jdi,lt070me05000"; + status = "okay"; + reg = <0>; + reset-gpios = <&gpio 17 1>; // LCD RST + enable-gpios = <&gpio 4 0>; // LCD Enable + dcdc-en-gpios = <&gpio 5 0>; // LCD DC-DC Enable + port { + panel_dsi_port: endpoint { + remote-endpoint = <&dsi_out_port>; + }; + }; + }; + }; + }; + + fragment@1 { + target = <&gpio>; + __overlay__ { + lt070me05000_pins: lt070me05000_pins { + brcm,pins = <4 5 17>; + brcm,function = <1 1 1>; // out + brcm,pull = <0 0 0>; // off + }; + }; + + }; + + __overrides__ { + reset = <<070me05000_pins>,"brcm,pins:8", + <<070me05000>,"reset-gpios:4"; + + enable = <<070me05000_pins>,"brcm,pins:0", + <<070me05000>,"enable-gpios:4"; + + dcdc-en = <<070me05000_pins>,"brcm,pins:4", + <<070me05000>,"dcdc-en-gpios:4"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/vc4-kms-dsi-lt070me05000-v2-overlay.dts b/arch/arm/boot/dts/overlays/vc4-kms-dsi-lt070me05000-v2-overlay.dts new file mode 100644 index 00000000000000..5dcd0f2243e228 --- /dev/null +++ b/arch/arm/boot/dts/overlays/vc4-kms-dsi-lt070me05000-v2-overlay.dts @@ -0,0 +1,64 @@ +/* + * Device Tree overlay to connect a JDI LT070ME05000 DSI panel to DSI1. + * This uses 4 DSI data lanes, so can only be used with a Compute Module. + * + * The overlay is for V2 of Harlab's interface board that uses a PCA9536 to + * handle the panel's control GPIOs instead of wiring it back to Pi GPIOs. + * + * Credit to Andrey Vostrukhin of Harlab for the overlay. + * + * Refer to https://github.com/harlab/CM4_LCD_LT070ME05000 for schematics and + * other documentation. + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2c_csi_dsi>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + pca: pca@41 { + compatible = "nxp,pca9536"; + reg = <0x41>; + gpio-controller; + #gpio-cells = <2>; + status = "okay"; + }; + }; + }; + + fragment@1 { + target = <&dsi1>; + __overlay__{ + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + port { + dsi_out_port:endpoint { + remote-endpoint = <&panel_dsi_port>; + }; + }; + + lt070me05000:lt070me05000@0 { + compatible = "jdi,lt070me05000"; + status = "okay"; + reg = <0>; + reset-gpios = <&pca 0 1>; + enable-gpios = <&pca 2 0>; + dcdc-en-gpios = <&pca 1 0>; + port { + panel_dsi_port: endpoint { + remote-endpoint = <&dsi_out_port>; + }; + }; + }; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/vc4-kms-dsi-waveshare-800x480-overlay.dts b/arch/arm/boot/dts/overlays/vc4-kms-dsi-waveshare-800x480-overlay.dts new file mode 100644 index 00000000000000..7a7058d14d6c7b --- /dev/null +++ b/arch/arm/boot/dts/overlays/vc4-kms-dsi-waveshare-800x480-overlay.dts @@ -0,0 +1,116 @@ +/* + * Device Tree overlay for Waveshare 4.3" 800x480 panel. + * It tries to look like a Pi 7" panel, but fails with some of the timing + * options. + */ + +/dts-v1/; +/plugin/; + +#include "edt-ft5406.dtsi" + +&ft5406 { + vcc-supply = <®_display>; + reset-gpio = <®_display 1 1>; +}; + +/ { + /* No compatible as it will have come from edt-ft5406.dtsi */ + + dsi_frag: fragment@0 { + target = <&dsi1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + port { + dsi_out: endpoint { + remote-endpoint = <&panel_dsi_port>; + }; + }; + + panel: panel-dsi-generic@0 { + // See panel-dsi.yaml binding + compatible = "waveshare,4-3-inch-dsi","panel-dsi"; + reg = <0>; + power-supply = <®_display>; + backlight = <®_display>; + dsi-color-format = "RGB888"; + mode = "MODE_VIDEO"; + width-mm = <0>; + height-mm = <0>; + + port { + panel_dsi_port: endpoint { + data-lanes = <1>; + remote-endpoint = <&dsi_out>; + }; + }; + + timing: panel-timing { + clock-frequency = <27777000>; + hactive = <800>; + vactive = <480>; + hfront-porch = <59>; + hsync-len = <2>; + hback-porch = <45>; + vfront-porch = <7>; + vsync-len = <2>; + vback-porch = <22>; + }; + }; + }; + }; + + fragment@1 { + target-path = "/"; + __overlay__ { + reg_bridge: reg_bridge@1 { + reg = <1>; + compatible = "regulator-fixed"; + regulator-name = "bridge_reg"; + gpio = <®_display 0 0>; + vin-supply = <®_display>; + enable-active-high; + }; + }; + }; + + i2c_frag: fragment@2 { + target = <&i2c_csi_dsi>; + i2cbus: __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + reg_display: reg_display@45 { + compatible = "raspberrypi,7inch-touchscreen-panel-regulator"; + reg = <0x45>; + gpio-controller; + #gpio-cells = <2>; + }; + }; + }; + + fragment@3 { + target = <&i2c0if>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@4 { + target = <&i2c0mux>; + __overlay__ { + status = "okay"; + }; + }; + + __overrides__ { + dsi0 = <&dsi_frag>, "target:0=",<&dsi0>, + <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>, + <®_bridge>, "reg:0=0", + <®_bridge>, "regulator-name=bridge_reg_0"; + disable_touch = <&ft5406>, "status=disabled"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/vc4-kms-dsi-waveshare-panel-overlay.dts b/arch/arm/boot/dts/overlays/vc4-kms-dsi-waveshare-panel-overlay.dts new file mode 100644 index 00000000000000..9973ac0ce389bc --- /dev/null +++ b/arch/arm/boot/dts/overlays/vc4-kms-dsi-waveshare-panel-overlay.dts @@ -0,0 +1,143 @@ +/* + * Device Tree overlay for Waveshare DSI Touchscreens + * + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + dsi_frag: fragment@0 { + target = <&dsi1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + port { + dsi_out: endpoint { + remote-endpoint = <&panel_in>; + }; + }; + }; + }; + + fragment@1 { + target-path = "/"; + __overlay__ { + }; + }; + + i2c_frag: fragment@2 { + target = <&i2c_csi_dsi>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + panel: panel_disp1@45 { + reg = <0x45>; + compatible = "waveshare,10.1inch-panel"; + + port { + panel_in: endpoint { + remote-endpoint = <&dsi_out>; + }; + }; + }; + + touch: goodix@14 { + reg = <0x14>; + compatible = "goodix,gt911"; + }; + + touch2: ilitek@41 { + compatible = "ilitek,ili251x"; + reg = <0x41>; + status = "disabled"; + }; + }; + }; + + fragment@3 { + target = <&i2c0if>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@4 { + target = <&i2c0mux>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@5 { + target = <&i2c_arm>; + __dormant__ { + status = "okay"; + }; + }; + + __overrides__ { + 2_8_inch = <&panel>, "compatible=waveshare,2.8inch-panel", + <&touch>, "touchscreen-size-x:0=480", + <&touch>, "touchscreen-size-y:0=640", + <&touch>, "touchscreen-inverted-y?", + <&touch>, "touchscreen-swapped-x-y?"; + 3_4_inch = <&panel>, "compatible=waveshare,3.4inch-panel", + <&touch>, "touchscreen-size-x:0=800", + <&touch>, "touchscreen-size-y:0=800"; + 4_0_inch = <&panel>, "compatible=waveshare,4.0inch-panel", + <&touch>, "touchscreen-size-x:0=800", + <&touch>, "touchscreen-size-y:0=480", + <&touch>, "touchscreen-inverted-x?", + <&touch>, "touchscreen-swapped-x-y?"; + 7_0_inchC = <&panel>, "compatible=waveshare,7.0inch-c-panel", + <&touch>, "touchscreen-size-x:0=800", + <&touch>, "touchscreen-size-y:0=480"; + 7_9_inch = <&panel>, "compatible=waveshare,7.9inch-panel", + <&touch>, "touchscreen-size-x:0=4096", + <&touch>, "touchscreen-size-y:0=4096", + <&touch>, "touchscreen-inverted-x?", + <&touch>, "touchscreen-swapped-x-y?"; + 8_0_inch = <&panel>, "compatible=waveshare,8.0inch-panel", + <&touch>, "touchscreen-size-x:0=800", + <&touch>, "touchscreen-size-y:0=1280", + <&touch>, "touchscreen-inverted-x?", + <&touch>, "touchscreen-swapped-x-y?"; + 10_1_inch = <&panel>, "compatible=waveshare,10.1inch-panel", + <&touch>, "touchscreen-size-x:0=800", + <&touch>, "touchscreen-size-y:0=1280", + <&touch>, "touchscreen-inverted-x?", + <&touch>, "touchscreen-swapped-x-y?"; + 11_9_inch = <&panel>, "compatible=waveshare,11.9inch-panel", + <&touch>, "touchscreen-inverted-x?", + <&touch>, "touchscreen-swapped-x-y?"; + 4_0_inchC = <&panel>, "compatible=waveshare,4inch-panel", + <&touch>, "touchscreen-size-x:0=720", + <&touch>, "touchscreen-size-y:0=720"; + 5_0_inch = <&panel>, "compatible=waveshare,5.0inch-panel", + <&touch>, "touchscreen-inverted-x?", + <&touch>, "touchscreen-inverted-y?"; + 6_25_inch = <&panel>, "compatible=waveshare,6.25inch-panel", + <&touch>, "touchscreen-inverted-x?", + <&touch>, "touchscreen-inverted-y?"; + 8_8_inch = <&panel>, "compatible=waveshare,8.8inch-panel"; + 13_3_inch_4lane = <&panel>, "compatible=waveshare,13.3inch-4lane-panel", + <&touch2>, "status=okay"; + 13_3_inch_2lane = <&panel>, "compatible=waveshare,13.3inch-2lane-panel", + <&touch2>, "status=okay"; + i2c1 = <&i2c_frag>, "target:0=",<&i2c1>, + <0>, "-3-4+5"; + disable_touch = <&touch>, "status=disabled"; + rotation = <&panel>, "rotation:0"; + invx = <&touch>,"touchscreen-inverted-x?"; + invy = <&touch>,"touchscreen-inverted-y?"; + swapxy = <&touch>,"touchscreen-swapped-x-y?"; + dsi0 = <&dsi_frag>, "target:0=",<&dsi0>, + <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>; + }; +}; diff --git a/arch/arm/boot/dts/overlays/vc4-kms-kippah-7inch-overlay.dts b/arch/arm/boot/dts/overlays/vc4-kms-kippah-7inch-overlay.dts new file mode 100644 index 00000000000000..4c1aa1c7015899 --- /dev/null +++ b/arch/arm/boot/dts/overlays/vc4-kms-kippah-7inch-overlay.dts @@ -0,0 +1,26 @@ +/* + * vc4-kms-kippah-7inch-overlay.dts + */ + +/dts-v1/; +/plugin/; + +#include "vc4-kms-dpi.dtsi" + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&panel>; + __overlay__ { + compatible = "ontat,yx700wv03", "simple-panel"; + }; + }; + + fragment@1 { + target = <&dpi>; + __overlay__ { + pinctrl-0 = <&dpi_18bit_gpio0>; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/vc4-kms-v3d-overlay.dts b/arch/arm/boot/dts/overlays/vc4-kms-v3d-overlay.dts new file mode 100644 index 00000000000000..f0b531b18dfced --- /dev/null +++ b/arch/arm/boot/dts/overlays/vc4-kms-v3d-overlay.dts @@ -0,0 +1,121 @@ +/* + * vc4-kms-v3d-overlay.dts + */ + +#include <dt-bindings/clock/bcm2835.h> + +#include "cma-overlay.dts" + +/ { + compatible = "brcm,bcm2835"; + + fragment@1 { + target = <&i2c2>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@2 { + target = <&fb>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@3 { + target = <&pixelvalve0>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@4 { + target = <&pixelvalve1>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@5 { + target = <&pixelvalve2>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@6 { + target = <&hvs>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@7 { + target = <&hdmi>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@8 { + target = <&v3d>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@9 { + target = <&vc4>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@10 { + target = <&clocks>; + __overlay__ { + claim-clocks = < + BCM2835_PLLD_DSI0 + BCM2835_PLLD_DSI1 + BCM2835_PLLH_AUX + BCM2835_PLLH_PIX + >; + }; + }; + + fragment@11 { + target = <&vec>; + __dormant__ { + status = "okay"; + }; + }; + + fragment@12 { + target = <&txp>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@13 { + target = <&hdmi>; + __dormant__ { + dmas; + }; + }; + + fragment@14 { + target = <&chosen>; + __overlay__ { + bootargs = "snd_bcm2835.enable_hdmi=0"; + }; + }; + + __overrides__ { + audio = <0>,"!13"; + noaudio = <0>,"=13"; + composite = <0>, "=11"; + nohdmi = <0>, "-1-7"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/vc4-kms-v3d-pi4-overlay.dts b/arch/arm/boot/dts/overlays/vc4-kms-v3d-pi4-overlay.dts new file mode 100644 index 00000000000000..02bbad743d772c --- /dev/null +++ b/arch/arm/boot/dts/overlays/vc4-kms-v3d-pi4-overlay.dts @@ -0,0 +1,197 @@ +/* + * vc4-kms-v3d-pi4-overlay.dts + */ + +#include <dt-bindings/clock/bcm2835.h> + +#include "cma-overlay.dts" + +&frag0 { + size = <((512-4)*1024*1024)>; +}; + +/ { + compatible = "brcm,bcm2711"; + + fragment@1 { + target = <&ddc0>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@2 { + target = <&ddc1>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@3 { + target = <&hdmi0>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@4 { + target = <&hdmi1>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@5 { + target = <&hvs>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@6 { + target = <&pixelvalve0>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@7 { + target = <&pixelvalve1>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@8 { + target = <&pixelvalve2>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@9 { + target = <&pixelvalve3>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@10 { + target = <&pixelvalve4>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@11 { + target = <&v3d>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@12 { + target = <&vc4>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@13 { + target = <&txp>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@14 { + target = <&fb>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@15 { + target = <&firmwarekms>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@16 { + target = <&vec>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@17 { + target = <&hdmi0>; + __dormant__ { + dmas; + }; + }; + + fragment@18 { + target = <&hdmi1>; + __dormant__ { + dmas; + }; + }; + + fragment@19 { + target-path = "/chosen"; + __overlay__ { + bootargs = "snd_bcm2835.enable_hdmi=0"; + }; + }; + + fragment@20 { + target = <&dvp>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@21 { + target = <&pixelvalve3>; + __dormant__ { + status = "okay"; + }; + }; + + fragment@22 { + target = <&vec>; + __dormant__ { + status = "okay"; + }; + }; + + fragment@23 { + target = <&aon_intr>; + __overlay__ { + status = "okay"; + }; + }; + + __overrides__ { + audio = <0>,"!17"; + audio1 = <0>,"!18"; + noaudio = <0>,"=17", <0>,"=18"; + composite = <0>, "!1", + <0>, "!2", + <0>, "!3", + <0>, "!4", + <0>, "!6", + <0>, "!7", + <0>, "!8", + <0>, "!9", + <0>, "!10", + <0>, "!16", + <0>, "=21", + <0>, "=22"; + nohdmi0 = <0>, "-1-3-8"; + nohdmi1 = <0>, "-2-4-10"; + nohdmi = <0>, "-1-2-3-4-8-10"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/vc4-kms-v3d-pi5-overlay.dts b/arch/arm/boot/dts/overlays/vc4-kms-v3d-pi5-overlay.dts new file mode 100644 index 00000000000000..f887818ef5e117 --- /dev/null +++ b/arch/arm/boot/dts/overlays/vc4-kms-v3d-pi5-overlay.dts @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include "cma-overlay.dts" + +&frag0 { + size = <(64*1024*1024)>; +}; + +/ { + compatible = "brcm,bcm2712"; + + fragment@1 { + target = <&fb>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@2 { + target = <&aon_intr>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@3 { + target = <&ddc0>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@4 { + target = <&ddc1>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@5 { + target = <&hdmi0>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@6 { + target = <&hdmi1>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@7 { + target = <&hvs>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@8 { + target = <&mop>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@9 { + target = <&moplet>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@10 { + target = <&pixelvalve0>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@11 { + target = <&pixelvalve1>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@12 { + target = <&v3d>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@13 { + target = <&vec>; + frag13: __overlay__ { + status = "disabled"; + }; + }; + + fragment@14 { + target = <&hdmi0>; + __dormant__ { + dmas; + }; + }; + + fragment@15 { + target = <&hdmi1>; + __dormant__ { + dmas; + }; + }; + + fragment@16 { + target = <&disp_intr>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@17 { + target = <&vc4>; + __overlay__ { + status = "okay"; + /* IOMMU attaches here, where we allocate DMA buffers */ + iommus = <&iommu4>; + }; + }; + + __overrides__ { + audio = <0>,"!14"; + audio1 = <0>,"!15"; + noaudio = <0>,"=14", <0>,"=15"; + composite = <0>, "!3", + <0>, "!4", + <0>, "!5", + <0>, "!6", + <0>, "!10", + <0>, "!11", + <&frag13>, "status"; + nohdmi0 = <0>, "-3-5-10"; + nohdmi1 = <0>, "-4-6-11"; + nohdmi = <0>, "-3-4-5-6-10-11"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/vc4-kms-vga666-overlay.dts b/arch/arm/boot/dts/overlays/vc4-kms-vga666-overlay.dts new file mode 100644 index 00000000000000..c3a682d5b7d9e9 --- /dev/null +++ b/arch/arm/boot/dts/overlays/vc4-kms-vga666-overlay.dts @@ -0,0 +1,107 @@ +/* + * vc4-kms-vga666-overlay.dts + * Configures a FenLogic or similar VGA666 DPI adapter when using the + * vc4-kms-v3d driver. + * If a suitable I2C level shifter is connected to GPIOs 0&1 and the VGA + * ID1/SDA (pin 12) and ID3/SCL (pin 15) lines, then there is the option to + * enable reading the EDID from the display. + */ + +/dts-v1/; +/plugin/; + +#include <dt-bindings/pinctrl/bcm2835.h> + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target-path = "/"; + __overlay__ { + vga_connector: vga_connector { + compatible = "vga-connector"; + label = "vga"; + + port { + vga_con_in: endpoint { + remote-endpoint = <&vga666_out>; + }; + }; + }; + + vga_dac { + compatible = "dumb-vga-dac"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + vga666_in: endpoint { + remote-endpoint = <&dpi_out>; + }; + }; + + port@1 { + reg = <1>; + + vga666_out: endpoint { + remote-endpoint = <&vga_con_in>; + }; + }; + }; + }; + + }; + }; + + fragment@1 { + target = <&dpi>; + __overlay__ { + status = "okay"; + + pinctrl-names = "default"; + pinctrl-0 = <&dpi_18bit_gpio2>; + + port { + dpi_out: endpoint@0 { + remote-endpoint = <&vga666_in>; + }; + }; + }; + }; + + fragment@2 { + target = <&vga_connector>; + __dormant__ { + ddc-i2c-bus = <&i2c_vc>; + }; + }; + + fragment@3 { + target = <&i2c0if>; + __dormant__ { + status = "okay"; + }; + }; + + fragment@4 { + target = <&i2c0mux>; + __dormant__ { + status = "okay"; + }; + }; + + fragment@5 { + target = <&i2c_vc>; + __dormant__ { + status = "okay"; + }; + }; + + __overrides__ { + ddc = <0>,"=2", <0>,"=3", <0>,"=4", <0>,"=5"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/vga666-overlay.dts b/arch/arm/boot/dts/overlays/vga666-overlay.dts new file mode 100644 index 00000000000000..a4968d180a5d05 --- /dev/null +++ b/arch/arm/boot/dts/overlays/vga666-overlay.dts @@ -0,0 +1,30 @@ +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2835"; + + // There is no VGA driver module, but we need a platform device + // node (that doesn't already use pinctrl) to hang the pinctrl + // reference on - leds will do + + fragment@0 { + target = <&leds>; + __overlay__ { + pinctrl-names = "default"; + pinctrl-0 = <&vga666_pins>; + }; + }; + + fragment@1 { + target = <&gpio>; + __overlay__ { + vga666_pins: vga666_pins { + brcm,pins = <2 3 4 5 6 7 8 9 10 11 12 + 13 14 15 16 17 18 19 20 21>; + brcm,function = <6>; /* alt2 */ + brcm,pull = <0>; /* no pull */ + }; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/vl805-overlay.dts b/arch/arm/boot/dts/overlays/vl805-overlay.dts new file mode 100644 index 00000000000000..81adf34b29f24b --- /dev/null +++ b/arch/arm/boot/dts/overlays/vl805-overlay.dts @@ -0,0 +1,18 @@ +/dts-v1/; +/plugin/; + +#include <dt-bindings/reset/raspberrypi,firmware-reset.h> + +/ { + compatible = "brcm,bcm2711"; + + fragment@0 { + target-path = "pcie0/pci@0,0"; + __overlay__ { + usb@0,0 { + reg = <0 0 0 0 0>; + resets = <&reset RASPBERRYPI_FIRMWARE_RESET_ID_USB>; + }; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/w1-gpio-overlay.dts b/arch/arm/boot/dts/overlays/w1-gpio-overlay.dts new file mode 100644 index 00000000000000..f44e325bc1f2ed --- /dev/null +++ b/arch/arm/boot/dts/overlays/w1-gpio-overlay.dts @@ -0,0 +1,40 @@ +// Definitions for w1-gpio module (without external pullup) +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target-path = "/"; + __overlay__ { + + w1: onewire@0 { + compatible = "w1-gpio"; + pinctrl-names = "default"; + pinctrl-0 = <&w1_pins>; + gpios = <&gpio 4 0>; + status = "okay"; + }; + }; + }; + + fragment@1 { + target = <&gpio>; + __overlay__ { + w1_pins: w1_pins@0 { + brcm,pins = <4>; + brcm,function = <0>; // in (initially) + brcm,pull = <0>; // off + }; + }; + }; + + __overrides__ { + gpiopin = <&w1>,"gpios:4", + <&w1>,"reg:0", + <&w1_pins>,"brcm,pins:0", + <&w1_pins>,"reg:0"; + pullup; // Silently ignore unneeded parameter + }; +}; diff --git a/arch/arm/boot/dts/overlays/w1-gpio-pi5-overlay.dts b/arch/arm/boot/dts/overlays/w1-gpio-pi5-overlay.dts new file mode 100644 index 00000000000000..36f60548a7c8d5 --- /dev/null +++ b/arch/arm/boot/dts/overlays/w1-gpio-pi5-overlay.dts @@ -0,0 +1,12 @@ +#include "w1-gpio-overlay.dts" + +/ { + compatible = "brcm,bcm2712"; + + fragment@2 { + target = <&w1>; + __overlay__ { + raspberrypi,delay-needs-poll; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/w1-gpio-pullup-overlay.dts b/arch/arm/boot/dts/overlays/w1-gpio-pullup-overlay.dts new file mode 100644 index 00000000000000..953c6a1aeab978 --- /dev/null +++ b/arch/arm/boot/dts/overlays/w1-gpio-pullup-overlay.dts @@ -0,0 +1,42 @@ +// Definitions for w1-gpio module (with external pullup) +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target-path = "/"; + __overlay__ { + + w1: onewire@0 { + compatible = "w1-gpio"; + pinctrl-names = "default"; + pinctrl-0 = <&w1_pins>; + gpios = <&gpio 4 0>, <&gpio 5 1>; + status = "okay"; + }; + }; + }; + + fragment@1 { + target = <&gpio>; + __overlay__ { + w1_pins: w1_pins@0 { + brcm,pins = <4 5>; + brcm,function = <0 1>; // in out + brcm,pull = <0 0>; // off off + }; + }; + }; + + __overrides__ { + gpiopin = <&w1>,"gpios:4", + <&w1>,"reg:0", + <&w1_pins>,"brcm,pins:0", + <&w1_pins>,"reg:0"; + extpullup = <&w1>,"gpios:16", + <&w1_pins>,"brcm,pins:4"; + pullup; // Silently ignore unneeded parameter + }; +}; diff --git a/arch/arm/boot/dts/overlays/w1-gpio-pullup-pi5-overlay.dts b/arch/arm/boot/dts/overlays/w1-gpio-pullup-pi5-overlay.dts new file mode 100644 index 00000000000000..bca44070cbacb1 --- /dev/null +++ b/arch/arm/boot/dts/overlays/w1-gpio-pullup-pi5-overlay.dts @@ -0,0 +1,12 @@ +#include "w1-gpio-pullup-overlay.dts" + +/ { + compatible = "brcm,bcm2712"; + + fragment@2 { + target = <&w1>; + __overlay__ { + raspberrypi,delay-needs-poll; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/w5500-overlay.dts b/arch/arm/boot/dts/overlays/w5500-overlay.dts new file mode 100644 index 00000000000000..4d3e6629675308 --- /dev/null +++ b/arch/arm/boot/dts/overlays/w5500-overlay.dts @@ -0,0 +1,63 @@ +// Overlay for the Wiznet w5500 Ethernet Controller +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@1 { + target = <&spidev1>; + __dormant__ { + status = "disabled"; + }; + }; + + fragment@2 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + status = "okay"; + + eth1: w5500@0{ + compatible = "wiznet,w5500"; + reg = <0>; /* CE0 */ + pinctrl-names = "default"; + pinctrl-0 = <ð1_pins>; + interrupt-parent = <&gpio>; + interrupts = <25 0x8>; + spi-max-frequency = <30000000>; +// local-mac-address = [aa bb cc dd ee ff]; + status = "okay"; + }; + }; + }; + + fragment@3 { + target = <&gpio>; + __overlay__ { + eth1_pins: eth1_pins { + brcm,pins = <25>; + brcm,function = <0>; /* in */ + brcm,pull = <0>; /* none */ + }; + }; + }; + + __overrides__ { + int_pin = <ð1>, "interrupts:0", + <ð1_pins>, "brcm,pins:0"; + speed = <ð1>, "spi-max-frequency:0"; + cs = <ð1>, "reg:0", + <0>, "!0=1"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/watterott-display-overlay.dts b/arch/arm/boot/dts/overlays/watterott-display-overlay.dts new file mode 100644 index 00000000000000..4388706d2c386c --- /dev/null +++ b/arch/arm/boot/dts/overlays/watterott-display-overlay.dts @@ -0,0 +1,150 @@ +/* + * Device Tree overlay for rpi-display by Watterott + * + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spi0>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@2 { + target = <&spidev1>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@3 { + target = <&gpio>; + __overlay__ { + rpi_display_pins: rpi_display_pins { + brcm,pins = <18 23 24 25>; + brcm,function = <1 1 1 0>; /* out out out in */ + brcm,pull = <0 0 0 2>; /* - - - up */ + }; + }; + }; + + fragment@4 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + rpidisplay: rpi-display@0{ + compatible = "ilitek,ili9341"; + reg = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&rpi_display_pins>; + + spi-max-frequency = <32000000>; + rotate = <270>; + bgr; + fps = <30>; + buswidth = <8>; + reset-gpios = <&gpio 23 1>; + dc-gpios = <&gpio 24 0>; + led-gpios = <&gpio 18 0>; + debug = <0>; + }; + + rpidisplay_ts: rpi-display-ts@1 { + compatible = "ti,ads7846"; + reg = <1>; + + spi-max-frequency = <2000000>; + interrupts = <25 2>; /* high-to-low edge triggered */ + interrupt-parent = <&gpio>; + pendown-gpio = <&gpio 25 1>; + ti,x-plate-ohms = /bits/ 16 <60>; + ti,pressure-max = /bits/ 16 <255>; + }; + }; + }; + + fragment@10 { + target = <&rpidisplay>; + __dormant__ { + backlight = <&backlight_gpio>; + }; + }; + + fragment@11 { + target-path = "/"; + __dormant__ { + backlight_gpio: backlight_gpio { + compatible = "gpio-backlight"; + gpios = <&gpio 18 0>; /* GPIO_ACTIVE_HIGH */ + }; + }; + }; + + fragment@20 { + target = <&rpidisplay>; + __dormant__ { + backlight = <&backlight_pwm>; + }; + }; + + fragment@21 { + target-path = "/"; + __dormant__ { + backlight_pwm: backlight_pwm { + compatible = "pwm-backlight"; + brightness-levels = <0 6 8 12 16 24 32 40 48 64 96 128 160 192 224 255>; + default-brightness-level = <16>; + pwms = <&pwm 0 200000 0>; + }; + }; + }; + + fragment@22 { + target = <&pwm>; + __dormant__ { + assigned-clock-rates = <1000000>; + status = "okay"; + }; + }; + + fragment@23 { + target = <&chosen>; + __dormant__ { + bootargs = "snd_bcm2835.enable_headphones=0"; + }; + }; + + __overrides__ { + speed = <&rpidisplay>,"spi-max-frequency:0"; + rotate = <&rpidisplay>,"rotate:0", /* fbtft */ + <&rpidisplay>,"rotation:0"; /* drm */ + fps = <&rpidisplay>,"fps:0"; + debug = <&rpidisplay>,"debug:0"; + xohms = <&rpidisplay_ts>,"ti,x-plate-ohms;0"; + swapxy = <&rpidisplay_ts>,"ti,swap-xy?"; + backlight = <&rpidisplay>,"led-gpios:4", + <&rpi_display_pins>,"brcm,pins:0"; + drm = <&rpidisplay>, "compatible=multi-inno,mi0283qt", + <&rpidisplay>, "spi-max-frequency:0=70000000", + <&rpidisplay>, "reset-gpios:8=0", /* GPIO_ACTIVE_HIGH */ + <0>, "+10+11"; + backlight-pwm = <0>, "-10-11+20+21+22+23", + <&rpi_display_pins>, "brcm,function:0=2"; /* Alt5 */ + }; +}; diff --git a/arch/arm/boot/dts/overlays/waveshare-can-fd-hat-mode-a-overlay.dts b/arch/arm/boot/dts/overlays/waveshare-can-fd-hat-mode-a-overlay.dts new file mode 100644 index 00000000000000..59388cc3b0b91f --- /dev/null +++ b/arch/arm/boot/dts/overlays/waveshare-can-fd-hat-mode-a-overlay.dts @@ -0,0 +1,140 @@ +// redo: ovmerge -c spi1-1cs-overlay.dts,cs0_pin=26,cs0_spidev=false mcp251xfd-overlay.dts,spi0-0,interrupt=25 mcp251xfd-overlay.dts,spi1-0,interrupt=16 + +// Device tree overlay for https://www.waveshare.com/2-ch-can-fd-hat.htm +// in "Mode A" (default) configuration +// for details see https://www.waveshare.com/wiki/2-CH_CAN_FD_HAT + +/dts-v1/; +/plugin/; + +#include <dt-bindings/gpio/gpio.h> +#include <dt-bindings/interrupt-controller/irq.h> +#include <dt-bindings/pinctrl/bcm2835.h> + +/ { + compatible = "brcm,bcm2835"; + fragment@0 { + target = <&gpio>; + __overlay__ { + spi1_pins: spi1_pins { + brcm,pins = <19 20 21>; + brcm,function = <3>; + }; + spi1_cs_pins: spi1_cs_pins { + brcm,pins = <26>; + brcm,function = <1>; + }; + }; + }; + fragment@1 { + target = <&spi1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&spi1_pins &spi1_cs_pins>; + cs-gpios = <&gpio 26 1>; + status = "okay"; + spidev@0 { + compatible = "spidev"; + reg = <0>; + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + status = "disabled"; + }; + }; + }; + fragment@2 { + target = <&aux>; + __overlay__ { + status = "okay"; + }; + }; + fragment@3 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + fragment@4 { + target = <&gpio>; + __overlay__ { + mcp251xfd_pins: mcp251xfd_spi0_0_pins { + brcm,pins = <25>; + brcm,function = <BCM2835_FSEL_GPIO_IN>; + }; + }; + }; + fragment@5 { + target-path = "/clocks"; + __overlay__ { + clk_mcp251xfd_osc: mcp251xfd-spi0-0-osc { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <40000000>; + }; + }; + }; + fragment@6 { + target = <&spi0>; + __overlay__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + mcp251xfd@0 { + compatible = "microchip,mcp251xfd"; + reg = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&mcp251xfd_pins>; + spi-max-frequency = <20000000>; + interrupt-parent = <&gpio>; + interrupts = <25 IRQ_TYPE_LEVEL_LOW>; + clocks = <&clk_mcp251xfd_osc>; + }; + }; + }; + fragment@7 { + target-path = "spi1/spidev@0"; + __overlay__ { + status = "disabled"; + }; + }; + fragment@8 { + target = <&gpio>; + __overlay__ { + mcp251xfd_pins_1: mcp251xfd_spi1_0_pins { + brcm,pins = <16>; + brcm,function = <BCM2835_FSEL_GPIO_IN>; + }; + }; + }; + fragment@9 { + target-path = "/clocks"; + __overlay__ { + clk_mcp251xfd_osc_1: mcp251xfd-spi1-0-osc { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <40000000>; + }; + }; + }; + fragment@10 { + target = <&spi1>; + __overlay__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + mcp251xfd@0 { + compatible = "microchip,mcp251xfd"; + reg = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&mcp251xfd_pins_1>; + spi-max-frequency = <20000000>; + interrupt-parent = <&gpio>; + interrupts = <16 IRQ_TYPE_LEVEL_LOW>; + clocks = <&clk_mcp251xfd_osc_1>; + }; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/waveshare-can-fd-hat-mode-b-overlay.dts b/arch/arm/boot/dts/overlays/waveshare-can-fd-hat-mode-b-overlay.dts new file mode 100644 index 00000000000000..b2504922c8de13 --- /dev/null +++ b/arch/arm/boot/dts/overlays/waveshare-can-fd-hat-mode-b-overlay.dts @@ -0,0 +1,103 @@ +// redo: ovmerge -c mcp251xfd-overlay.dts,spi0-0,interrupt=25 mcp251xfd-overlay.dts,spi0-1,interrupt=16 + +// Device tree overlay for https://www.waveshare.com/2-ch-can-fd-hat.htm +// in "Mode B" (requried hardware modification) configuration +// for details see https://www.waveshare.com/wiki/2-CH_CAN_FD_HAT + + +/dts-v1/; +/plugin/; + +#include <dt-bindings/gpio/gpio.h> +#include <dt-bindings/interrupt-controller/irq.h> +#include <dt-bindings/pinctrl/bcm2835.h> + +/ { + compatible = "brcm,bcm2835"; + fragment@0 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + fragment@1 { + target = <&gpio>; + __overlay__ { + mcp251xfd_pins: mcp251xfd_spi0_0_pins { + brcm,pins = <25>; + brcm,function = <BCM2835_FSEL_GPIO_IN>; + }; + }; + }; + fragment@2 { + target-path = "/clocks"; + __overlay__ { + clk_mcp251xfd_osc: mcp251xfd-spi0-0-osc { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <40000000>; + }; + }; + }; + fragment@3 { + target = <&spi0>; + __overlay__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + mcp251xfd@0 { + compatible = "microchip,mcp251xfd"; + reg = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&mcp251xfd_pins>; + spi-max-frequency = <20000000>; + interrupt-parent = <&gpio>; + interrupts = <25 IRQ_TYPE_LEVEL_LOW>; + clocks = <&clk_mcp251xfd_osc>; + }; + }; + }; + fragment@4 { + target = <&spidev1>; + __overlay__ { + status = "disabled"; + }; + }; + fragment@5 { + target = <&gpio>; + __overlay__ { + mcp251xfd_pins_1: mcp251xfd_spi0_1_pins { + brcm,pins = <16>; + brcm,function = <BCM2835_FSEL_GPIO_IN>; + }; + }; + }; + fragment@6 { + target-path = "/clocks"; + __overlay__ { + clk_mcp251xfd_osc_1: mcp251xfd-spi0-1-osc { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <40000000>; + }; + }; + }; + fragment@7 { + target = <&spi0>; + __overlay__ { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + mcp251xfd@1 { + compatible = "microchip,mcp251xfd"; + reg = <1>; + pinctrl-names = "default"; + pinctrl-0 = <&mcp251xfd_pins_1>; + spi-max-frequency = <20000000>; + interrupt-parent = <&gpio>; + interrupts = <16 IRQ_TYPE_LEVEL_LOW>; + clocks = <&clk_mcp251xfd_osc_1>; + }; + }; + }; +}; diff --git a/arch/arm/boot/dts/overlays/wittypi-overlay.dts b/arch/arm/boot/dts/overlays/wittypi-overlay.dts new file mode 100644 index 00000000000000..71ce806186deb6 --- /dev/null +++ b/arch/arm/boot/dts/overlays/wittypi-overlay.dts @@ -0,0 +1,44 @@ +/* + * Device Tree overlay for Witty Pi extension board by UUGear + * + */ + +/dts-v1/; +/plugin/; + +/ { + + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&leds>; + __overlay__ { + compatible = "gpio-leds"; + wittypi_led: wittypi_led { + label = "wittypi_led"; + linux,default-trigger = "default-on"; + gpios = <&gpio 17 0>; + }; + }; + }; + + fragment@1 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + + rtc: ds1337@68 { + compatible = "dallas,ds1337"; + reg = <0x68>; + wakeup-source; + }; + }; + }; + + __overrides__ { + led_gpio = <&wittypi_led>,"gpios:4"; + led_trigger = <&wittypi_led>,"linux,default-trigger"; + }; + +}; diff --git a/arch/arm/boot/dts/overlays/wm8960-soundcard-overlay.dts b/arch/arm/boot/dts/overlays/wm8960-soundcard-overlay.dts new file mode 100644 index 00000000000000..d896c59f469b95 --- /dev/null +++ b/arch/arm/boot/dts/overlays/wm8960-soundcard-overlay.dts @@ -0,0 +1,82 @@ +// Definitions for Waveshare WM8960 https://github.com/waveshare/WM8960-Audio-HAT +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2s_clk_producer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target-path = "/"; + __overlay__ { + wm8960_mclk: wm8960_mclk { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <12288000>; + }; + }; + }; + fragment@2 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + wm8960: wm8960@1a { + compatible = "wlf,wm8960"; + reg = <0x1a>; + #sound-dai-cells = <0>; + AVDD-supply = <&vdd_5v0_reg>; + DVDD-supply = <&vdd_3v3_reg>; + clocks = <&wm8960_mclk>; + clock-names = "mclk"; + }; + }; + }; + + fragment@3 { + target = <&sound>; + slave_overlay: __overlay__ { + compatible = "simple-audio-card"; + simple-audio-card,format = "i2s"; + simple-audio-card,name = "wm8960-soundcard"; + status = "okay"; + + simple-audio-card,widgets = + "Microphone", "Mic Jack", + "Line", "Line In", + "Line", "Line Out", + "Speaker", "Speaker", + "Headphone", "Headphone Jack"; + simple-audio-card,routing = + "Headphone Jack", "HP_L", + "Headphone Jack", "HP_R", + "Speaker", "SPK_LP", + "Speaker", "SPK_LN", + "LINPUT1", "Mic Jack", + "LINPUT3", "Mic Jack", + "RINPUT1", "Mic Jack", + "RINPUT2", "Mic Jack"; + + simple-audio-card,cpu { + sound-dai = <&i2s_clk_producer>; + }; + + dailink0_slave: simple-audio-card,codec { + sound-dai = <&wm8960>; + }; + }; + }; + + __overrides__ { + alsaname = <&slave_overlay>,"simple-audio-card,name"; + compatible = <&wm8960>,"compatible"; + }; +}; diff --git a/arch/arm/boot/dts/overlays/ws2812-pio-overlay.dts b/arch/arm/boot/dts/overlays/ws2812-pio-overlay.dts new file mode 100644 index 00000000000000..4550e16d44e1c7 --- /dev/null +++ b/arch/arm/boot/dts/overlays/ws2812-pio-overlay.dts @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-2.0 +// Device tree overlay for RP1 PIO WS2812 driver. +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2712"; + + fragment@0 { + target = <&gpio>; + __overlay__ { + ws2812_pio_pins: ws2812_pio_pins@4 { + brcm,pins = <4>; /* gpio 4 */ + function = "pio"; + bias-disable; + }; + }; + }; + + fragment@1 { + target-path = "/"; + __overlay__ { + ws2812_pio: ws2812_pio@4 { + compatible = "raspberrypi,ws2812-pio-rp1"; + pinctrl-names = "default"; + pinctrl-0 = <&ws2812_pio_pins>; + dev-name = "leds%d"; + leds-gpios = <&gpio 4 0>; + rpi,num-leds = <60>; + rpi,brightness = <255>; + }; + }; + }; + + __overrides__ { + brightness = <&ws2812_pio>, "rpi,brightness:0"; + dev_name = <&ws2812_pio>, "dev-name"; + gpio = <&ws2812_pio>,"leds-gpios:4", + <&ws2812_pio_pins>,"brcm,pins:0", + /* modify reg values to allow multiple instantiation */ + <&ws2812_pio>,"reg:0", + <&ws2812_pio_pins>,"reg:0"; + num_leds = <&ws2812_pio>, "rpi,num-leds:0"; + rgbw = <&ws2812_pio>, "rpi,rgbw?"; + }; +}; diff --git a/arch/arm/configs/bcm2709_defconfig b/arch/arm/configs/bcm2709_defconfig new file mode 100644 index 00000000000000..1a11b88f460102 --- /dev/null +++ b/arch/arm/configs/bcm2709_defconfig @@ -0,0 +1,1617 @@ +CONFIG_LOCALVERSION="-v7" +# CONFIG_LOCALVERSION_AUTO is not set +CONFIG_SYSVIPC=y +CONFIG_POSIX_MQUEUE=y +CONFIG_GENERIC_IRQ_DEBUGFS=y +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_BPF_SYSCALL=y +CONFIG_PREEMPT_VOLUNTARY=y +CONFIG_BSD_PROCESS_ACCT=y +CONFIG_BSD_PROCESS_ACCT_V3=y +CONFIG_TASKSTATS=y +CONFIG_TASK_DELAY_ACCT=y +CONFIG_TASK_XACCT=y +CONFIG_TASK_IO_ACCOUNTING=y +CONFIG_PSI=y +CONFIG_PSI_DEFAULT_DISABLED=y +CONFIG_IKCONFIG=m +CONFIG_IKCONFIG_PROC=y +CONFIG_MEMCG=y +CONFIG_BLK_CGROUP=y +CONFIG_CFS_BANDWIDTH=y +CONFIG_CGROUP_PIDS=y +CONFIG_CGROUP_FREEZER=y +CONFIG_CPUSETS=y +CONFIG_CPUSETS_V1=y +CONFIG_CGROUP_DEVICE=y +CONFIG_CGROUP_CPUACCT=y +CONFIG_CGROUP_PERF=y +CONFIG_CGROUP_BPF=y +CONFIG_NAMESPACES=y +CONFIG_USER_NS=y +CONFIG_SCHED_AUTOGROUP=y +CONFIG_BLK_DEV_INITRD=y +CONFIG_EXPERT=y +CONFIG_PROFILING=y +CONFIG_ARCH_BCM=y +CONFIG_ARCH_BCM2835=y +# CONFIG_CACHE_L2X0 is not set +CONFIG_SMP=y +CONFIG_VMSPLIT_2G=y +CONFIG_UACCESS_WITH_MEMCPY=y +# CONFIG_ATAGS is not set +CONFIG_CMDLINE="console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 root=/dev/mmcblk0p2 rootfstype=ext4 rootwait" +CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_STAT=y +CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND=y +CONFIG_CPU_FREQ_GOV_POWERSAVE=y +CONFIG_CPU_FREQ_GOV_USERSPACE=y +CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y +CONFIG_CPU_FREQ_GOV_SCHEDUTIL=y +CONFIG_CPUFREQ_DT=y +CONFIG_ARM_RASPBERRYPI_CPUFREQ=y +CONFIG_VFP=y +CONFIG_NEON=y +CONFIG_KERNEL_MODE_NEON=y +# CONFIG_SUSPEND is not set +CONFIG_PM=y +CONFIG_JUMP_LABEL=y +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_MODVERSIONS=y +CONFIG_MODULE_SRCVERSION_ALL=y +CONFIG_MODULE_COMPRESS=y +CONFIG_MODULE_COMPRESS_XZ=y +CONFIG_PARTITION_ADVANCED=y +CONFIG_MAC_PARTITION=y +CONFIG_BINFMT_MISC=m +CONFIG_ZSWAP=y +# CONFIG_COMPAT_BRK is not set +CONFIG_CMA=y +CONFIG_CMA_AREAS=7 +CONFIG_LRU_GEN=y +CONFIG_LRU_GEN_ENABLED=y +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_XFRM_USER=y +CONFIG_XFRM_INTERFACE=m +CONFIG_XFRM_SUB_POLICY=y +CONFIG_XFRM_STATISTICS=y +CONFIG_NET_KEY=m +CONFIG_IP_MULTICAST=y +CONFIG_IP_ADVANCED_ROUTER=y +CONFIG_IP_MULTIPLE_TABLES=y +CONFIG_IP_ROUTE_MULTIPATH=y +CONFIG_IP_ROUTE_VERBOSE=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y +CONFIG_IP_PNP_RARP=y +CONFIG_NET_IPIP=m +CONFIG_NET_IPGRE_DEMUX=m +CONFIG_NET_IPGRE=m +CONFIG_IP_MROUTE=y +CONFIG_IP_MROUTE_MULTIPLE_TABLES=y +CONFIG_IP_PIMSM_V1=y +CONFIG_IP_PIMSM_V2=y +CONFIG_NET_IPVTI=m +CONFIG_NET_FOU_IP_TUNNELS=y +CONFIG_INET_AH=m +CONFIG_INET_ESP=m +CONFIG_INET_IPCOMP=m +CONFIG_INET_DIAG=m +CONFIG_TCP_CONG_ADVANCED=y +CONFIG_TCP_CONG_BBR=m +CONFIG_IPV6=m +CONFIG_IPV6_ROUTER_PREF=y +CONFIG_IPV6_ROUTE_INFO=y +CONFIG_INET6_AH=m +CONFIG_INET6_ESP=m +CONFIG_INET6_ESP_OFFLOAD=m +CONFIG_INET6_IPCOMP=m +CONFIG_IPV6_ILA=m +CONFIG_IPV6_VTI=m +CONFIG_IPV6_SIT_6RD=y +CONFIG_IPV6_GRE=m +CONFIG_IPV6_MULTIPLE_TABLES=y +CONFIG_IPV6_SUBTREES=y +CONFIG_IPV6_MROUTE=y +CONFIG_IPV6_MROUTE_MULTIPLE_TABLES=y +CONFIG_IPV6_PIMSM_V2=y +CONFIG_NETFILTER=y +CONFIG_BRIDGE_NETFILTER=m +CONFIG_NF_CONNTRACK=m +CONFIG_NF_CONNTRACK_ZONES=y +CONFIG_NF_CONNTRACK_EVENTS=y +CONFIG_NF_CONNTRACK_TIMESTAMP=y +CONFIG_NF_CONNTRACK_AMANDA=m +CONFIG_NF_CONNTRACK_FTP=m +CONFIG_NF_CONNTRACK_H323=m +CONFIG_NF_CONNTRACK_IRC=m +CONFIG_NF_CONNTRACK_NETBIOS_NS=m +CONFIG_NF_CONNTRACK_SNMP=m +CONFIG_NF_CONNTRACK_PPTP=m +CONFIG_NF_CONNTRACK_SANE=m +CONFIG_NF_CONNTRACK_SIP=m +CONFIG_NF_CONNTRACK_TFTP=m +CONFIG_NF_CT_NETLINK=m +CONFIG_NF_TABLES=m +CONFIG_NF_TABLES_INET=y +CONFIG_NF_TABLES_NETDEV=y +CONFIG_NFT_NUMGEN=m +CONFIG_NFT_CT=m +CONFIG_NFT_FLOW_OFFLOAD=m +CONFIG_NFT_CONNLIMIT=m +CONFIG_NFT_LOG=m +CONFIG_NFT_LIMIT=m +CONFIG_NFT_MASQ=m +CONFIG_NFT_REDIR=m +CONFIG_NFT_NAT=m +CONFIG_NFT_TUNNEL=m +CONFIG_NFT_QUEUE=m +CONFIG_NFT_QUOTA=m +CONFIG_NFT_REJECT=m +CONFIG_NFT_COMPAT=m +CONFIG_NFT_HASH=m +CONFIG_NFT_FIB_INET=m +CONFIG_NFT_XFRM=m +CONFIG_NFT_SOCKET=m +CONFIG_NFT_OSF=m +CONFIG_NFT_TPROXY=m +CONFIG_NFT_SYNPROXY=m +CONFIG_NFT_DUP_NETDEV=m +CONFIG_NFT_FWD_NETDEV=m +CONFIG_NFT_FIB_NETDEV=m +CONFIG_NF_FLOW_TABLE_INET=m +CONFIG_NF_FLOW_TABLE=m +CONFIG_NETFILTER_XT_SET=m +CONFIG_NETFILTER_XT_TARGET_CHECKSUM=m +CONFIG_NETFILTER_XT_TARGET_CLASSIFY=m +CONFIG_NETFILTER_XT_TARGET_CONNMARK=m +CONFIG_NETFILTER_XT_TARGET_DSCP=m +CONFIG_NETFILTER_XT_TARGET_HMARK=m +CONFIG_NETFILTER_XT_TARGET_IDLETIMER=m +CONFIG_NETFILTER_XT_TARGET_LED=m +CONFIG_NETFILTER_XT_TARGET_LOG=m +CONFIG_NETFILTER_XT_TARGET_MARK=m +CONFIG_NETFILTER_XT_TARGET_NFLOG=m +CONFIG_NETFILTER_XT_TARGET_NFQUEUE=m +CONFIG_NETFILTER_XT_TARGET_NOTRACK=m +CONFIG_NETFILTER_XT_TARGET_TEE=m +CONFIG_NETFILTER_XT_TARGET_TPROXY=m +CONFIG_NETFILTER_XT_TARGET_TRACE=m +CONFIG_NETFILTER_XT_TARGET_TCPMSS=m +CONFIG_NETFILTER_XT_TARGET_TCPOPTSTRIP=m +CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=m +CONFIG_NETFILTER_XT_MATCH_BPF=m +CONFIG_NETFILTER_XT_MATCH_CLUSTER=m +CONFIG_NETFILTER_XT_MATCH_COMMENT=m +CONFIG_NETFILTER_XT_MATCH_CONNBYTES=m +CONFIG_NETFILTER_XT_MATCH_CONNLABEL=m +CONFIG_NETFILTER_XT_MATCH_CONNLIMIT=m +CONFIG_NETFILTER_XT_MATCH_CONNMARK=m +CONFIG_NETFILTER_XT_MATCH_CONNTRACK=m +CONFIG_NETFILTER_XT_MATCH_CPU=m +CONFIG_NETFILTER_XT_MATCH_DCCP=m +CONFIG_NETFILTER_XT_MATCH_DEVGROUP=m +CONFIG_NETFILTER_XT_MATCH_DSCP=m +CONFIG_NETFILTER_XT_MATCH_ESP=m +CONFIG_NETFILTER_XT_MATCH_HASHLIMIT=m +CONFIG_NETFILTER_XT_MATCH_HELPER=m +CONFIG_NETFILTER_XT_MATCH_IPRANGE=m +CONFIG_NETFILTER_XT_MATCH_IPVS=m +CONFIG_NETFILTER_XT_MATCH_LENGTH=m +CONFIG_NETFILTER_XT_MATCH_LIMIT=m +CONFIG_NETFILTER_XT_MATCH_MAC=m +CONFIG_NETFILTER_XT_MATCH_MARK=m +CONFIG_NETFILTER_XT_MATCH_MULTIPORT=m +CONFIG_NETFILTER_XT_MATCH_NFACCT=m +CONFIG_NETFILTER_XT_MATCH_OSF=m +CONFIG_NETFILTER_XT_MATCH_OWNER=m +CONFIG_NETFILTER_XT_MATCH_POLICY=m +CONFIG_NETFILTER_XT_MATCH_PHYSDEV=m +CONFIG_NETFILTER_XT_MATCH_PKTTYPE=m +CONFIG_NETFILTER_XT_MATCH_QUOTA=m +CONFIG_NETFILTER_XT_MATCH_RATEEST=m +CONFIG_NETFILTER_XT_MATCH_REALM=m +CONFIG_NETFILTER_XT_MATCH_RECENT=m +CONFIG_NETFILTER_XT_MATCH_SOCKET=m +CONFIG_NETFILTER_XT_MATCH_STATE=m +CONFIG_NETFILTER_XT_MATCH_STATISTIC=m +CONFIG_NETFILTER_XT_MATCH_STRING=m +CONFIG_NETFILTER_XT_MATCH_TCPMSS=m +CONFIG_NETFILTER_XT_MATCH_TIME=m +CONFIG_NETFILTER_XT_MATCH_U32=m +CONFIG_IP_SET=m +CONFIG_IP_SET_BITMAP_IP=m +CONFIG_IP_SET_BITMAP_IPMAC=m +CONFIG_IP_SET_BITMAP_PORT=m +CONFIG_IP_SET_HASH_IP=m +CONFIG_IP_SET_HASH_IPPORT=m +CONFIG_IP_SET_HASH_IPPORTIP=m +CONFIG_IP_SET_HASH_IPPORTNET=m +CONFIG_IP_SET_HASH_NET=m +CONFIG_IP_SET_HASH_NETPORT=m +CONFIG_IP_SET_HASH_NETIFACE=m +CONFIG_IP_SET_LIST_SET=m +CONFIG_IP_VS=m +CONFIG_IP_VS_IPV6=y +CONFIG_IP_VS_PROTO_TCP=y +CONFIG_IP_VS_PROTO_UDP=y +CONFIG_IP_VS_PROTO_ESP=y +CONFIG_IP_VS_PROTO_AH=y +CONFIG_IP_VS_PROTO_SCTP=y +CONFIG_IP_VS_RR=m +CONFIG_IP_VS_WRR=m +CONFIG_IP_VS_LC=m +CONFIG_IP_VS_WLC=m +CONFIG_IP_VS_LBLC=m +CONFIG_IP_VS_LBLCR=m +CONFIG_IP_VS_DH=m +CONFIG_IP_VS_SH=m +CONFIG_IP_VS_SED=m +CONFIG_IP_VS_NQ=m +CONFIG_IP_VS_FTP=m +CONFIG_IP_VS_PE_SIP=m +CONFIG_NFT_DUP_IPV4=m +CONFIG_NFT_FIB_IPV4=m +CONFIG_NF_TABLES_ARP=y +CONFIG_NF_LOG_ARP=m +CONFIG_NF_LOG_IPV4=m +CONFIG_IP_NF_IPTABLES=m +CONFIG_IP_NF_MATCH_AH=m +CONFIG_IP_NF_MATCH_ECN=m +CONFIG_IP_NF_MATCH_RPFILTER=m +CONFIG_IP_NF_MATCH_TTL=m +CONFIG_IP_NF_FILTER=m +CONFIG_IP_NF_TARGET_REJECT=m +CONFIG_IP_NF_TARGET_SYNPROXY=m +CONFIG_IP_NF_NAT=m +CONFIG_IP_NF_TARGET_MASQUERADE=m +CONFIG_IP_NF_TARGET_NETMAP=m +CONFIG_IP_NF_TARGET_REDIRECT=m +CONFIG_IP_NF_MANGLE=m +CONFIG_IP_NF_TARGET_ECN=m +CONFIG_IP_NF_TARGET_TTL=m +CONFIG_IP_NF_RAW=m +CONFIG_IP_NF_ARPFILTER=m +CONFIG_IP_NF_ARP_MANGLE=m +CONFIG_NFT_DUP_IPV6=m +CONFIG_NFT_FIB_IPV6=m +CONFIG_IP6_NF_IPTABLES=m +CONFIG_IP6_NF_MATCH_AH=m +CONFIG_IP6_NF_MATCH_EUI64=m +CONFIG_IP6_NF_MATCH_FRAG=m +CONFIG_IP6_NF_MATCH_OPTS=m +CONFIG_IP6_NF_MATCH_HL=m +CONFIG_IP6_NF_MATCH_IPV6HEADER=m +CONFIG_IP6_NF_MATCH_MH=m +CONFIG_IP6_NF_MATCH_RPFILTER=m +CONFIG_IP6_NF_MATCH_RT=m +CONFIG_IP6_NF_MATCH_SRH=m +CONFIG_IP6_NF_TARGET_HL=m +CONFIG_IP6_NF_FILTER=m +CONFIG_IP6_NF_TARGET_REJECT=m +CONFIG_IP6_NF_TARGET_SYNPROXY=m +CONFIG_IP6_NF_MANGLE=m +CONFIG_IP6_NF_RAW=m +CONFIG_IP6_NF_SECURITY=m +CONFIG_IP6_NF_NAT=m +CONFIG_IP6_NF_TARGET_MASQUERADE=m +CONFIG_IP6_NF_TARGET_NPT=m +CONFIG_NF_TABLES_BRIDGE=m +CONFIG_NFT_BRIDGE_REJECT=m +CONFIG_BRIDGE_NF_EBTABLES=m +CONFIG_BRIDGE_EBT_BROUTE=m +CONFIG_BRIDGE_EBT_T_FILTER=m +CONFIG_BRIDGE_EBT_T_NAT=m +CONFIG_BRIDGE_EBT_802_3=m +CONFIG_BRIDGE_EBT_AMONG=m +CONFIG_BRIDGE_EBT_ARP=m +CONFIG_BRIDGE_EBT_IP=m +CONFIG_BRIDGE_EBT_IP6=m +CONFIG_BRIDGE_EBT_LIMIT=m +CONFIG_BRIDGE_EBT_MARK=m +CONFIG_BRIDGE_EBT_PKTTYPE=m +CONFIG_BRIDGE_EBT_STP=m +CONFIG_BRIDGE_EBT_VLAN=m +CONFIG_BRIDGE_EBT_ARPREPLY=m +CONFIG_BRIDGE_EBT_DNAT=m +CONFIG_BRIDGE_EBT_MARK_T=m +CONFIG_BRIDGE_EBT_REDIRECT=m +CONFIG_BRIDGE_EBT_SNAT=m +CONFIG_BRIDGE_EBT_LOG=m +CONFIG_BRIDGE_EBT_NFLOG=m +CONFIG_SCTP_COOKIE_HMAC_SHA1=y +CONFIG_ATM=m +CONFIG_L2TP=m +CONFIG_L2TP_V3=y +CONFIG_L2TP_IP=m +CONFIG_L2TP_ETH=m +CONFIG_BRIDGE=m +CONFIG_BRIDGE_VLAN_FILTERING=y +CONFIG_VLAN_8021Q=m +CONFIG_VLAN_8021Q_GVRP=y +CONFIG_ATALK=m +CONFIG_6LOWPAN=m +CONFIG_IEEE802154=m +CONFIG_IEEE802154_6LOWPAN=m +CONFIG_MAC802154=m +CONFIG_NET_SCHED=y +CONFIG_NET_SCH_HTB=m +CONFIG_NET_SCH_HFSC=m +CONFIG_NET_SCH_PRIO=m +CONFIG_NET_SCH_MULTIQ=m +CONFIG_NET_SCH_RED=m +CONFIG_NET_SCH_SFB=m +CONFIG_NET_SCH_SFQ=m +CONFIG_NET_SCH_TEQL=m +CONFIG_NET_SCH_TBF=m +CONFIG_NET_SCH_GRED=m +CONFIG_NET_SCH_NETEM=m +CONFIG_NET_SCH_DRR=m +CONFIG_NET_SCH_MQPRIO=m +CONFIG_NET_SCH_CHOKE=m +CONFIG_NET_SCH_QFQ=m +CONFIG_NET_SCH_CODEL=m +CONFIG_NET_SCH_FQ_CODEL=m +CONFIG_NET_SCH_CAKE=m +CONFIG_NET_SCH_FQ=m +CONFIG_NET_SCH_HHF=m +CONFIG_NET_SCH_PIE=m +CONFIG_NET_SCH_INGRESS=m +CONFIG_NET_SCH_PLUG=m +CONFIG_NET_CLS_BASIC=m +CONFIG_NET_CLS_ROUTE4=m +CONFIG_NET_CLS_FW=m +CONFIG_NET_CLS_U32=m +CONFIG_CLS_U32_MARK=y +CONFIG_NET_CLS_FLOW=m +CONFIG_NET_CLS_CGROUP=m +CONFIG_NET_EMATCH=y +CONFIG_NET_EMATCH_CMP=m +CONFIG_NET_EMATCH_NBYTE=m +CONFIG_NET_EMATCH_U32=m +CONFIG_NET_EMATCH_META=m +CONFIG_NET_EMATCH_TEXT=m +CONFIG_NET_EMATCH_IPSET=m +CONFIG_NET_CLS_ACT=y +CONFIG_NET_ACT_POLICE=m +CONFIG_NET_ACT_GACT=m +CONFIG_GACT_PROB=y +CONFIG_NET_ACT_MIRRED=m +CONFIG_NET_ACT_NAT=m +CONFIG_NET_ACT_PEDIT=m +CONFIG_NET_ACT_SIMP=m +CONFIG_NET_ACT_SKBEDIT=m +CONFIG_NET_ACT_CSUM=m +CONFIG_BATMAN_ADV=m +CONFIG_OPENVSWITCH=m +CONFIG_CGROUP_NET_PRIO=y +CONFIG_NET_PKTGEN=m +CONFIG_HAMRADIO=y +CONFIG_AX25=m +CONFIG_NETROM=m +CONFIG_ROSE=m +CONFIG_MKISS=m +CONFIG_6PACK=m +CONFIG_BPQETHER=m +CONFIG_BAYCOM_SER_FDX=m +CONFIG_BAYCOM_SER_HDX=m +CONFIG_YAM=m +CONFIG_CAN=m +CONFIG_CAN_J1939=m +CONFIG_CAN_ISOTP=m +CONFIG_BT=m +CONFIG_BT_RFCOMM=m +CONFIG_BT_RFCOMM_TTY=y +CONFIG_BT_BNEP=m +CONFIG_BT_BNEP_MC_FILTER=y +CONFIG_BT_BNEP_PROTO_FILTER=y +CONFIG_BT_HIDP=m +CONFIG_BT_6LOWPAN=m +CONFIG_BT_HCIBTUSB=m +CONFIG_BT_HCIUART=m +CONFIG_BT_HCIUART_3WIRE=y +CONFIG_BT_HCIUART_BCM=y +CONFIG_BT_HCIBCM203X=m +CONFIG_BT_HCIBPA10X=m +CONFIG_BT_HCIBFUSB=m +CONFIG_BT_HCIVHCI=m +CONFIG_BT_MRVL=m +CONFIG_BT_MRVL_SDIO=m +CONFIG_BT_ATH3K=m +CONFIG_CFG80211=m +CONFIG_CFG80211_WEXT=y +CONFIG_MAC80211=m +CONFIG_MAC80211_MESH=y +CONFIG_RFKILL=m +CONFIG_RFKILL_INPUT=y +CONFIG_NET_9P=m +CONFIG_NFC=m +CONFIG_UEVENT_HELPER=y +CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y +CONFIG_RASPBERRYPI_FIRMWARE=y +CONFIG_MTD=m +CONFIG_MTD_BLOCK=m +CONFIG_MTD_BLOCK2MTD=m +CONFIG_MTD_SPI_NAND=m +CONFIG_MTD_SPI_NOR=m +CONFIG_MTD_UBI=m +CONFIG_OF_CONFIGFS=y +CONFIG_ZRAM=m +CONFIG_ZRAM_BACKEND_LZ4=y +CONFIG_ZRAM_BACKEND_ZSTD=y +CONFIG_ZRAM_BACKEND_LZO=y +CONFIG_ZRAM_DEF_COMP_ZSTD=y +CONFIG_ZRAM_WRITEBACK=y +CONFIG_ZRAM_MULTI_COMP=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_DRBD=m +CONFIG_BLK_DEV_NBD=m +CONFIG_BLK_DEV_RAM=y +CONFIG_ATA_OVER_ETH=m +CONFIG_BLK_DEV_RBD=m +CONFIG_EEPROM_AT24=m +CONFIG_EEPROM_AT25=m +CONFIG_TI_ST=m +CONFIG_SCSI=y +# CONFIG_SCSI_PROC_FS is not set +CONFIG_BLK_DEV_SD=y +CONFIG_CHR_DEV_ST=m +CONFIG_BLK_DEV_SR=m +CONFIG_CHR_DEV_SG=m +CONFIG_SCSI_ISCSI_ATTRS=y +CONFIG_ISCSI_TCP=m +CONFIG_ISCSI_BOOT_SYSFS=m +CONFIG_ATA=m +CONFIG_MD=y +CONFIG_BCACHE=m +CONFIG_BLK_DEV_DM=m +CONFIG_DM_CRYPT=m +CONFIG_DM_SNAPSHOT=m +CONFIG_DM_THIN_PROVISIONING=m +CONFIG_DM_CACHE=m +CONFIG_DM_MIRROR=m +CONFIG_DM_LOG_USERSPACE=m +CONFIG_DM_RAID=m +CONFIG_DM_ZERO=m +CONFIG_DM_MULTIPATH=m +CONFIG_DM_DELAY=m +CONFIG_DM_VERITY=m +CONFIG_DM_INTEGRITY=m +CONFIG_NETDEVICES=y +CONFIG_BONDING=m +CONFIG_DUMMY=m +CONFIG_WIREGUARD=m +CONFIG_IFB=m +CONFIG_MACVLAN=m +CONFIG_IPVLAN=m +CONFIG_VXLAN=m +CONFIG_NETCONSOLE=m +CONFIG_TUN=m +CONFIG_VETH=m +CONFIG_NETKIT=y +CONFIG_NET_VRF=m +CONFIG_ENC28J60=m +CONFIG_QCA7000_SPI=m +CONFIG_QCA7000_UART=m +CONFIG_MSE102X=m +CONFIG_WIZNET_W5100=m +CONFIG_WIZNET_W5100_SPI=m +CONFIG_CAN_VCAN=m +CONFIG_CAN_SLCAN=m +CONFIG_CAN_MCP251X=m +CONFIG_CAN_MCP251XFD=m +CONFIG_CAN_8DEV_USB=m +CONFIG_CAN_EMS_USB=m +CONFIG_CAN_GS_USB=m +CONFIG_CAN_PEAK_USB=m +CONFIG_MDIO_BITBANG=m +CONFIG_PPP=m +CONFIG_PPP_BSDCOMP=m +CONFIG_PPP_DEFLATE=m +CONFIG_PPP_FILTER=y +CONFIG_PPP_MPPE=m +CONFIG_PPP_MULTILINK=y +CONFIG_PPPOATM=m +CONFIG_PPPOE=m +CONFIG_PPPOL2TP=m +CONFIG_PPP_ASYNC=m +CONFIG_PPP_SYNC_TTY=m +CONFIG_SLIP=m +CONFIG_SLIP_COMPRESSED=y +CONFIG_SLIP_SMART=y +CONFIG_USB_CATC=m +CONFIG_USB_KAWETH=m +CONFIG_USB_PEGASUS=m +CONFIG_USB_RTL8150=m +CONFIG_USB_RTL8152=m +CONFIG_USB_LAN78XX=y +CONFIG_USB_USBNET=y +CONFIG_USB_NET_AX8817X=m +CONFIG_USB_NET_AX88179_178A=m +CONFIG_USB_NET_CDCETHER=m +CONFIG_USB_NET_CDC_EEM=m +CONFIG_USB_NET_CDC_NCM=m +CONFIG_USB_NET_HUAWEI_CDC_NCM=m +CONFIG_USB_NET_CDC_MBIM=m +CONFIG_USB_NET_DM9601=m +CONFIG_USB_NET_SR9700=m +CONFIG_USB_NET_SR9800=m +CONFIG_USB_NET_SMSC75XX=m +CONFIG_USB_NET_SMSC95XX=y +CONFIG_USB_NET_GL620A=m +CONFIG_USB_NET_NET1080=m +CONFIG_USB_NET_PLUSB=m +CONFIG_USB_NET_MCS7830=m +CONFIG_USB_NET_RNDIS_HOST=m +CONFIG_USB_NET_CDC_SUBSET=m +CONFIG_USB_ALI_M5632=y +CONFIG_USB_AN2720=y +CONFIG_USB_EPSON2888=y +CONFIG_USB_KC2190=y +CONFIG_USB_NET_ZAURUS=m +CONFIG_USB_NET_CX82310_ETH=m +CONFIG_USB_NET_KALMIA=m +CONFIG_USB_NET_QMI_WWAN=m +CONFIG_USB_HSO=m +CONFIG_USB_NET_INT51X1=m +CONFIG_USB_IPHETH=m +CONFIG_USB_SIERRA_NET=m +CONFIG_USB_VL600=m +CONFIG_USB_NET_AQC111=m +CONFIG_ATH9K=m +CONFIG_ATH9K_HTC=m +CONFIG_CARL9170=m +CONFIG_ATH6KL=m +CONFIG_ATH6KL_USB=m +CONFIG_AR5523=m +CONFIG_AT76C50X_USB=m +CONFIG_B43=m +# CONFIG_B43_PHY_N is not set +CONFIG_B43LEGACY=m +CONFIG_BRCMFMAC=m +CONFIG_BRCMFMAC_USB=y +CONFIG_BRCMDBG=y +CONFIG_P54_COMMON=m +CONFIG_P54_USB=m +CONFIG_LIBERTAS=m +CONFIG_LIBERTAS_USB=m +CONFIG_LIBERTAS_SDIO=m +CONFIG_LIBERTAS_THINFIRM=m +CONFIG_LIBERTAS_THINFIRM_USB=m +CONFIG_MWIFIEX=m +CONFIG_MWIFIEX_SDIO=m +CONFIG_MT7601U=m +CONFIG_MT76x0U=m +CONFIG_MT76x2U=m +CONFIG_MT7921U=m +CONFIG_RT2X00=m +CONFIG_RT2500USB=m +CONFIG_RT73USB=m +CONFIG_RT2800USB=m +CONFIG_RT2800USB_RT3573=y +CONFIG_RT2800USB_RT53XX=y +CONFIG_RT2800USB_RT55XX=y +CONFIG_RT2800USB_UNKNOWN=y +CONFIG_RTL8187=m +CONFIG_RTL8192CU=m +CONFIG_RTL8XXXU=m +CONFIG_RTW88=m +CONFIG_RTW88_8822BU=m +CONFIG_RTW88_8822CU=m +CONFIG_RTW88_8723DU=m +CONFIG_RTW88_8821CU=m +CONFIG_ZD1211RW=m +CONFIG_MAC80211_HWSIM=m +CONFIG_IEEE802154_AT86RF230=m +CONFIG_IEEE802154_MRF24J40=m +CONFIG_IEEE802154_CC2520=m +CONFIG_INPUT_MOUSEDEV=y +CONFIG_INPUT_JOYDEV=m +CONFIG_INPUT_EVDEV=y +# CONFIG_KEYBOARD_ATKBD is not set +CONFIG_KEYBOARD_GPIO=m +CONFIG_KEYBOARD_TCA6416=m +CONFIG_KEYBOARD_TCA8418=m +CONFIG_KEYBOARD_MATRIX=m +CONFIG_KEYBOARD_CAP11XX=m +# CONFIG_INPUT_MOUSE is not set +CONFIG_INPUT_JOYSTICK=y +CONFIG_JOYSTICK_IFORCE=m +CONFIG_JOYSTICK_IFORCE_USB=m +CONFIG_JOYSTICK_XPAD=m +CONFIG_JOYSTICK_XPAD_FF=y +CONFIG_JOYSTICK_XPAD_LEDS=y +CONFIG_JOYSTICK_PSXPAD_SPI=m +CONFIG_JOYSTICK_PSXPAD_SPI_FF=y +CONFIG_JOYSTICK_FSIA6B=m +CONFIG_JOYSTICK_SENSEHAT=m +CONFIG_INPUT_TOUCHSCREEN=y +CONFIG_TOUCHSCREEN_ADS7846=m +CONFIG_TOUCHSCREEN_EGALAX=m +CONFIG_TOUCHSCREEN_EXC3000=m +CONFIG_TOUCHSCREEN_GOODIX=m +CONFIG_TOUCHSCREEN_ILI210X=m +CONFIG_TOUCHSCREEN_EDT_FT5X06=m +CONFIG_TOUCHSCREEN_RASPBERRYPI_FW=m +CONFIG_TOUCHSCREEN_USB_COMPOSITE=m +CONFIG_TOUCHSCREEN_TSC2007=m +CONFIG_TOUCHSCREEN_TSC2007_IIO=y +CONFIG_TOUCHSCREEN_STMPE=m +CONFIG_TOUCHSCREEN_IQS5XX=m +CONFIG_INPUT_MISC=y +CONFIG_INPUT_AD714X=m +CONFIG_INPUT_ATI_REMOTE2=m +CONFIG_INPUT_KEYSPAN_REMOTE=m +CONFIG_INPUT_POWERMATE=m +CONFIG_INPUT_YEALINK=m +CONFIG_INPUT_CM109=m +CONFIG_INPUT_UINPUT=m +CONFIG_INPUT_GPIO_ROTARY_ENCODER=m +CONFIG_INPUT_ADXL34X=m +CONFIG_INPUT_CMA3000=m +CONFIG_SERIO=m +CONFIG_SERIO_RAW=m +CONFIG_GAMEPORT=m +CONFIG_BRCM_CHAR_DRIVERS=y +CONFIG_BCM_VCIO=y +# CONFIG_LEGACY_PTYS is not set +CONFIG_SERIAL_8250=y +# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set +CONFIG_SERIAL_8250_CONSOLE=y +# CONFIG_SERIAL_8250_DMA is not set +CONFIG_SERIAL_8250_NR_UARTS=1 +CONFIG_SERIAL_8250_RUNTIME_UARTS=0 +CONFIG_SERIAL_8250_EXTENDED=y +CONFIG_SERIAL_8250_SHARE_IRQ=y +CONFIG_SERIAL_8250_BCM2835AUX=y +CONFIG_SERIAL_OF_PLATFORM=y +CONFIG_SERIAL_AMBA_PL011=y +CONFIG_SERIAL_AMBA_PL011_CONSOLE=y +CONFIG_SERIAL_SC16IS7XX=m +CONFIG_SERIAL_RPI_FW=m +CONFIG_SERIAL_DEV_BUS=y +CONFIG_TTY_PRINTK=y +CONFIG_HW_RANDOM=y +CONFIG_TCG_TPM=m +CONFIG_TCG_TIS_SPI=m +CONFIG_TCG_TIS_I2C=m +CONFIG_RASPBERRYPI_GPIOMEM=m +CONFIG_I2C=y +CONFIG_I2C_CHARDEV=m +CONFIG_I2C_MUX_GPMUX=m +CONFIG_I2C_MUX_PCA954x=m +CONFIG_I2C_MUX_PINCTRL=m +CONFIG_I2C_BCM2708=m +CONFIG_I2C_BCM2835=m +# CONFIG_I2C_BRCMSTB is not set +CONFIG_I2C_GPIO=m +CONFIG_I2C_ROBOTFUZZ_OSIF=m +CONFIG_I2C_TINY_USB=m +CONFIG_SPI=y +CONFIG_SPI_BCM2835=m +CONFIG_SPI_BCM2835AUX=m +CONFIG_SPI_GPIO=m +CONFIG_SPI_RP2040_GPIO_BRIDGE=m +CONFIG_SPI_SPIDEV=m +CONFIG_SPI_SLAVE=y +CONFIG_PPS_CLIENT_LDISC=m +CONFIG_PPS_CLIENT_GPIO=m +CONFIG_PINCTRL_MCP23S08=m +CONFIG_GPIO_SYSFS=y +CONFIG_GPIO_BCM_VIRT=y +CONFIG_GPIO_MAX7300=m +CONFIG_GPIO_PCA953X=m +CONFIG_GPIO_PCA953X_IRQ=y +CONFIG_GPIO_PCF857X=m +CONFIG_GPIO_ARIZONA=m +CONFIG_GPIO_FSM=m +CONFIG_GPIO_STMPE=y +CONFIG_GPIO_MAX7301=m +CONFIG_GPIO_MOCKUP=m +CONFIG_W1=m +CONFIG_W1_MASTER_DS2490=m +CONFIG_W1_MASTER_DS2482=m +CONFIG_W1_MASTER_GPIO=m +CONFIG_W1_SLAVE_THERM=m +CONFIG_W1_SLAVE_SMEM=m +CONFIG_W1_SLAVE_DS2408=m +CONFIG_W1_SLAVE_DS2413=m +CONFIG_W1_SLAVE_DS2406=m +CONFIG_W1_SLAVE_DS2423=m +CONFIG_W1_SLAVE_DS2431=m +CONFIG_W1_SLAVE_DS2433=m +CONFIG_W1_SLAVE_DS2438=m +CONFIG_W1_SLAVE_DS2780=m +CONFIG_W1_SLAVE_DS2781=m +CONFIG_W1_SLAVE_DS28E04=m +CONFIG_W1_SLAVE_DS28E17=m +CONFIG_POWER_RESET=y +CONFIG_POWER_RESET_GPIO=y +CONFIG_RPI_POE_POWER=m +CONFIG_BATTERY_DS2760=m +CONFIG_BATTERY_MAX17040=m +CONFIG_CHARGER_GPIO=m +CONFIG_BATTERY_GAUGE_LTC2941=m +CONFIG_SENSORS_ADT7410=m +CONFIG_SENSORS_AHT10=m +CONFIG_SENSORS_CHIPCAP2=m +CONFIG_SENSORS_DRIVETEMP=m +CONFIG_SENSORS_DS1621=m +CONFIG_SENSORS_GPIO_FAN=m +CONFIG_SENSORS_IIO_HWMON=m +CONFIG_SENSORS_JC42=m +CONFIG_SENSORS_LM75=m +CONFIG_SENSORS_PWM_FAN=m +CONFIG_SENSORS_RASPBERRYPI_HWMON=m +CONFIG_SENSORS_SHT21=m +CONFIG_SENSORS_SHT3x=m +CONFIG_SENSORS_SHT4x=m +CONFIG_SENSORS_SHTC1=m +CONFIG_SENSORS_EMC2305=m +CONFIG_SENSORS_INA2XX=m +CONFIG_SENSORS_INA238=m +CONFIG_SENSORS_TMP102=m +CONFIG_BCM2835_THERMAL=y +CONFIG_WATCHDOG=y +CONFIG_GPIO_WATCHDOG=m +CONFIG_BCM2835_WDT=y +CONFIG_MFD_RASPBERRYPI_POE_HAT=m +CONFIG_MFD_STMPE=y +CONFIG_STMPE_SPI=y +CONFIG_MFD_ARIZONA_I2C=m +CONFIG_MFD_ARIZONA_SPI=m +CONFIG_MFD_WM5102=y +CONFIG_REGULATOR=y +CONFIG_REGULATOR_FIXED_VOLTAGE=m +CONFIG_REGULATOR_ARIZONA_LDO1=m +CONFIG_REGULATOR_ARIZONA_MICSUPP=m +CONFIG_REGULATOR_RASPBERRYPI_TOUCHSCREEN_ATTINY=m +CONFIG_REGULATOR_RASPBERRYPI_TOUCHSCREEN_V2=m +CONFIG_RC_CORE=y +CONFIG_BPF_LIRC_MODE2=y +CONFIG_LIRC=y +CONFIG_RC_DECODERS=y +CONFIG_IR_IMON_DECODER=m +CONFIG_IR_JVC_DECODER=m +CONFIG_IR_MCE_KBD_DECODER=m +CONFIG_IR_NEC_DECODER=m +CONFIG_IR_RC5_DECODER=m +CONFIG_IR_RC6_DECODER=m +CONFIG_IR_SANYO_DECODER=m +CONFIG_IR_SHARP_DECODER=m +CONFIG_IR_SONY_DECODER=m +CONFIG_IR_XMP_DECODER=m +CONFIG_RC_DEVICES=y +CONFIG_IR_GPIO_CIR=m +CONFIG_IR_GPIO_TX=m +CONFIG_IR_IGUANA=m +CONFIG_IR_IMON=m +CONFIG_IR_MCEUSB=m +CONFIG_IR_PWM_TX=m +CONFIG_IR_REDRAT3=m +CONFIG_IR_STREAMZAP=m +CONFIG_IR_TOY=m +CONFIG_IR_TTUSBIR=m +CONFIG_RC_ATI_REMOTE=m +CONFIG_RC_LOOPBACK=m +CONFIG_MEDIA_CEC_RC=y +CONFIG_MEDIA_SUPPORT=m +CONFIG_MEDIA_USB_SUPPORT=y +CONFIG_USB_GSPCA=m +CONFIG_USB_GSPCA_BENQ=m +CONFIG_USB_GSPCA_CONEX=m +CONFIG_USB_GSPCA_CPIA1=m +CONFIG_USB_GSPCA_DTCS033=m +CONFIG_USB_GSPCA_ETOMS=m +CONFIG_USB_GSPCA_FINEPIX=m +CONFIG_USB_GSPCA_JEILINJ=m +CONFIG_USB_GSPCA_JL2005BCD=m +CONFIG_USB_GSPCA_KINECT=m +CONFIG_USB_GSPCA_KONICA=m +CONFIG_USB_GSPCA_MARS=m +CONFIG_USB_GSPCA_MR97310A=m +CONFIG_USB_GSPCA_NW80X=m +CONFIG_USB_GSPCA_OV519=m +CONFIG_USB_GSPCA_OV534=m +CONFIG_USB_GSPCA_OV534_9=m +CONFIG_USB_GSPCA_PAC207=m +CONFIG_USB_GSPCA_PAC7302=m +CONFIG_USB_GSPCA_PAC7311=m +CONFIG_USB_GSPCA_SE401=m +CONFIG_USB_GSPCA_SN9C2028=m +CONFIG_USB_GSPCA_SN9C20X=m +CONFIG_USB_GSPCA_SONIXB=m +CONFIG_USB_GSPCA_SONIXJ=m +CONFIG_USB_GSPCA_SPCA1528=m +CONFIG_USB_GSPCA_SPCA500=m +CONFIG_USB_GSPCA_SPCA501=m +CONFIG_USB_GSPCA_SPCA505=m +CONFIG_USB_GSPCA_SPCA506=m +CONFIG_USB_GSPCA_SPCA508=m +CONFIG_USB_GSPCA_SPCA561=m +CONFIG_USB_GSPCA_SQ905=m +CONFIG_USB_GSPCA_SQ905C=m +CONFIG_USB_GSPCA_SQ930X=m +CONFIG_USB_GSPCA_STK014=m +CONFIG_USB_GSPCA_STK1135=m +CONFIG_USB_GSPCA_STV0680=m +CONFIG_USB_GSPCA_SUNPLUS=m +CONFIG_USB_GSPCA_T613=m +CONFIG_USB_GSPCA_TOPRO=m +CONFIG_USB_GSPCA_TOUPTEK=m +CONFIG_USB_GSPCA_TV8532=m +CONFIG_USB_GSPCA_VC032X=m +CONFIG_USB_GSPCA_VICAM=m +CONFIG_USB_GSPCA_XIRLINK_CIT=m +CONFIG_USB_GSPCA_ZC3XX=m +CONFIG_USB_GL860=m +CONFIG_USB_M5602=m +CONFIG_USB_STV06XX=m +CONFIG_USB_PWC=m +CONFIG_USB_S2255=m +CONFIG_VIDEO_USBTV=m +CONFIG_USB_VIDEO_CLASS=m +CONFIG_VIDEO_GO7007=m +CONFIG_VIDEO_GO7007_USB=m +CONFIG_VIDEO_GO7007_USB_S2250_BOARD=m +CONFIG_VIDEO_HDPVR=m +CONFIG_VIDEO_PVRUSB2=m +CONFIG_VIDEO_STK1160=m +CONFIG_VIDEO_AU0828=m +CONFIG_VIDEO_AU0828_RC=y +CONFIG_VIDEO_CX231XX=m +CONFIG_VIDEO_CX231XX_ALSA=m +CONFIG_VIDEO_CX231XX_DVB=m +CONFIG_DVB_AS102=m +CONFIG_DVB_B2C2_FLEXCOP_USB=m +CONFIG_DVB_USB_V2=m +CONFIG_DVB_USB_AF9015=m +CONFIG_DVB_USB_AF9035=m +CONFIG_DVB_USB_ANYSEE=m +CONFIG_DVB_USB_AU6610=m +CONFIG_DVB_USB_AZ6007=m +CONFIG_DVB_USB_CE6230=m +CONFIG_DVB_USB_DVBSKY=m +CONFIG_DVB_USB_EC168=m +CONFIG_DVB_USB_GL861=m +CONFIG_DVB_USB_LME2510=m +CONFIG_DVB_USB_MXL111SF=m +CONFIG_DVB_USB_RTL28XXU=m +CONFIG_DVB_USB=m +CONFIG_DVB_USB_A800=m +CONFIG_DVB_USB_AF9005=m +CONFIG_DVB_USB_AF9005_REMOTE=m +CONFIG_DVB_USB_AZ6027=m +CONFIG_DVB_USB_CINERGY_T2=m +CONFIG_DVB_USB_CXUSB=m +CONFIG_DVB_USB_DIB0700=m +CONFIG_DVB_USB_DIBUSB_MB=m +CONFIG_DVB_USB_DIBUSB_MB_FAULTY=y +CONFIG_DVB_USB_DIBUSB_MC=m +CONFIG_DVB_USB_DIGITV=m +CONFIG_DVB_USB_DTT200U=m +CONFIG_DVB_USB_DTV5100=m +CONFIG_DVB_USB_DW2102=m +CONFIG_DVB_USB_GP8PSK=m +CONFIG_DVB_USB_M920X=m +CONFIG_DVB_USB_NOVA_T_USB2=m +CONFIG_DVB_USB_OPERA1=m +CONFIG_DVB_USB_PCTV452E=m +CONFIG_DVB_USB_TECHNISAT_USB2=m +CONFIG_DVB_USB_TTUSB2=m +CONFIG_DVB_USB_UMT_010=m +CONFIG_DVB_USB_VP702X=m +CONFIG_DVB_USB_VP7045=m +CONFIG_SMS_USB_DRV=m +CONFIG_VIDEO_EM28XX=m +CONFIG_VIDEO_EM28XX_V4L2=m +CONFIG_VIDEO_EM28XX_ALSA=m +CONFIG_VIDEO_EM28XX_DVB=m +CONFIG_RADIO_SAA7706H=m +CONFIG_RADIO_SHARK=m +CONFIG_RADIO_SHARK2=m +CONFIG_RADIO_SI4713=m +CONFIG_RADIO_TEA5764=m +CONFIG_RADIO_TEF6862=m +CONFIG_RADIO_WL1273=m +CONFIG_USB_DSBR=m +CONFIG_USB_KEENE=m +CONFIG_USB_MA901=m +CONFIG_USB_MR800=m +CONFIG_RADIO_SI470X=m +CONFIG_USB_SI470X=m +CONFIG_I2C_SI470X=m +CONFIG_I2C_SI4713=m +CONFIG_RADIO_WL128X=m +CONFIG_V4L_PLATFORM_DRIVERS=y +CONFIG_VIDEO_MUX=m +CONFIG_VIDEO_BCM2835_UNICAM_LEGACY=m +CONFIG_VIDEO_BCM2835_UNICAM=m +CONFIG_VIDEO_ARDUCAM_64MP=m +CONFIG_VIDEO_ARDUCAM_PIVARIETY=m +CONFIG_VIDEO_IMX219=m +CONFIG_VIDEO_IMX258=m +CONFIG_VIDEO_IMX290=m +CONFIG_VIDEO_IMX296=m +CONFIG_VIDEO_IMX415=m +CONFIG_VIDEO_IMX477=m +CONFIG_VIDEO_IMX500=m +CONFIG_VIDEO_IMX519=m +CONFIG_VIDEO_IMX708=m +CONFIG_VIDEO_MT9V011=m +CONFIG_VIDEO_OV2311=m +CONFIG_VIDEO_OV5647=m +CONFIG_VIDEO_OV64A40=m +CONFIG_VIDEO_OV7251=m +CONFIG_VIDEO_OV7640=m +CONFIG_VIDEO_OV9282=m +CONFIG_VIDEO_AD5398=m +CONFIG_VIDEO_AK7375=m +CONFIG_VIDEO_BU64754=m +CONFIG_VIDEO_DW9807_VCM=m +CONFIG_VIDEO_SONY_BTF_MPX=m +CONFIG_VIDEO_UDA1342=m +CONFIG_VIDEO_ADV7180=m +CONFIG_VIDEO_TC358743=m +CONFIG_VIDEO_TVP5150=m +CONFIG_VIDEO_TW2804=m +CONFIG_VIDEO_TW9903=m +CONFIG_VIDEO_TW9906=m +CONFIG_VIDEO_IRS1125=m +CONFIG_VIDEO_I2C=m +CONFIG_AUXDISPLAY=y +CONFIG_HD44780=m +CONFIG_DRM=m +CONFIG_DRM_LOAD_EDID_FIRMWARE=y +CONFIG_DRM_UDL=m +CONFIG_DRM_PANEL_ILITEK_ILI9806E=m +CONFIG_DRM_PANEL_ILITEK_ILI9881C=m +CONFIG_DRM_PANEL_JDI_LT070ME05000=m +CONFIG_DRM_PANEL_RASPBERRYPI_TOUCHSCREEN=m +CONFIG_DRM_PANEL_SITRONIX_ST7701=m +CONFIG_DRM_PANEL_SIMPLE=m +CONFIG_DRM_PANEL_TPO_Y17P=m +CONFIG_DRM_PANEL_WAVESHARE_TOUCHSCREEN=m +CONFIG_DRM_DISPLAY_CONNECTOR=m +CONFIG_DRM_SIMPLE_BRIDGE=m +CONFIG_DRM_TOSHIBA_TC358762=m +CONFIG_DRM_VC4=m +CONFIG_DRM_VC4_HDMI_CEC=y +CONFIG_DRM_PANEL_MIPI_DBI=m +CONFIG_TINYDRM_HX8357D=m +CONFIG_TINYDRM_ILI9225=m +CONFIG_TINYDRM_ILI9341=m +CONFIG_TINYDRM_ILI9486=m +CONFIG_TINYDRM_MI0283QT=m +CONFIG_TINYDRM_REPAPER=m +CONFIG_TINYDRM_ST7586=m +CONFIG_TINYDRM_ST7735R=m +CONFIG_DRM_GUD=m +CONFIG_DRM_SSD130X=m +CONFIG_DRM_SSD130X_I2C=m +CONFIG_DRM_SSD130X_SPI=m +CONFIG_FB=y +CONFIG_FB_BCM2708=y +CONFIG_FB_SIMPLE=y +CONFIG_FB_SSD1307=m +CONFIG_FB_RPISENSE=m +CONFIG_BACKLIGHT_PWM=m +CONFIG_BACKLIGHT_RPI=m +CONFIG_BACKLIGHT_LM3630A=m +CONFIG_BACKLIGHT_GPIO=m +CONFIG_FRAMEBUFFER_CONSOLE_ROTATION=y +CONFIG_LOGO=y +# CONFIG_LOGO_LINUX_MONO is not set +# CONFIG_LOGO_LINUX_VGA16 is not set +CONFIG_SOUND=y +CONFIG_SND=m +CONFIG_SND_OSSEMUL=y +CONFIG_SND_PCM_OSS=m +CONFIG_SND_HRTIMER=m +CONFIG_SND_DYNAMIC_MINORS=y +CONFIG_SND_SEQUENCER=m +CONFIG_SND_SEQ_DUMMY=m +CONFIG_SND_DUMMY=m +CONFIG_SND_ALOOP=m +CONFIG_SND_VIRMIDI=m +CONFIG_SND_MTPAV=m +CONFIG_SND_SERIAL_U16550=m +CONFIG_SND_MPU401=m +CONFIG_SND_PIMIDI=m +CONFIG_SND_USB_AUDIO=m +CONFIG_SND_USB_UA101=m +CONFIG_SND_USB_CAIAQ=m +CONFIG_SND_USB_CAIAQ_INPUT=y +CONFIG_SND_USB_6FIRE=m +CONFIG_SND_USB_HIFACE=m +CONFIG_SND_USB_TONEPORT=m +CONFIG_SND_SOC=m +CONFIG_SND_BCM2835_SOC_I2S=m +CONFIG_SND_BCM2708_SOC_CHIPDIP_DAC=m +CONFIG_SND_BCM2708_SOC_GOOGLEVOICEHAT_SOUNDCARD=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_ADC=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_DAC=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUS=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSHD=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSADC=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSADCPRO=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSDSP=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_DIGI=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_AMP=m +CONFIG_SND_BCM2708_SOC_PIFI_40=m +CONFIG_SND_BCM2708_SOC_RPI_CIRRUS=m +CONFIG_SND_BCM2708_SOC_RPI_DAC=m +CONFIG_SND_BCM2708_SOC_RPI_PROTO=m +CONFIG_SND_BCM2708_SOC_JUSTBOOM_BOTH=m +CONFIG_SND_BCM2708_SOC_JUSTBOOM_DAC=m +CONFIG_SND_BCM2708_SOC_JUSTBOOM_DIGI=m +CONFIG_SND_BCM2708_SOC_IQAUDIO_CODEC=m +CONFIG_SND_BCM2708_SOC_IQAUDIO_DAC=m +CONFIG_SND_BCM2708_SOC_IQAUDIO_DIGI=m +CONFIG_SND_BCM2708_SOC_I_SABRE_Q2M=m +CONFIG_SND_BCM2708_SOC_ADAU1977_ADC=m +CONFIG_SND_AUDIOINJECTOR_PI_SOUNDCARD=m +CONFIG_SND_AUDIOINJECTOR_OCTO_SOUNDCARD=m +CONFIG_SND_AUDIOINJECTOR_ISOLATED_SOUNDCARD=m +CONFIG_SND_AUDIOSENSE_PI=m +CONFIG_SND_DIGIDAC1_SOUNDCARD=m +CONFIG_SND_BCM2708_SOC_DIONAUDIO_LOCO=m +CONFIG_SND_BCM2708_SOC_DIONAUDIO_LOCO_V2=m +CONFIG_SND_BCM2708_SOC_ALLO_PIANO_DAC=m +CONFIG_SND_BCM2708_SOC_ALLO_PIANO_DAC_PLUS=m +CONFIG_SND_BCM2708_SOC_ALLO_BOSS_DAC=m +CONFIG_SND_BCM2708_SOC_ALLO_BOSS2_DAC=m +CONFIG_SND_BCM2708_SOC_ALLO_DIGIONE=m +CONFIG_SND_BCM2708_SOC_ALLO_KATANA_DAC=m +CONFIG_SND_BCM2708_SOC_FE_PI_AUDIO=m +CONFIG_SND_PISOUND=m +CONFIG_SND_DACBERRY400=m +CONFIG_SND_SOC_AD193X_SPI=m +CONFIG_SND_SOC_AD193X_I2C=m +CONFIG_SND_SOC_ADAU1701=m +CONFIG_SND_SOC_ADAU7002=m +CONFIG_SND_SOC_AK4554=m +CONFIG_SND_SOC_CS4265=m +CONFIG_SND_SOC_ICS43432=m +CONFIG_SND_SOC_MA120X0P=m +CONFIG_SND_SOC_MAX98357A=m +CONFIG_SND_SOC_SPDIF=m +CONFIG_SND_SOC_TLV320AIC23_I2C=m +CONFIG_SND_SOC_WM8804_I2C=m +CONFIG_SND_SOC_WM8904=m +CONFIG_SND_SOC_WM8960=m +CONFIG_SND_SIMPLE_CARD=m +CONFIG_HID_BATTERY_STRENGTH=y +CONFIG_HIDRAW=y +CONFIG_UHID=m +CONFIG_HID_A4TECH=m +CONFIG_HID_ACRUX=m +CONFIG_HID_APPLE=m +CONFIG_HID_ASUS=m +CONFIG_HID_BELKIN=m +CONFIG_HID_BETOP_FF=m +CONFIG_HID_BIGBEN_FF=m +CONFIG_HID_CHERRY=m +CONFIG_HID_CHICONY=m +CONFIG_HID_CYPRESS=m +CONFIG_HID_DRAGONRISE=m +CONFIG_HID_EMS_FF=m +CONFIG_HID_ELECOM=m +CONFIG_HID_ELO=m +CONFIG_HID_EZKEY=m +CONFIG_HID_GEMBIRD=m +CONFIG_HID_HOLTEK=m +CONFIG_HID_KEYTOUCH=m +CONFIG_HID_KYE=m +CONFIG_HID_UCLOGIC=m +CONFIG_HID_WALTOP=m +CONFIG_HID_GYRATION=m +CONFIG_HID_TWINHAN=m +CONFIG_HID_KENSINGTON=m +CONFIG_HID_LCPOWER=m +CONFIG_HID_LOGITECH=m +CONFIG_HID_LOGITECH_DJ=m +CONFIG_LOGITECH_FF=y +CONFIG_LOGIRUMBLEPAD2_FF=y +CONFIG_LOGIG940_FF=y +CONFIG_HID_MAGICMOUSE=m +CONFIG_HID_MICROSOFT=m +CONFIG_HID_MONTEREY=m +CONFIG_HID_MULTITOUCH=m +CONFIG_HID_NINTENDO=m +CONFIG_NINTENDO_FF=y +CONFIG_HID_NTRIG=m +CONFIG_HID_ORTEK=m +CONFIG_HID_PANTHERLORD=m +CONFIG_HID_PETALYNX=m +CONFIG_HID_PICOLCD=m +CONFIG_HID_PLAYSTATION=m +CONFIG_PLAYSTATION_FF=y +CONFIG_HID_ROCCAT=m +CONFIG_HID_SAMSUNG=m +CONFIG_HID_SONY=m +CONFIG_SONY_FF=y +CONFIG_HID_SPEEDLINK=m +CONFIG_HID_STEAM=m +CONFIG_HID_SUNPLUS=m +CONFIG_HID_GREENASIA=m +CONFIG_HID_SMARTJOYPLUS=m +CONFIG_HID_TOPSEED=m +CONFIG_HID_THINGM=m +CONFIG_HID_THRUSTMASTER=m +CONFIG_HID_WACOM=m +CONFIG_HID_WIIMOTE=m +CONFIG_HID_XINMO=m +CONFIG_HID_ZEROPLUS=m +CONFIG_HID_ZYDACRON=m +CONFIG_HID_PID=y +CONFIG_USB_HIDDEV=y +CONFIG_I2C_HID_OF=m +CONFIG_USB=y +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y +CONFIG_USB_MON=m +CONFIG_USB_DWCOTG=y +CONFIG_USB_PRINTER=m +CONFIG_USB_TMC=m +CONFIG_USB_STORAGE=y +CONFIG_USB_STORAGE_REALTEK=m +CONFIG_USB_STORAGE_DATAFAB=m +CONFIG_USB_STORAGE_FREECOM=m +CONFIG_USB_STORAGE_ISD200=m +CONFIG_USB_STORAGE_USBAT=m +CONFIG_USB_STORAGE_SDDR09=m +CONFIG_USB_STORAGE_SDDR55=m +CONFIG_USB_STORAGE_JUMPSHOT=m +CONFIG_USB_STORAGE_ALAUDA=m +CONFIG_USB_STORAGE_ONETOUCH=m +CONFIG_USB_STORAGE_KARMA=m +CONFIG_USB_STORAGE_CYPRESS_ATACB=m +CONFIG_USB_STORAGE_ENE_UB6250=m +CONFIG_USB_UAS=m +CONFIG_USB_MDC800=m +CONFIG_USB_MICROTEK=m +CONFIG_USBIP_CORE=m +CONFIG_USBIP_VHCI_HCD=m +CONFIG_USBIP_HOST=m +CONFIG_USBIP_VUDC=m +CONFIG_USB_DWC2=m +CONFIG_USB_SERIAL=m +CONFIG_USB_SERIAL_GENERIC=y +CONFIG_USB_SERIAL_SIMPLE=m +CONFIG_USB_SERIAL_AIRCABLE=m +CONFIG_USB_SERIAL_ARK3116=m +CONFIG_USB_SERIAL_BELKIN=m +CONFIG_USB_SERIAL_CH341=m +CONFIG_USB_SERIAL_WHITEHEAT=m +CONFIG_USB_SERIAL_DIGI_ACCELEPORT=m +CONFIG_USB_SERIAL_CP210X=m +CONFIG_USB_SERIAL_CYPRESS_M8=m +CONFIG_USB_SERIAL_EMPEG=m +CONFIG_USB_SERIAL_FTDI_SIO=m +CONFIG_USB_SERIAL_VISOR=m +CONFIG_USB_SERIAL_IPAQ=m +CONFIG_USB_SERIAL_IR=m +CONFIG_USB_SERIAL_EDGEPORT=m +CONFIG_USB_SERIAL_EDGEPORT_TI=m +CONFIG_USB_SERIAL_F81232=m +CONFIG_USB_SERIAL_F8153X=m +CONFIG_USB_SERIAL_GARMIN=m +CONFIG_USB_SERIAL_IPW=m +CONFIG_USB_SERIAL_IUU=m +CONFIG_USB_SERIAL_KEYSPAN_PDA=m +CONFIG_USB_SERIAL_KEYSPAN=m +CONFIG_USB_SERIAL_KLSI=m +CONFIG_USB_SERIAL_KOBIL_SCT=m +CONFIG_USB_SERIAL_MCT_U232=m +CONFIG_USB_SERIAL_METRO=m +CONFIG_USB_SERIAL_MOS7720=m +CONFIG_USB_SERIAL_MOS7840=m +CONFIG_USB_SERIAL_MXUPORT=m +CONFIG_USB_SERIAL_NAVMAN=m +CONFIG_USB_SERIAL_PL2303=m +CONFIG_USB_SERIAL_OTI6858=m +CONFIG_USB_SERIAL_QCAUX=m +CONFIG_USB_SERIAL_QUALCOMM=m +CONFIG_USB_SERIAL_SPCP8X5=m +CONFIG_USB_SERIAL_SAFE=m +CONFIG_USB_SERIAL_SIERRAWIRELESS=m +CONFIG_USB_SERIAL_SYMBOL=m +CONFIG_USB_SERIAL_TI=m +CONFIG_USB_SERIAL_CYBERJACK=m +CONFIG_USB_SERIAL_OPTION=m +CONFIG_USB_SERIAL_OMNINET=m +CONFIG_USB_SERIAL_OPTICON=m +CONFIG_USB_SERIAL_XSENS_MT=m +CONFIG_USB_SERIAL_WISHBONE=m +CONFIG_USB_SERIAL_SSU100=m +CONFIG_USB_SERIAL_QT2=m +CONFIG_USB_SERIAL_UPD78F0730=m +CONFIG_USB_SERIAL_XR=m +CONFIG_USB_SERIAL_DEBUG=m +CONFIG_USB_EMI62=m +CONFIG_USB_EMI26=m +CONFIG_USB_ADUTUX=m +CONFIG_USB_SEVSEG=m +CONFIG_USB_LEGOTOWER=m +CONFIG_USB_LCD=m +CONFIG_USB_CYPRESS_CY7C63=m +CONFIG_USB_CYTHERM=m +CONFIG_USB_IDMOUSE=m +CONFIG_USB_APPLEDISPLAY=m +CONFIG_USB_LD=m +CONFIG_USB_TRANCEVIBRATOR=m +CONFIG_USB_IOWARRIOR=m +CONFIG_USB_TEST=m +CONFIG_USB_ISIGHTFW=m +CONFIG_USB_YUREX=m +CONFIG_USB_ATM=m +CONFIG_USB_SPEEDTOUCH=m +CONFIG_USB_CXACRU=m +CONFIG_USB_UEAGLEATM=m +CONFIG_USB_XUSBATM=m +CONFIG_NOP_USB_XCEIV=y +CONFIG_USB_GADGET=y +CONFIG_USB_CONFIGFS=m +CONFIG_USB_CONFIGFS_SERIAL=y +CONFIG_USB_CONFIGFS_ACM=y +CONFIG_USB_CONFIGFS_OBEX=y +CONFIG_USB_CONFIGFS_NCM=y +CONFIG_USB_CONFIGFS_ECM=y +CONFIG_USB_CONFIGFS_ECM_SUBSET=y +CONFIG_USB_CONFIGFS_RNDIS=y +CONFIG_USB_CONFIGFS_EEM=y +CONFIG_USB_CONFIGFS_MASS_STORAGE=y +CONFIG_USB_CONFIGFS_F_LB_SS=y +CONFIG_USB_CONFIGFS_F_FS=y +CONFIG_USB_CONFIGFS_F_UAC1=y +CONFIG_USB_CONFIGFS_F_UAC2=y +CONFIG_USB_CONFIGFS_F_MIDI=y +CONFIG_USB_CONFIGFS_F_HID=y +CONFIG_USB_CONFIGFS_F_UVC=y +CONFIG_USB_CONFIGFS_F_PRINTER=y +CONFIG_USB_ZERO=m +CONFIG_USB_AUDIO=m +CONFIG_USB_ETH=m +CONFIG_USB_GADGETFS=m +CONFIG_USB_MASS_STORAGE=m +CONFIG_USB_G_SERIAL=m +CONFIG_USB_MIDI_GADGET=m +CONFIG_USB_G_PRINTER=m +CONFIG_USB_CDC_COMPOSITE=m +CONFIG_USB_G_ACM_MS=m +CONFIG_USB_G_MULTI=m +CONFIG_USB_G_HID=m +CONFIG_USB_G_WEBCAM=m +CONFIG_MMC=y +CONFIG_MMC_BLOCK_MINORS=32 +CONFIG_MMC_BCM2835_MMC=y +CONFIG_MMC_BCM2835_DMA=y +CONFIG_MMC_SDHCI=y +CONFIG_MMC_SDHCI_PLTFM=y +CONFIG_MMC_SPI=m +CONFIG_MMC_HSQ=y +CONFIG_MMC_BCM2835=y +CONFIG_LEDS_CLASS=y +CONFIG_LEDS_CLASS_MULTICOLOR=m +CONFIG_LEDS_PCA9532=m +CONFIG_LEDS_GPIO=y +CONFIG_LEDS_PCA955X=m +CONFIG_LEDS_PCA963X=m +CONFIG_LEDS_PWM=y +CONFIG_LEDS_IS31FL32XX=m +CONFIG_LEDS_TRIGGER_TIMER=y +CONFIG_LEDS_TRIGGER_ONESHOT=y +CONFIG_LEDS_TRIGGER_HEARTBEAT=y +CONFIG_LEDS_TRIGGER_BACKLIGHT=y +CONFIG_LEDS_TRIGGER_CPU=y +CONFIG_LEDS_TRIGGER_DEFAULT_ON=y +CONFIG_LEDS_TRIGGER_TRANSIENT=m +CONFIG_LEDS_TRIGGER_CAMERA=m +CONFIG_LEDS_TRIGGER_INPUT=y +CONFIG_LEDS_TRIGGER_PANIC=y +CONFIG_LEDS_TRIGGER_NETDEV=m +CONFIG_LEDS_TRIGGER_PATTERN=m +CONFIG_LEDS_TRIGGER_ACTPWR=y +CONFIG_ACCESSIBILITY=y +CONFIG_SPEAKUP=m +CONFIG_SPEAKUP_SYNTH_SOFT=m +CONFIG_RTC_CLASS=y +CONFIG_RTC_DRV_ABX80X=m +CONFIG_RTC_DRV_DS1307=m +CONFIG_RTC_DRV_DS1374=m +CONFIG_RTC_DRV_DS1672=m +CONFIG_RTC_DRV_MAX6900=m +CONFIG_RTC_DRV_RS5C372=m +CONFIG_RTC_DRV_ISL1208=m +CONFIG_RTC_DRV_ISL12022=m +CONFIG_RTC_DRV_X1205=m +CONFIG_RTC_DRV_PCF8523=m +CONFIG_RTC_DRV_PCF85063=m +CONFIG_RTC_DRV_PCF85363=m +CONFIG_RTC_DRV_PCF8563=m +CONFIG_RTC_DRV_PCF8583=m +CONFIG_RTC_DRV_M41T80=m +CONFIG_RTC_DRV_BQ32K=m +CONFIG_RTC_DRV_S35390A=m +CONFIG_RTC_DRV_FM3130=m +CONFIG_RTC_DRV_RX8581=m +CONFIG_RTC_DRV_RX8025=m +CONFIG_RTC_DRV_EM3027=m +CONFIG_RTC_DRV_RV3028=m +CONFIG_RTC_DRV_RV3032=m +CONFIG_RTC_DRV_RV8803=m +CONFIG_RTC_DRV_SD3078=m +CONFIG_RTC_DRV_M41T93=m +CONFIG_RTC_DRV_M41T94=m +CONFIG_RTC_DRV_DS1302=m +CONFIG_RTC_DRV_DS1305=m +CONFIG_RTC_DRV_DS1390=m +CONFIG_RTC_DRV_R9701=m +CONFIG_RTC_DRV_RX4581=m +CONFIG_RTC_DRV_RS5C348=m +CONFIG_RTC_DRV_MAX6902=m +CONFIG_RTC_DRV_PCF2123=m +CONFIG_RTC_DRV_DS3232=m +CONFIG_RTC_DRV_PCF2127=m +CONFIG_RTC_DRV_RV3029C2=m +CONFIG_DMADEVICES=y +CONFIG_DMA_BCM2835=y +CONFIG_DMA_BCM2708=y +CONFIG_DMABUF_HEAPS=y +CONFIG_DMABUF_HEAPS_SYSTEM=y +CONFIG_DMABUF_HEAPS_CMA=y +CONFIG_UIO=m +CONFIG_UIO_PDRV_GENIRQ=m +CONFIG_STAGING=y +CONFIG_R8712U=m +CONFIG_VT6656=m +CONFIG_STAGING_MEDIA=y +CONFIG_STAGING_MEDIA_DEPRECATED=y +CONFIG_FB_TFT=m +CONFIG_FB_TFT_AGM1264K_FL=m +CONFIG_FB_TFT_BD663474=m +CONFIG_FB_TFT_HX8340BN=m +CONFIG_FB_TFT_HX8347D=m +CONFIG_FB_TFT_HX8353D=m +CONFIG_FB_TFT_HX8357D=m +CONFIG_FB_TFT_ILI9163=m +CONFIG_FB_TFT_ILI9320=m +CONFIG_FB_TFT_ILI9325=m +CONFIG_FB_TFT_ILI9340=m +CONFIG_FB_TFT_ILI9341=m +CONFIG_FB_TFT_ILI9481=m +CONFIG_FB_TFT_ILI9486=m +CONFIG_FB_TFT_PCD8544=m +CONFIG_FB_TFT_RA8875=m +CONFIG_FB_TFT_S6D02A1=m +CONFIG_FB_TFT_S6D1121=m +CONFIG_FB_TFT_SH1106=m +CONFIG_FB_TFT_SSD1289=m +CONFIG_FB_TFT_SSD1306=m +CONFIG_FB_TFT_SSD1331=m +CONFIG_FB_TFT_SSD1351=m +CONFIG_FB_TFT_ST7735R=m +CONFIG_FB_TFT_ST7789V=m +CONFIG_FB_TFT_TINYLCD=m +CONFIG_FB_TFT_TLS8204=m +CONFIG_FB_TFT_UC1611=m +CONFIG_FB_TFT_UC1701=m +CONFIG_FB_TFT_UPD161704=m +CONFIG_BCM2835_VCHIQ=y +CONFIG_SND_BCM2835=m +CONFIG_VIDEO_BCM2835=m +CONFIG_VIDEO_CODEC_BCM2835=m +CONFIG_VIDEO_ISP_BCM2835=m +CONFIG_CLK_RASPBERRYPI=y +CONFIG_MAILBOX=y +CONFIG_BCM2835_MBOX=y +# CONFIG_IOMMU_SUPPORT is not set +CONFIG_RASPBERRYPI_POWER=y +CONFIG_IIO=m +CONFIG_IIO_BUFFER_CB=m +CONFIG_IIO_SW_TRIGGER=m +CONFIG_MCP320X=m +CONFIG_MCP3422=m +CONFIG_TI_ADS1015=m +CONFIG_BME680=m +CONFIG_CCS811=m +CONFIG_SENSIRION_SGP30=m +CONFIG_SPS30_I2C=m +CONFIG_MAX30102=m +CONFIG_DHT11=m +CONFIG_HDC100X=m +CONFIG_HDC3020=m +CONFIG_HTS221=m +CONFIG_HTU21=m +CONFIG_SI7020=m +CONFIG_BOSCH_BNO055_I2C=m +CONFIG_INV_MPU6050_I2C=m +CONFIG_APDS9960=m +CONFIG_AS73211=m +CONFIG_BH1750=m +CONFIG_TSL4531=m +CONFIG_VEML6070=m +CONFIG_VEML6075=m +CONFIG_IIO_HRTIMER_TRIGGER=m +CONFIG_IIO_INTERRUPT_TRIGGER=m +CONFIG_IIO_SYSFS_TRIGGER=m +CONFIG_BMP280=m +CONFIG_MS5637=m +CONFIG_MAXIM_THERMOCOUPLE=m +CONFIG_MAX31856=m +CONFIG_PWM=y +CONFIG_PWM_BCM2835=m +CONFIG_PWM_GPIO=m +CONFIG_PWM_PCA9685=m +CONFIG_PWM_RASPBERRYPI_POE=m +CONFIG_RPI_AXIPERF=m +CONFIG_MUX_GPIO=m +CONFIG_EXT4_FS=y +CONFIG_EXT4_FS_POSIX_ACL=y +CONFIG_EXT4_FS_SECURITY=y +CONFIG_REISERFS_FS=m +CONFIG_REISERFS_FS_XATTR=y +CONFIG_REISERFS_FS_POSIX_ACL=y +CONFIG_REISERFS_FS_SECURITY=y +CONFIG_JFS_FS=m +CONFIG_JFS_POSIX_ACL=y +CONFIG_JFS_SECURITY=y +CONFIG_JFS_STATISTICS=y +CONFIG_XFS_FS=m +CONFIG_XFS_QUOTA=y +CONFIG_XFS_POSIX_ACL=y +CONFIG_XFS_RT=y +CONFIG_GFS2_FS=m +CONFIG_OCFS2_FS=m +CONFIG_BTRFS_FS=m +CONFIG_BTRFS_FS_POSIX_ACL=y +CONFIG_NILFS2_FS=m +CONFIG_F2FS_FS=y +CONFIG_F2FS_FS_SECURITY=y +CONFIG_BCACHEFS_FS=m +CONFIG_BCACHEFS_QUOTA=y +CONFIG_BCACHEFS_POSIX_ACL=y +CONFIG_FS_ENCRYPTION=y +CONFIG_FANOTIFY=y +CONFIG_QFMT_V1=m +CONFIG_QFMT_V2=m +CONFIG_AUTOFS_FS=y +CONFIG_FUSE_FS=m +CONFIG_CUSE=m +CONFIG_OVERLAY_FS=m +CONFIG_FSCACHE=y +CONFIG_FSCACHE_STATS=y +CONFIG_CACHEFILES=m +CONFIG_ISO9660_FS=m +CONFIG_JOLIET=y +CONFIG_ZISOFS=y +CONFIG_UDF_FS=m +CONFIG_MSDOS_FS=y +CONFIG_VFAT_FS=y +CONFIG_FAT_DEFAULT_IOCHARSET="ascii" +CONFIG_EXFAT_FS=m +CONFIG_NTFS3_FS=m +CONFIG_TMPFS=y +CONFIG_TMPFS_POSIX_ACL=y +CONFIG_ECRYPT_FS=m +CONFIG_HFS_FS=m +CONFIG_HFSPLUS_FS=m +CONFIG_JFFS2_FS=m +CONFIG_JFFS2_SUMMARY=y +CONFIG_UBIFS_FS=m +CONFIG_SQUASHFS=m +CONFIG_SQUASHFS_XATTR=y +CONFIG_SQUASHFS_LZO=y +CONFIG_SQUASHFS_XZ=y +CONFIG_SQUASHFS_ZSTD=y +CONFIG_PSTORE=y +CONFIG_PSTORE_CONSOLE=y +CONFIG_PSTORE_RAM=y +CONFIG_NFS_FS=y +CONFIG_NFS_V2=y +CONFIG_NFS_V3_ACL=y +CONFIG_NFS_V4=y +CONFIG_NFS_SWAP=y +CONFIG_NFS_V4_1=y +CONFIG_NFS_V4_2=y +CONFIG_ROOT_NFS=y +CONFIG_NFSD=m +CONFIG_NFSD_V2=y +CONFIG_NFSD_V2_ACL=y +CONFIG_NFSD_V3_ACL=y +CONFIG_NFSD_V4=y +CONFIG_CEPH_FS=m +CONFIG_CIFS=m +CONFIG_CIFS_UPCALL=y +CONFIG_CIFS_XATTR=y +CONFIG_CIFS_DFS_UPCALL=y +CONFIG_CIFS_FSCACHE=y +CONFIG_SMB_SERVER=m +CONFIG_9P_FS=m +CONFIG_9P_FS_POSIX_ACL=y +CONFIG_NLS_DEFAULT="utf8" +CONFIG_NLS_CODEPAGE_437=y +CONFIG_NLS_CODEPAGE_737=m +CONFIG_NLS_CODEPAGE_775=m +CONFIG_NLS_CODEPAGE_850=m +CONFIG_NLS_CODEPAGE_852=m +CONFIG_NLS_CODEPAGE_855=m +CONFIG_NLS_CODEPAGE_857=m +CONFIG_NLS_CODEPAGE_860=m +CONFIG_NLS_CODEPAGE_861=m +CONFIG_NLS_CODEPAGE_862=m +CONFIG_NLS_CODEPAGE_863=m +CONFIG_NLS_CODEPAGE_864=m +CONFIG_NLS_CODEPAGE_865=m +CONFIG_NLS_CODEPAGE_866=m +CONFIG_NLS_CODEPAGE_869=m +CONFIG_NLS_CODEPAGE_936=m +CONFIG_NLS_CODEPAGE_950=m +CONFIG_NLS_CODEPAGE_932=m +CONFIG_NLS_CODEPAGE_949=m +CONFIG_NLS_CODEPAGE_874=m +CONFIG_NLS_ISO8859_8=m +CONFIG_NLS_CODEPAGE_1250=m +CONFIG_NLS_CODEPAGE_1251=m +CONFIG_NLS_ASCII=y +CONFIG_NLS_ISO8859_1=m +CONFIG_NLS_ISO8859_2=m +CONFIG_NLS_ISO8859_3=m +CONFIG_NLS_ISO8859_4=m +CONFIG_NLS_ISO8859_5=m +CONFIG_NLS_ISO8859_6=m +CONFIG_NLS_ISO8859_7=m +CONFIG_NLS_ISO8859_9=m +CONFIG_NLS_ISO8859_13=m +CONFIG_NLS_ISO8859_14=m +CONFIG_NLS_ISO8859_15=m +CONFIG_NLS_KOI8_R=m +CONFIG_NLS_KOI8_U=m +CONFIG_DLM=m +CONFIG_KEY_DH_OPERATIONS=y +CONFIG_SECURITY=y +CONFIG_SECURITY_APPARMOR=y +CONFIG_LSM="" +CONFIG_CRYPTO_USER=m +CONFIG_CRYPTO_CAST5=m +CONFIG_CRYPTO_DES=y +CONFIG_CRYPTO_TWOFISH=m +CONFIG_CRYPTO_ADIANTUM=m +CONFIG_CRYPTO_CHACHA20POLY1305=m +CONFIG_CRYPTO_MD4=m +CONFIG_CRYPTO_SHA512=m +CONFIG_CRYPTO_WP512=m +CONFIG_CRYPTO_XCBC=m +CONFIG_CRYPTO_LZ4=m +CONFIG_CRYPTO_USER_API_HASH=m +CONFIG_CRYPTO_USER_API_SKCIPHER=m +CONFIG_CRYPTO_USER_API_RNG=m +CONFIG_CRYPTO_USER_API_AEAD=m +CONFIG_CRYPTO_NHPOLY1305_NEON=m +CONFIG_CRYPTO_SHA1_ARM_NEON=m +CONFIG_CRYPTO_AES_ARM_BS=m +# CONFIG_CRYPTO_HW is not set +CONFIG_PKCS8_PRIVATE_KEY_PARSER=m +CONFIG_CRC_ITU_T=y +CONFIG_LIBCRC32C=y +CONFIG_DMA_CMA=y +CONFIG_CMA_SIZE_MBYTES=5 +CONFIG_PRINTK_TIME=y +CONFIG_BOOT_PRINTK_DELAY=y +CONFIG_MAGIC_SYSRQ_DEFAULT_ENABLE=0x1f6 +CONFIG_KGDB=y +CONFIG_KGDB_KDB=y +CONFIG_KDB_KEYBOARD=y +CONFIG_DEBUG_MEMORY_INIT=y +CONFIG_DETECT_HUNG_TASK=y +# CONFIG_RCU_TRACE is not set +CONFIG_LATENCYTOP=y +CONFIG_FUNCTION_PROFILER=y +CONFIG_STACK_TRACER=y +CONFIG_SCHED_TRACER=y +CONFIG_BLK_DEV_IO_TRACE=y +# CONFIG_UPROBE_EVENTS is not set diff --git a/arch/arm/configs/bcm2711_defconfig b/arch/arm/configs/bcm2711_defconfig new file mode 100644 index 00000000000000..9bbb8f8ebeda81 --- /dev/null +++ b/arch/arm/configs/bcm2711_defconfig @@ -0,0 +1,1654 @@ +CONFIG_LOCALVERSION="-v7l" +# CONFIG_LOCALVERSION_AUTO is not set +CONFIG_SYSVIPC=y +CONFIG_POSIX_MQUEUE=y +CONFIG_GENERIC_IRQ_DEBUGFS=y +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_BPF_SYSCALL=y +CONFIG_PREEMPT_VOLUNTARY=y +CONFIG_BSD_PROCESS_ACCT=y +CONFIG_BSD_PROCESS_ACCT_V3=y +CONFIG_TASKSTATS=y +CONFIG_TASK_DELAY_ACCT=y +CONFIG_TASK_XACCT=y +CONFIG_TASK_IO_ACCOUNTING=y +CONFIG_PSI=y +CONFIG_PSI_DEFAULT_DISABLED=y +CONFIG_IKCONFIG=m +CONFIG_IKCONFIG_PROC=y +CONFIG_MEMCG=y +CONFIG_BLK_CGROUP=y +CONFIG_CFS_BANDWIDTH=y +CONFIG_CGROUP_PIDS=y +CONFIG_CGROUP_FREEZER=y +CONFIG_CPUSETS=y +CONFIG_CPUSETS_V1=y +CONFIG_CGROUP_DEVICE=y +CONFIG_CGROUP_CPUACCT=y +CONFIG_CGROUP_PERF=y +CONFIG_CGROUP_BPF=y +CONFIG_NAMESPACES=y +CONFIG_USER_NS=y +CONFIG_SCHED_AUTOGROUP=y +CONFIG_BLK_DEV_INITRD=y +CONFIG_EXPERT=y +CONFIG_PROFILING=y +CONFIG_ARCH_BCM=y +CONFIG_ARCH_BCM2835=y +CONFIG_ARM_LPAE=y +# CONFIG_CACHE_L2X0 is not set +CONFIG_SMP=y +CONFIG_HIGHMEM=y +CONFIG_UACCESS_WITH_MEMCPY=y +# CONFIG_ATAGS is not set +CONFIG_CMDLINE="console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 root=/dev/mmcblk0p2 rootfstype=ext4 rootwait" +CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_STAT=y +CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND=y +CONFIG_CPU_FREQ_GOV_POWERSAVE=y +CONFIG_CPU_FREQ_GOV_USERSPACE=y +CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y +CONFIG_CPU_FREQ_GOV_SCHEDUTIL=y +CONFIG_CPUFREQ_DT=y +CONFIG_ARM_RASPBERRYPI_CPUFREQ=y +CONFIG_VFP=y +CONFIG_NEON=y +CONFIG_KERNEL_MODE_NEON=y +# CONFIG_SUSPEND is not set +CONFIG_PM=y +CONFIG_JUMP_LABEL=y +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_MODVERSIONS=y +CONFIG_MODULE_SRCVERSION_ALL=y +CONFIG_MODULE_COMPRESS=y +CONFIG_MODULE_COMPRESS_XZ=y +CONFIG_BLK_DEV_THROTTLING=y +CONFIG_PARTITION_ADVANCED=y +CONFIG_MAC_PARTITION=y +CONFIG_BINFMT_MISC=m +CONFIG_ZSWAP=y +# CONFIG_COMPAT_BRK is not set +CONFIG_CMA=y +CONFIG_CMA_AREAS=7 +CONFIG_LRU_GEN=y +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_XFRM_USER=y +CONFIG_XFRM_INTERFACE=m +CONFIG_XFRM_SUB_POLICY=y +CONFIG_XFRM_STATISTICS=y +CONFIG_NET_KEY=m +CONFIG_IP_MULTICAST=y +CONFIG_IP_ADVANCED_ROUTER=y +CONFIG_IP_MULTIPLE_TABLES=y +CONFIG_IP_ROUTE_MULTIPATH=y +CONFIG_IP_ROUTE_VERBOSE=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y +CONFIG_IP_PNP_RARP=y +CONFIG_NET_IPIP=m +CONFIG_NET_IPGRE_DEMUX=m +CONFIG_NET_IPGRE=m +CONFIG_IP_MROUTE=y +CONFIG_IP_MROUTE_MULTIPLE_TABLES=y +CONFIG_IP_PIMSM_V1=y +CONFIG_IP_PIMSM_V2=y +CONFIG_NET_IPVTI=m +CONFIG_NET_FOU_IP_TUNNELS=y +CONFIG_INET_AH=m +CONFIG_INET_ESP=m +CONFIG_INET_IPCOMP=m +CONFIG_INET_DIAG=m +CONFIG_TCP_CONG_ADVANCED=y +CONFIG_TCP_CONG_BBR=m +CONFIG_IPV6=m +CONFIG_IPV6_ROUTER_PREF=y +CONFIG_IPV6_ROUTE_INFO=y +CONFIG_INET6_AH=m +CONFIG_INET6_ESP=m +CONFIG_INET6_ESP_OFFLOAD=m +CONFIG_INET6_IPCOMP=m +CONFIG_IPV6_ILA=m +CONFIG_IPV6_VTI=m +CONFIG_IPV6_SIT_6RD=y +CONFIG_IPV6_GRE=m +CONFIG_IPV6_MULTIPLE_TABLES=y +CONFIG_IPV6_SUBTREES=y +CONFIG_IPV6_MROUTE=y +CONFIG_IPV6_MROUTE_MULTIPLE_TABLES=y +CONFIG_IPV6_PIMSM_V2=y +CONFIG_NETWORK_PHY_TIMESTAMPING=y +CONFIG_NETFILTER=y +CONFIG_BRIDGE_NETFILTER=m +CONFIG_NF_CONNTRACK=m +CONFIG_NF_CONNTRACK_ZONES=y +CONFIG_NF_CONNTRACK_EVENTS=y +CONFIG_NF_CONNTRACK_TIMESTAMP=y +CONFIG_NF_CONNTRACK_AMANDA=m +CONFIG_NF_CONNTRACK_FTP=m +CONFIG_NF_CONNTRACK_H323=m +CONFIG_NF_CONNTRACK_IRC=m +CONFIG_NF_CONNTRACK_NETBIOS_NS=m +CONFIG_NF_CONNTRACK_SNMP=m +CONFIG_NF_CONNTRACK_PPTP=m +CONFIG_NF_CONNTRACK_SANE=m +CONFIG_NF_CONNTRACK_SIP=m +CONFIG_NF_CONNTRACK_TFTP=m +CONFIG_NF_CT_NETLINK=m +CONFIG_NF_TABLES=m +CONFIG_NF_TABLES_INET=y +CONFIG_NF_TABLES_NETDEV=y +CONFIG_NFT_NUMGEN=m +CONFIG_NFT_CT=m +CONFIG_NFT_FLOW_OFFLOAD=m +CONFIG_NFT_CONNLIMIT=m +CONFIG_NFT_LOG=m +CONFIG_NFT_LIMIT=m +CONFIG_NFT_MASQ=m +CONFIG_NFT_REDIR=m +CONFIG_NFT_NAT=m +CONFIG_NFT_TUNNEL=m +CONFIG_NFT_QUEUE=m +CONFIG_NFT_QUOTA=m +CONFIG_NFT_REJECT=m +CONFIG_NFT_COMPAT=m +CONFIG_NFT_HASH=m +CONFIG_NFT_FIB_INET=m +CONFIG_NFT_XFRM=m +CONFIG_NFT_SOCKET=m +CONFIG_NFT_OSF=m +CONFIG_NFT_TPROXY=m +CONFIG_NFT_SYNPROXY=m +CONFIG_NFT_DUP_NETDEV=m +CONFIG_NFT_FWD_NETDEV=m +CONFIG_NFT_FIB_NETDEV=m +CONFIG_NF_FLOW_TABLE_INET=m +CONFIG_NF_FLOW_TABLE=m +CONFIG_NETFILTER_XT_SET=m +CONFIG_NETFILTER_XT_TARGET_CHECKSUM=m +CONFIG_NETFILTER_XT_TARGET_CLASSIFY=m +CONFIG_NETFILTER_XT_TARGET_CONNMARK=m +CONFIG_NETFILTER_XT_TARGET_DSCP=m +CONFIG_NETFILTER_XT_TARGET_HMARK=m +CONFIG_NETFILTER_XT_TARGET_IDLETIMER=m +CONFIG_NETFILTER_XT_TARGET_LED=m +CONFIG_NETFILTER_XT_TARGET_LOG=m +CONFIG_NETFILTER_XT_TARGET_MARK=m +CONFIG_NETFILTER_XT_TARGET_NFLOG=m +CONFIG_NETFILTER_XT_TARGET_NFQUEUE=m +CONFIG_NETFILTER_XT_TARGET_NOTRACK=m +CONFIG_NETFILTER_XT_TARGET_TEE=m +CONFIG_NETFILTER_XT_TARGET_TPROXY=m +CONFIG_NETFILTER_XT_TARGET_TRACE=m +CONFIG_NETFILTER_XT_TARGET_TCPMSS=m +CONFIG_NETFILTER_XT_TARGET_TCPOPTSTRIP=m +CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=m +CONFIG_NETFILTER_XT_MATCH_BPF=m +CONFIG_NETFILTER_XT_MATCH_CLUSTER=m +CONFIG_NETFILTER_XT_MATCH_COMMENT=m +CONFIG_NETFILTER_XT_MATCH_CONNBYTES=m +CONFIG_NETFILTER_XT_MATCH_CONNLABEL=m +CONFIG_NETFILTER_XT_MATCH_CONNLIMIT=m +CONFIG_NETFILTER_XT_MATCH_CONNMARK=m +CONFIG_NETFILTER_XT_MATCH_CONNTRACK=m +CONFIG_NETFILTER_XT_MATCH_CPU=m +CONFIG_NETFILTER_XT_MATCH_DCCP=m +CONFIG_NETFILTER_XT_MATCH_DEVGROUP=m +CONFIG_NETFILTER_XT_MATCH_DSCP=m +CONFIG_NETFILTER_XT_MATCH_ESP=m +CONFIG_NETFILTER_XT_MATCH_HASHLIMIT=m +CONFIG_NETFILTER_XT_MATCH_HELPER=m +CONFIG_NETFILTER_XT_MATCH_IPRANGE=m +CONFIG_NETFILTER_XT_MATCH_IPVS=m +CONFIG_NETFILTER_XT_MATCH_LENGTH=m +CONFIG_NETFILTER_XT_MATCH_LIMIT=m +CONFIG_NETFILTER_XT_MATCH_MAC=m +CONFIG_NETFILTER_XT_MATCH_MARK=m +CONFIG_NETFILTER_XT_MATCH_MULTIPORT=m +CONFIG_NETFILTER_XT_MATCH_NFACCT=m +CONFIG_NETFILTER_XT_MATCH_OSF=m +CONFIG_NETFILTER_XT_MATCH_OWNER=m +CONFIG_NETFILTER_XT_MATCH_POLICY=m +CONFIG_NETFILTER_XT_MATCH_PHYSDEV=m +CONFIG_NETFILTER_XT_MATCH_PKTTYPE=m +CONFIG_NETFILTER_XT_MATCH_QUOTA=m +CONFIG_NETFILTER_XT_MATCH_RATEEST=m +CONFIG_NETFILTER_XT_MATCH_REALM=m +CONFIG_NETFILTER_XT_MATCH_RECENT=m +CONFIG_NETFILTER_XT_MATCH_SOCKET=m +CONFIG_NETFILTER_XT_MATCH_STATE=m +CONFIG_NETFILTER_XT_MATCH_STATISTIC=m +CONFIG_NETFILTER_XT_MATCH_STRING=m +CONFIG_NETFILTER_XT_MATCH_TCPMSS=m +CONFIG_NETFILTER_XT_MATCH_TIME=m +CONFIG_NETFILTER_XT_MATCH_U32=m +CONFIG_IP_SET=m +CONFIG_IP_SET_BITMAP_IP=m +CONFIG_IP_SET_BITMAP_IPMAC=m +CONFIG_IP_SET_BITMAP_PORT=m +CONFIG_IP_SET_HASH_IP=m +CONFIG_IP_SET_HASH_IPPORT=m +CONFIG_IP_SET_HASH_IPPORTIP=m +CONFIG_IP_SET_HASH_IPPORTNET=m +CONFIG_IP_SET_HASH_NET=m +CONFIG_IP_SET_HASH_NETPORT=m +CONFIG_IP_SET_HASH_NETIFACE=m +CONFIG_IP_SET_LIST_SET=m +CONFIG_IP_VS=m +CONFIG_IP_VS_IPV6=y +CONFIG_IP_VS_PROTO_TCP=y +CONFIG_IP_VS_PROTO_UDP=y +CONFIG_IP_VS_PROTO_ESP=y +CONFIG_IP_VS_PROTO_AH=y +CONFIG_IP_VS_PROTO_SCTP=y +CONFIG_IP_VS_RR=m +CONFIG_IP_VS_WRR=m +CONFIG_IP_VS_LC=m +CONFIG_IP_VS_WLC=m +CONFIG_IP_VS_LBLC=m +CONFIG_IP_VS_LBLCR=m +CONFIG_IP_VS_DH=m +CONFIG_IP_VS_SH=m +CONFIG_IP_VS_SED=m +CONFIG_IP_VS_NQ=m +CONFIG_IP_VS_FTP=m +CONFIG_IP_VS_PE_SIP=m +CONFIG_NFT_DUP_IPV4=m +CONFIG_NFT_FIB_IPV4=m +CONFIG_NF_TABLES_ARP=y +CONFIG_NF_LOG_ARP=m +CONFIG_NF_LOG_IPV4=m +CONFIG_IP_NF_IPTABLES=m +CONFIG_IP_NF_MATCH_AH=m +CONFIG_IP_NF_MATCH_ECN=m +CONFIG_IP_NF_MATCH_RPFILTER=m +CONFIG_IP_NF_MATCH_TTL=m +CONFIG_IP_NF_FILTER=m +CONFIG_IP_NF_TARGET_REJECT=m +CONFIG_IP_NF_TARGET_SYNPROXY=m +CONFIG_IP_NF_NAT=m +CONFIG_IP_NF_TARGET_MASQUERADE=m +CONFIG_IP_NF_TARGET_NETMAP=m +CONFIG_IP_NF_TARGET_REDIRECT=m +CONFIG_IP_NF_MANGLE=m +CONFIG_IP_NF_TARGET_ECN=m +CONFIG_IP_NF_TARGET_TTL=m +CONFIG_IP_NF_RAW=m +CONFIG_IP_NF_ARPFILTER=m +CONFIG_IP_NF_ARP_MANGLE=m +CONFIG_NFT_DUP_IPV6=m +CONFIG_NFT_FIB_IPV6=m +CONFIG_IP6_NF_IPTABLES=m +CONFIG_IP6_NF_MATCH_AH=m +CONFIG_IP6_NF_MATCH_EUI64=m +CONFIG_IP6_NF_MATCH_FRAG=m +CONFIG_IP6_NF_MATCH_OPTS=m +CONFIG_IP6_NF_MATCH_HL=m +CONFIG_IP6_NF_MATCH_IPV6HEADER=m +CONFIG_IP6_NF_MATCH_MH=m +CONFIG_IP6_NF_MATCH_RPFILTER=m +CONFIG_IP6_NF_MATCH_RT=m +CONFIG_IP6_NF_MATCH_SRH=m +CONFIG_IP6_NF_TARGET_HL=m +CONFIG_IP6_NF_FILTER=m +CONFIG_IP6_NF_TARGET_REJECT=m +CONFIG_IP6_NF_TARGET_SYNPROXY=m +CONFIG_IP6_NF_MANGLE=m +CONFIG_IP6_NF_RAW=m +CONFIG_IP6_NF_SECURITY=m +CONFIG_IP6_NF_NAT=m +CONFIG_IP6_NF_TARGET_MASQUERADE=m +CONFIG_IP6_NF_TARGET_NPT=m +CONFIG_NF_TABLES_BRIDGE=m +CONFIG_NFT_BRIDGE_REJECT=m +CONFIG_BRIDGE_NF_EBTABLES=m +CONFIG_BRIDGE_EBT_BROUTE=m +CONFIG_BRIDGE_EBT_T_FILTER=m +CONFIG_BRIDGE_EBT_T_NAT=m +CONFIG_BRIDGE_EBT_802_3=m +CONFIG_BRIDGE_EBT_AMONG=m +CONFIG_BRIDGE_EBT_ARP=m +CONFIG_BRIDGE_EBT_IP=m +CONFIG_BRIDGE_EBT_IP6=m +CONFIG_BRIDGE_EBT_LIMIT=m +CONFIG_BRIDGE_EBT_MARK=m +CONFIG_BRIDGE_EBT_PKTTYPE=m +CONFIG_BRIDGE_EBT_STP=m +CONFIG_BRIDGE_EBT_VLAN=m +CONFIG_BRIDGE_EBT_ARPREPLY=m +CONFIG_BRIDGE_EBT_DNAT=m +CONFIG_BRIDGE_EBT_MARK_T=m +CONFIG_BRIDGE_EBT_REDIRECT=m +CONFIG_BRIDGE_EBT_SNAT=m +CONFIG_BRIDGE_EBT_LOG=m +CONFIG_BRIDGE_EBT_NFLOG=m +CONFIG_SCTP_COOKIE_HMAC_SHA1=y +CONFIG_ATM=m +CONFIG_L2TP=m +CONFIG_L2TP_V3=y +CONFIG_L2TP_IP=m +CONFIG_L2TP_ETH=m +CONFIG_BRIDGE=m +CONFIG_BRIDGE_VLAN_FILTERING=y +CONFIG_VLAN_8021Q=m +CONFIG_VLAN_8021Q_GVRP=y +CONFIG_ATALK=m +CONFIG_6LOWPAN=m +CONFIG_IEEE802154=m +CONFIG_IEEE802154_6LOWPAN=m +CONFIG_MAC802154=m +CONFIG_NET_SCHED=y +CONFIG_NET_SCH_HTB=m +CONFIG_NET_SCH_HFSC=m +CONFIG_NET_SCH_PRIO=m +CONFIG_NET_SCH_MULTIQ=m +CONFIG_NET_SCH_RED=m +CONFIG_NET_SCH_SFB=m +CONFIG_NET_SCH_SFQ=m +CONFIG_NET_SCH_TEQL=m +CONFIG_NET_SCH_TBF=m +CONFIG_NET_SCH_GRED=m +CONFIG_NET_SCH_NETEM=m +CONFIG_NET_SCH_DRR=m +CONFIG_NET_SCH_MQPRIO=m +CONFIG_NET_SCH_CHOKE=m +CONFIG_NET_SCH_QFQ=m +CONFIG_NET_SCH_CODEL=m +CONFIG_NET_SCH_FQ_CODEL=m +CONFIG_NET_SCH_CAKE=m +CONFIG_NET_SCH_FQ=m +CONFIG_NET_SCH_HHF=m +CONFIG_NET_SCH_PIE=m +CONFIG_NET_SCH_INGRESS=m +CONFIG_NET_SCH_PLUG=m +CONFIG_NET_CLS_BASIC=m +CONFIG_NET_CLS_ROUTE4=m +CONFIG_NET_CLS_FW=m +CONFIG_NET_CLS_U32=m +CONFIG_CLS_U32_MARK=y +CONFIG_NET_CLS_FLOW=m +CONFIG_NET_CLS_CGROUP=m +CONFIG_NET_EMATCH=y +CONFIG_NET_EMATCH_CMP=m +CONFIG_NET_EMATCH_NBYTE=m +CONFIG_NET_EMATCH_U32=m +CONFIG_NET_EMATCH_META=m +CONFIG_NET_EMATCH_TEXT=m +CONFIG_NET_EMATCH_IPSET=m +CONFIG_NET_CLS_ACT=y +CONFIG_NET_ACT_POLICE=m +CONFIG_NET_ACT_GACT=m +CONFIG_GACT_PROB=y +CONFIG_NET_ACT_MIRRED=m +CONFIG_NET_ACT_NAT=m +CONFIG_NET_ACT_PEDIT=m +CONFIG_NET_ACT_SIMP=m +CONFIG_NET_ACT_SKBEDIT=m +CONFIG_NET_ACT_CSUM=m +CONFIG_BATMAN_ADV=m +CONFIG_OPENVSWITCH=m +CONFIG_CGROUP_NET_PRIO=y +CONFIG_NET_PKTGEN=m +CONFIG_HAMRADIO=y +CONFIG_AX25=m +CONFIG_NETROM=m +CONFIG_ROSE=m +CONFIG_MKISS=m +CONFIG_6PACK=m +CONFIG_BPQETHER=m +CONFIG_BAYCOM_SER_FDX=m +CONFIG_BAYCOM_SER_HDX=m +CONFIG_YAM=m +CONFIG_CAN=m +CONFIG_CAN_J1939=m +CONFIG_CAN_ISOTP=m +CONFIG_BT=m +CONFIG_BT_RFCOMM=m +CONFIG_BT_RFCOMM_TTY=y +CONFIG_BT_BNEP=m +CONFIG_BT_BNEP_MC_FILTER=y +CONFIG_BT_BNEP_PROTO_FILTER=y +CONFIG_BT_HIDP=m +CONFIG_BT_6LOWPAN=m +CONFIG_BT_HCIBTUSB=m +CONFIG_BT_HCIUART=m +CONFIG_BT_HCIUART_3WIRE=y +CONFIG_BT_HCIUART_BCM=y +CONFIG_BT_HCIBCM203X=m +CONFIG_BT_HCIBPA10X=m +CONFIG_BT_HCIBFUSB=m +CONFIG_BT_HCIVHCI=m +CONFIG_BT_MRVL=m +CONFIG_BT_MRVL_SDIO=m +CONFIG_BT_ATH3K=m +CONFIG_CFG80211=m +CONFIG_CFG80211_WEXT=y +CONFIG_MAC80211=m +CONFIG_MAC80211_MESH=y +CONFIG_RFKILL=m +CONFIG_RFKILL_INPUT=y +CONFIG_NET_9P=m +CONFIG_NFC=m +CONFIG_PCI=y +CONFIG_PCIEPORTBUS=y +# CONFIG_PCIEASPM is not set +CONFIG_PCI_MSI=y +CONFIG_PCIE_BRCMSTB=y +CONFIG_UEVENT_HELPER=y +CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y +CONFIG_RASPBERRYPI_FIRMWARE=y +CONFIG_MTD=m +CONFIG_MTD_BLOCK=m +CONFIG_MTD_BLOCK2MTD=m +CONFIG_MTD_SPI_NAND=m +CONFIG_MTD_SPI_NOR=m +CONFIG_MTD_UBI=m +CONFIG_OF_CONFIGFS=y +CONFIG_ZRAM=m +CONFIG_ZRAM_BACKEND_LZ4=y +CONFIG_ZRAM_BACKEND_ZSTD=y +CONFIG_ZRAM_BACKEND_LZO=y +CONFIG_ZRAM_DEF_COMP_ZSTD=y +CONFIG_ZRAM_WRITEBACK=y +CONFIG_ZRAM_MULTI_COMP=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_DRBD=m +CONFIG_BLK_DEV_NBD=m +CONFIG_BLK_DEV_RAM=y +CONFIG_ATA_OVER_ETH=m +CONFIG_BLK_DEV_RBD=m +CONFIG_BLK_DEV_NVME=y +CONFIG_EEPROM_AT24=m +CONFIG_EEPROM_AT25=m +CONFIG_TI_ST=m +CONFIG_SCSI=y +# CONFIG_SCSI_PROC_FS is not set +CONFIG_BLK_DEV_SD=y +CONFIG_CHR_DEV_ST=m +CONFIG_BLK_DEV_SR=m +CONFIG_CHR_DEV_SG=m +CONFIG_SCSI_ISCSI_ATTRS=y +CONFIG_ISCSI_TCP=m +CONFIG_ISCSI_BOOT_SYSFS=m +CONFIG_ATA=m +CONFIG_SATA_AHCI=m +CONFIG_SATA_MV=m +CONFIG_MD=y +CONFIG_BCACHE=m +CONFIG_BLK_DEV_DM=m +CONFIG_DM_CRYPT=m +CONFIG_DM_SNAPSHOT=m +CONFIG_DM_THIN_PROVISIONING=m +CONFIG_DM_CACHE=m +CONFIG_DM_WRITECACHE=m +CONFIG_DM_MIRROR=m +CONFIG_DM_LOG_USERSPACE=m +CONFIG_DM_RAID=m +CONFIG_DM_ZERO=m +CONFIG_DM_MULTIPATH=m +CONFIG_DM_DELAY=m +CONFIG_DM_VERITY=m +CONFIG_DM_INTEGRITY=m +CONFIG_NETDEVICES=y +CONFIG_BONDING=m +CONFIG_DUMMY=m +CONFIG_WIREGUARD=m +CONFIG_IFB=m +CONFIG_MACVLAN=m +CONFIG_IPVLAN=m +CONFIG_VXLAN=m +CONFIG_NETCONSOLE=m +CONFIG_TUN=m +CONFIG_VETH=m +CONFIG_NETKIT=y +CONFIG_NET_VRF=m +CONFIG_BCMGENET=y +CONFIG_IGB=m +CONFIG_IXGBE=m +CONFIG_I40E=m +CONFIG_IGC=m +CONFIG_ENC28J60=m +CONFIG_LAN743X=m +CONFIG_QCA7000_SPI=m +CONFIG_QCA7000_UART=m +CONFIG_R8169=m +CONFIG_MSE102X=m +CONFIG_WIZNET_W5100=m +CONFIG_WIZNET_W5100_SPI=m +CONFIG_MICREL_PHY=y +CONFIG_CAN_VCAN=m +CONFIG_CAN_SLCAN=m +CONFIG_CAN_MCP251X=m +CONFIG_CAN_MCP251XFD=m +CONFIG_CAN_8DEV_USB=m +CONFIG_CAN_EMS_USB=m +CONFIG_CAN_GS_USB=m +CONFIG_CAN_PEAK_USB=m +CONFIG_MDIO_BITBANG=m +CONFIG_PPP=m +CONFIG_PPP_BSDCOMP=m +CONFIG_PPP_DEFLATE=m +CONFIG_PPP_FILTER=y +CONFIG_PPP_MPPE=m +CONFIG_PPP_MULTILINK=y +CONFIG_PPPOATM=m +CONFIG_PPPOE=m +CONFIG_PPPOL2TP=m +CONFIG_PPP_ASYNC=m +CONFIG_PPP_SYNC_TTY=m +CONFIG_SLIP=m +CONFIG_SLIP_COMPRESSED=y +CONFIG_SLIP_SMART=y +CONFIG_USB_CATC=m +CONFIG_USB_KAWETH=m +CONFIG_USB_PEGASUS=m +CONFIG_USB_RTL8150=m +CONFIG_USB_RTL8152=y +CONFIG_USB_LAN78XX=y +CONFIG_USB_USBNET=y +CONFIG_USB_NET_AX8817X=m +CONFIG_USB_NET_AX88179_178A=m +CONFIG_USB_NET_CDCETHER=m +CONFIG_USB_NET_CDC_EEM=m +CONFIG_USB_NET_CDC_NCM=m +CONFIG_USB_NET_HUAWEI_CDC_NCM=m +CONFIG_USB_NET_CDC_MBIM=m +CONFIG_USB_NET_DM9601=m +CONFIG_USB_NET_SR9700=m +CONFIG_USB_NET_SR9800=m +CONFIG_USB_NET_SMSC75XX=m +CONFIG_USB_NET_SMSC95XX=y +CONFIG_USB_NET_GL620A=m +CONFIG_USB_NET_NET1080=m +CONFIG_USB_NET_PLUSB=m +CONFIG_USB_NET_MCS7830=m +CONFIG_USB_NET_RNDIS_HOST=m +CONFIG_USB_NET_CDC_SUBSET=m +CONFIG_USB_ALI_M5632=y +CONFIG_USB_AN2720=y +CONFIG_USB_EPSON2888=y +CONFIG_USB_KC2190=y +CONFIG_USB_NET_ZAURUS=m +CONFIG_USB_NET_CX82310_ETH=m +CONFIG_USB_NET_KALMIA=m +CONFIG_USB_NET_QMI_WWAN=m +CONFIG_USB_HSO=m +CONFIG_USB_NET_INT51X1=m +CONFIG_USB_IPHETH=m +CONFIG_USB_SIERRA_NET=m +CONFIG_USB_VL600=m +CONFIG_USB_NET_AQC111=m +CONFIG_ATH9K=m +CONFIG_ATH9K_HTC=m +CONFIG_CARL9170=m +CONFIG_ATH6KL=m +CONFIG_ATH6KL_USB=m +CONFIG_AR5523=m +CONFIG_AT76C50X_USB=m +CONFIG_B43=m +# CONFIG_B43_PHY_N is not set +CONFIG_B43LEGACY=m +CONFIG_BRCMFMAC=m +CONFIG_BRCMFMAC_USB=y +CONFIG_BRCMDBG=y +CONFIG_IWLWIFI=m +CONFIG_IWLDVM=m +CONFIG_IWLMVM=m +CONFIG_P54_COMMON=m +CONFIG_P54_USB=m +CONFIG_LIBERTAS=m +CONFIG_LIBERTAS_USB=m +CONFIG_LIBERTAS_SDIO=m +CONFIG_LIBERTAS_THINFIRM=m +CONFIG_LIBERTAS_THINFIRM_USB=m +CONFIG_MWIFIEX=m +CONFIG_MWIFIEX_SDIO=m +CONFIG_MT7601U=m +CONFIG_MT76x0U=m +CONFIG_MT76x2U=m +CONFIG_MT7921U=m +CONFIG_RT2X00=m +CONFIG_RT2500USB=m +CONFIG_RT73USB=m +CONFIG_RT2800USB=m +CONFIG_RT2800USB_RT3573=y +CONFIG_RT2800USB_RT53XX=y +CONFIG_RT2800USB_RT55XX=y +CONFIG_RT2800USB_UNKNOWN=y +CONFIG_RTL8187=m +CONFIG_RTL8192CU=m +CONFIG_RTL8XXXU=m +CONFIG_RTW88=m +CONFIG_RTW88_8822BU=m +CONFIG_RTW88_8822CU=m +CONFIG_RTW88_8723DU=m +CONFIG_RTW88_8821CU=m +CONFIG_ZD1211RW=m +CONFIG_MAC80211_HWSIM=m +CONFIG_IEEE802154_AT86RF230=m +CONFIG_IEEE802154_MRF24J40=m +CONFIG_IEEE802154_CC2520=m +CONFIG_INPUT_MOUSEDEV=y +CONFIG_INPUT_JOYDEV=m +CONFIG_INPUT_EVDEV=y +# CONFIG_KEYBOARD_ATKBD is not set +CONFIG_KEYBOARD_GPIO=m +CONFIG_KEYBOARD_TCA6416=m +CONFIG_KEYBOARD_TCA8418=m +CONFIG_KEYBOARD_MATRIX=m +CONFIG_KEYBOARD_CAP11XX=m +# CONFIG_INPUT_MOUSE is not set +CONFIG_INPUT_JOYSTICK=y +CONFIG_JOYSTICK_IFORCE=m +CONFIG_JOYSTICK_IFORCE_USB=m +CONFIG_JOYSTICK_XPAD=m +CONFIG_JOYSTICK_XPAD_FF=y +CONFIG_JOYSTICK_XPAD_LEDS=y +CONFIG_JOYSTICK_PSXPAD_SPI=m +CONFIG_JOYSTICK_PSXPAD_SPI_FF=y +CONFIG_JOYSTICK_FSIA6B=m +CONFIG_JOYSTICK_SENSEHAT=m +CONFIG_INPUT_TOUCHSCREEN=y +CONFIG_TOUCHSCREEN_ADS7846=m +CONFIG_TOUCHSCREEN_EGALAX=m +CONFIG_TOUCHSCREEN_EXC3000=m +CONFIG_TOUCHSCREEN_GOODIX=m +CONFIG_TOUCHSCREEN_ILI210X=m +CONFIG_TOUCHSCREEN_EDT_FT5X06=m +CONFIG_TOUCHSCREEN_RASPBERRYPI_FW=m +CONFIG_TOUCHSCREEN_USB_COMPOSITE=m +CONFIG_TOUCHSCREEN_TSC2007=m +CONFIG_TOUCHSCREEN_TSC2007_IIO=y +CONFIG_TOUCHSCREEN_STMPE=m +CONFIG_TOUCHSCREEN_IQS5XX=m +CONFIG_INPUT_MISC=y +CONFIG_INPUT_AD714X=m +CONFIG_INPUT_ATI_REMOTE2=m +CONFIG_INPUT_KEYSPAN_REMOTE=m +CONFIG_INPUT_POWERMATE=m +CONFIG_INPUT_YEALINK=m +CONFIG_INPUT_CM109=m +CONFIG_INPUT_UINPUT=m +CONFIG_INPUT_GPIO_ROTARY_ENCODER=m +CONFIG_INPUT_ADXL34X=m +CONFIG_INPUT_CMA3000=m +CONFIG_SERIO=m +CONFIG_SERIO_RAW=m +CONFIG_GAMEPORT=m +CONFIG_BRCM_CHAR_DRIVERS=y +CONFIG_BCM_VCIO=y +# CONFIG_LEGACY_PTYS is not set +CONFIG_SERIAL_8250=y +# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set +CONFIG_SERIAL_8250_CONSOLE=y +# CONFIG_SERIAL_8250_DMA is not set +CONFIG_SERIAL_8250_NR_UARTS=5 +CONFIG_SERIAL_8250_RUNTIME_UARTS=0 +CONFIG_SERIAL_8250_EXTENDED=y +CONFIG_SERIAL_8250_SHARE_IRQ=y +CONFIG_SERIAL_8250_BCM2835AUX=y +CONFIG_SERIAL_OF_PLATFORM=y +CONFIG_SERIAL_AMBA_PL011=y +CONFIG_SERIAL_AMBA_PL011_CONSOLE=y +CONFIG_SERIAL_SC16IS7XX=m +CONFIG_SERIAL_RPI_FW=m +CONFIG_SERIAL_DEV_BUS=y +CONFIG_TTY_PRINTK=y +CONFIG_HW_RANDOM=y +CONFIG_TCG_TPM=m +CONFIG_TCG_TIS_SPI=m +CONFIG_TCG_TIS_I2C=m +CONFIG_XILLYBUS=m +CONFIG_XILLYBUS_PCIE=m +CONFIG_XILLYUSB=m +CONFIG_RASPBERRYPI_GPIOMEM=m +CONFIG_I2C=y +CONFIG_I2C_CHARDEV=m +CONFIG_I2C_MUX_GPMUX=m +CONFIG_I2C_MUX_PCA954x=m +CONFIG_I2C_MUX_PINCTRL=m +CONFIG_I2C_BCM2708=m +CONFIG_I2C_BCM2835=m +CONFIG_I2C_BRCMSTB=m +CONFIG_I2C_GPIO=m +CONFIG_I2C_ROBOTFUZZ_OSIF=m +CONFIG_I2C_TINY_USB=m +CONFIG_SPI=y +CONFIG_SPI_BCM2835=m +CONFIG_SPI_BCM2835AUX=m +CONFIG_SPI_GPIO=m +CONFIG_SPI_RP2040_GPIO_BRIDGE=m +CONFIG_SPI_SPIDEV=m +CONFIG_SPI_SLAVE=y +CONFIG_PPS_CLIENT_LDISC=m +CONFIG_PPS_CLIENT_GPIO=m +CONFIG_PINCTRL_MCP23S08=m +CONFIG_GPIO_SYSFS=y +CONFIG_GPIO_BCM_VIRT=y +CONFIG_GPIO_MAX7300=m +CONFIG_GPIO_PCA953X=m +CONFIG_GPIO_PCA953X_IRQ=y +CONFIG_GPIO_PCF857X=m +CONFIG_GPIO_ARIZONA=m +CONFIG_GPIO_FSM=m +CONFIG_GPIO_STMPE=y +CONFIG_GPIO_MAX7301=m +CONFIG_GPIO_MOCKUP=m +CONFIG_W1=m +CONFIG_W1_MASTER_DS2490=m +CONFIG_W1_MASTER_DS2482=m +CONFIG_W1_MASTER_GPIO=m +CONFIG_W1_SLAVE_THERM=m +CONFIG_W1_SLAVE_SMEM=m +CONFIG_W1_SLAVE_DS2408=m +CONFIG_W1_SLAVE_DS2413=m +CONFIG_W1_SLAVE_DS2406=m +CONFIG_W1_SLAVE_DS2423=m +CONFIG_W1_SLAVE_DS2431=m +CONFIG_W1_SLAVE_DS2433=m +CONFIG_W1_SLAVE_DS2438=m +CONFIG_W1_SLAVE_DS2780=m +CONFIG_W1_SLAVE_DS2781=m +CONFIG_W1_SLAVE_DS28E04=m +CONFIG_W1_SLAVE_DS28E17=m +CONFIG_POWER_RESET=y +CONFIG_POWER_RESET_GPIO=y +CONFIG_RPI_POE_POWER=m +CONFIG_BATTERY_DS2760=m +CONFIG_BATTERY_MAX17040=m +CONFIG_CHARGER_GPIO=m +CONFIG_BATTERY_GAUGE_LTC2941=m +CONFIG_SENSORS_ADT7410=m +CONFIG_SENSORS_AHT10=m +CONFIG_SENSORS_CHIPCAP2=m +CONFIG_SENSORS_DRIVETEMP=m +CONFIG_SENSORS_DS1621=m +CONFIG_SENSORS_GPIO_FAN=m +CONFIG_SENSORS_IIO_HWMON=m +CONFIG_SENSORS_JC42=m +CONFIG_SENSORS_LM75=m +CONFIG_SENSORS_PWM_FAN=m +CONFIG_SENSORS_RASPBERRYPI_HWMON=m +CONFIG_SENSORS_SHT21=m +CONFIG_SENSORS_SHT3x=m +CONFIG_SENSORS_SHT4x=m +CONFIG_SENSORS_SHTC1=m +CONFIG_SENSORS_EMC2305=m +CONFIG_SENSORS_INA2XX=m +CONFIG_SENSORS_INA238=m +CONFIG_SENSORS_TMP102=m +CONFIG_BCM2711_THERMAL=y +CONFIG_WATCHDOG=y +CONFIG_GPIO_WATCHDOG=m +CONFIG_BCM2835_WDT=y +CONFIG_MFD_RASPBERRYPI_POE_HAT=m +CONFIG_MFD_STMPE=y +CONFIG_STMPE_SPI=y +CONFIG_MFD_ARIZONA_I2C=m +CONFIG_MFD_ARIZONA_SPI=m +CONFIG_MFD_WM5102=y +CONFIG_REGULATOR=y +CONFIG_REGULATOR_FIXED_VOLTAGE=y +CONFIG_REGULATOR_ARIZONA_LDO1=m +CONFIG_REGULATOR_ARIZONA_MICSUPP=m +CONFIG_REGULATOR_GPIO=y +CONFIG_REGULATOR_RASPBERRYPI_TOUCHSCREEN_ATTINY=m +CONFIG_REGULATOR_RASPBERRYPI_TOUCHSCREEN_V2=m +CONFIG_RC_CORE=y +CONFIG_BPF_LIRC_MODE2=y +CONFIG_LIRC=y +CONFIG_RC_DECODERS=y +CONFIG_IR_IMON_DECODER=m +CONFIG_IR_JVC_DECODER=m +CONFIG_IR_MCE_KBD_DECODER=m +CONFIG_IR_NEC_DECODER=m +CONFIG_IR_RC5_DECODER=m +CONFIG_IR_RC6_DECODER=m +CONFIG_IR_SANYO_DECODER=m +CONFIG_IR_SHARP_DECODER=m +CONFIG_IR_SONY_DECODER=m +CONFIG_IR_XMP_DECODER=m +CONFIG_RC_DEVICES=y +CONFIG_IR_GPIO_CIR=m +CONFIG_IR_GPIO_TX=m +CONFIG_IR_IGUANA=m +CONFIG_IR_IMON=m +CONFIG_IR_MCEUSB=m +CONFIG_IR_PWM_TX=m +CONFIG_IR_REDRAT3=m +CONFIG_IR_STREAMZAP=m +CONFIG_IR_TOY=m +CONFIG_IR_TTUSBIR=m +CONFIG_RC_ATI_REMOTE=m +CONFIG_RC_LOOPBACK=m +CONFIG_MEDIA_CEC_RC=y +CONFIG_MEDIA_SUPPORT=m +CONFIG_MEDIA_USB_SUPPORT=y +CONFIG_USB_GSPCA=m +CONFIG_USB_GSPCA_BENQ=m +CONFIG_USB_GSPCA_CONEX=m +CONFIG_USB_GSPCA_CPIA1=m +CONFIG_USB_GSPCA_DTCS033=m +CONFIG_USB_GSPCA_ETOMS=m +CONFIG_USB_GSPCA_FINEPIX=m +CONFIG_USB_GSPCA_JEILINJ=m +CONFIG_USB_GSPCA_JL2005BCD=m +CONFIG_USB_GSPCA_KINECT=m +CONFIG_USB_GSPCA_KONICA=m +CONFIG_USB_GSPCA_MARS=m +CONFIG_USB_GSPCA_MR97310A=m +CONFIG_USB_GSPCA_NW80X=m +CONFIG_USB_GSPCA_OV519=m +CONFIG_USB_GSPCA_OV534=m +CONFIG_USB_GSPCA_OV534_9=m +CONFIG_USB_GSPCA_PAC207=m +CONFIG_USB_GSPCA_PAC7302=m +CONFIG_USB_GSPCA_PAC7311=m +CONFIG_USB_GSPCA_SE401=m +CONFIG_USB_GSPCA_SN9C2028=m +CONFIG_USB_GSPCA_SN9C20X=m +CONFIG_USB_GSPCA_SONIXB=m +CONFIG_USB_GSPCA_SONIXJ=m +CONFIG_USB_GSPCA_SPCA1528=m +CONFIG_USB_GSPCA_SPCA500=m +CONFIG_USB_GSPCA_SPCA501=m +CONFIG_USB_GSPCA_SPCA505=m +CONFIG_USB_GSPCA_SPCA506=m +CONFIG_USB_GSPCA_SPCA508=m +CONFIG_USB_GSPCA_SPCA561=m +CONFIG_USB_GSPCA_SQ905=m +CONFIG_USB_GSPCA_SQ905C=m +CONFIG_USB_GSPCA_SQ930X=m +CONFIG_USB_GSPCA_STK014=m +CONFIG_USB_GSPCA_STK1135=m +CONFIG_USB_GSPCA_STV0680=m +CONFIG_USB_GSPCA_SUNPLUS=m +CONFIG_USB_GSPCA_T613=m +CONFIG_USB_GSPCA_TOPRO=m +CONFIG_USB_GSPCA_TOUPTEK=m +CONFIG_USB_GSPCA_TV8532=m +CONFIG_USB_GSPCA_VC032X=m +CONFIG_USB_GSPCA_VICAM=m +CONFIG_USB_GSPCA_XIRLINK_CIT=m +CONFIG_USB_GSPCA_ZC3XX=m +CONFIG_USB_GL860=m +CONFIG_USB_M5602=m +CONFIG_USB_STV06XX=m +CONFIG_USB_PWC=m +CONFIG_USB_S2255=m +CONFIG_VIDEO_USBTV=m +CONFIG_USB_VIDEO_CLASS=m +CONFIG_VIDEO_GO7007=m +CONFIG_VIDEO_GO7007_USB=m +CONFIG_VIDEO_GO7007_USB_S2250_BOARD=m +CONFIG_VIDEO_HDPVR=m +CONFIG_VIDEO_PVRUSB2=m +CONFIG_VIDEO_STK1160=m +CONFIG_VIDEO_AU0828=m +CONFIG_VIDEO_AU0828_RC=y +CONFIG_VIDEO_CX231XX=m +CONFIG_VIDEO_CX231XX_ALSA=m +CONFIG_VIDEO_CX231XX_DVB=m +CONFIG_DVB_AS102=m +CONFIG_DVB_B2C2_FLEXCOP_USB=m +CONFIG_DVB_USB_V2=m +CONFIG_DVB_USB_AF9015=m +CONFIG_DVB_USB_AF9035=m +CONFIG_DVB_USB_ANYSEE=m +CONFIG_DVB_USB_AU6610=m +CONFIG_DVB_USB_AZ6007=m +CONFIG_DVB_USB_CE6230=m +CONFIG_DVB_USB_DVBSKY=m +CONFIG_DVB_USB_EC168=m +CONFIG_DVB_USB_GL861=m +CONFIG_DVB_USB_LME2510=m +CONFIG_DVB_USB_MXL111SF=m +CONFIG_DVB_USB_RTL28XXU=m +CONFIG_DVB_USB=m +CONFIG_DVB_USB_A800=m +CONFIG_DVB_USB_AF9005=m +CONFIG_DVB_USB_AF9005_REMOTE=m +CONFIG_DVB_USB_AZ6027=m +CONFIG_DVB_USB_CINERGY_T2=m +CONFIG_DVB_USB_CXUSB=m +CONFIG_DVB_USB_DIB0700=m +CONFIG_DVB_USB_DIBUSB_MB=m +CONFIG_DVB_USB_DIBUSB_MB_FAULTY=y +CONFIG_DVB_USB_DIBUSB_MC=m +CONFIG_DVB_USB_DIGITV=m +CONFIG_DVB_USB_DTT200U=m +CONFIG_DVB_USB_DTV5100=m +CONFIG_DVB_USB_DW2102=m +CONFIG_DVB_USB_GP8PSK=m +CONFIG_DVB_USB_M920X=m +CONFIG_DVB_USB_NOVA_T_USB2=m +CONFIG_DVB_USB_OPERA1=m +CONFIG_DVB_USB_PCTV452E=m +CONFIG_DVB_USB_TECHNISAT_USB2=m +CONFIG_DVB_USB_TTUSB2=m +CONFIG_DVB_USB_UMT_010=m +CONFIG_DVB_USB_VP702X=m +CONFIG_DVB_USB_VP7045=m +CONFIG_SMS_USB_DRV=m +CONFIG_VIDEO_EM28XX=m +CONFIG_VIDEO_EM28XX_V4L2=m +CONFIG_VIDEO_EM28XX_ALSA=m +CONFIG_VIDEO_EM28XX_DVB=m +CONFIG_MEDIA_PCI_SUPPORT=y +CONFIG_MEDIA_PCI_HAILO=m +CONFIG_RADIO_SAA7706H=m +CONFIG_RADIO_SHARK=m +CONFIG_RADIO_SHARK2=m +CONFIG_RADIO_SI4713=m +CONFIG_RADIO_TEA5764=m +CONFIG_RADIO_TEF6862=m +CONFIG_RADIO_WL1273=m +CONFIG_USB_DSBR=m +CONFIG_USB_KEENE=m +CONFIG_USB_MA901=m +CONFIG_USB_MR800=m +CONFIG_RADIO_SI470X=m +CONFIG_USB_SI470X=m +CONFIG_I2C_SI470X=m +CONFIG_I2C_SI4713=m +CONFIG_RADIO_WL128X=m +CONFIG_V4L_PLATFORM_DRIVERS=y +CONFIG_VIDEO_MUX=m +CONFIG_VIDEO_BCM2835_UNICAM_LEGACY=m +CONFIG_VIDEO_BCM2835_UNICAM=m +CONFIG_VIDEO_ARDUCAM_64MP=m +CONFIG_VIDEO_ARDUCAM_PIVARIETY=m +CONFIG_VIDEO_IMX219=m +CONFIG_VIDEO_IMX258=m +CONFIG_VIDEO_IMX290=m +CONFIG_VIDEO_IMX296=m +CONFIG_VIDEO_IMX415=m +CONFIG_VIDEO_IMX477=m +CONFIG_VIDEO_IMX500=m +CONFIG_VIDEO_IMX519=m +CONFIG_VIDEO_IMX708=m +CONFIG_VIDEO_MT9V011=m +CONFIG_VIDEO_OV2311=m +CONFIG_VIDEO_OV5647=m +CONFIG_VIDEO_OV64A40=m +CONFIG_VIDEO_OV7251=m +CONFIG_VIDEO_OV7640=m +CONFIG_VIDEO_OV9282=m +CONFIG_VIDEO_AD5398=m +CONFIG_VIDEO_AK7375=m +CONFIG_VIDEO_BU64754=m +CONFIG_VIDEO_DW9807_VCM=m +CONFIG_VIDEO_SONY_BTF_MPX=m +CONFIG_VIDEO_UDA1342=m +CONFIG_VIDEO_ADV7180=m +CONFIG_VIDEO_TC358743=m +CONFIG_VIDEO_TVP5150=m +CONFIG_VIDEO_TW2804=m +CONFIG_VIDEO_TW9903=m +CONFIG_VIDEO_TW9906=m +CONFIG_VIDEO_IRS1125=m +CONFIG_VIDEO_I2C=m +CONFIG_AUXDISPLAY=y +CONFIG_HD44780=m +CONFIG_DRM=m +CONFIG_DRM_LOAD_EDID_FIRMWARE=y +CONFIG_DRM_UDL=m +CONFIG_DRM_PANEL_ILITEK_ILI9806E=m +CONFIG_DRM_PANEL_ILITEK_ILI9881C=m +CONFIG_DRM_PANEL_JDI_LT070ME05000=m +CONFIG_DRM_PANEL_RASPBERRYPI_TOUCHSCREEN=m +CONFIG_DRM_PANEL_SITRONIX_ST7701=m +CONFIG_DRM_PANEL_SIMPLE=m +CONFIG_DRM_PANEL_TPO_Y17P=m +CONFIG_DRM_PANEL_WAVESHARE_TOUCHSCREEN=m +CONFIG_DRM_DISPLAY_CONNECTOR=m +CONFIG_DRM_SIMPLE_BRIDGE=m +CONFIG_DRM_TOSHIBA_TC358762=m +CONFIG_DRM_V3D=m +CONFIG_DRM_VC4=m +CONFIG_DRM_VC4_HDMI_CEC=y +CONFIG_DRM_PANEL_MIPI_DBI=m +CONFIG_TINYDRM_HX8357D=m +CONFIG_TINYDRM_ILI9225=m +CONFIG_TINYDRM_ILI9341=m +CONFIG_TINYDRM_ILI9486=m +CONFIG_TINYDRM_MI0283QT=m +CONFIG_TINYDRM_REPAPER=m +CONFIG_TINYDRM_ST7586=m +CONFIG_TINYDRM_ST7735R=m +CONFIG_DRM_GUD=m +CONFIG_DRM_SSD130X=m +CONFIG_DRM_SSD130X_I2C=m +CONFIG_DRM_SSD130X_SPI=m +CONFIG_FB=y +CONFIG_FB_BCM2708=y +CONFIG_FB_SIMPLE=y +CONFIG_FB_SSD1307=m +CONFIG_FB_RPISENSE=m +CONFIG_BACKLIGHT_PWM=m +CONFIG_BACKLIGHT_RPI=m +CONFIG_BACKLIGHT_LM3630A=m +CONFIG_BACKLIGHT_GPIO=m +CONFIG_FRAMEBUFFER_CONSOLE_ROTATION=y +CONFIG_LOGO=y +# CONFIG_LOGO_LINUX_MONO is not set +# CONFIG_LOGO_LINUX_VGA16 is not set +CONFIG_SOUND=y +CONFIG_SND=m +CONFIG_SND_OSSEMUL=y +CONFIG_SND_PCM_OSS=m +CONFIG_SND_HRTIMER=m +CONFIG_SND_DYNAMIC_MINORS=y +CONFIG_SND_SEQUENCER=m +CONFIG_SND_SEQ_DUMMY=m +CONFIG_SND_DUMMY=m +CONFIG_SND_ALOOP=m +CONFIG_SND_VIRMIDI=m +CONFIG_SND_MTPAV=m +CONFIG_SND_SERIAL_U16550=m +CONFIG_SND_MPU401=m +CONFIG_SND_PIMIDI=m +CONFIG_SND_USB_AUDIO=m +CONFIG_SND_USB_UA101=m +CONFIG_SND_USB_CAIAQ=m +CONFIG_SND_USB_CAIAQ_INPUT=y +CONFIG_SND_USB_6FIRE=m +CONFIG_SND_USB_HIFACE=m +CONFIG_SND_USB_TONEPORT=m +CONFIG_SND_SOC=m +CONFIG_SND_BCM2835_SOC_I2S=m +CONFIG_SND_BCM2708_SOC_CHIPDIP_DAC=m +CONFIG_SND_BCM2708_SOC_GOOGLEVOICEHAT_SOUNDCARD=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_ADC=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_DAC=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUS=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSHD=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSADC=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSADCPRO=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSDSP=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_DIGI=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_AMP=m +CONFIG_SND_BCM2708_SOC_PIFI_40=m +CONFIG_SND_BCM2708_SOC_RPI_CIRRUS=m +CONFIG_SND_BCM2708_SOC_RPI_DAC=m +CONFIG_SND_BCM2708_SOC_RPI_PROTO=m +CONFIG_SND_BCM2708_SOC_JUSTBOOM_BOTH=m +CONFIG_SND_BCM2708_SOC_JUSTBOOM_DAC=m +CONFIG_SND_BCM2708_SOC_JUSTBOOM_DIGI=m +CONFIG_SND_BCM2708_SOC_IQAUDIO_CODEC=m +CONFIG_SND_BCM2708_SOC_IQAUDIO_DAC=m +CONFIG_SND_BCM2708_SOC_IQAUDIO_DIGI=m +CONFIG_SND_BCM2708_SOC_I_SABRE_Q2M=m +CONFIG_SND_BCM2708_SOC_ADAU1977_ADC=m +CONFIG_SND_AUDIOINJECTOR_PI_SOUNDCARD=m +CONFIG_SND_AUDIOINJECTOR_OCTO_SOUNDCARD=m +CONFIG_SND_AUDIOINJECTOR_ISOLATED_SOUNDCARD=m +CONFIG_SND_AUDIOSENSE_PI=m +CONFIG_SND_DIGIDAC1_SOUNDCARD=m +CONFIG_SND_BCM2708_SOC_DIONAUDIO_LOCO=m +CONFIG_SND_BCM2708_SOC_DIONAUDIO_LOCO_V2=m +CONFIG_SND_BCM2708_SOC_ALLO_PIANO_DAC=m +CONFIG_SND_BCM2708_SOC_ALLO_PIANO_DAC_PLUS=m +CONFIG_SND_BCM2708_SOC_ALLO_BOSS_DAC=m +CONFIG_SND_BCM2708_SOC_ALLO_BOSS2_DAC=m +CONFIG_SND_BCM2708_SOC_ALLO_DIGIONE=m +CONFIG_SND_BCM2708_SOC_ALLO_KATANA_DAC=m +CONFIG_SND_BCM2708_SOC_FE_PI_AUDIO=m +CONFIG_SND_PISOUND=m +CONFIG_SND_DACBERRY400=m +CONFIG_SND_SOC_AD193X_SPI=m +CONFIG_SND_SOC_AD193X_I2C=m +CONFIG_SND_SOC_ADAU1701=m +CONFIG_SND_SOC_ADAU7002=m +CONFIG_SND_SOC_AK4554=m +CONFIG_SND_SOC_CS4265=m +CONFIG_SND_SOC_ICS43432=m +CONFIG_SND_SOC_MA120X0P=m +CONFIG_SND_SOC_MAX98357A=m +CONFIG_SND_SOC_SPDIF=m +CONFIG_SND_SOC_TLV320AIC23_I2C=m +CONFIG_SND_SOC_WM8804_I2C=m +CONFIG_SND_SOC_WM8904=m +CONFIG_SND_SOC_WM8960=m +CONFIG_SND_SIMPLE_CARD=m +CONFIG_HID_BATTERY_STRENGTH=y +CONFIG_HIDRAW=y +CONFIG_UHID=m +CONFIG_HID_A4TECH=m +CONFIG_HID_ACRUX=m +CONFIG_HID_APPLE=m +CONFIG_HID_ASUS=m +CONFIG_HID_BELKIN=m +CONFIG_HID_BETOP_FF=m +CONFIG_HID_BIGBEN_FF=m +CONFIG_HID_CHERRY=m +CONFIG_HID_CHICONY=m +CONFIG_HID_CYPRESS=m +CONFIG_HID_DRAGONRISE=m +CONFIG_HID_EMS_FF=m +CONFIG_HID_ELECOM=m +CONFIG_HID_ELO=m +CONFIG_HID_EZKEY=m +CONFIG_HID_GEMBIRD=m +CONFIG_HID_HOLTEK=m +CONFIG_HID_KEYTOUCH=m +CONFIG_HID_KYE=m +CONFIG_HID_UCLOGIC=m +CONFIG_HID_WALTOP=m +CONFIG_HID_GYRATION=m +CONFIG_HID_TWINHAN=m +CONFIG_HID_KENSINGTON=m +CONFIG_HID_LCPOWER=m +CONFIG_HID_LOGITECH=m +CONFIG_HID_LOGITECH_DJ=m +CONFIG_LOGITECH_FF=y +CONFIG_LOGIRUMBLEPAD2_FF=y +CONFIG_LOGIG940_FF=y +CONFIG_HID_MAGICMOUSE=m +CONFIG_HID_MICROSOFT=m +CONFIG_HID_MONTEREY=m +CONFIG_HID_MULTITOUCH=m +CONFIG_HID_NINTENDO=m +CONFIG_NINTENDO_FF=y +CONFIG_HID_NTRIG=m +CONFIG_HID_ORTEK=m +CONFIG_HID_PANTHERLORD=m +CONFIG_HID_PETALYNX=m +CONFIG_HID_PICOLCD=m +CONFIG_HID_PLAYSTATION=m +CONFIG_PLAYSTATION_FF=y +CONFIG_HID_ROCCAT=m +CONFIG_HID_SAMSUNG=m +CONFIG_HID_SONY=m +CONFIG_SONY_FF=y +CONFIG_HID_SPEEDLINK=m +CONFIG_HID_STEAM=m +CONFIG_HID_SUNPLUS=m +CONFIG_HID_GREENASIA=m +CONFIG_HID_SMARTJOYPLUS=m +CONFIG_HID_TOPSEED=m +CONFIG_HID_THINGM=m +CONFIG_HID_THRUSTMASTER=m +CONFIG_HID_WACOM=m +CONFIG_HID_WIIMOTE=m +CONFIG_HID_XINMO=m +CONFIG_HID_ZEROPLUS=m +CONFIG_HID_ZYDACRON=m +CONFIG_HID_PID=y +CONFIG_USB_HIDDEV=y +CONFIG_I2C_HID_OF=m +CONFIG_USB=y +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y +CONFIG_USB_MON=m +CONFIG_USB_XHCI_HCD=y +CONFIG_USB_XHCI_PCI_RENESAS=m +CONFIG_USB_XHCI_PLATFORM=y +CONFIG_USB_DWCOTG=y +CONFIG_USB_PRINTER=m +CONFIG_USB_TMC=m +CONFIG_USB_STORAGE=y +CONFIG_USB_STORAGE_REALTEK=m +CONFIG_USB_STORAGE_DATAFAB=m +CONFIG_USB_STORAGE_FREECOM=m +CONFIG_USB_STORAGE_ISD200=m +CONFIG_USB_STORAGE_USBAT=m +CONFIG_USB_STORAGE_SDDR09=m +CONFIG_USB_STORAGE_SDDR55=m +CONFIG_USB_STORAGE_JUMPSHOT=m +CONFIG_USB_STORAGE_ALAUDA=m +CONFIG_USB_STORAGE_ONETOUCH=m +CONFIG_USB_STORAGE_KARMA=m +CONFIG_USB_STORAGE_CYPRESS_ATACB=m +CONFIG_USB_STORAGE_ENE_UB6250=m +CONFIG_USB_UAS=y +CONFIG_USB_MDC800=m +CONFIG_USB_MICROTEK=m +CONFIG_USBIP_CORE=m +CONFIG_USBIP_VHCI_HCD=m +CONFIG_USBIP_HOST=m +CONFIG_USBIP_VUDC=m +CONFIG_USB_DWC2=m +CONFIG_USB_SERIAL=m +CONFIG_USB_SERIAL_GENERIC=y +CONFIG_USB_SERIAL_SIMPLE=m +CONFIG_USB_SERIAL_AIRCABLE=m +CONFIG_USB_SERIAL_ARK3116=m +CONFIG_USB_SERIAL_BELKIN=m +CONFIG_USB_SERIAL_CH341=m +CONFIG_USB_SERIAL_WHITEHEAT=m +CONFIG_USB_SERIAL_DIGI_ACCELEPORT=m +CONFIG_USB_SERIAL_CP210X=m +CONFIG_USB_SERIAL_CYPRESS_M8=m +CONFIG_USB_SERIAL_EMPEG=m +CONFIG_USB_SERIAL_FTDI_SIO=m +CONFIG_USB_SERIAL_VISOR=m +CONFIG_USB_SERIAL_IPAQ=m +CONFIG_USB_SERIAL_IR=m +CONFIG_USB_SERIAL_EDGEPORT=m +CONFIG_USB_SERIAL_EDGEPORT_TI=m +CONFIG_USB_SERIAL_F81232=m +CONFIG_USB_SERIAL_F8153X=m +CONFIG_USB_SERIAL_GARMIN=m +CONFIG_USB_SERIAL_IPW=m +CONFIG_USB_SERIAL_IUU=m +CONFIG_USB_SERIAL_KEYSPAN_PDA=m +CONFIG_USB_SERIAL_KEYSPAN=m +CONFIG_USB_SERIAL_KLSI=m +CONFIG_USB_SERIAL_KOBIL_SCT=m +CONFIG_USB_SERIAL_MCT_U232=m +CONFIG_USB_SERIAL_METRO=m +CONFIG_USB_SERIAL_MOS7720=m +CONFIG_USB_SERIAL_MOS7840=m +CONFIG_USB_SERIAL_MXUPORT=m +CONFIG_USB_SERIAL_NAVMAN=m +CONFIG_USB_SERIAL_PL2303=m +CONFIG_USB_SERIAL_OTI6858=m +CONFIG_USB_SERIAL_QCAUX=m +CONFIG_USB_SERIAL_QUALCOMM=m +CONFIG_USB_SERIAL_SPCP8X5=m +CONFIG_USB_SERIAL_SAFE=m +CONFIG_USB_SERIAL_SIERRAWIRELESS=m +CONFIG_USB_SERIAL_SYMBOL=m +CONFIG_USB_SERIAL_TI=m +CONFIG_USB_SERIAL_CYBERJACK=m +CONFIG_USB_SERIAL_OPTION=m +CONFIG_USB_SERIAL_OMNINET=m +CONFIG_USB_SERIAL_OPTICON=m +CONFIG_USB_SERIAL_XSENS_MT=m +CONFIG_USB_SERIAL_WISHBONE=m +CONFIG_USB_SERIAL_SSU100=m +CONFIG_USB_SERIAL_QT2=m +CONFIG_USB_SERIAL_UPD78F0730=m +CONFIG_USB_SERIAL_XR=m +CONFIG_USB_SERIAL_DEBUG=m +CONFIG_USB_EMI62=m +CONFIG_USB_EMI26=m +CONFIG_USB_ADUTUX=m +CONFIG_USB_SEVSEG=m +CONFIG_USB_LEGOTOWER=m +CONFIG_USB_LCD=m +CONFIG_USB_CYPRESS_CY7C63=m +CONFIG_USB_CYTHERM=m +CONFIG_USB_IDMOUSE=m +CONFIG_USB_APPLEDISPLAY=m +CONFIG_USB_LD=m +CONFIG_USB_TRANCEVIBRATOR=m +CONFIG_USB_IOWARRIOR=m +CONFIG_USB_TEST=m +CONFIG_USB_ISIGHTFW=m +CONFIG_USB_YUREX=m +CONFIG_USB_ATM=m +CONFIG_USB_SPEEDTOUCH=m +CONFIG_USB_CXACRU=m +CONFIG_USB_UEAGLEATM=m +CONFIG_USB_XUSBATM=m +CONFIG_NOP_USB_XCEIV=y +CONFIG_USB_GADGET=y +CONFIG_USB_CONFIGFS=m +CONFIG_USB_CONFIGFS_SERIAL=y +CONFIG_USB_CONFIGFS_ACM=y +CONFIG_USB_CONFIGFS_OBEX=y +CONFIG_USB_CONFIGFS_NCM=y +CONFIG_USB_CONFIGFS_ECM=y +CONFIG_USB_CONFIGFS_ECM_SUBSET=y +CONFIG_USB_CONFIGFS_RNDIS=y +CONFIG_USB_CONFIGFS_EEM=y +CONFIG_USB_CONFIGFS_MASS_STORAGE=y +CONFIG_USB_CONFIGFS_F_LB_SS=y +CONFIG_USB_CONFIGFS_F_FS=y +CONFIG_USB_CONFIGFS_F_UAC1=y +CONFIG_USB_CONFIGFS_F_UAC2=y +CONFIG_USB_CONFIGFS_F_MIDI=y +CONFIG_USB_CONFIGFS_F_HID=y +CONFIG_USB_CONFIGFS_F_UVC=y +CONFIG_USB_CONFIGFS_F_PRINTER=y +CONFIG_USB_ZERO=m +CONFIG_USB_AUDIO=m +CONFIG_USB_ETH=m +CONFIG_USB_GADGETFS=m +CONFIG_USB_MASS_STORAGE=m +CONFIG_USB_G_SERIAL=m +CONFIG_USB_MIDI_GADGET=m +CONFIG_USB_G_PRINTER=m +CONFIG_USB_CDC_COMPOSITE=m +CONFIG_USB_G_ACM_MS=m +CONFIG_USB_G_MULTI=m +CONFIG_USB_G_HID=m +CONFIG_USB_G_WEBCAM=m +CONFIG_MMC=y +CONFIG_MMC_BLOCK_MINORS=32 +CONFIG_MMC_BCM2835_MMC=y +CONFIG_MMC_BCM2835_DMA=y +CONFIG_MMC_SDHCI=y +CONFIG_MMC_SDHCI_PLTFM=y +CONFIG_MMC_SDHCI_IPROC=y +CONFIG_MMC_SPI=m +CONFIG_MMC_HSQ=y +CONFIG_MMC_BCM2835=y +CONFIG_LEDS_CLASS=y +CONFIG_LEDS_CLASS_MULTICOLOR=m +CONFIG_LEDS_PCA9532=m +CONFIG_LEDS_GPIO=y +CONFIG_LEDS_PCA955X=m +CONFIG_LEDS_PCA963X=m +CONFIG_LEDS_PWM=y +CONFIG_LEDS_IS31FL32XX=m +CONFIG_LEDS_TRIGGER_TIMER=y +CONFIG_LEDS_TRIGGER_ONESHOT=y +CONFIG_LEDS_TRIGGER_HEARTBEAT=y +CONFIG_LEDS_TRIGGER_BACKLIGHT=y +CONFIG_LEDS_TRIGGER_CPU=y +CONFIG_LEDS_TRIGGER_DEFAULT_ON=y +CONFIG_LEDS_TRIGGER_TRANSIENT=m +CONFIG_LEDS_TRIGGER_CAMERA=m +CONFIG_LEDS_TRIGGER_INPUT=y +CONFIG_LEDS_TRIGGER_PANIC=y +CONFIG_LEDS_TRIGGER_NETDEV=m +CONFIG_LEDS_TRIGGER_PATTERN=m +CONFIG_LEDS_TRIGGER_ACTPWR=y +CONFIG_ACCESSIBILITY=y +CONFIG_SPEAKUP=m +CONFIG_SPEAKUP_SYNTH_SOFT=m +CONFIG_RTC_CLASS=y +CONFIG_RTC_DRV_ABX80X=m +CONFIG_RTC_DRV_DS1307=m +CONFIG_RTC_DRV_DS1374=m +CONFIG_RTC_DRV_DS1672=m +CONFIG_RTC_DRV_MAX6900=m +CONFIG_RTC_DRV_RS5C372=m +CONFIG_RTC_DRV_ISL1208=m +CONFIG_RTC_DRV_ISL12022=m +CONFIG_RTC_DRV_X1205=m +CONFIG_RTC_DRV_PCF8523=m +CONFIG_RTC_DRV_PCF85063=m +CONFIG_RTC_DRV_PCF85363=m +CONFIG_RTC_DRV_PCF8563=m +CONFIG_RTC_DRV_PCF8583=m +CONFIG_RTC_DRV_M41T80=m +CONFIG_RTC_DRV_BQ32K=m +CONFIG_RTC_DRV_S35390A=m +CONFIG_RTC_DRV_FM3130=m +CONFIG_RTC_DRV_RX8581=m +CONFIG_RTC_DRV_RX8025=m +CONFIG_RTC_DRV_EM3027=m +CONFIG_RTC_DRV_RV3028=m +CONFIG_RTC_DRV_RV3032=m +CONFIG_RTC_DRV_RV8803=m +CONFIG_RTC_DRV_SD3078=m +CONFIG_RTC_DRV_M41T93=m +CONFIG_RTC_DRV_M41T94=m +CONFIG_RTC_DRV_DS1302=m +CONFIG_RTC_DRV_DS1305=m +CONFIG_RTC_DRV_DS1390=m +CONFIG_RTC_DRV_R9701=m +CONFIG_RTC_DRV_RX4581=m +CONFIG_RTC_DRV_RS5C348=m +CONFIG_RTC_DRV_MAX6902=m +CONFIG_RTC_DRV_PCF2123=m +CONFIG_RTC_DRV_DS3232=m +CONFIG_RTC_DRV_PCF2127=m +CONFIG_RTC_DRV_RV3029C2=m +CONFIG_DMADEVICES=y +CONFIG_DMA_BCM2835=y +CONFIG_DMA_BCM2708=y +CONFIG_DMABUF_HEAPS=y +CONFIG_DMABUF_HEAPS_SYSTEM=y +CONFIG_DMABUF_HEAPS_CMA=y +CONFIG_UIO=m +CONFIG_UIO_PDRV_GENIRQ=m +CONFIG_STAGING=y +CONFIG_R8712U=m +CONFIG_VT6656=m +CONFIG_STAGING_MEDIA=y +CONFIG_VIDEO_RPIVID=m +CONFIG_STAGING_MEDIA_DEPRECATED=y +CONFIG_FB_TFT=m +CONFIG_FB_TFT_AGM1264K_FL=m +CONFIG_FB_TFT_BD663474=m +CONFIG_FB_TFT_HX8340BN=m +CONFIG_FB_TFT_HX8347D=m +CONFIG_FB_TFT_HX8353D=m +CONFIG_FB_TFT_HX8357D=m +CONFIG_FB_TFT_ILI9163=m +CONFIG_FB_TFT_ILI9320=m +CONFIG_FB_TFT_ILI9325=m +CONFIG_FB_TFT_ILI9340=m +CONFIG_FB_TFT_ILI9341=m +CONFIG_FB_TFT_ILI9481=m +CONFIG_FB_TFT_ILI9486=m +CONFIG_FB_TFT_PCD8544=m +CONFIG_FB_TFT_RA8875=m +CONFIG_FB_TFT_S6D02A1=m +CONFIG_FB_TFT_S6D1121=m +CONFIG_FB_TFT_SH1106=m +CONFIG_FB_TFT_SSD1289=m +CONFIG_FB_TFT_SSD1306=m +CONFIG_FB_TFT_SSD1331=m +CONFIG_FB_TFT_SSD1351=m +CONFIG_FB_TFT_ST7735R=m +CONFIG_FB_TFT_ST7789V=m +CONFIG_FB_TFT_TINYLCD=m +CONFIG_FB_TFT_TLS8204=m +CONFIG_FB_TFT_UC1611=m +CONFIG_FB_TFT_UC1701=m +CONFIG_FB_TFT_UPD161704=m +CONFIG_BCM2835_VCHIQ=y +CONFIG_SND_BCM2835=m +CONFIG_VIDEO_BCM2835=m +CONFIG_VIDEO_CODEC_BCM2835=m +CONFIG_VIDEO_ISP_BCM2835=m +CONFIG_CLK_RASPBERRYPI=y +CONFIG_MAILBOX=y +CONFIG_BCM2835_MBOX=y +# CONFIG_IOMMU_SUPPORT is not set +CONFIG_RASPBERRYPI_POWER=y +CONFIG_IIO=m +CONFIG_IIO_BUFFER_CB=m +CONFIG_IIO_SW_TRIGGER=m +CONFIG_MCP320X=m +CONFIG_MCP3422=m +CONFIG_TI_ADS1015=m +CONFIG_BME680=m +CONFIG_CCS811=m +CONFIG_SENSIRION_SGP30=m +CONFIG_SPS30_I2C=m +CONFIG_MAX30102=m +CONFIG_DHT11=m +CONFIG_HDC100X=m +CONFIG_HDC3020=m +CONFIG_HTS221=m +CONFIG_HTU21=m +CONFIG_SI7020=m +CONFIG_BOSCH_BNO055_I2C=m +CONFIG_INV_MPU6050_I2C=m +CONFIG_APDS9960=m +CONFIG_AS73211=m +CONFIG_BH1750=m +CONFIG_TSL4531=m +CONFIG_VEML6070=m +CONFIG_VEML6075=m +CONFIG_IIO_HRTIMER_TRIGGER=m +CONFIG_IIO_INTERRUPT_TRIGGER=m +CONFIG_IIO_SYSFS_TRIGGER=m +CONFIG_BMP280=m +CONFIG_MS5637=m +CONFIG_MAXIM_THERMOCOUPLE=m +CONFIG_MAX31856=m +CONFIG_PWM=y +CONFIG_PWM_BCM2835=m +CONFIG_PWM_GPIO=m +CONFIG_PWM_PCA9685=m +CONFIG_PWM_RASPBERRYPI_POE=m +CONFIG_RPI_AXIPERF=m +CONFIG_NVMEM_RMEM=m +CONFIG_MUX_GPIO=m +CONFIG_EXT4_FS=y +CONFIG_EXT4_FS_POSIX_ACL=y +CONFIG_EXT4_FS_SECURITY=y +CONFIG_REISERFS_FS=m +CONFIG_REISERFS_FS_XATTR=y +CONFIG_REISERFS_FS_POSIX_ACL=y +CONFIG_REISERFS_FS_SECURITY=y +CONFIG_JFS_FS=m +CONFIG_JFS_POSIX_ACL=y +CONFIG_JFS_SECURITY=y +CONFIG_JFS_STATISTICS=y +CONFIG_XFS_FS=m +CONFIG_XFS_QUOTA=y +CONFIG_XFS_POSIX_ACL=y +CONFIG_XFS_RT=y +CONFIG_GFS2_FS=m +CONFIG_OCFS2_FS=m +CONFIG_BTRFS_FS=m +CONFIG_BTRFS_FS_POSIX_ACL=y +CONFIG_NILFS2_FS=m +CONFIG_F2FS_FS=y +CONFIG_F2FS_FS_SECURITY=y +CONFIG_BCACHEFS_FS=m +CONFIG_BCACHEFS_QUOTA=y +CONFIG_BCACHEFS_POSIX_ACL=y +CONFIG_FS_ENCRYPTION=y +CONFIG_FANOTIFY=y +CONFIG_QFMT_V1=m +CONFIG_QFMT_V2=m +CONFIG_AUTOFS_FS=y +CONFIG_FUSE_FS=m +CONFIG_CUSE=m +CONFIG_OVERLAY_FS=m +CONFIG_FSCACHE=y +CONFIG_FSCACHE_STATS=y +CONFIG_CACHEFILES=m +CONFIG_ISO9660_FS=m +CONFIG_JOLIET=y +CONFIG_ZISOFS=y +CONFIG_UDF_FS=m +CONFIG_MSDOS_FS=y +CONFIG_VFAT_FS=y +CONFIG_FAT_DEFAULT_IOCHARSET="ascii" +CONFIG_EXFAT_FS=m +CONFIG_NTFS3_FS=m +CONFIG_TMPFS=y +CONFIG_TMPFS_POSIX_ACL=y +CONFIG_ECRYPT_FS=m +CONFIG_HFS_FS=m +CONFIG_HFSPLUS_FS=m +CONFIG_JFFS2_FS=m +CONFIG_JFFS2_SUMMARY=y +CONFIG_UBIFS_FS=m +CONFIG_SQUASHFS=m +CONFIG_SQUASHFS_XATTR=y +CONFIG_SQUASHFS_LZO=y +CONFIG_SQUASHFS_XZ=y +CONFIG_SQUASHFS_ZSTD=y +CONFIG_PSTORE=y +CONFIG_PSTORE_CONSOLE=y +CONFIG_PSTORE_RAM=y +CONFIG_NFS_FS=y +CONFIG_NFS_V2=y +CONFIG_NFS_V3_ACL=y +CONFIG_NFS_V4=y +CONFIG_NFS_SWAP=y +CONFIG_NFS_V4_1=y +CONFIG_NFS_V4_2=y +CONFIG_ROOT_NFS=y +CONFIG_NFSD=m +CONFIG_NFSD_V2=y +CONFIG_NFSD_V2_ACL=y +CONFIG_NFSD_V3_ACL=y +CONFIG_NFSD_V4=y +CONFIG_CEPH_FS=m +CONFIG_CIFS=m +CONFIG_CIFS_UPCALL=y +CONFIG_CIFS_XATTR=y +CONFIG_CIFS_POSIX=y +CONFIG_CIFS_DFS_UPCALL=y +CONFIG_CIFS_FSCACHE=y +CONFIG_SMB_SERVER=m +CONFIG_9P_FS=m +CONFIG_9P_FS_POSIX_ACL=y +CONFIG_NLS_DEFAULT="utf8" +CONFIG_NLS_CODEPAGE_437=y +CONFIG_NLS_CODEPAGE_737=m +CONFIG_NLS_CODEPAGE_775=m +CONFIG_NLS_CODEPAGE_850=m +CONFIG_NLS_CODEPAGE_852=m +CONFIG_NLS_CODEPAGE_855=m +CONFIG_NLS_CODEPAGE_857=m +CONFIG_NLS_CODEPAGE_860=m +CONFIG_NLS_CODEPAGE_861=m +CONFIG_NLS_CODEPAGE_862=m +CONFIG_NLS_CODEPAGE_863=m +CONFIG_NLS_CODEPAGE_864=m +CONFIG_NLS_CODEPAGE_865=m +CONFIG_NLS_CODEPAGE_866=m +CONFIG_NLS_CODEPAGE_869=m +CONFIG_NLS_CODEPAGE_936=m +CONFIG_NLS_CODEPAGE_950=m +CONFIG_NLS_CODEPAGE_932=m +CONFIG_NLS_CODEPAGE_949=m +CONFIG_NLS_CODEPAGE_874=m +CONFIG_NLS_ISO8859_8=m +CONFIG_NLS_CODEPAGE_1250=m +CONFIG_NLS_CODEPAGE_1251=m +CONFIG_NLS_ASCII=y +CONFIG_NLS_ISO8859_1=m +CONFIG_NLS_ISO8859_2=m +CONFIG_NLS_ISO8859_3=m +CONFIG_NLS_ISO8859_4=m +CONFIG_NLS_ISO8859_5=m +CONFIG_NLS_ISO8859_6=m +CONFIG_NLS_ISO8859_7=m +CONFIG_NLS_ISO8859_9=m +CONFIG_NLS_ISO8859_13=m +CONFIG_NLS_ISO8859_14=m +CONFIG_NLS_ISO8859_15=m +CONFIG_NLS_KOI8_R=m +CONFIG_NLS_KOI8_U=m +CONFIG_DLM=m +CONFIG_KEY_DH_OPERATIONS=y +CONFIG_SECURITY=y +CONFIG_SECURITY_APPARMOR=y +CONFIG_LSM="" +CONFIG_CRYPTO_USER=m +CONFIG_CRYPTO_AES=m +CONFIG_CRYPTO_CAST5=m +CONFIG_CRYPTO_DES=y +CONFIG_CRYPTO_TWOFISH=m +CONFIG_CRYPTO_ADIANTUM=m +CONFIG_CRYPTO_CHACHA20POLY1305=m +CONFIG_CRYPTO_MD4=m +CONFIG_CRYPTO_SHA512=m +CONFIG_CRYPTO_WP512=m +CONFIG_CRYPTO_XCBC=m +CONFIG_CRYPTO_LZ4=m +CONFIG_CRYPTO_USER_API_HASH=m +CONFIG_CRYPTO_USER_API_SKCIPHER=m +CONFIG_CRYPTO_USER_API_RNG=m +CONFIG_CRYPTO_USER_API_AEAD=m +CONFIG_CRYPTO_NHPOLY1305_NEON=m +CONFIG_CRYPTO_SHA1_ARM_NEON=m +CONFIG_CRYPTO_AES_ARM_BS=m +# CONFIG_CRYPTO_HW is not set +CONFIG_PKCS8_PRIVATE_KEY_PARSER=m +CONFIG_CRC_ITU_T=y +CONFIG_LIBCRC32C=y +CONFIG_DMA_CMA=y +CONFIG_CMA_SIZE_MBYTES=5 +CONFIG_PRINTK_TIME=y +CONFIG_BOOT_PRINTK_DELAY=y +CONFIG_MAGIC_SYSRQ_DEFAULT_ENABLE=0x1f6 +CONFIG_KGDB=y +CONFIG_KGDB_KDB=y +CONFIG_KDB_KEYBOARD=y +CONFIG_DEBUG_MEMORY_INIT=y +CONFIG_DETECT_HUNG_TASK=y +# CONFIG_RCU_TRACE is not set +CONFIG_LATENCYTOP=y +CONFIG_FUNCTION_PROFILER=y +CONFIG_STACK_TRACER=y +CONFIG_SCHED_TRACER=y +CONFIG_BLK_DEV_IO_TRACE=y +# CONFIG_UPROBE_EVENTS is not set diff --git a/arch/arm/configs/bcmrpi_defconfig b/arch/arm/configs/bcmrpi_defconfig new file mode 100644 index 00000000000000..3a063a38070d1a --- /dev/null +++ b/arch/arm/configs/bcmrpi_defconfig @@ -0,0 +1,1609 @@ +# CONFIG_LOCALVERSION_AUTO is not set +CONFIG_SYSVIPC=y +CONFIG_POSIX_MQUEUE=y +CONFIG_GENERIC_IRQ_DEBUGFS=y +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_BPF_SYSCALL=y +CONFIG_PREEMPT_VOLUNTARY=y +CONFIG_BSD_PROCESS_ACCT=y +CONFIG_BSD_PROCESS_ACCT_V3=y +CONFIG_TASKSTATS=y +CONFIG_TASK_DELAY_ACCT=y +CONFIG_TASK_XACCT=y +CONFIG_TASK_IO_ACCOUNTING=y +CONFIG_PSI=y +CONFIG_PSI_DEFAULT_DISABLED=y +CONFIG_IKCONFIG=m +CONFIG_IKCONFIG_PROC=y +CONFIG_MEMCG=y +CONFIG_BLK_CGROUP=y +CONFIG_CFS_BANDWIDTH=y +CONFIG_CGROUP_PIDS=y +CONFIG_CGROUP_FREEZER=y +CONFIG_CGROUP_DEVICE=y +CONFIG_CGROUP_CPUACCT=y +CONFIG_CGROUP_PERF=y +CONFIG_CGROUP_BPF=y +CONFIG_NAMESPACES=y +CONFIG_USER_NS=y +CONFIG_SCHED_AUTOGROUP=y +CONFIG_BLK_DEV_INITRD=y +CONFIG_EXPERT=y +CONFIG_PROFILING=y +CONFIG_ARCH_MULTI_V6=y +# CONFIG_ARCH_MULTI_V7 is not set +CONFIG_ARCH_BCM=y +CONFIG_ARCH_BCM2835=y +# CONFIG_CACHE_L2X0 is not set +CONFIG_UACCESS_WITH_MEMCPY=y +# CONFIG_ATAGS is not set +CONFIG_CMDLINE="console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 root=/dev/mmcblk0p2 rootfstype=ext4 rootwait" +CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_STAT=y +CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND=y +CONFIG_CPU_FREQ_GOV_POWERSAVE=y +CONFIG_CPU_FREQ_GOV_USERSPACE=y +CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y +CONFIG_CPUFREQ_DT=y +CONFIG_ARM_RASPBERRYPI_CPUFREQ=y +CONFIG_VFP=y +# CONFIG_SUSPEND is not set +CONFIG_PM=y +CONFIG_JUMP_LABEL=y +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_MODVERSIONS=y +CONFIG_MODULE_SRCVERSION_ALL=y +CONFIG_MODULE_COMPRESS=y +CONFIG_MODULE_COMPRESS_XZ=y +CONFIG_PARTITION_ADVANCED=y +CONFIG_MAC_PARTITION=y +CONFIG_BINFMT_MISC=m +CONFIG_ZSWAP=y +# CONFIG_COMPAT_BRK is not set +CONFIG_CMA=y +CONFIG_CMA_AREAS=7 +CONFIG_LRU_GEN=y +CONFIG_LRU_GEN_ENABLED=y +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_XFRM_USER=y +CONFIG_XFRM_INTERFACE=m +CONFIG_XFRM_SUB_POLICY=y +CONFIG_XFRM_STATISTICS=y +CONFIG_NET_KEY=m +CONFIG_IP_MULTICAST=y +CONFIG_IP_ADVANCED_ROUTER=y +CONFIG_IP_MULTIPLE_TABLES=y +CONFIG_IP_ROUTE_MULTIPATH=y +CONFIG_IP_ROUTE_VERBOSE=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y +CONFIG_IP_PNP_RARP=y +CONFIG_NET_IPIP=m +CONFIG_NET_IPGRE_DEMUX=m +CONFIG_NET_IPGRE=m +CONFIG_IP_MROUTE=y +CONFIG_IP_MROUTE_MULTIPLE_TABLES=y +CONFIG_IP_PIMSM_V1=y +CONFIG_IP_PIMSM_V2=y +CONFIG_NET_IPVTI=m +CONFIG_NET_FOU_IP_TUNNELS=y +CONFIG_INET_AH=m +CONFIG_INET_ESP=m +CONFIG_INET_IPCOMP=m +CONFIG_INET_DIAG=m +CONFIG_TCP_CONG_ADVANCED=y +CONFIG_TCP_CONG_BBR=m +CONFIG_IPV6=m +CONFIG_IPV6_ROUTER_PREF=y +CONFIG_IPV6_ROUTE_INFO=y +CONFIG_INET6_AH=m +CONFIG_INET6_ESP=m +CONFIG_INET6_ESP_OFFLOAD=m +CONFIG_INET6_IPCOMP=m +CONFIG_IPV6_ILA=m +CONFIG_IPV6_VTI=m +CONFIG_IPV6_SIT_6RD=y +CONFIG_IPV6_GRE=m +CONFIG_IPV6_MULTIPLE_TABLES=y +CONFIG_IPV6_SUBTREES=y +CONFIG_IPV6_MROUTE=y +CONFIG_IPV6_MROUTE_MULTIPLE_TABLES=y +CONFIG_IPV6_PIMSM_V2=y +CONFIG_NETFILTER=y +CONFIG_BRIDGE_NETFILTER=m +CONFIG_NF_CONNTRACK=m +CONFIG_NF_CONNTRACK_ZONES=y +CONFIG_NF_CONNTRACK_EVENTS=y +CONFIG_NF_CONNTRACK_TIMESTAMP=y +CONFIG_NF_CONNTRACK_AMANDA=m +CONFIG_NF_CONNTRACK_FTP=m +CONFIG_NF_CONNTRACK_H323=m +CONFIG_NF_CONNTRACK_IRC=m +CONFIG_NF_CONNTRACK_NETBIOS_NS=m +CONFIG_NF_CONNTRACK_SNMP=m +CONFIG_NF_CONNTRACK_PPTP=m +CONFIG_NF_CONNTRACK_SANE=m +CONFIG_NF_CONNTRACK_SIP=m +CONFIG_NF_CONNTRACK_TFTP=m +CONFIG_NF_CT_NETLINK=m +CONFIG_NF_TABLES=m +CONFIG_NF_TABLES_INET=y +CONFIG_NF_TABLES_NETDEV=y +CONFIG_NFT_NUMGEN=m +CONFIG_NFT_CT=m +CONFIG_NFT_FLOW_OFFLOAD=m +CONFIG_NFT_CONNLIMIT=m +CONFIG_NFT_LOG=m +CONFIG_NFT_LIMIT=m +CONFIG_NFT_MASQ=m +CONFIG_NFT_REDIR=m +CONFIG_NFT_NAT=m +CONFIG_NFT_TUNNEL=m +CONFIG_NFT_QUEUE=m +CONFIG_NFT_QUOTA=m +CONFIG_NFT_REJECT=m +CONFIG_NFT_COMPAT=m +CONFIG_NFT_HASH=m +CONFIG_NFT_FIB_INET=m +CONFIG_NFT_XFRM=m +CONFIG_NFT_SOCKET=m +CONFIG_NFT_OSF=m +CONFIG_NFT_TPROXY=m +CONFIG_NFT_SYNPROXY=m +CONFIG_NFT_DUP_NETDEV=m +CONFIG_NFT_FWD_NETDEV=m +CONFIG_NFT_FIB_NETDEV=m +CONFIG_NF_FLOW_TABLE_INET=m +CONFIG_NF_FLOW_TABLE=m +CONFIG_NETFILTER_XT_SET=m +CONFIG_NETFILTER_XT_TARGET_CHECKSUM=m +CONFIG_NETFILTER_XT_TARGET_CLASSIFY=m +CONFIG_NETFILTER_XT_TARGET_CONNMARK=m +CONFIG_NETFILTER_XT_TARGET_DSCP=m +CONFIG_NETFILTER_XT_TARGET_HMARK=m +CONFIG_NETFILTER_XT_TARGET_IDLETIMER=m +CONFIG_NETFILTER_XT_TARGET_LED=m +CONFIG_NETFILTER_XT_TARGET_LOG=m +CONFIG_NETFILTER_XT_TARGET_MARK=m +CONFIG_NETFILTER_XT_TARGET_NFLOG=m +CONFIG_NETFILTER_XT_TARGET_NFQUEUE=m +CONFIG_NETFILTER_XT_TARGET_NOTRACK=m +CONFIG_NETFILTER_XT_TARGET_TEE=m +CONFIG_NETFILTER_XT_TARGET_TPROXY=m +CONFIG_NETFILTER_XT_TARGET_TRACE=m +CONFIG_NETFILTER_XT_TARGET_TCPMSS=m +CONFIG_NETFILTER_XT_TARGET_TCPOPTSTRIP=m +CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=m +CONFIG_NETFILTER_XT_MATCH_BPF=m +CONFIG_NETFILTER_XT_MATCH_CLUSTER=m +CONFIG_NETFILTER_XT_MATCH_COMMENT=m +CONFIG_NETFILTER_XT_MATCH_CONNBYTES=m +CONFIG_NETFILTER_XT_MATCH_CONNLABEL=m +CONFIG_NETFILTER_XT_MATCH_CONNLIMIT=m +CONFIG_NETFILTER_XT_MATCH_CONNMARK=m +CONFIG_NETFILTER_XT_MATCH_CONNTRACK=m +CONFIG_NETFILTER_XT_MATCH_CPU=m +CONFIG_NETFILTER_XT_MATCH_DCCP=m +CONFIG_NETFILTER_XT_MATCH_DEVGROUP=m +CONFIG_NETFILTER_XT_MATCH_DSCP=m +CONFIG_NETFILTER_XT_MATCH_ESP=m +CONFIG_NETFILTER_XT_MATCH_HASHLIMIT=m +CONFIG_NETFILTER_XT_MATCH_HELPER=m +CONFIG_NETFILTER_XT_MATCH_IPRANGE=m +CONFIG_NETFILTER_XT_MATCH_IPVS=m +CONFIG_NETFILTER_XT_MATCH_LENGTH=m +CONFIG_NETFILTER_XT_MATCH_LIMIT=m +CONFIG_NETFILTER_XT_MATCH_MAC=m +CONFIG_NETFILTER_XT_MATCH_MARK=m +CONFIG_NETFILTER_XT_MATCH_MULTIPORT=m +CONFIG_NETFILTER_XT_MATCH_NFACCT=m +CONFIG_NETFILTER_XT_MATCH_OSF=m +CONFIG_NETFILTER_XT_MATCH_OWNER=m +CONFIG_NETFILTER_XT_MATCH_POLICY=m +CONFIG_NETFILTER_XT_MATCH_PHYSDEV=m +CONFIG_NETFILTER_XT_MATCH_PKTTYPE=m +CONFIG_NETFILTER_XT_MATCH_QUOTA=m +CONFIG_NETFILTER_XT_MATCH_RATEEST=m +CONFIG_NETFILTER_XT_MATCH_REALM=m +CONFIG_NETFILTER_XT_MATCH_RECENT=m +CONFIG_NETFILTER_XT_MATCH_SOCKET=m +CONFIG_NETFILTER_XT_MATCH_STATE=m +CONFIG_NETFILTER_XT_MATCH_STATISTIC=m +CONFIG_NETFILTER_XT_MATCH_STRING=m +CONFIG_NETFILTER_XT_MATCH_TCPMSS=m +CONFIG_NETFILTER_XT_MATCH_TIME=m +CONFIG_NETFILTER_XT_MATCH_U32=m +CONFIG_IP_SET=m +CONFIG_IP_SET_BITMAP_IP=m +CONFIG_IP_SET_BITMAP_IPMAC=m +CONFIG_IP_SET_BITMAP_PORT=m +CONFIG_IP_SET_HASH_IP=m +CONFIG_IP_SET_HASH_IPPORT=m +CONFIG_IP_SET_HASH_IPPORTIP=m +CONFIG_IP_SET_HASH_IPPORTNET=m +CONFIG_IP_SET_HASH_NET=m +CONFIG_IP_SET_HASH_NETPORT=m +CONFIG_IP_SET_HASH_NETIFACE=m +CONFIG_IP_SET_LIST_SET=m +CONFIG_IP_VS=m +CONFIG_IP_VS_IPV6=y +CONFIG_IP_VS_PROTO_TCP=y +CONFIG_IP_VS_PROTO_UDP=y +CONFIG_IP_VS_PROTO_ESP=y +CONFIG_IP_VS_PROTO_AH=y +CONFIG_IP_VS_PROTO_SCTP=y +CONFIG_IP_VS_RR=m +CONFIG_IP_VS_WRR=m +CONFIG_IP_VS_LC=m +CONFIG_IP_VS_WLC=m +CONFIG_IP_VS_LBLC=m +CONFIG_IP_VS_LBLCR=m +CONFIG_IP_VS_DH=m +CONFIG_IP_VS_SH=m +CONFIG_IP_VS_SED=m +CONFIG_IP_VS_NQ=m +CONFIG_IP_VS_FTP=m +CONFIG_IP_VS_PE_SIP=m +CONFIG_NFT_DUP_IPV4=m +CONFIG_NFT_FIB_IPV4=m +CONFIG_NF_TABLES_ARP=y +CONFIG_NF_LOG_ARP=m +CONFIG_NF_LOG_IPV4=m +CONFIG_IP_NF_IPTABLES=m +CONFIG_IP_NF_MATCH_AH=m +CONFIG_IP_NF_MATCH_ECN=m +CONFIG_IP_NF_MATCH_RPFILTER=m +CONFIG_IP_NF_MATCH_TTL=m +CONFIG_IP_NF_FILTER=m +CONFIG_IP_NF_TARGET_REJECT=m +CONFIG_IP_NF_TARGET_SYNPROXY=m +CONFIG_IP_NF_NAT=m +CONFIG_IP_NF_TARGET_MASQUERADE=m +CONFIG_IP_NF_TARGET_NETMAP=m +CONFIG_IP_NF_TARGET_REDIRECT=m +CONFIG_IP_NF_MANGLE=m +CONFIG_IP_NF_TARGET_ECN=m +CONFIG_IP_NF_TARGET_TTL=m +CONFIG_IP_NF_RAW=m +CONFIG_IP_NF_ARPFILTER=m +CONFIG_IP_NF_ARP_MANGLE=m +CONFIG_NFT_DUP_IPV6=m +CONFIG_NFT_FIB_IPV6=m +CONFIG_IP6_NF_IPTABLES=m +CONFIG_IP6_NF_MATCH_AH=m +CONFIG_IP6_NF_MATCH_EUI64=m +CONFIG_IP6_NF_MATCH_FRAG=m +CONFIG_IP6_NF_MATCH_OPTS=m +CONFIG_IP6_NF_MATCH_HL=m +CONFIG_IP6_NF_MATCH_IPV6HEADER=m +CONFIG_IP6_NF_MATCH_MH=m +CONFIG_IP6_NF_MATCH_RPFILTER=m +CONFIG_IP6_NF_MATCH_RT=m +CONFIG_IP6_NF_MATCH_SRH=m +CONFIG_IP6_NF_TARGET_HL=m +CONFIG_IP6_NF_FILTER=m +CONFIG_IP6_NF_TARGET_REJECT=m +CONFIG_IP6_NF_TARGET_SYNPROXY=m +CONFIG_IP6_NF_MANGLE=m +CONFIG_IP6_NF_RAW=m +CONFIG_IP6_NF_SECURITY=m +CONFIG_IP6_NF_NAT=m +CONFIG_IP6_NF_TARGET_MASQUERADE=m +CONFIG_IP6_NF_TARGET_NPT=m +CONFIG_NF_TABLES_BRIDGE=m +CONFIG_NFT_BRIDGE_REJECT=m +CONFIG_BRIDGE_NF_EBTABLES=m +CONFIG_BRIDGE_EBT_BROUTE=m +CONFIG_BRIDGE_EBT_T_FILTER=m +CONFIG_BRIDGE_EBT_T_NAT=m +CONFIG_BRIDGE_EBT_802_3=m +CONFIG_BRIDGE_EBT_AMONG=m +CONFIG_BRIDGE_EBT_ARP=m +CONFIG_BRIDGE_EBT_IP=m +CONFIG_BRIDGE_EBT_IP6=m +CONFIG_BRIDGE_EBT_LIMIT=m +CONFIG_BRIDGE_EBT_MARK=m +CONFIG_BRIDGE_EBT_PKTTYPE=m +CONFIG_BRIDGE_EBT_STP=m +CONFIG_BRIDGE_EBT_VLAN=m +CONFIG_BRIDGE_EBT_ARPREPLY=m +CONFIG_BRIDGE_EBT_DNAT=m +CONFIG_BRIDGE_EBT_MARK_T=m +CONFIG_BRIDGE_EBT_REDIRECT=m +CONFIG_BRIDGE_EBT_SNAT=m +CONFIG_BRIDGE_EBT_LOG=m +CONFIG_BRIDGE_EBT_NFLOG=m +CONFIG_SCTP_COOKIE_HMAC_SHA1=y +CONFIG_ATM=m +CONFIG_L2TP=m +CONFIG_L2TP_V3=y +CONFIG_L2TP_IP=m +CONFIG_L2TP_ETH=m +CONFIG_BRIDGE=m +CONFIG_BRIDGE_VLAN_FILTERING=y +CONFIG_VLAN_8021Q=m +CONFIG_VLAN_8021Q_GVRP=y +CONFIG_ATALK=m +CONFIG_6LOWPAN=m +CONFIG_IEEE802154=m +CONFIG_IEEE802154_6LOWPAN=m +CONFIG_MAC802154=m +CONFIG_NET_SCHED=y +CONFIG_NET_SCH_HTB=m +CONFIG_NET_SCH_HFSC=m +CONFIG_NET_SCH_PRIO=m +CONFIG_NET_SCH_MULTIQ=m +CONFIG_NET_SCH_RED=m +CONFIG_NET_SCH_SFB=m +CONFIG_NET_SCH_SFQ=m +CONFIG_NET_SCH_TEQL=m +CONFIG_NET_SCH_TBF=m +CONFIG_NET_SCH_GRED=m +CONFIG_NET_SCH_NETEM=m +CONFIG_NET_SCH_DRR=m +CONFIG_NET_SCH_MQPRIO=m +CONFIG_NET_SCH_CHOKE=m +CONFIG_NET_SCH_QFQ=m +CONFIG_NET_SCH_CODEL=m +CONFIG_NET_SCH_FQ_CODEL=m +CONFIG_NET_SCH_CAKE=m +CONFIG_NET_SCH_FQ=m +CONFIG_NET_SCH_HHF=m +CONFIG_NET_SCH_PIE=m +CONFIG_NET_SCH_INGRESS=m +CONFIG_NET_SCH_PLUG=m +CONFIG_NET_CLS_BASIC=m +CONFIG_NET_CLS_ROUTE4=m +CONFIG_NET_CLS_FW=m +CONFIG_NET_CLS_U32=m +CONFIG_CLS_U32_MARK=y +CONFIG_NET_CLS_FLOW=m +CONFIG_NET_CLS_CGROUP=m +CONFIG_NET_EMATCH=y +CONFIG_NET_EMATCH_CMP=m +CONFIG_NET_EMATCH_NBYTE=m +CONFIG_NET_EMATCH_U32=m +CONFIG_NET_EMATCH_META=m +CONFIG_NET_EMATCH_TEXT=m +CONFIG_NET_EMATCH_IPSET=m +CONFIG_NET_CLS_ACT=y +CONFIG_NET_ACT_POLICE=m +CONFIG_NET_ACT_GACT=m +CONFIG_GACT_PROB=y +CONFIG_NET_ACT_MIRRED=m +CONFIG_NET_ACT_NAT=m +CONFIG_NET_ACT_PEDIT=m +CONFIG_NET_ACT_SIMP=m +CONFIG_NET_ACT_SKBEDIT=m +CONFIG_NET_ACT_CSUM=m +CONFIG_BATMAN_ADV=m +CONFIG_OPENVSWITCH=m +CONFIG_CGROUP_NET_PRIO=y +CONFIG_NET_PKTGEN=m +CONFIG_HAMRADIO=y +CONFIG_AX25=m +CONFIG_NETROM=m +CONFIG_ROSE=m +CONFIG_MKISS=m +CONFIG_6PACK=m +CONFIG_BPQETHER=m +CONFIG_BAYCOM_SER_FDX=m +CONFIG_BAYCOM_SER_HDX=m +CONFIG_YAM=m +CONFIG_CAN=m +CONFIG_CAN_J1939=m +CONFIG_CAN_ISOTP=m +CONFIG_BT=m +CONFIG_BT_RFCOMM=m +CONFIG_BT_RFCOMM_TTY=y +CONFIG_BT_BNEP=m +CONFIG_BT_BNEP_MC_FILTER=y +CONFIG_BT_BNEP_PROTO_FILTER=y +CONFIG_BT_HIDP=m +CONFIG_BT_6LOWPAN=m +CONFIG_BT_HCIBTUSB=m +CONFIG_BT_HCIUART=m +CONFIG_BT_HCIUART_3WIRE=y +CONFIG_BT_HCIUART_BCM=y +CONFIG_BT_HCIBCM203X=m +CONFIG_BT_HCIBPA10X=m +CONFIG_BT_HCIBFUSB=m +CONFIG_BT_HCIVHCI=m +CONFIG_BT_MRVL=m +CONFIG_BT_MRVL_SDIO=m +CONFIG_BT_ATH3K=m +CONFIG_CFG80211=m +CONFIG_CFG80211_WEXT=y +CONFIG_MAC80211=m +CONFIG_MAC80211_MESH=y +CONFIG_RFKILL=m +CONFIG_RFKILL_INPUT=y +CONFIG_NET_9P=m +CONFIG_NFC=m +CONFIG_UEVENT_HELPER=y +CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y +CONFIG_RASPBERRYPI_FIRMWARE=y +CONFIG_MTD=m +CONFIG_MTD_BLOCK=m +CONFIG_MTD_BLOCK2MTD=m +CONFIG_MTD_SPI_NAND=m +CONFIG_MTD_SPI_NOR=m +CONFIG_MTD_UBI=m +CONFIG_OF_CONFIGFS=y +CONFIG_ZRAM=m +CONFIG_ZRAM_BACKEND_LZ4=y +CONFIG_ZRAM_BACKEND_ZSTD=y +CONFIG_ZRAM_BACKEND_LZO=y +CONFIG_ZRAM_DEF_COMP_ZSTD=y +CONFIG_ZRAM_WRITEBACK=y +CONFIG_ZRAM_MULTI_COMP=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_DRBD=m +CONFIG_BLK_DEV_NBD=m +CONFIG_BLK_DEV_RAM=y +CONFIG_ATA_OVER_ETH=m +CONFIG_BLK_DEV_RBD=m +CONFIG_EEPROM_AT24=m +CONFIG_EEPROM_AT25=m +CONFIG_TI_ST=m +CONFIG_SCSI=y +# CONFIG_SCSI_PROC_FS is not set +CONFIG_BLK_DEV_SD=y +CONFIG_CHR_DEV_ST=m +CONFIG_BLK_DEV_SR=m +CONFIG_CHR_DEV_SG=m +CONFIG_SCSI_ISCSI_ATTRS=y +CONFIG_ISCSI_TCP=m +CONFIG_ISCSI_BOOT_SYSFS=m +CONFIG_ATA=m +CONFIG_MD=y +CONFIG_BCACHE=m +CONFIG_BLK_DEV_DM=m +CONFIG_DM_CRYPT=m +CONFIG_DM_SNAPSHOT=m +CONFIG_DM_THIN_PROVISIONING=m +CONFIG_DM_CACHE=m +CONFIG_DM_MIRROR=m +CONFIG_DM_LOG_USERSPACE=m +CONFIG_DM_RAID=m +CONFIG_DM_ZERO=m +CONFIG_DM_MULTIPATH=m +CONFIG_DM_DELAY=m +CONFIG_DM_VERITY=m +CONFIG_DM_INTEGRITY=m +CONFIG_NETDEVICES=y +CONFIG_BONDING=m +CONFIG_DUMMY=m +CONFIG_WIREGUARD=m +CONFIG_IFB=m +CONFIG_MACVLAN=m +CONFIG_IPVLAN=m +CONFIG_VXLAN=m +CONFIG_NETCONSOLE=m +CONFIG_TUN=m +CONFIG_VETH=m +CONFIG_NETKIT=y +CONFIG_NET_VRF=m +CONFIG_ENC28J60=m +CONFIG_QCA7000_SPI=m +CONFIG_QCA7000_UART=m +CONFIG_MSE102X=m +CONFIG_WIZNET_W5100=m +CONFIG_WIZNET_W5100_SPI=m +CONFIG_CAN_VCAN=m +CONFIG_CAN_SLCAN=m +CONFIG_CAN_MCP251X=m +CONFIG_CAN_MCP251XFD=m +CONFIG_CAN_8DEV_USB=m +CONFIG_CAN_EMS_USB=m +CONFIG_CAN_GS_USB=m +CONFIG_CAN_PEAK_USB=m +CONFIG_MDIO_BITBANG=m +CONFIG_PPP=m +CONFIG_PPP_BSDCOMP=m +CONFIG_PPP_DEFLATE=m +CONFIG_PPP_FILTER=y +CONFIG_PPP_MPPE=m +CONFIG_PPP_MULTILINK=y +CONFIG_PPPOATM=m +CONFIG_PPPOE=m +CONFIG_PPPOL2TP=m +CONFIG_PPP_ASYNC=m +CONFIG_PPP_SYNC_TTY=m +CONFIG_SLIP=m +CONFIG_SLIP_COMPRESSED=y +CONFIG_SLIP_SMART=y +CONFIG_USB_CATC=m +CONFIG_USB_KAWETH=m +CONFIG_USB_PEGASUS=m +CONFIG_USB_RTL8150=m +CONFIG_USB_RTL8152=m +CONFIG_USB_LAN78XX=m +CONFIG_USB_USBNET=y +CONFIG_USB_NET_AX8817X=m +CONFIG_USB_NET_AX88179_178A=m +CONFIG_USB_NET_CDCETHER=m +CONFIG_USB_NET_CDC_EEM=m +CONFIG_USB_NET_CDC_NCM=m +CONFIG_USB_NET_HUAWEI_CDC_NCM=m +CONFIG_USB_NET_CDC_MBIM=m +CONFIG_USB_NET_DM9601=m +CONFIG_USB_NET_SR9700=m +CONFIG_USB_NET_SR9800=m +CONFIG_USB_NET_SMSC75XX=m +CONFIG_USB_NET_SMSC95XX=y +CONFIG_USB_NET_GL620A=m +CONFIG_USB_NET_NET1080=m +CONFIG_USB_NET_PLUSB=m +CONFIG_USB_NET_MCS7830=m +CONFIG_USB_NET_RNDIS_HOST=m +CONFIG_USB_NET_CDC_SUBSET=m +CONFIG_USB_ALI_M5632=y +CONFIG_USB_AN2720=y +CONFIG_USB_EPSON2888=y +CONFIG_USB_KC2190=y +CONFIG_USB_NET_ZAURUS=m +CONFIG_USB_NET_CX82310_ETH=m +CONFIG_USB_NET_KALMIA=m +CONFIG_USB_NET_QMI_WWAN=m +CONFIG_USB_HSO=m +CONFIG_USB_NET_INT51X1=m +CONFIG_USB_IPHETH=m +CONFIG_USB_SIERRA_NET=m +CONFIG_USB_VL600=m +CONFIG_USB_NET_AQC111=m +CONFIG_ATH9K=m +CONFIG_ATH9K_HTC=m +CONFIG_CARL9170=m +CONFIG_ATH6KL=m +CONFIG_ATH6KL_USB=m +CONFIG_AR5523=m +CONFIG_AT76C50X_USB=m +CONFIG_B43=m +# CONFIG_B43_PHY_N is not set +CONFIG_B43LEGACY=m +CONFIG_BRCMFMAC=m +CONFIG_BRCMFMAC_USB=y +CONFIG_BRCMDBG=y +CONFIG_P54_COMMON=m +CONFIG_P54_USB=m +CONFIG_LIBERTAS=m +CONFIG_LIBERTAS_USB=m +CONFIG_LIBERTAS_SDIO=m +CONFIG_LIBERTAS_THINFIRM=m +CONFIG_LIBERTAS_THINFIRM_USB=m +CONFIG_MWIFIEX=m +CONFIG_MWIFIEX_SDIO=m +CONFIG_MT7601U=m +CONFIG_MT76x0U=m +CONFIG_MT76x2U=m +CONFIG_MT7921U=m +CONFIG_RT2X00=m +CONFIG_RT2500USB=m +CONFIG_RT73USB=m +CONFIG_RT2800USB=m +CONFIG_RT2800USB_RT3573=y +CONFIG_RT2800USB_RT53XX=y +CONFIG_RT2800USB_RT55XX=y +CONFIG_RT2800USB_UNKNOWN=y +CONFIG_RTL8187=m +CONFIG_RTL8192CU=m +CONFIG_RTL8XXXU=m +CONFIG_RTW88=m +CONFIG_RTW88_8822BU=m +CONFIG_RTW88_8822CU=m +CONFIG_RTW88_8723DU=m +CONFIG_RTW88_8821CU=m +CONFIG_ZD1211RW=m +CONFIG_MAC80211_HWSIM=m +CONFIG_IEEE802154_AT86RF230=m +CONFIG_IEEE802154_MRF24J40=m +CONFIG_IEEE802154_CC2520=m +CONFIG_INPUT_MOUSEDEV=y +CONFIG_INPUT_JOYDEV=m +CONFIG_INPUT_EVDEV=y +# CONFIG_KEYBOARD_ATKBD is not set +CONFIG_KEYBOARD_GPIO=m +CONFIG_KEYBOARD_TCA6416=m +CONFIG_KEYBOARD_TCA8418=m +CONFIG_KEYBOARD_MATRIX=m +CONFIG_KEYBOARD_CAP11XX=m +# CONFIG_INPUT_MOUSE is not set +CONFIG_INPUT_JOYSTICK=y +CONFIG_JOYSTICK_IFORCE=m +CONFIG_JOYSTICK_IFORCE_USB=m +CONFIG_JOYSTICK_XPAD=m +CONFIG_JOYSTICK_XPAD_FF=y +CONFIG_JOYSTICK_XPAD_LEDS=y +CONFIG_JOYSTICK_PSXPAD_SPI=m +CONFIG_JOYSTICK_PSXPAD_SPI_FF=y +CONFIG_JOYSTICK_FSIA6B=m +CONFIG_JOYSTICK_SENSEHAT=m +CONFIG_INPUT_TOUCHSCREEN=y +CONFIG_TOUCHSCREEN_ADS7846=m +CONFIG_TOUCHSCREEN_EGALAX=m +CONFIG_TOUCHSCREEN_EXC3000=m +CONFIG_TOUCHSCREEN_GOODIX=m +CONFIG_TOUCHSCREEN_ILI210X=m +CONFIG_TOUCHSCREEN_EDT_FT5X06=m +CONFIG_TOUCHSCREEN_RASPBERRYPI_FW=m +CONFIG_TOUCHSCREEN_USB_COMPOSITE=m +CONFIG_TOUCHSCREEN_TSC2007=m +CONFIG_TOUCHSCREEN_TSC2007_IIO=y +CONFIG_TOUCHSCREEN_STMPE=m +CONFIG_TOUCHSCREEN_IQS5XX=m +CONFIG_INPUT_MISC=y +CONFIG_INPUT_AD714X=m +CONFIG_INPUT_ATI_REMOTE2=m +CONFIG_INPUT_KEYSPAN_REMOTE=m +CONFIG_INPUT_POWERMATE=m +CONFIG_INPUT_YEALINK=m +CONFIG_INPUT_CM109=m +CONFIG_INPUT_UINPUT=m +CONFIG_INPUT_GPIO_ROTARY_ENCODER=m +CONFIG_INPUT_ADXL34X=m +CONFIG_INPUT_CMA3000=m +CONFIG_SERIO=m +CONFIG_SERIO_RAW=m +CONFIG_GAMEPORT=m +CONFIG_BRCM_CHAR_DRIVERS=y +CONFIG_BCM_VCIO=y +# CONFIG_LEGACY_PTYS is not set +CONFIG_SERIAL_8250=y +# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set +CONFIG_SERIAL_8250_CONSOLE=y +# CONFIG_SERIAL_8250_DMA is not set +CONFIG_SERIAL_8250_NR_UARTS=1 +CONFIG_SERIAL_8250_RUNTIME_UARTS=0 +CONFIG_SERIAL_8250_EXTENDED=y +CONFIG_SERIAL_8250_SHARE_IRQ=y +CONFIG_SERIAL_8250_BCM2835AUX=y +CONFIG_SERIAL_OF_PLATFORM=y +CONFIG_SERIAL_AMBA_PL011=y +CONFIG_SERIAL_AMBA_PL011_CONSOLE=y +CONFIG_SERIAL_SC16IS7XX=m +CONFIG_SERIAL_RPI_FW=m +CONFIG_SERIAL_DEV_BUS=y +CONFIG_TTY_PRINTK=y +CONFIG_HW_RANDOM=y +CONFIG_TCG_TPM=m +CONFIG_TCG_TIS_SPI=m +CONFIG_TCG_TIS_I2C=m +CONFIG_RASPBERRYPI_GPIOMEM=m +CONFIG_I2C=y +CONFIG_I2C_CHARDEV=m +CONFIG_I2C_MUX_GPMUX=m +CONFIG_I2C_MUX_PCA954x=m +CONFIG_I2C_MUX_PINCTRL=m +CONFIG_I2C_BCM2708=m +CONFIG_I2C_BCM2835=m +# CONFIG_I2C_BRCMSTB is not set +CONFIG_I2C_GPIO=m +CONFIG_I2C_ROBOTFUZZ_OSIF=m +CONFIG_I2C_TINY_USB=m +CONFIG_SPI=y +CONFIG_SPI_BCM2835=m +CONFIG_SPI_BCM2835AUX=m +CONFIG_SPI_GPIO=m +CONFIG_SPI_RP2040_GPIO_BRIDGE=m +CONFIG_SPI_SPIDEV=m +CONFIG_SPI_SLAVE=y +CONFIG_PPS_CLIENT_LDISC=m +CONFIG_PPS_CLIENT_GPIO=m +CONFIG_PINCTRL_MCP23S08=m +CONFIG_GPIO_SYSFS=y +CONFIG_GPIO_MAX7300=m +CONFIG_GPIO_PCA953X=m +CONFIG_GPIO_PCA953X_IRQ=y +CONFIG_GPIO_PCF857X=m +CONFIG_GPIO_ARIZONA=m +CONFIG_GPIO_FSM=m +CONFIG_GPIO_STMPE=y +CONFIG_GPIO_MAX7301=m +CONFIG_GPIO_MOCKUP=m +CONFIG_W1=m +CONFIG_W1_MASTER_DS2490=m +CONFIG_W1_MASTER_DS2482=m +CONFIG_W1_MASTER_GPIO=m +CONFIG_W1_SLAVE_THERM=m +CONFIG_W1_SLAVE_SMEM=m +CONFIG_W1_SLAVE_DS2408=m +CONFIG_W1_SLAVE_DS2413=m +CONFIG_W1_SLAVE_DS2406=m +CONFIG_W1_SLAVE_DS2423=m +CONFIG_W1_SLAVE_DS2431=m +CONFIG_W1_SLAVE_DS2433=m +CONFIG_W1_SLAVE_DS2438=m +CONFIG_W1_SLAVE_DS2780=m +CONFIG_W1_SLAVE_DS2781=m +CONFIG_W1_SLAVE_DS28E04=m +CONFIG_W1_SLAVE_DS28E17=m +CONFIG_POWER_RESET=y +CONFIG_POWER_RESET_GPIO=y +CONFIG_RPI_POE_POWER=m +CONFIG_BATTERY_DS2760=m +CONFIG_BATTERY_MAX17040=m +CONFIG_CHARGER_GPIO=m +CONFIG_BATTERY_GAUGE_LTC2941=m +CONFIG_SENSORS_ADT7410=m +CONFIG_SENSORS_AHT10=m +CONFIG_SENSORS_CHIPCAP2=m +CONFIG_SENSORS_DRIVETEMP=m +CONFIG_SENSORS_DS1621=m +CONFIG_SENSORS_GPIO_FAN=m +CONFIG_SENSORS_IIO_HWMON=m +CONFIG_SENSORS_JC42=m +CONFIG_SENSORS_LM75=m +CONFIG_SENSORS_PWM_FAN=m +CONFIG_SENSORS_RASPBERRYPI_HWMON=m +CONFIG_SENSORS_SHT21=m +CONFIG_SENSORS_SHT3x=m +CONFIG_SENSORS_SHT4x=m +CONFIG_SENSORS_SHTC1=m +CONFIG_SENSORS_EMC2305=m +CONFIG_SENSORS_INA2XX=m +CONFIG_SENSORS_INA238=m +CONFIG_SENSORS_TMP102=m +CONFIG_BCM2835_THERMAL=y +CONFIG_WATCHDOG=y +CONFIG_GPIO_WATCHDOG=m +CONFIG_BCM2835_WDT=y +CONFIG_MFD_RASPBERRYPI_POE_HAT=m +CONFIG_MFD_STMPE=y +CONFIG_STMPE_SPI=y +CONFIG_MFD_ARIZONA_I2C=m +CONFIG_MFD_ARIZONA_SPI=m +CONFIG_MFD_WM5102=y +CONFIG_REGULATOR=y +CONFIG_REGULATOR_FIXED_VOLTAGE=m +CONFIG_REGULATOR_ARIZONA_LDO1=m +CONFIG_REGULATOR_ARIZONA_MICSUPP=m +CONFIG_REGULATOR_RASPBERRYPI_TOUCHSCREEN_ATTINY=m +CONFIG_REGULATOR_RASPBERRYPI_TOUCHSCREEN_V2=m +CONFIG_RC_CORE=y +CONFIG_BPF_LIRC_MODE2=y +CONFIG_LIRC=y +CONFIG_RC_DECODERS=y +CONFIG_IR_IMON_DECODER=m +CONFIG_IR_JVC_DECODER=m +CONFIG_IR_MCE_KBD_DECODER=m +CONFIG_IR_NEC_DECODER=m +CONFIG_IR_RC5_DECODER=m +CONFIG_IR_RC6_DECODER=m +CONFIG_IR_SANYO_DECODER=m +CONFIG_IR_SHARP_DECODER=m +CONFIG_IR_SONY_DECODER=m +CONFIG_IR_XMP_DECODER=m +CONFIG_RC_DEVICES=y +CONFIG_IR_GPIO_CIR=m +CONFIG_IR_GPIO_TX=m +CONFIG_IR_IGUANA=m +CONFIG_IR_IMON=m +CONFIG_IR_MCEUSB=m +CONFIG_IR_PWM_TX=m +CONFIG_IR_REDRAT3=m +CONFIG_IR_STREAMZAP=m +CONFIG_IR_TOY=m +CONFIG_IR_TTUSBIR=m +CONFIG_RC_ATI_REMOTE=m +CONFIG_RC_LOOPBACK=m +CONFIG_MEDIA_CEC_RC=y +CONFIG_MEDIA_SUPPORT=m +CONFIG_MEDIA_USB_SUPPORT=y +CONFIG_USB_GSPCA=m +CONFIG_USB_GSPCA_BENQ=m +CONFIG_USB_GSPCA_CONEX=m +CONFIG_USB_GSPCA_CPIA1=m +CONFIG_USB_GSPCA_DTCS033=m +CONFIG_USB_GSPCA_ETOMS=m +CONFIG_USB_GSPCA_FINEPIX=m +CONFIG_USB_GSPCA_JEILINJ=m +CONFIG_USB_GSPCA_JL2005BCD=m +CONFIG_USB_GSPCA_KINECT=m +CONFIG_USB_GSPCA_KONICA=m +CONFIG_USB_GSPCA_MARS=m +CONFIG_USB_GSPCA_MR97310A=m +CONFIG_USB_GSPCA_NW80X=m +CONFIG_USB_GSPCA_OV519=m +CONFIG_USB_GSPCA_OV534=m +CONFIG_USB_GSPCA_OV534_9=m +CONFIG_USB_GSPCA_PAC207=m +CONFIG_USB_GSPCA_PAC7302=m +CONFIG_USB_GSPCA_PAC7311=m +CONFIG_USB_GSPCA_SE401=m +CONFIG_USB_GSPCA_SN9C2028=m +CONFIG_USB_GSPCA_SN9C20X=m +CONFIG_USB_GSPCA_SONIXB=m +CONFIG_USB_GSPCA_SONIXJ=m +CONFIG_USB_GSPCA_SPCA1528=m +CONFIG_USB_GSPCA_SPCA500=m +CONFIG_USB_GSPCA_SPCA501=m +CONFIG_USB_GSPCA_SPCA505=m +CONFIG_USB_GSPCA_SPCA506=m +CONFIG_USB_GSPCA_SPCA508=m +CONFIG_USB_GSPCA_SPCA561=m +CONFIG_USB_GSPCA_SQ905=m +CONFIG_USB_GSPCA_SQ905C=m +CONFIG_USB_GSPCA_SQ930X=m +CONFIG_USB_GSPCA_STK014=m +CONFIG_USB_GSPCA_STK1135=m +CONFIG_USB_GSPCA_STV0680=m +CONFIG_USB_GSPCA_SUNPLUS=m +CONFIG_USB_GSPCA_T613=m +CONFIG_USB_GSPCA_TOPRO=m +CONFIG_USB_GSPCA_TOUPTEK=m +CONFIG_USB_GSPCA_TV8532=m +CONFIG_USB_GSPCA_VC032X=m +CONFIG_USB_GSPCA_VICAM=m +CONFIG_USB_GSPCA_XIRLINK_CIT=m +CONFIG_USB_GSPCA_ZC3XX=m +CONFIG_USB_GL860=m +CONFIG_USB_M5602=m +CONFIG_USB_STV06XX=m +CONFIG_USB_PWC=m +CONFIG_USB_S2255=m +CONFIG_VIDEO_USBTV=m +CONFIG_USB_VIDEO_CLASS=m +CONFIG_VIDEO_GO7007=m +CONFIG_VIDEO_GO7007_USB=m +CONFIG_VIDEO_GO7007_USB_S2250_BOARD=m +CONFIG_VIDEO_HDPVR=m +CONFIG_VIDEO_PVRUSB2=m +CONFIG_VIDEO_STK1160=m +CONFIG_VIDEO_AU0828=m +CONFIG_VIDEO_AU0828_RC=y +CONFIG_VIDEO_CX231XX=m +CONFIG_VIDEO_CX231XX_ALSA=m +CONFIG_VIDEO_CX231XX_DVB=m +CONFIG_DVB_AS102=m +CONFIG_DVB_B2C2_FLEXCOP_USB=m +CONFIG_DVB_USB_V2=m +CONFIG_DVB_USB_AF9015=m +CONFIG_DVB_USB_AF9035=m +CONFIG_DVB_USB_ANYSEE=m +CONFIG_DVB_USB_AU6610=m +CONFIG_DVB_USB_AZ6007=m +CONFIG_DVB_USB_CE6230=m +CONFIG_DVB_USB_DVBSKY=m +CONFIG_DVB_USB_EC168=m +CONFIG_DVB_USB_GL861=m +CONFIG_DVB_USB_LME2510=m +CONFIG_DVB_USB_MXL111SF=m +CONFIG_DVB_USB_RTL28XXU=m +CONFIG_DVB_USB=m +CONFIG_DVB_USB_A800=m +CONFIG_DVB_USB_AF9005=m +CONFIG_DVB_USB_AF9005_REMOTE=m +CONFIG_DVB_USB_AZ6027=m +CONFIG_DVB_USB_CINERGY_T2=m +CONFIG_DVB_USB_CXUSB=m +CONFIG_DVB_USB_DIB0700=m +CONFIG_DVB_USB_DIBUSB_MB=m +CONFIG_DVB_USB_DIBUSB_MB_FAULTY=y +CONFIG_DVB_USB_DIBUSB_MC=m +CONFIG_DVB_USB_DIGITV=m +CONFIG_DVB_USB_DTT200U=m +CONFIG_DVB_USB_DTV5100=m +CONFIG_DVB_USB_DW2102=m +CONFIG_DVB_USB_GP8PSK=m +CONFIG_DVB_USB_M920X=m +CONFIG_DVB_USB_NOVA_T_USB2=m +CONFIG_DVB_USB_OPERA1=m +CONFIG_DVB_USB_PCTV452E=m +CONFIG_DVB_USB_TECHNISAT_USB2=m +CONFIG_DVB_USB_TTUSB2=m +CONFIG_DVB_USB_UMT_010=m +CONFIG_DVB_USB_VP702X=m +CONFIG_DVB_USB_VP7045=m +CONFIG_SMS_USB_DRV=m +CONFIG_VIDEO_EM28XX=m +CONFIG_VIDEO_EM28XX_V4L2=m +CONFIG_VIDEO_EM28XX_ALSA=m +CONFIG_VIDEO_EM28XX_DVB=m +CONFIG_RADIO_SAA7706H=m +CONFIG_RADIO_SHARK=m +CONFIG_RADIO_SHARK2=m +CONFIG_RADIO_SI4713=m +CONFIG_RADIO_TEA5764=m +CONFIG_RADIO_TEF6862=m +CONFIG_RADIO_WL1273=m +CONFIG_USB_DSBR=m +CONFIG_USB_KEENE=m +CONFIG_USB_MA901=m +CONFIG_USB_MR800=m +CONFIG_RADIO_SI470X=m +CONFIG_USB_SI470X=m +CONFIG_I2C_SI470X=m +CONFIG_I2C_SI4713=m +CONFIG_RADIO_WL128X=m +CONFIG_V4L_PLATFORM_DRIVERS=y +CONFIG_VIDEO_MUX=m +CONFIG_VIDEO_BCM2835_UNICAM_LEGACY=m +CONFIG_VIDEO_BCM2835_UNICAM=m +CONFIG_VIDEO_ARDUCAM_64MP=m +CONFIG_VIDEO_ARDUCAM_PIVARIETY=m +CONFIG_VIDEO_IMX219=m +CONFIG_VIDEO_IMX258=m +CONFIG_VIDEO_IMX290=m +CONFIG_VIDEO_IMX296=m +CONFIG_VIDEO_IMX415=m +CONFIG_VIDEO_IMX477=m +CONFIG_VIDEO_IMX500=m +CONFIG_VIDEO_IMX519=m +CONFIG_VIDEO_IMX708=m +CONFIG_VIDEO_MT9V011=m +CONFIG_VIDEO_OV2311=m +CONFIG_VIDEO_OV5647=m +CONFIG_VIDEO_OV64A40=m +CONFIG_VIDEO_OV7251=m +CONFIG_VIDEO_OV7640=m +CONFIG_VIDEO_OV9282=m +CONFIG_VIDEO_AD5398=m +CONFIG_VIDEO_AK7375=m +CONFIG_VIDEO_BU64754=m +CONFIG_VIDEO_DW9807_VCM=m +CONFIG_VIDEO_SONY_BTF_MPX=m +CONFIG_VIDEO_UDA1342=m +CONFIG_VIDEO_ADV7180=m +CONFIG_VIDEO_TC358743=m +CONFIG_VIDEO_TVP5150=m +CONFIG_VIDEO_TW2804=m +CONFIG_VIDEO_TW9903=m +CONFIG_VIDEO_TW9906=m +CONFIG_VIDEO_IRS1125=m +CONFIG_VIDEO_I2C=m +CONFIG_AUXDISPLAY=y +CONFIG_HD44780=m +CONFIG_DRM=m +CONFIG_DRM_LOAD_EDID_FIRMWARE=y +CONFIG_DRM_UDL=m +CONFIG_DRM_PANEL_ILITEK_ILI9806E=m +CONFIG_DRM_PANEL_ILITEK_ILI9881C=m +CONFIG_DRM_PANEL_JDI_LT070ME05000=m +CONFIG_DRM_PANEL_RASPBERRYPI_TOUCHSCREEN=m +CONFIG_DRM_PANEL_SITRONIX_ST7701=m +CONFIG_DRM_PANEL_SIMPLE=m +CONFIG_DRM_PANEL_TPO_Y17P=m +CONFIG_DRM_PANEL_WAVESHARE_TOUCHSCREEN=m +CONFIG_DRM_DISPLAY_CONNECTOR=m +CONFIG_DRM_SIMPLE_BRIDGE=m +CONFIG_DRM_TOSHIBA_TC358762=m +CONFIG_DRM_VC4=m +CONFIG_DRM_VC4_HDMI_CEC=y +CONFIG_DRM_PANEL_MIPI_DBI=m +CONFIG_TINYDRM_HX8357D=m +CONFIG_TINYDRM_ILI9225=m +CONFIG_TINYDRM_ILI9341=m +CONFIG_TINYDRM_ILI9486=m +CONFIG_TINYDRM_MI0283QT=m +CONFIG_TINYDRM_REPAPER=m +CONFIG_TINYDRM_ST7586=m +CONFIG_TINYDRM_ST7735R=m +CONFIG_DRM_GUD=m +CONFIG_DRM_SSD130X=m +CONFIG_DRM_SSD130X_I2C=m +CONFIG_DRM_SSD130X_SPI=m +CONFIG_FB=y +CONFIG_FB_BCM2708=y +CONFIG_FB_SIMPLE=y +CONFIG_FB_SSD1307=m +CONFIG_FB_RPISENSE=m +CONFIG_BACKLIGHT_PWM=m +CONFIG_BACKLIGHT_RPI=m +CONFIG_BACKLIGHT_LM3630A=m +CONFIG_BACKLIGHT_GPIO=m +CONFIG_FRAMEBUFFER_CONSOLE_ROTATION=y +CONFIG_LOGO=y +# CONFIG_LOGO_LINUX_MONO is not set +# CONFIG_LOGO_LINUX_VGA16 is not set +CONFIG_SOUND=y +CONFIG_SND=m +CONFIG_SND_OSSEMUL=y +CONFIG_SND_PCM_OSS=m +CONFIG_SND_HRTIMER=m +CONFIG_SND_DYNAMIC_MINORS=y +CONFIG_SND_SEQUENCER=m +CONFIG_SND_SEQ_DUMMY=m +CONFIG_SND_DUMMY=m +CONFIG_SND_ALOOP=m +CONFIG_SND_VIRMIDI=m +CONFIG_SND_MTPAV=m +CONFIG_SND_SERIAL_U16550=m +CONFIG_SND_MPU401=m +CONFIG_SND_PIMIDI=m +CONFIG_SND_USB_AUDIO=m +CONFIG_SND_USB_UA101=m +CONFIG_SND_USB_CAIAQ=m +CONFIG_SND_USB_CAIAQ_INPUT=y +CONFIG_SND_USB_6FIRE=m +CONFIG_SND_USB_HIFACE=m +CONFIG_SND_USB_TONEPORT=m +CONFIG_SND_SOC=m +CONFIG_SND_BCM2835_SOC_I2S=m +CONFIG_SND_BCM2708_SOC_CHIPDIP_DAC=m +CONFIG_SND_BCM2708_SOC_GOOGLEVOICEHAT_SOUNDCARD=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_ADC=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_DAC=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUS=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSHD=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSADC=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSADCPRO=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSDSP=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_DIGI=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_AMP=m +CONFIG_SND_BCM2708_SOC_PIFI_40=m +CONFIG_SND_BCM2708_SOC_RPI_CIRRUS=m +CONFIG_SND_BCM2708_SOC_RPI_DAC=m +CONFIG_SND_BCM2708_SOC_RPI_PROTO=m +CONFIG_SND_BCM2708_SOC_JUSTBOOM_BOTH=m +CONFIG_SND_BCM2708_SOC_JUSTBOOM_DAC=m +CONFIG_SND_BCM2708_SOC_JUSTBOOM_DIGI=m +CONFIG_SND_BCM2708_SOC_IQAUDIO_CODEC=m +CONFIG_SND_BCM2708_SOC_IQAUDIO_DAC=m +CONFIG_SND_BCM2708_SOC_IQAUDIO_DIGI=m +CONFIG_SND_BCM2708_SOC_I_SABRE_Q2M=m +CONFIG_SND_BCM2708_SOC_ADAU1977_ADC=m +CONFIG_SND_AUDIOINJECTOR_PI_SOUNDCARD=m +CONFIG_SND_AUDIOINJECTOR_OCTO_SOUNDCARD=m +CONFIG_SND_AUDIOINJECTOR_ISOLATED_SOUNDCARD=m +CONFIG_SND_AUDIOSENSE_PI=m +CONFIG_SND_DIGIDAC1_SOUNDCARD=m +CONFIG_SND_BCM2708_SOC_DIONAUDIO_LOCO=m +CONFIG_SND_BCM2708_SOC_DIONAUDIO_LOCO_V2=m +CONFIG_SND_BCM2708_SOC_ALLO_PIANO_DAC=m +CONFIG_SND_BCM2708_SOC_ALLO_PIANO_DAC_PLUS=m +CONFIG_SND_BCM2708_SOC_ALLO_BOSS_DAC=m +CONFIG_SND_BCM2708_SOC_ALLO_BOSS2_DAC=m +CONFIG_SND_BCM2708_SOC_ALLO_DIGIONE=m +CONFIG_SND_BCM2708_SOC_ALLO_KATANA_DAC=m +CONFIG_SND_BCM2708_SOC_FE_PI_AUDIO=m +CONFIG_SND_PISOUND=m +CONFIG_SND_SOC_AD193X_SPI=m +CONFIG_SND_SOC_AD193X_I2C=m +CONFIG_SND_SOC_ADAU1701=m +CONFIG_SND_SOC_ADAU7002=m +CONFIG_SND_SOC_AK4554=m +CONFIG_SND_SOC_CS4265=m +CONFIG_SND_SOC_ICS43432=m +CONFIG_SND_SOC_MA120X0P=m +CONFIG_SND_SOC_MAX98357A=m +CONFIG_SND_SOC_SPDIF=m +CONFIG_SND_SOC_TLV320AIC23_I2C=m +CONFIG_SND_SOC_WM8804_I2C=m +CONFIG_SND_SOC_WM8904=m +CONFIG_SND_SOC_WM8960=m +CONFIG_SND_SIMPLE_CARD=m +CONFIG_HID_BATTERY_STRENGTH=y +CONFIG_HIDRAW=y +CONFIG_UHID=m +CONFIG_HID_A4TECH=m +CONFIG_HID_ACRUX=m +CONFIG_HID_APPLE=m +CONFIG_HID_ASUS=m +CONFIG_HID_BELKIN=m +CONFIG_HID_BETOP_FF=m +CONFIG_HID_BIGBEN_FF=m +CONFIG_HID_CHERRY=m +CONFIG_HID_CHICONY=m +CONFIG_HID_CYPRESS=m +CONFIG_HID_DRAGONRISE=m +CONFIG_HID_EMS_FF=m +CONFIG_HID_ELECOM=m +CONFIG_HID_ELO=m +CONFIG_HID_EZKEY=m +CONFIG_HID_GEMBIRD=m +CONFIG_HID_HOLTEK=m +CONFIG_HID_KEYTOUCH=m +CONFIG_HID_KYE=m +CONFIG_HID_UCLOGIC=m +CONFIG_HID_WALTOP=m +CONFIG_HID_GYRATION=m +CONFIG_HID_TWINHAN=m +CONFIG_HID_KENSINGTON=m +CONFIG_HID_LCPOWER=m +CONFIG_HID_LOGITECH=m +CONFIG_HID_LOGITECH_DJ=m +CONFIG_LOGITECH_FF=y +CONFIG_LOGIRUMBLEPAD2_FF=y +CONFIG_LOGIG940_FF=y +CONFIG_HID_MAGICMOUSE=m +CONFIG_HID_MICROSOFT=m +CONFIG_HID_MONTEREY=m +CONFIG_HID_MULTITOUCH=m +CONFIG_HID_NINTENDO=m +CONFIG_NINTENDO_FF=y +CONFIG_HID_NTRIG=m +CONFIG_HID_ORTEK=m +CONFIG_HID_PANTHERLORD=m +CONFIG_HID_PETALYNX=m +CONFIG_HID_PICOLCD=m +CONFIG_HID_PLAYSTATION=m +CONFIG_PLAYSTATION_FF=y +CONFIG_HID_ROCCAT=m +CONFIG_HID_SAMSUNG=m +CONFIG_HID_SONY=m +CONFIG_SONY_FF=y +CONFIG_HID_SPEEDLINK=m +CONFIG_HID_STEAM=m +CONFIG_HID_SUNPLUS=m +CONFIG_HID_GREENASIA=m +CONFIG_HID_SMARTJOYPLUS=m +CONFIG_HID_TOPSEED=m +CONFIG_HID_THINGM=m +CONFIG_HID_THRUSTMASTER=m +CONFIG_HID_WACOM=m +CONFIG_HID_WIIMOTE=m +CONFIG_HID_XINMO=m +CONFIG_HID_ZEROPLUS=m +CONFIG_HID_ZYDACRON=m +CONFIG_HID_PID=y +CONFIG_USB_HIDDEV=y +CONFIG_I2C_HID_OF=m +CONFIG_USB=y +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y +CONFIG_USB_MON=m +CONFIG_USB_DWCOTG=y +CONFIG_USB_PRINTER=m +CONFIG_USB_TMC=m +CONFIG_USB_STORAGE=y +CONFIG_USB_STORAGE_REALTEK=m +CONFIG_USB_STORAGE_DATAFAB=m +CONFIG_USB_STORAGE_FREECOM=m +CONFIG_USB_STORAGE_ISD200=m +CONFIG_USB_STORAGE_USBAT=m +CONFIG_USB_STORAGE_SDDR09=m +CONFIG_USB_STORAGE_SDDR55=m +CONFIG_USB_STORAGE_JUMPSHOT=m +CONFIG_USB_STORAGE_ALAUDA=m +CONFIG_USB_STORAGE_ONETOUCH=m +CONFIG_USB_STORAGE_KARMA=m +CONFIG_USB_STORAGE_CYPRESS_ATACB=m +CONFIG_USB_STORAGE_ENE_UB6250=m +CONFIG_USB_UAS=m +CONFIG_USB_MDC800=m +CONFIG_USB_MICROTEK=m +CONFIG_USBIP_CORE=m +CONFIG_USBIP_VHCI_HCD=m +CONFIG_USBIP_HOST=m +CONFIG_USBIP_VUDC=m +CONFIG_USB_DWC2=m +CONFIG_USB_SERIAL=m +CONFIG_USB_SERIAL_GENERIC=y +CONFIG_USB_SERIAL_SIMPLE=m +CONFIG_USB_SERIAL_AIRCABLE=m +CONFIG_USB_SERIAL_ARK3116=m +CONFIG_USB_SERIAL_BELKIN=m +CONFIG_USB_SERIAL_CH341=m +CONFIG_USB_SERIAL_WHITEHEAT=m +CONFIG_USB_SERIAL_DIGI_ACCELEPORT=m +CONFIG_USB_SERIAL_CP210X=m +CONFIG_USB_SERIAL_CYPRESS_M8=m +CONFIG_USB_SERIAL_EMPEG=m +CONFIG_USB_SERIAL_FTDI_SIO=m +CONFIG_USB_SERIAL_VISOR=m +CONFIG_USB_SERIAL_IPAQ=m +CONFIG_USB_SERIAL_IR=m +CONFIG_USB_SERIAL_EDGEPORT=m +CONFIG_USB_SERIAL_EDGEPORT_TI=m +CONFIG_USB_SERIAL_F81232=m +CONFIG_USB_SERIAL_F8153X=m +CONFIG_USB_SERIAL_GARMIN=m +CONFIG_USB_SERIAL_IPW=m +CONFIG_USB_SERIAL_IUU=m +CONFIG_USB_SERIAL_KEYSPAN_PDA=m +CONFIG_USB_SERIAL_KEYSPAN=m +CONFIG_USB_SERIAL_KLSI=m +CONFIG_USB_SERIAL_KOBIL_SCT=m +CONFIG_USB_SERIAL_MCT_U232=m +CONFIG_USB_SERIAL_METRO=m +CONFIG_USB_SERIAL_MOS7720=m +CONFIG_USB_SERIAL_MOS7840=m +CONFIG_USB_SERIAL_MXUPORT=m +CONFIG_USB_SERIAL_NAVMAN=m +CONFIG_USB_SERIAL_PL2303=m +CONFIG_USB_SERIAL_OTI6858=m +CONFIG_USB_SERIAL_QCAUX=m +CONFIG_USB_SERIAL_QUALCOMM=m +CONFIG_USB_SERIAL_SPCP8X5=m +CONFIG_USB_SERIAL_SAFE=m +CONFIG_USB_SERIAL_SIERRAWIRELESS=m +CONFIG_USB_SERIAL_SYMBOL=m +CONFIG_USB_SERIAL_TI=m +CONFIG_USB_SERIAL_CYBERJACK=m +CONFIG_USB_SERIAL_OPTION=m +CONFIG_USB_SERIAL_OMNINET=m +CONFIG_USB_SERIAL_OPTICON=m +CONFIG_USB_SERIAL_XSENS_MT=m +CONFIG_USB_SERIAL_WISHBONE=m +CONFIG_USB_SERIAL_SSU100=m +CONFIG_USB_SERIAL_QT2=m +CONFIG_USB_SERIAL_UPD78F0730=m +CONFIG_USB_SERIAL_XR=m +CONFIG_USB_SERIAL_DEBUG=m +CONFIG_USB_EMI62=m +CONFIG_USB_EMI26=m +CONFIG_USB_ADUTUX=m +CONFIG_USB_SEVSEG=m +CONFIG_USB_LEGOTOWER=m +CONFIG_USB_LCD=m +CONFIG_USB_CYPRESS_CY7C63=m +CONFIG_USB_CYTHERM=m +CONFIG_USB_IDMOUSE=m +CONFIG_USB_APPLEDISPLAY=m +CONFIG_USB_LD=m +CONFIG_USB_TRANCEVIBRATOR=m +CONFIG_USB_IOWARRIOR=m +CONFIG_USB_TEST=m +CONFIG_USB_ISIGHTFW=m +CONFIG_USB_YUREX=m +CONFIG_USB_ATM=m +CONFIG_USB_SPEEDTOUCH=m +CONFIG_USB_CXACRU=m +CONFIG_USB_UEAGLEATM=m +CONFIG_USB_XUSBATM=m +CONFIG_NOP_USB_XCEIV=y +CONFIG_USB_GADGET=y +CONFIG_USB_CONFIGFS=m +CONFIG_USB_CONFIGFS_SERIAL=y +CONFIG_USB_CONFIGFS_ACM=y +CONFIG_USB_CONFIGFS_OBEX=y +CONFIG_USB_CONFIGFS_NCM=y +CONFIG_USB_CONFIGFS_ECM=y +CONFIG_USB_CONFIGFS_ECM_SUBSET=y +CONFIG_USB_CONFIGFS_RNDIS=y +CONFIG_USB_CONFIGFS_EEM=y +CONFIG_USB_CONFIGFS_MASS_STORAGE=y +CONFIG_USB_CONFIGFS_F_LB_SS=y +CONFIG_USB_CONFIGFS_F_FS=y +CONFIG_USB_CONFIGFS_F_UAC1=y +CONFIG_USB_CONFIGFS_F_UAC2=y +CONFIG_USB_CONFIGFS_F_MIDI=y +CONFIG_USB_CONFIGFS_F_HID=y +CONFIG_USB_CONFIGFS_F_UVC=y +CONFIG_USB_CONFIGFS_F_PRINTER=y +CONFIG_USB_ZERO=m +CONFIG_USB_AUDIO=m +CONFIG_USB_ETH=m +CONFIG_USB_GADGETFS=m +CONFIG_USB_MASS_STORAGE=m +CONFIG_USB_G_SERIAL=m +CONFIG_USB_MIDI_GADGET=m +CONFIG_USB_G_PRINTER=m +CONFIG_USB_CDC_COMPOSITE=m +CONFIG_USB_G_ACM_MS=m +CONFIG_USB_G_MULTI=m +CONFIG_USB_G_HID=m +CONFIG_USB_G_WEBCAM=m +CONFIG_MMC=y +CONFIG_MMC_BLOCK_MINORS=32 +CONFIG_MMC_BCM2835_MMC=y +CONFIG_MMC_BCM2835_DMA=y +CONFIG_MMC_SDHCI=y +CONFIG_MMC_SDHCI_PLTFM=y +CONFIG_MMC_SPI=m +CONFIG_MMC_HSQ=y +CONFIG_MMC_BCM2835=y +CONFIG_LEDS_CLASS=y +CONFIG_LEDS_CLASS_MULTICOLOR=m +CONFIG_LEDS_PCA9532=m +CONFIG_LEDS_GPIO=y +CONFIG_LEDS_PCA955X=m +CONFIG_LEDS_PCA963X=m +CONFIG_LEDS_PWM=y +CONFIG_LEDS_IS31FL32XX=m +CONFIG_LEDS_TRIGGER_TIMER=y +CONFIG_LEDS_TRIGGER_ONESHOT=y +CONFIG_LEDS_TRIGGER_HEARTBEAT=y +CONFIG_LEDS_TRIGGER_BACKLIGHT=y +CONFIG_LEDS_TRIGGER_CPU=y +CONFIG_LEDS_TRIGGER_DEFAULT_ON=y +CONFIG_LEDS_TRIGGER_TRANSIENT=m +CONFIG_LEDS_TRIGGER_CAMERA=m +CONFIG_LEDS_TRIGGER_INPUT=y +CONFIG_LEDS_TRIGGER_PANIC=y +CONFIG_LEDS_TRIGGER_NETDEV=m +CONFIG_LEDS_TRIGGER_PATTERN=m +CONFIG_LEDS_TRIGGER_ACTPWR=y +CONFIG_ACCESSIBILITY=y +CONFIG_SPEAKUP=m +CONFIG_SPEAKUP_SYNTH_SOFT=m +CONFIG_RTC_CLASS=y +CONFIG_RTC_DRV_ABX80X=m +CONFIG_RTC_DRV_DS1307=m +CONFIG_RTC_DRV_DS1374=m +CONFIG_RTC_DRV_DS1672=m +CONFIG_RTC_DRV_MAX6900=m +CONFIG_RTC_DRV_RS5C372=m +CONFIG_RTC_DRV_ISL1208=m +CONFIG_RTC_DRV_ISL12022=m +CONFIG_RTC_DRV_X1205=m +CONFIG_RTC_DRV_PCF8523=m +CONFIG_RTC_DRV_PCF85063=m +CONFIG_RTC_DRV_PCF85363=m +CONFIG_RTC_DRV_PCF8563=m +CONFIG_RTC_DRV_PCF8583=m +CONFIG_RTC_DRV_M41T80=m +CONFIG_RTC_DRV_BQ32K=m +CONFIG_RTC_DRV_S35390A=m +CONFIG_RTC_DRV_FM3130=m +CONFIG_RTC_DRV_RX8581=m +CONFIG_RTC_DRV_RX8025=m +CONFIG_RTC_DRV_EM3027=m +CONFIG_RTC_DRV_RV3028=m +CONFIG_RTC_DRV_RV3032=m +CONFIG_RTC_DRV_RV8803=m +CONFIG_RTC_DRV_SD3078=m +CONFIG_RTC_DRV_M41T93=m +CONFIG_RTC_DRV_M41T94=m +CONFIG_RTC_DRV_DS1302=m +CONFIG_RTC_DRV_DS1305=m +CONFIG_RTC_DRV_DS1390=m +CONFIG_RTC_DRV_R9701=m +CONFIG_RTC_DRV_RX4581=m +CONFIG_RTC_DRV_RS5C348=m +CONFIG_RTC_DRV_MAX6902=m +CONFIG_RTC_DRV_PCF2123=m +CONFIG_RTC_DRV_DS3232=m +CONFIG_RTC_DRV_PCF2127=m +CONFIG_RTC_DRV_RV3029C2=m +CONFIG_DMADEVICES=y +CONFIG_DMA_BCM2835=y +CONFIG_DMA_BCM2708=y +CONFIG_DMABUF_HEAPS=y +CONFIG_DMABUF_HEAPS_SYSTEM=y +CONFIG_DMABUF_HEAPS_CMA=y +CONFIG_UIO=m +CONFIG_UIO_PDRV_GENIRQ=m +CONFIG_STAGING=y +CONFIG_R8712U=m +CONFIG_VT6656=m +CONFIG_STAGING_MEDIA=y +CONFIG_STAGING_MEDIA_DEPRECATED=y +CONFIG_FB_TFT=m +CONFIG_FB_TFT_AGM1264K_FL=m +CONFIG_FB_TFT_BD663474=m +CONFIG_FB_TFT_HX8340BN=m +CONFIG_FB_TFT_HX8347D=m +CONFIG_FB_TFT_HX8353D=m +CONFIG_FB_TFT_HX8357D=m +CONFIG_FB_TFT_ILI9163=m +CONFIG_FB_TFT_ILI9320=m +CONFIG_FB_TFT_ILI9325=m +CONFIG_FB_TFT_ILI9340=m +CONFIG_FB_TFT_ILI9341=m +CONFIG_FB_TFT_ILI9481=m +CONFIG_FB_TFT_ILI9486=m +CONFIG_FB_TFT_PCD8544=m +CONFIG_FB_TFT_RA8875=m +CONFIG_FB_TFT_S6D02A1=m +CONFIG_FB_TFT_S6D1121=m +CONFIG_FB_TFT_SH1106=m +CONFIG_FB_TFT_SSD1289=m +CONFIG_FB_TFT_SSD1306=m +CONFIG_FB_TFT_SSD1331=m +CONFIG_FB_TFT_SSD1351=m +CONFIG_FB_TFT_ST7735R=m +CONFIG_FB_TFT_ST7789V=m +CONFIG_FB_TFT_TINYLCD=m +CONFIG_FB_TFT_TLS8204=m +CONFIG_FB_TFT_UC1611=m +CONFIG_FB_TFT_UC1701=m +CONFIG_FB_TFT_UPD161704=m +CONFIG_BCM2835_VCHIQ=y +CONFIG_SND_BCM2835=m +CONFIG_VIDEO_BCM2835=m +CONFIG_VIDEO_CODEC_BCM2835=m +CONFIG_VIDEO_ISP_BCM2835=m +CONFIG_CLK_RASPBERRYPI=y +CONFIG_MAILBOX=y +CONFIG_BCM2835_MBOX=y +# CONFIG_IOMMU_SUPPORT is not set +CONFIG_RASPBERRYPI_POWER=y +CONFIG_IIO=m +CONFIG_IIO_BUFFER_CB=m +CONFIG_IIO_SW_TRIGGER=m +CONFIG_MCP320X=m +CONFIG_MCP3422=m +CONFIG_TI_ADS1015=m +CONFIG_BME680=m +CONFIG_CCS811=m +CONFIG_SENSIRION_SGP30=m +CONFIG_SPS30_I2C=m +CONFIG_MAX30102=m +CONFIG_DHT11=m +CONFIG_HDC100X=m +CONFIG_HDC3020=m +CONFIG_HTS221=m +CONFIG_HTU21=m +CONFIG_SI7020=m +CONFIG_BOSCH_BNO055_I2C=m +CONFIG_INV_MPU6050_I2C=m +CONFIG_APDS9960=m +CONFIG_AS73211=m +CONFIG_BH1750=m +CONFIG_TSL4531=m +CONFIG_VEML6070=m +CONFIG_VEML6075=m +CONFIG_IIO_HRTIMER_TRIGGER=m +CONFIG_IIO_INTERRUPT_TRIGGER=m +CONFIG_IIO_SYSFS_TRIGGER=m +CONFIG_BMP280=m +CONFIG_MS5637=m +CONFIG_MAXIM_THERMOCOUPLE=m +CONFIG_MAX31856=m +CONFIG_PWM=y +CONFIG_PWM_BCM2835=m +CONFIG_PWM_GPIO=m +CONFIG_PWM_PCA9685=m +CONFIG_PWM_RASPBERRYPI_POE=m +CONFIG_RPI_AXIPERF=m +CONFIG_MUX_GPIO=m +CONFIG_EXT4_FS=y +CONFIG_EXT4_FS_POSIX_ACL=y +CONFIG_EXT4_FS_SECURITY=y +CONFIG_REISERFS_FS=m +CONFIG_REISERFS_FS_XATTR=y +CONFIG_REISERFS_FS_POSIX_ACL=y +CONFIG_REISERFS_FS_SECURITY=y +CONFIG_JFS_FS=m +CONFIG_JFS_POSIX_ACL=y +CONFIG_JFS_SECURITY=y +CONFIG_JFS_STATISTICS=y +CONFIG_XFS_FS=m +CONFIG_XFS_QUOTA=y +CONFIG_XFS_POSIX_ACL=y +CONFIG_XFS_RT=y +CONFIG_GFS2_FS=m +CONFIG_OCFS2_FS=m +CONFIG_BTRFS_FS=m +CONFIG_BTRFS_FS_POSIX_ACL=y +CONFIG_NILFS2_FS=m +CONFIG_F2FS_FS=y +CONFIG_F2FS_FS_SECURITY=y +CONFIG_BCACHEFS_FS=m +CONFIG_BCACHEFS_QUOTA=y +CONFIG_BCACHEFS_POSIX_ACL=y +CONFIG_FANOTIFY=y +CONFIG_QFMT_V1=m +CONFIG_QFMT_V2=m +CONFIG_AUTOFS_FS=y +CONFIG_FUSE_FS=m +CONFIG_CUSE=m +CONFIG_OVERLAY_FS=m +CONFIG_FSCACHE=y +CONFIG_FSCACHE_STATS=y +CONFIG_CACHEFILES=m +CONFIG_ISO9660_FS=m +CONFIG_JOLIET=y +CONFIG_ZISOFS=y +CONFIG_UDF_FS=m +CONFIG_MSDOS_FS=y +CONFIG_VFAT_FS=y +CONFIG_FAT_DEFAULT_IOCHARSET="ascii" +CONFIG_EXFAT_FS=m +CONFIG_NTFS3_FS=m +CONFIG_TMPFS=y +CONFIG_TMPFS_POSIX_ACL=y +CONFIG_ECRYPT_FS=m +CONFIG_HFS_FS=m +CONFIG_HFSPLUS_FS=m +CONFIG_JFFS2_FS=m +CONFIG_JFFS2_SUMMARY=y +CONFIG_UBIFS_FS=m +CONFIG_SQUASHFS=m +CONFIG_SQUASHFS_XATTR=y +CONFIG_SQUASHFS_LZO=y +CONFIG_SQUASHFS_XZ=y +CONFIG_SQUASHFS_ZSTD=y +CONFIG_PSTORE=y +CONFIG_PSTORE_CONSOLE=y +CONFIG_PSTORE_RAM=y +CONFIG_NFS_FS=y +CONFIG_NFS_V2=y +CONFIG_NFS_V3_ACL=y +CONFIG_NFS_V4=y +CONFIG_NFS_SWAP=y +CONFIG_NFS_V4_1=y +CONFIG_NFS_V4_2=y +CONFIG_ROOT_NFS=y +CONFIG_NFSD=m +CONFIG_NFSD_V2=y +CONFIG_NFSD_V2_ACL=y +CONFIG_NFSD_V3_ACL=y +CONFIG_NFSD_V4=y +CONFIG_CEPH_FS=m +CONFIG_CIFS=m +CONFIG_CIFS_UPCALL=y +CONFIG_CIFS_XATTR=y +CONFIG_CIFS_DFS_UPCALL=y +CONFIG_CIFS_FSCACHE=y +CONFIG_SMB_SERVER=m +CONFIG_9P_FS=m +CONFIG_9P_FS_POSIX_ACL=y +CONFIG_NLS_DEFAULT="utf8" +CONFIG_NLS_CODEPAGE_437=y +CONFIG_NLS_CODEPAGE_737=m +CONFIG_NLS_CODEPAGE_775=m +CONFIG_NLS_CODEPAGE_850=m +CONFIG_NLS_CODEPAGE_852=m +CONFIG_NLS_CODEPAGE_855=m +CONFIG_NLS_CODEPAGE_857=m +CONFIG_NLS_CODEPAGE_860=m +CONFIG_NLS_CODEPAGE_861=m +CONFIG_NLS_CODEPAGE_862=m +CONFIG_NLS_CODEPAGE_863=m +CONFIG_NLS_CODEPAGE_864=m +CONFIG_NLS_CODEPAGE_865=m +CONFIG_NLS_CODEPAGE_866=m +CONFIG_NLS_CODEPAGE_869=m +CONFIG_NLS_CODEPAGE_936=m +CONFIG_NLS_CODEPAGE_950=m +CONFIG_NLS_CODEPAGE_932=m +CONFIG_NLS_CODEPAGE_949=m +CONFIG_NLS_CODEPAGE_874=m +CONFIG_NLS_ISO8859_8=m +CONFIG_NLS_CODEPAGE_1250=m +CONFIG_NLS_CODEPAGE_1251=m +CONFIG_NLS_ASCII=y +CONFIG_NLS_ISO8859_1=m +CONFIG_NLS_ISO8859_2=m +CONFIG_NLS_ISO8859_3=m +CONFIG_NLS_ISO8859_4=m +CONFIG_NLS_ISO8859_5=m +CONFIG_NLS_ISO8859_6=m +CONFIG_NLS_ISO8859_7=m +CONFIG_NLS_ISO8859_9=m +CONFIG_NLS_ISO8859_13=m +CONFIG_NLS_ISO8859_14=m +CONFIG_NLS_ISO8859_15=m +CONFIG_NLS_KOI8_R=m +CONFIG_NLS_KOI8_U=m +CONFIG_DLM=m +CONFIG_KEY_DH_OPERATIONS=y +CONFIG_SECURITY=y +CONFIG_SECURITY_APPARMOR=y +CONFIG_LSM="" +CONFIG_CRYPTO_USER=m +CONFIG_CRYPTO_CRYPTD=m +CONFIG_CRYPTO_CAST5=m +CONFIG_CRYPTO_DES=y +CONFIG_CRYPTO_TWOFISH=m +CONFIG_CRYPTO_ADIANTUM=m +CONFIG_CRYPTO_CBC=y +CONFIG_CRYPTO_CTS=m +CONFIG_CRYPTO_XTS=m +CONFIG_CRYPTO_CHACHA20POLY1305=m +CONFIG_CRYPTO_MD4=m +CONFIG_CRYPTO_WP512=m +CONFIG_CRYPTO_XCBC=m +CONFIG_CRYPTO_LZ4=m +CONFIG_CRYPTO_USER_API_HASH=m +CONFIG_CRYPTO_USER_API_SKCIPHER=m +CONFIG_CRYPTO_USER_API_RNG=m +CONFIG_CRYPTO_USER_API_AEAD=m +CONFIG_CRYPTO_SHA1_ARM=m +CONFIG_CRYPTO_AES_ARM=m +# CONFIG_CRYPTO_HW is not set +CONFIG_PKCS8_PRIVATE_KEY_PARSER=m +CONFIG_CRC_ITU_T=y +CONFIG_LIBCRC32C=y +CONFIG_DMA_CMA=y +CONFIG_CMA_SIZE_MBYTES=5 +CONFIG_PRINTK_TIME=y +CONFIG_BOOT_PRINTK_DELAY=y +CONFIG_MAGIC_SYSRQ_DEFAULT_ENABLE=0x1f6 +CONFIG_KGDB=y +CONFIG_KGDB_KDB=y +CONFIG_KDB_KEYBOARD=y +CONFIG_DEBUG_MEMORY_INIT=y +CONFIG_DETECT_HUNG_TASK=y +CONFIG_LATENCYTOP=y +CONFIG_FUNCTION_PROFILER=y +CONFIG_STACK_TRACER=y +CONFIG_SCHED_TRACER=y +CONFIG_BLK_DEV_IO_TRACE=y +# CONFIG_UPROBE_EVENTS is not set diff --git a/arch/arm/include/asm/irqflags.h b/arch/arm/include/asm/irqflags.h index aeec7f24eb75be..a3b186608c6092 100644 --- a/arch/arm/include/asm/irqflags.h +++ b/arch/arm/include/asm/irqflags.h @@ -163,13 +163,23 @@ static inline unsigned long arch_local_save_flags(void) } /* - * restore saved IRQ & FIQ state + * restore saved IRQ state */ #define arch_local_irq_restore arch_local_irq_restore static inline void arch_local_irq_restore(unsigned long flags) { - asm volatile( - " msr " IRQMASK_REG_NAME_W ", %0 @ local_irq_restore" + unsigned long temp = 0; + flags &= ~(1 << 6); + asm volatile ( + " mrs %0, cpsr" + : "=r" (temp) + : + : "memory", "cc"); + /* Preserve FIQ bit */ + temp &= (1 << 6); + flags = flags | temp; + asm volatile ( + " msr cpsr_c, %0 @ local_irq_restore" : : "r" (flags) : "memory", "cc"); diff --git a/arch/arm/include/asm/string.h b/arch/arm/include/asm/string.h index 6c607c68f3ad75..ba7fc0bc9a15d3 100644 --- a/arch/arm/include/asm/string.h +++ b/arch/arm/include/asm/string.h @@ -65,4 +65,9 @@ static inline void *memset64(uint64_t *p, uint64_t v, __kernel_size_t n) #endif +#ifdef CONFIG_BCM2835_FAST_MEMCPY +#define __HAVE_ARCH_MEMCMP +extern int memcmp(const void *, const void *, size_t); +#endif + #endif diff --git a/arch/arm/include/asm/uaccess.h b/arch/arm/include/asm/uaccess.h index f90be312418e87..4fe53e1a647cef 100644 --- a/arch/arm/include/asm/uaccess.h +++ b/arch/arm/include/asm/uaccess.h @@ -533,6 +533,9 @@ do { \ extern unsigned long __must_check arm_copy_from_user(void *to, const void __user *from, unsigned long n); +extern unsigned long __must_check +__copy_from_user_std(void *to, const void __user *from, unsigned long n); + static inline unsigned long __must_check raw_copy_from_user(void *to, const void __user *from, unsigned long n) { diff --git a/arch/arm/kernel/fiq.c b/arch/arm/kernel/fiq.c index d2c8e53135397e..bb320f08ad2fce 100644 --- a/arch/arm/kernel/fiq.c +++ b/arch/arm/kernel/fiq.c @@ -57,6 +57,8 @@ static unsigned long dfl_fiq_insn; static struct pt_regs dfl_fiq_regs; +extern int irq_activate(struct irq_desc *desc); + /* Default reacquire function * - we always relinquish FIQ control * - we always reacquire FIQ control @@ -141,6 +143,8 @@ static int fiq_start; void enable_fiq(int fiq) { + struct irq_desc *desc = irq_to_desc(fiq + fiq_start); + irq_activate(desc); enable_irq(fiq + fiq_start); } diff --git a/arch/arm/kernel/fiqasm.S b/arch/arm/kernel/fiqasm.S index 8dd26e1a9bd690..eef484756af217 100644 --- a/arch/arm/kernel/fiqasm.S +++ b/arch/arm/kernel/fiqasm.S @@ -47,3 +47,7 @@ ENTRY(__get_fiq_regs) mov r0, r0 @ avoid hazard prior to ARMv4 ret lr ENDPROC(__get_fiq_regs) + +ENTRY(__FIQ_Branch) + mov pc, r8 +ENDPROC(__FIQ_Branch) diff --git a/arch/arm/kernel/reboot.c b/arch/arm/kernel/reboot.c index 3f0d5c3dae11b5..cfdbcc9826c0de 100644 --- a/arch/arm/kernel/reboot.c +++ b/arch/arm/kernel/reboot.c @@ -102,9 +102,7 @@ void machine_shutdown(void) */ void machine_halt(void) { - local_irq_disable(); - smp_send_stop(); - while (1); + machine_power_off(); } /* diff --git a/arch/arm/kernel/setup.c b/arch/arm/kernel/setup.c index e6a857bf0ce69e..6fa23db5ac5a24 100644 --- a/arch/arm/kernel/setup.c +++ b/arch/arm/kernel/setup.c @@ -1266,6 +1266,8 @@ static int c_show(struct seq_file *m, void *v) { int i, j; u32 cpuid; + struct device_node *np; + const char *model; for_each_online_cpu(i) { /* @@ -1325,6 +1327,14 @@ static int c_show(struct seq_file *m, void *v) seq_printf(m, "Revision\t: %04x\n", system_rev); seq_printf(m, "Serial\t\t: %s\n", system_serial); + np = of_find_node_by_path("/"); + if (np) { + if (!of_property_read_string(np, "model", + &model)) + seq_printf(m, "Model\t\t: %s\n", model); + of_node_put(np); + } + return 0; } diff --git a/arch/arm/lib/Makefile b/arch/arm/lib/Makefile index 0ca5aae1bcc3e3..c3a0c40550eeff 100644 --- a/arch/arm/lib/Makefile +++ b/arch/arm/lib/Makefile @@ -7,8 +7,8 @@ lib-y := changebit.o csumipv6.o csumpartial.o \ csumpartialcopy.o csumpartialcopyuser.o clearbit.o \ - delay.o delay-loop.o findbit.o memchr.o memcpy.o \ - memmove.o memset.o setbit.o \ + delay.o delay-loop.o findbit.o memchr.o \ + setbit.o \ strchr.o strrchr.o \ testchangebit.o testclearbit.o testsetbit.o \ ashldi3.o ashrdi3.o lshrdi3.o muldi3.o \ @@ -25,6 +25,16 @@ else lib-y += backtrace.o endif +# Choose optimised implementations for Raspberry Pi +ifeq ($(CONFIG_BCM2835_FAST_MEMCPY),y) + CFLAGS_uaccess_with_memcpy.o += -DCOPY_FROM_USER_THRESHOLD=1600 + CFLAGS_uaccess_with_memcpy.o += -DCOPY_TO_USER_THRESHOLD=672 + obj-$(CONFIG_MODULES) += exports_rpi.o + lib-y += memcpy_rpi.o memmove_rpi.o memset_rpi.o memcmp_rpi.o +else + lib-y += memcpy.o memmove.o memset.o +endif + # using lib_ here won't override already available weak symbols obj-$(CONFIG_UACCESS_WITH_MEMCPY) += uaccess_with_memcpy.o diff --git a/arch/arm/lib/arm-mem.h b/arch/arm/lib/arm-mem.h new file mode 100644 index 00000000000000..5d4bda19ad207c --- /dev/null +++ b/arch/arm/lib/arm-mem.h @@ -0,0 +1,159 @@ +/* +Copyright (c) 2013, Raspberry Pi Foundation +Copyright (c) 2013, RISC OS Open Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +.macro myfunc fname + .func fname + .global fname +fname: +.endm + +.macro preload_leading_step1 backwards, ptr, base +/* If the destination is already 16-byte aligned, then we need to preload + * between 0 and prefetch_distance (inclusive) cache lines ahead so there + * are no gaps when the inner loop starts. + */ + .if backwards + sub ptr, base, #1 + bic ptr, ptr, #31 + .else + bic ptr, base, #31 + .endif + .set OFFSET, 0 + .rept prefetch_distance+1 + pld [ptr, #OFFSET] + .if backwards + .set OFFSET, OFFSET-32 + .else + .set OFFSET, OFFSET+32 + .endif + .endr +.endm + +.macro preload_leading_step2 backwards, ptr, base, leading_bytes, tmp +/* However, if the destination is not 16-byte aligned, we may need to + * preload one more cache line than that. The question we need to ask is: + * are the leading bytes more than the amount by which the source + * pointer will be rounded down for preloading, and if so, by how many + * cache lines? + */ + .if backwards +/* Here we compare against how many bytes we are into the + * cache line, counting down from the highest such address. + * Effectively, we want to calculate + * leading_bytes = dst&15 + * cacheline_offset = 31-((src-leading_bytes-1)&31) + * extra_needed = leading_bytes - cacheline_offset + * and test if extra_needed is <= 0, or rearranging: + * leading_bytes + (src-leading_bytes-1)&31 <= 31 + */ + mov tmp, base, lsl #32-5 + sbc tmp, tmp, leading_bytes, lsl #32-5 + adds tmp, tmp, leading_bytes, lsl #32-5 + bcc 61f + pld [ptr, #-32*(prefetch_distance+1)] + .else +/* Effectively, we want to calculate + * leading_bytes = (-dst)&15 + * cacheline_offset = (src+leading_bytes)&31 + * extra_needed = leading_bytes - cacheline_offset + * and test if extra_needed is <= 0. + */ + mov tmp, base, lsl #32-5 + add tmp, tmp, leading_bytes, lsl #32-5 + rsbs tmp, tmp, leading_bytes, lsl #32-5 + bls 61f + pld [ptr, #32*(prefetch_distance+1)] + .endif +61: +.endm + +.macro preload_trailing backwards, base, remain, tmp + /* We need either 0, 1 or 2 extra preloads */ + .if backwards + rsb tmp, base, #0 + mov tmp, tmp, lsl #32-5 + .else + mov tmp, base, lsl #32-5 + .endif + adds tmp, tmp, remain, lsl #32-5 + adceqs tmp, tmp, #0 + /* The instruction above has two effects: ensures Z is only + * set if C was clear (so Z indicates that both shifted quantities + * were 0), and clears C if Z was set (so C indicates that the sum + * of the shifted quantities was greater and not equal to 32) */ + beq 82f + .if backwards + sub tmp, base, #1 + bic tmp, tmp, #31 + .else + bic tmp, base, #31 + .endif + bcc 81f + .if backwards + pld [tmp, #-32*(prefetch_distance+1)] +81: + pld [tmp, #-32*prefetch_distance] + .else + pld [tmp, #32*(prefetch_distance+2)] +81: + pld [tmp, #32*(prefetch_distance+1)] + .endif +82: +.endm + +.macro preload_all backwards, narrow_case, shift, base, remain, tmp0, tmp1 + .if backwards + sub tmp0, base, #1 + bic tmp0, tmp0, #31 + pld [tmp0] + sub tmp1, base, remain, lsl #shift + .else + bic tmp0, base, #31 + pld [tmp0] + add tmp1, base, remain, lsl #shift + sub tmp1, tmp1, #1 + .endif + bic tmp1, tmp1, #31 + cmp tmp1, tmp0 + beq 92f + .if narrow_case + /* In this case, all the data fits in either 1 or 2 cache lines */ + pld [tmp1] + .else +91: + .if backwards + sub tmp0, tmp0, #32 + .else + add tmp0, tmp0, #32 + .endif + cmp tmp0, tmp1 + pld [tmp0] + bne 91b + .endif +92: +.endm diff --git a/arch/arm/lib/copy_from_user.S b/arch/arm/lib/copy_from_user.S index 270de7debd0f10..2eda93fc22e67e 100644 --- a/arch/arm/lib/copy_from_user.S +++ b/arch/arm/lib/copy_from_user.S @@ -104,7 +104,8 @@ UNWIND( .save {r0, r2, r3, \regs} ) .text -ENTRY(arm_copy_from_user) +ENTRY(__copy_from_user_std) +WEAK(arm_copy_from_user) #ifdef CONFIG_CPU_SPECTRE ldr r3, =TASK_SIZE uaccess_mask_range_ptr r1, r2, r3, ip @@ -113,6 +114,7 @@ ENTRY(arm_copy_from_user) #include "copy_template.S" ENDPROC(arm_copy_from_user) +ENDPROC(__copy_from_user_std) .pushsection .text.fixup,"ax" .align 0 diff --git a/arch/arm/lib/exports_rpi.c b/arch/arm/lib/exports_rpi.c new file mode 100644 index 00000000000000..1f826047db754f --- /dev/null +++ b/arch/arm/lib/exports_rpi.c @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2014, Raspberry Pi (Trading) Ltd. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The names of the above-listed copyright holders may not be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2, as published by the Free + * Software Foundation. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <linux/kernel.h> +#include <linux/module.h> + +EXPORT_SYMBOL(memcmp); diff --git a/arch/arm/lib/memcmp_rpi.S b/arch/arm/lib/memcmp_rpi.S new file mode 100644 index 00000000000000..bf6e4edfc9d3b9 --- /dev/null +++ b/arch/arm/lib/memcmp_rpi.S @@ -0,0 +1,285 @@ +/* +Copyright (c) 2013, Raspberry Pi Foundation +Copyright (c) 2013, RISC OS Open Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include <linux/linkage.h> +#include "arm-mem.h" + +/* Prevent the stack from becoming executable */ +#if defined(__linux__) && defined(__ELF__) +.section .note.GNU-stack,"",%progbits +#endif + + .text + .arch armv6 + .object_arch armv4 + .arm + .altmacro + .p2align 2 + +.macro memcmp_process_head unaligned + .if unaligned + ldr DAT0, [S_1], #4 + ldr DAT1, [S_1], #4 + ldr DAT2, [S_1], #4 + ldr DAT3, [S_1], #4 + .else + ldmia S_1!, {DAT0, DAT1, DAT2, DAT3} + .endif + ldmia S_2!, {DAT4, DAT5, DAT6, DAT7} +.endm + +.macro memcmp_process_tail + cmp DAT0, DAT4 + cmpeq DAT1, DAT5 + cmpeq DAT2, DAT6 + cmpeq DAT3, DAT7 + bne 200f +.endm + +.macro memcmp_leading_31bytes + movs DAT0, OFF, lsl #31 + ldrmib DAT0, [S_1], #1 + ldrcsh DAT1, [S_1], #2 + ldrmib DAT4, [S_2], #1 + ldrcsh DAT5, [S_2], #2 + movpl DAT0, #0 + movcc DAT1, #0 + movpl DAT4, #0 + movcc DAT5, #0 + submi N, N, #1 + subcs N, N, #2 + cmp DAT0, DAT4 + cmpeq DAT1, DAT5 + bne 200f + movs DAT0, OFF, lsl #29 + ldrmi DAT0, [S_1], #4 + ldrcs DAT1, [S_1], #4 + ldrcs DAT2, [S_1], #4 + ldrmi DAT4, [S_2], #4 + ldmcsia S_2!, {DAT5, DAT6} + movpl DAT0, #0 + movcc DAT1, #0 + movcc DAT2, #0 + movpl DAT4, #0 + movcc DAT5, #0 + movcc DAT6, #0 + submi N, N, #4 + subcs N, N, #8 + cmp DAT0, DAT4 + cmpeq DAT1, DAT5 + cmpeq DAT2, DAT6 + bne 200f + tst OFF, #16 + beq 105f + memcmp_process_head 1 + sub N, N, #16 + memcmp_process_tail +105: +.endm + +.macro memcmp_trailing_15bytes unaligned + movs N, N, lsl #29 + .if unaligned + ldrcs DAT0, [S_1], #4 + ldrcs DAT1, [S_1], #4 + .else + ldmcsia S_1!, {DAT0, DAT1} + .endif + ldrmi DAT2, [S_1], #4 + ldmcsia S_2!, {DAT4, DAT5} + ldrmi DAT6, [S_2], #4 + movcc DAT0, #0 + movcc DAT1, #0 + movpl DAT2, #0 + movcc DAT4, #0 + movcc DAT5, #0 + movpl DAT6, #0 + cmp DAT0, DAT4 + cmpeq DAT1, DAT5 + cmpeq DAT2, DAT6 + bne 200f + movs N, N, lsl #2 + ldrcsh DAT0, [S_1], #2 + ldrmib DAT1, [S_1] + ldrcsh DAT4, [S_2], #2 + ldrmib DAT5, [S_2] + movcc DAT0, #0 + movpl DAT1, #0 + movcc DAT4, #0 + movpl DAT5, #0 + cmp DAT0, DAT4 + cmpeq DAT1, DAT5 + bne 200f +.endm + +.macro memcmp_long_inner_loop unaligned +110: + memcmp_process_head unaligned + pld [S_2, #prefetch_distance*32 + 16] + memcmp_process_tail + memcmp_process_head unaligned + pld [S_1, OFF] + memcmp_process_tail + subs N, N, #32 + bhs 110b + /* Just before the final (prefetch_distance+1) 32-byte blocks, + * deal with final preloads */ + preload_trailing 0, S_1, N, DAT0 + preload_trailing 0, S_2, N, DAT0 + add N, N, #(prefetch_distance+2)*32 - 16 +120: + memcmp_process_head unaligned + memcmp_process_tail + subs N, N, #16 + bhs 120b + /* Trailing words and bytes */ + tst N, #15 + beq 199f + memcmp_trailing_15bytes unaligned +199: /* Reached end without detecting a difference */ + mov a1, #0 + setend le + pop {DAT1-DAT6, pc} +.endm + +.macro memcmp_short_inner_loop unaligned + subs N, N, #16 /* simplifies inner loop termination */ + blo 122f +120: + memcmp_process_head unaligned + memcmp_process_tail + subs N, N, #16 + bhs 120b +122: /* Trailing words and bytes */ + tst N, #15 + beq 199f + memcmp_trailing_15bytes unaligned +199: /* Reached end without detecting a difference */ + mov a1, #0 + setend le + pop {DAT1-DAT6, pc} +.endm + +/* + * int memcmp(const void *s1, const void *s2, size_t n); + * On entry: + * a1 = pointer to buffer 1 + * a2 = pointer to buffer 2 + * a3 = number of bytes to compare (as unsigned chars) + * On exit: + * a1 = >0/=0/<0 if s1 >/=/< s2 + */ + +.set prefetch_distance, 2 + +ENTRY(memcmp) + S_1 .req a1 + S_2 .req a2 + N .req a3 + DAT0 .req a4 + DAT1 .req v1 + DAT2 .req v2 + DAT3 .req v3 + DAT4 .req v4 + DAT5 .req v5 + DAT6 .req v6 + DAT7 .req ip + OFF .req lr + + push {DAT1-DAT6, lr} + setend be /* lowest-addressed bytes are most significant */ + + /* To preload ahead as we go, we need at least (prefetch_distance+2) 32-byte blocks */ + cmp N, #(prefetch_distance+3)*32 - 1 + blo 170f + + /* Long case */ + /* Adjust N so that the decrement instruction can also test for + * inner loop termination. We want it to stop when there are + * (prefetch_distance+1) complete blocks to go. */ + sub N, N, #(prefetch_distance+2)*32 + preload_leading_step1 0, DAT0, S_1 + preload_leading_step1 0, DAT1, S_2 + tst S_2, #31 + beq 154f + rsb OFF, S_2, #0 /* no need to AND with 15 here */ + preload_leading_step2 0, DAT0, S_1, OFF, DAT2 + preload_leading_step2 0, DAT1, S_2, OFF, DAT2 + memcmp_leading_31bytes +154: /* Second source now cacheline (32-byte) aligned; we have at + * least one prefetch to go. */ + /* Prefetch offset is best selected such that it lies in the + * first 8 of each 32 bytes - but it's just as easy to aim for + * the first one */ + and OFF, S_1, #31 + rsb OFF, OFF, #32*prefetch_distance + tst S_1, #3 + bne 140f + memcmp_long_inner_loop 0 +140: memcmp_long_inner_loop 1 + +170: /* Short case */ + teq N, #0 + beq 199f + preload_all 0, 0, 0, S_1, N, DAT0, DAT1 + preload_all 0, 0, 0, S_2, N, DAT0, DAT1 + tst S_2, #3 + beq 174f +172: subs N, N, #1 + blo 199f + ldrb DAT0, [S_1], #1 + ldrb DAT4, [S_2], #1 + cmp DAT0, DAT4 + bne 200f + tst S_2, #3 + bne 172b +174: /* Second source now 4-byte aligned; we have 0 or more bytes to go */ + tst S_1, #3 + bne 140f + memcmp_short_inner_loop 0 +140: memcmp_short_inner_loop 1 + +200: /* Difference found: determine sign. */ + movhi a1, #1 + movlo a1, #-1 + setend le + pop {DAT1-DAT6, pc} + + .unreq S_1 + .unreq S_2 + .unreq N + .unreq DAT0 + .unreq DAT1 + .unreq DAT2 + .unreq DAT3 + .unreq DAT4 + .unreq DAT5 + .unreq DAT6 + .unreq DAT7 + .unreq OFF +ENDPROC(memcmp) diff --git a/arch/arm/lib/memcpy_rpi.S b/arch/arm/lib/memcpy_rpi.S new file mode 100644 index 00000000000000..d246f9f3903aca --- /dev/null +++ b/arch/arm/lib/memcpy_rpi.S @@ -0,0 +1,65 @@ +/* +Copyright (c) 2013, Raspberry Pi Foundation +Copyright (c) 2013, RISC OS Open Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include <linux/linkage.h> +#include <asm/assembler.h> +#include <asm/unwind.h> +#include "arm-mem.h" +#include "memcpymove.h" + +/* Prevent the stack from becoming executable */ +#if defined(__linux__) && defined(__ELF__) +.section .note.GNU-stack,"",%progbits +#endif + + .text + .arch armv6 + .object_arch armv4 + .arm + .altmacro + .p2align 2 + +/* + * void *memcpy(void * restrict s1, const void * restrict s2, size_t n); + * On entry: + * a1 = pointer to destination + * a2 = pointer to source + * a3 = number of bytes to copy + * On exit: + * a1 preserved + */ + +.set prefetch_distance, 3 + +ENTRY(mmiocpy) +ENTRY(memcpy) +ENTRY(__memcpy) + memcpy 0 +ENDPROC(__memcpy) +ENDPROC(memcpy) +ENDPROC(mmiocpy) diff --git a/arch/arm/lib/memcpymove.h b/arch/arm/lib/memcpymove.h new file mode 100644 index 00000000000000..65a6e065a7f2fe --- /dev/null +++ b/arch/arm/lib/memcpymove.h @@ -0,0 +1,488 @@ +/* +Copyright (c) 2013, Raspberry Pi Foundation +Copyright (c) 2013, RISC OS Open Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +.macro unaligned_words backwards, align, use_pld, words, r0, r1, r2, r3, r4, r5, r6, r7, r8 + .if words == 1 + .if backwards + mov r1, r0, lsl #32-align*8 + ldr r0, [S, #-4]! + orr r1, r1, r0, lsr #align*8 + str r1, [D, #-4]! + .else + mov r0, r1, lsr #align*8 + ldr r1, [S, #4]! + orr r0, r0, r1, lsl #32-align*8 + str r0, [D], #4 + .endif + .elseif words == 2 + .if backwards + ldr r1, [S, #-4]! + mov r2, r0, lsl #32-align*8 + ldr r0, [S, #-4]! + orr r2, r2, r1, lsr #align*8 + mov r1, r1, lsl #32-align*8 + orr r1, r1, r0, lsr #align*8 + stmdb D!, {r1, r2} + .else + ldr r1, [S, #4]! + mov r0, r2, lsr #align*8 + ldr r2, [S, #4]! + orr r0, r0, r1, lsl #32-align*8 + mov r1, r1, lsr #align*8 + orr r1, r1, r2, lsl #32-align*8 + stmia D!, {r0, r1} + .endif + .elseif words == 4 + .if backwards + ldmdb S!, {r2, r3} + mov r4, r0, lsl #32-align*8 + ldmdb S!, {r0, r1} + orr r4, r4, r3, lsr #align*8 + mov r3, r3, lsl #32-align*8 + orr r3, r3, r2, lsr #align*8 + mov r2, r2, lsl #32-align*8 + orr r2, r2, r1, lsr #align*8 + mov r1, r1, lsl #32-align*8 + orr r1, r1, r0, lsr #align*8 + stmdb D!, {r1, r2, r3, r4} + .else + ldmib S!, {r1, r2} + mov r0, r4, lsr #align*8 + ldmib S!, {r3, r4} + orr r0, r0, r1, lsl #32-align*8 + mov r1, r1, lsr #align*8 + orr r1, r1, r2, lsl #32-align*8 + mov r2, r2, lsr #align*8 + orr r2, r2, r3, lsl #32-align*8 + mov r3, r3, lsr #align*8 + orr r3, r3, r4, lsl #32-align*8 + stmia D!, {r0, r1, r2, r3} + .endif + .elseif words == 8 + .if backwards + ldmdb S!, {r4, r5, r6, r7} + mov r8, r0, lsl #32-align*8 + ldmdb S!, {r0, r1, r2, r3} + .if use_pld + pld [S, OFF] + .endif + orr r8, r8, r7, lsr #align*8 + mov r7, r7, lsl #32-align*8 + orr r7, r7, r6, lsr #align*8 + mov r6, r6, lsl #32-align*8 + orr r6, r6, r5, lsr #align*8 + mov r5, r5, lsl #32-align*8 + orr r5, r5, r4, lsr #align*8 + mov r4, r4, lsl #32-align*8 + orr r4, r4, r3, lsr #align*8 + mov r3, r3, lsl #32-align*8 + orr r3, r3, r2, lsr #align*8 + mov r2, r2, lsl #32-align*8 + orr r2, r2, r1, lsr #align*8 + mov r1, r1, lsl #32-align*8 + orr r1, r1, r0, lsr #align*8 + stmdb D!, {r5, r6, r7, r8} + stmdb D!, {r1, r2, r3, r4} + .else + ldmib S!, {r1, r2, r3, r4} + mov r0, r8, lsr #align*8 + ldmib S!, {r5, r6, r7, r8} + .if use_pld + pld [S, OFF] + .endif + orr r0, r0, r1, lsl #32-align*8 + mov r1, r1, lsr #align*8 + orr r1, r1, r2, lsl #32-align*8 + mov r2, r2, lsr #align*8 + orr r2, r2, r3, lsl #32-align*8 + mov r3, r3, lsr #align*8 + orr r3, r3, r4, lsl #32-align*8 + mov r4, r4, lsr #align*8 + orr r4, r4, r5, lsl #32-align*8 + mov r5, r5, lsr #align*8 + orr r5, r5, r6, lsl #32-align*8 + mov r6, r6, lsr #align*8 + orr r6, r6, r7, lsl #32-align*8 + mov r7, r7, lsr #align*8 + orr r7, r7, r8, lsl #32-align*8 + stmia D!, {r0, r1, r2, r3} + stmia D!, {r4, r5, r6, r7} + .endif + .endif +.endm + +.macro memcpy_leading_15bytes backwards, align + movs DAT1, DAT2, lsl #31 + sub N, N, DAT2 + .if backwards + ldrmib DAT0, [S, #-1]! + ldrcsh DAT1, [S, #-2]! + strmib DAT0, [D, #-1]! + strcsh DAT1, [D, #-2]! + .else + ldrmib DAT0, [S], #1 + ldrcsh DAT1, [S], #2 + strmib DAT0, [D], #1 + strcsh DAT1, [D], #2 + .endif + movs DAT1, DAT2, lsl #29 + .if backwards + ldrmi DAT0, [S, #-4]! + .if align == 0 + ldmcsdb S!, {DAT1, DAT2} + .else + ldrcs DAT2, [S, #-4]! + ldrcs DAT1, [S, #-4]! + .endif + strmi DAT0, [D, #-4]! + stmcsdb D!, {DAT1, DAT2} + .else + ldrmi DAT0, [S], #4 + .if align == 0 + ldmcsia S!, {DAT1, DAT2} + .else + ldrcs DAT1, [S], #4 + ldrcs DAT2, [S], #4 + .endif + strmi DAT0, [D], #4 + stmcsia D!, {DAT1, DAT2} + .endif +.endm + +.macro memcpy_trailing_15bytes backwards, align + movs N, N, lsl #29 + .if backwards + .if align == 0 + ldmcsdb S!, {DAT0, DAT1} + .else + ldrcs DAT1, [S, #-4]! + ldrcs DAT0, [S, #-4]! + .endif + ldrmi DAT2, [S, #-4]! + stmcsdb D!, {DAT0, DAT1} + strmi DAT2, [D, #-4]! + .else + .if align == 0 + ldmcsia S!, {DAT0, DAT1} + .else + ldrcs DAT0, [S], #4 + ldrcs DAT1, [S], #4 + .endif + ldrmi DAT2, [S], #4 + stmcsia D!, {DAT0, DAT1} + strmi DAT2, [D], #4 + .endif + movs N, N, lsl #2 + .if backwards + ldrcsh DAT0, [S, #-2]! + ldrmib DAT1, [S, #-1] + strcsh DAT0, [D, #-2]! + strmib DAT1, [D, #-1] + .else + ldrcsh DAT0, [S], #2 + ldrmib DAT1, [S] + strcsh DAT0, [D], #2 + strmib DAT1, [D] + .endif +.endm + +.macro memcpy_long_inner_loop backwards, align + .if align != 0 + .if backwards + ldr DAT0, [S, #-align]! + .else + ldr LAST, [S, #-align]! + .endif + .endif +110: + .if align == 0 + .if backwards + ldmdb S!, {DAT0, DAT1, DAT2, DAT3, DAT4, DAT5, DAT6, LAST} + pld [S, OFF] + stmdb D!, {DAT4, DAT5, DAT6, LAST} + stmdb D!, {DAT0, DAT1, DAT2, DAT3} + .else + ldmia S!, {DAT0, DAT1, DAT2, DAT3, DAT4, DAT5, DAT6, LAST} + pld [S, OFF] + stmia D!, {DAT0, DAT1, DAT2, DAT3} + stmia D!, {DAT4, DAT5, DAT6, LAST} + .endif + .else + unaligned_words backwards, align, 1, 8, DAT0, DAT1, DAT2, DAT3, DAT4, DAT5, DAT6, DAT7, LAST + .endif + subs N, N, #32 + bhs 110b + /* Just before the final (prefetch_distance+1) 32-byte blocks, deal with final preloads */ + preload_trailing backwards, S, N, OFF + add N, N, #(prefetch_distance+2)*32 - 32 +120: + .if align == 0 + .if backwards + ldmdb S!, {DAT0, DAT1, DAT2, DAT3, DAT4, DAT5, DAT6, LAST} + stmdb D!, {DAT4, DAT5, DAT6, LAST} + stmdb D!, {DAT0, DAT1, DAT2, DAT3} + .else + ldmia S!, {DAT0, DAT1, DAT2, DAT3, DAT4, DAT5, DAT6, LAST} + stmia D!, {DAT0, DAT1, DAT2, DAT3} + stmia D!, {DAT4, DAT5, DAT6, LAST} + .endif + .else + unaligned_words backwards, align, 0, 8, DAT0, DAT1, DAT2, DAT3, DAT4, DAT5, DAT6, DAT7, LAST + .endif + subs N, N, #32 + bhs 120b + tst N, #16 + .if align == 0 + .if backwards + ldmnedb S!, {DAT0, DAT1, DAT2, LAST} + stmnedb D!, {DAT0, DAT1, DAT2, LAST} + .else + ldmneia S!, {DAT0, DAT1, DAT2, LAST} + stmneia D!, {DAT0, DAT1, DAT2, LAST} + .endif + .else + beq 130f + unaligned_words backwards, align, 0, 4, DAT0, DAT1, DAT2, DAT3, LAST +130: + .endif + /* Trailing words and bytes */ + tst N, #15 + beq 199f + .if align != 0 + add S, S, #align + .endif + memcpy_trailing_15bytes backwards, align +199: + pop {DAT3, DAT4, DAT5, DAT6, DAT7} + pop {D, DAT1, DAT2, pc} +.endm + +.macro memcpy_medium_inner_loop backwards, align +120: + .if backwards + .if align == 0 + ldmdb S!, {DAT0, DAT1, DAT2, LAST} + .else + ldr LAST, [S, #-4]! + ldr DAT2, [S, #-4]! + ldr DAT1, [S, #-4]! + ldr DAT0, [S, #-4]! + .endif + stmdb D!, {DAT0, DAT1, DAT2, LAST} + .else + .if align == 0 + ldmia S!, {DAT0, DAT1, DAT2, LAST} + .else + ldr DAT0, [S], #4 + ldr DAT1, [S], #4 + ldr DAT2, [S], #4 + ldr LAST, [S], #4 + .endif + stmia D!, {DAT0, DAT1, DAT2, LAST} + .endif + subs N, N, #16 + bhs 120b + /* Trailing words and bytes */ + tst N, #15 + beq 199f + memcpy_trailing_15bytes backwards, align +199: + pop {D, DAT1, DAT2, pc} +.endm + +.macro memcpy_short_inner_loop backwards, align + tst N, #16 + .if backwards + .if align == 0 + ldmnedb S!, {DAT0, DAT1, DAT2, LAST} + .else + ldrne LAST, [S, #-4]! + ldrne DAT2, [S, #-4]! + ldrne DAT1, [S, #-4]! + ldrne DAT0, [S, #-4]! + .endif + stmnedb D!, {DAT0, DAT1, DAT2, LAST} + .else + .if align == 0 + ldmneia S!, {DAT0, DAT1, DAT2, LAST} + .else + ldrne DAT0, [S], #4 + ldrne DAT1, [S], #4 + ldrne DAT2, [S], #4 + ldrne LAST, [S], #4 + .endif + stmneia D!, {DAT0, DAT1, DAT2, LAST} + .endif + memcpy_trailing_15bytes backwards, align +199: + pop {D, DAT1, DAT2, pc} +.endm + +.macro memcpy backwards + D .req a1 + S .req a2 + N .req a3 + DAT0 .req a4 + DAT1 .req v1 + DAT2 .req v2 + DAT3 .req v3 + DAT4 .req v4 + DAT5 .req v5 + DAT6 .req v6 + DAT7 .req sl + LAST .req ip + OFF .req lr + + UNWIND( .fnstart ) + + push {D, DAT1, DAT2, lr} + UNWIND( .fnend ) + + UNWIND( .fnstart ) + UNWIND( .save {D, DAT1, DAT2, lr} ) + + .if backwards + add D, D, N + add S, S, N + .endif + + /* See if we're guaranteed to have at least one 16-byte aligned 16-byte write */ + cmp N, #31 + blo 170f + /* To preload ahead as we go, we need at least (prefetch_distance+2) 32-byte blocks */ + cmp N, #(prefetch_distance+3)*32 - 1 + blo 160f + + /* Long case */ + push {DAT3, DAT4, DAT5, DAT6, DAT7} + UNWIND( .fnend ) + + UNWIND( .fnstart ) + UNWIND( .save {D, DAT1, DAT2, lr} ) + UNWIND( .save {DAT3, DAT4, DAT5, DAT6, DAT7} ) + + /* Adjust N so that the decrement instruction can also test for + * inner loop termination. We want it to stop when there are + * (prefetch_distance+1) complete blocks to go. */ + sub N, N, #(prefetch_distance+2)*32 + preload_leading_step1 backwards, DAT0, S + .if backwards + /* Bug in GAS: it accepts, but mis-assembles the instruction + * ands DAT2, D, #60, 2 + * which sets DAT2 to the number of leading bytes until destination is aligned and also clears C (sets borrow) + */ + .word 0xE210513C + beq 154f + .else + ands DAT2, D, #15 + beq 154f + rsb DAT2, DAT2, #16 /* number of leading bytes until destination aligned */ + .endif + preload_leading_step2 backwards, DAT0, S, DAT2, OFF + memcpy_leading_15bytes backwards, 1 +154: /* Destination now 16-byte aligned; we have at least one prefetch as well as at least one 16-byte output block */ + /* Prefetch offset is best selected such that it lies in the first 8 of each 32 bytes - but it's just as easy to aim for the first one */ + .if backwards + rsb OFF, S, #3 + and OFF, OFF, #28 + sub OFF, OFF, #32*(prefetch_distance+1) + .else + and OFF, S, #28 + rsb OFF, OFF, #32*prefetch_distance + .endif + movs DAT0, S, lsl #31 + bhi 157f + bcs 156f + bmi 155f + memcpy_long_inner_loop backwards, 0 +155: memcpy_long_inner_loop backwards, 1 +156: memcpy_long_inner_loop backwards, 2 +157: memcpy_long_inner_loop backwards, 3 + + UNWIND( .fnend ) + + UNWIND( .fnstart ) + UNWIND( .save {D, DAT1, DAT2, lr} ) + +160: /* Medium case */ + preload_all backwards, 0, 0, S, N, DAT2, OFF + sub N, N, #16 /* simplifies inner loop termination */ + .if backwards + ands DAT2, D, #15 + beq 164f + .else + ands DAT2, D, #15 + beq 164f + rsb DAT2, DAT2, #16 + .endif + memcpy_leading_15bytes backwards, align +164: /* Destination now 16-byte aligned; we have at least one 16-byte output block */ + tst S, #3 + bne 140f + memcpy_medium_inner_loop backwards, 0 +140: memcpy_medium_inner_loop backwards, 1 + +170: /* Short case, less than 31 bytes, so no guarantee of at least one 16-byte block */ + teq N, #0 + beq 199f + preload_all backwards, 1, 0, S, N, DAT2, LAST + tst D, #3 + beq 174f +172: subs N, N, #1 + blo 199f + .if backwards + ldrb DAT0, [S, #-1]! + strb DAT0, [D, #-1]! + .else + ldrb DAT0, [S], #1 + strb DAT0, [D], #1 + .endif + tst D, #3 + bne 172b +174: /* Destination now 4-byte aligned; we have 0 or more output bytes to go */ + tst S, #3 + bne 140f + memcpy_short_inner_loop backwards, 0 +140: memcpy_short_inner_loop backwards, 1 + + UNWIND( .fnend ) + + .unreq D + .unreq S + .unreq N + .unreq DAT0 + .unreq DAT1 + .unreq DAT2 + .unreq DAT3 + .unreq DAT4 + .unreq DAT5 + .unreq DAT6 + .unreq DAT7 + .unreq LAST + .unreq OFF +.endm diff --git a/arch/arm/lib/memmove_rpi.S b/arch/arm/lib/memmove_rpi.S new file mode 100644 index 00000000000000..5715dfd958597c --- /dev/null +++ b/arch/arm/lib/memmove_rpi.S @@ -0,0 +1,63 @@ +/* +Copyright (c) 2013, Raspberry Pi Foundation +Copyright (c) 2013, RISC OS Open Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include <linux/linkage.h> +#include <asm/assembler.h> +#include <asm/unwind.h> +#include "arm-mem.h" +#include "memcpymove.h" + +/* Prevent the stack from becoming executable */ +#if defined(__linux__) && defined(__ELF__) +.section .note.GNU-stack,"",%progbits +#endif + + .text + .arch armv6 + .object_arch armv4 + .arm + .altmacro + .p2align 2 + +/* + * void *memmove(void *s1, const void *s2, size_t n); + * On entry: + * a1 = pointer to destination + * a2 = pointer to source + * a3 = number of bytes to copy + * On exit: + * a1 preserved + */ + +.set prefetch_distance, 3 + +ENTRY(memmove) + cmp a2, a1 + bpl memcpy /* pl works even over -1 - 0 and 0x7fffffff - 0x80000000 boundaries */ + memcpy 1 +ENDPROC(memmove) diff --git a/arch/arm/lib/memset_rpi.S b/arch/arm/lib/memset_rpi.S new file mode 100644 index 00000000000000..087d68ea5d1821 --- /dev/null +++ b/arch/arm/lib/memset_rpi.S @@ -0,0 +1,132 @@ +/* +Copyright (c) 2013, Raspberry Pi Foundation +Copyright (c) 2013, RISC OS Open Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include <linux/linkage.h> +#include "arm-mem.h" + +/* Prevent the stack from becoming executable */ +#if defined(__linux__) && defined(__ELF__) +.section .note.GNU-stack,"",%progbits +#endif + + .text + .arch armv6 + .object_arch armv4 + .arm + .altmacro + .p2align 2 + +/* + * void *memset(void *s, int c, size_t n); + * On entry: + * a1 = pointer to buffer to fill + * a2 = byte pattern to fill with (caller-narrowed) + * a3 = number of bytes to fill + * On exit: + * a1 preserved + */ +ENTRY(mmioset) +ENTRY(memset) +ENTRY(__memset) + + S .req a1 + DAT0 .req a2 + N .req a3 + DAT1 .req a4 + DAT2 .req ip + DAT3 .req lr + + orr DAT0, DAT0, DAT0, lsl #8 + orr DAT0, DAT0, DAT0, lsl #16 + +ENTRY(__memset32) + mov DAT1, DAT0 + +ENTRY(__memset64) + push {S, lr} + + /* See if we're guaranteed to have at least one 16-byte aligned 16-byte write */ + cmp N, #31 + blo 170f + +161: sub N, N, #16 /* simplifies inner loop termination */ + /* Leading words and bytes */ + tst S, #15 + beq 164f + rsb DAT3, S, #0 /* bits 0-3 = number of leading bytes until aligned */ + movs DAT2, DAT3, lsl #31 + submi N, N, #1 + strmib DAT0, [S], #1 + subcs N, N, #2 + strcsh DAT0, [S], #2 + movs DAT2, DAT3, lsl #29 + submi N, N, #4 + strmi DAT0, [S], #4 + subcs N, N, #8 + stmcsia S!, {DAT0, DAT1} +164: /* Delayed set up of DAT2 and DAT3 so we could use them as scratch registers above */ + mov DAT2, DAT0 + mov DAT3, DAT1 + /* Now the inner loop of 16-byte stores */ +165: stmia S!, {DAT0, DAT1, DAT2, DAT3} + subs N, N, #16 + bhs 165b +166: /* Trailing words and bytes */ + movs N, N, lsl #29 + stmcsia S!, {DAT0, DAT1} + strmi DAT0, [S], #4 + movs N, N, lsl #2 + strcsh DAT0, [S], #2 + strmib DAT0, [S] +199: pop {S, pc} + +170: /* Short case */ + mov DAT2, DAT0 + mov DAT3, DAT1 + tst S, #3 + beq 174f +172: subs N, N, #1 + blo 199b + strb DAT0, [S], #1 + tst S, #3 + bne 172b +174: tst N, #16 + stmneia S!, {DAT0, DAT1, DAT2, DAT3} + b 166b + + .unreq S + .unreq DAT0 + .unreq N + .unreq DAT1 + .unreq DAT2 + .unreq DAT3 +ENDPROC(__memset64) +ENDPROC(__memset32) +ENDPROC(__memset) +ENDPROC(memset) +ENDPROC(mmioset) diff --git a/arch/arm/lib/uaccess_with_memcpy.c b/arch/arm/lib/uaccess_with_memcpy.c index c0ac7796d77508..c416c7d3242fe5 100644 --- a/arch/arm/lib/uaccess_with_memcpy.c +++ b/arch/arm/lib/uaccess_with_memcpy.c @@ -19,6 +19,14 @@ #include <asm/current.h> #include <asm/page.h> +#ifndef COPY_FROM_USER_THRESHOLD +#define COPY_FROM_USER_THRESHOLD 64 +#endif + +#ifndef COPY_TO_USER_THRESHOLD +#define COPY_TO_USER_THRESHOLD 64 +#endif + static int pin_page_for_write(const void __user *_addr, pte_t **ptep, spinlock_t **ptlp) { @@ -43,7 +51,7 @@ pin_page_for_write(const void __user *_addr, pte_t **ptep, spinlock_t **ptlp) return 0; pmd = pmd_offset(pud, addr); - if (unlikely(pmd_none(*pmd))) + if (unlikely(pmd_none(*pmd) || pmd_bad(*pmd))) return 0; /* @@ -89,6 +97,47 @@ pin_page_for_write(const void __user *_addr, pte_t **ptep, spinlock_t **ptlp) return 1; } +#ifdef CONFIG_BCM2835_FAST_MEMCPY +static int +pin_page_for_read(const void __user *_addr, pte_t **ptep, spinlock_t **ptlp) +{ + unsigned long addr = (unsigned long)_addr; + pgd_t *pgd; + p4d_t *p4d; + pmd_t *pmd; + pte_t *pte; + pud_t *pud; + spinlock_t *ptl; + + pgd = pgd_offset(current->mm, addr); + if (unlikely(pgd_none(*pgd) || pgd_bad(*pgd))) + return 0; + + p4d = p4d_offset(pgd, addr); + if (unlikely(p4d_none(*p4d) || p4d_bad(*p4d))) + return 0; + + pud = pud_offset(p4d, addr); + if (unlikely(pud_none(*pud) || pud_bad(*pud))) + return 0; + + pmd = pmd_offset(pud, addr); + if (unlikely(pmd_none(*pmd) || pmd_bad(*pmd))) + return 0; + + pte = pte_offset_map_lock(current->mm, pmd, addr, &ptl); + if (unlikely(!pte_present(*pte) || !pte_young(*pte))) { + pte_unmap_unlock(pte, ptl); + return 0; + } + + *ptep = pte; + *ptlp = ptl; + + return 1; +} +#endif + static unsigned long noinline __copy_to_user_memcpy(void __user *to, const void *from, unsigned long n) { @@ -137,6 +186,54 @@ __copy_to_user_memcpy(void __user *to, const void *from, unsigned long n) return n; } +#ifdef CONFIG_BCM2835_FAST_MEMCPY +static unsigned long noinline +__copy_from_user_memcpy(void *to, const void __user *from, unsigned long n) +{ + unsigned long ua_flags; + int atomic; + + /* the mmap semaphore is taken only if not in an atomic context */ + atomic = in_atomic(); + + if (!atomic) + mmap_read_lock(current->mm); + while (n) { + pte_t *pte; + spinlock_t *ptl; + int tocopy; + + while (!pin_page_for_read(from, &pte, &ptl)) { + char temp; + if (!atomic) + mmap_read_unlock(current->mm); + if (__get_user(temp, (char __user *)from)) + goto out; + if (!atomic) + mmap_read_lock(current->mm); + } + + tocopy = (~(unsigned long)from & ~PAGE_MASK) + 1; + if (tocopy > n) + tocopy = n; + + ua_flags = uaccess_save_and_enable(); + memcpy(to, (const void *)from, tocopy); + uaccess_restore(ua_flags); + to += tocopy; + from += tocopy; + n -= tocopy; + + pte_unmap_unlock(pte, ptl); + } + if (!atomic) + mmap_read_unlock(current->mm); + +out: + return n; +} +#endif + unsigned long arm_copy_to_user(void __user *to, const void *from, unsigned long n) { @@ -147,7 +244,7 @@ arm_copy_to_user(void __user *to, const void *from, unsigned long n) * With frame pointer disabled, tail call optimization kicks in * as well making this test almost invisible. */ - if (n < 64) { + if (n < COPY_TO_USER_THRESHOLD) { unsigned long ua_flags = uaccess_save_and_enable(); n = __copy_to_user_std(to, from, n); uaccess_restore(ua_flags); @@ -157,6 +254,32 @@ arm_copy_to_user(void __user *to, const void *from, unsigned long n) } return n; } + +unsigned long __must_check +arm_copy_from_user(void *to, const void __user *from, unsigned long n) +{ +#ifdef CONFIG_BCM2835_FAST_MEMCPY + /* + * This test is stubbed out of the main function above to keep + * the overhead for small copies low by avoiding a large + * register dump on the stack just to reload them right away. + * With frame pointer disabled, tail call optimization kicks in + * as well making this test almost invisible. + */ + if (n < COPY_TO_USER_THRESHOLD) { + unsigned long ua_flags = uaccess_save_and_enable(); + n = __copy_from_user_std(to, from, n); + uaccess_restore(ua_flags); + } else { + n = __copy_from_user_memcpy(to, from, n); + } +#else + unsigned long ua_flags = uaccess_save_and_enable(); + n = __copy_from_user_std(to, from, n); + uaccess_restore(ua_flags); +#endif + return n; +} static unsigned long noinline __clear_user_memset(void __user *addr, unsigned long n) diff --git a/arch/arm/mach-bcm/Kconfig b/arch/arm/mach-bcm/Kconfig index 24bc6e18d80674..203be91af9eba8 100644 --- a/arch/arm/mach-bcm/Kconfig +++ b/arch/arm/mach-bcm/Kconfig @@ -158,9 +158,11 @@ config ARCH_BCM2835 select ARM_TIMER_SP804 select HAVE_ARM_ARCH_TIMER if ARCH_MULTI_V7 select BCM2835_TIMER + select FIQ select PINCTRL select PINCTRL_BCM2835 select MFD_CORE + select MFD_SYSCON if ARCH_MULTI_V7 help This enables support for the Broadcom BCM2711 and BCM283x SoCs. This SoC is used in the Raspberry Pi and Roku 2 devices. @@ -179,6 +181,13 @@ config ARCH_BCM_53573 The base chip is BCM53573 and there are some packaging modifications like BCM47189 and BCM47452. +config BCM2835_FAST_MEMCPY + bool "Enable optimized __copy_to_user and __copy_from_user" + depends on ARCH_BCM2835 && ARCH_MULTI_V6 + default y + help + Optimized versions of __copy_to_user and __copy_from_user for Pi1. + config ARCH_BRCMSTB bool "Broadcom BCM7XXX based boards" depends on ARCH_MULTI_V7 diff --git a/arch/arm/mach-bcm/board_bcm2835.c b/arch/arm/mach-bcm/board_bcm2835.c index bfc556f7672039..869f22ef562a85 100644 --- a/arch/arm/mach-bcm/board_bcm2835.c +++ b/arch/arm/mach-bcm/board_bcm2835.c @@ -5,13 +5,103 @@ #include <linux/init.h> #include <linux/irqchip.h> +#include <linux/mm.h> #include <linux/of_address.h> +#include <linux/of_fdt.h> +#include <asm/system_info.h> #include <asm/mach/arch.h> #include <asm/mach/map.h> +#include <asm/memory.h> +#include <asm/pgtable.h> #include "platsmp.h" +#define BCM2835_USB_VIRT_BASE (VMALLOC_START) +#define BCM2835_USB_VIRT_MPHI (VMALLOC_START + 0x10000) + +static void __init bcm2835_init(void) +{ + struct device_node *np = of_find_node_by_path("/system"); + u32 val; + u64 val64; + + if (!of_property_read_u32(np, "linux,revision", &val)) + system_rev = val; + if (!of_property_read_u64(np, "linux,serial", &val64)) + system_serial_low = val64; +} + +/* + * We need to map registers that are going to be accessed by the FIQ + * very early, before any kernel threads are spawned. Because if done + * later, the mapping tables are not updated instantly but lazily upon + * first access through a data abort handler. While that is fine + * when executing regular kernel code, if the first access in a specific + * thread happens while running FIQ code this will result in a panic. + * + * For more background see the following old mailing list thread: + * https://www.spinics.net/lists/arm-kernel/msg325250.html + */ +static int __init bcm2835_map_usb(unsigned long node, const char *uname, + int depth, void *data) +{ + struct map_desc map[2]; + const __be32 *reg; + int len; + unsigned long p2b_offset = *((unsigned long *) data); + + if (!of_flat_dt_is_compatible(node, "brcm,bcm2708-usb")) + return 0; + reg = of_get_flat_dt_prop(node, "reg", &len); + if (!reg || len != (sizeof(unsigned long) * 4)) + return 0; + + /* Use information about the physical addresses of the + * registers from the device tree, but use legacy + * iotable_init() static mapping function to map them, + * as ioremap() is not functional at this stage in boot. + */ + map[0].virtual = (unsigned long) BCM2835_USB_VIRT_BASE; + map[0].pfn = __phys_to_pfn(be32_to_cpu(reg[0]) - p2b_offset); + map[0].length = be32_to_cpu(reg[1]); + map[0].type = MT_DEVICE; + map[1].virtual = (unsigned long) BCM2835_USB_VIRT_MPHI; + map[1].pfn = __phys_to_pfn(be32_to_cpu(reg[2]) - p2b_offset); + map[1].length = be32_to_cpu(reg[3]); + map[1].type = MT_DEVICE; + iotable_init(map, 2); + + return 1; +} + +static void __init bcm2835_map_io(void) +{ + const __be32 *ranges, *address_cells; + unsigned long root, addr_cells; + int soc, len; + unsigned long p2b_offset; + + debug_ll_io_init(); + + root = of_get_flat_dt_root(); + /* Find out how to map bus to physical address first from soc/ranges */ + soc = of_get_flat_dt_subnode_by_name(root, "soc"); + if (soc < 0) + return; + address_cells = of_get_flat_dt_prop(root, "#address-cells", &len); + if (!address_cells || len < (sizeof(unsigned long))) + return; + addr_cells = be32_to_cpu(address_cells[0]); + ranges = of_get_flat_dt_prop(soc, "ranges", &len); + if (!ranges || len < (sizeof(unsigned long) * (2 + addr_cells))) + return; + p2b_offset = be32_to_cpu(ranges[0]) - be32_to_cpu(ranges[addr_cells]); + + /* Now search for bcm2708-usb node in device tree */ + of_scan_flat_dt(bcm2835_map_usb, &p2b_offset); +} + static const char * const bcm2835_compat[] = { #ifdef CONFIG_ARCH_MULTI_V6 "brcm,bcm2835", @@ -19,11 +109,31 @@ static const char * const bcm2835_compat[] = { #ifdef CONFIG_ARCH_MULTI_V7 "brcm,bcm2836", "brcm,bcm2837", + "brcm,bcm2711", #endif NULL }; DT_MACHINE_START(BCM2835, "BCM2835") + .map_io = bcm2835_map_io, + .init_machine = bcm2835_init, .dt_compat = bcm2835_compat, .smp = smp_ops(bcm2836_smp_ops), MACHINE_END + +static const char * const bcm2711_compat[] = { +#ifdef CONFIG_ARCH_MULTI_V7 + "brcm,bcm2711", +#endif + NULL +}; + +DT_MACHINE_START(BCM2711, "BCM2711") +#if defined(CONFIG_ZONE_DMA) && defined(CONFIG_ARM_LPAE) + .dma_zone_size = SZ_1G, +#endif + .map_io = bcm2835_map_io, + .init_machine = bcm2835_init, + .dt_compat = bcm2711_compat, + .smp = smp_ops(bcm2836_smp_ops), +MACHINE_END diff --git a/arch/arm/mm/cache-v6.S b/arch/arm/mm/cache-v6.S index 9f415476e2183d..68f211eef60f10 100644 --- a/arch/arm/mm/cache-v6.S +++ b/arch/arm/mm/cache-v6.S @@ -206,7 +206,7 @@ SYM_FUNC_END(v6_flush_kern_dcache_area) * - start - virtual start address of region * - end - virtual end address of region */ -v6_dma_inv_range: +ENTRY(v6_dma_inv_range) tst r0, #D_CACHE_LINE_SIZE - 1 bic r0, r0, #D_CACHE_LINE_SIZE - 1 #ifdef HARVARD_CACHE @@ -239,7 +239,7 @@ v6_dma_inv_range: * - start - virtual start address of region * - end - virtual end address of region */ -v6_dma_clean_range: +ENTRY(v6_dma_clean_range) bic r0, r0, #D_CACHE_LINE_SIZE - 1 1: #ifdef HARVARD_CACHE diff --git a/arch/arm/mm/cache-v7.S b/arch/arm/mm/cache-v7.S index 201ca05436fad5..3c52c327d293c2 100644 --- a/arch/arm/mm/cache-v7.S +++ b/arch/arm/mm/cache-v7.S @@ -364,7 +364,8 @@ SYM_FUNC_END(v7_flush_kern_dcache_area) * - start - virtual start address of region * - end - virtual end address of region */ -v7_dma_inv_range: +ENTRY(b15_dma_inv_range) +ENTRY(v7_dma_inv_range) dcache_line_size r2, r3 sub r3, r2, #1 tst r0, r3 @@ -394,7 +395,8 @@ ENDPROC(v7_dma_inv_range) * - start - virtual start address of region * - end - virtual end address of region */ -v7_dma_clean_range: +ENTRY(b15_dma_clean_range) +ENTRY(v7_dma_clean_range) dcache_line_size r2, r3 sub r3, r2, #1 bic r0, r0, r3 diff --git a/arch/arm/mm/proc-v6.S b/arch/arm/mm/proc-v6.S index 90a01f5950b98a..3ff019fb1f43cd 100644 --- a/arch/arm/mm/proc-v6.S +++ b/arch/arm/mm/proc-v6.S @@ -75,10 +75,19 @@ SYM_FUNC_END(cpu_v6_reset) * * IRQs are already disabled. */ + +/* See jira SW-5991 for details of this workaround */ SYM_TYPED_FUNC_START(cpu_v6_do_idle) - mov r1, #0 - mcr p15, 0, r1, c7, c10, 4 @ DWB - WFI may enter a low-power mode - mcr p15, 0, r1, c7, c0, 4 @ wait for interrupt + .align 5 + mov r1, #2 +1: subs r1, #1 + nop + mcreq p15, 0, r1, c7, c10, 4 @ DWB - WFI may enter a low-power mode + mcreq p15, 0, r1, c7, c0, 4 @ wait for interrupt + nop + nop + nop + bne 1b ret lr SYM_FUNC_END(cpu_v6_do_idle) diff --git a/arch/arm/vfp/vfpmodule.c b/arch/arm/vfp/vfpmodule.c index b68efe643a12ca..030359368455d1 100644 --- a/arch/arm/vfp/vfpmodule.c +++ b/arch/arm/vfp/vfpmodule.c @@ -176,8 +176,11 @@ static int vfp_notifier(struct notifier_block *self, unsigned long cmd, void *v) * case the thread migrates to a different CPU. The * restoring is done lazily. */ - if ((fpexc & FPEXC_EN) && vfp_current_hw_state[cpu]) + if ((fpexc & FPEXC_EN) && vfp_current_hw_state[cpu]) { + /* vfp_save_state oopses on VFP11 if EX bit set */ + fmxr(FPEXC, fpexc & ~FPEXC_EX); vfp_save_state(vfp_current_hw_state[cpu], fpexc); + } #endif /* @@ -451,13 +454,16 @@ static int vfp_pm_suspend(void) /* if vfp is on, then save state for resumption */ if (fpexc & FPEXC_EN) { pr_debug("%s: saving vfp state\n", __func__); + /* vfp_save_state oopses on VFP11 if EX bit set */ + fmxr(FPEXC, fpexc & ~FPEXC_EX); vfp_save_state(&ti->vfpstate, fpexc); /* disable, just in case */ fmxr(FPEXC, fmrx(FPEXC) & ~FPEXC_EN); } else if (vfp_current_hw_state[ti->cpu]) { #ifndef CONFIG_SMP - fmxr(FPEXC, fpexc | FPEXC_EN); + /* vfp_save_state oopses on VFP11 if EX bit set */ + fmxr(FPEXC, (fpexc & ~FPEXC_EX) | FPEXC_EN); vfp_save_state(vfp_current_hw_state[ti->cpu], fpexc); fmxr(FPEXC, fpexc); #endif @@ -522,7 +528,8 @@ void vfp_sync_hwstate(struct thread_info *thread) /* * Save the last VFP state on this CPU. */ - fmxr(FPEXC, fpexc | FPEXC_EN); + /* vfp_save_state oopses on VFP11 if EX bit set */ + fmxr(FPEXC, (fpexc & ~FPEXC_EX) | FPEXC_EN); vfp_save_state(&thread->vfpstate, fpexc | FPEXC_EN); fmxr(FPEXC, fpexc); } @@ -589,6 +596,7 @@ int vfp_restore_user_hwstate(struct user_vfp *ufp, struct user_vfp_exc *ufp_exc) struct thread_info *thread = current_thread_info(); struct vfp_hard_struct *hwstate = &thread->vfpstate.hard; unsigned long fpexc; + u32 fpsid = fmrx(FPSID); /* Disable VFP to avoid corrupting the new thread state. */ vfp_flush_hwstate(thread); @@ -611,8 +619,12 @@ int vfp_restore_user_hwstate(struct user_vfp *ufp, struct user_vfp_exc *ufp_exc) /* Ensure the VFP is enabled. */ fpexc |= FPEXC_EN; - /* Ensure FPINST2 is invalid and the exception flag is cleared. */ - fpexc &= ~(FPEXC_EX | FPEXC_FP2V); + /* Mask FPXEC_EX and FPEXC_FP2V if not required by VFP arch */ + if ((fpsid & FPSID_ARCH_MASK) != (1 << FPSID_ARCH_BIT)) { + /* Ensure FPINST2 is invalid and the exception flag is cleared. */ + fpexc &= ~(FPEXC_EX | FPEXC_FP2V); + } + hwstate->fpexc = fpexc; hwstate->fpinst = ufp_exc->fpinst; @@ -848,7 +860,8 @@ void kernel_neon_begin(void) cpu = __smp_processor_id(); fpexc = fmrx(FPEXC) | FPEXC_EN; - fmxr(FPEXC, fpexc); + /* vfp_save_state oopses on VFP11 if EX bit set */ + fmxr(FPEXC, fpexc & ~FPEXC_EX); /* * Save the userland NEON/VFP state. Under UP, diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index a11a7a42edbfb5..942c02943bce43 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -130,7 +130,8 @@ config ARM64 select CRC32 select DCACHE_WORD_ACCESS select DYNAMIC_FTRACE if FUNCTION_TRACER - select DMA_BOUNCE_UNALIGNED_KMALLOC + # Disable this to save 64MB when DMA controllers can reach all of RAM + # select DMA_BOUNCE_UNALIGNED_KMALLOC select DMA_DIRECT_REMAP select EDAC_SUPPORT select FRAME_POINTER diff --git a/arch/arm64/boot/dts/Makefile b/arch/arm64/boot/dts/Makefile index 21cd3a87f38530..4b79ed838b1266 100644 --- a/arch/arm64/boot/dts/Makefile +++ b/arch/arm64/boot/dts/Makefile @@ -34,3 +34,5 @@ subdir-y += tesla subdir-y += ti subdir-y += toshiba subdir-y += xilinx + +subdir-y += overlays diff --git a/arch/arm64/boot/dts/broadcom/Makefile b/arch/arm64/boot/dts/broadcom/Makefile index 92565e9781ad3c..0d43290040d9a2 100644 --- a/arch/arm64/boot/dts/broadcom/Makefile +++ b/arch/arm64/boot/dts/broadcom/Makefile @@ -7,12 +7,34 @@ dtb-$(CONFIG_ARCH_BCM2835) += bcm2711-rpi-400.dtb \ bcm2711-rpi-4-b.dtb \ bcm2711-rpi-cm4-io.dtb \ bcm2712-rpi-5-b.dtb \ + bcm2712-d-rpi-5-b.dtb \ bcm2837-rpi-3-a-plus.dtb \ bcm2837-rpi-3-b.dtb \ bcm2837-rpi-3-b-plus.dtb \ bcm2837-rpi-cm3-io3.dtb \ bcm2837-rpi-zero-2-w.dtb +dtb-$(CONFIG_ARCH_BCM2835) += bcm2710-rpi-zero-2.dtb +dtb-$(CONFIG_ARCH_BCM2835) += bcm2710-rpi-zero-2-w.dtb +dtb-$(CONFIG_ARCH_BCM2835) += bcm2710-rpi-2-b.dtb +dtb-$(CONFIG_ARCH_BCM2835) += bcm2710-rpi-3-b.dtb +dtb-$(CONFIG_ARCH_BCM2835) += bcm2710-rpi-3-b-plus.dtb +dtb-$(CONFIG_ARCH_BCM2835) += bcm2710-rpi-cm3.dtb +dtb-$(CONFIG_ARCH_BCM2835) += bcm2711-rpi-cm4.dtb +dtb-$(CONFIG_ARCH_BCM2835) += bcm2711-rpi-cm4s.dtb +dtb-$(CONFIG_ARCH_BCM2835) += bcm2712-rpi-5-b.dtb +dtb-$(CONFIG_ARCH_BCM2835) += bcm2712d0-rpi-5-b.dtb +dtb-$(CONFIG_ARCH_BCM2835) += bcm2712-rpi-500.dtb +dtb-$(CONFIG_ARCH_BCM2835) += bcm2712-rpi-cm5-cm5io.dtb +dtb-$(CONFIG_ARCH_BCM2835) += bcm2712-rpi-cm5-cm4io.dtb +dtb-$(CONFIG_ARCH_BCM2835) += bcm2712-rpi-cm5l-cm5io.dtb +dtb-$(CONFIG_ARCH_BCM2835) += bcm2712-rpi-cm5l-cm4io.dtb + subdir-y += bcmbca subdir-y += northstar2 subdir-y += stingray + +# Enable fixups to support overlays on BCM2835 platforms +ifeq ($(CONFIG_ARCH_BCM2835),y) + DTC_FLAGS += -@ +endif diff --git a/arch/arm64/boot/dts/broadcom/bcm2710-rpi-2-b.dts b/arch/arm64/boot/dts/broadcom/bcm2710-rpi-2-b.dts new file mode 100644 index 00000000000000..9b2c0120842a70 --- /dev/null +++ b/arch/arm64/boot/dts/broadcom/bcm2710-rpi-2-b.dts @@ -0,0 +1 @@ +#include "arm/broadcom/bcm2710-rpi-2-b.dts" diff --git a/arch/arm64/boot/dts/broadcom/bcm2710-rpi-3-b-plus.dts b/arch/arm64/boot/dts/broadcom/bcm2710-rpi-3-b-plus.dts new file mode 100644 index 00000000000000..bc869aeaee9b71 --- /dev/null +++ b/arch/arm64/boot/dts/broadcom/bcm2710-rpi-3-b-plus.dts @@ -0,0 +1 @@ +#include "arm/broadcom/bcm2710-rpi-3-b-plus.dts" diff --git a/arch/arm64/boot/dts/broadcom/bcm2710-rpi-3-b.dts b/arch/arm64/boot/dts/broadcom/bcm2710-rpi-3-b.dts new file mode 100644 index 00000000000000..263fc8db863a78 --- /dev/null +++ b/arch/arm64/boot/dts/broadcom/bcm2710-rpi-3-b.dts @@ -0,0 +1 @@ +#include "arm/broadcom/bcm2710-rpi-3-b.dts" diff --git a/arch/arm64/boot/dts/broadcom/bcm2710-rpi-cm3.dts b/arch/arm64/boot/dts/broadcom/bcm2710-rpi-cm3.dts new file mode 100644 index 00000000000000..6beee41b007700 --- /dev/null +++ b/arch/arm64/boot/dts/broadcom/bcm2710-rpi-cm3.dts @@ -0,0 +1 @@ +#include "arm/broadcom/bcm2710-rpi-cm3.dts" diff --git a/arch/arm64/boot/dts/broadcom/bcm2710-rpi-zero-2-w.dts b/arch/arm64/boot/dts/broadcom/bcm2710-rpi-zero-2-w.dts new file mode 100644 index 00000000000000..65fa59a939b719 --- /dev/null +++ b/arch/arm64/boot/dts/broadcom/bcm2710-rpi-zero-2-w.dts @@ -0,0 +1 @@ +#include "arm/broadcom/bcm2710-rpi-zero-2-w.dts" diff --git a/arch/arm64/boot/dts/broadcom/bcm2710-rpi-zero-2.dts b/arch/arm64/boot/dts/broadcom/bcm2710-rpi-zero-2.dts new file mode 100644 index 00000000000000..65fa59a939b719 --- /dev/null +++ b/arch/arm64/boot/dts/broadcom/bcm2710-rpi-zero-2.dts @@ -0,0 +1 @@ +#include "arm/broadcom/bcm2710-rpi-zero-2-w.dts" diff --git a/arch/arm64/boot/dts/broadcom/bcm2711-rpi-cm4.dts b/arch/arm64/boot/dts/broadcom/bcm2711-rpi-cm4.dts new file mode 100644 index 00000000000000..3e25a0e1797f6f --- /dev/null +++ b/arch/arm64/boot/dts/broadcom/bcm2711-rpi-cm4.dts @@ -0,0 +1 @@ +#include "arm/broadcom/bcm2711-rpi-cm4.dts" diff --git a/arch/arm64/boot/dts/broadcom/bcm2711-rpi-cm4s.dts b/arch/arm64/boot/dts/broadcom/bcm2711-rpi-cm4s.dts new file mode 100644 index 00000000000000..c72d752e74006a --- /dev/null +++ b/arch/arm64/boot/dts/broadcom/bcm2711-rpi-cm4s.dts @@ -0,0 +1 @@ +#include "arm/broadcom/bcm2711-rpi-cm4s.dts" diff --git a/arch/arm64/boot/dts/broadcom/bcm2712-d-rpi-5-b.dts b/arch/arm64/boot/dts/broadcom/bcm2712-d-rpi-5-b.dts new file mode 100644 index 00000000000000..7de24d60bcd1a7 --- /dev/null +++ b/arch/arm64/boot/dts/broadcom/bcm2712-d-rpi-5-b.dts @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: (GPL-2.0 OR MIT) +/dts-v1/; + +#include "bcm2712-rpi-5-b.dts" + +&gio_aon { + brcm,gpio-bank-widths = <15 6>; + + gpio-line-names = + "RP1_SDA", // AON_GPIO_00 + "RP1_SCL", // AON_GPIO_01 + "RP1_RUN", // AON_GPIO_02 + "SD_IOVDD_SEL", // AON_GPIO_03 + "SD_PWR_ON", // AON_GPIO_04 + "SD_CDET_N", // AON_GPIO_05 + "SD_FLG_N", // AON_GPIO_06 + "", // AON_GPIO_07 + "2712_WAKE", // AON_GPIO_08 + "2712_STAT_LED", // AON_GPIO_09 + "", // AON_GPIO_10 + "", // AON_GPIO_11 + "PMIC_INT", // AON_GPIO_12 + "UART_TX_FS", // AON_GPIO_13 + "UART_RX_FS", // AON_GPIO_14 + "", // AON_GPIO_15 + "", // AON_GPIO_16 + + // Pad bank0 out to 32 entries + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + + "HDMI0_SCL", // AON_SGPIO_00 + "HDMI0_SDA", // AON_SGPIO_01 + "HDMI1_SCL", // AON_SGPIO_02 + "HDMI1_SDA", // AON_SGPIO_03 + "PMIC_SCL", // AON_SGPIO_04 + "PMIC_SDA"; // AON_SGPIO_05 +}; diff --git a/arch/arm64/boot/dts/broadcom/bcm2712-ds.dtsi b/arch/arm64/boot/dts/broadcom/bcm2712-ds.dtsi new file mode 100644 index 00000000000000..e797fa76ad6ea8 --- /dev/null +++ b/arch/arm64/boot/dts/broadcom/bcm2712-ds.dtsi @@ -0,0 +1,676 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <dt-bindings/soc/bcm2835-pm.h> + +#include "bcm2712.dtsi" + +/ { +#ifndef FIRMWARE_UPDATED + #size-cells = <1>; + + reserved-memory { + ranges; + #address-cells = <2>; + #size-cells = <1>; + + atf@0 { + reg = <0x0 0x0 0x80000>; + }; + + linux,cma { + size = <0x4000000>; /* 64MB */ + alloc-ranges = <0x0 0x00000000 0x40000000>; + }; + }; +#endif + + arm-pmu { + compatible = "arm,cortex-a76-pmu"; + interrupts = <GIC_SPI 16 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 17 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 18 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 19 IRQ_TYPE_LEVEL_HIGH>; + interrupt-affinity = <&cpu0>, <&cpu1>, <&cpu2>, <&cpu3>; + }; + + clocks: clocks { + compatible = "simple-bus"; + #address-cells = <1>; + #size-cells = <0>; + + clk_usb: clk-usb { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-output-names = "otg"; + clock-frequency = <480000000>; + }; + }; + + thermal-zones { + cpu_thermal: cpu-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + coefficients = <(-550) 450000>; + thermal-sensors = <&thermal>; + + thermal_trips: trips { + cpu_crit: cpu-crit { + temperature = <110000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling_maps: cooling-maps { + }; + }; + }; + + firmwarekms: firmwarekms { + compatible = "raspberrypi,rpi-firmware-kms-2712"; + interrupt-parent = <&cpu_l2_irq>; + interrupts = <19>; + brcm,firmware = <&firmware>; + status = "disabled"; + }; + + usbphy: phy { + compatible = "usb-nop-xceiv"; + #phy-cells = <0>; + }; +}; + +&soc { + system_timer: timer@7c003000 { + compatible = "brcm,bcm2835-system-timer"; + reg = <0x7c003000 0x1000>; + interrupts = <GIC_SPI 64 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 65 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>; + clock-frequency = <1000000>; + }; + + axiperf: axiperf@7c012800 { + compatible = "brcm,bcm2712-axiperf"; + reg = <0x7c012800 0x100>, + <0x7e000000 0x100>; + firmware = <&firmware>; + status = "disabled"; + }; + + spi10: spi@7d004000 { + compatible = "brcm,bcm2835-spi"; + reg = <0x7d004000 0x200>; + interrupts = <GIC_SPI 118 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clk_vpu>; + num-cs = <1>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + + i2c10: i2c@7d005600 { + compatible = "brcm,bcm2711-i2c", "brcm,bcm2835-i2c"; + reg = <0x7d005600 0x20>; + interrupts = <GIC_SPI 117 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clk_vpu>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + + pm: watchdog@7d200000 { + compatible = "brcm,bcm2712-pm"; + reg = <0x7d200000 0x308>; + reg-names = "pm"; + #power-domain-cells = <1>; + #reset-cells = <1>; + system-power-controller; + }; + + random: rng@7d208000 { + compatible = "brcm,bcm2711-rng200"; + reg = <0x7d208000 0x28>; + status = "okay"; + }; + + cpu_l2_irq: intc@7d503000 { + compatible = "brcm,l2-intc"; + reg = <0x7d503000 0x18>; + interrupts = <GIC_SPI 238 IRQ_TYPE_LEVEL_HIGH>; + interrupt-controller; + #interrupt-cells = <1>; + }; + + pinctrl: pinctrl@7d504100 { + compatible = "brcm,bcm2712-pinctrl"; + reg = <0x7d504100 0x30>; + + uarta_24_pins: uarta_24_pins { + pin_rts { + function = "uart0"; + pins = "gpio24"; + bias-disable; + }; + pin_cts { + function = "uart0"; + pins = "gpio25"; + bias-pull-up; + }; + pin_txd { + function = "uart0"; + pins = "gpio26"; + bias-disable; + }; + pin_rxd { + function = "uart0"; + pins = "gpio27"; + bias-pull-up; + }; + }; + + sdio2_30_pins: sdio2_30_pins { + pin_clk { + function = "sd2"; + pins = "gpio30"; + bias-disable; + }; + pin_cmd { + function = "sd2"; + pins = "gpio31"; + bias-pull-up; + }; + pins_dat { + function = "sd2"; + pins = "gpio32", "gpio33", "gpio34", "gpio35"; + bias-pull-up; + }; + }; + }; + + gio: gpio@7d508500 { + compatible = "brcm,brcmstb-gpio"; + reg = <0x7d508500 0x40>; + interrupt-parent = <&main_irq>; + interrupts = <0>; + gpio-controller; + #gpio-cells = <2>; + interrupt-controller; + #interrupt-cells = <2>; + brcm,gpio-bank-widths = <32 22>; + brcm,gpio-direct; + }; + + uarta: serial@7d50c000 { + compatible = "brcm,bcm7271-uart"; + reg = <0x7d50c000 0x20>; + reg-names = "uart"; + reg-shift = <2>; + reg-io-width = <4>; + interrupts = <GIC_SPI 276 IRQ_TYPE_LEVEL_HIGH>; + skip-init; + status = "disabled"; + }; + + pinctrl_aon: pinctrl@7d510700 { + compatible = "brcm,bcm2712-aon-pinctrl"; + reg = <0x7d510700 0x20>; + + i2c3_m4_agpio0_pins: i2c3_m4_agpio0_pins { + function = "vc_i2c3"; + pins = "aon_gpio0", "aon_gpio1"; + bias-pull-up; + }; + + bsc_m1_agpio13_pins: bsc_m1_agpio13_pins { + function = "bsc_m1"; + pins = "aon_gpio13", "aon_gpio14"; + bias-pull-up; + }; + + bsc_pmu_sgpio4_pins: bsc_pmu_sgpio4_pins { + function = "avs_pmu_bsc"; + pins = "aon_sgpio4", "aon_sgpio5"; + }; + + bsc_m2_sgpio4_pins: bsc_m2_sgpio4_pins { + function = "bsc_m2"; + pins = "aon_sgpio4", "aon_sgpio5"; + }; + + pwm_aon_agpio1_pins: pwm_aon_agpio1_pins { + function = "aon_pwm"; + pins = "aon_gpio1", "aon_gpio2"; + }; + + pwm_aon_agpio4_pins: pwm_aon_agpio4_pins { + function = "vc_pwm0"; + pins = "aon_gpio4", "aon_gpio5"; + }; + + pwm_aon_agpio7_pins: pwm_aon_agpio7_pins { + function = "aon_pwm"; + pins = "aon_gpio7", "aon_gpio9"; + }; + }; + + interrupt-controller@7d517000 { + status = "disabled"; + }; + + main_aon_irq: intc@7d517ac0 { + compatible = "brcm,bcm7271-l2-intc"; + reg = <0x7d517ac0 0x10>; + interrupts = <GIC_SPI 245 IRQ_TYPE_LEVEL_HIGH>; + interrupt-controller; + #interrupt-cells = <1>; + }; + + avs_monitor: avs-monitor@7d542000 { + compatible = "brcm,bcm2711-avs-monitor", + "syscon", "simple-mfd"; + reg = <0x7d542000 0xf00>; + status = "okay"; + + thermal: thermal { + compatible = "brcm,bcm2711-thermal"; + #thermal-sensor-cells = <0>; + }; + }; +}; + +&axi { + iommu2: iommu@5100 { + /* IOMMU2 for PISP-BE, HEVC; and (unused) H264 accelerators */ + compatible = "brcm,bcm2712-iommu"; + reg = <0x10 0x5100 0x0 0x80>; + cache = <&iommuc>; + #iommu-cells = <0>; + }; + + iommu4: iommu@5200 { + /* IOMMU4 for HVS, MPL/TXP; and (unused) Unicam, PISP-FE, MiniBVN */ + compatible = "brcm,bcm2712-iommu"; + reg = <0x10 0x5200 0x0 0x80>; + cache = <&iommuc>; + #iommu-cells = <0>; + #interconnect-cells = <0>; + }; + + iommu5: iommu@5280 { + /* IOMMU5 for PCIe2 (RP1); and (unused) BSTM */ + compatible = "brcm,bcm2712-iommu"; + reg = <0x10 0x5280 0x0 0x80>; + cache = <&iommuc>; + #iommu-cells = <0>; + dma-iova-offset = <0x10 0x00000000>; // HACK for RP1 masters over PCIe + }; + + iommuc: iommuc@5b00 { + compatible = "brcm,bcm2712-iommuc"; + reg = <0x10 0x5b00 0x0 0x80>; + }; + + dma32: dma@10000 { + compatible = "brcm,bcm2712-dma"; + reg = <0x10 0x00010000 0 0x600>; + interrupts = <GIC_SPI 80 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 81 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 82 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 83 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 85 IRQ_TYPE_LEVEL_HIGH>; + interrupt-names = "dma0", + "dma1", + "dma2", + "dma3", + "dma4", + "dma5"; + #dma-cells = <1>; + brcm,dma-channel-mask = <0x0035>; + }; + + dma40: dma@10600 { + compatible = "brcm,bcm2712-dma"; + reg = <0x10 0x00010600 0 0x600>; + interrupts = + <GIC_SPI 86 IRQ_TYPE_LEVEL_HIGH>, /* dma4 6 */ + <GIC_SPI 87 IRQ_TYPE_LEVEL_HIGH>, /* dma4 7 */ + <GIC_SPI 88 IRQ_TYPE_LEVEL_HIGH>, /* dma4 8 */ + <GIC_SPI 89 IRQ_TYPE_LEVEL_HIGH>, /* dma4 9 */ + <GIC_SPI 90 IRQ_TYPE_LEVEL_HIGH>, /* dma4 10 */ + <GIC_SPI 91 IRQ_TYPE_LEVEL_HIGH>; /* dma4 11 */ + interrupt-names = "dma6", + "dma7", + "dma8", + "dma9", + "dma10", + "dma11"; + #dma-cells = <1>; + brcm,dma-channel-mask = <0x0fc0>; + }; + + // Single-lane Gen3 PCIe + // Outbound window at 0x14_000000-0x17_ffffff + pcie0: pcie@100000 { + compatible = "brcm,bcm2712-pcie"; + reg = <0x10 0x00100000 0x0 0x9310>; + device_type = "pci"; + max-link-speed = <2>; + #address-cells = <3>; + #interrupt-cells = <1>; + #size-cells = <2>; + /* + * Unused interrupts: + * 208: AER + * 215: NMI + * 216: PME + */ + interrupt-parent = <&gicv2>; + interrupts = <GIC_SPI 213 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 214 IRQ_TYPE_LEVEL_HIGH>; + interrupt-names = "pcie", "msi"; + interrupt-map-mask = <0x0 0x0 0x0 0x7>; + interrupt-map = <0 0 0 1 &gicv2 GIC_SPI 209 + IRQ_TYPE_LEVEL_HIGH>, + <0 0 0 2 &gicv2 GIC_SPI 210 + IRQ_TYPE_LEVEL_HIGH>, + <0 0 0 3 &gicv2 GIC_SPI 211 + IRQ_TYPE_LEVEL_HIGH>, + <0 0 0 4 &gicv2 GIC_SPI 212 + IRQ_TYPE_LEVEL_HIGH>; + resets = <&bcm_reset 5>, <&bcm_reset 42>, <&pcie_rescal>; + reset-names = "swinit", "bridge", "rescal"; + msi-controller; + msi-parent = <&pcie0>; + + ranges = <0x02000000 0x00 0x00000000 + 0x17 0x00000000 + 0x0 0xfffffffc>, + <0x43000000 0x04 0x00000000 + 0x14 0x00000000 + 0x3 0x00000000>; + + dma-ranges = <0x43000000 0x10 0x00000000 + 0x00 0x00000000 + 0x10 0x00000000>; + + status = "disabled"; + }; + + // Single-lane Gen3 PCIe + // Outbound window at 0x18_000000-0x1b_ffffff + pcie1: pcie@110000 { + compatible = "brcm,bcm2712-pcie"; + reg = <0x10 0x00110000 0x0 0x9310>; + device_type = "pci"; + max-link-speed = <2>; + #address-cells = <3>; + #interrupt-cells = <1>; + #size-cells = <2>; + /* + * Unused interrupts: + * 218: AER + * 225: NMI + * 226: PME + */ + interrupt-parent = <&gicv2>; + interrupts = <GIC_SPI 223 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 224 IRQ_TYPE_LEVEL_HIGH>; + interrupt-names = "pcie", "msi"; + interrupt-map-mask = <0x0 0x0 0x0 0x7>; + interrupt-map = <0 0 0 1 &gicv2 GIC_SPI 219 + IRQ_TYPE_LEVEL_HIGH>, + <0 0 0 2 &gicv2 GIC_SPI 220 + IRQ_TYPE_LEVEL_HIGH>, + <0 0 0 3 &gicv2 GIC_SPI 221 + IRQ_TYPE_LEVEL_HIGH>, + <0 0 0 4 &gicv2 GIC_SPI 222 + IRQ_TYPE_LEVEL_HIGH>; + resets = <&bcm_reset 7>, <&bcm_reset 43>, <&pcie_rescal>; + reset-names = "swinit", "bridge", "rescal"; + msi-controller; + msi-parent = <&mip1>; + + // 2GB, 32-bit, non-prefetchable at PCIe 00_80000000 + ranges = <0x02000000 0x00 0x80000000 + 0x1b 0x80000000 + 0x00 0x80000000>, + // 14GB, 64-bit, prefetchable at PCIe 04_00000000 + <0x43000000 0x04 0x00000000 + 0x18 0x00000000 + 0x03 0x80000000>; + + dma-ranges = <0x03000000 0x10 0x00000000 + 0x00 0x00000000 + 0x10 0x00000000>; + + status = "disabled"; + }; + + pcie_rescal: reset-controller@119500 { + compatible = "brcm,bcm7216-pcie-sata-rescal"; + reg = <0x10 0x00119500 0x0 0x10>; + #reset-cells = <0>; + }; + + // Quad-lane Gen3 PCIe + // Outbound window at 0x1c_000000-0x1f_ffffff + pcie2: pcie@120000 { + compatible = "brcm,bcm2712-pcie"; + reg = <0x10 0x00120000 0x0 0x9310>; + device_type = "pci"; + max-link-speed = <2>; + #address-cells = <3>; + #interrupt-cells = <1>; + #size-cells = <2>; + /* + * Unused interrupts: + * 228: AER + * 235: NMI + * 236: PME + */ + interrupt-parent = <&gicv2>; + interrupts = <GIC_SPI 233 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 234 IRQ_TYPE_LEVEL_HIGH>; + interrupt-names = "pcie", "msi"; + interrupt-map-mask = <0x0 0x0 0x0 0x7>; + interrupt-map = <0 0 0 1 &gicv2 GIC_SPI 229 + IRQ_TYPE_LEVEL_HIGH>, + <0 0 0 2 &gicv2 GIC_SPI 230 + IRQ_TYPE_LEVEL_HIGH>, + <0 0 0 3 &gicv2 GIC_SPI 231 + IRQ_TYPE_LEVEL_HIGH>, + <0 0 0 4 &gicv2 GIC_SPI 232 + IRQ_TYPE_LEVEL_HIGH>; + resets = <&bcm_reset 32>, <&bcm_reset 44>, <&pcie_rescal>; + reset-names = "swinit", "bridge", "rescal"; + msi-controller; + msi-parent = <&mip0>; + + // ~4GB, 32-bit, not-prefetchable at PCIe 00_00000000 + ranges = <0x02000000 0x00 0x00000000 + 0x1f 0x00000000 + 0x0 0xfffffffc>, + // 12GB, 64-bit, prefetchable at PCIe 04_00000000 + <0x43000000 0x04 0x00000000 + 0x1c 0x00000000 + 0x03 0x00000000>; + + // 64GB system RAM space at PCIe 10_00000000 + dma-ranges = <0x02000000 0x00 0x00000000 + 0x1f 0x00000000 + 0x00 0x00400000>, + <0x43000000 0x10 0x00000000 + 0x00 0x00000000 + 0x10 0x00000000>; + + status = "disabled"; + }; + + mip0: msi-controller@130000 { + compatible = "brcm,bcm2712-mip-intc"; + reg = <0x10 0x00130000 0x0 0xc0>; + msi-controller; + interrupt-controller; + #interrupt-cells = <2>; + brcm,msi-base-spi = <128>; + brcm,msi-num-spis = <64>; + brcm,msi-offset = <0>; + brcm,msi-pci-addr = <0xff 0xfffff000>; + }; + + mip1: msi-controller@131000 { + compatible = "brcm,bcm2712-mip-intc"; + reg = <0x10 0x00131000 0x0 0xc0>; + msi-controller; + interrupt-controller; + #interrupt-cells = <2>; + brcm,msi-base-spi = <247>; + /* Actually 20 total, but the others are + * both sparse and non-consecutive */ + brcm,msi-num-spis = <8>; + brcm,msi-offset = <8>; + brcm,msi-pci-addr = <0xff 0xffffe000>; + }; + + syscon_piarbctl: syscon@400018 { + compatible = "brcm,syscon-piarbctl", "syscon", "simple-mfd"; + reg = <0x10 0x00400018 0x0 0x18>; + }; + + usb: usb@480000 { + compatible = "brcm,bcm2835-usb"; + reg = <0x10 0x00480000 0x0 0x10000>; + interrupts = <GIC_SPI 73 IRQ_TYPE_LEVEL_HIGH>; + #address-cells = <1>; + #size-cells = <0>; + clocks = <&clk_usb>; + clock-names = "otg"; + phys = <&usbphy>; + phy-names = "usb2-phy"; + status = "disabled"; + }; + + rpivid: codec@800000 { + compatible = "raspberrypi,rpivid-vid-decoder"; + reg = <0x10 0x00800000 0x0 0x10000>, /* HEVC */ + <0x10 0x00840000 0x0 0x1000>; /* INTC */ + reg-names = "hevc", + "intc"; + + interrupts = <GIC_SPI 98 IRQ_TYPE_LEVEL_HIGH>; + + clocks = <&firmware_clocks 11>; + clock-names = "hevc"; + iommus = <&iommu2>; + status = "disabled"; + }; + + pisp_be: pisp_be@880000 { + compatible = "raspberrypi,pispbe"; + reg = <0x10 0x00880000 0x0 0x4000>; + interrupts = <GIC_SPI 72 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&firmware_clocks 7>; + clocks-names = "isp_be"; + status = "okay"; + iommus = <&iommu2>; + }; + + sdio2: mmc@1100000 { + compatible = "brcm,bcm2712-sdhci"; + reg = <0x10 0x01100000 0x0 0x260>, + <0x10 0x01100400 0x0 0x200>; + reg-names = "host", "cfg"; + interrupts = <GIC_SPI 274 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clk_emmc2>; + sdhci-caps-mask = <0x0000C000 0x0>; + sdhci-caps = <0x0 0x0>; + supports-cqe = <1>; + mmc-ddr-3_3v; + status = "disabled"; + }; + + bcm_reset: reset-controller@1504318 { + compatible = "brcm,brcmstb-reset"; + reg = <0x10 0x01504318 0x0 0x30>; + #reset-cells = <1>; + }; + + v3d: v3d@2000000 { + compatible = "brcm,2712-v3d"; + reg = <0x10 0x02000000 0x0 0x4000>, + <0x10 0x02008000 0x0 0x6000>; + reg-names = "hub", "core0"; + + power-domains = <&pm BCM2835_POWER_DOMAIN_GRAFX_V3D>; + resets = <&pm BCM2835_RESET_V3D>; + clocks = <&firmware_clocks 5>; + clocks-names = "v3d"; + interrupts = <GIC_SPI 250 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 249 IRQ_TYPE_LEVEL_HIGH>; + status = "disabled"; + }; +}; + +&gicv2 { + interrupts = <GIC_PPI 9 (GIC_CPU_MASK_SIMPLE(4) | + IRQ_TYPE_LEVEL_HIGH)>; +}; + +&uart10 { + arm,primecell-periphid = <0x00341011>; +}; + +&aon_intr { + status = "disabled"; +}; + +&gio_aon { + compatible = "brcm,brcmstb-gpio"; + brcm,gpio-direct; +}; + +&hvs { + status = "disabled"; +}; + +&hdmi0 { + status = "disabled"; +}; + +&hdmi1 { + status = "disabled"; +}; + +&pixelvalve0 { + status = "disabled"; +}; + +&pixelvalve1 { + status = "disabled"; +}; + +&mop { + status = "disabled"; +}; + +&moplet { + status = "disabled"; +}; + +&ddc0 { + status = "disabled"; +}; + +&ddc1 { + status = "disabled"; +}; + +&disp_intr { + status = "disabled"; +}; + +&vc4 { + status = "disabled"; +}; diff --git a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-5-b.dts b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-5-b.dts index 2bdbb6780242a1..5181fcad52224a 100644 --- a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-5-b.dts +++ b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-5-b.dts @@ -1,28 +1,48 @@ -// SPDX-License-Identifier: (GPL-2.0 OR MIT) +// SPDX-License-Identifier: GPL-2.0 /dts-v1/; #include <dt-bindings/gpio/gpio.h> -#include "bcm2712.dtsi" +#include <dt-bindings/clock/rp1.h> +#include <dt-bindings/interrupt-controller/irq.h> +#include <dt-bindings/mfd/rp1.h> +#include <dt-bindings/pwm/pwm.h> +#include <dt-bindings/reset/raspberrypi,firmware-reset.h> + +#include "bcm2712-ds.dtsi" / { compatible = "raspberrypi,5-model-b", "brcm,bcm2712"; model = "Raspberry Pi 5"; - aliases { - serial10 = &uart10; - }; - - chosen: chosen { - stdout-path = "serial10:115200n8"; - }; - /* Will be filled by the bootloader */ memory@0 { device_type = "memory"; +#ifndef FIRMWARE_UPDATED + reg = <0 0 0x28000000>; +#else reg = <0 0 0 0x28000000>; +#endif + }; + + leds: leds { + compatible = "gpio-leds"; + + led_pwr: led-pwr { + label = "PWR"; + gpios = <&rp1_gpio 44 GPIO_ACTIVE_LOW>; + default-state = "off"; + linux,default-trigger = "none"; + }; + + led_act: led-act { + label = "ACT"; + gpios = <&gio_aon 9 GPIO_ACTIVE_LOW>; + default-state = "off"; + linux,default-trigger = "mmc0"; + }; }; - sd_io_1v8_reg: sd-io-1v8-reg { + sd_io_1v8_reg: sd_io_1v8_reg { compatible = "regulator-gpio"; regulator-name = "vdd-sd-io"; regulator-min-microvolt = <1800000>; @@ -31,11 +51,12 @@ regulator-always-on; regulator-settling-time-us = <5000>; gpios = <&gio_aon 3 GPIO_ACTIVE_HIGH>; - states = <1800000 1>, - <3300000 0>; + states = <1800000 0x1 + 3300000 0x0>; + status = "okay"; }; - sd_vcc_reg: sd-vcc-reg { + sd_vcc_reg: sd_vcc_reg { compatible = "regulator-fixed"; regulator-name = "vcc-sd"; regulator-min-microvolt = <3300000>; @@ -43,22 +64,664 @@ regulator-boot-on; enable-active-high; gpios = <&gio_aon 4 GPIO_ACTIVE_HIGH>; + status = "okay"; + }; + + wl_on_reg: wl_on_reg { + compatible = "regulator-fixed"; + regulator-name = "wl-on-regulator"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + pinctrl-0 = <&wl_on_pins>; + pinctrl-names = "default"; + + gpio = <&gio 28 GPIO_ACTIVE_HIGH>; + + startup-delay-us = <150000>; + enable-active-high; + }; + + cam1_clk: cam1_clk { + compatible = "fixed-clock"; + #clock-cells = <0>; + status = "disabled"; + }; + + cam0_clk: cam0_clk { + compatible = "fixed-clock"; + #clock-cells = <0>; + status = "disabled"; + }; + + cam0_reg: cam0_reg { + compatible = "regulator-fixed"; + regulator-name = "cam0_reg"; + enable-active-high; + status = "okay"; + gpio = <&rp1_gpio 34 0>; // CD0_IO0_MICCLK, to MIPI 0 connector + }; + + cam1_reg: cam1_reg { + compatible = "regulator-fixed"; + regulator-name = "cam1_reg"; + enable-active-high; + status = "okay"; + gpio = <&rp1_gpio 46 0>; // CD1_IO0_MICCLK, to MIPI 1 connector + }; + + cam_dummy_reg: cam_dummy_reg { + compatible = "regulator-fixed"; + regulator-name = "cam-dummy-reg"; + status = "okay"; + }; + + dummy: dummy { + // A target for unwanted overlay fragments }; + + + // A few extra labels to keep overlays happy + + i2c0if: i2c0if {}; + i2c0mux: i2c0mux {}; }; -/* The Debug UART, on Rpi5 it's on JST-SH 1.0mm 3-pin connector - * labeled "UART", i.e. the interface with the system console. - */ -&uart10 { +rp1_target: &pcie2 { + brcm,enable-mps-rcb; + brcm,vdm-qos-map = <0xbbaa9888>; + aspm-no-l0s; status = "okay"; }; +&pcie1 { + brcm,vdm-qos-map = <0x33333333>; +}; + +// Add some labels to 2712 device + +// The system UART +&uart10 { status = "okay"; }; + +// The system SPI for the bootloader EEPROM +&spi10 { status = "okay"; }; + +#include "rp1.dtsi" + +&rp1 { + // PCIe address space layout: + // 00_00000000-00_00xxxxxx = RP1 peripherals + // 10_00000000-1x_xxxxxxxx = up to 64GB system RAM + + // outbound access aimed at PCIe 0_00xxxxxx -> RP1 c0_40xxxxxx + // This is the RP1 peripheral space + ranges = <0xc0 0x40000000 + 0x02000000 0x00 0x00000000 + 0x00 0x00410000>; + + dma-ranges = + // inbound RP1 1x_xxxxxxxx -> PCIe 1x_xxxxxxxx + <0x10 0x00000000 + 0x43000000 0x10 0x00000000 + 0x10 0x00000000>, + + // inbound RP1 c0_40xxxxxx -> PCIe 00_00xxxxxx + // This allows the RP1 DMA controller to address RP1 hardware + <0xc0 0x40000000 + 0x02000000 0x0 0x00000000 + 0x0 0x00410000>, + + // inbound RP1 0x_xxxxxxxx -> PCIe 1x_xxxxxxxx + <0x00 0x00000000 + 0x02000000 0x10 0x00000000 + 0x10 0x00000000>; +}; + +// Expose RP1 nodes as system nodes with labels + +&rp1_dma { + status = "okay"; +}; + +&rp1_eth { + status = "okay"; + phy-handle = <&phy1>; + phy-reset-gpios = <&rp1_gpio 32 GPIO_ACTIVE_LOW>; + phy-reset-duration = <5>; + + phy1: ethernet-phy@1 { + reg = <0x1>; + brcm,powerdown-enable; + }; +}; + +gpio: &rp1_gpio { + status = "okay"; +}; + +aux: &dummy {}; + +&rp1_usb0 { + pinctrl-0 = <&usb_vbus_pins>; + pinctrl-names = "default"; + status = "okay"; +}; + +&rp1_usb1 { + status = "okay"; +}; + +#include "bcm2712-rpi.dtsi" + +i2c_csi_dsi0: &i2c6 { // Note: This is for MIPI0 connector only + pinctrl-0 = <&rp1_i2c6_38_39>; + pinctrl-names = "default"; + clock-frequency = <100000>; + symlink = "i2c-6"; +}; + +i2c_csi_dsi1: &i2c4 { // Note: This is for MIPI1 connector only + pinctrl-0 = <&rp1_i2c4_40_41>; + pinctrl-names = "default"; + clock-frequency = <100000>; + symlink = "i2c-4"; +}; + +i2c_csi_dsi: &i2c_csi_dsi1 { }; // An alias for compatibility + +csi0: &rp1_csi0 { }; +csi1: &rp1_csi1 { }; +dsi0: &rp1_dsi0 { }; +dsi1: &rp1_dsi1 { }; +dpi: &rp1_dpi { }; +vec: &rp1_vec { }; +dpi_gpio0: &rp1_dpi_24bit_gpio0 { }; +dpi_gpio1: &rp1_dpi_24bit_gpio2 { }; +dpi_18bit_cpadhi_gpio0: &rp1_dpi_18bit_cpadhi_gpio0 { }; +dpi_18bit_cpadhi_gpio2: &rp1_dpi_18bit_cpadhi_gpio2 { }; +dpi_18bit_gpio0: &rp1_dpi_18bit_gpio0 { }; +dpi_18bit_gpio2: &rp1_dpi_18bit_gpio2 { }; +dpi_16bit_cpadhi_gpio0: &rp1_dpi_16bit_cpadhi_gpio0 { }; +dpi_16bit_cpadhi_gpio2: &rp1_dpi_16bit_cpadhi_gpio2 { }; +dpi_16bit_gpio0: &rp1_dpi_16bit_gpio0 { }; +dpi_16bit_gpio2: &rp1_dpi_16bit_gpio2 { }; + +/* Add the IOMMUs for some RP1 bus masters */ + +&csi0 { + iommus = <&iommu5>; +}; + +&csi1 { + iommus = <&iommu5>; +}; + +&dsi0 { + iommus = <&iommu5>; +}; + +&dsi1 { + iommus = <&iommu5>; +}; + +&dpi { + iommus = <&iommu5>; +}; + +&vec { + iommus = <&iommu5>; +}; + +&ddc0 { + status = "disabled"; +}; + +&ddc1 { + status = "disabled"; +}; + +&hdmi0 { + clocks = <&firmware_clocks 13>, <&firmware_clocks 14>, <&dvp 0>, <&clk_27MHz>; + clock-names = "hdmi", "bvb", "audio", "cec"; + status = "disabled"; +}; + +&hdmi1 { + clocks = <&firmware_clocks 13>, <&firmware_clocks 14>, <&dvp 1>, <&clk_27MHz>; + clock-names = "hdmi", "bvb", "audio", "cec"; + status = "disabled"; +}; + +&hvs { + clocks = <&firmware_clocks 4>, <&firmware_clocks 16>; + clock-names = "core", "disp"; +}; + +&mop { + status = "disabled"; +}; + +&moplet { + status = "disabled"; +}; + +&pixelvalve0 { + status = "disabled"; +}; + +&pixelvalve1 { + status = "disabled"; +}; + +&disp_intr { + status = "disabled"; +}; + /* SDIO1 is used to drive the SD card */ &sdio1 { + pinctrl-0 = <&emmc_sd_pulls>, <&emmc_aon_cd_pins>; + pinctrl-names = "default"; vqmmc-supply = <&sd_io_1v8_reg>; vmmc-supply = <&sd_vcc_reg>; bus-width = <4>; sd-uhs-sdr50; sd-uhs-ddr50; sd-uhs-sdr104; + supports-cqe = <1>; + cd-gpios = <&gio_aon 5 GPIO_ACTIVE_LOW>; + //no-1-8-v; + status = "okay"; +}; + +&pinctrl_aon { + emmc_aon_cd_pins: emmc_aon_cd_pins { + function = "sd_card_g"; + pins = "aon_gpio5"; + bias-pull-up; + }; + + /* Slight hack - only one PWM pin (status LED) is usable */ + aon_pwm_1pin: aon_pwm_1pin { + function = "aon_pwm"; + pins = "aon_gpio9"; + }; +}; + +&pinctrl { + pwr_button_pins: pwr_button_pins { + function = "gpio"; + pins = "gpio20"; + bias-pull-up; + }; + + wl_on_pins: wl_on_pins { + function = "gpio"; + pins = "gpio28"; + }; + + bt_shutdown_pins: bt_shutdown_pins { + function = "gpio"; + pins = "gpio29"; + }; + + emmc_sd_pulls: emmc_sd_pulls { + pins = "emmc_cmd", "emmc_dat0", "emmc_dat1", "emmc_dat2", "emmc_dat3"; + bias-pull-up; + }; +}; + +/* uarta communicates with the BT module */ +&uarta { + uart-has-rtscts; + auto-flow-control; + status = "okay"; + clock-frequency = <96000000>; + pinctrl-0 = <&uarta_24_pins &bt_shutdown_pins>; + pinctrl-names = "default"; + + bluetooth: bluetooth { + compatible = "brcm,bcm43438-bt"; + max-speed = <3000000>; + shutdown-gpios = <&gio 29 GPIO_ACTIVE_HIGH>; + local-bd-address = [ 00 00 00 00 00 00 ]; + }; +}; + +&i2c10 { + clock-frequency = <400000>; + pinctrl-0 = <&i2c3_m4_agpio0_pins>; + pinctrl-names = "default"; +}; + +/ { + fan: cooling_fan { + status = "disabled"; + compatible = "pwm-fan"; + #cooling-cells = <2>; + cooling-min-state = <0>; + cooling-max-state = <3>; + cooling-levels = <0 75 125 175 250>; + pwms = <&rp1_pwm1 3 41566 PWM_POLARITY_INVERTED>; + rpm-regmap = <&rp1_pwm1>; + rpm-offset = <0x3c>; + }; + + pwr_button { + compatible = "gpio-keys"; + + pinctrl-names = "default"; + pinctrl-0 = <&pwr_button_pins>; + status = "okay"; + + pwr_key: pwr { + label = "pwr_button"; + // linux,code = <205>; // KEY_SUSPEND + linux,code = <116>; // KEY_POWER + gpios = <&gio 20 GPIO_ACTIVE_LOW>; + debounce-interval = <50>; // ms + }; + }; +}; + +&usb { + power-domains = <&power RPI_POWER_DOMAIN_USB>; +}; + +/* SDIO2 drives the WLAN interface */ +&sdio2 { + pinctrl-0 = <&sdio2_30_pins>; + pinctrl-names = "default"; + bus-width = <4>; + vmmc-supply = <&wl_on_reg>; + sd-uhs-ddr50; + non-removable; + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + wifi: wifi@1 { + reg = <1>; + compatible = "brcm,bcm4329-fmac"; + local-mac-address = [00 00 00 00 00 00]; + }; +}; + +&rpivid { + status = "okay"; +}; + +&pinctrl { + spi10_gpio2: spi10_gpio2 { + function = "vc_spi0"; + pins = "gpio2", "gpio3", "gpio4"; + bias-disable; + }; + + spi10_cs_gpio1: spi10_cs_gpio1 { + function = "gpio"; + pins = "gpio1"; + bias-pull-up; + }; +}; + +spi10_pins: &spi10_gpio2 {}; +spi10_cs_pins: &spi10_cs_gpio1 {}; + +&spi10 { + pinctrl-names = "default"; + cs-gpios = <&gio 1 1>; + pinctrl-0 = <&spi10_pins &spi10_cs_pins>; + + spidev10: spidev@0 { + compatible = "spidev"; + reg = <0>; /* CE0 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <20000000>; + status = "okay"; + }; +}; + +// ============================================= +// Board specific stuff here + +&gio_aon { + // Don't use GIO_AON as an interrupt controller because it will + // clash with the firmware monitoring the PMIC interrupt via the VPU. + + /delete-property/ interrupt-controller; + /delete-property/ #interrupt-cells; +}; + +&main_aon_irq { + // Don't use the MAIN_AON_IRQ interrupt controller because it will + // clash with the firmware monitoring the PMIC interrupt via the VPU. + + status = "disabled"; +}; + +&rp1_pwm1 { + status = "disabled"; + pinctrl-0 = <&rp1_pwm1_gpio45>; + pinctrl-names = "default"; +}; + +&thermal_trips { + cpu_tepid: cpu-tepid { + temperature = <50000>; + hysteresis = <5000>; + type = "active"; + }; + + cpu_warm: cpu-warm { + temperature = <60000>; + hysteresis = <5000>; + type = "active"; + }; + + cpu_hot: cpu-hot { + temperature = <67500>; + hysteresis = <5000>; + type = "active"; + }; + + cpu_vhot: cpu-vhot { + temperature = <75000>; + hysteresis = <5000>; + type = "active"; + }; +}; + +&cooling_maps { + tepid { + trip = <&cpu_tepid>; + cooling-device = <&fan 1 1>; + }; + + warm { + trip = <&cpu_warm>; + cooling-device = <&fan 2 2>; + }; + + hot { + trip = <&cpu_hot>; + cooling-device = <&fan 3 3>; + }; + + vhot { + trip = <&cpu_vhot>; + cooling-device = <&fan 4 4>; + }; + + melt { + trip = <&cpu_crit>; + cooling-device = <&fan 4 4>; + }; +}; + +&gio { + // The GPIOs above 35 are not used on Pi 5, so shrink the upper bank + // to reduce the clutter in gpioinfo/pinctrl + brcm,gpio-bank-widths = <32 4>; + + gpio-line-names = + "-", // GPIO_000 + "2712_BOOT_CS_N", // GPIO_001 + "2712_BOOT_MISO", // GPIO_002 + "2712_BOOT_MOSI", // GPIO_003 + "2712_BOOT_SCLK", // GPIO_004 + "-", // GPIO_005 + "-", // GPIO_006 + "-", // GPIO_007 + "-", // GPIO_008 + "-", // GPIO_009 + "-", // GPIO_010 + "-", // GPIO_011 + "-", // GPIO_012 + "-", // GPIO_013 + "PCIE_SDA", // GPIO_014 + "PCIE_SCL", // GPIO_015 + "-", // GPIO_016 + "-", // GPIO_017 + "-", // GPIO_018 + "-", // GPIO_019 + "PWR_GPIO", // GPIO_020 + "2712_G21_FS", // GPIO_021 + "-", // GPIO_022 + "-", // GPIO_023 + "BT_RTS", // GPIO_024 + "BT_CTS", // GPIO_025 + "BT_TXD", // GPIO_026 + "BT_RXD", // GPIO_027 + "WL_ON", // GPIO_028 + "BT_ON", // GPIO_029 + "WIFI_SDIO_CLK", // GPIO_030 + "WIFI_SDIO_CMD", // GPIO_031 + "WIFI_SDIO_D0", // GPIO_032 + "WIFI_SDIO_D1", // GPIO_033 + "WIFI_SDIO_D2", // GPIO_034 + "WIFI_SDIO_D3"; // GPIO_035 +}; + +&gio_aon { + gpio-line-names = + "RP1_SDA", // AON_GPIO_00 + "RP1_SCL", // AON_GPIO_01 + "RP1_RUN", // AON_GPIO_02 + "SD_IOVDD_SEL", // AON_GPIO_03 + "SD_PWR_ON", // AON_GPIO_04 + "SD_CDET_N", // AON_GPIO_05 + "SD_FLG_N", // AON_GPIO_06 + "-", // AON_GPIO_07 + "2712_WAKE", // AON_GPIO_08 + "2712_STAT_LED", // AON_GPIO_09 + "-", // AON_GPIO_10 + "-", // AON_GPIO_11 + "PMIC_INT", // AON_GPIO_12 + "UART_TX_FS", // AON_GPIO_13 + "UART_RX_FS", // AON_GPIO_14 + "-", // AON_GPIO_15 + "-", // AON_GPIO_16 + + // Pad bank0 out to 32 entries + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + + "HDMI0_SCL", // AON_SGPIO_00 + "HDMI0_SDA", // AON_SGPIO_01 + "HDMI1_SCL", // AON_SGPIO_02 + "HDMI1_SDA", // AON_SGPIO_03 + "PMIC_SCL", // AON_SGPIO_04 + "PMIC_SDA"; // AON_SGPIO_05 + + rp1_run_hog { + gpio-hog; + gpios = <2 GPIO_ACTIVE_HIGH>; + output-high; + line-name = "RP1 RUN pin"; + }; +}; + +&rp1_gpio { + gpio-line-names = + "ID_SDA", // GPIO0 + "ID_SCL", // GPIO1 + "GPIO2", // GPIO2 + "GPIO3", // GPIO3 + "GPIO4", // GPIO4 + "GPIO5", // GPIO5 + "GPIO6", // GPIO6 + "GPIO7", // GPIO7 + "GPIO8", // GPIO8 + "GPIO9", // GPIO9 + "GPIO10", // GPIO10 + "GPIO11", // GPIO11 + "GPIO12", // GPIO12 + "GPIO13", // GPIO13 + "GPIO14", // GPIO14 + "GPIO15", // GPIO15 + "GPIO16", // GPIO16 + "GPIO17", // GPIO17 + "GPIO18", // GPIO18 + "GPIO19", // GPIO19 + "GPIO20", // GPIO20 + "GPIO21", // GPIO21 + "GPIO22", // GPIO22 + "GPIO23", // GPIO23 + "GPIO24", // GPIO24 + "GPIO25", // GPIO25 + "GPIO26", // GPIO26 + "GPIO27", // GPIO27 + + "PCIE_RP1_WAKE", // GPIO28 + "FAN_TACH", // GPIO29 + "HOST_SDA", // GPIO30 + "HOST_SCL", // GPIO31 + "ETH_RST_N", // GPIO32 + "-", // GPIO33 + + "CD0_IO0_MICCLK", // GPIO34 + "CD0_IO0_MICDAT0", // GPIO35 + "RP1_PCIE_CLKREQ_N", // GPIO36 + "-", // GPIO37 + "CD0_SDA", // GPIO38 + "CD0_SCL", // GPIO39 + "CD1_SDA", // GPIO40 + "CD1_SCL", // GPIO41 + "USB_VBUS_EN", // GPIO42 + "USB_OC_N", // GPIO43 + "RP1_STAT_LED", // GPIO44 + "FAN_PWM", // GPIO45 + "CD1_IO0_MICCLK", // GPIO46 + "2712_WAKE", // GPIO47 + "CD1_IO1_MICDAT1", // GPIO48 + "EN_MAX_USB_CUR", // GPIO49 + "-", // GPIO50 + "-", // GPIO51 + "-", // GPIO52 + "-"; // GPIO53 + + usb_vbus_pins: usb_vbus_pins { + function = "vbus1"; + pins = "gpio42", "gpio43"; + }; +}; + +/ { + __overrides__ { + sd_cqe = <&sdio1>, "supports-cqe:0"; + }; +}; + +&hvs { + clocks = <&firmware_clocks 4>, <&firmware_clocks 16>; + clock-names = "core", "disp"; +}; + +&hdmi0 { + clocks = <&firmware_clocks 13>, <&firmware_clocks 14>, <&dvp 0>, <&clk_27MHz>; + clock-names = "hdmi", "bvb", "audio", "cec"; +}; + +&hdmi1 { + clocks = <&firmware_clocks 13>, <&firmware_clocks 14>, <&dvp 1>, <&clk_27MHz>; + clock-names = "hdmi", "bvb", "audio", "cec"; }; diff --git a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-500.dts b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-500.dts new file mode 100644 index 00000000000000..1862e55fa1d2b7 --- /dev/null +++ b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-500.dts @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "bcm2712d0-rpi-5-b.dts" + +/ { + compatible = "raspberrypi,500", "brcm,bcm2712"; + model = "Raspberry Pi 500"; +}; + +&pwr_key { + debounce-interval = <400>; +}; + +&gio { + gpio-line-names = + "", // GPIO_000 + "2712_BOOT_CS_N", // GPIO_001 + "2712_BOOT_MISO", // GPIO_002 + "2712_BOOT_MOSI", // GPIO_003 + "2712_BOOT_SCLK", // GPIO_004 + "", // GPIO_005 + "", // GPIO_006 + "", // GPIO_007 + "", // GPIO_008 + "", // GPIO_009 + "-", // GPIO_010 + "-", // GPIO_011 + "-", // GPIO_012 + "-", // GPIO_013 + "M2_DET_WAKE", // GPIO_014 + "M2_PWR_EN", // GPIO_015 + "", // GPIO_016 + "", // GPIO_017 + "KEYB_BOOTSEL", // GPIO_018 + "-", // GPIO_019 + "PWR_GPIO", // GPIO_020 + "KEYB_RUN", // GPIO_021 + "-", // GPIO_022 + "USER_LED", // GPIO_023 + "BT_RTS", // GPIO_024 + "BT_CTS", // GPIO_025 + "BT_TXD", // GPIO_026 + "BT_RXD", // GPIO_027 + "WL_ON", // GPIO_028 + "BT_ON", // GPIO_029 + "WIFI_SDIO_CLK", // GPIO_030 + "WIFI_SDIO_CMD", // GPIO_031 + "WIFI_SDIO_D0", // GPIO_032 + "WIFI_SDIO_D1", // GPIO_033 + "WIFI_SDIO_D2", // GPIO_034 + "WIFI_SDIO_D3"; // GPIO_035 +}; + +&gio_aon { + gpio-line-names = + "RP1_SDA", // AON_GPIO_00 + "RP1_SCL", // AON_GPIO_01 + "RP1_RUN", // AON_GPIO_02 + "SD_IOVDD_SEL", // AON_GPIO_03 + "SD_PWR_ON", // AON_GPIO_04 + "SD_CDET_N", // AON_GPIO_05 + "SD_FLG_N", // AON_GPIO_06 + "", // AON_GPIO_07 + "2712_WAKE", // AON_GPIO_08 + "2712_STAT_LED", // AON_GPIO_09 + "", // AON_GPIO_10 + "", // AON_GPIO_11 + "PMIC_INT", // AON_GPIO_12 + "UART_TX_FS", // AON_GPIO_13 + "UART_RX_FS", // AON_GPIO_14 + "", // AON_GPIO_15 + "", // AON_GPIO_16 + + // Pad bank0 out to 32 entries + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + + "HDMI0_SCL", // AON_SGPIO_00 + "HDMI0_SDA", // AON_SGPIO_01 + "HDMI1_SCL", // AON_SGPIO_02 + "HDMI1_SDA", // AON_SGPIO_03 + "PMIC_SCL", // AON_SGPIO_04 + "PMIC_SDA"; // AON_SGPIO_05 +}; + +&rp1_gpio { + gpio-line-names = + "ID_SDA", // GPIO0 + "ID_SCL", // GPIO1 + "GPIO2", // GPIO2 + "GPIO3", // GPIO3 + "GPIO4", // GPIO4 + "GPIO5", // GPIO5 + "GPIO6", // GPIO6 + "GPIO7", // GPIO7 + "GPIO8", // GPIO8 + "GPIO9", // GPIO9 + "GPIO10", // GPIO10 + "GPIO11", // GPIO11 + "GPIO12", // GPIO12 + "GPIO13", // GPIO13 + "GPIO14", // GPIO14 + "GPIO15", // GPIO15 + "GPIO16", // GPIO16 + "GPIO17", // GPIO17 + "GPIO18", // GPIO18 + "GPIO19", // GPIO19 + "GPIO20", // GPIO20 + "GPIO21", // GPIO21 + "GPIO22", // GPIO22 + "GPIO23", // GPIO23 + "GPIO24", // GPIO24 + "GPIO25", // GPIO25 + "GPIO26", // GPIO26 + "GPIO27", // GPIO27 + + "PCIE_RP1_WAKE", // GPIO28 + "-", // GPIO29 + "HOST_SDA", // GPIO30 + "HOST_SCL", // GPIO31 + "ETH_RST_N", // GPIO32 + "PCIE_DET_WAKE", // GPIO33 + + "-", // GPIO34 + "-", // GPIO35 + "RP1_PCIE_CLKREQ_N", // GPIO36 + "-", // GPIO37 + "-", // GPIO38 + "-", // GPIO39 + "CD1_SDA", // GPIO40 + "CD1_SCL", // GPIO41 + "USB_VBUS_EN", // GPIO42 + "USB_OC_N", // GPIO43 + "RP1_STAT_LED", // GPIO44 + "-", // GPIO45 + "-", // GPIO46 + "HOST_WAKE", // GPIO47 + "-", // GPIO48 + "EN_MAX_USB_CUR", // GPIO49 + "-", // GPIO50 + "-", // GPIO51 + "-", // GPIO52 + "-"; // GPIO53 +}; diff --git a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm4io.dtsi b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm4io.dtsi new file mode 100644 index 00000000000000..1b4c42a61817cd --- /dev/null +++ b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm4io.dtsi @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-2.0 + +i2c_csi_dsi0: &i2c0 { // Note: For CAM0 and DISP0 connectors +}; + +i2c_csi_dsi1: &i2c6 { // Note: For CAM1, DISP1, on-board RTC, and fan controller + pinctrl-0 = <&rp1_i2c6_38_39>; + pinctrl-names = "default"; + clock-frequency = <100000>; + symlink = "i2c-6"; +}; + +i2c_csi_dsi: &i2c_csi_dsi1 { }; // The connector that needs no jumper to enable + +&aliases { + /delete-property/ i2c11; + i2c10 = &i2c_csi_dsi; +}; + +// The RP1 USB3 interfaces are not usable on CM4IO + +&rp1_usb0 { + status = "disabled"; +}; + +&rp1_usb1 { + status = "disabled"; +}; diff --git a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5-cm4io.dts b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5-cm4io.dts new file mode 100644 index 00000000000000..96cd7cf735d58c --- /dev/null +++ b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5-cm4io.dts @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: GPL-2.0 +/dts-v1/; + +#include "bcm2712-rpi-cm5.dtsi" +#include "bcm2712-rpi-cm4io.dtsi" diff --git a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5-cm5io.dts b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5-cm5io.dts new file mode 100644 index 00000000000000..6b5e147d569d2f --- /dev/null +++ b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5-cm5io.dts @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: GPL-2.0 +/dts-v1/; + +#include "bcm2712-rpi-cm5.dtsi" +#include "bcm2712-rpi-cm5io.dtsi" diff --git a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5.dtsi b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5.dtsi new file mode 100644 index 00000000000000..42f5c012b1ccbc --- /dev/null +++ b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5.dtsi @@ -0,0 +1,735 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <dt-bindings/gpio/gpio.h> +#include <dt-bindings/clock/rp1.h> +#include <dt-bindings/interrupt-controller/irq.h> +#include <dt-bindings/mfd/rp1.h> +#include <dt-bindings/pwm/pwm.h> +#include <dt-bindings/reset/raspberrypi,firmware-reset.h> + +#include "bcm2712-ds.dtsi" + +/ { + compatible = "raspberrypi,5-compute-module", "brcm,bcm2712"; + model = "Raspberry Pi Compute Module 5"; + + /* Will be filled by the bootloader */ + memory@0 { + device_type = "memory"; +#ifndef FIRMWARE_UPDATED + reg = <0 0 0x28000000>; +#else + reg = <0 0 0 0x28000000>; +#endif + }; + + leds: leds { + compatible = "gpio-leds"; + + led_pwr: led-pwr { + label = "PWR"; + gpios = <&rp1_gpio 44 GPIO_ACTIVE_LOW>; + default-state = "off"; + linux,default-trigger = "none"; + }; + + led_act: led-act { + label = "ACT"; + gpios = <&gio_aon 9 GPIO_ACTIVE_LOW>; + default-state = "off"; + linux,default-trigger = "mmc0"; + }; + }; + + sd_io_1v8_reg: sd_io_1v8_reg { + compatible = "regulator-fixed"; + regulator-name = "vdd-sd-io"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; + }; + + sd_vcc_reg: sd_vcc_reg { + compatible = "regulator-fixed"; + regulator-name = "vcc-sd"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-boot-on; + enable-active-high; + gpios = <&gio_aon 4 GPIO_ACTIVE_HIGH>; + status = "okay"; + }; + + wl_on_reg: wl_on_reg { + compatible = "regulator-fixed"; + regulator-name = "wl-on-regulator"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + pinctrl-0 = <&wl_on_pins>; + pinctrl-names = "default"; + + gpio = <&gio 28 GPIO_ACTIVE_HIGH>; + + startup-delay-us = <150000>; + enable-active-high; + }; + + cam1_clk: cam1_clk { + compatible = "fixed-clock"; + #clock-cells = <0>; + status = "disabled"; + }; + + cam0_clk: cam0_clk { + compatible = "fixed-clock"; + #clock-cells = <0>; + status = "disabled"; + }; + + cam0_reg: cam0_reg { + compatible = "regulator-fixed"; + regulator-name = "cam0_reg"; + enable-active-high; + status = "okay"; + gpio = <&rp1_gpio 34 0>; // CD0_IO0_MICCLK, to CAM_GPIO on connector + }; + + cam_dummy_reg: cam_dummy_reg { + compatible = "regulator-fixed"; + regulator-name = "cam-dummy-reg"; + status = "okay"; + }; + + dummy: dummy { + // A target for unwanted overlay fragments + }; + + + // A few extra labels to keep overlays happy + + i2c0if: i2c0if {}; + i2c0mux: i2c0mux {}; +}; + +rp1_target: &pcie2 { + brcm,enable-mps-rcb; + brcm,vdm-qos-map = <0xbbaa9888>; + aspm-no-l0s; + status = "okay"; +}; + +&pcie1 { + brcm,vdm-qos-map = <0x33333333>; +}; + +// Add some labels to 2712 device + +// The system UART +&uart10 { status = "okay"; }; + +// The system SPI for the bootloader EEPROM +&spi10 { status = "okay"; }; + +#include "rp1.dtsi" + +&rp1 { + // PCIe address space layout: + // 00_00000000-00_00xxxxxx = RP1 peripherals + // 10_00000000-1x_xxxxxxxx = up to 64GB system RAM + + // outbound access aimed at PCIe 0_00xxxxxx -> RP1 c0_40xxxxxx + // This is the RP1 peripheral space + ranges = <0xc0 0x40000000 + 0x02000000 0x00 0x00000000 + 0x00 0x00410000>; + + dma-ranges = + // inbound RP1 1x_xxxxxxxx -> PCIe 1x_xxxxxxxx + <0x10 0x00000000 + 0x43000000 0x10 0x00000000 + 0x10 0x00000000>, + + // inbound RP1 c0_40xxxxxx -> PCIe 00_00xxxxxx + // This allows the RP1 DMA controller to address RP1 hardware + <0xc0 0x40000000 + 0x02000000 0x0 0x00000000 + 0x0 0x00410000>, + + // inbound RP1 0x_xxxxxxxx -> PCIe 1x_xxxxxxxx + <0x00 0x00000000 + 0x02000000 0x10 0x00000000 + 0x10 0x00000000>; +}; + +// Expose RP1 nodes as system nodes with labels + +&rp1_dma { + status = "okay"; +}; + +&rp1_eth { + status = "okay"; + phy-handle = <&phy1>; + phy-reset-gpios = <&rp1_gpio 32 GPIO_ACTIVE_LOW>; + phy-reset-duration = <5>; + + phy1: ethernet-phy@1 { + reg = <0x1>; + brcm,powerdown-enable; + interrupt-parent = <&gpio>; + interrupts = <37 IRQ_TYPE_LEVEL_LOW>; + }; +}; + +gpio: &rp1_gpio { + status = "okay"; +}; + +aux: &dummy {}; + +&rp1_usb0 { + pinctrl-0 = <&usb_vbus_pins>; + pinctrl-names = "default"; + status = "okay"; +}; + +&rp1_usb1 { + status = "okay"; +}; + +#include "bcm2712-rpi.dtsi" + +cam1_reg: &cam0_reg { // Shares CAM_GPIO with cam0_reg +}; + +csi0: &rp1_csi0 { }; +csi1: &rp1_csi1 { }; +dsi0: &rp1_dsi0 { }; +dsi1: &rp1_dsi1 { }; +dpi: &rp1_dpi { }; +vec: &rp1_vec { }; +dpi_gpio0: &rp1_dpi_24bit_gpio0 { }; +dpi_gpio1: &rp1_dpi_24bit_gpio2 { }; +dpi_18bit_cpadhi_gpio0: &rp1_dpi_18bit_cpadhi_gpio0 { }; +dpi_18bit_cpadhi_gpio2: &rp1_dpi_18bit_cpadhi_gpio2 { }; +dpi_18bit_gpio0: &rp1_dpi_18bit_gpio0 { }; +dpi_18bit_gpio2: &rp1_dpi_18bit_gpio2 { }; +dpi_16bit_cpadhi_gpio0: &rp1_dpi_16bit_cpadhi_gpio0 { }; +dpi_16bit_cpadhi_gpio2: &rp1_dpi_16bit_cpadhi_gpio2 { }; +dpi_16bit_gpio0: &rp1_dpi_16bit_gpio0 { }; +dpi_16bit_gpio2: &rp1_dpi_16bit_gpio2 { }; + +/* Add the IOMMUs for some RP1 bus masters */ + +&csi0 { + iommus = <&iommu5>; +}; + +&csi1 { + iommus = <&iommu5>; +}; + +&dsi0 { + iommus = <&iommu5>; +}; + +&dsi1 { + iommus = <&iommu5>; +}; + +&dpi { + iommus = <&iommu5>; +}; + +&vec { + iommus = <&iommu5>; +}; + +&ddc0 { + status = "disabled"; +}; + +&ddc1 { + status = "disabled"; +}; + +&hdmi0 { + clocks = <&firmware_clocks 13>, <&firmware_clocks 14>, <&dvp 0>, <&clk_27MHz>; + clock-names = "hdmi", "bvb", "audio", "cec"; + status = "disabled"; +}; + +&hdmi1 { + clocks = <&firmware_clocks 13>, <&firmware_clocks 14>, <&dvp 1>, <&clk_27MHz>; + clock-names = "hdmi", "bvb", "audio", "cec"; + status = "disabled"; +}; + +&hvs { + clocks = <&firmware_clocks 4>, <&firmware_clocks 16>; + clock-names = "core", "disp"; +}; + +&mop { + status = "disabled"; +}; + +&moplet { + status = "disabled"; +}; + +&pixelvalve0 { + status = "disabled"; +}; + +&pixelvalve1 { + status = "disabled"; +}; + +&disp_intr { + status = "disabled"; +}; + +/* SDIO1 is used to drive the eMMC/SD card */ +&sdio1 { + pinctrl-0 = <&emmc_cmddat_pulls>, <&emmc_ds_pull>; + pinctrl-names = "default"; + vqmmc-supply = <&sd_io_1v8_reg>; + vmmc-supply = <&sd_vcc_reg>; + bus-width = <8>; + sd-uhs-sdr50; + sd-uhs-ddr50; + sd-uhs-sdr104; + mmc-hs200-1_8v; + mmc-hs400-1_8v; + mmc-hs400-enhanced-strobe; + broken-cd; + supports-cqe = <1>; + status = "okay"; +}; + +&pinctrl_aon { + ant_pins: ant_pins { + function = "gpio"; + pins = "aon_gpio5", "aon_gpio6"; + }; + + /* Slight hack - only one PWM pin (status LED) is usable */ + aon_pwm_1pin: aon_pwm_1pin { + function = "aon_pwm"; + pins = "aon_gpio9"; + }; +}; + +&pinctrl { + pwr_button_pins: pwr_button_pins { + function = "gpio"; + pins = "gpio20"; + bias-pull-up; + }; + + wl_on_pins: wl_on_pins { + function = "gpio"; + pins = "gpio28"; + }; + + bt_shutdown_pins: bt_shutdown_pins { + function = "gpio"; + pins = "gpio29"; + }; + + emmc_ds_pull: emmc_ds_pull { + pins = "emmc_ds"; + bias-pull-down; + }; + + emmc_cmddat_pulls: emmc_cmddat_pulls { + pins = "emmc_cmd", "emmc_dat0", "emmc_dat1", "emmc_dat2", "emmc_dat3", + "emmc_dat4", "emmc_dat5", "emmc_dat6", "emmc_dat7"; + bias-pull-up; + }; +}; + +/* uarta communicates with the BT module */ +&uarta { + uart-has-rtscts; + auto-flow-control; + status = "okay"; + clock-frequency = <96000000>; + pinctrl-0 = <&uarta_24_pins &bt_shutdown_pins>; + pinctrl-names = "default"; + + bluetooth: bluetooth { + compatible = "brcm,bcm43438-bt"; + max-speed = <3000000>; + shutdown-gpios = <&gio 29 GPIO_ACTIVE_HIGH>; + local-bd-address = [ 00 00 00 00 00 00 ]; + }; +}; + +&i2c10 { + clock-frequency = <400000>; + pinctrl-0 = <&i2c3_m4_agpio0_pins>; + pinctrl-names = "default"; +}; + +/ { + fan: cooling_fan { + status = "disabled"; + compatible = "pwm-fan"; + #cooling-cells = <2>; + cooling-min-state = <0>; + cooling-max-state = <3>; + cooling-levels = <0 75 125 175 250>; + pwms = <&rp1_pwm1 3 41566 PWM_POLARITY_INVERTED>; + rpm-regmap = <&rp1_pwm1>; + rpm-offset = <0x3c>; + }; + + pwr_button { + compatible = "gpio-keys"; + + pinctrl-names = "default"; + pinctrl-0 = <&pwr_button_pins>; + status = "okay"; + + pwr_key: pwr { + label = "pwr_button"; + // linux,code = <205>; // KEY_SUSPEND + linux,code = <116>; // KEY_POWER + gpios = <&gio 20 GPIO_ACTIVE_LOW>; + debounce-interval = <50>; // ms + }; + }; +}; + +&usb { + power-domains = <&power RPI_POWER_DOMAIN_USB>; +}; + +/* SDIO2 drives the WLAN interface */ +&sdio2 { + pinctrl-0 = <&sdio2_30_pins>, <&ant_pins>; + pinctrl-names = "default"; + bus-width = <4>; + vmmc-supply = <&wl_on_reg>; + sd-uhs-ddr50; + non-removable; + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + wifi: wifi@1 { + reg = <1>; + compatible = "brcm,bcm4329-fmac"; + local-mac-address = [00 00 00 00 00 00]; + }; +}; + +&rpivid { + status = "okay"; +}; + +&pinctrl { + spi10_gpio2: spi10_gpio2 { + function = "vc_spi0"; + pins = "gpio2", "gpio3", "gpio4"; + bias-disable; + }; + + spi10_cs_gpio1: spi10_cs_gpio1 { + function = "gpio"; + pins = "gpio1"; + bias-pull-up; + }; +}; + +spi10_pins: &spi10_gpio2 {}; +spi10_cs_pins: &spi10_cs_gpio1 {}; + +&spi10 { + pinctrl-names = "default"; + cs-gpios = <&gio 1 1>; + pinctrl-0 = <&spi10_pins &spi10_cs_pins>; + + spidev10: spidev@0 { + compatible = "spidev"; + reg = <0>; /* CE0 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <20000000>; + status = "okay"; + }; +}; + +// ============================================= +// Board specific stuff here + +&gio_aon { + // Don't use GIO_AON as an interrupt controller because it will + // clash with the firmware monitoring the PMIC interrupt via the VPU. + + /delete-property/ interrupt-controller; + /delete-property/ #interrupt-cells; +}; + +&main_aon_irq { + // Don't use the MAIN_AON_IRQ interrupt controller because it will + // clash with the firmware monitoring the PMIC interrupt via the VPU. + + status = "disabled"; +}; + +&rp1_pwm1 { + status = "disabled"; + pinctrl-0 = <&rp1_pwm1_gpio45>; + pinctrl-names = "default"; +}; + +&thermal_trips { + cpu_tepid: cpu-tepid { + temperature = <50000>; + hysteresis = <5000>; + type = "active"; + }; + + cpu_warm: cpu-warm { + temperature = <60000>; + hysteresis = <5000>; + type = "active"; + }; + + cpu_hot: cpu-hot { + temperature = <67500>; + hysteresis = <5000>; + type = "active"; + }; + + cpu_vhot: cpu-vhot { + temperature = <75000>; + hysteresis = <5000>; + type = "active"; + }; +}; + +&cooling_maps { + tepid { + trip = <&cpu_tepid>; + cooling-device = <&fan 1 1>; + }; + + warm { + trip = <&cpu_warm>; + cooling-device = <&fan 2 2>; + }; + + hot { + trip = <&cpu_hot>; + cooling-device = <&fan 3 3>; + }; + + vhot { + trip = <&cpu_vhot>; + cooling-device = <&fan 4 4>; + }; + + melt { + trip = <&cpu_crit>; + cooling-device = <&fan 4 4>; + }; +}; + +&gio { + // The GPIOs above 35 are not used on Pi 5, so shrink the upper bank + // to reduce the clutter in gpioinfo/pinctrl + brcm,gpio-bank-widths = <32 4>; + + gpio-line-names = + "-", // GPIO_000 + "2712_BOOT_CS_N", // GPIO_001 + "2712_BOOT_MISO", // GPIO_002 + "2712_BOOT_MOSI", // GPIO_003 + "2712_BOOT_SCLK", // GPIO_004 + "-", // GPIO_005 + "-", // GPIO_006 + "-", // GPIO_007 + "-", // GPIO_008 + "-", // GPIO_009 + "-", // GPIO_010 + "-", // GPIO_011 + "-", // GPIO_012 + "-", // GPIO_013 + "-", // GPIO_014 + "-", // GPIO_015 + "-", // GPIO_016 + "-", // GPIO_017 + "-", // GPIO_018 + "-", // GPIO_019 + "PWR_GPIO", // GPIO_020 + "2712_G21_FS", // GPIO_021 + "-", // GPIO_022 + "-", // GPIO_023 + "BT_RTS", // GPIO_024 + "BT_CTS", // GPIO_025 + "BT_TXD", // GPIO_026 + "BT_RXD", // GPIO_027 + "WL_ON", // GPIO_028 + "BT_ON", // GPIO_029 + "WIFI_SDIO_CLK", // GPIO_030 + "WIFI_SDIO_CMD", // GPIO_031 + "WIFI_SDIO_D0", // GPIO_032 + "WIFI_SDIO_D1", // GPIO_033 + "WIFI_SDIO_D2", // GPIO_034 + "WIFI_SDIO_D3"; // GPIO_035 +}; + +&gio_aon { + gpio-line-names = + "RP1_SDA", // AON_GPIO_00 + "RP1_SCL", // AON_GPIO_01 + "RP1_RUN", // AON_GPIO_02 + "SD_IOVDD_SEL", // AON_GPIO_03 + "SD_PWR_ON", // AON_GPIO_04 + "ANT1", // AON_GPIO_05 + "ANT2", // AON_GPIO_06 + "-", // AON_GPIO_07 + "2712_WAKE", // AON_GPIO_08 + "2712_STAT_LED", // AON_GPIO_09 + "-", // AON_GPIO_10 + "-", // AON_GPIO_11 + "PMIC_INT", // AON_GPIO_12 + "UART_TX_FS", // AON_GPIO_13 + "UART_RX_FS", // AON_GPIO_14 + "-", // AON_GPIO_15 + "-", // AON_GPIO_16 + + // Pad bank0 out to 32 entries + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + + "HDMI0_SCL", // AON_SGPIO_00 + "HDMI0_SDA", // AON_SGPIO_01 + "HDMI1_SCL", // AON_SGPIO_02 + "HDMI1_SDA", // AON_SGPIO_03 + "PMIC_SCL", // AON_SGPIO_04 + "PMIC_SDA"; // AON_SGPIO_05 + + rp1_run_hog { + gpio-hog; + gpios = <2 GPIO_ACTIVE_HIGH>; + output-high; + line-name = "RP1 RUN pin"; + }; + + ant1: ant1-hog { + gpio-hog; + gpios = <5 GPIO_ACTIVE_HIGH>; + /* internal antenna enabled */ + output-high; + line-name = "ant1"; + }; + + ant2: ant2-hog { + gpio-hog; + gpios = <6 GPIO_ACTIVE_HIGH>; + /* external antenna disabled */ + output-low; + line-name = "ant2"; + }; +}; + +&rp1_gpio { + gpio-line-names = + "ID_SDA", // GPIO0 + "ID_SCL", // GPIO1 + "GPIO2", // GPIO2 + "GPIO3", // GPIO3 + "GPIO4", // GPIO4 + "GPIO5", // GPIO5 + "GPIO6", // GPIO6 + "GPIO7", // GPIO7 + "GPIO8", // GPIO8 + "GPIO9", // GPIO9 + "GPIO10", // GPIO10 + "GPIO11", // GPIO11 + "GPIO12", // GPIO12 + "GPIO13", // GPIO13 + "GPIO14", // GPIO14 + "GPIO15", // GPIO15 + "GPIO16", // GPIO16 + "GPIO17", // GPIO17 + "GPIO18", // GPIO18 + "GPIO19", // GPIO19 + "GPIO20", // GPIO20 + "GPIO21", // GPIO21 + "GPIO22", // GPIO22 + "GPIO23", // GPIO23 + "GPIO24", // GPIO24 + "GPIO25", // GPIO25 + "GPIO26", // GPIO26 + "GPIO27", // GPIO27 + + "PCIE_PWR_EN", // GPIO28 + "FAN_TACH", // GPIO29 + "HOST_SDA", // GPIO30 + "HOST_SCL", // GPIO31 + "ETH_RST_N", // GPIO32 + "PCIE_DET_WAKE", // GPIO33 + + "CD0_IO0_MICCLK", // GPIO34 + "CD0_IO0_MICDAT0", // GPIO35 + "RP1_PCIE_CLKREQ_N", // GPIO36 + "ETH_IRQ_N", // GPIO37 + "SDA0", // GPIO38 + "SCL0", // GPIO39 + "-", // GPIO40 + "-", // GPIO41 + "USB_VBUS_EN", // GPIO42 + "-", // GPIO43 + "RP1_STAT_LED", // GPIO44 + "FAN_PWM", // GPIO45 + "-", // GPIO46 + "2712_WAKE", // GPIO47 + "-", // GPIO48 + "-", // GPIO49 + "-", // GPIO50 + "-", // GPIO51 + "-", // GPIO52 + "-"; // GPIO53 + + usb_vbus_pins: usb_vbus_pins { + function = "vbus1"; + pins = "gpio42", "gpio43"; + }; + + micclk1_hog { + gpio-hog; + gpios = <46 GPIO_ACTIVE_HIGH>; + output-high; + }; + + micdat1_hog { + gpio-hog; + gpios = <48 GPIO_ACTIVE_HIGH>; + output-high; + }; +}; + +/ { + __overrides__ { + ant1 = <&ant1>,"output-high?=on", + <&ant1>, "output-low?=off", + <&ant2>, "output-high?=off", + <&ant2>, "output-low?=on"; + ant2 = <&ant1>,"output-high?=off", + <&ant1>, "output-low?=on", + <&ant2>, "output-high?=on", + <&ant2>, "output-low?=off"; + noant = <&ant1>,"output-high?=off", + <&ant1>, "output-low?=on", + <&ant2>, "output-high?=off", + <&ant2>, "output-low?=on"; + noanthogs = <&ant1>,"status=disabled", + <&ant2>, "status=disabled"; + sd_cqe = <&sdio1>, "supports-cqe:0"; + }; +}; diff --git a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5io.dtsi b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5io.dtsi new file mode 100644 index 00000000000000..4c616a21dc76b7 --- /dev/null +++ b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5io.dtsi @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0 + +i2c_csi_dsi1: &i2c0 { // Note: This is for CAM/DISP 1 connector + symlink = "i2c-11"; +}; + +i2c_csi_dsi0: &i2c6 { // Note: This is for CAM/DISP 0 connector + pinctrl-0 = <&rp1_i2c6_38_39>; + pinctrl-names = "default"; + clock-frequency = <100000>; + symlink = "i2c-6"; +}; + +i2c_csi_dsi: &i2c_csi_dsi1 { }; // The connector that needs no jumper to enable diff --git a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5l-cm4io.dts b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5l-cm4io.dts new file mode 100644 index 00000000000000..71259a673d999a --- /dev/null +++ b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5l-cm4io.dts @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: GPL-2.0 +/dts-v1/; + +#include "bcm2712-rpi-cm5l.dtsi" +#include "bcm2712-rpi-cm4io.dtsi" diff --git a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5l-cm5io.dts b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5l-cm5io.dts new file mode 100644 index 00000000000000..11a56dfb7b484b --- /dev/null +++ b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5l-cm5io.dts @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: GPL-2.0 +/dts-v1/; + +#include "bcm2712-rpi-cm5l.dtsi" +#include "bcm2712-rpi-cm5io.dtsi" diff --git a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5l.dtsi b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5l.dtsi new file mode 100644 index 00000000000000..6ba1a3efdd8706 --- /dev/null +++ b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5l.dtsi @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include "bcm2712-rpi-cm5.dtsi" + +/ { + model = "Raspberry Pi Compute Module 5 Lite"; +}; + +&sd_io_1v8_reg { + compatible = "regulator-gpio"; + regulator-max-microvolt = <3300000>; + regulator-settling-time-us = <5000>; + gpios = <&gio_aon 3 GPIO_ACTIVE_HIGH>; + states = <1800000 0x1 + 3300000 0x0>; +}; + +&sdio1 { + /delete-property/ mmc-hs400-1_8v; + /delete-property/ mmc-hs400-enhanced-strobe; +}; diff --git a/arch/arm64/boot/dts/broadcom/bcm2712-rpi.dtsi b/arch/arm64/boot/dts/broadcom/bcm2712-rpi.dtsi new file mode 100644 index 00000000000000..54a13e1f759764 --- /dev/null +++ b/arch/arm64/boot/dts/broadcom/bcm2712-rpi.dtsi @@ -0,0 +1,485 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <dt-bindings/power/raspberrypi-power.h> + +&soc { + firmware: firmware { + compatible = "raspberrypi,bcm2835-firmware", "simple-mfd"; + #address-cells = <1>; + #size-cells = <1>; + + mboxes = <&mailbox>; + dma-ranges; + + firmware_clocks: clocks { + compatible = "raspberrypi,firmware-clocks"; + #clock-cells = <1>; + }; + + reset: reset { + compatible = "raspberrypi,firmware-reset"; + #reset-cells = <1>; + }; + + vcio: vcio { + compatible = "raspberrypi,vcio"; + }; + }; + + power: power { + compatible = "raspberrypi,bcm2835-power"; + firmware = <&firmware>; + #power-domain-cells = <1>; + }; + + fb: fb { + compatible = "brcm,bcm2708-fb"; + firmware = <&firmware>; + status = "okay"; + }; + + rpi_rtc: rpi_rtc { + compatible = "raspberrypi,rpi-rtc"; + firmware = <&firmware>; + status = "okay"; + trickle-charge-microvolt = <0>; + }; + + nvmem { + compatible = "simple-bus"; + #address-cells = <1>; + #size-cells = <1>; + + nvmem_otp: nvmem_otp { + compatible = "raspberrypi,rpi-otp"; + firmware = <&firmware>; + reg = <0 192>; + status = "okay"; + }; + + nvmem_cust: nvmem_cust { + compatible = "raspberrypi,rpi-otp"; + firmware = <&firmware>; + reg = <1 8>; + status = "okay"; + }; + + nvmem_mac: nvmem_mac { + compatible = "raspberrypi,rpi-otp"; + firmware = <&firmware>; + reg = <2 6>; + status = "okay"; + }; + + nvmem_priv: nvmem_priv { + compatible = "raspberrypi,rpi-otp"; + firmware = <&firmware>; + reg = <3 16>; + status = "okay"; + }; + }; + + /* Define these notional regulators for use by overlays, etc. */ + vdd_3v3_reg: fixedregulator_3v3 { + compatible = "regulator-fixed"; + regulator-always-on; + regulator-max-microvolt = <3300000>; + regulator-min-microvolt = <3300000>; + regulator-name = "3v3"; + }; + + vdd_5v0_reg: fixedregulator_5v0 { + compatible = "regulator-fixed"; + regulator-always-on; + regulator-max-microvolt = <5000000>; + regulator-min-microvolt = <5000000>; + regulator-name = "5v0"; + }; +}; + +pio: &rp1_pio { + status = "okay"; +}; + +/ { + chosen: chosen { + bootargs = "reboot=w coherent_pool=1M 8250.nr_uarts=1 pci=pcie_bus_safe cgroup_disable=memory numa_policy=interleave"; + stdout-path = "serial10:115200n8"; + }; + + aliases: aliases { + blconfig = &blconfig; + blpubkey = &blpubkey; + bluetooth = &bluetooth; + console = &uart10; + drm-dsi1 = &dsi0; + drm-dsi2 = &dsi1; + ethernet0 = &rp1_eth; + fb = &fb; + gpio0 = &gpio; + gpio1 = &gio; + gpio2 = &gio_aon; + gpio3 = &pinctrl; + gpio4 = &pinctrl_aon; + gpiochip0 = &gpio; + gpiochip10 = &gio; + i2c = &i2c_arm; + i2c0 = &i2c0; + i2c1 = &i2c1; + i2c2 = &i2c2; + i2c3 = &i2c3; + i2c10 = &i2c_csi_dsi0; + i2c11 = &i2c_csi_dsi1; + i2c12 = &i2c10; + mailbox = &mailbox; + mmc0 = &sdio1; + pio0 = &pio; + serial0 = &uart0; + serial1 = &uart1; + serial10 = &uart10; + serial2 = &uart2; + serial3 = &uart3; + serial4 = &uart4; + spi0 = &spi0; + spi1 = &spi1; + spi10 = &spi10; + spi2 = &spi2; + spi3 = &spi3; + spi4 = &spi4; + spi5 = &spi5; + uart0 = &uart0; + uart1 = &uart1; + uart10 = &uart10; + uart2 = &uart2; + uart3 = &uart3; + uart4 = &uart4; + usb0 = &rp1_usb0; + usb1 = &rp1_usb1; + wifi0 = &wifi; + }; + + __overrides__ { + act_led_gpio = <&led_act>,"gpios:4",<&led_act>,"gpios:0=",<&gpio>; + act_led_activelow = <&led_act>, "gpios:8"; + act_led_trigger = <&led_act>, "linux,default-trigger"; + axiperf = <&axiperf>,"status"; + bdaddr = <&bluetooth>, "local-bd-address["; + button_debounce = <&pwr_key>, "debounce-interval:0"; + cooling_fan = <&fan>, "status", <&rp1_pwm1>, "status"; + drm_fb0_rp1_dpi = <&aliases>, "drm-fb0=",&dpi; + drm_fb0_rp1_dsi0 = <&aliases>, "drm-fb0=",&dsi0; + drm_fb0_rp1_dsi1 = <&aliases>, "drm-fb0=",&dsi1; + drm_fb0_vc4 = <&aliases>, "drm-fb0=",&vc4; + drm_fb1_rp1_dpi = <&aliases>, "drm-fb1=",&dpi; + drm_fb1_rp1_dsi0 = <&aliases>, "drm-fb1=",&dsi0; + drm_fb1_rp1_dsi1 = <&aliases>, "drm-fb1=",&dsi1; + drm_fb1_vc4 = <&aliases>, "drm-fb1=",&vc4; + drm_fb2_rp1_dpi = <&aliases>, "drm-fb2=",&dpi; + drm_fb2_rp1_dsi0 = <&aliases>, "drm-fb2=",&dsi0; + drm_fb2_rp1_dsi1 = <&aliases>, "drm-fb2=",&dsi1; + drm_fb2_vc4 = <&aliases>, "drm-fb2=",&vc4; + eth_led0 = <&phy1>,"led-modes:0"; + eth_led1 = <&phy1>,"led-modes:4"; + fan_temp0 = <&cpu_tepid>,"temperature:0"; + fan_temp0_hyst = <&cpu_tepid>,"hysteresis:0"; + fan_temp0_speed = <&fan>, "cooling-levels:4"; + fan_temp1 = <&cpu_warm>,"temperature:0"; + fan_temp1_hyst = <&cpu_warm>,"hysteresis:0"; + fan_temp1_speed = <&fan>, "cooling-levels:8"; + fan_temp2 = <&cpu_hot>,"temperature:0"; + fan_temp2_hyst = <&cpu_hot>,"hysteresis:0"; + fan_temp2_speed = <&fan>, "cooling-levels:12"; + fan_temp3 = <&cpu_vhot>,"temperature:0"; + fan_temp3_hyst = <&cpu_vhot>,"hysteresis:0"; + fan_temp3_speed = <&fan>, "cooling-levels:16"; + i2c = <&i2c1>, "status"; + i2c_arm = <&i2c_arm>, "status"; + i2c_arm_baudrate = <&i2c_arm>, "clock-frequency:0"; + i2c_baudrate = <&i2c_arm>, "clock-frequency:0"; + i2c_csi_dsi = <&i2c_csi_dsi>, "status"; + i2c_csi_dsi0 = <&i2c_csi_dsi0>, "status"; + i2c_csi_dsi1 = <&i2c_csi_dsi1>, "status"; + i2c_vc = <&i2c_vc>, "status"; + i2c_vc_baudrate = <&i2c_vc>, "clock-frequency:0"; + i2c0 = <&i2c0>, "status"; + i2c0_baudrate = <&i2c0>, "clock-frequency:0"; + i2c1 = <&i2c1>, "status"; + i2c1_baudrate = <&i2c1>, "clock-frequency:0"; + krnbt = <&bluetooth>, "status"; + nvme = <&pciex1>, "status"; + nvmem_cust_rw = <&nvmem_cust>,"rw?"; + nvmem_mac_rw = <&nvmem_mac>,"rw?"; + nvmem_priv_rw = <&nvmem_priv>,"rw?"; + pcie_tperst_clk_ms = <&pciex1>, "brcm,tperst-clk-ms:0"; + pciex1 = <&pciex1>, "status"; + pciex1_gen = <&pciex1> , "max-link-speed:0"; + pciex1_no_l0s = <&pciex1>, "aspm-no-l0s?"; + pciex1_tperst_clk_ms = <&pciex1>, "brcm,tperst-clk-ms:0"; + pwr_led_gpio = <&led_pwr>, "gpios:4"; + pwr_led_activelow = <&led_pwr>, "gpios:8"; + pwr_led_trigger = <&led_pwr>, "linux,default-trigger"; + random = <&random>, "status"; + rtc = <&rpi_rtc>, "status"; + rtc_bbat_vchg = <&rpi_rtc>, "trickle-charge-microvolt:0"; + spi = <&spi0>, "status"; + strict_gpiod = <&chosen>, "bootargs=pinctrl_rp1.persist_gpio_outputs=n"; + suspend = <&pwr_key>, "linux,code:0=205"; + uart0 = <&uart0>, "status"; + uart0_console = <&uart0>,"status", <&aliases>, "console=",&uart0; + wifiaddr = <&wifi>, "local-mac-address["; + + cam0_reg = <&cam0_reg>,"status"; + cam0_reg_gpio = <&cam0_reg>,"gpio:4", + <&cam0_reg>,"gpio:0=", <&gpio>; + cam1_reg = <&cam1_reg>,"status"; + cam1_reg_gpio = <&cam1_reg>,"gpio:4", + <&cam1_reg>,"gpio:0=", <&gpio>; + }; +}; + +pciex1: &pcie1 { }; +pciex4: &pcie2 { }; + +&dma32 { + /* The VPU firmware uses DMA channel 11 for VCHIQ */ + brcm,dma-channel-mask = <0x03f>; +}; + +&dma40 { + /* The VPU firmware DMA channel 11 for VCHIQ */ + brcm,dma-channel-mask = <0x07c0>; +}; + +&hdmi0 { + dmas = <&dma40 (10|(1<<30)|(1<<24)|(10<<16)|(15<<20))>; + dma-names = "audio-rx"; +}; + +&hdmi1 { + dmas = <&dma40 (17|(1<<30)|(1<<24)|(10<<16)|(15<<20))>; + dma-names = "audio-rx"; +}; + +&spi10 { + dmas = <&dma40 6>, <&dma40 7>; + dma-names = "tx", "rx"; +}; + +&usb { + power-domains = <&power RPI_POWER_DOMAIN_USB>; +}; + +&rmem { + /* + * RPi5's co-processor will copy the board's bootloader configuration + * into memory for the OS to consume. It'll also update this node with + * its placement information. + */ + blconfig: nvram@0 { + compatible = "raspberrypi,bootloader-config", "nvmem-rmem"; + #address-cells = <1>; + #size-cells = <1>; +#ifndef FIRMWARE_UPDATED + reg = <0x0 0x0 0x0>; +#else + reg = <0x0 0x0 0x0 0x0>; +#endif + no-map; + status = "disabled"; + }; + /* + * RPi5 will copy the binary public key blob (if present) from the bootloader + * into memory for use by the OS. + */ + blpubkey: nvram@1 { + compatible = "raspberrypi,bootloader-public-key", "nvmem-rmem"; + #address-cells = <1>; + #size-cells = <1>; +#ifndef FIRMWARE_UPDATED + reg = <0x0 0x0 0x0>; +#else + reg = <0x0 0x0 0x0 0x0>; +#endif + no-map; + status = "disabled"; + }; +}; + +&rp1_adc { + status = "okay"; +}; + +&rp1_mbox { + status = "okay"; +}; + +/* Add some gpiomem nodes to make the devices accessible to userspace. + * /dev/gpiomem<n> should expose the registers for the interface with DT alias + * gpio<n>. + */ + +&rp1 { + gpiomem@d0000 { + /* Export IO_BANKs, RIO_BANKs and PADS_BANKs to userspace */ + compatible = "raspberrypi,gpiomem"; + reg = <0xc0 0x400d0000 0x0 0x30000>; + chardev-name = "gpiomem0"; + }; +}; + +&soc { + gpiomem@7d508500 { + compatible = "raspberrypi,gpiomem"; + reg = <0x7d508500 0x40>; + chardev-name = "gpiomem1"; + }; + + gpiomem@7d517c00 { + compatible = "raspberrypi,gpiomem"; + reg = <0x7d517c00 0x40>; + chardev-name = "gpiomem2"; + }; + + gpiomem@7d504100 { + compatible = "raspberrypi,gpiomem"; + reg = <0x7d504100 0x20>; + chardev-name = "gpiomem3"; + }; + + gpiomem@7d510700 { + compatible = "raspberrypi,gpiomem"; + reg = <0x7d510700 0x20>; + chardev-name = "gpiomem4"; + }; + + sound: sound { + status = "disabled"; + }; +}; + +i2c0: &rp1_i2c0 { }; +i2c1: &rp1_i2c1 { }; +i2c2: &rp1_i2c2 { }; +i2c3: &rp1_i2c3 { }; +i2c4: &rp1_i2c4 { }; +i2c5: &rp1_i2c5 { }; +i2c6: &rp1_i2c6 { }; +i2s: &rp1_i2s0 { }; +i2s_clk_producer: &rp1_i2s0 { }; +i2s_clk_consumer: &rp1_i2s1 { }; +pwm0: &rp1_pwm0 { }; +pwm1: &rp1_pwm1 { }; +pwm: &pwm0 { }; +spi0: &rp1_spi0 { }; +spi1: &rp1_spi1 { }; +spi2: &rp1_spi2 { }; +spi3: &rp1_spi3 { }; +spi4: &rp1_spi4 { }; +spi5: &rp1_spi5 { }; + +uart0_pins: &rp1_uart0_14_15 {}; +uart0_ctsrts_pins: &rp1_uart0_ctsrts_16_17 {}; +uart0: &rp1_uart0 { + pinctrl-0 = <&uart0_pins>; +}; + +uart1_pins: &rp1_uart1_0_1 {}; +uart1_ctsrts_pins: &rp1_uart1_ctsrts_2_3 {}; +uart1: &rp1_uart1 { }; + +uart2_pins: &rp1_uart2_4_5 {}; +uart2_ctsrts_pins: &rp1_uart2_ctsrts_6_7 {}; +uart2: &rp1_uart2 { }; + +uart3_pins: &rp1_uart3_8_9 {}; +uart3_ctsrts_pins: &rp1_uart3_ctsrts_10_11 {}; +uart3: &rp1_uart3 { }; + +uart4_pins: &rp1_uart4_12_13 {}; +uart4_ctsrts_pins: &rp1_uart4_ctsrts_14_15 {}; +uart4: &rp1_uart4 { }; + +i2c0_pins: &rp1_i2c0_0_1 {}; +i2c_vc: &i2c0 { // This is pins 27,28 on the header (not MIPI) + pinctrl-0 = <&i2c0_pins>; + pinctrl-names = "default"; + clock-frequency = <100000>; +}; + +i2c1_pins: &rp1_i2c1_2_3 {}; +i2c_arm: &i2c1 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c1_pins>; + clock-frequency = <100000>; +}; + +i2c2_pins: &rp1_i2c2_4_5 {}; +&i2c2 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c2_pins>; +}; + +i2c3_pins: &rp1_i2c3_6_7 {}; +&i2c3 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c3_pins>; +}; + +&i2s_clk_producer { + pinctrl-names = "default"; + pinctrl-0 = <&rp1_i2s0_18_21>; +}; + +&i2s_clk_consumer { + pinctrl-names = "default"; + pinctrl-0 = <&rp1_i2s1_18_21>; +}; + +spi0_pins: &rp1_spi0_gpio9 {}; +spi0_cs_pins: &rp1_spi0_cs_gpio7 {}; + +&spi0 { + pinctrl-names = "default"; + pinctrl-0 = <&spi0_pins &spi0_cs_pins>; + cs-gpios = <&gpio 8 1>, <&gpio 7 1>; + + spidev0: spidev@0 { + compatible = "spidev"; + reg = <0>; /* CE0 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + }; + + spidev1: spidev@1 { + compatible = "spidev"; + reg = <1>; /* CE1 */ + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <125000000>; + }; +}; + +spi2_pins: &rp1_spi2_gpio1 {}; +&spi2 { + pinctrl-names = "default"; + pinctrl-0 = <&spi2_pins>; +}; + +spi3_pins: &rp1_spi3_gpio5 {}; +&spi3 { + pinctrl-names = "default"; + pinctrl-0 = <&spi3_pins>; +}; + +spi4_pins: &rp1_spi4_gpio9 {}; +&spi4 { + pinctrl-names = "default"; + pinctrl-0 = <&spi4_pins>; +}; + +spi5_pins: &rp1_spi5_gpio13 {}; +&spi5 { + pinctrl-names = "default"; + pinctrl-0 = <&spi5_pins>; +}; diff --git a/arch/arm64/boot/dts/broadcom/bcm2712.dtsi b/arch/arm64/boot/dts/broadcom/bcm2712.dtsi index 26a29e5e5078d5..5d8626926d0671 100644 --- a/arch/arm64/boot/dts/broadcom/bcm2712.dtsi +++ b/arch/arm64/boot/dts/broadcom/bcm2712.dtsi @@ -265,6 +265,172 @@ interrupt-controller; #interrupt-cells = <3>; }; + + aon_intr: interrupt-controller@7d510600 { + compatible = "brcm,bcm2711-l2-intc", "brcm,l2-intc"; + reg = <0x7d510600 0x30>; + interrupts = <GIC_SPI 239 IRQ_TYPE_LEVEL_HIGH>; + interrupt-controller; + #interrupt-cells = <1>; + }; + + pixelvalve0: pixelvalve@7c410000 { + compatible = "brcm,bcm2712-pixelvalve0"; + reg = <0x7c410000 0x100>; + interrupts = <GIC_SPI 101 IRQ_TYPE_LEVEL_HIGH>; + }; + + pixelvalve1: pixelvalve@7c411000 { + compatible = "brcm,bcm2712-pixelvalve1"; + reg = <0x7c411000 0x100>; + interrupts = <GIC_SPI 110 IRQ_TYPE_LEVEL_HIGH>; + }; + + mop: mop@7c500000 { + compatible = "brcm,bcm2712-mop"; + reg = <0x7c500000 0x28>; + interrupt-parent = <&disp_intr>; + interrupts = <1>; + }; + + moplet: moplet@7c501000 { + compatible = "brcm,bcm2712-moplet"; + reg = <0x7c501000 0x20>; + interrupt-parent = <&disp_intr>; + interrupts = <0>; + }; + + disp_intr: interrupt-controller@7c502000 { + compatible = "brcm,bcm2711-l2-intc", "brcm,l2-intc"; + reg = <0x7c502000 0x30>; + interrupts = <GIC_SPI 97 IRQ_TYPE_LEVEL_HIGH>; + interrupt-controller; + #interrupt-cells = <1>; + }; + + dvp: clock@7c700000 { + compatible = "brcm,brcm2711-dvp"; + reg = <0x7c700000 0x10>; + clocks = <&clk_108MHz>; + #clock-cells = <1>; + #reset-cells = <1>; + }; + + ddc0: i2c@7d508200 { + compatible = "brcm,brcmstb-i2c"; + reg = <0x7d508200 0x58>; + interrupt-parent = <&bsc_irq>; + interrupts = <1>; + clock-frequency = <97500>; + #address-cells = <1>; + #size-cells = <0>; + }; + + ddc1: i2c@7d508280 { + compatible = "brcm,brcmstb-i2c"; + reg = <0x7d508280 0x58>; + interrupt-parent = <&bsc_irq>; + interrupts = <2>; + clock-frequency = <97500>; + #address-cells = <1>; + #size-cells = <0>; + }; + + bsc_irq: intc@7d508380 { + compatible = "brcm,bcm7271-l2-intc"; + reg = <0x7d508380 0x10>; + interrupts = <GIC_SPI 242 IRQ_TYPE_LEVEL_HIGH>; + interrupt-controller; + #interrupt-cells = <1>; + }; + + main_irq: intc@7d508400 { + compatible = "brcm,bcm7271-l2-intc"; + reg = <0x7d508400 0x10>; + interrupts = <GIC_SPI 244 IRQ_TYPE_LEVEL_HIGH>; + interrupt-controller; + #interrupt-cells = <1>; + }; + + hdmi0: hdmi@7ef00700 { + compatible = "brcm,bcm2712-hdmi0"; + reg = <0x7c701400 0x300>, + <0x7c701000 0x200>, + <0x7c701d00 0x300>, + <0x7c702000 0x80>, + <0x7c703800 0x200>, + <0x7c704000 0x800>, + <0x7c700100 0x80>, + <0x7d510800 0x100>, + <0x7c720000 0x100>; + reg-names = "hdmi", + "dvp", + "phy", + "rm", + "packet", + "metadata", + "csc", + "cec", + "hd"; + resets = <&dvp 1>; + interrupt-parent = <&aon_intr>; + interrupts = <1>, <2>, <3>, + <7>, <8>; + interrupt-names = "cec-tx", "cec-rx", "cec-low", + "hpd-connected", "hpd-removed"; + ddc = <&ddc0>; + }; + + hdmi1: hdmi@7ef05700 { + compatible = "brcm,bcm2712-hdmi1"; + reg = <0x7c706400 0x300>, + <0x7c706000 0x200>, + <0x7c706d00 0x300>, + <0x7c707000 0x80>, + <0x7c708800 0x200>, + <0x7c709000 0x800>, + <0x7c700180 0x80>, + <0x7d511000 0x100>, + <0x7c720000 0x100>; + reg-names = "hdmi", + "dvp", + "phy", + "rm", + "packet", + "metadata", + "csc", + "cec", + "hd"; + resets = <&dvp 2>; + interrupt-parent = <&aon_intr>; + interrupts = <11>, <12>, <13>, + <14>, <15>; + interrupt-names = "cec-tx", "cec-rx", "cec-low", + "hpd-connected", "hpd-removed"; + ddc = <&ddc1>; + }; + }; + + axi: axi { + compatible = "simple-bus"; + #address-cells = <2>; + #size-cells = <2>; + + ranges = <0x00 0x00000000 0x00 0x00000000 0x10 0x00000000>, + <0x10 0x00000000 0x10 0x00000000 0x01 0x00000000>, + <0x14 0x00000000 0x14 0x00000000 0x04 0x00000000>, + <0x18 0x00000000 0x18 0x00000000 0x04 0x00000000>, + <0x1c 0x00000000 0x1c 0x00000000 0x04 0x00000000>; + + dma-ranges = <0x00 0x00000000 0x00 0x00000000 0x10 0x00000000>, + <0x10 0x00000000 0x10 0x00000000 0x01 0x00000000>, + <0x14 0x00000000 0x14 0x00000000 0x04 0x00000000>, + <0x18 0x00000000 0x18 0x00000000 0x04 0x00000000>, + <0x1c 0x00000000 0x1c 0x00000000 0x04 0x00000000>; + + vc4: gpu { + compatible = "brcm,bcm2712-vc6"; + }; }; timer { @@ -280,4 +446,31 @@ <GIC_PPI 12 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>; }; + + clk_27MHz: clk-27M { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <27000000>; + clock-output-names = "27MHz-clock"; + }; + + clk_108MHz: clk-108M { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <108000000>; + clock-output-names = "108MHz-clock"; + }; + + hvs: hvs@107c580000 { + compatible = "brcm,bcm2712-hvs"; +#ifndef FIRMWARE_UPDATED + reg = <0x10 0x7c580000 0x1a000>; +#else + reg = <0x10 0x7c580000 0x0 0x1a000>; +#endif + interrupt-parent = <&disp_intr>; + interrupts = <2>, <9>, <16>; + interrupt-names = "ch0-eof", "ch1-eof", "ch2-eof"; + iommus = <&iommu4>; + }; }; diff --git a/arch/arm64/boot/dts/broadcom/bcm2712d0-rpi-5-b.dts b/arch/arm64/boot/dts/broadcom/bcm2712d0-rpi-5-b.dts new file mode 100644 index 00000000000000..d06536bc7592ed --- /dev/null +++ b/arch/arm64/boot/dts/broadcom/bcm2712d0-rpi-5-b.dts @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "bcm2712-rpi-5-b.dts" + +&gio { + brcm,gpio-bank-widths = <32 4>; + + gpio-line-names = + "", // GPIO_000 + "2712_BOOT_CS_N", // GPIO_001 + "2712_BOOT_MISO", // GPIO_002 + "2712_BOOT_MOSI", // GPIO_003 + "2712_BOOT_SCLK", // GPIO_004 + "", // GPIO_005 + "", // GPIO_006 + "", // GPIO_007 + "", // GPIO_008 + "", // GPIO_009 + "-", // GPIO_010 + "-", // GPIO_011 + "-", // GPIO_012 + "-", // GPIO_013 + "PCIE_SDA", // GPIO_014 + "PCIE_SCL", // GPIO_015 + "", // GPIO_016 + "", // GPIO_017 + "-", // GPIO_018 + "-", // GPIO_019 + "PWR_GPIO", // GPIO_020 + "2712_G21_FS", // GPIO_021 + "-", // GPIO_022 + "-", // GPIO_023 + "BT_RTS", // GPIO_024 + "BT_CTS", // GPIO_025 + "BT_TXD", // GPIO_026 + "BT_RXD", // GPIO_027 + "WL_ON", // GPIO_028 + "BT_ON", // GPIO_029 + "WIFI_SDIO_CLK", // GPIO_030 + "WIFI_SDIO_CMD", // GPIO_031 + "WIFI_SDIO_D0", // GPIO_032 + "WIFI_SDIO_D1", // GPIO_033 + "WIFI_SDIO_D2", // GPIO_034 + "WIFI_SDIO_D3"; // GPIO_035 +}; + +&gio_aon { + brcm,gpio-bank-widths = <15 6>; + + gpio-line-names = + "RP1_SDA", // AON_GPIO_00 + "RP1_SCL", // AON_GPIO_01 + "RP1_RUN", // AON_GPIO_02 + "SD_IOVDD_SEL", // AON_GPIO_03 + "SD_PWR_ON", // AON_GPIO_04 + "SD_CDET_N", // AON_GPIO_05 + "SD_FLG_N", // AON_GPIO_06 + "", // AON_GPIO_07 + "2712_WAKE", // AON_GPIO_08 + "2712_STAT_LED", // AON_GPIO_09 + "", // AON_GPIO_10 + "", // AON_GPIO_11 + "PMIC_INT", // AON_GPIO_12 + "UART_TX_FS", // AON_GPIO_13 + "UART_RX_FS", // AON_GPIO_14 + "", // AON_GPIO_15 + "", // AON_GPIO_16 + + // Pad bank0 out to 32 entries + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + + "HDMI0_SCL", // AON_SGPIO_00 + "HDMI0_SDA", // AON_SGPIO_01 + "HDMI1_SCL", // AON_SGPIO_02 + "HDMI1_SDA", // AON_SGPIO_03 + "PMIC_SCL", // AON_SGPIO_04 + "PMIC_SDA"; // AON_SGPIO_05 +}; + +&pinctrl { + compatible = "brcm,bcm2712d0-pinctrl"; + reg = <0x7d504100 0x20>; +}; + +&pinctrl_aon { + compatible = "brcm,bcm2712d0-aon-pinctrl"; + reg = <0x7d510700 0x1c>; +}; + +&vc4 { + compatible = "brcm,bcm2712d0-vc6", "brcm,bcm2712-vc6"; +}; + +&uart10 { + interrupts = <GIC_SPI 120 IRQ_TYPE_LEVEL_HIGH>; +}; + +&spi10 { + dmas = <&dma40 3>, <&dma40 4>; +}; + +&hdmi0 { + dmas = <&dma40 (12|(1<<30)|(1<<24)|(10<<16)|(15<<20))>; +}; + +&hdmi1 { + dmas = <&dma40 (13|(1<<30)|(1<<24)|(10<<16)|(15<<20))>; +}; diff --git a/arch/arm64/boot/dts/broadcom/rp1.dtsi b/arch/arm64/boot/dts/broadcom/rp1.dtsi new file mode 100644 index 00000000000000..3a798aa31dde92 --- /dev/null +++ b/arch/arm64/boot/dts/broadcom/rp1.dtsi @@ -0,0 +1,1343 @@ +#include <dt-bindings/clock/rp1.h> +#include <dt-bindings/interrupt-controller/irq.h> +#include <dt-bindings/mfd/rp1.h> + +&rp1_target { + rp1: rp1 { + compatible = "simple-bus"; + #address-cells = <2>; + #size-cells = <2>; + #interrupt-cells = <2>; + interrupt-controller; + interrupt-parent = <&rp1>; + + // ranges and dma-ranges must be provided by the includer + + rp1_mbox: mailbox@8000 { + compatible = "raspberrypi,rp1-mbox"; + status = "disabled"; + reg = <0xc0 0x40008000 0x0 0x4000>; // SYSCFG + interrupts = <RP1_INT_SYSCFG IRQ_TYPE_LEVEL_HIGH>; + #mbox-cells = <1>; + }; + + rp1_clocks: clocks@18000 { + compatible = "raspberrypi,rp1-clocks"; + #clock-cells = <1>; + reg = <0xc0 0x40018000 0x0 0x10038>; + clocks = <&clk_xosc>; + + assigned-clocks = <&rp1_clocks RP1_PLL_SYS_CORE>, + <&rp1_clocks RP1_PLL_AUDIO_CORE>, + // RP1_PLL_VIDEO_CORE and dividers are now managed by VEC,DPI drivers + <&rp1_clocks RP1_PLL_SYS>, + <&rp1_clocks RP1_PLL_SYS_SEC>, + <&rp1_clocks RP1_CLK_ETH>, + <&rp1_clocks RP1_PLL_AUDIO>, + <&rp1_clocks RP1_PLL_AUDIO_SEC>, + <&rp1_clocks RP1_CLK_SYS>, + <&rp1_clocks RP1_PLL_SYS_PRI_PH>, + // RP1_CLK_SLOW_SYS is used for the frequency counter (FC0) + <&rp1_clocks RP1_CLK_SLOW_SYS>, + <&rp1_clocks RP1_CLK_SDIO_TIMER>, + <&rp1_clocks RP1_CLK_SDIO_ALT_SRC>, + <&rp1_clocks RP1_CLK_ETH_TSU>; + + assigned-clock-rates = <1000000000>, // RP1_PLL_SYS_CORE + <1536000000>, // RP1_PLL_AUDIO_CORE + <200000000>, // RP1_PLL_SYS + <125000000>, // RP1_PLL_SYS_SEC + <125000000>, // RP1_CLK_ETH + <61440000>, // RP1_PLL_AUDIO + <153600000>, // RP1_PLL_AUDIO_SEC + <200000000>, // RP1_CLK_SYS + <100000000>, // RP1_PLL_SYS_PRI_PH + // Must match the XOSC frequency + <50000000>, // RP1_CLK_SLOW_SYS + <1000000>, // RP1_CLK_SDIO_TIMER + <200000000>, // RP1_CLK_SDIO_ALT_SRC + <50000000>; // RP1_CLK_ETH_TSU + }; + + rp1_uart0: serial@30000 { + compatible = "arm,pl011-axi"; + reg = <0xc0 0x40030000 0x0 0x100>; + interrupts = <RP1_INT_UART0 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&rp1_clocks RP1_CLK_UART &rp1_clocks RP1_PLL_SYS_PRI_PH>; + clock-names = "uartclk", "apb_pclk"; + dmas = <&rp1_dma RP1_DMA_UART0_TX>, + <&rp1_dma RP1_DMA_UART0_RX>; + dma-names = "tx", "rx"; + pinctrl-names = "default"; + arm,primecell-periphid = <0x00341011>; + uart-has-rtscts; + cts-event-workaround; + skip-init; + status = "disabled"; + }; + + rp1_uart1: serial@34000 { + compatible = "arm,pl011-axi"; + reg = <0xc0 0x40034000 0x0 0x100>; + interrupts = <RP1_INT_UART1 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&rp1_clocks RP1_CLK_UART &rp1_clocks RP1_PLL_SYS_PRI_PH>; + clock-names = "uartclk", "apb_pclk"; + // dmas = <&rp1_dma RP1_DMA_UART1_TX>, + // <&rp1_dma RP1_DMA_UART1_RX>; + // dma-names = "tx", "rx"; + pinctrl-names = "default"; + arm,primecell-periphid = <0x00341011>; + uart-has-rtscts; + cts-event-workaround; + skip-init; + status = "disabled"; + }; + + rp1_uart2: serial@38000 { + compatible = "arm,pl011-axi"; + reg = <0xc0 0x40038000 0x0 0x100>; + interrupts = <RP1_INT_UART2 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&rp1_clocks RP1_CLK_UART &rp1_clocks RP1_PLL_SYS_PRI_PH>; + clock-names = "uartclk", "apb_pclk"; + // dmas = <&rp1_dma RP1_DMA_UART2_TX>, + // <&rp1_dma RP1_DMA_UART2_RX>; + // dma-names = "tx", "rx"; + pinctrl-names = "default"; + arm,primecell-periphid = <0x00341011>; + uart-has-rtscts; + cts-event-workaround; + skip-init; + status = "disabled"; + }; + + rp1_uart3: serial@3c000 { + compatible = "arm,pl011-axi"; + reg = <0xc0 0x4003c000 0x0 0x100>; + interrupts = <RP1_INT_UART3 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&rp1_clocks RP1_CLK_UART &rp1_clocks RP1_PLL_SYS_PRI_PH>; + clock-names = "uartclk", "apb_pclk"; + // dmas = <&rp1_dma RP1_DMA_UART3_TX>, + // <&rp1_dma RP1_DMA_UART3_RX>; + // dma-names = "tx", "rx"; + pinctrl-names = "default"; + arm,primecell-periphid = <0x00341011>; + uart-has-rtscts; + cts-event-workaround; + skip-init; + status = "disabled"; + }; + + rp1_uart4: serial@40000 { + compatible = "arm,pl011-axi"; + reg = <0xc0 0x40040000 0x0 0x100>; + interrupts = <RP1_INT_UART4 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&rp1_clocks RP1_CLK_UART &rp1_clocks RP1_PLL_SYS_PRI_PH>; + clock-names = "uartclk", "apb_pclk"; + // dmas = <&rp1_dma RP1_DMA_UART4_TX>, + // <&rp1_dma RP1_DMA_UART4_RX>; + // dma-names = "tx", "rx"; + pinctrl-names = "default"; + arm,primecell-periphid = <0x00341011>; + uart-has-rtscts; + cts-event-workaround; + skip-init; + status = "disabled"; + }; + + rp1_uart5: serial@44000 { + compatible = "arm,pl011-axi"; + reg = <0xc0 0x40044000 0x0 0x100>; + interrupts = <RP1_INT_UART5 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&rp1_clocks RP1_CLK_UART &rp1_clocks RP1_PLL_SYS_PRI_PH>; + clock-names = "uartclk", "apb_pclk"; + // dmas = <&rp1_dma RP1_DMA_UART5_TX>, + // <&rp1_dma RP1_DMA_UART5_RX>; + // dma-names = "tx", "rx"; + pinctrl-names = "default"; + arm,primecell-periphid = <0x00341011>; + uart-has-rtscts; + cts-event-workaround; + skip-init; + status = "disabled"; + }; + + rp1_spi8: spi@4c000 { + reg = <0xc0 0x4004c000 0x0 0x130>; + compatible = "snps,dw-apb-ssi"; + interrupts = <RP1_INT_SPI8 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&rp1_clocks RP1_CLK_SYS>; + clock-names = "ssi_clk"; + #address-cells = <1>; + #size-cells = <0>; + num-cs = <2>; + dmas = <&rp1_dma RP1_DMA_SPI8_TX>, + <&rp1_dma RP1_DMA_SPI8_RX>; + dma-names = "tx", "rx"; + status = "disabled"; + }; + + rp1_spi0: spi@50000 { + reg = <0xc0 0x40050000 0x0 0x130>; + compatible = "snps,dw-apb-ssi"; + interrupts = <RP1_INT_SPI0 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&rp1_clocks RP1_CLK_SYS>; + clock-names = "ssi_clk"; + #address-cells = <1>; + #size-cells = <0>; + num-cs = <2>; + dmas = <&rp1_dma RP1_DMA_SPI0_TX>, + <&rp1_dma RP1_DMA_SPI0_RX>; + dma-names = "tx", "rx"; + status = "disabled"; + }; + + rp1_spi1: spi@54000 { + reg = <0xc0 0x40054000 0x0 0x130>; + compatible = "snps,dw-apb-ssi"; + interrupts = <RP1_INT_SPI1 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&rp1_clocks RP1_CLK_SYS>; + clock-names = "ssi_clk"; + #address-cells = <1>; + #size-cells = <0>; + num-cs = <2>; + dmas = <&rp1_dma RP1_DMA_SPI1_TX>, + <&rp1_dma RP1_DMA_SPI1_RX>; + dma-names = "tx", "rx"; + status = "disabled"; + }; + + rp1_spi2: spi@58000 { + reg = <0xc0 0x40058000 0x0 0x130>; + compatible = "snps,dw-apb-ssi"; + interrupts = <RP1_INT_SPI2 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&rp1_clocks RP1_CLK_SYS>; + clock-names = "ssi_clk"; + #address-cells = <1>; + #size-cells = <0>; + num-cs = <2>; + dmas = <&rp1_dma RP1_DMA_SPI2_TX>, + <&rp1_dma RP1_DMA_SPI2_RX>; + dma-names = "tx", "rx"; + status = "disabled"; + }; + + rp1_spi3: spi@5c000 { + reg = <0xc0 0x4005c000 0x0 0x130>; + compatible = "snps,dw-apb-ssi"; + interrupts = <RP1_INT_SPI3 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&rp1_clocks RP1_CLK_SYS>; + clock-names = "ssi_clk"; + #address-cells = <1>; + #size-cells = <0>; + num-cs = <2>; + dmas = <&rp1_dma RP1_DMA_SPI3_TX>, + <&rp1_dma RP1_DMA_SPI3_RX>; + dma-names = "tx", "rx"; + status = "disabled"; + }; + + // SPI4 is a target/slave interface + rp1_spi4: spi@60000 { + reg = <0xc0 0x40060000 0x0 0x130>; + compatible = "snps,dw-apb-ssi"; + interrupts = <RP1_INT_SPI4 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&rp1_clocks RP1_CLK_SYS>; + clock-names = "ssi_clk"; + #address-cells = <0>; + #size-cells = <0>; + num-cs = <1>; + spi-slave; + dmas = <&rp1_dma RP1_DMA_SPI4_TX>, + <&rp1_dma RP1_DMA_SPI4_RX>; + dma-names = "tx", "rx"; + status = "disabled"; + + slave { + compatible = "spidev"; + spi-max-frequency = <1000000>; + }; + }; + + rp1_spi5: spi@64000 { + reg = <0xc0 0x40064000 0x0 0x130>; + compatible = "snps,dw-apb-ssi"; + interrupts = <RP1_INT_SPI5 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&rp1_clocks RP1_CLK_SYS>; + clock-names = "ssi_clk"; + #address-cells = <1>; + #size-cells = <0>; + num-cs = <2>; + dmas = <&rp1_dma RP1_DMA_SPI5_TX>, + <&rp1_dma RP1_DMA_SPI5_RX>; + dma-names = "tx", "rx"; + status = "disabled"; + }; + + rp1_spi6: spi@68000 { + reg = <0xc0 0x40068000 0x0 0x130>; + compatible = "snps,dw-apb-ssi"; + interrupts = <RP1_INT_SPI6 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&rp1_clocks RP1_CLK_SYS>; + clock-names = "ssi_clk"; + #address-cells = <1>; + #size-cells = <0>; + num-cs = <2>; + dmas = <&rp1_dma RP1_DMA_SPI6_TX>, + <&rp1_dma RP1_DMA_SPI6_RX>; + dma-names = "tx", "rx"; + status = "disabled"; + }; + + // SPI7 is a target/slave interface + rp1_spi7: spi@6c000 { + reg = <0xc0 0x4006c000 0x0 0x130>; + compatible = "snps,dw-apb-ssi"; + interrupts = <RP1_INT_SPI7 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&rp1_clocks RP1_CLK_SYS>; + clock-names = "ssi_clk"; + #address-cells = <0>; + #size-cells = <0>; + num-cs = <1>; + spi-slave; + dmas = <&rp1_dma RP1_DMA_SPI7_TX>, + <&rp1_dma RP1_DMA_SPI7_RX>; + dma-names = "tx", "rx"; + status = "disabled"; + + slave { + compatible = "spidev"; + spi-max-frequency = <1000000>; + }; + }; + + rp1_i2c0: i2c@70000 { + reg = <0xc0 0x40070000 0x0 0x1000>; + compatible = "snps,designware-i2c"; + interrupts = <RP1_INT_I2C0 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&rp1_clocks RP1_CLK_SYS>; + i2c-scl-rising-time-ns = <65>; + i2c-scl-falling-time-ns = <100>; + status = "disabled"; + }; + + rp1_i2c1: i2c@74000 { + reg = <0xc0 0x40074000 0x0 0x1000>; + compatible = "snps,designware-i2c"; + interrupts = <RP1_INT_I2C1 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&rp1_clocks RP1_CLK_SYS>; + i2c-scl-rising-time-ns = <65>; + i2c-scl-falling-time-ns = <100>; + status = "disabled"; + }; + + rp1_i2c2: i2c@78000 { + reg = <0xc0 0x40078000 0x0 0x1000>; + compatible = "snps,designware-i2c"; + interrupts = <RP1_INT_I2C2 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&rp1_clocks RP1_CLK_SYS>; + i2c-scl-rising-time-ns = <65>; + i2c-scl-falling-time-ns = <100>; + status = "disabled"; + }; + + rp1_i2c3: i2c@7c000 { + reg = <0xc0 0x4007c000 0x0 0x1000>; + compatible = "snps,designware-i2c"; + interrupts = <RP1_INT_I2C3 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&rp1_clocks RP1_CLK_SYS>; + i2c-scl-rising-time-ns = <65>; + i2c-scl-falling-time-ns = <100>; + status = "disabled"; + }; + + rp1_i2c4: i2c@80000 { + reg = <0xc0 0x40080000 0x0 0x1000>; + compatible = "snps,designware-i2c"; + interrupts = <RP1_INT_I2C4 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&rp1_clocks RP1_CLK_SYS>; + i2c-scl-rising-time-ns = <65>; + i2c-scl-falling-time-ns = <100>; + status = "disabled"; + }; + + rp1_i2c5: i2c@84000 { + reg = <0xc0 0x40084000 0x0 0x1000>; + compatible = "snps,designware-i2c"; + interrupts = <RP1_INT_I2C5 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&rp1_clocks RP1_CLK_SYS>; + i2c-scl-rising-time-ns = <65>; + i2c-scl-falling-time-ns = <100>; + status = "disabled"; + }; + + rp1_i2c6: i2c@88000 { + reg = <0xc0 0x40088000 0x0 0x1000>; + compatible = "snps,designware-i2c"; + interrupts = <RP1_INT_I2C6 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&rp1_clocks RP1_CLK_SYS>; + i2c-scl-rising-time-ns = <65>; + i2c-scl-falling-time-ns = <100>; + status = "disabled"; + }; + + rp1_audio_out: audio_out@94000 { + compatible = "raspberrypi,rp1-audio-out"; + reg = <0xc0 0x40094000 0x0 0x4000>; + clocks = <&rp1_clocks RP1_CLK_AUDIO_OUT>; + assigned-clocks = <&rp1_clocks RP1_CLK_AUDIO_OUT>; + assigned-clock-rates = <153600000>; + assigned-clock-parents = <&rp1_clocks RP1_PLL_AUDIO_SEC>; + dmas = <&rp1_dma RP1_DMA_AUDIO_OUT>; + dma-maxburst = <4>; + dma-names = "tx"; + #sound-dai-cells = <0>; + status = "disabled"; + }; + + rp1_pwm0: pwm@98000 { + compatible = "raspberrypi,rp1-pwm"; + reg = <0xc0 0x40098000 0x0 0x100>; + #pwm-cells = <3>; + clocks = <&rp1_clocks RP1_CLK_PWM0>; + assigned-clocks = <&rp1_clocks RP1_CLK_PWM0>; + assigned-clock-rates = <50000000>; + status = "disabled"; + }; + + rp1_pwm1: pwm@9c000 { + compatible = "raspberrypi,rp1-pwm"; + reg = <0xc0 0x4009c000 0x0 0x100>; + #pwm-cells = <3>; + clocks = <&rp1_clocks RP1_CLK_PWM1>; + assigned-clocks = <&rp1_clocks RP1_CLK_PWM1>; + assigned-clock-rates = <50000000>; + status = "disabled"; + }; + + rp1_i2s0: i2s@a0000 { + reg = <0xc0 0x400a0000 0x0 0x1000>; + compatible = "snps,designware-i2s"; + // Providing an interrupt disables DMA + // interrupts = <RP1_INT_I2S0 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&rp1_clocks RP1_CLK_I2S>; + clock-names = "i2sclk"; + #sound-dai-cells = <0>; + dmas = <&rp1_dma RP1_DMA_I2S0_TX>,<&rp1_dma RP1_DMA_I2S0_RX>; + dma-names = "tx", "rx"; + dma-maxburst = <4>; + status = "disabled"; + }; + + rp1_i2s1: i2s@a4000 { + reg = <0xc0 0x400a4000 0x0 0x1000>; + compatible = "snps,designware-i2s"; + // Providing an interrupt disables DMA + // interrupts = <RP1_INT_I2S1 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&rp1_clocks RP1_CLK_I2S>; + clock-names = "i2sclk"; + #sound-dai-cells = <0>; + dmas = <&rp1_dma RP1_DMA_I2S1_TX>,<&rp1_dma RP1_DMA_I2S1_RX>; + dma-names = "tx", "rx"; + dma-maxburst = <4>; + status = "disabled"; + }; + + rp1_i2s2: i2s@a8000 { + reg = <0xc0 0x400a8000 0x0 0x1000>; + compatible = "snps,designware-i2s"; + // Providing an interrupt disables DMA + // interrupts = <RP1_INT_I2S2 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&rp1_clocks RP1_CLK_I2S>; + status = "disabled"; + }; + + rp1_sdio_clk0: sdio_clk0@b0004 { + compatible = "raspberrypi,rp1-sdio-clk"; + reg = <0xc0 0x400b0004 0x0 0x1c>; + clocks = <&sdio_src &sdhci_core>; + clock-names = "src", "base"; + #clock-cells = <0>; + status = "disabled"; + }; + + rp1_sdio_clk1: sdio_clk1@b4004 { + compatible = "raspberrypi,rp1-sdio-clk"; + reg = <0xc0 0x400b4004 0x0 0x1c>; + clocks = <&sdio_src &sdhci_core>; + clock-names = "src", "base"; + #clock-cells = <0>; + status = "disabled"; + }; + + rp1_adc: adc@c8000 { + compatible = "raspberrypi,rp1-adc"; + reg = <0xc0 0x400c8000 0x0 0x4000>; + clocks = <&rp1_clocks RP1_CLK_ADC>; + clock-names = "adcclk"; + #clock-cells = <0>; + vref-supply = <&rp1_vdd_3v3>; + status = "disabled"; + }; + + rp1_gpio: gpio@d0000 { + reg = <0xc0 0x400d0000 0x0 0xc000>, + <0xc0 0x400e0000 0x0 0xc000>, + <0xc0 0x400f0000 0x0 0xc000>; + compatible = "raspberrypi,rp1-gpio"; + interrupts = <RP1_INT_IO_BANK0 IRQ_TYPE_LEVEL_HIGH>, + <RP1_INT_IO_BANK1 IRQ_TYPE_LEVEL_HIGH>, + <RP1_INT_IO_BANK2 IRQ_TYPE_LEVEL_HIGH>; + gpio-controller; + #gpio-cells = <2>; + interrupt-controller; + #interrupt-cells = <2>; + gpio-ranges = <&rp1_gpio 0 0 54>; + + rp1_uart0_14_15: rp1_uart0_14_15 { + pin_txd { + function = "uart0"; + pins = "gpio14"; + bias-disable; + }; + pin_rxd { + function = "uart0"; + pins = "gpio15"; + bias-pull-up; + }; + }; + rp1_uart0_ctsrts_16_17: rp1_uart0_ctsrts_16_17 { + pin_cts { + function = "uart0"; + pins = "gpio16"; + bias-pull-up; + }; + pin_rts { + function = "uart0"; + pins = "gpio17"; + bias-disable; + }; + }; + rp1_uart1_0_1: rp1_uart1_0_1 { + pin_txd { + function = "uart1"; + pins = "gpio0"; + bias-disable; + }; + pin_rxd { + function = "uart1"; + pins = "gpio1"; + bias-pull-up; + }; + }; + rp1_uart1_ctsrts_2_3: rp1_uart1_ctsrts_2_3 { + pin_cts { + function = "uart1"; + pins = "gpio2"; + bias-pull-up; + }; + pin_rts { + function = "uart1"; + pins = "gpio3"; + bias-disable; + }; + }; + rp1_uart2_4_5: rp1_uart2_4_5 { + pin_txd { + function = "uart2"; + pins = "gpio4"; + bias-disable; + }; + pin_rxd { + function = "uart2"; + pins = "gpio5"; + bias-pull-up; + }; + }; + rp1_uart2_ctsrts_6_7: rp1_uart2_ctsrts_6_7 { + pin_cts { + function = "uart2"; + pins = "gpio6"; + bias-pull-up; + }; + pin_rts { + function = "uart2"; + pins = "gpio7"; + bias-disable; + }; + }; + rp1_uart3_8_9: rp1_uart3_8_9 { + pin_txd { + function = "uart3"; + pins = "gpio8"; + bias-disable; + }; + pin_rxd { + function = "uart3"; + pins = "gpio9"; + bias-pull-up; + }; + }; + rp1_uart3_ctsrts_10_11: rp1_uart3_ctsrts_10_11 { + pin_cts { + function = "uart3"; + pins = "gpio10"; + bias-pull-up; + }; + pin_rts { + function = "uart3"; + pins = "gpio11"; + bias-disable; + }; + }; + rp1_uart4_12_13: rp1_uart4_12_13 { + pin_txd { + function = "uart4"; + pins = "gpio12"; + bias-disable; + }; + pin_rxd { + function = "uart4"; + pins = "gpio13"; + bias-pull-up; + }; + }; + rp1_uart4_ctsrts_14_15: rp1_uart4_ctsrts_14_15 { + pin_cts { + function = "uart4"; + pins = "gpio14"; + bias-pull-up; + }; + pin_rts { + function = "uart4"; + pins = "gpio15"; + bias-disable; + }; + }; + + rp1_sdio0_22_27: rp1_sdio0_22_27 { + pin_clk { + function = "sd0"; + pins = "gpio22"; + bias-disable; + drive-strength = <12>; + slew-rate = <1>; + }; + pin_cmd { + function = "sd0"; + pins = "gpio23"; + bias-pull-up; + drive-strength = <12>; + slew-rate = <1>; + }; + pins_dat { + function = "sd0"; + pins = "gpio24", "gpio25", "gpio26", "gpio27"; + bias-pull-up; + drive-strength = <12>; + slew-rate = <1>; + }; + }; + + rp1_sdio1_28_33: rp1_sdio1_28_33 { + pin_clk { + function = "sd1"; + pins = "gpio28"; + bias-disable; + drive-strength = <12>; + slew-rate = <1>; + }; + pin_cmd { + function = "sd1"; + pins = "gpio29"; + bias-pull-up; + drive-strength = <12>; + slew-rate = <1>; + }; + pins_dat { + function = "sd1"; + pins = "gpio30", "gpio31", "gpio32", "gpio33"; + bias-pull-up; + drive-strength = <12>; + slew-rate = <1>; + }; + }; + + rp1_i2s0_18_21: rp1_i2s0_18_21 { + function = "i2s0"; + pins = "gpio18", "gpio19", "gpio20", "gpio21"; + bias-disable; + }; + + rp1_i2s1_18_21: rp1_i2s1_18_21 { + function = "i2s1"; + pins = "gpio18", "gpio19", "gpio20", "gpio21"; + bias-disable; + }; + + rp1_i2c4_34_35: rp1_i2c4_34_35 { + function = "i2c4"; + pins = "gpio34", "gpio35"; + drive-strength = <12>; + bias-pull-up; + }; + rp1_i2c6_38_39: rp1_i2c6_38_39 { + function = "i2c6"; + pins = "gpio38", "gpio39"; + drive-strength = <12>; + bias-pull-up; + }; + rp1_i2c4_40_41: rp1_i2c4_40_41 { + function = "i2c4"; + pins = "gpio40", "gpio41"; + drive-strength = <12>; + bias-pull-up; + }; + rp1_i2c5_44_45: rp1_i2c5_44_45 { + function = "i2c5"; + pins = "gpio44", "gpio45"; + drive-strength = <12>; + bias-pull-up; + }; + rp1_i2c0_0_1: rp1_i2c0_0_1 { + function = "i2c0"; + pins = "gpio0", "gpio1"; + drive-strength = <12>; + bias-pull-up; + }; + rp1_i2c0_8_9: rp1_i2c0_8_9 { + function = "i2c0"; + pins = "gpio8", "gpio9"; + drive-strength = <12>; + bias-pull-up; + }; + rp1_i2c1_2_3: rp1_i2c1_2_3 { + function = "i2c1"; + pins = "gpio2", "gpio3"; + drive-strength = <12>; + bias-pull-up; + }; + rp1_i2c1_10_11: rp1_i2c1_10_11 { + function = "i2c1"; + pins = "gpio10", "gpio11"; + drive-strength = <12>; + bias-pull-up; + }; + rp1_i2c2_4_5: rp1_i2c2_4_5 { + function = "i2c2"; + pins = "gpio4", "gpio5"; + drive-strength = <12>; + bias-pull-up; + }; + rp1_i2c2_12_13: rp1_i2c2_12_13 { + function = "i2c2"; + pins = "gpio12", "gpio13"; + drive-strength = <12>; + bias-pull-up; + }; + rp1_i2c3_6_7: rp1_i2c3_6_7 { + function = "i2c3"; + pins = "gpio6", "gpio7"; + drive-strength = <12>; + bias-pull-up; + }; + rp1_i2c3_14_15: rp1_i2c3_14_15 { + function = "i2c3"; + pins = "gpio14", "gpio15"; + drive-strength = <12>; + bias-pull-up; + }; + rp1_i2c3_22_23: rp1_i2c3_22_23 { + function = "i2c3"; + pins = "gpio22", "gpio23"; + drive-strength = <12>; + bias-pull-up; + }; + + // DPI mappings with HSYNC,VSYNC but without PIXCLK,DE + rp1_dpi_16bit_gpio2: rp1_dpi_16bit_gpio2 { /* Mode 2, not fully supported by RP1 */ + function = "dpi"; + pins = "gpio2", "gpio3", "gpio4", "gpio5", + "gpio6", "gpio7", "gpio8", "gpio9", + "gpio10", "gpio11", "gpio12", "gpio13", + "gpio14", "gpio15", "gpio16", "gpio17", + "gpio18", "gpio19"; + bias-disable; + }; + rp1_dpi_16bit_cpadhi_gpio2: rp1_dpi_16bit_cpadhi_gpio2 { /* Mode 3 */ + function = "dpi"; + pins = "gpio2", "gpio3", "gpio4", "gpio5", + "gpio6", "gpio7", "gpio8", + "gpio12", "gpio13", "gpio14", "gpio15", + "gpio16", "gpio17", + "gpio20", "gpio21", "gpio22", "gpio23", + "gpio24"; + bias-disable; + }; + rp1_dpi_16bit_pad666_gpio2: rp1_dpi_16bit_pad666_gpio2 { /* Mode 4 */ + function = "dpi"; + pins = "gpio2", "gpio3", + "gpio5", "gpio6", "gpio7", "gpio8", + "gpio9", + "gpio12", "gpio13", "gpio14", "gpio15", + "gpio16", "gpio17", + "gpio21", "gpio22", "gpio23", "gpio24", + "gpio25"; + bias-disable; + }; + rp1_dpi_18bit_gpio2: rp1_dpi_18bit_gpio2 { /* Mode 5, not fully supported by RP1 */ + function = "dpi"; + pins = "gpio2", "gpio3", "gpio4", "gpio5", + "gpio6", "gpio7", "gpio8", "gpio9", + "gpio10", "gpio11", "gpio12", "gpio13", + "gpio14", "gpio15", "gpio16", "gpio17", + "gpio18", "gpio19", "gpio20", "gpio21"; + bias-disable; + }; + rp1_dpi_18bit_cpadhi_gpio2: rp1_dpi_18bit_cpadhi_gpio2 { /* Mode 6 */ + function = "dpi"; + pins = "gpio2", "gpio3", "gpio4", "gpio5", + "gpio6", "gpio7", "gpio8", "gpio9", + "gpio12", "gpio13", "gpio14", "gpio15", + "gpio16", "gpio17", + "gpio20", "gpio21", "gpio22", "gpio23", + "gpio24", "gpio25"; + bias-disable; + }; + rp1_dpi_24bit_gpio2: rp1_dpi_24bit_gpio2 { /* Mode 7 */ + function = "dpi"; + pins = "gpio2", "gpio3", "gpio4", "gpio5", + "gpio6", "gpio7", "gpio8", "gpio9", + "gpio10", "gpio11", "gpio12", "gpio13", + "gpio14", "gpio15", "gpio16", "gpio17", + "gpio18", "gpio19", "gpio20", "gpio21", + "gpio22", "gpio23", "gpio24", "gpio25", + "gpio26", "gpio27"; + bias-disable; + }; + rp1_dpi_hvsync: rp1_dpi_hvsync { /* Sync only, for use with int VDAC */ + function = "dpi"; + pins = "gpio2", "gpio3"; + bias-disable; + }; + + // More DPI mappings, including PIXCLK,DE on GPIOs 0,1 + rp1_dpi_16bit_gpio0: rp1_dpi_16bit_gpio0 { /* Mode 2, not fully supported by RP1 */ + function = "dpi"; + pins = "gpio0", "gpio1", "gpio2", "gpio3", + "gpio4", "gpio5", "gpio6", "gpio7", + "gpio8", "gpio9", "gpio10", "gpio11", + "gpio12", "gpio13", "gpio14", "gpio15", + "gpio16", "gpio17", "gpio18", "gpio19"; + bias-disable; + }; + rp1_dpi_16bit_cpadhi_gpio0: rp1_dpi_16bit_cpadhi_gpio0 { /* Mode 3 */ + function = "dpi"; + pins = "gpio0", "gpio1", "gpio2", "gpio3", + "gpio4", "gpio5", "gpio6", "gpio7", + "gpio8", + "gpio12", "gpio13", "gpio14", "gpio15", + "gpio16", "gpio17", + "gpio20", "gpio21", "gpio22", "gpio23", + "gpio24"; + bias-disable; + }; + rp1_dpi_16bit_pad666_gpio0: rp1_dpi_16bit_pad666_gpio0 { /* Mode 4 */ + function = "dpi"; + pins = "gpio0", "gpio1", "gpio2", "gpio3", + "gpio5", "gpio6", "gpio7", "gpio8", + "gpio9", + "gpio12", "gpio13", "gpio14", "gpio15", + "gpio16", "gpio17", + "gpio21", "gpio22", "gpio23", "gpio24", + "gpio25"; + bias-disable; + }; + rp1_dpi_18bit_gpio0: rp1_dpi_18bit_gpio0 { /* Mode 5, not fully supported by RP1 */ + function = "dpi"; + pins = "gpio0", "gpio1", "gpio2", "gpio3", + "gpio4", "gpio5", "gpio6", "gpio7", + "gpio8", "gpio9", "gpio10", "gpio11", + "gpio12", "gpio13", "gpio14", "gpio15", + "gpio16", "gpio17", "gpio18", "gpio19", + "gpio20", "gpio21"; + bias-disable; + }; + rp1_dpi_18bit_cpadhi_gpio0: rp1_dpi_18bit_cpadhi_gpio0 { /* Mode 6 */ + function = "dpi"; + pins = "gpio0", "gpio1", "gpio2", "gpio3", + "gpio4", "gpio5", "gpio6", "gpio7", + "gpio8", "gpio9", + "gpio12", "gpio13", "gpio14", "gpio15", + "gpio16", "gpio17", + "gpio20", "gpio21", "gpio22", "gpio23", + "gpio24", "gpio25"; + bias-disable; + }; + rp1_dpi_24bit_gpio0: rp1_dpi_24bit_gpio0 { /* Mode 7 -- All GPIOs used! */ + function = "dpi"; + pins = "gpio0", "gpio1", "gpio2", "gpio3", + "gpio4", "gpio5", "gpio6", "gpio7", + "gpio8", "gpio9", "gpio10", "gpio11", + "gpio12", "gpio13", "gpio14", "gpio15", + "gpio16", "gpio17", "gpio18", "gpio19", + "gpio20", "gpio21", "gpio22", "gpio23", + "gpio24", "gpio25", "gpio26", "gpio27"; + bias-disable; + }; + + rp1_gpclksrc0_gpio4: rp1_gpclksrc0_gpio4 { + function = "gpclk0"; + pins = "gpio4"; + bias-disable; + }; + + rp1_gpclksrc0_gpio20: rp1_gpclksrc0_gpio20 { + function = "gpclk0"; + pins = "gpio20"; + bias-disable; + }; + + rp1_gpclksrc1_gpio5: rp1_gpclksrc1_gpio5 { + function = "gpclk1"; + pins = "gpio5"; + bias-disable; + }; + + rp1_gpclksrc1_gpio18: rp1_gpclksrc1_gpio18 { + function = "gpclk1"; + pins = "gpio18"; + bias-disable; + }; + + rp1_gpclksrc1_gpio21: rp1_gpclksrc1_gpio21 { + function = "gpclk1"; + pins = "gpio21"; + bias-disable; + }; + + rp1_pwm1_gpio45: rp1_pwm1_gpio45 { + function = "pwm1"; + pins = "gpio45"; + bias-pull-down; + }; + + rp1_spi0_gpio9: rp1_spi0_gpio9 { + function = "spi0"; + pins = "gpio9", "gpio10", "gpio11"; + bias-disable; + drive-strength = <12>; + slew-rate = <1>; + }; + + rp1_spi0_cs_gpio7: rp1_spi0_cs_gpio7 { + function = "spi0"; + pins = "gpio7", "gpio8"; + bias-pull-up; + }; + + rp1_spi1_gpio19: rp1_spi1_gpio19 { + function = "spi1"; + pins = "gpio19", "gpio20", "gpio21"; + bias-disable; + drive-strength = <12>; + slew-rate = <1>; + }; + + rp1_spi2_gpio1: rp1_spi2_gpio1 { + function = "spi2"; + pins = "gpio1", "gpio2", "gpio3"; + bias-disable; + drive-strength = <12>; + slew-rate = <1>; + }; + + rp1_spi3_gpio5: rp1_spi3_gpio5 { + function = "spi3"; + pins = "gpio5", "gpio6", "gpio7"; + bias-disable; + drive-strength = <12>; + slew-rate = <1>; + }; + + rp1_spi4_gpio9: rp1_spi4_gpio9 { + function = "spi4"; + pins = "gpio9", "gpio10", "gpio11"; + bias-disable; + drive-strength = <12>; + slew-rate = <1>; + }; + + rp1_spi5_gpio13: rp1_spi5_gpio13 { + function = "spi5"; + pins = "gpio13", "gpio14", "gpio15"; + bias-disable; + drive-strength = <12>; + slew-rate = <1>; + }; + + rp1_spi8_gpio49: rp1_spi8_gpio49 { + function = "spi8"; + pins = "gpio49", "gpio50", "gpio51"; + bias-disable; + drive-strength = <12>; + slew-rate = <1>; + }; + + rp1_spi8_cs_gpio52: rp1_spi8_cs_gpio52 { + function = "spi0"; + pins = "gpio52", "gpio53"; + bias-pull-up; + }; + + rp1_audio_out_12_13: rp1_audio_out_12_13 { + function = "aaud"; + pins = "gpio12", "gpio13"; + bias-disable; + }; + }; + + rp1_eth: ethernet@100000 { + reg = <0xc0 0x40100000 0x0 0x4000>; + compatible = "raspberrypi,rp1-gem", "cdns,macb"; + #address-cells = <1>; + #size-cells = <0>; + interrupts = <RP1_INT_ETH IRQ_TYPE_LEVEL_HIGH>; + clocks = <&rp1_clocks RP1_CLK_SYS + &rp1_clocks RP1_CLK_SYS + &rp1_clocks RP1_CLK_ETH_TSU + &rp1_clocks RP1_CLK_ETH>; + clock-names = "pclk", "hclk", "tsu_clk", "tx_clk"; + phy-mode = "rgmii-id"; + cdns,aw2w-max-pipe = /bits/ 8 <8>; + cdns,ar2r-max-pipe = /bits/ 8 <8>; + cdns,use-aw2b-fill; + local-mac-address = [00 00 00 00 00 00]; + status = "disabled"; + }; + + rp1_csi0: csi@110000 { + compatible = "raspberrypi,rp1-cfe"; + reg = <0xc0 0x40110000 0x0 0x100>, // CSI2 DMA address + <0xc0 0x40114000 0x0 0x100>, // PHY/CSI Host address + <0xc0 0x40120000 0x0 0x100>, // MIPI CFG address + <0xc0 0x40124000 0x0 0x1000>; // PiSP FE address + + // interrupts must match rp1_pisp_fe setup + interrupts = <RP1_INT_MIPI0 IRQ_TYPE_LEVEL_HIGH>; + + clocks = <&rp1_clocks RP1_CLK_MIPI0_CFG>; + assigned-clocks = <&rp1_clocks RP1_CLK_MIPI0_CFG>; + assigned-clock-rates = <25000000>; + + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + + rp1_csi1: csi@128000 { + compatible = "raspberrypi,rp1-cfe"; + reg = <0xc0 0x40128000 0x0 0x100>, // CSI2 DMA address + <0xc0 0x4012c000 0x0 0x100>, // PHY/CSI Host address + <0xc0 0x40138000 0x0 0x100>, // MIPI CFG address + <0xc0 0x4013c000 0x0 0x1000>; // PiSP FE address + + // interrupts must match rp1_pisp_fe setup + interrupts = <RP1_INT_MIPI1 IRQ_TYPE_LEVEL_HIGH>; + + clocks = <&rp1_clocks RP1_CLK_MIPI1_CFG>; + assigned-clocks = <&rp1_clocks RP1_CLK_MIPI1_CFG>; + assigned-clock-rates = <25000000>; + + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + + rp1_pio: pio@178000 { + reg = <0xc0 0x40178000 0x0 0x20>; + compatible = "raspberrypi,rp1-pio"; + firmware = <&rp1_firmware>; + dmas = <&rp1_dma RP1_DMA_PIO_CH0_TX>, <&rp1_dma RP1_DMA_PIO_CH0_RX>, + <&rp1_dma RP1_DMA_PIO_CH1_TX>, <&rp1_dma RP1_DMA_PIO_CH1_RX>, + <&rp1_dma RP1_DMA_PIO_CH2_TX>, <&rp1_dma RP1_DMA_PIO_CH2_RX>, + <&rp1_dma RP1_DMA_PIO_CH3_TX>, <&rp1_dma RP1_DMA_PIO_CH3_RX>; + dma-names = "tx0", "rx0", "tx1", "rx1", "tx2", "rx2", "tx3", "rx3"; + status = "disabled"; + }; + + rp1_mmc0: mmc@180000 { + reg = <0xc0 0x40180000 0x0 0x100>; + compatible = "raspberrypi,rp1-dwcmshc"; + interrupts = <RP1_INT_SDIO0 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&rp1_clocks RP1_CLK_SYS &sdhci_core + &rp1_clocks RP1_CLK_SDIO_TIMER + &rp1_sdio_clk0>; + clock-names = "bus", "core", "timeout", "sdio"; + /* Bank 0 VDDIO is fixed */ + no-1-8-v; + bus-width = <4>; + vmmc-supply = <&rp1_vdd_3v3>; + broken-cd; + status = "disabled"; + }; + + rp1_mmc1: mmc@184000 { + reg = <0xc0 0x40184000 0x0 0x100>; + compatible = "raspberrypi,rp1-dwcmshc"; + interrupts = <RP1_INT_SDIO1 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&rp1_clocks RP1_CLK_SYS &sdhci_core + &rp1_clocks RP1_CLK_SDIO_TIMER + &rp1_sdio_clk1>; + clock-names = "bus", "core", "timeout", "sdio"; + bus-width = <4>; + vmmc-supply = <&rp1_vdd_3v3>; + /* Nerf SDR speeds */ + sdhci-caps-mask = <0x3 0x0>; + broken-cd; + status = "disabled"; + }; + + rp1_dma: dma@188000 { + reg = <0xc0 0x40188000 0x0 0x1000>; + compatible = "snps,axi-dma-1.01a"; + interrupts = <RP1_INT_DMA IRQ_TYPE_LEVEL_HIGH>; + clocks = <&rp1_clocks RP1_CLK_DMA &rp1_clocks RP1_CLK_SYS>; + clock-names = "core-clk", "cfgr-clk"; + + #dma-cells = <1>; + dma-channels = <8>; + snps,dma-masters = <1>; + snps,dma-targets = <64>; + snps,data-width = <4>; // (8 << 4) == 128 bits + snps,block-size = <0x40000 0x40000 0x40000 0x40000 0x40000 0x40000 0x40000 0x40000>; + snps,priority = <0 1 2 3 4 5 6 7>; + snps,axi-max-burst-len = <4>; + status = "disabled"; + }; + + rp1_usb0: usb@200000 { + reg = <0xc0 0x40200000 0x0 0x100000>; + compatible = "snps,dwc3"; + dr_mode = "host"; + usb3-lpm-capable; + snps,axi-pipe-limit = /bits/ 8 <8>; + snps,dis_rxdet_inp3_quirk; + snps,enhanced-nak-fs-quirk; + snps,parkmode-disable-ss-quirk; + snps,parkmode-disable-hs-quirk; + snps,parkmode-disable-fsls-quirk; + snps,tx-max-burst = /bits/ 8 <8>; + snps,tx-thr-num-pkt = /bits/ 8 <2>; + interrupts = <RP1_INT_USBHOST0_0 IRQ_TYPE_EDGE_RISING>; + status = "disabled"; + }; + + rp1_usb1: usb@300000 { + reg = <0xc0 0x40300000 0x0 0x100000>; + compatible = "snps,dwc3"; + dr_mode = "host"; + usb3-lpm-capable; + snps,axi-pipe-limit = /bits/ 8 <8>; + snps,dis_rxdet_inp3_quirk; + snps,enhanced-nak-fs-quirk; + snps,parkmode-disable-ss-quirk; + snps,parkmode-disable-hs-quirk; + snps,parkmode-disable-fsls-quirk; + snps,tx-max-burst = /bits/ 8 <8>; + snps,tx-thr-num-pkt = /bits/ 8 <2>; + interrupts = <RP1_INT_USBHOST1_0 IRQ_TYPE_EDGE_RISING>; + status = "disabled"; + }; + + rp1_dsi0: dsi@110000 { + compatible = "raspberrypi,rp1dsi"; + status = "disabled"; + reg = <0xc0 0x40118000 0x0 0x1000>, // MIPI0 DSI DMA (ArgonDPI) + <0xc0 0x4011c000 0x0 0x1000>, // MIPI0 DSI Host (SNPS) + <0xc0 0x40120000 0x0 0x1000>; // MIPI0 CFG + + interrupts = <RP1_INT_MIPI0 IRQ_TYPE_LEVEL_HIGH>; + + clocks = <&rp1_clocks RP1_CLK_MIPI0_CFG>, + <&rp1_clocks RP1_CLK_MIPI0_DPI>, + <&rp1_clocks RP1_CLK_MIPI0_DSI_BYTECLOCK>, + <&clk_xosc>, // hardwired to DSI "refclk" + <&rp1_clocks RP1_PLL_SYS>; // alternate parent for divide + clock-names = "cfgclk", "dpiclk", "byteclk", "refclk", "pllsys"; + + assigned-clocks = <&rp1_clocks RP1_CLK_MIPI0_CFG>; + assigned-clock-rates = <25000000>; + }; + + rp1_dsi1: dsi@128000 { + compatible = "raspberrypi,rp1dsi"; + status = "disabled"; + reg = <0xc0 0x40130000 0x0 0x1000>, // MIPI1 DSI DMA (ArgonDPI) + <0xc0 0x40134000 0x0 0x1000>, // MIPI1 DSI Host (SNPS) + <0xc0 0x40138000 0x0 0x1000>; // MIPI1 CFG + + interrupts = <RP1_INT_MIPI1 IRQ_TYPE_LEVEL_HIGH>; + + clocks = <&rp1_clocks RP1_CLK_MIPI1_CFG>, + <&rp1_clocks RP1_CLK_MIPI1_DPI>, + <&rp1_clocks RP1_CLK_MIPI1_DSI_BYTECLOCK>, + <&clk_xosc>, // hardwired to DSI "refclk" + <&rp1_clocks RP1_PLL_SYS>; // alternate parent for divide + clock-names = "cfgclk", "dpiclk", "byteclk", "refclk", "pllsys"; + + assigned-clocks = <&rp1_clocks RP1_CLK_MIPI1_CFG>; + assigned-clock-rates = <25000000>; + }; + + /* VEC and DPI both need to control PLL_VIDEO and cannot work together; */ + /* config.txt should enable one or other using dtparam=vec or an overlay. */ + rp1_vec: vec@144000 { + compatible = "raspberrypi,rp1vec"; + status = "disabled"; + reg = <0xc0 0x40144000 0x0 0x1000>, // VIDEO_OUT_VEC + <0xc0 0x40140000 0x0 0x1000>; // VIDEO_OUT_CFG + + interrupts = <RP1_INT_VIDEO_OUT IRQ_TYPE_LEVEL_HIGH>; + + clocks = <&rp1_clocks RP1_CLK_VEC>; + + assigned-clocks = <&rp1_clocks RP1_PLL_VIDEO_CORE>, + <&rp1_clocks RP1_PLL_VIDEO_SEC>, + <&rp1_clocks RP1_CLK_VEC>; + assigned-clock-rates = <1188000000>, + <108000000>, + <108000000>; + assigned-clock-parents = <0>, + <&rp1_clocks RP1_PLL_VIDEO_CORE>, + <&rp1_clocks RP1_PLL_VIDEO_SEC>; + }; + + rp1_dpi: dpi@148000 { + compatible = "raspberrypi,rp1dpi"; + status = "disabled"; + reg = <0xc0 0x40148000 0x0 0x1000>, // VIDEO_OUT DPI + <0xc0 0x40140000 0x0 0x1000>; // VIDEO_OUT_CFG + + interrupts = <RP1_INT_VIDEO_OUT IRQ_TYPE_LEVEL_HIGH>; + + clocks = <&rp1_clocks RP1_CLK_DPI>, // DPI pixel clock + <&rp1_clocks RP1_PLL_VIDEO>, // PLL primary divider, and + <&rp1_clocks RP1_PLL_VIDEO_CORE>; // VCO, which we also control + clock-names = "dpiclk", "plldiv", "pllcore"; + + assigned-clocks = <&rp1_clocks RP1_CLK_DPI>; + assigned-clock-parents = <&rp1_clocks RP1_PLL_VIDEO>; + }; + + sram: sram@400000 { + compatible = "mmio-sram"; + reg = <0xc0 0x40400000 0x0 0x10000>; + #address-cells = <1>; + #size-cells = <1>; + ranges = <0 0xc0 0x40400000 0x10000>; + + rp1_fw_shmem: shmem@ff00 { + compatible = "raspberrypi,rp1-shmem"; + reg = <0xff00 0x100>; // firmware mailbox buffer + }; + }; + }; +}; + +&clocks { + clk_xosc: clk_xosc { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-output-names = "xosc"; + clock-frequency = <50000000>; + }; + sdio_src: sdio_src { + // 400 MHz on FPGA. PLL sys VCO on asic + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-output-names = "src"; + clock-frequency = <1000000000>; + }; + sdhci_core: sdhci_core { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-output-names = "core"; + clock-frequency = <50000000>; + }; + /* GPIO derived clock sources. Each GPIO with a GPCLK function + * can drive its output from the respective GPCLK + * generator, and provide a clock source to other internal + * dividers. Add dummy sources here so that they can be overridden + * with overlays. + */ + clksrc_gp0: clksrc_gp0 { + status = "disabled"; + compatible = "fixed-factor-clock"; + #clock-cells = <0>; + clock-div = <1>; + clock-mult = <1>; + clocks = <&rp1_clocks RP1_CLK_GP0>; + clock-output-names = "clksrc_gp0"; + }; + clksrc_gp1: clksrc_gp1 { + status = "disabled"; + compatible = "fixed-factor-clock"; + #clock-cells = <0>; + clock-div = <1>; + clock-mult = <1>; + clocks = <&rp1_clocks RP1_CLK_GP1>; + clock-output-names = "clksrc_gp1"; + }; + clksrc_gp2: clksrc_gp2 { + status = "disabled"; + compatible = "fixed-factor-clock"; + clock-div = <1>; + clock-mult = <1>; + #clock-cells = <0>; + clocks = <&rp1_clocks RP1_CLK_GP2>; + clock-output-names = "clksrc_gp2"; + }; + clksrc_gp3: clksrc_gp3 { + status = "disabled"; + compatible = "fixed-factor-clock"; + clock-div = <1>; + clock-mult = <1>; + #clock-cells = <0>; + clocks = <&rp1_clocks RP1_CLK_GP3>; + clock-output-names = "clksrc_gp3"; + }; + clksrc_gp4: clksrc_gp4 { + status = "disabled"; + compatible = "fixed-factor-clock"; + #clock-cells = <0>; + clock-div = <1>; + clock-mult = <1>; + clocks = <&rp1_clocks RP1_CLK_GP4>; + clock-output-names = "clksrc_gp4"; + }; + clksrc_gp5: clksrc_gp5 { + status = "disabled"; + compatible = "fixed-factor-clock"; + #clock-cells = <0>; + clock-div = <1>; + clock-mult = <1>; + clocks = <&rp1_clocks RP1_CLK_GP5>; + clock-output-names = "clksrc_gp5"; + }; +}; + +/ { + rp1_firmware: rp1_firmware { + compatible = "raspberrypi,rp1-firmware", "simple-mfd"; + mboxes = <&rp1_mbox 0>; + shmem = <&rp1_fw_shmem>; + }; + + rp1_vdd_3v3: rp1_vdd_3v3 { + compatible = "regulator-fixed"; + regulator-name = "vdd-3v3"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + }; +}; diff --git a/arch/arm64/boot/dts/overlays b/arch/arm64/boot/dts/overlays new file mode 120000 index 00000000000000..ded08646b6f66c --- /dev/null +++ b/arch/arm64/boot/dts/overlays @@ -0,0 +1 @@ +../../../arm/boot/dts/overlays \ No newline at end of file diff --git a/arch/arm64/configs/bcm2711_defconfig b/arch/arm64/configs/bcm2711_defconfig new file mode 100644 index 00000000000000..974e7028bd7f37 --- /dev/null +++ b/arch/arm64/configs/bcm2711_defconfig @@ -0,0 +1,1733 @@ +CONFIG_LOCALVERSION="-v8" +# CONFIG_LOCALVERSION_AUTO is not set +CONFIG_SYSVIPC=y +CONFIG_POSIX_MQUEUE=y +CONFIG_GENERIC_IRQ_DEBUGFS=y +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_BPF_SYSCALL=y +CONFIG_BPF_JIT=y +CONFIG_PREEMPT=y +CONFIG_BSD_PROCESS_ACCT=y +CONFIG_BSD_PROCESS_ACCT_V3=y +CONFIG_TASKSTATS=y +CONFIG_TASK_DELAY_ACCT=y +CONFIG_TASK_XACCT=y +CONFIG_TASK_IO_ACCOUNTING=y +CONFIG_PSI=y +CONFIG_PSI_DEFAULT_DISABLED=y +CONFIG_IKCONFIG=m +CONFIG_IKCONFIG_PROC=y +CONFIG_MEMCG=y +CONFIG_BLK_CGROUP=y +CONFIG_CFS_BANDWIDTH=y +CONFIG_CGROUP_PIDS=y +CONFIG_CGROUP_FREEZER=y +CONFIG_CPUSETS=y +CONFIG_CPUSETS_V1=y +CONFIG_CGROUP_DEVICE=y +CONFIG_CGROUP_CPUACCT=y +CONFIG_CGROUP_PERF=y +CONFIG_CGROUP_BPF=y +CONFIG_NAMESPACES=y +CONFIG_USER_NS=y +CONFIG_CHECKPOINT_RESTORE=y +CONFIG_SCHED_AUTOGROUP=y +CONFIG_BLK_DEV_INITRD=y +CONFIG_EXPERT=y +CONFIG_PROFILING=y +CONFIG_ARCH_BCM=y +CONFIG_ARCH_BCM2835=y +CONFIG_ARCH_BRCMSTB=y +CONFIG_ARM64_ERRATUM_834220=y +CONFIG_ARM64_ERRATUM_2441007=y +CONFIG_ARM64_ERRATUM_1286807=y +CONFIG_ARM64_ERRATUM_1542419=y +CONFIG_ARM64_ERRATUM_2441009=y +# CONFIG_CAVIUM_ERRATUM_22375 is not set +# CONFIG_CAVIUM_ERRATUM_23154 is not set +# CONFIG_CAVIUM_ERRATUM_27456 is not set +CONFIG_ARM64_VA_BITS_39=y +CONFIG_NR_CPUS=4 +CONFIG_HOTPLUG_CPU=y +CONFIG_NUMA=y +CONFIG_COMPAT=y +CONFIG_ARMV8_DEPRECATED=y +CONFIG_SWP_EMULATION=y +CONFIG_CP15_BARRIER_EMULATION=y +CONFIG_SETEND_EMULATION=y +CONFIG_RANDOMIZE_BASE=y +CONFIG_CMDLINE="console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 root=/dev/mmcblk0p2 rootfstype=ext4 rootwait" +# CONFIG_SUSPEND is not set +CONFIG_PM=y +CONFIG_PM_DEBUG=y +CONFIG_CPU_IDLE=y +CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_STAT=y +CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND=y +CONFIG_CPU_FREQ_GOV_POWERSAVE=y +CONFIG_CPU_FREQ_GOV_USERSPACE=y +CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y +CONFIG_CPU_FREQ_GOV_SCHEDUTIL=y +CONFIG_CPUFREQ_DT=y +CONFIG_ARM_RASPBERRYPI_CPUFREQ=y +CONFIG_VIRTUALIZATION=y +CONFIG_KVM=y +CONFIG_JUMP_LABEL=y +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_MODVERSIONS=y +CONFIG_MODULE_SRCVERSION_ALL=y +CONFIG_MODULE_COMPRESS=y +CONFIG_MODULE_COMPRESS_XZ=y +CONFIG_BLK_DEV_THROTTLING=y +CONFIG_PARTITION_ADVANCED=y +CONFIG_MAC_PARTITION=y +CONFIG_BINFMT_MISC=m +CONFIG_ZSWAP=y +# CONFIG_COMPAT_BRK is not set +CONFIG_CMA=y +CONFIG_CMA_AREAS=7 +CONFIG_LRU_GEN=y +CONFIG_LRU_GEN_ENABLED=y +CONFIG_NUMA_EMU=y +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_XFRM_USER=m +CONFIG_XFRM_INTERFACE=m +CONFIG_XFRM_SUB_POLICY=y +CONFIG_XFRM_STATISTICS=y +CONFIG_NET_KEY=m +CONFIG_IP_MULTICAST=y +CONFIG_IP_ADVANCED_ROUTER=y +CONFIG_IP_MULTIPLE_TABLES=y +CONFIG_IP_ROUTE_MULTIPATH=y +CONFIG_IP_ROUTE_VERBOSE=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y +CONFIG_IP_PNP_RARP=y +CONFIG_NET_IPIP=m +CONFIG_NET_IPGRE_DEMUX=m +CONFIG_NET_IPGRE=m +CONFIG_IP_MROUTE=y +CONFIG_IP_MROUTE_MULTIPLE_TABLES=y +CONFIG_IP_PIMSM_V1=y +CONFIG_IP_PIMSM_V2=y +CONFIG_NET_IPVTI=m +CONFIG_NET_FOU_IP_TUNNELS=y +CONFIG_INET_AH=m +CONFIG_INET_ESP=m +CONFIG_INET_IPCOMP=m +CONFIG_INET_DIAG=m +CONFIG_TCP_CONG_ADVANCED=y +CONFIG_TCP_CONG_BBR=m +CONFIG_IPV6=m +CONFIG_IPV6_ROUTER_PREF=y +CONFIG_IPV6_ROUTE_INFO=y +CONFIG_INET6_AH=m +CONFIG_INET6_ESP=m +CONFIG_INET6_ESP_OFFLOAD=m +CONFIG_INET6_IPCOMP=m +CONFIG_IPV6_ILA=m +CONFIG_IPV6_VTI=m +CONFIG_IPV6_SIT_6RD=y +CONFIG_IPV6_GRE=m +CONFIG_IPV6_MULTIPLE_TABLES=y +CONFIG_IPV6_SUBTREES=y +CONFIG_IPV6_MROUTE=y +CONFIG_IPV6_MROUTE_MULTIPLE_TABLES=y +CONFIG_IPV6_PIMSM_V2=y +CONFIG_MPTCP=y +CONFIG_NETWORK_PHY_TIMESTAMPING=y +CONFIG_NETFILTER=y +CONFIG_BRIDGE_NETFILTER=m +CONFIG_NF_CONNTRACK=m +CONFIG_NF_CONNTRACK_ZONES=y +CONFIG_NF_CONNTRACK_EVENTS=y +CONFIG_NF_CONNTRACK_TIMESTAMP=y +CONFIG_NF_CONNTRACK_AMANDA=m +CONFIG_NF_CONNTRACK_FTP=m +CONFIG_NF_CONNTRACK_H323=m +CONFIG_NF_CONNTRACK_IRC=m +CONFIG_NF_CONNTRACK_NETBIOS_NS=m +CONFIG_NF_CONNTRACK_SNMP=m +CONFIG_NF_CONNTRACK_PPTP=m +CONFIG_NF_CONNTRACK_SANE=m +CONFIG_NF_CONNTRACK_SIP=m +CONFIG_NF_CONNTRACK_TFTP=m +CONFIG_NF_CT_NETLINK=m +CONFIG_NF_TABLES=m +CONFIG_NF_TABLES_INET=y +CONFIG_NF_TABLES_NETDEV=y +CONFIG_NFT_NUMGEN=m +CONFIG_NFT_CT=m +CONFIG_NFT_FLOW_OFFLOAD=m +CONFIG_NFT_CONNLIMIT=m +CONFIG_NFT_LOG=m +CONFIG_NFT_LIMIT=m +CONFIG_NFT_MASQ=m +CONFIG_NFT_REDIR=m +CONFIG_NFT_NAT=m +CONFIG_NFT_TUNNEL=m +CONFIG_NFT_QUEUE=m +CONFIG_NFT_QUOTA=m +CONFIG_NFT_REJECT=m +CONFIG_NFT_COMPAT=m +CONFIG_NFT_HASH=m +CONFIG_NFT_FIB_INET=m +CONFIG_NFT_XFRM=m +CONFIG_NFT_SOCKET=m +CONFIG_NFT_OSF=m +CONFIG_NFT_TPROXY=m +CONFIG_NFT_SYNPROXY=m +CONFIG_NFT_DUP_NETDEV=m +CONFIG_NFT_FWD_NETDEV=m +CONFIG_NFT_FIB_NETDEV=m +CONFIG_NF_FLOW_TABLE_INET=m +CONFIG_NF_FLOW_TABLE=m +CONFIG_NETFILTER_XTABLES_COMPAT=y +CONFIG_NETFILTER_XT_SET=m +CONFIG_NETFILTER_XT_TARGET_CHECKSUM=m +CONFIG_NETFILTER_XT_TARGET_CLASSIFY=m +CONFIG_NETFILTER_XT_TARGET_CONNMARK=m +CONFIG_NETFILTER_XT_TARGET_DSCP=m +CONFIG_NETFILTER_XT_TARGET_HMARK=m +CONFIG_NETFILTER_XT_TARGET_IDLETIMER=m +CONFIG_NETFILTER_XT_TARGET_LED=m +CONFIG_NETFILTER_XT_TARGET_LOG=m +CONFIG_NETFILTER_XT_TARGET_MARK=m +CONFIG_NETFILTER_XT_TARGET_NFLOG=m +CONFIG_NETFILTER_XT_TARGET_NFQUEUE=m +CONFIG_NETFILTER_XT_TARGET_NOTRACK=m +CONFIG_NETFILTER_XT_TARGET_TEE=m +CONFIG_NETFILTER_XT_TARGET_TPROXY=m +CONFIG_NETFILTER_XT_TARGET_TRACE=m +CONFIG_NETFILTER_XT_TARGET_TCPMSS=m +CONFIG_NETFILTER_XT_TARGET_TCPOPTSTRIP=m +CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=m +CONFIG_NETFILTER_XT_MATCH_BPF=m +CONFIG_NETFILTER_XT_MATCH_CLUSTER=m +CONFIG_NETFILTER_XT_MATCH_COMMENT=m +CONFIG_NETFILTER_XT_MATCH_CONNBYTES=m +CONFIG_NETFILTER_XT_MATCH_CONNLABEL=m +CONFIG_NETFILTER_XT_MATCH_CONNLIMIT=m +CONFIG_NETFILTER_XT_MATCH_CONNMARK=m +CONFIG_NETFILTER_XT_MATCH_CONNTRACK=m +CONFIG_NETFILTER_XT_MATCH_CPU=m +CONFIG_NETFILTER_XT_MATCH_DCCP=m +CONFIG_NETFILTER_XT_MATCH_DEVGROUP=m +CONFIG_NETFILTER_XT_MATCH_DSCP=m +CONFIG_NETFILTER_XT_MATCH_ESP=m +CONFIG_NETFILTER_XT_MATCH_HASHLIMIT=m +CONFIG_NETFILTER_XT_MATCH_HELPER=m +CONFIG_NETFILTER_XT_MATCH_IPRANGE=m +CONFIG_NETFILTER_XT_MATCH_IPVS=m +CONFIG_NETFILTER_XT_MATCH_LENGTH=m +CONFIG_NETFILTER_XT_MATCH_LIMIT=m +CONFIG_NETFILTER_XT_MATCH_MAC=m +CONFIG_NETFILTER_XT_MATCH_MARK=m +CONFIG_NETFILTER_XT_MATCH_MULTIPORT=m +CONFIG_NETFILTER_XT_MATCH_NFACCT=m +CONFIG_NETFILTER_XT_MATCH_OSF=m +CONFIG_NETFILTER_XT_MATCH_OWNER=m +CONFIG_NETFILTER_XT_MATCH_POLICY=m +CONFIG_NETFILTER_XT_MATCH_PHYSDEV=m +CONFIG_NETFILTER_XT_MATCH_PKTTYPE=m +CONFIG_NETFILTER_XT_MATCH_QUOTA=m +CONFIG_NETFILTER_XT_MATCH_RATEEST=m +CONFIG_NETFILTER_XT_MATCH_REALM=m +CONFIG_NETFILTER_XT_MATCH_RECENT=m +CONFIG_NETFILTER_XT_MATCH_SOCKET=m +CONFIG_NETFILTER_XT_MATCH_STATE=m +CONFIG_NETFILTER_XT_MATCH_STATISTIC=m +CONFIG_NETFILTER_XT_MATCH_STRING=m +CONFIG_NETFILTER_XT_MATCH_TCPMSS=m +CONFIG_NETFILTER_XT_MATCH_TIME=m +CONFIG_NETFILTER_XT_MATCH_U32=m +CONFIG_IP_SET=m +CONFIG_IP_SET_BITMAP_IP=m +CONFIG_IP_SET_BITMAP_IPMAC=m +CONFIG_IP_SET_BITMAP_PORT=m +CONFIG_IP_SET_HASH_IP=m +CONFIG_IP_SET_HASH_IPPORT=m +CONFIG_IP_SET_HASH_IPPORTIP=m +CONFIG_IP_SET_HASH_IPPORTNET=m +CONFIG_IP_SET_HASH_NET=m +CONFIG_IP_SET_HASH_NETPORT=m +CONFIG_IP_SET_HASH_NETIFACE=m +CONFIG_IP_SET_LIST_SET=m +CONFIG_IP_VS=m +CONFIG_IP_VS_IPV6=y +CONFIG_IP_VS_PROTO_TCP=y +CONFIG_IP_VS_PROTO_UDP=y +CONFIG_IP_VS_PROTO_ESP=y +CONFIG_IP_VS_PROTO_AH=y +CONFIG_IP_VS_PROTO_SCTP=y +CONFIG_IP_VS_RR=m +CONFIG_IP_VS_WRR=m +CONFIG_IP_VS_LC=m +CONFIG_IP_VS_WLC=m +CONFIG_IP_VS_LBLC=m +CONFIG_IP_VS_LBLCR=m +CONFIG_IP_VS_DH=m +CONFIG_IP_VS_SH=m +CONFIG_IP_VS_SED=m +CONFIG_IP_VS_NQ=m +CONFIG_IP_VS_FTP=m +CONFIG_IP_VS_PE_SIP=m +CONFIG_NFT_DUP_IPV4=m +CONFIG_NFT_FIB_IPV4=m +CONFIG_NF_TABLES_ARP=y +CONFIG_NF_LOG_ARP=m +CONFIG_NF_LOG_IPV4=m +CONFIG_IP_NF_IPTABLES=m +CONFIG_IP_NF_MATCH_AH=m +CONFIG_IP_NF_MATCH_ECN=m +CONFIG_IP_NF_MATCH_RPFILTER=m +CONFIG_IP_NF_MATCH_TTL=m +CONFIG_IP_NF_FILTER=m +CONFIG_IP_NF_TARGET_REJECT=m +CONFIG_IP_NF_TARGET_SYNPROXY=m +CONFIG_IP_NF_NAT=m +CONFIG_IP_NF_TARGET_MASQUERADE=m +CONFIG_IP_NF_TARGET_NETMAP=m +CONFIG_IP_NF_TARGET_REDIRECT=m +CONFIG_IP_NF_MANGLE=m +CONFIG_IP_NF_TARGET_ECN=m +CONFIG_IP_NF_TARGET_TTL=m +CONFIG_IP_NF_RAW=m +CONFIG_IP_NF_ARPFILTER=m +CONFIG_IP_NF_ARP_MANGLE=m +CONFIG_NFT_DUP_IPV6=m +CONFIG_NFT_FIB_IPV6=m +CONFIG_IP6_NF_IPTABLES=m +CONFIG_IP6_NF_MATCH_AH=m +CONFIG_IP6_NF_MATCH_EUI64=m +CONFIG_IP6_NF_MATCH_FRAG=m +CONFIG_IP6_NF_MATCH_OPTS=m +CONFIG_IP6_NF_MATCH_HL=m +CONFIG_IP6_NF_MATCH_IPV6HEADER=m +CONFIG_IP6_NF_MATCH_MH=m +CONFIG_IP6_NF_MATCH_RPFILTER=m +CONFIG_IP6_NF_MATCH_RT=m +CONFIG_IP6_NF_MATCH_SRH=m +CONFIG_IP6_NF_TARGET_HL=m +CONFIG_IP6_NF_FILTER=m +CONFIG_IP6_NF_TARGET_REJECT=m +CONFIG_IP6_NF_TARGET_SYNPROXY=m +CONFIG_IP6_NF_MANGLE=m +CONFIG_IP6_NF_RAW=m +CONFIG_IP6_NF_SECURITY=m +CONFIG_IP6_NF_NAT=m +CONFIG_IP6_NF_TARGET_MASQUERADE=m +CONFIG_IP6_NF_TARGET_NPT=m +CONFIG_NF_TABLES_BRIDGE=m +CONFIG_NFT_BRIDGE_REJECT=m +CONFIG_BRIDGE_NF_EBTABLES=m +CONFIG_BRIDGE_EBT_BROUTE=m +CONFIG_BRIDGE_EBT_T_FILTER=m +CONFIG_BRIDGE_EBT_T_NAT=m +CONFIG_BRIDGE_EBT_802_3=m +CONFIG_BRIDGE_EBT_AMONG=m +CONFIG_BRIDGE_EBT_ARP=m +CONFIG_BRIDGE_EBT_IP=m +CONFIG_BRIDGE_EBT_IP6=m +CONFIG_BRIDGE_EBT_LIMIT=m +CONFIG_BRIDGE_EBT_MARK=m +CONFIG_BRIDGE_EBT_PKTTYPE=m +CONFIG_BRIDGE_EBT_STP=m +CONFIG_BRIDGE_EBT_VLAN=m +CONFIG_BRIDGE_EBT_ARPREPLY=m +CONFIG_BRIDGE_EBT_DNAT=m +CONFIG_BRIDGE_EBT_MARK_T=m +CONFIG_BRIDGE_EBT_REDIRECT=m +CONFIG_BRIDGE_EBT_SNAT=m +CONFIG_BRIDGE_EBT_LOG=m +CONFIG_BRIDGE_EBT_NFLOG=m +CONFIG_SCTP_COOKIE_HMAC_SHA1=y +CONFIG_ATM=m +CONFIG_L2TP=m +CONFIG_L2TP_V3=y +CONFIG_L2TP_IP=m +CONFIG_L2TP_ETH=m +CONFIG_BRIDGE=m +CONFIG_BRIDGE_VLAN_FILTERING=y +CONFIG_VLAN_8021Q=m +CONFIG_VLAN_8021Q_GVRP=y +CONFIG_ATALK=m +CONFIG_6LOWPAN=m +CONFIG_IEEE802154=m +CONFIG_IEEE802154_6LOWPAN=m +CONFIG_MAC802154=m +CONFIG_NET_SCHED=y +CONFIG_NET_SCH_HTB=m +CONFIG_NET_SCH_HFSC=m +CONFIG_NET_SCH_PRIO=m +CONFIG_NET_SCH_MULTIQ=m +CONFIG_NET_SCH_RED=m +CONFIG_NET_SCH_SFB=m +CONFIG_NET_SCH_SFQ=m +CONFIG_NET_SCH_TEQL=m +CONFIG_NET_SCH_TBF=m +CONFIG_NET_SCH_GRED=m +CONFIG_NET_SCH_NETEM=m +CONFIG_NET_SCH_DRR=m +CONFIG_NET_SCH_MQPRIO=m +CONFIG_NET_SCH_CHOKE=m +CONFIG_NET_SCH_QFQ=m +CONFIG_NET_SCH_CODEL=m +CONFIG_NET_SCH_FQ_CODEL=m +CONFIG_NET_SCH_CAKE=m +CONFIG_NET_SCH_FQ=m +CONFIG_NET_SCH_HHF=m +CONFIG_NET_SCH_PIE=m +CONFIG_NET_SCH_INGRESS=m +CONFIG_NET_SCH_PLUG=m +CONFIG_NET_CLS_BASIC=m +CONFIG_NET_CLS_ROUTE4=m +CONFIG_NET_CLS_FW=m +CONFIG_NET_CLS_U32=m +CONFIG_CLS_U32_MARK=y +CONFIG_NET_CLS_FLOW=m +CONFIG_NET_CLS_CGROUP=m +CONFIG_NET_CLS_BPF=y +CONFIG_NET_EMATCH=y +CONFIG_NET_EMATCH_CMP=m +CONFIG_NET_EMATCH_NBYTE=m +CONFIG_NET_EMATCH_U32=m +CONFIG_NET_EMATCH_META=m +CONFIG_NET_EMATCH_TEXT=m +CONFIG_NET_EMATCH_IPSET=m +CONFIG_NET_CLS_ACT=y +CONFIG_NET_ACT_POLICE=m +CONFIG_NET_ACT_GACT=m +CONFIG_GACT_PROB=y +CONFIG_NET_ACT_MIRRED=m +CONFIG_NET_ACT_NAT=m +CONFIG_NET_ACT_PEDIT=m +CONFIG_NET_ACT_SIMP=m +CONFIG_NET_ACT_SKBEDIT=m +CONFIG_NET_ACT_CSUM=m +CONFIG_BATMAN_ADV=m +CONFIG_OPENVSWITCH=m +CONFIG_VSOCKETS=m +CONFIG_CGROUP_NET_PRIO=y +CONFIG_NET_PKTGEN=m +CONFIG_HAMRADIO=y +CONFIG_AX25=m +CONFIG_NETROM=m +CONFIG_ROSE=m +CONFIG_MKISS=m +CONFIG_6PACK=m +CONFIG_BPQETHER=m +CONFIG_BAYCOM_SER_FDX=m +CONFIG_BAYCOM_SER_HDX=m +CONFIG_YAM=m +CONFIG_CAN=m +CONFIG_CAN_J1939=m +CONFIG_CAN_ISOTP=m +CONFIG_BT=m +CONFIG_BT_RFCOMM=m +CONFIG_BT_RFCOMM_TTY=y +CONFIG_BT_BNEP=m +CONFIG_BT_BNEP_MC_FILTER=y +CONFIG_BT_BNEP_PROTO_FILTER=y +CONFIG_BT_HIDP=m +CONFIG_BT_6LOWPAN=m +CONFIG_BT_HCIBTUSB=m +CONFIG_BT_HCIUART=m +CONFIG_BT_HCIUART_3WIRE=y +CONFIG_BT_HCIUART_BCM=y +CONFIG_BT_HCIBCM203X=m +CONFIG_BT_HCIBPA10X=m +CONFIG_BT_HCIBFUSB=m +CONFIG_BT_HCIVHCI=m +CONFIG_BT_MRVL=m +CONFIG_BT_MRVL_SDIO=m +CONFIG_BT_ATH3K=m +CONFIG_CFG80211=m +CONFIG_CFG80211_WEXT=y +CONFIG_MAC80211=m +CONFIG_MAC80211_MESH=y +CONFIG_RFKILL=m +CONFIG_RFKILL_INPUT=y +CONFIG_NET_9P=m +CONFIG_NFC=m +CONFIG_PCI=y +CONFIG_PCIEPORTBUS=y +CONFIG_PCIEAER=y +CONFIG_PCIEASPM_POWERSAVE=y +CONFIG_PCIE_DPC=y +CONFIG_UEVENT_HELPER=y +CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y +# CONFIG_BRCMSTB_GISB_ARB is not set +CONFIG_RASPBERRYPI_FIRMWARE=y +CONFIG_FIRMWARE_RP1=m +# CONFIG_EFI_VARS_PSTORE is not set +CONFIG_MTD=m +CONFIG_MTD_BLOCK=m +CONFIG_MTD_BLOCK2MTD=m +CONFIG_MTD_SPI_NAND=m +CONFIG_MTD_SPI_NOR=m +CONFIG_MTD_UBI=m +CONFIG_OF_CONFIGFS=y +CONFIG_ZRAM=m +CONFIG_ZRAM_BACKEND_LZ4=y +CONFIG_ZRAM_BACKEND_ZSTD=y +CONFIG_ZRAM_BACKEND_LZO=y +CONFIG_ZRAM_DEF_COMP_ZSTD=y +CONFIG_ZRAM_WRITEBACK=y +CONFIG_ZRAM_MULTI_COMP=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_DRBD=m +CONFIG_BLK_DEV_NBD=m +CONFIG_BLK_DEV_RAM=y +CONFIG_ATA_OVER_ETH=m +CONFIG_BLK_DEV_RBD=m +CONFIG_BLK_DEV_NVME=y +CONFIG_NVME_HWMON=y +CONFIG_RP1_PIO=m +CONFIG_WS2812_PIO_RP1=m +CONFIG_SRAM=y +CONFIG_EEPROM_AT24=m +CONFIG_EEPROM_AT25=m +CONFIG_TI_ST=m +CONFIG_SCSI=y +# CONFIG_SCSI_PROC_FS is not set +CONFIG_BLK_DEV_SD=y +CONFIG_CHR_DEV_ST=m +CONFIG_BLK_DEV_SR=m +CONFIG_CHR_DEV_SG=m +CONFIG_SCSI_ISCSI_ATTRS=y +CONFIG_ISCSI_TCP=m +CONFIG_ISCSI_BOOT_SYSFS=m +CONFIG_ATA=m +CONFIG_SATA_AHCI=m +CONFIG_SATA_MV=m +CONFIG_MD=y +CONFIG_BCACHE=m +CONFIG_BLK_DEV_DM=m +CONFIG_DM_CRYPT=m +CONFIG_DM_SNAPSHOT=m +CONFIG_DM_THIN_PROVISIONING=m +CONFIG_DM_CACHE=m +CONFIG_DM_WRITECACHE=m +CONFIG_DM_MIRROR=m +CONFIG_DM_LOG_USERSPACE=m +CONFIG_DM_RAID=m +CONFIG_DM_ZERO=m +CONFIG_DM_MULTIPATH=m +CONFIG_DM_DELAY=m +CONFIG_DM_VERITY=m +CONFIG_DM_INTEGRITY=m +CONFIG_NETDEVICES=y +CONFIG_BONDING=m +CONFIG_DUMMY=m +CONFIG_WIREGUARD=m +CONFIG_IFB=m +CONFIG_MACVLAN=m +CONFIG_MACVTAP=m +CONFIG_IPVLAN=m +CONFIG_VXLAN=m +CONFIG_NETCONSOLE=m +CONFIG_TUN=m +CONFIG_VETH=m +CONFIG_NETKIT=y +CONFIG_NET_VRF=m +CONFIG_VSOCKMON=m +CONFIG_BCMGENET=y +CONFIG_MACB=y +CONFIG_IGB=m +CONFIG_IXGBE=m +CONFIG_I40E=m +CONFIG_IGC=m +CONFIG_ENC28J60=m +CONFIG_LAN743X=m +CONFIG_QCA7000_SPI=m +CONFIG_QCA7000_UART=m +CONFIG_R8169=m +CONFIG_MSE102X=m +CONFIG_WIZNET_W5100=m +CONFIG_WIZNET_W5100_SPI=m +CONFIG_MICREL_PHY=y +CONFIG_CAN_VCAN=m +CONFIG_CAN_SLCAN=m +CONFIG_CAN_MCP251X=m +CONFIG_CAN_MCP251XFD=m +CONFIG_CAN_8DEV_USB=m +CONFIG_CAN_EMS_USB=m +CONFIG_CAN_GS_USB=m +CONFIG_CAN_PEAK_USB=m +CONFIG_MDIO_BITBANG=m +CONFIG_PPP=m +CONFIG_PPP_BSDCOMP=m +CONFIG_PPP_DEFLATE=m +CONFIG_PPP_FILTER=y +CONFIG_PPP_MPPE=m +CONFIG_PPP_MULTILINK=y +CONFIG_PPPOATM=m +CONFIG_PPPOE=m +CONFIG_PPPOL2TP=m +CONFIG_PPP_ASYNC=m +CONFIG_PPP_SYNC_TTY=m +CONFIG_SLIP=m +CONFIG_SLIP_COMPRESSED=y +CONFIG_SLIP_SMART=y +CONFIG_USB_CATC=m +CONFIG_USB_KAWETH=m +CONFIG_USB_PEGASUS=m +CONFIG_USB_RTL8150=m +CONFIG_USB_RTL8152=y +CONFIG_USB_LAN78XX=y +CONFIG_USB_USBNET=y +CONFIG_USB_NET_AX8817X=m +CONFIG_USB_NET_AX88179_178A=m +CONFIG_USB_NET_CDCETHER=m +CONFIG_USB_NET_CDC_EEM=m +CONFIG_USB_NET_CDC_NCM=m +CONFIG_USB_NET_HUAWEI_CDC_NCM=m +CONFIG_USB_NET_CDC_MBIM=m +CONFIG_USB_NET_DM9601=m +CONFIG_USB_NET_SR9700=m +CONFIG_USB_NET_SR9800=m +CONFIG_USB_NET_SMSC75XX=m +CONFIG_USB_NET_SMSC95XX=y +CONFIG_USB_NET_GL620A=m +CONFIG_USB_NET_NET1080=m +CONFIG_USB_NET_PLUSB=m +CONFIG_USB_NET_MCS7830=m +CONFIG_USB_NET_RNDIS_HOST=m +CONFIG_USB_NET_CDC_SUBSET=m +CONFIG_USB_ALI_M5632=y +CONFIG_USB_AN2720=y +CONFIG_USB_EPSON2888=y +CONFIG_USB_KC2190=y +CONFIG_USB_NET_ZAURUS=m +CONFIG_USB_NET_CX82310_ETH=m +CONFIG_USB_NET_KALMIA=m +CONFIG_USB_NET_QMI_WWAN=m +CONFIG_USB_HSO=m +CONFIG_USB_NET_INT51X1=m +CONFIG_USB_IPHETH=m +CONFIG_USB_SIERRA_NET=m +CONFIG_USB_VL600=m +CONFIG_USB_NET_AQC111=m +CONFIG_ATH9K=m +CONFIG_ATH9K_HTC=m +CONFIG_CARL9170=m +CONFIG_ATH6KL=m +CONFIG_ATH6KL_USB=m +CONFIG_AR5523=m +CONFIG_AT76C50X_USB=m +CONFIG_B43=m +# CONFIG_B43_PHY_N is not set +CONFIG_B43LEGACY=m +CONFIG_BRCMFMAC=m +CONFIG_BRCMFMAC_USB=y +CONFIG_BRCMDBG=y +CONFIG_IWLWIFI=m +CONFIG_IWLDVM=m +CONFIG_IWLMVM=m +CONFIG_P54_COMMON=m +CONFIG_P54_USB=m +CONFIG_LIBERTAS=m +CONFIG_LIBERTAS_USB=m +CONFIG_LIBERTAS_SDIO=m +CONFIG_LIBERTAS_THINFIRM=m +CONFIG_LIBERTAS_THINFIRM_USB=m +CONFIG_MWIFIEX=m +CONFIG_MWIFIEX_SDIO=m +CONFIG_MT7601U=m +CONFIG_MT76x0U=m +CONFIG_MT76x2U=m +CONFIG_MT7921U=m +CONFIG_RT2X00=m +CONFIG_RT2500USB=m +CONFIG_RT73USB=m +CONFIG_RT2800USB=m +CONFIG_RT2800USB_RT3573=y +CONFIG_RT2800USB_RT53XX=y +CONFIG_RT2800USB_RT55XX=y +CONFIG_RT2800USB_UNKNOWN=y +CONFIG_RTL8187=m +CONFIG_RTL8192CU=m +CONFIG_RTL8XXXU=m +CONFIG_RTW88=m +CONFIG_RTW88_8822BU=m +CONFIG_RTW88_8822CU=m +CONFIG_RTW88_8723DU=m +CONFIG_RTW88_8821CU=m +CONFIG_ZD1211RW=m +CONFIG_MAC80211_HWSIM=m +CONFIG_IEEE802154_AT86RF230=m +CONFIG_IEEE802154_MRF24J40=m +CONFIG_IEEE802154_CC2520=m +CONFIG_INPUT_MOUSEDEV=y +CONFIG_INPUT_JOYDEV=m +CONFIG_INPUT_EVDEV=y +# CONFIG_KEYBOARD_ATKBD is not set +CONFIG_KEYBOARD_GPIO=m +CONFIG_KEYBOARD_TCA6416=m +CONFIG_KEYBOARD_TCA8418=m +CONFIG_KEYBOARD_MATRIX=m +CONFIG_KEYBOARD_CAP11XX=m +# CONFIG_INPUT_MOUSE is not set +CONFIG_INPUT_JOYSTICK=y +CONFIG_JOYSTICK_IFORCE=m +CONFIG_JOYSTICK_IFORCE_USB=m +CONFIG_JOYSTICK_XPAD=m +CONFIG_JOYSTICK_XPAD_FF=y +CONFIG_JOYSTICK_XPAD_LEDS=y +CONFIG_JOYSTICK_PSXPAD_SPI=m +CONFIG_JOYSTICK_PSXPAD_SPI_FF=y +CONFIG_JOYSTICK_FSIA6B=m +CONFIG_JOYSTICK_SENSEHAT=m +CONFIG_INPUT_TOUCHSCREEN=y +CONFIG_TOUCHSCREEN_ADS7846=m +CONFIG_TOUCHSCREEN_EGALAX=m +CONFIG_TOUCHSCREEN_EXC3000=m +CONFIG_TOUCHSCREEN_GOODIX=m +CONFIG_TOUCHSCREEN_ILI210X=m +CONFIG_TOUCHSCREEN_EDT_FT5X06=m +CONFIG_TOUCHSCREEN_RASPBERRYPI_FW=m +CONFIG_TOUCHSCREEN_USB_COMPOSITE=m +CONFIG_TOUCHSCREEN_TSC2007=m +CONFIG_TOUCHSCREEN_TSC2007_IIO=y +CONFIG_TOUCHSCREEN_STMPE=m +CONFIG_TOUCHSCREEN_IQS5XX=m +CONFIG_INPUT_MISC=y +CONFIG_INPUT_AD714X=m +CONFIG_INPUT_ATI_REMOTE2=m +CONFIG_INPUT_KEYSPAN_REMOTE=m +CONFIG_INPUT_POWERMATE=m +CONFIG_INPUT_YEALINK=m +CONFIG_INPUT_CM109=m +CONFIG_INPUT_UINPUT=m +CONFIG_INPUT_GPIO_ROTARY_ENCODER=m +CONFIG_INPUT_ADXL34X=m +CONFIG_INPUT_CMA3000=m +CONFIG_SERIO=m +CONFIG_SERIO_RAW=m +CONFIG_GAMEPORT=m +CONFIG_BRCM_CHAR_DRIVERS=y +CONFIG_BCM_VCIO=y +# CONFIG_LEGACY_PTYS is not set +CONFIG_SERIAL_8250=y +# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set +CONFIG_SERIAL_8250_CONSOLE=y +# CONFIG_SERIAL_8250_DMA is not set +CONFIG_SERIAL_8250_NR_UARTS=5 +CONFIG_SERIAL_8250_RUNTIME_UARTS=0 +CONFIG_SERIAL_8250_EXTENDED=y +CONFIG_SERIAL_8250_SHARE_IRQ=y +CONFIG_SERIAL_8250_BCM2835AUX=y +CONFIG_SERIAL_OF_PLATFORM=y +CONFIG_SERIAL_AMBA_PL011=y +CONFIG_SERIAL_AMBA_PL011_CONSOLE=y +CONFIG_SERIAL_SC16IS7XX=m +CONFIG_SERIAL_RPI_FW=m +CONFIG_SERIAL_DEV_BUS=y +CONFIG_TTY_PRINTK=y +CONFIG_HW_RANDOM=y +CONFIG_TCG_TPM=m +CONFIG_TCG_TIS_SPI=m +CONFIG_TCG_TIS_I2C=m +CONFIG_XILLYBUS=m +CONFIG_XILLYBUS_PCIE=m +CONFIG_XILLYUSB=m +CONFIG_RASPBERRYPI_GPIOMEM=m +CONFIG_I2C=y +CONFIG_I2C_CHARDEV=m +CONFIG_I2C_MUX_GPMUX=m +CONFIG_I2C_MUX_PCA954x=m +CONFIG_I2C_MUX_PINCTRL=m +CONFIG_I2C_BCM2708=m +CONFIG_I2C_BCM2835=m +CONFIG_I2C_BRCMSTB=m +CONFIG_I2C_DESIGNWARE_CORE=m +CONFIG_I2C_GPIO=m +CONFIG_I2C_ROBOTFUZZ_OSIF=m +CONFIG_I2C_TINY_USB=m +CONFIG_SPI=y +CONFIG_SPI_BCM2835=m +CONFIG_SPI_BCM2835AUX=m +CONFIG_SPI_DESIGNWARE=m +CONFIG_SPI_DW_DMA=y +CONFIG_SPI_DW_MMIO=m +CONFIG_SPI_GPIO=m +CONFIG_SPI_RP2040_GPIO_BRIDGE=m +CONFIG_SPI_SPIDEV=m +CONFIG_SPI_SLAVE=y +CONFIG_PPS_CLIENT_LDISC=m +CONFIG_PPS_CLIENT_GPIO=m +CONFIG_PINCTRL_MCP23S08=m +CONFIG_PINCTRL_RP1=y +CONFIG_PINCTRL_BCM2712=y +CONFIG_GPIO_SYSFS=y +CONFIG_GPIO_BCM_VIRT=y +CONFIG_GPIO_MAX7300=m +CONFIG_GPIO_PCA953X=m +CONFIG_GPIO_PCA953X_IRQ=y +CONFIG_GPIO_PCF857X=m +CONFIG_GPIO_ARIZONA=m +CONFIG_GPIO_FSM=m +CONFIG_GPIO_STMPE=y +CONFIG_GPIO_MAX7301=m +CONFIG_GPIO_MOCKUP=m +CONFIG_W1=m +CONFIG_W1_MASTER_DS2490=m +CONFIG_W1_MASTER_DS2482=m +CONFIG_W1_MASTER_GPIO=m +CONFIG_W1_SLAVE_THERM=m +CONFIG_W1_SLAVE_SMEM=m +CONFIG_W1_SLAVE_DS2408=m +CONFIG_W1_SLAVE_DS2413=m +CONFIG_W1_SLAVE_DS2406=m +CONFIG_W1_SLAVE_DS2423=m +CONFIG_W1_SLAVE_DS2431=m +CONFIG_W1_SLAVE_DS2433=m +CONFIG_W1_SLAVE_DS2438=m +CONFIG_W1_SLAVE_DS2780=m +CONFIG_W1_SLAVE_DS2781=m +CONFIG_W1_SLAVE_DS28E04=m +CONFIG_W1_SLAVE_DS28E17=m +# CONFIG_POWER_RESET_BRCMSTB is not set +CONFIG_POWER_RESET_GPIO=y +CONFIG_RPI_POE_POWER=m +CONFIG_BATTERY_DS2760=m +CONFIG_BATTERY_MAX17040=m +CONFIG_CHARGER_GPIO=m +CONFIG_BATTERY_GAUGE_LTC2941=m +CONFIG_SENSORS_ADT7410=m +CONFIG_SENSORS_AHT10=m +CONFIG_SENSORS_CHIPCAP2=m +CONFIG_SENSORS_DRIVETEMP=m +CONFIG_SENSORS_DS1621=m +CONFIG_SENSORS_GPIO_FAN=m +CONFIG_SENSORS_IIO_HWMON=m +CONFIG_SENSORS_JC42=m +CONFIG_SENSORS_LM75=m +CONFIG_SENSORS_PWM_FAN=m +CONFIG_SENSORS_RASPBERRYPI_HWMON=m +CONFIG_SENSORS_SHT21=m +CONFIG_SENSORS_SHT3x=m +CONFIG_SENSORS_SHT4x=m +CONFIG_SENSORS_SHTC1=m +CONFIG_SENSORS_EMC2305=m +CONFIG_SENSORS_INA2XX=m +CONFIG_SENSORS_INA238=m +CONFIG_SENSORS_TMP102=m +CONFIG_SENSORS_RP1_ADC=m +CONFIG_BCM2711_THERMAL=y +CONFIG_BCM2835_THERMAL=y +CONFIG_WATCHDOG=y +CONFIG_GPIO_WATCHDOG=m +CONFIG_BCM2835_WDT=y +CONFIG_MFD_RASPBERRYPI_POE_HAT=m +CONFIG_MFD_STMPE=y +CONFIG_STMPE_SPI=y +CONFIG_MFD_SYSCON=y +CONFIG_MFD_ARIZONA_I2C=m +CONFIG_MFD_ARIZONA_SPI=m +CONFIG_MFD_WM5102=y +CONFIG_MFD_RP1=y +CONFIG_REGULATOR=y +CONFIG_REGULATOR_FIXED_VOLTAGE=y +CONFIG_REGULATOR_ARIZONA_LDO1=m +CONFIG_REGULATOR_ARIZONA_MICSUPP=m +CONFIG_REGULATOR_GPIO=y +CONFIG_REGULATOR_RASPBERRYPI_TOUCHSCREEN_ATTINY=m +CONFIG_REGULATOR_RASPBERRYPI_TOUCHSCREEN_V2=m +CONFIG_RC_CORE=y +CONFIG_BPF_LIRC_MODE2=y +CONFIG_LIRC=y +CONFIG_RC_DECODERS=y +CONFIG_IR_IMON_DECODER=m +CONFIG_IR_JVC_DECODER=m +CONFIG_IR_MCE_KBD_DECODER=m +CONFIG_IR_NEC_DECODER=m +CONFIG_IR_RC5_DECODER=m +CONFIG_IR_RC6_DECODER=m +CONFIG_IR_SANYO_DECODER=m +CONFIG_IR_SHARP_DECODER=m +CONFIG_IR_SONY_DECODER=m +CONFIG_IR_XMP_DECODER=m +CONFIG_RC_DEVICES=y +CONFIG_IR_GPIO_CIR=m +CONFIG_IR_GPIO_TX=m +CONFIG_IR_IGUANA=m +CONFIG_IR_IMON=m +CONFIG_IR_MCEUSB=m +CONFIG_IR_PWM_TX=m +CONFIG_IR_REDRAT3=m +CONFIG_IR_STREAMZAP=m +CONFIG_IR_TOY=m +CONFIG_IR_TTUSBIR=m +CONFIG_RC_ATI_REMOTE=m +CONFIG_RC_LOOPBACK=m +CONFIG_MEDIA_CEC_RC=y +CONFIG_MEDIA_SUPPORT=m +CONFIG_MEDIA_USB_SUPPORT=y +CONFIG_USB_GSPCA=m +CONFIG_USB_GSPCA_BENQ=m +CONFIG_USB_GSPCA_CONEX=m +CONFIG_USB_GSPCA_CPIA1=m +CONFIG_USB_GSPCA_DTCS033=m +CONFIG_USB_GSPCA_ETOMS=m +CONFIG_USB_GSPCA_FINEPIX=m +CONFIG_USB_GSPCA_JEILINJ=m +CONFIG_USB_GSPCA_JL2005BCD=m +CONFIG_USB_GSPCA_KINECT=m +CONFIG_USB_GSPCA_KONICA=m +CONFIG_USB_GSPCA_MARS=m +CONFIG_USB_GSPCA_MR97310A=m +CONFIG_USB_GSPCA_NW80X=m +CONFIG_USB_GSPCA_OV519=m +CONFIG_USB_GSPCA_OV534=m +CONFIG_USB_GSPCA_OV534_9=m +CONFIG_USB_GSPCA_PAC207=m +CONFIG_USB_GSPCA_PAC7302=m +CONFIG_USB_GSPCA_PAC7311=m +CONFIG_USB_GSPCA_SE401=m +CONFIG_USB_GSPCA_SN9C2028=m +CONFIG_USB_GSPCA_SN9C20X=m +CONFIG_USB_GSPCA_SONIXB=m +CONFIG_USB_GSPCA_SONIXJ=m +CONFIG_USB_GSPCA_SPCA1528=m +CONFIG_USB_GSPCA_SPCA500=m +CONFIG_USB_GSPCA_SPCA501=m +CONFIG_USB_GSPCA_SPCA505=m +CONFIG_USB_GSPCA_SPCA506=m +CONFIG_USB_GSPCA_SPCA508=m +CONFIG_USB_GSPCA_SPCA561=m +CONFIG_USB_GSPCA_SQ905=m +CONFIG_USB_GSPCA_SQ905C=m +CONFIG_USB_GSPCA_SQ930X=m +CONFIG_USB_GSPCA_STK014=m +CONFIG_USB_GSPCA_STK1135=m +CONFIG_USB_GSPCA_STV0680=m +CONFIG_USB_GSPCA_SUNPLUS=m +CONFIG_USB_GSPCA_T613=m +CONFIG_USB_GSPCA_TOPRO=m +CONFIG_USB_GSPCA_TOUPTEK=m +CONFIG_USB_GSPCA_TV8532=m +CONFIG_USB_GSPCA_VC032X=m +CONFIG_USB_GSPCA_VICAM=m +CONFIG_USB_GSPCA_XIRLINK_CIT=m +CONFIG_USB_GSPCA_ZC3XX=m +CONFIG_USB_GL860=m +CONFIG_USB_M5602=m +CONFIG_USB_STV06XX=m +CONFIG_USB_PWC=m +CONFIG_USB_S2255=m +CONFIG_VIDEO_USBTV=m +CONFIG_USB_VIDEO_CLASS=m +CONFIG_VIDEO_GO7007=m +CONFIG_VIDEO_GO7007_USB=m +CONFIG_VIDEO_GO7007_USB_S2250_BOARD=m +CONFIG_VIDEO_HDPVR=m +CONFIG_VIDEO_PVRUSB2=m +CONFIG_VIDEO_STK1160=m +CONFIG_VIDEO_AU0828=m +CONFIG_VIDEO_AU0828_RC=y +CONFIG_VIDEO_CX231XX=m +CONFIG_VIDEO_CX231XX_ALSA=m +CONFIG_VIDEO_CX231XX_DVB=m +CONFIG_DVB_AS102=m +CONFIG_DVB_B2C2_FLEXCOP_USB=m +CONFIG_DVB_USB_V2=m +CONFIG_DVB_USB_AF9015=m +CONFIG_DVB_USB_AF9035=m +CONFIG_DVB_USB_ANYSEE=m +CONFIG_DVB_USB_AU6610=m +CONFIG_DVB_USB_AZ6007=m +CONFIG_DVB_USB_CE6230=m +CONFIG_DVB_USB_DVBSKY=m +CONFIG_DVB_USB_EC168=m +CONFIG_DVB_USB_GL861=m +CONFIG_DVB_USB_LME2510=m +CONFIG_DVB_USB_MXL111SF=m +CONFIG_DVB_USB_RTL28XXU=m +CONFIG_DVB_USB=m +CONFIG_DVB_USB_A800=m +CONFIG_DVB_USB_AF9005=m +CONFIG_DVB_USB_AF9005_REMOTE=m +CONFIG_DVB_USB_AZ6027=m +CONFIG_DVB_USB_CINERGY_T2=m +CONFIG_DVB_USB_CXUSB=m +CONFIG_DVB_USB_DIB0700=m +CONFIG_DVB_USB_DIBUSB_MB=m +CONFIG_DVB_USB_DIBUSB_MB_FAULTY=y +CONFIG_DVB_USB_DIBUSB_MC=m +CONFIG_DVB_USB_DIGITV=m +CONFIG_DVB_USB_DTT200U=m +CONFIG_DVB_USB_DTV5100=m +CONFIG_DVB_USB_DW2102=m +CONFIG_DVB_USB_GP8PSK=m +CONFIG_DVB_USB_M920X=m +CONFIG_DVB_USB_NOVA_T_USB2=m +CONFIG_DVB_USB_OPERA1=m +CONFIG_DVB_USB_PCTV452E=m +CONFIG_DVB_USB_TECHNISAT_USB2=m +CONFIG_DVB_USB_TTUSB2=m +CONFIG_DVB_USB_UMT_010=m +CONFIG_DVB_USB_VP702X=m +CONFIG_DVB_USB_VP7045=m +CONFIG_SMS_USB_DRV=m +CONFIG_VIDEO_EM28XX=m +CONFIG_VIDEO_EM28XX_V4L2=m +CONFIG_VIDEO_EM28XX_ALSA=m +CONFIG_VIDEO_EM28XX_DVB=m +CONFIG_MEDIA_PCI_SUPPORT=y +CONFIG_MEDIA_PCI_HAILO=m +CONFIG_RADIO_SAA7706H=m +CONFIG_RADIO_SHARK=m +CONFIG_RADIO_SHARK2=m +CONFIG_RADIO_SI4713=m +CONFIG_RADIO_TEA5764=m +CONFIG_RADIO_TEF6862=m +CONFIG_RADIO_WL1273=m +CONFIG_USB_DSBR=m +CONFIG_USB_KEENE=m +CONFIG_USB_MA901=m +CONFIG_USB_MR800=m +CONFIG_RADIO_SI470X=m +CONFIG_USB_SI470X=m +CONFIG_I2C_SI470X=m +CONFIG_I2C_SI4713=m +CONFIG_RADIO_WL128X=m +CONFIG_V4L_PLATFORM_DRIVERS=y +CONFIG_VIDEO_MUX=m +CONFIG_VIDEO_BCM2835_UNICAM_LEGACY=m +CONFIG_VIDEO_BCM2835_UNICAM=m +CONFIG_VIDEO_RASPBERRYPI_PISP_BE=m +CONFIG_VIDEO_RP1_CFE=m +CONFIG_V4L_TEST_DRIVERS=y +CONFIG_VIDEO_VIM2M=m +CONFIG_VIDEO_VICODEC=m +CONFIG_VIDEO_VIMC=m +CONFIG_VIDEO_VIVID=m +CONFIG_VIDEO_ARDUCAM_64MP=m +CONFIG_VIDEO_ARDUCAM_PIVARIETY=m +CONFIG_VIDEO_IMX219=m +CONFIG_VIDEO_IMX258=m +CONFIG_VIDEO_IMX290=m +CONFIG_VIDEO_IMX296=m +CONFIG_VIDEO_IMX415=m +CONFIG_VIDEO_IMX477=m +CONFIG_VIDEO_IMX500=m +CONFIG_VIDEO_IMX519=m +CONFIG_VIDEO_IMX708=m +CONFIG_VIDEO_MT9V011=m +CONFIG_VIDEO_OV2311=m +CONFIG_VIDEO_OV5647=m +CONFIG_VIDEO_OV64A40=m +CONFIG_VIDEO_OV7251=m +CONFIG_VIDEO_OV7640=m +CONFIG_VIDEO_OV9282=m +CONFIG_VIDEO_AD5398=m +CONFIG_VIDEO_AK7375=m +CONFIG_VIDEO_BU64754=m +CONFIG_VIDEO_DW9807_VCM=m +CONFIG_VIDEO_SONY_BTF_MPX=m +CONFIG_VIDEO_UDA1342=m +CONFIG_VIDEO_ADV7180=m +CONFIG_VIDEO_TC358743=m +CONFIG_VIDEO_TVP5150=m +CONFIG_VIDEO_TW2804=m +CONFIG_VIDEO_TW9903=m +CONFIG_VIDEO_TW9906=m +CONFIG_VIDEO_IRS1125=m +CONFIG_VIDEO_I2C=m +CONFIG_AUXDISPLAY=y +CONFIG_HD44780=m +CONFIG_DRM=m +CONFIG_DRM_LOAD_EDID_FIRMWARE=y +CONFIG_DRM_UDL=m +CONFIG_DRM_PANEL_ILITEK_ILI9806E=m +CONFIG_DRM_PANEL_ILITEK_ILI9881C=m +CONFIG_DRM_PANEL_JDI_LT070ME05000=m +CONFIG_DRM_PANEL_RASPBERRYPI_TOUCHSCREEN=m +CONFIG_DRM_PANEL_SITRONIX_ST7701=m +CONFIG_DRM_PANEL_SIMPLE=m +CONFIG_DRM_PANEL_TPO_Y17P=m +CONFIG_DRM_PANEL_WAVESHARE_TOUCHSCREEN=m +CONFIG_DRM_DISPLAY_CONNECTOR=m +CONFIG_DRM_SIMPLE_BRIDGE=m +CONFIG_DRM_TOSHIBA_TC358762=m +CONFIG_DRM_V3D=m +CONFIG_DRM_VC4=m +CONFIG_DRM_VC4_HDMI_CEC=y +CONFIG_DRM_RP1_DSI=m +CONFIG_DRM_RP1_DPI=m +CONFIG_DRM_RP1_VEC=m +CONFIG_DRM_PANEL_MIPI_DBI=m +CONFIG_TINYDRM_HX8357D=m +CONFIG_TINYDRM_ILI9225=m +CONFIG_TINYDRM_ILI9341=m +CONFIG_TINYDRM_ILI9486=m +CONFIG_TINYDRM_MI0283QT=m +CONFIG_TINYDRM_REPAPER=m +CONFIG_TINYDRM_ST7586=m +CONFIG_TINYDRM_ST7735R=m +CONFIG_DRM_GUD=m +CONFIG_DRM_SSD130X=m +CONFIG_DRM_SSD130X_I2C=m +CONFIG_DRM_SSD130X_SPI=m +CONFIG_FB=y +CONFIG_FB_BCM2708=y +CONFIG_FB_SIMPLE=y +CONFIG_FB_SSD1307=m +CONFIG_FB_RPISENSE=m +CONFIG_BACKLIGHT_PWM=m +CONFIG_BACKLIGHT_RPI=m +CONFIG_BACKLIGHT_LM3630A=m +CONFIG_BACKLIGHT_GPIO=m +CONFIG_FRAMEBUFFER_CONSOLE_ROTATION=y +CONFIG_LOGO=y +# CONFIG_LOGO_LINUX_MONO is not set +# CONFIG_LOGO_LINUX_VGA16 is not set +CONFIG_SOUND=y +CONFIG_SND=m +CONFIG_SND_OSSEMUL=y +CONFIG_SND_PCM_OSS=m +CONFIG_SND_HRTIMER=m +CONFIG_SND_DYNAMIC_MINORS=y +CONFIG_SND_SEQUENCER=m +CONFIG_SND_SEQ_DUMMY=m +CONFIG_SND_DUMMY=m +CONFIG_SND_ALOOP=m +CONFIG_SND_VIRMIDI=m +CONFIG_SND_MTPAV=m +CONFIG_SND_SERIAL_U16550=m +CONFIG_SND_MPU401=m +CONFIG_SND_PIMIDI=m +CONFIG_SND_USB_AUDIO=m +CONFIG_SND_USB_UA101=m +CONFIG_SND_USB_CAIAQ=m +CONFIG_SND_USB_CAIAQ_INPUT=y +CONFIG_SND_USB_6FIRE=m +CONFIG_SND_USB_HIFACE=m +CONFIG_SND_USB_TONEPORT=m +CONFIG_SND_SOC=m +CONFIG_SND_BCM2835_SOC_I2S=m +CONFIG_SND_BCM2708_SOC_CHIPDIP_DAC=m +CONFIG_SND_BCM2708_SOC_GOOGLEVOICEHAT_SOUNDCARD=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_ADC=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_DAC=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUS=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSHD=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSADC=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSADCPRO=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSDSP=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_DIGI=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_AMP=m +CONFIG_SND_BCM2708_SOC_PIFI_40=m +CONFIG_SND_BCM2708_SOC_RPI_CIRRUS=m +CONFIG_SND_BCM2708_SOC_RPI_DAC=m +CONFIG_SND_BCM2708_SOC_RPI_PROTO=m +CONFIG_SND_BCM2708_SOC_JUSTBOOM_BOTH=m +CONFIG_SND_BCM2708_SOC_JUSTBOOM_DAC=m +CONFIG_SND_BCM2708_SOC_JUSTBOOM_DIGI=m +CONFIG_SND_BCM2708_SOC_IQAUDIO_CODEC=m +CONFIG_SND_BCM2708_SOC_IQAUDIO_DAC=m +CONFIG_SND_BCM2708_SOC_IQAUDIO_DIGI=m +CONFIG_SND_BCM2708_SOC_I_SABRE_Q2M=m +CONFIG_SND_BCM2708_SOC_ADAU1977_ADC=m +CONFIG_SND_AUDIOINJECTOR_PI_SOUNDCARD=m +CONFIG_SND_AUDIOINJECTOR_OCTO_SOUNDCARD=m +CONFIG_SND_AUDIOINJECTOR_ISOLATED_SOUNDCARD=m +CONFIG_SND_AUDIOSENSE_PI=m +CONFIG_SND_DIGIDAC1_SOUNDCARD=m +CONFIG_SND_BCM2708_SOC_DIONAUDIO_LOCO=m +CONFIG_SND_BCM2708_SOC_DIONAUDIO_LOCO_V2=m +CONFIG_SND_BCM2708_SOC_ALLO_PIANO_DAC=m +CONFIG_SND_BCM2708_SOC_ALLO_PIANO_DAC_PLUS=m +CONFIG_SND_BCM2708_SOC_ALLO_BOSS_DAC=m +CONFIG_SND_BCM2708_SOC_ALLO_BOSS2_DAC=m +CONFIG_SND_BCM2708_SOC_ALLO_DIGIONE=m +CONFIG_SND_BCM2708_SOC_ALLO_KATANA_DAC=m +CONFIG_SND_BCM2708_SOC_FE_PI_AUDIO=m +CONFIG_SND_PISOUND=m +CONFIG_SND_DACBERRY400=m +CONFIG_SND_DESIGNWARE_I2S=m +CONFIG_SND_DESIGNWARE_PCM=y +CONFIG_SND_RP1_AUDIO_OUT=m +CONFIG_SND_SOC_AD193X_SPI=m +CONFIG_SND_SOC_AD193X_I2C=m +CONFIG_SND_SOC_ADAU1701=m +CONFIG_SND_SOC_ADAU7002=m +CONFIG_SND_SOC_AK4554=m +CONFIG_SND_SOC_CS4265=m +CONFIG_SND_SOC_ICS43432=m +CONFIG_SND_SOC_MA120X0P=m +CONFIG_SND_SOC_MAX98357A=m +CONFIG_SND_SOC_PCM3168A_I2C=m +CONFIG_SND_SOC_TLV320AIC23_I2C=m +CONFIG_SND_SOC_WM8804_I2C=m +CONFIG_SND_SOC_WM8904=m +CONFIG_SND_SOC_WM8960=m +CONFIG_SND_SIMPLE_CARD=m +CONFIG_HID_BATTERY_STRENGTH=y +CONFIG_HIDRAW=y +CONFIG_UHID=m +CONFIG_HID_A4TECH=m +CONFIG_HID_ACRUX=m +CONFIG_HID_APPLE=m +CONFIG_HID_ASUS=m +CONFIG_HID_BELKIN=m +CONFIG_HID_BETOP_FF=m +CONFIG_HID_BIGBEN_FF=m +CONFIG_HID_CHERRY=m +CONFIG_HID_CHICONY=m +CONFIG_HID_CYPRESS=m +CONFIG_HID_DRAGONRISE=m +CONFIG_HID_EMS_FF=m +CONFIG_HID_ELECOM=m +CONFIG_HID_ELO=m +CONFIG_HID_EZKEY=m +CONFIG_HID_GEMBIRD=m +CONFIG_HID_HOLTEK=m +CONFIG_HID_KEYTOUCH=m +CONFIG_HID_KYE=m +CONFIG_HID_UCLOGIC=m +CONFIG_HID_WALTOP=m +CONFIG_HID_GYRATION=m +CONFIG_HID_TWINHAN=m +CONFIG_HID_KENSINGTON=m +CONFIG_HID_LCPOWER=m +CONFIG_HID_LOGITECH=m +CONFIG_HID_LOGITECH_DJ=m +CONFIG_LOGITECH_FF=y +CONFIG_LOGIRUMBLEPAD2_FF=y +CONFIG_LOGIG940_FF=y +CONFIG_HID_MAGICMOUSE=m +CONFIG_HID_MICROSOFT=m +CONFIG_HID_MONTEREY=m +CONFIG_HID_MULTITOUCH=m +CONFIG_HID_NINTENDO=m +CONFIG_NINTENDO_FF=y +CONFIG_HID_NTRIG=m +CONFIG_HID_ORTEK=m +CONFIG_HID_PANTHERLORD=m +CONFIG_HID_PETALYNX=m +CONFIG_HID_PICOLCD=m +CONFIG_HID_PLAYSTATION=m +CONFIG_PLAYSTATION_FF=y +CONFIG_HID_ROCCAT=m +CONFIG_HID_SAMSUNG=m +CONFIG_HID_SONY=m +CONFIG_SONY_FF=y +CONFIG_HID_SPEEDLINK=m +CONFIG_HID_STEAM=m +CONFIG_HID_SUNPLUS=m +CONFIG_HID_GREENASIA=m +CONFIG_HID_SMARTJOYPLUS=m +CONFIG_HID_TOPSEED=m +CONFIG_HID_THINGM=m +CONFIG_HID_THRUSTMASTER=m +CONFIG_HID_WACOM=m +CONFIG_HID_WIIMOTE=m +CONFIG_HID_XINMO=m +CONFIG_HID_ZEROPLUS=m +CONFIG_HID_ZYDACRON=m +CONFIG_HID_PID=y +CONFIG_USB_HIDDEV=y +CONFIG_I2C_HID_OF=m +CONFIG_USB=y +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y +CONFIG_USB_MON=m +CONFIG_USB_XHCI_HCD=y +CONFIG_USB_XHCI_PCI_RENESAS=m +CONFIG_USB_DWCOTG=y +CONFIG_USB_PRINTER=m +CONFIG_USB_TMC=m +CONFIG_USB_STORAGE=y +CONFIG_USB_STORAGE_REALTEK=m +CONFIG_USB_STORAGE_DATAFAB=m +CONFIG_USB_STORAGE_FREECOM=m +CONFIG_USB_STORAGE_ISD200=m +CONFIG_USB_STORAGE_USBAT=m +CONFIG_USB_STORAGE_SDDR09=m +CONFIG_USB_STORAGE_SDDR55=m +CONFIG_USB_STORAGE_JUMPSHOT=m +CONFIG_USB_STORAGE_ALAUDA=m +CONFIG_USB_STORAGE_ONETOUCH=m +CONFIG_USB_STORAGE_KARMA=m +CONFIG_USB_STORAGE_CYPRESS_ATACB=m +CONFIG_USB_STORAGE_ENE_UB6250=m +CONFIG_USB_UAS=y +CONFIG_USB_MDC800=m +CONFIG_USB_MICROTEK=m +CONFIG_USBIP_CORE=m +CONFIG_USBIP_VHCI_HCD=m +CONFIG_USBIP_HOST=m +CONFIG_USBIP_VUDC=m +CONFIG_USB_DWC3=y +CONFIG_USB_DWC2=m +CONFIG_USB_SERIAL=m +CONFIG_USB_SERIAL_GENERIC=y +CONFIG_USB_SERIAL_SIMPLE=m +CONFIG_USB_SERIAL_AIRCABLE=m +CONFIG_USB_SERIAL_ARK3116=m +CONFIG_USB_SERIAL_BELKIN=m +CONFIG_USB_SERIAL_CH341=m +CONFIG_USB_SERIAL_WHITEHEAT=m +CONFIG_USB_SERIAL_DIGI_ACCELEPORT=m +CONFIG_USB_SERIAL_CP210X=m +CONFIG_USB_SERIAL_CYPRESS_M8=m +CONFIG_USB_SERIAL_EMPEG=m +CONFIG_USB_SERIAL_FTDI_SIO=m +CONFIG_USB_SERIAL_VISOR=m +CONFIG_USB_SERIAL_IPAQ=m +CONFIG_USB_SERIAL_IR=m +CONFIG_USB_SERIAL_EDGEPORT=m +CONFIG_USB_SERIAL_EDGEPORT_TI=m +CONFIG_USB_SERIAL_F81232=m +CONFIG_USB_SERIAL_F8153X=m +CONFIG_USB_SERIAL_GARMIN=m +CONFIG_USB_SERIAL_IPW=m +CONFIG_USB_SERIAL_IUU=m +CONFIG_USB_SERIAL_KEYSPAN_PDA=m +CONFIG_USB_SERIAL_KEYSPAN=m +CONFIG_USB_SERIAL_KLSI=m +CONFIG_USB_SERIAL_KOBIL_SCT=m +CONFIG_USB_SERIAL_MCT_U232=m +CONFIG_USB_SERIAL_METRO=m +CONFIG_USB_SERIAL_MOS7720=m +CONFIG_USB_SERIAL_MOS7840=m +CONFIG_USB_SERIAL_MXUPORT=m +CONFIG_USB_SERIAL_NAVMAN=m +CONFIG_USB_SERIAL_PL2303=m +CONFIG_USB_SERIAL_OTI6858=m +CONFIG_USB_SERIAL_QCAUX=m +CONFIG_USB_SERIAL_QUALCOMM=m +CONFIG_USB_SERIAL_SPCP8X5=m +CONFIG_USB_SERIAL_SAFE=m +CONFIG_USB_SERIAL_SIERRAWIRELESS=m +CONFIG_USB_SERIAL_SYMBOL=m +CONFIG_USB_SERIAL_TI=m +CONFIG_USB_SERIAL_CYBERJACK=m +CONFIG_USB_SERIAL_OPTION=m +CONFIG_USB_SERIAL_OMNINET=m +CONFIG_USB_SERIAL_OPTICON=m +CONFIG_USB_SERIAL_XSENS_MT=m +CONFIG_USB_SERIAL_WISHBONE=m +CONFIG_USB_SERIAL_SSU100=m +CONFIG_USB_SERIAL_QT2=m +CONFIG_USB_SERIAL_UPD78F0730=m +CONFIG_USB_SERIAL_XR=m +CONFIG_USB_SERIAL_DEBUG=m +CONFIG_USB_EMI62=m +CONFIG_USB_EMI26=m +CONFIG_USB_ADUTUX=m +CONFIG_USB_SEVSEG=m +CONFIG_USB_LEGOTOWER=m +CONFIG_USB_LCD=m +CONFIG_USB_CYPRESS_CY7C63=m +CONFIG_USB_CYTHERM=m +CONFIG_USB_IDMOUSE=m +CONFIG_USB_APPLEDISPLAY=m +CONFIG_USB_LD=m +CONFIG_USB_TRANCEVIBRATOR=m +CONFIG_USB_IOWARRIOR=m +CONFIG_USB_TEST=m +CONFIG_USB_ISIGHTFW=m +CONFIG_USB_YUREX=m +CONFIG_USB_ATM=m +CONFIG_USB_SPEEDTOUCH=m +CONFIG_USB_CXACRU=m +CONFIG_USB_UEAGLEATM=m +CONFIG_USB_XUSBATM=m +CONFIG_NOP_USB_XCEIV=y +CONFIG_USB_GADGET=y +CONFIG_USB_CONFIGFS=m +CONFIG_USB_CONFIGFS_SERIAL=y +CONFIG_USB_CONFIGFS_ACM=y +CONFIG_USB_CONFIGFS_OBEX=y +CONFIG_USB_CONFIGFS_NCM=y +CONFIG_USB_CONFIGFS_ECM=y +CONFIG_USB_CONFIGFS_ECM_SUBSET=y +CONFIG_USB_CONFIGFS_RNDIS=y +CONFIG_USB_CONFIGFS_EEM=y +CONFIG_USB_CONFIGFS_MASS_STORAGE=y +CONFIG_USB_CONFIGFS_F_LB_SS=y +CONFIG_USB_CONFIGFS_F_FS=y +CONFIG_USB_CONFIGFS_F_UAC1=y +CONFIG_USB_CONFIGFS_F_UAC2=y +CONFIG_USB_CONFIGFS_F_MIDI=y +CONFIG_USB_CONFIGFS_F_HID=y +CONFIG_USB_CONFIGFS_F_UVC=y +CONFIG_USB_CONFIGFS_F_PRINTER=y +CONFIG_USB_ZERO=m +CONFIG_USB_AUDIO=m +CONFIG_USB_ETH=m +CONFIG_USB_GADGETFS=m +CONFIG_USB_MASS_STORAGE=m +CONFIG_USB_G_SERIAL=m +CONFIG_USB_MIDI_GADGET=m +CONFIG_USB_G_PRINTER=m +CONFIG_USB_CDC_COMPOSITE=m +CONFIG_USB_G_ACM_MS=m +CONFIG_USB_G_MULTI=m +CONFIG_USB_G_HID=m +CONFIG_USB_G_WEBCAM=m +CONFIG_MMC=y +CONFIG_MMC_BLOCK_MINORS=32 +CONFIG_MMC_BCM2835_MMC=y +CONFIG_MMC_BCM2835_DMA=y +CONFIG_MMC_SDHCI=y +CONFIG_MMC_SDHCI_PLTFM=y +CONFIG_MMC_SDHCI_OF_DWCMSHC=m +CONFIG_MMC_SDHCI_IPROC=y +CONFIG_MMC_SPI=m +CONFIG_MMC_HSQ=y +CONFIG_MMC_BCM2835=y +CONFIG_LEDS_CLASS=y +CONFIG_LEDS_CLASS_MULTICOLOR=m +CONFIG_LEDS_PCA9532=m +CONFIG_LEDS_GPIO=y +CONFIG_LEDS_PCA955X=m +CONFIG_LEDS_PCA963X=m +CONFIG_LEDS_PWM=y +CONFIG_LEDS_IS31FL32XX=m +CONFIG_LEDS_TRIGGER_TIMER=y +CONFIG_LEDS_TRIGGER_ONESHOT=y +CONFIG_LEDS_TRIGGER_HEARTBEAT=y +CONFIG_LEDS_TRIGGER_BACKLIGHT=y +CONFIG_LEDS_TRIGGER_CPU=y +CONFIG_LEDS_TRIGGER_DEFAULT_ON=y +CONFIG_LEDS_TRIGGER_TRANSIENT=m +CONFIG_LEDS_TRIGGER_CAMERA=m +CONFIG_LEDS_TRIGGER_INPUT=y +CONFIG_LEDS_TRIGGER_PANIC=y +CONFIG_LEDS_TRIGGER_NETDEV=m +CONFIG_LEDS_TRIGGER_PATTERN=m +CONFIG_LEDS_TRIGGER_ACTPWR=y +CONFIG_ACCESSIBILITY=y +CONFIG_SPEAKUP=m +CONFIG_SPEAKUP_SYNTH_SOFT=m +CONFIG_RTC_CLASS=y +CONFIG_RTC_DRV_ABX80X=m +CONFIG_RTC_DRV_DS1307=m +CONFIG_RTC_DRV_DS1374=m +CONFIG_RTC_DRV_DS1672=m +CONFIG_RTC_DRV_MAX6900=m +CONFIG_RTC_DRV_RS5C372=m +CONFIG_RTC_DRV_ISL1208=m +CONFIG_RTC_DRV_ISL12022=m +CONFIG_RTC_DRV_X1205=m +CONFIG_RTC_DRV_PCF8523=m +CONFIG_RTC_DRV_PCF85063=m +CONFIG_RTC_DRV_PCF85363=m +CONFIG_RTC_DRV_PCF8563=m +CONFIG_RTC_DRV_PCF8583=m +CONFIG_RTC_DRV_M41T80=m +CONFIG_RTC_DRV_BQ32K=m +CONFIG_RTC_DRV_S35390A=m +CONFIG_RTC_DRV_FM3130=m +CONFIG_RTC_DRV_RX8581=m +CONFIG_RTC_DRV_RX8025=m +CONFIG_RTC_DRV_EM3027=m +CONFIG_RTC_DRV_RV3028=m +CONFIG_RTC_DRV_RV3032=m +CONFIG_RTC_DRV_RV8803=m +CONFIG_RTC_DRV_SD3078=m +CONFIG_RTC_DRV_M41T93=m +CONFIG_RTC_DRV_M41T94=m +CONFIG_RTC_DRV_DS1302=m +CONFIG_RTC_DRV_DS1305=m +CONFIG_RTC_DRV_DS1390=m +CONFIG_RTC_DRV_R9701=m +CONFIG_RTC_DRV_RX4581=m +CONFIG_RTC_DRV_RS5C348=m +CONFIG_RTC_DRV_MAX6902=m +CONFIG_RTC_DRV_PCF2123=m +CONFIG_RTC_DRV_DS3232=m +CONFIG_RTC_DRV_PCF2127=m +CONFIG_RTC_DRV_RV3029C2=m +CONFIG_DMADEVICES=y +CONFIG_DMA_BCM2835=y +CONFIG_DW_AXI_DMAC=y +CONFIG_DMA_BCM2708=y +CONFIG_DMABUF_HEAPS=y +CONFIG_DMABUF_HEAPS_SYSTEM=y +CONFIG_DMABUF_HEAPS_CMA=y +CONFIG_UIO=m +CONFIG_UIO_PDRV_GENIRQ=m +CONFIG_VHOST_NET=m +CONFIG_VHOST_VSOCK=m +CONFIG_VHOST_CROSS_ENDIAN_LEGACY=y +CONFIG_STAGING=y +CONFIG_R8712U=m +CONFIG_VT6656=m +CONFIG_STAGING_MEDIA=y +CONFIG_VIDEO_RPIVID=m +CONFIG_STAGING_MEDIA_DEPRECATED=y +CONFIG_FB_TFT=m +CONFIG_FB_TFT_AGM1264K_FL=m +CONFIG_FB_TFT_BD663474=m +CONFIG_FB_TFT_HX8340BN=m +CONFIG_FB_TFT_HX8347D=m +CONFIG_FB_TFT_HX8353D=m +CONFIG_FB_TFT_HX8357D=m +CONFIG_FB_TFT_ILI9163=m +CONFIG_FB_TFT_ILI9320=m +CONFIG_FB_TFT_ILI9325=m +CONFIG_FB_TFT_ILI9340=m +CONFIG_FB_TFT_ILI9341=m +CONFIG_FB_TFT_ILI9481=m +CONFIG_FB_TFT_ILI9486=m +CONFIG_FB_TFT_PCD8544=m +CONFIG_FB_TFT_RA8875=m +CONFIG_FB_TFT_S6D02A1=m +CONFIG_FB_TFT_S6D1121=m +CONFIG_FB_TFT_SH1106=m +CONFIG_FB_TFT_SSD1289=m +CONFIG_FB_TFT_SSD1306=m +CONFIG_FB_TFT_SSD1331=m +CONFIG_FB_TFT_SSD1351=m +CONFIG_FB_TFT_ST7735R=m +CONFIG_FB_TFT_ST7789V=m +CONFIG_FB_TFT_TINYLCD=m +CONFIG_FB_TFT_TLS8204=m +CONFIG_FB_TFT_UC1611=m +CONFIG_FB_TFT_UC1701=m +CONFIG_FB_TFT_UPD161704=m +CONFIG_BCM2835_VCHIQ=y +CONFIG_SND_BCM2835=m +CONFIG_VIDEO_BCM2835=m +CONFIG_VIDEO_CODEC_BCM2835=m +CONFIG_VIDEO_ISP_BCM2835=m +CONFIG_COMMON_CLK_RP1=y +CONFIG_COMMON_CLK_RP1_SDIO=y +CONFIG_CLK_RASPBERRYPI=y +CONFIG_MAILBOX=y +CONFIG_BCM2835_MBOX=y +CONFIG_MBOX_RP1=m +CONFIG_BCM2712_IOMMU=y +CONFIG_RASPBERRYPI_POWER=y +CONFIG_IIO=m +CONFIG_IIO_BUFFER_CB=m +CONFIG_IIO_SW_TRIGGER=m +CONFIG_MCP320X=m +CONFIG_MCP3422=m +CONFIG_TI_ADS1015=m +CONFIG_BME680=m +CONFIG_CCS811=m +CONFIG_SENSIRION_SGP30=m +CONFIG_SPS30_I2C=m +CONFIG_MAX30102=m +CONFIG_DHT11=m +CONFIG_HDC100X=m +CONFIG_HDC3020=m +CONFIG_HTS221=m +CONFIG_HTU21=m +CONFIG_SI7020=m +CONFIG_BOSCH_BNO055_I2C=m +CONFIG_INV_MPU6050_I2C=m +CONFIG_APDS9960=m +CONFIG_AS73211=m +CONFIG_BH1750=m +CONFIG_TSL4531=m +CONFIG_VEML6070=m +CONFIG_VEML6075=m +CONFIG_IIO_HRTIMER_TRIGGER=m +CONFIG_IIO_INTERRUPT_TRIGGER=m +CONFIG_IIO_SYSFS_TRIGGER=m +CONFIG_BMP280=m +CONFIG_MS5637=m +CONFIG_MAXIM_THERMOCOUPLE=m +CONFIG_MAX31856=m +CONFIG_PWM=y +CONFIG_PWM_BCM2835=m +CONFIG_PWM_BRCMSTB=y +CONFIG_PWM_GPIO=m +CONFIG_PWM_PCA9685=m +CONFIG_PWM_PIO_RP1=m +CONFIG_PWM_RASPBERRYPI_POE=m +CONFIG_PWM_RP1=y +CONFIG_BCM2712_MIP=y +CONFIG_RPI_AXIPERF=m +CONFIG_ANDROID_BINDER_IPC=y +CONFIG_ANDROID_BINDERFS=y +CONFIG_NVMEM_RMEM=m +CONFIG_MUX_GPIO=m +CONFIG_EXT4_FS=y +CONFIG_EXT4_FS_POSIX_ACL=y +CONFIG_EXT4_FS_SECURITY=y +CONFIG_REISERFS_FS=m +CONFIG_REISERFS_FS_XATTR=y +CONFIG_REISERFS_FS_POSIX_ACL=y +CONFIG_REISERFS_FS_SECURITY=y +CONFIG_JFS_FS=m +CONFIG_JFS_POSIX_ACL=y +CONFIG_JFS_SECURITY=y +CONFIG_JFS_STATISTICS=y +CONFIG_XFS_FS=m +CONFIG_XFS_QUOTA=y +CONFIG_XFS_POSIX_ACL=y +CONFIG_XFS_RT=y +CONFIG_GFS2_FS=m +CONFIG_OCFS2_FS=m +CONFIG_BTRFS_FS=m +CONFIG_BTRFS_FS_POSIX_ACL=y +CONFIG_NILFS2_FS=m +CONFIG_F2FS_FS=y +CONFIG_F2FS_FS_SECURITY=y +CONFIG_BCACHEFS_FS=m +CONFIG_BCACHEFS_QUOTA=y +CONFIG_BCACHEFS_POSIX_ACL=y +CONFIG_FS_ENCRYPTION=y +CONFIG_FANOTIFY=y +CONFIG_QFMT_V1=m +CONFIG_QFMT_V2=m +CONFIG_AUTOFS_FS=y +CONFIG_FUSE_FS=m +CONFIG_CUSE=m +CONFIG_OVERLAY_FS=m +CONFIG_FSCACHE=y +CONFIG_FSCACHE_STATS=y +CONFIG_CACHEFILES=m +CONFIG_ISO9660_FS=m +CONFIG_JOLIET=y +CONFIG_ZISOFS=y +CONFIG_UDF_FS=m +CONFIG_MSDOS_FS=y +CONFIG_VFAT_FS=y +CONFIG_FAT_DEFAULT_IOCHARSET="ascii" +CONFIG_EXFAT_FS=m +CONFIG_NTFS3_FS=m +CONFIG_TMPFS=y +CONFIG_TMPFS_POSIX_ACL=y +CONFIG_ECRYPT_FS=m +CONFIG_HFS_FS=m +CONFIG_HFSPLUS_FS=m +CONFIG_JFFS2_FS=m +CONFIG_JFFS2_SUMMARY=y +CONFIG_UBIFS_FS=m +CONFIG_SQUASHFS=m +CONFIG_SQUASHFS_XATTR=y +CONFIG_SQUASHFS_LZO=y +CONFIG_SQUASHFS_XZ=y +CONFIG_SQUASHFS_ZSTD=y +CONFIG_PSTORE=y +CONFIG_PSTORE_CONSOLE=y +CONFIG_PSTORE_RAM=y +CONFIG_NFS_FS=y +CONFIG_NFS_V2=y +CONFIG_NFS_V3_ACL=y +CONFIG_NFS_V4=y +CONFIG_NFS_SWAP=y +CONFIG_NFS_V4_1=y +CONFIG_NFS_V4_2=y +CONFIG_ROOT_NFS=y +CONFIG_NFSD=m +CONFIG_NFSD_V2=y +CONFIG_NFSD_V2_ACL=y +CONFIG_NFSD_V3_ACL=y +CONFIG_NFSD_V4=y +CONFIG_CEPH_FS=m +CONFIG_CIFS=m +CONFIG_CIFS_UPCALL=y +CONFIG_CIFS_XATTR=y +CONFIG_CIFS_POSIX=y +CONFIG_CIFS_DFS_UPCALL=y +CONFIG_CIFS_FSCACHE=y +CONFIG_SMB_SERVER=m +CONFIG_9P_FS=m +CONFIG_9P_FS_POSIX_ACL=y +CONFIG_NLS_DEFAULT="utf8" +CONFIG_NLS_CODEPAGE_437=y +CONFIG_NLS_CODEPAGE_737=m +CONFIG_NLS_CODEPAGE_775=m +CONFIG_NLS_CODEPAGE_850=m +CONFIG_NLS_CODEPAGE_852=m +CONFIG_NLS_CODEPAGE_855=m +CONFIG_NLS_CODEPAGE_857=m +CONFIG_NLS_CODEPAGE_860=m +CONFIG_NLS_CODEPAGE_861=m +CONFIG_NLS_CODEPAGE_862=m +CONFIG_NLS_CODEPAGE_863=m +CONFIG_NLS_CODEPAGE_864=m +CONFIG_NLS_CODEPAGE_865=m +CONFIG_NLS_CODEPAGE_866=m +CONFIG_NLS_CODEPAGE_869=m +CONFIG_NLS_CODEPAGE_936=m +CONFIG_NLS_CODEPAGE_950=m +CONFIG_NLS_CODEPAGE_932=m +CONFIG_NLS_CODEPAGE_949=m +CONFIG_NLS_CODEPAGE_874=m +CONFIG_NLS_ISO8859_8=m +CONFIG_NLS_CODEPAGE_1250=m +CONFIG_NLS_CODEPAGE_1251=m +CONFIG_NLS_ASCII=y +CONFIG_NLS_ISO8859_1=m +CONFIG_NLS_ISO8859_2=m +CONFIG_NLS_ISO8859_3=m +CONFIG_NLS_ISO8859_4=m +CONFIG_NLS_ISO8859_5=m +CONFIG_NLS_ISO8859_6=m +CONFIG_NLS_ISO8859_7=m +CONFIG_NLS_ISO8859_9=m +CONFIG_NLS_ISO8859_13=m +CONFIG_NLS_ISO8859_14=m +CONFIG_NLS_ISO8859_15=m +CONFIG_NLS_KOI8_R=m +CONFIG_NLS_KOI8_U=m +CONFIG_DLM=m +CONFIG_KEY_DH_OPERATIONS=y +CONFIG_SECURITY=y +CONFIG_SECURITY_APPARMOR=y +CONFIG_LSM="" +CONFIG_CRYPTO_USER=m +CONFIG_CRYPTO_CRYPTD=m +CONFIG_CRYPTO_AES=m +CONFIG_CRYPTO_CAST5=m +CONFIG_CRYPTO_DES=y +CONFIG_CRYPTO_TWOFISH=m +CONFIG_CRYPTO_ADIANTUM=m +CONFIG_CRYPTO_CBC=m +CONFIG_CRYPTO_CHACHA20POLY1305=m +CONFIG_CRYPTO_MD4=m +CONFIG_CRYPTO_SHA512=m +CONFIG_CRYPTO_WP512=m +CONFIG_CRYPTO_XCBC=m +CONFIG_CRYPTO_LZ4=m +CONFIG_CRYPTO_USER_API_HASH=m +CONFIG_CRYPTO_USER_API_SKCIPHER=m +CONFIG_CRYPTO_USER_API_RNG=m +CONFIG_CRYPTO_USER_API_AEAD=m +CONFIG_CRYPTO_NHPOLY1305_NEON=m +CONFIG_CRYPTO_GHASH_ARM64_CE=m +CONFIG_CRYPTO_SHA1_ARM64_CE=m +CONFIG_CRYPTO_SHA2_ARM64_CE=m +CONFIG_CRYPTO_SHA512_ARM64_CE=m +CONFIG_CRYPTO_SHA3_ARM64=m +CONFIG_CRYPTO_SM3_ARM64_CE=m +CONFIG_CRYPTO_AES_ARM64=m +CONFIG_CRYPTO_AES_ARM64_BS=m +CONFIG_CRYPTO_SM4_ARM64_CE=m +CONFIG_CRYPTO_AES_ARM64_CE_CCM=m +# CONFIG_CRYPTO_HW is not set +CONFIG_PKCS8_PRIVATE_KEY_PARSER=m +CONFIG_CRC_ITU_T=y +CONFIG_LIBCRC32C=y +CONFIG_DMA_CMA=y +CONFIG_CMA_SIZE_MBYTES=5 +CONFIG_PRINTK_TIME=y +CONFIG_BOOT_PRINTK_DELAY=y +CONFIG_MAGIC_SYSRQ_DEFAULT_ENABLE=0x1f6 +CONFIG_KGDB=y +CONFIG_KGDB_KDB=y +CONFIG_KDB_KEYBOARD=y +CONFIG_DEBUG_MEMORY_INIT=y +CONFIG_DETECT_HUNG_TASK=y +CONFIG_LATENCYTOP=y +CONFIG_FUNCTION_PROFILER=y +CONFIG_STACK_TRACER=y +CONFIG_SCHED_TRACER=y +CONFIG_BLK_DEV_IO_TRACE=y +# CONFIG_UPROBE_EVENTS is not set +# CONFIG_STRICT_DEVMEM is not set diff --git a/arch/arm64/configs/bcm2712_defconfig b/arch/arm64/configs/bcm2712_defconfig new file mode 100644 index 00000000000000..94cbd28ff74c54 --- /dev/null +++ b/arch/arm64/configs/bcm2712_defconfig @@ -0,0 +1,1735 @@ +CONFIG_LOCALVERSION="-v8-16k" +# CONFIG_LOCALVERSION_AUTO is not set +CONFIG_SYSVIPC=y +CONFIG_POSIX_MQUEUE=y +CONFIG_GENERIC_IRQ_DEBUGFS=y +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_BPF_SYSCALL=y +CONFIG_BPF_JIT=y +CONFIG_PREEMPT=y +CONFIG_BSD_PROCESS_ACCT=y +CONFIG_BSD_PROCESS_ACCT_V3=y +CONFIG_TASKSTATS=y +CONFIG_TASK_DELAY_ACCT=y +CONFIG_TASK_XACCT=y +CONFIG_TASK_IO_ACCOUNTING=y +CONFIG_PSI=y +CONFIG_PSI_DEFAULT_DISABLED=y +CONFIG_IKCONFIG=m +CONFIG_IKCONFIG_PROC=y +CONFIG_MEMCG=y +CONFIG_BLK_CGROUP=y +CONFIG_CFS_BANDWIDTH=y +CONFIG_CGROUP_PIDS=y +CONFIG_CGROUP_FREEZER=y +CONFIG_CPUSETS=y +CONFIG_CPUSETS_V1=y +CONFIG_CGROUP_DEVICE=y +CONFIG_CGROUP_CPUACCT=y +CONFIG_CGROUP_PERF=y +CONFIG_CGROUP_BPF=y +CONFIG_NAMESPACES=y +CONFIG_USER_NS=y +CONFIG_CHECKPOINT_RESTORE=y +CONFIG_SCHED_AUTOGROUP=y +CONFIG_BLK_DEV_INITRD=y +CONFIG_EXPERT=y +CONFIG_PROFILING=y +CONFIG_ARCH_BCM=y +CONFIG_ARCH_BCM2835=y +CONFIG_ARCH_BRCMSTB=y +CONFIG_ARM64_ERRATUM_834220=y +CONFIG_ARM64_ERRATUM_2441007=y +CONFIG_ARM64_ERRATUM_1286807=y +CONFIG_ARM64_ERRATUM_1542419=y +CONFIG_ARM64_ERRATUM_2441009=y +# CONFIG_CAVIUM_ERRATUM_22375 is not set +# CONFIG_CAVIUM_ERRATUM_23154 is not set +# CONFIG_CAVIUM_ERRATUM_27456 is not set +CONFIG_ARM64_16K_PAGES=y +CONFIG_ARM64_VA_BITS_47=y +CONFIG_NR_CPUS=4 +CONFIG_HOTPLUG_CPU=y +CONFIG_NUMA=y +CONFIG_COMPAT=y +CONFIG_ARMV8_DEPRECATED=y +CONFIG_SWP_EMULATION=y +CONFIG_CP15_BARRIER_EMULATION=y +CONFIG_SETEND_EMULATION=y +CONFIG_RANDOMIZE_BASE=y +CONFIG_CMDLINE="console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 root=/dev/mmcblk0p2 rootfstype=ext4 rootwait" +# CONFIG_SUSPEND is not set +CONFIG_PM=y +CONFIG_PM_DEBUG=y +CONFIG_CPU_IDLE=y +CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_STAT=y +CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND=y +CONFIG_CPU_FREQ_GOV_POWERSAVE=y +CONFIG_CPU_FREQ_GOV_USERSPACE=y +CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y +CONFIG_CPU_FREQ_GOV_SCHEDUTIL=y +CONFIG_CPUFREQ_DT=y +CONFIG_ARM_RASPBERRYPI_CPUFREQ=y +CONFIG_VIRTUALIZATION=y +CONFIG_KVM=y +CONFIG_JUMP_LABEL=y +CONFIG_ARCH_MMAP_RND_BITS=18 +CONFIG_ARCH_MMAP_RND_COMPAT_BITS=11 +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_MODVERSIONS=y +CONFIG_MODULE_SRCVERSION_ALL=y +CONFIG_MODULE_COMPRESS=y +CONFIG_MODULE_COMPRESS_XZ=y +CONFIG_BLK_DEV_THROTTLING=y +CONFIG_PARTITION_ADVANCED=y +CONFIG_MAC_PARTITION=y +CONFIG_BINFMT_MISC=m +CONFIG_ZSWAP=y +# CONFIG_COMPAT_BRK is not set +CONFIG_CMA=y +CONFIG_CMA_AREAS=7 +CONFIG_LRU_GEN=y +CONFIG_LRU_GEN_ENABLED=y +CONFIG_NUMA_EMU=y +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_XFRM_USER=m +CONFIG_XFRM_INTERFACE=m +CONFIG_XFRM_SUB_POLICY=y +CONFIG_XFRM_STATISTICS=y +CONFIG_NET_KEY=m +CONFIG_IP_MULTICAST=y +CONFIG_IP_ADVANCED_ROUTER=y +CONFIG_IP_MULTIPLE_TABLES=y +CONFIG_IP_ROUTE_MULTIPATH=y +CONFIG_IP_ROUTE_VERBOSE=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y +CONFIG_IP_PNP_RARP=y +CONFIG_NET_IPIP=m +CONFIG_NET_IPGRE_DEMUX=m +CONFIG_NET_IPGRE=m +CONFIG_IP_MROUTE=y +CONFIG_IP_MROUTE_MULTIPLE_TABLES=y +CONFIG_IP_PIMSM_V1=y +CONFIG_IP_PIMSM_V2=y +CONFIG_NET_IPVTI=m +CONFIG_NET_FOU_IP_TUNNELS=y +CONFIG_INET_AH=m +CONFIG_INET_ESP=m +CONFIG_INET_IPCOMP=m +CONFIG_INET_DIAG=m +CONFIG_TCP_CONG_ADVANCED=y +CONFIG_TCP_CONG_BBR=m +CONFIG_IPV6=m +CONFIG_IPV6_ROUTER_PREF=y +CONFIG_IPV6_ROUTE_INFO=y +CONFIG_INET6_AH=m +CONFIG_INET6_ESP=m +CONFIG_INET6_ESP_OFFLOAD=m +CONFIG_INET6_IPCOMP=m +CONFIG_IPV6_ILA=m +CONFIG_IPV6_VTI=m +CONFIG_IPV6_SIT_6RD=y +CONFIG_IPV6_GRE=m +CONFIG_IPV6_MULTIPLE_TABLES=y +CONFIG_IPV6_SUBTREES=y +CONFIG_IPV6_MROUTE=y +CONFIG_IPV6_MROUTE_MULTIPLE_TABLES=y +CONFIG_IPV6_PIMSM_V2=y +CONFIG_MPTCP=y +CONFIG_NETWORK_PHY_TIMESTAMPING=y +CONFIG_NETFILTER=y +CONFIG_BRIDGE_NETFILTER=m +CONFIG_NF_CONNTRACK=m +CONFIG_NF_CONNTRACK_ZONES=y +CONFIG_NF_CONNTRACK_EVENTS=y +CONFIG_NF_CONNTRACK_TIMESTAMP=y +CONFIG_NF_CONNTRACK_AMANDA=m +CONFIG_NF_CONNTRACK_FTP=m +CONFIG_NF_CONNTRACK_H323=m +CONFIG_NF_CONNTRACK_IRC=m +CONFIG_NF_CONNTRACK_NETBIOS_NS=m +CONFIG_NF_CONNTRACK_SNMP=m +CONFIG_NF_CONNTRACK_PPTP=m +CONFIG_NF_CONNTRACK_SANE=m +CONFIG_NF_CONNTRACK_SIP=m +CONFIG_NF_CONNTRACK_TFTP=m +CONFIG_NF_CT_NETLINK=m +CONFIG_NF_TABLES=m +CONFIG_NF_TABLES_INET=y +CONFIG_NF_TABLES_NETDEV=y +CONFIG_NFT_NUMGEN=m +CONFIG_NFT_CT=m +CONFIG_NFT_FLOW_OFFLOAD=m +CONFIG_NFT_CONNLIMIT=m +CONFIG_NFT_LOG=m +CONFIG_NFT_LIMIT=m +CONFIG_NFT_MASQ=m +CONFIG_NFT_REDIR=m +CONFIG_NFT_NAT=m +CONFIG_NFT_TUNNEL=m +CONFIG_NFT_QUEUE=m +CONFIG_NFT_QUOTA=m +CONFIG_NFT_REJECT=m +CONFIG_NFT_COMPAT=m +CONFIG_NFT_HASH=m +CONFIG_NFT_FIB_INET=m +CONFIG_NFT_XFRM=m +CONFIG_NFT_SOCKET=m +CONFIG_NFT_OSF=m +CONFIG_NFT_TPROXY=m +CONFIG_NFT_SYNPROXY=m +CONFIG_NFT_DUP_NETDEV=m +CONFIG_NFT_FWD_NETDEV=m +CONFIG_NFT_FIB_NETDEV=m +CONFIG_NF_FLOW_TABLE_INET=m +CONFIG_NF_FLOW_TABLE=m +CONFIG_NETFILTER_XTABLES_COMPAT=y +CONFIG_NETFILTER_XT_SET=m +CONFIG_NETFILTER_XT_TARGET_CHECKSUM=m +CONFIG_NETFILTER_XT_TARGET_CLASSIFY=m +CONFIG_NETFILTER_XT_TARGET_CONNMARK=m +CONFIG_NETFILTER_XT_TARGET_DSCP=m +CONFIG_NETFILTER_XT_TARGET_HMARK=m +CONFIG_NETFILTER_XT_TARGET_IDLETIMER=m +CONFIG_NETFILTER_XT_TARGET_LED=m +CONFIG_NETFILTER_XT_TARGET_LOG=m +CONFIG_NETFILTER_XT_TARGET_MARK=m +CONFIG_NETFILTER_XT_TARGET_NFLOG=m +CONFIG_NETFILTER_XT_TARGET_NFQUEUE=m +CONFIG_NETFILTER_XT_TARGET_NOTRACK=m +CONFIG_NETFILTER_XT_TARGET_TEE=m +CONFIG_NETFILTER_XT_TARGET_TPROXY=m +CONFIG_NETFILTER_XT_TARGET_TRACE=m +CONFIG_NETFILTER_XT_TARGET_TCPMSS=m +CONFIG_NETFILTER_XT_TARGET_TCPOPTSTRIP=m +CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=m +CONFIG_NETFILTER_XT_MATCH_BPF=m +CONFIG_NETFILTER_XT_MATCH_CLUSTER=m +CONFIG_NETFILTER_XT_MATCH_COMMENT=m +CONFIG_NETFILTER_XT_MATCH_CONNBYTES=m +CONFIG_NETFILTER_XT_MATCH_CONNLABEL=m +CONFIG_NETFILTER_XT_MATCH_CONNLIMIT=m +CONFIG_NETFILTER_XT_MATCH_CONNMARK=m +CONFIG_NETFILTER_XT_MATCH_CONNTRACK=m +CONFIG_NETFILTER_XT_MATCH_CPU=m +CONFIG_NETFILTER_XT_MATCH_DCCP=m +CONFIG_NETFILTER_XT_MATCH_DEVGROUP=m +CONFIG_NETFILTER_XT_MATCH_DSCP=m +CONFIG_NETFILTER_XT_MATCH_ESP=m +CONFIG_NETFILTER_XT_MATCH_HASHLIMIT=m +CONFIG_NETFILTER_XT_MATCH_HELPER=m +CONFIG_NETFILTER_XT_MATCH_IPRANGE=m +CONFIG_NETFILTER_XT_MATCH_IPVS=m +CONFIG_NETFILTER_XT_MATCH_LENGTH=m +CONFIG_NETFILTER_XT_MATCH_LIMIT=m +CONFIG_NETFILTER_XT_MATCH_MAC=m +CONFIG_NETFILTER_XT_MATCH_MARK=m +CONFIG_NETFILTER_XT_MATCH_MULTIPORT=m +CONFIG_NETFILTER_XT_MATCH_NFACCT=m +CONFIG_NETFILTER_XT_MATCH_OSF=m +CONFIG_NETFILTER_XT_MATCH_OWNER=m +CONFIG_NETFILTER_XT_MATCH_POLICY=m +CONFIG_NETFILTER_XT_MATCH_PHYSDEV=m +CONFIG_NETFILTER_XT_MATCH_PKTTYPE=m +CONFIG_NETFILTER_XT_MATCH_QUOTA=m +CONFIG_NETFILTER_XT_MATCH_RATEEST=m +CONFIG_NETFILTER_XT_MATCH_REALM=m +CONFIG_NETFILTER_XT_MATCH_RECENT=m +CONFIG_NETFILTER_XT_MATCH_SOCKET=m +CONFIG_NETFILTER_XT_MATCH_STATE=m +CONFIG_NETFILTER_XT_MATCH_STATISTIC=m +CONFIG_NETFILTER_XT_MATCH_STRING=m +CONFIG_NETFILTER_XT_MATCH_TCPMSS=m +CONFIG_NETFILTER_XT_MATCH_TIME=m +CONFIG_NETFILTER_XT_MATCH_U32=m +CONFIG_IP_SET=m +CONFIG_IP_SET_BITMAP_IP=m +CONFIG_IP_SET_BITMAP_IPMAC=m +CONFIG_IP_SET_BITMAP_PORT=m +CONFIG_IP_SET_HASH_IP=m +CONFIG_IP_SET_HASH_IPPORT=m +CONFIG_IP_SET_HASH_IPPORTIP=m +CONFIG_IP_SET_HASH_IPPORTNET=m +CONFIG_IP_SET_HASH_NET=m +CONFIG_IP_SET_HASH_NETPORT=m +CONFIG_IP_SET_HASH_NETIFACE=m +CONFIG_IP_SET_LIST_SET=m +CONFIG_IP_VS=m +CONFIG_IP_VS_IPV6=y +CONFIG_IP_VS_PROTO_TCP=y +CONFIG_IP_VS_PROTO_UDP=y +CONFIG_IP_VS_PROTO_ESP=y +CONFIG_IP_VS_PROTO_AH=y +CONFIG_IP_VS_PROTO_SCTP=y +CONFIG_IP_VS_RR=m +CONFIG_IP_VS_WRR=m +CONFIG_IP_VS_LC=m +CONFIG_IP_VS_WLC=m +CONFIG_IP_VS_LBLC=m +CONFIG_IP_VS_LBLCR=m +CONFIG_IP_VS_DH=m +CONFIG_IP_VS_SH=m +CONFIG_IP_VS_SED=m +CONFIG_IP_VS_NQ=m +CONFIG_IP_VS_FTP=m +CONFIG_IP_VS_PE_SIP=m +CONFIG_NFT_DUP_IPV4=m +CONFIG_NFT_FIB_IPV4=m +CONFIG_NF_TABLES_ARP=y +CONFIG_NF_LOG_ARP=m +CONFIG_NF_LOG_IPV4=m +CONFIG_IP_NF_IPTABLES=m +CONFIG_IP_NF_MATCH_AH=m +CONFIG_IP_NF_MATCH_ECN=m +CONFIG_IP_NF_MATCH_RPFILTER=m +CONFIG_IP_NF_MATCH_TTL=m +CONFIG_IP_NF_FILTER=m +CONFIG_IP_NF_TARGET_REJECT=m +CONFIG_IP_NF_TARGET_SYNPROXY=m +CONFIG_IP_NF_NAT=m +CONFIG_IP_NF_TARGET_MASQUERADE=m +CONFIG_IP_NF_TARGET_NETMAP=m +CONFIG_IP_NF_TARGET_REDIRECT=m +CONFIG_IP_NF_MANGLE=m +CONFIG_IP_NF_TARGET_ECN=m +CONFIG_IP_NF_TARGET_TTL=m +CONFIG_IP_NF_RAW=m +CONFIG_IP_NF_ARPFILTER=m +CONFIG_IP_NF_ARP_MANGLE=m +CONFIG_NFT_DUP_IPV6=m +CONFIG_NFT_FIB_IPV6=m +CONFIG_IP6_NF_IPTABLES=m +CONFIG_IP6_NF_MATCH_AH=m +CONFIG_IP6_NF_MATCH_EUI64=m +CONFIG_IP6_NF_MATCH_FRAG=m +CONFIG_IP6_NF_MATCH_OPTS=m +CONFIG_IP6_NF_MATCH_HL=m +CONFIG_IP6_NF_MATCH_IPV6HEADER=m +CONFIG_IP6_NF_MATCH_MH=m +CONFIG_IP6_NF_MATCH_RPFILTER=m +CONFIG_IP6_NF_MATCH_RT=m +CONFIG_IP6_NF_MATCH_SRH=m +CONFIG_IP6_NF_TARGET_HL=m +CONFIG_IP6_NF_FILTER=m +CONFIG_IP6_NF_TARGET_REJECT=m +CONFIG_IP6_NF_TARGET_SYNPROXY=m +CONFIG_IP6_NF_MANGLE=m +CONFIG_IP6_NF_RAW=m +CONFIG_IP6_NF_SECURITY=m +CONFIG_IP6_NF_NAT=m +CONFIG_IP6_NF_TARGET_MASQUERADE=m +CONFIG_IP6_NF_TARGET_NPT=m +CONFIG_NF_TABLES_BRIDGE=m +CONFIG_NFT_BRIDGE_REJECT=m +CONFIG_BRIDGE_NF_EBTABLES=m +CONFIG_BRIDGE_EBT_BROUTE=m +CONFIG_BRIDGE_EBT_T_FILTER=m +CONFIG_BRIDGE_EBT_T_NAT=m +CONFIG_BRIDGE_EBT_802_3=m +CONFIG_BRIDGE_EBT_AMONG=m +CONFIG_BRIDGE_EBT_ARP=m +CONFIG_BRIDGE_EBT_IP=m +CONFIG_BRIDGE_EBT_IP6=m +CONFIG_BRIDGE_EBT_LIMIT=m +CONFIG_BRIDGE_EBT_MARK=m +CONFIG_BRIDGE_EBT_PKTTYPE=m +CONFIG_BRIDGE_EBT_STP=m +CONFIG_BRIDGE_EBT_VLAN=m +CONFIG_BRIDGE_EBT_ARPREPLY=m +CONFIG_BRIDGE_EBT_DNAT=m +CONFIG_BRIDGE_EBT_MARK_T=m +CONFIG_BRIDGE_EBT_REDIRECT=m +CONFIG_BRIDGE_EBT_SNAT=m +CONFIG_BRIDGE_EBT_LOG=m +CONFIG_BRIDGE_EBT_NFLOG=m +CONFIG_SCTP_COOKIE_HMAC_SHA1=y +CONFIG_ATM=m +CONFIG_L2TP=m +CONFIG_L2TP_V3=y +CONFIG_L2TP_IP=m +CONFIG_L2TP_ETH=m +CONFIG_BRIDGE=m +CONFIG_BRIDGE_VLAN_FILTERING=y +CONFIG_VLAN_8021Q=m +CONFIG_VLAN_8021Q_GVRP=y +CONFIG_ATALK=m +CONFIG_6LOWPAN=m +CONFIG_IEEE802154=m +CONFIG_IEEE802154_6LOWPAN=m +CONFIG_MAC802154=m +CONFIG_NET_SCHED=y +CONFIG_NET_SCH_HTB=m +CONFIG_NET_SCH_HFSC=m +CONFIG_NET_SCH_PRIO=m +CONFIG_NET_SCH_MULTIQ=m +CONFIG_NET_SCH_RED=m +CONFIG_NET_SCH_SFB=m +CONFIG_NET_SCH_SFQ=m +CONFIG_NET_SCH_TEQL=m +CONFIG_NET_SCH_TBF=m +CONFIG_NET_SCH_GRED=m +CONFIG_NET_SCH_NETEM=m +CONFIG_NET_SCH_DRR=m +CONFIG_NET_SCH_MQPRIO=m +CONFIG_NET_SCH_CHOKE=m +CONFIG_NET_SCH_QFQ=m +CONFIG_NET_SCH_CODEL=m +CONFIG_NET_SCH_FQ_CODEL=m +CONFIG_NET_SCH_CAKE=m +CONFIG_NET_SCH_FQ=m +CONFIG_NET_SCH_HHF=m +CONFIG_NET_SCH_PIE=m +CONFIG_NET_SCH_INGRESS=m +CONFIG_NET_SCH_PLUG=m +CONFIG_NET_CLS_BASIC=m +CONFIG_NET_CLS_ROUTE4=m +CONFIG_NET_CLS_FW=m +CONFIG_NET_CLS_U32=m +CONFIG_CLS_U32_MARK=y +CONFIG_NET_CLS_FLOW=m +CONFIG_NET_CLS_CGROUP=m +CONFIG_NET_CLS_BPF=y +CONFIG_NET_EMATCH=y +CONFIG_NET_EMATCH_CMP=m +CONFIG_NET_EMATCH_NBYTE=m +CONFIG_NET_EMATCH_U32=m +CONFIG_NET_EMATCH_META=m +CONFIG_NET_EMATCH_TEXT=m +CONFIG_NET_EMATCH_IPSET=m +CONFIG_NET_CLS_ACT=y +CONFIG_NET_ACT_POLICE=m +CONFIG_NET_ACT_GACT=m +CONFIG_GACT_PROB=y +CONFIG_NET_ACT_MIRRED=m +CONFIG_NET_ACT_NAT=m +CONFIG_NET_ACT_PEDIT=m +CONFIG_NET_ACT_SIMP=m +CONFIG_NET_ACT_SKBEDIT=m +CONFIG_NET_ACT_CSUM=m +CONFIG_BATMAN_ADV=m +CONFIG_OPENVSWITCH=m +CONFIG_VSOCKETS=m +CONFIG_CGROUP_NET_PRIO=y +CONFIG_NET_PKTGEN=m +CONFIG_HAMRADIO=y +CONFIG_AX25=m +CONFIG_NETROM=m +CONFIG_ROSE=m +CONFIG_MKISS=m +CONFIG_6PACK=m +CONFIG_BPQETHER=m +CONFIG_BAYCOM_SER_FDX=m +CONFIG_BAYCOM_SER_HDX=m +CONFIG_YAM=m +CONFIG_CAN=m +CONFIG_CAN_J1939=m +CONFIG_CAN_ISOTP=m +CONFIG_BT=m +CONFIG_BT_RFCOMM=m +CONFIG_BT_RFCOMM_TTY=y +CONFIG_BT_BNEP=m +CONFIG_BT_BNEP_MC_FILTER=y +CONFIG_BT_BNEP_PROTO_FILTER=y +CONFIG_BT_HIDP=m +CONFIG_BT_6LOWPAN=m +CONFIG_BT_HCIBTUSB=m +CONFIG_BT_HCIUART=m +CONFIG_BT_HCIUART_3WIRE=y +CONFIG_BT_HCIUART_BCM=y +CONFIG_BT_HCIBCM203X=m +CONFIG_BT_HCIBPA10X=m +CONFIG_BT_HCIBFUSB=m +CONFIG_BT_HCIVHCI=m +CONFIG_BT_MRVL=m +CONFIG_BT_MRVL_SDIO=m +CONFIG_BT_ATH3K=m +CONFIG_CFG80211=m +CONFIG_CFG80211_WEXT=y +CONFIG_MAC80211=m +CONFIG_MAC80211_MESH=y +CONFIG_RFKILL=m +CONFIG_RFKILL_INPUT=y +CONFIG_NET_9P=m +CONFIG_NFC=m +CONFIG_PCI=y +CONFIG_PCIEPORTBUS=y +CONFIG_PCIEAER=y +CONFIG_PCIEASPM_POWERSAVE=y +CONFIG_PCIE_DPC=y +CONFIG_UEVENT_HELPER=y +CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y +# CONFIG_BRCMSTB_GISB_ARB is not set +CONFIG_RASPBERRYPI_FIRMWARE=y +CONFIG_FIRMWARE_RP1=m +# CONFIG_EFI_VARS_PSTORE is not set +CONFIG_MTD=m +CONFIG_MTD_BLOCK=m +CONFIG_MTD_BLOCK2MTD=m +CONFIG_MTD_SPI_NAND=m +CONFIG_MTD_SPI_NOR=m +CONFIG_MTD_UBI=m +CONFIG_OF_CONFIGFS=y +CONFIG_ZRAM=m +CONFIG_ZRAM_BACKEND_LZ4=y +CONFIG_ZRAM_BACKEND_ZSTD=y +CONFIG_ZRAM_BACKEND_LZO=y +CONFIG_ZRAM_DEF_COMP_ZSTD=y +CONFIG_ZRAM_WRITEBACK=y +CONFIG_ZRAM_MULTI_COMP=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_DRBD=m +CONFIG_BLK_DEV_NBD=m +CONFIG_BLK_DEV_RAM=y +CONFIG_ATA_OVER_ETH=m +CONFIG_BLK_DEV_RBD=m +CONFIG_BLK_DEV_NVME=y +CONFIG_NVME_HWMON=y +CONFIG_RP1_PIO=m +CONFIG_WS2812_PIO_RP1=m +CONFIG_SRAM=y +CONFIG_EEPROM_AT24=m +CONFIG_EEPROM_AT25=m +CONFIG_TI_ST=m +CONFIG_SCSI=y +# CONFIG_SCSI_PROC_FS is not set +CONFIG_BLK_DEV_SD=y +CONFIG_CHR_DEV_ST=m +CONFIG_BLK_DEV_SR=m +CONFIG_CHR_DEV_SG=m +CONFIG_SCSI_ISCSI_ATTRS=y +CONFIG_ISCSI_TCP=m +CONFIG_ISCSI_BOOT_SYSFS=m +CONFIG_ATA=m +CONFIG_SATA_AHCI=m +CONFIG_SATA_MV=m +CONFIG_MD=y +CONFIG_BCACHE=m +CONFIG_BLK_DEV_DM=m +CONFIG_DM_CRYPT=m +CONFIG_DM_SNAPSHOT=m +CONFIG_DM_THIN_PROVISIONING=m +CONFIG_DM_CACHE=m +CONFIG_DM_WRITECACHE=m +CONFIG_DM_MIRROR=m +CONFIG_DM_LOG_USERSPACE=m +CONFIG_DM_RAID=m +CONFIG_DM_ZERO=m +CONFIG_DM_MULTIPATH=m +CONFIG_DM_DELAY=m +CONFIG_DM_VERITY=m +CONFIG_DM_INTEGRITY=m +CONFIG_NETDEVICES=y +CONFIG_BONDING=m +CONFIG_DUMMY=m +CONFIG_WIREGUARD=m +CONFIG_IFB=m +CONFIG_MACVLAN=m +CONFIG_MACVTAP=m +CONFIG_IPVLAN=m +CONFIG_VXLAN=m +CONFIG_NETCONSOLE=m +CONFIG_TUN=m +CONFIG_VETH=m +CONFIG_NETKIT=y +CONFIG_NET_VRF=m +CONFIG_VSOCKMON=m +CONFIG_BCMGENET=y +CONFIG_MACB=y +CONFIG_IGB=m +CONFIG_IXGBE=m +CONFIG_I40E=m +CONFIG_IGC=m +CONFIG_ENC28J60=m +CONFIG_LAN743X=m +CONFIG_QCA7000_SPI=m +CONFIG_QCA7000_UART=m +CONFIG_R8169=m +CONFIG_MSE102X=m +CONFIG_WIZNET_W5100=m +CONFIG_WIZNET_W5100_SPI=m +CONFIG_MICREL_PHY=y +CONFIG_CAN_VCAN=m +CONFIG_CAN_SLCAN=m +CONFIG_CAN_MCP251X=m +CONFIG_CAN_MCP251XFD=m +CONFIG_CAN_8DEV_USB=m +CONFIG_CAN_EMS_USB=m +CONFIG_CAN_GS_USB=m +CONFIG_CAN_PEAK_USB=m +CONFIG_MDIO_BITBANG=m +CONFIG_PPP=m +CONFIG_PPP_BSDCOMP=m +CONFIG_PPP_DEFLATE=m +CONFIG_PPP_FILTER=y +CONFIG_PPP_MPPE=m +CONFIG_PPP_MULTILINK=y +CONFIG_PPPOATM=m +CONFIG_PPPOE=m +CONFIG_PPPOL2TP=m +CONFIG_PPP_ASYNC=m +CONFIG_PPP_SYNC_TTY=m +CONFIG_SLIP=m +CONFIG_SLIP_COMPRESSED=y +CONFIG_SLIP_SMART=y +CONFIG_USB_CATC=m +CONFIG_USB_KAWETH=m +CONFIG_USB_PEGASUS=m +CONFIG_USB_RTL8150=m +CONFIG_USB_RTL8152=y +CONFIG_USB_LAN78XX=y +CONFIG_USB_USBNET=y +CONFIG_USB_NET_AX8817X=m +CONFIG_USB_NET_AX88179_178A=m +CONFIG_USB_NET_CDCETHER=m +CONFIG_USB_NET_CDC_EEM=m +CONFIG_USB_NET_CDC_NCM=m +CONFIG_USB_NET_HUAWEI_CDC_NCM=m +CONFIG_USB_NET_CDC_MBIM=m +CONFIG_USB_NET_DM9601=m +CONFIG_USB_NET_SR9700=m +CONFIG_USB_NET_SR9800=m +CONFIG_USB_NET_SMSC75XX=m +CONFIG_USB_NET_SMSC95XX=y +CONFIG_USB_NET_GL620A=m +CONFIG_USB_NET_NET1080=m +CONFIG_USB_NET_PLUSB=m +CONFIG_USB_NET_MCS7830=m +CONFIG_USB_NET_RNDIS_HOST=m +CONFIG_USB_NET_CDC_SUBSET=m +CONFIG_USB_ALI_M5632=y +CONFIG_USB_AN2720=y +CONFIG_USB_EPSON2888=y +CONFIG_USB_KC2190=y +CONFIG_USB_NET_ZAURUS=m +CONFIG_USB_NET_CX82310_ETH=m +CONFIG_USB_NET_KALMIA=m +CONFIG_USB_NET_QMI_WWAN=m +CONFIG_USB_HSO=m +CONFIG_USB_NET_INT51X1=m +CONFIG_USB_IPHETH=m +CONFIG_USB_SIERRA_NET=m +CONFIG_USB_VL600=m +CONFIG_USB_NET_AQC111=m +CONFIG_ATH9K=m +CONFIG_ATH9K_HTC=m +CONFIG_CARL9170=m +CONFIG_ATH6KL=m +CONFIG_ATH6KL_USB=m +CONFIG_AR5523=m +CONFIG_AT76C50X_USB=m +CONFIG_B43=m +# CONFIG_B43_PHY_N is not set +CONFIG_B43LEGACY=m +CONFIG_BRCMFMAC=m +CONFIG_BRCMFMAC_USB=y +CONFIG_BRCMDBG=y +CONFIG_IWLWIFI=m +CONFIG_IWLDVM=m +CONFIG_IWLMVM=m +CONFIG_P54_COMMON=m +CONFIG_P54_USB=m +CONFIG_LIBERTAS=m +CONFIG_LIBERTAS_USB=m +CONFIG_LIBERTAS_SDIO=m +CONFIG_LIBERTAS_THINFIRM=m +CONFIG_LIBERTAS_THINFIRM_USB=m +CONFIG_MWIFIEX=m +CONFIG_MWIFIEX_SDIO=m +CONFIG_MT7601U=m +CONFIG_MT76x0U=m +CONFIG_MT76x2U=m +CONFIG_MT7921U=m +CONFIG_RT2X00=m +CONFIG_RT2500USB=m +CONFIG_RT73USB=m +CONFIG_RT2800USB=m +CONFIG_RT2800USB_RT3573=y +CONFIG_RT2800USB_RT53XX=y +CONFIG_RT2800USB_RT55XX=y +CONFIG_RT2800USB_UNKNOWN=y +CONFIG_RTL8187=m +CONFIG_RTL8192CU=m +CONFIG_RTL8XXXU=m +CONFIG_RTW88=m +CONFIG_RTW88_8822BU=m +CONFIG_RTW88_8822CU=m +CONFIG_RTW88_8723DU=m +CONFIG_RTW88_8821CU=m +CONFIG_ZD1211RW=m +CONFIG_MAC80211_HWSIM=m +CONFIG_IEEE802154_AT86RF230=m +CONFIG_IEEE802154_MRF24J40=m +CONFIG_IEEE802154_CC2520=m +CONFIG_INPUT_MOUSEDEV=y +CONFIG_INPUT_JOYDEV=m +CONFIG_INPUT_EVDEV=y +# CONFIG_KEYBOARD_ATKBD is not set +CONFIG_KEYBOARD_GPIO=m +CONFIG_KEYBOARD_TCA6416=m +CONFIG_KEYBOARD_TCA8418=m +CONFIG_KEYBOARD_MATRIX=m +CONFIG_KEYBOARD_CAP11XX=m +# CONFIG_INPUT_MOUSE is not set +CONFIG_INPUT_JOYSTICK=y +CONFIG_JOYSTICK_IFORCE=m +CONFIG_JOYSTICK_IFORCE_USB=m +CONFIG_JOYSTICK_XPAD=m +CONFIG_JOYSTICK_XPAD_FF=y +CONFIG_JOYSTICK_XPAD_LEDS=y +CONFIG_JOYSTICK_PSXPAD_SPI=m +CONFIG_JOYSTICK_PSXPAD_SPI_FF=y +CONFIG_JOYSTICK_FSIA6B=m +CONFIG_JOYSTICK_SENSEHAT=m +CONFIG_INPUT_TOUCHSCREEN=y +CONFIG_TOUCHSCREEN_ADS7846=m +CONFIG_TOUCHSCREEN_EGALAX=m +CONFIG_TOUCHSCREEN_EXC3000=m +CONFIG_TOUCHSCREEN_GOODIX=m +CONFIG_TOUCHSCREEN_ILI210X=m +CONFIG_TOUCHSCREEN_EDT_FT5X06=m +CONFIG_TOUCHSCREEN_RASPBERRYPI_FW=m +CONFIG_TOUCHSCREEN_USB_COMPOSITE=m +CONFIG_TOUCHSCREEN_TSC2007=m +CONFIG_TOUCHSCREEN_TSC2007_IIO=y +CONFIG_TOUCHSCREEN_STMPE=m +CONFIG_TOUCHSCREEN_IQS5XX=m +CONFIG_INPUT_MISC=y +CONFIG_INPUT_AD714X=m +CONFIG_INPUT_ATI_REMOTE2=m +CONFIG_INPUT_KEYSPAN_REMOTE=m +CONFIG_INPUT_POWERMATE=m +CONFIG_INPUT_YEALINK=m +CONFIG_INPUT_CM109=m +CONFIG_INPUT_UINPUT=m +CONFIG_INPUT_GPIO_ROTARY_ENCODER=m +CONFIG_INPUT_ADXL34X=m +CONFIG_INPUT_CMA3000=m +CONFIG_SERIO=m +CONFIG_SERIO_RAW=m +CONFIG_GAMEPORT=m +CONFIG_BRCM_CHAR_DRIVERS=y +CONFIG_BCM_VCIO=y +# CONFIG_LEGACY_PTYS is not set +CONFIG_SERIAL_8250=y +# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set +CONFIG_SERIAL_8250_CONSOLE=y +# CONFIG_SERIAL_8250_DMA is not set +CONFIG_SERIAL_8250_NR_UARTS=5 +CONFIG_SERIAL_8250_RUNTIME_UARTS=0 +CONFIG_SERIAL_8250_EXTENDED=y +CONFIG_SERIAL_8250_SHARE_IRQ=y +CONFIG_SERIAL_8250_BCM2835AUX=y +CONFIG_SERIAL_OF_PLATFORM=y +CONFIG_SERIAL_AMBA_PL011=y +CONFIG_SERIAL_AMBA_PL011_CONSOLE=y +CONFIG_SERIAL_SC16IS7XX=m +CONFIG_SERIAL_DEV_BUS=y +CONFIG_TTY_PRINTK=y +CONFIG_HW_RANDOM=y +CONFIG_TCG_TPM=m +CONFIG_TCG_TIS_SPI=m +CONFIG_TCG_TIS_I2C=m +CONFIG_XILLYBUS=m +CONFIG_XILLYBUS_PCIE=m +CONFIG_XILLYUSB=m +CONFIG_RASPBERRYPI_GPIOMEM=m +CONFIG_I2C=y +CONFIG_I2C_CHARDEV=m +CONFIG_I2C_MUX_GPMUX=m +CONFIG_I2C_MUX_PCA954x=m +CONFIG_I2C_MUX_PINCTRL=m +CONFIG_I2C_BCM2708=m +CONFIG_I2C_BCM2835=m +CONFIG_I2C_BRCMSTB=m +CONFIG_I2C_DESIGNWARE_CORE=m +CONFIG_I2C_GPIO=m +CONFIG_I2C_ROBOTFUZZ_OSIF=m +CONFIG_I2C_TINY_USB=m +CONFIG_SPI=y +CONFIG_SPI_BCM2835=m +CONFIG_SPI_BCM2835AUX=m +CONFIG_SPI_DESIGNWARE=m +CONFIG_SPI_DW_DMA=y +CONFIG_SPI_DW_MMIO=m +CONFIG_SPI_GPIO=m +CONFIG_SPI_RP2040_GPIO_BRIDGE=m +CONFIG_SPI_SPIDEV=m +CONFIG_SPI_SLAVE=y +CONFIG_PPS_CLIENT_LDISC=m +CONFIG_PPS_CLIENT_GPIO=m +CONFIG_PINCTRL_MCP23S08=m +CONFIG_PINCTRL_RP1=y +CONFIG_PINCTRL_BCM2712=y +CONFIG_GPIO_SYSFS=y +CONFIG_GPIO_BCM_VIRT=y +CONFIG_GPIO_MAX7300=m +CONFIG_GPIO_PCA953X=m +CONFIG_GPIO_PCA953X_IRQ=y +CONFIG_GPIO_PCF857X=m +CONFIG_GPIO_ARIZONA=m +CONFIG_GPIO_FSM=m +CONFIG_GPIO_STMPE=y +CONFIG_GPIO_MAX7301=m +CONFIG_GPIO_MOCKUP=m +CONFIG_W1=m +CONFIG_W1_MASTER_DS2490=m +CONFIG_W1_MASTER_DS2482=m +CONFIG_W1_MASTER_GPIO=m +CONFIG_W1_SLAVE_THERM=m +CONFIG_W1_SLAVE_SMEM=m +CONFIG_W1_SLAVE_DS2408=m +CONFIG_W1_SLAVE_DS2413=m +CONFIG_W1_SLAVE_DS2406=m +CONFIG_W1_SLAVE_DS2423=m +CONFIG_W1_SLAVE_DS2431=m +CONFIG_W1_SLAVE_DS2433=m +CONFIG_W1_SLAVE_DS2438=m +CONFIG_W1_SLAVE_DS2780=m +CONFIG_W1_SLAVE_DS2781=m +CONFIG_W1_SLAVE_DS28E04=m +CONFIG_W1_SLAVE_DS28E17=m +# CONFIG_POWER_RESET_BRCMSTB is not set +CONFIG_POWER_RESET_GPIO=y +CONFIG_RPI_POE_POWER=m +CONFIG_BATTERY_DS2760=m +CONFIG_BATTERY_MAX17040=m +CONFIG_CHARGER_GPIO=m +CONFIG_BATTERY_GAUGE_LTC2941=m +CONFIG_SENSORS_ADT7410=m +CONFIG_SENSORS_AHT10=m +CONFIG_SENSORS_CHIPCAP2=m +CONFIG_SENSORS_DRIVETEMP=m +CONFIG_SENSORS_DS1621=m +CONFIG_SENSORS_GPIO_FAN=m +CONFIG_SENSORS_IIO_HWMON=m +CONFIG_SENSORS_JC42=m +CONFIG_SENSORS_LM75=m +CONFIG_SENSORS_PWM_FAN=m +CONFIG_SENSORS_RASPBERRYPI_HWMON=m +CONFIG_SENSORS_SHT21=m +CONFIG_SENSORS_SHT3x=m +CONFIG_SENSORS_SHT4x=m +CONFIG_SENSORS_SHTC1=m +CONFIG_SENSORS_EMC2305=m +CONFIG_SENSORS_INA2XX=m +CONFIG_SENSORS_INA238=m +CONFIG_SENSORS_TMP102=m +CONFIG_SENSORS_RP1_ADC=m +CONFIG_BCM2711_THERMAL=y +CONFIG_BCM2835_THERMAL=y +CONFIG_WATCHDOG=y +CONFIG_GPIO_WATCHDOG=m +CONFIG_BCM2835_WDT=y +CONFIG_MFD_RASPBERRYPI_POE_HAT=m +CONFIG_MFD_STMPE=y +CONFIG_STMPE_SPI=y +CONFIG_MFD_SYSCON=y +CONFIG_MFD_ARIZONA_I2C=m +CONFIG_MFD_ARIZONA_SPI=m +CONFIG_MFD_WM5102=y +CONFIG_MFD_RP1=y +CONFIG_REGULATOR=y +CONFIG_REGULATOR_FIXED_VOLTAGE=y +CONFIG_REGULATOR_ARIZONA_LDO1=m +CONFIG_REGULATOR_ARIZONA_MICSUPP=m +CONFIG_REGULATOR_GPIO=y +CONFIG_REGULATOR_RASPBERRYPI_TOUCHSCREEN_ATTINY=m +CONFIG_REGULATOR_RASPBERRYPI_TOUCHSCREEN_V2=m +CONFIG_RC_CORE=y +CONFIG_BPF_LIRC_MODE2=y +CONFIG_LIRC=y +CONFIG_RC_DECODERS=y +CONFIG_IR_IMON_DECODER=m +CONFIG_IR_JVC_DECODER=m +CONFIG_IR_MCE_KBD_DECODER=m +CONFIG_IR_NEC_DECODER=m +CONFIG_IR_RC5_DECODER=m +CONFIG_IR_RC6_DECODER=m +CONFIG_IR_SANYO_DECODER=m +CONFIG_IR_SHARP_DECODER=m +CONFIG_IR_SONY_DECODER=m +CONFIG_IR_XMP_DECODER=m +CONFIG_RC_DEVICES=y +CONFIG_IR_GPIO_CIR=m +CONFIG_IR_GPIO_TX=m +CONFIG_IR_IGUANA=m +CONFIG_IR_IMON=m +CONFIG_IR_MCEUSB=m +CONFIG_IR_PWM_TX=m +CONFIG_IR_REDRAT3=m +CONFIG_IR_STREAMZAP=m +CONFIG_IR_TOY=m +CONFIG_IR_TTUSBIR=m +CONFIG_RC_ATI_REMOTE=m +CONFIG_RC_LOOPBACK=m +CONFIG_MEDIA_CEC_RC=y +CONFIG_MEDIA_SUPPORT=m +CONFIG_MEDIA_USB_SUPPORT=y +CONFIG_USB_GSPCA=m +CONFIG_USB_GSPCA_BENQ=m +CONFIG_USB_GSPCA_CONEX=m +CONFIG_USB_GSPCA_CPIA1=m +CONFIG_USB_GSPCA_DTCS033=m +CONFIG_USB_GSPCA_ETOMS=m +CONFIG_USB_GSPCA_FINEPIX=m +CONFIG_USB_GSPCA_JEILINJ=m +CONFIG_USB_GSPCA_JL2005BCD=m +CONFIG_USB_GSPCA_KINECT=m +CONFIG_USB_GSPCA_KONICA=m +CONFIG_USB_GSPCA_MARS=m +CONFIG_USB_GSPCA_MR97310A=m +CONFIG_USB_GSPCA_NW80X=m +CONFIG_USB_GSPCA_OV519=m +CONFIG_USB_GSPCA_OV534=m +CONFIG_USB_GSPCA_OV534_9=m +CONFIG_USB_GSPCA_PAC207=m +CONFIG_USB_GSPCA_PAC7302=m +CONFIG_USB_GSPCA_PAC7311=m +CONFIG_USB_GSPCA_SE401=m +CONFIG_USB_GSPCA_SN9C2028=m +CONFIG_USB_GSPCA_SN9C20X=m +CONFIG_USB_GSPCA_SONIXB=m +CONFIG_USB_GSPCA_SONIXJ=m +CONFIG_USB_GSPCA_SPCA1528=m +CONFIG_USB_GSPCA_SPCA500=m +CONFIG_USB_GSPCA_SPCA501=m +CONFIG_USB_GSPCA_SPCA505=m +CONFIG_USB_GSPCA_SPCA506=m +CONFIG_USB_GSPCA_SPCA508=m +CONFIG_USB_GSPCA_SPCA561=m +CONFIG_USB_GSPCA_SQ905=m +CONFIG_USB_GSPCA_SQ905C=m +CONFIG_USB_GSPCA_SQ930X=m +CONFIG_USB_GSPCA_STK014=m +CONFIG_USB_GSPCA_STK1135=m +CONFIG_USB_GSPCA_STV0680=m +CONFIG_USB_GSPCA_SUNPLUS=m +CONFIG_USB_GSPCA_T613=m +CONFIG_USB_GSPCA_TOPRO=m +CONFIG_USB_GSPCA_TOUPTEK=m +CONFIG_USB_GSPCA_TV8532=m +CONFIG_USB_GSPCA_VC032X=m +CONFIG_USB_GSPCA_VICAM=m +CONFIG_USB_GSPCA_XIRLINK_CIT=m +CONFIG_USB_GSPCA_ZC3XX=m +CONFIG_USB_GL860=m +CONFIG_USB_M5602=m +CONFIG_USB_STV06XX=m +CONFIG_USB_PWC=m +CONFIG_USB_S2255=m +CONFIG_VIDEO_USBTV=m +CONFIG_USB_VIDEO_CLASS=m +CONFIG_VIDEO_GO7007=m +CONFIG_VIDEO_GO7007_USB=m +CONFIG_VIDEO_GO7007_USB_S2250_BOARD=m +CONFIG_VIDEO_HDPVR=m +CONFIG_VIDEO_PVRUSB2=m +CONFIG_VIDEO_STK1160=m +CONFIG_VIDEO_AU0828=m +CONFIG_VIDEO_AU0828_RC=y +CONFIG_VIDEO_CX231XX=m +CONFIG_VIDEO_CX231XX_ALSA=m +CONFIG_VIDEO_CX231XX_DVB=m +CONFIG_DVB_AS102=m +CONFIG_DVB_B2C2_FLEXCOP_USB=m +CONFIG_DVB_USB_V2=m +CONFIG_DVB_USB_AF9015=m +CONFIG_DVB_USB_AF9035=m +CONFIG_DVB_USB_ANYSEE=m +CONFIG_DVB_USB_AU6610=m +CONFIG_DVB_USB_AZ6007=m +CONFIG_DVB_USB_CE6230=m +CONFIG_DVB_USB_DVBSKY=m +CONFIG_DVB_USB_EC168=m +CONFIG_DVB_USB_GL861=m +CONFIG_DVB_USB_LME2510=m +CONFIG_DVB_USB_MXL111SF=m +CONFIG_DVB_USB_RTL28XXU=m +CONFIG_DVB_USB=m +CONFIG_DVB_USB_A800=m +CONFIG_DVB_USB_AF9005=m +CONFIG_DVB_USB_AF9005_REMOTE=m +CONFIG_DVB_USB_AZ6027=m +CONFIG_DVB_USB_CINERGY_T2=m +CONFIG_DVB_USB_CXUSB=m +CONFIG_DVB_USB_DIB0700=m +CONFIG_DVB_USB_DIBUSB_MB=m +CONFIG_DVB_USB_DIBUSB_MB_FAULTY=y +CONFIG_DVB_USB_DIBUSB_MC=m +CONFIG_DVB_USB_DIGITV=m +CONFIG_DVB_USB_DTT200U=m +CONFIG_DVB_USB_DTV5100=m +CONFIG_DVB_USB_DW2102=m +CONFIG_DVB_USB_GP8PSK=m +CONFIG_DVB_USB_M920X=m +CONFIG_DVB_USB_NOVA_T_USB2=m +CONFIG_DVB_USB_OPERA1=m +CONFIG_DVB_USB_PCTV452E=m +CONFIG_DVB_USB_TECHNISAT_USB2=m +CONFIG_DVB_USB_TTUSB2=m +CONFIG_DVB_USB_UMT_010=m +CONFIG_DVB_USB_VP702X=m +CONFIG_DVB_USB_VP7045=m +CONFIG_SMS_USB_DRV=m +CONFIG_VIDEO_EM28XX=m +CONFIG_VIDEO_EM28XX_V4L2=m +CONFIG_VIDEO_EM28XX_ALSA=m +CONFIG_VIDEO_EM28XX_DVB=m +CONFIG_MEDIA_PCI_SUPPORT=y +CONFIG_MEDIA_PCI_HAILO=m +CONFIG_RADIO_SAA7706H=m +CONFIG_RADIO_SHARK=m +CONFIG_RADIO_SHARK2=m +CONFIG_RADIO_SI4713=m +CONFIG_RADIO_TEA5764=m +CONFIG_RADIO_TEF6862=m +CONFIG_RADIO_WL1273=m +CONFIG_USB_DSBR=m +CONFIG_USB_KEENE=m +CONFIG_USB_MA901=m +CONFIG_USB_MR800=m +CONFIG_RADIO_SI470X=m +CONFIG_USB_SI470X=m +CONFIG_I2C_SI470X=m +CONFIG_I2C_SI4713=m +CONFIG_RADIO_WL128X=m +CONFIG_V4L_PLATFORM_DRIVERS=y +CONFIG_VIDEO_MUX=m +CONFIG_VIDEO_BCM2835_UNICAM_LEGACY=m +CONFIG_VIDEO_BCM2835_UNICAM=m +CONFIG_VIDEO_RASPBERRYPI_PISP_BE=m +CONFIG_VIDEO_RP1_CFE=m +CONFIG_V4L_TEST_DRIVERS=y +CONFIG_VIDEO_VIM2M=m +CONFIG_VIDEO_VICODEC=m +CONFIG_VIDEO_VIMC=m +CONFIG_VIDEO_VIVID=m +CONFIG_VIDEO_ARDUCAM_64MP=m +CONFIG_VIDEO_ARDUCAM_PIVARIETY=m +CONFIG_VIDEO_IMX219=m +CONFIG_VIDEO_IMX258=m +CONFIG_VIDEO_IMX290=m +CONFIG_VIDEO_IMX296=m +CONFIG_VIDEO_IMX415=m +CONFIG_VIDEO_IMX477=m +CONFIG_VIDEO_IMX500=m +CONFIG_VIDEO_IMX519=m +CONFIG_VIDEO_IMX708=m +CONFIG_VIDEO_MT9V011=m +CONFIG_VIDEO_OV2311=m +CONFIG_VIDEO_OV5647=m +CONFIG_VIDEO_OV64A40=m +CONFIG_VIDEO_OV7251=m +CONFIG_VIDEO_OV7640=m +CONFIG_VIDEO_OV9282=m +CONFIG_VIDEO_AD5398=m +CONFIG_VIDEO_AK7375=m +CONFIG_VIDEO_BU64754=m +CONFIG_VIDEO_DW9807_VCM=m +CONFIG_VIDEO_SONY_BTF_MPX=m +CONFIG_VIDEO_UDA1342=m +CONFIG_VIDEO_ADV7180=m +CONFIG_VIDEO_TC358743=m +CONFIG_VIDEO_TVP5150=m +CONFIG_VIDEO_TW2804=m +CONFIG_VIDEO_TW9903=m +CONFIG_VIDEO_TW9906=m +CONFIG_VIDEO_IRS1125=m +CONFIG_VIDEO_I2C=m +CONFIG_AUXDISPLAY=y +CONFIG_HD44780=m +CONFIG_DRM=m +CONFIG_DRM_LOAD_EDID_FIRMWARE=y +CONFIG_DRM_UDL=m +CONFIG_DRM_PANEL_ILITEK_ILI9806E=m +CONFIG_DRM_PANEL_ILITEK_ILI9881C=m +CONFIG_DRM_PANEL_JDI_LT070ME05000=m +CONFIG_DRM_PANEL_RASPBERRYPI_TOUCHSCREEN=m +CONFIG_DRM_PANEL_SITRONIX_ST7701=m +CONFIG_DRM_PANEL_SIMPLE=m +CONFIG_DRM_PANEL_TPO_Y17P=m +CONFIG_DRM_PANEL_WAVESHARE_TOUCHSCREEN=m +CONFIG_DRM_DISPLAY_CONNECTOR=m +CONFIG_DRM_SIMPLE_BRIDGE=m +CONFIG_DRM_TOSHIBA_TC358762=m +CONFIG_DRM_V3D=m +CONFIG_DRM_VC4=m +CONFIG_DRM_VC4_HDMI_CEC=y +CONFIG_DRM_RP1_DSI=m +CONFIG_DRM_RP1_DPI=m +CONFIG_DRM_RP1_VEC=m +CONFIG_DRM_PANEL_MIPI_DBI=m +CONFIG_TINYDRM_HX8357D=m +CONFIG_TINYDRM_ILI9225=m +CONFIG_TINYDRM_ILI9341=m +CONFIG_TINYDRM_ILI9486=m +CONFIG_TINYDRM_MI0283QT=m +CONFIG_TINYDRM_REPAPER=m +CONFIG_TINYDRM_ST7586=m +CONFIG_TINYDRM_ST7735R=m +CONFIG_DRM_GUD=m +CONFIG_DRM_SSD130X=m +CONFIG_DRM_SSD130X_I2C=m +CONFIG_DRM_SSD130X_SPI=m +CONFIG_FB=y +CONFIG_FB_BCM2708=y +CONFIG_FB_SIMPLE=y +CONFIG_FB_SSD1307=m +CONFIG_FB_RPISENSE=m +CONFIG_BACKLIGHT_PWM=m +CONFIG_BACKLIGHT_RPI=m +CONFIG_BACKLIGHT_LM3630A=m +CONFIG_BACKLIGHT_GPIO=m +CONFIG_FRAMEBUFFER_CONSOLE_ROTATION=y +CONFIG_LOGO=y +# CONFIG_LOGO_LINUX_MONO is not set +# CONFIG_LOGO_LINUX_VGA16 is not set +CONFIG_SOUND=y +CONFIG_SND=m +CONFIG_SND_OSSEMUL=y +CONFIG_SND_PCM_OSS=m +CONFIG_SND_HRTIMER=m +CONFIG_SND_DYNAMIC_MINORS=y +CONFIG_SND_SEQUENCER=m +CONFIG_SND_SEQ_DUMMY=m +CONFIG_SND_DUMMY=m +CONFIG_SND_ALOOP=m +CONFIG_SND_VIRMIDI=m +CONFIG_SND_MTPAV=m +CONFIG_SND_SERIAL_U16550=m +CONFIG_SND_MPU401=m +CONFIG_SND_PIMIDI=m +CONFIG_SND_USB_AUDIO=m +CONFIG_SND_USB_UA101=m +CONFIG_SND_USB_CAIAQ=m +CONFIG_SND_USB_CAIAQ_INPUT=y +CONFIG_SND_USB_6FIRE=m +CONFIG_SND_USB_HIFACE=m +CONFIG_SND_USB_TONEPORT=m +CONFIG_SND_SOC=m +CONFIG_SND_BCM2835_SOC_I2S=m +CONFIG_SND_BCM2708_SOC_CHIPDIP_DAC=m +CONFIG_SND_BCM2708_SOC_GOOGLEVOICEHAT_SOUNDCARD=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_ADC=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_DAC=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUS=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSHD=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSADC=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSADCPRO=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSDSP=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_DIGI=m +CONFIG_SND_BCM2708_SOC_HIFIBERRY_AMP=m +CONFIG_SND_BCM2708_SOC_PIFI_40=m +CONFIG_SND_BCM2708_SOC_RPI_CIRRUS=m +CONFIG_SND_BCM2708_SOC_RPI_DAC=m +CONFIG_SND_BCM2708_SOC_RPI_PROTO=m +CONFIG_SND_BCM2708_SOC_JUSTBOOM_BOTH=m +CONFIG_SND_BCM2708_SOC_JUSTBOOM_DAC=m +CONFIG_SND_BCM2708_SOC_JUSTBOOM_DIGI=m +CONFIG_SND_BCM2708_SOC_IQAUDIO_CODEC=m +CONFIG_SND_BCM2708_SOC_IQAUDIO_DAC=m +CONFIG_SND_BCM2708_SOC_IQAUDIO_DIGI=m +CONFIG_SND_BCM2708_SOC_I_SABRE_Q2M=m +CONFIG_SND_BCM2708_SOC_ADAU1977_ADC=m +CONFIG_SND_AUDIOINJECTOR_PI_SOUNDCARD=m +CONFIG_SND_AUDIOINJECTOR_OCTO_SOUNDCARD=m +CONFIG_SND_AUDIOINJECTOR_ISOLATED_SOUNDCARD=m +CONFIG_SND_AUDIOSENSE_PI=m +CONFIG_SND_DIGIDAC1_SOUNDCARD=m +CONFIG_SND_BCM2708_SOC_DIONAUDIO_LOCO=m +CONFIG_SND_BCM2708_SOC_DIONAUDIO_LOCO_V2=m +CONFIG_SND_BCM2708_SOC_ALLO_PIANO_DAC=m +CONFIG_SND_BCM2708_SOC_ALLO_PIANO_DAC_PLUS=m +CONFIG_SND_BCM2708_SOC_ALLO_BOSS_DAC=m +CONFIG_SND_BCM2708_SOC_ALLO_BOSS2_DAC=m +CONFIG_SND_BCM2708_SOC_ALLO_DIGIONE=m +CONFIG_SND_BCM2708_SOC_ALLO_KATANA_DAC=m +CONFIG_SND_BCM2708_SOC_FE_PI_AUDIO=m +CONFIG_SND_PISOUND=m +CONFIG_SND_DACBERRY400=m +CONFIG_SND_DESIGNWARE_I2S=m +CONFIG_SND_DESIGNWARE_PCM=y +CONFIG_SND_RP1_AUDIO_OUT=m +CONFIG_SND_SOC_AD193X_SPI=m +CONFIG_SND_SOC_AD193X_I2C=m +CONFIG_SND_SOC_ADAU1701=m +CONFIG_SND_SOC_ADAU7002=m +CONFIG_SND_SOC_AK4554=m +CONFIG_SND_SOC_CS4265=m +CONFIG_SND_SOC_ICS43432=m +CONFIG_SND_SOC_MA120X0P=m +CONFIG_SND_SOC_MAX98357A=m +CONFIG_SND_SOC_PCM3168A_I2C=m +CONFIG_SND_SOC_TLV320AIC23_I2C=m +CONFIG_SND_SOC_WM8804_I2C=m +CONFIG_SND_SOC_WM8904=m +CONFIG_SND_SOC_WM8960=m +CONFIG_SND_SIMPLE_CARD=m +CONFIG_HID_BATTERY_STRENGTH=y +CONFIG_HIDRAW=y +CONFIG_UHID=m +CONFIG_HID_A4TECH=m +CONFIG_HID_ACRUX=m +CONFIG_HID_APPLE=m +CONFIG_HID_ASUS=m +CONFIG_HID_BELKIN=m +CONFIG_HID_BETOP_FF=m +CONFIG_HID_BIGBEN_FF=m +CONFIG_HID_CHERRY=m +CONFIG_HID_CHICONY=m +CONFIG_HID_CYPRESS=m +CONFIG_HID_DRAGONRISE=m +CONFIG_HID_EMS_FF=m +CONFIG_HID_ELECOM=m +CONFIG_HID_ELO=m +CONFIG_HID_EZKEY=m +CONFIG_HID_GEMBIRD=m +CONFIG_HID_HOLTEK=m +CONFIG_HID_KEYTOUCH=m +CONFIG_HID_KYE=m +CONFIG_HID_UCLOGIC=m +CONFIG_HID_WALTOP=m +CONFIG_HID_GYRATION=m +CONFIG_HID_TWINHAN=m +CONFIG_HID_KENSINGTON=m +CONFIG_HID_LCPOWER=m +CONFIG_HID_LOGITECH=m +CONFIG_HID_LOGITECH_DJ=m +CONFIG_LOGITECH_FF=y +CONFIG_LOGIRUMBLEPAD2_FF=y +CONFIG_LOGIG940_FF=y +CONFIG_HID_MAGICMOUSE=m +CONFIG_HID_MICROSOFT=m +CONFIG_HID_MONTEREY=m +CONFIG_HID_MULTITOUCH=m +CONFIG_HID_NINTENDO=m +CONFIG_NINTENDO_FF=y +CONFIG_HID_NTRIG=m +CONFIG_HID_ORTEK=m +CONFIG_HID_PANTHERLORD=m +CONFIG_HID_PETALYNX=m +CONFIG_HID_PICOLCD=m +CONFIG_HID_PLAYSTATION=m +CONFIG_PLAYSTATION_FF=y +CONFIG_HID_ROCCAT=m +CONFIG_HID_SAMSUNG=m +CONFIG_HID_SONY=m +CONFIG_SONY_FF=y +CONFIG_HID_SPEEDLINK=m +CONFIG_HID_STEAM=m +CONFIG_HID_SUNPLUS=m +CONFIG_HID_GREENASIA=m +CONFIG_HID_SMARTJOYPLUS=m +CONFIG_HID_TOPSEED=m +CONFIG_HID_THINGM=m +CONFIG_HID_THRUSTMASTER=m +CONFIG_HID_WACOM=m +CONFIG_HID_WIIMOTE=m +CONFIG_HID_XINMO=m +CONFIG_HID_ZEROPLUS=m +CONFIG_HID_ZYDACRON=m +CONFIG_HID_PID=y +CONFIG_USB_HIDDEV=y +CONFIG_I2C_HID_OF=m +CONFIG_USB=y +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y +CONFIG_USB_MON=m +CONFIG_USB_XHCI_HCD=y +CONFIG_USB_XHCI_PCI_RENESAS=m +CONFIG_USB_DWCOTG=y +CONFIG_USB_PRINTER=m +CONFIG_USB_TMC=m +CONFIG_USB_STORAGE=y +CONFIG_USB_STORAGE_REALTEK=m +CONFIG_USB_STORAGE_DATAFAB=m +CONFIG_USB_STORAGE_FREECOM=m +CONFIG_USB_STORAGE_ISD200=m +CONFIG_USB_STORAGE_USBAT=m +CONFIG_USB_STORAGE_SDDR09=m +CONFIG_USB_STORAGE_SDDR55=m +CONFIG_USB_STORAGE_JUMPSHOT=m +CONFIG_USB_STORAGE_ALAUDA=m +CONFIG_USB_STORAGE_ONETOUCH=m +CONFIG_USB_STORAGE_KARMA=m +CONFIG_USB_STORAGE_CYPRESS_ATACB=m +CONFIG_USB_STORAGE_ENE_UB6250=m +CONFIG_USB_UAS=y +CONFIG_USB_MDC800=m +CONFIG_USB_MICROTEK=m +CONFIG_USBIP_CORE=m +CONFIG_USBIP_VHCI_HCD=m +CONFIG_USBIP_HOST=m +CONFIG_USBIP_VUDC=m +CONFIG_USB_DWC3=y +CONFIG_USB_DWC2=m +CONFIG_USB_SERIAL=m +CONFIG_USB_SERIAL_GENERIC=y +CONFIG_USB_SERIAL_SIMPLE=m +CONFIG_USB_SERIAL_AIRCABLE=m +CONFIG_USB_SERIAL_ARK3116=m +CONFIG_USB_SERIAL_BELKIN=m +CONFIG_USB_SERIAL_CH341=m +CONFIG_USB_SERIAL_WHITEHEAT=m +CONFIG_USB_SERIAL_DIGI_ACCELEPORT=m +CONFIG_USB_SERIAL_CP210X=m +CONFIG_USB_SERIAL_CYPRESS_M8=m +CONFIG_USB_SERIAL_EMPEG=m +CONFIG_USB_SERIAL_FTDI_SIO=m +CONFIG_USB_SERIAL_VISOR=m +CONFIG_USB_SERIAL_IPAQ=m +CONFIG_USB_SERIAL_IR=m +CONFIG_USB_SERIAL_EDGEPORT=m +CONFIG_USB_SERIAL_EDGEPORT_TI=m +CONFIG_USB_SERIAL_F81232=m +CONFIG_USB_SERIAL_F8153X=m +CONFIG_USB_SERIAL_GARMIN=m +CONFIG_USB_SERIAL_IPW=m +CONFIG_USB_SERIAL_IUU=m +CONFIG_USB_SERIAL_KEYSPAN_PDA=m +CONFIG_USB_SERIAL_KEYSPAN=m +CONFIG_USB_SERIAL_KLSI=m +CONFIG_USB_SERIAL_KOBIL_SCT=m +CONFIG_USB_SERIAL_MCT_U232=m +CONFIG_USB_SERIAL_METRO=m +CONFIG_USB_SERIAL_MOS7720=m +CONFIG_USB_SERIAL_MOS7840=m +CONFIG_USB_SERIAL_MXUPORT=m +CONFIG_USB_SERIAL_NAVMAN=m +CONFIG_USB_SERIAL_PL2303=m +CONFIG_USB_SERIAL_OTI6858=m +CONFIG_USB_SERIAL_QCAUX=m +CONFIG_USB_SERIAL_QUALCOMM=m +CONFIG_USB_SERIAL_SPCP8X5=m +CONFIG_USB_SERIAL_SAFE=m +CONFIG_USB_SERIAL_SIERRAWIRELESS=m +CONFIG_USB_SERIAL_SYMBOL=m +CONFIG_USB_SERIAL_TI=m +CONFIG_USB_SERIAL_CYBERJACK=m +CONFIG_USB_SERIAL_OPTION=m +CONFIG_USB_SERIAL_OMNINET=m +CONFIG_USB_SERIAL_OPTICON=m +CONFIG_USB_SERIAL_XSENS_MT=m +CONFIG_USB_SERIAL_WISHBONE=m +CONFIG_USB_SERIAL_SSU100=m +CONFIG_USB_SERIAL_QT2=m +CONFIG_USB_SERIAL_UPD78F0730=m +CONFIG_USB_SERIAL_XR=m +CONFIG_USB_SERIAL_DEBUG=m +CONFIG_USB_EMI62=m +CONFIG_USB_EMI26=m +CONFIG_USB_ADUTUX=m +CONFIG_USB_SEVSEG=m +CONFIG_USB_LEGOTOWER=m +CONFIG_USB_LCD=m +CONFIG_USB_CYPRESS_CY7C63=m +CONFIG_USB_CYTHERM=m +CONFIG_USB_IDMOUSE=m +CONFIG_USB_APPLEDISPLAY=m +CONFIG_USB_LD=m +CONFIG_USB_TRANCEVIBRATOR=m +CONFIG_USB_IOWARRIOR=m +CONFIG_USB_TEST=m +CONFIG_USB_ISIGHTFW=m +CONFIG_USB_YUREX=m +CONFIG_USB_ATM=m +CONFIG_USB_SPEEDTOUCH=m +CONFIG_USB_CXACRU=m +CONFIG_USB_UEAGLEATM=m +CONFIG_USB_XUSBATM=m +CONFIG_NOP_USB_XCEIV=y +CONFIG_USB_GADGET=y +CONFIG_USB_CONFIGFS=m +CONFIG_USB_CONFIGFS_SERIAL=y +CONFIG_USB_CONFIGFS_ACM=y +CONFIG_USB_CONFIGFS_OBEX=y +CONFIG_USB_CONFIGFS_NCM=y +CONFIG_USB_CONFIGFS_ECM=y +CONFIG_USB_CONFIGFS_ECM_SUBSET=y +CONFIG_USB_CONFIGFS_RNDIS=y +CONFIG_USB_CONFIGFS_EEM=y +CONFIG_USB_CONFIGFS_MASS_STORAGE=y +CONFIG_USB_CONFIGFS_F_LB_SS=y +CONFIG_USB_CONFIGFS_F_FS=y +CONFIG_USB_CONFIGFS_F_UAC1=y +CONFIG_USB_CONFIGFS_F_UAC2=y +CONFIG_USB_CONFIGFS_F_MIDI=y +CONFIG_USB_CONFIGFS_F_HID=y +CONFIG_USB_CONFIGFS_F_UVC=y +CONFIG_USB_CONFIGFS_F_PRINTER=y +CONFIG_USB_ZERO=m +CONFIG_USB_AUDIO=m +CONFIG_USB_ETH=m +CONFIG_USB_GADGETFS=m +CONFIG_USB_MASS_STORAGE=m +CONFIG_USB_G_SERIAL=m +CONFIG_USB_MIDI_GADGET=m +CONFIG_USB_G_PRINTER=m +CONFIG_USB_CDC_COMPOSITE=m +CONFIG_USB_G_ACM_MS=m +CONFIG_USB_G_MULTI=m +CONFIG_USB_G_HID=m +CONFIG_USB_G_WEBCAM=m +CONFIG_MMC=y +CONFIG_MMC_BLOCK_MINORS=32 +CONFIG_MMC_BCM2835_MMC=y +CONFIG_MMC_BCM2835_DMA=y +CONFIG_MMC_SDHCI=y +CONFIG_MMC_SDHCI_PLTFM=y +CONFIG_MMC_SDHCI_OF_DWCMSHC=m +CONFIG_MMC_SDHCI_IPROC=y +CONFIG_MMC_SPI=m +CONFIG_MMC_HSQ=y +CONFIG_MMC_BCM2835=y +CONFIG_LEDS_CLASS=y +CONFIG_LEDS_CLASS_MULTICOLOR=m +CONFIG_LEDS_PCA9532=m +CONFIG_LEDS_GPIO=y +CONFIG_LEDS_PCA955X=m +CONFIG_LEDS_PCA963X=m +CONFIG_LEDS_PWM=y +CONFIG_LEDS_IS31FL32XX=m +CONFIG_LEDS_TRIGGER_TIMER=y +CONFIG_LEDS_TRIGGER_ONESHOT=y +CONFIG_LEDS_TRIGGER_HEARTBEAT=y +CONFIG_LEDS_TRIGGER_BACKLIGHT=y +CONFIG_LEDS_TRIGGER_CPU=y +CONFIG_LEDS_TRIGGER_DEFAULT_ON=y +CONFIG_LEDS_TRIGGER_TRANSIENT=m +CONFIG_LEDS_TRIGGER_CAMERA=m +CONFIG_LEDS_TRIGGER_INPUT=y +CONFIG_LEDS_TRIGGER_PANIC=y +CONFIG_LEDS_TRIGGER_NETDEV=m +CONFIG_LEDS_TRIGGER_PATTERN=m +CONFIG_LEDS_TRIGGER_ACTPWR=y +CONFIG_ACCESSIBILITY=y +CONFIG_SPEAKUP=m +CONFIG_SPEAKUP_SYNTH_SOFT=m +CONFIG_RTC_CLASS=y +CONFIG_RTC_DRV_ABX80X=m +CONFIG_RTC_DRV_DS1307=m +CONFIG_RTC_DRV_DS1374=m +CONFIG_RTC_DRV_DS1672=m +CONFIG_RTC_DRV_MAX6900=m +CONFIG_RTC_DRV_RS5C372=m +CONFIG_RTC_DRV_ISL1208=m +CONFIG_RTC_DRV_ISL12022=m +CONFIG_RTC_DRV_X1205=m +CONFIG_RTC_DRV_PCF8523=m +CONFIG_RTC_DRV_PCF85063=m +CONFIG_RTC_DRV_PCF85363=m +CONFIG_RTC_DRV_PCF8563=m +CONFIG_RTC_DRV_PCF8583=m +CONFIG_RTC_DRV_M41T80=m +CONFIG_RTC_DRV_BQ32K=m +CONFIG_RTC_DRV_S35390A=m +CONFIG_RTC_DRV_FM3130=m +CONFIG_RTC_DRV_RX8581=m +CONFIG_RTC_DRV_RX8025=m +CONFIG_RTC_DRV_EM3027=m +CONFIG_RTC_DRV_RV3028=m +CONFIG_RTC_DRV_RV3032=m +CONFIG_RTC_DRV_RV8803=m +CONFIG_RTC_DRV_SD3078=m +CONFIG_RTC_DRV_M41T93=m +CONFIG_RTC_DRV_M41T94=m +CONFIG_RTC_DRV_DS1302=m +CONFIG_RTC_DRV_DS1305=m +CONFIG_RTC_DRV_DS1390=m +CONFIG_RTC_DRV_R9701=m +CONFIG_RTC_DRV_RX4581=m +CONFIG_RTC_DRV_RS5C348=m +CONFIG_RTC_DRV_MAX6902=m +CONFIG_RTC_DRV_PCF2123=m +CONFIG_RTC_DRV_DS3232=m +CONFIG_RTC_DRV_PCF2127=m +CONFIG_RTC_DRV_RV3029C2=m +CONFIG_DMADEVICES=y +CONFIG_DMA_BCM2835=y +CONFIG_DW_AXI_DMAC=y +CONFIG_DMA_BCM2708=y +CONFIG_DMABUF_HEAPS=y +CONFIG_DMABUF_HEAPS_SYSTEM=y +CONFIG_DMABUF_HEAPS_CMA=y +CONFIG_UIO=m +CONFIG_UIO_PDRV_GENIRQ=m +CONFIG_VHOST_NET=m +CONFIG_VHOST_VSOCK=m +CONFIG_VHOST_CROSS_ENDIAN_LEGACY=y +CONFIG_STAGING=y +CONFIG_R8712U=m +CONFIG_VT6656=m +CONFIG_STAGING_MEDIA=y +CONFIG_VIDEO_RPIVID=m +CONFIG_STAGING_MEDIA_DEPRECATED=y +CONFIG_FB_TFT=m +CONFIG_FB_TFT_AGM1264K_FL=m +CONFIG_FB_TFT_BD663474=m +CONFIG_FB_TFT_HX8340BN=m +CONFIG_FB_TFT_HX8347D=m +CONFIG_FB_TFT_HX8353D=m +CONFIG_FB_TFT_HX8357D=m +CONFIG_FB_TFT_ILI9163=m +CONFIG_FB_TFT_ILI9320=m +CONFIG_FB_TFT_ILI9325=m +CONFIG_FB_TFT_ILI9340=m +CONFIG_FB_TFT_ILI9341=m +CONFIG_FB_TFT_ILI9481=m +CONFIG_FB_TFT_ILI9486=m +CONFIG_FB_TFT_PCD8544=m +CONFIG_FB_TFT_RA8875=m +CONFIG_FB_TFT_S6D02A1=m +CONFIG_FB_TFT_S6D1121=m +CONFIG_FB_TFT_SH1106=m +CONFIG_FB_TFT_SSD1289=m +CONFIG_FB_TFT_SSD1306=m +CONFIG_FB_TFT_SSD1331=m +CONFIG_FB_TFT_SSD1351=m +CONFIG_FB_TFT_ST7735R=m +CONFIG_FB_TFT_ST7789V=m +CONFIG_FB_TFT_TINYLCD=m +CONFIG_FB_TFT_TLS8204=m +CONFIG_FB_TFT_UC1611=m +CONFIG_FB_TFT_UC1701=m +CONFIG_FB_TFT_UPD161704=m +CONFIG_BCM2835_VCHIQ=y +CONFIG_SND_BCM2835=m +CONFIG_VIDEO_BCM2835=m +CONFIG_VIDEO_CODEC_BCM2835=m +CONFIG_VIDEO_ISP_BCM2835=m +CONFIG_COMMON_CLK_RP1=y +CONFIG_COMMON_CLK_RP1_SDIO=y +CONFIG_CLK_RASPBERRYPI=y +CONFIG_MAILBOX=y +CONFIG_BCM2835_MBOX=y +CONFIG_MBOX_RP1=m +CONFIG_BCM2712_IOMMU=y +CONFIG_RASPBERRYPI_POWER=y +CONFIG_IIO=m +CONFIG_IIO_BUFFER_CB=m +CONFIG_IIO_SW_TRIGGER=m +CONFIG_MCP320X=m +CONFIG_MCP3422=m +CONFIG_TI_ADS1015=m +CONFIG_BME680=m +CONFIG_CCS811=m +CONFIG_SENSIRION_SGP30=m +CONFIG_SPS30_I2C=m +CONFIG_MAX30102=m +CONFIG_DHT11=m +CONFIG_HDC100X=m +CONFIG_HDC3020=m +CONFIG_HTS221=m +CONFIG_HTU21=m +CONFIG_SI7020=m +CONFIG_BOSCH_BNO055_I2C=m +CONFIG_INV_MPU6050_I2C=m +CONFIG_APDS9960=m +CONFIG_AS73211=m +CONFIG_BH1750=m +CONFIG_TSL4531=m +CONFIG_VEML6070=m +CONFIG_VEML6075=m +CONFIG_IIO_HRTIMER_TRIGGER=m +CONFIG_IIO_INTERRUPT_TRIGGER=m +CONFIG_IIO_SYSFS_TRIGGER=m +CONFIG_BMP280=m +CONFIG_MS5637=m +CONFIG_MAXIM_THERMOCOUPLE=m +CONFIG_MAX31856=m +CONFIG_PWM=y +CONFIG_PWM_BCM2835=m +CONFIG_PWM_BRCMSTB=y +CONFIG_PWM_GPIO=m +CONFIG_PWM_PCA9685=m +CONFIG_PWM_PIO_RP1=m +CONFIG_PWM_RASPBERRYPI_POE=m +CONFIG_PWM_RP1=y +CONFIG_BCM2712_MIP=y +CONFIG_RPI_AXIPERF=m +CONFIG_ANDROID_BINDER_IPC=y +CONFIG_ANDROID_BINDERFS=y +CONFIG_NVMEM_RMEM=m +CONFIG_MUX_GPIO=m +CONFIG_EXT4_FS=y +CONFIG_EXT4_FS_POSIX_ACL=y +CONFIG_EXT4_FS_SECURITY=y +CONFIG_REISERFS_FS=m +CONFIG_REISERFS_FS_XATTR=y +CONFIG_REISERFS_FS_POSIX_ACL=y +CONFIG_REISERFS_FS_SECURITY=y +CONFIG_JFS_FS=m +CONFIG_JFS_POSIX_ACL=y +CONFIG_JFS_SECURITY=y +CONFIG_JFS_STATISTICS=y +CONFIG_XFS_FS=m +CONFIG_XFS_QUOTA=y +CONFIG_XFS_POSIX_ACL=y +CONFIG_XFS_RT=y +CONFIG_GFS2_FS=m +CONFIG_OCFS2_FS=m +CONFIG_BTRFS_FS=m +CONFIG_BTRFS_FS_POSIX_ACL=y +CONFIG_NILFS2_FS=m +CONFIG_F2FS_FS=y +CONFIG_F2FS_FS_SECURITY=y +CONFIG_BCACHEFS_FS=m +CONFIG_BCACHEFS_QUOTA=y +CONFIG_BCACHEFS_POSIX_ACL=y +CONFIG_FS_ENCRYPTION=y +CONFIG_FANOTIFY=y +CONFIG_QFMT_V1=m +CONFIG_QFMT_V2=m +CONFIG_AUTOFS_FS=y +CONFIG_FUSE_FS=m +CONFIG_CUSE=m +CONFIG_OVERLAY_FS=m +CONFIG_FSCACHE=y +CONFIG_FSCACHE_STATS=y +CONFIG_CACHEFILES=m +CONFIG_ISO9660_FS=m +CONFIG_JOLIET=y +CONFIG_ZISOFS=y +CONFIG_UDF_FS=m +CONFIG_MSDOS_FS=y +CONFIG_VFAT_FS=y +CONFIG_FAT_DEFAULT_IOCHARSET="ascii" +CONFIG_EXFAT_FS=m +CONFIG_NTFS3_FS=m +CONFIG_TMPFS=y +CONFIG_TMPFS_POSIX_ACL=y +CONFIG_ECRYPT_FS=m +CONFIG_HFS_FS=m +CONFIG_HFSPLUS_FS=m +CONFIG_JFFS2_FS=m +CONFIG_JFFS2_SUMMARY=y +CONFIG_UBIFS_FS=m +CONFIG_SQUASHFS=m +CONFIG_SQUASHFS_XATTR=y +CONFIG_SQUASHFS_LZO=y +CONFIG_SQUASHFS_XZ=y +CONFIG_SQUASHFS_ZSTD=y +CONFIG_PSTORE=y +CONFIG_PSTORE_CONSOLE=y +CONFIG_PSTORE_RAM=y +CONFIG_NFS_FS=y +CONFIG_NFS_V2=y +CONFIG_NFS_V3_ACL=y +CONFIG_NFS_V4=y +CONFIG_NFS_SWAP=y +CONFIG_NFS_V4_1=y +CONFIG_NFS_V4_2=y +CONFIG_ROOT_NFS=y +CONFIG_NFSD=m +CONFIG_NFSD_V2=y +CONFIG_NFSD_V2_ACL=y +CONFIG_NFSD_V3_ACL=y +CONFIG_NFSD_V4=y +CONFIG_CEPH_FS=m +CONFIG_CIFS=m +CONFIG_CIFS_UPCALL=y +CONFIG_CIFS_XATTR=y +CONFIG_CIFS_POSIX=y +CONFIG_CIFS_DFS_UPCALL=y +CONFIG_CIFS_FSCACHE=y +CONFIG_SMB_SERVER=m +CONFIG_9P_FS=m +CONFIG_9P_FS_POSIX_ACL=y +CONFIG_NLS_DEFAULT="utf8" +CONFIG_NLS_CODEPAGE_437=y +CONFIG_NLS_CODEPAGE_737=m +CONFIG_NLS_CODEPAGE_775=m +CONFIG_NLS_CODEPAGE_850=m +CONFIG_NLS_CODEPAGE_852=m +CONFIG_NLS_CODEPAGE_855=m +CONFIG_NLS_CODEPAGE_857=m +CONFIG_NLS_CODEPAGE_860=m +CONFIG_NLS_CODEPAGE_861=m +CONFIG_NLS_CODEPAGE_862=m +CONFIG_NLS_CODEPAGE_863=m +CONFIG_NLS_CODEPAGE_864=m +CONFIG_NLS_CODEPAGE_865=m +CONFIG_NLS_CODEPAGE_866=m +CONFIG_NLS_CODEPAGE_869=m +CONFIG_NLS_CODEPAGE_936=m +CONFIG_NLS_CODEPAGE_950=m +CONFIG_NLS_CODEPAGE_932=m +CONFIG_NLS_CODEPAGE_949=m +CONFIG_NLS_CODEPAGE_874=m +CONFIG_NLS_ISO8859_8=m +CONFIG_NLS_CODEPAGE_1250=m +CONFIG_NLS_CODEPAGE_1251=m +CONFIG_NLS_ASCII=y +CONFIG_NLS_ISO8859_1=m +CONFIG_NLS_ISO8859_2=m +CONFIG_NLS_ISO8859_3=m +CONFIG_NLS_ISO8859_4=m +CONFIG_NLS_ISO8859_5=m +CONFIG_NLS_ISO8859_6=m +CONFIG_NLS_ISO8859_7=m +CONFIG_NLS_ISO8859_9=m +CONFIG_NLS_ISO8859_13=m +CONFIG_NLS_ISO8859_14=m +CONFIG_NLS_ISO8859_15=m +CONFIG_NLS_KOI8_R=m +CONFIG_NLS_KOI8_U=m +CONFIG_DLM=m +CONFIG_KEY_DH_OPERATIONS=y +CONFIG_SECURITY=y +CONFIG_SECURITY_APPARMOR=y +CONFIG_LSM="" +CONFIG_CRYPTO_USER=m +CONFIG_CRYPTO_CRYPTD=m +CONFIG_CRYPTO_AES=m +CONFIG_CRYPTO_CAST5=m +CONFIG_CRYPTO_DES=y +CONFIG_CRYPTO_TWOFISH=m +CONFIG_CRYPTO_ADIANTUM=m +CONFIG_CRYPTO_CBC=m +CONFIG_CRYPTO_CHACHA20POLY1305=m +CONFIG_CRYPTO_MD4=m +CONFIG_CRYPTO_SHA512=m +CONFIG_CRYPTO_WP512=m +CONFIG_CRYPTO_XCBC=m +CONFIG_CRYPTO_LZ4=m +CONFIG_CRYPTO_USER_API_HASH=m +CONFIG_CRYPTO_USER_API_SKCIPHER=m +CONFIG_CRYPTO_USER_API_RNG=m +CONFIG_CRYPTO_USER_API_AEAD=m +CONFIG_CRYPTO_NHPOLY1305_NEON=m +CONFIG_CRYPTO_GHASH_ARM64_CE=m +CONFIG_CRYPTO_SHA1_ARM64_CE=m +CONFIG_CRYPTO_SHA2_ARM64_CE=m +CONFIG_CRYPTO_SHA512_ARM64_CE=m +CONFIG_CRYPTO_SHA3_ARM64=m +CONFIG_CRYPTO_SM3_ARM64_CE=m +CONFIG_CRYPTO_AES_ARM64=m +CONFIG_CRYPTO_AES_ARM64_BS=m +CONFIG_CRYPTO_SM4_ARM64_CE=m +CONFIG_CRYPTO_AES_ARM64_CE_CCM=m +# CONFIG_CRYPTO_HW is not set +CONFIG_PKCS8_PRIVATE_KEY_PARSER=m +CONFIG_CRC_ITU_T=y +CONFIG_LIBCRC32C=y +CONFIG_DMA_CMA=y +CONFIG_CMA_SIZE_MBYTES=5 +CONFIG_PRINTK_TIME=y +CONFIG_BOOT_PRINTK_DELAY=y +CONFIG_MAGIC_SYSRQ_DEFAULT_ENABLE=0x1f6 +CONFIG_KGDB=y +CONFIG_KGDB_KDB=y +CONFIG_KDB_KEYBOARD=y +CONFIG_DEBUG_MEMORY_INIT=y +CONFIG_DETECT_HUNG_TASK=y +CONFIG_LATENCYTOP=y +CONFIG_FUNCTION_PROFILER=y +CONFIG_STACK_TRACER=y +CONFIG_SCHED_TRACER=y +CONFIG_BLK_DEV_IO_TRACE=y +# CONFIG_UPROBE_EVENTS is not set +# CONFIG_STRICT_DEVMEM is not set diff --git a/arch/arm64/crypto/aes-cipher-glue.c b/arch/arm64/crypto/aes-cipher-glue.c index 4ec55e568941c0..bfaa0f1d3cc688 100644 --- a/arch/arm64/crypto/aes-cipher-glue.c +++ b/arch/arm64/crypto/aes-cipher-glue.c @@ -9,6 +9,17 @@ #include <crypto/algapi.h> #include <linux/module.h> +MODULE_ALIAS_CRYPTO("ecb(aes)"); +MODULE_ALIAS_CRYPTO("cbc(aes)"); +MODULE_ALIAS_CRYPTO("ctr(aes)"); +MODULE_ALIAS_CRYPTO("xts(aes)"); +MODULE_ALIAS_CRYPTO("xctr(aes)"); +MODULE_ALIAS_CRYPTO("cts(cbc(aes))"); +MODULE_ALIAS_CRYPTO("essiv(cbc(aes),sha256)"); +MODULE_ALIAS_CRYPTO("cmac(aes)"); +MODULE_ALIAS_CRYPTO("xcbc(aes)"); +MODULE_ALIAS_CRYPTO("cbcmac(aes)"); + asmlinkage void __aes_arm64_encrypt(u32 *rk, u8 *out, const u8 *in, int rounds); asmlinkage void __aes_arm64_decrypt(u32 *rk, u8 *out, const u8 *in, int rounds); diff --git a/arch/arm64/crypto/aes-glue.c b/arch/arm64/crypto/aes-glue.c index a147e847a5a181..1cbf1577c43d7b 100644 --- a/arch/arm64/crypto/aes-glue.c +++ b/arch/arm64/crypto/aes-glue.c @@ -57,18 +57,18 @@ MODULE_DESCRIPTION("AES-ECB/CBC/CTR/XTS/XCTR using ARMv8 Crypto Extensions"); #define aes_mac_update neon_aes_mac_update MODULE_DESCRIPTION("AES-ECB/CBC/CTR/XTS/XCTR using ARMv8 NEON"); #endif -#if defined(USE_V8_CRYPTO_EXTENSIONS) || !IS_ENABLED(CONFIG_CRYPTO_AES_ARM64_BS) +#if defined(USE_V8_CRYPTO_EXTENSIONS) MODULE_ALIAS_CRYPTO("ecb(aes)"); MODULE_ALIAS_CRYPTO("cbc(aes)"); MODULE_ALIAS_CRYPTO("ctr(aes)"); MODULE_ALIAS_CRYPTO("xts(aes)"); MODULE_ALIAS_CRYPTO("xctr(aes)"); -#endif MODULE_ALIAS_CRYPTO("cts(cbc(aes))"); MODULE_ALIAS_CRYPTO("essiv(cbc(aes),sha256)"); MODULE_ALIAS_CRYPTO("cmac(aes)"); MODULE_ALIAS_CRYPTO("xcbc(aes)"); MODULE_ALIAS_CRYPTO("cbcmac(aes)"); +#endif MODULE_AUTHOR("Ard Biesheuvel <ard.biesheuvel@linaro.org>"); MODULE_LICENSE("GPL v2"); diff --git a/arch/arm64/crypto/aes-neonbs-glue.c b/arch/arm64/crypto/aes-neonbs-glue.c index 46425e7b9755e0..2809349884daa6 100644 --- a/arch/arm64/crypto/aes-neonbs-glue.c +++ b/arch/arm64/crypto/aes-neonbs-glue.c @@ -19,11 +19,6 @@ MODULE_AUTHOR("Ard Biesheuvel <ard.biesheuvel@linaro.org>"); MODULE_DESCRIPTION("Bit sliced AES using NEON instructions"); MODULE_LICENSE("GPL v2"); -MODULE_ALIAS_CRYPTO("ecb(aes)"); -MODULE_ALIAS_CRYPTO("cbc(aes)"); -MODULE_ALIAS_CRYPTO("ctr(aes)"); -MODULE_ALIAS_CRYPTO("xts(aes)"); - asmlinkage void aesbs_convert_key(u8 out[], u32 const rk[], int rounds); asmlinkage void aesbs_ecb_encrypt(u8 out[], u8 const in[], u8 const rk[], diff --git a/arch/arm64/kernel/armv8_deprecated.c b/arch/arm64/kernel/armv8_deprecated.c index e737c6295ec759..abb590819ac0ba 100644 --- a/arch/arm64/kernel/armv8_deprecated.c +++ b/arch/arm64/kernel/armv8_deprecated.c @@ -540,9 +540,14 @@ static void __init register_insn_emulation(struct insn_emulation *insn) switch (insn->status) { case INSN_DEPRECATED: +#if 0 insn->current_mode = INSN_EMULATE; /* Disable the HW mode if it was turned on at early boot time */ run_all_cpu_set_hw_mode(insn, false); +#else + insn->current_mode = INSN_HW; + run_all_cpu_set_hw_mode(insn, true); +#endif insn->max = INSN_HW; break; case INSN_OBSOLETE: diff --git a/arch/arm64/kernel/cpuinfo.c b/arch/arm64/kernel/cpuinfo.c index aec5e3947c780a..1534bbbcd0d05f 100644 --- a/arch/arm64/kernel/cpuinfo.c +++ b/arch/arm64/kernel/cpuinfo.c @@ -17,6 +17,8 @@ #include <linux/elf.h> #include <linux/init.h> #include <linux/kernel.h> +#include <linux/of.h> +#include <linux/of_platform.h> #include <linux/personality.h> #include <linux/preempt.h> #include <linux/printk.h> @@ -195,6 +197,10 @@ static int c_show(struct seq_file *m, void *v) { int i, j; bool compat = personality(current->personality) == PER_LINUX32; + struct device_node *np; + const char *model; + const char *serial; + u32 revision; for_each_online_cpu(i) { struct cpuinfo_arm64 *cpuinfo = &per_cpu(cpu_data, i); @@ -255,6 +261,24 @@ static int c_show(struct seq_file *m, void *v) seq_printf(m, "CPU revision\t: %d\n\n", MIDR_REVISION(midr)); } + np = of_find_node_by_path("/system"); + if (np) { + if (!of_property_read_u32(np, "linux,revision", &revision)) + seq_printf(m, "Revision\t: %04x\n", revision); + of_node_put(np); + } + + np = of_find_node_by_path("/"); + if (np) { + if (!of_property_read_string(np, "serial-number", + &serial)) + seq_printf(m, "Serial\t\t: %s\n", serial); + if (!of_property_read_string(np, "model", + &model)) + seq_printf(m, "Model\t\t: %s\n", model); + of_node_put(np); + } + return 0; } diff --git a/arch/arm64/kernel/process.c b/arch/arm64/kernel/process.c index 2bbcbb11d844c9..bec38af5806c5e 100644 --- a/arch/arm64/kernel/process.c +++ b/arch/arm64/kernel/process.c @@ -97,9 +97,7 @@ void machine_shutdown(void) */ void machine_halt(void) { - local_irq_disable(); - smp_send_stop(); - while (1); + machine_power_off(); } /* diff --git a/arch/arm64/kernel/setup.c b/arch/arm64/kernel/setup.c index 87f61fd6783c20..aecd77c9746e18 100644 --- a/arch/arm64/kernel/setup.c +++ b/arch/arm64/kernel/setup.c @@ -214,9 +214,9 @@ static void __init request_standard_resources(void) size_t res_size; kernel_code.start = __pa_symbol(_stext); - kernel_code.end = __pa_symbol(__init_begin - 1); + kernel_code.end = __pa_symbol(__init_begin) - 1; kernel_data.start = __pa_symbol(_sdata); - kernel_data.end = __pa_symbol(_end - 1); + kernel_data.end = __pa_symbol(_end) - 1; insert_resource(&iomem_resource, &kernel_code); insert_resource(&iomem_resource, &kernel_data); diff --git a/drivers/bluetooth/btbcm.c b/drivers/bluetooth/btbcm.c index 0a60660fc8ce80..dd1e6f21d55566 100644 --- a/drivers/bluetooth/btbcm.c +++ b/drivers/bluetooth/btbcm.c @@ -25,12 +25,15 @@ #define BDADDR_BCM20702A1 (&(bdaddr_t) {{0x00, 0x00, 0xa0, 0x02, 0x70, 0x20}}) #define BDADDR_BCM2076B1 (&(bdaddr_t) {{0x79, 0x56, 0x00, 0xa0, 0x76, 0x20}}) #define BDADDR_BCM43430A0 (&(bdaddr_t) {{0xac, 0x1f, 0x12, 0xa0, 0x43, 0x43}}) -#define BDADDR_BCM43430A1 (&(bdaddr_t) {{0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa}}) +#define BDADDR_BCM43430A1 (&(bdaddr_t) {{0xac, 0x1f, 0x12, 0xa1, 0x43, 0x43}}) +#define BDADDR_BCM43430B0 (&(bdaddr_t) {{0xac, 0x1f, 0x37, 0xb0, 0x43, 0x43}}) #define BDADDR_BCM4324B3 (&(bdaddr_t) {{0x00, 0x00, 0x00, 0xb3, 0x24, 0x43}}) #define BDADDR_BCM4330B1 (&(bdaddr_t) {{0x00, 0x00, 0x00, 0xb1, 0x30, 0x43}}) #define BDADDR_BCM4334B0 (&(bdaddr_t) {{0x00, 0x00, 0x00, 0xb0, 0x34, 0x43}}) +#define BDADDR_BCM4345C0 (&(bdaddr_t) {{0xac, 0x1f, 0x00, 0xc0, 0x45, 0x43}}) #define BDADDR_BCM4345C5 (&(bdaddr_t) {{0xac, 0x1f, 0x00, 0xc5, 0x45, 0x43}}) #define BDADDR_BCM43341B (&(bdaddr_t) {{0xac, 0x1f, 0x00, 0x1b, 0x34, 0x43}}) +#define BDADDR_BCM43438 (&(bdaddr_t) {{0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa}}) #define BCM_FW_NAME_LEN 64 #define BCM_FW_NAME_COUNT_MAX 4 @@ -127,9 +130,12 @@ int btbcm_check_bdaddr(struct hci_dev *hdev) !bacmp(&bda->bdaddr, BDADDR_BCM4324B3) || !bacmp(&bda->bdaddr, BDADDR_BCM4330B1) || !bacmp(&bda->bdaddr, BDADDR_BCM4334B0) || + !bacmp(&bda->bdaddr, BDADDR_BCM4345C0) || !bacmp(&bda->bdaddr, BDADDR_BCM4345C5) || !bacmp(&bda->bdaddr, BDADDR_BCM43430A0) || !bacmp(&bda->bdaddr, BDADDR_BCM43430A1) || + !bacmp(&bda->bdaddr, BDADDR_BCM43430B0) || + !bacmp(&bda->bdaddr, BDADDR_BCM43438) || !bacmp(&bda->bdaddr, BDADDR_BCM43341B)) { /* Try falling back to BDADDR EFI variable */ if (btbcm_set_bdaddr_from_efi(hdev) != 0) { @@ -515,6 +521,7 @@ static const struct bcm_subver_table bcm_uart_subver_table[] = { { 0x4106, "BCM4335A0" }, /* 002.001.006 */ { 0x410c, "BCM43430B0" }, /* 002.001.012 */ { 0x2119, "BCM4373A0" }, /* 001.001.025 */ + { 0x2310, "BCM4343A2" }, /* 001.003.016 */ { } }; diff --git a/drivers/bluetooth/hci_h5.c b/drivers/bluetooth/hci_h5.c index c0436881a533c5..0717d12f972a56 100644 --- a/drivers/bluetooth/hci_h5.c +++ b/drivers/bluetooth/hci_h5.c @@ -358,7 +358,8 @@ static void h5_handle_internal_rx(struct hci_uart *hu) h5_link_control(hu, conf_req, 3); } else if (memcmp(data, conf_req, 2) == 0) { h5_link_control(hu, conf_rsp, 2); - h5_link_control(hu, conf_req, 3); + if (h5->state != H5_ACTIVE) + h5_link_control(hu, conf_req, 3); } else if (memcmp(data, conf_rsp, 2) == 0) { if (H5_HDR_LEN(hdr) > 2) h5->tx_win = (data[2] & 0x07); diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index 7c8dd0abcfdf72..8b38fd39bba699 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -5,6 +5,8 @@ menu "Character devices" +source "drivers/char/broadcom/Kconfig" + source "drivers/tty/Kconfig" config TTY_PRINTK @@ -422,4 +424,12 @@ config ADI and SSM (Silicon Secured Memory). Intended consumers of this driver include crash and makedumpfile. +config RASPBERRYPI_GPIOMEM + tristate "Rootless GPIO access via mmap() on Raspberry Pi boards" + default n + help + Provides users with root-free access to the GPIO registers + on the board. Calling mmap(/dev/gpiomem) will map the GPIO + register page to the user's pointer. + endmenu diff --git a/drivers/char/Makefile b/drivers/char/Makefile index e9b360cdc99a7f..c8a078fa491241 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -43,3 +43,5 @@ obj-$(CONFIG_PS3_FLASH) += ps3flash.o obj-$(CONFIG_XILLYBUS_CLASS) += xillybus/ obj-$(CONFIG_POWERNV_OP_PANEL) += powernv-op-panel.o obj-$(CONFIG_ADI) += adi.o +obj-$(CONFIG_BRCM_CHAR_DRIVERS) += broadcom/ +obj-$(CONFIG_RASPBERRYPI_GPIOMEM) += raspberrypi-gpiomem.o diff --git a/drivers/char/broadcom/Kconfig b/drivers/char/broadcom/Kconfig new file mode 100644 index 00000000000000..29d880d472820c --- /dev/null +++ b/drivers/char/broadcom/Kconfig @@ -0,0 +1,33 @@ +# +# Broadcom char driver config +# + +menuconfig BRCM_CHAR_DRIVERS + bool "Broadcom Char Drivers" + help + Broadcom's char drivers + +if BRCM_CHAR_DRIVERS + +config BCM2708_VCMEM + bool "Videocore Memory" + default y + help + Helper for videocore memory access and total size allocation. + +config BCM_VCIO + tristate "Mailbox userspace access" + depends on BCM2835_MBOX + help + Gives access to the mailbox property channel from userspace. + +endif + +config BCM2835_SMI_DEV + tristate "Character device driver for BCM2835 Secondary Memory Interface" + depends on BCM2835_SMI + default m + help + This driver provides a character device interface (ioctl + read/write) to + Broadcom's Secondary Memory interface. The low-level functionality is provided + by the SMI driver itself. diff --git a/drivers/char/broadcom/Makefile b/drivers/char/broadcom/Makefile new file mode 100644 index 00000000000000..2ae3e9d411e927 --- /dev/null +++ b/drivers/char/broadcom/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_BCM2708_VCMEM) += vc_mem.o +obj-$(CONFIG_BCM_VCIO) += vcio.o +obj-$(CONFIG_BCM2835_SMI_DEV) += bcm2835_smi_dev.o diff --git a/drivers/char/broadcom/bcm2835_smi_dev.c b/drivers/char/broadcom/bcm2835_smi_dev.c new file mode 100644 index 00000000000000..ae0c0d24fad91d --- /dev/null +++ b/drivers/char/broadcom/bcm2835_smi_dev.c @@ -0,0 +1,408 @@ +/** + * Character device driver for Broadcom Secondary Memory Interface + * + * Written by Luke Wren <luke@raspberrypi.org> + * Copyright (c) 2015, Raspberry Pi (Trading) Ltd. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The names of the above-listed copyright holders may not be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2, as published by the Free + * Software Foundation. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/pagemap.h> +#include <linux/fs.h> +#include <linux/cdev.h> +#include <linux/fs.h> + +#include <linux/broadcom/bcm2835_smi.h> + +#define DEVICE_NAME "bcm2835-smi-dev" +#define DRIVER_NAME "smi-dev-bcm2835" +#define DEVICE_MINOR 0 + +static struct cdev bcm2835_smi_cdev; +static dev_t bcm2835_smi_devid; +static struct class *bcm2835_smi_class; +static struct device *bcm2835_smi_dev; + +struct bcm2835_smi_dev_instance { + struct device *dev; +}; + +static struct bcm2835_smi_instance *smi_inst; +static struct bcm2835_smi_dev_instance *inst; + +static const char *const ioctl_names[] = { + "READ_SETTINGS", + "WRITE_SETTINGS", + "ADDRESS" +}; + +/**************************************************************************** +* +* SMI chardev file ops +* +***************************************************************************/ +static long +bcm2835_smi_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + long ret = 0; + + dev_info(inst->dev, "serving ioctl..."); + + switch (cmd) { + case BCM2835_SMI_IOC_GET_SETTINGS:{ + struct smi_settings *settings; + + dev_info(inst->dev, "Reading SMI settings to user."); + settings = bcm2835_smi_get_settings_from_regs(smi_inst); + if (copy_to_user((void *)arg, settings, + sizeof(struct smi_settings))) + dev_err(inst->dev, "settings copy failed."); + break; + } + case BCM2835_SMI_IOC_WRITE_SETTINGS:{ + struct smi_settings *settings; + + dev_info(inst->dev, "Setting user's SMI settings."); + settings = bcm2835_smi_get_settings_from_regs(smi_inst); + if (copy_from_user(settings, (void *)arg, + sizeof(struct smi_settings))) + dev_err(inst->dev, "settings copy failed."); + else + bcm2835_smi_set_regs_from_settings(smi_inst); + break; + } + case BCM2835_SMI_IOC_ADDRESS: + dev_info(inst->dev, "SMI address set: 0x%02x", (int)arg); + bcm2835_smi_set_address(smi_inst, arg); + break; + default: + dev_err(inst->dev, "invalid ioctl cmd: %d", cmd); + ret = -ENOTTY; + break; + } + + return ret; +} + +static int bcm2835_smi_open(struct inode *inode, struct file *file) +{ + int dev = iminor(inode); + + dev_dbg(inst->dev, "SMI device opened."); + + if (dev != DEVICE_MINOR) { + dev_err(inst->dev, + "bcm2835_smi_release: Unknown minor device: %d", + dev); + return -ENXIO; + } + + return 0; +} + +static int bcm2835_smi_release(struct inode *inode, struct file *file) +{ + int dev = iminor(inode); + + if (dev != DEVICE_MINOR) { + dev_err(inst->dev, + "bcm2835_smi_release: Unknown minor device %d", dev); + return -ENXIO; + } + + return 0; +} + +static ssize_t dma_bounce_user( + enum dma_transfer_direction dma_dir, + char __user *user_ptr, + size_t count, + struct bcm2835_smi_bounce_info *bounce) +{ + int chunk_size; + int chunk_no = 0; + int count_left = count; + + while (count_left) { + int rv; + void *buf; + + /* Wait for current chunk to complete: */ + if (down_timeout(&bounce->callback_sem, + msecs_to_jiffies(1000))) { + dev_err(inst->dev, "DMA bounce timed out"); + count -= (count_left); + break; + } + + if (bounce->callback_sem.count >= DMA_BOUNCE_BUFFER_COUNT - 1) + dev_err(inst->dev, "WARNING: Ring buffer overflow"); + chunk_size = count_left > DMA_BOUNCE_BUFFER_SIZE ? + DMA_BOUNCE_BUFFER_SIZE : count_left; + buf = bounce->buffer[chunk_no % DMA_BOUNCE_BUFFER_COUNT]; + if (dma_dir == DMA_DEV_TO_MEM) + rv = copy_to_user(user_ptr, buf, chunk_size); + else + rv = copy_from_user(buf, user_ptr, chunk_size); + if (rv) + dev_err(inst->dev, "copy_*_user() failed!: %d", rv); + user_ptr += chunk_size; + count_left -= chunk_size; + chunk_no++; + } + return count; +} + +static ssize_t +bcm2835_read_file(struct file *f, char __user *user_ptr, + size_t count, loff_t *offs) +{ + int odd_bytes; + size_t count_check; + + dev_dbg(inst->dev, "User reading %zu bytes from SMI.", count); + /* We don't want to DMA a number of bytes % 4 != 0 (32 bit FIFO) */ + if (count > DMA_THRESHOLD_BYTES) + odd_bytes = count & 0x3; + else + odd_bytes = count; + count -= odd_bytes; + count_check = count; + if (count) { + struct bcm2835_smi_bounce_info *bounce; + + count = bcm2835_smi_user_dma(smi_inst, + DMA_DEV_TO_MEM, user_ptr, count, + &bounce); + if (count) + count = dma_bounce_user(DMA_DEV_TO_MEM, user_ptr, + count, bounce); + } + if (odd_bytes && (count == count_check)) { + /* Read from FIFO directly if not using DMA */ + uint8_t buf[DMA_THRESHOLD_BYTES]; + unsigned long bytes_not_transferred; + + bcm2835_smi_read_buf(smi_inst, buf, odd_bytes); + bytes_not_transferred = copy_to_user(user_ptr + count, buf, odd_bytes); + if (bytes_not_transferred) + dev_err(inst->dev, "copy_to_user() failed."); + count += odd_bytes - bytes_not_transferred; + } + return count; +} + +static ssize_t +bcm2835_write_file(struct file *f, const char __user *user_ptr, + size_t count, loff_t *offs) +{ + int odd_bytes; + size_t count_check; + + dev_dbg(inst->dev, "User writing %zu bytes to SMI.", count); + if (count > DMA_THRESHOLD_BYTES) + odd_bytes = count & 0x3; + else + odd_bytes = count; + count -= odd_bytes; + count_check = count; + if (count) { + struct bcm2835_smi_bounce_info *bounce; + + count = bcm2835_smi_user_dma(smi_inst, + DMA_MEM_TO_DEV, (char __user *)user_ptr, count, + &bounce); + if (count) + count = dma_bounce_user(DMA_MEM_TO_DEV, + (char __user *)user_ptr, + count, bounce); + } + if (odd_bytes && (count == count_check)) { + uint8_t buf[DMA_THRESHOLD_BYTES]; + unsigned long bytes_not_transferred; + + bytes_not_transferred = copy_from_user(buf, user_ptr + count, odd_bytes); + if (bytes_not_transferred) + dev_err(inst->dev, "copy_from_user() failed."); + else + bcm2835_smi_write_buf(smi_inst, buf, odd_bytes); + count += odd_bytes - bytes_not_transferred; + } + return count; +} + +static const struct file_operations +bcm2835_smi_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = bcm2835_smi_ioctl, + .open = bcm2835_smi_open, + .release = bcm2835_smi_release, + .read = bcm2835_read_file, + .write = bcm2835_write_file, +}; + + +/**************************************************************************** +* +* bcm2835_smi_probe - called when the driver is loaded. +* +***************************************************************************/ + +static int bcm2835_smi_dev_probe(struct platform_device *pdev) +{ + int err; + void *ptr_err; + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node, *smi_node; + + if (!node) { + dev_err(dev, "No device tree node supplied!"); + return -EINVAL; + } + + smi_node = of_parse_phandle(node, "smi_handle", 0); + + if (!smi_node) { + dev_err(dev, "No such property: smi_handle"); + return -ENXIO; + } + + smi_inst = bcm2835_smi_get(smi_node); + + if (!smi_inst) + return -EPROBE_DEFER; + + /* Allocate buffers and instance data */ + + inst = devm_kzalloc(dev, sizeof(*inst), GFP_KERNEL); + + if (!inst) + return -ENOMEM; + + inst->dev = dev; + + /* Create character device entries */ + + err = alloc_chrdev_region(&bcm2835_smi_devid, + DEVICE_MINOR, 1, DEVICE_NAME); + if (err != 0) { + dev_err(inst->dev, "unable to allocate device number"); + return -ENOMEM; + } + cdev_init(&bcm2835_smi_cdev, &bcm2835_smi_fops); + bcm2835_smi_cdev.owner = THIS_MODULE; + err = cdev_add(&bcm2835_smi_cdev, bcm2835_smi_devid, 1); + if (err != 0) { + dev_err(inst->dev, "unable to register device"); + err = -ENOMEM; + goto failed_cdev_add; + } + + /* Create sysfs entries */ + + bcm2835_smi_class = class_create(DEVICE_NAME); + ptr_err = bcm2835_smi_class; + if (IS_ERR(ptr_err)) + goto failed_class_create; + + bcm2835_smi_dev = device_create(bcm2835_smi_class, NULL, + bcm2835_smi_devid, NULL, + "smi"); + ptr_err = bcm2835_smi_dev; + if (IS_ERR(ptr_err)) + goto failed_device_create; + + dev_info(inst->dev, "initialised"); + + return 0; + +failed_device_create: + class_destroy(bcm2835_smi_class); +failed_class_create: + cdev_del(&bcm2835_smi_cdev); + err = PTR_ERR(ptr_err); +failed_cdev_add: + unregister_chrdev_region(bcm2835_smi_devid, 1); + dev_err(dev, "could not load bcm2835_smi_dev"); + return err; +} + +/**************************************************************************** +* +* bcm2835_smi_remove - called when the driver is unloaded. +* +***************************************************************************/ + +static void bcm2835_smi_dev_remove(struct platform_device *pdev) +{ + device_destroy(bcm2835_smi_class, bcm2835_smi_devid); + class_destroy(bcm2835_smi_class); + cdev_del(&bcm2835_smi_cdev); + unregister_chrdev_region(bcm2835_smi_devid, 1); + + dev_info(inst->dev, "SMI character dev removed - OK"); +} + +/**************************************************************************** +* +* Register the driver with device tree +* +***************************************************************************/ + +static const struct of_device_id bcm2835_smi_dev_of_match[] = { + {.compatible = "brcm,bcm2835-smi-dev",}, + { /* sentinel */ }, +}; + +MODULE_DEVICE_TABLE(of, bcm2835_smi_dev_of_match); + +static struct platform_driver bcm2835_smi_dev_driver = { + .probe = bcm2835_smi_dev_probe, + .remove = bcm2835_smi_dev_remove, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = bcm2835_smi_dev_of_match, + }, +}; + +module_platform_driver(bcm2835_smi_dev_driver); + +MODULE_ALIAS("platform:smi-dev-bcm2835"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION( + "Character device driver for BCM2835's secondary memory interface"); +MODULE_AUTHOR("Luke Wren <luke@raspberrypi.org>"); diff --git a/drivers/char/broadcom/vc_mem.c b/drivers/char/broadcom/vc_mem.c new file mode 100644 index 00000000000000..4b7faf084e0c22 --- /dev/null +++ b/drivers/char/broadcom/vc_mem.c @@ -0,0 +1,635 @@ +/* + * Copyright 2010 - 2011 Broadcom Corporation. All rights reserved. + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2, available at + * http://www.broadcom.com/licenses/GPLv2.php (the "GPL"). + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a + * license other than the GPL, without Broadcom's express prior written + * consent. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/device.h> +#include <linux/cdev.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/debugfs.h> +#include <linux/uaccess.h> +#include <linux/dma-mapping.h> +#include <linux/broadcom/vc_mem.h> +#include <linux/compat.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/platform_data/dma-bcm2708.h> +#include <soc/bcm2835/raspberrypi-firmware.h> + +#define DRIVER_NAME "vc-mem" + +/* N.B. These use a different magic value for compatibility with bmc7208_fb */ +#define VC_MEM_IOC_DMACOPY _IOW('z', 0x22, struct vc_mem_dmacopy) +#define VC_MEM_IOC_DMACOPY32 _IOW('z', 0x22, struct vc_mem_dmacopy32) + +/* address with no aliases */ +#define INTALIAS_NORMAL(x) ((x) & ~0xc0000000) +/* cache coherent but non-allocating in L1 and L2 */ +#define INTALIAS_L1L2_NONALLOCATING(x) (((x) & ~0xc0000000) | 0x80000000) + +/* Device (/dev) related variables */ +static dev_t vc_mem_devnum; +static struct class *vc_mem_class; +static struct cdev vc_mem_cdev; +static int vc_mem_inited; + +#ifdef CONFIG_DEBUG_FS +static struct dentry *vc_mem_debugfs_entry; +#endif + +struct vc_mem_dmacopy { + void *dst; + __u32 src; + __u32 length; +}; + +#ifdef CONFIG_COMPAT +struct vc_mem_dmacopy32 { + compat_uptr_t dst; + __u32 src; + __u32 length; +}; +#endif + +/* + * Videocore memory addresses and size + * + * Drivers that wish to know the videocore memory addresses and sizes should + * use these variables instead of the MM_IO_BASE and MM_ADDR_IO defines in + * headers. This allows the other drivers to not be tied down to a a certain + * address/size at compile time. + * + * In the future, the goal is to have the videocore memory virtual address and + * size be calculated at boot time rather than at compile time. The decision of + * where the videocore memory resides and its size would be in the hands of the + * bootloader (and/or kernel). When that happens, the values of these variables + * would be calculated and assigned in the init function. + */ +/* In the 2835 VC in mapped above ARM, but ARM has full access to VC space */ +unsigned long mm_vc_mem_phys_addr; +EXPORT_SYMBOL(mm_vc_mem_phys_addr); +unsigned int mm_vc_mem_size; +EXPORT_SYMBOL(mm_vc_mem_size); +unsigned int mm_vc_mem_base; +EXPORT_SYMBOL(mm_vc_mem_base); + +static uint phys_addr; +static uint mem_size; +static uint mem_base; + +struct vc_mem_dma { + struct device *dev; + int dma_chan; + int dma_irq; + void __iomem *dma_chan_base; + wait_queue_head_t dma_waitq; + void *cb_base; /* DMA control blocks */ + dma_addr_t cb_handle; +}; + +struct { u32 base, length; } gpu_mem; +static struct mutex dma_mutex; +static struct vc_mem_dma vc_mem_dma; + +static int +vc_mem_open(struct inode *inode, struct file *file) +{ + (void)inode; + + pr_debug("%s: called file = 0x%p\n", __func__, file); + + return 0; +} + +static int +vc_mem_release(struct inode *inode, struct file *file) +{ + (void)inode; + + pr_debug("%s: called file = 0x%p\n", __func__, file); + + return 0; +} + +static void +vc_mem_get_size(void) +{ +} + +static void +vc_mem_get_base(void) +{ +} + +int +vc_mem_get_current_size(void) +{ + return mm_vc_mem_size; +} +EXPORT_SYMBOL_GPL(vc_mem_get_current_size); + +static int +vc_mem_dma_init(void) +{ + struct vc_mem_dma *vcdma = &vc_mem_dma; + struct platform_device *pdev; + struct device_node *fwnode; + struct rpi_firmware *fw; + struct device *dev; + u32 revision; + int rc; + + if (vcdma->dev) + return 0; + + fwnode = of_find_node_by_path("/system"); + rc = of_property_read_u32(fwnode, "linux,revision", &revision); + revision = (revision >> 12) & 0xf; + if (revision != 1 && revision != 2) { + /* Only BCM2709 and BCM2710 may have logs where the ARMs + * can't see them. + */ + return -ENXIO; + } + + fwnode = rpi_firmware_find_node(); + if (!fwnode) + return -ENXIO; + + pdev = of_find_device_by_node(fwnode); + dev = &pdev->dev; + + rc = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(32)); + if (rc) + return rc; + + fw = rpi_firmware_get(fwnode); + if (!fw) + return -ENXIO; + rc = rpi_firmware_property(fw, RPI_FIRMWARE_GET_VC_MEMORY, + &gpu_mem, sizeof(gpu_mem)); + if (rc) + return rc; + + gpu_mem.base = INTALIAS_NORMAL(gpu_mem.base); + + if (!gpu_mem.base || !gpu_mem.length) { + dev_err(dev, "%s: unable to determine gpu memory (%x,%x)\n", + __func__, gpu_mem.base, gpu_mem.length); + return -EFAULT; + } + + vcdma->cb_base = dma_alloc_wc(dev, SZ_4K, &vcdma->cb_handle, GFP_KERNEL); + if (!vcdma->cb_base) { + dev_err(dev, "failed to allocate DMA CBs\n"); + return -ENOMEM; + } + + rc = bcm_dma_chan_alloc(BCM_DMA_FEATURE_BULK, + &vcdma->dma_chan_base, + &vcdma->dma_irq); + if (rc < 0) { + dev_err(dev, "failed to allocate a DMA channel\n"); + goto free_cb; + } + + vcdma->dma_chan = rc; + + init_waitqueue_head(&vcdma->dma_waitq); + + vcdma->dev = dev; + + return 0; + +free_cb: + dma_free_wc(dev, SZ_4K, vcdma->cb_base, vcdma->cb_handle); + + return rc; +} + +static void +vc_mem_dma_uninit(void) +{ + struct vc_mem_dma *vcdma = &vc_mem_dma; + + if (vcdma->dev) { + bcm_dma_chan_free(vcdma->dma_chan); + dma_free_wc(vcdma->dev, SZ_4K, vcdma->cb_base, vcdma->cb_handle); + vcdma->dev = NULL; + } +} + +static int dma_memcpy(struct vc_mem_dma *vcdma, dma_addr_t dst, dma_addr_t src, + int size) +{ + struct bcm2708_dma_cb *cb = vcdma->cb_base; + int burst_size = (vcdma->dma_chan == 0) ? 8 : 2; + + cb->info = BCM2708_DMA_BURST(burst_size) | BCM2708_DMA_S_WIDTH | + BCM2708_DMA_S_INC | BCM2708_DMA_D_WIDTH | + BCM2708_DMA_D_INC; + cb->dst = dst; + cb->src = src; + cb->length = size; + cb->stride = 0; + cb->pad[0] = 0; + cb->pad[1] = 0; + cb->next = 0; + + bcm_dma_start(vcdma->dma_chan_base, vcdma->cb_handle); + bcm_dma_wait_idle(vcdma->dma_chan_base); + + return 0; +} + +static long vc_mem_copy(struct vc_mem_dmacopy *ioparam) +{ + struct vc_mem_dma *vcdma = &vc_mem_dma; + size_t size = PAGE_SIZE; + const u32 dma_xfer_chunk = 256; + u32 *buf = NULL; + dma_addr_t bus_addr; + long rc = 0; + size_t offset; + + /* restrict this to root user */ + if (!uid_eq(current_euid(), GLOBAL_ROOT_UID)) + return -EFAULT; + + if (mutex_lock_interruptible(&dma_mutex)) + return -EINTR; + + rc = vc_mem_dma_init(); + if (rc) + goto out; + + vcdma = &vc_mem_dma; + + if (INTALIAS_NORMAL(ioparam->src) < gpu_mem.base || + INTALIAS_NORMAL(ioparam->src) >= gpu_mem.base + gpu_mem.length) { + pr_err("%s: invalid memory access %x (%x-%x)", __func__, + INTALIAS_NORMAL(ioparam->src), gpu_mem.base, + gpu_mem.base + gpu_mem.length); + rc = -EFAULT; + goto out; + } + + buf = dma_alloc_coherent(vcdma->dev, PAGE_ALIGN(size), &bus_addr, + GFP_ATOMIC); + if (!buf) { + rc = -ENOMEM; + goto out; + } + + for (offset = 0; offset < ioparam->length; offset += size) { + size_t remaining = ioparam->length - offset; + size_t s = min(size, remaining); + u8 *p = (u8 *)((uintptr_t)ioparam->src + offset); + u8 *q = (u8 *)ioparam->dst + offset; + + rc = dma_memcpy(vcdma, bus_addr, + INTALIAS_L1L2_NONALLOCATING((u32)(uintptr_t)p), + (s + dma_xfer_chunk - 1) & ~(dma_xfer_chunk - 1)); + if (rc) { + dev_err(vcdma->dev, "dma_memcpy failed\n"); + break; + } + if (copy_to_user(q, buf, s) != 0) { + pr_err("%s: copy_to_user failed\n", __func__); + rc = -EFAULT; + break; + } + } + +out: + if (buf) + dma_free_coherent(vcdma->dev, PAGE_ALIGN(size), buf, + bus_addr); + + mutex_unlock(&dma_mutex); + + return rc; +} + +static long +vc_mem_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int rc = 0; + + (void) cmd; + (void) arg; + + pr_debug("%s: called file = 0x%p, cmd %08x\n", __func__, file, cmd); + + switch (cmd) { + case VC_MEM_IOC_MEM_PHYS_ADDR: + { + pr_debug("%s: VC_MEM_IOC_MEM_PHYS_ADDR=0x%p\n", + __func__, (void *)mm_vc_mem_phys_addr); + + if (copy_to_user((void *)arg, &mm_vc_mem_phys_addr, + sizeof(mm_vc_mem_phys_addr))) { + rc = -EFAULT; + } + break; + } + case VC_MEM_IOC_MEM_SIZE: + { + /* Get the videocore memory size first */ + vc_mem_get_size(); + + pr_debug("%s: VC_MEM_IOC_MEM_SIZE=%x\n", __func__, + mm_vc_mem_size); + + if (copy_to_user((void *)arg, &mm_vc_mem_size, + sizeof(mm_vc_mem_size))) { + rc = -EFAULT; + } + break; + } + case VC_MEM_IOC_MEM_BASE: + { + /* Get the videocore memory base */ + vc_mem_get_base(); + + pr_debug("%s: VC_MEM_IOC_MEM_BASE=%x\n", __func__, + mm_vc_mem_base); + + if (copy_to_user((void *)arg, &mm_vc_mem_base, + sizeof(mm_vc_mem_base))) { + rc = -EFAULT; + } + break; + } + case VC_MEM_IOC_MEM_LOAD: + { + /* Get the videocore memory base */ + vc_mem_get_base(); + + pr_debug("%s: VC_MEM_IOC_MEM_LOAD=%x\n", __func__, + mm_vc_mem_base); + + if (copy_to_user((void *)arg, &mm_vc_mem_base, + sizeof(mm_vc_mem_base))) { + rc = -EFAULT; + } + break; + } + case VC_MEM_IOC_DMACOPY: + { + struct vc_mem_dmacopy ioparam; + /* Get the parameter data. + */ + if (copy_from_user + (&ioparam, (void *)arg, sizeof(ioparam))) { + pr_err("%s: copy_from_user failed\n", __func__); + rc = -EFAULT; + break; + } + + rc = vc_mem_copy(&ioparam); + break; + } + default: + { + return -ENOTTY; + } + } + pr_debug("%s: file = 0x%p returning %d\n", __func__, file, rc); + + return rc; +} + +#ifdef CONFIG_COMPAT +static long +vc_mem_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int rc = 0; + + switch (cmd) { + case VC_MEM_IOC_MEM_PHYS_ADDR32: + pr_debug("%s: VC_MEM_IOC_MEM_PHYS_ADDR32=0x%p\n", + __func__, (void *)mm_vc_mem_phys_addr); + + /* This isn't correct, but will cover us for now as + * VideoCore is 32bit only. + */ + if (copy_to_user((void *)arg, &mm_vc_mem_phys_addr, + sizeof(compat_ulong_t))) + rc = -EFAULT; + + break; + + case VC_MEM_IOC_DMACOPY32: + { + struct vc_mem_dmacopy32 param32; + struct vc_mem_dmacopy param; + /* Get the parameter data. + */ + if (copy_from_user(¶m32, (void *)arg, sizeof(param32))) { + pr_err("%s: copy_from_user failed\n", __func__); + rc = -EFAULT; + break; + } + param.dst = compat_ptr(param32.dst); + param.src = param32.src; + param.length = param32.length; + rc = vc_mem_copy(¶m); + break; + } + + default: + rc = vc_mem_ioctl(file, cmd, arg); + break; + } + + return rc; +} +#endif + +static int +vc_mem_mmap(struct file *filp, struct vm_area_struct *vma) +{ + int rc = 0; + unsigned long length = vma->vm_end - vma->vm_start; + unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; + + pr_debug("%s: vm_start = 0x%08lx vm_end = 0x%08lx vm_pgoff = 0x%08lx\n", + __func__, (long)vma->vm_start, (long)vma->vm_end, + (long)vma->vm_pgoff); + + if (offset + length > mm_vc_mem_size) { + pr_err("%s: length %ld is too big\n", __func__, length); + return -EINVAL; + } + /* Do not cache the memory map */ + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + rc = remap_pfn_range(vma, vma->vm_start, + (mm_vc_mem_phys_addr >> PAGE_SHIFT) + + vma->vm_pgoff, length, vma->vm_page_prot); + if (rc) + pr_err("%s: remap_pfn_range failed (rc=%d)\n", __func__, rc); + + return rc; +} + +/* File Operations for the driver. */ +static const struct file_operations vc_mem_fops = { + .owner = THIS_MODULE, + .open = vc_mem_open, + .release = vc_mem_release, + .unlocked_ioctl = vc_mem_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = vc_mem_compat_ioctl, +#endif + .mmap = vc_mem_mmap, +}; + +#ifdef CONFIG_DEBUG_FS +static void vc_mem_debugfs_deinit(void) +{ + debugfs_remove_recursive(vc_mem_debugfs_entry); + vc_mem_debugfs_entry = NULL; +} + + +static int vc_mem_debugfs_init( + struct device *dev) +{ + vc_mem_debugfs_entry = debugfs_create_dir(DRIVER_NAME, NULL); + if (!vc_mem_debugfs_entry) { + dev_warn(dev, "could not create debugfs entry\n"); + return -EFAULT; + } + + debugfs_create_x32("vc_mem_phys_addr", + 0444, + vc_mem_debugfs_entry, + (u32 *)&mm_vc_mem_phys_addr); + debugfs_create_x32("vc_mem_size", + 0444, + vc_mem_debugfs_entry, + (u32 *)&mm_vc_mem_size); + debugfs_create_x32("vc_mem_base", + 0444, + vc_mem_debugfs_entry, + (u32 *)&mm_vc_mem_base); + + return 0; +} + +#endif /* CONFIG_DEBUG_FS */ + +/* Module load/unload functions */ + +static int __init +vc_mem_init(void) +{ + int rc = -EFAULT; + struct device *dev; + + pr_debug("%s: called\n", __func__); + + mm_vc_mem_phys_addr = phys_addr; + mm_vc_mem_size = mem_size; + mm_vc_mem_base = mem_base; + + vc_mem_get_size(); + + pr_info("vc-mem: phys_addr:0x%08lx mem_base=0x%08x mem_size:0x%08x(%u MiB)\n", + mm_vc_mem_phys_addr, mm_vc_mem_base, mm_vc_mem_size, + mm_vc_mem_size / (1024 * 1024)); + + rc = alloc_chrdev_region(&vc_mem_devnum, 0, 1, DRIVER_NAME); + if (rc < 0) { + pr_err("%s: alloc_chrdev_region failed (rc=%d)\n", + __func__, rc); + goto out_err; + } + + cdev_init(&vc_mem_cdev, &vc_mem_fops); + rc = cdev_add(&vc_mem_cdev, vc_mem_devnum, 1); + if (rc) { + pr_err("%s: cdev_add failed (rc=%d)\n", __func__, rc); + goto out_unregister; + } + + vc_mem_class = class_create(DRIVER_NAME); + if (IS_ERR(vc_mem_class)) { + rc = PTR_ERR(vc_mem_class); + pr_err("%s: class_create failed (rc=%d)\n", __func__, rc); + goto out_cdev_del; + } + + dev = device_create(vc_mem_class, NULL, vc_mem_devnum, NULL, + DRIVER_NAME); + if (IS_ERR(dev)) { + rc = PTR_ERR(dev); + pr_err("%s: device_create failed (rc=%d)\n", __func__, rc); + goto out_class_destroy; + } + +#ifdef CONFIG_DEBUG_FS + /* don't fail if the debug entries cannot be created */ + vc_mem_debugfs_init(dev); +#endif + + mutex_init(&dma_mutex); + vc_mem_inited = 1; + return 0; + +out_class_destroy: + class_destroy(vc_mem_class); + vc_mem_class = NULL; + +out_cdev_del: + cdev_del(&vc_mem_cdev); + +out_unregister: + unregister_chrdev_region(vc_mem_devnum, 1); + +out_err: + return -1; +} + +static void __exit +vc_mem_exit(void) +{ + pr_debug("%s: called\n", __func__); + + vc_mem_dma_uninit(); + if (vc_mem_inited) { +#ifdef CONFIG_DEBUG_FS + vc_mem_debugfs_deinit(); +#endif + device_destroy(vc_mem_class, vc_mem_devnum); + class_destroy(vc_mem_class); + cdev_del(&vc_mem_cdev); + unregister_chrdev_region(vc_mem_devnum, 1); + vc_mem_inited = 0; + } +} + +module_init(vc_mem_init); +module_exit(vc_mem_exit); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Broadcom Corporation"); + +module_param(phys_addr, uint, 0644); +module_param(mem_size, uint, 0644); +module_param(mem_base, uint, 0644); diff --git a/drivers/char/broadcom/vcio.c b/drivers/char/broadcom/vcio.c new file mode 100644 index 00000000000000..9db2408c781a74 --- /dev/null +++ b/drivers/char/broadcom/vcio.c @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2010 Broadcom + * Copyright (C) 2015 Noralf Trønnes + * Copyright (C) 2021 Raspberry Pi (Trading) 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 <linux/cdev.h> +#include <linux/device.h> +#include <linux/fs.h> +#include <linux/init.h> +#include <linux/ioctl.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/compat.h> +#include <linux/miscdevice.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <soc/bcm2835/raspberrypi-firmware.h> + +#define MODULE_NAME "vcio" +#define VCIO_IOC_MAGIC 100 +#define IOCTL_MBOX_PROPERTY _IOWR(VCIO_IOC_MAGIC, 0, char *) +#ifdef CONFIG_COMPAT +#define IOCTL_MBOX_PROPERTY32 _IOWR(VCIO_IOC_MAGIC, 0, compat_uptr_t) +#endif + +struct vcio_data { + struct rpi_firmware *fw; + struct miscdevice misc_dev; +}; + +static int vcio_user_property_list(struct vcio_data *vcio, void *user) +{ + u32 *buf, size; + int ret; + + /* The first 32-bit is the size of the buffer */ + if (copy_from_user(&size, user, sizeof(size))) + return -EFAULT; + + buf = kmalloc(size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + if (copy_from_user(buf, user, size)) { + kfree(buf); + return -EFAULT; + } + + /* Strip off protocol encapsulation */ + ret = rpi_firmware_property_list(vcio->fw, &buf[2], size - 12); + if (ret) { + kfree(buf); + return ret; + } + + buf[1] = RPI_FIRMWARE_STATUS_SUCCESS; + if (copy_to_user(user, buf, size)) + ret = -EFAULT; + + kfree(buf); + + return ret; +} + +static int vcio_device_open(struct inode *inode, struct file *file) +{ + try_module_get(THIS_MODULE); + + return 0; +} + +static int vcio_device_release(struct inode *inode, struct file *file) +{ + module_put(THIS_MODULE); + + return 0; +} + +static long vcio_device_ioctl(struct file *file, unsigned int ioctl_num, + unsigned long ioctl_param) +{ + struct vcio_data *vcio = container_of(file->private_data, + struct vcio_data, misc_dev); + + switch (ioctl_num) { + case IOCTL_MBOX_PROPERTY: + return vcio_user_property_list(vcio, (void *)ioctl_param); + default: + pr_err("unknown ioctl: %x\n", ioctl_num); + return -EINVAL; + } +} + +#ifdef CONFIG_COMPAT +static long vcio_device_compat_ioctl(struct file *file, unsigned int ioctl_num, + unsigned long ioctl_param) +{ + struct vcio_data *vcio = container_of(file->private_data, + struct vcio_data, misc_dev); + + switch (ioctl_num) { + case IOCTL_MBOX_PROPERTY32: + return vcio_user_property_list(vcio, compat_ptr(ioctl_param)); + default: + pr_err("unknown ioctl: %x\n", ioctl_num); + return -EINVAL; + } +} +#endif + +const struct file_operations vcio_fops = { + .unlocked_ioctl = vcio_device_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = vcio_device_compat_ioctl, +#endif + .open = vcio_device_open, + .release = vcio_device_release, +}; + +static int vcio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct device_node *fw_node; + struct rpi_firmware *fw; + struct vcio_data *vcio; + + fw_node = of_get_parent(np); + if (!fw_node) { + dev_err(dev, "Missing firmware node\n"); + return -ENOENT; + } + + fw = rpi_firmware_get(fw_node); + of_node_put(fw_node); + if (!fw) + return -EPROBE_DEFER; + + vcio = devm_kzalloc(dev, sizeof(struct vcio_data), GFP_KERNEL); + if (!vcio) + return -ENOMEM; + + vcio->fw = fw; + vcio->misc_dev.fops = &vcio_fops; + vcio->misc_dev.minor = MISC_DYNAMIC_MINOR; + vcio->misc_dev.name = "vcio"; + vcio->misc_dev.parent = dev; + + return misc_register(&vcio->misc_dev); +} + +static void vcio_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + + misc_deregister(dev_get_drvdata(dev)); +} + +static const struct of_device_id vcio_ids[] = { + { .compatible = "raspberrypi,vcio" }, + { } +}; +MODULE_DEVICE_TABLE(of, vcio_ids); + +static struct platform_driver vcio_driver = { + .driver = { + .name = MODULE_NAME, + .of_match_table = of_match_ptr(vcio_ids), + }, + .probe = vcio_probe, + .remove = vcio_remove, +}; + +module_platform_driver(vcio_driver); + +MODULE_AUTHOR("Gray Girling"); +MODULE_AUTHOR("Noralf Trønnes"); +MODULE_DESCRIPTION("Mailbox userspace access"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:rpi-vcio"); diff --git a/drivers/char/hw_random/Kconfig b/drivers/char/hw_random/Kconfig index b51d9e243f3512..c5d4719e96f310 100644 --- a/drivers/char/hw_random/Kconfig +++ b/drivers/char/hw_random/Kconfig @@ -105,7 +105,7 @@ config HW_RANDOM_IPROC_RNG200 default HW_RANDOM help This driver provides kernel-side support for the RNG200 - hardware found on the Broadcom iProc and STB SoCs. + hardware found on the Broadcom iProc, BCM2711 and STB SoCs. To compile this driver as a module, choose M here: the module will be called iproc-rng200 diff --git a/drivers/char/hw_random/bcm2835-rng.c b/drivers/char/hw_random/bcm2835-rng.c index aa2b135e3ee230..05a4569ce28f24 100644 --- a/drivers/char/hw_random/bcm2835-rng.c +++ b/drivers/char/hw_random/bcm2835-rng.c @@ -13,6 +13,7 @@ #include <linux/printk.h> #include <linux/clk.h> #include <linux/reset.h> +#include <linux/delay.h> #define RNG_CTRL 0x0 #define RNG_STATUS 0x4 @@ -27,6 +28,9 @@ #define RNG_INT_OFF 0x1 +#define RNG_FIFO_WORDS 4 +#define RNG_US_PER_WORD 34 /* Tuned for throughput */ + struct bcm2835_rng_priv { struct hwrng rng; void __iomem *base; @@ -63,19 +67,23 @@ static inline void rng_writel(struct bcm2835_rng_priv *priv, u32 val, static int bcm2835_rng_read(struct hwrng *rng, void *buf, size_t max, bool wait) { + u32 retries = 1000000/(RNG_FIFO_WORDS * RNG_US_PER_WORD); struct bcm2835_rng_priv *priv = to_rng_priv(rng); u32 max_words = max / sizeof(u32); u32 num_words, count; - while ((rng_readl(priv, RNG_STATUS) >> 24) == 0) { - if (!wait) + num_words = rng_readl(priv, RNG_STATUS) >> 24; + + while (!num_words) { + if (!wait || !retries) return 0; - hwrng_yield(rng); + retries--; + usleep_range((u32)RNG_US_PER_WORD, + (u32)RNG_US_PER_WORD * RNG_FIFO_WORDS); + num_words = rng_readl(priv, RNG_STATUS) >> 24; } - num_words = rng_readl(priv, RNG_STATUS) >> 24; - if (num_words > max_words) - num_words = max_words; + num_words = min(num_words, max_words); for (count = 0; count < num_words; count++) ((u32 *)buf)[count] = rng_readl(priv, RNG_DATA); @@ -107,8 +115,10 @@ static int bcm2835_rng_init(struct hwrng *rng) } /* set warm-up count & enable */ - rng_writel(priv, RNG_WARMUP_COUNT, RNG_STATUS); - rng_writel(priv, RNG_RBGEN, RNG_CTRL); + if (!(rng_readl(priv, RNG_CTRL) & RNG_RBGEN)) { + rng_writel(priv, RNG_WARMUP_COUNT, RNG_STATUS); + rng_writel(priv, RNG_RBGEN, RNG_CTRL); + } return ret; } diff --git a/drivers/char/hw_random/iproc-rng200.c b/drivers/char/hw_random/iproc-rng200.c index 440fe28bddc02e..33bc28f429f614 100644 --- a/drivers/char/hw_random/iproc-rng200.c +++ b/drivers/char/hw_random/iproc-rng200.c @@ -13,6 +13,7 @@ #include <linux/kernel.h> #include <linux/module.h> #include <linux/mod_devicetable.h> +#include <linux/of.h> #include <linux/platform_device.h> #include <linux/delay.h> @@ -20,6 +21,7 @@ #define RNG_CTRL_OFFSET 0x00 #define RNG_CTRL_RNG_RBGEN_MASK 0x00001FFF #define RNG_CTRL_RNG_RBGEN_ENABLE 0x00000001 +#define RNG_CTRL_RNG_DIV_CTRL_SHIFT 13 #define RNG_SOFT_RESET_OFFSET 0x04 #define RNG_SOFT_RESET 0x00000001 @@ -27,16 +29,23 @@ #define RBG_SOFT_RESET_OFFSET 0x08 #define RBG_SOFT_RESET 0x00000001 +#define RNG_TOTAL_BIT_COUNT_OFFSET 0x0C + +#define RNG_TOTAL_BIT_COUNT_THRESHOLD_OFFSET 0x10 + #define RNG_INT_STATUS_OFFSET 0x18 #define RNG_INT_STATUS_MASTER_FAIL_LOCKOUT_IRQ_MASK 0x80000000 #define RNG_INT_STATUS_STARTUP_TRANSITIONS_MET_IRQ_MASK 0x00020000 #define RNG_INT_STATUS_NIST_FAIL_IRQ_MASK 0x00000020 #define RNG_INT_STATUS_TOTAL_BITS_COUNT_IRQ_MASK 0x00000001 +#define RNG_INT_ENABLE_OFFSET 0x1C + #define RNG_FIFO_DATA_OFFSET 0x20 #define RNG_FIFO_COUNT_OFFSET 0x24 #define RNG_FIFO_COUNT_RNG_FIFO_COUNT_MASK 0x000000FF +#define RNG_FIFO_COUNT_RNG_FIFO_THRESHOLD_SHIFT 8 struct iproc_rng200_dev { struct hwrng rng; @@ -157,6 +166,64 @@ static int iproc_rng200_init(struct hwrng *rng) return 0; } +static int bcm2711_rng200_read(struct hwrng *rng, void *buf, size_t max, + bool wait) +{ + struct iproc_rng200_dev *priv = to_rng_priv(rng); + u32 max_words = max / sizeof(u32); + u32 num_words, count, val; + + /* ensure warm up period has elapsed */ + while (1) { + val = ioread32(priv->base + RNG_TOTAL_BIT_COUNT_OFFSET); + if (val > 16) + break; + cpu_relax(); + } + + /* ensure fifo is not empty */ + while (1) { + num_words = ioread32(priv->base + RNG_FIFO_COUNT_OFFSET) & + RNG_FIFO_COUNT_RNG_FIFO_COUNT_MASK; + if (num_words) + break; + if (!wait) + return 0; + cpu_relax(); + } + + if (num_words > max_words) + num_words = max_words; + + for (count = 0; count < num_words; count++) { + ((u32 *)buf)[count] = ioread32(priv->base + + RNG_FIFO_DATA_OFFSET); + } + + return num_words * sizeof(u32); +} + +static int bcm2711_rng200_init(struct hwrng *rng) +{ + struct iproc_rng200_dev *priv = to_rng_priv(rng); + uint32_t val; + + if (ioread32(priv->base + RNG_CTRL_OFFSET) & RNG_CTRL_RNG_RBGEN_MASK) + return 0; + + /* initial numbers generated are "less random" so will be discarded */ + val = 0x40000; + iowrite32(val, priv->base + RNG_TOTAL_BIT_COUNT_THRESHOLD_OFFSET); + /* min fifo count to generate full interrupt */ + val = 2 << RNG_FIFO_COUNT_RNG_FIFO_THRESHOLD_SHIFT; + iowrite32(val, priv->base + RNG_FIFO_COUNT_OFFSET); + /* enable the rng - 1Mhz sample rate */ + val = (0x3 << RNG_CTRL_RNG_DIV_CTRL_SHIFT) | RNG_CTRL_RNG_RBGEN_MASK; + iowrite32(val, priv->base + RNG_CTRL_OFFSET); + + return 0; +} + static void iproc_rng200_cleanup(struct hwrng *rng) { struct iproc_rng200_dev *priv = to_rng_priv(rng); @@ -183,11 +250,17 @@ static int iproc_rng200_probe(struct platform_device *pdev) dev_set_drvdata(dev, priv); - priv->rng.name = "iproc-rng200"; - priv->rng.read = iproc_rng200_read; - priv->rng.init = iproc_rng200_init; + priv->rng.name = pdev->name; priv->rng.cleanup = iproc_rng200_cleanup; + if (of_device_is_compatible(dev->of_node, "brcm,bcm2711-rng200")) { + priv->rng.init = bcm2711_rng200_init; + priv->rng.read = bcm2711_rng200_read; + } else { + priv->rng.init = iproc_rng200_init; + priv->rng.read = iproc_rng200_read; + } + /* Register driver */ ret = devm_hwrng_register(dev, &priv->rng); if (ret) { diff --git a/drivers/char/random.c b/drivers/char/random.c index 23ee76bbb4aa72..800a05bd2c697f 100644 --- a/drivers/char/random.c +++ b/drivers/char/random.c @@ -867,6 +867,14 @@ void __init random_init_early(const char *command_line) unsigned long entropy[BLAKE2S_BLOCK_SIZE / sizeof(long)]; size_t i, longs, arch_bits; + /* + * If we were initialized by the bootloader before jump labels are + * initialized, then we should enable the static branch here, where + * it's guaranteed that jump labels have been initialized. + */ + if (!static_branch_likely(&crng_is_ready) && crng_init >= CRNG_READY) + crng_set_ready(NULL); + #if defined(LATENT_ENTROPY_PLUGIN) static const u8 compiletime_seed[BLAKE2S_BLOCK_SIZE] __initconst __latent_entropy; _mix_pool_bytes(compiletime_seed, sizeof(compiletime_seed)); diff --git a/drivers/char/raspberrypi-gpiomem.c b/drivers/char/raspberrypi-gpiomem.c new file mode 100644 index 00000000000000..0f9b15bc14a80f --- /dev/null +++ b/drivers/char/raspberrypi-gpiomem.c @@ -0,0 +1,275 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/** + * raspberrypi-gpiomem.c + * + * Provides MMIO access to discontiguous section of Device memory as a linear + * user mapping. Successor to bcm2835-gpiomem.c. + * + * Copyright (c) 2023, Raspberry Pi Ltd. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/cdev.h> +#include <linux/pagemap.h> +#include <linux/io.h> + +#define DRIVER_NAME "rpi-gpiomem" +#define DEVICE_MINOR 0 + +/* + * Sensible max for a hypothetical "gpio" controller that splits pads, + * IO controls, GPIO in/out/enable, and function selection into different + * ranges. Most use only one or two. + */ +#define MAX_RANGES 4 + +struct io_windows { + unsigned long phys_base; + unsigned long len; +}; + +struct rpi_gpiomem_priv { + dev_t devid; + struct class *class; + struct cdev rpi_gpiomem_cdev; + struct device *dev; + const char *name; + unsigned int nr_wins; + struct io_windows iowins[4]; +}; + +static int rpi_gpiomem_open(struct inode *inode, struct file *file) +{ + int dev = iminor(inode); + int ret = 0; + struct rpi_gpiomem_priv *priv; + + if (dev != DEVICE_MINOR) + ret = -ENXIO; + + priv = container_of(inode->i_cdev, struct rpi_gpiomem_priv, + rpi_gpiomem_cdev); + if (!priv) + return -EINVAL; + file->private_data = priv; + return ret; +} + +static int rpi_gpiomem_release(struct inode *inode, struct file *file) +{ + int dev = iminor(inode); + int ret = 0; + + if (dev != DEVICE_MINOR) + ret = -ENXIO; + + return ret; +} + +static const struct vm_operations_struct rpi_gpiomem_vm_ops = { +#ifdef CONFIG_HAVE_IOREMAP_PROT + .access = generic_access_phys +#endif +}; + +static int rpi_gpiomem_mmap(struct file *file, struct vm_area_struct *vma) +{ + int i; + struct rpi_gpiomem_priv *priv; + unsigned long base; + unsigned long len = 0; + unsigned long offset; + + priv = file->private_data; + /* + * Userspace must provide a virtual address space at least + * the size of the concatenated ranges. + */ + for (i = 0; i < priv->nr_wins; i++) + len += priv->iowins[i].len; + if (len > vma->vm_end - vma->vm_start + 1) + return -EINVAL; + + vma->vm_ops = &rpi_gpiomem_vm_ops; + offset = vma->vm_start; + for (i = 0; i < priv->nr_wins; i++) { + base = priv->iowins[i].phys_base >> PAGE_SHIFT; + len = priv->iowins[i].len; + vma->vm_page_prot = phys_mem_access_prot(file, base, len, + vma->vm_page_prot); + if (remap_pfn_range(vma, offset, + base, len, + vma->vm_page_prot)) + break; + offset += len; + } + + if (i < priv->nr_wins) + return -EAGAIN; + + return 0; +} + +static const struct file_operations rpi_gpiomem_fops = { + .owner = THIS_MODULE, + .open = rpi_gpiomem_open, + .release = rpi_gpiomem_release, + .mmap = rpi_gpiomem_mmap, +}; + +static const struct of_device_id rpi_gpiomem_of_match[]; + +static int rpi_gpiomem_probe(struct platform_device *pdev) +{ + int err, i; + const struct of_device_id *id; + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct resource *ioresource; + struct rpi_gpiomem_priv *priv; + + /* Allocate buffers and instance data */ + + priv = kzalloc(sizeof(struct rpi_gpiomem_priv), GFP_KERNEL); + + if (!priv) { + err = -ENOMEM; + goto failed_inst_alloc; + } + platform_set_drvdata(pdev, priv); + + priv->dev = dev; + id = of_match_device(rpi_gpiomem_of_match, dev); + if (!id) + return -EINVAL; + + /* + * Device node naming - for legacy (bcm2835) DT bindings, the driver + * created the node based on a hardcoded name - for new bindings, + * take the node name from DT. + */ + if (id == &rpi_gpiomem_of_match[0]) { + priv->name = "gpiomem"; + } else { + err = of_property_read_string(node, "chardev-name", &priv->name); + if (err) + return -EINVAL; + } + + /* + * Go find the register ranges associated with this instance + */ + for (i = 0; i < MAX_RANGES; i++) { + ioresource = platform_get_resource(pdev, IORESOURCE_MEM, i); + if (!ioresource && i == 0) { + dev_err(priv->dev, "failed to get IO resource - no ranges available\n"); + err = -ENOENT; + goto failed_get_resource; + } + if (!ioresource) + break; + + priv->iowins[i].phys_base = ioresource->start; + priv->iowins[i].len = (ioresource->end + 1) - ioresource->start; + dev_info(&pdev->dev, "window base 0x%08lx size 0x%08lx\n", + priv->iowins[i].phys_base, priv->iowins[i].len); + priv->nr_wins++; + } + + /* Create character device entries */ + + err = alloc_chrdev_region(&priv->devid, + DEVICE_MINOR, 1, priv->name); + if (err != 0) { + dev_err(priv->dev, "unable to allocate device number"); + goto failed_alloc_chrdev; + } + cdev_init(&priv->rpi_gpiomem_cdev, &rpi_gpiomem_fops); + priv->rpi_gpiomem_cdev.owner = THIS_MODULE; + err = cdev_add(&priv->rpi_gpiomem_cdev, priv->devid, 1); + if (err != 0) { + dev_err(priv->dev, "unable to register device"); + goto failed_cdev_add; + } + + /* Create sysfs entries */ + + priv->class = class_create(priv->name); + if (IS_ERR(priv->class)) { + err = PTR_ERR(priv->class); + goto failed_class_create; + } + + dev = device_create(priv->class, NULL, priv->devid, NULL, priv->name); + if (IS_ERR(dev)) { + err = PTR_ERR(dev); + goto failed_device_create; + } + + dev_info(priv->dev, "initialised %u regions as /dev/%s\n", + priv->nr_wins, priv->name); + + return 0; + +failed_device_create: + class_destroy(priv->class); +failed_class_create: + cdev_del(&priv->rpi_gpiomem_cdev); +failed_cdev_add: + unregister_chrdev_region(priv->devid, 1); +failed_alloc_chrdev: +failed_get_resource: + kfree(priv); +failed_inst_alloc: + dev_err(&pdev->dev, "could not load rpi_gpiomem"); + return err; +} + +static void rpi_gpiomem_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rpi_gpiomem_priv *priv = platform_get_drvdata(pdev); + + device_destroy(priv->class, priv->devid); + class_destroy(priv->class); + cdev_del(&priv->rpi_gpiomem_cdev); + unregister_chrdev_region(priv->devid, 1); + kfree(priv); + + dev_info(dev, "%s driver removed - OK", priv->name); +} + +static const struct of_device_id rpi_gpiomem_of_match[] = { + { + .compatible = "brcm,bcm2835-gpiomem", + }, + { + .compatible = "raspberrypi,gpiomem", + }, + { /* sentinel */ }, +}; + +MODULE_DEVICE_TABLE(of, rpi_gpiomem_of_match); + +static struct platform_driver rpi_gpiomem_driver = { + .probe = rpi_gpiomem_probe, + .remove = rpi_gpiomem_remove, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = rpi_gpiomem_of_match, + }, +}; + +module_platform_driver(rpi_gpiomem_driver); + +MODULE_ALIAS("platform:rpi-gpiomem"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_DESCRIPTION("Driver for accessing GPIOs from userspace"); +MODULE_AUTHOR("Jonathan Bell <jonathan@raspberrypi.com>"); diff --git a/drivers/char/tpm/tpm_tis_spi_main.c b/drivers/char/tpm/tpm_tis_spi_main.c index 61b42c83ced817..2a7dee1e180764 100644 --- a/drivers/char/tpm/tpm_tis_spi_main.c +++ b/drivers/char/tpm/tpm_tis_spi_main.c @@ -350,7 +350,11 @@ static struct spi_driver tpm_tis_spi_driver = { .pm = &tpm_tis_pm, .of_match_table = of_match_ptr(of_tis_spi_match), .acpi_match_table = ACPI_PTR(acpi_tis_spi_match), +#ifdef CONFIG_IMA + .probe_type = PROBE_FORCE_SYNCHRONOUS, +#else .probe_type = PROBE_PREFER_ASYNCHRONOUS, +#endif }, .probe = tpm_tis_spi_driver_probe, .remove = tpm_tis_spi_remove, diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index 0fe07a594b4e1b..8a59e6e1cf58aa 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -88,6 +88,19 @@ config COMMON_CLK_RK808 These multi-function devices have two fixed-rate oscillators, clocked at 32KHz each. Clkout1 is always on, Clkout2 can off by control register. +config COMMON_CLK_RP1 + tristate "Raspberry Pi RP1-based clock support" + depends on PCI || COMPILE_TEST + depends on COMMON_CLK + help + Enable common clock framework support for Raspberry Pi RP1 + +config COMMON_CLK_RP1_SDIO + tristate "Clock driver for the RP1 SDIO interfaces" + depends on MFD_RP1 + help + SDIO clock driver for the RP1 support chip + config COMMON_CLK_HI655X tristate "Clock driver for Hi655x" if EXPERT depends on (MFD_HI655X_PMIC || COMPILE_TEST) @@ -98,6 +111,12 @@ config COMMON_CLK_HI655X multi-function device has one fixed-rate oscillator, clocked at 32KHz. +config COMMON_CLK_HIFIBERRY_DACPLUSHD + tristate + +config COMMON_CLK_HIFIBERRY_DACPRO + tristate + config COMMON_CLK_SCMI tristate "Clock driver controlled via SCMI interface" depends on ARM_SCMI_PROTOCOL || COMPILE_TEST diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index fb8878a5d7d93d..45f8d39a062945 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -56,6 +56,8 @@ obj-$(CONFIG_COMMON_CLK_LAN966X) += clk-lan966x.o obj-$(CONFIG_COMMON_CLK_LOCHNAGAR) += clk-lochnagar.o obj-$(CONFIG_MACH_LOONGSON32) += clk-loongson1.o obj-$(CONFIG_COMMON_CLK_LOONGSON2) += clk-loongson2.o +obj-$(CONFIG_COMMON_CLK_HIFIBERRY_DACPRO) += clk-hifiberry-dacpro.o +obj-$(CONFIG_COMMON_CLK_HIFIBERRY_DACPLUSHD) += clk-hifiberry-dachd.o obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o obj-$(CONFIG_COMMON_CLK_MAX9485) += clk-max9485.o obj-$(CONFIG_ARCH_MILBEAUT_M10V) += clk-milbeaut.o @@ -68,6 +70,8 @@ obj-$(CONFIG_CLK_LS1028A_PLLDIG) += clk-plldig.o obj-$(CONFIG_COMMON_CLK_PWM) += clk-pwm.o obj-$(CONFIG_CLK_QORIQ) += clk-qoriq.o obj-$(CONFIG_COMMON_CLK_RK808) += clk-rk808.o +obj-$(CONFIG_COMMON_CLK_RP1) += clk-rp1.o +obj-$(CONFIG_COMMON_CLK_RP1_SDIO) += clk-rp1-sdio.o obj-$(CONFIG_COMMON_CLK_HI655X) += clk-hi655x.o obj-$(CONFIG_COMMON_CLK_S2MPS11) += clk-s2mps11.o obj-$(CONFIG_COMMON_CLK_SCMI) += clk-scmi.o diff --git a/drivers/clk/bcm/clk-bcm2835.c b/drivers/clk/bcm/clk-bcm2835.c index fb04734afc8062..6b6f77ba0a796b 100644 --- a/drivers/clk/bcm/clk-bcm2835.c +++ b/drivers/clk/bcm/clk-bcm2835.c @@ -36,6 +36,7 @@ #include <linux/platform_device.h> #include <linux/slab.h> #include <dt-bindings/clock/bcm2835.h> +#include <soc/bcm2835/raspberrypi-firmware.h> #define CM_PASSWORD 0x5a000000 @@ -106,6 +107,7 @@ #define CM_UARTDIV 0x0f4 #define CM_VECCTL 0x0f8 #define CM_VECDIV 0x0fc +#define CM_DSI0HSCK 0x120 #define CM_PULSECTL 0x190 #define CM_PULSEDIV 0x194 #define CM_SDCCTL 0x1a8 @@ -296,6 +298,8 @@ #define SOC_BCM2711 BIT(1) #define SOC_ALL (SOC_BCM2835 | SOC_BCM2711) +#define VCMSG_ID_CORE_CLOCK 4 + /* * Names of clocks used within the driver that need to be replaced * with an external parent's name. This array is in the order that @@ -314,6 +318,7 @@ static const char *const cprman_parent_names[] = { struct bcm2835_cprman { struct device *dev; void __iomem *regs; + struct rpi_firmware *fw; spinlock_t regs_lock; /* spinlock for all clocks */ unsigned int soc; @@ -643,15 +648,17 @@ static int bcm2835_pll_on(struct clk_hw *hw) spin_unlock(&cprman->regs_lock); /* Wait for the PLL to lock. */ - timeout = ktime_add_ns(ktime_get(), LOCK_TIMEOUT_NS); - while (!(cprman_read(cprman, CM_LOCK) & data->lock_mask)) { - if (ktime_after(ktime_get(), timeout)) { - dev_err(cprman->dev, "%s: couldn't lock PLL\n", - clk_hw_get_name(hw)); - return -ETIMEDOUT; + if (strcmp(data->name, "pllh")) { + timeout = ktime_add_ns(ktime_get(), LOCK_TIMEOUT_NS); + while (!(cprman_read(cprman, CM_LOCK) & data->lock_mask)) { + if (ktime_after(ktime_get(), timeout)) { + dev_err(cprman->dev, "%s: couldn't lock PLL\n", + clk_hw_get_name(hw)); + return -ETIMEDOUT; + } + + cpu_relax(); } - - cpu_relax(); } cprman_write(cprman, data->a2w_ctrl_reg, @@ -1039,6 +1046,30 @@ static unsigned long bcm2835_clock_get_rate(struct clk_hw *hw, return rate; } +static unsigned long bcm2835_clock_get_rate_vpu(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct bcm2835_clock *clock = bcm2835_clock_from_hw(hw); + struct bcm2835_cprman *cprman = clock->cprman; + + if (cprman->fw) { + struct { + u32 id; + u32 val; + } packet; + + packet.id = VCMSG_ID_CORE_CLOCK; + packet.val = 0; + + if (!rpi_firmware_property(cprman->fw, + RPI_FIRMWARE_GET_MAX_CLOCK_RATE, + &packet, sizeof(packet))) + return packet.val; + } + + return bcm2835_clock_get_rate(hw, parent_rate); +} + static void bcm2835_clock_wait_busy(struct bcm2835_clock *clock) { struct bcm2835_cprman *cprman = clock->cprman; @@ -1097,8 +1128,10 @@ static int bcm2835_clock_on(struct clk_hw *hw) return 0; } -static int bcm2835_clock_set_rate(struct clk_hw *hw, - unsigned long rate, unsigned long parent_rate) +static int bcm2835_clock_set_rate_and_parent(struct clk_hw *hw, + unsigned long rate, + unsigned long parent_rate, + u8 parent) { struct bcm2835_clock *clock = bcm2835_clock_from_hw(hw); struct bcm2835_cprman *cprman = clock->cprman; @@ -1108,15 +1141,24 @@ static int bcm2835_clock_set_rate(struct clk_hw *hw, spin_lock(&cprman->regs_lock); - /* - * Setting up frac support - * - * In principle it is recommended to stop/start the clock first, - * but as we set CLK_SET_RATE_GATE during registration of the - * clock this requirement should be take care of by the - * clk-framework. + ctl = cprman_read(cprman, data->ctl_reg); + + /* If the clock is running, we have to pause clock generation while + * updating the control and div regs. This is glitchless (no clock + * signals generated faster than the rate) but each reg access is two + * OSC cycles so the clock will slow down for a moment. */ - ctl = cprman_read(cprman, data->ctl_reg) & ~CM_FRAC; + if (ctl & CM_ENABLE) { + cprman_write(cprman, data->ctl_reg, ctl & ~CM_ENABLE); + bcm2835_clock_wait_busy(clock); + } + + if (parent != 0xff) { + ctl &= ~(CM_SRC_MASK << CM_SRC_SHIFT); + ctl |= parent << CM_SRC_SHIFT; + } + + ctl &= ~CM_FRAC; ctl |= (div & CM_DIV_FRAC_MASK) ? CM_FRAC : 0; cprman_write(cprman, data->ctl_reg, ctl); @@ -1127,6 +1169,12 @@ static int bcm2835_clock_set_rate(struct clk_hw *hw, return 0; } +static int bcm2835_clock_set_rate(struct clk_hw *hw, + unsigned long rate, unsigned long parent_rate) +{ + return bcm2835_clock_set_rate_and_parent(hw, rate, parent_rate, 0xff); +} + static bool bcm2835_clk_is_pllc(struct clk_hw *hw) { @@ -1310,6 +1358,7 @@ static const struct clk_ops bcm2835_clock_clk_ops = { .unprepare = bcm2835_clock_off, .recalc_rate = bcm2835_clock_get_rate, .set_rate = bcm2835_clock_set_rate, + .set_rate_and_parent = bcm2835_clock_set_rate_and_parent, .determine_rate = bcm2835_clock_determine_rate, .set_parent = bcm2835_clock_set_parent, .get_parent = bcm2835_clock_get_parent, @@ -1327,7 +1376,7 @@ static int bcm2835_vpu_clock_is_on(struct clk_hw *hw) */ static const struct clk_ops bcm2835_vpu_clock_clk_ops = { .is_prepared = bcm2835_vpu_clock_is_on, - .recalc_rate = bcm2835_clock_get_rate, + .recalc_rate = bcm2835_clock_get_rate_vpu, .set_rate = bcm2835_clock_set_rate, .determine_rate = bcm2835_clock_determine_rate, .set_parent = bcm2835_clock_set_parent, @@ -1335,6 +1384,8 @@ static const struct clk_ops bcm2835_vpu_clock_clk_ops = { .debug_init = bcm2835_clock_debug_init, }; +static bool bcm2835_clk_is_claimed(const char *name); + static struct clk_hw *bcm2835_register_pll(struct bcm2835_cprman *cprman, const void *data) { @@ -1352,6 +1403,9 @@ static struct clk_hw *bcm2835_register_pll(struct bcm2835_cprman *cprman, init.ops = &bcm2835_pll_clk_ops; init.flags = pll_data->flags | CLK_IGNORE_UNUSED; + if (!bcm2835_clk_is_claimed(pll_data->name)) + init.flags |= CLK_IS_CRITICAL; + pll = kzalloc(sizeof(*pll), GFP_KERNEL); if (!pll) return NULL; @@ -1407,6 +1461,13 @@ bcm2835_register_pll_divider(struct bcm2835_cprman *cprman, divider->div.hw.init = &init; divider->div.table = NULL; + if (!(cprman_read(cprman, divider_data->cm_reg) & divider_data->hold_mask)) { + if (!bcm2835_clk_is_claimed(divider_data->source_pll)) + init.flags |= CLK_IS_CRITICAL; + if (!bcm2835_clk_is_claimed(divider_data->name)) + divider->div.flags |= CLK_IS_CRITICAL; + } + divider->cprman = cprman; divider->data = divider_data; @@ -1460,6 +1521,15 @@ static struct clk_hw *bcm2835_register_clock(struct bcm2835_cprman *cprman, init.name = clock_data->name; init.flags = clock_data->flags | CLK_IGNORE_UNUSED; + /* + * Some GPIO clocks for ethernet/wifi PLLs are marked as + * critical (since some platforms use them), but if the + * firmware didn't have them turned on then they clearly + * aren't actually critical. + */ + if ((cprman_read(cprman, clock_data->ctl_reg) & CM_ENABLE) == 0) + init.flags &= ~CLK_IS_CRITICAL; + /* * Pass the CLK_SET_RATE_PARENT flag if we are allowed to propagate * rate changes on at least of the parents. @@ -1471,7 +1541,6 @@ static struct clk_hw *bcm2835_register_clock(struct bcm2835_cprman *cprman, init.ops = &bcm2835_vpu_clock_clk_ops; } else { init.ops = &bcm2835_clock_clk_ops; - init.flags |= CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE; /* If the clock wasn't actually enabled at boot, it's not * critical. @@ -1696,16 +1765,12 @@ static const struct bcm2835_clk_desc clk_desc_array[] = { .hold_mask = CM_PLLA_HOLDCORE, .fixed_divider = 1, .flags = CLK_SET_RATE_PARENT), - [BCM2835_PLLA_PER] = REGISTER_PLL_DIV( - SOC_ALL, - .name = "plla_per", - .source_pll = "plla", - .cm_reg = CM_PLLA, - .a2w_reg = A2W_PLLA_PER, - .load_mask = CM_PLLA_LOADPER, - .hold_mask = CM_PLLA_HOLDPER, - .fixed_divider = 1, - .flags = CLK_SET_RATE_PARENT), + + /* + * PLLA_PER is used for gpu clocks. Controlled by firmware, see + * clk-raspberrypi.c. + */ + [BCM2835_PLLA_DSI0] = REGISTER_PLL_DIV( SOC_ALL, .name = "plla_dsi0", @@ -2006,14 +2071,12 @@ static const struct bcm2835_clk_desc clk_desc_array[] = { .int_bits = 6, .frac_bits = 0, .tcnt_mux = 3), - [BCM2835_CLOCK_V3D] = REGISTER_VPU_CLK( - SOC_ALL, - .name = "v3d", - .ctl_reg = CM_V3DCTL, - .div_reg = CM_V3DDIV, - .int_bits = 4, - .frac_bits = 8, - .tcnt_mux = 4), + + /* + * CLOCK_V3D is used for v3d clock. Controlled by firmware, see + * clk-raspberrypi.c. + */ + /* * VPU clock. This doesn't have an enable bit, since it drives * the bus for everything else, and is special so it doesn't need @@ -2176,21 +2239,6 @@ static const struct bcm2835_clk_desc clk_desc_array[] = { .tcnt_mux = 28, .round_up = true), - /* TV encoder clock. Only operating frequency is 108Mhz. */ - [BCM2835_CLOCK_VEC] = REGISTER_PER_CLK( - SOC_ALL, - .name = "vec", - .ctl_reg = CM_VECCTL, - .div_reg = CM_VECDIV, - .int_bits = 4, - .frac_bits = 0, - /* - * Allow rate change propagation only on PLLH_AUX which is - * assigned index 7 in the parent array. - */ - .set_rate_parent = BIT(7), - .tcnt_mux = 29), - /* dsi clocks */ [BCM2835_CLOCK_DSI0E] = REGISTER_PER_CLK( SOC_ALL, @@ -2240,6 +2288,8 @@ static const struct bcm2835_clk_desc clk_desc_array[] = { .ctl_reg = CM_PERIICTL), }; +static bool bcm2835_clk_claimed[ARRAY_SIZE(clk_desc_array)]; + /* * Permanently take a reference on the parent of the SDRAM clock. * @@ -2259,6 +2309,21 @@ static int bcm2835_mark_sdc_parent_critical(struct clk *sdc) return clk_prepare_enable(parent); } +static bool bcm2835_clk_is_claimed(const char *name) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(clk_desc_array); i++) { + if (clk_desc_array[i].data) { + const char *clk_name = *(const char **)(clk_desc_array[i].data); + if (!strcmp(name, clk_name)) + return bcm2835_clk_claimed[i]; + } + } + + return false; +} + static int bcm2835_clk_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -2267,7 +2332,9 @@ static int bcm2835_clk_probe(struct platform_device *pdev) const struct bcm2835_clk_desc *desc; const size_t asize = ARRAY_SIZE(clk_desc_array); const struct cprman_plat_data *pdata; + struct device_node *fw_node; size_t i; + u32 clk_id; int ret; pdata = of_device_get_match_data(&pdev->dev); @@ -2286,6 +2353,24 @@ static int bcm2835_clk_probe(struct platform_device *pdev) if (IS_ERR(cprman->regs)) return PTR_ERR(cprman->regs); + /* Mux DSI0 clock to PLLD */ + cprman_write(cprman, CM_DSI0HSCK, 1); + + fw_node = of_parse_phandle(dev->of_node, "firmware", 0); + if (fw_node) { + struct rpi_firmware *fw = rpi_firmware_get(fw_node); + if (!fw) + return -EPROBE_DEFER; + cprman->fw = fw; + } + + memset(bcm2835_clk_claimed, 0, sizeof(bcm2835_clk_claimed)); + for (i = 0; + !of_property_read_u32_index(pdev->dev.of_node, "claim-clocks", + i, &clk_id); + i++) + bcm2835_clk_claimed[clk_id]= true; + memcpy(cprman->real_parent_names, cprman_parent_names, sizeof(cprman_parent_names)); of_clk_parent_fill(dev->of_node, cprman->real_parent_names, @@ -2319,8 +2404,15 @@ static int bcm2835_clk_probe(struct platform_device *pdev) if (ret) return ret; - return of_clk_add_hw_provider(dev->of_node, of_clk_hw_onecell_get, + ret = of_clk_add_hw_provider(dev->of_node, of_clk_hw_onecell_get, &cprman->onecell); + if (ret) + return ret; + + /* note that we have registered all the clocks */ + dev_dbg(dev, "registered %zd clocks\n", asize); + + return 0; } static const struct cprman_plat_data cprman_bcm2835_plat_data = { @@ -2346,7 +2438,15 @@ static struct platform_driver bcm2835_clk_driver = { .probe = bcm2835_clk_probe, }; -builtin_platform_driver(bcm2835_clk_driver); +static int __init __bcm2835_clk_driver_init(void) +{ + return platform_driver_register(&bcm2835_clk_driver); +} +#ifdef CONFIG_IMA +subsys_initcall(__bcm2835_clk_driver_init); +#else +postcore_initcall(__bcm2835_clk_driver_init); +#endif MODULE_AUTHOR("Eric Anholt <eric@anholt.net>"); MODULE_DESCRIPTION("BCM2835 clock driver"); diff --git a/drivers/clk/bcm/clk-raspberrypi.c b/drivers/clk/bcm/clk-raspberrypi.c index a18a8768feb405..aff9f3195573fe 100644 --- a/drivers/clk/bcm/clk-raspberrypi.c +++ b/drivers/clk/bcm/clk-raspberrypi.c @@ -34,6 +34,7 @@ static char *rpi_firmware_clk_names[] = { [RPI_FIRMWARE_M2MC_CLK_ID] = "m2mc", [RPI_FIRMWARE_PIXEL_BVB_CLK_ID] = "pixel-bvb", [RPI_FIRMWARE_VEC_CLK_ID] = "vec", + [RPI_FIRMWARE_DISP_CLK_ID] = "disp", }; #define RPI_FIRMWARE_STATE_ENABLE_BIT BIT(0) @@ -56,6 +57,12 @@ struct raspberrypi_clk_data { struct raspberrypi_clk *rpi; }; +static inline +const struct raspberrypi_clk_data *clk_hw_to_data(const struct clk_hw *hw) +{ + return container_of(hw, struct raspberrypi_clk_data, hw); +} + struct raspberrypi_clk_variant { bool export; char *clkdev; @@ -111,18 +118,31 @@ raspberrypi_clk_variants[RPI_FIRMWARE_NUM_CLK_ID] = { }, [RPI_FIRMWARE_V3D_CLK_ID] = { .export = true, + .minimize = true, }, [RPI_FIRMWARE_PIXEL_CLK_ID] = { .export = true, + .minimize = true, }, [RPI_FIRMWARE_HEVC_CLK_ID] = { .export = true, + .minimize = true, + }, + [RPI_FIRMWARE_ISP_CLK_ID] = { + .export = true, + .minimize = true, }, [RPI_FIRMWARE_PIXEL_BVB_CLK_ID] = { .export = true, + .minimize = true, }, [RPI_FIRMWARE_VEC_CLK_ID] = { .export = true, + .minimize = true, + }, + [RPI_FIRMWARE_DISP_CLK_ID] = { + .export = true, + .minimize = true, }, }; @@ -153,7 +173,7 @@ static int raspberrypi_clock_property(struct rpi_firmware *firmware, struct raspberrypi_firmware_prop msg = { .id = cpu_to_le32(data->id), .val = cpu_to_le32(*val), - .disable_turbo = cpu_to_le32(1), + .disable_turbo = cpu_to_le32(0), }; int ret; @@ -168,8 +188,7 @@ static int raspberrypi_clock_property(struct rpi_firmware *firmware, static int raspberrypi_fw_is_prepared(struct clk_hw *hw) { - struct raspberrypi_clk_data *data = - container_of(hw, struct raspberrypi_clk_data, hw); + const struct raspberrypi_clk_data *data = clk_hw_to_data(hw); struct raspberrypi_clk *rpi = data->rpi; u32 val = 0; int ret; @@ -186,8 +205,7 @@ static int raspberrypi_fw_is_prepared(struct clk_hw *hw) static unsigned long raspberrypi_fw_get_rate(struct clk_hw *hw, unsigned long parent_rate) { - struct raspberrypi_clk_data *data = - container_of(hw, struct raspberrypi_clk_data, hw); + const struct raspberrypi_clk_data *data = clk_hw_to_data(hw); struct raspberrypi_clk *rpi = data->rpi; u32 val = 0; int ret; @@ -203,8 +221,7 @@ static unsigned long raspberrypi_fw_get_rate(struct clk_hw *hw, static int raspberrypi_fw_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) { - struct raspberrypi_clk_data *data = - container_of(hw, struct raspberrypi_clk_data, hw); + const struct raspberrypi_clk_data *data = clk_hw_to_data(hw); struct raspberrypi_clk *rpi = data->rpi; u32 _rate = rate; int ret; @@ -221,8 +238,7 @@ static int raspberrypi_fw_set_rate(struct clk_hw *hw, unsigned long rate, static int raspberrypi_fw_dumb_determine_rate(struct clk_hw *hw, struct clk_rate_request *req) { - struct raspberrypi_clk_data *data = - container_of(hw, struct raspberrypi_clk_data, hw); + const struct raspberrypi_clk_data *data = clk_hw_to_data(hw); struct raspberrypi_clk_variant *variant = data->variant; /* diff --git a/drivers/clk/clk-hifiberry-dachd.c b/drivers/clk/clk-hifiberry-dachd.c new file mode 100644 index 00000000000000..5280b5100559d2 --- /dev/null +++ b/drivers/clk/clk-hifiberry-dachd.c @@ -0,0 +1,331 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Clock Driver for HiFiBerry DAC+ HD + * + * Author: Joerg Schambacher, i2Audio GmbH for HiFiBerry + * Copyright 2020 + * + * 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 <linux/clk-provider.h> +#include <linux/clk.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/i2c.h> +#include <linux/regmap.h> + +#define NO_PLL_RESET 0 +#define PLL_RESET 1 +#define HIFIBERRY_PLL_MAX_REGISTER 256 +#define DEFAULT_RATE 44100 + +static struct reg_default hifiberry_pll_reg_defaults[] = { + {0x02, 0x53}, {0x03, 0x00}, {0x07, 0x20}, {0x0F, 0x00}, + {0x10, 0x0D}, {0x11, 0x1D}, {0x12, 0x0D}, {0x13, 0x8C}, + {0x14, 0x8C}, {0x15, 0x8C}, {0x16, 0x8C}, {0x17, 0x8C}, + {0x18, 0x2A}, {0x1C, 0x00}, {0x1D, 0x0F}, {0x1F, 0x00}, + {0x2A, 0x00}, {0x2C, 0x00}, {0x2F, 0x00}, {0x30, 0x00}, + {0x31, 0x00}, {0x32, 0x00}, {0x34, 0x00}, {0x37, 0x00}, + {0x38, 0x00}, {0x39, 0x00}, {0x3A, 0x00}, {0x3B, 0x01}, + {0x3E, 0x00}, {0x3F, 0x00}, {0x40, 0x00}, {0x41, 0x00}, + {0x5A, 0x00}, {0x5B, 0x00}, {0x95, 0x00}, {0x96, 0x00}, + {0x97, 0x00}, {0x98, 0x00}, {0x99, 0x00}, {0x9A, 0x00}, + {0x9B, 0x00}, {0xA2, 0x00}, {0xA3, 0x00}, {0xA4, 0x00}, + {0xB7, 0x92}, + {0x1A, 0x3D}, {0x1B, 0x09}, {0x1E, 0xF3}, {0x20, 0x13}, + {0x21, 0x75}, {0x2B, 0x04}, {0x2D, 0x11}, {0x2E, 0xE0}, + {0x3D, 0x7A}, + {0x35, 0x9D}, {0x36, 0x00}, {0x3C, 0x42}, + { 177, 0xAC}, +}; +static struct reg_default common_pll_regs[HIFIBERRY_PLL_MAX_REGISTER]; +static int num_common_pll_regs; +static struct reg_default dedicated_192k_pll_regs[HIFIBERRY_PLL_MAX_REGISTER]; +static int num_dedicated_192k_pll_regs; +static struct reg_default dedicated_96k_pll_regs[HIFIBERRY_PLL_MAX_REGISTER]; +static int num_dedicated_96k_pll_regs; +static struct reg_default dedicated_48k_pll_regs[HIFIBERRY_PLL_MAX_REGISTER]; +static int num_dedicated_48k_pll_regs; +static struct reg_default dedicated_176k4_pll_regs[HIFIBERRY_PLL_MAX_REGISTER]; +static int num_dedicated_176k4_pll_regs; +static struct reg_default dedicated_88k2_pll_regs[HIFIBERRY_PLL_MAX_REGISTER]; +static int num_dedicated_88k2_pll_regs; +static struct reg_default dedicated_44k1_pll_regs[HIFIBERRY_PLL_MAX_REGISTER]; +static int num_dedicated_44k1_pll_regs; + +/** + * struct clk_hifiberry_drvdata - Common struct to the HiFiBerry DAC HD Clk + * @hw: clk_hw for the common clk framework + */ +struct clk_hifiberry_drvdata { + struct regmap *regmap; + struct clk *clk; + struct clk_hw hw; + unsigned long rate; +}; + +#define to_hifiberry_clk(_hw) \ + container_of(_hw, struct clk_hifiberry_drvdata, hw) + +static int clk_hifiberry_dachd_write_pll_regs(struct regmap *regmap, + struct reg_default *regs, + int num, int do_pll_reset) +{ + int i; + int ret = 0; + char pll_soft_reset[] = { 177, 0xAC, }; + + for (i = 0; i < num; i++) { + ret |= regmap_write(regmap, regs[i].reg, regs[i].def); + if (ret) + return ret; + } + if (do_pll_reset) { + ret |= regmap_write(regmap, pll_soft_reset[0], + pll_soft_reset[1]); + mdelay(10); + } + return ret; +} + +static unsigned long clk_hifiberry_dachd_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + return to_hifiberry_clk(hw)->rate; +} + +static long clk_hifiberry_dachd_round_rate(struct clk_hw *hw, + unsigned long rate, unsigned long *parent_rate) +{ + return rate; +} + +static int clk_hifiberry_dachd_set_rate(struct clk_hw *hw, + unsigned long rate, unsigned long parent_rate) +{ + int ret; + struct clk_hifiberry_drvdata *drvdata = to_hifiberry_clk(hw); + + switch (rate) { + case 44100: + ret = clk_hifiberry_dachd_write_pll_regs(drvdata->regmap, + dedicated_44k1_pll_regs, num_dedicated_44k1_pll_regs, + PLL_RESET); + break; + case 88200: + ret = clk_hifiberry_dachd_write_pll_regs(drvdata->regmap, + dedicated_88k2_pll_regs, num_dedicated_88k2_pll_regs, + PLL_RESET); + break; + case 176400: + ret = clk_hifiberry_dachd_write_pll_regs(drvdata->regmap, + dedicated_176k4_pll_regs, num_dedicated_176k4_pll_regs, + PLL_RESET); + break; + case 48000: + ret = clk_hifiberry_dachd_write_pll_regs(drvdata->regmap, + dedicated_48k_pll_regs, num_dedicated_48k_pll_regs, + PLL_RESET); + break; + case 96000: + ret = clk_hifiberry_dachd_write_pll_regs(drvdata->regmap, + dedicated_96k_pll_regs, num_dedicated_96k_pll_regs, + PLL_RESET); + break; + case 192000: + ret = clk_hifiberry_dachd_write_pll_regs(drvdata->regmap, + dedicated_192k_pll_regs, num_dedicated_192k_pll_regs, + PLL_RESET); + break; + default: + ret = -EINVAL; + break; + } + to_hifiberry_clk(hw)->rate = rate; + + return ret; +} + +const struct clk_ops clk_hifiberry_dachd_rate_ops = { + .recalc_rate = clk_hifiberry_dachd_recalc_rate, + .round_rate = clk_hifiberry_dachd_round_rate, + .set_rate = clk_hifiberry_dachd_set_rate, +}; + +static int clk_hifiberry_get_prop_values(struct device *dev, + char *prop_name, + struct reg_default *regs) +{ + int ret; + int i; + u8 tmp[2 * HIFIBERRY_PLL_MAX_REGISTER]; + + ret = of_property_read_variable_u8_array(dev->of_node, prop_name, + tmp, 0, 2 * HIFIBERRY_PLL_MAX_REGISTER); + if (ret < 0) + return ret; + if (ret & 1) { + dev_err(dev, + "%s <%s> -> #%i odd number of bytes for reg/val pairs!", + __func__, + prop_name, + ret); + return -EINVAL; + } + ret /= 2; + for (i = 0; i < ret; i++) { + regs[i].reg = (u32)tmp[2 * i]; + regs[i].def = (u32)tmp[2 * i + 1]; + } + return ret; +} + + +static int clk_hifiberry_dachd_dt_parse(struct device *dev) +{ + num_common_pll_regs = clk_hifiberry_get_prop_values(dev, + "common_pll_regs", common_pll_regs); + num_dedicated_44k1_pll_regs = clk_hifiberry_get_prop_values(dev, + "44k1_pll_regs", dedicated_44k1_pll_regs); + num_dedicated_88k2_pll_regs = clk_hifiberry_get_prop_values(dev, + "88k2_pll_regs", dedicated_88k2_pll_regs); + num_dedicated_176k4_pll_regs = clk_hifiberry_get_prop_values(dev, + "176k4_pll_regs", dedicated_176k4_pll_regs); + num_dedicated_48k_pll_regs = clk_hifiberry_get_prop_values(dev, + "48k_pll_regs", dedicated_48k_pll_regs); + num_dedicated_96k_pll_regs = clk_hifiberry_get_prop_values(dev, + "96k_pll_regs", dedicated_96k_pll_regs); + num_dedicated_192k_pll_regs = clk_hifiberry_get_prop_values(dev, + "192k_pll_regs", dedicated_192k_pll_regs); + return 0; +} + + +static int clk_hifiberry_dachd_remove(struct device *dev) +{ + of_clk_del_provider(dev->of_node); + return 0; +} + +const struct regmap_config hifiberry_pll_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = HIFIBERRY_PLL_MAX_REGISTER, + .reg_defaults = hifiberry_pll_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(hifiberry_pll_reg_defaults), + .cache_type = REGCACHE_RBTREE, +}; +EXPORT_SYMBOL_GPL(hifiberry_pll_regmap); + + +static int clk_hifiberry_dachd_i2c_probe(struct i2c_client *i2c) +{ + struct clk_hifiberry_drvdata *hdclk; + int ret = 0; + struct clk_init_data init; + struct device *dev = &i2c->dev; + struct device_node *dev_node = dev->of_node; + struct regmap_config config = hifiberry_pll_regmap; + + hdclk = devm_kzalloc(&i2c->dev, + sizeof(struct clk_hifiberry_drvdata), GFP_KERNEL); + if (!hdclk) + return -ENOMEM; + + i2c_set_clientdata(i2c, hdclk); + + hdclk->regmap = devm_regmap_init_i2c(i2c, &config); + + if (IS_ERR(hdclk->regmap)) + return PTR_ERR(hdclk->regmap); + + /* start PLL to allow detection of DAC */ + ret = clk_hifiberry_dachd_write_pll_regs(hdclk->regmap, + hifiberry_pll_reg_defaults, + ARRAY_SIZE(hifiberry_pll_reg_defaults), + PLL_RESET); + if (ret) + return ret; + + clk_hifiberry_dachd_dt_parse(dev); + + /* restart PLL with configs from DTB */ + ret = clk_hifiberry_dachd_write_pll_regs(hdclk->regmap, common_pll_regs, + num_common_pll_regs, PLL_RESET); + if (ret) + return ret; + + init.name = "clk-hifiberry-dachd"; + init.ops = &clk_hifiberry_dachd_rate_ops; + init.flags = 0; + init.parent_names = NULL; + init.num_parents = 0; + + hdclk->hw.init = &init; + + hdclk->clk = devm_clk_register(dev, &hdclk->hw); + if (IS_ERR(hdclk->clk)) { + dev_err(dev, "unable to register %s\n", init.name); + return PTR_ERR(hdclk->clk); + } + + ret = of_clk_add_provider(dev_node, of_clk_src_simple_get, hdclk->clk); + if (ret != 0) { + dev_err(dev, "Cannot of_clk_add_provider"); + return ret; + } + + ret = clk_set_rate(hdclk->hw.clk, DEFAULT_RATE); + if (ret != 0) { + dev_err(dev, "Cannot set rate : %d\n", ret); + return -EINVAL; + } + + return ret; +} + +static void clk_hifiberry_dachd_i2c_remove(struct i2c_client *i2c) +{ + clk_hifiberry_dachd_remove(&i2c->dev); +} + +static const struct i2c_device_id clk_hifiberry_dachd_i2c_id[] = { + { "dachd-clk", }, + { } +}; +MODULE_DEVICE_TABLE(i2c, clk_hifiberry_dachd_i2c_id); + +static const struct of_device_id clk_hifiberry_dachd_of_match[] = { + { .compatible = "hifiberry,dachd-clk", }, + { } +}; +MODULE_DEVICE_TABLE(of, clk_hifiberry_dachd_of_match); + +static struct i2c_driver clk_hifiberry_dachd_i2c_driver = { + .probe = clk_hifiberry_dachd_i2c_probe, + .remove = clk_hifiberry_dachd_i2c_remove, + .id_table = clk_hifiberry_dachd_i2c_id, + .driver = { + .name = "dachd-clk", + .of_match_table = of_match_ptr(clk_hifiberry_dachd_of_match), + }, +}; + +module_i2c_driver(clk_hifiberry_dachd_i2c_driver); + + +MODULE_DESCRIPTION("HiFiBerry DAC+ HD clock driver"); +MODULE_AUTHOR("Joerg Schambacher <joerg@i2audio.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:clk-hifiberry-dachd"); diff --git a/drivers/clk/clk-hifiberry-dacpro.c b/drivers/clk/clk-hifiberry-dacpro.c new file mode 100644 index 00000000000000..0cbc0349f9f6e8 --- /dev/null +++ b/drivers/clk/clk-hifiberry-dacpro.c @@ -0,0 +1,180 @@ +/* + * Clock Driver for HiFiBerry DAC Pro + * + * Author: Stuart MacLean + * Copyright 2015 + * + * 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 <linux/clk-provider.h> +#include <linux/clkdev.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/slab.h> +#include <linux/platform_device.h> + +struct ext_clk_rates { + /* Clock rate of CLK44EN attached to GPIO6 pin */ + unsigned long clk_44en; + /* Clock rate of CLK48EN attached to GPIO3 pin */ + unsigned long clk_48en; +}; + +/** + * struct hifiberry_dacpro_clk - Common struct to the HiFiBerry DAC Pro + * @hw: clk_hw for the common clk framework + * @mode: 0 => CLK44EN, 1 => CLK48EN + */ +struct clk_hifiberry_hw { + struct clk_hw hw; + uint8_t mode; + struct ext_clk_rates clk_rates; +}; + +#define to_hifiberry_clk(_hw) container_of(_hw, struct clk_hifiberry_hw, hw) + +static const struct ext_clk_rates hifiberry_dacpro_clks = { + .clk_44en = 22579200UL, + .clk_48en = 24576000UL, +}; + +static const struct ext_clk_rates allo_dac_clks = { + .clk_44en = 45158400UL, + .clk_48en = 49152000UL, +}; + +static const struct of_device_id clk_hifiberry_dacpro_dt_ids[] = { + { .compatible = "hifiberry,dacpro-clk", &hifiberry_dacpro_clks }, + { .compatible = "allo,dac-clk", &allo_dac_clks }, + { } +}; +MODULE_DEVICE_TABLE(of, clk_hifiberry_dacpro_dt_ids); + +static unsigned long clk_hifiberry_dacpro_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_hifiberry_hw *clk = to_hifiberry_clk(hw); + return (clk->mode == 0) ? clk->clk_rates.clk_44en : + clk->clk_rates.clk_48en; +} + +static long clk_hifiberry_dacpro_round_rate(struct clk_hw *hw, + unsigned long rate, unsigned long *parent_rate) +{ + struct clk_hifiberry_hw *clk = to_hifiberry_clk(hw); + long actual_rate; + + if (rate <= clk->clk_rates.clk_44en) { + actual_rate = (long)clk->clk_rates.clk_44en; + } else if (rate >= clk->clk_rates.clk_48en) { + actual_rate = (long)clk->clk_rates.clk_48en; + } else { + long diff44Rate = (long)(rate - clk->clk_rates.clk_44en); + long diff48Rate = (long)(clk->clk_rates.clk_48en - rate); + + if (diff44Rate < diff48Rate) + actual_rate = (long)clk->clk_rates.clk_44en; + else + actual_rate = (long)clk->clk_rates.clk_48en; + } + return actual_rate; +} + + +static int clk_hifiberry_dacpro_set_rate(struct clk_hw *hw, + unsigned long rate, unsigned long parent_rate) +{ + struct clk_hifiberry_hw *clk = to_hifiberry_clk(hw); + unsigned long actual_rate; + + actual_rate = (unsigned long)clk_hifiberry_dacpro_round_rate(hw, rate, + &parent_rate); + clk->mode = (actual_rate == clk->clk_rates.clk_44en) ? 0 : 1; + return 0; +} + + +const struct clk_ops clk_hifiberry_dacpro_rate_ops = { + .recalc_rate = clk_hifiberry_dacpro_recalc_rate, + .round_rate = clk_hifiberry_dacpro_round_rate, + .set_rate = clk_hifiberry_dacpro_set_rate, +}; + +static int clk_hifiberry_dacpro_probe(struct platform_device *pdev) +{ + const struct of_device_id *of_id; + struct clk_hifiberry_hw *proclk; + struct clk *clk; + struct device *dev; + struct clk_init_data init; + int ret; + + dev = &pdev->dev; + of_id = of_match_node(clk_hifiberry_dacpro_dt_ids, dev->of_node); + if (!of_id) + return -EINVAL; + + proclk = kzalloc(sizeof(struct clk_hifiberry_hw), GFP_KERNEL); + if (!proclk) + return -ENOMEM; + + init.name = "clk-hifiberry-dacpro"; + init.ops = &clk_hifiberry_dacpro_rate_ops; + init.flags = 0; + init.parent_names = NULL; + init.num_parents = 0; + + proclk->mode = 0; + proclk->hw.init = &init; + memcpy(&proclk->clk_rates, of_id->data, sizeof(proclk->clk_rates)); + + clk = devm_clk_register(dev, &proclk->hw); + if (!IS_ERR(clk)) { + ret = of_clk_add_provider(dev->of_node, of_clk_src_simple_get, + clk); + } else { + dev_err(dev, "Fail to register clock driver\n"); + kfree(proclk); + ret = PTR_ERR(clk); + } + return ret; +} + +static void clk_hifiberry_dacpro_remove(struct platform_device *pdev) +{ + of_clk_del_provider(pdev->dev.of_node); +} + +static struct platform_driver clk_hifiberry_dacpro_driver = { + .probe = clk_hifiberry_dacpro_probe, + .remove = clk_hifiberry_dacpro_remove, + .driver = { + .name = "clk-hifiberry-dacpro", + .of_match_table = clk_hifiberry_dacpro_dt_ids, + }, +}; + +static int __init clk_hifiberry_dacpro_init(void) +{ + return platform_driver_register(&clk_hifiberry_dacpro_driver); +} +core_initcall(clk_hifiberry_dacpro_init); + +static void __exit clk_hifiberry_dacpro_exit(void) +{ + platform_driver_unregister(&clk_hifiberry_dacpro_driver); +} +module_exit(clk_hifiberry_dacpro_exit); + +MODULE_DESCRIPTION("HiFiBerry DAC Pro clock driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:clk-hifiberry-dacpro"); diff --git a/drivers/clk/clk-rp1-sdio.c b/drivers/clk/clk-rp1-sdio.c new file mode 100644 index 00000000000000..c459bab0575cb9 --- /dev/null +++ b/drivers/clk/clk-rp1-sdio.c @@ -0,0 +1,599 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * SDIO clock driver for RP1 + * + * Copyright (C) 2023 Raspberry Pi Ltd. + */ + +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +// Register : MODE +#define MODE 0x00000000 +#define MODE_BITS 0x70030000 +#define MODE_RESET 0x00000000 +// Field : MODE_STEPS_PER_CYCLE +#define MODE_STEPS_PER_CYCLE_RESET 0x0 +#define MODE_STEPS_PER_CYCLE_BITS 0x70000000 +#define MODE_STEPS_PER_CYCLE_MSB 30 +#define MODE_STEPS_PER_CYCLE_LSB 28 +#define MODE_STEPS_PER_CYCLE_VALUE_STEPS_20 0x0 +#define MODE_STEPS_PER_CYCLE_VALUE_STEPS_10 0x1 +#define MODE_STEPS_PER_CYCLE_VALUE_STEPS_16 0x2 +#define MODE_STEPS_PER_CYCLE_VALUE_STEPS_8 0x3 +#define MODE_STEPS_PER_CYCLE_VALUE_STEPS_12 0x4 +#define MODE_STEPS_PER_CYCLE_VALUE_STEPS_6 0x5 +#define MODE_STEPS_PER_CYCLE_VALUE_STEPS_5 0x6 +#define MODE_STEPS_PER_CYCLE_VALUE_STEPS_4 0x7 +// Field : MODE_SRC_SEL +#define MODE_SRC_SEL_RESET 0x0 +#define MODE_SRC_SEL_BITS 0x00030000 +#define MODE_SRC_SEL_MSB 17 +#define MODE_SRC_SEL_LSB 16 +#define MODE_SRC_SEL_VALUE_STOP 0x0 +#define MODE_SRC_SEL_VALUE_CLK_ALT_SRC 0x1 +#define MODE_SRC_SEL_VALUE_PLL_SYS_VCO 0x2 +#define MODE_SRC_SEL_VALUE_PLL_SYS_VCO_AGAIN 0x3 +// Register : FROMIP +#define FROMIP 0x00000004 +#define FROMIP_BITS 0x0f9713ff +#define FROMIP_RESET 0x00000000 +// Field : FROMIP_TUNING_CCLK_SEL +#define FROMIP_TUNING_CCLK_SEL_RESET 0x0 +#define FROMIP_TUNING_CCLK_SEL_BITS 0x0f000000 +#define FROMIP_TUNING_CCLK_SEL_MSB 27 +#define FROMIP_TUNING_CCLK_SEL_LSB 24 +// Field : FROMIP_TUNING_CCLK_UPDATE +#define FROMIP_TUNING_CCLK_UPDATE_RESET 0x0 +#define FROMIP_TUNING_CCLK_UPDATE_BITS 0x00800000 +#define FROMIP_TUNING_CCLK_UPDATE_MSB 23 +#define FROMIP_TUNING_CCLK_UPDATE_LSB 23 +// Field : FROMIP_SAMPLE_CCLK_SEL +#define FROMIP_SAMPLE_CCLK_SEL_RESET 0x0 +#define FROMIP_SAMPLE_CCLK_SEL_BITS 0x00100000 +#define FROMIP_SAMPLE_CCLK_SEL_MSB 20 +#define FROMIP_SAMPLE_CCLK_SEL_LSB 20 +// Field : FROMIP_CLK2CARD_ON +#define FROMIP_CLK2CARD_ON_RESET 0x0 +#define FROMIP_CLK2CARD_ON_BITS 0x00040000 +#define FROMIP_CLK2CARD_ON_MSB 18 +#define FROMIP_CLK2CARD_ON_LSB 18 +// Field : FROMIP_CARD_CLK_STABLE +#define FROMIP_CARD_CLK_STABLE_RESET 0x0 +#define FROMIP_CARD_CLK_STABLE_BITS 0x00020000 +#define FROMIP_CARD_CLK_STABLE_MSB 17 +#define FROMIP_CARD_CLK_STABLE_LSB 17 +// Field : FROMIP_CARD_CLK_EN +#define FROMIP_CARD_CLK_EN_RESET 0x0 +#define FROMIP_CARD_CLK_EN_BITS 0x00010000 +#define FROMIP_CARD_CLK_EN_MSB 16 +#define FROMIP_CARD_CLK_EN_LSB 16 +// Field : FROMIP_CLK_GEN_SEL +#define FROMIP_CLK_GEN_SEL_RESET 0x0 +#define FROMIP_CLK_GEN_SEL_BITS 0x00001000 +#define FROMIP_CLK_GEN_SEL_MSB 12 +#define FROMIP_CLK_GEN_SEL_LSB 12 +// Field : FROMIP_FREQ_SEL +#define FROMIP_FREQ_SEL_RESET 0x000 +#define FROMIP_FREQ_SEL_BITS 0x000003ff +#define FROMIP_FREQ_SEL_MSB 9 +#define FROMIP_FREQ_SEL_LSB 0 +// Register : LOCAL +#define LOCAL 0x00000008 +#define LOCAL_BITS 0x1f9713ff +#define LOCAL_RESET 0x00000000 +// Field : LOCAL_TUNING_CCLK_SEL +#define LOCAL_TUNING_CCLK_SEL_RESET 0x00 +#define LOCAL_TUNING_CCLK_SEL_BITS 0x1f000000 +#define LOCAL_TUNING_CCLK_SEL_MSB 28 +#define LOCAL_TUNING_CCLK_SEL_LSB 24 +// Field : LOCAL_TUNING_CCLK_UPDATE +#define LOCAL_TUNING_CCLK_UPDATE_RESET 0x0 +#define LOCAL_TUNING_CCLK_UPDATE_BITS 0x00800000 +#define LOCAL_TUNING_CCLK_UPDATE_MSB 23 +#define LOCAL_TUNING_CCLK_UPDATE_LSB 23 +// Field : LOCAL_SAMPLE_CCLK_SEL +#define LOCAL_SAMPLE_CCLK_SEL_RESET 0x0 +#define LOCAL_SAMPLE_CCLK_SEL_BITS 0x00100000 +#define LOCAL_SAMPLE_CCLK_SEL_MSB 20 +#define LOCAL_SAMPLE_CCLK_SEL_LSB 20 +// Field : LOCAL_CLK2CARD_ON +#define LOCAL_CLK2CARD_ON_RESET 0x0 +#define LOCAL_CLK2CARD_ON_BITS 0x00040000 +#define LOCAL_CLK2CARD_ON_MSB 18 +#define LOCAL_CLK2CARD_ON_LSB 18 +// Field : LOCAL_CARD_CLK_STABLE +#define LOCAL_CARD_CLK_STABLE_RESET 0x0 +#define LOCAL_CARD_CLK_STABLE_BITS 0x00020000 +#define LOCAL_CARD_CLK_STABLE_MSB 17 +#define LOCAL_CARD_CLK_STABLE_LSB 17 +// Field : LOCAL_CARD_CLK_EN +#define LOCAL_CARD_CLK_EN_RESET 0x0 +#define LOCAL_CARD_CLK_EN_BITS 0x00010000 +#define LOCAL_CARD_CLK_EN_MSB 16 +#define LOCAL_CARD_CLK_EN_LSB 16 +// Field : LOCAL_CLK_GEN_SEL +#define LOCAL_CLK_GEN_SEL_RESET 0x0 +#define LOCAL_CLK_GEN_SEL_BITS 0x00001000 +#define LOCAL_CLK_GEN_SEL_MSB 12 +#define LOCAL_CLK_GEN_SEL_LSB 12 +#define LOCAL_CLK_GEN_SEL_VALUE_PROGCLOCKMODE 0x0 +#define LOCAL_CLK_GEN_SEL_VALUE_DIVCLOCKMODE 0x1 +// Field : LOCAL_FREQ_SEL +#define LOCAL_FREQ_SEL_RESET 0x000 +#define LOCAL_FREQ_SEL_BITS 0x000003ff +#define LOCAL_FREQ_SEL_MSB 9 +#define LOCAL_FREQ_SEL_LSB 0 +// Register : USE_LOCAL +#define USE_LOCAL 0x0000000c +#define USE_LOCAL_BITS 0x01951001 +#define USE_LOCAL_RESET 0x00000000 +// Field : USE_LOCAL_TUNING_CCLK_SEL +#define USE_LOCAL_TUNING_CCLK_SEL_RESET 0x0 +#define USE_LOCAL_TUNING_CCLK_SEL_BITS 0x01000000 +#define USE_LOCAL_TUNING_CCLK_SEL_MSB 24 +#define USE_LOCAL_TUNING_CCLK_SEL_LSB 24 +// Field : USE_LOCAL_TUNING_CCLK_UPDATE +#define USE_LOCAL_TUNING_CCLK_UPDATE_RESET 0x0 +#define USE_LOCAL_TUNING_CCLK_UPDATE_BITS 0x00800000 +#define USE_LOCAL_TUNING_CCLK_UPDATE_MSB 23 +#define USE_LOCAL_TUNING_CCLK_UPDATE_LSB 23 +// Field : USE_LOCAL_SAMPLE_CCLK_SEL +#define USE_LOCAL_SAMPLE_CCLK_SEL_RESET 0x0 +#define USE_LOCAL_SAMPLE_CCLK_SEL_BITS 0x00100000 +#define USE_LOCAL_SAMPLE_CCLK_SEL_MSB 20 +#define USE_LOCAL_SAMPLE_CCLK_SEL_LSB 20 +// Field : USE_LOCAL_CLK2CARD_ON +#define USE_LOCAL_CLK2CARD_ON_RESET 0x0 +#define USE_LOCAL_CLK2CARD_ON_BITS 0x00040000 +#define USE_LOCAL_CLK2CARD_ON_MSB 18 +#define USE_LOCAL_CLK2CARD_ON_LSB 18 +// Field : USE_LOCAL_CARD_CLK_EN +#define USE_LOCAL_CARD_CLK_EN_RESET 0x0 +#define USE_LOCAL_CARD_CLK_EN_BITS 0x00010000 +#define USE_LOCAL_CARD_CLK_EN_MSB 16 +#define USE_LOCAL_CARD_CLK_EN_LSB 16 +// Field : USE_LOCAL_CLK_GEN_SEL +#define USE_LOCAL_CLK_GEN_SEL_RESET 0x0 +#define USE_LOCAL_CLK_GEN_SEL_BITS 0x00001000 +#define USE_LOCAL_CLK_GEN_SEL_MSB 12 +#define USE_LOCAL_CLK_GEN_SEL_LSB 12 +// Field : USE_LOCAL_FREQ_SEL +#define USE_LOCAL_FREQ_SEL_RESET 0x0 +#define USE_LOCAL_FREQ_SEL_BITS 0x00000001 +#define USE_LOCAL_FREQ_SEL_MSB 0 +#define USE_LOCAL_FREQ_SEL_LSB 0 +// Register : SD_DELAY +#define SD_DELAY 0x00000010 +#define SD_DELAY_BITS 0x0000001f +#define SD_DELAY_RESET 0x00000000 +// Field : SD_DELAY_STEPS +#define SD_DELAY_STEPS_RESET 0x00 +#define SD_DELAY_STEPS_BITS 0x0000001f +#define SD_DELAY_STEPS_MSB 4 +#define SD_DELAY_STEPS_LSB 0 +// Register : RX_DELAY +#define RX_DELAY 0x00000014 +#define RX_DELAY_BITS 0x19f3331f +#define RX_DELAY_RESET 0x00000000 +// Field : RX_DELAY_BYPASS +#define RX_DELAY_BYPASS_RESET 0x0 +#define RX_DELAY_BYPASS_BITS 0x10000000 +#define RX_DELAY_BYPASS_MSB 28 +#define RX_DELAY_BYPASS_LSB 28 +// Field : RX_DELAY_FAIL_ACTUAL +#define RX_DELAY_FAIL_ACTUAL_RESET 0x0 +#define RX_DELAY_FAIL_ACTUAL_BITS 0x08000000 +#define RX_DELAY_FAIL_ACTUAL_MSB 27 +#define RX_DELAY_FAIL_ACTUAL_LSB 27 +// Field : RX_DELAY_ACTUAL +#define RX_DELAY_ACTUAL_RESET 0x00 +#define RX_DELAY_ACTUAL_BITS 0x01f00000 +#define RX_DELAY_ACTUAL_MSB 24 +#define RX_DELAY_ACTUAL_LSB 20 +// Field : RX_DELAY_OFFSET +#define RX_DELAY_OFFSET_RESET 0x0 +#define RX_DELAY_OFFSET_BITS 0x00030000 +#define RX_DELAY_OFFSET_MSB 17 +#define RX_DELAY_OFFSET_LSB 16 +// Field : RX_DELAY_OVERFLOW +#define RX_DELAY_OVERFLOW_RESET 0x0 +#define RX_DELAY_OVERFLOW_BITS 0x00003000 +#define RX_DELAY_OVERFLOW_MSB 13 +#define RX_DELAY_OVERFLOW_LSB 12 +#define RX_DELAY_OVERFLOW_VALUE_ALLOW 0x0 +#define RX_DELAY_OVERFLOW_VALUE_CLAMP 0x1 +#define RX_DELAY_OVERFLOW_VALUE_FAIL 0x2 +// Field : RX_DELAY_MAP +#define RX_DELAY_MAP_RESET 0x0 +#define RX_DELAY_MAP_BITS 0x00000300 +#define RX_DELAY_MAP_MSB 9 +#define RX_DELAY_MAP_LSB 8 +#define RX_DELAY_MAP_VALUE_DIRECT 0x0 +#define RX_DELAY_MAP_VALUE 0x1 +#define RX_DELAY_MAP_VALUE_STRETCH 0x2 +// Field : RX_DELAY_FIXED +#define RX_DELAY_FIXED_RESET 0x00 +#define RX_DELAY_FIXED_BITS 0x0000001f +#define RX_DELAY_FIXED_MSB 4 +#define RX_DELAY_FIXED_LSB 0 +// Register : NDIV +#define NDIV 0x00000018 +#define NDIV_BITS 0x1fff0000 +#define NDIV_RESET 0x00110000 +// Field : NDIV_DIVB +#define NDIV_DIVB_RESET 0x001 +#define NDIV_DIVB_BITS 0x1ff00000 +#define NDIV_DIVB_MSB 28 +#define NDIV_DIVB_LSB 20 +// Field : NDIV_DIVA +#define NDIV_DIVA_RESET 0x1 +#define NDIV_DIVA_BITS 0x000f0000 +#define NDIV_DIVA_MSB 19 +#define NDIV_DIVA_LSB 16 +// Register : CS +#define CS 0x0000001c +#define CS_BITS 0x00111101 +#define CS_RESET 0x00000001 +// Field : CS_RX_DEL_UPDATED +#define CS_RX_DEL_UPDATED_RESET 0x0 +#define CS_RX_DEL_UPDATED_BITS 0x00100000 +#define CS_RX_DEL_UPDATED_MSB 20 +#define CS_RX_DEL_UPDATED_LSB 20 +// Field : CS_RX_CLK_RUNNING +#define CS_RX_CLK_RUNNING_RESET 0x0 +#define CS_RX_CLK_RUNNING_BITS 0x00010000 +#define CS_RX_CLK_RUNNING_MSB 16 +#define CS_RX_CLK_RUNNING_LSB 16 +// Field : CS_SD_CLK_RUNNING +#define CS_SD_CLK_RUNNING_RESET 0x0 +#define CS_SD_CLK_RUNNING_BITS 0x00001000 +#define CS_SD_CLK_RUNNING_MSB 12 +#define CS_SD_CLK_RUNNING_LSB 12 +// Field : CS_TX_CLK_RUNNING +#define CS_TX_CLK_RUNNING_RESET 0x0 +#define CS_TX_CLK_RUNNING_BITS 0x00000100 +#define CS_TX_CLK_RUNNING_MSB 8 +#define CS_TX_CLK_RUNNING_LSB 8 +// Field : CS_RESET +#define CS_RESET_RESET 0x1 +#define CS_RESET_BITS 0x00000001 +#define CS_RESET_MSB 0 +#define CS_RESET_LSB 0 + +#define FPGA_SRC_RATE 400000000 + +/* Base number of steps to delay in relation to tx clk. + * The relationship of the 3 clocks are as follows: + * tx_clk: This clock is provided to the controller. Data is sent out + * to the pads using this clock. + * sd_clk: This clock is sent out to the card. + * rx_clk: This clock is used to sample the data coming back from the card. + * This may need to be several steps ahead of the tx_clk. The default rx delay + * is used as a base delay, and can be further adjusted by the sd host + * controller during the tuning process if using a DDR50 or faster SD card + */ +/* + * PRJY-1813 - the default SD clock delay needs to be set to ~60% of the total + * number of steps to meet tISU (>6ns) and tIH (>2ns) in high-speed mode. + * On FPGA this means delay SDCLK by 5, and sample RX with a delay of 6. + */ +#define DEFAULT_RX_DELAY 6 +#define DEFAULT_SD_DELAY 5 + +struct rp1_sdio_clkgen { + struct device *dev; + + /* Source clock. Either PLL VCO or fixed freq on FPGA */ + struct clk *src_clk; + /* Desired base frequency. Max freq card can go */ + struct clk *base_clk; + + struct clk_hw hw; + void __iomem *regs; + + /* Starting value of local register before changing freq */ + u32 local_base; +}; + +static inline void clkgen_write(struct rp1_sdio_clkgen *clkgen, u32 reg, u32 val) +{ + dev_dbg(clkgen->dev, "%s: write reg 0x%x: 0x%x\n", __func__, reg, val); + writel(val, clkgen->regs + reg); +} + +static inline u32 clkgen_read(struct rp1_sdio_clkgen *clkgen, u32 reg) +{ + u32 val = readl(clkgen->regs + reg); + + dev_dbg(clkgen->dev, "%s: read reg 0x%x: 0x%x\n", __func__, reg, val); + return val; +} + +static int get_steps(unsigned int steps) +{ + int ret = -1; + + if (steps == 4) + ret = MODE_STEPS_PER_CYCLE_VALUE_STEPS_4; + else if (steps == 5) + ret = MODE_STEPS_PER_CYCLE_VALUE_STEPS_5; + else if (steps == 6) + ret = MODE_STEPS_PER_CYCLE_VALUE_STEPS_6; + else if (steps == 8) + ret = MODE_STEPS_PER_CYCLE_VALUE_STEPS_8; + else if (steps == 10) + ret = MODE_STEPS_PER_CYCLE_VALUE_STEPS_10; + else if (steps == 12) + ret = MODE_STEPS_PER_CYCLE_VALUE_STEPS_12; + else if (steps == 16) + ret = MODE_STEPS_PER_CYCLE_VALUE_STEPS_16; + else if (steps == 20) + ret = MODE_STEPS_PER_CYCLE_VALUE_STEPS_20; + return ret; +} + +static int rp1_sdio_clk_init(struct rp1_sdio_clkgen *clkgen) +{ + unsigned long src_rate = clk_get_rate(clkgen->src_clk); + unsigned long base_rate = clk_get_rate(clkgen->base_clk); + unsigned int steps = src_rate / base_rate; + u32 reg = 0; + int steps_value = 0; + + dev_dbg(clkgen->dev, "init: src_rate %lu, base_rate %lu, steps %d\n", + src_rate, base_rate, steps); + + /* Assert reset while we set up clkgen */ + clkgen_write(clkgen, CS, CS_RESET_BITS); + + /* Pick clock source */ + if (src_rate == FPGA_SRC_RATE) { + /* Using ALT SRC */ + reg |= MODE_SRC_SEL_VALUE_CLK_ALT_SRC << MODE_SRC_SEL_LSB; + } else { + /* Assume we are using PLL SYS VCO */ + reg |= MODE_SRC_SEL_VALUE_PLL_SYS_VCO << MODE_SRC_SEL_LSB; + } + + /* How many delay steps are available in one cycle for this source */ + steps_value = get_steps(steps); + if (steps_value < 0) { + dev_err(clkgen->dev, "Invalid step value: %d\n", steps); + return -EINVAL; + } + reg |= steps_value << MODE_STEPS_PER_CYCLE_LSB; + + /* Mode register is done now*/ + clkgen_write(clkgen, MODE, reg); + + /* Now set delay mode */ + /* Clamp value if out of range rx delay is used */ + reg = RX_DELAY_OVERFLOW_VALUE_CLAMP << RX_DELAY_OVERFLOW_LSB; + /* SD tuning bus goes from 0x0 to 0xf but we don't necessarily have that + * many steps available depending on the source so map 0x0 -> 0xf to one + * cycle of rx delay + */ + reg |= RX_DELAY_MAP_VALUE_STRETCH << RX_DELAY_MAP_LSB; + + /* Default RX delay */ + dev_dbg(clkgen->dev, "default rx delay %d\n", DEFAULT_RX_DELAY); + reg |= (DEFAULT_RX_DELAY & RX_DELAY_FIXED_BITS) << RX_DELAY_FIXED_LSB; + clkgen_write(clkgen, RX_DELAY, reg); + + /* Default SD delay */ + dev_dbg(clkgen->dev, "default sd delay %d\n", DEFAULT_SD_DELAY); + reg = (DEFAULT_SD_DELAY & SD_DELAY_STEPS_BITS) << SD_DELAY_STEPS_LSB; + clkgen_write(clkgen, SD_DELAY, reg); + + /* We select freq, we turn on tx clock, we turn on sd clk, + * we pick clock generator mode + */ + reg = USE_LOCAL_FREQ_SEL_BITS | USE_LOCAL_CARD_CLK_EN_BITS | + USE_LOCAL_CLK2CARD_ON_BITS | USE_LOCAL_CLK_GEN_SEL_BITS; + clkgen_write(clkgen, USE_LOCAL, reg); + + /* Deassert reset. Reset bit is only writable bit of CS + * reg so fine to write a 0. + */ + clkgen_write(clkgen, CS, 0); + + return 0; +} + +#define RUNNING \ + (CS_TX_CLK_RUNNING_BITS | CS_RX_CLK_RUNNING_BITS | \ + CS_SD_CLK_RUNNING_BITS) +static int rp1_sdio_clk_is_prepared(struct clk_hw *hw) +{ + struct rp1_sdio_clkgen *clkgen = + container_of(hw, struct rp1_sdio_clkgen, hw); + u32 status; + + dev_dbg(clkgen->dev, "is_prepared\n"); + status = clkgen_read(clkgen, CS); + return ((status & RUNNING) == RUNNING); +} + +/* Can define an additional divider if an sd card isn't working at full speed */ +/* #define SLOWDOWN 3 */ + +static unsigned long rp1_sdio_clk_get_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + /* Get the current rate */ + struct rp1_sdio_clkgen *clkgen = + container_of(hw, struct rp1_sdio_clkgen, hw); + unsigned long actual_rate = 0; + u32 ndiv_diva; + u32 ndiv_divb; + u32 tmp; + u32 div; + + tmp = clkgen_read(clkgen, LOCAL); + if ((tmp & LOCAL_CLK2CARD_ON_BITS) == 0) { + dev_dbg(clkgen->dev, "get_rate 0\n"); + return 0; + } + + tmp = clkgen_read(clkgen, NDIV); + ndiv_diva = (tmp & NDIV_DIVA_BITS) >> NDIV_DIVA_LSB; + ndiv_divb = (tmp & NDIV_DIVB_BITS) >> NDIV_DIVB_LSB; + div = ndiv_diva * ndiv_divb; + actual_rate = (clk_get_rate(clkgen->base_clk) / div); + +#ifdef SLOWDOWN + actual_rate *= SLOWDOWN; +#endif + + dev_dbg(clkgen->dev, "get_rate. ndiv_diva %d, ndiv_divb %d = %lu\n", + ndiv_diva, ndiv_divb, actual_rate); + + return actual_rate; +} + +static int rp1_sdio_clk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct rp1_sdio_clkgen *clkgen = + container_of(hw, struct rp1_sdio_clkgen, hw); + u32 div; + u32 reg; + + dev_dbg(clkgen->dev, "set_rate %lu\n", rate); + + if (rate == 0) { + /* Keep tx clock running */ + clkgen_write(clkgen, LOCAL, LOCAL_CARD_CLK_EN_BITS); + return 0; + } + +#ifdef SLOWDOWN + rate /= SLOWDOWN; +#endif + + div = (clk_get_rate(clkgen->base_clk) / rate) - 1; + reg = LOCAL_CLK_GEN_SEL_BITS | LOCAL_CARD_CLK_EN_BITS | + LOCAL_CLK2CARD_ON_BITS | (div << LOCAL_FREQ_SEL_LSB); + clkgen_write(clkgen, LOCAL, reg); + + return 0; +} + +#define MAX_NDIV (256 * 8) +static int rp1_sdio_clk_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + unsigned long rate; + struct rp1_sdio_clkgen *clkgen = + container_of(hw, struct rp1_sdio_clkgen, hw); + unsigned long base_rate = clk_get_rate(clkgen->base_clk); + u32 div; + + /* What is the actual rate I can get if I request xyz */ + if (req->rate) { + div = min((u32)(base_rate / req->rate), (u32)MAX_NDIV); + rate = base_rate / div; + req->rate = rate; + dev_dbg(clkgen->dev, "determine_rate %lu: %lu / %d = %lu\n", + req->rate, base_rate, div, rate); + } else { + rate = 0; + dev_dbg(clkgen->dev, "determine_rate %lu: %lu\n", req->rate, + rate); + } + + return 0; +} + +static const struct clk_ops rp1_sdio_clk_ops = { + .is_prepared = rp1_sdio_clk_is_prepared, + .recalc_rate = rp1_sdio_clk_get_rate, + .set_rate = rp1_sdio_clk_set_rate, + .determine_rate = rp1_sdio_clk_determine_rate, +}; + +static int rp1_sdio_clk_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct rp1_sdio_clkgen *clkgen; + void __iomem *regs; + struct clk_init_data init = {}; + int ret; + + clkgen = devm_kzalloc(&pdev->dev, sizeof(*clkgen), GFP_KERNEL); + if (!clkgen) + return -ENOMEM; + platform_set_drvdata(pdev, clkgen); + + clkgen->dev = &pdev->dev; + + /* Source freq */ + clkgen->src_clk = devm_clk_get(&pdev->dev, "src"); + if (IS_ERR(clkgen->src_clk)) { + int err = PTR_ERR(clkgen->src_clk); + + dev_err(&pdev->dev, "failed to get src clk: %d\n", err); + return err; + } + + /* Desired maximum output freq (i.e. base freq) */ + clkgen->base_clk = devm_clk_get(&pdev->dev, "base"); + if (IS_ERR(clkgen->base_clk)) { + int err = PTR_ERR(clkgen->base_clk); + + dev_err(&pdev->dev, "failed to get base clk: %d\n", err); + return err; + } + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + init.name = node->name; + init.ops = &rp1_sdio_clk_ops; + init.flags = CLK_GET_RATE_NOCACHE; + + clkgen->hw.init = &init; + clkgen->regs = regs; + + dev_info(&pdev->dev, "loaded %s\n", init.name); + + ret = devm_clk_hw_register(&pdev->dev, &clkgen->hw); + if (ret) + return ret; + + ret = of_clk_add_hw_provider(node, of_clk_hw_simple_get, &clkgen->hw); + if (ret) + return ret; + + ret = rp1_sdio_clk_init(clkgen); + return ret; +} + +static void rp1_sdio_clk_remove(struct platform_device *pdev) +{ +} + +static const struct of_device_id rp1_sdio_clk_dt_ids[] = { + { .compatible = "raspberrypi,rp1-sdio-clk", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, rp1_sdio_clk_dt_ids); + +static struct platform_driver rp1_sdio_clk_driver = { + .probe = rp1_sdio_clk_probe, + .remove = rp1_sdio_clk_remove, + .driver = { + .name = "rp1-sdio-clk", + .of_match_table = rp1_sdio_clk_dt_ids, + }, +}; +module_platform_driver(rp1_sdio_clk_driver); + +MODULE_AUTHOR("Liam Fraser <liam@raspberrypi.com>"); +MODULE_DESCRIPTION("RP1 SDIO clock driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/clk/clk-rp1.c b/drivers/clk/clk-rp1.c new file mode 100644 index 00000000000000..a04d218dd3846a --- /dev/null +++ b/drivers/clk/clk-rp1.c @@ -0,0 +1,2494 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2023 Raspberry Pi Ltd. + * + * Clock driver for RP1 PCIe multifunction chip. + */ + +#include <linux/clk-provider.h> +#include <linux/clkdev.h> +#include <linux/clk.h> +#include <linux/debugfs.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/rp1_platform.h> +#include <linux/slab.h> + +#include <asm/div64.h> + +#include <dt-bindings/clock/rp1.h> + +#define PLL_SYS_CS 0x08000 +#define PLL_SYS_PWR 0x08004 +#define PLL_SYS_FBDIV_INT 0x08008 +#define PLL_SYS_FBDIV_FRAC 0x0800c +#define PLL_SYS_PRIM 0x08010 +#define PLL_SYS_SEC 0x08014 + +#define PLL_AUDIO_CS 0x0c000 +#define PLL_AUDIO_PWR 0x0c004 +#define PLL_AUDIO_FBDIV_INT 0x0c008 +#define PLL_AUDIO_FBDIV_FRAC 0x0c00c +#define PLL_AUDIO_PRIM 0x0c010 +#define PLL_AUDIO_SEC 0x0c014 +#define PLL_AUDIO_TERN 0x0c018 + +#define PLL_VIDEO_CS 0x10000 +#define PLL_VIDEO_PWR 0x10004 +#define PLL_VIDEO_FBDIV_INT 0x10008 +#define PLL_VIDEO_FBDIV_FRAC 0x1000c +#define PLL_VIDEO_PRIM 0x10010 +#define PLL_VIDEO_SEC 0x10014 + +#define GPCLK_OE_CTRL 0x00000 + +#define CLK_SYS_CTRL 0x00014 +#define CLK_SYS_DIV_INT 0x00018 +#define CLK_SYS_SEL 0x00020 + +#define CLK_SLOW_SYS_CTRL 0x00024 +#define CLK_SLOW_SYS_DIV_INT 0x00028 +#define CLK_SLOW_SYS_SEL 0x00030 + +#define CLK_DMA_CTRL 0x00044 +#define CLK_DMA_DIV_INT 0x00048 +#define CLK_DMA_SEL 0x00050 + +#define CLK_UART_CTRL 0x00054 +#define CLK_UART_DIV_INT 0x00058 +#define CLK_UART_SEL 0x00060 + +#define CLK_ETH_CTRL 0x00064 +#define CLK_ETH_DIV_INT 0x00068 +#define CLK_ETH_SEL 0x00070 + +#define CLK_PWM0_CTRL 0x00074 +#define CLK_PWM0_DIV_INT 0x00078 +#define CLK_PWM0_DIV_FRAC 0x0007c +#define CLK_PWM0_SEL 0x00080 + +#define CLK_PWM1_CTRL 0x00084 +#define CLK_PWM1_DIV_INT 0x00088 +#define CLK_PWM1_DIV_FRAC 0x0008c +#define CLK_PWM1_SEL 0x00090 + +#define CLK_AUDIO_IN_CTRL 0x00094 +#define CLK_AUDIO_IN_DIV_INT 0x00098 +#define CLK_AUDIO_IN_SEL 0x000a0 + +#define CLK_AUDIO_OUT_CTRL 0x000a4 +#define CLK_AUDIO_OUT_DIV_INT 0x000a8 +#define CLK_AUDIO_OUT_SEL 0x000b0 + +#define CLK_I2S_CTRL 0x000b4 +#define CLK_I2S_DIV_INT 0x000b8 +#define CLK_I2S_SEL 0x000c0 + +#define CLK_MIPI0_CFG_CTRL 0x000c4 +#define CLK_MIPI0_CFG_DIV_INT 0x000c8 +#define CLK_MIPI0_CFG_SEL 0x000d0 + +#define CLK_MIPI1_CFG_CTRL 0x000d4 +#define CLK_MIPI1_CFG_DIV_INT 0x000d8 +#define CLK_MIPI1_CFG_SEL 0x000e0 + +#define CLK_PCIE_AUX_CTRL 0x000e4 +#define CLK_PCIE_AUX_DIV_INT 0x000e8 +#define CLK_PCIE_AUX_SEL 0x000f0 + +#define CLK_USBH0_MICROFRAME_CTRL 0x000f4 +#define CLK_USBH0_MICROFRAME_DIV_INT 0x000f8 +#define CLK_USBH0_MICROFRAME_SEL 0x00100 + +#define CLK_USBH1_MICROFRAME_CTRL 0x00104 +#define CLK_USBH1_MICROFRAME_DIV_INT 0x00108 +#define CLK_USBH1_MICROFRAME_SEL 0x00110 + +#define CLK_USBH0_SUSPEND_CTRL 0x00114 +#define CLK_USBH0_SUSPEND_DIV_INT 0x00118 +#define CLK_USBH0_SUSPEND_SEL 0x00120 + +#define CLK_USBH1_SUSPEND_CTRL 0x00124 +#define CLK_USBH1_SUSPEND_DIV_INT 0x00128 +#define CLK_USBH1_SUSPEND_SEL 0x00130 + +#define CLK_ETH_TSU_CTRL 0x00134 +#define CLK_ETH_TSU_DIV_INT 0x00138 +#define CLK_ETH_TSU_SEL 0x00140 + +#define CLK_ADC_CTRL 0x00144 +#define CLK_ADC_DIV_INT 0x00148 +#define CLK_ADC_SEL 0x00150 + +#define CLK_SDIO_TIMER_CTRL 0x00154 +#define CLK_SDIO_TIMER_DIV_INT 0x00158 +#define CLK_SDIO_TIMER_SEL 0x00160 + +#define CLK_SDIO_ALT_SRC_CTRL 0x00164 +#define CLK_SDIO_ALT_SRC_DIV_INT 0x00168 +#define CLK_SDIO_ALT_SRC_SEL 0x00170 + +#define CLK_GP0_CTRL 0x00174 +#define CLK_GP0_DIV_INT 0x00178 +#define CLK_GP0_DIV_FRAC 0x0017c +#define CLK_GP0_SEL 0x00180 + +#define CLK_GP1_CTRL 0x00184 +#define CLK_GP1_DIV_INT 0x00188 +#define CLK_GP1_DIV_FRAC 0x0018c +#define CLK_GP1_SEL 0x00190 + +#define CLK_GP2_CTRL 0x00194 +#define CLK_GP2_DIV_INT 0x00198 +#define CLK_GP2_DIV_FRAC 0x0019c +#define CLK_GP2_SEL 0x001a0 + +#define CLK_GP3_CTRL 0x001a4 +#define CLK_GP3_DIV_INT 0x001a8 +#define CLK_GP3_DIV_FRAC 0x001ac +#define CLK_GP3_SEL 0x001b0 + +#define CLK_GP4_CTRL 0x001b4 +#define CLK_GP4_DIV_INT 0x001b8 +#define CLK_GP4_DIV_FRAC 0x001bc +#define CLK_GP4_SEL 0x001c0 + +#define CLK_GP5_CTRL 0x001c4 +#define CLK_GP5_DIV_INT 0x001c8 +#define CLK_GP5_DIV_FRAC 0x001cc +#define CLK_GP5_SEL 0x001d0 + +#define CLK_SYS_RESUS_CTRL 0x0020c + +#define CLK_SLOW_SYS_RESUS_CTRL 0x00214 + +#define FC0_REF_KHZ 0x0021c +#define FC0_MIN_KHZ 0x00220 +#define FC0_MAX_KHZ 0x00224 +#define FC0_DELAY 0x00228 +#define FC0_INTERVAL 0x0022c +#define FC0_SRC 0x00230 +#define FC0_STATUS 0x00234 +#define FC0_RESULT 0x00238 +#define FC_SIZE 0x20 +#define FC_COUNT 8 +#define FC_NUM(idx, off) ((idx) * 32 + (off)) + +#define AUX_SEL 1 + +#define VIDEO_CLOCKS_OFFSET 0x4000 +#define VIDEO_CLK_VEC_CTRL (VIDEO_CLOCKS_OFFSET + 0x0000) +#define VIDEO_CLK_VEC_DIV_INT (VIDEO_CLOCKS_OFFSET + 0x0004) +#define VIDEO_CLK_VEC_SEL (VIDEO_CLOCKS_OFFSET + 0x000c) +#define VIDEO_CLK_DPI_CTRL (VIDEO_CLOCKS_OFFSET + 0x0010) +#define VIDEO_CLK_DPI_DIV_INT (VIDEO_CLOCKS_OFFSET + 0x0014) +#define VIDEO_CLK_DPI_SEL (VIDEO_CLOCKS_OFFSET + 0x001c) +#define VIDEO_CLK_MIPI0_DPI_CTRL (VIDEO_CLOCKS_OFFSET + 0x0020) +#define VIDEO_CLK_MIPI0_DPI_DIV_INT (VIDEO_CLOCKS_OFFSET + 0x0024) +#define VIDEO_CLK_MIPI0_DPI_DIV_FRAC (VIDEO_CLOCKS_OFFSET + 0x0028) +#define VIDEO_CLK_MIPI0_DPI_SEL (VIDEO_CLOCKS_OFFSET + 0x002c) +#define VIDEO_CLK_MIPI1_DPI_CTRL (VIDEO_CLOCKS_OFFSET + 0x0030) +#define VIDEO_CLK_MIPI1_DPI_DIV_INT (VIDEO_CLOCKS_OFFSET + 0x0034) +#define VIDEO_CLK_MIPI1_DPI_DIV_FRAC (VIDEO_CLOCKS_OFFSET + 0x0038) +#define VIDEO_CLK_MIPI1_DPI_SEL (VIDEO_CLOCKS_OFFSET + 0x003c) + +#define DIV_INT_8BIT_MAX 0x000000ffu /* max divide for most clocks */ +#define DIV_INT_16BIT_MAX 0x0000ffffu /* max divide for GPx, PWM */ +#define DIV_INT_24BIT_MAX 0x00ffffffu /* max divide for CLK_SYS */ + +#define FC0_STATUS_DONE BIT(4) +#define FC0_STATUS_RUNNING BIT(8) +#define FC0_RESULT_FRAC_SHIFT 5 + +#define PLL_PRIM_DIV1_SHIFT 16 +#define PLL_PRIM_DIV1_MASK 0x00070000 +#define PLL_PRIM_DIV2_SHIFT 12 +#define PLL_PRIM_DIV2_MASK 0x00007000 + +#define PLL_SEC_DIV_SHIFT 8 +#define PLL_SEC_DIV_WIDTH 5 +#define PLL_SEC_DIV_MASK 0x00001f00 + +#define PLL_CS_LOCK BIT(31) +#define PLL_CS_REFDIV_SHIFT 0 + +#define PLL_PWR_PD BIT(0) +#define PLL_PWR_DACPD BIT(1) +#define PLL_PWR_DSMPD BIT(2) +#define PLL_PWR_POSTDIVPD BIT(3) +#define PLL_PWR_4PHASEPD BIT(4) +#define PLL_PWR_VCOPD BIT(5) +#define PLL_PWR_MASK 0x0000003f + +#define PLL_SEC_RST BIT(16) +#define PLL_SEC_IMPL BIT(31) + +/* PLL phase output for both PRI and SEC */ +#define PLL_PH_EN BIT(4) +#define PLL_PH_PHASE_SHIFT 0 + +#define RP1_PLL_PHASE_0 0 +#define RP1_PLL_PHASE_90 1 +#define RP1_PLL_PHASE_180 2 +#define RP1_PLL_PHASE_270 3 + +/* Clock fields for all clocks */ +#define CLK_CTRL_ENABLE BIT(11) +#define CLK_CTRL_AUXSRC_MASK 0x000003e0 +#define CLK_CTRL_AUXSRC_SHIFT 5 +#define CLK_CTRL_SRC_SHIFT 0 +#define CLK_DIV_FRAC_BITS 16 + +#define KHz 1000 +#define MHz (KHz * KHz) +#define LOCK_TIMEOUT_NS 100000000 +#define FC_TIMEOUT_NS 100000000 + +#define MAX_CLK_PARENTS 16 + +#define MEASURE_CLOCK_RATE +const char * const fc0_ref_clk_name = "clk_slow_sys"; + +#define ABS_DIFF(a, b) ((a) > (b) ? (a) - (b) : (b) - (a)) +#define DIV_NEAREST(a, b) (((a) + ((b) >> 1)) / (b)) +#define DIV_U64_NEAREST(a, b) div_u64(((a) + ((b) >> 1)), (b)) + +/* + * Names of the reference clock for the pll cores. This name must match + * the DT reference clock-output-name. + */ +static const char *const ref_clock = "xosc"; + +/* + * Secondary PLL channel output divider table. + * Divider values range from 8 to 19. + * Invalid values default to 19 + */ +static const struct clk_div_table pll_sec_div_table[] = { + { 0x00, 19 }, + { 0x01, 19 }, + { 0x02, 19 }, + { 0x03, 19 }, + { 0x04, 19 }, + { 0x05, 19 }, + { 0x06, 19 }, + { 0x07, 19 }, + { 0x08, 8 }, + { 0x09, 9 }, + { 0x0a, 10 }, + { 0x0b, 11 }, + { 0x0c, 12 }, + { 0x0d, 13 }, + { 0x0e, 14 }, + { 0x0f, 15 }, + { 0x10, 16 }, + { 0x11, 17 }, + { 0x12, 18 }, + { 0x13, 19 }, + { 0x14, 19 }, + { 0x15, 19 }, + { 0x16, 19 }, + { 0x17, 19 }, + { 0x18, 19 }, + { 0x19, 19 }, + { 0x1a, 19 }, + { 0x1b, 19 }, + { 0x1c, 19 }, + { 0x1d, 19 }, + { 0x1e, 19 }, + { 0x1f, 19 }, + { 0 } +}; + +struct rp1_clockman { + struct device *dev; + void __iomem *regs; + spinlock_t regs_lock; /* spinlock for all clocks */ + + /* Must be last */ + struct clk_hw_onecell_data onecell; +}; + +struct rp1_pll_core_data { + const char *name; + u32 cs_reg; + u32 pwr_reg; + u32 fbdiv_int_reg; + u32 fbdiv_frac_reg; + unsigned long flags; + u32 fc0_src; +}; + +struct rp1_pll_data { + const char *name; + const char *source_pll; + u32 ctrl_reg; + unsigned long flags; + u32 fc0_src; +}; + +struct rp1_pll_ph_data { + const char *name; + const char *source_pll; + unsigned int phase; + unsigned int fixed_divider; + u32 ph_reg; + unsigned long flags; + u32 fc0_src; +}; + +struct rp1_pll_divider_data { + const char *name; + const char *source_pll; + u32 sec_reg; + unsigned long flags; + u32 fc0_src; +}; + +struct rp1_clock_data { + const char *name; + const char *const parents[MAX_CLK_PARENTS]; + int num_std_parents; + int num_aux_parents; + unsigned long flags; + u32 oe_mask; + u32 clk_src_mask; + u32 ctrl_reg; + u32 div_int_reg; + u32 div_frac_reg; + u32 sel_reg; + u32 div_int_max; + unsigned long max_freq; + u32 fc0_src; +}; + +struct rp1_pll_core { + struct clk_hw hw; + struct rp1_clockman *clockman; + const struct rp1_pll_core_data *data; + unsigned long cached_rate; +}; + +struct rp1_pll { + struct clk_hw hw; + struct clk_divider div; + struct rp1_clockman *clockman; + const struct rp1_pll_data *data; + unsigned long cached_rate; +}; + +struct rp1_pll_ph { + struct clk_hw hw; + struct rp1_clockman *clockman; + const struct rp1_pll_ph_data *data; +}; + +struct rp1_clock { + struct clk_hw hw; + struct rp1_clockman *clockman; + const struct rp1_clock_data *data; + unsigned long cached_rate; +}; + +struct rp1_varsrc { + struct clk_hw hw; + struct rp1_clockman *clockman; + unsigned long rate; +}; + +struct rp1_clk_change { + struct clk_hw *hw; + unsigned long new_rate; +}; + +struct rp1_clk_change rp1_clk_chg_tree[3]; + +static struct clk_hw *clk_xosc; +static struct clk_hw *clk_audio; +static struct clk_hw *clk_i2s; + +static void rp1_debugfs_regset(struct rp1_clockman *clockman, u32 base, + const struct debugfs_reg32 *regs, + size_t nregs, struct dentry *dentry) +{ + struct debugfs_regset32 *regset; + + regset = devm_kzalloc(clockman->dev, sizeof(*regset), GFP_KERNEL); + if (!regset) + return; + + regset->regs = regs; + regset->nregs = nregs; + regset->base = clockman->regs + base; + + debugfs_create_regset32("regdump", 0444, dentry, regset); +} + +static inline u32 set_register_field(u32 reg, u32 val, u32 mask, u32 shift) +{ + reg &= ~mask; + reg |= (val << shift) & mask; + return reg; +} + +static inline +void clockman_write(struct rp1_clockman *clockman, u32 reg, u32 val) +{ + writel(val, clockman->regs + reg); +} + +static inline u32 clockman_read(struct rp1_clockman *clockman, u32 reg) +{ + return readl(clockman->regs + reg); +} + +#ifdef MEASURE_CLOCK_RATE +static unsigned long clockman_measure_clock(struct rp1_clockman *clockman, + const char *clk_name, + unsigned int fc0_src) +{ + struct clk *ref_clk = __clk_lookup(fc0_ref_clk_name); + unsigned long result; + ktime_t timeout; + unsigned int fc_idx, fc_offset, fc_src; + + fc_idx = fc0_src / 32; + fc_src = fc0_src % 32; + + /* fc_src == 0 is invalid. */ + if (!fc_src || fc_idx >= FC_COUNT) + return 0; + + fc_offset = fc_idx * FC_SIZE; + + /* Ensure the frequency counter is idle. */ + timeout = ktime_add_ns(ktime_get(), FC_TIMEOUT_NS); + while (clockman_read(clockman, fc_offset + FC0_STATUS) & FC0_STATUS_RUNNING) { + if (ktime_after(ktime_get(), timeout)) { + dev_err(clockman->dev, "%s: FC0 busy timeout\n", + clk_name); + return 0; + } + cpu_relax(); + } + + spin_lock(&clockman->regs_lock); + clockman_write(clockman, fc_offset + FC0_REF_KHZ, + clk_get_rate(ref_clk) / KHz); + clockman_write(clockman, fc_offset + FC0_MIN_KHZ, 0); + clockman_write(clockman, fc_offset + FC0_MAX_KHZ, 0x1ffffff); + clockman_write(clockman, fc_offset + FC0_INTERVAL, 8); + clockman_write(clockman, fc_offset + FC0_DELAY, 7); + clockman_write(clockman, fc_offset + FC0_SRC, fc_src); + spin_unlock(&clockman->regs_lock); + + /* Ensure the frequency counter is idle. */ + timeout = ktime_add_ns(ktime_get(), FC_TIMEOUT_NS); + while (!(clockman_read(clockman, fc_offset + FC0_STATUS) & FC0_STATUS_DONE)) { + if (ktime_after(ktime_get(), timeout)) { + dev_err(clockman->dev, "%s: FC0 wait timeout\n", + clk_name); + return 0; + } + cpu_relax(); + } + + result = clockman_read(clockman, fc_offset + FC0_RESULT); + + /* Disable FC0 */ + spin_lock(&clockman->regs_lock); + clockman_write(clockman, fc_offset + FC0_SRC, 0); + spin_unlock(&clockman->regs_lock); + + return result; +} +#endif + +static int rp1_pll_core_is_on(struct clk_hw *hw) +{ + struct rp1_pll_core *pll_core = container_of(hw, struct rp1_pll_core, hw); + struct rp1_clockman *clockman = pll_core->clockman; + const struct rp1_pll_core_data *data = pll_core->data; + u32 pwr = clockman_read(clockman, data->pwr_reg); + + return (pwr & PLL_PWR_PD) || (pwr & PLL_PWR_POSTDIVPD); +} + +static int rp1_pll_core_on(struct clk_hw *hw) +{ + struct rp1_pll_core *pll_core = container_of(hw, struct rp1_pll_core, hw); + struct rp1_clockman *clockman = pll_core->clockman; + const struct rp1_pll_core_data *data = pll_core->data; + u32 fbdiv_frac; + ktime_t timeout; + + spin_lock(&clockman->regs_lock); + + if (!(clockman_read(clockman, data->cs_reg) & PLL_CS_LOCK)) { + /* Reset to a known state. */ + clockman_write(clockman, data->pwr_reg, PLL_PWR_MASK); + clockman_write(clockman, data->fbdiv_int_reg, 20); + clockman_write(clockman, data->fbdiv_frac_reg, 0); + clockman_write(clockman, data->cs_reg, 1 << PLL_CS_REFDIV_SHIFT); + } + + /* Come out of reset. */ + fbdiv_frac = clockman_read(clockman, data->fbdiv_frac_reg); + clockman_write(clockman, data->pwr_reg, fbdiv_frac ? 0 : PLL_PWR_DSMPD); + spin_unlock(&clockman->regs_lock); + + /* Wait for the PLL to lock. */ + timeout = ktime_add_ns(ktime_get(), LOCK_TIMEOUT_NS); + while (!(clockman_read(clockman, data->cs_reg) & PLL_CS_LOCK)) { + if (ktime_after(ktime_get(), timeout)) { + dev_err(clockman->dev, "%s: can't lock PLL\n", + clk_hw_get_name(hw)); + return -ETIMEDOUT; + } + cpu_relax(); + } + + return 0; +} + +static void rp1_pll_core_off(struct clk_hw *hw) +{ + struct rp1_pll_core *pll_core = container_of(hw, struct rp1_pll_core, hw); + struct rp1_clockman *clockman = pll_core->clockman; + const struct rp1_pll_core_data *data = pll_core->data; + + spin_lock(&clockman->regs_lock); + clockman_write(clockman, data->pwr_reg, 0); + spin_unlock(&clockman->regs_lock); +} + +static inline unsigned long get_pll_core_divider(struct clk_hw *hw, + unsigned long rate, + unsigned long parent_rate, + u32 *div_int, u32 *div_frac) +{ + unsigned long calc_rate; + u32 fbdiv_int, fbdiv_frac; + u64 div_fp64; /* 32.32 fixed point fraction. */ + + /* Factor of reference clock to VCO frequency. */ + div_fp64 = (u64)(rate) << 32; + div_fp64 = DIV_U64_NEAREST(div_fp64, parent_rate); + + /* Round the fractional component at 24 bits. */ + div_fp64 += 1 << (32 - 24 - 1); + + fbdiv_int = div_fp64 >> 32; + fbdiv_frac = (div_fp64 >> (32 - 24)) & 0xffffff; + + calc_rate = + ((u64)parent_rate * (((u64)fbdiv_int << 24) + fbdiv_frac) + (1 << 23)) >> 24; + + *div_int = fbdiv_int; + *div_frac = fbdiv_frac; + + return calc_rate; +} + +static int rp1_pll_core_set_rate(struct clk_hw *hw, + unsigned long rate, unsigned long parent_rate) +{ + struct rp1_pll_core *pll_core = container_of(hw, struct rp1_pll_core, hw); + struct rp1_clockman *clockman = pll_core->clockman; + const struct rp1_pll_core_data *data = pll_core->data; + unsigned long calc_rate; + u32 fbdiv_int, fbdiv_frac; + + // todo: is this needed?? + //rp1_pll_off(hw); + + /* Disable dividers to start with. */ + spin_lock(&clockman->regs_lock); + clockman_write(clockman, data->fbdiv_int_reg, 0); + clockman_write(clockman, data->fbdiv_frac_reg, 0); + spin_unlock(&clockman->regs_lock); + + calc_rate = get_pll_core_divider(hw, rate, parent_rate, + &fbdiv_int, &fbdiv_frac); + + spin_lock(&clockman->regs_lock); + clockman_write(clockman, data->pwr_reg, fbdiv_frac ? 0 : PLL_PWR_DSMPD); + clockman_write(clockman, data->fbdiv_int_reg, fbdiv_int); + clockman_write(clockman, data->fbdiv_frac_reg, fbdiv_frac); + spin_unlock(&clockman->regs_lock); + + /* Check that reference frequency is no greater than VCO / 16. */ + BUG_ON(parent_rate > (rate / 16)); + + pll_core->cached_rate = calc_rate; + + spin_lock(&clockman->regs_lock); + /* Don't need to divide ref unless parent_rate > (output freq / 16) */ + clockman_write(clockman, data->cs_reg, + clockman_read(clockman, data->cs_reg) | + (1 << PLL_CS_REFDIV_SHIFT)); + spin_unlock(&clockman->regs_lock); + + return 0; +} + +static unsigned long rp1_pll_core_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct rp1_pll_core *pll_core = container_of(hw, struct rp1_pll_core, hw); + struct rp1_clockman *clockman = pll_core->clockman; + const struct rp1_pll_core_data *data = pll_core->data; + u32 fbdiv_int, fbdiv_frac; + unsigned long calc_rate; + + fbdiv_int = clockman_read(clockman, data->fbdiv_int_reg); + fbdiv_frac = clockman_read(clockman, data->fbdiv_frac_reg); + calc_rate = + ((u64)parent_rate * (((u64)fbdiv_int << 24) + fbdiv_frac) + (1 << 23)) >> 24; + + return calc_rate; +} + +static long rp1_pll_core_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + u32 fbdiv_int, fbdiv_frac; + long calc_rate; + + calc_rate = get_pll_core_divider(hw, rate, *parent_rate, + &fbdiv_int, &fbdiv_frac); + return calc_rate; +} + +static void rp1_pll_core_debug_init(struct clk_hw *hw, struct dentry *dentry) +{ + struct rp1_pll_core *pll_core = container_of(hw, struct rp1_pll_core, hw); + struct rp1_clockman *clockman = pll_core->clockman; + const struct rp1_pll_core_data *data = pll_core->data; + struct debugfs_reg32 *regs; + + regs = devm_kcalloc(clockman->dev, 4, sizeof(*regs), GFP_KERNEL); + if (!regs) + return; + + regs[0].name = "cs"; + regs[0].offset = data->cs_reg; + regs[1].name = "pwr"; + regs[1].offset = data->pwr_reg; + regs[2].name = "fbdiv_int"; + regs[2].offset = data->fbdiv_int_reg; + regs[3].name = "fbdiv_frac"; + regs[3].offset = data->fbdiv_frac_reg; + + rp1_debugfs_regset(clockman, 0, regs, 4, dentry); +} + +static void get_pll_prim_dividers(unsigned long rate, unsigned long parent_rate, + u32 *divider1, u32 *divider2) +{ + unsigned int div1, div2; + unsigned int best_div1 = 7, best_div2 = 7; + unsigned long best_rate_diff = + ABS_DIFF(DIV_ROUND_CLOSEST(parent_rate, best_div1 * best_div2), rate); + long rate_diff, calc_rate; + + for (div1 = 1; div1 <= 7; div1++) { + for (div2 = 1; div2 <= div1; div2++) { + calc_rate = DIV_ROUND_CLOSEST(parent_rate, div1 * div2); + rate_diff = ABS_DIFF(calc_rate, rate); + + if (calc_rate == rate) { + best_div1 = div1; + best_div2 = div2; + goto done; + } else if (rate_diff < best_rate_diff) { + best_div1 = div1; + best_div2 = div2; + best_rate_diff = rate_diff; + } + } + } + +done: + *divider1 = best_div1; + *divider2 = best_div2; +} + +static int rp1_pll_set_rate(struct clk_hw *hw, + unsigned long rate, unsigned long parent_rate) +{ + struct rp1_pll *pll = container_of(hw, struct rp1_pll, hw); + struct rp1_clockman *clockman = pll->clockman; + const struct rp1_pll_data *data = pll->data; + u32 prim, prim_div1, prim_div2; + + get_pll_prim_dividers(rate, parent_rate, &prim_div1, &prim_div2); + + spin_lock(&clockman->regs_lock); + prim = clockman_read(clockman, data->ctrl_reg); + prim = set_register_field(prim, prim_div1, PLL_PRIM_DIV1_MASK, + PLL_PRIM_DIV1_SHIFT); + prim = set_register_field(prim, prim_div2, PLL_PRIM_DIV2_MASK, + PLL_PRIM_DIV2_SHIFT); + clockman_write(clockman, data->ctrl_reg, prim); + spin_unlock(&clockman->regs_lock); + +#ifdef MEASURE_CLOCK_RATE + clockman_measure_clock(clockman, data->name, data->fc0_src); +#endif + return 0; +} + +static unsigned long rp1_pll_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct rp1_pll *pll = container_of(hw, struct rp1_pll, hw); + struct rp1_clockman *clockman = pll->clockman; + const struct rp1_pll_data *data = pll->data; + u32 prim, prim_div1, prim_div2; + + prim = clockman_read(clockman, data->ctrl_reg); + prim_div1 = (prim & PLL_PRIM_DIV1_MASK) >> PLL_PRIM_DIV1_SHIFT; + prim_div2 = (prim & PLL_PRIM_DIV2_MASK) >> PLL_PRIM_DIV2_SHIFT; + + if (!prim_div1 || !prim_div2) { + dev_err(clockman->dev, "%s: (%s) zero divider value\n", + __func__, data->name); + return 0; + } + + return DIV_ROUND_CLOSEST(parent_rate, prim_div1 * prim_div2); +} + +static long rp1_pll_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + const struct rp1_clk_change *chg = &rp1_clk_chg_tree[1]; + u32 div1, div2; + + if (chg->hw == hw && chg->new_rate == rate) + *parent_rate = chg[1].new_rate; + + get_pll_prim_dividers(rate, *parent_rate, &div1, &div2); + + return DIV_ROUND_CLOSEST(*parent_rate, div1 * div2); +} + +static void rp1_pll_debug_init(struct clk_hw *hw, + struct dentry *dentry) +{ + struct rp1_pll *pll = container_of(hw, struct rp1_pll, hw); + struct rp1_clockman *clockman = pll->clockman; + const struct rp1_pll_data *data = pll->data; + struct debugfs_reg32 *regs; + + regs = devm_kcalloc(clockman->dev, 1, sizeof(*regs), GFP_KERNEL); + if (!regs) + return; + + regs[0].name = "prim"; + regs[0].offset = data->ctrl_reg; + + rp1_debugfs_regset(clockman, 0, regs, 1, dentry); +} + +static int rp1_pll_ph_is_on(struct clk_hw *hw) +{ + struct rp1_pll_ph *pll = container_of(hw, struct rp1_pll_ph, hw); + struct rp1_clockman *clockman = pll->clockman; + const struct rp1_pll_ph_data *data = pll->data; + + return !!(clockman_read(clockman, data->ph_reg) & PLL_PH_EN); +} + +static int rp1_pll_ph_on(struct clk_hw *hw) +{ + struct rp1_pll_ph *pll_ph = container_of(hw, struct rp1_pll_ph, hw); + struct rp1_clockman *clockman = pll_ph->clockman; + const struct rp1_pll_ph_data *data = pll_ph->data; + u32 ph_reg; + + /* todo: ensure pri/sec is enabled! */ + spin_lock(&clockman->regs_lock); + ph_reg = clockman_read(clockman, data->ph_reg); + ph_reg |= data->phase << PLL_PH_PHASE_SHIFT; + ph_reg |= PLL_PH_EN; + clockman_write(clockman, data->ph_reg, ph_reg); + spin_unlock(&clockman->regs_lock); + +#ifdef MEASURE_CLOCK_RATE + clockman_measure_clock(clockman, data->name, data->fc0_src); +#endif + return 0; +} + +static void rp1_pll_ph_off(struct clk_hw *hw) +{ + struct rp1_pll_ph *pll_ph = container_of(hw, struct rp1_pll_ph, hw); + struct rp1_clockman *clockman = pll_ph->clockman; + const struct rp1_pll_ph_data *data = pll_ph->data; + + spin_lock(&clockman->regs_lock); + clockman_write(clockman, data->ph_reg, + clockman_read(clockman, data->ph_reg) & ~PLL_PH_EN); + spin_unlock(&clockman->regs_lock); +} + +static int rp1_pll_ph_set_rate(struct clk_hw *hw, + unsigned long rate, unsigned long parent_rate) +{ + struct rp1_pll_ph *pll_ph = container_of(hw, struct rp1_pll_ph, hw); + const struct rp1_pll_ph_data *data = pll_ph->data; + struct rp1_clockman *clockman = pll_ph->clockman; + + /* Nothing really to do here! */ + WARN_ON(data->fixed_divider != 1 && data->fixed_divider != 2); + WARN_ON(rate != parent_rate / data->fixed_divider); + +#ifdef MEASURE_CLOCK_RATE + if (rp1_pll_ph_is_on(hw)) + clockman_measure_clock(clockman, data->name, data->fc0_src); +#endif + return 0; +} + +static unsigned long rp1_pll_ph_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct rp1_pll_ph *pll_ph = container_of(hw, struct rp1_pll_ph, hw); + const struct rp1_pll_ph_data *data = pll_ph->data; + + return parent_rate / data->fixed_divider; +} + +static long rp1_pll_ph_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct rp1_pll_ph *pll_ph = container_of(hw, struct rp1_pll_ph, hw); + const struct rp1_pll_ph_data *data = pll_ph->data; + + return *parent_rate / data->fixed_divider; +} + +static void rp1_pll_ph_debug_init(struct clk_hw *hw, + struct dentry *dentry) +{ + struct rp1_pll_ph *pll_ph = container_of(hw, struct rp1_pll_ph, hw); + const struct rp1_pll_ph_data *data = pll_ph->data; + struct rp1_clockman *clockman = pll_ph->clockman; + struct debugfs_reg32 *regs; + + regs = devm_kcalloc(clockman->dev, 1, sizeof(*regs), GFP_KERNEL); + if (!regs) + return; + + regs[0].name = "ph_reg"; + regs[0].offset = data->ph_reg; + + rp1_debugfs_regset(clockman, 0, regs, 1, dentry); +} + +static int rp1_pll_divider_is_on(struct clk_hw *hw) +{ + struct rp1_pll *divider = container_of(hw, struct rp1_pll, div.hw); + struct rp1_clockman *clockman = divider->clockman; + const struct rp1_pll_data *data = divider->data; + + return !(clockman_read(clockman, data->ctrl_reg) & PLL_SEC_RST); +} + +static int rp1_pll_divider_on(struct clk_hw *hw) +{ + struct rp1_pll *divider = container_of(hw, struct rp1_pll, div.hw); + struct rp1_clockman *clockman = divider->clockman; + const struct rp1_pll_data *data = divider->data; + + spin_lock(&clockman->regs_lock); + /* Check the implementation bit is set! */ + WARN_ON(!(clockman_read(clockman, data->ctrl_reg) & PLL_SEC_IMPL)); + clockman_write(clockman, data->ctrl_reg, + clockman_read(clockman, data->ctrl_reg) & ~PLL_SEC_RST); + spin_unlock(&clockman->regs_lock); + +#ifdef MEASURE_CLOCK_RATE + clockman_measure_clock(clockman, data->name, data->fc0_src); +#endif + return 0; +} + +static void rp1_pll_divider_off(struct clk_hw *hw) +{ + struct rp1_pll *divider = container_of(hw, struct rp1_pll, div.hw); + struct rp1_clockman *clockman = divider->clockman; + const struct rp1_pll_data *data = divider->data; + + spin_lock(&clockman->regs_lock); + clockman_write(clockman, data->ctrl_reg, + clockman_read(clockman, data->ctrl_reg) | PLL_SEC_RST); + spin_unlock(&clockman->regs_lock); +} + +static int rp1_pll_divider_set_rate(struct clk_hw *hw, + unsigned long rate, + unsigned long parent_rate) +{ + struct rp1_pll *divider = container_of(hw, struct rp1_pll, div.hw); + struct rp1_clockman *clockman = divider->clockman; + const struct rp1_pll_data *data = divider->data; + u32 div, sec; + + div = DIV_ROUND_UP_ULL(parent_rate, rate); + div = clamp(div, 8u, 19u); + + spin_lock(&clockman->regs_lock); + sec = clockman_read(clockman, data->ctrl_reg); + sec = set_register_field(sec, div, PLL_SEC_DIV_MASK, PLL_SEC_DIV_SHIFT); + + /* Must keep the divider in reset to change the value. */ + sec |= PLL_SEC_RST; + clockman_write(clockman, data->ctrl_reg, sec); + + // todo: must sleep 10 pll vco cycles + sec &= ~PLL_SEC_RST; + clockman_write(clockman, data->ctrl_reg, sec); + spin_unlock(&clockman->regs_lock); + +#ifdef MEASURE_CLOCK_RATE + if (rp1_pll_divider_is_on(hw)) + clockman_measure_clock(clockman, data->name, data->fc0_src); +#endif + return 0; +} + +static unsigned long rp1_pll_divider_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + return clk_divider_ops.recalc_rate(hw, parent_rate); +} + +static long rp1_pll_divider_round_rate(struct clk_hw *hw, + unsigned long rate, + unsigned long *parent_rate) +{ + return clk_divider_ops.round_rate(hw, rate, parent_rate); +} + +static void rp1_pll_divider_debug_init(struct clk_hw *hw, struct dentry *dentry) +{ + struct rp1_pll *divider = container_of(hw, struct rp1_pll, div.hw); + struct rp1_clockman *clockman = divider->clockman; + const struct rp1_pll_data *data = divider->data; + struct debugfs_reg32 *regs; + + regs = devm_kcalloc(clockman->dev, 1, sizeof(*regs), GFP_KERNEL); + if (!regs) + return; + + regs[0].name = "sec"; + regs[0].offset = data->ctrl_reg; + + rp1_debugfs_regset(clockman, 0, regs, 1, dentry); +} + +static int rp1_clock_is_on(struct clk_hw *hw) +{ + struct rp1_clock *clock = container_of(hw, struct rp1_clock, hw); + struct rp1_clockman *clockman = clock->clockman; + const struct rp1_clock_data *data = clock->data; + + return !!(clockman_read(clockman, data->ctrl_reg) & CLK_CTRL_ENABLE); +} + +static unsigned long rp1_clock_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct rp1_clock *clock = container_of(hw, struct rp1_clock, hw); + struct rp1_clockman *clockman = clock->clockman; + const struct rp1_clock_data *data = clock->data; + u64 calc_rate; + u64 div; + + u32 frac; + + div = clockman_read(clockman, data->div_int_reg); + frac = (data->div_frac_reg != 0) ? + clockman_read(clockman, data->div_frac_reg) : 0; + + /* If the integer portion of the divider is 0, treat it as 2^16 */ + if (!div) + div = 1 << 16; + + div = (div << CLK_DIV_FRAC_BITS) | (frac >> (32 - CLK_DIV_FRAC_BITS)); + + calc_rate = (u64)parent_rate << CLK_DIV_FRAC_BITS; + calc_rate = div64_u64(calc_rate, div); + + return calc_rate; +} + +static int rp1_clock_on(struct clk_hw *hw) +{ + struct rp1_clock *clock = container_of(hw, struct rp1_clock, hw); + struct rp1_clockman *clockman = clock->clockman; + const struct rp1_clock_data *data = clock->data; + + spin_lock(&clockman->regs_lock); + clockman_write(clockman, data->ctrl_reg, + clockman_read(clockman, data->ctrl_reg) | CLK_CTRL_ENABLE); + /* If this is a GPCLK, turn on the output-enable */ + if (data->oe_mask) + clockman_write(clockman, GPCLK_OE_CTRL, + clockman_read(clockman, GPCLK_OE_CTRL) | data->oe_mask); + spin_unlock(&clockman->regs_lock); + +#ifdef MEASURE_CLOCK_RATE + clockman_measure_clock(clockman, data->name, data->fc0_src); +#endif + return 0; +} + +static void rp1_clock_off(struct clk_hw *hw) +{ + struct rp1_clock *clock = container_of(hw, struct rp1_clock, hw); + struct rp1_clockman *clockman = clock->clockman; + const struct rp1_clock_data *data = clock->data; + + spin_lock(&clockman->regs_lock); + clockman_write(clockman, data->ctrl_reg, + clockman_read(clockman, data->ctrl_reg) & ~CLK_CTRL_ENABLE); + /* If this is a GPCLK, turn off the output-enable */ + if (data->oe_mask) + clockman_write(clockman, GPCLK_OE_CTRL, + clockman_read(clockman, GPCLK_OE_CTRL) & ~data->oe_mask); + spin_unlock(&clockman->regs_lock); +} + +static u32 rp1_clock_choose_div(unsigned long rate, unsigned long parent_rate, + const struct rp1_clock_data *data) +{ + u64 div; + + /* + * Due to earlier rounding, calculated parent_rate may differ from + * expected value. Don't fail on a small discrepancy near unity divide. + */ + if (!rate || rate > parent_rate + (parent_rate >> CLK_DIV_FRAC_BITS)) + return 0; + + /* + * Always express div in fixed-point format for fractional division; + * If no fractional divider is present, the fraction part will be zero. + */ + if (data->div_frac_reg) { + div = (u64)parent_rate << CLK_DIV_FRAC_BITS; + div = DIV_U64_NEAREST(div, rate); + } else { + div = DIV_U64_NEAREST(parent_rate, rate); + div <<= CLK_DIV_FRAC_BITS; + } + + div = clamp(div, + 1ull << CLK_DIV_FRAC_BITS, + (u64)data->div_int_max << CLK_DIV_FRAC_BITS); + + return div; +} + +static u8 rp1_clock_get_parent(struct clk_hw *hw) +{ + struct rp1_clock *clock = container_of(hw, struct rp1_clock, hw); + struct rp1_clockman *clockman = clock->clockman; + const struct rp1_clock_data *data = clock->data; + u32 sel, ctrl; + u8 parent; + + /* Sel is one-hot, so find the first bit set */ + sel = clockman_read(clockman, data->sel_reg); + parent = ffs(sel) - 1; + + /* sel == 0 implies the parent clock is not enabled yet. */ + if (!sel) { + /* Read the clock src from the CTRL register instead */ + ctrl = clockman_read(clockman, data->ctrl_reg); + parent = (ctrl & data->clk_src_mask) >> CLK_CTRL_SRC_SHIFT; + } + + if (parent >= data->num_std_parents) + parent = AUX_SEL; + + if (parent == AUX_SEL) { + /* + * Clock parent is an auxiliary source, so get the parent from + * the AUXSRC register field. + */ + ctrl = clockman_read(clockman, data->ctrl_reg); + parent = (ctrl & CLK_CTRL_AUXSRC_MASK) >> CLK_CTRL_AUXSRC_SHIFT; + parent += data->num_std_parents; + } + + return parent; +} + +static int rp1_clock_set_parent(struct clk_hw *hw, u8 index) +{ + struct rp1_clock *clock = container_of(hw, struct rp1_clock, hw); + struct rp1_clockman *clockman = clock->clockman; + const struct rp1_clock_data *data = clock->data; + u32 ctrl, sel; + + spin_lock(&clockman->regs_lock); + ctrl = clockman_read(clockman, data->ctrl_reg); + + if (index >= data->num_std_parents) { + /* This is an aux source request */ + if (index >= data->num_std_parents + data->num_aux_parents) + return -EINVAL; + + /* Select parent from aux list */ + ctrl = set_register_field(ctrl, index - data->num_std_parents, + CLK_CTRL_AUXSRC_MASK, + CLK_CTRL_AUXSRC_SHIFT); + /* Set src to aux list */ + ctrl = set_register_field(ctrl, AUX_SEL, data->clk_src_mask, + CLK_CTRL_SRC_SHIFT); + } else { + ctrl = set_register_field(ctrl, index, data->clk_src_mask, + CLK_CTRL_SRC_SHIFT); + } + + clockman_write(clockman, data->ctrl_reg, ctrl); + spin_unlock(&clockman->regs_lock); + + sel = rp1_clock_get_parent(hw); + WARN(sel != index, "(%s): Parent index req %u returned back %u\n", + data->name, index, sel); + + return 0; +} + +static int rp1_clock_set_rate_and_parent(struct clk_hw *hw, + unsigned long rate, + unsigned long parent_rate, + u8 parent) +{ + struct rp1_clock *clock = container_of(hw, struct rp1_clock, hw); + struct rp1_clockman *clockman = clock->clockman; + const struct rp1_clock_data *data = clock->data; + u32 div = rp1_clock_choose_div(rate, parent_rate, data); + + WARN(rate > 4000000000ll, "rate is -ve (%d)\n", (int)rate); + + if (WARN(!div, + "clk divider calculated as 0! (%s, rate %ld, parent rate %ld)\n", + data->name, rate, parent_rate)) + div = 1 << CLK_DIV_FRAC_BITS; + + spin_lock(&clockman->regs_lock); + + clockman_write(clockman, data->div_int_reg, div >> CLK_DIV_FRAC_BITS); + if (data->div_frac_reg) + clockman_write(clockman, data->div_frac_reg, div << (32 - CLK_DIV_FRAC_BITS)); + + spin_unlock(&clockman->regs_lock); + + if (parent != 0xff) + rp1_clock_set_parent(hw, parent); + +#ifdef MEASURE_CLOCK_RATE + if (rp1_clock_is_on(hw)) + clockman_measure_clock(clockman, data->name, data->fc0_src); +#endif + return 0; +} + +static int rp1_clock_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + return rp1_clock_set_rate_and_parent(hw, rate, parent_rate, 0xff); +} + +static unsigned long calc_core_pll_rate(struct clk_hw *pll_hw, + unsigned long target_rate, + int *pdiv_prim, int *pdiv_clk) +{ + static const int prim_divs[] = { + 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 15, 16, + 18, 20, 21, 24, 25, 28, 30, 35, 36, 42, 49, + }; + const unsigned long xosc_rate = clk_hw_get_rate(clk_xosc); + const unsigned long core_max = 2400000000; + const unsigned long core_min = xosc_rate * 16; + unsigned long best_rate = core_max + 1; + int best_div_prim = 1, best_div_clk = 1; + unsigned long core_rate = 0; + int div_int, div_frac; + u64 div; + int i; + + /* Given the target rate, choose a set of divisors/multipliers */ + for (i = 0; i < ARRAY_SIZE(prim_divs); i++) { + int div_prim = prim_divs[i]; + int div_clk; + + for (div_clk = 1; div_clk <= 256; div_clk++) { + core_rate = target_rate * div_clk * div_prim; + if (core_rate >= core_min) { + if (core_rate < best_rate) { + best_rate = core_rate; + best_div_prim = div_prim; + best_div_clk = div_clk; + } + break; + } + } + } + + if (best_rate < core_max) { + div = ((best_rate << 24) + xosc_rate / 2) / xosc_rate; + div_int = div >> 24; + div_frac = div % (1 << 24); + core_rate = (xosc_rate * ((div_int << 24) + div_frac) + (1 << 23)) >> 24; + } else { + core_rate = 0; + } + + if (pdiv_prim) + *pdiv_prim = best_div_prim; + if (pdiv_clk) + *pdiv_clk = best_div_clk; + + return core_rate; +} + +static void rp1_clock_choose_div_and_prate(struct clk_hw *hw, + int parent_idx, + unsigned long rate, + unsigned long *prate, + unsigned long *calc_rate) +{ + struct rp1_clock *clock = container_of(hw, struct rp1_clock, hw); + const struct rp1_clock_data *data = clock->data; + struct clk_hw *parent; + u32 div; + u64 tmp; + int i; + + parent = clk_hw_get_parent_by_index(hw, parent_idx); + + for (i = 0; i < ARRAY_SIZE(rp1_clk_chg_tree); i++) { + const struct rp1_clk_change *chg = &rp1_clk_chg_tree[i]; + + if (chg->hw == hw && chg->new_rate == rate) { + if (i == 2) + *prate = clk_hw_get_rate(clk_xosc); + else if (parent == rp1_clk_chg_tree[i + 1].hw) + *prate = rp1_clk_chg_tree[i + 1].new_rate; + else + continue; + *calc_rate = chg->new_rate; + return; + } + } + + if (hw == clk_i2s && parent == clk_audio) { + unsigned long core_rate, audio_rate, i2s_rate; + int div_prim, div_clk; + + core_rate = calc_core_pll_rate(parent, rate, &div_prim, &div_clk); + audio_rate = DIV_NEAREST(core_rate, div_prim); + i2s_rate = DIV_NEAREST(audio_rate, div_clk); + rp1_clk_chg_tree[2].hw = clk_hw_get_parent(parent); + rp1_clk_chg_tree[2].new_rate = core_rate; + rp1_clk_chg_tree[1].hw = clk_audio; + rp1_clk_chg_tree[1].new_rate = audio_rate; + rp1_clk_chg_tree[0].hw = clk_i2s; + rp1_clk_chg_tree[0].new_rate = i2s_rate; + *prate = audio_rate; + *calc_rate = i2s_rate; + return; + } + + *prate = clk_hw_get_rate(parent); + div = rp1_clock_choose_div(rate, *prate, data); + + if (!div) { + *calc_rate = 0; + return; + } + + /* Recalculate to account for rounding errors */ + tmp = (u64)*prate << CLK_DIV_FRAC_BITS; + tmp = div_u64(tmp, div); + /* + * Prevent overclocks - if all parent choices result in + * a downstream clock in excess of the maximum, then the + * call to set the clock will fail. But due to round-to- + * nearest in the PLL core (which has 24 fractional bits), + * it's expedient to tolerate a tiny error (1Hz/33MHz). + */ + if (tmp > clock->data->max_freq + (clock->data->max_freq >> 25)) + *calc_rate = 0; + else + *calc_rate = tmp; +} + +static int rp1_clock_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct clk_hw *parent, *best_parent = NULL; + unsigned long best_rate = 0; + unsigned long best_prate = 0; + unsigned long best_rate_diff = ULONG_MAX; + unsigned long prate, calc_rate; + size_t i; + + /* + * If the NO_REPARENT flag is set, try to use existing parent. + */ + if ((clk_hw_get_flags(hw) & CLK_SET_RATE_NO_REPARENT)) { + i = rp1_clock_get_parent(hw); + parent = clk_hw_get_parent_by_index(hw, i); + if (parent) { + rp1_clock_choose_div_and_prate(hw, i, req->rate, &prate, + &calc_rate); + if (calc_rate > 0) { + req->best_parent_hw = parent; + req->best_parent_rate = prate; + req->rate = calc_rate; + return 0; + } + } + } + + /* + * Select parent clock that results in the closest rate (lower or + * higher) + */ + for (i = 0; i < clk_hw_get_num_parents(hw); i++) { + parent = clk_hw_get_parent_by_index(hw, i); + if (!parent) + continue; + + rp1_clock_choose_div_and_prate(hw, i, req->rate, &prate, + &calc_rate); + + if (ABS_DIFF(calc_rate, req->rate) < best_rate_diff) { + best_parent = parent; + best_prate = prate; + best_rate = calc_rate; + best_rate_diff = ABS_DIFF(calc_rate, req->rate); + + if (best_rate_diff == 0) + break; + } + } + + if (best_rate == 0) + return -EINVAL; + + req->best_parent_hw = best_parent; + req->best_parent_rate = best_prate; + req->rate = best_rate; + + return 0; +} + +static void rp1_clk_debug_init(struct clk_hw *hw, struct dentry *dentry) +{ + struct rp1_clock *clock = container_of(hw, struct rp1_clock, hw); + struct rp1_clockman *clockman = clock->clockman; + const struct rp1_clock_data *data = clock->data; + struct debugfs_reg32 *regs; + int i; + + regs = devm_kcalloc(clockman->dev, 4, sizeof(*regs), GFP_KERNEL); + if (!regs) + return; + + i = 0; + regs[i].name = "ctrl"; + regs[i++].offset = data->ctrl_reg; + regs[i].name = "div_int"; + regs[i++].offset = data->div_int_reg; + regs[i].name = "div_frac"; + regs[i++].offset = data->div_frac_reg; + regs[i].name = "sel"; + regs[i++].offset = data->sel_reg; + + rp1_debugfs_regset(clockman, 0, regs, i, dentry); +} + +static int rp1_varsrc_set_rate(struct clk_hw *hw, + unsigned long rate, unsigned long parent_rate) +{ + struct rp1_varsrc *varsrc = container_of(hw, struct rp1_varsrc, hw); + + /* + * "varsrc" exists purely to let clock dividers know the frequency + * of an externally-managed clock source (such as MIPI DSI byte-clock) + * which may change at run-time as a side-effect of some other driver. + */ + varsrc->rate = rate; + return 0; +} + +static unsigned long rp1_varsrc_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct rp1_varsrc *varsrc = container_of(hw, struct rp1_varsrc, hw); + + return varsrc->rate; +} + +static long rp1_varsrc_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + return rate; +} + +static const struct clk_ops rp1_pll_core_ops = { + .is_prepared = rp1_pll_core_is_on, + .prepare = rp1_pll_core_on, + .unprepare = rp1_pll_core_off, + .set_rate = rp1_pll_core_set_rate, + .recalc_rate = rp1_pll_core_recalc_rate, + .round_rate = rp1_pll_core_round_rate, + .debug_init = rp1_pll_core_debug_init, +}; + +static const struct clk_ops rp1_pll_ops = { + .set_rate = rp1_pll_set_rate, + .recalc_rate = rp1_pll_recalc_rate, + .round_rate = rp1_pll_round_rate, + .debug_init = rp1_pll_debug_init, +}; + +static const struct clk_ops rp1_pll_ph_ops = { + .is_prepared = rp1_pll_ph_is_on, + .prepare = rp1_pll_ph_on, + .unprepare = rp1_pll_ph_off, + .set_rate = rp1_pll_ph_set_rate, + .recalc_rate = rp1_pll_ph_recalc_rate, + .round_rate = rp1_pll_ph_round_rate, + .debug_init = rp1_pll_ph_debug_init, +}; + +static const struct clk_ops rp1_pll_divider_ops = { + .is_prepared = rp1_pll_divider_is_on, + .prepare = rp1_pll_divider_on, + .unprepare = rp1_pll_divider_off, + .set_rate = rp1_pll_divider_set_rate, + .recalc_rate = rp1_pll_divider_recalc_rate, + .round_rate = rp1_pll_divider_round_rate, + .debug_init = rp1_pll_divider_debug_init, +}; + +static const struct clk_ops rp1_clk_ops = { + .is_prepared = rp1_clock_is_on, + .prepare = rp1_clock_on, + .unprepare = rp1_clock_off, + .recalc_rate = rp1_clock_recalc_rate, + .get_parent = rp1_clock_get_parent, + .set_parent = rp1_clock_set_parent, + .set_rate_and_parent = rp1_clock_set_rate_and_parent, + .set_rate = rp1_clock_set_rate, + .determine_rate = rp1_clock_determine_rate, + .debug_init = rp1_clk_debug_init, +}; + +static const struct clk_ops rp1_varsrc_ops = { + .set_rate = rp1_varsrc_set_rate, + .recalc_rate = rp1_varsrc_recalc_rate, + .round_rate = rp1_varsrc_round_rate, +}; + +static struct clk_hw *rp1_register_pll_core(struct rp1_clockman *clockman, + const void *data) +{ + const struct rp1_pll_core_data *pll_core_data = data; + struct rp1_pll_core *pll_core; + struct clk_init_data init; + int ret; + + memset(&init, 0, sizeof(init)); + + /* All of the PLL cores derive from the external oscillator. */ + init.parent_names = &ref_clock; + init.num_parents = 1; + init.name = pll_core_data->name; + init.ops = &rp1_pll_core_ops; + init.flags = pll_core_data->flags | CLK_IS_CRITICAL; + + pll_core = kzalloc(sizeof(*pll_core), GFP_KERNEL); + if (!pll_core) + return NULL; + + pll_core->clockman = clockman; + pll_core->data = pll_core_data; + pll_core->hw.init = &init; + + ret = devm_clk_hw_register(clockman->dev, &pll_core->hw); + if (ret) { + kfree(pll_core); + return NULL; + } + + return &pll_core->hw; +} + +static struct clk_hw *rp1_register_pll(struct rp1_clockman *clockman, + const void *data) +{ + const struct rp1_pll_data *pll_data = data; + struct rp1_pll *pll; + struct clk_init_data init; + int ret; + + memset(&init, 0, sizeof(init)); + + init.parent_names = &pll_data->source_pll; + init.num_parents = 1; + init.name = pll_data->name; + init.ops = &rp1_pll_ops; + init.flags = pll_data->flags; + + pll = kzalloc(sizeof(*pll), GFP_KERNEL); + if (!pll) + return NULL; + + pll->clockman = clockman; + pll->data = pll_data; + pll->hw.init = &init; + + ret = devm_clk_hw_register(clockman->dev, &pll->hw); + if (ret) { + kfree(pll); + return NULL; + } + + return &pll->hw; +} + +static struct clk_hw *rp1_register_pll_ph(struct rp1_clockman *clockman, + const void *data) +{ + const struct rp1_pll_ph_data *ph_data = data; + struct rp1_pll_ph *ph; + struct clk_init_data init; + int ret; + + memset(&init, 0, sizeof(init)); + + /* All of the PLLs derive from the external oscillator. */ + init.parent_names = &ph_data->source_pll; + init.num_parents = 1; + init.name = ph_data->name; + init.ops = &rp1_pll_ph_ops; + init.flags = ph_data->flags; + + ph = kzalloc(sizeof(*ph), GFP_KERNEL); + if (!ph) + return NULL; + + ph->clockman = clockman; + ph->data = ph_data; + ph->hw.init = &init; + + ret = devm_clk_hw_register(clockman->dev, &ph->hw); + if (ret) { + kfree(ph); + return NULL; + } + + return &ph->hw; +} + +static struct clk_hw *rp1_register_pll_divider(struct rp1_clockman *clockman, + const void *data) +{ + const struct rp1_pll_data *divider_data = data; + struct rp1_pll *divider; + struct clk_init_data init; + int ret; + + memset(&init, 0, sizeof(init)); + + init.parent_names = ÷r_data->source_pll; + init.num_parents = 1; + init.name = divider_data->name; + init.ops = &rp1_pll_divider_ops; + init.flags = divider_data->flags; + + divider = devm_kzalloc(clockman->dev, sizeof(*divider), GFP_KERNEL); + if (!divider) + return NULL; + + divider->div.reg = clockman->regs + divider_data->ctrl_reg; + divider->div.shift = PLL_SEC_DIV_SHIFT; + divider->div.width = PLL_SEC_DIV_WIDTH; + divider->div.flags = CLK_DIVIDER_ROUND_CLOSEST; + divider->div.lock = &clockman->regs_lock; + divider->div.hw.init = &init; + divider->div.table = pll_sec_div_table; + + divider->clockman = clockman; + divider->data = divider_data; + + ret = devm_clk_hw_register(clockman->dev, ÷r->div.hw); + if (ret) + return ERR_PTR(ret); + + return ÷r->div.hw; +} + +static struct clk_hw *rp1_register_clock(struct rp1_clockman *clockman, + const void *data) +{ + const struct rp1_clock_data *clock_data = data; + struct rp1_clock *clock; + struct clk_init_data init; + int ret; + + BUG_ON(MAX_CLK_PARENTS < + clock_data->num_std_parents + clock_data->num_aux_parents); + /* There must be a gap for the AUX selector */ + BUG_ON((clock_data->num_std_parents > AUX_SEL) && + strcmp("-", clock_data->parents[AUX_SEL])); + + memset(&init, 0, sizeof(init)); + init.parent_names = clock_data->parents; + init.num_parents = + clock_data->num_std_parents + clock_data->num_aux_parents; + init.name = clock_data->name; + init.flags = clock_data->flags; + init.ops = &rp1_clk_ops; + + clock = devm_kzalloc(clockman->dev, sizeof(*clock), GFP_KERNEL); + if (!clock) + return NULL; + + clock->clockman = clockman; + clock->data = clock_data; + clock->hw.init = &init; + + ret = devm_clk_hw_register(clockman->dev, &clock->hw); + if (ret) + return ERR_PTR(ret); + + return &clock->hw; +} + +static struct clk_hw *rp1_register_varsrc(struct rp1_clockman *clockman, + const void *data) +{ + const char *name = *(char const * const *)data; + struct rp1_varsrc *clock; + struct clk_init_data init; + int ret; + + memset(&init, 0, sizeof(init)); + init.parent_names = &ref_clock; + init.num_parents = 1; + init.name = name; + init.ops = &rp1_varsrc_ops; + + clock = devm_kzalloc(clockman->dev, sizeof(*clock), GFP_KERNEL); + if (!clock) + return NULL; + + clock->clockman = clockman; + clock->hw.init = &init; + + ret = devm_clk_hw_register(clockman->dev, &clock->hw); + if (ret) + return ERR_PTR(ret); + + return &clock->hw; +} + +struct rp1_clk_desc { + struct clk_hw *(*clk_register)(struct rp1_clockman *clockman, + const void *data); + const void *data; +}; + +/* Assignment helper macros for different clock types. */ +#define _REGISTER(f, ...) { .clk_register = f, .data = __VA_ARGS__ } + +#define REGISTER_PLL_CORE(...) _REGISTER(&rp1_register_pll_core, \ + &(struct rp1_pll_core_data) \ + {__VA_ARGS__}) + +#define REGISTER_PLL(...) _REGISTER(&rp1_register_pll, \ + &(struct rp1_pll_data) \ + {__VA_ARGS__}) + +#define REGISTER_PLL_PH(...) _REGISTER(&rp1_register_pll_ph, \ + &(struct rp1_pll_ph_data) \ + {__VA_ARGS__}) + +#define REGISTER_PLL_DIV(...) _REGISTER(&rp1_register_pll_divider, \ + &(struct rp1_pll_data) \ + {__VA_ARGS__}) + +#define REGISTER_CLK(...) _REGISTER(&rp1_register_clock, \ + &(struct rp1_clock_data) \ + {__VA_ARGS__}) + +#define REGISTER_VARSRC(n) _REGISTER(&rp1_register_varsrc, &(const char *){n}) + +static const struct rp1_clk_desc clk_desc_array[] = { + [RP1_PLL_SYS_CORE] = REGISTER_PLL_CORE( + .name = "pll_sys_core", + .cs_reg = PLL_SYS_CS, + .pwr_reg = PLL_SYS_PWR, + .fbdiv_int_reg = PLL_SYS_FBDIV_INT, + .fbdiv_frac_reg = PLL_SYS_FBDIV_FRAC, + ), + + [RP1_PLL_AUDIO_CORE] = REGISTER_PLL_CORE( + .name = "pll_audio_core", + .cs_reg = PLL_AUDIO_CS, + .pwr_reg = PLL_AUDIO_PWR, + .fbdiv_int_reg = PLL_AUDIO_FBDIV_INT, + .fbdiv_frac_reg = PLL_AUDIO_FBDIV_FRAC, + ), + + [RP1_PLL_VIDEO_CORE] = REGISTER_PLL_CORE( + .name = "pll_video_core", + .cs_reg = PLL_VIDEO_CS, + .pwr_reg = PLL_VIDEO_PWR, + .fbdiv_int_reg = PLL_VIDEO_FBDIV_INT, + .fbdiv_frac_reg = PLL_VIDEO_FBDIV_FRAC, + ), + + [RP1_PLL_SYS] = REGISTER_PLL( + .name = "pll_sys", + .source_pll = "pll_sys_core", + .ctrl_reg = PLL_SYS_PRIM, + .fc0_src = FC_NUM(0, 2), + ), + + [RP1_PLL_AUDIO] = REGISTER_PLL( + .name = "pll_audio", + .source_pll = "pll_audio_core", + .ctrl_reg = PLL_AUDIO_PRIM, + .fc0_src = FC_NUM(4, 2), + .flags = CLK_SET_RATE_PARENT, + ), + + [RP1_PLL_VIDEO] = REGISTER_PLL( + .name = "pll_video", + .source_pll = "pll_video_core", + .ctrl_reg = PLL_VIDEO_PRIM, + .fc0_src = FC_NUM(3, 2), + ), + + [RP1_PLL_SYS_PRI_PH] = REGISTER_PLL_PH( + .name = "pll_sys_pri_ph", + .source_pll = "pll_sys", + .ph_reg = PLL_SYS_PRIM, + .fixed_divider = 2, + .phase = RP1_PLL_PHASE_0, + .fc0_src = FC_NUM(1, 2), + ), + + [RP1_PLL_AUDIO_PRI_PH] = REGISTER_PLL_PH( + .name = "pll_audio_pri_ph", + .source_pll = "pll_audio", + .ph_reg = PLL_AUDIO_PRIM, + .fixed_divider = 2, + .phase = RP1_PLL_PHASE_0, + .fc0_src = FC_NUM(5, 1), + ), + + [RP1_PLL_VIDEO_PRI_PH] = REGISTER_PLL_PH( + .name = "pll_video_pri_ph", + .source_pll = "pll_video", + .ph_reg = PLL_VIDEO_PRIM, + .fixed_divider = 2, + .phase = RP1_PLL_PHASE_0, + .fc0_src = FC_NUM(4, 3), + ), + + [RP1_PLL_SYS_SEC] = REGISTER_PLL_DIV( + .name = "pll_sys_sec", + .source_pll = "pll_sys_core", + .ctrl_reg = PLL_SYS_SEC, + .fc0_src = FC_NUM(2, 2), + ), + + [RP1_PLL_AUDIO_SEC] = REGISTER_PLL_DIV( + .name = "pll_audio_sec", + .source_pll = "pll_audio_core", + .ctrl_reg = PLL_AUDIO_SEC, + .fc0_src = FC_NUM(6, 2), + ), + + [RP1_PLL_VIDEO_SEC] = REGISTER_PLL_DIV( + .name = "pll_video_sec", + .source_pll = "pll_video_core", + .ctrl_reg = PLL_VIDEO_SEC, + .fc0_src = FC_NUM(5, 3), + ), + + [RP1_PLL_AUDIO_TERN] = REGISTER_PLL_DIV( + .name = "pll_audio_tern", + .source_pll = "pll_audio_core", + .ctrl_reg = PLL_AUDIO_TERN, + .fc0_src = FC_NUM(6, 2), + ), + + [RP1_CLK_SYS] = REGISTER_CLK( + .name = "clk_sys", + .parents = {"xosc", "-", "pll_sys"}, + .num_std_parents = 3, + .num_aux_parents = 0, + .ctrl_reg = CLK_SYS_CTRL, + .div_int_reg = CLK_SYS_DIV_INT, + .sel_reg = CLK_SYS_SEL, + .div_int_max = DIV_INT_24BIT_MAX, + .max_freq = 200 * MHz, + .fc0_src = FC_NUM(0, 4), + .clk_src_mask = 0x3, + /* Always enabled in hardware */ + .flags = CLK_IS_CRITICAL, + ), + + [RP1_CLK_SLOW_SYS] = REGISTER_CLK( + .name = "clk_slow_sys", + .parents = {"xosc"}, + .num_std_parents = 1, + .num_aux_parents = 0, + .ctrl_reg = CLK_SLOW_SYS_CTRL, + .div_int_reg = CLK_SLOW_SYS_DIV_INT, + .sel_reg = CLK_SLOW_SYS_SEL, + .div_int_max = DIV_INT_8BIT_MAX, + .max_freq = 50 * MHz, + .fc0_src = FC_NUM(1, 4), + .clk_src_mask = 0x1, + /* Always enabled in hardware */ + .flags = CLK_IS_CRITICAL, + ), + + [RP1_CLK_DMA] = REGISTER_CLK( + .name = "clk_dma", + .parents = {"pll_sys_pri_ph", + "pll_video", + "xosc", + "clksrc_gp0", + "clksrc_gp1", + "clksrc_gp2", + "clksrc_gp3", + "clksrc_gp4", + "clksrc_gp5"}, + .num_std_parents = 0, + .num_aux_parents = 9, + .ctrl_reg = CLK_DMA_CTRL, + .div_int_reg = CLK_DMA_DIV_INT, + .sel_reg = CLK_DMA_SEL, + .div_int_max = DIV_INT_8BIT_MAX, + .max_freq = 100 * MHz, + .fc0_src = FC_NUM(2, 2), + ), + + [RP1_CLK_UART] = REGISTER_CLK( + .name = "clk_uart", + .parents = {"pll_sys_pri_ph", + "pll_video", + "xosc", + "clksrc_gp0", + "clksrc_gp1", + "clksrc_gp2", + "clksrc_gp3", + "clksrc_gp4", + "clksrc_gp5"}, + .num_std_parents = 0, + .num_aux_parents = 9, + .ctrl_reg = CLK_UART_CTRL, + .div_int_reg = CLK_UART_DIV_INT, + .sel_reg = CLK_UART_SEL, + .div_int_max = DIV_INT_8BIT_MAX, + .max_freq = 100 * MHz, + .fc0_src = FC_NUM(6, 7), + ), + + [RP1_CLK_ETH] = REGISTER_CLK( + .name = "clk_eth", + .parents = {"pll_sys_sec", + "pll_sys", + "pll_video_sec", + "clksrc_gp0", + "clksrc_gp1", + "clksrc_gp2", + "clksrc_gp3", + "clksrc_gp4", + "clksrc_gp5"}, + .num_std_parents = 0, + .num_aux_parents = 9, + .ctrl_reg = CLK_ETH_CTRL, + .div_int_reg = CLK_ETH_DIV_INT, + .sel_reg = CLK_ETH_SEL, + .div_int_max = DIV_INT_8BIT_MAX, + .max_freq = 125 * MHz, + .fc0_src = FC_NUM(4, 6), + ), + + [RP1_CLK_PWM0] = REGISTER_CLK( + .name = "clk_pwm0", + .parents = {"", // "pll_audio_pri_ph", + "pll_video_sec", + "xosc", + "clksrc_gp0", + "clksrc_gp1", + "clksrc_gp2", + "clksrc_gp3", + "clksrc_gp4", + "clksrc_gp5"}, + .num_std_parents = 0, + .num_aux_parents = 9, + .ctrl_reg = CLK_PWM0_CTRL, + .div_int_reg = CLK_PWM0_DIV_INT, + .div_frac_reg = CLK_PWM0_DIV_FRAC, + .sel_reg = CLK_PWM0_SEL, + .div_int_max = DIV_INT_16BIT_MAX, + .max_freq = 76800 * KHz, + .fc0_src = FC_NUM(0, 5), + ), + + [RP1_CLK_PWM1] = REGISTER_CLK( + .name = "clk_pwm1", + .parents = {"", // "pll_audio_pri_ph", + "pll_video_sec", + "xosc", + "clksrc_gp0", + "clksrc_gp1", + "clksrc_gp2", + "clksrc_gp3", + "clksrc_gp4", + "clksrc_gp5"}, + .num_std_parents = 0, + .num_aux_parents = 9, + .ctrl_reg = CLK_PWM1_CTRL, + .div_int_reg = CLK_PWM1_DIV_INT, + .div_frac_reg = CLK_PWM1_DIV_FRAC, + .sel_reg = CLK_PWM1_SEL, + .div_int_max = DIV_INT_16BIT_MAX, + .max_freq = 76800 * KHz, + .fc0_src = FC_NUM(1, 5), + ), + + [RP1_CLK_AUDIO_IN] = REGISTER_CLK( + .name = "clk_audio_in", + .parents = {"", //"pll_audio", + "", //"pll_audio_pri_ph", + "", //"pll_audio_sec", + "pll_video_sec", + "xosc", + "clksrc_gp0", + "clksrc_gp1", + "clksrc_gp2", + "clksrc_gp3", + "clksrc_gp4", + "clksrc_gp5"}, + .num_std_parents = 0, + .num_aux_parents = 11, + .ctrl_reg = CLK_AUDIO_IN_CTRL, + .div_int_reg = CLK_AUDIO_IN_DIV_INT, + .sel_reg = CLK_AUDIO_IN_SEL, + .div_int_max = DIV_INT_8BIT_MAX, + .max_freq = 76800 * KHz, + .fc0_src = FC_NUM(2, 5), + ), + + [RP1_CLK_AUDIO_OUT] = REGISTER_CLK( + .name = "clk_audio_out", + .parents = {"", //"pll_audio", + "pll_audio_sec", + "pll_video_sec", + "xosc", + "clksrc_gp0", + "clksrc_gp1", + "clksrc_gp2", + "clksrc_gp3", + "clksrc_gp4", + "clksrc_gp5"}, + .num_std_parents = 0, + .num_aux_parents = 10, + .ctrl_reg = CLK_AUDIO_OUT_CTRL, + .div_int_reg = CLK_AUDIO_OUT_DIV_INT, + .sel_reg = CLK_AUDIO_OUT_SEL, + .div_int_max = DIV_INT_8BIT_MAX, + .max_freq = 153600 * KHz, + .fc0_src = FC_NUM(3, 5), + ), + + [RP1_CLK_I2S] = REGISTER_CLK( + .name = "clk_i2s", + .parents = {"xosc", + "pll_audio", + "pll_audio_sec", + "clksrc_gp0", + "clksrc_gp1", + "clksrc_gp2", + "clksrc_gp3", + "clksrc_gp4", + "clksrc_gp5"}, + .num_std_parents = 0, + .num_aux_parents = 9, + .ctrl_reg = CLK_I2S_CTRL, + .div_int_reg = CLK_I2S_DIV_INT, + .sel_reg = CLK_I2S_SEL, + .div_int_max = DIV_INT_8BIT_MAX, + .max_freq = 50 * MHz, + .fc0_src = FC_NUM(4, 4), + .flags = CLK_SET_RATE_PARENT, + ), + + [RP1_CLK_MIPI0_CFG] = REGISTER_CLK( + .name = "clk_mipi0_cfg", + .parents = {"xosc"}, + .num_std_parents = 0, + .num_aux_parents = 1, + .ctrl_reg = CLK_MIPI0_CFG_CTRL, + .div_int_reg = CLK_MIPI0_CFG_DIV_INT, + .sel_reg = CLK_MIPI0_CFG_SEL, + .div_int_max = DIV_INT_8BIT_MAX, + .max_freq = 50 * MHz, + .fc0_src = FC_NUM(4, 5), + ), + + [RP1_CLK_MIPI1_CFG] = REGISTER_CLK( + .name = "clk_mipi1_cfg", + .parents = {"xosc"}, + .num_std_parents = 0, + .num_aux_parents = 1, + .ctrl_reg = CLK_MIPI1_CFG_CTRL, + .div_int_reg = CLK_MIPI1_CFG_DIV_INT, + .sel_reg = CLK_MIPI1_CFG_SEL, + .clk_src_mask = 1, + .div_int_max = DIV_INT_8BIT_MAX, + .max_freq = 50 * MHz, + .fc0_src = FC_NUM(5, 6), + ), + + [RP1_CLK_ETH_TSU] = REGISTER_CLK( + .name = "clk_eth_tsu", + .parents = {"xosc", + "pll_video_sec", + "clksrc_gp0", + "clksrc_gp1", + "clksrc_gp2", + "clksrc_gp3", + "clksrc_gp4", + "clksrc_gp5"}, + .num_std_parents = 0, + .num_aux_parents = 8, + .ctrl_reg = CLK_ETH_TSU_CTRL, + .div_int_reg = CLK_ETH_TSU_DIV_INT, + .sel_reg = CLK_ETH_TSU_SEL, + .div_int_max = DIV_INT_8BIT_MAX, + .max_freq = 50 * MHz, + .fc0_src = FC_NUM(5, 7), + ), + + [RP1_CLK_ADC] = REGISTER_CLK( + .name = "clk_adc", + .parents = {"xosc", + "", //"pll_audio_tern", + "clksrc_gp0", + "clksrc_gp1", + "clksrc_gp2", + "clksrc_gp3", + "clksrc_gp4", + "clksrc_gp5"}, + .num_std_parents = 0, + .num_aux_parents = 8, + .ctrl_reg = CLK_ADC_CTRL, + .div_int_reg = CLK_ADC_DIV_INT, + .sel_reg = CLK_ADC_SEL, + .div_int_max = DIV_INT_8BIT_MAX, + .max_freq = 50 * MHz, + .fc0_src = FC_NUM(5, 5), + ), + + [RP1_CLK_SDIO_TIMER] = REGISTER_CLK( + .name = "clk_sdio_timer", + .parents = {"xosc"}, + .num_std_parents = 0, + .num_aux_parents = 1, + .ctrl_reg = CLK_SDIO_TIMER_CTRL, + .div_int_reg = CLK_SDIO_TIMER_DIV_INT, + .sel_reg = CLK_SDIO_TIMER_SEL, + .div_int_max = DIV_INT_8BIT_MAX, + .max_freq = 50 * MHz, + .fc0_src = FC_NUM(3, 4), + ), + + [RP1_CLK_SDIO_ALT_SRC] = REGISTER_CLK( + .name = "clk_sdio_alt_src", + .parents = {"pll_sys"}, + .num_std_parents = 0, + .num_aux_parents = 1, + .ctrl_reg = CLK_SDIO_ALT_SRC_CTRL, + .div_int_reg = CLK_SDIO_ALT_SRC_DIV_INT, + .sel_reg = CLK_SDIO_ALT_SRC_SEL, + .div_int_max = DIV_INT_8BIT_MAX, + .max_freq = 200 * MHz, + .fc0_src = FC_NUM(5, 4), + ), + + [RP1_CLK_GP0] = REGISTER_CLK( + .name = "clk_gp0", + .parents = {"xosc", + "clksrc_gp1", + "clksrc_gp2", + "clksrc_gp3", + "clksrc_gp4", + "clksrc_gp5", + "pll_sys", + "", //"pll_audio", + "", + "", + "clk_i2s", + "clk_adc", + "", + "", + "", + "clk_sys"}, + .num_std_parents = 0, + .num_aux_parents = 16, + .oe_mask = BIT(0), + .ctrl_reg = CLK_GP0_CTRL, + .div_int_reg = CLK_GP0_DIV_INT, + .div_frac_reg = CLK_GP0_DIV_FRAC, + .sel_reg = CLK_GP0_SEL, + .div_int_max = DIV_INT_16BIT_MAX, + .max_freq = 100 * MHz, + .fc0_src = FC_NUM(0, 1), + ), + + [RP1_CLK_GP1] = REGISTER_CLK( + .name = "clk_gp1", + .parents = {"clk_sdio_timer", + "clksrc_gp0", + "clksrc_gp2", + "clksrc_gp3", + "clksrc_gp4", + "clksrc_gp5", + "pll_sys_pri_ph", + "", //"pll_audio_pri_ph", + "", + "", + "clk_adc", + "clk_dpi", + "clk_pwm0", + "", + "", + ""}, + .num_std_parents = 0, + .num_aux_parents = 16, + .oe_mask = BIT(1), + .ctrl_reg = CLK_GP1_CTRL, + .div_int_reg = CLK_GP1_DIV_INT, + .div_frac_reg = CLK_GP1_DIV_FRAC, + .sel_reg = CLK_GP1_SEL, + .div_int_max = DIV_INT_16BIT_MAX, + .max_freq = 100 * MHz, + .fc0_src = FC_NUM(1, 1), + ), + + [RP1_CLK_GP2] = REGISTER_CLK( + .name = "clk_gp2", + .parents = {"clk_sdio_alt_src", + "clksrc_gp0", + "clksrc_gp1", + "clksrc_gp3", + "clksrc_gp4", + "clksrc_gp5", + "pll_sys_sec", + "", //"pll_audio_sec", + "pll_video", + "clk_audio_in", + "clk_dpi", + "clk_pwm0", + "clk_pwm1", + "clk_mipi0_dpi", + "clk_mipi1_cfg", + "clk_sys"}, + .num_std_parents = 0, + .num_aux_parents = 16, + .oe_mask = BIT(2), + .ctrl_reg = CLK_GP2_CTRL, + .div_int_reg = CLK_GP2_DIV_INT, + .div_frac_reg = CLK_GP2_DIV_FRAC, + .sel_reg = CLK_GP2_SEL, + .div_int_max = DIV_INT_16BIT_MAX, + .max_freq = 100 * MHz, + .fc0_src = FC_NUM(2, 1), + ), + + [RP1_CLK_GP3] = REGISTER_CLK( + .name = "clk_gp3", + .parents = {"xosc", + "clksrc_gp0", + "clksrc_gp1", + "clksrc_gp2", + "clksrc_gp4", + "clksrc_gp5", + "", + "", + "pll_video_pri_ph", + "clk_audio_out", + "", + "", + "clk_mipi1_dpi", + "", + "", + ""}, + .num_std_parents = 0, + .num_aux_parents = 16, + .oe_mask = BIT(3), + .ctrl_reg = CLK_GP3_CTRL, + .div_int_reg = CLK_GP3_DIV_INT, + .div_frac_reg = CLK_GP3_DIV_FRAC, + .sel_reg = CLK_GP3_SEL, + .div_int_max = DIV_INT_16BIT_MAX, + .max_freq = 100 * MHz, + .fc0_src = FC_NUM(3, 1), + ), + + [RP1_CLK_GP4] = REGISTER_CLK( + .name = "clk_gp4", + .parents = {"xosc", + "clksrc_gp0", + "clksrc_gp1", + "clksrc_gp2", + "clksrc_gp3", + "clksrc_gp5", + "", //"pll_audio_tern", + "pll_video_sec", + "", + "", + "", + "clk_mipi0_cfg", + "clk_uart", + "", + "", + "clk_sys", + }, + .num_std_parents = 0, + .num_aux_parents = 16, + .oe_mask = BIT(4), + .ctrl_reg = CLK_GP4_CTRL, + .div_int_reg = CLK_GP4_DIV_INT, + .div_frac_reg = CLK_GP4_DIV_FRAC, + .sel_reg = CLK_GP4_SEL, + .div_int_max = DIV_INT_16BIT_MAX, + .max_freq = 100 * MHz, + .fc0_src = FC_NUM(4, 1), + ), + + [RP1_CLK_GP5] = REGISTER_CLK( + .name = "clk_gp5", + .parents = {"xosc", + "clksrc_gp0", + "clksrc_gp1", + "clksrc_gp2", + "clksrc_gp3", + "clksrc_gp4", + "", //"pll_audio_tern", + "pll_video_sec", + "clk_eth_tsu", + "", + "clk_vec", + "", + "", + "", + "", + ""}, + .num_std_parents = 0, + .num_aux_parents = 16, + .oe_mask = BIT(5), + .ctrl_reg = CLK_GP5_CTRL, + .div_int_reg = CLK_GP5_DIV_INT, + .div_frac_reg = CLK_GP5_DIV_FRAC, + .sel_reg = CLK_GP5_SEL, + .div_int_max = DIV_INT_16BIT_MAX, + .max_freq = 100 * MHz, + .fc0_src = FC_NUM(5, 1), + ), + + [RP1_CLK_VEC] = REGISTER_CLK( + .name = "clk_vec", + .parents = {"pll_sys_pri_ph", + "pll_video_sec", + "pll_video", + "clksrc_gp0", + "clksrc_gp1", + "clksrc_gp2", + "clksrc_gp3", + "clksrc_gp4"}, + .num_std_parents = 0, + .num_aux_parents = 8, /* XXX in fact there are more than 8 */ + .ctrl_reg = VIDEO_CLK_VEC_CTRL, + .div_int_reg = VIDEO_CLK_VEC_DIV_INT, + .sel_reg = VIDEO_CLK_VEC_SEL, + .flags = CLK_SET_RATE_NO_REPARENT, /* Let VEC driver set parent */ + .div_int_max = DIV_INT_8BIT_MAX, + .max_freq = 108 * MHz, + .fc0_src = FC_NUM(0, 6), + ), + + [RP1_CLK_DPI] = REGISTER_CLK( + .name = "clk_dpi", + .parents = {"pll_sys", + "pll_video_sec", + "pll_video", + "clksrc_gp0", + "clksrc_gp1", + "clksrc_gp2", + "clksrc_gp3", + "clksrc_gp4"}, + .num_std_parents = 0, + .num_aux_parents = 8, /* XXX in fact there are more than 8 */ + .ctrl_reg = VIDEO_CLK_DPI_CTRL, + .div_int_reg = VIDEO_CLK_DPI_DIV_INT, + .sel_reg = VIDEO_CLK_DPI_SEL, + .flags = CLK_SET_RATE_NO_REPARENT, /* Let DPI driver set parent */ + .div_int_max = DIV_INT_8BIT_MAX, + .max_freq = 200 * MHz, + .fc0_src = FC_NUM(1, 6), + ), + + [RP1_CLK_MIPI0_DPI] = REGISTER_CLK( + .name = "clk_mipi0_dpi", + .parents = {"pll_sys", + "pll_video_sec", + "pll_video", + "clksrc_mipi0_dsi_byteclk", + "clksrc_gp0", + "clksrc_gp1", + "clksrc_gp2", + "clksrc_gp3"}, + .num_std_parents = 0, + .num_aux_parents = 8, /* XXX in fact there are more than 8 */ + .ctrl_reg = VIDEO_CLK_MIPI0_DPI_CTRL, + .div_int_reg = VIDEO_CLK_MIPI0_DPI_DIV_INT, + .div_frac_reg = VIDEO_CLK_MIPI0_DPI_DIV_FRAC, + .sel_reg = VIDEO_CLK_MIPI0_DPI_SEL, + .flags = CLK_SET_RATE_NO_REPARENT, /* Let DSI driver set parent */ + .div_int_max = DIV_INT_8BIT_MAX, + .max_freq = 200 * MHz, + .fc0_src = FC_NUM(2, 6), + ), + + [RP1_CLK_MIPI1_DPI] = REGISTER_CLK( + .name = "clk_mipi1_dpi", + .parents = {"pll_sys", + "pll_video_sec", + "pll_video", + "clksrc_mipi1_dsi_byteclk", + "clksrc_gp0", + "clksrc_gp1", + "clksrc_gp2", + "clksrc_gp3"}, + .num_std_parents = 0, + .num_aux_parents = 8, /* XXX in fact there are more than 8 */ + .ctrl_reg = VIDEO_CLK_MIPI1_DPI_CTRL, + .div_int_reg = VIDEO_CLK_MIPI1_DPI_DIV_INT, + .div_frac_reg = VIDEO_CLK_MIPI1_DPI_DIV_FRAC, + .sel_reg = VIDEO_CLK_MIPI1_DPI_SEL, + .flags = CLK_SET_RATE_NO_REPARENT, /* Let DSI driver set parent */ + .div_int_max = DIV_INT_8BIT_MAX, + .max_freq = 200 * MHz, + .fc0_src = FC_NUM(3, 6), + ), + + [RP1_CLK_MIPI0_DSI_BYTECLOCK] = REGISTER_VARSRC("clksrc_mipi0_dsi_byteclk"), + [RP1_CLK_MIPI1_DSI_BYTECLOCK] = REGISTER_VARSRC("clksrc_mipi1_dsi_byteclk"), +}; + +static int rp1_clk_probe(struct platform_device *pdev) +{ + const struct rp1_clk_desc *desc; + struct device *dev = &pdev->dev; + struct rp1_clockman *clockman; + struct resource *res; + struct clk_hw **hws; + const size_t asize = ARRAY_SIZE(clk_desc_array); + u32 chip_id, platform; + unsigned int i; + int ret; + + clockman = devm_kzalloc(dev, struct_size(clockman, onecell.hws, asize), + GFP_KERNEL); + if (!clockman) + return -ENOMEM; + + rp1_get_platform(&chip_id, &platform); + + spin_lock_init(&clockman->regs_lock); + clockman->dev = dev; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + clockman->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(clockman->regs)) + return PTR_ERR(clockman->regs); + + platform_set_drvdata(pdev, clockman); + + clockman->onecell.num = asize; + hws = clockman->onecell.hws; + + for (i = 0; i < asize; i++) { + desc = &clk_desc_array[i]; + if (desc->clk_register && desc->data) { + hws[i] = desc->clk_register(clockman, desc->data); + if (IS_ERR_OR_NULL(hws[i])) { + pr_err("Failed to register RP1 clock '%s' (%ld) - wrong dtbs?\n", *(char **)desc->data, PTR_ERR(hws[i])); + hws[i] = NULL; + continue; + } + if (!strcmp(clk_hw_get_name(hws[i]), "clk_i2s")) { + clk_i2s = hws[i]; + clk_xosc = clk_hw_get_parent_by_index(clk_i2s, 0); + clk_audio = clk_hw_get_parent_by_index(clk_i2s, 1); + } + } + } + + ret = of_clk_add_hw_provider(dev->of_node, of_clk_hw_onecell_get, + &clockman->onecell); + if (ret) + return ret; + + return 0; +} + +static const struct of_device_id rp1_clk_of_match[] = { + { .compatible = "raspberrypi,rp1-clocks" }, + {} +}; +MODULE_DEVICE_TABLE(of, rp1_clk_of_match); + +static struct platform_driver rp1_clk_driver = { + .driver = { + .name = "rp1-clk", + .of_match_table = rp1_clk_of_match, + }, + .probe = rp1_clk_probe, +}; + +static int __init __rp1_clk_driver_init(void) +{ + return platform_driver_register(&rp1_clk_driver); +} +postcore_initcall(__rp1_clk_driver_init); + +MODULE_AUTHOR("Naushir Patuck <naush@raspberrypi.com>"); +MODULE_DESCRIPTION("RP1 clock driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/dma-buf/heaps/system_heap.c b/drivers/dma-buf/heaps/system_heap.c index d78cdb9d01e5e4..7518268e882462 100644 --- a/drivers/dma-buf/heaps/system_heap.c +++ b/drivers/dma-buf/heaps/system_heap.c @@ -54,6 +54,11 @@ static gfp_t order_flags[] = {HIGH_ORDER_GFP, HIGH_ORDER_GFP, LOW_ORDER_GFP}; static const unsigned int orders[] = {8, 4, 0}; #define NUM_ORDERS ARRAY_SIZE(orders) +static unsigned int module_max_order = orders[0]; + +module_param_named(max_order, module_max_order, uint, 0400); +MODULE_PARM_DESC(max_order, "Maximum allocation order override."); + static struct sg_table *dup_sg_table(struct sg_table *table) { struct sg_table *new_table; @@ -339,7 +344,7 @@ static struct dma_buf *system_heap_allocate(struct dma_heap *heap, struct system_heap_buffer *buffer; DEFINE_DMA_BUF_EXPORT_INFO(exp_info); unsigned long size_remaining = len; - unsigned int max_order = orders[0]; + unsigned int max_order = module_max_order; struct dma_buf *dmabuf; struct sg_table *table; struct scatterlist *sg; @@ -433,6 +438,9 @@ static int system_heap_create(void) if (IS_ERR(sys_heap)) return PTR_ERR(sys_heap); + if (module_max_order > orders[0]) + module_max_order = orders[0]; + return 0; } module_init(system_heap_create); diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index d9ec1e69e42831..c4376c21d1d492 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -669,6 +669,10 @@ config UNIPHIER_XDMAC UniPhier platform. This DMA controller can transfer data from memory to memory, memory to peripheral and peripheral to memory. +config DMA_BCM2708 + tristate "BCM2708 DMA legacy API support" + depends on DMA_BCM2835 + config XGENE_DMA tristate "APM X-Gene DMA support" depends on ARCH_XGENE || COMPILE_TEST diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile index ad6a03c052ec4a..86314bce68e6e4 100644 --- a/drivers/dma/Makefile +++ b/drivers/dma/Makefile @@ -22,6 +22,7 @@ obj-$(CONFIG_AT_HDMAC) += at_hdmac.o obj-$(CONFIG_AT_XDMAC) += at_xdmac.o obj-$(CONFIG_AXI_DMAC) += dma-axi-dmac.o obj-$(CONFIG_BCM_SBA_RAID) += bcm-sba-raid.o +obj-$(CONFIG_DMA_BCM2708) += bcm2708-dmaengine.o obj-$(CONFIG_DMA_BCM2835) += bcm2835-dma.o obj-$(CONFIG_DMA_JZ4780) += dma-jz4780.o obj-$(CONFIG_DMA_SA11X0) += sa11x0-dma.o diff --git a/drivers/dma/bcm2708-dmaengine.c b/drivers/dma/bcm2708-dmaengine.c new file mode 100644 index 00000000000000..a9a7f92584c8cc --- /dev/null +++ b/drivers/dma/bcm2708-dmaengine.c @@ -0,0 +1,281 @@ +/* + * BCM2708 legacy DMA API + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/platform_data/dma-bcm2708.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/spinlock.h> + +#include "virt-dma.h" + +#define CACHE_LINE_MASK 31 +#define DEFAULT_DMACHAN_BITMAP 0x10 /* channel 4 only */ + +/* valid only for channels 0 - 14, 15 has its own base address */ +#define BCM2708_DMA_CHAN(n) ((n) << 8) /* base address */ +#define BCM2708_DMA_CHANIO(dma_base, n) \ + ((void __iomem *)((char *)(dma_base) + BCM2708_DMA_CHAN(n))) + +struct vc_dmaman { + void __iomem *dma_base; + u32 chan_available; /* bitmap of available channels */ + u32 has_feature[BCM_DMA_FEATURE_COUNT]; /* bitmap of feature presence */ + struct mutex lock; +}; + +static struct device *dmaman_dev; /* we assume there's only one! */ +static struct vc_dmaman *g_dmaman; /* DMA manager */ + +/* DMA Auxiliary Functions */ + +/* A DMA buffer on an arbitrary boundary may separate a cache line into a + section inside the DMA buffer and another section outside it. + Even if we flush DMA buffers from the cache there is always the chance that + during a DMA someone will access the part of a cache line that is outside + the DMA buffer - which will then bring in unwelcome data. + Without being able to dictate our own buffer pools we must insist that + DMA buffers consist of a whole number of cache lines. +*/ +extern int bcm_sg_suitable_for_dma(struct scatterlist *sg_ptr, int sg_len) +{ + int i; + + for (i = 0; i < sg_len; i++) { + if (sg_ptr[i].offset & CACHE_LINE_MASK || + sg_ptr[i].length & CACHE_LINE_MASK) + return 0; + } + + return 1; +} +EXPORT_SYMBOL_GPL(bcm_sg_suitable_for_dma); + +extern void bcm_dma_start(void __iomem *dma_chan_base, + dma_addr_t control_block) +{ + dsb(sy); /* ARM data synchronization (push) operation */ + + writel(control_block, dma_chan_base + BCM2708_DMA_ADDR); + writel(BCM2708_DMA_ACTIVE, dma_chan_base + BCM2708_DMA_CS); +} +EXPORT_SYMBOL_GPL(bcm_dma_start); + +extern void bcm_dma_wait_idle(void __iomem *dma_chan_base) +{ + dsb(sy); + + /* ugly busy wait only option for now */ + while (readl(dma_chan_base + BCM2708_DMA_CS) & BCM2708_DMA_ACTIVE) + cpu_relax(); +} +EXPORT_SYMBOL_GPL(bcm_dma_wait_idle); + +extern bool bcm_dma_is_busy(void __iomem *dma_chan_base) +{ + dsb(sy); + + return readl(dma_chan_base + BCM2708_DMA_CS) & BCM2708_DMA_ACTIVE; +} +EXPORT_SYMBOL_GPL(bcm_dma_is_busy); + +/* Complete an ongoing DMA (assuming its results are to be ignored) + Does nothing if there is no DMA in progress. + This routine waits for the current AXI transfer to complete before + terminating the current DMA. If the current transfer is hung on a DREQ used + by an uncooperative peripheral the AXI transfer may never complete. In this + case the routine times out and return a non-zero error code. + Use of this routine doesn't guarantee that the ongoing or aborted DMA + does not produce an interrupt. +*/ +extern int bcm_dma_abort(void __iomem *dma_chan_base) +{ + unsigned long int cs; + int rc = 0; + + cs = readl(dma_chan_base + BCM2708_DMA_CS); + + if (BCM2708_DMA_ACTIVE & cs) { + long int timeout = 10000; + + /* write 0 to the active bit - pause the DMA */ + writel(0, dma_chan_base + BCM2708_DMA_CS); + + /* wait for any current AXI transfer to complete */ + while (0 != (cs & BCM2708_DMA_ISPAUSED) && --timeout >= 0) + cs = readl(dma_chan_base + BCM2708_DMA_CS); + + if (0 != (cs & BCM2708_DMA_ISPAUSED)) { + /* we'll un-pause when we set of our next DMA */ + rc = -ETIMEDOUT; + + } else if (BCM2708_DMA_ACTIVE & cs) { + /* terminate the control block chain */ + writel(0, dma_chan_base + BCM2708_DMA_NEXTCB); + + /* abort the whole DMA */ + writel(BCM2708_DMA_ABORT | BCM2708_DMA_ACTIVE, + dma_chan_base + BCM2708_DMA_CS); + } + } + + return rc; +} +EXPORT_SYMBOL_GPL(bcm_dma_abort); + + /* DMA Manager Device Methods */ + +static void vc_dmaman_init(struct vc_dmaman *dmaman, void __iomem *dma_base, + u32 chans_available) +{ + dmaman->dma_base = dma_base; + dmaman->chan_available = chans_available; + dmaman->has_feature[BCM_DMA_FEATURE_FAST_ORD] = 0x0c; /* 2 & 3 */ + dmaman->has_feature[BCM_DMA_FEATURE_BULK_ORD] = 0x01; /* 0 */ + dmaman->has_feature[BCM_DMA_FEATURE_NORMAL_ORD] = 0xfe; /* 1 to 7 */ + dmaman->has_feature[BCM_DMA_FEATURE_LITE_ORD] = 0x7f00; /* 8 to 14 */ +} + +static int vc_dmaman_chan_alloc(struct vc_dmaman *dmaman, + unsigned required_feature_set) +{ + u32 chans; + int chan = 0; + int feature; + + chans = dmaman->chan_available; + for (feature = 0; feature < BCM_DMA_FEATURE_COUNT; feature++) + /* select the subset of available channels with the desired + features */ + if (required_feature_set & (1 << feature)) + chans &= dmaman->has_feature[feature]; + + if (!chans) + return -ENOENT; + + /* return the ordinal of the first channel in the bitmap */ + while (chans != 0 && (chans & 1) == 0) { + chans >>= 1; + chan++; + } + /* claim the channel */ + dmaman->chan_available &= ~(1 << chan); + + return chan; +} + +static int vc_dmaman_chan_free(struct vc_dmaman *dmaman, int chan) +{ + if (chan < 0) + return -EINVAL; + + if ((1 << chan) & dmaman->chan_available) + return -EIDRM; + + dmaman->chan_available |= (1 << chan); + + return 0; +} + +/* DMA Manager Monitor */ + +extern int bcm_dma_chan_alloc(unsigned required_feature_set, + void __iomem **out_dma_base, int *out_dma_irq) +{ + struct vc_dmaman *dmaman = g_dmaman; + struct platform_device *pdev = to_platform_device(dmaman_dev); + int chan; + int irq; + + if (!dmaman_dev) + return -ENODEV; + + mutex_lock(&dmaman->lock); + chan = vc_dmaman_chan_alloc(dmaman, required_feature_set); + if (chan < 0) + goto out; + + irq = platform_get_irq(pdev, (unsigned int)chan); + if (irq < 0) { + dev_err(dmaman_dev, "failed to get irq for DMA channel %d\n", + chan); + vc_dmaman_chan_free(dmaman, chan); + chan = -ENOENT; + goto out; + } + + *out_dma_base = BCM2708_DMA_CHANIO(dmaman->dma_base, chan); + *out_dma_irq = irq; + dev_dbg(dmaman_dev, + "Legacy API allocated channel=%d, base=%p, irq=%i\n", + chan, *out_dma_base, *out_dma_irq); + +out: + mutex_unlock(&dmaman->lock); + + return chan; +} +EXPORT_SYMBOL_GPL(bcm_dma_chan_alloc); + +extern int bcm_dma_chan_free(int channel) +{ + struct vc_dmaman *dmaman = g_dmaman; + int rc; + + if (!dmaman_dev) + return -ENODEV; + + mutex_lock(&dmaman->lock); + rc = vc_dmaman_chan_free(dmaman, channel); + mutex_unlock(&dmaman->lock); + + return rc; +} +EXPORT_SYMBOL_GPL(bcm_dma_chan_free); + +int bcm_dmaman_probe(struct platform_device *pdev, void __iomem *base, + u32 chans_available) +{ + struct device *dev = &pdev->dev; + struct vc_dmaman *dmaman; + + dmaman = devm_kzalloc(dev, sizeof(*dmaman), GFP_KERNEL); + if (!dmaman) + return -ENOMEM; + + mutex_init(&dmaman->lock); + vc_dmaman_init(dmaman, base, chans_available); + g_dmaman = dmaman; + dmaman_dev = dev; + + dev_info(dev, "DMA legacy API manager, dmachans=0x%x\n", + chans_available); + + return 0; +} +EXPORT_SYMBOL(bcm_dmaman_probe); + +int bcm_dmaman_remove(struct platform_device *pdev) +{ + dmaman_dev = NULL; + + return 0; +} +EXPORT_SYMBOL(bcm_dmaman_remove); + +MODULE_LICENSE("GPL"); diff --git a/drivers/dma/bcm2835-dma.c b/drivers/dma/bcm2835-dma.c index e1b92b4d7b056e..f0f17bc9373f2f 100644 --- a/drivers/dma/bcm2835-dma.c +++ b/drivers/dma/bcm2835-dma.c @@ -18,6 +18,7 @@ * Copyright 2012 Marvell International Ltd. */ #include <linux/dmaengine.h> +#include <linux/dma-direct.h> #include <linux/dma-mapping.h> #include <linux/dmapool.h> #include <linux/err.h> @@ -25,6 +26,7 @@ #include <linux/interrupt.h> #include <linux/list.h> #include <linux/module.h> +#include <linux/platform_data/dma-bcm2708.h> #include <linux/platform_device.h> #include <linux/slab.h> #include <linux/io.h> @@ -36,6 +38,13 @@ #define BCM2835_DMA_MAX_DMA_CHAN_SUPPORTED 14 #define BCM2835_DMA_CHAN_NAME_SIZE 8 +#define BCM2835_DMA_BULK_MASK BIT(0) +#define BCM2711_DMA_MEMCPY_CHAN 14 + +struct bcm2835_dma_cfg_data { + u64 dma_mask; + u32 chan_40bit_mask; +}; /** * struct bcm2835_dmadev - BCM2835 DMA controller @@ -48,6 +57,7 @@ struct bcm2835_dmadev { struct dma_device ddev; void __iomem *base; dma_addr_t zero_page; + const struct bcm2835_dma_cfg_data *cfg_data; }; struct bcm2835_dma_cb { @@ -60,6 +70,17 @@ struct bcm2835_dma_cb { uint32_t pad[2]; }; +struct bcm2711_dma40_scb { + uint32_t ti; + uint32_t src; + uint32_t srci; + uint32_t dst; + uint32_t dsti; + uint32_t len; + uint32_t next_cb; + uint32_t rsvd; +}; + struct bcm2835_cb_entry { struct bcm2835_dma_cb *cb; dma_addr_t paddr; @@ -80,6 +101,8 @@ struct bcm2835_chan { unsigned int irq_flags; bool is_lite_channel; + bool is_40bit_channel; + bool is_2712; }; struct bcm2835_desc { @@ -136,11 +159,37 @@ struct bcm2835_desc { #define BCM2835_DMA_S_WIDTH BIT(9) /* 128bit writes if set */ #define BCM2835_DMA_S_DREQ BIT(10) /* enable SREQ for source */ #define BCM2835_DMA_S_IGNORE BIT(11) /* ignore source reads - read 0 */ -#define BCM2835_DMA_BURST_LENGTH(x) ((x & 15) << 12) +#define BCM2835_DMA_BURST_LENGTH(x) (((x) & 15) << 12) +#define BCM2835_DMA_GET_BURST_LENGTH(x) (((x) >> 12) & 15) +#define BCM2835_DMA_CS_FLAGS(x) (x & (BCM2835_DMA_PRIORITY(15) | \ + BCM2835_DMA_PANIC_PRIORITY(15) | \ + BCM2835_DMA_WAIT_FOR_WRITES | \ + BCM2835_DMA_DIS_DEBUG)) #define BCM2835_DMA_PER_MAP(x) ((x & 31) << 16) /* REQ source */ #define BCM2835_DMA_WAIT(x) ((x & 31) << 21) /* add DMA-wait cycles */ #define BCM2835_DMA_NO_WIDE_BURSTS BIT(26) /* no 2 beat write bursts */ +/* A fake bit to request that the driver doesn't set the WAIT_RESP bit. */ +#define BCM2835_DMA_NO_WAIT_RESP BIT(27) +#define WAIT_RESP(x) ((x & BCM2835_DMA_NO_WAIT_RESP) ? \ + 0 : BCM2835_DMA_WAIT_RESP) + +/* A fake bit to request that the driver requires wide reads */ +#define BCM2835_DMA_WIDE_SOURCE BIT(24) +#define WIDE_SOURCE(x) ((x & BCM2835_DMA_WIDE_SOURCE) ? \ + BCM2835_DMA_S_WIDTH : 0) + +/* A fake bit to request that the driver requires wide writes */ +#define BCM2835_DMA_WIDE_DEST BIT(25) +#define WIDE_DEST(x) ((x & BCM2835_DMA_WIDE_DEST) ? \ + BCM2835_DMA_D_WIDTH : 0) + +/* A fake bit to request that the driver requires multi-beat burst */ +#define BCM2835_DMA_BURST BIT(30) +#define BURST_LENGTH(x) ((x & BCM2835_DMA_BURST) ? \ + BCM2835_DMA_BURST_LENGTH(3) : 0) + + /* debug register bits */ #define BCM2835_DMA_DEBUG_LAST_NOT_SET_ERR BIT(0) #define BCM2835_DMA_DEBUG_FIFO_ERR BIT(1) @@ -165,13 +214,130 @@ struct bcm2835_desc { #define BCM2835_DMA_DATA_TYPE_S128 16 /* Valid only for channels 0 - 14, 15 has its own base address */ -#define BCM2835_DMA_CHAN(n) ((n) << 8) /* Base address */ +#define BCM2835_DMA_CHAN_SIZE 0x100 +#define BCM2835_DMA_CHAN(n) ((n) * BCM2835_DMA_CHAN_SIZE) /* Base address */ #define BCM2835_DMA_CHANIO(base, n) ((base) + BCM2835_DMA_CHAN(n)) /* the max dma length for different channels */ #define MAX_DMA_LEN SZ_1G #define MAX_LITE_DMA_LEN (SZ_64K - 4) +/* 40-bit DMA support */ +#define BCM2711_DMA40_CS 0x00 +#define BCM2711_DMA40_CB 0x04 +#define BCM2711_DMA40_DEBUG 0x0c +#define BCM2711_DMA40_TI 0x10 +#define BCM2711_DMA40_SRC 0x14 +#define BCM2711_DMA40_SRCI 0x18 +#define BCM2711_DMA40_DEST 0x1c +#define BCM2711_DMA40_DESTI 0x20 +#define BCM2711_DMA40_LEN 0x24 +#define BCM2711_DMA40_NEXT_CB 0x28 +#define BCM2711_DMA40_DEBUG2 0x2c + +#define BCM2711_DMA40_ACTIVE BIT(0) +#define BCM2711_DMA40_END BIT(1) +#define BCM2711_DMA40_INT BIT(2) +#define BCM2711_DMA40_DREQ BIT(3) /* DREQ state */ +#define BCM2711_DMA40_RD_PAUSED BIT(4) /* Reading is paused */ +#define BCM2711_DMA40_WR_PAUSED BIT(5) /* Writing is paused */ +#define BCM2711_DMA40_DREQ_PAUSED BIT(6) /* Is paused by DREQ flow control */ +#define BCM2711_DMA40_WAITING_FOR_WRITES BIT(7) /* Waiting for last write */ +// we always want to run in supervisor mode +#define BCM2711_DMA40_PROT (BIT(8)|BIT(9)) +#define BCM2711_DMA40_ERR BIT(10) +#define BCM2711_DMA40_QOS(x) (((x) & 0x1f) << 16) +#define BCM2711_DMA40_PANIC_QOS(x) (((x) & 0x1f) << 20) +#define BCM2711_DMA40_TRANSACTIONS BIT(25) +#define BCM2711_DMA40_WAIT_FOR_WRITES BIT(28) +#define BCM2711_DMA40_DISDEBUG BIT(29) +#define BCM2711_DMA40_ABORT BIT(30) +#define BCM2711_DMA40_HALT BIT(31) + +#define BCM2711_DMA40_CS_FLAGS(x) (x & (BCM2711_DMA40_QOS(15) | \ + BCM2711_DMA40_PANIC_QOS(15) | \ + BCM2711_DMA40_WAIT_FOR_WRITES | \ + BCM2711_DMA40_DISDEBUG)) + +/* Transfer information bits */ +#define BCM2711_DMA40_INTEN BIT(0) +#define BCM2711_DMA40_TDMODE BIT(1) /* 2D-Mode */ +#define BCM2711_DMA40_WAIT_RESP BIT(2) /* wait for AXI write to be acked */ +#define BCM2711_DMA40_WAIT_RD_RESP BIT(3) /* wait for AXI read to complete */ +#define BCM2711_DMA40_PER_MAP(x) ((x & 31) << 9) /* REQ source */ +#define BCM2711_DMA40_S_DREQ BIT(14) /* enable SREQ for source */ +#define BCM2711_DMA40_D_DREQ BIT(15) /* enable DREQ for destination */ +#define BCM2711_DMA40_S_WAIT(x) ((x & 0xff) << 16) /* add DMA read-wait cycles */ +#define BCM2711_DMA40_D_WAIT(x) ((x & 0xff) << 24) /* add DMA write-wait cycles */ + +/* debug register bits */ +#define BCM2711_DMA40_DEBUG_WRITE_ERR BIT(0) +#define BCM2711_DMA40_DEBUG_FIFO_ERR BIT(1) +#define BCM2711_DMA40_DEBUG_READ_ERR BIT(2) +#define BCM2711_DMA40_DEBUG_READ_CB_ERR BIT(3) +#define BCM2711_DMA40_DEBUG_IN_ON_ERR BIT(8) +#define BCM2711_DMA40_DEBUG_ABORT_ON_ERR BIT(9) +#define BCM2711_DMA40_DEBUG_HALT_ON_ERR BIT(10) +#define BCM2711_DMA40_DEBUG_DISABLE_CLK_GATE BIT(11) +#define BCM2711_DMA40_DEBUG_RSTATE_SHIFT 14 +#define BCM2711_DMA40_DEBUG_RSTATE_BITS 4 +#define BCM2711_DMA40_DEBUG_WSTATE_SHIFT 18 +#define BCM2711_DMA40_DEBUG_WSTATE_BITS 4 +#define BCM2711_DMA40_DEBUG_RESET BIT(23) +#define BCM2711_DMA40_DEBUG_ID_SHIFT 24 +#define BCM2711_DMA40_DEBUG_ID_BITS 4 +#define BCM2711_DMA40_DEBUG_VERSION_SHIFT 28 +#define BCM2711_DMA40_DEBUG_VERSION_BITS 4 + +/* Valid only for channels 0 - 3 (11 - 14) */ +#define BCM2711_DMA40_CHAN(n) (((n) + 11) << 8) /* Base address */ +#define BCM2711_DMA40_CHANIO(base, n) ((base) + BCM2711_DMA_CHAN(n)) + +/* the max dma length for different channels */ +#define MAX_DMA40_LEN SZ_1G + +#define BCM2711_DMA40_BURST_LEN(x) (((x) & 15) << 8) +#define BCM2711_DMA40_INC BIT(12) +#define BCM2711_DMA40_SIZE_32 (0 << 13) +#define BCM2711_DMA40_SIZE_64 (1 << 13) +#define BCM2711_DMA40_SIZE_128 (2 << 13) +#define BCM2711_DMA40_SIZE_256 (3 << 13) +#define BCM2711_DMA40_IGNORE BIT(15) +#define BCM2711_DMA40_STRIDE(x) ((x) << 16) /* For 2D mode */ + +#define BCM2711_DMA40_MEMCPY_FLAGS \ + (BCM2711_DMA40_QOS(0) | \ + BCM2711_DMA40_PANIC_QOS(0) | \ + BCM2711_DMA40_WAIT_FOR_WRITES | \ + BCM2711_DMA40_DISDEBUG) + +#define BCM2711_DMA40_MEMCPY_XFER_INFO \ + (BCM2711_DMA40_SIZE_128 | \ + BCM2711_DMA40_INC | \ + BCM2711_DMA40_BURST_LEN(16)) + +struct bcm2835_dmadev *memcpy_parent; +static void __iomem *memcpy_chan; +static struct bcm2711_dma40_scb *memcpy_scb; +static dma_addr_t memcpy_scb_dma; +DEFINE_SPINLOCK(memcpy_lock); + +static const struct bcm2835_dma_cfg_data bcm2835_dma_cfg = { + .chan_40bit_mask = 0, + .dma_mask = DMA_BIT_MASK(32), +}; + +static const struct bcm2835_dma_cfg_data bcm2711_dma_cfg = { + .chan_40bit_mask = BIT(11) | BIT(12) | BIT(13) | BIT(14), + .dma_mask = DMA_BIT_MASK(36), +}; + +static const struct bcm2835_dma_cfg_data bcm2712_dma_cfg = { + .chan_40bit_mask = BIT(6) | BIT(7) | BIT(8) | BIT(9) | + BIT(10) | BIT(11), + .dma_mask = DMA_BIT_MASK(40), +}; + static inline size_t bcm2835_dma_max_frame_length(struct bcm2835_chan *c) { /* lite and normal channels have different max frame length */ @@ -201,6 +367,36 @@ static inline struct bcm2835_desc *to_bcm2835_dma_desc( return container_of(t, struct bcm2835_desc, vd.tx); } +static inline uint32_t to_bcm2711_ti(uint32_t info) +{ + return ((info & BCM2835_DMA_INT_EN) ? BCM2711_DMA40_INTEN : 0) | + ((info & BCM2835_DMA_WAIT_RESP) ? BCM2711_DMA40_WAIT_RESP : 0) | + ((info & BCM2835_DMA_S_DREQ) ? + (BCM2711_DMA40_S_DREQ | BCM2711_DMA40_WAIT_RD_RESP) : 0) | + ((info & BCM2835_DMA_D_DREQ) ? BCM2711_DMA40_D_DREQ : 0) | + BCM2711_DMA40_PER_MAP((info >> 16) & 0x1f); +} + +static inline uint32_t to_bcm2711_srci(uint32_t info) +{ + return ((info & BCM2835_DMA_S_INC) ? BCM2711_DMA40_INC : 0) | + ((info & BCM2835_DMA_S_WIDTH) ? BCM2711_DMA40_SIZE_128 : 0) | + BCM2711_DMA40_BURST_LEN(BCM2835_DMA_GET_BURST_LENGTH(info)); +} + +static inline uint32_t to_bcm2711_dsti(uint32_t info) +{ + return ((info & BCM2835_DMA_D_INC) ? BCM2711_DMA40_INC : 0) | + ((info & BCM2835_DMA_D_WIDTH) ? BCM2711_DMA40_SIZE_128 : 0) | + BCM2711_DMA40_BURST_LEN(BCM2835_DMA_GET_BURST_LENGTH(info)); +} + +static inline uint32_t to_40bit_cbaddr(dma_addr_t addr) +{ + BUG_ON(addr & 0x1f); + return (addr >> 5); +} + static void bcm2835_dma_free_cb_chain(struct bcm2835_desc *desc) { size_t i; @@ -219,45 +415,53 @@ static void bcm2835_dma_desc_free(struct virt_dma_desc *vd) } static void bcm2835_dma_create_cb_set_length( - struct bcm2835_chan *chan, + struct bcm2835_chan *c, struct bcm2835_dma_cb *control_block, size_t len, size_t period_len, size_t *total_len, u32 finalextrainfo) { - size_t max_len = bcm2835_dma_max_frame_length(chan); + size_t max_len = bcm2835_dma_max_frame_length(c); + uint32_t cb_len; /* set the length taking lite-channel limitations into account */ - control_block->length = min_t(u32, len, max_len); + cb_len = min_t(u32, len, max_len); - /* finished if we have no period_length */ - if (!period_len) - return; + if (period_len) { + /* + * period_len means: that we need to generate + * transfers that are terminating at every + * multiple of period_len - this is typically + * used to set the interrupt flag in info + * which is required during cyclic transfers + */ - /* - * period_len means: that we need to generate - * transfers that are terminating at every - * multiple of period_len - this is typically - * used to set the interrupt flag in info - * which is required during cyclic transfers - */ + /* have we filled in period_length yet? */ + if (*total_len + cb_len < period_len) { + /* update number of bytes in this period so far */ + *total_len += cb_len; + } else { + /* calculate the length that remains to reach period_len */ + cb_len = period_len - *total_len; - /* have we filled in period_length yet? */ - if (*total_len + control_block->length < period_len) { - /* update number of bytes in this period so far */ - *total_len += control_block->length; - return; + /* reset total_length for next period */ + *total_len = 0; + } } - /* calculate the length that remains to reach period_length */ - control_block->length = period_len - *total_len; + if (c->is_40bit_channel) { + struct bcm2711_dma40_scb *scb = + (struct bcm2711_dma40_scb *)control_block; - /* reset total_length for next period */ - *total_len = 0; - - /* add extrainfo bits in info */ - control_block->info |= finalextrainfo; + scb->len = cb_len; + /* add extrainfo bits to ti */ + scb->ti |= to_bcm2711_ti(finalextrainfo); + } else { + control_block->length = cb_len; + /* add extrainfo bits to info */ + control_block->info |= finalextrainfo; + } } static inline size_t bcm2835_dma_count_frames_for_sg( @@ -280,7 +484,7 @@ static inline size_t bcm2835_dma_count_frames_for_sg( /** * bcm2835_dma_create_cb_chain - create a control block and fills data in * - * @chan: the @dma_chan for which we run this + * @c: the @bcm2835_chan for which we run this * @direction: the direction in which we transfer * @cyclic: it is a cyclic transfer * @info: the default info bits to apply per controlblock @@ -298,12 +502,11 @@ static inline size_t bcm2835_dma_count_frames_for_sg( * @gfp: the GFP flag to use for allocation */ static struct bcm2835_desc *bcm2835_dma_create_cb_chain( - struct dma_chan *chan, enum dma_transfer_direction direction, + struct bcm2835_chan *c, enum dma_transfer_direction direction, bool cyclic, u32 info, u32 finalextrainfo, size_t frames, dma_addr_t src, dma_addr_t dst, size_t buf_len, size_t period_len, gfp_t gfp) { - struct bcm2835_chan *c = to_bcm2835_dma_chan(chan); size_t len = buf_len, total_len; size_t frame; struct bcm2835_desc *d; @@ -335,11 +538,27 @@ static struct bcm2835_desc *bcm2835_dma_create_cb_chain( /* fill in the control block */ control_block = cb_entry->cb; - control_block->info = info; - control_block->src = src; - control_block->dst = dst; - control_block->stride = 0; - control_block->next = 0; + if (c->is_40bit_channel) { + struct bcm2711_dma40_scb *scb = + (struct bcm2711_dma40_scb *)control_block; + scb->ti = to_bcm2711_ti(info); + scb->src = lower_32_bits(src); + scb->srci= upper_32_bits(src) | to_bcm2711_srci(info); + scb->dst = lower_32_bits(dst); + scb->dsti = upper_32_bits(dst) | to_bcm2711_dsti(info); + scb->next_cb = 0; + } else { + control_block->info = info; + control_block->src = src; + control_block->dst = dst; + if (c->is_2712) + control_block->stride = (upper_32_bits(dst) << 8) | + upper_32_bits(src); + else + control_block->stride = 0; + control_block->next = 0; + } + /* set up length in control_block if requested */ if (buf_len) { /* calculate length honoring period_length */ @@ -349,25 +568,52 @@ static struct bcm2835_desc *bcm2835_dma_create_cb_chain( cyclic ? finalextrainfo : 0); /* calculate new remaining length */ - len -= control_block->length; + if (c->is_40bit_channel) + len -= ((struct bcm2711_dma40_scb *)control_block)->len; + else + len -= control_block->length; } /* link this the last controlblock */ - if (frame) - d->cb_list[frame - 1].cb->next = cb_entry->paddr; + if (frame && c->is_40bit_channel) + ((struct bcm2711_dma40_scb *) + d->cb_list[frame - 1].cb)->next_cb = + to_40bit_cbaddr(cb_entry->paddr); + if (frame && !c->is_40bit_channel) + d->cb_list[frame - 1].cb->next = c->is_2712 ? + to_40bit_cbaddr(cb_entry->paddr) : cb_entry->paddr; /* update src and dst and length */ - if (src && (info & BCM2835_DMA_S_INC)) - src += control_block->length; - if (dst && (info & BCM2835_DMA_D_INC)) - dst += control_block->length; + if (src && (info & BCM2835_DMA_S_INC)) { + if (c->is_40bit_channel) + src += ((struct bcm2711_dma40_scb *)control_block)->len; + else + src += control_block->length; + } + + if (dst && (info & BCM2835_DMA_D_INC)) { + if (c->is_40bit_channel) + dst += ((struct bcm2711_dma40_scb *)control_block)->len; + else + dst += control_block->length; + } /* Length of total transfer */ - d->size += control_block->length; + if (c->is_40bit_channel) + d->size += ((struct bcm2711_dma40_scb *)control_block)->len; + else + d->size += control_block->length; } /* the last frame requires extra flags */ - d->cb_list[d->frames - 1].cb->info |= finalextrainfo; + if (c->is_40bit_channel) { + struct bcm2711_dma40_scb *scb = + (struct bcm2711_dma40_scb *)d->cb_list[d->frames-1].cb; + + scb->ti |= to_bcm2711_ti(finalextrainfo); + } else { + d->cb_list[d->frames - 1].cb->info |= finalextrainfo; + } /* detect a size mismatch */ if (buf_len && (d->size != buf_len)) @@ -381,13 +627,12 @@ static struct bcm2835_desc *bcm2835_dma_create_cb_chain( } static void bcm2835_dma_fill_cb_chain_with_sg( - struct dma_chan *chan, + struct bcm2835_chan *c, enum dma_transfer_direction direction, struct bcm2835_cb_entry *cb, struct scatterlist *sgl, unsigned int sg_len) { - struct bcm2835_chan *c = to_bcm2835_dma_chan(chan); size_t len, max_len; unsigned int i; dma_addr_t addr; @@ -395,14 +640,35 @@ static void bcm2835_dma_fill_cb_chain_with_sg( max_len = bcm2835_dma_max_frame_length(c); for_each_sg(sgl, sgent, sg_len, i) { - for (addr = sg_dma_address(sgent), len = sg_dma_len(sgent); - len > 0; - addr += cb->cb->length, len -= cb->cb->length, cb++) { - if (direction == DMA_DEV_TO_MEM) - cb->cb->dst = addr; - else - cb->cb->src = addr; - cb->cb->length = min(len, max_len); + if (c->is_40bit_channel) { + struct bcm2711_dma40_scb *scb; + + for (addr = sg_dma_address(sgent), + len = sg_dma_len(sgent); + len > 0; + addr += scb->len, len -= scb->len, cb++) { + scb = (struct bcm2711_dma40_scb *)cb->cb; + if (direction == DMA_DEV_TO_MEM) { + scb->dst = lower_32_bits(addr); + scb->dsti = upper_32_bits(addr) | BCM2711_DMA40_INC; + } else { + scb->src = lower_32_bits(addr); + scb->srci = upper_32_bits(addr) | BCM2711_DMA40_INC; + } + scb->len = min(len, max_len); + } + } else { + for (addr = sg_dma_address(sgent), + len = sg_dma_len(sgent); + len > 0; + addr += cb->cb->length, len -= cb->cb->length, + cb++) { + if (direction == DMA_DEV_TO_MEM) + cb->cb->dst = addr; + else + cb->cb->src = addr; + cb->cb->length = min(len, max_len); + } } } } @@ -410,29 +676,74 @@ static void bcm2835_dma_fill_cb_chain_with_sg( static void bcm2835_dma_abort(struct bcm2835_chan *c) { void __iomem *chan_base = c->chan_base; - long int timeout = 10000; + long timeout = 100; - /* - * A zero control block address means the channel is idle. - * (The ACTIVE flag in the CS register is not a reliable indicator.) - */ - if (!readl(chan_base + BCM2835_DMA_ADDR)) - return; + if (c->is_40bit_channel) { + /* + * A zero control block address means the channel is idle. + * (The ACTIVE flag in the CS register is not a reliable indicator.) + */ + if (!readl(chan_base + BCM2711_DMA40_CB)) + return; + + /* Pause the current DMA */ + writel(readl(chan_base + BCM2711_DMA40_CS) & ~BCM2711_DMA40_ACTIVE, + chan_base + BCM2711_DMA40_CS); + + /* wait for outstanding transactions to complete */ + while ((readl(chan_base + BCM2711_DMA40_CS) & BCM2711_DMA40_TRANSACTIONS) && + --timeout) + cpu_relax(); + + /* Peripheral might be stuck and fail to complete */ + if (!timeout) + dev_err(c->vc.chan.device->dev, + "failed to complete pause on dma %d (CS:%08x)\n", c->ch, + readl(chan_base + BCM2711_DMA40_CS)); + + /* Set CS back to default state */ + writel(BCM2711_DMA40_PROT, chan_base + BCM2711_DMA40_CS); + + /* Reset the DMA */ + writel(readl(chan_base + BCM2711_DMA40_DEBUG) | BCM2711_DMA40_DEBUG_RESET, + chan_base + BCM2711_DMA40_DEBUG); + } else { + /* + * A zero control block address means the channel is idle. + * (The ACTIVE flag in the CS register is not a reliable indicator.) + */ + if (!readl(chan_base + BCM2835_DMA_ADDR)) + return; + + /* We need to clear the next DMA block pending */ + writel(0, chan_base + BCM2835_DMA_NEXTCB); - /* Write 0 to the active bit - Pause the DMA */ - writel(0, chan_base + BCM2835_DMA_CS); + /* Abort the DMA, which needs to be enabled to complete */ + writel(readl(chan_base + BCM2835_DMA_CS) | BCM2835_DMA_ABORT | BCM2835_DMA_ACTIVE, + chan_base + BCM2835_DMA_CS); - /* Wait for any current AXI transfer to complete */ - while ((readl(chan_base + BCM2835_DMA_CS) & - BCM2835_DMA_WAITING_FOR_WRITES) && --timeout) - cpu_relax(); + /* wait for DMA to be aborted */ + while ((readl(chan_base + BCM2835_DMA_CS) & BCM2835_DMA_ABORT) && --timeout) + cpu_relax(); - /* Peripheral might be stuck and fail to signal AXI write responses */ - if (!timeout) - dev_err(c->vc.chan.device->dev, - "failed to complete outstanding writes\n"); + /* Write 0 to the active bit - Pause the DMA */ + writel(readl(chan_base + BCM2835_DMA_CS) & ~BCM2835_DMA_ACTIVE, + chan_base + BCM2835_DMA_CS); - writel(BCM2835_DMA_RESET, chan_base + BCM2835_DMA_CS); + /* + * Peripheral might be stuck and fail to complete + * This is expected when dreqs are enabled but not asserted + * so only report error in non dreq case + */ + if (!timeout && !(readl(chan_base + BCM2835_DMA_TI) & + (BCM2835_DMA_S_DREQ | BCM2835_DMA_D_DREQ))) + dev_err(c->vc.chan.device->dev, + "failed to complete pause on dma %d (CS:%08x)\n", c->ch, + readl(chan_base + BCM2835_DMA_CS)); + + /* Set CS back to default state and reset the DMA */ + writel(BCM2835_DMA_RESET, chan_base + BCM2835_DMA_CS); + } } static void bcm2835_dma_start_desc(struct bcm2835_chan *c) @@ -449,8 +760,19 @@ static void bcm2835_dma_start_desc(struct bcm2835_chan *c) c->desc = d = to_bcm2835_dma_desc(&vd->tx); - writel(d->cb_list[0].paddr, c->chan_base + BCM2835_DMA_ADDR); - writel(BCM2835_DMA_ACTIVE, c->chan_base + BCM2835_DMA_CS); + if (c->is_40bit_channel) { + writel(to_40bit_cbaddr(d->cb_list[0].paddr), + c->chan_base + BCM2711_DMA40_CB); + writel(BCM2711_DMA40_ACTIVE | BCM2711_DMA40_PROT | BCM2711_DMA40_CS_FLAGS(c->dreq), + c->chan_base + BCM2711_DMA40_CS); + } else { + writel(BIT(31), c->chan_base + BCM2835_DMA_CS); + + writel(c->is_2712 ? to_40bit_cbaddr(d->cb_list[0].paddr) : d->cb_list[0].paddr, + c->chan_base + BCM2835_DMA_ADDR); + writel(BCM2835_DMA_ACTIVE | BCM2835_DMA_CS_FLAGS(c->dreq), + c->chan_base + BCM2835_DMA_CS); + } } static irqreturn_t bcm2835_dma_callback(int irq, void *data) @@ -477,8 +799,13 @@ static irqreturn_t bcm2835_dma_callback(int irq, void *data) * if this IRQ handler is threaded.) If the channel is finished, it * will remain idle despite the ACTIVE flag being set. */ - writel(BCM2835_DMA_INT | BCM2835_DMA_ACTIVE, - c->chan_base + BCM2835_DMA_CS); + if (c->is_40bit_channel) + writel(BCM2835_DMA_INT | BCM2711_DMA40_ACTIVE | BCM2711_DMA40_PROT | + BCM2711_DMA40_CS_FLAGS(c->dreq), + c->chan_base + BCM2711_DMA40_CS); + else + writel(BCM2835_DMA_INT | BCM2835_DMA_ACTIVE | BCM2835_DMA_CS_FLAGS(c->dreq), + c->chan_base + BCM2835_DMA_CS); d = c->desc; @@ -540,20 +867,39 @@ static size_t bcm2835_dma_desc_size_pos(struct bcm2835_desc *d, dma_addr_t addr) unsigned int i; size_t size; - for (size = i = 0; i < d->frames; i++) { - struct bcm2835_dma_cb *control_block = d->cb_list[i].cb; - size_t this_size = control_block->length; - dma_addr_t dma; + if (d->c->is_40bit_channel) { + for (size = i = 0; i < d->frames; i++) { + struct bcm2711_dma40_scb *control_block = + (struct bcm2711_dma40_scb *)d->cb_list[i].cb; + size_t this_size = control_block->len; + dma_addr_t dma; - if (d->dir == DMA_DEV_TO_MEM) - dma = control_block->dst; - else - dma = control_block->src; + if (d->dir == DMA_DEV_TO_MEM) + dma = control_block->dst; + else + dma = control_block->src; + + if (size) + size += this_size; + else if (addr >= dma && addr < dma + this_size) + size += dma + this_size - addr; + } + } else { + for (size = i = 0; i < d->frames; i++) { + struct bcm2835_dma_cb *control_block = d->cb_list[i].cb; + size_t this_size = control_block->length; + dma_addr_t dma; + + if (d->dir == DMA_DEV_TO_MEM) + dma = control_block->dst; + else + dma = control_block->src; - if (size) - size += this_size; - else if (addr >= dma && addr < dma + this_size) - size += dma + this_size - addr; + if (size) + size += this_size; + else if (addr >= dma && addr < dma + this_size) + size += dma + this_size - addr; + } } return size; @@ -580,12 +926,25 @@ static enum dma_status bcm2835_dma_tx_status(struct dma_chan *chan, struct bcm2835_desc *d = c->desc; dma_addr_t pos; - if (d->dir == DMA_MEM_TO_DEV) + if (d->dir == DMA_MEM_TO_DEV && c->is_40bit_channel) { + u64 lo_bits, hi_bits; + + lo_bits = readl(c->chan_base + BCM2711_DMA40_SRC); + hi_bits = readl(c->chan_base + BCM2711_DMA40_SRCI) & 0xff; + pos = (hi_bits << 32) | lo_bits; + } else if (d->dir == DMA_MEM_TO_DEV && !c->is_40bit_channel) { pos = readl(c->chan_base + BCM2835_DMA_SOURCE_AD); - else if (d->dir == DMA_DEV_TO_MEM) + } else if (d->dir == DMA_DEV_TO_MEM && c->is_40bit_channel) { + u64 lo_bits, hi_bits; + + lo_bits = readl(c->chan_base + BCM2711_DMA40_DEST); + hi_bits = readl(c->chan_base + BCM2711_DMA40_DESTI) & 0xff; + pos = (hi_bits << 32) | lo_bits; + } else if (d->dir == DMA_DEV_TO_MEM && !c->is_40bit_channel) { pos = readl(c->chan_base + BCM2835_DMA_DEST_AD); - else + } else { pos = 0; + } txstate->residue = bcm2835_dma_desc_size_pos(d, pos); } else { @@ -615,8 +974,10 @@ static struct dma_async_tx_descriptor *bcm2835_dma_prep_dma_memcpy( { struct bcm2835_chan *c = to_bcm2835_dma_chan(chan); struct bcm2835_desc *d; - u32 info = BCM2835_DMA_D_INC | BCM2835_DMA_S_INC; - u32 extra = BCM2835_DMA_INT_EN | BCM2835_DMA_WAIT_RESP; + u32 info = BCM2835_DMA_D_INC | BCM2835_DMA_S_INC | + WAIT_RESP(c->dreq) | WIDE_SOURCE(c->dreq) | + WIDE_DEST(c->dreq) | BURST_LENGTH(c->dreq); + u32 extra = BCM2835_DMA_INT_EN; size_t max_len = bcm2835_dma_max_frame_length(c); size_t frames; @@ -628,7 +989,7 @@ static struct dma_async_tx_descriptor *bcm2835_dma_prep_dma_memcpy( frames = bcm2835_dma_frames_for_length(len, max_len); /* allocate the CB chain - this also fills in the pointers */ - d = bcm2835_dma_create_cb_chain(chan, DMA_MEM_TO_MEM, false, + d = bcm2835_dma_create_cb_chain(c, DMA_MEM_TO_MEM, false, info, extra, frames, src, dst, len, 0, GFP_KERNEL); if (!d) @@ -646,7 +1007,8 @@ static struct dma_async_tx_descriptor *bcm2835_dma_prep_slave_sg( struct bcm2835_chan *c = to_bcm2835_dma_chan(chan); struct bcm2835_desc *d; dma_addr_t src = 0, dst = 0; - u32 info = BCM2835_DMA_WAIT_RESP; + u32 info = WAIT_RESP(c->dreq) | WIDE_SOURCE(c->dreq) | + WIDE_DEST(c->dreq) | BURST_LENGTH(c->dreq); u32 extra = BCM2835_DMA_INT_EN; size_t frames; @@ -662,12 +1024,12 @@ static struct dma_async_tx_descriptor *bcm2835_dma_prep_slave_sg( if (direction == DMA_DEV_TO_MEM) { if (c->cfg.src_addr_width != DMA_SLAVE_BUSWIDTH_4_BYTES) return NULL; - src = c->cfg.src_addr; + src = phys_to_dma(chan->device->dev, c->cfg.src_addr); info |= BCM2835_DMA_S_DREQ | BCM2835_DMA_D_INC; } else { if (c->cfg.dst_addr_width != DMA_SLAVE_BUSWIDTH_4_BYTES) return NULL; - dst = c->cfg.dst_addr; + dst = phys_to_dma(chan->device->dev, c->cfg.dst_addr); info |= BCM2835_DMA_D_DREQ | BCM2835_DMA_S_INC; } @@ -675,7 +1037,7 @@ static struct dma_async_tx_descriptor *bcm2835_dma_prep_slave_sg( frames = bcm2835_dma_count_frames_for_sg(c, sgl, sg_len); /* allocate the CB chain */ - d = bcm2835_dma_create_cb_chain(chan, direction, false, + d = bcm2835_dma_create_cb_chain(c, direction, false, info, extra, frames, src, dst, 0, 0, GFP_NOWAIT); @@ -683,7 +1045,7 @@ static struct dma_async_tx_descriptor *bcm2835_dma_prep_slave_sg( return NULL; /* fill in frames with scatterlist pointers */ - bcm2835_dma_fill_cb_chain_with_sg(chan, direction, d->cb_list, + bcm2835_dma_fill_cb_chain_with_sg(c, direction, d->cb_list, sgl, sg_len); return vchan_tx_prep(&c->vc, &d->vd, flags); @@ -698,7 +1060,8 @@ static struct dma_async_tx_descriptor *bcm2835_dma_prep_dma_cyclic( struct bcm2835_chan *c = to_bcm2835_dma_chan(chan); struct bcm2835_desc *d; dma_addr_t src, dst; - u32 info = BCM2835_DMA_WAIT_RESP; + u32 info = WAIT_RESP(c->dreq) | WIDE_SOURCE(c->dreq) | + WIDE_DEST(c->dreq) | BURST_LENGTH(c->dreq); u32 extra = 0; size_t max_len = bcm2835_dma_max_frame_length(c); size_t frames; @@ -736,13 +1099,13 @@ static struct dma_async_tx_descriptor *bcm2835_dma_prep_dma_cyclic( if (direction == DMA_DEV_TO_MEM) { if (c->cfg.src_addr_width != DMA_SLAVE_BUSWIDTH_4_BYTES) return NULL; - src = c->cfg.src_addr; + src = phys_to_dma(chan->device->dev, c->cfg.src_addr); dst = buf_addr; info |= BCM2835_DMA_S_DREQ | BCM2835_DMA_D_INC; } else { if (c->cfg.dst_addr_width != DMA_SLAVE_BUSWIDTH_4_BYTES) return NULL; - dst = c->cfg.dst_addr; + dst = phys_to_dma(chan->device->dev, c->cfg.dst_addr); src = buf_addr; info |= BCM2835_DMA_D_DREQ | BCM2835_DMA_S_INC; @@ -762,7 +1125,7 @@ static struct dma_async_tx_descriptor *bcm2835_dma_prep_dma_cyclic( * note that we need to use GFP_NOWAIT, as the ALSA i2s dmaengine * implementation calls prep_dma_cyclic with interrupts disabled. */ - d = bcm2835_dma_create_cb_chain(chan, direction, true, + d = bcm2835_dma_create_cb_chain(c, direction, true, info, extra, frames, src, dst, buf_len, period_len, GFP_NOWAIT); @@ -770,7 +1133,13 @@ static struct dma_async_tx_descriptor *bcm2835_dma_prep_dma_cyclic( return NULL; /* wrap around into a loop */ - d->cb_list[d->frames - 1].cb->next = d->cb_list[0].paddr; + if (c->is_40bit_channel) + ((struct bcm2711_dma40_scb *) + d->cb_list[frames - 1].cb)->next_cb = + to_40bit_cbaddr(d->cb_list[0].paddr); + else + d->cb_list[d->frames - 1].cb->next = c->is_2712 ? + to_40bit_cbaddr(d->cb_list[0].paddr) : d->cb_list[0].paddr; return vchan_tx_prep(&c->vc, &d->vd, flags); } @@ -831,10 +1200,14 @@ static int bcm2835_dma_chan_init(struct bcm2835_dmadev *d, int chan_id, c->irq_number = irq; c->irq_flags = irq_flags; - /* check in DEBUG register if this is a LITE channel */ - if (readl(c->chan_base + BCM2835_DMA_DEBUG) & - BCM2835_DMA_DEBUG_LITE) + /* check for 40bit and lite channels */ + if (d->cfg_data->chan_40bit_mask & BIT(chan_id)) + c->is_40bit_channel = true; + else if (readl(c->chan_base + BCM2835_DMA_DEBUG) & + BCM2835_DMA_DEBUG_LITE) c->is_lite_channel = true; + if (d->cfg_data->dma_mask == DMA_BIT_MASK(40)) + c->is_2712 = true; return 0; } @@ -854,7 +1227,9 @@ static void bcm2835_dma_free(struct bcm2835_dmadev *od) } static const struct of_device_id bcm2835_dma_of_match[] = { - { .compatible = "brcm,bcm2835-dma", }, + { .compatible = "brcm,bcm2835-dma", .data = &bcm2835_dma_cfg }, + { .compatible = "brcm,bcm2711-dma", .data = &bcm2711_dma_cfg }, + { .compatible = "brcm,bcm2712-dma", .data = &bcm2712_dma_cfg }, {}, }; MODULE_DEVICE_TABLE(of, bcm2835_dma_of_match); @@ -877,7 +1252,10 @@ static struct dma_chan *bcm2835_dma_xlate(struct of_phandle_args *spec, static int bcm2835_dma_probe(struct platform_device *pdev) { + const struct bcm2835_dma_cfg_data *cfg_data; + const struct of_device_id *of_id; struct bcm2835_dmadev *od; + struct resource *res; void __iomem *base; int rc; int i, j; @@ -885,11 +1263,20 @@ static int bcm2835_dma_probe(struct platform_device *pdev) int irq_flags; uint32_t chans_available; char chan_name[BCM2835_DMA_CHAN_NAME_SIZE]; + int chan_count, chan_start, chan_end; + + of_id = of_match_node(bcm2835_dma_of_match, pdev->dev.of_node); + if (!of_id) { + dev_err(&pdev->dev, "Failed to match compatible string\n"); + return -EINVAL; + } + + cfg_data = of_id->data; if (!pdev->dev.dma_mask) pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask; - rc = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + rc = dma_set_mask_and_coherent(&pdev->dev, cfg_data->dma_mask); if (rc) { dev_err(&pdev->dev, "Unable to set DMA mask\n"); return rc; @@ -901,10 +1288,17 @@ static int bcm2835_dma_probe(struct platform_device *pdev) dma_set_max_seg_size(&pdev->dev, 0x3FFFFFFF); - base = devm_platform_ioremap_resource(pdev, 0); + base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); if (IS_ERR(base)) return PTR_ERR(base); + /* The set of channels can be split across multiple instances. */ + chan_start = ((u32)(uintptr_t)base / BCM2835_DMA_CHAN_SIZE) & 0xf; + base -= BCM2835_DMA_CHAN(chan_start); + chan_count = resource_size(res) / BCM2835_DMA_CHAN_SIZE; + chan_end = min(chan_start + chan_count, + BCM2835_DMA_MAX_DMA_CHAN_SUPPORTED + 1); + od->base = base; dma_cap_set(DMA_SLAVE, od->ddev.cap_mask); @@ -940,6 +1334,14 @@ static int bcm2835_dma_probe(struct platform_device *pdev) return -ENOMEM; } + of_id = of_match_node(bcm2835_dma_of_match, pdev->dev.of_node); + if (!of_id) { + dev_err(&pdev->dev, "Failed to match compatible string\n"); + return -EINVAL; + } + + od->cfg_data = cfg_data; + /* Request DMA channel mask from device tree */ if (of_property_read_u32(pdev->dev.of_node, "brcm,dma-channel-mask", @@ -949,8 +1351,36 @@ static int bcm2835_dma_probe(struct platform_device *pdev) goto err_no_dma; } +#ifdef CONFIG_DMA_BCM2708 + /* One channel is reserved for the legacy API */ + if (chans_available & BCM2835_DMA_BULK_MASK) { + rc = bcm_dmaman_probe(pdev, base, + chans_available & BCM2835_DMA_BULK_MASK); + if (rc) + dev_err(&pdev->dev, + "Failed to initialize the legacy API\n"); + + chans_available &= ~BCM2835_DMA_BULK_MASK; + } +#endif + + /* And possibly one for the 40-bit DMA memcpy API */ + if (chans_available & od->cfg_data->chan_40bit_mask & + BIT(BCM2711_DMA_MEMCPY_CHAN)) { + memcpy_parent = od; + memcpy_chan = BCM2835_DMA_CHANIO(base, BCM2711_DMA_MEMCPY_CHAN); + memcpy_scb = dma_alloc_coherent(memcpy_parent->ddev.dev, + sizeof(*memcpy_scb), + &memcpy_scb_dma, GFP_KERNEL); + if (!memcpy_scb) + dev_warn(&pdev->dev, + "Failed to allocated memcpy scb\n"); + + chans_available &= ~BIT(BCM2711_DMA_MEMCPY_CHAN); + } + /* get irqs for each channel that we support */ - for (i = 0; i <= BCM2835_DMA_MAX_DMA_CHAN_SUPPORTED; i++) { + for (i = chan_start; i < chan_end; i++) { /* skip masked out channels */ if (!(chans_available & (1 << i))) { irq[i] = -1; @@ -973,13 +1403,17 @@ static int bcm2835_dma_probe(struct platform_device *pdev) irq[i] = platform_get_irq(pdev, i < 11 ? i : 11); } + chan_count = 0; + /* get irqs for each channel */ - for (i = 0; i <= BCM2835_DMA_MAX_DMA_CHAN_SUPPORTED; i++) { + for (i = chan_start; i < chan_end; i++) { /* skip channels without irq */ if (irq[i] < 0) continue; /* check if there are other channels that also use this irq */ + /* FIXME: This will fail if interrupts are shared across + instances */ irq_flags = 0; for (j = 0; j <= BCM2835_DMA_MAX_DMA_CHAN_SUPPORTED; j++) if ((i != j) && (irq[j] == irq[i])) { @@ -991,9 +1425,10 @@ static int bcm2835_dma_probe(struct platform_device *pdev) rc = bcm2835_dma_chan_init(od, i, irq[i], irq_flags); if (rc) goto err_no_dma; + chan_count++; } - dev_dbg(&pdev->dev, "Initialized %i DMA channels\n", i); + dev_dbg(&pdev->dev, "Initialized %i DMA channels\n", chan_count); /* Device-tree DMA controller registration */ rc = of_dma_controller_register(pdev->dev.of_node, @@ -1023,7 +1458,15 @@ static void bcm2835_dma_remove(struct platform_device *pdev) { struct bcm2835_dmadev *od = platform_get_drvdata(pdev); + bcm_dmaman_remove(pdev); dma_async_device_unregister(&od->ddev); + if (memcpy_parent == od) { + dma_free_coherent(&pdev->dev, sizeof(*memcpy_scb), memcpy_scb, + memcpy_scb_dma); + memcpy_parent = NULL; + memcpy_scb = NULL; + memcpy_chan = NULL; + } bcm2835_dma_free(od); } @@ -1036,7 +1479,22 @@ static struct platform_driver bcm2835_dma_driver = { }, }; -module_platform_driver(bcm2835_dma_driver); +static int bcm2835_dma_init(void) +{ + return platform_driver_register(&bcm2835_dma_driver); +} + +static void bcm2835_dma_exit(void) +{ + platform_driver_unregister(&bcm2835_dma_driver); +} + +/* + * Load after serial driver (arch_initcall) so we see the messages if it fails, + * but before drivers (module_init) that need a DMA channel. + */ +subsys_initcall(bcm2835_dma_init); +module_exit(bcm2835_dma_exit); MODULE_ALIAS("platform:bcm2835-dma"); MODULE_DESCRIPTION("BCM2835 DMA engine driver"); diff --git a/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c b/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c index fffafa86d964e0..5734ae16e21283 100644 --- a/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c +++ b/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c @@ -12,6 +12,7 @@ #include <linux/device.h> #include <linux/dmaengine.h> #include <linux/dmapool.h> +#include <linux/dma-direct.h> #include <linux/dma-mapping.h> #include <linux/err.h> #include <linux/interrupt.h> @@ -95,6 +96,17 @@ axi_chan_iowrite64(struct axi_dma_chan *chan, u32 reg, u64 val) iowrite32(upper_32_bits(val), chan->chan_regs + reg + 4); } +static inline u64 +axi_chan_ioread64(struct axi_dma_chan *chan, u32 reg) +{ + /* + * We split one 64 bit read into two 32 bit reads as some HW doesn't + * support 64 bit access. + */ + return ((u64)ioread32(chan->chan_regs + reg + 4) << 32) + + ioread32(chan->chan_regs + reg); +} + static inline void axi_chan_config_write(struct axi_dma_chan *chan, struct axi_dma_chan_config *config) { @@ -266,7 +278,18 @@ static void axi_dma_hw_init(struct axi_dma_chip *chip) { int ret; u32 i; - + int retries = 1000; + + axi_dma_iowrite32(chip, DMAC_RESET, 1); + while (axi_dma_ioread32(chip, DMAC_RESET)) { + retries--; + if (!retries) { + dev_err(chip->dev, "%s: DMAC failed to reset\n", + __func__); + return; + } + cpu_relax(); + } for (i = 0; i < chip->dw->hdata->nr_channels; i++) { axi_chan_irq_disable(&chip->dw->chan[i], DWAXIDMAC_IRQ_ALL); axi_chan_disable(&chip->dw->chan[i]); @@ -284,6 +307,15 @@ static u32 axi_chan_get_xfer_width(struct axi_dma_chan *chan, dma_addr_t src, return __ffs(src | dst | len | BIT(max_width)); } +static u32 axi_dma_encode_msize(u32 max_burst) +{ + if (max_burst <= 1) + return DWAXIDMAC_BURST_TRANS_LEN_1; + if (max_burst > 1024) + return DWAXIDMAC_BURST_TRANS_LEN_1024; + return fls(max_burst) - 2; +} + static inline const char *axi_chan_name(struct axi_dma_chan *chan) { return dma_chan_name(&chan->vc.chan); @@ -351,6 +383,48 @@ static void vchan_desc_put(struct virt_dma_desc *vdesc) axi_desc_put(vd_to_axi_desc(vdesc)); } +static u32 axi_dma_desc_src_pos(struct axi_dma_desc *desc, dma_addr_t addr) +{ + unsigned int idx = 0; + u32 pos = 0; + + while (pos < desc->length) { + struct axi_dma_hw_desc *hw_desc = &desc->hw_desc[idx++]; + u32 len = hw_desc->len; + dma_addr_t start = le64_to_cpu(hw_desc->lli->sar); + + if (addr >= start && addr <= (start + len)) { + pos += addr - start; + break; + } + + pos += len; + } + + return pos; +} + +static u32 axi_dma_desc_dst_pos(struct axi_dma_desc *desc, dma_addr_t addr) +{ + unsigned int idx = 0; + u32 pos = 0; + + while (pos < desc->length) { + struct axi_dma_hw_desc *hw_desc = &desc->hw_desc[idx++]; + u32 len = hw_desc->len; + dma_addr_t start = le64_to_cpu(hw_desc->lli->dar); + + if (addr >= start && addr <= (start + len)) { + pos += addr - start; + break; + } + + pos += len; + } + + return pos; +} + static enum dma_status dma_chan_tx_status(struct dma_chan *dchan, dma_cookie_t cookie, struct dma_tx_state *txstate) @@ -360,10 +434,7 @@ dma_chan_tx_status(struct dma_chan *dchan, dma_cookie_t cookie, enum dma_status status; u32 completed_length; unsigned long flags; - u32 completed_blocks; size_t bytes = 0; - u32 length; - u32 len; status = dma_cookie_status(dchan, cookie, txstate); if (status == DMA_COMPLETE || !txstate) @@ -372,16 +443,31 @@ dma_chan_tx_status(struct dma_chan *dchan, dma_cookie_t cookie, spin_lock_irqsave(&chan->vc.lock, flags); vdesc = vchan_find_desc(&chan->vc, cookie); - if (vdesc) { - length = vd_to_axi_desc(vdesc)->length; - completed_blocks = vd_to_axi_desc(vdesc)->completed_blocks; - len = vd_to_axi_desc(vdesc)->hw_desc[0].len; - completed_length = completed_blocks * len; - bytes = length - completed_length; + if (vdesc && vdesc == vchan_next_desc(&chan->vc)) { + /* This descriptor is in-progress */ + struct axi_dma_desc *desc = vd_to_axi_desc(vdesc); + dma_addr_t addr; + + if (chan->direction == DMA_MEM_TO_DEV) { + addr = axi_chan_ioread64(chan, CH_SAR); + completed_length = axi_dma_desc_src_pos(desc, addr); + } else if (chan->direction == DMA_DEV_TO_MEM) { + addr = axi_chan_ioread64(chan, CH_DAR); + completed_length = axi_dma_desc_dst_pos(desc, addr); + } else { + completed_length = 0; + } + bytes = desc->length - completed_length; + } else if (vdesc) { + /* Still in the queue so not started */ + bytes = vd_to_axi_desc(vdesc)->length; } - spin_unlock_irqrestore(&chan->vc.lock, flags); + if (chan->is_paused && status == DMA_IN_PROGRESS) + status = DMA_PAUSED; + dma_set_residue(txstate, bytes); + spin_unlock_irqrestore(&chan->vc.lock, flags); return status; } @@ -435,8 +521,6 @@ static void axi_chan_block_xfer_start(struct axi_dma_chan *chan, return; } - axi_dma_enable(chan->chip); - config.dst_multblk_type = DWAXIDMAC_MBLK_TYPE_LL; config.src_multblk_type = DWAXIDMAC_MBLK_TYPE_LL; config.tt_fc = DWAXIDMAC_TT_FC_MEM_TO_MEM_DMAC; @@ -499,9 +583,11 @@ static void dma_chan_issue_pending(struct dma_chan *dchan) { struct axi_dma_chan *chan = dchan_to_axi_dma_chan(dchan); unsigned long flags; + bool was_empty; spin_lock_irqsave(&chan->vc.lock, flags); - if (vchan_issue_pending(&chan->vc)) + was_empty = list_empty(&chan->vc.desc_issued); + if (vchan_issue_pending(&chan->vc) && was_empty) axi_chan_start_first_queued(chan); spin_unlock_irqrestore(&chan->vc.lock, flags); } @@ -569,7 +655,7 @@ static void dw_axi_dma_set_hw_channel(struct axi_dma_chan *chan, bool set) unsigned long reg_value, val; if (!chip->apb_regs) { - dev_err(chip->dev, "apb_regs not initialized\n"); + dev_dbg(chip->dev, "apb_regs not initialized\n"); return; } @@ -657,34 +743,53 @@ static int dw_axi_dma_set_hw_desc(struct axi_dma_chan *chan, size_t axi_block_ts; size_t block_ts; u32 ctllo, ctlhi; - u32 burst_len; + u32 burst_len = 0, mem_burst_msize, reg_burst_msize; axi_block_ts = chan->chip->dw->hdata->block_size[chan->id]; mem_width = __ffs(data_width | mem_addr | len); - if (mem_width > DWAXIDMAC_TRANS_WIDTH_32) - mem_width = DWAXIDMAC_TRANS_WIDTH_32; if (!IS_ALIGNED(mem_addr, 4)) { dev_err(chan->chip->dev, "invalid buffer alignment\n"); return -EINVAL; } + /* Use a reasonable upper limit otherwise residue reporting granularity grows large */ + mem_burst_msize = axi_dma_encode_msize(16); + switch (chan->direction) { case DMA_MEM_TO_DEV: + reg_burst_msize = axi_dma_encode_msize(chan->config.dst_maxburst); reg_width = __ffs(chan->config.dst_addr_width); - device_addr = chan->config.dst_addr; + device_addr = phys_to_dma(chan->chip->dev, chan->config.dst_addr); ctllo = reg_width << CH_CTL_L_DST_WIDTH_POS | mem_width << CH_CTL_L_SRC_WIDTH_POS | + reg_burst_msize << CH_CTL_L_DST_MSIZE_POS | + mem_burst_msize << CH_CTL_L_SRC_MSIZE_POS | DWAXIDMAC_CH_CTL_L_NOINC << CH_CTL_L_DST_INC_POS | DWAXIDMAC_CH_CTL_L_INC << CH_CTL_L_SRC_INC_POS; block_ts = len >> mem_width; break; case DMA_DEV_TO_MEM: + reg_burst_msize = axi_dma_encode_msize(chan->config.src_maxburst); reg_width = __ffs(chan->config.src_addr_width); - device_addr = chan->config.src_addr; + /* + * For devices where transfer lengths are not known upfront, + * there is a danger when the destination is wider than the + * source that partial words can be lost at the end of a transfer. + * Ideally the controller would be able to flush the residue, but + * it can't - it's not even possible to tell that there is any. + * Instead, allow the client driver to avoid the problem by setting + * a smaller width. + */ + if (chan->config.dst_addr_width && + (chan->config.dst_addr_width < mem_width)) + mem_width = chan->config.dst_addr_width; + device_addr = phys_to_dma(chan->chip->dev, chan->config.src_addr); ctllo = reg_width << CH_CTL_L_SRC_WIDTH_POS | mem_width << CH_CTL_L_DST_WIDTH_POS | + mem_burst_msize << CH_CTL_L_DST_MSIZE_POS | + reg_burst_msize << CH_CTL_L_SRC_MSIZE_POS | DWAXIDMAC_CH_CTL_L_INC << CH_CTL_L_DST_INC_POS | DWAXIDMAC_CH_CTL_L_NOINC << CH_CTL_L_SRC_INC_POS; block_ts = len >> reg_width; @@ -720,14 +825,17 @@ static int dw_axi_dma_set_hw_desc(struct axi_dma_chan *chan, } hw_desc->lli->block_ts_lo = cpu_to_le32(block_ts - 1); - - ctllo |= DWAXIDMAC_BURST_TRANS_LEN_4 << CH_CTL_L_DST_MSIZE_POS | - DWAXIDMAC_BURST_TRANS_LEN_4 << CH_CTL_L_SRC_MSIZE_POS; hw_desc->lli->ctl_lo = cpu_to_le32(ctllo); set_desc_src_master(hw_desc); hw_desc->len = len; + + if (burst_len && (chan->config.src_maxburst > burst_len)) + dev_warn_ratelimited(chan2dev(chan), + "%s: requested source burst length %u exceeds supported burst length %u - data may be lost\n", + axi_chan_name(chan), chan->config.src_maxburst, burst_len); + return 0; } @@ -744,9 +852,6 @@ static size_t calculate_block_len(struct axi_dma_chan *chan, case DMA_MEM_TO_DEV: data_width = BIT(chan->chip->dw->hdata->m_data_width); mem_width = __ffs(data_width | dma_addr | buf_len); - if (mem_width > DWAXIDMAC_TRANS_WIDTH_32) - mem_width = DWAXIDMAC_TRANS_WIDTH_32; - block_len = axi_block_ts << mem_width; break; case DMA_DEV_TO_MEM: @@ -817,6 +922,8 @@ dw_axi_dma_chan_prep_cyclic(struct dma_chan *dchan, dma_addr_t dma_addr, src_addr += segment_len; } + desc->nr_hw_descs = total_segments; + llp = desc->hw_desc[0].llp; /* Managed transfer list */ @@ -882,6 +989,9 @@ dw_axi_dma_chan_prep_slave_sg(struct dma_chan *dchan, struct scatterlist *sgl, mem = sg_dma_address(sg); len = sg_dma_len(sg); num_segments = DIV_ROUND_UP(sg_dma_len(sg), axi_block_len); + if (!num_segments) + continue; + segment_len = DIV_ROUND_UP(sg_dma_len(sg), num_segments); do { @@ -896,6 +1006,8 @@ dw_axi_dma_chan_prep_slave_sg(struct dma_chan *dchan, struct scatterlist *sgl, } while (len >= segment_len); } + desc->nr_hw_descs = loop; + /* Set end-of-link to the last link descriptor of list */ set_desc_last(&desc->hw_desc[num_sgs - 1]); @@ -1003,6 +1115,8 @@ dma_chan_prep_dma_memcpy(struct dma_chan *dchan, dma_addr_t dst_adr, num++; } + desc->nr_hw_descs = num; + /* Set end-of-link to the last link descriptor of list */ set_desc_last(&desc->hw_desc[num - 1]); /* Managed transfer list */ @@ -1051,7 +1165,7 @@ static void axi_chan_dump_lli(struct axi_dma_chan *chan, static void axi_chan_list_dump_lli(struct axi_dma_chan *chan, struct axi_dma_desc *desc_head) { - int count = atomic_read(&chan->descs_allocated); + int count = desc_head->nr_hw_descs; int i; for (i = 0; i < count; i++) @@ -1094,11 +1208,11 @@ static noinline void axi_chan_handle_err(struct axi_dma_chan *chan, u32 status) static void axi_chan_block_xfer_complete(struct axi_dma_chan *chan) { - int count = atomic_read(&chan->descs_allocated); struct axi_dma_hw_desc *hw_desc; struct axi_dma_desc *desc; struct virt_dma_desc *vd; unsigned long flags; + int count; u64 llp; int i; @@ -1120,6 +1234,7 @@ static void axi_chan_block_xfer_complete(struct axi_dma_chan *chan) if (chan->cyclic) { desc = vd_to_axi_desc(vd); if (desc) { + count = desc->nr_hw_descs; llp = lo_hi_readq(chan->chan_regs + CH_LLP); for (i = 0; i < count; i++) { hw_desc = &desc->hw_desc[i]; @@ -1397,6 +1512,10 @@ static int parse_device_properties(struct axi_dma_chip *chip) chip->dw->hdata->nr_masters = tmp; + ret = device_property_read_u32(dev, "snps,dma-targets", &tmp); + if (!ret && tmp > 16) + chip->dw->hdata->use_cfg2 = true; + ret = device_property_read_u32(dev, "snps,data-width", &tmp); if (ret) return ret; @@ -1467,6 +1586,7 @@ static int dw_probe(struct platform_device *pdev) struct dw_axi_dma *dw; struct dw_axi_dma_hcfg *hdata; struct reset_control *resets; + unsigned int max_seg_size; unsigned int flags; u32 i; int ret; @@ -1577,9 +1697,21 @@ static int dw_probe(struct platform_device *pdev) * Synopsis DesignWare AxiDMA datasheet mentioned Maximum * supported blocks is 1024. Device register width is 4 bytes. * Therefore, set constraint to 1024 * 4. + * However, if all channels specify a greater value, use that instead. */ + dw->dma.dev->dma_parms = &dw->dma_parms; - dma_set_max_seg_size(&pdev->dev, MAX_BLOCK_SIZE); + max_seg_size = UINT_MAX; + for (i = 0; i < dw->hdata->nr_channels; i++) { + unsigned int block_size = chip->dw->hdata->block_size[i]; + + if (!block_size) + block_size = MAX_BLOCK_SIZE; + max_seg_size = min(block_size, max_seg_size); + } + + dma_set_max_seg_size(&pdev->dev, max_seg_size); + platform_set_drvdata(pdev, chip); pm_runtime_enable(chip->dev); diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig index 9f35f69e0f9e2b..07872ef6c70038 100644 --- a/drivers/firmware/Kconfig +++ b/drivers/firmware/Kconfig @@ -120,6 +120,15 @@ config RASPBERRYPI_FIRMWARE This option enables support for communicating with the firmware on the Raspberry Pi. +config FIRMWARE_RP1 + tristate "RP1 Firmware Driver" + depends on MBOX_RP1 + help + The Raspberry Pi RP1 processor presents a firmware + interface using shared memory and a mailbox. To enable + the driver that communicates with it, say Y. Otherwise, + say N. + config FW_CFG_SYSFS tristate "QEMU fw_cfg device support in sysfs" depends on SYSFS && (ARM || ARM64 || PARISC || PPC_PMAC || RISCV || SPARC || X86) diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile index 7a8d486e718f86..890925767b078d 100644 --- a/drivers/firmware/Makefile +++ b/drivers/firmware/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_ISCSI_IBFT) += iscsi_ibft.o obj-$(CONFIG_FIRMWARE_MEMMAP) += memmap.o obj-$(CONFIG_MTK_ADSP_IPC) += mtk-adsp-ipc.o obj-$(CONFIG_RASPBERRYPI_FIRMWARE) += raspberrypi.o +obj-$(CONFIG_FIRMWARE_RP1) += rp1.o obj-$(CONFIG_FW_CFG_SYSFS) += qemu_fw_cfg.o obj-$(CONFIG_SYSFB) += sysfb.o obj-$(CONFIG_SYSFB_SIMPLEFB) += sysfb_simplefb.o diff --git a/drivers/firmware/psci/psci.c b/drivers/firmware/psci/psci.c index 2328ca58bba61f..7a1e42237cf207 100644 --- a/drivers/firmware/psci/psci.c +++ b/drivers/firmware/psci/psci.c @@ -315,7 +315,14 @@ static int psci_sys_reset(struct notifier_block *nb, unsigned long action, * reset_type[30:0] = 0 (SYSTEM_WARM_RESET) * cookie = 0 (ignored by the implementation) */ - invoke_psci_fn(PSCI_FN_NATIVE(1_1, SYSTEM_RESET2), 0, 0, 0); + // Allow extra arguments separated by spaces after + // the partition number. + unsigned long val; + u8 partition = 0; + + if (data && sscanf(data, "%lu", &val) == 1 && val < 63) + partition = val; + invoke_psci_fn(PSCI_FN_NATIVE(1_1, SYSTEM_RESET2), 0, partition, 0); } else { invoke_psci_fn(PSCI_0_2_FN_SYSTEM_RESET, 0, 0, 0); } diff --git a/drivers/firmware/raspberrypi.c b/drivers/firmware/raspberrypi.c index 18cc3498710853..a03b7f95a54028 100644 --- a/drivers/firmware/raspberrypi.c +++ b/drivers/firmware/raspberrypi.c @@ -14,6 +14,7 @@ #include <linux/of.h> #include <linux/of_platform.h> #include <linux/platform_device.h> +#include <linux/reboot.h> #include <linux/slab.h> #include <soc/bcm2835/raspberrypi-firmware.h> @@ -34,6 +35,8 @@ struct rpi_firmware { struct kref consumers; }; +static struct platform_device *g_pdev; + static DEFINE_MUTEX(transaction_lock); static void response_callback(struct mbox_client *cl, void *msg) @@ -180,11 +183,61 @@ int rpi_firmware_property(struct rpi_firmware *fw, } EXPORT_SYMBOL_GPL(rpi_firmware_property); +static int rpi_firmware_notify_reboot(struct notifier_block *nb, + unsigned long action, + void *data) +{ + struct rpi_firmware *fw; + struct platform_device *pdev = g_pdev; + u32 reboot_flags = 0; + + if (!pdev) + return 0; + + fw = platform_get_drvdata(pdev); + if (!fw) + return 0; + + // The partition id is the first parameter followed by zero or + // more flags separated by spaces indicating the reason for the reboot. + // + // 'tryboot': Sets a one-shot flag which is cleared upon reboot and + // causes the tryboot.txt to be loaded instead of config.txt + // by the bootloader and the start.elf firmware. + // + // This is intended to allow automatic fallback to a known + // good image if an OS/FW upgrade fails. + // + // N.B. The firmware mechanism for storing reboot flags may vary + // on different Raspberry Pi models. + if (data && strstr(data, " tryboot")) + reboot_flags |= 0x1; + + // The mailbox might have been called earlier, directly via vcmailbox + // so only overwrite if reboot flags are passed to the reboot command. + if (reboot_flags) + (void)rpi_firmware_property(fw, RPI_FIRMWARE_SET_REBOOT_FLAGS, + &reboot_flags, sizeof(reboot_flags)); + + (void)rpi_firmware_property(fw, RPI_FIRMWARE_NOTIFY_REBOOT, NULL, 0); + + return 0; +} + static void rpi_firmware_print_firmware_revision(struct rpi_firmware *fw) { time64_t date_and_time; u32 packet; + static const char * const variant_strs[] = { + "unknown", + "start", + "start_x", + "start_db", + "start_cd", + }; + const char *variant_str = "cmd unsupported"; + u32 variant; int ret = rpi_firmware_property(fw, RPI_FIRMWARE_GET_FIRMWARE_REVISION, &packet, sizeof(packet)); @@ -194,7 +247,35 @@ rpi_firmware_print_firmware_revision(struct rpi_firmware *fw) /* This is not compatible with y2038 */ date_and_time = packet; - dev_info(fw->cl.dev, "Attached to firmware from %ptT\n", &date_and_time); + + ret = rpi_firmware_property(fw, RPI_FIRMWARE_GET_FIRMWARE_VARIANT, + &variant, sizeof(variant)); + + if (!ret) { + if (variant >= ARRAY_SIZE(variant_strs)) + variant = 0; + variant_str = variant_strs[variant]; + } + + dev_info(fw->cl.dev, + "Attached to firmware from %ptT, variant %s\n", + &date_and_time, variant_str); +} + +static void +rpi_firmware_print_firmware_hash(struct rpi_firmware *fw) +{ + u32 hash[5]; + int ret = rpi_firmware_property(fw, + RPI_FIRMWARE_GET_FIRMWARE_HASH, + hash, sizeof(hash)); + + if (ret) + return; + + dev_info(fw->cl.dev, + "Firmware hash is %08x%08x%08x%08x%08x\n", + hash[0], hash[1], hash[2], hash[3], hash[4]); } static void @@ -301,8 +382,10 @@ static int rpi_firmware_probe(struct platform_device *pdev) kref_init(&fw->consumers); platform_set_drvdata(pdev, fw); + g_pdev = pdev; rpi_firmware_print_firmware_revision(fw); + rpi_firmware_print_firmware_hash(fw); rpi_register_hwmon_driver(dev, fw); rpi_register_clk_driver(dev); @@ -329,6 +412,7 @@ static void rpi_firmware_remove(struct platform_device *pdev) rpi_clk = NULL; rpi_firmware_put(fw); + g_pdev = NULL; } static const struct of_device_id rpi_firmware_of_match[] = { @@ -408,7 +492,35 @@ static struct platform_driver rpi_firmware_driver = { .shutdown = rpi_firmware_shutdown, .remove_new = rpi_firmware_remove, }; -module_platform_driver(rpi_firmware_driver); + +static struct notifier_block rpi_firmware_reboot_notifier = { + .notifier_call = rpi_firmware_notify_reboot, +}; + +static int __init rpi_firmware_init(void) +{ + int ret = register_reboot_notifier(&rpi_firmware_reboot_notifier); + if (ret) + goto out1; + ret = platform_driver_register(&rpi_firmware_driver); + if (ret) + goto out2; + + return 0; + +out2: + unregister_reboot_notifier(&rpi_firmware_reboot_notifier); +out1: + return ret; +} +core_initcall(rpi_firmware_init); + +static void __init rpi_firmware_exit(void) +{ + platform_driver_unregister(&rpi_firmware_driver); + unregister_reboot_notifier(&rpi_firmware_reboot_notifier); +} +module_exit(rpi_firmware_exit); MODULE_AUTHOR("Eric Anholt <eric@anholt.net>"); MODULE_DESCRIPTION("Raspberry Pi firmware driver"); diff --git a/drivers/firmware/rp1.c b/drivers/firmware/rp1.c new file mode 100644 index 00000000000000..be33ac6ed2186f --- /dev/null +++ b/drivers/firmware/rp1.c @@ -0,0 +1,308 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2023-24 Raspberry Pi Ltd. + * + * Parts of this driver are based on: + * - raspberrypi.c, by Eric Anholt <eric@anholt.net> + * Copyright (C) 2015 Broadcom + */ + +#include <linux/dma-mapping.h> +#include <linux/kref.h> +#include <linux/mailbox_client.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/rp1-firmware.h> + +#define RP1_MAILBOX_FIRMWARE 0 + +enum rp1_firmware_ops { + MBOX_SUCCESS = 0x0000, + GET_FIRMWARE_VERSION = 0x0001, // na -> 160-bit version + GET_FEATURE = 0x0002, // FOURCC -> op base (0 == unsupported), op count + + COMMON_COUNT +}; + +struct rp1_firmware { + struct mbox_client cl; + struct mbox_chan *chan; /* The doorbell channel */ + uint32_t __iomem *buf; /* The shared buffer */ + u32 buf_size; /* The size of the shared buffer */ + struct completion c; + + struct kref consumers; +}; + +struct rp1_get_feature_resp { + uint32_t op_base; + uint32_t op_count; +}; + +static DEFINE_MUTEX(transaction_lock); + +static const struct of_device_id rp1_firmware_of_match[] = { + { .compatible = "raspberrypi,rp1-firmware", }, + {}, +}; +MODULE_DEVICE_TABLE(of, rp1_firmware_of_match); + +static void response_callback(struct mbox_client *cl, void *msg) +{ + struct rp1_firmware *fw = container_of(cl, struct rp1_firmware, cl); + + complete(&fw->c); +} + +/* + * Sends a request to the RP1 firmware and synchronously waits for the reply. + * Returns zero or a positive count of response bytes on success, negative on + * error. + */ + +int rp1_firmware_message(struct rp1_firmware *fw, uint16_t op, + const void *data, unsigned int data_len, + void *resp, unsigned int resp_space) +{ + int ret; + u32 rc; + + if (data_len + 4 > fw->buf_size) + return -EINVAL; + + mutex_lock(&transaction_lock); + + memcpy_toio(&fw->buf[1], data, data_len); + writel((op << 16) | data_len, fw->buf); + + reinit_completion(&fw->c); + ret = mbox_send_message(fw->chan, NULL); + if (ret >= 0) { + if (wait_for_completion_timeout(&fw->c, HZ)) + ret = 0; + else + ret = -ETIMEDOUT; + } else { + dev_err(fw->cl.dev, "mbox_send_message returned %d\n", ret); + } + + if (ret == 0) { + rc = readl(fw->buf); + if (rc & 0x80000000) { + ret = (int32_t)rc; + } else { + ret = min(rc, resp_space); + memcpy_fromio(resp, &fw->buf[1], ret); + } + } + + mutex_unlock(&transaction_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(rp1_firmware_message); + +static void rp1_firmware_delete(struct kref *kref) +{ + struct rp1_firmware *fw = container_of(kref, struct rp1_firmware, consumers); + + mbox_free_channel(fw->chan); + kfree(fw); +} + +void rp1_firmware_put(struct rp1_firmware *fw) +{ + if (!IS_ERR_OR_NULL(fw)) + kref_put(&fw->consumers, rp1_firmware_delete); +} +EXPORT_SYMBOL_GPL(rp1_firmware_put); + +int rp1_firmware_get_feature(struct rp1_firmware *fw, uint32_t fourcc, + uint32_t *op_base, uint32_t *op_count) +{ + struct rp1_get_feature_resp resp; + int ret; + + memset(&resp, 0, sizeof(resp)); + ret = rp1_firmware_message(fw, GET_FEATURE, + &fourcc, sizeof(fourcc), + &resp, sizeof(resp)); + *op_base = resp.op_base; + *op_count = resp.op_count; + if (ret < 0) + return ret; + if (ret < sizeof(resp) || !resp.op_base) + return -EOPNOTSUPP; + return 0; +} +EXPORT_SYMBOL_GPL(rp1_firmware_get_feature); + +static void devm_rp1_firmware_put(void *data) +{ + struct rp1_firmware *fw = data; + + rp1_firmware_put(fw); +} + +/** + * rp1_firmware_get - Get pointer to rp1_firmware structure. + * + * The reference to rp1_firmware has to be released with rp1_firmware_put(). + * + * Returns an error pointer on failure. + */ +struct rp1_firmware *rp1_firmware_get(struct device_node *client) +{ + const char *match = rp1_firmware_of_match[0].compatible; + struct platform_device *pdev; + struct device_node *fwnode; + struct rp1_firmware *fw = NULL; + + if (!client) + return NULL; + fwnode = of_parse_phandle(client, "firmware", 0); + if (!fwnode) + return NULL; + if (!of_device_is_compatible(fwnode, match)) { + of_node_put(fwnode); + return ERR_PTR(-ENXIO); + } + + pdev = of_find_device_by_node(fwnode); + of_node_put(fwnode); + + if (!pdev) + return ERR_PTR(-ENXIO); + + fw = platform_get_drvdata(pdev); + if (IS_ERR_OR_NULL(fw)) + goto err_exit; + + if (!kref_get_unless_zero(&fw->consumers)) + goto err_exit; + + put_device(&pdev->dev); + + return fw; + +err_exit: + put_device(&pdev->dev); + return fw; +} +EXPORT_SYMBOL_GPL(rp1_firmware_get); + +/** + * devm_rp1_firmware_get - Get pointer to rp1_firmware structure. + * @firmware_node: Pointer to the firmware Device Tree node. + * + * Returns NULL is the firmware device is not ready. + */ +struct rp1_firmware *devm_rp1_firmware_get(struct device *dev, struct device_node *client) +{ + struct rp1_firmware *fw; + int ret; + + fw = rp1_firmware_get(client); + if (IS_ERR_OR_NULL(fw)) + return fw; + + ret = devm_add_action_or_reset(dev, devm_rp1_firmware_put, fw); + if (ret) + return ERR_PTR(ret); + + return fw; +} +EXPORT_SYMBOL_GPL(devm_rp1_firmware_get); + +static int rp1_firmware_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *shmem; + struct rp1_firmware *fw; + struct resource res; + uint32_t version[5]; + int ret; + + shmem = of_parse_phandle(dev->of_node, "shmem", 0); + if (!of_device_is_compatible(shmem, "raspberrypi,rp1-shmem")) { + of_node_put(shmem); + return -ENXIO; + } + + ret = of_address_to_resource(shmem, 0, &res); + of_node_put(shmem); + if (ret) { + dev_err(dev, "failed to get shared memory (%pOF) - %d\n", shmem, ret); + return ret; + } + + /* + * Memory will be freed by rp1_firmware_delete() once all users have + * released their firmware handles. Don't use devm_kzalloc() here. + */ + fw = kzalloc(sizeof(*fw), GFP_KERNEL); + if (!fw) + return -ENOMEM; + + fw->buf_size = resource_size(&res); + fw->buf = devm_ioremap(dev, res.start, fw->buf_size); + if (!fw->buf) { + dev_err(dev, "failed to ioremap shared memory\n"); + kfree(fw); + return -EADDRNOTAVAIL; + } + + fw->cl.dev = dev; + fw->cl.rx_callback = response_callback; + fw->cl.tx_block = false; + + fw->chan = mbox_request_channel(&fw->cl, RP1_MAILBOX_FIRMWARE); + if (IS_ERR(fw->chan)) { + int ret = PTR_ERR(fw->chan); + + if (ret != -EPROBE_DEFER) + dev_err(dev, "Failed to get mbox channel: %d\n", ret); + kfree(fw); + return ret; + } + + init_completion(&fw->c); + kref_init(&fw->consumers); + + ret = rp1_firmware_message(fw, GET_FIRMWARE_VERSION, + NULL, 0, &version, sizeof(version)); + if (ret == sizeof(version)) { + dev_info(dev, "RP1 Firmware version %08x%08x%08x%08x%08x\n", + version[0], version[1], version[2], version[3], version[4]); + platform_set_drvdata(pdev, fw); + } else { + rp1_firmware_put(fw); + platform_set_drvdata(pdev, ERR_PTR(-ENOENT)); + } + + return 0; +} + +static void rp1_firmware_remove(struct platform_device *pdev) +{ + struct rp1_firmware *fw = platform_get_drvdata(pdev); + + rp1_firmware_put(fw); +} + +static struct platform_driver rp1_firmware_driver = { + .driver = { + .name = "rp1-firmware", + .of_match_table = rp1_firmware_of_match, + }, + .probe = rp1_firmware_probe, + .remove = rp1_firmware_remove, +}; + +module_platform_driver(rp1_firmware_driver); + +MODULE_AUTHOR("Phil Elwell <phil@raspberrypi.com>"); +MODULE_DESCRIPTION("RP1 firmware driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index d93cd4f722b401..1962af5ef51206 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -216,6 +216,12 @@ config GPIO_BCM_XGS_IPROC help Say yes here to enable GPIO support for Broadcom XGS iProc SoCs. +config GPIO_BCM_VIRT + bool "Broadcom Virt GPIO" + depends on OF_GPIO && RASPBERRYPI_FIRMWARE && (ARCH_BCM2835 || COMPILE_TEST) + help + Turn on virtual GPIO support for Broadcom BCM283X chips. + config GPIO_BRCMSTB tristate "BRCMSTB GPIO support" default y if (ARCH_BRCMSTB || BMIPS_GENERIC) @@ -549,6 +555,14 @@ config GPIO_PL061 help Say yes here to support the PrimeCell PL061 GPIO device. +config GPIO_PWM + tristate "PWM chip GPIO" + depends on OF_GPIO + depends on PWM + help + Turn on support for exposing a PWM chip as a GPIO + driver. + config GPIO_PXA bool "PXA GPIO support" depends on ARCH_PXA || ARCH_MMP || COMPILE_TEST @@ -1369,6 +1383,15 @@ config GPIO_ELKHARTLAKE To compile this driver as a module, choose M here: the module will be called gpio-elkhartlake. +config GPIO_FSM + tristate "GPIO FSM support" + help + The GPIO FSM driver allows the creation of state machines for + manipulating GPIOs (both real and virtual), with state transitions + triggered by GPIO edges or delays. + + If unsure, say N. + config GPIO_JANZ_TTL tristate "Janz VMOD-TTL Digital IO Module" depends on MFD_JANZ_CMODIO diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 1429e8c0229b92..dad056db13df05 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -39,6 +39,7 @@ obj-$(CONFIG_GPIO_ASPEED_SGPIO) += gpio-aspeed-sgpio.o obj-$(CONFIG_GPIO_ATH79) += gpio-ath79.o obj-$(CONFIG_GPIO_BCM_KONA) += gpio-bcm-kona.o obj-$(CONFIG_GPIO_BCM_XGS_IPROC) += gpio-xgs-iproc.o +obj-$(CONFIG_GPIO_BCM_VIRT) += gpio-bcm-virt.o obj-$(CONFIG_GPIO_BD71815) += gpio-bd71815.o obj-$(CONFIG_GPIO_BD71828) += gpio-bd71828.o obj-$(CONFIG_GPIO_BD9571MWV) += gpio-bd9571mwv.o @@ -63,6 +64,7 @@ obj-$(CONFIG_GPIO_EN7523) += gpio-en7523.o obj-$(CONFIG_GPIO_EP93XX) += gpio-ep93xx.o obj-$(CONFIG_GPIO_EXAR) += gpio-exar.o obj-$(CONFIG_GPIO_F7188X) += gpio-f7188x.o +obj-$(CONFIG_GPIO_FSM) += gpio-fsm.o obj-$(CONFIG_GPIO_FTGPIO010) += gpio-ftgpio010.o obj-$(CONFIG_GPIO_FXL6408) += gpio-fxl6408.o obj-$(CONFIG_GPIO_GE_FPGA) += gpio-ge.o @@ -133,6 +135,7 @@ obj-$(CONFIG_GPIO_PCI_IDIO_16) += gpio-pci-idio-16.o obj-$(CONFIG_GPIO_PISOSR) += gpio-pisosr.o obj-$(CONFIG_GPIO_PL061) += gpio-pl061.o obj-$(CONFIG_GPIO_PMIC_EIC_SPRD) += gpio-pmic-eic-sprd.o +obj-$(CONFIG_GPIO_PWM) += gpio-pwm.o obj-$(CONFIG_GPIO_PXA) += gpio-pxa.o obj-$(CONFIG_GPIO_RASPBERRYPI_EXP) += gpio-raspberrypi-exp.o obj-$(CONFIG_GPIO_RC5T583) += gpio-rc5t583.o diff --git a/drivers/gpio/gpio-bcm-virt.c b/drivers/gpio/gpio-bcm-virt.c new file mode 100644 index 00000000000000..45069c7f0b5a2c --- /dev/null +++ b/drivers/gpio/gpio-bcm-virt.c @@ -0,0 +1,212 @@ +/* + * brcmvirt GPIO driver + * + * Copyright (C) 2012,2013 Dom Cobley <popcornmix@gmail.com> + * Based on gpio-clps711x.c by Alexander Shiyan <shc_work@mail.ru> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/err.h> +#include <linux/gpio/driver.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <soc/bcm2835/raspberrypi-firmware.h> + +#define MODULE_NAME "brcmvirt-gpio" +#define NUM_GPIO 2 + +struct brcmvirt_gpio { + struct gpio_chip gc; + u32 __iomem *ts_base; + /* two packed 16-bit counts of enabled and disables + Allows host to detect a brief enable that was missed */ + u32 enables_disables[NUM_GPIO]; + dma_addr_t bus_addr; +}; + +static int brcmvirt_gpio_dir_in(struct gpio_chip *gc, unsigned off) +{ + struct brcmvirt_gpio *gpio; + gpio = container_of(gc, struct brcmvirt_gpio, gc); + return -EINVAL; +} + +static int brcmvirt_gpio_dir_out(struct gpio_chip *gc, unsigned off, int val) +{ + struct brcmvirt_gpio *gpio; + gpio = container_of(gc, struct brcmvirt_gpio, gc); + return 0; +} + +static int brcmvirt_gpio_get(struct gpio_chip *gc, unsigned off) +{ + struct brcmvirt_gpio *gpio; + unsigned v; + gpio = container_of(gc, struct brcmvirt_gpio, gc); + v = readl(gpio->ts_base + off); + return (s16)((v >> 16) - v) > 0; +} + +static void brcmvirt_gpio_set(struct gpio_chip *gc, unsigned off, int val) +{ + struct brcmvirt_gpio *gpio; + u16 enables, disables; + s16 diff; + bool lit; + gpio = container_of(gc, struct brcmvirt_gpio, gc); + enables = gpio->enables_disables[off] >> 16; + disables = gpio->enables_disables[off] >> 0; + diff = (s16)(enables - disables); + lit = diff > 0; + if ((val && lit) || (!val && !lit)) + return; + if (val) + enables++; + else + disables++; + diff = (s16)(enables - disables); + BUG_ON(diff != 0 && diff != 1); + gpio->enables_disables[off] = (enables << 16) | (disables << 0); + writel(gpio->enables_disables[off], gpio->ts_base + off); +} + +static int brcmvirt_gpio_probe(struct platform_device *pdev) +{ + int err = 0; + struct device *dev = &pdev->dev; + struct device_node *np = dev_of_node(dev); + struct device_node *fw_node; + struct rpi_firmware *fw; + struct brcmvirt_gpio *ucb; + u32 gpiovirtbuf; + + fw_node = of_get_parent(np); + if (!fw_node) { + dev_err(dev, "Missing firmware node\n"); + return -ENOENT; + } + + fw = devm_rpi_firmware_get(&pdev->dev, fw_node); + of_node_put(fw_node); + if (!fw) + return -EPROBE_DEFER; + + ucb = devm_kzalloc(dev, sizeof *ucb, GFP_KERNEL); + if (!ucb) { + err = -EINVAL; + goto out; + } + + ucb->ts_base = dma_alloc_coherent(dev, PAGE_SIZE, &ucb->bus_addr, GFP_KERNEL); + if (!ucb->ts_base) { + pr_err("[%s]: failed to dma_alloc_coherent(%ld)\n", + __func__, PAGE_SIZE); + err = -ENOMEM; + goto out; + } + + gpiovirtbuf = (u32)ucb->bus_addr; + err = rpi_firmware_property(fw, RPI_FIRMWARE_FRAMEBUFFER_SET_GPIOVIRTBUF, + &gpiovirtbuf, sizeof(gpiovirtbuf)); + + if (err || gpiovirtbuf != 0) { + dev_warn(dev, "Failed to set gpiovirtbuf, trying to get err:%x\n", err); + dma_free_coherent(dev, PAGE_SIZE, ucb->ts_base, ucb->bus_addr); + ucb->ts_base = 0; + ucb->bus_addr = 0; + } + + if (!ucb->ts_base) { + err = rpi_firmware_property(fw, RPI_FIRMWARE_FRAMEBUFFER_GET_GPIOVIRTBUF, + &gpiovirtbuf, sizeof(gpiovirtbuf)); + + if (err) { + dev_err(dev, "Failed to get gpiovirtbuf\n"); + goto out; + } + + if (!gpiovirtbuf) { + dev_err(dev, "No virtgpio buffer\n"); + err = -ENOENT; + goto out; + } + + // mmap the physical memory + gpiovirtbuf &= ~0xc0000000; + ucb->ts_base = ioremap(gpiovirtbuf, 4096); + if (ucb->ts_base == NULL) { + dev_err(dev, "Failed to map physical address\n"); + err = -ENOENT; + goto out; + } + ucb->bus_addr = 0; + } + ucb->gc.parent = dev; + ucb->gc.label = MODULE_NAME; + ucb->gc.owner = THIS_MODULE; + ucb->gc.base = -1; + ucb->gc.ngpio = NUM_GPIO; + + ucb->gc.direction_input = brcmvirt_gpio_dir_in; + ucb->gc.direction_output = brcmvirt_gpio_dir_out; + ucb->gc.get = brcmvirt_gpio_get; + ucb->gc.set = brcmvirt_gpio_set; + ucb->gc.can_sleep = true; + + err = gpiochip_add_data(&ucb->gc, NULL); + if (err) + goto out; + + platform_set_drvdata(pdev, ucb); + + return 0; +out: + if (ucb->bus_addr) { + dma_free_coherent(dev, PAGE_SIZE, ucb->ts_base, ucb->bus_addr); + ucb->bus_addr = 0; + ucb->ts_base = NULL; + } else if (ucb->ts_base) { + iounmap(ucb->ts_base); + ucb->ts_base = NULL; + } + return err; +} + +static void brcmvirt_gpio_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct brcmvirt_gpio *ucb = platform_get_drvdata(pdev); + + gpiochip_remove(&ucb->gc); + if (ucb->bus_addr) + dma_free_coherent(dev, PAGE_SIZE, ucb->ts_base, ucb->bus_addr); + else if (ucb->ts_base) + iounmap(ucb->ts_base); +} + +static const struct of_device_id __maybe_unused brcmvirt_gpio_ids[] = { + { .compatible = "brcm,bcm2835-virtgpio" }, + { } +}; +MODULE_DEVICE_TABLE(of, brcmvirt_gpio_ids); + +static struct platform_driver brcmvirt_gpio_driver = { + .driver = { + .name = MODULE_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(brcmvirt_gpio_ids), + }, + .probe = brcmvirt_gpio_probe, + .remove = brcmvirt_gpio_remove, +}; +module_platform_driver(brcmvirt_gpio_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Dom Cobley <popcornmix@gmail.com>"); +MODULE_DESCRIPTION("brcmvirt GPIO driver"); +MODULE_ALIAS("platform:brcmvirt-gpio"); diff --git a/drivers/gpio/gpio-brcmstb.c b/drivers/gpio/gpio-brcmstb.c index 5762e517338eea..b48431c2ec0176 100644 --- a/drivers/gpio/gpio-brcmstb.c +++ b/drivers/gpio/gpio-brcmstb.c @@ -633,6 +633,8 @@ static int brcmstb_gpio_probe(struct platform_device *pdev) #if defined(CONFIG_MIPS) && defined(__BIG_ENDIAN) flags = BGPIOF_BIG_ENDIAN_BYTE_ORDER; #endif + if (of_property_read_bool(np, "brcm,gpio-direct")) + flags |= BGPIOF_REG_DIRECT; of_property_for_each_u32(np, "brcm,gpio-bank-widths", bank_width) { struct brcmstb_gpio_bank *bank; @@ -681,7 +683,9 @@ static int brcmstb_gpio_probe(struct platform_device *pdev) } gc->owner = THIS_MODULE; - gc->label = devm_kasprintf(dev, GFP_KERNEL, "%pOF", np); + gc->label = devm_kasprintf(dev, GFP_KERNEL, "gpio-brcmstb@%zx", + (size_t)res->start + + GIO_BANK_OFF(bank->id, 0)); if (!gc->label) { err = -ENOMEM; goto fail; @@ -689,7 +693,7 @@ static int brcmstb_gpio_probe(struct platform_device *pdev) gc->of_gpio_n_cells = 2; gc->of_xlate = brcmstb_gpio_of_xlate; /* not all ngpio lines are valid, will use bank width later */ - gc->ngpio = MAX_GPIO_PER_BANK; + gc->ngpio = bank_width; gc->offset = bank->id * MAX_GPIO_PER_BANK; gc->request = gpiochip_generic_request; gc->free = gpiochip_generic_free; @@ -700,8 +704,10 @@ static int brcmstb_gpio_probe(struct platform_device *pdev) * Mask all interrupts by default, since wakeup interrupts may * be retained from S5 cold boot */ - need_wakeup_event |= !!__brcmstb_gpio_get_active_irqs(bank); - gc->write_reg(reg_base + GIO_MASK(bank->id), 0); + if (priv->parent_irq > 0) { + need_wakeup_event |= !!__brcmstb_gpio_get_active_irqs(bank); + gc->write_reg(reg_base + GIO_MASK(bank->id), 0); + } err = gpiochip_add_data(gc, bank); if (err) { diff --git a/drivers/gpio/gpio-fsm.c b/drivers/gpio/gpio-fsm.c new file mode 100644 index 00000000000000..785827c70ed7ec --- /dev/null +++ b/drivers/gpio/gpio-fsm.c @@ -0,0 +1,1210 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * GPIO FSM driver + * + * This driver implements simple state machines that allow real GPIOs to be + * controlled in response to inputs from other GPIOs - real and soft/virtual - + * and time delays. It can: + * + create dummy GPIOs for drivers that demand them + * + drive multiple GPIOs from a single input, with optional delays + * + add a debounce circuit to an input + * + drive pattern sequences onto LEDs + * etc. + * + * Copyright (C) 2020 Raspberry Pi (Trading) Ltd. + */ + +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/gpio/driver.h> +#include <linux/interrupt.h> +#include <linux/kdev_t.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/sysfs.h> + +#include <dt-bindings/gpio/gpio-fsm.h> + +#define MODULE_NAME "gpio-fsm" + +#define GF_IO_TYPE(x) ((u32)(x) & 0xffff) +#define GF_IO_INDEX(x) ((u32)(x) >> 16) + +enum { + SIGNAL_GPIO, + SIGNAL_SOFT +}; + +enum { + INPUT_GPIO, + INPUT_SOFT +}; + +enum { + SYM_UNDEFINED, + SYM_NAME, + SYM_SET, + SYM_START, + SYM_SHUTDOWN, + + SYM_MAX +}; + +struct soft_gpio { + int dir; + int value; +}; + +struct input_gpio_state { + struct gpio_fsm *gf; + struct gpio_desc *desc; + struct fsm_state *target; + int index; + int value; + int irq; + bool enabled; + bool active_low; +}; + +struct gpio_event { + int index; + int value; + struct fsm_state *target; +}; + +struct symtab_entry { + const char *name; + void *value; + struct symtab_entry *next; +}; + +struct output_signal { + u8 type; + u8 value; + u16 index; +}; + +struct fsm_state { + const char *name; + struct output_signal *signals; + struct gpio_event *gpio_events; + struct gpio_event *soft_events; + struct fsm_state *delay_target; + struct fsm_state *shutdown_target; + unsigned int num_signals; + unsigned int num_gpio_events; + unsigned int num_soft_events; + unsigned int delay_ms; + unsigned int shutdown_ms; +}; + +struct gpio_fsm { + struct gpio_chip gc; + struct device *dev; + spinlock_t spinlock; + struct work_struct work; + struct timer_list timer; + wait_queue_head_t shutdown_event; + struct fsm_state *states; + struct input_gpio_state *input_gpio_states; + struct gpio_descs *input_gpios; + struct gpio_descs *output_gpios; + struct soft_gpio *soft_gpios; + struct fsm_state *start_state; + struct fsm_state *shutdown_state; + unsigned int num_states; + unsigned int num_output_gpios; + unsigned int num_input_gpios; + unsigned int num_soft_gpios; + unsigned int shutdown_timeout_ms; + unsigned int shutdown_jiffies; + + struct fsm_state *current_state; + struct fsm_state *next_state; + struct fsm_state *delay_target_state; + unsigned long delay_jiffies; + int delay_ms; + unsigned int debug; + bool shutting_down; + struct symtab_entry *symtab; +}; + +static struct symtab_entry *do_add_symbol(struct symtab_entry **symtab, + const char *name, void *value) +{ + struct symtab_entry **p = symtab; + + while (*p && strcmp((*p)->name, name)) + p = &(*p)->next; + + if (*p) { + /* This is an existing symbol */ + if ((*p)->value) { + /* Already defined */ + if (value) { + if ((uintptr_t)value < SYM_MAX) + return ERR_PTR(-EINVAL); + else + return ERR_PTR(-EEXIST); + } + } else { + /* Undefined */ + (*p)->value = value; + } + } else { + /* This is a new symbol */ + *p = kmalloc(sizeof(struct symtab_entry), GFP_KERNEL); + if (*p) { + (*p)->name = name; + (*p)->value = value; + (*p)->next = NULL; + } + } + return *p; +} + +static int add_symbol(struct symtab_entry **symtab, + const char *name, void *value) +{ + struct symtab_entry *sym = do_add_symbol(symtab, name, value); + + return PTR_ERR_OR_ZERO(sym); +} + +static struct symtab_entry *get_symbol(struct symtab_entry **symtab, + const char *name) +{ + struct symtab_entry *sym = do_add_symbol(symtab, name, NULL); + + if (IS_ERR(sym)) + return NULL; + return sym; +} + +static void free_symbols(struct symtab_entry **symtab) +{ + struct symtab_entry *sym = *symtab; + void *p; + + *symtab = NULL; + while (sym) { + p = sym; + sym = sym->next; + kfree(p); + } +} + +static void gpio_fsm_set_soft(struct gpio_fsm *gf, + unsigned int off, int val); + +static void gpio_fsm_enter_state(struct gpio_fsm *gf, + struct fsm_state *state) +{ + struct input_gpio_state *inp_state; + struct output_signal *signal; + struct gpio_event *event; + struct gpio_desc *gpiod; + struct soft_gpio *soft; + int value; + int i; + + dev_dbg(gf->dev, "enter_state(%s)\n", state->name); + + gf->current_state = state; + gf->delay_target_state = NULL; + + // 1. Apply any listed signals + for (i = 0; i < state->num_signals; i++) { + signal = &state->signals[i]; + + if (gf->debug) + dev_info(gf->dev, " set %s %d->%d\n", + (signal->type == SIGNAL_GPIO) ? "GF_OUT" : + "GF_SOFT", + signal->index, signal->value); + switch (signal->type) { + case SIGNAL_GPIO: + gpiod = gf->output_gpios->desc[signal->index]; + gpiod_set_value_cansleep(gpiod, signal->value); + break; + case SIGNAL_SOFT: + soft = &gf->soft_gpios[signal->index]; + gpio_fsm_set_soft(gf, signal->index, signal->value); + break; + } + } + + // 2. Exit if successfully reached shutdown state + if (gf->shutting_down && state == state->shutdown_target) { + wake_up(&gf->shutdown_event); + return; + } + + // 3. Schedule a timer callback if shutting down + if (state->shutdown_target) { + // Remember the absolute shutdown time in case remove is called + // at a later time. + gf->shutdown_jiffies = + jiffies + msecs_to_jiffies(state->shutdown_ms); + + if (gf->shutting_down) { + gf->delay_jiffies = gf->shutdown_jiffies; + gf->delay_target_state = state->shutdown_target; + gf->delay_ms = state->shutdown_ms; + mod_timer(&gf->timer, gf->delay_jiffies); + } + } + + // During shutdown, skip everything else + if (gf->shutting_down) + return; + + // Otherwise record what the shutdown time would be + gf->shutdown_jiffies = jiffies + msecs_to_jiffies(state->shutdown_ms); + + // 4. Check soft inputs for transitions to take + for (i = 0; i < state->num_soft_events; i++) { + event = &state->soft_events[i]; + if (gf->soft_gpios[event->index].value == event->value) { + if (gf->debug) + dev_info(gf->dev, + "GF_SOFT %d=%d -> %s\n", event->index, + event->value, event->target->name); + gpio_fsm_enter_state(gf, event->target); + return; + } + } + + // 5. Check GPIOs for transitions to take, enabling the IRQs + for (i = 0; i < state->num_gpio_events; i++) { + event = &state->gpio_events[i]; + inp_state = &gf->input_gpio_states[event->index]; + inp_state->target = event->target; + inp_state->value = event->value; + inp_state->enabled = true; + + value = gpiod_get_value_cansleep(gf->input_gpios->desc[event->index]); + + // Clear stale event state + disable_irq(inp_state->irq); + + irq_set_irq_type(inp_state->irq, + (inp_state->value ^ inp_state->active_low) ? + IRQF_TRIGGER_RISING : IRQF_TRIGGER_FALLING); + enable_irq(inp_state->irq); + + if (value == event->value && inp_state->target) { + if (gf->debug) + dev_info(gf->dev, + "GF_IN %d=%d -> %s\n", event->index, + event->value, event->target->name); + gpio_fsm_enter_state(gf, event->target); + return; + } + } + + // 6. Schedule a timer callback if delay_target + if (state->delay_target) { + gf->delay_target_state = state->delay_target; + gf->delay_jiffies = jiffies + + msecs_to_jiffies(state->delay_ms); + gf->delay_ms = state->delay_ms; + mod_timer(&gf->timer, gf->delay_jiffies); + } +} + +static void gpio_fsm_go_to_state(struct gpio_fsm *gf, + struct fsm_state *new_state) +{ + struct input_gpio_state *inp_state; + struct gpio_event *gp_ev; + struct fsm_state *state; + int i; + + dev_dbg(gf->dev, "go_to_state(%s)\n", + new_state ? new_state->name : "<unset>"); + + state = gf->current_state; + + /* Disable any enabled GPIO IRQs */ + for (i = 0; i < state->num_gpio_events; i++) { + gp_ev = &state->gpio_events[i]; + inp_state = &gf->input_gpio_states[gp_ev->index]; + if (inp_state->enabled) { + inp_state->enabled = false; + irq_set_irq_type(inp_state->irq, + IRQF_TRIGGER_NONE); + } + } + + gpio_fsm_enter_state(gf, new_state); +} + +static void gpio_fsm_go_to_state_deferred(struct gpio_fsm *gf, + struct fsm_state *new_state) +{ + struct input_gpio_state *inp_state; + struct gpio_event *gp_ev; + struct fsm_state *state; + int i; + + dev_dbg(gf->dev, "go_to_state_deferred(%s)\n", + new_state ? new_state->name : "<unset>"); + + spin_lock(&gf->spinlock); + + if (gf->next_state) { + /* Something else has already requested a transition */ + spin_unlock(&gf->spinlock); + return; + } + + gf->next_state = new_state; + state = gf->current_state; + + /* Disarm any GPIO IRQs */ + for (i = 0; i < state->num_gpio_events; i++) { + gp_ev = &state->gpio_events[i]; + inp_state = &gf->input_gpio_states[gp_ev->index]; + inp_state->target = NULL; + } + + spin_unlock(&gf->spinlock); + + schedule_work(&gf->work); +} + +static void gpio_fsm_work(struct work_struct *work) +{ + struct fsm_state *new_state; + struct gpio_fsm *gf; + + gf = container_of(work, struct gpio_fsm, work); + spin_lock(&gf->spinlock); + new_state = gf->next_state; + gf->next_state = NULL; + spin_unlock(&gf->spinlock); + + gpio_fsm_go_to_state(gf, new_state); +} + +static irqreturn_t gpio_fsm_gpio_irq_handler(int irq, void *dev_id) +{ + struct input_gpio_state *inp_state = dev_id; + struct gpio_fsm *gf = inp_state->gf; + struct fsm_state *target; + + target = inp_state->target; + if (!target) + return IRQ_NONE; + + /* If the IRQ has fired then the desired state _must_ have occurred */ + inp_state->enabled = false; + irq_set_irq_type(inp_state->irq, IRQF_TRIGGER_NONE); + if (gf->debug) + dev_info(gf->dev, "GF_IN %d->%d -> %s\n", + inp_state->index, inp_state->value, target->name); + gpio_fsm_go_to_state_deferred(gf, target); + return IRQ_HANDLED; +} + +static void gpio_fsm_timer(struct timer_list *timer) +{ + struct gpio_fsm *gf = container_of(timer, struct gpio_fsm, timer); + struct fsm_state *target; + + target = gf->delay_target_state; + if (!target) + return; + if (gf->debug) + dev_info(gf->dev, "GF_DELAY %d -> %s\n", gf->delay_ms, + target->name); + + gpio_fsm_go_to_state_deferred(gf, target); +} + +static int gpio_fsm_parse_signals(struct gpio_fsm *gf, struct fsm_state *state, + struct property *prop) +{ + const __be32 *cells = prop->value; + struct output_signal *signal; + u32 io; + u32 type; + u32 index; + u32 value; + int ret = 0; + int i; + + if (prop->length % 8) { + dev_err(gf->dev, "malformed set in state %s\n", + state->name); + return -EINVAL; + } + + state->num_signals = prop->length/8; + state->signals = devm_kcalloc(gf->dev, state->num_signals, + sizeof(struct output_signal), + GFP_KERNEL); + for (i = 0; i < state->num_signals; i++) { + signal = &state->signals[i]; + io = be32_to_cpu(cells[0]); + type = GF_IO_TYPE(io); + index = GF_IO_INDEX(io); + value = be32_to_cpu(cells[1]); + + if (type != GF_OUT && type != GF_SOFT) { + dev_err(gf->dev, + "invalid set type %d in state %s\n", + type, state->name); + ret = -EINVAL; + break; + } + if (type == GF_OUT && index >= gf->num_output_gpios) { + dev_err(gf->dev, + "invalid GF_OUT number %d in state %s\n", + index, state->name); + ret = -EINVAL; + break; + } + if (type == GF_SOFT && index >= gf->num_soft_gpios) { + dev_err(gf->dev, + "invalid GF_SOFT number %d in state %s\n", + index, state->name); + ret = -EINVAL; + break; + } + if (value != 0 && value != 1) { + dev_err(gf->dev, + "invalid set value %d in state %s\n", + value, state->name); + ret = -EINVAL; + break; + } + signal->type = (type == GF_OUT) ? SIGNAL_GPIO : SIGNAL_SOFT; + signal->index = index; + signal->value = value; + cells += 2; + } + + return ret; +} + +static struct gpio_event *new_event(struct gpio_event **events, int *num_events) +{ + int num = ++(*num_events); + *events = krealloc(*events, num * sizeof(struct gpio_event), + GFP_KERNEL); + return *events ? *events + (num - 1) : NULL; +} + +static int gpio_fsm_parse_events(struct gpio_fsm *gf, struct fsm_state *state, + struct property *prop) +{ + const __be32 *cells = prop->value; + struct symtab_entry *sym; + int num_cells; + int ret = 0; + int i; + + if (prop->length % 8) { + dev_err(gf->dev, + "malformed transitions from state %s to state %s\n", + state->name, prop->name); + return -EINVAL; + } + + sym = get_symbol(&gf->symtab, prop->name); + num_cells = prop->length / 4; + i = 0; + while (i < num_cells) { + struct gpio_event *gp_ev; + u32 event, param; + u32 index; + + event = be32_to_cpu(cells[i++]); + param = be32_to_cpu(cells[i++]); + index = GF_IO_INDEX(event); + + switch (GF_IO_TYPE(event)) { + case GF_IN: + if (index >= gf->num_input_gpios) { + dev_err(gf->dev, + "invalid GF_IN %d in transitions from state %s to state %s\n", + index, state->name, prop->name); + return -EINVAL; + } + if (param > 1) { + dev_err(gf->dev, + "invalid GF_IN value %d in transitions from state %s to state %s\n", + param, state->name, prop->name); + return -EINVAL; + } + gp_ev = new_event(&state->gpio_events, + &state->num_gpio_events); + if (!gp_ev) + return -ENOMEM; + gp_ev->index = index; + gp_ev->value = param; + gp_ev->target = (struct fsm_state *)sym; + break; + + case GF_SOFT: + if (index >= gf->num_soft_gpios) { + dev_err(gf->dev, + "invalid GF_SOFT %d in transitions from state %s to state %s\n", + index, state->name, prop->name); + return -EINVAL; + } + if (param > 1) { + dev_err(gf->dev, + "invalid GF_SOFT value %d in transitions from state %s to state %s\n", + param, state->name, prop->name); + return -EINVAL; + } + gp_ev = new_event(&state->soft_events, + &state->num_soft_events); + if (!gp_ev) + return -ENOMEM; + gp_ev->index = index; + gp_ev->value = param; + gp_ev->target = (struct fsm_state *)sym; + break; + + case GF_DELAY: + if (state->delay_target) { + dev_err(gf->dev, + "state %s has multiple GF_DELAYs\n", + state->name); + return -EINVAL; + } + state->delay_target = (struct fsm_state *)sym; + state->delay_ms = param; + break; + + case GF_SHUTDOWN: + if (state->shutdown_target == state) { + dev_err(gf->dev, + "shutdown state %s has GF_SHUTDOWN\n", + state->name); + return -EINVAL; + } else if (state->shutdown_target) { + dev_err(gf->dev, + "state %s has multiple GF_SHUTDOWNs\n", + state->name); + return -EINVAL; + } + state->shutdown_target = + (struct fsm_state *)sym; + state->shutdown_ms = param; + break; + + default: + dev_err(gf->dev, + "invalid event %08x in transitions from state %s to state %s\n", + event, state->name, prop->name); + return -EINVAL; + } + } + if (i != num_cells) { + dev_err(gf->dev, + "malformed transitions from state %s to state %s\n", + state->name, prop->name); + return -EINVAL; + } + + return ret; +} + +static int gpio_fsm_parse_state(struct gpio_fsm *gf, + struct fsm_state *state, + struct device_node *np) +{ + struct symtab_entry *sym; + struct property *prop; + int ret; + + state->name = np->name; + ret = add_symbol(&gf->symtab, np->name, state); + if (ret) { + switch (ret) { + case -EINVAL: + dev_err(gf->dev, "'%s' is not a valid state name\n", + np->name); + break; + case -EEXIST: + dev_err(gf->dev, "state %s already defined\n", + np->name); + break; + default: + dev_err(gf->dev, "error %d adding state %s symbol\n", + ret, np->name); + break; + } + return ret; + } + + for_each_property_of_node(np, prop) { + sym = get_symbol(&gf->symtab, prop->name); + if (!sym) { + ret = -ENOMEM; + break; + } + + switch ((uintptr_t)sym->value) { + case SYM_SET: + ret = gpio_fsm_parse_signals(gf, state, prop); + break; + case SYM_START: + if (gf->start_state) { + dev_err(gf->dev, "multiple start states\n"); + ret = -EINVAL; + } else { + gf->start_state = state; + } + break; + case SYM_SHUTDOWN: + state->shutdown_target = state; + gf->shutdown_state = state; + break; + case SYM_NAME: + /* Ignore */ + break; + default: + /* A set of transition events to this state */ + ret = gpio_fsm_parse_events(gf, state, prop); + break; + } + } + + return ret; +} + +static void dump_all(struct gpio_fsm *gf) +{ + int i, j; + + dev_info(gf->dev, "Input GPIOs:\n"); + for (i = 0; i < gf->num_input_gpios; i++) + dev_info(gf->dev, " %d: %p\n", i, + gf->input_gpios->desc[i]); + + dev_info(gf->dev, "Output GPIOs:\n"); + for (i = 0; i < gf->num_output_gpios; i++) + dev_info(gf->dev, " %d: %p\n", i, + gf->output_gpios->desc[i]); + + dev_info(gf->dev, "Soft GPIOs:\n"); + for (i = 0; i < gf->num_soft_gpios; i++) + dev_info(gf->dev, " %d: %s %d\n", i, + (gf->soft_gpios[i].dir == GPIOF_IN) ? "IN" : "OUT", + gf->soft_gpios[i].value); + + dev_info(gf->dev, "Start state: %s\n", + gf->start_state ? gf->start_state->name : "-"); + + dev_info(gf->dev, "Shutdown timeout: %d ms\n", + gf->shutdown_timeout_ms); + + for (i = 0; i < gf->num_states; i++) { + struct fsm_state *state = &gf->states[i]; + + dev_info(gf->dev, "State %s:\n", state->name); + + if (state->shutdown_target == state) + dev_info(gf->dev, " Shutdown state\n"); + + dev_info(gf->dev, " Signals:\n"); + for (j = 0; j < state->num_signals; j++) { + struct output_signal *signal = &state->signals[j]; + + dev_info(gf->dev, " %d: %s %d=%d\n", j, + (signal->type == SIGNAL_GPIO) ? "GPIO" : + "SOFT", + signal->index, signal->value); + } + + dev_info(gf->dev, " GPIO events:\n"); + for (j = 0; j < state->num_gpio_events; j++) { + struct gpio_event *event = &state->gpio_events[j]; + + dev_info(gf->dev, " %d: %d=%d -> %s\n", j, + event->index, event->value, + event->target->name); + } + + dev_info(gf->dev, " Soft events:\n"); + for (j = 0; j < state->num_soft_events; j++) { + struct gpio_event *event = &state->soft_events[j]; + + dev_info(gf->dev, " %d: %d=%d -> %s\n", j, + event->index, event->value, + event->target->name); + } + + if (state->delay_target) + dev_info(gf->dev, " Delay: %d ms -> %s\n", + state->delay_ms, state->delay_target->name); + + if (state->shutdown_target && state->shutdown_target != state) + dev_info(gf->dev, " Shutdown: %d ms -> %s\n", + state->shutdown_ms, + state->shutdown_target->name); + } + dev_info(gf->dev, "\n"); +} + +static int resolve_sym_to_state(struct gpio_fsm *gf, struct fsm_state **pstate) +{ + struct symtab_entry *sym = (struct symtab_entry *)*pstate; + + if (!sym) + return -ENOMEM; + + *pstate = sym->value; + + if (!*pstate) { + dev_err(gf->dev, "state %s not defined\n", + sym->name); + return -EINVAL; + } + + return 0; +} + +static void gpio_fsm_set_soft(struct gpio_fsm *gf, + unsigned int off, int val) +{ + struct soft_gpio *sg = &gf->soft_gpios[off]; + struct gpio_event *gp_ev; + struct fsm_state *state; + int i; + + dev_dbg(gf->dev, "set(%d,%d)\n", off, val); + state = gf->current_state; + sg->value = val; + for (i = 0; i < state->num_soft_events; i++) { + gp_ev = &state->soft_events[i]; + if (gp_ev->index == off && gp_ev->value == val) { + if (gf->debug) + dev_info(gf->dev, + "GF_SOFT %d->%d -> %s\n", gp_ev->index, + gp_ev->value, gp_ev->target->name); + gpio_fsm_go_to_state(gf, gp_ev->target); + break; + } + } +} + +static int gpio_fsm_get(struct gpio_chip *gc, unsigned int off) +{ + struct gpio_fsm *gf = gpiochip_get_data(gc); + struct soft_gpio *sg; + + if (off >= gf->num_soft_gpios) + return -EINVAL; + sg = &gf->soft_gpios[off]; + + return sg->value; +} + +static void gpio_fsm_set(struct gpio_chip *gc, unsigned int off, int val) +{ + struct gpio_fsm *gf; + + gf = gpiochip_get_data(gc); + if (off < gf->num_soft_gpios) + gpio_fsm_set_soft(gf, off, val); +} + +static int gpio_fsm_get_direction(struct gpio_chip *gc, unsigned int off) +{ + struct gpio_fsm *gf = gpiochip_get_data(gc); + struct soft_gpio *sg; + + if (off >= gf->num_soft_gpios) + return -EINVAL; + sg = &gf->soft_gpios[off]; + + return sg->dir; +} + +static int gpio_fsm_direction_input(struct gpio_chip *gc, unsigned int off) +{ + struct gpio_fsm *gf = gpiochip_get_data(gc); + struct soft_gpio *sg; + + if (off >= gf->num_soft_gpios) + return -EINVAL; + sg = &gf->soft_gpios[off]; + sg->dir = GPIOF_IN; + + return 0; +} + +static int gpio_fsm_direction_output(struct gpio_chip *gc, unsigned int off, + int value) +{ + struct gpio_fsm *gf = gpiochip_get_data(gc); + struct soft_gpio *sg; + + if (off >= gf->num_soft_gpios) + return -EINVAL; + sg = &gf->soft_gpios[off]; + sg->dir = GPIOF_OUT_INIT_LOW; + gpio_fsm_set_soft(gf, off, value); + + return 0; +} + +/* + * /sys/class/gpio-fsm/<fsm-name>/ + * /state ... the current state + */ + +static ssize_t state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + const struct gpio_fsm *gf = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", gf->current_state->name); +} +static DEVICE_ATTR_RO(state); + +static ssize_t delay_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + const struct gpio_fsm *gf = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", + gf->delay_target_state ? gf->delay_target_state->name : + "-"); +} + +static DEVICE_ATTR_RO(delay_state); + +static ssize_t delay_ms_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + const struct gpio_fsm *gf = dev_get_drvdata(dev); + int jiffies_left; + + jiffies_left = max((int)(gf->delay_jiffies - jiffies), 0); + return sprintf(buf, + gf->delay_target_state ? "%u\n" : "-\n", + jiffies_to_msecs(jiffies_left)); +} +static DEVICE_ATTR_RO(delay_ms); + +static struct attribute *gpio_fsm_attrs[] = { + &dev_attr_state.attr, + &dev_attr_delay_state.attr, + &dev_attr_delay_ms.attr, + NULL, +}; + +static const struct attribute_group gpio_fsm_group = { + .attrs = gpio_fsm_attrs, + //.is_visible = gpio_is_visible, +}; + +static const struct attribute_group *gpio_fsm_groups[] = { + &gpio_fsm_group, + NULL +}; + +static struct attribute *gpio_fsm_class_attrs[] = { + // There are no top-level attributes + NULL, +}; +ATTRIBUTE_GROUPS(gpio_fsm_class); + +static struct class gpio_fsm_class = { + .name = MODULE_NAME, + + .class_groups = gpio_fsm_class_groups, +}; + +static int gpio_fsm_probe(struct platform_device *pdev) +{ + struct input_gpio_state *inp_state; + struct device *dev = &pdev->dev; + struct device *sysfs_dev; + struct device_node *np = dev_of_node(dev); + struct device_node *cp; + struct gpio_fsm *gf; + u32 debug = 0; + int num_states; + u32 num_soft_gpios; + int ret; + int i; + static const char *const reserved_symbols[] = { + [SYM_NAME] = "name", + [SYM_SET] = "set", + [SYM_START] = "start_state", + [SYM_SHUTDOWN] = "shutdown_state", + }; + + if (of_property_read_u32(np, "num-swgpios", &num_soft_gpios) && + of_property_read_u32(np, "num-soft-gpios", &num_soft_gpios)) { + dev_err(dev, "missing 'num-swgpios' property\n"); + return -EINVAL; + } + + of_property_read_u32(np, "debug", &debug); + + gf = devm_kzalloc(dev, sizeof(*gf), GFP_KERNEL); + if (!gf) + return -ENOMEM; + + gf->dev = dev; + gf->debug = debug; + + if (of_property_read_u32(np, "shutdown-timeout-ms", + &gf->shutdown_timeout_ms)) + gf->shutdown_timeout_ms = 5000; + + gf->num_soft_gpios = num_soft_gpios; + gf->soft_gpios = devm_kcalloc(dev, num_soft_gpios, + sizeof(struct soft_gpio), GFP_KERNEL); + if (!gf->soft_gpios) + return -ENOMEM; + for (i = 0; i < num_soft_gpios; i++) { + struct soft_gpio *sg = &gf->soft_gpios[i]; + + sg->dir = GPIOF_IN; + sg->value = 0; + } + + gf->input_gpios = devm_gpiod_get_array_optional(dev, "input", GPIOD_IN); + if (IS_ERR(gf->input_gpios)) { + ret = PTR_ERR(gf->input_gpios); + dev_err(dev, "failed to get input gpios from DT - %d\n", ret); + return ret; + } + gf->num_input_gpios = (gf->input_gpios ? gf->input_gpios->ndescs : 0); + + gf->input_gpio_states = devm_kcalloc(dev, gf->num_input_gpios, + sizeof(struct input_gpio_state), + GFP_KERNEL); + if (!gf->input_gpio_states) + return -ENOMEM; + for (i = 0; i < gf->num_input_gpios; i++) { + inp_state = &gf->input_gpio_states[i]; + inp_state->desc = gf->input_gpios->desc[i]; + inp_state->gf = gf; + inp_state->index = i; + inp_state->irq = gpiod_to_irq(inp_state->desc); + inp_state->active_low = gpiod_is_active_low(inp_state->desc); + if (inp_state->irq >= 0) + ret = devm_request_irq(gf->dev, inp_state->irq, + gpio_fsm_gpio_irq_handler, + IRQF_TRIGGER_NONE, + dev_name(dev), + inp_state); + else + ret = inp_state->irq; + + if (ret) { + dev_err(dev, + "failed to get IRQ for input gpio - %d\n", + ret); + return ret; + } + } + + gf->output_gpios = devm_gpiod_get_array_optional(dev, "output", + GPIOD_OUT_LOW); + if (IS_ERR(gf->output_gpios)) { + ret = PTR_ERR(gf->output_gpios); + dev_err(dev, "failed to get output gpios from DT - %d\n", ret); + return ret; + } + gf->num_output_gpios = (gf->output_gpios ? gf->output_gpios->ndescs : + 0); + + num_states = of_get_child_count(np); + if (!num_states) { + dev_err(dev, "no states declared\n"); + return -EINVAL; + } + gf->states = devm_kcalloc(dev, num_states, + sizeof(struct fsm_state), GFP_KERNEL); + if (!gf->states) + return -ENOMEM; + + // add reserved words to the symbol table + for (i = 0; i < ARRAY_SIZE(reserved_symbols); i++) { + if (reserved_symbols[i]) + add_symbol(&gf->symtab, reserved_symbols[i], + (void *)(uintptr_t)i); + } + + // parse the state + for_each_child_of_node(np, cp) { + struct fsm_state *state = &gf->states[gf->num_states]; + + ret = gpio_fsm_parse_state(gf, state, cp); + if (ret) + return ret; + gf->num_states++; + } + + if (!gf->start_state) { + dev_err(gf->dev, "no start state defined\n"); + return -EINVAL; + } + + // resolve symbol pointers into state pointers + for (i = 0; !ret && i < gf->num_states; i++) { + struct fsm_state *state = &gf->states[i]; + int j; + + for (j = 0; !ret && j < state->num_gpio_events; j++) { + struct gpio_event *ev = &state->gpio_events[j]; + + ret = resolve_sym_to_state(gf, &ev->target); + } + + for (j = 0; !ret && j < state->num_soft_events; j++) { + struct gpio_event *ev = &state->soft_events[j]; + + ret = resolve_sym_to_state(gf, &ev->target); + } + + if (!ret) { + resolve_sym_to_state(gf, &state->delay_target); + if (state->shutdown_target != state) + resolve_sym_to_state(gf, + &state->shutdown_target); + } + } + + if (!ret && gf->debug > 1) + dump_all(gf); + + free_symbols(&gf->symtab); + + if (ret) + return ret; + + gf->gc.parent = dev; + gf->gc.label = np->name; + gf->gc.owner = THIS_MODULE; + gf->gc.base = -1; + gf->gc.ngpio = num_soft_gpios; + + gf->gc.get_direction = gpio_fsm_get_direction; + gf->gc.direction_input = gpio_fsm_direction_input; + gf->gc.direction_output = gpio_fsm_direction_output; + gf->gc.get = gpio_fsm_get; + gf->gc.set = gpio_fsm_set; + gf->gc.can_sleep = true; + spin_lock_init(&gf->spinlock); + INIT_WORK(&gf->work, gpio_fsm_work); + timer_setup(&gf->timer, gpio_fsm_timer, 0); + init_waitqueue_head(&gf->shutdown_event); + + platform_set_drvdata(pdev, gf); + + sysfs_dev = device_create_with_groups(&gpio_fsm_class, dev, + MKDEV(0, 0), gf, + gpio_fsm_groups, + "%s", np->name); + if (IS_ERR(sysfs_dev)) + dev_err(gf->dev, "Error creating sysfs entry\n"); + + if (gf->debug) + dev_info(gf->dev, "Start -> %s\n", gf->start_state->name); + + gpio_fsm_enter_state(gf, gf->start_state); + + return devm_gpiochip_add_data(dev, &gf->gc, gf); +} + +static void gpio_fsm_remove(struct platform_device *pdev) +{ + struct gpio_fsm *gf = platform_get_drvdata(pdev); + int i; + + if (gf->shutdown_state) { + if (gf->debug) + dev_info(gf->dev, "Shutting down...\n"); + + spin_lock(&gf->spinlock); + gf->shutting_down = true; + if (gf->current_state->shutdown_target && + gf->current_state->shutdown_target != gf->current_state) { + gf->delay_target_state = + gf->current_state->shutdown_target; + mod_timer(&gf->timer, gf->shutdown_jiffies); + } + spin_unlock(&gf->spinlock); + + wait_event_timeout(gf->shutdown_event, + gf->current_state->shutdown_target == + gf->current_state, + msecs_to_jiffies(gf->shutdown_timeout_ms)); + /* On failure to reach a shutdown state, jump to one */ + if (gf->current_state->shutdown_target != gf->current_state) + gpio_fsm_enter_state(gf, gf->shutdown_state); + } + cancel_work_sync(&gf->work); + del_timer_sync(&gf->timer); + + /* Events aren't allocated from managed storage */ + for (i = 0; i < gf->num_states; i++) { + kfree(gf->states[i].gpio_events); + kfree(gf->states[i].soft_events); + } + if (gf->debug) + dev_info(gf->dev, "Exiting\n"); +} + +static void gpio_fsm_shutdown(struct platform_device *pdev) +{ + gpio_fsm_remove(pdev); +} + +static const struct of_device_id gpio_fsm_ids[] = { + { .compatible = "rpi,gpio-fsm" }, + { } +}; +MODULE_DEVICE_TABLE(of, gpio_fsm_ids); + +static struct platform_driver gpio_fsm_driver = { + .driver = { + .name = MODULE_NAME, + .of_match_table = of_match_ptr(gpio_fsm_ids), + }, + .probe = gpio_fsm_probe, + .remove = gpio_fsm_remove, + .shutdown = gpio_fsm_shutdown, +}; + +static int gpio_fsm_init(void) +{ + int ret; + + ret = class_register(&gpio_fsm_class); + if (ret) + return ret; + + ret = platform_driver_register(&gpio_fsm_driver); + if (ret) + class_unregister(&gpio_fsm_class); + + return ret; +} +module_init(gpio_fsm_init); + +static void gpio_fsm_exit(void) +{ + platform_driver_unregister(&gpio_fsm_driver); + class_unregister(&gpio_fsm_class); +} +module_exit(gpio_fsm_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Phil Elwell <phil@raspberrypi.com>"); +MODULE_DESCRIPTION("GPIO FSM driver"); +MODULE_ALIAS("platform:gpio-fsm"); diff --git a/drivers/gpio/gpio-mmio.c b/drivers/gpio/gpio-mmio.c index d89e78f0ead31f..36bb04afa5633d 100644 --- a/drivers/gpio/gpio-mmio.c +++ b/drivers/gpio/gpio-mmio.c @@ -231,6 +231,25 @@ static void bgpio_set(struct gpio_chip *gc, unsigned int gpio, int val) raw_spin_unlock_irqrestore(&gc->bgpio_lock, flags); } +static void bgpio_set_direct(struct gpio_chip *gc, unsigned int gpio, int val) +{ + unsigned long mask = bgpio_line2mask(gc, gpio); + unsigned long flags; + + raw_spin_lock_irqsave(&gc->bgpio_lock, flags); + + gc->bgpio_data = gc->read_reg(gc->reg_dat); + + if (val) + gc->bgpio_data |= mask; + else + gc->bgpio_data &= ~mask; + + gc->write_reg(gc->reg_dat, gc->bgpio_data); + + raw_spin_unlock_irqrestore(&gc->bgpio_lock, flags); +} + static void bgpio_set_with_clear(struct gpio_chip *gc, unsigned int gpio, int val) { @@ -323,6 +342,27 @@ static void bgpio_set_multiple_with_clear(struct gpio_chip *gc, gc->write_reg(gc->reg_clr, clear_mask); } +static void bgpio_set_multiple_direct(struct gpio_chip *gc, + unsigned long *mask, + unsigned long *bits) +{ + unsigned long flags; + unsigned long set_mask, clear_mask; + + raw_spin_lock_irqsave(&gc->bgpio_lock, flags); + + bgpio_multiple_get_masks(gc, mask, bits, &set_mask, &clear_mask); + + gc->bgpio_data = gc->read_reg(gc->reg_dat); + + gc->bgpio_data |= set_mask; + gc->bgpio_data &= ~clear_mask; + + gc->write_reg(gc->reg_dat, gc->bgpio_data); + + raw_spin_unlock_irqrestore(&gc->bgpio_lock, flags); +} + static int bgpio_simple_dir_in(struct gpio_chip *gc, unsigned int gpio) { return 0; @@ -360,6 +400,29 @@ static int bgpio_dir_in(struct gpio_chip *gc, unsigned int gpio) return 0; } +static int bgpio_dir_in_direct(struct gpio_chip *gc, unsigned int gpio) +{ + unsigned long flags; + + raw_spin_lock_irqsave(&gc->bgpio_lock, flags); + + if (gc->reg_dir_in) + gc->bgpio_dir = ~gc->read_reg(gc->reg_dir_in); + if (gc->reg_dir_out) + gc->bgpio_dir = gc->read_reg(gc->reg_dir_out); + + gc->bgpio_dir &= ~bgpio_line2mask(gc, gpio); + + if (gc->reg_dir_in) + gc->write_reg(gc->reg_dir_in, ~gc->bgpio_dir); + if (gc->reg_dir_out) + gc->write_reg(gc->reg_dir_out, gc->bgpio_dir); + + raw_spin_unlock_irqrestore(&gc->bgpio_lock, flags); + + return 0; +} + static int bgpio_get_dir(struct gpio_chip *gc, unsigned int gpio) { /* Return 0 if output, 1 if input */ @@ -398,6 +461,28 @@ static void bgpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val) raw_spin_unlock_irqrestore(&gc->bgpio_lock, flags); } +static void bgpio_dir_out_direct(struct gpio_chip *gc, unsigned int gpio, + int val) +{ + unsigned long flags; + + raw_spin_lock_irqsave(&gc->bgpio_lock, flags); + + if (gc->reg_dir_in) + gc->bgpio_dir = ~gc->read_reg(gc->reg_dir_in); + if (gc->reg_dir_out) + gc->bgpio_dir = gc->read_reg(gc->reg_dir_out); + + gc->bgpio_dir |= bgpio_line2mask(gc, gpio); + + if (gc->reg_dir_in) + gc->write_reg(gc->reg_dir_in, ~gc->bgpio_dir); + if (gc->reg_dir_out) + gc->write_reg(gc->reg_dir_out, gc->bgpio_dir); + + raw_spin_unlock_irqrestore(&gc->bgpio_lock, flags); +} + static int bgpio_dir_out_dir_first(struct gpio_chip *gc, unsigned int gpio, int val) { @@ -414,6 +499,22 @@ static int bgpio_dir_out_val_first(struct gpio_chip *gc, unsigned int gpio, return 0; } +static int bgpio_dir_out_dir_first_direct(struct gpio_chip *gc, + unsigned int gpio, int val) +{ + bgpio_dir_out_direct(gc, gpio, val); + gc->set(gc, gpio, val); + return 0; +} + +static int bgpio_dir_out_val_first_direct(struct gpio_chip *gc, + unsigned int gpio, int val) +{ + gc->set(gc, gpio, val); + bgpio_dir_out_direct(gc, gpio, val); + return 0; +} + static int bgpio_setup_accessors(struct device *dev, struct gpio_chip *gc, bool byte_be) @@ -507,6 +608,9 @@ static int bgpio_setup_io(struct gpio_chip *gc, } else if (flags & BGPIOF_NO_OUTPUT) { gc->set = bgpio_set_none; gc->set_multiple = NULL; + } else if (flags & BGPIOF_REG_DIRECT) { + gc->set = bgpio_set_direct; + gc->set_multiple = bgpio_set_multiple_direct; } else { gc->set = bgpio_set; gc->set_multiple = bgpio_set_multiple; @@ -543,11 +647,21 @@ static int bgpio_setup_direction(struct gpio_chip *gc, if (dirout || dirin) { gc->reg_dir_out = dirout; gc->reg_dir_in = dirin; - if (flags & BGPIOF_NO_SET_ON_INPUT) - gc->direction_output = bgpio_dir_out_dir_first; - else - gc->direction_output = bgpio_dir_out_val_first; - gc->direction_input = bgpio_dir_in; + if (flags & BGPIOF_REG_DIRECT) { + if (flags & BGPIOF_NO_SET_ON_INPUT) + gc->direction_output = + bgpio_dir_out_dir_first_direct; + else + gc->direction_output = + bgpio_dir_out_val_first_direct; + gc->direction_input = bgpio_dir_in_direct; + } else { + if (flags & BGPIOF_NO_SET_ON_INPUT) + gc->direction_output = bgpio_dir_out_dir_first; + else + gc->direction_output = bgpio_dir_out_val_first; + gc->direction_input = bgpio_dir_in; + } gc->get_direction = bgpio_get_dir; } else { if (flags & BGPIOF_NO_OUTPUT) diff --git a/drivers/gpio/gpio-pca953x.c b/drivers/gpio/gpio-pca953x.c index d764a3af634670..ea12d62e6b099f 100644 --- a/drivers/gpio/gpio-pca953x.c +++ b/drivers/gpio/gpio-pca953x.c @@ -1300,6 +1300,7 @@ static const struct of_device_id pca953x_dt_ids[] = { { .compatible = "ti,tca9535", .data = OF_953X(16, PCA_INT), }, { .compatible = "ti,tca9538", .data = OF_953X( 8, PCA_INT), }, { .compatible = "ti,tca9539", .data = OF_953X(16, PCA_INT), }, + { .compatible = "ti,tca9554", .data = OF_953X( 8, PCA_INT), }, { .compatible = "onnn,cat9554", .data = OF_953X( 8, PCA_INT), }, { .compatible = "onnn,pca9654", .data = OF_953X( 8, PCA_INT), }, diff --git a/drivers/gpio/gpio-pwm.c b/drivers/gpio/gpio-pwm.c new file mode 100644 index 00000000000000..01a2d404d4a310 --- /dev/null +++ b/drivers/gpio/gpio-pwm.c @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * GPIO driver wrapping PWM API + * + * PWM 0% and PWM 100% are equivalent to digital GPIO + * outputs, and there are times where it is useful to use + * PWM outputs as straight GPIOs (eg outputs of NXP PCA9685 + * I2C PWM chip). This driver wraps the PWM API as a GPIO + * controller. + * + * Copyright (C) 2021 Raspberry Pi (Trading) Ltd. + */ + +#include <linux/err.h> +#include <linux/gpio/driver.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> + +struct pwm_gpio { + struct gpio_chip gc; + struct pwm_device **pwm; +}; + +static int pwm_gpio_get_direction(struct gpio_chip *gc, unsigned int off) +{ + return GPIO_LINE_DIRECTION_OUT; +} + +static void pwm_gpio_set(struct gpio_chip *gc, unsigned int off, int val) +{ + struct pwm_gpio *pwm_gpio = gpiochip_get_data(gc); + struct pwm_state state; + + pwm_get_state(pwm_gpio->pwm[off], &state); + state.duty_cycle = val ? state.period : 0; + pwm_apply_might_sleep(pwm_gpio->pwm[off], &state); +} + +static int pwm_gpio_parse_dt(struct pwm_gpio *pwm_gpio, + struct device *dev) +{ + struct device_node *node = dev->of_node; + struct pwm_state state; + int ret = 0, i, num_gpios; + const char *pwm_name; + + if (!node) + return -ENODEV; + + num_gpios = of_property_count_strings(node, "pwm-names"); + if (num_gpios <= 0) + return 0; + + pwm_gpio->pwm = devm_kzalloc(dev, + sizeof(*pwm_gpio->pwm) * num_gpios, + GFP_KERNEL); + if (!pwm_gpio->pwm) + return -ENOMEM; + + for (i = 0; i < num_gpios; i++) { + ret = of_property_read_string_index(node, "pwm-names", i, + &pwm_name); + if (ret) { + dev_err(dev, "unable to get pwm device index %d, name %s", + i, pwm_name); + goto error; + } + + pwm_gpio->pwm[i] = devm_pwm_get(dev, pwm_name); + if (IS_ERR(pwm_gpio->pwm[i])) { + ret = PTR_ERR(pwm_gpio->pwm[i]); + if (ret != -EPROBE_DEFER) + dev_err(dev, "unable to request PWM\n"); + goto error; + } + + /* Sync up PWM state. */ + pwm_init_state(pwm_gpio->pwm[i], &state); + + state.duty_cycle = 0; + pwm_apply_might_sleep(pwm_gpio->pwm[i], &state); + } + + pwm_gpio->gc.ngpio = num_gpios; + +error: + return ret; +} + +static int pwm_gpio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct pwm_gpio *pwm_gpio; + int ret; + + pwm_gpio = devm_kzalloc(dev, sizeof(*pwm_gpio), GFP_KERNEL); + if (!pwm_gpio) + return -ENOMEM; + + pwm_gpio->gc.parent = dev; + pwm_gpio->gc.label = "pwm-gpio"; + pwm_gpio->gc.owner = THIS_MODULE; + pwm_gpio->gc.fwnode = dev->fwnode; + pwm_gpio->gc.base = -1; + + pwm_gpio->gc.get_direction = pwm_gpio_get_direction; + pwm_gpio->gc.set = pwm_gpio_set; + pwm_gpio->gc.can_sleep = true; + + ret = pwm_gpio_parse_dt(pwm_gpio, dev); + if (ret) + return ret; + + if (!pwm_gpio->gc.ngpio) + return 0; + + return devm_gpiochip_add_data(dev, &pwm_gpio->gc, pwm_gpio); +} + +static void pwm_gpio_remove(struct platform_device *pdev) +{ +} + +static const struct of_device_id pwm_gpio_of_match[] = { + { .compatible = "pwm-gpio" }, + { } +}; +MODULE_DEVICE_TABLE(of, pwm_gpio_of_match); + +static struct platform_driver pwm_gpio_driver = { + .driver = { + .name = "pwm-gpio", + .of_match_table = of_match_ptr(pwm_gpio_of_match), + }, + .probe = pwm_gpio_probe, + .remove = pwm_gpio_remove, +}; +module_platform_driver(pwm_gpio_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Dave Stevenson <dave.stevenson@raspberrypi.com>"); +MODULE_DESCRIPTION("PWM GPIO driver"); diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 209871c219d697..c0387fca533abb 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -51,6 +51,8 @@ * GPIOs can sometimes cost only an instruction or two per bit. */ +#define dont_test_bit(b,d) (0) + /* Device and char device-related information */ static DEFINE_IDA(gpio_ida); static dev_t gpio_devt; @@ -103,6 +105,7 @@ static int gpiochip_irqchip_init_valid_mask(struct gpio_chip *gc); static void gpiochip_irqchip_free_valid_mask(struct gpio_chip *gc); static bool gpiolib_initialized; +static int first_dynamic_gpiochip_num = -1; const char *gpiod_get_label(struct gpio_desc *desc) { @@ -925,6 +928,7 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data, unsigned int desc_index; int base = 0; int ret = 0; + int id; /* * First: allocate and populate the internal stat container, and @@ -951,7 +955,16 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data, else if (gc->parent) device_set_node(&gdev->dev, dev_fwnode(gc->parent)); - gdev->id = ida_alloc(&gpio_ida, GFP_KERNEL); + if (first_dynamic_gpiochip_num < 0) { + id = of_alias_get_highest_id("gpiochip"); + first_dynamic_gpiochip_num = (id >= 0) ? (id + 1) : 0; + } + + id = of_alias_get_id(gdev->dev.of_node, "gpiochip"); + if (id < 0) + id = first_dynamic_gpiochip_num; + + gdev->id = ida_alloc_range(&gpio_ida, id, ~0, GFP_KERNEL); if (gdev->id < 0) { ret = gdev->id; goto err_free_gdev; @@ -2814,8 +2827,8 @@ int gpiod_direction_output(struct gpio_desc *desc, int value) value = !!value; /* GPIOs used for enabled IRQs shall not be set as output */ - if (test_bit(FLAG_USED_AS_IRQ, &flags) && - test_bit(FLAG_IRQ_IS_ENABLED, &flags)) { + if (dont_test_bit(FLAG_USED_AS_IRQ, &flags) && + dont_test_bit(FLAG_IRQ_IS_ENABLED, &flags)) { gpiod_err(desc, "%s: tried to set a GPIO tied to an IRQ as output\n", __func__); @@ -3766,8 +3779,8 @@ int gpiochip_lock_as_irq(struct gpio_chip *gc, unsigned int offset) } /* To be valid for IRQ the line needs to be input or open drain */ - if (test_bit(FLAG_IS_OUT, &desc->flags) && - !test_bit(FLAG_OPEN_DRAIN, &desc->flags)) { + if (dont_test_bit(FLAG_IS_OUT, &desc->flags) && + !dont_test_bit(FLAG_OPEN_DRAIN, &desc->flags)) { chip_err(gc, "%s: tried to flag a GPIO set as output for IRQ\n", __func__); diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 7408ea8caacc3c..fd58d703844461 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -424,6 +424,8 @@ source "drivers/gpu/drm/v3d/Kconfig" source "drivers/gpu/drm/vc4/Kconfig" +source "drivers/gpu/drm/rp1/Kconfig" + source "drivers/gpu/drm/loongson/Kconfig" source "drivers/gpu/drm/etnaviv/Kconfig" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 84746054c721a3..90bcbee9bec341 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -220,3 +220,4 @@ obj-y += solomon/ obj-$(CONFIG_DRM_SPRD) += sprd/ obj-$(CONFIG_DRM_LOONGSON) += loongson/ obj-$(CONFIG_DRM_POWERVR) += imagination/ +obj-y += rp1/ diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 3eb955333c809e..6fb668a2e2f89d 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -73,6 +73,7 @@ config DRM_CROS_EC_ANX7688 config DRM_DISPLAY_CONNECTOR tristate "Display connector support" depends on OF + select DRM_KMS_HELPER help Driver for display connectors with support for DDC and hot-plug detection. Most display controllers handle display connectors diff --git a/drivers/gpu/drm/bridge/panel.c b/drivers/gpu/drm/bridge/panel.c index 6e88339dec0f5f..718c86c47615a4 100644 --- a/drivers/gpu/drm/bridge/panel.c +++ b/drivers/gpu/drm/bridge/panel.c @@ -16,6 +16,7 @@ #include <drm/drm_panel.h> #include <drm/drm_print.h> #include <drm/drm_probe_helper.h> +#include <linux/backlight.h> struct panel_bridge { struct drm_bridge bridge; @@ -78,11 +79,18 @@ static int panel_bridge_attach(struct drm_bridge *bridge, return ret; } + connector->interlace_allowed = true; + drm_panel_bridge_set_orientation(connector, bridge); drm_connector_attach_encoder(&panel_bridge->connector, bridge->encoder); +#if IS_REACHABLE(CONFIG_BACKLIGHT_CLASS_DEVICE) + backlight_set_display_name(panel_bridge->panel->backlight, + panel_bridge->connector.name); +#endif + if (bridge->dev->registered) { if (connector->funcs->reset) connector->funcs->reset(connector); diff --git a/drivers/gpu/drm/bridge/tc358762.c b/drivers/gpu/drm/bridge/tc358762.c index 46198af9eebbf8..3f6db0f66812e2 100644 --- a/drivers/gpu/drm/bridge/tc358762.c +++ b/drivers/gpu/drm/bridge/tc358762.c @@ -53,6 +53,12 @@ #define LCDCTRL_VSPOL BIT(19) /* Polarity of VSYNC signal */ #define LCDCTRL_VSDELAY(v) (((v) & 0xfff) << 20) /* VSYNC delay */ +/* First parameter is in the 16bits, second is in the top 16bits */ +#define LCD_HS_HBP 0x0424 +#define LCD_HDISP_HFP 0x0428 +#define LCD_VS_VBP 0x042c +#define LCD_VDISP_VFP 0x0430 + /* SPI Master Registers */ #define SPICMR 0x0450 #define SPITCR 0x0454 @@ -139,6 +145,15 @@ static int tc358762_init(struct tc358762 *ctx) tc358762_write(ctx, LCDCTRL, lcdctrl); tc358762_write(ctx, SYSCTRL, 0x040f); + + tc358762_write(ctx, LCD_HS_HBP, (ctx->mode.hsync_end - ctx->mode.hsync_start) | + ((ctx->mode.htotal - ctx->mode.hsync_end) << 16)); + tc358762_write(ctx, LCD_HDISP_HFP, ctx->mode.hdisplay | + ((ctx->mode.hsync_start - ctx->mode.hdisplay) << 16)); + tc358762_write(ctx, LCD_VS_VBP, (ctx->mode.vsync_end - ctx->mode.vsync_start) | + ((ctx->mode.vtotal - ctx->mode.vsync_end) << 16)); + tc358762_write(ctx, LCD_VDISP_VFP, ctx->mode.vdisplay | + ((ctx->mode.vsync_start - ctx->mode.vdisplay) << 16)); msleep(100); tc358762_write(ctx, PPI_STARTPPI, PPI_START_FUNCTION); @@ -185,14 +200,6 @@ static void tc358762_pre_enable(struct drm_bridge *bridge, struct drm_bridge_sta usleep_range(5000, 10000); } - ctx->pre_enabled = true; -} - -static void tc358762_enable(struct drm_bridge *bridge, struct drm_bridge_state *state) -{ - struct tc358762 *ctx = bridge_to_tc358762(bridge); - int ret; - ret = tc358762_init(ctx); if (ret < 0) dev_err(ctx->dev, "error initializing bridge (%d)\n", ret); @@ -219,7 +226,6 @@ static void tc358762_bridge_mode_set(struct drm_bridge *bridge, static const struct drm_bridge_funcs tc358762_bridge_funcs = { .atomic_post_disable = tc358762_post_disable, .atomic_pre_enable = tc358762_pre_enable, - .atomic_enable = tc358762_enable, .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, .atomic_reset = drm_atomic_helper_bridge_reset, @@ -294,7 +300,7 @@ static int tc358762_probe(struct mipi_dsi_device *dsi) ret = mipi_dsi_attach(dsi); if (ret < 0) { drm_bridge_remove(&ctx->bridge); - dev_err(dev, "failed to attach dsi\n"); + dev_err_probe(dev, ret, "failed to attach dsi\n"); } return ret; diff --git a/drivers/gpu/drm/display/drm_hdmi_state_helper.c b/drivers/gpu/drm/display/drm_hdmi_state_helper.c index 936a8f95d80f7e..34d411245fe89e 100644 --- a/drivers/gpu/drm/display/drm_hdmi_state_helper.c +++ b/drivers/gpu/drm/display/drm_hdmi_state_helper.c @@ -24,7 +24,7 @@ void __drm_atomic_helper_connector_hdmi_reset(struct drm_connector *connector, unsigned int max_bpc = connector->max_bpc; new_conn_state->max_bpc = max_bpc; - new_conn_state->max_requested_bpc = max_bpc; + new_conn_state->max_requested_bpc = 8; new_conn_state->hdmi.broadcast_rgb = DRM_HDMI_BROADCAST_RGB_AUTO; } EXPORT_SYMBOL(__drm_atomic_helper_connector_hdmi_reset); @@ -294,6 +294,11 @@ hdmi_compute_format(const struct drm_connector *connector, return 0; } + if (hdmi_try_format_bpc(connector, conn_state, mode, bpc, HDMI_COLORSPACE_YUV422)) { + conn_state->hdmi.output_format = HDMI_COLORSPACE_YUV422; + return 0; + } + drm_dbg_kms(dev, "Failed. No Format Supported for that bpc count.\n"); return -EINVAL; diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c index 0fc99da93afe1d..5427d67d51e66a 100644 --- a/drivers/gpu/drm/drm_atomic.c +++ b/drivers/gpu/drm/drm_atomic.c @@ -449,7 +449,7 @@ static void drm_atomic_crtc_print_state(struct drm_printer *p, drm_printf(p, "\tactive_changed=%d\n", state->active_changed); drm_printf(p, "\tconnectors_changed=%d\n", state->connectors_changed); drm_printf(p, "\tcolor_mgmt_changed=%d\n", state->color_mgmt_changed); - drm_printf(p, "\tplane_mask=%x\n", state->plane_mask); + drm_printf(p, "\tplane_mask=%llx\n", state->plane_mask); drm_printf(p, "\tconnector_mask=%x\n", state->connector_mask); drm_printf(p, "\tencoder_mask=%x\n", state->encoder_mask); drm_printf(p, "\tmode: " DRM_MODE_FMT "\n", DRM_MODE_ARG(&state->mode)); diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c index 5186d2114a5037..fba2e08e16c930 100644 --- a/drivers/gpu/drm/drm_atomic_helper.c +++ b/drivers/gpu/drm/drm_atomic_helper.c @@ -444,6 +444,11 @@ mode_fixup(struct drm_atomic_state *state) new_crtc_state = drm_atomic_get_new_crtc_state(state, new_conn_state->crtc); + if (!new_crtc_state->mode_changed && + !new_crtc_state->connectors_changed) { + continue; + } + /* * Each encoder has at most one connector (since we always steal * it away), so we won't call ->mode_fixup twice. @@ -1651,13 +1656,6 @@ drm_atomic_helper_wait_for_vblanks(struct drm_device *dev, int i, ret; unsigned int crtc_mask = 0; - /* - * Legacy cursor ioctls are completely unsynced, and userspace - * relies on that (by doing tons of cursor updates). - */ - if (old_state->legacy_cursor_update) - return; - for_each_oldnew_crtc_in_state(old_state, crtc, old_crtc_state, new_crtc_state, i) { if (!new_crtc_state->active) continue; @@ -2307,12 +2305,6 @@ int drm_atomic_helper_setup_commit(struct drm_atomic_state *state, continue; } - /* Legacy cursor updates are fully unsynced. */ - if (state->legacy_cursor_update) { - complete_all(&commit->flip_done); - continue; - } - if (!new_crtc_state->event) { commit->event = kzalloc(sizeof(*commit->event), GFP_KERNEL); diff --git a/drivers/gpu/drm/drm_atomic_state_helper.c b/drivers/gpu/drm/drm_atomic_state_helper.c index 519228eb109533..c7ff2123780650 100644 --- a/drivers/gpu/drm/drm_atomic_state_helper.c +++ b/drivers/gpu/drm/drm_atomic_state_helper.c @@ -267,6 +267,20 @@ void __drm_atomic_helper_plane_state_reset(struct drm_plane_state *plane_state, plane_state->color_range = val; } + if (plane->chroma_siting_h_property) { + if (!drm_object_property_get_default_value(&plane->base, + plane->chroma_siting_h_property, + &val)) + plane_state->chroma_siting_h = val; + } + + if (plane->chroma_siting_v_property) { + if (!drm_object_property_get_default_value(&plane->base, + plane->chroma_siting_v_property, + &val)) + plane_state->chroma_siting_v = val; + } + if (plane->zpos_property) { if (!drm_object_property_get_default_value(&plane->base, plane->zpos_property, diff --git a/drivers/gpu/drm/drm_atomic_uapi.c b/drivers/gpu/drm/drm_atomic_uapi.c index 370dc676e3aa54..63ac578801b5fb 100644 --- a/drivers/gpu/drm/drm_atomic_uapi.c +++ b/drivers/gpu/drm/drm_atomic_uapi.c @@ -538,6 +538,10 @@ static int drm_atomic_plane_set_property(struct drm_plane *plane, state->color_encoding = val; } else if (property == plane->color_range_property) { state->color_range = val; + } else if (property == plane->chroma_siting_h_property) { + state->chroma_siting_h = val; + } else if (property == plane->chroma_siting_v_property) { + state->chroma_siting_v = val; } else if (property == config->prop_fb_damage_clips) { ret = drm_property_replace_blob_from_id(dev, &state->fb_damage_clips, @@ -620,6 +624,10 @@ drm_atomic_plane_get_property(struct drm_plane *plane, *val = state->color_encoding; } else if (property == plane->color_range_property) { *val = state->color_range; + } else if (property == plane->chroma_siting_h_property) { + *val = state->chroma_siting_h; + } else if (property == plane->chroma_siting_v_property) { + *val = state->chroma_siting_v; } else if (property == config->prop_fb_damage_clips) { *val = (state->fb_damage_clips) ? state->fb_damage_clips->base.id : 0; @@ -671,6 +679,7 @@ static int drm_atomic_connector_set_property(struct drm_connector *connector, { struct drm_device *dev = connector->dev; struct drm_mode_config *config = &dev->mode_config; + bool margins_updated = false; bool replaced = false; int ret; @@ -699,12 +708,16 @@ static int drm_atomic_connector_set_property(struct drm_connector *connector, state->tv.subconnector = val; } else if (property == config->tv_left_margin_property) { state->tv.margins.left = val; + margins_updated = true; } else if (property == config->tv_right_margin_property) { state->tv.margins.right = val; + margins_updated = true; } else if (property == config->tv_top_margin_property) { state->tv.margins.top = val; + margins_updated = true; } else if (property == config->tv_bottom_margin_property) { state->tv.margins.bottom = val; + margins_updated = true; } else if (property == config->legacy_tv_mode_property) { state->tv.legacy_mode = val; } else if (property == config->tv_mode_property) { @@ -778,6 +791,14 @@ static int drm_atomic_connector_set_property(struct drm_connector *connector, state->privacy_screen_sw_state = val; } else if (property == connector->broadcast_rgb_property) { state->hdmi.broadcast_rgb = val; + } else if (property == connector->rotation_property) { + if (!is_power_of_2(val & DRM_MODE_ROTATE_MASK)) { + drm_dbg_atomic(connector->dev, + "[CONNECTOR:%d:%s] bad rotation bitmask: 0x%llx\n", + connector->base.id, connector->name, val); + return -EINVAL; + } + state->rotation = val; } else if (connector->funcs->atomic_set_property) { return connector->funcs->atomic_set_property(connector, state, property, val); @@ -789,6 +810,12 @@ static int drm_atomic_connector_set_property(struct drm_connector *connector, return -EINVAL; } + if (margins_updated && state->crtc) { + ret = drm_atomic_add_affected_planes(state->state, state->crtc); + + return ret; + } + return 0; } @@ -863,6 +890,8 @@ drm_atomic_connector_get_property(struct drm_connector *connector, *val = state->privacy_screen_sw_state; } else if (property == connector->broadcast_rgb_property) { *val = state->hdmi.broadcast_rgb; + } else if (property == connector->rotation_property) { + *val = state->rotation; } else if (connector->funcs->atomic_get_property) { return connector->funcs->atomic_get_property(connector, state, property, val); diff --git a/drivers/gpu/drm/drm_blend.c b/drivers/gpu/drm/drm_blend.c index 6e74de8334663c..4e63000713dbfd 100644 --- a/drivers/gpu/drm/drm_blend.c +++ b/drivers/gpu/drm/drm_blend.c @@ -235,6 +235,16 @@ int drm_plane_create_alpha_property(struct drm_plane *plane) } EXPORT_SYMBOL(drm_plane_create_alpha_property); +static const struct drm_prop_enum_list drm_rotate_props[] = { + { __builtin_ffs(DRM_MODE_ROTATE_0) - 1, "rotate-0" }, + { __builtin_ffs(DRM_MODE_ROTATE_90) - 1, "rotate-90" }, + { __builtin_ffs(DRM_MODE_ROTATE_180) - 1, "rotate-180" }, + { __builtin_ffs(DRM_MODE_ROTATE_270) - 1, "rotate-270" }, + { __builtin_ffs(DRM_MODE_REFLECT_X) - 1, "reflect-x" }, + { __builtin_ffs(DRM_MODE_REFLECT_Y) - 1, "reflect-y" }, + { __builtin_ffs(DRM_MODE_TRANSPOSE) - 1, "transpose" }, +}; + /** * drm_plane_create_rotation_property - create a new rotation property * @plane: drm plane @@ -263,6 +273,8 @@ EXPORT_SYMBOL(drm_plane_create_alpha_property); * "reflect-x" * DRM_MODE_REFLECT_Y: * "reflect-y" + * DRM_MODE_TRANSPOSE: + * "transpose" * * Rotation is the specified amount in degrees in counter clockwise direction, * the X and Y axis are within the source rectangle, i.e. the X/Y axis before @@ -273,14 +285,6 @@ int drm_plane_create_rotation_property(struct drm_plane *plane, unsigned int rotation, unsigned int supported_rotations) { - static const struct drm_prop_enum_list props[] = { - { __builtin_ffs(DRM_MODE_ROTATE_0) - 1, "rotate-0" }, - { __builtin_ffs(DRM_MODE_ROTATE_90) - 1, "rotate-90" }, - { __builtin_ffs(DRM_MODE_ROTATE_180) - 1, "rotate-180" }, - { __builtin_ffs(DRM_MODE_ROTATE_270) - 1, "rotate-270" }, - { __builtin_ffs(DRM_MODE_REFLECT_X) - 1, "reflect-x" }, - { __builtin_ffs(DRM_MODE_REFLECT_Y) - 1, "reflect-y" }, - }; struct drm_property *prop; WARN_ON((supported_rotations & DRM_MODE_ROTATE_MASK) == 0); @@ -288,7 +292,8 @@ int drm_plane_create_rotation_property(struct drm_plane *plane, WARN_ON(rotation & ~supported_rotations); prop = drm_property_create_bitmask(plane->dev, 0, "rotation", - props, ARRAY_SIZE(props), + drm_rotate_props, + ARRAY_SIZE(drm_rotate_props), supported_rotations); if (!prop) return -ENOMEM; @@ -304,6 +309,34 @@ int drm_plane_create_rotation_property(struct drm_plane *plane, } EXPORT_SYMBOL(drm_plane_create_rotation_property); +int drm_connector_create_rotation_property(struct drm_connector *conn, + unsigned int rotation, + unsigned int supported_rotations) +{ + struct drm_property *prop; + + WARN_ON((supported_rotations & DRM_MODE_ROTATE_MASK) == 0); + WARN_ON(!is_power_of_2(rotation & DRM_MODE_ROTATE_MASK)); + WARN_ON(rotation & ~supported_rotations); + + prop = drm_property_create_bitmask(conn->dev, 0, "rotation", + drm_rotate_props, + ARRAY_SIZE(drm_rotate_props), + supported_rotations); + if (!prop) + return -ENOMEM; + + drm_object_attach_property(&conn->base, prop, rotation); + + if (conn->state) + conn->state->rotation = rotation; + + conn->rotation_property = prop; + + return 0; +} +EXPORT_SYMBOL(drm_connector_create_rotation_property); + /** * drm_rotation_simplify() - Try to simplify the rotation * @rotation: Rotation to be simplified diff --git a/drivers/gpu/drm/drm_color_mgmt.c b/drivers/gpu/drm/drm_color_mgmt.c index 3969dc548cff60..c183da01b8fbcc 100644 --- a/drivers/gpu/drm/drm_color_mgmt.c +++ b/drivers/gpu/drm/drm_color_mgmt.c @@ -330,7 +330,9 @@ static int drm_crtc_legacy_gamma_set(struct drm_crtc *crtc, replaced = drm_property_replace_blob(&crtc_state->degamma_lut, use_gamma_lut ? NULL : blob); replaced |= drm_property_replace_blob(&crtc_state->ctm, NULL); - replaced |= drm_property_replace_blob(&crtc_state->gamma_lut, + if (!crtc_state->gamma_lut || !crtc_state->gamma_lut->data || + memcmp(crtc_state->gamma_lut->data, blob_data, blob->length)) + replaced |= drm_property_replace_blob(&crtc_state->gamma_lut, use_gamma_lut ? blob : NULL); crtc_state->color_mgmt_changed |= replaced; @@ -588,6 +590,42 @@ int drm_plane_create_color_properties(struct drm_plane *plane, } EXPORT_SYMBOL(drm_plane_create_color_properties); +/** + * drm_plane_create_chroma_siting_properties - chroma siting related plane properties + * @plane: plane object + * + * Create and attach plane specific CHROMA_SITING + * properties to @plane. + */ +int drm_plane_create_chroma_siting_properties(struct drm_plane *plane, + int32_t default_chroma_siting_h, + int32_t default_chroma_siting_v) +{ + struct drm_device *dev = plane->dev; + struct drm_property *prop; + + prop = drm_property_create_range(dev, 0, "CHROMA_SITING_H", + 0, 1<<16); + if (!prop) + return -ENOMEM; + plane->chroma_siting_h_property = prop; + drm_object_attach_property(&plane->base, prop, default_chroma_siting_h); + + prop = drm_property_create_range(dev, 0, "CHROMA_SITING_V", + 0, 1<<16); + if (!prop) + return -ENOMEM; + plane->chroma_siting_v_property = prop; + drm_object_attach_property(&plane->base, prop, default_chroma_siting_v); + + if (plane->state) { + plane->state->chroma_siting_h = default_chroma_siting_h; + plane->state->chroma_siting_v = default_chroma_siting_v; + } + return 0; +} +EXPORT_SYMBOL(drm_plane_create_chroma_siting_properties); + /** * drm_color_lut_check - check validity of lookup table * @lut: property blob containing LUT to check diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c index 0e6021235a9304..39de95505f7e8a 100644 --- a/drivers/gpu/drm/drm_connector.c +++ b/drivers/gpu/drm/drm_connector.c @@ -33,6 +33,7 @@ #include <drm/drm_sysfs.h> #include <drm/drm_utils.h> +#include <linux/of.h> #include <linux/property.h> #include <linux/uaccess.h> @@ -83,6 +84,7 @@ struct drm_conn_prop_enum_list { int type; const char *name; struct ida ida; + int first_dyn_num; }; /* @@ -112,12 +114,41 @@ static struct drm_conn_prop_enum_list drm_connector_enum_list[] = { { DRM_MODE_CONNECTOR_USB, "USB" }, }; +#define MAX_DT_NODE_NAME_LEN 20 +#define DT_DRM_NODE_PREFIX "drm-" + +static void drm_connector_get_of_name(int type, char *node_name, int length) +{ + int i = 0; + + strcpy(node_name, DT_DRM_NODE_PREFIX); + + do { + node_name[i + strlen(DT_DRM_NODE_PREFIX)] = + tolower(drm_connector_enum_list[type].name[i]); + + } while (drm_connector_enum_list[type].name[i++] && + i < length); + + node_name[length - 1] = '\0'; +} + void drm_connector_ida_init(void) { - int i; + int i, id; + char node_name[MAX_DT_NODE_NAME_LEN]; - for (i = 0; i < ARRAY_SIZE(drm_connector_enum_list); i++) + for (i = 0; i < ARRAY_SIZE(drm_connector_enum_list); i++) { ida_init(&drm_connector_enum_list[i].ida); + + drm_connector_get_of_name(i, node_name, MAX_DT_NODE_NAME_LEN); + + id = of_alias_get_highest_id(node_name); + if (id > 0) + drm_connector_enum_list[i].first_dyn_num = id + 1; + else + drm_connector_enum_list[i].first_dyn_num = 1; + } } void drm_connector_ida_destroy(void) @@ -225,7 +256,9 @@ static int __drm_connector_init(struct drm_device *dev, struct i2c_adapter *ddc) { struct drm_mode_config *config = &dev->mode_config; + char node_name[MAX_DT_NODE_NAME_LEN]; int ret; + int id; struct ida *connector_ida = &drm_connector_enum_list[connector_type].ida; @@ -255,8 +288,28 @@ static int __drm_connector_init(struct drm_device *dev, ret = 0; connector->connector_type = connector_type; - connector->connector_type_id = - ida_alloc_min(connector_ida, 1, GFP_KERNEL); + connector->connector_type_id = 0; + + drm_connector_get_of_name(connector_type, node_name, MAX_DT_NODE_NAME_LEN); + id = of_alias_get_id(dev->dev->of_node, node_name); + if (id > 0) { + /* Try and allocate the requested ID + * Valid range is 1 to 31, hence ignoring 0 as an error + */ + int type_id = ida_alloc_range(connector_ida, id, id, GFP_KERNEL); + + if (type_id > 0) + connector->connector_type_id = type_id; + else + drm_err(dev, "Failed to acquire type ID %d for interface type %s, ret %d\n", + id, drm_connector_enum_list[connector_type].name, + type_id); + } + if (!connector->connector_type_id) + connector->connector_type_id = + ida_alloc_min(connector_ida, + drm_connector_enum_list[connector_type].first_dyn_num, + GFP_KERNEL); if (connector->connector_type_id < 0) { ret = connector->connector_type_id; goto out_put_id; @@ -310,7 +363,8 @@ static int __drm_connector_init(struct drm_device *dev, drm_object_attach_property(&connector->base, config->non_desktop_property, - 0); + (connector_type != DRM_MODE_CONNECTOR_VIRTUAL && + connector_type != DRM_MODE_CONNECTOR_WRITEBACK) ? 0 : 1); drm_object_attach_property(&connector->base, config->tile_property, 0); @@ -2673,8 +2727,8 @@ int drm_connector_attach_max_bpc_property(struct drm_connector *connector, } drm_object_attach_property(&connector->base, prop, max); - connector->state->max_requested_bpc = max; - connector->state->max_bpc = max; + connector->state->max_requested_bpc = min; + connector->state->max_bpc = min; return 0; } @@ -2908,10 +2962,15 @@ int drm_connector_set_orientation_from_panel( { enum drm_panel_orientation orientation; - if (panel && panel->funcs && panel->funcs->get_orientation) + if (panel && panel->funcs && panel->funcs->get_orientation) { orientation = panel->funcs->get_orientation(panel); - else + } else { orientation = DRM_MODE_PANEL_ORIENTATION_UNKNOWN; + if (panel) { + of_drm_get_panel_orientation(panel->dev->of_node, + &orientation); + } + } return drm_connector_set_panel_orientation(connector, orientation); } diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 13bc4c290b17d5..a339cfdd293bdf 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -6587,7 +6587,11 @@ static void drm_reset_display_info(struct drm_connector *connector) info->height_mm = 0; info->bpc = 0; - info->color_formats = 0; + if (connector->connector_type == DRM_MODE_CONNECTOR_HDMIA || + connector->connector_type == DRM_MODE_CONNECTOR_HDMIB) + info->color_formats = DRM_COLOR_FORMAT_RGB444; + else + info->color_formats = 0; info->cea_rev = 0; info->max_tmds_clock = 0; info->dvi_dual = false; diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c index eaac2e5726e750..b94b950bfc26a9 100644 --- a/drivers/gpu/drm/drm_fb_helper.c +++ b/drivers/gpu/drm/drm_fb_helper.c @@ -1840,7 +1840,7 @@ __drm_fb_helper_initial_config_and_unlock(struct drm_fb_helper *fb_helper) struct drm_device *dev = fb_helper->dev; struct fb_info *info; unsigned int width, height; - int ret; + int ret, id; width = dev->mode_config.max_width; height = dev->mode_config.max_height; @@ -1868,6 +1868,15 @@ __drm_fb_helper_initial_config_and_unlock(struct drm_fb_helper *fb_helper) * register the fbdev emulation instance in kernel_fb_helper_list. */ mutex_unlock(&fb_helper->lock); + id = of_alias_get_highest_id("drm-fb"); + if (id >= 0) + fb_set_lowest_dynamic_fb(id + 1); + + id = of_alias_get_id(dev->dev->of_node, "drm-fb"); + if (id >= 0) { + info->node = id; + info->custom_fb_num = true; + } ret = register_framebuffer(info); if (ret < 0) return ret; diff --git a/drivers/gpu/drm/drm_framebuffer.c b/drivers/gpu/drm/drm_framebuffer.c index 888aadb6a4acbb..f9c24baf18e5e2 100644 --- a/drivers/gpu/drm/drm_framebuffer.c +++ b/drivers/gpu/drm/drm_framebuffer.c @@ -975,7 +975,7 @@ static int atomic_remove_fb(struct drm_framebuffer *fb) struct drm_connector *conn __maybe_unused; struct drm_connector_state *conn_state; int i, ret; - unsigned plane_mask; + u64 plane_mask; bool disable_crtcs = false; retry_disable: diff --git a/drivers/gpu/drm/drm_gem.c b/drivers/gpu/drm/drm_gem.c index 149b8e25da5bbf..ee811764c3df4b 100644 --- a/drivers/gpu/drm/drm_gem.c +++ b/drivers/gpu/drm/drm_gem.c @@ -114,22 +114,32 @@ drm_gem_init(struct drm_device *dev) } /** - * drm_gem_object_init - initialize an allocated shmem-backed GEM object + * drm_gem_object_init_with_mnt - initialize an allocated shmem-backed GEM + * object in a given shmfs mountpoint + * * @dev: drm_device the object should be initialized for * @obj: drm_gem_object to initialize * @size: object size + * @gemfs: tmpfs mount where the GEM object will be created. If NULL, use + * the usual tmpfs mountpoint (`shm_mnt`). * * Initialize an already allocated GEM object of the specified size with * shmfs backing store. */ -int drm_gem_object_init(struct drm_device *dev, - struct drm_gem_object *obj, size_t size) +int drm_gem_object_init_with_mnt(struct drm_device *dev, + struct drm_gem_object *obj, size_t size, + struct vfsmount *gemfs) { struct file *filp; drm_gem_private_object_init(dev, obj, size); - filp = shmem_file_setup("drm mm object", size, VM_NORESERVE); + if (gemfs) + filp = shmem_file_setup_with_mnt(gemfs, "drm mm object", size, + VM_NORESERVE); + else + filp = shmem_file_setup("drm mm object", size, VM_NORESERVE); + if (IS_ERR(filp)) return PTR_ERR(filp); @@ -137,6 +147,22 @@ int drm_gem_object_init(struct drm_device *dev, return 0; } +EXPORT_SYMBOL(drm_gem_object_init_with_mnt); + +/** + * drm_gem_object_init - initialize an allocated shmem-backed GEM object + * @dev: drm_device the object should be initialized for + * @obj: drm_gem_object to initialize + * @size: object size + * + * Initialize an already allocated GEM object of the specified size with + * shmfs backing store. + */ +int drm_gem_object_init(struct drm_device *dev, struct drm_gem_object *obj, + size_t size) +{ + return drm_gem_object_init_with_mnt(dev, obj, size, NULL); +} EXPORT_SYMBOL(drm_gem_object_init); /** diff --git a/drivers/gpu/drm/drm_gem_shmem_helper.c b/drivers/gpu/drm/drm_gem_shmem_helper.c index 53c003983ad183..8508060a1a95cc 100644 --- a/drivers/gpu/drm/drm_gem_shmem_helper.c +++ b/drivers/gpu/drm/drm_gem_shmem_helper.c @@ -49,7 +49,8 @@ static const struct drm_gem_object_funcs drm_gem_shmem_funcs = { }; static struct drm_gem_shmem_object * -__drm_gem_shmem_create(struct drm_device *dev, size_t size, bool private) +__drm_gem_shmem_create(struct drm_device *dev, size_t size, bool private, + struct vfsmount *gemfs) { struct drm_gem_shmem_object *shmem; struct drm_gem_object *obj; @@ -76,7 +77,7 @@ __drm_gem_shmem_create(struct drm_device *dev, size_t size, bool private) drm_gem_private_object_init(dev, obj, size); shmem->map_wc = false; /* dma-buf mappings use always writecombine */ } else { - ret = drm_gem_object_init(dev, obj, size); + ret = drm_gem_object_init_with_mnt(dev, obj, size, gemfs); } if (ret) { drm_gem_private_object_fini(obj); @@ -123,10 +124,31 @@ __drm_gem_shmem_create(struct drm_device *dev, size_t size, bool private) */ struct drm_gem_shmem_object *drm_gem_shmem_create(struct drm_device *dev, size_t size) { - return __drm_gem_shmem_create(dev, size, false); + return __drm_gem_shmem_create(dev, size, false, NULL); } EXPORT_SYMBOL_GPL(drm_gem_shmem_create); +/** + * drm_gem_shmem_create_with_mnt - Allocate an object with the given size in a + * given mountpoint + * @dev: DRM device + * @size: Size of the object to allocate + * @gemfs: tmpfs mount where the GEM object will be created + * + * This function creates a shmem GEM object in a given tmpfs mountpoint. + * + * Returns: + * A struct drm_gem_shmem_object * on success or an ERR_PTR()-encoded negative + * error code on failure. + */ +struct drm_gem_shmem_object *drm_gem_shmem_create_with_mnt(struct drm_device *dev, + size_t size, + struct vfsmount *gemfs) +{ + return __drm_gem_shmem_create(dev, size, false, gemfs); +} +EXPORT_SYMBOL_GPL(drm_gem_shmem_create_with_mnt); + /** * drm_gem_shmem_free - Free resources associated with a shmem GEM object * @shmem: shmem GEM object to free @@ -765,7 +787,7 @@ drm_gem_shmem_prime_import_sg_table(struct drm_device *dev, size_t size = PAGE_ALIGN(attach->dmabuf->size); struct drm_gem_shmem_object *shmem; - shmem = __drm_gem_shmem_create(dev, size, true); + shmem = __drm_gem_shmem_create(dev, size, true, NULL); if (IS_ERR(shmem)) return ERR_CAST(shmem); diff --git a/drivers/gpu/drm/drm_mode_config.c b/drivers/gpu/drm/drm_mode_config.c index 37d2e0a4ef4be2..fdc44fd1c56d41 100644 --- a/drivers/gpu/drm/drm_mode_config.c +++ b/drivers/gpu/drm/drm_mode_config.c @@ -643,7 +643,7 @@ void drm_mode_config_validate(struct drm_device *dev) struct drm_encoder *encoder; struct drm_crtc *crtc; struct drm_plane *plane; - u32 primary_with_crtc = 0, cursor_with_crtc = 0; + u64 primary_with_crtc = 0, cursor_with_crtc = 0; unsigned int num_primary = 0; if (!drm_core_check_feature(dev, DRIVER_MODESET)) diff --git a/drivers/gpu/drm/drm_plane.c b/drivers/gpu/drm/drm_plane.c index a28b22fdd7a41a..3fab749b45dd09 100644 --- a/drivers/gpu/drm/drm_plane.c +++ b/drivers/gpu/drm/drm_plane.c @@ -365,7 +365,7 @@ static int __drm_universal_plane_init(struct drm_device *dev, int ret; /* plane index is used with 32bit bitmasks */ - if (WARN_ON(config->num_total_plane >= 32)) + if (WARN_ON(config->num_total_plane >= 64)) return -EINVAL; /* diff --git a/drivers/gpu/drm/i915/display/intel_display.c b/drivers/gpu/drm/i915/display/intel_display.c index 2c6d0da8a16f8c..125df962b620a2 100644 --- a/drivers/gpu/drm/i915/display/intel_display.c +++ b/drivers/gpu/drm/i915/display/intel_display.c @@ -7666,6 +7666,19 @@ int intel_atomic_commit(struct drm_device *dev, struct drm_atomic_state *_state, state->base.legacy_cursor_update = false; } + /* + * FIXME: Cut over to (async) commit helpers instead of hand-rolling + * everything. + */ + if (state->base.legacy_cursor_update) { + struct intel_crtc_state *new_crtc_state; + struct intel_crtc *crtc; + int i; + + for_each_new_intel_crtc_in_state(state, crtc, new_crtc_state, i) + complete_all(&new_crtc_state->uapi.commit->flip_done); + } + ret = intel_atomic_prepare_commit(state); if (ret) { drm_dbg_atomic(&dev_priv->drm, diff --git a/drivers/gpu/drm/imx/ipuv3/ipuv3-crtc.c b/drivers/gpu/drm/imx/ipuv3/ipuv3-crtc.c index 99db53e167bd02..43ee5f55d69ba4 100644 --- a/drivers/gpu/drm/imx/ipuv3/ipuv3-crtc.c +++ b/drivers/gpu/drm/imx/ipuv3/ipuv3-crtc.c @@ -230,7 +230,7 @@ static int ipu_crtc_atomic_check(struct drm_crtc *crtc, { struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc); - u32 primary_plane_mask = drm_plane_mask(crtc->primary); + u64 primary_plane_mask = drm_plane_mask(crtc->primary); if (crtc_state->active && (primary_plane_mask & crtc_state->plane_mask) == 0) return -EINVAL; diff --git a/drivers/gpu/drm/msm/msm_atomic.c b/drivers/gpu/drm/msm/msm_atomic.c index 9c45d641b5212c..5c8e5611304fbd 100644 --- a/drivers/gpu/drm/msm/msm_atomic.c +++ b/drivers/gpu/drm/msm/msm_atomic.c @@ -242,6 +242,8 @@ void msm_atomic_commit_tail(struct drm_atomic_state *state) /* async updates are limited to single-crtc updates: */ WARN_ON(crtc_mask != drm_crtc_mask(async_crtc)); + complete_all(&async_crtc->state->commit->flip_done); + /* * Start timer if we don't already have an update pending * on this crtc: diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig index d3a9a9fafe4ec7..59fde083703d66 100644 --- a/drivers/gpu/drm/panel/Kconfig +++ b/drivers/gpu/drm/panel/Kconfig @@ -909,6 +909,17 @@ config DRM_PANEL_TDO_TL070WSH30 24 bit RGB per pixel. It provides a MIPI DSI interface to the host, a built-in LED backlight and touch controller. +config DRM_PANEL_TPO_Y17P + tristate "TDO Y17P-based panels" + depends on OF && SPI + select DRM_KMS_HELPER + depends on DRM_GEM_DMA_HELPER + depends on BACKLIGHT_CLASS_DEVICE + select DRM_MIPI_DBI + help + Say Y if you want to enable support for panels based on the + TDO Y17P controller. + config DRM_PANEL_TPO_TD028TTEC1 tristate "Toppoly (TPO) TD028TTEC1 panel driver" depends on OF && SPI @@ -969,6 +980,16 @@ config DRM_PANEL_VISIONOX_VTDR6130 Say Y here if you want to enable support for Visionox VTDR6130 1080x2400 AMOLED DSI panel. +config DRM_PANEL_WAVESHARE_TOUCHSCREEN + tristate "Waveshare touchscreen panels" + depends on DRM_MIPI_DSI + depends on I2C + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y here if you want to enable support for the Waveshare + DSI Touchscreens. To compile this driver as a module, + choose M here. + config DRM_PANEL_WIDECHIPS_WS2401 tristate "Widechips WS2401 DPI panel driver" depends on SPI && GPIOLIB diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile index 987a0870241035..41ea40f0c1d262 100644 --- a/drivers/gpu/drm/panel/Makefile +++ b/drivers/gpu/drm/panel/Makefile @@ -91,6 +91,7 @@ obj-$(CONFIG_DRM_PANEL_SONY_TD4353_JDI) += panel-sony-td4353-jdi.o obj-$(CONFIG_DRM_PANEL_SONY_TULIP_TRULY_NT35521) += panel-sony-tulip-truly-nt35521.o obj-$(CONFIG_DRM_PANEL_STARTEK_KD070FHFID015) += panel-startek-kd070fhfid015.o obj-$(CONFIG_DRM_PANEL_TDO_TL070WSH30) += panel-tdo-tl070wsh30.o +obj-$(CONFIG_DRM_PANEL_TPO_Y17P) += panel-tdo-y17p.o obj-$(CONFIG_DRM_PANEL_TPO_TD028TTEC1) += panel-tpo-td028ttec1.o obj-$(CONFIG_DRM_PANEL_TPO_TD043MTEA1) += panel-tpo-td043mtea1.o obj-$(CONFIG_DRM_PANEL_TPO_TPG110) += panel-tpo-tpg110.o @@ -98,5 +99,6 @@ obj-$(CONFIG_DRM_PANEL_TRULY_NT35597_WQXGA) += panel-truly-nt35597.o obj-$(CONFIG_DRM_PANEL_VISIONOX_RM69299) += panel-visionox-rm69299.o obj-$(CONFIG_DRM_PANEL_VISIONOX_VTDR6130) += panel-visionox-vtdr6130.o obj-$(CONFIG_DRM_PANEL_VISIONOX_R66451) += panel-visionox-r66451.o +obj-$(CONFIG_DRM_PANEL_WAVESHARE_TOUCHSCREEN) += panel-waveshare-dsi.o obj-$(CONFIG_DRM_PANEL_WIDECHIPS_WS2401) += panel-widechips-ws2401.o obj-$(CONFIG_DRM_PANEL_XINPENG_XPP055C272) += panel-xinpeng-xpp055c272.o diff --git a/drivers/gpu/drm/panel/panel-ilitek-ili9881c.c b/drivers/gpu/drm/panel/panel-ilitek-ili9881c.c index 084c37fa73485b..c9964957abf99e 100644 --- a/drivers/gpu/drm/panel/panel-ilitek-ili9881c.c +++ b/drivers/gpu/drm/panel/panel-ilitek-ili9881c.c @@ -1,6 +1,9 @@ // SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2017-2018, Bootlin + * Copyright (C) 2021, Henson Li <henson@cutiepi.io> + * Copyright (C) 2021, Penk Chen <penk@cutiepi.io> + * Copyright (C) 2022, Mark Williams <mark@crystalfontz.com> */ #include <linux/delay.h> @@ -37,11 +40,19 @@ struct ili9881c_instr { } arg; }; +enum ili9881_desc_flags { + ILI9881_FLAGS_NO_SHUTDOWN_CMDS = BIT(0), + ILI9881_FLAGS_PANEL_ON_IN_PREPARE = BIT(1), + ILI9881_FLAGS_MAX = BIT(31), +}; + struct ili9881c_desc { const struct ili9881c_instr *init; const size_t init_length; const struct drm_display_mode *mode; const unsigned long mode_flags; + unsigned int lanes; + enum ili9881_desc_flags flags; }; struct ili9881c { @@ -1223,153 +1234,979 @@ static const struct ili9881c_instr am8001280g_init[] = { ILI9881C_COMMAND_INSTR(MIPI_DCS_WRITE_POWER_SAVE, 0x00), }; -static inline struct ili9881c *panel_to_ili9881c(struct drm_panel *panel) -{ - return container_of(panel, struct ili9881c, panel); -} - -/* - * The panel seems to accept some private DCS commands that map - * directly to registers. - * - * It is organised by page, with each page having its own set of - * registers, and the first page looks like it's holding the standard - * DCS commands. - * - * So before any attempt at sending a command or data, we have to be - * sure if we're in the right page or not. - */ -static int ili9881c_switch_page(struct ili9881c *ctx, u8 page) -{ - u8 buf[4] = { 0xff, 0x98, 0x81, page }; - int ret; - - ret = mipi_dsi_dcs_write_buffer(ctx->dsi, buf, sizeof(buf)); - if (ret < 0) - return ret; - - return 0; -} - -static int ili9881c_send_cmd_data(struct ili9881c *ctx, u8 cmd, u8 data) -{ - u8 buf[2] = { cmd, data }; - int ret; - - ret = mipi_dsi_dcs_write_buffer(ctx->dsi, buf, sizeof(buf)); - if (ret < 0) - return ret; - - return 0; -} - -static int ili9881c_prepare(struct drm_panel *panel) -{ - struct ili9881c *ctx = panel_to_ili9881c(panel); - unsigned int i; - int ret; - - /* Power the panel */ - ret = regulator_enable(ctx->power); - if (ret) - return ret; - msleep(5); - - /* And reset it */ - gpiod_set_value_cansleep(ctx->reset, 1); - msleep(20); - - gpiod_set_value_cansleep(ctx->reset, 0); - msleep(20); - - for (i = 0; i < ctx->desc->init_length; i++) { - const struct ili9881c_instr *instr = &ctx->desc->init[i]; - - if (instr->op == ILI9881C_SWITCH_PAGE) - ret = ili9881c_switch_page(ctx, instr->arg.page); - else if (instr->op == ILI9881C_COMMAND) - ret = ili9881c_send_cmd_data(ctx, instr->arg.cmd.cmd, - instr->arg.cmd.data); - - if (ret) - return ret; - } - - ret = ili9881c_switch_page(ctx, 0); - if (ret) - return ret; - - ret = mipi_dsi_dcs_set_tear_on(ctx->dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK); - if (ret) - return ret; - - ret = mipi_dsi_dcs_exit_sleep_mode(ctx->dsi); - if (ret) - return ret; - - return 0; -} - -static int ili9881c_enable(struct drm_panel *panel) -{ - struct ili9881c *ctx = panel_to_ili9881c(panel); +static const struct ili9881c_instr nwe080_init[] = { + ILI9881C_SWITCH_PAGE_INSTR(3), + //GIP_1 + ILI9881C_COMMAND_INSTR(0x01, 0x00), + ILI9881C_COMMAND_INSTR(0x02, 0x00), + ILI9881C_COMMAND_INSTR(0x03, 0x73), + ILI9881C_COMMAND_INSTR(0x04, 0x00), + ILI9881C_COMMAND_INSTR(0x05, 0x00), + ILI9881C_COMMAND_INSTR(0x06, 0x0A), + ILI9881C_COMMAND_INSTR(0x07, 0x00), + ILI9881C_COMMAND_INSTR(0x08, 0x00), + ILI9881C_COMMAND_INSTR(0x09, 0x20), + ILI9881C_COMMAND_INSTR(0x0a, 0x20), + ILI9881C_COMMAND_INSTR(0x0b, 0x00), + ILI9881C_COMMAND_INSTR(0x0c, 0x00), + ILI9881C_COMMAND_INSTR(0x0d, 0x00), + ILI9881C_COMMAND_INSTR(0x0e, 0x00), + ILI9881C_COMMAND_INSTR(0x0f, 0x1E), + ILI9881C_COMMAND_INSTR(0x10, 0x1E), + ILI9881C_COMMAND_INSTR(0x11, 0x00), + ILI9881C_COMMAND_INSTR(0x12, 0x00), + ILI9881C_COMMAND_INSTR(0x13, 0x00), + ILI9881C_COMMAND_INSTR(0x14, 0x00), + ILI9881C_COMMAND_INSTR(0x15, 0x00), + ILI9881C_COMMAND_INSTR(0x16, 0x00), + ILI9881C_COMMAND_INSTR(0x17, 0x00), + ILI9881C_COMMAND_INSTR(0x18, 0x00), + ILI9881C_COMMAND_INSTR(0x19, 0x00), + ILI9881C_COMMAND_INSTR(0x1A, 0x00), + ILI9881C_COMMAND_INSTR(0x1B, 0x00), + ILI9881C_COMMAND_INSTR(0x1C, 0x00), + ILI9881C_COMMAND_INSTR(0x1D, 0x00), + ILI9881C_COMMAND_INSTR(0x1E, 0x40), + ILI9881C_COMMAND_INSTR(0x1F, 0x80), + ILI9881C_COMMAND_INSTR(0x20, 0x06), + ILI9881C_COMMAND_INSTR(0x21, 0x01), + ILI9881C_COMMAND_INSTR(0x22, 0x00), + ILI9881C_COMMAND_INSTR(0x23, 0x00), + ILI9881C_COMMAND_INSTR(0x24, 0x00), + ILI9881C_COMMAND_INSTR(0x25, 0x00), + ILI9881C_COMMAND_INSTR(0x26, 0x00), + ILI9881C_COMMAND_INSTR(0x27, 0x00), + ILI9881C_COMMAND_INSTR(0x28, 0x33), + ILI9881C_COMMAND_INSTR(0x29, 0x03), + ILI9881C_COMMAND_INSTR(0x2A, 0x00), + ILI9881C_COMMAND_INSTR(0x2B, 0x00), + ILI9881C_COMMAND_INSTR(0x2C, 0x00), + ILI9881C_COMMAND_INSTR(0x2D, 0x00), + ILI9881C_COMMAND_INSTR(0x2E, 0x00), + ILI9881C_COMMAND_INSTR(0x2F, 0x00), - msleep(120); + ILI9881C_COMMAND_INSTR(0x30, 0x00), + ILI9881C_COMMAND_INSTR(0x31, 0x00), + ILI9881C_COMMAND_INSTR(0x32, 0x00), + ILI9881C_COMMAND_INSTR(0x33, 0x00), + ILI9881C_COMMAND_INSTR(0x34, 0x04), + ILI9881C_COMMAND_INSTR(0x35, 0x00), + ILI9881C_COMMAND_INSTR(0x36, 0x00), + ILI9881C_COMMAND_INSTR(0x37, 0x00), + ILI9881C_COMMAND_INSTR(0x38, 0x3C), + ILI9881C_COMMAND_INSTR(0x39, 0x00), + ILI9881C_COMMAND_INSTR(0x3A, 0x00), + ILI9881C_COMMAND_INSTR(0x3B, 0x00), + ILI9881C_COMMAND_INSTR(0x3C, 0x00), + ILI9881C_COMMAND_INSTR(0x3D, 0x00), + ILI9881C_COMMAND_INSTR(0x3E, 0x00), + ILI9881C_COMMAND_INSTR(0x3F, 0x00), - mipi_dsi_dcs_set_display_on(ctx->dsi); + ILI9881C_COMMAND_INSTR(0x40, 0x00), + ILI9881C_COMMAND_INSTR(0x41, 0x00), + ILI9881C_COMMAND_INSTR(0x42, 0x00), + ILI9881C_COMMAND_INSTR(0x43, 0x00), + ILI9881C_COMMAND_INSTR(0x44, 0x00), - return 0; -} + ILI9881C_COMMAND_INSTR(0x50, 0x10), + ILI9881C_COMMAND_INSTR(0x51, 0x32), + ILI9881C_COMMAND_INSTR(0x52, 0x54), + ILI9881C_COMMAND_INSTR(0x53, 0x76), + ILI9881C_COMMAND_INSTR(0x54, 0x98), + ILI9881C_COMMAND_INSTR(0x55, 0xba), + ILI9881C_COMMAND_INSTR(0x56, 0x10), + ILI9881C_COMMAND_INSTR(0x57, 0x32), + ILI9881C_COMMAND_INSTR(0x58, 0x54), + ILI9881C_COMMAND_INSTR(0x59, 0x76), + ILI9881C_COMMAND_INSTR(0x5A, 0x98), + ILI9881C_COMMAND_INSTR(0x5B, 0xba), + ILI9881C_COMMAND_INSTR(0x5C, 0xdc), + ILI9881C_COMMAND_INSTR(0x5D, 0xfe), + + //GIP_3 + ILI9881C_COMMAND_INSTR(0x5E, 0x00), + ILI9881C_COMMAND_INSTR(0x5F, 0x01), + ILI9881C_COMMAND_INSTR(0x60, 0x00), + ILI9881C_COMMAND_INSTR(0x61, 0x15), + ILI9881C_COMMAND_INSTR(0x62, 0x14), + ILI9881C_COMMAND_INSTR(0x63, 0x0E), + ILI9881C_COMMAND_INSTR(0x64, 0x0F), + ILI9881C_COMMAND_INSTR(0x65, 0x0C), + ILI9881C_COMMAND_INSTR(0x66, 0x0D), + ILI9881C_COMMAND_INSTR(0x67, 0x06), + ILI9881C_COMMAND_INSTR(0x68, 0x02), + ILI9881C_COMMAND_INSTR(0x69, 0x02), + ILI9881C_COMMAND_INSTR(0x6A, 0x02), + ILI9881C_COMMAND_INSTR(0x6B, 0x02), + ILI9881C_COMMAND_INSTR(0x6C, 0x02), + ILI9881C_COMMAND_INSTR(0x6D, 0x02), + ILI9881C_COMMAND_INSTR(0x6E, 0x07), + ILI9881C_COMMAND_INSTR(0x6F, 0x02), -static int ili9881c_disable(struct drm_panel *panel) -{ - struct ili9881c *ctx = panel_to_ili9881c(panel); + ILI9881C_COMMAND_INSTR(0x70, 0x02), + ILI9881C_COMMAND_INSTR(0x71, 0x02), + ILI9881C_COMMAND_INSTR(0x72, 0x02), + ILI9881C_COMMAND_INSTR(0x73, 0x02), + ILI9881C_COMMAND_INSTR(0x74, 0x02), + ILI9881C_COMMAND_INSTR(0x75, 0x01), + ILI9881C_COMMAND_INSTR(0x76, 0x00), + ILI9881C_COMMAND_INSTR(0x77, 0x14), + ILI9881C_COMMAND_INSTR(0x78, 0x15), + ILI9881C_COMMAND_INSTR(0x79, 0x0E), + ILI9881C_COMMAND_INSTR(0x7A, 0x0F), + ILI9881C_COMMAND_INSTR(0x7B, 0x0C), + ILI9881C_COMMAND_INSTR(0x7C, 0x0D), + ILI9881C_COMMAND_INSTR(0x7D, 0x06), + ILI9881C_COMMAND_INSTR(0x7E, 0x02), + ILI9881C_COMMAND_INSTR(0x7F, 0x02), - return mipi_dsi_dcs_set_display_off(ctx->dsi); -} + ILI9881C_COMMAND_INSTR(0x80, 0x02), + ILI9881C_COMMAND_INSTR(0x81, 0x02), + ILI9881C_COMMAND_INSTR(0x82, 0x02), + ILI9881C_COMMAND_INSTR(0x83, 0x02), + ILI9881C_COMMAND_INSTR(0x84, 0x07), + ILI9881C_COMMAND_INSTR(0x85, 0x02), + ILI9881C_COMMAND_INSTR(0x86, 0x02), + ILI9881C_COMMAND_INSTR(0x87, 0x02), + ILI9881C_COMMAND_INSTR(0x88, 0x02), + ILI9881C_COMMAND_INSTR(0x89, 0x02), + ILI9881C_COMMAND_INSTR(0x8A, 0x02), -static int ili9881c_unprepare(struct drm_panel *panel) -{ - struct ili9881c *ctx = panel_to_ili9881c(panel); + ILI9881C_SWITCH_PAGE_INSTR(4), + ILI9881C_COMMAND_INSTR(0x6C, 0x15), + ILI9881C_COMMAND_INSTR(0x6E, 0x2A), - mipi_dsi_dcs_enter_sleep_mode(ctx->dsi); - regulator_disable(ctx->power); - gpiod_set_value_cansleep(ctx->reset, 1); + //clamp 15V + ILI9881C_COMMAND_INSTR(0x6F, 0x35), + ILI9881C_COMMAND_INSTR(0x3A, 0x92), + ILI9881C_COMMAND_INSTR(0x8D, 0x1F), + ILI9881C_COMMAND_INSTR(0x87, 0xBA), + ILI9881C_COMMAND_INSTR(0x26, 0x76), + ILI9881C_COMMAND_INSTR(0xB2, 0xD1), + ILI9881C_COMMAND_INSTR(0xB5, 0x27), + ILI9881C_COMMAND_INSTR(0x31, 0x75), + ILI9881C_COMMAND_INSTR(0x30, 0x03), + ILI9881C_COMMAND_INSTR(0x3B, 0x98), + ILI9881C_COMMAND_INSTR(0x35, 0x17), + ILI9881C_COMMAND_INSTR(0x33, 0x14), + ILI9881C_COMMAND_INSTR(0x38, 0x01), + ILI9881C_COMMAND_INSTR(0x39, 0x00), - return 0; -} + ILI9881C_SWITCH_PAGE_INSTR(1), + // direction rotate + //ILI9881C_COMMAND_INSTR(0x22, 0x0B), + ILI9881C_COMMAND_INSTR(0x22, 0x0A), + ILI9881C_COMMAND_INSTR(0x31, 0x00), + ILI9881C_COMMAND_INSTR(0x53, 0x63), + ILI9881C_COMMAND_INSTR(0x55, 0x69), + ILI9881C_COMMAND_INSTR(0x50, 0xC7), + ILI9881C_COMMAND_INSTR(0x51, 0xC2), + ILI9881C_COMMAND_INSTR(0x60, 0x26), -static const struct drm_display_mode lhr050h41_default_mode = { - .clock = 62000, + ILI9881C_COMMAND_INSTR(0xA0, 0x08), + ILI9881C_COMMAND_INSTR(0xA1, 0x0F), + ILI9881C_COMMAND_INSTR(0xA2, 0x25), + ILI9881C_COMMAND_INSTR(0xA3, 0x01), + ILI9881C_COMMAND_INSTR(0xA4, 0x23), + ILI9881C_COMMAND_INSTR(0xA5, 0x18), + ILI9881C_COMMAND_INSTR(0xA6, 0x11), + ILI9881C_COMMAND_INSTR(0xA7, 0x1A), + ILI9881C_COMMAND_INSTR(0xA8, 0x81), + ILI9881C_COMMAND_INSTR(0xA9, 0x19), + ILI9881C_COMMAND_INSTR(0xAA, 0x26), + ILI9881C_COMMAND_INSTR(0xAB, 0x7C), + ILI9881C_COMMAND_INSTR(0xAC, 0x24), + ILI9881C_COMMAND_INSTR(0xAD, 0x1E), + ILI9881C_COMMAND_INSTR(0xAE, 0x5C), + ILI9881C_COMMAND_INSTR(0xAF, 0x2A), + ILI9881C_COMMAND_INSTR(0xB0, 0x2B), + ILI9881C_COMMAND_INSTR(0xB1, 0x50), + ILI9881C_COMMAND_INSTR(0xB2, 0x5C), + ILI9881C_COMMAND_INSTR(0xB3, 0x39), - .hdisplay = 720, - .hsync_start = 720 + 10, - .hsync_end = 720 + 10 + 20, - .htotal = 720 + 10 + 20 + 30, + ILI9881C_COMMAND_INSTR(0xC0, 0x08), + ILI9881C_COMMAND_INSTR(0xC1, 0x1F), + ILI9881C_COMMAND_INSTR(0xC2, 0x24), + ILI9881C_COMMAND_INSTR(0xC3, 0x1D), + ILI9881C_COMMAND_INSTR(0xC4, 0x04), + ILI9881C_COMMAND_INSTR(0xC5, 0x32), + ILI9881C_COMMAND_INSTR(0xC6, 0x24), + ILI9881C_COMMAND_INSTR(0xC7, 0x1F), + ILI9881C_COMMAND_INSTR(0xC8, 0x90), + ILI9881C_COMMAND_INSTR(0xC9, 0x20), + ILI9881C_COMMAND_INSTR(0xCA, 0x2C), + ILI9881C_COMMAND_INSTR(0xCB, 0x82), + ILI9881C_COMMAND_INSTR(0xCC, 0x19), + ILI9881C_COMMAND_INSTR(0xCD, 0x22), + ILI9881C_COMMAND_INSTR(0xCE, 0x4E), + ILI9881C_COMMAND_INSTR(0xCF, 0x28), + ILI9881C_COMMAND_INSTR(0xD0, 0x2D), + ILI9881C_COMMAND_INSTR(0xD1, 0x51), + ILI9881C_COMMAND_INSTR(0xD2, 0x5D), + ILI9881C_COMMAND_INSTR(0xD3, 0x39), - .vdisplay = 1280, - .vsync_start = 1280 + 10, - .vsync_end = 1280 + 10 + 10, - .vtotal = 1280 + 10 + 10 + 20, + ILI9881C_SWITCH_PAGE_INSTR(0), + //PWM + ILI9881C_COMMAND_INSTR(0x51, 0x0F), + ILI9881C_COMMAND_INSTR(0x52, 0xFF), + ILI9881C_COMMAND_INSTR(0x53, 0x2C), - .width_mm = 62, - .height_mm = 110, + ILI9881C_COMMAND_INSTR(0x11, 0x00), + ILI9881C_COMMAND_INSTR(0x29, 0x00), + ILI9881C_COMMAND_INSTR(0x35, 0x00), }; -static const struct drm_display_mode k101_im2byl02_default_mode = { - .clock = 69700, - - .hdisplay = 800, - .hsync_start = 800 + 52, - .hsync_end = 800 + 52 + 8, - .htotal = 800 + 52 + 8 + 48, - - .vdisplay = 1280, - .vsync_start = 1280 + 16, - .vsync_end = 1280 + 16 + 6, - .vtotal = 1280 + 16 + 6 + 15, - - .width_mm = 135, +static const struct ili9881c_instr cfaf7201280a0_050tx_init[] = { + //ILI9881C PAGE3 + ILI9881C_SWITCH_PAGE_INSTR(3), + //GIP_1 + ILI9881C_COMMAND_INSTR(0x01, 0x00), //added + ILI9881C_COMMAND_INSTR(0x02, 0x00), + ILI9881C_COMMAND_INSTR(0x03, 0x73), + ILI9881C_COMMAND_INSTR(0x04, 0x00), + ILI9881C_COMMAND_INSTR(0x05, 0x00), + ILI9881C_COMMAND_INSTR(0x06, 0x0A), + ILI9881C_COMMAND_INSTR(0x07, 0x00), + ILI9881C_COMMAND_INSTR(0x08, 0x00), + ILI9881C_COMMAND_INSTR(0x09, 0x01), + ILI9881C_COMMAND_INSTR(0x0A, 0x00), + ILI9881C_COMMAND_INSTR(0x0B, 0x00), + ILI9881C_COMMAND_INSTR(0x0C, 0x01), + ILI9881C_COMMAND_INSTR(0x0D, 0x00), + ILI9881C_COMMAND_INSTR(0x0E, 0x00), + ILI9881C_COMMAND_INSTR(0x0F, 0x1D), + ILI9881C_COMMAND_INSTR(0x10, 0x1D), + ILI9881C_COMMAND_INSTR(0x11, 0x00), + ILI9881C_COMMAND_INSTR(0x12, 0x00), + ILI9881C_COMMAND_INSTR(0x13, 0x00), + ILI9881C_COMMAND_INSTR(0x14, 0x00), + ILI9881C_COMMAND_INSTR(0x15, 0x00), + ILI9881C_COMMAND_INSTR(0x16, 0x00), + ILI9881C_COMMAND_INSTR(0x17, 0x00), + ILI9881C_COMMAND_INSTR(0x18, 0x00), + ILI9881C_COMMAND_INSTR(0x19, 0x00), + ILI9881C_COMMAND_INSTR(0x1A, 0x00), + ILI9881C_COMMAND_INSTR(0x1B, 0x00), + ILI9881C_COMMAND_INSTR(0x1C, 0x00), + ILI9881C_COMMAND_INSTR(0x1D, 0x00), + ILI9881C_COMMAND_INSTR(0x1E, 0x40), + ILI9881C_COMMAND_INSTR(0x1F, 0x80), + ILI9881C_COMMAND_INSTR(0x20, 0x06), + ILI9881C_COMMAND_INSTR(0x21, 0x02), + ILI9881C_COMMAND_INSTR(0x22, 0x00), + ILI9881C_COMMAND_INSTR(0x23, 0x00), + ILI9881C_COMMAND_INSTR(0x24, 0x00), + ILI9881C_COMMAND_INSTR(0x25, 0x00), + ILI9881C_COMMAND_INSTR(0x26, 0x00), + ILI9881C_COMMAND_INSTR(0x27, 0x00), + ILI9881C_COMMAND_INSTR(0x28, 0x33), + ILI9881C_COMMAND_INSTR(0x29, 0x03), + ILI9881C_COMMAND_INSTR(0x2A, 0x00), + ILI9881C_COMMAND_INSTR(0x2B, 0x00), + ILI9881C_COMMAND_INSTR(0x2C, 0x00), + ILI9881C_COMMAND_INSTR(0x2D, 0x00), + ILI9881C_COMMAND_INSTR(0x2E, 0x00), + ILI9881C_COMMAND_INSTR(0x2F, 0x00), + ILI9881C_COMMAND_INSTR(0x30, 0x00), + ILI9881C_COMMAND_INSTR(0x31, 0x00), + ILI9881C_COMMAND_INSTR(0x32, 0x00), + ILI9881C_COMMAND_INSTR(0x33, 0x00), + ILI9881C_COMMAND_INSTR(0x34, 0x04), + ILI9881C_COMMAND_INSTR(0x35, 0x00), + ILI9881C_COMMAND_INSTR(0x36, 0x00), + ILI9881C_COMMAND_INSTR(0x37, 0x00), + ILI9881C_COMMAND_INSTR(0x38, 0x3C), + ILI9881C_COMMAND_INSTR(0x39, 0x00), + ILI9881C_COMMAND_INSTR(0x3A, 0x40), + ILI9881C_COMMAND_INSTR(0x3B, 0x40), + ILI9881C_COMMAND_INSTR(0x3C, 0x00), + ILI9881C_COMMAND_INSTR(0x3D, 0x00), + ILI9881C_COMMAND_INSTR(0x3E, 0x00), + ILI9881C_COMMAND_INSTR(0x3F, 0x00), + ILI9881C_COMMAND_INSTR(0x40, 0x00), + ILI9881C_COMMAND_INSTR(0x41, 0x00), + ILI9881C_COMMAND_INSTR(0x42, 0x00), + ILI9881C_COMMAND_INSTR(0x43, 0x00), + ILI9881C_COMMAND_INSTR(0x44, 0x00), + //GIP_2 + ILI9881C_COMMAND_INSTR(0x50, 0x01), + ILI9881C_COMMAND_INSTR(0x51, 0x23), + ILI9881C_COMMAND_INSTR(0x52, 0x45), + ILI9881C_COMMAND_INSTR(0x53, 0x67), + ILI9881C_COMMAND_INSTR(0x54, 0x89), + ILI9881C_COMMAND_INSTR(0x55, 0xAB), + ILI9881C_COMMAND_INSTR(0x56, 0x01), + ILI9881C_COMMAND_INSTR(0x57, 0x23), + ILI9881C_COMMAND_INSTR(0x58, 0x45), + ILI9881C_COMMAND_INSTR(0x59, 0x67), + ILI9881C_COMMAND_INSTR(0x5A, 0x89), + ILI9881C_COMMAND_INSTR(0x5B, 0xAB), + ILI9881C_COMMAND_INSTR(0x5C, 0xCD), + ILI9881C_COMMAND_INSTR(0x5D, 0xEF), + //GIP_3 + ILI9881C_COMMAND_INSTR(0x5E, 0x11), + ILI9881C_COMMAND_INSTR(0x5F, 0x01), + ILI9881C_COMMAND_INSTR(0x60, 0x00), + ILI9881C_COMMAND_INSTR(0x61, 0x15), + ILI9881C_COMMAND_INSTR(0x62, 0x14), + ILI9881C_COMMAND_INSTR(0x63, 0x0E), + ILI9881C_COMMAND_INSTR(0x64, 0x0F), + ILI9881C_COMMAND_INSTR(0x65, 0x0C), + ILI9881C_COMMAND_INSTR(0x66, 0x0D), + ILI9881C_COMMAND_INSTR(0x67, 0x06), + ILI9881C_COMMAND_INSTR(0x68, 0x02), + ILI9881C_COMMAND_INSTR(0x69, 0x07), + ILI9881C_COMMAND_INSTR(0x6A, 0x02), + ILI9881C_COMMAND_INSTR(0x6B, 0x02), + ILI9881C_COMMAND_INSTR(0x6C, 0x02), + ILI9881C_COMMAND_INSTR(0x6D, 0x02), + ILI9881C_COMMAND_INSTR(0x6E, 0x02), + ILI9881C_COMMAND_INSTR(0x6F, 0x02), + ILI9881C_COMMAND_INSTR(0x70, 0x02), + ILI9881C_COMMAND_INSTR(0x71, 0x02), + ILI9881C_COMMAND_INSTR(0x72, 0x02), + ILI9881C_COMMAND_INSTR(0x73, 0x02), + ILI9881C_COMMAND_INSTR(0x74, 0x02), + ILI9881C_COMMAND_INSTR(0x75, 0x01), + ILI9881C_COMMAND_INSTR(0x76, 0x00), + ILI9881C_COMMAND_INSTR(0x77, 0x14), + ILI9881C_COMMAND_INSTR(0x78, 0x15), + ILI9881C_COMMAND_INSTR(0x79, 0x0E), + ILI9881C_COMMAND_INSTR(0x7A, 0x0F), + ILI9881C_COMMAND_INSTR(0x7B, 0x0C), + ILI9881C_COMMAND_INSTR(0x7C, 0x0D), + ILI9881C_COMMAND_INSTR(0x7D, 0x06), + ILI9881C_COMMAND_INSTR(0x7E, 0x02), + ILI9881C_COMMAND_INSTR(0x7F, 0x07), + ILI9881C_COMMAND_INSTR(0x80, 0x02), + ILI9881C_COMMAND_INSTR(0x81, 0x02), + ILI9881C_COMMAND_INSTR(0x82, 0x02), + ILI9881C_COMMAND_INSTR(0x83, 0x02), + ILI9881C_COMMAND_INSTR(0x84, 0x02), + ILI9881C_COMMAND_INSTR(0x85, 0x02), + ILI9881C_COMMAND_INSTR(0x86, 0x02), + ILI9881C_COMMAND_INSTR(0x87, 0x02), + ILI9881C_COMMAND_INSTR(0x88, 0x02), + ILI9881C_COMMAND_INSTR(0x89, 0x02), + ILI9881C_COMMAND_INSTR(0x8A, 0x02), + //ILI9881C PAGE4 + ILI9881C_SWITCH_PAGE_INSTR(4), + ILI9881C_COMMAND_INSTR(0x6C, 0x15), + ILI9881C_COMMAND_INSTR(0x6E, 0x2B), + // VGH & VGL OUTPUT + ILI9881C_COMMAND_INSTR(0x6F, 0x33), + ILI9881C_COMMAND_INSTR(0x8D, 0x18), + ILI9881C_COMMAND_INSTR(0x87, 0xBA), + ILI9881C_COMMAND_INSTR(0x26, 0x76), + //Reload Gamma setting + ILI9881C_COMMAND_INSTR(0xB2, 0xD1), + ILI9881C_COMMAND_INSTR(0xB5, 0x06), + ILI9881C_COMMAND_INSTR(0x3A, 0x24), + ILI9881C_COMMAND_INSTR(0x35, 0x1F), + + //ILI9881C PAGE1 + ILI9881C_SWITCH_PAGE_INSTR(1), + ILI9881C_COMMAND_INSTR(0x22, 0x09), + //Column inversion + ILI9881C_COMMAND_INSTR(0x31, 0x00), + ILI9881C_COMMAND_INSTR(0x40, 0x33), + ILI9881C_COMMAND_INSTR(0x53, 0xA2), + ILI9881C_COMMAND_INSTR(0x55, 0x92), + ILI9881C_COMMAND_INSTR(0x50, 0x96), + ILI9881C_COMMAND_INSTR(0x51, 0x96), + ILI9881C_COMMAND_INSTR(0x60, 0x22), + ILI9881C_COMMAND_INSTR(0x61, 0x00), + ILI9881C_COMMAND_INSTR(0x62, 0x19), + ILI9881C_COMMAND_INSTR(0x63, 0x00), + //---P-GAMMA START--- + ILI9881C_COMMAND_INSTR(0xA0, 0x08), + ILI9881C_COMMAND_INSTR(0xA1, 0x11), + ILI9881C_COMMAND_INSTR(0xA2, 0x19), + ILI9881C_COMMAND_INSTR(0xA3, 0x0D), + ILI9881C_COMMAND_INSTR(0xA4, 0x0D), + ILI9881C_COMMAND_INSTR(0xA5, 0x1E), + ILI9881C_COMMAND_INSTR(0xA6, 0x14), + ILI9881C_COMMAND_INSTR(0xA7, 0x17), + ILI9881C_COMMAND_INSTR(0xA8, 0x4F), + ILI9881C_COMMAND_INSTR(0xA9, 0x1A), + ILI9881C_COMMAND_INSTR(0xAA, 0x27), + ILI9881C_COMMAND_INSTR(0xAB, 0x49), + ILI9881C_COMMAND_INSTR(0xAC, 0x1A), + ILI9881C_COMMAND_INSTR(0xAD, 0x18), + ILI9881C_COMMAND_INSTR(0xAE, 0x4C), + ILI9881C_COMMAND_INSTR(0xAF, 0x22), + ILI9881C_COMMAND_INSTR(0xB0, 0x27), + ILI9881C_COMMAND_INSTR(0xB1, 0x4B), + ILI9881C_COMMAND_INSTR(0xB2, 0x60), + ILI9881C_COMMAND_INSTR(0xB3, 0x39), + //--- N-GAMMA START--- + ILI9881C_COMMAND_INSTR(0xC0, 0x08), + ILI9881C_COMMAND_INSTR(0xC1, 0x11), + ILI9881C_COMMAND_INSTR(0xC2, 0x19), + ILI9881C_COMMAND_INSTR(0xC3, 0x0D), + ILI9881C_COMMAND_INSTR(0xC4, 0x0D), + ILI9881C_COMMAND_INSTR(0xC5, 0x1E), + ILI9881C_COMMAND_INSTR(0xC6, 0x14), + ILI9881C_COMMAND_INSTR(0xC7, 0x17), + ILI9881C_COMMAND_INSTR(0xC8, 0x4F), + ILI9881C_COMMAND_INSTR(0xC9, 0x1A), + ILI9881C_COMMAND_INSTR(0xCA, 0x27), + ILI9881C_COMMAND_INSTR(0xCB, 0x49), + ILI9881C_COMMAND_INSTR(0xCC, 0x1A), + ILI9881C_COMMAND_INSTR(0xCD, 0x18), + ILI9881C_COMMAND_INSTR(0xCE, 0x4C), + ILI9881C_COMMAND_INSTR(0xCF, 0x33), + ILI9881C_COMMAND_INSTR(0xD0, 0x27), + ILI9881C_COMMAND_INSTR(0xD1, 0x4B), + ILI9881C_COMMAND_INSTR(0xD2, 0x60), + ILI9881C_COMMAND_INSTR(0xD3, 0x39), +}; + +static const struct ili9881c_instr rpi_5inch_init[] = { + ILI9881C_SWITCH_PAGE_INSTR(3), + ILI9881C_COMMAND_INSTR(0x01, 0x00), + ILI9881C_COMMAND_INSTR(0x02, 0x00), + ILI9881C_COMMAND_INSTR(0x03, 0x73), + ILI9881C_COMMAND_INSTR(0x04, 0x73), + ILI9881C_COMMAND_INSTR(0x05, 0x00), + ILI9881C_COMMAND_INSTR(0x06, 0x06), + ILI9881C_COMMAND_INSTR(0x07, 0x02), + ILI9881C_COMMAND_INSTR(0x08, 0x00), + ILI9881C_COMMAND_INSTR(0x09, 0x01), + ILI9881C_COMMAND_INSTR(0x0a, 0x01), + ILI9881C_COMMAND_INSTR(0x0b, 0x01), + ILI9881C_COMMAND_INSTR(0x0c, 0x01), + ILI9881C_COMMAND_INSTR(0x0d, 0x01), + ILI9881C_COMMAND_INSTR(0x0e, 0x01), + ILI9881C_COMMAND_INSTR(0x0f, 0x01), + ILI9881C_COMMAND_INSTR(0x10, 0x01), + ILI9881C_COMMAND_INSTR(0x11, 0x00), + ILI9881C_COMMAND_INSTR(0x12, 0x00), + ILI9881C_COMMAND_INSTR(0x13, 0x01), + ILI9881C_COMMAND_INSTR(0x14, 0x00), + ILI9881C_COMMAND_INSTR(0x15, 0x00), + ILI9881C_COMMAND_INSTR(0x16, 0x00), + ILI9881C_COMMAND_INSTR(0x17, 0x00), + ILI9881C_COMMAND_INSTR(0x18, 0x00), + ILI9881C_COMMAND_INSTR(0x19, 0x00), + ILI9881C_COMMAND_INSTR(0x1a, 0x00), + ILI9881C_COMMAND_INSTR(0x1b, 0x00), + ILI9881C_COMMAND_INSTR(0x1c, 0x00), + ILI9881C_COMMAND_INSTR(0x1d, 0x00), + ILI9881C_COMMAND_INSTR(0x1e, 0xc0), + ILI9881C_COMMAND_INSTR(0x1f, 0x80), + ILI9881C_COMMAND_INSTR(0x20, 0x04), + ILI9881C_COMMAND_INSTR(0x21, 0x03), + ILI9881C_COMMAND_INSTR(0x22, 0x00), + ILI9881C_COMMAND_INSTR(0x23, 0x00), + ILI9881C_COMMAND_INSTR(0x24, 0x00), + ILI9881C_COMMAND_INSTR(0x25, 0x00), + ILI9881C_COMMAND_INSTR(0x26, 0x00), + ILI9881C_COMMAND_INSTR(0x27, 0x00), + ILI9881C_COMMAND_INSTR(0x28, 0x33), + ILI9881C_COMMAND_INSTR(0x29, 0x03), + ILI9881C_COMMAND_INSTR(0x2a, 0x00), + ILI9881C_COMMAND_INSTR(0x2b, 0x00), + ILI9881C_COMMAND_INSTR(0x2c, 0x00), + ILI9881C_COMMAND_INSTR(0x2d, 0x00), + ILI9881C_COMMAND_INSTR(0x2e, 0x00), + ILI9881C_COMMAND_INSTR(0x2f, 0x00), + ILI9881C_COMMAND_INSTR(0x30, 0x00), + ILI9881C_COMMAND_INSTR(0x31, 0x00), + ILI9881C_COMMAND_INSTR(0x32, 0x00), + ILI9881C_COMMAND_INSTR(0x33, 0x00), + ILI9881C_COMMAND_INSTR(0x34, 0x03), + ILI9881C_COMMAND_INSTR(0x35, 0x00), + ILI9881C_COMMAND_INSTR(0x36, 0x03), + ILI9881C_COMMAND_INSTR(0x37, 0x00), + ILI9881C_COMMAND_INSTR(0x38, 0x00), + ILI9881C_COMMAND_INSTR(0x39, 0x00), + ILI9881C_COMMAND_INSTR(0x3a, 0x00), + ILI9881C_COMMAND_INSTR(0x3b, 0x00), + ILI9881C_COMMAND_INSTR(0x3c, 0x00), + ILI9881C_COMMAND_INSTR(0x3d, 0x00), + ILI9881C_COMMAND_INSTR(0x3e, 0x00), + ILI9881C_COMMAND_INSTR(0x3f, 0x00), + ILI9881C_COMMAND_INSTR(0x40, 0x00), + ILI9881C_COMMAND_INSTR(0x41, 0x00), + ILI9881C_COMMAND_INSTR(0x42, 0x00), + ILI9881C_COMMAND_INSTR(0x43, 0x00), + ILI9881C_COMMAND_INSTR(0x44, 0x00), + ILI9881C_COMMAND_INSTR(0x50, 0x01), + ILI9881C_COMMAND_INSTR(0x51, 0x23), + ILI9881C_COMMAND_INSTR(0x52, 0x45), + ILI9881C_COMMAND_INSTR(0x53, 0x67), + ILI9881C_COMMAND_INSTR(0x54, 0x89), + ILI9881C_COMMAND_INSTR(0x55, 0xab), + ILI9881C_COMMAND_INSTR(0x56, 0x01), + ILI9881C_COMMAND_INSTR(0x57, 0x23), + ILI9881C_COMMAND_INSTR(0x58, 0x45), + ILI9881C_COMMAND_INSTR(0x59, 0x67), + ILI9881C_COMMAND_INSTR(0x5a, 0x89), + ILI9881C_COMMAND_INSTR(0x5b, 0xab), + ILI9881C_COMMAND_INSTR(0x5c, 0xcd), + ILI9881C_COMMAND_INSTR(0x5d, 0xef), + ILI9881C_COMMAND_INSTR(0x5e, 0x10), + ILI9881C_COMMAND_INSTR(0x5f, 0x09), + ILI9881C_COMMAND_INSTR(0x60, 0x08), + ILI9881C_COMMAND_INSTR(0x61, 0x0f), + ILI9881C_COMMAND_INSTR(0x62, 0x0e), + ILI9881C_COMMAND_INSTR(0x63, 0x0d), + ILI9881C_COMMAND_INSTR(0x64, 0x0c), + ILI9881C_COMMAND_INSTR(0x65, 0x02), + ILI9881C_COMMAND_INSTR(0x66, 0x02), + ILI9881C_COMMAND_INSTR(0x67, 0x02), + ILI9881C_COMMAND_INSTR(0x68, 0x02), + ILI9881C_COMMAND_INSTR(0x69, 0x02), + ILI9881C_COMMAND_INSTR(0x6a, 0x02), + ILI9881C_COMMAND_INSTR(0x6b, 0x02), + ILI9881C_COMMAND_INSTR(0x6c, 0x02), + ILI9881C_COMMAND_INSTR(0x6d, 0x02), + ILI9881C_COMMAND_INSTR(0x6e, 0x02), + ILI9881C_COMMAND_INSTR(0x6f, 0x02), + ILI9881C_COMMAND_INSTR(0x70, 0x02), + ILI9881C_COMMAND_INSTR(0x71, 0x06), + ILI9881C_COMMAND_INSTR(0x72, 0x07), + ILI9881C_COMMAND_INSTR(0x73, 0x02), + ILI9881C_COMMAND_INSTR(0x74, 0x02), + ILI9881C_COMMAND_INSTR(0x75, 0x06), + ILI9881C_COMMAND_INSTR(0x76, 0x07), + ILI9881C_COMMAND_INSTR(0x77, 0x0e), + ILI9881C_COMMAND_INSTR(0x78, 0x0f), + ILI9881C_COMMAND_INSTR(0x79, 0x0c), + ILI9881C_COMMAND_INSTR(0x7a, 0x0d), + ILI9881C_COMMAND_INSTR(0x7b, 0x02), + ILI9881C_COMMAND_INSTR(0x7c, 0x02), + ILI9881C_COMMAND_INSTR(0x7d, 0x02), + ILI9881C_COMMAND_INSTR(0x7e, 0x02), + ILI9881C_COMMAND_INSTR(0x7f, 0x02), + ILI9881C_COMMAND_INSTR(0x80, 0x02), + ILI9881C_COMMAND_INSTR(0x81, 0x02), + ILI9881C_COMMAND_INSTR(0x82, 0x02), + ILI9881C_COMMAND_INSTR(0x83, 0x02), + ILI9881C_COMMAND_INSTR(0x84, 0x02), + ILI9881C_COMMAND_INSTR(0x85, 0x02), + ILI9881C_COMMAND_INSTR(0x86, 0x02), + ILI9881C_COMMAND_INSTR(0x87, 0x09), + ILI9881C_COMMAND_INSTR(0x88, 0x08), + ILI9881C_COMMAND_INSTR(0x89, 0x02), + ILI9881C_COMMAND_INSTR(0x8A, 0x02), + ILI9881C_SWITCH_PAGE_INSTR(4), + ILI9881C_COMMAND_INSTR(0x6C, 0x15), + ILI9881C_COMMAND_INSTR(0x6E, 0x2a), + ILI9881C_COMMAND_INSTR(0x6F, 0x57), + ILI9881C_COMMAND_INSTR(0x3A, 0xa4), + ILI9881C_COMMAND_INSTR(0x8D, 0x1a), + ILI9881C_COMMAND_INSTR(0x87, 0xba), + ILI9881C_COMMAND_INSTR(0x26, 0x76), + ILI9881C_COMMAND_INSTR(0xB2, 0xd1), + ILI9881C_SWITCH_PAGE_INSTR(1), + ILI9881C_COMMAND_INSTR(0x22, 0x0A), + ILI9881C_COMMAND_INSTR(0x31, 0x00), + ILI9881C_COMMAND_INSTR(0x53, 0x35), + ILI9881C_COMMAND_INSTR(0x55, 0x50), + ILI9881C_COMMAND_INSTR(0x50, 0xaf), + ILI9881C_COMMAND_INSTR(0x51, 0xaf), + ILI9881C_COMMAND_INSTR(0x60, 0x14), + ILI9881C_COMMAND_INSTR(0xA0, 0x08), + ILI9881C_COMMAND_INSTR(0xA1, 0x1d), + ILI9881C_COMMAND_INSTR(0xA2, 0x2c), + ILI9881C_COMMAND_INSTR(0xA3, 0x14), + ILI9881C_COMMAND_INSTR(0xA4, 0x19), + ILI9881C_COMMAND_INSTR(0xA5, 0x2e), + ILI9881C_COMMAND_INSTR(0xA6, 0x22), + ILI9881C_COMMAND_INSTR(0xA7, 0x23), + ILI9881C_COMMAND_INSTR(0xA8, 0x97), + ILI9881C_COMMAND_INSTR(0xA9, 0x1e), + ILI9881C_COMMAND_INSTR(0xAA, 0x29), + ILI9881C_COMMAND_INSTR(0xAB, 0x7b), + ILI9881C_COMMAND_INSTR(0xAC, 0x18), + ILI9881C_COMMAND_INSTR(0xAD, 0x17), + ILI9881C_COMMAND_INSTR(0xAE, 0x4b), + ILI9881C_COMMAND_INSTR(0xAF, 0x1f), + ILI9881C_COMMAND_INSTR(0xB0, 0x27), + ILI9881C_COMMAND_INSTR(0xB1, 0x52), + ILI9881C_COMMAND_INSTR(0xB2, 0x63), + ILI9881C_COMMAND_INSTR(0xB3, 0x39), + ILI9881C_COMMAND_INSTR(0xC0, 0x08), + ILI9881C_COMMAND_INSTR(0xC1, 0x1d), + ILI9881C_COMMAND_INSTR(0xC2, 0x2c), + ILI9881C_COMMAND_INSTR(0xC3, 0x14), + ILI9881C_COMMAND_INSTR(0xC4, 0x19), + ILI9881C_COMMAND_INSTR(0xC5, 0x2e), + ILI9881C_COMMAND_INSTR(0xC6, 0x22), + ILI9881C_COMMAND_INSTR(0xC7, 0x23), + ILI9881C_COMMAND_INSTR(0xC8, 0x97), + ILI9881C_COMMAND_INSTR(0xC9, 0x1e), + ILI9881C_COMMAND_INSTR(0xCA, 0x29), + ILI9881C_COMMAND_INSTR(0xCB, 0x7b), + ILI9881C_COMMAND_INSTR(0xCC, 0x18), + ILI9881C_COMMAND_INSTR(0xCD, 0x17), + ILI9881C_COMMAND_INSTR(0xCE, 0x4b), + ILI9881C_COMMAND_INSTR(0xCF, 0x1f), + ILI9881C_COMMAND_INSTR(0xD0, 0x27), + ILI9881C_COMMAND_INSTR(0xD1, 0x52), + ILI9881C_COMMAND_INSTR(0xD2, 0x63), + ILI9881C_COMMAND_INSTR(0xD3, 0x39), +}; + +static const struct ili9881c_instr rpi_7inch_init[] = { + ILI9881C_SWITCH_PAGE_INSTR(3), + ILI9881C_COMMAND_INSTR(0x01, 0x00), + ILI9881C_COMMAND_INSTR(0x02, 0x00), + ILI9881C_COMMAND_INSTR(0x03, 0x73), + ILI9881C_COMMAND_INSTR(0x04, 0x00), + ILI9881C_COMMAND_INSTR(0x05, 0x00), + ILI9881C_COMMAND_INSTR(0x06, 0x0a), + ILI9881C_COMMAND_INSTR(0x07, 0x00), + ILI9881C_COMMAND_INSTR(0x08, 0x00), + ILI9881C_COMMAND_INSTR(0x09, 0x61), + ILI9881C_COMMAND_INSTR(0x0a, 0x00), + ILI9881C_COMMAND_INSTR(0x0b, 0x00), + ILI9881C_COMMAND_INSTR(0x0c, 0x01), + ILI9881C_COMMAND_INSTR(0x0d, 0x00), + ILI9881C_COMMAND_INSTR(0x0e, 0x00), + ILI9881C_COMMAND_INSTR(0x0f, 0x61), + ILI9881C_COMMAND_INSTR(0x10, 0x61), + ILI9881C_COMMAND_INSTR(0x11, 0x00), + ILI9881C_COMMAND_INSTR(0x12, 0x00), + ILI9881C_COMMAND_INSTR(0x13, 0x00), + ILI9881C_COMMAND_INSTR(0x14, 0x00), + ILI9881C_COMMAND_INSTR(0x15, 0x00), + ILI9881C_COMMAND_INSTR(0x16, 0x00), + ILI9881C_COMMAND_INSTR(0x17, 0x00), + ILI9881C_COMMAND_INSTR(0x18, 0x00), + ILI9881C_COMMAND_INSTR(0x19, 0x00), + ILI9881C_COMMAND_INSTR(0x1a, 0x00), + ILI9881C_COMMAND_INSTR(0x1b, 0x00), + ILI9881C_COMMAND_INSTR(0x1c, 0x00), + ILI9881C_COMMAND_INSTR(0x1d, 0x00), + ILI9881C_COMMAND_INSTR(0x1e, 0x40), + ILI9881C_COMMAND_INSTR(0x1f, 0x80), + ILI9881C_COMMAND_INSTR(0x20, 0x06), + ILI9881C_COMMAND_INSTR(0x21, 0x01), + ILI9881C_COMMAND_INSTR(0x22, 0x00), + ILI9881C_COMMAND_INSTR(0x23, 0x00), + ILI9881C_COMMAND_INSTR(0x24, 0x00), + ILI9881C_COMMAND_INSTR(0x25, 0x00), + ILI9881C_COMMAND_INSTR(0x26, 0x00), + ILI9881C_COMMAND_INSTR(0x27, 0x00), + ILI9881C_COMMAND_INSTR(0x28, 0x33), + ILI9881C_COMMAND_INSTR(0x29, 0x03), + ILI9881C_COMMAND_INSTR(0x2a, 0x00), + ILI9881C_COMMAND_INSTR(0x2b, 0x00), + ILI9881C_COMMAND_INSTR(0x2c, 0x00), + ILI9881C_COMMAND_INSTR(0x2d, 0x00), + ILI9881C_COMMAND_INSTR(0x2e, 0x00), + ILI9881C_COMMAND_INSTR(0x2f, 0x00), + ILI9881C_COMMAND_INSTR(0x30, 0x00), + ILI9881C_COMMAND_INSTR(0x31, 0x00), + ILI9881C_COMMAND_INSTR(0x32, 0x00), + ILI9881C_COMMAND_INSTR(0x33, 0x00), + ILI9881C_COMMAND_INSTR(0x34, 0x04), + ILI9881C_COMMAND_INSTR(0x35, 0x00), + ILI9881C_COMMAND_INSTR(0x36, 0x00), + ILI9881C_COMMAND_INSTR(0x37, 0x00), + ILI9881C_COMMAND_INSTR(0x38, 0x3c), + ILI9881C_COMMAND_INSTR(0x39, 0x00), + ILI9881C_COMMAND_INSTR(0x3a, 0x00), + ILI9881C_COMMAND_INSTR(0x3b, 0x00), + ILI9881C_COMMAND_INSTR(0x3c, 0x00), + ILI9881C_COMMAND_INSTR(0x3d, 0x00), + ILI9881C_COMMAND_INSTR(0x3e, 0x00), + ILI9881C_COMMAND_INSTR(0x3f, 0x00), + ILI9881C_COMMAND_INSTR(0x40, 0x00), + ILI9881C_COMMAND_INSTR(0x41, 0x00), + ILI9881C_COMMAND_INSTR(0x42, 0x00), + ILI9881C_COMMAND_INSTR(0x43, 0x00), + ILI9881C_COMMAND_INSTR(0x44, 0x00), + ILI9881C_COMMAND_INSTR(0x50, 0x10), + ILI9881C_COMMAND_INSTR(0x51, 0x32), + ILI9881C_COMMAND_INSTR(0x52, 0x54), + ILI9881C_COMMAND_INSTR(0x53, 0x76), + ILI9881C_COMMAND_INSTR(0x54, 0x98), + ILI9881C_COMMAND_INSTR(0x55, 0xba), + ILI9881C_COMMAND_INSTR(0x56, 0x10), + ILI9881C_COMMAND_INSTR(0x57, 0x32), + ILI9881C_COMMAND_INSTR(0x58, 0x54), + ILI9881C_COMMAND_INSTR(0x59, 0x76), + ILI9881C_COMMAND_INSTR(0x5a, 0x98), + ILI9881C_COMMAND_INSTR(0x5b, 0xba), + ILI9881C_COMMAND_INSTR(0x5c, 0xdc), + ILI9881C_COMMAND_INSTR(0x5d, 0xfe), + ILI9881C_COMMAND_INSTR(0x5e, 0x00), + ILI9881C_COMMAND_INSTR(0x5f, 0x0e), + ILI9881C_COMMAND_INSTR(0x60, 0x0f), + ILI9881C_COMMAND_INSTR(0x61, 0x0c), + ILI9881C_COMMAND_INSTR(0x62, 0x0d), + ILI9881C_COMMAND_INSTR(0x63, 0x06), + ILI9881C_COMMAND_INSTR(0x64, 0x07), + ILI9881C_COMMAND_INSTR(0x65, 0x02), + ILI9881C_COMMAND_INSTR(0x66, 0x02), + ILI9881C_COMMAND_INSTR(0x67, 0x02), + ILI9881C_COMMAND_INSTR(0x68, 0x02), + ILI9881C_COMMAND_INSTR(0x69, 0x01), + ILI9881C_COMMAND_INSTR(0x6a, 0x00), + ILI9881C_COMMAND_INSTR(0x6b, 0x02), + ILI9881C_COMMAND_INSTR(0x6c, 0x15), + ILI9881C_COMMAND_INSTR(0x6d, 0x14), + ILI9881C_COMMAND_INSTR(0x6e, 0x02), + ILI9881C_COMMAND_INSTR(0x6f, 0x02), + ILI9881C_COMMAND_INSTR(0x70, 0x02), + ILI9881C_COMMAND_INSTR(0x71, 0x02), + ILI9881C_COMMAND_INSTR(0x72, 0x02), + ILI9881C_COMMAND_INSTR(0x73, 0x02), + ILI9881C_COMMAND_INSTR(0x74, 0x02), + ILI9881C_COMMAND_INSTR(0x75, 0x0e), + ILI9881C_COMMAND_INSTR(0x76, 0x0f), + ILI9881C_COMMAND_INSTR(0x77, 0x0c), + ILI9881C_COMMAND_INSTR(0x78, 0x0d), + ILI9881C_COMMAND_INSTR(0x79, 0x06), + ILI9881C_COMMAND_INSTR(0x7a, 0x07), + ILI9881C_COMMAND_INSTR(0x7b, 0x02), + ILI9881C_COMMAND_INSTR(0x7c, 0x02), + ILI9881C_COMMAND_INSTR(0x7d, 0x02), + ILI9881C_COMMAND_INSTR(0x7e, 0x02), + ILI9881C_COMMAND_INSTR(0x7f, 0x01), + ILI9881C_COMMAND_INSTR(0x80, 0x00), + ILI9881C_COMMAND_INSTR(0x81, 0x02), + ILI9881C_COMMAND_INSTR(0x82, 0x14), + ILI9881C_COMMAND_INSTR(0x83, 0x15), + ILI9881C_COMMAND_INSTR(0x84, 0x02), + ILI9881C_COMMAND_INSTR(0x85, 0x02), + ILI9881C_COMMAND_INSTR(0x86, 0x02), + ILI9881C_COMMAND_INSTR(0x87, 0x02), + ILI9881C_COMMAND_INSTR(0x88, 0x02), + ILI9881C_COMMAND_INSTR(0x89, 0x02), + ILI9881C_COMMAND_INSTR(0x8A, 0x02), + ILI9881C_SWITCH_PAGE_INSTR(4), + ILI9881C_COMMAND_INSTR(0x6C, 0x15), + ILI9881C_COMMAND_INSTR(0x6E, 0x2A), + ILI9881C_COMMAND_INSTR(0x6F, 0x33), + ILI9881C_COMMAND_INSTR(0x3B, 0x98), + ILI9881C_COMMAND_INSTR(0x3a, 0x94), + ILI9881C_COMMAND_INSTR(0x8D, 0x14), + ILI9881C_COMMAND_INSTR(0x87, 0xBA), + ILI9881C_COMMAND_INSTR(0x26, 0x76), + ILI9881C_COMMAND_INSTR(0xB2, 0xD1), + ILI9881C_COMMAND_INSTR(0xB5, 0x06), + ILI9881C_COMMAND_INSTR(0x38, 0x01), + ILI9881C_COMMAND_INSTR(0x39, 0x00), + ILI9881C_SWITCH_PAGE_INSTR(1), + ILI9881C_COMMAND_INSTR(0x22, 0x0A), + ILI9881C_COMMAND_INSTR(0x31, 0x00), + ILI9881C_COMMAND_INSTR(0x53, 0x7d), + ILI9881C_COMMAND_INSTR(0x55, 0x8f), + ILI9881C_COMMAND_INSTR(0x40, 0x33), + ILI9881C_COMMAND_INSTR(0x50, 0x96), + ILI9881C_COMMAND_INSTR(0x51, 0x96), + ILI9881C_COMMAND_INSTR(0x60, 0x23), + ILI9881C_COMMAND_INSTR(0xA0, 0x08), + ILI9881C_COMMAND_INSTR(0xA1, 0x1d), + ILI9881C_COMMAND_INSTR(0xA2, 0x2a), + ILI9881C_COMMAND_INSTR(0xA3, 0x10), + ILI9881C_COMMAND_INSTR(0xA4, 0x15), + ILI9881C_COMMAND_INSTR(0xA5, 0x28), + ILI9881C_COMMAND_INSTR(0xA6, 0x1c), + ILI9881C_COMMAND_INSTR(0xA7, 0x1d), + ILI9881C_COMMAND_INSTR(0xA8, 0x7e), + ILI9881C_COMMAND_INSTR(0xA9, 0x1d), + ILI9881C_COMMAND_INSTR(0xAA, 0x29), + ILI9881C_COMMAND_INSTR(0xAB, 0x6b), + ILI9881C_COMMAND_INSTR(0xAC, 0x1a), + ILI9881C_COMMAND_INSTR(0xAD, 0x18), + ILI9881C_COMMAND_INSTR(0xAE, 0x4b), + ILI9881C_COMMAND_INSTR(0xAF, 0x20), + ILI9881C_COMMAND_INSTR(0xB0, 0x27), + ILI9881C_COMMAND_INSTR(0xB1, 0x50), + ILI9881C_COMMAND_INSTR(0xB2, 0x64), + ILI9881C_COMMAND_INSTR(0xB3, 0x39), + ILI9881C_COMMAND_INSTR(0xC0, 0x08), + ILI9881C_COMMAND_INSTR(0xC1, 0x1d), + ILI9881C_COMMAND_INSTR(0xC2, 0x2a), + ILI9881C_COMMAND_INSTR(0xC3, 0x10), + ILI9881C_COMMAND_INSTR(0xC4, 0x15), + ILI9881C_COMMAND_INSTR(0xC5, 0x28), + ILI9881C_COMMAND_INSTR(0xC6, 0x1c), + ILI9881C_COMMAND_INSTR(0xC7, 0x1d), + ILI9881C_COMMAND_INSTR(0xC8, 0x7e), + ILI9881C_COMMAND_INSTR(0xC9, 0x1d), + ILI9881C_COMMAND_INSTR(0xCA, 0x29), + ILI9881C_COMMAND_INSTR(0xCB, 0x6b), + ILI9881C_COMMAND_INSTR(0xCC, 0x1a), + ILI9881C_COMMAND_INSTR(0xCD, 0x18), + ILI9881C_COMMAND_INSTR(0xCE, 0x4b), + ILI9881C_COMMAND_INSTR(0xCF, 0x20), + ILI9881C_COMMAND_INSTR(0xD0, 0x27), + ILI9881C_COMMAND_INSTR(0xD1, 0x50), + ILI9881C_COMMAND_INSTR(0xD2, 0x64), + ILI9881C_COMMAND_INSTR(0xD3, 0x39), +}; + +static inline struct ili9881c *panel_to_ili9881c(struct drm_panel *panel) +{ + return container_of(panel, struct ili9881c, panel); +} + +/* + * The panel seems to accept some private DCS commands that map + * directly to registers. + * + * It is organised by page, with each page having its own set of + * registers, and the first page looks like it's holding the standard + * DCS commands. + * + * So before any attempt at sending a command or data, we have to be + * sure if we're in the right page or not. + */ +static int ili9881c_switch_page(struct ili9881c *ctx, u8 page) +{ + u8 buf[4] = { 0xff, 0x98, 0x81, page }; + int ret; + + ret = mipi_dsi_dcs_write_buffer(ctx->dsi, buf, sizeof(buf)); + if (ret < 0) + return ret; + + return 0; +} + +static int ili9881c_send_cmd_data(struct ili9881c *ctx, u8 cmd, u8 data) +{ + u8 buf[2] = { cmd, data }; + int ret; + + ret = mipi_dsi_dcs_write_buffer(ctx->dsi, buf, sizeof(buf)); + if (ret < 0) + return ret; + + return 0; +} + +static int ili9881c_prepare(struct drm_panel *panel) +{ + struct ili9881c *ctx = panel_to_ili9881c(panel); + unsigned int i; + int ret; + + /* Power the panel */ + ret = regulator_enable(ctx->power); + if (ret) + return ret; + msleep(5); + + /* And reset it */ + gpiod_set_value_cansleep(ctx->reset, 1); + msleep(20); + + gpiod_set_value_cansleep(ctx->reset, 0); + msleep(20); + + for (i = 0; i < ctx->desc->init_length; i++) { + const struct ili9881c_instr *instr = &ctx->desc->init[i]; + + if (instr->op == ILI9881C_SWITCH_PAGE) + ret = ili9881c_switch_page(ctx, instr->arg.page); + else if (instr->op == ILI9881C_COMMAND) + ret = ili9881c_send_cmd_data(ctx, instr->arg.cmd.cmd, + instr->arg.cmd.data); + + if (ret) + return ret; + } + + ret = ili9881c_switch_page(ctx, 0); + if (ret) + return ret; + + ret = mipi_dsi_dcs_set_tear_on(ctx->dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK); + if (ret) + return ret; + + ret = mipi_dsi_dcs_exit_sleep_mode(ctx->dsi); + if (ret) + return ret; + + if (ctx->desc->flags & ILI9881_FLAGS_PANEL_ON_IN_PREPARE) { + msleep(120); + + ret = mipi_dsi_dcs_set_display_on(ctx->dsi); + } + + return 0; +} + +static int ili9881c_enable(struct drm_panel *panel) +{ + struct ili9881c *ctx = panel_to_ili9881c(panel); + + if (!(ctx->desc->flags & ILI9881_FLAGS_PANEL_ON_IN_PREPARE)) { + msleep(120); + + mipi_dsi_dcs_set_display_on(ctx->dsi); + } + + return 0; +} + +static int ili9881c_disable(struct drm_panel *panel) +{ + struct ili9881c *ctx = panel_to_ili9881c(panel); + + if (!(ctx->desc->flags & ILI9881_FLAGS_PANEL_ON_IN_PREPARE)) + mipi_dsi_dcs_set_display_off(ctx->dsi); + + return 0; +} + +static int ili9881c_unprepare(struct drm_panel *panel) +{ + struct ili9881c *ctx = panel_to_ili9881c(panel); + + if (!(ctx->desc->flags & ILI9881_FLAGS_NO_SHUTDOWN_CMDS)) { + if (ctx->desc->flags & ILI9881_FLAGS_PANEL_ON_IN_PREPARE) + mipi_dsi_dcs_set_display_off(ctx->dsi); + + mipi_dsi_dcs_enter_sleep_mode(ctx->dsi); + } + + regulator_disable(ctx->power); + gpiod_set_value_cansleep(ctx->reset, 1); + + return 0; +} + +static const struct drm_display_mode lhr050h41_default_mode = { + .clock = 62000, + + .hdisplay = 720, + .hsync_start = 720 + 10, + .hsync_end = 720 + 10 + 20, + .htotal = 720 + 10 + 20 + 30, + + .vdisplay = 1280, + .vsync_start = 1280 + 10, + .vsync_end = 1280 + 10 + 10, + .vtotal = 1280 + 10 + 10 + 20, + + .width_mm = 62, + .height_mm = 110, +}; + +static const struct drm_display_mode k101_im2byl02_default_mode = { + .clock = 69700, + + .hdisplay = 800, + .hsync_start = 800 + 52, + .hsync_end = 800 + 52 + 8, + .htotal = 800 + 52 + 8 + 48, + + .vdisplay = 1280, + .vsync_start = 1280 + 16, + .vsync_end = 1280 + 16 + 6, + .vtotal = 1280 + 16 + 6 + 15, + + .width_mm = 135, .height_mm = 217, }; @@ -1441,6 +2278,71 @@ static const struct drm_display_mode am8001280g_default_mode = { .height_mm = 151, }; +static const struct drm_display_mode nwe080_default_mode = { + .clock = 71750, + + .hdisplay = 800, + .hsync_start = 800 + 52, + .hsync_end = 800 + 52 + 8, + .htotal = 800 + 52 + 8 + 48, + + .vdisplay = 1280, + .vsync_start = 1280 + 16, + .vsync_end = 1280 + 16 + 6, + .vtotal = 1280 + 16 + 6 + 15, + + .width_mm = 107, + .height_mm = 170, +}; + +static const struct drm_display_mode cfaf7201280a0_050tx_default_mode = { + .clock = 72830, + .hdisplay = 720, + .hsync_start = 720 + 87, + .hsync_end = 720 + 87 + 20, + .htotal = 720 + 87 + 20 + 87, + .vdisplay = 1280, + .vsync_start = 1280 + 16, + .vsync_end = 1280 + 16 + 8, + .vtotal = 1280 + 16 + 8 + 16, + .width_mm = 62, + .height_mm = 1108 +}; + +static const struct drm_display_mode rpi_5inch_default_mode = { + .clock = 83333, + + .hdisplay = 720, + .hsync_start = 720 + 110, + .hsync_end = 720 + 110 + 2, + .htotal = 720 + 110 + 2 + 105, + + .vdisplay = 1280, + .vsync_start = 1280 + 100, + .vsync_end = 1280 + 100 + 2, + .vtotal = 1280 + 100 + 2 + 100, + + .width_mm = 62, + .height_mm = 110, +}; + +static const struct drm_display_mode rpi_7inch_default_mode = { + .clock = 83330, + + .hdisplay = 720, + .hsync_start = 720 + 239, + .hsync_end = 720 + 239 + 33, + .htotal = 720 + 239 + 33 + 50, + + .vdisplay = 1280, + .vsync_start = 1280 + 20, + .vsync_end = 1280 + 20 + 2, + .vtotal = 1280 + 20 + 2 + 30, + + .width_mm = 90, + .height_mm = 151, +}; + static int ili9881c_get_modes(struct drm_panel *panel, struct drm_connector *connector) { @@ -1501,6 +2403,7 @@ static int ili9881c_dsi_probe(struct mipi_dsi_device *dsi) ctx->dsi = dsi; ctx->desc = of_device_get_match_data(&dsi->dev); + ctx->panel.prepare_prev_first = true; drm_panel_init(&ctx->panel, &dsi->dev, &ili9881c_funcs, DRM_MODE_CONNECTOR_DSI); @@ -1531,9 +2434,13 @@ static int ili9881c_dsi_probe(struct mipi_dsi_device *dsi) dsi->mode_flags = ctx->desc->mode_flags; dsi->format = MIPI_DSI_FMT_RGB888; - dsi->lanes = 4; + dsi->lanes = ctx->desc->lanes; + + ret = mipi_dsi_attach(dsi); + if (ret) + drm_panel_remove(&ctx->panel); - return mipi_dsi_attach(dsi); + return ret; } static void ili9881c_dsi_remove(struct mipi_dsi_device *dsi) @@ -1542,6 +2449,9 @@ static void ili9881c_dsi_remove(struct mipi_dsi_device *dsi) mipi_dsi_detach(dsi); drm_panel_remove(&ctx->panel); + + gpiod_set_value_cansleep(ctx->reset, 1); + regulator_disable(ctx->power); } static const struct ili9881c_desc lhr050h41_desc = { @@ -1549,6 +2459,7 @@ static const struct ili9881c_desc lhr050h41_desc = { .init_length = ARRAY_SIZE(lhr050h41_init), .mode = &lhr050h41_default_mode, .mode_flags = MIPI_DSI_MODE_VIDEO_SYNC_PULSE, + .lanes = 4, }; static const struct ili9881c_desc k101_im2byl02_desc = { @@ -1556,6 +2467,7 @@ static const struct ili9881c_desc k101_im2byl02_desc = { .init_length = ARRAY_SIZE(k101_im2byl02_init), .mode = &k101_im2byl02_default_mode, .mode_flags = MIPI_DSI_MODE_VIDEO_SYNC_PULSE, + .lanes = 4, }; static const struct ili9881c_desc kd050hdfia020_desc = { @@ -1580,6 +2492,7 @@ static const struct ili9881c_desc w552946aba_desc = { .mode = &w552946aba_default_mode, .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_NO_EOT_PACKET, + .lanes = 4, }; static const struct ili9881c_desc am8001280g_desc = { @@ -1590,6 +2503,39 @@ static const struct ili9881c_desc am8001280g_desc = { MIPI_DSI_CLOCK_NON_CONTINUOUS | MIPI_DSI_MODE_LPM, }; +static const struct ili9881c_desc nwe080_desc = { + .init = nwe080_init, + .init_length = ARRAY_SIZE(nwe080_init), + .mode = &nwe080_default_mode, + .mode_flags = MIPI_DSI_MODE_VIDEO_SYNC_PULSE | MIPI_DSI_MODE_VIDEO, +}; + +static const struct ili9881c_desc cfaf7201280a0_050tx_desc = { + .init = cfaf7201280a0_050tx_init, + .init_length = ARRAY_SIZE(cfaf7201280a0_050tx_init), + .mode = &cfaf7201280a0_050tx_default_mode, + .mode_flags = MIPI_DSI_MODE_VIDEO_SYNC_PULSE | MIPI_DSI_MODE_VIDEO, + .lanes = 4, +}; + +static const struct ili9881c_desc rpi_5inch_desc = { + .init = rpi_5inch_init, + .init_length = ARRAY_SIZE(rpi_5inch_init), + .mode = &rpi_5inch_default_mode, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_LPM, + .lanes = 2, +}; + +static const struct ili9881c_desc rpi_7inch_desc = { + .init = rpi_7inch_init, + .init_length = ARRAY_SIZE(rpi_7inch_init), + .mode = &rpi_7inch_default_mode, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_LPM, + .lanes = 2, + .flags = ILI9881_FLAGS_NO_SHUTDOWN_CMDS | + ILI9881_FLAGS_PANEL_ON_IN_PREPARE, +}; + static const struct of_device_id ili9881c_of_match[] = { { .compatible = "bananapi,lhr050h41", .data = &lhr050h41_desc }, { .compatible = "feixin,k101-im2byl02", .data = &k101_im2byl02_desc }, @@ -1597,6 +2543,10 @@ static const struct of_device_id ili9881c_of_match[] = { { .compatible = "tdo,tl050hdv35", .data = &tl050hdv35_desc }, { .compatible = "wanchanglong,w552946aba", .data = &w552946aba_desc }, { .compatible = "ampire,am8001280g", .data = &am8001280g_desc }, + { .compatible = "crystalfontz,cfaf7201280a0_050tx", .data = &cfaf7201280a0_050tx_desc }, + { .compatible = "nwe,nwe080", .data = &nwe080_desc }, + { .compatible = "raspberrypi,dsi-5inch", &rpi_5inch_desc }, + { .compatible = "raspberrypi,dsi-7inch", &rpi_7inch_desc }, { } }; MODULE_DEVICE_TABLE(of, ili9881c_of_match); diff --git a/drivers/gpu/drm/panel/panel-jdi-lt070me05000.c b/drivers/gpu/drm/panel/panel-jdi-lt070me05000.c index b1ce186de2616b..e3076a2d8b8e42 100644 --- a/drivers/gpu/drm/panel/panel-jdi-lt070me05000.c +++ b/drivers/gpu/drm/panel/panel-jdi-lt070me05000.c @@ -190,11 +190,11 @@ static int jdi_panel_unprepare(struct drm_panel *panel) if (ret < 0) dev_err(dev, "regulator disable failed, %d\n", ret); - gpiod_set_value(jdi->enable_gpio, 0); + gpiod_set_value_cansleep(jdi->enable_gpio, 0); - gpiod_set_value(jdi->reset_gpio, 1); + gpiod_set_value_cansleep(jdi->reset_gpio, 1); - gpiod_set_value(jdi->dcdc_en_gpio, 0); + gpiod_set_value_cansleep(jdi->dcdc_en_gpio, 0); return 0; } @@ -213,13 +213,13 @@ static int jdi_panel_prepare(struct drm_panel *panel) msleep(20); - gpiod_set_value(jdi->dcdc_en_gpio, 1); + gpiod_set_value_cansleep(jdi->dcdc_en_gpio, 1); usleep_range(10, 20); - gpiod_set_value(jdi->reset_gpio, 0); + gpiod_set_value_cansleep(jdi->reset_gpio, 0); usleep_range(10, 20); - gpiod_set_value(jdi->enable_gpio, 1); + gpiod_set_value_cansleep(jdi->enable_gpio, 1); usleep_range(10, 20); ret = jdi_panel_init(jdi); @@ -241,11 +241,11 @@ static int jdi_panel_prepare(struct drm_panel *panel) if (ret < 0) dev_err(dev, "regulator disable failed, %d\n", ret); - gpiod_set_value(jdi->enable_gpio, 0); + gpiod_set_value_cansleep(jdi->enable_gpio, 0); - gpiod_set_value(jdi->reset_gpio, 1); + gpiod_set_value_cansleep(jdi->reset_gpio, 1); - gpiod_set_value(jdi->dcdc_en_gpio, 0); + gpiod_set_value_cansleep(jdi->dcdc_en_gpio, 0); return ret; } @@ -402,6 +402,7 @@ static int jdi_panel_add(struct jdi_panel *jdi) return dev_err_probe(dev, PTR_ERR(jdi->backlight), "failed to register backlight %d\n", ret); + jdi->base.prepare_prev_first = true; drm_panel_init(&jdi->base, &jdi->dsi->dev, &jdi_panel_funcs, DRM_MODE_CONNECTOR_DSI); diff --git a/drivers/gpu/drm/panel/panel-raspberrypi-touchscreen.c b/drivers/gpu/drm/panel/panel-raspberrypi-touchscreen.c index 4618c892cdd652..4c418962aa9b56 100644 --- a/drivers/gpu/drm/panel/panel-raspberrypi-touchscreen.c +++ b/drivers/gpu/drm/panel/panel-raspberrypi-touchscreen.c @@ -218,7 +218,35 @@ static struct rpi_touchscreen *panel_to_ts(struct drm_panel *panel) static int rpi_touchscreen_i2c_read(struct rpi_touchscreen *ts, u8 reg) { - return i2c_smbus_read_byte_data(ts->i2c, reg); + struct i2c_client *client = ts->i2c; + struct i2c_msg msgs[1]; + u8 addr_buf[1] = { reg }; + u8 data_buf[1] = { 0, }; + int ret; + + /* Write register address */ + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = ARRAY_SIZE(addr_buf); + msgs[0].buf = addr_buf; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + usleep_range(100, 300); + + /* Read data from register */ + msgs[0].addr = client->addr; + msgs[0].flags = I2C_M_RD; + msgs[0].len = 1; + msgs[0].buf = data_buf; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + return data_buf[0]; } static void rpi_touchscreen_i2c_write(struct rpi_touchscreen *ts, @@ -267,12 +295,21 @@ static int rpi_touchscreen_noop(struct drm_panel *panel) static int rpi_touchscreen_prepare(struct drm_panel *panel) { struct rpi_touchscreen *ts = panel_to_ts(panel); - int i; + int i, data; + /* + * Power up the Toshiba bridge. The Atmel device can misbehave + * over I2C for a few ms after writes to REG_POWERON (including the + * write in rpi_touchscreen_disable()), so sleep before and after. + * Also to ensure that the bridge has been off for at least 100ms. + */ + msleep(100); rpi_touchscreen_i2c_write(ts, REG_POWERON, 1); + usleep_range(20000, 25000); /* Wait for nPWRDWN to go low to indicate poweron is done. */ for (i = 0; i < 100; i++) { - if (rpi_touchscreen_i2c_read(ts, REG_PORTB) & 1) + data = rpi_touchscreen_i2c_read(ts, REG_PORTB); + if (data >= 0 && (data & 1)) break; } @@ -398,6 +435,7 @@ static int rpi_touchscreen_probe(struct i2c_client *i2c) /* Turn off at boot, so we can cleanly sequence powering on. */ rpi_touchscreen_i2c_write(ts, REG_POWERON, 0); + usleep_range(20000, 25000); /* Look up the DSI host. It needs to probe before we do. */ endpoint = of_graph_get_next_endpoint(dev->of_node, NULL); diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c index 06381c62820975..9fcee5fabe2845 100644 --- a/drivers/gpu/drm/panel/panel-simple.c +++ b/drivers/gpu/drm/panel/panel-simple.c @@ -151,8 +151,6 @@ struct panel_simple { const struct drm_edid *drm_edid; struct drm_display_mode override_mode; - - enum drm_panel_orientation orientation; }; static inline struct panel_simple *to_panel_simple(struct drm_panel *panel) @@ -387,12 +385,6 @@ static int panel_simple_get_modes(struct drm_panel *panel, /* add hard-coded panel modes */ num += panel_simple_get_non_edid_modes(p, connector); - /* - * TODO: Remove once all drm drivers call - * drm_connector_set_orientation_from_panel() - */ - drm_connector_set_panel_orientation(connector, p->orientation); - return num; } @@ -413,20 +405,12 @@ static int panel_simple_get_timings(struct drm_panel *panel, return p->desc->num_timings; } -static enum drm_panel_orientation panel_simple_get_orientation(struct drm_panel *panel) -{ - struct panel_simple *p = to_panel_simple(panel); - - return p->orientation; -} - static const struct drm_panel_funcs panel_simple_funcs = { .disable = panel_simple_disable, .unprepare = panel_simple_unprepare, .prepare = panel_simple_prepare, .enable = panel_simple_enable, .get_modes = panel_simple_get_modes, - .get_orientation = panel_simple_get_orientation, .get_timings = panel_simple_get_timings, }; @@ -463,6 +447,7 @@ static int panel_dpi_probe(struct device *dev, of_property_read_u32(np, "width-mm", &desc->size.width); of_property_read_u32(np, "height-mm", &desc->size.height); + of_property_read_u32(np, "bus-format", &desc->bus_format); /* Extract bus_flags from display_timing */ bus_flags = 0; @@ -472,6 +457,8 @@ static int panel_dpi_probe(struct device *dev, /* We do not know the connector for the DT node, so guess it */ desc->connector_type = DRM_MODE_CONNECTOR_DPI; + /* Likewise for the bit depth. */ + desc->bpc = 8; panel->desc = desc; @@ -595,12 +582,6 @@ static int panel_simple_probe(struct device *dev, const struct panel_desc *desc) return dev_err_probe(dev, PTR_ERR(panel->enable_gpio), "failed to request GPIO\n"); - err = of_drm_get_panel_orientation(dev->of_node, &panel->orientation); - if (err) { - dev_err(dev, "%pOF: failed to get orientation %d\n", dev->of_node, err); - return err; - } - ddc = of_parse_phandle(dev->of_node, "ddc-i2c-bus", 0); if (ddc) { panel->ddc = of_find_i2c_adapter_by_node(ddc); @@ -2261,6 +2242,32 @@ static const struct panel_desc friendlyarm_hd702e = { }, }; +static const struct drm_display_mode geekworm_mzp280_mode = { + .clock = 32000, + .hdisplay = 480, + .hsync_start = 480 + 41, + .hsync_end = 480 + 41 + 20, + .htotal = 480 + 41 + 20 + 60, + .vdisplay = 640, + .vsync_start = 640 + 5, + .vsync_end = 640 + 5 + 10, + .vtotal = 640 + 5 + 10 + 10, + .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, +}; + +static const struct panel_desc geekworm_mzp280 = { + .modes = &geekworm_mzp280_mode, + .num_modes = 1, + .bpc = 6, + .size = { + .width = 47, + .height = 61, + }, + .bus_format = MEDIA_BUS_FMT_RGB565_1X24_CPADHI, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE, + .connector_type = DRM_MODE_CONNECTOR_DPI, +}; + static const struct drm_display_mode giantplus_gpg482739qs5_mode = { .clock = 9000, .hdisplay = 480, @@ -2441,6 +2448,38 @@ static const struct panel_desc innolux_at043tn24 = { .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE, }; +static const struct display_timing innolux_at056tn53v1_timing = { + .pixelclock = { 39700000, 39700000, 39700000}, + .hactive = { 640, 640, 640 }, + .hfront_porch = { 16, 16, 16 }, + .hback_porch = { 134, 134, 134 }, + .hsync_len = { 10, 10, 10}, + .vactive = { 480, 480, 480 }, + .vfront_porch = { 32, 32, 32}, + .vback_porch = { 11, 11, 11 }, + .vsync_len = { 2, 2, 2 }, + .flags = DRM_MODE_FLAG_PVSYNC | DRM_MODE_FLAG_PHSYNC, +}; + +static const struct panel_desc innolux_at056tn53v1 = { + .timings = &innolux_at056tn53v1_timing, + .num_timings = 1, + .bpc = 6, + .size = { + .width = 112, + .height = 84, + }, + .delay = { + .prepare = 50, + .enable = 200, + .disable = 110, + .unprepare = 200, + }, + .bus_format = MEDIA_BUS_FMT_BGR666_1X24_CPADHI, + .bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE, + .connector_type = DRM_MODE_CONNECTOR_DPI, +}; + static const struct drm_display_mode innolux_at070tn92_mode = { .clock = 33333, .hdisplay = 800, @@ -3854,6 +3893,31 @@ static const struct panel_desc rocktech_rk043fn48h = { .connector_type = DRM_MODE_CONNECTOR_DPI, }; +static const struct drm_display_mode raspberrypi_7inch_mode = { + .clock = 30000, + .hdisplay = 800, + .hsync_start = 800 + 131, + .hsync_end = 800 + 131 + 2, + .htotal = 800 + 131 + 2 + 45, + .vdisplay = 480, + .vsync_start = 480 + 7, + .vsync_end = 480 + 7 + 2, + .vtotal = 480 + 7 + 2 + 22, + .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, +}; + +static const struct panel_desc raspberrypi_7inch = { + .modes = &raspberrypi_7inch_mode, + .num_modes = 1, + .bpc = 8, + .size = { + .width = 154, + .height = 86, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, + .connector_type = DRM_MODE_CONNECTOR_DSI, +}; + static const struct display_timing rocktech_rk070er9427_timing = { .pixelclock = { 26400000, 33300000, 46800000 }, .hactive = { 800, 800, 800 }, @@ -4797,6 +4861,9 @@ static const struct of_device_id platform_of_match[] = { }, { .compatible = "friendlyarm,hd702e", .data = &friendlyarm_hd702e, + }, { + .compatible = "geekworm,mzp280", + .data = &geekworm_mzp280, }, { .compatible = "giantplus,gpg482739qs5", .data = &giantplus_gpg482739qs5 @@ -4818,6 +4885,9 @@ static const struct of_device_id platform_of_match[] = { }, { .compatible = "innolux,at043tn24", .data = &innolux_at043tn24, + }, { + .compatible = "innolux,at056tn53v1", + .data = &innolux_at056tn53v1, }, { .compatible = "innolux,at070tn92", .data = &innolux_at070tn92, @@ -4977,6 +5047,9 @@ static const struct of_device_id platform_of_match[] = { }, { .compatible = "rocktech,rk043fn48h", .data = &rocktech_rk043fn48h, + }, { + .compatible = "raspberrypi,7inch-dsi", + .data = &raspberrypi_7inch, }, { .compatible = "rocktech,rk070er9427", .data = &rocktech_rk070er9427, @@ -5334,6 +5407,9 @@ static const struct panel_desc_dsi osd101t2045_53ts = { .lanes = 4, }; +// for panels using generic panel-dsi binding +static struct panel_desc_dsi panel_dsi; + static const struct of_device_id dsi_of_match[] = { { .compatible = "auo,b080uan01", @@ -5356,21 +5432,138 @@ static const struct of_device_id dsi_of_match[] = { }, { .compatible = "osddisplays,osd101t2045-53ts", .data = &osd101t2045_53ts + }, { + /* Must be the last entry */ + .compatible = "panel-dsi", + .data = &panel_dsi, }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, dsi_of_match); + +/* Checks for DSI panel definition in device-tree, analog to panel_dpi */ +static int panel_dsi_dt_probe(struct device *dev, + struct panel_desc_dsi *desc_dsi) +{ + struct panel_desc *desc; + struct display_timing *timing; + const struct device_node *np; + const char *dsi_color_format; + const char *dsi_mode_flags; + struct property *prop; + int dsi_lanes, ret; + + np = dev->of_node; + + desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL); + if (!desc) + return -ENOMEM; + + timing = devm_kzalloc(dev, sizeof(*timing), GFP_KERNEL); + if (!timing) + return -ENOMEM; + + ret = of_get_display_timing(np, "panel-timing", timing); + if (ret < 0) { + dev_err(dev, "%pOF: no panel-timing node found for \"panel-dsi\" binding\n", + np); + return ret; + } + + desc->timings = timing; + desc->num_timings = 1; + + of_property_read_u32(np, "width-mm", &desc->size.width); + of_property_read_u32(np, "height-mm", &desc->size.height); + + dsi_lanes = drm_of_get_data_lanes_count_ep(np, 0, 0, 1, 4); + + if (dsi_lanes < 0) { + dev_err(dev, "%pOF: no or too many data-lanes defined", np); + return dsi_lanes; + } + + desc_dsi->lanes = dsi_lanes; + + of_property_read_string(np, "dsi-color-format", &dsi_color_format); + if (!strcmp(dsi_color_format, "RGB888")) { + desc_dsi->format = MIPI_DSI_FMT_RGB888; + desc->bpc = 8; + } else if (!strcmp(dsi_color_format, "RGB565")) { + desc_dsi->format = MIPI_DSI_FMT_RGB565; + desc->bpc = 6; + } else if (!strcmp(dsi_color_format, "RGB666")) { + desc_dsi->format = MIPI_DSI_FMT_RGB666; + desc->bpc = 6; + } else if (!strcmp(dsi_color_format, "RGB666_PACKED")) { + desc_dsi->format = MIPI_DSI_FMT_RGB666_PACKED; + desc->bpc = 6; + } else { + dev_err(dev, "%pOF: no valid dsi-color-format defined", np); + return -EINVAL; + } + + + of_property_for_each_string(np, "mode", prop, dsi_mode_flags) { + if (!strcmp(dsi_mode_flags, "MODE_VIDEO")) + desc_dsi->flags |= MIPI_DSI_MODE_VIDEO; + else if (!strcmp(dsi_mode_flags, "MODE_VIDEO_BURST")) + desc_dsi->flags |= MIPI_DSI_MODE_VIDEO_BURST; + else if (!strcmp(dsi_mode_flags, "MODE_VIDEO_SYNC_PULSE")) + desc_dsi->flags |= MIPI_DSI_MODE_VIDEO_SYNC_PULSE; + else if (!strcmp(dsi_mode_flags, "MODE_VIDEO_AUTO_VERT")) + desc_dsi->flags |= MIPI_DSI_MODE_VIDEO_AUTO_VERT; + else if (!strcmp(dsi_mode_flags, "MODE_VIDEO_HSE")) + desc_dsi->flags |= MIPI_DSI_MODE_VIDEO_HSE; + else if (!strcmp(dsi_mode_flags, "MODE_VIDEO_NO_HFP")) + desc_dsi->flags |= MIPI_DSI_MODE_VIDEO_NO_HFP; + else if (!strcmp(dsi_mode_flags, "MODE_VIDEO_NO_HBP")) + desc_dsi->flags |= MIPI_DSI_MODE_VIDEO_NO_HBP; + else if (!strcmp(dsi_mode_flags, "MODE_VIDEO_NO_HSA")) + desc_dsi->flags |= MIPI_DSI_MODE_VIDEO_NO_HSA; + else if (!strcmp(dsi_mode_flags, "MODE_VSYNC_FLUSH")) + desc_dsi->flags |= MIPI_DSI_MODE_VSYNC_FLUSH; + else if (!strcmp(dsi_mode_flags, "MODE_NO_EOT_PACKET")) + desc_dsi->flags |= MIPI_DSI_MODE_NO_EOT_PACKET; + else if (!strcmp(dsi_mode_flags, "CLOCK_NON_CONTINUOUS")) + desc_dsi->flags |= MIPI_DSI_CLOCK_NON_CONTINUOUS; + else if (!strcmp(dsi_mode_flags, "MODE_LPM")) + desc_dsi->flags |= MIPI_DSI_MODE_LPM; + else if (!strcmp(dsi_mode_flags, "HS_PKT_END_ALIGNED")) + desc_dsi->flags |= MIPI_DSI_HS_PKT_END_ALIGNED; + } + + desc->connector_type = DRM_MODE_CONNECTOR_DSI; + desc_dsi->desc = *desc; + + return 0; +} + static int panel_simple_dsi_probe(struct mipi_dsi_device *dsi) { const struct panel_desc_dsi *desc; + struct panel_desc_dsi *dt_desc; int err; desc = of_device_get_match_data(&dsi->dev); if (!desc) return -ENODEV; + if (desc == &panel_dsi) { + /* Handle the generic panel-dsi binding */ + dt_desc = devm_kzalloc(&dsi->dev, sizeof(*dt_desc), GFP_KERNEL); + if (!dt_desc) + return -ENOMEM; + + err = panel_dsi_dt_probe(&dsi->dev, dt_desc); + if (err < 0) + return err; + + desc = dt_desc; + } + err = panel_simple_probe(&dsi->dev, &desc->desc); if (err < 0) return err; diff --git a/drivers/gpu/drm/panel/panel-tdo-y17p.c b/drivers/gpu/drm/panel/panel-tdo-y17p.c new file mode 100644 index 00000000000000..ca6818dc465daf --- /dev/null +++ b/drivers/gpu/drm/panel/panel-tdo-y17p.c @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * TDO Y17P TFT LCD drm_panel driver. + * + * SPI configured DPI display controller + * Copyright (C) 2022 Raspberry Pi Ltd + * + * Derived from drivers/drm/gpu/panel/panel-sitronix-st7789v.c + * Copyright (C) 2017 Free Electrons + */ + +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +#include <linux/bitops.h> +#include <linux/gpio/consumer.h> +#include <linux/media-bus-format.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/spi/spi.h> + +#include <video/mipi_display.h> +#include <video/of_videomode.h> +#include <video/videomode.h> + +struct tdo_y17p { + struct drm_panel panel; + struct spi_device *spi; + struct gpio_desc *reset; + struct regulator *power; + u32 bus_format; +}; + +static const u16 panel_init[] = { + 0x0ff, 0x1ff, 0x198, 0x106, 0x104, 0x101, 0x008, 0x110, + 0x021, 0x109, 0x030, 0x102, 0x031, 0x100, 0x040, 0x110, + 0x041, 0x155, 0x042, 0x102, 0x043, 0x109, 0x044, 0x107, + 0x050, 0x178, 0x051, 0x178, 0x052, 0x100, 0x053, 0x16d, + 0x060, 0x107, 0x061, 0x100, 0x062, 0x108, 0x063, 0x100, + 0x0a0, 0x100, 0x0a1, 0x107, 0x0a2, 0x10c, 0x0a3, 0x10b, + 0x0a4, 0x103, 0x0a5, 0x107, 0x0a6, 0x106, 0x0a7, 0x104, + 0x0a8, 0x108, 0x0a9, 0x10c, 0x0aa, 0x113, 0x0ab, 0x106, + 0x0ac, 0x10d, 0x0ad, 0x119, 0x0ae, 0x110, 0x0af, 0x100, + 0x0c0, 0x100, 0x0c1, 0x107, 0x0c2, 0x10c, 0x0c3, 0x10b, + 0x0c4, 0x103, 0x0c5, 0x107, 0x0c6, 0x107, 0x0c7, 0x104, + 0x0c8, 0x108, 0x0c9, 0x10c, 0x0ca, 0x113, 0x0cb, 0x106, + 0x0cc, 0x10d, 0x0cd, 0x118, 0x0ce, 0x110, 0x0cf, 0x100, + 0x0ff, 0x1ff, 0x198, 0x106, 0x104, 0x106, 0x000, 0x120, + 0x001, 0x10a, 0x002, 0x100, 0x003, 0x100, 0x004, 0x101, + 0x005, 0x101, 0x006, 0x198, 0x007, 0x106, 0x008, 0x101, + 0x009, 0x180, 0x00a, 0x100, 0x00b, 0x100, 0x00c, 0x101, + 0x00d, 0x101, 0x00e, 0x100, 0x00f, 0x100, 0x010, 0x1f0, + 0x011, 0x1f4, 0x012, 0x101, 0x013, 0x100, 0x014, 0x100, + 0x015, 0x1c0, 0x016, 0x108, 0x017, 0x100, 0x018, 0x100, + 0x019, 0x100, 0x01a, 0x100, 0x01b, 0x100, 0x01c, 0x100, + 0x01d, 0x100, 0x020, 0x101, 0x021, 0x123, 0x022, 0x145, + 0x023, 0x167, 0x024, 0x101, 0x025, 0x123, 0x026, 0x145, + 0x027, 0x167, 0x030, 0x111, 0x031, 0x111, 0x032, 0x100, + 0x033, 0x1ee, 0x034, 0x1ff, 0x035, 0x1bb, 0x036, 0x1aa, + 0x037, 0x1dd, 0x038, 0x1cc, 0x039, 0x166, 0x03a, 0x177, + 0x03b, 0x122, 0x03c, 0x122, 0x03d, 0x122, 0x03e, 0x122, + 0x03f, 0x122, 0x040, 0x122, 0x052, 0x110, 0x053, 0x110, + 0x0ff, 0x1ff, 0x198, 0x106, 0x104, 0x107, 0x018, 0x11d, + 0x017, 0x122, 0x002, 0x177, 0x026, 0x1b2, 0x0e1, 0x179, + 0x0ff, 0x1ff, 0x198, 0x106, 0x104, 0x100, 0x03a, 0x160, + 0x035, 0x100, 0x011, 0x100, +}; + +#define NUM_INIT_REGS ARRAY_SIZE(panel_init) + +static inline struct tdo_y17p *panel_to_tdo_y17p(struct drm_panel *panel) +{ + return container_of(panel, struct tdo_y17p, panel); +} + +static int tdo_y17p_write_msg(struct tdo_y17p *ctx, const u16 *msg, unsigned int len) +{ + struct spi_transfer xfer = { }; + struct spi_message spi; + + spi_message_init(&spi); + + xfer.tx_buf = msg; + xfer.bits_per_word = 9; + xfer.len = sizeof(u16) * len; + + spi_message_add_tail(&xfer, &spi); + return spi_sync(ctx->spi, &spi); +} + +static const struct drm_display_mode tdo_y17pe_720x720_mode = { + .clock = 36720, + .hdisplay = 720, + .hsync_start = 720 + 20, + .hsync_end = 720 + 20 + 20, + .htotal = 720 + 20 + 20 + 40, + .vdisplay = 720, + .vsync_start = 720 + 15, + .vsync_end = 720 + 15 + 15, + .vtotal = 720 + 15 + 15 + 15, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC, +}; + +static int tdo_y17p_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct tdo_y17p *ctx = panel_to_tdo_y17p(panel); + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, &tdo_y17pe_720x720_mode); + if (!mode) { + dev_err(panel->dev, "failed to add mode %ux%ux@%u\n", + tdo_y17pe_720x720_mode.hdisplay, + tdo_y17pe_720x720_mode.vdisplay, + drm_mode_vrefresh(&tdo_y17pe_720x720_mode)); + return -ENOMEM; + } + + drm_mode_set_name(mode); + + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + drm_mode_probed_add(connector, mode); + + connector->display_info.width_mm = 72; + connector->display_info.height_mm = 72; + drm_display_info_set_bus_formats(&connector->display_info, + &ctx->bus_format, 1); + connector->display_info.bus_flags = 0; + + return 1; +} + +static int tdo_y17p_prepare(struct drm_panel *panel) +{ + struct tdo_y17p *ctx = panel_to_tdo_y17p(panel); + int ret; + + ret = regulator_enable(ctx->power); + if (ret) + return ret; + + ret = tdo_y17p_write_msg(ctx, panel_init, NUM_INIT_REGS); + + msleep(120); + + return ret; +} + +static int tdo_y17p_enable(struct drm_panel *panel) +{ + struct tdo_y17p *ctx = panel_to_tdo_y17p(panel); + const u16 msg[] = { MIPI_DCS_SET_DISPLAY_ON }; + int ret; + + ret = tdo_y17p_write_msg(ctx, msg, ARRAY_SIZE(msg)); + + return ret; +} + +static int tdo_y17p_disable(struct drm_panel *panel) +{ + struct tdo_y17p *ctx = panel_to_tdo_y17p(panel); + const u16 msg[] = { MIPI_DCS_SET_DISPLAY_OFF }; + int ret; + + ret = tdo_y17p_write_msg(ctx, msg, ARRAY_SIZE(msg)); + + return ret; +} + +static int tdo_y17p_unprepare(struct drm_panel *panel) +{ + struct tdo_y17p *ctx = panel_to_tdo_y17p(panel); + const u16 msg[] = { MIPI_DCS_ENTER_SLEEP_MODE }; + int ret; + + ret = tdo_y17p_write_msg(ctx, msg, ARRAY_SIZE(msg)); + + return ret; +} + +static const struct drm_panel_funcs tdo_y17p_drm_funcs = { + .disable = tdo_y17p_disable, + .enable = tdo_y17p_enable, + .get_modes = tdo_y17p_get_modes, + .prepare = tdo_y17p_prepare, + .unprepare = tdo_y17p_unprepare, +}; + +static const struct of_device_id tdo_y17p_of_match[] = { + { .compatible = "tdo,tl040hds20ct", + .data = (void *)MEDIA_BUS_FMT_BGR888_1X24, + }, { + .compatible = "pimoroni,hyperpixel4square", + .data = (void *)MEDIA_BUS_FMT_BGR666_1X24_CPADHI, + }, { + .compatible = "tdo,y17p", + .data = (void *)MEDIA_BUS_FMT_BGR888_1X24, + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(of, tdo_y17p_of_match); + +static int tdo_y17p_probe(struct spi_device *spi) +{ + const struct of_device_id *id; + struct tdo_y17p *ctx; + int ret; + + ctx = devm_kzalloc(&spi->dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + id = of_match_node(tdo_y17p_of_match, spi->dev.of_node); + if (!id) + return -ENODEV; + + ctx->bus_format = (u32)(uintptr_t)id->data; + + spi_set_drvdata(spi, ctx); + ctx->spi = spi; + + drm_panel_init(&ctx->panel, &spi->dev, &tdo_y17p_drm_funcs, + DRM_MODE_CONNECTOR_DPI); + + ctx->power = devm_regulator_get(&spi->dev, "power"); + if (IS_ERR(ctx->power)) + return PTR_ERR(ctx->power); + + ctx->reset = devm_gpiod_get_optional(&spi->dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ctx->reset)) { + dev_err(&spi->dev, "Couldn't get our reset line\n"); + return PTR_ERR(ctx->reset); + } + + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) + return ret; + + drm_panel_add(&ctx->panel); + + return 0; +} + +static void tdo_y17p_remove(struct spi_device *spi) +{ + struct tdo_y17p *ctx = spi_get_drvdata(spi); + + drm_panel_remove(&ctx->panel); +} + +static const struct spi_device_id tdo_y17p_ids[] = { + { "tl040hds20ct", 0 }, + { "hyperpixel4square", 0 }, + { "y17p", 0 }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(spi, tdo_y17p_ids); + +static struct spi_driver tdo_y17p_driver = { + .probe = tdo_y17p_probe, + .remove = tdo_y17p_remove, + .driver = { + .name = "tdo_y17p", + .of_match_table = tdo_y17p_of_match, + }, + .id_table = tdo_y17p_ids, +}; +module_spi_driver(tdo_y17p_driver); + +MODULE_AUTHOR("Dave Stevenson <dave.stevenson@raspberrypi.com>"); +MODULE_DESCRIPTION("TDO Y17P LCD panel driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/panel/panel-waveshare-dsi.c b/drivers/gpu/drm/panel/panel-waveshare-dsi.c new file mode 100644 index 00000000000000..4041d31c71a270 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-waveshare-dsi.c @@ -0,0 +1,609 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright © 2023 Raspberry Pi Ltd + * + * Based on panel-raspberrypi-touchscreen by Broadcom + */ + +#include <linux/backlight.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/fb.h> +#include <linux/i2c.h> +#include <linux/media-bus-format.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_graph.h> +#include <linux/pm.h> + +#include <drm/drm_crtc.h> +#include <drm/drm_device.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_panel.h> + +#define WS_DSI_DRIVER_NAME "ws-ts-dsi" + +struct ws_panel { + struct drm_panel base; + struct mipi_dsi_device *dsi; + struct i2c_client *i2c; + const struct drm_display_mode *mode; + enum drm_panel_orientation orientation; +}; + +struct ws_panel_data { + const struct drm_display_mode *mode; + int lanes; + unsigned long mode_flags; +}; + +/* 2.8inch 480x640 + * https://www.waveshare.com/product/raspberry-pi/displays/2.8inch-dsi-lcd.htm + */ +static const struct drm_display_mode ws_panel_2_8_mode = { + .clock = 50000, + .hdisplay = 480, + .hsync_start = 480 + 150, + .hsync_end = 480 + 150 + 50, + .htotal = 480 + 150 + 50 + 150, + .vdisplay = 640, + .vsync_start = 640 + 150, + .vsync_end = 640 + 150 + 50, + .vtotal = 640 + 150 + 50 + 150, +}; + +static const struct ws_panel_data ws_panel_2_8_data = { + .mode = &ws_panel_2_8_mode, + .lanes = 2, + .mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO | MIPI_DSI_CLOCK_NON_CONTINUOUS, +}; + +/* 3.4inch 800x800 Round + * https://www.waveshare.com/product/displays/lcd-oled/3.4inch-dsi-lcd-c.htm + */ +static const struct drm_display_mode ws_panel_3_4_mode = { + .clock = 50000, + .hdisplay = 800, + .hsync_start = 800 + 32, + .hsync_end = 800 + 32 + 6, + .htotal = 800 + 32 + 6 + 120, + .vdisplay = 800, + .vsync_start = 800 + 8, + .vsync_end = 800 + 8 + 4, + .vtotal = 800 + 8 + 4 + 16, +}; + +static const struct ws_panel_data ws_panel_3_4_data = { + .mode = &ws_panel_3_4_mode, + .lanes = 2, + .mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO | MIPI_DSI_CLOCK_NON_CONTINUOUS, +}; + +/* 4.0inch 480x800 + * https://www.waveshare.com/product/raspberry-pi/displays/4inch-dsi-lcd.htm + */ +static const struct drm_display_mode ws_panel_4_0_mode = { + .clock = 50000, + .hdisplay = 480, + .hsync_start = 480 + 150, + .hsync_end = 480 + 150 + 100, + .htotal = 480 + 150 + 100 + 150, + .vdisplay = 800, + .vsync_start = 800 + 20, + .vsync_end = 800 + 20 + 100, + .vtotal = 800 + 20 + 100 + 20, +}; + +static const struct ws_panel_data ws_panel_4_0_data = { + .mode = &ws_panel_4_0_mode, + .lanes = 2, + .mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO | MIPI_DSI_CLOCK_NON_CONTINUOUS, +}; + +/* 7.0inch C 1024x600 + * https://www.waveshare.com/product/raspberry-pi/displays/lcd-oled/7inch-dsi-lcd-c-with-case-a.htm + */ +static const struct drm_display_mode ws_panel_7_0_c_mode = { + .clock = 50000, + .hdisplay = 1024, + .hsync_start = 1024 + 100, + .hsync_end = 1024 + 100 + 100, + .htotal = 1024 + 100 + 100 + 100, + .vdisplay = 600, + .vsync_start = 600 + 10, + .vsync_end = 600 + 10 + 10, + .vtotal = 600 + 10 + 10 + 10, +}; + +static const struct ws_panel_data ws_panel_7_0_c_data = { + .mode = &ws_panel_7_0_c_mode, + .lanes = 2, + .mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO | MIPI_DSI_CLOCK_NON_CONTINUOUS, +}; + +/* 7.9inch 400x1280 + * https://www.waveshare.com/product/raspberry-pi/displays/7.9inch-dsi-lcd.htm + */ +static const struct drm_display_mode ws_panel_7_9_mode = { + .clock = 50000, + .hdisplay = 400, + .hsync_start = 400 + 40, + .hsync_end = 400 + 40 + 30, + .htotal = 400 + 40 + 30 + 40, + .vdisplay = 1280, + .vsync_start = 1280 + 20, + .vsync_end = 1280 + 20 + 10, + .vtotal = 1280 + 20 + 10 + 20, +}; + +static const struct ws_panel_data ws_panel_7_9_data = { + .mode = &ws_panel_7_9_mode, + .lanes = 2, + .mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO | MIPI_DSI_CLOCK_NON_CONTINUOUS, +}; + +/* 8.0inch or 10.1inch 1280x800 + * https://www.waveshare.com/product/raspberry-pi/displays/8inch-dsi-lcd-c.htm + * https://www.waveshare.com/product/raspberry-pi/displays/10.1inch-dsi-lcd-c.htm + */ +static const struct drm_display_mode ws_panel_10_1_mode = { + .clock = 83333, + .hdisplay = 1280, + .hsync_start = 1280 + 156, + .hsync_end = 1280 + 156 + 20, + .htotal = 1280 + 156 + 20 + 40, + .vdisplay = 800, + .vsync_start = 800 + 40, + .vsync_end = 800 + 40 + 48, + .vtotal = 800 + 40 + 48 + 40, +}; + +static const struct ws_panel_data ws_panel_10_1_data = { + .mode = &ws_panel_10_1_mode, + .lanes = 2, + .mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO | MIPI_DSI_CLOCK_NON_CONTINUOUS, +}; + +/* 11.9inch 320x1480 + * https://www.waveshare.com/product/raspberry-pi/displays/11.9inch-dsi-lcd.htm + */ +static const struct drm_display_mode ws_panel_11_9_mode = { + .clock = 50000, + .hdisplay = 320, + .hsync_start = 320 + 60, + .hsync_end = 320 + 60 + 60, + .htotal = 320 + 60 + 60 + 60, + .vdisplay = 1480, + .vsync_start = 1480 + 60, + .vsync_end = 1480 + 60 + 60, + .vtotal = 1480 + 60 + 60 + 60, +}; + +static const struct ws_panel_data ws_panel_11_9_data = { + .mode = &ws_panel_11_9_mode, + .lanes = 2, + .mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO | MIPI_DSI_CLOCK_NON_CONTINUOUS, +}; + +static const struct drm_display_mode ws_panel_4_mode = { + .clock = 50000, + .hdisplay = 720, + .hsync_start = 720 + 32, + .hsync_end = 720 + 32 + 200, + .htotal = 720 + 32 + 200 + 120, + .vdisplay = 720, + .vsync_start = 720 + 8, + .vsync_end = 720 + 8 + 4, + .vtotal = 720 + 8 + 4 + 16, +}; + +static const struct ws_panel_data ws_panel_4_data = { + .mode = &ws_panel_4_mode, + .lanes = 2, + .mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO | MIPI_DSI_CLOCK_NON_CONTINUOUS, +}; + +/* 5.0inch 720x1280 + * https://www.waveshare.com/5inch-dsi-lcd-d.htm + */ +static const struct drm_display_mode ws_panel_5_0_mode = { + .clock = 83333, + .hdisplay = 720, + .hsync_start = 720 + 100, + .hsync_end = 720 + 100 + 80, + .htotal = 720 + 100 + 80 + 100, + .vdisplay = 1280, + .vsync_start = 1280 + 20, + .vsync_end = 1280 + 20 + 20, + .vtotal = 1280 + 20 + 20 + 20, +}; + +static const struct ws_panel_data ws_panel_5_0_data = { + .mode = &ws_panel_5_0_mode, + .lanes = 2, + .mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO | MIPI_DSI_CLOCK_NON_CONTINUOUS, +}; + +/* 6.25inch 720x1560 + * https://www.waveshare.com/6.25inch-dsi-lcd.htm + */ +static const struct drm_display_mode ws_panel_6_25_mode = { + .clock = 83333, + .hdisplay = 720, + .hsync_start = 720 + 50, + .hsync_end = 720 + 50 + 50, + .htotal = 720 + 50 + 50 + 50, + .vdisplay = 1560, + .vsync_start = 1560 + 20, + .vsync_end = 1560 + 20 + 20, + .vtotal = 1560 + 20 + 20 + 20, +}; + +static const struct ws_panel_data ws_panel_6_25_data = { + .mode = &ws_panel_6_25_mode, + .lanes = 2, + .mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO | MIPI_DSI_CLOCK_NON_CONTINUOUS, +}; + +/* 8.8inch 480x1920 + * https://www.waveshare.com/8.8inch-dsi-lcd.htm + */ +static const struct drm_display_mode ws_panel_8_8_mode = { + .clock = 83333, + .hdisplay = 480, + .hsync_start = 480 + 50, + .hsync_end = 480 + 50 + 50, + .htotal = 480 + 50 + 50 + 50, + .vdisplay = 1920, + .vsync_start = 1920 + 20, + .vsync_end = 1920 + 20 + 20, + .vtotal = 1920 + 20 + 20 + 20, +}; + +static const struct ws_panel_data ws_panel_8_8_data = { + .mode = &ws_panel_8_8_mode, + .lanes = 2, + .mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO | MIPI_DSI_CLOCK_NON_CONTINUOUS, +}; + +static const struct drm_display_mode ws_panel_13_3_4lane_mode = { + .clock = 148500, + .hdisplay = 1920, + .hsync_start = 1920 + 88, + .hsync_end = 1920 + 88 + 44, + .htotal = 1920 + 88 + 44 + 148, + .vdisplay = 1080, + .vsync_start = 1080 + 4, + .vsync_end = 1080 + 4 + 5, + .vtotal = 1080 + 4 + 5 + 36, +}; + +static const struct ws_panel_data ws_panel_13_3_4lane_data = { + .mode = &ws_panel_13_3_4lane_mode, + .lanes = 4, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_LPM, +}; + +static const struct drm_display_mode ws_panel_13_3_2lane_mode = { + .clock = 83333, + .hdisplay = 1920, + .hsync_start = 1920 + 88, + .hsync_end = 1920 + 88 + 44, + .htotal = 1920 + 88 + 44 + 148, + .vdisplay = 1080, + .vsync_start = 1080 + 4, + .vsync_end = 1080 + 4 + 5, + .vtotal = 1080 + 4 + 5 + 36, +}; + +static const struct ws_panel_data ws_panel_13_3_2lane_data = { + .mode = &ws_panel_13_3_2lane_mode, + .lanes = 2, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_LPM, +}; + +static struct ws_panel *panel_to_ts(struct drm_panel *panel) +{ + return container_of(panel, struct ws_panel, base); +} + +static void ws_panel_i2c_write(struct ws_panel *ts, u8 reg, u8 val) +{ + int ret; + + ret = i2c_smbus_write_byte_data(ts->i2c, reg, val); + if (ret) + dev_err(&ts->i2c->dev, "I2C write failed: %d\n", ret); +} + +static int ws_panel_disable(struct drm_panel *panel) +{ + struct ws_panel *ts = panel_to_ts(panel); + + ws_panel_i2c_write(ts, 0xad, 0x00); + + return 0; +} + +static int ws_panel_unprepare(struct drm_panel *panel) +{ + return 0; +} + +static int ws_panel_prepare(struct drm_panel *panel) +{ + return 0; +} + +static int ws_panel_enable(struct drm_panel *panel) +{ + struct ws_panel *ts = panel_to_ts(panel); + + if (ts->mode == &ws_panel_13_3_2lane_mode) + ws_panel_i2c_write(ts, 0xad, 0x02); + else + ws_panel_i2c_write(ts, 0xad, 0x01); + + return 0; +} + +static int ws_panel_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + static const u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24; + struct ws_panel *ts = panel_to_ts(panel); + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, ts->mode); + if (!mode) { + dev_err(panel->dev, "failed to add mode %ux%u@%u\n", + ts->mode->hdisplay, + ts->mode->vdisplay, + drm_mode_vrefresh(ts->mode)); + } + + mode->type |= DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + + drm_mode_set_name(mode); + + drm_mode_probed_add(connector, mode); + + connector->display_info.bpc = 8; + connector->display_info.width_mm = 154; + connector->display_info.height_mm = 86; + drm_display_info_set_bus_formats(&connector->display_info, + &bus_format, 1); + + /* + * TODO: Remove once all drm drivers call + * drm_connector_set_orientation_from_panel() + */ + drm_connector_set_panel_orientation(connector, ts->orientation); + + return 1; +} + +static enum drm_panel_orientation ws_panel_get_orientation(struct drm_panel *panel) +{ + struct ws_panel *ts = panel_to_ts(panel); + + return ts->orientation; +} + +static const struct drm_panel_funcs ws_panel_funcs = { + .disable = ws_panel_disable, + .unprepare = ws_panel_unprepare, + .prepare = ws_panel_prepare, + .enable = ws_panel_enable, + .get_modes = ws_panel_get_modes, + .get_orientation = ws_panel_get_orientation, +}; + +static int ws_panel_bl_update_status(struct backlight_device *bl) +{ + struct ws_panel *ts = bl_get_data(bl); + + ws_panel_i2c_write(ts, 0xab, 0xff - backlight_get_brightness(bl)); + ws_panel_i2c_write(ts, 0xaa, 0x01); + + return 0; +} + +static const struct backlight_ops ws_panel_bl_ops = { + .update_status = ws_panel_bl_update_status, +}; + +static struct backlight_device * +ws_panel_create_backlight(struct ws_panel *ts) +{ + struct device *dev = ts->base.dev; + const struct backlight_properties props = { + .type = BACKLIGHT_RAW, + .brightness = 255, + .max_brightness = 255, + }; + + return devm_backlight_device_register(dev, dev_name(dev), dev, ts, + &ws_panel_bl_ops, &props); +} + +static int ws_panel_probe(struct i2c_client *i2c) +{ + struct device *dev = &i2c->dev; + struct ws_panel *ts; + struct device_node *endpoint, *dsi_host_node; + struct mipi_dsi_host *host; + struct mipi_dsi_device_info info = { + .type = WS_DSI_DRIVER_NAME, + .channel = 0, + .node = NULL, + }; + const struct ws_panel_data *_ws_panel_data; + int ret; + + ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL); + if (!ts) + return -ENOMEM; + + _ws_panel_data = of_device_get_match_data(dev); + if (!_ws_panel_data) + return -EINVAL; + + ts->mode = _ws_panel_data->mode; + if (!ts->mode) + return -EINVAL; + + i2c_set_clientdata(i2c, ts); + + ts->i2c = i2c; + + ws_panel_i2c_write(ts, 0xc0, 0x01); + ws_panel_i2c_write(ts, 0xc2, 0x01); + ws_panel_i2c_write(ts, 0xac, 0x01); + + ret = of_drm_get_panel_orientation(dev->of_node, &ts->orientation); + if (ret) { + dev_err(dev, "%pOF: failed to get orientation %d\n", dev->of_node, ret); + return ret; + } + + /* Look up the DSI host. It needs to probe before we do. */ + endpoint = of_graph_get_next_endpoint(dev->of_node, NULL); + if (!endpoint) + return -ENODEV; + + dsi_host_node = of_graph_get_remote_port_parent(endpoint); + if (!dsi_host_node) + goto error; + + host = of_find_mipi_dsi_host_by_node(dsi_host_node); + of_node_put(dsi_host_node); + if (!host) { + of_node_put(endpoint); + return -EPROBE_DEFER; + } + + info.node = of_graph_get_remote_port(endpoint); + if (!info.node) + goto error; + + of_node_put(endpoint); + + ts->dsi = devm_mipi_dsi_device_register_full(dev, host, &info); + if (IS_ERR(ts->dsi)) { + dev_err(dev, "DSI device registration failed: %ld\n", + PTR_ERR(ts->dsi)); + return PTR_ERR(ts->dsi); + } + + drm_panel_init(&ts->base, dev, &ws_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + + ts->base.backlight = ws_panel_create_backlight(ts); + if (IS_ERR(ts->base.backlight)) { + ret = PTR_ERR(ts->base.backlight); + dev_err(dev, "Failed to create backlight: %d\n", ret); + return ret; + } + + /* This appears last, as it's what will unblock the DSI host + * driver's component bind function. + */ + drm_panel_add(&ts->base); + + ts->dsi->mode_flags = _ws_panel_data->mode_flags; + ts->dsi->format = MIPI_DSI_FMT_RGB888; + ts->dsi->lanes = _ws_panel_data->lanes; + + ret = devm_mipi_dsi_attach(dev, ts->dsi); + + if (ret) + dev_err(dev, "failed to attach dsi to host: %d\n", ret); + + return 0; + +error: + of_node_put(endpoint); + return -ENODEV; +} + +static void ws_panel_remove(struct i2c_client *i2c) +{ + struct ws_panel *ts = i2c_get_clientdata(i2c); + + ws_panel_disable(&ts->base); + + drm_panel_remove(&ts->base); +} + +static void ws_panel_shutdown(struct i2c_client *i2c) +{ + struct ws_panel *ts = i2c_get_clientdata(i2c); + + ws_panel_disable(&ts->base); +} + +static const struct of_device_id ws_panel_of_ids[] = { + { + .compatible = "waveshare,2.8inch-panel", + .data = &ws_panel_2_8_data, + }, { + .compatible = "waveshare,3.4inch-panel", + .data = &ws_panel_3_4_data, + }, { + .compatible = "waveshare,4.0inch-panel", + .data = &ws_panel_4_0_data, + }, { + .compatible = "waveshare,7.0inch-c-panel", + .data = &ws_panel_7_0_c_data, + }, { + .compatible = "waveshare,7.9inch-panel", + .data = &ws_panel_7_9_data, + }, { + .compatible = "waveshare,8.0inch-panel", + .data = &ws_panel_10_1_data, + }, { + .compatible = "waveshare,10.1inch-panel", + .data = &ws_panel_10_1_data, + }, { + .compatible = "waveshare,11.9inch-panel", + .data = &ws_panel_11_9_data, + }, { + .compatible = "waveshare,4inch-panel", + .data = &ws_panel_4_data, + }, { + .compatible = "waveshare,5.0inch-panel", + .data = &ws_panel_5_0_data, + }, { + .compatible = "waveshare,6.25inch-panel", + .data = &ws_panel_6_25_data, + }, { + .compatible = "waveshare,8.8inch-panel", + .data = &ws_panel_8_8_data, + }, { + .compatible = "waveshare,13.3inch-4lane-panel", + .data = &ws_panel_13_3_4lane_data, + }, { + .compatible = "waveshare,13.3inch-2lane-panel", + .data = &ws_panel_13_3_2lane_data, + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(of, ws_panel_of_ids); + +static struct i2c_driver ws_panel_driver = { + .driver = { + .name = "ws_touchscreen", + .of_match_table = ws_panel_of_ids, + }, + .probe = ws_panel_probe, + .remove = ws_panel_remove, + .shutdown = ws_panel_shutdown, +}; +module_i2c_driver(ws_panel_driver); + +MODULE_AUTHOR("Dave Stevenson <dave.stevenson@raspberrypi.com>"); +MODULE_DESCRIPTION("Waveshare DSI panel driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/rp1/Kconfig b/drivers/gpu/drm/rp1/Kconfig new file mode 100644 index 00000000000000..d73c62e0ab64c7 --- /dev/null +++ b/drivers/gpu/drm/rp1/Kconfig @@ -0,0 +1,5 @@ +source "drivers/gpu/drm/rp1/rp1-dsi/Kconfig" + +source "drivers/gpu/drm/rp1/rp1-dpi/Kconfig" + +source "drivers/gpu/drm/rp1/rp1-vec/Kconfig" diff --git a/drivers/gpu/drm/rp1/Makefile b/drivers/gpu/drm/rp1/Makefile new file mode 100644 index 00000000000000..0f915b158e96f8 --- /dev/null +++ b/drivers/gpu/drm/rp1/Makefile @@ -0,0 +1,4 @@ +obj-$(CONFIG_DRM_RP1_DSI) += rp1-dsi/ +obj-$(CONFIG_DRM_RP1_DPI) += rp1-dpi/ +obj-$(CONFIG_DRM_RP1_VEC) += rp1-vec/ + diff --git a/drivers/gpu/drm/rp1/rp1-dpi/Kconfig b/drivers/gpu/drm/rp1/rp1-dpi/Kconfig new file mode 100644 index 00000000000000..95d17902094db6 --- /dev/null +++ b/drivers/gpu/drm/rp1/rp1-dpi/Kconfig @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DRM_RP1_DPI + tristate "DRM Support for RP1 DPI" + depends on DRM && MFD_RP1 + select DRM_GEM_DMA_HELPER + select DRM_KMS_HELPER + select DRM_VRAM_HELPER + select DRM_TTM + select DRM_TTM_HELPER + depends on RP1_PIO || !RP1_PIO + help + Choose this option to enable DPI output on Raspberry Pi RP1 + + There is an optional dependency on RP1_PIO, as the PIO block + must be used to fix up interlaced sync. Interlaced DPI modes + will be unavailable when RP1_PIO is not selected. diff --git a/drivers/gpu/drm/rp1/rp1-dpi/Makefile b/drivers/gpu/drm/rp1/rp1-dpi/Makefile new file mode 100644 index 00000000000000..30d499c2959eb3 --- /dev/null +++ b/drivers/gpu/drm/rp1/rp1-dpi/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only + +drm-rp1-dpi-y := rp1_dpi.o rp1_dpi_hw.o rp1_dpi_cfg.o rp1_dpi_pio.o + +obj-$(CONFIG_DRM_RP1_DPI) += drm-rp1-dpi.o diff --git a/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi.c b/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi.c new file mode 100644 index 00000000000000..26d667bc0fefeb --- /dev/null +++ b/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi.c @@ -0,0 +1,478 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * DRM Driver for DPI output on Raspberry Pi RP1 + * + * Copyright (c) 2023 Raspberry Pi Limited. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/list.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/printk.h> +#include <linux/console.h> +#include <linux/debugfs.h> +#include <linux/uaccess.h> +#include <linux/io.h> +#include <linux/dma-mapping.h> +#include <linux/cred.h> +#include <linux/media-bus-format.h> +#include <linux/pinctrl/consumer.h> +#include <drm/drm_drv.h> +#include <drm/drm_mm.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_gem_atomic_helper.h> +#include <drm/drm_gem_dma_helper.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_managed.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_encoder.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_fbdev_ttm.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_gem.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_simple_kms_helper.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_modeset_helper_vtables.h> +#include <drm/drm_vblank.h> +#include <drm/drm_of.h> + +#include "rp1_dpi.h" + +/* + * Default bus format, where not specified by a connector/bridge + * and not overridden by the OF property "default_bus_fmt". + * This value is for compatibility with vc4 and VGA666-style boards, + * even though RP1 hardware cannot achieve the full 18-bit depth + * with that pinout (MEDIA_BUS_FMT_RGB666_1X24_CPADHI is preferred). + */ +static unsigned int default_bus_fmt = MEDIA_BUS_FMT_RGB666_1X18; +module_param(default_bus_fmt, uint, 0644); + +/* -------------------------------------------------------------- */ + +static void rp1dpi_pipe_update(struct drm_simple_display_pipe *pipe, + struct drm_plane_state *old_state) +{ + struct drm_pending_vblank_event *event; + unsigned long flags; + struct drm_framebuffer *fb = pipe->plane.state->fb; + struct rp1_dpi *dpi = pipe->crtc.dev->dev_private; + struct drm_gem_object *gem = fb ? drm_gem_fb_get_obj(fb, 0) : NULL; + struct drm_gem_dma_object *dma_obj = gem ? to_drm_gem_dma_obj(gem) : NULL; + bool can_update = fb && dma_obj && dpi && dpi->pipe_enabled; + + /* (Re-)start DPI-DMA where required; and update FB address */ + if (can_update) { + if (!dpi->dpi_running || fb->format->format != dpi->cur_fmt) { + if (dpi->dpi_running && + fb->format->format != dpi->cur_fmt) { + rp1dpi_hw_stop(dpi); + rp1dpi_pio_stop(dpi); + dpi->dpi_running = false; + } + if (!dpi->dpi_running) { + rp1dpi_hw_setup(dpi, + fb->format->format, + dpi->bus_fmt, + dpi->de_inv, + &pipe->crtc.state->mode); + rp1dpi_pio_start(dpi, &pipe->crtc.state->mode); + dpi->dpi_running = true; + } + dpi->cur_fmt = fb->format->format; + drm_crtc_vblank_on(&pipe->crtc); + } + rp1dpi_hw_update(dpi, dma_obj->dma_addr, fb->offsets[0], fb->pitches[0]); + } + + /* Arm VBLANK event (or call it immediately in some error cases) */ + spin_lock_irqsave(&pipe->crtc.dev->event_lock, flags); + event = pipe->crtc.state->event; + if (event) { + pipe->crtc.state->event = NULL; + if (can_update && drm_crtc_vblank_get(&pipe->crtc) == 0) + drm_crtc_arm_vblank_event(&pipe->crtc, event); + else + drm_crtc_send_vblank_event(&pipe->crtc, event); + } + spin_unlock_irqrestore(&pipe->crtc.dev->event_lock, flags); +} + +static void rp1dpi_pipe_enable(struct drm_simple_display_pipe *pipe, + struct drm_crtc_state *crtc_state, + struct drm_plane_state *plane_state) +{ + static const unsigned int M = 1000000; + struct rp1_dpi *dpi = pipe->crtc.dev->dev_private; + struct drm_connector *conn; + struct drm_connector_list_iter conn_iter; + unsigned int fpix, fdiv, fvco; + int ret; + + /* Look up the connector attached to DPI so we can get the + * bus_format. Ideally the bridge would tell us the + * bus_format we want, but it doesn't yet, so assume that it's + * uniform throughout the bridge chain. + */ + dev_info(&dpi->pdev->dev, __func__); + drm_connector_list_iter_begin(pipe->encoder.dev, &conn_iter); + drm_for_each_connector_iter(conn, &conn_iter) { + if (conn->encoder == &pipe->encoder) { + dpi->de_inv = !!(conn->display_info.bus_flags & + DRM_BUS_FLAG_DE_LOW); + dpi->clk_inv = !!(conn->display_info.bus_flags & + DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE); + if (conn->display_info.num_bus_formats) + dpi->bus_fmt = conn->display_info.bus_formats[0]; + break; + } + } + drm_connector_list_iter_end(&conn_iter); + + /* Set DPI clock to desired frequency. Currently (experimentally) + * we take control of the VideoPLL, to ensure we can generate it + * accurately. NB: this prevents concurrent use of DPI and VEC! + * Magic numbers ensure the parent clock is within [100MHz, 200MHz] + * with VCO in [1GHz, 1.33GHz]. The initial divide is by 6, 8 or 10. + */ + fpix = 1000 * pipe->crtc.state->mode.clock; + fpix = clamp(fpix, 1 * M, 200 * M); + fdiv = fpix; + while (fdiv < 100 * M) + fdiv *= 2; + fvco = fdiv * 2 * DIV_ROUND_UP(500 * M, fdiv); + ret = clk_set_rate(dpi->clocks[RP1DPI_CLK_PLLCORE], fvco); + if (ret) + dev_err(&dpi->pdev->dev, "Failed to set PLL VCO to %u (%d)", fvco, ret); + ret = clk_set_rate(dpi->clocks[RP1DPI_CLK_PLLDIV], fdiv); + if (ret) + dev_err(&dpi->pdev->dev, "Failed to set PLL output to %u (%d)", fdiv, ret); + ret = clk_set_rate(dpi->clocks[RP1DPI_CLK_DPI], fpix); + if (ret) + dev_err(&dpi->pdev->dev, "Failed to set DPI clock to %u (%d)", fpix, ret); + + rp1dpi_vidout_setup(dpi, dpi->clk_inv); + clk_prepare_enable(dpi->clocks[RP1DPI_CLK_PLLCORE]); + clk_prepare_enable(dpi->clocks[RP1DPI_CLK_PLLDIV]); + pinctrl_pm_select_default_state(&dpi->pdev->dev); + clk_prepare_enable(dpi->clocks[RP1DPI_CLK_DPI]); + dev_info(&dpi->pdev->dev, "Want %u /%u %u /%u %u; got VCO=%lu DIV=%lu DPI=%lu", + fvco, fvco / fdiv, fdiv, fdiv / fpix, fpix, + clk_get_rate(dpi->clocks[RP1DPI_CLK_PLLCORE]), + clk_get_rate(dpi->clocks[RP1DPI_CLK_PLLDIV]), + clk_get_rate(dpi->clocks[RP1DPI_CLK_DPI])); + + /* Start DPI-DMA. pipe already has the new crtc and plane state. */ + dpi->pipe_enabled = true; + dpi->cur_fmt = 0xdeadbeef; + rp1dpi_pipe_update(pipe, 0); +} + +static void rp1dpi_pipe_disable(struct drm_simple_display_pipe *pipe) +{ + struct rp1_dpi *dpi = pipe->crtc.dev->dev_private; + + dev_info(&dpi->pdev->dev, __func__); + drm_crtc_vblank_off(&pipe->crtc); + if (dpi->dpi_running) { + rp1dpi_hw_stop(dpi); + rp1dpi_pio_stop(dpi); + dpi->dpi_running = false; + } + clk_disable_unprepare(dpi->clocks[RP1DPI_CLK_DPI]); + pinctrl_pm_select_sleep_state(&dpi->pdev->dev); + clk_disable_unprepare(dpi->clocks[RP1DPI_CLK_PLLDIV]); + clk_disable_unprepare(dpi->clocks[RP1DPI_CLK_PLLCORE]); + dpi->pipe_enabled = false; +} + +static int rp1dpi_pipe_enable_vblank(struct drm_simple_display_pipe *pipe) +{ + struct rp1_dpi *dpi = pipe->crtc.dev->dev_private; + + if (dpi) + rp1dpi_hw_vblank_ctrl(dpi, 1); + + return 0; +} + +static void rp1dpi_pipe_disable_vblank(struct drm_simple_display_pipe *pipe) +{ + struct rp1_dpi *dpi = pipe->crtc.dev->dev_private; + + if (dpi) + rp1dpi_hw_vblank_ctrl(dpi, 0); +} + +static enum drm_mode_status rp1dpi_pipe_mode_valid(struct drm_simple_display_pipe *pipe, + const struct drm_display_mode *mode) +{ +#if !IS_REACHABLE(CONFIG_RP1_PIO) + if (mode->flags & DRM_MODE_FLAG_INTERLACE) + return MODE_NO_INTERLACE; +#endif + if (mode->clock < 1000) /* 1 MHz */ + return MODE_CLOCK_LOW; + if (mode->clock > 200000) /* 200 MHz */ + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +static const struct drm_simple_display_pipe_funcs rp1dpi_pipe_funcs = { + .enable = rp1dpi_pipe_enable, + .update = rp1dpi_pipe_update, + .disable = rp1dpi_pipe_disable, + .enable_vblank = rp1dpi_pipe_enable_vblank, + .disable_vblank = rp1dpi_pipe_disable_vblank, + .mode_valid = rp1dpi_pipe_mode_valid, +}; + +static const struct drm_mode_config_funcs rp1dpi_mode_funcs = { + .fb_create = drm_gem_fb_create, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +static void rp1dpi_stopall(struct drm_device *drm) +{ + if (drm->dev_private) { + struct rp1_dpi *dpi = drm->dev_private; + + if (dpi->dpi_running || rp1dpi_hw_busy(dpi)) { + rp1dpi_hw_stop(dpi); + clk_disable_unprepare(dpi->clocks[RP1DPI_CLK_DPI]); + rp1dpi_pio_stop(dpi); + dpi->dpi_running = false; + } + rp1dpi_vidout_poweroff(dpi); + pinctrl_pm_select_sleep_state(&dpi->pdev->dev); + } +} + +DEFINE_DRM_GEM_DMA_FOPS(rp1dpi_fops); + +static struct drm_driver rp1dpi_driver = { + .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC, + .fops = &rp1dpi_fops, + .name = "drm-rp1-dpi", + .desc = "drm-rp1-dpi", + .date = "0", + .major = 1, + .minor = 0, + DRM_GEM_DMA_DRIVER_OPS, + .release = rp1dpi_stopall, +}; + +static const u32 rp1dpi_formats[] = { + DRM_FORMAT_XRGB8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_RGB888, + DRM_FORMAT_BGR888, + DRM_FORMAT_RGB565 +}; + +static int rp1dpi_platform_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rp1_dpi *dpi; + struct drm_bridge *bridge = NULL; + const char *rgb_order = NULL; + struct drm_panel *panel; + int i, j, ret; + + dev_info(dev, __func__); + ret = drm_of_find_panel_or_bridge(pdev->dev.of_node, 0, 0, + &panel, &bridge); + if (ret) { + dev_info(dev, "%s: bridge not found\n", __func__); + return -EPROBE_DEFER; + } + if (panel) { + bridge = devm_drm_panel_bridge_add(dev, panel); + if (IS_ERR(bridge)) + return PTR_ERR(bridge); + } + + dpi = devm_drm_dev_alloc(dev, &rp1dpi_driver, struct rp1_dpi, drm); + if (IS_ERR(dpi)) { + ret = PTR_ERR(dpi); + dev_err(dev, "%s devm_drm_dev_alloc %d", __func__, ret); + return ret; + } + dpi->pdev = pdev; + spin_lock_init(&dpi->hw_lock); + + dpi->bus_fmt = default_bus_fmt; + ret = of_property_read_u32(dev->of_node, "default_bus_fmt", &dpi->bus_fmt); + + for (i = 0; i < RP1DPI_NUM_HW_BLOCKS; i++) { + dpi->hw_base[i] = + devm_ioremap_resource(dev, + platform_get_resource(dpi->pdev, IORESOURCE_MEM, i)); + if (IS_ERR(dpi->hw_base[i])) { + dev_err(dev, "Error memory mapping regs[%d]\n", i); + return PTR_ERR(dpi->hw_base[i]); + } + } + ret = platform_get_irq(dpi->pdev, 0); + if (ret > 0) + ret = devm_request_irq(dev, ret, rp1dpi_hw_isr, + IRQF_SHARED, "rp1-dpi", dpi); + if (ret) { + dev_err(dev, "Unable to request interrupt\n"); + return -EINVAL; + } + + for (i = 0; i < RP1DPI_NUM_CLOCKS; i++) { + static const char * const myclocknames[RP1DPI_NUM_CLOCKS] = { + "dpiclk", "plldiv", "pllcore" + }; + dpi->clocks[i] = devm_clk_get(dev, myclocknames[i]); + if (IS_ERR(dpi->clocks[i])) { + dev_err(dev, "Unable to request clock %s\n", myclocknames[i]); + return PTR_ERR(dpi->clocks[i]); + } + } + + ret = drmm_mode_config_init(&dpi->drm); + if (ret) + goto done_err; + + dpi->rgb_order_override = RP1DPI_ORDER_UNCHANGED; + if (!of_property_read_string(dev->of_node, "rgb_order", &rgb_order)) { + if (!strcmp(rgb_order, "rgb")) + dpi->rgb_order_override = RP1DPI_ORDER_RGB; + else if (!strcmp(rgb_order, "bgr")) + dpi->rgb_order_override = RP1DPI_ORDER_BGR; + else if (!strcmp(rgb_order, "grb")) + dpi->rgb_order_override = RP1DPI_ORDER_GRB; + else if (!strcmp(rgb_order, "brg")) + dpi->rgb_order_override = RP1DPI_ORDER_BRG; + else + DRM_ERROR("Invalid dpi order %s - ignored\n", rgb_order); + } + + /* Check if PIO can snoop on or override DPI's GPIO1 */ + dpi->gpio1_used = false; + for (i = 0; !dpi->gpio1_used; i++) { + u32 p = 0; + const char *str = NULL; + struct device_node *np1 = of_parse_phandle(dev->of_node, "pinctrl-0", i); + + if (!np1) + break; + + if (!of_property_read_string(np1, "function", &str) && !strcmp(str, "dpi")) { + for (j = 0; !dpi->gpio1_used; j++) { + if (of_property_read_string_index(np1, "pins", j, &str)) + break; + if (!strcmp(str, "gpio1")) + dpi->gpio1_used = true; + } + for (j = 0; !dpi->gpio1_used; j++) { + if (of_property_read_u32_index(np1, "brcm,pins", j, &p)) + break; + if (p == 1) + dpi->gpio1_used = true; + } + } + of_node_put(np1); + } + + /* Now we have all our resources, finish driver initialization */ + dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)); + init_completion(&dpi->finished); + dpi->drm.dev_private = dpi; + platform_set_drvdata(pdev, &dpi->drm); + + dpi->drm.mode_config.max_width = 4096; + dpi->drm.mode_config.max_height = 4096; + dpi->drm.mode_config.preferred_depth = 32; + dpi->drm.mode_config.prefer_shadow = 0; + dpi->drm.mode_config.quirk_addfb_prefer_host_byte_order = true; + dpi->drm.mode_config.funcs = &rp1dpi_mode_funcs; + drm_vblank_init(&dpi->drm, 1); + + ret = drm_simple_display_pipe_init(&dpi->drm, + &dpi->pipe, + &rp1dpi_pipe_funcs, + rp1dpi_formats, + ARRAY_SIZE(rp1dpi_formats), + NULL, NULL); + if (!ret) + ret = drm_simple_display_pipe_attach_bridge(&dpi->pipe, bridge); + if (ret) + goto done_err; + + drm_mode_config_reset(&dpi->drm); + + ret = drm_dev_register(&dpi->drm, 0); + if (ret) + return ret; + + drm_fbdev_ttm_setup(&dpi->drm, 32); + return ret; + +done_err: + dev_err(dev, "%s fail %d\n", __func__, ret); + return ret; +} + +static void rp1dpi_platform_remove(struct platform_device *pdev) +{ + struct drm_device *drm = platform_get_drvdata(pdev); + + rp1dpi_stopall(drm); + drm_dev_unregister(drm); + drm_atomic_helper_shutdown(drm); + drm_dev_put(drm); +} + +static void rp1dpi_platform_shutdown(struct platform_device *pdev) +{ + struct drm_device *drm = platform_get_drvdata(pdev); + + rp1dpi_stopall(drm); +} + +static const struct of_device_id rp1dpi_of_match[] = { + { + .compatible = "raspberrypi,rp1dpi", + }, + { /* sentinel */ }, +}; + +MODULE_DEVICE_TABLE(of, rp1dpi_of_match); + +static struct platform_driver rp1dpi_platform_driver = { + .probe = rp1dpi_platform_probe, + .remove = rp1dpi_platform_remove, + .shutdown = rp1dpi_platform_shutdown, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = rp1dpi_of_match, + }, +}; + +module_platform_driver(rp1dpi_platform_driver); + +MODULE_AUTHOR("Nick Hollinghurst"); +MODULE_DESCRIPTION("DRM driver for DPI output on Raspberry Pi RP1"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi.h b/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi.h new file mode 100644 index 00000000000000..848a043e1e247a --- /dev/null +++ b/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi.h @@ -0,0 +1,95 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * DRM Driver for DSI output on Raspberry Pi RP1 + * + * Copyright (c) 2023 Raspberry Pi Limited. + */ + +#include <linux/types.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <drm/drm_device.h> +#include <drm/drm_simple_kms_helper.h> + +#define MODULE_NAME "drm-rp1-dpi" +#define DRIVER_NAME "drm-rp1-dpi" + +/* ---------------------------------------------------------------------- */ + +#define RP1DPI_HW_BLOCK_DPI 0 +#define RP1DPI_HW_BLOCK_CFG 1 +#define RP1DPI_NUM_HW_BLOCKS 2 + +#define RP1DPI_CLK_DPI 0 +#define RP1DPI_CLK_PLLDIV 1 +#define RP1DPI_CLK_PLLCORE 2 +#define RP1DPI_NUM_CLOCKS 3 + +/* Codes (in LE byte order) used for S/W permutation */ +#define RP1DPI_ORDER_UNCHANGED 0 +#define RP1DPI_ORDER_RGB 0x020100 +#define RP1DPI_ORDER_BGR 0x000102 +#define RP1DPI_ORDER_GRB 0x020001 +#define RP1DPI_ORDER_BRG 0x010002 + +/* ---------------------------------------------------------------------- */ + +struct rp1_dpi { + /* DRM base and platform device pointer */ + struct drm_device drm; + struct platform_device *pdev; + + /* Framework and helper objects */ + struct drm_simple_display_pipe pipe; + struct drm_connector connector; + + /* Clocks: Video PLL, its primary divider, and DPI clock. */ + struct clk *clocks[RP1DPI_NUM_CLOCKS]; + + /* Block (DPI, VOCFG) base addresses, and current state */ + void __iomem *hw_base[RP1DPI_NUM_HW_BLOCKS]; + u32 cur_fmt; + u32 bus_fmt; + bool de_inv, clk_inv; + bool dpi_running, pipe_enabled; + unsigned int rgb_order_override; + struct completion finished; + + /* Experimental stuff for interlace follows */ + struct rp1_pio_client *pio; + bool gpio1_used; + bool pio_stole_gpio2; + + spinlock_t hw_lock; /* the following are used in line-match ISR */ + dma_addr_t last_dma_addr; + u32 last_stride; + u32 shorter_front_porch; + bool interlaced; + bool lower_field_flag; +}; + +/* ---------------------------------------------------------------------- */ +/* Functions to control the DPI/DMA block */ + +void rp1dpi_hw_setup(struct rp1_dpi *dpi, + u32 in_format, + u32 bus_format, + bool de_inv, + struct drm_display_mode const *mode); +void rp1dpi_hw_update(struct rp1_dpi *dpi, dma_addr_t addr, u32 offset, u32 stride); +void rp1dpi_hw_stop(struct rp1_dpi *dpi); +int rp1dpi_hw_busy(struct rp1_dpi *dpi); +irqreturn_t rp1dpi_hw_isr(int irq, void *dev); +void rp1dpi_hw_vblank_ctrl(struct rp1_dpi *dpi, int enable); + +/* ---------------------------------------------------------------------- */ +/* Functions to control the VIDEO OUT CFG block and check RP1 platform */ + +void rp1dpi_vidout_setup(struct rp1_dpi *dpi, bool drive_negedge); +void rp1dpi_vidout_poweroff(struct rp1_dpi *dpi); + +/* ---------------------------------------------------------------------- */ +/* PIO control -- we need PIO to generate VSync (from DE) when interlaced */ + +int rp1dpi_pio_start(struct rp1_dpi *dpi, const struct drm_display_mode *mode); +void rp1dpi_pio_stop(struct rp1_dpi *dpi); diff --git a/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_cfg.c b/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_cfg.c new file mode 100644 index 00000000000000..cd328b98d4dac2 --- /dev/null +++ b/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_cfg.c @@ -0,0 +1,510 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * DRM Driver for DPI output on Raspberry Pi RP1 + * + * Copyright (c) 2023 Raspberry Pi Limited. + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/mm.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/printk.h> +#include <linux/rp1_platform.h> + +#include "rp1_dpi.h" + +// ============================================================================= +// Register : VIDEO_OUT_CFG_SEL +// JTAG access : synchronous +// Description : Selects source: VEC or DPI +#define VIDEO_OUT_CFG_SEL_OFFSET 0x00000000 +#define VIDEO_OUT_CFG_SEL_BITS 0x00000013 +#define VIDEO_OUT_CFG_SEL_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_SEL_PCLK_INV +// Description : Select dpi_pclk output port polarity inversion. +#define VIDEO_OUT_CFG_SEL_PCLK_INV_RESET 0x0 +#define VIDEO_OUT_CFG_SEL_PCLK_INV_BITS 0x00000010 +#define VIDEO_OUT_CFG_SEL_PCLK_INV_MSB 4 +#define VIDEO_OUT_CFG_SEL_PCLK_INV_LSB 4 +#define VIDEO_OUT_CFG_SEL_PCLK_INV_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_SEL_PAD_MUX +// Description : VEC 1 DPI 0 +#define VIDEO_OUT_CFG_SEL_PAD_MUX_RESET 0x0 +#define VIDEO_OUT_CFG_SEL_PAD_MUX_BITS 0x00000002 +#define VIDEO_OUT_CFG_SEL_PAD_MUX_MSB 1 +#define VIDEO_OUT_CFG_SEL_PAD_MUX_LSB 1 +#define VIDEO_OUT_CFG_SEL_PAD_MUX_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_SEL_VDAC_MUX +// Description : VEC 1 DPI 0 +#define VIDEO_OUT_CFG_SEL_VDAC_MUX_RESET 0x0 +#define VIDEO_OUT_CFG_SEL_VDAC_MUX_BITS 0x00000001 +#define VIDEO_OUT_CFG_SEL_VDAC_MUX_MSB 0 +#define VIDEO_OUT_CFG_SEL_VDAC_MUX_LSB 0 +#define VIDEO_OUT_CFG_SEL_VDAC_MUX_ACCESS "RW" +// ============================================================================= +// Register : VIDEO_OUT_CFG_VDAC_CFG +// JTAG access : synchronous +// Description : Configure SNPS VDAC +#define VIDEO_OUT_CFG_VDAC_CFG_OFFSET 0x00000004 +#define VIDEO_OUT_CFG_VDAC_CFG_BITS 0x1fffffff +#define VIDEO_OUT_CFG_VDAC_CFG_RESET 0x0003ffff +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_VDAC_CFG_ENCTR +// Description : None +#define VIDEO_OUT_CFG_VDAC_CFG_ENCTR_RESET 0x0 +#define VIDEO_OUT_CFG_VDAC_CFG_ENCTR_BITS 0x1c000000 +#define VIDEO_OUT_CFG_VDAC_CFG_ENCTR_MSB 28 +#define VIDEO_OUT_CFG_VDAC_CFG_ENCTR_LSB 26 +#define VIDEO_OUT_CFG_VDAC_CFG_ENCTR_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_VDAC_CFG_ENSC +// Description : None +#define VIDEO_OUT_CFG_VDAC_CFG_ENSC_RESET 0x0 +#define VIDEO_OUT_CFG_VDAC_CFG_ENSC_BITS 0x03800000 +#define VIDEO_OUT_CFG_VDAC_CFG_ENSC_MSB 25 +#define VIDEO_OUT_CFG_VDAC_CFG_ENSC_LSB 23 +#define VIDEO_OUT_CFG_VDAC_CFG_ENSC_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_VDAC_CFG_ENDAC +// Description : None +#define VIDEO_OUT_CFG_VDAC_CFG_ENDAC_RESET 0x0 +#define VIDEO_OUT_CFG_VDAC_CFG_ENDAC_BITS 0x00700000 +#define VIDEO_OUT_CFG_VDAC_CFG_ENDAC_MSB 22 +#define VIDEO_OUT_CFG_VDAC_CFG_ENDAC_LSB 20 +#define VIDEO_OUT_CFG_VDAC_CFG_ENDAC_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_VDAC_CFG_ENVBG +// Description : None +#define VIDEO_OUT_CFG_VDAC_CFG_ENVBG_RESET 0x0 +#define VIDEO_OUT_CFG_VDAC_CFG_ENVBG_BITS 0x00080000 +#define VIDEO_OUT_CFG_VDAC_CFG_ENVBG_MSB 19 +#define VIDEO_OUT_CFG_VDAC_CFG_ENVBG_LSB 19 +#define VIDEO_OUT_CFG_VDAC_CFG_ENVBG_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_VDAC_CFG_ENEXTREF +// Description : None +#define VIDEO_OUT_CFG_VDAC_CFG_ENEXTREF_RESET 0x0 +#define VIDEO_OUT_CFG_VDAC_CFG_ENEXTREF_BITS 0x00040000 +#define VIDEO_OUT_CFG_VDAC_CFG_ENEXTREF_MSB 18 +#define VIDEO_OUT_CFG_VDAC_CFG_ENEXTREF_LSB 18 +#define VIDEO_OUT_CFG_VDAC_CFG_ENEXTREF_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_VDAC_CFG_DAC2GC +// Description : dac2 gain control +#define VIDEO_OUT_CFG_VDAC_CFG_DAC2GC_RESET 0x3f +#define VIDEO_OUT_CFG_VDAC_CFG_DAC2GC_BITS 0x0003f000 +#define VIDEO_OUT_CFG_VDAC_CFG_DAC2GC_MSB 17 +#define VIDEO_OUT_CFG_VDAC_CFG_DAC2GC_LSB 12 +#define VIDEO_OUT_CFG_VDAC_CFG_DAC2GC_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_VDAC_CFG_DAC1GC +// Description : dac1 gain control +#define VIDEO_OUT_CFG_VDAC_CFG_DAC1GC_RESET 0x3f +#define VIDEO_OUT_CFG_VDAC_CFG_DAC1GC_BITS 0x00000fc0 +#define VIDEO_OUT_CFG_VDAC_CFG_DAC1GC_MSB 11 +#define VIDEO_OUT_CFG_VDAC_CFG_DAC1GC_LSB 6 +#define VIDEO_OUT_CFG_VDAC_CFG_DAC1GC_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_VDAC_CFG_DAC0GC +// Description : dac0 gain control +#define VIDEO_OUT_CFG_VDAC_CFG_DAC0GC_RESET 0x3f +#define VIDEO_OUT_CFG_VDAC_CFG_DAC0GC_BITS 0x0000003f +#define VIDEO_OUT_CFG_VDAC_CFG_DAC0GC_MSB 5 +#define VIDEO_OUT_CFG_VDAC_CFG_DAC0GC_LSB 0 +#define VIDEO_OUT_CFG_VDAC_CFG_DAC0GC_ACCESS "RW" +// ============================================================================= +// Register : VIDEO_OUT_CFG_VDAC_STATUS +// JTAG access : synchronous +// Description : Read VDAC status +#define VIDEO_OUT_CFG_VDAC_STATUS_OFFSET 0x00000008 +#define VIDEO_OUT_CFG_VDAC_STATUS_BITS 0x00000017 +#define VIDEO_OUT_CFG_VDAC_STATUS_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_VDAC_STATUS_ENCTR3 +// Description : None +#define VIDEO_OUT_CFG_VDAC_STATUS_ENCTR3_RESET 0x0 +#define VIDEO_OUT_CFG_VDAC_STATUS_ENCTR3_BITS 0x00000010 +#define VIDEO_OUT_CFG_VDAC_STATUS_ENCTR3_MSB 4 +#define VIDEO_OUT_CFG_VDAC_STATUS_ENCTR3_LSB 4 +#define VIDEO_OUT_CFG_VDAC_STATUS_ENCTR3_ACCESS "RO" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_VDAC_STATUS_CABLEOUT +// Description : None +#define VIDEO_OUT_CFG_VDAC_STATUS_CABLEOUT_RESET "-" +#define VIDEO_OUT_CFG_VDAC_STATUS_CABLEOUT_BITS 0x00000007 +#define VIDEO_OUT_CFG_VDAC_STATUS_CABLEOUT_MSB 2 +#define VIDEO_OUT_CFG_VDAC_STATUS_CABLEOUT_LSB 0 +#define VIDEO_OUT_CFG_VDAC_STATUS_CABLEOUT_ACCESS "RO" +// ============================================================================= +// Register : VIDEO_OUT_CFG_MEM_PD +// JTAG access : synchronous +// Description : Control memory power down +#define VIDEO_OUT_CFG_MEM_PD_OFFSET 0x0000000c +#define VIDEO_OUT_CFG_MEM_PD_BITS 0x00000003 +#define VIDEO_OUT_CFG_MEM_PD_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_MEM_PD_VEC +// Description : None +#define VIDEO_OUT_CFG_MEM_PD_VEC_RESET 0x0 +#define VIDEO_OUT_CFG_MEM_PD_VEC_BITS 0x00000002 +#define VIDEO_OUT_CFG_MEM_PD_VEC_MSB 1 +#define VIDEO_OUT_CFG_MEM_PD_VEC_LSB 1 +#define VIDEO_OUT_CFG_MEM_PD_VEC_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_MEM_PD_DPI +// Description : None +#define VIDEO_OUT_CFG_MEM_PD_DPI_RESET 0x0 +#define VIDEO_OUT_CFG_MEM_PD_DPI_BITS 0x00000001 +#define VIDEO_OUT_CFG_MEM_PD_DPI_MSB 0 +#define VIDEO_OUT_CFG_MEM_PD_DPI_LSB 0 +#define VIDEO_OUT_CFG_MEM_PD_DPI_ACCESS "RW" +// ============================================================================= +// Register : VIDEO_OUT_CFG_TEST_OVERRIDE +// JTAG access : synchronous +// Description : None +#define VIDEO_OUT_CFG_TEST_OVERRIDE_OFFSET 0x00000010 +#define VIDEO_OUT_CFG_TEST_OVERRIDE_BITS 0xffffffff +#define VIDEO_OUT_CFG_TEST_OVERRIDE_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_TEST_OVERRIDE_PAD +// Description : None +#define VIDEO_OUT_CFG_TEST_OVERRIDE_PAD_RESET 0x0 +#define VIDEO_OUT_CFG_TEST_OVERRIDE_PAD_BITS 0x80000000 +#define VIDEO_OUT_CFG_TEST_OVERRIDE_PAD_MSB 31 +#define VIDEO_OUT_CFG_TEST_OVERRIDE_PAD_LSB 31 +#define VIDEO_OUT_CFG_TEST_OVERRIDE_PAD_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_TEST_OVERRIDE_VDAC +// Description : None +#define VIDEO_OUT_CFG_TEST_OVERRIDE_VDAC_RESET 0x0 +#define VIDEO_OUT_CFG_TEST_OVERRIDE_VDAC_BITS 0x40000000 +#define VIDEO_OUT_CFG_TEST_OVERRIDE_VDAC_MSB 30 +#define VIDEO_OUT_CFG_TEST_OVERRIDE_VDAC_LSB 30 +#define VIDEO_OUT_CFG_TEST_OVERRIDE_VDAC_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_TEST_OVERRIDE_RGBVAL +// Description : None +#define VIDEO_OUT_CFG_TEST_OVERRIDE_RGBVAL_RESET 0x00000000 +#define VIDEO_OUT_CFG_TEST_OVERRIDE_RGBVAL_BITS 0x3fffffff +#define VIDEO_OUT_CFG_TEST_OVERRIDE_RGBVAL_MSB 29 +#define VIDEO_OUT_CFG_TEST_OVERRIDE_RGBVAL_LSB 0 +#define VIDEO_OUT_CFG_TEST_OVERRIDE_RGBVAL_ACCESS "RW" +// ============================================================================= +// Register : VIDEO_OUT_CFG_INTR +// JTAG access : synchronous +// Description : Raw Interrupts +#define VIDEO_OUT_CFG_INTR_OFFSET 0x00000014 +#define VIDEO_OUT_CFG_INTR_BITS 0x00000003 +#define VIDEO_OUT_CFG_INTR_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_INTR_DPI +// Description : None +#define VIDEO_OUT_CFG_INTR_DPI_RESET 0x0 +#define VIDEO_OUT_CFG_INTR_DPI_BITS 0x00000002 +#define VIDEO_OUT_CFG_INTR_DPI_MSB 1 +#define VIDEO_OUT_CFG_INTR_DPI_LSB 1 +#define VIDEO_OUT_CFG_INTR_DPI_ACCESS "RO" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_INTR_VEC +// Description : None +#define VIDEO_OUT_CFG_INTR_VEC_RESET 0x0 +#define VIDEO_OUT_CFG_INTR_VEC_BITS 0x00000001 +#define VIDEO_OUT_CFG_INTR_VEC_MSB 0 +#define VIDEO_OUT_CFG_INTR_VEC_LSB 0 +#define VIDEO_OUT_CFG_INTR_VEC_ACCESS "RO" +// ============================================================================= +// Register : VIDEO_OUT_CFG_INTE +// JTAG access : synchronous +// Description : Interrupt Enable +#define VIDEO_OUT_CFG_INTE_OFFSET 0x00000018 +#define VIDEO_OUT_CFG_INTE_BITS 0x00000003 +#define VIDEO_OUT_CFG_INTE_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_INTE_DPI +// Description : None +#define VIDEO_OUT_CFG_INTE_DPI_RESET 0x0 +#define VIDEO_OUT_CFG_INTE_DPI_BITS 0x00000002 +#define VIDEO_OUT_CFG_INTE_DPI_MSB 1 +#define VIDEO_OUT_CFG_INTE_DPI_LSB 1 +#define VIDEO_OUT_CFG_INTE_DPI_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_INTE_VEC +// Description : None +#define VIDEO_OUT_CFG_INTE_VEC_RESET 0x0 +#define VIDEO_OUT_CFG_INTE_VEC_BITS 0x00000001 +#define VIDEO_OUT_CFG_INTE_VEC_MSB 0 +#define VIDEO_OUT_CFG_INTE_VEC_LSB 0 +#define VIDEO_OUT_CFG_INTE_VEC_ACCESS "RW" +// ============================================================================= +// Register : VIDEO_OUT_CFG_INTF +// JTAG access : synchronous +// Description : Interrupt Force +#define VIDEO_OUT_CFG_INTF_OFFSET 0x0000001c +#define VIDEO_OUT_CFG_INTF_BITS 0x00000003 +#define VIDEO_OUT_CFG_INTF_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_INTF_DPI +// Description : None +#define VIDEO_OUT_CFG_INTF_DPI_RESET 0x0 +#define VIDEO_OUT_CFG_INTF_DPI_BITS 0x00000002 +#define VIDEO_OUT_CFG_INTF_DPI_MSB 1 +#define VIDEO_OUT_CFG_INTF_DPI_LSB 1 +#define VIDEO_OUT_CFG_INTF_DPI_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_INTF_VEC +// Description : None +#define VIDEO_OUT_CFG_INTF_VEC_RESET 0x0 +#define VIDEO_OUT_CFG_INTF_VEC_BITS 0x00000001 +#define VIDEO_OUT_CFG_INTF_VEC_MSB 0 +#define VIDEO_OUT_CFG_INTF_VEC_LSB 0 +#define VIDEO_OUT_CFG_INTF_VEC_ACCESS "RW" +// ============================================================================= +// Register : VIDEO_OUT_CFG_INTS +// JTAG access : synchronous +// Description : Interrupt status after masking & forcing +#define VIDEO_OUT_CFG_INTS_OFFSET 0x00000020 +#define VIDEO_OUT_CFG_INTS_BITS 0x00000003 +#define VIDEO_OUT_CFG_INTS_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_INTS_DPI +// Description : None +#define VIDEO_OUT_CFG_INTS_DPI_RESET 0x0 +#define VIDEO_OUT_CFG_INTS_DPI_BITS 0x00000002 +#define VIDEO_OUT_CFG_INTS_DPI_MSB 1 +#define VIDEO_OUT_CFG_INTS_DPI_LSB 1 +#define VIDEO_OUT_CFG_INTS_DPI_ACCESS "RO" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_INTS_VEC +// Description : None +#define VIDEO_OUT_CFG_INTS_VEC_RESET 0x0 +#define VIDEO_OUT_CFG_INTS_VEC_BITS 0x00000001 +#define VIDEO_OUT_CFG_INTS_VEC_MSB 0 +#define VIDEO_OUT_CFG_INTS_VEC_LSB 0 +#define VIDEO_OUT_CFG_INTS_VEC_ACCESS "RO" +// ============================================================================= +// Register : VIDEO_OUT_CFG_BLOCK_ID +// JTAG access : synchronous +// Description : Block Identifier +// Hexadecimal representation of "VOCF" +#define VIDEO_OUT_CFG_BLOCK_ID_OFFSET 0x00000024 +#define VIDEO_OUT_CFG_BLOCK_ID_BITS 0xffffffff +#define VIDEO_OUT_CFG_BLOCK_ID_RESET 0x564f4346 +#define VIDEO_OUT_CFG_BLOCK_ID_MSB 31 +#define VIDEO_OUT_CFG_BLOCK_ID_LSB 0 +#define VIDEO_OUT_CFG_BLOCK_ID_ACCESS "RO" +// ============================================================================= +// Register : VIDEO_OUT_CFG_INSTANCE_ID +// JTAG access : synchronous +// Description : Block Instance Identifier +#define VIDEO_OUT_CFG_INSTANCE_ID_OFFSET 0x00000028 +#define VIDEO_OUT_CFG_INSTANCE_ID_BITS 0x0000000f +#define VIDEO_OUT_CFG_INSTANCE_ID_RESET 0x00000000 +#define VIDEO_OUT_CFG_INSTANCE_ID_MSB 3 +#define VIDEO_OUT_CFG_INSTANCE_ID_LSB 0 +#define VIDEO_OUT_CFG_INSTANCE_ID_ACCESS "RO" +// ============================================================================= +// Register : VIDEO_OUT_CFG_RSTSEQ_AUTO +// JTAG access : synchronous +// Description : None +#define VIDEO_OUT_CFG_RSTSEQ_AUTO_OFFSET 0x0000002c +#define VIDEO_OUT_CFG_RSTSEQ_AUTO_BITS 0x00000007 +#define VIDEO_OUT_CFG_RSTSEQ_AUTO_RESET 0x00000007 +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_RSTSEQ_AUTO_VEC +// Description : 1 = reset is controlled by the sequencer +// 0 = reset is controlled by rstseq_ctrl +#define VIDEO_OUT_CFG_RSTSEQ_AUTO_VEC_RESET 0x1 +#define VIDEO_OUT_CFG_RSTSEQ_AUTO_VEC_BITS 0x00000004 +#define VIDEO_OUT_CFG_RSTSEQ_AUTO_VEC_MSB 2 +#define VIDEO_OUT_CFG_RSTSEQ_AUTO_VEC_LSB 2 +#define VIDEO_OUT_CFG_RSTSEQ_AUTO_VEC_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_RSTSEQ_AUTO_DPI +// Description : 1 = reset is controlled by the sequencer +// 0 = reset is controlled by rstseq_ctrl +#define VIDEO_OUT_CFG_RSTSEQ_AUTO_DPI_RESET 0x1 +#define VIDEO_OUT_CFG_RSTSEQ_AUTO_DPI_BITS 0x00000002 +#define VIDEO_OUT_CFG_RSTSEQ_AUTO_DPI_MSB 1 +#define VIDEO_OUT_CFG_RSTSEQ_AUTO_DPI_LSB 1 +#define VIDEO_OUT_CFG_RSTSEQ_AUTO_DPI_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_RSTSEQ_AUTO_BUSADAPTER +// Description : 1 = reset is controlled by the sequencer +// 0 = reset is controlled by rstseq_ctrl +#define VIDEO_OUT_CFG_RSTSEQ_AUTO_BUSADAPTER_RESET 0x1 +#define VIDEO_OUT_CFG_RSTSEQ_AUTO_BUSADAPTER_BITS 0x00000001 +#define VIDEO_OUT_CFG_RSTSEQ_AUTO_BUSADAPTER_MSB 0 +#define VIDEO_OUT_CFG_RSTSEQ_AUTO_BUSADAPTER_LSB 0 +#define VIDEO_OUT_CFG_RSTSEQ_AUTO_BUSADAPTER_ACCESS "RW" +// ============================================================================= +// Register : VIDEO_OUT_CFG_RSTSEQ_PARALLEL +// JTAG access : synchronous +// Description : None +#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_OFFSET 0x00000030 +#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_BITS 0x00000007 +#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_RESET 0x00000006 +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_RSTSEQ_PARALLEL_VEC +// Description : Is this reset parallel (i.e. not part of the sequence) +#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_VEC_RESET 0x1 +#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_VEC_BITS 0x00000004 +#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_VEC_MSB 2 +#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_VEC_LSB 2 +#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_VEC_ACCESS "RO" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_RSTSEQ_PARALLEL_DPI +// Description : Is this reset parallel (i.e. not part of the sequence) +#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_DPI_RESET 0x1 +#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_DPI_BITS 0x00000002 +#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_DPI_MSB 1 +#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_DPI_LSB 1 +#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_DPI_ACCESS "RO" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_RSTSEQ_PARALLEL_BUSADAPTER +// Description : Is this reset parallel (i.e. not part of the sequence) +#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_BUSADAPTER_RESET 0x0 +#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_BUSADAPTER_BITS 0x00000001 +#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_BUSADAPTER_MSB 0 +#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_BUSADAPTER_LSB 0 +#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_BUSADAPTER_ACCESS "RO" +// ============================================================================= +// Register : VIDEO_OUT_CFG_RSTSEQ_CTRL +// JTAG access : synchronous +// Description : None +#define VIDEO_OUT_CFG_RSTSEQ_CTRL_OFFSET 0x00000034 +#define VIDEO_OUT_CFG_RSTSEQ_CTRL_BITS 0x00000007 +#define VIDEO_OUT_CFG_RSTSEQ_CTRL_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_RSTSEQ_CTRL_VEC +// Description : 1 = keep the reset asserted +// 0 = keep the reset deasserted +// This is ignored if rstseq_auto=1 +#define VIDEO_OUT_CFG_RSTSEQ_CTRL_VEC_RESET 0x0 +#define VIDEO_OUT_CFG_RSTSEQ_CTRL_VEC_BITS 0x00000004 +#define VIDEO_OUT_CFG_RSTSEQ_CTRL_VEC_MSB 2 +#define VIDEO_OUT_CFG_RSTSEQ_CTRL_VEC_LSB 2 +#define VIDEO_OUT_CFG_RSTSEQ_CTRL_VEC_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_RSTSEQ_CTRL_DPI +// Description : 1 = keep the reset asserted +// 0 = keep the reset deasserted +// This is ignored if rstseq_auto=1 +#define VIDEO_OUT_CFG_RSTSEQ_CTRL_DPI_RESET 0x0 +#define VIDEO_OUT_CFG_RSTSEQ_CTRL_DPI_BITS 0x00000002 +#define VIDEO_OUT_CFG_RSTSEQ_CTRL_DPI_MSB 1 +#define VIDEO_OUT_CFG_RSTSEQ_CTRL_DPI_LSB 1 +#define VIDEO_OUT_CFG_RSTSEQ_CTRL_DPI_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_RSTSEQ_CTRL_BUSADAPTER +// Description : 1 = keep the reset asserted +// 0 = keep the reset deasserted +// This is ignored if rstseq_auto=1 +#define VIDEO_OUT_CFG_RSTSEQ_CTRL_BUSADAPTER_RESET 0x0 +#define VIDEO_OUT_CFG_RSTSEQ_CTRL_BUSADAPTER_BITS 0x00000001 +#define VIDEO_OUT_CFG_RSTSEQ_CTRL_BUSADAPTER_MSB 0 +#define VIDEO_OUT_CFG_RSTSEQ_CTRL_BUSADAPTER_LSB 0 +#define VIDEO_OUT_CFG_RSTSEQ_CTRL_BUSADAPTER_ACCESS "RW" +// ============================================================================= +// Register : VIDEO_OUT_CFG_RSTSEQ_TRIG +// JTAG access : synchronous +// Description : None +#define VIDEO_OUT_CFG_RSTSEQ_TRIG_OFFSET 0x00000038 +#define VIDEO_OUT_CFG_RSTSEQ_TRIG_BITS 0x00000007 +#define VIDEO_OUT_CFG_RSTSEQ_TRIG_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_RSTSEQ_TRIG_VEC +// Description : Pulses the reset output +#define VIDEO_OUT_CFG_RSTSEQ_TRIG_VEC_RESET 0x0 +#define VIDEO_OUT_CFG_RSTSEQ_TRIG_VEC_BITS 0x00000004 +#define VIDEO_OUT_CFG_RSTSEQ_TRIG_VEC_MSB 2 +#define VIDEO_OUT_CFG_RSTSEQ_TRIG_VEC_LSB 2 +#define VIDEO_OUT_CFG_RSTSEQ_TRIG_VEC_ACCESS "SC" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_RSTSEQ_TRIG_DPI +// Description : Pulses the reset output +#define VIDEO_OUT_CFG_RSTSEQ_TRIG_DPI_RESET 0x0 +#define VIDEO_OUT_CFG_RSTSEQ_TRIG_DPI_BITS 0x00000002 +#define VIDEO_OUT_CFG_RSTSEQ_TRIG_DPI_MSB 1 +#define VIDEO_OUT_CFG_RSTSEQ_TRIG_DPI_LSB 1 +#define VIDEO_OUT_CFG_RSTSEQ_TRIG_DPI_ACCESS "SC" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_RSTSEQ_TRIG_BUSADAPTER +// Description : Pulses the reset output +#define VIDEO_OUT_CFG_RSTSEQ_TRIG_BUSADAPTER_RESET 0x0 +#define VIDEO_OUT_CFG_RSTSEQ_TRIG_BUSADAPTER_BITS 0x00000001 +#define VIDEO_OUT_CFG_RSTSEQ_TRIG_BUSADAPTER_MSB 0 +#define VIDEO_OUT_CFG_RSTSEQ_TRIG_BUSADAPTER_LSB 0 +#define VIDEO_OUT_CFG_RSTSEQ_TRIG_BUSADAPTER_ACCESS "SC" +// ============================================================================= +// Register : VIDEO_OUT_CFG_RSTSEQ_DONE +// JTAG access : synchronous +// Description : None +#define VIDEO_OUT_CFG_RSTSEQ_DONE_OFFSET 0x0000003c +#define VIDEO_OUT_CFG_RSTSEQ_DONE_BITS 0x00000007 +#define VIDEO_OUT_CFG_RSTSEQ_DONE_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_RSTSEQ_DONE_VEC +// Description : Indicates the current state of the reset +#define VIDEO_OUT_CFG_RSTSEQ_DONE_VEC_RESET 0x0 +#define VIDEO_OUT_CFG_RSTSEQ_DONE_VEC_BITS 0x00000004 +#define VIDEO_OUT_CFG_RSTSEQ_DONE_VEC_MSB 2 +#define VIDEO_OUT_CFG_RSTSEQ_DONE_VEC_LSB 2 +#define VIDEO_OUT_CFG_RSTSEQ_DONE_VEC_ACCESS "RO" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_RSTSEQ_DONE_DPI +// Description : Indicates the current state of the reset +#define VIDEO_OUT_CFG_RSTSEQ_DONE_DPI_RESET 0x0 +#define VIDEO_OUT_CFG_RSTSEQ_DONE_DPI_BITS 0x00000002 +#define VIDEO_OUT_CFG_RSTSEQ_DONE_DPI_MSB 1 +#define VIDEO_OUT_CFG_RSTSEQ_DONE_DPI_LSB 1 +#define VIDEO_OUT_CFG_RSTSEQ_DONE_DPI_ACCESS "RO" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_RSTSEQ_DONE_BUSADAPTER +// Description : Indicates the current state of the reset +#define VIDEO_OUT_CFG_RSTSEQ_DONE_BUSADAPTER_RESET 0x0 +#define VIDEO_OUT_CFG_RSTSEQ_DONE_BUSADAPTER_BITS 0x00000001 +#define VIDEO_OUT_CFG_RSTSEQ_DONE_BUSADAPTER_MSB 0 +#define VIDEO_OUT_CFG_RSTSEQ_DONE_BUSADAPTER_LSB 0 +#define VIDEO_OUT_CFG_RSTSEQ_DONE_BUSADAPTER_ACCESS "RO" +// ============================================================================= + +#define CFG_WRITE(reg, val) writel((val), dpi->hw_base[RP1DPI_HW_BLOCK_CFG] + (reg ## _OFFSET)) +#define CFG_READ(reg) readl(dpi->hw_base[RP1DPI_HW_BLOCK_CFG] + (reg ## _OFFSET)) + +void rp1dpi_vidout_setup(struct rp1_dpi *dpi, bool drive_negedge) +{ + /* + * We assume DPI and VEC can't be used at the same time (due to + * clashing requirements for PLL_VIDEO, and potentially for VDAC). + * We therefore leave VEC memories powered down. + */ + CFG_WRITE(VIDEO_OUT_CFG_MEM_PD, VIDEO_OUT_CFG_MEM_PD_VEC_BITS); + CFG_WRITE(VIDEO_OUT_CFG_TEST_OVERRIDE, + VIDEO_OUT_CFG_TEST_OVERRIDE_VDAC_BITS); + + /* DPI->Pads; DPI->VDAC; optionally flip PCLK polarity */ + CFG_WRITE(VIDEO_OUT_CFG_SEL, + drive_negedge ? VIDEO_OUT_CFG_SEL_PCLK_INV_BITS : 0); + + /* configure VDAC for 3 channels, bandgap on, 710mV swing */ + CFG_WRITE(VIDEO_OUT_CFG_VDAC_CFG, 0); + + /* enable DPI interrupt */ + CFG_WRITE(VIDEO_OUT_CFG_INTE, VIDEO_OUT_CFG_INTE_DPI_BITS); +} + +void rp1dpi_vidout_poweroff(struct rp1_dpi *dpi) +{ + /* disable DPI interrupt */ + CFG_WRITE(VIDEO_OUT_CFG_INTE, 0); + + /* Ensure VDAC is turned off; power down DPI,VEC memories */ + CFG_WRITE(VIDEO_OUT_CFG_VDAC_CFG, 0); + CFG_WRITE(VIDEO_OUT_CFG_MEM_PD, VIDEO_OUT_CFG_MEM_PD_BITS); +} diff --git a/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_hw.c b/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_hw.c new file mode 100644 index 00000000000000..b6b844afb2e3d1 --- /dev/null +++ b/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_hw.c @@ -0,0 +1,668 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * DRM Driver for DPI output on Raspberry Pi RP1 + * + * Copyright (c) 2023 Raspberry Pi Limited. + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/mm.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/media-bus-format.h> +#include <linux/platform_device.h> +#include <linux/printk.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_print.h> +#include <drm/drm_vblank.h> + +#include "rp1_dpi.h" + +// --- DPI DMA REGISTERS --- + +// Control +#define DPI_DMA_CONTROL 0x0 +#define DPI_DMA_CONTROL_ARM_SHIFT 0 +#define DPI_DMA_CONTROL_ARM_MASK BIT(DPI_DMA_CONTROL_ARM_SHIFT) +#define DPI_DMA_CONTROL_ALIGN16_SHIFT 2 +#define DPI_DMA_CONTROL_ALIGN16_MASK BIT(DPI_DMA_CONTROL_ALIGN16_SHIFT) +#define DPI_DMA_CONTROL_AUTO_REPEAT_SHIFT 1 +#define DPI_DMA_CONTROL_AUTO_REPEAT_MASK BIT(DPI_DMA_CONTROL_AUTO_REPEAT_SHIFT) +#define DPI_DMA_CONTROL_HIGH_WATER_SHIFT 3 +#define DPI_DMA_CONTROL_HIGH_WATER_MASK (0x1FF << DPI_DMA_CONTROL_HIGH_WATER_SHIFT) +#define DPI_DMA_CONTROL_DEN_POL_SHIFT 12 +#define DPI_DMA_CONTROL_DEN_POL_MASK BIT(DPI_DMA_CONTROL_DEN_POL_SHIFT) +#define DPI_DMA_CONTROL_HSYNC_POL_SHIFT 13 +#define DPI_DMA_CONTROL_HSYNC_POL_MASK BIT(DPI_DMA_CONTROL_HSYNC_POL_SHIFT) +#define DPI_DMA_CONTROL_VSYNC_POL_SHIFT 14 +#define DPI_DMA_CONTROL_VSYNC_POL_MASK BIT(DPI_DMA_CONTROL_VSYNC_POL_SHIFT) +#define DPI_DMA_CONTROL_COLORM_SHIFT 15 +#define DPI_DMA_CONTROL_COLORM_MASK BIT(DPI_DMA_CONTROL_COLORM_SHIFT) +#define DPI_DMA_CONTROL_SHUTDN_SHIFT 16 +#define DPI_DMA_CONTROL_SHUTDN_MASK BIT(DPI_DMA_CONTROL_SHUTDN_SHIFT) +#define DPI_DMA_CONTROL_HBP_EN_SHIFT 17 +#define DPI_DMA_CONTROL_HBP_EN_MASK BIT(DPI_DMA_CONTROL_HBP_EN_SHIFT) +#define DPI_DMA_CONTROL_HFP_EN_SHIFT 18 +#define DPI_DMA_CONTROL_HFP_EN_MASK BIT(DPI_DMA_CONTROL_HFP_EN_SHIFT) +#define DPI_DMA_CONTROL_VBP_EN_SHIFT 19 +#define DPI_DMA_CONTROL_VBP_EN_MASK BIT(DPI_DMA_CONTROL_VBP_EN_SHIFT) +#define DPI_DMA_CONTROL_VFP_EN_SHIFT 20 +#define DPI_DMA_CONTROL_VFP_EN_MASK BIT(DPI_DMA_CONTROL_VFP_EN_SHIFT) +#define DPI_DMA_CONTROL_HSYNC_EN_SHIFT 21 +#define DPI_DMA_CONTROL_HSYNC_EN_MASK BIT(DPI_DMA_CONTROL_HSYNC_EN_SHIFT) +#define DPI_DMA_CONTROL_VSYNC_EN_SHIFT 22 +#define DPI_DMA_CONTROL_VSYNC_EN_MASK BIT(DPI_DMA_CONTROL_VSYNC_EN_SHIFT) +#define DPI_DMA_CONTROL_FORCE_IMMED_SHIFT 23 +#define DPI_DMA_CONTROL_FORCE_IMMED_MASK BIT(DPI_DMA_CONTROL_FORCE_IMMED_SHIFT) +#define DPI_DMA_CONTROL_FORCE_DRAIN_SHIFT 24 +#define DPI_DMA_CONTROL_FORCE_DRAIN_MASK BIT(DPI_DMA_CONTROL_FORCE_DRAIN_SHIFT) +#define DPI_DMA_CONTROL_FORCE_EMPTY_SHIFT 25 +#define DPI_DMA_CONTROL_FORCE_EMPTY_MASK BIT(DPI_DMA_CONTROL_FORCE_EMPTY_SHIFT) + +// IRQ_ENABLES +#define DPI_DMA_IRQ_EN 0x04 +#define DPI_DMA_IRQ_EN_DMA_READY_SHIFT 0 +#define DPI_DMA_IRQ_EN_DMA_READY_MASK BIT(DPI_DMA_IRQ_EN_DMA_READY_SHIFT) +#define DPI_DMA_IRQ_EN_UNDERFLOW_SHIFT 1 +#define DPI_DMA_IRQ_EN_UNDERFLOW_MASK BIT(DPI_DMA_IRQ_EN_UNDERFLOW_SHIFT) +#define DPI_DMA_IRQ_EN_FRAME_START_SHIFT 2 +#define DPI_DMA_IRQ_EN_FRAME_START_MASK BIT(DPI_DMA_IRQ_EN_FRAME_START_SHIFT) +#define DPI_DMA_IRQ_EN_AFIFO_EMPTY_SHIFT 3 +#define DPI_DMA_IRQ_EN_AFIFO_EMPTY_MASK BIT(DPI_DMA_IRQ_EN_AFIFO_EMPTY_SHIFT) +#define DPI_DMA_IRQ_EN_TE_SHIFT 4 +#define DPI_DMA_IRQ_EN_TE_MASK BIT(DPI_DMA_IRQ_EN_TE_SHIFT) +#define DPI_DMA_IRQ_EN_ERROR_SHIFT 5 +#define DPI_DMA_IRQ_EN_ERROR_MASK BIT(DPI_DMA_IRQ_EN_ERROR_SHIFT) +#define DPI_DMA_IRQ_EN_MATCH_SHIFT 6 +#define DPI_DMA_IRQ_EN_MATCH_MASK BIT(DPI_DMA_IRQ_EN_MATCH_SHIFT) +#define DPI_DMA_IRQ_EN_MATCH_LINE_SHIFT 16 +#define DPI_DMA_IRQ_EN_MATCH_LINE_MASK (0xFFF << DPI_DMA_IRQ_EN_MATCH_LINE_SHIFT) + +// IRQ_FLAGS +#define DPI_DMA_IRQ_FLAGS 0x08 +#define DPI_DMA_IRQ_FLAGS_DMA_READY_SHIFT 0 +#define DPI_DMA_IRQ_FLAGS_DMA_READY_MASK BIT(DPI_DMA_IRQ_FLAGS_DMA_READY_SHIFT) +#define DPI_DMA_IRQ_FLAGS_UNDERFLOW_SHIFT 1 +#define DPI_DMA_IRQ_FLAGS_UNDERFLOW_MASK BIT(DPI_DMA_IRQ_FLAGS_UNDERFLOW_SHIFT) +#define DPI_DMA_IRQ_FLAGS_FRAME_START_SHIFT 2 +#define DPI_DMA_IRQ_FLAGS_FRAME_START_MASK BIT(DPI_DMA_IRQ_FLAGS_FRAME_START_SHIFT) +#define DPI_DMA_IRQ_FLAGS_AFIFO_EMPTY_SHIFT 3 +#define DPI_DMA_IRQ_FLAGS_AFIFO_EMPTY_MASK BIT(DPI_DMA_IRQ_FLAGS_AFIFO_EMPTY_SHIFT) +#define DPI_DMA_IRQ_FLAGS_TE_SHIFT 4 +#define DPI_DMA_IRQ_FLAGS_TE_MASK BIT(DPI_DMA_IRQ_FLAGS_TE_SHIFT) +#define DPI_DMA_IRQ_FLAGS_ERROR_SHIFT 5 +#define DPI_DMA_IRQ_FLAGS_ERROR_MASK BIT(DPI_DMA_IRQ_FLAGS_ERROR_SHIFT) +#define DPI_DMA_IRQ_FLAGS_MATCH_SHIFT 6 +#define DPI_DMA_IRQ_FLAGS_MATCH_MASK BIT(DPI_DMA_IRQ_FLAGS_MATCH_SHIFT) + +// QOS +#define DPI_DMA_QOS 0xC +#define DPI_DMA_QOS_DQOS_SHIFT 0 +#define DPI_DMA_QOS_DQOS_MASK (0xF << DPI_DMA_QOS_DQOS_SHIFT) +#define DPI_DMA_QOS_ULEV_SHIFT 4 +#define DPI_DMA_QOS_ULEV_MASK (0xF << DPI_DMA_QOS_ULEV_SHIFT) +#define DPI_DMA_QOS_UQOS_SHIFT 8 +#define DPI_DMA_QOS_UQOS_MASK (0xF << DPI_DMA_QOS_UQOS_SHIFT) +#define DPI_DMA_QOS_LLEV_SHIFT 12 +#define DPI_DMA_QOS_LLEV_MASK (0xF << DPI_DMA_QOS_LLEV_SHIFT) +#define DPI_DMA_QOS_LQOS_SHIFT 16 +#define DPI_DMA_QOS_LQOS_MASK (0xF << DPI_DMA_QOS_LQOS_SHIFT) + +// Panics +#define DPI_DMA_PANICS 0x38 +#define DPI_DMA_PANICS_UPPER_COUNT_SHIFT 0 +#define DPI_DMA_PANICS_UPPER_COUNT_MASK \ + (0x0000FFFF << DPI_DMA_PANICS_UPPER_COUNT_SHIFT) +#define DPI_DMA_PANICS_LOWER_COUNT_SHIFT 16 +#define DPI_DMA_PANICS_LOWER_COUNT_MASK \ + (0x0000FFFF << DPI_DMA_PANICS_LOWER_COUNT_SHIFT) + +// DMA Address Lower: +#define DPI_DMA_DMA_ADDR_L 0x10 + +// DMA Address Upper: +#define DPI_DMA_DMA_ADDR_H 0x40 + +// DMA stride +#define DPI_DMA_DMA_STRIDE 0x14 + +// Visible Area +#define DPI_DMA_VISIBLE_AREA 0x18 +#define DPI_DMA_VISIBLE_AREA_ROWSM1_SHIFT 0 +#define DPI_DMA_VISIBLE_AREA_ROWSM1_MASK (0x0FFF << DPI_DMA_VISIBLE_AREA_ROWSM1_SHIFT) +#define DPI_DMA_VISIBLE_AREA_COLSM1_SHIFT 16 +#define DPI_DMA_VISIBLE_AREA_COLSM1_MASK (0x0FFF << DPI_DMA_VISIBLE_AREA_COLSM1_SHIFT) + +// Sync width +#define DPI_DMA_SYNC_WIDTH 0x1C +#define DPI_DMA_SYNC_WIDTH_ROWSM1_SHIFT 0 +#define DPI_DMA_SYNC_WIDTH_ROWSM1_MASK (0x0FFF << DPI_DMA_SYNC_WIDTH_ROWSM1_SHIFT) +#define DPI_DMA_SYNC_WIDTH_COLSM1_SHIFT 16 +#define DPI_DMA_SYNC_WIDTH_COLSM1_MASK (0x0FFF << DPI_DMA_SYNC_WIDTH_COLSM1_SHIFT) + +// Back porch +#define DPI_DMA_BACK_PORCH 0x20 +#define DPI_DMA_BACK_PORCH_ROWSM1_SHIFT 0 +#define DPI_DMA_BACK_PORCH_ROWSM1_MASK (0x0FFF << DPI_DMA_BACK_PORCH_ROWSM1_SHIFT) +#define DPI_DMA_BACK_PORCH_COLSM1_SHIFT 16 +#define DPI_DMA_BACK_PORCH_COLSM1_MASK (0x0FFF << DPI_DMA_BACK_PORCH_COLSM1_SHIFT) + +// Front porch +#define DPI_DMA_FRONT_PORCH 0x24 +#define DPI_DMA_FRONT_PORCH_ROWSM1_SHIFT 0 +#define DPI_DMA_FRONT_PORCH_ROWSM1_MASK (0x0FFF << DPI_DMA_FRONT_PORCH_ROWSM1_SHIFT) +#define DPI_DMA_FRONT_PORCH_COLSM1_SHIFT 16 +#define DPI_DMA_FRONT_PORCH_COLSM1_MASK (0x0FFF << DPI_DMA_FRONT_PORCH_COLSM1_SHIFT) + +// Input masks +#define DPI_DMA_IMASK 0x2C +#define DPI_DMA_IMASK_R_SHIFT 0 +#define DPI_DMA_IMASK_R_MASK (0x3FF << DPI_DMA_IMASK_R_SHIFT) +#define DPI_DMA_IMASK_G_SHIFT 10 +#define DPI_DMA_IMASK_G_MASK (0x3FF << DPI_DMA_IMASK_G_SHIFT) +#define DPI_DMA_IMASK_B_SHIFT 20 +#define DPI_DMA_IMASK_B_MASK (0x3FF << DPI_DMA_IMASK_B_SHIFT) + +// Output Masks +#define DPI_DMA_OMASK 0x30 +#define DPI_DMA_OMASK_R_SHIFT 0 +#define DPI_DMA_OMASK_R_MASK (0x3FF << DPI_DMA_OMASK_R_SHIFT) +#define DPI_DMA_OMASK_G_SHIFT 10 +#define DPI_DMA_OMASK_G_MASK (0x3FF << DPI_DMA_OMASK_G_SHIFT) +#define DPI_DMA_OMASK_B_SHIFT 20 +#define DPI_DMA_OMASK_B_MASK (0x3FF << DPI_DMA_OMASK_B_SHIFT) + +// Shifts +#define DPI_DMA_SHIFT 0x28 +#define DPI_DMA_SHIFT_IR_SHIFT 0 +#define DPI_DMA_SHIFT_IR_MASK (0x1F << DPI_DMA_SHIFT_IR_SHIFT) +#define DPI_DMA_SHIFT_IG_SHIFT 5 +#define DPI_DMA_SHIFT_IG_MASK (0x1F << DPI_DMA_SHIFT_IG_SHIFT) +#define DPI_DMA_SHIFT_IB_SHIFT 10 +#define DPI_DMA_SHIFT_IB_MASK (0x1F << DPI_DMA_SHIFT_IB_SHIFT) +#define DPI_DMA_SHIFT_OR_SHIFT 15 +#define DPI_DMA_SHIFT_OR_MASK (0x1F << DPI_DMA_SHIFT_OR_SHIFT) +#define DPI_DMA_SHIFT_OG_SHIFT 20 +#define DPI_DMA_SHIFT_OG_MASK (0x1F << DPI_DMA_SHIFT_OG_SHIFT) +#define DPI_DMA_SHIFT_OB_SHIFT 25 +#define DPI_DMA_SHIFT_OB_MASK (0x1F << DPI_DMA_SHIFT_OB_SHIFT) + +// Scaling +#define DPI_DMA_RGBSZ 0x34 +#define DPI_DMA_RGBSZ_BPP_SHIFT 16 +#define DPI_DMA_RGBSZ_BPP_MASK (0x3 << DPI_DMA_RGBSZ_BPP_SHIFT) +#define DPI_DMA_RGBSZ_R_SHIFT 0 +#define DPI_DMA_RGBSZ_R_MASK (0xF << DPI_DMA_RGBSZ_R_SHIFT) +#define DPI_DMA_RGBSZ_G_SHIFT 4 +#define DPI_DMA_RGBSZ_G_MASK (0xF << DPI_DMA_RGBSZ_G_SHIFT) +#define DPI_DMA_RGBSZ_B_SHIFT 8 +#define DPI_DMA_RGBSZ_B_MASK (0xF << DPI_DMA_RGBSZ_B_SHIFT) + +// Status +#define DPI_DMA_STATUS 0x3c + +#define BITS(field, val) FIELD_PREP((field ## _MASK), val) + +static unsigned int rp1dpi_hw_read(struct rp1_dpi *dpi, unsigned int reg) +{ + void __iomem *addr = dpi->hw_base[RP1DPI_HW_BLOCK_DPI] + reg; + + return readl(addr); +} + +static void rp1dpi_hw_write(struct rp1_dpi *dpi, unsigned int reg, unsigned int val) +{ + void __iomem *addr = dpi->hw_base[RP1DPI_HW_BLOCK_DPI] + reg; + + writel(val, addr); +} + +int rp1dpi_hw_busy(struct rp1_dpi *dpi) +{ + return (rp1dpi_hw_read(dpi, DPI_DMA_STATUS) & 0xF8F) ? 1 : 0; +} + +/* + * Table of supported input (in-memory/DMA) pixel formats. + * + * RP1 DPI describes RGB components in terms of their MS bit position, a 10-bit + * left-aligned bit-mask, and an optional right-shift-and-OR used for scaling. + * To make it easier to permute R, G and B components, we re-pack these fields + * into 32-bit code-words, which don't themselves correspond to any register. + */ + +#define RGB_CODE(scale, shift, mask) (((scale) << 24) | ((shift) << 16) | (mask)) +#define RGB_SCALE(c) ((c) >> 24) +#define RGB_SHIFT(c) (((c) >> 16) & 31) +#define RGB_MASK(c) ((c) & 0x3ff) + +struct rp1dpi_ipixfmt { + u32 format; /* DRM format code */ + u32 rgb_code[3]; /* (width&7), MS bit position, 10-bit mask */ + u32 bpp; /* Bytes per pixel minus one */ +}; + +static const struct rp1dpi_ipixfmt my_formats[] = { + { + .format = DRM_FORMAT_XRGB8888, + .rgb_code = { + RGB_CODE(0, 23, 0x3fc), + RGB_CODE(0, 15, 0x3fc), + RGB_CODE(0, 7, 0x3fc), + }, + .bpp = 3, + }, + { + .format = DRM_FORMAT_XBGR8888, + .rgb_code = { + RGB_CODE(0, 7, 0x3fc), + RGB_CODE(0, 15, 0x3fc), + RGB_CODE(0, 23, 0x3fc), + }, + .bpp = 3, + }, + { + .format = DRM_FORMAT_ARGB8888, + .rgb_code = { + RGB_CODE(0, 23, 0x3fc), + RGB_CODE(0, 15, 0x3fc), + RGB_CODE(0, 7, 0x3fc), + }, + .bpp = 3, + }, + { + .format = DRM_FORMAT_ABGR8888, + .rgb_code = { + RGB_CODE(0, 7, 0x3fc), + RGB_CODE(0, 15, 0x3fc), + RGB_CODE(0, 23, 0x3fc), + }, + .bpp = 3, + }, + { + .format = DRM_FORMAT_RGB888, + .rgb_code = { + RGB_CODE(0, 23, 0x3fc), + RGB_CODE(0, 15, 0x3fc), + RGB_CODE(0, 7, 0x3fc), + }, + .bpp = 2, + }, + { + .format = DRM_FORMAT_BGR888, + .rgb_code = { + RGB_CODE(0, 7, 0x3fc), + RGB_CODE(0, 15, 0x3fc), + RGB_CODE(0, 23, 0x3fc), + }, + .bpp = 2, + }, + { + .format = DRM_FORMAT_RGB565, + .rgb_code = { + RGB_CODE(5, 15, 0x3e0), + RGB_CODE(6, 10, 0x3f0), + RGB_CODE(5, 4, 0x3e0), + }, + .bpp = 1, + }, +}; + +#define IMASK_RGB(r, g, b) (FIELD_PREP_CONST(DPI_DMA_IMASK_R_MASK, r) | \ + FIELD_PREP_CONST(DPI_DMA_IMASK_G_MASK, g) | \ + FIELD_PREP_CONST(DPI_DMA_IMASK_B_MASK, b)) +#define OMASK_RGB(r, g, b) (FIELD_PREP_CONST(DPI_DMA_OMASK_R_MASK, r) | \ + FIELD_PREP_CONST(DPI_DMA_OMASK_G_MASK, g) | \ + FIELD_PREP_CONST(DPI_DMA_OMASK_B_MASK, b)) +#define ISHIFT_RGB(r, g, b) (FIELD_PREP_CONST(DPI_DMA_SHIFT_IR_MASK, r) | \ + FIELD_PREP_CONST(DPI_DMA_SHIFT_IG_MASK, g) | \ + FIELD_PREP_CONST(DPI_DMA_SHIFT_IB_MASK, b)) +#define OSHIFT_RGB(r, g, b) (FIELD_PREP_CONST(DPI_DMA_SHIFT_OR_MASK, r) | \ + FIELD_PREP_CONST(DPI_DMA_SHIFT_OG_MASK, g) | \ + FIELD_PREP_CONST(DPI_DMA_SHIFT_OB_MASK, b)) + +/* + * Function to update *shift with output positions, and return output RGB masks. + * By the time we get here, RGB order has been normalized to RGB (R most significant). + * Note that an internal bus is 30 bits wide: bits [21:20], [11:10], [1:0] are dropped. + * This makes the packed RGB5656 and RGB666 formats problematic, as colour components + * need to straddle the gaps; we mitigate this by hijacking input masks and scaling. + */ +static u32 set_output_format(u32 bus_format, u32 *shift, u32 *imask, u32 *rgbsz) +{ + switch (bus_format) { + case MEDIA_BUS_FMT_RGB565_1X16: + if (*shift == ISHIFT_RGB(15, 10, 4)) { + /* When framebuffer is RGB565, we can output RGB565 */ + *shift = ISHIFT_RGB(15, 7, 0) | OSHIFT_RGB(19, 9, 0); + *imask = IMASK_RGB(0x3fc, 0x3fc, 0); + *rgbsz &= DPI_DMA_RGBSZ_BPP_MASK; + return OMASK_RGB(0x3fc, 0x3fc, 0); + } + + /* due to a HW limitation, bit-depth is effectively RGB535 */ + *shift |= OSHIFT_RGB(19, 14, 6); + *imask &= IMASK_RGB(0x3e0, 0x380, 0x3e0); + *rgbsz = BITS(DPI_DMA_RGBSZ_G, 5) | (*rgbsz & DPI_DMA_RGBSZ_BPP_MASK); + return OMASK_RGB(0x3e0, 0x39c, 0x3e0); + + case MEDIA_BUS_FMT_RGB666_1X18: + case MEDIA_BUS_FMT_BGR666_1X18: + /* due to a HW limitation, bit-depth is effectively RGB444 */ + *shift |= OSHIFT_RGB(23, 15, 7); + *imask = IMASK_RGB(0x3c0, 0x3c0, 0x3c0); + *rgbsz = BITS(DPI_DMA_RGBSZ_R, 2) | (*rgbsz & DPI_DMA_RGBSZ_BPP_MASK); + return OMASK_RGB(0x330, 0x3c0, 0x3c0); + + case MEDIA_BUS_FMT_RGB888_1X24: + case MEDIA_BUS_FMT_BGR888_1X24: + case MEDIA_BUS_FMT_RGB101010_1X30: + /* The full 24 bits can be output. Note that RP1's internal wiring means + * that 8.8.8 to GPIO pads can share with 10.10.10 to the onboard VDAC. + */ + *shift |= OSHIFT_RGB(29, 19, 9); + return OMASK_RGB(0x3fc, 0x3fc, 0x3fc); + + case MEDIA_BUS_FMT_RGB565_1X24_CPADHI: + /* This should match Raspberry Pi legacy "mode 3" */ + *shift |= OSHIFT_RGB(26, 17, 6); + *rgbsz &= DPI_DMA_RGBSZ_BPP_MASK; + return OMASK_RGB(0x3e0, 0x3f0, 0x3e0); + + default: + /* RGB666_1x24_CPADHI, BGR666_1X24_CPADHI and "mode 4" formats */ + *shift |= OSHIFT_RGB(27, 17, 7); + *rgbsz &= DPI_DMA_RGBSZ_BPP_MASK; + return OMASK_RGB(0x3f0, 0x3f0, 0x3f0); + } +} + +#define BUS_FMT_IS_BGR(fmt) ( \ + ((fmt) == MEDIA_BUS_FMT_BGR666_1X18) || \ + ((fmt) == MEDIA_BUS_FMT_BGR666_1X24_CPADHI) || \ + ((fmt) == MEDIA_BUS_FMT_BGR888_1X24)) + +void rp1dpi_hw_setup(struct rp1_dpi *dpi, + u32 in_format, u32 bus_format, bool de_inv, + struct drm_display_mode const *mode) +{ + u32 shift, imask, omask, rgbsz, vctrl; + u32 rgb_code[3]; + int order, i; + + drm_info(&dpi->drm, + "in_fmt=\'%c%c%c%c\' bus_fmt=0x%x mode=%dx%d total=%dx%d%s %dkHz %cH%cV%cD%cC", + in_format, in_format >> 8, in_format >> 16, in_format >> 24, bus_format, + mode->hdisplay, mode->vdisplay, + mode->htotal, mode->vtotal, + (mode->flags & DRM_MODE_FLAG_INTERLACE) ? "i" : "", + mode->clock, + (mode->flags & DRM_MODE_FLAG_NHSYNC) ? '-' : '+', + (mode->flags & DRM_MODE_FLAG_NVSYNC) ? '-' : '+', + de_inv ? '-' : '+', + dpi->clk_inv ? '-' : '+'); + + /* Look up the input (in-memory) pixel format */ + for (i = 0; i < ARRAY_SIZE(my_formats); ++i) { + if (my_formats[i].format == in_format) + break; + } + if (i >= ARRAY_SIZE(my_formats)) { + pr_err("%s: bad input format\n", __func__); + i = ARRAY_SIZE(my_formats) - 1; + } + + /* + * Although these RGB orderings refer to the output (DPI bus) format, + * here we permute the *input* components. After this point, "Red" + * will be most significant (highest numbered GPIOs), regardless + * of rgb_order or bus_format. This simplifies later workarounds. + */ + order = dpi->rgb_order_override; + if (order == RP1DPI_ORDER_UNCHANGED) + order = BUS_FMT_IS_BGR(bus_format) ? RP1DPI_ORDER_BGR : RP1DPI_ORDER_RGB; + rgb_code[0] = my_formats[i].rgb_code[order & 3]; + rgb_code[1] = my_formats[i].rgb_code[(order >> 8) & 3]; + rgb_code[2] = my_formats[i].rgb_code[(order >> 16) & 3]; + rgbsz = FIELD_PREP(DPI_DMA_RGBSZ_BPP_MASK, my_formats[i].bpp) | + FIELD_PREP(DPI_DMA_RGBSZ_R_MASK, RGB_SCALE(rgb_code[0])) | + FIELD_PREP(DPI_DMA_RGBSZ_G_MASK, RGB_SCALE(rgb_code[1])) | + FIELD_PREP(DPI_DMA_RGBSZ_B_MASK, RGB_SCALE(rgb_code[2])); + shift = FIELD_PREP(DPI_DMA_SHIFT_IR_MASK, RGB_SHIFT(rgb_code[0])) | + FIELD_PREP(DPI_DMA_SHIFT_IG_MASK, RGB_SHIFT(rgb_code[1])) | + FIELD_PREP(DPI_DMA_SHIFT_IB_MASK, RGB_SHIFT(rgb_code[2])); + imask = FIELD_PREP(DPI_DMA_IMASK_R_MASK, RGB_MASK(rgb_code[0])) | + FIELD_PREP(DPI_DMA_IMASK_G_MASK, RGB_MASK(rgb_code[1])) | + FIELD_PREP(DPI_DMA_IMASK_B_MASK, RGB_MASK(rgb_code[2])); + omask = set_output_format(bus_format, &shift, &imask, &rgbsz); + + /* + * Configure all DPI/DMA block registers, except base address. + * DMA will not actually start until a FB base address is specified + * using rp1dpi_hw_update(). + */ + rp1dpi_hw_write(dpi, DPI_DMA_IMASK, imask); + rp1dpi_hw_write(dpi, DPI_DMA_OMASK, omask); + rp1dpi_hw_write(dpi, DPI_DMA_SHIFT, shift); + rp1dpi_hw_write(dpi, DPI_DMA_RGBSZ, rgbsz); + + rp1dpi_hw_write(dpi, DPI_DMA_QOS, + BITS(DPI_DMA_QOS_DQOS, 0x0) | + BITS(DPI_DMA_QOS_ULEV, 0xb) | + BITS(DPI_DMA_QOS_UQOS, 0x2) | + BITS(DPI_DMA_QOS_LLEV, 0x8) | + BITS(DPI_DMA_QOS_LQOS, 0x7)); + + if (!(mode->flags & DRM_MODE_FLAG_INTERLACE)) { + rp1dpi_hw_write(dpi, DPI_DMA_VISIBLE_AREA, + BITS(DPI_DMA_VISIBLE_AREA_ROWSM1, mode->vdisplay - 1) | + BITS(DPI_DMA_VISIBLE_AREA_COLSM1, mode->hdisplay - 1)); + + rp1dpi_hw_write(dpi, DPI_DMA_SYNC_WIDTH, + BITS(DPI_DMA_SYNC_WIDTH_ROWSM1, + mode->vsync_end - mode->vsync_start - 1) | + BITS(DPI_DMA_SYNC_WIDTH_COLSM1, + mode->hsync_end - mode->hsync_start - 1)); + + /* In these registers, "back porch" time includes sync width */ + rp1dpi_hw_write(dpi, DPI_DMA_BACK_PORCH, + BITS(DPI_DMA_BACK_PORCH_ROWSM1, + mode->vtotal - mode->vsync_start - 1) | + BITS(DPI_DMA_BACK_PORCH_COLSM1, + mode->htotal - mode->hsync_start - 1)); + + rp1dpi_hw_write(dpi, DPI_DMA_FRONT_PORCH, + BITS(DPI_DMA_FRONT_PORCH_ROWSM1, + mode->vsync_start - mode->vdisplay - 1) | + BITS(DPI_DMA_FRONT_PORCH_COLSM1, + mode->hsync_start - mode->hdisplay - 1)); + + vctrl = BITS(DPI_DMA_CONTROL_VSYNC_POL, !!(mode->flags & DRM_MODE_FLAG_NVSYNC)) | + BITS(DPI_DMA_CONTROL_VBP_EN, (mode->vtotal != mode->vsync_start)) | + BITS(DPI_DMA_CONTROL_VFP_EN, (mode->vsync_start != mode->vdisplay)) | + BITS(DPI_DMA_CONTROL_VSYNC_EN, (mode->vsync_end != mode->vsync_start)); + + dpi->interlaced = false; + } else { + /* + * Experimental interlace support + * + * RP1 DPI hardware wasn't designed to support interlace, but lets us change + * both the VFP line count and the next DMA address while running. That allows + * pixel data to be correctly timed for interlace, but VSYNC remains wrong. + * + * It is necessary to use external hardware (such as PIO) to regenerate VSYNC + * based on HSYNC, DE (which *must* both be mapped to GPIOs 1, 3 respectively). + * This driver includes a PIO program to do that, when DE is enabled. + * + * An alternative fixup is to synthesize CSYNC from HSYNC and modified-VSYNC. + * We don't implement that here, but to facilitate it, DPI's VSYNC is replaced + * by a "helper signal" that pulses low for 1 or 2 scan-lines, starting 2.0 or + * 2.5 scan-lines respectively before nominal VSYNC start. + */ + int vact = mode->vdisplay >> 1; /* visible lines per field. Can't do half-lines */ + int vtot0 = mode->vtotal >> 1; /* vtotal should always be odd when interlaced. */ + int vfp0 = (mode->vsync_start >= mode->vdisplay + 4) ? + ((mode->vsync_start - mode->vdisplay - 2) >> 1) : 1; + int vbp = max(0, vtot0 - vact - vfp0); + + rp1dpi_hw_write(dpi, DPI_DMA_VISIBLE_AREA, + BITS(DPI_DMA_VISIBLE_AREA_ROWSM1, vact - 1) | + BITS(DPI_DMA_VISIBLE_AREA_COLSM1, mode->hdisplay - 1)); + + rp1dpi_hw_write(dpi, DPI_DMA_SYNC_WIDTH, + BITS(DPI_DMA_SYNC_WIDTH_ROWSM1, vtot0 - 2) | + BITS(DPI_DMA_SYNC_WIDTH_COLSM1, + mode->hsync_end - mode->hsync_start - 1)); + + rp1dpi_hw_write(dpi, DPI_DMA_BACK_PORCH, + BITS(DPI_DMA_BACK_PORCH_ROWSM1, vbp - 1) | + BITS(DPI_DMA_BACK_PORCH_COLSM1, + mode->htotal - mode->hsync_start - 1)); + + dpi->shorter_front_porch = + BITS(DPI_DMA_FRONT_PORCH_ROWSM1, vfp0 - 1) | + BITS(DPI_DMA_FRONT_PORCH_COLSM1, + mode->hsync_start - mode->hdisplay - 1); + rp1dpi_hw_write(dpi, DPI_DMA_FRONT_PORCH, dpi->shorter_front_porch); + + vctrl = BITS(DPI_DMA_CONTROL_VSYNC_POL, 0) | + BITS(DPI_DMA_CONTROL_VBP_EN, (vbp > 0)) | + BITS(DPI_DMA_CONTROL_VFP_EN, 1) | + BITS(DPI_DMA_CONTROL_VSYNC_EN, 1); + + dpi->interlaced = true; + } + dpi->lower_field_flag = false; + dpi->last_dma_addr = 0; + + rp1dpi_hw_write(dpi, DPI_DMA_IRQ_FLAGS, -1); + rp1dpi_hw_vblank_ctrl(dpi, 1); + + i = rp1dpi_hw_busy(dpi); + if (i) + pr_warn("%s: Unexpectedly busy at start!", __func__); + + rp1dpi_hw_write(dpi, DPI_DMA_CONTROL, + vctrl | + BITS(DPI_DMA_CONTROL_ARM, !i) | + BITS(DPI_DMA_CONTROL_AUTO_REPEAT, 1) | + BITS(DPI_DMA_CONTROL_HIGH_WATER, 448) | + BITS(DPI_DMA_CONTROL_DEN_POL, de_inv) | + BITS(DPI_DMA_CONTROL_HSYNC_POL, !!(mode->flags & DRM_MODE_FLAG_NHSYNC)) | + BITS(DPI_DMA_CONTROL_HBP_EN, (mode->htotal != mode->hsync_end)) | + BITS(DPI_DMA_CONTROL_HFP_EN, (mode->hsync_start != mode->hdisplay)) | + BITS(DPI_DMA_CONTROL_HSYNC_EN, (mode->hsync_end != mode->hsync_start))); +} + +void rp1dpi_hw_update(struct rp1_dpi *dpi, dma_addr_t addr, u32 offset, u32 stride) +{ + unsigned long flags; + + spin_lock_irqsave(&dpi->hw_lock, flags); + + /* + * Update STRIDE, DMAH and DMAL only. When called after rp1dpi_hw_setup(), + * DMA starts immediately; if already running, the buffer will flip at + * the next vertical sync event. In interlaced mode, we need to adjust + * the address and stride to display only the current field, saving + * the original address (so it can be flipped for subsequent fields). + */ + addr += offset; + dpi->last_dma_addr = addr; + dpi->last_stride = stride; + if (dpi->interlaced) { + if (dpi->lower_field_flag) + addr += stride; + stride *= 2; + } + rp1dpi_hw_write(dpi, DPI_DMA_DMA_STRIDE, stride); + rp1dpi_hw_write(dpi, DPI_DMA_DMA_ADDR_H, addr >> 32); + rp1dpi_hw_write(dpi, DPI_DMA_DMA_ADDR_L, addr & 0xFFFFFFFFu); + + spin_unlock_irqrestore(&dpi->hw_lock, flags); +} + +void rp1dpi_hw_stop(struct rp1_dpi *dpi) +{ + u32 ctrl; + unsigned long flags; + + /* + * Stop DMA by turning off Auto-Repeat (and disable S/W field-flip), + * then wait up to 100ms for the current and any queued frame to end. + * (There is a "force drain" flag, but it can leave DPI in a broken + * state which prevents it from restarting; it's safer to wait.) + */ + spin_lock_irqsave(&dpi->hw_lock, flags); + dpi->last_dma_addr = 0; + reinit_completion(&dpi->finished); + ctrl = rp1dpi_hw_read(dpi, DPI_DMA_CONTROL); + ctrl &= ~(DPI_DMA_CONTROL_ARM_MASK | DPI_DMA_CONTROL_AUTO_REPEAT_MASK); + rp1dpi_hw_write(dpi, DPI_DMA_CONTROL, ctrl); + spin_unlock_irqrestore(&dpi->hw_lock, flags); + + if (!wait_for_completion_timeout(&dpi->finished, HZ / 10)) + drm_err(&dpi->drm, "%s: timed out waiting for idle\n", __func__); + rp1dpi_hw_write(dpi, DPI_DMA_IRQ_EN, 0); +} + +void rp1dpi_hw_vblank_ctrl(struct rp1_dpi *dpi, int enable) +{ + rp1dpi_hw_write(dpi, DPI_DMA_IRQ_EN, + BITS(DPI_DMA_IRQ_EN_AFIFO_EMPTY, 1) | + BITS(DPI_DMA_IRQ_EN_UNDERFLOW, 1) | + BITS(DPI_DMA_IRQ_EN_DMA_READY, !!enable) | + BITS(DPI_DMA_IRQ_EN_MATCH, dpi->interlaced) | + BITS(DPI_DMA_IRQ_EN_MATCH_LINE, 32)); +} + +irqreturn_t rp1dpi_hw_isr(int irq, void *dev) +{ + struct rp1_dpi *dpi = dev; + u32 u = rp1dpi_hw_read(dpi, DPI_DMA_IRQ_FLAGS); + + if (u) { + rp1dpi_hw_write(dpi, DPI_DMA_IRQ_FLAGS, u); + if (dpi) { + if (u & DPI_DMA_IRQ_FLAGS_UNDERFLOW_MASK) + drm_err_ratelimited(&dpi->drm, + "Underflow! (panics=0x%08x)\n", + rp1dpi_hw_read(dpi, DPI_DMA_PANICS)); + if (u & DPI_DMA_IRQ_FLAGS_DMA_READY_MASK) + drm_crtc_handle_vblank(&dpi->pipe.crtc); + if (u & DPI_DMA_IRQ_FLAGS_AFIFO_EMPTY_MASK) + complete(&dpi->finished); + + /* + * Added for interlace support: We use this mid-frame interrupt to + * wobble the VFP between fields, re-submitting the next-buffer address + * with an offset to display the opposite field. NB: rp1dpi_hw_update() + * may be called at any time, before or after, so locking is needed. + * H/W Auto-update is no longer needed (unless this IRQ is lost). + */ + if ((u & DPI_DMA_IRQ_FLAGS_MATCH_MASK) && dpi->interlaced) { + unsigned long flags; + dma_addr_t a; + + spin_lock_irqsave(&dpi->hw_lock, flags); + dpi->lower_field_flag = !dpi->lower_field_flag; + rp1dpi_hw_write(dpi, DPI_DMA_FRONT_PORCH, + dpi->shorter_front_porch + + BITS(DPI_DMA_FRONT_PORCH_ROWSM1, + dpi->lower_field_flag)); + a = dpi->last_dma_addr; + if (a) { + if (dpi->lower_field_flag) + a += dpi->last_stride; + rp1dpi_hw_write(dpi, DPI_DMA_DMA_ADDR_H, a >> 32); + rp1dpi_hw_write(dpi, DPI_DMA_DMA_ADDR_L, a & 0xFFFFFFFFu); + } + spin_unlock_irqrestore(&dpi->hw_lock, flags); + } + } + } + + return u ? IRQ_HANDLED : IRQ_NONE; +} diff --git a/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_pio.c b/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_pio.c new file mode 100644 index 00000000000000..f636f4cbcc1f7c --- /dev/null +++ b/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_pio.c @@ -0,0 +1,257 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * PIO code for Raspberry Pi RP1 DPI driver + * + * Copyright (c) 2024 Raspberry Pi Limited. + */ + +/* + * Use PIO to fix up VSYNC for interlaced modes. + * + * For this to work we *require* DPI's pinctrl to enable DE on GPIO1. + * PIO can then snoop on HSYNC and DE pins to generate corrected VSYNC. + * + * Note that corrected VSYNC outputs will not be synchronous to DPICLK, + * will lag HSYNC by about 30ns and may suffer up to 5ns of jitter. + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/of.h> +#include <linux/pinctrl/consumer.h> +#include <linux/platform_device.h> +#include <drm/drm_print.h> + +#include "rp1_dpi.h" + +#if IS_REACHABLE(CONFIG_RP1_PIO) + +#include <linux/pio_rp1.h> + +/* + * Start a PIO SM to generate two interrupts for each horizontal line. + * The first occurs shortly before the middle of the line. The second + * is timed such that after receiving the IRQ plus 1 extra delay cycle, + * another SM's output will align with the next HSYNC within -5ns .. +10ns. + * To achieve this, we need an accurate measure of (cycles per line) / 2. + * + * Measured GPIO -> { wait gpio ; irq set | irq wait ; sideset } -> GPIO + * round-trip delay is about 8 cycles when pins are not heavily loaded. + * + * PIO code ; Notional time % 1000-cycle period + * -------- ; --------------------------------- + * 0: wait 1 gpio 3 ; 0.. 8 + * 1: mov x, y ; 8.. 9 + * 2: jmp x--, 2 ; 9..499 (Y should be T/2 - 11) + * 3: irq set 1 ; 499..500 + * 4: mov x, y [8] ; 500..509 + * 5: jmp x--, 5 ; 509..999 + * 6: irq set 1 ; 999..1000 + */ + +static int rp1dpi_pio_start_timer_both(struct rp1_dpi *dpi, u32 flags, u32 tc) +{ + static const u16 instructions[2][7] = { + { 0x2083, 0xa022, 0x0042, 0xc001, 0xa822, 0x0045, 0xc001 }, /* +H */ + { 0x2003, 0xa022, 0x0042, 0xc001, 0xa822, 0x0045, 0xc001 }, /* -H */ + }; + const struct pio_program prog = { + .instructions = instructions[(flags & DRM_MODE_FLAG_NHSYNC) ? 1 : 0], + .length = ARRAY_SIZE(instructions[0]), + .origin = -1 + }; + int offset, sm; + + sm = pio_claim_unused_sm(dpi->pio, true); + if (sm < 0) + return -EBUSY; + + offset = pio_add_program(dpi->pio, &prog); + if (offset == PIO_ORIGIN_ANY) { + pio_sm_unclaim(dpi->pio, sm); + return -EBUSY; + } + + pio_sm_config cfg = pio_get_default_sm_config(); + + pio_sm_set_enabled(dpi->pio, sm, false); + sm_config_set_wrap(&cfg, offset, offset + 6); + pio_sm_init(dpi->pio, sm, offset, &cfg); + + pio_sm_put(dpi->pio, sm, tc - 11); + pio_sm_exec(dpi->pio, sm, pio_encode_pull(false, false)); + pio_sm_exec(dpi->pio, sm, pio_encode_out(pio_y, 32)); + pio_sm_set_enabled(dpi->pio, sm, true); + + return 0; +} + +/* + * Snoop on DE, HSYNC to count half-lines in the vertical blanking interval + * to determine when the VSYNC pulse should start and finish. Then, at a + * suitable moment (which should be an odd number of half-lines since the + * last active line), sample DE again to detect field phase. + * + * This version assumes VFP length is within 2..256 half-lines for any field + * (one half-line delay is needed to sample DE; we always wait for the next + * half-line boundary to improve VSync start accuracy) and VBP in 1..255. + */ + +static int rp1dpi_pio_vsync_ilace(struct rp1_dpi *dpi, + struct drm_display_mode const *mode) +{ + u16 instructions[] = { /* This is mutable */ + // .wrap_target + 0xa0e6, // 0: mov osr, isr side 0 ; top: rewind parameters + 0x2081, // 1: wait 1 gpio, 1 side 0 ; main: while (!DE) wait; + 0x2783, // 2: wait 1 gpio, 3 side 0 [7] ; do { @HSync + 0xc041, // 3: irq clear 1 side 0 ; flush stale IRQs + 0x20c1, // 4: wait 1 irq, 1 side 0 ; @midline + 0x00c2, // 5: jmp pin, 2 side 0 ; } while (DE) + 0x0007, // 6: jmp 7 side 0 ; <modify for -DE fixup> + 0x6028, // 7: out x, 8 side 0 ; x = VFPlen - 2 + 0x20c1, // 8: wait 1 irq, 1 side 0 ; do { @halfline + 0x0048, // 9: jmp x--, 8 side 0 ; } while (x--) + 0xb022, // 10: mov x, y side 1 ; VSYNC=1; x = VSyncLen + 0x30c1, // 11: wait 1 irq, 1 side 1 ; VSYNC=1; do { @halfline + 0x104b, // 12: jmp x--, 11 side 1 ; VSYNC=1; } while (x--) + 0x6028, // 13: out x, 8 side 0 ; VSYNC=0; x = VBPLen - 1 + 0x20c1, // 14: wait 1 irq, 1 side 0 ; do { @halfline + 0x004e, // 15: jmp x--, 14 side 0 ; } while (x--) + 0x00c0, // 16: jmp pin, 0 side 0 ; if (DE) reset phase + 0x0012, // 17: jmp 18 side 0 ; <modify for -DE fixup> + 0x00e1, // 18: jmp !osre, 1 side 0 ; if (!phase) goto main + // .wrap ; goto top + }; + struct pio_program prog = { + .instructions = instructions, + .length = ARRAY_SIZE(instructions), + .origin = -1 + }; + pio_sm_config cfg = pio_get_default_sm_config(); + unsigned int i, offset; + u32 tc, vfp, vbp; + u32 sysclk = clock_get_hz(clk_sys); + int sm = pio_claim_unused_sm(dpi->pio, true); + + if (sm < 0) + return -EBUSY; + + /* + * Compute half-line time constant (round uppish so that VSync should + * switch never > 5ns before DPICLK, while defeating roundoff errors) + * and start the timer SM. + */ + tc = (u32)clk_get_rate(dpi->clocks[RP1DPI_CLK_DPI]); + if (!tc) + tc = 1000u * mode->clock; + tc = ((u64)mode->htotal * (u64)sysclk + ((7ul * tc) >> 2)) / + (u64)(2ul * tc); + if (rp1dpi_pio_start_timer_both(dpi, mode->flags, tc) < 0) { + pio_sm_unclaim(dpi->pio, sm); + return -EBUSY; + } + + /* Adapt program code according to DE and Sync polarity; configure program */ + pio_sm_set_enabled(dpi->pio, sm, false); + if (dpi->de_inv) { + instructions[1] ^= 0x0080; + instructions[5] = 0x00c7; + instructions[6] = 0x0002; + instructions[16] = 0x00d2; + instructions[17] = 0x0000; + } + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + instructions[2] ^= 0x0080; + if (mode->flags & DRM_MODE_FLAG_NVSYNC) { + for (i = 0; i < ARRAY_SIZE(instructions); i++) + instructions[i] ^= 0x1000; + } + offset = pio_add_program(dpi->pio, &prog); + if (offset == PIO_ORIGIN_ANY) + return -EBUSY; + + /* Configure pins and SM */ + dpi->pio_stole_gpio2 = true; + sm_config_set_wrap(&cfg, offset, offset + ARRAY_SIZE(instructions) - 1); + sm_config_set_sideset(&cfg, 1, false, false); + sm_config_set_sideset_pins(&cfg, 2); + pio_gpio_init(dpi->pio, 2); + sm_config_set_jmp_pin(&cfg, 1); /* "DE" is always GPIO1 */ + pio_sm_init(dpi->pio, sm, offset, &cfg); + pio_sm_set_consecutive_pindirs(dpi->pio, sm, 2, 1, true); + + /* Compute vertical times, remembering how we rounded vdisplay, vtotal */ + vfp = mode->vsync_start - (mode->vdisplay & ~1); + vbp = (mode->vtotal | 1) - mode->vsync_end; + if (vfp > 256) { + vbp += vfp - 256; + vfp = 256; + } else if (vfp < 3) { + vbp = (vbp > 3 - vfp) ? (vbp - 3 + vfp) : 1; + vfp = 3; + } + + pio_sm_put(dpi->pio, sm, + (vfp - 2) + ((vbp - 1) << 8) + + ((vfp - 3) << 16) + (vbp << 24)); + pio_sm_put(dpi->pio, sm, mode->vsync_end - mode->vsync_start - 1); + pio_sm_exec(dpi->pio, sm, pio_encode_pull(false, false)); + pio_sm_exec(dpi->pio, sm, pio_encode_out(pio_y, 32)); + pio_sm_exec(dpi->pio, sm, pio_encode_in(pio_y, 32)); + pio_sm_exec(dpi->pio, sm, pio_encode_pull(false, false)); + pio_sm_exec(dpi->pio, sm, pio_encode_out(pio_y, 32)); + pio_sm_set_enabled(dpi->pio, sm, true); + + return 0; +} + +int rp1dpi_pio_start(struct rp1_dpi *dpi, const struct drm_display_mode *mode) +{ + int r; + + if (!(mode->flags & DRM_MODE_FLAG_INTERLACE) || !dpi->gpio1_used) + return 0; + + if (dpi->pio) + pio_close(dpi->pio); + + dpi->pio = pio_open(); + if (IS_ERR(dpi->pio)) { + drm_err(&dpi->drm, "Could not open PIO\n"); + dpi->pio = NULL; + return -ENODEV; + } + + r = rp1dpi_pio_vsync_ilace(dpi, mode); + if (r) { + drm_err(&dpi->drm, "Failed to initialize PIO\n"); + rp1dpi_pio_stop(dpi); + } + + return r; +} + +void rp1dpi_pio_stop(struct rp1_dpi *dpi) +{ + if (dpi->pio) { + if (dpi->pio_stole_gpio2) + pio_gpio_set_function(dpi->pio, 2, GPIO_FUNC_FSEL1); + pio_close(dpi->pio); + dpi->pio_stole_gpio2 = false; + dpi->pio = NULL; + } +} + +#else /* !IS_REACHABLE(CONFIG_RP1_PIO) */ + +int rp1dpi_pio_start(struct rp1_dpi *dpi, const struct drm_display_mode *mode) +{ + return -ENODEV; +} + +void rp1dpi_pio_stop(struct rp1_dpi *dpi) +{ +} + +#endif diff --git a/drivers/gpu/drm/rp1/rp1-dsi/Kconfig b/drivers/gpu/drm/rp1/rp1-dsi/Kconfig new file mode 100644 index 00000000000000..80c57bc4879255 --- /dev/null +++ b/drivers/gpu/drm/rp1/rp1-dsi/Kconfig @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DRM_RP1_DSI + tristate "DRM Support for RP1 DSI" + depends on DRM && MFD_RP1 + select DRM_GEM_DMA_HELPER + select DRM_KMS_HELPER + select DRM_MIPI_DSI + select DRM_VRAM_HELPER + select DRM_TTM + select DRM_TTM_HELPER + select GENERIC_PHY + select GENERIC_PHY_MIPI_DPHY + help + Choose this option to enable DSI display on RP1 diff --git a/drivers/gpu/drm/rp1/rp1-dsi/Makefile b/drivers/gpu/drm/rp1/rp1-dsi/Makefile new file mode 100644 index 00000000000000..1a9672c7bda027 --- /dev/null +++ b/drivers/gpu/drm/rp1/rp1-dsi/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only + +drm-rp1-dsi-y := rp1_dsi.o rp1_dsi_dma.o rp1_dsi_dsi.o + +obj-$(CONFIG_DRM_RP1_DSI) += drm-rp1-dsi.o diff --git a/drivers/gpu/drm/rp1/rp1-dsi/rp1_dsi.c b/drivers/gpu/drm/rp1/rp1-dsi/rp1_dsi.c new file mode 100644 index 00000000000000..4e2d1ea2b990f3 --- /dev/null +++ b/drivers/gpu/drm/rp1/rp1-dsi/rp1_dsi.c @@ -0,0 +1,540 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * DRM Driver for DSI output on Raspberry Pi RP1 + * + * Copyright (c) 2023 Raspberry Pi Limited. + */ + +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/phy/phy-mipi-dphy.h> +#include <linux/string.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_drv.h> +#include <drm/drm_encoder.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_fbdev_ttm.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_gem.h> +#include <drm/drm_gem_atomic_helper.h> +#include <drm/drm_gem_dma_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_managed.h> +#include <drm/drm_modeset_helper_vtables.h> +#include <drm/drm_of.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_simple_kms_helper.h> +#include <drm/drm_vblank.h> + +#include "rp1_dsi.h" + +static inline struct rp1_dsi * +bridge_to_rp1_dsi(struct drm_bridge *bridge) +{ + return container_of(bridge, struct rp1_dsi, bridge); +} + +static void rp1_dsi_bridge_pre_enable(struct drm_bridge *bridge, + struct drm_bridge_state *old_state) +{ + struct rp1_dsi *dsi = bridge_to_rp1_dsi(bridge); + + rp1dsi_dsi_setup(dsi, &dsi->pipe.crtc.state->adjusted_mode); + dsi->dsi_running = true; +} + +static void rp1_dsi_bridge_enable(struct drm_bridge *bridge, + struct drm_bridge_state *old_state) +{ +} + +static void rp1_dsi_bridge_disable(struct drm_bridge *bridge, + struct drm_bridge_state *state) +{ +} + +static void rp1_dsi_bridge_post_disable(struct drm_bridge *bridge, + struct drm_bridge_state *state) +{ + struct rp1_dsi *dsi = bridge_to_rp1_dsi(bridge); + + if (dsi->dsi_running) { + rp1dsi_dsi_stop(dsi); + dsi->dsi_running = false; + } +} + +static int rp1_dsi_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct rp1_dsi *dsi = bridge_to_rp1_dsi(bridge); + + /* Attach the panel or bridge to the dsi bridge */ + return drm_bridge_attach(bridge->encoder, dsi->out_bridge, + &dsi->bridge, flags); + return 0; +} + +static const struct drm_bridge_funcs rp1_dsi_bridge_funcs = { + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_reset = drm_atomic_helper_bridge_reset, + .atomic_pre_enable = rp1_dsi_bridge_pre_enable, + .atomic_enable = rp1_dsi_bridge_enable, + .atomic_disable = rp1_dsi_bridge_disable, + .atomic_post_disable = rp1_dsi_bridge_post_disable, + .attach = rp1_dsi_bridge_attach, +}; + +static void rp1dsi_pipe_update(struct drm_simple_display_pipe *pipe, + struct drm_plane_state *old_state) +{ + struct drm_pending_vblank_event *event; + unsigned long flags; + struct drm_framebuffer *fb = pipe->plane.state->fb; + struct rp1_dsi *dsi = pipe->crtc.dev->dev_private; + struct drm_gem_object *gem = fb ? drm_gem_fb_get_obj(fb, 0) : NULL; + struct drm_gem_dma_object *dma_obj = gem ? to_drm_gem_dma_obj(gem) : NULL; + bool can_update = fb && dma_obj && dsi && dsi->pipe_enabled; + + /* (Re-)start DSI,DMA where required; and update FB address */ + if (can_update) { + if (!dsi->dma_running || fb->format->format != dsi->cur_fmt) { + if (dsi->dma_running && fb->format->format != dsi->cur_fmt) { + rp1dsi_dma_stop(dsi); + dsi->dma_running = false; + } + if (!dsi->dma_running) { + rp1dsi_dma_setup(dsi, + fb->format->format, dsi->display_format, + &pipe->crtc.state->adjusted_mode); + dsi->dma_running = true; + } + dsi->cur_fmt = fb->format->format; + drm_crtc_vblank_on(&pipe->crtc); + } + rp1dsi_dma_update(dsi, dma_obj->dma_addr, fb->offsets[0], fb->pitches[0]); + } + + /* Arm VBLANK event (or call it immediately in some error cases) */ + spin_lock_irqsave(&pipe->crtc.dev->event_lock, flags); + event = pipe->crtc.state->event; + if (event) { + pipe->crtc.state->event = NULL; + if (can_update && drm_crtc_vblank_get(&pipe->crtc) == 0) + drm_crtc_arm_vblank_event(&pipe->crtc, event); + else + drm_crtc_send_vblank_event(&pipe->crtc, event); + } + spin_unlock_irqrestore(&pipe->crtc.dev->event_lock, flags); +} + +static inline struct rp1_dsi * +encoder_to_rp1_dsi(struct drm_encoder *encoder) +{ + struct drm_simple_display_pipe *pipe = + container_of(encoder, struct drm_simple_display_pipe, encoder); + return container_of(pipe, struct rp1_dsi, pipe); +} + +static void rp1dsi_encoder_enable(struct drm_encoder *encoder) +{ + struct rp1_dsi *dsi = encoder_to_rp1_dsi(encoder); + + /* Put DSI into video mode before starting video */ + rp1dsi_dsi_set_cmdmode(dsi, 0); + + /* Start DMA -> DPI */ + dsi->pipe_enabled = true; + dsi->cur_fmt = 0xdeadbeef; + rp1dsi_pipe_update(&dsi->pipe, 0); +} + +static void rp1dsi_encoder_disable(struct drm_encoder *encoder) +{ + struct rp1_dsi *dsi = encoder_to_rp1_dsi(encoder); + + drm_crtc_vblank_off(&dsi->pipe.crtc); + if (dsi->dma_running) { + rp1dsi_dma_stop(dsi); + dsi->dma_running = false; + } + dsi->pipe_enabled = false; + + /* Return to command mode after stopping video */ + rp1dsi_dsi_set_cmdmode(dsi, 1); +} + +static const struct drm_encoder_helper_funcs rp1_dsi_encoder_funcs = { + .enable = rp1dsi_encoder_enable, + .disable = rp1dsi_encoder_disable, +}; + +static void rp1dsi_pipe_enable(struct drm_simple_display_pipe *pipe, + struct drm_crtc_state *crtc_state, + struct drm_plane_state *plane_state) +{ +} + +static void rp1dsi_pipe_disable(struct drm_simple_display_pipe *pipe) +{ +} + +static int rp1dsi_pipe_enable_vblank(struct drm_simple_display_pipe *pipe) +{ + struct rp1_dsi *dsi = pipe->crtc.dev->dev_private; + + if (dsi) + rp1dsi_dma_vblank_ctrl(dsi, 1); + + return 0; +} + +static void rp1dsi_pipe_disable_vblank(struct drm_simple_display_pipe *pipe) +{ + struct rp1_dsi *dsi = pipe->crtc.dev->dev_private; + + if (dsi) + rp1dsi_dma_vblank_ctrl(dsi, 0); +} + +static const struct drm_simple_display_pipe_funcs rp1dsi_pipe_funcs = { + .enable = rp1dsi_pipe_enable, + .update = rp1dsi_pipe_update, + .disable = rp1dsi_pipe_disable, + .enable_vblank = rp1dsi_pipe_enable_vblank, + .disable_vblank = rp1dsi_pipe_disable_vblank, +}; + +static const struct drm_mode_config_funcs rp1dsi_mode_funcs = { + .fb_create = drm_gem_fb_create, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +static const u32 rp1dsi_formats[] = { + DRM_FORMAT_XRGB8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_RGB888, + DRM_FORMAT_BGR888, + DRM_FORMAT_RGB565 +}; + +static void rp1dsi_stopall(struct drm_device *drm) +{ + if (drm->dev_private) { + struct rp1_dsi *dsi = drm->dev_private; + + if (dsi->dma_running || rp1dsi_dma_busy(dsi)) { + rp1dsi_dma_stop(dsi); + dsi->dma_running = false; + } + if (dsi->dsi_running) { + rp1dsi_dsi_stop(dsi); + dsi->dsi_running = false; + } + if (dsi->clocks[RP1DSI_CLOCK_CFG]) + clk_disable_unprepare(dsi->clocks[RP1DSI_CLOCK_CFG]); + } +} + +DEFINE_DRM_GEM_DMA_FOPS(rp1dsi_fops); + +static struct drm_driver rp1dsi_driver = { + .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC, + .fops = &rp1dsi_fops, + .name = "drm-rp1-dsi", + .desc = "drm-rp1-dsi", + .date = "0", + .major = 1, + .minor = 0, + DRM_GEM_DMA_DRIVER_OPS, + .release = rp1dsi_stopall, +}; + +static int rp1dsi_bind(struct rp1_dsi *dsi) +{ + struct platform_device *pdev = dsi->pdev; + struct drm_device *drm = dsi->drm; + int ret; + + dsi->out_bridge = drmm_of_get_bridge(drm, pdev->dev.of_node, 0, 0); + if (IS_ERR(dsi->out_bridge)) + return PTR_ERR(dsi->out_bridge); + + ret = drmm_mode_config_init(drm); + if (ret) + goto rtn; + + drm->mode_config.max_width = 4096; + drm->mode_config.max_height = 4096; + drm->mode_config.preferred_depth = 32; + drm->mode_config.prefer_shadow = 0; + drm->mode_config.quirk_addfb_prefer_host_byte_order = true; + drm->mode_config.funcs = &rp1dsi_mode_funcs; + drm_vblank_init(drm, 1); + + ret = drm_simple_display_pipe_init(drm, + &dsi->pipe, + &rp1dsi_pipe_funcs, + rp1dsi_formats, + ARRAY_SIZE(rp1dsi_formats), + NULL, NULL); + if (ret) + goto rtn; + + /* We need slightly more complex encoder handling (enabling/disabling + * video mode), so add encoder helper functions. + */ + drm_encoder_helper_add(&dsi->pipe.encoder, &rp1_dsi_encoder_funcs); + + ret = drm_simple_display_pipe_attach_bridge(&dsi->pipe, &dsi->bridge); + if (ret) + goto rtn; + + drm_bridge_add(&dsi->bridge); + + drm_mode_config_reset(drm); + + if (dsi->clocks[RP1DSI_CLOCK_CFG]) + clk_prepare_enable(dsi->clocks[RP1DSI_CLOCK_CFG]); + + ret = drm_dev_register(drm, 0); + + if (ret == 0) + drm_fbdev_ttm_setup(drm, 32); + +rtn: + if (ret) + dev_err(&pdev->dev, "%s returned %d\n", __func__, ret); + else + dev_info(&pdev->dev, "%s succeeded", __func__); + + return ret; +} + +static void rp1dsi_unbind(struct rp1_dsi *dsi) +{ + struct drm_device *drm = dsi->drm; + + rp1dsi_stopall(drm); + drm_dev_unregister(drm); + drm_atomic_helper_shutdown(drm); +} + +static int rp1dsi_host_attach(struct mipi_dsi_host *host, struct mipi_dsi_device *dsi_dev) +{ + struct rp1_dsi *dsi = container_of(host, struct rp1_dsi, dsi_host); + + dev_info(&dsi->pdev->dev, "%s: Attach DSI device name=%s channel=%d lanes=%d format=%d flags=0x%lx hs_rate=%lu lp_rate=%lu", + __func__, dsi_dev->name, dsi_dev->channel, dsi_dev->lanes, + dsi_dev->format, dsi_dev->mode_flags, dsi_dev->hs_rate, + dsi_dev->lp_rate); + dsi->vc = dsi_dev->channel & 3; + dsi->lanes = dsi_dev->lanes; + + switch (dsi_dev->format) { + case MIPI_DSI_FMT_RGB666: + case MIPI_DSI_FMT_RGB666_PACKED: + case MIPI_DSI_FMT_RGB565: + case MIPI_DSI_FMT_RGB888: + break; + default: + return -EINVAL; + } + dsi->display_format = dsi_dev->format; + dsi->display_flags = dsi_dev->mode_flags; + dsi->display_hs_rate = dsi_dev->hs_rate; + dsi->display_lp_rate = dsi_dev->lp_rate; + + /* + * Previously, we added a separate component to handle panel/bridge + * discovery and DRM registration, but now it's just a function call. + * The downstream/attaching device should deal with -EPROBE_DEFER + */ + return rp1dsi_bind(dsi); +} + +static int rp1dsi_host_detach(struct mipi_dsi_host *host, struct mipi_dsi_device *dsi_dev) +{ + struct rp1_dsi *dsi = container_of(host, struct rp1_dsi, dsi_host); + + /* + * Unregister the DRM driver. + * TODO: Check we are cleaning up correctly and not doing things multiple times! + */ + rp1dsi_unbind(dsi); + return 0; +} + +static ssize_t rp1dsi_host_transfer(struct mipi_dsi_host *host, const struct mipi_dsi_msg *msg) +{ + struct rp1_dsi *dsi = container_of(host, struct rp1_dsi, dsi_host); + struct mipi_dsi_packet packet; + int ret = 0; + + /* Write */ + ret = mipi_dsi_create_packet(&packet, msg); + if (ret) { + dev_err(dsi->drm->dev, "RP1DSI: failed to create packet: %d\n", ret); + return ret; + } + + rp1dsi_dsi_send(dsi, *(u32 *)(&packet.header), + packet.payload_length, packet.payload, + !!(msg->flags & MIPI_DSI_MSG_USE_LPM), + !!(msg->flags & MIPI_DSI_MSG_REQ_ACK)); + + /* Optional read back */ + if (msg->rx_len && msg->rx_buf) + ret = rp1dsi_dsi_recv(dsi, msg->rx_len, msg->rx_buf); + + return (ssize_t)ret; +} + +static const struct mipi_dsi_host_ops rp1dsi_mipi_dsi_host_ops = { + .attach = rp1dsi_host_attach, + .detach = rp1dsi_host_detach, + .transfer = rp1dsi_host_transfer +}; + +static int rp1dsi_platform_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct drm_device *drm; + struct rp1_dsi *dsi; + int i, ret; + + drm = drm_dev_alloc(&rp1dsi_driver, dev); + if (IS_ERR(drm)) { + ret = PTR_ERR(drm); + return ret; + } + dsi = drmm_kzalloc(drm, sizeof(*dsi), GFP_KERNEL); + if (!dsi) { + ret = -ENOMEM; + goto err_free_drm; + } + init_completion(&dsi->finished); + dsi->drm = drm; + dsi->pdev = pdev; + drm->dev_private = dsi; + platform_set_drvdata(pdev, drm); + + dsi->bridge.funcs = &rp1_dsi_bridge_funcs; + dsi->bridge.of_node = dev->of_node; + dsi->bridge.type = DRM_MODE_CONNECTOR_DSI; + + /* Safe default values for DSI mode */ + dsi->lanes = 1; + dsi->display_format = MIPI_DSI_FMT_RGB888; + dsi->display_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_LPM; + + /* Hardware resources */ + for (i = 0; i < RP1DSI_NUM_CLOCKS; i++) { + static const char * const myclocknames[RP1DSI_NUM_CLOCKS] = { + "cfgclk", "dpiclk", "byteclk", "refclk", "pllsys" + }; + dsi->clocks[i] = devm_clk_get(dev, myclocknames[i]); + if (IS_ERR(dsi->clocks[i])) { + ret = PTR_ERR(dsi->clocks[i]); + dev_err(dev, "Error getting clocks[%d]\n", i); + goto err_free_drm; + } + } + + for (i = 0; i < RP1DSI_NUM_HW_BLOCKS; i++) { + dsi->hw_base[i] = + devm_ioremap_resource(dev, + platform_get_resource(dsi->pdev, + IORESOURCE_MEM, + i)); + if (IS_ERR(dsi->hw_base[i])) { + ret = PTR_ERR(dsi->hw_base[i]); + dev_err(dev, "Error memory mapping regs[%d]\n", i); + goto err_free_drm; + } + } + ret = platform_get_irq(dsi->pdev, 0); + if (ret > 0) + ret = devm_request_irq(dev, ret, rp1dsi_dma_isr, + IRQF_SHARED, "rp1-dsi", dsi); + if (ret) { + dev_err(dev, "Unable to request interrupt\n"); + ret = -EINVAL; + goto err_free_drm; + } + rp1dsi_mipicfg_setup(dsi); + dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)); + + /* Create the MIPI DSI Host and wait for the panel/bridge to attach to it */ + dsi->dsi_host.ops = &rp1dsi_mipi_dsi_host_ops; + dsi->dsi_host.dev = dev; + ret = mipi_dsi_host_register(&dsi->dsi_host); + if (ret) + goto err_free_drm; + + return ret; + +err_free_drm: + dev_err(dev, "%s fail %d\n", __func__, ret); + drm_dev_put(drm); + return ret; +} + +static void rp1dsi_platform_remove(struct platform_device *pdev) +{ + struct drm_device *drm = platform_get_drvdata(pdev); + struct rp1_dsi *dsi = drm->dev_private; + + mipi_dsi_host_unregister(&dsi->dsi_host); +} + +static void rp1dsi_platform_shutdown(struct platform_device *pdev) +{ + struct drm_device *drm = platform_get_drvdata(pdev); + + rp1dsi_stopall(drm); +} + +static const struct of_device_id rp1dsi_of_match[] = { + { + .compatible = "raspberrypi,rp1dsi", + }, + { /* sentinel */ }, +}; + +MODULE_DEVICE_TABLE(of, rp1dsi_of_match); + +static struct platform_driver rp1dsi_platform_driver = { + .probe = rp1dsi_platform_probe, + .remove = rp1dsi_platform_remove, + .shutdown = rp1dsi_platform_shutdown, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = rp1dsi_of_match, + }, +}; + +module_platform_driver(rp1dsi_platform_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("MIPI DSI driver for Raspberry Pi RP1"); +MODULE_AUTHOR("Nick Hollinghurst"); diff --git a/drivers/gpu/drm/rp1/rp1-dsi/rp1_dsi.h b/drivers/gpu/drm/rp1/rp1-dsi/rp1_dsi.h new file mode 100644 index 00000000000000..468325ed2480ec --- /dev/null +++ b/drivers/gpu/drm/rp1/rp1-dsi/rp1_dsi.h @@ -0,0 +1,96 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * DRM Driver for DSI output on Raspberry Pi RP1 + * + * Copyright (c) 2023 Raspberry Pi Limited. + */ +#ifndef _RP1_DSI_H_ +#define _RP1_DSI_H_ + +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/types.h> + +#include <drm/drm_bridge.h> +#include <drm/drm_device.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_simple_kms_helper.h> + +#define MODULE_NAME "drm-rp1-dsi" +#define DRIVER_NAME "drm-rp1-dsi" + +/* ---------------------------------------------------------------------- */ + +#define RP1DSI_HW_BLOCK_DMA 0 +#define RP1DSI_HW_BLOCK_DSI 1 +#define RP1DSI_HW_BLOCK_CFG 2 +#define RP1DSI_NUM_HW_BLOCKS 3 + +#define RP1DSI_CLOCK_CFG 0 +#define RP1DSI_CLOCK_DPI 1 +#define RP1DSI_CLOCK_BYTE 2 +#define RP1DSI_CLOCK_REF 3 +#define RP1DSI_CLOCK_PLLSYS 4 +#define RP1DSI_NUM_CLOCKS 5 + +/* ---------------------------------------------------------------------- */ + +struct rp1_dsi { + /* DRM and platform device pointers */ + struct drm_device *drm; + struct platform_device *pdev; + + /* Framework and helper objects */ + struct drm_simple_display_pipe pipe; + struct drm_bridge bridge; + struct drm_bridge *out_bridge; + struct mipi_dsi_host dsi_host; + + /* Clocks. We need DPI clock; the others are frequency references */ + struct clk *clocks[RP1DSI_NUM_CLOCKS]; + + /* Block (DSI DMA, DSI Host) base addresses, and current state */ + void __iomem *hw_base[RP1DSI_NUM_HW_BLOCKS]; + u32 cur_fmt; + bool dsi_running, dma_running, pipe_enabled; + struct completion finished; + + /* Attached display parameters (from mipi_dsi_device) */ + unsigned long display_flags, display_hs_rate, display_lp_rate; + enum mipi_dsi_pixel_format display_format; + u8 vc; + u8 lanes; + + /* DPHY */ + u8 hsfreq_index; +}; + +/* ---------------------------------------------------------------------- */ +/* Functions to control the DSI/DPI/DMA block */ + +void rp1dsi_dma_setup(struct rp1_dsi *dsi, + u32 in_format, enum mipi_dsi_pixel_format out_format, + struct drm_display_mode const *mode); +void rp1dsi_dma_update(struct rp1_dsi *dsi, dma_addr_t addr, u32 offset, u32 stride); +void rp1dsi_dma_stop(struct rp1_dsi *dsi); +int rp1dsi_dma_busy(struct rp1_dsi *dsi); +irqreturn_t rp1dsi_dma_isr(int irq, void *dev); +void rp1dsi_dma_vblank_ctrl(struct rp1_dsi *dsi, int enable); + +/* ---------------------------------------------------------------------- */ +/* Functions to control the MIPICFG block and check RP1 platform */ + +void rp1dsi_mipicfg_setup(struct rp1_dsi *dsi); + +/* ---------------------------------------------------------------------- */ +/* Functions to control the SNPS D-PHY and DSI block setup */ + +void rp1dsi_dsi_setup(struct rp1_dsi *dsi, struct drm_display_mode const *mode); +void rp1dsi_dsi_send(struct rp1_dsi *dsi, u32 header, int len, const u8 *buf, + bool use_lpm, bool req_ack); +int rp1dsi_dsi_recv(struct rp1_dsi *dsi, int len, u8 *buf); +void rp1dsi_dsi_set_cmdmode(struct rp1_dsi *dsi, int cmd_mode); +void rp1dsi_dsi_stop(struct rp1_dsi *dsi); + +#endif + diff --git a/drivers/gpu/drm/rp1/rp1-dsi/rp1_dsi_dma.c b/drivers/gpu/drm/rp1/rp1-dsi/rp1_dsi_dma.c new file mode 100644 index 00000000000000..86fa351562b4e9 --- /dev/null +++ b/drivers/gpu/drm/rp1/rp1-dsi/rp1_dsi_dma.c @@ -0,0 +1,455 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * DRM Driver for DSI output on Raspberry Pi RP1 + * + * Copyright (c) 2023 Raspberry Pi Limited. + */ + +#include <linux/interrupt.h> +#include <linux/platform_device.h> + +#include <drm/drm_fourcc.h> +#include <drm/drm_print.h> +#include <drm/drm_vblank.h> + +#include "rp1_dsi.h" + +// --- DPI DMA REGISTERS (derived from Argon firmware, via RP1 drivers/mipi, with corrections) --- + +// Control +#define DPI_DMA_CONTROL 0x0 +#define DPI_DMA_CONTROL_ARM_SHIFT 0 +#define DPI_DMA_CONTROL_ARM_MASK BIT(DPI_DMA_CONTROL_ARM_SHIFT) +#define DPI_DMA_CONTROL_ALIGN16_SHIFT 2 +#define DPI_DMA_CONTROL_ALIGN16_MASK BIT(DPI_DMA_CONTROL_ALIGN16_SHIFT) +#define DPI_DMA_CONTROL_AUTO_REPEAT_SHIFT 1 +#define DPI_DMA_CONTROL_AUTO_REPEAT_MASK BIT(DPI_DMA_CONTROL_AUTO_REPEAT_SHIFT) +#define DPI_DMA_CONTROL_HIGH_WATER_SHIFT 3 +#define DPI_DMA_CONTROL_HIGH_WATER_MASK (0x1FF << DPI_DMA_CONTROL_HIGH_WATER_SHIFT) +#define DPI_DMA_CONTROL_DEN_POL_SHIFT 12 +#define DPI_DMA_CONTROL_DEN_POL_MASK BIT(DPI_DMA_CONTROL_DEN_POL_SHIFT) +#define DPI_DMA_CONTROL_HSYNC_POL_SHIFT 13 +#define DPI_DMA_CONTROL_HSYNC_POL_MASK BIT(DPI_DMA_CONTROL_HSYNC_POL_SHIFT) +#define DPI_DMA_CONTROL_VSYNC_POL_SHIFT 14 +#define DPI_DMA_CONTROL_VSYNC_POL_MASK BIT(DPI_DMA_CONTROL_VSYNC_POL_SHIFT) +#define DPI_DMA_CONTROL_COLORM_SHIFT 15 +#define DPI_DMA_CONTROL_COLORM_MASK BIT(DPI_DMA_CONTROL_COLORM_SHIFT) +#define DPI_DMA_CONTROL_SHUTDN_SHIFT 16 +#define DPI_DMA_CONTROL_SHUTDN_MASK BIT(DPI_DMA_CONTROL_SHUTDN_SHIFT) +#define DPI_DMA_CONTROL_HBP_EN_SHIFT 17 +#define DPI_DMA_CONTROL_HBP_EN_MASK BIT(DPI_DMA_CONTROL_HBP_EN_SHIFT) +#define DPI_DMA_CONTROL_HFP_EN_SHIFT 18 +#define DPI_DMA_CONTROL_HFP_EN_MASK BIT(DPI_DMA_CONTROL_HFP_EN_SHIFT) +#define DPI_DMA_CONTROL_VBP_EN_SHIFT 19 +#define DPI_DMA_CONTROL_VBP_EN_MASK BIT(DPI_DMA_CONTROL_VBP_EN_SHIFT) +#define DPI_DMA_CONTROL_VFP_EN_SHIFT 20 +#define DPI_DMA_CONTROL_VFP_EN_MASK BIT(DPI_DMA_CONTROL_VFP_EN_SHIFT) +#define DPI_DMA_CONTROL_HSYNC_EN_SHIFT 21 +#define DPI_DMA_CONTROL_HSYNC_EN_MASK BIT(DPI_DMA_CONTROL_HSYNC_EN_SHIFT) +#define DPI_DMA_CONTROL_VSYNC_EN_SHIFT 22 +#define DPI_DMA_CONTROL_VSYNC_EN_MASK BIT(DPI_DMA_CONTROL_VSYNC_EN_SHIFT) +#define DPI_DMA_CONTROL_FORCE_IMMED_SHIFT 23 +#define DPI_DMA_CONTROL_FORCE_IMMED_MASK BIT(DPI_DMA_CONTROL_FORCE_IMMED_SHIFT) +#define DPI_DMA_CONTROL_FORCE_DRAIN_SHIFT 24 +#define DPI_DMA_CONTROL_FORCE_DRAIN_MASK BIT(DPI_DMA_CONTROL_FORCE_DRAIN_SHIFT) +#define DPI_DMA_CONTROL_FORCE_EMPTY_SHIFT 25 +#define DPI_DMA_CONTROL_FORCE_EMPTY_MASK BIT(DPI_DMA_CONTROL_FORCE_EMPTY_SHIFT) + +// IRQ_ENABLES +#define DPI_DMA_IRQ_EN 0x04 +#define DPI_DMA_IRQ_EN_DMA_READY_SHIFT 0 +#define DPI_DMA_IRQ_EN_DMA_READY_MASK BIT(DPI_DMA_IRQ_EN_DMA_READY_SHIFT) +#define DPI_DMA_IRQ_EN_UNDERFLOW_SHIFT 1 +#define DPI_DMA_IRQ_EN_UNDERFLOW_MASK BIT(DPI_DMA_IRQ_EN_UNDERFLOW_SHIFT) +#define DPI_DMA_IRQ_EN_FRAME_START_SHIFT 2 +#define DPI_DMA_IRQ_EN_FRAME_START_MASK BIT(DPI_DMA_IRQ_EN_FRAME_START_SHIFT) +#define DPI_DMA_IRQ_EN_AFIFO_EMPTY_SHIFT 3 +#define DPI_DMA_IRQ_EN_AFIFO_EMPTY_MASK BIT(DPI_DMA_IRQ_EN_AFIFO_EMPTY_SHIFT) +#define DPI_DMA_IRQ_EN_TE_SHIFT 4 +#define DPI_DMA_IRQ_EN_TE_MASK BIT(DPI_DMA_IRQ_EN_TE_SHIFT) +#define DPI_DMA_IRQ_EN_ERROR_SHIFT 5 +#define DPI_DMA_IRQ_EN_ERROR_MASK BIT(DPI_DMA_IRQ_EN_ERROR_SHIFT) +#define DPI_DMA_IRQ_EN_MATCH_SHIFT 6 +#define DPI_DMA_IRQ_EN_MATCH_MASK BIT(DPI_DMA_IRQ_EN_MATCH_SHIFT) +#define DPI_DMA_IRQ_EN_MATCH_LINE_SHIFT 16 +#define DPI_DMA_IRQ_EN_MATCH_LINE_MASK (0xFFF << DPI_DMA_IRQ_EN_MATCH_LINE_SHIFT) + +// IRQ_FLAGS +#define DPI_DMA_IRQ_FLAGS 0x08 +#define DPI_DMA_IRQ_FLAGS_DMA_READY_SHIFT 0 +#define DPI_DMA_IRQ_FLAGS_DMA_READY_MASK BIT(DPI_DMA_IRQ_FLAGS_DMA_READY_SHIFT) +#define DPI_DMA_IRQ_FLAGS_UNDERFLOW_SHIFT 1 +#define DPI_DMA_IRQ_FLAGS_UNDERFLOW_MASK BIT(DPI_DMA_IRQ_FLAGS_UNDERFLOW_SHIFT) +#define DPI_DMA_IRQ_FLAGS_FRAME_START_SHIFT 2 +#define DPI_DMA_IRQ_FLAGS_FRAME_START_MASK BIT(DPI_DMA_IRQ_FLAGS_FRAME_START_SHIFT) +#define DPI_DMA_IRQ_FLAGS_AFIFO_EMPTY_SHIFT 3 +#define DPI_DMA_IRQ_FLAGS_AFIFO_EMPTY_MASK BIT(DPI_DMA_IRQ_FLAGS_AFIFO_EMPTY_SHIFT) +#define DPI_DMA_IRQ_FLAGS_TE_SHIFT 4 +#define DPI_DMA_IRQ_FLAGS_TE_MASK BIT(DPI_DMA_IRQ_FLAGS_TE_SHIFT) +#define DPI_DMA_IRQ_FLAGS_ERROR_SHIFT 5 +#define DPI_DMA_IRQ_FLAGS_ERROR_MASK BIT(DPI_DMA_IRQ_FLAGS_ERROR_SHIFT) +#define DPI_DMA_IRQ_FLAGS_MATCH_SHIFT 6 +#define DPI_DMA_IRQ_FLAGS_MATCH_MASK BIT(DPI_DMA_IRQ_FLAGS_MATCH_SHIFT) + +// QOS +#define DPI_DMA_QOS 0xC +#define DPI_DMA_QOS_DQOS_SHIFT 0 +#define DPI_DMA_QOS_DQOS_MASK (0xF << DPI_DMA_QOS_DQOS_SHIFT) +#define DPI_DMA_QOS_ULEV_SHIFT 4 +#define DPI_DMA_QOS_ULEV_MASK (0xF << DPI_DMA_QOS_ULEV_SHIFT) +#define DPI_DMA_QOS_UQOS_SHIFT 8 +#define DPI_DMA_QOS_UQOS_MASK (0xF << DPI_DMA_QOS_UQOS_SHIFT) +#define DPI_DMA_QOS_LLEV_SHIFT 12 +#define DPI_DMA_QOS_LLEV_MASK (0xF << DPI_DMA_QOS_LLEV_SHIFT) +#define DPI_DMA_QOS_LQOS_SHIFT 16 +#define DPI_DMA_QOS_LQOS_MASK (0xF << DPI_DMA_QOS_LQOS_SHIFT) + +// Panics +#define DPI_DMA_PANICS 0x38 +#define DPI_DMA_PANICS_UPPER_COUNT_SHIFT 0 +#define DPI_DMA_PANICS_UPPER_COUNT_MASK \ + (0x0000FFFF << DPI_DMA_PANICS_UPPER_COUNT_SHIFT) +#define DPI_DMA_PANICS_LOWER_COUNT_SHIFT 16 +#define DPI_DMA_PANICS_LOWER_COUNT_MASK \ + (0x0000FFFF << DPI_DMA_PANICS_LOWER_COUNT_SHIFT) + +// DMA Address Lower: +#define DPI_DMA_DMA_ADDR_L 0x10 + +// DMA Address Upper: +#define DPI_DMA_DMA_ADDR_H 0x40 + +// DMA stride +#define DPI_DMA_DMA_STRIDE 0x14 + +// Visible Area +#define DPI_DMA_VISIBLE_AREA 0x18 +#define DPI_DMA_VISIBLE_AREA_ROWSM1_SHIFT 0 +#define DPI_DMA_VISIBLE_AREA_ROWSM1_MASK (0x0FFF << DPI_DMA_VISIBLE_AREA_ROWSM1_SHIFT) +#define DPI_DMA_VISIBLE_AREA_COLSM1_SHIFT 16 +#define DPI_DMA_VISIBLE_AREA_COLSM1_MASK (0x0FFF << DPI_DMA_VISIBLE_AREA_COLSM1_SHIFT) + +// Sync width +#define DPI_DMA_SYNC_WIDTH 0x1C +#define DPI_DMA_SYNC_WIDTH_ROWSM1_SHIFT 0 +#define DPI_DMA_SYNC_WIDTH_ROWSM1_MASK (0x0FFF << DPI_DMA_SYNC_WIDTH_ROWSM1_SHIFT) +#define DPI_DMA_SYNC_WIDTH_COLSM1_SHIFT 16 +#define DPI_DMA_SYNC_WIDTH_COLSM1_MASK (0x0FFF << DPI_DMA_SYNC_WIDTH_COLSM1_SHIFT) + +// Back porch +#define DPI_DMA_BACK_PORCH 0x20 +#define DPI_DMA_BACK_PORCH_ROWSM1_SHIFT 0 +#define DPI_DMA_BACK_PORCH_ROWSM1_MASK (0x0FFF << DPI_DMA_BACK_PORCH_ROWSM1_SHIFT) +#define DPI_DMA_BACK_PORCH_COLSM1_SHIFT 16 +#define DPI_DMA_BACK_PORCH_COLSM1_MASK (0x0FFF << DPI_DMA_BACK_PORCH_COLSM1_SHIFT) + +// Front porch +#define DPI_DMA_FRONT_PORCH 0x24 +#define DPI_DMA_FRONT_PORCH_ROWSM1_SHIFT 0 +#define DPI_DMA_FRONT_PORCH_ROWSM1_MASK (0x0FFF << DPI_DMA_FRONT_PORCH_ROWSM1_SHIFT) +#define DPI_DMA_FRONT_PORCH_COLSM1_SHIFT 16 +#define DPI_DMA_FRONT_PORCH_COLSM1_MASK (0x0FFF << DPI_DMA_FRONT_PORCH_COLSM1_SHIFT) + +// Input masks +#define DPI_DMA_IMASK 0x2C +#define DPI_DMA_IMASK_R_SHIFT 0 +#define DPI_DMA_IMASK_R_MASK (0x3FF << DPI_DMA_IMASK_R_SHIFT) +#define DPI_DMA_IMASK_G_SHIFT 10 +#define DPI_DMA_IMASK_G_MASK (0x3FF << DPI_DMA_IMASK_G_SHIFT) +#define DPI_DMA_IMASK_B_SHIFT 20 +#define DPI_DMA_IMASK_B_MASK (0x3FF << DPI_DMA_IMASK_B_SHIFT) + +// Output Masks +#define DPI_DMA_OMASK 0x30 +#define DPI_DMA_OMASK_R_SHIFT 0 +#define DPI_DMA_OMASK_R_MASK (0x3FF << DPI_DMA_OMASK_R_SHIFT) +#define DPI_DMA_OMASK_G_SHIFT 10 +#define DPI_DMA_OMASK_G_MASK (0x3FF << DPI_DMA_OMASK_G_SHIFT) +#define DPI_DMA_OMASK_B_SHIFT 20 +#define DPI_DMA_OMASK_B_MASK (0x3FF << DPI_DMA_OMASK_B_SHIFT) + +// Shifts +#define DPI_DMA_SHIFT 0x28 +#define DPI_DMA_SHIFT_IR_SHIFT 0 +#define DPI_DMA_SHIFT_IR_MASK (0x1F << DPI_DMA_SHIFT_IR_SHIFT) +#define DPI_DMA_SHIFT_IG_SHIFT 5 +#define DPI_DMA_SHIFT_IG_MASK (0x1F << DPI_DMA_SHIFT_IG_SHIFT) +#define DPI_DMA_SHIFT_IB_SHIFT 10 +#define DPI_DMA_SHIFT_IB_MASK (0x1F << DPI_DMA_SHIFT_IB_SHIFT) +#define DPI_DMA_SHIFT_OR_SHIFT 15 +#define DPI_DMA_SHIFT_OR_MASK (0x1F << DPI_DMA_SHIFT_OR_SHIFT) +#define DPI_DMA_SHIFT_OG_SHIFT 20 +#define DPI_DMA_SHIFT_OG_MASK (0x1F << DPI_DMA_SHIFT_OG_SHIFT) +#define DPI_DMA_SHIFT_OB_SHIFT 25 +#define DPI_DMA_SHIFT_OB_MASK (0x1F << DPI_DMA_SHIFT_OB_SHIFT) + +// Scaling +#define DPI_DMA_RGBSZ 0x34 +#define DPI_DMA_RGBSZ_BPP_SHIFT 16 +#define DPI_DMA_RGBSZ_BPP_MASK (0x3 << DPI_DMA_RGBSZ_BPP_SHIFT) +#define DPI_DMA_RGBSZ_R_SHIFT 0 +#define DPI_DMA_RGBSZ_R_MASK (0xF << DPI_DMA_RGBSZ_R_SHIFT) +#define DPI_DMA_RGBSZ_G_SHIFT 4 +#define DPI_DMA_RGBSZ_G_MASK (0xF << DPI_DMA_RGBSZ_G_SHIFT) +#define DPI_DMA_RGBSZ_B_SHIFT 8 +#define DPI_DMA_RGBSZ_B_MASK (0xF << DPI_DMA_RGBSZ_B_SHIFT) + +// Status +#define DPI_DMA_STATUS 0x3c + +#define BITS(field, val) (((val) << (field ## _SHIFT)) & (field ## _MASK)) + +static unsigned int rp1dsi_dma_read(struct rp1_dsi *dsi, unsigned int reg) +{ + void __iomem *addr = dsi->hw_base[RP1DSI_HW_BLOCK_DMA] + reg; + + return readl(addr); +} + +static void rp1dsi_dma_write(struct rp1_dsi *dsi, unsigned int reg, unsigned int val) +{ + void __iomem *addr = dsi->hw_base[RP1DSI_HW_BLOCK_DMA] + reg; + + writel(val, addr); +} + +int rp1dsi_dma_busy(struct rp1_dsi *dsi) +{ + return (rp1dsi_dma_read(dsi, DPI_DMA_STATUS) & 0xF8F) ? 1 : 0; +} + +/* Table of supported input (in-memory/DMA) pixel formats. */ +struct rp1dsi_ipixfmt { + u32 format; /* DRM format code */ + u32 mask; /* RGB masks (10 bits each, left justified) */ + u32 shift; /* RGB MSB positions in the memory word */ + u32 rgbsz; /* Shifts used for scaling; also (BPP/8-1) */ +}; + +#define IMASK_RGB(r, g, b) (BITS(DPI_DMA_IMASK_R, r) | \ + BITS(DPI_DMA_IMASK_G, g) | \ + BITS(DPI_DMA_IMASK_B, b)) +#define ISHIFT_RGB(r, g, b) (BITS(DPI_DMA_SHIFT_IR, r) | \ + BITS(DPI_DMA_SHIFT_IG, g) | \ + BITS(DPI_DMA_SHIFT_IB, b)) + +static const struct rp1dsi_ipixfmt my_formats[] = { + { + .format = DRM_FORMAT_XRGB8888, + .mask = IMASK_RGB(0x3fc, 0x3fc, 0x3fc), + .shift = ISHIFT_RGB(23, 15, 7), + .rgbsz = BITS(DPI_DMA_RGBSZ_BPP, 3), + }, + { + .format = DRM_FORMAT_XBGR8888, + .mask = IMASK_RGB(0x3fc, 0x3fc, 0x3fc), + .shift = ISHIFT_RGB(7, 15, 23), + .rgbsz = BITS(DPI_DMA_RGBSZ_BPP, 3), + }, + { + .format = DRM_FORMAT_ARGB8888, + .mask = IMASK_RGB(0x3fc, 0x3fc, 0x3fc), + .shift = ISHIFT_RGB(23, 15, 7), + .rgbsz = BITS(DPI_DMA_RGBSZ_BPP, 3), + }, + { + .format = DRM_FORMAT_ABGR8888, + .mask = IMASK_RGB(0x3fc, 0x3fc, 0x3fc), + .shift = ISHIFT_RGB(7, 15, 23), + .rgbsz = BITS(DPI_DMA_RGBSZ_BPP, 3), + }, + { + .format = DRM_FORMAT_RGB888, + .mask = IMASK_RGB(0x3fc, 0x3fc, 0x3fc), + .shift = ISHIFT_RGB(23, 15, 7), + .rgbsz = BITS(DPI_DMA_RGBSZ_BPP, 2), + }, + { + .format = DRM_FORMAT_BGR888, + .mask = IMASK_RGB(0x3fc, 0x3fc, 0x3fc), + .shift = ISHIFT_RGB(7, 15, 23), + .rgbsz = BITS(DPI_DMA_RGBSZ_BPP, 2), + }, + { + .format = DRM_FORMAT_RGB565, + .mask = IMASK_RGB(0x3e0, 0x3f0, 0x3e0), + .shift = ISHIFT_RGB(15, 10, 4), + .rgbsz = BITS(DPI_DMA_RGBSZ_R, 5) | BITS(DPI_DMA_RGBSZ_G, 6) | + BITS(DPI_DMA_RGBSZ_B, 5) | BITS(DPI_DMA_RGBSZ_BPP, 1), + } +}; + +/* Choose the internal on-the-bus DPI format as expected by DSI Host. */ +static u32 get_omask_oshift(enum mipi_dsi_pixel_format fmt, u32 *oshift) +{ + switch (fmt) { + case MIPI_DSI_FMT_RGB565: + *oshift = BITS(DPI_DMA_SHIFT_OR, 15) | + BITS(DPI_DMA_SHIFT_OG, 10) | + BITS(DPI_DMA_SHIFT_OB, 4); + return BITS(DPI_DMA_OMASK_R, 0x3e0) | + BITS(DPI_DMA_OMASK_G, 0x3f0) | + BITS(DPI_DMA_OMASK_B, 0x3e0); + case MIPI_DSI_FMT_RGB666_PACKED: + *oshift = BITS(DPI_DMA_SHIFT_OR, 17) | + BITS(DPI_DMA_SHIFT_OG, 11) | + BITS(DPI_DMA_SHIFT_OB, 5); + return BITS(DPI_DMA_OMASK_R, 0x3f0) | + BITS(DPI_DMA_OMASK_G, 0x3f0) | + BITS(DPI_DMA_OMASK_B, 0x3f0); + case MIPI_DSI_FMT_RGB666: + *oshift = BITS(DPI_DMA_SHIFT_OR, 21) | + BITS(DPI_DMA_SHIFT_OG, 13) | + BITS(DPI_DMA_SHIFT_OB, 5); + return BITS(DPI_DMA_OMASK_R, 0x3f0) | + BITS(DPI_DMA_OMASK_G, 0x3f0) | + BITS(DPI_DMA_OMASK_B, 0x3f0); + default: + *oshift = BITS(DPI_DMA_SHIFT_OR, 23) | + BITS(DPI_DMA_SHIFT_OG, 15) | + BITS(DPI_DMA_SHIFT_OB, 7); + return BITS(DPI_DMA_OMASK_R, 0x3fc) | + BITS(DPI_DMA_OMASK_G, 0x3fc) | + BITS(DPI_DMA_OMASK_B, 0x3fc); + } +} + +void rp1dsi_dma_setup(struct rp1_dsi *dsi, + u32 in_format, enum mipi_dsi_pixel_format out_format, + struct drm_display_mode const *mode) +{ + u32 oshift; + int i; + + /* + * Configure all DSI/DPI/DMA block registers, except base address. + * DMA will not actually start until a FB base address is specified + * using rp1dsi_dma_update(). + */ + + rp1dsi_dma_write(dsi, DPI_DMA_VISIBLE_AREA, + BITS(DPI_DMA_VISIBLE_AREA_ROWSM1, mode->vdisplay - 1) | + BITS(DPI_DMA_VISIBLE_AREA_COLSM1, mode->hdisplay - 1)); + + rp1dsi_dma_write(dsi, DPI_DMA_SYNC_WIDTH, + BITS(DPI_DMA_SYNC_WIDTH_ROWSM1, mode->vsync_end - mode->vsync_start - 1) | + BITS(DPI_DMA_SYNC_WIDTH_COLSM1, mode->hsync_end - mode->hsync_start - 1)); + + /* In the DPIDMA registers, "back porch" time includes sync width */ + rp1dsi_dma_write(dsi, DPI_DMA_BACK_PORCH, + BITS(DPI_DMA_BACK_PORCH_ROWSM1, mode->vtotal - mode->vsync_start - 1) | + BITS(DPI_DMA_BACK_PORCH_COLSM1, mode->htotal - mode->hsync_start - 1)); + + rp1dsi_dma_write(dsi, DPI_DMA_FRONT_PORCH, + BITS(DPI_DMA_FRONT_PORCH_ROWSM1, mode->vsync_start - mode->vdisplay - 1) | + BITS(DPI_DMA_FRONT_PORCH_COLSM1, mode->hsync_start - mode->hdisplay - 1)); + + /* Input to output pixel format conversion */ + for (i = 0; i < ARRAY_SIZE(my_formats); ++i) { + if (my_formats[i].format == in_format) + break; + } + if (i >= ARRAY_SIZE(my_formats)) { + drm_err(dsi->drm, "%s: bad input format\n", __func__); + i = 0; + } + rp1dsi_dma_write(dsi, DPI_DMA_IMASK, my_formats[i].mask); + rp1dsi_dma_write(dsi, DPI_DMA_OMASK, get_omask_oshift(out_format, &oshift)); + rp1dsi_dma_write(dsi, DPI_DMA_SHIFT, my_formats[i].shift | oshift); + if (out_format == MIPI_DSI_FMT_RGB888) + rp1dsi_dma_write(dsi, DPI_DMA_RGBSZ, my_formats[i].rgbsz); + else + rp1dsi_dma_write(dsi, DPI_DMA_RGBSZ, my_formats[i].rgbsz & DPI_DMA_RGBSZ_BPP_MASK); + + rp1dsi_dma_write(dsi, DPI_DMA_QOS, + BITS(DPI_DMA_QOS_DQOS, 0x0) | + BITS(DPI_DMA_QOS_ULEV, 0xb) | + BITS(DPI_DMA_QOS_UQOS, 0x2) | + BITS(DPI_DMA_QOS_LLEV, 0x8) | + BITS(DPI_DMA_QOS_LQOS, 0x7)); + + rp1dsi_dma_write(dsi, DPI_DMA_IRQ_FLAGS, -1); + rp1dsi_dma_vblank_ctrl(dsi, 1); + + i = rp1dsi_dma_busy(dsi); + if (i) + drm_err(dsi->drm, "RP1DSI: Unexpectedly busy at start!"); + + rp1dsi_dma_write(dsi, DPI_DMA_CONTROL, + BITS(DPI_DMA_CONTROL_ARM, (i == 0)) | + BITS(DPI_DMA_CONTROL_AUTO_REPEAT, 1) | + BITS(DPI_DMA_CONTROL_HIGH_WATER, 448) | + BITS(DPI_DMA_CONTROL_DEN_POL, 0) | + BITS(DPI_DMA_CONTROL_HSYNC_POL, 0) | + BITS(DPI_DMA_CONTROL_VSYNC_POL, 0) | + BITS(DPI_DMA_CONTROL_COLORM, 0) | + BITS(DPI_DMA_CONTROL_SHUTDN, 0) | + BITS(DPI_DMA_CONTROL_HBP_EN, 1) | + BITS(DPI_DMA_CONTROL_HFP_EN, 1) | + BITS(DPI_DMA_CONTROL_VBP_EN, 1) | + BITS(DPI_DMA_CONTROL_VFP_EN, 1) | + BITS(DPI_DMA_CONTROL_HSYNC_EN, 1) | + BITS(DPI_DMA_CONTROL_VSYNC_EN, 1)); +} + +void rp1dsi_dma_update(struct rp1_dsi *dsi, dma_addr_t addr, u32 offset, u32 stride) +{ + /* + * Update STRIDE, DMAH and DMAL only. When called after rp1dsi_dma_setup(), + * DMA starts immediately; if already running, the buffer will flip at + * the next vertical sync event. + */ + u64 a = addr + offset; + + rp1dsi_dma_write(dsi, DPI_DMA_DMA_STRIDE, stride); + rp1dsi_dma_write(dsi, DPI_DMA_DMA_ADDR_H, a >> 32); + rp1dsi_dma_write(dsi, DPI_DMA_DMA_ADDR_L, a & 0xFFFFFFFFu); +} + +void rp1dsi_dma_stop(struct rp1_dsi *dsi) +{ + /* + * Stop DMA by turning off the Auto-Repeat flag, and wait up to 100ms for + * the current and any queued frame to end. "Force drain" flags are not used, + * as they seem to prevent DMA from re-starting properly; it's safer to wait. + */ + u32 ctrl; + + reinit_completion(&dsi->finished); + ctrl = rp1dsi_dma_read(dsi, DPI_DMA_CONTROL); + ctrl &= ~(DPI_DMA_CONTROL_ARM_MASK | DPI_DMA_CONTROL_AUTO_REPEAT_MASK); + rp1dsi_dma_write(dsi, DPI_DMA_CONTROL, ctrl); + if (!wait_for_completion_timeout(&dsi->finished, HZ / 10)) + drm_err(dsi->drm, "%s: timed out waiting for idle\n", __func__); + rp1dsi_dma_write(dsi, DPI_DMA_IRQ_EN, 0); +} + +void rp1dsi_dma_vblank_ctrl(struct rp1_dsi *dsi, int enable) +{ + rp1dsi_dma_write(dsi, DPI_DMA_IRQ_EN, + BITS(DPI_DMA_IRQ_EN_AFIFO_EMPTY, 1) | + BITS(DPI_DMA_IRQ_EN_UNDERFLOW, 1) | + BITS(DPI_DMA_IRQ_EN_DMA_READY, !!enable) | + BITS(DPI_DMA_IRQ_EN_MATCH_LINE, 4095)); +} + +irqreturn_t rp1dsi_dma_isr(int irq, void *dev) +{ + struct rp1_dsi *dsi = dev; + u32 u = rp1dsi_dma_read(dsi, DPI_DMA_IRQ_FLAGS); + + if (u) { + rp1dsi_dma_write(dsi, DPI_DMA_IRQ_FLAGS, u); + if (dsi) { + if (u & DPI_DMA_IRQ_FLAGS_UNDERFLOW_MASK) + drm_err_ratelimited(dsi->drm, + "Underflow! (panics=0x%08x)\n", + rp1dsi_dma_read(dsi, DPI_DMA_PANICS)); + if (u & DPI_DMA_IRQ_FLAGS_DMA_READY_MASK) + drm_crtc_handle_vblank(&dsi->pipe.crtc); + if (u & DPI_DMA_IRQ_FLAGS_AFIFO_EMPTY_MASK) + complete(&dsi->finished); + } + } + return u ? IRQ_HANDLED : IRQ_NONE; +} diff --git a/drivers/gpu/drm/rp1/rp1-dsi/rp1_dsi_dsi.c b/drivers/gpu/drm/rp1/rp1-dsi/rp1_dsi_dsi.c new file mode 100644 index 00000000000000..ecdcff13404fe9 --- /dev/null +++ b/drivers/gpu/drm/rp1/rp1-dsi/rp1_dsi_dsi.c @@ -0,0 +1,1598 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * DRM Driver for DSI output on Raspberry Pi RP1 + * + * Copyright (c) 2023 Raspberry Pi Limited. + */ + +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/math64.h> +#include <linux/platform_device.h> +#include <linux/rp1_platform.h> +#include "drm/drm_print.h" + +#include "rp1_dsi.h" + +/* ------------------------------- Synopsis DSI ------------------------ */ +#define DSI_VERSION_CFG 0x000 +#define DSI_PWR_UP 0x004 +#define DSI_CLKMGR_CFG 0x008 +#define DSI_DPI_VCID 0x00C +#define DSI_DPI_COLOR_CODING 0x010 +#define DSI_DPI_CFG_POL 0x014 +#define DSI_DPI_LP_CMD_TIM 0x018 +#define DSI_DBI_VCID 0x01C +#define DSI_DBI_CFG 0x020 +#define DSI_DBI_PARTITIONING_EN 0x024 +#define DSI_DBI_CMDSIZE 0x028 +#define DSI_PCKHDL_CFG 0x02C +#define DSI_GEN_VCID 0x030 +#define DSI_MODE_CFG 0x034 +#define DSI_VID_MODE_CFG 0x038 +#define DSI_VID_PKT_SIZE 0x03C +#define DSI_VID_NUM_CHUNKS 0x040 +#define DSI_VID_NULL_SIZE 0x044 +#define DSI_VID_HSA_TIME 0x048 +#define DSI_VID_HBP_TIME 0x04C +#define DSI_VID_HLINE_TIME 0x050 +#define DSI_VID_VSA_LINES 0x054 +#define DSI_VID_VBP_LINES 0x058 +#define DSI_VID_VFP_LINES 0x05C +#define DSI_VID_VACTIVE_LINES 0x060 +#define DSI_EDPI_CMD_SIZE 0x064 +#define DSI_CMD_MODE_CFG 0x068 +#define DSI_GEN_HDR 0x06C +#define DSI_GEN_PLD_DATA 0x070 +#define DSI_CMD_PKT_STATUS 0x074 +#define DSI_TO_CNT_CFG 0x078 +#define DSI_HS_RD_TO_CNT 0x07C +#define DSI_LP_RD_TO_CNT 0x080 +#define DSI_HS_WR_TO_CNT 0x084 +#define DSI_LP_WR_TO_CNT 0x088 +#define DSI_BTA_TO_CNT 0x08C +#define DSI_SDF_3D 0x090 +#define DSI_LPCLK_CTRL 0x094 +#define DSI_PHY_TMR_LPCLK_CFG 0x098 +#define DSI_PHY_TMR_HS2LP_LSB 16 +#define DSI_PHY_TMR_LP2HS_LSB 0 +#define DSI_PHY_TMR_CFG 0x09C +#define DSI_PHY_TMR_RD_CFG 0x0F4 +#define DSI_PHYRSTZ 0x0A0 +#define DSI_PHY_IF_CFG 0x0A4 +#define DSI_PHY_ULPS_CTRL 0x0A8 +#define DSI_PHY_TX_TRIGGERS 0x0AC +#define DSI_PHY_STATUS 0x0B0 + +#define DSI_PHY_TST_CTRL0 0x0B4 +#define DSI_PHY_TST_CTRL1 0x0B8 +#define DSI_INT_ST0 0x0BC +#define DSI_INT_ST1 0x0C0 +#define DSI_INT_MASK0_CFG 0x0C4 +#define DSI_INT_MASK1_CFG 0x0C8 +#define DSI_PHY_CAL 0x0CC +#define DSI_HEXP_NPKT_CLR 0x104 +#define DSI_HEXP_NPKT_SIZE 0x108 +#define DSI_VID_SHADOW_CTRL 0x100 + +#define DSI_DPI_VCID_ACT 0x10C +#define DSI_DPI_COLOR_CODING_ACT 0x110 +#define DSI_DPI_LP_CMD_TIM_ACT 0x118 +#define DSI_VID_MODE_CFG_ACT 0x138 +#define DSI_VID_PKT_SIZE_ACT 0x13C +#define DSI_VID_NUM_CHUNKS_ACT 0x140 +#define DSI_VID_NULL_SIZE_ACT 0x144 +#define DSI_VID_HSA_TIME_ACT 0x148 +#define DSI_VID_HBP_TIME_ACT 0x14C +#define DSI_VID_HLINE_TIME_ACT 0x150 +#define DSI_VID_VSA_LINES_ACT 0x154 +#define DSI_VID_VBP_LINES_ACT 0x158 +#define DSI_VID_VFP_LINES_ACT 0x15C +#define DSI_VID_VACTIVE_LINES_ACT 0x160 +#define DSI_SDF_3D_CFG_ACT 0x190 + +#define DSI_INT_FORCE0 0x0D8 +#define DSI_INT_FORCE1 0x0DC + +#define DSI_AUTO_ULPS_MODE 0x0E0 +#define DSI_AUTO_ULPS_ENTRY_DELAY 0x0E4 +#define DSI_AUTO_ULPS_WAKEUP_TIME 0x0E8 +#define DSI_EDPI_ADV_FEATURES 0x0EC + +#define DSI_DSC_PARAMETER 0x0F0 + +/* And some bitfield definitions */ + +#define DSI_PCKHDL_EOTP_TX_EN BIT(0) +#define DSI_PCKHDL_BTA_EN BIT(2) + +#define DSI_VID_MODE_LP_CMD_EN BIT(15) +#define DSI_VID_MODE_FRAME_BTA_ACK_EN BIT(14) +#define DSI_VID_MODE_LP_HFP_EN BIT(13) +#define DSI_VID_MODE_LP_HBP_EN BIT(12) +#define DSI_VID_MODE_LP_VACT_EN BIT(11) +#define DSI_VID_MODE_LP_VFP_EN BIT(10) +#define DSI_VID_MODE_LP_VBP_EN BIT(9) +#define DSI_VID_MODE_LP_VSA_EN BIT(8) +#define DSI_VID_MODE_SYNC_PULSES 0 +#define DSI_VID_MODE_SYNC_EVENTS 1 +#define DSI_VID_MODE_BURST 2 + +#define DSI_CMD_MODE_ALL_LP 0x10f7f00 +#define DSI_CMD_MODE_ACK_RQST_EN BIT(1) + +#define DPHY_PWR_UP_SHUTDOWNZ_LSB 0 +#define DPHY_PWR_UP_SHUTDOWNZ_BITS BIT(DPHY_PWR_UP_SHUTDOWNZ_LSB) + +#define DPHY_CTRL0_PHY_TESTCLK_LSB 1 +#define DPHY_CTRL0_PHY_TESTCLK_BITS BIT(DPHY_CTRL0_PHY_TESTCLK_LSB) +#define DPHY_CTRL0_PHY_TESTCLR_LSB 0 +#define DPHY_CTRL0_PHY_TESTCLR_BITS BIT(DPHY_CTRL0_PHY_TESTCLR_LSB) + +#define DPHY_CTRL1_PHY_TESTDIN_LSB 0 +#define DPHY_CTRL1_PHY_TESTDIN_BITS (0xff << DPHY_CTRL1_PHY_TESTDIN_LSB) +#define DPHY_CTRL1_PHY_TESTDOUT_LSB 8 +#define DPHY_CTRL1_PHY_TESTDOUT_BITS (0xff << DPHY_CTRL1_PHY_TESTDOUT_LSB) +#define DPHY_CTRL1_PHY_TESTEN_LSB 16 +#define DPHY_CTRL1_PHY_TESTEN_BITS BIT(DPHY_CTRL1_PHY_TESTEN_LSB) + +#define DSI_PHYRSTZ_SHUTDOWNZ_LSB 0 +#define DSI_PHYRSTZ_SHUTDOWNZ_BITS BIT(DSI_PHYRSTZ_SHUTDOWNZ_LSB) +#define DSI_PHYRSTZ_RSTZ_LSB 1 +#define DSI_PHYRSTZ_RSTZ_BITS BIT(DSI_PHYRSTZ_RSTZ_LSB) +#define DSI_PHYRSTZ_ENABLECLK_LSB 2 +#define DSI_PHYRSTZ_ENABLECLK_BITS BIT(DSI_PHYRSTZ_ENABLECLK_LSB) +#define DSI_PHYRSTZ_FORCEPLL_LSB 3 +#define DSI_PHYRSTZ_FORCEPLL_BITS BIT(DSI_PHYRSTZ_FORCEPLL_LSB) + +#define DPHY_HS_RX_CTRL_LANE0_OFFSET 0x44 +#define DPHY_PLL_INPUT_DIV_OFFSET 0x17 +#define DPHY_PLL_LOOP_DIV_OFFSET 0x18 +#define DPHY_PLL_DIV_CTRL_OFFSET 0x19 + +#define DPHY_PLL_BIAS_OFFSET 0x10 +#define DPHY_PLL_BIAS_VCO_RANGE_LSB 3 +#define DPHY_PLL_BIAS_USE_PROGRAMMED_VCO_RANGE BIT(7) + +#define DPHY_PLL_CHARGE_PUMP_OFFSET 0x11 +#define DPHY_PLL_LPF_OFFSET 0x12 + +#define DSI_WRITE(reg, val) writel((val), dsi->hw_base[RP1DSI_HW_BLOCK_DSI] + (reg)) +#define DSI_READ(reg) readl(dsi->hw_base[RP1DSI_HW_BLOCK_DSI] + (reg)) + +// ================================================================================ +// Register block : RPI_MIPICFG +// Version : 1 +// Bus type : apb +// Description : Register block to control mipi DPHY +// ================================================================================ +#define RPI_MIPICFG_REGS_RWTYPE_MSB 13 +#define RPI_MIPICFG_REGS_RWTYPE_LSB 12 +// ================================================================================ +// Register : RPI_MIPICFG_CLK2FC +// JTAG access : synchronous +// Description : None +#define RPI_MIPICFG_CLK2FC_OFFSET 0x00000000 +#define RPI_MIPICFG_CLK2FC_BITS 0x00000007 +#define RPI_MIPICFG_CLK2FC_RESET 0x00000000 +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_CLK2FC_SEL +// Description : select a clock to be sent to the frequency counter +// 7 = none +// 6 = none +// 5 = none +// 4 = rxbyteclkhs (187.5MHz) +// 3 = rxclkesc0 (20MHz) +// 2 = txbyteclkhs (187.5MHz) +// 1 = txclkesc (125MHz) +// 0 = none +#define RPI_MIPICFG_CLK2FC_SEL_RESET 0x0 +#define RPI_MIPICFG_CLK2FC_SEL_BITS 0x00000007 +#define RPI_MIPICFG_CLK2FC_SEL_MSB 2 +#define RPI_MIPICFG_CLK2FC_SEL_LSB 0 +#define RPI_MIPICFG_CLK2FC_SEL_ACCESS "RW" +// ================================================================================ +// Register : RPI_MIPICFG_CFG +// JTAG access : asynchronous +// Description : Top level configuration +#define RPI_MIPICFG_CFG_OFFSET 0x00000004 +#define RPI_MIPICFG_CFG_BITS 0x00000111 +#define RPI_MIPICFG_CFG_RESET 0x00000001 +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_CFG_DPIUPDATE +// Description : Indicate the DSI block that the next frame will have a new video configuration +#define RPI_MIPICFG_CFG_DPIUPDATE_RESET 0x0 +#define RPI_MIPICFG_CFG_DPIUPDATE_BITS 0x00000100 +#define RPI_MIPICFG_CFG_DPIUPDATE_MSB 8 +#define RPI_MIPICFG_CFG_DPIUPDATE_LSB 8 +#define RPI_MIPICFG_CFG_DPIUPDATE_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_CFG_SEL_TE_EXT +// Description : Select the TE source: 1 - ext, 0 - int +#define RPI_MIPICFG_CFG_SEL_TE_EXT_RESET 0x0 +#define RPI_MIPICFG_CFG_SEL_TE_EXT_BITS 0x00000010 +#define RPI_MIPICFG_CFG_SEL_TE_EXT_MSB 4 +#define RPI_MIPICFG_CFG_SEL_TE_EXT_LSB 4 +#define RPI_MIPICFG_CFG_SEL_TE_EXT_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_CFG_SEL_CSI_DSI_N +// Description : Select PHY direction: input to CSI, output from DSI. CSI 1 DSI 0 +#define RPI_MIPICFG_CFG_SEL_CSI_DSI_N_RESET 0x1 +#define RPI_MIPICFG_CFG_SEL_CSI_DSI_N_BITS 0x00000001 +#define RPI_MIPICFG_CFG_SEL_CSI_DSI_N_MSB 0 +#define RPI_MIPICFG_CFG_SEL_CSI_DSI_N_LSB 0 +#define RPI_MIPICFG_CFG_SEL_CSI_DSI_N_ACCESS "RW" +// ================================================================================ +// Register : RPI_MIPICFG_TE +// JTAG access : synchronous +// Description : Tearing effect processing +#define RPI_MIPICFG_TE_OFFSET 0x00000008 +#define RPI_MIPICFG_TE_BITS 0x10ffffff +#define RPI_MIPICFG_TE_RESET 0x00000000 +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_TE_ARM +// Description : Tearing effect arm +#define RPI_MIPICFG_TE_ARM_RESET 0x0 +#define RPI_MIPICFG_TE_ARM_BITS 0x10000000 +#define RPI_MIPICFG_TE_ARM_MSB 28 +#define RPI_MIPICFG_TE_ARM_LSB 28 +#define RPI_MIPICFG_TE_ARM_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_TE_HALT_CYC +// Description : When arm pulse has been seen, wait for te; then halt the dpi block +// for this many clk_dpi cycles +#define RPI_MIPICFG_TE_HALT_CYC_RESET 0x000000 +#define RPI_MIPICFG_TE_HALT_CYC_BITS 0x00ffffff +#define RPI_MIPICFG_TE_HALT_CYC_MSB 23 +#define RPI_MIPICFG_TE_HALT_CYC_LSB 0 +#define RPI_MIPICFG_TE_HALT_CYC_ACCESS "RW" +// ================================================================================ +// Register : RPI_MIPICFG_DPHY_MONITOR +// JTAG access : asynchronous +// Description : DPHY status monitors for analog DFT +#define RPI_MIPICFG_DPHY_MONITOR_OFFSET 0x00000010 +#define RPI_MIPICFG_DPHY_MONITOR_BITS 0x00111fff +#define RPI_MIPICFG_DPHY_MONITOR_RESET 0x00000000 +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_MONITOR_LOCK +// Description : None +#define RPI_MIPICFG_DPHY_MONITOR_LOCK_RESET 0x0 +#define RPI_MIPICFG_DPHY_MONITOR_LOCK_BITS 0x00100000 +#define RPI_MIPICFG_DPHY_MONITOR_LOCK_MSB 20 +#define RPI_MIPICFG_DPHY_MONITOR_LOCK_LSB 20 +#define RPI_MIPICFG_DPHY_MONITOR_LOCK_ACCESS "RO" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_MONITOR_BISTOK +// Description : None +#define RPI_MIPICFG_DPHY_MONITOR_BISTOK_RESET 0x0 +#define RPI_MIPICFG_DPHY_MONITOR_BISTOK_BITS 0x00010000 +#define RPI_MIPICFG_DPHY_MONITOR_BISTOK_MSB 16 +#define RPI_MIPICFG_DPHY_MONITOR_BISTOK_LSB 16 +#define RPI_MIPICFG_DPHY_MONITOR_BISTOK_ACCESS "RO" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_MONITOR_STOPSTATECLK +// Description : None +#define RPI_MIPICFG_DPHY_MONITOR_STOPSTATECLK_RESET 0x0 +#define RPI_MIPICFG_DPHY_MONITOR_STOPSTATECLK_BITS 0x00001000 +#define RPI_MIPICFG_DPHY_MONITOR_STOPSTATECLK_MSB 12 +#define RPI_MIPICFG_DPHY_MONITOR_STOPSTATECLK_LSB 12 +#define RPI_MIPICFG_DPHY_MONITOR_STOPSTATECLK_ACCESS "RO" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_MONITOR_STOPSTATEDATA +// Description : None +#define RPI_MIPICFG_DPHY_MONITOR_STOPSTATEDATA_RESET 0x0 +#define RPI_MIPICFG_DPHY_MONITOR_STOPSTATEDATA_BITS 0x00000f00 +#define RPI_MIPICFG_DPHY_MONITOR_STOPSTATEDATA_MSB 11 +#define RPI_MIPICFG_DPHY_MONITOR_STOPSTATEDATA_LSB 8 +#define RPI_MIPICFG_DPHY_MONITOR_STOPSTATEDATA_ACCESS "RO" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_MONITOR_TESTDOUT +// Description : None +#define RPI_MIPICFG_DPHY_MONITOR_TESTDOUT_RESET 0x00 +#define RPI_MIPICFG_DPHY_MONITOR_TESTDOUT_BITS 0x000000ff +#define RPI_MIPICFG_DPHY_MONITOR_TESTDOUT_MSB 7 +#define RPI_MIPICFG_DPHY_MONITOR_TESTDOUT_LSB 0 +#define RPI_MIPICFG_DPHY_MONITOR_TESTDOUT_ACCESS "RO" +// ================================================================================ +// Register : RPI_MIPICFG_DPHY_CTRL_0 +// JTAG access : asynchronous +// Description : DPHY control for analog DFT +#define RPI_MIPICFG_DPHY_CTRL_0_OFFSET 0x00000014 +#define RPI_MIPICFG_DPHY_CTRL_0_BITS 0x0000003f +#define RPI_MIPICFG_DPHY_CTRL_0_RESET 0x00000000 +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_0_TEST_LPMODE +// Description : When set in lpmode, TXCLKESC is driven from clk_vec(driven from clocks block) +#define RPI_MIPICFG_DPHY_CTRL_0_TEST_LPMODE_RESET 0x0 +#define RPI_MIPICFG_DPHY_CTRL_0_TEST_LPMODE_BITS 0x00000020 +#define RPI_MIPICFG_DPHY_CTRL_0_TEST_LPMODE_MSB 5 +#define RPI_MIPICFG_DPHY_CTRL_0_TEST_LPMODE_LSB 5 +#define RPI_MIPICFG_DPHY_CTRL_0_TEST_LPMODE_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_0_TEST_ENA +// Description : When set, drive the DPHY from the test registers +#define RPI_MIPICFG_DPHY_CTRL_0_TEST_ENA_RESET 0x0 +#define RPI_MIPICFG_DPHY_CTRL_0_TEST_ENA_BITS 0x00000010 +#define RPI_MIPICFG_DPHY_CTRL_0_TEST_ENA_MSB 4 +#define RPI_MIPICFG_DPHY_CTRL_0_TEST_ENA_LSB 4 +#define RPI_MIPICFG_DPHY_CTRL_0_TEST_ENA_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_0_CFG_CLK_DIS +// Description : When test_ena is set, disable cfg_clk +#define RPI_MIPICFG_DPHY_CTRL_0_CFG_CLK_DIS_RESET 0x0 +#define RPI_MIPICFG_DPHY_CTRL_0_CFG_CLK_DIS_BITS 0x00000008 +#define RPI_MIPICFG_DPHY_CTRL_0_CFG_CLK_DIS_MSB 3 +#define RPI_MIPICFG_DPHY_CTRL_0_CFG_CLK_DIS_LSB 3 +#define RPI_MIPICFG_DPHY_CTRL_0_CFG_CLK_DIS_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_0_REFCLK_DIS +// Description : When test_ena is set, disable refclk +#define RPI_MIPICFG_DPHY_CTRL_0_REFCLK_DIS_RESET 0x0 +#define RPI_MIPICFG_DPHY_CTRL_0_REFCLK_DIS_BITS 0x00000004 +#define RPI_MIPICFG_DPHY_CTRL_0_REFCLK_DIS_MSB 2 +#define RPI_MIPICFG_DPHY_CTRL_0_REFCLK_DIS_LSB 2 +#define RPI_MIPICFG_DPHY_CTRL_0_REFCLK_DIS_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_0_TXCLKESC_DIS +// Description : When test_ena is set, disable txclkesc +#define RPI_MIPICFG_DPHY_CTRL_0_TXCLKESC_DIS_RESET 0x0 +#define RPI_MIPICFG_DPHY_CTRL_0_TXCLKESC_DIS_BITS 0x00000002 +#define RPI_MIPICFG_DPHY_CTRL_0_TXCLKESC_DIS_MSB 1 +#define RPI_MIPICFG_DPHY_CTRL_0_TXCLKESC_DIS_LSB 1 +#define RPI_MIPICFG_DPHY_CTRL_0_TXCLKESC_DIS_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_0_TXBYTECLKHS_DIS +// Description : When test_ena is set, disable txbyteclkhs +#define RPI_MIPICFG_DPHY_CTRL_0_TXBYTECLKHS_DIS_RESET 0x0 +#define RPI_MIPICFG_DPHY_CTRL_0_TXBYTECLKHS_DIS_BITS 0x00000001 +#define RPI_MIPICFG_DPHY_CTRL_0_TXBYTECLKHS_DIS_MSB 0 +#define RPI_MIPICFG_DPHY_CTRL_0_TXBYTECLKHS_DIS_LSB 0 +#define RPI_MIPICFG_DPHY_CTRL_0_TXBYTECLKHS_DIS_ACCESS "RW" +// ================================================================================ +// Register : RPI_MIPICFG_DPHY_CTRL_1 +// JTAG access : asynchronous +// Description : DPHY control for analog DFT +#define RPI_MIPICFG_DPHY_CTRL_1_OFFSET 0x00000018 +#define RPI_MIPICFG_DPHY_CTRL_1_BITS 0x7fffffff +#define RPI_MIPICFG_DPHY_CTRL_1_RESET 0x00000000 +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_1_FORCEPLL +// Description : None +#define RPI_MIPICFG_DPHY_CTRL_1_FORCEPLL_RESET 0x0 +#define RPI_MIPICFG_DPHY_CTRL_1_FORCEPLL_BITS 0x40000000 +#define RPI_MIPICFG_DPHY_CTRL_1_FORCEPLL_MSB 30 +#define RPI_MIPICFG_DPHY_CTRL_1_FORCEPLL_LSB 30 +#define RPI_MIPICFG_DPHY_CTRL_1_FORCEPLL_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_1_SHUTDOWNZ +// Description : None +#define RPI_MIPICFG_DPHY_CTRL_1_SHUTDOWNZ_RESET 0x0 +#define RPI_MIPICFG_DPHY_CTRL_1_SHUTDOWNZ_BITS 0x20000000 +#define RPI_MIPICFG_DPHY_CTRL_1_SHUTDOWNZ_MSB 29 +#define RPI_MIPICFG_DPHY_CTRL_1_SHUTDOWNZ_LSB 29 +#define RPI_MIPICFG_DPHY_CTRL_1_SHUTDOWNZ_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_1_RSTZ +// Description : None +#define RPI_MIPICFG_DPHY_CTRL_1_RSTZ_RESET 0x0 +#define RPI_MIPICFG_DPHY_CTRL_1_RSTZ_BITS 0x10000000 +#define RPI_MIPICFG_DPHY_CTRL_1_RSTZ_MSB 28 +#define RPI_MIPICFG_DPHY_CTRL_1_RSTZ_LSB 28 +#define RPI_MIPICFG_DPHY_CTRL_1_RSTZ_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_1_MASTERSLAVEZ +// Description : None +#define RPI_MIPICFG_DPHY_CTRL_1_MASTERSLAVEZ_RESET 0x0 +#define RPI_MIPICFG_DPHY_CTRL_1_MASTERSLAVEZ_BITS 0x08000000 +#define RPI_MIPICFG_DPHY_CTRL_1_MASTERSLAVEZ_MSB 27 +#define RPI_MIPICFG_DPHY_CTRL_1_MASTERSLAVEZ_LSB 27 +#define RPI_MIPICFG_DPHY_CTRL_1_MASTERSLAVEZ_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_1_BISTON +// Description : None +#define RPI_MIPICFG_DPHY_CTRL_1_BISTON_RESET 0x0 +#define RPI_MIPICFG_DPHY_CTRL_1_BISTON_BITS 0x04000000 +#define RPI_MIPICFG_DPHY_CTRL_1_BISTON_MSB 26 +#define RPI_MIPICFG_DPHY_CTRL_1_BISTON_LSB 26 +#define RPI_MIPICFG_DPHY_CTRL_1_BISTON_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTHSCLK +// Description : None +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTHSCLK_RESET 0x0 +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTHSCLK_BITS 0x02000000 +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTHSCLK_MSB 25 +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTHSCLK_LSB 25 +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTHSCLK_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_1_ENABLECLK +// Description : None +#define RPI_MIPICFG_DPHY_CTRL_1_ENABLECLK_RESET 0x0 +#define RPI_MIPICFG_DPHY_CTRL_1_ENABLECLK_BITS 0x01000000 +#define RPI_MIPICFG_DPHY_CTRL_1_ENABLECLK_MSB 24 +#define RPI_MIPICFG_DPHY_CTRL_1_ENABLECLK_LSB 24 +#define RPI_MIPICFG_DPHY_CTRL_1_ENABLECLK_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_1_ENABLE_3 +// Description : None +#define RPI_MIPICFG_DPHY_CTRL_1_ENABLE_3_RESET 0x0 +#define RPI_MIPICFG_DPHY_CTRL_1_ENABLE_3_BITS 0x00800000 +#define RPI_MIPICFG_DPHY_CTRL_1_ENABLE_3_MSB 23 +#define RPI_MIPICFG_DPHY_CTRL_1_ENABLE_3_LSB 23 +#define RPI_MIPICFG_DPHY_CTRL_1_ENABLE_3_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_1_ENABLE_2 +// Description : None +#define RPI_MIPICFG_DPHY_CTRL_1_ENABLE_2_RESET 0x0 +#define RPI_MIPICFG_DPHY_CTRL_1_ENABLE_2_BITS 0x00400000 +#define RPI_MIPICFG_DPHY_CTRL_1_ENABLE_2_MSB 22 +#define RPI_MIPICFG_DPHY_CTRL_1_ENABLE_2_LSB 22 +#define RPI_MIPICFG_DPHY_CTRL_1_ENABLE_2_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_1_ENABLE_1 +// Description : None +#define RPI_MIPICFG_DPHY_CTRL_1_ENABLE_1_RESET 0x0 +#define RPI_MIPICFG_DPHY_CTRL_1_ENABLE_1_BITS 0x00200000 +#define RPI_MIPICFG_DPHY_CTRL_1_ENABLE_1_MSB 21 +#define RPI_MIPICFG_DPHY_CTRL_1_ENABLE_1_LSB 21 +#define RPI_MIPICFG_DPHY_CTRL_1_ENABLE_1_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_1_ENABLE_0 +// Description : None +#define RPI_MIPICFG_DPHY_CTRL_1_ENABLE_0_RESET 0x0 +#define RPI_MIPICFG_DPHY_CTRL_1_ENABLE_0_BITS 0x00100000 +#define RPI_MIPICFG_DPHY_CTRL_1_ENABLE_0_MSB 20 +#define RPI_MIPICFG_DPHY_CTRL_1_ENABLE_0_LSB 20 +#define RPI_MIPICFG_DPHY_CTRL_1_ENABLE_0_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_1_BASEDIR_3 +// Description : None +#define RPI_MIPICFG_DPHY_CTRL_1_BASEDIR_3_RESET 0x0 +#define RPI_MIPICFG_DPHY_CTRL_1_BASEDIR_3_BITS 0x00080000 +#define RPI_MIPICFG_DPHY_CTRL_1_BASEDIR_3_MSB 19 +#define RPI_MIPICFG_DPHY_CTRL_1_BASEDIR_3_LSB 19 +#define RPI_MIPICFG_DPHY_CTRL_1_BASEDIR_3_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_1_BASEDIR_2 +// Description : None +#define RPI_MIPICFG_DPHY_CTRL_1_BASEDIR_2_RESET 0x0 +#define RPI_MIPICFG_DPHY_CTRL_1_BASEDIR_2_BITS 0x00040000 +#define RPI_MIPICFG_DPHY_CTRL_1_BASEDIR_2_MSB 18 +#define RPI_MIPICFG_DPHY_CTRL_1_BASEDIR_2_LSB 18 +#define RPI_MIPICFG_DPHY_CTRL_1_BASEDIR_2_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_1_BASEDIR_1 +// Description : None +#define RPI_MIPICFG_DPHY_CTRL_1_BASEDIR_1_RESET 0x0 +#define RPI_MIPICFG_DPHY_CTRL_1_BASEDIR_1_BITS 0x00020000 +#define RPI_MIPICFG_DPHY_CTRL_1_BASEDIR_1_MSB 17 +#define RPI_MIPICFG_DPHY_CTRL_1_BASEDIR_1_LSB 17 +#define RPI_MIPICFG_DPHY_CTRL_1_BASEDIR_1_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_1_BASEDIR_0 +// Description : None +#define RPI_MIPICFG_DPHY_CTRL_1_BASEDIR_0_RESET 0x0 +#define RPI_MIPICFG_DPHY_CTRL_1_BASEDIR_0_BITS 0x00010000 +#define RPI_MIPICFG_DPHY_CTRL_1_BASEDIR_0_MSB 16 +#define RPI_MIPICFG_DPHY_CTRL_1_BASEDIR_0_LSB 16 +#define RPI_MIPICFG_DPHY_CTRL_1_BASEDIR_0_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_1_TXLPDTESC_3 +// Description : None +#define RPI_MIPICFG_DPHY_CTRL_1_TXLPDTESC_3_RESET 0x0 +#define RPI_MIPICFG_DPHY_CTRL_1_TXLPDTESC_3_BITS 0x00008000 +#define RPI_MIPICFG_DPHY_CTRL_1_TXLPDTESC_3_MSB 15 +#define RPI_MIPICFG_DPHY_CTRL_1_TXLPDTESC_3_LSB 15 +#define RPI_MIPICFG_DPHY_CTRL_1_TXLPDTESC_3_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_1_TXLPDTESC_2 +// Description : None +#define RPI_MIPICFG_DPHY_CTRL_1_TXLPDTESC_2_RESET 0x0 +#define RPI_MIPICFG_DPHY_CTRL_1_TXLPDTESC_2_BITS 0x00004000 +#define RPI_MIPICFG_DPHY_CTRL_1_TXLPDTESC_2_MSB 14 +#define RPI_MIPICFG_DPHY_CTRL_1_TXLPDTESC_2_LSB 14 +#define RPI_MIPICFG_DPHY_CTRL_1_TXLPDTESC_2_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_1_TXLPDTESC_1 +// Description : None +#define RPI_MIPICFG_DPHY_CTRL_1_TXLPDTESC_1_RESET 0x0 +#define RPI_MIPICFG_DPHY_CTRL_1_TXLPDTESC_1_BITS 0x00002000 +#define RPI_MIPICFG_DPHY_CTRL_1_TXLPDTESC_1_MSB 13 +#define RPI_MIPICFG_DPHY_CTRL_1_TXLPDTESC_1_LSB 13 +#define RPI_MIPICFG_DPHY_CTRL_1_TXLPDTESC_1_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_1_TXLPDTESC_0 +// Description : None +#define RPI_MIPICFG_DPHY_CTRL_1_TXLPDTESC_0_RESET 0x0 +#define RPI_MIPICFG_DPHY_CTRL_1_TXLPDTESC_0_BITS 0x00001000 +#define RPI_MIPICFG_DPHY_CTRL_1_TXLPDTESC_0_MSB 12 +#define RPI_MIPICFG_DPHY_CTRL_1_TXLPDTESC_0_LSB 12 +#define RPI_MIPICFG_DPHY_CTRL_1_TXLPDTESC_0_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_1_TXVALIDESC_3 +// Description : None +#define RPI_MIPICFG_DPHY_CTRL_1_TXVALIDESC_3_RESET 0x0 +#define RPI_MIPICFG_DPHY_CTRL_1_TXVALIDESC_3_BITS 0x00000800 +#define RPI_MIPICFG_DPHY_CTRL_1_TXVALIDESC_3_MSB 11 +#define RPI_MIPICFG_DPHY_CTRL_1_TXVALIDESC_3_LSB 11 +#define RPI_MIPICFG_DPHY_CTRL_1_TXVALIDESC_3_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_1_TXVALIDESC_2 +// Description : None +#define RPI_MIPICFG_DPHY_CTRL_1_TXVALIDESC_2_RESET 0x0 +#define RPI_MIPICFG_DPHY_CTRL_1_TXVALIDESC_2_BITS 0x00000400 +#define RPI_MIPICFG_DPHY_CTRL_1_TXVALIDESC_2_MSB 10 +#define RPI_MIPICFG_DPHY_CTRL_1_TXVALIDESC_2_LSB 10 +#define RPI_MIPICFG_DPHY_CTRL_1_TXVALIDESC_2_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_1_TXVALIDESC_1 +// Description : None +#define RPI_MIPICFG_DPHY_CTRL_1_TXVALIDESC_1_RESET 0x0 +#define RPI_MIPICFG_DPHY_CTRL_1_TXVALIDESC_1_BITS 0x00000200 +#define RPI_MIPICFG_DPHY_CTRL_1_TXVALIDESC_1_MSB 9 +#define RPI_MIPICFG_DPHY_CTRL_1_TXVALIDESC_1_LSB 9 +#define RPI_MIPICFG_DPHY_CTRL_1_TXVALIDESC_1_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_1_TXVALIDESC_0 +// Description : None +#define RPI_MIPICFG_DPHY_CTRL_1_TXVALIDESC_0_RESET 0x0 +#define RPI_MIPICFG_DPHY_CTRL_1_TXVALIDESC_0_BITS 0x00000100 +#define RPI_MIPICFG_DPHY_CTRL_1_TXVALIDESC_0_MSB 8 +#define RPI_MIPICFG_DPHY_CTRL_1_TXVALIDESC_0_LSB 8 +#define RPI_MIPICFG_DPHY_CTRL_1_TXVALIDESC_0_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTESC_3 +// Description : None +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTESC_3_RESET 0x0 +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTESC_3_BITS 0x00000080 +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTESC_3_MSB 7 +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTESC_3_LSB 7 +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTESC_3_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTESC_2 +// Description : None +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTESC_2_RESET 0x0 +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTESC_2_BITS 0x00000040 +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTESC_2_MSB 6 +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTESC_2_LSB 6 +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTESC_2_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTESC_1 +// Description : None +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTESC_1_RESET 0x0 +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTESC_1_BITS 0x00000020 +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTESC_1_MSB 5 +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTESC_1_LSB 5 +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTESC_1_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTESC_0 +// Description : None +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTESC_0_RESET 0x0 +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTESC_0_BITS 0x00000010 +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTESC_0_MSB 4 +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTESC_0_LSB 4 +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTESC_0_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTDATAHS_3 +// Description : None +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTDATAHS_3_RESET 0x0 +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTDATAHS_3_BITS 0x00000008 +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTDATAHS_3_MSB 3 +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTDATAHS_3_LSB 3 +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTDATAHS_3_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTDATAHS_2 +// Description : None +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTDATAHS_2_RESET 0x0 +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTDATAHS_2_BITS 0x00000004 +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTDATAHS_2_MSB 2 +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTDATAHS_2_LSB 2 +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTDATAHS_2_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTDATAHS_1 +// Description : None +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTDATAHS_1_RESET 0x0 +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTDATAHS_1_BITS 0x00000002 +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTDATAHS_1_MSB 1 +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTDATAHS_1_LSB 1 +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTDATAHS_1_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTDATAHS_0 +// Description : None +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTDATAHS_0_RESET 0x0 +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTDATAHS_0_BITS 0x00000001 +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTDATAHS_0_MSB 0 +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTDATAHS_0_LSB 0 +#define RPI_MIPICFG_DPHY_CTRL_1_TXREQUESTDATAHS_0_ACCESS "RW" +// ================================================================================ +// Register : RPI_MIPICFG_DPHY_CTRL_2 +// JTAG access : asynchronous +// Description : DPHY control for analog DFT +#define RPI_MIPICFG_DPHY_CTRL_2_OFFSET 0x0000001c +#define RPI_MIPICFG_DPHY_CTRL_2_BITS 0x000007ff +#define RPI_MIPICFG_DPHY_CTRL_2_RESET 0x00000000 +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_2_TESTCLK +// Description : None +#define RPI_MIPICFG_DPHY_CTRL_2_TESTCLK_RESET 0x0 +#define RPI_MIPICFG_DPHY_CTRL_2_TESTCLK_BITS 0x00000400 +#define RPI_MIPICFG_DPHY_CTRL_2_TESTCLK_MSB 10 +#define RPI_MIPICFG_DPHY_CTRL_2_TESTCLK_LSB 10 +#define RPI_MIPICFG_DPHY_CTRL_2_TESTCLK_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_2_TESTEN +// Description : None +#define RPI_MIPICFG_DPHY_CTRL_2_TESTEN_RESET 0x0 +#define RPI_MIPICFG_DPHY_CTRL_2_TESTEN_BITS 0x00000200 +#define RPI_MIPICFG_DPHY_CTRL_2_TESTEN_MSB 9 +#define RPI_MIPICFG_DPHY_CTRL_2_TESTEN_LSB 9 +#define RPI_MIPICFG_DPHY_CTRL_2_TESTEN_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_2_TESTCLR +// Description : None +#define RPI_MIPICFG_DPHY_CTRL_2_TESTCLR_RESET 0x0 +#define RPI_MIPICFG_DPHY_CTRL_2_TESTCLR_BITS 0x00000100 +#define RPI_MIPICFG_DPHY_CTRL_2_TESTCLR_MSB 8 +#define RPI_MIPICFG_DPHY_CTRL_2_TESTCLR_LSB 8 +#define RPI_MIPICFG_DPHY_CTRL_2_TESTCLR_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_2_TESTDIN +// Description : None +#define RPI_MIPICFG_DPHY_CTRL_2_TESTDIN_RESET 0x00 +#define RPI_MIPICFG_DPHY_CTRL_2_TESTDIN_BITS 0x000000ff +#define RPI_MIPICFG_DPHY_CTRL_2_TESTDIN_MSB 7 +#define RPI_MIPICFG_DPHY_CTRL_2_TESTDIN_LSB 0 +#define RPI_MIPICFG_DPHY_CTRL_2_TESTDIN_ACCESS "RW" +// ================================================================================ +// Register : RPI_MIPICFG_DPHY_CTRL_3 +// JTAG access : asynchronous +// Description : DPHY control for analog DFT +#define RPI_MIPICFG_DPHY_CTRL_3_OFFSET 0x00000020 +#define RPI_MIPICFG_DPHY_CTRL_3_BITS 0xffffffff +#define RPI_MIPICFG_DPHY_CTRL_3_RESET 0x00000000 +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_3_TXDATAESC_3 +// Description : None +#define RPI_MIPICFG_DPHY_CTRL_3_TXDATAESC_3_RESET 0x00 +#define RPI_MIPICFG_DPHY_CTRL_3_TXDATAESC_3_BITS 0xff000000 +#define RPI_MIPICFG_DPHY_CTRL_3_TXDATAESC_3_MSB 31 +#define RPI_MIPICFG_DPHY_CTRL_3_TXDATAESC_3_LSB 24 +#define RPI_MIPICFG_DPHY_CTRL_3_TXDATAESC_3_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_3_TXDATAESC_2 +// Description : None +#define RPI_MIPICFG_DPHY_CTRL_3_TXDATAESC_2_RESET 0x00 +#define RPI_MIPICFG_DPHY_CTRL_3_TXDATAESC_2_BITS 0x00ff0000 +#define RPI_MIPICFG_DPHY_CTRL_3_TXDATAESC_2_MSB 23 +#define RPI_MIPICFG_DPHY_CTRL_3_TXDATAESC_2_LSB 16 +#define RPI_MIPICFG_DPHY_CTRL_3_TXDATAESC_2_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_3_TXDATAESC_1 +// Description : None +#define RPI_MIPICFG_DPHY_CTRL_3_TXDATAESC_1_RESET 0x00 +#define RPI_MIPICFG_DPHY_CTRL_3_TXDATAESC_1_BITS 0x0000ff00 +#define RPI_MIPICFG_DPHY_CTRL_3_TXDATAESC_1_MSB 15 +#define RPI_MIPICFG_DPHY_CTRL_3_TXDATAESC_1_LSB 8 +#define RPI_MIPICFG_DPHY_CTRL_3_TXDATAESC_1_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_3_TXDATAESC_0 +// Description : None +#define RPI_MIPICFG_DPHY_CTRL_3_TXDATAESC_0_RESET 0x00 +#define RPI_MIPICFG_DPHY_CTRL_3_TXDATAESC_0_BITS 0x000000ff +#define RPI_MIPICFG_DPHY_CTRL_3_TXDATAESC_0_MSB 7 +#define RPI_MIPICFG_DPHY_CTRL_3_TXDATAESC_0_LSB 0 +#define RPI_MIPICFG_DPHY_CTRL_3_TXDATAESC_0_ACCESS "RW" +// ================================================================================ +// Register : RPI_MIPICFG_DPHY_CTRL_4 +// JTAG access : asynchronous +// Description : DPHY control for analog DFT +#define RPI_MIPICFG_DPHY_CTRL_4_OFFSET 0x00000024 +#define RPI_MIPICFG_DPHY_CTRL_4_BITS 0xffffffff +#define RPI_MIPICFG_DPHY_CTRL_4_RESET 0x00000000 +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_4_TXDATAHS_3 +// Description : None +#define RPI_MIPICFG_DPHY_CTRL_4_TXDATAHS_3_RESET 0x00 +#define RPI_MIPICFG_DPHY_CTRL_4_TXDATAHS_3_BITS 0xff000000 +#define RPI_MIPICFG_DPHY_CTRL_4_TXDATAHS_3_MSB 31 +#define RPI_MIPICFG_DPHY_CTRL_4_TXDATAHS_3_LSB 24 +#define RPI_MIPICFG_DPHY_CTRL_4_TXDATAHS_3_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_4_TXDATAHS_2 +// Description : None +#define RPI_MIPICFG_DPHY_CTRL_4_TXDATAHS_2_RESET 0x00 +#define RPI_MIPICFG_DPHY_CTRL_4_TXDATAHS_2_BITS 0x00ff0000 +#define RPI_MIPICFG_DPHY_CTRL_4_TXDATAHS_2_MSB 23 +#define RPI_MIPICFG_DPHY_CTRL_4_TXDATAHS_2_LSB 16 +#define RPI_MIPICFG_DPHY_CTRL_4_TXDATAHS_2_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_4_TXDATAHS_1 +// Description : None +#define RPI_MIPICFG_DPHY_CTRL_4_TXDATAHS_1_RESET 0x00 +#define RPI_MIPICFG_DPHY_CTRL_4_TXDATAHS_1_BITS 0x0000ff00 +#define RPI_MIPICFG_DPHY_CTRL_4_TXDATAHS_1_MSB 15 +#define RPI_MIPICFG_DPHY_CTRL_4_TXDATAHS_1_LSB 8 +#define RPI_MIPICFG_DPHY_CTRL_4_TXDATAHS_1_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DPHY_CTRL_4_TXDATAHS_0 +// Description : None +#define RPI_MIPICFG_DPHY_CTRL_4_TXDATAHS_0_RESET 0x00 +#define RPI_MIPICFG_DPHY_CTRL_4_TXDATAHS_0_BITS 0x000000ff +#define RPI_MIPICFG_DPHY_CTRL_4_TXDATAHS_0_MSB 7 +#define RPI_MIPICFG_DPHY_CTRL_4_TXDATAHS_0_LSB 0 +#define RPI_MIPICFG_DPHY_CTRL_4_TXDATAHS_0_ACCESS "RW" +// ================================================================================ +// Register : RPI_MIPICFG_INTR +// JTAG access : synchronous +// Description : Raw Interrupts +#define RPI_MIPICFG_INTR_OFFSET 0x00000028 +#define RPI_MIPICFG_INTR_BITS 0x0000000f +#define RPI_MIPICFG_INTR_RESET 0x00000000 +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_INTR_DSI_HOST +// Description : None +#define RPI_MIPICFG_INTR_DSI_HOST_RESET 0x0 +#define RPI_MIPICFG_INTR_DSI_HOST_BITS 0x00000008 +#define RPI_MIPICFG_INTR_DSI_HOST_MSB 3 +#define RPI_MIPICFG_INTR_DSI_HOST_LSB 3 +#define RPI_MIPICFG_INTR_DSI_HOST_ACCESS "RO" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_INTR_CSI_HOST +// Description : None +#define RPI_MIPICFG_INTR_CSI_HOST_RESET 0x0 +#define RPI_MIPICFG_INTR_CSI_HOST_BITS 0x00000004 +#define RPI_MIPICFG_INTR_CSI_HOST_MSB 2 +#define RPI_MIPICFG_INTR_CSI_HOST_LSB 2 +#define RPI_MIPICFG_INTR_CSI_HOST_ACCESS "RO" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_INTR_DSI_DMA +// Description : None +#define RPI_MIPICFG_INTR_DSI_DMA_RESET 0x0 +#define RPI_MIPICFG_INTR_DSI_DMA_BITS 0x00000002 +#define RPI_MIPICFG_INTR_DSI_DMA_MSB 1 +#define RPI_MIPICFG_INTR_DSI_DMA_LSB 1 +#define RPI_MIPICFG_INTR_DSI_DMA_ACCESS "RO" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_INTR_CSI_DMA +// Description : None +#define RPI_MIPICFG_INTR_CSI_DMA_RESET 0x0 +#define RPI_MIPICFG_INTR_CSI_DMA_BITS 0x00000001 +#define RPI_MIPICFG_INTR_CSI_DMA_MSB 0 +#define RPI_MIPICFG_INTR_CSI_DMA_LSB 0 +#define RPI_MIPICFG_INTR_CSI_DMA_ACCESS "RO" +// ================================================================================ +// Register : RPI_MIPICFG_INTE +// JTAG access : synchronous +// Description : Interrupt Enable +#define RPI_MIPICFG_INTE_OFFSET 0x0000002c +#define RPI_MIPICFG_INTE_BITS 0x0000000f +#define RPI_MIPICFG_INTE_RESET 0x00000000 +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_INTE_DSI_HOST +// Description : None +#define RPI_MIPICFG_INTE_DSI_HOST_RESET 0x0 +#define RPI_MIPICFG_INTE_DSI_HOST_BITS 0x00000008 +#define RPI_MIPICFG_INTE_DSI_HOST_MSB 3 +#define RPI_MIPICFG_INTE_DSI_HOST_LSB 3 +#define RPI_MIPICFG_INTE_DSI_HOST_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_INTE_CSI_HOST +// Description : None +#define RPI_MIPICFG_INTE_CSI_HOST_RESET 0x0 +#define RPI_MIPICFG_INTE_CSI_HOST_BITS 0x00000004 +#define RPI_MIPICFG_INTE_CSI_HOST_MSB 2 +#define RPI_MIPICFG_INTE_CSI_HOST_LSB 2 +#define RPI_MIPICFG_INTE_CSI_HOST_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_INTE_DSI_DMA +// Description : None +#define RPI_MIPICFG_INTE_DSI_DMA_RESET 0x0 +#define RPI_MIPICFG_INTE_DSI_DMA_BITS 0x00000002 +#define RPI_MIPICFG_INTE_DSI_DMA_MSB 1 +#define RPI_MIPICFG_INTE_DSI_DMA_LSB 1 +#define RPI_MIPICFG_INTE_DSI_DMA_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_INTE_CSI_DMA +// Description : None +#define RPI_MIPICFG_INTE_CSI_DMA_RESET 0x0 +#define RPI_MIPICFG_INTE_CSI_DMA_BITS 0x00000001 +#define RPI_MIPICFG_INTE_CSI_DMA_MSB 0 +#define RPI_MIPICFG_INTE_CSI_DMA_LSB 0 +#define RPI_MIPICFG_INTE_CSI_DMA_ACCESS "RW" +// ================================================================================ +// Register : RPI_MIPICFG_INTF +// JTAG access : synchronous +// Description : Interrupt Force +#define RPI_MIPICFG_INTF_OFFSET 0x00000030 +#define RPI_MIPICFG_INTF_BITS 0x0000000f +#define RPI_MIPICFG_INTF_RESET 0x00000000 +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_INTF_DSI_HOST +// Description : None +#define RPI_MIPICFG_INTF_DSI_HOST_RESET 0x0 +#define RPI_MIPICFG_INTF_DSI_HOST_BITS 0x00000008 +#define RPI_MIPICFG_INTF_DSI_HOST_MSB 3 +#define RPI_MIPICFG_INTF_DSI_HOST_LSB 3 +#define RPI_MIPICFG_INTF_DSI_HOST_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_INTF_CSI_HOST +// Description : None +#define RPI_MIPICFG_INTF_CSI_HOST_RESET 0x0 +#define RPI_MIPICFG_INTF_CSI_HOST_BITS 0x00000004 +#define RPI_MIPICFG_INTF_CSI_HOST_MSB 2 +#define RPI_MIPICFG_INTF_CSI_HOST_LSB 2 +#define RPI_MIPICFG_INTF_CSI_HOST_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_INTF_DSI_DMA +// Description : None +#define RPI_MIPICFG_INTF_DSI_DMA_RESET 0x0 +#define RPI_MIPICFG_INTF_DSI_DMA_BITS 0x00000002 +#define RPI_MIPICFG_INTF_DSI_DMA_MSB 1 +#define RPI_MIPICFG_INTF_DSI_DMA_LSB 1 +#define RPI_MIPICFG_INTF_DSI_DMA_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_INTF_CSI_DMA +// Description : None +#define RPI_MIPICFG_INTF_CSI_DMA_RESET 0x0 +#define RPI_MIPICFG_INTF_CSI_DMA_BITS 0x00000001 +#define RPI_MIPICFG_INTF_CSI_DMA_MSB 0 +#define RPI_MIPICFG_INTF_CSI_DMA_LSB 0 +#define RPI_MIPICFG_INTF_CSI_DMA_ACCESS "RW" +// ================================================================================ +// Register : RPI_MIPICFG_INTS +// JTAG access : synchronous +// Description : Interrupt status after masking & forcing +#define RPI_MIPICFG_INTS_OFFSET 0x00000034 +#define RPI_MIPICFG_INTS_BITS 0x0000000f +#define RPI_MIPICFG_INTS_RESET 0x00000000 +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_INTS_DSI_HOST +// Description : None +#define RPI_MIPICFG_INTS_DSI_HOST_RESET 0x0 +#define RPI_MIPICFG_INTS_DSI_HOST_BITS 0x00000008 +#define RPI_MIPICFG_INTS_DSI_HOST_MSB 3 +#define RPI_MIPICFG_INTS_DSI_HOST_LSB 3 +#define RPI_MIPICFG_INTS_DSI_HOST_ACCESS "RO" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_INTS_CSI_HOST +// Description : None +#define RPI_MIPICFG_INTS_CSI_HOST_RESET 0x0 +#define RPI_MIPICFG_INTS_CSI_HOST_BITS 0x00000004 +#define RPI_MIPICFG_INTS_CSI_HOST_MSB 2 +#define RPI_MIPICFG_INTS_CSI_HOST_LSB 2 +#define RPI_MIPICFG_INTS_CSI_HOST_ACCESS "RO" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_INTS_DSI_DMA +// Description : None +#define RPI_MIPICFG_INTS_DSI_DMA_RESET 0x0 +#define RPI_MIPICFG_INTS_DSI_DMA_BITS 0x00000002 +#define RPI_MIPICFG_INTS_DSI_DMA_MSB 1 +#define RPI_MIPICFG_INTS_DSI_DMA_LSB 1 +#define RPI_MIPICFG_INTS_DSI_DMA_ACCESS "RO" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_INTS_CSI_DMA +// Description : None +#define RPI_MIPICFG_INTS_CSI_DMA_RESET 0x0 +#define RPI_MIPICFG_INTS_CSI_DMA_BITS 0x00000001 +#define RPI_MIPICFG_INTS_CSI_DMA_MSB 0 +#define RPI_MIPICFG_INTS_CSI_DMA_LSB 0 +#define RPI_MIPICFG_INTS_CSI_DMA_ACCESS "RO" +// ================================================================================ +// Register : RPI_MIPICFG_BLOCK_ID +// JTAG access : asynchronous +// Description : Block Identifier +#define RPI_MIPICFG_BLOCK_ID_OFFSET 0x00000038 +#define RPI_MIPICFG_BLOCK_ID_BITS 0xffffffff +#define RPI_MIPICFG_BLOCK_ID_RESET 0x4d495049 +#define RPI_MIPICFG_BLOCK_ID_MSB 31 +#define RPI_MIPICFG_BLOCK_ID_LSB 0 +#define RPI_MIPICFG_BLOCK_ID_ACCESS "RO" +// ================================================================================ +// Register : RPI_MIPICFG_INSTANCE_ID +// JTAG access : asynchronous +// Description : Block Instance Identifier +#define RPI_MIPICFG_INSTANCE_ID_OFFSET 0x0000003c +#define RPI_MIPICFG_INSTANCE_ID_BITS 0x0000000f +#define RPI_MIPICFG_INSTANCE_ID_RESET 0x00000000 +#define RPI_MIPICFG_INSTANCE_ID_MSB 3 +#define RPI_MIPICFG_INSTANCE_ID_LSB 0 +#define RPI_MIPICFG_INSTANCE_ID_ACCESS "RO" +// ================================================================================ +// Register : RPI_MIPICFG_RSTSEQ_AUTO +// JTAG access : synchronous +// Description : None +#define RPI_MIPICFG_RSTSEQ_AUTO_OFFSET 0x00000040 +#define RPI_MIPICFG_RSTSEQ_AUTO_BITS 0x00000007 +#define RPI_MIPICFG_RSTSEQ_AUTO_RESET 0x00000007 +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_RSTSEQ_AUTO_CSI +// Description : 1 = reset is controlled by the sequencer +// 0 = reset is controlled by rstseq_ctrl +#define RPI_MIPICFG_RSTSEQ_AUTO_CSI_RESET 0x1 +#define RPI_MIPICFG_RSTSEQ_AUTO_CSI_BITS 0x00000004 +#define RPI_MIPICFG_RSTSEQ_AUTO_CSI_MSB 2 +#define RPI_MIPICFG_RSTSEQ_AUTO_CSI_LSB 2 +#define RPI_MIPICFG_RSTSEQ_AUTO_CSI_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_RSTSEQ_AUTO_DPI +// Description : 1 = reset is controlled by the sequencer +// 0 = reset is controlled by rstseq_ctrl +#define RPI_MIPICFG_RSTSEQ_AUTO_DPI_RESET 0x1 +#define RPI_MIPICFG_RSTSEQ_AUTO_DPI_BITS 0x00000002 +#define RPI_MIPICFG_RSTSEQ_AUTO_DPI_MSB 1 +#define RPI_MIPICFG_RSTSEQ_AUTO_DPI_LSB 1 +#define RPI_MIPICFG_RSTSEQ_AUTO_DPI_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_RSTSEQ_AUTO_BUSADAPTER +// Description : 1 = reset is controlled by the sequencer +// 0 = reset is controlled by rstseq_ctrl +#define RPI_MIPICFG_RSTSEQ_AUTO_BUSADAPTER_RESET 0x1 +#define RPI_MIPICFG_RSTSEQ_AUTO_BUSADAPTER_BITS 0x00000001 +#define RPI_MIPICFG_RSTSEQ_AUTO_BUSADAPTER_MSB 0 +#define RPI_MIPICFG_RSTSEQ_AUTO_BUSADAPTER_LSB 0 +#define RPI_MIPICFG_RSTSEQ_AUTO_BUSADAPTER_ACCESS "RW" +// ================================================================================ +// Register : RPI_MIPICFG_RSTSEQ_PARALLEL +// JTAG access : synchronous +// Description : None +#define RPI_MIPICFG_RSTSEQ_PARALLEL_OFFSET 0x00000044 +#define RPI_MIPICFG_RSTSEQ_PARALLEL_BITS 0x00000007 +#define RPI_MIPICFG_RSTSEQ_PARALLEL_RESET 0x00000006 +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_RSTSEQ_PARALLEL_CSI +// Description : Is this reset parallel (i.e. not part of the sequence) +#define RPI_MIPICFG_RSTSEQ_PARALLEL_CSI_RESET 0x1 +#define RPI_MIPICFG_RSTSEQ_PARALLEL_CSI_BITS 0x00000004 +#define RPI_MIPICFG_RSTSEQ_PARALLEL_CSI_MSB 2 +#define RPI_MIPICFG_RSTSEQ_PARALLEL_CSI_LSB 2 +#define RPI_MIPICFG_RSTSEQ_PARALLEL_CSI_ACCESS "RO" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_RSTSEQ_PARALLEL_DPI +// Description : Is this reset parallel (i.e. not part of the sequence) +#define RPI_MIPICFG_RSTSEQ_PARALLEL_DPI_RESET 0x1 +#define RPI_MIPICFG_RSTSEQ_PARALLEL_DPI_BITS 0x00000002 +#define RPI_MIPICFG_RSTSEQ_PARALLEL_DPI_MSB 1 +#define RPI_MIPICFG_RSTSEQ_PARALLEL_DPI_LSB 1 +#define RPI_MIPICFG_RSTSEQ_PARALLEL_DPI_ACCESS "RO" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_RSTSEQ_PARALLEL_BUSADAPTER +// Description : Is this reset parallel (i.e. not part of the sequence) +#define RPI_MIPICFG_RSTSEQ_PARALLEL_BUSADAPTER_RESET 0x0 +#define RPI_MIPICFG_RSTSEQ_PARALLEL_BUSADAPTER_BITS 0x00000001 +#define RPI_MIPICFG_RSTSEQ_PARALLEL_BUSADAPTER_MSB 0 +#define RPI_MIPICFG_RSTSEQ_PARALLEL_BUSADAPTER_LSB 0 +#define RPI_MIPICFG_RSTSEQ_PARALLEL_BUSADAPTER_ACCESS "RO" +// ================================================================================ +// Register : RPI_MIPICFG_RSTSEQ_CTRL +// JTAG access : synchronous +// Description : None +#define RPI_MIPICFG_RSTSEQ_CTRL_OFFSET 0x00000048 +#define RPI_MIPICFG_RSTSEQ_CTRL_BITS 0x00000007 +#define RPI_MIPICFG_RSTSEQ_CTRL_RESET 0x00000000 +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_RSTSEQ_CTRL_CSI +// Description : 1 = keep the reset asserted +// 0 = keep the reset deasserted +// This is ignored if rstseq_auto=1 +#define RPI_MIPICFG_RSTSEQ_CTRL_CSI_RESET 0x0 +#define RPI_MIPICFG_RSTSEQ_CTRL_CSI_BITS 0x00000004 +#define RPI_MIPICFG_RSTSEQ_CTRL_CSI_MSB 2 +#define RPI_MIPICFG_RSTSEQ_CTRL_CSI_LSB 2 +#define RPI_MIPICFG_RSTSEQ_CTRL_CSI_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_RSTSEQ_CTRL_DPI +// Description : 1 = keep the reset asserted +// 0 = keep the reset deasserted +// This is ignored if rstseq_auto=1 +#define RPI_MIPICFG_RSTSEQ_CTRL_DPI_RESET 0x0 +#define RPI_MIPICFG_RSTSEQ_CTRL_DPI_BITS 0x00000002 +#define RPI_MIPICFG_RSTSEQ_CTRL_DPI_MSB 1 +#define RPI_MIPICFG_RSTSEQ_CTRL_DPI_LSB 1 +#define RPI_MIPICFG_RSTSEQ_CTRL_DPI_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_RSTSEQ_CTRL_BUSADAPTER +// Description : 1 = keep the reset asserted +// 0 = keep the reset deasserted +// This is ignored if rstseq_auto=1 +#define RPI_MIPICFG_RSTSEQ_CTRL_BUSADAPTER_RESET 0x0 +#define RPI_MIPICFG_RSTSEQ_CTRL_BUSADAPTER_BITS 0x00000001 +#define RPI_MIPICFG_RSTSEQ_CTRL_BUSADAPTER_MSB 0 +#define RPI_MIPICFG_RSTSEQ_CTRL_BUSADAPTER_LSB 0 +#define RPI_MIPICFG_RSTSEQ_CTRL_BUSADAPTER_ACCESS "RW" +// ================================================================================ +// Register : RPI_MIPICFG_RSTSEQ_TRIG +// JTAG access : synchronous +// Description : None +#define RPI_MIPICFG_RSTSEQ_TRIG_OFFSET 0x0000004c +#define RPI_MIPICFG_RSTSEQ_TRIG_BITS 0x00000007 +#define RPI_MIPICFG_RSTSEQ_TRIG_RESET 0x00000000 +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_RSTSEQ_TRIG_CSI +// Description : Pulses the reset output +#define RPI_MIPICFG_RSTSEQ_TRIG_CSI_RESET 0x0 +#define RPI_MIPICFG_RSTSEQ_TRIG_CSI_BITS 0x00000004 +#define RPI_MIPICFG_RSTSEQ_TRIG_CSI_MSB 2 +#define RPI_MIPICFG_RSTSEQ_TRIG_CSI_LSB 2 +#define RPI_MIPICFG_RSTSEQ_TRIG_CSI_ACCESS "SC" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_RSTSEQ_TRIG_DPI +// Description : Pulses the reset output +#define RPI_MIPICFG_RSTSEQ_TRIG_DPI_RESET 0x0 +#define RPI_MIPICFG_RSTSEQ_TRIG_DPI_BITS 0x00000002 +#define RPI_MIPICFG_RSTSEQ_TRIG_DPI_MSB 1 +#define RPI_MIPICFG_RSTSEQ_TRIG_DPI_LSB 1 +#define RPI_MIPICFG_RSTSEQ_TRIG_DPI_ACCESS "SC" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_RSTSEQ_TRIG_BUSADAPTER +// Description : Pulses the reset output +#define RPI_MIPICFG_RSTSEQ_TRIG_BUSADAPTER_RESET 0x0 +#define RPI_MIPICFG_RSTSEQ_TRIG_BUSADAPTER_BITS 0x00000001 +#define RPI_MIPICFG_RSTSEQ_TRIG_BUSADAPTER_MSB 0 +#define RPI_MIPICFG_RSTSEQ_TRIG_BUSADAPTER_LSB 0 +#define RPI_MIPICFG_RSTSEQ_TRIG_BUSADAPTER_ACCESS "SC" +// ================================================================================ +// Register : RPI_MIPICFG_RSTSEQ_DONE +// JTAG access : synchronous +// Description : None +#define RPI_MIPICFG_RSTSEQ_DONE_OFFSET 0x00000050 +#define RPI_MIPICFG_RSTSEQ_DONE_BITS 0x00000007 +#define RPI_MIPICFG_RSTSEQ_DONE_RESET 0x00000000 +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_RSTSEQ_DONE_CSI +// Description : Indicates the current state of the reset +#define RPI_MIPICFG_RSTSEQ_DONE_CSI_RESET 0x0 +#define RPI_MIPICFG_RSTSEQ_DONE_CSI_BITS 0x00000004 +#define RPI_MIPICFG_RSTSEQ_DONE_CSI_MSB 2 +#define RPI_MIPICFG_RSTSEQ_DONE_CSI_LSB 2 +#define RPI_MIPICFG_RSTSEQ_DONE_CSI_ACCESS "RO" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_RSTSEQ_DONE_DPI +// Description : Indicates the current state of the reset +#define RPI_MIPICFG_RSTSEQ_DONE_DPI_RESET 0x0 +#define RPI_MIPICFG_RSTSEQ_DONE_DPI_BITS 0x00000002 +#define RPI_MIPICFG_RSTSEQ_DONE_DPI_MSB 1 +#define RPI_MIPICFG_RSTSEQ_DONE_DPI_LSB 1 +#define RPI_MIPICFG_RSTSEQ_DONE_DPI_ACCESS "RO" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_RSTSEQ_DONE_BUSADAPTER +// Description : Indicates the current state of the reset +#define RPI_MIPICFG_RSTSEQ_DONE_BUSADAPTER_RESET 0x0 +#define RPI_MIPICFG_RSTSEQ_DONE_BUSADAPTER_BITS 0x00000001 +#define RPI_MIPICFG_RSTSEQ_DONE_BUSADAPTER_MSB 0 +#define RPI_MIPICFG_RSTSEQ_DONE_BUSADAPTER_LSB 0 +#define RPI_MIPICFG_RSTSEQ_DONE_BUSADAPTER_ACCESS "RO" +// ================================================================================ +// Register : RPI_MIPICFG_DFTSS +// JTAG access : asynchronous +// Description : None +#define RPI_MIPICFG_DFTSS_OFFSET 0x00000054 +#define RPI_MIPICFG_DFTSS_BITS 0x0000001f +#define RPI_MIPICFG_DFTSS_RESET 0x00000000 +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DFTSS_JTAG_COPY +// Description : None +#define RPI_MIPICFG_DFTSS_JTAG_COPY_RESET 0x0 +#define RPI_MIPICFG_DFTSS_JTAG_COPY_BITS 0x00000010 +#define RPI_MIPICFG_DFTSS_JTAG_COPY_MSB 4 +#define RPI_MIPICFG_DFTSS_JTAG_COPY_LSB 4 +#define RPI_MIPICFG_DFTSS_JTAG_COPY_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DFTSS_JTAG_ACCESS_ONLY +// Description : None +#define RPI_MIPICFG_DFTSS_JTAG_ACCESS_ONLY_RESET 0x0 +#define RPI_MIPICFG_DFTSS_JTAG_ACCESS_ONLY_BITS 0x00000008 +#define RPI_MIPICFG_DFTSS_JTAG_ACCESS_ONLY_MSB 3 +#define RPI_MIPICFG_DFTSS_JTAG_ACCESS_ONLY_LSB 3 +#define RPI_MIPICFG_DFTSS_JTAG_ACCESS_ONLY_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DFTSS_BYPASS_OUTSYNCS +// Description : None +#define RPI_MIPICFG_DFTSS_BYPASS_OUTSYNCS_RESET 0x0 +#define RPI_MIPICFG_DFTSS_BYPASS_OUTSYNCS_BITS 0x00000004 +#define RPI_MIPICFG_DFTSS_BYPASS_OUTSYNCS_MSB 2 +#define RPI_MIPICFG_DFTSS_BYPASS_OUTSYNCS_LSB 2 +#define RPI_MIPICFG_DFTSS_BYPASS_OUTSYNCS_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DFTSS_BYPASS_INSYNCS +// Description : None +#define RPI_MIPICFG_DFTSS_BYPASS_INSYNCS_RESET 0x0 +#define RPI_MIPICFG_DFTSS_BYPASS_INSYNCS_BITS 0x00000002 +#define RPI_MIPICFG_DFTSS_BYPASS_INSYNCS_MSB 1 +#define RPI_MIPICFG_DFTSS_BYPASS_INSYNCS_LSB 1 +#define RPI_MIPICFG_DFTSS_BYPASS_INSYNCS_ACCESS "RW" +// -------------------------------------------------------------------------------- +// Field : RPI_MIPICFG_DFTSS_BYPASS_RESETSYNCS +// Description : None +#define RPI_MIPICFG_DFTSS_BYPASS_RESETSYNCS_RESET 0x0 +#define RPI_MIPICFG_DFTSS_BYPASS_RESETSYNCS_BITS 0x00000001 +#define RPI_MIPICFG_DFTSS_BYPASS_RESETSYNCS_MSB 0 +#define RPI_MIPICFG_DFTSS_BYPASS_RESETSYNCS_LSB 0 +#define RPI_MIPICFG_DFTSS_BYPASS_RESETSYNCS_ACCESS "RW" + +#define CFG_WRITE(reg, val) writel((val), dsi->hw_base[RP1DSI_HW_BLOCK_CFG] + (reg ## _OFFSET)) +#define CFG_READ(reg) readl(dsi->hw_base[RP1DSI_HW_BLOCK_CFG] + (reg ## _OFFSET)) + +/* ------------------------------- DPHY setup stuff ------------------------ */ + +static void dphy_transaction(struct rp1_dsi *dsi, uint8_t test_code, uint8_t test_data) +{ + /* + * See pg 101 of mipi dphy bidir databook + * Assume we start with testclk high. + * Each APB write takes at least 10ns and we ignore TESTDOUT + * so there is no need for extra delays between the transitions. + */ + + DSI_WRITE(DSI_PHY_TST_CTRL1, test_code | DPHY_CTRL1_PHY_TESTEN_BITS); + DSI_WRITE(DSI_PHY_TST_CTRL0, 0); + DSI_READ(DSI_PHY_TST_CTRL1); /* XXX possibly not needed */ + DSI_WRITE(DSI_PHY_TST_CTRL1, test_data); + DSI_WRITE(DSI_PHY_TST_CTRL0, DPHY_CTRL0_PHY_TESTCLK_BITS); +} + +static u64 dphy_get_div(u32 refclk, u64 vco_freq, u32 *ptr_m, u32 *ptr_n) +{ + /* + * See pg 77-78 of dphy databook + * fvco = m/n * refclk + * with the limit + * 40MHz >= fREFCLK / N >= 5MHz + * M (multiplier) must be an even number between 2 and 300 + * N (input divider) must be an integer between 1 and 100 + * + * In practice, given a 50MHz reference clock, it can produce any + * multiple of 10MHz, 11.1111MHz, 12.5MHz, 14.286MHz or 16.667MHz + * with < 1% error for all frequencies above 495MHz. + * + * vco_freq should be set to the lane bit rate (not the MIPI clock + * which is half of this). These frequencies are now measured in Hz. + * They should fit within u32, but u64 is needed for calculations. + */ + + static const u32 REF_DIVN_MAX = 40000000; + static const u32 REF_DIVN_MIN = 5000000; + u32 n, best_n, best_m; + u64 best_err = vco_freq; + + for (n = 1 + refclk / REF_DIVN_MAX; n * REF_DIVN_MIN <= refclk && n < 100; ++n) { + u32 half_m = DIV_U64_ROUND_CLOSEST(n * vco_freq, 2 * refclk); + + if (half_m < 150) { + u64 f = div_u64(mul_u32_u32(2 * half_m, refclk), n); + u64 err = (f > vco_freq) ? f - vco_freq : vco_freq - f; + + if (err < best_err) { + best_n = n; + best_m = 2 * half_m; + best_err = err; + if (err == 0) + break; + } + } + } + + if (64 * best_err >= vco_freq) + return 0; + + *ptr_n = best_n; + *ptr_m = best_m; + return div_u64(mul_u32_u32(best_m, refclk), best_n); +} + +struct hsfreq_range { + u16 mhz_max; + u8 hsfreqrange; + u8 clk_lp2hs; + u8 clk_hs2lp; + u8 data_lp2hs; /* excluding clk lane entry */ + u8 data_hs2lp; +}; + +/* See Table A-3 on page 258 of dphy databook */ +static const struct hsfreq_range hsfreq_table[] = { + { 89, 0b000000, 32, 20, 26, 13 }, + { 99, 0b010000, 35, 23, 28, 14 }, + { 109, 0b100000, 32, 22, 26, 13 }, + { 129, 0b000001, 31, 20, 27, 13 }, + { 139, 0b010001, 33, 22, 26, 14 }, + { 149, 0b100001, 33, 21, 26, 14 }, + { 169, 0b000010, 32, 20, 27, 13 }, + { 179, 0b010010, 36, 23, 30, 15 }, + { 199, 0b100010, 40, 22, 33, 15 }, + { 219, 0b000011, 40, 22, 33, 15 }, + { 239, 0b010011, 44, 24, 36, 16 }, + { 249, 0b100011, 48, 24, 38, 17 }, + { 269, 0b000100, 48, 24, 38, 17 }, + { 299, 0b010100, 50, 27, 41, 18 }, + { 329, 0b000101, 56, 28, 45, 18 }, + { 359, 0b010101, 59, 28, 48, 19 }, + { 399, 0b100101, 61, 30, 50, 20 }, + { 449, 0b000110, 67, 31, 55, 21 }, + { 499, 0b010110, 73, 31, 59, 22 }, + { 549, 0b000111, 79, 36, 63, 24 }, + { 599, 0b010111, 83, 37, 68, 25 }, + { 649, 0b001000, 90, 38, 73, 27 }, + { 699, 0b011000, 95, 40, 77, 28 }, + { 749, 0b001001, 102, 40, 84, 28 }, + { 799, 0b011001, 106, 42, 87, 30 }, + { 849, 0b101001, 113, 44, 93, 31 }, + { 899, 0b111001, 118, 47, 98, 32 }, + { 949, 0b001010, 124, 47, 102, 34 }, + { 999, 0b011010, 130, 49, 107, 35 }, + { 1049, 0b101010, 135, 51, 111, 37 }, + { 1099, 0b111010, 139, 51, 114, 38 }, + { 1149, 0b001011, 146, 54, 120, 40 }, + { 1199, 0b011011, 153, 57, 125, 41 }, + { 1249, 0b101011, 158, 58, 130, 42 }, + { 1299, 0b111011, 163, 58, 135, 44 }, + { 1349, 0b001100, 168, 60, 140, 45 }, + { 1399, 0b011100, 172, 64, 144, 47 }, + { 1449, 0b101100, 176, 65, 148, 48 }, + { 1500, 0b111100, 181, 66, 153, 50 }, +}; + +static void dphy_set_hsfreqrange(struct rp1_dsi *dsi, u32 freq_mhz) +{ + unsigned int i; + + if (freq_mhz < 80 || freq_mhz > 1500) + drm_err(dsi->drm, "DPHY: Frequency %u MHz out of range\n", + freq_mhz); + + for (i = 0; i < ARRAY_SIZE(hsfreq_table) - 1; i++) { + if (freq_mhz <= hsfreq_table[i].mhz_max) + break; + } + + dsi->hsfreq_index = i; + dphy_transaction(dsi, DPHY_HS_RX_CTRL_LANE0_OFFSET, + hsfreq_table[i].hsfreqrange << 1); +} + +static u32 dphy_configure_pll(struct rp1_dsi *dsi, u32 refclk, u32 vco_freq) +{ + u32 m = 0; + u32 n = 0; + u32 actual_vco_freq = dphy_get_div(refclk, vco_freq, &m, &n); + + if (actual_vco_freq) { + dphy_set_hsfreqrange(dsi, actual_vco_freq / 1000000); + /* Program m,n from registers */ + dphy_transaction(dsi, DPHY_PLL_DIV_CTRL_OFFSET, 0x30); + /* N (program N-1) */ + dphy_transaction(dsi, DPHY_PLL_INPUT_DIV_OFFSET, n - 1); + /* M[8:5] ?? */ + dphy_transaction(dsi, DPHY_PLL_LOOP_DIV_OFFSET, 0x80 | ((m - 1) >> 5)); + /* M[4:0] (program M-1) */ + dphy_transaction(dsi, DPHY_PLL_LOOP_DIV_OFFSET, ((m - 1) & 0x1F)); + drm_dbg_driver(dsi->drm, + "DPHY: vco freq want %uHz got %uHz = %d * (%uHz / %d), hsfreqrange = 0x%02x\n", + vco_freq, actual_vco_freq, m, refclk, n, + hsfreq_table[dsi->hsfreq_index].hsfreqrange); + } else { + drm_err(dsi->drm, + "rp1dsi: Error configuring DPHY PLL %uHz\n", vco_freq); + } + + return actual_vco_freq; +} + +static u32 dphy_init(struct rp1_dsi *dsi, u32 ref_freq, u32 vco_freq) +{ + u32 actual_vco_freq; + + /* Reset the PHY */ + DSI_WRITE(DSI_PHYRSTZ, 0); + DSI_WRITE(DSI_PHY_TST_CTRL0, DPHY_CTRL0_PHY_TESTCLK_BITS); + DSI_WRITE(DSI_PHY_TST_CTRL1, 0); + DSI_WRITE(DSI_PHY_TST_CTRL0, (DPHY_CTRL0_PHY_TESTCLK_BITS | DPHY_CTRL0_PHY_TESTCLR_BITS)); + udelay(1); + DSI_WRITE(DSI_PHY_TST_CTRL0, DPHY_CTRL0_PHY_TESTCLK_BITS); + udelay(1); + /* Since we are in DSI (not CSI2) mode here, start the PLL */ + actual_vco_freq = dphy_configure_pll(dsi, ref_freq, vco_freq); + udelay(1); + /* Unreset */ + DSI_WRITE(DSI_PHYRSTZ, DSI_PHYRSTZ_SHUTDOWNZ_BITS); + udelay(1); + DSI_WRITE(DSI_PHYRSTZ, (DSI_PHYRSTZ_SHUTDOWNZ_BITS | DSI_PHYRSTZ_RSTZ_BITS)); + udelay(1); /* so we can see PLL coming up? */ + + return actual_vco_freq; +} + +void rp1dsi_mipicfg_setup(struct rp1_dsi *dsi) +{ + /* Select DSI rather than CSI-2 */ + CFG_WRITE(RPI_MIPICFG_CFG, 0); + /* Enable DSIDMA interrupt only */ + CFG_WRITE(RPI_MIPICFG_INTE, RPI_MIPICFG_INTE_DSI_DMA_BITS); +} + +static unsigned long rp1dsi_refclk_freq(struct rp1_dsi *dsi) +{ + unsigned long u; + + u = (dsi->clocks[RP1DSI_CLOCK_REF]) ? clk_get_rate(dsi->clocks[RP1DSI_CLOCK_REF]) : 0; + if (u < 1 || u >= (1ul << 30)) + u = 50000000ul; /* default XOSC frequency */ + return u; +} + +static void rp1dsi_dpiclk_start(struct rp1_dsi *dsi, u32 byte_clock, + unsigned int bpp, unsigned int lanes) +{ + /* Dummy clk_set_rate() to declare the actual DSI byte-clock rate */ + clk_set_rate(dsi->clocks[RP1DSI_CLOCK_BYTE], byte_clock); + + /* + * Prefer the DSI byte-clock source where possible, so that DSI and DPI + * clocks will be in an exact ratio and downstream devices can recover + * perfect timings. But when DPI clock is faster, fall back on PLL_SYS. + * To defeat rounding errors, specify explicitly which source to use. + */ + if (bpp >= 8 * lanes) + clk_set_parent(dsi->clocks[RP1DSI_CLOCK_DPI], dsi->clocks[RP1DSI_CLOCK_BYTE]); + else if (dsi->clocks[RP1DSI_CLOCK_PLLSYS]) + clk_set_parent(dsi->clocks[RP1DSI_CLOCK_DPI], dsi->clocks[RP1DSI_CLOCK_PLLSYS]); + + clk_set_rate(dsi->clocks[RP1DSI_CLOCK_DPI], (4 * lanes * byte_clock) / (bpp >> 1)); + clk_prepare_enable(dsi->clocks[RP1DSI_CLOCK_DPI]); + drm_info(dsi->drm, + "rp1dsi: Nominal Byte clock %u DPI clock %lu (parent rate %lu)\n", + byte_clock, + clk_get_rate(dsi->clocks[RP1DSI_CLOCK_DPI]), + clk_get_rate(clk_get_parent(dsi->clocks[RP1DSI_CLOCK_DPI]))); +} + +static void rp1dsi_dpiclk_stop(struct rp1_dsi *dsi) +{ + if (dsi->clocks[RP1DSI_CLOCK_DPI]) + clk_disable_unprepare(dsi->clocks[RP1DSI_CLOCK_DPI]); +} + +/* Choose the internal on-the-bus DPI format, and DSI packing flag. */ +static u32 get_colorcode(enum mipi_dsi_pixel_format fmt) +{ + switch (fmt) { + case MIPI_DSI_FMT_RGB666: + return 0x104; + case MIPI_DSI_FMT_RGB666_PACKED: + return 0x003; + case MIPI_DSI_FMT_RGB565: + return 0x000; + case MIPI_DSI_FMT_RGB888: + return 0x005; + } + + /* This should be impossible as the format is validated in + * rp1dsi_host_attach + */ + WARN_ONCE(1, "Invalid colour format configured for DSI"); + return 0x005; +} + +/* Frequency limits for DPI, HS and LP clocks, and some magic numbers */ +#define RP1DSI_DPI_MAX_KHZ 200000 +#define RP1DSI_BYTE_CLK_MIN 10000000 +#define RP1DSI_BYTE_CLK_MAX 187500000 +#define RP1DSI_ESC_CLK_MAX 20000000 +#define RP1DSI_TO_CLK_DIV 0x50 +#define RP1DSI_LPRX_TO_VAL 0x40 +#define RP1DSI_BTA_TO_VAL 0xd00 + +void rp1dsi_dsi_setup(struct rp1_dsi *dsi, struct drm_display_mode const *mode) +{ + int cmdtim; + u32 timeout, mask, clkdiv; + unsigned int bpp = mipi_dsi_pixel_format_to_bpp(dsi->display_format); + u32 byte_clock = clamp((bpp * 125 * min(mode->clock, RP1DSI_DPI_MAX_KHZ)) / dsi->lanes, + RP1DSI_BYTE_CLK_MIN, RP1DSI_BYTE_CLK_MAX); + + DSI_WRITE(DSI_PHY_IF_CFG, dsi->lanes - 1); + DSI_WRITE(DSI_DPI_CFG_POL, 0); + DSI_WRITE(DSI_GEN_VCID, dsi->vc); + DSI_WRITE(DSI_DPI_COLOR_CODING, get_colorcode(dsi->display_format)); + + /* + * Flags to configure use of LP, EoTp, Burst Mode, Sync Events/Pulses. + * Note that Burst Mode implies Sync Events; the two flags need not be + * set concurrently, and in this RP1 variant *should not* both be set: + * doing so would (counter-intuitively) enable Sync Pulses and may fail + * if there is not sufficient time to return to LP11 state during HBP. + */ + mask = DSI_VID_MODE_LP_HFP_EN | DSI_VID_MODE_LP_HBP_EN | + DSI_VID_MODE_LP_VACT_EN | DSI_VID_MODE_LP_VFP_EN | + DSI_VID_MODE_LP_VBP_EN | DSI_VID_MODE_LP_VSA_EN; + if (dsi->display_flags & MIPI_DSI_MODE_LPM) + mask |= DSI_VID_MODE_LP_CMD_EN; + if (dsi->display_flags & MIPI_DSI_MODE_VIDEO_BURST) + mask |= DSI_VID_MODE_BURST; + else if (!(dsi->display_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)) + mask |= DSI_VID_MODE_SYNC_EVENTS; + else if (8 * dsi->lanes > bpp) + mask &= ~DSI_VID_MODE_LP_HBP_EN; /* PULSE && inexact DPICLK => fix HBP time */ + DSI_WRITE(DSI_VID_MODE_CFG, mask); + DSI_WRITE(DSI_CMD_MODE_CFG, + (dsi->display_flags & MIPI_DSI_MODE_LPM) ? DSI_CMD_MODE_ALL_LP : 0); + DSI_WRITE(DSI_PCKHDL_CFG, + DSI_PCKHDL_BTA_EN | + ((dsi->display_flags & MIPI_DSI_MODE_NO_EOT_PACKET) ? 0 : DSI_PCKHDL_EOTP_TX_EN)); + + /* Select Command Mode */ + DSI_WRITE(DSI_MODE_CFG, 1); + + /* Set timeouts and clock dividers */ + timeout = (bpp * mode->htotal * mode->vdisplay) / (7 * RP1DSI_TO_CLK_DIV * dsi->lanes); + if (timeout > 0xFFFFu) + timeout = 0; + DSI_WRITE(DSI_TO_CNT_CFG, (timeout << 16) | RP1DSI_LPRX_TO_VAL); + DSI_WRITE(DSI_BTA_TO_CNT, RP1DSI_BTA_TO_VAL); + clkdiv = max(2u, 1u + byte_clock / RP1DSI_ESC_CLK_MAX); /* byte clocks per escape clock */ + DSI_WRITE(DSI_CLKMGR_CFG, + (RP1DSI_TO_CLK_DIV << 8) | clkdiv); + + /* Configure video timings */ + DSI_WRITE(DSI_VID_PKT_SIZE, mode->hdisplay); + DSI_WRITE(DSI_VID_NUM_CHUNKS, 0); + DSI_WRITE(DSI_VID_NULL_SIZE, 0); + DSI_WRITE(DSI_VID_HSA_TIME, + (bpp * (mode->hsync_end - mode->hsync_start)) / (8 * dsi->lanes)); + DSI_WRITE(DSI_VID_HBP_TIME, + (bpp * (mode->htotal - mode->hsync_end)) / (8 * dsi->lanes)); + DSI_WRITE(DSI_VID_HLINE_TIME, (bpp * mode->htotal) / (8 * dsi->lanes)); + DSI_WRITE(DSI_VID_VSA_LINES, (mode->vsync_end - mode->vsync_start)); + DSI_WRITE(DSI_VID_VBP_LINES, (mode->vtotal - mode->vsync_end)); + DSI_WRITE(DSI_VID_VFP_LINES, (mode->vsync_start - mode->vdisplay)); + DSI_WRITE(DSI_VID_VACTIVE_LINES, mode->vdisplay); + + /* Init PHY */ + byte_clock = dphy_init(dsi, rp1dsi_refclk_freq(dsi), 8 * byte_clock) >> 3; + + DSI_WRITE(DSI_PHY_TMR_LPCLK_CFG, + (hsfreq_table[dsi->hsfreq_index].clk_lp2hs << DSI_PHY_TMR_LP2HS_LSB) | + (hsfreq_table[dsi->hsfreq_index].clk_hs2lp << DSI_PHY_TMR_HS2LP_LSB)); + DSI_WRITE(DSI_PHY_TMR_CFG, + (hsfreq_table[dsi->hsfreq_index].data_lp2hs << DSI_PHY_TMR_LP2HS_LSB) | + (hsfreq_table[dsi->hsfreq_index].data_hs2lp << DSI_PHY_TMR_HS2LP_LSB)); + + /* Estimate how many LP bytes can be sent during vertical blanking (Databook 3.6.2.1) */ + cmdtim = mode->htotal; + if (dsi->display_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) + cmdtim -= mode->hsync_end - mode->hsync_start; + cmdtim = (bpp * cmdtim - 64) / (8 * dsi->lanes); /* byte clocks after HSS and EoTp */ + cmdtim -= hsfreq_table[dsi->hsfreq_index].data_hs2lp; + cmdtim -= hsfreq_table[dsi->hsfreq_index].data_lp2hs; + cmdtim = (cmdtim / clkdiv) - 24; /* escape clocks for commands */ + cmdtim = max(0, cmdtim >> 4); /* bytes (at 2 clocks per bit) */ + drm_info(dsi->drm, "rp1dsi: Command time (outvact): %d\n", cmdtim); + DSI_WRITE(DSI_DPI_LP_CMD_TIM, cmdtim << 16); + + /* Wait for PLL lock */ + for (timeout = (1 << 14); timeout != 0; --timeout) { + usleep_range(10, 50); + if (DSI_READ(DSI_PHY_STATUS) & (1 << 0)) + break; + } + if (timeout == 0) + drm_err(dsi->drm, "RP1DSI: Time out waiting for PLL\n"); + + DSI_WRITE(DSI_LPCLK_CTRL, + (dsi->display_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) ? 0x3 : 0x1); + DSI_WRITE(DSI_PHY_TST_CTRL0, 0x2); + DSI_WRITE(DSI_PWR_UP, 0x1); /* power up */ + + /* Now it should be safe to start the external DPI clock divider */ + rp1dsi_dpiclk_start(dsi, byte_clock, bpp, dsi->lanes); + + /* Wait for all lane(s) to be in Stopstate */ + mask = (1 << 4); + if (dsi->lanes >= 2) + mask |= (1 << 7); + if (dsi->lanes >= 3) + mask |= (1 << 9); + if (dsi->lanes >= 4) + mask |= (1 << 11); + for (timeout = (1 << 10); timeout != 0; --timeout) { + usleep_range(10, 50); + if ((DSI_READ(DSI_PHY_STATUS) & mask) == mask) + break; + } + if (timeout == 0) + drm_err(dsi->drm, "RP1DSI: Time out waiting for lanes (%x %x)\n", + mask, DSI_READ(DSI_PHY_STATUS)); +} + +void rp1dsi_dsi_send(struct rp1_dsi *dsi, u32 hdr, int len, const u8 *buf, + bool use_lpm, bool req_ack) +{ + u32 val; + + /* Wait for both FIFOs empty */ + for (val = 256; val > 0; --val) { + if ((DSI_READ(DSI_CMD_PKT_STATUS) & 0xF) == 0x5) + break; + usleep_range(100, 150); + } + + /* + * Update global configuration flags for LP/HS and ACK options. + * XXX It's not clear if having empty FIFOs (checked above and below) guarantees that + * the last command has completed and been ACKed, or how closely these control registers + * align with command/payload FIFO writes (as each is an independent clock-crossing)? + */ + val = DSI_READ(DSI_VID_MODE_CFG); + if (use_lpm) + val |= DSI_VID_MODE_LP_CMD_EN; + else + val &= ~DSI_VID_MODE_LP_CMD_EN; + DSI_WRITE(DSI_VID_MODE_CFG, val); + val = (use_lpm) ? DSI_CMD_MODE_ALL_LP : 0; + if (req_ack) + val |= DSI_CMD_MODE_ACK_RQST_EN; + DSI_WRITE(DSI_CMD_MODE_CFG, val); + (void)DSI_READ(DSI_CMD_MODE_CFG); + + /* Write payload (in 32-bit words) and header */ + for (; len > 0; len -= 4) { + val = *buf++; + if (len > 1) + val |= (*buf++) << 8; + if (len > 2) + val |= (*buf++) << 16; + if (len > 3) + val |= (*buf++) << 24; + DSI_WRITE(DSI_GEN_PLD_DATA, val); + } + DSI_WRITE(DSI_GEN_HDR, hdr); + + /* Wait for both FIFOs empty */ + for (val = 256; val > 0; --val) { + if ((DSI_READ(DSI_CMD_PKT_STATUS) & 0xF) == 0x5) + break; + usleep_range(100, 150); + } +} + +int rp1dsi_dsi_recv(struct rp1_dsi *dsi, int len, u8 *buf) +{ + int i, j; + u32 val; + + /* Wait until not busy and FIFO not empty */ + for (i = 1024; i > 0; --i) { + val = DSI_READ(DSI_CMD_PKT_STATUS); + if ((val & ((1 << 6) | (1 << 4))) == 0) + break; + usleep_range(100, 150); + } + if (!i) { + drm_warn(dsi->drm, "Receive failed\n"); + return -EIO; + } + + for (i = 0; i < len; i += 4) { + /* Read fifo must not be empty before all bytes are read */ + if (DSI_READ(DSI_CMD_PKT_STATUS) & (1 << 4)) + break; + + val = DSI_READ(DSI_GEN_PLD_DATA); + for (j = 0; j < 4 && j + i < len; j++) + *buf++ = val >> (8 * j); + } + + return (i >= len) ? len : (i > 0) ? i : -EIO; +} + +void rp1dsi_dsi_stop(struct rp1_dsi *dsi) +{ + DSI_WRITE(DSI_MODE_CFG, 1); /* Return to Command Mode */ + DSI_WRITE(DSI_LPCLK_CTRL, 2); /* Stop the HS clock */ + DSI_WRITE(DSI_PWR_UP, 0x0); /* Power down host controller */ + DSI_WRITE(DSI_PHYRSTZ, 0); /* PHY into reset. */ + rp1dsi_dpiclk_stop(dsi); +} + +void rp1dsi_dsi_set_cmdmode(struct rp1_dsi *dsi, int mode) +{ + DSI_WRITE(DSI_MODE_CFG, mode); +} diff --git a/drivers/gpu/drm/rp1/rp1-vec/Kconfig b/drivers/gpu/drm/rp1/rp1-vec/Kconfig new file mode 100644 index 00000000000000..f646c01af5ae14 --- /dev/null +++ b/drivers/gpu/drm/rp1/rp1-vec/Kconfig @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DRM_RP1_VEC + tristate "DRM Support for RP1 VEC" + depends on DRM && MFD_RP1 + select DRM_GEM_DMA_HELPER + select DRM_KMS_HELPER + select DRM_VRAM_HELPER + select DRM_TTM + select DRM_TTM_HELPER + help + Choose this option to enable Video Out on RP1 diff --git a/drivers/gpu/drm/rp1/rp1-vec/Makefile b/drivers/gpu/drm/rp1/rp1-vec/Makefile new file mode 100644 index 00000000000000..7e941cad342e5e --- /dev/null +++ b/drivers/gpu/drm/rp1/rp1-vec/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only + +drm-rp1-vec-y := rp1_vec.o rp1_vec_hw.o rp1_vec_cfg.o + +obj-$(CONFIG_DRM_RP1_VEC) += drm-rp1-vec.o diff --git a/drivers/gpu/drm/rp1/rp1-vec/rp1_vec.c b/drivers/gpu/drm/rp1/rp1-vec/rp1_vec.c new file mode 100644 index 00000000000000..b10b94a7010d43 --- /dev/null +++ b/drivers/gpu/drm/rp1/rp1-vec/rp1_vec.c @@ -0,0 +1,602 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * DRM Driver for VEC output on Raspberry Pi RP1 + * + * Copyright (c) 2023 Raspberry Pi Limited. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/list.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/console.h> +#include <linux/debugfs.h> +#include <linux/uaccess.h> +#include <linux/io.h> +#include <linux/dma-mapping.h> +#include <linux/cred.h> +#include <drm/drm_drv.h> +#include <drm/drm_mm.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_managed.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_encoder.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_fbdev_ttm.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_gem.h> +#include <drm/drm_gem_atomic_helper.h> +#include <drm/drm_gem_dma_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_simple_kms_helper.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_modeset_helper_vtables.h> +#include <drm/drm_vblank.h> +#include <drm/drm_of.h> + +#include "rp1_vec.h" + +/* + * Linux doesn't make it easy to create custom video modes for the console + * with non-CVT timings; so add a module parameter for it. The format is: + * "<pclk>,<hact>,<hfp>,<hsync>,<hbp>,<vact>,<vfp>,<vsync>,<vbp>[,i]" + * (where each comma may be replaced by any sequence of punctuation). + * pclk should be 108000/n for 5 <= n <= 16 (twice this for "fake" modes). + */ + +static char *rp1vec_cmode_str; +module_param_named(cmode, rp1vec_cmode_str, charp, 0600); +MODULE_PARM_DESC(cmode, "Custom video mode:\n" + "\t\t<pclk>,<hact>,<hfp>,<hsync>,<hbp>,<vact>,<vfp>,<vsync>,<vbp>[,i]\n"); + +static struct drm_display_mode *rp1vec_parse_custom_mode(struct drm_device *dev) +{ + char const *p = rp1vec_cmode_str; + struct drm_display_mode *mode; + unsigned int n, vals[9]; + + if (!p) + return NULL; + + for (n = 0; n < 9; n++) { + unsigned int v = 0; + + if (!isdigit(*p)) + return NULL; + do { + v = 10u * v + (*p - '0'); + } while (isdigit(*++p)); + + vals[n] = v; + while (ispunct(*p)) + p++; + } + + mode = drm_mode_create(dev); + if (!mode) + return NULL; + + mode->clock = vals[0]; + mode->hdisplay = vals[1]; + mode->hsync_start = mode->hdisplay + vals[2]; + mode->hsync_end = mode->hsync_start + vals[3]; + mode->htotal = mode->hsync_end + vals[4]; + mode->vdisplay = vals[5]; + mode->vsync_start = mode->vdisplay + vals[6]; + mode->vsync_end = mode->vsync_start + vals[7]; + mode->vtotal = mode->vsync_end + vals[8]; + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + mode->flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC; + if (strchr(p, 'i')) + mode->flags |= DRM_MODE_FLAG_INTERLACE; + + return mode; +} + +static void rp1vec_pipe_update(struct drm_simple_display_pipe *pipe, + struct drm_plane_state *old_state) +{ + struct drm_pending_vblank_event *event; + unsigned long flags; + struct drm_framebuffer *fb = pipe->plane.state->fb; + struct rp1_vec *vec = pipe->crtc.dev->dev_private; + struct drm_gem_object *gem = fb ? drm_gem_fb_get_obj(fb, 0) : NULL; + struct drm_gem_dma_object *dma_obj = gem ? to_drm_gem_dma_obj(gem) : NULL; + bool can_update = fb && dma_obj && vec && vec->pipe_enabled; + + /* (Re-)start VEC where required; and update FB address */ + if (can_update) { + if (!vec->vec_running || fb->format->format != vec->cur_fmt) { + if (vec->vec_running && fb->format->format != vec->cur_fmt) { + rp1vec_hw_stop(vec); + vec->vec_running = false; + } + if (!vec->vec_running) { + rp1vec_hw_setup(vec, + fb->format->format, + &pipe->crtc.state->mode, + vec->connector.state->tv.mode); + vec->vec_running = true; + } + vec->cur_fmt = fb->format->format; + drm_crtc_vblank_on(&pipe->crtc); + } + rp1vec_hw_update(vec, dma_obj->dma_addr, fb->offsets[0], fb->pitches[0]); + } + + /* Check if VBLANK callback needs to be armed (or sent immediately in some error cases). + * Note there is a tiny probability of a race between rp1vec_dma_update() and IRQ; + * ordering it this way around is safe, but theoretically might delay an extra frame. + */ + spin_lock_irqsave(&pipe->crtc.dev->event_lock, flags); + event = pipe->crtc.state->event; + if (event) { + pipe->crtc.state->event = NULL; + if (can_update && drm_crtc_vblank_get(&pipe->crtc) == 0) + drm_crtc_arm_vblank_event(&pipe->crtc, event); + else + drm_crtc_send_vblank_event(&pipe->crtc, event); + } + spin_unlock_irqrestore(&pipe->crtc.dev->event_lock, flags); +} + +static void rp1vec_pipe_enable(struct drm_simple_display_pipe *pipe, + struct drm_crtc_state *crtc_state, + struct drm_plane_state *plane_state) +{ + struct rp1_vec *vec = pipe->crtc.dev->dev_private; + + dev_info(&vec->pdev->dev, __func__); + vec->pipe_enabled = true; + vec->cur_fmt = 0xdeadbeef; + rp1vec_vidout_setup(vec); + rp1vec_pipe_update(pipe, 0); +} + +static void rp1vec_pipe_disable(struct drm_simple_display_pipe *pipe) +{ + struct rp1_vec *vec = pipe->crtc.dev->dev_private; + + dev_info(&vec->pdev->dev, __func__); + drm_crtc_vblank_off(&pipe->crtc); + if (vec) { + if (vec->vec_running) { + rp1vec_hw_stop(vec); + vec->vec_running = false; + } + vec->pipe_enabled = false; + } +} + +static int rp1vec_pipe_enable_vblank(struct drm_simple_display_pipe *pipe) +{ + if (pipe && pipe->crtc.dev) { + struct rp1_vec *vec = pipe->crtc.dev->dev_private; + + if (vec) + rp1vec_hw_vblank_ctrl(vec, 1); + } + return 0; +} + +static void rp1vec_pipe_disable_vblank(struct drm_simple_display_pipe *pipe) +{ + if (pipe && pipe->crtc.dev) { + struct rp1_vec *vec = pipe->crtc.dev->dev_private; + + if (vec) + rp1vec_hw_vblank_ctrl(vec, 0); + } +} + +static const struct drm_simple_display_pipe_funcs rp1vec_pipe_funcs = { + .enable = rp1vec_pipe_enable, + .update = rp1vec_pipe_update, + .disable = rp1vec_pipe_disable, + .enable_vblank = rp1vec_pipe_enable_vblank, + .disable_vblank = rp1vec_pipe_disable_vblank, +}; + +static void rp1vec_connector_destroy(struct drm_connector *connector) +{ + drm_connector_unregister(connector); + drm_connector_cleanup(connector); +} + +/* + * Check the mode roughly matches something we can generate. + * The choice of hardware TV mode depends on total lines and frame rate. + * Within each hardware mode, allow pixel clock, image size and offsets + * to vary, up to a maximum horizontal active period and line count. + * Don't check sync timings here: the HW driver will sanitize them. + */ + +static enum drm_mode_status rp1vec_mode_valid(struct drm_device *dev, + const struct drm_display_mode *mode) +{ + int prog = !(mode->flags & DRM_MODE_FLAG_INTERLACE); + int fake_31khz = prog && mode->vtotal >= 500; + int vtotal_2fld = mode->vtotal << (prog && !fake_31khz); + int vdisplay_2fld = mode->vdisplay << (prog && !fake_31khz); + int real_clock = mode->clock >> fake_31khz; + + /* Check pixel clock is in the permitted range */ + if (real_clock < 6750) + return MODE_CLOCK_LOW; + else if (real_clock > 21600) + return MODE_CLOCK_HIGH; + + /* Try to match against the 525-line 60Hz mode (System M) */ + if (vtotal_2fld >= 524 && vtotal_2fld <= 526 && vdisplay_2fld <= 486 && + mode->htotal * vtotal_2fld > 32 * real_clock && + mode->htotal * vtotal_2fld < 34 * real_clock && + 37 * mode->hdisplay <= 2 * real_clock) /* 54us */ + return MODE_OK; + + /* All other supported TV Systems (625-, 405-, 819-line) are 50Hz */ + if (mode->htotal * vtotal_2fld > 39 * real_clock && + mode->htotal * vtotal_2fld < 41 * real_clock) { + if (vtotal_2fld >= 624 && vtotal_2fld <= 626 && vdisplay_2fld <= 576 && + 37 * mode->hdisplay <= 2 * real_clock) /* 54us */ + return MODE_OK; + + if (vtotal_2fld == 405 && vdisplay_2fld <= 380 && + 49 * mode->hdisplay <= 4 * real_clock) /* 81.6us */ + return MODE_OK; + + if (vtotal_2fld == 819 && vdisplay_2fld <= 738 && + 25 * mode->hdisplay <= real_clock) /* 40us */ + return MODE_OK; + } + + return MODE_BAD; +} + +static const struct drm_display_mode rp1vec_modes[6] = { + { /* Full size 525/60i with Rec.601 pixel rate */ + DRM_MODE("720x480i", DRM_MODE_TYPE_DRIVER, 13500, + 720, 720 + 16, 720 + 16 + 64, 858, 0, + 480, 480 + 6, 480 + 6 + 6, 525, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | + DRM_MODE_FLAG_INTERLACE) + }, + { /* Cropped and horizontally squashed to be TV-safe */ + DRM_MODE("704x432i", DRM_MODE_TYPE_DRIVER, 15429, + 704, 704 + 76, 704 + 76 + 72, 980, 0, + 432, 432 + 30, 432 + 30 + 6, 525, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | + DRM_MODE_FLAG_INTERLACE) + }, + { /* Full size 625/50i with Rec.601 pixel rate */ + DRM_MODE("720x576i", DRM_MODE_TYPE_DRIVER, 13500, + 720, 720 + 12, 720 + 12 + 64, 864, 0, + 576, 576 + 5, 576 + 5 + 5, 625, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | + DRM_MODE_FLAG_INTERLACE) + }, + { /* Cropped and squashed, for square(ish) pixels */ + DRM_MODE("704x512i", DRM_MODE_TYPE_DRIVER, 15429, + 704, 704 + 72, 704 + 72 + 72, 987, 0, + 512, 512 + 37, 512 + 37 + 5, 625, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | + DRM_MODE_FLAG_INTERLACE) + }, + { /* System A (405 lines) */ + DRM_MODE("544x380i", DRM_MODE_TYPE_DRIVER, 6750, + 544, 544 + 12, 544 + 12 + 60, 667, 0, + 380, 380 + 0, 380 + 0 + 8, 405, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | + DRM_MODE_FLAG_INTERLACE) + }, + { /* System E (819 lines) */ + DRM_MODE("848x738i", DRM_MODE_TYPE_DRIVER, 21600, + 848, 848 + 12, 848 + 12 + 54, 1055, 0, + 738, 738 + 6, 738 + 6 + 1, 819, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | + DRM_MODE_FLAG_INTERLACE) + } +}; + +/* + * Advertise a custom mode, if specified; then those from the table above. + * From each interlaced mode above, derive a half-height progressive one. + * + * This driver always supports all 525-line and 625-line standard modes + * regardless of connector's tv_mode; non-standard combinations generally + * default to PAL[-BDGHIK] or NTSC[-M] (with a special case for "PAL60"). + * + * The "vintage" standards (System A, System E) are advertised only when + * the default tv_mode was DRM_MODE_TV_MODE_MONOCHROME, and only interlaced. + */ + +static int rp1vec_connector_get_modes(struct drm_connector *connector) +{ + u64 tvstd; + int i, prog, limit, n = 0, preferred_lines = 525; + struct drm_display_mode *mode; + + if (!drm_object_property_get_default_value(&connector->base, + connector->dev->mode_config.tv_mode_property, + &tvstd)) + preferred_lines = (tvstd == DRM_MODE_TV_MODE_PAL || + tvstd == DRM_MODE_TV_MODE_PAL_N || + tvstd >= DRM_MODE_TV_MODE_SECAM) ? 625 : 525; + + mode = rp1vec_parse_custom_mode(connector->dev); + if (mode) { + if (rp1vec_mode_valid(connector->dev, mode) == 0) { + drm_mode_set_name(mode); + drm_mode_probed_add(connector, mode); + n++; + preferred_lines = 0; + } else { + drm_mode_destroy(connector->dev, mode); + } + } + + limit = (tvstd < DRM_MODE_TV_MODE_MONOCHROME) ? 4 : ARRAY_SIZE(rp1vec_modes); + for (i = 0; i < limit; i++) { + for (prog = 0; prog < 2; prog++) { + mode = drm_mode_duplicate(connector->dev, &rp1vec_modes[i]); + if (!mode) + return n; + + if (prog) { + mode->flags &= ~DRM_MODE_FLAG_INTERLACE; + mode->vdisplay >>= 1; + mode->vsync_start >>= 1; + mode->vsync_end >>= 1; + mode->vtotal >>= 1; + } else if (mode->hdisplay == 704 && mode->vtotal == preferred_lines) { + mode->type |= DRM_MODE_TYPE_PREFERRED; + } + drm_mode_set_name(mode); + drm_mode_probed_add(connector, mode); + n++; + + if (mode->vtotal == 405 || mode->vtotal == 819) + break; /* Don't offer progressive for Systems A, E */ + } + } + + return n; +} + +static void rp1vec_connector_reset(struct drm_connector *connector) +{ + drm_atomic_helper_connector_reset(connector); + drm_atomic_helper_connector_tv_reset(connector); +} + +static int rp1vec_connector_atomic_check(struct drm_connector *conn, + struct drm_atomic_state *state) +{ struct drm_connector_state *old_state = + drm_atomic_get_old_connector_state(state, conn); + struct drm_connector_state *new_state = + drm_atomic_get_new_connector_state(state, conn); + + if (new_state->crtc && old_state->tv.mode != new_state->tv.mode) { + struct drm_crtc_state *crtc_state = + drm_atomic_get_new_crtc_state(state, new_state->crtc); + + crtc_state->mode_changed = true; + } + + return 0; +} + +static const struct drm_connector_helper_funcs rp1vec_connector_helper_funcs = { + .get_modes = rp1vec_connector_get_modes, + .atomic_check = rp1vec_connector_atomic_check, +}; + +static const struct drm_connector_funcs rp1vec_connector_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = rp1vec_connector_destroy, + .reset = rp1vec_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static const struct drm_mode_config_funcs rp1vec_mode_funcs = { + .fb_create = drm_gem_fb_create, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, + .mode_valid = rp1vec_mode_valid, +}; + +static const u32 rp1vec_formats[] = { + DRM_FORMAT_XRGB8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_RGB888, + DRM_FORMAT_BGR888, + DRM_FORMAT_RGB565 +}; + +static void rp1vec_stopall(struct drm_device *drm) +{ + if (drm->dev_private) { + struct rp1_vec *vec = drm->dev_private; + + if (vec->vec_running || rp1vec_hw_busy(vec)) { + rp1vec_hw_stop(vec); + vec->vec_running = false; + } + rp1vec_vidout_poweroff(vec); + } +} + +DEFINE_DRM_GEM_DMA_FOPS(rp1vec_fops); + +static struct drm_driver rp1vec_driver = { + .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC, + .fops = &rp1vec_fops, + .name = "drm-rp1-vec", + .desc = "drm-rp1-vec", + .date = "0", + .major = 1, + .minor = 0, + DRM_GEM_DMA_DRIVER_OPS, + .release = rp1vec_stopall, +}; + +static int rp1vec_platform_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rp1_vec *vec; + int i, ret; + + dev_info(dev, __func__); + vec = devm_drm_dev_alloc(dev, &rp1vec_driver, struct rp1_vec, drm); + if (IS_ERR(vec)) { + ret = PTR_ERR(vec); + dev_err(dev, "%s devm_drm_dev_alloc %d", __func__, ret); + return ret; + } + vec->pdev = pdev; + + for (i = 0; i < RP1VEC_NUM_HW_BLOCKS; i++) { + vec->hw_base[i] = + devm_ioremap_resource(dev, + platform_get_resource(vec->pdev, IORESOURCE_MEM, i)); + if (IS_ERR(vec->hw_base[i])) { + ret = PTR_ERR(vec->hw_base[i]); + dev_err(dev, "Error memory mapping regs[%d]\n", i); + goto done_err; + } + } + ret = platform_get_irq(vec->pdev, 0); + if (ret > 0) + ret = devm_request_irq(dev, ret, rp1vec_hw_isr, + IRQF_SHARED, "rp1-vec", vec); + if (ret) { + dev_err(dev, "Unable to request interrupt\n"); + ret = -EINVAL; + goto done_err; + } + + vec->vec_clock = devm_clk_get(dev, NULL); + if (IS_ERR(vec->vec_clock)) { + ret = PTR_ERR(vec->vec_clock); + goto done_err; + } + ret = clk_prepare_enable(vec->vec_clock); + + ret = drmm_mode_config_init(&vec->drm); + if (ret) + goto done_err; + + /* Now we have all our resources, finish driver initialization */ + dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)); + init_completion(&vec->finished); + vec->drm.dev_private = vec; + platform_set_drvdata(pdev, &vec->drm); + + vec->drm.mode_config.min_width = 256; + vec->drm.mode_config.min_height = 128; + vec->drm.mode_config.max_width = 960; /* for "widescreen" @ 18MHz */ + vec->drm.mode_config.max_height = 738; /* for System E only */ + vec->drm.mode_config.preferred_depth = 32; + vec->drm.mode_config.prefer_shadow = 0; + vec->drm.mode_config.quirk_addfb_prefer_host_byte_order = true; + vec->drm.mode_config.funcs = &rp1vec_mode_funcs; + drm_vblank_init(&vec->drm, 1); + + ret = drm_mode_create_tv_properties(&vec->drm, RP1VEC_SUPPORTED_TV_MODES); + if (ret) + goto done_err; + + drm_connector_init(&vec->drm, &vec->connector, &rp1vec_connector_funcs, + DRM_MODE_CONNECTOR_Composite); + if (ret) + goto done_err; + + vec->connector.interlace_allowed = true; + drm_connector_helper_add(&vec->connector, &rp1vec_connector_helper_funcs); + + drm_object_attach_property(&vec->connector.base, + vec->drm.mode_config.tv_mode_property, + (vec->connector.cmdline_mode.tv_mode_specified) ? + vec->connector.cmdline_mode.tv_mode : + DRM_MODE_TV_MODE_NTSC); + + ret = drm_simple_display_pipe_init(&vec->drm, + &vec->pipe, + &rp1vec_pipe_funcs, + rp1vec_formats, + ARRAY_SIZE(rp1vec_formats), + NULL, + &vec->connector); + if (ret) + goto done_err; + + drm_mode_config_reset(&vec->drm); + + ret = drm_dev_register(&vec->drm, 0); + if (ret) + goto done_err; + + drm_fbdev_ttm_setup(&vec->drm, 32); + return ret; + +done_err: + dev_err(dev, "%s fail %d", __func__, ret); + return ret; +} + +static void rp1vec_platform_remove(struct platform_device *pdev) +{ + struct drm_device *drm = platform_get_drvdata(pdev); + + rp1vec_stopall(drm); + drm_dev_unregister(drm); + drm_atomic_helper_shutdown(drm); + drm_dev_put(drm); +} + +static void rp1vec_platform_shutdown(struct platform_device *pdev) +{ + struct drm_device *drm = platform_get_drvdata(pdev); + + rp1vec_stopall(drm); +} + +static const struct of_device_id rp1vec_of_match[] = { + { + .compatible = "raspberrypi,rp1vec", + }, + { /* sentinel */ }, +}; + +MODULE_DEVICE_TABLE(of, rp1vec_of_match); + +static struct platform_driver rp1vec_platform_driver = { + .probe = rp1vec_platform_probe, + .remove = rp1vec_platform_remove, + .shutdown = rp1vec_platform_shutdown, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = rp1vec_of_match, + }, +}; + +module_platform_driver(rp1vec_platform_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("DRM driver for Composite Video on Raspberry Pi RP1"); +MODULE_AUTHOR("Nick Hollinghurst"); diff --git a/drivers/gpu/drm/rp1/rp1-vec/rp1_vec.h b/drivers/gpu/drm/rp1/rp1-vec/rp1_vec.h new file mode 100644 index 00000000000000..ae283a25b0a460 --- /dev/null +++ b/drivers/gpu/drm/rp1/rp1-vec/rp1_vec.h @@ -0,0 +1,72 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * DRM Driver for DSI output on Raspberry Pi RP1 + * + * Copyright (c) 2023 Raspberry Pi Limited. + */ + +#include <linux/types.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <drm/drm_device.h> +#include <drm/drm_simple_kms_helper.h> + +#define MODULE_NAME "drm-rp1-vec" +#define DRIVER_NAME "drm-rp1-vec" + +/* ---------------------------------------------------------------------- */ + +#define RP1VEC_HW_BLOCK_VEC 0 +#define RP1VEC_HW_BLOCK_CFG 1 +#define RP1VEC_NUM_HW_BLOCKS 2 + +#define RP1VEC_SUPPORTED_TV_MODES \ + (BIT(DRM_MODE_TV_MODE_NTSC) | \ + BIT(DRM_MODE_TV_MODE_NTSC_443) | \ + BIT(DRM_MODE_TV_MODE_NTSC_J) | \ + BIT(DRM_MODE_TV_MODE_PAL) | \ + BIT(DRM_MODE_TV_MODE_PAL_M) | \ + BIT(DRM_MODE_TV_MODE_PAL_N) | \ + BIT(DRM_MODE_TV_MODE_MONOCHROME)) + +#define RP1VEC_VDAC_KHZ 108000 + +/* ---------------------------------------------------------------------- */ + +struct rp1_vec { + /* DRM base and platform device pointer */ + struct drm_device drm; + struct platform_device *pdev; + + /* Framework and helper objects */ + struct drm_simple_display_pipe pipe; + struct drm_connector connector; + + /* Clock. We assume this is always at 108 MHz. */ + struct clk *vec_clock; + + /* Block (VCC, CFG) base addresses, and current state */ + void __iomem *hw_base[RP1VEC_NUM_HW_BLOCKS]; + u32 cur_fmt; + bool fake_31khz, vec_running, pipe_enabled; + struct completion finished; +}; + +/* ---------------------------------------------------------------------- */ +/* Functions to control the VEC/DMA block */ + +void rp1vec_hw_setup(struct rp1_vec *vec, + u32 in_format, + struct drm_display_mode const *mode, + int tvstd); +void rp1vec_hw_update(struct rp1_vec *vec, dma_addr_t addr, u32 offset, u32 stride); +void rp1vec_hw_stop(struct rp1_vec *vec); +int rp1vec_hw_busy(struct rp1_vec *vec); +irqreturn_t rp1vec_hw_isr(int irq, void *dev); +void rp1vec_hw_vblank_ctrl(struct rp1_vec *vec, int enable); + +/* ---------------------------------------------------------------------- */ +/* Functions to control the VIDEO OUT CFG block and check RP1 platform */ + +void rp1vec_vidout_setup(struct rp1_vec *vec); +void rp1vec_vidout_poweroff(struct rp1_vec *vec); diff --git a/drivers/gpu/drm/rp1/rp1-vec/rp1_vec_cfg.c b/drivers/gpu/drm/rp1/rp1-vec/rp1_vec_cfg.c new file mode 100644 index 00000000000000..241dedee58894b --- /dev/null +++ b/drivers/gpu/drm/rp1/rp1-vec/rp1_vec_cfg.c @@ -0,0 +1,508 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * DRM Driver for DSI output on Raspberry Pi RP1 + * + * Copyright (c) 2023 Raspberry Pi Limited. + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/mm.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/printk.h> +#include <linux/rp1_platform.h> + +#include "rp1_vec.h" + +// ============================================================================= +// Register : VIDEO_OUT_CFG_SEL +// JTAG access : synchronous +// Description : Selects source: VEC or DPI +#define VIDEO_OUT_CFG_SEL_OFFSET 0x00000000 +#define VIDEO_OUT_CFG_SEL_BITS 0x00000013 +#define VIDEO_OUT_CFG_SEL_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_SEL_PCLK_INV +// Description : Select dpi_pclk output port polarity inversion. +#define VIDEO_OUT_CFG_SEL_PCLK_INV_RESET 0x0 +#define VIDEO_OUT_CFG_SEL_PCLK_INV_BITS 0x00000010 +#define VIDEO_OUT_CFG_SEL_PCLK_INV_MSB 4 +#define VIDEO_OUT_CFG_SEL_PCLK_INV_LSB 4 +#define VIDEO_OUT_CFG_SEL_PCLK_INV_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_SEL_PAD_MUX +// Description : VEC 1 DPI 0 +#define VIDEO_OUT_CFG_SEL_PAD_MUX_RESET 0x0 +#define VIDEO_OUT_CFG_SEL_PAD_MUX_BITS 0x00000002 +#define VIDEO_OUT_CFG_SEL_PAD_MUX_MSB 1 +#define VIDEO_OUT_CFG_SEL_PAD_MUX_LSB 1 +#define VIDEO_OUT_CFG_SEL_PAD_MUX_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_SEL_VDAC_MUX +// Description : VEC 1 DPI 0 +#define VIDEO_OUT_CFG_SEL_VDAC_MUX_RESET 0x0 +#define VIDEO_OUT_CFG_SEL_VDAC_MUX_BITS 0x00000001 +#define VIDEO_OUT_CFG_SEL_VDAC_MUX_MSB 0 +#define VIDEO_OUT_CFG_SEL_VDAC_MUX_LSB 0 +#define VIDEO_OUT_CFG_SEL_VDAC_MUX_ACCESS "RW" +// ============================================================================= +// Register : VIDEO_OUT_CFG_VDAC_CFG +// JTAG access : synchronous +// Description : Configure SNPS VDAC +#define VIDEO_OUT_CFG_VDAC_CFG_OFFSET 0x00000004 +#define VIDEO_OUT_CFG_VDAC_CFG_BITS 0x1fffffff +#define VIDEO_OUT_CFG_VDAC_CFG_RESET 0x0003ffff +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_VDAC_CFG_ENCTR +// Description : None +#define VIDEO_OUT_CFG_VDAC_CFG_ENCTR_RESET 0x0 +#define VIDEO_OUT_CFG_VDAC_CFG_ENCTR_BITS 0x1c000000 +#define VIDEO_OUT_CFG_VDAC_CFG_ENCTR_MSB 28 +#define VIDEO_OUT_CFG_VDAC_CFG_ENCTR_LSB 26 +#define VIDEO_OUT_CFG_VDAC_CFG_ENCTR_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_VDAC_CFG_ENSC +// Description : None +#define VIDEO_OUT_CFG_VDAC_CFG_ENSC_RESET 0x0 +#define VIDEO_OUT_CFG_VDAC_CFG_ENSC_BITS 0x03800000 +#define VIDEO_OUT_CFG_VDAC_CFG_ENSC_MSB 25 +#define VIDEO_OUT_CFG_VDAC_CFG_ENSC_LSB 23 +#define VIDEO_OUT_CFG_VDAC_CFG_ENSC_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_VDAC_CFG_ENDAC +// Description : None +#define VIDEO_OUT_CFG_VDAC_CFG_ENDAC_RESET 0x0 +#define VIDEO_OUT_CFG_VDAC_CFG_ENDAC_BITS 0x00700000 +#define VIDEO_OUT_CFG_VDAC_CFG_ENDAC_MSB 22 +#define VIDEO_OUT_CFG_VDAC_CFG_ENDAC_LSB 20 +#define VIDEO_OUT_CFG_VDAC_CFG_ENDAC_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_VDAC_CFG_ENVBG +// Description : None +#define VIDEO_OUT_CFG_VDAC_CFG_ENVBG_RESET 0x0 +#define VIDEO_OUT_CFG_VDAC_CFG_ENVBG_BITS 0x00080000 +#define VIDEO_OUT_CFG_VDAC_CFG_ENVBG_MSB 19 +#define VIDEO_OUT_CFG_VDAC_CFG_ENVBG_LSB 19 +#define VIDEO_OUT_CFG_VDAC_CFG_ENVBG_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_VDAC_CFG_ENEXTREF +// Description : None +#define VIDEO_OUT_CFG_VDAC_CFG_ENEXTREF_RESET 0x0 +#define VIDEO_OUT_CFG_VDAC_CFG_ENEXTREF_BITS 0x00040000 +#define VIDEO_OUT_CFG_VDAC_CFG_ENEXTREF_MSB 18 +#define VIDEO_OUT_CFG_VDAC_CFG_ENEXTREF_LSB 18 +#define VIDEO_OUT_CFG_VDAC_CFG_ENEXTREF_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_VDAC_CFG_DAC2GC +// Description : dac2 gain control +#define VIDEO_OUT_CFG_VDAC_CFG_DAC2GC_RESET 0x3f +#define VIDEO_OUT_CFG_VDAC_CFG_DAC2GC_BITS 0x0003f000 +#define VIDEO_OUT_CFG_VDAC_CFG_DAC2GC_MSB 17 +#define VIDEO_OUT_CFG_VDAC_CFG_DAC2GC_LSB 12 +#define VIDEO_OUT_CFG_VDAC_CFG_DAC2GC_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_VDAC_CFG_DAC1GC +// Description : dac1 gain control +#define VIDEO_OUT_CFG_VDAC_CFG_DAC1GC_RESET 0x3f +#define VIDEO_OUT_CFG_VDAC_CFG_DAC1GC_BITS 0x00000fc0 +#define VIDEO_OUT_CFG_VDAC_CFG_DAC1GC_MSB 11 +#define VIDEO_OUT_CFG_VDAC_CFG_DAC1GC_LSB 6 +#define VIDEO_OUT_CFG_VDAC_CFG_DAC1GC_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_VDAC_CFG_DAC0GC +// Description : dac0 gain control +#define VIDEO_OUT_CFG_VDAC_CFG_DAC0GC_RESET 0x3f +#define VIDEO_OUT_CFG_VDAC_CFG_DAC0GC_BITS 0x0000003f +#define VIDEO_OUT_CFG_VDAC_CFG_DAC0GC_MSB 5 +#define VIDEO_OUT_CFG_VDAC_CFG_DAC0GC_LSB 0 +#define VIDEO_OUT_CFG_VDAC_CFG_DAC0GC_ACCESS "RW" +// ============================================================================= +// Register : VIDEO_OUT_CFG_VDAC_STATUS +// JTAG access : synchronous +// Description : Read VDAC status +#define VIDEO_OUT_CFG_VDAC_STATUS_OFFSET 0x00000008 +#define VIDEO_OUT_CFG_VDAC_STATUS_BITS 0x00000017 +#define VIDEO_OUT_CFG_VDAC_STATUS_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_VDAC_STATUS_ENCTR3 +// Description : None +#define VIDEO_OUT_CFG_VDAC_STATUS_ENCTR3_RESET 0x0 +#define VIDEO_OUT_CFG_VDAC_STATUS_ENCTR3_BITS 0x00000010 +#define VIDEO_OUT_CFG_VDAC_STATUS_ENCTR3_MSB 4 +#define VIDEO_OUT_CFG_VDAC_STATUS_ENCTR3_LSB 4 +#define VIDEO_OUT_CFG_VDAC_STATUS_ENCTR3_ACCESS "RO" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_VDAC_STATUS_CABLEOUT +// Description : None +#define VIDEO_OUT_CFG_VDAC_STATUS_CABLEOUT_RESET "-" +#define VIDEO_OUT_CFG_VDAC_STATUS_CABLEOUT_BITS 0x00000007 +#define VIDEO_OUT_CFG_VDAC_STATUS_CABLEOUT_MSB 2 +#define VIDEO_OUT_CFG_VDAC_STATUS_CABLEOUT_LSB 0 +#define VIDEO_OUT_CFG_VDAC_STATUS_CABLEOUT_ACCESS "RO" +// ============================================================================= +// Register : VIDEO_OUT_CFG_MEM_PD +// JTAG access : synchronous +// Description : Control memory power down +#define VIDEO_OUT_CFG_MEM_PD_OFFSET 0x0000000c +#define VIDEO_OUT_CFG_MEM_PD_BITS 0x00000003 +#define VIDEO_OUT_CFG_MEM_PD_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_MEM_PD_VEC +// Description : None +#define VIDEO_OUT_CFG_MEM_PD_VEC_RESET 0x0 +#define VIDEO_OUT_CFG_MEM_PD_VEC_BITS 0x00000002 +#define VIDEO_OUT_CFG_MEM_PD_VEC_MSB 1 +#define VIDEO_OUT_CFG_MEM_PD_VEC_LSB 1 +#define VIDEO_OUT_CFG_MEM_PD_VEC_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_MEM_PD_DPI +// Description : None +#define VIDEO_OUT_CFG_MEM_PD_DPI_RESET 0x0 +#define VIDEO_OUT_CFG_MEM_PD_DPI_BITS 0x00000001 +#define VIDEO_OUT_CFG_MEM_PD_DPI_MSB 0 +#define VIDEO_OUT_CFG_MEM_PD_DPI_LSB 0 +#define VIDEO_OUT_CFG_MEM_PD_DPI_ACCESS "RW" +// ============================================================================= +// Register : VIDEO_OUT_CFG_TEST_OVERRIDE +// JTAG access : synchronous +// Description : None +#define VIDEO_OUT_CFG_TEST_OVERRIDE_OFFSET 0x00000010 +#define VIDEO_OUT_CFG_TEST_OVERRIDE_BITS 0xffffffff +#define VIDEO_OUT_CFG_TEST_OVERRIDE_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_TEST_OVERRIDE_PAD +// Description : None +#define VIDEO_OUT_CFG_TEST_OVERRIDE_PAD_RESET 0x0 +#define VIDEO_OUT_CFG_TEST_OVERRIDE_PAD_BITS 0x80000000 +#define VIDEO_OUT_CFG_TEST_OVERRIDE_PAD_MSB 31 +#define VIDEO_OUT_CFG_TEST_OVERRIDE_PAD_LSB 31 +#define VIDEO_OUT_CFG_TEST_OVERRIDE_PAD_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_TEST_OVERRIDE_VDAC +// Description : None +#define VIDEO_OUT_CFG_TEST_OVERRIDE_VDAC_RESET 0x0 +#define VIDEO_OUT_CFG_TEST_OVERRIDE_VDAC_BITS 0x40000000 +#define VIDEO_OUT_CFG_TEST_OVERRIDE_VDAC_MSB 30 +#define VIDEO_OUT_CFG_TEST_OVERRIDE_VDAC_LSB 30 +#define VIDEO_OUT_CFG_TEST_OVERRIDE_VDAC_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_TEST_OVERRIDE_RGBVAL +// Description : None +#define VIDEO_OUT_CFG_TEST_OVERRIDE_RGBVAL_RESET 0x00000000 +#define VIDEO_OUT_CFG_TEST_OVERRIDE_RGBVAL_BITS 0x3fffffff +#define VIDEO_OUT_CFG_TEST_OVERRIDE_RGBVAL_MSB 29 +#define VIDEO_OUT_CFG_TEST_OVERRIDE_RGBVAL_LSB 0 +#define VIDEO_OUT_CFG_TEST_OVERRIDE_RGBVAL_ACCESS "RW" +// ============================================================================= +// Register : VIDEO_OUT_CFG_INTR +// JTAG access : synchronous +// Description : Raw Interrupts +#define VIDEO_OUT_CFG_INTR_OFFSET 0x00000014 +#define VIDEO_OUT_CFG_INTR_BITS 0x00000003 +#define VIDEO_OUT_CFG_INTR_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_INTR_DPI +// Description : None +#define VIDEO_OUT_CFG_INTR_DPI_RESET 0x0 +#define VIDEO_OUT_CFG_INTR_DPI_BITS 0x00000002 +#define VIDEO_OUT_CFG_INTR_DPI_MSB 1 +#define VIDEO_OUT_CFG_INTR_DPI_LSB 1 +#define VIDEO_OUT_CFG_INTR_DPI_ACCESS "RO" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_INTR_VEC +// Description : None +#define VIDEO_OUT_CFG_INTR_VEC_RESET 0x0 +#define VIDEO_OUT_CFG_INTR_VEC_BITS 0x00000001 +#define VIDEO_OUT_CFG_INTR_VEC_MSB 0 +#define VIDEO_OUT_CFG_INTR_VEC_LSB 0 +#define VIDEO_OUT_CFG_INTR_VEC_ACCESS "RO" +// ============================================================================= +// Register : VIDEO_OUT_CFG_INTE +// JTAG access : synchronous +// Description : Interrupt Enable +#define VIDEO_OUT_CFG_INTE_OFFSET 0x00000018 +#define VIDEO_OUT_CFG_INTE_BITS 0x00000003 +#define VIDEO_OUT_CFG_INTE_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_INTE_DPI +// Description : None +#define VIDEO_OUT_CFG_INTE_DPI_RESET 0x0 +#define VIDEO_OUT_CFG_INTE_DPI_BITS 0x00000002 +#define VIDEO_OUT_CFG_INTE_DPI_MSB 1 +#define VIDEO_OUT_CFG_INTE_DPI_LSB 1 +#define VIDEO_OUT_CFG_INTE_DPI_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_INTE_VEC +// Description : None +#define VIDEO_OUT_CFG_INTE_VEC_RESET 0x0 +#define VIDEO_OUT_CFG_INTE_VEC_BITS 0x00000001 +#define VIDEO_OUT_CFG_INTE_VEC_MSB 0 +#define VIDEO_OUT_CFG_INTE_VEC_LSB 0 +#define VIDEO_OUT_CFG_INTE_VEC_ACCESS "RW" +// ============================================================================= +// Register : VIDEO_OUT_CFG_INTF +// JTAG access : synchronous +// Description : Interrupt Force +#define VIDEO_OUT_CFG_INTF_OFFSET 0x0000001c +#define VIDEO_OUT_CFG_INTF_BITS 0x00000003 +#define VIDEO_OUT_CFG_INTF_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_INTF_DPI +// Description : None +#define VIDEO_OUT_CFG_INTF_DPI_RESET 0x0 +#define VIDEO_OUT_CFG_INTF_DPI_BITS 0x00000002 +#define VIDEO_OUT_CFG_INTF_DPI_MSB 1 +#define VIDEO_OUT_CFG_INTF_DPI_LSB 1 +#define VIDEO_OUT_CFG_INTF_DPI_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_INTF_VEC +// Description : None +#define VIDEO_OUT_CFG_INTF_VEC_RESET 0x0 +#define VIDEO_OUT_CFG_INTF_VEC_BITS 0x00000001 +#define VIDEO_OUT_CFG_INTF_VEC_MSB 0 +#define VIDEO_OUT_CFG_INTF_VEC_LSB 0 +#define VIDEO_OUT_CFG_INTF_VEC_ACCESS "RW" +// ============================================================================= +// Register : VIDEO_OUT_CFG_INTS +// JTAG access : synchronous +// Description : Interrupt status after masking & forcing +#define VIDEO_OUT_CFG_INTS_OFFSET 0x00000020 +#define VIDEO_OUT_CFG_INTS_BITS 0x00000003 +#define VIDEO_OUT_CFG_INTS_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_INTS_DPI +// Description : None +#define VIDEO_OUT_CFG_INTS_DPI_RESET 0x0 +#define VIDEO_OUT_CFG_INTS_DPI_BITS 0x00000002 +#define VIDEO_OUT_CFG_INTS_DPI_MSB 1 +#define VIDEO_OUT_CFG_INTS_DPI_LSB 1 +#define VIDEO_OUT_CFG_INTS_DPI_ACCESS "RO" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_INTS_VEC +// Description : None +#define VIDEO_OUT_CFG_INTS_VEC_RESET 0x0 +#define VIDEO_OUT_CFG_INTS_VEC_BITS 0x00000001 +#define VIDEO_OUT_CFG_INTS_VEC_MSB 0 +#define VIDEO_OUT_CFG_INTS_VEC_LSB 0 +#define VIDEO_OUT_CFG_INTS_VEC_ACCESS "RO" +// ============================================================================= +// Register : VIDEO_OUT_CFG_BLOCK_ID +// JTAG access : synchronous +// Description : Block Identifier +// Hexadecimal representation of "VOCF" +#define VIDEO_OUT_CFG_BLOCK_ID_OFFSET 0x00000024 +#define VIDEO_OUT_CFG_BLOCK_ID_BITS 0xffffffff +#define VIDEO_OUT_CFG_BLOCK_ID_RESET 0x564f4346 +#define VIDEO_OUT_CFG_BLOCK_ID_MSB 31 +#define VIDEO_OUT_CFG_BLOCK_ID_LSB 0 +#define VIDEO_OUT_CFG_BLOCK_ID_ACCESS "RO" +// ============================================================================= +// Register : VIDEO_OUT_CFG_INSTANCE_ID +// JTAG access : synchronous +// Description : Block Instance Identifier +#define VIDEO_OUT_CFG_INSTANCE_ID_OFFSET 0x00000028 +#define VIDEO_OUT_CFG_INSTANCE_ID_BITS 0x0000000f +#define VIDEO_OUT_CFG_INSTANCE_ID_RESET 0x00000000 +#define VIDEO_OUT_CFG_INSTANCE_ID_MSB 3 +#define VIDEO_OUT_CFG_INSTANCE_ID_LSB 0 +#define VIDEO_OUT_CFG_INSTANCE_ID_ACCESS "RO" +// ============================================================================= +// Register : VIDEO_OUT_CFG_RSTSEQ_AUTO +// JTAG access : synchronous +// Description : None +#define VIDEO_OUT_CFG_RSTSEQ_AUTO_OFFSET 0x0000002c +#define VIDEO_OUT_CFG_RSTSEQ_AUTO_BITS 0x00000007 +#define VIDEO_OUT_CFG_RSTSEQ_AUTO_RESET 0x00000007 +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_RSTSEQ_AUTO_VEC +// Description : 1 = reset is controlled by the sequencer +// 0 = reset is controlled by rstseq_ctrl +#define VIDEO_OUT_CFG_RSTSEQ_AUTO_VEC_RESET 0x1 +#define VIDEO_OUT_CFG_RSTSEQ_AUTO_VEC_BITS 0x00000004 +#define VIDEO_OUT_CFG_RSTSEQ_AUTO_VEC_MSB 2 +#define VIDEO_OUT_CFG_RSTSEQ_AUTO_VEC_LSB 2 +#define VIDEO_OUT_CFG_RSTSEQ_AUTO_VEC_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_RSTSEQ_AUTO_DPI +// Description : 1 = reset is controlled by the sequencer +// 0 = reset is controlled by rstseq_ctrl +#define VIDEO_OUT_CFG_RSTSEQ_AUTO_DPI_RESET 0x1 +#define VIDEO_OUT_CFG_RSTSEQ_AUTO_DPI_BITS 0x00000002 +#define VIDEO_OUT_CFG_RSTSEQ_AUTO_DPI_MSB 1 +#define VIDEO_OUT_CFG_RSTSEQ_AUTO_DPI_LSB 1 +#define VIDEO_OUT_CFG_RSTSEQ_AUTO_DPI_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_RSTSEQ_AUTO_BUSADAPTER +// Description : 1 = reset is controlled by the sequencer +// 0 = reset is controlled by rstseq_ctrl +#define VIDEO_OUT_CFG_RSTSEQ_AUTO_BUSADAPTER_RESET 0x1 +#define VIDEO_OUT_CFG_RSTSEQ_AUTO_BUSADAPTER_BITS 0x00000001 +#define VIDEO_OUT_CFG_RSTSEQ_AUTO_BUSADAPTER_MSB 0 +#define VIDEO_OUT_CFG_RSTSEQ_AUTO_BUSADAPTER_LSB 0 +#define VIDEO_OUT_CFG_RSTSEQ_AUTO_BUSADAPTER_ACCESS "RW" +// ============================================================================= +// Register : VIDEO_OUT_CFG_RSTSEQ_PARALLEL +// JTAG access : synchronous +// Description : None +#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_OFFSET 0x00000030 +#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_BITS 0x00000007 +#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_RESET 0x00000006 +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_RSTSEQ_PARALLEL_VEC +// Description : Is this reset parallel (i.e. not part of the sequence) +#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_VEC_RESET 0x1 +#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_VEC_BITS 0x00000004 +#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_VEC_MSB 2 +#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_VEC_LSB 2 +#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_VEC_ACCESS "RO" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_RSTSEQ_PARALLEL_DPI +// Description : Is this reset parallel (i.e. not part of the sequence) +#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_DPI_RESET 0x1 +#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_DPI_BITS 0x00000002 +#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_DPI_MSB 1 +#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_DPI_LSB 1 +#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_DPI_ACCESS "RO" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_RSTSEQ_PARALLEL_BUSADAPTER +// Description : Is this reset parallel (i.e. not part of the sequence) +#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_BUSADAPTER_RESET 0x0 +#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_BUSADAPTER_BITS 0x00000001 +#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_BUSADAPTER_MSB 0 +#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_BUSADAPTER_LSB 0 +#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_BUSADAPTER_ACCESS "RO" +// ============================================================================= +// Register : VIDEO_OUT_CFG_RSTSEQ_CTRL +// JTAG access : synchronous +// Description : None +#define VIDEO_OUT_CFG_RSTSEQ_CTRL_OFFSET 0x00000034 +#define VIDEO_OUT_CFG_RSTSEQ_CTRL_BITS 0x00000007 +#define VIDEO_OUT_CFG_RSTSEQ_CTRL_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_RSTSEQ_CTRL_VEC +// Description : 1 = keep the reset asserted +// 0 = keep the reset deasserted +// This is ignored if rstseq_auto=1 +#define VIDEO_OUT_CFG_RSTSEQ_CTRL_VEC_RESET 0x0 +#define VIDEO_OUT_CFG_RSTSEQ_CTRL_VEC_BITS 0x00000004 +#define VIDEO_OUT_CFG_RSTSEQ_CTRL_VEC_MSB 2 +#define VIDEO_OUT_CFG_RSTSEQ_CTRL_VEC_LSB 2 +#define VIDEO_OUT_CFG_RSTSEQ_CTRL_VEC_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_RSTSEQ_CTRL_DPI +// Description : 1 = keep the reset asserted +// 0 = keep the reset deasserted +// This is ignored if rstseq_auto=1 +#define VIDEO_OUT_CFG_RSTSEQ_CTRL_DPI_RESET 0x0 +#define VIDEO_OUT_CFG_RSTSEQ_CTRL_DPI_BITS 0x00000002 +#define VIDEO_OUT_CFG_RSTSEQ_CTRL_DPI_MSB 1 +#define VIDEO_OUT_CFG_RSTSEQ_CTRL_DPI_LSB 1 +#define VIDEO_OUT_CFG_RSTSEQ_CTRL_DPI_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_RSTSEQ_CTRL_BUSADAPTER +// Description : 1 = keep the reset asserted +// 0 = keep the reset deasserted +// This is ignored if rstseq_auto=1 +#define VIDEO_OUT_CFG_RSTSEQ_CTRL_BUSADAPTER_RESET 0x0 +#define VIDEO_OUT_CFG_RSTSEQ_CTRL_BUSADAPTER_BITS 0x00000001 +#define VIDEO_OUT_CFG_RSTSEQ_CTRL_BUSADAPTER_MSB 0 +#define VIDEO_OUT_CFG_RSTSEQ_CTRL_BUSADAPTER_LSB 0 +#define VIDEO_OUT_CFG_RSTSEQ_CTRL_BUSADAPTER_ACCESS "RW" +// ============================================================================= +// Register : VIDEO_OUT_CFG_RSTSEQ_TRIG +// JTAG access : synchronous +// Description : None +#define VIDEO_OUT_CFG_RSTSEQ_TRIG_OFFSET 0x00000038 +#define VIDEO_OUT_CFG_RSTSEQ_TRIG_BITS 0x00000007 +#define VIDEO_OUT_CFG_RSTSEQ_TRIG_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_RSTSEQ_TRIG_VEC +// Description : Pulses the reset output +#define VIDEO_OUT_CFG_RSTSEQ_TRIG_VEC_RESET 0x0 +#define VIDEO_OUT_CFG_RSTSEQ_TRIG_VEC_BITS 0x00000004 +#define VIDEO_OUT_CFG_RSTSEQ_TRIG_VEC_MSB 2 +#define VIDEO_OUT_CFG_RSTSEQ_TRIG_VEC_LSB 2 +#define VIDEO_OUT_CFG_RSTSEQ_TRIG_VEC_ACCESS "SC" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_RSTSEQ_TRIG_DPI +// Description : Pulses the reset output +#define VIDEO_OUT_CFG_RSTSEQ_TRIG_DPI_RESET 0x0 +#define VIDEO_OUT_CFG_RSTSEQ_TRIG_DPI_BITS 0x00000002 +#define VIDEO_OUT_CFG_RSTSEQ_TRIG_DPI_MSB 1 +#define VIDEO_OUT_CFG_RSTSEQ_TRIG_DPI_LSB 1 +#define VIDEO_OUT_CFG_RSTSEQ_TRIG_DPI_ACCESS "SC" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_RSTSEQ_TRIG_BUSADAPTER +// Description : Pulses the reset output +#define VIDEO_OUT_CFG_RSTSEQ_TRIG_BUSADAPTER_RESET 0x0 +#define VIDEO_OUT_CFG_RSTSEQ_TRIG_BUSADAPTER_BITS 0x00000001 +#define VIDEO_OUT_CFG_RSTSEQ_TRIG_BUSADAPTER_MSB 0 +#define VIDEO_OUT_CFG_RSTSEQ_TRIG_BUSADAPTER_LSB 0 +#define VIDEO_OUT_CFG_RSTSEQ_TRIG_BUSADAPTER_ACCESS "SC" +// ============================================================================= +// Register : VIDEO_OUT_CFG_RSTSEQ_DONE +// JTAG access : synchronous +// Description : None +#define VIDEO_OUT_CFG_RSTSEQ_DONE_OFFSET 0x0000003c +#define VIDEO_OUT_CFG_RSTSEQ_DONE_BITS 0x00000007 +#define VIDEO_OUT_CFG_RSTSEQ_DONE_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_RSTSEQ_DONE_VEC +// Description : Indicates the current state of the reset +#define VIDEO_OUT_CFG_RSTSEQ_DONE_VEC_RESET 0x0 +#define VIDEO_OUT_CFG_RSTSEQ_DONE_VEC_BITS 0x00000004 +#define VIDEO_OUT_CFG_RSTSEQ_DONE_VEC_MSB 2 +#define VIDEO_OUT_CFG_RSTSEQ_DONE_VEC_LSB 2 +#define VIDEO_OUT_CFG_RSTSEQ_DONE_VEC_ACCESS "RO" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_RSTSEQ_DONE_DPI +// Description : Indicates the current state of the reset +#define VIDEO_OUT_CFG_RSTSEQ_DONE_DPI_RESET 0x0 +#define VIDEO_OUT_CFG_RSTSEQ_DONE_DPI_BITS 0x00000002 +#define VIDEO_OUT_CFG_RSTSEQ_DONE_DPI_MSB 1 +#define VIDEO_OUT_CFG_RSTSEQ_DONE_DPI_LSB 1 +#define VIDEO_OUT_CFG_RSTSEQ_DONE_DPI_ACCESS "RO" +// ----------------------------------------------------------------------------- +// Field : VIDEO_OUT_CFG_RSTSEQ_DONE_BUSADAPTER +// Description : Indicates the current state of the reset +#define VIDEO_OUT_CFG_RSTSEQ_DONE_BUSADAPTER_RESET 0x0 +#define VIDEO_OUT_CFG_RSTSEQ_DONE_BUSADAPTER_BITS 0x00000001 +#define VIDEO_OUT_CFG_RSTSEQ_DONE_BUSADAPTER_MSB 0 +#define VIDEO_OUT_CFG_RSTSEQ_DONE_BUSADAPTER_LSB 0 +#define VIDEO_OUT_CFG_RSTSEQ_DONE_BUSADAPTER_ACCESS "RO" +// ============================================================================= + +#define CFG_WRITE(reg, val) writel((val), vec->hw_base[RP1VEC_HW_BLOCK_CFG] + (reg ## _OFFSET)) +#define CFG_READ(reg) readl(vec->hw_base[RP1VEC_HW_BLOCK_CFG] + (reg ## _OFFSET)) + +void rp1vec_vidout_setup(struct rp1_vec *vec) +{ + /* + * We assume DPI and VEC can't be used at the same time (due to + * clashing requirements for PLL_VIDEO, and potentially for VDAC). + * We therefore leave DPI memories powered down. + */ + CFG_WRITE(VIDEO_OUT_CFG_MEM_PD, VIDEO_OUT_CFG_MEM_PD_DPI_BITS); + CFG_WRITE(VIDEO_OUT_CFG_TEST_OVERRIDE, 0x00000000); + + /* DPI->Pads; VEC->VDAC */ + CFG_WRITE(VIDEO_OUT_CFG_SEL, VIDEO_OUT_CFG_SEL_VDAC_MUX_BITS); + + /* configure VDAC for 1 channel, bandgap on, 1.28V swing */ + CFG_WRITE(VIDEO_OUT_CFG_VDAC_CFG, 0x0019ffff); + + /* enable VEC interrupt */ + CFG_WRITE(VIDEO_OUT_CFG_INTE, VIDEO_OUT_CFG_INTE_VEC_BITS); +} + +void rp1vec_vidout_poweroff(struct rp1_vec *vec) +{ + /* disable VEC interrupt */ + CFG_WRITE(VIDEO_OUT_CFG_INTE, 0); + + /* Ensure VDAC is turned off; power down DPI,VEC memories */ + CFG_WRITE(VIDEO_OUT_CFG_VDAC_CFG, 0); + CFG_WRITE(VIDEO_OUT_CFG_MEM_PD, VIDEO_OUT_CFG_MEM_PD_BITS); +} diff --git a/drivers/gpu/drm/rp1/rp1-vec/rp1_vec_hw.c b/drivers/gpu/drm/rp1/rp1-vec/rp1_vec_hw.c new file mode 100644 index 00000000000000..1f70ecf420131b --- /dev/null +++ b/drivers/gpu/drm/rp1/rp1-vec/rp1_vec_hw.c @@ -0,0 +1,580 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * DRM Driver for VEC output on Raspberry Pi RP1 + * + * Copyright (c) 2023 Raspberry Pi Limited. + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/mm.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_print.h> +#include <drm/drm_vblank.h> + +#include "rp1_vec.h" +#include "vec_regs.h" + +#define BITS(field, val) (((val) << (field ## _LSB)) & (field ## _BITS)) +#define VEC_WRITE(reg, val) writel((val), vec->hw_base[RP1VEC_HW_BLOCK_VEC] + (reg ## _OFFSET)) +#define VEC_READ(reg) readl(vec->hw_base[RP1VEC_HW_BLOCK_VEC] + (reg ## _OFFSET)) + +static void rp1vec_write_regs(struct rp1_vec *vec, u32 offset, u32 const *vals, u32 num) +{ + while (num--) { + writel(*vals++, vec->hw_base[RP1VEC_HW_BLOCK_VEC] + offset); + offset += 4; + } +} + +int rp1vec_hw_busy(struct rp1_vec *vec) +{ + /* Read the undocumented "pline_busy" flag */ + return VEC_READ(VEC_STATUS) & 1; +} + +/* Table of supported input (in-memory/DMA) pixel formats. */ +struct rp1vec_ipixfmt { + u32 format; /* DRM format code */ + u32 mask; /* RGB masks (10 bits each, left justified) */ + u32 shift; /* RGB MSB positions in the memory word */ + u32 rgbsz; /* Shifts used for scaling; also (BPP/8-1) */ +}; + +#define MASK_RGB(r, g, b) \ + (BITS(VEC_IMASK_MASK_R, r) | BITS(VEC_IMASK_MASK_G, g) | BITS(VEC_IMASK_MASK_B, b)) +#define SHIFT_RGB(r, g, b) \ + (BITS(VEC_SHIFT_SHIFT_R, r) | BITS(VEC_SHIFT_SHIFT_G, g) | BITS(VEC_SHIFT_SHIFT_B, b)) + +static const struct rp1vec_ipixfmt my_formats[] = { + { + .format = DRM_FORMAT_XRGB8888, + .mask = MASK_RGB(0x3fc, 0x3fc, 0x3fc), + .shift = SHIFT_RGB(23, 15, 7), + .rgbsz = BITS(VEC_RGBSZ_BYTES_PER_PIXEL_MINUS1, 3), + }, + { + .format = DRM_FORMAT_XBGR8888, + .mask = MASK_RGB(0x3fc, 0x3fc, 0x3fc), + .shift = SHIFT_RGB(7, 15, 23), + .rgbsz = BITS(VEC_RGBSZ_BYTES_PER_PIXEL_MINUS1, 3), + }, + { + .format = DRM_FORMAT_ARGB8888, + .mask = MASK_RGB(0x3fc, 0x3fc, 0x3fc), + .shift = SHIFT_RGB(23, 15, 7), + .rgbsz = BITS(VEC_RGBSZ_BYTES_PER_PIXEL_MINUS1, 3), + }, + { + .format = DRM_FORMAT_ABGR8888, + .mask = MASK_RGB(0x3fc, 0x3fc, 0x3fc), + .shift = SHIFT_RGB(7, 15, 23), + .rgbsz = BITS(VEC_RGBSZ_BYTES_PER_PIXEL_MINUS1, 3), + }, + { + .format = DRM_FORMAT_RGB888, + .mask = MASK_RGB(0x3fc, 0x3fc, 0x3fc), + .shift = SHIFT_RGB(23, 15, 7), + .rgbsz = BITS(VEC_RGBSZ_BYTES_PER_PIXEL_MINUS1, 2), + }, + { + .format = DRM_FORMAT_BGR888, + .mask = MASK_RGB(0x3fc, 0x3fc, 0x3fc), + .shift = SHIFT_RGB(7, 15, 23), + .rgbsz = BITS(VEC_RGBSZ_BYTES_PER_PIXEL_MINUS1, 2), + }, + { + .format = DRM_FORMAT_RGB565, + .mask = MASK_RGB(0x3e0, 0x3f0, 0x3e0), + .shift = SHIFT_RGB(15, 10, 4), + .rgbsz = BITS(VEC_RGBSZ_SCALE_R, 5) | + BITS(VEC_RGBSZ_SCALE_G, 6) | + BITS(VEC_RGBSZ_SCALE_B, 5) | + BITS(VEC_RGBSZ_BYTES_PER_PIXEL_MINUS1, 1), + } +}; + +/* + * Hardware mode descriptions (@ 108 MHz VDAC clock) + * See "vec_regs.h" for further descriptions of these registers and fields. + * Driver should adjust some values for other TV standards and for pixel rate, + * and must ensure that ((de_end - de_bgn) % rate) == 0. + */ + +struct rp1vec_hwmode { + u16 max_rows_per_field; /* active lines per field (including partial ones) */ + u16 ref_vfp; /* nominal (vsync_start - vdisplay) when max height */ + bool interlaced; /* set for interlaced */ + bool first_field_odd; /* depends confusingly on line numbering convention */ + s16 scale_v; /* V scale in 2.8 format (for power-of-2 CIC rates) */ + s16 scale_u; /* U scale in 2.8 format (for power-of-2 CIC rates) */ + u16 scale_y; /* Y scale in 2.8 format (for power-of-2 CIC rates) */ + u16 de_end; /* end of horizontal Data Active period at 108MHz */ + u16 de_bgn; /* start of horizontal Data Active period */ + u16 half_lines_per_field; /* number of half lines per field */ + s16 pedestal; /* pedestal (1024 = 100IRE) including FIR overshoot */ + u16 scale_luma; /* back end luma scaling in 1.15 format wrt DAC FSD */ + u16 scale_sync; /* back end sync scaling / blanking level as above */ + u32 scale_burst_chroma; /* back end { burst, chroma } scaling */ + u32 misc; /* Contents of the "EC" register except rate,shift */ + u64 nco_freq; /* colour carrier frequency * (2**64) / 108MHz */ + u32 timing_regs[14]; /* other back end registers 0x84 .. 0xB8 */ +}; + +/* { NTSC, PAL, PAL-M } x { progressive, interlaced } */ +static const struct rp1vec_hwmode rp1vec_hwmodes[3][2] = { + { + /* NTSC */ + { + .max_rows_per_field = 240, + .ref_vfp = 2, + .interlaced = false, + .first_field_odd = false, + .scale_v = 0x0cf, + .scale_u = 0x074, + .scale_y = 0x107, + .de_end = 0x1a4f, + .de_bgn = 0x038f, + .half_lines_per_field = 524, /* also works with 526/2 lines */ + .pedestal = 0x04c, + .scale_luma = 0x8c9a, + .scale_sync = 0x3851, + .scale_burst_chroma = 0x11195561, + .misc = 0x00090c00, /* 5-tap FIR, SEQ_EN, 4 fld sync */ + .nco_freq = 0x087c1f07c1f07c1f, + .timing_regs = { + 0x03e10cc6, 0x0d6801fb, 0x023d034c, 0x00f80b6d, + 0x00000005, 0x0006000b, 0x000c0011, 0x000a0106, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00170106, 0x00000000 + }, + }, { + .max_rows_per_field = 243, + .ref_vfp = 3, + .interlaced = true, + .first_field_odd = true, + .scale_v = 0x0cf, + .scale_u = 0x074, + .scale_y = 0x107, + .de_end = 0x1a4f, + .de_bgn = 0x038f, + .half_lines_per_field = 525, + .pedestal = 0x04c, + .scale_luma = 0x8c9a, + .scale_sync = 0x3851, + .scale_burst_chroma = 0x11195561, + .misc = 0x00094c02, /* 5-tap FIR, SEQ_EN, 2 flds, 4 fld sync, ilace */ + .nco_freq = 0x087c1f07c1f07c1f, + .timing_regs = { + 0x03e10cc6, 0x0d6801fb, 0x023d034c, 0x00f80b6d, + 0x00000005, 0x0006000b, 0x000c0011, 0x000a0107, + 0x0111020d, 0x00000000, 0x00000000, 0x011c020d, + 0x00150106, 0x0107011b, + }, + }, + }, { + /* PAL */ + { + .max_rows_per_field = 288, + .ref_vfp = 2, + .interlaced = false, + .first_field_odd = false, + .scale_v = 0x0e0, + .scale_u = 0x07e, + .scale_y = 0x11c, + .de_end = 0x1ab6, + .de_bgn = 0x03f6, + .half_lines_per_field = 624, + .pedestal = 0x00a, /* nonzero for max FIR overshoot after CIC */ + .scale_luma = 0x89d8, + .scale_sync = 0x3c00, + .scale_burst_chroma = 0x0caf53b5, + .misc = 0x00091c01, /* 5-tap FIR, SEQ_EN, 8 fld sync, PAL */ + .nco_freq = 0x0a8262b2cc48c1d1, + .timing_regs = { + 0x04660cee, 0x0d8001fb, 0x025c034f, 0x00fd0b84, + 0x026c0270, 0x00000004, 0x00050009, 0x00070135, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00170136, 0x00000000, + }, + }, { + .max_rows_per_field = 288, + .ref_vfp = 5, + .interlaced = true, + .first_field_odd = false, + .scale_v = 0x0e0, + .scale_u = 0x07e, + .scale_y = 0x11c, + .de_end = 0x1ab6, + .de_bgn = 0x03f6, + .half_lines_per_field = 625, + .pedestal = 0x00a, + .scale_luma = 0x89d8, + .scale_sync = 0x3c00, + .scale_burst_chroma = 0x0caf53b5, + .misc = 0x0009dc03, /* 5-tap FIR, SEQ_EN, 4 flds, 8 fld sync, ilace, PAL */ + .nco_freq = 0x0a8262b2cc48c1d1, + .timing_regs = { + 0x04660cee, 0x0d8001fb, 0x025c034f, 0x00fd0b84, + 0x026c0270, 0x00000004, 0x00050009, 0x00070135, + 0x013f026d, 0x00060136, 0x0140026e, 0x0150026e, + 0x00180136, 0x026f0017, + }, + }, + }, { + /* PAL-M */ + { + .max_rows_per_field = 240, + .ref_vfp = 2, + .interlaced = false, + .first_field_odd = false, + .scale_v = 0x0e0, + .scale_u = 0x07e, + .scale_y = 0x11c, + .de_end = 0x1a4f, + .de_bgn = 0x038f, + .half_lines_per_field = 524, + .pedestal = 0x00a, + .scale_luma = 0x89d8, + .scale_sync = 0x3851, + .scale_burst_chroma = 0x0d5c53b5, + .misc = 0x00091c01, /* 5-tap FIR, SEQ_EN, 8 fld sync PAL */ + .nco_freq = 0x0879bbf8d6d33ea8, + .timing_regs = { + 0x03e10cc6, 0x0d6801fb, 0x023c034c, 0x00f80b6e, + 0x00000005, 0x0006000b, 0x000c0011, 0x000a0106, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00170106, 0x00000000, + }, + }, { + .max_rows_per_field = 243, + .ref_vfp = 3, + .interlaced = true, + .first_field_odd = true, + .scale_v = 0x0e0, + .scale_u = 0x07e, + .scale_y = 0x11c, + .de_end = 0x1a4f, + .de_bgn = 0x038f, + .half_lines_per_field = 525, + .pedestal = 0x00a, + .scale_luma = 0x89d8, + .scale_sync = 0x3851, + .scale_burst_chroma = 0x0d5c53b5, + .misc = 0x0009dc03, /* 5-tap FIR, SEQ_EN, 4 flds, 8 fld sync, ilace, PAL */ + .nco_freq = 0x0879bbf8d6d33ea8, + .timing_regs = { + 0x03e10cc6, 0x0d6801fb, 0x023c034c, 0x00f80b6e, + 0x00140019, 0x00000005, 0x0006000b, 0x00090103, + 0x010f0209, 0x00080102, 0x010e020a, 0x0119020a, + 0x00120103, 0x01040118, + }, + }, + }, +}; + +/* System A, System E */ +static const struct rp1vec_hwmode rp1vec_vintage_modes[2] = { + { + .max_rows_per_field = 190, + .ref_vfp = 0, + .interlaced = true, + .first_field_odd = true, + .scale_v = 0, + .scale_u = 0, + .scale_y = 0x11c, + .de_end = 0x2920, + .de_bgn = 0x06a0, + .half_lines_per_field = 405, + .pedestal = 0x00a, + .scale_luma = 0x89d8, + .scale_sync = 0x3c00, + .scale_burst_chroma = 0, + .misc = 0x00084002, /* 5-tap FIR, 2 fields, interlace */ + .nco_freq = 0, + .timing_regs = { + 0x06f01430, 0x14d503cc, 0x00000000, 0x000010de, + 0x00000000, 0x00000007, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00d90195, + 0x000e00ca, 0x00cb00d8, + }, + }, { + .max_rows_per_field = 369, + .ref_vfp = 6, + .interlaced = true, + .first_field_odd = true, + .scale_v = 0, + .scale_u = 0, + .scale_y = 0x11c, + .de_end = 0x145f, + .de_bgn = 0x03a7, + .half_lines_per_field = 819, + .pedestal = 0x0010, + .scale_luma = 0x89d8, + .scale_sync = 0x3b13, + .scale_burst_chroma = 0, + .misc = 0x00084002, /* 5-tap FIR, 2 fields, interlace */ + .nco_freq = 0, + .timing_regs = { + 0x03c10a08, 0x0a4d0114, 0x00000000, 0x000008a6, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x01c10330, + 0x00270196, 0x019701c0, + }, + }, +}; + +static const u32 rp1vec_fir_regs[4] = { + 0x00000000, 0x0be20200, 0x20f0f800, 0x265c7f00, +}; + +/* + * Correction for the 4th order CIC filter's gain of (rate ** 4) + * expressed as a right-shift and a reciprocal scale factor (Q12). + * These arrays are indexed by [rate - 4] where 4 <= rate <= 16. + */ + +static const int rp1vec_scale_table[13] = { + 4096, 6711, 6473, 6988, + 4096, 5114, 6711, 4584, + 6473, 4699, 6988, 5302, + 4096 +}; + +static const u32 rp1vec_rate_shift_table[13] = { + BITS(VEC_DAC_EC_INTERP_RATE_MINUS1, 3) | BITS(VEC_DAC_EC_INTERP_SHIFT_MINUS1, 7), + BITS(VEC_DAC_EC_INTERP_RATE_MINUS1, 4) | BITS(VEC_DAC_EC_INTERP_SHIFT_MINUS1, 9), + BITS(VEC_DAC_EC_INTERP_RATE_MINUS1, 5) | BITS(VEC_DAC_EC_INTERP_SHIFT_MINUS1, 10), + BITS(VEC_DAC_EC_INTERP_RATE_MINUS1, 6) | BITS(VEC_DAC_EC_INTERP_SHIFT_MINUS1, 11), + BITS(VEC_DAC_EC_INTERP_RATE_MINUS1, 7) | BITS(VEC_DAC_EC_INTERP_SHIFT_MINUS1, 11), + BITS(VEC_DAC_EC_INTERP_RATE_MINUS1, 8) | BITS(VEC_DAC_EC_INTERP_SHIFT_MINUS1, 12), + BITS(VEC_DAC_EC_INTERP_RATE_MINUS1, 9) | BITS(VEC_DAC_EC_INTERP_SHIFT_MINUS1, 13), + BITS(VEC_DAC_EC_INTERP_RATE_MINUS1, 10) | BITS(VEC_DAC_EC_INTERP_SHIFT_MINUS1, 13), + BITS(VEC_DAC_EC_INTERP_RATE_MINUS1, 11) | BITS(VEC_DAC_EC_INTERP_SHIFT_MINUS1, 14), + BITS(VEC_DAC_EC_INTERP_RATE_MINUS1, 12) | BITS(VEC_DAC_EC_INTERP_SHIFT_MINUS1, 14), + BITS(VEC_DAC_EC_INTERP_RATE_MINUS1, 13) | BITS(VEC_DAC_EC_INTERP_SHIFT_MINUS1, 15), + BITS(VEC_DAC_EC_INTERP_RATE_MINUS1, 14) | BITS(VEC_DAC_EC_INTERP_SHIFT_MINUS1, 15), + BITS(VEC_DAC_EC_INTERP_RATE_MINUS1, 15) | BITS(VEC_DAC_EC_INTERP_SHIFT_MINUS1, 15), +}; + +void rp1vec_hw_setup(struct rp1_vec *vec, + u32 in_format, + struct drm_display_mode const *mode, + int tvstd) +{ + int i, mode_family, w, h; + const struct rp1vec_hwmode *hwm; + int wmax, hpad_r, vpad_b, rate, ref_2mid, usr_2mid; + u32 misc; + + /* Input pixel format conversion */ + for (i = 0; i < ARRAY_SIZE(my_formats); ++i) { + if (my_formats[i].format == in_format) + break; + } + if (i >= ARRAY_SIZE(my_formats)) { + dev_err(&vec->pdev->dev, "%s: bad input format\n", __func__); + i = 0; + } + VEC_WRITE(VEC_IMASK, my_formats[i].mask); + VEC_WRITE(VEC_SHIFT, my_formats[i].shift); + VEC_WRITE(VEC_RGBSZ, my_formats[i].rgbsz); + + /* Pick an appropriate "base" mode, which we may modify. + * Note that this driver supports a limited selection of video modes. + * (A complete TV mode cannot be directly inferred from a DRM display mode: + * features such as chroma burst sequence, half-lines and equalizing pulses + * would be under-specified, and timings prone to rounding errors.) + */ + if (mode->vtotal == 405 || mode->vtotal == 819) { + /* Systems A and E (interlaced only) */ + vec->fake_31khz = false; + mode_family = 1; + hwm = &rp1vec_vintage_modes[(mode->vtotal == 819) ? 1 : 0]; + } else { + /* 525- and 625-line modes, with half-height and "fake" progressive variants */ + vec->fake_31khz = mode->vtotal >= 500 && !(mode->flags & DRM_MODE_FLAG_INTERLACE); + h = (mode->vtotal >= 500) ? (mode->vtotal >> 1) : mode->vtotal; + if (h >= 272) + mode_family = 1; /* PAL-625 */ + else if (tvstd == DRM_MODE_TV_MODE_PAL_M || tvstd == DRM_MODE_TV_MODE_PAL) + mode_family = 2; /* PAL-525 */ + else + mode_family = 0; /* NTSC-525 */ + hwm = &rp1vec_hwmodes[mode_family][(mode->flags & DRM_MODE_FLAG_INTERLACE) ? 1 : 0]; + } + + /* + * Choose the upsampling rate (to 108MHz) in the range 4..16. + * Clip dimensions to the limits of the chosen hardware mode, then add + * padding as required, making some attempt to respect the DRM mode's + * display position (relative to H and V sync start). Note that "wmax" + * should be wider than the horizontal active region, to avoid boundary + * artifacts (e.g. wmax = 728, w = 720, active ~= 704 in Rec.601 modes). + */ + i = (vec->fake_31khz) ? (mode->clock >> 1) : mode->clock; + rate = (i < (RP1VEC_VDAC_KHZ / 16)) ? 16 : max(4, (RP1VEC_VDAC_KHZ + 256) / i); + wmax = min((hwm->de_end - hwm->de_bgn) / rate, 1020); + w = min(mode->hdisplay, wmax); + ref_2mid = (hwm->de_bgn + hwm->de_end) / rate + 4; /* + 4 for FIR delay */ + usr_2mid = (2 * (mode->htotal - mode->hsync_start) + w) * 2 * (hwm->timing_regs[1] >> 16) / + (rate * mode->htotal); + hpad_r = (wmax - w + ref_2mid - usr_2mid) >> 1; + hpad_r = min(max(0, hpad_r), wmax - w); + h = mode->vdisplay >> (hwm->interlaced || vec->fake_31khz); + h = min(h, 0 + hwm->max_rows_per_field); + vpad_b = ((mode->vsync_start - hwm->ref_vfp) >> (hwm->interlaced || vec->fake_31khz)) - h; + vpad_b = min(max(0, vpad_b), hwm->max_rows_per_field - h); + + /* Configure the hardware "front end" (in the sysclock domain) */ + VEC_WRITE(VEC_APB_TIMEOUT, 0x38); + VEC_WRITE(VEC_QOS, + BITS(VEC_QOS_DQOS, 0x0) | + BITS(VEC_QOS_ULEV, 0x8) | + BITS(VEC_QOS_UQOS, 0x2) | + BITS(VEC_QOS_LLEV, 0x4) | + BITS(VEC_QOS_LQOS, 0x7)); + VEC_WRITE(VEC_DMA_AREA, + BITS(VEC_DMA_AREA_COLS_MINUS1, w - 1) | + BITS(VEC_DMA_AREA_ROWS_PER_FIELD_MINUS1, h - 1)); + VEC_WRITE(VEC_YUV_SCALING, + BITS(VEC_YUV_SCALING_U10_SCALE_Y, + (hwm->scale_y * rp1vec_scale_table[rate - 4] + 2048) >> 12) | + BITS(VEC_YUV_SCALING_S10_SCALE_U, + (hwm->scale_u * rp1vec_scale_table[rate - 4] + 2048) >> 12) | + BITS(VEC_YUV_SCALING_S10_SCALE_V, + (hwm->scale_v * rp1vec_scale_table[rate - 4] + 2048) >> 12)); + VEC_WRITE(VEC_BACK_PORCH, + BITS(VEC_BACK_PORCH_HBP_MINUS1, wmax - w - hpad_r - 1) | + BITS(VEC_BACK_PORCH_VBP_MINUS1, hwm->max_rows_per_field - h - vpad_b - 1)); + VEC_WRITE(VEC_FRONT_PORCH, + BITS(VEC_FRONT_PORCH_HFP_MINUS1, hpad_r - 1) | + BITS(VEC_FRONT_PORCH_VFP_MINUS1, vpad_b - 1)); + VEC_WRITE(VEC_MODE, + BITS(VEC_MODE_HIGH_WATER, 0xE0) | + BITS(VEC_MODE_ALIGN16, !((w | mode->hdisplay) & 15)) | + BITS(VEC_MODE_VFP_EN, (vpad_b > 0)) | + BITS(VEC_MODE_VBP_EN, (hwm->max_rows_per_field > h + vpad_b)) | + BITS(VEC_MODE_HFP_EN, (hpad_r > 0)) | + BITS(VEC_MODE_HBP_EN, (wmax > w + hpad_r)) | + BITS(VEC_MODE_FIELDS_PER_FRAME_MINUS1, hwm->interlaced) | + BITS(VEC_MODE_FIRST_FIELD_ODD, hwm->first_field_odd)); + + /* Configure the hardware "back end" (in the VDAC clock domain) */ + VEC_WRITE(VEC_DAC_80, + BITS(VEC_DAC_80_U14_DE_BGN, hwm->de_bgn) | + BITS(VEC_DAC_80_U14_DE_END, hwm->de_bgn + wmax * rate)); + rp1vec_write_regs(vec, 0x84, hwm->timing_regs, ARRAY_SIZE(hwm->timing_regs)); + VEC_WRITE(VEC_DAC_C0, 0x0); /* DAC control/status -- not wired up in RP1 */ + VEC_WRITE(VEC_DAC_C4, 0x007bffff); /* DAC control -- not wired up in RP1 */ + misc = hwm->half_lines_per_field; + if (misc == 524 && (mode->vtotal >> vec->fake_31khz) == 263) + misc += 2; + if (tvstd == DRM_MODE_TV_MODE_NTSC_J && mode_family == 0) { + /* NTSC-J modification: reduce pedestal and increase gain */ + VEC_WRITE(VEC_DAC_BC, + BITS(VEC_DAC_BC_U11_HALF_LINES_PER_FIELD, misc) | + BITS(VEC_DAC_BC_S11_PEDESTAL, 0x00a)); + VEC_WRITE(VEC_DAC_C8, + BITS(VEC_DAC_C8_U16_SCALE_LUMA, 0x9400) | + BITS(VEC_DAC_C8_U16_SCALE_SYNC, hwm->scale_sync)); + } else { + VEC_WRITE(VEC_DAC_BC, + BITS(VEC_DAC_BC_U11_HALF_LINES_PER_FIELD, misc) | + BITS(VEC_DAC_BC_S11_PEDESTAL, hwm->pedestal)); + VEC_WRITE(VEC_DAC_C8, + BITS(VEC_DAC_C8_U16_SCALE_LUMA, hwm->scale_luma) | + BITS(VEC_DAC_C8_U16_SCALE_SYNC, hwm->scale_sync)); + } + VEC_WRITE(VEC_DAC_CC, (tvstd >= DRM_MODE_TV_MODE_SECAM) ? 0 : hwm->scale_burst_chroma); + VEC_WRITE(VEC_DAC_D0, 0x02000000); /* ADC offsets -- not needed in RP1? */ + misc = hwm->misc; + if ((tvstd == DRM_MODE_TV_MODE_NTSC_443 || tvstd == DRM_MODE_TV_MODE_PAL) && + mode_family != 1) { + /* Change colour carrier frequency to 4433618.75 Hz; disable hard sync */ + VEC_WRITE(VEC_DAC_D4, 0xcc48c1d1); + VEC_WRITE(VEC_DAC_D8, 0x0a8262b2); + misc &= ~VEC_DAC_EC_SEQ_EN_BITS; + } else if (tvstd == DRM_MODE_TV_MODE_PAL_N && mode_family == 1) { + /* Change colour carrier frequency to 3582056.25 Hz */ + VEC_WRITE(VEC_DAC_D4, 0x9ce075f7); + VEC_WRITE(VEC_DAC_D8, 0x087da511); + } else { + VEC_WRITE(VEC_DAC_D4, (u32)(hwm->nco_freq)); + VEC_WRITE(VEC_DAC_D8, (u32)(hwm->nco_freq >> 32)); + } + VEC_WRITE(VEC_DAC_EC, misc | rp1vec_rate_shift_table[rate - 4]); + rp1vec_write_regs(vec, 0xDC, rp1vec_fir_regs, ARRAY_SIZE(rp1vec_fir_regs)); + + /* Set up interrupts and initialise VEC. It will start on the next rp1vec_hw_update() */ + VEC_WRITE(VEC_IRQ_FLAGS, 0xFFFFFFFFu); + rp1vec_hw_vblank_ctrl(vec, 1); + i = rp1vec_hw_busy(vec); + if (i) + dev_warn(&vec->pdev->dev, + "%s: VEC unexpectedly busy at start (0x%08x)", + __func__, VEC_READ(VEC_STATUS)); + + VEC_WRITE(VEC_CONTROL, + BITS(VEC_CONTROL_START_ARM, (!i)) | + BITS(VEC_CONTROL_AUTO_REPEAT, 1)); +} + +void rp1vec_hw_update(struct rp1_vec *vec, dma_addr_t addr, u32 offset, u32 stride) +{ + /* + * Update STRIDE, DMAH and DMAL only. When called after rp1vec_hw_setup(), + * DMA starts immediately; if already running, the buffer will flip at + * the next vertical sync event. + */ + u64 a = addr + offset; + + if (vec->fake_31khz) { + a += stride; + stride *= 2; + } + VEC_WRITE(VEC_DMA_STRIDE, stride); + VEC_WRITE(VEC_DMA_ADDR_H, a >> 32); + VEC_WRITE(VEC_DMA_ADDR_L, a & 0xFFFFFFFFu); +} + +void rp1vec_hw_stop(struct rp1_vec *vec) +{ + /* + * Stop DMA by turning off the Auto-Repeat flag, and wait up to 100ms for + * the current and any queued frame to end. "Force drain" flags are not used, + * as they seem to prevent DMA from re-starting properly; it's safer to wait. + */ + + reinit_completion(&vec->finished); + VEC_WRITE(VEC_CONTROL, 0); + if (!wait_for_completion_timeout(&vec->finished, HZ / 10)) + drm_err(&vec->drm, "%s: timed out waiting for idle\n", __func__); + VEC_WRITE(VEC_IRQ_ENABLES, 0); +} + +void rp1vec_hw_vblank_ctrl(struct rp1_vec *vec, int enable) +{ + VEC_WRITE(VEC_IRQ_ENABLES, + BITS(VEC_IRQ_ENABLES_DONE, 1) | + BITS(VEC_IRQ_ENABLES_DMA, (enable ? 1 : 0)) | + BITS(VEC_IRQ_ENABLES_MATCH_ROW, 1023)); +} + +irqreturn_t rp1vec_hw_isr(int irq, void *dev) +{ + struct rp1_vec *vec = dev; + u32 u = VEC_READ(VEC_IRQ_FLAGS); + + if (u) { + VEC_WRITE(VEC_IRQ_FLAGS, u); + if (u & VEC_IRQ_FLAGS_DMA_BITS) + drm_crtc_handle_vblank(&vec->pipe.crtc); + if (u & VEC_IRQ_FLAGS_DONE_BITS) + complete(&vec->finished); + } + return u ? IRQ_HANDLED : IRQ_NONE; +} diff --git a/drivers/gpu/drm/rp1/rp1-vec/vec_regs.h b/drivers/gpu/drm/rp1/rp1-vec/vec_regs.h new file mode 100644 index 00000000000000..03632f2e8d4b07 --- /dev/null +++ b/drivers/gpu/drm/rp1/rp1-vec/vec_regs.h @@ -0,0 +1,1420 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +// ============================================================================= +// Copyright Raspberry Pi Ltd. 2023 +// vrbuild version: 56aac1a23c016cbbd229108f3b6efc1343842156-clean +// THIS FILE IS GENERATED BY VRBUILD - DO NOT EDIT +// ============================================================================= +// Register block : VEC +// Version : 1 +// Bus type : apb +// Description : None +// ============================================================================= +#ifndef VEC_REGS_DEFINED +#define VEC_REGS_DEFINED +#define VEC_REGS_RWTYPE_MSB 13 +#define VEC_REGS_RWTYPE_LSB 12 +// ============================================================================= +// Register : VEC_CONTROL +// JTAG access : synchronous +// Description : None +#define VEC_CONTROL_OFFSET 0x00000000 +#define VEC_CONTROL_BITS 0x00000007 +#define VEC_CONTROL_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VEC_CONTROL_BARS +// Description : Write '1' to display colour bar test pattern +#define VEC_CONTROL_BARS_RESET 0x0 +#define VEC_CONTROL_BARS_BITS 0x00000004 +#define VEC_CONTROL_BARS_MSB 2 +#define VEC_CONTROL_BARS_LSB 2 +#define VEC_CONTROL_BARS_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_CONTROL_AUTO_REPEAT +// Description : Write '1' to re-display same frame continuously +#define VEC_CONTROL_AUTO_REPEAT_RESET 0x0 +#define VEC_CONTROL_AUTO_REPEAT_BITS 0x00000002 +#define VEC_CONTROL_AUTO_REPEAT_MSB 1 +#define VEC_CONTROL_AUTO_REPEAT_LSB 1 +#define VEC_CONTROL_AUTO_REPEAT_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_CONTROL_START_ARM +// Description : Write '1' before first DMA address is written This bit always +// reads back as '0' +#define VEC_CONTROL_START_ARM_RESET 0x0 +#define VEC_CONTROL_START_ARM_BITS 0x00000001 +#define VEC_CONTROL_START_ARM_MSB 0 +#define VEC_CONTROL_START_ARM_LSB 0 +#define VEC_CONTROL_START_ARM_ACCESS "SC" +// ============================================================================= +// Register : VEC_IRQ_ENABLES +// JTAG access : synchronous +// Description : None +#define VEC_IRQ_ENABLES_OFFSET 0x00000004 +#define VEC_IRQ_ENABLES_BITS 0x03ff003f +#define VEC_IRQ_ENABLES_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VEC_IRQ_ENABLES_MATCH_ROW +// Description : Raster line at which MATCH interrupt is signalled +#define VEC_IRQ_ENABLES_MATCH_ROW_RESET 0x000 +#define VEC_IRQ_ENABLES_MATCH_ROW_BITS 0x03ff0000 +#define VEC_IRQ_ENABLES_MATCH_ROW_MSB 25 +#define VEC_IRQ_ENABLES_MATCH_ROW_LSB 16 +#define VEC_IRQ_ENABLES_MATCH_ROW_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_IRQ_ENABLES_MATCH +// Description : Output raster == match_row reached +#define VEC_IRQ_ENABLES_MATCH_RESET 0x0 +#define VEC_IRQ_ENABLES_MATCH_BITS 0x00000020 +#define VEC_IRQ_ENABLES_MATCH_MSB 5 +#define VEC_IRQ_ENABLES_MATCH_LSB 5 +#define VEC_IRQ_ENABLES_MATCH_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_IRQ_ENABLES_ERROR +// Description : DMA address overwritten before it was taken +#define VEC_IRQ_ENABLES_ERROR_RESET 0x0 +#define VEC_IRQ_ENABLES_ERROR_BITS 0x00000010 +#define VEC_IRQ_ENABLES_ERROR_MSB 4 +#define VEC_IRQ_ENABLES_ERROR_LSB 4 +#define VEC_IRQ_ENABLES_ERROR_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_IRQ_ENABLES_DONE +// Description : Last word sent to DAC after end of video (= all clear) +#define VEC_IRQ_ENABLES_DONE_RESET 0x0 +#define VEC_IRQ_ENABLES_DONE_BITS 0x00000008 +#define VEC_IRQ_ENABLES_DONE_MSB 3 +#define VEC_IRQ_ENABLES_DONE_LSB 3 +#define VEC_IRQ_ENABLES_DONE_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_IRQ_ENABLES_FRAME +// Description : Start of frame +#define VEC_IRQ_ENABLES_FRAME_RESET 0x0 +#define VEC_IRQ_ENABLES_FRAME_BITS 0x00000004 +#define VEC_IRQ_ENABLES_FRAME_MSB 2 +#define VEC_IRQ_ENABLES_FRAME_LSB 2 +#define VEC_IRQ_ENABLES_FRAME_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_IRQ_ENABLES_UNDERFLOW +// Description : Underflow has occurred +#define VEC_IRQ_ENABLES_UNDERFLOW_RESET 0x0 +#define VEC_IRQ_ENABLES_UNDERFLOW_BITS 0x00000002 +#define VEC_IRQ_ENABLES_UNDERFLOW_MSB 1 +#define VEC_IRQ_ENABLES_UNDERFLOW_LSB 1 +#define VEC_IRQ_ENABLES_UNDERFLOW_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_IRQ_ENABLES_DMA +// Description : DMA ready to accept next frame start address +#define VEC_IRQ_ENABLES_DMA_RESET 0x0 +#define VEC_IRQ_ENABLES_DMA_BITS 0x00000001 +#define VEC_IRQ_ENABLES_DMA_MSB 0 +#define VEC_IRQ_ENABLES_DMA_LSB 0 +#define VEC_IRQ_ENABLES_DMA_ACCESS "RW" +// ============================================================================= +// Register : VEC_IRQ_FLAGS +// JTAG access : synchronous +// Description : None +#define VEC_IRQ_FLAGS_OFFSET 0x00000008 +#define VEC_IRQ_FLAGS_BITS 0x0000003f +#define VEC_IRQ_FLAGS_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VEC_IRQ_FLAGS_MATCH +// Description : Output raster == match_row reached +#define VEC_IRQ_FLAGS_MATCH_RESET 0x0 +#define VEC_IRQ_FLAGS_MATCH_BITS 0x00000020 +#define VEC_IRQ_FLAGS_MATCH_MSB 5 +#define VEC_IRQ_FLAGS_MATCH_LSB 5 +#define VEC_IRQ_FLAGS_MATCH_ACCESS "WC" +// ----------------------------------------------------------------------------- +// Field : VEC_IRQ_FLAGS_ERROR +// Description : DMA address overwritten before it was taken +#define VEC_IRQ_FLAGS_ERROR_RESET 0x0 +#define VEC_IRQ_FLAGS_ERROR_BITS 0x00000010 +#define VEC_IRQ_FLAGS_ERROR_MSB 4 +#define VEC_IRQ_FLAGS_ERROR_LSB 4 +#define VEC_IRQ_FLAGS_ERROR_ACCESS "WC" +// ----------------------------------------------------------------------------- +// Field : VEC_IRQ_FLAGS_DONE +// Description : Last word sent to DAC after end of video (= all clear) +#define VEC_IRQ_FLAGS_DONE_RESET 0x0 +#define VEC_IRQ_FLAGS_DONE_BITS 0x00000008 +#define VEC_IRQ_FLAGS_DONE_MSB 3 +#define VEC_IRQ_FLAGS_DONE_LSB 3 +#define VEC_IRQ_FLAGS_DONE_ACCESS "WC" +// ----------------------------------------------------------------------------- +// Field : VEC_IRQ_FLAGS_FRAME +// Description : Start of frame +#define VEC_IRQ_FLAGS_FRAME_RESET 0x0 +#define VEC_IRQ_FLAGS_FRAME_BITS 0x00000004 +#define VEC_IRQ_FLAGS_FRAME_MSB 2 +#define VEC_IRQ_FLAGS_FRAME_LSB 2 +#define VEC_IRQ_FLAGS_FRAME_ACCESS "WC" +// ----------------------------------------------------------------------------- +// Field : VEC_IRQ_FLAGS_UNDERFLOW +// Description : Underflow has occurred +#define VEC_IRQ_FLAGS_UNDERFLOW_RESET 0x0 +#define VEC_IRQ_FLAGS_UNDERFLOW_BITS 0x00000002 +#define VEC_IRQ_FLAGS_UNDERFLOW_MSB 1 +#define VEC_IRQ_FLAGS_UNDERFLOW_LSB 1 +#define VEC_IRQ_FLAGS_UNDERFLOW_ACCESS "WC" +// ----------------------------------------------------------------------------- +// Field : VEC_IRQ_FLAGS_DMA +// Description : DMA ready to accept next frame start address +#define VEC_IRQ_FLAGS_DMA_RESET 0x0 +#define VEC_IRQ_FLAGS_DMA_BITS 0x00000001 +#define VEC_IRQ_FLAGS_DMA_MSB 0 +#define VEC_IRQ_FLAGS_DMA_LSB 0 +#define VEC_IRQ_FLAGS_DMA_ACCESS "WC" +// ============================================================================= +// Register : VEC_QOS +// JTAG access : synchronous +// Description : This register configures panic levels for the AXI ar_qos +// quality of service field. Panic status is driven by the number +// of rows held in the SRAM cache: +#define VEC_QOS_OFFSET 0x0000000c +#define VEC_QOS_BITS 0x000fffff +#define VEC_QOS_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VEC_QOS_UQOS +// Description : Upper AXI QOS +#define VEC_QOS_UQOS_RESET 0x0 +#define VEC_QOS_UQOS_BITS 0x000f0000 +#define VEC_QOS_UQOS_MSB 19 +#define VEC_QOS_UQOS_LSB 16 +#define VEC_QOS_UQOS_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_QOS_ULEV +// Description : Upper trip level (resolution = 1 / 16 of cache size) +#define VEC_QOS_ULEV_RESET 0x0 +#define VEC_QOS_ULEV_BITS 0x0000f000 +#define VEC_QOS_ULEV_MSB 15 +#define VEC_QOS_ULEV_LSB 12 +#define VEC_QOS_ULEV_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_QOS_LQOS +// Description : Lower AXI QOS +#define VEC_QOS_LQOS_RESET 0x0 +#define VEC_QOS_LQOS_BITS 0x00000f00 +#define VEC_QOS_LQOS_MSB 11 +#define VEC_QOS_LQOS_LSB 8 +#define VEC_QOS_LQOS_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_QOS_LLEV +// Description : Lower trip level (resolution = 1 / 16 of cache size) +#define VEC_QOS_LLEV_RESET 0x0 +#define VEC_QOS_LLEV_BITS 0x000000f0 +#define VEC_QOS_LLEV_MSB 7 +#define VEC_QOS_LLEV_LSB 4 +#define VEC_QOS_LLEV_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_QOS_DQOS +// Description : Default QOS +#define VEC_QOS_DQOS_RESET 0x0 +#define VEC_QOS_DQOS_BITS 0x0000000f +#define VEC_QOS_DQOS_MSB 3 +#define VEC_QOS_DQOS_LSB 0 +#define VEC_QOS_DQOS_ACCESS "RW" +// ============================================================================= +// Register : VEC_DMA_ADDR_L +// JTAG access : synchronous +// Description : Lower 32-bits +#define VEC_DMA_ADDR_L_OFFSET 0x00000010 +#define VEC_DMA_ADDR_L_BITS 0xffffffff +#define VEC_DMA_ADDR_L_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VEC_DMA_ADDR_L_AXI_ADDR +// Description : Byte address of DMA transfer frame buffer. +#define VEC_DMA_ADDR_L_AXI_ADDR_RESET 0x00000000 +#define VEC_DMA_ADDR_L_AXI_ADDR_BITS 0xffffffff +#define VEC_DMA_ADDR_L_AXI_ADDR_MSB 31 +#define VEC_DMA_ADDR_L_AXI_ADDR_LSB 0 +#define VEC_DMA_ADDR_L_AXI_ADDR_ACCESS "RWF" +// ============================================================================= +// Register : VEC_DMA_STRIDE +// JTAG access : synchronous +// Description : This register sets the line byte stride. +#define VEC_DMA_STRIDE_OFFSET 0x00000014 +#define VEC_DMA_STRIDE_BITS 0xffffffff +#define VEC_DMA_STRIDE_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VEC_DMA_STRIDE_STRIDE +// Description : Byte stride +#define VEC_DMA_STRIDE_STRIDE_RESET 0x00000000 +#define VEC_DMA_STRIDE_STRIDE_BITS 0xffffffff +#define VEC_DMA_STRIDE_STRIDE_MSB 31 +#define VEC_DMA_STRIDE_STRIDE_LSB 0 +#define VEC_DMA_STRIDE_STRIDE_ACCESS "RW" +// ============================================================================= +// Register : VEC_DMA_AREA +// JTAG access : synchronous +// Description : Interlaced pixel area. See example driver code. +#define VEC_DMA_AREA_OFFSET 0x00000018 +#define VEC_DMA_AREA_BITS 0x03ff03ff +#define VEC_DMA_AREA_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VEC_DMA_AREA_COLS_MINUS1 +// Description : Width +#define VEC_DMA_AREA_COLS_MINUS1_RESET 0x000 +#define VEC_DMA_AREA_COLS_MINUS1_BITS 0x03ff0000 +#define VEC_DMA_AREA_COLS_MINUS1_MSB 25 +#define VEC_DMA_AREA_COLS_MINUS1_LSB 16 +#define VEC_DMA_AREA_COLS_MINUS1_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_DMA_AREA_ROWS_PER_FIELD_MINUS1 +// Description : Lines per field = half of lines per interlaced frame +#define VEC_DMA_AREA_ROWS_PER_FIELD_MINUS1_RESET 0x000 +#define VEC_DMA_AREA_ROWS_PER_FIELD_MINUS1_BITS 0x000003ff +#define VEC_DMA_AREA_ROWS_PER_FIELD_MINUS1_MSB 9 +#define VEC_DMA_AREA_ROWS_PER_FIELD_MINUS1_LSB 0 +#define VEC_DMA_AREA_ROWS_PER_FIELD_MINUS1_ACCESS "RW" +// ============================================================================= +// Register : VEC_YUV_SCALING +// JTAG access : synchronous +// Description : None +#define VEC_YUV_SCALING_OFFSET 0x0000001c +#define VEC_YUV_SCALING_BITS 0x3fffffff +#define VEC_YUV_SCALING_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VEC_YUV_SCALING_U10_SCALE_Y +// Description : Y unsigned scaling factor - 8 binary places +#define VEC_YUV_SCALING_U10_SCALE_Y_RESET 0x000 +#define VEC_YUV_SCALING_U10_SCALE_Y_BITS 0x3ff00000 +#define VEC_YUV_SCALING_U10_SCALE_Y_MSB 29 +#define VEC_YUV_SCALING_U10_SCALE_Y_LSB 20 +#define VEC_YUV_SCALING_U10_SCALE_Y_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_YUV_SCALING_S10_SCALE_U +// Description : U signed scaling factor - 8 binary places +#define VEC_YUV_SCALING_S10_SCALE_U_RESET 0x000 +#define VEC_YUV_SCALING_S10_SCALE_U_BITS 0x000ffc00 +#define VEC_YUV_SCALING_S10_SCALE_U_MSB 19 +#define VEC_YUV_SCALING_S10_SCALE_U_LSB 10 +#define VEC_YUV_SCALING_S10_SCALE_U_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_YUV_SCALING_S10_SCALE_V +// Description : V signed scaling factor - 8 binary please +#define VEC_YUV_SCALING_S10_SCALE_V_RESET 0x000 +#define VEC_YUV_SCALING_S10_SCALE_V_BITS 0x000003ff +#define VEC_YUV_SCALING_S10_SCALE_V_MSB 9 +#define VEC_YUV_SCALING_S10_SCALE_V_LSB 0 +#define VEC_YUV_SCALING_S10_SCALE_V_ACCESS "RW" +// ============================================================================= +// Register : VEC_BACK_PORCH +// JTAG access : synchronous +// Description : None +#define VEC_BACK_PORCH_OFFSET 0x00000020 +#define VEC_BACK_PORCH_BITS 0x03ff03ff +#define VEC_BACK_PORCH_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VEC_BACK_PORCH_HBP_MINUS1 +// Description : Horizontal back porch +#define VEC_BACK_PORCH_HBP_MINUS1_RESET 0x000 +#define VEC_BACK_PORCH_HBP_MINUS1_BITS 0x03ff0000 +#define VEC_BACK_PORCH_HBP_MINUS1_MSB 25 +#define VEC_BACK_PORCH_HBP_MINUS1_LSB 16 +#define VEC_BACK_PORCH_HBP_MINUS1_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_BACK_PORCH_VBP_MINUS1 +// Description : Vertical back porch +#define VEC_BACK_PORCH_VBP_MINUS1_RESET 0x000 +#define VEC_BACK_PORCH_VBP_MINUS1_BITS 0x000003ff +#define VEC_BACK_PORCH_VBP_MINUS1_MSB 9 +#define VEC_BACK_PORCH_VBP_MINUS1_LSB 0 +#define VEC_BACK_PORCH_VBP_MINUS1_ACCESS "RW" +// ============================================================================= +// Register : VEC_FRONT_PORCH +// JTAG access : synchronous +// Description : None +#define VEC_FRONT_PORCH_OFFSET 0x00000024 +#define VEC_FRONT_PORCH_BITS 0x03ff03ff +#define VEC_FRONT_PORCH_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VEC_FRONT_PORCH_HFP_MINUS1 +// Description : Horizontal front porch +#define VEC_FRONT_PORCH_HFP_MINUS1_RESET 0x000 +#define VEC_FRONT_PORCH_HFP_MINUS1_BITS 0x03ff0000 +#define VEC_FRONT_PORCH_HFP_MINUS1_MSB 25 +#define VEC_FRONT_PORCH_HFP_MINUS1_LSB 16 +#define VEC_FRONT_PORCH_HFP_MINUS1_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_FRONT_PORCH_VFP_MINUS1 +// Description : Vertical front porch +#define VEC_FRONT_PORCH_VFP_MINUS1_RESET 0x000 +#define VEC_FRONT_PORCH_VFP_MINUS1_BITS 0x000003ff +#define VEC_FRONT_PORCH_VFP_MINUS1_MSB 9 +#define VEC_FRONT_PORCH_VFP_MINUS1_LSB 0 +#define VEC_FRONT_PORCH_VFP_MINUS1_ACCESS "RW" +// ============================================================================= +// Register : VEC_SHIFT +// JTAG access : synchronous +// Description : Positions of R,G,B MS bits in the memory word. Note: due to an +// unintended red/blue swap, these fields have been renamed since +// a previous version. There is no functional change. +#define VEC_SHIFT_OFFSET 0x00000028 +#define VEC_SHIFT_BITS 0x00007fff +#define VEC_SHIFT_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VEC_SHIFT_SHIFT_R +// Description : Red MSB +#define VEC_SHIFT_SHIFT_R_RESET 0x00 +#define VEC_SHIFT_SHIFT_R_BITS 0x00007c00 +#define VEC_SHIFT_SHIFT_R_MSB 14 +#define VEC_SHIFT_SHIFT_R_LSB 10 +#define VEC_SHIFT_SHIFT_R_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_SHIFT_SHIFT_G +// Description : Green MSB +#define VEC_SHIFT_SHIFT_G_RESET 0x00 +#define VEC_SHIFT_SHIFT_G_BITS 0x000003e0 +#define VEC_SHIFT_SHIFT_G_MSB 9 +#define VEC_SHIFT_SHIFT_G_LSB 5 +#define VEC_SHIFT_SHIFT_G_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_SHIFT_SHIFT_B +// Description : Blue MSB +#define VEC_SHIFT_SHIFT_B_RESET 0x00 +#define VEC_SHIFT_SHIFT_B_BITS 0x0000001f +#define VEC_SHIFT_SHIFT_B_MSB 4 +#define VEC_SHIFT_SHIFT_B_LSB 0 +#define VEC_SHIFT_SHIFT_B_ACCESS "RW" +// ============================================================================= +// Register : VEC_IMASK +// JTAG access : synchronous +// Description : Masks for R,G,B significant bits, left-justified within 10-bit +// fields. +#define VEC_IMASK_OFFSET 0x0000002c +#define VEC_IMASK_BITS 0x3fffffff +#define VEC_IMASK_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VEC_IMASK_MASK_R +// Description : Red mask +#define VEC_IMASK_MASK_R_RESET 0x000 +#define VEC_IMASK_MASK_R_BITS 0x3ff00000 +#define VEC_IMASK_MASK_R_MSB 29 +#define VEC_IMASK_MASK_R_LSB 20 +#define VEC_IMASK_MASK_R_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_IMASK_MASK_G +// Description : Green mask +#define VEC_IMASK_MASK_G_RESET 0x000 +#define VEC_IMASK_MASK_G_BITS 0x000ffc00 +#define VEC_IMASK_MASK_G_MSB 19 +#define VEC_IMASK_MASK_G_LSB 10 +#define VEC_IMASK_MASK_G_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_IMASK_MASK_B +// Description : Blue mask +#define VEC_IMASK_MASK_B_RESET 0x000 +#define VEC_IMASK_MASK_B_BITS 0x000003ff +#define VEC_IMASK_MASK_B_MSB 9 +#define VEC_IMASK_MASK_B_LSB 0 +#define VEC_IMASK_MASK_B_ACCESS "RW" +// ============================================================================= +// Register : VEC_MODE +// JTAG access : synchronous +// Description : None +#define VEC_MODE_OFFSET 0x00000030 +#define VEC_MODE_BITS 0x01ff003f +#define VEC_MODE_RESET 0x01c00000 +// ----------------------------------------------------------------------------- +// Field : VEC_MODE_HIGH_WATER +// Description : ALWAYS WRITE 8'hE0 +#define VEC_MODE_HIGH_WATER_RESET 0xe0 +#define VEC_MODE_HIGH_WATER_BITS 0x01fe0000 +#define VEC_MODE_HIGH_WATER_MSB 24 +#define VEC_MODE_HIGH_WATER_LSB 17 +#define VEC_MODE_HIGH_WATER_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_MODE_ALIGN16 +// Description : Data: 0=BYTE aligned; 1=BEAT aligned +#define VEC_MODE_ALIGN16_RESET 0x0 +#define VEC_MODE_ALIGN16_BITS 0x00010000 +#define VEC_MODE_ALIGN16_MSB 16 +#define VEC_MODE_ALIGN16_LSB 16 +#define VEC_MODE_ALIGN16_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_MODE_VFP_EN +// Description : Enable vertical front porch +#define VEC_MODE_VFP_EN_RESET 0x0 +#define VEC_MODE_VFP_EN_BITS 0x00000020 +#define VEC_MODE_VFP_EN_MSB 5 +#define VEC_MODE_VFP_EN_LSB 5 +#define VEC_MODE_VFP_EN_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_MODE_VBP_EN +// Description : Enable vertical back porch +#define VEC_MODE_VBP_EN_RESET 0x0 +#define VEC_MODE_VBP_EN_BITS 0x00000010 +#define VEC_MODE_VBP_EN_MSB 4 +#define VEC_MODE_VBP_EN_LSB 4 +#define VEC_MODE_VBP_EN_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_MODE_HFP_EN +// Description : Enable horizontal front porch +#define VEC_MODE_HFP_EN_RESET 0x0 +#define VEC_MODE_HFP_EN_BITS 0x00000008 +#define VEC_MODE_HFP_EN_MSB 3 +#define VEC_MODE_HFP_EN_LSB 3 +#define VEC_MODE_HFP_EN_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_MODE_HBP_EN +// Description : Enable horizontal back porch +#define VEC_MODE_HBP_EN_RESET 0x0 +#define VEC_MODE_HBP_EN_BITS 0x00000004 +#define VEC_MODE_HBP_EN_MSB 2 +#define VEC_MODE_HBP_EN_LSB 2 +#define VEC_MODE_HBP_EN_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_MODE_FIELDS_PER_FRAME_MINUS1 +// Description : Interlaced / progressive +#define VEC_MODE_FIELDS_PER_FRAME_MINUS1_RESET 0x0 +#define VEC_MODE_FIELDS_PER_FRAME_MINUS1_BITS 0x00000002 +#define VEC_MODE_FIELDS_PER_FRAME_MINUS1_MSB 1 +#define VEC_MODE_FIELDS_PER_FRAME_MINUS1_LSB 1 +#define VEC_MODE_FIELDS_PER_FRAME_MINUS1_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_MODE_FIRST_FIELD_ODD +// Description : Interlacing order: odd/even or even/odd +#define VEC_MODE_FIRST_FIELD_ODD_RESET 0x0 +#define VEC_MODE_FIRST_FIELD_ODD_BITS 0x00000001 +#define VEC_MODE_FIRST_FIELD_ODD_MSB 0 +#define VEC_MODE_FIRST_FIELD_ODD_LSB 0 +#define VEC_MODE_FIRST_FIELD_ODD_ACCESS "RW" +// ============================================================================= +// Register : VEC_RGBSZ +// JTAG access : synchronous +// Description : None +#define VEC_RGBSZ_OFFSET 0x00000034 +#define VEC_RGBSZ_BITS 0x00030fff +#define VEC_RGBSZ_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VEC_RGBSZ_BYTES_PER_PIXEL_MINUS1 +// Description : Pixel stride +#define VEC_RGBSZ_BYTES_PER_PIXEL_MINUS1_RESET 0x0 +#define VEC_RGBSZ_BYTES_PER_PIXEL_MINUS1_BITS 0x00030000 +#define VEC_RGBSZ_BYTES_PER_PIXEL_MINUS1_MSB 17 +#define VEC_RGBSZ_BYTES_PER_PIXEL_MINUS1_LSB 16 +#define VEC_RGBSZ_BYTES_PER_PIXEL_MINUS1_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_RGBSZ_SCALE_R +// Description : Red number of bits for shift-and-OR scaling +#define VEC_RGBSZ_SCALE_R_RESET 0x0 +#define VEC_RGBSZ_SCALE_R_BITS 0x00000f00 +#define VEC_RGBSZ_SCALE_R_MSB 11 +#define VEC_RGBSZ_SCALE_R_LSB 8 +#define VEC_RGBSZ_SCALE_R_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_RGBSZ_SCALE_G +// Description : Green number of bits for shift-and-OR scaling +#define VEC_RGBSZ_SCALE_G_RESET 0x0 +#define VEC_RGBSZ_SCALE_G_BITS 0x000000f0 +#define VEC_RGBSZ_SCALE_G_MSB 7 +#define VEC_RGBSZ_SCALE_G_LSB 4 +#define VEC_RGBSZ_SCALE_G_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_RGBSZ_SCALE_B +// Description : Blue number of bits for shift-and-OR scaling +#define VEC_RGBSZ_SCALE_B_RESET 0x0 +#define VEC_RGBSZ_SCALE_B_BITS 0x0000000f +#define VEC_RGBSZ_SCALE_B_MSB 3 +#define VEC_RGBSZ_SCALE_B_LSB 0 +#define VEC_RGBSZ_SCALE_B_ACCESS "RW" +// ============================================================================= +// Register : VEC_PANICS +// JTAG access : synchronous +// Description : None +#define VEC_PANICS_OFFSET 0x00000038 +#define VEC_PANICS_BITS 0xffffffff +#define VEC_PANICS_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VEC_PANICS_UCOUNT +// Description : Upper panic count +#define VEC_PANICS_UCOUNT_RESET 0x0000 +#define VEC_PANICS_UCOUNT_BITS 0xffff0000 +#define VEC_PANICS_UCOUNT_MSB 31 +#define VEC_PANICS_UCOUNT_LSB 16 +#define VEC_PANICS_UCOUNT_ACCESS "WC" +// ----------------------------------------------------------------------------- +// Field : VEC_PANICS_LCOUNT +// Description : Lower panic count +#define VEC_PANICS_LCOUNT_RESET 0x0000 +#define VEC_PANICS_LCOUNT_BITS 0x0000ffff +#define VEC_PANICS_LCOUNT_MSB 15 +#define VEC_PANICS_LCOUNT_LSB 0 +#define VEC_PANICS_LCOUNT_ACCESS "WC" +// ============================================================================= +// Register : VEC_STATUS +// JTAG access : synchronous +// Description : None +#define VEC_STATUS_OFFSET 0x0000003c +#define VEC_STATUS_BITS 0xff000000 +#define VEC_STATUS_RESET 0x0d000000 +// ----------------------------------------------------------------------------- +// Field : VEC_STATUS_VERSION +// Description : VEC module version code +#define VEC_STATUS_VERSION_RESET 0x0d +#define VEC_STATUS_VERSION_BITS 0xff000000 +#define VEC_STATUS_VERSION_MSB 31 +#define VEC_STATUS_VERSION_LSB 24 +#define VEC_STATUS_VERSION_ACCESS "RO" +// ============================================================================= +// Register : VEC_DMA_ADDR_H +// JTAG access : synchronous +// Description : Upper 32-bits +#define VEC_DMA_ADDR_H_OFFSET 0x00000040 +#define VEC_DMA_ADDR_H_BITS 0xffffffff +#define VEC_DMA_ADDR_H_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VEC_DMA_ADDR_H_AXI_ADDR +// Description : Byte address of DMA transfer frame buffer. +#define VEC_DMA_ADDR_H_AXI_ADDR_RESET 0x00000000 +#define VEC_DMA_ADDR_H_AXI_ADDR_BITS 0xffffffff +#define VEC_DMA_ADDR_H_AXI_ADDR_MSB 31 +#define VEC_DMA_ADDR_H_AXI_ADDR_LSB 0 +#define VEC_DMA_ADDR_H_AXI_ADDR_ACCESS "RW" +// ============================================================================= +// Register : VEC_BURST_ADDR_L +// JTAG access : synchronous +// Description : None +#define VEC_BURST_ADDR_L_OFFSET 0x00000044 +#define VEC_BURST_ADDR_L_BITS 0xffffffff +#define VEC_BURST_ADDR_L_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VEC_BURST_ADDR_L_BURST_ADDR +// Description : the lower 32-bits of the most recent read request sent to AXI +// memory. +#define VEC_BURST_ADDR_L_BURST_ADDR_RESET 0x00000000 +#define VEC_BURST_ADDR_L_BURST_ADDR_BITS 0xffffffff +#define VEC_BURST_ADDR_L_BURST_ADDR_MSB 31 +#define VEC_BURST_ADDR_L_BURST_ADDR_LSB 0 +#define VEC_BURST_ADDR_L_BURST_ADDR_ACCESS "RO" +// ============================================================================= +// Register : VEC_APB_TIMEOUT +// JTAG access : synchronous +// Description : None +#define VEC_APB_TIMEOUT_OFFSET 0x00000048 +#define VEC_APB_TIMEOUT_BITS 0x000103ff +#define VEC_APB_TIMEOUT_RESET 0x00000014 +// ----------------------------------------------------------------------------- +// Field : VEC_APB_TIMEOUT_SLVERR_EN +// Description : 1 = Assert PREADY and PSLVERR on timeout 0 = Assert PREADY only +#define VEC_APB_TIMEOUT_SLVERR_EN_RESET 0x0 +#define VEC_APB_TIMEOUT_SLVERR_EN_BITS 0x00010000 +#define VEC_APB_TIMEOUT_SLVERR_EN_MSB 16 +#define VEC_APB_TIMEOUT_SLVERR_EN_LSB 16 +#define VEC_APB_TIMEOUT_SLVERR_EN_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_APB_TIMEOUT_TIMEOUT +// Description : Maximum AXI clock cycles to wait for responses from DAC clock +// domain APB block +#define VEC_APB_TIMEOUT_TIMEOUT_RESET 0x014 +#define VEC_APB_TIMEOUT_TIMEOUT_BITS 0x000003ff +#define VEC_APB_TIMEOUT_TIMEOUT_MSB 9 +#define VEC_APB_TIMEOUT_TIMEOUT_LSB 0 +#define VEC_APB_TIMEOUT_TIMEOUT_ACCESS "RW" +// ============================================================================= +// Register : VEC_DAC_80 +// JTAG access : synchronous +// Description : None +#define VEC_DAC_80_OFFSET 0x00000080 +#define VEC_DAC_80_BITS 0x3fff3fff +#define VEC_DAC_80_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_80_U14_DE_BGN +// Description : Beginning of active data enable within each visible line +#define VEC_DAC_80_U14_DE_BGN_RESET 0x0000 +#define VEC_DAC_80_U14_DE_BGN_BITS 0x3fff0000 +#define VEC_DAC_80_U14_DE_BGN_MSB 29 +#define VEC_DAC_80_U14_DE_BGN_LSB 16 +#define VEC_DAC_80_U14_DE_BGN_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_80_U14_DE_END +// Description : End of active data enable within each visible line +#define VEC_DAC_80_U14_DE_END_RESET 0x0000 +#define VEC_DAC_80_U14_DE_END_BITS 0x00003fff +#define VEC_DAC_80_U14_DE_END_MSB 13 +#define VEC_DAC_80_U14_DE_END_LSB 0 +#define VEC_DAC_80_U14_DE_END_ACCESS "RW" +// ============================================================================= +// Register : VEC_DAC_84 +// JTAG access : synchronous +// Description : None +#define VEC_DAC_84_OFFSET 0x00000084 +#define VEC_DAC_84_BITS 0x1fff1fff +#define VEC_DAC_84_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_84_U13_ACTIVE_RISE +// Description : Horizontal blanking interval +#define VEC_DAC_84_U13_ACTIVE_RISE_RESET 0x0000 +#define VEC_DAC_84_U13_ACTIVE_RISE_BITS 0x1fff0000 +#define VEC_DAC_84_U13_ACTIVE_RISE_MSB 28 +#define VEC_DAC_84_U13_ACTIVE_RISE_LSB 16 +#define VEC_DAC_84_U13_ACTIVE_RISE_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_84_U13_ACTIVE_FALL +// Description : Horizontal blanking interval +#define VEC_DAC_84_U13_ACTIVE_FALL_RESET 0x0000 +#define VEC_DAC_84_U13_ACTIVE_FALL_BITS 0x00001fff +#define VEC_DAC_84_U13_ACTIVE_FALL_MSB 12 +#define VEC_DAC_84_U13_ACTIVE_FALL_LSB 0 +#define VEC_DAC_84_U13_ACTIVE_FALL_ACCESS "RW" +// ============================================================================= +// Register : VEC_DAC_88 +// JTAG access : synchronous +// Description : None +#define VEC_DAC_88_OFFSET 0x00000088 +#define VEC_DAC_88_BITS 0x1fff1fff +#define VEC_DAC_88_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_88_U13_HALF_LINE_PERIOD +// Description : Ratio of DAC clock to horizontal line rate, halved +#define VEC_DAC_88_U13_HALF_LINE_PERIOD_RESET 0x0000 +#define VEC_DAC_88_U13_HALF_LINE_PERIOD_BITS 0x1fff0000 +#define VEC_DAC_88_U13_HALF_LINE_PERIOD_MSB 28 +#define VEC_DAC_88_U13_HALF_LINE_PERIOD_LSB 16 +#define VEC_DAC_88_U13_HALF_LINE_PERIOD_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_88_U13_HORZ_SYNC +// Description : Width of horizontal sync pulses +#define VEC_DAC_88_U13_HORZ_SYNC_RESET 0x0000 +#define VEC_DAC_88_U13_HORZ_SYNC_BITS 0x00001fff +#define VEC_DAC_88_U13_HORZ_SYNC_MSB 12 +#define VEC_DAC_88_U13_HORZ_SYNC_LSB 0 +#define VEC_DAC_88_U13_HORZ_SYNC_ACCESS "RW" +// ============================================================================= +// Register : VEC_DAC_8C +// JTAG access : synchronous +// Description : None +#define VEC_DAC_8C_OFFSET 0x0000008c +#define VEC_DAC_8C_BITS 0x1fff1fff +#define VEC_DAC_8C_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_8C_U13_BURST_RISE +// Description : Start of raised-cosine colour burst envelope +#define VEC_DAC_8C_U13_BURST_RISE_RESET 0x0000 +#define VEC_DAC_8C_U13_BURST_RISE_BITS 0x1fff0000 +#define VEC_DAC_8C_U13_BURST_RISE_MSB 28 +#define VEC_DAC_8C_U13_BURST_RISE_LSB 16 +#define VEC_DAC_8C_U13_BURST_RISE_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_8C_U13_BURST_FALL +// Description : End of raised-cosine colour burst envelope +#define VEC_DAC_8C_U13_BURST_FALL_RESET 0x0000 +#define VEC_DAC_8C_U13_BURST_FALL_BITS 0x00001fff +#define VEC_DAC_8C_U13_BURST_FALL_MSB 12 +#define VEC_DAC_8C_U13_BURST_FALL_LSB 0 +#define VEC_DAC_8C_U13_BURST_FALL_ACCESS "RW" +// ============================================================================= +// Register : VEC_DAC_90 +// JTAG access : synchronous +// Description : None +#define VEC_DAC_90_OFFSET 0x00000090 +#define VEC_DAC_90_BITS 0x1fff3fff +#define VEC_DAC_90_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_90_U13_VERT_EQ +// Description : Width of vertical equalisation pulses (= half line minus +// serration) +#define VEC_DAC_90_U13_VERT_EQ_RESET 0x0000 +#define VEC_DAC_90_U13_VERT_EQ_BITS 0x1fff0000 +#define VEC_DAC_90_U13_VERT_EQ_MSB 28 +#define VEC_DAC_90_U13_VERT_EQ_LSB 16 +#define VEC_DAC_90_U13_VERT_EQ_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_90_U14_VERT_SYNC +// Description : Width of vertical sync pulses +#define VEC_DAC_90_U14_VERT_SYNC_RESET 0x0000 +#define VEC_DAC_90_U14_VERT_SYNC_BITS 0x00003fff +#define VEC_DAC_90_U14_VERT_SYNC_MSB 13 +#define VEC_DAC_90_U14_VERT_SYNC_LSB 0 +#define VEC_DAC_90_U14_VERT_SYNC_ACCESS "RW" +// ============================================================================= +// Register : VEC_DAC_94 +// JTAG access : synchronous +// Description : None +#define VEC_DAC_94_OFFSET 0x00000094 +#define VEC_DAC_94_BITS 0x03ff03ff +#define VEC_DAC_94_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_94_U10_PRE_EQ_BGN +// Description : Half-lines, inclusive, relative to field datum, where vertical +// pre-equalisation pulses start +#define VEC_DAC_94_U10_PRE_EQ_BGN_RESET 0x000 +#define VEC_DAC_94_U10_PRE_EQ_BGN_BITS 0x03ff0000 +#define VEC_DAC_94_U10_PRE_EQ_BGN_MSB 25 +#define VEC_DAC_94_U10_PRE_EQ_BGN_LSB 16 +#define VEC_DAC_94_U10_PRE_EQ_BGN_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_94_U10_PRE_EQ_END +// Description : Half-lines, inclusive, relative to field datum, where vertical +// pre-equalisation pulses end +#define VEC_DAC_94_U10_PRE_EQ_END_RESET 0x000 +#define VEC_DAC_94_U10_PRE_EQ_END_BITS 0x000003ff +#define VEC_DAC_94_U10_PRE_EQ_END_MSB 9 +#define VEC_DAC_94_U10_PRE_EQ_END_LSB 0 +#define VEC_DAC_94_U10_PRE_EQ_END_ACCESS "RW" +// ============================================================================= +// Register : VEC_DAC_98 +// JTAG access : synchronous +// Description : None +#define VEC_DAC_98_OFFSET 0x00000098 +#define VEC_DAC_98_BITS 0x03ff03ff +#define VEC_DAC_98_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_98_U10_FIELD_SYNC_BGN +// Description : Half-lines containing vertical sync pulses (inclusive) +#define VEC_DAC_98_U10_FIELD_SYNC_BGN_RESET 0x000 +#define VEC_DAC_98_U10_FIELD_SYNC_BGN_BITS 0x03ff0000 +#define VEC_DAC_98_U10_FIELD_SYNC_BGN_MSB 25 +#define VEC_DAC_98_U10_FIELD_SYNC_BGN_LSB 16 +#define VEC_DAC_98_U10_FIELD_SYNC_BGN_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_98_U10_FIELD_SYNC_END +// Description : Half-lines containing vertical sync pulses (inclusive) +#define VEC_DAC_98_U10_FIELD_SYNC_END_RESET 0x000 +#define VEC_DAC_98_U10_FIELD_SYNC_END_BITS 0x000003ff +#define VEC_DAC_98_U10_FIELD_SYNC_END_MSB 9 +#define VEC_DAC_98_U10_FIELD_SYNC_END_LSB 0 +#define VEC_DAC_98_U10_FIELD_SYNC_END_ACCESS "RW" +// ============================================================================= +// Register : VEC_DAC_9C +// JTAG access : synchronous +// Description : None +#define VEC_DAC_9C_OFFSET 0x0000009c +#define VEC_DAC_9C_BITS 0x03ff03ff +#define VEC_DAC_9C_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_9C_U10_POST_EQ_BGN +// Description : Half-lines containing vertical post-equalisation pulses +#define VEC_DAC_9C_U10_POST_EQ_BGN_RESET 0x000 +#define VEC_DAC_9C_U10_POST_EQ_BGN_BITS 0x03ff0000 +#define VEC_DAC_9C_U10_POST_EQ_BGN_MSB 25 +#define VEC_DAC_9C_U10_POST_EQ_BGN_LSB 16 +#define VEC_DAC_9C_U10_POST_EQ_BGN_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_9C_U10_POST_EQ_END +// Description : Half-lines containing vertical post-equalisation pulses +#define VEC_DAC_9C_U10_POST_EQ_END_RESET 0x000 +#define VEC_DAC_9C_U10_POST_EQ_END_BITS 0x000003ff +#define VEC_DAC_9C_U10_POST_EQ_END_MSB 9 +#define VEC_DAC_9C_U10_POST_EQ_END_LSB 0 +#define VEC_DAC_9C_U10_POST_EQ_END_ACCESS "RW" +// ============================================================================= +// Register : VEC_DAC_A0 +// JTAG access : synchronous +// Description : None +#define VEC_DAC_A0_OFFSET 0x000000a0 +#define VEC_DAC_A0_BITS 0x03ff03ff +#define VEC_DAC_A0_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_A0_U10_FLD1_BURST_BGN +// Description : First and last full frame lines (1-based numbering) within the +// PAL/NTSC four field sequence which require a colour burst +#define VEC_DAC_A0_U10_FLD1_BURST_BGN_RESET 0x000 +#define VEC_DAC_A0_U10_FLD1_BURST_BGN_BITS 0x03ff0000 +#define VEC_DAC_A0_U10_FLD1_BURST_BGN_MSB 25 +#define VEC_DAC_A0_U10_FLD1_BURST_BGN_LSB 16 +#define VEC_DAC_A0_U10_FLD1_BURST_BGN_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_A0_U10_FLD1_BURST_END +// Description : First and last full frame lines (1-based numbering) within the +// PAL/NTSC four field sequence which require a colour burst +#define VEC_DAC_A0_U10_FLD1_BURST_END_RESET 0x000 +#define VEC_DAC_A0_U10_FLD1_BURST_END_BITS 0x000003ff +#define VEC_DAC_A0_U10_FLD1_BURST_END_MSB 9 +#define VEC_DAC_A0_U10_FLD1_BURST_END_LSB 0 +#define VEC_DAC_A0_U10_FLD1_BURST_END_ACCESS "RW" +// ============================================================================= +// Register : VEC_DAC_A4 +// JTAG access : synchronous +// Description : None +#define VEC_DAC_A4_OFFSET 0x000000a4 +#define VEC_DAC_A4_BITS 0x03ff03ff +#define VEC_DAC_A4_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_A4_U10_FLD2_BURST_BGN +// Description : First and last full frame lines (1-based numbering) within the +// PAL/NTSC four field sequence which require a colour burst +#define VEC_DAC_A4_U10_FLD2_BURST_BGN_RESET 0x000 +#define VEC_DAC_A4_U10_FLD2_BURST_BGN_BITS 0x03ff0000 +#define VEC_DAC_A4_U10_FLD2_BURST_BGN_MSB 25 +#define VEC_DAC_A4_U10_FLD2_BURST_BGN_LSB 16 +#define VEC_DAC_A4_U10_FLD2_BURST_BGN_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_A4_U10_FLD2_BURST_END +// Description : First and last full frame lines (1-based numbering) within the +// PAL/NTSC four field sequence which require a colour burst +#define VEC_DAC_A4_U10_FLD2_BURST_END_RESET 0x000 +#define VEC_DAC_A4_U10_FLD2_BURST_END_BITS 0x000003ff +#define VEC_DAC_A4_U10_FLD2_BURST_END_MSB 9 +#define VEC_DAC_A4_U10_FLD2_BURST_END_LSB 0 +#define VEC_DAC_A4_U10_FLD2_BURST_END_ACCESS "RW" +// ============================================================================= +// Register : VEC_DAC_A8 +// JTAG access : synchronous +// Description : None +#define VEC_DAC_A8_OFFSET 0x000000a8 +#define VEC_DAC_A8_BITS 0x03ff03ff +#define VEC_DAC_A8_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_A8_U10_FLD3_BURST_BGN +// Description : First and last full frame lines (1-based numbering) within the +// PAL/NTSC four field sequence which require a colour burst +#define VEC_DAC_A8_U10_FLD3_BURST_BGN_RESET 0x000 +#define VEC_DAC_A8_U10_FLD3_BURST_BGN_BITS 0x03ff0000 +#define VEC_DAC_A8_U10_FLD3_BURST_BGN_MSB 25 +#define VEC_DAC_A8_U10_FLD3_BURST_BGN_LSB 16 +#define VEC_DAC_A8_U10_FLD3_BURST_BGN_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_A8_U10_FLD3_BURST_END +// Description : First and last full frame lines (1-based numbering) within the +// PAL/NTSC four field sequence which require a colour burst +#define VEC_DAC_A8_U10_FLD3_BURST_END_RESET 0x000 +#define VEC_DAC_A8_U10_FLD3_BURST_END_BITS 0x000003ff +#define VEC_DAC_A8_U10_FLD3_BURST_END_MSB 9 +#define VEC_DAC_A8_U10_FLD3_BURST_END_LSB 0 +#define VEC_DAC_A8_U10_FLD3_BURST_END_ACCESS "RW" +// ============================================================================= +// Register : VEC_DAC_AC +// JTAG access : synchronous +// Description : None +#define VEC_DAC_AC_OFFSET 0x000000ac +#define VEC_DAC_AC_BITS 0x03ff03ff +#define VEC_DAC_AC_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_AC_U10_FLD4_BURST_BGN +// Description : First and last full frame lines (1-based numbering) within the +// PAL/NTSC four field sequence which require a colour burst +#define VEC_DAC_AC_U10_FLD4_BURST_BGN_RESET 0x000 +#define VEC_DAC_AC_U10_FLD4_BURST_BGN_BITS 0x03ff0000 +#define VEC_DAC_AC_U10_FLD4_BURST_BGN_MSB 25 +#define VEC_DAC_AC_U10_FLD4_BURST_BGN_LSB 16 +#define VEC_DAC_AC_U10_FLD4_BURST_BGN_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_AC_U10_FLD4_BURST_END +// Description : First and last full frame lines (1-based numbering) within the +// PAL/NTSC four field sequence which require a colour burst +#define VEC_DAC_AC_U10_FLD4_BURST_END_RESET 0x000 +#define VEC_DAC_AC_U10_FLD4_BURST_END_BITS 0x000003ff +#define VEC_DAC_AC_U10_FLD4_BURST_END_MSB 9 +#define VEC_DAC_AC_U10_FLD4_BURST_END_LSB 0 +#define VEC_DAC_AC_U10_FLD4_BURST_END_ACCESS "RW" +// ============================================================================= +// Register : VEC_DAC_B0 +// JTAG access : synchronous +// Description : None +#define VEC_DAC_B0_OFFSET 0x000000b0 +#define VEC_DAC_B0_BITS 0x03ff03ff +#define VEC_DAC_B0_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_B0_U10_FLD24_FULL_LINE_BGN +// Description : First and last full visible lines (1-based numbering) in the +// PAL/NTSC four field sequence +#define VEC_DAC_B0_U10_FLD24_FULL_LINE_BGN_RESET 0x000 +#define VEC_DAC_B0_U10_FLD24_FULL_LINE_BGN_BITS 0x03ff0000 +#define VEC_DAC_B0_U10_FLD24_FULL_LINE_BGN_MSB 25 +#define VEC_DAC_B0_U10_FLD24_FULL_LINE_BGN_LSB 16 +#define VEC_DAC_B0_U10_FLD24_FULL_LINE_BGN_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_B0_U10_FLD24_FULL_LINE_END +// Description : First and last full visible lines (1-based numbering) in the +// PAL/NTSC four field sequence +#define VEC_DAC_B0_U10_FLD24_FULL_LINE_END_RESET 0x000 +#define VEC_DAC_B0_U10_FLD24_FULL_LINE_END_BITS 0x000003ff +#define VEC_DAC_B0_U10_FLD24_FULL_LINE_END_MSB 9 +#define VEC_DAC_B0_U10_FLD24_FULL_LINE_END_LSB 0 +#define VEC_DAC_B0_U10_FLD24_FULL_LINE_END_ACCESS "RW" +// ============================================================================= +// Register : VEC_DAC_B4 +// JTAG access : synchronous +// Description : None +#define VEC_DAC_B4_OFFSET 0x000000b4 +#define VEC_DAC_B4_BITS 0x03ff03ff +#define VEC_DAC_B4_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_B4_U10_FLD13_FULL_LINE_BGN +// Description : First and last full visible lines (1-based numbering) in the +// PAL/NTSC four field sequence +#define VEC_DAC_B4_U10_FLD13_FULL_LINE_BGN_RESET 0x000 +#define VEC_DAC_B4_U10_FLD13_FULL_LINE_BGN_BITS 0x03ff0000 +#define VEC_DAC_B4_U10_FLD13_FULL_LINE_BGN_MSB 25 +#define VEC_DAC_B4_U10_FLD13_FULL_LINE_BGN_LSB 16 +#define VEC_DAC_B4_U10_FLD13_FULL_LINE_BGN_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_B4_U10_FLD13_FULL_LINE_END +// Description : First and last full visible lines (1-based numbering) in the +// PAL/NTSC four field sequence +#define VEC_DAC_B4_U10_FLD13_FULL_LINE_END_RESET 0x000 +#define VEC_DAC_B4_U10_FLD13_FULL_LINE_END_BITS 0x000003ff +#define VEC_DAC_B4_U10_FLD13_FULL_LINE_END_MSB 9 +#define VEC_DAC_B4_U10_FLD13_FULL_LINE_END_LSB 0 +#define VEC_DAC_B4_U10_FLD13_FULL_LINE_END_ACCESS "RW" +// ============================================================================= +// Register : VEC_DAC_B8 +// JTAG access : synchronous +// Description : None +#define VEC_DAC_B8_OFFSET 0x000000b8 +#define VEC_DAC_B8_BITS 0x03ff03ff +#define VEC_DAC_B8_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_B8_U10_BOT_HALF_LINE +// Description : Top and bottom visible half-lines in 1-based standard full +// frame numbering, for interlaced modes. Set to zero to disable. +#define VEC_DAC_B8_U10_BOT_HALF_LINE_RESET 0x000 +#define VEC_DAC_B8_U10_BOT_HALF_LINE_BITS 0x03ff0000 +#define VEC_DAC_B8_U10_BOT_HALF_LINE_MSB 25 +#define VEC_DAC_B8_U10_BOT_HALF_LINE_LSB 16 +#define VEC_DAC_B8_U10_BOT_HALF_LINE_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_B8_U10_TOP_HALF_LINE +// Description : Top and bottom visible half-lines in 1-based standard full +// frame numbering, for interlaced modes. Set to zero to disable. +#define VEC_DAC_B8_U10_TOP_HALF_LINE_RESET 0x000 +#define VEC_DAC_B8_U10_TOP_HALF_LINE_BITS 0x000003ff +#define VEC_DAC_B8_U10_TOP_HALF_LINE_MSB 9 +#define VEC_DAC_B8_U10_TOP_HALF_LINE_LSB 0 +#define VEC_DAC_B8_U10_TOP_HALF_LINE_ACCESS "RW" +// ============================================================================= +// Register : VEC_DAC_BC +// JTAG access : synchronous +// Description : None +#define VEC_DAC_BC_OFFSET 0x000000bc +#define VEC_DAC_BC_BITS 0x07ff07ff +#define VEC_DAC_BC_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_BC_S11_PEDESTAL +// Description : NTSC pedestal. For 7.5 IRE, this field is 1024 * 7.5/100. For +// PAL, or Japanese NTSC, this field should be zero. +#define VEC_DAC_BC_S11_PEDESTAL_RESET 0x000 +#define VEC_DAC_BC_S11_PEDESTAL_BITS 0x07ff0000 +#define VEC_DAC_BC_S11_PEDESTAL_MSB 26 +#define VEC_DAC_BC_S11_PEDESTAL_LSB 16 +#define VEC_DAC_BC_S11_PEDESTAL_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_BC_U11_HALF_LINES_PER_FIELD +// Description : Mode = 625 PAL, Lines per field = 312.5, +// u11_half_lines_per_field = 1+2*312 Mode = 525 NTSC, Lines per +// field = 262.5, u11_half_lines_per_field = 1+2*262 +#define VEC_DAC_BC_U11_HALF_LINES_PER_FIELD_RESET 0x000 +#define VEC_DAC_BC_U11_HALF_LINES_PER_FIELD_BITS 0x000007ff +#define VEC_DAC_BC_U11_HALF_LINES_PER_FIELD_MSB 10 +#define VEC_DAC_BC_U11_HALF_LINES_PER_FIELD_LSB 0 +#define VEC_DAC_BC_U11_HALF_LINES_PER_FIELD_ACCESS "RW" +// ============================================================================= +// Register : VEC_DAC_C0 +// JTAG access : synchronous +// Description : Synopsis DesignWare control +#define VEC_DAC_C0_OFFSET 0x000000c0 +#define VEC_DAC_C0_BITS 0x000fffff +#define VEC_DAC_C0_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_C0_DWC_CABLE_ENCTR3 +// Description : Synopsis test input +#define VEC_DAC_C0_DWC_CABLE_ENCTR3_RESET 0x0 +#define VEC_DAC_C0_DWC_CABLE_ENCTR3_BITS 0x00080000 +#define VEC_DAC_C0_DWC_CABLE_ENCTR3_MSB 19 +#define VEC_DAC_C0_DWC_CABLE_ENCTR3_LSB 19 +#define VEC_DAC_C0_DWC_CABLE_ENCTR3_ACCESS "RO" +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_C0_DWC_CABLE_CABLEOUT +// Description : cable detect state +#define VEC_DAC_C0_DWC_CABLE_CABLEOUT_RESET 0x0 +#define VEC_DAC_C0_DWC_CABLE_CABLEOUT_BITS 0x00070000 +#define VEC_DAC_C0_DWC_CABLE_CABLEOUT_MSB 18 +#define VEC_DAC_C0_DWC_CABLE_CABLEOUT_LSB 16 +#define VEC_DAC_C0_DWC_CABLE_CABLEOUT_ACCESS "RO" +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_C0_DWC_MUX_2 +// Description : Select DAC channel 2 output +#define VEC_DAC_C0_DWC_MUX_2_RESET 0x0 +#define VEC_DAC_C0_DWC_MUX_2_BITS 0x0000c000 +#define VEC_DAC_C0_DWC_MUX_2_MSB 15 +#define VEC_DAC_C0_DWC_MUX_2_LSB 14 +#define VEC_DAC_C0_DWC_MUX_2_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_C0_DWC_MUX_1 +// Description : Select DAC channel 1 output +#define VEC_DAC_C0_DWC_MUX_1_RESET 0x0 +#define VEC_DAC_C0_DWC_MUX_1_BITS 0x00003000 +#define VEC_DAC_C0_DWC_MUX_1_MSB 13 +#define VEC_DAC_C0_DWC_MUX_1_LSB 12 +#define VEC_DAC_C0_DWC_MUX_1_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_C0_DWC_MUX_0 +// Description : Select DAC channel 0 output +#define VEC_DAC_C0_DWC_MUX_0_RESET 0x0 +#define VEC_DAC_C0_DWC_MUX_0_BITS 0x00000c00 +#define VEC_DAC_C0_DWC_MUX_0_MSB 11 +#define VEC_DAC_C0_DWC_MUX_0_LSB 10 +#define VEC_DAC_C0_DWC_MUX_0_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_C0_DWC_TEST +// Description : Fixed DAC command word +#define VEC_DAC_C0_DWC_TEST_RESET 0x000 +#define VEC_DAC_C0_DWC_TEST_BITS 0x000003ff +#define VEC_DAC_C0_DWC_TEST_MSB 9 +#define VEC_DAC_C0_DWC_TEST_LSB 0 +#define VEC_DAC_C0_DWC_TEST_ACCESS "RW" +// ============================================================================= +// Register : VEC_DAC_C4 +// JTAG access : synchronous +// Description : Synopsis DAC control +#define VEC_DAC_C4_OFFSET 0x000000c4 +#define VEC_DAC_C4_BITS 0x1fffffff +#define VEC_DAC_C4_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_C4_ENCTR +// Description : Always write3'b000 +#define VEC_DAC_C4_ENCTR_RESET 0x0 +#define VEC_DAC_C4_ENCTR_BITS 0x1c000000 +#define VEC_DAC_C4_ENCTR_MSB 28 +#define VEC_DAC_C4_ENCTR_LSB 26 +#define VEC_DAC_C4_ENCTR_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_C4_ENSC +// Description : Enable cable detect - write 3'b000 +#define VEC_DAC_C4_ENSC_RESET 0x0 +#define VEC_DAC_C4_ENSC_BITS 0x03800000 +#define VEC_DAC_C4_ENSC_MSB 25 +#define VEC_DAC_C4_ENSC_LSB 23 +#define VEC_DAC_C4_ENSC_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_C4_ENDAC +// Description : Enable DAC channel +#define VEC_DAC_C4_ENDAC_RESET 0x0 +#define VEC_DAC_C4_ENDAC_BITS 0x00700000 +#define VEC_DAC_C4_ENDAC_MSB 22 +#define VEC_DAC_C4_ENDAC_LSB 20 +#define VEC_DAC_C4_ENDAC_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_C4_ENVBG +// Description : Enable internal bandgap reference - write '1' +#define VEC_DAC_C4_ENVBG_RESET 0x0 +#define VEC_DAC_C4_ENVBG_BITS 0x00080000 +#define VEC_DAC_C4_ENVBG_MSB 19 +#define VEC_DAC_C4_ENVBG_LSB 19 +#define VEC_DAC_C4_ENVBG_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_C4_ENEXTREF +// Description : Enable external reference - write '0' +#define VEC_DAC_C4_ENEXTREF_RESET 0x0 +#define VEC_DAC_C4_ENEXTREF_BITS 0x00040000 +#define VEC_DAC_C4_ENEXTREF_MSB 18 +#define VEC_DAC_C4_ENEXTREF_LSB 18 +#define VEC_DAC_C4_ENEXTREF_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_C4_DAC2GC +// Description : DAC channel 2 gain control - write 6'd63 +#define VEC_DAC_C4_DAC2GC_RESET 0x00 +#define VEC_DAC_C4_DAC2GC_BITS 0x0003f000 +#define VEC_DAC_C4_DAC2GC_MSB 17 +#define VEC_DAC_C4_DAC2GC_LSB 12 +#define VEC_DAC_C4_DAC2GC_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_C4_DAC1GC +// Description : DAC channel 1 gain control - write 6'd63 +#define VEC_DAC_C4_DAC1GC_RESET 0x00 +#define VEC_DAC_C4_DAC1GC_BITS 0x00000fc0 +#define VEC_DAC_C4_DAC1GC_MSB 11 +#define VEC_DAC_C4_DAC1GC_LSB 6 +#define VEC_DAC_C4_DAC1GC_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_C4_DAC0GC +// Description : DAC channel 0 gain control - write 6'd63 +#define VEC_DAC_C4_DAC0GC_RESET 0x00 +#define VEC_DAC_C4_DAC0GC_BITS 0x0000003f +#define VEC_DAC_C4_DAC0GC_MSB 5 +#define VEC_DAC_C4_DAC0GC_LSB 0 +#define VEC_DAC_C4_DAC0GC_ACCESS "RW" +// ============================================================================= +// Register : VEC_DAC_C8 +// JTAG access : synchronous +// Description : None +#define VEC_DAC_C8_OFFSET 0x000000c8 +#define VEC_DAC_C8_BITS 0xffffffff +#define VEC_DAC_C8_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_C8_U16_SCALE_SYNC +// Description : Scaling applied prior to final summation to form the DAC +// command word(s) +#define VEC_DAC_C8_U16_SCALE_SYNC_RESET 0x0000 +#define VEC_DAC_C8_U16_SCALE_SYNC_BITS 0xffff0000 +#define VEC_DAC_C8_U16_SCALE_SYNC_MSB 31 +#define VEC_DAC_C8_U16_SCALE_SYNC_LSB 16 +#define VEC_DAC_C8_U16_SCALE_SYNC_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_C8_U16_SCALE_LUMA +// Description : Scaling applied prior to final summation to form the DAC +// command word(s) +#define VEC_DAC_C8_U16_SCALE_LUMA_RESET 0x0000 +#define VEC_DAC_C8_U16_SCALE_LUMA_BITS 0x0000ffff +#define VEC_DAC_C8_U16_SCALE_LUMA_MSB 15 +#define VEC_DAC_C8_U16_SCALE_LUMA_LSB 0 +#define VEC_DAC_C8_U16_SCALE_LUMA_ACCESS "RW" +// ============================================================================= +// Register : VEC_DAC_CC +// JTAG access : synchronous +// Description : None +#define VEC_DAC_CC_OFFSET 0x000000cc +#define VEC_DAC_CC_BITS 0xffffffff +#define VEC_DAC_CC_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_CC_S16_SCALE_BURST +// Description : Scaling applied prior to final summation to form the DAC +// command word(s) +#define VEC_DAC_CC_S16_SCALE_BURST_RESET 0x0000 +#define VEC_DAC_CC_S16_SCALE_BURST_BITS 0xffff0000 +#define VEC_DAC_CC_S16_SCALE_BURST_MSB 31 +#define VEC_DAC_CC_S16_SCALE_BURST_LSB 16 +#define VEC_DAC_CC_S16_SCALE_BURST_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_CC_S16_SCALE_CHROMA +// Description : Scaling applied prior to final summation to form the DAC +// command word(s) +#define VEC_DAC_CC_S16_SCALE_CHROMA_RESET 0x0000 +#define VEC_DAC_CC_S16_SCALE_CHROMA_BITS 0x0000ffff +#define VEC_DAC_CC_S16_SCALE_CHROMA_MSB 15 +#define VEC_DAC_CC_S16_SCALE_CHROMA_LSB 0 +#define VEC_DAC_CC_S16_SCALE_CHROMA_ACCESS "RW" +// ============================================================================= +// Register : VEC_DAC_D0 +// JTAG access : synchronous +// Description : None +#define VEC_DAC_D0_OFFSET 0x000000d0 +#define VEC_DAC_D0_BITS 0xffffffff +#define VEC_DAC_D0_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_D0_S16_OFFSET_LUMA +// Description : These offsets are applied to the chroma and luma channels +// before the final MUX +#define VEC_DAC_D0_S16_OFFSET_LUMA_RESET 0x0000 +#define VEC_DAC_D0_S16_OFFSET_LUMA_BITS 0xffff0000 +#define VEC_DAC_D0_S16_OFFSET_LUMA_MSB 31 +#define VEC_DAC_D0_S16_OFFSET_LUMA_LSB 16 +#define VEC_DAC_D0_S16_OFFSET_LUMA_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_D0_S16_OFFSET_CHRO +// Description : These offsets are applied to the chroma and luma channels +// before the final MUX +#define VEC_DAC_D0_S16_OFFSET_CHRO_RESET 0x0000 +#define VEC_DAC_D0_S16_OFFSET_CHRO_BITS 0x0000ffff +#define VEC_DAC_D0_S16_OFFSET_CHRO_MSB 15 +#define VEC_DAC_D0_S16_OFFSET_CHRO_LSB 0 +#define VEC_DAC_D0_S16_OFFSET_CHRO_ACCESS "RW" +// ============================================================================= +// Register : VEC_DAC_D4 +// JTAG access : synchronous +// Description : None +#define VEC_DAC_D4_OFFSET 0x000000d4 +#define VEC_DAC_D4_BITS 0xffffffff +#define VEC_DAC_D4_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_D4_NCO_FREQ +// Description : This 64-bit frequency command is applied to the phase +// accumulator of the NCO (numerically controlled oscillator) +// which generates the colour sub-carrier. This value is computed +// as ratio of sub-carrier frequency to DAC clock multiplied by +// 2^64. +#define VEC_DAC_D4_NCO_FREQ_RESET 0x00000000 +#define VEC_DAC_D4_NCO_FREQ_BITS 0xffffffff +#define VEC_DAC_D4_NCO_FREQ_MSB 31 +#define VEC_DAC_D4_NCO_FREQ_LSB 0 +#define VEC_DAC_D4_NCO_FREQ_ACCESS "RW" +// ============================================================================= +// Register : VEC_DAC_D8 +// JTAG access : synchronous +// Description : None +#define VEC_DAC_D8_OFFSET 0x000000d8 +#define VEC_DAC_D8_BITS 0xffffffff +#define VEC_DAC_D8_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_D8_NCO_FREQ +// Description : This 64-bit frequency command is applied to the phase +// accumulator of the NCO (numerically controlled oscillator) +// which generates the colour sub-carrier. This value is computed +// as ratio of sub-carrier frequency to DAC clock multiplied by +// 2^64. +#define VEC_DAC_D8_NCO_FREQ_RESET 0x00000000 +#define VEC_DAC_D8_NCO_FREQ_BITS 0xffffffff +#define VEC_DAC_D8_NCO_FREQ_MSB 31 +#define VEC_DAC_D8_NCO_FREQ_LSB 0 +#define VEC_DAC_D8_NCO_FREQ_ACCESS "RW" +// ============================================================================= +// Register : VEC_DAC_DC +// JTAG access : synchronous +// Description : None +#define VEC_DAC_DC_OFFSET 0x000000dc +#define VEC_DAC_DC_BITS 0xffffffff +#define VEC_DAC_DC_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_DC_FIR_COEFF_CHROMA_0_6 +// Description : FIR filter coefficients +#define VEC_DAC_DC_FIR_COEFF_CHROMA_0_6_RESET 0x0000 +#define VEC_DAC_DC_FIR_COEFF_CHROMA_0_6_BITS 0xffff0000 +#define VEC_DAC_DC_FIR_COEFF_CHROMA_0_6_MSB 31 +#define VEC_DAC_DC_FIR_COEFF_CHROMA_0_6_LSB 16 +#define VEC_DAC_DC_FIR_COEFF_CHROMA_0_6_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_DC_FIR_COEFF_LUMA_0_6 +// Description : FIR filter coefficients +#define VEC_DAC_DC_FIR_COEFF_LUMA_0_6_RESET 0x0000 +#define VEC_DAC_DC_FIR_COEFF_LUMA_0_6_BITS 0x0000ffff +#define VEC_DAC_DC_FIR_COEFF_LUMA_0_6_MSB 15 +#define VEC_DAC_DC_FIR_COEFF_LUMA_0_6_LSB 0 +#define VEC_DAC_DC_FIR_COEFF_LUMA_0_6_ACCESS "RW" +// ============================================================================= +// Register : VEC_DAC_E0 +// JTAG access : synchronous +// Description : None +#define VEC_DAC_E0_OFFSET 0x000000e0 +#define VEC_DAC_E0_BITS 0xffffffff +#define VEC_DAC_E0_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_E0_FIR_COEFF_CHROMA_1_5 +// Description : FIR filter coefficients +#define VEC_DAC_E0_FIR_COEFF_CHROMA_1_5_RESET 0x0000 +#define VEC_DAC_E0_FIR_COEFF_CHROMA_1_5_BITS 0xffff0000 +#define VEC_DAC_E0_FIR_COEFF_CHROMA_1_5_MSB 31 +#define VEC_DAC_E0_FIR_COEFF_CHROMA_1_5_LSB 16 +#define VEC_DAC_E0_FIR_COEFF_CHROMA_1_5_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_E0_FIR_COEFF_LUMA_1_5 +// Description : FIR filter coefficients +#define VEC_DAC_E0_FIR_COEFF_LUMA_1_5_RESET 0x0000 +#define VEC_DAC_E0_FIR_COEFF_LUMA_1_5_BITS 0x0000ffff +#define VEC_DAC_E0_FIR_COEFF_LUMA_1_5_MSB 15 +#define VEC_DAC_E0_FIR_COEFF_LUMA_1_5_LSB 0 +#define VEC_DAC_E0_FIR_COEFF_LUMA_1_5_ACCESS "RW" +// ============================================================================= +// Register : VEC_DAC_E4 +// JTAG access : synchronous +// Description : None +#define VEC_DAC_E4_OFFSET 0x000000e4 +#define VEC_DAC_E4_BITS 0xffffffff +#define VEC_DAC_E4_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_E4_FIR_COEFF_CHROMA_2_4 +// Description : FIR filter coefficients +#define VEC_DAC_E4_FIR_COEFF_CHROMA_2_4_RESET 0x0000 +#define VEC_DAC_E4_FIR_COEFF_CHROMA_2_4_BITS 0xffff0000 +#define VEC_DAC_E4_FIR_COEFF_CHROMA_2_4_MSB 31 +#define VEC_DAC_E4_FIR_COEFF_CHROMA_2_4_LSB 16 +#define VEC_DAC_E4_FIR_COEFF_CHROMA_2_4_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_E4_FIR_COEFF_LUMA_2_4 +// Description : FIR filter coefficients +#define VEC_DAC_E4_FIR_COEFF_LUMA_2_4_RESET 0x0000 +#define VEC_DAC_E4_FIR_COEFF_LUMA_2_4_BITS 0x0000ffff +#define VEC_DAC_E4_FIR_COEFF_LUMA_2_4_MSB 15 +#define VEC_DAC_E4_FIR_COEFF_LUMA_2_4_LSB 0 +#define VEC_DAC_E4_FIR_COEFF_LUMA_2_4_ACCESS "RW" +// ============================================================================= +// Register : VEC_DAC_E8 +// JTAG access : synchronous +// Description : None +#define VEC_DAC_E8_OFFSET 0x000000e8 +#define VEC_DAC_E8_BITS 0xffffffff +#define VEC_DAC_E8_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_E8_FIR_COEFF_CHROMA_3 +// Description : FIR filter coefficients +#define VEC_DAC_E8_FIR_COEFF_CHROMA_3_RESET 0x0000 +#define VEC_DAC_E8_FIR_COEFF_CHROMA_3_BITS 0xffff0000 +#define VEC_DAC_E8_FIR_COEFF_CHROMA_3_MSB 31 +#define VEC_DAC_E8_FIR_COEFF_CHROMA_3_LSB 16 +#define VEC_DAC_E8_FIR_COEFF_CHROMA_3_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_E8_FIR_COEFF_LUMA_3 +// Description : FIR filter coefficients +#define VEC_DAC_E8_FIR_COEFF_LUMA_3_RESET 0x0000 +#define VEC_DAC_E8_FIR_COEFF_LUMA_3_BITS 0x0000ffff +#define VEC_DAC_E8_FIR_COEFF_LUMA_3_MSB 15 +#define VEC_DAC_E8_FIR_COEFF_LUMA_3_LSB 0 +#define VEC_DAC_E8_FIR_COEFF_LUMA_3_ACCESS "RW" +// ============================================================================= +// Register : VEC_DAC_EC +// JTAG access : synchronous +// Description : Misc. control +#define VEC_DAC_EC_OFFSET 0x000000ec +#define VEC_DAC_EC_BITS 0x001fffff +#define VEC_DAC_EC_RESET 0x00000000 +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_EC_SLOW_CLOCK +// Description : Doubles the raised-cosine rate +#define VEC_DAC_EC_SLOW_CLOCK_RESET 0x0 +#define VEC_DAC_EC_SLOW_CLOCK_BITS 0x00100000 +#define VEC_DAC_EC_SLOW_CLOCK_MSB 20 +#define VEC_DAC_EC_SLOW_CLOCK_LSB 20 +#define VEC_DAC_EC_SLOW_CLOCK_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_EC_FIR_RMINUS1 +// Description : Select 1, 3, 5 or 7 FIR taps +#define VEC_DAC_EC_FIR_RMINUS1_RESET 0x0 +#define VEC_DAC_EC_FIR_RMINUS1_BITS 0x000c0000 +#define VEC_DAC_EC_FIR_RMINUS1_MSB 19 +#define VEC_DAC_EC_FIR_RMINUS1_LSB 18 +#define VEC_DAC_EC_FIR_RMINUS1_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_EC_VERT_FULL_NOT_HALF +// Description : Disable half-line pulses during VBI +#define VEC_DAC_EC_VERT_FULL_NOT_HALF_RESET 0x0 +#define VEC_DAC_EC_VERT_FULL_NOT_HALF_BITS 0x00020000 +#define VEC_DAC_EC_VERT_FULL_NOT_HALF_MSB 17 +#define VEC_DAC_EC_VERT_FULL_NOT_HALF_LSB 17 +#define VEC_DAC_EC_VERT_FULL_NOT_HALF_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_EC_SEQ_EN +// Description : Enable NCO reset +#define VEC_DAC_EC_SEQ_EN_RESET 0x0 +#define VEC_DAC_EC_SEQ_EN_BITS 0x00010000 +#define VEC_DAC_EC_SEQ_EN_MSB 16 +#define VEC_DAC_EC_SEQ_EN_LSB 16 +#define VEC_DAC_EC_SEQ_EN_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_EC_U2_FLD_MASK +// Description : Field sequence +#define VEC_DAC_EC_U2_FLD_MASK_RESET 0x0 +#define VEC_DAC_EC_U2_FLD_MASK_BITS 0x0000c000 +#define VEC_DAC_EC_U2_FLD_MASK_MSB 15 +#define VEC_DAC_EC_U2_FLD_MASK_LSB 14 +#define VEC_DAC_EC_U2_FLD_MASK_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_EC_U4_SEQ_MASK +// Description : NCO reset sequence +#define VEC_DAC_EC_U4_SEQ_MASK_RESET 0x0 +#define VEC_DAC_EC_U4_SEQ_MASK_BITS 0x00003c00 +#define VEC_DAC_EC_U4_SEQ_MASK_MSB 13 +#define VEC_DAC_EC_U4_SEQ_MASK_LSB 10 +#define VEC_DAC_EC_U4_SEQ_MASK_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_EC_INTERP_RATE_MINUS1 +// Description : Interpolation rate 2<=R<=16 +#define VEC_DAC_EC_INTERP_RATE_MINUS1_RESET 0x0 +#define VEC_DAC_EC_INTERP_RATE_MINUS1_BITS 0x000003c0 +#define VEC_DAC_EC_INTERP_RATE_MINUS1_MSB 9 +#define VEC_DAC_EC_INTERP_RATE_MINUS1_LSB 6 +#define VEC_DAC_EC_INTERP_RATE_MINUS1_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_EC_INTERP_SHIFT_MINUS1 +// Description : Power-of-2 scaling after interpolation +#define VEC_DAC_EC_INTERP_SHIFT_MINUS1_RESET 0x0 +#define VEC_DAC_EC_INTERP_SHIFT_MINUS1_BITS 0x0000003c +#define VEC_DAC_EC_INTERP_SHIFT_MINUS1_MSB 5 +#define VEC_DAC_EC_INTERP_SHIFT_MINUS1_LSB 2 +#define VEC_DAC_EC_INTERP_SHIFT_MINUS1_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_EC_FIELDS_PER_FRAME_MINUS1 +// Description : Interlaced / progressive +#define VEC_DAC_EC_FIELDS_PER_FRAME_MINUS1_RESET 0x0 +#define VEC_DAC_EC_FIELDS_PER_FRAME_MINUS1_BITS 0x00000002 +#define VEC_DAC_EC_FIELDS_PER_FRAME_MINUS1_MSB 1 +#define VEC_DAC_EC_FIELDS_PER_FRAME_MINUS1_LSB 1 +#define VEC_DAC_EC_FIELDS_PER_FRAME_MINUS1_ACCESS "RW" +// ----------------------------------------------------------------------------- +// Field : VEC_DAC_EC_PAL_EN +// Description : Enable phase alternate line (PAL) mode +#define VEC_DAC_EC_PAL_EN_RESET 0x0 +#define VEC_DAC_EC_PAL_EN_BITS 0x00000001 +#define VEC_DAC_EC_PAL_EN_MSB 0 +#define VEC_DAC_EC_PAL_EN_LSB 0 +#define VEC_DAC_EC_PAL_EN_ACCESS "RW" +// ============================================================================= +#endif // VEC_REGS_DEFINED diff --git a/drivers/gpu/drm/tiny/ili9486.c b/drivers/gpu/drm/tiny/ili9486.c index 70d36626004108..249b3ad9db9545 100644 --- a/drivers/gpu/drm/tiny/ili9486.c +++ b/drivers/gpu/drm/tiny/ili9486.c @@ -188,7 +188,6 @@ static const struct of_device_id ili9486_of_match[] = { MODULE_DEVICE_TABLE(of, ili9486_of_match); static const struct spi_device_id ili9486_id[] = { - { "ili9486", 0 }, { "rpi-lcd-35", 0 }, { "piscreen", 0 }, { } diff --git a/drivers/gpu/drm/v3d/Makefile b/drivers/gpu/drm/v3d/Makefile index b7d673f1153bef..fcf710926057b3 100644 --- a/drivers/gpu/drm/v3d/Makefile +++ b/drivers/gpu/drm/v3d/Makefile @@ -13,7 +13,8 @@ v3d-y := \ v3d_trace_points.o \ v3d_sched.o \ v3d_sysfs.o \ - v3d_submit.o + v3d_submit.o \ + v3d_gemfs.o v3d-$(CONFIG_DEBUG_FS) += v3d_debugfs.o diff --git a/drivers/gpu/drm/v3d/v3d_bo.c b/drivers/gpu/drm/v3d/v3d_bo.c index ebe52bef4ffb8d..bb7815599435bf 100644 --- a/drivers/gpu/drm/v3d/v3d_bo.c +++ b/drivers/gpu/drm/v3d/v3d_bo.c @@ -13,10 +13,6 @@ * Display engines requiring physically contiguous allocations should * look into Mesa's "renderonly" support (as used by the Mesa pl111 * driver) for an example of how to integrate with V3D. - * - * Long term, we should support evicting pages from the MMU when under - * memory pressure (thus the v3d_bo_get_pages() refcounting), but - * that's not a high priority since our systems tend to not have swap. */ #include <linux/dma-buf.h> @@ -107,6 +103,7 @@ v3d_bo_create_finish(struct drm_gem_object *obj) struct v3d_dev *v3d = to_v3d_dev(obj->dev); struct v3d_bo *bo = to_v3d_bo(obj); struct sg_table *sgt; + u64 align; int ret; /* So far we pin the BO in the MMU for its lifetime, so use @@ -116,6 +113,15 @@ v3d_bo_create_finish(struct drm_gem_object *obj) if (IS_ERR(sgt)) return PTR_ERR(sgt); + if (!v3d->gemfs) + align = SZ_4K; + else if (obj->size >= SZ_1M) + align = SZ_1M; + else if (obj->size >= SZ_64K) + align = SZ_64K; + else + align = SZ_4K; + spin_lock(&v3d->mm_lock); /* Allocate the object's space in the GPU's page tables. * Inserting PTEs will happen later, but the offset is for the @@ -123,7 +129,7 @@ v3d_bo_create_finish(struct drm_gem_object *obj) */ ret = drm_mm_insert_node_generic(&v3d->mm, &bo->node, obj->size >> V3D_MMU_PAGE_SHIFT, - GMP_GRANULARITY >> V3D_MMU_PAGE_SHIFT, 0, 0); + align >> V3D_MMU_PAGE_SHIFT, 0, 0); spin_unlock(&v3d->mm_lock); if (ret) return ret; @@ -143,10 +149,12 @@ struct v3d_bo *v3d_bo_create(struct drm_device *dev, struct drm_file *file_priv, size_t unaligned_size) { struct drm_gem_shmem_object *shmem_obj; + struct v3d_dev *v3d = to_v3d_dev(dev); struct v3d_bo *bo; int ret; - shmem_obj = drm_gem_shmem_create(dev, unaligned_size); + shmem_obj = drm_gem_shmem_create_with_mnt(dev, unaligned_size, + v3d->gemfs); if (IS_ERR(shmem_obj)) return ERR_CAST(shmem_obj); bo = to_v3d_bo(&shmem_obj->base); diff --git a/drivers/gpu/drm/v3d/v3d_drv.c b/drivers/gpu/drm/v3d/v3d_drv.c index d7ff1f5fa481f7..d520094b83e144 100644 --- a/drivers/gpu/drm/v3d/v3d_drv.c +++ b/drivers/gpu/drm/v3d/v3d_drv.c @@ -17,6 +17,7 @@ #include <linux/dma-mapping.h> #include <linux/io.h> #include <linux/module.h> +#include <linux/of.h> #include <linux/of_platform.h> #include <linux/platform_device.h> #include <linux/sched/clock.h> @@ -24,6 +25,9 @@ #include <drm/drm_drv.h> #include <drm/drm_managed.h> + +#include <soc/bcm2835/raspberrypi-firmware.h> + #include <uapi/drm/v3d_drm.h> #include "v3d_drv.h" @@ -36,6 +40,13 @@ #define DRIVER_MINOR 0 #define DRIVER_PATCHLEVEL 0 +/* Only expose the `super_pages` modparam if THP is enabled. */ +#ifdef CONFIG_TRANSPARENT_HUGEPAGE +bool super_pages = true; +module_param_named(super_pages, super_pages, bool, 0400); +MODULE_PARM_DESC(super_pages, "Enable/Disable Super Pages support."); +#endif + static int v3d_get_param_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv) { @@ -97,6 +108,9 @@ static int v3d_get_param_ioctl(struct drm_device *dev, void *data, case DRM_V3D_PARAM_MAX_PERF_COUNTERS: args->value = v3d->perfmon_info.max_counters; return 0; + case DRM_V3D_PARAM_SUPPORTS_SUPER_PAGES: + args->value = !!v3d->gemfs; + return 0; default: DRM_DEBUG("Unknown parameter %d\n", args->param); return -EINVAL; @@ -214,6 +228,7 @@ static const struct drm_ioctl_desc v3d_drm_ioctls[] = { DRM_IOCTL_DEF_DRV(V3D_PERFMON_GET_VALUES, v3d_perfmon_get_values_ioctl, DRM_RENDER_ALLOW), DRM_IOCTL_DEF_DRV(V3D_SUBMIT_CPU, v3d_submit_cpu_ioctl, DRM_RENDER_ALLOW | DRM_AUTH), DRM_IOCTL_DEF_DRV(V3D_PERFMON_GET_COUNTER, v3d_perfmon_get_counter_ioctl, DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(V3D_PERFMON_SET_GLOBAL, v3d_perfmon_set_global_ioctl, DRM_RENDER_ALLOW), }; static const struct drm_driver v3d_drm_driver = { @@ -245,6 +260,7 @@ static const struct drm_driver v3d_drm_driver = { }; static const struct of_device_id v3d_of_match[] = { + { .compatible = "brcm,2712-v3d" }, { .compatible = "brcm,2711-v3d" }, { .compatible = "brcm,2712-v3d" }, { .compatible = "brcm,7268-v3d" }, @@ -263,6 +279,8 @@ map_regs(struct v3d_dev *v3d, void __iomem **regs, const char *name) static int v3d_platform_drm_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; + struct rpi_firmware *firmware; + struct device_node *node; struct drm_device *drm; struct v3d_dev *v3d; int ret; @@ -321,6 +339,34 @@ static int v3d_platform_drm_probe(struct platform_device *pdev) } } + v3d->clk = devm_clk_get(dev, NULL); + if (IS_ERR_OR_NULL(v3d->clk)) { + if (PTR_ERR(v3d->clk) != -EPROBE_DEFER) + dev_err(dev, "Failed to get clock (%ld)\n", PTR_ERR(v3d->clk)); + return PTR_ERR(v3d->clk); + } + + node = rpi_firmware_find_node(); + if (!node) + return -EINVAL; + + firmware = rpi_firmware_get(node); + of_node_put(node); + if (!firmware) + return -EPROBE_DEFER; + + v3d->clk_up_rate = rpi_firmware_clk_get_max_rate(firmware, + RPI_FIRMWARE_V3D_CLK_ID); + rpi_firmware_put(firmware); + + /* For downclocking, drop it to the minimum frequency we can get from + * the CPRMAN clock generator dividing off our parent. The divider is + * 4 bits, but ask for just higher than that so that rounding doesn't + * make cprman reject our rate. + */ + v3d->clk_down_rate = + (clk_get_rate(clk_get_parent(v3d->clk)) / (1 << 4)) + 10000; + if (v3d->ver < 41) { ret = map_regs(v3d, &v3d->gca_regs, "gca"); if (ret) @@ -349,6 +395,8 @@ static int v3d_platform_drm_probe(struct platform_device *pdev) ret = v3d_sysfs_init(dev); if (ret) goto drm_unregister; + ret = clk_set_min_rate(v3d->clk, v3d->clk_down_rate); + WARN_ON_ONCE(ret != 0); return 0; diff --git a/drivers/gpu/drm/v3d/v3d_drv.h b/drivers/gpu/drm/v3d/v3d_drv.h index 75b4725d49c7e1..71733e4d19e79b 100644 --- a/drivers/gpu/drm/v3d/v3d_drv.h +++ b/drivers/gpu/drm/v3d/v3d_drv.h @@ -19,9 +19,8 @@ struct clk; struct platform_device; struct reset_control; -#define GMP_GRANULARITY (128 * 1024) - #define V3D_MMU_PAGE_SHIFT 12 +#define V3D_PAGE_FACTOR (PAGE_SIZE >> V3D_MMU_PAGE_SHIFT) #define V3D_MAX_QUEUES (V3D_CPU + 1) @@ -113,6 +112,12 @@ struct v3d_dev { void __iomem *bridge_regs; void __iomem *gca_regs; struct clk *clk; + struct delayed_work clk_down_work; + unsigned long clk_up_rate, clk_down_rate; + struct mutex clk_lock; + u32 clk_refcount; + bool clk_up; + struct reset_control *reset; /* Virtual and DMA addresses of the single shared page table. */ @@ -137,13 +142,17 @@ struct v3d_dev { struct drm_mm mm; spinlock_t mm_lock; + /* + * tmpfs instance used for shmem backed objects + */ + struct vfsmount *gemfs; + struct work_struct overflow_mem_work; struct v3d_bin_job *bin_job; struct v3d_render_job *render_job; struct v3d_tfu_job *tfu_job; struct v3d_csd_job *csd_job; - struct v3d_cpu_job *cpu_job; struct v3d_queue_state queue[V3D_MAX_QUEUES]; @@ -179,6 +188,12 @@ struct v3d_dev { u32 num_allocated; u32 pages_allocated; } bo_stats; + + /* To support a performance analysis tool in user space, we require + * a single, globally configured performance monitor (perfmon) for + * all jobs. + */ + struct v3d_perfmon *global_perfmon; }; static inline struct v3d_dev * @@ -534,6 +549,11 @@ void v3d_reset(struct v3d_dev *v3d); void v3d_invalidate_caches(struct v3d_dev *v3d); void v3d_clean_caches(struct v3d_dev *v3d); +/* v3d_gemfs.c */ +extern bool super_pages; +void v3d_gemfs_init(struct v3d_dev *v3d); +void v3d_gemfs_fini(struct v3d_dev *v3d); + /* v3d_submit.c */ void v3d_job_cleanup(struct v3d_job *job); void v3d_job_put(struct v3d_job *job); @@ -585,7 +605,10 @@ int v3d_perfmon_get_values_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv); int v3d_perfmon_get_counter_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv); +int v3d_perfmon_set_global_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv); /* v3d_sysfs.c */ int v3d_sysfs_init(struct device *dev); void v3d_sysfs_destroy(struct device *dev); +void v3d_submit_init(struct drm_device *dev); diff --git a/drivers/gpu/drm/v3d/v3d_gem.c b/drivers/gpu/drm/v3d/v3d_gem.c index da8faf3b901165..6b1cbd4be54eae 100644 --- a/drivers/gpu/drm/v3d/v3d_gem.c +++ b/drivers/gpu/drm/v3d/v3d_gem.c @@ -4,6 +4,7 @@ #include <linux/device.h> #include <linux/dma-mapping.h> #include <linux/io.h> +#include <linux/clk.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/reset.h> @@ -269,6 +270,8 @@ v3d_gem_init(struct drm_device *dev) if (ret) return ret; + v3d_submit_init(dev); + /* Note: We don't allocate address 0. Various bits of HW * treat 0 as special, such as the occlusion query counters * where 0 means "disabled". @@ -288,11 +291,14 @@ v3d_gem_init(struct drm_device *dev) v3d_init_hw_state(v3d); v3d_mmu_set_page_table(v3d); + v3d_gemfs_init(v3d); + ret = v3d_sched_init(v3d); if (ret) { drm_mm_takedown(&v3d->mm); - dma_free_coherent(v3d->drm.dev, 4096 * 1024, (void *)v3d->pt, + dma_free_coherent(v3d->drm.dev, pt_size, (void *)v3d->pt, v3d->pt_paddr); + return ret; } return 0; @@ -304,6 +310,7 @@ v3d_gem_destroy(struct drm_device *dev) struct v3d_dev *v3d = to_v3d_dev(dev); v3d_sched_fini(v3d); + v3d_gemfs_fini(v3d); /* Waiting for jobs to finish would need to be done before * unregistering V3D. diff --git a/drivers/gpu/drm/v3d/v3d_gemfs.c b/drivers/gpu/drm/v3d/v3d_gemfs.c new file mode 100644 index 00000000000000..4c5e18590a5cf1 --- /dev/null +++ b/drivers/gpu/drm/v3d/v3d_gemfs.c @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Copyright (C) 2024 Raspberry Pi */ + +#include <linux/fs.h> +#include <linux/mount.h> + +#include "v3d_drv.h" + +void v3d_gemfs_init(struct v3d_dev *v3d) +{ + char huge_opt[] = "huge=within_size"; + struct file_system_type *type; + struct vfsmount *gemfs; + + /* + * By creating our own shmemfs mountpoint, we can pass in + * mount flags that better match our usecase. However, we + * only do so on platforms which benefit from it. + */ + if (!IS_ENABLED(CONFIG_TRANSPARENT_HUGEPAGE)) + goto err; + + /* The user doesn't want to enable Super Pages */ + if (!super_pages) + goto err; + + type = get_fs_type("tmpfs"); + if (!type) + goto err; + + gemfs = vfs_kern_mount(type, SB_KERNMOUNT, type->name, huge_opt); + if (IS_ERR(gemfs)) + goto err; + + v3d->gemfs = gemfs; + drm_info(&v3d->drm, "Using Transparent Hugepages\n"); + + return; + +err: + v3d->gemfs = NULL; + drm_notice(&v3d->drm, + "Transparent Hugepage support is recommended for optimal performance on this platform!\n"); +} + +void v3d_gemfs_fini(struct v3d_dev *v3d) +{ + if (v3d->gemfs) + kern_unmount(v3d->gemfs); +} diff --git a/drivers/gpu/drm/v3d/v3d_irq.c b/drivers/gpu/drm/v3d/v3d_irq.c index 72b6a119412fa7..279a4f740a7e37 100644 --- a/drivers/gpu/drm/v3d/v3d_irq.c +++ b/drivers/gpu/drm/v3d/v3d_irq.c @@ -197,6 +197,7 @@ v3d_hub_irq(int irq, void *arg) "GMP", }; const char *client = "?"; + static int logged_error; V3D_WRITE(V3D_MMU_CTL, V3D_READ(V3D_MMU_CTL)); @@ -206,6 +207,7 @@ v3d_hub_irq(int irq, void *arg) client = v3d41_axi_ids[axi_id]; } + if (!logged_error) dev_err(v3d->drm.dev, "MMU error from client %s (%d) at 0x%llx%s%s%s\n", client, axi_id, (long long)vio_addr, ((intsts & V3D_HUB_INT_MMU_WRV) ? @@ -214,6 +216,7 @@ v3d_hub_irq(int irq, void *arg) ", pte invalid" : ""), ((intsts & V3D_HUB_INT_MMU_CAP) ? ", cap exceeded" : "")); + logged_error = 1; status = IRQ_HANDLED; } diff --git a/drivers/gpu/drm/v3d/v3d_mmu.c b/drivers/gpu/drm/v3d/v3d_mmu.c index 5bb7821c0243c6..a25d25a8ae617b 100644 --- a/drivers/gpu/drm/v3d/v3d_mmu.c +++ b/drivers/gpu/drm/v3d/v3d_mmu.c @@ -4,7 +4,7 @@ /** * DOC: Broadcom V3D MMU * - * The V3D 3.x hardware (compared to VC4) now includes an MMU. It has + * The V3D 3.x hardware (compared to VC4) now includes an MMU. It has * a single level of page tables for the V3D's 4GB address space to * map to AXI bus addresses, thus it could need up to 4MB of * physically contiguous memory to store the PTEs. @@ -15,19 +15,26 @@ * * To protect clients from each other, we should use the GMP to * quickly mask out (at 128kb granularity) what pages are available to - * each client. This is not yet implemented. + * each client. This is not yet implemented. */ #include "v3d_drv.h" #include "v3d_regs.h" -/* Note: All PTEs for the 1MB superpage must be filled with the - * superpage bit set. +/* Note: All PTEs for the 64KB bigpage or 1MB superpage must be filled + * with the bigpage/superpage bit set. */ #define V3D_PTE_SUPERPAGE BIT(31) +#define V3D_PTE_BIGPAGE BIT(30) #define V3D_PTE_WRITEABLE BIT(29) #define V3D_PTE_VALID BIT(28) +static bool v3d_mmu_is_aligned(u32 page, u32 page_address, size_t alignment) +{ + return IS_ALIGNED(page, alignment >> V3D_MMU_PAGE_SHIFT) && + IS_ALIGNED(page_address, alignment >> V3D_MMU_PAGE_SHIFT); +} + int v3d_mmu_flush_all(struct v3d_dev *v3d) { int ret; @@ -78,19 +85,40 @@ void v3d_mmu_insert_ptes(struct v3d_bo *bo) struct drm_gem_shmem_object *shmem_obj = &bo->base; struct v3d_dev *v3d = to_v3d_dev(shmem_obj->base.dev); u32 page = bo->node.start; - u32 page_prot = V3D_PTE_WRITEABLE | V3D_PTE_VALID; - struct sg_dma_page_iter dma_iter; - - for_each_sgtable_dma_page(shmem_obj->sgt, &dma_iter, 0) { - dma_addr_t dma_addr = sg_page_iter_dma_address(&dma_iter); - u32 page_address = dma_addr >> V3D_MMU_PAGE_SHIFT; - u32 pte = page_prot | page_address; - u32 i; - - BUG_ON(page_address + (PAGE_SIZE >> V3D_MMU_PAGE_SHIFT) >= - BIT(24)); - for (i = 0; i < PAGE_SIZE >> V3D_MMU_PAGE_SHIFT; i++) - v3d->pt[page++] = pte + i; + struct scatterlist *sgl; + unsigned int count; + + for_each_sgtable_dma_sg(shmem_obj->sgt, sgl, count) { + dma_addr_t dma_addr = sg_dma_address(sgl); + u32 pfn = dma_addr >> V3D_MMU_PAGE_SHIFT; + unsigned int len = sg_dma_len(sgl); + + while (len > 0) { + u32 page_prot = V3D_PTE_WRITEABLE | V3D_PTE_VALID; + u32 page_address = page_prot | pfn; + unsigned int i, page_size; + + BUG_ON(pfn + V3D_PAGE_FACTOR >= BIT(24)); + + if (len >= SZ_1M && + v3d_mmu_is_aligned(page, page_address, SZ_1M)) { + page_size = SZ_1M; + page_address |= V3D_PTE_SUPERPAGE; + } else if (len >= SZ_64K && + v3d_mmu_is_aligned(page, page_address, SZ_64K)) { + page_size = SZ_64K; + page_address |= V3D_PTE_BIGPAGE; + } else { + page_size = SZ_4K; + } + + for (i = 0; i < page_size >> V3D_MMU_PAGE_SHIFT; i++) { + v3d->pt[page++] = page_address + i; + pfn++; + } + + len -= page_size; + } } WARN_ON_ONCE(page - bo->node.start != diff --git a/drivers/gpu/drm/v3d/v3d_perfmon.c b/drivers/gpu/drm/v3d/v3d_perfmon.c index 1abfd738a6017d..3ebda2fa46fc47 100644 --- a/drivers/gpu/drm/v3d/v3d_perfmon.c +++ b/drivers/gpu/drm/v3d/v3d_perfmon.c @@ -313,6 +313,9 @@ static int v3d_perfmon_idr_del(int id, void *elem, void *data) if (perfmon == v3d->active_perfmon) v3d_perfmon_stop(v3d, perfmon, false); + /* If the global perfmon is being destroyed, set it to NULL */ + cmpxchg(&v3d->global_perfmon, perfmon, NULL); + v3d_perfmon_put(perfmon); return 0; @@ -398,6 +401,9 @@ int v3d_perfmon_destroy_ioctl(struct drm_device *dev, void *data, if (perfmon == v3d->active_perfmon) v3d_perfmon_stop(v3d, perfmon, false); + /* If the global perfmon is being destroyed, set it to NULL */ + cmpxchg(&v3d->global_perfmon, perfmon, NULL); + v3d_perfmon_put(perfmon); return 0; @@ -415,11 +421,7 @@ int v3d_perfmon_get_values_ioctl(struct drm_device *dev, void *data, if (req->pad != 0) return -EINVAL; - mutex_lock(&v3d_priv->perfmon.lock); - perfmon = idr_find(&v3d_priv->perfmon.idr, req->id); - v3d_perfmon_get(perfmon); - mutex_unlock(&v3d_priv->perfmon.lock); - + perfmon = v3d_perfmon_find(v3d_priv, req->id); if (!perfmon) return -EINVAL; @@ -461,3 +463,34 @@ int v3d_perfmon_get_counter_ioctl(struct drm_device *dev, void *data, return 0; } + +int v3d_perfmon_set_global_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct v3d_file_priv *v3d_priv = file_priv->driver_priv; + struct drm_v3d_perfmon_set_global *req = data; + struct v3d_dev *v3d = to_v3d_dev(dev); + struct v3d_perfmon *perfmon; + + if (req->flags & ~DRM_V3D_PERFMON_CLEAR_GLOBAL) + return -EINVAL; + + perfmon = v3d_perfmon_find(v3d_priv, req->id); + if (!perfmon) + return -EINVAL; + + /* If the request is to clear the global performance monitor */ + if (req->flags & DRM_V3D_PERFMON_CLEAR_GLOBAL) { + if (!v3d->global_perfmon) + return -EINVAL; + + xchg(&v3d->global_perfmon, NULL); + + return 0; + } + + if (cmpxchg(&v3d->global_perfmon, NULL, perfmon)) + return -EBUSY; + + return 0; +} diff --git a/drivers/gpu/drm/v3d/v3d_performance_counters.h b/drivers/gpu/drm/v3d/v3d_performance_counters.h index d919a2fc94490b..2bc4cce0744a16 100644 --- a/drivers/gpu/drm/v3d/v3d_performance_counters.h +++ b/drivers/gpu/drm/v3d/v3d_performance_counters.h @@ -2,11 +2,12 @@ /* * Copyright (C) 2024 Raspberry Pi */ + #ifndef V3D_PERFORMANCE_COUNTERS_H #define V3D_PERFORMANCE_COUNTERS_H -/* Holds a description of a given performance counter. The index of performance - * counter is given by the array on v3d_performance_counter.h +/* Holds a description of a given performance counter. The index of + * performance counter is given by the array on `v3d_performance_counter.c`. */ struct v3d_perf_counter_desc { /* Category of the counter */ @@ -20,15 +21,12 @@ struct v3d_perf_counter_desc { }; struct v3d_perfmon_info { - /* - * Different revisions of V3D have different total number of + /* Different revisions of V3D have different total number of * performance counters. */ unsigned int max_counters; - /* - * Array of counters valid for the platform. - */ + /* Array of counters valid for the platform. */ const struct v3d_perf_counter_desc *counters; }; diff --git a/drivers/gpu/drm/v3d/v3d_sched.c b/drivers/gpu/drm/v3d/v3d_sched.c index 4f935f1d50a943..230832c16cadec 100644 --- a/drivers/gpu/drm/v3d/v3d_sched.c +++ b/drivers/gpu/drm/v3d/v3d_sched.c @@ -5,16 +5,16 @@ * DOC: Broadcom V3D scheduling * * The shared DRM GPU scheduler is used to coordinate submitting jobs - * to the hardware. Each DRM fd (roughly a client process) gets its - * own scheduler entity, which will process jobs in order. The GPU - * scheduler will round-robin between clients to submit the next job. + * to the hardware. Each DRM fd (roughly a client process) gets its + * own scheduler entity, which will process jobs in order. The GPU + * scheduler will schedule the clients with a FIFO scheduling algorithm. * * For simplicity, and in order to keep latency low for interactive * jobs when bulk background jobs are queued up, we submit a new job * to the HW only when it has completed the last one, instead of - * filling up the CT[01]Q FIFOs with jobs. Similarly, we use - * drm_sched_job_add_dependency() to manage the dependency between bin and - * render, instead of having the clients submit jobs using the HW's + * filling up the CT[01]Q FIFOs with jobs. Similarly, we use + * `drm_sched_job_add_dependency()` to manage the dependency between bin + * and render, instead of having the clients submit jobs using the HW's * semaphores to interlock between them. */ @@ -120,11 +120,19 @@ v3d_cpu_job_free(struct drm_sched_job *sched_job) static void v3d_switch_perfmon(struct v3d_dev *v3d, struct v3d_job *job) { - if (job->perfmon != v3d->active_perfmon) + struct v3d_perfmon *perfmon = v3d->global_perfmon; + + if (!perfmon) + perfmon = job->perfmon; + + if (perfmon == v3d->active_perfmon) + return; + + if (perfmon != v3d->active_perfmon) v3d_perfmon_stop(v3d, v3d->active_perfmon, true); - if (job->perfmon && v3d->active_perfmon != job->perfmon) - v3d_perfmon_start(v3d, job->perfmon); + if (perfmon && v3d->active_perfmon != perfmon) + v3d_perfmon_start(v3d, perfmon); } static void @@ -648,8 +656,6 @@ v3d_cpu_job_run(struct drm_sched_job *sched_job) struct v3d_cpu_job *job = to_cpu_job(sched_job); struct v3d_dev *v3d = job->base.v3d; - v3d->cpu_job = job; - if (job->job_type >= ARRAY_SIZE(cpu_job_function)) { DRM_DEBUG_DRIVER("Unknown CPU job: %d\n", job->job_type); return NULL; diff --git a/drivers/gpu/drm/v3d/v3d_submit.c b/drivers/gpu/drm/v3d/v3d_submit.c index d607aa9c4ec210..c39de2d68fb7fd 100644 --- a/drivers/gpu/drm/v3d/v3d_submit.c +++ b/drivers/gpu/drm/v3d/v3d_submit.c @@ -5,16 +5,58 @@ */ #include <drm/drm_syncobj.h> +#include <linux/clk.h> #include "v3d_drv.h" #include "v3d_regs.h" #include "v3d_trace.h" +static void +v3d_clock_down_work(struct work_struct *work) +{ + struct v3d_dev *v3d = + container_of(work, struct v3d_dev, clk_down_work.work); + int ret; + + ret = clk_set_min_rate(v3d->clk, v3d->clk_down_rate); + v3d->clk_up = false; + WARN_ON_ONCE(ret != 0); +} + +static void +v3d_clock_up_get(struct v3d_dev *v3d) +{ + mutex_lock(&v3d->clk_lock); + if (v3d->clk_refcount++ == 0) { + cancel_delayed_work_sync(&v3d->clk_down_work); + if (!v3d->clk_up) { + int ret; + + ret = clk_set_min_rate(v3d->clk, v3d->clk_up_rate); + WARN_ON_ONCE(ret != 0); + v3d->clk_up = true; + } + } + mutex_unlock(&v3d->clk_lock); +} + +static void +v3d_clock_up_put(struct v3d_dev *v3d) +{ + mutex_lock(&v3d->clk_lock); + if (--v3d->clk_refcount == 0) { + schedule_delayed_work(&v3d->clk_down_work, + msecs_to_jiffies(100)); + } + mutex_unlock(&v3d->clk_lock); +} + /* Takes the reservation lock on all the BOs being referenced, so that - * at queue submit time we can update the reservations. + * we can attach fences and update the reservations after pushing the job + * to the queue. * * We don't lock the RCL the tile alloc/state BOs, or overflow memory - * (all of which are on exec->unref_list). They're entirely private + * (all of which are on render->unref_list). They're entirely private * to v3d, so we don't attach dma-buf fences to them. */ static int @@ -55,11 +97,11 @@ v3d_lock_bo_reservations(struct v3d_job *job, * @bo_count: Number of GEM handles passed in * * The command validator needs to reference BOs by their index within - * the submitted job's BO list. This does the validation of the job's + * the submitted job's BO list. This does the validation of the job's * BO list and reference counting for the lifetime of the job. * * Note that this function doesn't need to unreference the BOs on - * failure, because that will happen at v3d_exec_cleanup() time. + * failure, because that will happen at `v3d_job_free()`. */ static int v3d_lookup_bos(struct drm_device *dev, @@ -84,9 +126,10 @@ v3d_lookup_bos(struct drm_device *dev, } static void -v3d_job_free(struct kref *ref) +v3d_job_free_common(struct v3d_job *job, + bool is_gpu_job) { - struct v3d_job *job = container_of(ref, struct v3d_job, refcount); + struct v3d_dev *v3d = job->v3d; int i; if (job->bo) { @@ -98,12 +141,31 @@ v3d_job_free(struct kref *ref) dma_fence_put(job->irq_fence); dma_fence_put(job->done_fence); + if (is_gpu_job) + v3d_clock_up_put(v3d); + if (job->perfmon) v3d_perfmon_put(job->perfmon); kfree(job); } +static void +v3d_job_free(struct kref *ref) +{ + struct v3d_job *job = container_of(ref, struct v3d_job, refcount); + + v3d_job_free_common(job, true); +} + +static void +v3d_cpu_job_free(struct kref *ref) +{ + struct v3d_job *job = container_of(ref, struct v3d_job, refcount); + + v3d_job_free_common(job, false); +} + static void v3d_render_job_free(struct kref *ref) { @@ -198,6 +260,8 @@ v3d_job_init(struct v3d_dev *v3d, struct drm_file *file_priv, if (ret && ret != -ENOENT) goto fail_deps; } + if (queue != V3D_CPU) + v3d_clock_up_get(v3d); kref_init(&job->refcount); @@ -981,6 +1045,11 @@ v3d_submit_cl_ioctl(struct drm_device *dev, void *data, goto fail; if (args->perfmon_id) { + if (v3d->global_perfmon) { + ret = -EAGAIN; + goto fail_perfmon; + } + render->base.perfmon = v3d_perfmon_find(v3d_priv, args->perfmon_id); @@ -1196,6 +1265,11 @@ v3d_submit_csd_ioctl(struct drm_device *dev, void *data, goto fail; if (args->perfmon_id) { + if (v3d->global_perfmon) { + ret = -EAGAIN; + goto fail_perfmon; + } + job->base.perfmon = v3d_perfmon_find(v3d_priv, args->perfmon_id); if (!job->base.perfmon) { @@ -1305,7 +1379,7 @@ v3d_submit_cpu_ioctl(struct drm_device *dev, void *data, trace_v3d_submit_cpu_ioctl(&v3d->drm, cpu_job->job_type); ret = v3d_job_init(v3d, file_priv, &cpu_job->base, - v3d_job_free, 0, &se, V3D_CPU); + v3d_cpu_job_free, 0, &se, V3D_CPU); if (ret) { v3d_job_deallocate((void *)&cpu_job); goto fail; @@ -1393,3 +1467,14 @@ v3d_submit_cpu_ioctl(struct drm_device *dev, void *data, return ret; } + +void v3d_submit_init(struct drm_device *dev) { + struct v3d_dev *v3d = to_v3d_dev(dev); + + mutex_init(&v3d->clk_lock); + INIT_DELAYED_WORK(&v3d->clk_down_work, v3d_clock_down_work); + + /* kick the clock so firmware knows we are using firmware clock interface */ + v3d_clock_up_get(v3d); + v3d_clock_up_put(v3d); +} \ No newline at end of file diff --git a/drivers/gpu/drm/vc4/Makefile b/drivers/gpu/drm/vc4/Makefile index c41f89a15a5504..823ffbe0b78d4d 100644 --- a/drivers/gpu/drm/vc4/Makefile +++ b/drivers/gpu/drm/vc4/Makefile @@ -9,6 +9,7 @@ vc4-y := \ vc4_dpi.o \ vc4_dsi.o \ vc4_fence.o \ + vc4_firmware_kms.o \ vc4_kms.o \ vc4_gem.o \ vc4_hdmi.o \ @@ -30,7 +31,8 @@ vc4-$(CONFIG_DRM_VC4_KUNIT_TEST) += \ tests/vc4_mock_crtc.o \ tests/vc4_mock_output.o \ tests/vc4_mock_plane.o \ - tests/vc4_test_pv_muxing.o + tests/vc4_test_pv_muxing.o \ + tests/vc4_test_lbm_size.o vc4-$(CONFIG_DEBUG_FS) += vc4_debugfs.o diff --git a/drivers/gpu/drm/vc4/tests/vc4_mock.c b/drivers/gpu/drm/vc4/tests/vc4_mock.c index 922849dd4b4787..be427069f165b6 100644 --- a/drivers/gpu/drm/vc4/tests/vc4_mock.c +++ b/drivers/gpu/drm/vc4/tests/vc4_mock.c @@ -51,8 +51,8 @@ struct vc4_mock_desc { static const struct vc4_mock_desc vc4_mock = VC4_MOCK_DESC( - VC4_MOCK_CRTC_DESC(&vc4_txp_crtc_data, - VC4_MOCK_OUTPUT_DESC(VC4_ENCODER_TYPE_TXP, + VC4_MOCK_CRTC_DESC(&bcm2835_txp_data.base, + VC4_MOCK_OUTPUT_DESC(VC4_ENCODER_TYPE_TXP0, DRM_MODE_ENCODER_VIRTUAL, DRM_MODE_CONNECTOR_WRITEBACK)), VC4_MOCK_PIXELVALVE_DESC(&bcm2835_pv0_data, @@ -77,8 +77,8 @@ static const struct vc4_mock_desc vc4_mock = static const struct vc4_mock_desc vc5_mock = VC4_MOCK_DESC( - VC4_MOCK_CRTC_DESC(&vc4_txp_crtc_data, - VC4_MOCK_OUTPUT_DESC(VC4_ENCODER_TYPE_TXP, + VC4_MOCK_CRTC_DESC(&bcm2835_txp_data.base, + VC4_MOCK_OUTPUT_DESC(VC4_ENCODER_TYPE_TXP0, DRM_MODE_ENCODER_VIRTUAL, DRM_MODE_CONNECTOR_WRITEBACK)), VC4_MOCK_PIXELVALVE_DESC(&bcm2711_pv0_data, @@ -106,6 +106,26 @@ static const struct vc4_mock_desc vc5_mock = DRM_MODE_CONNECTOR_HDMIA)), ); +static const struct vc4_mock_desc vc6_mock = + VC4_MOCK_DESC( + VC4_MOCK_CRTC_DESC(&bcm2712_mop_data.base, + VC4_MOCK_OUTPUT_DESC(VC4_ENCODER_TYPE_TXP0, + DRM_MODE_ENCODER_VIRTUAL, + DRM_MODE_CONNECTOR_WRITEBACK)), + VC4_MOCK_CRTC_DESC(&bcm2712_moplet_data.base, + VC4_MOCK_OUTPUT_DESC(VC4_ENCODER_TYPE_TXP1, + DRM_MODE_ENCODER_VIRTUAL, + DRM_MODE_CONNECTOR_WRITEBACK)), + VC4_MOCK_PIXELVALVE_DESC(&bcm2712_pv0_data, + VC4_MOCK_OUTPUT_DESC(VC4_ENCODER_TYPE_HDMI0, + DRM_MODE_ENCODER_TMDS, + DRM_MODE_CONNECTOR_HDMIA)), + VC4_MOCK_PIXELVALVE_DESC(&bcm2712_pv1_data, + VC4_MOCK_OUTPUT_DESC(VC4_ENCODER_TYPE_HDMI1, + DRM_MODE_ENCODER_TMDS, + DRM_MODE_CONNECTOR_HDMIA)), +); + static int __build_one_pipe(struct kunit *test, struct drm_device *drm, const struct vc4_mock_pipe_desc *pipe) { @@ -157,13 +177,31 @@ KUNIT_DEFINE_ACTION_WRAPPER(kunit_action_drm_dev_unregister, static struct vc4_dev *__mock_device(struct kunit *test, enum vc4_gen gen) { + const struct vc4_mock_desc *desc; + const struct drm_driver *drv; struct drm_device *drm; - const struct drm_driver *drv = (gen == VC4_GEN_5) ? &vc5_drm_driver : &vc4_drm_driver; - const struct vc4_mock_desc *desc = (gen == VC4_GEN_5) ? &vc5_mock : &vc4_mock; struct vc4_dev *vc4; struct device *dev; int ret; + switch (gen) { + case VC4_GEN_4: + drv = &vc4_drm_driver; + desc = &vc4_mock; + break; + case VC4_GEN_5: + drv = &vc5_drm_driver; + desc = &vc5_mock; + break; + case VC4_GEN_6_C: + drv = &vc5_drm_driver; + desc = &vc6_mock; + break; + + default: + return NULL; + } + dev = drm_kunit_helper_alloc_device(test); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); @@ -175,7 +213,7 @@ static struct vc4_dev *__mock_device(struct kunit *test, enum vc4_gen gen) vc4->dev = dev; vc4->gen = gen; - vc4->hvs = __vc4_hvs_alloc(vc4, NULL); + vc4->hvs = __vc4_hvs_alloc(vc4, NULL, NULL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, vc4->hvs); drm = &vc4->base; @@ -205,3 +243,8 @@ struct vc4_dev *vc5_mock_device(struct kunit *test) { return __mock_device(test, VC4_GEN_5); } + +struct vc4_dev *vc6_mock_device(struct kunit *test) +{ + return __mock_device(test, VC4_GEN_6_C); +} diff --git a/drivers/gpu/drm/vc4/tests/vc4_mock.h b/drivers/gpu/drm/vc4/tests/vc4_mock.h index 002b6218960c48..37f6db2e4ff874 100644 --- a/drivers/gpu/drm/vc4/tests/vc4_mock.h +++ b/drivers/gpu/drm/vc4/tests/vc4_mock.h @@ -7,9 +7,9 @@ static inline struct drm_crtc *vc4_find_crtc_for_encoder(struct kunit *test, - struct drm_device *drm, struct drm_encoder *encoder) { + struct drm_device *drm = encoder->dev; struct drm_crtc *crtc; KUNIT_ASSERT_EQ(test, hweight32(encoder->possible_crtcs), 1); @@ -21,9 +21,28 @@ struct drm_crtc *vc4_find_crtc_for_encoder(struct kunit *test, return NULL; } +static inline +struct drm_plane *vc4_mock_find_plane_for_crtc(struct kunit *test, + struct drm_crtc *crtc) +{ + struct drm_device *drm = crtc->dev; + struct drm_plane *plane; + + drm_for_each_plane(plane, drm) + if (plane->possible_crtcs & drm_crtc_mask(crtc)) + return plane; + + return NULL; +} + struct drm_plane *vc4_dummy_plane(struct kunit *test, struct drm_device *drm, enum drm_plane_type type); +struct drm_plane * +vc4_mock_atomic_add_plane(struct kunit *test, + struct drm_atomic_state *state, + struct drm_crtc *crtc); + struct vc4_dummy_crtc { struct vc4_crtc crtc; }; @@ -50,10 +69,12 @@ struct vc4_dummy_output *vc4_dummy_output(struct kunit *test, struct vc4_dev *vc4_mock_device(struct kunit *test); struct vc4_dev *vc5_mock_device(struct kunit *test); +struct vc4_dev *vc6_mock_device(struct kunit *test); -int vc4_mock_atomic_add_output(struct kunit *test, - struct drm_atomic_state *state, - enum vc4_encoder_type type); +struct vc4_dummy_output * +vc4_mock_atomic_add_output(struct kunit *test, + struct drm_atomic_state *state, + enum vc4_encoder_type type); int vc4_mock_atomic_del_output(struct kunit *test, struct drm_atomic_state *state, enum vc4_encoder_type type); diff --git a/drivers/gpu/drm/vc4/tests/vc4_mock_output.c b/drivers/gpu/drm/vc4/tests/vc4_mock_output.c index e70d7c3076acf1..bcc6c78d80abba 100644 --- a/drivers/gpu/drm/vc4/tests/vc4_mock_output.c +++ b/drivers/gpu/drm/vc4/tests/vc4_mock_output.c @@ -61,9 +61,10 @@ static const struct drm_display_mode default_mode = { DRM_SIMPLE_MODE(640, 480, 64, 48) }; -int vc4_mock_atomic_add_output(struct kunit *test, - struct drm_atomic_state *state, - enum vc4_encoder_type type) +struct vc4_dummy_output * +vc4_mock_atomic_add_output(struct kunit *test, + struct drm_atomic_state *state, + enum vc4_encoder_type type) { struct drm_device *drm = state->dev; struct drm_connector_state *conn_state; @@ -77,7 +78,7 @@ int vc4_mock_atomic_add_output(struct kunit *test, encoder = vc4_find_encoder_by_type(drm, type); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, encoder); - crtc = vc4_find_crtc_for_encoder(test, drm, encoder); + crtc = vc4_find_crtc_for_encoder(test, encoder); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc); output = encoder_to_vc4_dummy_output(encoder); @@ -96,7 +97,7 @@ int vc4_mock_atomic_add_output(struct kunit *test, crtc_state->active = true; - return 0; + return output; } int vc4_mock_atomic_del_output(struct kunit *test, @@ -115,7 +116,7 @@ int vc4_mock_atomic_del_output(struct kunit *test, encoder = vc4_find_encoder_by_type(drm, type); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, encoder); - crtc = vc4_find_crtc_for_encoder(test, drm, encoder); + crtc = vc4_find_crtc_for_encoder(test, encoder); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc); crtc_state = drm_atomic_get_crtc_state(state, crtc); diff --git a/drivers/gpu/drm/vc4/tests/vc4_mock_plane.c b/drivers/gpu/drm/vc4/tests/vc4_mock_plane.c index 14357db82238be..32e2a57de37803 100644 --- a/drivers/gpu/drm/vc4/tests/vc4_mock_plane.c +++ b/drivers/gpu/drm/vc4/tests/vc4_mock_plane.c @@ -1,12 +1,31 @@ // SPDX-License-Identifier: GPL-2.0 +#include <drm/drm_modeset_helper_vtables.h> #include <drm/drm_kunit_helpers.h> +#include <drm/drm_atomic_uapi.h> #include <drm/drm_plane.h> #include <kunit/test.h> #include "vc4_mock.h" +static const struct drm_plane_helper_funcs vc4_dummy_plane_helper_funcs = { + .atomic_check = vc4_plane_atomic_check, +}; + +static const struct drm_plane_funcs vc4_dummy_plane_funcs = { + .atomic_destroy_state = vc4_plane_destroy_state, + .atomic_duplicate_state = vc4_plane_duplicate_state, + .reset = vc4_plane_reset, +}; + +static const uint32_t vc4_dummy_plane_formats[] = { + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_YUV420, + DRM_FORMAT_YUV422, +}; + struct drm_plane *vc4_dummy_plane(struct kunit *test, struct drm_device *drm, enum drm_plane_type type) { @@ -15,11 +34,33 @@ struct drm_plane *vc4_dummy_plane(struct kunit *test, struct drm_device *drm, KUNIT_ASSERT_EQ(test, type, DRM_PLANE_TYPE_PRIMARY); plane = drm_kunit_helper_create_primary_plane(test, drm, - NULL, - NULL, - NULL, 0, + &vc4_dummy_plane_funcs, + &vc4_dummy_plane_helper_funcs, + vc4_dummy_plane_formats, + ARRAY_SIZE(vc4_dummy_plane_formats), NULL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane); return plane; } + +struct drm_plane * +vc4_mock_atomic_add_plane(struct kunit *test, + struct drm_atomic_state *state, + struct drm_crtc *crtc) +{ + struct drm_plane_state *plane_state; + struct drm_plane *plane; + int ret; + + plane = vc4_mock_find_plane_for_crtc(test, crtc); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane); + + plane_state = drm_atomic_get_plane_state(state, plane); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_state); + + ret = drm_atomic_set_crtc_for_plane(plane_state, crtc); + KUNIT_EXPECT_EQ(test, ret, 0); + + return plane; +} diff --git a/drivers/gpu/drm/vc4/tests/vc4_test_lbm_size.c b/drivers/gpu/drm/vc4/tests/vc4_test_lbm_size.c new file mode 100644 index 00000000000000..122c2664b09382 --- /dev/null +++ b/drivers/gpu/drm/vc4/tests/vc4_test_lbm_size.c @@ -0,0 +1,314 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_atomic_uapi.h> +#include <drm/drm_drv.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_plane.h> +#include <drm/drm_kunit_helpers.h> + +#include "../../drm_crtc_internal.h" +#include "../../drm_internal.h" + +#include <kunit/test.h> + +#include "../vc4_drv.h" + +#include "vc4_mock.h" + +u32 vc4_lbm_size(struct drm_plane_state *state); + +struct vc4_lbm_size_priv { + struct vc4_dev *vc4; + struct drm_file *file; + struct drm_modeset_acquire_ctx ctx; + struct drm_atomic_state *state; +}; + +struct vc4_lbm_size_param { + unsigned int src_w, src_h; + unsigned int crtc_w, crtc_h; + bool forced_alpha; + u32 fourcc; + enum vc4_scaling_mode expected_x_scaling[2]; + enum vc4_scaling_mode expected_y_scaling[2]; + unsigned int expected_lbm_size; +}; + +static const struct vc4_lbm_size_param vc4_test_lbm_size_params[] = { + { + .src_w = 256, + .crtc_w = 256, + .src_h = 256, + .crtc_h = 512, + .fourcc = DRM_FORMAT_ARGB8888, + .expected_x_scaling = { VC4_SCALING_NONE, }, + .expected_y_scaling = { VC4_SCALING_PPF, }, + .expected_lbm_size = 32, + }, + { + .src_w = 256, + .crtc_w = 179, + .src_h = 256, + .crtc_h = 512, + .fourcc = DRM_FORMAT_ARGB8888, + .expected_x_scaling = { VC4_SCALING_PPF, }, + .expected_y_scaling = { VC4_SCALING_PPF, }, + .expected_lbm_size = 23, + }, + { + .src_w = 256, + .crtc_w = 256, + .src_h = 256, + .crtc_h = 512, + .fourcc = DRM_FORMAT_XRGB8888, + .expected_x_scaling = { VC4_SCALING_NONE, }, + .expected_y_scaling = { VC4_SCALING_PPF, }, + .expected_lbm_size = 24, + }, + { + .src_w = 100, + .crtc_w = 73, + .src_h = 100, + .crtc_h = 73, + .fourcc = DRM_FORMAT_XRGB8888, + .expected_x_scaling = { VC4_SCALING_PPF, }, + .expected_y_scaling = { VC4_SCALING_PPF, }, + .expected_lbm_size = 8, + }, + { + .src_w = 256, + .crtc_w = 256, + .src_h = 256, + .crtc_h = 512, + .forced_alpha = true, + .fourcc = DRM_FORMAT_ARGB8888, + .expected_x_scaling = { VC4_SCALING_NONE, }, + .expected_y_scaling = { VC4_SCALING_PPF, }, + .expected_lbm_size = 24, + }, + { + .src_w = 100, + .crtc_w = 73, + .src_h = 100, + .crtc_h = 73, + .forced_alpha = true, + .fourcc = DRM_FORMAT_ARGB8888, + .expected_x_scaling = { VC4_SCALING_PPF, }, + .expected_y_scaling = { VC4_SCALING_PPF, }, + .expected_lbm_size = 8, + }, + { + .src_w = 256, + .crtc_w = 94, + .src_h = 256, + .crtc_h = 94, + .fourcc = DRM_FORMAT_ARGB8888, + .expected_x_scaling = { VC4_SCALING_TPZ, }, + .expected_y_scaling = { VC4_SCALING_TPZ, }, + .expected_lbm_size = 6, + }, + +/* + * TODO: Those tests reflect the LBM size calculation examples, but the + * driver ends up taking different scaler filters decisions, and thus + * doesn't end up with the same sizes. It would be valuable to have + * those tests, but the driver doesn't take a bad decision either, so + * it's not clear what we should do at this point. + */ +#if 0 + { + .src_w = 320, + .crtc_w = 320, + .src_h = 320, + .crtc_h = 320, + .fourcc = DRM_FORMAT_YUV420, + .expected_x_scaling = { VC4_SCALING_NONE, VC4_SCALING_NONE, }, + .expected_y_scaling = { VC4_SCALING_NONE, VC4_SCALING_PPF, }, + .expected_lbm_size = 10, + }, + { + .src_w = 512, + .crtc_w = 512, + .src_h = 512, + .crtc_h = 256, + .fourcc = DRM_FORMAT_YUV420, + .expected_x_scaling = { VC4_SCALING_NONE, VC4_SCALING_NONE, }, + .expected_y_scaling = { VC4_SCALING_TPZ, VC4_SCALING_NONE, }, + .expected_lbm_size = 5, + }, + { + .src_w = 486, + .crtc_w = 157, + .src_h = 404, + .crtc_h = 929, + .fourcc = DRM_FORMAT_YUV422, + .expected_x_scaling = { VC4_SCALING_PPF, VC4_SCALING_PPF, }, + .expected_y_scaling = { VC4_SCALING_PPF, VC4_SCALING_PPF, }, + .expected_lbm_size = 20, + }, + { + .src_w = 320, + .crtc_w = 128, + .src_h = 176, + .crtc_h = 70, + .fourcc = DRM_FORMAT_YUV420, + .expected_x_scaling = { VC4_SCALING_TPZ, VC4_SCALING_TPZ, }, + .expected_y_scaling = { VC4_SCALING_TPZ, VC4_SCALING_TPZ, }, + .expected_lbm_size = 8, + }, +#endif +}; + +static void vc4_test_lbm_size_desc(const struct vc4_lbm_size_param *t, char *desc) +{ + snprintf(desc, KUNIT_PARAM_DESC_SIZE, + "%ux%u to %ux%u %s(%p4cc)", + t->src_w, t->src_h, + t->crtc_w, t->crtc_h, + t->forced_alpha ? "with forced alpha " : "", + &t->fourcc); +} + +KUNIT_ARRAY_PARAM(vc4_test_lbm_size, + vc4_test_lbm_size_params, + vc4_test_lbm_size_desc); + +static void drm_vc4_test_vc4_lbm_size(struct kunit *test) +{ + const struct vc4_lbm_size_param *params = test->param_value; + const struct vc4_lbm_size_priv *priv = test->priv; + const struct drm_format_info *info; + struct drm_mode_fb_cmd2 fb_req = { }; + struct drm_atomic_state *state = priv->state; + struct vc4_plane_state *vc4_plane_state; + struct drm_plane_state *plane_state; + struct vc4_dummy_output *output; + struct drm_framebuffer *fb; + struct drm_plane *plane; + struct drm_crtc *crtc; + struct vc4_dev *vc4; + unsigned int i; + int ret; + + info = drm_format_info(params->fourcc); + KUNIT_ASSERT_NOT_NULL(test, info); + + output = vc4_mock_atomic_add_output(test, state, VC4_ENCODER_TYPE_HDMI0); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, output); + + crtc = vc4_find_crtc_for_encoder(test, &output->encoder.base); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc); + + plane = vc4_mock_atomic_add_plane(test, state, crtc); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane); + + plane_state = drm_atomic_get_plane_state(state, plane); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_state); + + vc4_plane_state = to_vc4_plane_state(plane_state); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, vc4_plane_state); + + fb_req.pixel_format = params->fourcc; + fb_req.width = params->src_w; + fb_req.height = params->src_h; + + for (i = 0; i < info->num_planes; i++) { + struct drm_mode_create_dumb dumb_args = { }; + + dumb_args.width = params->src_w; + dumb_args.height = params->src_h; + dumb_args.bpp = drm_format_info_bpp(info, i); + + ret = drm_mode_create_dumb(state->dev, &dumb_args, priv->file); + KUNIT_ASSERT_EQ(test, ret, 0); + + fb_req.handles[i] = dumb_args.handle; + fb_req.pitches[i] = dumb_args.pitch; + } + + fb = drm_internal_framebuffer_create(state->dev, &fb_req, priv->file); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fb); + + drm_atomic_set_fb_for_plane(plane_state, fb); + + plane_state->src_x = 0; + plane_state->src_y = 0; + plane_state->src_h = params->src_h << 16; + plane_state->src_w = params->src_w << 16; + + plane_state->crtc_x = 0; + plane_state->crtc_y = 0; + plane_state->crtc_h = params->crtc_h; + plane_state->crtc_w = params->crtc_w; + + if (params->forced_alpha) + plane_state->alpha = 128; + + ret = drm_atomic_check_only(state); + KUNIT_ASSERT_EQ(test, ret, 0); + + vc4 = to_vc4_dev(state->dev); + KUNIT_ASSERT_NOT_NULL(test, vc4); + KUNIT_ASSERT_NOT_NULL(test, vc4->hvs); + KUNIT_EXPECT_EQ(test, + vc4->hvs->lbm_refcounts[vc4_plane_state->lbm_handle].size, + params->expected_lbm_size); + + for (i = 0; i < 2; i++) { + KUNIT_EXPECT_EQ(test, + vc4_plane_state->x_scaling[i], + params->expected_x_scaling[i]); + KUNIT_EXPECT_EQ(test, + vc4_plane_state->y_scaling[i], + params->expected_y_scaling[i]); + } + + drm_framebuffer_put(fb); + + for (i = 0; i < info->num_planes; i++) + drm_mode_destroy_dumb(state->dev, fb_req.handles[i], priv->file); +} + +static struct kunit_case vc4_lbm_size_tests[] = { + KUNIT_CASE_PARAM(drm_vc4_test_vc4_lbm_size, + vc4_test_lbm_size_gen_params), + {} +}; + +static int vc4_lbm_size_test_init(struct kunit *test) +{ + struct drm_modeset_acquire_ctx *ctx; + struct vc4_lbm_size_priv *priv; + struct drm_device *drm; + struct vc4_dev *vc4; + + priv = kunit_kzalloc(test, sizeof(*priv), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, priv); + test->priv = priv; + + vc4 = vc6_mock_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, vc4); + priv->vc4 = vc4; + + priv->file = drm_file_alloc(priv->vc4->base.primary); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->file); + + ctx = drm_kunit_helper_acquire_ctx_alloc(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + + drm = &vc4->base; + priv->state = drm_kunit_helper_atomic_state_alloc(test, drm, ctx); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->state); + + return 0; +} + +static struct kunit_suite vc4_lbm_size_test_suite = { + .name = "vc4-lbm-size", + .init = vc4_lbm_size_test_init, + .test_cases = vc4_lbm_size_tests, +}; + +kunit_test_suite(vc4_lbm_size_test_suite); diff --git a/drivers/gpu/drm/vc4/tests/vc4_test_pv_muxing.c b/drivers/gpu/drm/vc4/tests/vc4_test_pv_muxing.c index 61622e95103120..9408e2549daf0c 100644 --- a/drivers/gpu/drm/vc4/tests/vc4_test_pv_muxing.c +++ b/drivers/gpu/drm/vc4/tests/vc4_test_pv_muxing.c @@ -90,20 +90,27 @@ static const struct encoder_constraint vc4_encoder_constraints[] = { ENCODER_CONSTRAINT(VC4_ENCODER_TYPE_DSI0, 0), ENCODER_CONSTRAINT(VC4_ENCODER_TYPE_HDMI0, 1), ENCODER_CONSTRAINT(VC4_ENCODER_TYPE_VEC, 1), - ENCODER_CONSTRAINT(VC4_ENCODER_TYPE_TXP, 2), - ENCODER_CONSTRAINT(VC4_ENCODER_TYPE_DSI1, 2), + ENCODER_CONSTRAINT(VC4_ENCODER_TYPE_TXP0, 2), + ENCODER_CONSTRAINT(VC4_ENCODER_TYPE_DSI1, 0, 1, 2), }; static const struct encoder_constraint vc5_encoder_constraints[] = { ENCODER_CONSTRAINT(VC4_ENCODER_TYPE_DPI, 0), ENCODER_CONSTRAINT(VC4_ENCODER_TYPE_DSI0, 0), ENCODER_CONSTRAINT(VC4_ENCODER_TYPE_VEC, 1), - ENCODER_CONSTRAINT(VC4_ENCODER_TYPE_TXP, 0, 2), + ENCODER_CONSTRAINT(VC4_ENCODER_TYPE_TXP0, 0, 2), ENCODER_CONSTRAINT(VC4_ENCODER_TYPE_DSI1, 0, 1, 2), ENCODER_CONSTRAINT(VC4_ENCODER_TYPE_HDMI0, 0, 1, 2), ENCODER_CONSTRAINT(VC4_ENCODER_TYPE_HDMI1, 0, 1, 2), }; +static const struct encoder_constraint vc6_encoder_constraints[] = { + ENCODER_CONSTRAINT(VC4_ENCODER_TYPE_HDMI0, 0), + ENCODER_CONSTRAINT(VC4_ENCODER_TYPE_HDMI1, 1), + ENCODER_CONSTRAINT(VC4_ENCODER_TYPE_TXP1, 1), + ENCODER_CONSTRAINT(VC4_ENCODER_TYPE_TXP0, 2), +}; + static bool check_vc4_encoder_constraints(enum vc4_encoder_type type, unsigned int channel) { return __check_encoder_constraints(vc4_encoder_constraints, @@ -118,6 +125,13 @@ static bool check_vc5_encoder_constraints(enum vc4_encoder_type type, unsigned i type, channel); } +static bool check_vc6_encoder_constraints(enum vc4_encoder_type type, unsigned int channel) +{ + return __check_encoder_constraints(vc6_encoder_constraints, + ARRAY_SIZE(vc6_encoder_constraints), + type, channel); +} + static struct vc4_crtc_state * get_vc4_crtc_state_for_encoder(struct kunit *test, const struct drm_atomic_state *state, @@ -131,7 +145,7 @@ get_vc4_crtc_state_for_encoder(struct kunit *test, encoder = vc4_find_encoder_by_type(drm, type); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, encoder); - crtc = vc4_find_crtc_for_encoder(test, drm, encoder); + crtc = vc4_find_crtc_for_encoder(test, encoder); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc); new_crtc_state = drm_atomic_get_new_crtc_state(state, crtc); @@ -195,6 +209,9 @@ static void vc4_test_pv_muxing_desc(const struct pv_muxing_param *t, char *desc) #define VC5_PV_MUXING_TEST(_name, ...) \ PV_MUXING_TEST(_name, vc5_mock_device, check_vc5_encoder_constraints, __VA_ARGS__) +#define VC6_PV_MUXING_TEST(_name, ...) \ + PV_MUXING_TEST(_name, vc6_mock_device, check_vc6_encoder_constraints, __VA_ARGS__) + static const struct pv_muxing_param vc4_test_pv_muxing_params[] = { VC4_PV_MUXING_TEST("1 output: DSI0", VC4_ENCODER_TYPE_DSI0), @@ -207,7 +224,7 @@ static const struct pv_muxing_param vc4_test_pv_muxing_params[] = { VC4_PV_MUXING_TEST("1 output: DSI1", VC4_ENCODER_TYPE_DSI1), VC4_PV_MUXING_TEST("1 output: TXP", - VC4_ENCODER_TYPE_TXP), + VC4_ENCODER_TYPE_TXP0), VC4_PV_MUXING_TEST("2 outputs: DSI0, HDMI0", VC4_ENCODER_TYPE_DSI0, VC4_ENCODER_TYPE_HDMI0), @@ -219,7 +236,7 @@ static const struct pv_muxing_param vc4_test_pv_muxing_params[] = { VC4_ENCODER_TYPE_DSI1), VC4_PV_MUXING_TEST("2 outputs: DSI0, TXP", VC4_ENCODER_TYPE_DSI0, - VC4_ENCODER_TYPE_TXP), + VC4_ENCODER_TYPE_TXP0), VC4_PV_MUXING_TEST("2 outputs: DPI, HDMI0", VC4_ENCODER_TYPE_DPI, VC4_ENCODER_TYPE_HDMI0), @@ -231,19 +248,22 @@ static const struct pv_muxing_param vc4_test_pv_muxing_params[] = { VC4_ENCODER_TYPE_DSI1), VC4_PV_MUXING_TEST("2 outputs: DPI, TXP", VC4_ENCODER_TYPE_DPI, - VC4_ENCODER_TYPE_TXP), + VC4_ENCODER_TYPE_TXP0), VC4_PV_MUXING_TEST("2 outputs: HDMI0, DSI1", VC4_ENCODER_TYPE_HDMI0, VC4_ENCODER_TYPE_DSI1), VC4_PV_MUXING_TEST("2 outputs: HDMI0, TXP", VC4_ENCODER_TYPE_HDMI0, - VC4_ENCODER_TYPE_TXP), + VC4_ENCODER_TYPE_TXP0), VC4_PV_MUXING_TEST("2 outputs: VEC, DSI1", VC4_ENCODER_TYPE_VEC, VC4_ENCODER_TYPE_DSI1), + VC4_PV_MUXING_TEST("2 outputs: TXP, DSI1", + VC4_ENCODER_TYPE_TXP0, + VC4_ENCODER_TYPE_DSI1), VC4_PV_MUXING_TEST("2 outputs: VEC, TXP", VC4_ENCODER_TYPE_VEC, - VC4_ENCODER_TYPE_TXP), + VC4_ENCODER_TYPE_TXP0), VC4_PV_MUXING_TEST("3 outputs: DSI0, HDMI0, DSI1", VC4_ENCODER_TYPE_DSI0, VC4_ENCODER_TYPE_HDMI0, @@ -251,7 +271,7 @@ static const struct pv_muxing_param vc4_test_pv_muxing_params[] = { VC4_PV_MUXING_TEST("3 outputs: DSI0, HDMI0, TXP", VC4_ENCODER_TYPE_DSI0, VC4_ENCODER_TYPE_HDMI0, - VC4_ENCODER_TYPE_TXP), + VC4_ENCODER_TYPE_TXP0), VC4_PV_MUXING_TEST("3 outputs: DSI0, VEC, DSI1", VC4_ENCODER_TYPE_DSI0, VC4_ENCODER_TYPE_VEC, @@ -259,7 +279,7 @@ static const struct pv_muxing_param vc4_test_pv_muxing_params[] = { VC4_PV_MUXING_TEST("3 outputs: DSI0, VEC, TXP", VC4_ENCODER_TYPE_DSI0, VC4_ENCODER_TYPE_VEC, - VC4_ENCODER_TYPE_TXP), + VC4_ENCODER_TYPE_TXP0), VC4_PV_MUXING_TEST("3 outputs: DPI, HDMI0, DSI1", VC4_ENCODER_TYPE_DPI, VC4_ENCODER_TYPE_HDMI0, @@ -267,7 +287,7 @@ static const struct pv_muxing_param vc4_test_pv_muxing_params[] = { VC4_PV_MUXING_TEST("3 outputs: DPI, HDMI0, TXP", VC4_ENCODER_TYPE_DPI, VC4_ENCODER_TYPE_HDMI0, - VC4_ENCODER_TYPE_TXP), + VC4_ENCODER_TYPE_TXP0), VC4_PV_MUXING_TEST("3 outputs: DPI, VEC, DSI1", VC4_ENCODER_TYPE_DPI, VC4_ENCODER_TYPE_VEC, @@ -275,7 +295,23 @@ static const struct pv_muxing_param vc4_test_pv_muxing_params[] = { VC4_PV_MUXING_TEST("3 outputs: DPI, VEC, TXP", VC4_ENCODER_TYPE_DPI, VC4_ENCODER_TYPE_VEC, - VC4_ENCODER_TYPE_TXP), + VC4_ENCODER_TYPE_TXP0), + VC4_PV_MUXING_TEST("3 outputs: DSI1, HDMI, TXP", + VC4_ENCODER_TYPE_DSI1, + VC4_ENCODER_TYPE_HDMI0, + VC4_ENCODER_TYPE_TXP0), + VC4_PV_MUXING_TEST("3 outputs: DSI1, VEC, TXP", + VC4_ENCODER_TYPE_DSI1, + VC4_ENCODER_TYPE_VEC, + VC4_ENCODER_TYPE_TXP0), + VC4_PV_MUXING_TEST("3 outputs: DSI1, DPI, TXP", + VC4_ENCODER_TYPE_DSI1, + VC4_ENCODER_TYPE_DPI, + VC4_ENCODER_TYPE_TXP0), + VC4_PV_MUXING_TEST("3 outputs: DSI1, DSI0, TXP", + VC4_ENCODER_TYPE_DSI1, + VC4_ENCODER_TYPE_DSI0, + VC4_ENCODER_TYPE_TXP0), }; KUNIT_ARRAY_PARAM(vc4_test_pv_muxing, @@ -286,9 +322,6 @@ static const struct pv_muxing_param vc4_test_pv_muxing_invalid_params[] = { VC4_PV_MUXING_TEST("DPI/DSI0 Conflict", VC4_ENCODER_TYPE_DPI, VC4_ENCODER_TYPE_DSI0), - VC4_PV_MUXING_TEST("TXP/DSI1 Conflict", - VC4_ENCODER_TYPE_TXP, - VC4_ENCODER_TYPE_DSI1), VC4_PV_MUXING_TEST("HDMI0/VEC Conflict", VC4_ENCODER_TYPE_HDMI0, VC4_ENCODER_TYPE_VEC), @@ -296,22 +329,22 @@ static const struct pv_muxing_param vc4_test_pv_muxing_invalid_params[] = { VC4_ENCODER_TYPE_DSI0, VC4_ENCODER_TYPE_HDMI0, VC4_ENCODER_TYPE_DSI1, - VC4_ENCODER_TYPE_TXP), + VC4_ENCODER_TYPE_TXP0), VC4_PV_MUXING_TEST("More than 3 outputs: DSI0, VEC, DSI1, TXP", VC4_ENCODER_TYPE_DSI0, VC4_ENCODER_TYPE_VEC, VC4_ENCODER_TYPE_DSI1, - VC4_ENCODER_TYPE_TXP), + VC4_ENCODER_TYPE_TXP0), VC4_PV_MUXING_TEST("More than 3 outputs: DPI, HDMI0, DSI1, TXP", VC4_ENCODER_TYPE_DPI, VC4_ENCODER_TYPE_HDMI0, VC4_ENCODER_TYPE_DSI1, - VC4_ENCODER_TYPE_TXP), + VC4_ENCODER_TYPE_TXP0), VC4_PV_MUXING_TEST("More than 3 outputs: DPI, VEC, DSI1, TXP", VC4_ENCODER_TYPE_DPI, VC4_ENCODER_TYPE_VEC, VC4_ENCODER_TYPE_DSI1, - VC4_ENCODER_TYPE_TXP), + VC4_ENCODER_TYPE_TXP0), }; KUNIT_ARRAY_PARAM(vc4_test_pv_muxing_invalid, @@ -342,7 +375,7 @@ static const struct pv_muxing_param vc5_test_pv_muxing_params[] = { VC4_ENCODER_TYPE_HDMI1), VC5_PV_MUXING_TEST("2 outputs: DPI, TXP", VC4_ENCODER_TYPE_DPI, - VC4_ENCODER_TYPE_TXP), + VC4_ENCODER_TYPE_TXP0), VC5_PV_MUXING_TEST("2 outputs: DPI, VEC", VC4_ENCODER_TYPE_DPI, VC4_ENCODER_TYPE_VEC), @@ -360,7 +393,7 @@ static const struct pv_muxing_param vc5_test_pv_muxing_params[] = { VC4_ENCODER_TYPE_HDMI1), VC5_PV_MUXING_TEST("2 outputs: DSI0, TXP", VC4_ENCODER_TYPE_DSI0, - VC4_ENCODER_TYPE_TXP), + VC4_ENCODER_TYPE_TXP0), VC5_PV_MUXING_TEST("2 outputs: DSI0, VEC", VC4_ENCODER_TYPE_DSI0, VC4_ENCODER_TYPE_VEC), @@ -372,7 +405,7 @@ static const struct pv_muxing_param vc5_test_pv_muxing_params[] = { VC4_ENCODER_TYPE_VEC), VC5_PV_MUXING_TEST("2 outputs: DSI1, TXP", VC4_ENCODER_TYPE_DSI1, - VC4_ENCODER_TYPE_TXP), + VC4_ENCODER_TYPE_TXP0), VC5_PV_MUXING_TEST("2 outputs: DSI1, HDMI0", VC4_ENCODER_TYPE_DSI1, VC4_ENCODER_TYPE_HDMI0), @@ -384,7 +417,7 @@ static const struct pv_muxing_param vc5_test_pv_muxing_params[] = { VC4_ENCODER_TYPE_VEC), VC5_PV_MUXING_TEST("2 outputs: HDMI0, TXP", VC4_ENCODER_TYPE_HDMI0, - VC4_ENCODER_TYPE_TXP), + VC4_ENCODER_TYPE_TXP0), VC5_PV_MUXING_TEST("2 outputs: HDMI0, HDMI1", VC4_ENCODER_TYPE_HDMI0, VC4_ENCODER_TYPE_HDMI1), @@ -393,14 +426,14 @@ static const struct pv_muxing_param vc5_test_pv_muxing_params[] = { VC4_ENCODER_TYPE_VEC), VC5_PV_MUXING_TEST("2 outputs: HDMI1, TXP", VC4_ENCODER_TYPE_HDMI1, - VC4_ENCODER_TYPE_TXP), + VC4_ENCODER_TYPE_TXP0), VC5_PV_MUXING_TEST("2 outputs: TXP, VEC", - VC4_ENCODER_TYPE_TXP, + VC4_ENCODER_TYPE_TXP0, VC4_ENCODER_TYPE_VEC), VC5_PV_MUXING_TEST("3 outputs: DPI, VEC, TXP", VC4_ENCODER_TYPE_DPI, VC4_ENCODER_TYPE_VEC, - VC4_ENCODER_TYPE_TXP), + VC4_ENCODER_TYPE_TXP0), VC5_PV_MUXING_TEST("3 outputs: DPI, VEC, DSI1", VC4_ENCODER_TYPE_DPI, VC4_ENCODER_TYPE_VEC, @@ -415,15 +448,15 @@ static const struct pv_muxing_param vc5_test_pv_muxing_params[] = { VC4_ENCODER_TYPE_HDMI1), VC5_PV_MUXING_TEST("3 outputs: DPI, TXP, DSI1", VC4_ENCODER_TYPE_DPI, - VC4_ENCODER_TYPE_TXP, + VC4_ENCODER_TYPE_TXP0, VC4_ENCODER_TYPE_DSI1), VC5_PV_MUXING_TEST("3 outputs: DPI, TXP, HDMI0", VC4_ENCODER_TYPE_DPI, - VC4_ENCODER_TYPE_TXP, + VC4_ENCODER_TYPE_TXP0, VC4_ENCODER_TYPE_HDMI0), VC5_PV_MUXING_TEST("3 outputs: DPI, TXP, HDMI1", VC4_ENCODER_TYPE_DPI, - VC4_ENCODER_TYPE_TXP, + VC4_ENCODER_TYPE_TXP0, VC4_ENCODER_TYPE_HDMI1), VC5_PV_MUXING_TEST("3 outputs: DPI, DSI1, HDMI0", VC4_ENCODER_TYPE_DPI, @@ -440,7 +473,7 @@ static const struct pv_muxing_param vc5_test_pv_muxing_params[] = { VC5_PV_MUXING_TEST("3 outputs: DSI0, VEC, TXP", VC4_ENCODER_TYPE_DSI0, VC4_ENCODER_TYPE_VEC, - VC4_ENCODER_TYPE_TXP), + VC4_ENCODER_TYPE_TXP0), VC5_PV_MUXING_TEST("3 outputs: DSI0, VEC, DSI1", VC4_ENCODER_TYPE_DSI0, VC4_ENCODER_TYPE_VEC, @@ -455,15 +488,15 @@ static const struct pv_muxing_param vc5_test_pv_muxing_params[] = { VC4_ENCODER_TYPE_HDMI1), VC5_PV_MUXING_TEST("3 outputs: DSI0, TXP, DSI1", VC4_ENCODER_TYPE_DSI0, - VC4_ENCODER_TYPE_TXP, + VC4_ENCODER_TYPE_TXP0, VC4_ENCODER_TYPE_DSI1), VC5_PV_MUXING_TEST("3 outputs: DSI0, TXP, HDMI0", VC4_ENCODER_TYPE_DSI0, - VC4_ENCODER_TYPE_TXP, + VC4_ENCODER_TYPE_TXP0, VC4_ENCODER_TYPE_HDMI0), VC5_PV_MUXING_TEST("3 outputs: DSI0, TXP, HDMI1", VC4_ENCODER_TYPE_DSI0, - VC4_ENCODER_TYPE_TXP, + VC4_ENCODER_TYPE_TXP0, VC4_ENCODER_TYPE_HDMI1), VC5_PV_MUXING_TEST("3 outputs: DSI0, DSI1, HDMI0", VC4_ENCODER_TYPE_DSI0, @@ -490,17 +523,17 @@ static const struct pv_muxing_param vc5_test_pv_muxing_invalid_params[] = { VC5_PV_MUXING_TEST("More than 3 outputs: DPI, VEC, TXP, DSI1", VC4_ENCODER_TYPE_DPI, VC4_ENCODER_TYPE_VEC, - VC4_ENCODER_TYPE_TXP, + VC4_ENCODER_TYPE_TXP0, VC4_ENCODER_TYPE_DSI1), VC5_PV_MUXING_TEST("More than 3 outputs: DPI, VEC, TXP, HDMI0", VC4_ENCODER_TYPE_DPI, VC4_ENCODER_TYPE_VEC, - VC4_ENCODER_TYPE_TXP, + VC4_ENCODER_TYPE_TXP0, VC4_ENCODER_TYPE_HDMI0), VC5_PV_MUXING_TEST("More than 3 outputs: DPI, VEC, TXP, HDMI1", VC4_ENCODER_TYPE_DPI, VC4_ENCODER_TYPE_VEC, - VC4_ENCODER_TYPE_TXP, + VC4_ENCODER_TYPE_TXP0, VC4_ENCODER_TYPE_HDMI1), VC5_PV_MUXING_TEST("More than 3 outputs: DPI, VEC, DSI1, HDMI0", VC4_ENCODER_TYPE_DPI, @@ -519,17 +552,17 @@ static const struct pv_muxing_param vc5_test_pv_muxing_invalid_params[] = { VC4_ENCODER_TYPE_HDMI1), VC5_PV_MUXING_TEST("More than 3 outputs: DPI, TXP, DSI1, HDMI0", VC4_ENCODER_TYPE_DPI, - VC4_ENCODER_TYPE_TXP, + VC4_ENCODER_TYPE_TXP0, VC4_ENCODER_TYPE_DSI1, VC4_ENCODER_TYPE_HDMI0), VC5_PV_MUXING_TEST("More than 3 outputs: DPI, TXP, DSI1, HDMI1", VC4_ENCODER_TYPE_DPI, - VC4_ENCODER_TYPE_TXP, + VC4_ENCODER_TYPE_TXP0, VC4_ENCODER_TYPE_DSI1, VC4_ENCODER_TYPE_HDMI1), VC5_PV_MUXING_TEST("More than 3 outputs: DPI, TXP, HDMI0, HDMI1", VC4_ENCODER_TYPE_DPI, - VC4_ENCODER_TYPE_TXP, + VC4_ENCODER_TYPE_TXP0, VC4_ENCODER_TYPE_HDMI0, VC4_ENCODER_TYPE_HDMI1), VC5_PV_MUXING_TEST("More than 3 outputs: DPI, DSI1, HDMI0, HDMI1", @@ -540,19 +573,19 @@ static const struct pv_muxing_param vc5_test_pv_muxing_invalid_params[] = { VC5_PV_MUXING_TEST("More than 3 outputs: DPI, VEC, TXP, DSI1, HDMI0", VC4_ENCODER_TYPE_DPI, VC4_ENCODER_TYPE_VEC, - VC4_ENCODER_TYPE_TXP, + VC4_ENCODER_TYPE_TXP0, VC4_ENCODER_TYPE_DSI1, VC4_ENCODER_TYPE_HDMI0), VC5_PV_MUXING_TEST("More than 3 outputs: DPI, VEC, TXP, DSI1, HDMI1", VC4_ENCODER_TYPE_DPI, VC4_ENCODER_TYPE_VEC, - VC4_ENCODER_TYPE_TXP, + VC4_ENCODER_TYPE_TXP0, VC4_ENCODER_TYPE_DSI1, VC4_ENCODER_TYPE_HDMI1), VC5_PV_MUXING_TEST("More than 3 outputs: DPI, VEC, TXP, HDMI0, HDMI1", VC4_ENCODER_TYPE_DPI, VC4_ENCODER_TYPE_VEC, - VC4_ENCODER_TYPE_TXP, + VC4_ENCODER_TYPE_TXP0, VC4_ENCODER_TYPE_HDMI0, VC4_ENCODER_TYPE_HDMI1), VC5_PV_MUXING_TEST("More than 3 outputs: DPI, VEC, DSI1, HDMI0, HDMI1", @@ -563,24 +596,24 @@ static const struct pv_muxing_param vc5_test_pv_muxing_invalid_params[] = { VC4_ENCODER_TYPE_HDMI1), VC5_PV_MUXING_TEST("More than 3 outputs: DPI, TXP, DSI1, HDMI0, HDMI1", VC4_ENCODER_TYPE_DPI, - VC4_ENCODER_TYPE_TXP, + VC4_ENCODER_TYPE_TXP0, VC4_ENCODER_TYPE_DSI1, VC4_ENCODER_TYPE_HDMI0, VC4_ENCODER_TYPE_HDMI1), VC5_PV_MUXING_TEST("More than 3 outputs: DSI0, VEC, TXP, DSI1", VC4_ENCODER_TYPE_DSI0, VC4_ENCODER_TYPE_VEC, - VC4_ENCODER_TYPE_TXP, + VC4_ENCODER_TYPE_TXP0, VC4_ENCODER_TYPE_DSI1), VC5_PV_MUXING_TEST("More than 3 outputs: DSI0, VEC, TXP, HDMI0", VC4_ENCODER_TYPE_DSI0, VC4_ENCODER_TYPE_VEC, - VC4_ENCODER_TYPE_TXP, + VC4_ENCODER_TYPE_TXP0, VC4_ENCODER_TYPE_HDMI0), VC5_PV_MUXING_TEST("More than 3 outputs: DSI0, VEC, TXP, HDMI1", VC4_ENCODER_TYPE_DSI0, VC4_ENCODER_TYPE_VEC, - VC4_ENCODER_TYPE_TXP, + VC4_ENCODER_TYPE_TXP0, VC4_ENCODER_TYPE_HDMI1), VC5_PV_MUXING_TEST("More than 3 outputs: DSI0, VEC, DSI1, HDMI0", VC4_ENCODER_TYPE_DSI0, @@ -599,17 +632,17 @@ static const struct pv_muxing_param vc5_test_pv_muxing_invalid_params[] = { VC4_ENCODER_TYPE_HDMI1), VC5_PV_MUXING_TEST("More than 3 outputs: DSI0, TXP, DSI1, HDMI0", VC4_ENCODER_TYPE_DSI0, - VC4_ENCODER_TYPE_TXP, + VC4_ENCODER_TYPE_TXP0, VC4_ENCODER_TYPE_DSI1, VC4_ENCODER_TYPE_HDMI0), VC5_PV_MUXING_TEST("More than 3 outputs: DSI0, TXP, DSI1, HDMI1", VC4_ENCODER_TYPE_DSI0, - VC4_ENCODER_TYPE_TXP, + VC4_ENCODER_TYPE_TXP0, VC4_ENCODER_TYPE_DSI1, VC4_ENCODER_TYPE_HDMI1), VC5_PV_MUXING_TEST("More than 3 outputs: DSI0, TXP, HDMI0, HDMI1", VC4_ENCODER_TYPE_DSI0, - VC4_ENCODER_TYPE_TXP, + VC4_ENCODER_TYPE_TXP0, VC4_ENCODER_TYPE_HDMI0, VC4_ENCODER_TYPE_HDMI1), VC5_PV_MUXING_TEST("More than 3 outputs: DSI0, DSI1, HDMI0, HDMI1", @@ -620,19 +653,19 @@ static const struct pv_muxing_param vc5_test_pv_muxing_invalid_params[] = { VC5_PV_MUXING_TEST("More than 3 outputs: DSI0, VEC, TXP, DSI1, HDMI0", VC4_ENCODER_TYPE_DSI0, VC4_ENCODER_TYPE_VEC, - VC4_ENCODER_TYPE_TXP, + VC4_ENCODER_TYPE_TXP0, VC4_ENCODER_TYPE_DSI1, VC4_ENCODER_TYPE_HDMI0), VC5_PV_MUXING_TEST("More than 3 outputs: DSI0, VEC, TXP, DSI1, HDMI1", VC4_ENCODER_TYPE_DSI0, VC4_ENCODER_TYPE_VEC, - VC4_ENCODER_TYPE_TXP, + VC4_ENCODER_TYPE_TXP0, VC4_ENCODER_TYPE_DSI1, VC4_ENCODER_TYPE_HDMI1), VC5_PV_MUXING_TEST("More than 3 outputs: DSI0, VEC, TXP, HDMI0, HDMI1", VC4_ENCODER_TYPE_DSI0, VC4_ENCODER_TYPE_VEC, - VC4_ENCODER_TYPE_TXP, + VC4_ENCODER_TYPE_TXP0, VC4_ENCODER_TYPE_HDMI0, VC4_ENCODER_TYPE_HDMI1), VC5_PV_MUXING_TEST("More than 3 outputs: DSI0, VEC, DSI1, HDMI0, HDMI1", @@ -643,27 +676,27 @@ static const struct pv_muxing_param vc5_test_pv_muxing_invalid_params[] = { VC4_ENCODER_TYPE_HDMI1), VC5_PV_MUXING_TEST("More than 3 outputs: DSI0, TXP, DSI1, HDMI0, HDMI1", VC4_ENCODER_TYPE_DSI0, - VC4_ENCODER_TYPE_TXP, + VC4_ENCODER_TYPE_TXP0, VC4_ENCODER_TYPE_DSI1, VC4_ENCODER_TYPE_HDMI0, VC4_ENCODER_TYPE_HDMI1), VC5_PV_MUXING_TEST("More than 3 outputs: VEC, TXP, DSI1, HDMI0, HDMI1", VC4_ENCODER_TYPE_VEC, - VC4_ENCODER_TYPE_TXP, + VC4_ENCODER_TYPE_TXP0, VC4_ENCODER_TYPE_DSI1, VC4_ENCODER_TYPE_HDMI0, VC4_ENCODER_TYPE_HDMI1), VC5_PV_MUXING_TEST("More than 3 outputs: DPI, VEC, TXP, DSI1, HDMI0, HDMI1", VC4_ENCODER_TYPE_DPI, VC4_ENCODER_TYPE_VEC, - VC4_ENCODER_TYPE_TXP, + VC4_ENCODER_TYPE_TXP0, VC4_ENCODER_TYPE_DSI1, VC4_ENCODER_TYPE_HDMI0, VC4_ENCODER_TYPE_HDMI1), VC5_PV_MUXING_TEST("More than 3 outputs: DSI0, VEC, TXP, DSI1, HDMI0, HDMI1", VC4_ENCODER_TYPE_DSI0, VC4_ENCODER_TYPE_VEC, - VC4_ENCODER_TYPE_TXP, + VC4_ENCODER_TYPE_TXP0, VC4_ENCODER_TYPE_DSI1, VC4_ENCODER_TYPE_HDMI0, VC4_ENCODER_TYPE_HDMI1), @@ -673,6 +706,54 @@ KUNIT_ARRAY_PARAM(vc5_test_pv_muxing_invalid, vc5_test_pv_muxing_invalid_params, vc4_test_pv_muxing_desc); +static const struct pv_muxing_param vc6_test_pv_muxing_params[] = { + VC6_PV_MUXING_TEST("1 output: HDMI0", + VC4_ENCODER_TYPE_HDMI0), + VC6_PV_MUXING_TEST("1 output: HDMI1", + VC4_ENCODER_TYPE_HDMI1), + VC6_PV_MUXING_TEST("1 output: MOPLET", + VC4_ENCODER_TYPE_TXP1), + VC6_PV_MUXING_TEST("1 output: MOP", + VC4_ENCODER_TYPE_TXP0), + VC6_PV_MUXING_TEST("2 outputs: HDMI0, HDMI1", + VC4_ENCODER_TYPE_HDMI0, + VC4_ENCODER_TYPE_HDMI1), + VC6_PV_MUXING_TEST("2 outputs: HDMI0, MOPLET", + VC4_ENCODER_TYPE_HDMI0, + VC4_ENCODER_TYPE_TXP1), + VC6_PV_MUXING_TEST("2 outputs: HDMI0, MOP", + VC4_ENCODER_TYPE_HDMI0, + VC4_ENCODER_TYPE_TXP0), + VC6_PV_MUXING_TEST("2 outputs: HDMI1, MOP", + VC4_ENCODER_TYPE_HDMI1, + VC4_ENCODER_TYPE_TXP0), + VC6_PV_MUXING_TEST("2 outputs: MOPLET, MOP", + VC4_ENCODER_TYPE_TXP1, + VC4_ENCODER_TYPE_TXP0), + VC6_PV_MUXING_TEST("3 outputs: HDMI0, HDMI1, MOP", + VC4_ENCODER_TYPE_HDMI0, + VC4_ENCODER_TYPE_HDMI1, + VC4_ENCODER_TYPE_TXP0), + VC6_PV_MUXING_TEST("3 outputs: HDMI0, MOPLET, MOP", + VC4_ENCODER_TYPE_HDMI0, + VC4_ENCODER_TYPE_TXP1, + VC4_ENCODER_TYPE_TXP0), +}; + +KUNIT_ARRAY_PARAM(vc6_test_pv_muxing, + vc6_test_pv_muxing_params, + vc4_test_pv_muxing_desc); + +static const struct pv_muxing_param vc6_test_pv_muxing_invalid_params[] = { + VC6_PV_MUXING_TEST("HDMI1/MOPLET Conflict", + VC4_ENCODER_TYPE_HDMI1, + VC4_ENCODER_TYPE_TXP1), +}; + +KUNIT_ARRAY_PARAM(vc6_test_pv_muxing_invalid, + vc6_test_pv_muxing_invalid_params, + vc4_test_pv_muxing_desc); + static void drm_vc4_test_pv_muxing(struct kunit *test) { const struct pv_muxing_param *params = test->param_value; @@ -682,10 +763,11 @@ static void drm_vc4_test_pv_muxing(struct kunit *test) int ret; for (i = 0; i < params->nencoders; i++) { + struct vc4_dummy_output *output; enum vc4_encoder_type enc_type = params->encoders[i]; - ret = vc4_mock_atomic_add_output(test, state, enc_type); - KUNIT_ASSERT_EQ(test, ret, 0); + output = vc4_mock_atomic_add_output(test, state, enc_type); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, output); } ret = drm_atomic_check_only(state); @@ -711,10 +793,11 @@ static void drm_vc4_test_pv_muxing_invalid(struct kunit *test) int ret; for (i = 0; i < params->nencoders; i++) { + struct vc4_dummy_output *output; enum vc4_encoder_type enc_type = params->encoders[i]; - ret = vc4_mock_atomic_add_output(test, state, enc_type); - KUNIT_ASSERT_EQ(test, ret, 0); + output = vc4_mock_atomic_add_output(test, state, enc_type); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, output); } ret = drm_atomic_check_only(state); @@ -775,6 +858,20 @@ static struct kunit_suite vc5_pv_muxing_test_suite = { .test_cases = vc5_pv_muxing_tests, }; +static struct kunit_case vc6_pv_muxing_tests[] = { + KUNIT_CASE_PARAM(drm_vc4_test_pv_muxing, + vc6_test_pv_muxing_gen_params), + KUNIT_CASE_PARAM(drm_vc4_test_pv_muxing_invalid, + vc6_test_pv_muxing_invalid_gen_params), + {} +}; + +static struct kunit_suite vc6_pv_muxing_test_suite = { + .name = "vc6-pv-muxing-combinations", + .init = vc4_pv_muxing_test_init, + .test_cases = vc6_pv_muxing_tests, +}; + /* See * https://lore.kernel.org/all/3e113525-aa89-b1e2-56b7-ca55bd41d057@samsung.com/ * and @@ -784,6 +881,7 @@ static void drm_test_vc5_pv_muxing_bugs_subsequent_crtc_enable(struct kunit *tes { struct drm_modeset_acquire_ctx *ctx; struct drm_atomic_state *state; + struct vc4_dummy_output *output; struct vc4_crtc_state *new_vc4_crtc_state; struct vc4_hvs_state *new_hvs_state; unsigned int hdmi0_channel; @@ -802,8 +900,8 @@ static void drm_test_vc5_pv_muxing_bugs_subsequent_crtc_enable(struct kunit *tes state = drm_kunit_helper_atomic_state_alloc(test, drm, ctx); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); - ret = vc4_mock_atomic_add_output(test, state, VC4_ENCODER_TYPE_HDMI0); - KUNIT_ASSERT_EQ(test, ret, 0); + output = vc4_mock_atomic_add_output(test, state, VC4_ENCODER_TYPE_HDMI0); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, output); ret = drm_atomic_check_only(state); KUNIT_ASSERT_EQ(test, ret, 0); @@ -825,8 +923,8 @@ static void drm_test_vc5_pv_muxing_bugs_subsequent_crtc_enable(struct kunit *tes state = drm_kunit_helper_atomic_state_alloc(test, drm, ctx); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); - ret = vc4_mock_atomic_add_output(test, state, VC4_ENCODER_TYPE_HDMI1); - KUNIT_ASSERT_EQ(test, ret, 0); + output = vc4_mock_atomic_add_output(test, state, VC4_ENCODER_TYPE_HDMI1); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, output); ret = drm_atomic_check_only(state); KUNIT_ASSERT_EQ(test, ret, 0); @@ -856,6 +954,7 @@ static void drm_test_vc5_pv_muxing_bugs_stable_fifo(struct kunit *test) { struct drm_modeset_acquire_ctx *ctx; struct drm_atomic_state *state; + struct vc4_dummy_output *output; struct vc4_crtc_state *new_vc4_crtc_state; struct vc4_hvs_state *new_hvs_state; unsigned int old_hdmi0_channel; @@ -874,11 +973,11 @@ static void drm_test_vc5_pv_muxing_bugs_stable_fifo(struct kunit *test) state = drm_kunit_helper_atomic_state_alloc(test, drm, ctx); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); - ret = vc4_mock_atomic_add_output(test, state, VC4_ENCODER_TYPE_HDMI0); - KUNIT_ASSERT_EQ(test, ret, 0); + output = vc4_mock_atomic_add_output(test, state, VC4_ENCODER_TYPE_HDMI0); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, output); - ret = vc4_mock_atomic_add_output(test, state, VC4_ENCODER_TYPE_HDMI1); - KUNIT_ASSERT_EQ(test, ret, 0); + output = vc4_mock_atomic_add_output(test, state, VC4_ENCODER_TYPE_HDMI1); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, output); ret = drm_atomic_check_only(state); KUNIT_ASSERT_EQ(test, ret, 0); @@ -951,6 +1050,7 @@ drm_test_vc5_pv_muxing_bugs_subsequent_crtc_enable_too_many_crtc_state(struct ku { struct drm_modeset_acquire_ctx *ctx; struct drm_atomic_state *state; + struct vc4_dummy_output *output; struct vc4_crtc_state *new_vc4_crtc_state; struct drm_device *drm; struct vc4_dev *vc4; @@ -966,8 +1066,8 @@ drm_test_vc5_pv_muxing_bugs_subsequent_crtc_enable_too_many_crtc_state(struct ku state = drm_kunit_helper_atomic_state_alloc(test, drm, ctx); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); - ret = vc4_mock_atomic_add_output(test, state, VC4_ENCODER_TYPE_HDMI0); - KUNIT_ASSERT_EQ(test, ret, 0); + output = vc4_mock_atomic_add_output(test, state, VC4_ENCODER_TYPE_HDMI0); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, output); ret = drm_atomic_check_only(state); KUNIT_ASSERT_EQ(test, ret, 0); @@ -978,8 +1078,8 @@ drm_test_vc5_pv_muxing_bugs_subsequent_crtc_enable_too_many_crtc_state(struct ku state = drm_kunit_helper_atomic_state_alloc(test, drm, ctx); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); - ret = vc4_mock_atomic_add_output(test, state, VC4_ENCODER_TYPE_HDMI1); - KUNIT_ASSERT_EQ(test, ret, 0); + output = vc4_mock_atomic_add_output(test, state, VC4_ENCODER_TYPE_HDMI1); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, output); ret = drm_atomic_check_only(state); KUNIT_ASSERT_EQ(test, ret, 0); @@ -1004,5 +1104,6 @@ static struct kunit_suite vc5_pv_muxing_bugs_test_suite = { kunit_test_suites( &vc4_pv_muxing_test_suite, &vc5_pv_muxing_test_suite, + &vc6_pv_muxing_test_suite, &vc5_pv_muxing_bugs_test_suite ); diff --git a/drivers/gpu/drm/vc4/vc4_bo.c b/drivers/gpu/drm/vc4/vc4_bo.c index 2a85d08b19852a..fb450b6a4d444c 100644 --- a/drivers/gpu/drm/vc4/vc4_bo.c +++ b/drivers/gpu/drm/vc4/vc4_bo.c @@ -251,7 +251,7 @@ void vc4_bo_add_to_purgeable_pool(struct vc4_bo *bo) { struct vc4_dev *vc4 = to_vc4_dev(bo->base.base.dev); - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return; mutex_lock(&vc4->purgeable.lock); @@ -265,7 +265,7 @@ static void vc4_bo_remove_from_purgeable_pool_locked(struct vc4_bo *bo) { struct vc4_dev *vc4 = to_vc4_dev(bo->base.base.dev); - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return; /* list_del_init() is used here because the caller might release @@ -396,7 +396,7 @@ struct drm_gem_object *vc4_create_object(struct drm_device *dev, size_t size) struct vc4_dev *vc4 = to_vc4_dev(dev); struct vc4_bo *bo; - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return ERR_PTR(-ENODEV); bo = kzalloc(sizeof(*bo), GFP_KERNEL); @@ -427,7 +427,7 @@ struct vc4_bo *vc4_bo_create(struct drm_device *dev, size_t unaligned_size, struct drm_gem_dma_object *dma_obj; struct vc4_bo *bo; - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return ERR_PTR(-ENODEV); if (size == 0) @@ -496,7 +496,7 @@ int vc4_bo_dumb_create(struct drm_file *file_priv, struct vc4_bo *bo = NULL; int ret; - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return -ENODEV; ret = vc4_dumb_fixup_args(args); @@ -622,7 +622,7 @@ int vc4_bo_inc_usecnt(struct vc4_bo *bo) struct vc4_dev *vc4 = to_vc4_dev(bo->base.base.dev); int ret; - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return -ENODEV; /* Fast path: if the BO is already retained by someone, no need to @@ -661,7 +661,7 @@ void vc4_bo_dec_usecnt(struct vc4_bo *bo) { struct vc4_dev *vc4 = to_vc4_dev(bo->base.base.dev); - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return; /* Fast path: if the BO is still retained by someone, no need to test @@ -783,7 +783,7 @@ int vc4_create_bo_ioctl(struct drm_device *dev, void *data, struct vc4_bo *bo = NULL; int ret; - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return -ENODEV; ret = vc4_grab_bin_bo(vc4, vc4file); @@ -813,7 +813,7 @@ int vc4_mmap_bo_ioctl(struct drm_device *dev, void *data, struct drm_vc4_mmap_bo *args = data; struct drm_gem_object *gem_obj; - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return -ENODEV; gem_obj = drm_gem_object_lookup(file_priv, args->handle); @@ -839,7 +839,7 @@ vc4_create_shader_bo_ioctl(struct drm_device *dev, void *data, struct vc4_bo *bo = NULL; int ret; - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return -ENODEV; if (args->size == 0) @@ -918,7 +918,7 @@ int vc4_set_tiling_ioctl(struct drm_device *dev, void *data, struct vc4_bo *bo; bool t_format; - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return -ENODEV; if (args->flags != 0) @@ -964,7 +964,7 @@ int vc4_get_tiling_ioctl(struct drm_device *dev, void *data, struct drm_gem_object *gem_obj; struct vc4_bo *bo; - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return -ENODEV; if (args->flags != 0 || args->modifier != 0) @@ -1007,7 +1007,7 @@ int vc4_bo_cache_init(struct drm_device *dev) int ret; int i; - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return -ENODEV; /* Create the initial set of BO labels that the kernel will @@ -1071,7 +1071,7 @@ int vc4_label_bo_ioctl(struct drm_device *dev, void *data, struct drm_gem_object *gem_obj; int ret = 0, label; - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return -ENODEV; if (!args->len) diff --git a/drivers/gpu/drm/vc4/vc4_crtc.c b/drivers/gpu/drm/vc4/vc4_crtc.c index 26a7cf7f646515..307a533136692c 100644 --- a/drivers/gpu/drm/vc4/vc4_crtc.c +++ b/drivers/gpu/drm/vc4/vc4_crtc.c @@ -83,13 +83,22 @@ static unsigned int vc4_crtc_get_cob_allocation(struct vc4_dev *vc4, unsigned int channel) { struct vc4_hvs *hvs = vc4->hvs; - u32 dispbase = HVS_READ(SCALER_DISPBASEX(channel)); + u32 dispbase, top, base; + /* Top/base are supposed to be 4-pixel aligned, but the * Raspberry Pi firmware fills the low bits (which are * presumably ignored). */ - u32 top = VC4_GET_FIELD(dispbase, SCALER_DISPBASEX_TOP) & ~3; - u32 base = VC4_GET_FIELD(dispbase, SCALER_DISPBASEX_BASE) & ~3; + + if (vc4->gen >= VC4_GEN_6_C) { + dispbase = HVS_READ(SCALER6_DISPX_COB(channel)); + top = VC4_GET_FIELD(dispbase, SCALER6_DISPX_COB_TOP) & ~3; + base = VC4_GET_FIELD(dispbase, SCALER6_DISPX_COB_BASE) & ~3; + } else { + dispbase = HVS_READ(SCALER_DISPBASEX(channel)); + top = VC4_GET_FIELD(dispbase, SCALER_DISPBASEX_TOP) & ~3; + base = VC4_GET_FIELD(dispbase, SCALER_DISPBASEX_BASE) & ~3; + } return top - base + 4; } @@ -105,6 +114,7 @@ static bool vc4_crtc_get_scanout_position(struct drm_crtc *crtc, struct vc4_hvs *hvs = vc4->hvs; struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc); struct vc4_crtc_state *vc4_crtc_state = to_vc4_crtc_state(crtc->state); + unsigned int channel = vc4_crtc_state->assigned_channel; unsigned int cob_size; u32 val; int fifo_lines; @@ -121,7 +131,10 @@ static bool vc4_crtc_get_scanout_position(struct drm_crtc *crtc, * Read vertical scanline which is currently composed for our * pixelvalve by the HVS, and also the scaler status. */ - val = HVS_READ(SCALER_DISPSTATX(vc4_crtc_state->assigned_channel)); + if (vc4->gen >= VC4_GEN_6_C) + val = HVS_READ(SCALER6_DISPX_STATUS(channel)); + else + val = HVS_READ(SCALER_DISPSTATX(channel)); /* Get optional system timestamp after query. */ if (etime) @@ -130,18 +143,23 @@ static bool vc4_crtc_get_scanout_position(struct drm_crtc *crtc, /* preempt_enable_rt() should go right here in PREEMPT_RT patchset. */ /* Vertical position of hvs composed scanline. */ - *vpos = VC4_GET_FIELD(val, SCALER_DISPSTATX_LINE); + + if (vc4->gen >= VC4_GEN_6_C) + *vpos = VC4_GET_FIELD(val, SCALER6_DISPX_STATUS_YLINE); + else + *vpos = VC4_GET_FIELD(val, SCALER_DISPSTATX_LINE); + *hpos = 0; if (mode->flags & DRM_MODE_FLAG_INTERLACE) { *vpos /= 2; /* Use hpos to correct for field offset in interlaced mode. */ - if (vc4_hvs_get_fifo_frame_count(hvs, vc4_crtc_state->assigned_channel) % 2) + if (vc4_hvs_get_fifo_frame_count(hvs, channel) % 2) *hpos += mode->crtc_htotal / 2; } - cob_size = vc4_crtc_get_cob_allocation(vc4, vc4_crtc_state->assigned_channel); + cob_size = vc4_crtc_get_cob_allocation(vc4, channel); /* This is the offset we need for translating hvs -> pv scanout pos. */ fifo_lines = cob_size / mode->crtc_hdisplay; @@ -222,6 +240,11 @@ static u32 vc4_get_fifo_full_level(struct vc4_crtc *vc4_crtc, u32 format) const struct vc4_crtc_data *crtc_data = vc4_crtc_to_vc4_crtc_data(vc4_crtc); const struct vc4_pv_data *pv_data = vc4_crtc_to_vc4_pv_data(vc4_crtc); struct vc4_dev *vc4 = to_vc4_dev(vc4_crtc->base.dev); + + /* + * NOTE: Could we use register 0x68 (PV_HW_CFG1) to get the FIFO + * size? + */ u32 fifo_len_bytes = pv_data->fifo_depth; /* @@ -338,7 +361,9 @@ static void vc4_crtc_config_pv(struct drm_crtc *crtc, struct drm_encoder *encode bool is_dsi1 = vc4_encoder->type == VC4_ENCODER_TYPE_DSI1; bool is_vec = vc4_encoder->type == VC4_ENCODER_TYPE_VEC; u32 format = is_dsi1 ? PV_CONTROL_FORMAT_DSIV_24 : PV_CONTROL_FORMAT_24; - u8 ppc = pv_data->pixels_per_clock; + u8 ppc = (mode->flags & DRM_MODE_FLAG_INTERLACE) ? + pv_data->pixels_per_clock_int : + pv_data->pixels_per_clock; u16 vert_bp = mode->crtc_vtotal - mode->crtc_vsync_end; u16 vert_sync = mode->crtc_vsync_end - mode->crtc_vsync_start; @@ -403,6 +428,8 @@ static void vc4_crtc_config_pv(struct drm_crtc *crtc, struct drm_encoder *encode */ CRTC_WRITE(PV_V_CONTROL, PV_VCONTROL_CONTINUOUS | + (vc4->gen >= VC4_GEN_6_C && ppc == 1 ? + PV_VCONTROL_ODD_TIMING : 0) | (is_dsi ? PV_VCONTROL_DSI : 0) | PV_VCONTROL_INTERLACE | (odd_field_first @@ -414,6 +441,8 @@ static void vc4_crtc_config_pv(struct drm_crtc *crtc, struct drm_encoder *encode } else { CRTC_WRITE(PV_V_CONTROL, PV_VCONTROL_CONTINUOUS | + (vc4->gen >= VC4_GEN_6_C && ppc == 1 ? + PV_VCONTROL_ODD_TIMING : 0) | (is_dsi ? PV_VCONTROL_DSI : 0)); CRTC_WRITE(PV_VSYNCD_EVEN, 0); } @@ -428,11 +457,17 @@ static void vc4_crtc_config_pv(struct drm_crtc *crtc, struct drm_encoder *encode if (is_dsi) CRTC_WRITE(PV_HACT_ACT, mode->hdisplay * pixel_rep); - if (vc4->gen == VC4_GEN_5) + if (vc4->gen >= VC4_GEN_5) CRTC_WRITE(PV_MUX_CFG, VC4_SET_FIELD(PV_MUX_CFG_RGB_PIXEL_MUX_MODE_NO_SWAP, PV_MUX_CFG_RGB_PIXEL_MUX_MODE)); + if (vc4->gen >= VC4_GEN_6_C) + CRTC_WRITE(PV_PIPE_INIT_CTRL, + VC4_SET_FIELD(1, PV_PIPE_INIT_CTRL_PV_INIT_WIDTH) | + VC4_SET_FIELD(1, PV_PIPE_INIT_CTRL_PV_INIT_IDLE) | + PV_PIPE_INIT_CTRL_PV_INIT_EN); + CRTC_WRITE(PV_CONTROL, PV_CONTROL_FIFO_CLR | vc4_crtc_get_fifo_full_level_bits(vc4_crtc, format) | VC4_SET_FIELD(format, PV_CONTROL_FORMAT) | @@ -458,8 +493,10 @@ static void require_hvs_enabled(struct drm_device *dev) struct vc4_dev *vc4 = to_vc4_dev(dev); struct vc4_hvs *hvs = vc4->hvs; - WARN_ON_ONCE((HVS_READ(SCALER_DISPCTRL) & SCALER_DISPCTRL_ENABLE) != - SCALER_DISPCTRL_ENABLE); + if (vc4->gen >= VC4_GEN_6_C) + WARN_ON_ONCE(!(HVS_READ(SCALER6_CONTROL) & SCALER6_CONTROL_HVS_EN)); + else + WARN_ON_ONCE(!(HVS_READ(SCALER_DISPCTRL) & SCALER_DISPCTRL_ENABLE)); } static int vc4_crtc_disable(struct drm_crtc *crtc, @@ -529,7 +566,11 @@ int vc4_crtc_disable_at_boot(struct drm_crtc *crtc) if (!(of_device_is_compatible(vc4_crtc->pdev->dev.of_node, "brcm,bcm2711-pixelvalve2") || of_device_is_compatible(vc4_crtc->pdev->dev.of_node, - "brcm,bcm2711-pixelvalve4"))) + "brcm,bcm2711-pixelvalve4") || + of_device_is_compatible(vc4_crtc->pdev->dev.of_node, + "brcm,bcm2712-pixelvalve0") || + of_device_is_compatible(vc4_crtc->pdev->dev.of_node, + "brcm,bcm2712-pixelvalve1"))) return 0; if (!(CRTC_READ(PV_CONTROL) & PV_CONTROL_EN)) @@ -603,11 +644,14 @@ static void vc4_crtc_atomic_disable(struct drm_crtc *crtc, vc4_crtc_disable(crtc, encoder, state, old_vc4_state->assigned_channel); + vc4_hvs_atomic_disable(crtc, state); + /* * Make sure we issue a vblank event after disabling the CRTC if * someone was waiting it. */ vc4_crtc_send_vblank(crtc); + msleep(20); } static void vc4_crtc_atomic_enable(struct drm_crtc *crtc, @@ -735,10 +779,17 @@ int vc4_crtc_atomic_check(struct drm_crtc *crtc, if (conn_state->crtc != crtc) continue; - vc4_state->margins.left = conn_state->tv.margins.left; - vc4_state->margins.right = conn_state->tv.margins.right; - vc4_state->margins.top = conn_state->tv.margins.top; - vc4_state->margins.bottom = conn_state->tv.margins.bottom; + if (memcmp(&vc4_state->margins, &conn_state->tv.margins, + sizeof(vc4_state->margins))) { + memcpy(&vc4_state->margins, &conn_state->tv.margins, + sizeof(vc4_state->margins)); + + /* + * Need to force the dlist entries for all planes to be + * updated so that the dest rectangles are changed. + */ + crtc_state->zpos_changed = true; + } break; } @@ -765,12 +816,15 @@ static void vc4_disable_vblank(struct drm_crtc *crtc) { struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc); struct drm_device *dev = crtc->dev; + struct drm_encoder *encoder = vc4_get_crtc_encoder(crtc, crtc->state); + struct vc4_encoder *vc4_encoder = to_vc4_encoder(encoder); int idx; if (!drm_dev_enter(dev, &idx)) return; - CRTC_WRITE(PV_INTEN, 0); + if (!vc4_encoder || vc4_encoder->type != VC4_ENCODER_TYPE_DSI0) + CRTC_WRITE(PV_INTEN, 0); drm_dev_exit(idx); } @@ -781,14 +835,21 @@ static void vc4_crtc_handle_page_flip(struct vc4_crtc *vc4_crtc) struct drm_device *dev = crtc->dev; struct vc4_dev *vc4 = to_vc4_dev(dev); struct vc4_hvs *hvs = vc4->hvs; + unsigned int current_dlist; u32 chan = vc4_crtc->current_hvs_channel; unsigned long flags; spin_lock_irqsave(&dev->event_lock, flags); spin_lock(&vc4_crtc->irq_lock); + + if (vc4->gen >= VC4_GEN_6_C) + current_dlist = VC4_GET_FIELD(HVS_READ(SCALER6_DISPX_DL(chan)), + SCALER6_DISPX_DL_LACT); + else + current_dlist = HVS_READ(SCALER_DISPLACTX(chan)); + if (vc4_crtc->event && - (vc4_crtc->current_dlist == HVS_READ(SCALER_DISPLACTX(chan)) || - vc4_crtc->feeds_txp)) { + (vc4_crtc->current_dlist == current_dlist || vc4_crtc->feeds_txp)) { drm_crtc_send_vblank_event(crtc, vc4_crtc->event); vc4_crtc->event = NULL; drm_crtc_vblank_put(crtc); @@ -799,7 +860,8 @@ static void vc4_crtc_handle_page_flip(struct vc4_crtc *vc4_crtc) * the CRTC and encoder already reconfigured, leading to * underruns. This can be seen when reconfiguring the CRTC. */ - vc4_hvs_unmask_underrun(hvs, chan); + if (0 && vc4->gen < VC4_GEN_6_C) + vc4_hvs_unmask_underrun(hvs, chan); } spin_unlock(&vc4_crtc->irq_lock); spin_unlock_irqrestore(&dev->event_lock, flags); @@ -807,7 +869,14 @@ static void vc4_crtc_handle_page_flip(struct vc4_crtc *vc4_crtc) void vc4_crtc_handle_vblank(struct vc4_crtc *crtc) { + struct drm_encoder *encoder = vc4_get_crtc_encoder(&crtc->base, crtc->base.state); + struct vc4_encoder *vc4_encoder = to_vc4_encoder(encoder); + crtc->t_vblank = ktime_get(); + + if (vc4_encoder && vc4_encoder->vblank) + vc4_encoder->vblank(encoder); + drm_crtc_handle_vblank(&crtc->base); vc4_crtc_handle_page_flip(crtc); } @@ -1000,7 +1069,7 @@ static int vc4_async_page_flip(struct drm_crtc *crtc, struct vc4_bo *bo = to_vc4_bo(&dma_bo->base); int ret; - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return -ENODEV; /* @@ -1043,7 +1112,7 @@ int vc4_page_flip(struct drm_crtc *crtc, struct drm_device *dev = crtc->dev; struct vc4_dev *vc4 = to_vc4_dev(dev); - if (vc4->gen == VC4_GEN_5) + if (vc4->gen > VC4_GEN_4) return vc5_async_page_flip(crtc, fb, event, flags); else return vc4_async_page_flip(crtc, fb, event, flags); @@ -1074,14 +1143,8 @@ void vc4_crtc_destroy_state(struct drm_crtc *crtc, struct vc4_dev *vc4 = to_vc4_dev(crtc->dev); struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(state); - if (drm_mm_node_allocated(&vc4_state->mm)) { - unsigned long flags; - - spin_lock_irqsave(&vc4->hvs->mm_lock, flags); - drm_mm_remove_node(&vc4_state->mm); - spin_unlock_irqrestore(&vc4->hvs->mm_lock, flags); - - } + vc4_hvs_mark_dlist_entry_stale(vc4->hvs, vc4_state->mm); + vc4_state->mm = NULL; drm_atomic_helper_crtc_destroy_state(crtc, state); } @@ -1149,6 +1212,7 @@ const struct vc4_pv_data bcm2835_pv0_data = { }, .fifo_depth = 64, .pixels_per_clock = 1, + .pixels_per_clock_int = 1, .encoder_types = { [PV_CONTROL_CLK_SELECT_DSI] = VC4_ENCODER_TYPE_DSI0, [PV_CONTROL_CLK_SELECT_DPI_SMI_HDMI] = VC4_ENCODER_TYPE_DPI, @@ -1159,11 +1223,12 @@ const struct vc4_pv_data bcm2835_pv1_data = { .base = { .name = "pixelvalve-1", .debugfs_name = "crtc1_regs", - .hvs_available_channels = BIT(2), + .hvs_available_channels = BIT(0) | BIT(1) | BIT(2), .hvs_output = 2, }, .fifo_depth = 64, .pixels_per_clock = 1, + .pixels_per_clock_int = 1, .encoder_types = { [PV_CONTROL_CLK_SELECT_DSI] = VC4_ENCODER_TYPE_DSI1, [PV_CONTROL_CLK_SELECT_DPI_SMI_HDMI] = VC4_ENCODER_TYPE_SMI, @@ -1179,6 +1244,7 @@ const struct vc4_pv_data bcm2835_pv2_data = { }, .fifo_depth = 64, .pixels_per_clock = 1, + .pixels_per_clock_int = 1, .encoder_types = { [PV_CONTROL_CLK_SELECT_DPI_SMI_HDMI] = VC4_ENCODER_TYPE_HDMI0, [PV_CONTROL_CLK_SELECT_VEC] = VC4_ENCODER_TYPE_VEC, @@ -1194,6 +1260,7 @@ const struct vc4_pv_data bcm2711_pv0_data = { }, .fifo_depth = 64, .pixels_per_clock = 1, + .pixels_per_clock_int = 1, .encoder_types = { [0] = VC4_ENCODER_TYPE_DSI0, [1] = VC4_ENCODER_TYPE_DPI, @@ -1209,6 +1276,7 @@ const struct vc4_pv_data bcm2711_pv1_data = { }, .fifo_depth = 64, .pixels_per_clock = 1, + .pixels_per_clock_int = 1, .encoder_types = { [0] = VC4_ENCODER_TYPE_DSI1, [1] = VC4_ENCODER_TYPE_SMI, @@ -1224,6 +1292,7 @@ const struct vc4_pv_data bcm2711_pv2_data = { }, .fifo_depth = 256, .pixels_per_clock = 2, + .pixels_per_clock_int = 2, .encoder_types = { [0] = VC4_ENCODER_TYPE_HDMI0, }, @@ -1238,6 +1307,7 @@ const struct vc4_pv_data bcm2711_pv3_data = { }, .fifo_depth = 64, .pixels_per_clock = 1, + .pixels_per_clock_int = 1, .encoder_types = { [PV_CONTROL_CLK_SELECT_VEC] = VC4_ENCODER_TYPE_VEC, }, @@ -1252,6 +1322,35 @@ const struct vc4_pv_data bcm2711_pv4_data = { }, .fifo_depth = 64, .pixels_per_clock = 2, + .pixels_per_clock_int = 2, + .encoder_types = { + [0] = VC4_ENCODER_TYPE_HDMI1, + }, +}; + +const struct vc4_pv_data bcm2712_pv0_data = { + .base = { + .debugfs_name = "crtc0_regs", + .hvs_available_channels = BIT(0), + .hvs_output = 0, + }, + .fifo_depth = 64, + .pixels_per_clock = 1, + .pixels_per_clock_int = 2, + .encoder_types = { + [0] = VC4_ENCODER_TYPE_HDMI0, + }, +}; + +const struct vc4_pv_data bcm2712_pv1_data = { + .base = { + .debugfs_name = "crtc1_regs", + .hvs_available_channels = BIT(1), + .hvs_output = 1, + }, + .fifo_depth = 64, + .pixels_per_clock = 1, + .pixels_per_clock_int = 2, .encoder_types = { [0] = VC4_ENCODER_TYPE_HDMI1, }, @@ -1266,6 +1365,8 @@ static const struct of_device_id vc4_crtc_dt_match[] = { { .compatible = "brcm,bcm2711-pixelvalve2", .data = &bcm2711_pv2_data }, { .compatible = "brcm,bcm2711-pixelvalve3", .data = &bcm2711_pv3_data }, { .compatible = "brcm,bcm2711-pixelvalve4", .data = &bcm2711_pv4_data }, + { .compatible = "brcm,bcm2712-pixelvalve0", .data = &bcm2712_pv0_data }, + { .compatible = "brcm,bcm2712-pixelvalve1", .data = &bcm2712_pv1_data }, {} }; diff --git a/drivers/gpu/drm/vc4/vc4_debugfs.c b/drivers/gpu/drm/vc4/vc4_debugfs.c index fac624a663ea07..8144dedf2248e1 100644 --- a/drivers/gpu/drm/vc4/vc4_debugfs.c +++ b/drivers/gpu/drm/vc4/vc4_debugfs.c @@ -24,7 +24,8 @@ vc4_debugfs_init(struct drm_minor *minor) struct vc4_dev *vc4 = to_vc4_dev(minor->dev); struct drm_device *drm = &vc4->base; - drm_WARN_ON(drm, vc4_hvs_debugfs_init(minor)); + if (vc4->hvs) + drm_WARN_ON(drm, vc4_hvs_debugfs_init(minor)); if (vc4->v3d) { drm_WARN_ON(drm, vc4_bo_debugfs_init(minor)); diff --git a/drivers/gpu/drm/vc4/vc4_dpi.c b/drivers/gpu/drm/vc4/vc4_dpi.c index a382dc4654bdd2..c486b8ef092f4f 100644 --- a/drivers/gpu/drm/vc4/vc4_dpi.c +++ b/drivers/gpu/drm/vc4/vc4_dpi.c @@ -95,6 +95,8 @@ struct vc4_dpi { struct clk *core_clock; struct debugfs_regset32 regset; + + int rgb_order_override; }; #define to_vc4_dpi(_encoder) \ @@ -205,6 +207,11 @@ static void vc4_dpi_encoder_enable(struct drm_encoder *encoder) } } + if (dpi->rgb_order_override >= 0) { + dpi_c &= ~DPI_ORDER_MASK; + dpi_c |= VC4_SET_FIELD(dpi->rgb_order_override, DPI_ORDER); + } + if (connector->display_info.bus_flags & DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE) dpi_c |= DPI_PIXEL_CLK_INVERT; @@ -313,6 +320,7 @@ static int vc4_dpi_bind(struct device *dev, struct device *master, void *data) { struct platform_device *pdev = to_platform_device(dev); struct drm_device *drm = dev_get_drvdata(master); + const char *rgb_order = NULL; struct vc4_dpi *dpi; int ret; @@ -361,6 +369,20 @@ static int vc4_dpi_bind(struct device *dev, struct device *master, void *data) if (ret) return ret; + dpi->rgb_order_override = -1; + if (!of_property_read_string(dev->of_node, "rgb_order", &rgb_order)) { + if (!strcmp(rgb_order, "rgb")) + dpi->rgb_order_override = DPI_ORDER_RGB; + else if (!strcmp(rgb_order, "bgr")) + dpi->rgb_order_override = DPI_ORDER_BGR; + else if (!strcmp(rgb_order, "grb")) + dpi->rgb_order_override = DPI_ORDER_GRB; + else if (!strcmp(rgb_order, "brg")) + dpi->rgb_order_override = DPI_ORDER_BRG; + else + DRM_ERROR("Invalid dpi order %s - ignored\n", rgb_order); + } + ret = drmm_encoder_init(drm, &dpi->encoder.base, &vc4_dpi_encoder_funcs, DRM_MODE_ENCODER_DPI, diff --git a/drivers/gpu/drm/vc4/vc4_drv.c b/drivers/gpu/drm/vc4/vc4_drv.c index 550324819f37fc..55879b5f36ae61 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.c +++ b/drivers/gpu/drm/vc4/vc4_drv.c @@ -29,6 +29,7 @@ #include <linux/of_device.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> +#include <linux/dma-direct.h> #include <drm/drm_aperture.h> #include <drm/drm_atomic_helper.h> @@ -98,7 +99,7 @@ static int vc4_get_param_ioctl(struct drm_device *dev, void *data, if (args->pad != 0) return -EINVAL; - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return -ENODEV; if (!vc4->v3d) @@ -147,7 +148,7 @@ static int vc4_open(struct drm_device *dev, struct drm_file *file) struct vc4_dev *vc4 = to_vc4_dev(dev); struct vc4_file *vc4file; - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return -ENODEV; vc4file = kzalloc(sizeof(*vc4file), GFP_KERNEL); @@ -165,7 +166,7 @@ static void vc4_close(struct drm_device *dev, struct drm_file *file) struct vc4_dev *vc4 = to_vc4_dev(dev); struct vc4_file *vc4file = file->driver_priv; - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return; if (vc4file->bin_bo_used) @@ -175,6 +176,19 @@ static void vc4_close(struct drm_device *dev, struct drm_file *file) kfree(vc4file); } +static struct drm_gem_object * +vc4_prime_import_sg_table(struct drm_device *dev, + struct dma_buf_attachment *attach, + struct sg_table *sgt) +{ + phys_addr_t phys = dma_to_phys(dev->dev, sg_dma_address(sgt->sgl)); + + if (swiotlb_find_pool(dev->dev, phys)) + return ERR_PTR(-EINVAL); + + return drm_gem_dma_prime_import_sg_table(dev, attach, sgt); +} + DEFINE_DRM_GEM_FOPS(vc4_drm_fops); static const struct drm_ioctl_desc vc4_drm_ioctls[] = { @@ -211,7 +225,8 @@ const struct drm_driver vc4_drm_driver = { .gem_create_object = vc4_create_object, - DRM_GEM_DMA_DRIVER_OPS_WITH_DUMB_CREATE(vc4_bo_dumb_create), + .dumb_create = vc4_bo_dumb_create, + .gem_prime_import_sg_table = vc4_prime_import_sg_table, .ioctls = vc4_drm_ioctls, .num_ioctls = ARRAY_SIZE(vc4_drm_ioctls), @@ -234,7 +249,8 @@ const struct drm_driver vc5_drm_driver = { .debugfs_init = vc4_debugfs_init, #endif - DRM_GEM_DMA_DRIVER_OPS_WITH_DUMB_CREATE(vc5_dumb_create), + .dumb_create = vc5_dumb_create, + .gem_prime_import_sg_table = vc4_prime_import_sg_table, .fops = &vc4_drm_fops, @@ -275,6 +291,7 @@ static void vc4_component_unbind_all(void *ptr) static const struct of_device_id vc4_dma_range_matches[] = { { .compatible = "brcm,bcm2711-hvs" }, + { .compatible = "brcm,bcm2712-hvs" }, { .compatible = "brcm,bcm2835-hvs" }, { .compatible = "brcm,bcm2835-v3d" }, { .compatible = "brcm,cygnus-v3d" }, @@ -282,6 +299,18 @@ static const struct of_device_id vc4_dma_range_matches[] = { {} }; +/* + * we need this helper function for determining presence of fkms + * before it's been bound + */ +static bool firmware_kms(void) +{ + return of_device_is_available(of_find_compatible_node(NULL, NULL, + "raspberrypi,rpi-firmware-kms")) || + of_device_is_available(of_find_compatible_node(NULL, NULL, + "raspberrypi,rpi-firmware-kms-2711")); +} + static int vc4_drm_bind(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); @@ -296,16 +325,18 @@ static int vc4_drm_bind(struct device *dev) dev->coherent_dma_mask = DMA_BIT_MASK(32); - if (of_device_is_compatible(dev->of_node, "brcm,bcm2711-vc5")) - gen = VC4_GEN_5; - else - gen = VC4_GEN_4; + gen = (enum vc4_gen)of_device_get_match_data(dev); - if (gen == VC4_GEN_5) + if (gen > VC4_GEN_4) driver = &vc5_drm_driver; else driver = &vc4_drm_driver; + if (gen >= VC4_GEN_6_C) + dma_set_mask_and_coherent(dev, DMA_BIT_MASK(36)); + else + dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); + node = of_find_matching_node_and_match(NULL, vc4_dma_range_matches, NULL); if (node) { @@ -360,7 +391,7 @@ static int vc4_drm_bind(struct device *dev) if (ret) goto err; - if (firmware) { + if (firmware && !firmware_kms()) { ret = rpi_firmware_property(firmware, RPI_FIRMWARE_NOTIFY_DISPLAY_DONE, NULL, 0); @@ -378,16 +409,20 @@ static int vc4_drm_bind(struct device *dev) if (ret) goto err; - ret = vc4_plane_create_additional_planes(drm); - if (ret) - goto err; + if (!vc4->firmware_kms) { + ret = vc4_plane_create_additional_planes(drm); + if (ret) + goto err; + } ret = vc4_kms_load(drm); if (ret < 0) goto err; - drm_for_each_crtc(crtc, drm) - vc4_crtc_disable_at_boot(crtc); + if (!vc4->firmware_kms) { + drm_for_each_crtc(crtc, drm) + vc4_crtc_disable_at_boot(crtc); + } ret = drm_dev_register(drm, 0); if (ret < 0) @@ -433,6 +468,7 @@ static struct platform_driver *const component_drivers[] = { &vc4_dsi_driver, &vc4_txp_driver, &vc4_crtc_driver, + &vc4_firmware_kms_driver, &vc4_v3d_driver, }; @@ -458,9 +494,11 @@ static void vc4_platform_drm_shutdown(struct platform_device *pdev) } static const struct of_device_id vc4_of_match[] = { - { .compatible = "brcm,bcm2711-vc5", }, - { .compatible = "brcm,bcm2835-vc4", }, - { .compatible = "brcm,cygnus-vc4", }, + { .compatible = "brcm,bcm2711-vc5", .data = (void *)VC4_GEN_5 }, + /* NB GEN_6_C will be corrected on D0 hw to GEN_6_D via vc4_hvs_bind */ + { .compatible = "brcm,bcm2712-vc6", .data = (void *)VC4_GEN_6_C }, + { .compatible = "brcm,bcm2835-vc4", .data = (void *)VC4_GEN_4 }, + { .compatible = "brcm,cygnus-vc4", .data = (void *)VC4_GEN_4 }, {}, }; MODULE_DEVICE_TABLE(of, vc4_of_match); diff --git a/drivers/gpu/drm/vc4/vc4_drv.h b/drivers/gpu/drm/vc4/vc4_drv.h index dd452e6a114304..2c20c4faaf8fe4 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.h +++ b/drivers/gpu/drm/vc4/vc4_drv.h @@ -15,6 +15,7 @@ #include <drm/drm_debugfs.h> #include <drm/drm_device.h> #include <drm/drm_encoder.h> +#include <drm/drm_fourcc.h> #include <drm/drm_gem_dma_helper.h> #include <drm/drm_managed.h> #include <drm/drm_mm.h> @@ -83,6 +84,8 @@ struct vc4_perfmon { enum vc4_gen { VC4_GEN_4, VC4_GEN_5, + VC4_GEN_6_C, + VC4_GEN_6_D, }; struct vc4_dev { @@ -93,8 +96,12 @@ struct vc4_dev { unsigned int irq; + bool firmware_kms; + struct rpi_firmware *firmware; + struct vc4_hvs *hvs; struct vc4_v3d *v3d; + struct vc4_fkms *fkms; struct vc4_hang_state *hang_state; @@ -315,6 +322,34 @@ struct vc4_v3d { struct debugfs_regset32 regset; }; +#define VC4_NUM_LBM_HANDLES 64 +struct vc4_lbm_refcounts { + refcount_t refcount; + + /* Allocation size */ + size_t size; + /* Our allocation in LBM. */ + struct drm_mm_node lbm; + + /* Pointer back to the HVS structure */ + struct vc4_hvs *hvs; +}; + +#define VC4_NUM_UPM_HANDLES 32 +struct vc4_upm_refcounts { + refcount_t refcount; + + /* Allocation size */ + size_t size; + /* Our allocation in UPM for prefetching. */ + struct drm_mm_node upm; + + /* Pointer back to the HVS structure */ + struct vc4_hvs *hvs; +}; + +#define HVS_NUM_CHANNELS 3 + struct vc4_hvs { struct vc4_dev *vc4; struct platform_device *pdev; @@ -323,6 +358,14 @@ struct vc4_hvs { unsigned int dlist_mem_size; struct clk *core_clk; + struct clk *disp_clk; + + struct { + unsigned int desc; + unsigned int enabled: 1; + } eof_irq[HVS_NUM_CHANNELS]; + + bool bg_fill[HVS_NUM_CHANNELS]; unsigned long max_core_rate; @@ -330,11 +373,24 @@ struct vc4_hvs { * list. Units are dwords. */ struct drm_mm dlist_mm; + /* Memory manager for the LBM memory used by HVS scaling. */ struct drm_mm lbm_mm; + struct ida lbm_handles; + struct vc4_lbm_refcounts lbm_refcounts[VC4_NUM_LBM_HANDLES + 1]; + + /* Memory manager for the UPM memory used for prefetching. */ + struct drm_mm upm_mm; + struct ida upm_handles; + struct vc4_upm_refcounts upm_refcounts[VC4_NUM_UPM_HANDLES + 1]; + spinlock_t mm_lock; + struct list_head stale_dlist_entries; + struct work_struct free_dlist_work; + struct drm_mm_node mitchell_netravali_filter; + struct drm_mm_node nearest_neighbour_filter; struct debugfs_regset32 regset; @@ -353,7 +409,7 @@ struct vc4_hvs { bool vc5_hdmi_enable_4096by2160; }; -#define HVS_NUM_CHANNELS 3 +#define HVS_UBM_WORD_SIZE 256 struct vc4_hvs_state { struct drm_private_state base; @@ -400,7 +456,7 @@ struct vc4_plane_state { */ u32 pos0_offset; u32 pos2_offset; - u32 ptr0_offset; + u32 ptr0_offset[DRM_FORMAT_MAX_PLANES]; u32 lbm_offset; /* Offset where the plane's dlist was last stored in the @@ -410,7 +466,7 @@ struct vc4_plane_state { /* Clipped coordinates of the plane on the display. */ int crtc_x, crtc_y, crtc_w, crtc_h; - /* Clipped area being scanned from in the FB. */ + /* Clipped area being scanned from in the FB in u16.16 format */ u32 src_x, src_y; u32 src_w[2], src_h[2]; @@ -420,13 +476,14 @@ struct vc4_plane_state { bool is_unity; bool is_yuv; - /* Offset to start scanning out from the start of the plane's - * BO. - */ - u32 offsets[3]; - /* Our allocation in LBM for temporary storage during scaling. */ - struct drm_mm_node lbm; + unsigned int lbm_handle; + + /* The Unified Pre-Fetcher Handle */ + unsigned int upm_handle[DRM_FORMAT_MAX_PLANES]; + + /* Number of lines to pre-fetch */ + unsigned int upm_buffer_lines; /* Set when the plane has per-pixel alpha content or does not cover * the entire screen. This is a hint to the CRTC that it might need @@ -462,7 +519,8 @@ enum vc4_encoder_type { VC4_ENCODER_TYPE_DSI1, VC4_ENCODER_TYPE_SMI, VC4_ENCODER_TYPE_DPI, - VC4_ENCODER_TYPE_TXP, + VC4_ENCODER_TYPE_TXP0, + VC4_ENCODER_TYPE_TXP1, }; struct vc4_encoder { @@ -476,6 +534,7 @@ struct vc4_encoder { void (*post_crtc_disable)(struct drm_encoder *encoder, struct drm_atomic_state *state); void (*post_crtc_powerdown)(struct drm_encoder *encoder, struct drm_atomic_state *state); + void (*vblank)(struct drm_encoder *encoder); }; #define to_vc4_encoder(_encoder) \ @@ -509,7 +568,18 @@ struct vc4_crtc_data { int hvs_output; }; -extern const struct vc4_crtc_data vc4_txp_crtc_data; +struct vc4_txp_data { + struct vc4_crtc_data base; + enum vc4_encoder_type encoder_type; + unsigned int high_addr_ptr_reg; + unsigned int has_byte_enable:1; + unsigned int size_minus_one:1; + unsigned int supports_40bit_addresses:1; +}; + +extern const struct vc4_txp_data bcm2712_mop_data; +extern const struct vc4_txp_data bcm2712_moplet_data; +extern const struct vc4_txp_data bcm2835_txp_data; struct vc4_pv_data { struct vc4_crtc_data base; @@ -519,6 +589,8 @@ struct vc4_pv_data { /* Number of pixels output per clock period */ u8 pixels_per_clock; + /* Number of pixels output per clock period when in an interlaced mode */ + u8 pixels_per_clock_int; enum vc4_encoder_type encoder_types[4]; }; @@ -531,6 +603,8 @@ extern const struct vc4_pv_data bcm2711_pv1_data; extern const struct vc4_pv_data bcm2711_pv2_data; extern const struct vc4_pv_data bcm2711_pv3_data; extern const struct vc4_pv_data bcm2711_pv4_data; +extern const struct vc4_pv_data bcm2712_pv0_data; +extern const struct vc4_pv_data bcm2712_pv1_data; struct vc4_crtc { struct drm_crtc base; @@ -597,19 +671,21 @@ vc4_crtc_to_vc4_pv_data(const struct vc4_crtc *crtc) struct drm_encoder *vc4_get_crtc_encoder(struct drm_crtc *crtc, struct drm_crtc_state *state); +struct vc4_hvs_dlist_allocation { + struct list_head node; + struct drm_mm_node mm_node; + unsigned int channel; + u8 target_frame_count; + bool dlist_programmed; +}; + struct vc4_crtc_state { struct drm_crtc_state base; - /* Dlist area for this CRTC configuration. */ - struct drm_mm_node mm; + struct vc4_hvs_dlist_allocation *mm; bool txp_armed; unsigned int assigned_channel; - struct { - unsigned int left; - unsigned int right; - unsigned int top; - unsigned int bottom; - } margins; + struct drm_connector_tv_margins margins; unsigned long hvs_load; @@ -646,6 +722,12 @@ struct vc4_crtc_state { writel(val, hvs->regs + (offset)); \ } while (0) +#define HVS_READ6(offset) \ + HVS_READ(hvs->vc4->gen == VC4_GEN_6_C ? SCALER6_ ## offset : SCALER6D_ ## offset) + +#define HVS_WRITE6(offset, val) \ + HVS_WRITE(hvs->vc4->gen == VC4_GEN_6_C ? SCALER6_ ## offset : SCALER6D_ ## offset, val) + #define VC4_REG32(reg) { .name = #reg, .offset = reg } struct vc4_exec_info { @@ -970,6 +1052,9 @@ extern struct platform_driver vc4_dsi_driver; /* vc4_fence.c */ extern const struct dma_fence_ops vc4_fence_ops; +/* vc4_firmware_kms.c */ +extern struct platform_driver vc4_firmware_kms_driver; + /* vc4_gem.c */ int vc4_gem_init(struct drm_device *dev); int vc4_submit_cl_ioctl(struct drm_device *dev, void *data, @@ -1008,10 +1093,14 @@ void vc4_irq_reset(struct drm_device *dev); /* vc4_hvs.c */ extern struct platform_driver vc4_hvs_driver; -struct vc4_hvs *__vc4_hvs_alloc(struct vc4_dev *vc4, struct platform_device *pdev); +struct vc4_hvs *__vc4_hvs_alloc(struct vc4_dev *vc4, + void __iomem *regs, + struct platform_device *pdev); void vc4_hvs_stop_channel(struct vc4_hvs *hvs, unsigned int output); int vc4_hvs_get_fifo_from_output(struct vc4_hvs *hvs, unsigned int output); u8 vc4_hvs_get_fifo_frame_count(struct vc4_hvs *hvs, unsigned int fifo); +void vc4_hvs_mark_dlist_entry_stale(struct vc4_hvs *hvs, + struct vc4_hvs_dlist_allocation *alloc); int vc4_hvs_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state); void vc4_hvs_atomic_begin(struct drm_crtc *crtc, struct drm_atomic_state *state); void vc4_hvs_atomic_enable(struct drm_crtc *crtc, struct drm_atomic_state *state); @@ -1029,6 +1118,12 @@ int vc4_kms_load(struct drm_device *dev); struct drm_plane *vc4_plane_init(struct drm_device *dev, enum drm_plane_type type, uint32_t possible_crtcs); +void vc4_plane_reset(struct drm_plane *plane); +void vc4_plane_destroy_state(struct drm_plane *plane, + struct drm_plane_state *state); +struct drm_plane_state *vc4_plane_duplicate_state(struct drm_plane *plane); +int vc4_plane_atomic_check(struct drm_plane *plane, + struct drm_atomic_state *state); int vc4_plane_create_additional_planes(struct drm_device *dev); u32 vc4_plane_write_dlist(struct drm_plane *plane, u32 __iomem *dlist); u32 vc4_plane_dlist_size(const struct drm_plane_state *state); diff --git a/drivers/gpu/drm/vc4/vc4_dsi.c b/drivers/gpu/drm/vc4/vc4_dsi.c index f5ccc1bf7a6370..6c239ab369656a 100644 --- a/drivers/gpu/drm/vc4/vc4_dsi.c +++ b/drivers/gpu/drm/vc4/vc4_dsi.c @@ -44,7 +44,6 @@ #define DSI_CMD_FIFO_DEPTH 16 #define DSI_PIX_FIFO_DEPTH 256 -#define DSI_PIX_FIFO_WIDTH 4 #define DSI0_CTRL 0x00 @@ -170,11 +169,15 @@ #define DSI1_DISP1_CTRL 0x2c /* Format of the data written to TXPKT_PIX_FIFO. */ # define DSI_DISP1_PFORMAT_MASK VC4_MASK(2, 1) -# define DSI_DISP1_PFORMAT_SHIFT 1 -# define DSI_DISP1_PFORMAT_16BIT 0 -# define DSI_DISP1_PFORMAT_24BIT 1 -# define DSI_DISP1_PFORMAT_32BIT_LE 2 -# define DSI_DISP1_PFORMAT_32BIT_BE 3 +# define DSI1_DISP1_PFORMAT_SHIFT 1 +# define DSI0_DISP1_PFORMAT_16BIT 0 +# define DSI0_DISP1_PFORMAT_16BIT_ADJ 1 +# define DSI0_DISP1_PFORMAT_24BIT 2 +# define DSI0_DISP1_PFORMAT_32BIT_LE 3 /* NB Invalid, but required for macros to work */ +# define DSI1_DISP1_PFORMAT_16BIT 0 +# define DSI1_DISP1_PFORMAT_24BIT 1 +# define DSI1_DISP1_PFORMAT_32BIT_LE 2 +# define DSI1_DISP1_PFORMAT_32BIT_BE 3 /* DISP1 is always command mode. */ # define DSI_DISP1_ENABLE BIT(0) @@ -286,6 +289,8 @@ DSI1_INT_PR_TO) #define DSI0_STAT 0x2c +# define DSI0_STAT_ERR_CONT_LP1 BIT(6) +# define DSI0_STAT_ERR_CONT_LP0 BIT(5) #define DSI0_HSTX_TO_CNT 0x30 #define DSI0_LPRX_TO_CNT 0x34 #define DSI0_TA_TO_CNT 0x38 @@ -358,6 +363,16 @@ # define DSI_PHY_AFEC0_CTATADJ_MASK VC4_MASK(3, 0) # define DSI_PHY_AFEC0_CTATADJ_SHIFT 0 +# define DSI0_AFEC0_PD_ALL_LANES (DSI0_PHY_AFEC0_PD | \ + DSI0_PHY_AFEC0_PD_BG | \ + DSI0_PHY_AFEC0_PD_DLANE1) + +# define DSI1_AFEC0_PD_ALL_LANES (DSI1_PHY_AFEC0_PD | \ + DSI1_PHY_AFEC0_PD_BG | \ + DSI1_PHY_AFEC0_PD_DLANE3 | \ + DSI1_PHY_AFEC0_PD_DLANE2 | \ + DSI1_PHY_AFEC0_PD_DLANE1) + #define DSI0_PHY_AFEC1 0x68 # define DSI0_PHY_AFEC1_IDR_DLANE1_MASK VC4_MASK(10, 8) # define DSI0_PHY_AFEC1_IDR_DLANE1_SHIFT 8 @@ -398,7 +413,8 @@ # define DSI1_CTRL_DISABLE_DISP_ECCC BIT(1) # define DSI0_CTRL_CTRL0 BIT(0) # define DSI1_CTRL_EN BIT(0) -# define DSI0_CTRL_RESET_FIFOS (DSI_CTRL_CLR_LDF | \ +# define DSI0_CTRL_RESET_FIFOS (DSI0_CTRL_CTRL0 | \ + DSI_CTRL_CLR_LDF | \ DSI0_CTRL_CLR_PBCF | \ DSI0_CTRL_CLR_CPBCF | \ DSI0_CTRL_CLR_PDF | \ @@ -540,6 +556,7 @@ struct vc4_dsi_variant { unsigned int port; bool broken_axi_workaround; + unsigned int cmd_fifo_width; const char *debugfs_name; const struct debugfs_reg32 *regs; @@ -816,6 +833,15 @@ static void vc4_dsi_bridge_post_disable(struct drm_bridge *bridge, struct vc4_dsi *dsi = bridge_to_vc4_dsi(bridge); struct device *dev = &dsi->pdev->dev; + /* Reset the DSI and all its fifos. */ + DSI_PORT_WRITE(CTRL, DSI_CTRL_SOFT_RESET_CFG | + DSI_PORT_BIT(CTRL_RESET_FIFOS)); + + /* Power down the analogue front end. */ + DSI_PORT_WRITE(PHY_AFEC0, DSI_PORT_BIT(PHY_AFEC0_RESET) | + DSI_PORT_BIT(PHY_AFEC0_PD) | + DSI_PORT_BIT(AFEC0_PD_ALL_LANES)); + clk_disable_unprepare(dsi->pll_phy_clock); clk_disable_unprepare(dsi->escape_clock); clk_disable_unprepare(dsi->pixel_clock); @@ -846,6 +872,7 @@ static bool vc4_dsi_bridge_mode_fixup(struct drm_bridge *bridge, unsigned long pixel_clock_hz = mode->clock * 1000; unsigned long pll_clock = pixel_clock_hz * dsi->divider; int divider; + u16 htotal; /* Find what divider gets us a faster clock than the requested * pixel clock. @@ -862,12 +889,27 @@ static bool vc4_dsi_bridge_mode_fixup(struct drm_bridge *bridge, pixel_clock_hz = pll_clock / dsi->divider; adjusted_mode->clock = pixel_clock_hz / 1000; + htotal = mode->htotal; + + if (dsi->variant->port == 0 && mode->clock == 30000 && + mode->hdisplay == 800 && mode->htotal == (800 + 59 + 2 + 45) && + mode->vdisplay == 480 && mode->vtotal == (480 + 7 + 2 + 22)) { + /* + * Raspberry Pi 7" panel via TC358762 seems to have an issue on + * DSI0 that it doesn't actually follow the vertical timing that + * is otherwise identical to that produced on DSI1. + * Fixup the mode. + */ + htotal = 800 + 59 + 2 + 47; + adjusted_mode->vtotal = 480 + 7 + 2 + 45; + adjusted_mode->crtc_vtotal = 480 + 7 + 2 + 45; + } /* Given the new pixel clock, adjust HFP to keep vrefresh the same. */ - adjusted_mode->htotal = adjusted_mode->clock * mode->htotal / + adjusted_mode->htotal = adjusted_mode->clock * htotal / mode->clock; - adjusted_mode->hsync_end += adjusted_mode->htotal - mode->htotal; - adjusted_mode->hsync_start += adjusted_mode->htotal - mode->htotal; + adjusted_mode->hsync_end += adjusted_mode->htotal - htotal; + adjusted_mode->hsync_start += adjusted_mode->htotal - htotal; return true; } @@ -927,12 +969,32 @@ static void vc4_dsi_bridge_pre_enable(struct drm_bridge *bridge, "Failed to set phy clock to %ld: %d\n", phy_clock, ret); } - /* Reset the DSI and all its fifos. */ + ret = clk_prepare_enable(dsi->escape_clock); + if (ret) { + drm_err(bridge->dev, "Failed to turn on DSI escape clock: %d\n", + ret); + return; + } + + ret = clk_prepare_enable(dsi->pll_phy_clock); + if (ret) { + drm_err(bridge->dev, "Failed to turn on DSI PLL: %d\n", ret); + return; + } + + hs_clock = clk_get_rate(dsi->pll_phy_clock); + + /* + * Reset the DSI and all its fifos. The block must be enabled for the + * FIFO resets to trigger. + */ DSI_PORT_WRITE(CTRL, DSI_CTRL_SOFT_RESET_CFG | DSI_PORT_BIT(CTRL_RESET_FIFOS)); DSI_PORT_WRITE(CTRL, + ((dsi->variant->port == 0) ? + DSI0_CTRL_CTRL0 : DSI1_CTRL_EN) | DSI_CTRL_HSDT_EOT_DISABLE | DSI_CTRL_RX_LPDT_EOT_DISABLE); @@ -985,21 +1047,6 @@ static void vc4_dsi_bridge_pre_enable(struct drm_bridge *bridge, mdelay(1); } - ret = clk_prepare_enable(dsi->escape_clock); - if (ret) { - drm_err(bridge->dev, "Failed to turn on DSI escape clock: %d\n", - ret); - return; - } - - ret = clk_prepare_enable(dsi->pll_phy_clock); - if (ret) { - drm_err(bridge->dev, "Failed to turn on DSI PLL: %d\n", ret); - return; - } - - hs_clock = clk_get_rate(dsi->pll_phy_clock); - /* Yes, we set the DSI0P/DSI1P pixel clock to the byte rate, * not the pixel clock rate. DSIxP take from the APHY's byte, * DDR2, or DDR4 clock (we use byte) and feed into the PV at @@ -1110,16 +1157,16 @@ static void vc4_dsi_bridge_pre_enable(struct drm_bridge *bridge, /* Set up DISP1 for transferring long command payloads through * the pixfifo. */ - DSI_PORT_WRITE(DISP1_CTRL, - VC4_SET_FIELD(DSI_DISP1_PFORMAT_32BIT_LE, - DSI_DISP1_PFORMAT) | - DSI_DISP1_ENABLE); - - /* Ungate the block. */ - if (dsi->variant->port == 0) - DSI_PORT_WRITE(CTRL, DSI_PORT_READ(CTRL) | DSI0_CTRL_CTRL0); + if (dsi->variant->cmd_fifo_width == 4) + DSI_PORT_WRITE(DISP1_CTRL, + VC4_SET_FIELD(DSI_PORT_BIT(DISP1_PFORMAT_32BIT_LE), + DSI_DISP1_PFORMAT) | + DSI_DISP1_ENABLE); else - DSI_PORT_WRITE(CTRL, DSI_PORT_READ(CTRL) | DSI1_CTRL_EN); + DSI_PORT_WRITE(DISP1_CTRL, + VC4_SET_FIELD(DSI_PORT_BIT(DISP1_PFORMAT_24BIT), + DSI_DISP1_PFORMAT) | + DSI_DISP1_ENABLE); /* Bring AFE out of reset. */ DSI_PORT_WRITE(PHY_AFEC0, @@ -1170,10 +1217,9 @@ static int vc4_dsi_bridge_attach(struct drm_bridge *bridge, &dsi->bridge, flags); } -static ssize_t vc4_dsi_host_transfer(struct mipi_dsi_host *host, - const struct mipi_dsi_msg *msg) +static ssize_t vc4_dsi_transfer(struct vc4_dsi *dsi, + const struct mipi_dsi_msg *msg, bool log_error) { - struct vc4_dsi *dsi = host_to_dsi(host); struct drm_device *drm = dsi->bridge.dev; struct mipi_dsi_packet packet; u32 pkth = 0, pktc = 0; @@ -1202,9 +1248,9 @@ static ssize_t vc4_dsi_host_transfer(struct mipi_dsi_host *host, pix_fifo_len = 0; } else { cmd_fifo_len = (packet.payload_length % - DSI_PIX_FIFO_WIDTH); + dsi->variant->cmd_fifo_width); pix_fifo_len = ((packet.payload_length - cmd_fifo_len) / - DSI_PIX_FIFO_WIDTH); + dsi->variant->cmd_fifo_width); } WARN_ON_ONCE(pix_fifo_len >= DSI_PIX_FIFO_DEPTH); @@ -1222,14 +1268,25 @@ static ssize_t vc4_dsi_host_transfer(struct mipi_dsi_host *host, for (i = 0; i < cmd_fifo_len; i++) DSI_PORT_WRITE(TXPKT_CMD_FIFO, packet.payload[i]); - for (i = 0; i < pix_fifo_len; i++) { - const u8 *pix = packet.payload + cmd_fifo_len + i * 4; - - DSI_PORT_WRITE(TXPKT_PIX_FIFO, - pix[0] | - pix[1] << 8 | - pix[2] << 16 | - pix[3] << 24); + if (dsi->variant->cmd_fifo_width == 4) { + for (i = 0; i < pix_fifo_len; i++) { + const u8 *pix = packet.payload + cmd_fifo_len + i * 4; + + DSI_PORT_WRITE(TXPKT_PIX_FIFO, + pix[0] | + pix[1] << 8 | + pix[2] << 16 | + pix[3] << 24); + } + } else { + for (i = 0; i < pix_fifo_len; i++) { + const u8 *pix = packet.payload + cmd_fifo_len + i * 3; + + DSI_PORT_WRITE(TXPKT_PIX_FIFO, + pix[2] | + pix[1] << 8 | + pix[0] << 16); + } } if (msg->flags & MIPI_DSI_MSG_USE_LPM) @@ -1283,10 +1340,12 @@ static ssize_t vc4_dsi_host_transfer(struct mipi_dsi_host *host, DSI_PORT_WRITE(TXPKT1C, pktc); if (!wait_for_completion_timeout(&dsi->xfer_completion, - msecs_to_jiffies(1000))) { - dev_err(&dsi->pdev->dev, "transfer interrupt wait timeout"); - dev_err(&dsi->pdev->dev, "instat: 0x%08x\n", - DSI_PORT_READ(INT_STAT)); + msecs_to_jiffies(500))) { + if (log_error) { + dev_err(&dsi->pdev->dev, "transfer interrupt wait timeout"); + dev_err(&dsi->pdev->dev, "instat: 0x%08x, stat: 0x%08x\n", + DSI_PORT_READ(INT_STAT), DSI_PORT_READ(INT_STAT)); + } ret = -ETIMEDOUT; } else { ret = dsi->xfer_result; @@ -1329,7 +1388,8 @@ static ssize_t vc4_dsi_host_transfer(struct mipi_dsi_host *host, return ret; reset_fifo_and_return: - drm_err(drm, "DSI transfer failed, resetting: %d\n", ret); + if (log_error) + drm_err(drm, "DSI transfer failed, resetting: %d\n", ret); DSI_PORT_WRITE(TXPKT1C, DSI_PORT_READ(TXPKT1C) & ~DSI_TXPKT1C_CMD_EN); udelay(1); @@ -1342,6 +1402,40 @@ static ssize_t vc4_dsi_host_transfer(struct mipi_dsi_host *host, return ret; } +static ssize_t vc4_dsi_host_transfer(struct mipi_dsi_host *host, + const struct mipi_dsi_msg *msg) +{ + struct vc4_dsi *dsi = host_to_dsi(host); + u32 stat, disp0_ctrl; + int ret; + + ret = vc4_dsi_transfer(dsi, msg, false); + + if (ret == -ETIMEDOUT) { + stat = DSI_PORT_READ(STAT); + disp0_ctrl = DSI_PORT_READ(DISP0_CTRL); + + DSI_PORT_WRITE(STAT, DSI_PORT_BIT(STAT_ERR_CONT_LP1)); + if (!(disp0_ctrl & DSI_DISP0_ENABLE)) { + /* If video mode not enabled, then try recovering by + * enabling it briefly to clear FIFOs and the state. + */ + disp0_ctrl |= DSI_DISP0_ENABLE; + DSI_PORT_WRITE(DISP0_CTRL, disp0_ctrl); + msleep(30); + disp0_ctrl &= ~DSI_DISP0_ENABLE; + DSI_PORT_WRITE(DISP0_CTRL, disp0_ctrl); + msleep(30); + + ret = vc4_dsi_transfer(dsi, msg, true); + } else { + DRM_ERROR("DSI transfer failed whilst in HS mode stat: 0x%08x\n", + stat); + } + } + return ret; +} + static const struct component_ops vc4_dsi_ops; static int vc4_dsi_host_attach(struct mipi_dsi_host *host, struct mipi_dsi_device *device) @@ -1421,6 +1515,15 @@ static const struct drm_bridge_funcs vc4_dsi_bridge_funcs = { .mode_fixup = vc4_dsi_bridge_mode_fixup, }; +static void vc4_dsi_reset_fifo(struct drm_encoder *encoder) +{ + struct vc4_dsi *dsi = to_vc4_dsi(encoder); + u32 val; + + val = DSI_PORT_READ(CTRL); + DSI_PORT_WRITE(CTRL, val | DSI0_CTRL_CLR_PBCF); +} + static int vc4_dsi_late_register(struct drm_encoder *encoder) { struct drm_device *drm = encoder->dev; @@ -1437,6 +1540,7 @@ static const struct drm_encoder_funcs vc4_dsi_encoder_funcs = { static const struct vc4_dsi_variant bcm2711_dsi1_variant = { .port = 1, + .cmd_fifo_width = 4, .debugfs_name = "dsi1_regs", .regs = dsi1_regs, .nregs = ARRAY_SIZE(dsi1_regs), @@ -1444,6 +1548,7 @@ static const struct vc4_dsi_variant bcm2711_dsi1_variant = { static const struct vc4_dsi_variant bcm2835_dsi0_variant = { .port = 0, + .cmd_fifo_width = 3, .debugfs_name = "dsi0_regs", .regs = dsi0_regs, .nregs = ARRAY_SIZE(dsi0_regs), @@ -1451,6 +1556,7 @@ static const struct vc4_dsi_variant bcm2835_dsi0_variant = { static const struct vc4_dsi_variant bcm2835_dsi1_variant = { .port = 1, + .cmd_fifo_width = 4, .broken_axi_workaround = true, .debugfs_name = "dsi1_regs", .regs = dsi1_regs, @@ -1666,6 +1772,9 @@ static int vc4_dsi_bind(struct device *dev, struct device *master, void *data) dsi->encoder.type = dsi->variant->port ? VC4_ENCODER_TYPE_DSI1 : VC4_ENCODER_TYPE_DSI0; + if (dsi->encoder.type == VC4_ENCODER_TYPE_DSI0) + dsi->encoder.vblank = vc4_dsi_reset_fifo; + dsi->regs = vc4_ioremap_regs(pdev, 0); if (IS_ERR(dsi->regs)) return PTR_ERR(dsi->regs); diff --git a/drivers/gpu/drm/vc4/vc4_firmware_kms.c b/drivers/gpu/drm/vc4/vc4_firmware_kms.c new file mode 100644 index 00000000000000..fd1c528ec69c8b --- /dev/null +++ b/drivers/gpu/drm/vc4/vc4_firmware_kms.c @@ -0,0 +1,2079 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2016 Broadcom + * + * 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. + */ + +/** + * DOC: VC4 firmware KMS module. + * + * As a hack to get us from the current closed source driver world + * toward a totally open stack, implement KMS on top of the Raspberry + * Pi's firmware display stack. + */ + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_blend.h> +#include <drm/drm_drv.h> +#include <drm/drm_edid.h> +#include <drm/drm_fb_dma_helper.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_gem_atomic_helper.h> +#include <drm/drm_plane_helper.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_vblank.h> + +#include <linux/component.h> +#include <linux/clk.h> +#include <linux/debugfs.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#include <soc/bcm2835/raspberrypi-firmware.h> + +#include "vc4_drv.h" +#include "vc4_regs.h" +#include "vc_image_types.h" + +int fkms_max_refresh_rate = 85; +module_param(fkms_max_refresh_rate, int, 0644); +MODULE_PARM_DESC(fkms_max_refresh_rate, "Max supported refresh rate"); + +struct get_display_cfg { + u32 max_pixel_clock[2]; //Max pixel clock for each display +}; + +enum vc4_fkms_revision { + BCM2835_6_7, + BCM2711, + BCM2712, +}; + +struct vc4_fkms { + struct get_display_cfg cfg; + enum vc4_fkms_revision revision; +}; + +#define PLANES_PER_CRTC 8 + +struct set_plane { + u8 display; + u8 plane_id; + u8 vc_image_type; + s8 layer; + + u16 width; + u16 height; + + u16 pitch; + u16 vpitch; + + u32 src_x; /* 16p16 */ + u32 src_y; /* 16p16 */ + + u32 src_w; /* 16p16 */ + u32 src_h; /* 16p16 */ + + s16 dst_x; + s16 dst_y; + + u16 dst_w; + u16 dst_h; + + u8 alpha; + u8 num_planes; + u8 is_vu; + u8 color_encoding; + + u32 planes[4]; /* DMA address of each plane */ + + u32 transform; +}; + +/* Values for the transform field */ +#define TRANSFORM_NO_ROTATE 0 +#define TRANSFORM_ROTATE_180 BIT(1) +#define TRANSFORM_FLIP_HRIZ BIT(16) +#define TRANSFORM_FLIP_VERT BIT(17) + +struct mailbox_set_plane { + struct rpi_firmware_property_tag_header tag; + struct set_plane plane; +}; + +struct mailbox_blank_display { + struct rpi_firmware_property_tag_header tag1; + u32 display; + struct rpi_firmware_property_tag_header tag2; + u32 blank; +}; + +struct mailbox_display_pwr { + struct rpi_firmware_property_tag_header tag1; + u32 display; + u32 state; +}; + +struct mailbox_get_edid { + struct rpi_firmware_property_tag_header tag1; + u32 block; + u32 display_number; + u8 edid[128]; +}; + +struct set_timings { + u8 display; + u8 padding; + u16 video_id_code; + + u32 clock; /* in kHz */ + + u16 hdisplay; + u16 hsync_start; + + u16 hsync_end; + u16 htotal; + + u16 hskew; + u16 vdisplay; + + u16 vsync_start; + u16 vsync_end; + + u16 vtotal; + u16 vscan; + + u16 vrefresh; + u16 padding2; + + u32 flags; +#define TIMINGS_FLAGS_H_SYNC_POS BIT(0) +#define TIMINGS_FLAGS_H_SYNC_NEG 0 +#define TIMINGS_FLAGS_V_SYNC_POS BIT(1) +#define TIMINGS_FLAGS_V_SYNC_NEG 0 +#define TIMINGS_FLAGS_INTERLACE BIT(2) + +#define TIMINGS_FLAGS_ASPECT_MASK GENMASK(7, 4) +#define TIMINGS_FLAGS_ASPECT_NONE (0 << 4) +#define TIMINGS_FLAGS_ASPECT_4_3 (1 << 4) +#define TIMINGS_FLAGS_ASPECT_16_9 (2 << 4) +#define TIMINGS_FLAGS_ASPECT_64_27 (3 << 4) +#define TIMINGS_FLAGS_ASPECT_256_135 (4 << 4) + +/* Limited range RGB flag. Not set corresponds to full range. */ +#define TIMINGS_FLAGS_RGB_LIMITED BIT(8) +/* DVI monitor, therefore disable infoframes. Not set corresponds to HDMI. */ +#define TIMINGS_FLAGS_DVI BIT(9) +/* Double clock */ +#define TIMINGS_FLAGS_DBL_CLK BIT(10) +}; + +struct mailbox_set_mode { + struct rpi_firmware_property_tag_header tag1; + struct set_timings timings; +}; + +static const struct vc_image_format { + u32 drm; /* DRM_FORMAT_* */ + u32 vc_image; /* VC_IMAGE_* */ + u32 is_vu; +} vc_image_formats[] = { + { + .drm = DRM_FORMAT_XRGB8888, + .vc_image = VC_IMAGE_XRGB8888, + }, + { + .drm = DRM_FORMAT_ARGB8888, + .vc_image = VC_IMAGE_ARGB8888, + }, + { + .drm = DRM_FORMAT_XBGR8888, + .vc_image = VC_IMAGE_RGBX32, + }, + { + .drm = DRM_FORMAT_ABGR8888, + .vc_image = VC_IMAGE_RGBA32, + }, + { + .drm = DRM_FORMAT_RGBX8888, + .vc_image = VC_IMAGE_BGRX8888, + }, + { + .drm = DRM_FORMAT_BGRX8888, + .vc_image = VC_IMAGE_RGBX8888, + }, + { + .drm = DRM_FORMAT_RGB565, + .vc_image = VC_IMAGE_RGB565, + }, + { + .drm = DRM_FORMAT_RGB888, + .vc_image = VC_IMAGE_BGR888, + }, + { + .drm = DRM_FORMAT_BGR888, + .vc_image = VC_IMAGE_RGB888, + }, + { + .drm = DRM_FORMAT_YUV422, + .vc_image = VC_IMAGE_YUV422PLANAR, + }, + { + .drm = DRM_FORMAT_YUV420, + .vc_image = VC_IMAGE_YUV420, + }, + { + .drm = DRM_FORMAT_YVU420, + .vc_image = VC_IMAGE_YUV420, + .is_vu = 1, + }, + { + .drm = DRM_FORMAT_NV12, + .vc_image = VC_IMAGE_YUV420SP, + }, + { + .drm = DRM_FORMAT_NV21, + .vc_image = VC_IMAGE_YUV420SP, + .is_vu = 1, + }, + { + .drm = DRM_FORMAT_P030, + .vc_image = VC_IMAGE_YUV10COL, + }, +}; + +static const struct vc_image_format *vc4_get_vc_image_fmt(u32 drm_format) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(vc_image_formats); i++) { + if (vc_image_formats[i].drm == drm_format) + return &vc_image_formats[i]; + } + + return NULL; +} + +/* The firmware delivers a vblank interrupt to us through the SMI + * hardware, which has only this one register. + */ +#define SMICS 0x0 +#define SMIDSW0 0x14 +#define SMIDSW1 0x1C +#define SMICS_INTERRUPTS (BIT(9) | BIT(10) | BIT(11)) + +/* Flag to denote that the firmware is giving multiple display callbacks */ +#define SMI_NEW 0xabcd0000 + +struct vc4_fkms_crtc { + struct drm_crtc base; + struct drm_encoder *encoder; + struct drm_connector *connector; + void __iomem *regs; + + struct drm_pending_vblank_event *event; + bool vblank_enabled; + u32 display_number; + u32 display_type; +}; + +static inline struct vc4_fkms_crtc *to_vc4_fkms_crtc(struct drm_crtc *crtc) +{ + return container_of(crtc, struct vc4_fkms_crtc, base); +} + +struct vc4_fkms_encoder { + struct drm_encoder base; + bool hdmi_monitor; + bool rgb_range_selectable; + int display_num; +}; + +static inline struct vc4_fkms_encoder * +to_vc4_fkms_encoder(struct drm_encoder *encoder) +{ + return container_of(encoder, struct vc4_fkms_encoder, base); +} + +/* "Broadcast RGB" property. + * Allows overriding of HDMI full or limited range RGB + */ +#define VC4_BROADCAST_RGB_AUTO 0 +#define VC4_BROADCAST_RGB_FULL 1 +#define VC4_BROADCAST_RGB_LIMITED 2 + +/* VC4 FKMS connector KMS struct */ +struct vc4_fkms_connector { + struct drm_connector base; + + /* Since the connector is attached to just the one encoder, + * this is the reference to it so we can do the best_encoder() + * hook. + */ + struct drm_encoder *encoder; + struct vc4_dev *vc4_dev; + u32 display_number; + u32 display_type; + + struct drm_property *broadcast_rgb_property; +}; + +static inline struct vc4_fkms_connector * +to_vc4_fkms_connector(struct drm_connector *connector) +{ + return container_of(connector, struct vc4_fkms_connector, base); +} + +/* VC4 FKMS connector state */ +struct vc4_fkms_connector_state { + struct drm_connector_state base; + + int broadcast_rgb; +}; + +#define to_vc4_fkms_connector_state(x) \ + container_of(x, struct vc4_fkms_connector_state, base) + +static u32 vc4_get_display_type(u32 display_number) +{ + const u32 display_types[] = { + /* The firmware display (DispmanX) IDs map to specific types in + * a fixed manner. + */ + DRM_MODE_ENCODER_DSI, /* MAIN_LCD - DSI or DPI */ + DRM_MODE_ENCODER_DSI, /* AUX_LCD */ + DRM_MODE_ENCODER_TMDS, /* HDMI0 */ + DRM_MODE_ENCODER_TVDAC, /* VEC */ + DRM_MODE_ENCODER_NONE, /* FORCE_LCD */ + DRM_MODE_ENCODER_NONE, /* FORCE_TV */ + DRM_MODE_ENCODER_NONE, /* FORCE_OTHER */ + DRM_MODE_ENCODER_TMDS, /* HDMI1 */ + DRM_MODE_ENCODER_NONE, /* FORCE_TV2 */ + }; + return display_number > ARRAY_SIZE(display_types) - 1 ? + DRM_MODE_ENCODER_NONE : display_types[display_number]; +} + +/* Firmware's structure for making an FB mbox call. */ +struct fbinfo_s { + u32 xres, yres, xres_virtual, yres_virtual; + u32 pitch, bpp; + u32 xoffset, yoffset; + u32 base; + u32 screen_size; + u16 cmap[256]; +}; + +struct vc4_fkms_plane { + struct drm_plane base; + struct fbinfo_s *fbinfo; + dma_addr_t fbinfo_bus_addr; + u32 pitch; + struct mailbox_set_plane mb; +}; + +static inline struct vc4_fkms_plane *to_vc4_fkms_plane(struct drm_plane *plane) +{ + return (struct vc4_fkms_plane *)plane; +} + +static int vc4_plane_set_blank(struct drm_plane *plane, bool blank) +{ + struct vc4_dev *vc4 = to_vc4_dev(plane->dev); + struct vc4_fkms_plane *vc4_plane = to_vc4_fkms_plane(plane); + struct mailbox_set_plane blank_mb = { + .tag = { RPI_FIRMWARE_SET_PLANE, sizeof(struct set_plane), 0 }, + .plane = { + .display = vc4_plane->mb.plane.display, + .plane_id = vc4_plane->mb.plane.plane_id, + } + }; + static const char * const plane_types[] = { + "overlay", + "primary", + "cursor" + }; + int ret; + + DRM_DEBUG_ATOMIC("[PLANE:%d:%s] %s plane %s", + plane->base.id, plane->name, plane_types[plane->type], + blank ? "blank" : "unblank"); + + if (blank) + ret = rpi_firmware_property_list(vc4->firmware, &blank_mb, + sizeof(blank_mb)); + else + ret = rpi_firmware_property_list(vc4->firmware, &vc4_plane->mb, + sizeof(vc4_plane->mb)); + + WARN_ONCE(ret, "%s: firmware call failed. Please update your firmware", + __func__); + return ret; +} + +static void vc4_fkms_crtc_get_margins(struct drm_crtc_state *state, + unsigned int *left, unsigned int *right, + unsigned int *top, unsigned int *bottom) +{ + struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(state); + struct drm_connector_state *conn_state; + struct drm_connector *conn; + int i; + + *left = vc4_state->margins.left; + *right = vc4_state->margins.right; + *top = vc4_state->margins.top; + *bottom = vc4_state->margins.bottom; + + /* We have to interate over all new connector states because + * vc4_fkms_crtc_get_margins() might be called before + * vc4_fkms_crtc_atomic_check() which means margins info in + * vc4_crtc_state might be outdated. + */ + for_each_new_connector_in_state(state->state, conn, conn_state, i) { + if (conn_state->crtc != state->crtc) + continue; + + *left = conn_state->tv.margins.left; + *right = conn_state->tv.margins.right; + *top = conn_state->tv.margins.top; + *bottom = conn_state->tv.margins.bottom; + break; + } +} + +static int vc4_fkms_margins_adj(struct drm_plane_state *pstate, + struct set_plane *plane) +{ + unsigned int left, right, top, bottom; + int adjhdisplay, adjvdisplay; + struct drm_crtc_state *crtc_state; + + crtc_state = drm_atomic_get_new_crtc_state(pstate->state, + pstate->crtc); + + vc4_fkms_crtc_get_margins(crtc_state, &left, &right, &top, &bottom); + + if (!left && !right && !top && !bottom) + return 0; + + if (left + right >= crtc_state->mode.hdisplay || + top + bottom >= crtc_state->mode.vdisplay) + return -EINVAL; + + adjhdisplay = crtc_state->mode.hdisplay - (left + right); + plane->dst_x = DIV_ROUND_CLOSEST(plane->dst_x * adjhdisplay, + (int)crtc_state->mode.hdisplay); + plane->dst_x += left; + if (plane->dst_x > (int)(crtc_state->mode.hdisplay - right)) + plane->dst_x = crtc_state->mode.hdisplay - right; + + adjvdisplay = crtc_state->mode.vdisplay - (top + bottom); + plane->dst_y = DIV_ROUND_CLOSEST(plane->dst_y * adjvdisplay, + (int)crtc_state->mode.vdisplay); + plane->dst_y += top; + if (plane->dst_y > (int)(crtc_state->mode.vdisplay - bottom)) + plane->dst_y = crtc_state->mode.vdisplay - bottom; + + plane->dst_w = DIV_ROUND_CLOSEST(plane->dst_w * adjhdisplay, + crtc_state->mode.hdisplay); + plane->dst_h = DIV_ROUND_CLOSEST(plane->dst_h * adjvdisplay, + crtc_state->mode.vdisplay); + + if (!plane->dst_w || !plane->dst_h) + return -EINVAL; + + return 0; +} + +static void vc4_plane_atomic_update(struct drm_plane *plane, + struct drm_atomic_state *old_state) +{ + struct drm_plane_state *state = plane->state; + + /* + * Do NOT set now, as we haven't checked if the crtc is active or not. + * Set from vc4_plane_set_blank instead. + * + * If the CRTC is on (or going to be on) and we're enabled, + * then unblank. Otherwise, stay blank until CRTC enable. + */ + if (state->crtc->state->active) + vc4_plane_set_blank(plane, false); +} + +static void vc4_plane_atomic_disable(struct drm_plane *plane, + struct drm_atomic_state *old_state) +{ + struct drm_plane_state *state = plane->state; + struct vc4_fkms_plane *vc4_plane = to_vc4_fkms_plane(plane); + + DRM_DEBUG_ATOMIC("[PLANE:%d:%s] plane disable %dx%d@%d +%d,%d\n", + plane->base.id, plane->name, + state->crtc_w, + state->crtc_h, + vc4_plane->mb.plane.vc_image_type, + state->crtc_x, + state->crtc_y); + vc4_plane_set_blank(plane, true); +} + +static bool plane_enabled(struct drm_plane_state *state) +{ + return state->fb && state->crtc; +} + +static int vc4_plane_to_mb(struct drm_plane *plane, + struct mailbox_set_plane *mb, + struct drm_plane_state *state) +{ + struct drm_framebuffer *fb = state->fb; + struct drm_gem_dma_object *bo; + const struct drm_format_info *drm_fmt = fb->format; + const struct vc_image_format *vc_fmt = + vc4_get_vc_image_fmt(drm_fmt->format); + int num_planes = fb->format->num_planes; + unsigned int rotation; + + mb->plane.vc_image_type = vc_fmt->vc_image; + mb->plane.width = fb->width; + mb->plane.height = fb->height; + mb->plane.pitch = fb->pitches[0]; + mb->plane.src_w = state->src_w; + mb->plane.src_h = state->src_h; + mb->plane.src_x = state->src_x; + mb->plane.src_y = state->src_y; + mb->plane.dst_w = state->crtc_w; + mb->plane.dst_h = state->crtc_h; + mb->plane.dst_x = state->crtc_x; + mb->plane.dst_y = state->crtc_y; + mb->plane.alpha = state->alpha >> 8; + mb->plane.layer = state->normalized_zpos ? + state->normalized_zpos : -127; + mb->plane.num_planes = num_planes; + mb->plane.is_vu = vc_fmt->is_vu; + bo = drm_fb_dma_get_gem_obj(fb, 0); + mb->plane.planes[0] = bo->dma_addr + fb->offsets[0]; + + rotation = drm_rotation_simplify(state->rotation, + DRM_MODE_ROTATE_0 | + DRM_MODE_REFLECT_X | + DRM_MODE_REFLECT_Y); + + mb->plane.transform = TRANSFORM_NO_ROTATE; + if (rotation & DRM_MODE_REFLECT_X) + mb->plane.transform |= TRANSFORM_FLIP_HRIZ; + if (rotation & DRM_MODE_REFLECT_Y) + mb->plane.transform |= TRANSFORM_FLIP_VERT; + + vc4_fkms_margins_adj(state, &mb->plane); + + if (num_planes > 1) { + /* Assume this must be YUV */ + /* Makes assumptions on the stride for the chroma planes as we + * can't easily plumb in non-standard pitches. + */ + bo = drm_fb_dma_get_gem_obj(fb, 1); + mb->plane.planes[1] = bo->dma_addr + fb->offsets[1]; + if (num_planes > 2) { + bo = drm_fb_dma_get_gem_obj(fb, 2); + mb->plane.planes[2] = bo->dma_addr + fb->offsets[2]; + } else { + mb->plane.planes[2] = 0; + } + + /* Special case the YUV420 with U and V as line interleaved + * planes as we have special handling for that case. + */ + if (num_planes == 3 && + (fb->offsets[2] - fb->offsets[1]) == fb->pitches[1]) + mb->plane.vc_image_type = VC_IMAGE_YUV420_S; + + switch (state->color_encoding) { + default: + case DRM_COLOR_YCBCR_BT601: + if (state->color_range == DRM_COLOR_YCBCR_LIMITED_RANGE) + mb->plane.color_encoding = + VC_IMAGE_YUVINFO_CSC_ITUR_BT601; + else + mb->plane.color_encoding = + VC_IMAGE_YUVINFO_CSC_JPEG_JFIF; + break; + case DRM_COLOR_YCBCR_BT709: + /* Currently no support for a full range BT709 */ + mb->plane.color_encoding = + VC_IMAGE_YUVINFO_CSC_ITUR_BT709; + break; + case DRM_COLOR_YCBCR_BT2020: + /* Currently no support for a full range BT2020 */ + mb->plane.color_encoding = + VC_IMAGE_YUVINFO_CSC_REC_2020; + break; + } + } else { + mb->plane.planes[1] = 0; + mb->plane.planes[2] = 0; + } + mb->plane.planes[3] = 0; + + switch (fourcc_mod_broadcom_mod(fb->modifier)) { + case DRM_FORMAT_MOD_BROADCOM_VC4_T_TILED: + switch (mb->plane.vc_image_type) { + case VC_IMAGE_XRGB8888: + mb->plane.vc_image_type = VC_IMAGE_TF_RGBX32; + break; + case VC_IMAGE_ARGB8888: + mb->plane.vc_image_type = VC_IMAGE_TF_RGBA32; + break; + case VC_IMAGE_RGB565: + mb->plane.vc_image_type = VC_IMAGE_TF_RGB565; + break; + } + break; + case DRM_FORMAT_MOD_BROADCOM_SAND128: + switch (mb->plane.vc_image_type) { + case VC_IMAGE_YUV420SP: + mb->plane.vc_image_type = VC_IMAGE_YUV_UV; + break; + /* VC_IMAGE_YUV10COL could be included in here, but it is only + * valid as a SAND128 format, so the table at the top will have + * already set the correct format. + */ + } + /* Note that the column pitch is passed across in lines, not + * bytes. + */ + mb->plane.pitch = fourcc_mod_broadcom_param(fb->modifier); + break; + } + + DRM_DEBUG_ATOMIC("[PLANE:%d:%s] plane update %dx%d@%d +dst(%d,%d, %d,%d) +src(%d,%d, %d,%d) 0x%08x/%08x/%08x/%d, alpha %u zpos %u\n", + plane->base.id, plane->name, + mb->plane.width, + mb->plane.height, + mb->plane.vc_image_type, + state->crtc_x, + state->crtc_y, + state->crtc_w, + state->crtc_h, + mb->plane.src_x, + mb->plane.src_y, + mb->plane.src_w, + mb->plane.src_h, + mb->plane.planes[0], + mb->plane.planes[1], + mb->plane.planes[2], + fb->pitches[0], + state->alpha, + state->normalized_zpos); + + return 0; +} + +static int vc4_fkms_plane_atomic_check(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, + plane); + struct vc4_fkms_plane *vc4_plane = to_vc4_fkms_plane(plane); + + if (!plane_enabled(new_plane_state)) + return 0; + + return vc4_plane_to_mb(plane, &vc4_plane->mb, new_plane_state); +} + +static void vc4_plane_atomic_async_update(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_plane_state *new_plane_state = + drm_atomic_get_new_plane_state(state, plane); + + swap(plane->state->fb, new_plane_state->fb); + plane->state->crtc_x = new_plane_state->crtc_x; + plane->state->crtc_y = new_plane_state->crtc_y; + plane->state->crtc_w = new_plane_state->crtc_w; + plane->state->crtc_h = new_plane_state->crtc_h; + plane->state->src_x = new_plane_state->src_x; + plane->state->src_y = new_plane_state->src_y; + plane->state->src_w = new_plane_state->src_w; + plane->state->src_h = new_plane_state->src_h; + plane->state->alpha = new_plane_state->alpha; + plane->state->pixel_blend_mode = new_plane_state->pixel_blend_mode; + plane->state->rotation = new_plane_state->rotation; + plane->state->zpos = new_plane_state->zpos; + plane->state->normalized_zpos = new_plane_state->normalized_zpos; + plane->state->color_encoding = new_plane_state->color_encoding; + plane->state->color_range = new_plane_state->color_range; + plane->state->src = new_plane_state->src; + plane->state->dst = new_plane_state->dst; + plane->state->visible = new_plane_state->visible; + + vc4_plane_set_blank(plane, false); +} + +static int vc4_plane_atomic_async_check(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_plane_state *new_plane_state = + drm_atomic_get_new_plane_state(state, plane); + int ret = -EINVAL; + + if (plane->type == 2 && + plane->state->fb && + new_plane_state->crtc->state->active) + ret = 0; + + return ret; +} + +/* Called during init to allocate the plane's atomic state. */ +static void vc4_fkms_plane_reset(struct drm_plane *plane) +{ + struct vc4_plane_state *vc4_state; + + WARN_ON(plane->state); + + vc4_state = kzalloc(sizeof(*vc4_state), GFP_KERNEL); + if (!vc4_state) + return; + + __drm_atomic_helper_plane_reset(plane, &vc4_state->base); +} + +static void vc4_plane_destroy(struct drm_plane *plane) +{ + drm_plane_cleanup(plane); +} + +static bool vc4_fkms_format_mod_supported(struct drm_plane *plane, + uint32_t format, + uint64_t modifier) +{ + /* Support T_TILING for RGB formats only. */ + switch (format) { + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_RGB565: + switch (modifier) { + case DRM_FORMAT_MOD_BROADCOM_VC4_T_TILED: + case DRM_FORMAT_MOD_LINEAR: + return true; + default: + return false; + } + case DRM_FORMAT_NV12: + switch (fourcc_mod_broadcom_mod(modifier)) { + case DRM_FORMAT_MOD_LINEAR: + case DRM_FORMAT_MOD_BROADCOM_SAND128: + return true; + default: + return false; + } + case DRM_FORMAT_P030: + switch (fourcc_mod_broadcom_mod(modifier)) { + case DRM_FORMAT_MOD_BROADCOM_SAND128: + return true; + default: + return false; + } + case DRM_FORMAT_NV21: + case DRM_FORMAT_RGB888: + case DRM_FORMAT_BGR888: + case DRM_FORMAT_YUV422: + case DRM_FORMAT_YUV420: + case DRM_FORMAT_YVU420: + default: + return (modifier == DRM_FORMAT_MOD_LINEAR); + } +} + +static struct drm_plane_state *vc4_fkms_plane_duplicate_state(struct drm_plane *plane) +{ + struct vc4_plane_state *vc4_state; + + if (WARN_ON(!plane->state)) + return NULL; + + vc4_state = kzalloc(sizeof(*vc4_state), GFP_KERNEL); + if (!vc4_state) + return NULL; + + __drm_atomic_helper_plane_duplicate_state(plane, &vc4_state->base); + + return &vc4_state->base; +} + +static const struct drm_plane_funcs vc4_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .destroy = vc4_plane_destroy, + .set_property = NULL, + .reset = vc4_fkms_plane_reset, + .atomic_duplicate_state = vc4_fkms_plane_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, + .format_mod_supported = vc4_fkms_format_mod_supported, +}; + +static const struct drm_plane_helper_funcs vc4_plane_helper_funcs = { + .prepare_fb = drm_gem_plane_helper_prepare_fb, + .cleanup_fb = NULL, + .atomic_check = vc4_fkms_plane_atomic_check, + .atomic_update = vc4_plane_atomic_update, + .atomic_disable = vc4_plane_atomic_disable, + .atomic_async_check = vc4_plane_atomic_async_check, + .atomic_async_update = vc4_plane_atomic_async_update, +}; + +static struct drm_plane *vc4_fkms_plane_init(struct drm_device *dev, + enum drm_plane_type type, + u8 display_num, + u8 plane_id) +{ + struct drm_plane *plane = NULL; + struct vc4_fkms_plane *vc4_plane; + u32 formats[ARRAY_SIZE(vc_image_formats)]; + unsigned int default_zpos = 0; + u32 num_formats = 0; + int ret = 0; + static const uint64_t modifiers[] = { + DRM_FORMAT_MOD_LINEAR, + /* VC4_T_TILED should come after linear, because we + * would prefer to scan out linear (less bus traffic). + */ + DRM_FORMAT_MOD_BROADCOM_VC4_T_TILED, + DRM_FORMAT_MOD_BROADCOM_SAND128, + DRM_FORMAT_MOD_INVALID, + }; + int i; + + vc4_plane = devm_kzalloc(dev->dev, sizeof(*vc4_plane), + GFP_KERNEL); + if (!vc4_plane) { + ret = -ENOMEM; + goto fail; + } + + for (i = 0; i < ARRAY_SIZE(vc_image_formats); i++) + formats[num_formats++] = vc_image_formats[i].drm; + + plane = &vc4_plane->base; + ret = drm_universal_plane_init(dev, plane, 0, + &vc4_plane_funcs, + formats, num_formats, modifiers, + type, NULL); + + /* FIXME: Do we need to be checking return values from all these calls? + */ + drm_plane_helper_add(plane, &vc4_plane_helper_funcs); + + drm_plane_create_alpha_property(plane); + drm_plane_create_rotation_property(plane, DRM_MODE_ROTATE_0, + DRM_MODE_ROTATE_0 | + DRM_MODE_ROTATE_180 | + DRM_MODE_REFLECT_X | + DRM_MODE_REFLECT_Y); + drm_plane_create_color_properties(plane, + BIT(DRM_COLOR_YCBCR_BT601) | + BIT(DRM_COLOR_YCBCR_BT709) | + BIT(DRM_COLOR_YCBCR_BT2020), + BIT(DRM_COLOR_YCBCR_LIMITED_RANGE) | + BIT(DRM_COLOR_YCBCR_FULL_RANGE), + DRM_COLOR_YCBCR_BT709, + DRM_COLOR_YCBCR_LIMITED_RANGE); + + /* + * Default frame buffer setup is with FB on -127, and raspistill etc + * tend to drop overlays on layer 2. Cursor plane was on layer +127. + * + * For F-KMS the mailbox call allows for a s8. + * Remap zpos 0 to -127 for the background layer, but leave all the + * other layers as requested by KMS. + */ + switch (type) { + default: + case DRM_PLANE_TYPE_PRIMARY: + default_zpos = 0; + break; + case DRM_PLANE_TYPE_OVERLAY: + default_zpos = 1; + break; + case DRM_PLANE_TYPE_CURSOR: + default_zpos = 2; + break; + } + drm_plane_create_zpos_property(plane, default_zpos, 0, 127); + + /* Prepare the static elements of the mailbox structure */ + vc4_plane->mb.tag.tag = RPI_FIRMWARE_SET_PLANE; + vc4_plane->mb.tag.buf_size = sizeof(struct set_plane); + vc4_plane->mb.tag.req_resp_size = 0; + vc4_plane->mb.plane.display = display_num; + vc4_plane->mb.plane.plane_id = plane_id; + vc4_plane->mb.plane.layer = default_zpos ? default_zpos : -127; + + return plane; +fail: + if (plane) + vc4_plane_destroy(plane); + + return ERR_PTR(ret); +} + +static void vc4_crtc_mode_set_nofb(struct drm_crtc *crtc) +{ + struct drm_device *dev = crtc->dev; + struct vc4_dev *vc4 = to_vc4_dev(dev); + struct vc4_fkms_crtc *vc4_fkms_crtc = to_vc4_fkms_crtc(crtc); + struct drm_display_mode *mode = &crtc->state->adjusted_mode; + struct vc4_fkms_encoder *vc4_encoder = + to_vc4_fkms_encoder(vc4_fkms_crtc->encoder); + struct mailbox_set_mode mb = { + .tag1 = { RPI_FIRMWARE_SET_TIMING, + sizeof(struct set_timings), 0}, + }; + union hdmi_infoframe frame; + int ret; + + ret = drm_hdmi_avi_infoframe_from_display_mode(&frame.avi, vc4_fkms_crtc->connector, mode); + if (ret < 0) { + DRM_ERROR("couldn't fill AVI infoframe\n"); + return; + } + + DRM_DEBUG_KMS("Setting mode for display num %u mode name %s, clk %d, h(disp %d, start %d, end %d, total %d, skew %d) v(disp %d, start %d, end %d, total %d, scan %d), vrefresh %d, par %u, flags 0x%04x\n", + vc4_fkms_crtc->display_number, mode->name, mode->clock, + mode->hdisplay, mode->hsync_start, mode->hsync_end, + mode->htotal, mode->hskew, mode->vdisplay, + mode->vsync_start, mode->vsync_end, mode->vtotal, + mode->vscan, drm_mode_vrefresh(mode), + mode->picture_aspect_ratio, mode->flags); + mb.timings.display = vc4_fkms_crtc->display_number; + + mb.timings.clock = mode->clock; + mb.timings.hdisplay = mode->hdisplay; + mb.timings.hsync_start = mode->hsync_start; + mb.timings.hsync_end = mode->hsync_end; + mb.timings.htotal = mode->htotal; + mb.timings.hskew = mode->hskew; + mb.timings.vdisplay = mode->vdisplay; + mb.timings.vsync_start = mode->vsync_start; + mb.timings.vsync_end = mode->vsync_end; + mb.timings.vtotal = mode->vtotal; + mb.timings.vscan = mode->vscan; + mb.timings.vrefresh = drm_mode_vrefresh(mode); + mb.timings.flags = 0; + if (mode->flags & DRM_MODE_FLAG_PHSYNC) + mb.timings.flags |= TIMINGS_FLAGS_H_SYNC_POS; + if (mode->flags & DRM_MODE_FLAG_PVSYNC) + mb.timings.flags |= TIMINGS_FLAGS_V_SYNC_POS; + + switch (frame.avi.picture_aspect) { + default: + case HDMI_PICTURE_ASPECT_NONE: + mb.timings.flags |= TIMINGS_FLAGS_ASPECT_NONE; + break; + case HDMI_PICTURE_ASPECT_4_3: + mb.timings.flags |= TIMINGS_FLAGS_ASPECT_4_3; + break; + case HDMI_PICTURE_ASPECT_16_9: + mb.timings.flags |= TIMINGS_FLAGS_ASPECT_16_9; + break; + case HDMI_PICTURE_ASPECT_64_27: + mb.timings.flags |= TIMINGS_FLAGS_ASPECT_64_27; + break; + case HDMI_PICTURE_ASPECT_256_135: + mb.timings.flags |= TIMINGS_FLAGS_ASPECT_256_135; + break; + } + + if (mode->flags & DRM_MODE_FLAG_INTERLACE) + mb.timings.flags |= TIMINGS_FLAGS_INTERLACE; + if (mode->flags & DRM_MODE_FLAG_DBLCLK) + mb.timings.flags |= TIMINGS_FLAGS_DBL_CLK; + + mb.timings.video_id_code = frame.avi.video_code; + + if (!vc4_encoder->hdmi_monitor) { + mb.timings.flags |= TIMINGS_FLAGS_DVI; + } else { + struct vc4_fkms_connector_state *conn_state = + to_vc4_fkms_connector_state(vc4_fkms_crtc->connector->state); + + if (conn_state->broadcast_rgb == VC4_BROADCAST_RGB_AUTO) { + /* See CEA-861-E - 5.1 Default Encoding Parameters */ + if (drm_default_rgb_quant_range(mode) == + HDMI_QUANTIZATION_RANGE_LIMITED) + mb.timings.flags |= TIMINGS_FLAGS_RGB_LIMITED; + } else { + if (conn_state->broadcast_rgb == + VC4_BROADCAST_RGB_LIMITED) + mb.timings.flags |= TIMINGS_FLAGS_RGB_LIMITED; + + /* If not using the default range, then do not provide + * a VIC as the HDMI spec requires that we do not + * signal the opposite of the defined range in the AVI + * infoframe. + */ + if (!!(mb.timings.flags & TIMINGS_FLAGS_RGB_LIMITED) != + (drm_default_rgb_quant_range(mode) == + HDMI_QUANTIZATION_RANGE_LIMITED)) + mb.timings.video_id_code = 0; + } + } + + /* + * FIXME: To implement + * switch(mode->flag & DRM_MODE_FLAG_3D_MASK) { + * case DRM_MODE_FLAG_3D_NONE: + * case DRM_MODE_FLAG_3D_FRAME_PACKING: + * case DRM_MODE_FLAG_3D_FIELD_ALTERNATIVE: + * case DRM_MODE_FLAG_3D_LINE_ALTERNATIVE: + * case DRM_MODE_FLAG_3D_SIDE_BY_SIDE_FULL: + * case DRM_MODE_FLAG_3D_L_DEPTH: + * case DRM_MODE_FLAG_3D_L_DEPTH_GFX_GFX_DEPTH: + * case DRM_MODE_FLAG_3D_TOP_AND_BOTTOM: + * case DRM_MODE_FLAG_3D_SIDE_BY_SIDE_HALF: + * } + */ + + ret = rpi_firmware_property_list(vc4->firmware, &mb, sizeof(mb)); +} + +static void vc4_crtc_disable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct drm_device *dev = crtc->dev; + struct drm_plane *plane; + + DRM_DEBUG_KMS("[CRTC:%d] vblanks off.\n", + crtc->base.id); + drm_crtc_vblank_off(crtc); + + /* Always turn the planes off on CRTC disable. In DRM, planes + * are enabled/disabled through the update/disable hooks + * above, and the CRTC enable/disable independently controls + * whether anything scans out at all, but the firmware doesn't + * give us a CRTC-level control for that. + */ + + drm_atomic_crtc_for_each_plane(plane, crtc) + vc4_plane_atomic_disable(plane, state); + + /* + * Make sure we issue a vblank event after disabling the CRTC if + * someone was waiting it. + */ + if (crtc->state->event) { + unsigned long flags; + + spin_lock_irqsave(&dev->event_lock, flags); + drm_crtc_send_vblank_event(crtc, crtc->state->event); + crtc->state->event = NULL; + spin_unlock_irqrestore(&dev->event_lock, flags); + } +} + +static void vc4_crtc_consume_event(struct drm_crtc *crtc) +{ + struct vc4_fkms_crtc *vc4_fkms_crtc = to_vc4_fkms_crtc(crtc); + struct drm_device *dev = crtc->dev; + unsigned long flags; + + if (!crtc->state->event) + return; + + crtc->state->event->pipe = drm_crtc_index(crtc); + + WARN_ON(drm_crtc_vblank_get(crtc) != 0); + + spin_lock_irqsave(&dev->event_lock, flags); + vc4_fkms_crtc->event = crtc->state->event; + crtc->state->event = NULL; + spin_unlock_irqrestore(&dev->event_lock, flags); +} + +static void vc4_crtc_enable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct drm_plane *plane; + + DRM_DEBUG_KMS("[CRTC:%d] vblanks on.\n", + crtc->base.id); + drm_crtc_vblank_on(crtc); + vc4_crtc_consume_event(crtc); + + /* Unblank the planes (if they're supposed to be displayed). */ + drm_atomic_crtc_for_each_plane(plane, crtc) + if (plane->state->fb) + vc4_plane_set_blank(plane, plane->state->visible); +} + +static enum drm_mode_status +vc4_crtc_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode *mode) +{ + struct vc4_fkms_crtc *vc4_fkms_crtc = to_vc4_fkms_crtc(crtc); + struct drm_device *dev = crtc->dev; + struct vc4_dev *vc4 = to_vc4_dev(dev); + struct vc4_fkms *fkms = vc4->fkms; + + /* Do not allow doublescan modes from user space */ + if (mode->flags & DRM_MODE_FLAG_DBLSCAN) { + DRM_DEBUG_KMS("[CRTC:%d] Doublescan mode rejected.\n", + crtc->base.id); + return MODE_NO_DBLESCAN; + } + + /* Disable refresh rates > defined threshold (default 85Hz) as limited + * gain from them + */ + if (drm_mode_vrefresh(mode) > fkms_max_refresh_rate) + return MODE_BAD_VVALUE; + + /* Limit the pixel clock based on the HDMI clock limits from the + * firmware + */ + switch (vc4_fkms_crtc->display_number) { + case 2: /* HDMI0 */ + if (fkms->cfg.max_pixel_clock[0] && + mode->clock > fkms->cfg.max_pixel_clock[0]) + return MODE_CLOCK_HIGH; + break; + case 7: /* HDMI1 */ + if (fkms->cfg.max_pixel_clock[1] && + mode->clock > fkms->cfg.max_pixel_clock[1]) + return MODE_CLOCK_HIGH; + break; + } + + /* Pi4 can't generate odd horizontal timings on HDMI, so reject modes + * that would set them. + */ + if (fkms->revision >= BCM2711 && + (vc4_fkms_crtc->display_number == 2 || vc4_fkms_crtc->display_number == 7) && + !(mode->flags & DRM_MODE_FLAG_DBLCLK) && + ((mode->hdisplay | /* active */ + (mode->hsync_start - mode->hdisplay) | /* front porch */ + (mode->hsync_end - mode->hsync_start) | /* sync pulse */ + (mode->htotal - mode->hsync_end)) & 1)) /* back porch */ { + DRM_DEBUG_KMS("[CRTC:%d] Odd timing rejected %u %u %u %u.\n", + crtc->base.id, mode->hdisplay, mode->hsync_start, + mode->hsync_end, mode->htotal); + return MODE_H_ILLEGAL; + } + + return MODE_OK; +} + +static int vc4_fkms_crtc_atomic_check(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, + crtc); + struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc_state); + struct drm_connector *conn; + struct drm_connector_state *conn_state; + int i; + + DRM_DEBUG_KMS("[CRTC:%d] crtc_atomic_check.\n", crtc->base.id); + + for_each_new_connector_in_state(crtc_state->state, conn, conn_state, i) { + if (conn_state->crtc != crtc) + continue; + + vc4_state->margins.left = conn_state->tv.margins.left; + vc4_state->margins.right = conn_state->tv.margins.right; + vc4_state->margins.top = conn_state->tv.margins.top; + vc4_state->margins.bottom = conn_state->tv.margins.bottom; + break; + } + return 0; +} + +static void vc4_crtc_atomic_flush(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct drm_crtc_state *old_state = drm_atomic_get_old_crtc_state(state, + crtc); + + DRM_DEBUG_KMS("[CRTC:%d] crtc_atomic_flush.\n", + crtc->base.id); + if (crtc->state->active && old_state->active && crtc->state->event) + vc4_crtc_consume_event(crtc); +} + +static void vc4_crtc_handle_page_flip(struct vc4_fkms_crtc *vc4_fkms_crtc) +{ + struct drm_crtc *crtc = &vc4_fkms_crtc->base; + struct drm_device *dev = crtc->dev; + unsigned long flags; + + spin_lock_irqsave(&dev->event_lock, flags); + if (vc4_fkms_crtc->event) { + drm_crtc_send_vblank_event(crtc, vc4_fkms_crtc->event); + vc4_fkms_crtc->event = NULL; + drm_crtc_vblank_put(crtc); + } + spin_unlock_irqrestore(&dev->event_lock, flags); +} + +static irqreturn_t vc4_crtc_irq_handler(int irq, void *data) +{ + struct vc4_fkms_crtc **crtc_list = data; + int i; + u32 stat = readl(crtc_list[0]->regs + SMICS); + irqreturn_t ret = IRQ_NONE; + u32 chan; + + if (stat & SMICS_INTERRUPTS) { + writel(0, crtc_list[0]->regs + SMICS); + + chan = readl(crtc_list[0]->regs + SMIDSW0); + + if ((chan & 0xFFFF0000) != SMI_NEW) { + /* Older firmware. Treat the one interrupt as vblank/ + * complete for all crtcs. + */ + for (i = 0; crtc_list[i]; i++) { + if (crtc_list[i]->vblank_enabled) + drm_crtc_handle_vblank(&crtc_list[i]->base); + vc4_crtc_handle_page_flip(crtc_list[i]); + } + } else { + if (chan & 1) { + writel(SMI_NEW, crtc_list[0]->regs + SMIDSW0); + if (crtc_list[0]->vblank_enabled) + drm_crtc_handle_vblank(&crtc_list[0]->base); + vc4_crtc_handle_page_flip(crtc_list[0]); + } + + if (crtc_list[1]) { + /* Check for the secondary display too */ + chan = readl(crtc_list[0]->regs + SMIDSW1); + + if (chan & 1) { + writel(SMI_NEW, crtc_list[0]->regs + SMIDSW1); + + if (crtc_list[1]->vblank_enabled) + drm_crtc_handle_vblank(&crtc_list[1]->base); + vc4_crtc_handle_page_flip(crtc_list[1]); + } + } + } + + ret = IRQ_HANDLED; + } + + return ret; +} + +static irqreturn_t vc4_crtc2712_irq_handler(int irq, void *data) +{ + struct vc4_fkms_crtc **crtc_list = data; + int i; + + for (i = 0; crtc_list[i]; i++) { + if (crtc_list[i]->vblank_enabled) + drm_crtc_handle_vblank(&crtc_list[i]->base); + vc4_crtc_handle_page_flip(crtc_list[i]); + } + + return IRQ_HANDLED; +} + +static int vc4_fkms_page_flip(struct drm_crtc *crtc, + struct drm_framebuffer *fb, + struct drm_pending_vblank_event *event, + uint32_t flags, + struct drm_modeset_acquire_ctx *ctx) +{ + if (flags & DRM_MODE_PAGE_FLIP_ASYNC) { + DRM_ERROR("Async flips aren't allowed\n"); + return -EINVAL; + } + + return drm_atomic_helper_page_flip(crtc, fb, event, flags, ctx); +} + +static struct drm_crtc_state * +vc4_fkms_crtc_duplicate_state(struct drm_crtc *crtc) +{ + struct vc4_crtc_state *vc4_state, *old_vc4_state; + + vc4_state = kzalloc(sizeof(*vc4_state), GFP_KERNEL); + if (!vc4_state) + return NULL; + + old_vc4_state = to_vc4_crtc_state(crtc->state); + vc4_state->margins = old_vc4_state->margins; + + __drm_atomic_helper_crtc_duplicate_state(crtc, &vc4_state->base); + return &vc4_state->base; +} + +static void +vc4_fkms_crtc_reset(struct drm_crtc *crtc) +{ + if (crtc->state) + __drm_atomic_helper_crtc_destroy_state(crtc->state); + + crtc->state = kzalloc(sizeof(*crtc->state), GFP_KERNEL); + if (crtc->state) + crtc->state->crtc = crtc; +} + +static int vc4_fkms_enable_vblank(struct drm_crtc *crtc) +{ + struct vc4_fkms_crtc *vc4_fkms_crtc = to_vc4_fkms_crtc(crtc); + + DRM_DEBUG_KMS("[CRTC:%d] enable_vblank.\n", + crtc->base.id); + vc4_fkms_crtc->vblank_enabled = true; + + return 0; +} + +static void vc4_fkms_disable_vblank(struct drm_crtc *crtc) +{ + struct vc4_fkms_crtc *vc4_fkms_crtc = to_vc4_fkms_crtc(crtc); + + DRM_DEBUG_KMS("[CRTC:%d] disable_vblank.\n", + crtc->base.id); + vc4_fkms_crtc->vblank_enabled = false; +} + +static const struct drm_crtc_funcs vc4_crtc_funcs = { + .set_config = drm_atomic_helper_set_config, + .destroy = drm_crtc_cleanup, + .page_flip = vc4_fkms_page_flip, + .set_property = NULL, + .cursor_set = NULL, /* handled by drm_mode_cursor_universal */ + .cursor_move = NULL, /* handled by drm_mode_cursor_universal */ + .reset = vc4_fkms_crtc_reset, + .atomic_duplicate_state = vc4_fkms_crtc_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, + .enable_vblank = vc4_fkms_enable_vblank, + .disable_vblank = vc4_fkms_disable_vblank, +}; + +static const struct drm_crtc_helper_funcs vc4_crtc_helper_funcs = { + .mode_set_nofb = vc4_crtc_mode_set_nofb, + .mode_valid = vc4_crtc_mode_valid, + .atomic_check = vc4_fkms_crtc_atomic_check, + .atomic_flush = vc4_crtc_atomic_flush, + .atomic_enable = vc4_crtc_enable, + .atomic_disable = vc4_crtc_disable, +}; + +static const struct of_device_id vc4_firmware_kms_dt_match[] = { + { .compatible = "raspberrypi,rpi-firmware-kms", + .data = (void *)BCM2835_6_7 }, + { .compatible = "raspberrypi,rpi-firmware-kms-2711", + .data = (void *)BCM2711 }, + { .compatible = "raspberrypi,rpi-firmware-kms-2712", + .data = (void *)BCM2712 }, + {} +}; + +static enum drm_connector_status +vc4_fkms_connector_detect(struct drm_connector *connector, bool force) +{ + DRM_DEBUG_KMS("connector detect.\n"); + return connector_status_connected; +} + +/* Queries the firmware to populate a drm_mode structure for this display */ +static int vc4_fkms_get_fw_mode(struct vc4_fkms_connector *fkms_connector, + struct drm_display_mode *mode) +{ + struct vc4_dev *vc4 = fkms_connector->vc4_dev; + struct set_timings timings = { 0 }; + int ret; + + timings.display = fkms_connector->display_number; + + ret = rpi_firmware_property(vc4->firmware, + RPI_FIRMWARE_GET_DISPLAY_TIMING, &timings, + sizeof(timings)); + if (ret || !timings.clock) + /* No mode returned - abort */ + return -1; + + /* Equivalent to DRM_MODE macro. */ + memset(mode, 0, sizeof(*mode)); + strncpy(mode->name, "FIXED_MODE", sizeof(mode->name)); + mode->status = 0; + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + mode->clock = timings.clock; + mode->hdisplay = timings.hdisplay; + mode->hsync_start = timings.hsync_start; + mode->hsync_end = timings.hsync_end; + mode->htotal = timings.htotal; + mode->hskew = 0; + mode->vdisplay = timings.vdisplay; + mode->vsync_start = timings.vsync_start; + mode->vsync_end = timings.vsync_end; + mode->vtotal = timings.vtotal; + mode->vscan = timings.vscan; + + if (timings.flags & TIMINGS_FLAGS_H_SYNC_POS) + mode->flags |= DRM_MODE_FLAG_PHSYNC; + else + mode->flags |= DRM_MODE_FLAG_NHSYNC; + + if (timings.flags & TIMINGS_FLAGS_V_SYNC_POS) + mode->flags |= DRM_MODE_FLAG_PVSYNC; + else + mode->flags |= DRM_MODE_FLAG_NVSYNC; + + if (timings.flags & TIMINGS_FLAGS_INTERLACE) + mode->flags |= DRM_MODE_FLAG_INTERLACE; + + return 0; +} + +static int vc4_fkms_get_edid_block(void *data, u8 *buf, unsigned int block, + size_t len) +{ + struct vc4_fkms_connector *fkms_connector = + (struct vc4_fkms_connector *)data; + struct vc4_dev *vc4 = fkms_connector->vc4_dev; + struct mailbox_get_edid mb = { + .tag1 = { RPI_FIRMWARE_GET_EDID_BLOCK_DISPLAY, + 128 + 8, 0 }, + .block = block, + .display_number = fkms_connector->display_number, + }; + int ret = 0; + + ret = rpi_firmware_property_list(vc4->firmware, &mb, sizeof(mb)); + + if (!ret) + memcpy(buf, mb.edid, len); + + return ret; +} + +static int vc4_fkms_connector_get_modes(struct drm_connector *connector) +{ + struct vc4_fkms_connector *fkms_connector = + to_vc4_fkms_connector(connector); + struct drm_encoder *encoder = fkms_connector->encoder; + struct vc4_fkms_encoder *vc4_encoder = to_vc4_fkms_encoder(encoder); + struct drm_display_mode fw_mode; + struct drm_display_mode *mode; + const struct drm_edid *drm_edid; + const struct edid *edid; + int num_modes; + + if (!vc4_fkms_get_fw_mode(fkms_connector, &fw_mode)) { + drm_mode_debug_printmodeline(&fw_mode); + mode = drm_mode_duplicate(connector->dev, + &fw_mode); + drm_mode_probed_add(connector, mode); + num_modes = 1; /* 1 mode */ + } else { + drm_edid = drm_edid_read_custom(connector, vc4_fkms_get_edid_block, + fkms_connector); + edid = drm_edid_raw(drm_edid); + + /* FIXME: Can we do CEC? + * cec_s_phys_addr_from_edid(vc4->hdmi->cec_adap, edid); + * if (!edid) + * return -ENODEV; + */ + + vc4_encoder->hdmi_monitor = drm_detect_hdmi_monitor(edid); + + drm_connector_update_edid_property(connector, edid); + num_modes = drm_add_edid_modes(connector, (struct edid *)edid); + kfree(drm_edid); + } + + return num_modes; +} + +/* This is the DSI panel resolution. Use this as a default should the firmware + * not respond to our request for the timings. + */ +static const struct drm_display_mode lcd_mode = { + DRM_MODE("800x480", DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, + 25979400 / 1000, + 800, 800 + 1, 800 + 1 + 2, 800 + 1 + 2 + 46, 0, + 480, 480 + 7, 480 + 7 + 2, 480 + 7 + 2 + 21, 0, + 0) +}; + +static int vc4_fkms_lcd_connector_get_modes(struct drm_connector *connector) +{ + struct vc4_fkms_connector *fkms_connector = + to_vc4_fkms_connector(connector); + struct drm_display_mode *mode; + struct drm_display_mode fw_mode; + + if (!vc4_fkms_get_fw_mode(fkms_connector, &fw_mode) && fw_mode.clock) + mode = drm_mode_duplicate(connector->dev, + &fw_mode); + else + mode = drm_mode_duplicate(connector->dev, + &lcd_mode); + + if (!mode) { + DRM_ERROR("Failed to create a new display mode\n"); + return -ENOMEM; + } + + drm_mode_probed_add(connector, mode); + + /* We have one mode */ + return 1; +} + +static struct drm_encoder * +vc4_fkms_connector_best_encoder(struct drm_connector *connector) +{ + struct vc4_fkms_connector *fkms_connector = + to_vc4_fkms_connector(connector); + DRM_DEBUG_KMS("best_connector.\n"); + return fkms_connector->encoder; +} + +static void vc4_fkms_connector_destroy(struct drm_connector *connector) +{ + DRM_DEBUG_KMS("[CONNECTOR:%d] destroy.\n", + connector->base.id); + drm_connector_unregister(connector); + drm_connector_cleanup(connector); +} + +/** + * vc4_connector_duplicate_state - duplicate connector state + * @connector: digital connector + * + * Allocates and returns a copy of the connector state (both common and + * digital connector specific) for the specified connector. + * + * Returns: The newly allocated connector state, or NULL on failure. + */ +static struct drm_connector_state * +vc4_connector_duplicate_state(struct drm_connector *connector) +{ + struct vc4_fkms_connector_state *state; + + state = kmemdup(connector->state, sizeof(*state), GFP_KERNEL); + if (!state) + return NULL; + + __drm_atomic_helper_connector_duplicate_state(connector, &state->base); + return &state->base; +} + +/** + * vc4_connector_atomic_get_property - hook for connector->atomic_get_property. + * @connector: Connector to get the property for. + * @state: Connector state to retrieve the property from. + * @property: Property to retrieve. + * @val: Return value for the property. + * + * Returns the atomic property value for a digital connector. + */ +static int vc4_connector_atomic_get_property(struct drm_connector *connector, + const struct drm_connector_state *state, + struct drm_property *property, + uint64_t *val) +{ + struct vc4_fkms_connector *fkms_connector = + to_vc4_fkms_connector(connector); + struct vc4_fkms_connector_state *vc4_conn_state = + to_vc4_fkms_connector_state(state); + + if (property == fkms_connector->broadcast_rgb_property) { + *val = vc4_conn_state->broadcast_rgb; + } else { + DRM_DEBUG_ATOMIC("Unknown property [PROP:%d:%s]\n", + property->base.id, property->name); + return -EINVAL; + } + + return 0; +} + +/** + * vc4_connector_atomic_set_property - hook for connector->atomic_set_property. + * @connector: Connector to set the property for. + * @state: Connector state to set the property on. + * @property: Property to set. + * @val: New value for the property. + * + * Sets the atomic property value for a digital connector. + */ +static int vc4_connector_atomic_set_property(struct drm_connector *connector, + struct drm_connector_state *state, + struct drm_property *property, + uint64_t val) +{ + struct vc4_fkms_connector *fkms_connector = + to_vc4_fkms_connector(connector); + struct vc4_fkms_connector_state *vc4_conn_state = + to_vc4_fkms_connector_state(state); + + if (property == fkms_connector->broadcast_rgb_property) { + vc4_conn_state->broadcast_rgb = val; + return 0; + } + + DRM_DEBUG_ATOMIC("Unknown property [PROP:%d:%s]\n", + property->base.id, property->name); + return -EINVAL; +} + +static int vc4_connector_atomic_check(struct drm_connector *connector, + struct drm_atomic_state *state) +{ + struct drm_connector_state *old_state = + drm_atomic_get_old_connector_state(state, connector); + struct vc4_fkms_connector_state *vc4_old_state = + to_vc4_fkms_connector_state(old_state); + struct drm_connector_state *new_state = + drm_atomic_get_new_connector_state(state, connector); + struct vc4_fkms_connector_state *vc4_new_state = + to_vc4_fkms_connector_state(new_state); + struct drm_crtc *crtc = new_state->crtc; + + if (!crtc) + return 0; + + if (vc4_old_state->broadcast_rgb != vc4_new_state->broadcast_rgb) { + struct drm_crtc_state *crtc_state; + + crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + + crtc_state->mode_changed = true; + } + return 0; +} + +static void vc4_hdmi_connector_reset(struct drm_connector *connector) +{ + drm_atomic_helper_connector_reset(connector); + drm_atomic_helper_connector_tv_margins_reset(connector); +} + +static const struct drm_connector_funcs vc4_fkms_connector_funcs = { + .detect = vc4_fkms_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = vc4_fkms_connector_destroy, + .reset = vc4_hdmi_connector_reset, + .atomic_duplicate_state = vc4_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .atomic_get_property = vc4_connector_atomic_get_property, + .atomic_set_property = vc4_connector_atomic_set_property, +}; + +static const struct drm_connector_helper_funcs vc4_fkms_connector_helper_funcs = { + .get_modes = vc4_fkms_connector_get_modes, + .best_encoder = vc4_fkms_connector_best_encoder, + .atomic_check = vc4_connector_atomic_check, +}; + +static const struct drm_connector_helper_funcs vc4_fkms_lcd_conn_helper_funcs = { + .get_modes = vc4_fkms_lcd_connector_get_modes, + .best_encoder = vc4_fkms_connector_best_encoder, +}; + +static const struct drm_prop_enum_list broadcast_rgb_names[] = { + { VC4_BROADCAST_RGB_AUTO, "Automatic" }, + { VC4_BROADCAST_RGB_FULL, "Full" }, + { VC4_BROADCAST_RGB_LIMITED, "Limited 16:235" }, +}; + +static void +vc4_attach_broadcast_rgb_property(struct vc4_fkms_connector *fkms_connector) +{ + struct drm_device *dev = fkms_connector->base.dev; + struct drm_property *prop; + + prop = fkms_connector->broadcast_rgb_property; + if (!prop) { + prop = drm_property_create_enum(dev, DRM_MODE_PROP_ENUM, + "Broadcast RGB", + broadcast_rgb_names, + ARRAY_SIZE(broadcast_rgb_names)); + if (!prop) + return; + + fkms_connector->broadcast_rgb_property = prop; + } + + drm_object_attach_property(&fkms_connector->base.base, prop, 0); +} + +static struct drm_connector * +vc4_fkms_connector_init(struct drm_device *dev, struct drm_encoder *encoder, + u32 display_num) +{ + struct drm_connector *connector = NULL; + struct vc4_fkms_connector *fkms_connector; + struct vc4_fkms_connector_state *conn_state = NULL; + struct vc4_dev *vc4_dev = to_vc4_dev(dev); + int ret = 0; + + DRM_DEBUG_KMS("connector_init, display_num %u\n", display_num); + + fkms_connector = devm_kzalloc(dev->dev, sizeof(*fkms_connector), + GFP_KERNEL); + if (!fkms_connector) + return ERR_PTR(-ENOMEM); + + /* + * Allocate enough memory to hold vc4_fkms_connector_state, + */ + conn_state = kzalloc(sizeof(*conn_state), GFP_KERNEL); + if (!conn_state) { + kfree(fkms_connector); + return ERR_PTR(-ENOMEM); + } + + connector = &fkms_connector->base; + + fkms_connector->encoder = encoder; + fkms_connector->display_number = display_num; + fkms_connector->display_type = vc4_get_display_type(display_num); + fkms_connector->vc4_dev = vc4_dev; + + __drm_atomic_helper_connector_reset(connector, + &conn_state->base); + + if (fkms_connector->display_type == DRM_MODE_ENCODER_DSI) { + drm_connector_init(dev, connector, &vc4_fkms_connector_funcs, + DRM_MODE_CONNECTOR_DSI); + drm_connector_helper_add(connector, + &vc4_fkms_lcd_conn_helper_funcs); + connector->interlace_allowed = 0; + } else if (fkms_connector->display_type == DRM_MODE_ENCODER_TVDAC) { + drm_connector_init(dev, connector, &vc4_fkms_connector_funcs, + DRM_MODE_CONNECTOR_Composite); + drm_connector_helper_add(connector, + &vc4_fkms_lcd_conn_helper_funcs); + connector->interlace_allowed = 1; + } else { + drm_connector_init(dev, connector, &vc4_fkms_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA); + drm_connector_helper_add(connector, + &vc4_fkms_connector_helper_funcs); + connector->interlace_allowed = 1; + } + + ret = drm_mode_create_tv_margin_properties(dev); + if (ret) + goto fail; + + drm_connector_attach_tv_margin_properties(connector); + + connector->polled = (DRM_CONNECTOR_POLL_CONNECT | + DRM_CONNECTOR_POLL_DISCONNECT); + + connector->doublescan_allowed = 0; + + vc4_attach_broadcast_rgb_property(fkms_connector); + + drm_connector_attach_encoder(connector, encoder); + + return connector; + + fail: + if (connector) + vc4_fkms_connector_destroy(connector); + + return ERR_PTR(ret); +} + +static void vc4_fkms_encoder_destroy(struct drm_encoder *encoder) +{ + DRM_DEBUG_KMS("Encoder_destroy\n"); + drm_encoder_cleanup(encoder); +} + +static const struct drm_encoder_funcs vc4_fkms_encoder_funcs = { + .destroy = vc4_fkms_encoder_destroy, +}; + +static void vc4_fkms_display_power(struct drm_encoder *encoder, bool power) +{ + struct vc4_fkms_encoder *vc4_encoder = to_vc4_fkms_encoder(encoder); + struct vc4_dev *vc4 = to_vc4_dev(encoder->dev); + + struct mailbox_display_pwr pwr = { + .tag1 = {RPI_FIRMWARE_SET_DISPLAY_POWER, 8, 0, }, + .display = vc4_encoder->display_num, + .state = power ? 1 : 0, + }; + + rpi_firmware_property_list(vc4->firmware, &pwr, sizeof(pwr)); +} + +static void vc4_fkms_encoder_enable(struct drm_encoder *encoder) +{ + vc4_fkms_display_power(encoder, true); + DRM_DEBUG_KMS("Encoder_enable\n"); +} + +static void vc4_fkms_encoder_disable(struct drm_encoder *encoder) +{ + vc4_fkms_display_power(encoder, false); + DRM_DEBUG_KMS("Encoder_disable\n"); +} + +static const struct drm_encoder_helper_funcs vc4_fkms_encoder_helper_funcs = { + .enable = vc4_fkms_encoder_enable, + .disable = vc4_fkms_encoder_disable, +}; + +static int vc4_fkms_create_screen(struct device *dev, struct drm_device *drm, + int display_idx, int display_ref, + struct vc4_fkms_crtc **ret_crtc) +{ + struct vc4_dev *vc4 = to_vc4_dev(drm); + struct vc4_fkms_crtc *vc4_fkms_crtc; + struct vc4_fkms_encoder *vc4_encoder; + struct drm_crtc *crtc; + struct drm_plane *destroy_plane, *temp; + struct mailbox_blank_display blank = { + .tag1 = {RPI_FIRMWARE_FRAMEBUFFER_SET_DISPLAY_NUM, 4, 0, }, + .display = display_idx, + .tag2 = { RPI_FIRMWARE_FRAMEBUFFER_BLANK, 4, 0, }, + .blank = 1, + }; + struct drm_plane *planes[PLANES_PER_CRTC]; + int ret, i; + + vc4_fkms_crtc = devm_kzalloc(dev, sizeof(*vc4_fkms_crtc), GFP_KERNEL); + if (!vc4_fkms_crtc) + return -ENOMEM; + crtc = &vc4_fkms_crtc->base; + + vc4_fkms_crtc->display_number = display_ref; + vc4_fkms_crtc->display_type = vc4_get_display_type(display_ref); + + /* Blank the firmware provided framebuffer */ + rpi_firmware_property_list(vc4->firmware, &blank, sizeof(blank)); + + for (i = 0; i < PLANES_PER_CRTC; i++) { + planes[i] = vc4_fkms_plane_init(drm, + (i == 0) ? + DRM_PLANE_TYPE_PRIMARY : + (i == PLANES_PER_CRTC - 1) ? + DRM_PLANE_TYPE_CURSOR : + DRM_PLANE_TYPE_OVERLAY, + display_ref, + i + (display_idx * PLANES_PER_CRTC) + ); + if (IS_ERR(planes[i])) { + dev_err(dev, "failed to construct plane %u\n", i); + ret = PTR_ERR(planes[i]); + goto err; + } + } + + drm_crtc_init_with_planes(drm, crtc, planes[0], + planes[PLANES_PER_CRTC - 1], &vc4_crtc_funcs, + NULL); + drm_crtc_helper_add(crtc, &vc4_crtc_helper_funcs); + + /* Update the possible_crtcs mask for the overlay plane(s) */ + for (i = 1; i < (PLANES_PER_CRTC - 1); i++) + planes[i]->possible_crtcs = drm_crtc_mask(crtc); + + vc4_encoder = devm_kzalloc(dev, sizeof(*vc4_encoder), GFP_KERNEL); + if (!vc4_encoder) + return -ENOMEM; + vc4_fkms_crtc->encoder = &vc4_encoder->base; + + vc4_encoder->display_num = display_ref; + vc4_encoder->base.possible_crtcs |= drm_crtc_mask(crtc); + + drm_encoder_init(drm, &vc4_encoder->base, &vc4_fkms_encoder_funcs, + vc4_fkms_crtc->display_type, NULL); + drm_encoder_helper_add(&vc4_encoder->base, + &vc4_fkms_encoder_helper_funcs); + + vc4_fkms_crtc->connector = vc4_fkms_connector_init(drm, &vc4_encoder->base, + display_ref); + if (IS_ERR(vc4_fkms_crtc->connector)) { + ret = PTR_ERR(vc4_fkms_crtc->connector); + goto err_destroy_encoder; + } + + *ret_crtc = vc4_fkms_crtc; + + return 0; + +err_destroy_encoder: + vc4_fkms_encoder_destroy(vc4_fkms_crtc->encoder); + list_for_each_entry_safe(destroy_plane, temp, + &drm->mode_config.plane_list, head) { + if (destroy_plane->possible_crtcs == 1 << drm_crtc_index(crtc)) + destroy_plane->funcs->destroy(destroy_plane); + } +err: + return ret; +} + +static int vc4_fkms_bind(struct device *dev, struct device *master, void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct drm_device *drm = dev_get_drvdata(master); + struct vc4_dev *vc4 = to_vc4_dev(drm); + struct device_node *firmware_node; + const struct of_device_id *match; + struct vc4_fkms_crtc **crtc_list; + u32 num_displays, display_num; + struct vc4_fkms *fkms; + int ret; + u32 display_id; + + vc4->firmware_kms = true; + + fkms = devm_kzalloc(dev, sizeof(*fkms), GFP_KERNEL); + if (!fkms) + return -ENOMEM; + + match = of_match_device(vc4_firmware_kms_dt_match, dev); + if (!match) + return -ENODEV; + fkms->revision = (enum vc4_fkms_revision)match->data; + + firmware_node = of_parse_phandle(dev->of_node, "brcm,firmware", 0); + vc4->firmware = devm_rpi_firmware_get(&pdev->dev, firmware_node); + if (!vc4->firmware) { + DRM_DEBUG("Failed to get Raspberry Pi firmware reference.\n"); + return -EPROBE_DEFER; + } + of_node_put(firmware_node); + + ret = rpi_firmware_property(vc4->firmware, + RPI_FIRMWARE_FRAMEBUFFER_GET_NUM_DISPLAYS, + &num_displays, sizeof(u32)); + + /* If we fail to get the number of displays, then + * assume old firmware that doesn't have the mailbox call, so just + * set one display + */ + if (ret) { + num_displays = 1; + DRM_WARN("Unable to determine number of displays - assuming 1\n"); + ret = 0; + } + + ret = rpi_firmware_property(vc4->firmware, + RPI_FIRMWARE_GET_DISPLAY_CFG, + &fkms->cfg, sizeof(fkms->cfg)); + + if (ret) + return -EINVAL; + /* The firmware works in Hz. This will be compared against kHz, so div + * 1000 now rather than multiple times later. + */ + fkms->cfg.max_pixel_clock[0] /= 1000; + fkms->cfg.max_pixel_clock[1] /= 1000; + + /* Allocate a list, with space for a NULL on the end */ + crtc_list = devm_kzalloc(dev, sizeof(crtc_list) * (num_displays + 1), + GFP_KERNEL); + if (!crtc_list) + return -ENOMEM; + + for (display_num = 0; display_num < num_displays; display_num++) { + display_id = display_num; + ret = rpi_firmware_property(vc4->firmware, + RPI_FIRMWARE_FRAMEBUFFER_GET_DISPLAY_ID, + &display_id, sizeof(display_id)); + /* FIXME: Determine the correct error handling here. + * Should we fail to create the one "screen" but keep the + * others, or fail the whole thing? + */ + if (ret) + DRM_ERROR("Failed to get display id %u\n", display_num); + + ret = vc4_fkms_create_screen(dev, drm, display_num, display_id, + &crtc_list[display_num]); + if (ret) + DRM_ERROR("Oh dear, failed to create display %u\n", + display_num); + } + + if (num_displays > 0) { + if (fkms->revision >= BCM2712) { + ret = devm_request_irq(dev, platform_get_irq(pdev, 0), + vc4_crtc2712_irq_handler, 0, + "vc4 firmware kms", crtc_list); + } else { + /* Map the SMI interrupt reg */ + crtc_list[0]->regs = vc4_ioremap_regs(pdev, 0); + if (IS_ERR(crtc_list[0]->regs)) + DRM_ERROR("Oh dear, failed to map registers\n"); + + writel(0, crtc_list[0]->regs + SMICS); + ret = devm_request_irq(dev, platform_get_irq(pdev, 0), + vc4_crtc_irq_handler, 0, + "vc4 firmware kms", crtc_list); + } + if (ret) + DRM_ERROR("Oh dear, failed to register IRQ\n"); + } else { + DRM_WARN("No displays found. Consider forcing hotplug if HDMI is attached\n"); + } + + vc4->fkms = fkms; + + platform_set_drvdata(pdev, crtc_list); + + return 0; +} + +static void vc4_fkms_unbind(struct device *dev, struct device *master, + void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct vc4_fkms_crtc **crtc_list = dev_get_drvdata(dev); + int i; + + for (i = 0; crtc_list[i]; i++) { + vc4_fkms_connector_destroy(crtc_list[i]->connector); + vc4_fkms_encoder_destroy(crtc_list[i]->encoder); + drm_crtc_cleanup(&crtc_list[i]->base); + } + + platform_set_drvdata(pdev, NULL); +} + +static const struct component_ops vc4_fkms_ops = { + .bind = vc4_fkms_bind, + .unbind = vc4_fkms_unbind, +}; + +static int vc4_fkms_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &vc4_fkms_ops); +} + +static void vc4_fkms_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &vc4_fkms_ops); +} + +struct platform_driver vc4_firmware_kms_driver = { + .probe = vc4_fkms_probe, + .remove = vc4_fkms_remove, + .driver = { + .name = "vc4_firmware_kms", + .of_match_table = vc4_firmware_kms_dt_match, + }, +}; diff --git a/drivers/gpu/drm/vc4/vc4_gem.c b/drivers/gpu/drm/vc4/vc4_gem.c index be9c0b72ebe869..22bccd69eb6299 100644 --- a/drivers/gpu/drm/vc4/vc4_gem.c +++ b/drivers/gpu/drm/vc4/vc4_gem.c @@ -76,7 +76,7 @@ vc4_get_hang_state_ioctl(struct drm_device *dev, void *data, u32 i; int ret = 0; - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return -ENODEV; if (!vc4->v3d) { @@ -389,7 +389,7 @@ vc4_wait_for_seqno(struct drm_device *dev, uint64_t seqno, uint64_t timeout_ns, unsigned long timeout_expire; DEFINE_WAIT(wait); - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return -ENODEV; if (vc4->finished_seqno >= seqno) @@ -474,7 +474,7 @@ vc4_submit_next_bin_job(struct drm_device *dev) struct vc4_dev *vc4 = to_vc4_dev(dev); struct vc4_exec_info *exec; - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return; again: @@ -522,7 +522,7 @@ vc4_submit_next_render_job(struct drm_device *dev) if (!exec) return; - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return; /* A previous RCL may have written to one of our textures, and @@ -543,7 +543,7 @@ vc4_move_job_to_render(struct drm_device *dev, struct vc4_exec_info *exec) struct vc4_dev *vc4 = to_vc4_dev(dev); bool was_empty = list_empty(&vc4->render_job_list); - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return; list_move_tail(&exec->head, &vc4->render_job_list); @@ -970,7 +970,7 @@ vc4_job_handle_completed(struct vc4_dev *vc4) unsigned long irqflags; struct vc4_seqno_cb *cb, *cb_temp; - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return; spin_lock_irqsave(&vc4->job_lock, irqflags); @@ -1009,7 +1009,7 @@ int vc4_queue_seqno_cb(struct drm_device *dev, struct vc4_dev *vc4 = to_vc4_dev(dev); unsigned long irqflags; - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return -ENODEV; cb->func = func; @@ -1065,7 +1065,7 @@ vc4_wait_seqno_ioctl(struct drm_device *dev, void *data, struct vc4_dev *vc4 = to_vc4_dev(dev); struct drm_vc4_wait_seqno *args = data; - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return -ENODEV; return vc4_wait_for_seqno_ioctl_helper(dev, args->seqno, @@ -1082,7 +1082,7 @@ vc4_wait_bo_ioctl(struct drm_device *dev, void *data, struct drm_gem_object *gem_obj; struct vc4_bo *bo; - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return -ENODEV; if (args->pad != 0) @@ -1131,7 +1131,7 @@ vc4_submit_cl_ioctl(struct drm_device *dev, void *data, args->shader_rec_size, args->bo_handle_count); - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return -ENODEV; if (!vc4->v3d) { @@ -1267,7 +1267,7 @@ int vc4_gem_init(struct drm_device *dev) struct vc4_dev *vc4 = to_vc4_dev(dev); int ret; - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return -ENODEV; vc4->dma_fence_context = dma_fence_context_alloc(1); @@ -1326,7 +1326,7 @@ int vc4_gem_madvise_ioctl(struct drm_device *dev, void *data, struct vc4_bo *bo; int ret; - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return -ENODEV; switch (args->madv) { diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index 6b83d02b5d62a5..206ecf42e09c69 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -43,6 +43,8 @@ #include <linux/component.h> #include <linux/gpio/consumer.h> #include <linux/i2c.h> +#include <linux/module.h> +#include <linux/moduleparam.h> #include <linux/of.h> #include <linux/of_address.h> #include <linux/pm_runtime.h> @@ -50,6 +52,7 @@ #include <linux/reset.h> #include <sound/dmaengine_pcm.h> #include <sound/hdmi-codec.h> +#include <sound/jack.h> #include <sound/pcm_drm_eld.h> #include <sound/pcm_params.h> #include <sound/soc.h> @@ -111,6 +114,10 @@ #define HDMI_14_MAX_TMDS_CLK (340 * 1000 * 1000) +/* bit field to force hotplug detection. bit0 = HDMI0 */ +static int force_hotplug; +module_param(force_hotplug, int, 0644); + static bool vc4_hdmi_supports_scrambling(struct vc4_hdmi *vc4_hdmi) { struct drm_display_info *display = &vc4_hdmi->connector.display_info; @@ -383,7 +390,7 @@ static void vc4_hdmi_handle_hotplug(struct vc4_hdmi *vc4_hdmi, enum drm_connector_status status) { struct drm_connector *connector = &vc4_hdmi->connector; - const struct drm_edid *drm_edid; + const struct drm_edid *drm_edid = NULL; int ret; /* @@ -400,13 +407,25 @@ static void vc4_hdmi_handle_hotplug(struct vc4_hdmi *vc4_hdmi, * the lock for now. */ + if (status != connector_status_disconnected) + drm_edid = drm_edid_read_ddc(connector, vc4_hdmi->ddc); + + /* + * Report plugged/unplugged events to ALSA jack detection. Do this + * *after* EDID probing, otherwise userspace might try to bring up + * audio before it's ready. + */ + mutex_lock(&vc4_hdmi->update_plugged_status_lock); + if (vc4_hdmi->plugged_cb && vc4_hdmi->codec_dev) + vc4_hdmi->plugged_cb(vc4_hdmi->codec_dev, + status != connector_status_disconnected); + mutex_unlock(&vc4_hdmi->update_plugged_status_lock); + if (status == connector_status_disconnected) { cec_phys_addr_invalidate(vc4_hdmi->cec_adap); return; } - drm_edid = drm_edid_read_ddc(connector, vc4_hdmi->ddc); - drm_edid_connector_update(connector, drm_edid); cec_s_phys_addr(vc4_hdmi->cec_adap, connector->display_info.source_physical_address, false); @@ -453,7 +472,9 @@ static int vc4_hdmi_connector_detect_ctx(struct drm_connector *connector, return connector_status_unknown; } - if (vc4_hdmi->hpd_gpio) { + if (force_hotplug & BIT(vc4_hdmi->encoder.type - VC4_ENCODER_TYPE_HDMI0)) + status = connector_status_connected; + else if (vc4_hdmi->hpd_gpio) { if (gpiod_get_value_cansleep(vc4_hdmi->hpd_gpio)) status = connector_status_connected; } else { @@ -751,6 +772,24 @@ static int vc4_hdmi_write_infoframe(struct drm_connector *connector, return ret; } +static int vc4_hdmi_clear_infoframe(struct drm_connector *connector, + enum hdmi_infoframe_type type) +{ + struct vc4_hdmi *vc4_hdmi = connector_to_vc4_hdmi(connector); + struct drm_device *drm = connector->dev; + int ret; + int idx; + + if (!drm_dev_enter(drm, &idx)) + return 0; + + ret = vc4_hdmi_stop_packet(vc4_hdmi, type, true); + if (ret) + drm_err(drm, "Failed to wait for infoframe to go idle: %d\n", ret); + + drm_dev_exit(idx); + return ret; +} #define SCRAMBLING_POLLING_DELAY_MS 1000 static void vc4_hdmi_enable_scrambling(struct drm_encoder *encoder) @@ -845,6 +884,7 @@ static void vc4_hdmi_encoder_post_crtc_disable(struct drm_encoder *encoder, { struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); struct drm_device *drm = vc4_hdmi->connector.dev; + struct vc4_dev *vc4 = to_vc4_dev(drm); unsigned long flags; int idx; @@ -861,14 +901,25 @@ static void vc4_hdmi_encoder_post_crtc_disable(struct drm_encoder *encoder, HDMI_WRITE(HDMI_VID_CTL, HDMI_READ(HDMI_VID_CTL) | VC4_HD_VID_CTL_CLRRGB); + if (vc4->gen >= VC4_GEN_6_C) + HDMI_WRITE(HDMI_VID_CTL, HDMI_READ(HDMI_VID_CTL) | + VC4_HD_VID_CTL_BLANKPIX); + spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags); mdelay(1); - spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); - HDMI_WRITE(HDMI_VID_CTL, - HDMI_READ(HDMI_VID_CTL) & ~VC4_HD_VID_CTL_ENABLE); - spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags); + /* + * TODO: This should work on BCM2712, but doesn't for some + * reason and result in a system lockup. + */ + if (vc4->gen < VC4_GEN_6_C) { + spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); + HDMI_WRITE(HDMI_VID_CTL, + HDMI_READ(HDMI_VID_CTL) & + ~VC4_HD_VID_CTL_ENABLE); + spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags); + } vc4_hdmi_disable_scrambling(encoder); @@ -900,6 +951,8 @@ static void vc4_hdmi_encoder_post_crtc_powerdown(struct drm_encoder *encoder, if (vc4_hdmi->variant->phy_disable) vc4_hdmi->variant->phy_disable(vc4_hdmi); + /* we no longer require a minimum clock rate */ + clk_set_min_rate(vc4_hdmi->pixel_bvb_clock, 0); clk_disable_unprepare(vc4_hdmi->pixel_bvb_clock); clk_disable_unprepare(vc4_hdmi->pixel_clock); @@ -1488,7 +1541,6 @@ static void vc4_hdmi_encoder_pre_crtc_configure(struct drm_encoder *encoder, goto err_put_runtime_pm; } - vc4_hdmi_cec_update_clk_div(vc4_hdmi); if (tmds_char_rate > 297000000) @@ -1594,10 +1646,13 @@ static void vc4_hdmi_encoder_post_crtc_enable(struct drm_encoder *encoder, spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); HDMI_WRITE(HDMI_VID_CTL, + (HDMI_READ(HDMI_VID_CTL) & + ~(VC4_HD_VID_CTL_VSYNC_LOW | VC4_HD_VID_CTL_HSYNC_LOW)) | VC4_HD_VID_CTL_ENABLE | VC4_HD_VID_CTL_CLRRGB | VC4_HD_VID_CTL_UNDERFLOW_ENABLE | VC4_HD_VID_CTL_FRAME_COUNTER_RESET | + VC4_HD_VID_CTL_BLANK_INSERT_EN | (vsync_pos ? 0 : VC4_HD_VID_CTL_VSYNC_LOW) | (hsync_pos ? 0 : VC4_HD_VID_CTL_HSYNC_LOW)); @@ -1695,6 +1750,7 @@ vc4_hdmi_connector_clock_valid(const struct drm_connector *connector, static const struct drm_connector_hdmi_funcs vc4_hdmi_hdmi_connector_funcs = { .tmds_char_rate_valid = vc4_hdmi_connector_clock_valid, .write_infoframe = vc4_hdmi_write_infoframe, + .clear_infoframe = vc4_hdmi_clear_infoframe, }; #define WIFI_2_4GHz_CH1_MIN_FREQ 2400000000ULL @@ -1709,7 +1765,9 @@ static int vc4_hdmi_encoder_atomic_check(struct drm_encoder *encoder, unsigned long long tmds_char_rate = mode->clock * 1000; unsigned long long tmds_bit_rate; - if (vc4_hdmi->variant->unsupported_odd_h_timings) { + if (vc4_hdmi->variant->unsupported_odd_h_timings || + (vc4_hdmi->variant->unsupported_int_odd_h_timings && + (mode->flags & DRM_MODE_FLAG_INTERLACE))) { if (mode->flags & DRM_MODE_FLAG_DBLCLK) { /* Only try to fixup DBLCLK modes to get 480i and 576i * working. @@ -2109,18 +2167,33 @@ static int vc4_hdmi_audio_prepare(struct device *dev, void *data, VC4_HDMI_AUDIO_PACKET_CEA_MASK); /* Set the MAI threshold */ - if (vc4->gen >= VC4_GEN_5) + switch (vc4->gen) { + case VC4_GEN_6_D: + HDMI_WRITE(HDMI_MAI_THR, + VC4_SET_FIELD(0x10, VC6_D_HD_MAI_THR_PANICHIGH) | + VC4_SET_FIELD(0x10, VC6_D_HD_MAI_THR_PANICLOW) | + VC4_SET_FIELD(0x1c, VC6_D_HD_MAI_THR_DREQHIGH) | + VC4_SET_FIELD(0x1c, VC6_D_HD_MAI_THR_DREQLOW)); + break; + case VC4_GEN_6_C: + case VC4_GEN_5: HDMI_WRITE(HDMI_MAI_THR, VC4_SET_FIELD(0x10, VC4_HD_MAI_THR_PANICHIGH) | VC4_SET_FIELD(0x10, VC4_HD_MAI_THR_PANICLOW) | VC4_SET_FIELD(0x1c, VC4_HD_MAI_THR_DREQHIGH) | VC4_SET_FIELD(0x1c, VC4_HD_MAI_THR_DREQLOW)); - else + break; + case VC4_GEN_4: HDMI_WRITE(HDMI_MAI_THR, VC4_SET_FIELD(0x8, VC4_HD_MAI_THR_PANICHIGH) | VC4_SET_FIELD(0x8, VC4_HD_MAI_THR_PANICLOW) | VC4_SET_FIELD(0x6, VC4_HD_MAI_THR_DREQHIGH) | VC4_SET_FIELD(0x8, VC4_HD_MAI_THR_DREQLOW)); + break; + default: + drm_err(drm, "Unknown VC4 generation: %d", vc4->gen); + break; + } HDMI_WRITE(HDMI_MAI_CONFIG, VC4_HDMI_MAI_CONFIG_BIT_REVERSE | @@ -2199,8 +2272,23 @@ static int vc4_hdmi_audio_get_eld(struct device *dev, void *data, return 0; } +static int vc4_hdmi_audio_hook_plugged_cb(struct device *dev, void *data, + hdmi_codec_plugged_cb fn, + struct device *codec_dev) +{ + struct vc4_hdmi *vc4_hdmi = dev_get_drvdata(dev); + + mutex_lock(&vc4_hdmi->update_plugged_status_lock); + vc4_hdmi->plugged_cb = fn; + vc4_hdmi->codec_dev = codec_dev; + mutex_unlock(&vc4_hdmi->update_plugged_status_lock); + + return 0; +} + static const struct hdmi_codec_ops vc4_hdmi_codec_ops = { .get_eld = vc4_hdmi_audio_get_eld, + .hook_plugged_cb = vc4_hdmi_audio_hook_plugged_cb, .prepare = vc4_hdmi_audio_prepare, .audio_shutdown = vc4_hdmi_audio_shutdown, .audio_startup = vc4_hdmi_audio_startup, @@ -2220,6 +2308,22 @@ static void vc4_hdmi_audio_codec_release(void *ptr) vc4_hdmi->audio.codec_pdev = NULL; } +static int vc4_hdmi_codec_init(struct snd_soc_pcm_runtime *rtd) +{ + struct vc4_hdmi *vc4_hdmi = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + int ret; + + ret = snd_soc_card_jack_new(rtd->card, "HDMI Jack", SND_JACK_LINEOUT, + &vc4_hdmi->hdmi_jack); + if (ret) { + dev_err(rtd->dev, "HDMI Jack creation failed: %d\n", ret); + return ret; + } + + return snd_soc_component_set_jack(component, &vc4_hdmi->hdmi_jack, NULL); +} + static int vc4_hdmi_audio_init(struct vc4_hdmi *vc4_hdmi) { const struct vc4_hdmi_register *mai_data = @@ -2228,7 +2332,7 @@ static int vc4_hdmi_audio_init(struct vc4_hdmi *vc4_hdmi) struct snd_soc_card *card = &vc4_hdmi->audio.card; struct device *dev = &vc4_hdmi->pdev->dev; struct platform_device *codec_pdev; - const __be32 *addr; + struct resource *iomem; int index, len; int ret; @@ -2264,22 +2368,18 @@ static int vc4_hdmi_audio_init(struct vc4_hdmi *vc4_hdmi) } /* - * Get the physical address of VC4_HD_MAI_DATA. We need to retrieve - * the bus address specified in the DT, because the physical address - * (the one returned by platform_get_resource()) is not appropriate - * for DMA transfers. - * This VC/MMU should probably be exposed to avoid this kind of hacks. + * Get the physical address of VC4_HD_MAI_DATA. */ index = of_property_match_string(dev->of_node, "reg-names", "hd"); /* Before BCM2711, we don't have a named register range */ if (index < 0) index = 1; - addr = of_get_address(dev->of_node, index, NULL, NULL); - if (!addr) + iomem = platform_get_resource(vc4_hdmi->pdev, IORESOURCE_MEM, index); + if (!iomem) return -EINVAL; - vc4_hdmi->audio.dma_data.addr = be32_to_cpup(addr) + mai_data->offset; + vc4_hdmi->audio.dma_data.addr = iomem->start + mai_data->offset; vc4_hdmi->audio.dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; vc4_hdmi->audio.dma_data.maxburst = 2; @@ -2349,6 +2449,8 @@ static int vc4_hdmi_audio_init(struct vc4_hdmi *vc4_hdmi) dai_link->codecs->name = dev_name(&codec_pdev->dev); dai_link->platforms->name = dev_name(dev); + dai_link->init = vc4_hdmi_codec_init; + card->dai_link = dai_link; card->num_links = 1; card->name = vc4_hdmi->variant->card_name; @@ -2378,7 +2480,7 @@ static irqreturn_t vc4_hdmi_hpd_irq_thread(int irq, void *priv) struct drm_connector *connector = &vc4_hdmi->connector; struct drm_device *dev = connector->dev; - if (dev && dev->registered) + if (dev && dev->registered && !force_hotplug) drm_connector_helper_hpd_irq_event(connector); return IRQ_HANDLED; @@ -3120,6 +3222,9 @@ static int vc4_hdmi_runtime_suspend(struct device *dev) { struct vc4_hdmi *vc4_hdmi = dev_get_drvdata(dev); + clk_disable_unprepare(vc4_hdmi->audio_clock); + /* we no longer require a minimum clock rate */ + clk_set_min_rate(vc4_hdmi->hsm_clock, 0); clk_disable_unprepare(vc4_hdmi->hsm_clock); return 0; @@ -3152,6 +3257,10 @@ static int vc4_hdmi_runtime_resume(struct device *dev) goto err_disable_clk; } + ret = clk_prepare_enable(vc4_hdmi->audio_clock); + if (ret) + goto err_disable_clk; + if (vc4_hdmi->variant->reset) vc4_hdmi->variant->reset(vc4_hdmi); @@ -3204,6 +3313,8 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) if (ret) return ret; + mutex_init(&vc4_hdmi->update_plugged_status_lock); + spin_lock_init(&vc4_hdmi->hw_lock); INIT_DELAYED_WORK(&vc4_hdmi->scrambling_work, vc4_hdmi_scrambling_wq); @@ -3240,7 +3351,7 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) vc4_hdmi->ddc = of_find_i2c_adapter_by_node(ddc_node); of_node_put(ddc_node); if (!vc4_hdmi->ddc) { - drm_err(drm, "Failed to get ddc i2c adapter by node\n"); + drm_dbg(drm, "Failed to get ddc i2c adapter by node\n"); return -EPROBE_DEFER; } @@ -3272,7 +3383,9 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) return ret; if ((of_device_is_compatible(dev->of_node, "brcm,bcm2711-hdmi0") || - of_device_is_compatible(dev->of_node, "brcm,bcm2711-hdmi1")) && + of_device_is_compatible(dev->of_node, "brcm,bcm2711-hdmi1") || + of_device_is_compatible(dev->of_node, "brcm,bcm2712-hdmi0") || + of_device_is_compatible(dev->of_node, "brcm,bcm2712-hdmi1")) && HDMI_READ(HDMI_VID_CTL) & VC4_HD_VID_CTL_ENABLE) { clk_prepare_enable(vc4_hdmi->pixel_clock); clk_prepare_enable(vc4_hdmi->hsm_clock); @@ -3314,8 +3427,16 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) return ret; } +static void vc4_hdmi_unbind(struct device *dev, struct device *master, void *data) +{ + struct vc4_hdmi *vc4_hdmi = dev_get_drvdata(dev); + + mutex_destroy(&vc4_hdmi->update_plugged_status_lock); +} + static const struct component_ops vc4_hdmi_ops = { .bind = vc4_hdmi_bind, + .unbind = vc4_hdmi_unbind, }; static int vc4_hdmi_dev_probe(struct platform_device *pdev) @@ -3362,6 +3483,7 @@ static const struct vc4_hdmi_variant bcm2711_hdmi0_variant = { PHY_LANE_CK, }, .unsupported_odd_h_timings = true, + .unsupported_int_odd_h_timings = true, .external_irq_controller = true, .init_resources = vc5_hdmi_init_resources, @@ -3391,6 +3513,7 @@ static const struct vc4_hdmi_variant bcm2711_hdmi1_variant = { PHY_LANE_2, }, .unsupported_odd_h_timings = true, + .unsupported_int_odd_h_timings = true, .external_irq_controller = true, .init_resources = vc5_hdmi_init_resources, @@ -3406,10 +3529,68 @@ static const struct vc4_hdmi_variant bcm2711_hdmi1_variant = { .hp_detect = vc5_hdmi_hp_detect, }; +static const struct vc4_hdmi_variant bcm2712_hdmi0_variant = { + .encoder_type = VC4_ENCODER_TYPE_HDMI0, + .debugfs_name = "hdmi0_regs", + .card_name = "vc4-hdmi-0", + .max_pixel_clock = 600000000, + .registers = vc6_hdmi_hdmi0_fields, + .num_registers = ARRAY_SIZE(vc6_hdmi_hdmi0_fields), + .phy_lane_mapping = { + PHY_LANE_0, + PHY_LANE_1, + PHY_LANE_2, + PHY_LANE_CK, + }, + .unsupported_odd_h_timings = false, + .unsupported_int_odd_h_timings = true, + .external_irq_controller = true, + + .init_resources = vc5_hdmi_init_resources, + .csc_setup = vc5_hdmi_csc_setup, + .reset = vc5_hdmi_reset, + .set_timings = vc5_hdmi_set_timings, + .phy_init = vc6_hdmi_phy_init, + .phy_disable = vc6_hdmi_phy_disable, + .channel_map = vc5_hdmi_channel_map, + .supports_hdr = true, + .hp_detect = vc5_hdmi_hp_detect, +}; + +static const struct vc4_hdmi_variant bcm2712_hdmi1_variant = { + .encoder_type = VC4_ENCODER_TYPE_HDMI1, + .debugfs_name = "hdmi1_regs", + .card_name = "vc4-hdmi-1", + .max_pixel_clock = 600000000, + .registers = vc6_hdmi_hdmi1_fields, + .num_registers = ARRAY_SIZE(vc6_hdmi_hdmi1_fields), + .phy_lane_mapping = { + PHY_LANE_0, + PHY_LANE_1, + PHY_LANE_2, + PHY_LANE_CK, + }, + .unsupported_odd_h_timings = false, + .unsupported_int_odd_h_timings = true, + .external_irq_controller = true, + + .init_resources = vc5_hdmi_init_resources, + .csc_setup = vc5_hdmi_csc_setup, + .reset = vc5_hdmi_reset, + .set_timings = vc5_hdmi_set_timings, + .phy_init = vc6_hdmi_phy_init, + .phy_disable = vc6_hdmi_phy_disable, + .channel_map = vc5_hdmi_channel_map, + .supports_hdr = true, + .hp_detect = vc5_hdmi_hp_detect, +}; + static const struct of_device_id vc4_hdmi_dt_match[] = { { .compatible = "brcm,bcm2835-hdmi", .data = &bcm2835_variant }, { .compatible = "brcm,bcm2711-hdmi0", .data = &bcm2711_hdmi0_variant }, { .compatible = "brcm,bcm2711-hdmi1", .data = &bcm2711_hdmi1_variant }, + { .compatible = "brcm,bcm2712-hdmi0", .data = &bcm2712_hdmi0_variant }, + { .compatible = "brcm,bcm2712-hdmi1", .data = &bcm2712_hdmi1_variant }, {} }; diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.h b/drivers/gpu/drm/vc4/vc4_hdmi.h index b37f1d2c3fe5e9..fb13f355d123e5 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.h +++ b/drivers/gpu/drm/vc4/vc4_hdmi.h @@ -2,8 +2,10 @@ #define _VC4_HDMI_H_ #include <drm/drm_connector.h> +#include <linux/mutex.h> #include <media/cec.h> #include <sound/dmaengine_pcm.h> +#include <sound/hdmi-codec.h> #include <sound/soc.h> #include "vc4_drv.h" @@ -46,6 +48,10 @@ struct vc4_hdmi_variant { /* The BCM2711 cannot deal with odd horizontal pixel timings */ bool unsupported_odd_h_timings; + /* The BCM2712 can handle odd horizontal pixel timings, but not in + * interlaced modes + */ + bool unsupported_int_odd_h_timings; /* * The BCM2711 CEC/hotplug IRQ controller is shared between the @@ -213,6 +219,31 @@ struct vc4_hdmi { * KMS hooks. Protected by @mutex. */ enum hdmi_colorspace output_format; + + /** + * @plugged_cb: Callback provided by hdmi-codec to indicate that an + * HDMI hotplug occurred and jack state should be updated. Protected by + * @update_plugged_status_lock. + */ + hdmi_codec_plugged_cb plugged_cb; + + /** + * @plugged_cb: Context for plugged_cb. Protected by + * @update_plugged_status_lock. + */ + struct device *codec_dev; + + /** + * @update_plugged_status_lock: Prevents a race condition where an HDMI + * hotplug might occur between @plugged_cb and @codec_dev being set. + */ + struct mutex update_plugged_status_lock; + + /** + * @hdmi_jack: Represents the connection state of the HDMI plug, for + * ALSA jack detection. + */ + struct snd_soc_jack hdmi_jack; }; #define connector_to_vc4_hdmi(_connector) \ @@ -237,4 +268,8 @@ void vc5_hdmi_phy_disable(struct vc4_hdmi *vc4_hdmi); void vc5_hdmi_phy_rng_enable(struct vc4_hdmi *vc4_hdmi); void vc5_hdmi_phy_rng_disable(struct vc4_hdmi *vc4_hdmi); +void vc6_hdmi_phy_init(struct vc4_hdmi *vc4_hdmi, + struct drm_connector_state *conn_state); +void vc6_hdmi_phy_disable(struct vc4_hdmi *vc4_hdmi); + #endif /* _VC4_HDMI_H_ */ diff --git a/drivers/gpu/drm/vc4/vc4_hdmi_phy.c b/drivers/gpu/drm/vc4/vc4_hdmi_phy.c index 1f5507fc7a03e4..4a1ab59d8810ee 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi_phy.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi_phy.c @@ -125,6 +125,49 @@ #define VC4_HDMI_RM_FORMAT_SHIFT_SHIFT 24 #define VC4_HDMI_RM_FORMAT_SHIFT_MASK VC4_MASK(25, 24) +#define VC6_HDMI_TX_PHY_HDMI_POWERUP_CTL_BG_PWRUP BIT(8) +#define VC6_HDMI_TX_PHY_HDMI_POWERUP_CTL_LDO_PWRUP BIT(7) +#define VC6_HDMI_TX_PHY_HDMI_POWERUP_CTL_BIAS_PWRUP BIT(6) +#define VC6_HDMI_TX_PHY_HDMI_POWERUP_CTL_RNDGEN_PWRUP BIT(4) +#define VC6_HDMI_TX_PHY_HDMI_POWERUP_CTL_TX_CK_PWRUP BIT(3) +#define VC6_HDMI_TX_PHY_HDMI_POWERUP_CTL_TX_2_PWRUP BIT(2) +#define VC6_HDMI_TX_PHY_HDMI_POWERUP_CTL_TX_1_PWRUP BIT(1) +#define VC6_HDMI_TX_PHY_HDMI_POWERUP_CTL_TX_0_PWRUP BIT(0) + +#define VC6_HDMI_TX_PHY_PLL_REFCLK_REFCLK_SEL_CMOS BIT(13) +#define VC6_HDMI_TX_PHY_PLL_REFCLK_REFFRQ_MASK VC4_MASK(9, 0) + +#define VC6_HDMI_TX_PHY_PLL_POST_KDIV_BYPASS_EN BIT(4) +#define VC6_HDMI_TX_PHY_PLL_POST_KDIV_CLK0_SEL_MASK VC4_MASK(3, 2) +#define VC6_HDMI_TX_PHY_PLL_POST_KDIV_KDIV_MASK VC4_MASK(1, 0) + +#define VC6_HDMI_TX_PHY_PLL_VCOCLK_DIV_VCODIV_EN BIT(10) +#define VC6_HDMI_TX_PHY_PLL_VCOCLK_DIV_VCODIV_MASK VC4_MASK(9, 0) + +#define VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_EXT_CURRENT_CTL_MASK VC4_MASK(31, 28) +#define VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_FFE_ENABLE_MASK VC4_MASK(27, 27) +#define VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_SLEW_RATE_CTL_MASK VC4_MASK(26, 26) +#define VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_FFE_POST_TAP_EN_MASK VC4_MASK(25, 25) +#define VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_LDMOS_BIAS_CTL_MASK VC4_MASK(24, 23) +#define VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_COM_MODE_LDMOS_EN_MASK VC4_MASK(22, 22) +#define VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_EDGE_SEL_MASK VC4_MASK(21, 21) +#define VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_EXT_CURRENT_SRC_HS_EN_MASK VC4_MASK(20, 20) +#define VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_TERM_CTL_MASK VC4_MASK(19, 18) +#define VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_EXT_CURRENT_SRC_EN_MASK VC4_MASK(17, 17) +#define VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_INT_CURRENT_SRC_EN_MASK VC4_MASK(16, 16) +#define VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_INT_CURRENT_CTL_MASK VC4_MASK(15, 12) +#define VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_INT_CURRENT_SRC_HS_EN_MASK VC4_MASK(11, 11) +#define VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_MAIN_TAP_CURRENT_SELECT_MASK VC4_MASK(10, 8) +#define VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_POST_TAP_CURRENT_SELECT_MASK VC4_MASK(7, 5) +#define VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_SLEW_CTL_SLOW_LOADING_MASK VC4_MASK(4, 3) +#define VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_SLEW_CTL_SLOW_DRIVING_MASK VC4_MASK(2, 1) +#define VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_FFE_PRE_TAP_EN_MASK VC4_MASK(0, 0) + +#define VC6_HDMI_TX_PHY_PLL_RESET_CTL_PLL_PLLPOST_RESETB BIT(1) +#define VC6_HDMI_TX_PHY_PLL_RESET_CTL_PLL_RESETB BIT(0) + +#define VC6_HDMI_TX_PHY_PLL_POWERUP_CTL_PLL_PWRUP BIT(0) + #define OSCILLATOR_FREQUENCY 54000000 void vc4_hdmi_phy_init(struct vc4_hdmi *vc4_hdmi, @@ -558,3 +601,607 @@ void vc5_hdmi_phy_rng_disable(struct vc4_hdmi *vc4_hdmi) VC4_HDMI_TX_PHY_POWERDOWN_CTL_RNDGEN_PWRDN); spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags); } + +#define VC6_VCO_MIN_FREQ (8ULL * 1000 * 1000 * 1000) +#define VC6_VCO_MAX_FREQ (12ULL * 1000 * 1000 * 1000) + +static unsigned long long +vc6_phy_get_vco_freq(unsigned long long tmds_rate, unsigned int *vco_div) +{ + unsigned int min_div; + unsigned int max_div; + unsigned int div; + + div = 0; + while (tmds_rate * div * 10 < VC6_VCO_MIN_FREQ) + div++; + min_div = div; + + while (tmds_rate * (div + 1) * 10 < VC6_VCO_MAX_FREQ) + div++; + max_div = div; + + div = min_div + (max_div - min_div) / 2; + + *vco_div = div; + return tmds_rate * div * 10; +} + +struct vc6_phy_lane_settings { + unsigned int ext_current_ctl:4; + unsigned int ffe_enable:1; + unsigned int slew_rate_ctl:1; + unsigned int ffe_post_tap_en:1; + unsigned int ldmos_bias_ctl:2; + unsigned int com_mode_ldmos_en:1; + unsigned int edge_sel:1; + unsigned int ext_current_src_hs_en:1; + unsigned int term_ctl:2; + unsigned int ext_current_src_en:1; + unsigned int int_current_src_en:1; + unsigned int int_current_ctl:4; + unsigned int int_current_src_hs_en:1; + unsigned int main_tap_current_select:3; + unsigned int post_tap_current_select:3; + unsigned int slew_ctl_slow_loading:2; + unsigned int slew_ctl_slow_driving:2; + unsigned int ffe_pre_tap_en:1; +}; + +struct vc6_phy_settings { + unsigned long long min_rate; + unsigned long long max_rate; + struct vc6_phy_lane_settings channel[3]; + struct vc6_phy_lane_settings clock; +}; + +static const struct vc6_phy_settings vc6_hdmi_phy_settings[] = { + { + 0, 222000000, + { + { + /* 200mA */ + .ext_current_ctl = 8, + + /* 0.85V */ + .ldmos_bias_ctl = 1, + + /* Enable External Current Source */ + .ext_current_src_en = 1, + + /* 200mA */ + .int_current_ctl = 8, + + /* 17.6 mA */ + .main_tap_current_select = 7, + }, + { + /* 200mA */ + .ext_current_ctl = 8, + + /* 0.85V */ + .ldmos_bias_ctl = 1, + + /* Enable External Current Source */ + .ext_current_src_en = 1, + + /* 200mA */ + .int_current_ctl = 8, + + /* 17.6 mA */ + .main_tap_current_select = 7, + }, + { + /* 200mA */ + .ext_current_ctl = 8, + + /* 0.85V */ + .ldmos_bias_ctl = 1, + + /* Enable External Current Source */ + .ext_current_src_en = 1, + + /* 200mA */ + .int_current_ctl = 8, + + /* 17.6 mA */ + .main_tap_current_select = 7, + }, + }, + { + /* 200mA */ + .ext_current_ctl = 8, + + /* 0.85V */ + .ldmos_bias_ctl = 1, + + /* Enable External Current Source */ + .ext_current_src_en = 1, + + /* 200mA */ + .int_current_ctl = 8, + + /* 17.6 mA */ + .main_tap_current_select = 7, + }, + }, + { + 222000001, 297000000, + { + { + /* 200mA and 180mA ?! */ + .ext_current_ctl = 12, + + /* 0.85V */ + .ldmos_bias_ctl = 1, + + /* 100 Ohm */ + .term_ctl = 1, + + /* Enable External Current Source */ + .ext_current_src_en = 1, + + /* Enable Internal Current Source */ + .int_current_src_en = 1, + }, + { + /* 200mA and 180mA ?! */ + .ext_current_ctl = 12, + + /* 0.85V */ + .ldmos_bias_ctl = 1, + + /* 100 Ohm */ + .term_ctl = 1, + + /* Enable External Current Source */ + .ext_current_src_en = 1, + + /* Enable Internal Current Source */ + .int_current_src_en = 1, + }, + { + /* 200mA and 180mA ?! */ + .ext_current_ctl = 12, + + /* 0.85V */ + .ldmos_bias_ctl = 1, + + /* 100 Ohm */ + .term_ctl = 1, + + /* Enable External Current Source */ + .ext_current_src_en = 1, + + /* Enable Internal Current Source */ + .int_current_src_en = 1, + }, + }, + { + /* 200mA and 180mA ?! */ + .ext_current_ctl = 12, + + /* 0.85V */ + .ldmos_bias_ctl = 1, + + /* 100 Ohm */ + .term_ctl = 1, + + /* Enable External Current Source */ + .ext_current_src_en = 1, + + /* Enable Internal Current Source */ + .int_current_src_en = 1, + + /* Internal Current Source Half Swing Enable*/ + .int_current_src_hs_en = 1, + }, + }, + { + 297000001, 597000044, + { + { + /* 200mA */ + .ext_current_ctl = 8, + + /* Normal Slew Rate Control */ + .slew_rate_ctl = 1, + + /* 0.85V */ + .ldmos_bias_ctl = 1, + + /* 50 Ohms */ + .term_ctl = 3, + + /* Enable External Current Source */ + .ext_current_src_en = 1, + + /* Enable Internal Current Source */ + .int_current_src_en = 1, + + /* 200mA */ + .int_current_ctl = 8, + + /* 17.6 mA */ + .main_tap_current_select = 7, + }, + { + /* 200mA */ + .ext_current_ctl = 8, + + /* Normal Slew Rate Control */ + .slew_rate_ctl = 1, + + /* 0.85V */ + .ldmos_bias_ctl = 1, + + /* 50 Ohms */ + .term_ctl = 3, + + /* Enable External Current Source */ + .ext_current_src_en = 1, + + /* Enable Internal Current Source */ + .int_current_src_en = 1, + + /* 200mA */ + .int_current_ctl = 8, + + /* 17.6 mA */ + .main_tap_current_select = 7, + }, + { + /* 200mA */ + .ext_current_ctl = 8, + + /* Normal Slew Rate Control */ + .slew_rate_ctl = 1, + + /* 0.85V */ + .ldmos_bias_ctl = 1, + + /* 50 Ohms */ + .term_ctl = 3, + + /* Enable External Current Source */ + .ext_current_src_en = 1, + + /* Enable Internal Current Source */ + .int_current_src_en = 1, + + /* 200mA */ + .int_current_ctl = 8, + + /* 17.6 mA */ + .main_tap_current_select = 7, + }, + }, + { + /* 200mA */ + .ext_current_ctl = 8, + + /* Normal Slew Rate Control */ + .slew_rate_ctl = 1, + + /* 0.85V */ + .ldmos_bias_ctl = 1, + + /* External Current Source Half Swing Enable*/ + .ext_current_src_hs_en = 1, + + /* 50 Ohms */ + .term_ctl = 3, + + /* Enable External Current Source */ + .ext_current_src_en = 1, + + /* Enable Internal Current Source */ + .int_current_src_en = 1, + + /* 200mA */ + .int_current_ctl = 8, + + /* Internal Current Source Half Swing Enable*/ + .int_current_src_hs_en = 1, + + /* 17.6 mA */ + .main_tap_current_select = 7, + }, + }, +}; + +static const struct vc6_phy_settings * +vc6_phy_get_settings(unsigned long long tmds_rate) +{ + unsigned int count = ARRAY_SIZE(vc6_hdmi_phy_settings); + unsigned int i; + + for (i = 0; i < count; i++) { + const struct vc6_phy_settings *s = &vc6_hdmi_phy_settings[i]; + + if (tmds_rate >= s->min_rate && tmds_rate <= s->max_rate) + return s; + } + + /* + * If the pixel clock exceeds our max setting, try the max + * setting anyway. + */ + return &vc6_hdmi_phy_settings[count - 1]; +} + +static const struct vc6_phy_lane_settings * +vc6_phy_get_channel_settings(enum vc4_hdmi_phy_channel chan, + unsigned long long tmds_rate) +{ + const struct vc6_phy_settings *settings = vc6_phy_get_settings(tmds_rate); + + if (chan == PHY_LANE_CK) + return &settings->clock; + + return &settings->channel[chan]; +} + +static void vc6_hdmi_reset_phy(struct vc4_hdmi *vc4_hdmi) +{ + lockdep_assert_held(&vc4_hdmi->hw_lock); + + HDMI_WRITE(HDMI_TX_PHY_RESET_CTL, 0); + HDMI_WRITE(HDMI_TX_PHY_POWERUP_CTL, 0); + HDMI_WRITE(HDMI_TX_PHY_PLL_POST_KDIV, VC6_HDMI_TX_PHY_PLL_POST_KDIV_BYPASS_EN); +} + +void vc6_hdmi_phy_init(struct vc4_hdmi *vc4_hdmi, + struct drm_connector_state *conn_state) +{ + const struct vc6_phy_lane_settings *chan0_settings; + const struct vc6_phy_lane_settings *chan1_settings; + const struct vc6_phy_lane_settings *chan2_settings; + const struct vc6_phy_lane_settings *clock_settings; + const struct vc4_hdmi_variant *variant = vc4_hdmi->variant; + unsigned long long pixel_freq = conn_state->hdmi.tmds_char_rate; + unsigned long long vco_freq; + unsigned char word_sel; + unsigned long flags; + unsigned int vco_div; + + vco_freq = vc6_phy_get_vco_freq(pixel_freq, &vco_div); + + spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); + + vc6_hdmi_reset_phy(vc4_hdmi); + + HDMI_WRITE(HDMI_TX_PHY_PLL_MISC_0, 0x810c6000); + HDMI_WRITE(HDMI_TX_PHY_PLL_MISC_1, 0x00b8c451); + HDMI_WRITE(HDMI_TX_PHY_PLL_MISC_2, 0x46402e31); + HDMI_WRITE(HDMI_TX_PHY_PLL_MISC_3, 0x00b8c005); + HDMI_WRITE(HDMI_TX_PHY_PLL_MISC_4, 0x42410261); + HDMI_WRITE(HDMI_TX_PHY_PLL_MISC_5, 0xcc021001); + HDMI_WRITE(HDMI_TX_PHY_PLL_MISC_6, 0xc8301c80); + HDMI_WRITE(HDMI_TX_PHY_PLL_MISC_7, 0xb0804444); + HDMI_WRITE(HDMI_TX_PHY_PLL_MISC_8, 0xf80f8000); + + HDMI_WRITE(HDMI_TX_PHY_PLL_REFCLK, + VC6_HDMI_TX_PHY_PLL_REFCLK_REFCLK_SEL_CMOS | + VC4_SET_FIELD(54, VC6_HDMI_TX_PHY_PLL_REFCLK_REFFRQ)); + + HDMI_WRITE(HDMI_TX_PHY_RESET_CTL, 0x7f); + + HDMI_WRITE(HDMI_RM_OFFSET, + VC4_HDMI_RM_OFFSET_ONLY | + VC4_SET_FIELD(phy_get_rm_offset(vco_freq), + VC4_HDMI_RM_OFFSET_OFFSET)); + + HDMI_WRITE(HDMI_TX_PHY_PLL_VCOCLK_DIV, + VC6_HDMI_TX_PHY_PLL_VCOCLK_DIV_VCODIV_EN | + VC4_SET_FIELD(vco_div, + VC6_HDMI_TX_PHY_PLL_VCOCLK_DIV_VCODIV)); + + HDMI_WRITE(HDMI_TX_PHY_PLL_CFG, + VC4_SET_FIELD(0, VC4_HDMI_TX_PHY_PLL_CFG_PDIV)); + + HDMI_WRITE(HDMI_TX_PHY_PLL_POST_KDIV, + VC4_SET_FIELD(2, VC6_HDMI_TX_PHY_PLL_POST_KDIV_CLK0_SEL) | + VC4_SET_FIELD(1, VC6_HDMI_TX_PHY_PLL_POST_KDIV_KDIV)); + + chan0_settings = + vc6_phy_get_channel_settings(variant->phy_lane_mapping[PHY_LANE_0], + pixel_freq); + HDMI_WRITE(HDMI_TX_PHY_CTL_0, + VC4_SET_FIELD(chan0_settings->ext_current_ctl, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_EXT_CURRENT_CTL) | + VC4_SET_FIELD(chan0_settings->ffe_enable, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_FFE_ENABLE) | + VC4_SET_FIELD(chan0_settings->slew_rate_ctl, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_SLEW_RATE_CTL) | + VC4_SET_FIELD(chan0_settings->ffe_post_tap_en, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_FFE_POST_TAP_EN) | + VC4_SET_FIELD(chan0_settings->ldmos_bias_ctl, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_LDMOS_BIAS_CTL) | + VC4_SET_FIELD(chan0_settings->com_mode_ldmos_en, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_COM_MODE_LDMOS_EN) | + VC4_SET_FIELD(chan0_settings->edge_sel, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_EDGE_SEL) | + VC4_SET_FIELD(chan0_settings->ext_current_src_hs_en, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_EXT_CURRENT_SRC_HS_EN) | + VC4_SET_FIELD(chan0_settings->term_ctl, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_TERM_CTL) | + VC4_SET_FIELD(chan0_settings->ext_current_src_en, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_EXT_CURRENT_SRC_EN) | + VC4_SET_FIELD(chan0_settings->int_current_src_en, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_INT_CURRENT_SRC_EN) | + VC4_SET_FIELD(chan0_settings->int_current_ctl, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_INT_CURRENT_CTL) | + VC4_SET_FIELD(chan0_settings->int_current_src_hs_en, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_INT_CURRENT_SRC_HS_EN) | + VC4_SET_FIELD(chan0_settings->main_tap_current_select, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_MAIN_TAP_CURRENT_SELECT) | + VC4_SET_FIELD(chan0_settings->post_tap_current_select, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_POST_TAP_CURRENT_SELECT) | + VC4_SET_FIELD(chan0_settings->slew_ctl_slow_loading, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_SLEW_CTL_SLOW_LOADING) | + VC4_SET_FIELD(chan0_settings->slew_ctl_slow_driving, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_SLEW_CTL_SLOW_DRIVING) | + VC4_SET_FIELD(chan0_settings->ffe_pre_tap_en, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_FFE_PRE_TAP_EN)); + + chan1_settings = + vc6_phy_get_channel_settings(variant->phy_lane_mapping[PHY_LANE_1], + pixel_freq); + HDMI_WRITE(HDMI_TX_PHY_CTL_1, + VC4_SET_FIELD(chan1_settings->ext_current_ctl, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_EXT_CURRENT_CTL) | + VC4_SET_FIELD(chan1_settings->ffe_enable, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_FFE_ENABLE) | + VC4_SET_FIELD(chan1_settings->slew_rate_ctl, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_SLEW_RATE_CTL) | + VC4_SET_FIELD(chan1_settings->ffe_post_tap_en, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_FFE_POST_TAP_EN) | + VC4_SET_FIELD(chan1_settings->ldmos_bias_ctl, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_LDMOS_BIAS_CTL) | + VC4_SET_FIELD(chan1_settings->com_mode_ldmos_en, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_COM_MODE_LDMOS_EN) | + VC4_SET_FIELD(chan1_settings->edge_sel, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_EDGE_SEL) | + VC4_SET_FIELD(chan1_settings->ext_current_src_hs_en, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_EXT_CURRENT_SRC_HS_EN) | + VC4_SET_FIELD(chan1_settings->term_ctl, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_TERM_CTL) | + VC4_SET_FIELD(chan1_settings->ext_current_src_en, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_EXT_CURRENT_SRC_EN) | + VC4_SET_FIELD(chan1_settings->int_current_src_en, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_INT_CURRENT_SRC_EN) | + VC4_SET_FIELD(chan1_settings->int_current_ctl, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_INT_CURRENT_CTL) | + VC4_SET_FIELD(chan1_settings->int_current_src_hs_en, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_INT_CURRENT_SRC_HS_EN) | + VC4_SET_FIELD(chan1_settings->main_tap_current_select, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_MAIN_TAP_CURRENT_SELECT) | + VC4_SET_FIELD(chan1_settings->post_tap_current_select, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_POST_TAP_CURRENT_SELECT) | + VC4_SET_FIELD(chan1_settings->slew_ctl_slow_loading, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_SLEW_CTL_SLOW_LOADING) | + VC4_SET_FIELD(chan1_settings->slew_ctl_slow_driving, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_SLEW_CTL_SLOW_DRIVING) | + VC4_SET_FIELD(chan1_settings->ffe_pre_tap_en, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_FFE_PRE_TAP_EN)); + + chan2_settings = + vc6_phy_get_channel_settings(variant->phy_lane_mapping[PHY_LANE_2], + pixel_freq); + HDMI_WRITE(HDMI_TX_PHY_CTL_2, + VC4_SET_FIELD(chan2_settings->ext_current_ctl, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_EXT_CURRENT_CTL) | + VC4_SET_FIELD(chan2_settings->ffe_enable, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_FFE_ENABLE) | + VC4_SET_FIELD(chan2_settings->slew_rate_ctl, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_SLEW_RATE_CTL) | + VC4_SET_FIELD(chan2_settings->ffe_post_tap_en, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_FFE_POST_TAP_EN) | + VC4_SET_FIELD(chan2_settings->ldmos_bias_ctl, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_LDMOS_BIAS_CTL) | + VC4_SET_FIELD(chan2_settings->com_mode_ldmos_en, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_COM_MODE_LDMOS_EN) | + VC4_SET_FIELD(chan2_settings->edge_sel, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_EDGE_SEL) | + VC4_SET_FIELD(chan2_settings->ext_current_src_hs_en, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_EXT_CURRENT_SRC_HS_EN) | + VC4_SET_FIELD(chan2_settings->term_ctl, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_TERM_CTL) | + VC4_SET_FIELD(chan2_settings->ext_current_src_en, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_EXT_CURRENT_SRC_EN) | + VC4_SET_FIELD(chan2_settings->int_current_src_en, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_INT_CURRENT_SRC_EN) | + VC4_SET_FIELD(chan2_settings->int_current_ctl, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_INT_CURRENT_CTL) | + VC4_SET_FIELD(chan2_settings->int_current_src_hs_en, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_INT_CURRENT_SRC_HS_EN) | + VC4_SET_FIELD(chan2_settings->main_tap_current_select, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_MAIN_TAP_CURRENT_SELECT) | + VC4_SET_FIELD(chan2_settings->post_tap_current_select, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_POST_TAP_CURRENT_SELECT) | + VC4_SET_FIELD(chan2_settings->slew_ctl_slow_loading, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_SLEW_CTL_SLOW_LOADING) | + VC4_SET_FIELD(chan2_settings->slew_ctl_slow_driving, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_SLEW_CTL_SLOW_DRIVING) | + VC4_SET_FIELD(chan2_settings->ffe_pre_tap_en, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_FFE_PRE_TAP_EN)); + + clock_settings = + vc6_phy_get_channel_settings(variant->phy_lane_mapping[PHY_LANE_CK], + pixel_freq); + HDMI_WRITE(HDMI_TX_PHY_CTL_CK, + VC4_SET_FIELD(clock_settings->ext_current_ctl, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_EXT_CURRENT_CTL) | + VC4_SET_FIELD(clock_settings->ffe_enable, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_FFE_ENABLE) | + VC4_SET_FIELD(clock_settings->slew_rate_ctl, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_SLEW_RATE_CTL) | + VC4_SET_FIELD(clock_settings->ffe_post_tap_en, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_FFE_POST_TAP_EN) | + VC4_SET_FIELD(clock_settings->ldmos_bias_ctl, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_LDMOS_BIAS_CTL) | + VC4_SET_FIELD(clock_settings->com_mode_ldmos_en, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_COM_MODE_LDMOS_EN) | + VC4_SET_FIELD(clock_settings->edge_sel, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_EDGE_SEL) | + VC4_SET_FIELD(clock_settings->ext_current_src_hs_en, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_EXT_CURRENT_SRC_HS_EN) | + VC4_SET_FIELD(clock_settings->term_ctl, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_TERM_CTL) | + VC4_SET_FIELD(clock_settings->ext_current_src_en, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_EXT_CURRENT_SRC_EN) | + VC4_SET_FIELD(clock_settings->int_current_src_en, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_INT_CURRENT_SRC_EN) | + VC4_SET_FIELD(clock_settings->int_current_ctl, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_INT_CURRENT_CTL) | + VC4_SET_FIELD(clock_settings->int_current_src_hs_en, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_INT_CURRENT_SRC_HS_EN) | + VC4_SET_FIELD(clock_settings->main_tap_current_select, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_MAIN_TAP_CURRENT_SELECT) | + VC4_SET_FIELD(clock_settings->post_tap_current_select, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_POST_TAP_CURRENT_SELECT) | + VC4_SET_FIELD(clock_settings->slew_ctl_slow_loading, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_SLEW_CTL_SLOW_LOADING) | + VC4_SET_FIELD(clock_settings->slew_ctl_slow_driving, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_SLEW_CTL_SLOW_DRIVING) | + VC4_SET_FIELD(clock_settings->ffe_pre_tap_en, + VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_FFE_PRE_TAP_EN)); + + if (pixel_freq >= 340000000) + word_sel = 3; + else + word_sel = 0; + HDMI_WRITE(HDMI_TX_PHY_TMDS_CLK_WORD_SEL, word_sel); + + HDMI_WRITE(HDMI_TX_PHY_POWERUP_CTL, + VC6_HDMI_TX_PHY_HDMI_POWERUP_CTL_BG_PWRUP | + VC6_HDMI_TX_PHY_HDMI_POWERUP_CTL_LDO_PWRUP | + VC6_HDMI_TX_PHY_HDMI_POWERUP_CTL_BIAS_PWRUP | + VC6_HDMI_TX_PHY_HDMI_POWERUP_CTL_TX_CK_PWRUP | + VC6_HDMI_TX_PHY_HDMI_POWERUP_CTL_TX_2_PWRUP | + VC6_HDMI_TX_PHY_HDMI_POWERUP_CTL_TX_1_PWRUP | + VC6_HDMI_TX_PHY_HDMI_POWERUP_CTL_TX_0_PWRUP); + + HDMI_WRITE(HDMI_TX_PHY_PLL_POWERUP_CTL, + VC6_HDMI_TX_PHY_PLL_POWERUP_CTL_PLL_PWRUP); + + HDMI_WRITE(HDMI_TX_PHY_PLL_RESET_CTL, + HDMI_READ(HDMI_TX_PHY_PLL_RESET_CTL) & + ~VC6_HDMI_TX_PHY_PLL_RESET_CTL_PLL_RESETB); + + HDMI_WRITE(HDMI_TX_PHY_PLL_RESET_CTL, + HDMI_READ(HDMI_TX_PHY_PLL_RESET_CTL) | + VC6_HDMI_TX_PHY_PLL_RESET_CTL_PLL_RESETB); + + spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags); +} + +void vc6_hdmi_phy_disable(struct vc4_hdmi *vc4_hdmi) +{ + unsigned long flags; + + spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); + vc6_hdmi_reset_phy(vc4_hdmi); + spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags); +} diff --git a/drivers/gpu/drm/vc4/vc4_hdmi_regs.h b/drivers/gpu/drm/vc4/vc4_hdmi_regs.h index b04b2fc8d83173..59bfd69f54d980 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi_regs.h +++ b/drivers/gpu/drm/vc4/vc4_hdmi_regs.h @@ -111,13 +111,30 @@ enum vc4_hdmi_field { HDMI_TX_PHY_CTL_1, HDMI_TX_PHY_CTL_2, HDMI_TX_PHY_CTL_3, + HDMI_TX_PHY_CTL_CK, HDMI_TX_PHY_PLL_CALIBRATION_CONFIG_1, HDMI_TX_PHY_PLL_CALIBRATION_CONFIG_2, HDMI_TX_PHY_PLL_CALIBRATION_CONFIG_4, HDMI_TX_PHY_PLL_CFG, + HDMI_TX_PHY_PLL_CFG_PDIV, HDMI_TX_PHY_PLL_CTL_0, HDMI_TX_PHY_PLL_CTL_1, + HDMI_TX_PHY_PLL_MISC_0, + HDMI_TX_PHY_PLL_MISC_1, + HDMI_TX_PHY_PLL_MISC_2, + HDMI_TX_PHY_PLL_MISC_3, + HDMI_TX_PHY_PLL_MISC_4, + HDMI_TX_PHY_PLL_MISC_5, + HDMI_TX_PHY_PLL_MISC_6, + HDMI_TX_PHY_PLL_MISC_7, + HDMI_TX_PHY_PLL_MISC_8, + HDMI_TX_PHY_PLL_POST_KDIV, + HDMI_TX_PHY_PLL_POWERUP_CTL, + HDMI_TX_PHY_PLL_REFCLK, + HDMI_TX_PHY_PLL_RESET_CTL, + HDMI_TX_PHY_PLL_VCOCLK_DIV, HDMI_TX_PHY_POWERDOWN_CTL, + HDMI_TX_PHY_POWERUP_CTL, HDMI_TX_PHY_RESET_CTL, HDMI_TX_PHY_TMDS_CLK_WORD_SEL, HDMI_VEC_INTERFACE_CFG, @@ -411,6 +428,206 @@ static const struct vc4_hdmi_register __maybe_unused vc5_hdmi_hdmi1_fields[] = { VC5_CSC_REG(HDMI_CSC_CHANNEL_CTL, 0x02c), }; +static const struct vc4_hdmi_register __maybe_unused vc6_hdmi_hdmi0_fields[] = { + VC4_HD_REG(HDMI_DVP_CTL, 0x0000), + VC4_HD_REG(HDMI_MAI_CTL, 0x0010), + VC4_HD_REG(HDMI_MAI_THR, 0x0014), + VC4_HD_REG(HDMI_MAI_FMT, 0x0018), + VC4_HD_REG(HDMI_MAI_DATA, 0x001c), + VC4_HD_REG(HDMI_MAI_SMP, 0x0020), + VC4_HD_REG(HDMI_VID_CTL, 0x0044), + VC4_HD_REG(HDMI_FRAME_COUNT, 0x0060), + + VC4_HDMI_REG(HDMI_FIFO_CTL, 0x07c), + VC4_HDMI_REG(HDMI_AUDIO_PACKET_CONFIG, 0x0c0), + VC4_HDMI_REG(HDMI_RAM_PACKET_CONFIG, 0x0c4), + VC4_HDMI_REG(HDMI_RAM_PACKET_STATUS, 0x0cc), + VC4_HDMI_REG(HDMI_CRP_CFG, 0x0d0), + VC4_HDMI_REG(HDMI_CTS_0, 0x0d4), + VC4_HDMI_REG(HDMI_CTS_1, 0x0d8), + VC4_HDMI_REG(HDMI_SCHEDULER_CONTROL, 0x0e8), + VC4_HDMI_REG(HDMI_HORZA, 0x0ec), + VC4_HDMI_REG(HDMI_HORZB, 0x0f0), + VC4_HDMI_REG(HDMI_VERTA0, 0x0f4), + VC4_HDMI_REG(HDMI_VERTB0, 0x0f8), + VC4_HDMI_REG(HDMI_VERTA1, 0x100), + VC4_HDMI_REG(HDMI_VERTB1, 0x104), + VC4_HDMI_REG(HDMI_MISC_CONTROL, 0x114), + VC4_HDMI_REG(HDMI_MAI_CHANNEL_MAP, 0x0a4), + VC4_HDMI_REG(HDMI_MAI_CONFIG, 0x0a8), + VC4_HDMI_REG(HDMI_FORMAT_DET_1, 0x148), + VC4_HDMI_REG(HDMI_FORMAT_DET_2, 0x14c), + VC4_HDMI_REG(HDMI_FORMAT_DET_3, 0x150), + VC4_HDMI_REG(HDMI_FORMAT_DET_4, 0x158), + VC4_HDMI_REG(HDMI_FORMAT_DET_5, 0x15c), + VC4_HDMI_REG(HDMI_FORMAT_DET_6, 0x160), + VC4_HDMI_REG(HDMI_FORMAT_DET_7, 0x164), + VC4_HDMI_REG(HDMI_FORMAT_DET_8, 0x168), + VC4_HDMI_REG(HDMI_FORMAT_DET_9, 0x16c), + VC4_HDMI_REG(HDMI_FORMAT_DET_10, 0x170), + VC4_HDMI_REG(HDMI_DEEP_COLOR_CONFIG_1, 0x18c), + VC4_HDMI_REG(HDMI_GCP_CONFIG, 0x194), + VC4_HDMI_REG(HDMI_GCP_WORD_1, 0x198), + VC4_HDMI_REG(HDMI_HOTPLUG, 0x1c8), + VC4_HDMI_REG(HDMI_SCRAMBLER_CTL, 0x1e4), + + VC5_DVP_REG(HDMI_CLOCK_STOP, 0x0bc), + VC5_DVP_REG(HDMI_VEC_INTERFACE_CFG, 0x0f0), + VC5_DVP_REG(HDMI_VEC_INTERFACE_XBAR, 0x0f4), + + VC5_PHY_REG(HDMI_TX_PHY_RESET_CTL, 0x000), + VC5_PHY_REG(HDMI_TX_PHY_POWERUP_CTL, 0x004), + VC5_PHY_REG(HDMI_TX_PHY_CTL_0, 0x008), + VC5_PHY_REG(HDMI_TX_PHY_CTL_1, 0x00c), + VC5_PHY_REG(HDMI_TX_PHY_CTL_2, 0x010), + VC5_PHY_REG(HDMI_TX_PHY_CTL_CK, 0x014), + VC5_PHY_REG(HDMI_TX_PHY_PLL_REFCLK, 0x01c), + VC5_PHY_REG(HDMI_TX_PHY_PLL_POST_KDIV, 0x028), + VC5_PHY_REG(HDMI_TX_PHY_PLL_VCOCLK_DIV, 0x02c), + VC5_PHY_REG(HDMI_TX_PHY_PLL_CFG, 0x044), + VC5_PHY_REG(HDMI_TX_PHY_TMDS_CLK_WORD_SEL, 0x054), + VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_0, 0x060), + VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_1, 0x064), + VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_2, 0x068), + VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_3, 0x06c), + VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_4, 0x070), + VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_5, 0x074), + VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_6, 0x078), + VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_7, 0x07c), + VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_8, 0x080), + VC5_PHY_REG(HDMI_TX_PHY_PLL_RESET_CTL, 0x190), + VC5_PHY_REG(HDMI_TX_PHY_PLL_POWERUP_CTL, 0x194), + + VC5_RM_REG(HDMI_RM_CONTROL, 0x000), + VC5_RM_REG(HDMI_RM_OFFSET, 0x018), + VC5_RM_REG(HDMI_RM_FORMAT, 0x01c), + + VC5_RAM_REG(HDMI_RAM_PACKET_START, 0x000), + + VC5_CEC_REG(HDMI_CEC_CNTRL_1, 0x010), + VC5_CEC_REG(HDMI_CEC_CNTRL_2, 0x014), + VC5_CEC_REG(HDMI_CEC_CNTRL_3, 0x018), + VC5_CEC_REG(HDMI_CEC_CNTRL_4, 0x01c), + VC5_CEC_REG(HDMI_CEC_CNTRL_5, 0x020), + VC5_CEC_REG(HDMI_CEC_TX_DATA_1, 0x028), + VC5_CEC_REG(HDMI_CEC_TX_DATA_2, 0x02c), + VC5_CEC_REG(HDMI_CEC_TX_DATA_3, 0x030), + VC5_CEC_REG(HDMI_CEC_TX_DATA_4, 0x034), + VC5_CEC_REG(HDMI_CEC_RX_DATA_1, 0x038), + VC5_CEC_REG(HDMI_CEC_RX_DATA_2, 0x03c), + VC5_CEC_REG(HDMI_CEC_RX_DATA_3, 0x040), + VC5_CEC_REG(HDMI_CEC_RX_DATA_4, 0x044), + + VC5_CSC_REG(HDMI_CSC_CTL, 0x000), + VC5_CSC_REG(HDMI_CSC_12_11, 0x004), + VC5_CSC_REG(HDMI_CSC_14_13, 0x008), + VC5_CSC_REG(HDMI_CSC_22_21, 0x00c), + VC5_CSC_REG(HDMI_CSC_24_23, 0x010), + VC5_CSC_REG(HDMI_CSC_32_31, 0x014), + VC5_CSC_REG(HDMI_CSC_34_33, 0x018), + VC5_CSC_REG(HDMI_CSC_CHANNEL_CTL, 0x02c), +}; + +static const struct vc4_hdmi_register __maybe_unused vc6_hdmi_hdmi1_fields[] = { + VC4_HD_REG(HDMI_DVP_CTL, 0x0000), + VC4_HD_REG(HDMI_MAI_CTL, 0x0030), + VC4_HD_REG(HDMI_MAI_THR, 0x0034), + VC4_HD_REG(HDMI_MAI_FMT, 0x0038), + VC4_HD_REG(HDMI_MAI_DATA, 0x003c), + VC4_HD_REG(HDMI_MAI_SMP, 0x0040), + VC4_HD_REG(HDMI_VID_CTL, 0x0048), + VC4_HD_REG(HDMI_FRAME_COUNT, 0x0064), + + VC4_HDMI_REG(HDMI_FIFO_CTL, 0x07c), + VC4_HDMI_REG(HDMI_AUDIO_PACKET_CONFIG, 0x0c0), + VC4_HDMI_REG(HDMI_RAM_PACKET_CONFIG, 0x0c4), + VC4_HDMI_REG(HDMI_RAM_PACKET_STATUS, 0x0cc), + VC4_HDMI_REG(HDMI_CRP_CFG, 0x0d0), + VC4_HDMI_REG(HDMI_CTS_0, 0x0d4), + VC4_HDMI_REG(HDMI_CTS_1, 0x0d8), + VC4_HDMI_REG(HDMI_SCHEDULER_CONTROL, 0x0e8), + VC4_HDMI_REG(HDMI_HORZA, 0x0ec), + VC4_HDMI_REG(HDMI_HORZB, 0x0f0), + VC4_HDMI_REG(HDMI_VERTA0, 0x0f4), + VC4_HDMI_REG(HDMI_VERTB0, 0x0f8), + VC4_HDMI_REG(HDMI_VERTA1, 0x100), + VC4_HDMI_REG(HDMI_VERTB1, 0x104), + VC4_HDMI_REG(HDMI_MISC_CONTROL, 0x114), + VC4_HDMI_REG(HDMI_MAI_CHANNEL_MAP, 0x0a4), + VC4_HDMI_REG(HDMI_MAI_CONFIG, 0x0a8), + VC4_HDMI_REG(HDMI_FORMAT_DET_1, 0x148), + VC4_HDMI_REG(HDMI_FORMAT_DET_2, 0x14c), + VC4_HDMI_REG(HDMI_FORMAT_DET_3, 0x150), + VC4_HDMI_REG(HDMI_FORMAT_DET_4, 0x158), + VC4_HDMI_REG(HDMI_FORMAT_DET_5, 0x15c), + VC4_HDMI_REG(HDMI_FORMAT_DET_6, 0x160), + VC4_HDMI_REG(HDMI_FORMAT_DET_7, 0x164), + VC4_HDMI_REG(HDMI_FORMAT_DET_8, 0x168), + VC4_HDMI_REG(HDMI_FORMAT_DET_9, 0x16c), + VC4_HDMI_REG(HDMI_FORMAT_DET_10, 0x170), + VC4_HDMI_REG(HDMI_DEEP_COLOR_CONFIG_1, 0x18c), + VC4_HDMI_REG(HDMI_GCP_CONFIG, 0x194), + VC4_HDMI_REG(HDMI_GCP_WORD_1, 0x198), + VC4_HDMI_REG(HDMI_HOTPLUG, 0x1c8), + VC4_HDMI_REG(HDMI_SCRAMBLER_CTL, 0x1e4), + + VC5_DVP_REG(HDMI_CLOCK_STOP, 0x0bc), + VC5_DVP_REG(HDMI_VEC_INTERFACE_CFG, 0x0f0), + VC5_DVP_REG(HDMI_VEC_INTERFACE_XBAR, 0x0f4), + + VC5_PHY_REG(HDMI_TX_PHY_RESET_CTL, 0x000), + VC5_PHY_REG(HDMI_TX_PHY_POWERUP_CTL, 0x004), + VC5_PHY_REG(HDMI_TX_PHY_CTL_0, 0x008), + VC5_PHY_REG(HDMI_TX_PHY_CTL_1, 0x00c), + VC5_PHY_REG(HDMI_TX_PHY_CTL_2, 0x010), + VC5_PHY_REG(HDMI_TX_PHY_CTL_CK, 0x014), + VC5_PHY_REG(HDMI_TX_PHY_PLL_REFCLK, 0x01c), + VC5_PHY_REG(HDMI_TX_PHY_PLL_POST_KDIV, 0x028), + VC5_PHY_REG(HDMI_TX_PHY_PLL_VCOCLK_DIV, 0x02c), + VC5_PHY_REG(HDMI_TX_PHY_PLL_CFG, 0x044), + VC5_PHY_REG(HDMI_TX_PHY_TMDS_CLK_WORD_SEL, 0x054), + VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_0, 0x060), + VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_1, 0x064), + VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_2, 0x068), + VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_3, 0x06c), + VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_4, 0x070), + VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_5, 0x074), + VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_6, 0x078), + VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_7, 0x07c), + VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_8, 0x080), + VC5_PHY_REG(HDMI_TX_PHY_PLL_RESET_CTL, 0x190), + VC5_PHY_REG(HDMI_TX_PHY_PLL_POWERUP_CTL, 0x194), + + VC5_RM_REG(HDMI_RM_CONTROL, 0x000), + VC5_RM_REG(HDMI_RM_OFFSET, 0x018), + VC5_RM_REG(HDMI_RM_FORMAT, 0x01c), + + VC5_RAM_REG(HDMI_RAM_PACKET_START, 0x000), + + VC5_CEC_REG(HDMI_CEC_CNTRL_1, 0x010), + VC5_CEC_REG(HDMI_CEC_CNTRL_2, 0x014), + VC5_CEC_REG(HDMI_CEC_CNTRL_3, 0x018), + VC5_CEC_REG(HDMI_CEC_CNTRL_4, 0x01c), + VC5_CEC_REG(HDMI_CEC_CNTRL_5, 0x020), + VC5_CEC_REG(HDMI_CEC_TX_DATA_1, 0x028), + VC5_CEC_REG(HDMI_CEC_TX_DATA_2, 0x02c), + VC5_CEC_REG(HDMI_CEC_TX_DATA_3, 0x030), + VC5_CEC_REG(HDMI_CEC_TX_DATA_4, 0x034), + VC5_CEC_REG(HDMI_CEC_RX_DATA_1, 0x038), + VC5_CEC_REG(HDMI_CEC_RX_DATA_2, 0x03c), + VC5_CEC_REG(HDMI_CEC_RX_DATA_3, 0x040), + VC5_CEC_REG(HDMI_CEC_RX_DATA_4, 0x044), + + VC5_CSC_REG(HDMI_CSC_CTL, 0x000), + VC5_CSC_REG(HDMI_CSC_12_11, 0x004), + VC5_CSC_REG(HDMI_CSC_14_13, 0x008), + VC5_CSC_REG(HDMI_CSC_22_21, 0x00c), + VC5_CSC_REG(HDMI_CSC_24_23, 0x010), + VC5_CSC_REG(HDMI_CSC_32_31, 0x014), + VC5_CSC_REG(HDMI_CSC_34_33, 0x018), + VC5_CSC_REG(HDMI_CSC_CHANNEL_CTL, 0x02c), +}; + static inline void __iomem *__vc4_hdmi_get_field_base(struct vc4_hdmi *hdmi, enum vc4_hdmi_regs reg) @@ -498,8 +715,11 @@ static inline void vc4_hdmi_write(struct vc4_hdmi *hdmi, field = &variant->registers[reg]; base = __vc4_hdmi_get_field_base(hdmi, field->reg); - if (!base) + if (!base) { + dev_warn(&hdmi->pdev->dev, + "Unknown register ID %u\n", reg); return; + } writel(value, base + field->offset); } diff --git a/drivers/gpu/drm/vc4/vc4_hvs.c b/drivers/gpu/drm/vc4/vc4_hvs.c index c389e82463bfdb..3258dbec9bddee 100644 --- a/drivers/gpu/drm/vc4/vc4_hvs.c +++ b/drivers/gpu/drm/vc4/vc4_hvs.c @@ -33,7 +33,7 @@ #include "vc4_drv.h" #include "vc4_regs.h" -static const struct debugfs_reg32 hvs_regs[] = { +static const struct debugfs_reg32 vc4_hvs_regs[] = { VC4_REG32(SCALER_DISPCTRL), VC4_REG32(SCALER_DISPSTAT), VC4_REG32(SCALER_DISPID), @@ -67,6 +67,140 @@ static const struct debugfs_reg32 hvs_regs[] = { VC4_REG32(SCALER_OLEDCOEF2), }; +static const struct debugfs_reg32 vc6_hvs_regs[] = { + VC4_REG32(SCALER6_VERSION), + VC4_REG32(SCALER6_CXM_SIZE), + VC4_REG32(SCALER6_LBM_SIZE), + VC4_REG32(SCALER6_UBM_SIZE), + VC4_REG32(SCALER6_COBA_SIZE), + VC4_REG32(SCALER6_COB_SIZE), + VC4_REG32(SCALER6_CONTROL), + VC4_REG32(SCALER6_FETCHER_STATUS), + VC4_REG32(SCALER6_FETCH_STATUS), + VC4_REG32(SCALER6_HANDLE_ERROR), + VC4_REG32(SCALER6_DISP0_CTRL0), + VC4_REG32(SCALER6_DISP0_CTRL1), + VC4_REG32(SCALER6_DISP0_BGND), + VC4_REG32(SCALER6_DISP0_LPTRS), + VC4_REG32(SCALER6_DISP0_COB), + VC4_REG32(SCALER6_DISP0_STATUS), + VC4_REG32(SCALER6_DISP0_DL), + VC4_REG32(SCALER6_DISP0_RUN), + VC4_REG32(SCALER6_DISP1_CTRL0), + VC4_REG32(SCALER6_DISP1_CTRL1), + VC4_REG32(SCALER6_DISP1_BGND), + VC4_REG32(SCALER6_DISP1_LPTRS), + VC4_REG32(SCALER6_DISP1_COB), + VC4_REG32(SCALER6_DISP1_STATUS), + VC4_REG32(SCALER6_DISP1_DL), + VC4_REG32(SCALER6_DISP1_RUN), + VC4_REG32(SCALER6_DISP2_CTRL0), + VC4_REG32(SCALER6_DISP2_CTRL1), + VC4_REG32(SCALER6_DISP2_BGND), + VC4_REG32(SCALER6_DISP2_LPTRS), + VC4_REG32(SCALER6_DISP2_COB), + VC4_REG32(SCALER6_DISP2_STATUS), + VC4_REG32(SCALER6_DISP2_DL), + VC4_REG32(SCALER6_DISP2_RUN), + VC4_REG32(SCALER6_EOLN), + VC4_REG32(SCALER6_DL_STATUS), + VC4_REG32(SCALER6_BFG_MISC), + VC4_REG32(SCALER6_QOS0), + VC4_REG32(SCALER6_PROF0), + VC4_REG32(SCALER6_QOS1), + VC4_REG32(SCALER6_PROF1), + VC4_REG32(SCALER6_QOS2), + VC4_REG32(SCALER6_PROF2), + VC4_REG32(SCALER6_PRI_MAP0), + VC4_REG32(SCALER6_PRI_MAP1), + VC4_REG32(SCALER6_HISTCTRL), + VC4_REG32(SCALER6_HISTBIN0), + VC4_REG32(SCALER6_HISTBIN1), + VC4_REG32(SCALER6_HISTBIN2), + VC4_REG32(SCALER6_HISTBIN3), + VC4_REG32(SCALER6_HISTBIN4), + VC4_REG32(SCALER6_HISTBIN5), + VC4_REG32(SCALER6_HISTBIN6), + VC4_REG32(SCALER6_HISTBIN7), + VC4_REG32(SCALER6_HDR_CFG_REMAP), + VC4_REG32(SCALER6_COL_SPACE), + VC4_REG32(SCALER6_HVS_ID), + VC4_REG32(SCALER6_CFC1), + VC4_REG32(SCALER6_DISP_UPM_ISO0), + VC4_REG32(SCALER6_DISP_UPM_ISO1), + VC4_REG32(SCALER6_DISP_UPM_ISO2), + VC4_REG32(SCALER6_DISP_LBM_ISO0), + VC4_REG32(SCALER6_DISP_LBM_ISO1), + VC4_REG32(SCALER6_DISP_LBM_ISO2), + VC4_REG32(SCALER6_DISP_COB_ISO0), + VC4_REG32(SCALER6_DISP_COB_ISO1), + VC4_REG32(SCALER6_DISP_COB_ISO2), + VC4_REG32(SCALER6_BAD_COB), + VC4_REG32(SCALER6_BAD_LBM), + VC4_REG32(SCALER6_BAD_UPM), + VC4_REG32(SCALER6_BAD_AXI), +}; + +static const struct debugfs_reg32 vc6_d_hvs_regs[] = { + VC4_REG32(SCALER6D_VERSION), + VC4_REG32(SCALER6D_CXM_SIZE), + VC4_REG32(SCALER6D_LBM_SIZE), + VC4_REG32(SCALER6D_UBM_SIZE), + VC4_REG32(SCALER6D_COBA_SIZE), + VC4_REG32(SCALER6D_COB_SIZE), + VC4_REG32(SCALER6D_CONTROL), + VC4_REG32(SCALER6D_FETCHER_STATUS), + VC4_REG32(SCALER6D_FETCH_STATUS), + VC4_REG32(SCALER6D_HANDLE_ERROR), + VC4_REG32(SCALER6D_DISP0_CTRL0), + VC4_REG32(SCALER6D_DISP0_CTRL1), + VC4_REG32(SCALER6D_DISP0_BGND0), + VC4_REG32(SCALER6D_DISP0_BGND1), + VC4_REG32(SCALER6D_DISP0_LPTRS), + VC4_REG32(SCALER6D_DISP0_COB), + VC4_REG32(SCALER6D_DISP0_STATUS), + VC4_REG32(SCALER6D_DISP0_DL), + VC4_REG32(SCALER6D_DISP0_RUN), + VC4_REG32(SCALER6D_DISP1_CTRL0), + VC4_REG32(SCALER6D_DISP1_CTRL1), + VC4_REG32(SCALER6D_DISP1_BGND0), + VC4_REG32(SCALER6D_DISP1_BGND1), + VC4_REG32(SCALER6D_DISP1_LPTRS), + VC4_REG32(SCALER6D_DISP1_COB), + VC4_REG32(SCALER6D_DISP1_STATUS), + VC4_REG32(SCALER6D_DISP1_DL), + VC4_REG32(SCALER6D_DISP1_RUN), + VC4_REG32(SCALER6D_DISP2_CTRL0), + VC4_REG32(SCALER6D_DISP2_CTRL1), + VC4_REG32(SCALER6D_DISP2_BGND0), + VC4_REG32(SCALER6D_DISP2_BGND1), + VC4_REG32(SCALER6D_DISP2_LPTRS), + VC4_REG32(SCALER6D_DISP2_COB), + VC4_REG32(SCALER6D_DISP2_STATUS), + VC4_REG32(SCALER6D_DISP2_DL), + VC4_REG32(SCALER6D_DISP2_RUN), + VC4_REG32(SCALER6D_EOLN), + VC4_REG32(SCALER6D_DL_STATUS), + VC4_REG32(SCALER6D_QOS0), + VC4_REG32(SCALER6D_PROF0), + VC4_REG32(SCALER6D_QOS1), + VC4_REG32(SCALER6D_PROF1), + VC4_REG32(SCALER6D_QOS2), + VC4_REG32(SCALER6D_PROF2), + VC4_REG32(SCALER6D_PRI_MAP0), + VC4_REG32(SCALER6D_PRI_MAP1), + VC4_REG32(SCALER6D_HISTCTRL), + VC4_REG32(SCALER6D_HISTBIN0), + VC4_REG32(SCALER6D_HISTBIN1), + VC4_REG32(SCALER6D_HISTBIN2), + VC4_REG32(SCALER6D_HISTBIN3), + VC4_REG32(SCALER6D_HISTBIN4), + VC4_REG32(SCALER6D_HISTBIN5), + VC4_REG32(SCALER6D_HISTBIN6), + VC4_REG32(SCALER6D_HISTBIN7), + VC4_REG32(SCALER6D_HVS_ID), +}; + void vc4_hvs_dump_state(struct vc4_hvs *hvs) { struct drm_device *drm = &hvs->vc4->base; @@ -145,6 +279,127 @@ static int vc4_hvs_debugfs_dlist(struct seq_file *m, void *data) return 0; } +static int vc6_hvs_debugfs_dlist(struct seq_file *m, void *data) +{ + struct drm_info_node *node = m->private; + struct drm_device *dev = node->minor->dev; + struct vc4_dev *vc4 = to_vc4_dev(dev); + struct vc4_hvs *hvs = vc4->hvs; + struct drm_printer p = drm_seq_file_printer(m); + unsigned int dlist_mem_size = hvs->dlist_mem_size; + unsigned int next_entry_start; + unsigned int i; + + for (i = 0; i < SCALER_CHANNELS_COUNT; i++) { + unsigned int active_dlist, dispstat; + unsigned int j; + + dispstat = VC4_GET_FIELD(HVS_READ(SCALER6_DISPX_STATUS(i)), + SCALER6_DISPX_STATUS_MODE); + if (dispstat == SCALER6_DISPX_STATUS_MODE_DISABLED || + dispstat == SCALER6_DISPX_STATUS_MODE_EOF) { + drm_printf(&p, "HVS chan %u disabled\n", i); + continue; + } + + drm_printf(&p, "HVS chan %u:\n", i); + + active_dlist = VC4_GET_FIELD(HVS_READ(SCALER6_DISPX_DL(i)), + SCALER6_DISPX_DL_LACT); + next_entry_start = 0; + + for (j = active_dlist; j < dlist_mem_size; j++) { + u32 dlist_word; + + dlist_word = readl((u32 __iomem *)vc4->hvs->dlist + j); + drm_printf(&p, "dlist: %02d: 0x%08x\n", j, + dlist_word); + if (!next_entry_start || + next_entry_start == j) { + if (dlist_word & SCALER_CTL0_END) + break; + next_entry_start = j + + VC4_GET_FIELD(dlist_word, + SCALER_CTL0_SIZE); + } + } + } + + return 0; +} + +static int vc6_hvs_debugfs_upm_allocs(struct seq_file *m, void *data) +{ + struct drm_debugfs_entry *entry = m->private; + struct drm_device *dev = entry->dev; + struct vc4_dev *vc4 = to_vc4_dev(dev); + struct vc4_hvs *hvs = vc4->hvs; + struct drm_printer p = drm_seq_file_printer(m); + struct vc4_upm_refcounts *refcount; + unsigned int i; + + drm_printf(&p, "UPM Handles:\n"); + for (i = 1; i <= VC4_NUM_UPM_HANDLES; i++) { + refcount = &hvs->upm_refcounts[i]; + drm_printf(&p, "handle %u: refcount %u, size %zu [%08llx + %08llx]\n", + i, refcount_read(&refcount->refcount), refcount->size, + refcount->upm.start, refcount->upm.size); + } + + return 0; +} + +static int vc4_hvs_debugfs_dlist_allocs(struct seq_file *m, void *data) +{ + struct drm_debugfs_entry *entry = m->private; + struct drm_device *dev = entry->dev; + struct vc4_dev *vc4 = to_vc4_dev(dev); + struct vc4_hvs *hvs = vc4->hvs; + struct drm_printer p = drm_seq_file_printer(m); + struct vc4_hvs_dlist_allocation *cur, *next; + struct drm_mm_node *mm_node; + unsigned long flags; + + spin_lock_irqsave(&hvs->mm_lock, flags); + + drm_printf(&p, "Allocated nodes:\n"); + list_for_each_entry(mm_node, drm_mm_nodes(&hvs->dlist_mm), node_list) { + drm_printf(&p, "node [%08llx + %08llx]\n", mm_node->start, mm_node->size); + } + + drm_printf(&p, "Stale nodes:\n"); + list_for_each_entry_safe(cur, next, &hvs->stale_dlist_entries, node) { + drm_printf(&p, "node [%08llx + %08llx] channel %u frcnt %u\n", + cur->mm_node.start, cur->mm_node.size, cur->channel, + cur->target_frame_count); + } + + spin_unlock_irqrestore(&hvs->mm_lock, flags); + + return 0; +} + +static int vc4_hvs_debugfs_lbm_allocs(struct seq_file *m, void *data) +{ + struct drm_debugfs_entry *entry = m->private; + struct drm_device *dev = entry->dev; + struct vc4_dev *vc4 = to_vc4_dev(dev); + struct vc4_hvs *hvs = vc4->hvs; + struct drm_printer p = drm_seq_file_printer(m); + struct vc4_lbm_refcounts *refcount; + unsigned int i; + + drm_printf(&p, "LBM Handles:\n"); + for (i = 0; i < VC4_NUM_LBM_HANDLES; i++) { + refcount = &hvs->lbm_refcounts[i]; + drm_printf(&p, "handle %u: refcount %u, size %zu [%08llx + %08llx]\n", + i, refcount_read(&refcount->refcount), refcount->size, + refcount->lbm.start, refcount->lbm.size); + } + + return 0; +} + /* The filter kernel is composed of dwords each containing 3 9-bit * signed integers packed next to each other. */ @@ -178,6 +433,9 @@ static int vc4_hvs_debugfs_dlist(struct seq_file *m, void *data) static const u32 mitchell_netravali_1_3_1_3_kernel[] = VC4_LINEAR_PHASE_KERNEL(0, -2, -6, -8, -10, -8, -3, 2, 18, 50, 82, 119, 155, 187, 213, 227); +static const u32 nearest_neighbour_kernel[] = + VC4_LINEAR_PHASE_KERNEL(0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 255, 255, 255, 255); static int vc4_hvs_upload_linear_kernel(struct vc4_hvs *hvs, struct drm_mm_node *space, @@ -215,12 +473,15 @@ static int vc4_hvs_upload_linear_kernel(struct vc4_hvs *hvs, static void vc4_hvs_lut_load(struct vc4_hvs *hvs, struct vc4_crtc *vc4_crtc) { - struct drm_device *drm = &hvs->vc4->base; + struct vc4_dev *vc4 = hvs->vc4; + struct drm_device *drm = &vc4->base; struct drm_crtc *crtc = &vc4_crtc->base; struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc->state); int idx; u32 i; + WARN_ON_ONCE(vc4->gen > VC4_GEN_5); + if (!drm_dev_enter(drm, &idx)) return; @@ -263,28 +524,326 @@ static void vc4_hvs_update_gamma_lut(struct vc4_hvs *hvs, vc4_hvs_lut_load(hvs, vc4_crtc); } +static void vc4_hvs_irq_enable_eof(struct vc4_hvs *hvs, + unsigned int channel) +{ + struct vc4_dev *vc4 = hvs->vc4; + + if (hvs->eof_irq[channel].enabled) + return; + + switch (vc4->gen) { + case VC4_GEN_4: + HVS_WRITE(SCALER_DISPCTRL, + HVS_READ(SCALER_DISPCTRL) | + SCALER_DISPCTRL_DSPEIEOF(channel)); + break; + + case VC4_GEN_5: + HVS_WRITE(SCALER_DISPCTRL, + HVS_READ(SCALER_DISPCTRL) | + SCALER5_DISPCTRL_DSPEIEOF(channel)); + break; + + case VC4_GEN_6_C: + case VC4_GEN_6_D: + enable_irq(hvs->eof_irq[channel].desc); + break; + + default: + break; + } + + hvs->eof_irq[channel].enabled = true; +} + +static void vc4_hvs_irq_clear_eof(struct vc4_hvs *hvs, + unsigned int channel) +{ + struct vc4_dev *vc4 = hvs->vc4; + + if (!hvs->eof_irq[channel].enabled) + return; + + switch (vc4->gen) { + case VC4_GEN_4: + HVS_WRITE(SCALER_DISPCTRL, + HVS_READ(SCALER_DISPCTRL) & + ~SCALER_DISPCTRL_DSPEIEOF(channel)); + break; + + case VC4_GEN_5: + HVS_WRITE(SCALER_DISPCTRL, + HVS_READ(SCALER_DISPCTRL) & + ~SCALER5_DISPCTRL_DSPEIEOF(channel)); + break; + + case VC4_GEN_6_C: + case VC4_GEN_6_D: + disable_irq_nosync(hvs->eof_irq[channel].desc); + break; + + default: + break; + } + + hvs->eof_irq[channel].enabled = false; +} + +static void vc4_hvs_free_dlist_entry_locked(struct vc4_hvs *hvs, + struct vc4_hvs_dlist_allocation *alloc); + +static struct vc4_hvs_dlist_allocation * +vc4_hvs_alloc_dlist_entry(struct vc4_hvs *hvs, + unsigned int channel, + size_t dlist_count) +{ + struct vc4_dev *vc4 = hvs->vc4; + struct drm_device *dev = &vc4->base; + struct vc4_hvs_dlist_allocation *alloc; + struct vc4_hvs_dlist_allocation *cur, *next; + unsigned long flags; + int ret; + + if (channel == VC4_HVS_CHANNEL_DISABLED) + return NULL; + + alloc = kzalloc(sizeof(*alloc), GFP_KERNEL); + if (!alloc) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&alloc->node); + + spin_lock_irqsave(&hvs->mm_lock, flags); + ret = drm_mm_insert_node(&hvs->dlist_mm, &alloc->mm_node, + dlist_count); + spin_unlock_irqrestore(&hvs->mm_lock, flags); + + if (ret) { + drm_err(dev, "Failed to allocate DLIST entry. Requested size=%zu. ret=%d. DISPCTRL is %08x\n", + dlist_count, ret, HVS_READ(SCALER_DISPCTRL)); + + /* This should never happen as stale entries should get released + * as the frame counter interrupt triggers. + * However we've seen this fail for reasons currently unknown. + * Free all stale entries now so we should be able to complete + * this allocation. + */ + spin_lock_irqsave(&hvs->mm_lock, flags); + list_for_each_entry_safe(cur, next, &hvs->stale_dlist_entries, node) { + vc4_hvs_free_dlist_entry_locked(hvs, cur); + } + + ret = drm_mm_insert_node(&hvs->dlist_mm, &alloc->mm_node, + dlist_count); + spin_unlock_irqrestore(&hvs->mm_lock, flags); + + if (ret) + return ERR_PTR(ret); + } + + alloc->channel = channel; + + return alloc; +} + +static void vc4_hvs_free_dlist_entry_locked(struct vc4_hvs *hvs, + struct vc4_hvs_dlist_allocation *alloc) +{ + lockdep_assert_held(&hvs->mm_lock); + + if (!list_empty(&alloc->node)) + list_del(&alloc->node); + + drm_mm_remove_node(&alloc->mm_node); + kfree(alloc); +} + +void vc4_hvs_mark_dlist_entry_stale(struct vc4_hvs *hvs, + struct vc4_hvs_dlist_allocation *alloc) +{ + unsigned long flags; + u8 frcnt; + + if (!alloc) + return; + + if (!drm_mm_node_allocated(&alloc->mm_node)) + return; + + /* + * Kunit tests run with a mock device and we consider any hardware + * access a test failure. Let's free the dlist allocation right away if + * we're running under kunit, we won't risk a dlist corruption anyway. + * + * Likewise if the allocation was only checked and never programmed, we + * can destroy the allocation immediately. + */ + if (kunit_get_current_test() || !alloc->dlist_programmed) { + spin_lock_irqsave(&hvs->mm_lock, flags); + vc4_hvs_free_dlist_entry_locked(hvs, alloc); + spin_unlock_irqrestore(&hvs->mm_lock, flags); + return; + } + + frcnt = vc4_hvs_get_fifo_frame_count(hvs, alloc->channel); + alloc->target_frame_count = (frcnt + 1) & ((1 << 6) - 1); + + spin_lock_irqsave(&hvs->mm_lock, flags); + + list_add_tail(&alloc->node, &hvs->stale_dlist_entries); + + HVS_WRITE(SCALER_DISPSTAT, SCALER_DISPSTAT_EOF(alloc->channel)); + vc4_hvs_irq_enable_eof(hvs, alloc->channel); + + spin_unlock_irqrestore(&hvs->mm_lock, flags); +} + +static void vc4_hvs_schedule_dlist_sweep(struct vc4_hvs *hvs, + unsigned int channel) +{ + unsigned long flags; + + spin_lock_irqsave(&hvs->mm_lock, flags); + + if (!list_empty(&hvs->stale_dlist_entries)) + queue_work(system_unbound_wq, &hvs->free_dlist_work); + + if (list_empty(&hvs->stale_dlist_entries)) + vc4_hvs_irq_clear_eof(hvs, channel); + + spin_unlock_irqrestore(&hvs->mm_lock, flags); +} + +/* + * Frame counts are essentially sequence numbers over 6 bits, and we + * thus can use sequence number arithmetic and follow the RFC1982 to + * implement proper comparison between them. + */ +static bool vc4_hvs_frcnt_lte(u8 cnt1, u8 cnt2) +{ + return (s8)((cnt1 << 2) - (cnt2 << 2)) <= 0; +} + +static bool vc4_hvs_check_channel_active(struct vc4_hvs *hvs, unsigned int fifo) +{ + struct vc4_dev *vc4 = hvs->vc4; + struct drm_device *drm = &vc4->base; + bool enabled = false; + int idx; + + if (!drm_dev_enter(drm, &idx)) + return 0; + + if (vc4->gen >= VC4_GEN_6_C) + enabled = HVS_READ(SCALER6_DISPX_CTRL0(fifo)) & SCALER6_DISPX_CTRL0_ENB; + else + enabled = HVS_READ(SCALER_DISPCTRLX(fifo)) & SCALER_DISPCTRLX_ENABLE; + + drm_dev_exit(idx); + return enabled; +} + +/* + * Some atomic commits (legacy cursor updates, mostly) will not wait for + * the next vblank and will just return once the commit has been pushed + * to the hardware. + * + * On the hardware side, our HVS stores the planes parameters in its + * context RAM, and will use part of the RAM to store data during the + * frame rendering. + * + * This interacts badly if we get multiple commits before the next + * vblank since we could end up overwriting the DLIST entries used by + * previous commits if our dlist allocation reuses that entry. In such a + * case, we would overwrite the data currently being used by the + * hardware, resulting in a corrupted frame. + * + * In order to work around this, we'll queue the dlist entries in a list + * once the associated CRTC state is destroyed. The HVS only allows us + * to know which entry is being active, but not which one are no longer + * being used, so in order to avoid freeing entries that are still used + * by the hardware we add a guesstimate of the frame count where our + * entry will no longer be used, and thus will only free those entries + * when we will have reached that frame count. + */ +static void vc4_hvs_dlist_free_work(struct work_struct *work) +{ + struct vc4_hvs *hvs = container_of(work, struct vc4_hvs, free_dlist_work); + struct vc4_hvs_dlist_allocation *cur, *next; + unsigned long flags; + bool active[3]; + u8 frcnt[3]; + int i; + + spin_lock_irqsave(&hvs->mm_lock, flags); + for (i = 0; i < 3; i++) { + frcnt[i] = vc4_hvs_get_fifo_frame_count(hvs, i); + active[i] = vc4_hvs_check_channel_active(hvs, i); + } + list_for_each_entry_safe(cur, next, &hvs->stale_dlist_entries, node) { + if (active[cur->channel] && + !vc4_hvs_frcnt_lte(cur->target_frame_count, frcnt[cur->channel])) + continue; + + vc4_hvs_free_dlist_entry_locked(hvs, cur); + } + spin_unlock_irqrestore(&hvs->mm_lock, flags); +} + u8 vc4_hvs_get_fifo_frame_count(struct vc4_hvs *hvs, unsigned int fifo) { - struct drm_device *drm = &hvs->vc4->base; + struct vc4_dev *vc4 = hvs->vc4; + struct drm_device *drm = &vc4->base; u8 field = 0; int idx; + WARN_ON_ONCE(vc4->gen > VC4_GEN_6_D); + if (!drm_dev_enter(drm, &idx)) return 0; - switch (fifo) { - case 0: - field = VC4_GET_FIELD(HVS_READ(SCALER_DISPSTAT1), - SCALER_DISPSTAT1_FRCNT0); + switch (vc4->gen) { + case VC4_GEN_6_C: + case VC4_GEN_6_D: + field = VC4_GET_FIELD(HVS_READ(SCALER6_DISPX_STATUS(fifo)), + SCALER6_DISPX_STATUS_FRCNT); break; - case 1: - field = VC4_GET_FIELD(HVS_READ(SCALER_DISPSTAT1), - SCALER_DISPSTAT1_FRCNT1); + case VC4_GEN_5: + switch (fifo) { + case 0: + field = VC4_GET_FIELD(HVS_READ(SCALER_DISPSTAT1), + SCALER5_DISPSTAT1_FRCNT0); + break; + case 1: + field = VC4_GET_FIELD(HVS_READ(SCALER_DISPSTAT1), + SCALER5_DISPSTAT1_FRCNT1); + break; + case 2: + field = VC4_GET_FIELD(HVS_READ(SCALER_DISPSTAT2), + SCALER5_DISPSTAT2_FRCNT2); + break; + } break; - case 2: - field = VC4_GET_FIELD(HVS_READ(SCALER_DISPSTAT2), - SCALER_DISPSTAT2_FRCNT2); + case VC4_GEN_4: + switch (fifo) { + case 0: + field = VC4_GET_FIELD(HVS_READ(SCALER_DISPSTAT1), + SCALER_DISPSTAT1_FRCNT0); + break; + case 1: + field = VC4_GET_FIELD(HVS_READ(SCALER_DISPSTAT1), + SCALER_DISPSTAT1_FRCNT1); + break; + case 2: + field = VC4_GET_FIELD(HVS_READ(SCALER_DISPSTAT2), + SCALER_DISPSTAT2_FRCNT2); + break; + } break; + default: + drm_err(drm, "Unknown VC4 generation: %d", vc4->gen); + return 0; } drm_dev_exit(idx); @@ -297,53 +856,80 @@ int vc4_hvs_get_fifo_from_output(struct vc4_hvs *hvs, unsigned int output) u32 reg; int ret; - if (vc4->gen == VC4_GEN_4) + WARN_ON_ONCE(vc4->gen > VC4_GEN_6_D); + + switch (vc4->gen) { + case VC4_GEN_4: return output; - /* - * NOTE: We should probably use drm_dev_enter()/drm_dev_exit() - * here, but this function is only used during the DRM device - * initialization, so we should be fine. - */ + case VC4_GEN_5: + /* + * NOTE: We should probably use + * drm_dev_enter()/drm_dev_exit() here, but this + * function is only used during the DRM device + * initialization, so we should be fine. + */ - switch (output) { - case 0: - return 0; + switch (output) { + case 0: + return 0; - case 1: - return 1; + case 1: + return 1; - case 2: - reg = HVS_READ(SCALER_DISPECTRL); - ret = FIELD_GET(SCALER_DISPECTRL_DSP2_MUX_MASK, reg); - if (ret == 0) - return 2; + case 2: + reg = HVS_READ(SCALER_DISPECTRL); + ret = FIELD_GET(SCALER_DISPECTRL_DSP2_MUX_MASK, reg); + if (ret == 0) + return 2; - return 0; + return 0; - case 3: - reg = HVS_READ(SCALER_DISPCTRL); - ret = FIELD_GET(SCALER_DISPCTRL_DSP3_MUX_MASK, reg); - if (ret == 3) - return -EPIPE; + case 3: + reg = HVS_READ(SCALER_DISPCTRL); + ret = FIELD_GET(SCALER_DISPCTRL_DSP3_MUX_MASK, reg); + if (ret == 3) + return -EPIPE; - return ret; + return ret; - case 4: - reg = HVS_READ(SCALER_DISPEOLN); - ret = FIELD_GET(SCALER_DISPEOLN_DSP4_MUX_MASK, reg); - if (ret == 3) - return -EPIPE; + case 4: + reg = HVS_READ(SCALER_DISPEOLN); + ret = FIELD_GET(SCALER_DISPEOLN_DSP4_MUX_MASK, reg); + if (ret == 3) + return -EPIPE; - return ret; + return ret; + + case 5: + reg = HVS_READ(SCALER_DISPDITHER); + ret = FIELD_GET(SCALER_DISPDITHER_DSP5_MUX_MASK, reg); + if (ret == 3) + return -EPIPE; - case 5: - reg = HVS_READ(SCALER_DISPDITHER); - ret = FIELD_GET(SCALER_DISPDITHER_DSP5_MUX_MASK, reg); - if (ret == 3) + return ret; + + default: return -EPIPE; + } - return ret; + case VC4_GEN_6_C: + case VC4_GEN_6_D: + switch (output) { + case 0: + return 0; + + case 2: + return 2; + + case 1: + case 3: + case 4: + return 1; + + default: + return -EPIPE; + } default: return -EPIPE; @@ -363,6 +949,8 @@ static int vc4_hvs_init_channel(struct vc4_hvs *hvs, struct drm_crtc *crtc, u32 dispctrl; int idx; + WARN_ON_ONCE(vc4->gen > VC4_GEN_5); + if (!drm_dev_enter(drm, &idx)) return -ENODEV; @@ -413,26 +1001,65 @@ static int vc4_hvs_init_channel(struct vc4_hvs *hvs, struct drm_crtc *crtc, return 0; } -void vc4_hvs_stop_channel(struct vc4_hvs *hvs, unsigned int chan) +static int vc6_hvs_init_channel(struct vc4_hvs *hvs, struct drm_crtc *crtc, + struct drm_display_mode *mode, bool oneshot) { - struct drm_device *drm = &hvs->vc4->base; + struct vc4_dev *vc4 = hvs->vc4; + struct drm_device *drm = &vc4->base; + struct vc4_crtc_state *vc4_crtc_state = to_vc4_crtc_state(crtc->state); + unsigned int chan = vc4_crtc_state->assigned_channel; + bool interlace = mode->flags & DRM_MODE_FLAG_INTERLACE; + u32 disp_ctrl1; int idx; + WARN_ON_ONCE(vc4->gen < VC4_GEN_6_C); + if (!drm_dev_enter(drm, &idx)) - return; + return -ENODEV; - if (!(HVS_READ(SCALER_DISPCTRLX(chan)) & SCALER_DISPCTRLX_ENABLE)) - goto out; + HVS_WRITE(SCALER6_DISPX_CTRL0(chan), SCALER6_DISPX_CTRL0_RESET); - HVS_WRITE(SCALER_DISPCTRLX(chan), SCALER_DISPCTRLX_RESET); - HVS_WRITE(SCALER_DISPCTRLX(chan), 0); + disp_ctrl1 = HVS_READ(SCALER6_DISPX_CTRL1(chan)); + disp_ctrl1 &= ~SCALER6_DISPX_CTRL1_INTLACE; + HVS_WRITE(SCALER6_DISPX_CTRL1(chan), + disp_ctrl1 | (interlace ? SCALER6_DISPX_CTRL1_INTLACE : 0)); - /* Once we leave, the scaler should be disabled and its fifo empty. */ - WARN_ON_ONCE(HVS_READ(SCALER_DISPCTRLX(chan)) & SCALER_DISPCTRLX_RESET); + HVS_WRITE(SCALER6_DISPX_CTRL0(chan), + SCALER6_DISPX_CTRL0_ENB | + VC4_SET_FIELD(mode->hdisplay - 1, + SCALER6_DISPX_CTRL0_FWIDTH) | + (oneshot ? SCALER6_DISPX_CTRL0_ONESHOT : 0) | + VC4_SET_FIELD(mode->vdisplay - 1, + SCALER6_DISPX_CTRL0_LINES)); - WARN_ON_ONCE(VC4_GET_FIELD(HVS_READ(SCALER_DISPSTATX(chan)), - SCALER_DISPSTATX_MODE) != - SCALER_DISPSTATX_MODE_DISABLED); + drm_dev_exit(idx); + + return 0; +} + +static void __vc4_hvs_stop_channel(struct vc4_hvs *hvs, unsigned int chan) +{ + struct vc4_dev *vc4 = hvs->vc4; + struct drm_device *drm = &vc4->base; + int idx; + + WARN_ON_ONCE(vc4->gen > VC4_GEN_5); + + if (!drm_dev_enter(drm, &idx)) + return; + + if (!(HVS_READ(SCALER_DISPCTRLX(chan)) & SCALER_DISPCTRLX_ENABLE)) + goto out; + + HVS_WRITE(SCALER_DISPCTRLX(chan), SCALER_DISPCTRLX_RESET); + HVS_WRITE(SCALER_DISPCTRLX(chan), 0); + + /* Once we leave, the scaler should be disabled and its fifo empty. */ + WARN_ON_ONCE(HVS_READ(SCALER_DISPCTRLX(chan)) & SCALER_DISPCTRLX_RESET); + + WARN_ON_ONCE(VC4_GET_FIELD(HVS_READ(SCALER_DISPSTATX(chan)), + SCALER_DISPSTATX_MODE) != + SCALER_DISPSTATX_MODE_DISABLED); WARN_ON_ONCE((HVS_READ(SCALER_DISPSTATX(chan)) & (SCALER_DISPSTATX_FULL | SCALER_DISPSTATX_EMPTY)) != @@ -442,17 +1069,54 @@ void vc4_hvs_stop_channel(struct vc4_hvs *hvs, unsigned int chan) drm_dev_exit(idx); } +static void __vc6_hvs_stop_channel(struct vc4_hvs *hvs, unsigned int chan) +{ + struct vc4_dev *vc4 = hvs->vc4; + struct drm_device *drm = &vc4->base; + int idx; + + WARN_ON_ONCE(vc4->gen < VC4_GEN_6_C); + + if (!drm_dev_enter(drm, &idx)) + return; + + if (!(HVS_READ(SCALER6_DISPX_CTRL0(chan)) & SCALER6_DISPX_CTRL0_ENB)) + goto out; + + HVS_WRITE(SCALER6_DISPX_CTRL0(chan), + HVS_READ(SCALER6_DISPX_CTRL0(chan)) | SCALER6_DISPX_CTRL0_RESET); + + HVS_WRITE(SCALER6_DISPX_CTRL0(chan), + HVS_READ(SCALER6_DISPX_CTRL0(chan)) & ~SCALER6_DISPX_CTRL0_ENB); + + WARN_ON_ONCE(VC4_GET_FIELD(HVS_READ(SCALER6_DISPX_STATUS(chan)), + SCALER6_DISPX_STATUS_MODE) != + SCALER6_DISPX_STATUS_MODE_DISABLED); + +out: + drm_dev_exit(idx); +} + +void vc4_hvs_stop_channel(struct vc4_hvs *hvs, unsigned int chan) +{ + struct vc4_dev *vc4 = hvs->vc4; + + if (vc4->gen >= VC4_GEN_6_C) + __vc6_hvs_stop_channel(hvs, chan); + else + __vc4_hvs_stop_channel(hvs, chan); +} + int vc4_hvs_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state) { struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc); struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc_state); + struct vc4_hvs_dlist_allocation *alloc; struct drm_device *dev = crtc->dev; struct vc4_dev *vc4 = to_vc4_dev(dev); struct drm_plane *plane; - unsigned long flags; const struct drm_plane_state *plane_state; u32 dlist_count = 0; - int ret; /* The pixelvalve can only feed one encoder (and encoders are * 1:1 with connectors.) @@ -460,17 +1124,26 @@ int vc4_hvs_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state) if (hweight32(crtc_state->connector_mask) > 1) return -EINVAL; - drm_atomic_crtc_state_for_each_plane_state(plane, plane_state, crtc_state) - dlist_count += vc4_plane_dlist_size(plane_state); + drm_atomic_crtc_state_for_each_plane_state(plane, plane_state, crtc_state) { + u32 plane_dlist_count = vc4_plane_dlist_size(plane_state); + + drm_dbg_driver(dev, "[CRTC:%d:%s] Found [PLANE:%d:%s] with DLIST size: %u\n", + crtc->base.id, crtc->name, + plane->base.id, plane->name, + plane_dlist_count); + + dlist_count += plane_dlist_count; + } dlist_count++; /* Account for SCALER_CTL0_END. */ - spin_lock_irqsave(&vc4->hvs->mm_lock, flags); - ret = drm_mm_insert_node(&vc4->hvs->dlist_mm, &vc4_state->mm, - dlist_count); - spin_unlock_irqrestore(&vc4->hvs->mm_lock, flags); - if (ret) - return ret; + drm_dbg_driver(dev, "[CRTC:%d:%s] Allocating DLIST block with size: %u\n", + crtc->base.id, crtc->name, dlist_count); + alloc = vc4_hvs_alloc_dlist_entry(vc4->hvs, vc4_state->assigned_channel, dlist_count); + if (IS_ERR(alloc)) + return PTR_ERR(alloc); + + vc4_state->mm = alloc; return 0; } @@ -486,8 +1159,17 @@ static void vc4_hvs_install_dlist(struct drm_crtc *crtc) if (!drm_dev_enter(dev, &idx)) return; - HVS_WRITE(SCALER_DISPLISTX(vc4_state->assigned_channel), - vc4_state->mm.start); + WARN_ON(!vc4_state->mm); + + vc4_state->mm->dlist_programmed = true; + + if (vc4->gen >= VC4_GEN_6_C) + HVS_WRITE(SCALER6_DISPX_LPTRS(vc4_state->assigned_channel), + VC4_SET_FIELD(vc4_state->mm->mm_node.start, + SCALER6_DISPX_LPTRS_HEADE)); + else + HVS_WRITE(SCALER_DISPLISTX(vc4_state->assigned_channel), + vc4_state->mm->mm_node.start); drm_dev_exit(idx); } @@ -514,8 +1196,10 @@ static void vc4_hvs_update_dlist(struct drm_crtc *crtc) spin_unlock_irqrestore(&dev->event_lock, flags); } + WARN_ON(!vc4_state->mm); + spin_lock_irqsave(&vc4_crtc->irq_lock, flags); - vc4_crtc->current_dlist = vc4_state->mm.start; + vc4_crtc->current_dlist = vc4_state->mm->mm_node.start; spin_unlock_irqrestore(&vc4_crtc->irq_lock, flags); } @@ -542,7 +1226,11 @@ void vc4_hvs_atomic_enable(struct drm_crtc *crtc, vc4_hvs_install_dlist(crtc); vc4_hvs_update_dlist(crtc); - vc4_hvs_init_channel(vc4->hvs, crtc, mode, oneshot); + + if (vc4->gen >= VC4_GEN_6_C) + vc6_hvs_init_channel(vc4->hvs, crtc, mode, oneshot); + else + vc4_hvs_init_channel(vc4->hvs, crtc, mode, oneshot); } void vc4_hvs_atomic_disable(struct drm_crtc *crtc, @@ -571,13 +1259,14 @@ void vc4_hvs_atomic_flush(struct drm_crtc *crtc, struct drm_plane *plane; struct vc4_plane_state *vc4_plane_state; bool debug_dump_regs = false; - bool enable_bg_fill = false; - u32 __iomem *dlist_start = vc4->hvs->dlist + vc4_state->mm.start; - u32 __iomem *dlist_next = dlist_start; + bool enable_bg_fill = true; + u32 __iomem *dlist_start, *dlist_next; unsigned int zpos = 0; bool found = false; int idx; + WARN_ON_ONCE(vc4->gen > VC4_GEN_6_D); + if (!drm_dev_enter(dev, &idx)) { vc4_crtc_send_vblank(crtc); return; @@ -591,6 +1280,9 @@ void vc4_hvs_atomic_flush(struct drm_crtc *crtc, vc4_hvs_dump_state(hvs); } + dlist_start = vc4->hvs->dlist + vc4_state->mm->mm_node.start; + dlist_next = dlist_start; + /* Copy all the active planes' dlist contents to the hardware dlist. */ do { found = false; @@ -624,15 +1316,22 @@ void vc4_hvs_atomic_flush(struct drm_crtc *crtc, writel(SCALER_CTL0_END, dlist_next); dlist_next++; - WARN_ON_ONCE(dlist_next - dlist_start != vc4_state->mm.size); + WARN_ON(!vc4_state->mm); + WARN_ON_ONCE(dlist_next - dlist_start != vc4_state->mm->mm_node.size); - if (enable_bg_fill) + if (vc4->gen >= VC4_GEN_6_C) { /* This sets a black background color fill, as is the case * with other DRM drivers. */ + hvs->bg_fill[channel] = enable_bg_fill; + } else { + /* we can actually run with a lower core clock when background + * fill is enabled on VC4_GEN_5 so leave it enabled always. + */ HVS_WRITE(SCALER_DISPBKGNDX(channel), HVS_READ(SCALER_DISPBKGNDX(channel)) | SCALER_DISPBKGND_FILL); + } /* Only update DISPLIST if the CRTC was already running and is not * being disabled. @@ -649,6 +1348,8 @@ void vc4_hvs_atomic_flush(struct drm_crtc *crtc, if (crtc->state->color_mgmt_changed) { u32 dispbkgndx = HVS_READ(SCALER_DISPBKGNDX(channel)); + WARN_ON_ONCE(vc4->gen > VC4_GEN_5); + if (crtc->state->gamma_lut) { vc4_hvs_update_gamma_lut(hvs, vc4_crtc); dispbkgndx |= SCALER_DISPBKGND_GAMMA; @@ -678,6 +1379,8 @@ void vc4_hvs_mask_underrun(struct vc4_hvs *hvs, int channel) u32 dispctrl; int idx; + WARN_ON(vc4->gen > VC4_GEN_5); + if (!drm_dev_enter(drm, &idx)) return; @@ -698,6 +1401,8 @@ void vc4_hvs_unmask_underrun(struct vc4_hvs *hvs, int channel) u32 dispctrl; int idx; + WARN_ON(vc4->gen > VC4_GEN_5); + if (!drm_dev_enter(drm, &idx)) return; @@ -732,6 +1437,8 @@ static irqreturn_t vc4_hvs_irq_handler(int irq, void *data) u32 status; u32 dspeislur; + WARN_ON(vc4->gen > VC4_GEN_5); + /* * NOTE: We don't need to protect the register access using * drm_dev_enter() there because the interrupt handler lifetime @@ -759,6 +1466,11 @@ static irqreturn_t vc4_hvs_irq_handler(int irq, void *data) irqret = IRQ_HANDLED; } + + if (status & SCALER_DISPSTAT_EOF(channel)) { + vc4_hvs_schedule_dlist_sweep(hvs, channel); + irqret = IRQ_HANDLED; + } } /* Clear every per-channel interrupt flag. */ @@ -769,6 +1481,36 @@ static irqreturn_t vc4_hvs_irq_handler(int irq, void *data) return irqret; } +static irqreturn_t vc6_hvs_eof_irq_handler(int irq, void *data) +{ + struct drm_device *dev = data; + struct vc4_dev *vc4 = to_vc4_dev(dev); + struct vc4_hvs *hvs = vc4->hvs; + unsigned int i; + + for (i = 0; i < HVS_NUM_CHANNELS; i++) { + if (!hvs->eof_irq[i].enabled) + continue; + + if (hvs->eof_irq[i].desc != irq) + continue; + + if (hvs->bg_fill[i]) + HVS_WRITE(SCALER6_DISPX_CTRL1(i), + HVS_READ(SCALER6_DISPX_CTRL1(i)) | + SCALER6_DISPX_CTRL1_BGENB); + else + HVS_WRITE(SCALER6_DISPX_CTRL1(i), + HVS_READ(SCALER6_DISPX_CTRL1(i)) & + ~SCALER6_DISPX_CTRL1_BGENB); + + vc4_hvs_schedule_dlist_sweep(hvs, i); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + int vc4_hvs_debugfs_init(struct drm_minor *minor) { struct drm_device *drm = minor->dev; @@ -783,129 +1525,160 @@ int vc4_hvs_debugfs_init(struct drm_minor *minor) minor->debugfs_root, &vc4->load_tracker_enabled); - drm_debugfs_add_file(drm, "hvs_dlists", vc4_hvs_debugfs_dlist, NULL); + if (vc4->gen >= VC4_GEN_6_C) { + drm_debugfs_add_file(drm, "hvs_dlists", vc6_hvs_debugfs_dlist, NULL); + drm_debugfs_add_file(drm, "hvs_upm", vc6_hvs_debugfs_upm_allocs, NULL); + } else { + drm_debugfs_add_file(drm, "hvs_dlists", vc4_hvs_debugfs_dlist, NULL); + } + + drm_debugfs_add_file(drm, "hvs_lbm", vc4_hvs_debugfs_lbm_allocs, NULL); drm_debugfs_add_file(drm, "hvs_underrun", vc4_hvs_debugfs_underrun, NULL); + drm_debugfs_add_file(drm, "hvs_dlist_allocs", vc4_hvs_debugfs_dlist_allocs, NULL); + vc4_debugfs_add_regset32(drm, "hvs_regs", &hvs->regset); return 0; } -struct vc4_hvs *__vc4_hvs_alloc(struct vc4_dev *vc4, struct platform_device *pdev) +struct vc4_hvs *__vc4_hvs_alloc(struct vc4_dev *vc4, + void __iomem *regs, + struct platform_device *pdev) { struct drm_device *drm = &vc4->base; struct vc4_hvs *hvs; + unsigned int dlist_start; + size_t dlist_size; + size_t lbm_size; + unsigned int i; hvs = drmm_kzalloc(drm, sizeof(*hvs), GFP_KERNEL); if (!hvs) return ERR_PTR(-ENOMEM); hvs->vc4 = vc4; + hvs->regs = regs; hvs->pdev = pdev; spin_lock_init(&hvs->mm_lock); + switch (vc4->gen) { + case VC4_GEN_4: + case VC4_GEN_5: + /* Set up the HVS display list memory manager. We never + * overwrite the setup from the bootloader (just 128b + * out of our 16K), since we don't want to scramble the + * screen when transitioning from the firmware's boot + * setup to runtime. + */ + dlist_start = HVS_BOOTLOADER_DLIST_END; + dlist_size = (SCALER_DLIST_SIZE >> 2) - HVS_BOOTLOADER_DLIST_END; + break; + + case VC4_GEN_6_C: + case VC4_GEN_6_D: + dlist_start = HVS_BOOTLOADER_DLIST_END; + + /* + * If we are running a test, it means that we can't + * access a register. Use a plausible size then. + */ + if (!kunit_get_current_test()) + dlist_size = HVS_READ(SCALER6_CXM_SIZE); + else + dlist_size = 4096; + + for (i = 0; i < VC4_NUM_UPM_HANDLES; i++) { + refcount_set(&hvs->upm_refcounts[i].refcount, 0); + hvs->upm_refcounts[i].hvs = hvs; + } + + break; + + default: + drm_err(drm, "Unknown VC4 generation: %d", vc4->gen); + return ERR_PTR(-ENODEV); + } + + drm_mm_init(&hvs->dlist_mm, dlist_start, dlist_size); + + hvs->dlist_mem_size = dlist_size; + + INIT_LIST_HEAD(&hvs->stale_dlist_entries); + INIT_WORK(&hvs->free_dlist_work, vc4_hvs_dlist_free_work); + /* Set up the HVS display list memory manager. We never * overwrite the setup from the bootloader (just 128b out of * our 16K), since we don't want to scramble the screen when * transitioning from the firmware's boot setup to runtime. */ - hvs->dlist_mem_size = (SCALER_DLIST_SIZE >> 2) - HVS_BOOTLOADER_DLIST_END; drm_mm_init(&hvs->dlist_mm, HVS_BOOTLOADER_DLIST_END, - hvs->dlist_mem_size); + (SCALER_DLIST_SIZE >> 2) - HVS_BOOTLOADER_DLIST_END); /* Set up the HVS LBM memory manager. We could have some more * complicated data structure that allowed reuse of LBM areas * between planes when they don't overlap on the screen, but * for now we just allocate globally. */ - if (vc4->gen == VC4_GEN_4) - /* 48k words of 2x12-bit pixels */ - drm_mm_init(&hvs->lbm_mm, 0, 48 * 1024); - else - /* 60k words of 4x12-bit pixels */ - drm_mm_init(&hvs->lbm_mm, 0, 60 * 1024); - - vc4->hvs = hvs; - - return hvs; -} - -static int vc4_hvs_bind(struct device *dev, struct device *master, void *data) -{ - struct platform_device *pdev = to_platform_device(dev); - struct drm_device *drm = dev_get_drvdata(master); - struct vc4_dev *vc4 = to_vc4_dev(drm); - struct vc4_hvs *hvs = NULL; - int ret; - u32 dispctrl; - u32 reg, top; - hvs = __vc4_hvs_alloc(vc4, NULL); - if (IS_ERR(hvs)) - return PTR_ERR(hvs); + switch (vc4->gen) { + case VC4_GEN_4: + /* 48k words of 2x12-bit pixels */ + lbm_size = 48 * SZ_1K; + break; - hvs->regs = vc4_ioremap_regs(pdev, 0); - if (IS_ERR(hvs->regs)) - return PTR_ERR(hvs->regs); + case VC4_GEN_5: + /* 60k words of 4x12-bit pixels */ + lbm_size = 60 * SZ_1K; + break; - hvs->regset.base = hvs->regs; - hvs->regset.regs = hvs_regs; - hvs->regset.nregs = ARRAY_SIZE(hvs_regs); + case VC4_GEN_6_C: + case VC4_GEN_6_D: + /* + * If we are running a test, it means that we can't + * access a register. Use a plausible size then. + */ + lbm_size = 1024; + break; - if (vc4->gen == VC4_GEN_5) { - struct rpi_firmware *firmware; - struct device_node *node; - unsigned int max_rate; + default: + drm_err(drm, "Unknown VC4 generation: %d", vc4->gen); + return ERR_PTR(-ENODEV); + } - node = rpi_firmware_find_node(); - if (!node) - return -EINVAL; + drm_mm_init(&hvs->lbm_mm, 0, lbm_size); + ida_init(&hvs->lbm_handles); - firmware = rpi_firmware_get(node); - of_node_put(node); - if (!firmware) - return -EPROBE_DEFER; - - hvs->core_clk = devm_clk_get(&pdev->dev, NULL); - if (IS_ERR(hvs->core_clk)) { - dev_err(&pdev->dev, "Couldn't get core clock\n"); - return PTR_ERR(hvs->core_clk); - } + if (vc4->gen >= VC4_GEN_6_C) { + ida_init(&hvs->upm_handles); - max_rate = rpi_firmware_clk_get_max_rate(firmware, - RPI_FIRMWARE_CORE_CLK_ID); - rpi_firmware_put(firmware); - if (max_rate >= 550000000) - hvs->vc5_hdmi_enable_hdmi_20 = true; + /* + * NOTE: On BCM2712, the size can also be read through + * the SCALER_UBM_SIZE register. We would need to do a + * register access though, which we can't do with kunit + * that also uses this function to create its mock + * device. + */ + drm_mm_init(&hvs->upm_mm, 0, 1024 * HVS_UBM_WORD_SIZE); + } - if (max_rate >= 600000000) - hvs->vc5_hdmi_enable_4096by2160 = true; - hvs->max_core_rate = max_rate; + vc4->hvs = hvs; - ret = clk_prepare_enable(hvs->core_clk); - if (ret) { - dev_err(&pdev->dev, "Couldn't enable the core clock\n"); - return ret; - } - } + return hvs; +} - if (vc4->gen == VC4_GEN_4) - hvs->dlist = hvs->regs + SCALER_DLIST_START; - else - hvs->dlist = hvs->regs + SCALER5_DLIST_START; +static int vc4_hvs_hw_init(struct vc4_hvs *hvs) +{ + struct vc4_dev *vc4 = hvs->vc4; + u32 dispctrl, reg; - /* Upload filter kernels. We only have the one for now, so we - * keep it around for the lifetime of the driver. - */ - ret = vc4_hvs_upload_linear_kernel(hvs, - &hvs->mitchell_netravali_filter, - mitchell_netravali_1_3_1_3_kernel); - if (ret) - return ret; + dispctrl = HVS_READ(SCALER_DISPCTRL); + dispctrl |= SCALER_DISPCTRL_ENABLE; + HVS_WRITE(SCALER_DISPCTRL, dispctrl); reg = HVS_READ(SCALER_DISPECTRL); reg &= ~SCALER_DISPECTRL_DSP2_MUX_MASK; @@ -928,8 +1701,6 @@ static int vc4_hvs_bind(struct device *dev, struct device *master, void *data) reg | VC4_SET_FIELD(3, SCALER_DISPDITHER_DSP5_MUX)); dispctrl = HVS_READ(SCALER_DISPCTRL); - - dispctrl |= SCALER_DISPCTRL_ENABLE; dispctrl |= SCALER_DISPCTRL_DISPEIRQ(0) | SCALER_DISPCTRL_DISPEIRQ(1) | SCALER_DISPCTRL_DISPEIRQ(2); @@ -987,9 +1758,160 @@ static int vc4_hvs_bind(struct device *dev, struct device *master, void *data) HVS_WRITE(SCALER_DISPCTRL, dispctrl); - /* Recompute Composite Output Buffer (COB) allocations for the displays + return 0; +} + +#define CFC1_N_NL_CSC_CTRL(x) (0xa000 + ((x) * 0x3000)) +#define CFC1_N_MA_CSC_COEFF_C00(x) (0xa008 + ((x) * 0x3000)) +#define CFC1_N_MA_CSC_COEFF_C01(x) (0xa00c + ((x) * 0x3000)) +#define CFC1_N_MA_CSC_COEFF_C02(x) (0xa010 + ((x) * 0x3000)) +#define CFC1_N_MA_CSC_COEFF_C03(x) (0xa014 + ((x) * 0x3000)) +#define CFC1_N_MA_CSC_COEFF_C04(x) (0xa018 + ((x) * 0x3000)) +#define CFC1_N_MA_CSC_COEFF_C10(x) (0xa01c + ((x) * 0x3000)) +#define CFC1_N_MA_CSC_COEFF_C11(x) (0xa020 + ((x) * 0x3000)) +#define CFC1_N_MA_CSC_COEFF_C12(x) (0xa024 + ((x) * 0x3000)) +#define CFC1_N_MA_CSC_COEFF_C13(x) (0xa028 + ((x) * 0x3000)) +#define CFC1_N_MA_CSC_COEFF_C14(x) (0xa02c + ((x) * 0x3000)) +#define CFC1_N_MA_CSC_COEFF_C20(x) (0xa030 + ((x) * 0x3000)) +#define CFC1_N_MA_CSC_COEFF_C21(x) (0xa034 + ((x) * 0x3000)) +#define CFC1_N_MA_CSC_COEFF_C22(x) (0xa038 + ((x) * 0x3000)) +#define CFC1_N_MA_CSC_COEFF_C23(x) (0xa03c + ((x) * 0x3000)) +#define CFC1_N_MA_CSC_COEFF_C24(x) (0xa040 + ((x) * 0x3000)) + +#define SCALER_PI_CMP_CSC_RED0(x) (0x200 + ((x) * 0x40)) +#define SCALER_PI_CMP_CSC_RED1(x) (0x204 + ((x) * 0x40)) +#define SCALER_PI_CMP_CSC_RED_CLAMP(x) (0x208 + ((x) * 0x40)) +#define SCALER_PI_CMP_CSC_CFG(x) (0x20c + ((x) * 0x40)) +#define SCALER_PI_CMP_CSC_GREEN0(x) (0x210 + ((x) * 0x40)) +#define SCALER_PI_CMP_CSC_GREEN1(x) (0x214 + ((x) * 0x40)) +#define SCALER_PI_CMP_CSC_GREEN_CLAMP(x) (0x218 + ((x) * 0x40)) +#define SCALER_PI_CMP_CSC_BLUE0(x) (0x220 + ((x) * 0x40)) +#define SCALER_PI_CMP_CSC_BLUE1(x) (0x224 + ((x) * 0x40)) +#define SCALER_PI_CMP_CSC_BLUE_CLAMP(x) (0x228 + ((x) * 0x40)) + +/* 4 S2.22 multiplication factors, and 1 S9.15 addititive element for each of 3 + * output components + */ +struct vc6_csc_coeff_entry { + u32 csc[3][5]; +}; + +static const struct vc6_csc_coeff_entry csc_coeffs[2][3] = { + [DRM_COLOR_YCBCR_LIMITED_RANGE] = { + [DRM_COLOR_YCBCR_BT601] = { + .csc = { + { 0x004A8542, 0x0, 0x0066254A, 0x0, 0xFF908A0D }, + { 0x004A8542, 0xFFE6ED5D, 0xFFCBF856, 0x0, 0x0043C9A3 }, + { 0x004A8542, 0x00811A54, 0x0, 0x0, 0xFF759502 } + } + }, + [DRM_COLOR_YCBCR_BT709] = { + .csc = { + { 0x004A8542, 0x0, 0x0072BC44, 0x0, 0xFF83F312 }, + { 0x004A8542, 0xFFF25A22, 0xFFDDE4D0, 0x0, 0x00267064 }, + { 0x004A8542, 0x00873197, 0x0, 0x0, 0xFF6F7DC0 } + } + }, + [DRM_COLOR_YCBCR_BT2020] = { + .csc = { + { 0x004A8542, 0x0, 0x006B4A17, 0x0, 0xFF8B653F }, + { 0x004A8542, 0xFFF402D9, 0xFFDDE4D0, 0x0, 0x0024C7AE }, + { 0x004A8542, 0x008912CC, 0x0, 0x0, 0xFF6D9C8B } + } + } + }, + [DRM_COLOR_YCBCR_FULL_RANGE] = { + [DRM_COLOR_YCBCR_BT601] = { + .csc = { + { 0x00400000, 0x0, 0x0059BA5E, 0x0, 0xFFA645A1 }, + { 0x00400000, 0xFFE9F9AC, 0xFFD24B97, 0x0, 0x0043BABB }, + { 0x00400000, 0x00716872, 0x0, 0x0, 0xFF8E978D } + } + }, + [DRM_COLOR_YCBCR_BT709] = { + .csc = { + { 0x00400000, 0x0, 0x0064C985, 0x0, 0xFF9B367A }, + { 0x00400000, 0xFFF402E1, 0xFFE20A40, 0x0, 0x0029F2DE }, + { 0x00400000, 0x0076C226, 0x0, 0x0, 0xFF893DD9 } + } + }, + [DRM_COLOR_YCBCR_BT2020] = { + .csc = { + { 0x00400000, 0x0, 0x005E3F14, 0x0, 0xFFA1C0EB }, + { 0x00400000, 0xFFF577F6, 0xFFDB580F, 0x0, 0x002F2FFA }, + { 0x00400000, 0x007868DB, 0x0, 0x0, 0xFF879724 } + } + } + } +}; + +static int vc6_hvs_hw_init(struct vc4_hvs *hvs) +{ + const struct vc6_csc_coeff_entry *coeffs; + unsigned int i; + + HVS_WRITE(SCALER6_CONTROL, + SCALER6_CONTROL_HVS_EN | + VC4_SET_FIELD(8, SCALER6_CONTROL_PF_LINES) | + VC4_SET_FIELD(15, SCALER6_CONTROL_MAX_REQS)); + + /* Set HVS arbiter priority to max */ + HVS_WRITE(SCALER6(PRI_MAP0), 0xffffffff); + HVS_WRITE(SCALER6(PRI_MAP1), 0xffffffff); + + if (hvs->vc4->gen == VC4_GEN_6_C) { + for (i = 0; i < 6; i++) { + coeffs = &csc_coeffs[i / 3][i % 3]; + + HVS_WRITE(CFC1_N_MA_CSC_COEFF_C00(i), coeffs->csc[0][0]); + HVS_WRITE(CFC1_N_MA_CSC_COEFF_C01(i), coeffs->csc[0][1]); + HVS_WRITE(CFC1_N_MA_CSC_COEFF_C02(i), coeffs->csc[0][2]); + HVS_WRITE(CFC1_N_MA_CSC_COEFF_C03(i), coeffs->csc[0][3]); + HVS_WRITE(CFC1_N_MA_CSC_COEFF_C04(i), coeffs->csc[0][4]); + + HVS_WRITE(CFC1_N_MA_CSC_COEFF_C10(i), coeffs->csc[1][0]); + HVS_WRITE(CFC1_N_MA_CSC_COEFF_C11(i), coeffs->csc[1][1]); + HVS_WRITE(CFC1_N_MA_CSC_COEFF_C12(i), coeffs->csc[1][2]); + HVS_WRITE(CFC1_N_MA_CSC_COEFF_C13(i), coeffs->csc[1][3]); + HVS_WRITE(CFC1_N_MA_CSC_COEFF_C14(i), coeffs->csc[1][4]); + + HVS_WRITE(CFC1_N_MA_CSC_COEFF_C20(i), coeffs->csc[2][0]); + HVS_WRITE(CFC1_N_MA_CSC_COEFF_C21(i), coeffs->csc[2][1]); + HVS_WRITE(CFC1_N_MA_CSC_COEFF_C22(i), coeffs->csc[2][2]); + HVS_WRITE(CFC1_N_MA_CSC_COEFF_C23(i), coeffs->csc[2][3]); + HVS_WRITE(CFC1_N_MA_CSC_COEFF_C24(i), coeffs->csc[2][4]); + + HVS_WRITE(CFC1_N_NL_CSC_CTRL(i), BIT(15)); + } + } else { + for (i = 0; i < 8; i++) { + HVS_WRITE(SCALER_PI_CMP_CSC_RED0(i), 0x1f002566); + HVS_WRITE(SCALER_PI_CMP_CSC_RED1(i), 0x3994); + HVS_WRITE(SCALER_PI_CMP_CSC_RED_CLAMP(i), 0xfff00000); + HVS_WRITE(SCALER_PI_CMP_CSC_CFG(i), 0x1); + HVS_WRITE(SCALER_PI_CMP_CSC_GREEN0(i), 0x18002566); + HVS_WRITE(SCALER_PI_CMP_CSC_GREEN1(i), 0xf927eee2); + HVS_WRITE(SCALER_PI_CMP_CSC_GREEN_CLAMP(i), 0xfff00000); + HVS_WRITE(SCALER_PI_CMP_CSC_BLUE0(i), 0x18002566); + HVS_WRITE(SCALER_PI_CMP_CSC_BLUE1(i), 0x43d80000); + HVS_WRITE(SCALER_PI_CMP_CSC_BLUE_CLAMP(i), 0xfff00000); + } + } + + return 0; +} + +static int vc4_hvs_cob_init(struct vc4_hvs *hvs) +{ + struct vc4_dev *vc4 = hvs->vc4; + u32 reg, top, base; + + /* + * Recompute Composite Output Buffer (COB) allocations for the + * displays */ - if (vc4->gen == VC4_GEN_4) { + switch (vc4->gen) { + case VC4_GEN_4: /* The COB is 20736 pixels, or just over 10 lines at 2048 wide. * The bottom 2048 pixels are full 32bpp RGBA (intended for the * TXP composing RGBA to memory), whilst the remainder are only @@ -1013,7 +1935,9 @@ static int vc4_hvs_bind(struct device *dev, struct device *master, void *data) top = VC4_COB_SIZE; reg |= (top - 1) << 16; HVS_WRITE(SCALER_DISPBASE0, reg); - } else { + break; + + case VC4_GEN_5: /* The COB is 44416 pixels, or 10.8 lines at 4096 wide. * The bottom 4096 pixels are full RGBA (intended for the TXP * composing RGBA to memory), whilst the remainder are only @@ -1039,13 +1963,191 @@ static int vc4_hvs_bind(struct device *dev, struct device *master, void *data) top = VC5_COB_SIZE; reg |= top << 16; HVS_WRITE(SCALER_DISPBASE0, reg); + break; + + case VC4_GEN_6_C: + case VC4_GEN_6_D: + #define VC6_COB_LINE_WIDTH 3840 + #define VC6_COB_NUM_LINES 4 + base = 0; + top = 3840; + + HVS_WRITE(SCALER6_DISPX_COB(2), + VC4_SET_FIELD(top, SCALER6_DISPX_COB_TOP) | + VC4_SET_FIELD(base, SCALER6_DISPX_COB_BASE)); + + base = top + 16; + top += VC6_COB_LINE_WIDTH * VC6_COB_NUM_LINES; + + HVS_WRITE(SCALER6_DISPX_COB(1), + VC4_SET_FIELD(top, SCALER6_DISPX_COB_TOP) | + VC4_SET_FIELD(base, SCALER6_DISPX_COB_BASE)); + + base = top + 16; + top += VC6_COB_LINE_WIDTH * VC6_COB_NUM_LINES; + + HVS_WRITE(SCALER6_DISPX_COB(0), + VC4_SET_FIELD(top, SCALER6_DISPX_COB_TOP) | + VC4_SET_FIELD(base, SCALER6_DISPX_COB_BASE)); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int vc4_hvs_bind(struct device *dev, struct device *master, void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct drm_device *drm = dev_get_drvdata(master); + struct vc4_dev *vc4 = to_vc4_dev(drm); + struct vc4_hvs *hvs = NULL; + void __iomem *regs; + int ret; + + regs = vc4_ioremap_regs(pdev, 0); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + hvs = __vc4_hvs_alloc(vc4, regs, pdev); + if (IS_ERR(hvs)) + return PTR_ERR(hvs); + + hvs->regset.base = hvs->regs; + + if (vc4->gen == VC4_GEN_6_C) { + hvs->regset.regs = vc6_hvs_regs; + hvs->regset.nregs = ARRAY_SIZE(vc6_hvs_regs); + + if (VC4_GET_FIELD(HVS_READ(SCALER6_VERSION), SCALER6_VERSION) == + SCALER6_VERSION_D0) { + vc4->gen = VC4_GEN_6_D; + hvs->regset.regs = vc6_d_hvs_regs; + hvs->regset.nregs = ARRAY_SIZE(vc6_d_hvs_regs); + } + } else { + hvs->regset.regs = vc4_hvs_regs; + hvs->regset.nregs = ARRAY_SIZE(vc4_hvs_regs); + } + + if (vc4->gen >= VC4_GEN_5) { + struct rpi_firmware *firmware; + struct device_node *node; + unsigned int max_rate; + + node = rpi_firmware_find_node(); + if (!node) + return -EINVAL; + + firmware = rpi_firmware_get(node); + of_node_put(node); + if (!firmware) + return -EPROBE_DEFER; + + hvs->core_clk = devm_clk_get(&pdev->dev, + (vc4->gen >= VC4_GEN_6_C) ? "core" : NULL); + if (IS_ERR(hvs->core_clk)) { + dev_err(&pdev->dev, "Couldn't get core clock\n"); + return PTR_ERR(hvs->core_clk); + } + + hvs->disp_clk = devm_clk_get(&pdev->dev, + (vc4->gen >= VC4_GEN_6_C) ? "disp" : NULL); + if (IS_ERR(hvs->disp_clk)) { + dev_err(&pdev->dev, "Couldn't get disp clock\n"); + return PTR_ERR(hvs->disp_clk); + } + + max_rate = rpi_firmware_clk_get_max_rate(firmware, + RPI_FIRMWARE_CORE_CLK_ID); + rpi_firmware_put(firmware); + if (max_rate >= 550000000) + hvs->vc5_hdmi_enable_hdmi_20 = true; + + if (max_rate >= 600000000) + hvs->vc5_hdmi_enable_4096by2160 = true; + + hvs->max_core_rate = max_rate; + + ret = clk_prepare_enable(hvs->core_clk); + if (ret) { + dev_err(&pdev->dev, "Couldn't enable the core clock\n"); + return ret; + } + + ret = clk_prepare_enable(hvs->disp_clk); + if (ret) { + dev_err(&pdev->dev, "Couldn't enable the disp clock\n"); + return ret; + } } - ret = devm_request_irq(dev, platform_get_irq(pdev, 0), - vc4_hvs_irq_handler, 0, "vc4 hvs", drm); + if (vc4->gen >= VC4_GEN_5) + hvs->dlist = hvs->regs + SCALER5_DLIST_START; + else + hvs->dlist = hvs->regs + SCALER_DLIST_START; + + if (vc4->gen >= VC4_GEN_6_C) + ret = vc6_hvs_hw_init(hvs); + else + ret = vc4_hvs_hw_init(hvs); if (ret) return ret; + /* Upload filter kernels. We only have the two for now, so we + * keep them around for the lifetime of the driver. + */ + ret = vc4_hvs_upload_linear_kernel(hvs, + &hvs->mitchell_netravali_filter, + mitchell_netravali_1_3_1_3_kernel); + if (ret) + return ret; + ret = vc4_hvs_upload_linear_kernel(hvs, + &hvs->nearest_neighbour_filter, + nearest_neighbour_kernel); + if (ret) + return ret; + + ret = vc4_hvs_cob_init(hvs); + if (ret) + return ret; + + if (vc4->gen < VC4_GEN_6_C) { + ret = devm_request_irq(dev, platform_get_irq(pdev, 0), + vc4_hvs_irq_handler, 0, "vc4 hvs", drm); + if (ret) + return ret; + } else { + unsigned int i; + + for (i = 0; i < HVS_NUM_CHANNELS; i++) { + char irq_name[16]; + int irq; + + snprintf(irq_name, sizeof(irq_name), "ch%u-eof", i); + + irq = platform_get_irq_byname(pdev, irq_name); + if (irq < 0) { + dev_err(&pdev->dev, + "Couldn't get %s interrupt: %d\n", + irq_name, irq); + + return irq; + } + + ret = devm_request_irq(&pdev->dev, + irq, + vc6_hvs_eof_irq_handler, + IRQF_NO_AUTOEN, + dev_name(&pdev->dev), + drm); + + hvs->eof_irq[i].desc = irq; + } + } + return 0; } @@ -1059,6 +2161,8 @@ static void vc4_hvs_unbind(struct device *dev, struct device *master, if (drm_mm_node_allocated(&vc4->hvs->mitchell_netravali_filter)) drm_mm_remove_node(&vc4->hvs->mitchell_netravali_filter); + if (drm_mm_node_allocated(&vc4->hvs->nearest_neighbour_filter)) + drm_mm_remove_node(&vc4->hvs->nearest_neighbour_filter); drm_mm_for_each_node_safe(node, next, &vc4->hvs->dlist_mm) drm_mm_remove_node(node); @@ -1069,6 +2173,10 @@ static void vc4_hvs_unbind(struct device *dev, struct device *master, drm_mm_remove_node(node); drm_mm_takedown(&vc4->hvs->lbm_mm); + /* we no longer require a minimum clock rate */ + clk_set_min_rate(hvs->disp_clk, 0); + clk_disable_unprepare(hvs->disp_clk); + clk_set_min_rate(hvs->core_clk, 0); clk_disable_unprepare(hvs->core_clk); vc4->hvs = NULL; @@ -1091,6 +2199,7 @@ static void vc4_hvs_dev_remove(struct platform_device *pdev) static const struct of_device_id vc4_hvs_dt_match[] = { { .compatible = "brcm,bcm2711-hvs" }, + { .compatible = "brcm,bcm2712-hvs" }, { .compatible = "brcm,bcm2835-hvs" }, {} }; diff --git a/drivers/gpu/drm/vc4/vc4_irq.c b/drivers/gpu/drm/vc4/vc4_irq.c index 968356d1b91dfb..69b399f3b8027c 100644 --- a/drivers/gpu/drm/vc4/vc4_irq.c +++ b/drivers/gpu/drm/vc4/vc4_irq.c @@ -263,7 +263,7 @@ vc4_irq_enable(struct drm_device *dev) { struct vc4_dev *vc4 = to_vc4_dev(dev); - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return; if (!vc4->v3d) @@ -280,7 +280,7 @@ vc4_irq_disable(struct drm_device *dev) { struct vc4_dev *vc4 = to_vc4_dev(dev); - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return; if (!vc4->v3d) @@ -303,7 +303,7 @@ int vc4_irq_install(struct drm_device *dev, int irq) struct vc4_dev *vc4 = to_vc4_dev(dev); int ret; - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return -ENODEV; if (irq == IRQ_NOTCONNECTED) @@ -324,7 +324,7 @@ void vc4_irq_uninstall(struct drm_device *dev) { struct vc4_dev *vc4 = to_vc4_dev(dev); - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return; vc4_irq_disable(dev); @@ -337,7 +337,7 @@ void vc4_irq_reset(struct drm_device *dev) struct vc4_dev *vc4 = to_vc4_dev(dev); unsigned long irqflags; - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return; /* Acknowledge any stale IRQs. */ diff --git a/drivers/gpu/drm/vc4/vc4_kms.c b/drivers/gpu/drm/vc4/vc4_kms.c index bddfcad1095013..2e6ae0d04518c3 100644 --- a/drivers/gpu/drm/vc4/vc4_kms.c +++ b/drivers/gpu/drm/vc4/vc4_kms.c @@ -138,6 +138,11 @@ vc4_ctm_commit(struct vc4_dev *vc4, struct drm_atomic_state *state) struct vc4_ctm_state *ctm_state = to_vc4_ctm_state(vc4->ctm_manager.state); struct drm_color_ctm *ctm = ctm_state->ctm; + if (vc4->firmware_kms) + return; + + WARN_ON_ONCE(vc4->gen > VC4_GEN_5); + if (ctm_state->fifo) { HVS_WRITE(SCALER_OLEDCOEF2, VC4_SET_FIELD(vc4_ctm_s31_32_to_s0_9(ctm->matrix[0]), @@ -213,16 +218,17 @@ static void vc4_hvs_pv_muxing_commit(struct vc4_dev *vc4, struct drm_crtc *crtc; unsigned int i; + WARN_ON_ONCE(vc4->gen != VC4_GEN_4); + for_each_new_crtc_in_state(state, crtc, crtc_state, i) { struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc); struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc_state); u32 dispctrl; - u32 dsp3_mux; if (!crtc_state->active) continue; - if (vc4_state->assigned_channel != 2) + if (vc4_crtc->data->hvs_output != 2) continue; /* @@ -230,19 +236,28 @@ static void vc4_hvs_pv_muxing_commit(struct vc4_dev *vc4, * FIFO X'. * SCALER_DISPCTRL_DSP3 = 3 means 'disable DSP 3'. * - * DSP3 is connected to FIFO2 unless the transposer is - * enabled. In this case, FIFO 2 is directly accessed by the - * TXP IP, and we need to disable the FIFO2 -> pixelvalve1 - * route. + * It is more likely that we want the TXP than 3 displays, so + * handle the mapping of DSP3 to any available FIFO. + * + * TXP can also run with a lower panic level than a live display, + * as it doesn't have the same real-time constraint. */ - if (vc4_crtc->feeds_txp) - dsp3_mux = VC4_SET_FIELD(3, SCALER_DISPCTRL_DSP3_MUX); - else - dsp3_mux = VC4_SET_FIELD(2, SCALER_DISPCTRL_DSP3_MUX); - dispctrl = HVS_READ(SCALER_DISPCTRL) & - ~SCALER_DISPCTRL_DSP3_MUX_MASK; - HVS_WRITE(SCALER_DISPCTRL, dispctrl | dsp3_mux); + ~SCALER_DISPCTRL_PANIC2_MASK; + + if (vc4_crtc->feeds_txp) { + dispctrl |= VC4_SET_FIELD(0, SCALER_DISPCTRL_PANIC2); + drm_WARN_ON(&vc4->base, + VC4_GET_FIELD(HVS_READ(SCALER_DISPCTRL), + SCALER_DISPCTRL_DSP3_MUX) == 2); + } else { + dispctrl &= ~SCALER_DISPCTRL_DSP3_MUX_MASK; + dispctrl |= VC4_SET_FIELD(vc4_state->assigned_channel, + SCALER_DISPCTRL_DSP3_MUX); + dispctrl |= VC4_SET_FIELD(2, SCALER_DISPCTRL_PANIC2); + } + + HVS_WRITE(SCALER_DISPCTRL, dispctrl); } } @@ -256,6 +271,8 @@ static void vc5_hvs_pv_muxing_commit(struct vc4_dev *vc4, unsigned int i; u32 reg; + WARN_ON_ONCE(vc4->gen != VC4_GEN_5); + for_each_new_crtc_in_state(state, crtc, crtc_state, i) { struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc_state); struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc); @@ -320,17 +337,62 @@ static void vc5_hvs_pv_muxing_commit(struct vc4_dev *vc4, } } +static void vc6_hvs_pv_muxing_commit(struct vc4_dev *vc4, + struct drm_atomic_state *state) +{ + struct vc4_hvs *hvs = vc4->hvs; + struct drm_crtc_state *crtc_state; + struct drm_crtc *crtc; + unsigned int i; + + WARN_ON_ONCE(vc4->gen != VC4_GEN_6_C && vc4->gen != VC4_GEN_6_D); + + for_each_new_crtc_in_state(state, crtc, crtc_state, i) { + struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc_state); + struct vc4_encoder *vc4_encoder; + struct drm_encoder *encoder; + unsigned char mux; + u32 reg; + + if (!vc4_state->update_muxing) + continue; + + if (vc4_state->assigned_channel != 1) + continue; + + encoder = vc4_get_crtc_encoder(crtc, crtc_state); + vc4_encoder = to_vc4_encoder(encoder); + switch (vc4_encoder->type) { + case VC4_ENCODER_TYPE_HDMI1: + mux = 0; + break; + + case VC4_ENCODER_TYPE_TXP1: + mux = 2; + break; + + default: + drm_err(&vc4->base, "Unhandled encoder type for PV muxing %d", + vc4_encoder->type); + mux = 0; + break; + } + + reg = HVS_READ(SCALER6_CONTROL); + HVS_WRITE(SCALER6_CONTROL, + (reg & ~SCALER6_CONTROL_DSP1_TARGET_MASK) | + VC4_SET_FIELD(mux, SCALER6_CONTROL_DSP1_TARGET)); + } +} + static void vc4_atomic_commit_tail(struct drm_atomic_state *state) { struct drm_device *dev = state->dev; struct vc4_dev *vc4 = to_vc4_dev(dev); struct vc4_hvs *hvs = vc4->hvs; - struct drm_crtc_state *new_crtc_state; struct vc4_hvs_state *new_hvs_state; - struct drm_crtc *crtc; struct vc4_hvs_state *old_hvs_state; unsigned int channel; - int i; old_hvs_state = vc4_hvs_get_old_global_state(state); if (WARN_ON(IS_ERR(old_hvs_state))) @@ -340,14 +402,21 @@ static void vc4_atomic_commit_tail(struct drm_atomic_state *state) if (WARN_ON(IS_ERR(new_hvs_state))) return; - for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) { - struct vc4_crtc_state *vc4_crtc_state; + if (0 && vc4->gen < VC4_GEN_6_C) { + struct drm_crtc_state *new_crtc_state; + struct drm_crtc *crtc; + int i; - if (!new_crtc_state->commit) - continue; + for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) { + struct vc4_crtc_state *vc4_crtc_state; - vc4_crtc_state = to_vc4_crtc_state(new_crtc_state); - vc4_hvs_mask_underrun(hvs, vc4_crtc_state->assigned_channel); + if (!new_crtc_state->commit || vc4->firmware_kms) + continue; + + + vc4_crtc_state = to_vc4_crtc_state(new_crtc_state); + vc4_hvs_mask_underrun(hvs, vc4_crtc_state->assigned_channel); + } } for (channel = 0; channel < HVS_NUM_CHANNELS; channel++) { @@ -369,7 +438,7 @@ static void vc4_atomic_commit_tail(struct drm_atomic_state *state) old_hvs_state->fifo_state[channel].pending_commit = NULL; } - if (vc4->gen == VC4_GEN_5) { + if (vc4->gen == VC4_GEN_5 && !vc4->firmware_kms) { unsigned long state_rate = max(old_hvs_state->core_clock_rate, new_hvs_state->core_clock_rate); unsigned long core_rate = clamp_t(unsigned long, state_rate, @@ -382,16 +451,34 @@ static void vc4_atomic_commit_tail(struct drm_atomic_state *state) * modeset. */ WARN_ON(clk_set_min_rate(hvs->core_clk, core_rate)); + WARN_ON(clk_set_min_rate(hvs->disp_clk, core_rate)); } drm_atomic_helper_commit_modeset_disables(dev, state); - vc4_ctm_commit(vc4, state); + if (vc4->gen <= VC4_GEN_5) + vc4_ctm_commit(vc4, state); + + if (!vc4->firmware_kms) { + switch (vc4->gen) { + case VC4_GEN_4: + vc4_hvs_pv_muxing_commit(vc4, state); + break; + + case VC4_GEN_5: + vc5_hvs_pv_muxing_commit(vc4, state); + break; + + case VC4_GEN_6_C: + case VC4_GEN_6_D: + vc6_hvs_pv_muxing_commit(vc4, state); + break; - if (vc4->gen == VC4_GEN_5) - vc5_hvs_pv_muxing_commit(vc4, state); - else - vc4_hvs_pv_muxing_commit(vc4, state); + default: + drm_err(dev, "Unknown VC4 generation: %d", vc4->gen); + break; + } + } drm_atomic_helper_commit_planes(dev, state, DRM_PLANE_COMMIT_ACTIVE_ONLY); @@ -406,7 +493,7 @@ static void vc4_atomic_commit_tail(struct drm_atomic_state *state) drm_atomic_helper_cleanup_planes(dev, state); - if (vc4->gen == VC4_GEN_5) { + if (vc4->gen == VC4_GEN_5 && !vc4->firmware_kms) { unsigned long core_rate = min_t(unsigned long, hvs->max_core_rate, new_hvs_state->core_clock_rate); @@ -418,6 +505,7 @@ static void vc4_atomic_commit_tail(struct drm_atomic_state *state) * requirements. */ WARN_ON(clk_set_min_rate(hvs->core_clk, core_rate)); + WARN_ON(clk_set_min_rate(hvs->disp_clk, core_rate)); drm_dbg(dev, "Core clock actual rate: %lu Hz\n", clk_get_rate(hvs->core_clk)); @@ -426,11 +514,21 @@ static void vc4_atomic_commit_tail(struct drm_atomic_state *state) static int vc4_atomic_commit_setup(struct drm_atomic_state *state) { + struct drm_device *dev = state->dev; + struct vc4_dev *vc4 = to_vc4_dev(dev); struct drm_crtc_state *crtc_state; struct vc4_hvs_state *hvs_state; struct drm_crtc *crtc; unsigned int i; + /* We know for sure we don't want an async update here. Set + * state->legacy_cursor_update to false to prevent + * drm_atomic_helper_setup_commit() from auto-completing + * commit->flip_done. + */ + if (!vc4->firmware_kms) + state->legacy_cursor_update = false; + hvs_state = vc4_hvs_get_new_global_state(state); if (WARN_ON(IS_ERR(hvs_state))) return PTR_ERR(hvs_state); @@ -461,7 +559,7 @@ static struct drm_framebuffer *vc4_fb_create(struct drm_device *dev, struct vc4_dev *vc4 = to_vc4_dev(dev); struct drm_mode_fb_cmd2 mode_cmd_local; - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return ERR_PTR(-ENODEV); /* If the user didn't specify a modifier, use the @@ -586,17 +684,26 @@ static int vc4_load_tracker_atomic_check(struct drm_atomic_state *state) for_each_oldnew_plane_in_state(state, plane, old_plane_state, new_plane_state, i) { struct vc4_plane_state *vc4_plane_state; + struct vc4_crtc *vc4_crtc; if (old_plane_state->fb && old_plane_state->crtc) { vc4_plane_state = to_vc4_plane_state(old_plane_state); - load_state->membus_load -= vc4_plane_state->membus_load; - load_state->hvs_load -= vc4_plane_state->hvs_load; + vc4_crtc = to_vc4_crtc(old_plane_state->crtc); + + if (!vc4_crtc->feeds_txp) { + load_state->membus_load -= vc4_plane_state->membus_load; + load_state->hvs_load -= vc4_plane_state->hvs_load; + } } if (new_plane_state->fb && new_plane_state->crtc) { vc4_plane_state = to_vc4_plane_state(new_plane_state); - load_state->membus_load += vc4_plane_state->membus_load; - load_state->hvs_load += vc4_plane_state->hvs_load; + vc4_crtc = to_vc4_crtc(new_plane_state->crtc); + + if (!vc4_crtc->feeds_txp) { + load_state->membus_load += vc4_plane_state->membus_load; + load_state->hvs_load += vc4_plane_state->hvs_load; + } } } @@ -799,6 +906,7 @@ static int cmp_vc4_crtc_hvs_output(const void *a, const void *b) static int vc4_pv_muxing_atomic_check(struct drm_device *dev, struct drm_atomic_state *state) { + struct vc4_dev *vc4 = to_vc4_dev(state->dev); struct vc4_hvs_state *hvs_new_state; struct drm_crtc **sorted_crtcs; struct drm_crtc *crtc; @@ -806,6 +914,9 @@ static int vc4_pv_muxing_atomic_check(struct drm_device *dev, unsigned int i; int ret; + if (vc4->firmware_kms) + return 0; + hvs_new_state = vc4_hvs_get_global_state(state); if (IS_ERR(hvs_new_state)) return PTR_ERR(hvs_new_state); @@ -1056,7 +1167,10 @@ int vc4_kms_load(struct drm_device *dev) return ret; } - if (vc4->gen == VC4_GEN_5) { + if (vc4->gen >= VC4_GEN_6_C) { + dev->mode_config.max_width = 8192; + dev->mode_config.max_height = 8192; + } else if (vc4->gen >= VC4_GEN_5) { dev->mode_config.max_width = 7680; dev->mode_config.max_height = 7680; } else { diff --git a/drivers/gpu/drm/vc4/vc4_perfmon.c b/drivers/gpu/drm/vc4/vc4_perfmon.c index e4fda72c19f92f..f1342f917cf76e 100644 --- a/drivers/gpu/drm/vc4/vc4_perfmon.c +++ b/drivers/gpu/drm/vc4/vc4_perfmon.c @@ -23,7 +23,7 @@ void vc4_perfmon_get(struct vc4_perfmon *perfmon) return; vc4 = perfmon->dev; - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return; refcount_inc(&perfmon->refcnt); @@ -37,7 +37,7 @@ void vc4_perfmon_put(struct vc4_perfmon *perfmon) return; vc4 = perfmon->dev; - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return; if (refcount_dec_and_test(&perfmon->refcnt)) @@ -49,7 +49,7 @@ void vc4_perfmon_start(struct vc4_dev *vc4, struct vc4_perfmon *perfmon) unsigned int i; u32 mask; - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return; if (WARN_ON_ONCE(!perfmon || vc4->active_perfmon)) @@ -69,7 +69,7 @@ void vc4_perfmon_stop(struct vc4_dev *vc4, struct vc4_perfmon *perfmon, { unsigned int i; - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return; if (WARN_ON_ONCE(!vc4->active_perfmon || @@ -90,7 +90,7 @@ struct vc4_perfmon *vc4_perfmon_find(struct vc4_file *vc4file, int id) struct vc4_dev *vc4 = vc4file->dev; struct vc4_perfmon *perfmon; - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return NULL; mutex_lock(&vc4file->perfmon.lock); @@ -105,7 +105,7 @@ void vc4_perfmon_open_file(struct vc4_file *vc4file) { struct vc4_dev *vc4 = vc4file->dev; - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return; mutex_init(&vc4file->perfmon.lock); @@ -131,7 +131,7 @@ void vc4_perfmon_close_file(struct vc4_file *vc4file) { struct vc4_dev *vc4 = vc4file->dev; - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return; mutex_lock(&vc4file->perfmon.lock); @@ -151,7 +151,7 @@ int vc4_perfmon_create_ioctl(struct drm_device *dev, void *data, unsigned int i; int ret; - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return -ENODEV; if (!vc4->v3d) { @@ -205,7 +205,7 @@ int vc4_perfmon_destroy_ioctl(struct drm_device *dev, void *data, struct drm_vc4_perfmon_destroy *req = data; struct vc4_perfmon *perfmon; - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return -ENODEV; if (!vc4->v3d) { @@ -233,7 +233,7 @@ int vc4_perfmon_get_values_ioctl(struct drm_device *dev, void *data, struct vc4_perfmon *perfmon; int ret; - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return -ENODEV; if (!vc4->v3d) { @@ -241,11 +241,7 @@ int vc4_perfmon_get_values_ioctl(struct drm_device *dev, void *data, return -ENODEV; } - mutex_lock(&vc4file->perfmon.lock); - perfmon = idr_find(&vc4file->perfmon.idr, req->id); - vc4_perfmon_get(perfmon); - mutex_unlock(&vc4file->perfmon.lock); - + perfmon = vc4_perfmon_find(vc4file, req->id); if (!perfmon) return -EINVAL; diff --git a/drivers/gpu/drm/vc4/vc4_plane.c b/drivers/gpu/drm/vc4/vc4_plane.c index 866bc46ee6d53a..5ce0598c92117a 100644 --- a/drivers/gpu/drm/vc4/vc4_plane.c +++ b/drivers/gpu/drm/vc4/vc4_plane.c @@ -109,6 +109,18 @@ static const struct hvs_format { .pixel_order = HVS_PIXEL_ORDER_XYCRCB, .pixel_order_hvs5 = HVS_PIXEL_ORDER_XYCRCB, }, + { + .drm = DRM_FORMAT_YUV444, + .hvs = HVS_PIXEL_FORMAT_YCBCR_YUV422_3PLANE, + .pixel_order = HVS_PIXEL_ORDER_XYCBCR, + .pixel_order_hvs5 = HVS_PIXEL_ORDER_XYCBCR, + }, + { + .drm = DRM_FORMAT_YVU444, + .hvs = HVS_PIXEL_FORMAT_YCBCR_YUV422_3PLANE, + .pixel_order = HVS_PIXEL_ORDER_XYCRCB, + .pixel_order_hvs5 = HVS_PIXEL_ORDER_XYCRCB, + }, { .drm = DRM_FORMAT_YUV420, .hvs = HVS_PIXEL_FORMAT_YCBCR_YUV420_3PLANE, @@ -251,9 +263,13 @@ static const struct hvs_format *vc4_get_hvs_format(u32 drm_format) static enum vc4_scaling_mode vc4_get_scaling_mode(u32 src, u32 dst) { - if (dst == src) + if (dst == src >> 16) return VC4_SCALING_NONE; - if (3 * dst >= 2 * src) + + if (src <= (1 << 16)) + /* Source rectangle <= 1 pixel can use TPZ for resize/upscale */ + return VC4_SCALING_TPZ; + else if (3 * dst >= 2 * (src >> 16)) return VC4_SCALING_PPF; else return VC4_SCALING_TPZ; @@ -264,9 +280,12 @@ static bool plane_enabled(struct drm_plane_state *state) return state->fb && !WARN_ON(!state->crtc); } -static struct drm_plane_state *vc4_plane_duplicate_state(struct drm_plane *plane) +struct drm_plane_state *vc4_plane_duplicate_state(struct drm_plane *plane) { + struct vc4_dev *vc4 = to_vc4_dev(plane->dev); + struct vc4_hvs *hvs = vc4->hvs; struct vc4_plane_state *vc4_state; + unsigned int i; if (WARN_ON(!plane->state)) return NULL; @@ -275,7 +294,13 @@ static struct drm_plane_state *vc4_plane_duplicate_state(struct drm_plane *plane if (!vc4_state) return NULL; - memset(&vc4_state->lbm, 0, sizeof(vc4_state->lbm)); + for (i = 0; i < DRM_FORMAT_MAX_PLANES; i++) { + if (vc4_state->upm_handle[i]) + refcount_inc(&hvs->upm_refcounts[vc4_state->upm_handle[i]].refcount); + } + if (vc4_state->lbm_handle) + refcount_inc(&hvs->lbm_refcounts[vc4_state->lbm_handle].refcount); + vc4_state->dlist_initialized = 0; __drm_atomic_helper_plane_duplicate_state(plane, &vc4_state->base); @@ -294,18 +319,63 @@ static struct drm_plane_state *vc4_plane_duplicate_state(struct drm_plane *plane return &vc4_state->base; } -static void vc4_plane_destroy_state(struct drm_plane *plane, - struct drm_plane_state *state) +static void vc4_plane_release_lbm_ida(struct vc4_hvs *hvs, unsigned int lbm_handle) +{ + struct vc4_lbm_refcounts *refcount = &hvs->lbm_refcounts[lbm_handle]; + unsigned long irqflags; + + spin_lock_irqsave(&hvs->mm_lock, irqflags); + drm_mm_remove_node(&refcount->lbm); + spin_unlock_irqrestore(&hvs->mm_lock, irqflags); + refcount->lbm.start = 0; + refcount->lbm.size = 0; + refcount->size = 0; + + ida_free(&hvs->lbm_handles, lbm_handle); +} + +static void vc4_plane_release_upm_ida(struct vc4_hvs *hvs, unsigned int upm_handle) +{ + struct vc4_upm_refcounts *refcount = &hvs->upm_refcounts[upm_handle]; + unsigned long irqflags; + + spin_lock_irqsave(&hvs->mm_lock, irqflags); + drm_mm_remove_node(&refcount->upm); + spin_unlock_irqrestore(&hvs->mm_lock, irqflags); + refcount->upm.start = 0; + refcount->upm.size = 0; + refcount->size = 0; + + ida_free(&hvs->upm_handles, upm_handle); +} + +void vc4_plane_destroy_state(struct drm_plane *plane, + struct drm_plane_state *state) { struct vc4_dev *vc4 = to_vc4_dev(plane->dev); + struct vc4_hvs *hvs = vc4->hvs; struct vc4_plane_state *vc4_state = to_vc4_plane_state(state); + unsigned int i; - if (drm_mm_node_allocated(&vc4_state->lbm)) { - unsigned long irqflags; + if (vc4_state->lbm_handle) { + struct vc4_lbm_refcounts *refcount; + + refcount = &hvs->lbm_refcounts[vc4_state->lbm_handle]; + + if (refcount_dec_and_test(&refcount->refcount)) + vc4_plane_release_lbm_ida(hvs, vc4_state->lbm_handle); + } + + for (i = 0; i < DRM_FORMAT_MAX_PLANES; i++) { + struct vc4_upm_refcounts *refcount; + + if (!vc4_state->upm_handle[i]) + continue; - spin_lock_irqsave(&vc4->hvs->mm_lock, irqflags); - drm_mm_remove_node(&vc4_state->lbm); - spin_unlock_irqrestore(&vc4->hvs->mm_lock, irqflags); + refcount = &hvs->upm_refcounts[vc4_state->upm_handle[i]]; + + if (refcount_dec_and_test(&refcount->refcount)) + vc4_plane_release_upm_ida(hvs, vc4_state->upm_handle[i]); } kfree(vc4_state->dlist); @@ -314,7 +384,7 @@ static void vc4_plane_destroy_state(struct drm_plane *plane, } /* Called during init to allocate the plane's atomic state. */ -static void vc4_plane_reset(struct drm_plane *plane) +void vc4_plane_reset(struct drm_plane *plane) { struct vc4_plane_state *vc4_state; @@ -438,12 +508,11 @@ static int vc4_plane_setup_clipping_and_scaling(struct drm_plane_state *state) { struct vc4_plane_state *vc4_state = to_vc4_plane_state(state); struct drm_framebuffer *fb = state->fb; - struct drm_gem_dma_object *bo; int num_planes = fb->format->num_planes; struct drm_crtc_state *crtc_state; u32 h_subsample = fb->format->hsub; u32 v_subsample = fb->format->vsub; - int i, ret; + int ret; crtc_state = drm_atomic_get_existing_crtc_state(state->state, state->crtc); @@ -457,20 +526,10 @@ static int vc4_plane_setup_clipping_and_scaling(struct drm_plane_state *state) if (ret) return ret; - for (i = 0; i < num_planes; i++) { - bo = drm_fb_dma_get_gem_obj(fb, i); - vc4_state->offsets[i] = bo->dma_addr + fb->offsets[i]; - } - - /* - * We don't support subpixel source positioning for scaling, - * but fractional coordinates can be generated by clipping - * so just round for now - */ - vc4_state->src_x = DIV_ROUND_CLOSEST(state->src.x1, 1 << 16); - vc4_state->src_y = DIV_ROUND_CLOSEST(state->src.y1, 1 << 16); - vc4_state->src_w[0] = DIV_ROUND_CLOSEST(state->src.x2, 1 << 16) - vc4_state->src_x; - vc4_state->src_h[0] = DIV_ROUND_CLOSEST(state->src.y2, 1 << 16) - vc4_state->src_y; + vc4_state->src_x = state->src.x1; + vc4_state->src_y = state->src.y1; + vc4_state->src_w[0] = state->src.x2 - vc4_state->src_x; + vc4_state->src_h[0] = state->src.y2 - vc4_state->src_y; vc4_state->crtc_x = state->dst.x1; vc4_state->crtc_y = state->dst.y1; @@ -510,6 +569,12 @@ static int vc4_plane_setup_clipping_and_scaling(struct drm_plane_state *state) */ if (vc4_state->x_scaling[1] == VC4_SCALING_NONE) vc4_state->x_scaling[1] = VC4_SCALING_PPF; + + /* Similarly UV needs vertical scaling to be enabled. + * Without this a 1:1 scaled YUV422 plane isn't rendered. + */ + if (vc4_state->y_scaling[1] == VC4_SCALING_NONE) + vc4_state->y_scaling[1] = VC4_SCALING_PPF; } else { vc4_state->is_yuv = false; vc4_state->x_scaling[1] = VC4_SCALING_NONE; @@ -521,33 +586,104 @@ static int vc4_plane_setup_clipping_and_scaling(struct drm_plane_state *state) static void vc4_write_tpz(struct vc4_plane_state *vc4_state, u32 src, u32 dst) { + struct vc4_dev *vc4 = to_vc4_dev(vc4_state->base.plane->dev); u32 scale, recip; - scale = (1 << 16) * src / dst; + WARN_ON_ONCE(vc4->gen > VC4_GEN_6_D); - /* The specs note that while the reciprocal would be defined - * as (1<<32)/scale, ~0 is close enough. - */ - recip = ~0 / scale; + if ((dst << 16) < src) { + scale = src / dst; + + /* The specs note that while the reciprocal would be defined + * as (1<<32)/scale, ~0 is close enough. + */ + recip = ~0 / scale; + } else { + scale = (1 << 16) + 1; + recip = (1 << 16) - 1; + } vc4_dlist_write(vc4_state, + /* + * The BCM2712 is lacking BIT(31) compared to + * the previous generations, but we don't use + * it. + */ VC4_SET_FIELD(scale, SCALER_TPZ0_SCALE) | VC4_SET_FIELD(0, SCALER_TPZ0_IPHASE)); vc4_dlist_write(vc4_state, VC4_SET_FIELD(recip, SCALER_TPZ1_RECIP)); } -static void vc4_write_ppf(struct vc4_plane_state *vc4_state, u32 src, u32 dst) +/* phase magnitude bits */ +#define PHASE_BITS 6 + +static void vc4_write_ppf(struct vc4_plane_state *vc4_state, u32 src, u32 dst, + u32 xy, int channel, int chroma_offset, + bool no_interpolate) { - u32 scale = (1 << 16) * src / dst; + struct vc4_dev *vc4 = to_vc4_dev(vc4_state->base.plane->dev); + u32 scale = src / dst; + s32 offset, offset2; + s32 phase; + + WARN_ON_ONCE(vc4->gen > VC4_GEN_6_D); + + /* + * Start the phase at 1/2 pixel from the 1st pixel at src_x. + * 1/4 pixel for YUV, plus the offset for chroma siting. + */ + if (channel) { + /* + * The phase is relative to scale_src->x, so shift it for + * display list's x value + */ + offset = (xy & 0x1ffff) >> (16 - PHASE_BITS) >> 1; + offset -= chroma_offset >> (17 - PHASE_BITS); + offset += -(1 << PHASE_BITS >> 2); + } else { + /* + * The phase is relative to scale_src->x, so shift it for + * display list's x value + */ + offset = (xy & 0xffff) >> (16 - PHASE_BITS); + offset += -(1 << PHASE_BITS >> 1); + + /* + * This is a kludge to make sure the scaling factors are + * consistent with YUV's luma scaling. We lose 1-bit precision + * because of this. + */ + scale &= ~1; + } + + /* + * There may be a also small error introduced by precision of scale. + * Add half of that as a compromise + */ + offset2 = src - dst * scale; + offset2 >>= 16 - PHASE_BITS; + phase = offset + (offset2 >> 1); + + /* Ensure +ve values don't touch the sign bit, then truncate negative values */ + if (phase >= 1 << PHASE_BITS) + phase = (1 << PHASE_BITS) - 1; + + phase &= SCALER_PPF_IPHASE_MASK; vc4_dlist_write(vc4_state, + (no_interpolate ? SCALER_PPF_NOINTERP : 0) | SCALER_PPF_AGC | VC4_SET_FIELD(scale, SCALER_PPF_SCALE) | - VC4_SET_FIELD(0, SCALER_PPF_IPHASE)); + /* + * The register layout documentation is slightly + * different to setup the phase in the BCM2712, + * but they seem equivalent. + */ + VC4_SET_FIELD(phase, SCALER_PPF_IPHASE)); } -static u32 vc4_lbm_size(struct drm_plane_state *state) +static u32 __vc4_lbm_size(struct drm_plane_state *state) { struct vc4_plane_state *vc4_state = to_vc4_plane_state(state); struct vc4_dev *vc4 = to_vc4_dev(state->plane->dev); @@ -569,7 +705,7 @@ static u32 vc4_lbm_size(struct drm_plane_state *state) if (vc4_state->x_scaling[0] == VC4_SCALING_TPZ) pix_per_line = vc4_state->crtc_w; else - pix_per_line = vc4_state->src_w[0]; + pix_per_line = vc4_state->src_w[0] >> 16; if (!vc4_state->is_yuv) { if (vc4_state->y_scaling[0] == VC4_SCALING_TPZ) @@ -595,34 +731,166 @@ static u32 vc4_lbm_size(struct drm_plane_state *state) return lbm; } +static unsigned int vc4_lbm_words_per_component(const struct drm_plane_state *state, + unsigned int channel) +{ + const struct vc4_plane_state *vc4_state = to_vc4_plane_state(state); + + switch (vc4_state->y_scaling[channel]) { + case VC4_SCALING_PPF: + return 4; + + case VC4_SCALING_TPZ: + return 2; + + default: + return 0; + } +} + +static unsigned int vc4_lbm_components(const struct drm_plane_state *state, + unsigned int channel) +{ + const struct drm_format_info *info = state->fb->format; + const struct vc4_plane_state *vc4_state = to_vc4_plane_state(state); + + if (vc4_state->y_scaling[channel] == VC4_SCALING_NONE) + return 0; + + if (info->is_yuv) + return channel ? 2 : 1; + + if (info->has_alpha) + return 4; + + return 3; +} + +static unsigned int vc4_lbm_channel_size(const struct drm_plane_state *state, + unsigned int channel) +{ + const struct drm_format_info *info = state->fb->format; + const struct vc4_plane_state *vc4_state = to_vc4_plane_state(state); + unsigned int channels_scaled = 0; + unsigned int components, words, wpc; + unsigned int width, lines; + unsigned int i; + + /* LBM is meant to use the smaller of source or dest width, but there + * is a issue with UV scaling that the size required for the second + * channel is based on the source width only. + */ + if (info->hsub > 1 && channel == 1) + width = state->src_w >> 16; + else + width = min(state->src_w >> 16, state->crtc_w); + width = round_up(width / info->hsub, 4); + + wpc = vc4_lbm_words_per_component(state, channel); + if (!wpc) + return 0; + + components = vc4_lbm_components(state, channel); + if (!components) + return 0; + + if (state->alpha != DRM_BLEND_ALPHA_OPAQUE && info->has_alpha) + components -= 1; + + words = width * wpc * components; + + lines = DIV_ROUND_UP(words, 128 / info->hsub); + + for (i = 0; i < 2; i++) + if (vc4_state->y_scaling[channel] != VC4_SCALING_NONE) + channels_scaled++; + + if (channels_scaled == 1) + lines = lines / 2; + + return lines; +} + +static unsigned int __vc6_lbm_size(const struct drm_plane_state *state) +{ + const struct drm_format_info *info = state->fb->format; + + if (info->hsub > 1) + return max(vc4_lbm_channel_size(state, 0), + vc4_lbm_channel_size(state, 1)); + else + return vc4_lbm_channel_size(state, 0); +} + +static u32 vc4_lbm_size(struct drm_plane_state *state) +{ + struct vc4_plane_state *vc4_state = to_vc4_plane_state(state); + struct vc4_dev *vc4 = to_vc4_dev(state->plane->dev); + + /* LBM is not needed when there's no vertical scaling. */ + if (vc4_state->y_scaling[0] == VC4_SCALING_NONE && + vc4_state->y_scaling[1] == VC4_SCALING_NONE) + return 0; + + if (vc4->gen >= VC4_GEN_6_C) + return __vc6_lbm_size(state); + else + return __vc4_lbm_size(state); +} + +static size_t vc6_upm_size(const struct drm_plane_state *state, + unsigned int plane) +{ + const struct vc4_plane_state *vc4_state = to_vc4_plane_state(state); + unsigned int stride = state->fb->pitches[plane]; + + /* + * TODO: This only works for raster formats, and is sub-optimal + * for buffers with a stride aligned on 32 bytes. + */ + unsigned int words_per_line = (stride + 62) / 32; + unsigned int fetch_region_size = words_per_line * 32; + unsigned int buffer_lines = 2 << vc4_state->upm_buffer_lines; + unsigned int buffer_size = fetch_region_size * buffer_lines; + + return ALIGN(buffer_size, HVS_UBM_WORD_SIZE); +} + static void vc4_write_scaling_parameters(struct drm_plane_state *state, int channel) { + struct vc4_dev *vc4 = to_vc4_dev(state->plane->dev); struct vc4_plane_state *vc4_state = to_vc4_plane_state(state); + WARN_ON_ONCE(vc4->gen > VC4_GEN_6_D); + /* Ch0 H-PPF Word 0: Scaling Parameters */ if (vc4_state->x_scaling[channel] == VC4_SCALING_PPF) { - vc4_write_ppf(vc4_state, - vc4_state->src_w[channel], vc4_state->crtc_w); + vc4_write_ppf(vc4_state, vc4_state->src_w[channel], + vc4_state->crtc_w, vc4_state->src_x, channel, + state->chroma_siting_h, + state->scaling_filter == DRM_SCALING_FILTER_NEAREST_NEIGHBOR); } /* Ch0 V-PPF Words 0-1: Scaling Parameters, Context */ if (vc4_state->y_scaling[channel] == VC4_SCALING_PPF) { - vc4_write_ppf(vc4_state, - vc4_state->src_h[channel], vc4_state->crtc_h); + vc4_write_ppf(vc4_state, vc4_state->src_h[channel], + vc4_state->crtc_h, vc4_state->src_y, channel, + state->chroma_siting_v, + state->scaling_filter == DRM_SCALING_FILTER_NEAREST_NEIGHBOR); vc4_dlist_write(vc4_state, 0xc0c0c0c0); } /* Ch0 H-TPZ Words 0-1: Scaling Parameters, Recip */ if (vc4_state->x_scaling[channel] == VC4_SCALING_TPZ) { - vc4_write_tpz(vc4_state, - vc4_state->src_w[channel], vc4_state->crtc_w); + vc4_write_tpz(vc4_state, vc4_state->src_w[channel], + vc4_state->crtc_w); } /* Ch0 V-TPZ Words 0-2: Scaling Parameters, Recip, Context */ if (vc4_state->y_scaling[channel] == VC4_SCALING_TPZ) { - vc4_write_tpz(vc4_state, - vc4_state->src_h[channel], vc4_state->crtc_h); + vc4_write_tpz(vc4_state, vc4_state->src_h[channel], + vc4_state->crtc_h); vc4_dlist_write(vc4_state, 0xc0c0c0c0); } } @@ -660,7 +928,8 @@ static void vc4_plane_calc_load(struct drm_plane_state *state) for (i = 0; i < fb->format->num_planes; i++) { /* Even if the bandwidth/plane required for a single frame is * - * vc4_state->src_w[i] * vc4_state->src_h[i] * cpp * vrefresh + * (vc4_state->src_w[i] >> 16) * (vc4_state->src_h[i] >> 16) * + * cpp * vrefresh * * when downscaling, we have to read more pixels per line in * the time frame reserved for a single line, so the bandwidth @@ -669,11 +938,11 @@ static void vc4_plane_calc_load(struct drm_plane_state *state) * load by this number. We're likely over-estimating the read * demand, but that's better than under-estimating it. */ - vscale_factor = DIV_ROUND_UP(vc4_state->src_h[i], + vscale_factor = DIV_ROUND_UP(vc4_state->src_h[i] >> 16, vc4_state->crtc_h); - vc4_state->membus_load += vc4_state->src_w[i] * - vc4_state->src_h[i] * vscale_factor * - fb->format->cpp[i]; + vc4_state->membus_load += (vc4_state->src_w[i] >> 16) * + (vc4_state->src_h[i] >> 16) * + vscale_factor * fb->format->cpp[i]; vc4_state->hvs_load += vc4_state->crtc_h * vc4_state->crtc_w; } @@ -684,43 +953,206 @@ static void vc4_plane_calc_load(struct drm_plane_state *state) static int vc4_plane_allocate_lbm(struct drm_plane_state *state) { - struct vc4_dev *vc4 = to_vc4_dev(state->plane->dev); + struct drm_device *drm = state->plane->dev; + struct vc4_dev *vc4 = to_vc4_dev(drm); + struct vc4_hvs *hvs = vc4->hvs; + struct drm_plane *plane = state->plane; struct vc4_plane_state *vc4_state = to_vc4_plane_state(state); + struct vc4_lbm_refcounts *refcount; unsigned long irqflags; + int lbm_handle; u32 lbm_size; + int ret; lbm_size = vc4_lbm_size(state); if (!lbm_size) return 0; + /* + * NOTE: BCM2712 doesn't need to be aligned, since the size + * returned by vc4_lbm_size() is in words already. + */ + if (vc4->gen == VC4_GEN_5) + lbm_size = ALIGN(lbm_size, 64); + else if (vc4->gen == VC4_GEN_4) + lbm_size = ALIGN(lbm_size, 32); + + drm_dbg_driver(drm, "[PLANE:%d:%s] LBM Allocation Size: %u\n", + plane->base.id, plane->name, lbm_size); + if (WARN_ON(!vc4_state->lbm_offset)) return -EINVAL; /* Allocate the LBM memory that the HVS will use for temporary * storage due to our scaling/format conversion. */ - if (!drm_mm_node_allocated(&vc4_state->lbm)) { - int ret; + lbm_handle = vc4_state->lbm_handle; + if (lbm_handle && + hvs->lbm_refcounts[lbm_handle].size == lbm_size) { + /* Allocation is the same size as the previous user of + * the plane. Keep the allocation. + */ + vc4_state->lbm_handle = lbm_handle; + } else { + if (lbm_handle && + refcount_dec_and_test(&hvs->lbm_refcounts[lbm_handle].refcount)) { + vc4_plane_release_lbm_ida(hvs, lbm_handle); + vc4_state->lbm_handle = 0; + } - spin_lock_irqsave(&vc4->hvs->mm_lock, irqflags); + lbm_handle = ida_alloc_range(&hvs->lbm_handles, 1, + VC4_NUM_LBM_HANDLES, + GFP_KERNEL); + if (lbm_handle < 0) { + drm_err(drm, "Out of lbm_handles\n"); + return lbm_handle; + } + vc4_state->lbm_handle = lbm_handle; + + refcount = &hvs->lbm_refcounts[lbm_handle]; + refcount_set(&refcount->refcount, 1); + refcount->size = lbm_size; + + spin_lock_irqsave(&hvs->mm_lock, irqflags); ret = drm_mm_insert_node_generic(&vc4->hvs->lbm_mm, - &vc4_state->lbm, - lbm_size, - vc4->gen == VC4_GEN_5 ? 64 : 32, + &refcount->lbm, + lbm_size, 1, 0, 0); - spin_unlock_irqrestore(&vc4->hvs->mm_lock, irqflags); + spin_unlock_irqrestore(&hvs->mm_lock, irqflags); - if (ret) + if (ret) { + drm_err(drm, "Failed to allocate LBM entry: %d\n", ret); + refcount_set(&refcount->refcount, 0); + ida_free(&hvs->lbm_handles, lbm_handle); + vc4_state->lbm_handle = 0; return ret; - } else { - WARN_ON_ONCE(lbm_size != vc4_state->lbm.size); + } } - vc4_state->dlist[vc4_state->lbm_offset] = vc4_state->lbm.start; + vc4_state->dlist[vc4_state->lbm_offset] = hvs->lbm_refcounts[lbm_handle].lbm.start; + + return 0; +} + +static void vc4_plane_free_lbm(struct drm_plane_state *state) +{ + struct vc4_plane_state *vc4_state = to_vc4_plane_state(state); + struct drm_device *drm = state->plane->dev; + struct vc4_dev *vc4 = to_vc4_dev(drm); + struct vc4_hvs *hvs = vc4->hvs; + unsigned int lbm_handle; + + lbm_handle = vc4_state->lbm_handle; + if (!lbm_handle) + return; + + if (refcount_dec_and_test(&hvs->lbm_refcounts[lbm_handle].refcount)) + vc4_plane_release_lbm_ida(hvs, lbm_handle); + vc4_state->lbm_handle = 0; +} + +static int vc6_plane_allocate_upm(struct drm_plane_state *state) +{ + const struct drm_format_info *info = state->fb->format; + struct drm_device *drm = state->plane->dev; + struct vc4_dev *vc4 = to_vc4_dev(drm); + struct vc4_hvs *hvs = vc4->hvs; + struct vc4_plane_state *vc4_state = to_vc4_plane_state(state); + unsigned int i; + int ret; + + WARN_ON_ONCE(vc4->gen < VC4_GEN_6_C); + + vc4_state->upm_buffer_lines = SCALER6_PTR0_UPM_BUFF_SIZE_2_LINES; + + for (i = 0; i < info->num_planes; i++) { + struct vc4_upm_refcounts *refcount; + int upm_handle; + unsigned long irqflags; + size_t upm_size; + + upm_size = vc6_upm_size(state, i); + if (!upm_size) + return -EINVAL; + upm_handle = vc4_state->upm_handle[i]; + + if (upm_handle && + hvs->upm_refcounts[upm_handle].size == upm_size) { + /* Allocation is the same size as the previous user of + * the plane. Keep the allocation. + */ + vc4_state->upm_handle[i] = upm_handle; + } else { + if (upm_handle && + refcount_dec_and_test(&hvs->upm_refcounts[upm_handle].refcount)) { + vc4_plane_release_upm_ida(hvs, upm_handle); + vc4_state->upm_handle[i] = 0; + } + + upm_handle = ida_alloc_range(&hvs->upm_handles, 1, + VC4_NUM_UPM_HANDLES, + GFP_KERNEL); + if (upm_handle < 0) { + drm_dbg(drm, "Out of upm_handles\n"); + return upm_handle; + } + vc4_state->upm_handle[i] = upm_handle; + + refcount = &hvs->upm_refcounts[upm_handle]; + refcount_set(&refcount->refcount, 1); + refcount->size = upm_size; + + spin_lock_irqsave(&hvs->mm_lock, irqflags); + ret = drm_mm_insert_node_generic(&hvs->upm_mm, + &refcount->upm, + upm_size, HVS_UBM_WORD_SIZE, + 0, 0); + spin_unlock_irqrestore(&hvs->mm_lock, irqflags); + if (ret) { + drm_err(drm, "Failed to allocate UPM entry: %d\n", ret); + refcount_set(&refcount->refcount, 0); + ida_free(&hvs->upm_handles, upm_handle); + vc4_state->upm_handle[i] = 0; + return ret; + } + } + + refcount = &hvs->upm_refcounts[upm_handle]; + vc4_state->dlist[vc4_state->ptr0_offset[i]] |= + VC4_SET_FIELD(refcount->upm.start / HVS_UBM_WORD_SIZE, + SCALER6_PTR0_UPM_BASE) | + VC4_SET_FIELD(vc4_state->upm_handle[i] - 1, + SCALER6_PTR0_UPM_HANDLE) | + VC4_SET_FIELD(vc4_state->upm_buffer_lines, + SCALER6_PTR0_UPM_BUFF_SIZE); + } return 0; } +static void vc6_plane_free_upm(struct drm_plane_state *state) +{ + struct vc4_plane_state *vc4_state = to_vc4_plane_state(state); + struct drm_device *drm = state->plane->dev; + struct vc4_dev *vc4 = to_vc4_dev(drm); + struct vc4_hvs *hvs = vc4->hvs; + unsigned int i; + + WARN_ON_ONCE(vc4->gen < VC4_GEN_6_C); + + for (i = 0; i < DRM_FORMAT_MAX_PLANES; i++) { + unsigned int upm_handle; + + upm_handle = vc4_state->upm_handle[i]; + if (!upm_handle) + continue; + + if (refcount_dec_and_test(&hvs->upm_refcounts[upm_handle].refcount)) + vc4_plane_release_upm_ida(hvs, upm_handle); + vc4_state->upm_handle[i] = 0; + } +} + /* * The colorspace conversion matrices are held in 3 entries in the dlist. * Create an array of them, with entries for each full and limited mode, and @@ -768,6 +1200,11 @@ static const u32 colorspace_coeffs[2][DRM_COLOR_ENCODING_MAX][3] = { static u32 vc4_hvs4_get_alpha_blend_mode(struct drm_plane_state *state) { + struct drm_device *dev = state->state->dev; + struct vc4_dev *vc4 = to_vc4_dev(dev); + + WARN_ON_ONCE(vc4->gen != VC4_GEN_4); + if (!state->fb->format->has_alpha) return VC4_SET_FIELD(SCALER_POS2_ALPHA_MODE_FIXED, SCALER_POS2_ALPHA_MODE); @@ -789,25 +1226,56 @@ static u32 vc4_hvs4_get_alpha_blend_mode(struct drm_plane_state *state) static u32 vc4_hvs5_get_alpha_blend_mode(struct drm_plane_state *state) { - if (!state->fb->format->has_alpha) - return VC4_SET_FIELD(SCALER5_CTL2_ALPHA_MODE_FIXED, - SCALER5_CTL2_ALPHA_MODE); + struct drm_device *dev = state->state->dev; + struct vc4_dev *vc4 = to_vc4_dev(dev); - switch (state->pixel_blend_mode) { - case DRM_MODE_BLEND_PIXEL_NONE: - return VC4_SET_FIELD(SCALER5_CTL2_ALPHA_MODE_FIXED, - SCALER5_CTL2_ALPHA_MODE); + WARN_ON_ONCE(vc4->gen != VC4_GEN_5 && vc4->gen != VC4_GEN_6_C && + vc4->gen != VC4_GEN_6_D); + + switch (vc4->gen) { default: - case DRM_MODE_BLEND_PREMULTI: - return VC4_SET_FIELD(SCALER5_CTL2_ALPHA_MODE_PIPELINE, - SCALER5_CTL2_ALPHA_MODE) | - SCALER5_CTL2_ALPHA_PREMULT; - case DRM_MODE_BLEND_COVERAGE: - return VC4_SET_FIELD(SCALER5_CTL2_ALPHA_MODE_PIPELINE, - SCALER5_CTL2_ALPHA_MODE); + case VC4_GEN_5: + case VC4_GEN_6_C: + if (!state->fb->format->has_alpha) + return VC4_SET_FIELD(SCALER5_CTL2_ALPHA_MODE_FIXED, + SCALER5_CTL2_ALPHA_MODE); + + switch (state->pixel_blend_mode) { + case DRM_MODE_BLEND_PIXEL_NONE: + return VC4_SET_FIELD(SCALER5_CTL2_ALPHA_MODE_FIXED, + SCALER5_CTL2_ALPHA_MODE); + default: + case DRM_MODE_BLEND_PREMULTI: + return VC4_SET_FIELD(SCALER5_CTL2_ALPHA_MODE_PIPELINE, + SCALER5_CTL2_ALPHA_MODE) | + SCALER5_CTL2_ALPHA_PREMULT; + case DRM_MODE_BLEND_COVERAGE: + return VC4_SET_FIELD(SCALER5_CTL2_ALPHA_MODE_PIPELINE, + SCALER5_CTL2_ALPHA_MODE); + } + case VC4_GEN_6_D: + /* 2712-D configures fixed alpha mode in CTL0 */ + return state->pixel_blend_mode == DRM_MODE_BLEND_PREMULTI ? + SCALER5_CTL2_ALPHA_PREMULT : 0; } } +static u32 vc4_hvs6_get_alpha_mask_mode(struct drm_plane_state *state) +{ + struct drm_device *dev = state->state->dev; + struct vc4_dev *vc4 = to_vc4_dev(dev); + + WARN_ON_ONCE(vc4->gen != VC4_GEN_6_C && vc4->gen != VC4_GEN_6_D); + + if (vc4->gen == VC4_GEN_6_D && + (!state->fb->format->has_alpha || + state->pixel_blend_mode == DRM_MODE_BLEND_PIXEL_NONE)) + return VC4_SET_FIELD(SCALER6D_CTL0_ALPHA_MASK_FIXED, + SCALER6_CTL0_ALPHA_MASK); + + return VC4_SET_FIELD(SCALER6_CTL0_ALPHA_MASK_NONE, SCALER6_CTL0_ALPHA_MASK); +} + /* Writes out a full display list for an active plane to the plane's * private dlist state. */ @@ -826,9 +1294,11 @@ static int vc4_plane_mode_set(struct drm_plane *plane, bool mix_plane_alpha; bool covers_screen; u32 scl0, scl1, pitch0; - u32 tiling, src_y; + u32 tiling, src_x, src_y; + u32 width, height; u32 hvs_format = format->hvs; unsigned int rotation; + u32 offsets[3] = { 0 }; int ret, i; if (vc4_state->dlist_initialized) @@ -838,6 +1308,16 @@ static int vc4_plane_mode_set(struct drm_plane *plane, if (ret) return ret; + if (!vc4_state->src_w[0] || !vc4_state->src_h[0] || + !vc4_state->crtc_w || !vc4_state->crtc_h) { + /* 0 source size probably means the plane is offscreen */ + vc4_state->dlist_initialized = 1; + return 0; + } + + width = vc4_state->src_w[0] >> 16; + height = vc4_state->src_h[0] >> 16; + /* SCL1 is used for Cb/Cr scaling of planar formats. For RGB * and 4:4:4, scl1 should be set to scl0 so both channels of * the scaler do the same thing. For YUV, the Y plane needs @@ -858,9 +1338,11 @@ static int vc4_plane_mode_set(struct drm_plane *plane, DRM_MODE_REFLECT_Y); /* We must point to the last line when Y reflection is enabled. */ - src_y = vc4_state->src_y; + src_y = vc4_state->src_y >> 16; if (rotation & DRM_MODE_REFLECT_Y) - src_y += vc4_state->src_h[0] - 1; + src_y += height - 1; + + src_x = vc4_state->src_x >> 16; switch (base_format_mod) { case DRM_FORMAT_MOD_LINEAR: @@ -871,13 +1353,8 @@ static int vc4_plane_mode_set(struct drm_plane *plane, * out. */ for (i = 0; i < num_planes; i++) { - vc4_state->offsets[i] += src_y / - (i ? v_subsample : 1) * - fb->pitches[i]; - - vc4_state->offsets[i] += vc4_state->src_x / - (i ? h_subsample : 1) * - fb->format->cpp[i]; + offsets[i] += src_y / (i ? v_subsample : 1) * fb->pitches[i]; + offsets[i] += src_x / (i ? h_subsample : 1) * fb->format->cpp[i]; } break; @@ -898,7 +1375,7 @@ static int vc4_plane_mode_set(struct drm_plane *plane, * pitch * tile_h == tile_size * tiles_per_row */ u32 tiles_w = fb->pitches[0] >> (tile_size_shift - tile_h_shift); - u32 tiles_l = vc4_state->src_x >> tile_w_shift; + u32 tiles_l = src_x >> tile_w_shift; u32 tiles_r = tiles_w - tiles_l; u32 tiles_t = src_y >> tile_h_shift; /* Intra-tile offsets, which modify the base address (the @@ -908,7 +1385,7 @@ static int vc4_plane_mode_set(struct drm_plane *plane, u32 tile_y = (src_y >> 4) & 1; u32 subtile_y = (src_y >> 2) & 3; u32 utile_y = src_y & 3; - u32 x_off = vc4_state->src_x & tile_w_mask; + u32 x_off = src_x & tile_w_mask; u32 y_off = src_y & tile_h_mask; /* When Y reflection is requested we must set the @@ -932,19 +1409,18 @@ static int vc4_plane_mode_set(struct drm_plane *plane, VC4_SET_FIELD(y_off, SCALER_PITCH0_TILE_Y_OFFSET) | VC4_SET_FIELD(tiles_l, SCALER_PITCH0_TILE_WIDTH_L) | VC4_SET_FIELD(tiles_r, SCALER_PITCH0_TILE_WIDTH_R)); - vc4_state->offsets[0] += tiles_t * (tiles_w << tile_size_shift); - vc4_state->offsets[0] += subtile_y << 8; - vc4_state->offsets[0] += utile_y << 4; + offsets[0] += tiles_t * (tiles_w << tile_size_shift); + offsets[0] += subtile_y << 8; + offsets[0] += utile_y << 4; /* Rows of tiles alternate left-to-right and right-to-left. */ if (tiles_t & 1) { pitch0 |= SCALER_PITCH0_TILE_INITIAL_LINE_DIR; - vc4_state->offsets[0] += (tiles_w - tiles_l) << - tile_size_shift; - vc4_state->offsets[0] -= (1 + !tile_y) << 10; + offsets[0] += (tiles_w - tiles_l) << tile_size_shift; + offsets[0] -= (1 + !tile_y) << 10; } else { - vc4_state->offsets[0] += tiles_l << tile_size_shift; - vc4_state->offsets[0] += tile_y << 10; + offsets[0] += tiles_l << tile_size_shift; + offsets[0] += tile_y << 10; } break; @@ -1004,7 +1480,7 @@ static int vc4_plane_mode_set(struct drm_plane *plane, * of the 12-pixels in that 128-bit word is the * first pixel to be used */ - u32 remaining_pixels = vc4_state->src_x % 96; + u32 remaining_pixels = src_x % 96; u32 aligned = remaining_pixels / 12; u32 last_bits = remaining_pixels % 12; @@ -1026,18 +1502,16 @@ static int vc4_plane_mode_set(struct drm_plane *plane, return -EINVAL; } pix_per_tile = tile_w / fb->format->cpp[0]; - x_off = (vc4_state->src_x % pix_per_tile) / + x_off = (src_x % pix_per_tile) / (i ? h_subsample : 1) * fb->format->cpp[i]; } - tile = vc4_state->src_x / pix_per_tile; + tile = src_x / pix_per_tile; - vc4_state->offsets[i] += param * tile_w * tile; - vc4_state->offsets[i] += src_y / - (i ? v_subsample : 1) * - tile_w; - vc4_state->offsets[i] += x_off & ~(i ? 1 : 0); + offsets[i] += param * tile_w * tile; + offsets[i] += src_y / (i ? v_subsample : 1) * tile_w; + offsets[i] += x_off & ~(i ? 1 : 0); } pitch0 = VC4_SET_FIELD(param, SCALER_TILE_HEIGHT); @@ -1050,6 +1524,30 @@ static int vc4_plane_mode_set(struct drm_plane *plane, return -EINVAL; } + /* fetch an extra pixel if we don't actually line up with the left edge. */ + if ((vc4_state->src_x & 0xffff) && vc4_state->src_x < (state->fb->width << 16)) + width++; + + /* same for the right side */ + if (((vc4_state->src_x + vc4_state->src_w[0]) & 0xffff) && + vc4_state->src_x + vc4_state->src_w[0] < (state->fb->width << 16)) + width++; + + /* now for the top */ + if ((vc4_state->src_y & 0xffff) && vc4_state->src_y < (state->fb->height << 16)) + height++; + + /* and the bottom */ + if (((vc4_state->src_y + vc4_state->src_h[0]) & 0xffff) && + vc4_state->src_y + vc4_state->src_h[0] < (state->fb->height << 16)) + height++; + + /* For YUV444 the hardware wants double the width, otherwise it doesn't + * fetch full width of chroma + */ + if (format->drm == DRM_FORMAT_YUV444 || format->drm == DRM_FORMAT_YVU444) + width <<= 1; + /* Don't waste cycles mixing with plane alpha if the set alpha * is opaque or there is no per-pixel alpha information. * In any case we use the alpha property value as the fixed alpha. @@ -1092,10 +1590,8 @@ static int vc4_plane_mode_set(struct drm_plane *plane, vc4_dlist_write(vc4_state, (mix_plane_alpha ? SCALER_POS2_ALPHA_MIX : 0) | vc4_hvs4_get_alpha_blend_mode(state) | - VC4_SET_FIELD(vc4_state->src_w[0], - SCALER_POS2_WIDTH) | - VC4_SET_FIELD(vc4_state->src_h[0], - SCALER_POS2_HEIGHT)); + VC4_SET_FIELD(width, SCALER_POS2_WIDTH) | + VC4_SET_FIELD(height, SCALER_POS2_HEIGHT)); /* Position Word 3: Context. Written by the HVS. */ vc4_dlist_write(vc4_state, 0xc0c0c0c0); @@ -1148,10 +1644,8 @@ static int vc4_plane_mode_set(struct drm_plane *plane, /* Position Word 2: Source Image Size */ vc4_state->pos2_offset = vc4_state->dlist_count; vc4_dlist_write(vc4_state, - VC4_SET_FIELD(vc4_state->src_w[0], - SCALER5_POS2_WIDTH) | - VC4_SET_FIELD(vc4_state->src_h[0], - SCALER5_POS2_HEIGHT)); + VC4_SET_FIELD(width, SCALER5_POS2_WIDTH) | + VC4_SET_FIELD(height, SCALER5_POS2_HEIGHT)); /* Position Word 3: Context. Written by the HVS. */ vc4_dlist_write(vc4_state, 0xc0c0c0c0); @@ -1162,9 +1656,13 @@ static int vc4_plane_mode_set(struct drm_plane *plane, * * The pointers may be any byte address. */ - vc4_state->ptr0_offset = vc4_state->dlist_count; - for (i = 0; i < num_planes; i++) - vc4_dlist_write(vc4_state, vc4_state->offsets[i]); + vc4_state->ptr0_offset[0] = vc4_state->dlist_count; + + for (i = 0; i < num_planes; i++) { + struct drm_gem_dma_object *bo = drm_fb_dma_get_gem_obj(fb, i); + + vc4_dlist_write(vc4_state, bo->dma_addr + fb->offsets[i] + offsets[i]); + } /* Pointer Context Word 0/1/2: Written by the HVS */ for (i = 0; i < num_planes; i++) @@ -1234,7 +1732,18 @@ static int vc4_plane_mode_set(struct drm_plane *plane, vc4_state->y_scaling[0] == VC4_SCALING_PPF || vc4_state->x_scaling[1] == VC4_SCALING_PPF || vc4_state->y_scaling[1] == VC4_SCALING_PPF) { - u32 kernel = VC4_SET_FIELD(vc4->hvs->mitchell_netravali_filter.start, + struct drm_mm_node *filter; + + switch (state->scaling_filter) { + case DRM_SCALING_FILTER_DEFAULT: + default: + filter = &vc4->hvs->mitchell_netravali_filter; + break; + case DRM_SCALING_FILTER_NEAREST_NEIGHBOR: + filter = &vc4->hvs->nearest_neighbour_filter; + break; + } + u32 kernel = VC4_SET_FIELD(filter->start, SCALER_PPF_KERNEL_OFFSET); /* HPPF plane 0 */ @@ -1274,31 +1783,489 @@ static int vc4_plane_mode_set(struct drm_plane *plane, return 0; } -/* If a modeset involves changing the setup of a plane, the atomic - * infrastructure will call this to validate a proposed plane setup. - * However, if a plane isn't getting updated, this (and the - * corresponding vc4_plane_atomic_update) won't get called. Thus, we - * compute the dlist here and have all active plane dlists get updated - * in the CRTC's flush. - */ -static int vc4_plane_atomic_check(struct drm_plane *plane, - struct drm_atomic_state *state) +static u32 vc6_plane_get_csc_mode(struct vc4_plane_state *vc4_state) { - struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, - plane); - struct vc4_plane_state *vc4_state = to_vc4_plane_state(new_plane_state); - int ret; - - vc4_state->dlist_count = 0; + struct drm_plane_state *state = &vc4_state->base; + struct vc4_dev *vc4 = to_vc4_dev(state->plane->dev); + u32 ret = 0; - if (!plane_enabled(new_plane_state)) - return 0; + if (vc4_state->is_yuv) { + enum drm_color_encoding color_encoding = state->color_encoding; + enum drm_color_range color_range = state->color_range; - ret = vc4_plane_mode_set(plane, new_plane_state); - if (ret) - return ret; + /* CSC pre-loaded with: + * 0 = BT601 limited range + * 1 = BT709 limited range + * 2 = BT2020 limited range + * 3 = BT601 full range + * 4 = BT709 full range + * 5 = BT2020 full range + */ + if (color_encoding > DRM_COLOR_YCBCR_BT2020) + color_encoding = DRM_COLOR_YCBCR_BT601; + if (color_range > DRM_COLOR_YCBCR_FULL_RANGE) + color_range = DRM_COLOR_YCBCR_LIMITED_RANGE; - return vc4_plane_allocate_lbm(new_plane_state); + if (vc4->gen == VC4_GEN_6_C) { + ret |= SCALER6C_CTL2_CSC_ENABLE; + ret |= VC4_SET_FIELD(color_encoding + (color_range * 3), + SCALER6C_CTL2_BRCM_CFC_CONTROL); + } else { + ret |= SCALER6D_CTL2_CSC_ENABLE; + ret |= VC4_SET_FIELD(color_encoding + (color_range * 3), + SCALER6D_CTL2_BRCM_CFC_CONTROL); + } + } + + return ret; +} + +static int vc6_plane_mode_set(struct drm_plane *plane, + struct drm_plane_state *state) +{ + struct drm_device *drm = plane->dev; + struct vc4_dev *vc4 = to_vc4_dev(drm); + struct vc4_plane_state *vc4_state = to_vc4_plane_state(state); + struct drm_framebuffer *fb = state->fb; + const struct hvs_format *format = vc4_get_hvs_format(fb->format->format); + u64 base_format_mod = fourcc_mod_broadcom_mod(fb->modifier); + int num_planes = fb->format->num_planes; + u32 h_subsample = fb->format->hsub; + u32 v_subsample = fb->format->vsub; + bool mix_plane_alpha; + bool covers_screen; + u32 scl0, scl1, pitch0; + u32 tiling, src_x, src_y; + u32 width, height; + u32 hvs_format = format->hvs; + u32 offsets[3] = { 0 }; + unsigned int rotation; + int ret, i; + + if (vc4_state->dlist_initialized) + return 0; + + ret = vc4_plane_setup_clipping_and_scaling(state); + if (ret) + return ret; + + if (!vc4_state->src_w[0] || !vc4_state->src_h[0] || + !vc4_state->crtc_w || !vc4_state->crtc_h) { + /* 0 source size probably means the plane is offscreen. + * 0 destination size is a redundant plane. + */ + vc4_state->dlist_initialized = 1; + return 0; + } + + width = vc4_state->src_w[0] >> 16; + height = vc4_state->src_h[0] >> 16; + + /* SCL1 is used for Cb/Cr scaling of planar formats. For RGB + * and 4:4:4, scl1 should be set to scl0 so both channels of + * the scaler do the same thing. For YUV, the Y plane needs + * to be put in channel 1 and Cb/Cr in channel 0, so we swap + * the scl fields here. + */ + if (num_planes == 1) { + scl0 = vc4_get_scl_field(state, 0); + scl1 = scl0; + } else { + scl0 = vc4_get_scl_field(state, 1); + scl1 = vc4_get_scl_field(state, 0); + } + + rotation = drm_rotation_simplify(state->rotation, + DRM_MODE_ROTATE_0 | + DRM_MODE_REFLECT_X | + DRM_MODE_REFLECT_Y); + + /* We must point to the last line when Y reflection is enabled. */ + src_y = vc4_state->src_y >> 16; + if (rotation & DRM_MODE_REFLECT_Y) + src_y += height - 1; + + src_x = vc4_state->src_x >> 16; + + switch (base_format_mod) { + case DRM_FORMAT_MOD_LINEAR: + tiling = SCALER6_CTL0_ADDR_MODE_LINEAR; + + /* Adjust the base pointer to the first pixel to be scanned + * out. + */ + for (i = 0; i < num_planes; i++) { + offsets[i] += src_y / (i ? v_subsample : 1) * fb->pitches[i]; + offsets[i] += src_x / (i ? h_subsample : 1) * fb->format->cpp[i]; + } + + break; + + case DRM_FORMAT_MOD_BROADCOM_SAND128: + case DRM_FORMAT_MOD_BROADCOM_SAND256: { + uint32_t param = fourcc_mod_broadcom_param(fb->modifier); + u32 components_per_word; + u32 starting_offset; + u32 fetch_count; + + if (param > SCALER_TILE_HEIGHT_MASK) { + DRM_DEBUG_KMS("SAND height too large (%d)\n", + param); + return -EINVAL; + } + + if (fb->format->format == DRM_FORMAT_P030) { + hvs_format = HVS_PIXEL_FORMAT_YCBCR_10BIT; + tiling = SCALER6_CTL0_ADDR_MODE_128B; + } else { + hvs_format = HVS_PIXEL_FORMAT_YCBCR_YUV420_2PLANE; + + switch (base_format_mod) { + case DRM_FORMAT_MOD_BROADCOM_SAND128: + tiling = SCALER6_CTL0_ADDR_MODE_128B; + break; + case DRM_FORMAT_MOD_BROADCOM_SAND256: + tiling = SCALER6_CTL0_ADDR_MODE_256B; + break; + default: + return -EINVAL; + } + } + + /* Adjust the base pointer to the first pixel to be scanned + * out. + * + * For P030, y_ptr [31:4] is the 128bit word for the start pixel + * y_ptr [3:0] is the pixel (0-11) contained within that 128bit + * word that should be taken as the first pixel. + * Ditto uv_ptr [31:4] vs [3:0], however [3:0] contains the + * element within the 128bit word, eg for pixel 3 the value + * should be 6. + */ + for (i = 0; i < num_planes; i++) { + u32 tile_w, tile, x_off, pix_per_tile; + + if (fb->format->format == DRM_FORMAT_P030) { + /* + * Spec says: bits [31:4] of the given address + * should point to the 128-bit word containing + * the desired starting pixel, and bits[3:0] + * should be between 0 and 11, indicating which + * of the 12-pixels in that 128-bit word is the + * first pixel to be used + */ + u32 remaining_pixels = src_x % 96; + u32 aligned = remaining_pixels / 12; + u32 last_bits = remaining_pixels % 12; + + x_off = aligned * 16 + last_bits; + tile_w = 128; + pix_per_tile = 96; + } else { + switch (base_format_mod) { + case DRM_FORMAT_MOD_BROADCOM_SAND128: + tile_w = 128; + break; + case DRM_FORMAT_MOD_BROADCOM_SAND256: + tile_w = 256; + break; + default: + return -EINVAL; + } + pix_per_tile = tile_w / fb->format->cpp[0]; + x_off = (src_x % pix_per_tile) / + (i ? h_subsample : 1) * + fb->format->cpp[i]; + } + + tile = src_x / pix_per_tile; + + offsets[i] += param * tile_w * tile; + offsets[i] += src_y / (i ? v_subsample : 1) * tile_w; + offsets[i] += x_off & ~(i ? 1 : 0); + } + + components_per_word = fb->format->format == DRM_FORMAT_P030 ? 24 : 32; + starting_offset = src_x % components_per_word; + fetch_count = (width + starting_offset + components_per_word - 1) / + components_per_word; + + pitch0 = VC4_SET_FIELD(param, SCALER6_PTR2_PITCH) | + VC4_SET_FIELD(fetch_count - 1, SCALER6_PTR2_FETCH_COUNT); + break; + } + + default: + DRM_DEBUG_KMS("Unsupported FB tiling flag 0x%16llx", + (long long)fb->modifier); + return -EINVAL; + } + + /* fetch an extra pixel if we don't actually line up with the left edge. */ + if ((vc4_state->src_x & 0xffff) && vc4_state->src_x < (state->fb->width << 16)) + width++; + + /* same for the right side */ + if (((vc4_state->src_x + vc4_state->src_w[0]) & 0xffff) && + vc4_state->src_x + vc4_state->src_w[0] < (state->fb->width << 16)) + width++; + + /* now for the top */ + if ((vc4_state->src_y & 0xffff) && vc4_state->src_y < (state->fb->height << 16)) + height++; + + /* and the bottom */ + if (((vc4_state->src_y + vc4_state->src_h[0]) & 0xffff) && + vc4_state->src_y + vc4_state->src_h[0] < (state->fb->height << 16)) + height++; + + /* for YUV444 hardware wants double the width, otherwise it doesn't + * fetch full width of chroma + */ + if (format->drm == DRM_FORMAT_YUV444 || format->drm == DRM_FORMAT_YVU444) + width <<= 1; + + /* Don't waste cycles mixing with plane alpha if the set alpha + * is opaque or there is no per-pixel alpha information. + * In any case we use the alpha property value as the fixed alpha. + */ + mix_plane_alpha = state->alpha != DRM_BLEND_ALPHA_OPAQUE && + fb->format->has_alpha; + + /* Control Word 0: Scaling Configuration & Element Validity*/ + vc4_dlist_write(vc4_state, + SCALER6_CTL0_VALID | + VC4_SET_FIELD(tiling, SCALER6_CTL0_ADDR_MODE) | + vc4_hvs6_get_alpha_mask_mode(state) | + (vc4_state->is_unity ? SCALER6_CTL0_UNITY : 0) | + VC4_SET_FIELD(format->pixel_order_hvs5, SCALER6_CTL0_ORDERRGBA) | + VC4_SET_FIELD(scl1, SCALER6_CTL0_SCL1_MODE) | + VC4_SET_FIELD(scl0, SCALER6_CTL0_SCL0_MODE) | + VC4_SET_FIELD(hvs_format, SCALER6_CTL0_PIXEL_FORMAT)); + + /* Position Word 0: Image Position */ + vc4_state->pos0_offset = vc4_state->dlist_count; + vc4_dlist_write(vc4_state, + VC4_SET_FIELD(vc4_state->crtc_y, SCALER6_POS0_START_Y) | + (rotation & DRM_MODE_REFLECT_X ? SCALER6_POS0_HFLIP : 0) | + VC4_SET_FIELD(vc4_state->crtc_x, SCALER6_POS0_START_X)); + + /* Control Word 2: Alpha Value & CSC */ + vc4_dlist_write(vc4_state, + vc6_plane_get_csc_mode(vc4_state) | + vc4_hvs5_get_alpha_blend_mode(state) | + (mix_plane_alpha ? SCALER6_CTL2_ALPHA_MIX : 0) | + VC4_SET_FIELD(state->alpha >> 4, SCALER5_CTL2_ALPHA)); + + /* Position Word 1: Scaled Image Dimensions */ + if (!vc4_state->is_unity) + vc4_dlist_write(vc4_state, + VC4_SET_FIELD(vc4_state->crtc_h - 1, + SCALER6_POS1_SCL_LINES) | + VC4_SET_FIELD(vc4_state->crtc_w - 1, + SCALER6_POS1_SCL_WIDTH)); + + /* Position Word 2: Source Image Size */ + vc4_state->pos2_offset = vc4_state->dlist_count; + vc4_dlist_write(vc4_state, + VC4_SET_FIELD(height - 1, + SCALER6_POS2_SRC_LINES) | + VC4_SET_FIELD(width - 1, + SCALER6_POS2_SRC_WIDTH)); + + /* Position Word 3: Context */ + vc4_dlist_write(vc4_state, 0xc0c0c0c0); + + /* + * TODO: This only covers Raster Scan Order planes + */ + for (i = 0; i < num_planes; i++) { + struct drm_gem_dma_object *bo = drm_fb_dma_get_gem_obj(fb, i); + dma_addr_t paddr = bo->dma_addr + fb->offsets[i] + offsets[i]; + + /* Pointer Word 0 */ + vc4_state->ptr0_offset[i] = vc4_state->dlist_count; + vc4_dlist_write(vc4_state, + (rotation & DRM_MODE_REFLECT_Y ? SCALER6_PTR0_VFLIP : 0) | + /* + * The UPM buffer will be allocated in + * vc6_plane_allocate_upm(). + */ + VC4_SET_FIELD(upper_32_bits(paddr) & 0xff, + SCALER6_PTR0_UPPER_ADDR)); + + /* Pointer Word 1 */ + vc4_dlist_write(vc4_state, lower_32_bits(paddr)); + + /* Pointer Word 2 */ + if (base_format_mod != DRM_FORMAT_MOD_BROADCOM_SAND128 && + base_format_mod != DRM_FORMAT_MOD_BROADCOM_SAND256) { + vc4_dlist_write(vc4_state, + VC4_SET_FIELD(fb->pitches[i], + SCALER6_PTR2_PITCH)); + } else { + vc4_dlist_write(vc4_state, pitch0); + } + } + + /* + * Palette Word 0 + * TODO: We're not using the palette mode + */ + + /* + * Trans Word 0 + * TODO: It's only relevant if we set the trans_rgb bit in the + * control word 0, and we don't at the moment. + */ + + vc4_state->lbm_offset = 0; + + if (!vc4_state->is_unity || fb->format->is_yuv) { + /* + * Reserve a slot for the LBM Base Address. The real value will + * be set when calling vc4_plane_allocate_lbm(). + */ + if (vc4_state->y_scaling[0] != VC4_SCALING_NONE || + vc4_state->y_scaling[1] != VC4_SCALING_NONE) { + vc4_state->lbm_offset = vc4_state->dlist_count; + vc4_dlist_counter_increment(vc4_state); + } + + if (vc4_state->x_scaling[0] != VC4_SCALING_NONE || + vc4_state->x_scaling[1] != VC4_SCALING_NONE || + vc4_state->y_scaling[0] != VC4_SCALING_NONE || + vc4_state->y_scaling[1] != VC4_SCALING_NONE) { + if (num_planes > 1) + /* + * Emit Cb/Cr as channel 0 and Y as channel + * 1. This matches how we set up scl0/scl1 + * above. + */ + vc4_write_scaling_parameters(state, 1); + + vc4_write_scaling_parameters(state, 0); + } + + /* + * If any PPF setup was done, then all the kernel + * pointers get uploaded. + */ + if (vc4_state->x_scaling[0] == VC4_SCALING_PPF || + vc4_state->y_scaling[0] == VC4_SCALING_PPF || + vc4_state->x_scaling[1] == VC4_SCALING_PPF || + vc4_state->y_scaling[1] == VC4_SCALING_PPF) { + struct drm_mm_node *filter; + + switch (state->scaling_filter) { + case DRM_SCALING_FILTER_DEFAULT: + default: + filter = &vc4->hvs->mitchell_netravali_filter; + break; + case DRM_SCALING_FILTER_NEAREST_NEIGHBOR: + filter = &vc4->hvs->nearest_neighbour_filter; + break; + } + u32 kernel = VC4_SET_FIELD(filter->start, + SCALER_PPF_KERNEL_OFFSET); + + /* HPPF plane 0 */ + vc4_dlist_write(vc4_state, kernel); + /* VPPF plane 0 */ + vc4_dlist_write(vc4_state, kernel); + /* HPPF plane 1 */ + vc4_dlist_write(vc4_state, kernel); + /* VPPF plane 1 */ + vc4_dlist_write(vc4_state, kernel); + } + } + + vc4_dlist_write(vc4_state, SCALER6_CTL0_END); + + vc4_state->dlist[0] |= + VC4_SET_FIELD(vc4_state->dlist_count, SCALER6_CTL0_NEXT); + + /* crtc_* are already clipped coordinates. */ + covers_screen = vc4_state->crtc_x == 0 && vc4_state->crtc_y == 0 && + vc4_state->crtc_w == state->crtc->mode.hdisplay && + vc4_state->crtc_h == state->crtc->mode.vdisplay; + + /* + * Background fill might be necessary when the plane has per-pixel + * alpha content or a non-opaque plane alpha and could blend from the + * background or does not cover the entire screen. + */ + vc4_state->needs_bg_fill = fb->format->has_alpha || !covers_screen || + state->alpha != DRM_BLEND_ALPHA_OPAQUE; + + /* + * Flag the dlist as initialized to avoid checking it twice in case + * the async update check already called vc4_plane_mode_set() and + * decided to fallback to sync update because async update was not + * possible. + */ + vc4_state->dlist_initialized = 1; + + vc4_plane_calc_load(state); + + drm_dbg_driver(drm, "[PLANE:%d:%s] Computed DLIST size: %u\n", + plane->base.id, plane->name, vc4_state->dlist_count); + + return 0; +} + +/* If a modeset involves changing the setup of a plane, the atomic + * infrastructure will call this to validate a proposed plane setup. + * However, if a plane isn't getting updated, this (and the + * corresponding vc4_plane_atomic_update) won't get called. Thus, we + * compute the dlist here and have all active plane dlists get updated + * in the CRTC's flush. + */ +int vc4_plane_atomic_check(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct vc4_dev *vc4 = to_vc4_dev(plane->dev); + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, + plane); + struct vc4_plane_state *vc4_state = to_vc4_plane_state(new_plane_state); + int ret; + + vc4_state->dlist_count = 0; + + if (!plane_enabled(new_plane_state)) { + struct drm_plane_state *old_plane_state = + drm_atomic_get_old_plane_state(state, plane); + + if (old_plane_state && plane_enabled(old_plane_state)) { + if (vc4->gen >= VC4_GEN_6_C) + vc6_plane_free_upm(new_plane_state); + vc4_plane_free_lbm(new_plane_state); + } + return 0; + } + + if (vc4->gen >= VC4_GEN_6_C) + ret = vc6_plane_mode_set(plane, new_plane_state); + else + ret = vc4_plane_mode_set(plane, new_plane_state); + if (ret) + return ret; + + if (!vc4_state->src_w[0] || !vc4_state->src_h[0] || + !vc4_state->crtc_w || !vc4_state->crtc_h) + return 0; + + ret = vc4_plane_allocate_lbm(new_plane_state); + if (ret) + return ret; + + if (vc4->gen >= VC4_GEN_6_C) { + ret = vc6_plane_allocate_upm(new_plane_state); + if (ret) + return ret; + } + + return 0; } static void vc4_plane_atomic_update(struct drm_plane *plane, @@ -1346,7 +2313,8 @@ void vc4_plane_async_set_fb(struct drm_plane *plane, struct drm_framebuffer *fb) { struct vc4_plane_state *vc4_state = to_vc4_plane_state(plane->state); struct drm_gem_dma_object *bo = drm_fb_dma_get_gem_obj(fb, 0); - uint32_t addr; + struct vc4_dev *vc4 = to_vc4_dev(plane->dev); + dma_addr_t dma_addr = bo->dma_addr + fb->offsets[0]; int idx; if (!drm_dev_enter(plane->dev, &idx)) @@ -1356,19 +2324,38 @@ void vc4_plane_async_set_fb(struct drm_plane *plane, struct drm_framebuffer *fb) * because this is only called on the primary plane. */ WARN_ON_ONCE(plane->state->crtc_x < 0 || plane->state->crtc_y < 0); - addr = bo->dma_addr + fb->offsets[0]; - /* Write the new address into the hardware immediately. The - * scanout will start from this address as soon as the FIFO - * needs to refill with pixels. - */ - writel(addr, &vc4_state->hw_dlist[vc4_state->ptr0_offset]); + if (vc4->gen == VC4_GEN_6_C) { + u32 value; - /* Also update the CPU-side dlist copy, so that any later - * atomic updates that don't do a new modeset on our plane - * also use our updated address. - */ - vc4_state->dlist[vc4_state->ptr0_offset] = addr; + value = vc4_state->dlist[vc4_state->ptr0_offset[0]] & + ~SCALER6_PTR0_UPPER_ADDR_MASK; + value |= VC4_SET_FIELD(upper_32_bits(dma_addr) & 0xff, + SCALER6_PTR0_UPPER_ADDR); + + writel(value, &vc4_state->hw_dlist[vc4_state->ptr0_offset[0]]); + vc4_state->dlist[vc4_state->ptr0_offset[0]] = value; + + value = lower_32_bits(dma_addr); + writel(value, &vc4_state->hw_dlist[vc4_state->ptr0_offset[0] + 1]); + vc4_state->dlist[vc4_state->ptr0_offset[0] + 1] = value; + } else { + u32 addr; + + addr = (u32)dma_addr; + + /* Write the new address into the hardware immediately. The + * scanout will start from this address as soon as the FIFO + * needs to refill with pixels. + */ + writel(addr, &vc4_state->hw_dlist[vc4_state->ptr0_offset[0]]); + + /* Also update the CPU-side dlist copy, so that any later + * atomic updates that don't do a new modeset on our plane + * also use our updated address. + */ + vc4_state->dlist[vc4_state->ptr0_offset[0]] = addr; + } drm_dev_exit(idx); } @@ -1423,8 +2410,6 @@ static void vc4_plane_atomic_async_update(struct drm_plane *plane, sizeof(vc4_state->y_scaling)); vc4_state->is_unity = new_vc4_state->is_unity; vc4_state->is_yuv = new_vc4_state->is_yuv; - memcpy(vc4_state->offsets, new_vc4_state->offsets, - sizeof(vc4_state->offsets)); vc4_state->needs_bg_fill = new_vc4_state->needs_bg_fill; /* Update the current vc4_state pos0, pos2 and ptr0 dlist entries. */ @@ -1432,8 +2417,8 @@ static void vc4_plane_atomic_async_update(struct drm_plane *plane, new_vc4_state->dlist[vc4_state->pos0_offset]; vc4_state->dlist[vc4_state->pos2_offset] = new_vc4_state->dlist[vc4_state->pos2_offset]; - vc4_state->dlist[vc4_state->ptr0_offset] = - new_vc4_state->dlist[vc4_state->ptr0_offset]; + vc4_state->dlist[vc4_state->ptr0_offset[0]] = + new_vc4_state->dlist[vc4_state->ptr0_offset[0]]; /* Note that we can't just call vc4_plane_write_dlist() * because that would smash the context data that the HVS is @@ -1443,8 +2428,8 @@ static void vc4_plane_atomic_async_update(struct drm_plane *plane, &vc4_state->hw_dlist[vc4_state->pos0_offset]); writel(vc4_state->dlist[vc4_state->pos2_offset], &vc4_state->hw_dlist[vc4_state->pos2_offset]); - writel(vc4_state->dlist[vc4_state->ptr0_offset], - &vc4_state->hw_dlist[vc4_state->ptr0_offset]); + writel(vc4_state->dlist[vc4_state->ptr0_offset[0]], + &vc4_state->hw_dlist[vc4_state->ptr0_offset[0]]); drm_dev_exit(idx); } @@ -1452,13 +2437,17 @@ static void vc4_plane_atomic_async_update(struct drm_plane *plane, static int vc4_plane_atomic_async_check(struct drm_plane *plane, struct drm_atomic_state *state) { + struct vc4_dev *vc4 = to_vc4_dev(plane->dev); struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, plane); struct vc4_plane_state *old_vc4_state, *new_vc4_state; int ret; u32 i; - ret = vc4_plane_mode_set(plane, new_plane_state); + if (vc4->gen <= VC4_GEN_5) + ret = vc4_plane_mode_set(plane, new_plane_state); + else + ret = vc6_plane_mode_set(plane, new_plane_state); if (ret) return ret; @@ -1471,7 +2460,7 @@ static int vc4_plane_atomic_async_check(struct drm_plane *plane, if (old_vc4_state->dlist_count != new_vc4_state->dlist_count || old_vc4_state->pos0_offset != new_vc4_state->pos0_offset || old_vc4_state->pos2_offset != new_vc4_state->pos2_offset || - old_vc4_state->ptr0_offset != new_vc4_state->ptr0_offset || + old_vc4_state->ptr0_offset[0] != new_vc4_state->ptr0_offset[0] || vc4_lbm_size(plane->state) != vc4_lbm_size(new_plane_state)) return -EINVAL; @@ -1481,7 +2470,7 @@ static int vc4_plane_atomic_async_check(struct drm_plane *plane, for (i = 0; i < new_vc4_state->dlist_count; i++) { if (i == new_vc4_state->pos0_offset || i == new_vc4_state->pos2_offset || - i == new_vc4_state->ptr0_offset || + i == new_vc4_state->ptr0_offset[0] || (new_vc4_state->lbm_offset && i == new_vc4_state->lbm_offset)) continue; @@ -1632,7 +2621,7 @@ struct drm_plane *vc4_plane_init(struct drm_device *dev, }; for (i = 0; i < ARRAY_SIZE(hvs_formats); i++) { - if (!hvs_formats[i].hvs5_only || vc4->gen == VC4_GEN_5) { + if (!hvs_formats[i].hvs5_only || vc4->gen >= VC4_GEN_5) { formats[num_formats] = hvs_formats[i].drm; num_formats++; } @@ -1647,7 +2636,7 @@ struct drm_plane *vc4_plane_init(struct drm_device *dev, return ERR_CAST(vc4_plane); plane = &vc4_plane->base; - if (vc4->gen == VC4_GEN_5) + if (vc4->gen >= VC4_GEN_5) drm_plane_helper_add(plane, &vc5_plane_helper_funcs); else drm_plane_helper_add(plane, &vc4_plane_helper_funcs); @@ -1672,6 +2661,12 @@ struct drm_plane *vc4_plane_init(struct drm_device *dev, DRM_COLOR_YCBCR_BT709, DRM_COLOR_YCBCR_LIMITED_RANGE); + drm_plane_create_scaling_filter_property(plane, + BIT(DRM_SCALING_FILTER_DEFAULT) | + BIT(DRM_SCALING_FILTER_NEAREST_NEIGHBOR)); + + drm_plane_create_chroma_siting_properties(plane, 0, 0); + if (type == DRM_PLANE_TYPE_PRIMARY) drm_plane_create_zpos_immutable_property(plane, 0); @@ -1679,12 +2674,27 @@ struct drm_plane *vc4_plane_init(struct drm_device *dev, } #define VC4_NUM_OVERLAY_PLANES 16 +#define VC4_NUM_TXP_OVERLAY_PLANES 32 int vc4_plane_create_additional_planes(struct drm_device *drm) { struct drm_plane *cursor_plane; struct drm_crtc *crtc; unsigned int i; + struct drm_crtc *txp_crtc; + uint32_t non_txp_crtc_mask; + + drm_for_each_crtc(crtc, drm) { + struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc); + + if (vc4_crtc->feeds_txp) { + txp_crtc = crtc; + break; + } + } + + non_txp_crtc_mask = GENMASK(drm->mode_config.num_crtc - 1, 0) - + drm_crtc_mask(txp_crtc); /* Set up some arbitrary number of planes. We're not limited * by a set number of physical registers, just the space in @@ -1698,7 +2708,22 @@ int vc4_plane_create_additional_planes(struct drm_device *drm) for (i = 0; i < VC4_NUM_OVERLAY_PLANES; i++) { struct drm_plane *plane = vc4_plane_init(drm, DRM_PLANE_TYPE_OVERLAY, - GENMASK(drm->mode_config.num_crtc - 1, 0)); + non_txp_crtc_mask); + + if (IS_ERR(plane)) + continue; + + /* Create zpos property. Max of all the overlays + 1 primary + + * 1 cursor plane on a crtc. + */ + drm_plane_create_zpos_property(plane, i + 1, 1, + VC4_NUM_OVERLAY_PLANES + 1); + } + + for (i = 0; i < VC4_NUM_TXP_OVERLAY_PLANES; i++) { + struct drm_plane *plane = + vc4_plane_init(drm, DRM_PLANE_TYPE_OVERLAY, + drm_crtc_mask(txp_crtc)); if (IS_ERR(plane)) continue; diff --git a/drivers/gpu/drm/vc4/vc4_regs.h b/drivers/gpu/drm/vc4/vc4_regs.h index 8ac9515554f8a0..27158be19952c8 100644 --- a/drivers/gpu/drm/vc4/vc4_regs.h +++ b/drivers/gpu/drm/vc4/vc4_regs.h @@ -19,6 +19,20 @@ #define VC4_GET_FIELD(word, field) FIELD_GET(field##_MASK, word) +#define VC6_SET_FIELD(value, field) \ + ({ \ + WARN_ON(!FIELD_FIT(hvs->vc4->gen == VC4_GEN_6_C ? \ + SCALER6_ ## field ## _MASK : \ + SCALER6D_ ## field ## _MASK, value));\ + FIELD_PREP(hvs->vc4->gen == VC4_GEN_6_C ? \ + SCALER6_ ## field ## _MASK : \ + SCALER6D_ ## field ## _MASK, value); \ + }) + +#define VC6_GET_FIELD(word, field) FIELD_GET(hvs->vc4->gen == VC4_GEN_6_C ? \ + SCALER6_ ## field ## _MASK : \ + SCALER6D_ ## field ## _MASK, word) + #define V3D_IDENT0 0x00000 # define V3D_EXPECTED_IDENT0 \ ((2 << 24) | \ @@ -155,6 +169,7 @@ # define PV_CONTROL_EN BIT(0) #define PV_V_CONTROL 0x04 +# define PV_VCONTROL_ODD_TIMING BIT(29) # define PV_VCONTROL_ODD_DELAY_MASK VC4_MASK(22, 6) # define PV_VCONTROL_ODD_DELAY_SHIFT 6 # define PV_VCONTROL_ODD_FIRST BIT(5) @@ -215,6 +230,11 @@ # define PV_MUX_CFG_RGB_PIXEL_MUX_MODE_SHIFT 2 # define PV_MUX_CFG_RGB_PIXEL_MUX_MODE_NO_SWAP 8 +#define PV_PIPE_INIT_CTRL 0x94 +# define PV_PIPE_INIT_CTRL_PV_INIT_WIDTH_MASK VC4_MASK(11, 8) +# define PV_PIPE_INIT_CTRL_PV_INIT_IDLE_MASK VC4_MASK(7, 4) +# define PV_PIPE_INIT_CTRL_PV_INIT_EN BIT(0) + #define SCALER_CHANNELS_COUNT 3 #define SCALER_DISPCTRL 0x00000000 @@ -418,6 +438,10 @@ # define SCALER_DISPSTAT1_FRCNT0_SHIFT 18 # define SCALER_DISPSTAT1_FRCNT1_MASK VC4_MASK(17, 12) # define SCALER_DISPSTAT1_FRCNT1_SHIFT 12 +# define SCALER5_DISPSTAT1_FRCNT0_MASK VC4_MASK(25, 20) +# define SCALER5_DISPSTAT1_FRCNT0_SHIFT 20 +# define SCALER5_DISPSTAT1_FRCNT1_MASK VC4_MASK(19, 14) +# define SCALER5_DISPSTAT1_FRCNT1_SHIFT 14 #define SCALER_DISPSTATX(x) (SCALER_DISPSTAT0 + \ (x) * (SCALER_DISPSTAT1 - \ @@ -436,6 +460,8 @@ #define SCALER_DISPSTAT2 0x00000068 # define SCALER_DISPSTAT2_FRCNT2_MASK VC4_MASK(17, 12) # define SCALER_DISPSTAT2_FRCNT2_SHIFT 12 +# define SCALER5_DISPSTAT2_FRCNT2_MASK VC4_MASK(19, 14) +# define SCALER5_DISPSTAT2_FRCNT2_SHIFT 14 #define SCALER_DISPBASE2 0x0000006c #define SCALER_DISPALPHA2 0x00000070 @@ -514,6 +540,206 @@ #define SCALER5_DLIST_START 0x00004000 +#define SCALER6_VERSION 0x00000000 +# define SCALER6_VERSION_MASK VC4_MASK(7, 0) +# define SCALER6_VERSION_C0 0x00000053 +# define SCALER6_VERSION_D0 0x00000054 +#define SCALER6_CXM_SIZE 0x00000004 +#define SCALER6_LBM_SIZE 0x00000008 +#define SCALER6_UBM_SIZE 0x0000000c +#define SCALER6_COBA_SIZE 0x00000010 +#define SCALER6_COB_SIZE 0x00000014 + +#define SCALER6_CONTROL 0x00000020 +# define SCALER6_CONTROL_HVS_EN BIT(31) +# define SCALER6_CONTROL_PF_LINES_MASK VC4_MASK(22, 18) +# define SCALER6_CONTROL_ABORT_ON_EMPTY BIT(16) +# define SCALER6_CONTROL_DSP1_TARGET_MASK VC4_MASK(13, 12) +# define SCALER6_CONTROL_MAX_REQS_MASK VC4_MASK(7, 4) + +#define SCALER6_FETCHER_STATUS 0x00000024 +#define SCALER6_FETCH_STATUS 0x00000028 +#define SCALER6_HANDLE_ERROR 0x0000002c + +#define SCALER6_DISP0_CTRL0 0x00000030 +#define SCALER6_DISPX_CTRL0(x) ((hvs->vc4->gen == VC4_GEN_6_C) ? \ + (SCALER6_DISP0_CTRL0 + ((x) * (SCALER6_DISP1_CTRL0 - SCALER6_DISP0_CTRL0))) : \ + (SCALER6D_DISP0_CTRL0 + ((x) * (SCALER6D_DISP1_CTRL0 - SCALER6D_DISP0_CTRL0)))) +# define SCALER6_DISPX_CTRL0_ENB BIT(31) +# define SCALER6_DISPX_CTRL0_RESET BIT(30) +# define SCALER6_DISPX_CTRL0_FWIDTH_MASK VC4_MASK(28, 16) +# define SCALER6_DISPX_CTRL0_ONESHOT BIT(15) +# define SCALER6_DISPX_CTRL0_ONECTX_MASK VC4_MASK(14, 13) +# define SCALER6_DISPX_CTRL0_LINES_MASK VC4_MASK(12, 0) + +#define SCALER6_DISP0_CTRL1 0x00000034 +#define SCALER6_DISPX_CTRL1(x) ((hvs->vc4->gen == VC4_GEN_6_C) ? \ + (SCALER6_DISP0_CTRL1 + ((x) * (SCALER6_DISP1_CTRL1 - SCALER6_DISP0_CTRL1))) : \ + (SCALER6D_DISP0_CTRL1 + ((x) * (SCALER6D_DISP1_CTRL1 - SCALER6D_DISP0_CTRL1)))) +# define SCALER6_DISPX_CTRL1_BGENB BIT(8) +# define SCALER6_DISPX_CTRL1_INTLACE BIT(0) + +#define SCALER6_DISP0_BGND 0x00000038 +#define SCALER6_DISPX_BGND(x) ((hvs->vc4->gen == VC4_GEN_6_C) ? \ + (SCALER6_DISP0_BGND + ((x) * (SCALER6_DISP1_BGND - SCALER6_DISP0_BGND))) : \ + (SCALER6D_DISP0_BGND + ((x) * (SCALER6D_DISP1_BGND - SCALER6D_DISP0_BGND)))) + +#define SCALER6_DISP0_LPTRS 0x0000003c +#define SCALER6_DISPX_LPTRS(x) ((hvs->vc4->gen == VC4_GEN_6_C) ? \ + (SCALER6_DISP0_LPTRS + ((x) * (SCALER6_DISP1_LPTRS - SCALER6_DISP0_LPTRS))) : \ + (SCALER6D_DISP0_LPTRS + ((x) * (SCALER6D_DISP1_LPTRS - SCALER6D_DISP0_LPTRS)))) +# define SCALER6_DISPX_LPTRS_HEADE_MASK VC4_MASK(11, 0) + +#define SCALER6_DISP0_COB 0x00000040 +#define SCALER6_DISPX_COB(x) ((hvs->vc4->gen == VC4_GEN_6_C) ? \ + (SCALER6_DISP0_COB + ((x) * (SCALER6_DISP1_COB - SCALER6_DISP0_COB))) : \ + (SCALER6D_DISP0_COB + ((x) * (SCALER6D_DISP1_COB - SCALER6D_DISP0_COB)))) +# define SCALER6_DISPX_COB_TOP_MASK VC4_MASK(31, 16) +# define SCALER6_DISPX_COB_BASE_MASK VC4_MASK(15, 0) + +#define SCALER6_DISP0_STATUS 0x00000044 +#define SCALER6_DISPX_STATUS(x) ((hvs->vc4->gen == VC4_GEN_6_C) ? \ + (SCALER6_DISP0_STATUS + ((x) * (SCALER6_DISP1_STATUS - SCALER6_DISP0_STATUS))) : \ + (SCALER6D_DISP0_STATUS + ((x) * (SCALER6D_DISP1_STATUS - SCALER6D_DISP0_STATUS)))) +# define SCALER6_DISPX_STATUS_EMPTY BIT(22) +# define SCALER6_DISPX_STATUS_FRCNT_MASK VC4_MASK(21, 16) +# define SCALER6_DISPX_STATUS_OFIELD BIT(15) +# define SCALER6_DISPX_STATUS_MODE_MASK VC4_MASK(14, 13) +# define SCALER6_DISPX_STATUS_MODE_DISABLED 0 +# define SCALER6_DISPX_STATUS_MODE_INIT 1 +# define SCALER6_DISPX_STATUS_MODE_RUN 2 +# define SCALER6_DISPX_STATUS_MODE_EOF 3 +# define SCALER6_DISPX_STATUS_YLINE_MASK VC4_MASK(12, 0) + +#define SCALER6_DISP0_DL 0x00000048 + +#define SCALER6_DISPX_DL(x) ((hvs->vc4->gen == VC4_GEN_6_C) ? \ + (SCALER6_DISP0_DL + ((x) * (SCALER6_DISP1_DL - SCALER6_DISP0_DL))) : \ + (SCALER6D_DISP0_DL + ((x) * (SCALER6D_DISP1_DL - SCALER6D_DISP0_DL)))) +# define SCALER6_DISPX_DL_LACT_MASK VC4_MASK(11, 0) + +#define SCALER6_DISP0_RUN 0x0000004c +#define SCALER6_DISP1_CTRL0 0x00000050 +#define SCALER6_DISP1_CTRL1 0x00000054 +#define SCALER6_DISP1_BGND 0x00000058 +#define SCALER6_DISP1_LPTRS 0x0000005c +#define SCALER6_DISP1_COB 0x00000060 +#define SCALER6_DISP1_STATUS 0x00000064 +#define SCALER6_DISP1_DL 0x00000068 +#define SCALER6_DISP1_RUN 0x0000006c +#define SCALER6_DISP2_CTRL0 0x00000070 +#define SCALER6_DISP2_CTRL1 0x00000074 +#define SCALER6_DISP2_BGND 0x00000078 +#define SCALER6_DISP2_LPTRS 0x0000007c +#define SCALER6_DISP2_COB 0x00000080 +#define SCALER6_DISP2_STATUS 0x00000084 +#define SCALER6_DISP2_DL 0x00000088 +#define SCALER6_DISP2_RUN 0x0000008c +#define SCALER6_EOLN 0x00000090 +#define SCALER6_DL_STATUS 0x00000094 +#define SCALER6_BFG_MISC 0x0000009c +#define SCALER6_QOS0 0x000000a0 +#define SCALER6_PROF0 0x000000a4 +#define SCALER6_QOS1 0x000000a8 +#define SCALER6_PROF1 0x000000ac +#define SCALER6_QOS2 0x000000b0 +#define SCALER6_PROF2 0x000000b4 +#define SCALER6_PRI_MAP0 0x000000b8 +#define SCALER6_PRI_MAP1 0x000000bc +#define SCALER6_HISTCTRL 0x000000c0 +#define SCALER6_HISTBIN0 0x000000c4 +#define SCALER6_HISTBIN1 0x000000c8 +#define SCALER6_HISTBIN2 0x000000cc +#define SCALER6_HISTBIN3 0x000000d0 +#define SCALER6_HISTBIN4 0x000000d4 +#define SCALER6_HISTBIN5 0x000000d8 +#define SCALER6_HISTBIN6 0x000000dc +#define SCALER6_HISTBIN7 0x000000e0 +#define SCALER6_HDR_CFG_REMAP 0x000000f4 +#define SCALER6_COL_SPACE 0x000000f8 +#define SCALER6_HVS_ID 0x000000fc +#define SCALER6_CFC1 0x00000100 +#define SCALER6_DISP_UPM_ISO0 0x00000200 +#define SCALER6_DISP_UPM_ISO1 0x00000204 +#define SCALER6_DISP_UPM_ISO2 0x00000208 +#define SCALER6_DISP_LBM_ISO0 0x0000020c +#define SCALER6_DISP_LBM_ISO1 0x00000210 +#define SCALER6_DISP_LBM_ISO2 0x00000214 +#define SCALER6_DISP_COB_ISO0 0x00000218 +#define SCALER6_DISP_COB_ISO1 0x0000021c +#define SCALER6_DISP_COB_ISO2 0x00000220 +#define SCALER6_BAD_COB 0x00000224 +#define SCALER6_BAD_LBM 0x00000228 +#define SCALER6_BAD_UPM 0x0000022c +#define SCALER6_BAD_AXI 0x00000230 + +#define SCALER6D_VERSION 0x00000000 +#define SCALER6D_CXM_SIZE 0x00000004 +#define SCALER6D_LBM_SIZE 0x00000008 +#define SCALER6D_UBM_SIZE 0x0000000c +#define SCALER6D_COBA_SIZE 0x00000010 +#define SCALER6D_COB_SIZE 0x00000014 +#define SCALER6D_CONTROL 0x00000020 +#define SCALER6D_FETCHER_STATUS 0x00000024 +#define SCALER6D_FETCH_STATUS 0x00000028 +#define SCALER6D_HANDLE_ERROR 0x0000002c +#define SCALER6D_EOLN 0x00000030 +#define SCALER6D_DL_STATUS 0x00000034 +#define SCALER6D_PRI_MAP0 0x00000038 +#define SCALER6D_PRI_MAP1 0x0000003c +#define SCALER6D_HISTCTRL 0x000000d0 +#define SCALER6D_HISTBIN0 0x000000d4 +#define SCALER6D_HISTBIN1 0x000000d8 +#define SCALER6D_HISTBIN2 0x000000dc +#define SCALER6D_HISTBIN3 0x000000e0 +#define SCALER6D_HISTBIN4 0x000000e4 +#define SCALER6D_HISTBIN5 0x000000e8 +#define SCALER6D_HISTBIN6 0x000000ec +#define SCALER6D_HISTBIN7 0x000000f0 +#define SCALER6D_HVS_ID 0x000000fc + +#define SCALER6D_DISP0_CTRL0 0x00000100 +#define SCALER6D_DISP0_CTRL1 0x00000104 +#define SCALER6D_DISP0_BGND 0x00000108 +#define SCALER6D_DISP0_LPTRS 0x00000110 +#define SCALER6D_DISP0_COB 0x00000114 +#define SCALER6D_DISP0_STATUS 0x00000118 +#define SCALER6D_DISP0_CTRL0 0x00000100 +#define SCALER6D_DISP0_CTRL1 0x00000104 +#define SCALER6D_DISP0_BGND0 0x00000108 +#define SCALER6D_DISP0_BGND1 0x0000010c +#define SCALER6D_DISP0_LPTRS 0x00000110 +#define SCALER6D_DISP0_COB 0x00000114 +#define SCALER6D_DISP0_STATUS 0x00000118 +#define SCALER6D_DISP0_DL 0x0000011c +#define SCALER6D_DISP0_RUN 0x00000120 +#define SCALER6D_QOS0 0x00000124 +#define SCALER6D_PROF0 0x00000128 +#define SCALER6D_DISP1_CTRL0 0x00000140 +#define SCALER6D_DISP1_CTRL1 0x00000144 +#define SCALER6D_DISP1_BGND0 0x00000148 +#define SCALER6D_DISP1_BGND1 0x0000014c +#define SCALER6D_DISP1_LPTRS 0x00000150 +#define SCALER6D_DISP1_COB 0x00000154 +#define SCALER6D_DISP1_STATUS 0x00000158 +#define SCALER6D_DISP1_DL 0x0000015c +#define SCALER6D_DISP1_RUN 0x00000160 +#define SCALER6D_QOS1 0x00000164 +#define SCALER6D_PROF1 0x00000168 +#define SCALER6D_DISP2_CTRL0 0x00000180 +#define SCALER6D_DISP2_CTRL1 0x00000184 +#define SCALER6D_DISP2_BGND0 0x00000188 +#define SCALER6D_DISP2_BGND1 0x0000018c +#define SCALER6D_DISP2_LPTRS 0x00000190 +#define SCALER6D_DISP2_COB 0x00000194 +#define SCALER6D_DISP2_STATUS 0x00000198 +#define SCALER6D_DISP2_DL 0x0000019c +#define SCALER6D_DISP2_RUN 0x000001a0 +#define SCALER6D_QOS2 0x000001a4 +#define SCALER6D_PROF2 0x000001a8 + +#define SCALER6(x) ((hvs->vc4->gen == VC4_GEN_6_C) ? SCALER6_ ## x : SCALER6D_ ## x) + # define VC4_HDMI_SW_RESET_FORMAT_DETECT BIT(1) # define VC4_HDMI_SW_RESET_HDMI BIT(0) @@ -761,6 +987,15 @@ enum { # define VC4_HD_MAI_THR_DREQLOW_MASK VC4_MASK(5, 0) # define VC4_HD_MAI_THR_DREQLOW_SHIFT 0 +# define VC6_D_HD_MAI_THR_PANICHIGH_MASK VC4_MASK(29, 23) +# define VC6_D_HD_MAI_THR_PANICHIGH_SHIFT 23 +# define VC6_D_HD_MAI_THR_PANICLOW_MASK VC4_MASK(21, 15) +# define VC6_D_HD_MAI_THR_PANICLOW_SHIFT 15 +# define VC6_D_HD_MAI_THR_DREQHIGH_MASK VC4_MASK(13, 7) +# define VC6_D_HD_MAI_THR_DREQHIGH_SHIFT 7 +# define VC6_D_HD_MAI_THR_DREQLOW_MASK VC4_MASK(6, 0) +# define VC6_D_HD_MAI_THR_DREQLOW_SHIFT 0 + /* Divider from HDMI HSM clock to MAI serial clock. Sampling period * converges to N / (M + 1) cycles. */ @@ -777,6 +1012,7 @@ enum { # define VC4_HD_VID_CTL_CLRSYNC BIT(24) # define VC4_HD_VID_CTL_CLRRGB BIT(23) # define VC4_HD_VID_CTL_BLANKPIX BIT(18) +# define VC4_HD_VID_CTL_BLANK_INSERT_EN BIT(16) # define VC4_HD_CSC_CTL_ORDER_MASK VC4_MASK(7, 5) # define VC4_HD_CSC_CTL_ORDER_SHIFT 5 @@ -967,6 +1203,9 @@ enum hvs_pixel_format { #define SCALER5_CTL2_ALPHA_MASK VC4_MASK(15, 4) #define SCALER5_CTL2_ALPHA_SHIFT 4 +#define SCALER6D_CTL2_CSC_ENABLE BIT(19) +#define SCALER6D_CTL2_BRCM_CFC_CONTROL_MASK VC4_MASK(22, 20) + #define SCALER_POS1_SCL_HEIGHT_MASK VC4_MASK(27, 16) #define SCALER_POS1_SCL_HEIGHT_SHIFT 16 @@ -1108,4 +1347,63 @@ enum hvs_pixel_format { #define SCALER_PITCH0_TILE_WIDTH_R_MASK VC4_MASK(6, 0) #define SCALER_PITCH0_TILE_WIDTH_R_SHIFT 0 +#define SCALER6_CTL0_END BIT(31) +#define SCALER6_CTL0_VALID BIT(30) +#define SCALER6_CTL0_NEXT_MASK VC4_MASK(29, 24) +#define SCALER6_CTL0_RGB_TRANS BIT(23) +#define SCALER6_CTL0_ADDR_MODE_MASK VC4_MASK(22, 20) +#define SCALER6_CTL0_ADDR_MODE_LINEAR 0 +#define SCALER6_CTL0_ADDR_MODE_128B 1 +#define SCALER6_CTL0_ADDR_MODE_256B 2 +#define SCALER6_CTL0_ADDR_MODE_MAP8 3 +#define SCALER6_CTL0_ADDR_MODE_UIF 4 + +#define SCALER6_CTL0_ALPHA_MASK_MASK VC4_MASK(19, 18) +#define SCALER6_CTL0_ALPHA_MASK_NONE 0 +#define SCALER6D_CTL0_ALPHA_MASK_FIXED 3 +#define SCALER6_CTL0_UNITY BIT(15) +#define SCALER6_CTL0_ORDERRGBA_MASK VC4_MASK(14, 13) +#define SCALER6_CTL0_SCL1_MODE_MASK VC4_MASK(10, 8) +#define SCALER6_CTL0_SCL0_MODE_MASK VC4_MASK(7, 5) +#define SCALER6_CTL0_PIXEL_FORMAT_MASK VC4_MASK(4, 0) + +#define SCALER6_POS0_START_Y_MASK VC4_MASK(28, 16) +#define SCALER6_POS0_HFLIP BIT(15) +#define SCALER6_POS0_START_X_MASK VC4_MASK(12, 0) + +#define SCALER6_CTL2_ALPHA_MODE_MASK VC4_MASK(31, 30) +#define SCALER6_CTL2_ALPHA_PREMULT BIT(29) +#define SCALER6_CTL2_ALPHA_MIX BIT(28) +#define SCALER6_CTL2_BFG BIT(26) +#define SCALER6C_CTL2_CSC_ENABLE BIT(25) +#define SCALER6C_CTL2_BRCM_CFC_CONTROL_MASK VC4_MASK(18, 16) +#define SCALER6_CTL2_ALPHA_MASK VC4_MASK(15, 4) + +#define SCALER6_POS1_SCL_LINES_MASK VC4_MASK(28, 16) +#define SCALER6_POS1_SCL_WIDTH_MASK VC4_MASK(12, 0) + +#define SCALER6_POS2_SRC_LINES_MASK VC4_MASK(28, 16) +#define SCALER6_POS2_SRC_WIDTH_MASK VC4_MASK(12, 0) + +#define SCALER6_PTR0_VFLIP BIT(31) +#define SCALER6_PTR0_UPM_BASE_MASK VC4_MASK(28, 16) +#define SCALER6_PTR0_UPM_HANDLE_MASK VC4_MASK(14, 10) +#define SCALER6_PTR0_UPM_BUFF_SIZE_MASK VC4_MASK(9, 8) +#define SCALER6_PTR0_UPM_BUFF_SIZE_16_LINES 3 +#define SCALER6_PTR0_UPM_BUFF_SIZE_8_LINES 2 +#define SCALER6_PTR0_UPM_BUFF_SIZE_4_LINES 1 +#define SCALER6_PTR0_UPM_BUFF_SIZE_2_LINES 0 +#define SCALER6_PTR0_UPPER_ADDR_MASK VC4_MASK(7, 0) + +#define SCALER6_PTR2_ALPHA_BPP_MASK VC4_MASK(31, 31) +#define SCALER6_PTR2_ALPHA_BPP_1BPP 1 +#define SCALER6_PTR2_ALPHA_BPP_8BPP 0 +#define SCALER6_PTR2_ALPHA_ORDER_MASK VC4_MASK(30, 30) +#define SCALER6_PTR2_ALPHA_ORDER_MSB_TO_LSB 1 +#define SCALER6_PTR2_ALPHA_ORDER_LSB_TO_MSB 0 +#define SCALER6_PTR2_ALPHA_OFFS_MASK VC4_MASK(29, 27) +#define SCALER6_PTR2_LSKIP_MASK VC4_MASK(26, 24) +#define SCALER6_PTR2_PITCH_MASK VC4_MASK(16, 0) +#define SCALER6_PTR2_FETCH_COUNT_MASK VC4_MASK(26, 16) + #endif /* VC4_REGS_H */ diff --git a/drivers/gpu/drm/vc4/vc4_render_cl.c b/drivers/gpu/drm/vc4/vc4_render_cl.c index ae4ad956f04ff8..14079853338ebd 100644 --- a/drivers/gpu/drm/vc4/vc4_render_cl.c +++ b/drivers/gpu/drm/vc4/vc4_render_cl.c @@ -599,7 +599,7 @@ int vc4_get_rcl(struct drm_device *dev, struct vc4_exec_info *exec) bool has_bin = args->bin_cl_size != 0; int ret; - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return -ENODEV; if (args->min_x_tile > args->max_x_tile || diff --git a/drivers/gpu/drm/vc4/vc4_txp.c b/drivers/gpu/drm/vc4/vc4_txp.c index ffe1f7d1b911d3..f739303b2e9ebe 100644 --- a/drivers/gpu/drm/vc4/vc4_txp.c +++ b/drivers/gpu/drm/vc4/vc4_txp.c @@ -15,6 +15,7 @@ #include <drm/drm_atomic.h> #include <drm/drm_atomic_helper.h> +#include <drm/drm_blend.h> #include <drm/drm_drv.h> #include <drm/drm_edid.h> #include <drm/drm_fb_dma_helper.h> @@ -145,6 +146,9 @@ /* Number of lines received and committed to memory. */ #define TXP_PROGRESS 0x10 +#define TXP_DST_PTR_HIGH_MOPLET 0x1c +#define TXP_DST_PTR_HIGH_MOP 0x24 + #define TXP_READ(offset) \ ({ \ kunit_fail_current_test("Accessing a register in a unit test!\n"); \ @@ -159,6 +163,7 @@ struct vc4_txp { struct vc4_crtc base; + const struct vc4_txp_data *data; struct platform_device *pdev; @@ -255,10 +260,22 @@ static int vc4_txp_connector_atomic_check(struct drm_connector *conn, crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc); fb = conn_state->writeback_job->fb; - if (fb->width != crtc_state->mode.hdisplay || - fb->height != crtc_state->mode.vdisplay) { - DRM_DEBUG_KMS("Invalid framebuffer size %ux%u\n", - fb->width, fb->height); + if ((conn_state->rotation == DRM_MODE_ROTATE_0 && + fb->width != crtc_state->mode.hdisplay && + fb->height != crtc_state->mode.vdisplay) || + (conn_state->rotation == (DRM_MODE_ROTATE_0 | DRM_MODE_TRANSPOSE) && + fb->width != crtc_state->mode.vdisplay && + fb->height != crtc_state->mode.hdisplay)) { + DRM_DEBUG_KMS("Invalid framebuffer size %ux%u vs mode %ux%u\n", + fb->width, fb->height, + crtc_state->mode.hdisplay, crtc_state->mode.vdisplay); + return -EINVAL; + } + + if (conn_state->rotation & DRM_MODE_TRANSPOSE && + (fb->format->format == DRM_FORMAT_RGB888 || + fb->format->format == DRM_FORMAT_BGR888)) { + DRM_DEBUG_KMS("24bpp formats not supported when transposing\n"); return -EINVAL; } @@ -286,9 +303,13 @@ static void vc4_txp_connector_atomic_commit(struct drm_connector *conn, struct drm_connector_state *conn_state = drm_atomic_get_new_connector_state(state, conn); struct vc4_txp *txp = connector_to_vc4_txp(conn); + const struct vc4_txp_data *txp_data = txp->data; struct drm_gem_dma_object *gem; struct drm_display_mode *mode; struct drm_framebuffer *fb; + unsigned int hdisplay; + unsigned int vdisplay; + dma_addr_t addr; u32 ctrl; int idx; int i; @@ -308,9 +329,11 @@ static void vc4_txp_connector_atomic_commit(struct drm_connector *conn, return; ctrl = TXP_GO | TXP_EI | - VC4_SET_FIELD(0xf, TXP_BYTE_ENABLE) | VC4_SET_FIELD(txp_fmts[i], TXP_FORMAT); + if (txp_data->has_byte_enable) + ctrl |= VC4_SET_FIELD(0xf, TXP_BYTE_ENABLE); + if (fb->format->has_alpha) ctrl |= TXP_ALPHA_ENABLE; else @@ -320,15 +343,32 @@ static void vc4_txp_connector_atomic_commit(struct drm_connector *conn, */ ctrl |= TXP_ALPHA_INVERT; + if (conn_state->rotation & DRM_MODE_TRANSPOSE) + ctrl |= TXP_TRANSPOSE; + if (!drm_dev_enter(drm, &idx)) return; gem = drm_fb_dma_get_gem_obj(fb, 0); - TXP_WRITE(TXP_DST_PTR, gem->dma_addr + fb->offsets[0]); + addr = gem->dma_addr + fb->offsets[0]; + + TXP_WRITE(TXP_DST_PTR, lower_32_bits(addr)); + + if (txp_data->supports_40bit_addresses) + TXP_WRITE(txp_data->high_addr_ptr_reg, upper_32_bits(addr) & 0xff); + TXP_WRITE(TXP_DST_PITCH, fb->pitches[0]); + + hdisplay = mode->hdisplay ?: 1; + vdisplay = mode->vdisplay ?: 1; + if (txp_data->size_minus_one) { + hdisplay -= 1; + vdisplay -= 1; + } + TXP_WRITE(TXP_DIM, - VC4_SET_FIELD(mode->hdisplay, TXP_WIDTH) | - VC4_SET_FIELD(mode->vdisplay, TXP_HEIGHT)); + VC4_SET_FIELD(hdisplay, TXP_WIDTH) | + VC4_SET_FIELD(vdisplay, TXP_HEIGHT)); TXP_WRITE(TXP_DST_CTRL, ctrl); @@ -362,6 +402,7 @@ static const struct drm_connector_funcs vc4_txp_connector_funcs = { static void vc4_txp_encoder_disable(struct drm_encoder *encoder) { struct drm_device *drm = encoder->dev; + struct vc4_dev *vc4 = to_vc4_dev(drm); struct vc4_txp *txp = encoder_to_vc4_txp(encoder); int idx; @@ -380,7 +421,8 @@ static void vc4_txp_encoder_disable(struct drm_encoder *encoder) WARN_ON(TXP_READ(TXP_DST_CTRL) & TXP_BUSY); } - TXP_WRITE(TXP_DST_CTRL, TXP_POWERDOWN); + if (vc4->gen < VC4_GEN_6_C) + TXP_WRITE(TXP_DST_CTRL, TXP_POWERDOWN); drm_dev_exit(idx); } @@ -484,17 +526,49 @@ static irqreturn_t vc4_txp_interrupt(int irq, void *data) return IRQ_HANDLED; } -const struct vc4_crtc_data vc4_txp_crtc_data = { - .name = "txp", - .debugfs_name = "txp_regs", - .hvs_available_channels = BIT(2), - .hvs_output = 2, +const struct vc4_txp_data bcm2712_mop_data = { + .base = { + .name = "mop", + .debugfs_name = "mop_regs", + .hvs_available_channels = BIT(2), + .hvs_output = 2, + }, + .encoder_type = VC4_ENCODER_TYPE_TXP0, + .high_addr_ptr_reg = TXP_DST_PTR_HIGH_MOP, + .has_byte_enable = true, + .size_minus_one = true, + .supports_40bit_addresses = true, +}; + +const struct vc4_txp_data bcm2712_moplet_data = { + .base = { + .name = "moplet", + .debugfs_name = "moplet_regs", + .hvs_available_channels = BIT(1), + .hvs_output = 4, + }, + .encoder_type = VC4_ENCODER_TYPE_TXP1, + .high_addr_ptr_reg = TXP_DST_PTR_HIGH_MOPLET, + .size_minus_one = true, + .supports_40bit_addresses = true, +}; + +const struct vc4_txp_data bcm2835_txp_data = { + .base = { + .name = "txp", + .debugfs_name = "txp_regs", + .hvs_available_channels = BIT(2), + .hvs_output = 2, + }, + .encoder_type = VC4_ENCODER_TYPE_TXP0, + .has_byte_enable = true, }; static int vc4_txp_bind(struct device *dev, struct device *master, void *data) { struct platform_device *pdev = to_platform_device(dev); struct drm_device *drm = dev_get_drvdata(master); + const struct vc4_txp_data *txp_data; struct vc4_encoder *vc4_encoder; struct drm_encoder *encoder; struct vc4_crtc *vc4_crtc; @@ -509,6 +583,11 @@ static int vc4_txp_bind(struct device *dev, struct device *master, void *data) if (!txp) return -ENOMEM; + txp_data = of_device_get_match_data(dev); + if (!txp_data) + return -ENODEV; + + txp->data = txp_data; txp->pdev = pdev; txp->regs = vc4_ioremap_regs(pdev, 0); if (IS_ERR(txp->regs)) @@ -519,13 +598,13 @@ static int vc4_txp_bind(struct device *dev, struct device *master, void *data) vc4_crtc->regset.regs = txp_regs; vc4_crtc->regset.nregs = ARRAY_SIZE(txp_regs); - ret = vc4_crtc_init(drm, pdev, vc4_crtc, &vc4_txp_crtc_data, + ret = vc4_crtc_init(drm, pdev, vc4_crtc, &txp_data->base, &vc4_txp_crtc_funcs, &vc4_txp_crtc_helper_funcs, true); if (ret) return ret; vc4_encoder = &txp->encoder; - txp->encoder.type = VC4_ENCODER_TYPE_TXP; + txp->encoder.type = txp_data->encoder_type; encoder = &vc4_encoder->base; encoder->possible_crtcs = drm_crtc_mask(&vc4_crtc->base); @@ -545,6 +624,10 @@ static int vc4_txp_bind(struct device *dev, struct device *master, void *data) if (ret) return ret; + drm_connector_create_rotation_property(&txp->connector.base, DRM_MODE_ROTATE_0, + DRM_MODE_ROTATE_0 | + DRM_MODE_TRANSPOSE); + ret = devm_request_irq(dev, irq, vc4_txp_interrupt, 0, dev_name(dev), txp); if (ret) @@ -579,7 +662,9 @@ static void vc4_txp_remove(struct platform_device *pdev) } static const struct of_device_id vc4_txp_dt_match[] = { - { .compatible = "brcm,bcm2835-txp" }, + { .compatible = "brcm,bcm2712-mop", .data = &bcm2712_mop_data }, + { .compatible = "brcm,bcm2712-moplet", .data = &bcm2712_moplet_data }, + { .compatible = "brcm,bcm2835-txp", .data = &bcm2835_txp_data }, { /* sentinel */ }, }; diff --git a/drivers/gpu/drm/vc4/vc4_v3d.c b/drivers/gpu/drm/vc4/vc4_v3d.c index 43f69d74e8761d..76502406f0c324 100644 --- a/drivers/gpu/drm/vc4/vc4_v3d.c +++ b/drivers/gpu/drm/vc4/vc4_v3d.c @@ -127,7 +127,7 @@ static int vc4_v3d_debugfs_ident(struct seq_file *m, void *unused) int vc4_v3d_pm_get(struct vc4_dev *vc4) { - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return -ENODEV; mutex_lock(&vc4->power_lock); @@ -148,7 +148,7 @@ vc4_v3d_pm_get(struct vc4_dev *vc4) void vc4_v3d_pm_put(struct vc4_dev *vc4) { - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return; mutex_lock(&vc4->power_lock); @@ -178,7 +178,7 @@ int vc4_v3d_get_bin_slot(struct vc4_dev *vc4) uint64_t seqno = 0; struct vc4_exec_info *exec; - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return -ENODEV; try_again: @@ -325,7 +325,7 @@ int vc4_v3d_bin_bo_get(struct vc4_dev *vc4, bool *used) { int ret = 0; - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return -ENODEV; mutex_lock(&vc4->bin_bo_lock); @@ -360,7 +360,7 @@ static void bin_bo_release(struct kref *ref) void vc4_v3d_bin_bo_put(struct vc4_dev *vc4) { - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return; mutex_lock(&vc4->bin_bo_lock); @@ -376,6 +376,8 @@ static int vc4_v3d_runtime_suspend(struct device *dev) vc4_irq_disable(&vc4->base); + /* we no longer require a minimum clock rate */ + clk_set_min_rate(v3d->clk, 0); clk_disable_unprepare(v3d->clk); return 0; diff --git a/drivers/gpu/drm/vc4/vc4_validate.c b/drivers/gpu/drm/vc4/vc4_validate.c index f3d7fdbe9083c5..5bf134968adecf 100644 --- a/drivers/gpu/drm/vc4/vc4_validate.c +++ b/drivers/gpu/drm/vc4/vc4_validate.c @@ -109,7 +109,7 @@ vc4_use_bo(struct vc4_exec_info *exec, uint32_t hindex) struct drm_gem_dma_object *obj; struct vc4_bo *bo; - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return NULL; if (hindex >= exec->bo_count) { @@ -169,7 +169,7 @@ vc4_check_tex_size(struct vc4_exec_info *exec, struct drm_gem_dma_object *fbo, uint32_t utile_w = utile_width(cpp); uint32_t utile_h = utile_height(cpp); - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return false; /* The shaded vertex format stores signed 12.4 fixed point @@ -495,7 +495,7 @@ vc4_validate_bin_cl(struct drm_device *dev, uint32_t dst_offset = 0; uint32_t src_offset = 0; - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return -ENODEV; while (src_offset < len) { @@ -942,7 +942,7 @@ vc4_validate_shader_recs(struct drm_device *dev, uint32_t i; int ret = 0; - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return -ENODEV; for (i = 0; i < exec->shader_state_count; i++) { diff --git a/drivers/gpu/drm/vc4/vc4_validate_shaders.c b/drivers/gpu/drm/vc4/vc4_validate_shaders.c index afb1a4d8268465..2d74e786914cb3 100644 --- a/drivers/gpu/drm/vc4/vc4_validate_shaders.c +++ b/drivers/gpu/drm/vc4/vc4_validate_shaders.c @@ -786,7 +786,7 @@ vc4_validate_shader(struct drm_gem_dma_object *shader_obj) struct vc4_validated_shader_info *validated_shader = NULL; struct vc4_shader_validation_state validation_state; - if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5)) + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return NULL; memset(&validation_state, 0, sizeof(validation_state)); diff --git a/drivers/gpu/drm/vc4/vc4_vec.c b/drivers/gpu/drm/vc4/vc4_vec.c index eb64e881051e6d..d191747a5e109d 100644 --- a/drivers/gpu/drm/vc4/vc4_vec.c +++ b/drivers/gpu/drm/vc4/vc4_vec.c @@ -67,7 +67,7 @@ #define VEC_CONFIG0_YCDELAY BIT(4) #define VEC_CONFIG0_RAMPEN BIT(2) #define VEC_CONFIG0_YCDIS BIT(2) -#define VEC_CONFIG0_STD_MASK GENMASK(1, 0) +#define VEC_CONFIG0_STD_MASK (VEC_CONFIG0_SECAM_STD | GENMASK(1, 0)) #define VEC_CONFIG0_NTSC_STD 0 #define VEC_CONFIG0_PAL_BDGHI_STD 1 #define VEC_CONFIG0_PAL_M_STD 2 @@ -186,6 +186,8 @@ #define VEC_DAC_MISC_DAC_RST_N BIT(0) +static char *vc4_vec_tv_norm; + struct vc4_vec_variant { u32 dac_config; }; @@ -272,6 +274,18 @@ static const struct debugfs_reg32 vec_regs[] = { VC4_REG32(VEC_DAC_MISC), }; +static const struct drm_display_mode drm_mode_240p = { + DRM_MODE("720x240", DRM_MODE_TYPE_DRIVER, 13500, + 720, 720 + 14, 720 + 14 + 64, 720 + 14 + 64 + 60, 0, + 240, 240 + 3, 240 + 3 + 3, 262, 0, 0) +}; + +static const struct drm_display_mode drm_mode_288p = { + DRM_MODE("720x288", DRM_MODE_TYPE_DRIVER, 13500, + 720, 720 + 20, 720 + 20 + 64, 720 + 20 + 64 + 60, 0, + 288, 288 + 2, 288 + 2 + 3, 312, 0, 0) +}; + static const struct vc4_vec_tv_mode vc4_vec_tv_modes[] = { { .mode = DRM_MODE_TV_MODE_NTSC, @@ -371,6 +385,35 @@ static const struct drm_prop_enum_list legacy_tv_mode_names[] = { { VC4_VEC_TV_MODE_MONOCHROME, "Mono", }, }; +static enum drm_connector_tv_mode +vc4_vec_get_default_mode(struct drm_connector *connector) +{ + if (connector->cmdline_mode.tv_mode_specified) { + return connector->cmdline_mode.tv_mode; + } else if (vc4_vec_tv_norm) { + int ret; + + ret = drm_get_tv_mode_from_name(vc4_vec_tv_norm, strlen(vc4_vec_tv_norm)); + if (ret >= 0) + return ret; + } else if (connector->cmdline_mode.specified && + ((connector->cmdline_mode.refresh_specified && + (connector->cmdline_mode.refresh == 25 || + connector->cmdline_mode.refresh == 50)) || + (!connector->cmdline_mode.refresh_specified && + (connector->cmdline_mode.yres == 288 || + connector->cmdline_mode.yres == 576)))) { + /* + * no explicitly specified TV norm; use PAL if a mode that + * looks like PAL has been specified on the command line + */ + return DRM_MODE_TV_MODE_PAL; + } + + /* in all other cases, default to NTSC */ + return DRM_MODE_TV_MODE_NTSC; +} + static enum drm_connector_status vc4_vec_connector_detect(struct drm_connector *connector, bool force) { @@ -436,52 +479,55 @@ vc4_vec_connector_set_property(struct drm_connector *connector, } static int -vc4_vec_connector_get_property(struct drm_connector *connector, - const struct drm_connector_state *state, - struct drm_property *property, - uint64_t *val) +vc4_vec_generic_tv_mode_to_legacy(enum drm_connector_tv_mode tv_mode) { - struct vc4_vec *vec = connector_to_vc4_vec(connector); - - if (property != vec->legacy_tv_mode_property) - return -EINVAL; - - switch (state->tv.mode) { + switch (tv_mode) { case DRM_MODE_TV_MODE_NTSC: - *val = VC4_VEC_TV_MODE_NTSC; - break; + return VC4_VEC_TV_MODE_NTSC; case DRM_MODE_TV_MODE_NTSC_443: - *val = VC4_VEC_TV_MODE_NTSC_443; - break; + return VC4_VEC_TV_MODE_NTSC_443; case DRM_MODE_TV_MODE_NTSC_J: - *val = VC4_VEC_TV_MODE_NTSC_J; - break; + return VC4_VEC_TV_MODE_NTSC_J; case DRM_MODE_TV_MODE_PAL: - *val = VC4_VEC_TV_MODE_PAL; - break; + return VC4_VEC_TV_MODE_PAL; case DRM_MODE_TV_MODE_PAL_M: - *val = VC4_VEC_TV_MODE_PAL_M; - break; + return VC4_VEC_TV_MODE_PAL_M; case DRM_MODE_TV_MODE_PAL_N: - *val = VC4_VEC_TV_MODE_PAL_N; - break; + return VC4_VEC_TV_MODE_PAL_N; case DRM_MODE_TV_MODE_SECAM: - *val = VC4_VEC_TV_MODE_SECAM; - break; + return VC4_VEC_TV_MODE_SECAM; case DRM_MODE_TV_MODE_MONOCHROME: - *val = VC4_VEC_TV_MODE_MONOCHROME; - break; + return VC4_VEC_TV_MODE_MONOCHROME; default: return -EINVAL; } +} + +static int +vc4_vec_connector_get_property(struct drm_connector *connector, + const struct drm_connector_state *state, + struct drm_property *property, + uint64_t *val) +{ + struct vc4_vec *vec = connector_to_vc4_vec(connector); + enum vc4_vec_tv_mode_id legacy_mode; + + if (property != vec->legacy_tv_mode_property) + return -EINVAL; + + legacy_mode = vc4_vec_generic_tv_mode_to_legacy(state->tv.mode); + if (legacy_mode < 0) + return legacy_mode; + + *val = legacy_mode; return 0; } @@ -496,14 +542,38 @@ static const struct drm_connector_funcs vc4_vec_connector_funcs = { .atomic_set_property = vc4_vec_connector_set_property, }; +static int vc4_vec_connector_get_modes(struct drm_connector *connector) +{ + struct drm_display_mode *mode; + int count = drm_connector_helper_tv_get_modes(connector); + + mode = drm_mode_duplicate(connector->dev, &drm_mode_240p); + if (!mode) + return -ENOMEM; + + drm_mode_probed_add(connector, mode); + count++; + + mode = drm_mode_duplicate(connector->dev, &drm_mode_288p); + if (!mode) + return -ENOMEM; + + drm_mode_probed_add(connector, mode); + count++; + + return count; +} + static const struct drm_connector_helper_funcs vc4_vec_connector_helper_funcs = { .atomic_check = drm_atomic_helper_connector_tv_check, - .get_modes = drm_connector_helper_tv_get_modes, + .get_modes = vc4_vec_connector_get_modes, }; static int vc4_vec_connector_init(struct drm_device *dev, struct vc4_vec *vec) { struct drm_connector *connector = &vec->connector; + enum vc4_vec_tv_mode_id legacy_default_mode; + enum drm_connector_tv_mode default_mode; struct drm_property *prop; int ret; @@ -516,9 +586,17 @@ static int vc4_vec_connector_init(struct drm_device *dev, struct vc4_vec *vec) drm_connector_helper_add(connector, &vc4_vec_connector_helper_funcs); + default_mode = vc4_vec_get_default_mode(connector); + if (default_mode < 0) + return default_mode; + drm_object_attach_property(&connector->base, dev->mode_config.tv_mode_property, - DRM_MODE_TV_MODE_NTSC); + default_mode); + + legacy_default_mode = vc4_vec_generic_tv_mode_to_legacy(default_mode); + if (legacy_default_mode < 0) + return legacy_default_mode; prop = drm_property_create_enum(dev, 0, "mode", legacy_tv_mode_names, @@ -527,7 +605,7 @@ static int vc4_vec_connector_init(struct drm_device *dev, struct vc4_vec *vec) return -ENOMEM; vec->legacy_tv_mode_property = prop; - drm_object_attach_property(&connector->base, prop, VC4_VEC_TV_MODE_NTSC); + drm_object_attach_property(&connector->base, prop, legacy_default_mode); drm_connector_attach_tv_margin_properties(connector); @@ -854,3 +932,10 @@ struct platform_driver vc4_vec_driver = { .of_match_table = vc4_vec_dt_match, }, }; + +module_param_named(tv_norm, vc4_vec_tv_norm, charp, 0600); +MODULE_PARM_DESC(tv_norm, "Default TV norm.\n" + "\t\tSupported: NTSC, NTSC-J, NTSC-443, PAL, PAL-M, PAL-N,\n" + "\t\t\tPAL60, SECAM.\n" + "\t\tDefault: PAL if a 50 Hz mode has been set via video=,\n" + "\t\t\tNTSC otherwise"); diff --git a/drivers/gpu/drm/vc4/vc_image_types.h b/drivers/gpu/drm/vc4/vc_image_types.h new file mode 100644 index 00000000000000..e8d2b4b162f7cd --- /dev/null +++ b/drivers/gpu/drm/vc4/vc_image_types.h @@ -0,0 +1,175 @@ + +/* + * Copyright (c) 2012, Broadcom Europe Ltd + * + * Values taken from vc_image_types.h released by Broadcom at + * https://github.com/raspberrypi/userland/blob/master/interface/vctypes/vc_image_types.h + * and vc_image_structs.h at + * https://github.com/raspberrypi/userland/blob/master/interface/vctypes/vc_image_structs.h + * + * 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. + */ + +enum { + VC_IMAGE_MIN = 0, //bounds for error checking + + VC_IMAGE_RGB565 = 1, + VC_IMAGE_1BPP, + VC_IMAGE_YUV420, + VC_IMAGE_48BPP, + VC_IMAGE_RGB888, + VC_IMAGE_8BPP, + /* 4bpp palettised image */ + VC_IMAGE_4BPP, + /* A separated format of 16 colour/light shorts followed by 16 z + * values + */ + VC_IMAGE_3D32, + /* 16 colours followed by 16 z values */ + VC_IMAGE_3D32B, + /* A separated format of 16 material/colour/light shorts followed by + * 16 z values + */ + VC_IMAGE_3D32MAT, + /* 32 bit format containing 18 bits of 6.6.6 RGB, 9 bits per short */ + VC_IMAGE_RGB2X9, + /* 32-bit format holding 18 bits of 6.6.6 RGB */ + VC_IMAGE_RGB666, + /* 4bpp palettised image with embedded palette */ + VC_IMAGE_PAL4_OBSOLETE, + /* 8bpp palettised image with embedded palette */ + VC_IMAGE_PAL8_OBSOLETE, + /* RGB888 with an alpha byte after each pixel */ + VC_IMAGE_RGBA32, + /* a line of Y (32-byte padded), a line of U (16-byte padded), and a + * line of V (16-byte padded) + */ + VC_IMAGE_YUV422, + /* RGB565 with a transparent patch */ + VC_IMAGE_RGBA565, + /* Compressed (4444) version of RGBA32 */ + VC_IMAGE_RGBA16, + /* VCIII codec format */ + VC_IMAGE_YUV_UV, + /* VCIII T-format RGBA8888 */ + VC_IMAGE_TF_RGBA32, + /* VCIII T-format RGBx8888 */ + VC_IMAGE_TF_RGBX32, + /* VCIII T-format float */ + VC_IMAGE_TF_FLOAT, + /* VCIII T-format RGBA4444 */ + VC_IMAGE_TF_RGBA16, + /* VCIII T-format RGB5551 */ + VC_IMAGE_TF_RGBA5551, + /* VCIII T-format RGB565 */ + VC_IMAGE_TF_RGB565, + /* VCIII T-format 8-bit luma and 8-bit alpha */ + VC_IMAGE_TF_YA88, + /* VCIII T-format 8 bit generic sample */ + VC_IMAGE_TF_BYTE, + /* VCIII T-format 8-bit palette */ + VC_IMAGE_TF_PAL8, + /* VCIII T-format 4-bit palette */ + VC_IMAGE_TF_PAL4, + /* VCIII T-format Ericsson Texture Compressed */ + VC_IMAGE_TF_ETC1, + /* RGB888 with R & B swapped */ + VC_IMAGE_BGR888, + /* RGB888 with R & B swapped, but with no pitch, i.e. no padding after + * each row of pixels + */ + VC_IMAGE_BGR888_NP, + /* Bayer image, extra defines which variant is being used */ + VC_IMAGE_BAYER, + /* General wrapper for codec images e.g. JPEG from camera */ + VC_IMAGE_CODEC, + /* VCIII codec format */ + VC_IMAGE_YUV_UV32, + /* VCIII T-format 8-bit luma */ + VC_IMAGE_TF_Y8, + /* VCIII T-format 8-bit alpha */ + VC_IMAGE_TF_A8, + /* VCIII T-format 16-bit generic sample */ + VC_IMAGE_TF_SHORT, + /* VCIII T-format 1bpp black/white */ + VC_IMAGE_TF_1BPP, + VC_IMAGE_OPENGL, + /* VCIII-B0 HVS YUV 4:4:4 interleaved samples */ + VC_IMAGE_YUV444I, + /* Y, U, & V planes separately (VC_IMAGE_YUV422 has them interleaved on + * a per line basis) + */ + VC_IMAGE_YUV422PLANAR, + /* 32bpp with 8bit alpha at MS byte, with R, G, B (LS byte) */ + VC_IMAGE_ARGB8888, + /* 32bpp with 8bit unused at MS byte, with R, G, B (LS byte) */ + VC_IMAGE_XRGB8888, + + /* interleaved 8 bit samples of Y, U, Y, V (4 flavours) */ + VC_IMAGE_YUV422YUYV, + VC_IMAGE_YUV422YVYU, + VC_IMAGE_YUV422UYVY, + VC_IMAGE_YUV422VYUY, + + /* 32bpp like RGBA32 but with unused alpha */ + VC_IMAGE_RGBX32, + /* 32bpp, corresponding to RGBA with unused alpha */ + VC_IMAGE_RGBX8888, + /* 32bpp, corresponding to BGRA with unused alpha */ + VC_IMAGE_BGRX8888, + + /* Y as a plane, then UV byte interleaved in plane with same pitch, + * half height + */ + VC_IMAGE_YUV420SP, + + /* Y, U, & V planes separately 4:4:4 */ + VC_IMAGE_YUV444PLANAR, + + /* T-format 8-bit U - same as TF_Y8 buf from U plane */ + VC_IMAGE_TF_U8, + /* T-format 8-bit U - same as TF_Y8 buf from V plane */ + VC_IMAGE_TF_V8, + + /* YUV4:2:0 planar, 16bit values */ + VC_IMAGE_YUV420_16, + /* YUV4:2:0 codec format, 16bit values */ + VC_IMAGE_YUV_UV_16, + /* YUV4:2:0 with U,V in side-by-side format */ + VC_IMAGE_YUV420_S, + /* 10-bit YUV 420 column image format */ + VC_IMAGE_YUV10COL, + /* 32-bpp, 10-bit R/G/B, 2-bit Alpha */ + VC_IMAGE_RGBA1010102, + + VC_IMAGE_MAX, /* bounds for error checking */ + VC_IMAGE_FORCE_ENUM_16BIT = 0xffff, +}; + +enum { + /* Unknown or unset - defaults to BT601 interstitial */ + VC_IMAGE_YUVINFO_UNSPECIFIED = 0, + + /* colour-space conversions data [4 bits] */ + + /* ITU-R BT.601-5 [SDTV] (compatible with VideoCore-II) */ + VC_IMAGE_YUVINFO_CSC_ITUR_BT601 = 1, + /* ITU-R BT.709-3 [HDTV] */ + VC_IMAGE_YUVINFO_CSC_ITUR_BT709 = 2, + /* JPEG JFIF */ + VC_IMAGE_YUVINFO_CSC_JPEG_JFIF = 3, + /* Title 47 Code of Federal Regulations (2003) 73.682 (a) (20) */ + VC_IMAGE_YUVINFO_CSC_FCC = 4, + /* Society of Motion Picture and Television Engineers 240M (1999) */ + VC_IMAGE_YUVINFO_CSC_SMPTE_240M = 5, + /* ITU-R BT.470-2 System M */ + VC_IMAGE_YUVINFO_CSC_ITUR_BT470_2_M = 6, + /* ITU-R BT.470-2 System B,G */ + VC_IMAGE_YUVINFO_CSC_ITUR_BT470_2_BG = 7, + /* JPEG JFIF, but with 16..255 luma */ + VC_IMAGE_YUVINFO_CSC_JPEG_JFIF_Y16_255 = 8, + /* Rec 2020 */ + VC_IMAGE_YUVINFO_CSC_REC_2020 = 9, +}; diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index ceb3b1a72e235c..5d3ce6b533c259 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -243,6 +243,9 @@ #define USB_VENDOR_ID_BAANTO 0x2453 #define USB_DEVICE_ID_BAANTO_MT_190W2 0x0100 +#define USB_VENDOR_ID_BEKEN 0x25a7 +#define USB_DEVICE_ID_AIRMOUSE_T3 0x2402 + #define USB_VENDOR_ID_BELKIN 0x050d #define USB_DEVICE_ID_FLIP_KVM 0x3201 @@ -1416,6 +1419,9 @@ #define USB_VENDOR_ID_XIAOMI 0x2717 #define USB_DEVICE_ID_MI_SILENT_MOUSE 0x5014 +#define USB_VENDOR_ID_XENTA 0x1d57 +#define USB_DEVICE_ID_AIRMOUSE_MX3 0xad03 + #define USB_VENDOR_ID_XIN_MO 0x16c0 #define USB_DEVICE_ID_XIN_MO_DUAL_ARCADE 0x05e1 #define USB_DEVICE_ID_THT_2P_ARCADE 0x75e1 diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index e0bbf0c6345d68..1d1949d62dfaff 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -42,6 +42,7 @@ static const struct hid_device_id hid_quirks[] = { { HID_USB_DEVICE(USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_CS682), HID_QUIRK_NOGET }, { HID_USB_DEVICE(USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_CS692), HID_QUIRK_NOGET }, { HID_USB_DEVICE(USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_UC100KM), HID_QUIRK_NOGET }, + { HID_USB_DEVICE(USB_VENDOR_ID_BEKEN, USB_DEVICE_ID_AIRMOUSE_T3), HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE }, { HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_MULTI_TOUCH), HID_QUIRK_MULTI_INPUT }, { HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_PIXART_USB_OPTICAL_MOUSE), HID_QUIRK_ALWAYS_POLL }, { HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_PIXART_USB_OPTICAL_MOUSE2), HID_QUIRK_ALWAYS_POLL }, @@ -209,6 +210,7 @@ static const struct hid_device_id hid_quirks[] = { { HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_QUAD_USB_JOYPAD), HID_QUIRK_NOGET | HID_QUIRK_MULTI_INPUT }, { HID_USB_DEVICE(USB_VENDOR_ID_XIN_MO, USB_DEVICE_ID_XIN_MO_DUAL_ARCADE), HID_QUIRK_MULTI_INPUT }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_GROUP_AUDIO), HID_QUIRK_NOGET }, + { HID_USB_DEVICE(USB_VENDOR_ID_XENTA, USB_DEVICE_ID_AIRMOUSE_MX3), HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE }, { 0 } }; diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c index a9e85bdd4cc656..f6dac04073ab96 100644 --- a/drivers/hid/usbhid/hid-core.c +++ b/drivers/hid/usbhid/hid-core.c @@ -46,7 +46,7 @@ * Module parameters. */ -static unsigned int hid_mousepoll_interval; +static unsigned int hid_mousepoll_interval = ~0; module_param_named(mousepoll, hid_mousepoll_interval, uint, 0644); MODULE_PARM_DESC(mousepoll, "Polling interval of mice"); @@ -1113,7 +1113,9 @@ static int usbhid_start(struct hid_device *hid) */ switch (hid->collection->usage) { case HID_GD_MOUSE: - if (hid_mousepoll_interval > 0) + if (hid_mousepoll_interval == ~0 && interval < 16) + interval = 16; + else if (hid_mousepoll_interval != ~0 && hid_mousepoll_interval != 0) interval = hid_mousepoll_interval; break; case HID_GD_JOYSTICK: @@ -1125,6 +1127,7 @@ static int usbhid_start(struct hid_device *hid) interval = hid_kbpoll_interval; break; } + usb_fixup_endpoint(dev, endpoint->bEndpointAddress, interval); ret = -ENOMEM; if (usb_endpoint_dir_in(endpoint)) { diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 58480a3f4683fe..03b39e5c243a6f 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2551,6 +2551,13 @@ config SENSORS_INTEL_M10_BMC_HWMON sensors monitor various telemetry data of different components on the card, e.g. board temperature, FPGA core temperature/voltage/current. +config SENSORS_RP1_ADC + tristate "RP1 ADC and temperature sensor driver" + depends on MFD_RP1 + help + Say yes here to enable support for the voltage and temperature + sensors of the Raspberry Pi RP1 peripheral chip. + if ACPI comment "ACPI drivers" diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 9554d2fdcf7bb5..d640c694f661b8 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -188,6 +188,7 @@ obj-$(CONFIG_SENSORS_POWR1220) += powr1220.o obj-$(CONFIG_SENSORS_PT5161L) += pt5161l.o obj-$(CONFIG_SENSORS_PWM_FAN) += pwm-fan.o obj-$(CONFIG_SENSORS_RASPBERRYPI_HWMON) += raspberrypi-hwmon.o +obj-$(CONFIG_SENSORS_RP1_ADC) += rp1-adc.o obj-$(CONFIG_SENSORS_SBTSI) += sbtsi_temp.o obj-$(CONFIG_SENSORS_SBRMI) += sbrmi.o obj-$(CONFIG_SENSORS_SCH56XX_COMMON)+= sch56xx-common.o diff --git a/drivers/hwmon/adt7410.c b/drivers/hwmon/adt7410.c index 3bf0e0a0882c11..1b52d90815acdc 100644 --- a/drivers/hwmon/adt7410.c +++ b/drivers/hwmon/adt7410.c @@ -94,9 +94,17 @@ static const struct i2c_device_id adt7410_ids[] = { }; MODULE_DEVICE_TABLE(i2c, adt7410_ids); +static const struct of_device_id adt7410_of_ids[] = { + { .compatible = "adi,adt7410" }, + { .compatible = "adi,adt7420" }, + {} +}; +MODULE_DEVICE_TABLE(of, adt7410_of_ids); + static struct i2c_driver adt7410_driver = { .driver = { .name = "adt7410", + .of_match_table = adt7410_of_ids, .pm = pm_sleep_ptr(&adt7x10_dev_pm_ops), }, .probe = adt7410_i2c_probe, diff --git a/drivers/hwmon/aht10.c b/drivers/hwmon/aht10.c index 312ef3e9875405..133f4110f20470 100644 --- a/drivers/hwmon/aht10.c +++ b/drivers/hwmon/aht10.c @@ -57,6 +57,12 @@ static const struct i2c_device_id aht10_id[] = { }; MODULE_DEVICE_TABLE(i2c, aht10_id); +static const struct of_device_id aht10_of_id[] = { + { .compatible = "aosong,aht10", }, + { } +}; +MODULE_DEVICE_TABLE(of, aht10_of_id); + /** * struct aht10_data - All the data required to operate an AHT10/AHT20 chip * @client: the i2c client associated with the AHT10/AHT20 @@ -380,6 +386,7 @@ static int aht10_probe(struct i2c_client *client) static struct i2c_driver aht10_driver = { .driver = { .name = "aht10", + .of_match_table = aht10_of_id, }, .probe = aht10_probe, .id_table = aht10_id, diff --git a/drivers/hwmon/ds1621.c b/drivers/hwmon/ds1621.c index 42ec34cb8a5f87..30d2a771054fc4 100644 --- a/drivers/hwmon/ds1621.c +++ b/drivers/hwmon/ds1621.c @@ -376,6 +376,16 @@ static const struct i2c_device_id ds1621_id[] = { }; MODULE_DEVICE_TABLE(i2c, ds1621_id); +static const struct of_device_id ds1621_of_ids[] = { + { .compatible = "dallas,ds1621", }, + { .compatible = "dallas,ds1625", }, + { .compatible = "dallas,ds1631", }, + { .compatible = "dallas,ds1721", }, + { .compatible = "dallas,ds1731", }, + { } +}; +MODULE_DEVICE_TABLE(of, ds1621_of_ids); + /* This is the driver that will be inserted */ static struct i2c_driver ds1621_driver = { .driver = { diff --git a/drivers/hwmon/emc2305.c b/drivers/hwmon/emc2305.c index 4d39fbd8376939..1d79fec099cfb5 100644 --- a/drivers/hwmon/emc2305.c +++ b/drivers/hwmon/emc2305.c @@ -12,12 +12,13 @@ #include <linux/platform_data/emc2305.h> #include <linux/thermal.h> +#define EMC2305_REG_FAN_STATUS 0x24 +#define EMC2305_REG_FAN_STALL_STATUS 0x25 #define EMC2305_REG_DRIVE_FAIL_STATUS 0x27 #define EMC2305_REG_VENDOR 0xfe #define EMC2305_FAN_MAX 0xff #define EMC2305_FAN_MIN 0x00 #define EMC2305_FAN_MAX_STATE 10 -#define EMC2305_DEVICE 0x34 #define EMC2305_VENDOR 0x5d #define EMC2305_REG_PRODUCT_ID 0xfd #define EMC2305_TACH_REGS_UNUSE_BITS 3 @@ -36,6 +37,7 @@ #define EMC2305_RPM_FACTOR 3932160 #define EMC2305_REG_FAN_DRIVE(n) (0x30 + 0x10 * (n)) +#define EMC2305_REG_FAN_CFG(n) (0x32 + 0x10 * (n)) #define EMC2305_REG_FAN_MIN_DRIVE(n) (0x38 + 0x10 * (n)) #define EMC2305_REG_FAN_TACH(n) (0x3e + 0x10 * (n)) @@ -55,6 +57,15 @@ static const struct i2c_device_id emc2305_ids[] = { }; MODULE_DEVICE_TABLE(i2c, emc2305_ids); +static const struct of_device_id emc2305_dt_ids[] = { + { .compatible = "microchip,emc2305" }, + { .compatible = "microchip,emc2303" }, + { .compatible = "microchip,emc2302" }, + { .compatible = "microchip,emc2301" }, + { } +}; +MODULE_DEVICE_TABLE(of, emc2305_dt_ids); + /** * struct emc2305_cdev_data - device-specific cooling device state * @cdev: cooling device @@ -100,6 +111,7 @@ struct emc2305_data { u8 pwm_num; bool pwm_separate; u8 pwm_min[EMC2305_PWM_MAX]; + u8 pwm_max; struct emc2305_cdev_data cdev_data[EMC2305_PWM_MAX]; }; @@ -272,7 +284,7 @@ static int emc2305_set_pwm(struct device *dev, long val, int channel) struct i2c_client *client = data->client; int ret; - if (val < data->pwm_min[channel] || val > EMC2305_FAN_MAX) + if (val < data->pwm_min[channel] || val > data->pwm_max) return -EINVAL; ret = i2c_smbus_write_byte_data(client, EMC2305_REG_FAN_DRIVE(channel), val); @@ -283,6 +295,49 @@ static int emc2305_set_pwm(struct device *dev, long val, int channel) return 0; } +static int emc2305_get_tz_of(struct device *dev) +{ + struct device_node *np = dev->of_node; + struct emc2305_data *data = dev_get_drvdata(dev); + int ret = 0; + u8 val; + int i; + + /* OF parameters are optional - overwrite default setting + * if some of them are provided. + */ + + ret = of_property_read_u8(np, "emc2305,cooling-levels", &val); + if (!ret) + data->max_state = val; + else if (ret != -EINVAL) + return ret; + + ret = of_property_read_u8(np, "emc2305,pwm-max", &val); + if (!ret) + data->pwm_max = val; + else if (ret != -EINVAL) + return ret; + + ret = of_property_read_u8(np, "emc2305,pwm-min", &val); + if (!ret) + for (i = 0; i < EMC2305_PWM_MAX; i++) + data->pwm_min[i] = val; + else if (ret != -EINVAL) + return ret; + + /* Not defined or 0 means one thermal zone over all cooling devices. + * Otherwise - separated thermal zones for each PWM channel. + */ + ret = of_property_read_u8(np, "emc2305,pwm-channel", &val); + if (!ret) + data->pwm_separate = (val != 0); + else if (ret != -EINVAL) + return ret; + + return 0; +} + static int emc2305_set_single_tz(struct device *dev, int idx) { struct emc2305_data *data = dev_get_drvdata(dev); @@ -292,9 +347,17 @@ static int emc2305_set_single_tz(struct device *dev, int idx) cdev_idx = (idx) ? idx - 1 : 0; pwm = data->pwm_min[cdev_idx]; - data->cdev_data[cdev_idx].cdev = - thermal_cooling_device_register(emc2305_fan_name[idx], data, - &emc2305_cooling_ops); + if (dev->of_node) + data->cdev_data[cdev_idx].cdev = + devm_thermal_of_cooling_device_register(dev, dev->of_node, + emc2305_fan_name[idx], + data, + &emc2305_cooling_ops); + else + data->cdev_data[cdev_idx].cdev = + thermal_cooling_device_register(emc2305_fan_name[idx], + data, + &emc2305_cooling_ops); if (IS_ERR(data->cdev_data[cdev_idx].cdev)) { dev_err(dev, "Failed to register cooling device %s\n", emc2305_fan_name[idx]); @@ -347,9 +410,11 @@ static void emc2305_unset_tz(struct device *dev) int i; /* Unregister cooling device. */ - for (i = 0; i < EMC2305_PWM_MAX; i++) - if (data->cdev_data[i].cdev) - thermal_cooling_device_unregister(data->cdev_data[i].cdev); + if (!dev->of_node) { + for (i = 0; i < EMC2305_PWM_MAX; i++) + if (data->cdev_data[i].cdev) + thermal_cooling_device_unregister(data->cdev_data[i].cdev); + } } static umode_t @@ -571,11 +636,18 @@ static int emc2305_probe(struct i2c_client *client) data->pwm_separate = pdata->pwm_separate; for (i = 0; i < EMC2305_PWM_MAX; i++) data->pwm_min[i] = pdata->pwm_min[i]; + data->pwm_max = EMC2305_FAN_MAX; } else { data->max_state = EMC2305_FAN_MAX_STATE; data->pwm_separate = false; for (i = 0; i < EMC2305_PWM_MAX; i++) data->pwm_min[i] = EMC2305_FAN_MIN; + data->pwm_max = EMC2305_FAN_MAX; + if (dev->of_node) { + ret = emc2305_get_tz_of(dev); + if (ret < 0) + return ret; + } } data->hwmon_dev = devm_hwmon_device_register_with_info(dev, "emc2305", data, @@ -596,6 +668,12 @@ static int emc2305_probe(struct i2c_client *client) return ret; } + /* Acknowledge any existing faults. Stops the device responding on the + * SMBus alert address. + */ + i2c_smbus_read_byte_data(client, EMC2305_REG_FAN_STALL_STATUS); + i2c_smbus_read_byte_data(client, EMC2305_REG_FAN_STATUS); + return 0; } @@ -610,6 +688,7 @@ static void emc2305_remove(struct i2c_client *client) static struct i2c_driver emc2305_driver = { .driver = { .name = "emc2305", + .of_match_table = emc2305_dt_ids, }, .probe = emc2305_probe, .remove = emc2305_remove, diff --git a/drivers/hwmon/pwm-fan.c b/drivers/hwmon/pwm-fan.c index c434db4656e7df..2f5e145a3692ae 100644 --- a/drivers/hwmon/pwm-fan.c +++ b/drivers/hwmon/pwm-fan.c @@ -13,6 +13,7 @@ #include <linux/module.h> #include <linux/mutex.h> #include <linux/platform_device.h> +#include <linux/of_address.h> #include <linux/property.h> #include <linux/pwm.h> #include <linux/regulator/consumer.h> @@ -52,6 +53,9 @@ struct pwm_fan_ctx { ktime_t sample_start; struct timer_list rpm_timer; + void __iomem *rpm_regbase; + unsigned int rpm_offset; + unsigned int pwm_value; unsigned int pwm_fan_state; unsigned int pwm_fan_max_state; @@ -62,6 +66,10 @@ struct pwm_fan_ctx { struct hwmon_channel_info fan_channel; }; +static const u32 rpm_reg_channel_config[] = { + HWMON_F_INPUT, 0 +}; + /* This handler assumes self resetting edge triggered interrupt. */ static irqreturn_t pulse_handler(int irq, void *dev_id) { @@ -337,7 +345,10 @@ static int pwm_fan_read(struct device *dev, enum hwmon_sensor_types type, } return -EOPNOTSUPP; case hwmon_fan: - *val = ctx->tachs[channel].rpm; + if (ctx->rpm_regbase) + *val = (long)readl(ctx->rpm_regbase + ctx->rpm_offset); + else + *val = ctx->tachs[channel].rpm; return 0; default: @@ -470,6 +481,7 @@ static void pwm_fan_cleanup(void *__ctx) /* Switch off everything */ ctx->enable_mode = pwm_disable_reg_disable; pwm_fan_power_off(ctx, true); + iounmap(ctx->rpm_regbase); } static int pwm_fan_probe(struct platform_device *pdev) @@ -542,10 +554,23 @@ static int pwm_fan_probe(struct platform_device *pdev) return ret; ctx->tach_count = platform_irq_count(pdev); + if (ctx->tach_count == 0) { + struct device_node *rpm_node; + + rpm_node = of_parse_phandle(dev->of_node, "rpm-regmap", 0); + if (rpm_node) + ctx->rpm_regbase = of_iomap(rpm_node, 0); + } + if (ctx->tach_count < 0) return dev_err_probe(dev, ctx->tach_count, "Could not get number of fan tachometer inputs\n"); - dev_dbg(dev, "%d fan tachometer inputs\n", ctx->tach_count); + if (IS_ERR(ctx->rpm_regbase)) + return dev_err_probe(dev, PTR_ERR(ctx->rpm_regbase), + "Could not get rpm reg\n"); + + dev_dbg(dev, "%d fan tachometer inputs, %d rpm regmap\n", ctx->tach_count, + !!ctx->rpm_regbase); if (ctx->tach_count) { channel_count++; /* We also have a FAN channel. */ @@ -576,12 +601,24 @@ static int pwm_fan_probe(struct platform_device *pdev) device_property_read_u32_array(dev, "pulses-per-revolution", ctx->pulses_per_revolution, ctx->tach_count); + } else if (ctx->rpm_regbase) { + channel_count++; /* We also have a FAN channel. */ + ctx->fan_channel.type = hwmon_fan; + ctx->fan_channel.config = rpm_reg_channel_config; + + if (device_property_read_u32(dev, "rpm-offset", &ctx->rpm_offset)) { + dev_err(&pdev->dev, "unable to read 'rpm-offset'"); + ret = -EINVAL; + goto error; + } } channels = devm_kcalloc(dev, channel_count + 1, sizeof(struct hwmon_channel_info *), GFP_KERNEL); - if (!channels) - return -ENOMEM; + if (!channels) { + ret = -ENOMEM; + goto error; + } channels[0] = HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT | HWMON_PWM_ENABLE); @@ -617,6 +654,8 @@ static int pwm_fan_probe(struct platform_device *pdev) ctx->sample_start = ktime_get(); mod_timer(&ctx->rpm_timer, jiffies + HZ); + channels[1] = &ctx->fan_channel; + } else if (ctx->rpm_regbase) { channels[1] = &ctx->fan_channel; } @@ -627,12 +666,13 @@ static int pwm_fan_probe(struct platform_device *pdev) ctx, &ctx->info, NULL); if (IS_ERR(hwmon)) { dev_err(dev, "Failed to register hwmon device\n"); - return PTR_ERR(hwmon); + ret = PTR_ERR(hwmon); + goto error; } ret = pwm_fan_get_cooling_data(dev, ctx); if (ret) - return ret; + goto error; ctx->pwm_fan_state = ctx->pwm_fan_max_state; if (IS_ENABLED(CONFIG_THERMAL)) { @@ -643,12 +683,17 @@ static int pwm_fan_probe(struct platform_device *pdev) dev_err(dev, "Failed to register pwm-fan as cooling device: %d\n", ret); - return ret; + goto error; } ctx->cdev = cdev; } return 0; + +error: + if (ctx->rpm_regbase) + iounmap(ctx->rpm_regbase); + return ret; } static void pwm_fan_shutdown(struct platform_device *pdev) diff --git a/drivers/hwmon/rp1-adc.c b/drivers/hwmon/rp1-adc.c new file mode 100644 index 00000000000000..3201a3cfa7a961 --- /dev/null +++ b/drivers/hwmon/rp1-adc.c @@ -0,0 +1,305 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for the RP1 ADC and temperature sensor + * Copyright (C) 2023 Raspberry Pi Ltd. + */ + +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> + +#define MODULE_NAME "rp1-adc" + +#define RP1_ADC_CS 0x00 +#define RP1_ADC_RESULT 0x04 +#define RP1_ADC_FCS 0x08 +#define RP1_ADC_FIFO 0x0c +#define RP1_ADC_DIV 0x10 + +#define RP1_ADC_INTR 0x14 +#define RP1_ADC_INTE 0x18 +#define RP1_ADC_INTF 0x1c +#define RP1_ADC_INTS 0x20 + +#define RP1_ADC_RWTYPE_SET 0x2000 +#define RP1_ADC_RWTYPE_CLR 0x3000 + +#define RP1_ADC_CS_RROBIN_MASK 0x1f +#define RP1_ADC_CS_RROBIN_SHIFT 16 +#define RP1_ADC_CS_AINSEL_MASK 0x7 +#define RP1_ADC_CS_AINSEL_SHIFT 12 +#define RP1_ADC_CS_ERR_STICKY 0x400 +#define RP1_ADC_CS_ERR 0x200 +#define RP1_ADC_CS_READY 0x100 +#define RP1_ADC_CS_START_MANY 0x8 +#define RP1_ADC_CS_START_ONCE 0x4 +#define RP1_ADC_CS_TS_EN 0x2 +#define RP1_ADC_CS_EN 0x1 + +#define RP1_ADC_FCS_THRESH_MASK 0xf +#define RP1_ADC_FCS_THRESH_SHIFT 24 +#define RP1_ADC_FCS_LEVEL_MASK 0xf +#define RP1_ADC_FCS_LEVEL_SHIFT 16 +#define RP1_ADC_FCS_OVER 0x800 +#define RP1_ADC_FCS_UNDER 0x400 +#define RP1_ADC_FCS_FULL 0x200 +#define RP1_ADC_FCS_EMPTY 0x100 +#define RP1_ADC_FCS_DREQ_EN 0x8 +#define RP1_ADC_FCS_ERR 0x4 +#define RP1_ADC_FCS_SHIFR 0x2 +#define RP1_ADC_FCS_EN 0x1 + +#define RP1_ADC_FIFO_ERR 0x8000 +#define RP1_ADC_FIFO_VAL_MASK 0xfff + +#define RP1_ADC_DIV_INT_MASK 0xffff +#define RP1_ADC_DIV_INT_SHIFT 8 +#define RP1_ADC_DIV_FRAC_MASK 0xff +#define RP1_ADC_DIV_FRAC_SHIFT 0 + +struct rp1_adc_data { + void __iomem *base; + spinlock_t lock; + struct device *hwmon_dev; + int vref_mv; +}; + +static int rp1_adc_ready_wait(struct rp1_adc_data *data) +{ + int retries = 10; + + while (retries && !(readl(data->base + RP1_ADC_CS) & RP1_ADC_CS_READY)) + retries--; + + return retries ? 0 : -EIO; +} + +static int rp1_adc_read(struct rp1_adc_data *data, + struct device_attribute *devattr, unsigned int *val) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int channel = attr->index; + int ret; + + spin_lock(&data->lock); + + writel(RP1_ADC_CS_AINSEL_MASK << RP1_ADC_CS_AINSEL_SHIFT, + data->base + RP1_ADC_RWTYPE_CLR + RP1_ADC_CS); + writel(channel << RP1_ADC_CS_AINSEL_SHIFT, + data->base + RP1_ADC_RWTYPE_SET + RP1_ADC_CS); + writel(RP1_ADC_CS_START_ONCE, + data->base + RP1_ADC_RWTYPE_SET + RP1_ADC_CS); + + ret = rp1_adc_ready_wait(data); + if (ret) + return ret; + + /* Asserted if the completed conversion had a convergence error */ + if (readl(data->base + RP1_ADC_CS) & RP1_ADC_CS_ERR) + return -EIO; + + *val = readl(data->base + RP1_ADC_RESULT); + + spin_unlock(&data->lock); + + return ret; +} + +static int rp1_adc_to_mv(struct rp1_adc_data *data, unsigned int val) +{ + return ((u64)data->vref_mv * val) / 0xfff; +} + +static ssize_t rp1_adc_show(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct rp1_adc_data *data = dev_get_drvdata(dev); + unsigned int val; + int ret; + + ret = rp1_adc_read(data, devattr, &val); + if (ret) + return ret; + + return sprintf(buf, "%d\n", rp1_adc_to_mv(data, val)); +} + +static ssize_t rp1_adc_temp_show(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct rp1_adc_data *data = dev_get_drvdata(dev); + unsigned int val; + int ret, mv, mc; + + writel(RP1_ADC_CS_TS_EN, + data->base + RP1_ADC_RWTYPE_SET + RP1_ADC_CS); + ret = rp1_adc_read(data, devattr, &val); + if (ret) + return ret; + + mv = rp1_adc_to_mv(data, val); + + /* T = 27 - (ADC_voltage - 0.706)/0.001721 */ + + mc = 27000 - DIV_ROUND_CLOSEST((mv - 706) * (s64)1000000, 1721); + + return sprintf(buf, "%d\n", mc); +} + +static ssize_t rp1_adc_raw_show(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct rp1_adc_data *data = dev_get_drvdata(dev); + unsigned int val; + int ret = rp1_adc_read(data, devattr, &val); + + if (ret) + return ret; + + return sprintf(buf, "%u\n", val); +} + +static ssize_t rp1_adc_temp_raw_show(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct rp1_adc_data *data = dev_get_drvdata(dev); + unsigned int val; + int ret = rp1_adc_read(data, devattr, &val); + + if (ret) + return ret; + + return sprintf(buf, "%u\n", val); +} + +static SENSOR_DEVICE_ATTR_RO(in1_input, rp1_adc, 0); +static SENSOR_DEVICE_ATTR_RO(in2_input, rp1_adc, 1); +static SENSOR_DEVICE_ATTR_RO(in3_input, rp1_adc, 2); +static SENSOR_DEVICE_ATTR_RO(in4_input, rp1_adc, 3); +static SENSOR_DEVICE_ATTR_RO(temp1_input, rp1_adc_temp, 4); +static SENSOR_DEVICE_ATTR_RO(in1_raw, rp1_adc_raw, 0); +static SENSOR_DEVICE_ATTR_RO(in2_raw, rp1_adc_raw, 1); +static SENSOR_DEVICE_ATTR_RO(in3_raw, rp1_adc_raw, 2); +static SENSOR_DEVICE_ATTR_RO(in4_raw, rp1_adc_raw, 3); +static SENSOR_DEVICE_ATTR_RO(temp1_raw, rp1_adc_temp_raw, 4); + +static struct attribute *rp1_adc_attrs[] = { + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_in1_raw.dev_attr.attr, + &sensor_dev_attr_in2_raw.dev_attr.attr, + &sensor_dev_attr_in3_raw.dev_attr.attr, + &sensor_dev_attr_in4_raw.dev_attr.attr, + &sensor_dev_attr_temp1_raw.dev_attr.attr, + NULL +}; + +static umode_t rp1_adc_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + return 0444; +} + +static const struct attribute_group rp1_adc_group = { + .attrs = rp1_adc_attrs, + .is_visible = rp1_adc_is_visible, +}; +__ATTRIBUTE_GROUPS(rp1_adc); + +static int __init rp1_adc_probe(struct platform_device *pdev) +{ + struct rp1_adc_data *data; + struct regulator *reg; + struct clk *clk; + int vref_uv, ret; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + spin_lock_init(&data->lock); + + data->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(data->base)) + return PTR_ERR(data->base); + + platform_set_drvdata(pdev, data); + + clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(clk)) + return -ENODEV; + + clk_set_rate(clk, 50000000); + clk_prepare_enable(clk); + + reg = devm_regulator_get(&pdev->dev, "vref"); + if (IS_ERR(reg)) + return PTR_ERR(reg); + + vref_uv = regulator_get_voltage(reg); + data->vref_mv = DIV_ROUND_CLOSEST(vref_uv, 1000); + + data->hwmon_dev = + devm_hwmon_device_register_with_groups(&pdev->dev, + "rp1_adc", + data, + rp1_adc_groups); + if (IS_ERR(data->hwmon_dev)) { + ret = PTR_ERR(data->hwmon_dev); + dev_err(&pdev->dev, "hwmon_device_register failed with %d.\n", ret); + goto err_register; + } + + /* Disable interrupts */ + writel(0, data->base + RP1_ADC_INTE); + + /* Enable the block, clearing any sticky error */ + writel(RP1_ADC_CS_EN | RP1_ADC_CS_ERR_STICKY, data->base + RP1_ADC_CS); + + return 0; + +err_register: + sysfs_remove_group(&pdev->dev.kobj, &rp1_adc_group); + + return ret; +} + +static void rp1_adc_remove(struct platform_device *pdev) +{ + struct rp1_adc_data *data = platform_get_drvdata(pdev); + + hwmon_device_unregister(data->hwmon_dev); +} + +static const struct of_device_id rp1_adc_dt_ids[] = { + { .compatible = "raspberrypi,rp1-adc", }, + { } +}; +MODULE_DEVICE_TABLE(of, rp1_adc_dt_ids); + +static struct platform_driver rp1_adc_driver = { + .remove = rp1_adc_remove, + .driver = { + .name = MODULE_NAME, + .of_match_table = rp1_adc_dt_ids, + }, +}; + +module_platform_driver_probe(rp1_adc_driver, rp1_adc_probe); + +MODULE_DESCRIPTION("RP1 ADC driver"); +MODULE_AUTHOR("Phil Elwell <phil@raspberrypi.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/sht3x.c b/drivers/hwmon/sht3x.c index 650b0bcc2359ee..c34157d3f15943 100644 --- a/drivers/hwmon/sht3x.c +++ b/drivers/hwmon/sht3x.c @@ -954,19 +954,19 @@ static int sht3x_probe(struct i2c_client *client) return PTR_ERR_OR_ZERO(hwmon_dev); } -/* device ID table */ -static const struct i2c_device_id sht3x_ids[] = { - {"sht3x", sht3x}, - {"sts3x", sts3x}, +static const struct of_device_id sht3x_of_ids[] = { + { .compatible = "sensirion,sht3x" }, + { .compatible = "sensirion,sts3x" }, {} }; - -MODULE_DEVICE_TABLE(i2c, sht3x_ids); +MODULE_DEVICE_TABLE(of, sht3x_of_ids); static struct i2c_driver sht3x_i2c_driver = { - .driver.name = "sht3x", + .driver = { + .name = "sht3x", + .of_match_table = sht3x_of_ids, + }, .probe = sht3x_probe, - .id_table = sht3x_ids, }; static int __init sht3x_init(void) diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 2254abda5c46c9..1ab7ff53c0ddf7 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -16,6 +16,25 @@ config I2C_CCGX_UCSI for Cypress CCGx Type-C controller. Individual bus drivers need to select this one on demand. +config I2C_BCM2708 + tristate "BCM2708 BSC" + depends on ARCH_BCM2835 + help + Enabling this option will add BSC (Broadcom Serial Controller) + support for the BCM2708. BSC is a Broadcom proprietary bus compatible + with I2C/TWI/SMBus. + +config I2C_BCM2708_BAUDRATE + prompt "BCM2708 I2C baudrate" + depends on I2C_BCM2708 + int + default 100000 + help + Set the I2C baudrate. This will alter the default value. A + different baudrate can be set by using a module parameter as well. If + no parameter is provided when loading, this is the value that will be + used. + config I2C_ALI1535 tristate "ALI 1535" depends on PCI && HAS_IOPORT diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index ecc07c50f2a0fe..a0ba5cd01edc08 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -3,6 +3,8 @@ # Makefile for the i2c bus drivers. # +obj-$(CONFIG_I2C_BCM2708) += i2c-bcm2708.o + # ACPI drivers obj-$(CONFIG_I2C_SCMI) += i2c-scmi.o diff --git a/drivers/i2c/busses/i2c-bcm2708.c b/drivers/i2c/busses/i2c-bcm2708.c new file mode 100644 index 00000000000000..09907b5c2540cf --- /dev/null +++ b/drivers/i2c/busses/i2c-bcm2708.c @@ -0,0 +1,510 @@ +/* + * Driver for Broadcom BCM2708 BSC Controllers + * + * Copyright (C) 2012 Chris Boot & Frank Buss + * + * This driver is inspired by: + * i2c-ocores.c, by Peter Korsgaard <jacmet@sunsite.dk> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/spinlock.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/sched.h> +#include <linux/wait.h> + +/* BSC register offsets */ +#define BSC_C 0x00 +#define BSC_S 0x04 +#define BSC_DLEN 0x08 +#define BSC_A 0x0c +#define BSC_FIFO 0x10 +#define BSC_DIV 0x14 +#define BSC_DEL 0x18 +#define BSC_CLKT 0x1c + +/* Bitfields in BSC_C */ +#define BSC_C_I2CEN 0x00008000 +#define BSC_C_INTR 0x00000400 +#define BSC_C_INTT 0x00000200 +#define BSC_C_INTD 0x00000100 +#define BSC_C_ST 0x00000080 +#define BSC_C_CLEAR_1 0x00000020 +#define BSC_C_CLEAR_2 0x00000010 +#define BSC_C_READ 0x00000001 + +/* Bitfields in BSC_S */ +#define BSC_S_CLKT 0x00000200 +#define BSC_S_ERR 0x00000100 +#define BSC_S_RXF 0x00000080 +#define BSC_S_TXE 0x00000040 +#define BSC_S_RXD 0x00000020 +#define BSC_S_TXD 0x00000010 +#define BSC_S_RXR 0x00000008 +#define BSC_S_TXW 0x00000004 +#define BSC_S_DONE 0x00000002 +#define BSC_S_TA 0x00000001 + +#define I2C_WAIT_LOOP_COUNT 200 + +#define DRV_NAME "bcm2708_i2c" + +static unsigned int baudrate; +module_param(baudrate, uint, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); +MODULE_PARM_DESC(baudrate, "The I2C baudrate"); + +static bool combined = false; +module_param(combined, bool, 0644); +MODULE_PARM_DESC(combined, "Use combined transactions"); + +struct bcm2708_i2c { + struct i2c_adapter adapter; + + spinlock_t lock; + void __iomem *base; + int irq; + struct clk *clk; + u32 cdiv; + u32 clk_tout; + + struct completion done; + + struct i2c_msg *msg; + int pos; + int nmsgs; + bool error; +}; + +static inline u32 bcm2708_rd(struct bcm2708_i2c *bi, unsigned reg) +{ + return readl(bi->base + reg); +} + +static inline void bcm2708_wr(struct bcm2708_i2c *bi, unsigned reg, u32 val) +{ + writel(val, bi->base + reg); +} + +static inline void bcm2708_bsc_reset(struct bcm2708_i2c *bi) +{ + bcm2708_wr(bi, BSC_C, 0); + bcm2708_wr(bi, BSC_S, BSC_S_CLKT | BSC_S_ERR | BSC_S_DONE); +} + +static inline void bcm2708_bsc_fifo_drain(struct bcm2708_i2c *bi) +{ + while ((bi->pos < bi->msg->len) && (bcm2708_rd(bi, BSC_S) & BSC_S_RXD)) + bi->msg->buf[bi->pos++] = bcm2708_rd(bi, BSC_FIFO); +} + +static inline void bcm2708_bsc_fifo_fill(struct bcm2708_i2c *bi) +{ + while ((bi->pos < bi->msg->len) && (bcm2708_rd(bi, BSC_S) & BSC_S_TXD)) + bcm2708_wr(bi, BSC_FIFO, bi->msg->buf[bi->pos++]); +} + +static inline int bcm2708_bsc_setup(struct bcm2708_i2c *bi) +{ + u32 cdiv, s, clk_tout; + u32 c = BSC_C_I2CEN | BSC_C_INTD | BSC_C_ST | BSC_C_CLEAR_1; + int wait_loops = I2C_WAIT_LOOP_COUNT; + + /* Can't call clk_get_rate as it locks a mutex and here we are spinlocked. + * Use the value that we cached in the probe. + */ + cdiv = bi->cdiv; + clk_tout = bi->clk_tout; + + if (bi->msg->flags & I2C_M_RD) + c |= BSC_C_INTR | BSC_C_READ; + else + c |= BSC_C_INTT; + + bcm2708_wr(bi, BSC_CLKT, clk_tout); + bcm2708_wr(bi, BSC_DIV, cdiv); + bcm2708_wr(bi, BSC_A, bi->msg->addr); + bcm2708_wr(bi, BSC_DLEN, bi->msg->len); + if (combined) + { + /* Do the next two messages meet combined transaction criteria? + - Current message is a write, next message is a read + - Both messages to same slave address + - Write message can fit inside FIFO (16 bytes or less) */ + if ( (bi->nmsgs > 1) && + !(bi->msg[0].flags & I2C_M_RD) && (bi->msg[1].flags & I2C_M_RD) && + (bi->msg[0].addr == bi->msg[1].addr) && (bi->msg[0].len <= 16)) { + + /* Clear FIFO */ + bcm2708_wr(bi, BSC_C, BSC_C_CLEAR_1); + + /* Fill FIFO with entire write message (16 byte FIFO) */ + while (bi->pos < bi->msg->len) { + bcm2708_wr(bi, BSC_FIFO, bi->msg->buf[bi->pos++]); + } + /* Start write transfer (no interrupts, don't clear FIFO) */ + bcm2708_wr(bi, BSC_C, BSC_C_I2CEN | BSC_C_ST); + + /* poll for transfer start bit (should only take 1-20 polls) */ + do { + s = bcm2708_rd(bi, BSC_S); + } while (!(s & (BSC_S_TA | BSC_S_ERR | BSC_S_CLKT | BSC_S_DONE)) && --wait_loops >= 0); + + /* did we time out or some error occured? */ + if (wait_loops < 0 || (s & (BSC_S_ERR | BSC_S_CLKT))) { + return -1; + } + + /* Send next read message before the write transfer finishes. */ + bi->nmsgs--; + bi->msg++; + bi->pos = 0; + bcm2708_wr(bi, BSC_DLEN, bi->msg->len); + c = BSC_C_I2CEN | BSC_C_INTD | BSC_C_INTR | BSC_C_ST | BSC_C_READ; + } + } + bcm2708_wr(bi, BSC_C, c); + + return 0; +} + +static irqreturn_t bcm2708_i2c_interrupt(int irq, void *dev_id) +{ + struct bcm2708_i2c *bi = dev_id; + bool handled = true; + u32 s; + int ret; + + spin_lock(&bi->lock); + + /* we may see camera interrupts on the "other" I2C channel + Just return if we've not sent anything */ + if (!bi->nmsgs || !bi->msg) { + goto early_exit; + } + + s = bcm2708_rd(bi, BSC_S); + + if (s & (BSC_S_CLKT | BSC_S_ERR)) { + bcm2708_bsc_reset(bi); + bi->error = true; + + bi->msg = 0; /* to inform the that all work is done */ + bi->nmsgs = 0; + /* wake up our bh */ + complete(&bi->done); + } else if (s & BSC_S_DONE) { + bi->nmsgs--; + + if (bi->msg->flags & I2C_M_RD) { + bcm2708_bsc_fifo_drain(bi); + } + + bcm2708_bsc_reset(bi); + + if (bi->nmsgs) { + /* advance to next message */ + bi->msg++; + bi->pos = 0; + ret = bcm2708_bsc_setup(bi); + if (ret < 0) { + bcm2708_bsc_reset(bi); + bi->error = true; + bi->msg = 0; /* to inform the that all work is done */ + bi->nmsgs = 0; + /* wake up our bh */ + complete(&bi->done); + goto early_exit; + } + } else { + bi->msg = 0; /* to inform the that all work is done */ + bi->nmsgs = 0; + /* wake up our bh */ + complete(&bi->done); + } + } else if (s & BSC_S_TXW) { + bcm2708_bsc_fifo_fill(bi); + } else if (s & BSC_S_RXR) { + bcm2708_bsc_fifo_drain(bi); + } else { + handled = false; + } + +early_exit: + spin_unlock(&bi->lock); + + return handled ? IRQ_HANDLED : IRQ_NONE; +} + +static int bcm2708_i2c_master_xfer(struct i2c_adapter *adap, + struct i2c_msg *msgs, int num) +{ + struct bcm2708_i2c *bi = adap->algo_data; + unsigned long flags; + int ret; + + spin_lock_irqsave(&bi->lock, flags); + + reinit_completion(&bi->done); + bi->msg = msgs; + bi->pos = 0; + bi->nmsgs = num; + bi->error = false; + + ret = bcm2708_bsc_setup(bi); + + spin_unlock_irqrestore(&bi->lock, flags); + + /* check the result of the setup */ + if (ret < 0) + { + dev_err(&adap->dev, "transfer setup timed out\n"); + goto error_timeout; + } + + ret = wait_for_completion_timeout(&bi->done, adap->timeout); + if (ret == 0) { + dev_err(&adap->dev, "transfer timed out\n"); + goto error_timeout; + } + + ret = bi->error ? -EIO : num; + return ret; + +error_timeout: + spin_lock_irqsave(&bi->lock, flags); + bcm2708_bsc_reset(bi); + bi->msg = 0; /* to inform the interrupt handler that there's nothing else to be done */ + bi->nmsgs = 0; + spin_unlock_irqrestore(&bi->lock, flags); + return -ETIMEDOUT; +} + +static u32 bcm2708_i2c_functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | /*I2C_FUNC_10BIT_ADDR |*/ I2C_FUNC_SMBUS_EMUL; +} + +static struct i2c_algorithm bcm2708_i2c_algorithm = { + .master_xfer = bcm2708_i2c_master_xfer, + .functionality = bcm2708_i2c_functionality, +}; + +static int bcm2708_i2c_probe(struct platform_device *pdev) +{ + struct resource *regs; + int irq, err = -ENOMEM; + struct clk *clk; + struct bcm2708_i2c *bi; + struct i2c_adapter *adap; + unsigned long bus_hz; + u32 cdiv, clk_tout; + u32 baud; + + baud = CONFIG_I2C_BCM2708_BAUDRATE; + + if (pdev->dev.of_node) { + u32 bus_clk_rate; + pdev->id = of_alias_get_id(pdev->dev.of_node, "i2c"); + if (pdev->id < 0) { + dev_err(&pdev->dev, "alias is missing\n"); + return -EINVAL; + } + if (!of_property_read_u32(pdev->dev.of_node, + "clock-frequency", &bus_clk_rate)) + baud = bus_clk_rate; + else + dev_warn(&pdev->dev, + "Could not read clock-frequency property\n"); + } + + if (baudrate) + baud = baudrate; + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!regs) { + dev_err(&pdev->dev, "could not get IO memory\n"); + return -ENXIO; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "could not get IRQ\n"); + return irq; + } + + clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "could not find clk: %ld\n", PTR_ERR(clk)); + return PTR_ERR(clk); + } + + err = clk_prepare_enable(clk); + if (err) { + dev_err(&pdev->dev, "could not enable clk: %d\n", err); + goto out_clk_put; + } + + bi = kzalloc(sizeof(*bi), GFP_KERNEL); + if (!bi) + goto out_clk_disable; + + platform_set_drvdata(pdev, bi); + + adap = &bi->adapter; + adap->class = I2C_CLASS_HWMON; + adap->algo = &bcm2708_i2c_algorithm; + adap->algo_data = bi; + adap->dev.parent = &pdev->dev; + adap->nr = pdev->id; + strscpy(adap->name, dev_name(&pdev->dev), sizeof(adap->name)); + adap->dev.of_node = pdev->dev.of_node; + + switch (pdev->id) { + case 0: + adap->class = I2C_CLASS_HWMON; + break; + case 1: + adap->class = I2C_CLASS_HWMON; + break; + case 2: + adap->class = I2C_CLASS_HWMON; + break; + default: + dev_err(&pdev->dev, "can only bind to BSC 0, 1 or 2\n"); + err = -ENXIO; + goto out_free_bi; + } + + spin_lock_init(&bi->lock); + init_completion(&bi->done); + + bi->base = ioremap(regs->start, resource_size(regs)); + if (!bi->base) { + dev_err(&pdev->dev, "could not remap memory\n"); + goto out_free_bi; + } + + bi->irq = irq; + bi->clk = clk; + + err = request_irq(irq, bcm2708_i2c_interrupt, IRQF_SHARED, + dev_name(&pdev->dev), bi); + if (err) { + dev_err(&pdev->dev, "could not request IRQ: %d\n", err); + goto out_iounmap; + } + + bcm2708_bsc_reset(bi); + + err = i2c_add_numbered_adapter(adap); + if (err < 0) { + dev_err(&pdev->dev, "could not add I2C adapter: %d\n", err); + goto out_free_irq; + } + + bus_hz = clk_get_rate(bi->clk); + cdiv = bus_hz / baud; + if (cdiv > 0xffff) { + cdiv = 0xffff; + baud = bus_hz / cdiv; + } + + clk_tout = 35/1000*baud; //35ms timeout as per SMBus specs. + if (clk_tout > 0xffff) + clk_tout = 0xffff; + + bi->cdiv = cdiv; + bi->clk_tout = clk_tout; + + dev_info(&pdev->dev, "BSC%d Controller at 0x%08lx (irq %d) (baudrate %d)\n", + pdev->id, (unsigned long)regs->start, irq, baud); + + return 0; + +out_free_irq: + free_irq(bi->irq, bi); +out_iounmap: + iounmap(bi->base); +out_free_bi: + kfree(bi); +out_clk_disable: + clk_disable_unprepare(clk); +out_clk_put: + clk_put(clk); + return err; +} + +static void bcm2708_i2c_remove(struct platform_device *pdev) +{ + struct bcm2708_i2c *bi = platform_get_drvdata(pdev); + + platform_set_drvdata(pdev, NULL); + + i2c_del_adapter(&bi->adapter); + free_irq(bi->irq, bi); + iounmap(bi->base); + clk_disable_unprepare(bi->clk); + clk_put(bi->clk); + kfree(bi); +} + +static const struct of_device_id bcm2708_i2c_of_match[] = { + { .compatible = "brcm,bcm2708-i2c" }, + {}, +}; +MODULE_DEVICE_TABLE(of, bcm2708_i2c_of_match); + +static struct platform_driver bcm2708_i2c_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = bcm2708_i2c_of_match, + }, + .probe = bcm2708_i2c_probe, + .remove = bcm2708_i2c_remove, +}; + +// module_platform_driver(bcm2708_i2c_driver); + + +static int __init bcm2708_i2c_init(void) +{ + return platform_driver_register(&bcm2708_i2c_driver); +} + +static void __exit bcm2708_i2c_exit(void) +{ + platform_driver_unregister(&bcm2708_i2c_driver); +} + +module_init(bcm2708_i2c_init); +module_exit(bcm2708_i2c_exit); + + + +MODULE_DESCRIPTION("BSC controller driver for Broadcom BCM2708"); +MODULE_AUTHOR("Chris Boot <bootc@bootc.net>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/drivers/i2c/busses/i2c-bcm2835.c b/drivers/i2c/busses/i2c-bcm2835.c index ae42e37052a886..1c3289e6991b7c 100644 --- a/drivers/i2c/busses/i2c-bcm2835.c +++ b/drivers/i2c/busses/i2c-bcm2835.c @@ -56,6 +56,22 @@ #define BCM2835_I2C_CDIV_MIN 0x0002 #define BCM2835_I2C_CDIV_MAX 0xFFFE +static unsigned int debug; +module_param(debug, uint, 0644); +MODULE_PARM_DESC(debug, "1=err, 2=isr, 3=xfer"); + +static unsigned int clk_tout_ms = 35; /* SMBUs-recommended 35ms */ +module_param(clk_tout_ms, uint, 0644); +MODULE_PARM_DESC(clk_tout_ms, "clock-stretch timeout (mS)"); + +#define BCM2835_DEBUG_MAX 512 +struct bcm2835_debug { + struct i2c_msg *msg; + int msg_idx; + size_t remain; + u32 status; +}; + struct bcm2835_i2c_dev { struct device *dev; void __iomem *regs; @@ -68,8 +84,78 @@ struct bcm2835_i2c_dev { u32 msg_err; u8 *msg_buf; size_t msg_buf_remaining; + struct bcm2835_debug debug[BCM2835_DEBUG_MAX]; + unsigned int debug_num; + unsigned int debug_num_msgs; }; +static inline void bcm2835_debug_add(struct bcm2835_i2c_dev *i2c_dev, u32 s) +{ + if (!i2c_dev->debug_num_msgs || i2c_dev->debug_num >= BCM2835_DEBUG_MAX) + return; + + i2c_dev->debug[i2c_dev->debug_num].msg = i2c_dev->curr_msg; + i2c_dev->debug[i2c_dev->debug_num].msg_idx = + i2c_dev->debug_num_msgs - i2c_dev->num_msgs; + i2c_dev->debug[i2c_dev->debug_num].remain = i2c_dev->msg_buf_remaining; + i2c_dev->debug[i2c_dev->debug_num].status = s; + i2c_dev->debug_num++; +} + +static void bcm2835_debug_print_status(struct bcm2835_i2c_dev *i2c_dev, + struct bcm2835_debug *d) +{ + u32 s = d->status; + + pr_info("isr: remain=%zu, status=0x%x : %s%s%s%s%s%s%s%s%s%s [i2c%d]\n", + d->remain, s, + s & BCM2835_I2C_S_TA ? "TA " : "", + s & BCM2835_I2C_S_DONE ? "DONE " : "", + s & BCM2835_I2C_S_TXW ? "TXW " : "", + s & BCM2835_I2C_S_RXR ? "RXR " : "", + s & BCM2835_I2C_S_TXD ? "TXD " : "", + s & BCM2835_I2C_S_RXD ? "RXD " : "", + s & BCM2835_I2C_S_TXE ? "TXE " : "", + s & BCM2835_I2C_S_RXF ? "RXF " : "", + s & BCM2835_I2C_S_ERR ? "ERR " : "", + s & BCM2835_I2C_S_CLKT ? "CLKT " : "", + i2c_dev->adapter.nr); +} + +static void bcm2835_debug_print_msg(struct bcm2835_i2c_dev *i2c_dev, + struct i2c_msg *msg, int i, int total, + const char *fname) +{ + pr_info("%s: msg(%d/%d) %s addr=0x%02x, len=%u flags=%s%s%s%s%s%s%s [i2c%d]\n", + fname, i, total, + msg->flags & I2C_M_RD ? "read" : "write", msg->addr, msg->len, + msg->flags & I2C_M_TEN ? "TEN" : "", + msg->flags & I2C_M_RECV_LEN ? "RECV_LEN" : "", + msg->flags & I2C_M_NO_RD_ACK ? "NO_RD_ACK" : "", + msg->flags & I2C_M_IGNORE_NAK ? "IGNORE_NAK" : "", + msg->flags & I2C_M_REV_DIR_ADDR ? "REV_DIR_ADDR" : "", + msg->flags & I2C_M_NOSTART ? "NOSTART" : "", + msg->flags & I2C_M_STOP ? "STOP" : "", + i2c_dev->adapter.nr); +} + +static void bcm2835_debug_print(struct bcm2835_i2c_dev *i2c_dev) +{ + struct bcm2835_debug *d; + unsigned int i; + + for (i = 0; i < i2c_dev->debug_num; i++) { + d = &i2c_dev->debug[i]; + if (d->status == ~0) + bcm2835_debug_print_msg(i2c_dev, d->msg, d->msg_idx, + i2c_dev->debug_num_msgs, "start_transfer"); + else + bcm2835_debug_print_status(i2c_dev, d); + } + if (i2c_dev->debug_num >= BCM2835_DEBUG_MAX) + pr_info("BCM2835_DEBUG_MAX reached\n"); +} + static inline void bcm2835_i2c_writel(struct bcm2835_i2c_dev *i2c_dev, u32 reg, u32 val) { @@ -111,6 +197,7 @@ static int clk_bcm2835_i2c_set_rate(struct clk_hw *hw, unsigned long rate, { struct clk_bcm2835_i2c *div = to_clk_bcm2835_i2c(hw); u32 redl, fedl; + u32 clk_tout; u32 divider = clk_bcm2835_i2c_calc_divider(rate, parent_rate); if (divider == -EINVAL) @@ -134,6 +221,17 @@ static int clk_bcm2835_i2c_set_rate(struct clk_hw *hw, unsigned long rate, bcm2835_i2c_writel(div->i2c_dev, BCM2835_I2C_DEL, (fedl << BCM2835_I2C_FEDL_SHIFT) | (redl << BCM2835_I2C_REDL_SHIFT)); + + /* + * Set the clock stretch timeout. + */ + if (rate > 0xffff*1000/clk_tout_ms) + clk_tout = 0xffff; + else + clk_tout = clk_tout_ms*rate/1000; + + bcm2835_i2c_writel(div->i2c_dev, BCM2835_I2C_CLKT, clk_tout); + return 0; } @@ -257,6 +355,7 @@ static void bcm2835_i2c_start_transfer(struct bcm2835_i2c_dev *i2c_dev) bcm2835_i2c_writel(i2c_dev, BCM2835_I2C_A, msg->addr); bcm2835_i2c_writel(i2c_dev, BCM2835_I2C_DLEN, msg->len); bcm2835_i2c_writel(i2c_dev, BCM2835_I2C_C, c); + bcm2835_debug_add(i2c_dev, ~0); } static void bcm2835_i2c_finish_transfer(struct bcm2835_i2c_dev *i2c_dev) @@ -283,12 +382,11 @@ static irqreturn_t bcm2835_i2c_isr(int this_irq, void *data) u32 val, err; val = bcm2835_i2c_readl(i2c_dev, BCM2835_I2C_S); + bcm2835_debug_add(i2c_dev, val); err = val & (BCM2835_I2C_S_CLKT | BCM2835_I2C_S_ERR); - if (err) { + if (err && !(val & BCM2835_I2C_S_TA)) i2c_dev->msg_err = err; - goto complete; - } if (val & BCM2835_I2C_S_DONE) { if (!i2c_dev->curr_msg) { @@ -300,8 +398,6 @@ static irqreturn_t bcm2835_i2c_isr(int this_irq, void *data) if ((val & BCM2835_I2C_S_RXD) || i2c_dev->msg_buf_remaining) i2c_dev->msg_err = BCM2835_I2C_S_LEN; - else - i2c_dev->msg_err = 0; goto complete; } @@ -347,17 +443,29 @@ static int bcm2835_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], { struct bcm2835_i2c_dev *i2c_dev = i2c_get_adapdata(adap); unsigned long time_left; + bool ignore_nak = false; int i; - for (i = 0; i < (num - 1); i++) + if (debug) + i2c_dev->debug_num_msgs = num; + + if (debug > 2) + for (i = 0; i < num; i++) + bcm2835_debug_print_msg(i2c_dev, &msgs[i], i + 1, num, __func__); + + for (i = 0; i < (num - 1); i++) { if (msgs[i].flags & I2C_M_RD) { dev_warn_once(i2c_dev->dev, "only one read message supported, has to be last\n"); return -EOPNOTSUPP; } + if (msgs[i].flags & I2C_M_IGNORE_NAK) + ignore_nak = true; + } i2c_dev->curr_msg = msgs; i2c_dev->num_msgs = num; + i2c_dev->msg_err = 0; reinit_completion(&i2c_dev->completion); bcm2835_i2c_start_transfer(i2c_dev); @@ -367,6 +475,13 @@ static int bcm2835_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], bcm2835_i2c_finish_transfer(i2c_dev); + if (ignore_nak) + i2c_dev->msg_err &= ~BCM2835_I2C_S_ERR; + + if (debug > 1 || (debug && (!time_left || i2c_dev->msg_err))) + bcm2835_debug_print(i2c_dev); + i2c_dev->debug_num_msgs = 0; + i2c_dev->debug_num = 0; if (!time_left) { bcm2835_i2c_writel(i2c_dev, BCM2835_I2C_C, BCM2835_I2C_C_CLEAR); @@ -376,7 +491,9 @@ static int bcm2835_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], if (!i2c_dev->msg_err) return num; - dev_dbg(i2c_dev->dev, "i2c transfer failed: %x\n", i2c_dev->msg_err); + if (debug) + dev_err(i2c_dev->dev, "i2c transfer failed: %x\n", + i2c_dev->msg_err); if (i2c_dev->msg_err & BCM2835_I2C_S_ERR) return -EREMOTEIO; @@ -386,7 +503,7 @@ static int bcm2835_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], static u32 bcm2835_i2c_func(struct i2c_adapter *adap) { - return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_PROTOCOL_MANGLING; } static const struct i2c_algorithm bcm2835_i2c_algo = { diff --git a/drivers/i2c/busses/i2c-designware-common.c b/drivers/i2c/busses/i2c-designware-common.c index b3282785523d48..9101e33885fdde 100644 --- a/drivers/i2c/busses/i2c-designware-common.c +++ b/drivers/i2c/busses/i2c-designware-common.c @@ -63,6 +63,8 @@ static char *abort_sources[] = { "slave lost the bus while transmitting data to a remote master", [ABRT_SLAVE_RD_INTX] = "incorrect slave-transmitter mode configuration", + [ABRT_SLAVE_SDA_STUCK_AT_LOW] = + "SDA stuck at low", }; static int dw_reg_read(void *context, unsigned int reg, unsigned int *val) @@ -360,6 +362,9 @@ static void i2c_dw_adjust_bus_speed(struct dw_i2c_dev *dev) { u32 acpi_speed = i2c_dw_acpi_round_bus_speed(dev->dev); struct i2c_timings *t = &dev->timings; + u32 wanted_speed; + u32 legal_speed = 0; + int i; /* * Find bus speed from the "clock-frequency" device property, ACPI @@ -371,6 +376,30 @@ static void i2c_dw_adjust_bus_speed(struct dw_i2c_dev *dev) t->bus_freq_hz = max(t->bus_freq_hz, acpi_speed); else t->bus_freq_hz = I2C_MAX_FAST_MODE_FREQ; + + wanted_speed = t->bus_freq_hz; + + /* For unsupported speeds, scale down the lowest speed which is faster. */ + for (i = 0; i < ARRAY_SIZE(supported_speeds); i++) { + /* supported speeds is in decreasing order */ + if (wanted_speed == supported_speeds[i]) { + legal_speed = 0; + break; + } + if (wanted_speed > supported_speeds[i]) + break; + + legal_speed = supported_speeds[i]; + } + + if (legal_speed) { + /* + * Pretend this was the requested speed, but preserve the preferred + * speed so the clock counts can be scaled. + */ + t->bus_freq_hz = legal_speed; + dev->wanted_bus_speed = wanted_speed; + } } int i2c_dw_fw_parse_and_configure(struct dw_i2c_dev *dev) @@ -665,8 +694,16 @@ int i2c_dw_wait_bus_not_busy(struct dw_i2c_dev *dev) int i2c_dw_handle_tx_abort(struct dw_i2c_dev *dev) { unsigned long abort_source = dev->abort_source; + unsigned int reg; int i; + if (abort_source & DW_IC_TX_ABRT_SLAVE_SDA_STUCK_AT_LOW) { + regmap_write(dev->map, DW_IC_ENABLE, + DW_IC_ENABLE_ENABLE | DW_IC_ENABLE_BUS_RECOVERY); + regmap_read_poll_timeout(dev->map, DW_IC_ENABLE, reg, + !(reg & DW_IC_ENABLE_BUS_RECOVERY), + 1100, 200000); + } if (abort_source & DW_IC_TX_ABRT_NOACK) { for_each_set_bit(i, &abort_source, ARRAY_SIZE(abort_sources)) dev_dbg(dev->dev, @@ -681,6 +718,8 @@ int i2c_dw_handle_tx_abort(struct dw_i2c_dev *dev) return -EAGAIN; else if (abort_source & DW_IC_TX_ABRT_GCALL_READ) return -EINVAL; /* wrong msgs[] data */ + else if (abort_source & DW_IC_TX_ABRT_SLAVE_SDA_STUCK_AT_LOW) + return -EREMOTEIO; else return -EIO; } diff --git a/drivers/i2c/busses/i2c-designware-core.h b/drivers/i2c/busses/i2c-designware-core.h index 2d32896d067346..e1ac3782718549 100644 --- a/drivers/i2c/busses/i2c-designware-core.h +++ b/drivers/i2c/busses/i2c-designware-core.h @@ -78,9 +78,12 @@ #define DW_IC_TX_ABRT_SOURCE 0x80 #define DW_IC_ENABLE_STATUS 0x9c #define DW_IC_CLR_RESTART_DET 0xa8 +#define DW_IC_SCL_STUCK_AT_LOW_TIMEOUT 0xac +#define DW_IC_SDA_STUCK_AT_LOW_TIMEOUT 0xb0 #define DW_IC_COMP_PARAM_1 0xf4 #define DW_IC_COMP_VERSION 0xf8 #define DW_IC_SDA_HOLD_MIN_VERS 0x3131312A /* "111*" == v1.11* */ +#define DW_IC_BUS_CLEAR_MIN_VERS 0x3230302A /* "200*" == v2.00* */ #define DW_IC_COMP_TYPE 0xfc #define DW_IC_COMP_TYPE_VALUE 0x44570140 /* "DW" + 0x0140 */ @@ -110,6 +113,7 @@ #define DW_IC_ENABLE_ENABLE BIT(0) #define DW_IC_ENABLE_ABORT BIT(1) +#define DW_IC_ENABLE_BUS_RECOVERY BIT(3) #define DW_IC_STATUS_ACTIVITY BIT(0) #define DW_IC_STATUS_TFE BIT(2) @@ -117,13 +121,16 @@ #define DW_IC_STATUS_MASTER_ACTIVITY BIT(5) #define DW_IC_STATUS_SLAVE_ACTIVITY BIT(6) #define DW_IC_STATUS_MASTER_HOLD_TX_FIFO_EMPTY BIT(7) +#define DW_IC_STATUS_SDA_STUCK_NOT_RECOVERED BIT(11) #define DW_IC_SDA_HOLD_RX_SHIFT 16 #define DW_IC_SDA_HOLD_RX_MASK GENMASK(23, 16) #define DW_IC_ERR_TX_ABRT 0x1 +#define DW_IC_TAR_SPECIAL BIT(11) #define DW_IC_TAR_10BITADDR_MASTER BIT(12) +#define DW_IC_TAR_SMBUS_QUICK_CMD BIT(16) #define DW_IC_COMP_PARAM_1_SPEED_MODE_HIGH (BIT(2) | BIT(3)) #define DW_IC_COMP_PARAM_1_SPEED_MODE_MASK GENMASK(3, 2) @@ -162,6 +169,7 @@ #define ABRT_SLAVE_FLUSH_TXFIFO 13 #define ABRT_SLAVE_ARBLOST 14 #define ABRT_SLAVE_RD_INTX 15 +#define ABRT_SLAVE_SDA_STUCK_AT_LOW 17 #define DW_IC_TX_ABRT_7B_ADDR_NOACK BIT(ABRT_7B_ADDR_NOACK) #define DW_IC_TX_ABRT_10ADDR1_NOACK BIT(ABRT_10ADDR1_NOACK) @@ -177,6 +185,7 @@ #define DW_IC_RX_ABRT_SLAVE_RD_INTX BIT(ABRT_SLAVE_RD_INTX) #define DW_IC_RX_ABRT_SLAVE_ARBLOST BIT(ABRT_SLAVE_ARBLOST) #define DW_IC_RX_ABRT_SLAVE_FLUSH_TXFIFO BIT(ABRT_SLAVE_FLUSH_TXFIFO) +#define DW_IC_TX_ABRT_SLAVE_SDA_STUCK_AT_LOW BIT(ABRT_SLAVE_SDA_STUCK_AT_LOW) #define DW_IC_TX_ABRT_NOACK (DW_IC_TX_ABRT_7B_ADDR_NOACK | \ DW_IC_TX_ABRT_10ADDR1_NOACK | \ @@ -291,6 +300,7 @@ struct dw_i2c_dev { u16 fp_lcnt; u16 hs_hcnt; u16 hs_lcnt; + u32 wanted_bus_speed; int (*acquire_lock)(void); void (*release_lock)(void); int semaphore_idx; diff --git a/drivers/i2c/busses/i2c-designware-master.c b/drivers/i2c/busses/i2c-designware-master.c index 28188c6d0555e0..21fdb8cd753fff 100644 --- a/drivers/i2c/busses/i2c-designware-master.c +++ b/drivers/i2c/busses/i2c-designware-master.c @@ -41,6 +41,34 @@ static void i2c_dw_configure_fifo_master(struct dw_i2c_dev *dev) regmap_write(dev->map, DW_IC_CON, dev->master_cfg); } +static u32 linear_interpolate(u32 x, u32 x1, u32 x2, u32 y1, u32 y2) +{ + return ((x - x1) * y2 + (x2 - x) * y1) / (x2 - x1); +} + +static u16 u16_clamp(u32 v) +{ + return (u16)min(v, 0xffff); +} + +static void clock_calc(struct dw_i2c_dev *dev, u32 *hcnt, u32 *lcnt) +{ + struct i2c_timings *t = &dev->timings; + u32 wanted_khz = (dev->wanted_bus_speed ?: t->bus_freq_hz)/1000; + u32 clk_khz = i2c_dw_clk_rate(dev); + u32 min_high_ns = (wanted_khz <= 100) ? 4000 : + (wanted_khz <= 400) ? + linear_interpolate(wanted_khz, 100, 400, 4000, 600) : + linear_interpolate(wanted_khz, 400, 1000, 600, 260); + u32 high_cycles = (u32)(((u64)clk_khz * min_high_ns + 999999) / 1000000) + 1; + u32 extra_high_cycles = (u32)((u64)clk_khz * t->scl_fall_ns / 1000000); + u32 extra_low_cycles = (u32)((u64)clk_khz * t->scl_rise_ns / 1000000); + u32 period = ((u64)clk_khz + wanted_khz - 1) / wanted_khz; + + *hcnt = u16_clamp(high_cycles - extra_high_cycles); + *lcnt = u16_clamp(period - high_cycles - extra_low_cycles); +} + static int i2c_dw_set_timings_master(struct dw_i2c_dev *dev) { unsigned int comp_param1; @@ -48,6 +76,7 @@ static int i2c_dw_set_timings_master(struct dw_i2c_dev *dev) struct i2c_timings *t = &dev->timings; const char *fp_str = ""; u32 ic_clk; + u32 hcnt, lcnt; int ret; ret = i2c_dw_acquire_lock(dev); @@ -63,6 +92,8 @@ static int i2c_dw_set_timings_master(struct dw_i2c_dev *dev) sda_falling_time = t->sda_fall_ns ?: 300; /* ns */ scl_falling_time = t->scl_fall_ns ?: 300; /* ns */ + clock_calc(dev, &hcnt, &lcnt); + /* Calculate SCL timing parameters for standard mode if not set */ if (!dev->ss_hcnt || !dev->ss_lcnt) { ic_clk = i2c_dw_clk_rate(dev); @@ -82,6 +113,8 @@ static int i2c_dw_set_timings_master(struct dw_i2c_dev *dev) scl_falling_time, 0); /* No offset */ } + dev->ss_hcnt = hcnt; + dev->ss_lcnt = lcnt; dev_dbg(dev->dev, "Standard Mode HCNT:LCNT = %d:%d\n", dev->ss_hcnt, dev->ss_lcnt); @@ -140,6 +173,8 @@ static int i2c_dw_set_timings_master(struct dw_i2c_dev *dev) scl_falling_time, 0); /* No offset */ } + dev->fs_hcnt = hcnt; + dev->fs_lcnt = lcnt; dev_dbg(dev->dev, "Fast Mode%s HCNT:LCNT = %d:%d\n", fp_str, dev->fs_hcnt, dev->fs_lcnt); @@ -172,10 +207,15 @@ static int i2c_dw_set_timings_master(struct dw_i2c_dev *dev) scl_falling_time, 0); /* No offset */ } + dev->hs_hcnt = hcnt; + dev->hs_lcnt = lcnt; dev_dbg(dev->dev, "High Speed Mode HCNT:LCNT = %d:%d\n", dev->hs_hcnt, dev->hs_lcnt); } + if (!dev->sda_hold_time) + dev->sda_hold_time = lcnt / 2; + ret = i2c_dw_set_sda_hold(dev); if (ret) return ret; @@ -194,6 +234,7 @@ static int i2c_dw_set_timings_master(struct dw_i2c_dev *dev) */ static int i2c_dw_init_master(struct dw_i2c_dev *dev) { + unsigned int timeout = 0; int ret; ret = i2c_dw_acquire_lock(dev); @@ -217,6 +258,17 @@ static int i2c_dw_init_master(struct dw_i2c_dev *dev) regmap_write(dev->map, DW_IC_HS_SCL_LCNT, dev->hs_lcnt); } + if (dev->master_cfg & DW_IC_CON_BUS_CLEAR_CTRL) { + /* Set a sensible timeout if not already configured */ + regmap_read(dev->map, DW_IC_SDA_STUCK_AT_LOW_TIMEOUT, &timeout); + if (timeout == ~0) { + /* Use 10ms as a timeout, which is 1000 cycles at 100kHz */ + timeout = i2c_dw_clk_rate(dev) * 10; /* clock rate is in kHz */ + regmap_write(dev->map, DW_IC_SDA_STUCK_AT_LOW_TIMEOUT, timeout); + regmap_write(dev->map, DW_IC_SCL_STUCK_AT_LOW_TIMEOUT, timeout); + } + } + /* Write SDA hold time if supported */ if (dev->sda_hold_time) regmap_write(dev->map, DW_IC_SDA_HOLD, dev->sda_hold_time); @@ -248,6 +300,10 @@ static void i2c_dw_xfer_init(struct dw_i2c_dev *dev) ic_tar = DW_IC_TAR_10BITADDR_MASTER; } + /* Convert a zero-length read into an SMBUS quick command */ + if (!msgs[dev->msg_write_idx].len) + ic_tar = DW_IC_TAR_SPECIAL | DW_IC_TAR_SMBUS_QUICK_CMD; + regmap_update_bits(dev->map, DW_IC_CON, DW_IC_CON_10BITADDR_MASTER, ic_con); @@ -456,6 +512,14 @@ i2c_dw_xfer_msg(struct dw_i2c_dev *dev) regmap_read(dev->map, DW_IC_RXFLR, &flr); rx_limit = dev->rx_fifo_depth - flr; + /* Handle SMBUS quick commands */ + if (!buf_len) { + if (msgs[dev->msg_write_idx].flags & I2C_M_RD) + regmap_write(dev->map, DW_IC_DATA_CMD, 0x300); + else + regmap_write(dev->map, DW_IC_DATA_CMD, 0x200); + } + while (buf_len > 0 && tx_limit > 0 && rx_limit > 0) { u32 cmd = 0; @@ -894,14 +958,15 @@ static const struct i2c_algorithm i2c_dw_algo = { }; static const struct i2c_adapter_quirks i2c_dw_quirks = { - .flags = I2C_AQ_NO_ZERO_LEN, + .flags = 0, }; void i2c_dw_configure_master(struct dw_i2c_dev *dev) { struct i2c_timings *t = &dev->timings; - dev->functionality = I2C_FUNC_10BIT_ADDR | DW_IC_DEFAULT_FUNCTIONALITY; + dev->functionality = I2C_FUNC_10BIT_ADDR | I2C_FUNC_SMBUS_QUICK | + DW_IC_DEFAULT_FUNCTIONALITY; dev->master_cfg = DW_IC_CON_MASTER | DW_IC_CON_SLAVE_DISABLE | DW_IC_CON_RESTART_EN; @@ -983,6 +1048,7 @@ int i2c_dw_probe_master(struct dw_i2c_dev *dev) struct i2c_adapter *adap = &dev->adapter; unsigned long irq_flags; unsigned int ic_con; + unsigned int id_ver; int ret; init_completion(&dev->cmd_complete); @@ -1017,7 +1083,11 @@ int i2c_dw_probe_master(struct dw_i2c_dev *dev) if (ret) return ret; - if (ic_con & DW_IC_CON_BUS_CLEAR_CTRL) + ret = regmap_read(dev->map, DW_IC_COMP_VERSION, &id_ver); + if (ret) + return ret; + + if (ic_con & DW_IC_CON_BUS_CLEAR_CTRL || id_ver >= DW_IC_BUS_CLEAR_MIN_VERS) dev->master_cfg |= DW_IC_CON_BUS_CLEAR_CTRL; ret = dev->init(dev); diff --git a/drivers/i2c/busses/i2c-gpio.c b/drivers/i2c/busses/i2c-gpio.c index e0bd218e2f146c..aebc2d96c23fd3 100644 --- a/drivers/i2c/busses/i2c-gpio.c +++ b/drivers/i2c/busses/i2c-gpio.c @@ -428,7 +428,9 @@ static int i2c_gpio_probe(struct platform_device *pdev) adap->dev.parent = dev; device_set_node(&adap->dev, fwnode); - adap->nr = pdev->id; + if (pdev->id != PLATFORM_DEVID_NONE || !pdev->dev.of_node || + of_property_read_u32(pdev->dev.of_node, "reg", &adap->nr)) + adap->nr = pdev->id; ret = i2c_bit_add_numbered_bus(adap); if (ret) return ret; diff --git a/drivers/i2c/i2c-mux.c b/drivers/i2c/i2c-mux.c index fda72e8be88507..6018f196d0fd06 100644 --- a/drivers/i2c/i2c-mux.c +++ b/drivers/i2c/i2c-mux.c @@ -333,8 +333,13 @@ int i2c_mux_add_adapter(struct i2c_mux_core *muxc, if (muxc->dev->of_node) { struct device_node *dev_node = muxc->dev->of_node; struct device_node *mux_node, *child = NULL; + u32 base_nr = 0; u32 reg; + of_property_read_u32(dev_node, "base-nr", &base_nr); + if (!force_nr && base_nr) + force_nr = base_nr + chan_id; + if (muxc->arbitrator) mux_node = of_get_child_by_name(dev_node, "i2c-arb"); else if (muxc->gate) diff --git a/drivers/iio/adc/mcp3422.c b/drivers/iio/adc/mcp3422.c index 50834fdcf7388f..e27813611a64ed 100644 --- a/drivers/iio/adc/mcp3422.c +++ b/drivers/iio/adc/mcp3422.c @@ -407,7 +407,14 @@ static const struct i2c_device_id mcp3422_id[] = { MODULE_DEVICE_TABLE(i2c, mcp3422_id); static const struct of_device_id mcp3422_of_match[] = { - { .compatible = "mcp3422" }, + { .compatible = "microchip,mcp3421" }, + { .compatible = "microchip,mcp3422" }, + { .compatible = "microchip,mcp3423" }, + { .compatible = "microchip,mcp3424" }, + { .compatible = "microchip,mcp3425" }, + { .compatible = "microchip,mcp3426" }, + { .compatible = "microchip,mcp3427" }, + { .compatible = "microchip,mcp3428" }, { } }; MODULE_DEVICE_TABLE(of, mcp3422_of_match); diff --git a/drivers/iio/humidity/dht11.c b/drivers/iio/humidity/dht11.c index c97e2544877296..81c0bb9dd85140 100644 --- a/drivers/iio/humidity/dht11.c +++ b/drivers/iio/humidity/dht11.c @@ -152,9 +152,9 @@ static int dht11_decode(struct dht11 *dht11, int offset) dht11->temperature = (((temp_int & 0x7f) << 8) + temp_dec) * ((temp_int & 0x80) ? -100 : 100); dht11->humidity = ((hum_int << 8) + hum_dec) * 100; - } else if (temp_dec == 0 && hum_dec == 0) { /* DHT11 */ - dht11->temperature = temp_int * 1000; - dht11->humidity = hum_int * 1000; + } else if (temp_dec < 10 && hum_dec < 10) { /* DHT11 */ + dht11->temperature = temp_int * 1000 + temp_dec * 100; + dht11->humidity = hum_int * 1000 + hum_dec * 100; } else { dev_err(dht11->dev, "Don't know how to decode data: %d %d %d %d\n", diff --git a/drivers/iio/light/tsl4531.c b/drivers/iio/light/tsl4531.c index a5788c09ad02fc..c918ebc930c3cf 100644 --- a/drivers/iio/light/tsl4531.c +++ b/drivers/iio/light/tsl4531.c @@ -232,9 +232,16 @@ static const struct i2c_device_id tsl4531_id[] = { }; MODULE_DEVICE_TABLE(i2c, tsl4531_id); +static const struct of_device_id tsl4531_of_id[] = { + { .compatible = "amstaos,tsl4531" }, + { } +}; +MODULE_DEVICE_TABLE(of, tsl4531_of_id); + static struct i2c_driver tsl4531_driver = { .driver = { .name = TSL4531_DRV_NAME, + .of_match_table = tsl4531_of_id, .pm = pm_sleep_ptr(&tsl4531_pm_ops), }, .probe = tsl4531_probe, diff --git a/drivers/iio/light/veml6070.c b/drivers/iio/light/veml6070.c index f8321d346d7757..15222705589ca2 100644 --- a/drivers/iio/light/veml6070.c +++ b/drivers/iio/light/veml6070.c @@ -194,9 +194,16 @@ static const struct i2c_device_id veml6070_id[] = { }; MODULE_DEVICE_TABLE(i2c, veml6070_id); +static const struct of_device_id veml6070_of_id[] = { + { .compatible = "vishay,veml6070" }, + { } +}; +MODULE_DEVICE_TABLE(of, veml6070_of_id); + static struct i2c_driver veml6070_driver = { .driver = { .name = VEML6070_DRV_NAME, + .of_match_table = veml6070_of_id, }, .probe = veml6070_probe, .remove = veml6070_remove, diff --git a/drivers/input/joystick/sensehat-joystick.c b/drivers/input/joystick/sensehat-joystick.c index a84df39d3b2fa6..f24beb98e44466 100644 --- a/drivers/input/joystick/sensehat-joystick.c +++ b/drivers/input/joystick/sensehat-joystick.c @@ -28,7 +28,7 @@ struct sensehat_joystick { }; static const unsigned int keymap[] = { - BTN_DPAD_DOWN, BTN_DPAD_RIGHT, BTN_DPAD_UP, BTN_SELECT, BTN_DPAD_LEFT, + KEY_DOWN, KEY_RIGHT, KEY_UP, KEY_ENTER, KEY_LEFT }; static irqreturn_t sensehat_joystick_report(int irq, void *cookie) diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c index 85c6d8ce003f3a..3bd31c0c8ab3a0 100644 --- a/drivers/input/touchscreen/edt-ft5x06.c +++ b/drivers/input/touchscreen/edt-ft5x06.c @@ -69,6 +69,7 @@ #define TOUCH_EVENT_RESERVED 0x03 #define EDT_NAME_LEN 23 +#define EDT_NAME_PREFIX_LEN 8 #define EDT_SWITCH_MODE_RETRIES 10 #define EDT_SWITCH_MODE_DELAY 5 /* msec */ #define EDT_RAW_DATA_RETRIES 100 @@ -80,6 +81,10 @@ #define M06_REG_CMD(factory) ((factory) ? 0xf3 : 0xfc) #define M06_REG_ADDR(factory, addr) ((factory) ? (addr) & 0x7f : (addr) & 0x3f) +#define RESET_DELAY_MS 300 /* reset deassert to I2C */ +#define FIRST_POLL_DELAY_MS 300 /* in addition to the above */ +#define POLL_INTERVAL_MS 17 /* 17ms = 60fps */ + enum edt_pmode { EDT_PMODE_NOT_SUPPORTED, EDT_PMODE_HIBERNATE, @@ -139,14 +144,19 @@ struct edt_ft5x06_ts_data { u8 tdata_cmd; int tdata_len; int tdata_offset; + unsigned int known_ids; - char name[EDT_NAME_LEN]; + char name[EDT_NAME_PREFIX_LEN + EDT_NAME_LEN]; char fw_version[EDT_NAME_LEN]; + int init_td_status; struct edt_reg_addr reg_addr; enum edt_ver version; unsigned int crc_errors; unsigned int header_errors; + + struct timer_list timer; + struct work_struct work_i2c_poll; }; struct edt_i2c_chip_data { @@ -303,17 +313,49 @@ static irqreturn_t edt_ft5x06_ts_isr(int irq, void *dev_id) u8 rdbuf[63]; int i, type, x, y, id; int error; + int num_points; + unsigned int active_ids = 0, known_ids = tsdata->known_ids; + long released_ids; + int b = 0; memset(rdbuf, 0, sizeof(rdbuf)); error = regmap_bulk_read(tsdata->regmap, tsdata->tdata_cmd, rdbuf, tsdata->tdata_len); + if (tsdata->version == EDT_M06) { + num_points = tsdata->max_support_points; + } else { + /* Register 2 is TD_STATUS, containing the number of touch + * points. + */ + num_points = min(rdbuf[2] & 0xf, tsdata->max_support_points); + + /* When polling FT5x06 without IRQ: initial register contents + * could be stale or undefined; discard all readings until + * TD_STATUS changes for the first time (or num_points is 0). + */ + if (tsdata->init_td_status) { + if (tsdata->init_td_status < 0) + tsdata->init_td_status = rdbuf[2]; + + if (num_points && rdbuf[2] == tsdata->init_td_status) + goto out; + + tsdata->init_td_status = 0; + } + + if (!error && num_points) + error = regmap_bulk_read(tsdata->regmap, + tsdata->tdata_offset, + &rdbuf[tsdata->tdata_offset], + tsdata->point_len * num_points); + } if (error) { dev_err_ratelimited(dev, "Unable to fetch data, error: %d\n", error); goto out; } - for (i = 0; i < tsdata->max_support_points; i++) { + for (i = 0; i < num_points; i++) { u8 *buf = &rdbuf[i * tsdata->point_len + tsdata->tdata_offset]; type = buf[0] >> 6; @@ -335,11 +377,26 @@ static irqreturn_t edt_ft5x06_ts_isr(int irq, void *dev_id) input_mt_slot(tsdata->input, id); if (input_mt_report_slot_state(tsdata->input, MT_TOOL_FINGER, - type != TOUCH_EVENT_UP)) + type != TOUCH_EVENT_UP)) { touchscreen_report_pos(tsdata->input, &tsdata->prop, x, y, true); + active_ids |= BIT(id); + } else { + known_ids &= ~BIT(id); + } } + /* One issue with the device is the TOUCH_UP message is not always + * returned. Instead track which ids we know about and report when they + * are no longer updated + */ + released_ids = known_ids & ~active_ids; + for_each_set_bit_from(b, &released_ids, tsdata->max_support_points) { + input_mt_slot(tsdata->input, b); + input_mt_report_slot_inactive(tsdata->input); + } + tsdata->known_ids = active_ids; + input_mt_report_pointer_emulation(tsdata->input, true); input_sync(tsdata->input); @@ -347,6 +404,22 @@ static irqreturn_t edt_ft5x06_ts_isr(int irq, void *dev_id) return IRQ_HANDLED; } +static void edt_ft5x06_ts_irq_poll_timer(struct timer_list *t) +{ + struct edt_ft5x06_ts_data *tsdata = from_timer(tsdata, t, timer); + + schedule_work(&tsdata->work_i2c_poll); + mod_timer(&tsdata->timer, jiffies + msecs_to_jiffies(POLL_INTERVAL_MS)); +} + +static void edt_ft5x06_ts_work_i2c_poll(struct work_struct *work) +{ + struct edt_ft5x06_ts_data *tsdata = container_of(work, + struct edt_ft5x06_ts_data, work_i2c_poll); + + edt_ft5x06_ts_isr(0, tsdata); +} + struct edt_ft5x06_attribute { struct device_attribute dattr; size_t field_offset; @@ -862,6 +935,9 @@ static int edt_ft5x06_ts_identify(struct i2c_client *client, char *model_name = tsdata->name; char *fw_version = tsdata->fw_version; + snprintf(model_name, EDT_NAME_PREFIX_LEN + 1, "%s ", dev_name(&client->dev)); + model_name += strlen(model_name); + /* see what we find if we assume it is a M06 * * if we get less than EDT_NAME_LEN, we don't want * to have garbage in there @@ -1050,20 +1126,23 @@ static void edt_ft5x06_ts_get_parameters(struct edt_ft5x06_ts_data *tsdata) static void edt_ft5x06_ts_set_tdata_parameters(struct edt_ft5x06_ts_data *tsdata) { int crclen; + int points; if (tsdata->version == EDT_M06) { tsdata->tdata_cmd = 0xf9; tsdata->tdata_offset = 5; tsdata->point_len = 4; crclen = 1; + points = tsdata->max_support_points; } else { tsdata->tdata_cmd = 0x0; tsdata->tdata_offset = 3; tsdata->point_len = 6; crclen = 0; + points = 0; } - tsdata->tdata_len = tsdata->point_len * tsdata->max_support_points + + tsdata->tdata_len = tsdata->point_len * points + tsdata->tdata_offset + crclen; } @@ -1258,7 +1337,7 @@ static int edt_ft5x06_ts_probe(struct i2c_client *client) if (tsdata->reset_gpio) { usleep_range(5000, 6000); gpiod_set_value_cansleep(tsdata->reset_gpio, 0); - msleep(300); + msleep(RESET_DELAY_MS); } input = devm_input_allocate_device(&client->dev); @@ -1332,17 +1411,28 @@ static int edt_ft5x06_ts_probe(struct i2c_client *client) return error; } - irq_flags = irq_get_trigger_type(client->irq); - if (irq_flags == IRQF_TRIGGER_NONE) - irq_flags = IRQF_TRIGGER_FALLING; - irq_flags |= IRQF_ONESHOT; + if (client->irq) { + irq_flags = irq_get_trigger_type(client->irq); + if (irq_flags == IRQF_TRIGGER_NONE) + irq_flags = IRQF_TRIGGER_FALLING; + irq_flags |= IRQF_ONESHOT; - error = devm_request_threaded_irq(&client->dev, client->irq, - NULL, edt_ft5x06_ts_isr, irq_flags, - client->name, tsdata); - if (error) { - dev_err(&client->dev, "Unable to request touchscreen IRQ.\n"); - return error; + error = devm_request_threaded_irq(&client->dev, client->irq, + NULL, edt_ft5x06_ts_isr, + irq_flags, client->name, + tsdata); + if (error) { + dev_err(&client->dev, "Unable to request touchscreen IRQ.\n"); + return error; + } + } else { + tsdata->init_td_status = -1; /* filter bogus initial data */ + INIT_WORK(&tsdata->work_i2c_poll, + edt_ft5x06_ts_work_i2c_poll); + timer_setup(&tsdata->timer, edt_ft5x06_ts_irq_poll_timer, 0); + tsdata->timer.expires = + jiffies + msecs_to_jiffies(FIRST_POLL_DELAY_MS); + add_timer(&tsdata->timer); } error = input_register_device(input); @@ -1364,6 +1454,10 @@ static void edt_ft5x06_ts_remove(struct i2c_client *client) { struct edt_ft5x06_ts_data *tsdata = i2c_get_clientdata(client); + if (!client->irq) { + del_timer(&tsdata->timer); + cancel_work_sync(&tsdata->work_i2c_poll); + } edt_ft5x06_ts_teardown_debugfs(tsdata); } diff --git a/drivers/input/touchscreen/goodix.c b/drivers/input/touchscreen/goodix.c index a3e8a51c914495..4920c740ca0b12 100644 --- a/drivers/input/touchscreen/goodix.c +++ b/drivers/input/touchscreen/goodix.c @@ -48,6 +48,8 @@ #define MAX_CONTACTS_LOC 5 #define TRIGGER_LOC 6 +#define POLL_INTERVAL_MS 17 /* 17ms = 60fps */ + /* Our special handling for GPIO accesses through ACPI is x86 specific */ #if defined CONFIG_X86 && defined CONFIG_ACPI #define ACPI_GPIO_SUPPORT @@ -513,16 +515,67 @@ static irqreturn_t goodix_ts_irq_handler(int irq, void *dev_id) return IRQ_HANDLED; } +static void goodix_ts_irq_poll_timer(struct timer_list *t) +{ + struct goodix_ts_data *ts = from_timer(ts, t, timer); + + schedule_work(&ts->work_i2c_poll); + mod_timer(&ts->timer, jiffies + msecs_to_jiffies(POLL_INTERVAL_MS)); +} + +static void goodix_ts_work_i2c_poll(struct work_struct *work) +{ + struct goodix_ts_data *ts = container_of(work, + struct goodix_ts_data, work_i2c_poll); + + goodix_process_events(ts); + goodix_i2c_write_u8(ts->client, GOODIX_READ_COOR_ADDR, 0); +} + +static void goodix_enable_irq(struct goodix_ts_data *ts) +{ + if (ts->client->irq) { + enable_irq(ts->client->irq); + } else { + ts->timer.expires = jiffies + msecs_to_jiffies(POLL_INTERVAL_MS); + add_timer(&ts->timer); + } +} + +static void goodix_disable_irq(struct goodix_ts_data *ts) +{ + if (ts->client->irq) { + disable_irq(ts->client->irq); + } else { + del_timer(&ts->timer); + cancel_work_sync(&ts->work_i2c_poll); + } +} + static void goodix_free_irq(struct goodix_ts_data *ts) { - devm_free_irq(&ts->client->dev, ts->client->irq, ts); + if (ts->client->irq) { + devm_free_irq(&ts->client->dev, ts->client->irq, ts); + } else { + del_timer(&ts->timer); + cancel_work_sync(&ts->work_i2c_poll); + } } static int goodix_request_irq(struct goodix_ts_data *ts) { - return devm_request_threaded_irq(&ts->client->dev, ts->client->irq, - NULL, goodix_ts_irq_handler, - ts->irq_flags, ts->client->name, ts); + if (ts->client->irq) { + return devm_request_threaded_irq(&ts->client->dev, ts->client->irq, + NULL, goodix_ts_irq_handler, + ts->irq_flags, ts->client->name, ts); + } else { + INIT_WORK(&ts->work_i2c_poll, + goodix_ts_work_i2c_poll); + timer_setup(&ts->timer, goodix_ts_irq_poll_timer, 0); + if (ts->irq_pin_access_method == IRQ_PIN_ACCESS_NONE) + goodix_enable_irq(ts); + return 0; + } } static int goodix_check_cfg_8(struct goodix_ts_data *ts, const u8 *cfg, int len) @@ -1141,7 +1194,10 @@ static int goodix_configure_dev(struct goodix_ts_data *ts) return -ENOMEM; } - ts->input_dev->name = "Goodix Capacitive TouchScreen"; + snprintf(ts->name, GOODIX_NAME_MAX_LEN, "%s Goodix Capacitive TouchScreen", + dev_name(&ts->client->dev)); + + ts->input_dev->name = ts->name; ts->input_dev->phys = "input/ts"; ts->input_dev->id.bustype = BUS_I2C; ts->input_dev->id.vendor = 0x0416; @@ -1407,6 +1463,11 @@ static void goodix_ts_remove(struct i2c_client *client) { struct goodix_ts_data *ts = i2c_get_clientdata(client); + if (!client->irq) { + del_timer(&ts->timer); + cancel_work_sync(&ts->work_i2c_poll); + } + if (ts->load_cfg_from_disk) wait_for_completion(&ts->firmware_loading_complete); } @@ -1422,7 +1483,7 @@ static int goodix_suspend(struct device *dev) /* We need gpio pins to suspend/resume */ if (ts->irq_pin_access_method == IRQ_PIN_ACCESS_NONE) { - disable_irq(client->irq); + goodix_disable_irq(ts); return 0; } @@ -1466,7 +1527,7 @@ static int goodix_resume(struct device *dev) int error; if (ts->irq_pin_access_method == IRQ_PIN_ACCESS_NONE) { - enable_irq(client->irq); + goodix_enable_irq(ts); return 0; } diff --git a/drivers/input/touchscreen/goodix.h b/drivers/input/touchscreen/goodix.h index 87797cc88b3243..235dd5f264c520 100644 --- a/drivers/input/touchscreen/goodix.h +++ b/drivers/input/touchscreen/goodix.h @@ -57,6 +57,8 @@ #define GOODIX_CONFIG_MAX_LENGTH 240 #define GOODIX_MAX_KEYS 7 +#define GOODIX_NAME_MAX_LEN 38 + enum goodix_irq_pin_access_method { IRQ_PIN_ACCESS_NONE, IRQ_PIN_ACCESS_GPIO, @@ -91,6 +93,7 @@ struct goodix_ts_data { enum gpiod_flags gpiod_rst_flags; char id[GOODIX_ID_MAX_LEN + 1]; char cfg_name[64]; + char name[GOODIX_NAME_MAX_LEN]; u16 version; bool reset_controller_at_probe; bool load_cfg_from_disk; @@ -104,6 +107,8 @@ struct goodix_ts_data { u8 main_clk[GOODIX_MAIN_CLK_LEN]; int bak_ref_len; u8 *bak_ref; + struct timer_list timer; + struct work_struct work_i2c_poll; }; int goodix_i2c_read(struct i2c_client *client, u16 reg, u8 *buf, int len); diff --git a/drivers/input/touchscreen/ili210x.c b/drivers/input/touchscreen/ili210x.c index 260c83dc23a2e2..b08d911c893c1e 100644 --- a/drivers/input/touchscreen/ili210x.c +++ b/drivers/input/touchscreen/ili210x.c @@ -67,6 +67,8 @@ struct ili210x { u8 version_proto[2]; u8 ic_mode[2]; bool stop; + struct timer_list poll_timer; + struct work_struct poll_work; }; static int ili210x_read_reg(struct i2c_client *client, @@ -360,6 +362,34 @@ static irqreturn_t ili210x_irq(int irq, void *irq_data) return IRQ_HANDLED; } +static void ili210x_poll_work(struct work_struct *work) +{ + struct ili210x *priv = container_of(work, struct ili210x, poll_work); + struct i2c_client *client = priv->client; + const struct ili2xxx_chip *chip = priv->chip; + u8 touchdata[ILI210X_DATA_SIZE] = { 0 }; + bool touch; + int error; + + error = chip->get_touch_data(client, touchdata); + if (error) { + dev_err(&client->dev, "Unable to get touch data: %d\n", error); + return; + } + + touch = ili210x_report_events(priv, touchdata); +} + +static void ili210x_poll_timer_callback(struct timer_list *t) +{ + struct ili210x *priv = from_timer(priv, t, poll_timer); + + schedule_work(&priv->poll_work); + + if (!priv->stop) + mod_timer(&priv->poll_timer, jiffies + msecs_to_jiffies(ILI2XXX_POLL_PERIOD)); +} + static int ili251x_firmware_update_resolution(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); @@ -947,11 +977,6 @@ static int ili210x_i2c_probe(struct i2c_client *client) return -ENODEV; } - if (client->irq <= 0) { - dev_err(dev, "No IRQ!\n"); - return -EINVAL; - } - reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); if (IS_ERR(reset_gpio)) return PTR_ERR(reset_gpio); @@ -1003,12 +1028,17 @@ static int ili210x_i2c_probe(struct i2c_client *client) return error; } - error = devm_request_threaded_irq(dev, client->irq, NULL, ili210x_irq, - IRQF_ONESHOT, client->name, priv); - if (error) { - dev_err(dev, "Unable to request touchscreen IRQ, err: %d\n", - error); - return error; + if (client->irq) { + error = devm_request_threaded_irq(dev, client->irq, NULL, ili210x_irq, + IRQF_ONESHOT, client->name, priv); + if (error) { + dev_err(dev, "Unable to request touchscreen IRQ, err: %d\n", error); + return error; + } + } else { + timer_setup(&priv->poll_timer, ili210x_poll_timer_callback, 0); + mod_timer(&priv->poll_timer, jiffies + msecs_to_jiffies(ILI2XXX_POLL_PERIOD)); + INIT_WORK(&priv->poll_work, ili210x_poll_work); } error = devm_add_action_or_reset(dev, ili210x_stop, priv); @@ -1024,6 +1054,16 @@ static int ili210x_i2c_probe(struct i2c_client *client) return 0; } +static void ili210x_i2c_remove(struct i2c_client *client) +{ + struct ili210x *tsdata = i2c_get_clientdata(client); + + if (!client->irq) { + del_timer(&tsdata->poll_timer); + cancel_work_sync(&tsdata->poll_work); + } +} + static const struct i2c_device_id ili210x_i2c_id[] = { { "ili210x", (long)&ili210x_chip }, { "ili2117", (long)&ili211x_chip }, @@ -1050,6 +1090,7 @@ static struct i2c_driver ili210x_ts_driver = { }, .id_table = ili210x_i2c_id, .probe = ili210x_i2c_probe, + .remove = ili210x_i2c_remove, }; module_i2c_driver(ili210x_ts_driver); diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig index b3aa1f5d53218b..10f956bbef5777 100644 --- a/drivers/iommu/Kconfig +++ b/drivers/iommu/Kconfig @@ -519,4 +519,11 @@ config SPRD_IOMMU Say Y here if you want to use the multimedia devices listed above. +config BCM2712_IOMMU + bool "BCM2712 IOMMU driver" + depends on ARM64 && ARCH_BCM + select IOMMU_API + help + IOMMU driver for BCM2712 + endif # IOMMU_SUPPORT diff --git a/drivers/iommu/Makefile b/drivers/iommu/Makefile index 542760d963ec7c..52970e291d60d6 100644 --- a/drivers/iommu/Makefile +++ b/drivers/iommu/Makefile @@ -30,3 +30,4 @@ obj-$(CONFIG_IOMMU_SVA) += iommu-sva.o obj-$(CONFIG_IOMMU_IOPF) += io-pgfault.o obj-$(CONFIG_SPRD_IOMMU) += sprd-iommu.o obj-$(CONFIG_APPLE_DART) += apple-dart.o +obj-$(CONFIG_BCM2712_IOMMU) += bcm2712-iommu.o bcm2712-iommu-cache.o diff --git a/drivers/iommu/bcm2712-iommu-cache.c b/drivers/iommu/bcm2712-iommu-cache.c new file mode 100644 index 00000000000000..fdea69f5370b18 --- /dev/null +++ b/drivers/iommu/bcm2712-iommu-cache.c @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * IOMMU driver for BCM2712 + * + * Copyright (c) 2023 Raspberry Pi Ltd. + */ + +#include "bcm2712-iommu.h" + +#include <linux/err.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> + +#define MMUC_CONTROL_ENABLE 1 +#define MMUC_CONTROL_FLUSH 2 +#define MMUC_CONTROL_FLUSHING 4 + +void bcm2712_iommu_cache_flush(struct bcm2712_iommu_cache *cache) +{ + unsigned long flags; + int i; + + spin_lock_irqsave(&cache->hw_lock, flags); + if (cache->reg_base) { + /* Enable and flush the TLB cache */ + writel(MMUC_CONTROL_ENABLE | MMUC_CONTROL_FLUSH, + cache->reg_base); + + /* Wait for flush to complete: it should be very quick */ + for (i = 0; i < 1024; i++) { + if (!(MMUC_CONTROL_FLUSHING & readl(cache->reg_base))) + break; + cpu_relax(); + } + } + spin_unlock_irqrestore(&cache->hw_lock, flags); +} + +static int bcm2712_iommu_cache_probe(struct platform_device *pdev) +{ + struct bcm2712_iommu_cache *cache; + + dev_info(&pdev->dev, __func__); + cache = devm_kzalloc(&pdev->dev, sizeof(*cache), GFP_KERNEL); + if (!cache) + return -ENOMEM; + + cache->dev = &pdev->dev; + platform_set_drvdata(pdev, cache); + spin_lock_init(&cache->hw_lock); + + /* Get IOMMUC registers; we only use the first register (IOMMUC_CTRL) */ + cache->reg_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(cache->reg_base)) { + dev_err(&pdev->dev, "Failed to get IOMMU Cache registers address\n"); + cache->reg_base = NULL; + } + return 0; +} + +static const struct of_device_id bcm2712_iommu_cache_of_match[] = { + { + . compatible = "brcm,bcm2712-iommuc" + }, + { /* sentinel */ }, +}; + +static struct platform_driver bcm2712_iommu_cache_driver = { + .probe = bcm2712_iommu_cache_probe, + .driver = { + .name = "bcm2712-iommu-cache", + .of_match_table = bcm2712_iommu_cache_of_match + }, +}; + +builtin_platform_driver(bcm2712_iommu_cache_driver); diff --git a/drivers/iommu/bcm2712-iommu.c b/drivers/iommu/bcm2712-iommu.c new file mode 100644 index 00000000000000..4ac6fdffa9b8a5 --- /dev/null +++ b/drivers/iommu/bcm2712-iommu.c @@ -0,0 +1,677 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * IOMMU driver for BCM2712 + * + * Copyright (c) 2023 Raspberry Pi Ltd. + */ + +#include "bcm2712-iommu.h" + +#include <linux/dma-mapping.h> +#include <linux/err.h> +#include <linux/iommu.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> + +#define MMU_WR(off, val) writel(val, mmu->reg_base + (off)) +#define MMU_RD(off) readl(mmu->reg_base + (off)) + +#define domain_to_mmu(d) (container_of(d, struct bcm2712_iommu_domain, base)->mmu) + +#define MMMU_CTRL_OFFSET 0x00 +#define MMMU_CTRL_CAP_EXCEEDED BIT(27) +#define MMMU_CTRL_CAP_EXCEEDED_ABORT_EN BIT(26) +#define MMMU_CTRL_CAP_EXCEEDED_INT_EN BIT(25) +#define MMMU_CTRL_CAP_EXCEEDED_EXCEPTION_EN BIT(24) +#define MMMU_CTRL_PT_INVALID BIT(20) +#define MMMU_CTRL_PT_INVALID_ABORT_EN BIT(19) +#define MMMU_CTRL_PT_INVALID_EXCEPTION_EN BIT(18) +#define MMMU_CTRL_PT_INVALID_EN BIT(17) +#define MMMU_CTRL_WRITE_VIOLATION BIT(12) +#define MMMU_CTRL_WRITE_VIOLATION_ABORT_EN BIT(11) +#define MMMU_CTRL_WRITE_VIOLATION_INT_EN BIT(10) +#define MMMU_CTRL_WRITE_VIOLATION_EXCEPTION_EN BIT(9) +#define MMMU_CTRL_BYPASS BIT(8) +#define MMMU_CTRL_TLB_CLEARING BIT(7) +#define MMMU_CTRL_STATS_CLEAR BIT(3) +#define MMMU_CTRL_TLB_CLEAR BIT(2) +#define MMMU_CTRL_STATS_ENABLE BIT(1) +#define MMMU_CTRL_ENABLE BIT(0) + +#define MMMU_PT_PA_BASE_OFFSET 0x04 + +#define MMMU_HIT_OFFSET 0x08 +#define MMMU_MISS_OFFSET 0x0C +#define MMMU_STALL_OFFSET 0x10 + +#define MMMU_ADDR_CAP_OFFSET 0x14 +#define MMMU_ADDR_CAP_ENABLE BIT(31) +#define ADDR_CAP_SHIFT 28 /* ADDR_CAP is defined to be in 256 MByte units */ + +#define MMMU_SHOOT_DOWN_OFFSET 0x18 +#define MMMU_SHOOT_DOWN_SHOOTING BIT(31) +#define MMMU_SHOOT_DOWN_SHOOT BIT(30) + +#define MMMU_BYPASS_START_OFFSET 0x1C +#define MMMU_BYPASS_START_ENABLE BIT(31) +#define MMMU_BYPASS_START_INVERT BIT(30) + +#define MMMU_BYPASS_END_OFFSET 0x20 +#define MMMU_BYPASS_END_ENABLE BIT(31) + +#define MMMU_MISC_OFFSET 0x24 +#define MMMU_MISC_SINGLE_TABLE BIT(31) + +#define MMMU_ILLEGAL_ADR_OFFSET 0x30 +#define MMMU_ILLEGAL_ADR_ENABLE BIT(31) + +#define MMMU_DEBUG_INFO_OFFSET 0x38 +#define MMMU_DEBUG_INFO_VERSION_MASK 0x0000000Fu +#define MMMU_DEBUG_INFO_VA_WIDTH_MASK 0x000000F0u +#define MMMU_DEBUG_INFO_PA_WIDTH_MASK 0x00000F00u +#define MMMU_DEBUG_INFO_BIGPAGE_WIDTH_MASK 0x000FF000u +#define MMMU_DEBUG_INFO_SUPERPAGE_WIDTH_MASK 0x0FF00000u +#define MMMU_DEBUG_INFO_BYPASS_4M BIT(28) +#define MMMU_DEBUG_INFO_BYPASS BIT(29) + +#define MMMU_PTE_PAGESIZE_MASK 0xC0000000u +#define MMMU_PTE_WRITEABLE BIT(29) +#define MMMU_PTE_VALID BIT(28) + +/* + * BCM2712 IOMMU is organized around 4Kbyte pages (MMU_PAGE_SIZE). + * Linux PAGE_SIZE must not be smaller but may be larger (e.g. 4K, 16K). + * + * Unlike many larger MMUs, this one uses a 4-byte word size, allowing + * 1024 entries within each 4K table page, and two-level translation. + * + * Let's allocate enough table space for 2GB of translated memory (IOVA). + * This requires 512 4K pages (2MB) of level-2 tables, one page of + * top-level table (only half-filled in this particular configuration), + * plus one "default" page to catch illegal requests. + * + * The translated virtual address region is between 40GB and 42GB; + * addresses below this range pass straight through to the SDRAM. + * + * Currently we assume a 1:1:1 correspondence of IOMMU, group and domain. + */ + +#define MMU_PAGE_SHIFT 12 +#define MMU_PAGE_SIZE BIT(MMU_PAGE_SHIFT) + +#define PAGEWORDS_SHIFT (MMU_PAGE_SHIFT - 2) +#define HUGEPAGE_SHIFT (MMU_PAGE_SHIFT + PAGEWORDS_SHIFT) +#define L1_CHUNK_SHIFT (MMU_PAGE_SHIFT + 2 * PAGEWORDS_SHIFT) + +#define APERTURE_BASE (40ul << 30) +#define APERTURE_SIZE (2ul << 30) +#define APERTURE_TOP (APERTURE_BASE + APERTURE_SIZE) +#define TRANSLATED_PAGES (APERTURE_SIZE >> MMU_PAGE_SHIFT) +#define L2_PAGES (TRANSLATED_PAGES >> PAGEWORDS_SHIFT) +#define TABLES_ALLOC_SIZE (L2_PAGES * MMU_PAGE_SIZE + 2 * PAGE_SIZE) + +static void bcm2712_iommu_init(struct bcm2712_iommu *mmu) +{ + unsigned int i, bypass_shift; + struct sg_dma_page_iter it; + u32 u = MMU_RD(MMMU_DEBUG_INFO_OFFSET); + + /* + * Check IOMMU version and hardware configuration. + * This driver is for VC IOMMU version >= 4 (with 2-level tables) + * and assumes at least 36 bits of virtual and physical address space. + * Bigpage and superpage sizes are typically 64K and 1M, but may vary + * (hugepage size is fixed at 4M, the range covered by an L2 page). + */ + dev_info(mmu->dev, "%s: DEBUG_INFO = 0x%08x\n", __func__, u); + WARN_ON(FIELD_GET(MMMU_DEBUG_INFO_VERSION_MASK, u) < 4 || + FIELD_GET(MMMU_DEBUG_INFO_VA_WIDTH_MASK, u) < 6 || + FIELD_GET(MMMU_DEBUG_INFO_PA_WIDTH_MASK, u) < 6 || + !(u & MMMU_DEBUG_INFO_BYPASS)); + + mmu->bigpage_mask = + ((1u << FIELD_GET(MMMU_DEBUG_INFO_BIGPAGE_WIDTH_MASK, u)) - 1u) << MMU_PAGE_SHIFT; + mmu->superpage_mask = + ((1u << FIELD_GET(MMMU_DEBUG_INFO_SUPERPAGE_WIDTH_MASK, u)) - 1u) << MMU_PAGE_SHIFT; + bypass_shift = (u & MMMU_DEBUG_INFO_BYPASS_4M) ? + HUGEPAGE_SHIFT : ADDR_CAP_SHIFT; + + /* Disable MMU and clear sticky flags; meanwhile flush the TLB */ + MMU_WR(MMMU_CTRL_OFFSET, + MMMU_CTRL_CAP_EXCEEDED | + MMMU_CTRL_PT_INVALID | + MMMU_CTRL_WRITE_VIOLATION | + MMMU_CTRL_STATS_CLEAR | + MMMU_CTRL_TLB_CLEAR); + + /* + * Put MMU into 2-level mode; set address cap and "bypass" range + * (note that some of these registers have unintuitive off-by-ones). + * Addresses below APERTURE_BASE are passed unchanged: this is + * useful for blocks which share an IOMMU with other blocks + * whose drivers are not IOMMU-aware. + */ + MMU_WR(MMMU_MISC_OFFSET, + MMU_RD(MMMU_MISC_OFFSET) & ~MMMU_MISC_SINGLE_TABLE); + MMU_WR(MMMU_ADDR_CAP_OFFSET, + MMMU_ADDR_CAP_ENABLE + + (APERTURE_TOP >> ADDR_CAP_SHIFT) - 1); + if (APERTURE_BASE > 0) { + MMU_WR(MMMU_BYPASS_START_OFFSET, + MMMU_BYPASS_START_ENABLE + MMMU_BYPASS_START_INVERT + + (APERTURE_BASE >> bypass_shift) - 1); + MMU_WR(MMMU_BYPASS_END_OFFSET, + MMMU_BYPASS_END_ENABLE + + (APERTURE_TOP >> bypass_shift)); + } else { + MMU_WR(MMMU_BYPASS_START_OFFSET, 0); + MMU_WR(MMMU_BYPASS_END_OFFSET, 0); + } + + /* Ensure tables are zeroed (which marks all pages as invalid) */ + dma_sync_sgtable_for_cpu(mmu->dev, mmu->sgt, DMA_TO_DEVICE); + memset(mmu->tables, 0, TABLES_ALLOC_SIZE); + mmu->nmapped_pages = 0; + + /* Initialize the high-level table to point to the low-level pages */ + __sg_page_iter_start(&it.base, mmu->sgt->sgl, mmu->sgt->nents, 0); + for (i = 0; i < L2_PAGES; i++) { + if (!(i % (PAGE_SIZE / MMU_PAGE_SIZE))) { + __sg_page_iter_dma_next(&it); + u = (sg_page_iter_dma_address(&it) >> MMU_PAGE_SHIFT); + } else { + u++; + } + mmu->tables[TRANSLATED_PAGES + i] = MMMU_PTE_VALID + u; + } + + /* + * Configure the addresses of the top-level table (offset because + * the aperture does not start from zero), and of the default page. + * For simplicity, both these regions are whole Linux pages. + */ + __sg_page_iter_dma_next(&it); + u = (sg_page_iter_dma_address(&it) >> MMU_PAGE_SHIFT); + MMU_WR(MMMU_PT_PA_BASE_OFFSET, u - (APERTURE_BASE >> L1_CHUNK_SHIFT)); + __sg_page_iter_dma_next(&it); + u = (sg_page_iter_dma_address(&it) >> MMU_PAGE_SHIFT); + MMU_WR(MMMU_ILLEGAL_ADR_OFFSET, MMMU_ILLEGAL_ADR_ENABLE + u); + dma_sync_sgtable_for_device(mmu->dev, mmu->sgt, DMA_TO_DEVICE); + mmu->dirty = false; + + /* Flush (and enable) the shared TLB cache; enable this MMU. */ + if (mmu->cache) + bcm2712_iommu_cache_flush(mmu->cache); + MMU_WR(MMMU_CTRL_OFFSET, + MMMU_CTRL_CAP_EXCEEDED_ABORT_EN | + MMMU_CTRL_PT_INVALID_ABORT_EN | + MMMU_CTRL_WRITE_VIOLATION_ABORT_EN | + MMMU_CTRL_STATS_ENABLE | + MMMU_CTRL_ENABLE); +} + +static int bcm2712_iommu_attach_dev(struct iommu_domain *domain, struct device *dev) +{ + struct bcm2712_iommu *mmu = dev ? dev_iommu_priv_get(dev) : 0; + struct bcm2712_iommu_domain *mydomain = + container_of(domain, struct bcm2712_iommu_domain, base); + + dev_info(dev, "%s: MMU %s\n", + __func__, mmu ? dev_name(mmu->dev) : ""); + + if (mmu) { + mydomain->mmu = mmu; + mmu->domain = mydomain; + + if (mmu->dma_iova_offset) { + domain->geometry.aperture_start = + mmu->dma_iova_offset + APERTURE_BASE; + domain->geometry.aperture_end = + mmu->dma_iova_offset + APERTURE_TOP - 1ul; + } + + return 0; + } + return -EINVAL; +} + +static int bcm2712_iommu_map(struct iommu_domain *domain, unsigned long iova, + phys_addr_t pa, size_t bytes, size_t count, + int prot, gfp_t gfp, size_t *mapped) +{ + struct bcm2712_iommu *mmu = domain_to_mmu(domain); + u32 entry = MMMU_PTE_VALID | (pa >> MMU_PAGE_SHIFT); + u32 align = (u32)(iova | pa | bytes); + unsigned int p; + + /* Reject if at least the first page is not within our aperture */ + if (iova < mmu->dma_iova_offset + APERTURE_BASE || + iova + bytes > mmu->dma_iova_offset + APERTURE_TOP) { + dev_warn(mmu->dev, "%s: iova=0x%lx pa=0x%llx bytes=0x%lx OUT OF RANGE\n", + __func__, iova, + (unsigned long long)pa, (unsigned long)bytes); + *mapped = 0; + + return -EINVAL; + } + + /* large page and write enable flags */ + if (!(align & ((1 << HUGEPAGE_SHIFT) - 1))) + entry |= FIELD_PREP(MMMU_PTE_PAGESIZE_MASK, 3); + else if (!(align & mmu->superpage_mask) && mmu->superpage_mask) + entry |= FIELD_PREP(MMMU_PTE_PAGESIZE_MASK, 2); + else if (!(align & mmu->bigpage_mask) && mmu->bigpage_mask) + entry |= FIELD_PREP(MMMU_PTE_PAGESIZE_MASK, 1); + if (prot & IOMMU_WRITE) + entry |= MMMU_PTE_WRITEABLE; + + /* Ensure tables are cache-coherent with CPU */ + if (!mmu->dirty) { + dma_sync_sgtable_for_cpu(mmu->dev, mmu->sgt, DMA_TO_DEVICE); + mmu->dirty = true; + } + + /* Make iova relative to table base; amalgamate count pages */ + iova -= (mmu->dma_iova_offset + APERTURE_BASE); + bytes = min(APERTURE_SIZE - iova, count * bytes); + + /* Iterate over table by smallest native IOMMU page size */ + for (p = iova >> MMU_PAGE_SHIFT; + p < (iova + bytes) >> MMU_PAGE_SHIFT; p++) { + mmu->nmapped_pages += !(mmu->tables[p]); + mmu->tables[p] = entry++; + } + + *mapped = bytes; + + return 0; +} + +static size_t bcm2712_iommu_unmap(struct iommu_domain *domain, unsigned long iova, + size_t bytes, size_t count, + struct iommu_iotlb_gather *gather) +{ + struct bcm2712_iommu *mmu = domain_to_mmu(domain); + unsigned int p; + + if (iova < mmu->dma_iova_offset + APERTURE_BASE || + iova + bytes > mmu->dma_iova_offset + APERTURE_TOP) + return 0; + + /* Record just the lower and upper bounds in "gather" */ + if (gather) { + bool empty = (gather->end <= gather->start); + + if (empty || gather->start < iova) + gather->start = iova; + if (empty || gather->end < iova + bytes) + gather->end = iova + bytes; + } + + /* Ensure tables are cache-coherent with CPU */ + if (!mmu->dirty) { + dma_sync_sgtable_for_cpu(mmu->dev, mmu->sgt, DMA_TO_DEVICE); + mmu->dirty = true; + } + + /* Make iova relative to table base; amalgamate count pages */ + iova -= (mmu->dma_iova_offset + APERTURE_BASE); + bytes = min(APERTURE_SIZE - iova, count * bytes); + + /* Clear table entries, this marks the addresses as illegal */ + for (p = iova >> MMU_PAGE_SHIFT; + p < (iova + bytes) >> MMU_PAGE_SHIFT; + p++) { + mmu->nmapped_pages -= !!(mmu->tables[p]); + mmu->tables[p] = 0; + } + + return bytes; +} + +static int bcm2712_iommu_sync_range(struct iommu_domain *domain, + unsigned long iova, size_t size) +{ + struct bcm2712_iommu *mmu = domain_to_mmu(domain); + unsigned long iova_end; + unsigned int i, p4; + + if (!mmu || !mmu->dirty) + return 0; + + /* Ensure tables are cleaned from CPU cache or write-buffer */ + dma_sync_sgtable_for_device(mmu->dev, mmu->sgt, DMA_TO_DEVICE); + mmu->dirty = false; + + /* Flush the shared TLB cache */ + if (mmu->cache) + bcm2712_iommu_cache_flush(mmu->cache); + + /* + * When flushing a large range or when nothing needs to be kept, + * it's quicker to use the"TLB_CLEAR" flag. Otherwise, invalidate + * TLB entries in lines of 4 words each. Each flush/clear operation + * should complete almost instantaneously. + */ + iova -= mmu->dma_iova_offset; + iova_end = min(APERTURE_TOP, iova + size); + iova = max(APERTURE_BASE, iova); + if (mmu->nmapped_pages == 0 || iova_end - iova >= APERTURE_SIZE / 8) { + MMU_WR(MMMU_CTRL_OFFSET, + MMMU_CTRL_CAP_EXCEEDED_ABORT_EN | + MMMU_CTRL_PT_INVALID_ABORT_EN | + MMMU_CTRL_WRITE_VIOLATION_ABORT_EN | + MMMU_CTRL_TLB_CLEAR | + MMMU_CTRL_STATS_ENABLE | + MMMU_CTRL_ENABLE); + for (i = 0; i < 1024; i++) { + if (!(MMMU_CTRL_TLB_CLEARING & MMU_RD(MMMU_CTRL_OFFSET))) + break; + cpu_relax(); + } + } else { + for (p4 = iova >> (MMU_PAGE_SHIFT + 2); + p4 < (iova_end + 3 * MMU_PAGE_SIZE) >> (MMU_PAGE_SHIFT + 2); + p4++) { + MMU_WR(MMMU_SHOOT_DOWN_OFFSET, + MMMU_SHOOT_DOWN_SHOOT + (p4 << 2)); + for (i = 0; i < 1024; i++) { + if (!(MMMU_SHOOT_DOWN_SHOOTING & MMU_RD(MMMU_SHOOT_DOWN_OFFSET))) + break; + cpu_relax(); + } + } + } + + return 0; +} + +static void bcm2712_iommu_sync(struct iommu_domain *domain, + struct iommu_iotlb_gather *gather) +{ + bcm2712_iommu_sync_range(domain, gather->start, + gather->end - gather->start); +} + +static void bcm2712_iommu_sync_all(struct iommu_domain *domain) +{ + bcm2712_iommu_sync_range(domain, APERTURE_BASE, APERTURE_SIZE); +} + +static phys_addr_t bcm2712_iommu_iova_to_phys(struct iommu_domain *domain, dma_addr_t iova) +{ + struct bcm2712_iommu *mmu = domain_to_mmu(domain); + u32 p; + + iova -= mmu->dma_iova_offset; + if (iova >= APERTURE_BASE && iova < APERTURE_TOP) { + p = (iova - APERTURE_BASE) >> MMU_PAGE_SHIFT; + p = mmu->tables[p] & 0x0FFFFFFFu; + return (((phys_addr_t)p) << MMU_PAGE_SHIFT) + (iova & (MMU_PAGE_SIZE - 1u)); + } else if (iova < APERTURE_BASE) { + return (phys_addr_t)iova; + } else { + return (phys_addr_t)-EINVAL; + } +} + +static void bcm2712_iommu_domain_free(struct iommu_domain *domain) +{ + struct bcm2712_iommu_domain *mydomain = + container_of(domain, struct bcm2712_iommu_domain, base); + + kfree(mydomain); +} + +static const struct iommu_domain_ops bcm2712_iommu_domain_ops = { + .attach_dev = bcm2712_iommu_attach_dev, + .map_pages = bcm2712_iommu_map, + .unmap_pages = bcm2712_iommu_unmap, + .iotlb_sync = bcm2712_iommu_sync, + .iotlb_sync_map = bcm2712_iommu_sync_range, + .flush_iotlb_all = bcm2712_iommu_sync_all, + .iova_to_phys = bcm2712_iommu_iova_to_phys, + .free = bcm2712_iommu_domain_free, +}; + +static struct iommu_domain *bcm2712_iommu_domain_alloc(unsigned int type) +{ + struct bcm2712_iommu_domain *domain; + + if (type != IOMMU_DOMAIN_UNMANAGED && type != IOMMU_DOMAIN_DMA) + return NULL; + + domain = kzalloc(sizeof(*domain), GFP_KERNEL); + if (!domain) + return NULL; + + domain->base.type = type; + domain->base.ops = &bcm2712_iommu_domain_ops; + domain->base.geometry.aperture_start = APERTURE_BASE; + domain->base.geometry.aperture_end = APERTURE_TOP - 1ul; + domain->base.geometry.force_aperture = true; + return &domain->base; +} + +static struct iommu_device *bcm2712_iommu_probe_device(struct device *dev) +{ + struct bcm2712_iommu *mmu; + + /* + * For reasons I don't fully understand, we need to try both + * cases (dev_iommu_priv_get() and platform_get_drvdata()) + * in order to get both GPU and ISP-BE to probe successfully. + */ + mmu = dev_iommu_priv_get(dev); + if (!mmu) { + struct device_node *np; + struct platform_device *pdev; + + /* Ignore devices that don't have an "iommus" property with exactly one phandle */ + if (!dev->of_node || + of_property_count_elems_of_size(dev->of_node, "iommus", sizeof(phandle)) != 1) + return ERR_PTR(-ENODEV); + + np = of_parse_phandle(dev->of_node, "iommus", 0); + if (!np) + return ERR_PTR(-EINVAL); + + pdev = of_find_device_by_node(np); + of_node_put(np); + if (pdev) + mmu = platform_get_drvdata(pdev); + + if (!mmu) + return ERR_PTR(-ENODEV); + } + + dev_info(dev, "%s: MMU %s\n", __func__, dev_name(mmu->dev)); + dev_iommu_priv_set(dev, mmu); + return &mmu->iommu; +} + +static void bcm2712_iommu_release_device(struct device *dev) +{ + dev_iommu_priv_set(dev, NULL); +} + +static struct iommu_group *bcm2712_iommu_device_group(struct device *dev) +{ + struct bcm2712_iommu *mmu = dev_iommu_priv_get(dev); + + if (!mmu || !mmu->group) + return ERR_PTR(-EINVAL); + + dev_info(dev, "%s: MMU %s\n", __func__, dev_name(mmu->dev)); + return iommu_group_ref_get(mmu->group); +} + +static int bcm2712_iommu_of_xlate(struct device *dev, + const struct of_phandle_args *args) +{ + struct platform_device *iommu_dev; + struct bcm2712_iommu *mmu; + + iommu_dev = of_find_device_by_node(args->np); + mmu = platform_get_drvdata(iommu_dev); + dev_iommu_priv_set(dev, mmu); + dev_info(dev, "%s: MMU %s\n", __func__, dev_name(mmu->dev)); + + return 0; +} + +static bool bcm2712_iommu_capable(struct device *dev, enum iommu_cap cap) +{ + return false; +} + +static const struct iommu_ops bcm2712_iommu_ops = { + .capable = bcm2712_iommu_capable, + .domain_alloc = bcm2712_iommu_domain_alloc, + .probe_device = bcm2712_iommu_probe_device, + .release_device = bcm2712_iommu_release_device, + .device_group = bcm2712_iommu_device_group, + /* Advertise native page sizes as well as 2M, 16K which Linux may prefer */ + .pgsize_bitmap = (SZ_4M | SZ_2M | SZ_1M | SZ_64K | SZ_16K | SZ_4K), + .default_domain_ops = &bcm2712_iommu_domain_ops, + .of_xlate = bcm2712_iommu_of_xlate, +}; + +static int bcm2712_iommu_probe(struct platform_device *pdev) +{ + struct bcm2712_iommu *mmu; + struct bcm2712_iommu_cache *cache = NULL; + int ret; + + /* First of all, check for an IOMMU shared cache */ + if (pdev->dev.of_node) { + struct device_node *cache_np; + struct platform_device *cache_pdev; + + cache_np = of_parse_phandle(pdev->dev.of_node, "cache", 0); + if (cache_np) { + cache_pdev = of_find_device_by_node(cache_np); + of_node_put(cache_np); + if (cache_pdev && !IS_ERR(cache_pdev)) + cache = platform_get_drvdata(cache_pdev); + if (!cache) + return -EPROBE_DEFER; + } + } + + /* Allocate private data */ + mmu = devm_kzalloc(&pdev->dev, sizeof(*mmu), GFP_KERNEL); + if (!mmu) + return -ENOMEM; + + mmu->name = dev_name(&pdev->dev); + mmu->dev = &pdev->dev; + mmu->cache = cache; + platform_set_drvdata(pdev, mmu); + spin_lock_init(&mmu->hw_lock); + + /* + * XXX When an IOMMU is downstream of a PCIe RC or some other chip/bus + * and serves some of the masters thereon (others using pass-through), + * we seem to fumble and lose the "dma-ranges" address offset for + * masters using IOMMU. This property restores it, where needed. + */ + if (!pdev->dev.of_node || + of_property_read_u64(pdev->dev.of_node, "dma-iova-offset", + &mmu->dma_iova_offset)) + mmu->dma_iova_offset = 0; + + /* + * The IOMMU is itself a device that allocates DMA-able memory + * to hold its translation tables. Provided the IOVA aperture + * is no larger than 4 GBytes (so that the L1 table fits within + * a single 4K page), we don't need the tables to be contiguous. + * Assume we can address at least 36 bits (64 GB). + */ + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(36)); + WARN_ON(ret); + mmu->sgt = dma_alloc_noncontiguous(&pdev->dev, TABLES_ALLOC_SIZE, + DMA_TO_DEVICE, GFP_KERNEL, + DMA_ATTR_ALLOC_SINGLE_PAGES); + if (!mmu->sgt) { + ret = -ENOMEM; + goto done_err; + } + mmu->tables = dma_vmap_noncontiguous(&pdev->dev, TABLES_ALLOC_SIZE, + mmu->sgt); + if (!mmu->tables) { + ret = -ENOMEM; + goto done_err; + } + + /* Get IOMMU registers */ + mmu->reg_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(mmu->reg_base)) { + dev_err(&pdev->dev, "Failed to get IOMMU registers address\n"); + ret = PTR_ERR(mmu->reg_base); + goto done_err; + } + + /* Stuff */ + mmu->group = iommu_group_alloc(); + if (IS_ERR(mmu->group)) { + ret = PTR_ERR(mmu->group); + mmu->group = NULL; + goto done_err; + } + ret = iommu_device_sysfs_add(&mmu->iommu, mmu->dev, NULL, mmu->name); + if (ret) + goto done_err; + + /* Initialize table and hardware */ + bcm2712_iommu_init(mmu); + ret = iommu_device_register(&mmu->iommu, &bcm2712_iommu_ops, &pdev->dev); + + dev_info(&pdev->dev, "%s: Success\n", __func__); + return 0; + +done_err: + dev_info(&pdev->dev, "%s: Failure %d\n", __func__, ret); + if (mmu->group) + iommu_group_put(mmu->group); + if (mmu->tables) + dma_vunmap_noncontiguous(&pdev->dev, + (void *)(mmu->tables)); + mmu->tables = NULL; + if (mmu->sgt) + dma_free_noncontiguous(&pdev->dev, TABLES_ALLOC_SIZE, + mmu->sgt, DMA_TO_DEVICE); + mmu->sgt = NULL; + kfree(mmu); + return ret; +} + +static void bcm2712_iommu_remove(struct platform_device *pdev) +{ + struct bcm2712_iommu *mmu = platform_get_drvdata(pdev); + + if (mmu->reg_base) + MMU_WR(MMMU_CTRL_OFFSET, 0); /* disable the MMU */ + if (mmu->sgt) + dma_free_noncontiguous(&pdev->dev, TABLES_ALLOC_SIZE, + mmu->sgt, DMA_TO_DEVICE); +} + +static const struct of_device_id bcm2712_iommu_of_match[] = { + { + . compatible = "brcm,bcm2712-iommu" + }, + { /* sentinel */ }, +}; + +static struct platform_driver bcm2712_iommu_driver = { + .probe = bcm2712_iommu_probe, + .remove = bcm2712_iommu_remove, + .driver = { + .name = "bcm2712-iommu", + .of_match_table = bcm2712_iommu_of_match + }, +}; + +builtin_platform_driver(bcm2712_iommu_driver); diff --git a/drivers/iommu/bcm2712-iommu.h b/drivers/iommu/bcm2712-iommu.h new file mode 100644 index 00000000000000..31b811e426ddb8 --- /dev/null +++ b/drivers/iommu/bcm2712-iommu.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * IOMMU driver for BCM2712 + * + * Copyright (c) 2023 Raspberry Pi Ltd. + */ + +#ifndef _BCM2712_IOMMU_H +#define _BCM2712_IOMMU_H + +#include <linux/iommu.h> +#include <linux/scatterlist.h> + +struct bcm2712_iommu_cache { + struct device *dev; + spinlock_t hw_lock; /* to protect HW registers */ + void __iomem *reg_base; +}; + +void bcm2712_iommu_cache_flush(struct bcm2712_iommu_cache *cache); + +struct bcm2712_iommu { + struct device *dev; + struct iommu_device iommu; + struct iommu_group *group; + struct bcm2712_iommu_domain *domain; + char const *name; + struct sg_table *sgt; /* allocated memory for page tables */ + u32 *tables; /* kernel mapping for page tables */ + struct bcm2712_iommu_cache *cache; + spinlock_t hw_lock; /* to protect HW registers */ + void __iomem *reg_base; + u64 dma_iova_offset; /* Hack for IOMMU attached to PCIe RC */ + u32 bigpage_mask; + u32 superpage_mask; + unsigned int nmapped_pages; + bool dirty; /* true when tables are oriented towards CPU */ +}; + +struct bcm2712_iommu_domain { + struct iommu_domain base; + struct bcm2712_iommu *mmu; +}; + +#endif diff --git a/drivers/iommu/dma-iommu.c b/drivers/iommu/dma-iommu.c index 2a9fa0c8cc00fe..35d17125e92538 100644 --- a/drivers/iommu/dma-iommu.c +++ b/drivers/iommu/dma-iommu.c @@ -21,6 +21,7 @@ #include <linux/iova.h> #include <linux/irq.h> #include <linux/list_sort.h> +#include <linux/mempolicy.h> #include <linux/memremap.h> #include <linux/mm.h> #include <linux/mutex.h> @@ -883,11 +884,65 @@ static void __iommu_dma_free_pages(struct page **pages, int count) kvfree(pages); } +#if IS_ENABLED(CONFIG_NUMA) +static struct mempolicy iommu_dma_mpol = { + .refcnt = ATOMIC_INIT(1), /* never free it */ + .mode = MPOL_LOCAL, +}; + +static struct mempolicy *dma_iommu_numa_policy(void) +{ + return &iommu_dma_mpol; +} + +static unsigned short dma_iommu_numa_mode(void) +{ + return iommu_dma_mpol.mode; +} + +static int __init setup_numapolicy(char *str) +{ + struct mempolicy pol = { }, *ppol = &pol; + char buf[128]; + int ret; + + if (str) + ret = mpol_parse_str(str, &ppol); + else + ret = -EINVAL; + + if (!ret) { + iommu_dma_mpol = pol; + mpol_to_str(buf, sizeof(buf), &pol); + pr_info("DMA IOMMU NUMA default policy overridden to '%s'\n", buf); + } else { + pr_warn("Unable to parse dma_iommu_numa_policy=\n"); + } + + return ret == 0; +} +__setup("iommu_dma_numa_policy=", setup_numapolicy); +#else +static struct mempolicy *dma_iommu_numa_policy(void) +{ + return NULL; +} + +static unsigned short dma_iommu_numa_mode(void) +{ + return MPOL_LOCAL; +} +#endif static struct page **__iommu_dma_alloc_pages(struct device *dev, unsigned int count, unsigned long order_mask, gfp_t gfp) { struct page **pages; unsigned int i = 0, nid = dev_to_node(dev); + const bool use_numa = nid == NUMA_NO_NODE && + dma_iommu_numa_mode() != MPOL_LOCAL; + + if (use_numa) + order_mask = 1; order_mask &= GENMASK(MAX_PAGE_ORDER, 0); if (!order_mask) @@ -903,6 +958,7 @@ static struct page **__iommu_dma_alloc_pages(struct device *dev, while (count) { struct page *page = NULL; unsigned int order_size; + nodemask_t *nodemask; /* * Higher-order allocations are a convenience rather @@ -917,6 +973,10 @@ static struct page **__iommu_dma_alloc_pages(struct device *dev, order_size = 1U << order; if (order_mask > order_size) alloc_flags |= __GFP_NORETRY; + if (use_numa) + nodemask = numa_policy_nodemask(gfp, + dma_iommu_numa_policy(), + i, &nid); page = alloc_pages_node(nid, alloc_flags, order); if (!page) continue; diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index c1f30483600859..1313828094a592 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -109,6 +109,14 @@ config I8259 bool select IRQ_DOMAIN +config BCM2712_MIP + bool "Broadcom 2712 MSI-X Interrupt Peripheral support" + depends on ARM_GIC + select GENERIC_IRQ_CHIP + select IRQ_DOMAIN + help + Enable support for the Broadcom BCM2712 MSI-X target peripheral. + config BCM6345_L1_IRQ bool select GENERIC_IRQ_CHIP diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index e3679ec2b9f76e..1066cb881b7971 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -62,6 +62,7 @@ obj-$(CONFIG_XTENSA_MX) += irq-xtensa-mx.o obj-$(CONFIG_XILINX_INTC) += irq-xilinx-intc.o obj-$(CONFIG_IRQ_CROSSBAR) += irq-crossbar.o obj-$(CONFIG_SOC_VF610) += irq-vf610-mscm-ir.o +obj-$(CONFIG_BCM2712_MIP) += irq-bcm2712-mip.o obj-$(CONFIG_BCM6345_L1_IRQ) += irq-bcm6345-l1.o obj-$(CONFIG_BCM7038_L1_IRQ) += irq-bcm7038-l1.o obj-$(CONFIG_BCM7120_L2_IRQ) += irq-bcm7120-l2.o diff --git a/drivers/irqchip/irq-bcm2712-mip.c b/drivers/irqchip/irq-bcm2712-mip.c new file mode 100644 index 00000000000000..2eaa3ac10cb62c --- /dev/null +++ b/drivers/irqchip/irq-bcm2712-mip.c @@ -0,0 +1,323 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021 Raspberry Pi Ltd., All Rights Reserved. + */ + +#include <linux/pci.h> +#include <linux/msi.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/of_pci.h> + +#include <linux/irqchip.h> + +#define MIP_INT_RAISED 0x00 +#define MIP_INT_CLEARED 0x10 +#define MIP_INT_CFGL_HOST 0x20 +#define MIP_INT_CFGH_HOST 0x30 +#define MIP_INT_MASKL_HOST 0x40 +#define MIP_INT_MASKH_HOST 0x50 +#define MIP_INT_MASKL_VPU 0x60 +#define MIP_INT_MASKH_VPU 0x70 +#define MIP_INT_STATUSL_HOST 0x80 +#define MIP_INT_STATUSH_HOST 0x90 +#define MIP_INT_STATUSL_VPU 0xa0 +#define MIP_INT_STATUSH_VPU 0xb0 + +struct mip_priv { + spinlock_t msi_map_lock; + spinlock_t hw_lock; + void * __iomem base; + phys_addr_t msg_addr; + u32 msi_base; /* The SGI number that MSIs start */ + u32 num_msis; /* The number of SGIs for MSIs */ + u32 msi_offset; /* Shift the allocated msi up by N */ + unsigned long *msi_map; +}; + +static void mip_mask_msi_irq(struct irq_data *d) +{ + pci_msi_mask_irq(d); + irq_chip_mask_parent(d); +} + +static void mip_unmask_msi_irq(struct irq_data *d) +{ + pci_msi_unmask_irq(d); + irq_chip_unmask_parent(d); +} + +static void mip_compose_msi_msg(struct irq_data *d, struct msi_msg *msg) +{ + struct mip_priv *priv = irq_data_get_irq_chip_data(d); + + msg->address_hi = upper_32_bits(priv->msg_addr); + msg->address_lo = lower_32_bits(priv->msg_addr); + msg->data = d->hwirq; +} + +// The "bus-specific" irq_chip (the MIP doesn't _have_ to be used with PCIe) + +static struct irq_chip mip_msi_irq_chip = { + .name = "MIP-MSI", + .irq_unmask = mip_unmask_msi_irq, + .irq_mask = mip_mask_msi_irq, + .irq_eoi = irq_chip_eoi_parent, + .irq_set_affinity = irq_chip_set_affinity_parent, +}; + +static struct msi_domain_info mip_msi_domain_info = { + .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | + MSI_FLAG_PCI_MSIX), + .chip = &mip_msi_irq_chip, +}; + +// The "middle" irq_chip (the hardware control part) + +static struct irq_chip mip_irq_chip = { + .name = "MIP", + .irq_mask = irq_chip_mask_parent, + .irq_unmask = irq_chip_unmask_parent, + .irq_eoi = irq_chip_eoi_parent, + .irq_set_affinity = irq_chip_set_affinity_parent, + .irq_set_type = irq_chip_set_type_parent, + .irq_compose_msi_msg = mip_compose_msi_msg, +}; + + +// And a domain to connect it to its parent (the GIC) + +static int mip_irq_domain_alloc(struct irq_domain *domain, + unsigned int virq, unsigned int nr_irqs, + void *args) +{ + struct mip_priv *priv = domain->host_data; + struct irq_fwspec fwspec; + struct irq_data *irqd; + int hwirq, ret, i; + + spin_lock(&priv->msi_map_lock); + + hwirq = bitmap_find_free_region(priv->msi_map, priv->num_msis, ilog2(nr_irqs)); + + spin_unlock(&priv->msi_map_lock); + + if (hwirq < 0) + return -ENOSPC; + + hwirq += priv->msi_offset; + fwspec.fwnode = domain->parent->fwnode; + fwspec.param_count = 3; + fwspec.param[0] = 0; + fwspec.param[1] = hwirq + priv->msi_base; + fwspec.param[2] = IRQ_TYPE_EDGE_RISING; + + ret = irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, &fwspec); + if (ret) + return ret; + + for (i = 0; i < nr_irqs; i++) { + irqd = irq_domain_get_irq_data(domain->parent, virq + i); + irqd->chip->irq_set_type(irqd, IRQ_TYPE_EDGE_RISING); + + irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i, + &mip_irq_chip, priv); + irqd = irq_get_irq_data(virq + i); + irqd_set_single_target(irqd); + irqd_set_affinity_on_activate(irqd); + } + + return 0; +} + +static void mip_irq_domain_free(struct irq_domain *domain, + unsigned int virq, unsigned int nr_irqs) +{ + struct irq_data *d = irq_domain_get_irq_data(domain, virq); + struct mip_priv *priv = irq_data_get_irq_chip_data(d); + + irq_domain_free_irqs_parent(domain, virq, nr_irqs); + d->hwirq -= priv->msi_offset; + + spin_lock(&priv->msi_map_lock); + + bitmap_release_region(priv->msi_map, d->hwirq, ilog2(nr_irqs)); + + spin_unlock(&priv->msi_map_lock); +} + +#if 0 +static int mip_irq_domain_activate(struct irq_domain *domain, + struct irq_data *d, bool reserve) +{ + struct mip_priv *priv = irq_data_get_irq_chip_data(d); + unsigned long flags; + unsigned int irq = d->hwirq; + void *__iomem reg = priv->base + + ((irq < 32) ? MIP_INT_MASKL_HOST : MIP_INT_MASKH_HOST); + u32 val; + + spin_lock_irqsave(&priv->hw_lock, flags); + val = readl(reg); + val &= ~(1 << (irq % 32)); // Clear the mask + writel(val, reg); + spin_unlock_irqrestore(&priv->hw_lock, flags); + return 0; +} + +static void mip_irq_domain_deactivate(struct irq_domain *domain, + struct irq_data *d) +{ + struct mip_priv *priv = irq_data_get_irq_chip_data(d); + unsigned long flags; + unsigned int irq = d->hwirq - priv->msi_base; + void *__iomem reg = priv->base + + ((irq < 32) ? MIP_INT_MASKL_HOST : MIP_INT_MASKH_HOST); + u32 val; + + spin_lock_irqsave(&priv->hw_lock, flags); + val = readl(reg); + val |= (1 << (irq % 32)); // Mask it out + writel(val, reg); + spin_unlock_irqrestore(&priv->hw_lock, flags); +} +#endif + +static const struct irq_domain_ops mip_irq_domain_ops = { + .alloc = mip_irq_domain_alloc, + .free = mip_irq_domain_free, + //.activate = mip_irq_domain_activate, + //.deactivate = mip_irq_domain_deactivate, +}; + +static int mip_init_domains(struct mip_priv *priv, + struct device_node *node) +{ + struct irq_domain *middle_domain, *msi_domain, *gic_domain; + struct device_node *gic_node; + + gic_node = of_irq_find_parent(node); + if (!gic_node) { + pr_err("Failed to find the GIC node\n"); + return -ENODEV; + } + + gic_domain = irq_find_host(gic_node); + if (!gic_domain) { + pr_err("Failed to find the GIC domain\n"); + return -ENXIO; + } + + middle_domain = irq_domain_add_hierarchy(gic_domain, 0, 0, NULL, + &mip_irq_domain_ops, + priv); + if (!middle_domain) { + pr_err("Failed to create the MIP middle domain\n"); + return -ENOMEM; + } + + msi_domain = pci_msi_create_irq_domain(of_node_to_fwnode(node), + &mip_msi_domain_info, + middle_domain); + if (!msi_domain) { + pr_err("Failed to create MSI domain\n"); + irq_domain_remove(middle_domain); + return -ENOMEM; + } + + return 0; +} + +static int __init mip_of_msi_init(struct device_node *node, + struct device_node *parent) +{ + struct mip_priv *priv; + struct resource res; + int ret; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + spin_lock_init(&priv->msi_map_lock); + spin_lock_init(&priv->hw_lock); + + ret = of_address_to_resource(node, 0, &res); + if (ret) { + pr_err("Failed to allocate resource\n"); + goto err_priv; + } + + if (of_property_read_u32(node, "brcm,msi-base-spi", &priv->msi_base)) { + pr_err("Unable to parse MSI base\n"); + ret = -EINVAL; + goto err_priv; + } + + if (of_property_read_u32(node, "brcm,msi-num-spis", &priv->num_msis)) { + pr_err("Unable to parse MSI numbers\n"); + ret = -EINVAL; + goto err_priv; + } + + if (of_property_read_u32(node, "brcm,msi-offset", &priv->msi_offset)) + priv->msi_offset = 0; + + if (of_property_read_u64(node, "brcm,msi-pci-addr", &priv->msg_addr)) { + pr_err("Unable to parse MSI address\n"); + ret = -EINVAL; + goto err_priv; + } + + priv->base = ioremap(res.start, resource_size(&res)); + if (!priv->base) { + pr_err("Failed to ioremap regs\n"); + ret = -ENOMEM; + goto err_priv; + } + + priv->msi_map = kcalloc(BITS_TO_LONGS(priv->num_msis), + sizeof(*priv->msi_map), + GFP_KERNEL); + if (!priv->msi_map) { + ret = -ENOMEM; + goto err_base; + } + + pr_debug("Registering %d msixs, starting at %d\n", + priv->num_msis, priv->msi_base); + + /* + * Begin with all MSI-Xs masked in for the host, masked out for the + * VPU, and edge-triggered. + */ + writel(0, priv->base + MIP_INT_MASKL_HOST); + writel(0, priv->base + MIP_INT_MASKH_HOST); + writel(~0, priv->base + MIP_INT_MASKL_VPU); + writel(~0, priv->base + MIP_INT_MASKH_VPU); + writel(~0, priv->base + MIP_INT_CFGL_HOST); + writel(~0, priv->base + MIP_INT_CFGH_HOST); + + ret = mip_init_domains(priv, node); + if (ret) { + pr_err("Failed to allocate msi_map\n"); + goto err_map; + } + + return 0; + +err_map: + kfree(priv->msi_map); + +err_base: + iounmap(priv->base); + +err_priv: + kfree(priv); + + pr_err("%s: failed - err %d\n", __func__, ret); + + return ret; +} +IRQCHIP_DECLARE(bcm_mip, "brcm,bcm2712-mip-intc", mip_of_msi_init); diff --git a/drivers/irqchip/irq-bcm2835.c b/drivers/irqchip/irq-bcm2835.c index 6c20604c2242f2..68b5986a14a9d4 100644 --- a/drivers/irqchip/irq-bcm2835.c +++ b/drivers/irqchip/irq-bcm2835.c @@ -40,12 +40,16 @@ #include <linux/of_address.h> #include <linux/of_irq.h> #include <linux/irqchip.h> +#include <linux/irqchip/irq-bcm2836.h> #include <linux/irqdomain.h> #include <asm/exception.h> +#ifndef CONFIG_ARM64 +#include <asm/mach/irq.h> +#endif /* Put the bank and irq (32 bits) into the hwirq */ -#define MAKE_HWIRQ(b, n) ((b << 5) | (n)) +#define MAKE_HWIRQ(b, n) (((b) << 5) | (n)) #define HWIRQ_BANK(i) (i >> 5) #define HWIRQ_BIT(i) BIT(i & 0x1f) @@ -60,11 +64,17 @@ #define BANK0_VALID_MASK (BANK0_HWIRQ_MASK | BANK1_HWIRQ | BANK2_HWIRQ \ | SHORTCUT1_MASK | SHORTCUT2_MASK) +#undef ARM_LOCAL_GPU_INT_ROUTING +#define ARM_LOCAL_GPU_INT_ROUTING 0x0c + #define REG_FIQ_CONTROL 0x0c #define FIQ_CONTROL_ENABLE BIT(7) +#define REG_FIQ_ENABLE FIQ_CONTROL_ENABLE +#define REG_FIQ_DISABLE 0 #define NR_BANKS 3 #define IRQS_PER_BANK 32 +#define NUMBER_IRQS MAKE_HWIRQ(NR_BANKS, 0) static const int reg_pending[] __initconst = { 0x00, 0x04, 0x08 }; static const int reg_enable[] __initconst = { 0x18, 0x10, 0x14 }; @@ -82,6 +92,7 @@ struct armctrl_ic { void __iomem *enable[NR_BANKS]; void __iomem *disable[NR_BANKS]; struct irq_domain *domain; + void __iomem *local_base; }; static struct armctrl_ic intc __read_mostly; @@ -89,22 +100,77 @@ static void __exception_irq_entry bcm2835_handle_irq( struct pt_regs *regs); static void bcm2836_chained_handle_irq(struct irq_desc *desc); +static inline unsigned int hwirq_to_fiq(unsigned long hwirq) +{ + hwirq -= NUMBER_IRQS; + /* + * The hwirq numbering used in this driver is: + * BASE (0-7) GPU1 (32-63) GPU2 (64-95). + * This differ from the one used in the FIQ register: + * GPU1 (0-31) GPU2 (32-63) BASE (64-71) + */ + if (hwirq >= 32) + return hwirq - 32; + + return hwirq + 64; +} + static void armctrl_mask_irq(struct irq_data *d) { - writel_relaxed(HWIRQ_BIT(d->hwirq), intc.disable[HWIRQ_BANK(d->hwirq)]); + if (d->hwirq >= NUMBER_IRQS) + writel_relaxed(REG_FIQ_DISABLE, intc.base + REG_FIQ_CONTROL); + else + writel_relaxed(HWIRQ_BIT(d->hwirq), + intc.disable[HWIRQ_BANK(d->hwirq)]); } static void armctrl_unmask_irq(struct irq_data *d) { - writel_relaxed(HWIRQ_BIT(d->hwirq), intc.enable[HWIRQ_BANK(d->hwirq)]); + if (d->hwirq >= NUMBER_IRQS) { + if (num_online_cpus() > 1) { + unsigned int data; + + if (!intc.local_base) { + pr_err("FIQ is disabled due to missing arm_local_intc\n"); + return; + } + + data = readl_relaxed(intc.local_base + + ARM_LOCAL_GPU_INT_ROUTING); + + data &= ~0xc; + data |= (1 << 2); + writel_relaxed(data, + intc.local_base + + ARM_LOCAL_GPU_INT_ROUTING); + } + + writel_relaxed(REG_FIQ_ENABLE | hwirq_to_fiq(d->hwirq), + intc.base + REG_FIQ_CONTROL); + } else { + writel_relaxed(HWIRQ_BIT(d->hwirq), + intc.enable[HWIRQ_BANK(d->hwirq)]); + } +} + +#ifdef CONFIG_ARM64 + +static void armctrl_ack_irq(struct irq_data *d) +{ + bcm2836_arm_irqchip_spin_gpu_irq(); } +#endif + static struct irq_chip armctrl_chip = { .name = "ARMCTRL-level", .irq_mask = armctrl_mask_irq, .irq_unmask = armctrl_unmask_irq, .flags = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_SKIP_SET_WAKE, +#ifdef CONFIG_ARM64 + .irq_ack = armctrl_ack_irq +#endif }; static int armctrl_xlate(struct irq_domain *d, struct device_node *ctrlr, @@ -137,15 +203,16 @@ static int __init armctrl_of_init(struct device_node *node, bool is_2836) { void __iomem *base; - int irq, b, i; + int irq = 0, last_irq, b, i; u32 reg; base = of_iomap(node, 0); if (!base) panic("%pOF: unable to map IC registers\n", node); - intc.domain = irq_domain_add_linear(node, MAKE_HWIRQ(NR_BANKS, 0), - &armctrl_ops, NULL); + intc.base = base; + intc.domain = irq_domain_add_linear(node, NUMBER_IRQS * 2, + &armctrl_ops, NULL); if (!intc.domain) panic("%pOF: unable to create IRQ domain\n", node); @@ -176,6 +243,8 @@ static int __init armctrl_of_init(struct device_node *node, pr_err(FW_BUG "Bootloader left fiq enabled\n"); } + last_irq = irq; + if (is_2836) { int parent_irq = irq_of_parse_and_map(node, 0); @@ -188,6 +257,27 @@ static int __init armctrl_of_init(struct device_node *node, set_handle_irq(bcm2835_handle_irq); } + if (is_2836) { + extern void __iomem * __attribute__((weak)) arm_local_intc; + intc.local_base = arm_local_intc; + if (!intc.local_base) + pr_err("Failed to get local intc base. FIQ is disabled for cpus > 1\n"); + } + + /* Make a duplicate irq range which is used to enable FIQ */ + for (b = 0; b < NR_BANKS; b++) { + for (i = 0; i < bank_irqs[b]; i++) { + irq = irq_create_mapping(intc.domain, + MAKE_HWIRQ(b, i) + NUMBER_IRQS); + BUG_ON(irq <= 0); + irq_set_chip(irq, &armctrl_chip); + irq_set_probe(irq); + } + } +#ifndef CONFIG_ARM64 + init_FIQ(irq - last_irq); +#endif + return 0; } @@ -255,7 +345,8 @@ static void bcm2836_chained_handle_irq(struct irq_desc *desc) { u32 hwirq; - while ((hwirq = get_next_armctrl_hwirq()) != ~0) + hwirq = get_next_armctrl_hwirq(); + if (hwirq != ~0) generic_handle_domain_irq(intc.domain, hwirq); } diff --git a/drivers/irqchip/irq-bcm2836.c b/drivers/irqchip/irq-bcm2836.c index e5f1059b989fe1..42660f14aaf67a 100644 --- a/drivers/irqchip/irq-bcm2836.c +++ b/drivers/irqchip/irq-bcm2836.c @@ -22,6 +22,9 @@ struct bcm2836_arm_irqchip_intc { static struct bcm2836_arm_irqchip_intc intc __read_mostly; +void __iomem *arm_local_intc; +EXPORT_SYMBOL_GPL(arm_local_intc); + static void bcm2836_arm_irqchip_mask_per_cpu_irq(unsigned int reg_offset, unsigned int bit, int cpu) @@ -84,6 +87,27 @@ static void bcm2836_arm_irqchip_unmask_gpu_irq(struct irq_data *d) { } +#ifdef CONFIG_ARM64 + +void bcm2836_arm_irqchip_spin_gpu_irq(void) +{ + u32 i; + void __iomem *gpurouting = (intc.base + LOCAL_GPU_ROUTING); + u32 routing_val = readl(gpurouting); + + for (i = 1; i <= 3; i++) { + u32 new_routing_val = (routing_val + i) & 3; + + if (cpu_active(new_routing_val)) { + writel(new_routing_val, gpurouting); + return; + } + } +} +EXPORT_SYMBOL(bcm2836_arm_irqchip_spin_gpu_irq); + +#endif + static struct irq_chip bcm2836_arm_irqchip_gpu = { .name = "bcm2836-gpu", .irq_mask = bcm2836_arm_irqchip_mask_gpu_irq, @@ -128,7 +152,7 @@ static int bcm2836_map(struct irq_domain *d, unsigned int irq, irq_set_percpu_devid(irq); irq_domain_set_info(d, irq, hw, chip, d->host_data, handle_percpu_devid_irq, NULL, NULL); - irq_set_status_flags(irq, IRQ_NOAUTOEN); + irq_set_status_flags(irq, IRQ_NOAUTOEN | IRQ_TYPE_LEVEL_LOW); return 0; } @@ -320,6 +344,8 @@ static int __init bcm2836_arm_irqchip_l1_intc_of_init(struct device_node *node, panic("%pOF: unable to map local interrupt registers\n", node); } + arm_local_intc = intc.base; + bcm2835_init_local_timer_frequency(); intc.domain = irq_domain_add_linear(node, LAST_IRQ + 1, diff --git a/drivers/irqchip/irq-brcmstb-l2.c b/drivers/irqchip/irq-brcmstb-l2.c index c988886917f739..986b1ea6194475 100644 --- a/drivers/irqchip/irq-brcmstb-l2.c +++ b/drivers/irqchip/irq-brcmstb-l2.c @@ -51,6 +51,16 @@ static const struct brcmstb_intc_init_params l2_lvl_intc_init = { .cpu_mask_clear = 0x0C }; +/* Register offsets in the 2711 L2 level interrupt controller */ +static const struct brcmstb_intc_init_params l2_2711_lvl_intc_init = { + .handler = handle_level_irq, + .cpu_status = 0x00, + .cpu_clear = 0x08, + .cpu_mask_status = 0x0c, + .cpu_mask_set = 0x10, + .cpu_mask_clear = 0x14 +}; + /* L2 intc private data structure */ struct brcmstb_l2_intc_data { struct irq_domain *domain; @@ -299,11 +309,18 @@ static int __init brcmstb_l2_lvl_intc_of_init(struct device_node *np, return brcmstb_l2_intc_of_init(np, parent, &l2_lvl_intc_init); } +static int __init brcmstb_l2_2711_lvl_intc_of_init(struct device_node *np, + struct device_node *parent) +{ + return brcmstb_l2_intc_of_init(np, parent, &l2_2711_lvl_intc_init); +} + IRQCHIP_PLATFORM_DRIVER_BEGIN(brcmstb_l2) IRQCHIP_MATCH("brcm,l2-intc", brcmstb_l2_edge_intc_of_init) IRQCHIP_MATCH("brcm,hif-spi-l2-intc", brcmstb_l2_edge_intc_of_init) IRQCHIP_MATCH("brcm,upg-aux-aon-l2-intc", brcmstb_l2_edge_intc_of_init) IRQCHIP_MATCH("brcm,bcm7271-l2-intc", brcmstb_l2_lvl_intc_of_init) +IRQCHIP_MATCH("brcm,bcm2711-l2-intc", brcmstb_l2_2711_lvl_intc_of_init) IRQCHIP_PLATFORM_DRIVER_END(brcmstb_l2) MODULE_DESCRIPTION("Broadcom STB generic L2 interrupt controller"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/leds/leds-gpio.c b/drivers/leds/leds-gpio.c index 4d1612d557c841..9e503b6cec57c6 100644 --- a/drivers/leds/leds-gpio.c +++ b/drivers/leds/leds-gpio.c @@ -52,8 +52,15 @@ static void gpio_led_set(struct led_classdev *led_cdev, led_dat->platform_gpio_blink_set(led_dat->gpiod, level, NULL, NULL); led_dat->blinking = 0; + } else if (led_dat->cdev.flags & SET_GPIO_INPUT) { + gpiod_direction_input(led_dat->gpiod); + led_dat->cdev.flags &= ~SET_GPIO_INPUT; + } else if (led_dat->cdev.flags & SET_GPIO_OUTPUT) { + gpiod_direction_output(led_dat->gpiod, level); + led_dat->cdev.flags &= ~SET_GPIO_OUTPUT; } else { - if (led_dat->can_sleep) + if (led_dat->can_sleep || + (led_dat->cdev.flags & (SET_GPIO_INPUT | SET_GPIO_OUTPUT) )) gpiod_set_value_cansleep(led_dat->gpiod, level); else gpiod_set_value(led_dat->gpiod, level); @@ -67,6 +74,13 @@ static int gpio_led_set_blocking(struct led_classdev *led_cdev, return 0; } +static enum led_brightness gpio_led_get(struct led_classdev *led_cdev) +{ + struct gpio_led_data *led_dat = + container_of(led_cdev, struct gpio_led_data, cdev); + return gpiod_get_value_cansleep(led_dat->gpiod) ? LED_FULL : LED_OFF; +} + static int gpio_blink_set(struct led_classdev *led_cdev, unsigned long *delay_on, unsigned long *delay_off) { @@ -96,6 +110,7 @@ static int create_gpio_led(const struct gpio_led *template, led_dat->platform_gpio_blink_set = blink_set; led_dat->cdev.blink_set = gpio_blink_set; } + led_dat->cdev.brightness_get = gpio_led_get; if (template->default_state == LEDS_GPIO_DEFSTATE_KEEP) { state = gpiod_get_value_cansleep(led_dat->gpiod); if (state < 0) diff --git a/drivers/leds/trigger/Kconfig b/drivers/leds/trigger/Kconfig index c11282a74b5ac3..c48c1c3e9e113c 100644 --- a/drivers/leds/trigger/Kconfig +++ b/drivers/leds/trigger/Kconfig @@ -113,6 +113,13 @@ config LEDS_TRIGGER_CAMERA This enables direct flash/torch on/off by the driver, kernel space. If unsure, say Y. +config LEDS_TRIGGER_INPUT + tristate "LED Input Trigger" + depends on LEDS_TRIGGERS + help + This allows the GPIOs assigned to be LEDs to be initialised to inputs. + If unsure, say Y. + config LEDS_TRIGGER_PANIC bool "LED Panic Trigger" help @@ -161,4 +168,15 @@ config LEDS_TRIGGER_INPUT_EVENTS When build as a module this driver will be called ledtrig-input-events. +config LEDS_TRIGGER_ACTPWR + tristate "ACT/PWR Input Trigger" + depends on LEDS_TRIGGERS + help + This trigger is intended for platforms that have one software- + controllable LED and no dedicated activity or power LEDs, hence the + need to make the one LED perform both functions. It cycles between + default-on and an inverted mmc0 every 500ms, guaranteeing that it is + on for at least half of the time. + If unsure, say N. + endif # LEDS_TRIGGERS diff --git a/drivers/leds/trigger/Makefile b/drivers/leds/trigger/Makefile index 3b3628889f6893..c17cbd1939d574 100644 --- a/drivers/leds/trigger/Makefile +++ b/drivers/leds/trigger/Makefile @@ -11,8 +11,10 @@ obj-$(CONFIG_LEDS_TRIGGER_ACTIVITY) += ledtrig-activity.o obj-$(CONFIG_LEDS_TRIGGER_DEFAULT_ON) += ledtrig-default-on.o obj-$(CONFIG_LEDS_TRIGGER_TRANSIENT) += ledtrig-transient.o obj-$(CONFIG_LEDS_TRIGGER_CAMERA) += ledtrig-camera.o +obj-$(CONFIG_LEDS_TRIGGER_INPUT) += ledtrig-input.o obj-$(CONFIG_LEDS_TRIGGER_PANIC) += ledtrig-panic.o obj-$(CONFIG_LEDS_TRIGGER_NETDEV) += ledtrig-netdev.o obj-$(CONFIG_LEDS_TRIGGER_PATTERN) += ledtrig-pattern.o obj-$(CONFIG_LEDS_TRIGGER_TTY) += ledtrig-tty.o obj-$(CONFIG_LEDS_TRIGGER_INPUT_EVENTS) += ledtrig-input-events.o +obj-$(CONFIG_LEDS_TRIGGER_ACTPWR) += ledtrig-actpwr.o diff --git a/drivers/leds/trigger/ledtrig-actpwr.c b/drivers/leds/trigger/ledtrig-actpwr.c new file mode 100644 index 00000000000000..1a52107ceb03bb --- /dev/null +++ b/drivers/leds/trigger/ledtrig-actpwr.c @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Activity/power trigger + * + * Copyright (C) 2020 Raspberry Pi (Trading) Ltd. + * + * Based on Atsushi Nemoto's ledtrig-heartbeat.c, although there may be + * nothing left of the original now. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/timer.h> +#include <linux/leds.h> +#include "../leds.h" + +enum { + TRIG_ACT, + TRIG_PWR, + + TRIG_COUNT +}; + +struct actpwr_trig_src { + const char *name; + int interval; + bool invert; +}; + +struct actpwr_vled { + struct led_classdev cdev; + struct actpwr_trig_data *parent; + enum led_brightness value; + unsigned int interval; + bool invert; +}; + +struct actpwr_trig_data { + struct led_trigger trig; + struct actpwr_vled virt_leds[TRIG_COUNT]; + struct actpwr_vled *active; + struct timer_list timer; + int next_active; +}; + +static int actpwr_trig_activate(struct led_classdev *led_cdev); +static void actpwr_trig_deactivate(struct led_classdev *led_cdev); + +static const struct actpwr_trig_src actpwr_trig_sources[TRIG_COUNT] = { + [TRIG_ACT] = { "mmc0", 500, true }, + [TRIG_PWR] = { "default-on", 500, false }, +}; + +static struct actpwr_trig_data actpwr_data = { + { + .name = "actpwr", + .activate = actpwr_trig_activate, + .deactivate = actpwr_trig_deactivate, + } +}; + +static void actpwr_brightness_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct actpwr_vled *vled = container_of(led_cdev, struct actpwr_vled, + cdev); + struct actpwr_trig_data *trig = vled->parent; + + if (vled->invert) + value = !value; + vled->value = value; + + if (vled == trig->active) + led_trigger_event(&trig->trig, value); +} + +static int actpwr_brightness_set_blocking(struct led_classdev *led_cdev, + enum led_brightness value) +{ + actpwr_brightness_set(led_cdev, value); + return 0; +} + +static enum led_brightness actpwr_brightness_get(struct led_classdev *led_cdev) +{ + struct actpwr_vled *vled = container_of(led_cdev, struct actpwr_vled, + cdev); + + return vled->value; +} + +static void actpwr_trig_cycle(struct timer_list *t) +{ + struct actpwr_trig_data *trig = &actpwr_data; + struct actpwr_vled *active; + + active = &trig->virt_leds[trig->next_active]; + trig->active = active; + trig->next_active = (trig->next_active + 1) % TRIG_COUNT; + + led_trigger_event(&trig->trig, active->value); + + mod_timer(&trig->timer, jiffies + msecs_to_jiffies(active->interval)); +} + +static int actpwr_trig_activate(struct led_classdev *led_cdev) +{ + struct actpwr_trig_data *trig = &actpwr_data; + + /* Start the timer if this is the first LED */ + if (!trig->active) + actpwr_trig_cycle(&trig->timer); + else + led_set_brightness_nosleep(led_cdev, trig->active->value); + + return 0; +} + +static void actpwr_trig_deactivate(struct led_classdev *led_cdev) +{ + struct actpwr_trig_data *trig = &actpwr_data; + + if (list_empty(&trig->trig.led_cdevs)) { + del_timer_sync(&trig->timer); + trig->active = NULL; + } +} + +static int __init actpwr_trig_init(void) +{ + struct actpwr_trig_data *trig = &actpwr_data; + int ret = 0; + int i; + + timer_setup(&trig->timer, actpwr_trig_cycle, 0); + + /* Register one "LED" for each source trigger */ + for (i = 0; i < TRIG_COUNT; i++) + { + struct actpwr_vled *vled = &trig->virt_leds[i]; + struct led_classdev *cdev = &vled->cdev; + const struct actpwr_trig_src *src = &actpwr_trig_sources[i]; + + vled->parent = trig; + vled->interval = src->interval; + vled->invert = src->invert; + cdev->name = src->name; + cdev->brightness_set = actpwr_brightness_set; + cdev->brightness_set_blocking = actpwr_brightness_set_blocking; + cdev->brightness_get = actpwr_brightness_get; + cdev->default_trigger = src->name; + ret = led_classdev_register(NULL, cdev); + if (ret) + goto error_classdev; + } + + ret = led_trigger_register(&trig->trig); + if (ret) + goto error_classdev; + + return 0; + +error_classdev: + while (i > 0) + { + i--; + led_classdev_unregister(&trig->virt_leds[i].cdev); + } + + return ret; +} + +static void __exit actpwr_trig_exit(void) +{ + int i; + + led_trigger_unregister(&actpwr_data.trig); + for (i = 0; i < TRIG_COUNT; i++) + { + led_classdev_unregister(&actpwr_data.virt_leds[i].cdev); + } +} + +module_init(actpwr_trig_init); +module_exit(actpwr_trig_exit); + +MODULE_AUTHOR("Phil Elwell <phil@raspberrypi.com>"); +MODULE_DESCRIPTION("ACT/PWR LED trigger"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/leds/trigger/ledtrig-input.c b/drivers/leds/trigger/ledtrig-input.c new file mode 100644 index 00000000000000..8a974a35565649 --- /dev/null +++ b/drivers/leds/trigger/ledtrig-input.c @@ -0,0 +1,55 @@ +/* + * Set LED GPIO to Input "Trigger" + * + * Copyright 2015 Phil Elwell <phil@raspberrypi.org> + * + * Based on Nick Forbes's ledtrig-default-on.c. + * + * 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 <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/leds.h> +#include <linux/gpio.h> +#include "../leds.h" + +static int input_trig_activate(struct led_classdev *led_cdev) +{ + led_cdev->flags |= SET_GPIO_INPUT; + led_set_brightness(led_cdev, 0); + return 0; +} + +static void input_trig_deactivate(struct led_classdev *led_cdev) +{ + led_cdev->flags |= SET_GPIO_OUTPUT; + led_set_brightness(led_cdev, 0); +} + +static struct led_trigger input_led_trigger = { + .name = "input", + .activate = input_trig_activate, + .deactivate = input_trig_deactivate, +}; + +static int __init input_trig_init(void) +{ + return led_trigger_register(&input_led_trigger); +} + +static void __exit input_trig_exit(void) +{ + led_trigger_unregister(&input_led_trigger); +} + +module_init(input_trig_init); +module_exit(input_trig_exit); + +MODULE_AUTHOR("Phil Elwell <phil@raspberrypi.org>"); +MODULE_DESCRIPTION("Set LED GPIO to Input \"trigger\""); +MODULE_LICENSE("GPL"); diff --git a/drivers/mailbox/Kconfig b/drivers/mailbox/Kconfig index 6fb995778636a3..916177e6b4f5b9 100644 --- a/drivers/mailbox/Kconfig +++ b/drivers/mailbox/Kconfig @@ -295,4 +295,13 @@ config QCOM_IPCC acts as an interrupt controller for receiving interrupts from clients. Say Y here if you want to build this driver. +config MBOX_RP1 + tristate "RP1 Mailbox" + depends on MFD_RP1 + help + An implementation of a mailbox interface to the Raspberry Pi RP1 I/O + interface. Although written as a mailbox driver, the hardware only + provides an array of 32 doorbells. + Say Y here if you want to use the RP1 Mailbox. + endif diff --git a/drivers/mailbox/Makefile b/drivers/mailbox/Makefile index 3c3c27d54c13de..e76d8e1c371048 100644 --- a/drivers/mailbox/Makefile +++ b/drivers/mailbox/Makefile @@ -64,3 +64,5 @@ obj-$(CONFIG_SPRD_MBOX) += sprd-mailbox.o obj-$(CONFIG_QCOM_CPUCP_MBOX) += qcom-cpucp-mbox.o obj-$(CONFIG_QCOM_IPCC) += qcom-ipcc.o + +obj-$(CONFIG_MBOX_RP1) += rp1-mailbox.o diff --git a/drivers/mailbox/bcm2835-mailbox.c b/drivers/mailbox/bcm2835-mailbox.c index ea12fb8d24015c..8c54980b4b7e12 100644 --- a/drivers/mailbox/bcm2835-mailbox.c +++ b/drivers/mailbox/bcm2835-mailbox.c @@ -45,12 +45,15 @@ #define MAIL1_WRT (ARM_0_MAIL1 + 0x00) #define MAIL1_STA (ARM_0_MAIL1 + 0x18) +/* On ARCH_BCM270x these come through <linux/interrupt.h> (arm_control.h ) */ +#ifndef ARM_MS_FULL /* Status register: FIFO state. */ #define ARM_MS_FULL BIT(31) #define ARM_MS_EMPTY BIT(30) /* Configuration register: Enable interrupts. */ #define ARM_MC_IHAVEDATAIRQEN BIT(0) +#endif struct bcm2835_mbox { void __iomem *regs; @@ -144,7 +147,7 @@ static int bcm2835_mbox_probe(struct platform_device *pdev) return -ENOMEM; spin_lock_init(&mbox->lock); - ret = devm_request_irq(dev, irq_of_parse_and_map(dev->of_node, 0), + ret = devm_request_irq(dev, platform_get_irq(pdev, 0), bcm2835_mbox_irq, IRQF_NO_SUSPEND, dev_name(dev), mbox); if (ret) { @@ -193,7 +196,18 @@ static struct platform_driver bcm2835_mbox_driver = { }, .probe = bcm2835_mbox_probe, }; -module_platform_driver(bcm2835_mbox_driver); + +static int __init bcm2835_mbox_init(void) +{ + return platform_driver_register(&bcm2835_mbox_driver); +} +arch_initcall(bcm2835_mbox_init); + +static void __init bcm2835_mbox_exit(void) +{ + platform_driver_unregister(&bcm2835_mbox_driver); +} +module_exit(bcm2835_mbox_exit); MODULE_AUTHOR("Lubomir Rintel <lkundrak@v3.sk>"); MODULE_DESCRIPTION("BCM2835 mailbox IPC driver"); diff --git a/drivers/mailbox/rp1-mailbox.c b/drivers/mailbox/rp1-mailbox.c new file mode 100644 index 00000000000000..0e8af098b62b22 --- /dev/null +++ b/drivers/mailbox/rp1-mailbox.c @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2023 Raspberry Pi Ltd. + * + * Parts of this driver are based on: + * - bcm2835-mailbox.c + * Copyright (C) 2010,2015 Broadcom + * Copyright (C) 2013-2014 Lubomir Rintel + * Copyright (C) 2013 Craig McGeachie + */ + +#include <linux/compat.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/kernel.h> +#include <linux/mailbox_controller.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/platform_device.h> + +/* + * RP1's PROC_EVENTS register can generate interrupts on the M3 cores (when + * enabled). The 32-bit register is treated as 32 events, all of which share a + * common interrupt. HOST_EVENTS is the same in the reverse direction. + */ +#define SYSCFG_PROC_EVENTS 0x00000008 +#define SYSCFG_HOST_EVENTS 0x0000000c +#define SYSCFG_HOST_EVENT_IRQ_EN 0x00000010 +#define SYSCFG_HOST_EVENT_IRQ 0x00000014 + +#define HW_SET_BITS 0x00002000 +#define HW_CLR_BITS 0x00003000 + +#define MAX_CHANS 4 /* 32 is the hardware limit */ + +struct rp1_mbox { + void __iomem *regs; + unsigned int irq; + struct mbox_controller controller; +}; + +static struct rp1_mbox *rp1_chan_mbox(struct mbox_chan *chan) +{ + return container_of(chan->mbox, struct rp1_mbox, controller); +} + +static unsigned int rp1_chan_event(struct mbox_chan *chan) +{ + return (unsigned int)(uintptr_t)chan->con_priv; +} + +static irqreturn_t rp1_mbox_irq(int irq, void *dev_id) +{ + struct rp1_mbox *mbox = dev_id; + struct mbox_chan *chan; + unsigned int doorbell; + unsigned int evs; + + evs = readl(mbox->regs + SYSCFG_HOST_EVENT_IRQ); + writel(evs, mbox->regs + SYSCFG_HOST_EVENTS + HW_CLR_BITS); + + while (evs) { + doorbell = __ffs(evs); + chan = &mbox->controller.chans[doorbell]; + mbox_chan_received_data(chan, NULL); + evs &= ~(1 << doorbell); + } + return IRQ_HANDLED; +} + +static int rp1_send_data(struct mbox_chan *chan, void *data) +{ + struct rp1_mbox *mbox = rp1_chan_mbox(chan); + unsigned int event = rp1_chan_event(chan); + + writel(event, mbox->regs + SYSCFG_PROC_EVENTS + HW_SET_BITS); + + return 0; +} + +static int rp1_startup(struct mbox_chan *chan) +{ + struct rp1_mbox *mbox = rp1_chan_mbox(chan); + unsigned int event = rp1_chan_event(chan); + + writel(event, mbox->regs + SYSCFG_HOST_EVENT_IRQ_EN + HW_SET_BITS); + + return 0; +} + +static void rp1_shutdown(struct mbox_chan *chan) +{ + struct rp1_mbox *mbox = rp1_chan_mbox(chan); + unsigned int event = rp1_chan_event(chan); + + writel(event, mbox->regs + SYSCFG_HOST_EVENT_IRQ_EN + HW_CLR_BITS); +} + +static bool rp1_last_tx_done(struct mbox_chan *chan) +{ + struct rp1_mbox *mbox = rp1_chan_mbox(chan); + unsigned int event = rp1_chan_event(chan); + unsigned int evs; + + evs = readl(mbox->regs + SYSCFG_HOST_EVENT_IRQ); + + return !(evs & event); +} + +static const struct mbox_chan_ops rp1_mbox_chan_ops = { + .send_data = rp1_send_data, + .startup = rp1_startup, + .shutdown = rp1_shutdown, + .last_tx_done = rp1_last_tx_done +}; + +static struct mbox_chan *rp1_mbox_xlate(struct mbox_controller *mbox, + const struct of_phandle_args *spec) +{ + struct mbox_chan *chan; + unsigned int doorbell; + + if (spec->args_count != 1) + return ERR_PTR(-EINVAL); + + doorbell = spec->args[0]; + if (doorbell >= MAX_CHANS) + return ERR_PTR(-EINVAL); + + chan = &mbox->chans[doorbell]; + + chan->con_priv = (void *)(uintptr_t)(1 << doorbell); + + return chan; +} + +static int rp1_mbox_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mbox_chan *chans; + struct rp1_mbox *mbox; + int ret = 0; + + mbox = devm_kzalloc(dev, sizeof(*mbox), GFP_KERNEL); + if (mbox == NULL) + return -ENOMEM; + + ret = devm_request_irq(dev, platform_get_irq(pdev, 0), + rp1_mbox_irq, 0, dev_name(dev), mbox); + if (ret) { + dev_err(dev, "Failed to register a mailbox IRQ handler: %d\n", + ret); + return -ENODEV; + } + + mbox->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(mbox->regs)) { + ret = PTR_ERR(mbox->regs); + return ret; + } + + chans = devm_kcalloc(dev, MAX_CHANS, sizeof(*chans), GFP_KERNEL); + if (!chans) + return -ENOMEM; + + mbox->controller.txdone_poll = true; + mbox->controller.txpoll_period = 5; + mbox->controller.ops = &rp1_mbox_chan_ops; + mbox->controller.of_xlate = &rp1_mbox_xlate; + mbox->controller.dev = dev; + mbox->controller.num_chans = MAX_CHANS; + mbox->controller.chans = chans; + + ret = devm_mbox_controller_register(dev, &mbox->controller); + if (ret) + return ret; + + platform_set_drvdata(pdev, mbox); + + return 0; +} + +static const struct of_device_id rp1_mbox_of_match[] = { + { .compatible = "raspberrypi,rp1-mbox", }, + {}, +}; +MODULE_DEVICE_TABLE(of, rp1_mbox_of_match); + +static struct platform_driver rp1_mbox_driver = { + .driver = { + .name = "rp1-mbox", + .of_match_table = rp1_mbox_of_match, + }, + .probe = rp1_mbox_probe, +}; + +module_platform_driver(rp1_mbox_driver); + +MODULE_AUTHOR("Phil Elwell <phil@raspberrypi.com>"); +MODULE_DESCRIPTION("RP1 mailbox IPC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/common/videobuf2/videobuf2-core.c b/drivers/media/common/videobuf2/videobuf2-core.c index b0523fc23506ac..4ebc327e296362 100644 --- a/drivers/media/common/videobuf2/videobuf2-core.c +++ b/drivers/media/common/videobuf2/videobuf2-core.c @@ -2424,11 +2424,11 @@ static int __find_plane_by_offset(struct vb2_queue *q, unsigned long offset, return 0; } -int vb2_core_expbuf(struct vb2_queue *q, int *fd, unsigned int type, - struct vb2_buffer *vb, unsigned int plane, unsigned int flags) +int vb2_core_expbuf_dmabuf(struct vb2_queue *q, unsigned int type, + struct vb2_buffer *vb, unsigned int plane, + unsigned int flags, struct dma_buf **dmabuf) { struct vb2_plane *vb_plane; - int ret; struct dma_buf *dbuf; if (q->memory != VB2_MEMORY_MMAP) { @@ -2473,6 +2473,21 @@ int vb2_core_expbuf(struct vb2_queue *q, int *fd, unsigned int type, return -EINVAL; } + *dmabuf = dbuf; + return 0; +} +EXPORT_SYMBOL_GPL(vb2_core_expbuf_dmabuf); + +int vb2_core_expbuf(struct vb2_queue *q, int *fd, unsigned int type, + struct vb2_buffer *vb, unsigned int plane, unsigned int flags) +{ + struct dma_buf *dbuf; + int ret; + + ret = vb2_core_expbuf_dmabuf(q, type, vb, plane, flags, &dbuf); + if (ret) + return ret; + ret = dma_buf_fd(dbuf, flags & ~O_ACCMODE); if (ret < 0) { dprintk(q, 3, "buffer %d, plane %d failed to export (%d)\n", diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 8ba096b8ebca24..989806018e4d73 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -100,6 +100,28 @@ config VIDEO_GC2145 To compile this driver as a module, choose M here: the module will be called gc2145. +config VIDEO_ARDUCAM_64MP + tristate "Arducam 64MP sensor support" + depends on I2C && VIDEO_DEV + select VIDEO_V4L2_SUBDEV_API + help + This is a Video4Linux2 sensor driver for the Arducam + 64MP camera. + + To compile this driver as a module, choose M here: the + module will be called arducam_64mp. + +config VIDEO_ARDUCAM_PIVARIETY + tristate "Arducam Pivariety sensor support" + depends on I2C && VIDEO_DEV + select VIDEO_V4L2_SUBDEV_API + help + This is a Video4Linux2 sensor driver for the Arducam + Pivariety camera series. + + To compile this driver as a module, choose M here: the + module will be called arducam-pivariety. + config VIDEO_HI556 tristate "Hynix Hi-556 sensor support" help @@ -264,6 +286,55 @@ config VIDEO_IMX415 To compile this driver as a module, choose M here: the module will be called imx415. +config VIDEO_IMX477 + tristate "Sony IMX477 sensor support" + depends on I2C && VIDEO_DEV + select VIDEO_V4L2_SUBDEV_API + select MEDIA_CONTROLLER + select V4L2_FWNODE + help + This is a Video4Linux2 sensor driver for the Sony + IMX477 camera. Also supports the Sony IMX378. + + To compile this driver as a module, choose M here: the + module will be called imx477. + +config VIDEO_IMX500 + tristate "Sony IMX500 sensor support" + depends on I2C && VIDEO_DEV + select VIDEO_V4L2_SUBDEV_API + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for the Sony + IMX500 camera. + + To compile this driver as a module, choose M here: the + module will be called IMX500. + +config VIDEO_IMX519 + tristate "Arducam IMX519 sensor support" + depends on I2C && VIDEO_DEV + select VIDEO_V4L2_SUBDEV_API + help + This is a Video4Linux2 sensor driver for the Arducam + IMX519 camera. + + To compile this driver as a module, choose M here: the + module will be called IMX519. + +config VIDEO_IMX708 + tristate "Sony IMX708 sensor support" + depends on I2C && VIDEO_DEV + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + This is a Video4Linux2 sensor driver for the Sony + IMX708 camera. + + To compile this driver as a module, choose M here: the + module will be called imx708. + config VIDEO_MAX9271_LIB tristate @@ -385,6 +456,16 @@ config VIDEO_OV13B10 This is a Video4Linux2 sensor driver for the OmniVision OV13B10 camera. +config VIDEO_OV2311 + tristate "OmniVision OV2311 sensor support" + depends on I2C && VIDEO_DEV + help + This is a Video4Linux2 sensor-level driver for the OmniVision + OV2311 camera. + + To compile this driver as a module, choose M here: the + module will be called ov2311. + config VIDEO_OV2640 tristate "OmniVision OV2640 sensor support" help @@ -724,6 +805,13 @@ endmenu menu "Lens drivers" visible if MEDIA_CAMERA_SUPPORT +config VIDEO_AD5398 + tristate "AD5398 lens voice coil support" + depends on GPIOLIB && I2C && VIDEO_DEV + select MEDIA_CONTROLLER + help + This is a driver for the AD5398 camera lens voice coil. + config VIDEO_AD5820 tristate "AD5820 lens voice coil support" depends on GPIOLIB && I2C && VIDEO_DEV @@ -745,6 +833,19 @@ config VIDEO_AK7375 capability. This is designed for linear control of voice coil motors, controlled via I2C serial interface. +config VIDEO_BU64754 + tristate "BU64754 Motor Driver for Camera Autofocus" + depends on I2C && VIDEO_DEV + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_ASYNC + select V4L2_CCI_I2C + help + This is a driver for the BU64754 Motor Driver for Camera + Autofocus. The BU64754GWZ is an actuator driver IC which + can be controlled the actuator position precisely using + with internal Hall Sensor. + config VIDEO_DW9714 tristate "DW9714 lens voice coil support" depends on I2C && VIDEO_DEV @@ -1324,6 +1425,18 @@ config VIDEO_TW9910 To compile this driver as a module, choose M here: the module will be called tw9910. +config VIDEO_IRS1125 + tristate "Infineon IRS1125 sensor support" + depends on I2C && VIDEO_DEV + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + This is a Video4Linux2 sensor-level driver for the Infineon + IRS1125 camera. + + To compile this driver as a module, choose M here: the + module will be called irs1125. + config VIDEO_VPX3220 tristate "vpx3220a, vpx3216b & vpx3214c video decoders" depends on VIDEO_DEV && I2C diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index fbb988bd067a1b..edcf0776d5eef0 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -3,6 +3,7 @@ msp3400-objs := msp3400-driver.o msp3400-kthreads.o obj-$(CONFIG_SDR_MAX2175) += max2175.o +obj-$(CONFIG_VIDEO_AD5398) += ad5398_vcm.o obj-$(CONFIG_VIDEO_AD5820) += ad5820.o obj-$(CONFIG_VIDEO_ADP1653) += adp1653.o obj-$(CONFIG_VIDEO_ADV7170) += adv7170.o @@ -20,9 +21,12 @@ obj-$(CONFIG_VIDEO_AK881X) += ak881x.o obj-$(CONFIG_VIDEO_ALVIUM_CSI2) += alvium-csi2.o obj-$(CONFIG_VIDEO_APTINA_PLL) += aptina-pll.o obj-$(CONFIG_VIDEO_AR0521) += ar0521.o +obj-$(CONFIG_VIDEO_ARDUCAM_64MP) += arducam_64mp.o +obj-$(CONFIG_VIDEO_ARDUCAM_PIVARIETY) += arducam-pivariety.o obj-$(CONFIG_VIDEO_BT819) += bt819.o obj-$(CONFIG_VIDEO_BT856) += bt856.o obj-$(CONFIG_VIDEO_BT866) += bt866.o +obj-$(CONFIG_VIDEO_BU64754) += bu64754.o obj-$(CONFIG_VIDEO_CCS) += ccs/ obj-$(CONFIG_VIDEO_CCS_PLL) += ccs-pll.o obj-$(CONFIG_VIDEO_CS3308) += cs3308.o @@ -59,7 +63,12 @@ obj-$(CONFIG_VIDEO_IMX335) += imx335.o obj-$(CONFIG_VIDEO_IMX355) += imx355.o obj-$(CONFIG_VIDEO_IMX412) += imx412.o obj-$(CONFIG_VIDEO_IMX415) += imx415.o +obj-$(CONFIG_VIDEO_IMX477) += imx477.o +obj-$(CONFIG_VIDEO_IMX500) += imx500.o +obj-$(CONFIG_VIDEO_IMX519) += imx519.o +obj-$(CONFIG_VIDEO_IMX708) += imx708.o obj-$(CONFIG_VIDEO_IR_I2C) += ir-kbd-i2c.o +obj-$(CONFIG_VIDEO_IRS1125) += irs1125.o obj-$(CONFIG_VIDEO_ISL7998X) += isl7998x.o obj-$(CONFIG_VIDEO_KS0127) += ks0127.o obj-$(CONFIG_VIDEO_LM3560) += lm3560.o @@ -86,6 +95,7 @@ obj-$(CONFIG_VIDEO_OV08D10) += ov08d10.o obj-$(CONFIG_VIDEO_OV08X40) += ov08x40.o obj-$(CONFIG_VIDEO_OV13858) += ov13858.o obj-$(CONFIG_VIDEO_OV13B10) += ov13b10.o +obj-$(CONFIG_VIDEO_OV2311) += ov2311.o obj-$(CONFIG_VIDEO_OV2640) += ov2640.o obj-$(CONFIG_VIDEO_OV2659) += ov2659.o obj-$(CONFIG_VIDEO_OV2680) += ov2680.o diff --git a/drivers/media/i2c/ad5398_vcm.c b/drivers/media/i2c/ad5398_vcm.c new file mode 100644 index 00000000000000..649ff0b9e9c812 --- /dev/null +++ b/drivers/media/i2c/ad5398_vcm.c @@ -0,0 +1,340 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AD5398 DAC driver for camera voice coil focus. + * Copyright (C) 2021 Raspberry Pi (Trading) Ltd. + * + * Based on AD5820 DAC driver by Nokia and TI. + * + * This driver uses the regulator framework notification hooks on the + * assumption that the VCM and sensor share a regulator. This means the VCM + * position will be restored when either the sensor or VCM subdevices are opened + * or powered up. The client can therefore choose to ignore the VCM subdevice, + * and the lens position will be as previously requested. Without that, there + * is a hard requirement to have the VCM subdevice open in order for the VCM + * to be powered and at the requested position. + */ + +#include <linux/errno.h> +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/regulator/consumer.h> +#include <linux/gpio/consumer.h> + +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-subdev.h> + +/* Register definitions */ +#define AD5398_POWER_DOWN BIT(15) +#define AD5398_DAC_SHIFT 4 + +#define to_ad5398_device(sd) container_of(sd, struct ad5398_device, subdev) + +struct ad5398_device { + struct v4l2_subdev subdev; + struct ad5398_platform_data *platform_data; + struct regulator *vana; + struct notifier_block nb; + + struct v4l2_ctrl_handler ctrls; + u32 focus_absolute; + + bool standby; +}; + +static int ad5398_write(struct ad5398_device *coil, u16 data) +{ + struct i2c_client *client = v4l2_get_subdevdata(&coil->subdev); + struct i2c_msg msg; + __be16 be_data; + int r; + + if (!client->adapter) + return -ENODEV; + + be_data = cpu_to_be16(data); + msg.addr = client->addr; + msg.flags = 0; + msg.len = 2; + msg.buf = (u8 *)&be_data; + + r = i2c_transfer(client->adapter, &msg, 1); + if (r < 0) { + dev_err(&client->dev, "write failed, error %d\n", r); + return r; + } + + return 0; +} + +/* + * Calculate status word and write it to the device based on current + * values of V4L2 controls. It is assumed that the stored V4L2 control + * values are properly limited and rounded. + */ +static int ad5398_update_hw(struct ad5398_device *coil) +{ + u16 status; + + status = coil->focus_absolute << AD5398_DAC_SHIFT; + + if (coil->standby) + status |= AD5398_POWER_DOWN; + + return ad5398_write(coil, status); +} + +/* + * Power handling + */ +static int ad5398_power_off(struct ad5398_device *coil) +{ + int ret = 0; + + coil->standby = true; + ret = ad5398_update_hw(coil); + + return ret; +} + +static int ad5398_power_on(struct ad5398_device *coil) +{ + int ret; + + /* Restore the hardware settings. */ + coil->standby = false; + ret = ad5398_update_hw(coil); + if (ret) + goto fail; + + return 0; + +fail: + coil->standby = true; + + return ret; +} + +/* + * V4L2 controls + */ +static int ad5398_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ad5398_device *coil = + container_of(ctrl->handler, struct ad5398_device, ctrls); + + switch (ctrl->id) { + case V4L2_CID_FOCUS_ABSOLUTE: + coil->focus_absolute = ctrl->val; + return ad5398_update_hw(coil); + } + + return 0; +} + +static const struct v4l2_ctrl_ops ad5398_ctrl_ops = { + .s_ctrl = ad5398_set_ctrl, +}; + +static int ad5398_init_controls(struct ad5398_device *coil) +{ + v4l2_ctrl_handler_init(&coil->ctrls, 1); + + /* + * V4L2_CID_FOCUS_ABSOLUTE + * + * Minimum current is 0 mA, maximum is 120 mA. Thus, 1 code is + * equivalent to 120/1023 = 0.1173 mA. Nevertheless, we do not use [mA] + * for focus position, because it is meaningless for user. Meaningful + * would be to use focus distance or even its inverse, but since the + * driver doesn't have sufficient knowledge to do the conversion, we + * will just use abstract codes here. In any case, smaller value = focus + * position farther from camera. The default zero value means focus at + * infinity, and also least current consumption. + */ + v4l2_ctrl_new_std(&coil->ctrls, &ad5398_ctrl_ops, + V4L2_CID_FOCUS_ABSOLUTE, 0, 1023, 1, 0); + + if (coil->ctrls.error) + return coil->ctrls.error; + + coil->focus_absolute = 0; + + coil->subdev.ctrl_handler = &coil->ctrls; + + return 0; +} + +/* + * V4L2 subdev operations + */ +static int ad5398_registered(struct v4l2_subdev *subdev) +{ + struct ad5398_device *coil = to_ad5398_device(subdev); + + return ad5398_init_controls(coil); +} + +static int +ad5398_set_power(struct v4l2_subdev *subdev, int on) +{ + struct ad5398_device *coil = to_ad5398_device(subdev); + int ret; + + if (on) + ret = regulator_enable(coil->vana); + else + ret = regulator_disable(coil->vana); + + return ret; +} + +static int ad5398_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct ad5398_device *coil = to_ad5398_device(sd); + + return regulator_enable(coil->vana); +} + +static int ad5398_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct ad5398_device *coil = to_ad5398_device(sd); + + return regulator_disable(coil->vana); +} + +static const struct v4l2_subdev_core_ops ad5398_core_ops = { + .s_power = ad5398_set_power, +}; + +static const struct v4l2_subdev_ops ad5398_ops = { + .core = &ad5398_core_ops, +}; + +static const struct v4l2_subdev_internal_ops ad5398_internal_ops = { + .registered = ad5398_registered, + .open = ad5398_open, + .close = ad5398_close, +}; + +/* + * I2C driver + */ +static int __maybe_unused ad5398_suspend(struct device *dev) +{ + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct ad5398_device *coil = to_ad5398_device(subdev); + + return regulator_enable(coil->vana); +} + +static int __maybe_unused ad5398_resume(struct device *dev) +{ + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct ad5398_device *coil = to_ad5398_device(subdev); + + return regulator_disable(coil->vana); +} + +static int ad5398_regulator_notifier(struct notifier_block *nb, + unsigned long event, + void *ignored) +{ + struct ad5398_device *coil = container_of(nb, struct ad5398_device, nb); + + if (event == REGULATOR_EVENT_ENABLE) + ad5398_power_on(coil); + else if (event == REGULATOR_EVENT_PRE_DISABLE) + ad5398_power_off(coil); + + return NOTIFY_OK; +} + +static int ad5398_probe(struct i2c_client *client) +{ + struct ad5398_device *coil; + int ret; + + coil = devm_kzalloc(&client->dev, sizeof(*coil), GFP_KERNEL); + if (!coil) + return -ENOMEM; + + coil->vana = devm_regulator_get(&client->dev, "VANA"); + if (IS_ERR(coil->vana)) { + ret = PTR_ERR(coil->vana); + if (ret != -EPROBE_DEFER) + dev_err(&client->dev, "could not get regulator for vana\n"); + return ret; + } + + v4l2_i2c_subdev_init(&coil->subdev, client, &ad5398_ops); + coil->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + coil->subdev.internal_ops = &ad5398_internal_ops; + coil->subdev.entity.function = MEDIA_ENT_F_LENS; + strscpy(coil->subdev.name, "ad5398 focus", sizeof(coil->subdev.name)); + + coil->nb.notifier_call = &ad5398_regulator_notifier; + ret = regulator_register_notifier(coil->vana, &coil->nb); + if (ret < 0) + return ret; + + ret = media_entity_pads_init(&coil->subdev.entity, 0, NULL); + if (ret < 0) + goto cleanup2; + + ret = v4l2_async_register_subdev(&coil->subdev); + if (ret < 0) + goto cleanup; + + return ret; + +cleanup: + media_entity_cleanup(&coil->subdev.entity); +cleanup2: + regulator_unregister_notifier(coil->vana, &coil->nb); + return ret; +} + +static void ad5398_remove(struct i2c_client *client) +{ + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct ad5398_device *coil = to_ad5398_device(subdev); + + v4l2_async_unregister_subdev(&coil->subdev); + v4l2_ctrl_handler_free(&coil->ctrls); + media_entity_cleanup(&coil->subdev.entity); +} + +static const struct i2c_device_id ad5398_id_table[] = { + { "ad5398", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ad5398_id_table); + +static const struct of_device_id ad5398_of_table[] = { + { .compatible = "adi,ad5398" }, + { } +}; +MODULE_DEVICE_TABLE(of, ad5398_of_table); + +static SIMPLE_DEV_PM_OPS(ad5398_pm, ad5398_suspend, ad5398_resume); + +static struct i2c_driver ad5398_i2c_driver = { + .driver = { + .name = "ad5398", + .pm = &ad5398_pm, + .of_match_table = ad5398_of_table, + }, + .probe = ad5398_probe, + .remove = ad5398_remove, + .id_table = ad5398_id_table, +}; + +module_i2c_driver(ad5398_i2c_driver); + +MODULE_AUTHOR("Dave Stevenson <dave.stevenson@raspberrypi.com>"); +MODULE_DESCRIPTION("AD5398 camera lens driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/adv7180.c b/drivers/media/i2c/adv7180.c index 819ff9f7c90fea..2ea54d8cfaeba2 100644 --- a/drivers/media/i2c/adv7180.c +++ b/drivers/media/i2c/adv7180.c @@ -189,6 +189,20 @@ /* Initial number of frames to skip to avoid possible garbage */ #define ADV7180_NUM_OF_SKIP_FRAMES 2 +enum adv7180_link_freq_idx { + INTERLACED_IDX, + I2P_IDX, +}; + +static const s64 adv7180_link_freqs[] = { + [INTERLACED_IDX] = 108000000, + [I2P_IDX] = 216000000, +}; + +static int dbg_input; +module_param(dbg_input, int, 0644); +MODULE_PARM_DESC(dbg_input, "Input number (0-31)"); + struct adv7180_state; #define ADV7180_FLAG_RESET_POWERED BIT(0) @@ -224,6 +238,7 @@ struct adv7180_state { const struct adv7180_chip_info *chip_info; enum v4l2_field field; bool force_bt656_4; + struct v4l2_ctrl *link_freq; }; #define to_adv7180_sd(_ctrl) (&container_of(_ctrl->handler, \ struct adv7180_state, \ @@ -407,10 +422,24 @@ static int adv7180_s_routing(struct v4l2_subdev *sd, u32 input, return ret; } +static void adv7180_check_input(struct v4l2_subdev *sd) +{ + struct adv7180_state *state = to_state(sd); + + if (state->input != dbg_input) + if (adv7180_s_routing(sd, dbg_input, 0, 0)) + /* Failed - reset dbg_input */ + dbg_input = state->input; +} + static int adv7180_g_input_status(struct v4l2_subdev *sd, u32 *status) { struct adv7180_state *state = to_state(sd); - int ret = mutex_lock_interruptible(&state->mutex); + int ret; + + adv7180_check_input(sd); + + ret = mutex_lock_interruptible(&state->mutex); if (ret) return ret; @@ -436,7 +465,11 @@ static int adv7180_program_std(struct adv7180_state *state) static int adv7180_s_std(struct v4l2_subdev *sd, v4l2_std_id std) { struct adv7180_state *state = to_state(sd); - int ret = mutex_lock_interruptible(&state->mutex); + int ret; + + adv7180_check_input(sd); + + ret = mutex_lock_interruptible(&state->mutex); if (ret) return ret; @@ -458,6 +491,8 @@ static int adv7180_g_std(struct v4l2_subdev *sd, v4l2_std_id *norm) { struct adv7180_state *state = to_state(sd); + adv7180_check_input(sd); + *norm = state->curr_norm; return 0; @@ -605,6 +640,9 @@ static int adv7180_s_ctrl(struct v4l2_ctrl *ctrl) if (ret) return ret; + if (ctrl->flags & V4L2_CTRL_FLAG_READ_ONLY) + goto unlock; + val = ctrl->val; switch (ctrl->id) { case V4L2_CID_BRIGHTNESS: @@ -646,6 +684,7 @@ static int adv7180_s_ctrl(struct v4l2_ctrl *ctrl) ret = -EINVAL; } +unlock: mutex_unlock(&state->mutex); return ret; } @@ -666,7 +705,7 @@ static const struct v4l2_ctrl_config adv7180_ctrl_fast_switch = { static int adv7180_init_controls(struct adv7180_state *state) { - v4l2_ctrl_handler_init(&state->ctrl_hdl, 4); + v4l2_ctrl_handler_init(&state->ctrl_hdl, 5); v4l2_ctrl_new_std(&state->ctrl_hdl, &adv7180_ctrl_ops, V4L2_CID_BRIGHTNESS, ADV7180_BRI_MIN, @@ -688,6 +727,17 @@ static int adv7180_init_controls(struct adv7180_state *state) 0, ARRAY_SIZE(test_pattern_menu) - 1, test_pattern_menu); + if (state->chip_info->flags & ADV7180_FLAG_MIPI_CSI2) { + state->link_freq = + v4l2_ctrl_new_int_menu(&state->ctrl_hdl, + &adv7180_ctrl_ops, + V4L2_CID_LINK_FREQ, + ARRAY_SIZE(adv7180_link_freqs) - 1, + 0, adv7180_link_freqs); + if (state->link_freq) + state->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + } + state->sd.ctrl_handler = &state->ctrl_hdl; if (state->ctrl_hdl.error) { int err = state->ctrl_hdl.error; @@ -708,10 +758,15 @@ static int adv7180_enum_mbus_code(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_mbus_code_enum *code) { + struct adv7180_state *state = to_state(sd); + if (code->index != 0) return -EINVAL; - code->code = MEDIA_BUS_FMT_UYVY8_2X8; + if (state->chip_info->flags & ADV7180_FLAG_MIPI_CSI2) + code->code = MEDIA_BUS_FMT_UYVY8_1X16; + else + code->code = MEDIA_BUS_FMT_UYVY8_2X8; return 0; } @@ -721,7 +776,10 @@ static int adv7180_mbus_fmt(struct v4l2_subdev *sd, { struct adv7180_state *state = to_state(sd); - fmt->code = MEDIA_BUS_FMT_UYVY8_2X8; + if (state->chip_info->flags & ADV7180_FLAG_MIPI_CSI2) + fmt->code = MEDIA_BUS_FMT_UYVY8_1X16; + else + fmt->code = MEDIA_BUS_FMT_UYVY8_2X8; fmt->colorspace = V4L2_COLORSPACE_SMPTE170M; fmt->width = 720; fmt->height = state->curr_norm & V4L2_STD_525_60 ? 480 : 576; @@ -812,6 +870,10 @@ static int adv7180_set_pad_format(struct v4l2_subdev *sd, adv7180_set_power(state, false); adv7180_set_field_mode(state); adv7180_set_power(state, true); + if (state->chip_info->flags & ADV7180_FLAG_MIPI_CSI2) + __v4l2_ctrl_s_ctrl(state->link_freq, + (state->field == V4L2_FIELD_NONE) ? + I2P_IDX : INTERLACED_IDX); } } else { framefmt = v4l2_subdev_state_get_format(sd_state, 0); @@ -895,6 +957,8 @@ static int adv7180_s_stream(struct v4l2_subdev *sd, int enable) return 0; } + adv7180_check_input(sd); + /* Must wait until querystd released the lock */ ret = mutex_lock_interruptible(&state->mutex); if (ret) @@ -1341,6 +1405,7 @@ static const struct adv7180_chip_info adv7282_m_info = { BIT(ADV7182_INPUT_SVIDEO_AIN1_AIN2) | BIT(ADV7182_INPUT_SVIDEO_AIN3_AIN4) | BIT(ADV7182_INPUT_SVIDEO_AIN7_AIN8) | + BIT(ADV7182_INPUT_YPRPB_AIN1_AIN2_AIN3) | BIT(ADV7182_INPUT_DIFF_CVBS_AIN1_AIN2) | BIT(ADV7182_INPUT_DIFF_CVBS_AIN3_AIN4) | BIT(ADV7182_INPUT_DIFF_CVBS_AIN7_AIN8), @@ -1352,6 +1417,7 @@ static const struct adv7180_chip_info adv7282_m_info = { static int init_device(struct adv7180_state *state) { int ret; + int i; mutex_lock(&state->mutex); @@ -1399,6 +1465,18 @@ static int init_device(struct adv7180_state *state) goto out_unlock; } + /* Select first valid input */ + for (i = 0; i < 32; i++) { + if (BIT(i) & state->chip_info->valid_input_mask) { + ret = state->chip_info->select_input(state, i); + + if (ret == 0) { + state->input = i; + break; + } + } + } + out_unlock: mutex_unlock(&state->mutex); diff --git a/drivers/media/i2c/arducam-pivariety.c b/drivers/media/i2c/arducam-pivariety.c new file mode 100644 index 00000000000000..945c462662dba0 --- /dev/null +++ b/drivers/media/i2c/arducam-pivariety.c @@ -0,0 +1,1475 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A V4L2 driver for Arducam Pivariety Cameras + * Copyright (C) 2022 Arducam Technology co., Ltd. + * + * Based on Sony IMX219 camera driver + * Copyright (C) 2019, Raspberry Pi (Trading) Ltd + * + * I2C read and write method is taken from the OV9281 driver + * Copyright (C) 2017 Fuzhou Rockchip Electronics Co., Ltd. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-event.h> +#include <media/v4l2-fwnode.h> +#include "arducam-pivariety.h" + +static int debug; +module_param(debug, int, 0644); + +/* regulator supplies */ +static const char * const pivariety_supply_name[] = { + /* Supplies can be enabled in any order */ + "VANA", /* Analog (2.8V) supply */ + "VDIG", /* Digital Core (1.8V) supply */ + "VDDL", /* IF (1.2V) supply */ +}; + +/* The supported raw formats. */ +static const u32 codes[] = { + MEDIA_BUS_FMT_SBGGR8_1X8, + MEDIA_BUS_FMT_SGBRG8_1X8, + MEDIA_BUS_FMT_SGRBG8_1X8, + MEDIA_BUS_FMT_SRGGB8_1X8, + MEDIA_BUS_FMT_Y8_1X8, + + MEDIA_BUS_FMT_SBGGR10_1X10, + MEDIA_BUS_FMT_SGBRG10_1X10, + MEDIA_BUS_FMT_SGRBG10_1X10, + MEDIA_BUS_FMT_SRGGB10_1X10, + MEDIA_BUS_FMT_Y10_1X10, + + MEDIA_BUS_FMT_SBGGR12_1X12, + MEDIA_BUS_FMT_SGBRG12_1X12, + MEDIA_BUS_FMT_SGRBG12_1X12, + MEDIA_BUS_FMT_SRGGB12_1X12, + MEDIA_BUS_FMT_Y12_1X12, +}; + +#define ARDUCAM_NUM_SUPPLIES ARRAY_SIZE(pivariety_supply_name) + +#define ARDUCAM_XCLR_MIN_DELAY_US 10000 +#define ARDUCAM_XCLR_DELAY_RANGE_US 1000 + +#define MAX_CTRLS 32 + +struct pivariety { + struct v4l2_subdev sd; + struct media_pad pad; + + struct v4l2_mbus_config_mipi_csi2 bus; + struct clk *xclk; + u32 xclk_freq; + + struct gpio_desc *reset_gpio; + struct regulator_bulk_data supplies[ARDUCAM_NUM_SUPPLIES]; + + struct arducam_format *supported_formats; + int num_supported_formats; + int current_format_idx; + int current_resolution_idx; + int lanes; + int bayer_order_volatile; + bool wait_until_free; + + struct v4l2_ctrl_handler ctrl_handler; + struct v4l2_ctrl *ctrls[MAX_CTRLS]; + /* V4L2 Controls */ + struct v4l2_ctrl *vflip; + struct v4l2_ctrl *hflip; + + struct v4l2_rect crop; + /* + * Mutex for serialized access: + * Protect sensor module set pad format and start/stop streaming safely. + */ + struct mutex mutex; + + /* Streaming on/off */ + bool streaming; +}; + +static inline struct pivariety *to_pivariety(struct v4l2_subdev *_sd) +{ + return container_of(_sd, struct pivariety, sd); +} + +/* Write registers up to 4 at a time */ +static int pivariety_write_reg(struct i2c_client *client, u16 reg, u32 val) +{ + unsigned int len = sizeof(u32); + u32 buf_i, val_i = 0; + u8 buf[6]; + u8 *val_p; + __be32 val_be; + + buf[0] = reg >> 8; + buf[1] = reg & 0xff; + + val_be = cpu_to_be32(val); + val_p = (u8 *)&val_be; + buf_i = 2; + + while (val_i < 4) + buf[buf_i++] = val_p[val_i++]; + + if (i2c_master_send(client, buf, len + 2) != len + 2) + return -EIO; + + return 0; +} + +/* Read registers up to 4 at a time */ +static int pivariety_read_reg(struct i2c_client *client, u16 reg, u32 *val) +{ + struct i2c_msg msgs[2]; + unsigned int len = sizeof(u32); + u8 *data_be_p; + __be32 data_be = 0; + __be16 reg_addr_be = cpu_to_be16(reg); + int ret; + + data_be_p = (u8 *)&data_be; + /* Write register address */ + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = 2; + msgs[0].buf = (u8 *)®_addr_be; + + /* Read data from register */ + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = data_be_p; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + *val = be32_to_cpu(data_be); + + return 0; +} + +static int +pivariety_read(struct pivariety *pivariety, u16 addr, u32 *value) +{ + struct v4l2_subdev *sd = &pivariety->sd; + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret, count = 0; + + while (count++ < I2C_READ_RETRY_COUNT) { + ret = pivariety_read_reg(client, addr, value); + if (!ret) { + v4l2_dbg(2, debug, sd, "%s: 0x%02x 0x%04x\n", + __func__, addr, *value); + return ret; + } + } + + v4l2_err(sd, "%s: Reading register 0x%02x failed\n", + __func__, addr); + + return ret; +} + +static int pivariety_write(struct pivariety *pivariety, u16 addr, u32 value) +{ + struct v4l2_subdev *sd = &pivariety->sd; + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret, count = 0; + + while (count++ < I2C_WRITE_RETRY_COUNT) { + ret = pivariety_write_reg(client, addr, value); + if (!ret) + return ret; + } + + v4l2_err(sd, "%s: Write 0x%04x to register 0x%02x failed\n", + __func__, value, addr); + + return ret; +} + +static int wait_for_free(struct pivariety *pivariety, int interval) +{ + u32 value; + u32 count = 0; + + while (count++ < (1000 / interval)) { + int ret = pivariety_read(pivariety, SYSTEM_IDLE_REG, &value); + + if (!ret && !value) + break; + msleep(interval); + } + + v4l2_dbg(2, debug, &pivariety->sd, "%s: End wait, Count: %d.\n", + __func__, count); + + return 0; +} + +static int is_raw(int pixformat) +{ + return pixformat >= 0x28 && pixformat <= 0x2D; +} + +static u32 bayer_to_mbus_code(int data_type, int bayer_order) +{ + const u32 depth8[] = { + MEDIA_BUS_FMT_SBGGR8_1X8, + MEDIA_BUS_FMT_SGBRG8_1X8, + MEDIA_BUS_FMT_SGRBG8_1X8, + MEDIA_BUS_FMT_SRGGB8_1X8, + MEDIA_BUS_FMT_Y8_1X8, + }; + + const u32 depth10[] = { + MEDIA_BUS_FMT_SBGGR10_1X10, + MEDIA_BUS_FMT_SGBRG10_1X10, + MEDIA_BUS_FMT_SGRBG10_1X10, + MEDIA_BUS_FMT_SRGGB10_1X10, + MEDIA_BUS_FMT_Y10_1X10, + }; + + const u32 depth12[] = { + MEDIA_BUS_FMT_SBGGR12_1X12, + MEDIA_BUS_FMT_SGBRG12_1X12, + MEDIA_BUS_FMT_SGRBG12_1X12, + MEDIA_BUS_FMT_SRGGB12_1X12, + MEDIA_BUS_FMT_Y12_1X12, + }; + + if (bayer_order < 0 || bayer_order > 4) + return 0; + + switch (data_type) { + case IMAGE_DT_RAW8: + return depth8[bayer_order]; + case IMAGE_DT_RAW10: + return depth10[bayer_order]; + case IMAGE_DT_RAW12: + return depth12[bayer_order]; + } + + return 0; +} + +static u32 yuv422_to_mbus_code(int data_type, int order) +{ + const u32 depth8[] = { + MEDIA_BUS_FMT_YUYV8_1X16, + MEDIA_BUS_FMT_YVYU8_1X16, + MEDIA_BUS_FMT_UYVY8_1X16, + MEDIA_BUS_FMT_VYUY8_1X16, + }; + + const u32 depth10[] = { + MEDIA_BUS_FMT_YUYV10_1X20, + MEDIA_BUS_FMT_YVYU10_1X20, + MEDIA_BUS_FMT_UYVY10_1X20, + MEDIA_BUS_FMT_VYUY10_1X20, + }; + + if (order < 0 || order > 3) + return 0; + + switch (data_type) { + case IMAGE_DT_YUV422_8: + return depth8[order]; + case IMAGE_DT_YUV422_10: + return depth10[order]; + } + + return 0; +} + +static u32 data_type_to_mbus_code(int data_type, int bayer_order) +{ + if (is_raw(data_type)) + return bayer_to_mbus_code(data_type, bayer_order); + + switch (data_type) { + case IMAGE_DT_YUV422_8: + case IMAGE_DT_YUV422_10: + return yuv422_to_mbus_code(data_type, bayer_order); + case IMAGE_DT_RGB565: + return MEDIA_BUS_FMT_RGB565_2X8_LE; + case IMAGE_DT_RGB888: + return MEDIA_BUS_FMT_RGB888_1X24; + } + + return 0; +} + +/* Get bayer order based on flip setting. */ +static u32 pivariety_get_format_code(struct pivariety *pivariety, + struct arducam_format *format) +{ + unsigned int order, origin_order; + + lockdep_assert_held(&pivariety->mutex); + + /* + * Only the bayer format needs to transform the format. + */ + if (!is_raw(format->data_type) || + !pivariety->bayer_order_volatile || + format->bayer_order == BAYER_ORDER_GRAY) + return data_type_to_mbus_code(format->data_type, + format->bayer_order); + + order = format->bayer_order; + + origin_order = order; + + order = (pivariety->hflip && pivariety->hflip->val ? order ^ 1 : order); + order = (pivariety->vflip && pivariety->vflip->val ? order ^ 2 : order); + + v4l2_dbg(1, debug, &pivariety->sd, "%s: before: %d, after: %d.\n", + __func__, origin_order, order); + + return data_type_to_mbus_code(format->data_type, order); +} + +/* Power/clock management functions */ +static int pivariety_power_on(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct pivariety *pivariety = to_pivariety(sd); + int ret; + + ret = regulator_bulk_enable(ARDUCAM_NUM_SUPPLIES, + pivariety->supplies); + if (ret) { + dev_err(dev, "%s: failed to enable regulators\n", + __func__); + return ret; + } + + ret = clk_prepare_enable(pivariety->xclk); + if (ret) { + dev_err(dev, "%s: failed to enable clock\n", + __func__); + goto reg_off; + } + + gpiod_set_value_cansleep(pivariety->reset_gpio, 1); + usleep_range(ARDUCAM_XCLR_MIN_DELAY_US, + ARDUCAM_XCLR_MIN_DELAY_US + ARDUCAM_XCLR_DELAY_RANGE_US); + + return 0; + +reg_off: + regulator_bulk_disable(ARDUCAM_NUM_SUPPLIES, pivariety->supplies); + + return ret; +} + +static int pivariety_power_off(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct pivariety *pivariety = to_pivariety(sd); + + gpiod_set_value_cansleep(pivariety->reset_gpio, 0); + regulator_bulk_disable(ARDUCAM_NUM_SUPPLIES, pivariety->supplies); + clk_disable_unprepare(pivariety->xclk); + + return 0; +} + +static int pivariety_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct pivariety *pivariety = to_pivariety(sd); + struct v4l2_mbus_framefmt *try_fmt = + v4l2_subdev_state_get_format(fh->state, 0); + struct arducam_format *def_fmt = &pivariety->supported_formats[0]; + + /* Initialize try_fmt */ + try_fmt->width = def_fmt->resolution_set->width; + try_fmt->height = def_fmt->resolution_set->height; + try_fmt->code = def_fmt->mbus_code; + try_fmt->field = V4L2_FIELD_NONE; + + return 0; +} + +static int pivariety_s_ctrl(struct v4l2_ctrl *ctrl) +{ + int ret, i; + struct pivariety *pivariety = + container_of(ctrl->handler, struct pivariety, + ctrl_handler); + struct arducam_format *supported_fmts = pivariety->supported_formats; + int num_supported_formats = pivariety->num_supported_formats; + + v4l2_dbg(3, debug, &pivariety->sd, "%s: cid = (0x%X), value = (%d).\n", + __func__, ctrl->id, ctrl->val); + + ret = pivariety_write(pivariety, CTRL_ID_REG, ctrl->id); + ret += pivariety_write(pivariety, CTRL_VALUE_REG, ctrl->val); + if (ret < 0) + return -EINVAL; + + /* When flip is set, modify all bayer formats */ + if (ctrl->id == V4L2_CID_VFLIP || ctrl->id == V4L2_CID_HFLIP) { + for (i = 0; i < num_supported_formats; i++) { + supported_fmts[i].mbus_code = + pivariety_get_format_code(pivariety, + &supported_fmts[i]); + } + } + + /* + * When starting streaming, controls are set in batches, + * and the short interval will cause some controls to be unsuccessfully + * set. + */ + if (pivariety->wait_until_free) + wait_for_free(pivariety, 1); + else + usleep_range(200, 210); + + return 0; +} + +static const struct v4l2_ctrl_ops pivariety_ctrl_ops = { + .s_ctrl = pivariety_s_ctrl, +}; + +static int pivariety_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct pivariety *pivariety = to_pivariety(sd); + struct arducam_format *supported_formats = pivariety->supported_formats; + int num_supported_formats = pivariety->num_supported_formats; + + v4l2_dbg(1, debug, sd, "%s: index = (%d)\n", __func__, code->index); + + if (code->index >= num_supported_formats) + return -EINVAL; + + code->code = supported_formats[code->index].mbus_code; + + return 0; +} + +static int pivariety_enum_framesizes(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + int i; + struct pivariety *pivariety = to_pivariety(sd); + struct arducam_format *supported_formats = pivariety->supported_formats; + int num_supported_formats = pivariety->num_supported_formats; + struct arducam_format *format; + struct arducam_resolution *resolution; + + v4l2_dbg(1, debug, sd, "%s: code = (0x%X), index = (%d)\n", + __func__, fse->code, fse->index); + + for (i = 0; i < num_supported_formats; i++) { + format = &supported_formats[i]; + if (fse->code == format->mbus_code) { + if (fse->index >= format->num_resolution_set) + return -EINVAL; + + resolution = &format->resolution_set[fse->index]; + fse->min_width = resolution->width; + fse->max_width = resolution->width; + fse->min_height = resolution->height; + fse->max_height = resolution->height; + + return 0; + } + } + + return -EINVAL; +} + +static int pivariety_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct pivariety *pivariety = to_pivariety(sd); + struct arducam_format *current_format; + struct v4l2_mbus_framefmt *fmt = &format->format; + int cur_res_idx; + + if (format->pad != 0) + return -EINVAL; + + mutex_lock(&pivariety->mutex); + + current_format = + &pivariety->supported_formats[pivariety->current_format_idx]; + cur_res_idx = pivariety->current_resolution_idx; + format->format.width = + current_format->resolution_set[cur_res_idx].width; + format->format.height = + current_format->resolution_set[cur_res_idx].height; + format->format.code = current_format->mbus_code; + format->format.field = V4L2_FIELD_NONE; + fmt->colorspace = V4L2_COLORSPACE_RAW; + fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace); + fmt->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true, + fmt->colorspace, + fmt->ycbcr_enc); + fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace); + + v4l2_dbg(1, debug, sd, "%s: width: (%d) height: (%d) code: (0x%X)\n", + __func__, format->format.width, format->format.height, + format->format.code); + + mutex_unlock(&pivariety->mutex); + return 0; +} + +static int pivariety_get_fmt_idx_by_code(struct pivariety *pivariety, + u32 mbus_code) +{ + int i; + u32 data_type; + struct arducam_format *formats = pivariety->supported_formats; + + for (i = 0; i < pivariety->num_supported_formats; i++) { + if (formats[i].mbus_code == mbus_code) + return i; + } + + /* + * If the specified format is not found in the list of supported + * formats, try to find a format of the same data type. + */ + for (i = 0; i < ARRAY_SIZE(codes); i++) + if (codes[i] == mbus_code) + break; + + if (i >= ARRAY_SIZE(codes)) + return -EINVAL; + + data_type = i / 5 + IMAGE_DT_RAW8; + + for (i = 0; i < pivariety->num_supported_formats; i++) { + if (formats[i].data_type == data_type) + return i; + } + + return -EINVAL; +} + +static struct v4l2_ctrl *get_control(struct pivariety *pivariety, + u32 id) +{ + int index = 0; + + while (index < MAX_CTRLS && pivariety->ctrls[index]) { + if (pivariety->ctrls[index]->id == id) + return pivariety->ctrls[index]; + index++; + } + + return NULL; +} + +static int update_control(struct pivariety *pivariety, u32 id) +{ + struct v4l2_subdev *sd = &pivariety->sd; + struct v4l2_ctrl *ctrl; + u32 min, max, step, def, id2; + int ret = 0; + + pivariety_write(pivariety, CTRL_ID_REG, id); + pivariety_read(pivariety, CTRL_ID_REG, &id2); + + v4l2_dbg(1, debug, sd, "%s: Write ID: 0x%08X Read ID: 0x%08X\n", + __func__, id, id2); + + pivariety_write(pivariety, CTRL_VALUE_REG, 0); + wait_for_free(pivariety, 1); + + ret += pivariety_read(pivariety, CTRL_MAX_REG, &max); + ret += pivariety_read(pivariety, CTRL_MIN_REG, &min); + ret += pivariety_read(pivariety, CTRL_DEF_REG, &def); + ret += pivariety_read(pivariety, CTRL_STEP_REG, &step); + + if (ret < 0) + goto err; + + if (id == NO_DATA_AVAILABLE || max == NO_DATA_AVAILABLE || + min == NO_DATA_AVAILABLE || def == NO_DATA_AVAILABLE || + step == NO_DATA_AVAILABLE) + goto err; + + v4l2_dbg(1, debug, sd, "%s: min: %d, max: %d, step: %d, def: %d\n", + __func__, min, max, step, def); + + ctrl = get_control(pivariety, id); + return __v4l2_ctrl_modify_range(ctrl, min, max, step, def); + +err: + return -EINVAL; +} + +static int update_controls(struct pivariety *pivariety) +{ + int ret = 0; + int index = 0; + + wait_for_free(pivariety, 5); + + while (index < MAX_CTRLS && pivariety->ctrls[index]) { + ret += update_control(pivariety, pivariety->ctrls[index]->id); + index++; + } + + return ret; +} + +static int pivariety_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + int i, j; + struct pivariety *pivariety = to_pivariety(sd); + struct arducam_format *supported_formats = pivariety->supported_formats; + + if (format->pad != 0) + return -EINVAL; + + mutex_lock(&pivariety->mutex); + + format->format.colorspace = V4L2_COLORSPACE_RAW; + format->format.field = V4L2_FIELD_NONE; + + v4l2_dbg(1, debug, sd, "%s: code: 0x%X, width: %d, height: %d\n", + __func__, format->format.code, format->format.width, + format->format.height); + + i = pivariety_get_fmt_idx_by_code(pivariety, format->format.code); + if (i < 0) + i = 0; + + format->format.code = supported_formats[i].mbus_code; + + for (j = 0; j < supported_formats[i].num_resolution_set; j++) { + if (supported_formats[i].resolution_set[j].width == + format->format.width && + supported_formats[i].resolution_set[j].height == + format->format.height) { + v4l2_dbg(1, debug, sd, + "%s: format match.\n", __func__); + v4l2_dbg(1, debug, sd, + "%s: set format to device: %d %d.\n", + __func__, supported_formats[i].index, j); + + pivariety_write(pivariety, PIXFORMAT_INDEX_REG, + supported_formats[i].index); + pivariety_write(pivariety, RESOLUTION_INDEX_REG, j); + + pivariety->current_format_idx = i; + pivariety->current_resolution_idx = j; + + update_controls(pivariety); + + goto unlock; + } + } + + format->format.width = supported_formats[i].resolution_set[0].width; + format->format.height = supported_formats[i].resolution_set[0].height; + + pivariety_write(pivariety, PIXFORMAT_INDEX_REG, + supported_formats[i].index); + pivariety_write(pivariety, RESOLUTION_INDEX_REG, 0); + + pivariety->current_format_idx = i; + pivariety->current_resolution_idx = 0; + update_controls(pivariety); + +unlock: + + mutex_unlock(&pivariety->mutex); + + return 0; +} + +/* Start streaming */ +static int pivariety_start_streaming(struct pivariety *pivariety) +{ + int ret; + + /* set stream on register */ + ret = pivariety_write(pivariety, MODE_SELECT_REG, + ARDUCAM_MODE_STREAMING); + + if (ret) + return ret; + + wait_for_free(pivariety, 2); + + /* + * When starting streaming, controls are set in batches, + * and the short interval will cause some controls to be unsuccessfully + * set. + */ + pivariety->wait_until_free = true; + /* Apply customized values from user */ + ret = __v4l2_ctrl_handler_setup(pivariety->sd.ctrl_handler); + + pivariety->wait_until_free = false; + if (ret) + return ret; + + wait_for_free(pivariety, 2); + + return ret; +} + +static int pivariety_read_sel(struct pivariety *pivariety, + struct v4l2_rect *rect) +{ + int ret = 0; + + ret += pivariety_read(pivariety, IPC_SEL_TOP_REG, &rect->top); + ret += pivariety_read(pivariety, IPC_SEL_LEFT_REG, &rect->left); + ret += pivariety_read(pivariety, IPC_SEL_WIDTH_REG, &rect->width); + ret += pivariety_read(pivariety, IPC_SEL_HEIGHT_REG, &rect->height); + + if (ret || rect->top == NO_DATA_AVAILABLE || + rect->left == NO_DATA_AVAILABLE || + rect->width == NO_DATA_AVAILABLE || + rect->height == NO_DATA_AVAILABLE) { + v4l2_err(&pivariety->sd, "%s: Failed to read selection.\n", + __func__); + return -EINVAL; + } + + return 0; +} + +static const struct v4l2_rect * +__pivariety_get_pad_crop(struct pivariety *pivariety, + struct v4l2_subdev_state *sd_state, + unsigned int pad, + enum v4l2_subdev_format_whence which) +{ + int ret; + + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_state_get_crop(sd_state, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + ret = pivariety_read_sel(pivariety, &pivariety->crop); + if (ret) + return NULL; + return &pivariety->crop; + } + + return NULL; +} + +static int pivariety_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + int ret = 0; + struct v4l2_rect rect; + struct pivariety *pivariety = to_pivariety(sd); + + ret = pivariety_write(pivariety, IPC_SEL_TARGET_REG, sel->target); + if (ret) { + v4l2_err(sd, "%s: Write register 0x%02x failed\n", + __func__, IPC_SEL_TARGET_REG); + return -EINVAL; + } + + wait_for_free(pivariety, 2); + + switch (sel->target) { + case V4L2_SEL_TGT_CROP: { + mutex_lock(&pivariety->mutex); + sel->r = *__pivariety_get_pad_crop(pivariety, sd_state, + sel->pad, + sel->which); + mutex_unlock(&pivariety->mutex); + + return 0; + } + + case V4L2_SEL_TGT_NATIVE_SIZE: + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + ret = pivariety_read_sel(pivariety, &rect); + if (ret) + return -EINVAL; + + sel->r = rect; + return 0; + } + + return -EINVAL; +} + +/* Stop streaming */ +static int pivariety_stop_streaming(struct pivariety *pivariety) +{ + int ret; + + /* set stream off register */ + ret = pivariety_write(pivariety, MODE_SELECT_REG, ARDUCAM_MODE_STANDBY); + if (ret) + v4l2_err(&pivariety->sd, "%s failed to set stream\n", __func__); + + /* + * Return success even if it was an error, as there is nothing the + * caller can do about it. + */ + return 0; +} + +static int pivariety_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct pivariety *pivariety = to_pivariety(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret = 0; + + mutex_lock(&pivariety->mutex); + if (pivariety->streaming == enable) { + mutex_unlock(&pivariety->mutex); + return 0; + } + + if (enable) { + ret = pm_runtime_get_sync(&client->dev); + if (ret < 0) { + pm_runtime_put_noidle(&client->dev); + goto err_unlock; + } + + /* + * Apply default & customized values + * and then start streaming. + */ + ret = pivariety_start_streaming(pivariety); + if (ret) + goto err_rpm_put; + } else { + pivariety_stop_streaming(pivariety); + pm_runtime_put(&client->dev); + } + + pivariety->streaming = enable; + + /* + * vflip and hflip cannot change during streaming + * Pivariety may not implement flip control. + */ + if (pivariety->vflip) + __v4l2_ctrl_grab(pivariety->vflip, enable); + + if (pivariety->hflip) + __v4l2_ctrl_grab(pivariety->hflip, enable); + + mutex_unlock(&pivariety->mutex); + + return ret; + +err_rpm_put: + pm_runtime_put(&client->dev); +err_unlock: + mutex_unlock(&pivariety->mutex); + + return ret; +} + +static int __maybe_unused pivariety_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct pivariety *pivariety = to_pivariety(sd); + + if (pivariety->streaming) + pivariety_stop_streaming(pivariety); + + return 0; +} + +static int __maybe_unused pivariety_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct pivariety *pivariety = to_pivariety(sd); + int ret; + + if (pivariety->streaming) { + ret = pivariety_start_streaming(pivariety); + if (ret) + goto error; + } + + return 0; + +error: + pivariety_stop_streaming(pivariety); + pivariety->streaming = 0; + return ret; +} + +static int pivariety_get_regulators(struct pivariety *pivariety) +{ + struct i2c_client *client = v4l2_get_subdevdata(&pivariety->sd); + int i; + + for (i = 0; i < ARDUCAM_NUM_SUPPLIES; i++) + pivariety->supplies[i].supply = pivariety_supply_name[i]; + + return devm_regulator_bulk_get(&client->dev, + ARDUCAM_NUM_SUPPLIES, + pivariety->supplies); +} + +static int pivariety_get_mbus_config(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_mbus_config *cfg) +{ + struct pivariety *pivariety = to_pivariety(sd); + + if (pivariety->lanes > pivariety->bus.num_data_lanes) + return -EINVAL; + + cfg->type = V4L2_MBUS_CSI2_DPHY; + cfg->bus.mipi_csi2.flags = pivariety->bus.flags; + cfg->bus.mipi_csi2.num_data_lanes = pivariety->lanes; + + return 0; +} + +static const struct v4l2_subdev_core_ops pivariety_core_ops = { + .subscribe_event = v4l2_ctrl_subdev_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_video_ops pivariety_video_ops = { + .s_stream = pivariety_set_stream, +}; + +static const struct v4l2_subdev_pad_ops pivariety_pad_ops = { + .enum_mbus_code = pivariety_enum_mbus_code, + .get_fmt = pivariety_get_fmt, + .set_fmt = pivariety_set_fmt, + .enum_frame_size = pivariety_enum_framesizes, + .get_selection = pivariety_get_selection, + .get_mbus_config = pivariety_get_mbus_config, +}; + +static const struct v4l2_subdev_ops pivariety_subdev_ops = { + .core = &pivariety_core_ops, + .video = &pivariety_video_ops, + .pad = &pivariety_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops pivariety_internal_ops = { + .open = pivariety_open, +}; + +static void pivariety_free_controls(struct pivariety *pivariety) +{ + v4l2_ctrl_handler_free(pivariety->sd.ctrl_handler); + mutex_destroy(&pivariety->mutex); +} + +static int pivariety_get_length_of_set(struct pivariety *pivariety, + u16 idx_reg, u16 val_reg) +{ + int ret; + int index = 0; + u32 val; + + while (1) { + ret = pivariety_write(pivariety, idx_reg, index); + ret += pivariety_read(pivariety, val_reg, &val); + + if (ret < 0) + return -1; + + if (val == NO_DATA_AVAILABLE) + break; + index++; + } + pivariety_write(pivariety, idx_reg, 0); + return index; +} + +static int pivariety_enum_resolution(struct pivariety *pivariety, + struct arducam_format *format) +{ + struct i2c_client *client = v4l2_get_subdevdata(&pivariety->sd); + int index = 0; + u32 width, height; + int num_resolution = 0; + int ret; + + num_resolution = pivariety_get_length_of_set(pivariety, + RESOLUTION_INDEX_REG, + FORMAT_WIDTH_REG); + if (num_resolution < 0) + goto err; + + format->resolution_set = devm_kzalloc(&client->dev, + sizeof(*format->resolution_set) * + num_resolution, + GFP_KERNEL); + while (1) { + ret = pivariety_write(pivariety, RESOLUTION_INDEX_REG, index); + ret += pivariety_read(pivariety, FORMAT_WIDTH_REG, &width); + ret += pivariety_read(pivariety, FORMAT_HEIGHT_REG, &height); + + if (ret < 0) + goto err; + + if (width == NO_DATA_AVAILABLE || height == NO_DATA_AVAILABLE) + break; + + format->resolution_set[index].width = width; + format->resolution_set[index].height = height; + + index++; + } + + format->num_resolution_set = index; + pivariety_write(pivariety, RESOLUTION_INDEX_REG, 0); + return 0; +err: + return -ENODEV; +} + +static int pivariety_enum_pixformat(struct pivariety *pivariety) +{ + int ret = 0; + u32 mbus_code = 0; + int pixfmt_type; + int bayer_order; + int bayer_order_not_volatile; + int lanes; + int index = 0; + int num_pixformat = 0; + struct arducam_format *arducam_fmt; + struct i2c_client *client = v4l2_get_subdevdata(&pivariety->sd); + + num_pixformat = pivariety_get_length_of_set(pivariety, + PIXFORMAT_INDEX_REG, + PIXFORMAT_TYPE_REG); + + if (num_pixformat < 0) + goto err; + + ret = pivariety_read(pivariety, FLIPS_DONT_CHANGE_ORDER_REG, + &bayer_order_not_volatile); + if (bayer_order_not_volatile == NO_DATA_AVAILABLE) + pivariety->bayer_order_volatile = 1; + else + pivariety->bayer_order_volatile = !bayer_order_not_volatile; + + if (ret < 0) + goto err; + + pivariety->supported_formats = + devm_kzalloc(&client->dev, + sizeof(*pivariety->supported_formats) * + num_pixformat, + GFP_KERNEL); + + while (1) { + ret = pivariety_write(pivariety, PIXFORMAT_INDEX_REG, index); + ret += pivariety_read(pivariety, PIXFORMAT_TYPE_REG, + &pixfmt_type); + + if (pixfmt_type == NO_DATA_AVAILABLE) + break; + + ret += pivariety_read(pivariety, MIPI_LANES_REG, &lanes); + if (lanes == NO_DATA_AVAILABLE) + break; + + ret += pivariety_read(pivariety, PIXFORMAT_ORDER_REG, + &bayer_order); + if (ret < 0) + goto err; + + mbus_code = data_type_to_mbus_code(pixfmt_type, bayer_order); + arducam_fmt = &pivariety->supported_formats[index]; + arducam_fmt->index = index; + arducam_fmt->mbus_code = mbus_code; + arducam_fmt->bayer_order = bayer_order; + arducam_fmt->data_type = pixfmt_type; + if (pivariety_enum_resolution(pivariety, arducam_fmt)) + goto err; + + index++; + } + + pivariety_write(pivariety, PIXFORMAT_INDEX_REG, 0); + pivariety->num_supported_formats = index; + pivariety->current_format_idx = 0; + pivariety->current_resolution_idx = 0; + pivariety->lanes = lanes; + + return 0; + +err: + return -ENODEV; +} + +static const char *pivariety_ctrl_get_name(u32 id) +{ + switch (id) { + case V4L2_CID_ARDUCAM_EXT_TRI: + return "trigger_mode"; + case V4L2_CID_ARDUCAM_IRCUT: + return "ircut"; + case V4L2_CID_ARDUCAM_STROBE_SHIFT: + return "strobe_shift"; + case V4L2_CID_ARDUCAM_STROBE_WIDTH: + return "strobe_width"; + case V4L2_CID_ARDUCAM_MODE: + return "mode"; + default: + return NULL; + } +} + +static enum v4l2_ctrl_type pivariety_get_v4l2_ctrl_type(u32 id) +{ + switch (id) { + case V4L2_CID_ARDUCAM_EXT_TRI: + return V4L2_CTRL_TYPE_BOOLEAN; + case V4L2_CID_ARDUCAM_IRCUT: + return V4L2_CTRL_TYPE_BOOLEAN; + default: + return V4L2_CTRL_TYPE_INTEGER; + } +} + +static struct v4l2_ctrl *v4l2_ctrl_new_arducam(struct v4l2_ctrl_handler *hdl, + const struct v4l2_ctrl_ops *ops, + u32 id, s64 min, s64 max, + u64 step, s64 def) +{ + struct v4l2_ctrl_config ctrl_cfg = { + .ops = ops, + .id = id, + .name = NULL, + .type = V4L2_CTRL_TYPE_INTEGER, + .flags = 0, + .min = min, + .max = max, + .def = def, + .step = step, + }; + + ctrl_cfg.name = pivariety_ctrl_get_name(id); + ctrl_cfg.type = pivariety_get_v4l2_ctrl_type(id); + + return v4l2_ctrl_new_custom(hdl, &ctrl_cfg, NULL); +} + +static int pivariety_enum_controls(struct pivariety *pivariety) +{ + struct v4l2_subdev *sd = &pivariety->sd; + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct v4l2_ctrl_handler *ctrl_hdlr = &pivariety->ctrl_handler; + struct v4l2_fwnode_device_properties props; + struct v4l2_ctrl **ctrl = pivariety->ctrls; + int ret, index, num_ctrls; + u32 id, min, max, def, step; + + num_ctrls = pivariety_get_length_of_set(pivariety, CTRL_INDEX_REG, + CTRL_ID_REG); + if (num_ctrls < 0) + goto err; + + v4l2_dbg(1, debug, sd, "%s: num_ctrls = %d\n", + __func__, num_ctrls); + + ret = v4l2_ctrl_handler_init(ctrl_hdlr, num_ctrls); + if (ret) + return ret; + + mutex_init(&pivariety->mutex); + + index = 0; + while (1) { + ret = pivariety_write(pivariety, CTRL_INDEX_REG, index); + pivariety_write(pivariety, CTRL_VALUE_REG, 0); + wait_for_free(pivariety, 1); + + ret += pivariety_read(pivariety, CTRL_ID_REG, &id); + ret += pivariety_read(pivariety, CTRL_MAX_REG, &max); + ret += pivariety_read(pivariety, CTRL_MIN_REG, &min); + ret += pivariety_read(pivariety, CTRL_DEF_REG, &def); + ret += pivariety_read(pivariety, CTRL_STEP_REG, &step); + if (ret < 0) + goto err; + + if (id == NO_DATA_AVAILABLE || max == NO_DATA_AVAILABLE || + min == NO_DATA_AVAILABLE || def == NO_DATA_AVAILABLE || + step == NO_DATA_AVAILABLE) + break; + + v4l2_dbg(1, debug, sd, + "%s: index = %d, id = 0x%x, max = %d, min = %d, def = %d, step = %d\n", + __func__, index, id, max, min, def, step); + + if (v4l2_ctrl_get_name(id)) { + *ctrl = v4l2_ctrl_new_std(ctrl_hdlr, + &pivariety_ctrl_ops, + id, min, + max, step, + def); + v4l2_dbg(1, debug, sd, "%s: ctrl: 0x%p\n", + __func__, *ctrl); + } else if (pivariety_ctrl_get_name(id)) { + *ctrl = v4l2_ctrl_new_arducam(ctrl_hdlr, + &pivariety_ctrl_ops, + id, min, max, step, def); + + v4l2_dbg(1, debug, sd, + "%s: new custom ctrl, ctrl: 0x%p.\n", + __func__, *ctrl); + } else { + index++; + continue; + } + + if (!*ctrl) + goto err; + + switch (id) { + case V4L2_CID_HFLIP: + pivariety->hflip = *ctrl; + if (pivariety->bayer_order_volatile) + pivariety->hflip->flags |= + V4L2_CTRL_FLAG_MODIFY_LAYOUT; + break; + + case V4L2_CID_VFLIP: + pivariety->vflip = *ctrl; + if (pivariety->bayer_order_volatile) + pivariety->vflip->flags |= + V4L2_CTRL_FLAG_MODIFY_LAYOUT; + break; + + case V4L2_CID_HBLANK: + (*ctrl)->flags |= V4L2_CTRL_FLAG_READ_ONLY; + break; + } + + ctrl++; + index++; + } + + pivariety_write(pivariety, CTRL_INDEX_REG, 0); + + ret = v4l2_fwnode_device_parse(&client->dev, &props); + if (ret) + goto err; + + ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, + &pivariety_ctrl_ops, + &props); + if (ret) + goto err; + + pivariety->sd.ctrl_handler = ctrl_hdlr; + v4l2_ctrl_handler_setup(ctrl_hdlr); + return 0; +err: + mutex_destroy(&pivariety->mutex); + return -ENODEV; +} + +static int pivariety_parse_dt(struct pivariety *pivariety, struct device *dev) +{ + struct fwnode_handle *endpoint; + struct v4l2_fwnode_endpoint ep_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; + int ret = -EINVAL; + + /* Get CSI2 bus config */ + endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL); + if (!endpoint) { + dev_err(dev, "endpoint node not found\n"); + return -EINVAL; + } + + if (v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep_cfg)) { + dev_err(dev, "could not parse endpoint\n"); + goto error_out; + } + + pivariety->bus = ep_cfg.bus.mipi_csi2; + + ret = 0; + +error_out: + v4l2_fwnode_endpoint_free(&ep_cfg); + fwnode_handle_put(endpoint); + + return ret; +} + +static int pivariety_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct pivariety *pivariety; + u32 device_id, firmware_version; + int ret; + + pivariety = devm_kzalloc(&client->dev, sizeof(*pivariety), GFP_KERNEL); + if (!pivariety) + return -ENOMEM; + + /* Initialize subdev */ + v4l2_i2c_subdev_init(&pivariety->sd, client, + &pivariety_subdev_ops); + + if (pivariety_parse_dt(pivariety, dev)) + return -EINVAL; + + /* Get system clock (xclk) */ + pivariety->xclk = devm_clk_get(dev, "xclk"); + if (IS_ERR(pivariety->xclk)) { + dev_err(dev, "failed to get xclk\n"); + return PTR_ERR(pivariety->xclk); + } + + pivariety->xclk_freq = clk_get_rate(pivariety->xclk); + if (pivariety->xclk_freq != 24000000) { + dev_err(dev, "xclk frequency not supported: %d Hz\n", + pivariety->xclk_freq); + return -EINVAL; + } + + ret = pivariety_get_regulators(pivariety); + if (ret) + return ret; + + /* Request optional enable pin */ + pivariety->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + + ret = pivariety_power_on(dev); + if (ret) + return ret; + + ret = pivariety_read(pivariety, DEVICE_ID_REG, &device_id); + if (ret || device_id != DEVICE_ID) { + dev_err(dev, "probe failed\n"); + ret = -ENODEV; + goto error_power_off; + } + + ret = pivariety_read(pivariety, DEVICE_VERSION_REG, &firmware_version); + if (ret) + dev_err(dev, "read firmware version failed\n"); + + dev_info(dev, "firmware version: 0x%04X\n", firmware_version); + + if (pivariety_enum_pixformat(pivariety)) { + dev_err(dev, "enum pixformat failed.\n"); + ret = -ENODEV; + goto error_power_off; + } + + if (pivariety_enum_controls(pivariety)) { + dev_err(dev, "enum controls failed.\n"); + ret = -ENODEV; + goto error_power_off; + } + + /* Initialize subdev */ + pivariety->sd.internal_ops = &pivariety_internal_ops; + pivariety->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + pivariety->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + /* Initialize source pad */ + pivariety->pad.flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&pivariety->sd.entity, 1, &pivariety->pad); + if (ret) + goto error_handler_free; + + ret = v4l2_async_register_subdev_sensor(&pivariety->sd); + if (ret < 0) + goto error_media_entity; + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_idle(dev); + + return 0; + +error_media_entity: + media_entity_cleanup(&pivariety->sd.entity); + +error_handler_free: + pivariety_free_controls(pivariety); + +error_power_off: + pivariety_power_off(dev); + + return ret; +} + +static void pivariety_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct pivariety *pivariety = to_pivariety(sd); + + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + pivariety_free_controls(pivariety); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); +} + +static const struct dev_pm_ops pivariety_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pivariety_suspend, pivariety_resume) + SET_RUNTIME_PM_OPS(pivariety_power_off, pivariety_power_on, NULL) +}; + +static const struct of_device_id arducam_pivariety_dt_ids[] = { + { .compatible = "arducam,arducam-pivariety" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, arducam_pivariety_dt_ids); + +static struct i2c_driver arducam_pivariety_i2c_driver = { + .driver = { + .name = "arducam-pivariety", + .of_match_table = arducam_pivariety_dt_ids, + .pm = &pivariety_pm_ops, + }, + .probe = pivariety_probe, + .remove = pivariety_remove, +}; + +module_i2c_driver(arducam_pivariety_i2c_driver); + +MODULE_AUTHOR("Lee Jackson <info@arducam.com>"); +MODULE_DESCRIPTION("Arducam Pivariety v4l2 driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/arducam-pivariety.h b/drivers/media/i2c/arducam-pivariety.h new file mode 100644 index 00000000000000..99d5ada309e8ce --- /dev/null +++ b/drivers/media/i2c/arducam-pivariety.h @@ -0,0 +1,110 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _ARDUCAM_PIVARIETY_H_ +#define _ARDUCAM_PIVARIETY_H_ + +#define DEVICE_REG_BASE 0x0100 +#define PIXFORMAT_REG_BASE 0x0200 +#define FORMAT_REG_BASE 0x0300 +#define CTRL_REG_BASE 0x0400 +#define IPC_REG_BASE 0x0600 + +#define ARDUCAM_MODE_STANDBY 0x00 +#define ARDUCAM_MODE_STREAMING 0x01 + +#define MODE_SELECT_REG (DEVICE_REG_BASE | 0x0000) +#define DEVICE_VERSION_REG (DEVICE_REG_BASE | 0x0001) +#define SENSOR_ID_REG (DEVICE_REG_BASE | 0x0002) +#define DEVICE_ID_REG (DEVICE_REG_BASE | 0x0003) +#define SYSTEM_IDLE_REG (DEVICE_REG_BASE | 0x0007) + +#define PIXFORMAT_INDEX_REG (PIXFORMAT_REG_BASE | 0x0000) +#define PIXFORMAT_TYPE_REG (PIXFORMAT_REG_BASE | 0x0001) +#define PIXFORMAT_ORDER_REG (PIXFORMAT_REG_BASE | 0x0002) +#define MIPI_LANES_REG (PIXFORMAT_REG_BASE | 0x0003) +#define FLIPS_DONT_CHANGE_ORDER_REG (PIXFORMAT_REG_BASE | 0x0004) + +#define RESOLUTION_INDEX_REG (FORMAT_REG_BASE | 0x0000) +#define FORMAT_WIDTH_REG (FORMAT_REG_BASE | 0x0001) +#define FORMAT_HEIGHT_REG (FORMAT_REG_BASE | 0x0002) + +#define CTRL_INDEX_REG (CTRL_REG_BASE | 0x0000) +#define CTRL_ID_REG (CTRL_REG_BASE | 0x0001) +#define CTRL_MIN_REG (CTRL_REG_BASE | 0x0002) +#define CTRL_MAX_REG (CTRL_REG_BASE | 0x0003) +#define CTRL_STEP_REG (CTRL_REG_BASE | 0x0004) +#define CTRL_DEF_REG (CTRL_REG_BASE | 0x0005) +#define CTRL_VALUE_REG (CTRL_REG_BASE | 0x0006) + +#define IPC_SEL_TARGET_REG (IPC_REG_BASE | 0x0000) +#define IPC_SEL_TOP_REG (IPC_REG_BASE | 0x0001) +#define IPC_SEL_LEFT_REG (IPC_REG_BASE | 0x0002) +#define IPC_SEL_WIDTH_REG (IPC_REG_BASE | 0x0003) +#define IPC_SEL_HEIGHT_REG (IPC_REG_BASE | 0x0004) +#define IPC_DELAY_REG (IPC_REG_BASE | 0x0005) + +#define NO_DATA_AVAILABLE 0xFFFFFFFE + +#define DEVICE_ID 0x0030 + +#define I2C_READ_RETRY_COUNT 3 +#define I2C_WRITE_RETRY_COUNT 2 + +#define V4L2_CID_ARDUCAM_BASE (V4L2_CID_USER_BASE + 0x1000) +#define V4L2_CID_ARDUCAM_EXT_TRI (V4L2_CID_ARDUCAM_BASE + 1) +#define V4L2_CID_ARDUCAM_IRCUT (V4L2_CID_ARDUCAM_BASE + 8) +#define V4L2_CID_ARDUCAM_STROBE_SHIFT (V4L2_CID_ARDUCAM_BASE + 14) +#define V4L2_CID_ARDUCAM_STROBE_WIDTH (V4L2_CID_ARDUCAM_BASE + 15) +#define V4L2_CID_ARDUCAM_MODE (V4L2_CID_ARDUCAM_BASE + 16) + +enum image_dt { + IMAGE_DT_YUV420_8 = 0x18, + IMAGE_DT_YUV420_10, + + IMAGE_DT_YUV420CSPS_8 = 0x1C, + IMAGE_DT_YUV420CSPS_10, + IMAGE_DT_YUV422_8, + IMAGE_DT_YUV422_10, + IMAGE_DT_RGB444, + IMAGE_DT_RGB555, + IMAGE_DT_RGB565, + IMAGE_DT_RGB666, + IMAGE_DT_RGB888, + + IMAGE_DT_RAW6 = 0x28, + IMAGE_DT_RAW7, + IMAGE_DT_RAW8, + IMAGE_DT_RAW10, + IMAGE_DT_RAW12, + IMAGE_DT_RAW14, +}; + +enum bayer_order { + BAYER_ORDER_BGGR = 0, + BAYER_ORDER_GBRG = 1, + BAYER_ORDER_GRBG = 2, + BAYER_ORDER_RGGB = 3, + BAYER_ORDER_GRAY = 4, +}; + +enum yuv_order { + YUV_ORDER_YUYV = 0, + YUV_ORDER_YVYU = 1, + YUV_ORDER_UYVY = 2, + YUV_ORDER_VYUY = 3, +}; + +struct arducam_resolution { + u32 width; + u32 height; +}; + +struct arducam_format { + u32 index; + u32 mbus_code; + u32 bayer_order; + u32 data_type; + u32 num_resolution_set; + struct arducam_resolution *resolution_set; +}; + +#endif diff --git a/drivers/media/i2c/arducam_64mp.c b/drivers/media/i2c/arducam_64mp.c new file mode 100644 index 00000000000000..10a4b257539798 --- /dev/null +++ b/drivers/media/i2c/arducam_64mp.c @@ -0,0 +1,2618 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A V4L2 driver for Arducam 64MP cameras. + * Copyright (C) 2021 Arducam Technology co., Ltd. + * + * Based on Sony IMX477 camera driver + * Copyright (C) 2020 Raspberry Pi (Trading) Ltd + */ +#include <linux/unaligned.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-event.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-mediabus.h> + +#define ARDUCAM_64MP_REG_VALUE_08BIT 1 +#define ARDUCAM_64MP_REG_VALUE_16BIT 2 + +/* Chip ID */ +#define ARDUCAM_64MP_REG_CHIP_ID 0x005E +#define ARDUCAM_64MP_CHIP_ID 0x4136 + +#define ARDUCAM_64MP_REG_MODE_SELECT 0x0100 +#define ARDUCAM_64MP_MODE_STANDBY 0x00 +#define ARDUCAM_64MP_MODE_STREAMING 0x01 + +#define ARDUCAM_64MP_REG_ORIENTATION 0x101 + +#define ARDUCAM_64MP_XCLK_FREQ 24000000 + +#define ARDUCAM_64MP_DEFAULT_LINK_FREQ 456000000 + +/* Pixel rate is fixed at 900MHz for all the modes */ +#define ARDUCAM_64MP_PIXEL_RATE 900000000 + +/* V_TIMING internal */ +#define ARDUCAM_64MP_REG_FRAME_LENGTH 0x0340 +#define ARDUCAM_64MP_FRAME_LENGTH_MAX 0xffff + +/* Long exposure multiplier */ +#define ARDUCAM_64MP_LONG_EXP_SHIFT_MAX 7 +#define ARDUCAM_64MP_LONG_EXP_SHIFT_REG 0x3100 + +/* Exposure control */ +#define ARDUCAM_64MP_REG_EXPOSURE 0x0202 +#define ARDUCAM_64MP_EXPOSURE_OFFSET 48 +#define ARDUCAM_64MP_EXPOSURE_MIN 9 +#define ARDUCAM_64MP_EXPOSURE_STEP 1 +#define ARDUCAM_64MP_EXPOSURE_DEFAULT 0x3e8 +#define ARDUCAM_64MP_EXPOSURE_MAX (ARDUCAM_64MP_FRAME_LENGTH_MAX - \ + ARDUCAM_64MP_EXPOSURE_OFFSET) + +/* Analog gain control */ +#define ARDUCAM_64MP_REG_ANALOG_GAIN 0x0204 +#define ARDUCAM_64MP_ANA_GAIN_MIN 0 +#define ARDUCAM_64MP_ANA_GAIN_MAX 1008 +#define ARDUCAM_64MP_ANA_GAIN_STEP 1 +#define ARDUCAM_64MP_ANA_GAIN_DEFAULT 0x0 + +/* Digital gain control */ +#define ARDUCAM_64MP_REG_DIGITAL_GAIN 0x020e +#define ARDUCAM_64MP_DGTL_GAIN_MIN 0x0100 +#define ARDUCAM_64MP_DGTL_GAIN_MAX 0x0fff +#define ARDUCAM_64MP_DGTL_GAIN_DEFAULT 0x0100 +#define ARDUCAM_64MP_DGTL_GAIN_STEP 1 + +/* Test Pattern Control */ +#define ARDUCAM_64MP_REG_TEST_PATTERN 0x0600 +#define ARDUCAM_64MP_TEST_PATTERN_DISABLE 0 +#define ARDUCAM_64MP_TEST_PATTERN_SOLID_COLOR 1 +#define ARDUCAM_64MP_TEST_PATTERN_COLOR_BARS 2 +#define ARDUCAM_64MP_TEST_PATTERN_GREY_COLOR 3 +#define ARDUCAM_64MP_TEST_PATTERN_PN9 4 + +/* Test pattern colour components */ +#define ARDUCAM_64MP_REG_TEST_PATTERN_R 0x0602 +#define ARDUCAM_64MP_REG_TEST_PATTERN_GR 0x0604 +#define ARDUCAM_64MP_REG_TEST_PATTERN_B 0x0606 +#define ARDUCAM_64MP_REG_TEST_PATTERN_GB 0x0608 +#define ARDUCAM_64MP_TEST_PATTERN_COLOUR_MIN 0 +#define ARDUCAM_64MP_TEST_PATTERN_COLOUR_MAX 0x0fff +#define ARDUCAM_64MP_TEST_PATTERN_COLOUR_STEP 1 +#define ARDUCAM_64MP_TEST_PATTERN_R_DEFAULT \ + ARDUCAM_64MP_TEST_PATTERN_COLOUR_MAX +#define ARDUCAM_64MP_TEST_PATTERN_GR_DEFAULT 0 +#define ARDUCAM_64MP_TEST_PATTERN_B_DEFAULT 0 +#define ARDUCAM_64MP_TEST_PATTERN_GB_DEFAULT 0 + +/* Embedded metadata stream structure */ +#define ARDUCAM_64MP_EMBEDDED_LINE_WIDTH (11560 * 3) +#define ARDUCAM_64MP_NUM_EMBEDDED_LINES 1 + +enum pad_types { + IMAGE_PAD, + METADATA_PAD, + NUM_PADS +}; + +/* ARDUCAM_64MP native and active pixel array size. */ +#define ARDUCAM_64MP_NATIVE_WIDTH 9344U +#define ARDUCAM_64MP_NATIVE_HEIGHT 7032U +#define ARDUCAM_64MP_PIXEL_ARRAY_LEFT 48U +#define ARDUCAM_64MP_PIXEL_ARRAY_TOP 40U +#define ARDUCAM_64MP_PIXEL_ARRAY_WIDTH 9248U +#define ARDUCAM_64MP_PIXEL_ARRAY_HEIGHT 6944U + +struct arducam_64mp_reg { + u16 address; + u8 val; +}; + +struct arducam_64mp_reg_list { + unsigned int num_of_regs; + const struct arducam_64mp_reg *regs; +}; + +/* Mode : resolution and related config&values */ +struct arducam_64mp_mode { + /* Frame width */ + unsigned int width; + + /* Frame height */ + unsigned int height; + + /* H-timing in pixels */ + unsigned int line_length_pix; + + /* Analog crop rectangle. */ + struct v4l2_rect crop; + + /* Default framerate. */ + struct v4l2_fract timeperframe_default; + + /* Default register values */ + struct arducam_64mp_reg_list reg_list; +}; + +static const s64 arducam_64mp_link_freq_menu[] = { + ARDUCAM_64MP_DEFAULT_LINK_FREQ, +}; + +static const struct arducam_64mp_reg mode_common_regs[] = { + {0x0100, 0x00}, + {0x0136, 0x18}, + {0x0137, 0x00}, + {0x33F0, 0x01}, + {0x33F1, 0x03}, + {0x0111, 0x02}, + {0x3062, 0x00}, + {0x3063, 0x30}, + {0x3076, 0x00}, + {0x3077, 0x30}, + {0x1f06, 0x06}, + {0x1f07, 0x82}, + {0x1f04, 0x71}, + {0x1f05, 0x01}, + {0x1f08, 0x01}, + {0x5bfe, 0x14}, + {0x5c0d, 0x2d}, + {0x5c1c, 0x30}, + {0x5c2b, 0x32}, + {0x5c37, 0x2e}, + {0x5c40, 0x30}, + {0x5c50, 0x14}, + {0x5c5f, 0x28}, + {0x5c6e, 0x28}, + {0x5c7d, 0x32}, + {0x5c89, 0x37}, + {0x5c92, 0x56}, + {0x5bfc, 0x12}, + {0x5c0b, 0x2a}, + {0x5c1a, 0x2c}, + {0x5c29, 0x2f}, + {0x5c36, 0x2e}, + {0x5c3f, 0x2e}, + {0x5c4e, 0x06}, + {0x5c5d, 0x1e}, + {0x5c6c, 0x20}, + {0x5c7b, 0x1e}, + {0x5c88, 0x32}, + {0x5c91, 0x32}, + {0x5c02, 0x14}, + {0x5c11, 0x2f}, + {0x5c20, 0x32}, + {0x5c2f, 0x34}, + {0x5c39, 0x31}, + {0x5c42, 0x31}, + {0x5c8b, 0x28}, + {0x5c94, 0x28}, + {0x5c00, 0x10}, + {0x5c0f, 0x2c}, + {0x5c1e, 0x2e}, + {0x5c2d, 0x32}, + {0x5c38, 0x2e}, + {0x5c41, 0x2b}, + {0x5c61, 0x0a}, + {0x5c70, 0x0a}, + {0x5c7f, 0x0a}, + {0x5c8a, 0x1e}, + {0x5c93, 0x2a}, + {0x5bfa, 0x2b}, + {0x5c09, 0x2d}, + {0x5c18, 0x2e}, + {0x5c27, 0x30}, + {0x5c5b, 0x28}, + {0x5c6a, 0x22}, + {0x5c79, 0x42}, + {0x5bfb, 0x2c}, + {0x5c0a, 0x2f}, + {0x5c19, 0x2e}, + {0x5c28, 0x2e}, + {0x5c4d, 0x20}, + {0x5c5c, 0x1e}, + {0x5c6b, 0x32}, + {0x5c7a, 0x32}, + {0x5bfd, 0x30}, + {0x5c0c, 0x32}, + {0x5c1b, 0x2e}, + {0x5c2a, 0x30}, + {0x5c4f, 0x28}, + {0x5c5e, 0x32}, + {0x5c6d, 0x37}, + {0x5c7c, 0x56}, + {0x5bff, 0x2e}, + {0x5c0e, 0x32}, + {0x5c1d, 0x2e}, + {0x5c2c, 0x2b}, + {0x5c51, 0x0a}, + {0x5c60, 0x0a}, + {0x5c6f, 0x1e}, + {0x5c7e, 0x2a}, + {0x5c01, 0x32}, + {0x5c10, 0x34}, + {0x5c1f, 0x31}, + {0x5c2e, 0x31}, + {0x5c71, 0x28}, + {0x5c80, 0x28}, + {0x5c4c, 0x2a}, + {0x33f2, 0x01}, + {0x1f04, 0x73}, + {0x1f05, 0x01}, + {0x5bfa, 0x35}, + {0x5c09, 0x38}, + {0x5c18, 0x3a}, + {0x5c27, 0x38}, + {0x5c5b, 0x25}, + {0x5c6a, 0x24}, + {0x5c79, 0x47}, + {0x5bfc, 0x15}, + {0x5c0b, 0x2e}, + {0x5c1a, 0x36}, + {0x5c29, 0x38}, + {0x5c36, 0x36}, + {0x5c3f, 0x36}, + {0x5c4e, 0x0b}, + {0x5c5d, 0x20}, + {0x5c6c, 0x2a}, + {0x5c7b, 0x25}, + {0x5c88, 0x25}, + {0x5c91, 0x22}, + {0x5bfe, 0x15}, + {0x5c0d, 0x32}, + {0x5c1c, 0x36}, + {0x5c2b, 0x36}, + {0x5c37, 0x3a}, + {0x5c40, 0x39}, + {0x5c50, 0x06}, + {0x5c5f, 0x22}, + {0x5c6e, 0x23}, + {0x5c7d, 0x2e}, + {0x5c89, 0x44}, + {0x5c92, 0x51}, + {0x5d7f, 0x0a}, + {0x5c00, 0x17}, + {0x5c0f, 0x36}, + {0x5c1e, 0x38}, + {0x5c2d, 0x3c}, + {0x5c38, 0x38}, + {0x5c41, 0x36}, + {0x5c52, 0x0a}, + {0x5c61, 0x21}, + {0x5c70, 0x23}, + {0x5c7f, 0x1b}, + {0x5c8a, 0x22}, + {0x5c93, 0x20}, + {0x5c02, 0x1a}, + {0x5c11, 0x3e}, + {0x5c20, 0x3f}, + {0x5c2f, 0x3d}, + {0x5c39, 0x3e}, + {0x5c42, 0x3c}, + {0x5c54, 0x02}, + {0x5c63, 0x12}, + {0x5c72, 0x14}, + {0x5c81, 0x24}, + {0x5c8b, 0x1c}, + {0x5c94, 0x4e}, + {0x5d8a, 0x09}, + {0x5bfb, 0x36}, + {0x5c0a, 0x38}, + {0x5c19, 0x36}, + {0x5c28, 0x36}, + {0x5c4d, 0x2a}, + {0x5c5c, 0x25}, + {0x5c6b, 0x25}, + {0x5c7a, 0x22}, + {0x5bfd, 0x36}, + {0x5c0c, 0x36}, + {0x5c1b, 0x3a}, + {0x5c2a, 0x39}, + {0x5c4f, 0x23}, + {0x5c5e, 0x2e}, + {0x5c6d, 0x44}, + {0x5c7c, 0x51}, + {0x5d63, 0x0a}, + {0x5bff, 0x38}, + {0x5c0e, 0x3c}, + {0x5c1d, 0x38}, + {0x5c2c, 0x36}, + {0x5c51, 0x23}, + {0x5c60, 0x1b}, + {0x5c6f, 0x22}, + {0x5c7e, 0x20}, + {0x5c01, 0x3f}, + {0x5c10, 0x3d}, + {0x5c1f, 0x3e}, + {0x5c2e, 0x3c}, + {0x5c53, 0x14}, + {0x5c62, 0x24}, + {0x5c71, 0x1c}, + {0x5c80, 0x4e}, + {0x5d76, 0x09}, + {0x5c4c, 0x2a}, + {0x33f2, 0x02}, + {0x1f04, 0x78}, + {0x1f05, 0x01}, + {0x5bfa, 0x37}, + {0x5c09, 0x36}, + {0x5c18, 0x39}, + {0x5c27, 0x38}, + {0x5c5b, 0x27}, + {0x5c6a, 0x2b}, + {0x5c79, 0x48}, + {0x5bfc, 0x16}, + {0x5c0b, 0x32}, + {0x5c1a, 0x33}, + {0x5c29, 0x37}, + {0x5c36, 0x36}, + {0x5c3f, 0x35}, + {0x5c4e, 0x0d}, + {0x5c5d, 0x2d}, + {0x5c6c, 0x23}, + {0x5c7b, 0x25}, + {0x5c88, 0x31}, + {0x5c91, 0x2e}, + {0x5bfe, 0x15}, + {0x5c0d, 0x31}, + {0x5c1c, 0x35}, + {0x5c2b, 0x36}, + {0x5c37, 0x35}, + {0x5c40, 0x37}, + {0x5c50, 0x0f}, + {0x5c5f, 0x31}, + {0x5c6e, 0x30}, + {0x5c7d, 0x33}, + {0x5c89, 0x36}, + {0x5c92, 0x5b}, + {0x5c00, 0x13}, + {0x5c0f, 0x2f}, + {0x5c1e, 0x2e}, + {0x5c2d, 0x34}, + {0x5c38, 0x33}, + {0x5c41, 0x32}, + {0x5c52, 0x0d}, + {0x5c61, 0x27}, + {0x5c70, 0x28}, + {0x5c7f, 0x1f}, + {0x5c8a, 0x25}, + {0x5c93, 0x2c}, + {0x5c02, 0x15}, + {0x5c11, 0x36}, + {0x5c20, 0x39}, + {0x5c2f, 0x3a}, + {0x5c39, 0x37}, + {0x5c42, 0x37}, + {0x5c54, 0x04}, + {0x5c63, 0x1c}, + {0x5c72, 0x1c}, + {0x5c81, 0x1c}, + {0x5c8b, 0x28}, + {0x5c94, 0x24}, + {0x5bfb, 0x33}, + {0x5c0a, 0x37}, + {0x5c19, 0x36}, + {0x5c28, 0x35}, + {0x5c4d, 0x23}, + {0x5c5c, 0x25}, + {0x5c6b, 0x31}, + {0x5c7a, 0x2e}, + {0x5bfd, 0x35}, + {0x5c0c, 0x36}, + {0x5c1b, 0x35}, + {0x5c2a, 0x37}, + {0x5c4f, 0x30}, + {0x5c5e, 0x33}, + {0x5c6d, 0x36}, + {0x5c7c, 0x5b}, + {0x5bff, 0x2e}, + {0x5c0e, 0x34}, + {0x5c1d, 0x33}, + {0x5c2c, 0x32}, + {0x5c51, 0x28}, + {0x5c60, 0x1f}, + {0x5c6f, 0x25}, + {0x5c7e, 0x2c}, + {0x5c01, 0x39}, + {0x5c10, 0x3a}, + {0x5c1f, 0x37}, + {0x5c2e, 0x37}, + {0x5c53, 0x1c}, + {0x5c62, 0x1c}, + {0x5c71, 0x28}, + {0x5c80, 0x24}, + {0x5c4c, 0x2c}, + {0x33f2, 0x03}, + {0x1f08, 0x00}, + {0x32c8, 0x00}, + {0x4017, 0x40}, + {0x40a2, 0x01}, + {0x40ac, 0x01}, + {0x4328, 0x00}, + {0x4329, 0xb3}, + {0x4e15, 0x10}, + {0x4e19, 0x2f}, + {0x4e21, 0x0f}, + {0x4e2f, 0x10}, + {0x4e3d, 0x10}, + {0x4e41, 0x2f}, + {0x4e57, 0x29}, + {0x4ffb, 0x2f}, + {0x5011, 0x24}, + {0x501d, 0x03}, + {0x505f, 0x41}, + {0x5060, 0xdf}, + {0x5065, 0xdf}, + {0x5066, 0x37}, + {0x506e, 0x57}, + {0x5070, 0xc5}, + {0x5072, 0x57}, + {0x5075, 0x53}, + {0x5076, 0x55}, + {0x5077, 0xc1}, + {0x5078, 0xc3}, + {0x5079, 0x53}, + {0x507a, 0x55}, + {0x507d, 0x57}, + {0x507e, 0xdf}, + {0x507f, 0xc5}, + {0x5081, 0x57}, + {0x53c8, 0x01}, + {0x53c9, 0xe2}, + {0x53ca, 0x03}, + {0x5422, 0x7a}, + {0x548e, 0x40}, + {0x5497, 0x5e}, + {0x54a1, 0x40}, + {0x54a9, 0x40}, + {0x54b2, 0x5e}, + {0x54bc, 0x40}, + {0x57c6, 0x00}, + {0x583d, 0x0e}, + {0x583e, 0x0e}, + {0x583f, 0x0e}, + {0x5840, 0x0e}, + {0x5841, 0x0e}, + {0x5842, 0x0e}, + {0x5900, 0x12}, + {0x5901, 0x12}, + {0x5902, 0x14}, + {0x5903, 0x12}, + {0x5904, 0x14}, + {0x5905, 0x12}, + {0x5906, 0x14}, + {0x5907, 0x12}, + {0x590f, 0x12}, + {0x5911, 0x12}, + {0x5913, 0x12}, + {0x591c, 0x12}, + {0x591e, 0x12}, + {0x5920, 0x12}, + {0x5948, 0x08}, + {0x5949, 0x08}, + {0x594a, 0x08}, + {0x594b, 0x08}, + {0x594c, 0x08}, + {0x594d, 0x08}, + {0x594e, 0x08}, + {0x594f, 0x08}, + {0x595c, 0x08}, + {0x595e, 0x08}, + {0x5960, 0x08}, + {0x596e, 0x08}, + {0x5970, 0x08}, + {0x5972, 0x08}, + {0x597e, 0x0f}, + {0x597f, 0x0f}, + {0x599a, 0x0f}, + {0x59de, 0x08}, + {0x59df, 0x08}, + {0x59fa, 0x08}, + {0x5a59, 0x22}, + {0x5a5b, 0x22}, + {0x5a5d, 0x1a}, + {0x5a5f, 0x22}, + {0x5a61, 0x1a}, + {0x5a63, 0x22}, + {0x5a65, 0x1a}, + {0x5a67, 0x22}, + {0x5a77, 0x22}, + {0x5a7b, 0x22}, + {0x5a7f, 0x22}, + {0x5a91, 0x22}, + {0x5a95, 0x22}, + {0x5a99, 0x22}, + {0x5ae9, 0x66}, + {0x5aeb, 0x66}, + {0x5aed, 0x5e}, + {0x5aef, 0x66}, + {0x5af1, 0x5e}, + {0x5af3, 0x66}, + {0x5af5, 0x5e}, + {0x5af7, 0x66}, + {0x5b07, 0x66}, + {0x5b0b, 0x66}, + {0x5b0f, 0x66}, + {0x5b21, 0x66}, + {0x5b25, 0x66}, + {0x5b29, 0x66}, + {0x5b79, 0x46}, + {0x5b7b, 0x3e}, + {0x5b7d, 0x3e}, + {0x5b89, 0x46}, + {0x5b8b, 0x46}, + {0x5b97, 0x46}, + {0x5b99, 0x46}, + {0x5c9e, 0x0a}, + {0x5c9f, 0x08}, + {0x5ca0, 0x0a}, + {0x5ca1, 0x0a}, + {0x5ca2, 0x0b}, + {0x5ca3, 0x06}, + {0x5ca4, 0x04}, + {0x5ca5, 0x06}, + {0x5ca6, 0x04}, + {0x5cad, 0x0b}, + {0x5cae, 0x0a}, + {0x5caf, 0x0c}, + {0x5cb0, 0x0a}, + {0x5cb1, 0x0b}, + {0x5cb2, 0x08}, + {0x5cb3, 0x06}, + {0x5cb4, 0x08}, + {0x5cb5, 0x04}, + {0x5cbc, 0x0b}, + {0x5cbd, 0x09}, + {0x5cbe, 0x08}, + {0x5cbf, 0x09}, + {0x5cc0, 0x0a}, + {0x5cc1, 0x08}, + {0x5cc2, 0x06}, + {0x5cc3, 0x08}, + {0x5cc4, 0x06}, + {0x5ccb, 0x0a}, + {0x5ccc, 0x09}, + {0x5ccd, 0x0a}, + {0x5cce, 0x08}, + {0x5ccf, 0x0a}, + {0x5cd0, 0x08}, + {0x5cd1, 0x08}, + {0x5cd2, 0x08}, + {0x5cd3, 0x08}, + {0x5cda, 0x09}, + {0x5cdb, 0x09}, + {0x5cdc, 0x08}, + {0x5cdd, 0x08}, + {0x5ce3, 0x09}, + {0x5ce4, 0x08}, + {0x5ce5, 0x08}, + {0x5ce6, 0x08}, + {0x5cf4, 0x04}, + {0x5d04, 0x04}, + {0x5d13, 0x06}, + {0x5d22, 0x06}, + {0x5d23, 0x04}, + {0x5d2e, 0x06}, + {0x5d37, 0x06}, + {0x5d6f, 0x09}, + {0x5d72, 0x0f}, + {0x5d88, 0x0f}, + {0x5de6, 0x01}, + {0x5de7, 0x01}, + {0x5de8, 0x01}, + {0x5de9, 0x01}, + {0x5dea, 0x01}, + {0x5deb, 0x01}, + {0x5dec, 0x01}, + {0x5df2, 0x01}, + {0x5df3, 0x01}, + {0x5df4, 0x01}, + {0x5df5, 0x01}, + {0x5df6, 0x01}, + {0x5df7, 0x01}, + {0x5df8, 0x01}, + {0x5dfe, 0x01}, + {0x5dff, 0x01}, + {0x5e00, 0x01}, + {0x5e01, 0x01}, + {0x5e02, 0x01}, + {0x5e03, 0x01}, + {0x5e04, 0x01}, + {0x5e0a, 0x01}, + {0x5e0b, 0x01}, + {0x5e0c, 0x01}, + {0x5e0d, 0x01}, + {0x5e0e, 0x01}, + {0x5e0f, 0x01}, + {0x5e10, 0x01}, + {0x5e16, 0x01}, + {0x5e17, 0x01}, + {0x5e18, 0x01}, + {0x5e1e, 0x01}, + {0x5e1f, 0x01}, + {0x5e20, 0x01}, + {0x5e6e, 0x5a}, + {0x5e6f, 0x46}, + {0x5e70, 0x46}, + {0x5e71, 0x3c}, + {0x5e72, 0x3c}, + {0x5e73, 0x28}, + {0x5e74, 0x28}, + {0x5e75, 0x6e}, + {0x5e76, 0x6e}, + {0x5e81, 0x46}, + {0x5e83, 0x3c}, + {0x5e85, 0x28}, + {0x5e87, 0x6e}, + {0x5e92, 0x46}, + {0x5e94, 0x3c}, + {0x5e96, 0x28}, + {0x5e98, 0x6e}, + {0x5ecb, 0x26}, + {0x5ecc, 0x26}, + {0x5ecd, 0x26}, + {0x5ece, 0x26}, + {0x5ed2, 0x26}, + {0x5ed3, 0x26}, + {0x5ed4, 0x26}, + {0x5ed5, 0x26}, + {0x5ed9, 0x26}, + {0x5eda, 0x26}, + {0x5ee5, 0x08}, + {0x5ee6, 0x08}, + {0x5ee7, 0x08}, + {0x6006, 0x14}, + {0x6007, 0x14}, + {0x6008, 0x14}, + {0x6009, 0x14}, + {0x600a, 0x14}, + {0x600b, 0x14}, + {0x600c, 0x14}, + {0x600d, 0x22}, + {0x600e, 0x22}, + {0x600f, 0x14}, + {0x601a, 0x14}, + {0x601b, 0x14}, + {0x601c, 0x14}, + {0x601d, 0x14}, + {0x601e, 0x14}, + {0x601f, 0x14}, + {0x6020, 0x14}, + {0x6021, 0x22}, + {0x6022, 0x22}, + {0x6023, 0x14}, + {0x602e, 0x14}, + {0x602f, 0x14}, + {0x6030, 0x14}, + {0x6031, 0x22}, + {0x6039, 0x14}, + {0x603a, 0x14}, + {0x603b, 0x14}, + {0x603c, 0x22}, + {0x6132, 0x0f}, + {0x6133, 0x0f}, + {0x6134, 0x0f}, + {0x6135, 0x0f}, + {0x6136, 0x0f}, + {0x6137, 0x0f}, + {0x6138, 0x0f}, + {0x613e, 0x0f}, + {0x613f, 0x0f}, + {0x6140, 0x0f}, + {0x6141, 0x0f}, + {0x6142, 0x0f}, + {0x6143, 0x0f}, + {0x6144, 0x0f}, + {0x614a, 0x0f}, + {0x614b, 0x0f}, + {0x614c, 0x0f}, + {0x614d, 0x0f}, + {0x614e, 0x0f}, + {0x614f, 0x0f}, + {0x6150, 0x0f}, + {0x6156, 0x0f}, + {0x6157, 0x0f}, + {0x6158, 0x0f}, + {0x6159, 0x0f}, + {0x615a, 0x0f}, + {0x615b, 0x0f}, + {0x615c, 0x0f}, + {0x6162, 0x0f}, + {0x6163, 0x0f}, + {0x6164, 0x0f}, + {0x616a, 0x0f}, + {0x616b, 0x0f}, + {0x616c, 0x0f}, + {0x6226, 0x00}, + {0x84f8, 0x01}, + {0x8501, 0x00}, + {0x8502, 0x01}, + {0x8505, 0x00}, + {0x8744, 0x00}, + {0x883c, 0x01}, + {0x8845, 0x00}, + {0x8846, 0x01}, + {0x8849, 0x00}, + {0x9004, 0x1f}, + {0x9064, 0x4d}, + {0x9065, 0x3d}, + {0x922e, 0x91}, + {0x922f, 0x2a}, + {0x9230, 0xe2}, + {0x9231, 0xc0}, + {0x9232, 0xe2}, + {0x9233, 0xc1}, + {0x9234, 0xe2}, + {0x9235, 0xc2}, + {0x9236, 0xe2}, + {0x9237, 0xc3}, + {0x9238, 0xe2}, + {0x9239, 0xd4}, + {0x923a, 0xe2}, + {0x923b, 0xd5}, + {0x923c, 0x90}, + {0x923d, 0x64}, + {0xb0b9, 0x10}, + {0xbc76, 0x00}, + {0xbc77, 0x00}, + {0xbc78, 0x00}, + {0xbc79, 0x00}, + {0xbc7b, 0x28}, + {0xbc7c, 0x00}, + {0xbc7d, 0x00}, + {0xbc7f, 0xc0}, + {0xc6b9, 0x01}, + {0xecb5, 0x04}, + {0xecbf, 0x04}, + {0x0112, 0x0a}, + {0x0113, 0x0a}, + {0x0114, 0x01}, + {0x0301, 0x08}, + {0x0303, 0x02}, + {0x0305, 0x04}, + {0x0306, 0x01}, + {0x0307, 0x2c}, + {0x030b, 0x02}, + {0x030d, 0x04}, + {0x030e, 0x01}, + {0x030f, 0x30}, + {0x0310, 0x01}, + {0x4018, 0x00}, + {0x4019, 0x00}, + {0x401a, 0x00}, + {0x401b, 0x00}, + {0x3400, 0x01}, + {0x3092, 0x01}, + {0x3093, 0x00}, + {0x0350, 0x00}, + {0x3419, 0x00}, +}; + +/* 64 mpix 2.7fps */ +static const struct arducam_64mp_reg mode_9152x6944_regs[] = { + {0x0342, 0xb6}, + {0x0343, 0xb2}, + {0x0340, 0x1b}, + {0x0341, 0x76}, + {0x0344, 0x00}, + {0x0345, 0x00}, + {0x0346, 0x00}, + {0x0347, 0x00}, + {0x0348, 0x24}, + {0x0349, 0x1f}, + {0x034a, 0x1b}, + {0x034b, 0x1f}, + {0x0900, 0x00}, + {0x0901, 0x11}, + {0x0902, 0x0a}, + {0x30d8, 0x00}, + {0x3200, 0x01}, + {0x3201, 0x01}, + {0x0408, 0x00}, + {0x0409, 0x00}, + {0x040a, 0x00}, + {0x040b, 0x00}, + {0x040c, 0x23}, + {0x040d, 0xc0}, + {0x040e, 0x1b}, + {0x040f, 0x20}, + {0x034c, 0x23}, + {0x034d, 0xc0}, + {0x034e, 0x1b}, + {0x034f, 0x20}, + {0x30d9, 0x01}, + {0x32d5, 0x01}, + {0x32d6, 0x00}, + {0x401e, 0x00}, + {0x40b8, 0x04}, + {0x40b9, 0x20}, + {0x40bc, 0x02}, + {0x40bd, 0x58}, + {0x40be, 0x02}, + {0x40bf, 0x58}, + {0x41a4, 0x00}, + {0x5a09, 0x01}, + {0x5a17, 0x01}, + {0x5a25, 0x01}, + {0x5a33, 0x01}, + {0x98d7, 0x14}, + {0x98d8, 0x14}, + {0x98d9, 0x00}, + {0x99c4, 0x00}, + {0x0202, 0x03}, + {0x0203, 0xe8}, + {0x0204, 0x00}, + {0x0205, 0x00}, + {0x020e, 0x01}, + {0x020f, 0x00}, + {0x341a, 0x00}, + {0x341b, 0x00}, + {0x341c, 0x00}, + {0x341d, 0x00}, + {0x341e, 0x02}, + {0x341f, 0x3c}, + {0x3420, 0x02}, + {0x3421, 0x42}, +}; + +/* 48 mpix 3.0fps */ +static const struct arducam_64mp_reg mode_8000x6000_regs[] = { + {0x0342, 0xb6}, + {0x0343, 0xb2}, + {0x0340, 0x19}, + {0x0341, 0x0e}, + {0x0344, 0x02}, + {0x0345, 0x70}, + {0x0346, 0x01}, + {0x0347, 0xd8}, + {0x0348, 0x21}, + {0x0349, 0xaf}, + {0x034a, 0x19}, + {0x034b, 0x47}, + {0x0900, 0x00}, + {0x0901, 0x11}, + {0x0902, 0x0a}, + {0x30d8, 0x00}, + {0x3200, 0x01}, + {0x3201, 0x01}, + {0x0408, 0x00}, + {0x0409, 0x00}, + {0x040a, 0x00}, + {0x040b, 0x00}, + {0x040c, 0x1f}, + {0x040d, 0x40}, + {0x040e, 0x17}, + {0x040f, 0x70}, + {0x034c, 0x1f}, + {0x034d, 0x40}, + {0x034e, 0x17}, + {0x034f, 0x70}, + {0x30d9, 0x01}, + {0x32d5, 0x01}, + {0x32d6, 0x00}, + {0x401e, 0x00}, + {0x40b8, 0x04}, + {0x40b9, 0x20}, + {0x40bc, 0x02}, + {0x40bd, 0x58}, + {0x40be, 0x02}, + {0x40bf, 0x58}, + {0x41a4, 0x00}, + {0x5a09, 0x01}, + {0x5a17, 0x01}, + {0x5a25, 0x01}, + {0x5a33, 0x01}, + {0x98d7, 0x14}, + {0x98d8, 0x14}, + {0x98d9, 0x00}, + {0x99c4, 0x00}, + {0x0202, 0x03}, + {0x0203, 0xe8}, + {0x0204, 0x00}, + {0x0205, 0x00}, + {0x020e, 0x01}, + {0x020f, 0x00}, + {0x341a, 0x00}, + {0x341b, 0x00}, + {0x341c, 0x00}, + {0x341d, 0x00}, + {0x341e, 0x01}, + {0x341f, 0xf4}, + {0x3420, 0x01}, + {0x3421, 0xf4}, +}; + +/* 16 mpix 10fps */ +static const struct arducam_64mp_reg mode_4624x3472_regs[] = { + {0x0342, 0x63}, + {0x0343, 0x97}, + {0x0340, 0x0d}, + {0x0341, 0xca}, + {0x0344, 0x00}, + {0x0345, 0x00}, + {0x0346, 0x00}, + {0x0347, 0x00}, + {0x0348, 0x24}, + {0x0349, 0x1f}, + {0x034a, 0x1b}, + {0x034b, 0x1f}, + {0x0900, 0x01}, + {0x0901, 0x22}, + {0x0902, 0x08}, + {0x30d8, 0x04}, + {0x3200, 0x41}, + {0x3201, 0x41}, + {0x0408, 0x00}, + {0x0409, 0x00}, + {0x040a, 0x00}, + {0x040b, 0x00}, + {0x040c, 0x12}, + {0x040d, 0x10}, + {0x040e, 0x0d}, + {0x040f, 0x90}, + {0x034c, 0x12}, + {0x034d, 0x10}, + {0x034e, 0x0d}, + {0x034f, 0x90}, + {0x30d9, 0x00}, + {0x32d5, 0x00}, + {0x32d6, 0x00}, + {0x401e, 0x00}, + {0x40b8, 0x01}, + {0x40b9, 0x2c}, + {0x40bc, 0x01}, + {0x40bd, 0x18}, + {0x40be, 0x00}, + {0x40bf, 0x00}, + {0x41a4, 0x00}, + {0x5a09, 0x01}, + {0x5a17, 0x01}, + {0x5a25, 0x01}, + {0x5a33, 0x01}, + {0x98d7, 0xb4}, + {0x98d8, 0x8c}, + {0x98d9, 0x0a}, + {0x99c4, 0x16}, + {0x341a, 0x00}, + {0x341b, 0x00}, + {0x341c, 0x00}, + {0x341d, 0x00}, + {0x341e, 0x01}, + {0x341f, 0x21}, + {0x3420, 0x01}, + {0x3421, 0x21}, +}; + +/* 4k 20fps mode */ +static const struct arducam_64mp_reg mode_3840x2160_regs[] = { + {0x0342, 0x4e}, + {0x0343, 0xb7}, + {0x0340, 0x08}, + {0x0341, 0xb9}, + {0x0344, 0x03}, + {0x0345, 0x10}, + {0x0346, 0x05}, + {0x0347, 0x20}, + {0x0348, 0x21}, + {0x0349, 0x0f}, + {0x034a, 0x15}, + {0x034b, 0xff}, + {0x0900, 0x01}, + {0x0901, 0x22}, + {0x0902, 0x08}, + {0x30d8, 0x04}, + {0x3200, 0x41}, + {0x3201, 0x41}, + {0x0408, 0x00}, + {0x0409, 0x00}, + {0x040a, 0x00}, + {0x040b, 0x00}, + {0x040c, 0x0f}, + {0x040d, 0x00}, + {0x040e, 0x08}, + {0x040f, 0x70}, + {0x034c, 0x0f}, + {0x034d, 0x00}, + {0x034e, 0x08}, + {0x034f, 0x70}, + {0x30d9, 0x00}, + {0x32d5, 0x00}, + {0x32d6, 0x00}, + {0x401e, 0x00}, + {0x40b8, 0x01}, + {0x40b9, 0x2c}, + {0x40bc, 0x01}, + {0x40bd, 0x18}, + {0x40be, 0x00}, + {0x40bf, 0x00}, + {0x41a4, 0x00}, + {0x5a09, 0x01}, + {0x5a17, 0x01}, + {0x5a25, 0x01}, + {0x5a33, 0x01}, + {0x98d7, 0xb4}, + {0x98d8, 0x8c}, + {0x98d9, 0x0a}, + {0x99c4, 0x16}, + {0x341a, 0x00}, + {0x341b, 0x00}, + {0x341c, 0x00}, + {0x341d, 0x00}, + {0x341e, 0x00}, + {0x341f, 0xf0}, + {0x3420, 0x00}, + {0x3421, 0xb4}, +}; + +/* 4x4 binned 30fps mode */ +static const struct arducam_64mp_reg mode_2312x1736_regs[] = { + {0x0342, 0x33}, + {0x0343, 0x60}, + {0x0340, 0x08}, + {0x0341, 0xe9}, + {0x0344, 0x00}, + {0x0345, 0x00}, + {0x0346, 0x00}, + {0x0347, 0x00}, + {0x0348, 0x24}, + {0x0349, 0x1f}, + {0x034a, 0x1b}, + {0x034b, 0x1f}, + {0x0900, 0x01}, + {0x0901, 0x44}, + {0x0902, 0x08}, + {0x30d8, 0x04}, + {0x3200, 0x43}, + {0x3201, 0x43}, + {0x0408, 0x00}, + {0x0409, 0x00}, + {0x040a, 0x00}, + {0x040b, 0x00}, + {0x040c, 0x09}, + {0x040d, 0x08}, + {0x040e, 0x06}, + {0x040f, 0xc8}, + {0x034c, 0x09}, + {0x034d, 0x08}, + {0x034e, 0x06}, + {0x034f, 0xc8}, + {0x30d9, 0x00}, + {0x32d5, 0x00}, + {0x32d6, 0x00}, + {0x401e, 0x00}, + {0x40b8, 0x01}, + {0x40b9, 0x2c}, + {0x40bc, 0x01}, + {0x40bd, 0x18}, + {0x40be, 0x00}, + {0x40bf, 0x00}, + {0x41a4, 0x00}, + {0x5a09, 0x01}, + {0x5a17, 0x01}, + {0x5a25, 0x01}, + {0x5a33, 0x01}, + {0x98d7, 0xb4}, + {0x98d8, 0x8c}, + {0x98d9, 0x0a}, + {0x99c4, 0x16}, + {0x341a, 0x00}, + {0x341b, 0x00}, + {0x341c, 0x00}, + {0x341d, 0x00}, + {0x341e, 0x00}, + {0x341f, 0x90}, + {0x3420, 0x00}, + {0x3421, 0x90}, +}; + +/* 1080p 60fps mode */ +static const struct arducam_64mp_reg mode_1920x1080_regs[] = { + {0x0342, 0x29}, + {0x0343, 0xe3}, + {0x0340, 0x05}, + {0x0341, 0x76}, + {0x0344, 0x03}, + {0x0345, 0x10}, + {0x0346, 0x05}, + {0x0347, 0x20}, + {0x0348, 0x21}, + {0x0349, 0x0f}, + {0x034a, 0x16}, + {0x034b, 0x0f}, + {0x0900, 0x01}, + {0x0901, 0x44}, + {0x0902, 0x08}, + {0x30d8, 0x04}, + {0x3200, 0x43}, + {0x3201, 0x43}, + {0x0408, 0x00}, + {0x0409, 0x00}, + {0x040a, 0x00}, + {0x040b, 0x00}, + {0x040c, 0x07}, + {0x040d, 0x80}, + {0x040e, 0x04}, + {0x040f, 0x38}, + {0x034c, 0x07}, + {0x034d, 0x80}, + {0x034e, 0x04}, + {0x034f, 0x38}, + {0x30d9, 0x00}, + {0x32d5, 0x00}, + {0x32d6, 0x00}, + {0x401e, 0x00}, + {0x40b8, 0x01}, + {0x40b9, 0x2c}, + {0x40bc, 0x01}, + {0x40bd, 0x18}, + {0x40be, 0x00}, + {0x40bf, 0x00}, + {0x41a4, 0x00}, + {0x5a09, 0x01}, + {0x5a17, 0x01}, + {0x5a25, 0x01}, + {0x5a33, 0x01}, + {0x98d7, 0xb4}, + {0x98d8, 0x8c}, + {0x98d9, 0x0a}, + {0x99c4, 0x16}, + {0x341a, 0x00}, + {0x341b, 0x00}, + {0x341c, 0x00}, + {0x341d, 0x00}, + {0x341e, 0x00}, + {0x341f, 0x78}, + {0x3420, 0x00}, + {0x3421, 0x5a}, +}; + +/* 720p 120fps mode */ +static const struct arducam_64mp_reg mode_1280x720_regs[] = { + {0x0342, 0x1b}, + {0x0343, 0x08}, + {0x0340, 0x04}, + {0x0341, 0x3b}, + {0x0344, 0x08}, + {0x0345, 0x10}, + {0x0346, 0x07}, + {0x0347, 0xf0}, + {0x0348, 0x1c}, + {0x0349, 0x0f}, + {0x034a, 0x13}, + {0x034b, 0x3f}, + {0x0900, 0x01}, + {0x0901, 0x44}, + {0x0902, 0x08}, + {0x30d8, 0x04}, + {0x3200, 0x43}, + {0x3201, 0x43}, + {0x0408, 0x00}, + {0x0409, 0x00}, + {0x040a, 0x00}, + {0x040b, 0x00}, + {0x040c, 0x05}, + {0x040d, 0x00}, + {0x040e, 0x02}, + {0x040f, 0xd0}, + {0x034c, 0x05}, + {0x034d, 0x00}, + {0x034e, 0x02}, + {0x034f, 0xd0}, + {0x30d9, 0x00}, + {0x32d5, 0x00}, + {0x32d6, 0x00}, + {0x401e, 0x00}, + {0x40b8, 0x01}, + {0x40b9, 0x2c}, + {0x40bc, 0x01}, + {0x40bd, 0x18}, + {0x40be, 0x00}, + {0x40bf, 0x00}, + {0x41a4, 0x00}, + {0x5a09, 0x01}, + {0x5a17, 0x01}, + {0x5a25, 0x01}, + {0x5a33, 0x01}, + {0x98d7, 0xb4}, + {0x98d8, 0x8c}, + {0x98d9, 0x0a}, + {0x99c4, 0x16}, + {0x341a, 0x00}, + {0x341b, 0x00}, + {0x341c, 0x00}, + {0x341d, 0x00}, + {0x341e, 0x00}, + {0x341f, 0x50}, + {0x3420, 0x00}, + {0x3421, 0x3c}, +}; + +/* Mode configs */ +static const struct arducam_64mp_mode supported_modes[] = { + { + .width = 9152, + .height = 6944, + .line_length_pix = 0xb6b2, + .crop = { + .left = ARDUCAM_64MP_PIXEL_ARRAY_LEFT, + .top = ARDUCAM_64MP_PIXEL_ARRAY_TOP, + .width = 9248, + .height = 6944, + }, + .timeperframe_default = { + .numerator = 100, + .denominator = 270 + }, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_9152x6944_regs), + .regs = mode_9152x6944_regs, + } + }, { + .width = 8000, + .height = 6000, + .line_length_pix = 0xb6b2, + .crop = { + .left = ARDUCAM_64MP_PIXEL_ARRAY_LEFT + 624, + .top = ARDUCAM_64MP_PIXEL_ARRAY_TOP + 472, + .width = 9248, + .height = 6944, + }, + .timeperframe_default = { + .numerator = 100, + .denominator = 300 + }, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_8000x6000_regs), + .regs = mode_8000x6000_regs, + } + }, { + .width = 4624, + .height = 3472, + .line_length_pix = 0x6397, + .crop = { + .left = ARDUCAM_64MP_PIXEL_ARRAY_LEFT, + .top = ARDUCAM_64MP_PIXEL_ARRAY_TOP, + .width = 9248, + .height = 6944, + }, + .timeperframe_default = { + .numerator = 100, + .denominator = 1000 + }, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_4624x3472_regs), + .regs = mode_4624x3472_regs, + } + }, { + .width = 3840, + .height = 2160, + .line_length_pix = 0x4eb7, + .crop = { + .left = ARDUCAM_64MP_PIXEL_ARRAY_LEFT + 784, + .top = ARDUCAM_64MP_PIXEL_ARRAY_TOP + 1312, + .width = 7680, + .height = 4320, + }, + .timeperframe_default = { + .numerator = 100, + .denominator = 2000 + }, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_3840x2160_regs), + .regs = mode_3840x2160_regs, + } + }, { + .width = 2312, + .height = 1736, + .line_length_pix = 0x3360, + .crop = { + .left = ARDUCAM_64MP_PIXEL_ARRAY_LEFT, + .top = ARDUCAM_64MP_PIXEL_ARRAY_TOP, + .width = 9248, + .height = 6944, + }, + .timeperframe_default = { + .numerator = 100, + .denominator = 3000 + }, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_2312x1736_regs), + .regs = mode_2312x1736_regs, + } + }, { + .width = 1920, + .height = 1080, + .line_length_pix = 0x29e3, + .crop = { + .left = ARDUCAM_64MP_PIXEL_ARRAY_LEFT + 784, + .top = ARDUCAM_64MP_PIXEL_ARRAY_TOP + 1312, + .width = 7680, + .height = 4320, + }, + .timeperframe_default = { + .numerator = 100, + .denominator = 6000 + }, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1920x1080_regs), + .regs = mode_1920x1080_regs, + } + }, { + .width = 1280, + .height = 720, + .line_length_pix = 0x1b08, + .crop = { + .left = ARDUCAM_64MP_PIXEL_ARRAY_LEFT + 2064, + .top = ARDUCAM_64MP_PIXEL_ARRAY_TOP + 2032, + .width = 5120, + .height = 2880, + }, + .timeperframe_default = { + .numerator = 100, + .denominator = 12000 + }, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1280x720_regs), + .regs = mode_1280x720_regs, + } + }, +}; + +/* + * The supported formats. + * This table MUST contain 4 entries per format, to cover the various flip + * combinations in the order + * - no flip + * - h flip + * - v flip + * - h&v flips + */ +static const u32 codes[] = { + /* 10-bit modes. */ + MEDIA_BUS_FMT_SRGGB10_1X10, + MEDIA_BUS_FMT_SGRBG10_1X10, + MEDIA_BUS_FMT_SGBRG10_1X10, + MEDIA_BUS_FMT_SBGGR10_1X10, +}; + +static const char * const arducam_64mp_test_pattern_menu[] = { + "Disabled", + "Color Bars", + "Solid Color", + "Grey Color Bars", + "PN9" +}; + +static const int arducam_64mp_test_pattern_val[] = { + ARDUCAM_64MP_TEST_PATTERN_DISABLE, + ARDUCAM_64MP_TEST_PATTERN_COLOR_BARS, + ARDUCAM_64MP_TEST_PATTERN_SOLID_COLOR, + ARDUCAM_64MP_TEST_PATTERN_GREY_COLOR, + ARDUCAM_64MP_TEST_PATTERN_PN9, +}; + +/* regulator supplies */ +static const char * const arducam_64mp_supply_name[] = { + /* Supplies can be enabled in any order */ + "VANA", /* Analog (2.8V) supply */ + "VDIG", /* Digital Core (1.05V) supply */ + "VDDL", /* IF (1.8V) supply */ +}; + +#define ARDUCAM_64MP_NUM_SUPPLIES ARRAY_SIZE(arducam_64mp_supply_name) + +/* + * Initialisation delay between XCLR low->high and the moment when the sensor + * can start capture (i.e. can leave software standby), given by T7 in the + * datasheet is 7.7ms. This does include I2C setup time as well. + * + * Note, that delay between XCLR low->high and reading the CCI ID register (T6 + * in the datasheet) is much smaller - 1ms. + */ +#define ARDUCAM_64MP_XCLR_MIN_DELAY_US 8000 +#define ARDUCAM_64MP_XCLR_DELAY_RANGE_US 1000 + +struct arducam_64mp { + struct v4l2_subdev sd; + struct media_pad pad[NUM_PADS]; + + unsigned int fmt_code; + + struct clk *xclk; + + struct gpio_desc *reset_gpio; + struct regulator_bulk_data supplies[ARDUCAM_64MP_NUM_SUPPLIES]; + + struct v4l2_ctrl_handler ctrl_handler; + /* V4L2 Controls */ + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *vflip; + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + + /* Current mode */ + const struct arducam_64mp_mode *mode; + + /* + * Mutex for serialized access: + * Protect sensor module set pad format and start/stop streaming safely. + */ + struct mutex mutex; + + /* Streaming on/off */ + bool streaming; + + /* Rewrite common registers on stream on? */ + bool common_regs_written; + + /* Current long exposure factor in use. Set through V4L2_CID_VBLANK */ + unsigned int long_exp_shift; +}; + +static inline struct arducam_64mp *to_arducam_64mp(struct v4l2_subdev *_sd) +{ + return container_of(_sd, struct arducam_64mp, sd); +} + +/* Read registers up to 2 at a time */ +static int arducam_64mp_read_reg(struct i2c_client *client, + u16 reg, u32 len, u32 *val) +{ + struct i2c_msg msgs[2]; + u8 addr_buf[2] = { reg >> 8, reg & 0xff }; + u8 data_buf[4] = { 0, }; + int ret; + + if (len > 4) + return -EINVAL; + + /* Write register address */ + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = ARRAY_SIZE(addr_buf); + msgs[0].buf = addr_buf; + + /* Read data from register */ + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = &data_buf[4 - len]; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + *val = get_unaligned_be32(data_buf); + + return 0; +} + +/* Write registers up to 2 at a time */ +static int arducam_64mp_write_reg(struct arducam_64mp *arducam_64mp, + u16 reg, u32 len, u32 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&arducam_64mp->sd); + u8 buf[6]; + + if (len > 4) + return -EINVAL; + + put_unaligned_be16(reg, buf); + put_unaligned_be32(val << (8 * (4 - len)), buf + 2); + if (i2c_master_send(client, buf, len + 2) != len + 2) + return -EIO; + + return 0; +} + +/* Write a list of registers */ +static int arducam_64mp_write_regs(struct arducam_64mp *arducam_64mp, + const struct arducam_64mp_reg *regs, u32 len) +{ + struct i2c_client *client = v4l2_get_subdevdata(&arducam_64mp->sd); + unsigned int i; + int ret; + + for (i = 0; i < len; i++) { + ret = arducam_64mp_write_reg(arducam_64mp, regs[i].address, 1, + regs[i].val); + if (ret) { + dev_err_ratelimited(&client->dev, + "Failed to write reg 0x%4.4x. error = %d\n", + regs[i].address, ret); + + return ret; + } + } + + return 0; +} + +/* Get bayer order based on flip setting. */ +static u32 arducam_64mp_get_format_code(struct arducam_64mp *arducam_64mp) +{ + unsigned int i; + + lockdep_assert_held(&arducam_64mp->mutex); + + i = (arducam_64mp->vflip->val ? 2 : 0) | + (arducam_64mp->hflip->val ? 1 : 0); + + return codes[i]; +} + +static int arducam_64mp_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct arducam_64mp *arducam_64mp = to_arducam_64mp(sd); + struct v4l2_mbus_framefmt *try_fmt_img = + v4l2_subdev_state_get_format(fh->state, IMAGE_PAD); + struct v4l2_mbus_framefmt *try_fmt_meta = + v4l2_subdev_state_get_format(fh->state, METADATA_PAD); + struct v4l2_rect *try_crop; + + mutex_lock(&arducam_64mp->mutex); + + /* Initialize try_fmt for the image pad */ + try_fmt_img->width = supported_modes[0].width; + try_fmt_img->height = supported_modes[0].height; + try_fmt_img->code = arducam_64mp_get_format_code(arducam_64mp); + try_fmt_img->field = V4L2_FIELD_NONE; + + /* Initialize try_fmt for the embedded metadata pad */ + try_fmt_meta->width = ARDUCAM_64MP_EMBEDDED_LINE_WIDTH; + try_fmt_meta->height = ARDUCAM_64MP_NUM_EMBEDDED_LINES; + try_fmt_meta->code = MEDIA_BUS_FMT_SENSOR_DATA; + try_fmt_meta->field = V4L2_FIELD_NONE; + + /* Initialize try_crop */ + try_crop = v4l2_subdev_state_get_crop(fh->state, IMAGE_PAD); + try_crop->left = ARDUCAM_64MP_PIXEL_ARRAY_LEFT; + try_crop->top = ARDUCAM_64MP_PIXEL_ARRAY_TOP; + try_crop->width = ARDUCAM_64MP_PIXEL_ARRAY_WIDTH; + try_crop->height = ARDUCAM_64MP_PIXEL_ARRAY_HEIGHT; + + mutex_unlock(&arducam_64mp->mutex); + + return 0; +} + +static void +arducam_64mp_adjust_exposure_range(struct arducam_64mp *arducam_64mp) +{ + int exposure_max, exposure_def; + + /* Honour the VBLANK limits when setting exposure. */ + exposure_max = arducam_64mp->mode->height + arducam_64mp->vblank->val - + ARDUCAM_64MP_EXPOSURE_OFFSET; + exposure_def = min(exposure_max, arducam_64mp->exposure->val); + __v4l2_ctrl_modify_range(arducam_64mp->exposure, + arducam_64mp->exposure->minimum, + exposure_max, arducam_64mp->exposure->step, + exposure_def); +} + +static int arducam_64mp_set_frame_length(struct arducam_64mp *arducam_64mp, + unsigned int vblank) +{ + unsigned int val = vblank + arducam_64mp->mode->height; + int ret = 0; + + arducam_64mp->long_exp_shift = 0; + + while (val > ARDUCAM_64MP_FRAME_LENGTH_MAX) { + arducam_64mp->long_exp_shift++; + val >>= 1; + } + + ret = arducam_64mp_write_reg(arducam_64mp, + ARDUCAM_64MP_REG_FRAME_LENGTH, + ARDUCAM_64MP_REG_VALUE_16BIT, val); + if (ret) + return ret; + + return arducam_64mp_write_reg(arducam_64mp, + ARDUCAM_64MP_LONG_EXP_SHIFT_REG, + ARDUCAM_64MP_REG_VALUE_08BIT, + arducam_64mp->long_exp_shift); +} + +static int arducam_64mp_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct arducam_64mp *arducam_64mp = + container_of(ctrl->handler, struct arducam_64mp, ctrl_handler); + struct i2c_client *client = v4l2_get_subdevdata(&arducam_64mp->sd); + int ret; + u32 val; + /* + * The VBLANK control may change the limits of usable exposure, so check + * and adjust if necessary. + */ + if (ctrl->id == V4L2_CID_VBLANK) + arducam_64mp_adjust_exposure_range(arducam_64mp); + + /* + * Applying V4L2 control value only happens + * when power is up for streaming + */ + if (pm_runtime_get_if_in_use(&client->dev) == 0) + return 0; + + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + ret = arducam_64mp_write_reg(arducam_64mp, + ARDUCAM_64MP_REG_ANALOG_GAIN, + ARDUCAM_64MP_REG_VALUE_16BIT, + ctrl->val); + break; + case V4L2_CID_EXPOSURE: + val = ctrl->val >> arducam_64mp->long_exp_shift; + ret = arducam_64mp_write_reg(arducam_64mp, + ARDUCAM_64MP_REG_EXPOSURE, + ARDUCAM_64MP_REG_VALUE_16BIT, + val); + break; + case V4L2_CID_DIGITAL_GAIN: + ret = arducam_64mp_write_reg(arducam_64mp, + ARDUCAM_64MP_REG_DIGITAL_GAIN, + ARDUCAM_64MP_REG_VALUE_16BIT, + ctrl->val); + break; + case V4L2_CID_TEST_PATTERN: + val = arducam_64mp_test_pattern_val[ctrl->val]; + ret = arducam_64mp_write_reg(arducam_64mp, + ARDUCAM_64MP_REG_TEST_PATTERN, + ARDUCAM_64MP_REG_VALUE_16BIT, + val); + break; + case V4L2_CID_TEST_PATTERN_RED: + ret = arducam_64mp_write_reg(arducam_64mp, + ARDUCAM_64MP_REG_TEST_PATTERN_R, + ARDUCAM_64MP_REG_VALUE_16BIT, + ctrl->val); + break; + case V4L2_CID_TEST_PATTERN_GREENR: + ret = arducam_64mp_write_reg(arducam_64mp, + ARDUCAM_64MP_REG_TEST_PATTERN_GR, + ARDUCAM_64MP_REG_VALUE_16BIT, + ctrl->val); + break; + case V4L2_CID_TEST_PATTERN_BLUE: + ret = arducam_64mp_write_reg(arducam_64mp, + ARDUCAM_64MP_REG_TEST_PATTERN_B, + ARDUCAM_64MP_REG_VALUE_16BIT, + ctrl->val); + break; + case V4L2_CID_TEST_PATTERN_GREENB: + ret = arducam_64mp_write_reg(arducam_64mp, + ARDUCAM_64MP_REG_TEST_PATTERN_GB, + ARDUCAM_64MP_REG_VALUE_16BIT, + ctrl->val); + break; + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: + ret = arducam_64mp_write_reg(arducam_64mp, + ARDUCAM_64MP_REG_ORIENTATION, 1, + arducam_64mp->hflip->val | + arducam_64mp->vflip->val << 1); + break; + case V4L2_CID_VBLANK: + ret = arducam_64mp_set_frame_length(arducam_64mp, ctrl->val); + break; + default: + dev_info(&client->dev, + "ctrl(id:0x%x,val:0x%x) is not handled\n", + ctrl->id, ctrl->val); + ret = -EINVAL; + break; + } + + pm_runtime_put(&client->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops arducam_64mp_ctrl_ops = { + .s_ctrl = arducam_64mp_set_ctrl, +}; + +static int arducam_64mp_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct arducam_64mp *arducam_64mp = to_arducam_64mp(sd); + + if (code->pad >= NUM_PADS) + return -EINVAL; + + if (code->pad == IMAGE_PAD) { + if (code->index > 0) + return -EINVAL; + + code->code = arducam_64mp_get_format_code(arducam_64mp); + } else { + if (code->index > 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_SENSOR_DATA; + } + + return 0; +} + +static int arducam_64mp_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct arducam_64mp *arducam_64mp = to_arducam_64mp(sd); + + if (fse->pad >= NUM_PADS) + return -EINVAL; + + if (fse->pad == IMAGE_PAD) { + if (fse->index >= ARRAY_SIZE(supported_modes)) + return -EINVAL; + + if (fse->code != arducam_64mp_get_format_code(arducam_64mp)) + return -EINVAL; + + fse->min_width = supported_modes[fse->index].width; + fse->max_width = fse->min_width; + fse->min_height = supported_modes[fse->index].height; + fse->max_height = fse->min_height; + } else { + if (fse->code != MEDIA_BUS_FMT_SENSOR_DATA || fse->index > 0) + return -EINVAL; + + fse->min_width = ARDUCAM_64MP_EMBEDDED_LINE_WIDTH; + fse->max_width = fse->min_width; + fse->min_height = ARDUCAM_64MP_NUM_EMBEDDED_LINES; + fse->max_height = fse->min_height; + } + + return 0; +} + +static void arducam_64mp_reset_colorspace(struct v4l2_mbus_framefmt *fmt) +{ + fmt->colorspace = V4L2_COLORSPACE_RAW; + fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace); + fmt->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true, + fmt->colorspace, + fmt->ycbcr_enc); + fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace); +} + +static void +arducam_64mp_update_image_pad_format(struct arducam_64mp *arducam_64mp, + const struct arducam_64mp_mode *mode, + struct v4l2_subdev_format *fmt) +{ + fmt->format.width = mode->width; + fmt->format.height = mode->height; + fmt->format.field = V4L2_FIELD_NONE; + arducam_64mp_reset_colorspace(&fmt->format); +} + +static void +arducam_64mp_update_metadata_pad_format(struct v4l2_subdev_format *fmt) +{ + fmt->format.width = ARDUCAM_64MP_EMBEDDED_LINE_WIDTH; + fmt->format.height = ARDUCAM_64MP_NUM_EMBEDDED_LINES; + fmt->format.code = MEDIA_BUS_FMT_SENSOR_DATA; + fmt->format.field = V4L2_FIELD_NONE; +} + +static int arducam_64mp_get_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct arducam_64mp *arducam_64mp = to_arducam_64mp(sd); + + if (fmt->pad >= NUM_PADS) + return -EINVAL; + + mutex_lock(&arducam_64mp->mutex); + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + struct v4l2_mbus_framefmt *try_fmt = + v4l2_subdev_state_get_format(sd_state, + fmt->pad); + /* update the code which could change due to vflip or hflip: */ + try_fmt->code = fmt->pad == IMAGE_PAD ? + arducam_64mp_get_format_code(arducam_64mp) : + MEDIA_BUS_FMT_SENSOR_DATA; + fmt->format = *try_fmt; + } else { + if (fmt->pad == IMAGE_PAD) { + arducam_64mp_update_image_pad_format(arducam_64mp, + arducam_64mp->mode, + fmt); + fmt->format.code = + arducam_64mp_get_format_code(arducam_64mp); + } else { + arducam_64mp_update_metadata_pad_format(fmt); + } + } + + mutex_unlock(&arducam_64mp->mutex); + return 0; +} + +static unsigned int +arducam_64mp_get_frame_length(const struct arducam_64mp_mode *mode, + const struct v4l2_fract *timeperframe) +{ + u64 frame_length; + + frame_length = (u64)timeperframe->numerator * ARDUCAM_64MP_PIXEL_RATE; + do_div(frame_length, + (u64)timeperframe->denominator * mode->line_length_pix); + + if (WARN_ON(frame_length > ARDUCAM_64MP_FRAME_LENGTH_MAX)) + frame_length = ARDUCAM_64MP_FRAME_LENGTH_MAX; + + return max_t(unsigned int, frame_length, mode->height); +} + +static void arducam_64mp_set_framing_limits(struct arducam_64mp *arducam_64mp) +{ + unsigned int frm_length_min, frm_length_default, hblank; + const struct arducam_64mp_mode *mode = arducam_64mp->mode; + + /* The default framerate is highest possible framerate. */ + frm_length_min = + arducam_64mp_get_frame_length(mode, + &mode->timeperframe_default); + frm_length_default = + arducam_64mp_get_frame_length(mode, + &mode->timeperframe_default); + + /* Default to no long exposure multiplier. */ + arducam_64mp->long_exp_shift = 0; + + /* Update limits and set FPS to default */ + __v4l2_ctrl_modify_range(arducam_64mp->vblank, + frm_length_min - mode->height, + ((1 << ARDUCAM_64MP_LONG_EXP_SHIFT_MAX) * + ARDUCAM_64MP_FRAME_LENGTH_MAX) - mode->height, + 1, frm_length_default - mode->height); + + /* Setting this will adjust the exposure limits as well. */ + __v4l2_ctrl_s_ctrl(arducam_64mp->vblank, + frm_length_default - mode->height); + + /* + * Currently PPL is fixed to the mode specified value, so hblank + * depends on mode->width only, and is not changeable in any + * way other than changing the mode. + */ + hblank = mode->line_length_pix - mode->width; + __v4l2_ctrl_modify_range(arducam_64mp->hblank, hblank, hblank, 1, + hblank); +} + +static int arducam_64mp_set_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct v4l2_mbus_framefmt *framefmt; + const struct arducam_64mp_mode *mode; + struct arducam_64mp *arducam_64mp = to_arducam_64mp(sd); + + if (fmt->pad >= NUM_PADS) + return -EINVAL; + + mutex_lock(&arducam_64mp->mutex); + + if (fmt->pad == IMAGE_PAD) { + /* Bayer order varies with flips */ + fmt->format.code = arducam_64mp_get_format_code(arducam_64mp); + + mode = v4l2_find_nearest_size(supported_modes, + ARRAY_SIZE(supported_modes), + width, height, + fmt->format.width, + fmt->format.height); + arducam_64mp_update_image_pad_format(arducam_64mp, mode, fmt); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + framefmt = v4l2_subdev_state_get_format(sd_state, + fmt->pad); + *framefmt = fmt->format; + } else { + arducam_64mp->mode = mode; + arducam_64mp->fmt_code = fmt->format.code; + arducam_64mp_set_framing_limits(arducam_64mp); + } + } else { + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + framefmt = v4l2_subdev_state_get_format(sd_state, + fmt->pad); + *framefmt = fmt->format; + } else { + /* Only one embedded data mode is supported */ + arducam_64mp_update_metadata_pad_format(fmt); + } + } + + mutex_unlock(&arducam_64mp->mutex); + + return 0; +} + +static const struct v4l2_rect * +__arducam_64mp_get_pad_crop(struct arducam_64mp *arducam_64mp, + struct v4l2_subdev_state *sd_state, + unsigned int pad, + enum v4l2_subdev_format_whence which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_state_get_crop(sd_state, + pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &arducam_64mp->mode->crop; + } + + return NULL; +} + +static int arducam_64mp_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + switch (sel->target) { + case V4L2_SEL_TGT_CROP: { + struct arducam_64mp *arducam_64mp = to_arducam_64mp(sd); + + mutex_lock(&arducam_64mp->mutex); + sel->r = *__arducam_64mp_get_pad_crop(arducam_64mp, sd_state, + sel->pad, sel->which); + mutex_unlock(&arducam_64mp->mutex); + + return 0; + } + + case V4L2_SEL_TGT_NATIVE_SIZE: + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = ARDUCAM_64MP_NATIVE_WIDTH; + sel->r.height = ARDUCAM_64MP_NATIVE_HEIGHT; + + return 0; + + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.left = ARDUCAM_64MP_PIXEL_ARRAY_LEFT; + sel->r.top = ARDUCAM_64MP_PIXEL_ARRAY_TOP; + sel->r.width = ARDUCAM_64MP_PIXEL_ARRAY_WIDTH; + sel->r.height = ARDUCAM_64MP_PIXEL_ARRAY_HEIGHT; + + return 0; + } + + return -EINVAL; +} + +/* Start streaming */ +static int arducam_64mp_start_streaming(struct arducam_64mp *arducam_64mp) +{ + struct i2c_client *client = v4l2_get_subdevdata(&arducam_64mp->sd); + const struct arducam_64mp_reg_list *reg_list; + int ret; + + if (!arducam_64mp->common_regs_written) { + ret = arducam_64mp_write_regs(arducam_64mp, mode_common_regs, + ARRAY_SIZE(mode_common_regs)); + + if (ret) { + dev_err(&client->dev, "%s failed to set common settings\n", + __func__); + return ret; + } + arducam_64mp->common_regs_written = true; + } + + /* Apply default values of current mode */ + reg_list = &arducam_64mp->mode->reg_list; + ret = arducam_64mp_write_regs(arducam_64mp, reg_list->regs, + reg_list->num_of_regs); + if (ret) { + dev_err(&client->dev, "%s failed to set mode\n", __func__); + return ret; + } + + /* Apply customized values from user */ + ret = __v4l2_ctrl_handler_setup(arducam_64mp->sd.ctrl_handler); + if (ret) + return ret; + + /* set stream on register */ + return arducam_64mp_write_reg(arducam_64mp, + ARDUCAM_64MP_REG_MODE_SELECT, + ARDUCAM_64MP_REG_VALUE_08BIT, + ARDUCAM_64MP_MODE_STREAMING); +} + +/* Stop streaming */ +static void arducam_64mp_stop_streaming(struct arducam_64mp *arducam_64mp) +{ + struct i2c_client *client = v4l2_get_subdevdata(&arducam_64mp->sd); + int ret; + + /* set stream off register */ + ret = arducam_64mp_write_reg(arducam_64mp, ARDUCAM_64MP_REG_MODE_SELECT, + ARDUCAM_64MP_REG_VALUE_08BIT, + ARDUCAM_64MP_MODE_STANDBY); + if (ret) + dev_err(&client->dev, "%s failed to set stream\n", __func__); +} + +static int arducam_64mp_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct arducam_64mp *arducam_64mp = to_arducam_64mp(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret = 0; + + mutex_lock(&arducam_64mp->mutex); + if (arducam_64mp->streaming == enable) { + mutex_unlock(&arducam_64mp->mutex); + return 0; + } + + if (enable) { + ret = pm_runtime_get_sync(&client->dev); + if (ret < 0) { + pm_runtime_put_noidle(&client->dev); + goto err_unlock; + } + + /* + * Apply default & customized values + * and then start streaming. + */ + ret = arducam_64mp_start_streaming(arducam_64mp); + if (ret) + goto err_rpm_put; + } else { + arducam_64mp_stop_streaming(arducam_64mp); + pm_runtime_put(&client->dev); + } + + arducam_64mp->streaming = enable; + + /* vflip and hflip cannot change during streaming */ + __v4l2_ctrl_grab(arducam_64mp->vflip, enable); + __v4l2_ctrl_grab(arducam_64mp->hflip, enable); + + mutex_unlock(&arducam_64mp->mutex); + + return ret; + +err_rpm_put: + pm_runtime_put(&client->dev); +err_unlock: + mutex_unlock(&arducam_64mp->mutex); + + return ret; +} + +/* Power/clock management functions */ +static int arducam_64mp_power_on(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct arducam_64mp *arducam_64mp = to_arducam_64mp(sd); + int ret; + + ret = regulator_bulk_enable(ARDUCAM_64MP_NUM_SUPPLIES, + arducam_64mp->supplies); + if (ret) { + dev_err(&client->dev, "%s: failed to enable regulators\n", + __func__); + return ret; + } + + ret = clk_prepare_enable(arducam_64mp->xclk); + if (ret) { + dev_err(&client->dev, "%s: failed to enable clock\n", + __func__); + goto reg_off; + } + + gpiod_set_value_cansleep(arducam_64mp->reset_gpio, 1); + usleep_range(ARDUCAM_64MP_XCLR_MIN_DELAY_US, + ARDUCAM_64MP_XCLR_MIN_DELAY_US + + ARDUCAM_64MP_XCLR_DELAY_RANGE_US); + + return 0; + +reg_off: + regulator_bulk_disable(ARDUCAM_64MP_NUM_SUPPLIES, + arducam_64mp->supplies); + return ret; +} + +static int arducam_64mp_power_off(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct arducam_64mp *arducam_64mp = to_arducam_64mp(sd); + + gpiod_set_value_cansleep(arducam_64mp->reset_gpio, 0); + regulator_bulk_disable(ARDUCAM_64MP_NUM_SUPPLIES, + arducam_64mp->supplies); + clk_disable_unprepare(arducam_64mp->xclk); + + /* Force reprogramming of the common registers when powered up again. */ + arducam_64mp->common_regs_written = false; + + return 0; +} + +static int __maybe_unused arducam_64mp_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct arducam_64mp *arducam_64mp = to_arducam_64mp(sd); + + if (arducam_64mp->streaming) + arducam_64mp_stop_streaming(arducam_64mp); + + return 0; +} + +static int __maybe_unused arducam_64mp_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct arducam_64mp *arducam_64mp = to_arducam_64mp(sd); + int ret; + + if (arducam_64mp->streaming) { + ret = arducam_64mp_start_streaming(arducam_64mp); + if (ret) + goto error; + } + + return 0; + +error: + arducam_64mp_stop_streaming(arducam_64mp); + arducam_64mp->streaming = 0; + return ret; +} + +static int arducam_64mp_get_regulators(struct arducam_64mp *arducam_64mp) +{ + struct i2c_client *client = v4l2_get_subdevdata(&arducam_64mp->sd); + unsigned int i; + + for (i = 0; i < ARDUCAM_64MP_NUM_SUPPLIES; i++) + arducam_64mp->supplies[i].supply = arducam_64mp_supply_name[i]; + + return devm_regulator_bulk_get(&client->dev, + ARDUCAM_64MP_NUM_SUPPLIES, + arducam_64mp->supplies); +} + +/* Verify chip ID */ +static int arducam_64mp_identify_module(struct arducam_64mp *arducam_64mp) +{ + struct i2c_client *client = v4l2_get_subdevdata(&arducam_64mp->sd); + struct i2c_client *arducam_identifier; + int ret; + u32 val; + + arducam_identifier = i2c_new_dummy_device(client->adapter, 0x50); + if (IS_ERR(arducam_identifier)) { + dev_err(&client->dev, "failed to create arducam_identifier\n"); + return PTR_ERR(arducam_identifier); + } + + ret = arducam_64mp_read_reg(arducam_identifier, + ARDUCAM_64MP_REG_CHIP_ID, + ARDUCAM_64MP_REG_VALUE_16BIT, &val); + if (ret) { + dev_err(&client->dev, "failed to read chip id %x, with error %d\n", + ARDUCAM_64MP_CHIP_ID, ret); + goto error; + } + + if (val != ARDUCAM_64MP_CHIP_ID) { + dev_err(&client->dev, "chip id mismatch: %x!=%x\n", + ARDUCAM_64MP_CHIP_ID, val); + ret = -EIO; + goto error; + } + + dev_info(&client->dev, "Device found Arducam 64MP.\n"); + +error: + i2c_unregister_device(arducam_identifier); + + return ret; +} + +static const struct v4l2_subdev_core_ops arducam_64mp_core_ops = { + .subscribe_event = v4l2_ctrl_subdev_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_video_ops arducam_64mp_video_ops = { + .s_stream = arducam_64mp_set_stream, +}; + +static const struct v4l2_subdev_pad_ops arducam_64mp_pad_ops = { + .enum_mbus_code = arducam_64mp_enum_mbus_code, + .get_fmt = arducam_64mp_get_pad_format, + .set_fmt = arducam_64mp_set_pad_format, + .get_selection = arducam_64mp_get_selection, + .enum_frame_size = arducam_64mp_enum_frame_size, +}; + +static const struct v4l2_subdev_ops arducam_64mp_subdev_ops = { + .core = &arducam_64mp_core_ops, + .video = &arducam_64mp_video_ops, + .pad = &arducam_64mp_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops arducam_64mp_internal_ops = { + .open = arducam_64mp_open, +}; + +/* Initialize control handlers */ +static int arducam_64mp_init_controls(struct arducam_64mp *arducam_64mp) +{ + struct v4l2_ctrl_handler *ctrl_hdlr; + struct i2c_client *client = v4l2_get_subdevdata(&arducam_64mp->sd); + struct v4l2_fwnode_device_properties props; + struct v4l2_ctrl *link_freq; + unsigned int i; + int ret; + u8 test_pattern_max; + u8 link_freq_max; + + ctrl_hdlr = &arducam_64mp->ctrl_handler; + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 16); + if (ret) + return ret; + + mutex_init(&arducam_64mp->mutex); + ctrl_hdlr->lock = &arducam_64mp->mutex; + + /* By default, PIXEL_RATE is read only */ + arducam_64mp->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, + &arducam_64mp_ctrl_ops, + V4L2_CID_PIXEL_RATE, + ARDUCAM_64MP_PIXEL_RATE, + ARDUCAM_64MP_PIXEL_RATE, 1, + ARDUCAM_64MP_PIXEL_RATE); + + /* LINK_FREQ is also read only */ + link_freq_max = ARRAY_SIZE(arducam_64mp_link_freq_menu) - 1; + link_freq = + v4l2_ctrl_new_int_menu(ctrl_hdlr, &arducam_64mp_ctrl_ops, + V4L2_CID_LINK_FREQ, + link_freq_max, 0, + arducam_64mp_link_freq_menu); + if (link_freq) + link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + /* + * Create the controls here, but mode specific limits are setup + * in the arducam_64mp_set_framing_limits() call below. + */ + arducam_64mp->vblank = v4l2_ctrl_new_std(ctrl_hdlr, + &arducam_64mp_ctrl_ops, + V4L2_CID_VBLANK, + 0, 0xffff, 1, 0); + arducam_64mp->hblank = v4l2_ctrl_new_std(ctrl_hdlr, + &arducam_64mp_ctrl_ops, + V4L2_CID_HBLANK, + 0, 0xffff, 1, 0); + + /* HBLANK is read-only, but does change with mode. */ + if (arducam_64mp->hblank) + arducam_64mp->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + arducam_64mp->exposure = + v4l2_ctrl_new_std(ctrl_hdlr, + &arducam_64mp_ctrl_ops, + V4L2_CID_EXPOSURE, + ARDUCAM_64MP_EXPOSURE_MIN, + ARDUCAM_64MP_EXPOSURE_MAX, + ARDUCAM_64MP_EXPOSURE_STEP, + ARDUCAM_64MP_EXPOSURE_DEFAULT); + + v4l2_ctrl_new_std(ctrl_hdlr, &arducam_64mp_ctrl_ops, + V4L2_CID_ANALOGUE_GAIN, ARDUCAM_64MP_ANA_GAIN_MIN, + ARDUCAM_64MP_ANA_GAIN_MAX, ARDUCAM_64MP_ANA_GAIN_STEP, + ARDUCAM_64MP_ANA_GAIN_DEFAULT); + + v4l2_ctrl_new_std(ctrl_hdlr, &arducam_64mp_ctrl_ops, + V4L2_CID_DIGITAL_GAIN, ARDUCAM_64MP_DGTL_GAIN_MIN, + ARDUCAM_64MP_DGTL_GAIN_MAX, + ARDUCAM_64MP_DGTL_GAIN_STEP, + ARDUCAM_64MP_DGTL_GAIN_DEFAULT); + + arducam_64mp->hflip = v4l2_ctrl_new_std(ctrl_hdlr, + &arducam_64mp_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + if (arducam_64mp->hflip) + arducam_64mp->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + + arducam_64mp->vflip = v4l2_ctrl_new_std(ctrl_hdlr, + &arducam_64mp_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + if (arducam_64mp->vflip) + arducam_64mp->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + + test_pattern_max = ARRAY_SIZE(arducam_64mp_test_pattern_menu) - 1; + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &arducam_64mp_ctrl_ops, + V4L2_CID_TEST_PATTERN, + test_pattern_max, + 0, 0, arducam_64mp_test_pattern_menu); + for (i = 0; i < 4; i++) { + /* + * The assumption is that + * V4L2_CID_TEST_PATTERN_GREENR == V4L2_CID_TEST_PATTERN_RED + 1 + * V4L2_CID_TEST_PATTERN_BLUE == V4L2_CID_TEST_PATTERN_RED + 2 + * V4L2_CID_TEST_PATTERN_GREENB == V4L2_CID_TEST_PATTERN_RED + 3 + */ + v4l2_ctrl_new_std(ctrl_hdlr, &arducam_64mp_ctrl_ops, + V4L2_CID_TEST_PATTERN_RED + i, + ARDUCAM_64MP_TEST_PATTERN_COLOUR_MIN, + ARDUCAM_64MP_TEST_PATTERN_COLOUR_MAX, + ARDUCAM_64MP_TEST_PATTERN_COLOUR_STEP, + ARDUCAM_64MP_TEST_PATTERN_COLOUR_MAX); + /* The "Solid color" pattern is white by default */ + } + + if (ctrl_hdlr->error) { + ret = ctrl_hdlr->error; + dev_err(&client->dev, "%s control init failed (%d)\n", + __func__, ret); + goto error; + } + + ret = v4l2_fwnode_device_parse(&client->dev, &props); + if (ret) + goto error; + + ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &arducam_64mp_ctrl_ops, + &props); + if (ret) + goto error; + + arducam_64mp->sd.ctrl_handler = ctrl_hdlr; + + /* Setup exposure and frame/line length limits. */ + arducam_64mp_set_framing_limits(arducam_64mp); + + return 0; + +error: + v4l2_ctrl_handler_free(ctrl_hdlr); + mutex_destroy(&arducam_64mp->mutex); + + return ret; +} + +static void arducam_64mp_free_controls(struct arducam_64mp *arducam_64mp) +{ + v4l2_ctrl_handler_free(arducam_64mp->sd.ctrl_handler); + mutex_destroy(&arducam_64mp->mutex); +} + +static int arducam_64mp_check_hwcfg(struct device *dev) +{ + struct fwnode_handle *endpoint; + struct v4l2_fwnode_endpoint ep_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; + int ret = -EINVAL; + + endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL); + if (!endpoint) { + dev_err(dev, "endpoint node not found\n"); + return -EINVAL; + } + + if (v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep_cfg)) { + dev_err(dev, "could not parse endpoint\n"); + goto error_out; + } + + /* Check the number of MIPI CSI2 data lanes */ + if (ep_cfg.bus.mipi_csi2.num_data_lanes != 2) { + dev_err(dev, "only 2 data lanes are currently supported\n"); + goto error_out; + } + + /* Check the link frequency set in device tree */ + if (!ep_cfg.nr_of_link_frequencies) { + dev_err(dev, "link-frequency property not found in DT\n"); + goto error_out; + } + + if (ep_cfg.nr_of_link_frequencies != 1 || + ep_cfg.link_frequencies[0] != ARDUCAM_64MP_DEFAULT_LINK_FREQ) { + dev_err(dev, "Link frequency not supported: %lld\n", + ep_cfg.link_frequencies[0]); + goto error_out; + } + + ret = 0; + +error_out: + v4l2_fwnode_endpoint_free(&ep_cfg); + fwnode_handle_put(endpoint); + + return ret; +} + +static const struct of_device_id arducam_64mp_dt_ids[] = { + { .compatible = "arducam,64mp"}, + { /* sentinel */ } +}; + +static int arducam_64mp_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct arducam_64mp *arducam_64mp; + const struct of_device_id *match; + u32 xclk_freq; + int ret; + + arducam_64mp = devm_kzalloc(&client->dev, sizeof(*arducam_64mp), + GFP_KERNEL); + if (!arducam_64mp) + return -ENOMEM; + + v4l2_i2c_subdev_init(&arducam_64mp->sd, client, + &arducam_64mp_subdev_ops); + + match = of_match_device(arducam_64mp_dt_ids, dev); + if (!match) + return -ENODEV; + + /* Check the hardware configuration in device tree */ + if (arducam_64mp_check_hwcfg(dev)) + return -EINVAL; + + /* Get system clock (xclk) */ + arducam_64mp->xclk = devm_clk_get(dev, NULL); + if (IS_ERR(arducam_64mp->xclk)) { + dev_err(dev, "failed to get xclk\n"); + return PTR_ERR(arducam_64mp->xclk); + } + + xclk_freq = clk_get_rate(arducam_64mp->xclk); + if (xclk_freq != ARDUCAM_64MP_XCLK_FREQ) { + dev_err(dev, "xclk frequency not supported: %d Hz\n", + xclk_freq); + return -EINVAL; + } + + ret = arducam_64mp_get_regulators(arducam_64mp); + if (ret) { + dev_err(dev, "failed to get regulators\n"); + return ret; + } + + /* Request optional enable pin */ + arducam_64mp->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + + /* + * The sensor must be powered for arducam_64mp_identify_module() + * to be able to read the CHIP_ID from arducam_identifier. + */ + ret = arducam_64mp_power_on(dev); + if (ret) + return ret; + + ret = arducam_64mp_identify_module(arducam_64mp); + if (ret) + goto error_power_off; + + /* Set default mode to max resolution */ + arducam_64mp->mode = &supported_modes[0]; + arducam_64mp->fmt_code = MEDIA_BUS_FMT_SRGGB10_1X10; + + /* Enable runtime PM and turn off the device */ + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_idle(dev); + + /* This needs the pm runtime to be registered. */ + ret = arducam_64mp_init_controls(arducam_64mp); + if (ret) + goto error_power_off; + + /* Initialize subdev */ + arducam_64mp->sd.internal_ops = &arducam_64mp_internal_ops; + arducam_64mp->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | + V4L2_SUBDEV_FL_HAS_EVENTS; + arducam_64mp->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + /* Initialize source pads */ + arducam_64mp->pad[IMAGE_PAD].flags = MEDIA_PAD_FL_SOURCE; + arducam_64mp->pad[METADATA_PAD].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&arducam_64mp->sd.entity, NUM_PADS, + arducam_64mp->pad); + if (ret) { + dev_err(dev, "failed to init entity pads: %d\n", ret); + goto error_handler_free; + } + + ret = v4l2_async_register_subdev_sensor(&arducam_64mp->sd); + if (ret < 0) { + dev_err(dev, "failed to register sensor sub-device: %d\n", ret); + goto error_media_entity; + } + + return 0; + +error_media_entity: + media_entity_cleanup(&arducam_64mp->sd.entity); + +error_handler_free: + arducam_64mp_free_controls(arducam_64mp); + +error_power_off: + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + arducam_64mp_power_off(&client->dev); + + return ret; +} + +static void arducam_64mp_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct arducam_64mp *arducam_64mp = to_arducam_64mp(sd); + + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + arducam_64mp_free_controls(arducam_64mp); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) + arducam_64mp_power_off(&client->dev); + pm_runtime_set_suspended(&client->dev); +} + +MODULE_DEVICE_TABLE(of, arducam_64mp_dt_ids); + +static const struct dev_pm_ops arducam_64mp_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(arducam_64mp_suspend, arducam_64mp_resume) + SET_RUNTIME_PM_OPS(arducam_64mp_power_off, arducam_64mp_power_on, NULL) +}; + +static struct i2c_driver arducam_64mp_i2c_driver = { + .driver = { + .name = "arducam_64mp", + .of_match_table = arducam_64mp_dt_ids, + .pm = &arducam_64mp_pm_ops, + }, + .probe = arducam_64mp_probe, + .remove = arducam_64mp_remove, +}; + +module_i2c_driver(arducam_64mp_i2c_driver); + +MODULE_AUTHOR("Lee Jackson <info@arducam.com>"); +MODULE_DESCRIPTION("Arducam 64MP sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/bu64754.c b/drivers/media/i2c/bu64754.c new file mode 100644 index 00000000000000..530b31e9876a55 --- /dev/null +++ b/drivers/media/i2c/bu64754.c @@ -0,0 +1,315 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * The BU64754GWZ is an actuator driver IC which can control the + * actuator position precisely using an internal Hall Sensor. + */ + +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> + +#include <media/v4l2-cci.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> + +#define BU64754_REG_ACTIVE CCI_REG16(0x07) +#define BU64754_ACTIVE_MODE 0x8080 + +#define BU64754_REG_SERVE CCI_REG16(0xd9) +#define BU64754_SERVE_ON 0x0404 + +#define BU64754_REG_POSITION CCI_REG16(0x45) +#define BU64753_POSITION_MAX 1023 /* 0x3ff */ +#define BU64753_POSITION_STEPS 1 + +#define BU64754_POWER_ON_DELAY 800 /* uS : t1, t3 */ + +struct bu64754 { + struct device *dev; + + struct v4l2_ctrl_handler ctrls_vcm; + struct v4l2_subdev sd; + struct regmap *cci; + + u16 current_val; + struct regulator *vdd; + struct notifier_block notifier; +}; + +static inline struct bu64754 *sd_to_bu64754(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct bu64754, sd); +} + +static int bu64754_set(struct bu64754 *bu64754, u16 position) +{ + int ret; + + position &= 0x3ff; /* BU64753_POSITION_MAX */ + ret = cci_write(bu64754->cci, BU64754_REG_POSITION, position, NULL); + if (ret) { + dev_err(bu64754->dev, "Set position failed ret=%d\n", ret); + return ret; + } + + return 0; +} + +static int bu64754_active(struct bu64754 *bu64754) +{ + int ret; + + /* Power on */ + ret = cci_write(bu64754->cci, BU64754_REG_ACTIVE, BU64754_ACTIVE_MODE, NULL); + if (ret < 0) { + dev_err(bu64754->dev, "Failed to set active mode ret = %d\n", + ret); + return ret; + } + + /* Serve on */ + ret = cci_write(bu64754->cci, BU64754_REG_SERVE, BU64754_SERVE_ON, NULL); + if (ret < 0) { + dev_err(bu64754->dev, "Failed to enable serve ret = %d\n", + ret); + return ret; + } + + return bu64754_set(bu64754, bu64754->current_val); +} + +static int bu64754_standby(struct bu64754 *bu64754) +{ + int ret; + + ret = cci_write(bu64754->cci, BU64754_REG_ACTIVE, 0, NULL); + if (ret < 0) + dev_err(bu64754->dev, "Failed to enter standby mode ret = %d\n", + ret); + + return ret; +} + +static int bu64754_regulator_event(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct bu64754 *bu64754 = container_of(nb, struct bu64754, notifier); + + if (action & REGULATOR_EVENT_ENABLE) { + /* + * Initialisation delay between VDD low->high and availability + * i2c operation. + */ + usleep_range(BU64754_POWER_ON_DELAY, + BU64754_POWER_ON_DELAY + 100); + + bu64754_active(bu64754); + } else if (action & REGULATOR_EVENT_PRE_DISABLE) { + bu64754_standby(bu64754); + } + + return 0; +} + +static int bu64754_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct bu64754 *bu64754 = container_of(ctrl->handler, + struct bu64754, ctrls_vcm); + + if (ctrl->id == V4L2_CID_FOCUS_ABSOLUTE) { + bu64754->current_val = ctrl->val; + return bu64754_set(bu64754, ctrl->val); + } + + return -EINVAL; +} + +static const struct v4l2_ctrl_ops bu64754_vcm_ctrl_ops = { + .s_ctrl = bu64754_set_ctrl, +}; + +static int bu64754_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + return pm_runtime_resume_and_get(sd->dev); +} + +static int bu64754_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + pm_runtime_put(sd->dev); + return 0; +} + +static const struct v4l2_subdev_internal_ops bu64754_int_ops = { + .open = bu64754_open, + .close = bu64754_close, +}; + +static const struct v4l2_subdev_ops bu64754_ops = { }; + +static void bu64754_subdev_cleanup(struct bu64754 *bu64754) +{ + v4l2_async_unregister_subdev(&bu64754->sd); + v4l2_ctrl_handler_free(&bu64754->ctrls_vcm); + media_entity_cleanup(&bu64754->sd.entity); +} + +static int bu64754_init_controls(struct bu64754 *bu64754) +{ + struct v4l2_ctrl_handler *hdl = &bu64754->ctrls_vcm; + const struct v4l2_ctrl_ops *ops = &bu64754_vcm_ctrl_ops; + + v4l2_ctrl_handler_init(hdl, 1); + + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FOCUS_ABSOLUTE, + 0, BU64753_POSITION_MAX, BU64753_POSITION_STEPS, + 0); + + bu64754->current_val = 0; + + bu64754->sd.ctrl_handler = hdl; + if (hdl->error) { + dev_err(bu64754->dev, "%s fail error: 0x%x\n", + __func__, hdl->error); + return hdl->error; + } + + return 0; +} + +static int bu64754_probe(struct i2c_client *client) +{ + struct bu64754 *bu64754; + int ret; + + bu64754 = devm_kzalloc(&client->dev, sizeof(*bu64754), GFP_KERNEL); + if (!bu64754) + return -ENOMEM; + + bu64754->dev = &client->dev; + + bu64754->cci = devm_cci_regmap_init_i2c(client, 8); + if (IS_ERR(bu64754->cci)) { + dev_err(bu64754->dev, "Failed to initialize CCI\n"); + return PTR_ERR(bu64754->cci); + } + + bu64754->vdd = devm_regulator_get_optional(&client->dev, "vdd"); + if (IS_ERR(bu64754->vdd)) { + if (PTR_ERR(bu64754->vdd) != -ENODEV) + return PTR_ERR(bu64754->vdd); + + bu64754->vdd = NULL; + } else { + bu64754->notifier.notifier_call = bu64754_regulator_event; + + ret = regulator_register_notifier(bu64754->vdd, + &bu64754->notifier); + if (ret) { + dev_err(bu64754->dev, + "could not register regulator notifier\n"); + return ret; + } + } + + v4l2_i2c_subdev_init(&bu64754->sd, client, &bu64754_ops); + bu64754->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + bu64754->sd.internal_ops = &bu64754_int_ops; + bu64754->sd.entity.function = MEDIA_ENT_F_LENS; + + ret = bu64754_init_controls(bu64754); + if (ret) + goto err_cleanup; + + ret = media_entity_pads_init(&bu64754->sd.entity, 0, NULL); + if (ret < 0) + goto err_cleanup; + + bu64754->sd.entity.function = MEDIA_ENT_F_LENS; + + ret = v4l2_async_register_subdev(&bu64754->sd); + if (ret < 0) + goto err_cleanup; + + if (!bu64754->vdd) + pm_runtime_set_active(&client->dev); + + pm_runtime_enable(&client->dev); + pm_runtime_idle(&client->dev); + + return 0; + +err_cleanup: + v4l2_ctrl_handler_free(&bu64754->ctrls_vcm); + media_entity_cleanup(&bu64754->sd.entity); + + return ret; +} + +static void bu64754_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct bu64754 *bu64754 = sd_to_bu64754(sd); + + if (bu64754->vdd) + regulator_unregister_notifier(bu64754->vdd, + &bu64754->notifier); + + pm_runtime_disable(&client->dev); + + bu64754_subdev_cleanup(bu64754); +} + +static int __maybe_unused bu64754_vcm_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct bu64754 *bu64754 = sd_to_bu64754(sd); + + if (bu64754->vdd) + return regulator_disable(bu64754->vdd); + + return bu64754_standby(bu64754); +} + +static int __maybe_unused bu64754_vcm_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct bu64754 *bu64754 = sd_to_bu64754(sd); + + if (bu64754->vdd) + return regulator_enable(bu64754->vdd); + + return bu64754_active(bu64754); +} + +static const struct of_device_id bu64754_of_table[] = { + { .compatible = "rohm,bu64754", }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, bu64754_of_table); + +static const struct dev_pm_ops bu64754_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(bu64754_vcm_suspend, bu64754_vcm_resume) + SET_RUNTIME_PM_OPS(bu64754_vcm_suspend, bu64754_vcm_resume, NULL) +}; + +static struct i2c_driver bu64754_i2c_driver = { + .driver = { + .name = "bu64754", + .pm = &bu64754_pm_ops, + .of_match_table = bu64754_of_table, + }, + .probe = bu64754_probe, + .remove = bu64754_remove, +}; + +module_i2c_driver(bu64754_i2c_driver); + +MODULE_AUTHOR("Kieran Bingham"); +MODULE_DESCRIPTION("BU64754 VCM driver"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/media/i2c/dw9807-vcm.c b/drivers/media/i2c/dw9807-vcm.c index 4148009e0e0170..2362c4813f5e0f 100644 --- a/drivers/media/i2c/dw9807-vcm.c +++ b/drivers/media/i2c/dw9807-vcm.c @@ -1,12 +1,21 @@ // SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2018 Intel Corporation +/* + * DW9807 is a 10-bit DAC driver, capable of sinking up to 100mA. + * + * DW9817 is a bidirectional 10-bit driver, driving up to +/- 100mA. + * Operationally it is identical to DW9807, except that the idle position is + * the mid-point, not 0. + */ + #include <linux/acpi.h> #include <linux/delay.h> #include <linux/i2c.h> #include <linux/iopoll.h> #include <linux/module.h> #include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> #include <media/v4l2-ctrls.h> #include <media/v4l2-device.h> @@ -38,10 +47,22 @@ #define MAX_RETRY 10 +#define DW9807_PW_MIN_DELAY_US 100 +#define DW9807_PW_DELAY_RANGE_US 10 + +struct dw9807_cfg { + unsigned int idle_pos; + unsigned int default_pos; +}; + struct dw9807_device { struct v4l2_ctrl_handler ctrls_vcm; struct v4l2_subdev sd; u16 current_val; + u16 idle_pos; + struct regulator *vdd; + struct notifier_block notifier; + bool first; }; static inline struct dw9807_device *sd_to_dw9807_vcm( @@ -109,6 +130,102 @@ static int dw9807_set_dac(struct i2c_client *client, u16 data) return 0; } +/* + * The lens position is gradually moved in units of DW9807_CTRL_STEPS, + * to make the movements smoothly. In all cases, even when "start" and + * "end" are the same, the lens will be set to the "end" position. + * + * (We don't use hardware slew rate control, because it differs widely + * between otherwise-compatible ICs, and may need lens-specific tuning.) + */ +static int dw9807_ramp(struct i2c_client *client, int start, int end) +{ + int step, val, ret; + + if (start < end) + step = DW9807_CTRL_STEPS; + else + step = -DW9807_CTRL_STEPS; + + val = start; + while (true) { + val += step; + if (step * (val - end) >= 0) + val = end; + ret = dw9807_set_dac(client, val); + if (ret) + dev_err_ratelimited(&client->dev, "%s I2C failure: %d", + __func__, ret); + if (val == end) + break; + usleep_range(DW9807_CTRL_DELAY_US, DW9807_CTRL_DELAY_US + 10); + } + + return ret; +} + +static int dw9807_active(struct dw9807_device *dw9807_dev) +{ + struct i2c_client *client = v4l2_get_subdevdata(&dw9807_dev->sd); + const char tx_data[2] = { DW9807_CTL_ADDR, 0x00 }; + int ret; + + /* Power on */ + ret = i2c_master_send(client, tx_data, sizeof(tx_data)); + if (ret < 0) { + dev_err(&client->dev, "I2C write CTL fail ret = %d\n", ret); + return ret; + } + + dw9807_dev->first = true; + + return dw9807_ramp(client, dw9807_dev->idle_pos, dw9807_dev->current_val); +} + +static int dw9807_standby(struct dw9807_device *dw9807_dev) +{ + struct i2c_client *client = v4l2_get_subdevdata(&dw9807_dev->sd); + const char tx_data[2] = { DW9807_CTL_ADDR, 0x01 }; + int ret; + + if (abs(dw9807_dev->current_val - dw9807_dev->idle_pos) > DW9807_CTRL_STEPS) + dw9807_ramp(client, dw9807_dev->current_val, dw9807_dev->idle_pos); + + /* Power down */ + ret = i2c_master_send(client, tx_data, sizeof(tx_data)); + if (ret < 0) { + dev_err(&client->dev, "I2C write CTL fail ret = %d\n", ret); + return ret; + } + + return 0; +} + +static int dw9807_regulator_event(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct dw9807_device *dw9807_dev = + container_of(nb, struct dw9807_device, notifier); + + if (action & REGULATOR_EVENT_ENABLE) { + /* + * Initialisation delay between VDD low->high and the moment + * when the i2c command is available. + * From the datasheet, it should be 10ms + 2ms (max power + * up sequence duration) + */ + usleep_range(DW9807_PW_MIN_DELAY_US, + DW9807_PW_MIN_DELAY_US + + DW9807_PW_DELAY_RANGE_US); + + dw9807_active(dw9807_dev); + } else if (action & REGULATOR_EVENT_PRE_DISABLE) { + dw9807_standby(dw9807_dev); + } + + return 0; +} + static int dw9807_set_ctrl(struct v4l2_ctrl *ctrl) { struct dw9807_device *dev_vcm = container_of(ctrl->handler, @@ -116,9 +233,11 @@ static int dw9807_set_ctrl(struct v4l2_ctrl *ctrl) if (ctrl->id == V4L2_CID_FOCUS_ABSOLUTE) { struct i2c_client *client = v4l2_get_subdevdata(&dev_vcm->sd); + int start = (dev_vcm->first) ? dev_vcm->current_val : ctrl->val; + dev_vcm->first = false; dev_vcm->current_val = ctrl->val; - return dw9807_set_dac(client, ctrl->val); + return dw9807_ramp(client, start, ctrl->val); } return -EINVAL; @@ -163,7 +282,8 @@ static int dw9807_init_controls(struct dw9807_device *dev_vcm) v4l2_ctrl_handler_init(hdl, 1); v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FOCUS_ABSOLUTE, - 0, DW9807_MAX_FOCUS_POS, DW9807_FOCUS_STEPS, 0); + 0, DW9807_MAX_FOCUS_POS, DW9807_FOCUS_STEPS, + dev_vcm->current_val); dev_vcm->sd.ctrl_handler = hdl; if (hdl->error) { @@ -175,9 +295,32 @@ static int dw9807_init_controls(struct dw9807_device *dev_vcm) return 0; } +/* Compatible devices; in fact there are many similar chips. + * "data" holds the powered-off (zero current) lens position and a + * default/initial control value (which need not be the same as the powered-off + * value). + */ +static const struct dw9807_cfg dw9807_cfg = { + .idle_pos = 0, + .default_pos = 0 +}; + +static const struct dw9807_cfg dw9817_cfg = { + .idle_pos = 512, + .default_pos = 480, +}; + +static const struct of_device_id dw9807_of_table[] = { + { .compatible = "dongwoon,dw9807-vcm", .data = &dw9807_cfg }, + { .compatible = "dongwoon,dw9817-vcm", .data = &dw9817_cfg }, + { /* sentinel */ } +}; + static int dw9807_probe(struct i2c_client *client) { struct dw9807_device *dw9807_dev; + const struct of_device_id *match; + const struct dw9807_cfg *cfg; int rval; dw9807_dev = devm_kzalloc(&client->dev, sizeof(*dw9807_dev), @@ -185,6 +328,31 @@ static int dw9807_probe(struct i2c_client *client) if (dw9807_dev == NULL) return -ENOMEM; + dw9807_dev->vdd = devm_regulator_get_optional(&client->dev, "VDD"); + if (IS_ERR(dw9807_dev->vdd)) { + if (PTR_ERR(dw9807_dev->vdd) != -ENODEV) + return PTR_ERR(dw9807_dev->vdd); + + dw9807_dev->vdd = NULL; + } else { + dw9807_dev->notifier.notifier_call = dw9807_regulator_event; + + rval = regulator_register_notifier(dw9807_dev->vdd, + &dw9807_dev->notifier); + if (rval) { + dev_err(&client->dev, + "could not register regulator notifier\n"); + return rval; + } + } + + match = i2c_of_match_device(dw9807_of_table, client); + if (match) { + cfg = (const struct dw9807_cfg *)match->data; + dw9807_dev->idle_pos = cfg->idle_pos; + dw9807_dev->current_val = cfg->default_pos; + } + v4l2_i2c_subdev_init(&dw9807_dev->sd, client, &dw9807_ops); dw9807_dev->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; dw9807_dev->sd.internal_ops = &dw9807_int_ops; @@ -203,7 +371,8 @@ static int dw9807_probe(struct i2c_client *client) if (rval < 0) goto err_cleanup; - pm_runtime_set_active(&client->dev); + if (!dw9807_dev->vdd) + pm_runtime_set_active(&client->dev); pm_runtime_enable(&client->dev); pm_runtime_idle(&client->dev); @@ -221,6 +390,10 @@ static void dw9807_remove(struct i2c_client *client) struct v4l2_subdev *sd = i2c_get_clientdata(client); struct dw9807_device *dw9807_dev = sd_to_dw9807_vcm(sd); + if (dw9807_dev->vdd) + regulator_unregister_notifier(dw9807_dev->vdd, + &dw9807_dev->notifier); + pm_runtime_disable(&client->dev); dw9807_subdev_cleanup(dw9807_dev); @@ -236,25 +409,11 @@ static int __maybe_unused dw9807_vcm_suspend(struct device *dev) struct i2c_client *client = to_i2c_client(dev); struct v4l2_subdev *sd = i2c_get_clientdata(client); struct dw9807_device *dw9807_dev = sd_to_dw9807_vcm(sd); - const char tx_data[2] = { DW9807_CTL_ADDR, 0x01 }; - int ret, val; - - for (val = dw9807_dev->current_val & ~(DW9807_CTRL_STEPS - 1); - val >= 0; val -= DW9807_CTRL_STEPS) { - ret = dw9807_set_dac(client, val); - if (ret) - dev_err_once(dev, "%s I2C failure: %d", __func__, ret); - usleep_range(DW9807_CTRL_DELAY_US, DW9807_CTRL_DELAY_US + 10); - } - /* Power down */ - ret = i2c_master_send(client, tx_data, sizeof(tx_data)); - if (ret < 0) { - dev_err(&client->dev, "I2C write CTL fail ret = %d\n", ret); - return ret; - } + if (dw9807_dev->vdd) + return regulator_disable(dw9807_dev->vdd); - return 0; + return dw9807_standby(dw9807_dev); } /* @@ -268,35 +427,13 @@ static int __maybe_unused dw9807_vcm_resume(struct device *dev) struct i2c_client *client = to_i2c_client(dev); struct v4l2_subdev *sd = i2c_get_clientdata(client); struct dw9807_device *dw9807_dev = sd_to_dw9807_vcm(sd); - const char tx_data[2] = { DW9807_CTL_ADDR, 0x00 }; - int ret, val; - - /* Power on */ - ret = i2c_master_send(client, tx_data, sizeof(tx_data)); - if (ret < 0) { - dev_err(&client->dev, "I2C write CTL fail ret = %d\n", ret); - return ret; - } - for (val = dw9807_dev->current_val % DW9807_CTRL_STEPS; - val < dw9807_dev->current_val + DW9807_CTRL_STEPS - 1; - val += DW9807_CTRL_STEPS) { - ret = dw9807_set_dac(client, val); - if (ret) - dev_err_ratelimited(dev, "%s I2C failure: %d", - __func__, ret); - usleep_range(DW9807_CTRL_DELAY_US, DW9807_CTRL_DELAY_US + 10); - } + if (dw9807_dev->vdd) + return regulator_enable(dw9807_dev->vdd); - return 0; + return dw9807_active(dw9807_dev); } -static const struct of_device_id dw9807_of_table[] = { - { .compatible = "dongwoon,dw9807-vcm" }, - /* Compatibility for older firmware, NEVER USE THIS IN FIRMWARE! */ - { .compatible = "dongwoon,dw9807" }, - { /* sentinel */ } -}; MODULE_DEVICE_TABLE(of, dw9807_of_table); static const struct dev_pm_ops dw9807_pm_ops = { diff --git a/drivers/media/i2c/imx219.c b/drivers/media/i2c/imx219.c index e78a80b2bb2e45..272c2e1a064ec5 100644 --- a/drivers/media/i2c/imx219.c +++ b/drivers/media/i2c/imx219.c @@ -74,7 +74,7 @@ #define IMX219_REG_VTS CCI_REG16(0x0160) #define IMX219_VTS_MAX 0xffff -#define IMX219_VBLANK_MIN 4 +#define IMX219_VBLANK_MIN 32 /* HBLANK control - read only */ #define IMX219_PPL_DEFAULT 3448 @@ -134,10 +134,11 @@ /* Pixel rate is fixed for all the modes */ #define IMX219_PIXEL_RATE 182400000 -#define IMX219_PIXEL_RATE_4LANE 280800000 +#define IMX219_PIXEL_RATE_4LANE 281600000 #define IMX219_DEFAULT_LINK_FREQ 456000000 -#define IMX219_DEFAULT_LINK_FREQ_4LANE 363000000 +#define IMX219_DEFAULT_LINK_FREQ_4LANE_UNSUPPORTED 363000000 +#define IMX219_DEFAULT_LINK_FREQ_4LANE 364000000 /* IMX219 native and active pixel array size. */ #define IMX219_NATIVE_WIDTH 3296U @@ -169,15 +170,6 @@ static const struct cci_reg_sequence imx219_common_regs[] = { { CCI_REG8(0x30eb), 0x05 }, { CCI_REG8(0x30eb), 0x09 }, - /* PLL Clock Table */ - { IMX219_REG_VTPXCK_DIV, 5 }, - { IMX219_REG_VTSYCK_DIV, 1 }, - { IMX219_REG_PREPLLCK_VT_DIV, 3 }, /* 0x03 = AUTO set */ - { IMX219_REG_PREPLLCK_OP_DIV, 3 }, /* 0x03 = AUTO set */ - { IMX219_REG_PLL_VT_MPY, 57 }, - { IMX219_REG_OPSYCK_DIV, 1 }, - { IMX219_REG_PLL_OP_MPY, 114 }, - /* Undocumented registers */ { CCI_REG8(0x455e), 0x00 }, { CCI_REG8(0x471e), 0x4b }, @@ -202,6 +194,34 @@ static const struct cci_reg_sequence imx219_common_regs[] = { { IMX219_REG_EXCK_FREQ, IMX219_EXCK_FREQ(IMX219_XCLK_FREQ / 1000000) }, }; +static const struct cci_reg_sequence imx219_2lane_regs[] = { + /* PLL Clock Table */ + { IMX219_REG_VTPXCK_DIV, 5 }, + { IMX219_REG_VTSYCK_DIV, 1 }, + { IMX219_REG_PREPLLCK_VT_DIV, 3 }, /* 0x03 = AUTO set */ + { IMX219_REG_PREPLLCK_OP_DIV, 3 }, /* 0x03 = AUTO set */ + { IMX219_REG_PLL_VT_MPY, 57 }, + { IMX219_REG_OPSYCK_DIV, 1 }, + { IMX219_REG_PLL_OP_MPY, 114 }, + + /* 2-Lane CSI Mode */ + { IMX219_REG_CSI_LANE_MODE, IMX219_CSI_2_LANE_MODE }, +}; + +static const struct cci_reg_sequence imx219_4lane_regs[] = { + /* PLL Clock Table */ + { IMX219_REG_VTPXCK_DIV, 5 }, + { IMX219_REG_VTSYCK_DIV, 1 }, + { IMX219_REG_PREPLLCK_VT_DIV, 3 }, /* 0x03 = AUTO set */ + { IMX219_REG_PREPLLCK_OP_DIV, 3 }, /* 0x03 = AUTO set */ + { IMX219_REG_PLL_VT_MPY, 88 }, + { IMX219_REG_OPSYCK_DIV, 1 }, + { IMX219_REG_PLL_OP_MPY, 91 }, + + /* 4-Lane CSI Mode */ + { IMX219_REG_CSI_LANE_MODE, IMX219_CSI_4_LANE_MODE }, +}; + static const s64 imx219_link_freq_menu[] = { IMX219_DEFAULT_LINK_FREQ, }; @@ -663,9 +683,11 @@ static int imx219_set_framefmt(struct imx219 *imx219, static int imx219_configure_lanes(struct imx219 *imx219) { - return cci_write(imx219->regmap, IMX219_REG_CSI_LANE_MODE, - imx219->lanes == 2 ? IMX219_CSI_2_LANE_MODE : - IMX219_CSI_4_LANE_MODE, NULL); + /* Write the appropriate PLL settings for the number of MIPI lanes */ + return cci_multi_reg_write(imx219->regmap, + imx219->lanes == 2 ? imx219_2lane_regs : imx219_4lane_regs, + imx219->lanes == 2 ? ARRAY_SIZE(imx219_2lane_regs) : + ARRAY_SIZE(imx219_4lane_regs), NULL); }; static int imx219_start_streaming(struct imx219 *imx219, @@ -1043,6 +1065,7 @@ static int imx219_check_hwcfg(struct device *dev, struct imx219 *imx219) .bus_type = V4L2_MBUS_CSI2_DPHY }; int ret = -EINVAL; + bool link_frequency_valid = false; endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL); if (!endpoint) @@ -1069,9 +1092,30 @@ static int imx219_check_hwcfg(struct device *dev, struct imx219 *imx219) goto error_out; } - if (ep_cfg.nr_of_link_frequencies != 1 || - (ep_cfg.link_frequencies[0] != ((imx219->lanes == 2) ? - IMX219_DEFAULT_LINK_FREQ : IMX219_DEFAULT_LINK_FREQ_4LANE))) { + if (ep_cfg.nr_of_link_frequencies == 1) { + switch (imx219->lanes) { + case 2: + if (ep_cfg.link_frequencies[0] == + IMX219_DEFAULT_LINK_FREQ) + link_frequency_valid = true; + break; + case 4: + if (ep_cfg.link_frequencies[0] == + IMX219_DEFAULT_LINK_FREQ_4LANE) + link_frequency_valid = true; + else if (ep_cfg.link_frequencies[0] == + IMX219_DEFAULT_LINK_FREQ_4LANE_UNSUPPORTED) { + dev_warn(dev, "Link frequency of %d not supported, but has been incorrectly advertised previously\n", + IMX219_DEFAULT_LINK_FREQ_4LANE_UNSUPPORTED); + dev_warn(dev, "Using link frequency of %d\n", + IMX219_DEFAULT_LINK_FREQ_4LANE); + link_frequency_valid = true; + } + break; + } + } + + if (!link_frequency_valid) { dev_err_probe(dev, -EINVAL, "Link frequency not supported: %lld\n", ep_cfg.link_frequencies[0]); diff --git a/drivers/media/i2c/imx290.c b/drivers/media/i2c/imx290.c index a87a265cd83957..91a196680e9137 100644 --- a/drivers/media/i2c/imx290.c +++ b/drivers/media/i2c/imx290.c @@ -13,6 +13,7 @@ #include <linux/gpio/consumer.h> #include <linux/i2c.h> #include <linux/module.h> +#include <linux/moduleparam.h> #include <linux/of.h> #include <linux/pm_runtime.h> #include <linux/regmap.h> @@ -41,6 +42,9 @@ #define IMX290_WINMODE_720P (1 << 4) #define IMX290_WINMODE_CROP (4 << 4) #define IMX290_FR_FDG_SEL CCI_REG8(0x3009) +#define IMX290_FDG_HCG BIT(4) +#define IMX290_FRSEL_60FPS BIT(0) +#define IMX290_FDG_LCG 0 #define IMX290_BLKLEVEL CCI_REG16_LE(0x300a) #define IMX290_GAIN CCI_REG8(0x3014) #define IMX290_VMAX CCI_REG24_LE(0x3018) @@ -162,6 +166,10 @@ #define IMX290_NUM_SUPPLIES 3 +static bool hcg_mode; +module_param(hcg_mode, bool, 0664); +MODULE_PARM_DESC(hcg_mode, "Enable HCG mode"); + enum imx290_colour_variant { IMX290_VARIANT_COLOUR, IMX290_VARIANT_MONO, @@ -172,12 +180,15 @@ enum imx290_model { IMX290_MODEL_IMX290LQR, IMX290_MODEL_IMX290LLR, IMX290_MODEL_IMX327LQR, + IMX290_MODEL_IMX462LQR, + IMX290_MODEL_IMX462LLR, }; struct imx290_model_info { enum imx290_colour_variant colour_variant; const struct cci_reg_sequence *init_regs; size_t init_regs_num; + unsigned int max_analog_gain; const char *name; }; @@ -317,6 +328,50 @@ static const struct cci_reg_sequence imx290_global_init_settings_290[] = { { CCI_REG8(0x33b3), 0x04 }, }; +static const struct cci_reg_sequence imx290_global_init_settings_462[] = { + { CCI_REG8(0x300f), 0x00 }, + { CCI_REG8(0x3010), 0x21 }, + { CCI_REG8(0x3011), 0x02 }, + { CCI_REG8(0x3016), 0x09 }, + { CCI_REG8(0x3070), 0x02 }, + { CCI_REG8(0x3071), 0x11 }, + { CCI_REG8(0x309b), 0x10 }, + { CCI_REG8(0x309c), 0x22 }, + { CCI_REG8(0x30a2), 0x02 }, + { CCI_REG8(0x30a6), 0x20 }, + { CCI_REG8(0x30a8), 0x20 }, + { CCI_REG8(0x30aa), 0x20 }, + { CCI_REG8(0x30ac), 0x20 }, + { CCI_REG8(0x30b0), 0x43 }, + { CCI_REG8(0x3119), 0x9e }, + { CCI_REG8(0x311c), 0x1e }, + { CCI_REG8(0x311e), 0x08 }, + { CCI_REG8(0x3128), 0x05 }, + { CCI_REG8(0x313d), 0x83 }, + { CCI_REG8(0x3150), 0x03 }, + { CCI_REG8(0x317e), 0x00 }, + { CCI_REG8(0x32b8), 0x50 }, + { CCI_REG8(0x32b9), 0x10 }, + { CCI_REG8(0x32ba), 0x00 }, + { CCI_REG8(0x32bb), 0x04 }, + { CCI_REG8(0x32c8), 0x50 }, + { CCI_REG8(0x32c9), 0x10 }, + { CCI_REG8(0x32ca), 0x00 }, + { CCI_REG8(0x32cb), 0x04 }, + { CCI_REG8(0x332c), 0xd3 }, + { CCI_REG8(0x332d), 0x10 }, + { CCI_REG8(0x332e), 0x0d }, + { CCI_REG8(0x3358), 0x06 }, + { CCI_REG8(0x3359), 0xe1 }, + { CCI_REG8(0x335a), 0x11 }, + { CCI_REG8(0x3360), 0x1e }, + { CCI_REG8(0x3361), 0x61 }, + { CCI_REG8(0x3362), 0x10 }, + { CCI_REG8(0x33b0), 0x50 }, + { CCI_REG8(0x33b2), 0x1a }, + { CCI_REG8(0x33b3), 0x04 }, +}; + #define IMX290_NUM_CLK_REGS 2 static const struct cci_reg_sequence xclk_regs[][IMX290_NUM_CLK_REGS] = { [IMX290_CLK_37_125] = { @@ -650,7 +705,8 @@ static int imx290_set_data_lanes(struct imx290 *imx290) &ret); cci_write(imx290->regmap, IMX290_CSI_LANE_MODE, imx290->nlanes - 1, &ret); - cci_write(imx290->regmap, IMX290_FR_FDG_SEL, 0x01, &ret); + cci_write(imx290->regmap, IMX290_FR_FDG_SEL, IMX290_FRSEL_60FPS | + (hcg_mode ? IMX290_FDG_HCG : IMX290_FDG_LCG), &ret); return ret; } @@ -879,14 +935,10 @@ static int imx290_ctrl_init(struct imx290 *imx290) * up to 72.0dB (240) add further digital gain. Limit the range to * analog gain only, support for digital gain can be added separately * if needed. - * - * The IMX327 and IMX462 are largely compatible with the IMX290, but - * have an analog gain range of 0.0dB to 29.4dB and 42dB of digital - * gain. When support for those sensors gets added to the driver, the - * gain control should be adjusted accordingly. */ v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops, - V4L2_CID_ANALOGUE_GAIN, 0, 100, 1, 0); + V4L2_CID_ANALOGUE_GAIN, 0, + imx290->model->max_analog_gain, 1, 0); /* * Correct range will be determined through imx290_ctrl_update setting @@ -1441,20 +1493,37 @@ static const struct imx290_model_info imx290_models[] = { .colour_variant = IMX290_VARIANT_COLOUR, .init_regs = imx290_global_init_settings_290, .init_regs_num = ARRAY_SIZE(imx290_global_init_settings_290), + .max_analog_gain = 100, .name = "imx290", }, [IMX290_MODEL_IMX290LLR] = { .colour_variant = IMX290_VARIANT_MONO, .init_regs = imx290_global_init_settings_290, .init_regs_num = ARRAY_SIZE(imx290_global_init_settings_290), + .max_analog_gain = 100, .name = "imx290", }, [IMX290_MODEL_IMX327LQR] = { .colour_variant = IMX290_VARIANT_COLOUR, .init_regs = imx290_global_init_settings_327, .init_regs_num = ARRAY_SIZE(imx290_global_init_settings_327), + .max_analog_gain = 98, .name = "imx327", }, + [IMX290_MODEL_IMX462LQR] = { + .colour_variant = IMX290_VARIANT_COLOUR, + .init_regs = imx290_global_init_settings_462, + .init_regs_num = ARRAY_SIZE(imx290_global_init_settings_462), + .max_analog_gain = 98, + .name = "imx462", + }, + [IMX290_MODEL_IMX462LLR] = { + .colour_variant = IMX290_VARIANT_MONO, + .init_regs = imx290_global_init_settings_462, + .init_regs_num = ARRAY_SIZE(imx290_global_init_settings_462), + .max_analog_gain = 98, + .name = "imx462", + }, }; static int imx290_parse_dt(struct imx290 *imx290) @@ -1650,6 +1719,12 @@ static const struct of_device_id imx290_of_match[] = { }, { .compatible = "sony,imx327lqr", .data = &imx290_models[IMX290_MODEL_IMX327LQR], + }, { + .compatible = "sony,imx462lqr", + .data = &imx290_models[IMX290_MODEL_IMX462LQR], + }, { + .compatible = "sony,imx462llr", + .data = &imx290_models[IMX290_MODEL_IMX462LLR], }, { /* sentinel */ }, }; diff --git a/drivers/media/i2c/imx296.c b/drivers/media/i2c/imx296.c index f3bec16b527c44..7f5c198f28c0c9 100644 --- a/drivers/media/i2c/imx296.c +++ b/drivers/media/i2c/imx296.c @@ -20,6 +20,10 @@ #include <media/v4l2-fwnode.h> #include <media/v4l2-subdev.h> +static int trigger_mode; +module_param(trigger_mode, int, 0644); +MODULE_PARM_DESC(trigger_mode, "Set trigger mode: 0=default, 1=XTRIG"); + #define IMX296_PIXEL_ARRAY_WIDTH 1456 #define IMX296_PIXEL_ARRAY_HEIGHT 1088 @@ -150,9 +154,9 @@ #define IMX296_FID0_ROIPH1 IMX296_REG_16BIT(0x3310) #define IMX296_FID0_ROIPV1 IMX296_REG_16BIT(0x3312) #define IMX296_FID0_ROIWH1 IMX296_REG_16BIT(0x3314) -#define IMX296_FID0_ROIWH1_MIN 80 +#define IMX296_FID0_ROIWH1_MIN 96 #define IMX296_FID0_ROIWV1 IMX296_REG_16BIT(0x3316) -#define IMX296_FID0_ROIWV1_MIN 4 +#define IMX296_FID0_ROIWV1_MIN 88 #define IMX296_CM_HSST_STARTTMG IMX296_REG_16BIT(0x4018) #define IMX296_CM_HSST_ENDTMG IMX296_REG_16BIT(0x401a) @@ -171,6 +175,7 @@ #define IMX296_CKREQSEL IMX296_REG_8BIT(0x4101) #define IMX296_CKREQSEL_HS BIT(2) #define IMX296_GTTABLENUM IMX296_REG_8BIT(0x4114) +#define IMX296_MIPIC_AREA3W IMX296_REG_16BIT(0x4182) #define IMX296_CTRL418C IMX296_REG_8BIT(0x418c) struct imx296_clk_params { @@ -207,6 +212,8 @@ struct imx296 { struct v4l2_ctrl_handler ctrls; struct v4l2_ctrl *hblank; struct v4l2_ctrl *vblank; + struct v4l2_ctrl *vflip; + struct v4l2_ctrl *hflip; }; static inline struct imx296 *to_imx296(struct v4l2_subdev *sd) @@ -248,6 +255,36 @@ static int imx296_write(struct imx296 *sensor, u32 addr, u32 value, int *err) return ret; } +/* + * The supported formats. + * This table MUST contain 4 entries per format, to cover the various flip + * combinations in the order + * - no flip + * - h flip + * - v flip + * - h&v flips + */ +static const u32 mbus_codes[] = { + /* 10-bit modes. */ + MEDIA_BUS_FMT_SRGGB10_1X10, + MEDIA_BUS_FMT_SGRBG10_1X10, + MEDIA_BUS_FMT_SGBRG10_1X10, + MEDIA_BUS_FMT_SBGGR10_1X10, +}; + +static u32 imx296_mbus_code(const struct imx296 *sensor) +{ + unsigned int i = 0; + + if (sensor->mono) + return MEDIA_BUS_FMT_Y10_1X10; + + if (sensor->vflip && sensor->hflip) + i = (sensor->vflip->val ? 2 : 0) | (sensor->hflip->val ? 1 : 0); + + return mbus_codes[i]; +} + static int imx296_power_on(struct imx296 *sensor) { int ret; @@ -342,6 +379,13 @@ static int imx296_s_ctrl(struct v4l2_ctrl *ctrl) &ret); break; + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: + imx296_write(sensor, IMX296_CTRL0E, + sensor->vflip->val | (sensor->hflip->val << 1), + &ret); + break; + case V4L2_CID_TEST_PATTERN: if (ctrl->val) { imx296_write(sensor, IMX296_PGHPOS, 8, &ret); @@ -383,10 +427,36 @@ static const struct v4l2_ctrl_ops imx296_ctrl_ops = { .s_ctrl = imx296_s_ctrl, }; +static void imx296_setup_hblank(struct imx296 *sensor, unsigned int width) +{ + /* + * Horizontal blanking is controlled through the HMAX register, which + * contains a line length in contains a line length in units of an + * internal 74.25 MHz clock derived from the INCLK. The HMAX value is + * currently fixed to 1100, convert it to a number of pixels based on + * the nominal pixel rate. + * + * Horizontal blanking is fixed, regardless of the crop width, so + * ensure the hblank limits are adjusted to account for this. + */ + unsigned int hblank = 1100 * 1188000000ULL / 10 / 74250000 - width; + + if (!sensor->hblank) { + sensor->hblank = v4l2_ctrl_new_std(&sensor->ctrls, + &imx296_ctrl_ops, + V4L2_CID_HBLANK, hblank, + hblank, 1, hblank); + if (sensor->hblank) + sensor->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + } else { + __v4l2_ctrl_modify_range(sensor->hblank, hblank, hblank, 1, + hblank); + } +} + static int imx296_ctrls_init(struct imx296 *sensor) { struct v4l2_fwnode_device_properties props; - unsigned int hblank; int ret; ret = v4l2_fwnode_device_parse(sensor->dev, &props); @@ -401,19 +471,17 @@ static int imx296_ctrls_init(struct imx296 *sensor) V4L2_CID_ANALOGUE_GAIN, IMX296_GAIN_MIN, IMX296_GAIN_MAX, 1, IMX296_GAIN_MIN); - /* - * Horizontal blanking is controlled through the HMAX register, which - * contains a line length in INCK clock units. The INCK frequency is - * fixed to 74.25 MHz. The HMAX value is currently fixed to 1100, - * convert it to a number of pixels based on the nominal pixel rate. - */ - hblank = 1100 * 1188000000ULL / 10 / 74250000 - - IMX296_PIXEL_ARRAY_WIDTH; - sensor->hblank = v4l2_ctrl_new_std(&sensor->ctrls, &imx296_ctrl_ops, - V4L2_CID_HBLANK, hblank, hblank, 1, - hblank); - if (sensor->hblank) - sensor->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + sensor->hflip = v4l2_ctrl_new_std(&sensor->ctrls, &imx296_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + if (sensor->hflip && !sensor->mono) + sensor->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + + sensor->vflip = v4l2_ctrl_new_std(&sensor->ctrls, &imx296_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + if (sensor->vflip && !sensor->mono) + sensor->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + + imx296_setup_hblank(sensor, IMX296_PIXEL_ARRAY_WIDTH); sensor->vblank = v4l2_ctrl_new_std(&sensor->ctrls, &imx296_ctrl_ops, V4L2_CID_VBLANK, 30, @@ -468,7 +536,7 @@ static const struct { { IMX296_REG_8BIT(0x30a4), 0x5f }, { IMX296_REG_8BIT(0x30a8), 0x91 }, { IMX296_REG_8BIT(0x30ac), 0x28 }, - { IMX296_REG_8BIT(0x30af), 0x09 }, + { IMX296_REG_8BIT(0x30af), 0x0b }, { IMX296_REG_8BIT(0x30df), 0x00 }, { IMX296_REG_8BIT(0x3165), 0x00 }, { IMX296_REG_8BIT(0x3169), 0x10 }, @@ -526,8 +594,11 @@ static int imx296_setup(struct imx296 *sensor, struct v4l2_subdev_state *state) imx296_write(sensor, IMX296_FID0_ROIPV1, crop->top, &ret); imx296_write(sensor, IMX296_FID0_ROIWH1, crop->width, &ret); imx296_write(sensor, IMX296_FID0_ROIWV1, crop->height, &ret); + imx296_write(sensor, IMX296_MIPIC_AREA3W, crop->height, &ret); } else { imx296_write(sensor, IMX296_FID0_ROI, 0, &ret); + imx296_write(sensor, IMX296_MIPIC_AREA3W, + IMX296_PIXEL_ARRAY_HEIGHT, &ret); } imx296_write(sensor, IMX296_CTRL0D, @@ -566,7 +637,7 @@ static int imx296_setup(struct imx296 *sensor, struct v4l2_subdev_state *state) imx296_write(sensor, IMX296_CTRL418C, sensor->clk_params->ctrl418c, &ret); - imx296_write(sensor, IMX296_GAINDLY, IMX296_GAINDLY_NONE, &ret); + imx296_write(sensor, IMX296_GAINDLY, IMX296_GAINDLY_1FRAME, &ret); imx296_write(sensor, IMX296_BLKLEVEL, 0x03c, &ret); return ret; @@ -578,8 +649,19 @@ static int imx296_stream_on(struct imx296 *sensor) imx296_write(sensor, IMX296_CTRL00, 0, &ret); usleep_range(2000, 5000); + + /* external trigger mode: 0=normal, 1=triggered */ + imx296_write(sensor, IMX296_CTRL0B, + (trigger_mode == 1) ? IMX296_CTRL0B_TRIGEN : 0, &ret); + imx296_write(sensor, IMX296_LOWLAGTRG, + (trigger_mode == 1) ? IMX296_LOWLAGTRG_FAST : 0, &ret); + imx296_write(sensor, IMX296_CTRL0A, 0, &ret); + /* vflip and hflip cannot change during streaming */ + __v4l2_ctrl_grab(sensor->vflip, 1); + __v4l2_ctrl_grab(sensor->hflip, 1); + return ret; } @@ -590,6 +672,9 @@ static int imx296_stream_off(struct imx296 *sensor) imx296_write(sensor, IMX296_CTRL0A, IMX296_CTRL0A_XMSTA, &ret); imx296_write(sensor, IMX296_CTRL00, IMX296_CTRL00_STANDBY, &ret); + __v4l2_ctrl_grab(sensor->vflip, 0); + __v4l2_ctrl_grab(sensor->hflip, 0); + return ret; } @@ -650,8 +735,7 @@ static int imx296_enum_mbus_code(struct v4l2_subdev *sd, if (code->index != 0) return -EINVAL; - code->code = sensor->mono ? MEDIA_BUS_FMT_Y10_1X10 - : MEDIA_BUS_FMT_SBGGR10_1X10; + code->code = imx296_mbus_code(sensor); return 0; } @@ -661,10 +745,15 @@ static int imx296_enum_frame_size(struct v4l2_subdev *sd, struct v4l2_subdev_frame_size_enum *fse) { const struct v4l2_mbus_framefmt *format; + struct imx296 *sensor = to_imx296(sd); format = v4l2_subdev_state_get_format(state, fse->pad); - if (fse->index >= 2 || fse->code != format->code) + /* + * Binning does not seem to work on either mono or colour sensor + * variants. Disable enumerating the binned frame size for now. + */ + if (fse->index >= 1 || fse->code != imx296_mbus_code(sensor)) return -EINVAL; fse->min_width = IMX296_PIXEL_ARRAY_WIDTH / (fse->index + 1); @@ -686,35 +775,12 @@ static int imx296_set_format(struct v4l2_subdev *sd, crop = v4l2_subdev_state_get_crop(state, fmt->pad); format = v4l2_subdev_state_get_format(state, fmt->pad); - /* - * Binning is only allowed when cropping is disabled according to the - * documentation. This should be double-checked. - */ - if (crop->width == IMX296_PIXEL_ARRAY_WIDTH && - crop->height == IMX296_PIXEL_ARRAY_HEIGHT) { - unsigned int width; - unsigned int height; - unsigned int hratio; - unsigned int vratio; - - /* Clamp the width and height to avoid dividing by zero. */ - width = clamp_t(unsigned int, fmt->format.width, - crop->width / 2, crop->width); - height = clamp_t(unsigned int, fmt->format.height, - crop->height / 2, crop->height); - - hratio = DIV_ROUND_CLOSEST(crop->width, width); - vratio = DIV_ROUND_CLOSEST(crop->height, height); - - format->width = crop->width / hratio; - format->height = crop->height / vratio; - } else { - format->width = crop->width; - format->height = crop->height; - } + format->width = crop->width; + format->height = crop->height; + + imx296_setup_hblank(sensor, format->width); - format->code = sensor->mono ? MEDIA_BUS_FMT_Y10_1X10 - : MEDIA_BUS_FMT_SBGGR10_1X10; + format->code = imx296_mbus_code(sensor); format->field = V4L2_FIELD_NONE; format->colorspace = V4L2_COLORSPACE_RAW; format->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; diff --git a/drivers/media/i2c/imx415.c b/drivers/media/i2c/imx415.c index a20b0db330d345..4d87d42c6f8ae1 100644 --- a/drivers/media/i2c/imx415.c +++ b/drivers/media/i2c/imx415.c @@ -26,6 +26,10 @@ #define IMX415_PIXEL_ARRAY_WIDTH 3864 #define IMX415_PIXEL_ARRAY_HEIGHT 2192 #define IMX415_PIXEL_ARRAY_VBLANK 58 +#define IMX415_EXPOSURE_OFFSET 8 + +#define IMX415_PIXEL_RATE_74_25MHZ 891000000 +#define IMX415_PIXEL_RATE_72MHZ 864000000 #define IMX415_NUM_CLK_PARAM_REGS 11 @@ -51,7 +55,10 @@ #define IMX415_OUTSEL CCI_REG8(0x30c0) #define IMX415_DRV CCI_REG8(0x30c1) #define IMX415_VMAX CCI_REG24_LE(0x3024) +#define IMX415_VMAX_MAX 0xfffff #define IMX415_HMAX CCI_REG16_LE(0x3028) +#define IMX415_HMAX_MAX 0xffff +#define IMX415_HMAX_MULTIPLIER 12 #define IMX415_SHR0 CCI_REG24_LE(0x3050) #define IMX415_GAIN_PCG_0 CCI_REG16_LE(0x3090) #define IMX415_AGAIN_MIN 0 @@ -445,11 +452,8 @@ static const struct imx415_clk_params imx415_clk_params[] = { }, }; -/* all-pixel 2-lane 720 Mbps 15.74 Hz mode */ -static const struct cci_reg_sequence imx415_mode_2_720[] = { - { IMX415_VMAX, 0x08CA }, - { IMX415_HMAX, 0x07F0 }, - { IMX415_LANEMODE, IMX415_LANEMODE_2 }, +/* 720 Mbps CSI configuration */ +static const struct cci_reg_sequence imx415_linkrate_720mbps[] = { { IMX415_TCLKPOST, 0x006F }, { IMX415_TCLKPREPARE, 0x002F }, { IMX415_TCLKTRAIL, 0x002F }, @@ -461,11 +465,8 @@ static const struct cci_reg_sequence imx415_mode_2_720[] = { { IMX415_TLPX, 0x0027 }, }; -/* all-pixel 2-lane 1440 Mbps 30.01 Hz mode */ -static const struct cci_reg_sequence imx415_mode_2_1440[] = { - { IMX415_VMAX, 0x08CA }, - { IMX415_HMAX, 0x042A }, - { IMX415_LANEMODE, IMX415_LANEMODE_2 }, +/* 1440 Mbps CSI configuration */ +static const struct cci_reg_sequence imx415_linkrate_1440mbps[] = { { IMX415_TCLKPOST, 0x009F }, { IMX415_TCLKPREPARE, 0x0057 }, { IMX415_TCLKTRAIL, 0x0057 }, @@ -477,11 +478,8 @@ static const struct cci_reg_sequence imx415_mode_2_1440[] = { { IMX415_TLPX, 0x004F }, }; -/* all-pixel 4-lane 891 Mbps 30 Hz mode */ -static const struct cci_reg_sequence imx415_mode_4_891[] = { - { IMX415_VMAX, 0x08CA }, - { IMX415_HMAX, 0x044C }, - { IMX415_LANEMODE, IMX415_LANEMODE_4 }, +/* 891 Mbps CSI configuration */ +static const struct cci_reg_sequence imx415_linkrate_891mbps[] = { { IMX415_TCLKPOST, 0x007F }, { IMX415_TCLKPREPARE, 0x0037 }, { IMX415_TCLKTRAIL, 0x0037 }, @@ -498,39 +496,9 @@ struct imx415_mode_reg_list { const struct cci_reg_sequence *regs; }; -/* - * Mode : number of lanes, lane rate and frame rate dependent settings - * - * pixel_rate and hmax_pix are needed to calculate hblank for the v4l2 ctrl - * interface. These values can not be found in the data sheet and should be - * treated as virtual values. Use following table when adding new modes. - * - * lane_rate lanes fps hmax_pix pixel_rate - * - * 594 2 10.000 4400 99000000 - * 891 2 15.000 4400 148500000 - * 720 2 15.748 4064 144000000 - * 1782 2 30.000 4400 297000000 - * 2079 2 30.000 4400 297000000 - * 1440 2 30.019 4510 304615385 - * - * 594 4 20.000 5500 247500000 - * 594 4 25.000 4400 247500000 - * 720 4 25.000 4400 247500000 - * 720 4 30.019 4510 304615385 - * 891 4 30.000 4400 297000000 - * 1440 4 30.019 4510 304615385 - * 1440 4 60.038 4510 609230769 - * 1485 4 60.000 4400 594000000 - * 1782 4 60.000 4400 594000000 - * 2079 4 60.000 4400 594000000 - * 2376 4 90.164 4392 891000000 - */ struct imx415_mode { u64 lane_rate; - u32 lanes; - u32 hmax_pix; - u64 pixel_rate; + u32 hmax_min[2]; struct imx415_mode_reg_list reg_list; }; @@ -538,32 +506,26 @@ struct imx415_mode { static const struct imx415_mode supported_modes[] = { { .lane_rate = 720000000, - .lanes = 2, - .hmax_pix = 4064, - .pixel_rate = 144000000, + .hmax_min = { 2032, 1066 }, .reg_list = { - .num_of_regs = ARRAY_SIZE(imx415_mode_2_720), - .regs = imx415_mode_2_720, + .num_of_regs = ARRAY_SIZE(imx415_linkrate_720mbps), + .regs = imx415_linkrate_720mbps, }, }, { .lane_rate = 1440000000, - .lanes = 2, - .hmax_pix = 4510, - .pixel_rate = 304615385, + .hmax_min = { 1066, 533 }, .reg_list = { - .num_of_regs = ARRAY_SIZE(imx415_mode_2_1440), - .regs = imx415_mode_2_1440, + .num_of_regs = ARRAY_SIZE(imx415_linkrate_1440mbps), + .regs = imx415_linkrate_1440mbps, }, }, { .lane_rate = 891000000, - .lanes = 4, - .hmax_pix = 4400, - .pixel_rate = 297000000, + .hmax_min = { 1100, 550 }, .reg_list = { - .num_of_regs = ARRAY_SIZE(imx415_mode_4_891), - .regs = imx415_mode_4_891, + .num_of_regs = ARRAY_SIZE(imx415_linkrate_891mbps), + .regs = imx415_linkrate_891mbps, }, }, }; @@ -587,6 +549,7 @@ static const char *const imx415_test_pattern_menu[] = { struct imx415 { struct device *dev; struct clk *clk; + unsigned long pixel_rate; struct regulator_bulk_data supplies[ARRAY_SIZE(imx415_supply_names)]; struct gpio_desc *reset; struct regmap *regmap; @@ -598,8 +561,10 @@ struct imx415 { struct v4l2_ctrl_handler ctrls; struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; struct v4l2_ctrl *hflip; struct v4l2_ctrl *vflip; + struct v4l2_ctrl *exposure; unsigned int cur_mode; unsigned int num_data_lanes; @@ -730,17 +695,37 @@ static int imx415_s_ctrl(struct v4l2_ctrl *ctrl) ctrls); const struct v4l2_mbus_framefmt *format; struct v4l2_subdev_state *state; + u32 exposure_max; unsigned int vmax; unsigned int flip; int ret; - if (!pm_runtime_get_if_in_use(sensor->dev)) - return 0; - state = v4l2_subdev_get_locked_active_state(&sensor->subdev); format = v4l2_subdev_state_get_format(state, 0); + if (ctrl->id == V4L2_CID_VBLANK) { + exposure_max = format->height + ctrl->val - + IMX415_EXPOSURE_OFFSET; + __v4l2_ctrl_modify_range(sensor->exposure, + sensor->exposure->minimum, + exposure_max, sensor->exposure->step, + sensor->exposure->default_value); + } + + if (!pm_runtime_get_if_in_use(sensor->dev)) + return 0; + switch (ctrl->id) { + case V4L2_CID_VBLANK: + ret = cci_write(sensor->regmap, IMX415_VMAX, + format->height + ctrl->val, NULL); + if (ret) + return ret; + /* + * Deliberately fall through as exposure is set based on VMAX + * which has just changed. + */ + fallthrough; case V4L2_CID_EXPOSURE: /* clamp the exposure value to VMAX. */ vmax = format->height + sensor->vblank->cur.val; @@ -766,6 +751,12 @@ static int imx415_s_ctrl(struct v4l2_ctrl *ctrl) ret = imx415_set_testpattern(sensor, ctrl->val); break; + case V4L2_CID_HBLANK: + return cci_write(sensor->regmap, IMX415_HMAX, + (format->width + ctrl->val) / + IMX415_HMAX_MULTIPLIER, + NULL); + default: ret = -EINVAL; break; @@ -784,11 +775,11 @@ static int imx415_ctrls_init(struct imx415 *sensor) { struct v4l2_fwnode_device_properties props; struct v4l2_ctrl *ctrl; - u64 pixel_rate = supported_modes[sensor->cur_mode].pixel_rate; u64 lane_rate = supported_modes[sensor->cur_mode].lane_rate; u32 exposure_max = IMX415_PIXEL_ARRAY_HEIGHT + - IMX415_PIXEL_ARRAY_VBLANK - 8; - u32 hblank; + IMX415_PIXEL_ARRAY_VBLANK - + IMX415_EXPOSURE_OFFSET; + u32 hblank_min, hblank_max; unsigned int i; int ret; @@ -816,36 +807,33 @@ static int imx415_ctrls_init(struct imx415 *sensor) if (ctrl) ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; - v4l2_ctrl_new_std(&sensor->ctrls, &imx415_ctrl_ops, V4L2_CID_EXPOSURE, - 4, exposure_max, 1, exposure_max); + sensor->exposure = v4l2_ctrl_new_std(&sensor->ctrls, &imx415_ctrl_ops, + V4L2_CID_EXPOSURE, 4, + exposure_max, 1, exposure_max); v4l2_ctrl_new_std(&sensor->ctrls, &imx415_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, IMX415_AGAIN_MIN, IMX415_AGAIN_MAX, IMX415_AGAIN_STEP, IMX415_AGAIN_MIN); - hblank = supported_modes[sensor->cur_mode].hmax_pix - - IMX415_PIXEL_ARRAY_WIDTH; + hblank_min = (supported_modes[sensor->cur_mode].hmax_min[sensor->num_data_lanes == 2 ? 0 : 1] * + IMX415_HMAX_MULTIPLIER) - IMX415_PIXEL_ARRAY_WIDTH; + hblank_max = (IMX415_HMAX_MAX * IMX415_HMAX_MULTIPLIER) - + IMX415_PIXEL_ARRAY_WIDTH; ctrl = v4l2_ctrl_new_std(&sensor->ctrls, &imx415_ctrl_ops, - V4L2_CID_HBLANK, hblank, hblank, 1, hblank); - if (ctrl) - ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + V4L2_CID_HBLANK, hblank_min, + hblank_max, IMX415_HMAX_MULTIPLIER, + hblank_min); sensor->vblank = v4l2_ctrl_new_std(&sensor->ctrls, &imx415_ctrl_ops, V4L2_CID_VBLANK, IMX415_PIXEL_ARRAY_VBLANK, - IMX415_PIXEL_ARRAY_VBLANK, 1, - IMX415_PIXEL_ARRAY_VBLANK); - if (sensor->vblank) - sensor->vblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + IMX415_VMAX_MAX - IMX415_PIXEL_ARRAY_HEIGHT, + 1, IMX415_PIXEL_ARRAY_VBLANK); - /* - * The pixel rate used here is a virtual value and can be used for - * calculating the frame rate together with hblank. It may not - * necessarily be the physically correct pixel clock. - */ - v4l2_ctrl_new_std(&sensor->ctrls, NULL, V4L2_CID_PIXEL_RATE, pixel_rate, - pixel_rate, 1, pixel_rate); + v4l2_ctrl_new_std(&sensor->ctrls, NULL, V4L2_CID_PIXEL_RATE, + sensor->pixel_rate, sensor->pixel_rate, 1, + sensor->pixel_rate); sensor->hflip = v4l2_ctrl_new_std(&sensor->ctrls, &imx415_ctrl_ops, V4L2_CID_HFLIP, 0, 1, 1, 0); @@ -890,7 +878,12 @@ static int imx415_set_mode(struct imx415 *sensor, int mode) IMX415_NUM_CLK_PARAM_REGS, &ret); - return 0; + ret = cci_write(sensor->regmap, IMX415_LANEMODE, + sensor->num_data_lanes == 2 ? IMX415_LANEMODE_2 : + IMX415_LANEMODE_4, + NULL); + + return ret; } static int imx415_setup(struct imx415 *sensor, struct v4l2_subdev_state *state) @@ -1302,8 +1295,6 @@ static int imx415_parse_hw_config(struct imx415 *sensor) } for (j = 0; j < ARRAY_SIZE(supported_modes); ++j) { - if (sensor->num_data_lanes != supported_modes[j].lanes) - continue; if (bus_cfg.link_frequencies[i] * 2 != supported_modes[j].lane_rate) continue; @@ -1318,6 +1309,17 @@ static int imx415_parse_hw_config(struct imx415 *sensor) "no valid sensor mode defined\n"); goto done_endpoint_free; } + switch (inck) { + case 27000000: + case 37125000: + case 74250000: + sensor->pixel_rate = IMX415_PIXEL_RATE_74_25MHZ; + break; + case 24000000: + case 72000000: + sensor->pixel_rate = IMX415_PIXEL_RATE_72MHZ; + break; + } lane_rate = supported_modes[sensor->cur_mode].lane_rate; for (i = 0; i < ARRAY_SIZE(imx415_clk_params); ++i) { diff --git a/drivers/media/i2c/imx477.c b/drivers/media/i2c/imx477.c new file mode 100644 index 00000000000000..e23463e918bb56 --- /dev/null +++ b/drivers/media/i2c/imx477.c @@ -0,0 +1,2387 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A V4L2 driver for Sony IMX477 cameras. + * Copyright (C) 2020, Raspberry Pi (Trading) Ltd + * + * Based on Sony imx219 camera driver + * Copyright (C) 2019-2020 Raspberry Pi (Trading) Ltd + */ +#include <linux/unaligned.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-event.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-mediabus.h> + +static int dpc_enable = 1; +module_param(dpc_enable, int, 0644); +MODULE_PARM_DESC(dpc_enable, "Enable on-sensor DPC"); + +static int trigger_mode; +module_param(trigger_mode, int, 0644); +MODULE_PARM_DESC(trigger_mode, "Set vsync trigger mode: 1=source, 2=sink"); + +#define IMX477_REG_VALUE_08BIT 1 +#define IMX477_REG_VALUE_16BIT 2 + +/* Chip ID */ +#define IMX477_REG_CHIP_ID 0x0016 +#define IMX477_CHIP_ID 0x0477 +#define IMX378_CHIP_ID 0x0378 + +#define IMX477_REG_MODE_SELECT 0x0100 +#define IMX477_MODE_STANDBY 0x00 +#define IMX477_MODE_STREAMING 0x01 + +#define IMX477_REG_ORIENTATION 0x101 + +#define IMX477_XCLK_FREQ 24000000 + +#define IMX477_DEFAULT_LINK_FREQ 450000000 + +/* Pixel rate is fixed at 840MHz for all the modes */ +#define IMX477_PIXEL_RATE 840000000 + +/* V_TIMING internal */ +#define IMX477_REG_FRAME_LENGTH 0x0340 +#define IMX477_FRAME_LENGTH_MAX 0xffdc + +/* H_TIMING internal */ +#define IMX477_REG_LINE_LENGTH 0x0342 +#define IMX477_LINE_LENGTH_MAX 0xfff0 + +/* Long exposure multiplier */ +#define IMX477_LONG_EXP_SHIFT_MAX 7 +#define IMX477_LONG_EXP_SHIFT_REG 0x3100 + +/* Exposure control */ +#define IMX477_REG_EXPOSURE 0x0202 +#define IMX477_EXPOSURE_OFFSET 22 +#define IMX477_EXPOSURE_MIN 4 +#define IMX477_EXPOSURE_STEP 1 +#define IMX477_EXPOSURE_DEFAULT 0x640 +#define IMX477_EXPOSURE_MAX (IMX477_FRAME_LENGTH_MAX - \ + IMX477_EXPOSURE_OFFSET) + +/* Analog gain control */ +#define IMX477_REG_ANALOG_GAIN 0x0204 +#define IMX477_ANA_GAIN_MIN 0 +#define IMX477_ANA_GAIN_MAX 978 +#define IMX477_ANA_GAIN_STEP 1 +#define IMX477_ANA_GAIN_DEFAULT 0x0 + +/* Digital gain control */ +#define IMX477_REG_DIGITAL_GAIN 0x020e +#define IMX477_DGTL_GAIN_MIN 0x0100 +#define IMX477_DGTL_GAIN_MAX 0xffff +#define IMX477_DGTL_GAIN_DEFAULT 0x0100 +#define IMX477_DGTL_GAIN_STEP 1 + +/* Test Pattern Control */ +#define IMX477_REG_TEST_PATTERN 0x0600 +#define IMX477_TEST_PATTERN_DISABLE 0 +#define IMX477_TEST_PATTERN_SOLID_COLOR 1 +#define IMX477_TEST_PATTERN_COLOR_BARS 2 +#define IMX477_TEST_PATTERN_GREY_COLOR 3 +#define IMX477_TEST_PATTERN_PN9 4 + +/* Test pattern colour components */ +#define IMX477_REG_TEST_PATTERN_R 0x0602 +#define IMX477_REG_TEST_PATTERN_GR 0x0604 +#define IMX477_REG_TEST_PATTERN_B 0x0606 +#define IMX477_REG_TEST_PATTERN_GB 0x0608 +#define IMX477_TEST_PATTERN_COLOUR_MIN 0 +#define IMX477_TEST_PATTERN_COLOUR_MAX 0x0fff +#define IMX477_TEST_PATTERN_COLOUR_STEP 1 +#define IMX477_TEST_PATTERN_R_DEFAULT IMX477_TEST_PATTERN_COLOUR_MAX +#define IMX477_TEST_PATTERN_GR_DEFAULT 0 +#define IMX477_TEST_PATTERN_B_DEFAULT 0 +#define IMX477_TEST_PATTERN_GB_DEFAULT 0 + +/* Trigger mode */ +#define IMX477_REG_MC_MODE 0x3f0b +#define IMX477_REG_MS_SEL 0x3041 +#define IMX477_REG_XVS_IO_CTRL 0x3040 +#define IMX477_REG_EXTOUT_EN 0x4b81 + +/* Embedded metadata stream structure */ +#define IMX477_EMBEDDED_LINE_WIDTH 16384 +#define IMX477_NUM_EMBEDDED_LINES 1 + +enum pad_types { + IMAGE_PAD, + METADATA_PAD, + NUM_PADS +}; + +/* IMX477 native and active pixel array size. */ +#define IMX477_NATIVE_WIDTH 4072U +#define IMX477_NATIVE_HEIGHT 3176U +#define IMX477_PIXEL_ARRAY_LEFT 8U +#define IMX477_PIXEL_ARRAY_TOP 16U +#define IMX477_PIXEL_ARRAY_WIDTH 4056U +#define IMX477_PIXEL_ARRAY_HEIGHT 3040U + +struct imx477_reg { + u16 address; + u8 val; +}; + +struct imx477_reg_list { + unsigned int num_of_regs; + const struct imx477_reg *regs; +}; + +/* Mode : resolution and related config&values */ +struct imx477_mode { + /* Frame width */ + unsigned int width; + + /* Frame height */ + unsigned int height; + + /* H-timing in pixels */ + unsigned int line_length_pix; + + /* Analog crop rectangle. */ + struct v4l2_rect crop; + + /* Highest possible framerate. */ + struct v4l2_fract timeperframe_min; + + /* Default framerate. */ + struct v4l2_fract timeperframe_default; + + /* Default register values */ + struct imx477_reg_list reg_list; +}; + +/* Link frequency setup */ +enum { + IMX477_LINK_FREQ_450MHZ, + IMX477_LINK_FREQ_453MHZ, + IMX477_LINK_FREQ_456MHZ, +}; + +static const s64 link_freqs[] = { + [IMX477_LINK_FREQ_450MHZ] = 450000000, + [IMX477_LINK_FREQ_453MHZ] = 453000000, + [IMX477_LINK_FREQ_456MHZ] = 456000000, +}; + +/* 450MHz is the nominal "default" link frequency */ +static const struct imx477_reg link_450Mhz_regs[] = { + {0x030E, 0x00}, + {0x030F, 0x96}, +}; + +static const struct imx477_reg link_453Mhz_regs[] = { + {0x030E, 0x00}, + {0x030F, 0x97}, +}; + +static const struct imx477_reg link_456Mhz_regs[] = { + {0x030E, 0x00}, + {0x030F, 0x98}, +}; + +static const struct imx477_reg_list link_freq_regs[] = { + [IMX477_LINK_FREQ_450MHZ] = { + .regs = link_450Mhz_regs, + .num_of_regs = ARRAY_SIZE(link_450Mhz_regs) + }, + [IMX477_LINK_FREQ_453MHZ] = { + .regs = link_453Mhz_regs, + .num_of_regs = ARRAY_SIZE(link_453Mhz_regs) + }, + [IMX477_LINK_FREQ_456MHZ] = { + .regs = link_456Mhz_regs, + .num_of_regs = ARRAY_SIZE(link_456Mhz_regs) + }, +}; + +static const struct imx477_reg mode_common_regs[] = { + {0x0136, 0x18}, + {0x0137, 0x00}, + {0x0138, 0x01}, + {0xe000, 0x00}, + {0xe07a, 0x01}, + {0x0808, 0x02}, + {0x4ae9, 0x18}, + {0x4aea, 0x08}, + {0xf61c, 0x04}, + {0xf61e, 0x04}, + {0x4ae9, 0x21}, + {0x4aea, 0x80}, + {0x38a8, 0x1f}, + {0x38a9, 0xff}, + {0x38aa, 0x1f}, + {0x38ab, 0xff}, + {0x55d4, 0x00}, + {0x55d5, 0x00}, + {0x55d6, 0x07}, + {0x55d7, 0xff}, + {0x55e8, 0x07}, + {0x55e9, 0xff}, + {0x55ea, 0x00}, + {0x55eb, 0x00}, + {0x574c, 0x07}, + {0x574d, 0xff}, + {0x574e, 0x00}, + {0x574f, 0x00}, + {0x5754, 0x00}, + {0x5755, 0x00}, + {0x5756, 0x07}, + {0x5757, 0xff}, + {0x5973, 0x04}, + {0x5974, 0x01}, + {0x5d13, 0xc3}, + {0x5d14, 0x58}, + {0x5d15, 0xa3}, + {0x5d16, 0x1d}, + {0x5d17, 0x65}, + {0x5d18, 0x8c}, + {0x5d1a, 0x06}, + {0x5d1b, 0xa9}, + {0x5d1c, 0x45}, + {0x5d1d, 0x3a}, + {0x5d1e, 0xab}, + {0x5d1f, 0x15}, + {0x5d21, 0x0e}, + {0x5d22, 0x52}, + {0x5d23, 0xaa}, + {0x5d24, 0x7d}, + {0x5d25, 0x57}, + {0x5d26, 0xa8}, + {0x5d37, 0x5a}, + {0x5d38, 0x5a}, + {0x5d77, 0x7f}, + {0x7b75, 0x0e}, + {0x7b76, 0x0b}, + {0x7b77, 0x08}, + {0x7b78, 0x0a}, + {0x7b79, 0x47}, + {0x7b7c, 0x00}, + {0x7b7d, 0x00}, + {0x8d1f, 0x00}, + {0x8d27, 0x00}, + {0x9004, 0x03}, + {0x9200, 0x50}, + {0x9201, 0x6c}, + {0x9202, 0x71}, + {0x9203, 0x00}, + {0x9204, 0x71}, + {0x9205, 0x01}, + {0x9371, 0x6a}, + {0x9373, 0x6a}, + {0x9375, 0x64}, + {0x991a, 0x00}, + {0x996b, 0x8c}, + {0x996c, 0x64}, + {0x996d, 0x50}, + {0x9a4c, 0x0d}, + {0x9a4d, 0x0d}, + {0xa001, 0x0a}, + {0xa003, 0x0a}, + {0xa005, 0x0a}, + {0xa006, 0x01}, + {0xa007, 0xc0}, + {0xa009, 0xc0}, + {0x3d8a, 0x01}, + {0x4421, 0x04}, + {0x7b3b, 0x01}, + {0x7b4c, 0x00}, + {0x9905, 0x00}, + {0x9907, 0x00}, + {0x9909, 0x00}, + {0x990b, 0x00}, + {0x9944, 0x3c}, + {0x9947, 0x3c}, + {0x994a, 0x8c}, + {0x994b, 0x50}, + {0x994c, 0x1b}, + {0x994d, 0x8c}, + {0x994e, 0x50}, + {0x994f, 0x1b}, + {0x9950, 0x8c}, + {0x9951, 0x1b}, + {0x9952, 0x0a}, + {0x9953, 0x8c}, + {0x9954, 0x1b}, + {0x9955, 0x0a}, + {0x9a13, 0x04}, + {0x9a14, 0x04}, + {0x9a19, 0x00}, + {0x9a1c, 0x04}, + {0x9a1d, 0x04}, + {0x9a26, 0x05}, + {0x9a27, 0x05}, + {0x9a2c, 0x01}, + {0x9a2d, 0x03}, + {0x9a2f, 0x05}, + {0x9a30, 0x05}, + {0x9a41, 0x00}, + {0x9a46, 0x00}, + {0x9a47, 0x00}, + {0x9c17, 0x35}, + {0x9c1d, 0x31}, + {0x9c29, 0x50}, + {0x9c3b, 0x2f}, + {0x9c41, 0x6b}, + {0x9c47, 0x2d}, + {0x9c4d, 0x40}, + {0x9c6b, 0x00}, + {0x9c71, 0xc8}, + {0x9c73, 0x32}, + {0x9c75, 0x04}, + {0x9c7d, 0x2d}, + {0x9c83, 0x40}, + {0x9c94, 0x3f}, + {0x9c95, 0x3f}, + {0x9c96, 0x3f}, + {0x9c97, 0x00}, + {0x9c98, 0x00}, + {0x9c99, 0x00}, + {0x9c9a, 0x3f}, + {0x9c9b, 0x3f}, + {0x9c9c, 0x3f}, + {0x9ca0, 0x0f}, + {0x9ca1, 0x0f}, + {0x9ca2, 0x0f}, + {0x9ca3, 0x00}, + {0x9ca4, 0x00}, + {0x9ca5, 0x00}, + {0x9ca6, 0x1e}, + {0x9ca7, 0x1e}, + {0x9ca8, 0x1e}, + {0x9ca9, 0x00}, + {0x9caa, 0x00}, + {0x9cab, 0x00}, + {0x9cac, 0x09}, + {0x9cad, 0x09}, + {0x9cae, 0x09}, + {0x9cbd, 0x50}, + {0x9cbf, 0x50}, + {0x9cc1, 0x50}, + {0x9cc3, 0x40}, + {0x9cc5, 0x40}, + {0x9cc7, 0x40}, + {0x9cc9, 0x0a}, + {0x9ccb, 0x0a}, + {0x9ccd, 0x0a}, + {0x9d17, 0x35}, + {0x9d1d, 0x31}, + {0x9d29, 0x50}, + {0x9d3b, 0x2f}, + {0x9d41, 0x6b}, + {0x9d47, 0x42}, + {0x9d4d, 0x5a}, + {0x9d6b, 0x00}, + {0x9d71, 0xc8}, + {0x9d73, 0x32}, + {0x9d75, 0x04}, + {0x9d7d, 0x42}, + {0x9d83, 0x5a}, + {0x9d94, 0x3f}, + {0x9d95, 0x3f}, + {0x9d96, 0x3f}, + {0x9d97, 0x00}, + {0x9d98, 0x00}, + {0x9d99, 0x00}, + {0x9d9a, 0x3f}, + {0x9d9b, 0x3f}, + {0x9d9c, 0x3f}, + {0x9d9d, 0x1f}, + {0x9d9e, 0x1f}, + {0x9d9f, 0x1f}, + {0x9da0, 0x0f}, + {0x9da1, 0x0f}, + {0x9da2, 0x0f}, + {0x9da3, 0x00}, + {0x9da4, 0x00}, + {0x9da5, 0x00}, + {0x9da6, 0x1e}, + {0x9da7, 0x1e}, + {0x9da8, 0x1e}, + {0x9da9, 0x00}, + {0x9daa, 0x00}, + {0x9dab, 0x00}, + {0x9dac, 0x09}, + {0x9dad, 0x09}, + {0x9dae, 0x09}, + {0x9dc9, 0x0a}, + {0x9dcb, 0x0a}, + {0x9dcd, 0x0a}, + {0x9e17, 0x35}, + {0x9e1d, 0x31}, + {0x9e29, 0x50}, + {0x9e3b, 0x2f}, + {0x9e41, 0x6b}, + {0x9e47, 0x2d}, + {0x9e4d, 0x40}, + {0x9e6b, 0x00}, + {0x9e71, 0xc8}, + {0x9e73, 0x32}, + {0x9e75, 0x04}, + {0x9e94, 0x0f}, + {0x9e95, 0x0f}, + {0x9e96, 0x0f}, + {0x9e97, 0x00}, + {0x9e98, 0x00}, + {0x9e99, 0x00}, + {0x9ea0, 0x0f}, + {0x9ea1, 0x0f}, + {0x9ea2, 0x0f}, + {0x9ea3, 0x00}, + {0x9ea4, 0x00}, + {0x9ea5, 0x00}, + {0x9ea6, 0x3f}, + {0x9ea7, 0x3f}, + {0x9ea8, 0x3f}, + {0x9ea9, 0x00}, + {0x9eaa, 0x00}, + {0x9eab, 0x00}, + {0x9eac, 0x09}, + {0x9ead, 0x09}, + {0x9eae, 0x09}, + {0x9ec9, 0x0a}, + {0x9ecb, 0x0a}, + {0x9ecd, 0x0a}, + {0x9f17, 0x35}, + {0x9f1d, 0x31}, + {0x9f29, 0x50}, + {0x9f3b, 0x2f}, + {0x9f41, 0x6b}, + {0x9f47, 0x42}, + {0x9f4d, 0x5a}, + {0x9f6b, 0x00}, + {0x9f71, 0xc8}, + {0x9f73, 0x32}, + {0x9f75, 0x04}, + {0x9f94, 0x0f}, + {0x9f95, 0x0f}, + {0x9f96, 0x0f}, + {0x9f97, 0x00}, + {0x9f98, 0x00}, + {0x9f99, 0x00}, + {0x9f9a, 0x2f}, + {0x9f9b, 0x2f}, + {0x9f9c, 0x2f}, + {0x9f9d, 0x00}, + {0x9f9e, 0x00}, + {0x9f9f, 0x00}, + {0x9fa0, 0x0f}, + {0x9fa1, 0x0f}, + {0x9fa2, 0x0f}, + {0x9fa3, 0x00}, + {0x9fa4, 0x00}, + {0x9fa5, 0x00}, + {0x9fa6, 0x1e}, + {0x9fa7, 0x1e}, + {0x9fa8, 0x1e}, + {0x9fa9, 0x00}, + {0x9faa, 0x00}, + {0x9fab, 0x00}, + {0x9fac, 0x09}, + {0x9fad, 0x09}, + {0x9fae, 0x09}, + {0x9fc9, 0x0a}, + {0x9fcb, 0x0a}, + {0x9fcd, 0x0a}, + {0xa14b, 0xff}, + {0xa151, 0x0c}, + {0xa153, 0x50}, + {0xa155, 0x02}, + {0xa157, 0x00}, + {0xa1ad, 0xff}, + {0xa1b3, 0x0c}, + {0xa1b5, 0x50}, + {0xa1b9, 0x00}, + {0xa24b, 0xff}, + {0xa257, 0x00}, + {0xa2ad, 0xff}, + {0xa2b9, 0x00}, + {0xb21f, 0x04}, + {0xb35c, 0x00}, + {0xb35e, 0x08}, + {0x0112, 0x0c}, + {0x0113, 0x0c}, + {0x0114, 0x01}, + {0x0350, 0x00}, + {0xbcf1, 0x02}, + {0x3ff9, 0x01}, +}; + +/* 12 mpix 10fps */ +static const struct imx477_reg mode_4056x3040_regs[] = { + {0x0342, 0x5d}, + {0x0343, 0xc0}, + {0x0344, 0x00}, + {0x0345, 0x00}, + {0x0346, 0x00}, + {0x0347, 0x00}, + {0x0348, 0x0f}, + {0x0349, 0xd7}, + {0x034a, 0x0b}, + {0x034b, 0xdf}, + {0x00e3, 0x00}, + {0x00e4, 0x00}, + {0x00fc, 0x0a}, + {0x00fd, 0x0a}, + {0x00fe, 0x0a}, + {0x00ff, 0x0a}, + {0x0220, 0x00}, + {0x0221, 0x11}, + {0x0381, 0x01}, + {0x0383, 0x01}, + {0x0385, 0x01}, + {0x0387, 0x01}, + {0x0900, 0x00}, + {0x0901, 0x11}, + {0x0902, 0x02}, + {0x3140, 0x02}, + {0x3c00, 0x00}, + {0x3c01, 0x03}, + {0x3c02, 0xa2}, + {0x3f0d, 0x01}, + {0x5748, 0x07}, + {0x5749, 0xff}, + {0x574a, 0x00}, + {0x574b, 0x00}, + {0x7b75, 0x0a}, + {0x7b76, 0x0c}, + {0x7b77, 0x07}, + {0x7b78, 0x06}, + {0x7b79, 0x3c}, + {0x7b53, 0x01}, + {0x9369, 0x5a}, + {0x936b, 0x55}, + {0x936d, 0x28}, + {0x9304, 0x00}, + {0x9305, 0x00}, + {0x9e9a, 0x2f}, + {0x9e9b, 0x2f}, + {0x9e9c, 0x2f}, + {0x9e9d, 0x00}, + {0x9e9e, 0x00}, + {0x9e9f, 0x00}, + {0xa2a9, 0x60}, + {0xa2b7, 0x00}, + {0x0401, 0x00}, + {0x0404, 0x00}, + {0x0405, 0x10}, + {0x0408, 0x00}, + {0x0409, 0x00}, + {0x040a, 0x00}, + {0x040b, 0x00}, + {0x040c, 0x0f}, + {0x040d, 0xd8}, + {0x040e, 0x0b}, + {0x040f, 0xe0}, + {0x034c, 0x0f}, + {0x034d, 0xd8}, + {0x034e, 0x0b}, + {0x034f, 0xe0}, + {0x0301, 0x05}, + {0x0303, 0x02}, + {0x0305, 0x04}, + {0x0306, 0x01}, + {0x0307, 0x5e}, + {0x0309, 0x0c}, + {0x030b, 0x02}, + {0x030d, 0x02}, + {0x0310, 0x01}, + {0x0820, 0x07}, + {0x0821, 0x08}, + {0x0822, 0x00}, + {0x0823, 0x00}, + {0x080a, 0x00}, + {0x080b, 0x7f}, + {0x080c, 0x00}, + {0x080d, 0x4f}, + {0x080e, 0x00}, + {0x080f, 0x77}, + {0x0810, 0x00}, + {0x0811, 0x5f}, + {0x0812, 0x00}, + {0x0813, 0x57}, + {0x0814, 0x00}, + {0x0815, 0x4f}, + {0x0816, 0x01}, + {0x0817, 0x27}, + {0x0818, 0x00}, + {0x0819, 0x3f}, + {0xe04c, 0x00}, + {0xe04d, 0x7f}, + {0xe04e, 0x00}, + {0xe04f, 0x1f}, + {0x3e20, 0x01}, + {0x3e37, 0x00}, + {0x3f50, 0x00}, + {0x3f56, 0x02}, + {0x3f57, 0xae}, +}; + +/* 2x2 binned. 40fps */ +static const struct imx477_reg mode_2028x1520_regs[] = { + {0x0342, 0x31}, + {0x0343, 0xc4}, + {0x0344, 0x00}, + {0x0345, 0x00}, + {0x0346, 0x00}, + {0x0347, 0x00}, + {0x0348, 0x0f}, + {0x0349, 0xd7}, + {0x034a, 0x0b}, + {0x034b, 0xdf}, + {0x0220, 0x00}, + {0x0221, 0x11}, + {0x0381, 0x01}, + {0x0383, 0x01}, + {0x0385, 0x01}, + {0x0387, 0x01}, + {0x0900, 0x01}, + {0x0901, 0x22}, + {0x0902, 0x02}, + {0x3140, 0x02}, + {0x3c00, 0x00}, + {0x3c01, 0x03}, + {0x3c02, 0xa2}, + {0x3f0d, 0x01}, + {0x5748, 0x07}, + {0x5749, 0xff}, + {0x574a, 0x00}, + {0x574b, 0x00}, + {0x7b53, 0x01}, + {0x9369, 0x73}, + {0x936b, 0x64}, + {0x936d, 0x5f}, + {0x9304, 0x00}, + {0x9305, 0x00}, + {0x9e9a, 0x2f}, + {0x9e9b, 0x2f}, + {0x9e9c, 0x2f}, + {0x9e9d, 0x00}, + {0x9e9e, 0x00}, + {0x9e9f, 0x00}, + {0xa2a9, 0x60}, + {0xa2b7, 0x00}, + {0x0401, 0x00}, + {0x0404, 0x00}, + {0x0405, 0x20}, + {0x0408, 0x00}, + {0x0409, 0x00}, + {0x040a, 0x00}, + {0x040b, 0x00}, + {0x040c, 0x0f}, + {0x040d, 0xd8}, + {0x040e, 0x0b}, + {0x040f, 0xe0}, + {0x034c, 0x07}, + {0x034d, 0xec}, + {0x034e, 0x05}, + {0x034f, 0xf0}, + {0x0301, 0x05}, + {0x0303, 0x02}, + {0x0305, 0x04}, + {0x0306, 0x01}, + {0x0307, 0x5e}, + {0x0309, 0x0c}, + {0x030b, 0x02}, + {0x030d, 0x02}, + {0x0310, 0x01}, + {0x0820, 0x07}, + {0x0821, 0x08}, + {0x0822, 0x00}, + {0x0823, 0x00}, + {0x080a, 0x00}, + {0x080b, 0x7f}, + {0x080c, 0x00}, + {0x080d, 0x4f}, + {0x080e, 0x00}, + {0x080f, 0x77}, + {0x0810, 0x00}, + {0x0811, 0x5f}, + {0x0812, 0x00}, + {0x0813, 0x57}, + {0x0814, 0x00}, + {0x0815, 0x4f}, + {0x0816, 0x01}, + {0x0817, 0x27}, + {0x0818, 0x00}, + {0x0819, 0x3f}, + {0xe04c, 0x00}, + {0xe04d, 0x7f}, + {0xe04e, 0x00}, + {0xe04f, 0x1f}, + {0x3e20, 0x01}, + {0x3e37, 0x00}, + {0x3f50, 0x00}, + {0x3f56, 0x01}, + {0x3f57, 0x6c}, +}; + +/* 1080p cropped mode */ +static const struct imx477_reg mode_2028x1080_regs[] = { + {0x0342, 0x31}, + {0x0343, 0xc4}, + {0x0344, 0x00}, + {0x0345, 0x00}, + {0x0346, 0x01}, + {0x0347, 0xb8}, + {0x0348, 0x0f}, + {0x0349, 0xd7}, + {0x034a, 0x0a}, + {0x034b, 0x27}, + {0x0220, 0x00}, + {0x0221, 0x11}, + {0x0381, 0x01}, + {0x0383, 0x01}, + {0x0385, 0x01}, + {0x0387, 0x01}, + {0x0900, 0x01}, + {0x0901, 0x22}, + {0x0902, 0x02}, + {0x3140, 0x02}, + {0x3c00, 0x00}, + {0x3c01, 0x03}, + {0x3c02, 0xa2}, + {0x3f0d, 0x01}, + {0x5748, 0x07}, + {0x5749, 0xff}, + {0x574a, 0x00}, + {0x574b, 0x00}, + {0x7b53, 0x01}, + {0x9369, 0x73}, + {0x936b, 0x64}, + {0x936d, 0x5f}, + {0x9304, 0x00}, + {0x9305, 0x00}, + {0x9e9a, 0x2f}, + {0x9e9b, 0x2f}, + {0x9e9c, 0x2f}, + {0x9e9d, 0x00}, + {0x9e9e, 0x00}, + {0x9e9f, 0x00}, + {0xa2a9, 0x60}, + {0xa2b7, 0x00}, + {0x0401, 0x00}, + {0x0404, 0x00}, + {0x0405, 0x20}, + {0x0408, 0x00}, + {0x0409, 0x00}, + {0x040a, 0x00}, + {0x040b, 0x00}, + {0x040c, 0x0f}, + {0x040d, 0xd8}, + {0x040e, 0x04}, + {0x040f, 0x38}, + {0x034c, 0x07}, + {0x034d, 0xec}, + {0x034e, 0x04}, + {0x034f, 0x38}, + {0x0301, 0x05}, + {0x0303, 0x02}, + {0x0305, 0x04}, + {0x0306, 0x01}, + {0x0307, 0x5e}, + {0x0309, 0x0c}, + {0x030b, 0x02}, + {0x030d, 0x02}, + {0x0310, 0x01}, + {0x0820, 0x07}, + {0x0821, 0x08}, + {0x0822, 0x00}, + {0x0823, 0x00}, + {0x080a, 0x00}, + {0x080b, 0x7f}, + {0x080c, 0x00}, + {0x080d, 0x4f}, + {0x080e, 0x00}, + {0x080f, 0x77}, + {0x0810, 0x00}, + {0x0811, 0x5f}, + {0x0812, 0x00}, + {0x0813, 0x57}, + {0x0814, 0x00}, + {0x0815, 0x4f}, + {0x0816, 0x01}, + {0x0817, 0x27}, + {0x0818, 0x00}, + {0x0819, 0x3f}, + {0xe04c, 0x00}, + {0xe04d, 0x7f}, + {0xe04e, 0x00}, + {0xe04f, 0x1f}, + {0x3e20, 0x01}, + {0x3e37, 0x00}, + {0x3f50, 0x00}, + {0x3f56, 0x01}, + {0x3f57, 0x6c}, +}; + +/* 4x4 binned. 120fps */ +static const struct imx477_reg mode_1332x990_regs[] = { + {0x420b, 0x01}, + {0x990c, 0x00}, + {0x990d, 0x08}, + {0x9956, 0x8c}, + {0x9957, 0x64}, + {0x9958, 0x50}, + {0x9a48, 0x06}, + {0x9a49, 0x06}, + {0x9a4a, 0x06}, + {0x9a4b, 0x06}, + {0x9a4c, 0x06}, + {0x9a4d, 0x06}, + {0x0112, 0x0a}, + {0x0113, 0x0a}, + {0x0114, 0x01}, + {0x0342, 0x1a}, + {0x0343, 0x08}, + {0x0340, 0x04}, + {0x0341, 0x1a}, + {0x0344, 0x00}, + {0x0345, 0x00}, + {0x0346, 0x02}, + {0x0347, 0x10}, + {0x0348, 0x0f}, + {0x0349, 0xd7}, + {0x034a, 0x09}, + {0x034b, 0xcf}, + {0x00e3, 0x00}, + {0x00e4, 0x00}, + {0x00fc, 0x0a}, + {0x00fd, 0x0a}, + {0x00fe, 0x0a}, + {0x00ff, 0x0a}, + {0xe013, 0x00}, + {0x0220, 0x00}, + {0x0221, 0x11}, + {0x0381, 0x01}, + {0x0383, 0x01}, + {0x0385, 0x01}, + {0x0387, 0x01}, + {0x0900, 0x01}, + {0x0901, 0x22}, + {0x0902, 0x02}, + {0x3140, 0x02}, + {0x3c00, 0x00}, + {0x3c01, 0x01}, + {0x3c02, 0x9c}, + {0x3f0d, 0x00}, + {0x5748, 0x00}, + {0x5749, 0x00}, + {0x574a, 0x00}, + {0x574b, 0xa4}, + {0x7b75, 0x0e}, + {0x7b76, 0x09}, + {0x7b77, 0x08}, + {0x7b78, 0x06}, + {0x7b79, 0x34}, + {0x7b53, 0x00}, + {0x9369, 0x73}, + {0x936b, 0x64}, + {0x936d, 0x5f}, + {0x9304, 0x03}, + {0x9305, 0x80}, + {0x9e9a, 0x2f}, + {0x9e9b, 0x2f}, + {0x9e9c, 0x2f}, + {0x9e9d, 0x00}, + {0x9e9e, 0x00}, + {0x9e9f, 0x00}, + {0xa2a9, 0x27}, + {0xa2b7, 0x03}, + {0x0401, 0x00}, + {0x0404, 0x00}, + {0x0405, 0x10}, + {0x0408, 0x01}, + {0x0409, 0x5c}, + {0x040a, 0x00}, + {0x040b, 0x00}, + {0x040c, 0x05}, + {0x040d, 0x34}, + {0x040e, 0x03}, + {0x040f, 0xde}, + {0x034c, 0x05}, + {0x034d, 0x34}, + {0x034e, 0x03}, + {0x034f, 0xde}, + {0x0301, 0x05}, + {0x0303, 0x02}, + {0x0305, 0x02}, + {0x0306, 0x00}, + {0x0307, 0xaf}, + {0x0309, 0x0a}, + {0x030b, 0x02}, + {0x030d, 0x02}, + {0x0310, 0x01}, + {0x0820, 0x07}, + {0x0821, 0x08}, + {0x0822, 0x00}, + {0x0823, 0x00}, + {0x080a, 0x00}, + {0x080b, 0x7f}, + {0x080c, 0x00}, + {0x080d, 0x4f}, + {0x080e, 0x00}, + {0x080f, 0x77}, + {0x0810, 0x00}, + {0x0811, 0x5f}, + {0x0812, 0x00}, + {0x0813, 0x57}, + {0x0814, 0x00}, + {0x0815, 0x4f}, + {0x0816, 0x01}, + {0x0817, 0x27}, + {0x0818, 0x00}, + {0x0819, 0x3f}, + {0xe04c, 0x00}, + {0xe04d, 0x5f}, + {0xe04e, 0x00}, + {0xe04f, 0x1f}, + {0x3e20, 0x01}, + {0x3e37, 0x00}, + {0x3f50, 0x00}, + {0x3f56, 0x00}, + {0x3f57, 0xbf}, +}; + +/* Mode configs */ +static const struct imx477_mode supported_modes_12bit[] = { + { + /* 12MPix 10fps mode */ + .width = 4056, + .height = 3040, + .line_length_pix = 0x5dc0, + .crop = { + .left = IMX477_PIXEL_ARRAY_LEFT, + .top = IMX477_PIXEL_ARRAY_TOP, + .width = 4056, + .height = 3040, + }, + .timeperframe_min = { + .numerator = 100, + .denominator = 1000 + }, + .timeperframe_default = { + .numerator = 100, + .denominator = 1000 + }, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_4056x3040_regs), + .regs = mode_4056x3040_regs, + }, + }, + { + /* 2x2 binned 40fps mode */ + .width = 2028, + .height = 1520, + .line_length_pix = 0x31c4, + .crop = { + .left = IMX477_PIXEL_ARRAY_LEFT, + .top = IMX477_PIXEL_ARRAY_TOP, + .width = 4056, + .height = 3040, + }, + .timeperframe_min = { + .numerator = 100, + .denominator = 4000 + }, + .timeperframe_default = { + .numerator = 100, + .denominator = 3000 + }, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_2028x1520_regs), + .regs = mode_2028x1520_regs, + }, + }, + { + /* 1080p 50fps cropped mode */ + .width = 2028, + .height = 1080, + .line_length_pix = 0x31c4, + .crop = { + .left = IMX477_PIXEL_ARRAY_LEFT, + .top = IMX477_PIXEL_ARRAY_TOP + 440, + .width = 4056, + .height = 2160, + }, + .timeperframe_min = { + .numerator = 100, + .denominator = 5000 + }, + .timeperframe_default = { + .numerator = 100, + .denominator = 3000 + }, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_2028x1080_regs), + .regs = mode_2028x1080_regs, + }, + } +}; + +static const struct imx477_mode supported_modes_10bit[] = { + { + /* 120fps. 2x2 binned and cropped */ + .width = 1332, + .height = 990, + .line_length_pix = 6664, + .crop = { + /* + * FIXME: the analog crop rectangle is actually + * programmed with a horizontal displacement of 0 + * pixels, not 4. It gets shrunk after going through + * the scaler. Move this information to the compose + * rectangle once the driver is expanded to represent + * its processing blocks with multiple subdevs. + */ + .left = IMX477_PIXEL_ARRAY_LEFT + 696, + .top = IMX477_PIXEL_ARRAY_TOP + 528, + .width = 2664, + .height = 1980, + }, + .timeperframe_min = { + .numerator = 100, + .denominator = 12000 + }, + .timeperframe_default = { + .numerator = 100, + .denominator = 12000 + }, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1332x990_regs), + .regs = mode_1332x990_regs, + } + } +}; + +/* + * The supported formats. + * This table MUST contain 4 entries per format, to cover the various flip + * combinations in the order + * - no flip + * - h flip + * - v flip + * - h&v flips + */ +static const u32 codes[] = { + /* 12-bit modes. */ + MEDIA_BUS_FMT_SRGGB12_1X12, + MEDIA_BUS_FMT_SGRBG12_1X12, + MEDIA_BUS_FMT_SGBRG12_1X12, + MEDIA_BUS_FMT_SBGGR12_1X12, + /* 10-bit modes. */ + MEDIA_BUS_FMT_SRGGB10_1X10, + MEDIA_BUS_FMT_SGRBG10_1X10, + MEDIA_BUS_FMT_SGBRG10_1X10, + MEDIA_BUS_FMT_SBGGR10_1X10, +}; + +static const char * const imx477_test_pattern_menu[] = { + "Disabled", + "Color Bars", + "Solid Color", + "Grey Color Bars", + "PN9" +}; + +static const int imx477_test_pattern_val[] = { + IMX477_TEST_PATTERN_DISABLE, + IMX477_TEST_PATTERN_COLOR_BARS, + IMX477_TEST_PATTERN_SOLID_COLOR, + IMX477_TEST_PATTERN_GREY_COLOR, + IMX477_TEST_PATTERN_PN9, +}; + +/* regulator supplies */ +static const char * const imx477_supply_name[] = { + /* Supplies can be enabled in any order */ + "VANA", /* Analog (2.8V) supply */ + "VDIG", /* Digital Core (1.05V) supply */ + "VDDL", /* IF (1.8V) supply */ +}; + +#define IMX477_NUM_SUPPLIES ARRAY_SIZE(imx477_supply_name) + +/* + * Initialisation delay between XCLR low->high and the moment when the sensor + * can start capture (i.e. can leave software standby), given by T7 in the + * datasheet is 8ms. This does include I2C setup time as well. + * + * Note, that delay between XCLR low->high and reading the CCI ID register (T6 + * in the datasheet) is much smaller - 600us. + */ +#define IMX477_XCLR_MIN_DELAY_US 8000 +#define IMX477_XCLR_DELAY_RANGE_US 1000 + +struct imx477_compatible_data { + unsigned int chip_id; + struct imx477_reg_list extra_regs; +}; + +struct imx477 { + struct v4l2_subdev sd; + struct media_pad pad[NUM_PADS]; + + unsigned int fmt_code; + + struct clk *xclk; + u32 xclk_freq; + + struct gpio_desc *reset_gpio; + struct regulator_bulk_data supplies[IMX477_NUM_SUPPLIES]; + + struct v4l2_ctrl_handler ctrl_handler; + /* V4L2 Controls */ + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *vflip; + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + + unsigned int link_freq_idx; + + /* Current mode */ + const struct imx477_mode *mode; + + /* Trigger mode */ + int trigger_mode_of; + + /* + * Mutex for serialized access: + * Protect sensor module set pad format and start/stop streaming safely. + */ + struct mutex mutex; + + /* Streaming on/off */ + bool streaming; + + /* Rewrite common registers on stream on? */ + bool common_regs_written; + + /* Current long exposure factor in use. Set through V4L2_CID_VBLANK */ + unsigned int long_exp_shift; + + /* Any extra information related to different compatible sensors */ + const struct imx477_compatible_data *compatible_data; +}; + +static inline struct imx477 *to_imx477(struct v4l2_subdev *_sd) +{ + return container_of(_sd, struct imx477, sd); +} + +static inline void get_mode_table(unsigned int code, + const struct imx477_mode **mode_list, + unsigned int *num_modes) +{ + switch (code) { + /* 12-bit */ + case MEDIA_BUS_FMT_SRGGB12_1X12: + case MEDIA_BUS_FMT_SGRBG12_1X12: + case MEDIA_BUS_FMT_SGBRG12_1X12: + case MEDIA_BUS_FMT_SBGGR12_1X12: + *mode_list = supported_modes_12bit; + *num_modes = ARRAY_SIZE(supported_modes_12bit); + break; + /* 10-bit */ + case MEDIA_BUS_FMT_SRGGB10_1X10: + case MEDIA_BUS_FMT_SGRBG10_1X10: + case MEDIA_BUS_FMT_SGBRG10_1X10: + case MEDIA_BUS_FMT_SBGGR10_1X10: + *mode_list = supported_modes_10bit; + *num_modes = ARRAY_SIZE(supported_modes_10bit); + break; + default: + *mode_list = NULL; + *num_modes = 0; + } +} + +/* Read registers up to 2 at a time */ +static int imx477_read_reg(struct imx477 *imx477, u16 reg, u32 len, u32 *val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx477->sd); + struct i2c_msg msgs[2]; + u8 addr_buf[2] = { reg >> 8, reg & 0xff }; + u8 data_buf[4] = { 0, }; + int ret; + + if (len > 4) + return -EINVAL; + + /* Write register address */ + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = ARRAY_SIZE(addr_buf); + msgs[0].buf = addr_buf; + + /* Read data from register */ + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = &data_buf[4 - len]; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + *val = get_unaligned_be32(data_buf); + + return 0; +} + +/* Write registers up to 2 at a time */ +static int imx477_write_reg(struct imx477 *imx477, u16 reg, u32 len, u32 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx477->sd); + u8 buf[6]; + + if (len > 4) + return -EINVAL; + + put_unaligned_be16(reg, buf); + put_unaligned_be32(val << (8 * (4 - len)), buf + 2); + if (i2c_master_send(client, buf, len + 2) != len + 2) + return -EIO; + + return 0; +} + +/* Write a list of registers */ +static int imx477_write_regs(struct imx477 *imx477, + const struct imx477_reg *regs, u32 len) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx477->sd); + unsigned int i; + int ret; + + for (i = 0; i < len; i++) { + ret = imx477_write_reg(imx477, regs[i].address, 1, regs[i].val); + if (ret) { + dev_err_ratelimited(&client->dev, + "Failed to write reg 0x%4.4x. error = %d\n", + regs[i].address, ret); + + return ret; + } + } + + return 0; +} + +/* Get bayer order based on flip setting. */ +static u32 imx477_get_format_code(struct imx477 *imx477, u32 code) +{ + unsigned int i; + + lockdep_assert_held(&imx477->mutex); + + for (i = 0; i < ARRAY_SIZE(codes); i++) + if (codes[i] == code) + break; + + if (i >= ARRAY_SIZE(codes)) + i = 0; + + i = (i & ~3) | (imx477->vflip->val ? 2 : 0) | + (imx477->hflip->val ? 1 : 0); + + return codes[i]; +} + +static void imx477_set_default_format(struct imx477 *imx477) +{ + /* Set default mode to max resolution */ + imx477->mode = &supported_modes_12bit[0]; + imx477->fmt_code = MEDIA_BUS_FMT_SRGGB12_1X12; +} + +static int imx477_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct imx477 *imx477 = to_imx477(sd); + struct v4l2_mbus_framefmt *try_fmt_img = + v4l2_subdev_state_get_format(fh->state, IMAGE_PAD); + struct v4l2_mbus_framefmt *try_fmt_meta = + v4l2_subdev_state_get_format(fh->state, METADATA_PAD); + struct v4l2_rect *try_crop; + + mutex_lock(&imx477->mutex); + + /* Initialize try_fmt for the image pad */ + try_fmt_img->width = supported_modes_12bit[0].width; + try_fmt_img->height = supported_modes_12bit[0].height; + try_fmt_img->code = imx477_get_format_code(imx477, + MEDIA_BUS_FMT_SRGGB12_1X12); + try_fmt_img->field = V4L2_FIELD_NONE; + + /* Initialize try_fmt for the embedded metadata pad */ + try_fmt_meta->width = IMX477_EMBEDDED_LINE_WIDTH; + try_fmt_meta->height = IMX477_NUM_EMBEDDED_LINES; + try_fmt_meta->code = MEDIA_BUS_FMT_SENSOR_DATA; + try_fmt_meta->field = V4L2_FIELD_NONE; + + /* Initialize try_crop */ + try_crop = v4l2_subdev_state_get_crop(fh->state, IMAGE_PAD); + try_crop->left = IMX477_PIXEL_ARRAY_LEFT; + try_crop->top = IMX477_PIXEL_ARRAY_TOP; + try_crop->width = IMX477_PIXEL_ARRAY_WIDTH; + try_crop->height = IMX477_PIXEL_ARRAY_HEIGHT; + + mutex_unlock(&imx477->mutex); + + return 0; +} + +static void imx477_adjust_exposure_range(struct imx477 *imx477) +{ + int exposure_max, exposure_def; + + /* Honour the VBLANK limits when setting exposure. */ + exposure_max = imx477->mode->height + imx477->vblank->val - + IMX477_EXPOSURE_OFFSET; + exposure_def = min(exposure_max, imx477->exposure->val); + __v4l2_ctrl_modify_range(imx477->exposure, imx477->exposure->minimum, + exposure_max, imx477->exposure->step, + exposure_def); +} + +static int imx477_set_frame_length(struct imx477 *imx477, unsigned int val) +{ + int ret = 0; + + imx477->long_exp_shift = 0; + + while (val > IMX477_FRAME_LENGTH_MAX) { + imx477->long_exp_shift++; + val >>= 1; + } + + ret = imx477_write_reg(imx477, IMX477_REG_FRAME_LENGTH, + IMX477_REG_VALUE_16BIT, val); + if (ret) + return ret; + + return imx477_write_reg(imx477, IMX477_LONG_EXP_SHIFT_REG, + IMX477_REG_VALUE_08BIT, imx477->long_exp_shift); +} + +static int imx477_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct imx477 *imx477 = + container_of(ctrl->handler, struct imx477, ctrl_handler); + struct i2c_client *client = v4l2_get_subdevdata(&imx477->sd); + int ret = 0; + + /* + * The VBLANK control may change the limits of usable exposure, so check + * and adjust if necessary. + */ + if (ctrl->id == V4L2_CID_VBLANK) + imx477_adjust_exposure_range(imx477); + + /* + * Applying V4L2 control value only happens + * when power is up for streaming + */ + if (pm_runtime_get_if_in_use(&client->dev) == 0) + return 0; + + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + ret = imx477_write_reg(imx477, IMX477_REG_ANALOG_GAIN, + IMX477_REG_VALUE_16BIT, ctrl->val); + break; + case V4L2_CID_EXPOSURE: + ret = imx477_write_reg(imx477, IMX477_REG_EXPOSURE, + IMX477_REG_VALUE_16BIT, ctrl->val >> + imx477->long_exp_shift); + break; + case V4L2_CID_DIGITAL_GAIN: + ret = imx477_write_reg(imx477, IMX477_REG_DIGITAL_GAIN, + IMX477_REG_VALUE_16BIT, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN: + ret = imx477_write_reg(imx477, IMX477_REG_TEST_PATTERN, + IMX477_REG_VALUE_16BIT, + imx477_test_pattern_val[ctrl->val]); + break; + case V4L2_CID_TEST_PATTERN_RED: + ret = imx477_write_reg(imx477, IMX477_REG_TEST_PATTERN_R, + IMX477_REG_VALUE_16BIT, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN_GREENR: + ret = imx477_write_reg(imx477, IMX477_REG_TEST_PATTERN_GR, + IMX477_REG_VALUE_16BIT, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN_BLUE: + ret = imx477_write_reg(imx477, IMX477_REG_TEST_PATTERN_B, + IMX477_REG_VALUE_16BIT, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN_GREENB: + ret = imx477_write_reg(imx477, IMX477_REG_TEST_PATTERN_GB, + IMX477_REG_VALUE_16BIT, ctrl->val); + break; + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: + ret = imx477_write_reg(imx477, IMX477_REG_ORIENTATION, 1, + imx477->hflip->val | + imx477->vflip->val << 1); + break; + case V4L2_CID_VBLANK: + ret = imx477_set_frame_length(imx477, + imx477->mode->height + ctrl->val); + break; + case V4L2_CID_HBLANK: + ret = imx477_write_reg(imx477, IMX477_REG_LINE_LENGTH, 2, + imx477->mode->width + ctrl->val); + break; + default: + dev_info(&client->dev, + "ctrl(id:0x%x,val:0x%x) is not handled\n", + ctrl->id, ctrl->val); + ret = -EINVAL; + break; + } + + pm_runtime_put(&client->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops imx477_ctrl_ops = { + .s_ctrl = imx477_set_ctrl, +}; + +static int imx477_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct imx477 *imx477 = to_imx477(sd); + + if (code->pad >= NUM_PADS) + return -EINVAL; + + if (code->pad == IMAGE_PAD) { + if (code->index >= (ARRAY_SIZE(codes) / 4)) + return -EINVAL; + + code->code = imx477_get_format_code(imx477, + codes[code->index * 4]); + } else { + if (code->index > 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_SENSOR_DATA; + } + + return 0; +} + +static int imx477_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct imx477 *imx477 = to_imx477(sd); + + if (fse->pad >= NUM_PADS) + return -EINVAL; + + if (fse->pad == IMAGE_PAD) { + const struct imx477_mode *mode_list; + unsigned int num_modes; + + get_mode_table(fse->code, &mode_list, &num_modes); + + if (fse->index >= num_modes) + return -EINVAL; + + if (fse->code != imx477_get_format_code(imx477, fse->code)) + return -EINVAL; + + fse->min_width = mode_list[fse->index].width; + fse->max_width = fse->min_width; + fse->min_height = mode_list[fse->index].height; + fse->max_height = fse->min_height; + } else { + if (fse->code != MEDIA_BUS_FMT_SENSOR_DATA || fse->index > 0) + return -EINVAL; + + fse->min_width = IMX477_EMBEDDED_LINE_WIDTH; + fse->max_width = fse->min_width; + fse->min_height = IMX477_NUM_EMBEDDED_LINES; + fse->max_height = fse->min_height; + } + + return 0; +} + +static void imx477_reset_colorspace(struct v4l2_mbus_framefmt *fmt) +{ + fmt->colorspace = V4L2_COLORSPACE_RAW; + fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace); + fmt->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true, + fmt->colorspace, + fmt->ycbcr_enc); + fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace); +} + +static void imx477_update_image_pad_format(struct imx477 *imx477, + const struct imx477_mode *mode, + struct v4l2_subdev_format *fmt) +{ + fmt->format.width = mode->width; + fmt->format.height = mode->height; + fmt->format.field = V4L2_FIELD_NONE; + imx477_reset_colorspace(&fmt->format); +} + +static void imx477_update_metadata_pad_format(struct v4l2_subdev_format *fmt) +{ + fmt->format.width = IMX477_EMBEDDED_LINE_WIDTH; + fmt->format.height = IMX477_NUM_EMBEDDED_LINES; + fmt->format.code = MEDIA_BUS_FMT_SENSOR_DATA; + fmt->format.field = V4L2_FIELD_NONE; +} + +static int imx477_get_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct imx477 *imx477 = to_imx477(sd); + + if (fmt->pad >= NUM_PADS) + return -EINVAL; + + mutex_lock(&imx477->mutex); + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + struct v4l2_mbus_framefmt *try_fmt = + v4l2_subdev_state_get_format(sd_state, + fmt->pad); + /* update the code which could change due to vflip or hflip: */ + try_fmt->code = fmt->pad == IMAGE_PAD ? + imx477_get_format_code(imx477, try_fmt->code) : + MEDIA_BUS_FMT_SENSOR_DATA; + fmt->format = *try_fmt; + } else { + if (fmt->pad == IMAGE_PAD) { + imx477_update_image_pad_format(imx477, imx477->mode, + fmt); + fmt->format.code = + imx477_get_format_code(imx477, imx477->fmt_code); + } else { + imx477_update_metadata_pad_format(fmt); + } + } + + mutex_unlock(&imx477->mutex); + return 0; +} + +static +unsigned int imx477_get_frame_length(const struct imx477_mode *mode, + const struct v4l2_fract *timeperframe) +{ + u64 frame_length; + + frame_length = (u64)timeperframe->numerator * IMX477_PIXEL_RATE; + do_div(frame_length, + (u64)timeperframe->denominator * mode->line_length_pix); + + if (WARN_ON(frame_length > IMX477_FRAME_LENGTH_MAX)) + frame_length = IMX477_FRAME_LENGTH_MAX; + + return max_t(unsigned int, frame_length, mode->height); +} + +static void imx477_set_framing_limits(struct imx477 *imx477) +{ + unsigned int frm_length_min, frm_length_default, hblank_min; + const struct imx477_mode *mode = imx477->mode; + + frm_length_min = imx477_get_frame_length(mode, &mode->timeperframe_min); + frm_length_default = + imx477_get_frame_length(mode, &mode->timeperframe_default); + + /* Default to no long exposure multiplier. */ + imx477->long_exp_shift = 0; + + /* Update limits and set FPS to default */ + __v4l2_ctrl_modify_range(imx477->vblank, frm_length_min - mode->height, + ((1 << IMX477_LONG_EXP_SHIFT_MAX) * + IMX477_FRAME_LENGTH_MAX) - mode->height, + 1, frm_length_default - mode->height); + + /* Setting this will adjust the exposure limits as well. */ + __v4l2_ctrl_s_ctrl(imx477->vblank, frm_length_default - mode->height); + + hblank_min = mode->line_length_pix - mode->width; + __v4l2_ctrl_modify_range(imx477->hblank, hblank_min, + IMX477_LINE_LENGTH_MAX, 1, hblank_min); + __v4l2_ctrl_s_ctrl(imx477->hblank, hblank_min); +} + +static int imx477_set_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct v4l2_mbus_framefmt *framefmt; + const struct imx477_mode *mode; + struct imx477 *imx477 = to_imx477(sd); + + if (fmt->pad >= NUM_PADS) + return -EINVAL; + + mutex_lock(&imx477->mutex); + + if (fmt->pad == IMAGE_PAD) { + const struct imx477_mode *mode_list; + unsigned int num_modes; + + /* Bayer order varies with flips */ + fmt->format.code = imx477_get_format_code(imx477, + fmt->format.code); + + get_mode_table(fmt->format.code, &mode_list, &num_modes); + + mode = v4l2_find_nearest_size(mode_list, + num_modes, + width, height, + fmt->format.width, + fmt->format.height); + imx477_update_image_pad_format(imx477, mode, fmt); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + framefmt = v4l2_subdev_state_get_format(sd_state, + fmt->pad); + *framefmt = fmt->format; + } else if (imx477->mode != mode) { + imx477->mode = mode; + imx477->fmt_code = fmt->format.code; + imx477_set_framing_limits(imx477); + } + } else { + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + framefmt = v4l2_subdev_state_get_format(sd_state, + fmt->pad); + *framefmt = fmt->format; + } else { + /* Only one embedded data mode is supported */ + imx477_update_metadata_pad_format(fmt); + } + } + + mutex_unlock(&imx477->mutex); + + return 0; +} + +static const struct v4l2_rect * +__imx477_get_pad_crop(struct imx477 *imx477, + struct v4l2_subdev_state *sd_state, + unsigned int pad, enum v4l2_subdev_format_whence which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_state_get_crop(sd_state, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &imx477->mode->crop; + } + + return NULL; +} + +static int imx477_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + switch (sel->target) { + case V4L2_SEL_TGT_CROP: { + struct imx477 *imx477 = to_imx477(sd); + + mutex_lock(&imx477->mutex); + sel->r = *__imx477_get_pad_crop(imx477, sd_state, sel->pad, + sel->which); + mutex_unlock(&imx477->mutex); + + return 0; + } + + case V4L2_SEL_TGT_NATIVE_SIZE: + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = IMX477_NATIVE_WIDTH; + sel->r.height = IMX477_NATIVE_HEIGHT; + + return 0; + + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.left = IMX477_PIXEL_ARRAY_LEFT; + sel->r.top = IMX477_PIXEL_ARRAY_TOP; + sel->r.width = IMX477_PIXEL_ARRAY_WIDTH; + sel->r.height = IMX477_PIXEL_ARRAY_HEIGHT; + + return 0; + } + + return -EINVAL; +} + +/* Start streaming */ +static int imx477_start_streaming(struct imx477 *imx477) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx477->sd); + const struct imx477_reg_list *reg_list, *freq_regs; + const struct imx477_reg_list *extra_regs; + int ret, tm; + + if (!imx477->common_regs_written) { + ret = imx477_write_regs(imx477, mode_common_regs, + ARRAY_SIZE(mode_common_regs)); + if (!ret) { + extra_regs = &imx477->compatible_data->extra_regs; + ret = imx477_write_regs(imx477, extra_regs->regs, + extra_regs->num_of_regs); + } + + if (!ret) { + /* Update the link frequency registers */ + freq_regs = &link_freq_regs[imx477->link_freq_idx]; + ret = imx477_write_regs(imx477, freq_regs->regs, + freq_regs->num_of_regs); + } + + if (ret) { + dev_err(&client->dev, "%s failed to set common settings\n", + __func__); + return ret; + } + imx477->common_regs_written = true; + } + + /* Apply default values of current mode */ + reg_list = &imx477->mode->reg_list; + ret = imx477_write_regs(imx477, reg_list->regs, reg_list->num_of_regs); + if (ret) { + dev_err(&client->dev, "%s failed to set mode\n", __func__); + return ret; + } + + /* Set on-sensor DPC. */ + imx477_write_reg(imx477, 0x0b05, IMX477_REG_VALUE_08BIT, !!dpc_enable); + imx477_write_reg(imx477, 0x0b06, IMX477_REG_VALUE_08BIT, !!dpc_enable); + + /* Apply customized values from user */ + ret = __v4l2_ctrl_handler_setup(imx477->sd.ctrl_handler); + if (ret) + return ret; + + /* Set vsync trigger mode: 0=standalone, 1=source, 2=sink */ + tm = (imx477->trigger_mode_of >= 0) ? imx477->trigger_mode_of : trigger_mode; + imx477_write_reg(imx477, IMX477_REG_MC_MODE, + IMX477_REG_VALUE_08BIT, (tm > 0) ? 1 : 0); + imx477_write_reg(imx477, IMX477_REG_MS_SEL, + IMX477_REG_VALUE_08BIT, (tm <= 1) ? 1 : 0); + imx477_write_reg(imx477, IMX477_REG_XVS_IO_CTRL, + IMX477_REG_VALUE_08BIT, (tm == 1) ? 1 : 0); + imx477_write_reg(imx477, IMX477_REG_EXTOUT_EN, + IMX477_REG_VALUE_08BIT, (tm == 1) ? 1 : 0); + + /* set stream on register */ + return imx477_write_reg(imx477, IMX477_REG_MODE_SELECT, + IMX477_REG_VALUE_08BIT, IMX477_MODE_STREAMING); +} + +/* Stop streaming */ +static void imx477_stop_streaming(struct imx477 *imx477) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx477->sd); + int ret; + + /* set stream off register */ + ret = imx477_write_reg(imx477, IMX477_REG_MODE_SELECT, + IMX477_REG_VALUE_08BIT, IMX477_MODE_STANDBY); + if (ret) + dev_err(&client->dev, "%s failed to set stream\n", __func__); + + /* Stop driving XVS out (there is still a weak pull-up) */ + imx477_write_reg(imx477, IMX477_REG_EXTOUT_EN, + IMX477_REG_VALUE_08BIT, 0); +} + +static int imx477_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct imx477 *imx477 = to_imx477(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret = 0; + + mutex_lock(&imx477->mutex); + if (imx477->streaming == enable) { + mutex_unlock(&imx477->mutex); + return 0; + } + + if (enable) { + ret = pm_runtime_get_sync(&client->dev); + if (ret < 0) { + pm_runtime_put_noidle(&client->dev); + goto err_unlock; + } + + /* + * Apply default & customized values + * and then start streaming. + */ + ret = imx477_start_streaming(imx477); + if (ret) + goto err_rpm_put; + } else { + imx477_stop_streaming(imx477); + pm_runtime_put(&client->dev); + } + + imx477->streaming = enable; + + /* vflip and hflip cannot change during streaming */ + __v4l2_ctrl_grab(imx477->vflip, enable); + __v4l2_ctrl_grab(imx477->hflip, enable); + + mutex_unlock(&imx477->mutex); + + return ret; + +err_rpm_put: + pm_runtime_put(&client->dev); +err_unlock: + mutex_unlock(&imx477->mutex); + + return ret; +} + +/* Power/clock management functions */ +static int imx477_power_on(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx477 *imx477 = to_imx477(sd); + int ret; + + ret = regulator_bulk_enable(IMX477_NUM_SUPPLIES, + imx477->supplies); + if (ret) { + dev_err(&client->dev, "%s: failed to enable regulators\n", + __func__); + return ret; + } + + ret = clk_prepare_enable(imx477->xclk); + if (ret) { + dev_err(&client->dev, "%s: failed to enable clock\n", + __func__); + goto reg_off; + } + + gpiod_set_value_cansleep(imx477->reset_gpio, 1); + usleep_range(IMX477_XCLR_MIN_DELAY_US, + IMX477_XCLR_MIN_DELAY_US + IMX477_XCLR_DELAY_RANGE_US); + + return 0; + +reg_off: + regulator_bulk_disable(IMX477_NUM_SUPPLIES, imx477->supplies); + return ret; +} + +static int imx477_power_off(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx477 *imx477 = to_imx477(sd); + + gpiod_set_value_cansleep(imx477->reset_gpio, 0); + regulator_bulk_disable(IMX477_NUM_SUPPLIES, imx477->supplies); + clk_disable_unprepare(imx477->xclk); + + /* Force reprogramming of the common registers when powered up again. */ + imx477->common_regs_written = false; + + return 0; +} + +static int __maybe_unused imx477_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx477 *imx477 = to_imx477(sd); + + if (imx477->streaming) + imx477_stop_streaming(imx477); + + return 0; +} + +static int __maybe_unused imx477_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx477 *imx477 = to_imx477(sd); + int ret; + + if (imx477->streaming) { + ret = imx477_start_streaming(imx477); + if (ret) + goto error; + } + + return 0; + +error: + imx477_stop_streaming(imx477); + imx477->streaming = 0; + return ret; +} + +static int imx477_get_regulators(struct imx477 *imx477) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx477->sd); + unsigned int i; + + for (i = 0; i < IMX477_NUM_SUPPLIES; i++) + imx477->supplies[i].supply = imx477_supply_name[i]; + + return devm_regulator_bulk_get(&client->dev, + IMX477_NUM_SUPPLIES, + imx477->supplies); +} + +/* Verify chip ID */ +static int imx477_identify_module(struct imx477 *imx477, u32 expected_id) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx477->sd); + int ret; + u32 val; + + ret = imx477_read_reg(imx477, IMX477_REG_CHIP_ID, + IMX477_REG_VALUE_16BIT, &val); + if (ret) { + dev_err(&client->dev, "failed to read chip id %x, with error %d\n", + expected_id, ret); + return ret; + } + + if (val != expected_id) { + dev_err(&client->dev, "chip id mismatch: %x!=%x\n", + expected_id, val); + return -EIO; + } + + dev_info(&client->dev, "Device found is imx%x\n", val); + + return 0; +} + +static const struct v4l2_subdev_core_ops imx477_core_ops = { + .subscribe_event = v4l2_ctrl_subdev_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_video_ops imx477_video_ops = { + .s_stream = imx477_set_stream, +}; + +static const struct v4l2_subdev_pad_ops imx477_pad_ops = { + .enum_mbus_code = imx477_enum_mbus_code, + .get_fmt = imx477_get_pad_format, + .set_fmt = imx477_set_pad_format, + .get_selection = imx477_get_selection, + .enum_frame_size = imx477_enum_frame_size, +}; + +static const struct v4l2_subdev_ops imx477_subdev_ops = { + .core = &imx477_core_ops, + .video = &imx477_video_ops, + .pad = &imx477_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops imx477_internal_ops = { + .open = imx477_open, +}; + +/* Initialize control handlers */ +static int imx477_init_controls(struct imx477 *imx477) +{ + struct v4l2_ctrl_handler *ctrl_hdlr; + struct i2c_client *client = v4l2_get_subdevdata(&imx477->sd); + struct v4l2_fwnode_device_properties props; + unsigned int i; + int ret; + + ctrl_hdlr = &imx477->ctrl_handler; + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 16); + if (ret) + return ret; + + mutex_init(&imx477->mutex); + ctrl_hdlr->lock = &imx477->mutex; + + /* By default, PIXEL_RATE is read only */ + imx477->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &imx477_ctrl_ops, + V4L2_CID_PIXEL_RATE, + IMX477_PIXEL_RATE, + IMX477_PIXEL_RATE, 1, + IMX477_PIXEL_RATE); + if (imx477->pixel_rate) + imx477->pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + /* LINK_FREQ is also read only */ + imx477->link_freq = + v4l2_ctrl_new_int_menu(ctrl_hdlr, &imx477_ctrl_ops, + V4L2_CID_LINK_FREQ, 0, 0, + &link_freqs[imx477->link_freq_idx]); + if (imx477->link_freq) + imx477->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + /* + * Create the controls here, but mode specific limits are setup + * in the imx477_set_framing_limits() call below. + */ + imx477->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx477_ctrl_ops, + V4L2_CID_VBLANK, 0, 0xffff, 1, 0); + imx477->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx477_ctrl_ops, + V4L2_CID_HBLANK, 0, 0xffff, 1, 0); + + imx477->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &imx477_ctrl_ops, + V4L2_CID_EXPOSURE, + IMX477_EXPOSURE_MIN, + IMX477_EXPOSURE_MAX, + IMX477_EXPOSURE_STEP, + IMX477_EXPOSURE_DEFAULT); + + v4l2_ctrl_new_std(ctrl_hdlr, &imx477_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + IMX477_ANA_GAIN_MIN, IMX477_ANA_GAIN_MAX, + IMX477_ANA_GAIN_STEP, IMX477_ANA_GAIN_DEFAULT); + + v4l2_ctrl_new_std(ctrl_hdlr, &imx477_ctrl_ops, V4L2_CID_DIGITAL_GAIN, + IMX477_DGTL_GAIN_MIN, IMX477_DGTL_GAIN_MAX, + IMX477_DGTL_GAIN_STEP, IMX477_DGTL_GAIN_DEFAULT); + + imx477->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx477_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + if (imx477->hflip) + imx477->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + + imx477->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx477_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + if (imx477->vflip) + imx477->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &imx477_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(imx477_test_pattern_menu) - 1, + 0, 0, imx477_test_pattern_menu); + for (i = 0; i < 4; i++) { + /* + * The assumption is that + * V4L2_CID_TEST_PATTERN_GREENR == V4L2_CID_TEST_PATTERN_RED + 1 + * V4L2_CID_TEST_PATTERN_BLUE == V4L2_CID_TEST_PATTERN_RED + 2 + * V4L2_CID_TEST_PATTERN_GREENB == V4L2_CID_TEST_PATTERN_RED + 3 + */ + v4l2_ctrl_new_std(ctrl_hdlr, &imx477_ctrl_ops, + V4L2_CID_TEST_PATTERN_RED + i, + IMX477_TEST_PATTERN_COLOUR_MIN, + IMX477_TEST_PATTERN_COLOUR_MAX, + IMX477_TEST_PATTERN_COLOUR_STEP, + IMX477_TEST_PATTERN_COLOUR_MAX); + /* The "Solid color" pattern is white by default */ + } + + if (ctrl_hdlr->error) { + ret = ctrl_hdlr->error; + dev_err(&client->dev, "%s control init failed (%d)\n", + __func__, ret); + goto error; + } + + ret = v4l2_fwnode_device_parse(&client->dev, &props); + if (ret) + goto error; + + ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &imx477_ctrl_ops, + &props); + if (ret) + goto error; + + imx477->sd.ctrl_handler = ctrl_hdlr; + + mutex_lock(&imx477->mutex); + + /* Setup exposure and frame/line length limits. */ + imx477_set_framing_limits(imx477); + + mutex_unlock(&imx477->mutex); + + return 0; + +error: + v4l2_ctrl_handler_free(ctrl_hdlr); + mutex_destroy(&imx477->mutex); + + return ret; +} + +static void imx477_free_controls(struct imx477 *imx477) +{ + v4l2_ctrl_handler_free(imx477->sd.ctrl_handler); + mutex_destroy(&imx477->mutex); +} + +static int imx477_check_hwcfg(struct device *dev, struct imx477 *imx477) +{ + struct fwnode_handle *endpoint; + struct v4l2_fwnode_endpoint ep_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; + int ret = -EINVAL; + int i; + + endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL); + if (!endpoint) { + dev_err(dev, "endpoint node not found\n"); + return -EINVAL; + } + + if (v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep_cfg)) { + dev_err(dev, "could not parse endpoint\n"); + goto error_out; + } + + /* Check the number of MIPI CSI2 data lanes */ + if (ep_cfg.bus.mipi_csi2.num_data_lanes != 2) { + dev_err(dev, "only 2 data lanes are currently supported\n"); + goto error_out; + } + + /* Check the link frequency set in device tree */ + if (!ep_cfg.nr_of_link_frequencies) { + dev_err(dev, "link-frequency property not found in DT\n"); + goto error_out; + } + + for (i = 0; i < ARRAY_SIZE(link_freqs); i++) { + if (link_freqs[i] == ep_cfg.link_frequencies[0]) { + imx477->link_freq_idx = i; + break; + } + } + + if (i == ARRAY_SIZE(link_freqs)) { + dev_err(dev, "Link frequency not supported: %lld\n", + ep_cfg.link_frequencies[0]); + ret = -EINVAL; + goto error_out; + } + + ret = 0; + +error_out: + v4l2_fwnode_endpoint_free(&ep_cfg); + fwnode_handle_put(endpoint); + + return ret; +} + +static const struct imx477_compatible_data imx477_compatible = { + .chip_id = IMX477_CHIP_ID, + .extra_regs = { + .num_of_regs = 0, + .regs = NULL + } +}; + +static const struct imx477_reg imx378_regs[] = { + {0x3e35, 0x01}, + {0x4421, 0x08}, + {0x3ff9, 0x00}, +}; + +static const struct imx477_compatible_data imx378_compatible = { + .chip_id = IMX378_CHIP_ID, + .extra_regs = { + .num_of_regs = ARRAY_SIZE(imx378_regs), + .regs = imx378_regs + } +}; + +static const struct of_device_id imx477_dt_ids[] = { + { .compatible = "sony,imx477", .data = &imx477_compatible }, + { .compatible = "sony,imx378", .data = &imx378_compatible }, + { /* sentinel */ } +}; + +static int imx477_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct imx477 *imx477; + const struct of_device_id *match; + int ret; + u32 tm_of; + + imx477 = devm_kzalloc(&client->dev, sizeof(*imx477), GFP_KERNEL); + if (!imx477) + return -ENOMEM; + + v4l2_i2c_subdev_init(&imx477->sd, client, &imx477_subdev_ops); + + match = of_match_device(imx477_dt_ids, dev); + if (!match) + return -ENODEV; + imx477->compatible_data = + (const struct imx477_compatible_data *)match->data; + + /* Check the hardware configuration in device tree */ + if (imx477_check_hwcfg(dev, imx477)) + return -EINVAL; + + /* Default the trigger mode from OF to -1, which means invalid */ + ret = of_property_read_u32(dev->of_node, "trigger-mode", &tm_of); + imx477->trigger_mode_of = (ret == 0) ? tm_of : -1; + + /* Get system clock (xclk) */ + imx477->xclk = devm_clk_get(dev, NULL); + if (IS_ERR(imx477->xclk)) { + dev_err(dev, "failed to get xclk\n"); + return PTR_ERR(imx477->xclk); + } + + imx477->xclk_freq = clk_get_rate(imx477->xclk); + if (imx477->xclk_freq != IMX477_XCLK_FREQ) { + dev_err(dev, "xclk frequency not supported: %d Hz\n", + imx477->xclk_freq); + return -EINVAL; + } + + ret = imx477_get_regulators(imx477); + if (ret) { + dev_err(dev, "failed to get regulators\n"); + return ret; + } + + /* Request optional enable pin */ + imx477->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + + /* + * The sensor must be powered for imx477_identify_module() + * to be able to read the CHIP_ID register + */ + ret = imx477_power_on(dev); + if (ret) + return ret; + + ret = imx477_identify_module(imx477, imx477->compatible_data->chip_id); + if (ret) + goto error_power_off; + + /* Initialize default format */ + imx477_set_default_format(imx477); + + /* Enable runtime PM and turn off the device */ + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_idle(dev); + + /* This needs the pm runtime to be registered. */ + ret = imx477_init_controls(imx477); + if (ret) + goto error_power_off; + + /* Initialize subdev */ + imx477->sd.internal_ops = &imx477_internal_ops; + imx477->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | + V4L2_SUBDEV_FL_HAS_EVENTS; + imx477->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + /* Initialize source pads */ + imx477->pad[IMAGE_PAD].flags = MEDIA_PAD_FL_SOURCE; + imx477->pad[METADATA_PAD].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&imx477->sd.entity, NUM_PADS, imx477->pad); + if (ret) { + dev_err(dev, "failed to init entity pads: %d\n", ret); + goto error_handler_free; + } + + ret = v4l2_async_register_subdev_sensor(&imx477->sd); + if (ret < 0) { + dev_err(dev, "failed to register sensor sub-device: %d\n", ret); + goto error_media_entity; + } + + return 0; + +error_media_entity: + media_entity_cleanup(&imx477->sd.entity); + +error_handler_free: + imx477_free_controls(imx477); + +error_power_off: + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + imx477_power_off(&client->dev); + + return ret; +} + +static void imx477_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx477 *imx477 = to_imx477(sd); + + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + imx477_free_controls(imx477); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) + imx477_power_off(&client->dev); + pm_runtime_set_suspended(&client->dev); +} + +MODULE_DEVICE_TABLE(of, imx477_dt_ids); + +static const struct dev_pm_ops imx477_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(imx477_suspend, imx477_resume) + SET_RUNTIME_PM_OPS(imx477_power_off, imx477_power_on, NULL) +}; + +static struct i2c_driver imx477_i2c_driver = { + .driver = { + .name = "imx477", + .of_match_table = imx477_dt_ids, + .pm = &imx477_pm_ops, + }, + .probe = imx477_probe, + .remove = imx477_remove, +}; + +module_i2c_driver(imx477_i2c_driver); + +MODULE_AUTHOR("Naushir Patuck <naush@raspberrypi.com>"); +MODULE_DESCRIPTION("Sony IMX477 sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/imx500.c b/drivers/media/i2c/imx500.c new file mode 100644 index 00000000000000..a16b577f483b6d --- /dev/null +++ b/drivers/media/i2c/imx500.c @@ -0,0 +1,3232 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A V4L2 driver for Sony IMX500 cameras. + * Copyright (C) 2024, Raspberry Pi Ltd + */ +#include <linux/unaligned.h> +#include <linux/clk.h> +#include <linux/debugfs.h> +#include <linux/delay.h> +#include <linux/earlycpio.h> +#include <linux/firmware.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/kernel_read_file.h> +#include <linux/limits.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> +#include <linux/vmalloc.h> +#include <media/v4l2-cci.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-event.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-mediabus.h> + +/* Chip ID */ +#define IMX500_REG_CHIP_ID CCI_REG16(0x0016) +#define IMX500_CHIP_ID 0x0500 + +#define IMX500_REG_MODE_SELECT CCI_REG8(0x0100) +#define IMX500_MODE_STANDBY 0x00 +#define IMX500_MODE_STREAMING 0x01 + +#define IMX500_REG_IMAGE_ONLY_MODE CCI_REG8(0xa700) +#define IMX500_IMAGE_ONLY_FALSE 0x00 +#define IMX500_IMAGE_ONLY_TRUE 0x01 + +#define IMX500_REG_ORIENTATION CCI_REG8(0x101) + +#define IMX500_XCLK_FREQ 24000000 + +#define IMX500_DEFAULT_LINK_FREQ 444000000 + +#define IMX500_PIXEL_RATE 744000000 + +/* V_TIMING internal */ +#define IMX500_REG_FRAME_LENGTH CCI_REG16(0x0340) +#define IMX500_FRAME_LENGTH_MAX 0xffdc +#define IMX500_VBLANK_MIN 1117 + +/* H_TIMING internal */ +#define IMX500_REG_LINE_LENGTH CCI_REG16(0x0342) +#define IMX500_LINE_LENGTH_MAX 0xfff0 + +/* Long exposure multiplier */ +#define IMX500_LONG_EXP_SHIFT_MAX 7 +#define IMX500_LONG_EXP_SHIFT_REG CCI_REG8(0x3210) +#define IMX500_LONG_EXP_CIT_SHIFT_REG CCI_REG8(0x3100) + +/* Exposure control */ +#define IMX500_REG_EXPOSURE CCI_REG16(0x0202) +#define IMX500_EXPOSURE_OFFSET 22 +#define IMX500_EXPOSURE_MIN 8 +#define IMX500_EXPOSURE_STEP 1 +#define IMX500_EXPOSURE_DEFAULT 0x640 +#define IMX500_EXPOSURE_MAX (IMX500_FRAME_LENGTH_MAX - IMX500_EXPOSURE_OFFSET) + +/* Analog gain control */ +#define IMX500_REG_ANALOG_GAIN CCI_REG16(0x0204) +#define IMX500_ANA_GAIN_MIN 0 +#define IMX500_ANA_GAIN_MAX 978 +#define IMX500_ANA_GAIN_STEP 1 +#define IMX500_ANA_GAIN_DEFAULT 0x0 + +/* Inference windows */ +#define IMX500_REG_DWP_AP_VC_VOFF CCI_REG16(0xD500) +#define IMX500_REG_DWP_AP_VC_HOFF CCI_REG16(0xD502) +#define IMX500_REG_DWP_AP_VC_VSIZE CCI_REG16(0xD504) +#define IMX500_REG_DWP_AP_VC_HSIZE CCI_REG16(0xD506) + +#define IMX500_REG_DD_CH06_X_OUT_SIZE \ + CCI_REG16(0x3054) /* Output pixel count for KPI */ +#define IMX500_REG_DD_CH07_X_OUT_SIZE \ + CCI_REG16(0x3056) /* Output pixel count for Input Tensor */ +#define IMX500_REG_DD_CH08_X_OUT_SIZE \ + CCI_REG16(0x3058) /* Output pixel count for Output Tensor */ +#define IMX500_REG_DD_CH09_X_OUT_SIZE \ + CCI_REG16(0x305A) /* Output pixel count for PQ Settings */ + +#define IMX500_REG_DD_CH06_Y_OUT_SIZE \ + CCI_REG16(0x305C) /* Output line count for KPI */ +#define IMX500_REG_DD_CH07_Y_OUT_SIZE \ + CCI_REG16(0x305E) /* Output line count for Input Tensor */ +#define IMX500_REG_DD_CH08_Y_OUT_SIZE \ + CCI_REG16(0x3060) /* Output line count for Output Tensor */ +#define IMX500_REG_DD_CH09_Y_OUT_SIZE \ + CCI_REG16(0x3062) /* Output line count for PQ Settings */ + +#define IMX500_REG_DD_CH06_VCID \ + CCI_REG8(0x3064) /* Virtual channel ID for KPI */ +#define IMX500_REG_DD_CH07_VCID \ + CCI_REG8(0x3065) /* Virtual channel ID for Input Tensor */ +#define IMX500_REG_DD_CH08_VCID \ + CCI_REG8(0x3066) /* Virtual channel ID for Output Tensor */ +#define IMX500_REG_DD_CH09_VCID \ + CCI_REG8(0x3067) /* Virtual channel ID for PQ Settings */ + +#define IMX500_REG_DD_CH06_DT CCI_REG8(0x3068) /* Data Type for KPI */ +#define IMX500_REG_DD_CH07_DT CCI_REG8(0x3069) /* Data Type for Input Tensor */ +#define IMX500_REG_DD_CH08_DT CCI_REG8(0x306A) /* Data Type for Output Tensor */ +#define IMX500_REG_DD_CH09_DT CCI_REG8(0x306B) /* Data Type for PQ Settings */ + +#define IMX500_REG_DD_CH06_PACKING \ + CCI_REG8(0x306C) /* Pixel/byte packing for KPI */ +#define IMX500_REG_DD_CH07_PACKING \ + CCI_REG8(0x306D) /* Pixel/byte packing for Input Tensor */ +#define IMX500_REG_DD_CH08_PACKING \ + CCI_REG8(0x306E) /* Pixel/byte packing for Output Tensor */ +#define IMX500_REG_DD_CH09_PACKING \ + CCI_REG8(0x306F) /* Pixel/byte packing for PQ Settings */ +#define IMX500_DD_PACKING_8BPP 2 /* 8 bits/pixel */ +#define IMX500_DD_PACKING_10BPP 3 /* 10 bits/pixel */ + +/* Interrupt command (start processing command inside IMX500 CPU) */ +#define IMX500_REG_DD_CMD_INT CCI_REG8(0x3080) +#define IMX500_DD_CMD_INT_ST_TRANS 0 +#define IMX500_DD_CMD_INT_UPDATE 1 +#define IMX500_DD_CMD_INT_FLASH_ERASE 2 + +/* State transition command type */ +#define IMX500_REG_DD_ST_TRANS_CMD CCI_REG8(0xD000) +#define IMX500_DD_ST_TRANS_CMD_LOADER_FW 0 +#define IMX500_DD_ST_TRANS_CMD_MAIN_FW 1 +#define IMX500_DD_ST_TRANS_CMD_NW_WEIGHTS 2 +#define IMX500_DD_ST_TRANS_CMD_CLEAR_WEIGHTS 3 + +/* Network weights update command */ +#define IMX500_REG_DD_UPDATE_CMD CCI_REG8(0xD001) +#define IMX500_DD_UPDATE_CMD_SRAM 0 +#define IMX500_DD_UPDATE_CMD_FLASH 1 + +/* Transfer source when loading into RAM */ +#define IMX500_REG_DD_LOAD_MODE CCI_REG8(0xD002) +#define IMX500_DD_LOAD_MODE_AP 0 +#define IMX500_DD_LOAD_MODE_FLASH 1 + +/* Image type to transfer */ +#define IMX500_REG_DD_IMAGE_TYPE CCI_REG8(0xD003) +#define IMX500_DD_IMAGE_TYPE_LOADER_FW 0 +#define IMX500_DD_IMAGE_TYPE_MAIN_FW 1 +#define IMX500_DD_IMAGE_TYPE_NETWORK_WEIGHTS 2 + +/* Number of divisions of download image file */ +#define IMX500_REG_DD_DOWNLOAD_DIV_NUM CCI_REG8(0xD004) + +#define IMX500_REG_DD_FLASH_TYPE CCI_REG8(0xD005) + +/* total size of download file (4-byte) */ +#define IMX500_REG_DD_DOWNLOAD_FILE_SIZE CCI_REG32(0xD008) + +/* Status notification (4-byte) */ +#define IMX500_REG_DD_REF_STS CCI_REG32(0xD010) +#define IMX500_DD_REF_STS_FATAL 0xFF +#define IMX500_DD_REF_STS_DETECT_CNT 0xFF00 +#define IMX500_DD_REF_STS_ERR_CNT 0xFF0000 +#define IMX500_DD_REF_CMD_REPLY_CNT 0xFF000000 + +/* Command reply status */ +#define IMX500_REG_DD_CMD_REPLY_STS CCI_REG8(0xD014) +#define IMX500_DD_CMD_REPLY_STS_TRANS_READY 0x00 +#define IMX500_DD_CMD_REPLY_STS_TRANS_DONE 0x01 +#define IMX500_DD_CMD_REPLY_STS_UPDATE_READY 0x10 +#define IMX500_DD_CMD_REPLY_STS_UPDATE_DONE 0x11 +#define IMX500_DD_CMD_REPLY_STS_UPDATE_CANCEL_DONE 0x12 +#define IMX500_DD_CMD_REPLY_STS_STATUS_ERROR 0xFF +#define IMX500_DD_CMD_REPLY_STS_MAC_AUTH_ERROR 0xFE +#define IMX500_DD_CMD_REPLY_STS_TIMEOUT_ERROR 0xFD +#define IMX500_DD_CMD_REPLY_STS_PARAMETER_ERROR 0xFC +#define IMX500_DD_CMD_REPLY_STS_INTERNAL_ERROR 0xFB +#define IMX500_DD_CMD_REPLY_STS_PACKET_FMT_ERROR 0xFA + +/* Download status */ +#define IMX500_REG_DD_DOWNLOAD_STS CCI_REG8(0xD015) +#define IMX500_DD_DOWNLOAD_STS_READY 0 +#define IMX500_DD_DOWNLOAD_STS_DOWNLOADING 1 + +/* Update cancel */ +#define IMX500_REG_DD_UPDATE_CANCEL CCI_REG8(0xD016) +#define IMX500_DD_UPDATE_CANCEL_NOT_CANCEL 0 +#define IMX500_DD_UPDATE_CANCEL_DO_CANCEL 1 + +/* Notify error status */ +#define IMX500_REG_DD_ERR_STS CCI_REG8(0xD020) +#define IMX500_DD_ERR_STS_STATUS_ERROR_BIT 0x1 +#define IMX500_DD_ERR_STS_INTERNAL_ERROR_BIT 0x2 +#define IMX500_DD_ERR_STS_PARAMETER_ERROR_BIT 0x4 + +/* System state */ +#define IMX500_REG_DD_SYS_STATE CCI_REG8(0xD02A) +#define IMX500_DD_SYS_STATE_STANDBY_NO_NETWORK 0 +#define IMX500_DD_SYS_STATE_STEAMING_NO_NETWORK 1 +#define IMX500_DD_SYS_STATE_STANDBY_WITH_NETWORK 2 +#define IMX500_DD_SYS_STATE_STREAMING_WITH_NETWORK 3 + +#define IMX500_REG_MAIN_FW_VERSION CCI_REG32(0xD07C) + +/* Colour balance controls */ +#define IMX500_REG_COLOUR_BALANCE_R CCI_REG16(0xd804) +#define IMX500_REG_COLOUR_BALANCE_GR CCI_REG16(0xd806) +#define IMX500_REG_COLOUR_BALANCE_GB CCI_REG16(0xd808) +#define IMX500_REG_COLOUR_BALANCE_B CCI_REG16(0xd80a) +#define IMX500_COLOUR_BALANCE_MIN 0x0001 +#define IMX500_COLOUR_BALANCE_MAX 0x0fff +#define IMX500_COLOUR_BALANCE_STEP 0x0001 +#define IMX500_COLOUR_BALANCE_DEFAULT 0x0100 + +/* Embedded sizes */ +#define IMX500_MAX_EMBEDDED_SIZE \ + (2 * ((((IMX500_PIXEL_ARRAY_WIDTH * 10) >> 3) + 15) & ~15)) + +/* Inference sizes */ +#define IMX500_INFERENCE_LINE_WIDTH 2560 +#define IMX500_NUM_KPI_LINES 1 +#define IMX500_NUM_PQ_LINES 1 + +/* IMX500 native and active pixel array size. */ +#define IMX500_NATIVE_WIDTH 4072U +#define IMX500_NATIVE_HEIGHT 3176U +#define IMX500_PIXEL_ARRAY_LEFT 8U +#define IMX500_PIXEL_ARRAY_TOP 16U +#define IMX500_PIXEL_ARRAY_WIDTH 4056U +#define IMX500_PIXEL_ARRAY_HEIGHT 3040U + +enum pad_types { IMAGE_PAD, METADATA_PAD, NUM_PADS }; + +#define V4L2_CID_USER_IMX500_INFERENCE_WINDOW (V4L2_CID_USER_IMX500_BASE + 0) +#define V4L2_CID_USER_IMX500_NETWORK_FW_FD (V4L2_CID_USER_IMX500_BASE + 1) + +#define ONE_MIB (1024 * 1024) + +/* regulator supplies */ +static const char *const imx500_supply_name[] = { + /* Supplies can be enabled in any order */ + "vana", /* Analog (2.7V) supply */ + "vdig", /* Digital Core (0.84V) supply */ + "vif", /* Interface (1.8V) supply */ +}; + +#define IMX500_NUM_SUPPLIES ARRAY_SIZE(imx500_supply_name) + +enum imx500_image_type { + TYPE_LOADER = 0, + TYPE_MAIN = 1, + TYPE_NW_WEIGHTS = 2, + TYPE_MAX +}; + +struct imx500_reg_list { + unsigned int num_of_regs; + const struct cci_reg_sequence *regs; +}; + +/* Mode : resolution and related config&values */ +struct imx500_mode { + /* Frame width */ + unsigned int width; + + /* Frame height */ + unsigned int height; + + /* H-timing in pixels */ + unsigned int line_length_pix; + + /* Analog crop rectangle. */ + struct v4l2_rect crop; + + /* Default register values */ + struct imx500_reg_list reg_list; +}; + +static const struct cci_reg_sequence mode_common_regs[] = { + { CCI_REG8(0x0305), 0x02 }, + { CCI_REG8(0x0306), 0x00 }, + { CCI_REG8(0x030d), 0x02 }, + { CCI_REG8(0x030e), 0x00 }, + { CCI_REG8(0x0106), 0x01 }, /* FAST_STANDBY_CTL */ + { CCI_REG8(0x0136), 0x1b }, /* EXCLK_FREQ */ + { CCI_REG8(0x0137), 0x00 }, + { CCI_REG8(0x0112), 0x0a }, + { CCI_REG8(0x0113), 0x0a }, + { CCI_REG8(0x0114), 0x01 }, /* CSI_LANE_MODE */ + { CCI_REG16(0x3054), IMX500_INFERENCE_LINE_WIDTH }, + { CCI_REG16(0x3056), IMX500_INFERENCE_LINE_WIDTH }, + { CCI_REG16(0x3058), IMX500_INFERENCE_LINE_WIDTH }, + { CCI_REG16(0x305A), IMX500_INFERENCE_LINE_WIDTH }, /* X_OUT */ + { CCI_REG16(0x305C), IMX500_NUM_KPI_LINES }, /* KPI Y_OUT */ + { CCI_REG16(0x3062), IMX500_NUM_PQ_LINES }, /* PQ Y_OUT */ + { CCI_REG8(0x3068), 0x30 }, + { CCI_REG8(0x3069), 0x31 }, + { CCI_REG8(0x306A), 0x32 }, + { CCI_REG8(0x306B), 0x33 }, /* Data Types */ +}; + +/* 12 mpix 15fps */ +static const struct cci_reg_sequence mode_4056x3040_regs[] = { + { CCI_REG8(0x0340), 0x12 }, + { CCI_REG8(0x0341), 0x42 }, + { CCI_REG8(0x0342), 0x45 }, + { CCI_REG8(0x0343), 0xec }, + { CCI_REG8(0x3210), 0x00 }, + { CCI_REG8(0x0344), 0x00 }, + { CCI_REG8(0x0345), 0x00 }, + { CCI_REG8(0x0346), 0x00 }, + { CCI_REG8(0x0347), 0x00 }, + { CCI_REG8(0x0348), 0x0f }, + { CCI_REG8(0x0349), 0xd7 }, + { CCI_REG8(0x0350), 0x00 }, + { CCI_REG8(0x034a), 0x0b }, + { CCI_REG8(0x034b), 0xdf }, + { CCI_REG8(0x3f58), 0x01 }, + { CCI_REG8(0x0381), 0x01 }, + { CCI_REG8(0x0383), 0x01 }, + { CCI_REG8(0x0385), 0x01 }, + { CCI_REG8(0x0387), 0x01 }, + { CCI_REG8(0x0900), 0x00 }, + { CCI_REG8(0x0901), 0x11 }, + { CCI_REG8(0x0902), 0x00 }, + { CCI_REG8(0x3241), 0x11 }, + { CCI_REG8(0x3242), 0x01 }, + { CCI_REG8(0x3250), 0x00 }, + { CCI_REG8(0x3f0f), 0x00 }, + { CCI_REG8(0x3f40), 0x00 }, + { CCI_REG8(0x3f41), 0x00 }, + { CCI_REG8(0x3f42), 0x00 }, + { CCI_REG8(0x3f43), 0x00 }, + { CCI_REG8(0xb34e), 0x00 }, + { CCI_REG8(0xb351), 0x20 }, + { CCI_REG8(0xb35c), 0x00 }, + { CCI_REG8(0xb35e), 0x08 }, + { CCI_REG8(0x0401), 0x00 }, + { CCI_REG8(0x0404), 0x00 }, + { CCI_REG8(0x0405), 0x10 }, + { CCI_REG8(0x0408), 0x00 }, + { CCI_REG8(0x0409), 0x00 }, + { CCI_REG8(0x040a), 0x00 }, + { CCI_REG8(0x040b), 0x00 }, + { CCI_REG8(0x040c), 0x0f }, + { CCI_REG8(0x040d), 0xd8 }, + { CCI_REG8(0x040e), 0x0b }, + { CCI_REG8(0x040f), 0xe0 }, + { CCI_REG8(0x034c), 0x0f }, + { CCI_REG8(0x034d), 0xd8 }, + { CCI_REG8(0x034e), 0x0b }, + { CCI_REG8(0x034f), 0xe0 }, + { CCI_REG8(0x0301), 0x05 }, + { CCI_REG8(0x0303), 0x02 }, + { CCI_REG8(0x0307), 0x9b }, + { CCI_REG8(0x0309), 0x0a }, + { CCI_REG8(0x030b), 0x01 }, + { CCI_REG8(0x030f), 0x4a }, + { CCI_REG8(0x0310), 0x01 }, + { CCI_REG8(0x0820), 0x07 }, + { CCI_REG8(0x0821), 0xce }, + { CCI_REG8(0x0822), 0x00 }, + { CCI_REG8(0x0823), 0x00 }, + { CCI_REG8(0x3e20), 0x01 }, + { CCI_REG8(0x3e35), 0x01 }, + { CCI_REG8(0x3e36), 0x01 }, + { CCI_REG8(0x3e37), 0x00 }, + { CCI_REG8(0x3e3a), 0x01 }, + { CCI_REG8(0x3e3b), 0x00 }, + { CCI_REG8(0x00e3), 0x00 }, + { CCI_REG8(0x00e4), 0x00 }, + { CCI_REG8(0x00e6), 0x00 }, + { CCI_REG8(0x00e7), 0x00 }, + { CCI_REG8(0x00e8), 0x00 }, + { CCI_REG8(0x00e9), 0x00 }, + { CCI_REG8(0x3f50), 0x00 }, + { CCI_REG8(0x3f56), 0x02 }, + { CCI_REG8(0x3f57), 0x42 }, + { CCI_REG8(0x3606), 0x01 }, + { CCI_REG8(0x3607), 0x01 }, + { CCI_REG8(0x3f26), 0x00 }, + { CCI_REG8(0x3f4a), 0x00 }, + { CCI_REG8(0x3f4b), 0x00 }, + { CCI_REG8(0x4bc0), 0x16 }, + { CCI_REG8(0x7ba8), 0x00 }, + { CCI_REG8(0x7ba9), 0x00 }, + { CCI_REG8(0x886b), 0x00 }, + { CCI_REG8(0x579a), 0x00 }, + { CCI_REG8(0x579b), 0x0a }, + { CCI_REG8(0x579c), 0x01 }, + { CCI_REG8(0x579d), 0x2a }, + { CCI_REG8(0x57ac), 0x00 }, + { CCI_REG8(0x57ad), 0x00 }, + { CCI_REG8(0x57ae), 0x00 }, + { CCI_REG8(0x57af), 0x81 }, + { CCI_REG8(0x57be), 0x00 }, + { CCI_REG8(0x57bf), 0x00 }, + { CCI_REG8(0x57c0), 0x00 }, + { CCI_REG8(0x57c1), 0x81 }, + { CCI_REG8(0x57d0), 0x00 }, + { CCI_REG8(0x57d1), 0x00 }, + { CCI_REG8(0x57d2), 0x00 }, + { CCI_REG8(0x57d3), 0x81 }, + { CCI_REG8(0x5324), 0x00 }, + { CCI_REG8(0x5325), 0x26 }, + { CCI_REG8(0x5326), 0x00 }, + { CCI_REG8(0x5327), 0x6b }, + { CCI_REG8(0xbca7), 0x00 }, + { CCI_REG8(0x5fcc), 0x28 }, + { CCI_REG8(0x5fd7), 0x2d }, + { CCI_REG8(0x5fe2), 0x2d }, + { CCI_REG8(0x5fed), 0x2d }, + { CCI_REG8(0x5ff8), 0x2d }, + { CCI_REG8(0x6003), 0x2d }, + { CCI_REG8(0x5d0b), 0x01 }, + { CCI_REG8(0x6f6d), 0x00 }, + { CCI_REG8(0x61c9), 0x00 }, + { CCI_REG8(0x5352), 0x00 }, + { CCI_REG8(0x5353), 0x49 }, + { CCI_REG8(0x5356), 0x00 }, + { CCI_REG8(0x5357), 0x30 }, + { CCI_REG8(0x5358), 0x00 }, + { CCI_REG8(0x5359), 0x3b }, + { CCI_REG8(0x535c), 0x00 }, + { CCI_REG8(0x535d), 0xb0 }, + { CCI_REG8(0x6187), 0x18 }, + { CCI_REG8(0x6189), 0x18 }, + { CCI_REG8(0x618b), 0x18 }, + { CCI_REG8(0x618d), 0x1d }, + { CCI_REG8(0x618f), 0x1d }, + { CCI_REG8(0x5414), 0x01 }, + { CCI_REG8(0x5415), 0x0c }, + { CCI_REG8(0xbca8), 0x0a }, + { CCI_REG8(0x5fcf), 0x1e }, + { CCI_REG8(0x5fda), 0x1e }, + { CCI_REG8(0x5fe5), 0x1e }, + { CCI_REG8(0x5ff0), 0x1e }, + { CCI_REG8(0x5ffb), 0x1e }, + { CCI_REG8(0x6006), 0x1e }, + { CCI_REG8(0x616e), 0x04 }, + { CCI_REG8(0x616f), 0x04 }, + { CCI_REG8(0x6170), 0x04 }, + { CCI_REG8(0x6171), 0x06 }, + { CCI_REG8(0x6172), 0x06 }, + { CCI_REG8(0x6173), 0x0c }, + { CCI_REG8(0x6174), 0x0c }, + { CCI_REG8(0x6175), 0x0c }, + { CCI_REG8(0x6176), 0x00 }, + { CCI_REG8(0x6177), 0x10 }, + { CCI_REG8(0x6178), 0x00 }, + { CCI_REG8(0x6179), 0x1a }, + { CCI_REG8(0x617a), 0x00 }, + { CCI_REG8(0x617b), 0x1a }, + { CCI_REG8(0x617c), 0x00 }, + { CCI_REG8(0x617d), 0x27 }, + { CCI_REG8(0x617e), 0x00 }, + { CCI_REG8(0x617f), 0x27 }, + { CCI_REG8(0x6180), 0x00 }, + { CCI_REG8(0x6181), 0x44 }, + { CCI_REG8(0x6182), 0x00 }, + { CCI_REG8(0x6183), 0x44 }, + { CCI_REG8(0x6184), 0x00 }, + { CCI_REG8(0x6185), 0x44 }, + { CCI_REG8(0x5dfc), 0x0a }, + { CCI_REG8(0x5e00), 0x0a }, + { CCI_REG8(0x5e04), 0x0a }, + { CCI_REG8(0x5e08), 0x0a }, + { CCI_REG8(0x5dfd), 0x0a }, + { CCI_REG8(0x5e01), 0x0a }, + { CCI_REG8(0x5e05), 0x0a }, + { CCI_REG8(0x5e09), 0x0a }, + { CCI_REG8(0x5dfe), 0x0a }, + { CCI_REG8(0x5e02), 0x0a }, + { CCI_REG8(0x5e06), 0x0a }, + { CCI_REG8(0x5e0a), 0x0a }, + { CCI_REG8(0x5dff), 0x0a }, + { CCI_REG8(0x5e03), 0x0a }, + { CCI_REG8(0x5e07), 0x0a }, + { CCI_REG8(0x5e0b), 0x0a }, + { CCI_REG8(0x5dec), 0x12 }, + { CCI_REG8(0x5df0), 0x12 }, + { CCI_REG8(0x5df4), 0x21 }, + { CCI_REG8(0x5df8), 0x31 }, + { CCI_REG8(0x5ded), 0x12 }, + { CCI_REG8(0x5df1), 0x12 }, + { CCI_REG8(0x5df5), 0x21 }, + { CCI_REG8(0x5df9), 0x31 }, + { CCI_REG8(0x5dee), 0x12 }, + { CCI_REG8(0x5df2), 0x12 }, + { CCI_REG8(0x5df6), 0x21 }, + { CCI_REG8(0x5dfa), 0x31 }, + { CCI_REG8(0x5def), 0x12 }, + { CCI_REG8(0x5df3), 0x12 }, + { CCI_REG8(0x5df7), 0x21 }, + { CCI_REG8(0x5dfb), 0x31 }, + { CCI_REG8(0x5ddc), 0x0d }, + { CCI_REG8(0x5de0), 0x0d }, + { CCI_REG8(0x5de4), 0x0d }, + { CCI_REG8(0x5de8), 0x0d }, + { CCI_REG8(0x5ddd), 0x0d }, + { CCI_REG8(0x5de1), 0x0d }, + { CCI_REG8(0x5de5), 0x0d }, + { CCI_REG8(0x5de9), 0x0d }, + { CCI_REG8(0x5dde), 0x0d }, + { CCI_REG8(0x5de2), 0x0d }, + { CCI_REG8(0x5de6), 0x0d }, + { CCI_REG8(0x5dea), 0x0d }, + { CCI_REG8(0x5ddf), 0x0d }, + { CCI_REG8(0x5de3), 0x0d }, + { CCI_REG8(0x5de7), 0x0d }, + { CCI_REG8(0x5deb), 0x0d }, + { CCI_REG8(0x5dcc), 0x55 }, + { CCI_REG8(0x5dd0), 0x50 }, + { CCI_REG8(0x5dd4), 0x4b }, + { CCI_REG8(0x5dd8), 0x4b }, + { CCI_REG8(0x5dcd), 0x55 }, + { CCI_REG8(0x5dd1), 0x50 }, + { CCI_REG8(0x5dd5), 0x4b }, + { CCI_REG8(0x5dd9), 0x4b }, + { CCI_REG8(0x5dce), 0x55 }, + { CCI_REG8(0x5dd2), 0x50 }, + { CCI_REG8(0x5dd6), 0x4b }, + { CCI_REG8(0x5dda), 0x4b }, + { CCI_REG8(0x5dcf), 0x55 }, + { CCI_REG8(0x5dd3), 0x50 }, + { CCI_REG8(0x5dd7), 0x4b }, + { CCI_REG8(0x5ddb), 0x4b }, + { CCI_REG8(0x0202), 0x12 }, + { CCI_REG8(0x0203), 0x2c }, + { CCI_REG8(0x0204), 0x00 }, + { CCI_REG8(0x0205), 0x00 }, + { CCI_REG8(0x020e), 0x01 }, + { CCI_REG8(0x020f), 0x00 }, + { CCI_REG8(0x0210), 0x01 }, + { CCI_REG8(0x0211), 0x00 }, + { CCI_REG8(0x0212), 0x01 }, + { CCI_REG8(0x0213), 0x00 }, + { CCI_REG8(0x0214), 0x01 }, + { CCI_REG8(0x0215), 0x00 }, +}; + +/* 2x2 binned. 56fps */ +static const struct cci_reg_sequence mode_2028x1520_regs[] = { + { CCI_REG8(0x0112), 0x0a }, + { CCI_REG8(0x0113), 0x0a }, + { CCI_REG8(0x0114), 0x01 }, + { CCI_REG8(0x0342), 0x24 }, + { CCI_REG8(0x0343), 0xb6 }, + { CCI_REG8(0x0340), 0x0b }, + { CCI_REG8(0x0341), 0x9c }, + { CCI_REG8(0x3210), 0x00 }, + { CCI_REG8(0x0344), 0x00 }, + { CCI_REG8(0x0345), 0x00 }, + { CCI_REG8(0x0346), 0x00 }, + { CCI_REG8(0x0347), 0x00 }, + { CCI_REG8(0x0348), 0x0f }, + { CCI_REG8(0x0349), 0xd7 }, + { CCI_REG8(0x0350), 0x00 }, + { CCI_REG8(0x034a), 0x0b }, + { CCI_REG8(0x034b), 0xdf }, + { CCI_REG8(0x3f58), 0x01 }, + { CCI_REG8(0x0381), 0x01 }, + { CCI_REG8(0x0383), 0x01 }, + { CCI_REG8(0x0385), 0x01 }, + { CCI_REG8(0x0387), 0x01 }, + { CCI_REG8(0x0900), 0x01 }, + { CCI_REG8(0x0901), 0x22 }, + { CCI_REG8(0x0902), 0x02 }, + { CCI_REG8(0x3241), 0x11 }, + { CCI_REG8(0x3242), 0x01 }, + { CCI_REG8(0x3250), 0x03 }, + { CCI_REG8(0x3f0f), 0x00 }, + { CCI_REG8(0x3f40), 0x00 }, + { CCI_REG8(0x3f41), 0x00 }, + { CCI_REG8(0x3f42), 0x00 }, + { CCI_REG8(0x3f43), 0x00 }, + { CCI_REG8(0xb34e), 0x00 }, + { CCI_REG8(0xb351), 0x20 }, + { CCI_REG8(0xb35c), 0x00 }, + { CCI_REG8(0xb35e), 0x08 }, + { CCI_REG8(0x0401), 0x00 }, + { CCI_REG8(0x0404), 0x00 }, + { CCI_REG8(0x0405), 0x10 }, + { CCI_REG8(0x0408), 0x00 }, + { CCI_REG8(0x0409), 0x00 }, + { CCI_REG8(0x040a), 0x00 }, + { CCI_REG8(0x040b), 0x00 }, + { CCI_REG8(0x040c), 0x07 }, + { CCI_REG8(0x040d), 0xec }, + { CCI_REG8(0x040e), 0x05 }, + { CCI_REG8(0x040f), 0xf0 }, + { CCI_REG8(0x034c), 0x07 }, + { CCI_REG8(0x034d), 0xec }, + { CCI_REG8(0x034e), 0x05 }, + { CCI_REG8(0x034f), 0xf0 }, + { CCI_REG8(0x0301), 0x05 }, + { CCI_REG8(0x0303), 0x02 }, + { CCI_REG8(0x0307), 0x9b }, + { CCI_REG8(0x0309), 0x0a }, + { CCI_REG8(0x030b), 0x01 }, + { CCI_REG8(0x030f), 0x4a }, + { CCI_REG8(0x0310), 0x01 }, + { CCI_REG8(0x0820), 0x07 }, + { CCI_REG8(0x0821), 0xce }, + { CCI_REG8(0x0822), 0x00 }, + { CCI_REG8(0x0823), 0x00 }, + { CCI_REG8(0x3e20), 0x01 }, + { CCI_REG8(0x3e35), 0x01 }, + { CCI_REG8(0x3e36), 0x01 }, + { CCI_REG8(0x3e37), 0x00 }, + { CCI_REG8(0x3e3a), 0x01 }, + { CCI_REG8(0x3e3b), 0x00 }, + { CCI_REG8(0x00e3), 0x00 }, + { CCI_REG8(0x00e4), 0x00 }, + { CCI_REG8(0x00e6), 0x00 }, + { CCI_REG8(0x00e7), 0x00 }, + { CCI_REG8(0x00e8), 0x00 }, + { CCI_REG8(0x00e9), 0x00 }, + { CCI_REG8(0x3f50), 0x00 }, + { CCI_REG8(0x3f56), 0x01 }, + { CCI_REG8(0x3f57), 0x30 }, + { CCI_REG8(0x3606), 0x01 }, + { CCI_REG8(0x3607), 0x01 }, + { CCI_REG8(0x3f26), 0x00 }, + { CCI_REG8(0x3f4a), 0x00 }, + { CCI_REG8(0x3f4b), 0x00 }, + { CCI_REG8(0x4bc0), 0x16 }, + { CCI_REG8(0x7ba8), 0x00 }, + { CCI_REG8(0x7ba9), 0x00 }, + { CCI_REG8(0x886b), 0x00 }, + { CCI_REG8(0x579a), 0x00 }, + { CCI_REG8(0x579b), 0x0a }, + { CCI_REG8(0x579c), 0x01 }, + { CCI_REG8(0x579d), 0x2a }, + { CCI_REG8(0x57ac), 0x00 }, + { CCI_REG8(0x57ad), 0x00 }, + { CCI_REG8(0x57ae), 0x00 }, + { CCI_REG8(0x57af), 0x81 }, + { CCI_REG8(0x57be), 0x00 }, + { CCI_REG8(0x57bf), 0x00 }, + { CCI_REG8(0x57c0), 0x00 }, + { CCI_REG8(0x57c1), 0x81 }, + { CCI_REG8(0x57d0), 0x00 }, + { CCI_REG8(0x57d1), 0x00 }, + { CCI_REG8(0x57d2), 0x00 }, + { CCI_REG8(0x57d3), 0x81 }, + { CCI_REG8(0x5324), 0x00 }, + { CCI_REG8(0x5325), 0x31 }, + { CCI_REG8(0x5326), 0x00 }, + { CCI_REG8(0x5327), 0x60 }, + { CCI_REG8(0xbca7), 0x08 }, + { CCI_REG8(0x5fcc), 0x1e }, + { CCI_REG8(0x5fd7), 0x1e }, + { CCI_REG8(0x5fe2), 0x1e }, + { CCI_REG8(0x5fed), 0x1e }, + { CCI_REG8(0x5ff8), 0x1e }, + { CCI_REG8(0x6003), 0x1e }, + { CCI_REG8(0x5d0b), 0x02 }, + { CCI_REG8(0x6f6d), 0x01 }, + { CCI_REG8(0x61c9), 0x68 }, + { CCI_REG8(0x5352), 0x00 }, + { CCI_REG8(0x5353), 0x3f }, + { CCI_REG8(0x5356), 0x00 }, + { CCI_REG8(0x5357), 0x1c }, + { CCI_REG8(0x5358), 0x00 }, + { CCI_REG8(0x5359), 0x3d }, + { CCI_REG8(0x535c), 0x00 }, + { CCI_REG8(0x535d), 0xa6 }, + { CCI_REG8(0x6187), 0x1d }, + { CCI_REG8(0x6189), 0x1d }, + { CCI_REG8(0x618b), 0x1d }, + { CCI_REG8(0x618d), 0x23 }, + { CCI_REG8(0x618f), 0x23 }, + { CCI_REG8(0x5414), 0x01 }, + { CCI_REG8(0x5415), 0x12 }, + { CCI_REG8(0xbca8), 0x00 }, + { CCI_REG8(0x5fcf), 0x28 }, + { CCI_REG8(0x5fda), 0x2d }, + { CCI_REG8(0x5fe5), 0x2d }, + { CCI_REG8(0x5ff0), 0x2d }, + { CCI_REG8(0x5ffb), 0x2d }, + { CCI_REG8(0x6006), 0x2d }, + { CCI_REG8(0x616e), 0x04 }, + { CCI_REG8(0x616f), 0x04 }, + { CCI_REG8(0x6170), 0x04 }, + { CCI_REG8(0x6171), 0x06 }, + { CCI_REG8(0x6172), 0x06 }, + { CCI_REG8(0x6173), 0x0c }, + { CCI_REG8(0x6174), 0x0c }, + { CCI_REG8(0x6175), 0x0c }, + { CCI_REG8(0x6176), 0x00 }, + { CCI_REG8(0x6177), 0x10 }, + { CCI_REG8(0x6178), 0x00 }, + { CCI_REG8(0x6179), 0x1a }, + { CCI_REG8(0x617a), 0x00 }, + { CCI_REG8(0x617b), 0x1a }, + { CCI_REG8(0x617c), 0x00 }, + { CCI_REG8(0x617d), 0x27 }, + { CCI_REG8(0x617e), 0x00 }, + { CCI_REG8(0x617f), 0x27 }, + { CCI_REG8(0x6180), 0x00 }, + { CCI_REG8(0x6181), 0x44 }, + { CCI_REG8(0x6182), 0x00 }, + { CCI_REG8(0x6183), 0x44 }, + { CCI_REG8(0x6184), 0x00 }, + { CCI_REG8(0x6185), 0x44 }, + { CCI_REG8(0x5dfc), 0x0a }, + { CCI_REG8(0x5e00), 0x0a }, + { CCI_REG8(0x5e04), 0x0a }, + { CCI_REG8(0x5e08), 0x0a }, + { CCI_REG8(0x5dfd), 0x0a }, + { CCI_REG8(0x5e01), 0x0a }, + { CCI_REG8(0x5e05), 0x0a }, + { CCI_REG8(0x5e09), 0x0a }, + { CCI_REG8(0x5dfe), 0x0a }, + { CCI_REG8(0x5e02), 0x0a }, + { CCI_REG8(0x5e06), 0x0a }, + { CCI_REG8(0x5e0a), 0x0a }, + { CCI_REG8(0x5dff), 0x0a }, + { CCI_REG8(0x5e03), 0x0a }, + { CCI_REG8(0x5e07), 0x0a }, + { CCI_REG8(0x5e0b), 0x0a }, + { CCI_REG8(0x5dec), 0x12 }, + { CCI_REG8(0x5df0), 0x12 }, + { CCI_REG8(0x5df4), 0x21 }, + { CCI_REG8(0x5df8), 0x31 }, + { CCI_REG8(0x5ded), 0x12 }, + { CCI_REG8(0x5df1), 0x12 }, + { CCI_REG8(0x5df5), 0x21 }, + { CCI_REG8(0x5df9), 0x31 }, + { CCI_REG8(0x5dee), 0x12 }, + { CCI_REG8(0x5df2), 0x12 }, + { CCI_REG8(0x5df6), 0x21 }, + { CCI_REG8(0x5dfa), 0x31 }, + { CCI_REG8(0x5def), 0x12 }, + { CCI_REG8(0x5df3), 0x12 }, + { CCI_REG8(0x5df7), 0x21 }, + { CCI_REG8(0x5dfb), 0x31 }, + { CCI_REG8(0x5ddc), 0x0d }, + { CCI_REG8(0x5de0), 0x0d }, + { CCI_REG8(0x5de4), 0x0d }, + { CCI_REG8(0x5de8), 0x0d }, + { CCI_REG8(0x5ddd), 0x0d }, + { CCI_REG8(0x5de1), 0x0d }, + { CCI_REG8(0x5de5), 0x0d }, + { CCI_REG8(0x5de9), 0x0d }, + { CCI_REG8(0x5dde), 0x0d }, + { CCI_REG8(0x5de2), 0x0d }, + { CCI_REG8(0x5de6), 0x0d }, + { CCI_REG8(0x5dea), 0x0d }, + { CCI_REG8(0x5ddf), 0x0d }, + { CCI_REG8(0x5de3), 0x0d }, + { CCI_REG8(0x5de7), 0x0d }, + { CCI_REG8(0x5deb), 0x0d }, + { CCI_REG8(0x5dcc), 0x55 }, + { CCI_REG8(0x5dd0), 0x50 }, + { CCI_REG8(0x5dd4), 0x4b }, + { CCI_REG8(0x5dd8), 0x4b }, + { CCI_REG8(0x5dcd), 0x55 }, + { CCI_REG8(0x5dd1), 0x50 }, + { CCI_REG8(0x5dd5), 0x4b }, + { CCI_REG8(0x5dd9), 0x4b }, + { CCI_REG8(0x5dce), 0x55 }, + { CCI_REG8(0x5dd2), 0x50 }, + { CCI_REG8(0x5dd6), 0x4b }, + { CCI_REG8(0x5dda), 0x4b }, + { CCI_REG8(0x5dcf), 0x55 }, + { CCI_REG8(0x5dd3), 0x50 }, + { CCI_REG8(0x5dd7), 0x4b }, + { CCI_REG8(0x5ddb), 0x4b }, + { CCI_REG8(0x0202), 0x0b }, + { CCI_REG8(0x0203), 0x86 }, + { CCI_REG8(0x0204), 0x00 }, + { CCI_REG8(0x0205), 0x00 }, + { CCI_REG8(0x020e), 0x01 }, + { CCI_REG8(0x020f), 0x00 }, + { CCI_REG8(0x0210), 0x01 }, + { CCI_REG8(0x0211), 0x00 }, + { CCI_REG8(0x0212), 0x01 }, + { CCI_REG8(0x0213), 0x00 }, + { CCI_REG8(0x0214), 0x01 }, + { CCI_REG8(0x0215), 0x00 }, +}; + +static const struct cci_reg_sequence metadata_output[] = { + { CCI_REG8(0x3050), 1 }, /* MIPI Output enabled */ + { CCI_REG8(0x3051), 1 }, /* MIPI output frame includes pixels data */ + { CCI_REG8(0x3052), 1 }, /* MIPI output frame includes meta data */ + { IMX500_REG_DD_CH06_VCID, 0 }, + { IMX500_REG_DD_CH07_VCID, 0 }, + { IMX500_REG_DD_CH08_VCID, 0 }, + { IMX500_REG_DD_CH09_VCID, 0 }, + { IMX500_REG_DD_CH06_DT, + 0x12 }, /* KPI - User Defined 8-bit Data Type 1 */ + { IMX500_REG_DD_CH07_DT, 0x12 }, /* Input Tensor - U.D. 8-bit type 2 */ + { IMX500_REG_DD_CH08_DT, 0x12 }, /* Output Tensor - U.D. 8-bit type 3 */ + { IMX500_REG_DD_CH09_DT, 0x12 }, /* PQ - U.D. 8-bit type 4 */ + { IMX500_REG_DD_CH06_PACKING, IMX500_DD_PACKING_8BPP }, + { IMX500_REG_DD_CH07_PACKING, IMX500_DD_PACKING_8BPP }, + { IMX500_REG_DD_CH08_PACKING, IMX500_DD_PACKING_8BPP }, + { IMX500_REG_DD_CH09_PACKING, IMX500_DD_PACKING_8BPP }, +}; + +static const struct cci_reg_sequence dnn_regs[] = { + { CCI_REG8(0xd960), 0x52 }, + { CCI_REG8(0xd961), 0x52 }, + { CCI_REG8(0xd962), 0x52 }, + { CCI_REG8(0xd963), 0x52 }, + { CCI_REG8(0xd96c), 0x44 }, + { CCI_REG8(0xd96d), 0x44 }, + { CCI_REG8(0xd96e), 0x44 }, + { CCI_REG8(0xd96f), 0x44 }, + { CCI_REG8(0xd600), 0x20 }, + /* Black level */ + { CCI_REG16(0xd80c), 0x100 }, + { CCI_REG16(0xd80e), 0x100 }, + { CCI_REG16(0xd810), 0x100 }, + { CCI_REG16(0xd812), 0x100 }, + /* Gamma */ + { CCI_REG8(0xd814), 1 }, + { CCI_REG32(0xd850), 0x10000 }, + { CCI_REG32(0xd854), 0x40002 }, + { CCI_REG32(0xd858), 0x60005 }, + { CCI_REG32(0xd85c), 0x90008 }, + { CCI_REG32(0xd860), 0xc000a }, + { CCI_REG32(0xd864), 0x12000f }, + { CCI_REG32(0xd868), 0x1c0014 }, + { CCI_REG32(0xd86c), 0x2a0024 }, + { CCI_REG32(0xd870), 0x360030 }, + { CCI_REG32(0xd874), 0x46003c }, + { CCI_REG32(0xd878), 0x5a0051 }, + { CCI_REG32(0xd87c), 0x750064 }, + { CCI_REG32(0xd880), 0x920084 }, + { CCI_REG32(0xd884), 0xa9009e }, + { CCI_REG32(0xd888), 0xba00b2 }, + { CCI_REG32(0xd88c), 0xc700c1 }, + { CCI_REG32(0xd890), 0xd100cd }, + { CCI_REG32(0xd894), 0xde00d6 }, + { CCI_REG32(0xd898), 0xe900e4 }, + { CCI_REG32(0xd89c), 0xf300ee }, + { CCI_REG32(0xd8a0), 0xfb00f7 }, + { CCI_REG16(0xd8a4), 0xff }, + { CCI_REG32(0xd8a8), 0x10000 }, + { CCI_REG32(0xd8ac), 0x40002 }, + { CCI_REG32(0xd8b0), 0x60005 }, + { CCI_REG32(0xd8b4), 0x90008 }, + { CCI_REG32(0xd8b8), 0xc000a }, + { CCI_REG32(0xd8bc), 0x12000f }, + { CCI_REG32(0xd8c0), 0x1c0014 }, + { CCI_REG32(0xd8c4), 0x2a0024 }, + { CCI_REG32(0xd8c8), 0x360030 }, + { CCI_REG32(0xd8cc), 0x46003c }, + { CCI_REG32(0xd8d0), 0x5a0051 }, + { CCI_REG32(0xd8d4), 0x750064 }, + { CCI_REG32(0xd8d8), 0x920084 }, + { CCI_REG32(0xd8dc), 0xa9009e }, + { CCI_REG32(0xd8e0), 0xba00b2 }, + { CCI_REG32(0xd8e4), 0xc700c1 }, + { CCI_REG32(0xd8e8), 0xd100cd }, + { CCI_REG32(0xd8ec), 0xde00d6 }, + { CCI_REG32(0xd8f0), 0xe900e4 }, + { CCI_REG32(0xd8f4), 0xf300ee }, + { CCI_REG32(0xd8f8), 0xfb00f7 }, + { CCI_REG16(0xd8fc), 0xff }, + { CCI_REG32(0xd900), 0x10000 }, + { CCI_REG32(0xd904), 0x40002 }, + { CCI_REG32(0xd908), 0x60005 }, + { CCI_REG32(0xd90c), 0x90008 }, + { CCI_REG32(0xd910), 0xc000a }, + { CCI_REG32(0xd914), 0x12000f }, + { CCI_REG32(0xd918), 0x1c0014 }, + { CCI_REG32(0xd91c), 0x2a0024 }, + { CCI_REG32(0xd920), 0x360030 }, + { CCI_REG32(0xd924), 0x46003c }, + { CCI_REG32(0xd928), 0x5a0051 }, + { CCI_REG32(0xd92c), 0x750064 }, + { CCI_REG32(0xd930), 0x920084 }, + { CCI_REG32(0xd934), 0xa9009e }, + { CCI_REG32(0xd938), 0xba00b2 }, + { CCI_REG32(0xd93c), 0xc700c1 }, + { CCI_REG32(0xd940), 0xd100cd }, + { CCI_REG32(0xd944), 0xde00d6 }, + { CCI_REG32(0xd948), 0xe900e4 }, + { CCI_REG32(0xd94c), 0xf300ee }, + { CCI_REG32(0xd950), 0xfb00f7 }, + { CCI_REG16(0xd954), 0xff }, + { CCI_REG8(0xd826), 1 }, + /* LSC */ + { CCI_REG32(0xe000), 0x2e502a0 }, + { CCI_REG32(0xe004), 0x2c80283 }, + { CCI_REG32(0xe008), 0x2700233 }, + { CCI_REG32(0xe00c), 0x22d01f6 }, + { CCI_REG32(0xe010), 0x1f401c3 }, + { CCI_REG32(0xe014), 0x1c5019c }, + { CCI_REG32(0xe018), 0x1bb0192 }, + { CCI_REG32(0xe01c), 0x1ba0192 }, + { CCI_REG32(0xe020), 0x1b90192 }, + { CCI_REG32(0xe024), 0x1ba0192 }, + { CCI_REG32(0xe028), 0x1ca019f }, + { CCI_REG32(0xe02c), 0x1fb01c8 }, + { CCI_REG32(0xe030), 0x23601fb }, + { CCI_REG32(0xe034), 0x27a0239 }, + { CCI_REG32(0xe038), 0x2d5028a }, + { CCI_REG32(0xe03c), 0x2f302a8 }, + { CCI_REG32(0xe040), 0x2c60283 }, + { CCI_REG32(0xe044), 0x27c0240 }, + { CCI_REG32(0xe048), 0x22d01f6 }, + { CCI_REG32(0xe04c), 0x1fd01cd }, + { CCI_REG32(0xe050), 0x1c4019c }, + { CCI_REG32(0xe054), 0x19c017b }, + { CCI_REG32(0xe058), 0x1810165 }, + { CCI_REG32(0xe05c), 0x175015c }, + { CCI_REG32(0xe060), 0x175015c }, + { CCI_REG32(0xe064), 0x1840167 }, + { CCI_REG32(0xe068), 0x1a0017e }, + { CCI_REG32(0xe06c), 0x1cc01a1 }, + { CCI_REG32(0xe070), 0x20501d1 }, + { CCI_REG32(0xe074), 0x23601fc }, + { CCI_REG32(0xe078), 0x2890246 }, + { CCI_REG32(0xe07c), 0x2d3028a }, + { CCI_REG32(0xe080), 0x2800243 }, + { CCI_REG32(0xe084), 0x245020e }, + { CCI_REG32(0xe088), 0x1ff01ce }, + { CCI_REG32(0xe08c), 0x1c4019c }, + { CCI_REG32(0xe090), 0x19a017b }, + { CCI_REG32(0xe094), 0x1650150 }, + { CCI_REG32(0xe098), 0x14a013a }, + { CCI_REG32(0xe09c), 0x13f0131 }, + { CCI_REG32(0xe0a0), 0x1400131 }, + { CCI_REG32(0xe0a4), 0x14d013c }, + { CCI_REG32(0xe0a8), 0x16a0154 }, + { CCI_REG32(0xe0ac), 0x1a1017e }, + { CCI_REG32(0xe0b0), 0x1cc01a1 }, + { CCI_REG32(0xe0b4), 0x20801d3 }, + { CCI_REG32(0xe0b8), 0x2510214 }, + { CCI_REG32(0xe0bc), 0x28b0249 }, + { CCI_REG32(0xe0c0), 0x2640229 }, + { CCI_REG32(0xe0c4), 0x22101ed }, + { CCI_REG32(0xe0c8), 0x1dc01b0 }, + { CCI_REG32(0xe0cc), 0x19c017c }, + { CCI_REG32(0xe0d0), 0x1650150 }, + { CCI_REG32(0xe0d4), 0x148013a }, + { CCI_REG32(0xe0d8), 0x123011c }, + { CCI_REG32(0xe0dc), 0x1190113 }, + { CCI_REG32(0xe0e0), 0x1190113 }, + { CCI_REG32(0xe0e4), 0x1280120 }, + { CCI_REG32(0xe0e8), 0x14c013c }, + { CCI_REG32(0xe0ec), 0x16b0154 }, + { CCI_REG32(0xe0f0), 0x1a30181 }, + { CCI_REG32(0xe0f4), 0x1e601b6 }, + { CCI_REG32(0xe0f8), 0x22c01f3 }, + { CCI_REG32(0xe0fc), 0x2700230 }, + { CCI_REG32(0xe100), 0x257021d }, + { CCI_REG32(0xe104), 0x20901d8 }, + { CCI_REG32(0xe108), 0x1c4019d }, + { CCI_REG32(0xe10c), 0x1820167 }, + { CCI_REG32(0xe110), 0x14b013b }, + { CCI_REG32(0xe114), 0x124011c }, + { CCI_REG32(0xe118), 0x1170113 }, + { CCI_REG32(0xe11c), 0x1010101 }, + { CCI_REG32(0xe120), 0x1030102 }, + { CCI_REG32(0xe124), 0x1190113 }, + { CCI_REG32(0xe128), 0x1280120 }, + { CCI_REG32(0xe12c), 0x14f013f }, + { CCI_REG32(0xe130), 0x189016c }, + { CCI_REG32(0xe134), 0x1ce01a3 }, + { CCI_REG32(0xe138), 0x21601df }, + { CCI_REG32(0xe13c), 0x2630224 }, + { CCI_REG32(0xe140), 0x257021d }, + { CCI_REG32(0xe144), 0x20101d0 }, + { CCI_REG32(0xe148), 0x1ba0194 }, + { CCI_REG32(0xe14c), 0x176015d }, + { CCI_REG32(0xe150), 0x13e0132 }, + { CCI_REG32(0xe154), 0x1190114 }, + { CCI_REG32(0xe158), 0x1010101 }, + { CCI_REG32(0xe15c), 0x1000100 }, + { CCI_REG32(0xe160), 0x1010100 }, + { CCI_REG32(0xe164), 0x1040103 }, + { CCI_REG32(0xe168), 0x11d0118 }, + { CCI_REG32(0xe16c), 0x1450136 }, + { CCI_REG32(0xe170), 0x17d0163 }, + { CCI_REG32(0xe174), 0x1c4019a }, + { CCI_REG32(0xe178), 0x20d01d6 }, + { CCI_REG32(0xe17c), 0x2630224 }, + { CCI_REG32(0xe180), 0x257021d }, + { CCI_REG32(0xe184), 0x20001d0 }, + { CCI_REG32(0xe188), 0x1b90194 }, + { CCI_REG32(0xe18c), 0x175015d }, + { CCI_REG32(0xe190), 0x13e0132 }, + { CCI_REG32(0xe194), 0x1180114 }, + { CCI_REG32(0xe198), 0x1040103 }, + { CCI_REG32(0xe19c), 0x1000100 }, + { CCI_REG32(0xe1a0), 0x1030102 }, + { CCI_REG32(0xe1a4), 0x1050103 }, + { CCI_REG32(0xe1a8), 0x11d0118 }, + { CCI_REG32(0xe1ac), 0x1450136 }, + { CCI_REG32(0xe1b0), 0x17d0163 }, + { CCI_REG32(0xe1b4), 0x1c4019a }, + { CCI_REG32(0xe1b8), 0x20d01d6 }, + { CCI_REG32(0xe1bc), 0x2640224 }, + { CCI_REG32(0xe1c0), 0x258021f }, + { CCI_REG32(0xe1c4), 0x20e01db }, + { CCI_REG32(0xe1c8), 0x1c7019f }, + { CCI_REG32(0xe1cc), 0x1840169 }, + { CCI_REG32(0xe1d0), 0x14d013e }, + { CCI_REG32(0xe1d4), 0x1290120 }, + { CCI_REG32(0xe1d8), 0x1180114 }, + { CCI_REG32(0xe1dc), 0x1050103 }, + { CCI_REG32(0xe1e0), 0x1050103 }, + { CCI_REG32(0xe1e4), 0x11e0117 }, + { CCI_REG32(0xe1e8), 0x12c0123 }, + { CCI_REG32(0xe1ec), 0x1530142 }, + { CCI_REG32(0xe1f0), 0x18d016f }, + { CCI_REG32(0xe1f4), 0x1d201a6 }, + { CCI_REG32(0xe1f8), 0x21a01e2 }, + { CCI_REG32(0xe1fc), 0x2640225 }, + { CCI_REG32(0xe200), 0x269022d }, + { CCI_REG32(0xe204), 0x22601f1 }, + { CCI_REG32(0xe208), 0x1e101b4 }, + { CCI_REG32(0xe20c), 0x1a10181 }, + { CCI_REG32(0xe210), 0x16c0156 }, + { CCI_REG32(0xe214), 0x14d013e }, + { CCI_REG32(0xe218), 0x1290120 }, + { CCI_REG32(0xe21c), 0x11f0118 }, + { CCI_REG32(0xe220), 0x11f0118 }, + { CCI_REG32(0xe224), 0x12b0123 }, + { CCI_REG32(0xe228), 0x1530142 }, + { CCI_REG32(0xe22c), 0x172015a }, + { CCI_REG32(0xe230), 0x1aa0187 }, + { CCI_REG32(0xe234), 0x1ec01bb }, + { CCI_REG32(0xe238), 0x23301f8 }, + { CCI_REG32(0xe23c), 0x2750233 }, + { CCI_REG32(0xe240), 0x28b024c }, + { CCI_REG32(0xe244), 0x24f0216 }, + { CCI_REG32(0xe248), 0x20701d4 }, + { CCI_REG32(0xe24c), 0x1ce01a4 }, + { CCI_REG32(0xe250), 0x1a10181 }, + { CCI_REG32(0xe254), 0x16c0156 }, + { CCI_REG32(0xe258), 0x1520141 }, + { CCI_REG32(0xe25c), 0x1480138 }, + { CCI_REG32(0xe260), 0x1480138 }, + { CCI_REG32(0xe264), 0x1550143 }, + { CCI_REG32(0xe268), 0x172015a }, + { CCI_REG32(0xe26c), 0x1aa0187 }, + { CCI_REG32(0xe270), 0x1d701a9 }, + { CCI_REG32(0xe274), 0x21201db }, + { CCI_REG32(0xe278), 0x25d021d }, + { CCI_REG32(0xe27c), 0x2990254 }, + { CCI_REG32(0xe280), 0x2d70291 }, + { CCI_REG32(0xe284), 0x28c024c }, + { CCI_REG32(0xe288), 0x2390201 }, + { CCI_REG32(0xe28c), 0x20701d4 }, + { CCI_REG32(0xe290), 0x1ce01a4 }, + { CCI_REG32(0xe294), 0x1a70184 }, + { CCI_REG32(0xe298), 0x18c016e }, + { CCI_REG32(0xe29c), 0x1810164 }, + { CCI_REG32(0xe2a0), 0x1810164 }, + { CCI_REG32(0xe2a4), 0x1900170 }, + { CCI_REG32(0xe2a8), 0x1ad0188 }, + { CCI_REG32(0xe2ac), 0x1d601a9 }, + { CCI_REG32(0xe2b0), 0x21201da }, + { CCI_REG32(0xe2b4), 0x2450207 }, + { CCI_REG32(0xe2b8), 0x29a0254 }, + { CCI_REG32(0xe2bc), 0x2ea029d }, + { CCI_REG32(0xe2c0), 0x2f602ae }, + { CCI_REG32(0xe2c4), 0x2d80291 }, + { CCI_REG32(0xe2c8), 0x280023f }, + { CCI_REG32(0xe2cc), 0x2390200 }, + { CCI_REG32(0xe2d0), 0x1fe01cc }, + { CCI_REG32(0xe2d4), 0x1d201a4 }, + { CCI_REG32(0xe2d8), 0x1c6019b }, + { CCI_REG32(0xe2dc), 0x1c6019b }, + { CCI_REG32(0xe2e0), 0x1c6019b }, + { CCI_REG32(0xe2e4), 0x1c8019b }, + { CCI_REG32(0xe2e8), 0x1d701a9 }, + { CCI_REG32(0xe2ec), 0x20801d1 }, + { CCI_REG32(0xe2f0), 0x2450206 }, + { CCI_REG32(0xe2f4), 0x28e0248 }, + { CCI_REG32(0xe2f8), 0x2ec029d }, + { CCI_REG32(0xe2fc), 0x30902b9 }, + { CCI_REG32(0xe300), 0x2a002a4 }, + { CCI_REG32(0xe304), 0x2830286 }, + { CCI_REG32(0xe308), 0x2330234 }, + { CCI_REG32(0xe30c), 0x1f601f7 }, + { CCI_REG32(0xe310), 0x1c301c4 }, + { CCI_REG32(0xe314), 0x19c019c }, + { CCI_REG32(0xe318), 0x1920193 }, + { CCI_REG32(0xe31c), 0x1920193 }, + { CCI_REG32(0xe320), 0x1920192 }, + { CCI_REG32(0xe324), 0x1920193 }, + { CCI_REG32(0xe328), 0x19f01a1 }, + { CCI_REG32(0xe32c), 0x1c801ca }, + { CCI_REG32(0xe330), 0x1fb01fe }, + { CCI_REG32(0xe334), 0x239023e }, + { CCI_REG32(0xe338), 0x28a0292 }, + { CCI_REG32(0xe33c), 0x2a802b0 }, + { CCI_REG32(0xe340), 0x2830287 }, + { CCI_REG32(0xe344), 0x2400242 }, + { CCI_REG32(0xe348), 0x1f601f8 }, + { CCI_REG32(0xe34c), 0x1cd01ce }, + { CCI_REG32(0xe350), 0x19c019d }, + { CCI_REG32(0xe354), 0x17b017d }, + { CCI_REG32(0xe358), 0x1650166 }, + { CCI_REG32(0xe35c), 0x15c015d }, + { CCI_REG32(0xe360), 0x15c015d }, + { CCI_REG32(0xe364), 0x1670168 }, + { CCI_REG32(0xe368), 0x17e0180 }, + { CCI_REG32(0xe36c), 0x1a101a3 }, + { CCI_REG32(0xe370), 0x1d101d3 }, + { CCI_REG32(0xe374), 0x1fc0200 }, + { CCI_REG32(0xe378), 0x246024c }, + { CCI_REG32(0xe37c), 0x28a0291 }, + { CCI_REG32(0xe380), 0x2430245 }, + { CCI_REG32(0xe384), 0x20e0211 }, + { CCI_REG32(0xe388), 0x1ce01d0 }, + { CCI_REG32(0xe38c), 0x19c019e }, + { CCI_REG32(0xe390), 0x17b017c }, + { CCI_REG32(0xe394), 0x1500152 }, + { CCI_REG32(0xe398), 0x13a013c }, + { CCI_REG32(0xe39c), 0x1310134 }, + { CCI_REG32(0xe3a0), 0x1310134 }, + { CCI_REG32(0xe3a4), 0x13c013f }, + { CCI_REG32(0xe3a8), 0x1540156 }, + { CCI_REG32(0xe3ac), 0x17e0180 }, + { CCI_REG32(0xe3b0), 0x1a101a4 }, + { CCI_REG32(0xe3b4), 0x1d301d8 }, + { CCI_REG32(0xe3b8), 0x2140219 }, + { CCI_REG32(0xe3bc), 0x249024e }, + { CCI_REG32(0xe3c0), 0x229022b }, + { CCI_REG32(0xe3c4), 0x1ed01ef }, + { CCI_REG32(0xe3c8), 0x1b001b2 }, + { CCI_REG32(0xe3cc), 0x17c017e }, + { CCI_REG32(0xe3d0), 0x1500151 }, + { CCI_REG32(0xe3d4), 0x13a013c }, + { CCI_REG32(0xe3d8), 0x11c011f }, + { CCI_REG32(0xe3dc), 0x1130117 }, + { CCI_REG32(0xe3e0), 0x1130117 }, + { CCI_REG32(0xe3e4), 0x1200123 }, + { CCI_REG32(0xe3e8), 0x13c013f }, + { CCI_REG32(0xe3ec), 0x1540156 }, + { CCI_REG32(0xe3f0), 0x1810183 }, + { CCI_REG32(0xe3f4), 0x1b601ba }, + { CCI_REG32(0xe3f8), 0x1f301f6 }, + { CCI_REG32(0xe3fc), 0x2300234 }, + { CCI_REG32(0xe400), 0x21d0221 }, + { CCI_REG32(0xe404), 0x1d801db }, + { CCI_REG32(0xe408), 0x19d019f }, + { CCI_REG32(0xe40c), 0x1670169 }, + { CCI_REG32(0xe410), 0x13b013d }, + { CCI_REG32(0xe414), 0x11c011f }, + { CCI_REG32(0xe418), 0x1130117 }, + { CCI_REG32(0xe41c), 0x1010106 }, + { CCI_REG32(0xe420), 0x1020108 }, + { CCI_REG32(0xe424), 0x1130117 }, + { CCI_REG32(0xe428), 0x1200123 }, + { CCI_REG32(0xe42c), 0x13f0142 }, + { CCI_REG32(0xe430), 0x16c016f }, + { CCI_REG32(0xe434), 0x1a301a6 }, + { CCI_REG32(0xe438), 0x1df01e2 }, + { CCI_REG32(0xe43c), 0x2240228 }, + { CCI_REG32(0xe440), 0x21d0220 }, + { CCI_REG32(0xe444), 0x1d001d3 }, + { CCI_REG32(0xe448), 0x1940196 }, + { CCI_REG32(0xe44c), 0x15d0160 }, + { CCI_REG32(0xe450), 0x1320135 }, + { CCI_REG32(0xe454), 0x1140118 }, + { CCI_REG32(0xe458), 0x1010106 }, + { CCI_REG32(0xe45c), 0x1000106 }, + { CCI_REG32(0xe460), 0x1000106 }, + { CCI_REG32(0xe464), 0x1030109 }, + { CCI_REG32(0xe468), 0x118011b }, + { CCI_REG32(0xe46c), 0x136013a }, + { CCI_REG32(0xe470), 0x1630165 }, + { CCI_REG32(0xe474), 0x19a019c }, + { CCI_REG32(0xe478), 0x1d601d9 }, + { CCI_REG32(0xe47c), 0x2240227 }, + { CCI_REG32(0xe480), 0x21d0220 }, + { CCI_REG32(0xe484), 0x1d001d3 }, + { CCI_REG32(0xe488), 0x1940196 }, + { CCI_REG32(0xe48c), 0x15d0160 }, + { CCI_REG32(0xe490), 0x1320135 }, + { CCI_REG32(0xe494), 0x1140118 }, + { CCI_REG32(0xe498), 0x1030109 }, + { CCI_REG32(0xe49c), 0x1000106 }, + { CCI_REG32(0xe4a0), 0x1020108 }, + { CCI_REG32(0xe4a4), 0x1030109 }, + { CCI_REG32(0xe4a8), 0x118011b }, + { CCI_REG32(0xe4ac), 0x1360139 }, + { CCI_REG32(0xe4b0), 0x1630165 }, + { CCI_REG32(0xe4b4), 0x19a019c }, + { CCI_REG32(0xe4b8), 0x1d601d9 }, + { CCI_REG32(0xe4bc), 0x2240227 }, + { CCI_REG32(0xe4c0), 0x21f0221 }, + { CCI_REG32(0xe4c4), 0x1db01de }, + { CCI_REG32(0xe4c8), 0x19f01a2 }, + { CCI_REG32(0xe4cc), 0x169016c }, + { CCI_REG32(0xe4d0), 0x13e0141 }, + { CCI_REG32(0xe4d4), 0x1200124 }, + { CCI_REG32(0xe4d8), 0x1140119 }, + { CCI_REG32(0xe4dc), 0x1030109 }, + { CCI_REG32(0xe4e0), 0x1030109 }, + { CCI_REG32(0xe4e4), 0x117011c }, + { CCI_REG32(0xe4e8), 0x1230126 }, + { CCI_REG32(0xe4ec), 0x1420145 }, + { CCI_REG32(0xe4f0), 0x16f0171 }, + { CCI_REG32(0xe4f4), 0x1a601a8 }, + { CCI_REG32(0xe4f8), 0x1e201e4 }, + { CCI_REG32(0xe4fc), 0x2250227 }, + { CCI_REG32(0xe500), 0x22d0231 }, + { CCI_REG32(0xe504), 0x1f101f4 }, + { CCI_REG32(0xe508), 0x1b401b7 }, + { CCI_REG32(0xe50c), 0x1810183 }, + { CCI_REG32(0xe510), 0x1560159 }, + { CCI_REG32(0xe514), 0x13e0141 }, + { CCI_REG32(0xe518), 0x1200124 }, + { CCI_REG32(0xe51c), 0x118011c }, + { CCI_REG32(0xe520), 0x118011c }, + { CCI_REG32(0xe524), 0x1230126 }, + { CCI_REG32(0xe528), 0x1420145 }, + { CCI_REG32(0xe52c), 0x15a015c }, + { CCI_REG32(0xe530), 0x1870188 }, + { CCI_REG32(0xe534), 0x1bb01bd }, + { CCI_REG32(0xe538), 0x1f801fb }, + { CCI_REG32(0xe53c), 0x2330236 }, + { CCI_REG32(0xe540), 0x24c0250 }, + { CCI_REG32(0xe544), 0x2160219 }, + { CCI_REG32(0xe548), 0x1d401d7 }, + { CCI_REG32(0xe54c), 0x1a401a6 }, + { CCI_REG32(0xe550), 0x1810183 }, + { CCI_REG32(0xe554), 0x1560158 }, + { CCI_REG32(0xe558), 0x1410144 }, + { CCI_REG32(0xe55c), 0x138013b }, + { CCI_REG32(0xe560), 0x138013b }, + { CCI_REG32(0xe564), 0x1430146 }, + { CCI_REG32(0xe568), 0x15a015c }, + { CCI_REG32(0xe56c), 0x1870188 }, + { CCI_REG32(0xe570), 0x1a901ab }, + { CCI_REG32(0xe574), 0x1db01dd }, + { CCI_REG32(0xe578), 0x21d0221 }, + { CCI_REG32(0xe57c), 0x2540259 }, + { CCI_REG32(0xe580), 0x2910296 }, + { CCI_REG32(0xe584), 0x24c0251 }, + { CCI_REG32(0xe588), 0x2010204 }, + { CCI_REG32(0xe58c), 0x1d401d6 }, + { CCI_REG32(0xe590), 0x1a401a5 }, + { CCI_REG32(0xe594), 0x1840186 }, + { CCI_REG32(0xe598), 0x16e0170 }, + { CCI_REG32(0xe59c), 0x1640167 }, + { CCI_REG32(0xe5a0), 0x1640167 }, + { CCI_REG32(0xe5a4), 0x1700173 }, + { CCI_REG32(0xe5a8), 0x188018a }, + { CCI_REG32(0xe5ac), 0x1a901ab }, + { CCI_REG32(0xe5b0), 0x1da01dd }, + { CCI_REG32(0xe5b4), 0x207020a }, + { CCI_REG32(0xe5b8), 0x2540259 }, + { CCI_REG32(0xe5bc), 0x29d02a3 }, + { CCI_REG32(0xe5c0), 0x2ae02b4 }, + { CCI_REG32(0xe5c4), 0x2910297 }, + { CCI_REG32(0xe5c8), 0x23f0243 }, + { CCI_REG32(0xe5cc), 0x2000201 }, + { CCI_REG32(0xe5d0), 0x1cc01cd }, + { CCI_REG32(0xe5d4), 0x1a401a6 }, + { CCI_REG32(0xe5d8), 0x19b019d }, + { CCI_REG32(0xe5dc), 0x19b019d }, + { CCI_REG32(0xe5e0), 0x19b019d }, + { CCI_REG32(0xe5e4), 0x19b019e }, + { CCI_REG32(0xe5e8), 0x1a901ab }, + { CCI_REG32(0xe5ec), 0x1d101d3 }, + { CCI_REG32(0xe5f0), 0x2060209 }, + { CCI_REG32(0xe5f4), 0x248024b }, + { CCI_REG32(0xe5f8), 0x29d02a3 }, + { CCI_REG32(0xe5fc), 0x2b902c0 }, + { CCI_REG8(0xd822), 0x01 }, + { CCI_REG8(0xd823), 0x0f }, +}; + +/* Mode configs */ +static const struct imx500_mode imx500_supported_modes[] = { + { + /* 12MPix 10fps mode */ + .width = 4056, + .height = 3040, + .line_length_pix = 17900, + .crop = { + .left = IMX500_PIXEL_ARRAY_LEFT, + .top = IMX500_PIXEL_ARRAY_TOP, + .width = 4056, + .height = 3040, + }, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_4056x3040_regs), + .regs = mode_4056x3040_regs, + }, + }, + { + /* 2x2 binned 40fps mode */ + .width = 2028, + .height = 1520, + .line_length_pix = 9398, + .crop = { + .left = IMX500_PIXEL_ARRAY_LEFT, + .top = IMX500_PIXEL_ARRAY_TOP, + .width = 4056, + .height = 3040, + }, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_2028x1520_regs), + .regs = mode_2028x1520_regs, + }, + }, +}; + +/* + * The supported formats. + * This table MUST contain 4 entries per format, to cover the various flip + * combinations in the order + * - no flip + * - h flip + * - v flip + * - h&v flips + */ +static const u32 codes[] = { + /* 10-bit modes. */ + MEDIA_BUS_FMT_SRGGB10_1X10, + MEDIA_BUS_FMT_SGRBG10_1X10, + MEDIA_BUS_FMT_SGBRG10_1X10, + MEDIA_BUS_FMT_SBGGR10_1X10, +}; + +enum imx500_state { + IMX500_STATE_RESET = 0, + IMX500_STATE_PROGRAM_EMPTY, + IMX500_STATE_WITHOUT_NETWORK, + IMX500_STATE_WITH_NETWORK, +}; + +struct imx500 { + struct dentry *debugfs; + struct v4l2_subdev sd; + struct media_pad pad[NUM_PADS]; + struct regmap *regmap; + + unsigned int fmt_code; + + struct clk *xclk; + u32 xclk_freq; + + struct gpio_desc *led_gpio; + struct gpio_desc *reset_gpio; + struct regulator_bulk_data supplies[IMX500_NUM_SUPPLIES]; + + struct v4l2_ctrl_handler ctrl_handler; + /* V4L2 Controls */ + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *vflip; + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *network_fw_ctrl; + + struct v4l2_rect inference_window; + + /* Current mode */ + const struct imx500_mode *mode; + + /* + * Mutex for serialized access: + * Protect sensor module set pad format and start/stop streaming safely. + */ + struct mutex mutex; + + /* Streaming on/off */ + bool streaming; + + /* Rewrite common registers on stream on? */ + bool common_regs_written; + + bool loader_and_main_written; + bool network_written; + + /* Current long exposure factor in use. Set through V4L2_CID_VBLANK */ + unsigned int long_exp_shift; + + struct spi_device *spi_device; + + const struct firmware *fw_loader; + const struct firmware *fw_main; + const u8 *fw_network; + size_t fw_network_size; + size_t fw_progress; + unsigned int fw_stage; + + enum imx500_state fsm_state; + + u32 num_inference_lines; +}; + +static inline struct imx500 *to_imx500(struct v4l2_subdev *_sd) +{ + return container_of(_sd, struct imx500, sd); +} + +static bool validate_normalization_yuv(u16 reg, uint8_t size, + uint32_t value) +{ + /* Some regs are 9-bit, some 8-bit, some 1-bit */ + switch (reg) { + case 0xD62A: + case 0xD632: + case 0xD63A: + case 0xD644: + case 0xD648: + case 0xD64C: + case 0xD650: + case 0xD654: + case 0xD658: + return size == 2 && !(value & ~0x1FF); + case 0xD600: + case 0xD601: + case 0xD602: + return size == 1 && !(value & ~0xFF); + case 0xD629: + case 0xD630: + case 0xD638: + case 0xD643: + case 0xD647: + case 0xD64B: + case 0xD64F: + case 0xD653: + case 0xD657: + return size == 1 && !(value & ~0x01); + default: + return false; + } +} + +/* Common function as bayer rgb + normalization use the same repeating register + * layout + */ +static bool validate_bit_pattern(u8 offset, uint8_t size, uint32_t value) +{ + /* There are no odd register addresses */ + if (offset & 1) + return false; + + /* Valid register sizes/patterns repeat every 4 */ + offset = (offset >> 1) & 3; + + if (offset == 1) + return size == 1 && !(value & ~1); + else + return size == 2 && !(value & ~0x1FF); +} + +static bool validate_bayer_rgb_normalization(u16 reg, uint8_t size, + uint32_t value) +{ + if (reg < 0xD684 || reg >= 0xD6E4) + return false; + return validate_bit_pattern(reg - 0xD684, size, value); +} + +static bool validate_normalization_registers(u16 reg, uint8_t size, + uint32_t value) +{ + if (reg < 0xD708 || reg >= 0xD750) + return false; + return validate_bit_pattern(reg - 0xD708, size, value); +} + +static bool validate_image_format_selection(u16 reg, uint8_t size, + uint32_t value) +{ + if (size != 1 || value > 5) + return false; + if (reg < 0xD750 || reg > 0xd752) + return false; + return true; +} + +static bool validate_yc_conversion_factor(u16 reg, uint8_t size, + uint32_t value) +{ + static const u32 allowed[9] = { + 0x0FFF0FFF, 0x0FFF1FFF, 0x0FFF0FFF, 0x0FFF1FFF, 0x0FFF0FFF, + 0x0FFF1FFF, 0x01FF01FF, 0x01FF01FF, 0x01FF01FF, + }; + + if (size > 4 || size & 1 || reg & 1 || reg < 0x76C || reg > 0xD7FA) + return false; + + if (size == 2) { + if (reg & 2) + reg -= 2; + else + value <<= 16; + } + + /* High registers (clip values) are all 2x 9-bit */ + if (reg >= 0xD7D8) + return !(value & ~0x01FF01FF); + + /* Early registers follow a repeating pattern */ + reg -= 0xD76C; + reg >>= 2; + return !(value & ~allowed[reg % sizeof(allowed)]); +} + +static bool validate_dnn_output_setting(u16 reg, uint8_t size, + uint32_t value) +{ + /* Only Y_OUT_SIZE for Input Tensor / Output Tensor is configurable from + * userspace + */ + return (size == 2) && (value < 2046) && + ((reg == CCI_REG_ADDR(IMX500_REG_DD_CH07_Y_OUT_SIZE)) || + (reg == CCI_REG_ADDR(IMX500_REG_DD_CH08_Y_OUT_SIZE))); +} + +static bool __must_check +imx500_validate_inference_register(const struct cci_reg_sequence *reg) +{ + unsigned int i; + + static bool (*const checks[])(uint16_t, uint8_t, uint32_t) = { + validate_normalization_yuv, + validate_bayer_rgb_normalization, + validate_normalization_registers, + validate_image_format_selection, + validate_yc_conversion_factor, + validate_dnn_output_setting, + }; + + if (!reg) + return false; + + for (i = 0; i < ARRAY_SIZE(checks); i++) { + if (checks[i](CCI_REG_ADDR(reg->reg), + CCI_REG_WIDTH_BYTES(reg->reg), reg->val)) + return true; + } + + return false; +} + +static int imx500_set_inference_window(struct imx500 *imx500) +{ + u16 left, top, width, height; + + if (!imx500->inference_window.width || + !imx500->inference_window.height) { + width = 4056; + height = 3040; + left = 0; + top = 0; + } else { + width = min_t(u16, imx500->inference_window.width, 4056); + height = min_t(u16, imx500->inference_window.height, 3040); + left = min_t(u16, imx500->inference_window.left, 4056); + top = min_t(u16, imx500->inference_window.top, 3040); + } + + const struct cci_reg_sequence window_regs[] = { + { IMX500_REG_DWP_AP_VC_HOFF, left }, + { IMX500_REG_DWP_AP_VC_VOFF, top }, + { IMX500_REG_DWP_AP_VC_HSIZE, width }, + { IMX500_REG_DWP_AP_VC_VSIZE, height }, + }; + + return cci_multi_reg_write(imx500->regmap, window_regs, + ARRAY_SIZE(window_regs), NULL); +} + +static int imx500_reg_val_write_cbk(void *arg, + const struct cci_reg_sequence *reg) +{ + struct imx500 *imx500 = arg; + + if (!imx500_validate_inference_register(reg)) + return -EINVAL; + + return cci_write(imx500->regmap, reg->reg, reg->val, NULL); +} + +/* Get bayer order based on flip setting. */ +static u32 imx500_get_format_code(struct imx500 *imx500) +{ + unsigned int i; + + lockdep_assert_held(&imx500->mutex); + + i = (imx500->vflip->val ? 2 : 0) | (imx500->hflip->val ? 1 : 0); + + return codes[i]; +} + +static void imx500_set_default_format(struct imx500 *imx500) +{ + /* Set default mode to max resolution */ + imx500->mode = &imx500_supported_modes[0]; + imx500->fmt_code = MEDIA_BUS_FMT_SRGGB10_1X10; +} + +/* -1 on fail, block size on success */ +static int imx500_validate_fw_block(const char *data, size_t maxlen) +{ + const size_t header_size = 32; + static const char header_id[] = { '9', '4', '6', '4' }; + + const size_t footer_size = 64; + static const char footer_id[] = { '3', '6', '9', '5' }; + + u32 data_size; + + const char *end = data + maxlen; + + if (!data) + return -1; + + if (maxlen < header_size) + return -1; + + if (memcmp(data, &header_id, sizeof(header_id))) + return -1; + + /* data_size is size of header + body */ + memcpy(&data_size, data + sizeof(header_id), sizeof(data_size)); + data_size = ___constant_swab32(data_size); + + if (end - data_size - footer_size < data) + return -1; + if (memcmp(data + data_size + footer_size - sizeof(footer_id), + &footer_id, sizeof(footer_id))) + return -1; + + return data_size + footer_size; +} + +/* Parse fw block by block, returning total valid fw size */ +static size_t imx500_valid_fw_bytes(const u8 *fw, + const size_t fw_size) +{ + int i; + size_t bytes = 0; + + const u8 *data = fw; + size_t size = fw_size; + + while ((i = imx500_validate_fw_block(data, size)) > 0) { + bytes += i; + data += i; + size -= i; + } + + return bytes; +} + +static int imx500_iterate_nw_regs( + const u8 *fw, size_t fw_size, void *arg, + int (*cbk)(void *arg, const struct cci_reg_sequence *reg)) +{ + struct cpio_data cd = { NULL, 0, "" }; + const u8 *read_pos; + size_t entries; + size_t size; + + if (!fw || !cbk) + return -EINVAL; + + size = imx500_valid_fw_bytes(fw, fw_size); + cd = find_cpio_data("imx500_regs", (void *)(fw + size), + fw_size - size, NULL); + if (!cd.data || cd.size % 7) + return -EINVAL; + + read_pos = cd.data; + entries = cd.size / 7; + + while (entries--) { + struct cci_reg_sequence reg = { 0, 0 }; + u16 addr; + u8 len; + u32 val; + int ret; + + memcpy(&addr, read_pos, sizeof(addr)); + read_pos += sizeof(addr); + memcpy(&len, read_pos, sizeof(len)); + read_pos += sizeof(len); + memcpy(&val, read_pos, sizeof(val)); + read_pos += sizeof(val); + + reg.reg = ((len << CCI_REG_WIDTH_SHIFT) | addr); + reg.val = val; + + ret = cbk(arg, ®); + if (ret) + return ret; + } + return 0; +} + +static int imx500_reg_tensor_lines_cbk(void *arg, + const struct cci_reg_sequence *reg) +{ + u16 *tensor_lines = arg; + + if (reg->val < 2046) { + switch (reg->reg) { + case IMX500_REG_DD_CH07_Y_OUT_SIZE: + tensor_lines[0] = reg->val; + break; + case IMX500_REG_DD_CH08_Y_OUT_SIZE: + tensor_lines[1] = reg->val; + break; + } + } + + return 0; +} + +static void imx500_calc_inference_lines(struct imx500 *imx500) +{ + u16 tensor_lines[2] = { 0, 0 }; + + if (!imx500->fw_network) { + imx500->num_inference_lines = 0; + return; + } + + imx500_iterate_nw_regs(imx500->fw_network, imx500->fw_network_size, + tensor_lines, imx500_reg_tensor_lines_cbk); + + /* Full-res mode, embedded lines are actually slightly shorter than inference + * lines 2544 vs 2560 (over-allocate with inf. width) + */ + imx500->num_inference_lines = IMX500_NUM_KPI_LINES + + IMX500_NUM_PQ_LINES + tensor_lines[0] + + tensor_lines[1]; +} + +static void imx500_adjust_exposure_range(struct imx500 *imx500) +{ + int exposure_max, exposure_def; + + /* Honour the VBLANK limits when setting exposure. */ + exposure_max = imx500->mode->height + imx500->vblank->val - + IMX500_EXPOSURE_OFFSET; + exposure_def = min(exposure_max, imx500->exposure->val); + __v4l2_ctrl_modify_range(imx500->exposure, imx500->exposure->minimum, + exposure_max, imx500->exposure->step, + exposure_def); +} + +static int imx500_set_frame_length(struct imx500 *imx500, unsigned int val) +{ + int ret = 0; + + imx500->long_exp_shift = 0; + + while (val > IMX500_FRAME_LENGTH_MAX) { + imx500->long_exp_shift++; + val >>= 1; + } + + ret = cci_write(imx500->regmap, IMX500_REG_FRAME_LENGTH, val, NULL); + if (ret) + return ret; + + ret = cci_write(imx500->regmap, IMX500_LONG_EXP_CIT_SHIFT_REG, + imx500->long_exp_shift, NULL); + if (ret) + return ret; + + return cci_write(imx500->regmap, IMX500_LONG_EXP_SHIFT_REG, + imx500->long_exp_shift, NULL); +} + +/* reg is both input and output: + * reg->val is the value we're polling until we're NEQ to + * It is then populated with the updated value. + */ +static int __must_check imx500_poll_status_reg(struct imx500 *state, + struct cci_reg_sequence *reg, + u8 timeout) +{ + u64 read_value; + int ret; + + while (timeout) { + ret = cci_read(state->regmap, reg->reg, &read_value, NULL); + if (ret) + return ret; + + if (read_value != reg->val) { + reg->val = read_value; + return 0; + } + + timeout--; + mdelay(50); + } + return -EAGAIN; +} + +static int imx500_prepare_poll_cmd_reply_sts(struct imx500 *imx500, + struct cci_reg_sequence *cmd_reply) +{ + /* Perform single-byte read of 4-byte IMX500_REG_DD_REF_STS register to + * target CMD_REPLY_STS_CNT sub-register + */ + cmd_reply->reg = CCI_REG8(CCI_REG_ADDR(IMX500_REG_DD_REF_STS)); + + return cci_read(imx500->regmap, cmd_reply->reg, &cmd_reply->val, NULL); +} + +static int imx500_clear_weights(struct imx500 *imx500) +{ + struct cci_reg_sequence cmd_reply_sts_cnt_reg; + u64 imx500_fsm_state; + u64 cmd_reply; + int ret; + + static const struct cci_reg_sequence request_clear[] = { + { IMX500_REG_DD_ST_TRANS_CMD, + IMX500_DD_ST_TRANS_CMD_CLEAR_WEIGHTS }, + { IMX500_REG_DD_CMD_INT, IMX500_DD_CMD_INT_ST_TRANS }, + }; + + if (imx500->fsm_state != IMX500_STATE_WITH_NETWORK) + return -EINVAL; + + ret = cci_read(imx500->regmap, IMX500_REG_DD_SYS_STATE, + &imx500_fsm_state, NULL); + if (ret || imx500_fsm_state != IMX500_DD_SYS_STATE_STANDBY_WITH_NETWORK) + return ret ? ret : -EREMOTEIO; + + ret = imx500_prepare_poll_cmd_reply_sts(imx500, &cmd_reply_sts_cnt_reg); + if (ret) + return ret; + + ret = cci_multi_reg_write(imx500->regmap, request_clear, + ARRAY_SIZE(request_clear), NULL); + if (ret) + return ret; + + ret = imx500_poll_status_reg(imx500, &cmd_reply_sts_cnt_reg, 5); + if (ret) + return ret; + + ret = cci_read(imx500->regmap, IMX500_REG_DD_CMD_REPLY_STS, &cmd_reply, + NULL); + if (ret || cmd_reply != IMX500_DD_CMD_REPLY_STS_TRANS_DONE) + return ret ? ret : -EREMOTEIO; + + imx500->fsm_state = IMX500_STATE_WITHOUT_NETWORK; + imx500->network_written = false; + return 0; +} + +static void imx500_clear_fw_network(struct imx500 *imx500) +{ + /* Remove any previous firmware blob. */ + if (imx500->fw_network) + vfree(imx500->fw_network); + + imx500->fw_network = NULL; + imx500->network_written = false; + imx500->fw_progress = 0; +} + +static int imx500_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct imx500 *imx500 = + container_of(ctrl->handler, struct imx500, ctrl_handler); + struct i2c_client *client = v4l2_get_subdevdata(&imx500->sd); + int ret = 0; + + if (ctrl->id == V4L2_CID_USER_IMX500_NETWORK_FW_FD) { + /* Reset state of the control. */ + if (ctrl->val < 0) { + return 0; + } else if (ctrl->val == S32_MAX) { + ctrl->val = -1; + if (pm_runtime_get_if_in_use(&client->dev) == 0) + return 0; + + if (imx500->network_written) + ret = imx500_clear_weights(imx500); + imx500_clear_fw_network(imx500); + + pm_runtime_mark_last_busy(&client->dev); + pm_runtime_put_autosuspend(&client->dev); + + return ret; + } + + imx500_clear_fw_network(imx500); + ret = kernel_read_file_from_fd(ctrl->val, 0, + (void **)&imx500->fw_network, INT_MAX, + &imx500->fw_network_size, + 1); + /* + * Back to reset state, the FD cannot be considered valid after + * this IOCTL completes. + */ + ctrl->val = -1; + + if (ret < 0) { + dev_err(&client->dev, "%s failed to read fw image: %d\n", + __func__, ret); + imx500_clear_fw_network(imx500); + return ret; + } + if (ret != imx500->fw_network_size) { + dev_err(&client->dev, "%s read fw image size mismatich: got %u, expected %zu\n", + __func__, ret, imx500->fw_network_size); + imx500_clear_fw_network(imx500); + return -EIO; + } + + imx500_calc_inference_lines(imx500); + return 0; + } + + /* + * The VBLANK control may change the limits of usable exposure, so check + * and adjust if necessary. + */ + if (ctrl->id == V4L2_CID_VBLANK) + imx500_adjust_exposure_range(imx500); + + /* + * Applying V4L2 control value only happens + * when power is up for streaming + */ + if (pm_runtime_get_if_in_use(&client->dev) == 0) + return 0; + + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + ret = cci_write(imx500->regmap, IMX500_REG_ANALOG_GAIN, + ctrl->val, NULL); + break; + case V4L2_CID_EXPOSURE: + ret = cci_write(imx500->regmap, IMX500_REG_EXPOSURE, + ctrl->val >> imx500->long_exp_shift, NULL); + break; + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: + ret = cci_write(imx500->regmap, IMX500_REG_ORIENTATION, + imx500->hflip->val | imx500->vflip->val << 1, + NULL); + break; + case V4L2_CID_VBLANK: + ret = imx500_set_frame_length(imx500, + imx500->mode->height + ctrl->val); + break; + case V4L2_CID_HBLANK: + ret = cci_write(imx500->regmap, IMX500_REG_LINE_LENGTH, + imx500->mode->width + ctrl->val, NULL); + break; + case V4L2_CID_NOTIFY_GAINS: + ret = cci_write(imx500->regmap, IMX500_REG_COLOUR_BALANCE_B, + ctrl->p_new.p_u32[0], NULL); + cci_write(imx500->regmap, IMX500_REG_COLOUR_BALANCE_GB, + ctrl->p_new.p_u32[1], &ret); + cci_write(imx500->regmap, IMX500_REG_COLOUR_BALANCE_GR, + ctrl->p_new.p_u32[2], &ret); + cci_write(imx500->regmap, IMX500_REG_COLOUR_BALANCE_R, + ctrl->p_new.p_u32[3], &ret); + break; + case V4L2_CID_USER_IMX500_INFERENCE_WINDOW: + memcpy(&imx500->inference_window, ctrl->p_new.p_u32, + sizeof(struct v4l2_rect)); + ret = imx500_set_inference_window(imx500); + break; + default: + dev_info(&client->dev, + "ctrl(id:0x%x,val:0x%x) is not handled\n", ctrl->id, + ctrl->val); + ret = -EINVAL; + break; + } + + pm_runtime_mark_last_busy(&client->dev); + pm_runtime_put_autosuspend(&client->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops imx500_ctrl_ops = { + .s_ctrl = imx500_set_ctrl, +}; + +static const struct v4l2_ctrl_config imx500_notify_gains_ctrl = { + .ops = &imx500_ctrl_ops, + .id = V4L2_CID_NOTIFY_GAINS, + .type = V4L2_CTRL_TYPE_U32, + .min = IMX500_COLOUR_BALANCE_MIN, + .max = IMX500_COLOUR_BALANCE_MAX, + .step = IMX500_COLOUR_BALANCE_STEP, + .def = IMX500_COLOUR_BALANCE_DEFAULT, + .dims = { 4 }, + .elem_size = sizeof(u32), +}; + +static int imx500_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct imx500 *imx500 = to_imx500(sd); + + if (code->pad >= NUM_PADS) + return -EINVAL; + + if (code->pad == IMAGE_PAD) { + if (code->index != 0) + return -EINVAL; + + code->code = imx500_get_format_code(imx500); + } else { + if (code->index > 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_SENSOR_DATA; + } + + return 0; +} + +static int imx500_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct imx500 *imx500 = to_imx500(sd); + + if (fse->pad >= NUM_PADS) + return -EINVAL; + + if (fse->pad == IMAGE_PAD) { + const struct imx500_mode *mode_list = imx500_supported_modes; + unsigned int num_modes = ARRAY_SIZE(imx500_supported_modes); + + if (fse->index >= num_modes) + return -EINVAL; + + if (fse->code != imx500_get_format_code(imx500)) + return -EINVAL; + + fse->min_width = mode_list[fse->index].width; + fse->max_width = fse->min_width; + fse->min_height = mode_list[fse->index].height; + fse->max_height = fse->min_height; + } else { + if (fse->code != MEDIA_BUS_FMT_SENSOR_DATA || fse->index > 0) + return -EINVAL; + + fse->min_width = IMX500_MAX_EMBEDDED_SIZE + + imx500->num_inference_lines * + IMX500_INFERENCE_LINE_WIDTH; + fse->max_width = fse->min_width; + fse->min_height = 1; + fse->max_height = fse->min_height; + } + + return 0; +} + +static void imx500_update_image_pad_format(struct imx500 *imx500, + const struct imx500_mode *mode, + struct v4l2_subdev_format *fmt) +{ + fmt->format.width = mode->width; + fmt->format.height = mode->height; + fmt->format.field = V4L2_FIELD_NONE; + fmt->format.colorspace = V4L2_COLORSPACE_RAW; + fmt->format.ycbcr_enc = + V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->format.colorspace); + fmt->format.quantization = V4L2_MAP_QUANTIZATION_DEFAULT( + true, fmt->format.colorspace, fmt->format.ycbcr_enc); + fmt->format.xfer_func = + V4L2_MAP_XFER_FUNC_DEFAULT(fmt->format.colorspace); +} + +static void imx500_update_metadata_pad_format(const struct imx500 *imx500, + struct v4l2_subdev_format *fmt) +{ + fmt->format.width = + IMX500_MAX_EMBEDDED_SIZE + + imx500->num_inference_lines * IMX500_INFERENCE_LINE_WIDTH; + fmt->format.height = 1; + fmt->format.code = MEDIA_BUS_FMT_SENSOR_DATA; + fmt->format.field = V4L2_FIELD_NONE; +} + +static int imx500_get_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct imx500 *imx500 = to_imx500(sd); + + if (fmt->pad >= NUM_PADS) + return -EINVAL; + + mutex_lock(&imx500->mutex); + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + struct v4l2_mbus_framefmt *try_fmt = v4l2_subdev_state_get_format( + sd_state, fmt->pad); + /* update the code which could change due to vflip or hflip */ + try_fmt->code = fmt->pad == IMAGE_PAD ? + imx500_get_format_code(imx500) : + MEDIA_BUS_FMT_SENSOR_DATA; + fmt->format = *try_fmt; + } else { + if (fmt->pad == IMAGE_PAD) { + imx500_update_image_pad_format(imx500, imx500->mode, + fmt); + fmt->format.code = imx500_get_format_code(imx500); + } else { + imx500_update_metadata_pad_format(imx500, fmt); + } + } + + mutex_unlock(&imx500->mutex); + return 0; +} + +static void imx500_set_framing_limits(struct imx500 *imx500) +{ + unsigned int hblank_min; + const struct imx500_mode *mode = imx500->mode; + + /* Default to no long exposure multiplier. */ + imx500->long_exp_shift = 0; + + /* Update limits and set FPS to default */ + __v4l2_ctrl_modify_range( + imx500->vblank, IMX500_VBLANK_MIN, + ((1 << IMX500_LONG_EXP_SHIFT_MAX) * IMX500_FRAME_LENGTH_MAX) - + mode->height, 1, IMX500_VBLANK_MIN); + + /* Setting this will adjust the exposure limits as well. */ + __v4l2_ctrl_s_ctrl(imx500->vblank, IMX500_VBLANK_MIN); + + hblank_min = mode->line_length_pix - mode->width; + __v4l2_ctrl_modify_range(imx500->hblank, hblank_min, hblank_min, 1, + hblank_min); + __v4l2_ctrl_s_ctrl(imx500->hblank, hblank_min); +} + +static int imx500_set_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct v4l2_mbus_framefmt *framefmt; + const struct imx500_mode *mode; + struct imx500 *imx500 = to_imx500(sd); + + if (fmt->pad >= NUM_PADS) + return -EINVAL; + + mutex_lock(&imx500->mutex); + + if (fmt->pad == IMAGE_PAD) { + const struct imx500_mode *mode_list = imx500_supported_modes; + unsigned int num_modes = ARRAY_SIZE(imx500_supported_modes); + + /* Bayer order varies with flips */ + fmt->format.code = imx500_get_format_code(imx500); + + mode = v4l2_find_nearest_size(mode_list, num_modes, width, + height, fmt->format.width, + fmt->format.height); + imx500_update_image_pad_format(imx500, mode, fmt); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + framefmt = v4l2_subdev_state_get_format(sd_state, fmt->pad); + *framefmt = fmt->format; + } else if (imx500->mode != mode) { + imx500->mode = mode; + imx500->fmt_code = fmt->format.code; + imx500_set_framing_limits(imx500); + } + } else { + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + framefmt = v4l2_subdev_state_get_format(sd_state, fmt->pad); + *framefmt = fmt->format; + } else { + /* Only one embedded data mode is supported */ + imx500_update_metadata_pad_format(imx500, fmt); + } + } + + mutex_unlock(&imx500->mutex); + + return 0; +} + +static const struct v4l2_rect * +__imx500_get_pad_crop(struct imx500 *imx500, struct v4l2_subdev_state *sd_state, + unsigned int pad, enum v4l2_subdev_format_whence which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_state_get_crop(sd_state, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &imx500->mode->crop; + } + + return NULL; +} + +static int imx500_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + switch (sel->target) { + case V4L2_SEL_TGT_CROP: { + struct imx500 *imx500 = to_imx500(sd); + + mutex_lock(&imx500->mutex); + sel->r = *__imx500_get_pad_crop(imx500, sd_state, sel->pad, + sel->which); + mutex_unlock(&imx500->mutex); + + return 0; + } + + case V4L2_SEL_TGT_NATIVE_SIZE: + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = IMX500_NATIVE_WIDTH; + sel->r.height = IMX500_NATIVE_HEIGHT; + + return 0; + + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.left = IMX500_PIXEL_ARRAY_LEFT; + sel->r.top = IMX500_PIXEL_ARRAY_TOP; + sel->r.width = IMX500_PIXEL_ARRAY_WIDTH; + sel->r.height = IMX500_PIXEL_ARRAY_HEIGHT; + + return 0; + } + + return -EINVAL; +} + +static int __must_check imx500_spi_write(struct imx500 *state, const u8 *data, + size_t size) +{ + if (size % 4 || size > ONE_MIB) + return -EINVAL; + + if (!state->spi_device) + return -ENODEV; + + return spi_write(state->spi_device, data, size); +} + +/* Moves the IMX500 internal state machine between states or updates. + * + * Prerequisites: Sensor is powered on and not currently streaming + */ +static int imx500_state_transition(struct imx500 *imx500, const u8 *fw, + size_t fw_size, enum imx500_image_type type, + bool update) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx500->sd); + struct cci_reg_sequence cmd_reply_sts_cnt_reg; + size_t valid_size; + int ret; + u64 tmp; + + if (!imx500 || !fw || type >= TYPE_MAX) + return -EINVAL; + + if (!update && (int)type != (int)imx500->fsm_state) + return -EINVAL; + + /* Validate firmware */ + valid_size = imx500_valid_fw_bytes(fw, fw_size); + if (!valid_size) + return -EINVAL; + + ret = imx500_prepare_poll_cmd_reply_sts(imx500, &cmd_reply_sts_cnt_reg); + if (ret) + return ret; + + struct cci_reg_sequence common_regs[] = { + { IMX500_REG_DD_FLASH_TYPE, 0x02 }, + { IMX500_REG_DD_LOAD_MODE, IMX500_DD_LOAD_MODE_AP }, + { IMX500_REG_DD_IMAGE_TYPE, type }, + { IMX500_REG_DD_DOWNLOAD_DIV_NUM, (valid_size - 1) / ONE_MIB }, + { IMX500_REG_DD_DOWNLOAD_FILE_SIZE, valid_size }, + }; + + struct cci_reg_sequence state_transition_regs[] = { + { IMX500_REG_DD_ST_TRANS_CMD, type }, + { IMX500_REG_DD_CMD_INT, IMX500_DD_CMD_INT_ST_TRANS }, + }; + + struct cci_reg_sequence update_regs[] = { + { IMX500_REG_DD_UPDATE_CMD, IMX500_DD_UPDATE_CMD_SRAM }, + { IMX500_REG_DD_CMD_INT, IMX500_DD_CMD_INT_UPDATE }, + }; + + ret = cci_multi_reg_write(imx500->regmap, common_regs, + ARRAY_SIZE(common_regs), NULL); + + cci_multi_reg_write(imx500->regmap, + update ? update_regs : state_transition_regs, 2, + &ret); + if (ret) + return ret; + + /* Poll CMD_REPLY_STS_CNT until a response is available */ + ret = imx500_poll_status_reg(imx500, &cmd_reply_sts_cnt_reg, 5); + if (ret) { + dev_err(&client->dev, "DD_REF_STS register did not update\n"); + return ret; + } + + /* Read response to state transition / update request */ + ret = cci_read(imx500->regmap, IMX500_REG_DD_CMD_REPLY_STS, &tmp, NULL); + if (ret || tmp != (update ? IMX500_DD_CMD_REPLY_STS_UPDATE_READY : + IMX500_DD_CMD_REPLY_STS_TRANS_READY)) + return ret ? ret : -EBUSY; + + imx500->fw_stage = type; + imx500->fw_progress = 0; + + for (size_t i = 0; i <= valid_size / ONE_MIB; i++) { + const u8 *data = fw + (i * ONE_MIB); + size_t size = valid_size - (i * ONE_MIB); + struct cci_reg_sequence download_sts_reg = { + IMX500_REG_DD_DOWNLOAD_STS, + IMX500_DD_DOWNLOAD_STS_DOWNLOADING, + }; + + /* Calculate SPI xfer size avoiding 0-sized TXNs */ + size = min_t(size_t, size, ONE_MIB); + if (!size) + break; + + /* Poll until device is ready for download */ + ret = imx500_poll_status_reg(imx500, &download_sts_reg, 100); + if (ret) { + dev_err(&client->dev, + "DD_DOWNLOAD_STS was never ready\n"); + return ret; + } + + /* Do SPI transfer */ + gpiod_set_value_cansleep(imx500->led_gpio, 1); + ret = imx500_spi_write(imx500, data, size); + gpiod_set_value_cansleep(imx500->led_gpio, 0); + + imx500->fw_progress += size; + + if (ret < 0) + return ret; + } + + /* Poll until another response is available */ + ret = imx500_poll_status_reg(imx500, &cmd_reply_sts_cnt_reg, 5); + if (ret) { + dev_err(&client->dev, + "DD_REF_STS register did not update after SPI write(s)\n"); + return ret; + } + + /* Verify that state transition / update completed successfully */ + ret = cci_read(imx500->regmap, IMX500_REG_DD_CMD_REPLY_STS, &tmp, NULL); + if (ret || tmp != (update ? IMX500_DD_CMD_REPLY_STS_UPDATE_DONE : + IMX500_DD_CMD_REPLY_STS_TRANS_DONE)) + return ret ? ret : -EREMOTEIO; + + if (!update && imx500->fsm_state < IMX500_STATE_WITH_NETWORK) + imx500->fsm_state++; + + imx500->fw_progress = fw_size; + + return 0; +} + +static int imx500_transition_to_standby_wo_network(struct imx500 *imx500) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx500->sd); + const struct firmware *firmware; + u64 fw_ver; + int ret; + + firmware = imx500->fw_loader; + ret = imx500_state_transition(imx500, firmware->data, firmware->size, + TYPE_LOADER, false); + if (ret) { + dev_err(&client->dev, "%s: failed to load loader firmware\n", + __func__); + return ret; + } + + firmware = imx500->fw_main; + ret = imx500_state_transition(imx500, firmware->data, firmware->size, + TYPE_MAIN, false); + if (ret) { + dev_err(&client->dev, "%s: failed to load main firmware\n", + __func__); + return ret; + } + + ret = cci_read(imx500->regmap, IMX500_REG_MAIN_FW_VERSION, &fw_ver, + NULL); + if (ret) { + dev_err(&client->dev, + "%s: could not read main firmware version\n", __func__); + return ret; + } + + dev_info(&client->dev, + "main firmware version: %llu%llu.%llu%llu.%llu%llu\n", + (fw_ver >> 20) & 0xF, (fw_ver >> 16) & 0xF, + (fw_ver >> 12) & 0xF, (fw_ver >> 8) & 0xF, (fw_ver >> 4) & 0xF, + fw_ver & 0xF); + + ret = cci_multi_reg_write(imx500->regmap, metadata_output, + ARRAY_SIZE(metadata_output), NULL); + if (ret) { + dev_err(&client->dev, + "%s: failed to configure MIPI output for DNN\n", + __func__); + return ret; + } + + ret = cci_multi_reg_write(imx500->regmap, dnn_regs, + ARRAY_SIZE(dnn_regs), NULL); + if (ret) { + dev_err(&client->dev, "%s: unable to write DNN regs\n", + __func__); + return ret; + } + + return 0; +} + +static int imx500_transition_to_network(struct imx500 *imx500) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx500->sd); + u64 imx500_fsm_state; + int ret; + + ret = imx500_iterate_nw_regs(imx500->fw_network, + imx500->fw_network_size, imx500, + imx500_reg_val_write_cbk); + if (ret) { + dev_err(&client->dev, + "%s: unable to apply register writes from firmware\n", + __func__); + return ret; + } + + /* Read IMX500 state to determine whether transition or update is required */ + ret = cci_read(imx500->regmap, IMX500_REG_DD_SYS_STATE, + &imx500_fsm_state, NULL); + if (ret || imx500_fsm_state & 1) + return ret ? ret : -EREMOTEIO; + + ret = imx500_state_transition( + imx500, imx500->fw_network, imx500->fw_network_size, + TYPE_NW_WEIGHTS, + imx500_fsm_state == IMX500_DD_SYS_STATE_STANDBY_WITH_NETWORK); + if (ret) { + dev_err(&client->dev, "%s: failed to load network weights\n", + __func__); + return ret; + } + + /* Select network 0 */ + ret = cci_write(imx500->regmap, CCI_REG8(0xD701), 0, NULL); + if (ret) { + dev_err(&client->dev, "%s: failed to select network 0\n", + __func__); + return ret; + } + + return ret; +} + +/* Start streaming */ +static int imx500_start_streaming(struct imx500 *imx500) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx500->sd); + const struct imx500_reg_list *reg_list; + int ret; + + ret = pm_runtime_resume_and_get(&client->dev); + if (ret < 0) + return ret; + + ret = cci_write(imx500->regmap, IMX500_REG_IMAGE_ONLY_MODE, + imx500->fw_network ? IMX500_IMAGE_ONLY_FALSE : + IMX500_IMAGE_ONLY_TRUE, + NULL); + if (ret) { + dev_err(&client->dev, "%s failed to set image mode\n", + __func__); + return ret; + } + + /* Acquire loader and main firmware if needed */ + if (imx500->fw_network) { + if (!imx500->fw_loader) { + ret = request_firmware(&imx500->fw_loader, + "imx500_loader.fpk", + &client->dev); + if (ret) { + dev_err(&client->dev, + "Unable to acquire firmware loader\n"); + return ret; + } + } + if (!imx500->fw_main) { + ret = request_firmware(&imx500->fw_main, + "imx500_firmware.fpk", + &client->dev); + if (ret) { + dev_err(&client->dev, + "Unable to acquire main firmware\n"); + return ret; + } + } + } + + if (!imx500->common_regs_written) { + ret = cci_multi_reg_write(imx500->regmap, mode_common_regs, + ARRAY_SIZE(mode_common_regs), NULL); + + if (ret) { + dev_err(&client->dev, + "%s failed to set common settings\n", __func__); + return ret; + } + + imx500->common_regs_written = true; + } + + if (imx500->fw_network && !imx500->loader_and_main_written) { + ret = imx500_transition_to_standby_wo_network(imx500); + if (ret) { + dev_err(&client->dev, + "%s failed to transition from program empty state\n", + __func__); + return ret; + } + imx500->loader_and_main_written = true; + } + + if (imx500->fw_network && !imx500->network_written) { + ret = imx500_transition_to_network(imx500); + if (ret) { + dev_err(&client->dev, + "%s failed to transition to network loaded\n", + __func__); + return ret; + } + imx500->network_written = true; + } + + /* Enable DNN */ + if (imx500->fw_network) { + ret = cci_write(imx500->regmap, CCI_REG8(0xD100), 4, NULL); + if (ret) { + dev_err(&client->dev, "%s failed to enable DNN\n", + __func__); + return ret; + } + } + + /* Apply default values of current mode */ + reg_list = &imx500->mode->reg_list; + ret = cci_multi_reg_write(imx500->regmap, reg_list->regs, + reg_list->num_of_regs, NULL); + if (ret) { + dev_err(&client->dev, "%s failed to set mode\n", __func__); + return ret; + } + + /* Apply customized values from user */ + ret = __v4l2_ctrl_handler_setup(imx500->sd.ctrl_handler); + + /* Disable any sensor startup frame drops. This must be written here! */ + cci_write(imx500->regmap, CCI_REG8(0xD405), 0, &ret); + + /* set stream on register */ + cci_write(imx500->regmap, IMX500_REG_MODE_SELECT, IMX500_MODE_STREAMING, + &ret); + + return ret; +} + +/* Stop streaming */ +static void imx500_stop_streaming(struct imx500 *imx500) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx500->sd); + int ret; + + /* set stream off register */ + ret = cci_write(imx500->regmap, IMX500_REG_MODE_SELECT, + IMX500_MODE_STANDBY, NULL); + if (ret) + dev_err(&client->dev, "%s failed to set stream\n", __func__); + + /* Disable DNN */ + ret = cci_write(imx500->regmap, CCI_REG8(0xD100), 0, NULL); + if (ret) + dev_err(&client->dev, "%s failed to disable DNN\n", __func__); + + pm_runtime_mark_last_busy(&client->dev); + pm_runtime_put_autosuspend(&client->dev); +} + +static int imx500_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct imx500 *imx500 = to_imx500(sd); + int ret = 0; + + mutex_lock(&imx500->mutex); + if (imx500->streaming == enable) { + mutex_unlock(&imx500->mutex); + return 0; + } + + if (enable) { + /* + * Apply default & customized values + * and then start streaming. + */ + ret = imx500_start_streaming(imx500); + if (ret) + goto err_start_streaming; + } else { + imx500_stop_streaming(imx500); + } + + imx500->streaming = enable; + + /* vflip and hflip cannot change during streaming */ + __v4l2_ctrl_grab(imx500->vflip, enable); + __v4l2_ctrl_grab(imx500->hflip, enable); + __v4l2_ctrl_grab(imx500->network_fw_ctrl, enable); + + mutex_unlock(&imx500->mutex); + + return ret; + +err_start_streaming: + mutex_unlock(&imx500->mutex); + + return ret; +} + +/* Power/clock management functions */ +static int imx500_power_on(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx500 *imx500 = to_imx500(sd); + int ret; + + ret = regulator_bulk_enable(IMX500_NUM_SUPPLIES, imx500->supplies); + if (ret) { + dev_err(&client->dev, "%s: failed to enable regulators\n", + __func__); + return ret; + } + + /* T4 - 1us + * Ambiguous: Regulators rising to INCK start is specified by the datasheet + * but also "Presence of INCK during Power off is acceptable" + */ + udelay(2); + + ret = clk_prepare_enable(imx500->xclk); + if (ret) { + dev_err(&client->dev, "%s: failed to enable clock\n", __func__); + goto reg_off; + } + + /* T5 - 0ms + * Ambiguous: Regulators rising to XCLR rising is specified by the datasheet + * as 0ms but also "XCLR pin should be set to 'High' after INCK supplied.". + * T4 and T5 are shown as overlapping. + */ + gpiod_set_value_cansleep(imx500->reset_gpio, 1); + + /* T7 - 9ms + * "INCK start and CXLR rising till Send Streaming Command wait time" + */ + usleep_range(9000, 12000); + + return 0; + +reg_off: + regulator_bulk_disable(IMX500_NUM_SUPPLIES, imx500->supplies); + return ret; +} + +static int imx500_power_off(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx500 *imx500 = to_imx500(sd); + + /* Datasheet specifies power down sequence as INCK disable, XCLR low, + * regulator disable. T1 (XCLR neg-edge to regulator disable) is specified + * as 0us. + * + * Note, this is not the reverse order of power up. + */ + clk_disable_unprepare(imx500->xclk); + gpiod_set_value_cansleep(imx500->reset_gpio, 0); + regulator_bulk_disable(IMX500_NUM_SUPPLIES, imx500->supplies); + + /* Force reprogramming of the common registers when powered up again. */ + imx500->fsm_state = IMX500_STATE_RESET; + imx500->common_regs_written = false; + imx500->loader_and_main_written = false; + imx500_clear_fw_network(imx500); + + return 0; +} + +static int imx500_get_regulators(struct imx500 *imx500) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx500->sd); + unsigned int i; + + for (i = 0; i < IMX500_NUM_SUPPLIES; i++) + imx500->supplies[i].supply = imx500_supply_name[i]; + + return devm_regulator_bulk_get(&client->dev, IMX500_NUM_SUPPLIES, + imx500->supplies); +} + +/* Verify chip ID */ +static int imx500_identify_module(struct imx500 *imx500) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx500->sd); + int ret; + u64 val; + + ret = cci_read(imx500->regmap, IMX500_REG_CHIP_ID, &val, NULL); + if (ret) { + dev_err(&client->dev, + "failed to read chip id %x, with error %d\n", + IMX500_CHIP_ID, ret); + return ret; + } + + if (val != IMX500_CHIP_ID) { + dev_err(&client->dev, "chip id mismatch: %x!=%llx\n", + IMX500_CHIP_ID, val); + return -EIO; + } + + dev_info(&client->dev, "Device found is imx%llx\n", val); + + return 0; +} + +static const struct v4l2_subdev_core_ops imx500_core_ops = { + .subscribe_event = v4l2_ctrl_subdev_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_video_ops imx500_video_ops = { + .s_stream = imx500_set_stream, +}; + +static const struct v4l2_subdev_pad_ops imx500_pad_ops = { + .enum_mbus_code = imx500_enum_mbus_code, + .get_fmt = imx500_get_pad_format, + .set_fmt = imx500_set_pad_format, + .get_selection = imx500_get_selection, + .enum_frame_size = imx500_enum_frame_size, +}; + +static const struct v4l2_subdev_ops imx500_subdev_ops = { + .core = &imx500_core_ops, + .video = &imx500_video_ops, + .pad = &imx500_pad_ops, +}; + +static const s64 imx500_link_freq_menu[] = { + IMX500_DEFAULT_LINK_FREQ, +}; + +/* Custom control for inference window */ +static const struct v4l2_ctrl_config inf_window_ctrl = { + .name = "IMX500 Inference Windows", + .id = V4L2_CID_USER_IMX500_INFERENCE_WINDOW, + .dims[0] = 4, + .ops = &imx500_ctrl_ops, + .type = V4L2_CTRL_TYPE_U32, + .elem_size = sizeof(u32), + .flags = V4L2_CTRL_FLAG_EXECUTE_ON_WRITE | + V4L2_CTRL_FLAG_HAS_PAYLOAD, + .def = 0, + .min = 0x00, + .max = 4032, + .step = 1, +}; + +/* Custom control for network firmware file FD */ +static const struct v4l2_ctrl_config network_fw_fd = { + .name = "IMX500 Network Firmware File FD", + .id = V4L2_CID_USER_IMX500_NETWORK_FW_FD, + .ops = &imx500_ctrl_ops, + .type = V4L2_CTRL_TYPE_INTEGER, + .flags = V4L2_CTRL_FLAG_EXECUTE_ON_WRITE | + V4L2_CTRL_FLAG_WRITE_ONLY, + .min = -1, + .max = S32_MAX, + .step = 1, + .def = -1, +}; + +/* Initialize control handlers */ +static int imx500_init_controls(struct imx500 *imx500) +{ + struct v4l2_ctrl_handler *ctrl_hdlr; + struct i2c_client *client = v4l2_get_subdevdata(&imx500->sd); + struct v4l2_fwnode_device_properties props; + int ret; + + ctrl_hdlr = &imx500->ctrl_handler; + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 16); + if (ret) + return ret; + + mutex_init(&imx500->mutex); + ctrl_hdlr->lock = &imx500->mutex; + + /* By default, PIXEL_RATE is read only */ + imx500->pixel_rate = v4l2_ctrl_new_std( + ctrl_hdlr, &imx500_ctrl_ops, V4L2_CID_PIXEL_RATE, + IMX500_PIXEL_RATE, IMX500_PIXEL_RATE, 1, IMX500_PIXEL_RATE); + + /* LINK_FREQ is also read only */ + imx500->link_freq = + v4l2_ctrl_new_int_menu(ctrl_hdlr, &imx500_ctrl_ops, + V4L2_CID_LINK_FREQ, + ARRAY_SIZE(imx500_link_freq_menu) - 1, 0, + imx500_link_freq_menu); + if (imx500->link_freq) + imx500->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + /* + * Create the controls here, but mode specific limits are setup + * in the imx500_set_framing_limits() call below. + */ + imx500->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx500_ctrl_ops, + V4L2_CID_VBLANK, IMX500_VBLANK_MIN, + 0xffff, 1, IMX500_VBLANK_MIN); + imx500->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx500_ctrl_ops, + V4L2_CID_HBLANK, 0, 0xffff, 1, 0); + + imx500->exposure = v4l2_ctrl_new_std( + ctrl_hdlr, &imx500_ctrl_ops, V4L2_CID_EXPOSURE, + IMX500_EXPOSURE_MIN, IMX500_EXPOSURE_MAX, IMX500_EXPOSURE_STEP, + IMX500_EXPOSURE_DEFAULT); + + v4l2_ctrl_new_std(ctrl_hdlr, &imx500_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + IMX500_ANA_GAIN_MIN, IMX500_ANA_GAIN_MAX, + IMX500_ANA_GAIN_STEP, IMX500_ANA_GAIN_DEFAULT); + + imx500->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx500_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + if (imx500->hflip) + imx500->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + + imx500->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx500_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + if (imx500->vflip) + imx500->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + + v4l2_ctrl_new_custom(ctrl_hdlr, &imx500_notify_gains_ctrl, NULL); + v4l2_ctrl_new_custom(ctrl_hdlr, &inf_window_ctrl, NULL); + imx500->network_fw_ctrl = + v4l2_ctrl_new_custom(ctrl_hdlr, &network_fw_fd, NULL); + + if (ctrl_hdlr->error) { + ret = ctrl_hdlr->error; + dev_err(&client->dev, "%s control init failed (%d)\n", __func__, + ret); + goto error; + } + + ret = v4l2_fwnode_device_parse(&client->dev, &props); + if (ret) + goto error; + + ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &imx500_ctrl_ops, + &props); + if (ret) + goto error; + + imx500->sd.ctrl_handler = ctrl_hdlr; + + /* Setup exposure and frame/line length limits. */ + imx500_set_framing_limits(imx500); + + return 0; + +error: + v4l2_ctrl_handler_free(ctrl_hdlr); + mutex_destroy(&imx500->mutex); + + return ret; +} + +static void imx500_free_controls(struct imx500 *imx500) +{ + v4l2_ctrl_handler_free(imx500->sd.ctrl_handler); + mutex_destroy(&imx500->mutex); +} + +static int imx500_check_hwcfg(struct device *dev) +{ + struct fwnode_handle *endpoint; + struct v4l2_fwnode_endpoint ep_cfg = { .bus_type = + V4L2_MBUS_CSI2_DPHY }; + int ret = -EINVAL; + + endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL); + if (!endpoint) { + dev_err(dev, "endpoint node not found\n"); + return -EINVAL; + } + + if (v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep_cfg)) { + dev_err(dev, "could not parse endpoint\n"); + goto error_out; + } + + /* Check the number of MIPI CSI2 data lanes */ + if (ep_cfg.bus.mipi_csi2.num_data_lanes != 2) { + dev_err(dev, "only 2 data lanes are currently supported\n"); + goto error_out; + } + + /* Check the link frequency set in device tree */ + if (!ep_cfg.nr_of_link_frequencies) { + dev_err(dev, "link-frequency property not found in DT\n"); + goto error_out; + } + + if (ep_cfg.nr_of_link_frequencies != 1 || + ep_cfg.link_frequencies[0] != IMX500_DEFAULT_LINK_FREQ) { + dev_err(dev, "Link frequency not supported: %lld\n", + ep_cfg.link_frequencies[0]); + goto error_out; + } + + ret = 0; + +error_out: + v4l2_fwnode_endpoint_free(&ep_cfg); + fwnode_handle_put(endpoint); + + return ret; +} + +static int fw_progress_show(struct seq_file *s, void *data) +{ + struct imx500 *imx500 = s->private; + + seq_printf(s, "%d %zu %zu\n", imx500->fw_stage, imx500->fw_progress, + imx500->fw_network_size); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(fw_progress); + +static int imx500_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct spi_device *spi = NULL; + char debugfs_name[128]; + struct imx500 *imx500; + int ret; + + struct device_node *spi_node = of_parse_phandle(dev->of_node, "spi", 0); + + if (spi_node) { + struct device *tmp = + bus_find_device_by_of_node(&spi_bus_type, spi_node); + of_node_put(spi_node); + spi = tmp ? to_spi_device(tmp) : NULL; + if (!spi) + return -EPROBE_DEFER; + } + + imx500 = devm_kzalloc(&client->dev, sizeof(*imx500), GFP_KERNEL); + if (!imx500) + return -ENOMEM; + + imx500->regmap = devm_cci_regmap_init_i2c(client, 16); + if (IS_ERR(imx500->regmap)) + return dev_err_probe(dev, PTR_ERR(imx500->regmap), + "failed to initialise CCI\n"); + + imx500->spi_device = spi; + + v4l2_i2c_subdev_init(&imx500->sd, client, &imx500_subdev_ops); + + /* Check the hardware configuration in device tree */ + if (imx500_check_hwcfg(dev)) + return -EINVAL; + + /* Get system clock (xclk) */ + imx500->xclk = devm_clk_get(dev, NULL); + if (IS_ERR(imx500->xclk)) + return dev_err_probe(dev, PTR_ERR(imx500->xclk), + "failed to get xclk\n"); + + imx500->xclk_freq = clk_get_rate(imx500->xclk); + if (imx500->xclk_freq != IMX500_XCLK_FREQ) { + dev_err(dev, "xclk frequency not supported: %d Hz\n", + imx500->xclk_freq); + return -EINVAL; + } + + ret = imx500_get_regulators(imx500); + if (ret) { + dev_err(dev, "failed to get regulators\n"); + return ret; + } + + imx500->led_gpio = devm_gpiod_get_optional(dev, "led", GPIOD_OUT_LOW); + + imx500->reset_gpio = + devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + + /* + * The sensor must be powered for imx500_identify_module() + * to be able to read the CHIP_ID register + */ + ret = imx500_power_on(dev); + if (ret) + return ret; + + pm_runtime_set_active(dev); + pm_runtime_get_noresume(dev); + pm_runtime_enable(dev); + pm_runtime_set_autosuspend_delay(dev, 5000); + pm_runtime_use_autosuspend(dev); + + ret = imx500_identify_module(imx500); + if (ret) + goto error_power_off; + + /* Initialize default format */ + imx500_set_default_format(imx500); + + /* This needs the pm runtime to be registered. */ + ret = imx500_init_controls(imx500); + if (ret) + goto error_power_off; + + /* Initialize subdev */ + imx500->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | + V4L2_SUBDEV_FL_HAS_EVENTS; + imx500->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + /* Initialize source pads */ + imx500->pad[IMAGE_PAD].flags = MEDIA_PAD_FL_SOURCE; + imx500->pad[METADATA_PAD].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&imx500->sd.entity, NUM_PADS, imx500->pad); + if (ret) { + dev_err(dev, "failed to init entity pads: %d\n", ret); + goto error_handler_free; + } + + ret = v4l2_async_register_subdev_sensor(&imx500->sd); + if (ret < 0) { + dev_err(dev, "failed to register sensor sub-device: %d\n", ret); + goto error_media_entity; + } + + snprintf(debugfs_name, sizeof(debugfs_name), "imx500-fw:%s", + dev_name(dev)); + imx500->debugfs = debugfs_create_dir(debugfs_name, NULL); + debugfs_create_file("fw_progress", 0444, imx500->debugfs, imx500, + &fw_progress_fops); + + pm_runtime_mark_last_busy(&client->dev); + pm_runtime_put_autosuspend(&client->dev); + + return 0; + +error_media_entity: + media_entity_cleanup(&imx500->sd.entity); + +error_handler_free: + imx500_free_controls(imx500); + +error_power_off: + pm_runtime_disable(&client->dev); + pm_runtime_put_noidle(&client->dev); + imx500_power_off(&client->dev); + + return ret; +} + +static void imx500_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx500 *imx500 = to_imx500(sd); + + if (imx500->spi_device) + put_device(&imx500->spi_device->dev); + + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + imx500_free_controls(imx500); + + if (imx500->fw_loader) + release_firmware(imx500->fw_loader); + + if (imx500->fw_main) + release_firmware(imx500->fw_main); + + imx500->fw_loader = NULL; + imx500->fw_main = NULL; + imx500_clear_fw_network(imx500); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) + imx500_power_off(&client->dev); + pm_runtime_set_suspended(&client->dev); +} + +static const struct of_device_id imx500_dt_ids[] = { + { .compatible = "sony,imx500" }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, imx500_dt_ids); + +static const struct dev_pm_ops imx500_pm_ops = { SET_RUNTIME_PM_OPS( + imx500_power_off, imx500_power_on, NULL) }; + +static struct i2c_driver imx500_i2c_driver = { + .driver = { + .name = "imx500", + .of_match_table = imx500_dt_ids, + .pm = &imx500_pm_ops, + }, + .probe = imx500_probe, + .remove = imx500_remove, +}; + +static int imx500_spi_probe(struct spi_device *spi) +{ + int result; + + spi->bits_per_word = 8; + spi->max_speed_hz = 35000000; + spi->mode = SPI_MODE_3; + + result = spi_setup(spi); + if (result < 0) + return dev_err_probe(&spi->dev, result, "spi_setup() failed"); + + return 0; +} + +static void imx500_spi_remove(struct spi_device *spi) +{ +} + +static const struct spi_device_id imx500_spi_id[] = { + { "imx500", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(spi, imx500_spi_id); + +static struct spi_driver imx500_spi_driver = { + .driver = { + .name = "imx500", + .of_match_table = imx500_dt_ids, + }, + .probe = imx500_spi_probe, + .remove = imx500_spi_remove, + .id_table = imx500_spi_id, +}; + +static int __init imx500_driver_init(void) +{ + int ret; + + ret = spi_register_driver(&imx500_spi_driver); + if (ret) + return ret; + + ret = i2c_add_driver(&imx500_i2c_driver); + if (ret) + spi_unregister_driver(&imx500_spi_driver); + + return ret; +} +module_init(imx500_driver_init); + +static void __exit imx500_driver_exit(void) +{ + i2c_del_driver(&imx500_i2c_driver); + spi_unregister_driver(&imx500_spi_driver); +} +module_exit(imx500_driver_exit); + +MODULE_AUTHOR("Naushir Patuck <naush@raspberrypi.com>"); +MODULE_DESCRIPTION("Sony IMX500 sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/imx519.c b/drivers/media/i2c/imx519.c new file mode 100644 index 00000000000000..5fe002ec3b2652 --- /dev/null +++ b/drivers/media/i2c/imx519.c @@ -0,0 +1,2146 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A V4L2 driver for Sony IMX519 cameras. + * Copyright (C) 2021 Arducam Technology co., Ltd. + * + * Based on Sony IMX477 camera driver + * Copyright (C) 2020 Raspberry Pi (Trading) Ltd + */ +#include <linux/unaligned.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-event.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-mediabus.h> + +#define IMX519_REG_VALUE_08BIT 1 +#define IMX519_REG_VALUE_16BIT 2 + +/* Chip ID */ +#define IMX519_REG_CHIP_ID 0x0016 +#define IMX519_CHIP_ID 0x0519 + +#define IMX519_REG_MODE_SELECT 0x0100 +#define IMX519_MODE_STANDBY 0x00 +#define IMX519_MODE_STREAMING 0x01 + +#define IMX519_REG_ORIENTATION 0x101 + +#define IMX519_XCLK_FREQ 24000000 + +#define IMX519_DEFAULT_LINK_FREQ 408000000 + +/* Pixel rate is fixed at 426MHz for all the modes */ +#define IMX519_PIXEL_RATE 426666667 + +/* V_TIMING internal */ +#define IMX519_REG_FRAME_LENGTH 0x0340 +#define IMX519_FRAME_LENGTH_MAX 0xffdc + +/* Long exposure multiplier */ +#define IMX519_LONG_EXP_SHIFT_MAX 7 +#define IMX519_LONG_EXP_SHIFT_REG 0x3100 + +/* Exposure control */ +#define IMX519_REG_EXPOSURE 0x0202 +#define IMX519_EXPOSURE_OFFSET 32 +#define IMX519_EXPOSURE_MIN 20 +#define IMX519_EXPOSURE_STEP 1 +#define IMX519_EXPOSURE_DEFAULT 0x3e8 +#define IMX519_EXPOSURE_MAX (IMX519_FRAME_LENGTH_MAX - \ + IMX519_EXPOSURE_OFFSET) + +/* Analog gain control */ +#define IMX519_REG_ANALOG_GAIN 0x0204 +#define IMX519_ANA_GAIN_MIN 0 +#define IMX519_ANA_GAIN_MAX 960 +#define IMX519_ANA_GAIN_STEP 1 +#define IMX519_ANA_GAIN_DEFAULT 0x0 + +/* Digital gain control */ +#define IMX519_REG_DIGITAL_GAIN 0x020e +#define IMX519_DGTL_GAIN_MIN 0x0100 +#define IMX519_DGTL_GAIN_MAX 0xffff +#define IMX519_DGTL_GAIN_DEFAULT 0x0100 +#define IMX519_DGTL_GAIN_STEP 1 + +/* Test Pattern Control */ +#define IMX519_REG_TEST_PATTERN 0x0600 +#define IMX519_TEST_PATTERN_DISABLE 0 +#define IMX519_TEST_PATTERN_SOLID_COLOR 1 +#define IMX519_TEST_PATTERN_COLOR_BARS 2 +#define IMX519_TEST_PATTERN_GREY_COLOR 3 +#define IMX519_TEST_PATTERN_PN9 4 + +/* Test pattern colour components */ +#define IMX519_REG_TEST_PATTERN_R 0x0602 +#define IMX519_REG_TEST_PATTERN_GR 0x0604 +#define IMX519_REG_TEST_PATTERN_B 0x0606 +#define IMX519_REG_TEST_PATTERN_GB 0x0608 +#define IMX519_TEST_PATTERN_COLOUR_MIN 0 +#define IMX519_TEST_PATTERN_COLOUR_MAX 0x0fff +#define IMX519_TEST_PATTERN_COLOUR_STEP 1 +#define IMX519_TEST_PATTERN_R_DEFAULT IMX519_TEST_PATTERN_COLOUR_MAX +#define IMX519_TEST_PATTERN_GR_DEFAULT 0 +#define IMX519_TEST_PATTERN_B_DEFAULT 0 +#define IMX519_TEST_PATTERN_GB_DEFAULT 0 + +/* Embedded metadata stream structure */ +#define IMX519_EMBEDDED_LINE_WIDTH (5820 * 3) +#define IMX519_NUM_EMBEDDED_LINES 1 + +enum pad_types { + IMAGE_PAD, + METADATA_PAD, + NUM_PADS +}; + +/* IMX519 native and active pixel array size. */ +#define IMX519_NATIVE_WIDTH 4672U +#define IMX519_NATIVE_HEIGHT 3648U +#define IMX519_PIXEL_ARRAY_LEFT 8U +#define IMX519_PIXEL_ARRAY_TOP 48U +#define IMX519_PIXEL_ARRAY_WIDTH 4656U +#define IMX519_PIXEL_ARRAY_HEIGHT 3496U + +struct imx519_reg { + u16 address; + u8 val; +}; + +struct imx519_reg_list { + unsigned int num_of_regs; + const struct imx519_reg *regs; +}; + +/* Mode : resolution and related config&values */ +struct imx519_mode { + /* Frame width */ + unsigned int width; + + /* Frame height */ + unsigned int height; + + /* H-timing in pixels */ + unsigned int line_length_pix; + + /* Analog crop rectangle. */ + struct v4l2_rect crop; + + /* Highest possible framerate. */ + struct v4l2_fract timeperframe_min; + + /* Default framerate. */ + struct v4l2_fract timeperframe_default; + + /* Default register values */ + struct imx519_reg_list reg_list; +}; + +static const s64 imx519_link_freq_menu[] = { + IMX519_DEFAULT_LINK_FREQ, +}; + +static const struct imx519_reg mode_common_regs[] = { + {0x0100, 0x00}, + {0x0136, 0x18}, + {0x0137, 0x00}, + {0x3c7e, 0x01}, + {0x3c7f, 0x07}, + {0x3020, 0x00}, + {0x3e35, 0x01}, + {0x3f7f, 0x01}, + {0x5609, 0x57}, + {0x5613, 0x51}, + {0x561f, 0x5e}, + {0x5623, 0xd2}, + {0x5637, 0x11}, + {0x5657, 0x11}, + {0x5659, 0x12}, + {0x5733, 0x60}, + {0x5905, 0x57}, + {0x590f, 0x51}, + {0x591b, 0x5e}, + {0x591f, 0xd2}, + {0x5933, 0x11}, + {0x5953, 0x11}, + {0x5955, 0x12}, + {0x5a2f, 0x60}, + {0x5a85, 0x57}, + {0x5a8f, 0x51}, + {0x5a9b, 0x5e}, + {0x5a9f, 0xd2}, + {0x5ab3, 0x11}, + {0x5ad3, 0x11}, + {0x5ad5, 0x12}, + {0x5baf, 0x60}, + {0x5c15, 0x2a}, + {0x5c17, 0x80}, + {0x5c19, 0x31}, + {0x5c1b, 0x87}, + {0x5c25, 0x25}, + {0x5c27, 0x7b}, + {0x5c29, 0x2a}, + {0x5c2b, 0x80}, + {0x5c2d, 0x31}, + {0x5c2f, 0x87}, + {0x5c35, 0x2b}, + {0x5c37, 0x81}, + {0x5c39, 0x31}, + {0x5c3b, 0x87}, + {0x5c45, 0x25}, + {0x5c47, 0x7b}, + {0x5c49, 0x2a}, + {0x5c4b, 0x80}, + {0x5c4d, 0x31}, + {0x5c4f, 0x87}, + {0x5c55, 0x2d}, + {0x5c57, 0x83}, + {0x5c59, 0x32}, + {0x5c5b, 0x88}, + {0x5c65, 0x29}, + {0x5c67, 0x7f}, + {0x5c69, 0x2e}, + {0x5c6b, 0x84}, + {0x5c6d, 0x32}, + {0x5c6f, 0x88}, + {0x5e69, 0x04}, + {0x5e9d, 0x00}, + {0x5f18, 0x10}, + {0x5f1a, 0x0e}, + {0x5f20, 0x12}, + {0x5f22, 0x10}, + {0x5f24, 0x0e}, + {0x5f28, 0x10}, + {0x5f2a, 0x0e}, + {0x5f30, 0x12}, + {0x5f32, 0x10}, + {0x5f34, 0x0e}, + {0x5f38, 0x0f}, + {0x5f39, 0x0d}, + {0x5f3c, 0x11}, + {0x5f3d, 0x0f}, + {0x5f3e, 0x0d}, + {0x5f61, 0x07}, + {0x5f64, 0x05}, + {0x5f67, 0x03}, + {0x5f6a, 0x03}, + {0x5f6d, 0x07}, + {0x5f70, 0x07}, + {0x5f73, 0x05}, + {0x5f76, 0x02}, + {0x5f79, 0x07}, + {0x5f7c, 0x07}, + {0x5f7f, 0x07}, + {0x5f82, 0x07}, + {0x5f85, 0x03}, + {0x5f88, 0x02}, + {0x5f8b, 0x01}, + {0x5f8e, 0x01}, + {0x5f91, 0x04}, + {0x5f94, 0x05}, + {0x5f97, 0x02}, + {0x5f9d, 0x07}, + {0x5fa0, 0x07}, + {0x5fa3, 0x07}, + {0x5fa6, 0x07}, + {0x5fa9, 0x03}, + {0x5fac, 0x01}, + {0x5faf, 0x01}, + {0x5fb5, 0x03}, + {0x5fb8, 0x02}, + {0x5fbb, 0x01}, + {0x5fc1, 0x07}, + {0x5fc4, 0x07}, + {0x5fc7, 0x07}, + {0x5fd1, 0x00}, + {0x6302, 0x79}, + {0x6305, 0x78}, + {0x6306, 0xa5}, + {0x6308, 0x03}, + {0x6309, 0x20}, + {0x630b, 0x0a}, + {0x630d, 0x48}, + {0x630f, 0x06}, + {0x6311, 0xa4}, + {0x6313, 0x03}, + {0x6314, 0x20}, + {0x6316, 0x0a}, + {0x6317, 0x31}, + {0x6318, 0x4a}, + {0x631a, 0x06}, + {0x631b, 0x40}, + {0x631c, 0xa4}, + {0x631e, 0x03}, + {0x631f, 0x20}, + {0x6321, 0x0a}, + {0x6323, 0x4a}, + {0x6328, 0x80}, + {0x6329, 0x01}, + {0x632a, 0x30}, + {0x632b, 0x02}, + {0x632c, 0x20}, + {0x632d, 0x02}, + {0x632e, 0x30}, + {0x6330, 0x60}, + {0x6332, 0x90}, + {0x6333, 0x01}, + {0x6334, 0x30}, + {0x6335, 0x02}, + {0x6336, 0x20}, + {0x6338, 0x80}, + {0x633a, 0xa0}, + {0x633b, 0x01}, + {0x633c, 0x60}, + {0x633d, 0x02}, + {0x633e, 0x60}, + {0x633f, 0x01}, + {0x6340, 0x30}, + {0x6341, 0x02}, + {0x6342, 0x20}, + {0x6343, 0x03}, + {0x6344, 0x80}, + {0x6345, 0x03}, + {0x6346, 0x90}, + {0x6348, 0xf0}, + {0x6349, 0x01}, + {0x634a, 0x20}, + {0x634b, 0x02}, + {0x634c, 0x10}, + {0x634d, 0x03}, + {0x634e, 0x60}, + {0x6350, 0xa0}, + {0x6351, 0x01}, + {0x6352, 0x60}, + {0x6353, 0x02}, + {0x6354, 0x50}, + {0x6355, 0x02}, + {0x6356, 0x60}, + {0x6357, 0x01}, + {0x6358, 0x30}, + {0x6359, 0x02}, + {0x635a, 0x30}, + {0x635b, 0x03}, + {0x635c, 0x90}, + {0x635f, 0x01}, + {0x6360, 0x10}, + {0x6361, 0x01}, + {0x6362, 0x40}, + {0x6363, 0x02}, + {0x6364, 0x50}, + {0x6368, 0x70}, + {0x636a, 0xa0}, + {0x636b, 0x01}, + {0x636c, 0x50}, + {0x637d, 0xe4}, + {0x637e, 0xb4}, + {0x638c, 0x8e}, + {0x638d, 0x38}, + {0x638e, 0xe3}, + {0x638f, 0x4c}, + {0x6390, 0x30}, + {0x6391, 0xc3}, + {0x6392, 0xae}, + {0x6393, 0xba}, + {0x6394, 0xeb}, + {0x6395, 0x6e}, + {0x6396, 0x34}, + {0x6397, 0xe3}, + {0x6398, 0xcf}, + {0x6399, 0x3c}, + {0x639a, 0xf3}, + {0x639b, 0x0c}, + {0x639c, 0x30}, + {0x639d, 0xc1}, + {0x63b9, 0xa3}, + {0x63ba, 0xfe}, + {0x7600, 0x01}, + {0x79a0, 0x01}, + {0x79a1, 0x01}, + {0x79a2, 0x01}, + {0x79a3, 0x01}, + {0x79a4, 0x01}, + {0x79a5, 0x20}, + {0x79a9, 0x00}, + {0x79aa, 0x01}, + {0x79ad, 0x00}, + {0x79af, 0x00}, + {0x8173, 0x01}, + {0x835c, 0x01}, + {0x8a74, 0x01}, + {0x8c1f, 0x00}, + {0x8c27, 0x00}, + {0x8c3b, 0x03}, + {0x9004, 0x0b}, + {0x920c, 0x6a}, + {0x920d, 0x22}, + {0x920e, 0x6a}, + {0x920f, 0x23}, + {0x9214, 0x6a}, + {0x9215, 0x20}, + {0x9216, 0x6a}, + {0x9217, 0x21}, + {0x9385, 0x3e}, + {0x9387, 0x1b}, + {0x938d, 0x4d}, + {0x938f, 0x43}, + {0x9391, 0x1b}, + {0x9395, 0x4d}, + {0x9397, 0x43}, + {0x9399, 0x1b}, + {0x939d, 0x3e}, + {0x939f, 0x2f}, + {0x93a5, 0x43}, + {0x93a7, 0x2f}, + {0x93a9, 0x2f}, + {0x93ad, 0x34}, + {0x93af, 0x2f}, + {0x93b5, 0x3e}, + {0x93b7, 0x2f}, + {0x93bd, 0x4d}, + {0x93bf, 0x43}, + {0x93c1, 0x2f}, + {0x93c5, 0x4d}, + {0x93c7, 0x43}, + {0x93c9, 0x2f}, + {0x974b, 0x02}, + {0x995c, 0x8c}, + {0x995d, 0x00}, + {0x995e, 0x00}, + {0x9963, 0x64}, + {0x9964, 0x50}, + {0xaa0a, 0x26}, + {0xae03, 0x04}, + {0xae04, 0x03}, + {0xae05, 0x03}, + {0xbc1c, 0x08}, + {0xbcf1, 0x02}, + {0x38a3, 0x00}, +}; + +/* 16 mpix 10fps */ +static const struct imx519_reg mode_4656x3496_regs[] = { + {0x0111, 0x02}, + {0x0112, 0x0a}, + {0x0113, 0x0a}, + {0x0114, 0x01}, + {0x0342, 0x31}, + {0x0343, 0x6a}, + {0x0340, 0x0d}, + {0x0341, 0xf4}, + {0x0344, 0x00}, + {0x0345, 0x00}, + {0x0346, 0x00}, + {0x0347, 0x00}, + {0x0348, 0x12}, + {0x0349, 0x2f}, + {0x034a, 0x0d}, + {0x034b, 0xa7}, + {0x0220, 0x00}, + {0x0221, 0x11}, + {0x0222, 0x01}, + {0x0900, 0x00}, + {0x0901, 0x11}, + {0x0902, 0x0a}, + {0x3f4c, 0x01}, + {0x3f4d, 0x01}, + {0x4254, 0x7f}, + {0x0401, 0x00}, + {0x0404, 0x00}, + {0x0405, 0x10}, + {0x0408, 0x00}, + {0x0409, 0x00}, + {0x040a, 0x00}, + {0x040b, 0x00}, + {0x040c, 0x12}, + {0x040d, 0x30}, + {0x040e, 0x0d}, + {0x040f, 0xa8}, + {0x034c, 0x12}, + {0x034d, 0x30}, + {0x034e, 0x0d}, + {0x034f, 0xa8}, + {0x0301, 0x06}, + {0x0303, 0x04}, + {0x0305, 0x06}, + {0x0306, 0x01}, + {0x0307, 0x40}, + {0x0309, 0x0a}, + {0x030b, 0x02}, + {0x030d, 0x04}, + {0x030e, 0x01}, + {0x030f, 0x10}, + {0x0310, 0x01}, + {0x0820, 0x0a}, + {0x0821, 0x20}, + {0x0822, 0x00}, + {0x0823, 0x00}, + {0x3e20, 0x01}, + {0x3e37, 0x01}, + {0x3e3b, 0x00}, + {0x38a4, 0x00}, + {0x38a5, 0x00}, + {0x38a6, 0x00}, + {0x38a7, 0x00}, + {0x38a8, 0x01}, + {0x38a9, 0x23}, + {0x38aa, 0x01}, + {0x38ab, 0x23}, + {0x0106, 0x00}, + {0x0b00, 0x00}, + {0x3230, 0x00}, + {0x3f14, 0x01}, + {0x3f3c, 0x01}, + {0x3f0d, 0x0a}, + {0x3fbc, 0x00}, + {0x3c06, 0x00}, + {0x3c07, 0x48}, + {0x3c0a, 0x00}, + {0x3c0b, 0x00}, + {0x3f78, 0x00}, + {0x3f79, 0x40}, + {0x3f7c, 0x00}, + {0x3f7d, 0x00}, +}; + +/* 4k 21fps mode */ +static const struct imx519_reg mode_3840x2160_regs[] = { + {0x0111, 0x02}, + {0x0112, 0x0a}, + {0x0113, 0x0a}, + {0x0114, 0x01}, + {0x0342, 0x28}, + {0x0343, 0xf6}, + {0x0340, 0x08}, + {0x0341, 0xd4}, + {0x0344, 0x01}, + {0x0345, 0x98}, + {0x0346, 0x02}, + {0x0347, 0xa0}, + {0x0348, 0x10}, + {0x0349, 0x97}, + {0x034a, 0x0b}, + {0x034b, 0x17}, + {0x0220, 0x00}, + {0x0221, 0x11}, + {0x0222, 0x01}, + {0x0900, 0x00}, + {0x0901, 0x11}, + {0x0902, 0x0a}, + {0x3f4c, 0x01}, + {0x3f4d, 0x01}, + {0x4254, 0x7f}, + {0x0401, 0x00}, + {0x0404, 0x00}, + {0x0405, 0x10}, + {0x0408, 0x00}, + {0x0409, 0x00}, + {0x040a, 0x00}, + {0x040b, 0x00}, + {0x040c, 0x0f}, + {0x040d, 0x00}, + {0x040e, 0x08}, + {0x040f, 0x70}, + {0x034c, 0x0f}, + {0x034d, 0x00}, + {0x034e, 0x08}, + {0x034f, 0x70}, + {0x0301, 0x06}, + {0x0303, 0x04}, + {0x0305, 0x06}, + {0x0306, 0x01}, + {0x0307, 0x40}, + {0x0309, 0x0a}, + {0x030b, 0x02}, + {0x030d, 0x04}, + {0x030e, 0x01}, + {0x030f, 0x10}, + {0x0310, 0x01}, + {0x0820, 0x0a}, + {0x0821, 0x20}, + {0x0822, 0x00}, + {0x0823, 0x00}, + {0x3e20, 0x01}, + {0x3e37, 0x01}, + {0x3e3b, 0x00}, + {0x38a4, 0x00}, + {0x38a5, 0x00}, + {0x38a6, 0x00}, + {0x38a7, 0x00}, + {0x38a8, 0x00}, + {0x38a9, 0xf0}, + {0x38aa, 0x00}, + {0x38ab, 0xb4}, + {0x0106, 0x00}, + {0x0b00, 0x00}, + {0x3230, 0x00}, + {0x3f14, 0x01}, + {0x3f3c, 0x01}, + {0x3f0d, 0x0a}, + {0x3fbc, 0x00}, + {0x3c06, 0x00}, + {0x3c07, 0x48}, + {0x3c0a, 0x00}, + {0x3c0b, 0x00}, + {0x3f78, 0x00}, + {0x3f79, 0x40}, + {0x3f7c, 0x00}, + {0x3f7d, 0x00}, +}; + +/* 2x2 binned 30fps mode */ +static const struct imx519_reg mode_2328x1748_regs[] = { + {0x0111, 0x02}, + {0x0112, 0x0a}, + {0x0113, 0x0a}, + {0x0114, 0x01}, + {0x0342, 0x19}, + {0x0343, 0x70}, + {0x0340, 0x08}, + {0x0341, 0x88}, + {0x0344, 0x00}, + {0x0345, 0x00}, + {0x0346, 0x00}, + {0x0347, 0x00}, + {0x0348, 0x12}, + {0x0349, 0x2f}, + {0x034a, 0x0d}, + {0x034b, 0xa7}, + {0x0220, 0x00}, + {0x0221, 0x11}, + {0x0222, 0x01}, + {0x0900, 0x01}, + {0x0901, 0x22}, + {0x0902, 0x0a}, + {0x3f4c, 0x05}, + {0x3f4d, 0x03}, + {0x4254, 0x7f}, + {0x0401, 0x00}, + {0x0404, 0x00}, + {0x0405, 0x10}, + {0x0408, 0x00}, + {0x0409, 0x00}, + {0x040a, 0x00}, + {0x040b, 0x00}, + {0x040c, 0x09}, + {0x040d, 0x18}, + {0x040e, 0x06}, + {0x040f, 0xd4}, + {0x034c, 0x09}, + {0x034d, 0x18}, + {0x034e, 0x06}, + {0x034f, 0xd4}, + {0x0301, 0x06}, + {0x0303, 0x04}, + {0x0305, 0x06}, + {0x0306, 0x01}, + {0x0307, 0x40}, + {0x0309, 0x0a}, + {0x030b, 0x02}, + {0x030d, 0x04}, + {0x030e, 0x01}, + {0x030f, 0x10}, + {0x0310, 0x01}, + {0x0820, 0x0a}, + {0x0821, 0x20}, + {0x0822, 0x00}, + {0x0823, 0x00}, + {0x3e20, 0x01}, + {0x3e37, 0x01}, + {0x3e3b, 0x00}, + {0x38a4, 0x00}, + {0x38a5, 0x00}, + {0x38a6, 0x00}, + {0x38a7, 0x00}, + {0x38a8, 0x00}, + {0x38a9, 0x91}, + {0x38aa, 0x00}, + {0x38ab, 0x91}, + {0x0106, 0x00}, + {0x0b00, 0x00}, + {0x3230, 0x00}, + {0x3f14, 0x01}, + {0x3f3c, 0x01}, + {0x3f0d, 0x0a}, + {0x3fbc, 0x00}, + {0x3c06, 0x00}, + {0x3c07, 0x48}, + {0x3c0a, 0x00}, + {0x3c0b, 0x00}, + {0x3f78, 0x00}, + {0x3f79, 0x40}, + {0x3f7c, 0x00}, + {0x3f7d, 0x00}, +}; + +/* 1080p 60fps mode */ +static const struct imx519_reg mode_1920x1080_regs[] = { + {0x0111, 0x02}, + {0x0112, 0x0a}, + {0x0113, 0x0a}, + {0x0114, 0x01}, + {0x0342, 0x17}, + {0x0343, 0x8b}, + {0x0340, 0x04}, + {0x0341, 0x9c}, + {0x0344, 0x01}, + {0x0345, 0x98}, + {0x0346, 0x02}, + {0x0347, 0xa2}, + {0x0348, 0x10}, + {0x0349, 0x97}, + {0x034a, 0x0b}, + {0x034b, 0x15}, + {0x0220, 0x00}, + {0x0221, 0x11}, + {0x0222, 0x01}, + {0x0900, 0x01}, + {0x0901, 0x22}, + {0x0902, 0x0a}, + {0x3f4c, 0x05}, + {0x3f4d, 0x03}, + {0x4254, 0x7f}, + {0x0401, 0x00}, + {0x0404, 0x00}, + {0x0405, 0x10}, + {0x0408, 0x00}, + {0x0409, 0x00}, + {0x040a, 0x00}, + {0x040b, 0x00}, + {0x040c, 0x07}, + {0x040d, 0x80}, + {0x040e, 0x04}, + {0x040f, 0x38}, + {0x034c, 0x07}, + {0x034d, 0x80}, + {0x034e, 0x04}, + {0x034f, 0x38}, + {0x0301, 0x06}, + {0x0303, 0x04}, + {0x0305, 0x06}, + {0x0306, 0x01}, + {0x0307, 0x40}, + {0x0309, 0x0a}, + {0x030b, 0x02}, + {0x030d, 0x04}, + {0x030e, 0x01}, + {0x030f, 0x10}, + {0x0310, 0x01}, + {0x0820, 0x0a}, + {0x0821, 0x20}, + {0x0822, 0x00}, + {0x0823, 0x00}, + {0x3e20, 0x01}, + {0x3e37, 0x01}, + {0x3e3b, 0x00}, + {0x38a4, 0x00}, + {0x38a5, 0x00}, + {0x38a6, 0x00}, + {0x38a7, 0x00}, + {0x38a8, 0x00}, + {0x38a9, 0x78}, + {0x38aa, 0x00}, + {0x38ab, 0x5a}, + {0x0106, 0x00}, + {0x0b00, 0x00}, + {0x3230, 0x00}, + {0x3f14, 0x01}, + {0x3f3c, 0x01}, + {0x3f0d, 0x0a}, + {0x3fbc, 0x00}, + {0x3c06, 0x00}, + {0x3c07, 0x48}, + {0x3c0a, 0x00}, + {0x3c0b, 0x00}, + {0x3f78, 0x00}, + {0x3f79, 0x40}, + {0x3f7c, 0x00}, + {0x3f7d, 0x00}, +}; + +/* 720p 120fps mode */ +static const struct imx519_reg mode_1280x720_regs[] = { + {0x0111, 0x02}, + {0x0112, 0x0a}, + {0x0113, 0x0a}, + {0x0114, 0x01}, + {0x0342, 0x18}, + {0x0343, 0x00}, + {0x0340, 0x03}, + {0x0341, 0x34}, + {0x0344, 0x04}, + {0x0345, 0x18}, + {0x0346, 0x04}, + {0x0347, 0x12}, + {0x0348, 0x0e}, + {0x0349, 0x17}, + {0x034a, 0x09}, + {0x034b, 0xb6}, + {0x0220, 0x00}, + {0x0221, 0x11}, + {0x0222, 0x01}, + {0x0900, 0x01}, + {0x0901, 0x22}, + {0x0902, 0x0a}, + {0x3f4c, 0x05}, + {0x3f4d, 0x03}, + {0x4254, 0x7f}, + {0x0401, 0x00}, + {0x0404, 0x00}, + {0x0405, 0x10}, + {0x0408, 0x00}, + {0x0409, 0x00}, + {0x040a, 0x00}, + {0x040b, 0x00}, + {0x040c, 0x05}, + {0x040d, 0x00}, + {0x040e, 0x02}, + {0x040f, 0xd0}, + {0x034c, 0x05}, + {0x034d, 0x00}, + {0x034e, 0x02}, + {0x034f, 0xd0}, + {0x0301, 0x06}, + {0x0303, 0x04}, + {0x0305, 0x06}, + {0x0306, 0x01}, + {0x0307, 0x40}, + {0x0309, 0x0a}, + {0x030b, 0x02}, + {0x030d, 0x04}, + {0x030e, 0x01}, + {0x030f, 0x10}, + {0x0310, 0x01}, + {0x0820, 0x0a}, + {0x0821, 0x20}, + {0x0822, 0x00}, + {0x0823, 0x00}, + {0x3e20, 0x01}, + {0x3e37, 0x01}, + {0x3e3b, 0x00}, + {0x38a4, 0x00}, + {0x38a5, 0x00}, + {0x38a6, 0x00}, + {0x38a7, 0x00}, + {0x38a8, 0x00}, + {0x38a9, 0x50}, + {0x38aa, 0x00}, + {0x38ab, 0x3c}, + {0x0106, 0x00}, + {0x0b00, 0x00}, + {0x3230, 0x00}, + {0x3f14, 0x01}, + {0x3f3c, 0x01}, + {0x3f0d, 0x0a}, + {0x3fbc, 0x00}, + {0x3c06, 0x00}, + {0x3c07, 0x48}, + {0x3c0a, 0x00}, + {0x3c0b, 0x00}, + {0x3f78, 0x00}, + {0x3f79, 0x40}, + {0x3f7c, 0x00}, + {0x3f7d, 0x00}, +}; + +/* Mode configs */ +static const struct imx519_mode supported_modes_10bit[] = { + { + .width = 4656, + .height = 3496, + .line_length_pix = 0x316a, + .crop = { + .left = IMX519_PIXEL_ARRAY_LEFT, + .top = IMX519_PIXEL_ARRAY_TOP, + .width = 4656, + .height = 3496, + }, + .timeperframe_min = { + .numerator = 100, + .denominator = 900 + }, + .timeperframe_default = { + .numerator = 100, + .denominator = 900 + }, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_4656x3496_regs), + .regs = mode_4656x3496_regs, + } + }, + { + .width = 3840, + .height = 2160, + .line_length_pix = 0x28f6, + .crop = { + .left = IMX519_PIXEL_ARRAY_LEFT + 408, + .top = IMX519_PIXEL_ARRAY_TOP + 672, + .width = 3840, + .height = 2160, + }, + .timeperframe_min = { + .numerator = 100, + .denominator = 1800 + }, + .timeperframe_default = { + .numerator = 100, + .denominator = 1800 + }, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_3840x2160_regs), + .regs = mode_3840x2160_regs, + } + }, + { + .width = 2328, + .height = 1748, + .line_length_pix = 0x1970, + .crop = { + .left = IMX519_PIXEL_ARRAY_LEFT, + .top = IMX519_PIXEL_ARRAY_TOP, + .width = 4656, + .height = 3496, + }, + .timeperframe_min = { + .numerator = 100, + .denominator = 3000 + }, + .timeperframe_default = { + .numerator = 100, + .denominator = 3000 + }, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_2328x1748_regs), + .regs = mode_2328x1748_regs, + } + }, + { + .width = 1920, + .height = 1080, + .line_length_pix = 0x178b, + .crop = { + .left = IMX519_PIXEL_ARRAY_LEFT + 408, + .top = IMX519_PIXEL_ARRAY_TOP + 674, + .width = 3840, + .height = 2160, + }, + .timeperframe_min = { + .numerator = 100, + .denominator = 6000 + }, + .timeperframe_default = { + .numerator = 100, + .denominator = 6000 + }, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1920x1080_regs), + .regs = mode_1920x1080_regs, + } + }, + { + .width = 1280, + .height = 720, + .line_length_pix = 0x1800, + .crop = { + .left = IMX519_PIXEL_ARRAY_LEFT + 1048, + .top = IMX519_PIXEL_ARRAY_TOP + 1042, + .width = 2560, + .height = 1440, + }, + .timeperframe_min = { + .numerator = 100, + .denominator = 8000 + }, + .timeperframe_default = { + .numerator = 100, + .denominator = 8000 + }, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1280x720_regs), + .regs = mode_1280x720_regs, + } + } +}; + +/* + * The supported formats. + * This table MUST contain 4 entries per format, to cover the various flip + * combinations in the order + * - no flip + * - h flip + * - v flip + * - h&v flips + */ +static const u32 codes[] = { + /* 10-bit modes. */ + MEDIA_BUS_FMT_SRGGB10_1X10, + MEDIA_BUS_FMT_SGRBG10_1X10, + MEDIA_BUS_FMT_SGBRG10_1X10, + MEDIA_BUS_FMT_SBGGR10_1X10, +}; + +static const char * const imx519_test_pattern_menu[] = { + "Disabled", + "Color Bars", + "Solid Color", + "Grey Color Bars", + "PN9" +}; + +static const int imx519_test_pattern_val[] = { + IMX519_TEST_PATTERN_DISABLE, + IMX519_TEST_PATTERN_COLOR_BARS, + IMX519_TEST_PATTERN_SOLID_COLOR, + IMX519_TEST_PATTERN_GREY_COLOR, + IMX519_TEST_PATTERN_PN9, +}; + +/* regulator supplies */ +static const char * const imx519_supply_name[] = { + /* Supplies can be enabled in any order */ + "VANA", /* Analog (2.8V) supply */ + "VDIG", /* Digital Core (1.05V) supply */ + "VDDL", /* IF (1.8V) supply */ +}; + +#define IMX519_NUM_SUPPLIES ARRAY_SIZE(imx519_supply_name) + +/* + * Initialisation delay between XCLR low->high and the moment when the sensor + * can start capture (i.e. can leave software standby), given by T7 in the + * datasheet is 8ms. This does include I2C setup time as well. + * + * Note, that delay between XCLR low->high and reading the CCI ID register (T6 + * in the datasheet) is much smaller - 600us. + */ +#define IMX519_XCLR_MIN_DELAY_US 8000 +#define IMX519_XCLR_DELAY_RANGE_US 1000 + +struct imx519 { + struct v4l2_subdev sd; + struct media_pad pad[NUM_PADS]; + + unsigned int fmt_code; + + struct clk *xclk; + + struct gpio_desc *reset_gpio; + struct regulator_bulk_data supplies[IMX519_NUM_SUPPLIES]; + + struct v4l2_ctrl_handler ctrl_handler; + /* V4L2 Controls */ + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *vflip; + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + + /* Current mode */ + const struct imx519_mode *mode; + + /* + * Mutex for serialized access: + * Protect sensor module set pad format and start/stop streaming safely. + */ + struct mutex mutex; + + /* Streaming on/off */ + bool streaming; + + /* Rewrite common registers on stream on? */ + bool common_regs_written; + + /* Current long exposure factor in use. Set through V4L2_CID_VBLANK */ + unsigned int long_exp_shift; +}; + +static inline struct imx519 *to_imx519(struct v4l2_subdev *_sd) +{ + return container_of(_sd, struct imx519, sd); +} + +/* Read registers up to 2 at a time */ +static int imx519_read_reg(struct imx519 *imx519, u16 reg, u32 len, u32 *val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx519->sd); + struct i2c_msg msgs[2]; + u8 addr_buf[2] = { reg >> 8, reg & 0xff }; + u8 data_buf[4] = { 0, }; + int ret; + + if (len > 4) + return -EINVAL; + + /* Write register address */ + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = ARRAY_SIZE(addr_buf); + msgs[0].buf = addr_buf; + + /* Read data from register */ + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = &data_buf[4 - len]; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + *val = get_unaligned_be32(data_buf); + + return 0; +} + +/* Write registers up to 2 at a time */ +static int imx519_write_reg(struct imx519 *imx519, u16 reg, u32 len, u32 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx519->sd); + u8 buf[6]; + + if (len > 4) + return -EINVAL; + + put_unaligned_be16(reg, buf); + put_unaligned_be32(val << (8 * (4 - len)), buf + 2); + if (i2c_master_send(client, buf, len + 2) != len + 2) + return -EIO; + + return 0; +} + +/* Write a list of registers */ +static int imx519_write_regs(struct imx519 *imx519, + const struct imx519_reg *regs, u32 len) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx519->sd); + unsigned int i; + int ret; + + for (i = 0; i < len; i++) { + ret = imx519_write_reg(imx519, regs[i].address, 1, regs[i].val); + if (ret) { + dev_err_ratelimited(&client->dev, + "Failed to write reg 0x%4.4x. error = %d\n", + regs[i].address, ret); + + return ret; + } + } + + return 0; +} + +/* Get bayer order based on flip setting. */ +static u32 imx519_get_format_code(struct imx519 *imx519) +{ + unsigned int i; + + lockdep_assert_held(&imx519->mutex); + + i = (imx519->vflip->val ? 2 : 0) | + (imx519->hflip->val ? 1 : 0); + + return codes[i]; +} + +static int imx519_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct imx519 *imx519 = to_imx519(sd); + struct v4l2_mbus_framefmt *try_fmt_img = + v4l2_subdev_state_get_format(fh->state, IMAGE_PAD); + struct v4l2_mbus_framefmt *try_fmt_meta = + v4l2_subdev_state_get_format(fh->state, METADATA_PAD); + struct v4l2_rect *try_crop; + + mutex_lock(&imx519->mutex); + + /* Initialize try_fmt for the image pad */ + try_fmt_img->width = supported_modes_10bit[0].width; + try_fmt_img->height = supported_modes_10bit[0].height; + try_fmt_img->code = imx519_get_format_code(imx519); + try_fmt_img->field = V4L2_FIELD_NONE; + + /* Initialize try_fmt for the embedded metadata pad */ + try_fmt_meta->width = IMX519_EMBEDDED_LINE_WIDTH; + try_fmt_meta->height = IMX519_NUM_EMBEDDED_LINES; + try_fmt_meta->code = MEDIA_BUS_FMT_SENSOR_DATA; + try_fmt_meta->field = V4L2_FIELD_NONE; + + /* Initialize try_crop */ + try_crop = v4l2_subdev_state_get_crop(fh->state, IMAGE_PAD); + try_crop->left = IMX519_PIXEL_ARRAY_LEFT; + try_crop->top = IMX519_PIXEL_ARRAY_TOP; + try_crop->width = IMX519_PIXEL_ARRAY_WIDTH; + try_crop->height = IMX519_PIXEL_ARRAY_HEIGHT; + + mutex_unlock(&imx519->mutex); + + return 0; +} + +static void imx519_adjust_exposure_range(struct imx519 *imx519) +{ + int exposure_max, exposure_def; + + /* Honour the VBLANK limits when setting exposure. */ + exposure_max = imx519->mode->height + imx519->vblank->val - + IMX519_EXPOSURE_OFFSET; + exposure_def = min(exposure_max, imx519->exposure->val); + __v4l2_ctrl_modify_range(imx519->exposure, imx519->exposure->minimum, + exposure_max, imx519->exposure->step, + exposure_def); +} + +static int imx519_set_frame_length(struct imx519 *imx519, unsigned int val) +{ + int ret = 0; + + imx519->long_exp_shift = 0; + + while (val > IMX519_FRAME_LENGTH_MAX) { + imx519->long_exp_shift++; + val >>= 1; + } + + ret = imx519_write_reg(imx519, IMX519_REG_FRAME_LENGTH, + IMX519_REG_VALUE_16BIT, val); + if (ret) + return ret; + + return imx519_write_reg(imx519, IMX519_LONG_EXP_SHIFT_REG, + IMX519_REG_VALUE_08BIT, imx519->long_exp_shift); +} + +static int imx519_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct imx519 *imx519 = + container_of(ctrl->handler, struct imx519, ctrl_handler); + struct i2c_client *client = v4l2_get_subdevdata(&imx519->sd); + int ret = 0; + + /* + * The VBLANK control may change the limits of usable exposure, so check + * and adjust if necessary. + */ + if (ctrl->id == V4L2_CID_VBLANK) + imx519_adjust_exposure_range(imx519); + + /* + * Applying V4L2 control value only happens + * when power is up for streaming + */ + if (pm_runtime_get_if_in_use(&client->dev) == 0) + return 0; + + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + ret = imx519_write_reg(imx519, IMX519_REG_ANALOG_GAIN, + IMX519_REG_VALUE_16BIT, ctrl->val); + break; + case V4L2_CID_EXPOSURE: + ret = imx519_write_reg(imx519, IMX519_REG_EXPOSURE, + IMX519_REG_VALUE_16BIT, ctrl->val >> + imx519->long_exp_shift); + break; + case V4L2_CID_DIGITAL_GAIN: + ret = imx519_write_reg(imx519, IMX519_REG_DIGITAL_GAIN, + IMX519_REG_VALUE_16BIT, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN: + ret = imx519_write_reg(imx519, IMX519_REG_TEST_PATTERN, + IMX519_REG_VALUE_16BIT, + imx519_test_pattern_val[ctrl->val]); + break; + case V4L2_CID_TEST_PATTERN_RED: + ret = imx519_write_reg(imx519, IMX519_REG_TEST_PATTERN_R, + IMX519_REG_VALUE_16BIT, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN_GREENR: + ret = imx519_write_reg(imx519, IMX519_REG_TEST_PATTERN_GR, + IMX519_REG_VALUE_16BIT, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN_BLUE: + ret = imx519_write_reg(imx519, IMX519_REG_TEST_PATTERN_B, + IMX519_REG_VALUE_16BIT, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN_GREENB: + ret = imx519_write_reg(imx519, IMX519_REG_TEST_PATTERN_GB, + IMX519_REG_VALUE_16BIT, ctrl->val); + break; + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: + ret = imx519_write_reg(imx519, IMX519_REG_ORIENTATION, 1, + imx519->hflip->val | + imx519->vflip->val << 1); + break; + case V4L2_CID_VBLANK: + ret = imx519_set_frame_length(imx519, + imx519->mode->height + ctrl->val); + break; + default: + dev_info(&client->dev, + "ctrl(id:0x%x,val:0x%x) is not handled\n", + ctrl->id, ctrl->val); + ret = -EINVAL; + break; + } + + pm_runtime_put(&client->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops imx519_ctrl_ops = { + .s_ctrl = imx519_set_ctrl, +}; + +static int imx519_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct imx519 *imx519 = to_imx519(sd); + + if (code->pad >= NUM_PADS) + return -EINVAL; + + if (code->pad == IMAGE_PAD) { + if (code->index > 0) + return -EINVAL; + + code->code = imx519_get_format_code(imx519); + } else { + if (code->index > 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_SENSOR_DATA; + } + + return 0; +} + +static int imx519_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct imx519 *imx519 = to_imx519(sd); + + if (fse->pad >= NUM_PADS) + return -EINVAL; + + if (fse->pad == IMAGE_PAD) { + if (fse->index >= ARRAY_SIZE(supported_modes_10bit)) + return -EINVAL; + + if (fse->code != imx519_get_format_code(imx519)) + return -EINVAL; + + fse->min_width = supported_modes_10bit[fse->index].width; + fse->max_width = fse->min_width; + fse->min_height = supported_modes_10bit[fse->index].height; + fse->max_height = fse->min_height; + } else { + if (fse->code != MEDIA_BUS_FMT_SENSOR_DATA || fse->index > 0) + return -EINVAL; + + fse->min_width = IMX519_EMBEDDED_LINE_WIDTH; + fse->max_width = fse->min_width; + fse->min_height = IMX519_NUM_EMBEDDED_LINES; + fse->max_height = fse->min_height; + } + + return 0; +} + +static void imx519_reset_colorspace(struct v4l2_mbus_framefmt *fmt) +{ + fmt->colorspace = V4L2_COLORSPACE_RAW; + fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace); + fmt->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true, + fmt->colorspace, + fmt->ycbcr_enc); + fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace); +} + +static void imx519_update_image_pad_format(struct imx519 *imx519, + const struct imx519_mode *mode, + struct v4l2_subdev_format *fmt) +{ + fmt->format.width = mode->width; + fmt->format.height = mode->height; + fmt->format.field = V4L2_FIELD_NONE; + imx519_reset_colorspace(&fmt->format); +} + +static void imx519_update_metadata_pad_format(struct v4l2_subdev_format *fmt) +{ + fmt->format.width = IMX519_EMBEDDED_LINE_WIDTH; + fmt->format.height = IMX519_NUM_EMBEDDED_LINES; + fmt->format.code = MEDIA_BUS_FMT_SENSOR_DATA; + fmt->format.field = V4L2_FIELD_NONE; +} + +static int imx519_get_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct imx519 *imx519 = to_imx519(sd); + + if (fmt->pad >= NUM_PADS) + return -EINVAL; + + mutex_lock(&imx519->mutex); + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + struct v4l2_mbus_framefmt *try_fmt = + v4l2_subdev_state_get_format(sd_state, + fmt->pad); + /* update the code which could change due to vflip or hflip: */ + try_fmt->code = fmt->pad == IMAGE_PAD ? + imx519_get_format_code(imx519) : + MEDIA_BUS_FMT_SENSOR_DATA; + fmt->format = *try_fmt; + } else { + if (fmt->pad == IMAGE_PAD) { + imx519_update_image_pad_format(imx519, imx519->mode, + fmt); + fmt->format.code = + imx519_get_format_code(imx519); + } else { + imx519_update_metadata_pad_format(fmt); + } + } + + mutex_unlock(&imx519->mutex); + return 0; +} + +static +unsigned int imx519_get_frame_length(const struct imx519_mode *mode, + const struct v4l2_fract *timeperframe) +{ + u64 frame_length; + + frame_length = (u64)timeperframe->numerator * IMX519_PIXEL_RATE; + do_div(frame_length, + (u64)timeperframe->denominator * mode->line_length_pix); + + if (WARN_ON(frame_length > IMX519_FRAME_LENGTH_MAX)) + frame_length = IMX519_FRAME_LENGTH_MAX; + + return max_t(unsigned int, frame_length, mode->height); +} + +static void imx519_set_framing_limits(struct imx519 *imx519) +{ + unsigned int frm_length_min, frm_length_default, hblank; + const struct imx519_mode *mode = imx519->mode; + + frm_length_min = imx519_get_frame_length(mode, &mode->timeperframe_min); + frm_length_default = + imx519_get_frame_length(mode, &mode->timeperframe_default); + + /* Default to no long exposure multiplier. */ + imx519->long_exp_shift = 0; + + /* Update limits and set FPS to default */ + __v4l2_ctrl_modify_range(imx519->vblank, frm_length_min - mode->height, + ((1 << IMX519_LONG_EXP_SHIFT_MAX) * + IMX519_FRAME_LENGTH_MAX) - mode->height, + 1, frm_length_default - mode->height); + + /* Setting this will adjust the exposure limits as well. */ + __v4l2_ctrl_s_ctrl(imx519->vblank, frm_length_default - mode->height); + + /* + * Currently PPL is fixed to the mode specified value, so hblank + * depends on mode->width only, and is not changeable in any + * way other than changing the mode. + */ + hblank = mode->line_length_pix - mode->width; + __v4l2_ctrl_modify_range(imx519->hblank, hblank, hblank, 1, hblank); +} + +static int imx519_set_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct v4l2_mbus_framefmt *framefmt; + const struct imx519_mode *mode; + struct imx519 *imx519 = to_imx519(sd); + + if (fmt->pad >= NUM_PADS) + return -EINVAL; + + mutex_lock(&imx519->mutex); + + if (fmt->pad == IMAGE_PAD) { + /* Bayer order varies with flips */ + fmt->format.code = imx519_get_format_code(imx519); + + mode = v4l2_find_nearest_size(supported_modes_10bit, + ARRAY_SIZE(supported_modes_10bit), + width, height, + fmt->format.width, + fmt->format.height); + imx519_update_image_pad_format(imx519, mode, fmt); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + framefmt = v4l2_subdev_state_get_format(sd_state, + fmt->pad); + *framefmt = fmt->format; + } else { + imx519->mode = mode; + imx519->fmt_code = fmt->format.code; + imx519_set_framing_limits(imx519); + } + } else { + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + framefmt = v4l2_subdev_state_get_format(sd_state, + fmt->pad); + *framefmt = fmt->format; + } else { + /* Only one embedded data mode is supported */ + imx519_update_metadata_pad_format(fmt); + } + } + + mutex_unlock(&imx519->mutex); + + return 0; +} + +static const struct v4l2_rect * +__imx519_get_pad_crop(struct imx519 *imx519, struct v4l2_subdev_state *sd_state, + unsigned int pad, enum v4l2_subdev_format_whence which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_state_get_crop(sd_state, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &imx519->mode->crop; + } + + return NULL; +} + +static int imx519_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + switch (sel->target) { + case V4L2_SEL_TGT_CROP: { + struct imx519 *imx519 = to_imx519(sd); + + mutex_lock(&imx519->mutex); + sel->r = *__imx519_get_pad_crop(imx519, sd_state, sel->pad, + sel->which); + mutex_unlock(&imx519->mutex); + + return 0; + } + + case V4L2_SEL_TGT_NATIVE_SIZE: + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = IMX519_NATIVE_WIDTH; + sel->r.height = IMX519_NATIVE_HEIGHT; + + return 0; + + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.left = IMX519_PIXEL_ARRAY_LEFT; + sel->r.top = IMX519_PIXEL_ARRAY_TOP; + sel->r.width = IMX519_PIXEL_ARRAY_WIDTH; + sel->r.height = IMX519_PIXEL_ARRAY_HEIGHT; + + return 0; + } + + return -EINVAL; +} + +/* Start streaming */ +static int imx519_start_streaming(struct imx519 *imx519) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx519->sd); + const struct imx519_reg_list *reg_list; + int ret; + + if (!imx519->common_regs_written) { + ret = imx519_write_regs(imx519, mode_common_regs, + ARRAY_SIZE(mode_common_regs)); + + if (ret) { + dev_err(&client->dev, "%s failed to set common settings\n", + __func__); + return ret; + } + imx519->common_regs_written = true; + } + + /* Apply default values of current mode */ + reg_list = &imx519->mode->reg_list; + ret = imx519_write_regs(imx519, reg_list->regs, reg_list->num_of_regs); + if (ret) { + dev_err(&client->dev, "%s failed to set mode\n", __func__); + return ret; + } + + /* Apply customized values from user */ + ret = __v4l2_ctrl_handler_setup(imx519->sd.ctrl_handler); + if (ret) + return ret; + + /* set stream on register */ + return imx519_write_reg(imx519, IMX519_REG_MODE_SELECT, + IMX519_REG_VALUE_08BIT, IMX519_MODE_STREAMING); +} + +/* Stop streaming */ +static void imx519_stop_streaming(struct imx519 *imx519) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx519->sd); + int ret; + + /* set stream off register */ + ret = imx519_write_reg(imx519, IMX519_REG_MODE_SELECT, + IMX519_REG_VALUE_08BIT, IMX519_MODE_STANDBY); + if (ret) + dev_err(&client->dev, "%s failed to set stream\n", __func__); +} + +static int imx519_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct imx519 *imx519 = to_imx519(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret = 0; + + mutex_lock(&imx519->mutex); + if (imx519->streaming == enable) { + mutex_unlock(&imx519->mutex); + return 0; + } + + if (enable) { + ret = pm_runtime_get_sync(&client->dev); + if (ret < 0) { + pm_runtime_put_noidle(&client->dev); + goto err_unlock; + } + + /* + * Apply default & customized values + * and then start streaming. + */ + ret = imx519_start_streaming(imx519); + if (ret) + goto err_rpm_put; + } else { + imx519_stop_streaming(imx519); + pm_runtime_put(&client->dev); + } + + imx519->streaming = enable; + + /* vflip and hflip cannot change during streaming */ + __v4l2_ctrl_grab(imx519->vflip, enable); + __v4l2_ctrl_grab(imx519->hflip, enable); + + mutex_unlock(&imx519->mutex); + + return ret; + +err_rpm_put: + pm_runtime_put(&client->dev); +err_unlock: + mutex_unlock(&imx519->mutex); + + return ret; +} + +/* Power/clock management functions */ +static int imx519_power_on(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx519 *imx519 = to_imx519(sd); + int ret; + + ret = regulator_bulk_enable(IMX519_NUM_SUPPLIES, + imx519->supplies); + if (ret) { + dev_err(&client->dev, "%s: failed to enable regulators\n", + __func__); + return ret; + } + + ret = clk_prepare_enable(imx519->xclk); + if (ret) { + dev_err(&client->dev, "%s: failed to enable clock\n", + __func__); + goto reg_off; + } + + gpiod_set_value_cansleep(imx519->reset_gpio, 1); + usleep_range(IMX519_XCLR_MIN_DELAY_US, + IMX519_XCLR_MIN_DELAY_US + IMX519_XCLR_DELAY_RANGE_US); + + return 0; + +reg_off: + regulator_bulk_disable(IMX519_NUM_SUPPLIES, imx519->supplies); + return ret; +} + +static int imx519_power_off(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx519 *imx519 = to_imx519(sd); + + gpiod_set_value_cansleep(imx519->reset_gpio, 0); + regulator_bulk_disable(IMX519_NUM_SUPPLIES, imx519->supplies); + clk_disable_unprepare(imx519->xclk); + + /* Force reprogramming of the common registers when powered up again. */ + imx519->common_regs_written = false; + + return 0; +} + +static int __maybe_unused imx519_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx519 *imx519 = to_imx519(sd); + + if (imx519->streaming) + imx519_stop_streaming(imx519); + + return 0; +} + +static int __maybe_unused imx519_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx519 *imx519 = to_imx519(sd); + int ret; + + if (imx519->streaming) { + ret = imx519_start_streaming(imx519); + if (ret) + goto error; + } + + return 0; + +error: + imx519_stop_streaming(imx519); + imx519->streaming = 0; + return ret; +} + +static int imx519_get_regulators(struct imx519 *imx519) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx519->sd); + unsigned int i; + + for (i = 0; i < IMX519_NUM_SUPPLIES; i++) + imx519->supplies[i].supply = imx519_supply_name[i]; + + return devm_regulator_bulk_get(&client->dev, + IMX519_NUM_SUPPLIES, + imx519->supplies); +} + +/* Verify chip ID */ +static int imx519_identify_module(struct imx519 *imx519, u32 expected_id) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx519->sd); + int ret; + u32 val; + + ret = imx519_read_reg(imx519, IMX519_REG_CHIP_ID, + IMX519_REG_VALUE_16BIT, &val); + if (ret) { + dev_err(&client->dev, "failed to read chip id %x, with error %d\n", + expected_id, ret); + return ret; + } + + if (val != expected_id) { + dev_err(&client->dev, "chip id mismatch: %x!=%x\n", + expected_id, val); + return -EIO; + } + + dev_info(&client->dev, "Device found is imx%x\n", val); + + return 0; +} + +static const struct v4l2_subdev_core_ops imx519_core_ops = { + .subscribe_event = v4l2_ctrl_subdev_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_video_ops imx519_video_ops = { + .s_stream = imx519_set_stream, +}; + +static const struct v4l2_subdev_pad_ops imx519_pad_ops = { + .enum_mbus_code = imx519_enum_mbus_code, + .get_fmt = imx519_get_pad_format, + .set_fmt = imx519_set_pad_format, + .get_selection = imx519_get_selection, + .enum_frame_size = imx519_enum_frame_size, +}; + +static const struct v4l2_subdev_ops imx519_subdev_ops = { + .core = &imx519_core_ops, + .video = &imx519_video_ops, + .pad = &imx519_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops imx519_internal_ops = { + .open = imx519_open, +}; + +/* Initialize control handlers */ +static int imx519_init_controls(struct imx519 *imx519) +{ + struct v4l2_ctrl_handler *ctrl_hdlr; + struct i2c_client *client = v4l2_get_subdevdata(&imx519->sd); + struct v4l2_fwnode_device_properties props; + struct v4l2_ctrl *link_freq; + unsigned int i; + int ret; + + ctrl_hdlr = &imx519->ctrl_handler; + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 16); + if (ret) + return ret; + + mutex_init(&imx519->mutex); + ctrl_hdlr->lock = &imx519->mutex; + + /* By default, PIXEL_RATE is read only */ + imx519->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &imx519_ctrl_ops, + V4L2_CID_PIXEL_RATE, + IMX519_PIXEL_RATE, + IMX519_PIXEL_RATE, 1, + IMX519_PIXEL_RATE); + + /* LINK_FREQ is also read only */ + link_freq = + v4l2_ctrl_new_int_menu(ctrl_hdlr, &imx519_ctrl_ops, + V4L2_CID_LINK_FREQ, + ARRAY_SIZE(imx519_link_freq_menu) - 1, 0, + imx519_link_freq_menu); + if (link_freq) + link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + /* + * Create the controls here, but mode specific limits are setup + * in the imx519_set_framing_limits() call below. + */ + imx519->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx519_ctrl_ops, + V4L2_CID_VBLANK, 0, 0xffff, 1, 0); + imx519->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx519_ctrl_ops, + V4L2_CID_HBLANK, 0, 0xffff, 1, 0); + + /* HBLANK is read-only for now, but does change with mode. */ + if (imx519->hblank) + imx519->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + imx519->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &imx519_ctrl_ops, + V4L2_CID_EXPOSURE, + IMX519_EXPOSURE_MIN, + IMX519_EXPOSURE_MAX, + IMX519_EXPOSURE_STEP, + IMX519_EXPOSURE_DEFAULT); + + v4l2_ctrl_new_std(ctrl_hdlr, &imx519_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + IMX519_ANA_GAIN_MIN, IMX519_ANA_GAIN_MAX, + IMX519_ANA_GAIN_STEP, IMX519_ANA_GAIN_DEFAULT); + + v4l2_ctrl_new_std(ctrl_hdlr, &imx519_ctrl_ops, V4L2_CID_DIGITAL_GAIN, + IMX519_DGTL_GAIN_MIN, IMX519_DGTL_GAIN_MAX, + IMX519_DGTL_GAIN_STEP, IMX519_DGTL_GAIN_DEFAULT); + + imx519->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx519_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + if (imx519->hflip) + imx519->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + + imx519->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx519_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + if (imx519->vflip) + imx519->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &imx519_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(imx519_test_pattern_menu) - 1, + 0, 0, imx519_test_pattern_menu); + for (i = 0; i < 4; i++) { + /* + * The assumption is that + * V4L2_CID_TEST_PATTERN_GREENR == V4L2_CID_TEST_PATTERN_RED + 1 + * V4L2_CID_TEST_PATTERN_BLUE == V4L2_CID_TEST_PATTERN_RED + 2 + * V4L2_CID_TEST_PATTERN_GREENB == V4L2_CID_TEST_PATTERN_RED + 3 + */ + v4l2_ctrl_new_std(ctrl_hdlr, &imx519_ctrl_ops, + V4L2_CID_TEST_PATTERN_RED + i, + IMX519_TEST_PATTERN_COLOUR_MIN, + IMX519_TEST_PATTERN_COLOUR_MAX, + IMX519_TEST_PATTERN_COLOUR_STEP, + IMX519_TEST_PATTERN_COLOUR_MAX); + /* The "Solid color" pattern is white by default */ + } + + if (ctrl_hdlr->error) { + ret = ctrl_hdlr->error; + dev_err(&client->dev, "%s control init failed (%d)\n", + __func__, ret); + goto error; + } + + ret = v4l2_fwnode_device_parse(&client->dev, &props); + if (ret) + goto error; + + ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &imx519_ctrl_ops, + &props); + if (ret) + goto error; + + imx519->sd.ctrl_handler = ctrl_hdlr; + + /* Setup exposure and frame/line length limits. */ + imx519_set_framing_limits(imx519); + + return 0; + +error: + v4l2_ctrl_handler_free(ctrl_hdlr); + mutex_destroy(&imx519->mutex); + + return ret; +} + +static void imx519_free_controls(struct imx519 *imx519) +{ + v4l2_ctrl_handler_free(imx519->sd.ctrl_handler); + mutex_destroy(&imx519->mutex); +} + +static int imx519_check_hwcfg(struct device *dev) +{ + struct fwnode_handle *endpoint; + struct v4l2_fwnode_endpoint ep_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; + int ret = -EINVAL; + + endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL); + if (!endpoint) { + dev_err(dev, "endpoint node not found\n"); + return -EINVAL; + } + + if (v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep_cfg)) { + dev_err(dev, "could not parse endpoint\n"); + goto error_out; + } + + /* Check the number of MIPI CSI2 data lanes */ + if (ep_cfg.bus.mipi_csi2.num_data_lanes != 2) { + dev_err(dev, "only 2 data lanes are currently supported\n"); + goto error_out; + } + + /* Check the link frequency set in device tree */ + if (!ep_cfg.nr_of_link_frequencies) { + dev_err(dev, "link-frequency property not found in DT\n"); + goto error_out; + } + + if (ep_cfg.nr_of_link_frequencies != 1 || + ep_cfg.link_frequencies[0] != IMX519_DEFAULT_LINK_FREQ) { + dev_err(dev, "Link frequency not supported: %lld\n", + ep_cfg.link_frequencies[0]); + goto error_out; + } + + ret = 0; + +error_out: + v4l2_fwnode_endpoint_free(&ep_cfg); + fwnode_handle_put(endpoint); + + return ret; +} + +static const struct of_device_id imx519_dt_ids[] = { + { .compatible = "sony,imx519"}, + { /* sentinel */ } +}; + +static int imx519_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct imx519 *imx519; + const struct of_device_id *match; + u32 xclk_freq; + int ret; + + imx519 = devm_kzalloc(&client->dev, sizeof(*imx519), GFP_KERNEL); + if (!imx519) + return -ENOMEM; + + v4l2_i2c_subdev_init(&imx519->sd, client, &imx519_subdev_ops); + + match = of_match_device(imx519_dt_ids, dev); + if (!match) + return -ENODEV; + + /* Check the hardware configuration in device tree */ + if (imx519_check_hwcfg(dev)) + return -EINVAL; + + /* Get system clock (xclk) */ + imx519->xclk = devm_clk_get(dev, NULL); + if (IS_ERR(imx519->xclk)) { + dev_err(dev, "failed to get xclk\n"); + return PTR_ERR(imx519->xclk); + } + + xclk_freq = clk_get_rate(imx519->xclk); + if (xclk_freq != IMX519_XCLK_FREQ) { + dev_err(dev, "xclk frequency not supported: %d Hz\n", + xclk_freq); + return -EINVAL; + } + + ret = imx519_get_regulators(imx519); + if (ret) { + dev_err(dev, "failed to get regulators\n"); + return ret; + } + + /* Request optional enable pin */ + imx519->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + + /* + * The sensor must be powered for imx519_identify_module() + * to be able to read the CHIP_ID register + */ + ret = imx519_power_on(dev); + if (ret) + return ret; + + ret = imx519_identify_module(imx519, IMX519_CHIP_ID); + if (ret) + goto error_power_off; + + /* Set default mode to max resolution */ + imx519->mode = &supported_modes_10bit[0]; + imx519->fmt_code = MEDIA_BUS_FMT_SRGGB10_1X10; + + /* Enable runtime PM and turn off the device */ + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_idle(dev); + + /* This needs the pm runtime to be registered. */ + ret = imx519_init_controls(imx519); + if (ret) + goto error_power_off; + + /* Initialize subdev */ + imx519->sd.internal_ops = &imx519_internal_ops; + imx519->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | + V4L2_SUBDEV_FL_HAS_EVENTS; + imx519->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + /* Initialize source pads */ + imx519->pad[IMAGE_PAD].flags = MEDIA_PAD_FL_SOURCE; + imx519->pad[METADATA_PAD].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&imx519->sd.entity, NUM_PADS, imx519->pad); + if (ret) { + dev_err(dev, "failed to init entity pads: %d\n", ret); + goto error_handler_free; + } + + ret = v4l2_async_register_subdev_sensor(&imx519->sd); + if (ret < 0) { + dev_err(dev, "failed to register sensor sub-device: %d\n", ret); + goto error_media_entity; + } + + return 0; + +error_media_entity: + media_entity_cleanup(&imx519->sd.entity); + +error_handler_free: + imx519_free_controls(imx519); + +error_power_off: + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + imx519_power_off(&client->dev); + + return ret; +} + +static void imx519_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx519 *imx519 = to_imx519(sd); + + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + imx519_free_controls(imx519); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) + imx519_power_off(&client->dev); + pm_runtime_set_suspended(&client->dev); +} + +MODULE_DEVICE_TABLE(of, imx519_dt_ids); + +static const struct dev_pm_ops imx519_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(imx519_suspend, imx519_resume) + SET_RUNTIME_PM_OPS(imx519_power_off, imx519_power_on, NULL) +}; + +static struct i2c_driver imx519_i2c_driver = { + .driver = { + .name = "imx519", + .of_match_table = imx519_dt_ids, + .pm = &imx519_pm_ops, + }, + .probe = imx519_probe, + .remove = imx519_remove, +}; + +module_i2c_driver(imx519_i2c_driver); + +MODULE_AUTHOR("Lee Jackson <info@arducam.com>"); +MODULE_DESCRIPTION("Sony IMX519 sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/imx708.c b/drivers/media/i2c/imx708.c new file mode 100644 index 00000000000000..a56478e31bb133 --- /dev/null +++ b/drivers/media/i2c/imx708.c @@ -0,0 +1,2116 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A V4L2 driver for Sony IMX708 cameras. + * Copyright (C) 2022, Raspberry Pi Ltd + * + * Based on Sony imx477 camera driver + * Copyright (C) 2020 Raspberry Pi Ltd + */ +#include <linux/unaligned.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-event.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-mediabus.h> + +/* + * Parameter to adjust Quad Bayer re-mosaic broken line correction + * strength, used in full-resolution mode only. Set zero to disable. + */ +static int qbc_adjust = 2; +module_param(qbc_adjust, int, 0644); +MODULE_PARM_DESC(qbc_adjust, "Quad Bayer broken line correction strength [0,2-5]"); + +#define IMX708_REG_VALUE_08BIT 1 +#define IMX708_REG_VALUE_16BIT 2 + +/* Chip ID */ +#define IMX708_REG_CHIP_ID 0x0016 +#define IMX708_CHIP_ID 0x0708 + +#define IMX708_REG_MODE_SELECT 0x0100 +#define IMX708_MODE_STANDBY 0x00 +#define IMX708_MODE_STREAMING 0x01 + +#define IMX708_REG_ORIENTATION 0x101 + +#define IMX708_INCLK_FREQ 24000000 + +/* Default initial pixel rate, will get updated for each mode. */ +#define IMX708_INITIAL_PIXEL_RATE 590000000 + +/* V_TIMING internal */ +#define IMX708_REG_FRAME_LENGTH 0x0340 +#define IMX708_FRAME_LENGTH_MAX 0xffff + +/* Long exposure multiplier */ +#define IMX708_LONG_EXP_SHIFT_MAX 7 +#define IMX708_LONG_EXP_SHIFT_REG 0x3100 + +/* Exposure control */ +#define IMX708_REG_EXPOSURE 0x0202 +#define IMX708_EXPOSURE_OFFSET 48 +#define IMX708_EXPOSURE_DEFAULT 0x640 +#define IMX708_EXPOSURE_STEP 1 +#define IMX708_EXPOSURE_MIN 1 +#define IMX708_EXPOSURE_MAX (IMX708_FRAME_LENGTH_MAX - \ + IMX708_EXPOSURE_OFFSET) + +/* Analog gain control */ +#define IMX708_REG_ANALOG_GAIN 0x0204 +#define IMX708_ANA_GAIN_MIN 112 +#define IMX708_ANA_GAIN_MAX 960 +#define IMX708_ANA_GAIN_STEP 1 +#define IMX708_ANA_GAIN_DEFAULT IMX708_ANA_GAIN_MIN + +/* Digital gain control */ +#define IMX708_REG_DIGITAL_GAIN 0x020e +#define IMX708_DGTL_GAIN_MIN 0x0100 +#define IMX708_DGTL_GAIN_MAX 0xffff +#define IMX708_DGTL_GAIN_DEFAULT 0x0100 +#define IMX708_DGTL_GAIN_STEP 1 + +/* Colour balance controls */ +#define IMX708_REG_COLOUR_BALANCE_RED 0x0b90 +#define IMX708_REG_COLOUR_BALANCE_BLUE 0x0b92 +#define IMX708_COLOUR_BALANCE_MIN 0x01 +#define IMX708_COLOUR_BALANCE_MAX 0xffff +#define IMX708_COLOUR_BALANCE_STEP 0x01 +#define IMX708_COLOUR_BALANCE_DEFAULT 0x100 + +/* Test Pattern Control */ +#define IMX708_REG_TEST_PATTERN 0x0600 +#define IMX708_TEST_PATTERN_DISABLE 0 +#define IMX708_TEST_PATTERN_SOLID_COLOR 1 +#define IMX708_TEST_PATTERN_COLOR_BARS 2 +#define IMX708_TEST_PATTERN_GREY_COLOR 3 +#define IMX708_TEST_PATTERN_PN9 4 + +/* Test pattern colour components */ +#define IMX708_REG_TEST_PATTERN_R 0x0602 +#define IMX708_REG_TEST_PATTERN_GR 0x0604 +#define IMX708_REG_TEST_PATTERN_B 0x0606 +#define IMX708_REG_TEST_PATTERN_GB 0x0608 +#define IMX708_TEST_PATTERN_COLOUR_MIN 0 +#define IMX708_TEST_PATTERN_COLOUR_MAX 0x0fff +#define IMX708_TEST_PATTERN_COLOUR_STEP 1 + +#define IMX708_REG_BASE_SPC_GAINS_L 0x7b10 +#define IMX708_REG_BASE_SPC_GAINS_R 0x7c00 + +/* HDR exposure ratio (long:med == med:short) */ +#define IMX708_HDR_EXPOSURE_RATIO 4 +#define IMX708_REG_MID_EXPOSURE 0x3116 +#define IMX708_REG_SHT_EXPOSURE 0x0224 +#define IMX708_REG_MID_ANALOG_GAIN 0x3118 +#define IMX708_REG_SHT_ANALOG_GAIN 0x0216 + +/* QBC Re-mosaic broken line correction registers */ +#define IMX708_LPF_INTENSITY_EN 0xC428 +#define IMX708_LPF_INTENSITY_ENABLED 0x00 +#define IMX708_LPF_INTENSITY_DISABLED 0x01 +#define IMX708_LPF_INTENSITY 0xC429 + +/* + * Metadata buffer holds a variety of data, all sent with the same VC/DT (0x12). + * It comprises two scanlines (of up to 5760 bytes each, for 4608 pixels) + * of embedded data, one line of PDAF data, and two lines of AE-HIST data + * (AE histograms are valid for HDR mode and empty in non-HDR modes). + */ +#define IMX708_EMBEDDED_LINE_WIDTH (5 * 5760) +#define IMX708_NUM_EMBEDDED_LINES 1 + +enum pad_types { + IMAGE_PAD, + METADATA_PAD, + NUM_PADS +}; + +/* IMX708 native and active pixel array size. */ +#define IMX708_NATIVE_WIDTH 4640U +#define IMX708_NATIVE_HEIGHT 2658U +#define IMX708_PIXEL_ARRAY_LEFT 16U +#define IMX708_PIXEL_ARRAY_TOP 24U +#define IMX708_PIXEL_ARRAY_WIDTH 4608U +#define IMX708_PIXEL_ARRAY_HEIGHT 2592U + +struct imx708_reg { + u16 address; + u8 val; +}; + +struct imx708_reg_list { + unsigned int num_of_regs; + const struct imx708_reg *regs; +}; + +/* Mode : resolution and related config&values */ +struct imx708_mode { + /* Frame width */ + unsigned int width; + + /* Frame height */ + unsigned int height; + + /* H-timing in pixels */ + unsigned int line_length_pix; + + /* Analog crop rectangle. */ + struct v4l2_rect crop; + + /* Highest possible framerate. */ + unsigned int vblank_min; + + /* Default framerate. */ + unsigned int vblank_default; + + /* Default register values */ + struct imx708_reg_list reg_list; + + /* Not all modes have the same pixel rate. */ + u64 pixel_rate; + + /* Not all modes have the same minimum exposure. */ + u32 exposure_lines_min; + + /* Not all modes have the same exposure lines step. */ + u32 exposure_lines_step; + + /* HDR flag, used for checking if the current mode is HDR */ + bool hdr; + + /* Quad Bayer Re-mosaic flag */ + bool remosaic; +}; + +/* Default PDAF pixel correction gains */ +static const u8 pdaf_gains[2][9] = { + { 0x4c, 0x4c, 0x4c, 0x46, 0x3e, 0x38, 0x35, 0x35, 0x35 }, + { 0x35, 0x35, 0x35, 0x38, 0x3e, 0x46, 0x4c, 0x4c, 0x4c } +}; + +/* Link frequency setup */ +enum { + IMX708_LINK_FREQ_450MHZ, + IMX708_LINK_FREQ_447MHZ, + IMX708_LINK_FREQ_453MHZ, +}; + +static const s64 link_freqs[] = { + [IMX708_LINK_FREQ_450MHZ] = 450000000, + [IMX708_LINK_FREQ_447MHZ] = 447000000, + [IMX708_LINK_FREQ_453MHZ] = 453000000, +}; + +/* 450MHz is the nominal "default" link frequency */ +static const struct imx708_reg link_450Mhz_regs[] = { + {0x030E, 0x01}, + {0x030F, 0x2c}, +}; + +static const struct imx708_reg link_447Mhz_regs[] = { + {0x030E, 0x01}, + {0x030F, 0x2a}, +}; + +static const struct imx708_reg link_453Mhz_regs[] = { + {0x030E, 0x01}, + {0x030F, 0x2e}, +}; + +static const struct imx708_reg_list link_freq_regs[] = { + [IMX708_LINK_FREQ_450MHZ] = { + .regs = link_450Mhz_regs, + .num_of_regs = ARRAY_SIZE(link_450Mhz_regs) + }, + [IMX708_LINK_FREQ_447MHZ] = { + .regs = link_447Mhz_regs, + .num_of_regs = ARRAY_SIZE(link_447Mhz_regs) + }, + [IMX708_LINK_FREQ_453MHZ] = { + .regs = link_453Mhz_regs, + .num_of_regs = ARRAY_SIZE(link_453Mhz_regs) + }, +}; + +static const struct imx708_reg mode_common_regs[] = { + {0x0100, 0x00}, + {0x0136, 0x18}, + {0x0137, 0x00}, + {0x33F0, 0x02}, + {0x33F1, 0x05}, + {0x3062, 0x00}, + {0x3063, 0x12}, + {0x3068, 0x00}, + {0x3069, 0x12}, + {0x306A, 0x00}, + {0x306B, 0x30}, + {0x3076, 0x00}, + {0x3077, 0x30}, + {0x3078, 0x00}, + {0x3079, 0x30}, + {0x5E54, 0x0C}, + {0x6E44, 0x00}, + {0xB0B6, 0x01}, + {0xE829, 0x00}, + {0xF001, 0x08}, + {0xF003, 0x08}, + {0xF00D, 0x10}, + {0xF00F, 0x10}, + {0xF031, 0x08}, + {0xF033, 0x08}, + {0xF03D, 0x10}, + {0xF03F, 0x10}, + {0x0112, 0x0A}, + {0x0113, 0x0A}, + {0x0114, 0x01}, + {0x0B8E, 0x01}, + {0x0B8F, 0x00}, + {0x0B94, 0x01}, + {0x0B95, 0x00}, + {0x3400, 0x01}, + {0x3478, 0x01}, + {0x3479, 0x1c}, + {0x3091, 0x01}, + {0x3092, 0x00}, + {0x3419, 0x00}, + {0xBCF1, 0x02}, + {0x3094, 0x01}, + {0x3095, 0x01}, + {0x3362, 0x00}, + {0x3363, 0x00}, + {0x3364, 0x00}, + {0x3365, 0x00}, + {0x0138, 0x01}, +}; + +/* 10-bit. */ +static const struct imx708_reg mode_4608x2592_regs[] = { + {0x0342, 0x3D}, + {0x0343, 0x20}, + {0x0340, 0x0A}, + {0x0341, 0x59}, + {0x0344, 0x00}, + {0x0345, 0x00}, + {0x0346, 0x00}, + {0x0347, 0x00}, + {0x0348, 0x11}, + {0x0349, 0xFF}, + {0x034A, 0X0A}, + {0x034B, 0x1F}, + {0x0220, 0x62}, + {0x0222, 0x01}, + {0x0900, 0x00}, + {0x0901, 0x11}, + {0x0902, 0x0A}, + {0x3200, 0x01}, + {0x3201, 0x01}, + {0x32D5, 0x01}, + {0x32D6, 0x00}, + {0x32DB, 0x01}, + {0x32DF, 0x00}, + {0x350C, 0x00}, + {0x350D, 0x00}, + {0x0408, 0x00}, + {0x0409, 0x00}, + {0x040A, 0x00}, + {0x040B, 0x00}, + {0x040C, 0x12}, + {0x040D, 0x00}, + {0x040E, 0x0A}, + {0x040F, 0x20}, + {0x034C, 0x12}, + {0x034D, 0x00}, + {0x034E, 0x0A}, + {0x034F, 0x20}, + {0x0301, 0x05}, + {0x0303, 0x02}, + {0x0305, 0x02}, + {0x0306, 0x00}, + {0x0307, 0x7C}, + {0x030B, 0x02}, + {0x030D, 0x04}, + {0x0310, 0x01}, + {0x3CA0, 0x00}, + {0x3CA1, 0x64}, + {0x3CA4, 0x00}, + {0x3CA5, 0x00}, + {0x3CA6, 0x00}, + {0x3CA7, 0x00}, + {0x3CAA, 0x00}, + {0x3CAB, 0x00}, + {0x3CB8, 0x00}, + {0x3CB9, 0x08}, + {0x3CBA, 0x00}, + {0x3CBB, 0x00}, + {0x3CBC, 0x00}, + {0x3CBD, 0x3C}, + {0x3CBE, 0x00}, + {0x3CBF, 0x00}, + {0x0202, 0x0A}, + {0x0203, 0x29}, + {0x0224, 0x01}, + {0x0225, 0xF4}, + {0x3116, 0x01}, + {0x3117, 0xF4}, + {0x0204, 0x00}, + {0x0205, 0x00}, + {0x0216, 0x00}, + {0x0217, 0x00}, + {0x0218, 0x01}, + {0x0219, 0x00}, + {0x020E, 0x01}, + {0x020F, 0x00}, + {0x3118, 0x00}, + {0x3119, 0x00}, + {0x311A, 0x01}, + {0x311B, 0x00}, + {0x341a, 0x00}, + {0x341b, 0x00}, + {0x341c, 0x00}, + {0x341d, 0x00}, + {0x341e, 0x01}, + {0x341f, 0x20}, + {0x3420, 0x00}, + {0x3421, 0xd8}, + {0x3366, 0x00}, + {0x3367, 0x00}, + {0x3368, 0x00}, + {0x3369, 0x00}, +}; + +static const struct imx708_reg mode_2x2binned_regs[] = { + {0x0342, 0x1E}, + {0x0343, 0x90}, + {0x0340, 0x05}, + {0x0341, 0x38}, + {0x0344, 0x00}, + {0x0345, 0x00}, + {0x0346, 0x00}, + {0x0347, 0x00}, + {0x0348, 0x11}, + {0x0349, 0xFF}, + {0x034A, 0X0A}, + {0x034B, 0x1F}, + {0x0220, 0x62}, + {0x0222, 0x01}, + {0x0900, 0x01}, + {0x0901, 0x22}, + {0x0902, 0x08}, + {0x3200, 0x41}, + {0x3201, 0x41}, + {0x32D5, 0x00}, + {0x32D6, 0x00}, + {0x32DB, 0x01}, + {0x32DF, 0x00}, + {0x350C, 0x00}, + {0x350D, 0x00}, + {0x0408, 0x00}, + {0x0409, 0x00}, + {0x040A, 0x00}, + {0x040B, 0x00}, + {0x040C, 0x09}, + {0x040D, 0x00}, + {0x040E, 0x05}, + {0x040F, 0x10}, + {0x034C, 0x09}, + {0x034D, 0x00}, + {0x034E, 0x05}, + {0x034F, 0x10}, + {0x0301, 0x05}, + {0x0303, 0x02}, + {0x0305, 0x02}, + {0x0306, 0x00}, + {0x0307, 0x7A}, + {0x030B, 0x02}, + {0x030D, 0x04}, + {0x0310, 0x01}, + {0x3CA0, 0x00}, + {0x3CA1, 0x3C}, + {0x3CA4, 0x00}, + {0x3CA5, 0x3C}, + {0x3CA6, 0x00}, + {0x3CA7, 0x00}, + {0x3CAA, 0x00}, + {0x3CAB, 0x00}, + {0x3CB8, 0x00}, + {0x3CB9, 0x1C}, + {0x3CBA, 0x00}, + {0x3CBB, 0x08}, + {0x3CBC, 0x00}, + {0x3CBD, 0x1E}, + {0x3CBE, 0x00}, + {0x3CBF, 0x0A}, + {0x0202, 0x05}, + {0x0203, 0x08}, + {0x0224, 0x01}, + {0x0225, 0xF4}, + {0x3116, 0x01}, + {0x3117, 0xF4}, + {0x0204, 0x00}, + {0x0205, 0x70}, + {0x0216, 0x00}, + {0x0217, 0x70}, + {0x0218, 0x01}, + {0x0219, 0x00}, + {0x020E, 0x01}, + {0x020F, 0x00}, + {0x3118, 0x00}, + {0x3119, 0x70}, + {0x311A, 0x01}, + {0x311B, 0x00}, + {0x341a, 0x00}, + {0x341b, 0x00}, + {0x341c, 0x00}, + {0x341d, 0x00}, + {0x341e, 0x00}, + {0x341f, 0x90}, + {0x3420, 0x00}, + {0x3421, 0x6c}, + {0x3366, 0x00}, + {0x3367, 0x00}, + {0x3368, 0x00}, + {0x3369, 0x00}, +}; + +static const struct imx708_reg mode_2x2binned_720p_regs[] = { + {0x0342, 0x14}, + {0x0343, 0x60}, + {0x0340, 0x04}, + {0x0341, 0xB6}, + {0x0344, 0x03}, + {0x0345, 0x00}, + {0x0346, 0x01}, + {0x0347, 0xB0}, + {0x0348, 0x0E}, + {0x0349, 0xFF}, + {0x034A, 0x08}, + {0x034B, 0x6F}, + {0x0220, 0x62}, + {0x0222, 0x01}, + {0x0900, 0x01}, + {0x0901, 0x22}, + {0x0902, 0x08}, + {0x3200, 0x41}, + {0x3201, 0x41}, + {0x32D5, 0x00}, + {0x32D6, 0x00}, + {0x32DB, 0x01}, + {0x32DF, 0x01}, + {0x350C, 0x00}, + {0x350D, 0x00}, + {0x0408, 0x00}, + {0x0409, 0x00}, + {0x040A, 0x00}, + {0x040B, 0x00}, + {0x040C, 0x06}, + {0x040D, 0x00}, + {0x040E, 0x03}, + {0x040F, 0x60}, + {0x034C, 0x06}, + {0x034D, 0x00}, + {0x034E, 0x03}, + {0x034F, 0x60}, + {0x0301, 0x05}, + {0x0303, 0x02}, + {0x0305, 0x02}, + {0x0306, 0x00}, + {0x0307, 0x76}, + {0x030B, 0x02}, + {0x030D, 0x04}, + {0x0310, 0x01}, + {0x3CA0, 0x00}, + {0x3CA1, 0x3C}, + {0x3CA4, 0x01}, + {0x3CA5, 0x5E}, + {0x3CA6, 0x00}, + {0x3CA7, 0x00}, + {0x3CAA, 0x00}, + {0x3CAB, 0x00}, + {0x3CB8, 0x00}, + {0x3CB9, 0x0C}, + {0x3CBA, 0x00}, + {0x3CBB, 0x04}, + {0x3CBC, 0x00}, + {0x3CBD, 0x1E}, + {0x3CBE, 0x00}, + {0x3CBF, 0x05}, + {0x0202, 0x04}, + {0x0203, 0x86}, + {0x0224, 0x01}, + {0x0225, 0xF4}, + {0x3116, 0x01}, + {0x3117, 0xF4}, + {0x0204, 0x00}, + {0x0205, 0x70}, + {0x0216, 0x00}, + {0x0217, 0x70}, + {0x0218, 0x01}, + {0x0219, 0x00}, + {0x020E, 0x01}, + {0x020F, 0x00}, + {0x3118, 0x00}, + {0x3119, 0x70}, + {0x311A, 0x01}, + {0x311B, 0x00}, + {0x341a, 0x00}, + {0x341b, 0x00}, + {0x341c, 0x00}, + {0x341d, 0x00}, + {0x341e, 0x00}, + {0x341f, 0x60}, + {0x3420, 0x00}, + {0x3421, 0x48}, + {0x3366, 0x00}, + {0x3367, 0x00}, + {0x3368, 0x00}, + {0x3369, 0x00}, +}; + +static const struct imx708_reg mode_hdr_regs[] = { + {0x0342, 0x14}, + {0x0343, 0x60}, + {0x0340, 0x0A}, + {0x0341, 0x5B}, + {0x0344, 0x00}, + {0x0345, 0x00}, + {0x0346, 0x00}, + {0x0347, 0x00}, + {0x0348, 0x11}, + {0x0349, 0xFF}, + {0x034A, 0X0A}, + {0x034B, 0x1F}, + {0x0220, 0x01}, + {0x0222, IMX708_HDR_EXPOSURE_RATIO}, + {0x0900, 0x00}, + {0x0901, 0x11}, + {0x0902, 0x0A}, + {0x3200, 0x01}, + {0x3201, 0x01}, + {0x32D5, 0x00}, + {0x32D6, 0x00}, + {0x32DB, 0x01}, + {0x32DF, 0x00}, + {0x350C, 0x00}, + {0x350D, 0x00}, + {0x0408, 0x00}, + {0x0409, 0x00}, + {0x040A, 0x00}, + {0x040B, 0x00}, + {0x040C, 0x09}, + {0x040D, 0x00}, + {0x040E, 0x05}, + {0x040F, 0x10}, + {0x034C, 0x09}, + {0x034D, 0x00}, + {0x034E, 0x05}, + {0x034F, 0x10}, + {0x0301, 0x05}, + {0x0303, 0x02}, + {0x0305, 0x02}, + {0x0306, 0x00}, + {0x0307, 0xA2}, + {0x030B, 0x02}, + {0x030D, 0x04}, + {0x0310, 0x01}, + {0x3CA0, 0x00}, + {0x3CA1, 0x00}, + {0x3CA4, 0x00}, + {0x3CA5, 0x00}, + {0x3CA6, 0x00}, + {0x3CA7, 0x28}, + {0x3CAA, 0x00}, + {0x3CAB, 0x00}, + {0x3CB8, 0x00}, + {0x3CB9, 0x30}, + {0x3CBA, 0x00}, + {0x3CBB, 0x00}, + {0x3CBC, 0x00}, + {0x3CBD, 0x32}, + {0x3CBE, 0x00}, + {0x3CBF, 0x00}, + {0x0202, 0x0A}, + {0x0203, 0x2B}, + {0x0224, 0x0A}, + {0x0225, 0x2B}, + {0x3116, 0x0A}, + {0x3117, 0x2B}, + {0x0204, 0x00}, + {0x0205, 0x00}, + {0x0216, 0x00}, + {0x0217, 0x00}, + {0x0218, 0x01}, + {0x0219, 0x00}, + {0x020E, 0x01}, + {0x020F, 0x00}, + {0x3118, 0x00}, + {0x3119, 0x00}, + {0x311A, 0x01}, + {0x311B, 0x00}, + {0x341a, 0x00}, + {0x341b, 0x00}, + {0x341c, 0x00}, + {0x341d, 0x00}, + {0x341e, 0x00}, + {0x341f, 0x90}, + {0x3420, 0x00}, + {0x3421, 0x6c}, + {0x3360, 0x01}, + {0x3361, 0x01}, + {0x3366, 0x09}, + {0x3367, 0x00}, + {0x3368, 0x05}, + {0x3369, 0x10}, +}; + +/* Mode configs. Keep separate lists for when HDR is enabled or not. */ +static const struct imx708_mode supported_modes_10bit_no_hdr[] = { + { + /* Full resolution. */ + .width = 4608, + .height = 2592, + .line_length_pix = 0x3d20, + .crop = { + .left = IMX708_PIXEL_ARRAY_LEFT, + .top = IMX708_PIXEL_ARRAY_TOP, + .width = 4608, + .height = 2592, + }, + .vblank_min = 58, + .vblank_default = 58, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_4608x2592_regs), + .regs = mode_4608x2592_regs, + }, + .pixel_rate = 595200000, + .exposure_lines_min = 8, + .exposure_lines_step = 1, + .hdr = false, + .remosaic = true + }, + { + /* regular 2x2 binned. */ + .width = 2304, + .height = 1296, + .line_length_pix = 0x1e90, + .crop = { + .left = IMX708_PIXEL_ARRAY_LEFT, + .top = IMX708_PIXEL_ARRAY_TOP, + .width = 4608, + .height = 2592, + }, + .vblank_min = 40, + .vblank_default = 1198, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_2x2binned_regs), + .regs = mode_2x2binned_regs, + }, + .pixel_rate = 585600000, + .exposure_lines_min = 4, + .exposure_lines_step = 2, + .hdr = false, + .remosaic = false + }, + { + /* 2x2 binned and cropped for 720p. */ + .width = 1536, + .height = 864, + .line_length_pix = 0x1460, + .crop = { + .left = IMX708_PIXEL_ARRAY_LEFT + 768, + .top = IMX708_PIXEL_ARRAY_TOP + 432, + .width = 3072, + .height = 1728, + }, + .vblank_min = 40, + .vblank_default = 2755, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_2x2binned_720p_regs), + .regs = mode_2x2binned_720p_regs, + }, + .pixel_rate = 566400000, + .exposure_lines_min = 4, + .exposure_lines_step = 2, + .hdr = false, + .remosaic = false + }, +}; + +static const struct imx708_mode supported_modes_10bit_hdr[] = { + { + /* There's only one HDR mode, which is 2x2 downscaled */ + .width = 2304, + .height = 1296, + .line_length_pix = 0x1460, + .crop = { + .left = IMX708_PIXEL_ARRAY_LEFT, + .top = IMX708_PIXEL_ARRAY_TOP, + .width = 4608, + .height = 2592, + }, + .vblank_min = 3673, + .vblank_default = 3673, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_hdr_regs), + .regs = mode_hdr_regs, + }, + .pixel_rate = 777600000, + .exposure_lines_min = 8 * IMX708_HDR_EXPOSURE_RATIO * IMX708_HDR_EXPOSURE_RATIO, + .exposure_lines_step = 2 * IMX708_HDR_EXPOSURE_RATIO * IMX708_HDR_EXPOSURE_RATIO, + .hdr = true, + .remosaic = false + } +}; + +/* + * The supported formats. + * This table MUST contain 4 entries per format, to cover the various flip + * combinations in the order + * - no flip + * - h flip + * - v flip + * - h&v flips + */ +static const u32 codes[] = { + /* 10-bit modes. */ + MEDIA_BUS_FMT_SRGGB10_1X10, + MEDIA_BUS_FMT_SGRBG10_1X10, + MEDIA_BUS_FMT_SGBRG10_1X10, + MEDIA_BUS_FMT_SBGGR10_1X10, +}; + +static const char * const imx708_test_pattern_menu[] = { + "Disabled", + "Color Bars", + "Solid Color", + "Grey Color Bars", + "PN9" +}; + +static const int imx708_test_pattern_val[] = { + IMX708_TEST_PATTERN_DISABLE, + IMX708_TEST_PATTERN_COLOR_BARS, + IMX708_TEST_PATTERN_SOLID_COLOR, + IMX708_TEST_PATTERN_GREY_COLOR, + IMX708_TEST_PATTERN_PN9, +}; + +/* regulator supplies */ +static const char * const imx708_supply_name[] = { + /* Supplies can be enabled in any order */ + "vana1", /* Analog1 (2.8V) supply */ + "vana2", /* Analog2 (1.8V) supply */ + "vdig", /* Digital Core (1.1V) supply */ + "vddl", /* IF (1.8V) supply */ +}; + +/* + * Initialisation delay between XCLR low->high and the moment when the sensor + * can start capture (i.e. can leave software standby), given by T7 in the + * datasheet is 8ms. This does include I2C setup time as well. + * + * Note, that delay between XCLR low->high and reading the CCI ID register (T6 + * in the datasheet) is much smaller - 600us. + */ +#define IMX708_XCLR_MIN_DELAY_US 8000 +#define IMX708_XCLR_DELAY_RANGE_US 1000 + +struct imx708 { + struct v4l2_subdev sd; + struct media_pad pad[NUM_PADS]; + + struct v4l2_mbus_framefmt fmt; + + struct clk *inclk; + u32 inclk_freq; + + struct gpio_desc *reset_gpio; + struct regulator_bulk_data supplies[ARRAY_SIZE(imx708_supply_name)]; + + struct v4l2_ctrl_handler ctrl_handler; + /* V4L2 Controls */ + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *hdr_mode; + struct v4l2_ctrl *link_freq; + struct { + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vflip; + }; + + /* Current mode */ + const struct imx708_mode *mode; + + /* + * Mutex for serialized access: + * Protect sensor module set pad format and start/stop streaming safely. + */ + struct mutex mutex; + + /* Streaming on/off */ + bool streaming; + + /* Rewrite common registers on stream on? */ + bool common_regs_written; + + /* Current long exposure factor in use. Set through V4L2_CID_VBLANK */ + unsigned int long_exp_shift; + + unsigned int link_freq_idx; +}; + +static inline struct imx708 *to_imx708(struct v4l2_subdev *_sd) +{ + return container_of(_sd, struct imx708, sd); +} + +static inline void get_mode_table(unsigned int code, + const struct imx708_mode **mode_list, + unsigned int *num_modes, + bool hdr_enable) +{ + switch (code) { + /* 10-bit */ + case MEDIA_BUS_FMT_SRGGB10_1X10: + case MEDIA_BUS_FMT_SGRBG10_1X10: + case MEDIA_BUS_FMT_SGBRG10_1X10: + case MEDIA_BUS_FMT_SBGGR10_1X10: + if (hdr_enable) { + *mode_list = supported_modes_10bit_hdr; + *num_modes = ARRAY_SIZE(supported_modes_10bit_hdr); + } else { + *mode_list = supported_modes_10bit_no_hdr; + *num_modes = ARRAY_SIZE(supported_modes_10bit_no_hdr); + } + break; + default: + *mode_list = NULL; + *num_modes = 0; + } +} + +/* Read registers up to 2 at a time */ +static int imx708_read_reg(struct imx708 *imx708, u16 reg, u32 len, u32 *val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); + struct i2c_msg msgs[2]; + u8 addr_buf[2] = { reg >> 8, reg & 0xff }; + u8 data_buf[4] = { 0, }; + int ret; + + if (len > 4) + return -EINVAL; + + /* Write register address */ + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = ARRAY_SIZE(addr_buf); + msgs[0].buf = addr_buf; + + /* Read data from register */ + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = &data_buf[4 - len]; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + *val = get_unaligned_be32(data_buf); + + return 0; +} + +/* Write registers up to 2 at a time */ +static int imx708_write_reg(struct imx708 *imx708, u16 reg, u32 len, u32 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); + u8 buf[6]; + + if (len > 4) + return -EINVAL; + + put_unaligned_be16(reg, buf); + put_unaligned_be32(val << (8 * (4 - len)), buf + 2); + if (i2c_master_send(client, buf, len + 2) != len + 2) + return -EIO; + + return 0; +} + +/* Write a list of registers */ +static int imx708_write_regs(struct imx708 *imx708, + const struct imx708_reg *regs, u32 len) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); + unsigned int i; + + for (i = 0; i < len; i++) { + int ret; + + ret = imx708_write_reg(imx708, regs[i].address, 1, regs[i].val); + if (ret) { + dev_err_ratelimited(&client->dev, + "Failed to write reg 0x%4.4x. error = %d\n", + regs[i].address, ret); + + return ret; + } + } + + return 0; +} + +/* Get bayer order based on flip setting. */ +static u32 imx708_get_format_code(struct imx708 *imx708) +{ + unsigned int i; + + lockdep_assert_held(&imx708->mutex); + + i = (imx708->vflip->val ? 2 : 0) | + (imx708->hflip->val ? 1 : 0); + + return codes[i]; +} + +static void imx708_set_default_format(struct imx708 *imx708) +{ + struct v4l2_mbus_framefmt *fmt = &imx708->fmt; + + /* Set default mode to max resolution */ + imx708->mode = &supported_modes_10bit_no_hdr[0]; + + /* fmt->code not set as it will always be computed based on flips */ + fmt->colorspace = V4L2_COLORSPACE_RAW; + fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace); + fmt->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true, + fmt->colorspace, + fmt->ycbcr_enc); + fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace); + fmt->width = imx708->mode->width; + fmt->height = imx708->mode->height; + fmt->field = V4L2_FIELD_NONE; +} + +static int imx708_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct imx708 *imx708 = to_imx708(sd); + struct v4l2_mbus_framefmt *try_fmt_img = + v4l2_subdev_state_get_format(fh->state, IMAGE_PAD); + struct v4l2_mbus_framefmt *try_fmt_meta = + v4l2_subdev_state_get_format(fh->state, METADATA_PAD); + struct v4l2_rect *try_crop; + + mutex_lock(&imx708->mutex); + + /* Initialize try_fmt for the image pad */ + if (imx708->hdr_mode->val) { + try_fmt_img->width = supported_modes_10bit_hdr[0].width; + try_fmt_img->height = supported_modes_10bit_hdr[0].height; + } else { + try_fmt_img->width = supported_modes_10bit_no_hdr[0].width; + try_fmt_img->height = supported_modes_10bit_no_hdr[0].height; + } + try_fmt_img->code = imx708_get_format_code(imx708); + try_fmt_img->field = V4L2_FIELD_NONE; + + /* Initialize try_fmt for the embedded metadata pad */ + try_fmt_meta->width = IMX708_EMBEDDED_LINE_WIDTH; + try_fmt_meta->height = IMX708_NUM_EMBEDDED_LINES; + try_fmt_meta->code = MEDIA_BUS_FMT_SENSOR_DATA; + try_fmt_meta->field = V4L2_FIELD_NONE; + + /* Initialize try_crop */ + try_crop = v4l2_subdev_state_get_crop(fh->state, IMAGE_PAD); + try_crop->left = IMX708_PIXEL_ARRAY_LEFT; + try_crop->top = IMX708_PIXEL_ARRAY_TOP; + try_crop->width = IMX708_PIXEL_ARRAY_WIDTH; + try_crop->height = IMX708_PIXEL_ARRAY_HEIGHT; + + mutex_unlock(&imx708->mutex); + + return 0; +} + +static int imx708_set_exposure(struct imx708 *imx708, unsigned int val) +{ + val = max(val, imx708->mode->exposure_lines_min); + val -= val % imx708->mode->exposure_lines_step; + + /* + * In HDR mode this will set the longest exposure. The sensor + * will automatically divide the medium and short ones by 4,16. + */ + return imx708_write_reg(imx708, IMX708_REG_EXPOSURE, + IMX708_REG_VALUE_16BIT, + val >> imx708->long_exp_shift); +} + +static void imx708_adjust_exposure_range(struct imx708 *imx708, + struct v4l2_ctrl *ctrl) +{ + int exposure_max, exposure_def; + + /* Honour the VBLANK limits when setting exposure. */ + exposure_max = imx708->mode->height + imx708->vblank->val - + IMX708_EXPOSURE_OFFSET; + exposure_def = min(exposure_max, imx708->exposure->val); + __v4l2_ctrl_modify_range(imx708->exposure, imx708->exposure->minimum, + exposure_max, imx708->exposure->step, + exposure_def); +} + +static int imx708_set_analogue_gain(struct imx708 *imx708, unsigned int val) +{ + int ret; + + /* + * In HDR mode this will set the gain for the longest exposure, + * and by default the sensor uses the same gain for all of them. + */ + ret = imx708_write_reg(imx708, IMX708_REG_ANALOG_GAIN, + IMX708_REG_VALUE_16BIT, val); + + return ret; +} + +static int imx708_set_frame_length(struct imx708 *imx708, unsigned int val) +{ + int ret; + + imx708->long_exp_shift = 0; + + while (val > IMX708_FRAME_LENGTH_MAX) { + imx708->long_exp_shift++; + val >>= 1; + } + + ret = imx708_write_reg(imx708, IMX708_REG_FRAME_LENGTH, + IMX708_REG_VALUE_16BIT, val); + if (ret) + return ret; + + return imx708_write_reg(imx708, IMX708_LONG_EXP_SHIFT_REG, + IMX708_REG_VALUE_08BIT, imx708->long_exp_shift); +} + +static void imx708_set_framing_limits(struct imx708 *imx708) +{ + const struct imx708_mode *mode = imx708->mode; + unsigned int hblank; + + __v4l2_ctrl_modify_range(imx708->pixel_rate, + mode->pixel_rate, mode->pixel_rate, + 1, mode->pixel_rate); + + /* Update limits and set FPS to default */ + __v4l2_ctrl_modify_range(imx708->vblank, mode->vblank_min, + ((1 << IMX708_LONG_EXP_SHIFT_MAX) * + IMX708_FRAME_LENGTH_MAX) - mode->height, + 1, mode->vblank_default); + + /* + * Currently PPL is fixed to the mode specified value, so hblank + * depends on mode->width only, and is not changeable in any + * way other than changing the mode. + */ + hblank = mode->line_length_pix - mode->width; + __v4l2_ctrl_modify_range(imx708->hblank, hblank, hblank, 1, hblank); +} + +static int imx708_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct imx708 *imx708 = + container_of(ctrl->handler, struct imx708, ctrl_handler); + struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); + const struct imx708_mode *mode_list; + unsigned int code, num_modes; + int ret = 0; + + switch (ctrl->id) { + case V4L2_CID_VBLANK: + /* + * The VBLANK control may change the limits of usable exposure, + * so check and adjust if necessary. + */ + imx708_adjust_exposure_range(imx708, ctrl); + break; + + case V4L2_CID_WIDE_DYNAMIC_RANGE: + /* + * The WIDE_DYNAMIC_RANGE control can also be applied immediately + * as it doesn't set any registers. Don't do anything if the mode + * already matches. + */ + if (imx708->mode && imx708->mode->hdr != ctrl->val) { + code = imx708_get_format_code(imx708); + get_mode_table(code, &mode_list, &num_modes, ctrl->val); + imx708->mode = v4l2_find_nearest_size(mode_list, + num_modes, + width, height, + imx708->mode->width, + imx708->mode->height); + imx708_set_framing_limits(imx708); + } + break; + } + + /* + * Applying V4L2 control value only happens + * when power is up for streaming + */ + if (pm_runtime_get_if_in_use(&client->dev) == 0) + return 0; + + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + imx708_set_analogue_gain(imx708, ctrl->val); + break; + case V4L2_CID_EXPOSURE: + ret = imx708_set_exposure(imx708, ctrl->val); + break; + case V4L2_CID_DIGITAL_GAIN: + ret = imx708_write_reg(imx708, IMX708_REG_DIGITAL_GAIN, + IMX708_REG_VALUE_16BIT, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN: + ret = imx708_write_reg(imx708, IMX708_REG_TEST_PATTERN, + IMX708_REG_VALUE_16BIT, + imx708_test_pattern_val[ctrl->val]); + break; + case V4L2_CID_TEST_PATTERN_RED: + ret = imx708_write_reg(imx708, IMX708_REG_TEST_PATTERN_R, + IMX708_REG_VALUE_16BIT, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN_GREENR: + ret = imx708_write_reg(imx708, IMX708_REG_TEST_PATTERN_GR, + IMX708_REG_VALUE_16BIT, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN_BLUE: + ret = imx708_write_reg(imx708, IMX708_REG_TEST_PATTERN_B, + IMX708_REG_VALUE_16BIT, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN_GREENB: + ret = imx708_write_reg(imx708, IMX708_REG_TEST_PATTERN_GB, + IMX708_REG_VALUE_16BIT, ctrl->val); + break; + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: + ret = imx708_write_reg(imx708, IMX708_REG_ORIENTATION, 1, + imx708->hflip->val | + imx708->vflip->val << 1); + break; + case V4L2_CID_VBLANK: + ret = imx708_set_frame_length(imx708, + imx708->mode->height + ctrl->val); + break; + case V4L2_CID_NOTIFY_GAINS: + ret = imx708_write_reg(imx708, IMX708_REG_COLOUR_BALANCE_BLUE, + IMX708_REG_VALUE_16BIT, + ctrl->p_new.p_u32[0]); + if (ret) + break; + ret = imx708_write_reg(imx708, IMX708_REG_COLOUR_BALANCE_RED, + IMX708_REG_VALUE_16BIT, + ctrl->p_new.p_u32[3]); + break; + case V4L2_CID_WIDE_DYNAMIC_RANGE: + /* Already handled above. */ + break; + default: + dev_info(&client->dev, + "ctrl(id:0x%x,val:0x%x) is not handled\n", + ctrl->id, ctrl->val); + ret = -EINVAL; + break; + } + + pm_runtime_put(&client->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops imx708_ctrl_ops = { + .s_ctrl = imx708_set_ctrl, +}; + +static int imx708_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct imx708 *imx708 = to_imx708(sd); + + if (code->pad >= NUM_PADS) + return -EINVAL; + + if (code->pad == IMAGE_PAD) { + if (code->index >= (ARRAY_SIZE(codes) / 4)) + return -EINVAL; + + code->code = imx708_get_format_code(imx708); + } else { + if (code->index > 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_SENSOR_DATA; + } + + return 0; +} + +static int imx708_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct imx708 *imx708 = to_imx708(sd); + + if (fse->pad >= NUM_PADS) + return -EINVAL; + + if (fse->pad == IMAGE_PAD) { + const struct imx708_mode *mode_list; + unsigned int num_modes; + + get_mode_table(fse->code, &mode_list, &num_modes, + imx708->hdr_mode->val); + + if (fse->index >= num_modes) + return -EINVAL; + + if (fse->code != imx708_get_format_code(imx708)) + return -EINVAL; + + fse->min_width = mode_list[fse->index].width; + fse->max_width = fse->min_width; + fse->min_height = mode_list[fse->index].height; + fse->max_height = fse->min_height; + } else { + if (fse->code != MEDIA_BUS_FMT_SENSOR_DATA || fse->index > 0) + return -EINVAL; + + fse->min_width = IMX708_EMBEDDED_LINE_WIDTH; + fse->max_width = fse->min_width; + fse->min_height = IMX708_NUM_EMBEDDED_LINES; + fse->max_height = fse->min_height; + } + + return 0; +} + +static void imx708_reset_colorspace(struct v4l2_mbus_framefmt *fmt) +{ + fmt->colorspace = V4L2_COLORSPACE_RAW; + fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace); + fmt->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true, + fmt->colorspace, + fmt->ycbcr_enc); + fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace); +} + +static void imx708_update_image_pad_format(struct imx708 *imx708, + const struct imx708_mode *mode, + struct v4l2_subdev_format *fmt) +{ + fmt->format.width = mode->width; + fmt->format.height = mode->height; + fmt->format.field = V4L2_FIELD_NONE; + imx708_reset_colorspace(&fmt->format); +} + +static void imx708_update_metadata_pad_format(struct v4l2_subdev_format *fmt) +{ + fmt->format.width = IMX708_EMBEDDED_LINE_WIDTH; + fmt->format.height = IMX708_NUM_EMBEDDED_LINES; + fmt->format.code = MEDIA_BUS_FMT_SENSOR_DATA; + fmt->format.field = V4L2_FIELD_NONE; +} + +static int imx708_get_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct imx708 *imx708 = to_imx708(sd); + + if (fmt->pad >= NUM_PADS) + return -EINVAL; + + mutex_lock(&imx708->mutex); + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + struct v4l2_mbus_framefmt *try_fmt = + v4l2_subdev_state_get_format(sd_state, + fmt->pad); + /* update the code which could change due to vflip or hflip */ + try_fmt->code = fmt->pad == IMAGE_PAD ? + imx708_get_format_code(imx708) : + MEDIA_BUS_FMT_SENSOR_DATA; + fmt->format = *try_fmt; + } else { + if (fmt->pad == IMAGE_PAD) { + imx708_update_image_pad_format(imx708, imx708->mode, + fmt); + fmt->format.code = imx708_get_format_code(imx708); + } else { + imx708_update_metadata_pad_format(fmt); + } + } + + mutex_unlock(&imx708->mutex); + return 0; +} + +static int imx708_set_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct v4l2_mbus_framefmt *framefmt; + const struct imx708_mode *mode; + struct imx708 *imx708 = to_imx708(sd); + + if (fmt->pad >= NUM_PADS) + return -EINVAL; + + mutex_lock(&imx708->mutex); + + if (fmt->pad == IMAGE_PAD) { + const struct imx708_mode *mode_list; + unsigned int num_modes; + + /* Bayer order varies with flips */ + fmt->format.code = imx708_get_format_code(imx708); + + get_mode_table(fmt->format.code, &mode_list, &num_modes, + imx708->hdr_mode->val); + + mode = v4l2_find_nearest_size(mode_list, + num_modes, + width, height, + fmt->format.width, + fmt->format.height); + imx708_update_image_pad_format(imx708, mode, fmt); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + framefmt = v4l2_subdev_state_get_format(sd_state, + fmt->pad); + *framefmt = fmt->format; + } else { + imx708->mode = mode; + imx708_set_framing_limits(imx708); + } + } else { + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + framefmt = v4l2_subdev_state_get_format(sd_state, + fmt->pad); + *framefmt = fmt->format; + } else { + /* Only one embedded data mode is supported */ + imx708_update_metadata_pad_format(fmt); + } + } + + mutex_unlock(&imx708->mutex); + + return 0; +} + +static const struct v4l2_rect * +__imx708_get_pad_crop(struct imx708 *imx708, struct v4l2_subdev_state *sd_state, + unsigned int pad, enum v4l2_subdev_format_whence which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_state_get_crop(sd_state, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &imx708->mode->crop; + } + + return NULL; +} + +static int imx708_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + switch (sel->target) { + case V4L2_SEL_TGT_CROP: { + struct imx708 *imx708 = to_imx708(sd); + + mutex_lock(&imx708->mutex); + sel->r = *__imx708_get_pad_crop(imx708, sd_state, sel->pad, + sel->which); + mutex_unlock(&imx708->mutex); + + return 0; + } + + case V4L2_SEL_TGT_NATIVE_SIZE: + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = IMX708_NATIVE_WIDTH; + sel->r.height = IMX708_NATIVE_HEIGHT; + + return 0; + + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.left = IMX708_PIXEL_ARRAY_LEFT; + sel->r.top = IMX708_PIXEL_ARRAY_TOP; + sel->r.width = IMX708_PIXEL_ARRAY_WIDTH; + sel->r.height = IMX708_PIXEL_ARRAY_HEIGHT; + + return 0; + } + + return -EINVAL; +} + +/* Start streaming */ +static int imx708_start_streaming(struct imx708 *imx708) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); + const struct imx708_reg_list *reg_list, *freq_regs; + int i, ret; + u32 val; + + if (!imx708->common_regs_written) { + ret = imx708_write_regs(imx708, mode_common_regs, + ARRAY_SIZE(mode_common_regs)); + if (ret) { + dev_err(&client->dev, "%s failed to set common settings\n", + __func__); + return ret; + } + + ret = imx708_read_reg(imx708, IMX708_REG_BASE_SPC_GAINS_L, + IMX708_REG_VALUE_08BIT, &val); + if (ret == 0 && val == 0x40) { + for (i = 0; i < 54 && ret == 0; i++) { + ret = imx708_write_reg(imx708, + IMX708_REG_BASE_SPC_GAINS_L + i, + IMX708_REG_VALUE_08BIT, + pdaf_gains[0][i % 9]); + } + for (i = 0; i < 54 && ret == 0; i++) { + ret = imx708_write_reg(imx708, + IMX708_REG_BASE_SPC_GAINS_R + i, + IMX708_REG_VALUE_08BIT, + pdaf_gains[1][i % 9]); + } + } + if (ret) { + dev_err(&client->dev, "%s failed to set PDAF gains\n", + __func__); + return ret; + } + + imx708->common_regs_written = true; + } + + /* Apply default values of current mode */ + reg_list = &imx708->mode->reg_list; + ret = imx708_write_regs(imx708, reg_list->regs, reg_list->num_of_regs); + if (ret) { + dev_err(&client->dev, "%s failed to set mode\n", __func__); + return ret; + } + + /* Update the link frequency registers */ + freq_regs = &link_freq_regs[imx708->link_freq_idx]; + ret = imx708_write_regs(imx708, freq_regs->regs, + freq_regs->num_of_regs); + if (ret) { + dev_err(&client->dev, "%s failed to set link frequency registers\n", + __func__); + return ret; + } + + /* Quad Bayer re-mosaic adjustments (for full-resolution mode only) */ + if (imx708->mode->remosaic && qbc_adjust > 0) { + imx708_write_reg(imx708, IMX708_LPF_INTENSITY, + IMX708_REG_VALUE_08BIT, qbc_adjust); + imx708_write_reg(imx708, + IMX708_LPF_INTENSITY_EN, + IMX708_REG_VALUE_08BIT, + IMX708_LPF_INTENSITY_ENABLED); + } else { + imx708_write_reg(imx708, + IMX708_LPF_INTENSITY_EN, + IMX708_REG_VALUE_08BIT, + IMX708_LPF_INTENSITY_DISABLED); + } + + /* Apply customized values from user */ + ret = __v4l2_ctrl_handler_setup(imx708->sd.ctrl_handler); + if (ret) + return ret; + + /* set stream on register */ + return imx708_write_reg(imx708, IMX708_REG_MODE_SELECT, + IMX708_REG_VALUE_08BIT, IMX708_MODE_STREAMING); +} + +/* Stop streaming */ +static void imx708_stop_streaming(struct imx708 *imx708) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); + int ret; + + /* set stream off register */ + ret = imx708_write_reg(imx708, IMX708_REG_MODE_SELECT, + IMX708_REG_VALUE_08BIT, IMX708_MODE_STANDBY); + if (ret) + dev_err(&client->dev, "%s failed to set stream\n", __func__); +} + +static int imx708_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct imx708 *imx708 = to_imx708(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret = 0; + + mutex_lock(&imx708->mutex); + if (imx708->streaming == enable) { + mutex_unlock(&imx708->mutex); + return 0; + } + + if (enable) { + ret = pm_runtime_get_sync(&client->dev); + if (ret < 0) { + pm_runtime_put_noidle(&client->dev); + goto err_unlock; + } + + /* + * Apply default & customized values + * and then start streaming. + */ + ret = imx708_start_streaming(imx708); + if (ret) + goto err_rpm_put; + } else { + imx708_stop_streaming(imx708); + pm_runtime_put(&client->dev); + } + + imx708->streaming = enable; + + /* vflip/hflip and hdr mode cannot change during streaming */ + __v4l2_ctrl_grab(imx708->vflip, enable); + __v4l2_ctrl_grab(imx708->hflip, enable); + __v4l2_ctrl_grab(imx708->hdr_mode, enable); + + mutex_unlock(&imx708->mutex); + + return ret; + +err_rpm_put: + pm_runtime_put(&client->dev); +err_unlock: + mutex_unlock(&imx708->mutex); + + return ret; +} + +/* Power/clock management functions */ +static int imx708_power_on(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx708 *imx708 = to_imx708(sd); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(imx708_supply_name), + imx708->supplies); + if (ret) { + dev_err(&client->dev, "%s: failed to enable regulators\n", + __func__); + return ret; + } + + ret = clk_prepare_enable(imx708->inclk); + if (ret) { + dev_err(&client->dev, "%s: failed to enable clock\n", + __func__); + goto reg_off; + } + + gpiod_set_value_cansleep(imx708->reset_gpio, 1); + usleep_range(IMX708_XCLR_MIN_DELAY_US, + IMX708_XCLR_MIN_DELAY_US + IMX708_XCLR_DELAY_RANGE_US); + + return 0; + +reg_off: + regulator_bulk_disable(ARRAY_SIZE(imx708_supply_name), + imx708->supplies); + return ret; +} + +static int imx708_power_off(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx708 *imx708 = to_imx708(sd); + + gpiod_set_value_cansleep(imx708->reset_gpio, 0); + regulator_bulk_disable(ARRAY_SIZE(imx708_supply_name), + imx708->supplies); + clk_disable_unprepare(imx708->inclk); + + /* Force reprogramming of the common registers when powered up again. */ + imx708->common_regs_written = false; + + return 0; +} + +static int __maybe_unused imx708_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx708 *imx708 = to_imx708(sd); + + if (imx708->streaming) + imx708_stop_streaming(imx708); + + return 0; +} + +static int __maybe_unused imx708_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx708 *imx708 = to_imx708(sd); + int ret; + + if (imx708->streaming) { + ret = imx708_start_streaming(imx708); + if (ret) + goto error; + } + + return 0; + +error: + imx708_stop_streaming(imx708); + imx708->streaming = 0; + return ret; +} + +static int imx708_get_regulators(struct imx708 *imx708) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(imx708_supply_name); i++) + imx708->supplies[i].supply = imx708_supply_name[i]; + + return devm_regulator_bulk_get(&client->dev, + ARRAY_SIZE(imx708_supply_name), + imx708->supplies); +} + +/* Verify chip ID */ +static int imx708_identify_module(struct imx708 *imx708) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); + int ret; + u32 val; + + ret = imx708_read_reg(imx708, IMX708_REG_CHIP_ID, + IMX708_REG_VALUE_16BIT, &val); + if (ret) { + dev_err(&client->dev, "failed to read chip id %x, with error %d\n", + IMX708_CHIP_ID, ret); + return ret; + } + + if (val != IMX708_CHIP_ID) { + dev_err(&client->dev, "chip id mismatch: %x!=%x\n", + IMX708_CHIP_ID, val); + return -EIO; + } + + ret = imx708_read_reg(imx708, 0x0000, IMX708_REG_VALUE_16BIT, &val); + if (!ret) { + dev_info(&client->dev, "camera module ID 0x%04x\n", val); + snprintf(imx708->sd.name, sizeof(imx708->sd.name), "imx708%s%s", + val & 0x02 ? "_wide" : "", + val & 0x80 ? "_noir" : ""); + } + + return 0; +} + +static const struct v4l2_subdev_core_ops imx708_core_ops = { + .subscribe_event = v4l2_ctrl_subdev_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_video_ops imx708_video_ops = { + .s_stream = imx708_set_stream, +}; + +static const struct v4l2_subdev_pad_ops imx708_pad_ops = { + .enum_mbus_code = imx708_enum_mbus_code, + .get_fmt = imx708_get_pad_format, + .set_fmt = imx708_set_pad_format, + .get_selection = imx708_get_selection, + .enum_frame_size = imx708_enum_frame_size, +}; + +static const struct v4l2_subdev_ops imx708_subdev_ops = { + .core = &imx708_core_ops, + .video = &imx708_video_ops, + .pad = &imx708_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops imx708_internal_ops = { + .open = imx708_open, +}; + +static const struct v4l2_ctrl_config imx708_notify_gains_ctrl = { + .ops = &imx708_ctrl_ops, + .id = V4L2_CID_NOTIFY_GAINS, + .type = V4L2_CTRL_TYPE_U32, + .min = IMX708_COLOUR_BALANCE_MIN, + .max = IMX708_COLOUR_BALANCE_MAX, + .step = IMX708_COLOUR_BALANCE_STEP, + .def = IMX708_COLOUR_BALANCE_DEFAULT, + .dims = { 4 }, + .elem_size = sizeof(u32), +}; + +/* Initialize control handlers */ +static int imx708_init_controls(struct imx708 *imx708) +{ + struct v4l2_ctrl_handler *ctrl_hdlr; + struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); + struct v4l2_fwnode_device_properties props; + struct v4l2_ctrl *ctrl; + unsigned int i; + int ret; + + ctrl_hdlr = &imx708->ctrl_handler; + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 16); + if (ret) + return ret; + + mutex_init(&imx708->mutex); + ctrl_hdlr->lock = &imx708->mutex; + + /* By default, PIXEL_RATE is read only */ + imx708->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_PIXEL_RATE, + IMX708_INITIAL_PIXEL_RATE, + IMX708_INITIAL_PIXEL_RATE, 1, + IMX708_INITIAL_PIXEL_RATE); + + ctrl = v4l2_ctrl_new_int_menu(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_LINK_FREQ, 0, 0, + &link_freqs[imx708->link_freq_idx]); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + /* + * Create the controls here, but mode specific limits are setup + * in the imx708_set_framing_limits() call below. + */ + imx708->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_VBLANK, 0, 0xffff, 1, 0); + imx708->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_HBLANK, 0, 0xffff, 1, 0); + + imx708->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_EXPOSURE, + IMX708_EXPOSURE_MIN, + IMX708_EXPOSURE_MAX, + IMX708_EXPOSURE_STEP, + IMX708_EXPOSURE_DEFAULT); + + v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + IMX708_ANA_GAIN_MIN, IMX708_ANA_GAIN_MAX, + IMX708_ANA_GAIN_STEP, IMX708_ANA_GAIN_DEFAULT); + + v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, V4L2_CID_DIGITAL_GAIN, + IMX708_DGTL_GAIN_MIN, IMX708_DGTL_GAIN_MAX, + IMX708_DGTL_GAIN_STEP, IMX708_DGTL_GAIN_DEFAULT); + + imx708->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + + imx708->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + v4l2_ctrl_cluster(2, &imx708->hflip); + + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(imx708_test_pattern_menu) - 1, + 0, 0, imx708_test_pattern_menu); + for (i = 0; i < 4; i++) { + /* + * The assumption is that + * V4L2_CID_TEST_PATTERN_GREENR == V4L2_CID_TEST_PATTERN_RED + 1 + * V4L2_CID_TEST_PATTERN_BLUE == V4L2_CID_TEST_PATTERN_RED + 2 + * V4L2_CID_TEST_PATTERN_GREENB == V4L2_CID_TEST_PATTERN_RED + 3 + */ + v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_TEST_PATTERN_RED + i, + IMX708_TEST_PATTERN_COLOUR_MIN, + IMX708_TEST_PATTERN_COLOUR_MAX, + IMX708_TEST_PATTERN_COLOUR_STEP, + IMX708_TEST_PATTERN_COLOUR_MAX); + /* The "Solid color" pattern is white by default */ + } + + v4l2_ctrl_new_custom(ctrl_hdlr, &imx708_notify_gains_ctrl, NULL); + + imx708->hdr_mode = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_WIDE_DYNAMIC_RANGE, + 0, 1, 1, 0); + + ret = v4l2_fwnode_device_parse(&client->dev, &props); + if (ret) + goto error; + + v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &imx708_ctrl_ops, &props); + + if (ctrl_hdlr->error) { + ret = ctrl_hdlr->error; + dev_err(&client->dev, "%s control init failed (%d)\n", + __func__, ret); + goto error; + } + + imx708->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + imx708->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + imx708->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + imx708->hdr_mode->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + + imx708->sd.ctrl_handler = ctrl_hdlr; + + /* Setup exposure and frame/line length limits. */ + imx708_set_framing_limits(imx708); + + return 0; + +error: + v4l2_ctrl_handler_free(ctrl_hdlr); + mutex_destroy(&imx708->mutex); + + return ret; +} + +static void imx708_free_controls(struct imx708 *imx708) +{ + v4l2_ctrl_handler_free(imx708->sd.ctrl_handler); + mutex_destroy(&imx708->mutex); +} + +static int imx708_check_hwcfg(struct device *dev, struct imx708 *imx708) +{ + struct fwnode_handle *endpoint; + struct v4l2_fwnode_endpoint ep_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; + int ret = -EINVAL; + int i; + + endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL); + if (!endpoint) { + dev_err(dev, "endpoint node not found\n"); + return -EINVAL; + } + + if (v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep_cfg)) { + dev_err(dev, "could not parse endpoint\n"); + goto error_out; + } + + /* Check the number of MIPI CSI2 data lanes */ + if (ep_cfg.bus.mipi_csi2.num_data_lanes != 2) { + dev_err(dev, "only 2 data lanes are currently supported\n"); + goto error_out; + } + + /* Check the link frequency set in device tree */ + if (!ep_cfg.nr_of_link_frequencies) { + dev_err(dev, "link-frequency property not found in DT\n"); + goto error_out; + } + + for (i = 0; i < ARRAY_SIZE(link_freqs); i++) { + if (link_freqs[i] == ep_cfg.link_frequencies[0]) { + imx708->link_freq_idx = i; + break; + } + } + + if (i == ARRAY_SIZE(link_freqs)) { + dev_err(dev, "Link frequency not supported: %lld\n", + ep_cfg.link_frequencies[0]); + ret = -EINVAL; + goto error_out; + } + + ret = 0; + +error_out: + v4l2_fwnode_endpoint_free(&ep_cfg); + fwnode_handle_put(endpoint); + + return ret; +} + +static int imx708_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct imx708 *imx708; + int ret; + + imx708 = devm_kzalloc(&client->dev, sizeof(*imx708), GFP_KERNEL); + if (!imx708) + return -ENOMEM; + + v4l2_i2c_subdev_init(&imx708->sd, client, &imx708_subdev_ops); + + /* Check the hardware configuration in device tree */ + if (imx708_check_hwcfg(dev, imx708)) + return -EINVAL; + + /* Get system clock (inclk) */ + imx708->inclk = devm_clk_get(dev, "inclk"); + if (IS_ERR(imx708->inclk)) + return dev_err_probe(dev, PTR_ERR(imx708->inclk), + "failed to get inclk\n"); + + imx708->inclk_freq = clk_get_rate(imx708->inclk); + if (imx708->inclk_freq != IMX708_INCLK_FREQ) + return dev_err_probe(dev, -EINVAL, + "inclk frequency not supported: %d Hz\n", + imx708->inclk_freq); + + ret = imx708_get_regulators(imx708); + if (ret) + return dev_err_probe(dev, ret, "failed to get regulators\n"); + + /* Request optional enable pin */ + imx708->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + + /* + * The sensor must be powered for imx708_identify_module() + * to be able to read the CHIP_ID register + */ + ret = imx708_power_on(dev); + if (ret) + return ret; + + ret = imx708_identify_module(imx708); + if (ret) + goto error_power_off; + + /* Initialize default format */ + imx708_set_default_format(imx708); + + /* Enable runtime PM and turn off the device */ + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_idle(dev); + + /* This needs the pm runtime to be registered. */ + ret = imx708_init_controls(imx708); + if (ret) + goto error_pm_runtime; + + /* Initialize subdev */ + imx708->sd.internal_ops = &imx708_internal_ops; + imx708->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | + V4L2_SUBDEV_FL_HAS_EVENTS; + imx708->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + /* Initialize source pads */ + imx708->pad[IMAGE_PAD].flags = MEDIA_PAD_FL_SOURCE; + imx708->pad[METADATA_PAD].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&imx708->sd.entity, NUM_PADS, imx708->pad); + if (ret) { + dev_err(dev, "failed to init entity pads: %d\n", ret); + goto error_handler_free; + } + + ret = v4l2_async_register_subdev_sensor(&imx708->sd); + if (ret < 0) { + dev_err(dev, "failed to register sensor sub-device: %d\n", ret); + goto error_media_entity; + } + + return 0; + +error_media_entity: + media_entity_cleanup(&imx708->sd.entity); + +error_handler_free: + imx708_free_controls(imx708); + +error_pm_runtime: + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + +error_power_off: + imx708_power_off(&client->dev); + + return ret; +} + +static void imx708_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx708 *imx708 = to_imx708(sd); + + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + imx708_free_controls(imx708); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) + imx708_power_off(&client->dev); + pm_runtime_set_suspended(&client->dev); +} + +static const struct of_device_id imx708_dt_ids[] = { + { .compatible = "sony,imx708" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx708_dt_ids); + +static const struct dev_pm_ops imx708_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(imx708_suspend, imx708_resume) + SET_RUNTIME_PM_OPS(imx708_power_off, imx708_power_on, NULL) +}; + +static struct i2c_driver imx708_i2c_driver = { + .driver = { + .name = "imx708", + .of_match_table = imx708_dt_ids, + .pm = &imx708_pm_ops, + }, + .probe = imx708_probe, + .remove = imx708_remove, +}; + +module_i2c_driver(imx708_i2c_driver); + +MODULE_AUTHOR("David Plowman <david.plowman@raspberrypi.com>"); +MODULE_DESCRIPTION("Sony IMX708 sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/irs1125.c b/drivers/media/i2c/irs1125.c new file mode 100644 index 00000000000000..f69b4c5a98279f --- /dev/null +++ b/drivers/media/i2c/irs1125.c @@ -0,0 +1,1197 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A V4L2 driver for Infineon IRS1125 TOF cameras. + * Copyright (C) 2018, pieye GmbH + * + * Based on V4L2 OmniVision OV5647 Image Sensor driver + * Copyright (C) 2016 Ramiro Oliveira <roliveir@synopsys.com> + * + * DT / fwnode changes, and GPIO control taken from ov5640.c + * Copyright (C) 2011-2013 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright (C) 2014-2017 Mentor Graphics Inc. + * + */ + +#include "irs1125.h" +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/firmware.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of_graph.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/videodev2.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-image-sizes.h> +#include <media/v4l2-mediabus.h> + +#define CHECK_BIT(val, pos) ((val) & BIT(pos)) + +#define SENSOR_NAME "irs1125" + +#define RESET_ACTIVE_DELAY_MS 20 + +#define IRS1125_ALTERNATE_FW "irs1125_af.bin" + +#define IRS1125_REG_SAFE_RECONFIG 0xA850 +#define IRS1125_REG_CSICFG 0xA882 +#define IRS1125_REG_DESIGN_STEP 0xB0AD +#define IRS1125_REG_EFUSEVAL2 0xB09F +#define IRS1125_REG_EFUSEVAL3 0xB0A0 +#define IRS1125_REG_EFUSEVAL4 0xB0A1 +#define IRS1125_REG_DMEM_SHADOW 0xC320 + +#define IRS1125_DESIGN_STEP_EXPECTED 0x0a12 + +#define IRS1125_ROW_START_DEF 0 +#define IRS1125_COLUMN_START_DEF 0 +#define IRS1125_WINDOW_HEIGHT_DEF 288 +#define IRS1125_WINDOW_WIDTH_DEF 352 + +struct regval_list { + u16 addr; + u16 data; +}; + +struct irs1125 { + struct v4l2_subdev sd; + struct media_pad pad; + /* the parsed DT endpoint info */ + struct v4l2_fwnode_endpoint ep; + + struct clk *xclk; + struct v4l2_ctrl_handler ctrl_handler; + + /* To serialize asynchronus callbacks */ + struct mutex lock; + + /* image data layout */ + unsigned int num_seq; + + /* reset pin */ + struct gpio_desc *reset; + + /* V4l2 Controls to grab */ + struct v4l2_ctrl *ctrl_modplls; + struct v4l2_ctrl *ctrl_numseq; + + int power_count; + bool mod_pll_init; +}; + +static inline struct irs1125 *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct irs1125, sd); +} + +static const char *expo_ctrl_names[IRS1125_NUM_SEQ_ENTRIES] = { + "safe reconfiguration of exposure of sequence 0", + "safe reconfiguration of exposure of sequence 1", + "safe reconfiguration of exposure of sequence 2", + "safe reconfiguration of exposure of sequence 3", + "safe reconfiguration of exposure of sequence 4", + "safe reconfiguration of exposure of sequence 5", + "safe reconfiguration of exposure of sequence 6", + "safe reconfiguration of exposure of sequence 7", + "safe reconfiguration of exposure of sequence 8", + "safe reconfiguration of exposure of sequence 9", + "safe reconfiguration of exposure of sequence 10", + "safe reconfiguration of exposure of sequence 11", + "safe reconfiguration of exposure of sequence 12", + "safe reconfiguration of exposure of sequence 13", + "safe reconfiguration of exposure of sequence 14", + "safe reconfiguration of exposure of sequence 15", + "safe reconfiguration of exposure of sequence 16", + "safe reconfiguration of exposure of sequence 17", + "safe reconfiguration of exposure of sequence 18", + "safe reconfiguration of exposure of sequence 19", +}; + +static const char *frame_ctrl_names[IRS1125_NUM_SEQ_ENTRIES] = { + "safe reconfiguration of framerate of sequence 0", + "safe reconfiguration of framerate of sequence 1", + "safe reconfiguration of framerate of sequence 2", + "safe reconfiguration of framerate of sequence 3", + "safe reconfiguration of framerate of sequence 4", + "safe reconfiguration of framerate of sequence 5", + "safe reconfiguration of framerate of sequence 6", + "safe reconfiguration of framerate of sequence 7", + "safe reconfiguration of framerate of sequence 8", + "safe reconfiguration of framerate of sequence 9", + "safe reconfiguration of framerate of sequence 10", + "safe reconfiguration of framerate of sequence 11", + "safe reconfiguration of framerate of sequence 12", + "safe reconfiguration of framerate of sequence 13", + "safe reconfiguration of framerate of sequence 14", + "safe reconfiguration of framerate of sequence 15", + "safe reconfiguration of framerate of sequence 16", + "safe reconfiguration of framerate of sequence 17", + "safe reconfiguration of framerate of sequence 18", + "safe reconfiguration of framerate of sequence 19", +}; + +static struct regval_list irs1125_26mhz[] = { + {0xB017, 0x0413}, + {0xB086, 0x3535}, + {0xB0AE, 0xEF02}, + {0xA000, 0x0004}, + {0xFFFF, 100}, + + {0xB062, 0x6383}, + {0xB063, 0x55A8}, + {0xB068, 0x7628}, + {0xB069, 0x03E2}, + + {0xFFFF, 100}, + {0xB05A, 0x01C5}, + {0xB05C, 0x0206}, + {0xB05D, 0x01C5}, + {0xB05F, 0x0206}, + {0xB016, 0x1335}, + {0xFFFF, 100}, + {0xA893, 0x8261}, + {0xA894, 0x89d8}, + {0xA895, 0x131d}, + {0xA896, 0x4251}, + {0xA897, 0x9D8A}, + {0xA898, 0x0BD8}, + {0xA899, 0x2245}, + {0xA89A, 0xAB9B}, + {0xA89B, 0x03B9}, + {0xA89C, 0x8041}, + {0xA89D, 0xE07E}, + {0xA89E, 0x0307}, + {0xFFFF, 100}, + {0xA88D, 0x0004}, + {0xA800, 0x0E68}, + {0xA801, 0x0000}, + {0xA802, 0x000C}, + {0xA803, 0x0000}, + {0xA804, 0x0E68}, + {0xA805, 0x0000}, + {0xA806, 0x0440}, + {0xA807, 0x0000}, + {0xA808, 0x0E68}, + {0xA809, 0x0000}, + {0xA80A, 0x0884}, + {0xA80B, 0x0000}, + {0xA80C, 0x0E68}, + {0xA80D, 0x0000}, + {0xA80E, 0x0CC8}, + {0xA80F, 0x0000}, + {0xA810, 0x0E68}, + {0xA811, 0x0000}, + {0xA812, 0x2000}, + {0xA813, 0x0000}, + {0xA882, 0x0081}, + {0xA88C, 0x403A}, + {0xA88F, 0x031E}, + {0xA892, 0x0351}, + {0x9813, 0x13FF}, + {0x981B, 0x7608}, + + {0xB008, 0x0000}, + {0xB015, 0x1513}, + + {0xFFFF, 100} +}; + +static struct regval_list irs1125_seq_cfg_init[] = { + {0xC3A0, 0x823D}, + {0xC3A1, 0xB13B}, + {0xC3A2, 0x0313}, + {0xC3A3, 0x4659}, + {0xC3A4, 0xC4EC}, + {0xC3A5, 0x03CE}, + {0xC3A6, 0x4259}, + {0xC3A7, 0xC4EC}, + {0xC3A8, 0x03CE}, + {0xC3A9, 0x8839}, + {0xC3AA, 0x89D8}, + {0xC3AB, 0x031D}, + + {0xC24C, 0x5529}, + {0xC24D, 0x0000}, + {0xC24E, 0x1200}, + {0xC24F, 0x6CB2}, + {0xC250, 0x0000}, + {0xC251, 0x5529}, + {0xC252, 0x42F4}, + {0xC253, 0xD1AF}, + {0xC254, 0x8A18}, + {0xC255, 0x0002}, + {0xC256, 0x5529}, + {0xC257, 0x6276}, + {0xC258, 0x11A7}, + {0xC259, 0xD907}, + {0xC25A, 0x0000}, + {0xC25B, 0x5529}, + {0xC25C, 0x07E0}, + {0xC25D, 0x7BFE}, + {0xC25E, 0x6402}, + {0xC25F, 0x0019}, + + {0xC3AC, 0x0007}, + {0xC3AD, 0xED88}, + {0xC320, 0x003E}, + {0xC321, 0x0000}, + {0xC322, 0x2000}, + {0xC323, 0x0000}, + {0xC324, 0x0271}, + {0xC325, 0x0000}, + {0xC326, 0x000C}, + {0xC327, 0x0000}, + {0xC328, 0x0271}, + {0xC329, 0x0000}, + {0xC32A, 0x0440}, + {0xC32B, 0x0000}, + {0xC32C, 0x0271}, + {0xC32D, 0x0000}, + {0xC32E, 0x0884}, + {0xC32F, 0x0000}, + {0xC330, 0x0271}, + {0xC331, 0x0000}, + {0xC332, 0x0CC8}, + {0xC333, 0x0000}, + {0xA88D, 0x0004}, + + {0xA890, 0x0000}, + {0xC219, 0x0002}, + {0xC21A, 0x0000}, + {0xC21B, 0x0000}, + {0xC21C, 0x00CD}, + {0xC21D, 0x0009}, + {0xC21E, 0x00CD}, + {0xC21F, 0x0009}, + + {0xA87C, 0x0000}, + {0xC032, 0x0001}, + {0xC034, 0x0000}, + {0xC035, 0x0001}, + {0xC039, 0x0000}, + {0xC401, 0x0002}, + + {0xFFFF, 1} +}; + +static int irs1125_write(struct v4l2_subdev *sd, u16 reg, u16 val) +{ + int ret; + unsigned char data[4] = { reg >> 8, reg & 0xff, val >> 8, val & 0xff}; + struct i2c_client *client = v4l2_get_subdevdata(sd); + + ret = i2c_master_send(client, data, 4); + if (ret < 0) + dev_err(&client->dev, "%s: i2c write error, reg: %x\n", + __func__, reg); + + dev_dbg(&client->dev, "write addr 0x%04x, val 0x%04x\n", reg, val); + return ret; +} + +static int irs1125_read(struct v4l2_subdev *sd, u16 reg, u16 *val) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct i2c_msg msgs[2]; + u8 addr_buf[2] = { reg >> 8, reg & 0xff }; + u8 data_buf[2] = { 0, }; + int ret; + + /* Write register address */ + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = ARRAY_SIZE(addr_buf); + msgs[0].buf = addr_buf; + + /* Read data from register */ + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = 2; + msgs[1].buf = data_buf; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) { + if (ret >= 0) + ret = -EIO; + return ret; + } + + *val = data_buf[1] | (data_buf[0] << 8); + + return 0; +} + +static int irs1125_write_array(struct v4l2_subdev *sd, + struct regval_list *regs, int array_size) +{ + int i, ret; + + for (i = 0; i < array_size; i++) { + if (regs[i].addr == 0xFFFF) { + msleep(regs[i].data); + } else { + ret = irs1125_write(sd, regs[i].addr, regs[i].data); + if (ret < 0) + return ret; + } + } + + return 0; +} + +static int irs1125_stream_on(struct v4l2_subdev *sd) +{ + int ret; + struct irs1125 *irs1125 = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + + v4l2_ctrl_grab(irs1125->ctrl_numseq, 1); + v4l2_ctrl_grab(irs1125->ctrl_modplls, 1); + + ret = irs1125_write(sd, 0xC400, 0x0001); + if (ret < 0) { + dev_err(&client->dev, "error enabling firmware: %d", ret); + return ret; + } + + msleep(100); + + return irs1125_write(sd, 0xA87C, 0x0001); +} + +static int irs1125_stream_off(struct v4l2_subdev *sd) +{ + int ret; + struct irs1125 *irs1125 = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + + v4l2_ctrl_grab(irs1125->ctrl_numseq, 0); + v4l2_ctrl_grab(irs1125->ctrl_modplls, 0); + + ret = irs1125_write(sd, 0xA87C, 0x0000); + if (ret < 0) { + dev_err(&client->dev, "error disabling trigger: %d", ret); + return ret; + } + + msleep(100); + + return irs1125_write(sd, 0xC400, 0x0002); +} + +static int __sensor_init(struct v4l2_subdev *sd) +{ + unsigned int cnt, idx; + int ret; + u16 val; + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct irs1125 *irs1125 = to_state(sd); + const struct firmware *fw; + struct regval_list *reg_data; + + cnt = 0; + while (1) { + ret = irs1125_read(sd, 0xC40F, &val); + if (ret < 0) { + dev_err(&client->dev, "read register 0xC40F failed\n"); + return ret; + } + if (CHECK_BIT(val, 14) == 0) + break; + + if (cnt >= 5) { + dev_err(&client->dev, "timeout waiting for 0xC40F\n"); + return -EAGAIN; + } + + cnt++; + } + + ret = irs1125_write_array(sd, irs1125_26mhz, + ARRAY_SIZE(irs1125_26mhz)); + if (ret < 0) { + dev_err(&client->dev, "write sensor default regs error\n"); + return ret; + } + + /* set CSI-2 number of data lanes */ + if (irs1125->ep.bus.mipi_csi2.num_data_lanes == 1) { + val = 0x0001; + } else if (irs1125->ep.bus.mipi_csi2.num_data_lanes == 2) { + val = 0x0081; + } else { + dev_err(&client->dev, "invalid number of data lanes %d\n", + irs1125->ep.bus.mipi_csi2.num_data_lanes); + return -EINVAL; + } + + ret = irs1125_write(sd, IRS1125_REG_CSICFG, val); + if (ret < 0) { + dev_err(&client->dev, "write sensor csi2 config error\n"); + return ret; + } + + /* request the firmware, this will block and timeout */ + ret = request_firmware(&fw, IRS1125_ALTERNATE_FW, &client->dev); + if (ret) { + dev_err(&client->dev, + "did not find the firmware file '%s' (status %d)\n", + IRS1125_ALTERNATE_FW, ret); + return ret; + } + + if (fw->size % 4) { + dev_err(&client->dev, "firmware file '%s' invalid\n", + IRS1125_ALTERNATE_FW); + release_firmware(fw); + return -EINVAL; + } + + for (idx = 0; idx < fw->size; idx += 4) { + reg_data = (struct regval_list *)&fw->data[idx]; + ret = irs1125_write(sd, reg_data->addr, reg_data->data); + if (ret < 0) { + dev_err(&client->dev, "firmware write error\n"); + release_firmware(fw); + return ret; + } + } + release_firmware(fw); + + ret = irs1125_write_array(sd, irs1125_seq_cfg_init, + ARRAY_SIZE(irs1125_seq_cfg_init)); + if (ret < 0) { + dev_err(&client->dev, "write default sequence failed\n"); + return ret; + } + + irs1125->mod_pll_init = true; + v4l2_ctrl_handler_setup(&irs1125->ctrl_handler); + irs1125->mod_pll_init = false; + + return irs1125_write(sd, 0xA87C, 0x0001); +} + +static int irs1125_sensor_power(struct v4l2_subdev *sd, int on) +{ + int ret = 0; + struct irs1125 *irs1125 = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + + mutex_lock(&irs1125->lock); + + if (on && !irs1125->power_count) { + gpiod_set_value_cansleep(irs1125->reset, 1); + msleep(RESET_ACTIVE_DELAY_MS); + + ret = clk_prepare_enable(irs1125->xclk); + if (ret < 0) { + dev_err(&client->dev, "clk prepare enable failed\n"); + goto out; + } + + ret = __sensor_init(sd); + if (ret < 0) { + clk_disable_unprepare(irs1125->xclk); + dev_err(&client->dev, + "Camera not available, check Power\n"); + goto out; + } + } else if (!on && irs1125->power_count == 1) { + gpiod_set_value_cansleep(irs1125->reset, 0); + } + + /* Update the power count. */ + irs1125->power_count += on ? 1 : -1; + WARN_ON(irs1125->power_count < 0); + +out: + mutex_unlock(&irs1125->lock); + + return ret; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int irs1125_sensor_get_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + u16 val; + int ret; + + ret = irs1125_read(sd, reg->reg & 0xffff, &val); + if (ret < 0) + return ret; + + reg->val = val; + reg->size = 1; + + return 0; +} + +static int irs1125_sensor_set_register(struct v4l2_subdev *sd, + const struct v4l2_dbg_register *reg) +{ + return irs1125_write(sd, reg->reg & 0xffff, reg->val & 0xffff); +} +#endif + +static const struct v4l2_subdev_core_ops irs1125_subdev_core_ops = { + .s_power = irs1125_sensor_power, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = irs1125_sensor_get_register, + .s_register = irs1125_sensor_set_register, +#endif +}; + +static int irs1125_s_stream(struct v4l2_subdev *sd, int enable) +{ + if (enable) + return irs1125_stream_on(sd); + else + return irs1125_stream_off(sd); +} + +static const struct v4l2_subdev_video_ops irs1125_subdev_video_ops = { + .s_stream = irs1125_s_stream, +}; + +static int irs1125_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index > 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_Y12_1X12; + + return 0; +} + +static int irs1125_set_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *fmt = &format->format; + struct irs1125 *irs1125 = to_state(sd); + + if (format->pad != 0) + return -EINVAL; + + /* Only one format is supported, so return that */ + memset(fmt, 0, sizeof(*fmt)); + fmt->code = MEDIA_BUS_FMT_Y12_1X12; + fmt->colorspace = V4L2_COLORSPACE_RAW; + fmt->field = V4L2_FIELD_NONE; + fmt->width = IRS1125_WINDOW_WIDTH_DEF; + fmt->height = IRS1125_WINDOW_HEIGHT_DEF * irs1125->num_seq; + + return 0; +} + +static const struct v4l2_subdev_pad_ops irs1125_subdev_pad_ops = { + .enum_mbus_code = irs1125_enum_mbus_code, + .set_fmt = irs1125_set_get_fmt, + .get_fmt = irs1125_set_get_fmt, +}; + +static const struct v4l2_subdev_ops irs1125_subdev_ops = { + .core = &irs1125_subdev_core_ops, + .video = &irs1125_subdev_video_ops, + .pad = &irs1125_subdev_pad_ops, +}; + +static int irs1125_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct irs1125 *dev = container_of(ctrl->handler, + struct irs1125, ctrl_handler); + struct i2c_client *client = v4l2_get_subdevdata(&dev->sd); + int err = 0, i; + + switch (ctrl->id) { + case IRS1125_CID_SAFE_RECONFIG_S0_EXPO: + case IRS1125_CID_SAFE_RECONFIG_S0_FRAME: + case IRS1125_CID_SAFE_RECONFIG_S1_EXPO: + case IRS1125_CID_SAFE_RECONFIG_S1_FRAME: + case IRS1125_CID_SAFE_RECONFIG_S2_EXPO: + case IRS1125_CID_SAFE_RECONFIG_S2_FRAME: + case IRS1125_CID_SAFE_RECONFIG_S3_EXPO: + case IRS1125_CID_SAFE_RECONFIG_S3_FRAME: + case IRS1125_CID_SAFE_RECONFIG_S4_EXPO: + case IRS1125_CID_SAFE_RECONFIG_S4_FRAME: + case IRS1125_CID_SAFE_RECONFIG_S5_EXPO: + case IRS1125_CID_SAFE_RECONFIG_S5_FRAME: + case IRS1125_CID_SAFE_RECONFIG_S6_EXPO: + case IRS1125_CID_SAFE_RECONFIG_S6_FRAME: + case IRS1125_CID_SAFE_RECONFIG_S7_EXPO: + case IRS1125_CID_SAFE_RECONFIG_S7_FRAME: + case IRS1125_CID_SAFE_RECONFIG_S8_EXPO: + case IRS1125_CID_SAFE_RECONFIG_S8_FRAME: + case IRS1125_CID_SAFE_RECONFIG_S9_EXPO: + case IRS1125_CID_SAFE_RECONFIG_S9_FRAME: + case IRS1125_CID_SAFE_RECONFIG_S10_EXPO: + case IRS1125_CID_SAFE_RECONFIG_S10_FRAME: + case IRS1125_CID_SAFE_RECONFIG_S11_EXPO: + case IRS1125_CID_SAFE_RECONFIG_S11_FRAME: + case IRS1125_CID_SAFE_RECONFIG_S12_EXPO: + case IRS1125_CID_SAFE_RECONFIG_S12_FRAME: + case IRS1125_CID_SAFE_RECONFIG_S13_EXPO: + case IRS1125_CID_SAFE_RECONFIG_S13_FRAME: + case IRS1125_CID_SAFE_RECONFIG_S14_EXPO: + case IRS1125_CID_SAFE_RECONFIG_S14_FRAME: + case IRS1125_CID_SAFE_RECONFIG_S15_EXPO: + case IRS1125_CID_SAFE_RECONFIG_S15_FRAME: + case IRS1125_CID_SAFE_RECONFIG_S16_EXPO: + case IRS1125_CID_SAFE_RECONFIG_S16_FRAME: + case IRS1125_CID_SAFE_RECONFIG_S17_EXPO: + case IRS1125_CID_SAFE_RECONFIG_S17_FRAME: + case IRS1125_CID_SAFE_RECONFIG_S18_EXPO: + case IRS1125_CID_SAFE_RECONFIG_S18_FRAME: + case IRS1125_CID_SAFE_RECONFIG_S19_EXPO: + case IRS1125_CID_SAFE_RECONFIG_S19_FRAME: { + unsigned int offset = ctrl->id - + IRS1125_CID_SAFE_RECONFIG_S0_EXPO; + + err = irs1125_write(&dev->sd, + IRS1125_REG_SAFE_RECONFIG + offset, + ctrl->val); + break; + } + case IRS1125_CID_MOD_PLL: { + struct irs1125_mod_pll *mod_new; + + if (dev->mod_pll_init) + break; + + mod_new = (struct irs1125_mod_pll *)ctrl->p_new.p; + for (i = 0; i < IRS1125_NUM_MOD_PLLS; i++) { + unsigned int pll_offset, ssc_offset; + + pll_offset = i * 3; + ssc_offset = i * 5; + + err = irs1125_write(&dev->sd, 0xC3A0 + pll_offset, + mod_new[i].pllcfg1); + if (err < 0) + break; + + err = irs1125_write(&dev->sd, 0xC3A1 + pll_offset, + mod_new[i].pllcfg2); + if (err < 0) + break; + + err = irs1125_write(&dev->sd, 0xC3A2 + pll_offset, + mod_new[i].pllcfg3); + if (err < 0) + break; + + err = irs1125_write(&dev->sd, 0xC24C + ssc_offset, + mod_new[i].pllcfg4); + if (err < 0) + break; + + err = irs1125_write(&dev->sd, 0xC24D + ssc_offset, + mod_new[i].pllcfg5); + if (err < 0) + break; + + err = irs1125_write(&dev->sd, 0xC24E + ssc_offset, + mod_new[i].pllcfg6); + if (err < 0) + break; + + err = irs1125_write(&dev->sd, 0xC24F + ssc_offset, + mod_new[i].pllcfg7); + if (err < 0) + break; + + err = irs1125_write(&dev->sd, 0xC250 + ssc_offset, + mod_new[i].pllcfg8); + if (err < 0) + break; + } + break; + } + case IRS1125_CID_SEQ_CONFIG: { + struct irs1125_seq_cfg *cfg_new; + + cfg_new = (struct irs1125_seq_cfg *)ctrl->p_new.p; + for (i = 0; i < IRS1125_NUM_SEQ_ENTRIES; i++) { + unsigned int seq_offset = i * 4; + u16 addr, val; + + addr = IRS1125_REG_DMEM_SHADOW + seq_offset; + val = cfg_new[i].exposure; + err = irs1125_write(&dev->sd, addr, val); + if (err < 0) + break; + + addr = IRS1125_REG_DMEM_SHADOW + 1 + seq_offset; + val = cfg_new[i].framerate; + err = irs1125_write(&dev->sd, addr, val); + if (err < 0) + break; + + addr = IRS1125_REG_DMEM_SHADOW + 2 + seq_offset; + val = cfg_new[i].ps; + err = irs1125_write(&dev->sd, addr, val); + if (err < 0) + break; + + addr = IRS1125_REG_DMEM_SHADOW + 3 + seq_offset; + val = cfg_new[i].pll; + err = irs1125_write(&dev->sd, addr, val); + if (err < 0) + break; + } + break; + } + case IRS1125_CID_NUM_SEQS: + err = irs1125_write(&dev->sd, 0xA88D, ctrl->val - 1); + if (err >= 0) + dev->num_seq = ctrl->val; + break; + case IRS1125_CID_CONTINUOUS_TRIG: + if (ctrl->val == 0) + err = irs1125_write(&dev->sd, 0xA87C, 0); + else + err = irs1125_write(&dev->sd, 0xA87C, 1); + break; + case IRS1125_CID_TRIGGER: + if (ctrl->val != 0) { + err = irs1125_write(&dev->sd, 0xA87C, 1); + if (err >= 0) + err = irs1125_write(&dev->sd, 0xA87C, 0); + } + break; + case IRS1125_CID_RECONFIG: + if (ctrl->val != 0) + err = irs1125_write(&dev->sd, 0xA87A, 1); + break; + case IRS1125_CID_ILLU_ON: + if (ctrl->val == 0) + err = irs1125_write(&dev->sd, 0xA892, 0x377); + else + err = irs1125_write(&dev->sd, 0xA892, 0x355); + break; + default: + break; + } + + if (err < 0) + dev_err(&client->dev, "Error executing control ID: %d, val %d, err %d", + ctrl->id, ctrl->val, err); + else + err = 0; + + return err; +} + +static const struct v4l2_ctrl_ops irs1125_ctrl_ops = { + .s_ctrl = irs1125_s_ctrl, +}; + +static const struct v4l2_ctrl_config irs1125_custom_ctrls[] = { + { + .ops = &irs1125_ctrl_ops, + .id = IRS1125_CID_NUM_SEQS, + .name = "Change number of sequences", + .type = V4L2_CTRL_TYPE_INTEGER, + .flags = V4L2_CTRL_FLAG_MODIFY_LAYOUT, + .min = 1, + .max = 20, + .step = 1, + .def = 5, + }, { + .ops = &irs1125_ctrl_ops, + .id = IRS1125_CID_MOD_PLL, + .name = "Reconfigure modulation PLLs", + .type = V4L2_CTRL_TYPE_U16, + .flags = V4L2_CTRL_FLAG_HAS_PAYLOAD, + .min = 0, + .max = U16_MAX, + .step = 1, + .def = 0, + .elem_size = sizeof(u16), + .dims = {sizeof(struct irs1125_mod_pll) / sizeof(u16), + IRS1125_NUM_MOD_PLLS} + }, { + .ops = &irs1125_ctrl_ops, + .id = IRS1125_CID_SEQ_CONFIG, + .name = "Change sequence settings", + .type = V4L2_CTRL_TYPE_U16, + .flags = V4L2_CTRL_FLAG_HAS_PAYLOAD, + .min = 0, + .max = U16_MAX, + .step = 1, + .def = 0, + .elem_size = sizeof(u16), + .dims = {sizeof(struct irs1125_seq_cfg) / sizeof(u16), + IRS1125_NUM_SEQ_ENTRIES} + }, { + .ops = &irs1125_ctrl_ops, + .id = IRS1125_CID_CONTINUOUS_TRIG, + .name = "Enable/disable continuous trigger", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .flags = V4L2_CTRL_FLAG_EXECUTE_ON_WRITE, + .min = 0, + .max = 1, + .step = 1, + .def = 0 + }, { + .ops = &irs1125_ctrl_ops, + .id = IRS1125_CID_TRIGGER, + .name = "Capture a single sequence", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .flags = V4L2_CTRL_FLAG_EXECUTE_ON_WRITE, + .min = 0, + .max = 1, + .step = 1, + .def = 0 + }, { + .ops = &irs1125_ctrl_ops, + .id = IRS1125_CID_RECONFIG, + .name = "Trigger imager reconfiguration", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .flags = V4L2_CTRL_FLAG_EXECUTE_ON_WRITE, + .min = 0, + .max = 1, + .step = 1, + .def = 0 + }, { + .ops = &irs1125_ctrl_ops, + .id = IRS1125_CID_ILLU_ON, + .name = "Turn illu on or off", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .flags = V4L2_CTRL_FLAG_EXECUTE_ON_WRITE, + .min = 0, + .max = 1, + .step = 1, + .def = 1 + }, { + .ops = &irs1125_ctrl_ops, + .id = IRS1125_CID_IDENT0, + .name = "Get ident 0 information", + .type = V4L2_CTRL_TYPE_INTEGER, + .flags = V4L2_CTRL_FLAG_READ_ONLY, + .min = S32_MIN, + .max = S32_MAX, + .step = 1, + .def = 0 + }, { + .ops = &irs1125_ctrl_ops, + .id = IRS1125_CID_IDENT1, + .name = "Get ident 1 information", + .type = V4L2_CTRL_TYPE_INTEGER, + .flags = V4L2_CTRL_FLAG_READ_ONLY, + .min = S32_MIN, + .max = S32_MAX, + .step = 1, + .def = 0 + }, { + .ops = &irs1125_ctrl_ops, + .id = IRS1125_CID_IDENT2, + .name = "Get ident 2 information", + .type = V4L2_CTRL_TYPE_INTEGER, + .flags = V4L2_CTRL_FLAG_READ_ONLY, + .min = S32_MIN, + .max = S32_MAX, + .step = 1, + .def = 0 + } +}; + +static int irs1125_detect(struct v4l2_subdev *sd) +{ + u16 read; + int ret; + struct i2c_client *client = v4l2_get_subdevdata(sd); + + ret = irs1125_read(sd, IRS1125_REG_DESIGN_STEP, &read); + if (ret < 0) { + dev_err(&client->dev, "error reading from i2c\n"); + return ret; + } + + if (read != IRS1125_DESIGN_STEP_EXPECTED) { + dev_err(&client->dev, "Design step expected 0x%x got 0x%x", + IRS1125_DESIGN_STEP_EXPECTED, read); + return -ENODEV; + } + + return 0; +} + +static int irs1125_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct v4l2_mbus_framefmt *format = + v4l2_subdev_state_get_format(fh->state, 0); + + format->code = MEDIA_BUS_FMT_Y12_1X12; + format->width = IRS1125_WINDOW_WIDTH_DEF; + format->height = IRS1125_WINDOW_HEIGHT_DEF; + format->field = V4L2_FIELD_NONE; + format->colorspace = V4L2_COLORSPACE_RAW; + + return 0; +} + +static const struct v4l2_subdev_internal_ops irs1125_subdev_internal_ops = { + .open = irs1125_open, +}; + +static int irs1125_ctrls_init(struct irs1125 *sensor, struct device *dev) +{ + struct v4l2_ctrl *ctrl; + int err, i; + struct v4l2_ctrl_handler *hdl = &sensor->ctrl_handler; + struct v4l2_ctrl_config ctrl_cfg = { + .ops = &irs1125_ctrl_ops, + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = U16_MAX, + .step = 1, + .def = 0x1000 + }; + + v4l2_ctrl_handler_init(hdl, ARRAY_SIZE(irs1125_custom_ctrls)); + + for (i = 0; i < ARRAY_SIZE(irs1125_custom_ctrls); i++) { + ctrl = v4l2_ctrl_new_custom(hdl, &irs1125_custom_ctrls[i], + NULL); + if (!ctrl) + dev_err(dev, "Failed to init custom control %s\n", + irs1125_custom_ctrls[i].name); + else if (irs1125_custom_ctrls[i].id == IRS1125_CID_NUM_SEQS) + sensor->ctrl_numseq = ctrl; + else if (irs1125_custom_ctrls[i].id == IRS1125_CID_MOD_PLL) + sensor->ctrl_modplls = ctrl; + } + + if (hdl->error) { + dev_err(dev, "Error %d adding controls\n", hdl->error); + err = hdl->error; + goto error_ctrls; + } + + for (i = 0; i < IRS1125_NUM_SEQ_ENTRIES; i++) { + ctrl_cfg.name = expo_ctrl_names[i]; + ctrl_cfg.id = IRS1125_CID_SAFE_RECONFIG_S0_EXPO + i * 2; + ctrl = v4l2_ctrl_new_custom(hdl, &ctrl_cfg, + NULL); + if (!ctrl) + dev_err(dev, "Failed to init exposure control %s\n", + ctrl_cfg.name); + } + + ctrl_cfg.def = 0; + for (i = 0; i < IRS1125_NUM_SEQ_ENTRIES; i++) { + ctrl_cfg.name = frame_ctrl_names[i]; + ctrl_cfg.id = IRS1125_CID_SAFE_RECONFIG_S0_FRAME + i * 2; + ctrl = v4l2_ctrl_new_custom(hdl, &ctrl_cfg, + NULL); + if (!ctrl) + dev_err(dev, "Failed to init framerate control %s\n", + ctrl_cfg.name); + } + + sensor->sd.ctrl_handler = hdl; + return 0; + +error_ctrls: + v4l2_ctrl_handler_free(&sensor->ctrl_handler); + return -err; +} + +static int irs1125_ident_setup(struct irs1125 *sensor, struct device *dev) +{ + int ret; + struct v4l2_ctrl *ctrl; + struct v4l2_subdev *sd; + u16 read; + + sd = &sensor->sd; + + ctrl = v4l2_ctrl_find(&sensor->ctrl_handler, IRS1125_CID_IDENT0); + if (!ctrl) { + dev_err(dev, "could not find device ctrl.\n"); + return -EINVAL; + } + + ret = irs1125_read(sd, IRS1125_REG_EFUSEVAL2, &read); + if (ret < 0) { + dev_err(dev, "error reading from i2c\n"); + return -EIO; + } + + v4l2_ctrl_s_ctrl(ctrl, read); + + ctrl = v4l2_ctrl_find(&sensor->ctrl_handler, IRS1125_CID_IDENT1); + if (!ctrl) { + dev_err(dev, "could not find device ctrl.\n"); + return -EINVAL; + } + + ret = irs1125_read(sd, IRS1125_REG_EFUSEVAL3, &read); + if (ret < 0) { + dev_err(dev, "error reading from i2c\n"); + return -EIO; + } + + v4l2_ctrl_s_ctrl(ctrl, read); + + ctrl = v4l2_ctrl_find(&sensor->ctrl_handler, IRS1125_CID_IDENT2); + if (!ctrl) { + dev_err(dev, "could not find device ctrl.\n"); + return -EINVAL; + } + + ret = irs1125_read(sd, IRS1125_REG_EFUSEVAL4, &read); + if (ret < 0) { + dev_err(dev, "error reading from i2c\n"); + return -EIO; + } + v4l2_ctrl_s_ctrl(ctrl, read & 0xFFFC); + + return 0; +} + +static int irs1125_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct irs1125 *sensor; + int ret; + struct fwnode_handle *endpoint; + u32 xclk_freq; + int gpio_num; + + sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL); + if (!sensor) + return -ENOMEM; + + v4l2_i2c_subdev_init(&sensor->sd, client, &irs1125_subdev_ops); + + /* Get CSI2 bus config */ + endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), + NULL); + if (!endpoint) { + dev_err(dev, "endpoint node not found\n"); + return -EINVAL; + } + + ret = v4l2_fwnode_endpoint_parse(endpoint, &sensor->ep); + fwnode_handle_put(endpoint); + if (ret) { + dev_err(dev, "Could not parse endpoint\n"); + return ret; + } + + /* get system clock (xclk) */ + sensor->xclk = devm_clk_get(dev, NULL); + if (IS_ERR(sensor->xclk)) { + dev_err(dev, "could not get xclk"); + return PTR_ERR(sensor->xclk); + } + + xclk_freq = clk_get_rate(sensor->xclk); + if (xclk_freq != 26000000) { + dev_err(dev, "Unsupported clock frequency: %u\n", xclk_freq); + return -EINVAL; + } + + sensor->num_seq = 5; + + /* Request the power down GPIO */ + sensor->reset = devm_gpiod_get(&client->dev, "pwdn", + GPIOD_OUT_LOW); + + if (IS_ERR(sensor->reset)) { + dev_err(dev, "could not get reset"); + return PTR_ERR(sensor->reset); + } + + gpio_num = desc_to_gpio(sensor->reset); + dev_dbg(&client->dev, "reset on GPIO num %d\n", gpio_num); + + mutex_init(&sensor->lock); + + ret = irs1125_ctrls_init(sensor, dev); + if (ret < 0) + goto mutex_remove; + + sensor->sd.internal_ops = &irs1125_subdev_internal_ops; + sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + sensor->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad); + if (ret < 0) + goto mutex_remove; + + gpiod_set_value_cansleep(sensor->reset, 1); + msleep(RESET_ACTIVE_DELAY_MS); + + ret = irs1125_detect(&sensor->sd); + if (ret < 0) + goto error; + + ret = irs1125_ident_setup(sensor, dev); + if (ret < 0) + goto error; + + gpiod_set_value_cansleep(sensor->reset, 0); + + ret = v4l2_async_register_subdev(&sensor->sd); + if (ret < 0) + goto error; + + dev_dbg(dev, "Infineon IRS1125 camera driver probed\n"); + + return 0; + +error: + media_entity_cleanup(&sensor->sd.entity); +mutex_remove: + mutex_destroy(&sensor->lock); + return ret; +} + +static void irs1125_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct irs1125 *irs1125 = to_state(sd); + + v4l2_async_unregister_subdev(&irs1125->sd); + media_entity_cleanup(&irs1125->sd.entity); + v4l2_device_unregister_subdev(sd); + mutex_destroy(&irs1125->lock); + v4l2_ctrl_handler_free(&irs1125->ctrl_handler); +} + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id irs1125_of_match[] = { + { .compatible = "infineon,irs1125" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, irs1125_of_match); +#endif + +static struct i2c_driver irs1125_driver = { + .driver = { + .of_match_table = of_match_ptr(irs1125_of_match), + .name = SENSOR_NAME, + }, + .probe = irs1125_probe, + .remove = irs1125_remove, +}; + +module_i2c_driver(irs1125_driver); + +MODULE_AUTHOR("Markus Proeller <markus.proeller@pieye.org>"); +MODULE_DESCRIPTION("Infineon irs1125 sensor driver"); +MODULE_LICENSE("GPL v2"); + diff --git a/drivers/media/i2c/irs1125.h b/drivers/media/i2c/irs1125.h new file mode 100644 index 00000000000000..96d676123d5ed6 --- /dev/null +++ b/drivers/media/i2c/irs1125.h @@ -0,0 +1,95 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * A V4L2 driver for Infineon IRS1125 TOF cameras. + * Copyright (C) 2018, pieye GmbH + * + * Based on V4L2 OmniVision OV5647 Image Sensor driver + * Copyright (C) 2016 Ramiro Oliveira <roliveir@synopsys.com> + * + * DT / fwnode changes, and GPIO control taken from ov5640.c + * Copyright (C) 2011-2013 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright (C) 2014-2017 Mentor Graphics Inc. + * + */ + +#ifndef IRS1125_H +#define IRS1125_H + +#include <linux/v4l2-controls.h> +#include <linux/types.h> + +#define IRS1125_NUM_SEQ_ENTRIES 20 +#define IRS1125_NUM_MOD_PLLS 4 + +#define IRS1125_CID_CUSTOM_BASE (V4L2_CID_USER_BASE | 0xf000) +#define IRS1125_CID_CONTINUOUS_TRIG (IRS1125_CID_CUSTOM_BASE + 1) +#define IRS1125_CID_TRIGGER (IRS1125_CID_CUSTOM_BASE + 2) +#define IRS1125_CID_RECONFIG (IRS1125_CID_CUSTOM_BASE + 3) +#define IRS1125_CID_ILLU_ON (IRS1125_CID_CUSTOM_BASE + 4) +#define IRS1125_CID_NUM_SEQS (IRS1125_CID_CUSTOM_BASE + 5) +#define IRS1125_CID_MOD_PLL (IRS1125_CID_CUSTOM_BASE + 6) +#define IRS1125_CID_SEQ_CONFIG (IRS1125_CID_CUSTOM_BASE + 7) +#define IRS1125_CID_IDENT0 (IRS1125_CID_CUSTOM_BASE + 8) +#define IRS1125_CID_IDENT1 (IRS1125_CID_CUSTOM_BASE + 9) +#define IRS1125_CID_IDENT2 (IRS1125_CID_CUSTOM_BASE + 10) +#define IRS1125_CID_SAFE_RECONFIG_S0_EXPO (IRS1125_CID_CUSTOM_BASE + 11) +#define IRS1125_CID_SAFE_RECONFIG_S0_FRAME (IRS1125_CID_CUSTOM_BASE + 12) +#define IRS1125_CID_SAFE_RECONFIG_S1_EXPO (IRS1125_CID_CUSTOM_BASE + 13) +#define IRS1125_CID_SAFE_RECONFIG_S1_FRAME (IRS1125_CID_CUSTOM_BASE + 14) +#define IRS1125_CID_SAFE_RECONFIG_S2_EXPO (IRS1125_CID_CUSTOM_BASE + 15) +#define IRS1125_CID_SAFE_RECONFIG_S2_FRAME (IRS1125_CID_CUSTOM_BASE + 16) +#define IRS1125_CID_SAFE_RECONFIG_S3_EXPO (IRS1125_CID_CUSTOM_BASE + 17) +#define IRS1125_CID_SAFE_RECONFIG_S3_FRAME (IRS1125_CID_CUSTOM_BASE + 18) +#define IRS1125_CID_SAFE_RECONFIG_S4_EXPO (IRS1125_CID_CUSTOM_BASE + 19) +#define IRS1125_CID_SAFE_RECONFIG_S4_FRAME (IRS1125_CID_CUSTOM_BASE + 20) +#define IRS1125_CID_SAFE_RECONFIG_S5_EXPO (IRS1125_CID_CUSTOM_BASE + 21) +#define IRS1125_CID_SAFE_RECONFIG_S5_FRAME (IRS1125_CID_CUSTOM_BASE + 22) +#define IRS1125_CID_SAFE_RECONFIG_S6_EXPO (IRS1125_CID_CUSTOM_BASE + 23) +#define IRS1125_CID_SAFE_RECONFIG_S6_FRAME (IRS1125_CID_CUSTOM_BASE + 24) +#define IRS1125_CID_SAFE_RECONFIG_S7_EXPO (IRS1125_CID_CUSTOM_BASE + 25) +#define IRS1125_CID_SAFE_RECONFIG_S7_FRAME (IRS1125_CID_CUSTOM_BASE + 26) +#define IRS1125_CID_SAFE_RECONFIG_S8_EXPO (IRS1125_CID_CUSTOM_BASE + 27) +#define IRS1125_CID_SAFE_RECONFIG_S8_FRAME (IRS1125_CID_CUSTOM_BASE + 28) +#define IRS1125_CID_SAFE_RECONFIG_S9_EXPO (IRS1125_CID_CUSTOM_BASE + 29) +#define IRS1125_CID_SAFE_RECONFIG_S9_FRAME (IRS1125_CID_CUSTOM_BASE + 30) +#define IRS1125_CID_SAFE_RECONFIG_S10_EXPO (IRS1125_CID_CUSTOM_BASE + 31) +#define IRS1125_CID_SAFE_RECONFIG_S10_FRAME (IRS1125_CID_CUSTOM_BASE + 32) +#define IRS1125_CID_SAFE_RECONFIG_S11_EXPO (IRS1125_CID_CUSTOM_BASE + 33) +#define IRS1125_CID_SAFE_RECONFIG_S11_FRAME (IRS1125_CID_CUSTOM_BASE + 34) +#define IRS1125_CID_SAFE_RECONFIG_S12_EXPO (IRS1125_CID_CUSTOM_BASE + 35) +#define IRS1125_CID_SAFE_RECONFIG_S12_FRAME (IRS1125_CID_CUSTOM_BASE + 36) +#define IRS1125_CID_SAFE_RECONFIG_S13_EXPO (IRS1125_CID_CUSTOM_BASE + 37) +#define IRS1125_CID_SAFE_RECONFIG_S13_FRAME (IRS1125_CID_CUSTOM_BASE + 38) +#define IRS1125_CID_SAFE_RECONFIG_S14_EXPO (IRS1125_CID_CUSTOM_BASE + 39) +#define IRS1125_CID_SAFE_RECONFIG_S14_FRAME (IRS1125_CID_CUSTOM_BASE + 40) +#define IRS1125_CID_SAFE_RECONFIG_S15_EXPO (IRS1125_CID_CUSTOM_BASE + 41) +#define IRS1125_CID_SAFE_RECONFIG_S15_FRAME (IRS1125_CID_CUSTOM_BASE + 42) +#define IRS1125_CID_SAFE_RECONFIG_S16_EXPO (IRS1125_CID_CUSTOM_BASE + 43) +#define IRS1125_CID_SAFE_RECONFIG_S16_FRAME (IRS1125_CID_CUSTOM_BASE + 44) +#define IRS1125_CID_SAFE_RECONFIG_S17_EXPO (IRS1125_CID_CUSTOM_BASE + 45) +#define IRS1125_CID_SAFE_RECONFIG_S17_FRAME (IRS1125_CID_CUSTOM_BASE + 46) +#define IRS1125_CID_SAFE_RECONFIG_S18_EXPO (IRS1125_CID_CUSTOM_BASE + 47) +#define IRS1125_CID_SAFE_RECONFIG_S18_FRAME (IRS1125_CID_CUSTOM_BASE + 48) +#define IRS1125_CID_SAFE_RECONFIG_S19_EXPO (IRS1125_CID_CUSTOM_BASE + 49) +#define IRS1125_CID_SAFE_RECONFIG_S19_FRAME (IRS1125_CID_CUSTOM_BASE + 50) + +struct irs1125_seq_cfg { + __u16 exposure; + __u16 framerate; + __u16 ps; + __u16 pll; +}; + +struct irs1125_mod_pll { + __u16 pllcfg1; + __u16 pllcfg2; + __u16 pllcfg3; + __u16 pllcfg4; + __u16 pllcfg5; + __u16 pllcfg6; + __u16 pllcfg7; + __u16 pllcfg8; +}; + +#endif /* IRS1125 */ + diff --git a/drivers/media/i2c/ov2311.c b/drivers/media/i2c/ov2311.c new file mode 100644 index 00000000000000..3680561ad779b7 --- /dev/null +++ b/drivers/media/i2c/ov2311.c @@ -0,0 +1,1178 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Omnivision OV2311 1600x1300 global shutter image sensor driver + * Copyright (C) 2022, Raspberry Pi (Trading) Ltd + * + * This driver is based on the OV9281 driver. + * Copyright (C) 2017 Fuzhou Rockchip Electronics Co., Ltd. + * Register configuration from + * https://github.com/ArduCAM/ArduCAM_USB_Camera_Shield/tree/master/Config/USB3.0_UC-425_Rev.C%2BUC-628_Rev.B/OV2311 + * with additional exposure and gain register information from + * https://github.com/renesas-rcar/linux-bsp/tree/0cf6e36f5bf49e1c2aab87139ec5b588623c56f8/drivers/media/i2c/imagers + */ + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> +#include <media/media-entity.h> +#include <media/v4l2-async.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-event.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-subdev.h> + +#define OV2311_LINK_FREQ 400000000 +#define OV2311_LANES 2 + +/* pixel rate = link frequency * 2 * lanes / BITS_PER_SAMPLE */ +#define OV2311_PIXEL_RATE_10BIT (OV2311_LINK_FREQ * 2 * \ + OV2311_LANES / 10) +#define OV2311_PIXEL_RATE_8BIT (OV2311_LINK_FREQ * 2 * \ + OV2311_LANES / 8) +#define OV2311_XVCLK_FREQ 24000000 + +#define CHIP_ID 0x2311 +#define OV2311_REG_CHIP_ID 0x300a + +#define OV2311_REG_CTRL_MODE 0x0100 +#define OV2311_MODE_SW_STANDBY 0x0 +#define OV2311_MODE_STREAMING BIT(0) + +#define OV2311_REG_V_FLIP 0x3820 +#define OV2311_REG_H_FLIP 0x3821 +#define OV2311_FLIP_BIT BIT(2) + +#define OV2311_REG_EXPOSURE 0x3501 +#define OV2311_EXPOSURE_MIN 4 +#define OV2311_EXPOSURE_STEP 1 +#define OV2311_VTS_MAX 0xffff + +#define OV2311_REG_GAIN_H 0x3508 +#define OV2311_REG_GAIN_L 0x3509 +#define OV2311_GAIN_H_MASK 0x07 +#define OV2311_GAIN_H_SHIFT 8 +#define OV2311_GAIN_L_MASK 0xff +#define OV2311_GAIN_MIN 0x100 +#define OV2311_GAIN_MAX 0x780 +#define OV2311_GAIN_STEP 1 +#define OV2311_GAIN_DEFAULT OV2311_GAIN_MIN + +#define OV2311_REG_TEST_PATTERN 0x5e00 +#define OV2311_TEST_PATTERN_ENABLE 0x80 +#define OV2311_TEST_PATTERN_DISABLE 0x0 + +#define OV2311_REG_VTS 0x380e + +/* + * OV2311 native and active pixel array size. + * Datasheet not available to confirm these values. renesas-rcar linux-bsp tree + * has these values. + */ +#define OV2311_NATIVE_WIDTH 1616U +#define OV2311_NATIVE_HEIGHT 1316U +#define OV2311_PIXEL_ARRAY_LEFT 8U +#define OV2311_PIXEL_ARRAY_TOP 8U +#define OV2311_PIXEL_ARRAY_WIDTH 1600U +#define OV2311_PIXEL_ARRAY_HEIGHT 1300U + +#define REG_NULL 0xFFFF + +#define OV2311_REG_VALUE_08BIT 1 +#define OV2311_REG_VALUE_16BIT 2 +#define OV2311_REG_VALUE_24BIT 3 + +#define OV2311_NAME "ov2311" + +static const char * const ov2311_supply_names[] = { + "avdd", /* Analog power */ + "dovdd", /* Digital I/O power */ + "dvdd", /* Digital core power */ +}; + +#define OV2311_NUM_SUPPLIES ARRAY_SIZE(ov2311_supply_names) + +struct regval { + u16 addr; + u8 val; +}; + +struct ov2311_mode { + u32 width; + u32 height; + u32 hts_def; + u32 vts_def; + u32 exp_def; + struct v4l2_rect crop; + const struct regval *reg_list; +}; + +struct ov2311 { + struct i2c_client *client; + struct clk *xvclk; + struct gpio_desc *reset_gpio; + struct gpio_desc *pwdn_gpio; + struct regulator_bulk_data supplies[OV2311_NUM_SUPPLIES]; + + struct v4l2_subdev subdev; + struct media_pad pad; + struct v4l2_ctrl_handler ctrl_handler; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *pixel_rate; + /* + * Mutex for serialized access: + * Protect sensor module set pad format and start/stop streaming safely. + */ + struct mutex mutex; + + /* Streaming on/off */ + bool streaming; + + const struct ov2311_mode *cur_mode; + u32 code; +}; + +#define to_ov2311(sd) container_of(sd, struct ov2311, subdev) + +/* + * Xclk 24Mhz + * max_framerate 60fps for 10 bit, 74.6fps for 8 bit. + */ +static const struct regval ov2311_common_regs[] = { + { 0x0103, 0x01 }, + { 0x0100, 0x00 }, + { 0x0300, 0x01 }, + { 0x0302, 0x32 }, + { 0x0303, 0x00 }, + { 0x0304, 0x03 }, + { 0x0305, 0x02 }, + { 0x0306, 0x01 }, + { 0x030e, 0x04 }, + { 0x3001, 0x02 }, + { 0x3004, 0x00 }, + { 0x3005, 0x00 }, + { 0x3006, 0x00 }, + { 0x3011, 0x0d }, + { 0x3014, 0x04 }, + { 0x301c, 0xf0 }, + { 0x3020, 0x00 }, + { 0x302c, 0x00 }, + { 0x302d, 0x12 }, + { 0x302e, 0x4c }, + { 0x302f, 0x8c }, + { 0x3030, 0x10 }, + { 0x303f, 0x03 }, + { 0x3103, 0x00 }, + { 0x3106, 0x08 }, + { 0x31ff, 0x01 }, + { 0x3501, 0x05 }, + { 0x3502, 0xba }, + { 0x3506, 0x00 }, + { 0x3507, 0x00 }, + { 0x3620, 0x67 }, + { 0x3633, 0x78 }, + { 0x3666, 0x00 }, + { 0x3670, 0x68 }, + { 0x3674, 0x10 }, + { 0x3675, 0x00 }, + { 0x3680, 0x84 }, + { 0x36a2, 0x04 }, + { 0x36a3, 0x80 }, + { 0x36b0, 0x00 }, + { 0x3700, 0x35 }, + { 0x3704, 0x59 }, + { 0x3712, 0x00 }, + { 0x3713, 0x02 }, + { 0x379b, 0x01 }, + { 0x379c, 0x10 }, + { 0x3800, 0x00 }, + { 0x3801, 0x00 }, + { 0x3804, 0x06 }, + { 0x3805, 0x4f }, + { 0x3810, 0x00 }, + { 0x3811, 0x08 }, + { 0x3812, 0x00 }, + { 0x3813, 0x08 }, + { 0x3814, 0x11 }, + { 0x3815, 0x11 }, + { 0x3816, 0x00 }, + { 0x3817, 0x00 }, + { 0x3818, 0x04 }, + { 0x3819, 0x00 }, + { 0x382b, 0x5a }, + { 0x382c, 0x09 }, + { 0x382d, 0x9a }, + { 0x3882, 0x02 }, + { 0x3883, 0x6c }, + { 0x3885, 0x07 }, + { 0x389d, 0x03 }, + { 0x38a6, 0x00 }, + { 0x38a7, 0x01 }, + { 0x38b3, 0x07 }, + { 0x38b1, 0x00 }, + { 0x38e5, 0x02 }, + { 0x38e7, 0x00 }, + { 0x38e8, 0x00 }, + { 0x3910, 0xff }, + { 0x3911, 0xff }, + { 0x3912, 0x08 }, + { 0x3913, 0x00 }, + { 0x3914, 0x00 }, + { 0x3915, 0x00 }, + { 0x391c, 0x00 }, + { 0x3920, 0xa5 }, + { 0x3921, 0x00 }, + { 0x3922, 0x00 }, + { 0x3923, 0x00 }, + { 0x3924, 0x05 }, + { 0x3925, 0x00 }, + { 0x3926, 0x00 }, + { 0x3927, 0x00 }, + { 0x3928, 0x1a }, + { 0x392d, 0x05 }, + { 0x392e, 0xf2 }, + { 0x392f, 0x40 }, + { 0x4001, 0x00 }, + { 0x4003, 0x40 }, + { 0x4008, 0x12 }, + { 0x4009, 0x1b }, + { 0x400c, 0x0c }, + { 0x400d, 0x13 }, + { 0x4010, 0xf0 }, + { 0x4011, 0x00 }, + { 0x4016, 0x00 }, + { 0x4017, 0x04 }, + { 0x4042, 0x11 }, + { 0x4043, 0x70 }, + { 0x4045, 0x00 }, + { 0x4409, 0x5f }, + { 0x450b, 0x00 }, + { 0x4600, 0x00 }, + { 0x4601, 0xa0 }, + { 0x4708, 0x09 }, + { 0x470c, 0x81 }, + { 0x4710, 0x06 }, + { 0x4711, 0x00 }, + { 0x4800, 0x00 }, + { 0x481f, 0x30 }, + { 0x4837, 0x14 }, + { 0x4f00, 0x00 }, + { 0x4f07, 0x00 }, + { 0x4f08, 0x03 }, + { 0x4f09, 0x08 }, + { 0x4f0c, 0x06 }, + { 0x4f0d, 0x02 }, + { 0x4f10, 0x00 }, + { 0x4f11, 0x00 }, + { 0x4f12, 0x07 }, + { 0x4f13, 0xe2 }, + { 0x5000, 0x9f }, + { 0x5001, 0x20 }, + { 0x5026, 0x00 }, + { 0x5c00, 0x00 }, + { 0x5c01, 0x2c }, + { 0x5c02, 0x00 }, + { 0x5c03, 0x7f }, + { 0x5e00, 0x00 }, + { 0x5e01, 0x41 }, + {REG_NULL, 0x00}, +}; + +static const struct regval ov2311_1600x1300_regs[] = { + { 0x3802, 0x00 }, + { 0x3803, 0x00 }, + { 0x3806, 0x05 }, + { 0x3807, 0x23 }, + { 0x3808, 0x06 }, + { 0x3809, 0x40 }, + { 0x380a, 0x05 }, + { 0x380b, 0x14 }, + { 0x380c, 0x03 }, + { 0x380d, 0x88 }, + {REG_NULL, 0x00}, +}; + +static const struct regval ov2311_1600x1080_regs[] = { + { 0x3802, 0x00 }, + { 0x3803, 0x6e }, + { 0x3806, 0x04 }, + { 0x3807, 0xae }, + { 0x3808, 0x06 }, + { 0x3809, 0x40 }, + { 0x380a, 0x04 }, + { 0x380b, 0x38 }, + { 0x380c, 0x03 }, + { 0x380d, 0x88 }, + + { 0x5d01, 0x00 }, + { 0x5d02, 0x04 }, + { 0x5d03, 0x00 }, + { 0x5d04, 0x04 }, + { 0x5d05, 0x00 }, + {REG_NULL, 0x00}, +}; + +static const struct regval op_10bit[] = { + { 0x030d, 0x5a }, + { 0x3662, 0x65 }, + {REG_NULL, 0x00}, +}; + +static const struct regval op_8bit[] = { + { 0x030d, 0x70 }, + { 0x3662, 0x67 }, + {REG_NULL, 0x00}, +}; + +static const struct ov2311_mode supported_modes[] = { + { + .width = 1600, + .height = 1300, + .exp_def = 0x0320, + .hts_def = (0x0388 * 2),/* Registers 0x380c / 0x380d * 2 */ + .vts_def = 0x5c2, /* Registers 0x380e / 0x380f + * 60fps for 10bpp + */ + .crop = { + .left = OV2311_PIXEL_ARRAY_LEFT, + .top = OV2311_PIXEL_ARRAY_TOP, + .width = 1600, + .height = 1300 + }, + .reg_list = ov2311_1600x1300_regs, + }, + { + .width = 1600, + .height = 1080, + .exp_def = 0x0320, + .hts_def = (0x0388 * 2),/* Registers 0x380c / 0x380d * 2 */ + .vts_def = 0x5c2, /* Registers 0x380e / 0x380f + * 60fps for 10bpp + */ + .crop = { + .left = OV2311_PIXEL_ARRAY_LEFT, + .top = 110 + OV2311_PIXEL_ARRAY_TOP, + .width = 1600, + .height = 1080 + }, + .reg_list = ov2311_1600x1080_regs, + }, +}; + +static const s64 link_freq_menu_items[] = { + OV2311_LINK_FREQ +}; + +static const char * const ov2311_test_pattern_menu[] = { + "Disabled", + "Vertical Color Bar Type 1", + "Vertical Color Bar Type 2", + "Vertical Color Bar Type 3", + "Vertical Color Bar Type 4" +}; + +/* Write registers up to 4 at a time */ +static int ov2311_write_reg(struct i2c_client *client, u16 reg, + u32 len, u32 val) +{ + u32 buf_i, val_i; + u8 buf[6]; + u8 *val_p; + __be32 val_be; + + if (len > 4) + return -EINVAL; + + buf[0] = reg >> 8; + buf[1] = reg & 0xff; + + val_be = cpu_to_be32(val); + val_p = (u8 *)&val_be; + buf_i = 2; + val_i = 4 - len; + + while (val_i < 4) + buf[buf_i++] = val_p[val_i++]; + + if (i2c_master_send(client, buf, len + 2) != len + 2) + return -EIO; + + return 0; +} + +static int ov2311_write_array(struct i2c_client *client, + const struct regval *regs) +{ + u32 i; + int ret = 0; + + for (i = 0; ret == 0 && regs[i].addr != REG_NULL; i++) + ret = ov2311_write_reg(client, regs[i].addr, + OV2311_REG_VALUE_08BIT, regs[i].val); + + return ret; +} + +/* Read registers up to 4 at a time */ +static int ov2311_read_reg(struct i2c_client *client, u16 reg, unsigned int len, + u32 *val) +{ + struct i2c_msg msgs[2]; + u8 *data_be_p; + __be32 data_be = 0; + __be16 reg_addr_be = cpu_to_be16(reg); + int ret; + + if (len > 4 || !len) + return -EINVAL; + + data_be_p = (u8 *)&data_be; + /* Write register address */ + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = 2; + msgs[0].buf = (u8 *)®_addr_be; + + /* Read data from register */ + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = &data_be_p[4 - len]; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + *val = be32_to_cpu(data_be); + + return 0; +} + +static int ov2311_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct ov2311 *ov2311 = to_ov2311(sd); + const struct ov2311_mode *mode; + s64 h_blank, vblank_def, pixel_rate; + + mutex_lock(&ov2311->mutex); + + mode = v4l2_find_nearest_size(supported_modes, + ARRAY_SIZE(supported_modes), + width, height, + fmt->format.width, + fmt->format.height); + if (fmt->format.code != MEDIA_BUS_FMT_Y8_1X8) + fmt->format.code = MEDIA_BUS_FMT_Y10_1X10; + fmt->format.width = mode->width; + fmt->format.height = mode->height; + fmt->format.field = V4L2_FIELD_NONE; + fmt->format.colorspace = V4L2_COLORSPACE_RAW; + fmt->format.ycbcr_enc = + V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->format.colorspace); + fmt->format.quantization = + V4L2_MAP_QUANTIZATION_DEFAULT(true, fmt->format.colorspace, + fmt->format.ycbcr_enc); + fmt->format.xfer_func = + V4L2_MAP_XFER_FUNC_DEFAULT(fmt->format.colorspace); + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + *v4l2_subdev_state_get_format(sd_state, fmt->pad) = + fmt->format; + } else { + ov2311->cur_mode = mode; + ov2311->code = fmt->format.code; + h_blank = mode->hts_def - mode->width; + __v4l2_ctrl_modify_range(ov2311->hblank, h_blank, + h_blank, 1, h_blank); + __v4l2_ctrl_s_ctrl(ov2311->hblank, h_blank); + vblank_def = mode->vts_def - mode->height; + __v4l2_ctrl_modify_range(ov2311->vblank, vblank_def, + OV2311_VTS_MAX - mode->height, + 1, vblank_def); + __v4l2_ctrl_s_ctrl(ov2311->vblank, vblank_def); + + pixel_rate = (fmt->format.code == MEDIA_BUS_FMT_Y10_1X10) ? + OV2311_PIXEL_RATE_10BIT : OV2311_PIXEL_RATE_8BIT; + __v4l2_ctrl_modify_range(ov2311->pixel_rate, pixel_rate, + pixel_rate, 1, pixel_rate); + } + + mutex_unlock(&ov2311->mutex); + + return 0; +} + +static int ov2311_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct ov2311 *ov2311 = to_ov2311(sd); + const struct ov2311_mode *mode = ov2311->cur_mode; + + mutex_lock(&ov2311->mutex); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + fmt->format = *v4l2_subdev_state_get_format(sd_state, fmt->pad); + } else { + fmt->format.width = mode->width; + fmt->format.height = mode->height; + fmt->format.code = ov2311->code; + fmt->format.field = V4L2_FIELD_NONE; + fmt->format.colorspace = V4L2_COLORSPACE_SRGB; + fmt->format.ycbcr_enc = + V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->format.colorspace); + fmt->format.quantization = + V4L2_MAP_QUANTIZATION_DEFAULT(true, + fmt->format.colorspace, + fmt->format.ycbcr_enc); + fmt->format.xfer_func = + V4L2_MAP_XFER_FUNC_DEFAULT(fmt->format.colorspace); + } + mutex_unlock(&ov2311->mutex); + + return 0; +} + +static int ov2311_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + switch (code->index) { + default: + return -EINVAL; + case 0: + code->code = MEDIA_BUS_FMT_Y10_1X10; + break; + case 1: + code->code = MEDIA_BUS_FMT_Y8_1X8; + break; + } + + return 0; +} + +static int ov2311_enum_frame_sizes(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->index >= ARRAY_SIZE(supported_modes)) + return -EINVAL; + + if (fse->code != MEDIA_BUS_FMT_Y10_1X10 && + fse->code != MEDIA_BUS_FMT_Y8_1X8) + return -EINVAL; + + fse->min_width = supported_modes[fse->index].width; + fse->max_width = supported_modes[fse->index].width; + fse->max_height = supported_modes[fse->index].height; + fse->min_height = supported_modes[fse->index].height; + + return 0; +} + +static int ov2311_enable_test_pattern(struct ov2311 *ov2311, u32 pattern) +{ + u32 val; + + if (pattern) + val = (pattern - 1) | OV2311_TEST_PATTERN_ENABLE; + else + val = OV2311_TEST_PATTERN_DISABLE; + + return ov2311_write_reg(ov2311->client, OV2311_REG_TEST_PATTERN, + OV2311_REG_VALUE_08BIT, val); +} + +static const struct v4l2_rect * +__ov2311_get_pad_crop(struct ov2311 *ov2311, struct v4l2_subdev_state *sd_state, + unsigned int pad, enum v4l2_subdev_format_whence which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_state_get_crop(sd_state, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &ov2311->cur_mode->crop; + } + + return NULL; +} + +static int ov2311_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + switch (sel->target) { + case V4L2_SEL_TGT_CROP: { + struct ov2311 *ov2311 = to_ov2311(sd); + + mutex_lock(&ov2311->mutex); + sel->r = *__ov2311_get_pad_crop(ov2311, sd_state, sel->pad, + sel->which); + mutex_unlock(&ov2311->mutex); + + return 0; + } + + case V4L2_SEL_TGT_NATIVE_SIZE: + sel->r.top = 0; + sel->r.left = 0; + sel->r.width = OV2311_NATIVE_WIDTH; + sel->r.height = OV2311_NATIVE_HEIGHT; + + return 0; + + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.top = OV2311_PIXEL_ARRAY_TOP; + sel->r.left = OV2311_PIXEL_ARRAY_LEFT; + sel->r.width = OV2311_PIXEL_ARRAY_WIDTH; + sel->r.height = OV2311_PIXEL_ARRAY_HEIGHT; + + return 0; + } + + return -EINVAL; +} + +static int ov2311_start_stream(struct ov2311 *ov2311) +{ + int ret; + + ret = ov2311_write_array(ov2311->client, ov2311_common_regs); + if (ret) + return ret; + + ret = ov2311_write_array(ov2311->client, ov2311->cur_mode->reg_list); + if (ret) + return ret; + + if (ov2311->code == MEDIA_BUS_FMT_Y10_1X10) + ret = ov2311_write_array(ov2311->client, op_10bit); + else + ret = ov2311_write_array(ov2311->client, op_8bit); + if (ret) + return ret; + + /* In case these controls are set before streaming */ + mutex_unlock(&ov2311->mutex); + ret = v4l2_ctrl_handler_setup(&ov2311->ctrl_handler); + mutex_lock(&ov2311->mutex); + if (ret) + return ret; + + return ov2311_write_reg(ov2311->client, OV2311_REG_CTRL_MODE, + OV2311_REG_VALUE_08BIT, OV2311_MODE_STREAMING); +} + +static int ov2311_stop_stream(struct ov2311 *ov2311) +{ + return ov2311_write_reg(ov2311->client, OV2311_REG_CTRL_MODE, + OV2311_REG_VALUE_08BIT, OV2311_MODE_SW_STANDBY); +} + +static int ov2311_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct ov2311 *ov2311 = to_ov2311(sd); + struct i2c_client *client = ov2311->client; + int ret = 0; + + mutex_lock(&ov2311->mutex); + if (ov2311->streaming == enable) { + mutex_unlock(&ov2311->mutex); + return 0; + } + + if (enable) { + ret = pm_runtime_resume_and_get(&client->dev); + if (ret < 0) + goto unlock_and_return; + + ret = ov2311_start_stream(ov2311); + if (ret) { + v4l2_err(sd, "start stream failed while write regs\n"); + pm_runtime_put(&client->dev); + goto unlock_and_return; + } + } else { + ov2311_stop_stream(ov2311); + pm_runtime_put(&client->dev); + } + + ov2311->streaming = enable; + +unlock_and_return: + mutex_unlock(&ov2311->mutex); + + return ret; +} + +static int ov2311_power_on(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct ov2311 *ov2311 = to_ov2311(sd); + int ret; + + ret = clk_set_rate(ov2311->xvclk, OV2311_XVCLK_FREQ); + if (ret < 0) + dev_warn(dev, "Failed to set xvclk rate (24MHz)\n"); + if (clk_get_rate(ov2311->xvclk) != OV2311_XVCLK_FREQ) + dev_warn(dev, "xvclk mismatched, modes are based on 24MHz - rate is %lu\n", + clk_get_rate(ov2311->xvclk)); + + ret = clk_prepare_enable(ov2311->xvclk); + if (ret < 0) { + dev_err(dev, "Failed to enable xvclk\n"); + return ret; + } + + gpiod_set_value_cansleep(ov2311->reset_gpio, 0); + + ret = regulator_bulk_enable(OV2311_NUM_SUPPLIES, ov2311->supplies); + if (ret < 0) { + dev_err(dev, "Failed to enable regulators\n"); + goto disable_clk; + } + + gpiod_set_value_cansleep(ov2311->reset_gpio, 1); + + usleep_range(500, 1000); + gpiod_set_value_cansleep(ov2311->pwdn_gpio, 1); + + usleep_range(1000, 2000); + + return 0; + +disable_clk: + clk_disable_unprepare(ov2311->xvclk); + + return ret; +} + +static int ov2311_power_off(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct ov2311 *ov2311 = to_ov2311(sd); + + gpiod_set_value_cansleep(ov2311->pwdn_gpio, 0); + clk_disable_unprepare(ov2311->xvclk); + gpiod_set_value_cansleep(ov2311->reset_gpio, 0); + regulator_bulk_disable(OV2311_NUM_SUPPLIES, ov2311->supplies); + + return 0; +} + +static int ov2311_runtime_resume(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct ov2311 *ov2311 = to_ov2311(sd); + int ret; + + if (ov2311->streaming) { + ret = ov2311_start_stream(ov2311); + if (ret) + goto error; + } + return 0; + +error: + ov2311_stop_stream(ov2311); + ov2311->streaming = 0; + return ret; +} + +static int ov2311_runtime_suspend(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct ov2311 *ov2311 = to_ov2311(sd); + + if (ov2311->streaming) + ov2311_stop_stream(ov2311); + + return 0; +} + +static int ov2311_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct ov2311 *ov2311 = to_ov2311(sd); + struct v4l2_mbus_framefmt *try_fmt = + v4l2_subdev_state_get_format(fh->state, 0); + const struct ov2311_mode *def_mode = &supported_modes[0]; + + mutex_lock(&ov2311->mutex); + /* Initialize try_fmt */ + try_fmt->width = def_mode->width; + try_fmt->height = def_mode->height; + try_fmt->code = MEDIA_BUS_FMT_Y10_1X10; + try_fmt->field = V4L2_FIELD_NONE; + try_fmt->colorspace = V4L2_COLORSPACE_RAW; + try_fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(try_fmt->colorspace); + try_fmt->quantization = + V4L2_MAP_QUANTIZATION_DEFAULT(true, try_fmt->colorspace, + try_fmt->ycbcr_enc); + try_fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(try_fmt->colorspace); + + mutex_unlock(&ov2311->mutex); + /* No crop or compose */ + + return 0; +} + +static const struct dev_pm_ops ov2311_pm_ops = { + SET_RUNTIME_PM_OPS(ov2311_runtime_suspend, ov2311_runtime_resume, NULL) + SET_RUNTIME_PM_OPS(ov2311_power_off, ov2311_power_on, NULL) +}; + +static const struct v4l2_subdev_internal_ops ov2311_internal_ops = { + .open = ov2311_open, +}; + +static const struct v4l2_subdev_core_ops ov2311_core_ops = { + .subscribe_event = v4l2_ctrl_subdev_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_video_ops ov2311_video_ops = { + .s_stream = ov2311_s_stream, +}; + +static const struct v4l2_subdev_pad_ops ov2311_pad_ops = { + .enum_mbus_code = ov2311_enum_mbus_code, + .enum_frame_size = ov2311_enum_frame_sizes, + .get_fmt = ov2311_get_fmt, + .set_fmt = ov2311_set_fmt, + .get_selection = ov2311_get_selection, +}; + +static const struct v4l2_subdev_ops ov2311_subdev_ops = { + .core = &ov2311_core_ops, + .video = &ov2311_video_ops, + .pad = &ov2311_pad_ops, +}; + +static int ov2311_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ov2311 *ov2311 = container_of(ctrl->handler, + struct ov2311, ctrl_handler); + struct i2c_client *client = ov2311->client; + s64 max; + int ret = 0; + + /* Propagate change of current control to all related controls */ + switch (ctrl->id) { + case V4L2_CID_VBLANK: + /* Update max exposure while meeting expected vblanking */ + max = ov2311->cur_mode->height + ctrl->val - 4; + __v4l2_ctrl_modify_range(ov2311->exposure, + ov2311->exposure->minimum, max, + ov2311->exposure->step, + ov2311->exposure->default_value); + break; + } + + if (pm_runtime_get(&client->dev) <= 0) + return 0; + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + ret = ov2311_write_reg(ov2311->client, OV2311_REG_EXPOSURE, + OV2311_REG_VALUE_16BIT, ctrl->val); + break; + case V4L2_CID_ANALOGUE_GAIN: + ret = ov2311_write_reg(ov2311->client, OV2311_REG_GAIN_H, + OV2311_REG_VALUE_08BIT, + (ctrl->val >> OV2311_GAIN_H_SHIFT) & + OV2311_GAIN_H_MASK); + ret |= ov2311_write_reg(ov2311->client, OV2311_REG_GAIN_L, + OV2311_REG_VALUE_08BIT, + ctrl->val & OV2311_GAIN_L_MASK); + break; + case V4L2_CID_VBLANK: + ret = ov2311_write_reg(ov2311->client, OV2311_REG_VTS, + OV2311_REG_VALUE_16BIT, + ctrl->val + ov2311->cur_mode->height); + break; + case V4L2_CID_TEST_PATTERN: + ret = ov2311_enable_test_pattern(ov2311, ctrl->val); + break; + case V4L2_CID_HFLIP: + ret = ov2311_write_reg(ov2311->client, OV2311_REG_H_FLIP, + OV2311_REG_VALUE_08BIT, + ctrl->val ? OV2311_FLIP_BIT : 0); + break; + case V4L2_CID_VFLIP: + ret = ov2311_write_reg(ov2311->client, OV2311_REG_V_FLIP, + OV2311_REG_VALUE_08BIT, + ctrl->val ? OV2311_FLIP_BIT : 0); + break; + default: + dev_warn(&client->dev, "%s Unhandled id:0x%x, val:0x%x\n", + __func__, ctrl->id, ctrl->val); + break; + } + + pm_runtime_put(&client->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops ov2311_ctrl_ops = { + .s_ctrl = ov2311_set_ctrl, +}; + +static int ov2311_initialize_controls(struct ov2311 *ov2311) +{ + struct v4l2_fwnode_device_properties props; + const struct ov2311_mode *mode; + struct v4l2_ctrl_handler *handler; + struct v4l2_ctrl *ctrl; + s64 exposure_max, vblank_def; + u32 h_blank; + int ret; + + handler = &ov2311->ctrl_handler; + mode = ov2311->cur_mode; + ret = v4l2_ctrl_handler_init(handler, 11); + if (ret) + return ret; + handler->lock = &ov2311->mutex; + + ctrl = v4l2_ctrl_new_int_menu(handler, NULL, V4L2_CID_LINK_FREQ, + 0, 0, link_freq_menu_items); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + ov2311->pixel_rate = v4l2_ctrl_new_std(handler, NULL, + V4L2_CID_PIXEL_RATE, + OV2311_PIXEL_RATE_10BIT, + OV2311_PIXEL_RATE_10BIT, 1, + OV2311_PIXEL_RATE_10BIT); + + h_blank = mode->hts_def - mode->width; + ov2311->hblank = v4l2_ctrl_new_std(handler, NULL, V4L2_CID_HBLANK, + h_blank, h_blank, 1, h_blank); + if (ov2311->hblank) + ov2311->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + vblank_def = mode->vts_def - mode->height; + ov2311->vblank = v4l2_ctrl_new_std(handler, &ov2311_ctrl_ops, + V4L2_CID_VBLANK, vblank_def, + OV2311_VTS_MAX - mode->height, 1, + vblank_def); + + exposure_max = mode->vts_def - 4; + ov2311->exposure = v4l2_ctrl_new_std(handler, &ov2311_ctrl_ops, + V4L2_CID_EXPOSURE, + OV2311_EXPOSURE_MIN, exposure_max, + OV2311_EXPOSURE_STEP, + mode->exp_def); + + v4l2_ctrl_new_std(handler, &ov2311_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + OV2311_GAIN_MIN, OV2311_GAIN_MAX, OV2311_GAIN_STEP, + OV2311_GAIN_DEFAULT); + + v4l2_ctrl_new_std_menu_items(handler, &ov2311_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(ov2311_test_pattern_menu) - 1, + 0, 0, ov2311_test_pattern_menu); + + v4l2_ctrl_new_std(handler, &ov2311_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + + v4l2_ctrl_new_std(handler, &ov2311_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + + ret = v4l2_fwnode_device_parse(&ov2311->client->dev, &props); + if (ret) + goto err_free_handler; + + ret = v4l2_ctrl_new_fwnode_properties(handler, &ov2311_ctrl_ops, + &props); + if (ret) + goto err_free_handler; + + if (handler->error) { + ret = handler->error; + dev_err(&ov2311->client->dev, + "Failed to init controls(%d)\n", ret); + goto err_free_handler; + } + + ov2311->subdev.ctrl_handler = handler; + + return 0; + +err_free_handler: + v4l2_ctrl_handler_free(handler); + + return ret; +} + +static int ov2311_check_sensor_id(struct ov2311 *ov2311, + struct i2c_client *client) +{ + struct device *dev = &ov2311->client->dev; + u32 id = 0, id_msb = 0; + int ret; + + ret = ov2311_read_reg(client, OV2311_REG_CHIP_ID + 1, + OV2311_REG_VALUE_08BIT, &id); + if (!ret) + ret = ov2311_read_reg(client, OV2311_REG_CHIP_ID, + OV2311_REG_VALUE_08BIT, &id_msb); + id |= (id_msb << 8); + if (ret || id != CHIP_ID) { + dev_err(dev, "Unexpected sensor id(%04x), ret(%d)\n", id, ret); + return -ENODEV; + } + + dev_info(dev, "Detected OV%06x sensor\n", CHIP_ID); + + return 0; +} + +static int ov2311_configure_regulators(struct ov2311 *ov2311) +{ + unsigned int i; + + for (i = 0; i < OV2311_NUM_SUPPLIES; i++) + ov2311->supplies[i].supply = ov2311_supply_names[i]; + + return devm_regulator_bulk_get(&ov2311->client->dev, + OV2311_NUM_SUPPLIES, + ov2311->supplies); +} + +static int ov2311_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct ov2311 *ov2311; + struct v4l2_subdev *sd; + int ret; + + ov2311 = devm_kzalloc(dev, sizeof(*ov2311), GFP_KERNEL); + if (!ov2311) + return -ENOMEM; + + ov2311->client = client; + ov2311->cur_mode = &supported_modes[0]; + + ov2311->xvclk = devm_clk_get(dev, "xvclk"); + if (IS_ERR(ov2311->xvclk)) { + dev_err(dev, "Failed to get xvclk\n"); + return -EINVAL; + } + + ov2311->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(ov2311->reset_gpio)) + dev_warn(dev, "Failed to get reset-gpios\n"); + + ov2311->pwdn_gpio = devm_gpiod_get_optional(dev, "pwdn", GPIOD_OUT_LOW); + if (IS_ERR(ov2311->pwdn_gpio)) + dev_warn(dev, "Failed to get pwdn-gpios\n"); + + ret = ov2311_configure_regulators(ov2311); + if (ret) { + dev_err(dev, "Failed to get power regulators\n"); + return ret; + } + + mutex_init(&ov2311->mutex); + + sd = &ov2311->subdev; + v4l2_i2c_subdev_init(sd, client, &ov2311_subdev_ops); + ret = ov2311_initialize_controls(ov2311); + if (ret) + goto err_destroy_mutex; + + ret = ov2311_power_on(&client->dev); + if (ret) + goto err_free_handler; + + ret = ov2311_check_sensor_id(ov2311, client); + if (ret) + goto err_power_off; + + sd->internal_ops = &ov2311_internal_ops; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + ov2311->pad.flags = MEDIA_PAD_FL_SOURCE; + sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; + ret = media_entity_pads_init(&sd->entity, 1, &ov2311->pad); + if (ret < 0) + goto err_power_off; + + ret = v4l2_async_register_subdev(sd); + if (ret) { + dev_err(dev, "v4l2 async register subdev failed\n"); + goto err_clean_entity; + } + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_idle(dev); + + return 0; + +err_clean_entity: + media_entity_cleanup(&sd->entity); +err_power_off: + ov2311_power_off(&client->dev); +err_free_handler: + v4l2_ctrl_handler_free(&ov2311->ctrl_handler); +err_destroy_mutex: + mutex_destroy(&ov2311->mutex); + + return ret; +} + +static void ov2311_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov2311 *ov2311 = to_ov2311(sd); + + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(&ov2311->ctrl_handler); + mutex_destroy(&ov2311->mutex); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) + ov2311_power_off(&client->dev); + pm_runtime_set_suspended(&client->dev); +} + +static const struct of_device_id ov2311_of_match[] = { + { .compatible = "ovti,ov2311" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ov2311_of_match); + +static const struct i2c_device_id ov2311_match_id[] = { + { "ovti,ov2311", 0 }, + { }, +}; + +static struct i2c_driver ov2311_i2c_driver = { + .driver = { + .name = OV2311_NAME, + .pm = &ov2311_pm_ops, + .of_match_table = of_match_ptr(ov2311_of_match), + }, + .probe = &ov2311_probe, + .remove = &ov2311_remove, + .id_table = ov2311_match_id, +}; + +module_i2c_driver(ov2311_i2c_driver); + +MODULE_AUTHOR("Dave Stevenson <dave.stevenson@raspberrypi.com"); +MODULE_DESCRIPTION("OmniVision OV2311 sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/ov5647.c b/drivers/media/i2c/ov5647.c index a727beb9d57e29..c1fbe9051ce529 100644 --- a/drivers/media/i2c/ov5647.c +++ b/drivers/media/i2c/ov5647.c @@ -20,6 +20,7 @@ #include <linux/module.h> #include <linux/of_graph.h> #include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> #include <linux/slab.h> #include <linux/videodev2.h> #include <media/v4l2-ctrls.h> @@ -52,8 +53,12 @@ #define OV5647_REG_AEC_AGC 0x3503 #define OV5647_REG_GAIN_HI 0x350a #define OV5647_REG_GAIN_LO 0x350b +#define OV5647_REG_HTS_HI 0x380c +#define OV5647_REG_HTS_LO 0x380d #define OV5647_REG_VTS_HI 0x380e #define OV5647_REG_VTS_LO 0x380f +#define OV5647_REG_VFLIP 0x3820 +#define OV5647_REG_HFLIP 0x3821 #define OV5647_REG_FRAME_OFF_NUMBER 0x4202 #define OV5647_REG_MIPI_CTRL00 0x4800 #define OV5647_REG_MIPI_CTRL14 0x4814 @@ -69,18 +74,36 @@ #define OV5647_NATIVE_HEIGHT 1956U #define OV5647_PIXEL_ARRAY_LEFT 16U -#define OV5647_PIXEL_ARRAY_TOP 16U +#define OV5647_PIXEL_ARRAY_TOP 6U #define OV5647_PIXEL_ARRAY_WIDTH 2592U #define OV5647_PIXEL_ARRAY_HEIGHT 1944U -#define OV5647_VBLANK_MIN 4 +#define OV5647_VBLANK_MIN 24 #define OV5647_VTS_MAX 32767 +#define OV5647_HTS_MAX 0x1fff + #define OV5647_EXPOSURE_MIN 4 #define OV5647_EXPOSURE_STEP 1 #define OV5647_EXPOSURE_DEFAULT 1000 #define OV5647_EXPOSURE_MAX 65535 +/* regulator supplies */ +static const char * const ov5647_supply_names[] = { + "avdd", /* Analog power */ + "dovdd", /* Digital I/O power */ + "dvdd", /* Digital core power */ +}; + +#define OV5647_NUM_SUPPLIES ARRAY_SIZE(ov5647_supply_names) + +#define FREQ_INDEX_FULL 0 +#define FREQ_INDEX_VGA 1 +static const s64 ov5647_link_freqs[] = { + [FREQ_INDEX_FULL] = 218500000, + [FREQ_INDEX_VGA] = 208333000, +}; + struct regval_list { u16 addr; u8 data; @@ -90,6 +113,7 @@ struct ov5647_mode { struct v4l2_mbus_framefmt format; struct v4l2_rect crop; u64 pixel_rate; + unsigned int link_freq_index; int hts; int vts; const struct regval_list *reg_list; @@ -102,6 +126,7 @@ struct ov5647 { struct mutex lock; struct clk *xclk; struct gpio_desc *pwdn; + struct regulator_bulk_data supplies[OV5647_NUM_SUPPLIES]; bool clock_ncont; struct v4l2_ctrl_handler ctrls; const struct ov5647_mode *mode; @@ -109,6 +134,9 @@ struct ov5647 { struct v4l2_ctrl *hblank; struct v4l2_ctrl *vblank; struct v4l2_ctrl *exposure; + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vflip; + struct v4l2_ctrl *link_freq; }; static inline struct ov5647 *to_sensor(struct v4l2_subdev *sd) @@ -142,22 +170,16 @@ static const struct regval_list sensor_oe_enable_regs[] = { {0x3002, 0xe4}, }; -static struct regval_list ov5647_2592x1944_10bpp[] = { +static struct regval_list ov5647_common_regs[] = { {0x0100, 0x00}, {0x0103, 0x01}, {0x3034, 0x1a}, {0x3035, 0x21}, - {0x3036, 0x69}, {0x303c, 0x11}, {0x3106, 0xf5}, - {0x3821, 0x06}, - {0x3820, 0x00}, {0x3827, 0xec}, {0x370c, 0x03}, - {0x3612, 0x5b}, - {0x3618, 0x04}, {0x5000, 0x06}, - {0x5002, 0x41}, {0x5003, 0x08}, {0x5a00, 0x08}, {0x3000, 0x00}, @@ -172,26 +194,6 @@ static struct regval_list ov5647_2592x1944_10bpp[] = { {0x3a19, 0xf8}, {0x3c01, 0x80}, {0x3b07, 0x0c}, - {0x380c, 0x0b}, - {0x380d, 0x1c}, - {0x3814, 0x11}, - {0x3815, 0x11}, - {0x3708, 0x64}, - {0x3709, 0x12}, - {0x3808, 0x0a}, - {0x3809, 0x20}, - {0x380a, 0x07}, - {0x380b, 0x98}, - {0x3800, 0x00}, - {0x3801, 0x00}, - {0x3802, 0x00}, - {0x3803, 0x00}, - {0x3804, 0x0a}, - {0x3805, 0x3f}, - {0x3806, 0x07}, - {0x3807, 0xa3}, - {0x3811, 0x10}, - {0x3813, 0x06}, {0x3630, 0x2e}, {0x3632, 0xe2}, {0x3633, 0x23}, @@ -211,11 +213,6 @@ static struct regval_list ov5647_2592x1944_10bpp[] = { {0x3f06, 0x10}, {0x3f01, 0x0a}, {0x3a08, 0x01}, - {0x3a09, 0x28}, - {0x3a0a, 0x00}, - {0x3a0b, 0xf6}, - {0x3a0d, 0x08}, - {0x3a0e, 0x06}, {0x3a0f, 0x58}, {0x3a10, 0x50}, {0x3a1b, 0x58}, @@ -223,54 +220,57 @@ static struct regval_list ov5647_2592x1944_10bpp[] = { {0x3a11, 0x60}, {0x3a1f, 0x28}, {0x4001, 0x02}, - {0x4004, 0x04}, {0x4000, 0x09}, + {0x3503, 0x03}, +}; + +static struct regval_list ov5647_2592x1944_10bpp[] = { + {0x3036, 0x69}, + {0x3821, 0x00}, + {0x3820, 0x00}, + {0x3612, 0x5b}, + {0x3618, 0x04}, + {0x5002, 0x41}, + {0x3814, 0x11}, + {0x3815, 0x11}, + {0x3708, 0x64}, + {0x3709, 0x12}, + {0x3800, 0x00}, + {0x3801, 0x00}, + {0x3802, 0x00}, + {0x3803, 0x00}, + {0x3804, 0x0a}, + {0x3805, 0x3f}, + {0x3806, 0x07}, + {0x3807, 0xa3}, + {0x3808, 0x0a}, + {0x3809, 0x20}, + {0x380a, 0x07}, + {0x380b, 0x98}, + {0x3811, 0x10}, + {0x3813, 0x06}, + {0x3a09, 0x28}, + {0x3a0a, 0x00}, + {0x3a0b, 0xf6}, + {0x3a0d, 0x08}, + {0x3a0e, 0x06}, + {0x4004, 0x04}, {0x4837, 0x19}, {0x4800, 0x24}, - {0x3503, 0x03}, {0x0100, 0x01}, }; static struct regval_list ov5647_1080p30_10bpp[] = { - {0x0100, 0x00}, - {0x0103, 0x01}, - {0x3034, 0x1a}, - {0x3035, 0x21}, - {0x3036, 0x62}, - {0x303c, 0x11}, - {0x3106, 0xf5}, - {0x3821, 0x06}, + {0x3036, 0x69}, + {0x3821, 0x00}, {0x3820, 0x00}, - {0x3827, 0xec}, - {0x370c, 0x03}, {0x3612, 0x5b}, {0x3618, 0x04}, - {0x5000, 0x06}, {0x5002, 0x41}, - {0x5003, 0x08}, - {0x5a00, 0x08}, - {0x3000, 0x00}, - {0x3001, 0x00}, - {0x3002, 0x00}, - {0x3016, 0x08}, - {0x3017, 0xe0}, - {0x3018, 0x44}, - {0x301c, 0xf8}, - {0x301d, 0xf0}, - {0x3a18, 0x00}, - {0x3a19, 0xf8}, - {0x3c01, 0x80}, - {0x3b07, 0x0c}, - {0x380c, 0x09}, - {0x380d, 0x70}, {0x3814, 0x11}, {0x3815, 0x11}, {0x3708, 0x64}, {0x3709, 0x12}, - {0x3808, 0x07}, - {0x3809, 0x80}, - {0x380a, 0x04}, - {0x380b, 0x38}, {0x3800, 0x01}, {0x3801, 0x5c}, {0x3802, 0x01}, @@ -279,75 +279,30 @@ static struct regval_list ov5647_1080p30_10bpp[] = { {0x3805, 0xe3}, {0x3806, 0x05}, {0x3807, 0xf1}, + {0x3808, 0x07}, + {0x3809, 0x80}, + {0x380a, 0x04}, + {0x380b, 0x38}, {0x3811, 0x04}, {0x3813, 0x02}, - {0x3630, 0x2e}, - {0x3632, 0xe2}, - {0x3633, 0x23}, - {0x3634, 0x44}, - {0x3636, 0x06}, - {0x3620, 0x64}, - {0x3621, 0xe0}, - {0x3600, 0x37}, - {0x3704, 0xa0}, - {0x3703, 0x5a}, - {0x3715, 0x78}, - {0x3717, 0x01}, - {0x3731, 0x02}, - {0x370b, 0x60}, - {0x3705, 0x1a}, - {0x3f05, 0x02}, - {0x3f06, 0x10}, - {0x3f01, 0x0a}, - {0x3a08, 0x01}, {0x3a09, 0x4b}, {0x3a0a, 0x01}, {0x3a0b, 0x13}, {0x3a0d, 0x04}, {0x3a0e, 0x03}, - {0x3a0f, 0x58}, - {0x3a10, 0x50}, - {0x3a1b, 0x58}, - {0x3a1e, 0x50}, - {0x3a11, 0x60}, - {0x3a1f, 0x28}, - {0x4001, 0x02}, {0x4004, 0x04}, - {0x4000, 0x09}, {0x4837, 0x19}, {0x4800, 0x34}, - {0x3503, 0x03}, {0x0100, 0x01}, }; static struct regval_list ov5647_2x2binned_10bpp[] = { - {0x0100, 0x00}, - {0x0103, 0x01}, - {0x3034, 0x1a}, - {0x3035, 0x21}, - {0x3036, 0x62}, - {0x303c, 0x11}, - {0x3106, 0xf5}, - {0x3827, 0xec}, - {0x370c, 0x03}, + {0x3036, 0x69}, + {0x3821, 0x01}, + {0x3820, 0x41}, {0x3612, 0x59}, {0x3618, 0x00}, - {0x5000, 0x06}, {0x5002, 0x41}, - {0x5003, 0x08}, - {0x5a00, 0x08}, - {0x3000, 0x00}, - {0x3001, 0x00}, - {0x3002, 0x00}, - {0x3016, 0x08}, - {0x3017, 0xe0}, - {0x3018, 0x44}, - {0x301c, 0xf8}, - {0x301d, 0xf0}, - {0x3a18, 0x00}, - {0x3a19, 0xf8}, - {0x3c01, 0x80}, - {0x3b07, 0x0c}, {0x3800, 0x00}, {0x3801, 0x00}, {0x3802, 0x00}, @@ -360,50 +315,18 @@ static struct regval_list ov5647_2x2binned_10bpp[] = { {0x3809, 0x10}, {0x380a, 0x03}, {0x380b, 0xcc}, - {0x380c, 0x07}, - {0x380d, 0x68}, {0x3811, 0x0c}, {0x3813, 0x06}, {0x3814, 0x31}, {0x3815, 0x31}, - {0x3630, 0x2e}, - {0x3632, 0xe2}, - {0x3633, 0x23}, - {0x3634, 0x44}, - {0x3636, 0x06}, - {0x3620, 0x64}, - {0x3621, 0xe0}, - {0x3600, 0x37}, - {0x3704, 0xa0}, - {0x3703, 0x5a}, - {0x3715, 0x78}, - {0x3717, 0x01}, - {0x3731, 0x02}, - {0x370b, 0x60}, - {0x3705, 0x1a}, - {0x3f05, 0x02}, - {0x3f06, 0x10}, - {0x3f01, 0x0a}, - {0x3a08, 0x01}, {0x3a09, 0x28}, {0x3a0a, 0x00}, {0x3a0b, 0xf6}, {0x3a0d, 0x08}, {0x3a0e, 0x06}, - {0x3a0f, 0x58}, - {0x3a10, 0x50}, - {0x3a1b, 0x58}, - {0x3a1e, 0x50}, - {0x3a11, 0x60}, - {0x3a1f, 0x28}, - {0x4001, 0x02}, {0x4004, 0x04}, - {0x4000, 0x09}, {0x4837, 0x16}, {0x4800, 0x24}, - {0x3503, 0x03}, - {0x3820, 0x41}, - {0x3821, 0x07}, {0x350a, 0x00}, {0x350b, 0x10}, {0x3500, 0x00}, @@ -414,37 +337,15 @@ static struct regval_list ov5647_2x2binned_10bpp[] = { }; static struct regval_list ov5647_640x480_10bpp[] = { - {0x0100, 0x00}, - {0x0103, 0x01}, - {0x3035, 0x11}, {0x3036, 0x46}, - {0x303c, 0x11}, - {0x3821, 0x07}, + {0x3821, 0x01}, {0x3820, 0x41}, - {0x370c, 0x03}, {0x3612, 0x59}, {0x3618, 0x00}, - {0x5000, 0x06}, - {0x5003, 0x08}, - {0x5a00, 0x08}, - {0x3000, 0xff}, - {0x3001, 0xff}, - {0x3002, 0xff}, - {0x301d, 0xf0}, - {0x3a18, 0x00}, - {0x3a19, 0xf8}, - {0x3c01, 0x80}, - {0x3b07, 0x0c}, - {0x380c, 0x07}, - {0x380d, 0x3c}, {0x3814, 0x35}, {0x3815, 0x35}, {0x3708, 0x64}, {0x3709, 0x52}, - {0x3808, 0x02}, - {0x3809, 0x80}, - {0x380a, 0x01}, - {0x380b, 0xe0}, {0x3800, 0x00}, {0x3801, 0x10}, {0x3802, 0x00}, @@ -453,53 +354,17 @@ static struct regval_list ov5647_640x480_10bpp[] = { {0x3805, 0x2f}, {0x3806, 0x07}, {0x3807, 0x9f}, - {0x3630, 0x2e}, - {0x3632, 0xe2}, - {0x3633, 0x23}, - {0x3634, 0x44}, - {0x3620, 0x64}, - {0x3621, 0xe0}, - {0x3600, 0x37}, - {0x3704, 0xa0}, - {0x3703, 0x5a}, - {0x3715, 0x78}, - {0x3717, 0x01}, - {0x3731, 0x02}, - {0x370b, 0x60}, - {0x3705, 0x1a}, - {0x3f05, 0x02}, - {0x3f06, 0x10}, - {0x3f01, 0x0a}, - {0x3a08, 0x01}, + {0x3808, 0x02}, + {0x3809, 0x80}, + {0x380a, 0x01}, + {0x380b, 0xe0}, {0x3a09, 0x2e}, {0x3a0a, 0x00}, {0x3a0b, 0xfb}, {0x3a0d, 0x02}, {0x3a0e, 0x01}, - {0x3a0f, 0x58}, - {0x3a10, 0x50}, - {0x3a1b, 0x58}, - {0x3a1e, 0x50}, - {0x3a11, 0x60}, - {0x3a1f, 0x28}, - {0x4001, 0x02}, {0x4004, 0x02}, - {0x4000, 0x09}, - {0x3000, 0x00}, - {0x3001, 0x00}, - {0x3002, 0x00}, - {0x3017, 0xe0}, - {0x301c, 0xfc}, - {0x3636, 0x06}, - {0x3016, 0x08}, - {0x3827, 0xec}, - {0x3018, 0x44}, - {0x3035, 0x21}, - {0x3106, 0xf5}, - {0x3034, 0x1a}, - {0x301c, 0xf8}, {0x4800, 0x34}, - {0x3503, 0x03}, {0x0100, 0x01}, }; @@ -508,7 +373,7 @@ static const struct ov5647_mode ov5647_modes[] = { { .format = { .code = MEDIA_BUS_FMT_SBGGR10_1X10, - .colorspace = V4L2_COLORSPACE_SRGB, + .colorspace = V4L2_COLORSPACE_RAW, .field = V4L2_FIELD_NONE, .width = 2592, .height = 1944 @@ -520,6 +385,7 @@ static const struct ov5647_mode ov5647_modes[] = { .height = 1944 }, .pixel_rate = 87500000, + .link_freq_index = FREQ_INDEX_FULL, .hts = 2844, .vts = 0x7b0, .reg_list = ov5647_2592x1944_10bpp, @@ -529,7 +395,7 @@ static const struct ov5647_mode ov5647_modes[] = { { .format = { .code = MEDIA_BUS_FMT_SBGGR10_1X10, - .colorspace = V4L2_COLORSPACE_SRGB, + .colorspace = V4L2_COLORSPACE_RAW, .field = V4L2_FIELD_NONE, .width = 1920, .height = 1080 @@ -540,7 +406,8 @@ static const struct ov5647_mode ov5647_modes[] = { .width = 1928, .height = 1080, }, - .pixel_rate = 81666700, + .pixel_rate = 87500000, + .link_freq_index = FREQ_INDEX_FULL, .hts = 2416, .vts = 0x450, .reg_list = ov5647_1080p30_10bpp, @@ -550,7 +417,7 @@ static const struct ov5647_mode ov5647_modes[] = { { .format = { .code = MEDIA_BUS_FMT_SBGGR10_1X10, - .colorspace = V4L2_COLORSPACE_SRGB, + .colorspace = V4L2_COLORSPACE_RAW, .field = V4L2_FIELD_NONE, .width = 1296, .height = 972 @@ -561,7 +428,8 @@ static const struct ov5647_mode ov5647_modes[] = { .width = 2592, .height = 1944, }, - .pixel_rate = 81666700, + .pixel_rate = 87500000, + .link_freq_index = FREQ_INDEX_FULL, .hts = 1896, .vts = 0x59b, .reg_list = ov5647_2x2binned_10bpp, @@ -571,7 +439,7 @@ static const struct ov5647_mode ov5647_modes[] = { { .format = { .code = MEDIA_BUS_FMT_SBGGR10_1X10, - .colorspace = V4L2_COLORSPACE_SRGB, + .colorspace = V4L2_COLORSPACE_RAW, .field = V4L2_FIELD_NONE, .width = 640, .height = 480 @@ -583,6 +451,7 @@ static const struct ov5647_mode ov5647_modes[] = { .height = 1920, }, .pixel_rate = 55000000, + .link_freq_index = FREQ_INDEX_VGA, .hts = 1852, .vts = 0x1f8, .reg_list = ov5647_640x480_10bpp, @@ -601,7 +470,13 @@ static int ov5647_write16(struct v4l2_subdev *sd, u16 reg, u16 val) int ret; ret = i2c_master_send(client, data, 4); - if (ret < 0) { + /* + * Writing the wrong number of bytes also needs to be flagged as an + * error. Success needs to produce a 0 return code. + */ + if (ret == 4) { + ret = 0; + } else { dev_dbg(&client->dev, "%s: i2c write error, reg: %x\n", __func__, reg); return ret; @@ -617,10 +492,17 @@ static int ov5647_write(struct v4l2_subdev *sd, u16 reg, u8 val) int ret; ret = i2c_master_send(client, data, 3); - if (ret < 0) { + /* + * Writing the wrong number of bytes also needs to be flagged as an + * error. Success needs to produce a 0 return code. + */ + if (ret == 3) { + ret = 0; + } else { dev_dbg(&client->dev, "%s: i2c write error, reg: %x\n", __func__, reg); - return ret; + if (ret >= 0) + ret = -EINVAL; } return 0; @@ -695,6 +577,13 @@ static int ov5647_set_mode(struct v4l2_subdev *sd) if (ret < 0) return ret; + ret = ov5647_write_array(sd, ov5647_common_regs, + ARRAY_SIZE(ov5647_common_regs)); + if (ret < 0) { + dev_err(&client->dev, "write sensor common regs error\n"); + return ret; + } + ret = ov5647_write_array(sd, sensor->mode->reg_list, sensor->mode->num_regs); if (ret < 0) { @@ -777,6 +666,12 @@ static int ov5647_power_on(struct device *dev) dev_dbg(dev, "OV5647 power on\n"); + ret = regulator_bulk_enable(OV5647_NUM_SUPPLIES, sensor->supplies); + if (ret < 0) { + dev_err(dev, "Failed to enable regulators\n"); + return ret; + } + if (sensor->pwdn) { gpiod_set_value_cansleep(sensor->pwdn, 0); msleep(PWDN_ACTIVE_DELAY_MS); @@ -808,6 +703,7 @@ static int ov5647_power_on(struct device *dev) clk_disable_unprepare(sensor->xclk); error_pwdn: gpiod_set_value_cansleep(sensor->pwdn, 1); + regulator_bulk_disable(OV5647_NUM_SUPPLIES, sensor->supplies); return ret; } @@ -837,6 +733,7 @@ static int ov5647_power_off(struct device *dev) clk_disable_unprepare(sensor->xclk); gpiod_set_value_cansleep(sensor->pwdn, 1); + regulator_bulk_disable(OV5647_NUM_SUPPLIES, sensor->supplies); return 0; } @@ -873,6 +770,8 @@ static const struct v4l2_subdev_core_ops ov5647_subdev_core_ops = { .g_register = ov5647_sensor_get_register, .s_register = ov5647_sensor_set_register, #endif + .subscribe_event = v4l2_ctrl_subdev_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, }; static const struct v4l2_rect * @@ -933,6 +832,25 @@ static const struct v4l2_subdev_video_ops ov5647_subdev_video_ops = { .s_stream = ov5647_s_stream, }; +/* This function returns the mbus code for the current settings of the + HFLIP and VFLIP controls. */ + +static u32 ov5647_get_mbus_code(struct v4l2_subdev *sd) +{ + struct ov5647 *sensor = to_sensor(sd); + /* The control values are only 0 or 1. */ + int index = sensor->hflip->val | (sensor->vflip->val << 1); + + static const u32 codes[4] = { + MEDIA_BUS_FMT_SGBRG10_1X10, + MEDIA_BUS_FMT_SBGGR10_1X10, + MEDIA_BUS_FMT_SRGGB10_1X10, + MEDIA_BUS_FMT_SGRBG10_1X10 + }; + + return codes[index]; +} + static int ov5647_enum_mbus_code(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_mbus_code_enum *code) @@ -940,7 +858,7 @@ static int ov5647_enum_mbus_code(struct v4l2_subdev *sd, if (code->index > 0) return -EINVAL; - code->code = MEDIA_BUS_FMT_SBGGR10_1X10; + code->code = ov5647_get_mbus_code(sd); return 0; } @@ -951,7 +869,7 @@ static int ov5647_enum_frame_size(struct v4l2_subdev *sd, { const struct v4l2_mbus_framefmt *fmt; - if (fse->code != MEDIA_BUS_FMT_SBGGR10_1X10 || + if (fse->code != ov5647_get_mbus_code(sd) || fse->index >= ARRAY_SIZE(ov5647_modes)) return -EINVAL; @@ -984,6 +902,8 @@ static int ov5647_get_pad_fmt(struct v4l2_subdev *sd, } *fmt = *sensor_format; + /* The code we pass back must reflect the current h/vflips. */ + fmt->code = ov5647_get_mbus_code(sd); mutex_unlock(&sensor->lock); return 0; @@ -1014,7 +934,8 @@ static int ov5647_set_pad_fmt(struct v4l2_subdev *sd, mode->pixel_rate, 1, mode->pixel_rate); hblank = mode->hts - mode->format.width; - __v4l2_ctrl_modify_range(sensor->hblank, hblank, hblank, 1, + __v4l2_ctrl_modify_range(sensor->hblank, hblank, + OV5647_HTS_MAX - mode->format.width, 1, hblank); vblank = mode->vts - mode->format.height; @@ -1029,8 +950,12 @@ static int ov5647_set_pad_fmt(struct v4l2_subdev *sd, sensor->exposure->minimum, exposure_max, sensor->exposure->step, exposure_def); + + __v4l2_ctrl_s_ctrl(sensor->link_freq, mode->link_freq_index); } *fmt = mode->format; + /* The code we pass back must reflect the current h/vflips. */ + fmt->code = ov5647_get_mbus_code(sd); mutex_unlock(&sensor->lock); return 0; @@ -1206,6 +1131,25 @@ static int ov5647_s_exposure(struct v4l2_subdev *sd, u32 val) return ov5647_write(sd, OV5647_REG_EXP_LO, (val & 0xf) << 4); } +static int ov5647_s_flip( struct v4l2_subdev *sd, u16 reg, u32 ctrl_val) +{ + int ret; + u8 reg_val; + + /* Set or clear bit 1 and leave everything else alone. */ + ret = ov5647_read(sd, reg, ®_val); + if (ret == 0) { + if (ctrl_val) + reg_val |= 2; + else + reg_val &= ~2; + + ret = ov5647_write(sd, reg, reg_val); + } + + return ret; +} + static int ov5647_s_ctrl(struct v4l2_ctrl *ctrl) { struct ov5647 *sensor = container_of(ctrl->handler, @@ -1257,6 +1201,10 @@ static int ov5647_s_ctrl(struct v4l2_ctrl *ctrl) ret = ov5647_write16(sd, OV5647_REG_VTS_HI, sensor->mode->format.height + ctrl->val); break; + case V4L2_CID_HBLANK: + ret = ov5647_write16(sd, OV5647_REG_HTS_HI, + sensor->mode->format.width + ctrl->val); + break; case V4L2_CID_TEST_PATTERN: ret = ov5647_write(sd, OV5647_REG_ISPCTRL3D, ov5647_test_pattern_val[ctrl->val]); @@ -1264,10 +1212,17 @@ static int ov5647_s_ctrl(struct v4l2_ctrl *ctrl) /* Read-only, but we adjust it based on mode. */ case V4L2_CID_PIXEL_RATE: - case V4L2_CID_HBLANK: /* Read-only, but we adjust it based on mode. */ break; + case V4L2_CID_HFLIP: + /* There's an in-built hflip in the sensor, so account for that here. */ + ov5647_s_flip(sd, OV5647_REG_HFLIP, !ctrl->val); + break; + case V4L2_CID_VFLIP: + ov5647_s_flip(sd, OV5647_REG_VFLIP, ctrl->val); + break; + default: dev_info(&client->dev, "Control (id:0x%x, val:0x%x) not supported\n", @@ -1284,12 +1239,25 @@ static const struct v4l2_ctrl_ops ov5647_ctrl_ops = { .s_ctrl = ov5647_s_ctrl, }; -static int ov5647_init_controls(struct ov5647 *sensor) +static int ov5647_configure_regulators(struct device *dev, + struct ov5647 *sensor) +{ + unsigned int i; + + for (i = 0; i < OV5647_NUM_SUPPLIES; i++) + sensor->supplies[i].supply = ov5647_supply_names[i]; + + return devm_regulator_bulk_get(dev, OV5647_NUM_SUPPLIES, + sensor->supplies); +} + +static int ov5647_init_controls(struct ov5647 *sensor, struct device *dev) { struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd); int hblank, exposure_max, exposure_def; + struct v4l2_fwnode_device_properties props; - v4l2_ctrl_handler_init(&sensor->ctrls, 9); + v4l2_ctrl_handler_init(&sensor->ctrls, 10); v4l2_ctrl_new_std(&sensor->ctrls, &ov5647_ctrl_ops, V4L2_CID_AUTOGAIN, 0, 1, 1, 0); @@ -1320,10 +1288,11 @@ static int ov5647_init_controls(struct ov5647 *sensor) sensor->mode->pixel_rate, 1, sensor->mode->pixel_rate); - /* By default, HBLANK is read only, but it does change per mode. */ hblank = sensor->mode->hts - sensor->mode->format.width; sensor->hblank = v4l2_ctrl_new_std(&sensor->ctrls, &ov5647_ctrl_ops, - V4L2_CID_HBLANK, hblank, hblank, 1, + V4L2_CID_HBLANK, hblank, + OV5647_HTS_MAX - + sensor->mode->format.width, 1, hblank); sensor->vblank = v4l2_ctrl_new_std(&sensor->ctrls, &ov5647_ctrl_ops, @@ -1338,11 +1307,33 @@ static int ov5647_init_controls(struct ov5647 *sensor) ARRAY_SIZE(ov5647_test_pattern_menu) - 1, 0, 0, ov5647_test_pattern_menu); + sensor->hflip = v4l2_ctrl_new_std(&sensor->ctrls, &ov5647_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + if (sensor->hflip) + sensor->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + + sensor->vflip = v4l2_ctrl_new_std(&sensor->ctrls, &ov5647_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + if (sensor->vflip) + sensor->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + + sensor->link_freq = + v4l2_ctrl_new_int_menu(&sensor->ctrls, &ov5647_ctrl_ops, + V4L2_CID_LINK_FREQ, + ARRAY_SIZE(ov5647_link_freqs) - 1, 0, + ov5647_link_freqs); + if (sensor->link_freq) + sensor->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + v4l2_fwnode_device_parse(dev, &props); + + v4l2_ctrl_new_fwnode_properties(&sensor->ctrls, &ov5647_ctrl_ops, + &props); + if (sensor->ctrls.error) goto handler_free; sensor->pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY; - sensor->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; sensor->sd.ctrl_handler = &sensor->ctrls; return 0; @@ -1417,11 +1408,17 @@ static int ov5647_probe(struct i2c_client *client) return -EINVAL; } + ret = ov5647_configure_regulators(dev, sensor); + if (ret) { + dev_err(dev, "Failed to get power regulators\n"); + return ret; + } + mutex_init(&sensor->lock); sensor->mode = OV5647_DEFAULT_MODE; - ret = ov5647_init_controls(sensor); + ret = ov5647_init_controls(sensor, dev); if (ret) goto mutex_destroy; @@ -1444,7 +1441,7 @@ static int ov5647_probe(struct i2c_client *client) if (ret < 0) goto power_off; - ret = v4l2_async_register_subdev(sd); + ret = v4l2_async_register_subdev_sensor(sd); if (ret < 0) goto power_off; diff --git a/drivers/media/i2c/ov7251.c b/drivers/media/i2c/ov7251.c index 30f61e04ecaf51..362a6b9a63645a 100644 --- a/drivers/media/i2c/ov7251.c +++ b/drivers/media/i2c/ov7251.c @@ -23,6 +23,10 @@ #include <media/v4l2-fwnode.h> #include <media/v4l2-subdev.h> +static int trigger_mode; +module_param(trigger_mode, int, 0644); +MODULE_PARM_DESC(trigger_mode, "Set vsync trigger mode: 0=standalone, (1=source - not implemented), 2=sink"); + #define OV7251_SC_MODE_SELECT 0x0100 #define OV7251_SC_MODE_SELECT_SW_STANDBY 0x0 #define OV7251_SC_MODE_SELECT_STREAMING 0x1 @@ -525,7 +529,6 @@ static const struct reg_value ov7251_setting_vga_90fps[] = { { 0x3662, 0x01 }, { 0x3663, 0x70 }, { 0x3664, 0x50 }, - { 0x3666, 0x0a }, { 0x3669, 0x1a }, { 0x366a, 0x00 }, { 0x366b, 0x50 }, @@ -592,9 +595,8 @@ static const struct reg_value ov7251_setting_vga_90fps[] = { { 0x3c00, 0x89 }, { 0x3c01, 0x63 }, { 0x3c02, 0x01 }, - { 0x3c03, 0x00 }, { 0x3c04, 0x00 }, - { 0x3c05, 0x03 }, + { 0x3c05, 0x01 }, { 0x3c06, 0x00 }, { 0x3c07, 0x06 }, { 0x3c0c, 0x01 }, @@ -624,6 +626,16 @@ static const struct reg_value ov7251_setting_vga_90fps[] = { { 0x5001, 0x80 }, }; +static const struct reg_value ov7251_ext_trig_on[] = { + { 0x3666, 0x00 }, + { 0x3c03, 0x17 }, +}; + +static const struct reg_value ov7251_ext_trig_off[] = { + { 0x3666, 0x0a }, + { 0x3c03, 0x00 }, +}; + static const unsigned long supported_xclk_rates[] = { [OV7251_19_2_MHZ] = 19200000, [OV7251_24_MHZ] = 24000000, @@ -1051,7 +1063,7 @@ static int ov7251_s_ctrl(struct v4l2_ctrl *ctrl) case V4L2_CID_EXPOSURE: ret = ov7251_set_exposure(ov7251, ctrl->val); break; - case V4L2_CID_GAIN: + case V4L2_CID_ANALOGUE_GAIN: ret = ov7251_set_gain(ov7251, ctrl->val); break; case V4L2_CID_TEST_PATTERN: @@ -1346,6 +1358,14 @@ static int ov7251_s_stream(struct v4l2_subdev *subdev, int enable) return ret; } + ret = ov7251_set_register_array(ov7251, + ov7251_global_init_setting, + ARRAY_SIZE(ov7251_global_init_setting)); + if (ret < 0) { + dev_err(ov7251->dev, "could not set global_init_setting\n"); + goto err_power_down; + } + ret = ov7251_pll_configure(ov7251); if (ret) { dev_err(ov7251->dev, "error configuring PLLs\n"); @@ -1366,6 +1386,23 @@ static int ov7251_s_stream(struct v4l2_subdev *subdev, int enable) dev_err(ov7251->dev, "could not sync v4l2 controls\n"); goto err_power_down; } + + /* Set vsync trigger mode */ + switch (trigger_mode) { + case 2: + ov7251_set_register_array(ov7251, + ov7251_ext_trig_on, + ARRAY_SIZE(ov7251_ext_trig_on)); + break; + case 0: + default: + /* case 1 for ext trig source currently not implemented */ + ov7251_set_register_array(ov7251, + ov7251_ext_trig_off, + ARRAY_SIZE(ov7251_ext_trig_off)); + break; + } + ret = ov7251_write_reg(ov7251, OV7251_SC_MODE_SELECT, OV7251_SC_MODE_SELECT_STREAMING); if (ret) @@ -1562,7 +1599,7 @@ static int ov7251_init_ctrls(struct ov7251 *ov7251) s64 pixel_rate; int hblank; - v4l2_ctrl_handler_init(&ov7251->ctrls, 7); + v4l2_ctrl_handler_init(&ov7251->ctrls, 9); ov7251->ctrls.lock = &ov7251->lock; v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops, @@ -1572,7 +1609,7 @@ static int ov7251_init_ctrls(struct ov7251 *ov7251) ov7251->exposure = v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops, V4L2_CID_EXPOSURE, 1, 32, 1, 32); ov7251->gain = v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops, - V4L2_CID_GAIN, 16, 1023, 1, 16); + V4L2_CID_ANALOGUE_GAIN, 16, 1023, 1, 16); v4l2_ctrl_new_std_menu_items(&ov7251->ctrls, &ov7251_ctrl_ops, V4L2_CID_TEST_PATTERN, ARRAY_SIZE(ov7251_test_pattern_menu) - 1, @@ -1621,6 +1658,7 @@ static int ov7251_init_ctrls(struct ov7251 *ov7251) static int ov7251_probe(struct i2c_client *client) { + struct v4l2_fwnode_device_properties props; struct device *dev = &client->dev; struct ov7251 *ov7251; unsigned int rate = 0, clk_rate = 0; @@ -1696,7 +1734,8 @@ static int ov7251_probe(struct i2c_client *client) return PTR_ERR(ov7251->analog_regulator); } - ov7251->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_HIGH); + ov7251->enable_gpio = devm_gpiod_get_optional(dev, "enable", + GPIOD_OUT_HIGH); if (IS_ERR(ov7251->enable_gpio)) { dev_err(dev, "cannot get enable gpio\n"); return PTR_ERR(ov7251->enable_gpio); @@ -1711,6 +1750,15 @@ static int ov7251_probe(struct i2c_client *client) goto destroy_mutex; } + ret = v4l2_fwnode_device_parse(&client->dev, &props); + if (ret) + goto free_ctrl; + + ret = v4l2_ctrl_new_fwnode_properties(&ov7251->ctrls, &ov7251_ctrl_ops, + &props); + if (ret) + goto free_ctrl; + v4l2_i2c_subdev_init(&ov7251->sd, client, &ov7251_subdev_ops); ov7251->sd.internal_ops = &ov7251_internal_ops; ov7251->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; diff --git a/drivers/media/i2c/ov9282.c b/drivers/media/i2c/ov9282.c index 87e5d7ce5a47ee..cff15da3436b35 100644 --- a/drivers/media/i2c/ov9282.c +++ b/drivers/media/i2c/ov9282.c @@ -1069,12 +1069,16 @@ static int ov9282_set_stream(struct v4l2_subdev *sd, int enable) static int ov9282_detect(struct ov9282 *ov9282) { int ret; - u32 val; + u32 val, msb; - ret = ov9282_read_reg(ov9282, OV9282_REG_ID, 2, &val); + ret = ov9282_read_reg(ov9282, OV9282_REG_ID + 1, 1, &val); + if (ret) + return ret; + ret = ov9282_read_reg(ov9282, OV9282_REG_ID, 1, &msb); if (ret) return ret; + val |= (msb << 8); if (val != OV9282_ID) { dev_err(ov9282->dev, "chip id mismatch: %x!=%x", OV9282_ID, val); diff --git a/drivers/media/i2c/tc358743.c b/drivers/media/i2c/tc358743.c index 344a670e732fa5..16c9905afb2da6 100644 --- a/drivers/media/i2c/tc358743.c +++ b/drivers/media/i2c/tc358743.c @@ -110,7 +110,7 @@ static inline struct tc358743_state *to_state(struct v4l2_subdev *sd) /* --------------- I2C --------------- */ -static void i2c_rd(struct v4l2_subdev *sd, u16 reg, u8 *values, u32 n) +static int i2c_rd(struct v4l2_subdev *sd, u16 reg, u8 *values, u32 n) { struct tc358743_state *state = to_state(sd); struct i2c_client *client = state->i2c_client; @@ -136,6 +136,7 @@ static void i2c_rd(struct v4l2_subdev *sd, u16 reg, u8 *values, u32 n) v4l2_err(sd, "%s: reading register 0x%x from 0x%x failed: %d\n", __func__, reg, client->addr, err); } + return err != ARRAY_SIZE(msgs); } static void i2c_wr(struct v4l2_subdev *sd, u16 reg, u8 *values, u32 n) @@ -192,15 +193,24 @@ static void i2c_wr(struct v4l2_subdev *sd, u16 reg, u8 *values, u32 n) } } -static noinline u32 i2c_rdreg(struct v4l2_subdev *sd, u16 reg, u32 n) +static noinline u32 i2c_rdreg_err(struct v4l2_subdev *sd, u16 reg, u32 n, + int *err) { + int error; __le32 val = 0; - i2c_rd(sd, reg, (u8 __force *)&val, n); + error = i2c_rd(sd, reg, (u8 __force *)&val, n); + if (err) + *err = error; return le32_to_cpu(val); } +static inline u32 i2c_rdreg(struct v4l2_subdev *sd, u16 reg, u32 n) +{ + return i2c_rdreg_err(sd, reg, n, NULL); +} + static noinline void i2c_wrreg(struct v4l2_subdev *sd, u16 reg, u32 val, u32 n) { __le32 raw = cpu_to_le32(val); @@ -229,6 +239,13 @@ static u16 i2c_rd16(struct v4l2_subdev *sd, u16 reg) return i2c_rdreg(sd, reg, 2); } +static int i2c_rd16_err(struct v4l2_subdev *sd, u16 reg, u16 *value) +{ + int err; + *value = i2c_rdreg_err(sd, reg, 2, &err); + return err; +} + static void i2c_wr16(struct v4l2_subdev *sd, u16 reg, u16 val) { i2c_wrreg(sd, reg, val, 2); @@ -1660,12 +1677,23 @@ static int tc358743_enum_mbus_code(struct v4l2_subdev *sd, return 0; } +static u32 tc358743_g_colorspace(u32 code) +{ + switch (code) { + case MEDIA_BUS_FMT_RGB888_1X24: + return V4L2_COLORSPACE_SRGB; + case MEDIA_BUS_FMT_UYVY8_1X16: + return V4L2_COLORSPACE_SMPTE170M; + default: + return 0; + } +} + static int tc358743_get_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { struct tc358743_state *state = to_state(sd); - u8 vi_rep = i2c_rd8(sd, VI_REP); if (format->pad != 0) return -EINVAL; @@ -1675,23 +1703,7 @@ static int tc358743_get_fmt(struct v4l2_subdev *sd, format->format.height = state->timings.bt.height; format->format.field = V4L2_FIELD_NONE; - switch (vi_rep & MASK_VOUT_COLOR_SEL) { - case MASK_VOUT_COLOR_RGB_FULL: - case MASK_VOUT_COLOR_RGB_LIMITED: - format->format.colorspace = V4L2_COLORSPACE_SRGB; - break; - case MASK_VOUT_COLOR_601_YCBCR_LIMITED: - case MASK_VOUT_COLOR_601_YCBCR_FULL: - format->format.colorspace = V4L2_COLORSPACE_SMPTE170M; - break; - case MASK_VOUT_COLOR_709_YCBCR_FULL: - case MASK_VOUT_COLOR_709_YCBCR_LIMITED: - format->format.colorspace = V4L2_COLORSPACE_REC709; - break; - default: - format->format.colorspace = 0; - break; - } + format->format.colorspace = tc358743_g_colorspace(format->format.code); return 0; } @@ -1705,19 +1717,14 @@ static int tc358743_set_fmt(struct v4l2_subdev *sd, u32 code = format->format.code; /* is overwritten by get_fmt */ int ret = tc358743_get_fmt(sd, sd_state, format); - format->format.code = code; + if (code == MEDIA_BUS_FMT_RGB888_1X24 || + code == MEDIA_BUS_FMT_UYVY8_1X16) + format->format.code = code; + format->format.colorspace = tc358743_g_colorspace(format->format.code); if (ret) return ret; - switch (code) { - case MEDIA_BUS_FMT_RGB888_1X24: - case MEDIA_BUS_FMT_UYVY8_1X16: - break; - default: - return -EINVAL; - } - if (format->which == V4L2_SUBDEV_FORMAT_TRY) return 0; @@ -1942,7 +1949,7 @@ static int tc358743_probe_of(struct tc358743_state *state) state->pdata.ddc5v_delay = DDC5V_DELAY_100_MS; state->pdata.enable_hdcp = false; /* A FIFO level of 16 should be enough for 2-lane 720p60 at 594 MHz. */ - state->pdata.fifo_level = 16; + state->pdata.fifo_level = 374; /* * The PLL input clock is obtained by dividing refclk by pll_prd. * It must be between 6 MHz and 40 MHz, lower frequency is better. @@ -1962,6 +1969,7 @@ static int tc358743_probe_of(struct tc358743_state *state) /* * The CSI bps per lane must be between 62.5 Mbps and 1 Gbps. * The default is 594 Mbps for 4-lane 1080p60 or 2-lane 720p60. + * 972 Mbps allows 1080P50 UYVY over 2-lane. */ bps_pr_lane = 2 * endpoint.link_frequencies[0]; if (bps_pr_lane < 62500000U || bps_pr_lane > 1000000000U) { @@ -1975,23 +1983,42 @@ static int tc358743_probe_of(struct tc358743_state *state) state->pdata.refclk_hz * state->pdata.pll_prd; /* - * FIXME: These timings are from REF_02 for 594 Mbps per lane (297 MHz - * link frequency). In principle it should be possible to calculate + * FIXME: These timings are from REF_02 for 594 or 972 Mbps per lane + * (297 MHz or 486 MHz link frequency). + * In principle it should be possible to calculate * them based on link frequency and resolution. */ - if (bps_pr_lane != 594000000U) + switch (bps_pr_lane) { + default: dev_warn(dev, "untested bps per lane: %u bps\n", bps_pr_lane); - state->pdata.lineinitcnt = 0xe80; - state->pdata.lptxtimecnt = 0x003; - /* tclk-preparecnt: 3, tclk-zerocnt: 20 */ - state->pdata.tclk_headercnt = 0x1403; - state->pdata.tclk_trailcnt = 0x00; - /* ths-preparecnt: 3, ths-zerocnt: 1 */ - state->pdata.ths_headercnt = 0x0103; - state->pdata.twakeup = 0x4882; - state->pdata.tclk_postcnt = 0x008; - state->pdata.ths_trailcnt = 0x2; - state->pdata.hstxvregcnt = 0; + fallthrough; + case 594000000U: + state->pdata.lineinitcnt = 0xe80; + state->pdata.lptxtimecnt = 0x003; + /* tclk-preparecnt: 3, tclk-zerocnt: 20 */ + state->pdata.tclk_headercnt = 0x1403; + state->pdata.tclk_trailcnt = 0x00; + /* ths-preparecnt: 3, ths-zerocnt: 1 */ + state->pdata.ths_headercnt = 0x0103; + state->pdata.twakeup = 0x4882; + state->pdata.tclk_postcnt = 0x008; + state->pdata.ths_trailcnt = 0x2; + state->pdata.hstxvregcnt = 0; + break; + case 972000000U: + state->pdata.lineinitcnt = 0x1b58; + state->pdata.lptxtimecnt = 0x007; + /* tclk-preparecnt: 6, tclk-zerocnt: 40 */ + state->pdata.tclk_headercnt = 0x2806; + state->pdata.tclk_trailcnt = 0x00; + /* ths-preparecnt: 6, ths-zerocnt: 8 */ + state->pdata.ths_headercnt = 0x0806; + state->pdata.twakeup = 0x4268; + state->pdata.tclk_postcnt = 0x008; + state->pdata.ths_trailcnt = 0x5; + state->pdata.hstxvregcnt = 0; + break; + } state->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); @@ -2030,6 +2057,7 @@ static int tc358743_probe(struct i2c_client *client) struct tc358743_platform_data *pdata = client->dev.platform_data; struct v4l2_subdev *sd; u16 irq_mask = MASK_HDMI_MSK | MASK_CSI_MSK; + u16 chipid; int err; if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) @@ -2061,7 +2089,8 @@ static int tc358743_probe(struct i2c_client *client) sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; /* i2c access */ - if ((i2c_rd16(sd, CHIPID) & MASK_CHIPID) != 0) { + if (i2c_rd16_err(sd, CHIPID, &chipid) || + (chipid & MASK_CHIPID) != 0) { v4l2_info(sd, "not a TC358743 on address 0x%x\n", client->addr << 1); return -ENODEV; diff --git a/drivers/media/mc/mc-request.c b/drivers/media/mc/mc-request.c index e064914c476e7c..b222f994760d40 100644 --- a/drivers/media/mc/mc-request.c +++ b/drivers/media/mc/mc-request.c @@ -505,3 +505,38 @@ void media_request_object_complete(struct media_request_object *obj) media_request_put(req); } EXPORT_SYMBOL_GPL(media_request_object_complete); + +void media_request_pin(struct media_request *req) +{ + unsigned long flags; + + spin_lock_irqsave(&req->lock, flags); + if (WARN_ON(req->state != MEDIA_REQUEST_STATE_QUEUED)) + goto unlock; + req->num_incomplete_objects++; +unlock: + spin_unlock_irqrestore(&req->lock, flags); +} +EXPORT_SYMBOL_GPL(media_request_pin); + +void media_request_unpin(struct media_request *req) +{ + unsigned long flags; + bool completed = false; + + spin_lock_irqsave(&req->lock, flags); + if (WARN_ON(!req->num_incomplete_objects) || + WARN_ON(req->state != MEDIA_REQUEST_STATE_QUEUED)) + goto unlock; + + if (!--req->num_incomplete_objects) { + req->state = MEDIA_REQUEST_STATE_COMPLETE; + wake_up_interruptible_all(&req->poll_wait); + completed = true; + } +unlock: + spin_unlock_irqrestore(&req->lock, flags); + if (completed) + media_request_put(req); +} +EXPORT_SYMBOL_GPL(media_request_unpin); diff --git a/drivers/media/pci/Kconfig b/drivers/media/pci/Kconfig index 7f65aa60938834..944ca87d0be791 100644 --- a/drivers/media/pci/Kconfig +++ b/drivers/media/pci/Kconfig @@ -75,6 +75,7 @@ config VIDEO_PCI_SKELETON when developing new drivers. source "drivers/media/pci/intel/Kconfig" +source "drivers/media/pci/hailo/Kconfig" endif #MEDIA_PCI_SUPPORT endif #PCI diff --git a/drivers/media/pci/Makefile b/drivers/media/pci/Makefile index f18c7e15abe3e9..78cc75f1c8f838 100644 --- a/drivers/media/pci/Makefile +++ b/drivers/media/pci/Makefile @@ -17,7 +17,8 @@ obj-y += ttpci/ \ saa7146/ \ smipcie/ \ netup_unidvb/ \ - intel/ + intel/ \ + hailo/ # Please keep it alphabetically sorted by Kconfig name # (e. g. LC_ALL=C sort Makefile) diff --git a/drivers/media/pci/hailo/Kconfig b/drivers/media/pci/hailo/Kconfig new file mode 100644 index 00000000000000..bd011b6b8f0e41 --- /dev/null +++ b/drivers/media/pci/hailo/Kconfig @@ -0,0 +1,6 @@ + +config MEDIA_PCI_HAILO + tristate "Hailo AI accelerator PCIe driver" + depends on PCI + help + Enable build of Hailo AI accelerator PCIe driver. diff --git a/drivers/media/pci/hailo/Makefile b/drivers/media/pci/hailo/Makefile new file mode 100644 index 00000000000000..5cf92acc7ceb1f --- /dev/null +++ b/drivers/media/pci/hailo/Makefile @@ -0,0 +1,34 @@ +# SPDX-License-Identifier: GPL-2.0 + +COMMON_SRC_DIRECTORY=common +VDMA_SRC_DIRECTORY=vdma +UTILS_SRC_DIRECTORY=utils + +obj-$(CONFIG_MEDIA_PCI_HAILO) := hailo_pci.o + +hailo_pci-objs += src/pcie.o +hailo_pci-objs += src/fops.o +hailo_pci-objs += src/sysfs.o +hailo_pci-objs += src/nnc.o +hailo_pci-objs += src/soc.o + +hailo_pci-objs += $(COMMON_SRC_DIRECTORY)/fw_validation.o +hailo_pci-objs += $(COMMON_SRC_DIRECTORY)/fw_operation.o +hailo_pci-objs += $(COMMON_SRC_DIRECTORY)/pcie_common.o +hailo_pci-objs += $(COMMON_SRC_DIRECTORY)/vdma_common.o +hailo_pci-objs += $(COMMON_SRC_DIRECTORY)/hailo_resource.o + +hailo_pci-objs += $(UTILS_SRC_DIRECTORY)/logs.o +hailo_pci-objs += $(UTILS_SRC_DIRECTORY)/integrated_nnc_utils.o + +hailo_pci-objs += $(VDMA_SRC_DIRECTORY)/vdma.o +hailo_pci-objs += $(VDMA_SRC_DIRECTORY)/memory.o +hailo_pci-objs += $(VDMA_SRC_DIRECTORY)/ioctl.o + +ccflags-y += -Werror +ccflags-y += -DHAILO_RASBERRY_PIE +ccflags-y += -I $(src) +ccflags-y += -I $(src)/include +ccflags-y += -I $(src)/common + +clean-files := $(hailo_pci-objs) diff --git a/drivers/media/pci/hailo/common/fw_operation.c b/drivers/media/pci/hailo/common/fw_operation.c new file mode 100644 index 00000000000000..fb3b7c16734033 --- /dev/null +++ b/drivers/media/pci/hailo/common/fw_operation.c @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: MIT +/** + * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved. + **/ + +#include "fw_operation.h" + +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/bug.h> + +typedef struct { + u32 host_offset; + u32 chip_offset; +} FW_DEBUG_BUFFER_HEADER_t; + +#define DEBUG_BUFFER_DATA_SIZE (DEBUG_BUFFER_TOTAL_SIZE - sizeof(FW_DEBUG_BUFFER_HEADER_t)) +#define PCIE_D2H_NOTIFICATION_SRAM_OFFSET (0x640 + 0x640) +#define PCIE_APP_CPU_DEBUG_OFFSET (8*1024) +#define PCIE_CORE_CPU_DEBUG_OFFSET (PCIE_APP_CPU_DEBUG_OFFSET + DEBUG_BUFFER_TOTAL_SIZE) + +int hailo_read_firmware_notification(struct hailo_resource *resource, struct hailo_d2h_notification *notification) +{ + hailo_d2h_buffer_details_t d2h_buffer_details = {0, 0}; + hailo_resource_read_buffer(resource, 0, sizeof(d2h_buffer_details), + &d2h_buffer_details); + + if ((sizeof(notification->buffer) < d2h_buffer_details.buffer_len) || (0 == d2h_buffer_details.is_buffer_in_use)) { + return -EINVAL; + } + + notification->buffer_len = d2h_buffer_details.buffer_len; + hailo_resource_read_buffer(resource, sizeof(d2h_buffer_details), notification->buffer_len, notification->buffer); + + // Write is_buffer_in_use = false + hailo_resource_write16(resource, 0, 0); + return 0; +} + +int hailo_pcie_read_firmware_notification(struct hailo_resource *resource, + struct hailo_d2h_notification *notification) +{ + struct hailo_resource notification_resource; + + if (PCIE_D2H_NOTIFICATION_SRAM_OFFSET > resource->size) { + return -EINVAL; + } + + notification_resource.address = resource->address + PCIE_D2H_NOTIFICATION_SRAM_OFFSET, + notification_resource.size = sizeof(struct hailo_d2h_notification); + + return hailo_read_firmware_notification(¬ification_resource, notification); +} + +static inline size_t calculate_log_ready_to_read(FW_DEBUG_BUFFER_HEADER_t *header) +{ + size_t ready_to_read = 0; + size_t host_offset = header->host_offset; + size_t chip_offset = header->chip_offset; + + if (chip_offset >= host_offset) { + ready_to_read = chip_offset - host_offset; + } else { + ready_to_read = DEBUG_BUFFER_DATA_SIZE - (host_offset - chip_offset); + } + + return ready_to_read; +} + +long hailo_read_firmware_log(struct hailo_resource *fw_logger_resource, struct hailo_read_log_params *params) +{ + FW_DEBUG_BUFFER_HEADER_t debug_buffer_header = {0}; + size_t read_offset = 0; + size_t ready_to_read = 0; + size_t size_to_read = 0; + uintptr_t user_buffer = (uintptr_t)params->buffer; + + if (params->buffer_size > ARRAY_SIZE(params->buffer)) { + return -EINVAL; + } + + hailo_resource_read_buffer(fw_logger_resource, 0, sizeof(debug_buffer_header), + &debug_buffer_header); + + /* Point to the start of the data buffer. */ + ready_to_read = calculate_log_ready_to_read(&debug_buffer_header); + if (0 == ready_to_read) { + params->read_bytes = 0; + return 0; + } + /* If ready to read is bigger than the buffer size, read only buffer size bytes. */ + ready_to_read = min(ready_to_read, params->buffer_size); + + /* Point to the data that is read to be read by the host. */ + read_offset = sizeof(debug_buffer_header) + debug_buffer_header.host_offset; + /* Check if the offset should cycle back to beginning. */ + if (DEBUG_BUFFER_DATA_SIZE <= debug_buffer_header.host_offset + ready_to_read) { + size_to_read = DEBUG_BUFFER_DATA_SIZE - debug_buffer_header.host_offset; + hailo_resource_read_buffer(fw_logger_resource, read_offset, size_to_read, (void*)user_buffer); + + user_buffer += size_to_read; + size_to_read = ready_to_read - size_to_read; + /* Point back to the beginning of the data buffer. */ + read_offset -= debug_buffer_header.host_offset; + } + else { + size_to_read = ready_to_read; + } + + /* size_to_read may become 0 if the read reached DEBUG_BUFFER_DATA_SIZE exactly */ + hailo_resource_read_buffer(fw_logger_resource, read_offset, size_to_read, (void*)user_buffer); + + /* Change current_offset to represent the new host offset. */ + read_offset += size_to_read; + hailo_resource_write32(fw_logger_resource, offsetof(FW_DEBUG_BUFFER_HEADER_t, host_offset), + (u32)(read_offset - sizeof(debug_buffer_header))); + + params->read_bytes = ready_to_read; + return 0; +} + +long hailo_pcie_read_firmware_log(struct hailo_resource *resource, struct hailo_read_log_params *params) +{ + long err = 0; + struct hailo_resource log_resource = {resource->address, DEBUG_BUFFER_TOTAL_SIZE}; + + if (HAILO_CPU_ID_CPU0 == params->cpu_id) { + log_resource.address += PCIE_APP_CPU_DEBUG_OFFSET; + } else if (HAILO_CPU_ID_CPU1 == params->cpu_id) { + log_resource.address += PCIE_CORE_CPU_DEBUG_OFFSET; + } else { + return -EINVAL; + } + + if (0 == params->buffer_size) { + params->read_bytes = 0; + return 0; + } + + err = hailo_read_firmware_log(&log_resource, params); + if (0 != err) { + return err; + } + + return 0; +} \ No newline at end of file diff --git a/drivers/media/pci/hailo/common/fw_operation.h b/drivers/media/pci/hailo/common/fw_operation.h new file mode 100644 index 00000000000000..8c8185ce6ba855 --- /dev/null +++ b/drivers/media/pci/hailo/common/fw_operation.h @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +/** + * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved. + **/ + +#ifndef _HAILO_COMMON_FIRMWARE_OPERATION_H_ +#define _HAILO_COMMON_FIRMWARE_OPERATION_H_ + +#include "hailo_resource.h" + +#define DEBUG_BUFFER_TOTAL_SIZE (4*1024) + +#ifdef __cplusplus +extern "C" { +#endif + +int hailo_read_firmware_notification(struct hailo_resource *resource, struct hailo_d2h_notification *notification); + +int hailo_pcie_read_firmware_notification(struct hailo_resource *resource, struct hailo_d2h_notification *notification); + +long hailo_read_firmware_log(struct hailo_resource *fw_logger_resource, struct hailo_read_log_params *params); + +long hailo_pcie_read_firmware_log(struct hailo_resource *resource, struct hailo_read_log_params *params); + +#ifdef __cplusplus +} +#endif + +#endif /* _HAILO_COMMON_FIRMWARE_OPERATION_H_ */ diff --git a/drivers/media/pci/hailo/common/fw_validation.c b/drivers/media/pci/hailo/common/fw_validation.c new file mode 100644 index 00000000000000..2eb59dfd746644 --- /dev/null +++ b/drivers/media/pci/hailo/common/fw_validation.c @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: MIT +/** + * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved. + **/ + +#include "fw_validation.h" +#include <linux/errno.h> +#include <linux/types.h> + + + +/* when reading the firmware we don't want to read past the firmware_size, + so we have a consumed_firmware_offset that is updated _before_ accessing data at that offset + of firmware_base_address */ +#define CONSUME_FIRMWARE(__size, __err) do { \ + consumed_firmware_offset += (u32) (__size); \ + if ((firmware_size < (__size)) || (firmware_size < consumed_firmware_offset)) { \ + err = __err; \ + goto exit; \ + } \ + } while(0) + +int FW_VALIDATION__validate_fw_header(uintptr_t firmware_base_address, + size_t firmware_size, u32 max_code_size, u32 *outer_consumed_firmware_offset, + firmware_header_t **out_firmware_header, enum hailo_board_type board_type) +{ + int err = -EINVAL; + firmware_header_t *firmware_header = NULL; + u32 consumed_firmware_offset = *outer_consumed_firmware_offset; + u32 expected_firmware_magic = 0; + + firmware_header = (firmware_header_t *) (firmware_base_address + consumed_firmware_offset); + CONSUME_FIRMWARE(sizeof(firmware_header_t), -EINVAL); + + switch (board_type) { + case HAILO_BOARD_TYPE_HAILO8: + expected_firmware_magic = FIRMWARE_HEADER_MAGIC_HAILO8; + break; + case HAILO_BOARD_TYPE_HAILO10H_LEGACY: + case HAILO_BOARD_TYPE_HAILO15: + case HAILO_BOARD_TYPE_HAILO10H: + expected_firmware_magic = FIRMWARE_HEADER_MAGIC_HAILO15; + break; + case HAILO_BOARD_TYPE_HAILO15L: + expected_firmware_magic = FIRMWARE_HEADER_MAGIC_HAILO15L; + break; + default: + err = -EINVAL; + goto exit; + } + + if (expected_firmware_magic != firmware_header->magic) { + err = -EINVAL; + goto exit; + } + + /* Validate that the firmware header version is supported */ + switch(firmware_header->header_version) { + case FIRMWARE_HEADER_VERSION_INITIAL: + break; + default: + err = -EINVAL; + goto exit; + break; + } + + if (MINIMUM_FIRMWARE_CODE_SIZE > firmware_header->code_size) { + err = -EINVAL; + goto exit; + } + + if (max_code_size < firmware_header->code_size) { + err = -EINVAL; + goto exit; + } + + CONSUME_FIRMWARE(firmware_header->code_size, -EINVAL); + + *outer_consumed_firmware_offset = consumed_firmware_offset; + *out_firmware_header = firmware_header; + err = 0; + +exit: + return err; +} + +int FW_VALIDATION__validate_cert_header(uintptr_t firmware_base_address, + size_t firmware_size, u32 *outer_consumed_firmware_offset, secure_boot_certificate_header_t **out_firmware_cert) +{ + + secure_boot_certificate_header_t *firmware_cert = NULL; + int err = -EINVAL; + u32 consumed_firmware_offset = *outer_consumed_firmware_offset; + + firmware_cert = (secure_boot_certificate_header_t *) (firmware_base_address + consumed_firmware_offset); + CONSUME_FIRMWARE(sizeof(secure_boot_certificate_header_t), -EINVAL); + + if ((MAXIMUM_FIRMWARE_CERT_KEY_SIZE < firmware_cert->key_size) || + (MAXIMUM_FIRMWARE_CERT_CONTENT_SIZE < firmware_cert->content_size)) { + err = -EINVAL; + goto exit; + } + + CONSUME_FIRMWARE(firmware_cert->key_size, -EINVAL); + CONSUME_FIRMWARE(firmware_cert->content_size, -EINVAL); + + *outer_consumed_firmware_offset = consumed_firmware_offset; + *out_firmware_cert = firmware_cert; + err = 0; + +exit: + return err; +} + diff --git a/drivers/media/pci/hailo/common/fw_validation.h b/drivers/media/pci/hailo/common/fw_validation.h new file mode 100644 index 00000000000000..b1f7f32bc7086b --- /dev/null +++ b/drivers/media/pci/hailo/common/fw_validation.h @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT +/** + * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved. + **/ + +#ifndef PCIE_COMMON_FIRMWARE_HEADER_UTILS_H_ +#define PCIE_COMMON_FIRMWARE_HEADER_UTILS_H_ + +#include "hailo_ioctl_common.h" +#include <linux/types.h> + +#define FIRMWARE_HEADER_MAGIC_HAILO8 (0x1DD89DE0) +#define FIRMWARE_HEADER_MAGIC_HAILO15 (0xE905DAAB) +#define FIRMWARE_HEADER_MAGIC_HAILO15L (0xF94739AB) + +typedef enum { + FIRMWARE_HEADER_VERSION_INITIAL = 0, + + /* MUST BE LAST */ + FIRMWARE_HEADER_VERSION_COUNT +} firmware_header_version_t; + +typedef struct { + u32 magic; + u32 header_version; + u32 firmware_major; + u32 firmware_minor; + u32 firmware_revision; + u32 code_size; +} firmware_header_t; + + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4200) +#endif /* _MSC_VER */ + +typedef struct { + u32 key_size; + u32 content_size; +} secure_boot_certificate_header_t; + +#ifdef _MSC_VER +#pragma warning(pop) +#endif /* _MSC_VER */ + +#define MINIMUM_FIRMWARE_CODE_SIZE (20*4) +#define MAXIMUM_FIRMWARE_CERT_KEY_SIZE (0x1000) +#define MAXIMUM_FIRMWARE_CERT_CONTENT_SIZE (0x1000) + +int FW_VALIDATION__validate_fw_header(uintptr_t firmware_base_address, + size_t firmware_size, u32 max_code_size, u32 *outer_consumed_firmware_offset, + firmware_header_t **out_firmware_header, enum hailo_board_type board_type); + +int FW_VALIDATION__validate_cert_header(uintptr_t firmware_base_address, + size_t firmware_size, u32 *outer_consumed_firmware_offset, secure_boot_certificate_header_t **out_firmware_cert); + +#endif diff --git a/drivers/media/pci/hailo/common/hailo_ioctl_common.h b/drivers/media/pci/hailo/common/hailo_ioctl_common.h new file mode 100644 index 00000000000000..3333513ce246d8 --- /dev/null +++ b/drivers/media/pci/hailo/common/hailo_ioctl_common.h @@ -0,0 +1,685 @@ +// SPDX-License-Identifier: (GPL-2.0 WITH Linux-syscall-note) AND MIT +/** + * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved. + **/ + +#ifndef _HAILO_IOCTL_COMMON_H_ +#define _HAILO_IOCTL_COMMON_H_ + +#define HAILO_DRV_VER_MAJOR 4 +#define HAILO_DRV_VER_MINOR 20 +#define HAILO_DRV_VER_REVISION 0 + +#define _STRINGIFY_EXPANDED( x ) #x +#define _STRINGIFY_NUMBER( x ) _STRINGIFY_EXPANDED(x) +#define HAILO_DRV_VER _STRINGIFY_NUMBER(HAILO_DRV_VER_MAJOR) "." _STRINGIFY_NUMBER(HAILO_DRV_VER_MINOR) "." _STRINGIFY_NUMBER(HAILO_DRV_VER_REVISION) + + +// This value is not easily changeable. +// For example: the channel interrupts ioctls assume we have up to 32 channels +#define MAX_VDMA_CHANNELS_PER_ENGINE (32) +#define VDMA_CHANNELS_PER_ENGINE_PER_DIRECTION (16) +#define MAX_VDMA_ENGINES (3) +#define SIZE_OF_VDMA_DESCRIPTOR (16) +#define VDMA_DEST_CHANNELS_START (16) +#define MAX_SG_DESCS_COUNT (64 * 1024u) + +#define HAILO_VDMA_MAX_ONGOING_TRANSFERS (128) +#define HAILO_VDMA_MAX_ONGOING_TRANSFERS_MASK (HAILO_VDMA_MAX_ONGOING_TRANSFERS - 1) + +#define CHANNEL_IRQ_TIMESTAMPS_SIZE (HAILO_VDMA_MAX_ONGOING_TRANSFERS * 2) +#define CHANNEL_IRQ_TIMESTAMPS_SIZE_MASK (CHANNEL_IRQ_TIMESTAMPS_SIZE - 1) + +#define INVALID_DRIVER_HANDLE_VALUE ((uintptr_t)-1) + +// Used by windows and unix driver to raise the right CPU control handle to the FW. The same as in pcie_service FW +#define FW_ACCESS_CORE_CPU_CONTROL_SHIFT (1) +#define FW_ACCESS_CORE_CPU_CONTROL_MASK (1 << FW_ACCESS_CORE_CPU_CONTROL_SHIFT) +#define FW_ACCESS_CONTROL_INTERRUPT_SHIFT (0) +#define FW_ACCESS_APP_CPU_CONTROL_MASK (1 << FW_ACCESS_CONTROL_INTERRUPT_SHIFT) +#define FW_ACCESS_DRIVER_SHUTDOWN_SHIFT (2) +#define FW_ACCESS_DRIVER_SHUTDOWN_MASK (1 << FW_ACCESS_DRIVER_SHUTDOWN_SHIFT) +// HRT-15790 TODO: separate nnc interrupts and soc interrupts +#define FW_ACCESS_SOFT_RESET_SHIFT (3) +#define FW_ACCESS_SOFT_RESET_MASK (1 << FW_ACCESS_SOFT_RESET_SHIFT) + +#define FW_ACCESS_SOC_CONTROL_SHIFT (3) +#define FW_ACCESS_SOC_CONTROL_MASK (1 << FW_ACCESS_SOC_CONTROL_SHIFT) + +#define INVALID_VDMA_CHANNEL (0xff) + + +#if !defined(__cplusplus) && defined(NTDDI_VERSION) +#include <wdm.h> +typedef ULONG uint32_t; +typedef UCHAR uint8_t; +typedef USHORT uint16_t; +typedef ULONGLONG uint64_t; +#endif /* !defined(__cplusplus) && defined(NTDDI_VERSION) */ + + +#ifdef _MSC_VER + +#include <initguid.h> + +#if !defined(bool) && !defined(__cplusplus) +typedef uint8_t bool; +#endif // !defined(bool) && !defined(__cplusplus) + +#if !defined(INT_MAX) +#define INT_MAX 0x7FFFFFFF +#endif // !defined(INT_MAX) + +#if !defined(ECONNRESET) +#define ECONNRESET 104 /* Connection reset by peer */ +#endif // !defined(ECONNRESET) + +// {d88d31f1-fede-4e71-ac2a-6ce0018c1501} +DEFINE_GUID (GUID_DEVINTERFACE_HailoKM_NNC, + 0xd88d31f1,0xfede,0x4e71,0xac,0x2a,0x6c,0xe0,0x01,0x8c,0x15,0x01); + +// {7f16047d-64b8-207a-0092-e970893970a2} +DEFINE_GUID (GUID_DEVINTERFACE_HailoKM_SOC, + 0x7f16047d,0x64b8,0x207a,0x00,0x92,0xe9,0x70,0x89,0x39,0x70,0xa2); + +#define HAILO_GENERAL_IOCTL_MAGIC 0 +#define HAILO_VDMA_IOCTL_MAGIC 1 +#define HAILO_SOC_IOCTL_MAGIC 2 +#define HAILO_PCI_EP_IOCTL_MAGIC 3 +#define HAILO_NNC_IOCTL_MAGIC 4 + +#define HAILO_IOCTL_COMPATIBLE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_BUFFERED, FILE_ANY_ACCESS) + + +typedef struct tCompatibleHailoIoctlParam +{ + union { + struct { + ULONG Size : 16; + ULONG Code : 8; + ULONG Type : 6; + ULONG Read : 1; + ULONG Write : 1; + } bits; + ULONG value; + } u; +} tCompatibleHailoIoctlParam; + +static ULONG FORCEINLINE _IOC_(ULONG nr, ULONG type, ULONG size, bool read, bool write) +{ + struct tCompatibleHailoIoctlParam param; + param.u.bits.Code = nr; + param.u.bits.Size = size; + param.u.bits.Type = type; + param.u.bits.Read = read ? 1 : 0; + param.u.bits.Write = write ? 1 : 0; + return param.u.value; +} + +#define _IOW_(type,nr,size) _IOC_(nr, type, sizeof(size), true, false) +#define _IOR_(type,nr,size) _IOC_(nr, type, sizeof(size), false, true) +#define _IOWR_(type,nr,size) _IOC_(nr, type, sizeof(size), true, true) +#define _IO_(type,nr) _IOC_(nr, type, 0, false, false) + +#elif defined(__linux__) // #ifdef _MSC_VER +#ifndef __KERNEL__ +// include the userspace headers only if this file is included by user space program +// It is discourged to include them when compiling the driver (https://lwn.net/Articles/113349/) +#include <stdint.h> +#include <sys/types.h> +#else +#include <linux/types.h> +#include <linux/limits.h> +#include <linux/kernel.h> +#endif // ifndef __KERNEL__ + +#include <linux/ioctl.h> + +#define _IOW_ _IOW +#define _IOR_ _IOR +#define _IOWR_ _IOWR +#define _IO_ _IO + +#define HAILO_GENERAL_IOCTL_MAGIC 'g' +#define HAILO_VDMA_IOCTL_MAGIC 'v' +#define HAILO_SOC_IOCTL_MAGIC 's' +#define HAILO_NNC_IOCTL_MAGIC 'n' +#define HAILO_PCI_EP_IOCTL_MAGIC 'p' + +#elif defined(__QNX__) // #ifdef _MSC_VER +#include <devctl.h> +#include <stdint.h> +#include <sys/types.h> +#include <sys/mman.h> +#include <stdbool.h> + +// defines for devctl +#define _IOW_ __DIOF +#define _IOR_ __DIOT +#define _IOWR_ __DIOTF +#define _IO_ __DION +#define HAILO_GENERAL_IOCTL_MAGIC _DCMD_ALL +#define HAILO_VDMA_IOCTL_MAGIC _DCMD_MISC + +#else // #ifdef _MSC_VER +#error "unsupported platform!" +#endif + +#pragma pack(push, 1) + +struct hailo_channel_interrupt_timestamp { + uint64_t timestamp_ns; + uint16_t desc_num_processed; +}; + +typedef struct { + uint16_t is_buffer_in_use; + uint16_t buffer_len; +} hailo_d2h_buffer_details_t; + +// This struct is the same as `enum dma_data_direction` (defined in linux/dma-direction) +enum hailo_dma_data_direction { + HAILO_DMA_BIDIRECTIONAL = 0, + HAILO_DMA_TO_DEVICE = 1, + HAILO_DMA_FROM_DEVICE = 2, + HAILO_DMA_NONE = 3, + + /** Max enum value to maintain ABI Integrity */ + HAILO_DMA_MAX_ENUM = INT_MAX, +}; + +// Enum that states what type of buffer we are working with in the driver +enum hailo_dma_buffer_type { + HAILO_DMA_USER_PTR_BUFFER = 0, + HAILO_DMA_DMABUF_BUFFER = 1, + + /** Max enum value to maintain ABI Integrity */ + HAILO_DMA_BUFFER_MAX_ENUM = INT_MAX, +}; + +// Enum that determines if buffer should be allocated from user space or from driver +enum hailo_allocation_mode { + HAILO_ALLOCATION_MODE_USERSPACE = 0, + HAILO_ALLOCATION_MODE_DRIVER = 1, + + /** Max enum value to maintain ABI Integrity */ + HAILO_ALLOCATION_MODE_MAX_ENUM = INT_MAX, +}; + +enum hailo_vdma_interrupts_domain { + HAILO_VDMA_INTERRUPTS_DOMAIN_NONE = 0, + HAILO_VDMA_INTERRUPTS_DOMAIN_DEVICE = (1 << 0), + HAILO_VDMA_INTERRUPTS_DOMAIN_HOST = (1 << 1), + + /** Max enum value to maintain ABI Integrity */ + HAILO_VDMA_INTERRUPTS_DOMAIN_MAX_ENUM = INT_MAX, +}; + +/* structure used in ioctl HAILO_VDMA_BUFFER_MAP */ +struct hailo_vdma_buffer_map_params { +#if defined(__linux__) || defined(_MSC_VER) + uintptr_t user_address; // in +#elif defined(__QNX__) + shm_handle_t shared_memory_handle; // in +#else +#error "unsupported platform!" +#endif // __linux__ + size_t size; // in + enum hailo_dma_data_direction data_direction; // in + enum hailo_dma_buffer_type buffer_type; // in + uintptr_t allocated_buffer_handle; // in + size_t mapped_handle; // out +}; + +/* structure used in ioctl HAILO_VDMA_BUFFER_UNMAP */ +struct hailo_vdma_buffer_unmap_params { + size_t mapped_handle; +}; + +/* structure used in ioctl HAILO_DESC_LIST_CREATE */ +struct hailo_desc_list_create_params { + size_t desc_count; // in + uint16_t desc_page_size; // in + bool is_circular; // in + uintptr_t desc_handle; // out + uint64_t dma_address; // out +}; + +/* structure used in ioctl HAILO_DESC_LIST_RELEASE */ +struct hailo_desc_list_release_params { + uintptr_t desc_handle; // in +}; + +struct hailo_write_action_list_params { + uint8_t *data; // in + size_t size; // in + uint64_t dma_address; // out +}; + +/* structure used in ioctl HAILO_DESC_LIST_BIND_VDMA_BUFFER */ +struct hailo_desc_list_program_params { + size_t buffer_handle; // in + size_t buffer_size; // in + size_t buffer_offset; // in + uintptr_t desc_handle; // in + uint8_t channel_index; // in + uint32_t starting_desc; // in + bool should_bind; // in + enum hailo_vdma_interrupts_domain last_interrupts_domain; // in + bool is_debug; // in +}; + +/* structure used in ioctl HAILO_VDMA_ENABLE_CHANNELS */ +struct hailo_vdma_enable_channels_params { + uint32_t channels_bitmap_per_engine[MAX_VDMA_ENGINES]; // in + bool enable_timestamps_measure; // in +}; + +/* structure used in ioctl HAILO_VDMA_DISABLE_CHANNELS */ +struct hailo_vdma_disable_channels_params { + uint32_t channels_bitmap_per_engine[MAX_VDMA_ENGINES]; // in +}; + +/* structure used in ioctl HAILO_VDMA_INTERRUPTS_WAIT */ +struct hailo_vdma_interrupts_channel_data { + uint8_t engine_index; + uint8_t channel_index; + bool is_active; // If not activate, num_processed is ignored. + uint8_t transfers_completed; // Number of transfers completed. + uint8_t host_error; // Channel errors bits on source side + uint8_t device_error; // Channel errors bits on dest side + bool validation_success; // If the validation of the channel was successful +}; + +struct hailo_vdma_interrupts_wait_params { + uint32_t channels_bitmap_per_engine[MAX_VDMA_ENGINES]; // in + uint8_t channels_count; // out + struct hailo_vdma_interrupts_channel_data + irq_data[MAX_VDMA_CHANNELS_PER_ENGINE * MAX_VDMA_ENGINES]; // out +}; + +/* structure used in ioctl HAILO_VDMA_INTERRUPTS_READ_TIMESTAMPS */ +struct hailo_vdma_interrupts_read_timestamp_params { + uint8_t engine_index; // in + uint8_t channel_index; // in + uint32_t timestamps_count; // out + struct hailo_channel_interrupt_timestamp timestamps[CHANNEL_IRQ_TIMESTAMPS_SIZE]; // out +}; + +/* structure used in ioctl HAILO_FW_CONTROL */ +#define MAX_CONTROL_LENGTH (1500) +#define PCIE_EXPECTED_MD5_LENGTH (16) + + +/* structure used in ioctl HAILO_FW_CONTROL and HAILO_READ_LOG */ +enum hailo_cpu_id { + HAILO_CPU_ID_CPU0 = 0, + HAILO_CPU_ID_CPU1, + HAILO_CPU_ID_NONE, + + /** Max enum value to maintain ABI Integrity */ + HAILO_CPU_MAX_ENUM = INT_MAX, +}; + +struct hailo_fw_control { + // expected_md5+buffer_len+buffer must be in this order at the start of the struct + uint8_t expected_md5[PCIE_EXPECTED_MD5_LENGTH]; + uint32_t buffer_len; + uint8_t buffer[MAX_CONTROL_LENGTH]; + uint32_t timeout_ms; + enum hailo_cpu_id cpu_id; +}; + +/* structure used in ioctl HAILO_MEMORY_TRANSFER */ +// Max bar transfer size gotten from ATR0_TABLE_SIZE +#define MAX_MEMORY_TRANSFER_LENGTH (4096) + +enum hailo_transfer_direction { + TRANSFER_READ = 0, + TRANSFER_WRITE, + + /** Max enum value to maintain ABI Integrity */ + TRANSFER_MAX_ENUM = INT_MAX, +}; + +enum hailo_transfer_memory_type { + HAILO_TRANSFER_DEVICE_DIRECT_MEMORY, + + // vDMA memories + HAILO_TRANSFER_MEMORY_VDMA0 = 0x100, + HAILO_TRANSFER_MEMORY_VDMA1, + HAILO_TRANSFER_MEMORY_VDMA2, + + // PCIe driver memories + HAILO_TRANSFER_MEMORY_PCIE_BAR0 = 0x200, + HAILO_TRANSFER_MEMORY_PCIE_BAR2 = 0x202, + HAILO_TRANSFER_MEMORY_PCIE_BAR4 = 0x204, + + // DRAM DMA driver memories + HAILO_TRANSFER_MEMORY_DMA_ENGINE0 = 0x300, + HAILO_TRANSFER_MEMORY_DMA_ENGINE1, + HAILO_TRANSFER_MEMORY_DMA_ENGINE2, + + // PCIe EP driver memories + HAILO_TRANSFER_MEMORY_PCIE_EP_CONFIG = 0x400, + HAILO_TRANSFER_MEMORY_PCIE_EP_BRIDGE, + + /** Max enum value to maintain ABI Integrity */ + HAILO_TRANSFER_MEMORY_MAX_ENUM = INT_MAX, +}; + +struct hailo_memory_transfer_params { + enum hailo_transfer_direction transfer_direction; // in + enum hailo_transfer_memory_type memory_type; // in + uint64_t address; // in + size_t count; // in + uint8_t buffer[MAX_MEMORY_TRANSFER_LENGTH]; // in/out +}; + +/* structure used in ioctl HAILO_VDMA_BUFFER_SYNC */ +enum hailo_vdma_buffer_sync_type { + HAILO_SYNC_FOR_CPU, + HAILO_SYNC_FOR_DEVICE, + + /** Max enum value to maintain ABI Integrity */ + HAILO_SYNC_MAX_ENUM = INT_MAX, +}; + +struct hailo_vdma_buffer_sync_params { + size_t handle; // in + enum hailo_vdma_buffer_sync_type sync_type; // in + size_t offset; // in + size_t count; // in +}; + +/* structure used in ioctl HAILO_READ_NOTIFICATION */ +#define MAX_NOTIFICATION_LENGTH (1500) + +struct hailo_d2h_notification { + size_t buffer_len; // out + uint8_t buffer[MAX_NOTIFICATION_LENGTH]; // out +}; + +enum hailo_board_type { + HAILO_BOARD_TYPE_HAILO8 = 0, + HAILO_BOARD_TYPE_HAILO15, + HAILO_BOARD_TYPE_HAILO15L, + HAILO_BOARD_TYPE_HAILO10H, + HAILO_BOARD_TYPE_HAILO10H_LEGACY, + HAILO_BOARD_TYPE_COUNT, + + /** Max enum value to maintain ABI Integrity */ + HAILO_BOARD_TYPE_MAX_ENUM = INT_MAX +}; + +enum hailo_accelerator_type { + HAILO_ACCELERATOR_TYPE_NNC, + HAILO_ACCELERATOR_TYPE_SOC, + + /** Max enum value to maintain ABI Integrity */ + HAILO_ACCELERATOR_TYPE_MAX_ENUM = INT_MAX +}; + +enum hailo_dma_type { + HAILO_DMA_TYPE_PCIE, + HAILO_DMA_TYPE_DRAM, + HAILO_DMA_TYPE_PCI_EP, + + /** Max enum value to maintain ABI Integrity */ + HAILO_DMA_TYPE_MAX_ENUM = INT_MAX, +}; + +struct hailo_device_properties { + uint16_t desc_max_page_size; + enum hailo_board_type board_type; + enum hailo_allocation_mode allocation_mode; + enum hailo_dma_type dma_type; + size_t dma_engines_count; + bool is_fw_loaded; +#ifdef __QNX__ + pid_t resource_manager_pid; +#endif // __QNX__ +}; + +struct hailo_driver_info { + uint32_t major_version; + uint32_t minor_version; + uint32_t revision_version; +}; + +/* structure used in ioctl HAILO_READ_LOG */ +#define MAX_FW_LOG_BUFFER_LENGTH (512) + +struct hailo_read_log_params { + enum hailo_cpu_id cpu_id; // in + uint8_t buffer[MAX_FW_LOG_BUFFER_LENGTH]; // out + size_t buffer_size; // in + size_t read_bytes; // out +}; + +/* structure used in ioctl HAILO_VDMA_LOW_MEMORY_BUFFER_ALLOC */ +struct hailo_allocate_low_memory_buffer_params { + size_t buffer_size; // in + uintptr_t buffer_handle; // out +}; + +/* structure used in ioctl HAILO_VDMA_LOW_MEMORY_BUFFER_FREE */ +struct hailo_free_low_memory_buffer_params { + uintptr_t buffer_handle; // in +}; + +struct hailo_mark_as_in_use_params { + bool in_use; // out +}; + +/* structure used in ioctl HAILO_VDMA_CONTINUOUS_BUFFER_ALLOC */ +struct hailo_allocate_continuous_buffer_params { + size_t buffer_size; // in + uintptr_t buffer_handle; // out + uint64_t dma_address; // out +}; + +/* structure used in ioctl HAILO_VDMA_CONTINUOUS_BUFFER_FREE */ +struct hailo_free_continuous_buffer_params { + uintptr_t buffer_handle; // in +}; + +/* structures used in ioctl HAILO_VDMA_LAUNCH_TRANSFER */ +struct hailo_vdma_transfer_buffer { + size_t mapped_buffer_handle; // in + uint32_t offset; // in + uint32_t size; // in +}; + +// We allow maximum 2 buffers per transfer since we may have an extra buffer +// to make sure each buffer is aligned to page size. +#define HAILO_MAX_BUFFERS_PER_SINGLE_TRANSFER (2) + +struct hailo_vdma_launch_transfer_params { + uint8_t engine_index; // in + uint8_t channel_index; // in + + uintptr_t desc_handle; // in + uint32_t starting_desc; // in + + bool should_bind; // in, if false, assumes buffer already bound. + uint8_t buffers_count; // in + struct hailo_vdma_transfer_buffer + buffers[HAILO_MAX_BUFFERS_PER_SINGLE_TRANSFER]; // in + + enum hailo_vdma_interrupts_domain first_interrupts_domain; // in + enum hailo_vdma_interrupts_domain last_interrupts_domain; // in + + bool is_debug; // in, if set program hw to send + // more info (e.g desc complete status) + + uint32_t descs_programed; // out, amount of descriptors programed. + int launch_transfer_status; // out, status of the launch transfer call. (only used in case of error) +}; + +/* structure used in ioctl HAILO_SOC_CONNECT */ +struct hailo_soc_connect_params { + uint16_t port_number; // in + uint8_t input_channel_index; // out + uint8_t output_channel_index; // out + uintptr_t input_desc_handle; // in + uintptr_t output_desc_handle; // in +}; + +/* structure used in ioctl HAILO_SOC_CLOSE */ +struct hailo_soc_close_params { + uint8_t input_channel_index; // in + uint8_t output_channel_index; // in +}; + +/* structure used in ioctl HAILO_PCI_EP_ACCEPT */ +struct hailo_pci_ep_accept_params { + uint16_t port_number; // in + uint8_t input_channel_index; // out + uint8_t output_channel_index; // out + uintptr_t input_desc_handle; // in + uintptr_t output_desc_handle; // in +}; + +/* structure used in ioctl HAILO_PCI_EP_CLOSE */ +struct hailo_pci_ep_close_params { + uint8_t input_channel_index; // in + uint8_t output_channel_index; // in +}; + +#ifdef _MSC_VER +struct tCompatibleHailoIoctlData +{ + tCompatibleHailoIoctlParam Parameters; + ULONG_PTR Value; + union { + struct hailo_memory_transfer_params MemoryTransfer; + struct hailo_vdma_enable_channels_params VdmaEnableChannels; + struct hailo_vdma_disable_channels_params VdmaDisableChannels; + struct hailo_vdma_interrupts_read_timestamp_params VdmaInterruptsReadTimestamps; + struct hailo_vdma_interrupts_wait_params VdmaInterruptsWait; + struct hailo_vdma_buffer_sync_params VdmaBufferSync; + struct hailo_fw_control FirmwareControl; + struct hailo_vdma_buffer_map_params VdmaBufferMap; + struct hailo_vdma_buffer_unmap_params VdmaBufferUnmap; + struct hailo_desc_list_create_params DescListCreate; + struct hailo_desc_list_release_params DescListReleaseParam; + struct hailo_desc_list_program_params DescListProgram; + struct hailo_d2h_notification D2HNotification; + struct hailo_device_properties DeviceProperties; + struct hailo_driver_info DriverInfo; + struct hailo_read_log_params ReadLog; + struct hailo_mark_as_in_use_params MarkAsInUse; + struct hailo_vdma_launch_transfer_params LaunchTransfer; + struct hailo_soc_connect_params ConnectParams; + struct hailo_soc_close_params SocCloseParams; + struct hailo_pci_ep_accept_params AcceptParams; + struct hailo_pci_ep_close_params PciEpCloseParams; + struct hailo_write_action_list_params WriteActionListParams; + } Buffer; +}; +#endif // _MSC_VER + +#pragma pack(pop) + +enum hailo_general_ioctl_code { + HAILO_MEMORY_TRANSFER_CODE, + HAILO_QUERY_DEVICE_PROPERTIES_CODE, + HAILO_QUERY_DRIVER_INFO_CODE, + + // Must be last + HAILO_GENERAL_IOCTL_MAX_NR, +}; + +#define HAILO_MEMORY_TRANSFER _IOWR_(HAILO_GENERAL_IOCTL_MAGIC, HAILO_MEMORY_TRANSFER_CODE, struct hailo_memory_transfer_params) +#define HAILO_QUERY_DEVICE_PROPERTIES _IOW_(HAILO_GENERAL_IOCTL_MAGIC, HAILO_QUERY_DEVICE_PROPERTIES_CODE, struct hailo_device_properties) +#define HAILO_QUERY_DRIVER_INFO _IOW_(HAILO_GENERAL_IOCTL_MAGIC, HAILO_QUERY_DRIVER_INFO_CODE, struct hailo_driver_info) + +enum hailo_vdma_ioctl_code { + HAILO_VDMA_ENABLE_CHANNELS_CODE, + HAILO_VDMA_DISABLE_CHANNELS_CODE, + HAILO_VDMA_INTERRUPTS_WAIT_CODE, + HAILO_VDMA_INTERRUPTS_READ_TIMESTAMPS_CODE, + HAILO_VDMA_BUFFER_MAP_CODE, + HAILO_VDMA_BUFFER_UNMAP_CODE, + HAILO_VDMA_BUFFER_SYNC_CODE, + HAILO_DESC_LIST_CREATE_CODE, + HAILO_DESC_LIST_RELEASE_CODE, + HAILO_DESC_LIST_PROGRAM_CODE, + HAILO_VDMA_LOW_MEMORY_BUFFER_ALLOC_CODE, + HAILO_VDMA_LOW_MEMORY_BUFFER_FREE_CODE, + HAILO_MARK_AS_IN_USE_CODE, + HAILO_VDMA_CONTINUOUS_BUFFER_ALLOC_CODE, + HAILO_VDMA_CONTINUOUS_BUFFER_FREE_CODE, + HAILO_VDMA_LAUNCH_TRANSFER_CODE, + + // Must be last + HAILO_VDMA_IOCTL_MAX_NR, +}; + +#define HAILO_VDMA_ENABLE_CHANNELS _IOR_(HAILO_VDMA_IOCTL_MAGIC, HAILO_VDMA_ENABLE_CHANNELS_CODE, struct hailo_vdma_enable_channels_params) +#define HAILO_VDMA_DISABLE_CHANNELS _IOR_(HAILO_VDMA_IOCTL_MAGIC, HAILO_VDMA_DISABLE_CHANNELS_CODE, struct hailo_vdma_disable_channels_params) +#define HAILO_VDMA_INTERRUPTS_WAIT _IOWR_(HAILO_VDMA_IOCTL_MAGIC, HAILO_VDMA_INTERRUPTS_WAIT_CODE, struct hailo_vdma_interrupts_wait_params) +#define HAILO_VDMA_INTERRUPTS_READ_TIMESTAMPS _IOWR_(HAILO_VDMA_IOCTL_MAGIC, HAILO_VDMA_INTERRUPTS_READ_TIMESTAMPS_CODE, struct hailo_vdma_interrupts_read_timestamp_params) + +#define HAILO_VDMA_BUFFER_MAP _IOWR_(HAILO_VDMA_IOCTL_MAGIC, HAILO_VDMA_BUFFER_MAP_CODE, struct hailo_vdma_buffer_map_params) +#define HAILO_VDMA_BUFFER_UNMAP _IOR_(HAILO_VDMA_IOCTL_MAGIC, HAILO_VDMA_BUFFER_UNMAP_CODE, struct hailo_vdma_buffer_unmap_params) +#define HAILO_VDMA_BUFFER_SYNC _IOR_(HAILO_VDMA_IOCTL_MAGIC, HAILO_VDMA_BUFFER_SYNC_CODE, struct hailo_vdma_buffer_sync_params) + +#define HAILO_DESC_LIST_CREATE _IOWR_(HAILO_VDMA_IOCTL_MAGIC, HAILO_DESC_LIST_CREATE_CODE, struct hailo_desc_list_create_params) +#define HAILO_DESC_LIST_RELEASE _IOR_(HAILO_VDMA_IOCTL_MAGIC, HAILO_DESC_LIST_RELEASE_CODE, struct hailo_desc_list_release_params) +#define HAILO_DESC_LIST_PROGRAM _IOR_(HAILO_VDMA_IOCTL_MAGIC, HAILO_DESC_LIST_PROGRAM_CODE, struct hailo_desc_list_program_params) + +#define HAILO_VDMA_LOW_MEMORY_BUFFER_ALLOC _IOWR_(HAILO_VDMA_IOCTL_MAGIC, HAILO_VDMA_LOW_MEMORY_BUFFER_ALLOC_CODE, struct hailo_allocate_low_memory_buffer_params) +#define HAILO_VDMA_LOW_MEMORY_BUFFER_FREE _IOR_(HAILO_VDMA_IOCTL_MAGIC, HAILO_VDMA_LOW_MEMORY_BUFFER_FREE_CODE, struct hailo_free_low_memory_buffer_params) + +#define HAILO_MARK_AS_IN_USE _IOW_(HAILO_VDMA_IOCTL_MAGIC, HAILO_MARK_AS_IN_USE_CODE, struct hailo_mark_as_in_use_params) + +#define HAILO_VDMA_CONTINUOUS_BUFFER_ALLOC _IOWR_(HAILO_VDMA_IOCTL_MAGIC, HAILO_VDMA_CONTINUOUS_BUFFER_ALLOC_CODE, struct hailo_allocate_continuous_buffer_params) +#define HAILO_VDMA_CONTINUOUS_BUFFER_FREE _IOR_(HAILO_VDMA_IOCTL_MAGIC, HAILO_VDMA_CONTINUOUS_BUFFER_FREE_CODE, struct hailo_free_continuous_buffer_params) + +#define HAILO_VDMA_LAUNCH_TRANSFER _IOWR_(HAILO_VDMA_IOCTL_MAGIC, HAILO_VDMA_LAUNCH_TRANSFER_CODE, struct hailo_vdma_launch_transfer_params) + +enum hailo_nnc_ioctl_code { + HAILO_FW_CONTROL_CODE, + HAILO_READ_NOTIFICATION_CODE, + HAILO_DISABLE_NOTIFICATION_CODE, + HAILO_READ_LOG_CODE, + HAILO_RESET_NN_CORE_CODE, + HAILO_WRITE_ACTION_LIST_CODE, + + // Must be last + HAILO_NNC_IOCTL_MAX_NR +}; + +#define HAILO_FW_CONTROL _IOWR_(HAILO_NNC_IOCTL_MAGIC, HAILO_FW_CONTROL_CODE, struct hailo_fw_control) +#define HAILO_READ_NOTIFICATION _IOW_(HAILO_NNC_IOCTL_MAGIC, HAILO_READ_NOTIFICATION_CODE, struct hailo_d2h_notification) +#define HAILO_DISABLE_NOTIFICATION _IO_(HAILO_NNC_IOCTL_MAGIC, HAILO_DISABLE_NOTIFICATION_CODE) +#define HAILO_READ_LOG _IOWR_(HAILO_NNC_IOCTL_MAGIC, HAILO_READ_LOG_CODE, struct hailo_read_log_params) +#define HAILO_RESET_NN_CORE _IO_(HAILO_NNC_IOCTL_MAGIC, HAILO_RESET_NN_CORE_CODE) +#define HAILO_WRITE_ACTION_LIST _IOW_(HAILO_NNC_IOCTL_MAGIC, HAILO_WRITE_ACTION_LIST_CODE, struct hailo_write_action_list_params) + +enum hailo_soc_ioctl_code { + HAILO_SOC_IOCTL_CONNECT_CODE, + HAILO_SOC_IOCTL_CLOSE_CODE, + + // Must be last + HAILO_SOC_IOCTL_MAX_NR, +}; + +#define HAILO_SOC_CONNECT _IOWR_(HAILO_SOC_IOCTL_MAGIC, HAILO_SOC_IOCTL_CONNECT_CODE, struct hailo_soc_connect_params) +#define HAILO_SOC_CLOSE _IOR_(HAILO_SOC_IOCTL_MAGIC, HAILO_SOC_IOCTL_CLOSE_CODE, struct hailo_soc_close_params) + + +enum hailo_pci_ep_ioctl_code { + HAILO_PCI_EP_ACCEPT_CODE, + HAILO_PCI_EP_CLOSE_CODE, + + // Must be last + HAILO_PCI_EP_IOCTL_MAX_NR, +}; + +#define HAILO_PCI_EP_ACCEPT _IOWR_(HAILO_PCI_EP_IOCTL_MAGIC, HAILO_PCI_EP_ACCEPT_CODE, struct hailo_pci_ep_accept_params) +#define HAILO_PCI_EP_CLOSE _IOR_(HAILO_PCI_EP_IOCTL_MAGIC, HAILO_PCI_EP_CLOSE_CODE, struct hailo_pci_ep_close_params) + +#endif /* _HAILO_IOCTL_COMMON_H_ */ diff --git a/drivers/media/pci/hailo/common/hailo_pcie_version.h b/drivers/media/pci/hailo/common/hailo_pcie_version.h new file mode 100644 index 00000000000000..059e5d8a5c8757 --- /dev/null +++ b/drivers/media/pci/hailo/common/hailo_pcie_version.h @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * Copyright (c) 2019-2022 Hailo Technologies Ltd. All rights reserved. + **/ + +#ifndef _HAILO_COMMON_PCIE_VERSION_H_ +#define _HAILO_COMMON_PCIE_VERSION_H_ + +#define HAILO_DRV_VER_MAJOR 4 +#define HAILO_DRV_VER_MINOR 17 +#define HAILO_DRV_VER_REVISION 0 + +#endif /* _HAILO_COMMON_PCIE_VERSION_H_ */ \ No newline at end of file diff --git a/drivers/media/pci/hailo/common/hailo_resource.c b/drivers/media/pci/hailo/common/hailo_resource.c new file mode 100644 index 00000000000000..548deb2da262a0 --- /dev/null +++ b/drivers/media/pci/hailo/common/hailo_resource.c @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: MIT +/** + * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved. + **/ + +#include "hailo_resource.h" + +#include "utils.h" + +#include <linux/io.h> +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/kernel.h> + +#define ALIGN_TO_32_BIT(addr) ((addr) & (~((uintptr_t)0x3))) + +u8 hailo_resource_read8(struct hailo_resource *resource, size_t offset) +{ + u32 val = ioread32((u8*)ALIGN_TO_32_BIT(resource->address + offset)); + u64 offset_in_bits = BITS_IN_BYTE * ((resource->address + offset) - ALIGN_TO_32_BIT(resource->address + offset)); + return (u8)READ_BITS_AT_OFFSET(BYTE_SIZE * BITS_IN_BYTE, offset_in_bits, val); +} + +u16 hailo_resource_read16(struct hailo_resource *resource, size_t offset) +{ + u32 val = ioread32((u8*)ALIGN_TO_32_BIT(resource->address + offset)); + u64 offset_in_bits = BITS_IN_BYTE * ((resource->address + offset) - ALIGN_TO_32_BIT(resource->address + offset)); + return (u16)READ_BITS_AT_OFFSET(WORD_SIZE * BITS_IN_BYTE, offset_in_bits, val); +} + +u32 hailo_resource_read32(struct hailo_resource *resource, size_t offset) +{ + return ioread32((u8*)resource->address + offset); +} + +void hailo_resource_write8(struct hailo_resource *resource, size_t offset, u8 value) +{ + u32 initial_val = ioread32((u8*)ALIGN_TO_32_BIT(resource->address + offset)); + u64 offset_in_bits = BITS_IN_BYTE * ((resource->address + offset) - ALIGN_TO_32_BIT(resource->address + offset)); + iowrite32(WRITE_BITS_AT_OFFSET(BYTE_SIZE * BITS_IN_BYTE, offset_in_bits, initial_val, value), + (u8*)ALIGN_TO_32_BIT(resource->address + offset)); +} + +void hailo_resource_write16(struct hailo_resource *resource, size_t offset, u16 value) +{ + u32 initial_val = ioread32((u8*)ALIGN_TO_32_BIT(resource->address + offset)); + u64 offset_in_bits = BITS_IN_BYTE * ((resource->address + offset) - ALIGN_TO_32_BIT(resource->address + offset)); + iowrite32(WRITE_BITS_AT_OFFSET(WORD_SIZE * BITS_IN_BYTE, offset_in_bits, initial_val, value), + (u8*)ALIGN_TO_32_BIT(resource->address + offset)); +} + +void hailo_resource_write32(struct hailo_resource *resource, size_t offset, u32 value) +{ + iowrite32(value, (u8*)resource->address + offset); +} + +void hailo_resource_read_buffer(struct hailo_resource *resource, size_t offset, size_t count, void *to) +{ + // Copied and modified from linux aarch64 (using ioread32 instead of readq that does not work all the time) + uintptr_t to_ptr = (uintptr_t)to; + while ((count > 0) && (!IS_ALIGNED(to_ptr, 4) || !IS_ALIGNED((uintptr_t)resource->address + offset, 4))) { + *(u8*)to_ptr = hailo_resource_read8(resource, offset); + to_ptr++; + offset++; + count--; + } + + while (count >= 4) { + *(u32*)to_ptr = hailo_resource_read32(resource, offset); + to_ptr += 4; + offset += 4; + count -= 4; + } + + while (count > 0) { + *(u8*)to_ptr = hailo_resource_read8(resource, offset); + to_ptr++; + offset++; + count--; + } +} + +int hailo_resource_write_buffer(struct hailo_resource *resource, size_t offset, size_t count, const void *from) +{ + // read the bytes after writing them for flushing the data. This function also checks if the pcie link + // is broken. + uintptr_t from_ptr = (uintptr_t)from; + while (count && (!IS_ALIGNED(resource->address + offset, 4) || !IS_ALIGNED(from_ptr, 4))) { + hailo_resource_write8(resource, offset, *(u8*)from_ptr); + if (hailo_resource_read8(resource, offset) != *(u8*)from_ptr) { + return -EIO; + } + from_ptr++; + offset++; + count--; + } + + while (count >= 4) { + hailo_resource_write32(resource, offset, *(u32*)from_ptr); + if (hailo_resource_read32(resource, offset) != *(u32*)from_ptr) { + return -EIO; + } + from_ptr += 4; + offset += 4; + count -= 4; + } + + while (count) { + hailo_resource_write8(resource, offset, *(u8*)from_ptr); + if (hailo_resource_read8(resource, offset) != *(u8*)from_ptr) { + return -EIO; + } + from_ptr++; + offset++; + count--; + } + + return 0; +} + +int hailo_resource_transfer(struct hailo_resource *resource, struct hailo_memory_transfer_params *transfer) +{ + // Check for transfer size (address is in resources address-space) + if ((transfer->address + transfer->count) > (u64)resource->size) { + return -EINVAL; + } + + if (transfer->count > ARRAY_SIZE(transfer->buffer)) { + return -EINVAL; + } + + switch (transfer->transfer_direction) { + case TRANSFER_READ: + hailo_resource_read_buffer(resource, (u32)transfer->address, transfer->count, transfer->buffer); + return 0; + case TRANSFER_WRITE: + return hailo_resource_write_buffer(resource, (u32)transfer->address, transfer->count, transfer->buffer); + default: + return -EINVAL; + } +} diff --git a/drivers/media/pci/hailo/common/hailo_resource.h b/drivers/media/pci/hailo/common/hailo_resource.h new file mode 100644 index 00000000000000..c27a097568760e --- /dev/null +++ b/drivers/media/pci/hailo/common/hailo_resource.h @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +/** + * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved. + **/ + +#ifndef _HAILO_COMMON_HAILO_RESOURCE_H_ +#define _HAILO_COMMON_HAILO_RESOURCE_H_ + +#include "hailo_ioctl_common.h" +#include <linux/types.h> + +struct hailo_resource { + uintptr_t address; + size_t size; +}; + +#ifdef __cplusplus +extern "C" { +#endif + +// Implemented by the specific platform +u32 hailo_resource_read32(struct hailo_resource *resource, size_t offset); +u16 hailo_resource_read16(struct hailo_resource *resource, size_t offset); +u8 hailo_resource_read8(struct hailo_resource *resource, size_t offset); +void hailo_resource_write32(struct hailo_resource *resource, size_t offset, u32 value); +void hailo_resource_write16(struct hailo_resource *resource, size_t offset, u16 value); +void hailo_resource_write8(struct hailo_resource *resource, size_t offset, u8 value); + +void hailo_resource_read_buffer(struct hailo_resource *resource, size_t offset, size_t count, void *to); +int hailo_resource_write_buffer(struct hailo_resource *resource, size_t offset, size_t count, const void *from); + +// Transfer (read/write) the given resource into/from transfer params. +int hailo_resource_transfer(struct hailo_resource *resource, struct hailo_memory_transfer_params *transfer); + +#ifdef __cplusplus +} +#endif + +#endif /* _HAILO_COMMON_HAILO_RESOURCE_H_ */ \ No newline at end of file diff --git a/drivers/media/pci/hailo/common/pcie_common.c b/drivers/media/pci/hailo/common/pcie_common.c new file mode 100644 index 00000000000000..a119d637cf4d75 --- /dev/null +++ b/drivers/media/pci/hailo/common/pcie_common.c @@ -0,0 +1,913 @@ +// SPDX-License-Identifier: MIT +/** + * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved. + **/ + +#include "pcie_common.h" +#include "fw_operation.h" +#include "soc_structs.h" + +#include <linux/errno.h> +#include <linux/bug.h> +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/printk.h> +#include <linux/device.h> + + +#define BSC_IMASK_HOST (0x0188) +#define BCS_ISTATUS_HOST (0x018C) +#define BCS_SOURCE_INTERRUPT_PER_CHANNEL (0x400) +#define BCS_DESTINATION_INTERRUPT_PER_CHANNEL (0x500) + +#define PO2_ROUND_UP(size, alignment) ((size + alignment-1) & ~(alignment-1)) + +#define ATR_PARAM (0x17) +#define ATR_SRC_ADDR (0x0) +#define ATR_TRSL_PARAM (6) +#define ATR_TABLE_SIZE (0x1000u) +#define ATR_TABLE_SIZE_MASK (0x1000u - 1) + +#define ATR0_PCIE_BRIDGE_OFFSET (0x700) + +#define ATR_PCIE_BRIDGE_OFFSET(atr_index) (ATR0_PCIE_BRIDGE_OFFSET + (atr_index * 0x20)) + +#define MAXIMUM_APP_FIRMWARE_CODE_SIZE (0x40000) +#define MAXIMUM_CORE_FIRMWARE_CODE_SIZE (0x20000) + +#define FIRMWARE_LOAD_WAIT_MAX_RETRIES (100) +#define FIRMWARE_LOAD_SLEEP_MS (50) + +#define PCIE_REQUEST_SIZE_OFFSET (0x640) + +#define PCIE_CONFIG_VENDOR_OFFSET (0x0098) + +#define HAILO_PCIE_DMA_DEVICE_INTERRUPTS_BITMASK (1 << 4) +#define HAILO_PCIE_DMA_HOST_INTERRUPTS_BITMASK (1 << 5) +#define HAILO_PCIE_DMA_SRC_CHANNELS_BITMASK (0x0000FFFF) + +#define HAILO_PCIE_MAX_ATR_TABLE_INDEX (3) + +#define BOOT_STATUS_UNINITIALIZED (0x1) + +#define PCIE_CONTROL_SECTION_ADDRESS_H8 (0x60000000) +#define PCIE_BLOCK_ADDRESS_ATR1 (0x200000) + +#define PCIE_CONFIG_PCIE_CFG_QM_ROUTING_MODE_SET(reg_offset) \ + (reg_offset) = (((reg_offset) & ~0x00000004L) | ((uint32_t)(1) << 2)) + + +struct hailo_fw_addresses { + u32 boot_fw_header; + u32 app_fw_code_ram_base; + u32 boot_key_cert; + u32 boot_cont_cert; + u32 core_code_ram_base; + u32 core_fw_header; + u32 raise_ready_offset; + u32 boot_status; + u32 pcie_cfg_regs; +}; + +struct hailo_board_compatibility { + struct hailo_fw_addresses fw_addresses; + const struct hailo_pcie_loading_stage stages[MAX_LOADING_STAGES]; +}; + +static const struct hailo_file_batch hailo10h_files_stg1[] = { + { + .filename = "hailo/hailo10h/customer_certificate.bin", + .address = 0xA0000, + .max_size = 0x8004, + .is_mandatory = true, + .has_header = false, + .has_core = false + }, + { + .filename = "hailo/hailo10h/u-boot.dtb.signed", + .address = 0xA8004, + .max_size = 0x20000, + .is_mandatory = true, + .has_header = false, + .has_core = false + }, + { + .filename = "hailo/hailo10h/scu_fw.bin", + .address = 0x20000, + .max_size = 0x40000, + .is_mandatory = true, + .has_header = true, + .has_core = false + }, + { + .filename = NULL, + .address = 0x00, + .max_size = 0x00, + .is_mandatory = false, + .has_header = false, + .has_core = false + } +}; + +static const struct hailo_file_batch hailo10h_files_stg2[] = { + { + .filename = "hailo/hailo10h/u-boot-spl.bin", + .address = 0x85000000, + .max_size = 0x1000000, + .is_mandatory = true, + .has_header = false, + .has_core = false + }, + { + .filename = "hailo/hailo10h/u-boot-tfa.itb", + .address = 0x86000000, + .max_size = 0x1000000, + .is_mandatory = true, + .has_header = false, + .has_core = false + }, + { + .filename = "hailo/hailo10h/fitImage", + .address = 0x87000000, + .max_size = 0x1000000, + .is_mandatory = true, + .has_header = false, + .has_core = false + }, + { + .filename = "hailo/hailo10h/image-fs", +#ifndef HAILO_EMULATOR + .address = 0x88000000, +#else + // TODO : HRT-15692 - merge two cases + .address = 0x89000000, +#endif /* ifndef HAILO_EMULATOR */ + .max_size = 0x20000000, // Max size 512MB + .is_mandatory = true, + .has_header = false, + .has_core = false + } +}; + +// If loading linux from EMMC - only need few files from second batch (u-boot-spl.bin and u-boot-tfa.itb) +static const struct hailo_file_batch hailo10h_files_stg2_linux_in_emmc[] = { + { + .filename = "hailo/hailo10h/u-boot-spl.bin", + .address = 0x85000000, + .max_size = 0x1000000, + .is_mandatory = true, + .has_header = false, + .has_core = false + }, + { + .filename = "hailo/hailo10h/u-boot-tfa.itb", + .address = 0x86000000, + .max_size = 0x1000000, + .is_mandatory = true, + .has_header = false, + .has_core = false + }, + { + .filename = NULL, + .address = 0x00, + .max_size = 0x00, + .is_mandatory = false, + .has_header = false, + .has_core = false + }, +}; + +static const struct hailo_file_batch hailo8_files_stg1[] = { + { + .filename = "hailo/hailo8_fw.bin", + .address = 0x20000, + .max_size = 0x50000, + .is_mandatory = true, + .has_header = true, + .has_core = true + }, + { + .filename = "hailo/hailo8_board_cfg.bin", + .address = 0x60001000, + .max_size = PCIE_HAILO8_BOARD_CFG_MAX_SIZE, + .is_mandatory = false, + .has_header = false, + .has_core = false + }, + { + .filename = "hailo/hailo8_fw_cfg.bin", + .address = 0x60001500, + .max_size = PCIE_HAILO8_FW_CFG_MAX_SIZE, + .is_mandatory = false, + .has_header = false, + .has_core = false + }, + { + .filename = NULL, + .address = 0x00, + .max_size = 0x00, + .is_mandatory = false, + .has_header = false, + .has_core = false + } +}; + +static const struct hailo_file_batch hailo10h_legacy_files_stg1[] = { + { + .filename = "hailo/hailo15_fw.bin", + .address = 0x20000, + .max_size = 0x100000, + .is_mandatory = true, + .has_header = true, + .has_core = true + }, + { + .filename = NULL, + .address = 0x00, + .max_size = 0x00, + .is_mandatory = false, + .has_header = false, + .has_core = false + } +}; + +// TODO HRT-15014 - Fix names for hailo15l legacy accelerator +static const struct hailo_file_batch hailo15l_files_stg1[] = { + { + .filename = "hailo/hailo15l_fw.bin", + .address = 0x20000, + .max_size = 0x100000, + .is_mandatory = true, + .has_header = true, + .has_core = true + }, + { + .filename = NULL, + .address = 0x00, + .max_size = 0x00, + .is_mandatory = false, + .has_header = false, + .has_core = false + } +}; + +static const struct hailo_board_compatibility compat[HAILO_BOARD_TYPE_COUNT] = { + [HAILO_BOARD_TYPE_HAILO8] = { + .fw_addresses = { + .boot_fw_header = 0xE0030, + .boot_key_cert = 0xE0048, + .boot_cont_cert = 0xE0390, + .app_fw_code_ram_base = 0x60000, + .core_code_ram_base = 0xC0000, + .core_fw_header = 0xA0000, + .raise_ready_offset = 0x1684, + .boot_status = 0xe0000, + }, + .stages = { + { + .batch = hailo8_files_stg1, + .trigger_address = 0xE0980, + .timeout = FIRMWARE_WAIT_TIMEOUT_MS, + .amount_of_files_in_stage = 3 + }, + }, + }, + [HAILO_BOARD_TYPE_HAILO10H_LEGACY] = { + .fw_addresses = { + .boot_fw_header = 0x88000, + .boot_key_cert = 0x88018, + .boot_cont_cert = 0x886a8, + .app_fw_code_ram_base = 0x20000, + .core_code_ram_base = 0x60000, + .core_fw_header = 0xC0000, + .raise_ready_offset = 0x1754, + .boot_status = 0x80000, + }, + .stages = { + { + .batch = hailo10h_legacy_files_stg1, + .trigger_address = 0x88c98, + .timeout = FIRMWARE_WAIT_TIMEOUT_MS, + .amount_of_files_in_stage = 1 + }, + }, + }, + [HAILO_BOARD_TYPE_HAILO10H] = { + .fw_addresses = { + .boot_fw_header = 0x88000, + .boot_key_cert = 0x88018, + .boot_cont_cert = 0x886a8, + .app_fw_code_ram_base = 0x20000, + .core_code_ram_base = 0, + .core_fw_header = 0, + .raise_ready_offset = 0x1754, + .boot_status = 0x80000, + .pcie_cfg_regs = 0x002009dc, + }, + .stages = { + { + .batch = hailo10h_files_stg1, + .trigger_address = 0x88c98, + .timeout = FIRMWARE_WAIT_TIMEOUT_MS, + .amount_of_files_in_stage = 3 + }, + { + .batch = hailo10h_files_stg2, + .trigger_address = 0x84000000, + .timeout = PCI_EP_WAIT_TIMEOUT_MS, + .amount_of_files_in_stage = 4 + }, + { + .batch = hailo10h_files_stg2_linux_in_emmc, + .trigger_address = 0x84000000, + .timeout = FIRMWARE_WAIT_TIMEOUT_MS, + .amount_of_files_in_stage = 2 + }, + }, + }, + // HRT-11344 : none of these matter except raise_ready_offset seeing as we load fw seperately - not through driver + // After implementing bootloader put correct values here + [HAILO_BOARD_TYPE_HAILO15L] = { + .fw_addresses = { + .boot_fw_header = 0x88000, + .boot_key_cert = 0x88018, + .boot_cont_cert = 0x886a8, + .app_fw_code_ram_base = 0x20000, + .core_code_ram_base = 0x60000, + .core_fw_header = 0xC0000, + // NOTE: After they update hw consts - check register fw_access_interrupt_w1s of pcie_config + .raise_ready_offset = 0x174c, + .boot_status = 0x80000, + }, + .stages = { + { + .batch = hailo15l_files_stg1, + .trigger_address = 0x88c98, + .timeout = FIRMWARE_WAIT_TIMEOUT_MS, + .amount_of_files_in_stage = 1 + }, + }, + } +}; + +const struct hailo_pcie_loading_stage *hailo_pcie_get_loading_stage_info(enum hailo_board_type board_type, + enum loading_stages stage) +{ + return &compat[board_type].stages[stage]; +} + +static u32 read_and_clear_reg(struct hailo_resource *resource, u32 offset) +{ + u32 value = hailo_resource_read32(resource, offset); + if (value != 0) { + hailo_resource_write32(resource, offset, value); + } + return value; +} + +bool hailo_pcie_read_interrupt(struct hailo_pcie_resources *resources, struct hailo_pcie_interrupt_source *source) +{ + u32 istatus_host = 0; + memset(source, 0, sizeof(*source)); + + istatus_host = read_and_clear_reg(&resources->config, BCS_ISTATUS_HOST); + if (0 == istatus_host) { + return false; + } + + source->sw_interrupts = (istatus_host >> BCS_ISTATUS_HOST_SW_IRQ_SHIFT); + + if (istatus_host & BCS_ISTATUS_HOST_VDMA_SRC_IRQ_MASK) { + source->vdma_channels_bitmap |= read_and_clear_reg(&resources->config, BCS_SOURCE_INTERRUPT_PER_CHANNEL); + } + if (istatus_host & BCS_ISTATUS_HOST_VDMA_DEST_IRQ_MASK) { + source->vdma_channels_bitmap |= read_and_clear_reg(&resources->config, BCS_DESTINATION_INTERRUPT_PER_CHANNEL); + } + + return true; +} + +int hailo_pcie_write_firmware_control(struct hailo_pcie_resources *resources, const struct hailo_fw_control *command) +{ + int err = 0; + u32 request_size = 0; + u8 fw_access_value = FW_ACCESS_APP_CPU_CONTROL_MASK; + const struct hailo_fw_addresses *fw_addresses = &(compat[resources->board_type].fw_addresses); + + if (!hailo_pcie_is_firmware_loaded(resources)) { + return -ENODEV; + } + + // Copy md5 + buffer_len + buffer + request_size = sizeof(command->expected_md5) + sizeof(command->buffer_len) + command->buffer_len; + err = hailo_resource_write_buffer(&resources->fw_access, 0, PO2_ROUND_UP(request_size, FW_CODE_SECTION_ALIGNMENT), + command); + if (err < 0) { + return err; + } + + // Raise the bit for the CPU that will handle the control + fw_access_value = (command->cpu_id == HAILO_CPU_ID_CPU1) ? FW_ACCESS_CORE_CPU_CONTROL_MASK : + FW_ACCESS_APP_CPU_CONTROL_MASK; + + // Raise ready flag to FW + hailo_resource_write32(&resources->fw_access, fw_addresses->raise_ready_offset, (u32)fw_access_value); + return 0; +} + +int hailo_pcie_read_firmware_control(struct hailo_pcie_resources *resources, struct hailo_fw_control *command) +{ + u32 response_header_size = 0; + + // Copy response md5 + buffer_len + response_header_size = sizeof(command->expected_md5) + sizeof(command->buffer_len); + + hailo_resource_read_buffer(&resources->fw_access, PCIE_REQUEST_SIZE_OFFSET, response_header_size, command); + + if (sizeof(command->buffer) < command->buffer_len) { + return -EINVAL; + } + + // Copy response buffer + hailo_resource_read_buffer(&resources->fw_access, PCIE_REQUEST_SIZE_OFFSET + (size_t)response_header_size, + command->buffer_len, &command->buffer); + + return 0; +} + +void hailo_pcie_write_firmware_driver_shutdown(struct hailo_pcie_resources *resources) +{ + const struct hailo_fw_addresses *fw_addresses = &(compat[resources->board_type].fw_addresses); + const u32 fw_access_value = FW_ACCESS_DRIVER_SHUTDOWN_MASK; + + // Write shutdown flag to FW + hailo_resource_write32(&resources->fw_access, fw_addresses->raise_ready_offset, fw_access_value); +} + +void hailo_pcie_write_firmware_soft_reset(struct hailo_pcie_resources *resources) +{ + const struct hailo_fw_addresses *fw_addresses = &(compat[resources->board_type].fw_addresses); + const u32 fw_access_value = FW_ACCESS_SOFT_RESET_MASK; + + // Write shutdown flag to FW + hailo_resource_write32(&resources->fw_access, fw_addresses->raise_ready_offset, fw_access_value); +} + +int hailo_pcie_configure_atr_table(struct hailo_resource *bridge_config, u64 trsl_addr, u32 atr_index) +{ + size_t offset = 0; + struct hailo_atr_config atr = { + .atr_param = (ATR_PARAM | (atr_index << 12)), + .atr_src = ATR_SRC_ADDR, + .atr_trsl_addr_1 = (u32)(trsl_addr & 0xFFFFFFFF), + .atr_trsl_addr_2 = (u32)(trsl_addr >> 32), + .atr_trsl_param = ATR_TRSL_PARAM + }; + + BUG_ON(HAILO_PCIE_MAX_ATR_TABLE_INDEX < atr_index); + offset = ATR_PCIE_BRIDGE_OFFSET(atr_index); + + return hailo_resource_write_buffer(bridge_config, offset, sizeof(atr), (void*)&atr); +} + +void hailo_pcie_read_atr_table(struct hailo_resource *bridge_config, struct hailo_atr_config *atr, u32 atr_index) +{ + size_t offset = 0; + + BUG_ON(HAILO_PCIE_MAX_ATR_TABLE_INDEX < atr_index); + offset = ATR_PCIE_BRIDGE_OFFSET(atr_index); + + hailo_resource_read_buffer(bridge_config, offset, sizeof(*atr), (void*)atr); +} + +static void write_memory_chunk(struct hailo_pcie_resources *resources, + hailo_ptr_t dest, u32 dest_offset, const void *src, u32 len) +{ + u32 ATR_INDEX = 0; + BUG_ON(dest_offset + len > (u32)resources->fw_access.size); + + (void)hailo_pcie_configure_atr_table(&resources->config, dest, ATR_INDEX); + (void)hailo_resource_write_buffer(&resources->fw_access, dest_offset, len, src); +} + +static void read_memory_chunk( + struct hailo_pcie_resources *resources, hailo_ptr_t src, u32 src_offset, void *dest, u32 len) +{ + u32 ATR_INDEX = 0; + BUG_ON(src_offset + len > (u32)resources->fw_access.size); + + (void)hailo_pcie_configure_atr_table(&resources->config, src, ATR_INDEX); + (void)hailo_resource_read_buffer(&resources->fw_access, src_offset, len, dest); +} + +// Note: this function modify the device ATR table (that is also used by the firmware for control and vdma). +// Use with caution, and restore the original atr if needed. +static void write_memory(struct hailo_pcie_resources *resources, hailo_ptr_t dest, const void *src, u32 len) +{ + struct hailo_atr_config previous_atr = {0}; + hailo_ptr_t base_address = (dest & ~ATR_TABLE_SIZE_MASK); + u32 chunk_len = 0; + u32 offset = 0; + u32 ATR_INDEX = 0; + + // Store previous ATR (Read/write modify the ATR). + hailo_pcie_read_atr_table(&resources->config, &previous_atr, ATR_INDEX); + + if (base_address != dest) { + // Data is not aligned, write the first chunk + chunk_len = min((u32)(base_address + ATR_TABLE_SIZE - dest), len); + write_memory_chunk(resources, base_address, (u32)(dest - base_address), src, chunk_len); + offset += chunk_len; + } + + while (offset < len) { + chunk_len = min(len - offset, ATR_TABLE_SIZE); + write_memory_chunk(resources, dest + offset, 0, (const u8*)src + offset, chunk_len); + offset += chunk_len; + } + + (void)hailo_pcie_configure_atr_table(&resources->config, + (((u64)(previous_atr.atr_trsl_addr_2) << 32) | previous_atr.atr_trsl_addr_1), ATR_INDEX); +} + +// Note: this function modify the device ATR table (that is also used by the firmware for control and vdma). +// Use with caution, and restore the original atr if needed. +static void read_memory(struct hailo_pcie_resources *resources, hailo_ptr_t src, void *dest, u32 len) +{ + struct hailo_atr_config previous_atr = {0}; + hailo_ptr_t base_address = (src & ~ATR_TABLE_SIZE_MASK); + u32 chunk_len = 0; + u32 offset = 0; + u32 ATR_INDEX = 0; + + // Store previous ATR (Read/write modify the ATR). + hailo_pcie_read_atr_table(&resources->config, &previous_atr, ATR_INDEX); + + if (base_address != src) { + // Data is not aligned, read the first chunk + chunk_len = min((u32)(base_address + ATR_TABLE_SIZE - src), len); + read_memory_chunk(resources, base_address, (u32)(src - base_address), dest, chunk_len); + offset += chunk_len; + } + + while (offset < len) { + chunk_len = min(len - offset, ATR_TABLE_SIZE); + read_memory_chunk(resources, src + offset, 0, (u8*)dest + offset, chunk_len); + offset += chunk_len; + } + + (void)hailo_pcie_configure_atr_table(&resources->config, + (((u64)(previous_atr.atr_trsl_addr_2) << 32) | previous_atr.atr_trsl_addr_1), ATR_INDEX); +} + +// Note: This function use for enabling the vDMA transaction host<->device by read modify write of the EP registers in the SOC - for fast boot over vDMA. +void hailo_pcie_configure_ep_registers_for_dma_transaction(struct hailo_pcie_resources *resources) +{ + u32 reg_routing_mercury = 0; + + BUG_ON(compat[resources->board_type].fw_addresses.pcie_cfg_regs == 0); + + read_memory(resources, compat[resources->board_type].fw_addresses.pcie_cfg_regs, ®_routing_mercury, sizeof(reg_routing_mercury)); + PCIE_CONFIG_PCIE_CFG_QM_ROUTING_MODE_SET(reg_routing_mercury); + write_memory(resources, compat[resources->board_type].fw_addresses.pcie_cfg_regs, ®_routing_mercury, sizeof(reg_routing_mercury)); +} + +static void hailo_write_app_firmware(struct hailo_pcie_resources *resources, firmware_header_t *fw_header, + secure_boot_certificate_header_t *fw_cert) +{ + const struct hailo_fw_addresses *fw_addresses = &(compat[resources->board_type].fw_addresses); + u8 *fw_code = ((u8*)fw_header + sizeof(firmware_header_t)); + u8 *key_data = ((u8*)fw_cert + sizeof(secure_boot_certificate_header_t)); + u8 *content_data = key_data + fw_cert->key_size; + + write_memory(resources, fw_addresses->boot_fw_header, fw_header, sizeof(firmware_header_t)); + + write_memory(resources, fw_addresses->app_fw_code_ram_base, fw_code, fw_header->code_size); + + write_memory(resources, fw_addresses->boot_key_cert, key_data, fw_cert->key_size); + write_memory(resources, fw_addresses->boot_cont_cert, content_data, fw_cert->content_size); +} + +static void hailo_write_core_firmware(struct hailo_pcie_resources *resources, firmware_header_t *fw_header) +{ + const struct hailo_fw_addresses *fw_addresses = &(compat[resources->board_type].fw_addresses); + void *fw_code = (void*)((u8*)fw_header + sizeof(firmware_header_t)); + + write_memory(resources, fw_addresses->core_code_ram_base, fw_code, fw_header->code_size); + write_memory(resources, fw_addresses->core_fw_header, fw_header, sizeof(firmware_header_t)); +} + +void hailo_trigger_firmware_boot(struct hailo_pcie_resources *resources, u32 stage) +{ + u32 pcie_finished = 1; + + write_memory(resources, compat[resources->board_type].stages[stage].trigger_address, (void*)&pcie_finished, sizeof(pcie_finished)); +} + +u32 hailo_get_boot_status(struct hailo_pcie_resources *resources) +{ + u32 boot_status = 0; + const struct hailo_fw_addresses *fw_addresses = &(compat[resources->board_type].fw_addresses); + + read_memory(resources, fw_addresses->boot_status, &boot_status, sizeof(boot_status)); + + return boot_status; +} + +/** +* Validates the FW headers. +* @param[in] address Address of the firmware. +* @param[in] firmware_size Size of the firmware. +* @param[out] out_app_firmware_header (optional) App firmware header +* @param[out] out_core_firmware_header (optional) Core firmware header +* @param[out] out_firmware_cert (optional) Firmware certificate header +*/ +static int FW_VALIDATION__validate_fw_headers(uintptr_t firmware_base_address, size_t firmware_size, + firmware_header_t **out_app_firmware_header, firmware_header_t **out_core_firmware_header, + secure_boot_certificate_header_t **out_firmware_cert, enum hailo_board_type board_type) +{ + firmware_header_t *app_firmware_header = NULL; + firmware_header_t *core_firmware_header = NULL; + secure_boot_certificate_header_t *firmware_cert = NULL; + int err = -EINVAL; + u32 consumed_firmware_offset = 0; + + err = FW_VALIDATION__validate_fw_header(firmware_base_address, firmware_size, MAXIMUM_APP_FIRMWARE_CODE_SIZE, + &consumed_firmware_offset, &app_firmware_header, board_type); + if (0 != err) { + err = -EINVAL; + goto exit; + } + + err = FW_VALIDATION__validate_cert_header(firmware_base_address, firmware_size, + &consumed_firmware_offset, &firmware_cert); + if (0 != err) { + err = -EINVAL; + goto exit; + } + + // Not validating with HAILO10H since core firmware doesn't loaded over pcie + if (HAILO_BOARD_TYPE_HAILO10H != board_type) { + err = FW_VALIDATION__validate_fw_header(firmware_base_address, firmware_size, MAXIMUM_CORE_FIRMWARE_CODE_SIZE, + &consumed_firmware_offset, &core_firmware_header, board_type); + if (0 != err) { + err = -EINVAL; + goto exit; + } + } + + if (consumed_firmware_offset != firmware_size) { + /* it is an error if there is leftover data after the last firmware header */ + err = -EINVAL; + goto exit; + } + + /* the out params are all optional */ + if (NULL != out_app_firmware_header) { + *out_app_firmware_header = app_firmware_header; + } + if (NULL != out_firmware_cert) { + *out_firmware_cert = firmware_cert; + } + if (NULL != out_core_firmware_header) { + *out_core_firmware_header = core_firmware_header; + } + err = 0; + +exit: + return err; +} + +static int write_single_file(struct hailo_pcie_resources *resources, const struct hailo_file_batch *file_info, struct device *dev) +{ + const struct firmware *firmware = NULL; + firmware_header_t *app_firmware_header = NULL; + secure_boot_certificate_header_t *firmware_cert = NULL; + firmware_header_t *core_firmware_header = NULL; + int err = 0; + + err = request_firmware_direct(&firmware, file_info->filename, dev); + if (err < 0) { + return err; + } + + if (firmware->size > file_info->max_size) { + release_firmware(firmware); + return -EFBIG; + } + + if (file_info->has_header) { + err = FW_VALIDATION__validate_fw_headers((uintptr_t)firmware->data, firmware->size, + &app_firmware_header, &core_firmware_header, &firmware_cert, resources->board_type); + if (err < 0) { + release_firmware(firmware); + return err; + } + + hailo_write_app_firmware(resources, app_firmware_header, firmware_cert); + if (file_info->has_core) { + hailo_write_core_firmware(resources, core_firmware_header); + } + } else { + write_memory(resources, file_info->address, (void*)firmware->data, firmware->size); + } + + release_firmware(firmware); + + return 0; +} + +int hailo_pcie_write_firmware_batch(struct device *dev, struct hailo_pcie_resources *resources, u32 stage) +{ + const struct hailo_pcie_loading_stage *stage_info = hailo_pcie_get_loading_stage_info(resources->board_type, stage); + const struct hailo_file_batch *files_batch = stage_info->batch; + const u8 amount_of_files = stage_info->amount_of_files_in_stage; + int file_index = 0; + int err = 0; + + for (file_index = 0; file_index < amount_of_files; file_index++) + { + dev_notice(dev, "Writing file %s\n", files_batch[file_index].filename); + + err = write_single_file(resources, &files_batch[file_index], dev); + if (err < 0) { + pr_warn("Failed to write file %s\n", files_batch[file_index].filename); + if (files_batch[file_index].is_mandatory) { + return err; + } + } + + dev_notice(dev, "File %s written successfully\n", files_batch[file_index].filename); + } + + hailo_trigger_firmware_boot(resources, stage); + + return 0; +} + +bool hailo_pcie_is_firmware_loaded(struct hailo_pcie_resources *resources) +{ + u32 offset; + u32 atr_value; + + if (HAILO_BOARD_TYPE_HAILO8 == resources->board_type) { + offset = ATR_PCIE_BRIDGE_OFFSET(0) + offsetof(struct hailo_atr_config, atr_trsl_addr_1); + atr_value = hailo_resource_read32(&resources->config, offset); + + return (PCIE_CONTROL_SECTION_ADDRESS_H8 == atr_value); + } + else { + offset = ATR_PCIE_BRIDGE_OFFSET(1) + offsetof(struct hailo_atr_config, atr_trsl_addr_1); + atr_value = hailo_resource_read32(&resources->config, offset); + + return (PCIE_BLOCK_ADDRESS_ATR1 == atr_value); + } + +} + +bool hailo_pcie_wait_for_firmware(struct hailo_pcie_resources *resources) +{ + size_t retries; + for (retries = 0; retries < FIRMWARE_LOAD_WAIT_MAX_RETRIES; retries++) { + if (hailo_pcie_is_firmware_loaded(resources)) { + return true; + } + + msleep(FIRMWARE_LOAD_SLEEP_MS); + } + + return false; +} + +void hailo_pcie_update_channel_interrupts_mask(struct hailo_pcie_resources* resources, u32 channels_bitmap) +{ + size_t i = 0; + u32 mask = hailo_resource_read32(&resources->config, BSC_IMASK_HOST); + + // Clear old channel interrupts + mask &= ~BCS_ISTATUS_HOST_VDMA_SRC_IRQ_MASK; + mask &= ~BCS_ISTATUS_HOST_VDMA_DEST_IRQ_MASK; + // Set interrupt by the bitmap + for (i = 0; i < MAX_VDMA_CHANNELS_PER_ENGINE; ++i) { + if (hailo_test_bit(i, &channels_bitmap)) { + // based on 18.5.2 "vDMA Interrupt Registers" in PLDA documentation + u32 offset = (i & 16) ? 8 : 0; + hailo_set_bit((((int)i*8) / MAX_VDMA_CHANNELS_PER_ENGINE) + offset, &mask); + } + } + hailo_resource_write32(&resources->config, BSC_IMASK_HOST, mask); +} + +void hailo_pcie_enable_interrupts(struct hailo_pcie_resources *resources) +{ + u32 mask = hailo_resource_read32(&resources->config, BSC_IMASK_HOST); + + hailo_resource_write32(&resources->config, BCS_ISTATUS_HOST, 0xFFFFFFFF); + hailo_resource_write32(&resources->config, BCS_DESTINATION_INTERRUPT_PER_CHANNEL, 0xFFFFFFFF); + hailo_resource_write32(&resources->config, BCS_SOURCE_INTERRUPT_PER_CHANNEL, 0xFFFFFFFF); + + mask |= BCS_ISTATUS_HOST_SW_IRQ_MASK; + hailo_resource_write32(&resources->config, BSC_IMASK_HOST, mask); +} + +void hailo_pcie_disable_interrupts(struct hailo_pcie_resources* resources) +{ + hailo_resource_write32(&resources->config, BSC_IMASK_HOST, 0); +} + +static int direct_memory_transfer(struct hailo_pcie_resources *resources, + struct hailo_memory_transfer_params *params) +{ + switch (params->transfer_direction) { + case TRANSFER_READ: + read_memory(resources, params->address, params->buffer, (u32)params->count); + break; + case TRANSFER_WRITE: + write_memory(resources, params->address, params->buffer, (u32)params->count); + break; + default: + return -EINVAL; + } + + return 0; +} + +int hailo_pcie_memory_transfer(struct hailo_pcie_resources *resources, struct hailo_memory_transfer_params *params) +{ + if (params->count > ARRAY_SIZE(params->buffer)) { + return -EINVAL; + } + + switch (params->memory_type) { + case HAILO_TRANSFER_DEVICE_DIRECT_MEMORY: + return direct_memory_transfer(resources, params); + case HAILO_TRANSFER_MEMORY_PCIE_BAR0: + return hailo_resource_transfer(&resources->config, params); + case HAILO_TRANSFER_MEMORY_PCIE_BAR2: + case HAILO_TRANSFER_MEMORY_VDMA0: + return hailo_resource_transfer(&resources->vdma_registers, params); + case HAILO_TRANSFER_MEMORY_PCIE_BAR4: + return hailo_resource_transfer(&resources->fw_access, params); + default: + return -EINVAL; + } +} + +bool hailo_pcie_is_device_connected(struct hailo_pcie_resources *resources) +{ + return PCI_VENDOR_ID_HAILO == hailo_resource_read16(&resources->config, PCIE_CONFIG_VENDOR_OFFSET); +} + +int hailo_set_device_type(struct hailo_pcie_resources *resources) +{ + switch(resources->board_type) { + case HAILO_BOARD_TYPE_HAILO8: + case HAILO_BOARD_TYPE_HAILO10H_LEGACY: + case HAILO_BOARD_TYPE_HAILO15L: + resources->accelerator_type = HAILO_ACCELERATOR_TYPE_NNC; + break; + case HAILO_BOARD_TYPE_HAILO10H: + resources->accelerator_type = HAILO_ACCELERATOR_TYPE_SOC; + break; + default: + return -EINVAL; + } + + return 0; +} + +// On PCIe, just return the start address +u64 hailo_pcie_encode_desc_dma_address_range(dma_addr_t dma_address_start, dma_addr_t dma_address_end, u32 step, u8 channel_id) +{ + (void)channel_id; + (void)dma_address_end; + (void)step; + return (u64)dma_address_start; +} + +struct hailo_vdma_hw hailo_pcie_vdma_hw = { + .hw_ops = { + .encode_desc_dma_address_range = hailo_pcie_encode_desc_dma_address_range, + }, + .ddr_data_id = HAILO_PCIE_HOST_DMA_DATA_ID, + .device_interrupts_bitmask = HAILO_PCIE_DMA_DEVICE_INTERRUPTS_BITMASK, + .host_interrupts_bitmask = HAILO_PCIE_DMA_HOST_INTERRUPTS_BITMASK, + .src_channels_bitmask = HAILO_PCIE_DMA_SRC_CHANNELS_BITMASK, +}; + +void hailo_pcie_soc_write_request(struct hailo_pcie_resources *resources, + const struct hailo_pcie_soc_request *request) +{ + const struct hailo_fw_addresses *fw_addresses = &(compat[resources->board_type].fw_addresses); + BUILD_BUG_ON_MSG((sizeof(*request) % sizeof(u32)) != 0, "Request must be a multiple of 4 bytes"); + + hailo_resource_write_buffer(&resources->fw_access, 0, sizeof(*request), (void*)request); + hailo_resource_write32(&resources->fw_access, fw_addresses->raise_ready_offset, FW_ACCESS_SOC_CONTROL_MASK); +} + +void hailo_pcie_soc_read_response(struct hailo_pcie_resources *resources, + struct hailo_pcie_soc_response *response) +{ + BUILD_BUG_ON_MSG((sizeof(*response) % sizeof(u32)) != 0, "Request must be a multiple of 4 bytes"); + hailo_resource_read_buffer(&resources->fw_access, 0, sizeof(*response), response); +} diff --git a/drivers/media/pci/hailo/common/pcie_common.h b/drivers/media/pci/hailo/common/pcie_common.h new file mode 100644 index 00000000000000..9248a3bbdd3a31 --- /dev/null +++ b/drivers/media/pci/hailo/common/pcie_common.h @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: MIT +/** + * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved. + **/ + +#ifndef _HAILO_COMMON_PCIE_COMMON_H_ +#define _HAILO_COMMON_PCIE_COMMON_H_ + +#include "hailo_resource.h" +#include "hailo_ioctl_common.h" +#include "fw_validation.h" +#include "fw_operation.h" +#include "utils.h" +#include "vdma_common.h" +#include "soc_structs.h" + +#include <linux/types.h> +#include <linux/firmware.h> + + +#define BCS_ISTATUS_HOST_SW_IRQ_MASK (0xFF000000) +#define BCS_ISTATUS_HOST_SW_IRQ_SHIFT (24) +#define BCS_ISTATUS_HOST_VDMA_SRC_IRQ_MASK (0x000000FF) +#define BCS_ISTATUS_HOST_VDMA_DEST_IRQ_MASK (0x0000FF00) + +#define PCIE_HAILO8_BOARD_CFG_MAX_SIZE (0x500) +#define PCIE_HAILO8_FW_CFG_MAX_SIZE (0x500) + +#define FW_CODE_SECTION_ALIGNMENT (4) + +#define HAILO_PCIE_CONFIG_BAR (0) +#define HAILO_PCIE_VDMA_REGS_BAR (2) +#define HAILO_PCIE_FW_ACCESS_BAR (4) + +#define HAILO_PCIE_DMA_ENGINES_COUNT (1) +#define PCI_VDMA_ENGINE_INDEX (0) + +#define MAX_FILES_PER_STAGE (4) + +#define HAILO_PCIE_HOST_DMA_DATA_ID (0) +#define HAILO_PCI_EP_HOST_DMA_DATA_ID (6) + +#define DRIVER_NAME "hailo" + +#define PCI_VENDOR_ID_HAILO 0x1e60 +#define PCI_DEVICE_ID_HAILO_HAILO8 0x2864 +#define PCI_DEVICE_ID_HAILO_HAILO10H 0x45C4 +#define PCI_DEVICE_ID_HAILO_HAILO15L 0x43a2 + +typedef u64 hailo_ptr_t; + +struct hailo_pcie_resources { + struct hailo_resource config; // BAR0 + struct hailo_resource vdma_registers; // BAR2 + struct hailo_resource fw_access; // BAR4 + enum hailo_board_type board_type; + enum hailo_accelerator_type accelerator_type; +}; + +struct hailo_atr_config { + u32 atr_param; + u32 atr_src; + u32 atr_trsl_addr_1; + u32 atr_trsl_addr_2; + u32 atr_trsl_param; +}; + +enum loading_stages { + FIRST_STAGE = 0, + SECOND_STAGE = 1, + SECOND_STAGE_LINUX_IN_EMMC = 2, + MAX_LOADING_STAGES = 3 +}; + +enum hailo_pcie_nnc_sw_interrupt_masks { + HAILO_PCIE_NNC_FW_NOTIFICATION_IRQ = 0x2, + HAILO_PCIE_NNC_FW_CONTROL_IRQ = 0x4, + HAILO_PCIE_NNC_DRIVER_DOWN_IRQ = 0x8, +}; + +enum hailo_pcie_soc_sw_interrupt_masks { + HAILO_PCIE_SOC_CONTROL_IRQ = 0x10, + HAILO_PCIE_SOC_CLOSE_IRQ = 0x20, +}; + +enum hailo_pcie_boot_interrupt_masks { + HAILO_PCIE_BOOT_SOFT_RESET_IRQ = 0x1, + HAILO_PCIE_BOOT_IRQ = 0x2, +}; + +struct hailo_pcie_interrupt_source { + u32 sw_interrupts; + u32 vdma_channels_bitmap; +}; + +struct hailo_file_batch { + const char *filename; + u32 address; + size_t max_size; + bool is_mandatory; + bool has_header; + bool has_core; +}; + +struct hailo_pcie_loading_stage { + const struct hailo_file_batch *batch; + u32 trigger_address; + u32 timeout; + u8 amount_of_files_in_stage; +}; + +// TODO: HRT-6144 - Align Windows/Linux to QNX +#ifdef __QNX__ +enum hailo_bar_index { + BAR0 = 0, + BAR2, + BAR4, + MAX_BAR +}; +#else +enum hailo_bar_index { + BAR0 = 0, + BAR1, + BAR2, + BAR3, + BAR4, + BAR5, + MAX_BAR +}; +#endif // ifdef (__QNX__) + +#ifdef __cplusplus +extern "C" { +#endif + + +#ifndef HAILO_EMULATOR +#define TIME_UNTIL_REACH_BOOTLOADER (10) +#define PCI_EP_WAIT_TIMEOUT_MS (40000) +#define FIRMWARE_WAIT_TIMEOUT_MS (5000) +#else /* ifndef HAILO_EMULATOR */ +// PCI EP timeout is defined to 50000000 because on Emulator the boot time + linux init time can be very long (4+ hours) +#define TIME_UNTIL_REACH_BOOTLOADER (10000) +#define PCI_EP_WAIT_TIMEOUT_MS (50000000) +#define FIRMWARE_WAIT_TIMEOUT_MS (5000000) +#endif /* ifndef HAILO_EMULATOR */ + +extern struct hailo_vdma_hw hailo_pcie_vdma_hw; + +const struct hailo_pcie_loading_stage* hailo_pcie_get_loading_stage_info(enum hailo_board_type board_type, + enum loading_stages stage); + +// Reads the interrupt source from BARs, return false if there is no interrupt. +// note - this function clears the interrupt signals. +bool hailo_pcie_read_interrupt(struct hailo_pcie_resources *resources, struct hailo_pcie_interrupt_source *source); +void hailo_pcie_update_channel_interrupts_mask(struct hailo_pcie_resources *resources, u32 channels_bitmap); +void hailo_pcie_enable_interrupts(struct hailo_pcie_resources *resources); +void hailo_pcie_disable_interrupts(struct hailo_pcie_resources *resources); + +int hailo_pcie_write_firmware_control(struct hailo_pcie_resources *resources, const struct hailo_fw_control *command); +int hailo_pcie_read_firmware_control(struct hailo_pcie_resources *resources, struct hailo_fw_control *command); + +int hailo_pcie_write_firmware_batch(struct device *dev, struct hailo_pcie_resources *resources, u32 stage); +bool hailo_pcie_is_firmware_loaded(struct hailo_pcie_resources *resources); +bool hailo_pcie_wait_for_firmware(struct hailo_pcie_resources *resources); + +int hailo_pcie_memory_transfer(struct hailo_pcie_resources *resources, struct hailo_memory_transfer_params *params); + +bool hailo_pcie_is_device_connected(struct hailo_pcie_resources *resources); +void hailo_pcie_write_firmware_driver_shutdown(struct hailo_pcie_resources *resources); +void hailo_pcie_write_firmware_soft_reset(struct hailo_pcie_resources *resources); +void hailo_pcie_configure_ep_registers_for_dma_transaction(struct hailo_pcie_resources *resources); +void hailo_trigger_firmware_boot(struct hailo_pcie_resources *resources, u32 stage); + +int hailo_set_device_type(struct hailo_pcie_resources *resources); + +u32 hailo_get_boot_status(struct hailo_pcie_resources *resources); + +int hailo_pcie_configure_atr_table(struct hailo_resource *bridge_config, u64 trsl_addr, u32 atr_index); +void hailo_pcie_read_atr_table(struct hailo_resource *bridge_config, struct hailo_atr_config *atr, u32 atr_index); + +u64 hailo_pcie_encode_desc_dma_address_range(dma_addr_t dma_address_start, dma_addr_t dma_address_end, u32 step, u8 channel_id); + +void hailo_pcie_soc_write_request(struct hailo_pcie_resources *resources, + const struct hailo_pcie_soc_request *request); +void hailo_pcie_soc_read_response(struct hailo_pcie_resources *resources, + struct hailo_pcie_soc_response *response); + +#ifdef __cplusplus +} +#endif + +#endif /* _HAILO_COMMON_PCIE_COMMON_H_ */ diff --git a/drivers/media/pci/hailo/common/soc_structs.h b/drivers/media/pci/hailo/common/soc_structs.h new file mode 100644 index 00000000000000..5a00c028c87f4e --- /dev/null +++ b/drivers/media/pci/hailo/common/soc_structs.h @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT +/** + * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved. + **/ +/** + * Contains definitions for pcie soc to pcie ep communication + */ + +#ifndef __HAILO_COMMON_SOC_STRUCTS__ +#define __HAILO_COMMON_SOC_STRUCTS__ + +#include <linux/types.h> + +#pragma pack(push, 1) + +struct hailo_pcie_soc_connect_request { + u16 port; +}; + +struct hailo_pcie_soc_connect_response { + u8 input_channel_index; + u8 output_channel_index; +}; + + +struct hailo_pcie_soc_close_request { + u32 channels_bitmap; +}; + +struct hailo_pcie_soc_close_response { + u8 reserved; +}; + +enum hailo_pcie_soc_control_code { + // Start from big initial value to ensure the right code was used (using 0 + // as initiale may cause confusion if the code was not set correctly). + HAILO_PCIE_SOC_CONTROL_CODE_CONNECT = 0x100, + HAILO_PCIE_SOC_CONTROL_CODE_CLOSE, + HAILO_PCIE_SOC_CONTROL_CODE_INVALID, +}; + +#define HAILO_PCIE_SOC_MAX_REQUEST_SIZE_BYTES (16) +#define HAILO_PCIE_SOC_MAX_RESPONSE_SIZE_BYTES (16) + +// IRQ to signal the PCIe that the EP was closed/released +#define PCI_EP_SOC_CLOSED_IRQ (0x00000020) +#define PCI_EP_SOC_CONNECT_RESPONSE (0x00000010) + +struct hailo_pcie_soc_request { + u32 control_code; + union { + struct hailo_pcie_soc_connect_request connect; + struct hailo_pcie_soc_close_request close; + u8 pad[HAILO_PCIE_SOC_MAX_REQUEST_SIZE_BYTES]; + }; +}; + +struct hailo_pcie_soc_response { + u32 control_code; + s32 status; + union { + struct hailo_pcie_soc_connect_response connect; + struct hailo_pcie_soc_close_response close; + u8 pad[HAILO_PCIE_SOC_MAX_RESPONSE_SIZE_BYTES]; + }; +}; + +#pragma pack(pop) + +// Compile time validate function. Don't need to call it. +static inline void __validate_soc_struct_sizes(void) +{ + BUILD_BUG_ON_MSG(sizeof(struct hailo_pcie_soc_request) != + sizeof(u32) + HAILO_PCIE_SOC_MAX_REQUEST_SIZE_BYTES, "Invalid request size"); + BUILD_BUG_ON_MSG(sizeof(struct hailo_pcie_soc_response) != + sizeof(u32) + sizeof(s32) + HAILO_PCIE_SOC_MAX_RESPONSE_SIZE_BYTES, "Invalid response size"); +} + +#endif /* __HAILO_COMMON_SOC_STRUCTS__ */ \ No newline at end of file diff --git a/drivers/media/pci/hailo/common/utils.h b/drivers/media/pci/hailo/common/utils.h new file mode 100644 index 00000000000000..0d53b65799b838 --- /dev/null +++ b/drivers/media/pci/hailo/common/utils.h @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: MIT +/** + * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved. + **/ + +#ifndef _HAILO_DRIVER_UTILS_H_ +#define _HAILO_DRIVER_UTILS_H_ + +#include <linux/bitops.h> + +#define DWORD_SIZE (4) +#define WORD_SIZE (2) +#define BYTE_SIZE (1) +#define BITS_IN_BYTE (8) + +#define hailo_clear_bit(bit, pval) { *(pval) &= ~(1 << bit); } +#define hailo_test_bit(pos,var_addr) ((*var_addr) & (1<<(pos))) + +#define READ_BITS_AT_OFFSET(amount_bits, offset, initial_value) \ + (((initial_value) >> (offset)) & ((1 << (amount_bits)) - 1)) +#define WRITE_BITS_AT_OFFSET(amount_bits, offset, initial_value, value) \ + (((initial_value) & ~(((1 << (amount_bits)) - 1) << (offset))) | \ + (((value) & ((1 << (amount_bits)) - 1)) << (offset))) + +#ifdef __cplusplus +extern "C" +{ +#endif + +static inline bool is_powerof2(size_t v) { + // bit trick + return (v & (v - 1)) == 0; +} + +static inline void hailo_set_bit(int nr, u32* addr) { + u32 mask = BIT_MASK(nr); + u32 *p = addr + BIT_WORD(nr); + + *p |= mask; +} + +static inline uint8_t ceil_log2(uint32_t n) +{ + uint8_t result = 0; + + if (n <= 1) { + return 0; + } + + while (n > 1) { + result++; + n = (n + 1) >> 1; + } + + return result; +} + +// Gets the nearest power of 2 >= value, for any value <= MAX_POWER_OF_2_VALUE. Otherwise POWER_OF_2_ERROR is returned. +#define MAX_POWER_OF_2_VALUE (0x80000000) +#define POWER_OF_2_ERROR ((uint32_t)-1) +static inline uint32_t get_nearest_powerof_2(uint32_t value) +{ + uint32_t power_of_2 = 1; + if (value > MAX_POWER_OF_2_VALUE) { + return POWER_OF_2_ERROR; + } + + while (value > power_of_2) { + power_of_2 <<= 1; + } + return power_of_2; +} + +#ifndef DIV_ROUND_UP +#define DIV_ROUND_UP(n,d) (((n) + (d) - 1) / (d)) +#endif + +#ifdef __cplusplus +} +#endif + +#endif // _HAILO_DRIVER_UTILS_H_ \ No newline at end of file diff --git a/drivers/media/pci/hailo/common/vdma_common.c b/drivers/media/pci/hailo/common/vdma_common.c new file mode 100644 index 00000000000000..56d879eed86472 --- /dev/null +++ b/drivers/media/pci/hailo/common/vdma_common.c @@ -0,0 +1,876 @@ +// SPDX-License-Identifier: MIT +/** + * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved. + **/ + +#include "vdma_common.h" + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/bug.h> +#include <linux/circ_buf.h> +#include <linux/ktime.h> +#include <linux/timekeeping.h> +#include <linux/kernel.h> +#include <linux/kconfig.h> +#include <linux/printk.h> + +#define VDMA_CHANNEL_CONTROL_START (0x1) +#define VDMA_CHANNEL_CONTROL_ABORT (0b00) +#define VDMA_CHANNEL_CONTROL_ABORT_PAUSE (0b10) +#define VDMA_CHANNEL_CONTROL_START_ABORT_PAUSE_RESUME_BITMASK (0x3) +#define VDMA_CHANNEL_CONTROL_START_ABORT_BITMASK (0x1) +#define VDMA_CHANNEL_CONTROL_MASK (0xFC) +#define VDMA_CHANNEL_CONTROL_START_RESUME (0b01) +#define VDMA_CHANNEL_CONTROL_START_PAUSE (0b11) +#define VDMA_CHANNEL_CONTROL_ABORT (0b00) +#define VDMA_CHANNEL_CONTROL_ABORT_PAUSE (0b10) +#define VDMA_CHANNEL_CONTROL_START_ABORT_PAUSE_RESUME_BITMASK (0x3) +#define VDMA_CHANNEL_DESC_DEPTH_WIDTH (4) +#define VDMA_CHANNEL_DESC_DEPTH_SHIFT (11) +#define VDMA_CHANNEL_DATA_ID_SHIFT (8) +#define VDMA_CHANNEL__MAX_CHECKS_CHANNEL_IS_IDLE (10000) +#define VDMA_CHANNEL__ADDRESS_L_OFFSET (0x0A) +#define VDMA_CHANNEL__ALIGNED_ADDRESS_L_OFFSET (0x8) +#define VDMA_CHANNEL__ADDRESS_H_OFFSET (0x0C) + +#define DESCRIPTOR_PAGE_SIZE_SHIFT (8) +#define DESCRIPTOR_DESC_CONTROL (0x2) +#define DESCRIPTOR_ADDR_L_MASK (0xFFFFFFC0) +#define DESCRIPTOR_LIST_MAX_DEPTH (16) + +#define DESCRIPTOR_DESC_STATUS_DONE_BIT (0x0) +#define DESCRIPTOR_DESC_STATUS_ERROR_BIT (0x1) +#define DESCRIPTOR_DESC_STATUS_MASK (0xFF) + +#define DESC_STATUS_REQ (1 << 0) +#define DESC_STATUS_REQ_ERR (1 << 1) +#define DESC_REQUEST_IRQ_PROCESSED (1 << 2) +#define DESC_REQUEST_IRQ_ERR (1 << 3) + +#define VDMA_CHANNEL_NUM_PROCESSED_WIDTH (16) +#define VDMA_CHANNEL_NUM_PROCESSED_MASK ((1 << VDMA_CHANNEL_NUM_PROCESSED_WIDTH) - 1) +#define VDMA_CHANNEL_NUM_ONGOING_MASK VDMA_CHANNEL_NUM_PROCESSED_MASK + +#define TIMESTAMPS_CIRC_SPACE(timestamp_list) \ + CIRC_SPACE((timestamp_list).head, (timestamp_list).tail, CHANNEL_IRQ_TIMESTAMPS_SIZE) +#define TIMESTAMPS_CIRC_CNT(timestamp_list) \ + CIRC_CNT((timestamp_list).head, (timestamp_list).tail, CHANNEL_IRQ_TIMESTAMPS_SIZE) + +#define ONGOING_TRANSFERS_CIRC_SPACE(transfers_list) \ + CIRC_SPACE((transfers_list).head, (transfers_list).tail, HAILO_VDMA_MAX_ONGOING_TRANSFERS) +#define ONGOING_TRANSFERS_CIRC_CNT(transfers_list) \ + CIRC_CNT((transfers_list).head, (transfers_list).tail, HAILO_VDMA_MAX_ONGOING_TRANSFERS) + +#ifndef for_each_sgtable_dma_sg +#define for_each_sgtable_dma_sg(sgt, sg, i) \ + for_each_sg((sgt)->sgl, sg, (sgt)->nents, i) +#endif /* for_each_sgtable_dma_sg */ + + +static int ongoing_transfer_push(struct hailo_vdma_channel *channel, + struct hailo_ongoing_transfer *ongoing_transfer) +{ + struct hailo_ongoing_transfers_list *transfers = &channel->ongoing_transfers; + if (!ONGOING_TRANSFERS_CIRC_SPACE(*transfers)) { + return -EFAULT; + } + + if (ongoing_transfer->dirty_descs_count > ARRAY_SIZE(ongoing_transfer->dirty_descs)) { + return -EFAULT; + } + + transfers->transfers[transfers->head] = *ongoing_transfer; + transfers->head = (transfers->head + 1) & HAILO_VDMA_MAX_ONGOING_TRANSFERS_MASK; + return 0; +} + +static int ongoing_transfer_pop(struct hailo_vdma_channel *channel, + struct hailo_ongoing_transfer *ongoing_transfer) +{ + struct hailo_ongoing_transfers_list *transfers = &channel->ongoing_transfers; + if (!ONGOING_TRANSFERS_CIRC_CNT(*transfers)) { + return -EFAULT; + } + + if (ongoing_transfer) { + *ongoing_transfer = transfers->transfers[transfers->tail]; + } + transfers->tail = (transfers->tail + 1) & HAILO_VDMA_MAX_ONGOING_TRANSFERS_MASK; + return 0; +} + +static void clear_dirty_desc(struct hailo_vdma_descriptors_list *desc_list, u16 desc) +{ + desc_list->desc_list[desc].PageSize_DescControl = + (u32)((desc_list->desc_page_size << DESCRIPTOR_PAGE_SIZE_SHIFT) + DESCRIPTOR_DESC_CONTROL); +} + +static void clear_dirty_descs(struct hailo_vdma_channel *channel, + struct hailo_ongoing_transfer *ongoing_transfer) +{ + u8 i = 0; + struct hailo_vdma_descriptors_list *desc_list = channel->last_desc_list; + BUG_ON(ongoing_transfer->dirty_descs_count > ARRAY_SIZE(ongoing_transfer->dirty_descs)); + for (i = 0; i < ongoing_transfer->dirty_descs_count; i++) { + clear_dirty_desc(desc_list, ongoing_transfer->dirty_descs[i]); + } +} + +static bool validate_last_desc_status(struct hailo_vdma_channel *channel, + struct hailo_ongoing_transfer *ongoing_transfer) +{ + u16 last_desc = ongoing_transfer->last_desc; + u32 last_desc_control = channel->last_desc_list->desc_list[last_desc].RemainingPageSize_Status & + DESCRIPTOR_DESC_STATUS_MASK; + if (!hailo_test_bit(DESCRIPTOR_DESC_STATUS_DONE_BIT, &last_desc_control)) { + pr_err("Expecting desc %d to be done\n", last_desc); + return false; + } + if (hailo_test_bit(DESCRIPTOR_DESC_STATUS_ERROR_BIT, &last_desc_control)) { + pr_err("Got unexpected error on desc %d\n", last_desc); + return false; + } + + return true; +} + +static void hailo_vdma_program_descriptor(struct hailo_vdma_descriptor *descriptor, u64 dma_address, size_t page_size, + u8 data_id) +{ + descriptor->PageSize_DescControl = (u32)((page_size << DESCRIPTOR_PAGE_SIZE_SHIFT) + + DESCRIPTOR_DESC_CONTROL); + descriptor->AddrL_rsvd_DataID = (u32)(((dma_address & DESCRIPTOR_ADDR_L_MASK)) | data_id); + descriptor->AddrH = (u32)(dma_address >> 32); + descriptor->RemainingPageSize_Status = 0 ; +} + +static u8 get_channel_id(u8 channel_index) +{ + return (channel_index < MAX_VDMA_CHANNELS_PER_ENGINE) ? (channel_index & 0xF) : INVALID_VDMA_CHANNEL; +} + +int hailo_vdma_program_descriptors_in_chunk( + struct hailo_vdma_hw *vdma_hw, + dma_addr_t chunk_addr, + unsigned int chunk_size, + struct hailo_vdma_descriptors_list *desc_list, + u32 desc_index, + u32 max_desc_index, + u8 channel_index, + u8 data_id) +{ + const u16 page_size = desc_list->desc_page_size; + const u32 descs_to_program = DIV_ROUND_UP(chunk_size, page_size); + const u32 starting_desc_index = desc_index; + const u32 residue_size = chunk_size % page_size; + struct hailo_vdma_descriptor *dma_desc = NULL; + u64 encoded_addr = 0; + + if (descs_to_program == 0) { + // Nothing to program + return 0; + } + + // We iterate through descriptors [desc_index, desc_index + descs_to_program) + if (desc_index + descs_to_program > max_desc_index + 1) { + return -ERANGE; + } + + encoded_addr = vdma_hw->hw_ops.encode_desc_dma_address_range(chunk_addr, chunk_addr + chunk_size, page_size, get_channel_id(channel_index)); + if (INVALID_VDMA_ADDRESS == encoded_addr) { + return -EFAULT; + } + + // Program all descriptors except the last one + for (desc_index = starting_desc_index; desc_index < starting_desc_index + descs_to_program - 1; desc_index++) { + // 'desc_index & desc_list_len_mask' is used instead of modulo; see hailo_vdma_descriptors_list documentation. + hailo_vdma_program_descriptor( + &desc_list->desc_list[desc_index & desc_list->desc_count_mask], + encoded_addr, page_size, data_id); + encoded_addr += page_size; + } + + // Handle the last descriptor outside of the loop + // 'desc_index & desc_list_len_mask' is used instead of modulo; see hailo_vdma_descriptors_list documentation. + dma_desc = &desc_list->desc_list[desc_index & desc_list->desc_count_mask]; + hailo_vdma_program_descriptor(dma_desc, encoded_addr, + (residue_size == 0) ? page_size : (u16)residue_size, data_id); + + return (int)descs_to_program; +} + +static unsigned long get_interrupts_bitmask(struct hailo_vdma_hw *vdma_hw, + enum hailo_vdma_interrupts_domain interrupts_domain, bool is_debug) +{ + unsigned long bitmask = 0; + + if (0 != (HAILO_VDMA_INTERRUPTS_DOMAIN_DEVICE & interrupts_domain)) { + bitmask |= vdma_hw->device_interrupts_bitmask; + } + if (0 != (HAILO_VDMA_INTERRUPTS_DOMAIN_HOST & interrupts_domain)) { + bitmask |= vdma_hw->host_interrupts_bitmask; + } + + if (bitmask != 0) { + bitmask |= DESC_REQUEST_IRQ_PROCESSED | DESC_REQUEST_IRQ_ERR; + if (is_debug) { + bitmask |= DESC_STATUS_REQ | DESC_STATUS_REQ_ERR; + } + } + + return bitmask; +} + +static int bind_and_program_descriptors_list( + struct hailo_vdma_hw *vdma_hw, + struct hailo_vdma_descriptors_list *desc_list, + u32 starting_desc, + struct hailo_vdma_mapped_transfer_buffer *buffer, + u8 channel_index, + enum hailo_vdma_interrupts_domain last_desc_interrupts, + bool is_debug) +{ + int desc_programmed = 0; + int descs_programmed_in_chunk = 0; + u32 max_desc_index = 0; + u32 chunk_size = 0; + struct scatterlist *sg_entry = NULL; + unsigned int i = 0; + size_t buffer_current_offset = 0; + dma_addr_t chunk_start_addr = 0; + u32 program_size = buffer->size; + + if (starting_desc >= desc_list->desc_count) { + return -EFAULT; + } + + if (buffer->offset % desc_list->desc_page_size != 0) { + return -EFAULT; + } + + // On circular buffer, allow programming desc_count descriptors (starting + // from starting_desc). On non circular, don't allow is to pass desc_count + max_desc_index = desc_list->is_circular ? + starting_desc + desc_list->desc_count - 1 : + desc_list->desc_count - 1; + for_each_sgtable_dma_sg(buffer->sg_table, sg_entry, i) { + // Skip sg entries until we reach the right buffer offset. offset can be in the middle of an sg entry. + if (buffer_current_offset + sg_dma_len(sg_entry) < buffer->offset) { + buffer_current_offset += sg_dma_len(sg_entry); + continue; + } + chunk_start_addr = (buffer_current_offset < buffer->offset) ? + sg_dma_address(sg_entry) + (buffer->offset - buffer_current_offset) : + sg_dma_address(sg_entry); + chunk_size = (buffer_current_offset < buffer->offset) ? + (u32)(sg_dma_len(sg_entry) - (buffer->offset - buffer_current_offset)) : + (u32)(sg_dma_len(sg_entry)); + chunk_size = min((u32)program_size, chunk_size); + + descs_programmed_in_chunk = hailo_vdma_program_descriptors_in_chunk(vdma_hw, chunk_start_addr, chunk_size, desc_list, + starting_desc, max_desc_index, channel_index, vdma_hw->ddr_data_id); + if (descs_programmed_in_chunk < 0) { + return descs_programmed_in_chunk; + } + + desc_programmed += descs_programmed_in_chunk; + starting_desc = starting_desc + descs_programmed_in_chunk; + program_size -= chunk_size; + buffer_current_offset += sg_dma_len(sg_entry); + } + + if (program_size != 0) { + // We didn't program all the buffer. + return -EFAULT; + } + + desc_list->desc_list[(starting_desc - 1) % desc_list->desc_count].PageSize_DescControl |= + get_interrupts_bitmask(vdma_hw, last_desc_interrupts, is_debug); + + return desc_programmed; +} + +static int program_last_desc( + struct hailo_vdma_hw *vdma_hw, + struct hailo_vdma_descriptors_list *desc_list, + u32 starting_desc, + struct hailo_vdma_mapped_transfer_buffer *transfer_buffer, + enum hailo_vdma_interrupts_domain last_desc_interrupts, + bool is_debug) +{ + u8 control = (u8)(DESCRIPTOR_DESC_CONTROL | get_interrupts_bitmask(vdma_hw, last_desc_interrupts, is_debug)); + u32 total_descs = DIV_ROUND_UP(transfer_buffer->size, desc_list->desc_page_size); + u32 last_desc = (starting_desc + total_descs - 1) % desc_list->desc_count; + u32 last_desc_size = transfer_buffer->size - (total_descs - 1) * desc_list->desc_page_size; + + // Configure only last descriptor with residue size + desc_list->desc_list[last_desc].PageSize_DescControl = (u32) + ((last_desc_size << DESCRIPTOR_PAGE_SIZE_SHIFT) + control); + return (int)total_descs; +} + +int hailo_vdma_program_descriptors_list( + struct hailo_vdma_hw *vdma_hw, + struct hailo_vdma_descriptors_list *desc_list, + u32 starting_desc, + struct hailo_vdma_mapped_transfer_buffer *buffer, + bool should_bind, + u8 channel_index, + enum hailo_vdma_interrupts_domain last_desc_interrupts, + bool is_debug) +{ + return should_bind ? + bind_and_program_descriptors_list(vdma_hw, desc_list, starting_desc, + buffer, channel_index, last_desc_interrupts, is_debug) : + program_last_desc(vdma_hw, desc_list, starting_desc, buffer, + last_desc_interrupts, is_debug); +} + + +static bool channel_control_reg_is_active(u8 control) +{ + return (control & VDMA_CHANNEL_CONTROL_START_ABORT_BITMASK) == VDMA_CHANNEL_CONTROL_START; +} + +static int validate_channel_state(struct hailo_vdma_channel *channel) +{ + u32 host_regs_value = ioread32(channel->host_regs); + const u8 control = READ_BITS_AT_OFFSET(BYTE_SIZE * BITS_IN_BYTE, CHANNEL_CONTROL_OFFSET * BITS_IN_BYTE, host_regs_value); + const u16 hw_num_avail = READ_BITS_AT_OFFSET(WORD_SIZE * BITS_IN_BYTE, CHANNEL_NUM_AVAIL_OFFSET * BITS_IN_BYTE, host_regs_value); + + if (!channel_control_reg_is_active(control)) { + return -ECONNRESET; + } + + if (hw_num_avail != channel->state.num_avail) { + pr_err("Channel %d hw state out of sync. num available is %d, expected %d\n", + channel->index, hw_num_avail, channel->state.num_avail); + return -EFAULT; + } + + return 0; +} + +void hailo_vdma_set_num_avail(u8 __iomem *regs, u16 num_avail) +{ + u32 regs_val = ioread32(regs); + iowrite32(WRITE_BITS_AT_OFFSET(WORD_SIZE * BITS_IN_BYTE, CHANNEL_NUM_AVAIL_OFFSET * BITS_IN_BYTE, regs_val, num_avail), + regs); +} + +u16 hailo_vdma_get_num_proc(u8 __iomem *regs) +{ + return READ_BITS_AT_OFFSET(WORD_SIZE * BITS_IN_BYTE, 0, ioread32(regs + CHANNEL_NUM_PROC_OFFSET)); +} + +int hailo_vdma_launch_transfer( + struct hailo_vdma_hw *vdma_hw, + struct hailo_vdma_channel *channel, + struct hailo_vdma_descriptors_list *desc_list, + u32 starting_desc, + u8 buffers_count, + struct hailo_vdma_mapped_transfer_buffer *buffers, + bool should_bind, + enum hailo_vdma_interrupts_domain first_interrupts_domain, + enum hailo_vdma_interrupts_domain last_desc_interrupts, + bool is_debug) +{ + int ret = -EFAULT; + u32 total_descs = 0; + u32 first_desc = starting_desc; + u32 last_desc = U32_MAX; + u16 new_num_avail = 0; + struct hailo_ongoing_transfer ongoing_transfer = {0}; + u8 i = 0; + + channel->state.desc_count_mask = (desc_list->desc_count - 1); + + if (NULL == channel->last_desc_list) { + // First transfer on this active channel, store desc list. + channel->last_desc_list = desc_list; + } else if (desc_list != channel->last_desc_list) { + // Shouldn't happen, desc list may change only after channel deactivation. + pr_err("Inconsistent desc list given to channel %d\n", channel->index); + return -EINVAL; + } + + ret = validate_channel_state(channel); + if (ret < 0) { + return ret; + } + + if (channel->state.num_avail != (u16)starting_desc) { + pr_err("Channel %d state out of sync. num available is %d, expected %d\n", + channel->index, channel->state.num_avail, (u16)starting_desc); + return -EFAULT; + } + + if (buffers_count > HAILO_MAX_BUFFERS_PER_SINGLE_TRANSFER) { + pr_err("Too many buffers %u for single transfer\n", buffers_count); + return -EINVAL; + } + + BUILD_BUG_ON_MSG((HAILO_MAX_BUFFERS_PER_SINGLE_TRANSFER + 1) != ARRAY_SIZE(ongoing_transfer.dirty_descs), + "Unexpected amount of dirty descriptors"); + ongoing_transfer.dirty_descs_count = buffers_count + 1; + ongoing_transfer.dirty_descs[0] = (u16)starting_desc; + + for (i = 0; i < buffers_count; i++) { + ret = hailo_vdma_program_descriptors_list(vdma_hw, desc_list, + starting_desc, &buffers[i], should_bind, channel->index, + (i == (buffers_count - 1) ? last_desc_interrupts : HAILO_VDMA_INTERRUPTS_DOMAIN_NONE), + is_debug); + + total_descs += ret; + last_desc = (starting_desc + ret - 1) % desc_list->desc_count; + starting_desc = (starting_desc + ret) % desc_list->desc_count; + + ongoing_transfer.dirty_descs[i+1] = (u16)last_desc; + ongoing_transfer.buffers[i] = buffers[i]; + } + ongoing_transfer.buffers_count = buffers_count; + + desc_list->desc_list[first_desc].PageSize_DescControl |= + get_interrupts_bitmask(vdma_hw, first_interrupts_domain, is_debug); + + ongoing_transfer.last_desc = (u16)last_desc; + ongoing_transfer.is_debug = is_debug; + ret = ongoing_transfer_push(channel, &ongoing_transfer); + if (ret < 0) { + pr_err("Failed push ongoing transfer to channel %d\n", channel->index); + return ret; + } + + new_num_avail = (u16)((last_desc + 1) % desc_list->desc_count); + channel->state.num_avail = new_num_avail; + hailo_vdma_set_num_avail(channel->host_regs, new_num_avail); + + return (int)total_descs; +} + +static void hailo_vdma_push_timestamp(struct hailo_vdma_channel *channel) +{ + struct hailo_channel_interrupt_timestamp_list *timestamp_list = &channel->timestamp_list; + const u16 num_proc = hailo_vdma_get_num_proc(channel->host_regs); + if (TIMESTAMPS_CIRC_SPACE(*timestamp_list) != 0) { + timestamp_list->timestamps[timestamp_list->head].timestamp_ns = ktime_get_ns(); + timestamp_list->timestamps[timestamp_list->head].desc_num_processed = num_proc; + timestamp_list->head = (timestamp_list->head + 1) & CHANNEL_IRQ_TIMESTAMPS_SIZE_MASK; + } +} + +// Returns false if there are no items +static bool hailo_vdma_pop_timestamp(struct hailo_channel_interrupt_timestamp_list *timestamp_list, + struct hailo_channel_interrupt_timestamp *out_timestamp) +{ + if (0 == TIMESTAMPS_CIRC_CNT(*timestamp_list)) { + return false; + } + + *out_timestamp = timestamp_list->timestamps[timestamp_list->tail]; + timestamp_list->tail = (timestamp_list->tail+1) & CHANNEL_IRQ_TIMESTAMPS_SIZE_MASK; + return true; +} + +static void hailo_vdma_pop_timestamps_to_response(struct hailo_vdma_channel *channel, + struct hailo_vdma_interrupts_read_timestamp_params *result) +{ + const u32 max_timestamps = ARRAY_SIZE(result->timestamps); + u32 i = 0; + + while (hailo_vdma_pop_timestamp(&channel->timestamp_list, &result->timestamps[i]) && + (i < max_timestamps)) { + // Although the hw_num_processed should be a number between 0 and + // desc_count-1, if desc_count < 0x10000 (the maximum desc size), + // the actual hw_num_processed is a number between 1 and desc_count. + // Therefore the value can be desc_count, in this case we change it to + // zero. + result->timestamps[i].desc_num_processed = result->timestamps[i].desc_num_processed & + channel->state.desc_count_mask; + i++; + } + + result->timestamps_count = i; +} + +static void channel_state_init(struct hailo_vdma_channel_state *state) +{ + state->num_avail = state->num_proc = 0; + + // Special value used when the channel is not activate. + state->desc_count_mask = U32_MAX; +} + +static u8 __iomem *get_channel_regs(u8 __iomem *regs_base, u8 channel_index, bool is_host_side, u32 src_channels_bitmask) +{ + // Check if getting host side regs or device side + u8 __iomem *channel_regs_base = regs_base + CHANNEL_BASE_OFFSET(channel_index); + if (is_host_side) { + return hailo_test_bit(channel_index, &src_channels_bitmask) ? channel_regs_base : + (channel_regs_base + CHANNEL_DEST_REGS_OFFSET); + } else { + return hailo_test_bit(channel_index, &src_channels_bitmask) ? (channel_regs_base + CHANNEL_DEST_REGS_OFFSET) : + channel_regs_base; + } +} + +void hailo_vdma_engine_init(struct hailo_vdma_engine *engine, u8 engine_index, + const struct hailo_resource *channel_registers, u32 src_channels_bitmask) +{ + u8 channel_index = 0; + struct hailo_vdma_channel *channel; + + engine->index = engine_index; + engine->enabled_channels = 0x0; + engine->interrupted_channels = 0x0; + + for_each_vdma_channel(engine, channel, channel_index) { + u8 __iomem *regs_base = (u8 __iomem *)channel_registers->address; + channel->host_regs = get_channel_regs(regs_base, channel_index, true, src_channels_bitmask); + channel->device_regs = get_channel_regs(regs_base, channel_index, false, src_channels_bitmask); + channel->index = channel_index; + channel->timestamp_measure_enabled = false; + + channel_state_init(&channel->state); + channel->last_desc_list = NULL; + + channel->ongoing_transfers.head = 0; + channel->ongoing_transfers.tail = 0; + } +} + +/** + * Enables the given channels bitmap in the given engine. Allows launching transfer + * and reading interrupts from the channels. + * + * @param engine - dma engine. + * @param bitmap - channels bitmap to enable. + * @param measure_timestamp - if set, allow interrupts timestamp measure. + */ +void hailo_vdma_engine_enable_channels(struct hailo_vdma_engine *engine, u32 bitmap, + bool measure_timestamp) +{ + struct hailo_vdma_channel *channel = NULL; + u8 channel_index = 0; + + for_each_vdma_channel(engine, channel, channel_index) { + if (hailo_test_bit(channel_index, &bitmap)) { + channel->timestamp_measure_enabled = measure_timestamp; + channel->timestamp_list.head = channel->timestamp_list.tail = 0; + } + } + + engine->enabled_channels |= bitmap; +} + +/** + * Disables the given channels bitmap in the given engine. + * + * @param engine - dma engine. + * @param bitmap - channels bitmap to enable. + * @param measure_timestamp - if set, allow interrupts timestamp measure. + */ +void hailo_vdma_engine_disable_channels(struct hailo_vdma_engine *engine, u32 bitmap) +{ + struct hailo_vdma_channel *channel = NULL; + u8 channel_index = 0; + + engine->enabled_channels &= ~bitmap; + + for_each_vdma_channel(engine, channel, channel_index) { + if (hailo_test_bit(channel_index, &bitmap)) { + channel_state_init(&channel->state); + + while (ONGOING_TRANSFERS_CIRC_CNT(channel->ongoing_transfers) > 0) { + struct hailo_ongoing_transfer transfer; + ongoing_transfer_pop(channel, &transfer); + + if (channel->last_desc_list == NULL) { + pr_err("Channel %d has ongoing transfers but no desc list\n", channel->index); + continue; + } + + clear_dirty_descs(channel, &transfer); + } + + channel->last_desc_list = NULL; + } + } +} + +void hailo_vdma_engine_push_timestamps(struct hailo_vdma_engine *engine, u32 bitmap) +{ + struct hailo_vdma_channel *channel = NULL; + u8 channel_index = 0; + + for_each_vdma_channel(engine, channel, channel_index) { + if (unlikely(hailo_test_bit(channel_index, &bitmap) && + channel->timestamp_measure_enabled)) { + hailo_vdma_push_timestamp(channel); + } + } +} + +int hailo_vdma_engine_read_timestamps(struct hailo_vdma_engine *engine, + struct hailo_vdma_interrupts_read_timestamp_params *params) +{ + struct hailo_vdma_channel *channel = NULL; + + if (params->channel_index >= MAX_VDMA_CHANNELS_PER_ENGINE) { + return -EINVAL; + } + + channel = &engine->channels[params->channel_index]; + hailo_vdma_pop_timestamps_to_response(channel, params); + return 0; +} + +void hailo_vdma_engine_clear_channel_interrupts(struct hailo_vdma_engine *engine, u32 bitmap) +{ + engine->interrupted_channels &= ~bitmap; +} + +void hailo_vdma_engine_set_channel_interrupts(struct hailo_vdma_engine *engine, u32 bitmap) +{ + engine->interrupted_channels |= bitmap; +} + +static void fill_channel_irq_data(struct hailo_vdma_interrupts_channel_data *irq_data, + struct hailo_vdma_engine *engine, struct hailo_vdma_channel *channel, u8 transfers_completed, + bool validation_success) +{ + u8 host_control = READ_BITS_AT_OFFSET(BYTE_SIZE * BITS_IN_BYTE, CHANNEL_CONTROL_OFFSET * BITS_IN_BYTE, ioread32(channel->host_regs)); + u8 device_control = READ_BITS_AT_OFFSET(BYTE_SIZE * BITS_IN_BYTE, CHANNEL_CONTROL_OFFSET * BITS_IN_BYTE, ioread32(channel->device_regs)); + + irq_data->engine_index = engine->index; + irq_data->channel_index = channel->index; + + irq_data->is_active = channel_control_reg_is_active(host_control) && + channel_control_reg_is_active(device_control); + + irq_data->transfers_completed = transfers_completed; + irq_data->host_error = READ_BITS_AT_OFFSET(BYTE_SIZE * BITS_IN_BYTE, 0, ioread32(channel->host_regs + CHANNEL_ERROR_OFFSET)); + irq_data->device_error = READ_BITS_AT_OFFSET(BYTE_SIZE * BITS_IN_BYTE, 0, ioread32(channel->device_regs + CHANNEL_ERROR_OFFSET)); + irq_data->validation_success = validation_success; +} + +static bool is_desc_between(u16 begin, u16 end, u16 desc) +{ + if (begin == end) { + // There is nothing between + return false; + } + if (begin < end) { + // desc needs to be in [begin, end) + return (begin <= desc) && (desc < end); + } + else { + // desc needs to be in [0, end) or [begin, m_descs.size()-1] + return (desc < end) || (begin <= desc); + } +} + +static bool is_transfer_complete(struct hailo_vdma_channel *channel, + struct hailo_ongoing_transfer *transfer, u16 hw_num_proc) +{ + if (channel->state.num_avail == hw_num_proc) { + return true; + } + + return is_desc_between(channel->state.num_proc, hw_num_proc, transfer->last_desc); +} + +int hailo_vdma_engine_fill_irq_data(struct hailo_vdma_interrupts_wait_params *irq_data, + struct hailo_vdma_engine *engine, u32 irq_channels_bitmap, + transfer_done_cb_t transfer_done, void *transfer_done_opaque) +{ + struct hailo_vdma_channel *channel = NULL; + u8 channel_index = 0; + bool validation_success = true; + + for_each_vdma_channel(engine, channel, channel_index) { + u8 transfers_completed = 0; + u16 hw_num_proc = U16_MAX; + + BUILD_BUG_ON_MSG(HAILO_VDMA_MAX_ONGOING_TRANSFERS >= U8_MAX, + "HAILO_VDMA_MAX_ONGOING_TRANSFERS must be less than U8_MAX to use transfers_completed as u8"); + + if (!hailo_test_bit(channel->index, &irq_channels_bitmap)) { + continue; + } + + if (channel->last_desc_list == NULL) { + // Channel not active or no transfer, skipping. + continue; + } + + if (irq_data->channels_count >= ARRAY_SIZE(irq_data->irq_data)) { + return -EINVAL; + } + + // Although the hw_num_processed should be a number between 0 and + // desc_count-1, if desc_count < 0x10000 (the maximum desc size), + // the actual hw_num_processed is a number between 1 and desc_count. + // Therefore the value can be desc_count, in this case we change it to + // zero. + hw_num_proc = hailo_vdma_get_num_proc(channel->host_regs) & channel->state.desc_count_mask; + + while (ONGOING_TRANSFERS_CIRC_CNT(channel->ongoing_transfers) > 0) { + struct hailo_ongoing_transfer *cur_transfer = + &channel->ongoing_transfers.transfers[channel->ongoing_transfers.tail]; + if (!is_transfer_complete(channel, cur_transfer, hw_num_proc)) { + break; + } + + if (cur_transfer->is_debug && + !validate_last_desc_status(channel, cur_transfer)) { + validation_success = false; + } + + clear_dirty_descs(channel, cur_transfer); + transfer_done(cur_transfer, transfer_done_opaque); + channel->state.num_proc = (u16)((cur_transfer->last_desc + 1) & channel->state.desc_count_mask); + + ongoing_transfer_pop(channel, NULL); + transfers_completed++; + } + + fill_channel_irq_data(&irq_data->irq_data[irq_data->channels_count], + engine, channel, transfers_completed, validation_success); + irq_data->channels_count++; + } + + return 0; +} + +// For all these functions - best way to optimize might be to not call the function when need to pause and then abort, +// Rather read value once and maybe save +// This function reads and writes the register - should try to make more optimized in future +static void start_vdma_control_register(u8 __iomem *host_regs) +{ + u32 host_regs_value = ioread32(host_regs); + iowrite32(WRITE_BITS_AT_OFFSET(BYTE_SIZE * BITS_IN_BYTE, CHANNEL_CONTROL_OFFSET * BITS_IN_BYTE, host_regs_value, + VDMA_CHANNEL_CONTROL_START_RESUME), host_regs); +} + +static void hailo_vdma_channel_pause(u8 __iomem *host_regs) +{ + u32 host_regs_value = ioread32(host_regs); + iowrite32(WRITE_BITS_AT_OFFSET(BYTE_SIZE * BITS_IN_BYTE, CHANNEL_CONTROL_OFFSET * BITS_IN_BYTE, host_regs_value, + VDMA_CHANNEL_CONTROL_START_PAUSE), host_regs); +} + +// This function reads and writes the register - should try to make more optimized in future +static void hailo_vdma_channel_abort(u8 __iomem *host_regs) +{ + u32 host_regs_value = ioread32(host_regs); + iowrite32(WRITE_BITS_AT_OFFSET(BYTE_SIZE * BITS_IN_BYTE, CHANNEL_CONTROL_OFFSET * BITS_IN_BYTE, host_regs_value, + VDMA_CHANNEL_CONTROL_ABORT), host_regs); +} + +int hailo_vdma_start_channel(u8 __iomem *regs, uint64_t desc_dma_address, uint32_t desc_count, + uint8_t data_id) +{ + u16 dma_address_l = 0; + u32 dma_address_h = 0; + u32 desc_depth_data_id = 0; + u8 desc_depth = ceil_log2(desc_count); + + if (((desc_dma_address & 0xFFFF) != 0) || + (desc_depth > DESCRIPTOR_LIST_MAX_DEPTH)) { + return -EINVAL; + } + + // According to spec, depth 16 is equivalent to depth 0. + if (DESCRIPTOR_LIST_MAX_DEPTH == desc_depth) { + desc_depth = 0; + } + + // Stop old channel state + hailo_vdma_stop_channel(regs); + + // Configure address, depth and id + dma_address_l = (uint16_t)((desc_dma_address >> 16) & 0xFFFF); + iowrite32(WRITE_BITS_AT_OFFSET(WORD_SIZE * BITS_IN_BYTE, (VDMA_CHANNEL__ADDRESS_L_OFFSET - + VDMA_CHANNEL__ALIGNED_ADDRESS_L_OFFSET) * BITS_IN_BYTE, ioread32(regs + + VDMA_CHANNEL__ALIGNED_ADDRESS_L_OFFSET), dma_address_l), regs + VDMA_CHANNEL__ALIGNED_ADDRESS_L_OFFSET); + + dma_address_h = (uint32_t)(desc_dma_address >> 32); + iowrite32(dma_address_h, regs + VDMA_CHANNEL__ADDRESS_H_OFFSET); + + desc_depth_data_id = (uint32_t)(desc_depth << VDMA_CHANNEL_DESC_DEPTH_SHIFT) | + (data_id << VDMA_CHANNEL_DATA_ID_SHIFT); + iowrite32(desc_depth_data_id, regs); + + start_vdma_control_register(regs); + + return 0; +} + +static bool hailo_vdma_channel_is_idle(u8 __iomem *host_regs, size_t host_side_max_desc_count) +{ + // Num processed and ongoing are next to each other in the memory. + // Reading them both in order to save BAR reads. + u32 host_side_num_processed_ongoing = ioread32(host_regs + CHANNEL_NUM_PROC_OFFSET); + u16 host_side_num_processed = (host_side_num_processed_ongoing & VDMA_CHANNEL_NUM_PROCESSED_MASK); + u16 host_side_num_ongoing = (host_side_num_processed_ongoing >> VDMA_CHANNEL_NUM_PROCESSED_WIDTH) & + VDMA_CHANNEL_NUM_ONGOING_MASK; + + if ((host_side_num_processed % host_side_max_desc_count) == (host_side_num_ongoing % host_side_max_desc_count)) { + return true; + } + + return false; +} + +static int hailo_vdma_wait_until_channel_idle(u8 __iomem *host_regs) +{ + bool is_idle = false; + uint32_t check_counter = 0; + + u8 depth = (uint8_t)(READ_BITS_AT_OFFSET(VDMA_CHANNEL_DESC_DEPTH_WIDTH, VDMA_CHANNEL_DESC_DEPTH_SHIFT, + ioread32(host_regs))); + size_t host_side_max_desc_count = (size_t)(1 << depth); + + for (check_counter = 0; check_counter < VDMA_CHANNEL__MAX_CHECKS_CHANNEL_IS_IDLE; check_counter++) { + is_idle = hailo_vdma_channel_is_idle(host_regs, host_side_max_desc_count); + if (is_idle) { + return 0; + } + } + + return -ETIMEDOUT; +} + +void hailo_vdma_stop_channel(u8 __iomem *regs) +{ + int err = 0; + u8 host_side_channel_regs = READ_BITS_AT_OFFSET(BYTE_SIZE * BITS_IN_BYTE, CHANNEL_CONTROL_OFFSET * BITS_IN_BYTE, ioread32(regs)); + + if ((host_side_channel_regs & VDMA_CHANNEL_CONTROL_START_ABORT_PAUSE_RESUME_BITMASK) == VDMA_CHANNEL_CONTROL_ABORT_PAUSE) { + // The channel is aborted (we set the channel to VDMA_CHANNEL_CONTROL_ABORT_PAUSE at the end of this function) + return; + } + + // Pause the channel + // The channel is paused to allow for "all transfers from fetched descriptors..." to be "...completed" + // (from PLDA PCIe refernce manual, "9.2.5 Starting a Channel and Transferring Data") + hailo_vdma_channel_pause(regs); + + // Even if channel is stuck and not idle, force abort and return error in the end + err = hailo_vdma_wait_until_channel_idle(regs); + // Success oriented - if error occured print error but still abort channel + if (err < 0) { + pr_err("Timeout occured while waiting for channel to become idle\n"); + } + + // Abort the channel (even of hailo_vdma_wait_until_channel_idle function fails) + hailo_vdma_channel_abort(regs); +} + +bool hailo_check_channel_index(u8 channel_index, u32 src_channels_bitmask, bool is_input_channel) +{ + return is_input_channel ? hailo_test_bit(channel_index, &src_channels_bitmask) : + (!hailo_test_bit(channel_index, &src_channels_bitmask)); +} \ No newline at end of file diff --git a/drivers/media/pci/hailo/common/vdma_common.h b/drivers/media/pci/hailo/common/vdma_common.h new file mode 100644 index 00000000000000..9176543b085c36 --- /dev/null +++ b/drivers/media/pci/hailo/common/vdma_common.h @@ -0,0 +1,284 @@ +// SPDX-License-Identifier: MIT +/** + * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved. + **/ + +#ifndef _HAILO_COMMON_VDMA_COMMON_H_ +#define _HAILO_COMMON_VDMA_COMMON_H_ + +#include "hailo_resource.h" +#include "utils.h" + +#include <linux/types.h> +#include <linux/scatterlist.h> +#include <linux/io.h> + +#define VDMA_DESCRIPTOR_LIST_ALIGN (1 << 16) +#define INVALID_VDMA_ADDRESS (0) + +#define CHANNEL_BASE_OFFSET(channel_index) ((channel_index) << 5) + +#define CHANNEL_CONTROL_OFFSET (0x0) +#define CHANNEL_DEPTH_ID_OFFSET (0x1) +#define CHANNEL_NUM_AVAIL_OFFSET (0x2) +#define CHANNEL_NUM_PROC_OFFSET (0x4) +#define CHANNEL_ERROR_OFFSET (0x8) +#define CHANNEL_DEST_REGS_OFFSET (0x10) + +#ifdef __cplusplus +extern "C" +{ +#endif + +struct hailo_vdma_descriptor { + u32 PageSize_DescControl; + u32 AddrL_rsvd_DataID; + u32 AddrH; + u32 RemainingPageSize_Status; +}; + +struct hailo_vdma_descriptors_list { + struct hailo_vdma_descriptor *desc_list; + // Must be power of 2 if is_circular is set. + u32 desc_count; + // The nearest power of 2 to desc_count (including desc_count), minus 1. + // * If the list is circular, then 'index & desc_count_mask' can be used instead of modulo. + // * Otherwise, we can't wrap around the list anyway. However, for any index < desc_count, 'index & desc_count_mask' + // will return the same value. + u32 desc_count_mask; + u16 desc_page_size; + bool is_circular; +}; + +struct hailo_channel_interrupt_timestamp_list { + int head; + int tail; + struct hailo_channel_interrupt_timestamp timestamps[CHANNEL_IRQ_TIMESTAMPS_SIZE]; +}; + + +// For each buffers in transfer, the last descriptor will be programmed with +// the residue size. In addition, if configured, the first descriptor (in +// all transfer) may be programmed with interrupts. +#define MAX_DIRTY_DESCRIPTORS_PER_TRANSFER \ + (HAILO_MAX_BUFFERS_PER_SINGLE_TRANSFER + 1) + +struct hailo_vdma_mapped_transfer_buffer { + struct sg_table *sg_table; + u32 size; + u32 offset; + void *opaque; // Drivers can set any opaque data here. +}; + +struct hailo_ongoing_transfer { + uint16_t last_desc; + + u8 buffers_count; + struct hailo_vdma_mapped_transfer_buffer buffers[HAILO_MAX_BUFFERS_PER_SINGLE_TRANSFER]; + + // Contains all descriptors that were programmed with non-default values + // for the transfer (by non-default we mean - different size or different + // interrupts domain). + uint8_t dirty_descs_count; + uint16_t dirty_descs[MAX_DIRTY_DESCRIPTORS_PER_TRANSFER]; + + // If set, validate descriptors status on transfer completion. + bool is_debug; +}; + +struct hailo_ongoing_transfers_list { + unsigned long head; + unsigned long tail; + struct hailo_ongoing_transfer transfers[HAILO_VDMA_MAX_ONGOING_TRANSFERS]; +}; + +struct hailo_vdma_channel_state { + // vdma channel counters. num_avail should be synchronized with the hw + // num_avail value. num_proc is the last num proc updated when the user + // reads interrupts. + u16 num_avail; + u16 num_proc; + + // Mask of the num-avail/num-proc counters. + u32 desc_count_mask; +}; + +struct hailo_vdma_channel { + u8 index; + + u8 __iomem *host_regs; + u8 __iomem *device_regs; + + // Last descriptors list attached to the channel. When it changes, + // assumes that the channel got reset. + struct hailo_vdma_descriptors_list *last_desc_list; + + struct hailo_vdma_channel_state state; + struct hailo_ongoing_transfers_list ongoing_transfers; + + bool timestamp_measure_enabled; + struct hailo_channel_interrupt_timestamp_list timestamp_list; +}; + +struct hailo_vdma_engine { + u8 index; + u32 enabled_channels; + u32 interrupted_channels; + struct hailo_vdma_channel channels[MAX_VDMA_CHANNELS_PER_ENGINE]; +}; + +struct hailo_vdma_hw_ops { + // Accepts start, end and step of an address range (of type dma_addr_t). + // Returns the encoded base address or INVALID_VDMA_ADDRESS if the range/step is invalid. + // All addresses in the range of [returned_addr, returned_addr + step, returned_addr + 2*step, ..., dma_address_end) are valid. + u64 (*encode_desc_dma_address_range)(dma_addr_t dma_address_start, dma_addr_t dma_address_end, u32 step, u8 channel_id); +}; + +struct hailo_vdma_hw { + struct hailo_vdma_hw_ops hw_ops; + + // The data_id code of ddr addresses. + u8 ddr_data_id; + + // Bitmask needed to set on each descriptor to enable interrupts (either host/device). + unsigned long host_interrupts_bitmask; + unsigned long device_interrupts_bitmask; + + // Bitmask for each vdma hw, which channels are src side by index (on pcie/dram - 0x0000FFFF, pci ep - 0xFFFF0000) + u32 src_channels_bitmask; +}; + +#define _for_each_element_array(array, size, element, index) \ + for (index = 0, element = &array[index]; index < size; index++, element = &array[index]) + +#define for_each_vdma_channel(engine, channel, channel_index) \ + _for_each_element_array((engine)->channels, MAX_VDMA_CHANNELS_PER_ENGINE, \ + channel, channel_index) + +/** + * Program the given descriptors list to map the given buffer. + * + * @param vdma_hw vdma hw object + * @param desc_list descriptors list object to program + * @param starting_desc index of the first descriptor to program. If the list + * is circular, this function may wrap around the list. + * @param buffer buffer to program to the descriptors list. + * @param should_bind If false, assumes the buffer was already bound to the + * desc list. Used for optimization. + * @param channel_index channel index of the channel attached. + * @param last_desc_interrupts - interrupts settings on last descriptor. + * @param is_debug program descriptors for debug run. + * + * @return On success - the amount of descriptors programmed, negative value on error. + */ +int hailo_vdma_program_descriptors_list( + struct hailo_vdma_hw *vdma_hw, + struct hailo_vdma_descriptors_list *desc_list, + u32 starting_desc, + struct hailo_vdma_mapped_transfer_buffer *buffer, + bool should_bind, + u8 channel_index, + enum hailo_vdma_interrupts_domain last_desc_interrupts, + bool is_debug); + +int hailo_vdma_program_descriptors_in_chunk( + struct hailo_vdma_hw *vdma_hw, + dma_addr_t chunk_addr, + unsigned int chunk_size, + struct hailo_vdma_descriptors_list *desc_list, + u32 desc_index, + u32 max_desc_index, + u8 channel_index, + u8 data_id); + +void hailo_vdma_set_num_avail(u8 __iomem *regs, u16 num_avail); + +u16 hailo_vdma_get_num_proc(u8 __iomem *regs); + +/** + * Launch a transfer on some vdma channel. Includes: + * 1. Binding the transfer buffers to the descriptors list. + * 2. Program the descriptors list. + * 3. Increase num available + * + * @param vdma_hw vdma hw object + * @param channel vdma channel object. + * @param desc_list descriptors list object to program. + * @param starting_desc index of the first descriptor to program. + * @param buffers_count amount of transfer mapped buffers to program. + * @param buffers array of buffers to program to the descriptors list. + * @param should_bind whether to bind the buffer to the descriptors list. + * @param first_interrupts_domain - interrupts settings on first descriptor. + * @param last_desc_interrupts - interrupts settings on last descriptor. + * @param is_debug program descriptors for debug run, adds some overhead (for + * example, hw will write desc complete status). + * + * @return On success - the amount of descriptors programmed, negative value on error. + */ +int hailo_vdma_launch_transfer( + struct hailo_vdma_hw *vdma_hw, + struct hailo_vdma_channel *channel, + struct hailo_vdma_descriptors_list *desc_list, + u32 starting_desc, + u8 buffers_count, + struct hailo_vdma_mapped_transfer_buffer *buffers, + bool should_bind, + enum hailo_vdma_interrupts_domain first_interrupts_domain, + enum hailo_vdma_interrupts_domain last_desc_interrupts, + bool is_debug); + +void hailo_vdma_engine_init(struct hailo_vdma_engine *engine, u8 engine_index, + const struct hailo_resource *channel_registers, u32 src_channels_bitmask); + +void hailo_vdma_engine_enable_channels(struct hailo_vdma_engine *engine, u32 bitmap, + bool measure_timestamp); + +void hailo_vdma_engine_disable_channels(struct hailo_vdma_engine *engine, u32 bitmap); + +void hailo_vdma_engine_push_timestamps(struct hailo_vdma_engine *engine, u32 bitmap); +int hailo_vdma_engine_read_timestamps(struct hailo_vdma_engine *engine, + struct hailo_vdma_interrupts_read_timestamp_params *params); + +static inline bool hailo_vdma_engine_got_interrupt(struct hailo_vdma_engine *engine, + u32 channels_bitmap) +{ + // Reading interrupts without lock is ok (needed only for writes) + const bool any_interrupt = (0 != (channels_bitmap & engine->interrupted_channels)); + const bool any_disabled = (channels_bitmap != (channels_bitmap & engine->enabled_channels)); + return (any_disabled || any_interrupt); +} + +// Set/Clear/Read channels interrupts, must called under some lock (driver specific) +void hailo_vdma_engine_clear_channel_interrupts(struct hailo_vdma_engine *engine, u32 bitmap); +void hailo_vdma_engine_set_channel_interrupts(struct hailo_vdma_engine *engine, u32 bitmap); + +static inline u32 hailo_vdma_engine_read_interrupts(struct hailo_vdma_engine *engine, + u32 requested_bitmap) +{ + // Interrupts only for channels that are requested and enabled. + u32 irq_channels_bitmap = requested_bitmap & + engine->enabled_channels & + engine->interrupted_channels; + engine->interrupted_channels &= ~irq_channels_bitmap; + + return irq_channels_bitmap; +} + +typedef void(*transfer_done_cb_t)(struct hailo_ongoing_transfer *transfer, void *opaque); + +// Assuming irq_data->channels_count contains the amount of channels already +// written (used for multiple engines). +int hailo_vdma_engine_fill_irq_data(struct hailo_vdma_interrupts_wait_params *irq_data, + struct hailo_vdma_engine *engine, u32 irq_channels_bitmap, + transfer_done_cb_t transfer_done, void *transfer_done_opaque); + +int hailo_vdma_start_channel(u8 __iomem *regs, uint64_t desc_dma_address, uint32_t desc_count, uint8_t data_id); + +void hailo_vdma_stop_channel(u8 __iomem *regs); + +bool hailo_check_channel_index(u8 channel_index, u32 src_channels_bitmask, bool is_input_channel); + +#ifdef __cplusplus +} +#endif +#endif /* _HAILO_COMMON_VDMA_COMMON_H_ */ diff --git a/drivers/media/pci/hailo/include/hailo_pcie_version.h b/drivers/media/pci/hailo/include/hailo_pcie_version.h new file mode 100755 index 00000000000000..936bd7d4a477ff --- /dev/null +++ b/drivers/media/pci/hailo/include/hailo_pcie_version.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * Copyright (c) 2019-2022 Hailo Technologies Ltd. All rights reserved. + **/ + +#ifndef _HAILO_PCIE_VERSION_H_ +#define _HAILO_PCIE_VERSION_H_ + +#include <linux/stringify.h> +#include "../common/hailo_pcie_version.h" + +#define HAILO_DRV_VER __stringify(HAILO_DRV_VER_MAJOR) "." __stringify(HAILO_DRV_VER_MINOR) "." __stringify(HAILO_DRV_VER_REVISION) + +#endif /* _HAILO_PCIE_VERSION_H_ */ diff --git a/drivers/media/pci/hailo/src/fops.c b/drivers/media/pci/hailo/src/fops.c new file mode 100644 index 00000000000000..3b1cba647be729 --- /dev/null +++ b/drivers/media/pci/hailo/src/fops.c @@ -0,0 +1,570 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved. + **/ + +#include <linux/version.h> +#include <linux/pci.h> +#include <linux/interrupt.h> +#include <linux/sched.h> +#include <linux/pagemap.h> +#include <linux/uaccess.h> +#include <linux/scatterlist.h> +#include <linux/slab.h> +#include <linux/delay.h> + +#include <asm/thread_info.h> + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) +#include <linux/sched/signal.h> +#endif + +#include "fops.h" +#include "vdma_common.h" +#include "utils/logs.h" +#include "vdma/memory.h" +#include "vdma/ioctl.h" +#include "utils/compact.h" +#include "nnc.h" +#include "soc.h" + + +#if LINUX_VERSION_CODE >= KERNEL_VERSION( 4, 13, 0 ) +#define wait_queue_t wait_queue_entry_t +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION( 4, 15, 0 ) +#define ACCESS_ONCE READ_ONCE +#endif + +#ifndef VM_RESERVED + #define VMEM_FLAGS (VM_IO | VM_DONTEXPAND | VM_DONTDUMP) +#else + #define VMEM_FLAGS (VM_IO | VM_RESERVED) +#endif + +#define IS_PO2_ALIGNED(size, alignment) (!(size & (alignment-1))) + +// On pcie driver there is only one dma engine +#define DEFAULT_VDMA_ENGINE_INDEX (0) + + +static struct hailo_file_context *create_file_context(struct hailo_pcie_board *board, struct file *filp) +{ + struct hailo_file_context *context = kzalloc(sizeof(*context), GFP_KERNEL); + if (!context) { + hailo_err(board, "Failed to alloc file context (required size %zu)\n", sizeof(*context)); + return ERR_PTR(-ENOMEM); + } + + context->filp = filp; + hailo_vdma_file_context_init(&context->vdma_context); + list_add(&context->open_files_list, &board->open_files_list); + context->is_valid = true; + return context; +} + +static void release_file_context(struct hailo_file_context *context) +{ + context->is_valid = false; + list_del(&context->open_files_list); + kfree(context); +} + +static struct hailo_file_context *find_file_context(struct hailo_pcie_board *board, struct file *filp) +{ + struct hailo_file_context *cur = NULL; + list_for_each_entry(cur, &board->open_files_list, open_files_list) { + if (cur->filp == filp) { + return cur; + } + } + return NULL; +} + +int hailo_pcie_fops_open(struct inode *inode, struct file *filp) +{ + u32 major = MAJOR(inode->i_rdev); + u32 minor = MINOR(inode->i_rdev); + struct hailo_pcie_board *pBoard; + int err = 0; + pci_power_t previous_power_state = PCI_UNKNOWN; + bool interrupts_enabled_by_filp = false; + struct hailo_file_context *context = NULL; + + pr_debug(DRIVER_NAME ": (%d: %d-%d): fops_open\n", current->tgid, major, minor); + + // allow multiple processes to open a device, count references in hailo_pcie_get_board_index. + if (!(pBoard = hailo_pcie_get_board_index(minor))) { + pr_err(DRIVER_NAME ": fops_open: PCIe board not found for /dev/hailo%d node.\n", minor); + err = -ENODEV; + goto l_exit; + } + + filp->private_data = pBoard; + + if (down_interruptible(&pBoard->mutex)) { + hailo_err(pBoard, "fops_open down_interruptible fail tgid:%d\n", current->tgid); + err = -ERESTARTSYS; + goto l_decrease_ref_count; + } + + context = create_file_context(pBoard, filp); + if (IS_ERR(context)) { + err = PTR_ERR(context); + goto l_release_mutex; + } + + previous_power_state = pBoard->pDev->current_state; + if (PCI_D0 != previous_power_state) { + hailo_info(pBoard, "Waking up board change state from %d to PCI_D0\n", previous_power_state); + err = pci_set_power_state(pBoard->pDev, PCI_D0); + if (err < 0) { + hailo_err(pBoard, "Failed waking up board %d", err); + goto l_free_context; + } + } + + if (!hailo_pcie_is_device_connected(&pBoard->pcie_resources)) { + hailo_err(pBoard, "Device disconnected while opening device\n"); + err = -ENXIO; + goto l_revert_power_state; + } + + // enable interrupts + if (!pBoard->interrupts_enabled) { + err = hailo_enable_interrupts(pBoard); + if (err < 0) { + hailo_err(pBoard, "Failed Enabling interrupts %d\n", err); + goto l_revert_power_state; + } + interrupts_enabled_by_filp = true; + } + + if (pBoard->pcie_resources.accelerator_type == HAILO_ACCELERATOR_TYPE_NNC) { + err = hailo_nnc_file_context_init(pBoard, context); + } else { + err = hailo_soc_file_context_init(pBoard, context); + } + if (err < 0) { + goto l_release_irq; + } + + hailo_dbg(pBoard, "(%d: %d-%d): fops_open: SUCCESS on /dev/hailo%d\n", current->tgid, + major, minor, minor); + + up(&pBoard->mutex); + return 0; + +l_release_irq: + if (interrupts_enabled_by_filp) { + hailo_disable_interrupts(pBoard); + } + +l_revert_power_state: + if (pBoard->pDev->current_state != previous_power_state) { + hailo_info(pBoard, "Power changing state from %d to %d\n", previous_power_state, pBoard->pDev->current_state); + if (pci_set_power_state(pBoard->pDev, previous_power_state) < 0) { + hailo_err(pBoard, "Failed setting power state back to %d\n", (int)previous_power_state); + } + } +l_free_context: + release_file_context(context); +l_release_mutex: + up(&pBoard->mutex); +l_decrease_ref_count: + atomic_dec(&pBoard->ref_count); +l_exit: + return err; +} + +int hailo_pcie_fops_release(struct inode *inode, struct file *filp) +{ + struct hailo_pcie_board *board = (struct hailo_pcie_board *)filp->private_data; + struct hailo_file_context *context = NULL; + + u32 major = MAJOR(inode->i_rdev); + u32 minor = MINOR(inode->i_rdev); + + if (board) { + hailo_info(board, "(%d: %d-%d): fops_release\n", current->tgid, major, minor); + + + down(&board->mutex); + + context = find_file_context(board, filp); + if (NULL == context) { + hailo_err(board, "Invalid driver state, file context does not exist\n"); + up(&board->mutex); + return -EINVAL; + } + + if (false == context->is_valid) { + // File context is invalid, but open. It's OK to continue finalize and release it. + hailo_err(board, "Invalid file context\n"); + } + + if (board->pcie_resources.accelerator_type == HAILO_ACCELERATOR_TYPE_NNC) { + hailo_nnc_file_context_finalize(board, context); + } else { + hailo_soc_file_context_finalize(board, context); + } + + hailo_vdma_file_context_finalize(&context->vdma_context, &board->vdma, filp); + release_file_context(context); + + if (atomic_dec_and_test(&board->ref_count)) { + // Disable interrupts + hailo_disable_interrupts(board); + + if (power_mode_enabled()) { + hailo_info(board, "Power change state to PCI_D3hot\n"); + if (board->pDev && pci_set_power_state(board->pDev, PCI_D3hot) < 0) { + hailo_err(board, "Failed setting power state to D3hot"); + } + } + + // deallocate board if already removed + if (!board->pDev) { + hailo_dbg(board, "fops_release, freed board\n"); + up(&board->mutex); + kfree(board); + board = NULL; + } else { + hailo_dbg(board, "fops_release, released resources for board\n"); + up(&board->mutex); + } + } else { + up(&board->mutex); + } + + hailo_dbg(board, "(%d: %d-%d): fops_release: SUCCESS on /dev/hailo%d\n", current->tgid, + major, minor, minor); + } + + return 0; +} + +static long hailo_memory_transfer_ioctl(struct hailo_pcie_board *board, unsigned long arg) +{ + long err = 0; + struct hailo_memory_transfer_params* transfer = &board->memory_transfer_params; + + hailo_dbg(board, "Start memory transfer ioctl\n"); + + if (copy_from_user(transfer, (void __user*)arg, sizeof(*transfer))) { + hailo_err(board, "copy_from_user fail\n"); + return -ENOMEM; + } + + err = hailo_pcie_memory_transfer(&board->pcie_resources, transfer); + if (err < 0) { + hailo_err(board, "memory transfer failed %ld", err); + } + + if (copy_to_user((void __user*)arg, transfer, sizeof(*transfer))) { + hailo_err(board, "copy_to_user fail\n"); + return -ENOMEM; + } + + return err; +} + +static void firmware_notification_irq_handler(struct hailo_pcie_board *board) +{ + struct hailo_notification_wait *notif_wait_cursor = NULL; + int err = 0; + unsigned long irq_saved_flags = 0; + + spin_lock_irqsave(&board->nnc.notification_read_spinlock, irq_saved_flags); + err = hailo_pcie_read_firmware_notification(&board->pcie_resources.fw_access, &board->nnc.notification_cache); + spin_unlock_irqrestore(&board->nnc.notification_read_spinlock, irq_saved_flags); + + if (err < 0) { + hailo_err(board, "Failed reading firmware notification"); + } + else { + // TODO: HRT-14502 move interrupt handling to nnc + rcu_read_lock(); + list_for_each_entry_rcu(notif_wait_cursor, &board->nnc.notification_wait_list, notification_wait_list) + { + complete(¬if_wait_cursor->notification_completion); + } + rcu_read_unlock(); + } +} + +static void boot_irq_handler(struct hailo_pcie_board *board, struct hailo_pcie_interrupt_source *irq_source) +{ + if (irq_source->sw_interrupts & HAILO_PCIE_BOOT_SOFT_RESET_IRQ) { + hailo_dbg(board, "soft reset trigger IRQ\n"); + complete(&board->soft_reset.reset_completed); + } + if (irq_source->sw_interrupts & HAILO_PCIE_BOOT_IRQ) { + hailo_dbg(board, "boot trigger IRQ\n"); + complete_all(&board->fw_boot.fw_loaded_completion); + } else { + board->fw_boot.boot_used_channel_bitmap &= ~irq_source->vdma_channels_bitmap; + hailo_dbg(board, "boot vDMA data IRQ - channel_bitmap = 0x%x\n", irq_source->vdma_channels_bitmap); + if (0 == board->fw_boot.boot_used_channel_bitmap) { + complete_all(&board->fw_boot.vdma_boot_completion); + hailo_dbg(board, "boot vDMA data trigger IRQ\n"); + } + } +} + +static void nnc_irq_handler(struct hailo_pcie_board *board, struct hailo_pcie_interrupt_source *irq_source) +{ + if (irq_source->sw_interrupts & HAILO_PCIE_NNC_FW_CONTROL_IRQ) { + complete(&board->nnc.fw_control.completion); + } + + if (irq_source->sw_interrupts & HAILO_PCIE_NNC_DRIVER_DOWN_IRQ) { + complete(&board->driver_down.reset_completed); + } + + if (irq_source->sw_interrupts & HAILO_PCIE_NNC_FW_NOTIFICATION_IRQ) { + firmware_notification_irq_handler(board); + } +} + +static void soc_irq_handler(struct hailo_pcie_board *board, struct hailo_pcie_interrupt_source *irq_source) +{ + if (irq_source->sw_interrupts & HAILO_PCIE_SOC_CONTROL_IRQ) { + complete_all(&board->soc.control_resp_ready); + } + + if (irq_source->sw_interrupts & HAILO_PCIE_SOC_CLOSE_IRQ) { + hailo_info(board, "soc_irq_handler - HAILO_PCIE_SOC_CLOSE_IRQ\n"); + // always use bitmap=0xFFFFFFFF - it is ok to wake all interrupts since each handler will check if the stream was aborted or not. + hailo_vdma_wakeup_interrupts(&board->vdma, &board->vdma.vdma_engines[DEFAULT_VDMA_ENGINE_INDEX], + 0xFFFFFFFF); + } +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22) +irqreturn_t hailo_irqhandler(int irq, void *dev_id, struct pt_regs *regs) +#else +irqreturn_t hailo_irqhandler(int irq, void *dev_id) +#endif +{ + irqreturn_t return_value = IRQ_NONE; + struct hailo_pcie_board *board = (struct hailo_pcie_board *)dev_id; + bool got_interrupt = false; + struct hailo_pcie_interrupt_source irq_source = {0}; + + hailo_dbg(board, "hailo_irqhandler\n"); + + while (true) { + if (!hailo_pcie_is_device_connected(&board->pcie_resources)) { + hailo_err(board, "Device disconnected while handling irq\n"); + break; + } + + got_interrupt = hailo_pcie_read_interrupt(&board->pcie_resources, &irq_source); + if (!got_interrupt) { + break; + } + + return_value = IRQ_HANDLED; + + if (board->fw_boot.is_in_boot) { + boot_irq_handler(board, &irq_source); + } else { + if (HAILO_ACCELERATOR_TYPE_NNC == board->pcie_resources.accelerator_type) { + nnc_irq_handler(board, &irq_source); + } else if (HAILO_ACCELERATOR_TYPE_SOC == board->pcie_resources.accelerator_type) { + soc_irq_handler(board, &irq_source); + } else { + hailo_err(board, "Invalid accelerator type %d\n", board->pcie_resources.accelerator_type); + } + + if (0 != irq_source.vdma_channels_bitmap) { + hailo_vdma_irq_handler(&board->vdma, DEFAULT_VDMA_ENGINE_INDEX, + irq_source.vdma_channels_bitmap); + } + } + } + + return return_value; +} + +static long hailo_query_device_properties(struct hailo_pcie_board *board, unsigned long arg) +{ + struct hailo_device_properties props = { + .desc_max_page_size = board->desc_max_page_size, + .board_type = board->pcie_resources.board_type, + .allocation_mode = board->allocation_mode, + .dma_type = HAILO_DMA_TYPE_PCIE, + .dma_engines_count = board->vdma.vdma_engines_count, + .is_fw_loaded = hailo_pcie_is_firmware_loaded(&board->pcie_resources), + }; + + hailo_info(board, "HAILO_QUERY_DEVICE_PROPERTIES: desc_max_page_size=%u\n", props.desc_max_page_size); + + if (copy_to_user((void __user*)arg, &props, sizeof(props))) { + hailo_err(board, "HAILO_QUERY_DEVICE_PROPERTIES, copy_to_user failed\n"); + return -ENOMEM; + } + + return 0; +} + +static long hailo_query_driver_info(struct hailo_pcie_board *board, unsigned long arg) +{ + struct hailo_driver_info info = { + .major_version = HAILO_DRV_VER_MAJOR, + .minor_version = HAILO_DRV_VER_MINOR, + .revision_version = HAILO_DRV_VER_REVISION + }; + + hailo_info(board, "HAILO_QUERY_DRIVER_INFO: major=%u, minor=%u, revision=%u\n", + info.major_version, info.minor_version, info.revision_version); + + if (copy_to_user((void __user*)arg, &info, sizeof(info))) { + hailo_err(board, "HAILO_QUERY_DRIVER_INFO, copy_to_user failed\n"); + return -ENOMEM; + } + + return 0; +} + +static long hailo_general_ioctl(struct hailo_pcie_board *board, unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + case HAILO_MEMORY_TRANSFER: + return hailo_memory_transfer_ioctl(board, arg); + case HAILO_QUERY_DEVICE_PROPERTIES: + return hailo_query_device_properties(board, arg); + case HAILO_QUERY_DRIVER_INFO: + return hailo_query_driver_info(board, arg); + default: + hailo_err(board, "Invalid general ioctl code 0x%x (nr: %d)\n", cmd, _IOC_NR(cmd)); + return -ENOTTY; + } +} + +long hailo_pcie_fops_unlockedioctl(struct file* filp, unsigned int cmd, unsigned long arg) +{ + long err = 0; + struct hailo_pcie_board* board = (struct hailo_pcie_board*) filp->private_data; + struct hailo_file_context *context = NULL; + bool should_up_board_mutex = true; + + + if (!board || !board->pDev) return -ENODEV; + + hailo_dbg(board, "(%d): fops_unlockedioctl. cmd:%d\n", current->tgid, _IOC_NR(cmd)); + + if (_IOC_DIR(cmd) & _IOC_READ) + { + err = !compatible_access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd)); + } + else if (_IOC_DIR(cmd) & _IOC_WRITE) + { + err = !compatible_access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd)); + } + + if (err) { + hailo_err(board, "Invalid ioctl parameter access 0x%x", cmd); + return -EFAULT; + } + + if (down_interruptible(&board->mutex)) { + hailo_err(board, "unlockedioctl down_interruptible failed"); + return -ERESTARTSYS; + } + BUG_ON(board->mutex.count != 0); + + context = find_file_context(board, filp); + if (NULL == context) { + hailo_err(board, "Invalid driver state, file context does not exist\n"); + up(&board->mutex); + return -EINVAL; + } + + if (false == context->is_valid) { + hailo_err(board, "Invalid file context\n"); + up(&board->mutex); + return -EINVAL; + } + + switch (_IOC_TYPE(cmd)) { + case HAILO_GENERAL_IOCTL_MAGIC: + err = hailo_general_ioctl(board, cmd, arg); + break; + case HAILO_VDMA_IOCTL_MAGIC: + err = hailo_vdma_ioctl(&context->vdma_context, &board->vdma, cmd, arg, filp, &board->mutex, + &should_up_board_mutex); + break; + case HAILO_SOC_IOCTL_MAGIC: + if (HAILO_ACCELERATOR_TYPE_SOC != board->pcie_resources.accelerator_type) { + hailo_err(board, "Ioctl %d is not supported on this accelerator type\n", _IOC_TYPE(cmd)); + err = -EINVAL; + } else { + err = hailo_soc_ioctl(board, context, &board->vdma, cmd, arg); + } + break; + case HAILO_NNC_IOCTL_MAGIC: + if (HAILO_ACCELERATOR_TYPE_NNC != board->pcie_resources.accelerator_type) { + hailo_err(board, "Ioctl %d is not supported on this accelerator type\n", _IOC_TYPE(cmd)); + err = -EINVAL; + } else { + err = hailo_nnc_ioctl(board, cmd, arg, filp, &should_up_board_mutex); + } + break; + default: + hailo_err(board, "Invalid ioctl type %d\n", _IOC_TYPE(cmd)); + err = -ENOTTY; + } + + if (should_up_board_mutex) { + up(&board->mutex); + } + + hailo_dbg(board, "(%d): fops_unlockedioct: SUCCESS\n", current->tgid); + return err; + +} + +int hailo_pcie_fops_mmap(struct file* filp, struct vm_area_struct *vma) +{ + int err = 0; + + uintptr_t vdma_handle = vma->vm_pgoff << PAGE_SHIFT; + + struct hailo_pcie_board* board = (struct hailo_pcie_board*)filp->private_data; + struct hailo_file_context *context = NULL; + + BUILD_BUG_ON_MSG(sizeof(vma->vm_pgoff) < sizeof(vdma_handle), + "If this expression fails to compile it means the target HW is not compatible with our approach to use " + "the page offset paramter of 'mmap' to pass the driver the 'handle' of the desired descriptor"); + + vma->vm_pgoff = 0; // vm_pgoff contains vdma_handle page offset, the actual offset from the phys addr is 0 + + hailo_info(board, "%d fops_mmap\n", current->tgid); + + if (!board || !board->pDev) return -ENODEV; + + if (down_interruptible(&board->mutex)) { + hailo_err(board, "hailo_pcie_fops_mmap down_interruptible fail tgid:%d\n", current->tgid); + return -ERESTARTSYS; + } + + context = find_file_context(board, filp); + if (NULL == context) { + up(&board->mutex); + hailo_err(board, "Invalid driver state, file context does not exist\n"); + return -EINVAL; + } + + if (false == context->is_valid) { + up(&board->mutex); + hailo_err(board, "Invalid file context\n"); + return -EINVAL; + } + + err = hailo_vdma_mmap(&context->vdma_context, &board->vdma, vma, vdma_handle); + up(&board->mutex); + return err; +} diff --git a/drivers/media/pci/hailo/src/fops.h b/drivers/media/pci/hailo/src/fops.h new file mode 100644 index 00000000000000..9b940249b1a7a6 --- /dev/null +++ b/drivers/media/pci/hailo/src/fops.h @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved. + **/ + +#ifndef _HAILO_PCI_FOPS_H_ +#define _HAILO_PCI_FOPS_H_ + +#include "pcie.h" + +int hailo_pcie_fops_open(struct inode* inode, struct file* filp); +int hailo_pcie_fops_release(struct inode* inode, struct file* filp); +long hailo_pcie_fops_unlockedioctl(struct file* filp, unsigned int cmd, unsigned long arg); +int hailo_pcie_fops_mmap(struct file* filp, struct vm_area_struct *vma); +void hailo_pcie_ep_init(struct hailo_pcie_board *board); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22) +irqreturn_t hailo_irqhandler(int irq, void* dev_id, struct pt_regs *regs); +#else +irqreturn_t hailo_irqhandler(int irq, void* dev_id); +#endif + +#endif /* _HAILO_PCI_FOPS_H_ */ diff --git a/drivers/media/pci/hailo/src/nnc.c b/drivers/media/pci/hailo/src/nnc.c new file mode 100644 index 00000000000000..10faafcb415f60 --- /dev/null +++ b/drivers/media/pci/hailo/src/nnc.c @@ -0,0 +1,299 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved. + **/ +/** + * A Hailo PCIe NNC device is a device contains a NNC (neural network core) and some basic FW. + * The device supports sending controls, receiving notification and reading the FW log. + */ + +#include "nnc.h" +#include "hailo_ioctl_common.h" + +#include "utils/logs.h" +#include "utils/compact.h" + +#include <linux/uaccess.h> + +#if !defined(HAILO_EMULATOR) +#define DEFAULT_SHUTDOWN_TIMEOUT_MS (5) +#else /* !defined(HAILO_EMULATOR) */ +#define DEFAULT_SHUTDOWN_TIMEOUT_MS (1000) +#endif /* !defined(HAILO_EMULATOR) */ + +void hailo_nnc_init(struct hailo_pcie_nnc *nnc) +{ + sema_init(&nnc->fw_control.mutex, 1); + spin_lock_init(&nnc->notification_read_spinlock); + init_completion(&nnc->fw_control.completion); + INIT_LIST_HEAD(&nnc->notification_wait_list); + memset(&nnc->notification_cache, 0, sizeof(nnc->notification_cache)); +} + +void hailo_nnc_finalize(struct hailo_pcie_nnc *nnc) +{ + struct hailo_notification_wait *cursor = NULL; + + // Lock rcu_read_lock and send notification_completion to wake anyone waiting on the notification_wait_list when removed + rcu_read_lock(); + list_for_each_entry_rcu(cursor, &nnc->notification_wait_list, notification_wait_list) { + cursor->is_disabled = true; + complete(&cursor->notification_completion); + } + rcu_read_unlock(); +} + +static int hailo_fw_control(struct hailo_pcie_board *board, unsigned long arg, bool* should_up_board_mutex) +{ + struct hailo_fw_control *command = &board->nnc.fw_control.command; + long completion_result = 0; + int err = 0; + + up(&board->mutex); + *should_up_board_mutex = false; + + if (down_interruptible(&board->nnc.fw_control.mutex)) { + hailo_info(board, "hailo_fw_control down_interruptible fail tgid:%d (process was interrupted or killed)\n", current->tgid); + return -ERESTARTSYS; + } + + if (copy_from_user(command, (void __user*)arg, sizeof(*command))) { + hailo_err(board, "hailo_fw_control, copy_from_user fail\n"); + err = -ENOMEM; + goto l_exit; + } + + reinit_completion(&board->nnc.fw_control.completion); + + err = hailo_pcie_write_firmware_control(&board->pcie_resources, command); + if (err < 0) { + hailo_err(board, "Failed writing fw control to pcie\n"); + goto l_exit; + } + + // Wait for response + completion_result = wait_for_completion_interruptible_timeout(&board->nnc.fw_control.completion, msecs_to_jiffies(command->timeout_ms)); + if (completion_result <= 0) { + if (0 == completion_result) { + hailo_err(board, "hailo_fw_control, timeout waiting for control (timeout_ms=%d)\n", command->timeout_ms); + err = -ETIMEDOUT; + } else { + hailo_info(board, "hailo_fw_control, wait for completion failed with err=%ld (process was interrupted or killed)\n", completion_result); + err = -EINTR; + } + goto l_exit; + } + + err = hailo_pcie_read_firmware_control(&board->pcie_resources, command); + if (err < 0) { + hailo_err(board, "Failed reading fw control from pcie\n"); + goto l_exit; + } + + if (copy_to_user((void __user*)arg, command, sizeof(*command))) { + hailo_err(board, "hailo_fw_control, copy_to_user fail\n"); + err = -ENOMEM; + goto l_exit; + } + +l_exit: + up(&board->nnc.fw_control.mutex); + return err; +} + +static long hailo_get_notification_wait_thread(struct hailo_pcie_board *board, struct file *filp, + struct hailo_notification_wait **current_waiting_thread) +{ + struct hailo_notification_wait *cursor = NULL; + // note: safe to access without rcu because the notification_wait_list is closed only on file release + list_for_each_entry(cursor, &board->nnc.notification_wait_list, notification_wait_list) + { + if ((current->tgid == cursor->tgid) && (filp == cursor->filp)) { + *current_waiting_thread = cursor; + return 0; + } + } + + return -EFAULT; +} + +static long hailo_read_notification_ioctl(struct hailo_pcie_board *board, unsigned long arg, struct file *filp, + bool* should_up_board_mutex) +{ + long err = 0; + struct hailo_notification_wait *current_waiting_thread = NULL; + struct hailo_d2h_notification *notification = &board->nnc.notification_to_user; + unsigned long irq_saved_flags; + + err = hailo_get_notification_wait_thread(board, filp, ¤t_waiting_thread); + if (0 != err) { + goto l_exit; + } + up(&board->mutex); + + if (0 > (err = wait_for_completion_interruptible(¤t_waiting_thread->notification_completion))) { + hailo_info(board, + "HAILO_READ_NOTIFICATION - wait_for_completion_interruptible error. err=%ld. tgid=%d (process was interrupted or killed)\n", + err, current_waiting_thread->tgid); + *should_up_board_mutex = false; + goto l_exit; + } + + if (down_interruptible(&board->mutex)) { + hailo_info(board, "HAILO_READ_NOTIFICATION - down_interruptible error (process was interrupted or killed)\n"); + *should_up_board_mutex = false; + err = -ERESTARTSYS; + goto l_exit; + } + + // Check if was disabled + if (current_waiting_thread->is_disabled) { + hailo_info(board, "HAILO_READ_NOTIFICATION - notification disabled for tgid=%d\n", current->tgid); + err = -ECANCELED; + goto l_exit; + } + + reinit_completion(¤t_waiting_thread->notification_completion); + + spin_lock_irqsave(&board->nnc.notification_read_spinlock, irq_saved_flags); + notification->buffer_len = board->nnc.notification_cache.buffer_len; + memcpy(notification->buffer, board->nnc.notification_cache.buffer, notification->buffer_len); + spin_unlock_irqrestore(&board->nnc.notification_read_spinlock, irq_saved_flags); + + if (copy_to_user((void __user*)arg, notification, sizeof(*notification))) { + hailo_err(board, "HAILO_READ_NOTIFICATION copy_to_user fail\n"); + err = -ENOMEM; + goto l_exit; + } + +l_exit: + return err; +} + +static long hailo_disable_notification(struct hailo_pcie_board *board, struct file *filp) +{ + struct hailo_notification_wait *cursor = NULL; + + hailo_info(board, "HAILO_DISABLE_NOTIFICATION: disable notification"); + rcu_read_lock(); + list_for_each_entry_rcu(cursor, &board->nnc.notification_wait_list, notification_wait_list) { + if ((current->tgid == cursor->tgid) && (filp == cursor->filp)) { + cursor->is_disabled = true; + complete(&cursor->notification_completion); + break; + } + } + rcu_read_unlock(); + + return 0; +} + +static long hailo_read_log_ioctl(struct hailo_pcie_board *board, unsigned long arg) +{ + long err = 0; + struct hailo_read_log_params params; + + if (copy_from_user(¶ms, (void __user*)arg, sizeof(params))) { + hailo_err(board, "HAILO_READ_LOG, copy_from_user fail\n"); + return -ENOMEM; + } + + if (0 > (err = hailo_pcie_read_firmware_log(&board->pcie_resources.fw_access, ¶ms))) { + hailo_err(board, "HAILO_READ_LOG, reading from log failed with error: %ld \n", err); + return err; + } + + if (copy_to_user((void*)arg, ¶ms, sizeof(params))) { + return -ENOMEM; + } + + return 0; +} + +long hailo_nnc_ioctl(struct hailo_pcie_board *board, unsigned int cmd, unsigned long arg, + struct file *filp, bool *should_up_board_mutex) +{ + switch (cmd) { + case HAILO_FW_CONTROL: + return hailo_fw_control(board, arg, should_up_board_mutex); + case HAILO_READ_NOTIFICATION: + return hailo_read_notification_ioctl(board, arg, filp, should_up_board_mutex); + case HAILO_DISABLE_NOTIFICATION: + return hailo_disable_notification(board, filp); + case HAILO_READ_LOG: + return hailo_read_log_ioctl(board, arg); + default: + hailo_err(board, "Invalid nnc ioctl code 0x%x (nr: %d)\n", cmd, _IOC_NR(cmd)); + return -ENOTTY; + } +} + + +static int add_notification_wait(struct hailo_pcie_board *board, struct file *filp) +{ + struct hailo_notification_wait *wait = kmalloc(sizeof(*wait), GFP_KERNEL); + if (!wait) { + hailo_err(board, "Failed to allocate notification wait structure.\n"); + return -ENOMEM; + } + wait->tgid = current->tgid; + wait->filp = filp; + wait->is_disabled = false; + init_completion(&wait->notification_completion); + list_add_rcu(&wait->notification_wait_list, &board->nnc.notification_wait_list); + return 0; +} + +int hailo_nnc_file_context_init(struct hailo_pcie_board *board, struct hailo_file_context *context) +{ + return add_notification_wait(board, context->filp); +} + +static void clear_notification_wait_list(struct hailo_pcie_board *board, struct file *filp) +{ + struct hailo_notification_wait *cur = NULL, *next = NULL; + list_for_each_entry_safe(cur, next, &board->nnc.notification_wait_list, notification_wait_list) { + if (cur->filp == filp) { + list_del_rcu(&cur->notification_wait_list); + synchronize_rcu(); + kfree(cur); + } + } +} + +int hailo_nnc_driver_down(struct hailo_pcie_board *board) +{ + long completion_result = 0; + int err = 0; + + reinit_completion(&board->driver_down.reset_completed); + + hailo_pcie_write_firmware_driver_shutdown(&board->pcie_resources); + + // Wait for response + completion_result = + wait_for_completion_timeout(&board->driver_down.reset_completed, msecs_to_jiffies(DEFAULT_SHUTDOWN_TIMEOUT_MS)); + if (completion_result <= 0) { + if (0 == completion_result) { + hailo_err(board, "hailo_nnc_driver_down, timeout waiting for shutdown response (timeout_ms=%d)\n", DEFAULT_SHUTDOWN_TIMEOUT_MS); + err = -ETIMEDOUT; + } else { + hailo_info(board, "hailo_nnc_driver_down, wait for completion failed with err=%ld (process was interrupted or killed)\n", + completion_result); + err = completion_result; + } + goto l_exit; + } + +l_exit: + return err; +} + +void hailo_nnc_file_context_finalize(struct hailo_pcie_board *board, struct hailo_file_context *context) +{ + clear_notification_wait_list(board, context->filp); + + if (context->filp == board->vdma.used_by_filp) { + hailo_nnc_driver_down(board); + } +} \ No newline at end of file diff --git a/drivers/media/pci/hailo/src/nnc.h b/drivers/media/pci/hailo/src/nnc.h new file mode 100644 index 00000000000000..1bfac99202f152 --- /dev/null +++ b/drivers/media/pci/hailo/src/nnc.h @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved. + **/ + +#ifndef _HAILO_PCI_NNC_H_ +#define _HAILO_PCI_NNC_H_ + +#include "pcie.h" + +void hailo_nnc_init(struct hailo_pcie_nnc *nnc); +void hailo_nnc_finalize(struct hailo_pcie_nnc *nnc); + +long hailo_nnc_ioctl(struct hailo_pcie_board *board, unsigned int cmd, unsigned long arg, + struct file *filp, bool *should_up_board_mutex); + +int hailo_nnc_file_context_init(struct hailo_pcie_board *board, struct hailo_file_context *context); +void hailo_nnc_file_context_finalize(struct hailo_pcie_board *board, struct hailo_file_context *context); + +int hailo_nnc_driver_down(struct hailo_pcie_board *board); + +#endif /* _HAILO_PCI_NNC_H_ */ \ No newline at end of file diff --git a/drivers/media/pci/hailo/src/pcie.c b/drivers/media/pci/hailo/src/pcie.c new file mode 100644 index 00000000000000..cbac1e5787a8c5 --- /dev/null +++ b/drivers/media/pci/hailo/src/pcie.c @@ -0,0 +1,1563 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved. + **/ + +#include <linux/version.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/pci_regs.h> +#include <linux/interrupt.h> +#include <linux/sched.h> +#include <linux/pagemap.h> +#include <linux/firmware.h> +#include <linux/kthread.h> +#include <linux/delay.h> + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) +#include <linux/dma-direct.h> +#endif + +#define KERNEL_CODE 1 + +#include "hailo_ioctl_common.h" +#include "pcie.h" +#include "nnc.h" +#include "soc.h" +#include "fops.h" +#include "sysfs.h" +#include "utils/logs.h" +#include "utils/compact.h" +#include "vdma/vdma.h" +#include "vdma/memory.h" + +#if LINUX_VERSION_CODE < KERNEL_VERSION( 5, 4, 0 ) +#include <linux/pci-aspm.h> +#endif + +// enum that represents values for the driver parameter to either force buffer from driver , userspace or not force +// and let driver decide +enum hailo_allocate_driver_buffer_driver_param { + HAILO_NO_FORCE_BUFFER = 0, + HAILO_FORCE_BUFFER_FROM_USERSPACE = 1, + HAILO_FORCE_BUFFER_FROM_DRIVER = 2, +}; + +// Debug flag +static int force_desc_page_size = 0; +static bool g_is_power_mode_enabled = true; +static int force_allocation_from_driver = HAILO_NO_FORCE_BUFFER; +static bool force_hailo10h_legacy_mode = false; +static bool force_boot_linux_from_eemc = false; +static bool support_soft_reset = true; + +#define DEVICE_NODE_NAME "hailo" +static int char_major = 0; +static struct class *chardev_class; + +static LIST_HEAD(g_hailo_board_list); +static struct semaphore g_hailo_add_board_mutex = __SEMAPHORE_INITIALIZER(g_hailo_add_board_mutex, 1); + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 22)) +#define HAILO_IRQ_FLAGS (SA_SHIRQ | SA_INTERRUPT) +#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 22) && LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0)) +#define HAILO_IRQ_FLAGS (IRQF_SHARED | IRQF_DISABLED) +#else +#define HAILO_IRQ_FLAGS (IRQF_SHARED) +#endif + + /* **************************** + ******************************* */ +bool power_mode_enabled(void) +{ +#if !defined(HAILO_EMULATOR) + return g_is_power_mode_enabled; +#else /* !defined(HAILO_EMULATOR) */ + return false; +#endif /* !defined(HAILO_EMULATOR) */ +} + + +/** + * Due to an HW bug, on system with low MaxReadReq ( < 512) we need to use different descriptors size. + * Returns the max descriptor size or 0 on failure. + */ +static int hailo_get_desc_page_size(struct pci_dev *pdev, u32 *out_page_size) +{ + u16 pcie_device_control = 0; + int err = 0; + // The default page size must be smaller/equal to 32K (due to PLDA registers limit). + const u32 max_page_size = 32u * 1024u; + const u32 defualt_page_size = min((u32)PAGE_SIZE, max_page_size); + + if (force_desc_page_size != 0) { + // The user given desc_page_size as a module parameter + if ((force_desc_page_size & (force_desc_page_size - 1)) != 0) { + pci_err(pdev, "force_desc_page_size must be a power of 2\n"); + return -EINVAL; + } + + if (force_desc_page_size > max_page_size) { + pci_err(pdev, "force_desc_page_size %d mustn't be larger than %u", force_desc_page_size, max_page_size); + return -EINVAL; + } + + pci_notice(pdev, "Probing: Force setting max_desc_page_size to %d (recommended value is %lu)\n", + force_desc_page_size, PAGE_SIZE); + *out_page_size = force_desc_page_size; + return 0; + } + + err = pcie_capability_read_word(pdev, PCI_EXP_DEVCTL, &pcie_device_control); + if (err < 0) { + pci_err(pdev, "Couldn't read DEVCTL capability\n"); + return err; + } + + switch (pcie_device_control & PCI_EXP_DEVCTL_READRQ) { + case PCI_EXP_DEVCTL_READRQ_128B: + pci_notice(pdev, "Probing: Setting max_desc_page_size to 128 (recommended value is %u)\n", defualt_page_size); + *out_page_size = 128; + return 0; + case PCI_EXP_DEVCTL_READRQ_256B: + pci_notice(pdev, "Probing: Setting max_desc_page_size to 256 (recommended value is %u)\n", defualt_page_size); + *out_page_size = 256; + return 0; + default: + pci_notice(pdev, "Probing: Setting max_desc_page_size to %u, (page_size=%lu)\n", defualt_page_size, PAGE_SIZE); + *out_page_size = defualt_page_size; + return 0; + }; +} + +// should be called only from fops_open (once) +struct hailo_pcie_board* hailo_pcie_get_board_index(u32 index) +{ + struct hailo_pcie_board *pBoard, *pRet = NULL; + + down(&g_hailo_add_board_mutex); + list_for_each_entry(pBoard, &g_hailo_board_list, board_list) + { + if ( index == pBoard->board_index ) + { + atomic_inc(&pBoard->ref_count); + pRet = pBoard; + break; + } + } + up(&g_hailo_add_board_mutex); + + return pRet; +} + +/** + * hailo_pcie_disable_aspm - Disable ASPM states + * @board: pointer to PCI board struct + * @state: bit-mask of ASPM states to disable + * @locked: indication if this context holds pci_bus_sem locked. + * + * Some devices *must* have certain ASPM states disabled per hardware errata. + **/ +static int hailo_pcie_disable_aspm(struct hailo_pcie_board *board, u16 state, bool locked) +{ + struct pci_dev *pdev = board->pDev; + struct pci_dev *parent = pdev->bus->self; + u16 aspm_dis_mask = 0; + u16 pdev_aspmc = 0; + u16 parent_aspmc = 0; + int err = 0; + + switch (state) { + case PCIE_LINK_STATE_L0S: + aspm_dis_mask |= PCI_EXP_LNKCTL_ASPM_L0S; + break; + case PCIE_LINK_STATE_L1: + aspm_dis_mask |= PCI_EXP_LNKCTL_ASPM_L1; + break; + default: + break; + } + + err = pcie_capability_read_word(pdev, PCI_EXP_LNKCTL, &pdev_aspmc); + if (err < 0) { + hailo_err(board, "Couldn't read LNKCTL capability\n"); + return err; + } + + pdev_aspmc &= PCI_EXP_LNKCTL_ASPMC; + + if (parent) { + err = pcie_capability_read_word(parent, PCI_EXP_LNKCTL, &parent_aspmc); + if (err < 0) { + hailo_err(board, "Couldn't read slot LNKCTL capability\n"); + return err; + } + parent_aspmc &= PCI_EXP_LNKCTL_ASPMC; + } + + hailo_notice(board, "Disabling ASPM %s %s\n", + (aspm_dis_mask & PCI_EXP_LNKCTL_ASPM_L0S) ? "L0s" : "", + (aspm_dis_mask & PCI_EXP_LNKCTL_ASPM_L1) ? "L1" : ""); + + // Disable L0s even if it is currently disabled as ASPM states can be enabled by the kernel when changing power modes +#ifdef CONFIG_PCIEASPM + if (locked) { + // Older kernel versions (<5.2.21) don't return value for this functions, so we try manual disabling anyway + (void)pci_disable_link_state_locked(pdev, state); + } else { + (void)pci_disable_link_state(pdev, state); + } + + /* Double-check ASPM control. If not disabled by the above, the + * BIOS is preventing that from happening (or CONFIG_PCIEASPM is + * not enabled); override by writing PCI config space directly. + */ + err = pcie_capability_read_word(pdev, PCI_EXP_LNKCTL, &pdev_aspmc); + if (err < 0) { + hailo_err(board, "Couldn't read LNKCTL capability\n"); + return err; + } + pdev_aspmc &= PCI_EXP_LNKCTL_ASPMC; + + if (!(aspm_dis_mask & pdev_aspmc)) { + hailo_notice(board, "Successfully disabled ASPM %s %s\n", + (aspm_dis_mask & PCI_EXP_LNKCTL_ASPM_L0S) ? "L0s" : "", + (aspm_dis_mask & PCI_EXP_LNKCTL_ASPM_L1) ? "L1" : ""); + return 0; + } +#endif + + /* Both device and parent should have the same ASPM setting. + * Disable ASPM in downstream component first and then upstream. + */ + err = pcie_capability_clear_word(pdev, PCI_EXP_LNKCTL, aspm_dis_mask); + if (err < 0) { + hailo_err(board, "Couldn't read LNKCTL capability\n"); + return err; + } + if (parent) { + err = pcie_capability_clear_word(parent, PCI_EXP_LNKCTL, aspm_dis_mask); + if (err < 0) { + hailo_err(board, "Couldn't read slot LNKCTL capability\n"); + return err; + } + } + hailo_notice(board, "Manually disabled ASPM %s %s\n", + (aspm_dis_mask & PCI_EXP_LNKCTL_ASPM_L0S) ? "L0s" : "", + (aspm_dis_mask & PCI_EXP_LNKCTL_ASPM_L1) ? "L1" : ""); + + return 0; +} + +static void hailo_pcie_insert_board(struct hailo_pcie_board* pBoard) +{ + u32 index = 0; + struct hailo_pcie_board *pCurrent, *pNext; + + + down(&g_hailo_add_board_mutex); + if ( list_empty(&g_hailo_board_list) || + list_first_entry(&g_hailo_board_list, struct hailo_pcie_board, board_list)->board_index > 0) + { + pBoard->board_index = 0; + list_add(&pBoard->board_list, &g_hailo_board_list); + + up(&g_hailo_add_board_mutex); + return; + } + + list_for_each_entry_safe(pCurrent, pNext, &g_hailo_board_list, board_list) + { + index = pCurrent->board_index+1; + if( list_is_last(&pCurrent->board_list, &g_hailo_board_list) || (index != pNext->board_index)) + { + break; + } + } + + pBoard->board_index = index; + list_add(&pBoard->board_list, &pCurrent->board_list); + + up(&g_hailo_add_board_mutex); + + return; +} + +static void hailo_pcie_remove_board(struct hailo_pcie_board* pBoard) +{ + down(&g_hailo_add_board_mutex); + if (pBoard) + { + list_del(&pBoard->board_list); + } + up(&g_hailo_add_board_mutex); +} + +/** + * Wait until the relevant completion is done. + * + * @param completion - pointer to the completion struct to wait for. + * @param msecs - the amount of time to wait in milliseconds. + * @return false if timed out, true if completed. + */ +static bool wait_for_firmware_completion(struct completion *completion, unsigned int msecs) +{ + return (0 != wait_for_completion_timeout(completion, msecs_to_jiffies(msecs))); +} + +/** + * Program one FW file descriptors to the vDMA engine. + * + * @param dev - pointer to the device struct we are working on. + * @param boot_dma_state - pointer to the boot dma state struct which includes all of the boot resources. + * @param file_address - the address of the file in the device memory. + * @param transfer_buffer - the buffer to program to the vDMA engine. + * @param channel_index - the index of the channel to program. + * @param filename - the name of the file to program. + * @param raise_int_on_completion - true if this is the last descriptors chunk in the specific channel in the boot flow, false otherwise. If true - will enable + * an IRQ for the relevant channel when the transfer is finished. + * @return the amount of descriptors programmed on success, negative error code on failure. + */ +static int pcie_vdma_program_one_file_descriptors(struct device *dev, struct hailo_pcie_boot_dma_channel_state *boot_channel_state, + u32 file_address, struct hailo_vdma_mapped_transfer_buffer transfer_buffer, u8 channel_index, const char *filename, bool raise_int_on_completion) +{ + int device_desc = 0, host_desc = 0; + enum hailo_vdma_interrupts_domain interrupts_domain = raise_int_on_completion ? HAILO_VDMA_INTERRUPTS_DOMAIN_HOST : + HAILO_VDMA_INTERRUPTS_DOMAIN_NONE; + + hailo_dev_dbg(dev, "channel_index = %d, file_name = %s, file_address = 0x%x, transfer_buffer.offset = 0x%x,\ + size_to_program = 0x%x, starting_desc/desc_index = 0x%x\n", channel_index, filename, file_address, + transfer_buffer.offset, transfer_buffer.size, boot_channel_state->desc_program_num); + + // program descriptors + device_desc = hailo_vdma_program_descriptors_in_chunk(&hailo_pcie_vdma_hw, file_address, transfer_buffer.size, + &boot_channel_state->device_descriptors_buffer.desc_list, boot_channel_state->desc_program_num, + (boot_channel_state->device_descriptors_buffer.desc_list.desc_count - 1), channel_index, HAILO_PCI_EP_HOST_DMA_DATA_ID); + if (device_desc < 0) { + hailo_dev_err(dev, "Failed to program device descriptors, error = %u\n", device_desc); + return device_desc; + } + + host_desc = hailo_vdma_program_descriptors_list(&hailo_pcie_vdma_hw, &boot_channel_state->host_descriptors_buffer.desc_list, + boot_channel_state->desc_program_num, &transfer_buffer, true, channel_index, interrupts_domain, false); + if (host_desc < 0) { + hailo_dev_err(dev, "Failed to program host descriptors, error = %u\n", host_desc); + return host_desc; + } + + // checks that same amount of decsriptors were programmed on device side and host side + if (host_desc != device_desc) { + hailo_dev_err(dev, "Host and device descriptors should be the same\n"); + return -EINVAL; + } + + return host_desc; +} + +/** + * Program one FW file to the vDMA engine. + * + * @param board - pointer to the board struct we are working on. + * @param boot_dma_state - pointer to the boot dma state struct which includes all of the boot resources. + * @param file_address - the address of the file in the device memory. + * @param filename - the name of the file to program. + * @param raise_int_on_completion - true if this is the last file in the boot flow, false otherwise. uses to enable an IRQ for the + * relevant channel when the transfer is finished. + * @return 0 on success, negative error code on failure. at the end of the function the firmware is released. + */ +static int pcie_vdma_program_one_file(struct hailo_pcie_board *board, struct hailo_pcie_boot_dma_state *boot_dma_state, u32 file_address, + const char *filename, bool raise_int_on_completion) +{ + const struct firmware *firmware = NULL; + struct hailo_vdma_mapped_transfer_buffer transfer_buffer = {0}; + int desc_programmed = 0; + int err = 0; + size_t bytes_copied = 0, remaining_size = 0, data_offset = 0, desc_num_left = 0, current_desc_to_program = 0; + + hailo_notice(board, "Programing file %s for dma transfer\n", filename); + + // load firmware directly without usermode helper for the relevant file + err = request_firmware_direct(&firmware, filename, board->vdma.dev); + if (err < 0) { + hailo_err(board, "Failed to allocate memory for file %s\n", filename); + return err; + } + + // set the remaining size as the whole file size to begin with + remaining_size = firmware->size; + + while (remaining_size > 0) { + struct hailo_pcie_boot_dma_channel_state *channel = &boot_dma_state->channels[boot_dma_state->curr_channel_index]; + bool is_last_desc_chunk_of_curr_channel = false; + bool rais_interrupt_on_last_chunk = false; + + hailo_dbg(board, "desc_program_num = 0x%x, desc_page_size = 0x%x, on channel = %d\n", + channel->desc_program_num, HAILO_PCI_OVER_VDMA_PAGE_SIZE, boot_dma_state->curr_channel_index); + + // increment the channel index if the current channel is full + if ((MAX_SG_DESCS_COUNT - 1) == channel->desc_program_num) { + boot_dma_state->curr_channel_index++; + channel = &boot_dma_state->channels[boot_dma_state->curr_channel_index]; + board->fw_boot.boot_used_channel_bitmap |= (1 << boot_dma_state->curr_channel_index); + } + + // calculate the number of descriptors left to program and the number of bytes left to program + desc_num_left = (MAX_SG_DESCS_COUNT - 1) - channel->desc_program_num; + + // prepare the transfer buffer to make sure all the fields are initialized + transfer_buffer.sg_table = &channel->sg_table; + transfer_buffer.size = min(remaining_size, (desc_num_left * HAILO_PCI_OVER_VDMA_PAGE_SIZE)); + // no need to check for overflow since the variables are constant and always desc_program_num <= max u16 (65536) + // & the buffer max size is 256 Mb << 4G (max u32) + transfer_buffer.offset = (channel->desc_program_num * HAILO_PCI_OVER_VDMA_PAGE_SIZE); + + // check if this is the last descriptor chunk to program in the whole boot flow + current_desc_to_program = (transfer_buffer.size / HAILO_PCI_OVER_VDMA_PAGE_SIZE); + is_last_desc_chunk_of_curr_channel = ((MAX_SG_DESCS_COUNT - 1) == + (current_desc_to_program + channel->desc_program_num)); + rais_interrupt_on_last_chunk = (is_last_desc_chunk_of_curr_channel || (raise_int_on_completion && + (remaining_size == transfer_buffer.size))); + + // try to copy the file to the buffer, if failed, release the firmware and return + bytes_copied = sg_pcopy_from_buffer(transfer_buffer.sg_table->sgl, transfer_buffer.sg_table->orig_nents, + &firmware->data[data_offset], transfer_buffer.size, transfer_buffer.offset); + if (transfer_buffer.size != bytes_copied) { + hailo_err(board, "There is not enough memory allocated to copy file %s\n", filename); + release_firmware(firmware); + return -EFBIG; + } + + // program the descriptors + desc_programmed = pcie_vdma_program_one_file_descriptors(&board->pDev->dev, channel, (file_address + data_offset), + transfer_buffer, boot_dma_state->curr_channel_index, filename, rais_interrupt_on_last_chunk); + if (desc_programmed < 0) { + hailo_err(board, "Failed to program descriptors for file %s, on cahnnel = %d\n", filename, + boot_dma_state->curr_channel_index); + release_firmware(firmware); + return desc_programmed; + } + + // Update remaining size, data_offset and desc_program_num for the next iteration + remaining_size -= transfer_buffer.size; + data_offset += transfer_buffer.size; + channel->desc_program_num += desc_programmed; + } + + hailo_notice(board, "File %s programed successfully\n", filename); + + release_firmware(firmware); + + return desc_programmed; +} + +/** + * Program the entire batch of firmware files to the vDMA engine. + * + * @param board - pointer to the board struct we are working on. + * @param boot_dma_state - pointer to the boot dma state struct which includes all of the boot resources. + * @param resources - pointer to the hailo_pcie_resources struct. + * @param stage - the stage to program. + * @return 0 on success, negative error code on failure. + */ +static long pcie_vdma_program_entire_batch(struct hailo_pcie_board *board, struct hailo_pcie_boot_dma_state *boot_dma_state, + struct hailo_pcie_resources *resources, u32 stage) +{ + long err = 0; + int file_index = 0; + const struct hailo_pcie_loading_stage *stage_info = hailo_pcie_get_loading_stage_info(resources->board_type, stage); + const struct hailo_file_batch *files_batch = stage_info->batch; + const u8 amount_of_files = stage_info->amount_of_files_in_stage; + const char *filename = NULL; + u32 file_address = 0; + + for (file_index = 0; file_index < amount_of_files; file_index++) + { + filename = files_batch[file_index].filename; + file_address = files_batch[file_index].address; + + if (NULL == filename) { + hailo_err(board, "The amount of files wasn't specified for stage %d\n", stage); + break; + } + + err = pcie_vdma_program_one_file(board, boot_dma_state, file_address, filename, + (file_index == (amount_of_files - 1))); + if (err < 0) { + hailo_err(board, "Failed to program file %s\n", filename); + return err; + } + } + + return 0; +} + +/** + * Release noncontinuous memory (virtual continuous memory). (sg table and kernel_addrs) + * + * @param dev - pointer to the device struct we are working on. + * @param sg_table - the sg table to release. + * @param kernel_addrs - the kernel address to release. + */ +static void pcie_vdma_release_noncontinuous_memory(struct device *dev, struct sg_table *sg_table, void *kernel_addrs) +{ + dma_unmap_sg(dev, sg_table->sgl, sg_table->orig_nents, DMA_TO_DEVICE); + sg_free_table(sg_table); + vfree(kernel_addrs); +} + +/** + * Allocate noncontinuous memory (virtual continuous memory). + * + * @param dev - pointer to the device struct we are working on. + * @param buffer_size - the size of the buffer to allocate. + * @param kernel_addrs - pointer to the allocated buffer. + * @param sg_table - pointer to the sg table struct. + * @return 0 on success, negative error code on failure. on failure all resurces are released. (pages array, sg table, kernel_addrs) + */ +static long pcie_vdma_allocate_noncontinuous_memory(struct device *dev, u64 buffer_size, void **kernel_addrs, struct sg_table *sg_table) +{ + struct page **pages = NULL; + size_t npages = 0; + struct scatterlist *sgl = NULL; + long err = 0; + size_t i = 0; + + // allocate noncontinuous memory for the kernel address (virtual continuous memory) + *kernel_addrs = vmalloc(buffer_size); + if (NULL == *kernel_addrs) { + hailo_dev_err(dev, "Failed to allocate memory for kernel_addrs\n"); + err = -ENOMEM; + goto exit; + } + + // map the memory to pages + npages = DIV_ROUND_UP(buffer_size, PAGE_SIZE); + + // allocate memory for a virtually contiguous array for the pages + pages = kvmalloc_array(npages, sizeof(*pages), GFP_KERNEL); + if (!pages) { + err = -ENOMEM; + hailo_dev_err(dev, "Failed to allocate memory for pages\n"); + goto release_user_addrs; + } + + // walk a vmap address to the struct page it maps + for (i = 0; i < npages; i++) { + pages[i] = vmalloc_to_page(*kernel_addrs + (i * PAGE_SIZE)); + if (!pages[i]) { + err = -ENOMEM; + hailo_dev_err(dev, "Failed to get page from vmap address\n"); + goto release_array; + } + } + + // allocate and initialize the sg table from a list of pages + sgl = sg_alloc_table_from_pages_segment_compat(sg_table, pages, npages, 0, buffer_size, SGL_MAX_SEGMENT_SIZE, NULL, + 0, GFP_KERNEL); + if (IS_ERR(sgl)) { + err = PTR_ERR(sgl); + hailo_dev_err(dev, "sg table alloc failed (err %ld)..\n", err); + goto release_array; + } + + // map the sg list + sg_table->nents = dma_map_sg(dev, sg_table->sgl, sg_table->orig_nents, DMA_TO_DEVICE); + if (0 == sg_table->nents) { + hailo_dev_err(dev, "failed to map sg list for user buffer\n"); + err = -ENXIO; + goto release_sg_table; + } + + // clean exit - just release the pages array & return err = 0 + err = 0; + kfree(pages); + goto exit; + +release_sg_table: + dma_unmap_sg(dev, sg_table->sgl, sg_table->orig_nents, DMA_TO_DEVICE); +release_array: + kfree(pages); +release_user_addrs: + vfree(*kernel_addrs); +exit: + return err; +} + +/** + * Release all boot resources. + * + * @param board - pointer to the board struct we are working on. + * @param engine - pointer to the vdma engine struct. + * @param boot_dma_state - pointer to the boot dma state struct which includes all of the boot resources. + */ +static void pcie_vdme_release_boot_resources(struct hailo_pcie_board *board, struct hailo_vdma_engine *engine, + struct hailo_pcie_boot_dma_state *boot_dma_state) +{ + u8 channel_index = 0; + + // release all the resources + for (channel_index = 0; channel_index < HAILO_PCI_OVER_VDMA_NUM_CHANNELS; channel_index++) { + struct hailo_pcie_boot_dma_channel_state *channel = &boot_dma_state->channels[channel_index]; + // release descriptor lists + if (channel->host_descriptors_buffer.kernel_address != NULL) { + hailo_desc_list_release(&board->pDev->dev, &channel->host_descriptors_buffer); + } + if (channel->device_descriptors_buffer.kernel_address != NULL) { + hailo_desc_list_release(&board->pDev->dev, &channel->device_descriptors_buffer); + } + + // stops all boot vDMA channels + hailo_vdma_stop_channel(engine->channels[channel_index].host_regs); + hailo_vdma_stop_channel(engine->channels[channel_index].device_regs); + + // release noncontinuous memory (virtual continuous memory) + if (channel->kernel_addrs != NULL) { + pcie_vdma_release_noncontinuous_memory(&board->pDev->dev, &channel->sg_table, channel->kernel_addrs); + } + } +} + +/** + * Allocate boot resources for vDMA transfer. + * + * @param desc_page_size - the size of the descriptor page. + * @param board - pointer to the board struct we are working on. + * @param boot_dma_state - pointer to the boot dma state struct which includes all of the boot resources. + * @param engine - pointer to the vDMA engine struct. + * @return 0 on success, negative error code on failure. in case of failure descriptor lists are released, + * boot vDMA channels are stopped and memory is released. + */ +static long pcie_vdme_allocate_boot_resources(u32 desc_page_size, struct hailo_pcie_board *board, + struct hailo_pcie_boot_dma_state *boot_dma_state, struct hailo_vdma_engine *engine) +{ + long err = 0; + uintptr_t device_handle = 0, host_handle = 0; + u8 channel_index = 0; + + for (channel_index = 0; channel_index < HAILO_PCI_OVER_VDMA_NUM_CHANNELS; channel_index++) { + struct hailo_pcie_boot_dma_channel_state *channel = &boot_dma_state->channels[channel_index]; + + // create 2 descriptors list - 1 for the host & 1 for the device for each channel + err = hailo_desc_list_create(&board->pDev->dev, MAX_SG_DESCS_COUNT, desc_page_size, host_handle, false, + &channel->host_descriptors_buffer); + if (err < 0) { + hailo_err(board, "failed to allocate host descriptors list buffer\n"); + goto release_all_resources; + } + + err = hailo_desc_list_create(&board->pDev->dev, MAX_SG_DESCS_COUNT, desc_page_size, device_handle, false, + &channel->device_descriptors_buffer); + if (err < 0) { + hailo_err(board, "failed to allocate device descriptors list buffer\n"); + goto release_all_resources; + } + + // start vDMA channels - both sides with DDR at the host side (AKA ID 0) + err = hailo_vdma_start_channel(engine->channels[channel_index].host_regs, + channel->host_descriptors_buffer.dma_address, + channel->host_descriptors_buffer.desc_list.desc_count, board->vdma.hw->ddr_data_id); + if (err < 0) { + hailo_err(board, "Error starting host vdma channel\n"); + goto release_all_resources; + } + + err = hailo_vdma_start_channel(engine->channels[channel_index].device_regs, + channel->device_descriptors_buffer.dma_address, + channel->device_descriptors_buffer.desc_list.desc_count, board->vdma.hw->ddr_data_id); + if (err < 0) { + hailo_err(board, "Error starting device vdma channel\n"); + goto release_all_resources; + } + + // initialize the buffer size per channel + channel->buffer_size = (MAX_SG_DESCS_COUNT * desc_page_size); + + // allocate noncontinuous memory (virtual continuous memory) + err = pcie_vdma_allocate_noncontinuous_memory(&board->pDev->dev, channel->buffer_size, &channel->kernel_addrs, + &channel->sg_table); + if (err < 0) { + hailo_err(board, "Failed to allocate noncontinuous memory\n"); + goto release_all_resources; + } + } + + return 0; + +release_all_resources: + pcie_vdme_release_boot_resources(board, engine, boot_dma_state); + return err; +} + +/** + * Write FW boot files over vDMA using multiple channels for timing optimizations. + * + * The function is divided into the following steps: + * 1) Allocate resources for the boot process. + * 2) Programs descriptors to point to the memory and start the vDMA. + * 3) Waits until the vDMA is done and triggers the device to start the boot process. + * 4) Releases all the resources. + * + * @param board - pointer to the board struct. + * @param stage - the stage of the boot process. + * @param desc_page_size - the size of the descriptor page. + * @return 0 on success, negative error code on failure. in any case all resurces are released. + */ +static long pcie_write_firmware_batch_over_dma(struct hailo_pcie_board *board, u32 stage, u32 desc_page_size) +{ + long err = 0; + struct hailo_vdma_engine *engine = &board->vdma.vdma_engines[PCI_VDMA_ENGINE_INDEX]; + u8 channel_index = 0; + + err = pcie_vdme_allocate_boot_resources(desc_page_size, board, &board->fw_boot.boot_dma_state, engine); + if (err < 0) { + hailo_err(board, "Failed to create descriptors and start channels\n"); + return err; + } + + // initialize the completion for the vDMA boot data completion + reinit_completion(&board->fw_boot.vdma_boot_completion); + + err = pcie_vdma_program_entire_batch(board, &board->fw_boot.boot_dma_state, &board->pcie_resources, stage); + if (err < 0) { + hailo_err(board, "Failed to program entire batch\n"); + goto release_all; + } + + // sync the sg tables for the device before statirng the vDMA + for (channel_index = 0; channel_index < HAILO_PCI_OVER_VDMA_NUM_CHANNELS; channel_index++) { + dma_sync_sgtable_for_device(&board->pDev->dev, &board->fw_boot.boot_dma_state.channels[channel_index].sg_table, + DMA_TO_DEVICE); + } + + // start the vDMA transfer on all channels + for (channel_index = 0; channel_index < HAILO_PCI_OVER_VDMA_NUM_CHANNELS; channel_index++) { + struct hailo_pcie_boot_dma_channel_state *channel = &board->fw_boot.boot_dma_state.channels[channel_index]; + if (channel->desc_program_num != 0) { + hailo_vdma_set_num_avail(engine->channels[channel_index].host_regs, channel->desc_program_num); + hailo_vdma_set_num_avail(engine->channels[channel_index].device_regs, channel->desc_program_num); + hailo_dbg(board, "Set num avail to %u, on channel %u\n", channel->desc_program_num, channel_index); + } + } + + if (!wait_for_firmware_completion(&board->fw_boot.vdma_boot_completion, hailo_pcie_get_loading_stage_info(board->pcie_resources.board_type, SECOND_STAGE)->timeout)) { + hailo_err(board, "Timeout waiting for vDMA boot data completion\n"); + err = -ETIMEDOUT; + goto release_all; + } + + hailo_notice(board, "vDMA transfer completed, triggering boot\n"); + reinit_completion(&board->fw_boot.fw_loaded_completion); + hailo_trigger_firmware_boot(&board->pcie_resources, stage); + +release_all: + pcie_vdme_release_boot_resources(board, engine, &board->fw_boot.boot_dma_state); + return err; +} + +static int load_soc_firmware(struct hailo_pcie_board *board, struct hailo_pcie_resources *resources, + struct device *dev, struct completion *fw_load_completion) +{ + u32 boot_status = 0; + int err = 0; + u32 second_stage = force_boot_linux_from_eemc ? SECOND_STAGE_LINUX_IN_EMMC : SECOND_STAGE; + + if (hailo_pcie_is_firmware_loaded(resources)) { + hailo_dev_warn(dev, "SOC Firmware batch was already loaded\n"); + return 0; + } + + // configure the EP registers for the DMA transaction + hailo_pcie_configure_ep_registers_for_dma_transaction(resources); + + init_completion(fw_load_completion); + init_completion(&board->fw_boot.vdma_boot_completion); + + err = hailo_pcie_write_firmware_batch(dev, resources, FIRST_STAGE); + if (err < 0) { + hailo_dev_err(dev, "Failed writing SOC FIRST_STAGE firmware files. err %d\n", err); + return err; + } + + if (!wait_for_firmware_completion(fw_load_completion, hailo_pcie_get_loading_stage_info(resources->board_type, FIRST_STAGE)->timeout)) { + boot_status = hailo_get_boot_status(resources); + hailo_dev_err(dev, "Timeout waiting for SOC FIRST_STAGE firmware file, boot status %u\n", boot_status); + return -ETIMEDOUT; + } + + reinit_completion(fw_load_completion); + + err = (int)pcie_write_firmware_batch_over_dma(board, second_stage, HAILO_PCI_OVER_VDMA_PAGE_SIZE); + if (err < 0) { + hailo_dev_err(dev, "Failed writing SOC SECOND_STAGE firmware files over vDMA. err %d\n", err); + return err; + } + + if (!wait_for_firmware_completion(fw_load_completion, hailo_pcie_get_loading_stage_info(resources->board_type, SECOND_STAGE)->timeout)) { + boot_status = hailo_get_boot_status(resources); + hailo_dev_err(dev, "Timeout waiting for SOC SECOND_STAGE firmware file, boot status %u\n", boot_status); + return -ETIMEDOUT; + } + + reinit_completion(fw_load_completion); + reinit_completion(&board->fw_boot.vdma_boot_completion); + + hailo_dev_notice(dev, "SOC Firmware Batch loaded successfully\n"); + + return 0; +} +static int load_nnc_firmware(struct hailo_pcie_board *board) +{ + u32 boot_status = 0; + int err = 0; + struct device *dev = &board->pDev->dev; + + if (hailo_pcie_is_firmware_loaded(&board->pcie_resources)) { + if (support_soft_reset) { + err = hailo_pcie_soft_reset(&board->pcie_resources, &board->soft_reset.reset_completed); // send control, wait for done + if (err < 0) { + hailo_dev_err(dev, "Failed hailo pcie soft reset. err %d\n", err); + return 0; + } + hailo_dev_notice(dev, "Soft reset done\n"); + } else { + hailo_dev_warn(dev, "NNC Firmware batch was already loaded\n"); + return 0; + } + } + + init_completion(&board->fw_boot.fw_loaded_completion); + + err = hailo_pcie_write_firmware_batch(dev, &board->pcie_resources, FIRST_STAGE); + if (err < 0) { + hailo_dev_err(dev, "Failed writing NNC firmware files. err %d\n", err); + return err; + } + + if (!wait_for_firmware_completion(&board->fw_boot.fw_loaded_completion, hailo_pcie_get_loading_stage_info(board->pcie_resources.board_type, FIRST_STAGE)->timeout)) { + boot_status = hailo_get_boot_status(&board->pcie_resources); + hailo_dev_err(dev, "Timeout waiting for NNC firmware file, boot status %u\n", boot_status); + return -ETIMEDOUT; + } + + hailo_dev_notice(dev, "NNC Firmware loaded successfully\n"); + + return 0; +} + +int hailo_pcie_soft_reset(struct hailo_pcie_resources *resources, struct completion *reset_completed) +{ + bool completion_result = false; + int err = 0; + + hailo_pcie_write_firmware_soft_reset(resources); + + reinit_completion(reset_completed); + + // Wait for response + completion_result = + wait_for_firmware_completion(reset_completed, msecs_to_jiffies(FIRMWARE_WAIT_TIMEOUT_MS)); + if (completion_result == false) { + pr_warn("hailo reset firmware, timeout waiting for shutdown response (timeout_ms=%d)\n", FIRMWARE_WAIT_TIMEOUT_MS); + err = -ETIMEDOUT; + return err; + } + + msleep(TIME_UNTIL_REACH_BOOTLOADER); + pr_notice("hailo_driver_down finished\n"); + + return err; +} + +static int load_firmware(struct hailo_pcie_board *board) +{ + switch (board->pcie_resources.accelerator_type) { + case HAILO_ACCELERATOR_TYPE_SOC: + return load_soc_firmware(board, &board->pcie_resources, &board->pDev->dev, &board->fw_boot.fw_loaded_completion); + case HAILO_ACCELERATOR_TYPE_NNC: + return load_nnc_firmware(board); + default: + hailo_err(board, "Invalid board type %d\n", board->pcie_resources.accelerator_type); + return -EINVAL; + } +} + +static int enable_boot_interrupts(struct hailo_pcie_board *board) +{ + int err = hailo_enable_interrupts(board); + if (err < 0) { + hailo_err(board, "Failed enabling interrupts %d\n", err); + return err; + } + + board->fw_boot.is_in_boot = true; + return 0; +} + +static void disable_boot_interrupts(struct hailo_pcie_board *board) +{ + board->fw_boot.is_in_boot = false; + hailo_disable_interrupts(board); +} + +static int hailo_activate_board(struct hailo_pcie_board *board) +{ + int err = 0; + ktime_t start_time = 0, end_time = 0; + + (void)hailo_pcie_disable_aspm(board, PCIE_LINK_STATE_L0S, false); + + err = enable_boot_interrupts(board); + if (err < 0) { + return err; + } + + start_time = ktime_get(); + err = load_firmware(board); + end_time = ktime_get(); + hailo_notice(board, "FW loaded, took %lld ms\n", ktime_to_ms(ktime_sub(end_time, start_time))); + disable_boot_interrupts(board); + + if (err < 0) { + hailo_err(board, "Firmware load failed\n"); + return err; + } + + if (power_mode_enabled()) { + // Setting the device to low power state, until the user opens the device + hailo_info(board, "Power change state to PCI_D3hot\n"); + err = pci_set_power_state(board->pDev, PCI_D3hot); + if (err < 0) { + hailo_err(board, "Set power state failed %d\n", err); + return err; + } + } + + return 0; +} + +int hailo_enable_interrupts(struct hailo_pcie_board *board) +{ + int err = 0; + + if (board->interrupts_enabled) { + hailo_crit(board, "Failed enabling interrupts (already enabled)\n"); + return -EINVAL; + } + + // TODO HRT-2253: use new api for enabling msi: (pci_alloc_irq_vectors) + if ((err = pci_enable_msi(board->pDev))) { + hailo_err(board, "Failed to enable MSI %d\n", err); + return err; + } + hailo_info(board, "Enabled MSI interrupt\n"); + + err = request_irq(board->pDev->irq, hailo_irqhandler, HAILO_IRQ_FLAGS, DRIVER_NAME, board); + if (err) { + hailo_err(board, "request_irq failed %d\n", err); + pci_disable_msi(board->pDev); + return err; + } + hailo_info(board, "irq enabled %u\n", board->pDev->irq); + + hailo_pcie_enable_interrupts(&board->pcie_resources); + + board->interrupts_enabled = true; + return 0; +} + +void hailo_disable_interrupts(struct hailo_pcie_board *board) +{ + // Sanity Check + if ((NULL == board) || (NULL == board->pDev)) { + pr_err("Failed to access board or device\n"); + return; + } + + if (!board->interrupts_enabled) { + return; + } + + board->interrupts_enabled = false; + hailo_pcie_disable_interrupts(&board->pcie_resources); + free_irq(board->pDev->irq, board); + pci_disable_msi(board->pDev); +} + +static int hailo_bar_iomap(struct pci_dev *pdev, int bar, struct hailo_resource *resource) +{ + resource->size = pci_resource_len(pdev, bar); + resource->address = (uintptr_t)(pci_iomap(pdev, bar, resource->size)); + + if (!resource->size || !resource->address) { + pci_err(pdev, "Probing: Invalid PCIe BAR %d", bar); + return -EINVAL; + } + + pci_notice(pdev, "Probing: mapped bar %d - %p %zu\n", bar, + (void*)resource->address, resource->size); + return 0; +} + +static void hailo_bar_iounmap(struct pci_dev *pdev, struct hailo_resource *resource) +{ + if (resource->address) { + pci_iounmap(pdev, (void*)resource->address); + resource->address = 0; + resource->size = 0; + } +} + +static int pcie_resources_init(struct pci_dev *pdev, struct hailo_pcie_resources *resources, + enum hailo_board_type board_type) +{ + int err = -EINVAL; + if (board_type >= HAILO_BOARD_TYPE_COUNT) { + pci_err(pdev, "Probing: Invalid board type %d\n", (int)board_type); + err = -EINVAL; + goto failure_exit; + } + + err = pci_request_regions(pdev, DRIVER_NAME); + if (err < 0) { + pci_err(pdev, "Probing: Error allocating bars %d\n", err); + goto failure_exit; + } + + err = hailo_bar_iomap(pdev, HAILO_PCIE_CONFIG_BAR, &resources->config); + if (err < 0) { + goto failure_release_regions; + } + + err = hailo_bar_iomap(pdev, HAILO_PCIE_VDMA_REGS_BAR, &resources->vdma_registers); + if (err < 0) { + goto failure_release_config; + } + + err = hailo_bar_iomap(pdev, HAILO_PCIE_FW_ACCESS_BAR, &resources->fw_access); + if (err < 0) { + goto failure_release_vdma_regs; + } + + + if (HAILO_BOARD_TYPE_HAILO10H == board_type){ + if (true == force_hailo10h_legacy_mode) { + board_type = HAILO_BOARD_TYPE_HAILO10H_LEGACY; + } + } + + resources->board_type = board_type; + + err = hailo_set_device_type(resources); + if (err < 0) { + goto failure_release_fw_access; + } + + if (!hailo_pcie_is_device_connected(resources)) { + pci_err(pdev, "Probing: Failed reading device BARs, device may be disconnected\n"); + err = -ENODEV; + goto failure_release_fw_access; + } + + return 0; + +failure_release_fw_access: + hailo_bar_iounmap(pdev, &resources->fw_access); +failure_release_vdma_regs: + hailo_bar_iounmap(pdev, &resources->vdma_registers); +failure_release_config: + hailo_bar_iounmap(pdev, &resources->config); +failure_release_regions: + pci_release_regions(pdev); +failure_exit: + return err; +} + +static void pcie_resources_release(struct pci_dev *pdev, struct hailo_pcie_resources *resources) +{ + hailo_bar_iounmap(pdev, &resources->config); + hailo_bar_iounmap(pdev, &resources->vdma_registers); + hailo_bar_iounmap(pdev, &resources->fw_access); + pci_release_regions(pdev); +} + +static void update_channel_interrupts(struct hailo_vdma_controller *controller, + size_t engine_index, u32 channels_bitmap) +{ + struct hailo_pcie_board *board = (struct hailo_pcie_board*) dev_get_drvdata(controller->dev); + if (engine_index >= board->vdma.vdma_engines_count) { + hailo_err(board, "Invalid engine index %zu", engine_index); + return; + } + + hailo_pcie_update_channel_interrupts_mask(&board->pcie_resources, channels_bitmap); +} + +static struct hailo_vdma_controller_ops pcie_vdma_controller_ops = { + .update_channel_interrupts = update_channel_interrupts, +}; + + +static int hailo_pcie_vdma_controller_init(struct hailo_vdma_controller *controller, + struct device *dev, struct hailo_resource *vdma_registers) +{ + const size_t engines_count = 1; + return hailo_vdma_controller_init(controller, dev, &hailo_pcie_vdma_hw, + &pcie_vdma_controller_ops, vdma_registers, engines_count); +} + +// Tries to check if address allocated with kmalloc is dma capable. +// If kmalloc address is not dma capable we assume other addresses +// won't be dma capable as well. +static bool is_kmalloc_dma_capable(struct device *dev) +{ + void *check_addr = NULL; + dma_addr_t dma_addr = 0; + phys_addr_t phys_addr = 0; + bool capable = false; + + if (!dev->dma_mask) { + return false; + } + + check_addr = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (NULL == check_addr) { + dev_err(dev, "failed allocating page!\n"); + return false; + } + + phys_addr = virt_to_phys(check_addr); + dma_addr = phys_to_dma(dev, phys_addr); + + capable = is_dma_capable(dev, dma_addr, PAGE_SIZE); + kfree(check_addr); + return capable; +} + +static int hailo_get_allocation_mode(struct pci_dev *pdev, enum hailo_allocation_mode *allocation_mode) +{ + // Check if module paramater was given to override driver choice + if (HAILO_NO_FORCE_BUFFER != force_allocation_from_driver) { + if (HAILO_FORCE_BUFFER_FROM_USERSPACE == force_allocation_from_driver) { + *allocation_mode = HAILO_ALLOCATION_MODE_USERSPACE; + pci_notice(pdev, "Probing: Using userspace allocated vdma buffers\n"); + } + else if (HAILO_FORCE_BUFFER_FROM_DRIVER == force_allocation_from_driver) { + *allocation_mode = HAILO_ALLOCATION_MODE_DRIVER; + pci_notice(pdev, "Probing: Using driver allocated vdma buffers\n"); + } + else { + pci_err(pdev, "Invalid value for force allocation driver paramater - value given: %d!\n", + force_allocation_from_driver); + return -EINVAL; + } + + return 0; + } + + if (is_kmalloc_dma_capable(&pdev->dev)) { + *allocation_mode = HAILO_ALLOCATION_MODE_USERSPACE; + pci_notice(pdev, "Probing: Using userspace allocated vdma buffers\n"); + } else { + *allocation_mode = HAILO_ALLOCATION_MODE_DRIVER; + pci_notice(pdev, "Probing: Using driver allocated vdma buffers\n"); + } + + return 0; +} + +static int hailo_pcie_probe(struct pci_dev* pDev, const struct pci_device_id* id) +{ + struct hailo_pcie_board * pBoard; + struct device *char_device = NULL; + int err = -EINVAL; + + pci_notice(pDev, "Probing on: %04x:%04x...\n", pDev->vendor, pDev->device); +#ifdef HAILO_EMULATOR + pci_notice(pDev, "PCIe driver was compiled in emulator mode\n"); +#endif /* HAILO_EMULATOR */ + if (!g_is_power_mode_enabled) { + pci_notice(pDev, "PCIe driver was compiled with power modes disabled\n"); + } + + /* Initialize device extension for the board*/ + pci_notice(pDev, "Probing: Allocate memory for device extension, %zu\n", sizeof(struct hailo_pcie_board)); + pBoard = (struct hailo_pcie_board*) kzalloc( sizeof(struct hailo_pcie_board), GFP_KERNEL); + if (pBoard == NULL) + { + pci_err(pDev, "Probing: Failed to allocate memory for device extension structure\n"); + err = -ENOMEM; + goto probe_exit; + } + + pBoard->pDev = pDev; + + if ( (err = pci_enable_device(pDev)) ) + { + pci_err(pDev, "Probing: Failed calling pci_enable_device %d\n", err); + goto probe_free_board; + } + pci_notice(pDev, "Probing: Device enabled\n"); + + pci_set_master(pDev); + + err = pcie_resources_init(pDev, &pBoard->pcie_resources, id->driver_data); + if (err < 0) { + pci_err(pDev, "Probing: Failed init pcie resources"); + goto probe_disable_device; + } + + err = hailo_get_desc_page_size(pDev, &pBoard->desc_max_page_size); + if (err < 0) { + goto probe_release_pcie_resources; + } + + pBoard->interrupts_enabled = false; + pBoard->fw_boot.is_in_boot = false; + init_completion(&pBoard->fw_boot.fw_loaded_completion); + + sema_init(&pBoard->mutex, 1); + atomic_set(&pBoard->ref_count, 0); + INIT_LIST_HEAD(&pBoard->open_files_list); + + // Init both soc and nnc, since the interrupts are shared. + hailo_nnc_init(&pBoard->nnc); + hailo_soc_init(&pBoard->soc); + + init_completion(&pBoard->driver_down.reset_completed); + init_completion(&pBoard->soft_reset.reset_completed); + + memset(&pBoard->memory_transfer_params, 0, sizeof(pBoard->memory_transfer_params)); + + err = hailo_pcie_vdma_controller_init(&pBoard->vdma, &pBoard->pDev->dev, + &pBoard->pcie_resources.vdma_registers); + if (err < 0) { + hailo_err(pBoard, "Failed init vdma controller %d\n", err); + goto probe_release_pcie_resources; + } + + // Checks the dma mask => it must be called after the device's dma_mask is set by hailo_pcie_vdma_controller_init + err = hailo_get_allocation_mode(pDev, &pBoard->allocation_mode); + if (err < 0) { + pci_err(pDev, "Failed determining allocation of buffers from driver. error type: %d\n", err); + goto probe_release_pcie_resources; + } + + // Initialize the boot channel bitmap to 1 since channel 0 is always used for boot + // (we will always use at least 1 channel which is LSB in the bitmap) + pBoard->fw_boot.boot_used_channel_bitmap = (1 << 0); + memset(&pBoard->fw_boot.boot_dma_state, 0, sizeof(pBoard->fw_boot.boot_dma_state)); + err = hailo_activate_board(pBoard); + if (err < 0) { + hailo_err(pBoard, "Failed activating board %d\n", err); + goto probe_release_pcie_resources; + } + + /* Keep track on the device, in order, to be able to remove it later */ + pci_set_drvdata(pDev, pBoard); + hailo_pcie_insert_board(pBoard); + + /* Create dynamically the device node*/ + char_device = device_create_with_groups(chardev_class, NULL, + MKDEV(char_major, pBoard->board_index), + pBoard, + g_hailo_dev_groups, + DEVICE_NODE_NAME"%d", pBoard->board_index); + if (IS_ERR(char_device)) { + hailo_err(pBoard, "Failed creating dynamic device %d\n", pBoard->board_index); + err = PTR_ERR(char_device); + goto probe_remove_board; + } + + hailo_notice(pBoard, "Probing: Added board %0x-%0x, /dev/hailo%d\n", pDev->vendor, pDev->device, pBoard->board_index); + + return 0; + +probe_remove_board: + hailo_pcie_remove_board(pBoard); + +probe_release_pcie_resources: + pcie_resources_release(pBoard->pDev, &pBoard->pcie_resources); + +probe_disable_device: + pci_disable_device(pDev); + +probe_free_board: + kfree(pBoard); + +probe_exit: + + return err; +} + +static void hailo_pcie_remove(struct pci_dev* pDev) +{ + struct hailo_pcie_board* pBoard = (struct hailo_pcie_board*) pci_get_drvdata(pDev); + + pci_notice(pDev, "Remove: Releasing board\n"); + + if (pBoard) + { + + // lock board to wait for any pending operations and for synchronization with open + down(&pBoard->mutex); + + + // remove board from active boards list + hailo_pcie_remove_board(pBoard); + + + /* Delete the device node */ + device_destroy(chardev_class, MKDEV(char_major, pBoard->board_index)); + + // disable interrupts - will only disable if they have not been disabled in release already + hailo_disable_interrupts(pBoard); + + pcie_resources_release(pBoard->pDev, &pBoard->pcie_resources); + + // deassociate device from board to be picked up by char device + pBoard->pDev = NULL; + + pBoard->vdma.dev = NULL; + + pci_disable_device(pDev); + + pci_set_drvdata(pDev, NULL); + + hailo_nnc_finalize(&pBoard->nnc); + + up(&pBoard->mutex); + + if ( 0 == atomic_read(&pBoard->ref_count) ) + { + // nobody has the board open - free + pci_notice(pDev, "Remove: Freed board, /dev/hailo%d\n", pBoard->board_index); + kfree(pBoard); + } + else + { + // board resources are freed on last close + pci_notice(pDev, "Remove: Scheduled for board removal, /dev/hailo%d\n", pBoard->board_index); + } + } + +} + +inline int driver_down(struct hailo_pcie_board *board) +{ + if (board->pcie_resources.accelerator_type == HAILO_ACCELERATOR_TYPE_NNC) { + return hailo_nnc_driver_down(board); + } else { + return hailo_soc_driver_down(board); + } +} + +#ifdef CONFIG_PM_SLEEP +static int hailo_pcie_suspend(struct device *dev) +{ + struct hailo_pcie_board *board = (struct hailo_pcie_board*) dev_get_drvdata(dev); + struct hailo_file_context *cur = NULL; + int err = 0; + + // lock board to wait for any pending operations + down(&board->mutex); + + if (board->vdma.used_by_filp != NULL) { + err = driver_down(board); + if (err < 0) { + dev_notice(dev, "Error while trying to call FW to close vdma channels\n"); + } + } + + // Disable all interrupts. All interrupts from Hailo chip would be masked. + hailo_disable_interrupts(board); + + // Un validate all activae file contexts so every new action would return error to the user. + list_for_each_entry(cur, &board->open_files_list, open_files_list) { + cur->is_valid = false; + } + + // Release board + up(&board->mutex); + + dev_notice(dev, "PM's suspend\n"); + // Success Oriented - Continue system suspend even in case of error (otherwise system will not suspend correctly) + return 0; +} + +static int hailo_pcie_resume(struct device *dev) +{ + struct hailo_pcie_board *board = (struct hailo_pcie_board*) dev_get_drvdata(dev); + int err = 0; + + if ((err = hailo_activate_board(board)) < 0) { + dev_err(dev, "Failed activating board %d\n", err); + } + + dev_notice(dev, "PM's resume\n"); + // Success Oriented - Continue system resume even in case of error (otherwise system will not suspend correctly) + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +static SIMPLE_DEV_PM_OPS(hailo_pcie_pm_ops, hailo_pcie_suspend, hailo_pcie_resume); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION( 3, 16, 0 ) +static void hailo_pci_reset_prepare(struct pci_dev *pdev) +{ + struct hailo_pcie_board* board = (struct hailo_pcie_board*) pci_get_drvdata(pdev); + int err = 0; + /* Reset preparation logic goes here */ + pci_err(pdev, "Reset preparation for PCI device \n"); + + if (board) + { + // lock board to wait for any pending operations and for synchronization with open + down(&board->mutex); + if (board->vdma.used_by_filp != NULL) { + // Try to close all vDMA channels before reset + err = driver_down(board); + if (err < 0) { + pci_err(pdev, "Error while trying to call FW to close vdma channels (errno %d)\n", err); + } + } + up(&board->mutex); + } +} +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION( 3, 16, 0 ) */ + +#if LINUX_VERSION_CODE < KERNEL_VERSION( 4, 13, 0 ) && LINUX_VERSION_CODE >= KERNEL_VERSION( 3, 16, 0 ) +static void hailo_pci_reset_notify(struct pci_dev *pdev, bool prepare) +{ + if (prepare) { + hailo_pci_reset_prepare(pdev); + } +} +#endif + +static const struct pci_error_handlers hailo_pcie_err_handlers = { +#if LINUX_VERSION_CODE < KERNEL_VERSION( 3, 16, 0 ) +/* No FLR callback */ +#elif LINUX_VERSION_CODE < KERNEL_VERSION( 4, 13, 0 ) +/* FLR Callback is reset_notify */ + .reset_notify = hailo_pci_reset_notify, +#else +/* FLR Callback is reset_prepare */ + .reset_prepare = hailo_pci_reset_prepare, +#endif +}; + +static struct pci_device_id hailo_pcie_id_table[] = +{ + {PCI_DEVICE_DATA(HAILO, HAILO8, HAILO_BOARD_TYPE_HAILO8)}, + {PCI_DEVICE_DATA(HAILO, HAILO10H, HAILO_BOARD_TYPE_HAILO10H)}, + {PCI_DEVICE_DATA(HAILO, HAILO15L, HAILO_BOARD_TYPE_HAILO15L)}, + {0,0,0,0,0,0,0 }, +}; + +static struct file_operations hailo_pcie_fops = +{ + owner: THIS_MODULE, + unlocked_ioctl: hailo_pcie_fops_unlockedioctl, + mmap: hailo_pcie_fops_mmap, + open: hailo_pcie_fops_open, + release: hailo_pcie_fops_release +}; + + +static struct pci_driver hailo_pci_driver = +{ + name: DRIVER_NAME, + id_table: hailo_pcie_id_table, + probe: hailo_pcie_probe, + remove: hailo_pcie_remove, + driver: { + pm: &hailo_pcie_pm_ops, + }, + err_handler: &hailo_pcie_err_handlers, +}; + +MODULE_DEVICE_TABLE (pci, hailo_pcie_id_table); + +static int hailo_pcie_register_chrdev(unsigned int major, const char *name) +{ + int char_major; + + char_major = register_chrdev(major, name, &hailo_pcie_fops); + + chardev_class = class_create_compat("hailo_chardev"); + + return char_major; +} + +static void hailo_pcie_unregister_chrdev(unsigned int major, const char *name) +{ + class_destroy(chardev_class); + unregister_chrdev(major, name); +} + +static int __init hailo_pcie_module_init(void) +{ + int err; + + pr_notice(DRIVER_NAME ": Init module. driver version %s\n", HAILO_DRV_VER); + + if ( 0 > (char_major = hailo_pcie_register_chrdev(0, DRIVER_NAME)) ) + { + pr_err(DRIVER_NAME ": Init Error, failed to call register_chrdev.\n"); + + return char_major; + } + + if ( 0 != (err = pci_register_driver(&hailo_pci_driver))) + { + pr_err(DRIVER_NAME ": Init Error, failed to call pci_register_driver.\n"); + class_destroy(chardev_class); + hailo_pcie_unregister_chrdev(char_major, DRIVER_NAME); + return err; + } + + return 0; +} + +static void __exit hailo_pcie_module_exit(void) +{ + + pr_notice(DRIVER_NAME ": Exit module.\n"); + + // Unregister the driver from pci bus + pci_unregister_driver(&hailo_pci_driver); + hailo_pcie_unregister_chrdev(char_major, DRIVER_NAME); + + pr_notice(DRIVER_NAME ": Hailo PCIe driver unloaded.\n"); +} + + +module_init(hailo_pcie_module_init); +module_exit(hailo_pcie_module_exit); + +module_param(o_dbg, int, S_IRUGO | S_IWUSR); + +module_param_named(no_power_mode, g_is_power_mode_enabled, invbool, S_IRUGO); +MODULE_PARM_DESC(no_power_mode, "Disables automatic D0->D3 PCIe transactions"); + +module_param(force_allocation_from_driver, int, S_IRUGO); +MODULE_PARM_DESC(force_allocation_from_driver, "Determines whether to force buffer allocation from driver or userspace"); + +module_param(force_desc_page_size, int, S_IRUGO); +MODULE_PARM_DESC(force_desc_page_size, "Determines the maximum DMA descriptor page size (must be a power of 2)"); + +module_param(force_hailo10h_legacy_mode, bool, S_IRUGO); +MODULE_PARM_DESC(force_hailo10h_legacy_mode, "Forces work with Hailo10h in legacy mode(relevant for emulators)"); + +module_param(force_boot_linux_from_eemc, bool, S_IRUGO); +MODULE_PARM_DESC(force_boot_linux_from_eemc, "Boot the linux image from eemc (Requires special Image)"); + +module_param(support_soft_reset, bool, S_IRUGO); +MODULE_PARM_DESC(support_soft_reset, "enables driver reload to reload a new firmware as well"); + +MODULE_AUTHOR("Hailo Technologies Ltd."); +MODULE_DESCRIPTION("Hailo PCIe driver"); +MODULE_LICENSE("GPL v2"); +MODULE_VERSION(HAILO_DRV_VER); + diff --git a/drivers/media/pci/hailo/src/pcie.h b/drivers/media/pci/hailo/src/pcie.h new file mode 100644 index 00000000000000..b90cbb6b2268e2 --- /dev/null +++ b/drivers/media/pci/hailo/src/pcie.h @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved. + **/ + +#ifndef _HAILO_PCI_PCIE_H_ +#define _HAILO_PCI_PCIE_H_ + +#include "vdma/vdma.h" +#include "hailo_ioctl_common.h" +#include "pcie_common.h" +#include "utils/fw_common.h" + +#include <linux/pci.h> +#include <linux/fs.h> +#include <linux/interrupt.h> +#include <linux/circ_buf.h> +#include <linux/device.h> + +#include <linux/ioctl.h> + +#define HAILO_PCI_OVER_VDMA_NUM_CHANNELS (8) +#define HAILO_PCI_OVER_VDMA_PAGE_SIZE (512) + +struct hailo_fw_control_info { + // protects that only one fw control will be send at a time + struct semaphore mutex; + // called from the interrupt handler to notify that a response is ready + struct completion completion; + // the command we are currently handling + struct hailo_fw_control command; +}; + +struct hailo_pcie_driver_down_info { + // called from the interrupt handler to notify that FW completed reset + struct completion reset_completed; +}; + +struct hailo_pcie_soft_reset { + // called from the interrupt handler to notify that FW completed reset + struct completion reset_completed; +}; + +struct hailo_fw_boot { + // the filp that enabled interrupts for fw boot. the interrupt is enabled if this is not null + struct file *filp; + // called from the interrupt handler to notify that an interrupt was raised + struct completion completion; +}; + + +struct hailo_pcie_nnc { + struct hailo_fw_control_info fw_control; + + spinlock_t notification_read_spinlock; + struct list_head notification_wait_list; + struct hailo_d2h_notification notification_cache; + struct hailo_d2h_notification notification_to_user; +}; + +struct hailo_pcie_soc { + struct completion control_resp_ready; +}; + +// Context for each open file handle +// TODO: store board and use as actual context +struct hailo_file_context { + struct list_head open_files_list; + struct file *filp; + struct hailo_vdma_file_context vdma_context; + bool is_valid; + u32 soc_used_channels_bitmap; +}; + +struct hailo_pcie_boot_dma_channel_state { + struct hailo_descriptors_list_buffer host_descriptors_buffer; + struct hailo_descriptors_list_buffer device_descriptors_buffer; + struct sg_table sg_table; + u64 buffer_size; + void *kernel_addrs; + u32 desc_program_num; +}; + +struct hailo_pcie_boot_dma_state { + struct hailo_pcie_boot_dma_channel_state channels[HAILO_PCI_OVER_VDMA_NUM_CHANNELS]; + u8 curr_channel_index; +}; + +struct hailo_pcie_fw_boot { + struct hailo_pcie_boot_dma_state boot_dma_state; + // is_in_boot is set to true when the board is in boot mode + bool is_in_boot; + // boot_used_channel_bitmap is a bitmap of the channels that are used for boot + u16 boot_used_channel_bitmap; + // fw_loaded_completion is used to notify that the FW was loaded - SOC & NNC + struct completion fw_loaded_completion; + // vdma_boot_completion is used to notify that the vDMA boot data was transferred completely on all used channels for boot + struct completion vdma_boot_completion; +}; + +struct hailo_pcie_board { + struct list_head board_list; + struct pci_dev *pDev; + u32 board_index; + atomic_t ref_count; + struct list_head open_files_list; + struct hailo_pcie_resources pcie_resources; + struct hailo_pcie_nnc nnc; + struct hailo_pcie_soc soc; + struct hailo_pcie_driver_down_info driver_down; + struct hailo_pcie_soft_reset soft_reset; + struct semaphore mutex; + struct hailo_vdma_controller vdma; + + struct hailo_pcie_fw_boot fw_boot; + + struct hailo_memory_transfer_params memory_transfer_params; + u32 desc_max_page_size; + enum hailo_allocation_mode allocation_mode; + bool interrupts_enabled; +}; + +bool power_mode_enabled(void); + +struct hailo_pcie_board* hailo_pcie_get_board_index(u32 index); +void hailo_disable_interrupts(struct hailo_pcie_board *board); +int hailo_enable_interrupts(struct hailo_pcie_board *board); +int hailo_pcie_soft_reset(struct hailo_pcie_resources *resources, struct completion *reset_completed); + +#endif /* _HAILO_PCI_PCIE_H_ */ + diff --git a/drivers/media/pci/hailo/src/soc.c b/drivers/media/pci/hailo/src/soc.c new file mode 100644 index 00000000000000..064567ce406aef --- /dev/null +++ b/drivers/media/pci/hailo/src/soc.c @@ -0,0 +1,244 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved. + **/ +/** + * A Hailo PCIe NNC device is a device contains a full SoC over PCIe. The SoC contains NNC (neural network core) and + * some application processor (pci_ep). + */ + +#include "soc.h" + +#include "vdma_common.h" +#include "utils/logs.h" +#include "vdma/memory.h" +#include "pcie_common.h" + +#include <linux/uaccess.h> + +#ifndef HAILO_EMULATOR +#define PCI_SOC_CONTROL_CONNECT_TIMEOUT_MS (1000) +#else +#define PCI_SOC_CONTROL_CONNECT_TIMEOUT_MS (1000000) +#endif /* ifndef HAILO_EMULATOR */ + +void hailo_soc_init(struct hailo_pcie_soc *soc) +{ + init_completion(&soc->control_resp_ready); +} + +long hailo_soc_ioctl(struct hailo_pcie_board *board, struct hailo_file_context *context, + struct hailo_vdma_controller *controller, unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + case HAILO_SOC_CONNECT: + return hailo_soc_connect_ioctl(board, context, controller, arg); + case HAILO_SOC_CLOSE: + return hailo_soc_close_ioctl(board, controller, context, arg); + default: + hailo_err(board, "Invalid pcie EP ioctl code 0x%x (nr: %d)\n", cmd, _IOC_NR(cmd)); + return -ENOTTY; + } +} + +static int soc_control(struct hailo_pcie_board *board, + const struct hailo_pcie_soc_request *request, + struct hailo_pcie_soc_response *response) +{ + int ret = 0; + reinit_completion(&board->soc.control_resp_ready); + + hailo_pcie_soc_write_request(&board->pcie_resources, request); + + ret = wait_for_completion_interruptible_timeout(&board->soc.control_resp_ready, + msecs_to_jiffies(PCI_SOC_CONTROL_CONNECT_TIMEOUT_MS)); + if (ret <= 0) { + if (0 == ret) { + hailo_err(board, "Timeout waiting for soc control (timeout_ms=%d)\n", PCI_SOC_CONTROL_CONNECT_TIMEOUT_MS); + return -ETIMEDOUT; + } else { + hailo_info(board, "soc control failed with err=%d (process was interrupted or killed)\n", + ret); + return ret; + } + } + + hailo_pcie_soc_read_response(&board->pcie_resources, response); + + if (response->status < 0) { + hailo_err(board, "soc control failed with status=%d\n", response->status); + return response->status; + } + + if (response->control_code != request->control_code) { + hailo_err(board, "Invalid response control code %d (expected %d)\n", + response->control_code, request->control_code); + return -EINVAL; + } + + return 0; +} + +long hailo_soc_connect_ioctl(struct hailo_pcie_board *board, struct hailo_file_context *context, + struct hailo_vdma_controller *controller, unsigned long arg) +{ + struct hailo_pcie_soc_request request = {0}; + struct hailo_pcie_soc_response response = {0}; + struct hailo_soc_connect_params params; + struct hailo_vdma_channel *input_channel = NULL; + struct hailo_vdma_channel *output_channel = NULL; + struct hailo_vdma_engine *vdma_engine = &controller->vdma_engines[PCI_VDMA_ENGINE_INDEX]; + struct hailo_descriptors_list_buffer *input_descriptors_buffer = NULL; + struct hailo_descriptors_list_buffer *output_descriptors_buffer = NULL; + int err = 0; + + if (copy_from_user(¶ms, (void *)arg, sizeof(params))) { + hailo_err(board, "copy_from_user fail\n"); + return -ENOMEM; + } + + request = (struct hailo_pcie_soc_request) { + .control_code = HAILO_PCIE_SOC_CONTROL_CODE_CONNECT, + .connect = { + .port = params.port_number + } + }; + err = soc_control(board, &request, &response); + if (err < 0) { + return err; + } + + params.input_channel_index = response.connect.input_channel_index; + params.output_channel_index = response.connect.output_channel_index; + + if (!hailo_check_channel_index(params.input_channel_index, controller->hw->src_channels_bitmask, true)) { + hailo_dev_err(&board->pDev->dev, "Invalid input channel index %u\n", params.input_channel_index); + return -EINVAL; + } + + if (!hailo_check_channel_index(params.output_channel_index, controller->hw->src_channels_bitmask, false)) { + hailo_dev_err(&board->pDev->dev, "Invalid output channel index %u\n", params.output_channel_index); + return -EINVAL; + } + + input_channel = &vdma_engine->channels[params.input_channel_index]; + output_channel = &vdma_engine->channels[params.output_channel_index]; + + input_descriptors_buffer = hailo_vdma_find_descriptors_buffer(&context->vdma_context, params.input_desc_handle); + output_descriptors_buffer = hailo_vdma_find_descriptors_buffer(&context->vdma_context, params.output_desc_handle); + if (NULL == input_descriptors_buffer || NULL == output_descriptors_buffer) { + hailo_dev_err(&board->pDev->dev, "input / output descriptors buffer not found \n"); + return -EINVAL; + } + + if (!is_powerof2((size_t)input_descriptors_buffer->desc_list.desc_count) || + !is_powerof2((size_t)output_descriptors_buffer->desc_list.desc_count)) { + hailo_dev_err(&board->pDev->dev, "Invalid desc list size\n"); + return -EINVAL; + } + + // configure and start input channel + // DMA Direction is only to get channel index - so + err = hailo_vdma_start_channel(input_channel->host_regs, input_descriptors_buffer->dma_address, input_descriptors_buffer->desc_list.desc_count, + board->vdma.hw->ddr_data_id); + if (err < 0) { + hailo_dev_err(&board->pDev->dev, "Error starting vdma input channel index %u\n", params.input_channel_index); + return -EINVAL; + } + + // Store the input channels state in bitmap (open) + hailo_set_bit(params.input_channel_index, &context->soc_used_channels_bitmap); + + // configure and start output channel + // DMA Direction is only to get channel index - so + err = hailo_vdma_start_channel(output_channel->host_regs, output_descriptors_buffer->dma_address, output_descriptors_buffer->desc_list.desc_count, + board->vdma.hw->ddr_data_id); + if (err < 0) { + hailo_dev_err(&board->pDev->dev, "Error starting vdma output channel index %u\n", params.output_channel_index); + // Close input channel + hailo_vdma_stop_channel(input_channel->host_regs); + return -EINVAL; + } + + // Store the output channels state in bitmap (open) + hailo_set_bit(params.output_channel_index, &context->soc_used_channels_bitmap); + + if (copy_to_user((void *)arg, ¶ms, sizeof(params))) { + hailo_dev_err(&board->pDev->dev, "copy_to_user fail\n"); + return -ENOMEM; + } + + return 0; +} + +static int close_channels(struct hailo_pcie_board *board, u32 channels_bitmap) +{ + struct hailo_pcie_soc_request request = {0}; + struct hailo_pcie_soc_response response = {0}; + struct hailo_vdma_engine *engine = &board->vdma.vdma_engines[PCI_VDMA_ENGINE_INDEX]; + struct hailo_vdma_channel *channel = NULL; + u8 channel_index = 0; + + hailo_info(board, "Closing channels bitmap 0x%x\n", channels_bitmap); + for_each_vdma_channel(engine, channel, channel_index) { + if (hailo_test_bit(channel_index, &channels_bitmap)) { + hailo_vdma_stop_channel(channel->host_regs); + } + } + + request = (struct hailo_pcie_soc_request) { + .control_code = HAILO_PCIE_SOC_CONTROL_CODE_CLOSE, + .close = { + .channels_bitmap = channels_bitmap + } + }; + return soc_control(board, &request, &response); +} + +long hailo_soc_close_ioctl(struct hailo_pcie_board *board, struct hailo_vdma_controller *controller, + struct hailo_file_context *context, unsigned long arg) +{ + struct hailo_soc_close_params params; + u32 channels_bitmap = 0; + int err = 0; + + if (copy_from_user(¶ms, (void *)arg, sizeof(params))) { + hailo_dev_err(&board->pDev->dev, "copy_from_user fail\n"); + return -ENOMEM; + } + + // TOOD: check channels are connected + + channels_bitmap = (1 << params.input_channel_index) | (1 << params.output_channel_index); + + err = close_channels(board, channels_bitmap); + if (0 != err) { + hailo_dev_err(&board->pDev->dev, "Error closing channels\n"); + return err; + } + + // Store the channel state in bitmap (closed) + hailo_clear_bit(params.input_channel_index, &context->soc_used_channels_bitmap); + hailo_clear_bit(params.output_channel_index, &context->soc_used_channels_bitmap); + + return err; +} + +int hailo_soc_file_context_init(struct hailo_pcie_board *board, struct hailo_file_context *context) +{ + // Nothing to init yet + return 0; +} + +void hailo_soc_file_context_finalize(struct hailo_pcie_board *board, struct hailo_file_context *context) +{ + // close only channels connected by this (by bitmap) + if (context->soc_used_channels_bitmap != 0) { + close_channels(board, context->soc_used_channels_bitmap); + } +} + +int hailo_soc_driver_down(struct hailo_pcie_board *board) +{ + return close_channels(board, 0xFFFFFFFF); +} \ No newline at end of file diff --git a/drivers/media/pci/hailo/src/soc.h b/drivers/media/pci/hailo/src/soc.h new file mode 100644 index 00000000000000..6b2e64deb30011 --- /dev/null +++ b/drivers/media/pci/hailo/src/soc.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved. + **/ + +#ifndef _HAILO_PCI_SOC_IOCTL_H_ +#define _HAILO_PCI_SOC_IOCTL_H_ + +#include "vdma/ioctl.h" +#include "pcie.h" + + +void hailo_soc_init(struct hailo_pcie_soc *soc); + +long hailo_soc_ioctl(struct hailo_pcie_board *board, struct hailo_file_context *context, + struct hailo_vdma_controller *controller, unsigned int cmd, unsigned long arg); +long hailo_soc_connect_ioctl(struct hailo_pcie_board *board, struct hailo_file_context *context, + struct hailo_vdma_controller *controller, unsigned long arg); +long hailo_soc_close_ioctl(struct hailo_pcie_board *board, struct hailo_vdma_controller *controller, struct hailo_file_context *context, unsigned long arg); + +int hailo_soc_file_context_init(struct hailo_pcie_board *board, struct hailo_file_context *context); +void hailo_soc_file_context_finalize(struct hailo_pcie_board *board, struct hailo_file_context *context); + +int hailo_soc_driver_down(struct hailo_pcie_board *board); + +#endif // _HAILO_PCI_SOC_IOCTL_H_ \ No newline at end of file diff --git a/drivers/media/pci/hailo/src/sysfs.c b/drivers/media/pci/hailo/src/sysfs.c new file mode 100644 index 00000000000000..67bccac5e097c1 --- /dev/null +++ b/drivers/media/pci/hailo/src/sysfs.c @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved. + **/ + +#include "sysfs.h" +#include "pcie.h" + +#include <linux/device.h> +#include <linux/sysfs.h> + +static ssize_t board_location_show(struct device *dev, struct device_attribute *_attr, + char *buf) +{ + struct hailo_pcie_board *board = (struct hailo_pcie_board *)dev_get_drvdata(dev); + const char *dev_info = pci_name(board->pDev); + return sprintf(buf, "%s", dev_info); +} +static DEVICE_ATTR_RO(board_location); + +static ssize_t device_id_show(struct device *dev, struct device_attribute *_attr, + char *buf) +{ + struct hailo_pcie_board *board = (struct hailo_pcie_board *)dev_get_drvdata(dev); + return sprintf(buf, "%x:%x", board->pDev->vendor, board->pDev->device); +} +static DEVICE_ATTR_RO(device_id); + +static ssize_t accelerator_type_show(struct device *dev, struct device_attribute *_attr, + char *buf) +{ + struct hailo_pcie_board *board = (struct hailo_pcie_board *)dev_get_drvdata(dev); + return sprintf(buf, "%d", board->pcie_resources.accelerator_type); +} +static DEVICE_ATTR_RO(accelerator_type); + +static struct attribute *hailo_dev_attrs[] = { + &dev_attr_board_location.attr, + &dev_attr_device_id.attr, + &dev_attr_accelerator_type.attr, + NULL +}; + +ATTRIBUTE_GROUPS(hailo_dev); +const struct attribute_group **g_hailo_dev_groups = hailo_dev_groups; diff --git a/drivers/media/pci/hailo/src/sysfs.h b/drivers/media/pci/hailo/src/sysfs.h new file mode 100644 index 00000000000000..135fb37f794286 --- /dev/null +++ b/drivers/media/pci/hailo/src/sysfs.h @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved. + **/ + +#ifndef _HAILO_PCI_SYSFS_H_ +#define _HAILO_PCI_SYSFS_H_ + +#include <linux/sysfs.h> + +extern const struct attribute_group **g_hailo_dev_groups; + +#endif /* _HAILO_PCI_SYSFS_H_ */ diff --git a/drivers/media/pci/hailo/src/utils.h b/drivers/media/pci/hailo/src/utils.h new file mode 100644 index 00000000000000..b357150086c70c --- /dev/null +++ b/drivers/media/pci/hailo/src/utils.h @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * Copyright (c) 2019-2022 Hailo Technologies Ltd. All rights reserved. + **/ + +#ifndef _HAILO_PCI_UTILS_H_ +#define _HAILO_PCI_UTILS_H_ + +#include <linux/version.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/interrupt.h> +#include <linux/sched.h> +#include <linux/pagemap.h> + +#include "pcie.h" + +void hailo_pcie_clear_notification_wait_list(struct hailo_pcie_board *pBoard, struct file *filp); + +#endif /* _HAILO_PCI_UTILS_H_ */ diff --git a/drivers/media/pci/hailo/utils/compact.h b/drivers/media/pci/hailo/utils/compact.h new file mode 100644 index 00000000000000..81d0dd5c053174 --- /dev/null +++ b/drivers/media/pci/hailo/utils/compact.h @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved. + **/ + +#ifndef _HAILO_PCI_COMPACT_H_ +#define _HAILO_PCI_COMPACT_H_ + +#include <linux/version.h> +#include <linux/scatterlist.h> +#include <linux/vmalloc.h> + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0) +#define class_create_compat class_create +#else +#define class_create_compat(name) class_create(THIS_MODULE, name) +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 16, 0) +#define pci_printk(level, pdev, fmt, arg...) \ + dev_printk(level, &(pdev)->dev, fmt, ##arg) +#define pci_emerg(pdev, fmt, arg...) dev_emerg(&(pdev)->dev, fmt, ##arg) +#define pci_alert(pdev, fmt, arg...) dev_alert(&(pdev)->dev, fmt, ##arg) +#define pci_crit(pdev, fmt, arg...) dev_crit(&(pdev)->dev, fmt, ##arg) +#define pci_err(pdev, fmt, arg...) dev_err(&(pdev)->dev, fmt, ##arg) +#define pci_warn(pdev, fmt, arg...) dev_warn(&(pdev)->dev, fmt, ##arg) +#define pci_notice(pdev, fmt, arg...) dev_notice(&(pdev)->dev, fmt, ##arg) +#define pci_info(pdev, fmt, arg...) dev_info(&(pdev)->dev, fmt, ##arg) +#define pci_dbg(pdev, fmt, arg...) dev_dbg(&(pdev)->dev, fmt, ##arg) +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 5, 0) +#define get_user_pages_compact get_user_pages +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0) +#define get_user_pages_compact(start, nr_pages, gup_flags, pages) \ + get_user_pages(start, nr_pages, gup_flags, pages, NULL) +#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 168)) && (LINUX_VERSION_CODE < KERNEL_VERSION(4, 5, 0)) +#define get_user_pages_compact(start, nr_pages, gup_flags, pages) \ + get_user_pages(current, current->mm, start, nr_pages, gup_flags, pages, NULL) +#else +static inline long get_user_pages_compact(unsigned long start, unsigned long nr_pages, + unsigned int gup_flags, struct page **pages) +{ + int write = !!((gup_flags & FOLL_WRITE) == FOLL_WRITE); + int force = !!((gup_flags & FOLL_FORCE) == FOLL_FORCE); + return get_user_pages(current, current->mm, start, nr_pages, write, force, + pages, NULL); +} +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 8, 0) +static inline void dma_sync_sgtable_for_device(struct device *dev, + struct sg_table *sgt, enum dma_data_direction dir) +{ + dma_sync_sg_for_device(dev, sgt->sgl, sgt->orig_nents, dir); +} +#endif + +#ifndef _LINUX_MMAP_LOCK_H +static inline void mmap_read_lock(struct mm_struct *mm) +{ + down_read(&mm->mmap_sem); +} + +static inline void mmap_read_unlock(struct mm_struct *mm) +{ + up_read(&mm->mmap_sem); +} +#endif /* _LINUX_MMAP_LOCK_H */ + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0) && LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0) +#define sg_alloc_table_from_pages_segment_compat __sg_alloc_table_from_pages +#else +static inline struct scatterlist *sg_alloc_table_from_pages_segment_compat(struct sg_table *sgt, + struct page **pages, unsigned int n_pages, unsigned int offset, + unsigned long size, unsigned int max_segment, + struct scatterlist *prv, unsigned int left_pages, + gfp_t gfp_mask) +{ + int res = 0; + + if (NULL != prv) { + // prv not suported + return ERR_PTR(-EINVAL); + } + + if (0 != left_pages) { + // Left pages not supported + return ERR_PTR(-EINVAL); + } + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0) + res = sg_alloc_table_from_pages_segment(sgt, pages, n_pages, offset, size, max_segment, gfp_mask); +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 15, 0) + res = __sg_alloc_table_from_pages(sgt, pages, n_pages, offset, size, max_segment, gfp_mask); +#else + res = sg_alloc_table_from_pages(sgt, pages, n_pages, offset, size, gfp_mask); +#endif + if (res < 0) { + return ERR_PTR(res); + } + + return sgt->sgl; +} +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION( 5, 0, 0 ) +#define compatible_access_ok(a,b,c) access_ok(b, c) +#else +#define compatible_access_ok(a,b,c) access_ok(a, b, c) +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 19, 0) +#define PCI_DEVICE_DATA(vend, dev, data) \ + .vendor = PCI_VENDOR_ID_##vend, .device = PCI_DEVICE_ID_##vend##_##dev, \ + .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, 0, 0, \ + .driver_data = (kernel_ulong_t)(data) +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 12, 0) +// On kernels < 4.1.12, kvmalloc, kvfree is not implemented. For simplicity, instead of implement our own +// kvmalloc/kvfree, just using vmalloc and vfree (It may reduce allocate/access performance, but it worth it). +static inline void *kvmalloc_array(size_t n, size_t size, gfp_t flags) +{ + (void)flags; //ignore + return vmalloc(n * size); +} + +#define kvfree vfree +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) +static inline bool is_dma_capable(struct device *dev, dma_addr_t dma_addr, size_t size) +{ +// Case for Rasberry Pie kernel versions 5.4.83 <=> 5.5.0 - already changed bus_dma_mask -> bus_dma_limit +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 5, 0)) || (defined(HAILO_RASBERRY_PIE) && LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 83)) + const u64 bus_dma_limit = dev->bus_dma_limit; +#else + const u64 bus_dma_limit = dev->bus_dma_mask; +#endif + + return (dma_addr <= min_not_zero(*dev->dma_mask, bus_dma_limit)); +} +#else +static inline bool is_dma_capable(struct device *dev, dma_addr_t dma_addr, size_t size) +{ + // Implementation of dma_capable from linux kernel + const u64 bus_dma_limit = (*dev->dma_mask + 1) & ~(*dev->dma_mask); + if (bus_dma_limit && size > bus_dma_limit) { + return false; + } + + if ((dma_addr | (dma_addr + size - 1)) & ~(*dev->dma_mask)) { + return false; + } + + return true; +} +#endif // LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) + +#endif /* _HAILO_PCI_COMPACT_H_ */ \ No newline at end of file diff --git a/drivers/media/pci/hailo/utils/fw_common.h b/drivers/media/pci/hailo/utils/fw_common.h new file mode 100644 index 00000000000000..5f61cf3f07399f --- /dev/null +++ b/drivers/media/pci/hailo/utils/fw_common.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved. + **/ + +#ifndef _HAILO_LINUX_COMMON_H_ +#define _HAILO_LINUX_COMMON_H_ + +#include "hailo_ioctl_common.h" + +struct hailo_notification_wait { + struct list_head notification_wait_list; + int tgid; + struct file* filp; + struct completion notification_completion; + bool is_disabled; +}; + +#endif /* _HAILO_LINUX_COMMON_H_ */ \ No newline at end of file diff --git a/drivers/media/pci/hailo/utils/integrated_nnc_utils.c b/drivers/media/pci/hailo/utils/integrated_nnc_utils.c new file mode 100755 index 00000000000000..4b717e42b4e982 --- /dev/null +++ b/drivers/media/pci/hailo/utils/integrated_nnc_utils.c @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved. + **/ + +#include "integrated_nnc_utils.h" +#include "utils/logs.h" + +#include <linux/uaccess.h> +#include <asm/io.h> +#include <linux/of_address.h> +#include <linux/cdev.h> + +int hailo_ioremap_resource(struct platform_device *pdev, struct hailo_resource *resource, + const char *name) +{ + void __iomem *address; + struct resource *platform_resource = platform_get_resource_byname(pdev, IORESOURCE_MEM, name); + if (NULL == platform_resource) { + return -ENOENT; + } + + address = devm_ioremap_resource(&pdev->dev, platform_resource); + if (IS_ERR(address)) { + return PTR_ERR(address); + } + + resource->address = (uintptr_t)address; + resource->size = resource_size(platform_resource); + + hailo_dev_dbg(&pdev->dev, "resource[%s]: remap %pr of %zx bytes to virtual start address %lx\n", + platform_resource->name, platform_resource, resource->size, (uintptr_t)address); + + return 0; +} + +// TODO: HRT-8475 - change to name instead of index +int hailo_ioremap_shmem(struct platform_device *pdev, int index, struct hailo_resource *resource) +{ + int ret; + struct resource res; + struct device_node *shmem; + void __iomem * remap_ptr; + + shmem = of_parse_phandle(pdev->dev.of_node, "shmem", index); + if (!shmem) { + hailo_dev_err(&pdev->dev, "Failed to find shmem node index: %d in device tree\n", index); + return -ENODEV; + } + + ret = of_address_to_resource(shmem, 0, &res); + if (ret) { + hailo_dev_err(&pdev->dev, "hailo_ioremap_shmem, failed to get memory (index: %d)\n", index); + of_node_put(shmem); + return ret; + } + + // Decrement the refcount of the node + of_node_put(shmem); + + remap_ptr = devm_ioremap(&pdev->dev, res.start, resource_size(&res)); + if (!remap_ptr) { + hailo_dev_err(&pdev->dev, "hailo_ioremap_shmem, failed to ioremap shmem (index: %d)\n", index); + return -EADDRNOTAVAIL; + } + + resource->address = (uintptr_t)remap_ptr; + resource->size = resource_size(&res); + + return 0; +} + +int direct_memory_transfer(struct platform_device *pdev, struct hailo_memory_transfer_params *params) +{ + int err = -EINVAL; + void __iomem *mem = ioremap(params->address, params->count); + if (NULL == mem) { + hailo_dev_err(&pdev->dev, "Failed ioremap %llu %zu\n", params->address, params->count); + return -ENOMEM; + } + + switch (params->transfer_direction) { + case TRANSFER_READ: + memcpy_fromio(params->buffer, mem, params->count); + err = 0; + break; + case TRANSFER_WRITE: + memcpy_toio(mem, params->buffer, params->count); + err = 0; + break; + default: + hailo_dev_err(&pdev->dev, "Invalid transfer direction %d\n", (int)params->transfer_direction); + err = -EINVAL; + } + + iounmap(mem); + return err; +} + +int hailo_get_resource_physical_addr(struct platform_device *pdev, const char *name, u64 *address) +{ + struct resource *platform_resource = platform_get_resource_byname(pdev, IORESOURCE_MEM, name); + if (NULL == platform_resource) { + return -ENOENT; + } + + *address = (u64)(platform_resource->start); + return 0; +} \ No newline at end of file diff --git a/drivers/media/pci/hailo/utils/integrated_nnc_utils.h b/drivers/media/pci/hailo/utils/integrated_nnc_utils.h new file mode 100755 index 00000000000000..4ec23d8ce3f980 --- /dev/null +++ b/drivers/media/pci/hailo/utils/integrated_nnc_utils.h @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved. + **/ + +#ifndef _INTEGRATED_NNC_UTILS_H_ +#define _INTEGRATED_NNC_UTILS_H_ + +#include <linux/platform_device.h> +#include "hailo_resource.h" + +#define HAILO15_CORE_CONTROL_MAILBOX_INDEX (0) +#define HAILO15_CORE_NOTIFICATION_MAILBOX_INDEX (1) +#define HAILO15_CORE_DRIVER_DOWN_MAILBOX_INDEX (2) + +#define HAILO15_CORE_CONTROL_MAILBOX_TX_SHMEM_INDEX (0) +#define HAILO15_CORE_CONTROL_MAILBOX_RX_SHMEM_INDEX (1) +#define HAILO15_CORE_NOTIFICATION_MAILBOX_RX_SHMEM_INDEX (2) + +int hailo_ioremap_resource(struct platform_device *pdev, struct hailo_resource *resource, + const char *name); + +// TODO: HRT-8475 - change to name instead of index +int hailo_ioremap_shmem(struct platform_device *pdev, int index, struct hailo_resource *resource); + +int direct_memory_transfer(struct platform_device *pDev, struct hailo_memory_transfer_params *params); + +int hailo_get_resource_physical_addr(struct platform_device *pdev, const char *name, u64 *address); + +#endif /* _INTEGRATED_NNC_UTILS_H_ */ diff --git a/drivers/media/pci/hailo/utils/logs.c b/drivers/media/pci/hailo/utils/logs.c new file mode 100644 index 00000000000000..3787fe1ffd0666 --- /dev/null +++ b/drivers/media/pci/hailo/utils/logs.c @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved. + **/ + +#include "logs.h" + +int o_dbg = LOGLEVEL_NOTICE; diff --git a/drivers/media/pci/hailo/utils/logs.h b/drivers/media/pci/hailo/utils/logs.h new file mode 100644 index 00000000000000..95b80a40524912 --- /dev/null +++ b/drivers/media/pci/hailo/utils/logs.h @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved. + **/ + +#ifndef _COMMON_LOGS_H_ +#define _COMMON_LOGS_H_ + +#include <linux/kern_levels.h> + +// Should be used only by "module_param". +// Specify the current debug level for the logs +extern int o_dbg; + + +// Logging, same interface as dev_*, uses o_dbg to filter +// log messages +#define hailo_printk(level, dev, fmt, ...) \ + do { \ + int __level = (level[1] - '0'); \ + if (__level <= o_dbg) { \ + dev_printk((level), dev, fmt, ##__VA_ARGS__); \ + } \ + } while (0) + +#define hailo_emerg(board, fmt, ...) hailo_printk(KERN_EMERG, &(board)->pDev->dev, fmt, ##__VA_ARGS__) +#define hailo_alert(board, fmt, ...) hailo_printk(KERN_ALERT, &(board)->pDev->dev, fmt, ##__VA_ARGS__) +#define hailo_crit(board, fmt, ...) hailo_printk(KERN_CRIT, &(board)->pDev->dev, fmt, ##__VA_ARGS__) +#define hailo_err(board, fmt, ...) hailo_printk(KERN_ERR, &(board)->pDev->dev, fmt, ##__VA_ARGS__) +#define hailo_warn(board, fmt, ...) hailo_printk(KERN_WARNING, &(board)->pDev->dev, fmt, ##__VA_ARGS__) +#define hailo_notice(board, fmt, ...) hailo_printk(KERN_NOTICE, &(board)->pDev->dev, fmt, ##__VA_ARGS__) +#define hailo_info(board, fmt, ...) hailo_printk(KERN_INFO, &(board)->pDev->dev, fmt, ##__VA_ARGS__) +#define hailo_dbg(board, fmt, ...) hailo_printk(KERN_DEBUG, &(board)->pDev->dev, fmt, ##__VA_ARGS__) + +#define hailo_dev_emerg(dev, fmt, ...) hailo_printk(KERN_EMERG, dev, fmt, ##__VA_ARGS__) +#define hailo_dev_alert(dev, fmt, ...) hailo_printk(KERN_ALERT, dev, fmt, ##__VA_ARGS__) +#define hailo_dev_crit(dev, fmt, ...) hailo_printk(KERN_CRIT, dev, fmt, ##__VA_ARGS__) +#define hailo_dev_err(dev, fmt, ...) hailo_printk(KERN_ERR, dev, fmt, ##__VA_ARGS__) +#define hailo_dev_warn(dev, fmt, ...) hailo_printk(KERN_WARNING, dev, fmt, ##__VA_ARGS__) +#define hailo_dev_notice(dev, fmt, ...) hailo_printk(KERN_NOTICE, dev, fmt, ##__VA_ARGS__) +#define hailo_dev_info(dev, fmt, ...) hailo_printk(KERN_INFO, dev, fmt, ##__VA_ARGS__) +#define hailo_dev_dbg(dev, fmt, ...) hailo_printk(KERN_DEBUG, dev, fmt, ##__VA_ARGS__) + + +#endif //_COMMON_LOGS_H_ \ No newline at end of file diff --git a/drivers/media/pci/hailo/vdma/ioctl.c b/drivers/media/pci/hailo/vdma/ioctl.c new file mode 100644 index 00000000000000..d5be26a6d1883d --- /dev/null +++ b/drivers/media/pci/hailo/vdma/ioctl.c @@ -0,0 +1,721 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved. + **/ + +#include "ioctl.h" +#include "memory.h" +#include "utils/logs.h" +#include "utils.h" + +#include <linux/slab.h> +#include <linux/uaccess.h> + + +long hailo_vdma_enable_channels_ioctl(struct hailo_vdma_controller *controller, unsigned long arg, struct hailo_vdma_file_context *context) +{ + struct hailo_vdma_enable_channels_params input; + struct hailo_vdma_engine *engine = NULL; + u8 engine_index = 0; + u32 channels_bitmap = 0; + + if (copy_from_user(&input, (void *)arg, sizeof(input))) { + hailo_dev_err(controller->dev, "copy_from_user fail\n"); + return -ENOMEM; + } + + // Validate params (ignoring engine_index >= controller->vdma_engines_count). + for_each_vdma_engine(controller, engine, engine_index) { + channels_bitmap = input.channels_bitmap_per_engine[engine_index]; + if (0 != (channels_bitmap & engine->enabled_channels)) { + hailo_dev_err(controller->dev, "Trying to enable channels that are already enabled\n"); + return -EINVAL; + } + } + + for_each_vdma_engine(controller, engine, engine_index) { + channels_bitmap = input.channels_bitmap_per_engine[engine_index]; + hailo_vdma_engine_enable_channels(engine, channels_bitmap, + input.enable_timestamps_measure); + hailo_vdma_update_interrupts_mask(controller, engine_index); + hailo_dev_info(controller->dev, "Enabled interrupts for engine %u, channels bitmap 0x%x\n", + engine_index, channels_bitmap); + + // Update the context with the enabled channels bitmap + context->enabled_channels_bitmap[engine_index] |= channels_bitmap; + } + + return 0; +} + +long hailo_vdma_disable_channels_ioctl(struct hailo_vdma_controller *controller, unsigned long arg, struct hailo_vdma_file_context *context) +{ + struct hailo_vdma_disable_channels_params input; + struct hailo_vdma_engine *engine = NULL; + u8 engine_index = 0; + u32 channels_bitmap = 0; + unsigned long irq_saved_flags = 0; + + if (copy_from_user(&input, (void*)arg, sizeof(input))) { + hailo_dev_err(controller->dev, "copy_from_user fail\n"); + return -ENOMEM; + } + + // Validate params (ignoring engine_index >= controller->vdma_engines_count). + for_each_vdma_engine(controller, engine, engine_index) { + channels_bitmap = input.channels_bitmap_per_engine[engine_index]; + if (channels_bitmap != (channels_bitmap & engine->enabled_channels)) { + hailo_dev_warn(controller->dev, "Trying to disable channels that were not enabled\n"); + } + } + + for_each_vdma_engine(controller, engine, engine_index) { + channels_bitmap = input.channels_bitmap_per_engine[engine_index]; + hailo_vdma_engine_disable_channels(engine, channels_bitmap); + hailo_vdma_update_interrupts_mask(controller, engine_index); + + spin_lock_irqsave(&controller->interrupts_lock, irq_saved_flags); + hailo_vdma_engine_clear_channel_interrupts(engine, channels_bitmap); + spin_unlock_irqrestore(&controller->interrupts_lock, irq_saved_flags); + + hailo_dev_info(controller->dev, "Disabled channels for engine %u, bitmap 0x%x\n", + engine_index, channels_bitmap); + + // Update the context with the disabled channels bitmap + context->enabled_channels_bitmap[engine_index] &= ~channels_bitmap; + } + + // Wake up threads waiting + wake_up_interruptible_all(&controller->interrupts_wq); + + return 0; +} + +static bool got_interrupt(struct hailo_vdma_controller *controller, + u32 channels_bitmap_per_engine[MAX_VDMA_ENGINES]) +{ + struct hailo_vdma_engine *engine = NULL; + u8 engine_index = 0; + for_each_vdma_engine(controller, engine, engine_index) { + if (hailo_vdma_engine_got_interrupt(engine, + channels_bitmap_per_engine[engine_index])) { + return true; + } + } + return false; +} + +static void transfer_done(struct hailo_ongoing_transfer *transfer, void *opaque) +{ + u8 i = 0; + struct hailo_vdma_controller *controller = (struct hailo_vdma_controller *)opaque; + for (i = 0; i < transfer->buffers_count; i++) { + struct hailo_vdma_buffer *mapped_buffer = (struct hailo_vdma_buffer *)transfer->buffers[i].opaque; + hailo_vdma_buffer_sync_cyclic(controller, mapped_buffer, HAILO_SYNC_FOR_CPU, + transfer->buffers[i].offset, transfer->buffers[i].size); + } +} + +long hailo_vdma_interrupts_wait_ioctl(struct hailo_vdma_controller *controller, unsigned long arg, + struct semaphore *mutex, bool *should_up_board_mutex) +{ + long err = 0; + struct hailo_vdma_interrupts_wait_params params = {0}; + struct hailo_vdma_engine *engine = NULL; + bool bitmap_not_empty = false; + u8 engine_index = 0; + u32 irq_bitmap = 0; + unsigned long irq_saved_flags = 0; + + if (copy_from_user(¶ms, (void*)arg, sizeof(params))) { + hailo_dev_err(controller->dev, "HAILO_VDMA_INTERRUPTS_WAIT, copy_from_user fail\n"); + return -ENOMEM; + } + + // We don't need to validate that channels_bitmap_per_engine are enabled - + // If the channel is not enabled we just return an empty interrupts list. + + // Validate params (ignoring engine_index >= controller->vdma_engines_count). + // It us ok to wait on a disabled channel - the wait will just exit. + for_each_vdma_engine(controller, engine, engine_index) { + if (0 != params.channels_bitmap_per_engine[engine_index]) { + bitmap_not_empty = true; + } + } + if (!bitmap_not_empty) { + hailo_dev_err(controller->dev, "Got an empty bitmap for wait interrupts\n"); + return -EINVAL; + } + + up(mutex); + err = wait_event_interruptible(controller->interrupts_wq, + got_interrupt(controller, params.channels_bitmap_per_engine)); + if (err < 0) { + hailo_dev_info(controller->dev, + "wait channel interrupts failed with err=%ld (process was interrupted or killed)\n", err); + *should_up_board_mutex = false; + return err; + } + + if (down_interruptible(mutex)) { + hailo_dev_info(controller->dev, "down_interruptible error (process was interrupted or killed)\n"); + *should_up_board_mutex = false; + return -ERESTARTSYS; + } + + params.channels_count = 0; + for_each_vdma_engine(controller, engine, engine_index) { + + spin_lock_irqsave(&controller->interrupts_lock, irq_saved_flags); + irq_bitmap = hailo_vdma_engine_read_interrupts(engine, + params.channels_bitmap_per_engine[engine->index]); + spin_unlock_irqrestore(&controller->interrupts_lock, irq_saved_flags); + + err = hailo_vdma_engine_fill_irq_data(¶ms, engine, irq_bitmap, + transfer_done, controller); + if (err < 0) { + hailo_dev_err(controller->dev, "Failed fill irq data %ld", err); + return err; + } + } + + if (copy_to_user((void __user*)arg, ¶ms, sizeof(params))) { + hailo_dev_err(controller->dev, "copy_to_user fail\n"); + return -ENOMEM; + } + + return 0; +} + +static uintptr_t hailo_get_next_vdma_handle(struct hailo_vdma_file_context *context) +{ + // Note: The kernel code left-shifts the 'offset' param from the user-space call to mmap by PAGE_SHIFT bits and + // stores the result in 'vm_area_struct.vm_pgoff'. We pass the desc_handle to mmap in the offset param. To + // counter this, we right-shift the desc_handle. See also 'mmap function'. + uintptr_t next_handle = 0; + next_handle = atomic_inc_return(&context->last_vdma_handle); + return (next_handle << PAGE_SHIFT); +} + +long hailo_vdma_buffer_map_ioctl(struct hailo_vdma_file_context *context, struct hailo_vdma_controller *controller, + unsigned long arg) +{ + struct hailo_vdma_buffer_map_params buf_info; + struct hailo_vdma_buffer *mapped_buffer = NULL; + enum dma_data_direction direction = DMA_NONE; + struct hailo_vdma_low_memory_buffer *low_memory_buffer = NULL; + + if (copy_from_user(&buf_info, (void __user*)arg, sizeof(buf_info))) { + hailo_dev_err(controller->dev, "copy from user fail\n"); + return -EFAULT; + } + + hailo_dev_dbg(controller->dev, "address %lx tgid %d size: %zu\n", + buf_info.user_address, current->tgid, buf_info.size); + + direction = get_dma_direction(buf_info.data_direction); + if (DMA_NONE == direction) { + hailo_dev_err(controller->dev, "invalid data direction %d\n", buf_info.data_direction); + return -EINVAL; + } + + low_memory_buffer = hailo_vdma_find_low_memory_buffer(context, buf_info.allocated_buffer_handle); + + mapped_buffer = hailo_vdma_buffer_map(controller->dev, + buf_info.user_address, buf_info.size, direction, buf_info.buffer_type, low_memory_buffer); + if (IS_ERR(mapped_buffer)) { + hailo_dev_err(controller->dev, "failed map buffer %lx\n", buf_info.user_address); + return PTR_ERR(mapped_buffer); + } + + mapped_buffer->handle = atomic_inc_return(&context->last_vdma_user_buffer_handle); + buf_info.mapped_handle = mapped_buffer->handle; + if (copy_to_user((void __user*)arg, &buf_info, sizeof(buf_info))) { + hailo_dev_err(controller->dev, "copy_to_user fail\n"); + hailo_vdma_buffer_put(mapped_buffer); + return -EFAULT; + } + + list_add(&mapped_buffer->mapped_user_buffer_list, &context->mapped_user_buffer_list); + hailo_dev_dbg(controller->dev, "buffer %lx (handle %zu) is mapped\n", + buf_info.user_address, buf_info.mapped_handle); + return 0; +} + +long hailo_vdma_buffer_unmap_ioctl(struct hailo_vdma_file_context *context, struct hailo_vdma_controller *controller, + unsigned long arg) +{ + struct hailo_vdma_buffer *mapped_buffer = NULL; + struct hailo_vdma_buffer_unmap_params buffer_unmap_params; + + if (copy_from_user(&buffer_unmap_params, (void __user*)arg, sizeof(buffer_unmap_params))) { + hailo_dev_err(controller->dev, "copy from user fail\n"); + return -EFAULT; + } + + hailo_dev_dbg(controller->dev, "unmap user buffer handle %zu\n", buffer_unmap_params.mapped_handle); + + mapped_buffer = hailo_vdma_find_mapped_user_buffer(context, buffer_unmap_params.mapped_handle); + if (mapped_buffer == NULL) { + hailo_dev_warn(controller->dev, "buffer handle %zu not found\n", buffer_unmap_params.mapped_handle); + return -EINVAL; + } + + list_del(&mapped_buffer->mapped_user_buffer_list); + hailo_vdma_buffer_put(mapped_buffer); + return 0; +} + +long hailo_vdma_buffer_sync_ioctl(struct hailo_vdma_file_context *context, struct hailo_vdma_controller *controller, unsigned long arg) +{ + struct hailo_vdma_buffer_sync_params sync_info = {}; + struct hailo_vdma_buffer *mapped_buffer = NULL; + + if (copy_from_user(&sync_info, (void __user*)arg, sizeof(sync_info))) { + hailo_dev_err(controller->dev, "copy_from_user fail\n"); + return -EFAULT; + } + + if (!(mapped_buffer = hailo_vdma_find_mapped_user_buffer(context, sync_info.handle))) { + hailo_dev_err(controller->dev, "buffer handle %zu doesn't exist\n", sync_info.handle); + return -EINVAL; + } + + if ((sync_info.sync_type != HAILO_SYNC_FOR_CPU) && (sync_info.sync_type != HAILO_SYNC_FOR_DEVICE)) { + hailo_dev_err(controller->dev, "Invalid sync_type given for vdma buffer sync.\n"); + return -EINVAL; + } + + if (sync_info.offset + sync_info.count > mapped_buffer->size) { + hailo_dev_err(controller->dev, "Invalid offset/count given for vdma buffer sync. offset %zu count %zu buffer size %u\n", + sync_info.offset, sync_info.count, mapped_buffer->size); + return -EINVAL; + } + + hailo_vdma_buffer_sync(controller, mapped_buffer, sync_info.sync_type, + sync_info.offset, sync_info.count); + return 0; +} + +long hailo_desc_list_create_ioctl(struct hailo_vdma_file_context *context, struct hailo_vdma_controller *controller, + unsigned long arg) +{ + struct hailo_desc_list_create_params params; + struct hailo_descriptors_list_buffer *descriptors_buffer = NULL; + uintptr_t next_handle = 0; + long err = -EINVAL; + + if (copy_from_user(¶ms, (void __user*)arg, sizeof(params))) { + hailo_dev_err(controller->dev, "copy_from_user fail\n"); + return -EFAULT; + } + + if (params.is_circular && !is_powerof2(params.desc_count)) { + hailo_dev_err(controller->dev, "Invalid desc count given : %zu , circular descriptors count must be power of 2\n", + params.desc_count); + return -EINVAL; + } + + if (!is_powerof2(params.desc_page_size)) { + hailo_dev_err(controller->dev, "Invalid desc page size given : %u\n", + params.desc_page_size); + return -EINVAL; + } + + hailo_dev_info(controller->dev, + "Create desc list desc_count: %zu desc_page_size: %u\n", + params.desc_count, params.desc_page_size); + + descriptors_buffer = kzalloc(sizeof(*descriptors_buffer), GFP_KERNEL); + if (NULL == descriptors_buffer) { + hailo_dev_err(controller->dev, "Failed to allocate buffer for descriptors list struct\n"); + return -ENOMEM; + } + + next_handle = hailo_get_next_vdma_handle(context); + + err = hailo_desc_list_create(controller->dev, params.desc_count, + params.desc_page_size, next_handle, params.is_circular, + descriptors_buffer); + if (err < 0) { + hailo_dev_err(controller->dev, "failed to allocate descriptors buffer\n"); + kfree(descriptors_buffer); + return err; + } + + list_add(&descriptors_buffer->descriptors_buffer_list, &context->descriptors_buffer_list); + + // Note: The physical address is required for CONTEXT_SWITCH firmware controls + BUILD_BUG_ON(sizeof(params.dma_address) < sizeof(descriptors_buffer->dma_address)); + params.dma_address = descriptors_buffer->dma_address; + params.desc_handle = descriptors_buffer->handle; + + if(copy_to_user((void __user*)arg, ¶ms, sizeof(params))){ + hailo_dev_err(controller->dev, "copy_to_user fail\n"); + list_del(&descriptors_buffer->descriptors_buffer_list); + hailo_desc_list_release(controller->dev, descriptors_buffer); + kfree(descriptors_buffer); + return -EFAULT; + } + + hailo_dev_info(controller->dev, "Created desc list, handle 0x%llu\n", + (u64)params.desc_handle); + return 0; +} + +long hailo_desc_list_release_ioctl(struct hailo_vdma_file_context *context, struct hailo_vdma_controller *controller, + unsigned long arg) +{ + struct hailo_desc_list_release_params params; + struct hailo_descriptors_list_buffer *descriptors_buffer = NULL; + + if (copy_from_user(¶ms, (void __user*)arg, sizeof(params))) { + hailo_dev_err(controller->dev, "copy_from_user fail\n"); + return -EFAULT; + } + + descriptors_buffer = hailo_vdma_find_descriptors_buffer(context, params.desc_handle); + if (descriptors_buffer == NULL) { + hailo_dev_warn(controller->dev, "not found desc handle %llu\n", (unsigned long long)params.desc_handle); + return -EINVAL; + } + + list_del(&descriptors_buffer->descriptors_buffer_list); + hailo_desc_list_release(controller->dev, descriptors_buffer); + kfree(descriptors_buffer); + return 0; +} + +long hailo_desc_list_program_ioctl(struct hailo_vdma_file_context *context, struct hailo_vdma_controller *controller, + unsigned long arg) +{ + struct hailo_desc_list_program_params configure_info; + struct hailo_vdma_buffer *mapped_buffer = NULL; + struct hailo_descriptors_list_buffer *descriptors_buffer = NULL; + struct hailo_vdma_mapped_transfer_buffer transfer_buffer = {0}; + + if (copy_from_user(&configure_info, (void __user*)arg, sizeof(configure_info))) { + hailo_dev_err(controller->dev, "copy from user fail\n"); + return -EFAULT; + } + hailo_dev_info(controller->dev, "config buffer_handle=%zu desc_handle=%llu starting_desc=%u\n", + configure_info.buffer_handle, (u64)configure_info.desc_handle, configure_info.starting_desc); + + mapped_buffer = hailo_vdma_find_mapped_user_buffer(context, configure_info.buffer_handle); + descriptors_buffer = hailo_vdma_find_descriptors_buffer(context, configure_info.desc_handle); + if (mapped_buffer == NULL || descriptors_buffer == NULL) { + hailo_dev_err(controller->dev, "invalid user/descriptors buffer\n"); + return -EFAULT; + } + + if (configure_info.buffer_size > mapped_buffer->size) { + hailo_dev_err(controller->dev, "invalid buffer size. \n"); + return -EFAULT; + } + + transfer_buffer.sg_table = &mapped_buffer->sg_table; + transfer_buffer.size = configure_info.buffer_size; + transfer_buffer.offset = configure_info.buffer_offset; + + return hailo_vdma_program_descriptors_list( + controller->hw, + &descriptors_buffer->desc_list, + configure_info.starting_desc, + &transfer_buffer, + configure_info.should_bind, + configure_info.channel_index, + configure_info.last_interrupts_domain, + configure_info.is_debug + ); +} + +long hailo_vdma_low_memory_buffer_alloc_ioctl(struct hailo_vdma_file_context *context, struct hailo_vdma_controller *controller, + unsigned long arg) +{ + struct hailo_allocate_low_memory_buffer_params buf_info = {0}; + struct hailo_vdma_low_memory_buffer *low_memory_buffer = NULL; + long err = -EINVAL; + + if (copy_from_user(&buf_info, (void __user*)arg, sizeof(buf_info))) { + hailo_dev_err(controller->dev, "copy from user fail\n"); + return -EFAULT; + } + + low_memory_buffer = kzalloc(sizeof(*low_memory_buffer), GFP_KERNEL); + if (NULL == low_memory_buffer) { + hailo_dev_err(controller->dev, "memory alloc failed\n"); + return -ENOMEM; + } + + err = hailo_vdma_low_memory_buffer_alloc(buf_info.buffer_size, low_memory_buffer); + if (err < 0) { + kfree(low_memory_buffer); + hailo_dev_err(controller->dev, "failed allocating buffer from driver\n"); + return err; + } + + // Get handle for allocated buffer + low_memory_buffer->handle = hailo_get_next_vdma_handle(context); + + list_add(&low_memory_buffer->vdma_low_memory_buffer_list, &context->vdma_low_memory_buffer_list); + + buf_info.buffer_handle = low_memory_buffer->handle; + if (copy_to_user((void __user*)arg, &buf_info, sizeof(buf_info))) { + hailo_dev_err(controller->dev, "copy_to_user fail\n"); + list_del(&low_memory_buffer->vdma_low_memory_buffer_list); + hailo_vdma_low_memory_buffer_free(low_memory_buffer); + kfree(low_memory_buffer); + return -EFAULT; + } + + return 0; +} + +long hailo_vdma_low_memory_buffer_free_ioctl(struct hailo_vdma_file_context *context, struct hailo_vdma_controller *controller, + unsigned long arg) +{ + struct hailo_vdma_low_memory_buffer *low_memory_buffer = NULL; + struct hailo_free_low_memory_buffer_params params = {0}; + + if (copy_from_user(¶ms, (void __user*)arg, sizeof(params))) { + hailo_dev_err(controller->dev, "copy from user fail\n"); + return -EFAULT; + } + + low_memory_buffer = hailo_vdma_find_low_memory_buffer(context, params.buffer_handle); + if (NULL == low_memory_buffer) { + hailo_dev_warn(controller->dev, "vdma buffer handle %lx not found\n", params.buffer_handle); + return -EINVAL; + } + + list_del(&low_memory_buffer->vdma_low_memory_buffer_list); + hailo_vdma_low_memory_buffer_free(low_memory_buffer); + kfree(low_memory_buffer); + return 0; +} + +long hailo_mark_as_in_use(struct hailo_vdma_controller *controller, unsigned long arg, struct file *filp) +{ + struct hailo_mark_as_in_use_params params = {0}; + + // If device is used by this FD, return false to indicate its free for usage + if (filp == controller->used_by_filp) { + params.in_use = false; + } else if (NULL != controller->used_by_filp) { + params.in_use = true; + } else { + controller->used_by_filp = filp; + params.in_use = false; + } + + if (copy_to_user((void __user*)arg, ¶ms, sizeof(params))) { + hailo_dev_err(controller->dev, "copy_to_user fail\n"); + return -EFAULT; + } + + return 0; +} + +long hailo_vdma_continuous_buffer_alloc_ioctl(struct hailo_vdma_file_context *context, struct hailo_vdma_controller *controller, unsigned long arg) +{ + struct hailo_allocate_continuous_buffer_params buf_info = {0}; + struct hailo_vdma_continuous_buffer *continuous_buffer = NULL; + long err = -EINVAL; + size_t aligned_buffer_size = 0; + + if (copy_from_user(&buf_info, (void __user*)arg, sizeof(buf_info))) { + hailo_dev_err(controller->dev, "copy from user fail\n"); + return -EFAULT; + } + + continuous_buffer = kzalloc(sizeof(*continuous_buffer), GFP_KERNEL); + if (NULL == continuous_buffer) { + hailo_dev_err(controller->dev, "memory alloc failed\n"); + return -ENOMEM; + } + + // We use PAGE_ALIGN to support mmap + aligned_buffer_size = PAGE_ALIGN(buf_info.buffer_size); + err = hailo_vdma_continuous_buffer_alloc(controller->dev, aligned_buffer_size, continuous_buffer); + if (err < 0) { + kfree(continuous_buffer); + return err; + } + + continuous_buffer->handle = hailo_get_next_vdma_handle(context); + list_add(&continuous_buffer->continuous_buffer_list, &context->continuous_buffer_list); + + buf_info.buffer_handle = continuous_buffer->handle; + buf_info.dma_address = continuous_buffer->dma_address; + if (copy_to_user((void __user*)arg, &buf_info, sizeof(buf_info))) { + hailo_dev_err(controller->dev, "copy_to_user fail\n"); + list_del(&continuous_buffer->continuous_buffer_list); + hailo_vdma_continuous_buffer_free(controller->dev, continuous_buffer); + kfree(continuous_buffer); + return -EFAULT; + } + + return 0; +} + +long hailo_vdma_continuous_buffer_free_ioctl(struct hailo_vdma_file_context *context, struct hailo_vdma_controller *controller, unsigned long arg) +{ + struct hailo_free_continuous_buffer_params params; + struct hailo_vdma_continuous_buffer *continuous_buffer = NULL; + + if (copy_from_user(¶ms, (void __user*)arg, sizeof(params))) { + hailo_dev_err(controller->dev, "copy from user fail\n"); + return -EFAULT; + } + + continuous_buffer = hailo_vdma_find_continuous_buffer(context, params.buffer_handle); + if (NULL == continuous_buffer) { + hailo_dev_warn(controller->dev, "vdma buffer handle %lx not found\n", params.buffer_handle); + return -EINVAL; + } + + list_del(&continuous_buffer->continuous_buffer_list); + hailo_vdma_continuous_buffer_free(controller->dev, continuous_buffer); + kfree(continuous_buffer); + return 0; +} + +long hailo_vdma_interrupts_read_timestamps_ioctl(struct hailo_vdma_controller *controller, unsigned long arg) +{ + struct hailo_vdma_interrupts_read_timestamp_params *params = &controller->read_interrupt_timestamps_params; + struct hailo_vdma_engine *engine = NULL; + int err = -EINVAL; + + hailo_dev_dbg(controller->dev, "Start read interrupt timestamps ioctl\n"); + + if (copy_from_user(params, (void __user*)arg, sizeof(*params))) { + hailo_dev_err(controller->dev, "copy_from_user fail\n"); + return -ENOMEM; + } + + if (params->engine_index >= controller->vdma_engines_count) { + hailo_dev_err(controller->dev, "Invalid engine %u", params->engine_index); + return -EINVAL; + } + engine = &controller->vdma_engines[params->engine_index]; + + err = hailo_vdma_engine_read_timestamps(engine, params); + if (err < 0) { + hailo_dev_err(controller->dev, "Failed read engine interrupts for %u:%u", + params->engine_index, params->channel_index); + return err; + } + + if (copy_to_user((void __user*)arg, params, sizeof(*params))) { + hailo_dev_err(controller->dev, "copy_to_user fail\n"); + return -ENOMEM; + } + + return 0; +} + +long hailo_vdma_launch_transfer_ioctl(struct hailo_vdma_file_context *context, struct hailo_vdma_controller *controller, + unsigned long arg) +{ + struct hailo_vdma_launch_transfer_params params; + struct hailo_vdma_engine *engine = NULL; + struct hailo_vdma_channel *channel = NULL; + struct hailo_descriptors_list_buffer *descriptors_buffer = NULL; + struct hailo_vdma_mapped_transfer_buffer mapped_transfer_buffers[ARRAY_SIZE(params.buffers)] = {0}; + int ret = -EINVAL; + u8 i = 0; + + if (copy_from_user(¶ms, (void __user*)arg, sizeof(params))) { + hailo_dev_err(controller->dev, "copy from user fail\n"); + return -EFAULT; + } + + if (params.engine_index >= controller->vdma_engines_count) { + hailo_dev_err(controller->dev, "Invalid engine %u", params.engine_index); + return -EINVAL; + } + engine = &controller->vdma_engines[params.engine_index]; + + if (params.channel_index >= ARRAY_SIZE(engine->channels)) { + hailo_dev_err(controller->dev, "Invalid channel %u", params.channel_index); + return -EINVAL; + } + channel = &engine->channels[params.channel_index]; + + if (params.buffers_count > ARRAY_SIZE(params.buffers)) { + hailo_dev_err(controller->dev, "too many buffers %u\n", params.buffers_count); + return -EINVAL; + } + + descriptors_buffer = hailo_vdma_find_descriptors_buffer(context, params.desc_handle); + if (descriptors_buffer == NULL) { + hailo_dev_err(controller->dev, "invalid descriptors list handle\n"); + return -EFAULT; + } + + for (i = 0; i < params.buffers_count; i++) { + struct hailo_vdma_buffer *mapped_buffer = + hailo_vdma_find_mapped_user_buffer(context, params.buffers[i].mapped_buffer_handle); + if (mapped_buffer == NULL) { + hailo_dev_err(controller->dev, "invalid user buffer\n"); + return -EFAULT; + } + + if (params.buffers[i].size > mapped_buffer->size) { + hailo_dev_err(controller->dev, "Syncing size %u while buffer size is %u\n", + params.buffers[i].size, mapped_buffer->size); + return -EINVAL; + } + + if (params.buffers[i].offset > mapped_buffer->size) { + hailo_dev_err(controller->dev, "Syncing offset %u while buffer size is %u\n", + params.buffers[i].offset, mapped_buffer->size); + return -EINVAL; + } + + // Syncing the buffer to device change its ownership from host to the device. + // We sync on D2H as well if the user owns the buffer since the buffer might have been changed by + // the host between the time it was mapped and the current async transfer. + hailo_vdma_buffer_sync_cyclic(controller, mapped_buffer, HAILO_SYNC_FOR_DEVICE, + params.buffers[i].offset, params.buffers[i].size); + + mapped_transfer_buffers[i].sg_table = &mapped_buffer->sg_table; + mapped_transfer_buffers[i].size = params.buffers[i].size; + mapped_transfer_buffers[i].offset = params.buffers[i].offset; + mapped_transfer_buffers[i].opaque = mapped_buffer; + } + + ret = hailo_vdma_launch_transfer( + controller->hw, + channel, + &descriptors_buffer->desc_list, + params.starting_desc, + params.buffers_count, + mapped_transfer_buffers, + params.should_bind, + params.first_interrupts_domain, + params.last_interrupts_domain, + params.is_debug + ); + if (ret < 0) { + params.launch_transfer_status = ret; + if (-ECONNRESET != ret) { + hailo_dev_err(controller->dev, "Failed launch transfer %d\n", ret); + } + // Still need to copy fail status back to userspace - success oriented + if (copy_to_user((void __user*)arg, ¶ms, sizeof(params))) { + hailo_dev_err(controller->dev, "copy_to_user fail\n"); + } + return ret; + } + + params.descs_programed = ret; + params.launch_transfer_status = 0; + + if (copy_to_user((void __user*)arg, ¶ms, sizeof(params))) { + hailo_dev_err(controller->dev, "copy_to_user fail\n"); + return -EFAULT; + } + + return 0; +} \ No newline at end of file diff --git a/drivers/media/pci/hailo/vdma/ioctl.h b/drivers/media/pci/hailo/vdma/ioctl.h new file mode 100644 index 00000000000000..a9016c3162a3c4 --- /dev/null +++ b/drivers/media/pci/hailo/vdma/ioctl.h @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved. + **/ + +#ifndef _HAILO_VDMA_IOCTL_H_ +#define _HAILO_VDMA_IOCTL_H_ + +#include "vdma/vdma.h" + +long hailo_vdma_enable_channels_ioctl(struct hailo_vdma_controller *controller, unsigned long arg, struct hailo_vdma_file_context *context); +long hailo_vdma_disable_channels_ioctl(struct hailo_vdma_controller *controller, unsigned long arg, struct hailo_vdma_file_context *context); +long hailo_vdma_interrupts_wait_ioctl(struct hailo_vdma_controller *controller, unsigned long arg, + struct semaphore *mutex, bool *should_up_board_mutex); + +long hailo_vdma_buffer_map_ioctl(struct hailo_vdma_file_context *context, struct hailo_vdma_controller *controller, unsigned long arg); +long hailo_vdma_buffer_unmap_ioctl(struct hailo_vdma_file_context *context, struct hailo_vdma_controller *controller, unsigned long handle); +long hailo_vdma_buffer_sync_ioctl(struct hailo_vdma_file_context *context, struct hailo_vdma_controller *controller, unsigned long arg); + +long hailo_desc_list_create_ioctl(struct hailo_vdma_file_context *context, struct hailo_vdma_controller *controller, unsigned long arg); +long hailo_desc_list_release_ioctl(struct hailo_vdma_file_context *context, struct hailo_vdma_controller *controller, unsigned long arg); +long hailo_desc_list_program_ioctl(struct hailo_vdma_file_context *context, struct hailo_vdma_controller *controller, unsigned long arg); + +long hailo_vdma_low_memory_buffer_alloc_ioctl(struct hailo_vdma_file_context *context, struct hailo_vdma_controller *controller, unsigned long arg); +long hailo_vdma_low_memory_buffer_free_ioctl(struct hailo_vdma_file_context *context, struct hailo_vdma_controller *controller, unsigned long arg); + +long hailo_mark_as_in_use(struct hailo_vdma_controller *controller, unsigned long arg, struct file *filp); + +long hailo_vdma_continuous_buffer_alloc_ioctl(struct hailo_vdma_file_context *context, struct hailo_vdma_controller *controller, unsigned long arg); +long hailo_vdma_continuous_buffer_free_ioctl(struct hailo_vdma_file_context *context, struct hailo_vdma_controller *controller, unsigned long arg); + +long hailo_vdma_interrupts_read_timestamps_ioctl(struct hailo_vdma_controller *controller, unsigned long arg); + +long hailo_vdma_launch_transfer_ioctl(struct hailo_vdma_file_context *context, struct hailo_vdma_controller *controller, + unsigned long arg); + +#endif /* _HAILO_VDMA_IOCTL_H_ */ \ No newline at end of file diff --git a/drivers/media/pci/hailo/vdma/memory.c b/drivers/media/pci/hailo/vdma/memory.c new file mode 100644 index 00000000000000..b9b4f6e07bbd5f --- /dev/null +++ b/drivers/media/pci/hailo/vdma/memory.c @@ -0,0 +1,764 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved. + **/ + +#define pr_fmt(fmt) "hailo: " fmt + +#include "memory.h" +#include "utils.h" +#include "utils/compact.h" + +#include <linux/highmem-internal.h> +#include <linux/slab.h> +#include <linux/scatterlist.h> +#include <linux/sched.h> +#include <linux/module.h> + +#define SGL_MAX_SEGMENT_SIZE (0x10000) +// See linux/mm.h +#define MMIO_AND_NO_PAGES_VMA_MASK (VM_IO | VM_PFNMAP) +// The linux kernel names the dmabuf's vma vm_file field "dmabuf" +#define VMA_VM_FILE_DMABUF_NAME ("dmabuf") + +static int map_mmio_address(uintptr_t user_address, u32 size, struct vm_area_struct *vma, + struct sg_table *sgt); +static int prepare_sg_table(struct sg_table *sg_table, uintptr_t user_address, u32 size, + struct hailo_vdma_low_memory_buffer *low_mem_driver_allocated_buffer); +static void clear_sg_table(struct sg_table *sgt); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION( 3, 3, 0 ) + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 13, 0) +#define DMA_NS_NAME DMA_BUF +#else +#define DMA_NS_NAME "DMA_BUF" +#endif // LINUX_VERSION_CODE < KERNEL_VERSION(6, 13, 0) + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0) +// Import DMA_BUF namespace for needed kernels +MODULE_IMPORT_NS(DMA_NS_NAME); +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0) */ + +static int hailo_map_dmabuf(struct device *dev, int dmabuf_fd, enum dma_data_direction direction, struct sg_table *sgt, + struct hailo_dmabuf_info *dmabuf_info) +{ + int ret = -EINVAL; + struct dma_buf *dmabuf = NULL; + struct dma_buf_attachment *dmabuf_attachment = NULL; + struct sg_table *res_sgt = NULL; + + dmabuf = dma_buf_get(dmabuf_fd); + if (IS_ERR(dmabuf)) { + dev_err(dev, "dma_buf_get failed, err=%ld\n", PTR_ERR(dmabuf)); + ret = -EINVAL; + goto cleanup; + } + + dmabuf_attachment = dma_buf_attach(dmabuf, dev); + if (IS_ERR(dmabuf_attachment)) { + dev_err(dev, "dma_buf_attach failed, err=%ld\n", PTR_ERR(dmabuf_attachment)); + ret = -EINVAL; + goto l_buf_get; + } + + res_sgt = dma_buf_map_attachment(dmabuf_attachment, direction); + if (IS_ERR(res_sgt)) { + dev_err(dev, "dma_buf_map_attachment failed, err=%ld\n", PTR_ERR(res_sgt)); + goto l_buf_attach; + } + + *sgt = *res_sgt; + + dmabuf_info->dmabuf = dmabuf; + dmabuf_info->dmabuf_attachment = dmabuf_attachment; + dmabuf_info->dmabuf_sg_table = res_sgt; + return 0; + +l_buf_attach: + dma_buf_detach(dmabuf, dmabuf_attachment); +l_buf_get: + dma_buf_put(dmabuf); +cleanup: + return ret; +} + +static void hailo_unmap_dmabuf(struct hailo_vdma_buffer *vdma_buffer) +{ + dma_buf_unmap_attachment(vdma_buffer->dmabuf_info.dmabuf_attachment, vdma_buffer->dmabuf_info.dmabuf_sg_table, vdma_buffer->data_direction); + dma_buf_detach(vdma_buffer->dmabuf_info.dmabuf, vdma_buffer->dmabuf_info.dmabuf_attachment); + dma_buf_put(vdma_buffer->dmabuf_info.dmabuf); +} + +#else /* LINUX_VERSION_CODE >= KERNEL_VERSION( 3, 3, 0 ) */ + +static int hailo_map_dmabuf(struct device *dev, int dmabuf_fd, enum dma_data_direction direction, struct sg_table *sgt, + struct hailo_dmabuf_info *dmabuf_info) +{ + (void) dmabuf_fd; + (void) direction; + (void) sgt; + (void) mapped_buffer; + dev_err(dev, "dmabuf not supported in kernel versions lower than 3.3.0\n"); + return -EINVAL; +} + +static void hailo_unmap_dmabuf(struct hailo_vdma_buffer *vdma_buffer) +{ + dev_err(vdma_buffer->device, "dmabuf not supported in kernel versions lower than 3.3.0\n"); + return -EINVAL; +} + +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION( 3, 3, 0 ) */ + +// Function that checks if the vma is backed by a mapped dmabuf +static bool is_dmabuf_vma(struct vm_area_struct *vma) +{ + return (vma && vma->vm_file && (0 == strcmp(vma->vm_file->f_path.dentry->d_name.name, VMA_VM_FILE_DMABUF_NAME))); +} + +static int create_fd_from_vma(struct device *dev, struct vm_area_struct *vma) { + struct file *file = NULL; + int fd = 0; + + if (!vma || !vma->vm_file) { + dev_err(dev, "Invalid VMA or no associated file.\n"); + return -EINVAL; + } + + file = vma->vm_file; + + // This functions increments the ref count of the file + get_file(file); + + // 0 for default flags + fd = get_unused_fd_flags(0); + if (fd < 0) { + dev_err(dev, "Failed to get unused file descriptor.\n"); + fput(file); + return fd; + } + + // Install the file into the file descriptor table + fd_install(fd, file); + return fd; +} + +struct hailo_vdma_buffer *hailo_vdma_buffer_map(struct device *dev, + uintptr_t user_address, size_t size, enum dma_data_direction direction, + enum hailo_dma_buffer_type buffer_type, struct hailo_vdma_low_memory_buffer *low_mem_driver_allocated_buffer) +{ + int ret = -EINVAL; + struct hailo_vdma_buffer *mapped_buffer = NULL; + struct sg_table sgt = {0}; + struct vm_area_struct *vma = NULL; + bool is_mmio = false; + struct hailo_dmabuf_info dmabuf_info = {0}; + bool created_dmabuf_fd_from_vma = false; + + mapped_buffer = kzalloc(sizeof(*mapped_buffer), GFP_KERNEL); + if (NULL == mapped_buffer) { + dev_err(dev, "memory alloc failed\n"); + ret = -ENOMEM; + goto cleanup; + } + + if (HAILO_DMA_DMABUF_BUFFER != buffer_type) { + vma = find_vma(current->mm, user_address); + if (IS_ENABLED(HAILO_SUPPORT_MMIO_DMA_MAPPING)) { + if (NULL == vma) { + dev_err(dev, "no vma for virt_addr/size = 0x%08lx/0x%08zx\n", user_address, size); + ret = -EFAULT; + goto cleanup; + } + } + + if (is_dmabuf_vma(vma)) { + dev_dbg(dev, "Given vma is backed by dmabuf - creating fd and mapping as dmabuf\n"); + buffer_type = HAILO_DMA_DMABUF_BUFFER; + ret = create_fd_from_vma(dev, vma); + if (ret < 0) { + dev_err(dev, "Failed creating fd from vma in given dmabuf\n"); + goto cleanup; + } + // Override user address with fd to the dmabuf - like normal dmabuf flow + user_address = ret; + created_dmabuf_fd_from_vma = true; + } + } + + // TODO: is MMIO DMA MAPPINGS STILL needed after dmabuf + if (IS_ENABLED(HAILO_SUPPORT_MMIO_DMA_MAPPING) && + (MMIO_AND_NO_PAGES_VMA_MASK == (vma->vm_flags & MMIO_AND_NO_PAGES_VMA_MASK)) && + (HAILO_DMA_DMABUF_BUFFER != buffer_type)) { + // user_address represents memory mapped I/O and isn't backed by 'struct page' (only by pure pfn) + if (NULL != low_mem_driver_allocated_buffer) { + // low_mem_driver_allocated_buffer are backed by regular 'struct page' addresses, just in low memory + dev_err(dev, "low_mem_driver_allocated_buffer shouldn't be provided with an mmio address\n"); + ret = -EINVAL; + goto free_buffer_struct; + } + + ret = map_mmio_address(user_address, size, vma, &sgt); + if (ret < 0) { + dev_err(dev, "failed to map mmio address %d\n", ret); + goto free_buffer_struct; + } + + is_mmio = true; + + } else if (HAILO_DMA_DMABUF_BUFFER == buffer_type) { + // Content user_address in case of dmabuf is fd - for now + ret = hailo_map_dmabuf(dev, user_address, direction, &sgt, &dmabuf_info); + if (ret < 0) { + dev_err(dev, "Failed mapping dmabuf\n"); + goto cleanup; + } + // If created dmabuf fd from vma need to decrement refcount and release fd + if (created_dmabuf_fd_from_vma) { + fput(vma->vm_file); + put_unused_fd(user_address); + } + } else { + // user_address is a standard 'struct page' backed memory address + ret = prepare_sg_table(&sgt, user_address, size, low_mem_driver_allocated_buffer); + if (ret < 0) { + dev_err(dev, "failed to set sg list for user buffer %d\n", ret); + goto free_buffer_struct; + } + sgt.nents = dma_map_sg(dev, sgt.sgl, sgt.orig_nents, direction); + if (0 == sgt.nents) { + dev_err(dev, "failed to map sg list for user buffer\n"); + ret = -ENXIO; + goto clear_sg_table; + } + } + + kref_init(&mapped_buffer->kref); + mapped_buffer->device = dev; + mapped_buffer->user_address = user_address; + mapped_buffer->size = size; + mapped_buffer->data_direction = direction; + mapped_buffer->sg_table = sgt; + mapped_buffer->is_mmio = is_mmio; + mapped_buffer->dmabuf_info = dmabuf_info; + + return mapped_buffer; + +clear_sg_table: + clear_sg_table(&sgt); +free_buffer_struct: + kfree(mapped_buffer); +cleanup: + return ERR_PTR(ret); +} + +static void unmap_buffer(struct kref *kref) +{ + struct hailo_vdma_buffer *buf = container_of(kref, struct hailo_vdma_buffer, kref); + + // If dmabuf - unmap and detatch dmabuf + if (NULL != buf->dmabuf_info.dmabuf) { + hailo_unmap_dmabuf(buf); + } else { + if (!buf->is_mmio) { + dma_unmap_sg(buf->device, buf->sg_table.sgl, buf->sg_table.orig_nents, buf->data_direction); + } + + clear_sg_table(&buf->sg_table); + } + kfree(buf); +} + +void hailo_vdma_buffer_get(struct hailo_vdma_buffer *buf) +{ + kref_get(&buf->kref); +} + +void hailo_vdma_buffer_put(struct hailo_vdma_buffer *buf) +{ + kref_put(&buf->kref, unmap_buffer); +} + +static void vdma_sync_entire_buffer(struct hailo_vdma_controller *controller, + struct hailo_vdma_buffer *mapped_buffer, enum hailo_vdma_buffer_sync_type sync_type) +{ + if (sync_type == HAILO_SYNC_FOR_CPU) { + dma_sync_sg_for_cpu(controller->dev, mapped_buffer->sg_table.sgl, mapped_buffer->sg_table.nents, + mapped_buffer->data_direction); + } else { + dma_sync_sg_for_device(controller->dev, mapped_buffer->sg_table.sgl, mapped_buffer->sg_table.nents, + mapped_buffer->data_direction); + } +} + +typedef void (*dma_sync_single_callback)(struct device *, dma_addr_t, size_t, enum dma_data_direction); +// Map sync_info->count bytes starting at sync_info->offset +static void vdma_sync_buffer_interval(struct hailo_vdma_controller *controller, + struct hailo_vdma_buffer *mapped_buffer, + size_t offset, size_t size, enum hailo_vdma_buffer_sync_type sync_type) +{ + size_t sync_start_offset = offset; + size_t sync_end_offset = offset + size; + dma_sync_single_callback dma_sync_single = (sync_type == HAILO_SYNC_FOR_CPU) ? + dma_sync_single_for_cpu : + dma_sync_single_for_device; + struct scatterlist* sg_entry = NULL; + size_t current_iter_offset = 0; + int i = 0; + + for_each_sg(mapped_buffer->sg_table.sgl, sg_entry, mapped_buffer->sg_table.nents, i) { + // Check if the intervals: [current_iter_offset, sg_dma_len(sg_entry)] and [sync_start_offset, sync_end_offset] + // have any intersection. If offset isn't at the start of a sg_entry, we still want to sync it. + if (max(sync_start_offset, current_iter_offset) <= min(sync_end_offset, current_iter_offset + sg_dma_len(sg_entry))) { + dma_sync_single(controller->dev, sg_dma_address(sg_entry), sg_dma_len(sg_entry), + mapped_buffer->data_direction); + } + + current_iter_offset += sg_dma_len(sg_entry); + } +} + +void hailo_vdma_buffer_sync(struct hailo_vdma_controller *controller, + struct hailo_vdma_buffer *mapped_buffer, enum hailo_vdma_buffer_sync_type sync_type, + size_t offset, size_t size) +{ + if ((IS_ENABLED(HAILO_SUPPORT_MMIO_DMA_MAPPING) && mapped_buffer->is_mmio) || + (NULL != mapped_buffer->dmabuf_info.dmabuf)) { + // MMIO buffers and dmabufs don't need to be sync'd + return; + } + + if ((offset == 0) && (size == mapped_buffer->size)) { + vdma_sync_entire_buffer(controller, mapped_buffer, sync_type); + } else { + vdma_sync_buffer_interval(controller, mapped_buffer, offset, size, sync_type); + } +} + +// Similar to vdma_buffer_sync, allow circular sync of the buffer. +void hailo_vdma_buffer_sync_cyclic(struct hailo_vdma_controller *controller, + struct hailo_vdma_buffer *mapped_buffer, enum hailo_vdma_buffer_sync_type sync_type, + size_t offset, size_t size) +{ + size_t size_to_end = min(size, mapped_buffer->size - offset); + + hailo_vdma_buffer_sync(controller, mapped_buffer, sync_type, offset, size_to_end); + + if (size_to_end < size) { + hailo_vdma_buffer_sync(controller, mapped_buffer, sync_type, 0, size - size_to_end); + } +} + +struct hailo_vdma_buffer* hailo_vdma_find_mapped_user_buffer(struct hailo_vdma_file_context *context, + size_t buffer_handle) +{ + struct hailo_vdma_buffer *cur = NULL; + list_for_each_entry(cur, &context->mapped_user_buffer_list, mapped_user_buffer_list) { + if (cur->handle == buffer_handle) { + return cur; + } + } + return NULL; +} + +void hailo_vdma_clear_mapped_user_buffer_list(struct hailo_vdma_file_context *context, + struct hailo_vdma_controller *controller) +{ + struct hailo_vdma_buffer *cur = NULL, *next = NULL; + list_for_each_entry_safe(cur, next, &context->mapped_user_buffer_list, mapped_user_buffer_list) { + list_del(&cur->mapped_user_buffer_list); + hailo_vdma_buffer_put(cur); + } +} + + +int hailo_desc_list_create(struct device *dev, u32 descriptors_count, u16 desc_page_size, + uintptr_t desc_handle, bool is_circular, struct hailo_descriptors_list_buffer *descriptors) +{ + size_t buffer_size = 0; + const u64 align = VDMA_DESCRIPTOR_LIST_ALIGN; //First addr must be aligned on 64 KB (from the VDMA registers documentation) + + if (MAX_POWER_OF_2_VALUE < descriptors_count) { + dev_err(dev, "Invalid descriptors count %u\n", descriptors_count); + return -EINVAL; + } + + buffer_size = descriptors_count * sizeof(struct hailo_vdma_descriptor); + buffer_size = ALIGN(buffer_size, align); + + descriptors->kernel_address = dma_alloc_coherent(dev, buffer_size, + &descriptors->dma_address, GFP_KERNEL | __GFP_ZERO); + if (descriptors->kernel_address == NULL) { + dev_err(dev, "Failed to allocate descriptors list, desc_count 0x%x, buffer_size 0x%zx, This failure means there is not a sufficient amount of CMA memory " + "(contiguous physical memory), This usually is caused by lack of general system memory. Please check you have sufficient memory.\n", + descriptors_count, buffer_size); + return -ENOBUFS; + } + + descriptors->buffer_size = buffer_size; + descriptors->handle = desc_handle; + + descriptors->desc_list.desc_list = descriptors->kernel_address; + descriptors->desc_list.desc_count = descriptors_count; + // No need to check the return value of get_nearest_powerof_2 because we already checked the input + descriptors->desc_list.desc_count_mask = is_circular ? (descriptors_count - 1) : (get_nearest_powerof_2(descriptors_count) - 1); + descriptors->desc_list.desc_page_size = desc_page_size; + descriptors->desc_list.is_circular = is_circular; + + return 0; +} + +void hailo_desc_list_release(struct device *dev, struct hailo_descriptors_list_buffer *descriptors) +{ + dma_free_coherent(dev, descriptors->buffer_size, descriptors->kernel_address, descriptors->dma_address); +} + +struct hailo_descriptors_list_buffer* hailo_vdma_find_descriptors_buffer(struct hailo_vdma_file_context *context, + uintptr_t desc_handle) +{ + struct hailo_descriptors_list_buffer *cur = NULL; + list_for_each_entry(cur, &context->descriptors_buffer_list, descriptors_buffer_list) { + if (cur->handle == desc_handle) { + return cur; + } + } + return NULL; +} + +void hailo_vdma_clear_descriptors_buffer_list(struct hailo_vdma_file_context *context, + struct hailo_vdma_controller *controller) +{ + struct hailo_descriptors_list_buffer *cur = NULL, *next = NULL; + list_for_each_entry_safe(cur, next, &context->descriptors_buffer_list, descriptors_buffer_list) { + list_del(&cur->descriptors_buffer_list); + hailo_desc_list_release(controller->dev, cur); + kfree(cur); + } +} + +int hailo_vdma_low_memory_buffer_alloc(size_t size, struct hailo_vdma_low_memory_buffer *low_memory_buffer) +{ + int ret = -EINVAL; + void *kernel_address = NULL; + size_t pages_count = (size + PAGE_SIZE - 1) >> PAGE_SHIFT; + size_t num_allocated = 0, i = 0; + void **pages = NULL; + + pages = kcalloc(pages_count, sizeof(*pages), GFP_KERNEL); + if (NULL == pages) { + pr_err("Failed to allocate pages for buffer (size %zu)\n", size); + ret = -ENOMEM; + goto cleanup; + } + + for (num_allocated = 0; num_allocated < pages_count; num_allocated++) { + // __GFP_DMA32 flag is used to limit system memory allocations to the lowest 4 GB of physical memory in order to guarantee DMA + // Operations will not have to use bounce buffers on certain architectures (e.g 32-bit DMA enabled architectures) + kernel_address = (void*)__get_free_page(__GFP_DMA32); + if (NULL == kernel_address) { + pr_err("Failed to allocate %zu coherent bytes\n", (size_t)PAGE_SIZE); + ret = -ENOMEM; + goto cleanup; + } + + pages[num_allocated] = kernel_address; + } + + low_memory_buffer->pages_count = pages_count; + low_memory_buffer->pages_address = pages; + + return 0; + +cleanup: + if (NULL != pages) { + for (i = 0; i < num_allocated; i++) { + free_page((long unsigned)pages[i]); + } + + kfree(pages); + } + + return ret; +} + +void hailo_vdma_low_memory_buffer_free(struct hailo_vdma_low_memory_buffer *low_memory_buffer) +{ + size_t i = 0; + if (NULL == low_memory_buffer) { + return; + } + + for (i = 0; i < low_memory_buffer->pages_count; i++) { + free_page((long unsigned)low_memory_buffer->pages_address[i]); + } + + kfree(low_memory_buffer->pages_address); +} + +struct hailo_vdma_low_memory_buffer* hailo_vdma_find_low_memory_buffer(struct hailo_vdma_file_context *context, + uintptr_t buf_handle) +{ + struct hailo_vdma_low_memory_buffer *cur = NULL; + list_for_each_entry(cur, &context->vdma_low_memory_buffer_list, vdma_low_memory_buffer_list) { + if (cur->handle == buf_handle) { + return cur; + } + } + + return NULL; +} + +void hailo_vdma_clear_low_memory_buffer_list(struct hailo_vdma_file_context *context) +{ + struct hailo_vdma_low_memory_buffer *cur = NULL, *next = NULL; + list_for_each_entry_safe(cur, next, &context->vdma_low_memory_buffer_list, vdma_low_memory_buffer_list) { + list_del(&cur->vdma_low_memory_buffer_list); + hailo_vdma_low_memory_buffer_free(cur); + kfree(cur); + } +} + +int hailo_vdma_continuous_buffer_alloc(struct device *dev, size_t size, + struct hailo_vdma_continuous_buffer *continuous_buffer) +{ + dma_addr_t dma_address = 0; + void *kernel_address = NULL; + + kernel_address = dma_alloc_coherent(dev, size, &dma_address, GFP_KERNEL); + if (NULL == kernel_address) { + dev_warn(dev, "Failed to allocate continuous buffer, size 0x%zx. This failure means there is not a sufficient amount of CMA memory " + "(contiguous physical memory), This usually is caused by lack of general system memory. Please check you have sufficent memory.\n", size); + return -ENOBUFS; + } + + continuous_buffer->kernel_address = kernel_address; + continuous_buffer->dma_address = dma_address; + continuous_buffer->size = size; + return 0; +} + +void hailo_vdma_continuous_buffer_free(struct device *dev, + struct hailo_vdma_continuous_buffer *continuous_buffer) +{ + dma_free_coherent(dev, continuous_buffer->size, continuous_buffer->kernel_address, + continuous_buffer->dma_address); +} + +struct hailo_vdma_continuous_buffer* hailo_vdma_find_continuous_buffer(struct hailo_vdma_file_context *context, + uintptr_t buf_handle) +{ + struct hailo_vdma_continuous_buffer *cur = NULL; + list_for_each_entry(cur, &context->continuous_buffer_list, continuous_buffer_list) { + if (cur->handle == buf_handle) { + return cur; + } + } + + return NULL; +} + +void hailo_vdma_clear_continuous_buffer_list(struct hailo_vdma_file_context *context, + struct hailo_vdma_controller *controller) +{ + struct hailo_vdma_continuous_buffer *cur = NULL, *next = NULL; + list_for_each_entry_safe(cur, next, &context->continuous_buffer_list, continuous_buffer_list) { + list_del(&cur->continuous_buffer_list); + hailo_vdma_continuous_buffer_free(controller->dev, cur); + kfree(cur); + } +} + +/** + * follow_pfn - look up PFN at a user virtual address + * @vma: memory mapping + * @address: user virtual address + * @pfn: location to store found PFN + * + * Only IO mappings and raw PFN mappings are allowed. + * + * This function does not allow the caller to read the permissions + * of the PTE. Do not use it. + * + * Return: zero and the pfn at @pfn on success, -ve otherwise. + */ +#if defined(HAILO_SUPPORT_MMIO_DMA_MAPPING) +static int follow_pfn(struct vm_area_struct *vma, unsigned long address, + unsigned long *pfn) +{ + int ret = -EINVAL; + spinlock_t *ptl; + pte_t *ptep; + + if (!(vma->vm_flags & (VM_IO | VM_PFNMAP))) + return ret; + + ret = follow_pte(vma, address, &ptep, &ptl); + if (ret) + return ret; + *pfn = pte_pfn(ptep_get(ptep)); + pte_unmap_unlock(ptep, ptl); + return 0; +} +#endif + +// Assumes the provided user_address belongs to the vma and that MMIO_AND_NO_PAGES_VMA_MASK bits are set under +// vma->vm_flags. This is validated in hailo_vdma_buffer_map, and won't be checked here +#if defined(HAILO_SUPPORT_MMIO_DMA_MAPPING) +static int map_mmio_address(uintptr_t user_address, u32 size, struct vm_area_struct *vma, + struct sg_table *sgt) +{ + int ret = -EINVAL; + unsigned long i = 0; + unsigned long pfn = 0; + unsigned long next_pfn = 0; + phys_addr_t phys_addr = 0; + dma_addr_t mmio_dma_address = 0; + const uintptr_t virt_addr = user_address; + const u32 vma_size = vma->vm_end - vma->vm_start + 1; + const uintptr_t num_pages = PFN_UP(virt_addr + size) - PFN_DOWN(virt_addr); + + // Check that the vma that was marked as MMIO_AND_NO_PAGES_VMA_MASK is big enough + if (vma_size < size) { + pr_err("vma (%u bytes) smaller than provided buffer (%u bytes)\n", vma_size, size); + return -EINVAL; + } + + // Get the physical address of user_address + ret = follow_pfn(vma, virt_addr, &pfn); + if (ret) { + pr_err("follow_pfn failed with %d\n", ret); + return ret; + } + phys_addr = __pfn_to_phys(pfn) + offset_in_page(virt_addr); + + // Make sure the physical memory is contiguous + for (i = 1; i < num_pages; ++i) { + ret = follow_pfn(vma, virt_addr + (i << PAGE_SHIFT), &next_pfn); + if (ret < 0) { + pr_err("follow_pfn failed with %d\n", ret); + return ret; + } + if (next_pfn != pfn + 1) { + pr_err("non-contiguous physical memory\n"); + return -EFAULT; + } + pfn = next_pfn; + } + + // phys_addr to dma + // TODO: need dma_map_resource here? doesn't work currently (we get dma_mapping_error on the returned dma addr) + // (HRT-12521) + mmio_dma_address = (dma_addr_t)phys_addr; + + // Create a page-less scatterlist. + ret = sg_alloc_table(sgt, 1, GFP_KERNEL); + if (ret < 0) { + return ret; + } + + sg_assign_page(sgt->sgl, NULL); + sg_dma_address(sgt->sgl) = mmio_dma_address; + sg_dma_len(sgt->sgl) = size; + + return 0; +} +#else /* defined(HAILO_SUPPORT_MMIO_DMA_MAPPING) */ +static int map_mmio_address(uintptr_t user_address, u32 size, struct vm_area_struct *vma, + struct sg_table *sgt) +{ + (void) user_address; + (void) size; + (void) vma; + (void) sgt; + pr_err("MMIO DMA MAPPINGS are not supported in this kernel version\n"); + return -EINVAL; +} +#endif /* defined(HAILO_SUPPORT_MMIO_DMA_MAPPING) */ + + +static int prepare_sg_table(struct sg_table *sg_table, uintptr_t user_address, u32 size, + struct hailo_vdma_low_memory_buffer *low_mem_driver_allocated_buffer) +{ + int ret = -EINVAL; + int pinned_pages = 0; + size_t npages = 0; + struct page **pages = NULL; + int i = 0; + struct scatterlist *sg_alloc_res = NULL; + + npages = (size + PAGE_SIZE - 1) >> PAGE_SHIFT; + pages = kvmalloc_array(npages, sizeof(*pages), GFP_KERNEL); + if (!pages) { + return -ENOMEM; + } + + // Check whether mapping user allocated buffer or driver allocated low memory buffer + if (NULL == low_mem_driver_allocated_buffer) { + mmap_read_lock(current->mm); + pinned_pages = get_user_pages_compact(user_address, npages, FOLL_WRITE | FOLL_FORCE, pages); + mmap_read_unlock(current->mm); + + if (pinned_pages < 0) { + pr_err("get_user_pages failed with %d\n", pinned_pages); + ret = pinned_pages; + goto exit; + } else if (pinned_pages != npages) { + pr_err("Pinned %d out of %zu\n", pinned_pages, npages); + ret = -EINVAL; + goto release_pages; + } + } else { + // Check to make sure in case user provides wrong buffer + if (npages != low_mem_driver_allocated_buffer->pages_count) { + pr_err("Received wrong amount of pages %zu to map expected %zu\n", + npages, low_mem_driver_allocated_buffer->pages_count); + ret = -EINVAL; + goto exit; + } + + for (i = 0; i < npages; i++) { + pages[i] = virt_to_page(low_mem_driver_allocated_buffer->pages_address[i]); + get_page(pages[i]); + } + } + + sg_alloc_res = sg_alloc_table_from_pages_segment_compat(sg_table, pages, npages, + 0, size, SGL_MAX_SEGMENT_SIZE, NULL, 0, GFP_KERNEL); + if (IS_ERR(sg_alloc_res)) { + ret = PTR_ERR(sg_alloc_res); + pr_err("sg table alloc failed (err %d)..\n", ret); + goto release_pages; + } + + ret = 0; + goto exit; +release_pages: + for (i = 0; i < pinned_pages; i++) { + if (!PageReserved(pages[i])) { + SetPageDirty(pages[i]); + } + put_page(pages[i]); + } +exit: + kvfree(pages); + return ret; +} + +static void clear_sg_table(struct sg_table *sgt) +{ + struct sg_page_iter iter; + struct page *page = NULL; + + for_each_sg_page(sgt->sgl, &iter, sgt->orig_nents, 0) { + page = sg_page_iter_page(&iter); + if (page) { + if (!PageReserved(page)) { + SetPageDirty(page); + } + put_page(page); + } + } + + sg_free_table(sgt); +} diff --git a/drivers/media/pci/hailo/vdma/memory.h b/drivers/media/pci/hailo/vdma/memory.h new file mode 100644 index 00000000000000..f8bffcf9143200 --- /dev/null +++ b/drivers/media/pci/hailo/vdma/memory.h @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved. + **/ +/** + * vDMA memory utility (including allocation and mappings) + */ + +#ifndef _HAILO_VDMA_MEMORY_H_ +#define _HAILO_VDMA_MEMORY_H_ + +#include "vdma/vdma.h" + +#define SGL_MAX_SEGMENT_SIZE (0x10000) + +struct hailo_vdma_buffer *hailo_vdma_buffer_map(struct device *dev, uintptr_t user_address, size_t size, + enum dma_data_direction direction, enum hailo_dma_buffer_type buffer_type, + struct hailo_vdma_low_memory_buffer *low_mem_driver_allocated_buffer); +void hailo_vdma_buffer_get(struct hailo_vdma_buffer *buf); +void hailo_vdma_buffer_put(struct hailo_vdma_buffer *buf); + +void hailo_vdma_buffer_sync(struct hailo_vdma_controller *controller, + struct hailo_vdma_buffer *mapped_buffer, enum hailo_vdma_buffer_sync_type sync_type, + size_t offset, size_t size); +void hailo_vdma_buffer_sync_cyclic(struct hailo_vdma_controller *controller, + struct hailo_vdma_buffer *mapped_buffer, enum hailo_vdma_buffer_sync_type sync_type, + size_t offset, size_t size); + +struct hailo_vdma_buffer* hailo_vdma_find_mapped_user_buffer(struct hailo_vdma_file_context *context, + size_t buffer_handle); +void hailo_vdma_clear_mapped_user_buffer_list(struct hailo_vdma_file_context *context, + struct hailo_vdma_controller *controller); + +int hailo_desc_list_create(struct device *dev, u32 descriptors_count, u16 desc_page_size, + uintptr_t desc_handle, bool is_circular, struct hailo_descriptors_list_buffer *descriptors); +void hailo_desc_list_release(struct device *dev, struct hailo_descriptors_list_buffer *descriptors); +struct hailo_descriptors_list_buffer* hailo_vdma_find_descriptors_buffer(struct hailo_vdma_file_context *context, + uintptr_t desc_handle); +void hailo_vdma_clear_descriptors_buffer_list(struct hailo_vdma_file_context *context, + struct hailo_vdma_controller *controller); + +int hailo_vdma_low_memory_buffer_alloc(size_t size, struct hailo_vdma_low_memory_buffer *low_memory_buffer); +void hailo_vdma_low_memory_buffer_free(struct hailo_vdma_low_memory_buffer *low_memory_buffer); +struct hailo_vdma_low_memory_buffer* hailo_vdma_find_low_memory_buffer(struct hailo_vdma_file_context *context, + uintptr_t buf_handle); +void hailo_vdma_clear_low_memory_buffer_list(struct hailo_vdma_file_context *context); + +int hailo_vdma_continuous_buffer_alloc(struct device *dev, size_t size, + struct hailo_vdma_continuous_buffer *continuous_buffer); +void hailo_vdma_continuous_buffer_free(struct device *dev, + struct hailo_vdma_continuous_buffer *continuous_buffer); +struct hailo_vdma_continuous_buffer* hailo_vdma_find_continuous_buffer(struct hailo_vdma_file_context *context, + uintptr_t buf_handle); +void hailo_vdma_clear_continuous_buffer_list(struct hailo_vdma_file_context *context, + struct hailo_vdma_controller *controller); +#endif /* _HAILO_VDMA_MEMORY_H_ */ \ No newline at end of file diff --git a/drivers/media/pci/hailo/vdma/vdma.c b/drivers/media/pci/hailo/vdma/vdma.c new file mode 100644 index 00000000000000..0ad2c5016a8fc2 --- /dev/null +++ b/drivers/media/pci/hailo/vdma/vdma.c @@ -0,0 +1,313 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved. + **/ + +#define pr_fmt(fmt) "hailo: " fmt + +#include "vdma.h" +#include "memory.h" +#include "ioctl.h" +#include "utils/logs.h" + +#include <linux/sched.h> +#include <linux/version.h> + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0) +#include <linux/dma-map-ops.h> +#else +#include <linux/dma-mapping.h> +#endif + + +static struct hailo_vdma_engine* init_vdma_engines(struct device *dev, + struct hailo_resource *channel_registers_per_engine, size_t engines_count, u32 src_channels_bitmask) +{ + struct hailo_vdma_engine *engines = NULL; + u8 i = 0; + + engines = devm_kmalloc_array(dev, engines_count, sizeof(*engines), GFP_KERNEL); + if (NULL == engines) { + dev_err(dev, "Failed allocating vdma engines\n"); + return ERR_PTR(-ENOMEM); + } + + for (i = 0; i < engines_count; i++) { + hailo_vdma_engine_init(&engines[i], i, &channel_registers_per_engine[i], src_channels_bitmask); + } + + return engines; +} + +static int hailo_set_dma_mask(struct device *dev) +{ + int err = -EINVAL; + /* Check and configure DMA length */ + if (!(err = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)))) { + dev_notice(dev, "Probing: Enabled 64 bit dma\n"); + } else if (!(err = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(48)))) { + dev_notice(dev, "Probing: Enabled 48 bit dma\n"); + } else if (!(err = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(40)))) { + dev_notice(dev, "Probing: Enabled 40 bit dma\n"); + } else if (!(err = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(36)))) { + dev_notice(dev, "Probing: Enabled 36 bit dma\n"); + } else if (!(err = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)))) { + dev_notice(dev, "Probing: Enabled 32 bit dma\n"); + } else { + dev_err(dev, "Probing: Error enabling dma %d\n", err); + return err; + } + + return 0; +} + +int hailo_vdma_controller_init(struct hailo_vdma_controller *controller, + struct device *dev, struct hailo_vdma_hw *vdma_hw, + struct hailo_vdma_controller_ops *ops, + struct hailo_resource *channel_registers_per_engine, size_t engines_count) +{ + int err = 0; + controller->hw = vdma_hw; + controller->ops = ops; + controller->dev = dev; + + controller->vdma_engines_count = engines_count; + controller->vdma_engines = init_vdma_engines(dev, channel_registers_per_engine, engines_count, + vdma_hw->src_channels_bitmask); + if (IS_ERR(controller->vdma_engines)) { + dev_err(dev, "Failed initialized vdma engines\n"); + return PTR_ERR(controller->vdma_engines); + } + + controller->used_by_filp = NULL; + spin_lock_init(&controller->interrupts_lock); + init_waitqueue_head(&controller->interrupts_wq); + + /* Check and configure DMA length */ + err = hailo_set_dma_mask(dev); + if (0 > err) { + return err; + } + + if (get_dma_ops(controller->dev)) { + hailo_dev_notice(controller->dev, "Probing: Using specialized dma_ops=%ps", get_dma_ops(controller->dev)); + } + + return 0; +} + +void hailo_vdma_file_context_init(struct hailo_vdma_file_context *context) +{ + atomic_set(&context->last_vdma_user_buffer_handle, 0); + INIT_LIST_HEAD(&context->mapped_user_buffer_list); + + atomic_set(&context->last_vdma_handle, 0); + INIT_LIST_HEAD(&context->descriptors_buffer_list); + INIT_LIST_HEAD(&context->vdma_low_memory_buffer_list); + INIT_LIST_HEAD(&context->continuous_buffer_list); + + BUILD_BUG_ON_MSG(MAX_VDMA_CHANNELS_PER_ENGINE > sizeof(context->enabled_channels_bitmap[0]) * BITS_IN_BYTE, + "Unexpected amount of VDMA channels per engine"); +} + +void hailo_vdma_update_interrupts_mask(struct hailo_vdma_controller *controller, + size_t engine_index) +{ + struct hailo_vdma_engine *engine = &controller->vdma_engines[engine_index]; + controller->ops->update_channel_interrupts(controller, engine_index, engine->enabled_channels); +} + +void hailo_vdma_file_context_finalize(struct hailo_vdma_file_context *context, + struct hailo_vdma_controller *controller, struct file *filp) +{ + size_t engine_index = 0; + struct hailo_vdma_engine *engine = NULL; + unsigned long irq_saved_flags = 0; + // In case of FLR, the vdma registers will be NULL + const bool is_device_up = (NULL != controller->dev); + + for_each_vdma_engine(controller, engine, engine_index) { + if (context->enabled_channels_bitmap[engine_index]) { + hailo_dev_info(controller->dev, "Disabling channels for engine %zu, channels bitmap 0x%x\n", engine_index, + context->enabled_channels_bitmap[engine_index]); + hailo_vdma_engine_disable_channels(engine, context->enabled_channels_bitmap[engine_index]); + + if (is_device_up) { + hailo_vdma_update_interrupts_mask(controller, engine_index); + } + + spin_lock_irqsave(&controller->interrupts_lock, irq_saved_flags); + hailo_vdma_engine_clear_channel_interrupts(engine, context->enabled_channels_bitmap[engine_index]); + spin_unlock_irqrestore(&controller->interrupts_lock, irq_saved_flags); + } + } + + hailo_vdma_clear_mapped_user_buffer_list(context, controller); + hailo_vdma_clear_descriptors_buffer_list(context, controller); + hailo_vdma_clear_low_memory_buffer_list(context); + hailo_vdma_clear_continuous_buffer_list(context, controller); + + if (filp == controller->used_by_filp) { + controller->used_by_filp = NULL; + } +} + +void hailo_vdma_wakeup_interrupts(struct hailo_vdma_controller *controller, struct hailo_vdma_engine *engine, + u32 channels_bitmap) +{ + unsigned long irq_saved_flags = 0; + + spin_lock_irqsave(&controller->interrupts_lock, irq_saved_flags); + hailo_vdma_engine_set_channel_interrupts(engine, channels_bitmap); + spin_unlock_irqrestore(&controller->interrupts_lock, irq_saved_flags); + + wake_up_interruptible_all(&controller->interrupts_wq); +} + +void hailo_vdma_irq_handler(struct hailo_vdma_controller *controller, + size_t engine_index, u32 channels_bitmap) +{ + struct hailo_vdma_engine *engine = NULL; + + BUG_ON(engine_index >= controller->vdma_engines_count); + engine = &controller->vdma_engines[engine_index]; + + hailo_vdma_engine_push_timestamps(engine, channels_bitmap); + + hailo_vdma_wakeup_interrupts(controller, engine, channels_bitmap); +} + +long hailo_vdma_ioctl(struct hailo_vdma_file_context *context, struct hailo_vdma_controller *controller, + unsigned int cmd, unsigned long arg, struct file *filp, struct semaphore *mutex, bool *should_up_board_mutex) +{ + switch (cmd) { + case HAILO_VDMA_ENABLE_CHANNELS: + return hailo_vdma_enable_channels_ioctl(controller, arg, context); + case HAILO_VDMA_DISABLE_CHANNELS: + return hailo_vdma_disable_channels_ioctl(controller, arg, context); + case HAILO_VDMA_INTERRUPTS_WAIT: + return hailo_vdma_interrupts_wait_ioctl(controller, arg, mutex, should_up_board_mutex); + case HAILO_VDMA_INTERRUPTS_READ_TIMESTAMPS: + return hailo_vdma_interrupts_read_timestamps_ioctl(controller, arg); + case HAILO_VDMA_BUFFER_MAP: + return hailo_vdma_buffer_map_ioctl(context, controller, arg); + case HAILO_VDMA_BUFFER_UNMAP: + return hailo_vdma_buffer_unmap_ioctl(context, controller, arg); + case HAILO_VDMA_BUFFER_SYNC: + return hailo_vdma_buffer_sync_ioctl(context, controller, arg); + case HAILO_DESC_LIST_CREATE: + return hailo_desc_list_create_ioctl(context, controller, arg); + case HAILO_DESC_LIST_RELEASE: + return hailo_desc_list_release_ioctl(context, controller, arg); + case HAILO_DESC_LIST_PROGRAM: + return hailo_desc_list_program_ioctl(context, controller, arg); + case HAILO_VDMA_LOW_MEMORY_BUFFER_ALLOC: + return hailo_vdma_low_memory_buffer_alloc_ioctl(context, controller, arg); + case HAILO_VDMA_LOW_MEMORY_BUFFER_FREE: + return hailo_vdma_low_memory_buffer_free_ioctl(context, controller, arg); + case HAILO_MARK_AS_IN_USE: + return hailo_mark_as_in_use(controller, arg, filp); + case HAILO_VDMA_CONTINUOUS_BUFFER_ALLOC: + return hailo_vdma_continuous_buffer_alloc_ioctl(context, controller, arg); + case HAILO_VDMA_CONTINUOUS_BUFFER_FREE: + return hailo_vdma_continuous_buffer_free_ioctl(context, controller, arg); + case HAILO_VDMA_LAUNCH_TRANSFER: + return hailo_vdma_launch_transfer_ioctl(context, controller, arg); + default: + hailo_dev_err(controller->dev, "Invalid vDMA ioctl code 0x%x (nr: %d)\n", cmd, _IOC_NR(cmd)); + return -ENOTTY; + } +} + +static int low_memory_buffer_mmap(struct hailo_vdma_controller *controller, + struct hailo_vdma_low_memory_buffer *vdma_buffer, struct vm_area_struct *vma) +{ + int err = 0; + size_t i = 0; + unsigned long vsize = vma->vm_end - vma->vm_start; + unsigned long orig_vm_start = vma->vm_start; + unsigned long orig_vm_end = vma->vm_end; + unsigned long page_fn = 0; + + if (vsize != vdma_buffer->pages_count * PAGE_SIZE) { + hailo_dev_err(controller->dev, "mmap size should be %lu (given %lu)\n", + vdma_buffer->pages_count * PAGE_SIZE, vsize); + return -EINVAL; + } + + for (i = 0 ; i < vdma_buffer->pages_count ; i++) { + if (i > 0) { + vma->vm_start = vma->vm_end; + } + vma->vm_end = vma->vm_start + PAGE_SIZE; + + page_fn = virt_to_phys(vdma_buffer->pages_address[i]) >> PAGE_SHIFT ; + err = remap_pfn_range(vma, vma->vm_start, page_fn, PAGE_SIZE, vma->vm_page_prot); + + if (err != 0) { + hailo_dev_err(controller->dev, " fops_mmap failed mapping kernel page %d\n", err); + return err; + } + } + + vma->vm_start = orig_vm_start; + vma->vm_end = orig_vm_end; + + return 0; +} + +static int continuous_buffer_mmap(struct hailo_vdma_controller *controller, + struct hailo_vdma_continuous_buffer *buffer, struct vm_area_struct *vma) +{ + int err = 0; + const unsigned long vsize = vma->vm_end - vma->vm_start; + + if (vsize > buffer->size) { + hailo_dev_err(controller->dev, "mmap size should be less than %zu (given %lu)\n", + buffer->size, vsize); + return -EINVAL; + } + + err = dma_mmap_coherent(controller->dev, vma, buffer->kernel_address, + buffer->dma_address, vsize); + if (err < 0) { + hailo_dev_err(controller->dev, " vdma_mmap failed dma_mmap_coherent %d\n", err); + return err; + } + + return 0; +} + +int hailo_vdma_mmap(struct hailo_vdma_file_context *context, struct hailo_vdma_controller *controller, + struct vm_area_struct *vma, uintptr_t vdma_handle) +{ + struct hailo_vdma_low_memory_buffer *low_memory_buffer = NULL; + struct hailo_vdma_continuous_buffer *continuous_buffer = NULL; + + hailo_dev_info(controller->dev, "Map vdma_handle %llu\n", (u64)vdma_handle); + if (NULL != (low_memory_buffer = hailo_vdma_find_low_memory_buffer(context, vdma_handle))) { + return low_memory_buffer_mmap(controller, low_memory_buffer, vma); + } + else if (NULL != (continuous_buffer = hailo_vdma_find_continuous_buffer(context, vdma_handle))) { + return continuous_buffer_mmap(controller, continuous_buffer, vma); + } + else { + hailo_dev_err(controller->dev, "Can't mmap vdma handle: %llu (not existing)\n", (u64)vdma_handle); + return -EINVAL; + } +} + +enum dma_data_direction get_dma_direction(enum hailo_dma_data_direction hailo_direction) +{ + switch (hailo_direction) { + case HAILO_DMA_BIDIRECTIONAL: + return DMA_BIDIRECTIONAL; + case HAILO_DMA_TO_DEVICE: + return DMA_TO_DEVICE; + case HAILO_DMA_FROM_DEVICE: + return DMA_FROM_DEVICE; + default: + pr_err("Invalid hailo direction %d\n", hailo_direction); + return DMA_NONE; + } +} diff --git a/drivers/media/pci/hailo/vdma/vdma.h b/drivers/media/pci/hailo/vdma/vdma.h new file mode 100644 index 00000000000000..d0e693e879c346 --- /dev/null +++ b/drivers/media/pci/hailo/vdma/vdma.h @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved. + **/ +/** + * Hailo vdma engine definitions + */ + +#ifndef _HAILO_VDMA_VDMA_H_ +#define _HAILO_VDMA_VDMA_H_ + +#include "hailo_ioctl_common.h" +#include "hailo_resource.h" +#include "vdma_common.h" + +#include <linux/dma-mapping.h> +#include <linux/types.h> +#include <linux/semaphore.h> +#include <linux/dma-buf.h> +#include <linux/version.h> + +#define VDMA_CHANNEL_CONTROL_REG_OFFSET(channel_index, direction) (((direction) == DMA_TO_DEVICE) ? \ + (((channel_index) << 5) + 0x0) : (((channel_index) << 5) + 0x10)) +#define VDMA_CHANNEL_CONTROL_REG_ADDRESS(vdma_registers, channel_index, direction) \ + ((u8*)((vdma_registers)->address) + VDMA_CHANNEL_CONTROL_REG_OFFSET(channel_index, direction)) + +#define VDMA_CHANNEL_NUM_PROC_OFFSET(channel_index, direction) (((direction) == DMA_TO_DEVICE) ? \ + (((channel_index) << 5) + 0x4) : (((channel_index) << 5) + 0x14)) +#define VDMA_CHANNEL_NUM_PROC_ADDRESS(vdma_registers, channel_index, direction) \ + ((u8*)((vdma_registers)->address) + VDMA_CHANNEL_NUM_PROC_OFFSET(channel_index, direction)) + + +// dmabuf is supported from linux kernel version 3.3 +#if LINUX_VERSION_CODE < KERNEL_VERSION( 3, 3, 0 ) +// Make dummy struct with one byte (C standards does not allow empty struct) - in order to not have to ifdef everywhere +struct hailo_dmabuf_info { + uint8_t dummy; +}; +#else +// dmabuf_sg_table is needed because in dma_buf_unmap_attachment() the sg_table's address has to match the +// The one returned from dma_buf_map_attachment() - otherwise we would need to malloc each time +struct hailo_dmabuf_info { + struct dma_buf *dmabuf; + struct dma_buf_attachment *dmabuf_attachment; + struct sg_table *dmabuf_sg_table; +}; +#endif // LINUX_VERSION_CODE < KERNEL_VERSION( 3, 3, 0 ) + +struct hailo_vdma_buffer { + struct list_head mapped_user_buffer_list; + size_t handle; + + struct kref kref; + struct device *device; + + uintptr_t user_address; + u32 size; + enum dma_data_direction data_direction; + struct sg_table sg_table; + + // If this flag is set, the buffer pointed by sg_table is not backed by + // 'struct page' (only by pure pfn). On this case, accessing to the page, + // or calling APIs that access the page (e.g. dma_sync_sg_for_cpu) is not + // allowed. + bool is_mmio; + + // Relevant paramaters that need to be saved in case of dmabuf - otherwise struct pointers will be NULL + struct hailo_dmabuf_info dmabuf_info; +}; + +// Continuous buffer that holds a descriptor list. +struct hailo_descriptors_list_buffer { + struct list_head descriptors_buffer_list; + uintptr_t handle; + void *kernel_address; + dma_addr_t dma_address; + u32 buffer_size; + struct hailo_vdma_descriptors_list desc_list; +}; + +struct hailo_vdma_low_memory_buffer { + struct list_head vdma_low_memory_buffer_list; + uintptr_t handle; + size_t pages_count; + void **pages_address; +}; + +struct hailo_vdma_continuous_buffer { + struct list_head continuous_buffer_list; + uintptr_t handle; + void *kernel_address; + dma_addr_t dma_address; + size_t size; +}; + +struct hailo_vdma_controller; +struct hailo_vdma_controller_ops { + void (*update_channel_interrupts)(struct hailo_vdma_controller *controller, size_t engine_index, + u32 channels_bitmap); +}; + +struct hailo_vdma_controller { + struct hailo_vdma_hw *hw; + struct hailo_vdma_controller_ops *ops; + struct device *dev; + + size_t vdma_engines_count; + struct hailo_vdma_engine *vdma_engines; + + spinlock_t interrupts_lock; + wait_queue_head_t interrupts_wq; + + struct file *used_by_filp; + + // Putting big IOCTL structures here to avoid stack allocation. + struct hailo_vdma_interrupts_read_timestamp_params read_interrupt_timestamps_params; +}; + +#define for_each_vdma_engine(controller, engine, engine_index) \ + _for_each_element_array(controller->vdma_engines, controller->vdma_engines_count, \ + engine, engine_index) + +struct hailo_vdma_file_context { + atomic_t last_vdma_user_buffer_handle; + struct list_head mapped_user_buffer_list; + + // Last_vdma_handle works as a handle for vdma decriptor list and for the vdma buffer - + // there will be no collisions between the two + atomic_t last_vdma_handle; + struct list_head descriptors_buffer_list; + struct list_head vdma_low_memory_buffer_list; + struct list_head continuous_buffer_list; + u32 enabled_channels_bitmap[MAX_VDMA_ENGINES]; +}; + + +int hailo_vdma_controller_init(struct hailo_vdma_controller *controller, + struct device *dev, struct hailo_vdma_hw *vdma_hw, + struct hailo_vdma_controller_ops *ops, + struct hailo_resource *channel_registers_per_engine, size_t engines_count); + +void hailo_vdma_update_interrupts_mask(struct hailo_vdma_controller *controller, + size_t engine_index); + +void hailo_vdma_file_context_init(struct hailo_vdma_file_context *context); +void hailo_vdma_file_context_finalize(struct hailo_vdma_file_context *context, + struct hailo_vdma_controller *controller, struct file *filp); + +void hailo_vdma_wakeup_interrupts(struct hailo_vdma_controller *controller, struct hailo_vdma_engine *engine, + u32 channels_bitmap); +void hailo_vdma_irq_handler(struct hailo_vdma_controller *controller, size_t engine_index, + u32 channels_bitmap); + +// TODO: reduce params count +long hailo_vdma_ioctl(struct hailo_vdma_file_context *context, struct hailo_vdma_controller *controller, + unsigned int cmd, unsigned long arg, struct file *filp, struct semaphore *mutex, bool *should_up_board_mutex); + +int hailo_vdma_mmap(struct hailo_vdma_file_context *context, struct hailo_vdma_controller *controller, + struct vm_area_struct *vma, uintptr_t vdma_handle); + +enum dma_data_direction get_dma_direction(enum hailo_dma_data_direction hailo_direction); +void hailo_vdma_disable_vdma_channels(struct hailo_vdma_controller *controller, const bool should_close_channels); + +#endif /* _HAILO_VDMA_VDMA_H_ */ \ No newline at end of file diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig index 85d2627776b6a4..9356334956fc32 100644 --- a/drivers/media/platform/Kconfig +++ b/drivers/media/platform/Kconfig @@ -67,6 +67,7 @@ source "drivers/media/platform/amlogic/Kconfig" source "drivers/media/platform/amphion/Kconfig" source "drivers/media/platform/aspeed/Kconfig" source "drivers/media/platform/atmel/Kconfig" +source "drivers/media/platform/bcm2835/Kconfig" source "drivers/media/platform/broadcom/Kconfig" source "drivers/media/platform/cadence/Kconfig" source "drivers/media/platform/chips-media/Kconfig" diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile index ace4e34483ddce..0f94e5830db242 100644 --- a/drivers/media/platform/Makefile +++ b/drivers/media/platform/Makefile @@ -10,6 +10,7 @@ obj-y += amlogic/ obj-y += amphion/ obj-y += aspeed/ obj-y += atmel/ +obj-y += bcm2835/ obj-y += broadcom/ obj-y += cadence/ obj-y += chips-media/ diff --git a/drivers/media/platform/bcm2835/Kconfig b/drivers/media/platform/bcm2835/Kconfig new file mode 100644 index 00000000000000..09d20dd524521e --- /dev/null +++ b/drivers/media/platform/bcm2835/Kconfig @@ -0,0 +1,24 @@ +# Broadcom VideoCore4 V4L2 camera support + +config VIDEO_BCM2835_UNICAM_LEGACY + tristate "Broadcom BCM283x/BCM271x Unicam video capture driver - no MC" + depends on VIDEO_DEV + depends on ARCH_BCM2835 || COMPILE_TEST + select VIDEO_V4L2_SUBDEV_API + select MEDIA_CONTROLLER + select VIDEOBUF2_DMA_CONTIG + select V4L2_FWNODE + help + Say Y here to enable support for the BCM283x/BCM271x CSI-2 receiver. + This is a V4L2 driver that controls the CSI-2 receiver directly, + independently from the VC4 firmware. + This is the downstream version of this driver that still supports + being driven from the video node for simple devices. The mainline + driver only supports using Media Controller. + This driver is mutually exclusive with the use of bcm2835-camera. The + firmware will disable all access to the peripheral from within the + firmware if it finds a DT node using it, and bcm2835-camera will + therefore fail to probe. + + To compile this driver as a module, choose M here. The module will be + called bcm2835-unicam. diff --git a/drivers/media/platform/bcm2835/Makefile b/drivers/media/platform/bcm2835/Makefile new file mode 100644 index 00000000000000..55a48691f51352 --- /dev/null +++ b/drivers/media/platform/bcm2835/Makefile @@ -0,0 +1,4 @@ +# Makefile for BCM2835 Unicam driver + +bcm2835-unicam-legacy-y := bcm2835-unicam.o +obj-$(CONFIG_VIDEO_BCM2835_UNICAM_LEGACY) += bcm2835-unicam-legacy.o diff --git a/drivers/media/platform/bcm2835/bcm2835-unicam.c b/drivers/media/platform/bcm2835/bcm2835-unicam.c new file mode 100644 index 00000000000000..1c4ae29d69a074 --- /dev/null +++ b/drivers/media/platform/bcm2835/bcm2835-unicam.c @@ -0,0 +1,3528 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * BCM283x / BCM271x Unicam Capture Driver + * + * Copyright (C) 2017-2020 - Raspberry Pi (Trading) Ltd. + * + * Dave Stevenson <dave.stevenson@raspberrypi.com> + * + * Based on TI am437x driver by + * Benoit Parrot <bparrot@ti.com> + * Lad, Prabhakar <prabhakar.csengg@gmail.com> + * + * and TI CAL camera interface driver by + * Benoit Parrot <bparrot@ti.com> + * + * + * There are two camera drivers in the kernel for BCM283x - this one + * and bcm2835-camera (currently in staging). + * + * This driver directly controls the Unicam peripheral - there is no + * involvement with the VideoCore firmware. Unicam receives CSI-2 or + * CCP2 data and writes it into SDRAM. + * The only potential processing options are to repack Bayer data into an + * alternate format, and applying windowing. + * The repacking does not shift the data, so can repack V4L2_PIX_FMT_Sxxxx10P + * to V4L2_PIX_FMT_Sxxxx10, or V4L2_PIX_FMT_Sxxxx12P to V4L2_PIX_FMT_Sxxxx12, + * but not generically up to V4L2_PIX_FMT_Sxxxx16. The driver will add both + * formats where the relevant formats are defined, and will automatically + * configure the repacking as required. + * Support for windowing may be added later. + * + * It should be possible to connect this driver to any sensor with a + * suitable output interface and V4L2 subdevice driver. + * + * bcm2835-camera uses the VideoCore firmware to control the sensor, + * Unicam, ISP, and all tuner control loops. Fully processed frames are + * delivered to the driver by the firmware. It only has sensor drivers + * for Omnivision OV5647, and Sony IMX219 sensors. + * + * The two drivers are mutually exclusive for the same Unicam instance. + * The VideoCore firmware checks the device tree configuration during boot. + * If it finds device tree nodes called csi0 or csi1 it will block the + * firmware from accessing the peripheral, and bcm2835-camera will + * not be able to stream data. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/err.h> +#include <linux/gpio/consumer.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/of_graph.h> +#include <linux/pinctrl/consumer.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/videodev2.h> + +#include <media/mipi-csi2.h> +#include <media/v4l2-common.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-dev.h> +#include <media/v4l2-device.h> +#include <media/v4l2-dv-timings.h> +#include <media/v4l2-event.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-fwnode.h> +#include <media/videobuf2-dma-contig.h> + +#include <media/v4l2-async.h> + +#include "vc4-regs-unicam.h" + +#define UNICAM_MODULE_NAME "unicam" +#define UNICAM_VERSION "0.1.0" + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Debug level 0-3"); + +static int media_controller; +module_param(media_controller, int, 0644); +MODULE_PARM_DESC(media_controller, "Use media controller API"); + +#define unicam_dbg(level, dev, fmt, arg...) \ + v4l2_dbg(level, debug, &(dev)->v4l2_dev, fmt, ##arg) +#define unicam_info(dev, fmt, arg...) \ + v4l2_info(&(dev)->v4l2_dev, fmt, ##arg) +#define unicam_err(dev, fmt, arg...) \ + v4l2_err(&(dev)->v4l2_dev, fmt, ##arg) + +/* + * Unicam must request a minimum of 250Mhz from the VPU clock. + * Otherwise the input FIFOs overrun and cause image corruption. + */ +#define MIN_VPU_CLOCK_RATE (250 * 1000 * 1000) +/* + * To protect against a dodgy sensor driver never returning an error from + * enum_mbus_code, set a maximum index value to be used. + */ +#define MAX_ENUM_MBUS_CODE 128 + +/* + * Stride is a 16 bit register, but also has to be a multiple of 32. + */ +#define BPL_ALIGNMENT 32 +#define MAX_BYTESPERLINE ((1 << 16) - BPL_ALIGNMENT) +/* + * Max width is therefore determined by the max stride divided by + * the number of bits per pixel. Take 32bpp as a + * worst case. + * No imposed limit on the height, so adopt a square image for want + * of anything better. + */ +#define MAX_WIDTH (MAX_BYTESPERLINE / 4) +#define MAX_HEIGHT MAX_WIDTH +/* Define a nominal minimum image size */ +#define MIN_WIDTH 16 +#define MIN_HEIGHT 16 +/* Default size of the embedded buffer */ +#define UNICAM_EMBEDDED_SIZE 16384 + +/* + * Size of the dummy buffer allocation. + * + * Due to a HW bug causing buffer overruns in circular buffer mode under certain + * (not yet fully known) conditions, the dummy buffer allocation is set to a + * a single page size, but the hardware gets programmed with a buffer size of 0. + */ +#define DUMMY_BUF_SIZE (PAGE_SIZE) + +enum pad_types { + IMAGE_PAD, + METADATA_PAD, + MAX_NODES +}; + +#define MASK_CS_DEFAULT BIT(V4L2_COLORSPACE_DEFAULT) +#define MASK_CS_SMPTE170M BIT(V4L2_COLORSPACE_SMPTE170M) +#define MASK_CS_SMPTE240M BIT(V4L2_COLORSPACE_SMPTE240M) +#define MASK_CS_REC709 BIT(V4L2_COLORSPACE_REC709) +#define MASK_CS_BT878 BIT(V4L2_COLORSPACE_BT878) +#define MASK_CS_470_M BIT(V4L2_COLORSPACE_470_SYSTEM_M) +#define MASK_CS_470_BG BIT(V4L2_COLORSPACE_470_SYSTEM_BG) +#define MASK_CS_JPEG BIT(V4L2_COLORSPACE_JPEG) +#define MASK_CS_SRGB BIT(V4L2_COLORSPACE_SRGB) +#define MASK_CS_OPRGB BIT(V4L2_COLORSPACE_OPRGB) +#define MASK_CS_BT2020 BIT(V4L2_COLORSPACE_BT2020) +#define MASK_CS_RAW BIT(V4L2_COLORSPACE_RAW) +#define MASK_CS_DCI_P3 BIT(V4L2_COLORSPACE_DCI_P3) + +#define MAX_COLORSPACE 32 + +/* + * struct unicam_fmt - Unicam media bus format information + * @pixelformat: V4L2 pixel format FCC identifier. 0 if n/a. + * @repacked_fourcc: V4L2 pixel format FCC identifier if the data is expanded + * out to 16bpp. 0 if n/a. + * @code: V4L2 media bus format code. + * @depth: Bits per pixel as delivered from the source. + * @csi_dt: CSI data type. + * @valid_colorspaces: Bitmask of valid colorspaces so that the Media Controller + * centric try_fmt can validate the colorspace and pass + * v4l2-compliance. + * @check_variants: Flag to denote that there are multiple mediabus formats + * still in the list that could match this V4L2 format. + * @mc_skip: Media Controller shouldn't list this format via ENUM_FMT as it is + * a duplicate of an earlier format. + * @metadata_fmt: This format only applies to the metadata pad. + */ +struct unicam_fmt { + u32 fourcc; + u32 repacked_fourcc; + u32 code; + u8 depth; + u8 csi_dt; + u32 valid_colorspaces; + u8 check_variants:1; + u8 mc_skip:1; + u8 metadata_fmt:1; +}; + +static const struct unicam_fmt formats[] = { + /* YUV Formats */ + { + .fourcc = V4L2_PIX_FMT_YUYV, + .code = MEDIA_BUS_FMT_YUYV8_2X8, + .depth = 16, + .csi_dt = MIPI_CSI2_DT_YUV422_8B, + .check_variants = 1, + .valid_colorspaces = MASK_CS_SMPTE170M | MASK_CS_REC709 | + MASK_CS_JPEG, + }, { + .fourcc = V4L2_PIX_FMT_UYVY, + .code = MEDIA_BUS_FMT_UYVY8_2X8, + .depth = 16, + .csi_dt = MIPI_CSI2_DT_YUV422_8B, + .check_variants = 1, + .valid_colorspaces = MASK_CS_SMPTE170M | MASK_CS_REC709 | + MASK_CS_JPEG, + }, { + .fourcc = V4L2_PIX_FMT_YVYU, + .code = MEDIA_BUS_FMT_YVYU8_2X8, + .depth = 16, + .csi_dt = MIPI_CSI2_DT_YUV422_8B, + .check_variants = 1, + .valid_colorspaces = MASK_CS_SMPTE170M | MASK_CS_REC709 | + MASK_CS_JPEG, + }, { + .fourcc = V4L2_PIX_FMT_VYUY, + .code = MEDIA_BUS_FMT_VYUY8_2X8, + .depth = 16, + .csi_dt = MIPI_CSI2_DT_YUV422_8B, + .check_variants = 1, + .valid_colorspaces = MASK_CS_SMPTE170M | MASK_CS_REC709 | + MASK_CS_JPEG, + }, { + .fourcc = V4L2_PIX_FMT_YUYV, + .code = MEDIA_BUS_FMT_YUYV8_1X16, + .depth = 16, + .csi_dt = MIPI_CSI2_DT_YUV422_8B, + .mc_skip = 1, + .valid_colorspaces = MASK_CS_SMPTE170M | MASK_CS_REC709 | + MASK_CS_JPEG, + }, { + .fourcc = V4L2_PIX_FMT_UYVY, + .code = MEDIA_BUS_FMT_UYVY8_1X16, + .depth = 16, + .csi_dt = MIPI_CSI2_DT_YUV422_8B, + .mc_skip = 1, + .valid_colorspaces = MASK_CS_SMPTE170M | MASK_CS_REC709 | + MASK_CS_JPEG, + }, { + .fourcc = V4L2_PIX_FMT_YVYU, + .code = MEDIA_BUS_FMT_YVYU8_1X16, + .depth = 16, + .csi_dt = MIPI_CSI2_DT_YUV422_8B, + .mc_skip = 1, + .valid_colorspaces = MASK_CS_SMPTE170M | MASK_CS_REC709 | + MASK_CS_JPEG, + }, { + .fourcc = V4L2_PIX_FMT_VYUY, + .code = MEDIA_BUS_FMT_VYUY8_1X16, + .depth = 16, + .csi_dt = MIPI_CSI2_DT_YUV422_8B, + .mc_skip = 1, + .valid_colorspaces = MASK_CS_SMPTE170M | MASK_CS_REC709 | + MASK_CS_JPEG, + }, { + /* RGB Formats */ + .fourcc = V4L2_PIX_FMT_RGB565, /* gggbbbbb rrrrrggg */ + .code = MEDIA_BUS_FMT_RGB565_2X8_LE, + .depth = 16, + .csi_dt = MIPI_CSI2_DT_RGB565, + .valid_colorspaces = MASK_CS_SRGB, + }, { + .fourcc = V4L2_PIX_FMT_RGB565X, /* rrrrrggg gggbbbbb */ + .code = MEDIA_BUS_FMT_RGB565_2X8_BE, + .depth = 16, + .csi_dt = MIPI_CSI2_DT_RGB565, + .valid_colorspaces = MASK_CS_SRGB, + }, { + .fourcc = V4L2_PIX_FMT_RGB555, /* gggbbbbb arrrrrgg */ + .code = MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE, + .depth = 16, + .csi_dt = MIPI_CSI2_DT_RGB555, + .valid_colorspaces = MASK_CS_SRGB, + }, { + .fourcc = V4L2_PIX_FMT_RGB555X, /* arrrrrgg gggbbbbb */ + .code = MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE, + .depth = 16, + .csi_dt = MIPI_CSI2_DT_RGB555, + .valid_colorspaces = MASK_CS_SRGB, + }, { + .fourcc = V4L2_PIX_FMT_RGB24, /* rgb */ + .code = MEDIA_BUS_FMT_RGB888_1X24, + .depth = 24, + .csi_dt = MIPI_CSI2_DT_RGB888, + .valid_colorspaces = MASK_CS_SRGB, + }, { + .fourcc = V4L2_PIX_FMT_BGR24, /* bgr */ + .code = MEDIA_BUS_FMT_BGR888_1X24, + .depth = 24, + .csi_dt = MIPI_CSI2_DT_RGB888, + .valid_colorspaces = MASK_CS_SRGB, + }, { + .fourcc = V4L2_PIX_FMT_RGB32, /* argb */ + .code = MEDIA_BUS_FMT_ARGB8888_1X32, + .depth = 32, + .csi_dt = 0x0, + .valid_colorspaces = MASK_CS_SRGB, + }, { + /* Bayer Formats */ + .fourcc = V4L2_PIX_FMT_SBGGR8, + .code = MEDIA_BUS_FMT_SBGGR8_1X8, + .depth = 8, + .csi_dt = MIPI_CSI2_DT_RAW8, + .valid_colorspaces = MASK_CS_RAW, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG8, + .code = MEDIA_BUS_FMT_SGBRG8_1X8, + .depth = 8, + .csi_dt = MIPI_CSI2_DT_RAW8, + .valid_colorspaces = MASK_CS_RAW, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG8, + .code = MEDIA_BUS_FMT_SGRBG8_1X8, + .depth = 8, + .csi_dt = MIPI_CSI2_DT_RAW8, + .valid_colorspaces = MASK_CS_RAW, + }, { + .fourcc = V4L2_PIX_FMT_SRGGB8, + .code = MEDIA_BUS_FMT_SRGGB8_1X8, + .depth = 8, + .csi_dt = MIPI_CSI2_DT_RAW8, + .valid_colorspaces = MASK_CS_RAW, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR10P, + .repacked_fourcc = V4L2_PIX_FMT_SBGGR10, + .code = MEDIA_BUS_FMT_SBGGR10_1X10, + .depth = 10, + .csi_dt = MIPI_CSI2_DT_RAW10, + .valid_colorspaces = MASK_CS_RAW, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG10P, + .repacked_fourcc = V4L2_PIX_FMT_SGBRG10, + .code = MEDIA_BUS_FMT_SGBRG10_1X10, + .depth = 10, + .csi_dt = MIPI_CSI2_DT_RAW10, + .valid_colorspaces = MASK_CS_RAW, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG10P, + .repacked_fourcc = V4L2_PIX_FMT_SGRBG10, + .code = MEDIA_BUS_FMT_SGRBG10_1X10, + .depth = 10, + .csi_dt = MIPI_CSI2_DT_RAW10, + .valid_colorspaces = MASK_CS_RAW, + }, { + .fourcc = V4L2_PIX_FMT_SRGGB10P, + .repacked_fourcc = V4L2_PIX_FMT_SRGGB10, + .code = MEDIA_BUS_FMT_SRGGB10_1X10, + .depth = 10, + .csi_dt = MIPI_CSI2_DT_RAW10, + .valid_colorspaces = MASK_CS_RAW, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR12P, + .repacked_fourcc = V4L2_PIX_FMT_SBGGR12, + .code = MEDIA_BUS_FMT_SBGGR12_1X12, + .depth = 12, + .csi_dt = MIPI_CSI2_DT_RAW12, + .valid_colorspaces = MASK_CS_RAW, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG12P, + .repacked_fourcc = V4L2_PIX_FMT_SGBRG12, + .code = MEDIA_BUS_FMT_SGBRG12_1X12, + .depth = 12, + .csi_dt = MIPI_CSI2_DT_RAW12, + .valid_colorspaces = MASK_CS_RAW, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG12P, + .repacked_fourcc = V4L2_PIX_FMT_SGRBG12, + .code = MEDIA_BUS_FMT_SGRBG12_1X12, + .depth = 12, + .csi_dt = MIPI_CSI2_DT_RAW12, + .valid_colorspaces = MASK_CS_RAW, + }, { + .fourcc = V4L2_PIX_FMT_SRGGB12P, + .repacked_fourcc = V4L2_PIX_FMT_SRGGB12, + .code = MEDIA_BUS_FMT_SRGGB12_1X12, + .depth = 12, + .csi_dt = MIPI_CSI2_DT_RAW12, + .valid_colorspaces = MASK_CS_RAW, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR14P, + .repacked_fourcc = V4L2_PIX_FMT_SBGGR14, + .code = MEDIA_BUS_FMT_SBGGR14_1X14, + .depth = 14, + .csi_dt = MIPI_CSI2_DT_RAW14, + .valid_colorspaces = MASK_CS_RAW, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG14P, + .repacked_fourcc = V4L2_PIX_FMT_SGBRG14, + .code = MEDIA_BUS_FMT_SGBRG14_1X14, + .depth = 14, + .csi_dt = MIPI_CSI2_DT_RAW14, + .valid_colorspaces = MASK_CS_RAW, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG14P, + .repacked_fourcc = V4L2_PIX_FMT_SGRBG14, + .code = MEDIA_BUS_FMT_SGRBG14_1X14, + .depth = 14, + .csi_dt = MIPI_CSI2_DT_RAW14, + .valid_colorspaces = MASK_CS_RAW, + }, { + .fourcc = V4L2_PIX_FMT_SRGGB14P, + .repacked_fourcc = V4L2_PIX_FMT_SRGGB14, + .code = MEDIA_BUS_FMT_SRGGB14_1X14, + .depth = 14, + .csi_dt = MIPI_CSI2_DT_RAW14, + .valid_colorspaces = MASK_CS_RAW, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR16, + .code = MEDIA_BUS_FMT_SBGGR16_1X16, + .depth = 16, + .csi_dt = MIPI_CSI2_DT_RAW16, + .valid_colorspaces = MASK_CS_RAW, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG16, + .code = MEDIA_BUS_FMT_SGBRG16_1X16, + .depth = 16, + .csi_dt = MIPI_CSI2_DT_RAW16, + .valid_colorspaces = MASK_CS_RAW, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG16, + .code = MEDIA_BUS_FMT_SGRBG16_1X16, + .depth = 16, + .csi_dt = MIPI_CSI2_DT_RAW16, + .valid_colorspaces = MASK_CS_RAW, + }, { + .fourcc = V4L2_PIX_FMT_SRGGB16, + .code = MEDIA_BUS_FMT_SRGGB16_1X16, + .depth = 16, + .csi_dt = MIPI_CSI2_DT_RAW16, + .valid_colorspaces = MASK_CS_RAW, + }, { + + /* Greyscale formats */ + .fourcc = V4L2_PIX_FMT_GREY, + .code = MEDIA_BUS_FMT_Y8_1X8, + .depth = 8, + .csi_dt = MIPI_CSI2_DT_RAW8, + .valid_colorspaces = MASK_CS_RAW, + }, { + .fourcc = V4L2_PIX_FMT_Y10P, + .repacked_fourcc = V4L2_PIX_FMT_Y10, + .code = MEDIA_BUS_FMT_Y10_1X10, + .depth = 10, + .csi_dt = MIPI_CSI2_DT_RAW10, + .valid_colorspaces = MASK_CS_RAW, + }, { + .fourcc = V4L2_PIX_FMT_Y12P, + .repacked_fourcc = V4L2_PIX_FMT_Y12, + .code = MEDIA_BUS_FMT_Y12_1X12, + .depth = 12, + .csi_dt = MIPI_CSI2_DT_RAW12, + }, { + .fourcc = V4L2_PIX_FMT_Y14P, + .repacked_fourcc = V4L2_PIX_FMT_Y14, + .code = MEDIA_BUS_FMT_Y14_1X14, + .depth = 14, + .csi_dt = MIPI_CSI2_DT_RAW14, + .valid_colorspaces = MASK_CS_RAW, + }, { + .fourcc = V4L2_PIX_FMT_Y16, + .code = MEDIA_BUS_FMT_Y16_1X16, + .depth = 16, + .csi_dt = MIPI_CSI2_DT_RAW16, + .valid_colorspaces = MASK_CS_RAW, + }, + /* Embedded data format */ + { + .fourcc = V4L2_META_FMT_SENSOR_DATA, + .code = MEDIA_BUS_FMT_SENSOR_DATA, + .depth = 8, + .metadata_fmt = 1, + } +}; + +struct unicam_buffer { + struct vb2_v4l2_buffer vb; + struct list_head list; +}; + +static inline struct unicam_buffer *to_unicam_buffer(struct vb2_buffer *vb) +{ + return container_of(vb, struct unicam_buffer, vb.vb2_buf); +} + +struct unicam_node { + bool registered; + int open; + bool streaming; + unsigned int pad_id; + /* Source pad id on the sensor for this node */ + unsigned int src_pad_id; + /* Pointer pointing to current v4l2_buffer */ + struct unicam_buffer *cur_frm; + /* Pointer pointing to next v4l2_buffer */ + struct unicam_buffer *next_frm; + /* video capture */ + const struct unicam_fmt *fmt; + /* Used to store current pixel format */ + struct v4l2_format v_fmt; + /* Used to store current mbus frame format */ + struct v4l2_mbus_framefmt m_fmt; + /* Buffer queue used in video-buf */ + struct vb2_queue buffer_queue; + /* Queue of filled frames */ + struct list_head dma_queue; + /* IRQ lock for DMA queue */ + spinlock_t dma_queue_lock; + /* lock used to access this structure */ + struct mutex lock; + /* Identifies video device for this channel */ + struct video_device video_dev; + /* Pointer to the parent handle */ + struct unicam_device *dev; + struct media_pad pad; + unsigned int embedded_lines; + struct media_pipeline pipe; + /* + * Dummy buffer intended to be used by unicam + * if we have no other queued buffers to swap to. + */ + void *dummy_buf_cpu_addr; + dma_addr_t dummy_buf_dma_addr; +}; + +struct unicam_device { + struct kref kref; + + /* V4l2 specific parameters */ + struct v4l2_async_connection *asd; + + /* peripheral base address */ + void __iomem *base; + /* clock gating base address */ + void __iomem *clk_gate_base; + /* lp clock handle */ + struct clk *clock; + /* vpu clock handle */ + struct clk *vpu_clock; + /* clock status for error handling */ + bool clocks_enabled; + /* V4l2 device */ + struct v4l2_device v4l2_dev; + struct media_device mdev; + + struct gpio_desc *sync_gpio; + + /* parent device */ + struct platform_device *pdev; + /* subdevice async Notifier */ + struct v4l2_async_notifier notifier; + unsigned int sequence; + bool frame_started; + + /* ptr to sub device */ + struct v4l2_subdev *sensor; + /* Pad config for the sensor */ + struct v4l2_subdev_state *sensor_state; + + enum v4l2_mbus_type bus_type; + /* + * Stores bus.mipi_csi2.flags for CSI2 sensors, or + * bus.mipi_csi1.strobe for CCP2. + */ + unsigned int bus_flags; + unsigned int max_data_lanes; + unsigned int active_data_lanes; + bool sensor_embedded_data; + + struct unicam_node node[MAX_NODES]; + struct v4l2_ctrl_handler ctrl_handler; + + bool mc_api; +}; + +static inline struct unicam_device * +to_unicam_device(struct v4l2_device *v4l2_dev) +{ + return container_of(v4l2_dev, struct unicam_device, v4l2_dev); +} + +/* Hardware access */ +static inline void clk_write(struct unicam_device *dev, u32 val) +{ + writel(val | 0x5a000000, dev->clk_gate_base); +} + +static inline u32 reg_read(struct unicam_device *dev, u32 offset) +{ + return readl(dev->base + offset); +} + +static inline void reg_write(struct unicam_device *dev, u32 offset, u32 val) +{ + writel(val, dev->base + offset); +} + +static inline int get_field(u32 value, u32 mask) +{ + return (value & mask) >> __ffs(mask); +} + +static inline void set_field(u32 *valp, u32 field, u32 mask) +{ + u32 val = *valp; + + val &= ~mask; + val |= (field << __ffs(mask)) & mask; + *valp = val; +} + +static inline u32 reg_read_field(struct unicam_device *dev, u32 offset, + u32 mask) +{ + return get_field(reg_read(dev, offset), mask); +} + +static inline void reg_write_field(struct unicam_device *dev, u32 offset, + u32 field, u32 mask) +{ + u32 val = reg_read(dev, offset); + + set_field(&val, field, mask); + reg_write(dev, offset, val); +} + +/* Power management functions */ +static inline int unicam_runtime_get(struct unicam_device *dev) +{ + return pm_runtime_get_sync(&dev->pdev->dev); +} + +static inline void unicam_runtime_put(struct unicam_device *dev) +{ + pm_runtime_put_sync(&dev->pdev->dev); +} + +/* Format setup functions */ +static const struct unicam_fmt *find_format_by_code(u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(formats); i++) { + if (formats[i].code == code) + return &formats[i]; + } + + return NULL; +} + +static int check_mbus_format(struct unicam_device *dev, + const struct unicam_fmt *format) +{ + unsigned int i; + int ret = 0; + + for (i = 0; !ret && i < MAX_ENUM_MBUS_CODE; i++) { + struct v4l2_subdev_mbus_code_enum mbus_code = { + .index = i, + .pad = IMAGE_PAD, + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + + ret = v4l2_subdev_call(dev->sensor, pad, enum_mbus_code, + NULL, &mbus_code); + + if (!ret && mbus_code.code == format->code) + return 1; + } + + return 0; +} + +static const struct unicam_fmt *find_format_by_pix(struct unicam_device *dev, + u32 pixelformat) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(formats); i++) { + if (formats[i].fourcc == pixelformat || + formats[i].repacked_fourcc == pixelformat) { + if (formats[i].check_variants && + !check_mbus_format(dev, &formats[i])) + continue; + return &formats[i]; + } + } + + return NULL; +} + +static unsigned int bytes_per_line(u32 width, const struct unicam_fmt *fmt, + u32 v4l2_fourcc) +{ + if (v4l2_fourcc == fmt->repacked_fourcc) + /* Repacking always goes to 16bpp */ + return ALIGN(width << 1, BPL_ALIGNMENT); + else + return ALIGN((width * fmt->depth) >> 3, BPL_ALIGNMENT); +} + +static int __subdev_get_format(struct unicam_device *dev, + struct v4l2_mbus_framefmt *fmt, int pad_id) +{ + struct v4l2_subdev_format sd_fmt = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + .pad = dev->node[pad_id].src_pad_id, + }; + int ret; + + ret = v4l2_subdev_call(dev->sensor, pad, get_fmt, dev->sensor_state, + &sd_fmt); + if (ret < 0) + return ret; + + *fmt = sd_fmt.format; + + unicam_dbg(1, dev, "%s %dx%d code:%04x\n", __func__, + fmt->width, fmt->height, fmt->code); + + return 0; +} + +static int __subdev_set_format(struct unicam_device *dev, + struct v4l2_mbus_framefmt *fmt, int pad_id) +{ + struct v4l2_subdev_format sd_fmt = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + .pad = dev->node[pad_id].src_pad_id, + }; + int ret; + + sd_fmt.format = *fmt; + + ret = v4l2_subdev_call(dev->sensor, pad, set_fmt, dev->sensor_state, + &sd_fmt); + if (ret < 0) + return ret; + + *fmt = sd_fmt.format; + + if (pad_id == IMAGE_PAD) + unicam_dbg(1, dev, "%s %dx%d code:%04x\n", __func__, fmt->width, + fmt->height, fmt->code); + else + unicam_dbg(1, dev, "%s Embedded data code:%04x\n", __func__, + sd_fmt.format.code); + + return 0; +} + +static int unicam_calc_format_size_bpl(struct unicam_device *dev, + const struct unicam_fmt *fmt, + struct v4l2_format *f) +{ + unsigned int min_bytesperline; + + v4l_bound_align_image(&f->fmt.pix.width, MIN_WIDTH, MAX_WIDTH, 2, + &f->fmt.pix.height, MIN_HEIGHT, MAX_HEIGHT, 0, + 0); + + min_bytesperline = bytes_per_line(f->fmt.pix.width, fmt, + f->fmt.pix.pixelformat); + + if (f->fmt.pix.bytesperline > min_bytesperline && + f->fmt.pix.bytesperline <= MAX_BYTESPERLINE) + f->fmt.pix.bytesperline = ALIGN(f->fmt.pix.bytesperline, + BPL_ALIGNMENT); + else + f->fmt.pix.bytesperline = min_bytesperline; + + f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline; + + unicam_dbg(3, dev, "%s: fourcc: %08X size: %dx%d bpl:%d img_size:%d\n", + __func__, + f->fmt.pix.pixelformat, + f->fmt.pix.width, f->fmt.pix.height, + f->fmt.pix.bytesperline, f->fmt.pix.sizeimage); + + return 0; +} + +static int unicam_reset_format(struct unicam_node *node) +{ + struct unicam_device *dev = node->dev; + struct v4l2_mbus_framefmt mbus_fmt; + int ret; + + if (dev->sensor_embedded_data || node->pad_id != METADATA_PAD) { + ret = __subdev_get_format(dev, &mbus_fmt, node->pad_id); + if (ret) { + unicam_err(dev, "Failed to get_format - ret %d\n", ret); + return ret; + } + + if (mbus_fmt.code != node->fmt->code) { + unicam_err(dev, "code mismatch - fmt->code %08x, mbus_fmt.code %08x\n", + node->fmt->code, mbus_fmt.code); + return ret; + } + } + + if (node->pad_id == IMAGE_PAD) { + v4l2_fill_pix_format(&node->v_fmt.fmt.pix, &mbus_fmt); + node->v_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + unicam_calc_format_size_bpl(dev, node->fmt, &node->v_fmt); + } else { + node->v_fmt.type = V4L2_BUF_TYPE_META_CAPTURE; + node->v_fmt.fmt.meta.dataformat = V4L2_META_FMT_SENSOR_DATA; + if (dev->sensor_embedded_data) { + node->v_fmt.fmt.meta.buffersize = + mbus_fmt.width * mbus_fmt.height; + node->embedded_lines = mbus_fmt.height; + } else { + node->v_fmt.fmt.meta.buffersize = UNICAM_EMBEDDED_SIZE; + node->embedded_lines = 1; + } + } + + node->m_fmt = mbus_fmt; + return 0; +} + +static void unicam_wr_dma_addr(struct unicam_device *dev, dma_addr_t dmaaddr, + unsigned int buffer_size, int pad_id) +{ + dma_addr_t endaddr = dmaaddr + buffer_size; + + if (pad_id == IMAGE_PAD) { + reg_write(dev, UNICAM_IBSA0, dmaaddr); + reg_write(dev, UNICAM_IBEA0, endaddr); + } else { + reg_write(dev, UNICAM_DBSA0, dmaaddr); + reg_write(dev, UNICAM_DBEA0, endaddr); + } +} + +static unsigned int unicam_get_lines_done(struct unicam_device *dev) +{ + dma_addr_t start_addr, cur_addr; + unsigned int stride = dev->node[IMAGE_PAD].v_fmt.fmt.pix.bytesperline; + struct unicam_buffer *frm = dev->node[IMAGE_PAD].cur_frm; + + if (!frm) + return 0; + + start_addr = vb2_dma_contig_plane_dma_addr(&frm->vb.vb2_buf, 0); + cur_addr = reg_read(dev, UNICAM_IBWP); + return (unsigned int)(cur_addr - start_addr) / stride; +} + +static void unicam_schedule_next_buffer(struct unicam_node *node) +{ + struct unicam_device *dev = node->dev; + struct unicam_buffer *buf; + unsigned int size; + dma_addr_t addr; + + buf = list_first_entry(&node->dma_queue, struct unicam_buffer, list); + node->next_frm = buf; + list_del(&buf->list); + + addr = vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0); + size = (node->pad_id == IMAGE_PAD) ? + node->v_fmt.fmt.pix.sizeimage : + node->v_fmt.fmt.meta.buffersize; + + unicam_wr_dma_addr(dev, addr, size, node->pad_id); +} + +static void unicam_schedule_dummy_buffer(struct unicam_node *node) +{ + struct unicam_device *dev = node->dev; + + unicam_dbg(3, dev, "Scheduling dummy buffer for node %d\n", + node->pad_id); + + unicam_wr_dma_addr(dev, node->dummy_buf_dma_addr, 0, node->pad_id); + node->next_frm = NULL; +} + +static void unicam_process_buffer_complete(struct unicam_node *node, + unsigned int sequence) +{ + node->cur_frm->vb.field = node->m_fmt.field; + node->cur_frm->vb.sequence = sequence; + + vb2_buffer_done(&node->cur_frm->vb.vb2_buf, VB2_BUF_STATE_DONE); +} + +static void unicam_queue_event_sof(struct unicam_device *unicam) +{ + struct v4l2_event event = { + .type = V4L2_EVENT_FRAME_SYNC, + .u.frame_sync.frame_sequence = unicam->sequence, + }; + + v4l2_event_queue(&unicam->node[IMAGE_PAD].video_dev, &event); +} + +/* + * unicam_isr : ISR handler for unicam capture + * @irq: irq number + * @dev_id: dev_id ptr + * + * It changes status of the captured buffer, takes next buffer from the queue + * and sets its address in unicam registers + */ +static irqreturn_t unicam_isr(int irq, void *dev) +{ + struct unicam_device *unicam = dev; + unsigned int lines_done = unicam_get_lines_done(dev); + unsigned int sequence = unicam->sequence; + unsigned int i; + u32 ista, sta; + bool fe; + u64 ts; + + sta = reg_read(unicam, UNICAM_STA); + /* Write value back to clear the interrupts */ + reg_write(unicam, UNICAM_STA, sta); + + ista = reg_read(unicam, UNICAM_ISTA); + /* Write value back to clear the interrupts */ + reg_write(unicam, UNICAM_ISTA, ista); + + unicam_dbg(3, unicam, "ISR: ISTA: 0x%X, STA: 0x%X, sequence %d, lines done %d", + ista, sta, sequence, lines_done); + + if (!(sta & (UNICAM_IS | UNICAM_PI0))) + return IRQ_HANDLED; + + /* + * Look for either the Frame End interrupt or the Packet Capture status + * to signal a frame end. + */ + fe = (ista & UNICAM_FEI || sta & UNICAM_PI0); + + /* + * We must run the frame end handler first. If we have a valid next_frm + * and we get a simultaneout FE + FS interrupt, running the FS handler + * first would null out the next_frm ptr and we would have lost the + * buffer forever. + */ + if (fe) { + bool inc_seq = unicam->frame_started; + + if (unicam->sync_gpio) + gpiod_set_value(unicam->sync_gpio, 0); + /* + * Ensure we have swapped buffers already as we can't + * stop the peripheral. If no buffer is available, use a + * dummy buffer to dump out frames until we get a new buffer + * to use. + */ + for (i = 0; i < ARRAY_SIZE(unicam->node); i++) { + struct unicam_node *node = &unicam->node[i]; + + if (!node->streaming) + continue; + + /* + * If cur_frm == next_frm, it means we have not had + * a chance to swap buffers, likely due to having + * multiple interrupts occurring simultaneously (like FE + * + FS + LS). In this case, we cannot signal the buffer + * as complete, as the HW will reuse that buffer. + */ + if (node->cur_frm && node->cur_frm != node->next_frm) { + /* + * This condition checks if FE + FS for the same + * frame has occurred. In such cases, we cannot + * return out the frame, as no buffer handling + * or timestamping has yet been done as part of + * the FS handler. + */ + if (!node->cur_frm->vb.vb2_buf.timestamp) { + unicam_dbg(2, unicam, "ISR: FE without FS, dropping frame\n"); + continue; + } + + unicam_process_buffer_complete(node, sequence); + node->cur_frm = node->next_frm; + node->next_frm = NULL; + inc_seq = true; + } else { + node->cur_frm = node->next_frm; + } + } + + /* + * Increment the sequence number conditionally on either a FS + * having already occurred, or in the FE + FS condition as + * caught in the FE handler above. This ensures the sequence + * number corresponds to the frames generated by the sensor, not + * the frames dequeued to userland. + */ + if (inc_seq) { + unicam->sequence++; + unicam->frame_started = false; + } + } + + if (ista & UNICAM_FSI) { + /* + * Timestamp is to be when the first data byte was captured, + * aka frame start. + */ + ts = ktime_get_ns(); + + if (unicam->sync_gpio) + gpiod_set_value(unicam->sync_gpio, 1); + + for (i = 0; i < ARRAY_SIZE(unicam->node); i++) { + if (!unicam->node[i].streaming) + continue; + + if (unicam->node[i].cur_frm) + unicam->node[i].cur_frm->vb.vb2_buf.timestamp = + ts; + else + unicam_dbg(2, unicam, "ISR: [%d] Dropping frame, buffer not available at FS\n", + i); + /* + * Set the next frame output to go to a dummy frame + * if no buffer currently queued. + */ + if (!unicam->node[i].next_frm || + unicam->node[i].next_frm == unicam->node[i].cur_frm) { + unicam_schedule_dummy_buffer(&unicam->node[i]); + } else if (unicam->node[i].cur_frm) { + /* + * Repeated FS without FE. Hardware will have + * swapped buffers, but the cur_frm doesn't + * contain valid data. Return cur_frm to the + * queue. + */ + spin_lock(&unicam->node[i].dma_queue_lock); + list_add_tail(&unicam->node[i].cur_frm->list, + &unicam->node[i].dma_queue); + spin_unlock(&unicam->node[i].dma_queue_lock); + unicam->node[i].cur_frm = unicam->node[i].next_frm; + unicam->node[i].next_frm = NULL; + } + } + + unicam_queue_event_sof(unicam); + unicam->frame_started = true; + } + + /* + * Cannot swap buffer at frame end, there may be a race condition + * where the HW does not actually swap it if the new frame has + * already started. + */ + if (ista & (UNICAM_FSI | UNICAM_LCI) && !fe) { + for (i = 0; i < ARRAY_SIZE(unicam->node); i++) { + if (!unicam->node[i].streaming) + continue; + + spin_lock(&unicam->node[i].dma_queue_lock); + if (!list_empty(&unicam->node[i].dma_queue) && + !unicam->node[i].next_frm) + unicam_schedule_next_buffer(&unicam->node[i]); + spin_unlock(&unicam->node[i].dma_queue_lock); + } + } + + return IRQ_HANDLED; +} + +/* V4L2 Common IOCTLs */ +static int unicam_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct unicam_node *node = video_drvdata(file); + struct unicam_device *dev = node->dev; + + strscpy(cap->driver, UNICAM_MODULE_NAME, sizeof(cap->driver)); + strscpy(cap->card, UNICAM_MODULE_NAME, sizeof(cap->card)); + + snprintf(cap->bus_info, sizeof(cap->bus_info), + "platform:%s", dev_name(&dev->pdev->dev)); + + cap->capabilities |= V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_META_CAPTURE; + + return 0; +} + +static int unicam_log_status(struct file *file, void *fh) +{ + struct unicam_node *node = video_drvdata(file); + struct unicam_device *dev = node->dev; + u32 reg; + + /* status for sub devices */ + v4l2_device_call_all(&dev->v4l2_dev, 0, core, log_status); + + unicam_info(dev, "-----Receiver status-----\n"); + unicam_info(dev, "V4L2 width/height: %ux%u\n", + node->v_fmt.fmt.pix.width, node->v_fmt.fmt.pix.height); + unicam_info(dev, "Mediabus format: %08x\n", node->fmt->code); + unicam_info(dev, "V4L2 format: %08x\n", + node->v_fmt.fmt.pix.pixelformat); + reg = reg_read(dev, UNICAM_IPIPE); + unicam_info(dev, "Unpacking/packing: %u / %u\n", + get_field(reg, UNICAM_PUM_MASK), + get_field(reg, UNICAM_PPM_MASK)); + unicam_info(dev, "----Live data----\n"); + unicam_info(dev, "Programmed stride: %4u\n", + reg_read(dev, UNICAM_IBLS)); + unicam_info(dev, "Detected resolution: %ux%u\n", + reg_read(dev, UNICAM_IHSTA), + reg_read(dev, UNICAM_IVSTA)); + unicam_info(dev, "Write pointer: %08x\n", + reg_read(dev, UNICAM_IBWP)); + + return 0; +} + +/* V4L2 Video Centric IOCTLs */ +static int unicam_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct unicam_node *node = video_drvdata(file); + struct unicam_device *dev = node->dev; + unsigned int index = 0; + unsigned int i; + int ret = 0; + + if (node->pad_id != IMAGE_PAD) + return -EINVAL; + + for (i = 0; !ret && i < MAX_ENUM_MBUS_CODE; i++) { + struct v4l2_subdev_mbus_code_enum mbus_code = { + .index = i, + .pad = IMAGE_PAD, + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + const struct unicam_fmt *fmt; + + ret = v4l2_subdev_call(dev->sensor, pad, enum_mbus_code, + NULL, &mbus_code); + if (ret < 0) { + unicam_dbg(2, dev, + "subdev->enum_mbus_code idx %d returned %d - index invalid\n", + i, ret); + return -EINVAL; + } + + fmt = find_format_by_code(mbus_code.code); + if (fmt) { + if (fmt->fourcc) { + if (index == f->index) { + f->pixelformat = fmt->fourcc; + break; + } + index++; + } + if (fmt->repacked_fourcc) { + if (index == f->index) { + f->pixelformat = fmt->repacked_fourcc; + break; + } + index++; + } + } + } + + return 0; +} + +static int unicam_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct v4l2_mbus_framefmt mbus_fmt = {0}; + struct unicam_node *node = video_drvdata(file); + struct unicam_device *dev = node->dev; + const struct unicam_fmt *fmt = NULL; + int ret; + + if (node->pad_id != IMAGE_PAD) + return -EINVAL; + + /* + * If a flip has occurred in the sensor, the fmt code might have + * changed. So we will need to re-fetch the format from the subdevice. + */ + ret = __subdev_get_format(dev, &mbus_fmt, node->pad_id); + if (ret) + return -EINVAL; + + /* Find the V4L2 format from mbus code. We must match a known format. */ + fmt = find_format_by_code(mbus_fmt.code); + if (!fmt) + return -EINVAL; + + if (node->fmt != fmt) { + /* + * The sensor format has changed so the pixelformat needs to + * be updated. Try and retain the packed/unpacked choice if + * at all possible. + */ + if (node->fmt->repacked_fourcc == + node->v_fmt.fmt.pix.pixelformat) + /* Using the repacked format */ + node->v_fmt.fmt.pix.pixelformat = fmt->repacked_fourcc; + else + /* Using the native format */ + node->v_fmt.fmt.pix.pixelformat = fmt->fourcc; + + node->fmt = fmt; + } + + *f = node->v_fmt; + + return 0; +} + +static const struct unicam_fmt * +get_first_supported_format(struct unicam_device *dev) +{ + struct v4l2_subdev_mbus_code_enum mbus_code; + const struct unicam_fmt *fmt = NULL; + unsigned int i; + int ret = 0; + + for (i = 0; ret != -EINVAL && ret != -ENOIOCTLCMD; ++i) { + memset(&mbus_code, 0, sizeof(mbus_code)); + mbus_code.index = i; + mbus_code.pad = IMAGE_PAD; + mbus_code.which = V4L2_SUBDEV_FORMAT_ACTIVE; + + ret = v4l2_subdev_call(dev->sensor, pad, enum_mbus_code, NULL, + &mbus_code); + if (ret < 0) { + unicam_dbg(2, dev, + "subdev->enum_mbus_code idx %u returned %d - continue\n", + i, ret); + continue; + } + + unicam_dbg(2, dev, "subdev %s: code: 0x%08x idx: %u\n", + dev->sensor->name, mbus_code.code, i); + + fmt = find_format_by_code(mbus_code.code); + unicam_dbg(2, dev, "fmt 0x%08x returned as %p, V4L2 FOURCC 0x%08x, csi_dt 0x%02x\n", + mbus_code.code, fmt, fmt ? fmt->fourcc : 0, + fmt ? fmt->csi_dt : 0); + if (fmt) + return fmt; + } + + return NULL; +} + +static int unicam_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct unicam_node *node = video_drvdata(file); + struct unicam_device *dev = node->dev; + struct v4l2_subdev_format sd_fmt = { + .which = V4L2_SUBDEV_FORMAT_TRY, + .pad = IMAGE_PAD + }; + struct v4l2_mbus_framefmt *mbus_fmt = &sd_fmt.format; + const struct unicam_fmt *fmt; + int ret; + + if (node->pad_id != IMAGE_PAD) + return -EINVAL; + + fmt = find_format_by_pix(dev, f->fmt.pix.pixelformat); + if (!fmt) { + /* + * Pixel format not supported by unicam. Choose the first + * supported format, and let the sensor choose something else. + */ + unicam_dbg(3, dev, "Fourcc format (0x%08x) not found. Use first format.\n", + f->fmt.pix.pixelformat); + + fmt = &formats[0]; + f->fmt.pix.pixelformat = fmt->fourcc; + } + + v4l2_fill_mbus_format(mbus_fmt, &f->fmt.pix, fmt->code); + /* + * No support for receiving interlaced video, so never + * request it from the sensor subdev. + */ + mbus_fmt->field = V4L2_FIELD_NONE; + + ret = v4l2_subdev_call(dev->sensor, pad, set_fmt, dev->sensor_state, + &sd_fmt); + if (ret && ret != -ENOIOCTLCMD && ret != -ENODEV) + return ret; + + if (mbus_fmt->field != V4L2_FIELD_NONE) + unicam_info(dev, "Sensor trying to send interlaced video - results may be unpredictable\n"); + + v4l2_fill_pix_format(&f->fmt.pix, &sd_fmt.format); + if (mbus_fmt->code != fmt->code) { + /* Sensor has returned an alternate format */ + fmt = find_format_by_code(mbus_fmt->code); + if (!fmt) { + /* + * The alternate format is one unicam can't support. + * Find the first format that is supported by both, and + * then set that. + */ + fmt = get_first_supported_format(dev); + mbus_fmt->code = fmt->code; + + ret = v4l2_subdev_call(dev->sensor, pad, set_fmt, + dev->sensor_state, &sd_fmt); + if (ret && ret != -ENOIOCTLCMD && ret != -ENODEV) + return ret; + + if (mbus_fmt->field != V4L2_FIELD_NONE) + unicam_info(dev, "Sensor trying to send interlaced video - results may be unpredictable\n"); + + v4l2_fill_pix_format(&f->fmt.pix, &sd_fmt.format); + + if (mbus_fmt->code != fmt->code) { + /* + * We've set a format that the sensor reports + * as being supported, but it refuses to set it. + * Not much else we can do. + * Assume that the sensor driver may accept the + * format when it is set (rather than tried). + */ + unicam_err(dev, "Sensor won't accept default format, and Unicam can't support sensor default\n"); + } + } + + if (fmt->fourcc) + f->fmt.pix.pixelformat = fmt->fourcc; + else + f->fmt.pix.pixelformat = fmt->repacked_fourcc; + } + + return unicam_calc_format_size_bpl(dev, fmt, f); +} + +static int unicam_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct unicam_node *node = video_drvdata(file); + struct unicam_device *dev = node->dev; + struct vb2_queue *q = &node->buffer_queue; + struct v4l2_mbus_framefmt mbus_fmt = {0}; + const struct unicam_fmt *fmt; + int ret; + + if (vb2_is_busy(q)) + return -EBUSY; + + ret = unicam_try_fmt_vid_cap(file, priv, f); + if (ret < 0) + return ret; + + fmt = find_format_by_pix(dev, f->fmt.pix.pixelformat); + if (!fmt) { + /* + * Unknown pixel format - adopt a default. + * This shouldn't happen as try_fmt should have resolved any + * issues first. + */ + fmt = get_first_supported_format(dev); + if (!fmt) + /* + * It shouldn't be possible to get here with no + * supported formats + */ + return -EINVAL; + f->fmt.pix.pixelformat = fmt->fourcc; + return -EINVAL; + } + + v4l2_fill_mbus_format(&mbus_fmt, &f->fmt.pix, fmt->code); + + ret = __subdev_set_format(dev, &mbus_fmt, node->pad_id); + if (ret) { + unicam_dbg(3, dev, "%s __subdev_set_format failed %d\n", + __func__, ret); + return ret; + } + + /* Just double check nothing has gone wrong */ + if (mbus_fmt.code != fmt->code) { + unicam_dbg(3, dev, + "%s subdev changed format on us, this should not happen\n", + __func__); + return -EINVAL; + } + + node->fmt = fmt; + node->v_fmt.fmt.pix.pixelformat = f->fmt.pix.pixelformat; + node->v_fmt.fmt.pix.bytesperline = f->fmt.pix.bytesperline; + unicam_reset_format(node); + + unicam_dbg(3, dev, + "%s %dx%d, mbus_fmt 0x%08X, V4L2 pix 0x%08X.\n", + __func__, node->v_fmt.fmt.pix.width, + node->v_fmt.fmt.pix.height, mbus_fmt.code, + node->v_fmt.fmt.pix.pixelformat); + + *f = node->v_fmt; + + return 0; +} + +static int unicam_enum_fmt_meta_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct unicam_node *node = video_drvdata(file); + struct unicam_device *dev = node->dev; + const struct unicam_fmt *fmt; + u32 code; + int ret = 0; + + if (node->pad_id != METADATA_PAD || f->index != 0) + return -EINVAL; + + if (dev->sensor_embedded_data) { + struct v4l2_subdev_mbus_code_enum mbus_code = { + .index = f->index, + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + .pad = METADATA_PAD, + }; + + ret = v4l2_subdev_call(dev->sensor, pad, enum_mbus_code, NULL, + &mbus_code); + if (ret < 0) { + unicam_dbg(2, dev, + "subdev->enum_mbus_code idx 0 returned %d - index invalid\n", + ret); + return -EINVAL; + } + + code = mbus_code.code; + } else { + code = MEDIA_BUS_FMT_SENSOR_DATA; + } + + fmt = find_format_by_code(code); + if (fmt) + f->pixelformat = fmt->fourcc; + + return 0; +} + +static int unicam_g_fmt_meta_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct unicam_node *node = video_drvdata(file); + + if (node->pad_id != METADATA_PAD) + return -EINVAL; + + *f = node->v_fmt; + + return 0; +} + +static int unicam_enum_input(struct file *file, void *priv, + struct v4l2_input *inp) +{ + struct unicam_node *node = video_drvdata(file); + struct unicam_device *dev = node->dev; + int ret; + + if (inp->index != 0) + return -EINVAL; + + inp->type = V4L2_INPUT_TYPE_CAMERA; + if (v4l2_subdev_has_op(dev->sensor, pad, s_dv_timings)) { + inp->capabilities = V4L2_IN_CAP_DV_TIMINGS; + inp->std = 0; + } else if (v4l2_subdev_has_op(dev->sensor, video, s_std)) { + inp->capabilities = V4L2_IN_CAP_STD; + if (v4l2_subdev_call(dev->sensor, video, g_tvnorms, &inp->std) < 0) + inp->std = V4L2_STD_ALL; + } else { + inp->capabilities = 0; + inp->std = 0; + } + + if (v4l2_subdev_has_op(dev->sensor, video, g_input_status)) { + ret = v4l2_subdev_call(dev->sensor, video, g_input_status, + &inp->status); + if (ret < 0) + return ret; + } + + snprintf(inp->name, sizeof(inp->name), "Camera 0"); + return 0; +} + +static int unicam_g_input(struct file *file, void *priv, unsigned int *i) +{ + *i = 0; + + return 0; +} + +static int unicam_s_input(struct file *file, void *priv, unsigned int i) +{ + /* + * FIXME: Ideally we would like to be able to query the source + * subdevice for information over the input connectors it supports, + * and map that through in to a call to video_ops->s_routing. + * There is no infrastructure support for defining that within + * devicetree at present. Until that is implemented we can't + * map a user physical connector number to s_routing input number. + */ + if (i > 0) + return -EINVAL; + + return 0; +} + +static int unicam_querystd(struct file *file, void *priv, + v4l2_std_id *std) +{ + struct unicam_node *node = video_drvdata(file); + struct unicam_device *dev = node->dev; + + return v4l2_subdev_call(dev->sensor, video, querystd, std); +} + +static int unicam_g_std(struct file *file, void *priv, v4l2_std_id *std) +{ + struct unicam_node *node = video_drvdata(file); + struct unicam_device *dev = node->dev; + + return v4l2_subdev_call(dev->sensor, video, g_std, std); +} + +static int unicam_s_std(struct file *file, void *priv, v4l2_std_id std) +{ + struct unicam_node *node = video_drvdata(file); + struct unicam_device *dev = node->dev; + int ret; + v4l2_std_id current_std; + + ret = v4l2_subdev_call(dev->sensor, video, g_std, ¤t_std); + if (ret) + return ret; + + if (std == current_std) + return 0; + + if (vb2_is_busy(&node->buffer_queue)) + return -EBUSY; + + ret = v4l2_subdev_call(dev->sensor, video, s_std, std); + + /* Force recomputation of bytesperline */ + node->v_fmt.fmt.pix.bytesperline = 0; + + unicam_reset_format(node); + + return ret; +} + +static int unicam_s_edid(struct file *file, void *priv, struct v4l2_edid *edid) +{ + struct unicam_node *node = video_drvdata(file); + struct unicam_device *dev = node->dev; + + return v4l2_subdev_call(dev->sensor, pad, set_edid, edid); +} + +static int unicam_g_edid(struct file *file, void *priv, struct v4l2_edid *edid) +{ + struct unicam_node *node = video_drvdata(file); + struct unicam_device *dev = node->dev; + + return v4l2_subdev_call(dev->sensor, pad, get_edid, edid); +} + +static int unicam_s_selection(struct file *file, void *priv, + struct v4l2_selection *sel) +{ + struct unicam_node *node = video_drvdata(file); + struct unicam_device *dev = node->dev; + struct v4l2_subdev_selection sdsel = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + .target = sel->target, + .flags = sel->flags, + .r = sel->r, + }; + + if (sel->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + return v4l2_subdev_call(dev->sensor, pad, set_selection, NULL, &sdsel); +} + +static int unicam_g_selection(struct file *file, void *priv, + struct v4l2_selection *sel) +{ + struct unicam_node *node = video_drvdata(file); + struct unicam_device *dev = node->dev; + struct v4l2_subdev_selection sdsel = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + .target = sel->target, + }; + int ret; + + if (sel->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + ret = v4l2_subdev_call(dev->sensor, pad, get_selection, NULL, &sdsel); + if (!ret) + sel->r = sdsel.r; + + return ret; +} + +static int unicam_enum_framesizes(struct file *file, void *priv, + struct v4l2_frmsizeenum *fsize) +{ + struct unicam_node *node = video_drvdata(file); + struct unicam_device *dev = node->dev; + const struct unicam_fmt *fmt; + struct v4l2_subdev_frame_size_enum fse; + int ret; + + /* check for valid format */ + fmt = find_format_by_pix(dev, fsize->pixel_format); + if (!fmt) { + unicam_dbg(3, dev, "Invalid pixel code: %x\n", + fsize->pixel_format); + return -EINVAL; + } + fse.code = fmt->code; + + fse.which = V4L2_SUBDEV_FORMAT_ACTIVE; + fse.index = fsize->index; + fse.pad = node->src_pad_id; + + ret = v4l2_subdev_call(dev->sensor, pad, enum_frame_size, NULL, &fse); + if (ret) + return ret; + + unicam_dbg(1, dev, "%s: index: %d code: %x W:[%d,%d] H:[%d,%d]\n", + __func__, fse.index, fse.code, fse.min_width, fse.max_width, + fse.min_height, fse.max_height); + + fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; + fsize->discrete.width = fse.max_width; + fsize->discrete.height = fse.max_height; + + return 0; +} + +static int unicam_enum_frameintervals(struct file *file, void *priv, + struct v4l2_frmivalenum *fival) +{ + struct unicam_node *node = video_drvdata(file); + struct unicam_device *dev = node->dev; + const struct unicam_fmt *fmt; + struct v4l2_subdev_frame_interval_enum fie = { + .index = fival->index, + .pad = node->src_pad_id, + .width = fival->width, + .height = fival->height, + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + int ret; + + fmt = find_format_by_pix(dev, fival->pixel_format); + if (!fmt) + return -EINVAL; + + fie.code = fmt->code; + ret = v4l2_subdev_call(dev->sensor, pad, enum_frame_interval, + NULL, &fie); + if (ret) + return ret; + + fival->type = V4L2_FRMIVAL_TYPE_DISCRETE; + fival->discrete = fie.interval; + + return 0; +} + +static int unicam_g_parm(struct file *file, void *fh, struct v4l2_streamparm *a) +{ + struct unicam_node *node = video_drvdata(file); + struct unicam_device *dev = node->dev; + + return v4l2_g_parm_cap(video_devdata(file), dev->sensor, a); +} + +static int unicam_s_parm(struct file *file, void *fh, struct v4l2_streamparm *a) +{ + struct unicam_node *node = video_drvdata(file); + struct unicam_device *dev = node->dev; + + return v4l2_s_parm_cap(video_devdata(file), dev->sensor, a); +} + +static int unicam_g_dv_timings(struct file *file, void *priv, + struct v4l2_dv_timings *timings) +{ + struct unicam_node *node = video_drvdata(file); + struct unicam_device *dev = node->dev; + + return v4l2_subdev_call(dev->sensor, pad, g_dv_timings, 0, timings); +} + +static int unicam_s_dv_timings(struct file *file, void *priv, + struct v4l2_dv_timings *timings) +{ + struct unicam_node *node = video_drvdata(file); + struct unicam_device *dev = node->dev; + struct v4l2_dv_timings current_timings; + int ret; + + ret = v4l2_subdev_call(dev->sensor, pad, g_dv_timings, 0, + ¤t_timings); + + if (ret < 0) + return ret; + + if (v4l2_match_dv_timings(timings, ¤t_timings, 0, false)) + return 0; + + if (vb2_is_busy(&node->buffer_queue)) + return -EBUSY; + + ret = v4l2_subdev_call(dev->sensor, pad, s_dv_timings, 0, timings); + + /* Force recomputation of bytesperline */ + node->v_fmt.fmt.pix.bytesperline = 0; + + unicam_reset_format(node); + + return ret; +} + +static int unicam_query_dv_timings(struct file *file, void *priv, + struct v4l2_dv_timings *timings) +{ + struct unicam_node *node = video_drvdata(file); + struct unicam_device *dev = node->dev; + + return v4l2_subdev_call(dev->sensor, pad, query_dv_timings, 0, timings); +} + +static int unicam_enum_dv_timings(struct file *file, void *priv, + struct v4l2_enum_dv_timings *timings) +{ + struct unicam_node *node = video_drvdata(file); + struct unicam_device *dev = node->dev; + int ret; + + timings->pad = node->src_pad_id; + ret = v4l2_subdev_call(dev->sensor, pad, enum_dv_timings, timings); + timings->pad = node->pad_id; + + return ret; +} + +static int unicam_dv_timings_cap(struct file *file, void *priv, + struct v4l2_dv_timings_cap *cap) +{ + struct unicam_node *node = video_drvdata(file); + struct unicam_device *dev = node->dev; + int ret; + + cap->pad = node->src_pad_id; + ret = v4l2_subdev_call(dev->sensor, pad, dv_timings_cap, cap); + cap->pad = node->pad_id; + + return ret; +} + +static int unicam_subscribe_event(struct v4l2_fh *fh, + const struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_FRAME_SYNC: + return v4l2_event_subscribe(fh, sub, 2, NULL); + case V4L2_EVENT_SOURCE_CHANGE: + return v4l2_event_subscribe(fh, sub, 4, NULL); + } + + return v4l2_ctrl_subscribe_event(fh, sub); +} + +static void unicam_notify(struct v4l2_subdev *sd, + unsigned int notification, void *arg) +{ + struct unicam_device *dev = to_unicam_device(sd->v4l2_dev); + + switch (notification) { + case V4L2_DEVICE_NOTIFY_EVENT: + v4l2_event_queue(&dev->node[IMAGE_PAD].video_dev, arg); + break; + default: + break; + } +} + +/* unicam capture ioctl operations */ +static const struct v4l2_ioctl_ops unicam_ioctl_ops = { + .vidioc_querycap = unicam_querycap, + .vidioc_enum_fmt_vid_cap = unicam_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = unicam_g_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = unicam_s_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = unicam_try_fmt_vid_cap, + + .vidioc_enum_fmt_meta_cap = unicam_enum_fmt_meta_cap, + .vidioc_g_fmt_meta_cap = unicam_g_fmt_meta_cap, + .vidioc_s_fmt_meta_cap = unicam_g_fmt_meta_cap, + .vidioc_try_fmt_meta_cap = unicam_g_fmt_meta_cap, + + .vidioc_enum_input = unicam_enum_input, + .vidioc_g_input = unicam_g_input, + .vidioc_s_input = unicam_s_input, + + .vidioc_querystd = unicam_querystd, + .vidioc_s_std = unicam_s_std, + .vidioc_g_std = unicam_g_std, + + .vidioc_g_edid = unicam_g_edid, + .vidioc_s_edid = unicam_s_edid, + + .vidioc_enum_framesizes = unicam_enum_framesizes, + .vidioc_enum_frameintervals = unicam_enum_frameintervals, + + .vidioc_g_selection = unicam_g_selection, + .vidioc_s_selection = unicam_s_selection, + + .vidioc_g_parm = unicam_g_parm, + .vidioc_s_parm = unicam_s_parm, + + .vidioc_s_dv_timings = unicam_s_dv_timings, + .vidioc_g_dv_timings = unicam_g_dv_timings, + .vidioc_query_dv_timings = unicam_query_dv_timings, + .vidioc_enum_dv_timings = unicam_enum_dv_timings, + .vidioc_dv_timings_cap = unicam_dv_timings_cap, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + + .vidioc_log_status = unicam_log_status, + .vidioc_subscribe_event = unicam_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +/* V4L2 Media Controller Centric IOCTLs */ + +static int unicam_mc_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + int i, j; + + for (i = 0, j = 0; i < ARRAY_SIZE(formats); i++) { + if (f->mbus_code && formats[i].code != f->mbus_code) + continue; + if (formats[i].mc_skip || formats[i].metadata_fmt) + continue; + + if (formats[i].fourcc) { + if (j == f->index) { + f->pixelformat = formats[i].fourcc; + f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + return 0; + } + j++; + } + if (formats[i].repacked_fourcc) { + if (j == f->index) { + f->pixelformat = formats[i].repacked_fourcc; + f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + return 0; + } + j++; + } + } + + return -EINVAL; +} + +static int unicam_mc_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct unicam_node *node = video_drvdata(file); + + if (node->pad_id != IMAGE_PAD) + return -EINVAL; + + *f = node->v_fmt; + + return 0; +} + +static void unicam_mc_try_fmt(struct unicam_node *node, struct v4l2_format *f, + const struct unicam_fmt **ret_fmt) +{ + struct v4l2_pix_format *v4l2_format = &f->fmt.pix; + struct unicam_device *dev = node->dev; + const struct unicam_fmt *fmt; + int is_rgb; + + /* + * Default to the first format if the requested pixel format code isn't + * supported. + */ + fmt = find_format_by_pix(dev, v4l2_format->pixelformat); + if (!fmt) { + fmt = &formats[0]; + v4l2_format->pixelformat = fmt->fourcc; + } + + unicam_calc_format_size_bpl(dev, fmt, f); + + if (v4l2_format->field == V4L2_FIELD_ANY) + v4l2_format->field = V4L2_FIELD_NONE; + + if (ret_fmt) + *ret_fmt = fmt; + + if (v4l2_format->colorspace >= MAX_COLORSPACE || + !(fmt->valid_colorspaces & (1 << v4l2_format->colorspace))) { + v4l2_format->colorspace = __ffs(fmt->valid_colorspaces); + + v4l2_format->xfer_func = + V4L2_MAP_XFER_FUNC_DEFAULT(v4l2_format->colorspace); + v4l2_format->ycbcr_enc = + V4L2_MAP_YCBCR_ENC_DEFAULT(v4l2_format->colorspace); + is_rgb = v4l2_format->colorspace == V4L2_COLORSPACE_SRGB; + v4l2_format->quantization = + V4L2_MAP_QUANTIZATION_DEFAULT(is_rgb, + v4l2_format->colorspace, + v4l2_format->ycbcr_enc); + } + + unicam_dbg(3, dev, "%s: %08x %ux%u (bytesperline %u sizeimage %u)\n", + __func__, v4l2_format->pixelformat, + v4l2_format->width, v4l2_format->height, + v4l2_format->bytesperline, v4l2_format->sizeimage); +} + +static int unicam_mc_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct unicam_node *node = video_drvdata(file); + + unicam_mc_try_fmt(node, f, NULL); + return 0; +} + +static int unicam_mc_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct unicam_node *node = video_drvdata(file); + struct unicam_device *dev = node->dev; + const struct unicam_fmt *fmt; + + if (vb2_is_busy(&node->buffer_queue)) { + unicam_dbg(3, dev, "%s device busy\n", __func__); + return -EBUSY; + } + + unicam_mc_try_fmt(node, f, &fmt); + + node->v_fmt = *f; + node->fmt = fmt; + + return 0; +} + +static int unicam_mc_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + struct unicam_node *node = video_drvdata(file); + struct unicam_device *dev = node->dev; + + if (fsize->index > 0) + return -EINVAL; + + if (!find_format_by_pix(dev, fsize->pixel_format)) { + unicam_dbg(3, dev, "Invalid pixel format 0x%08x\n", + fsize->pixel_format); + return -EINVAL; + } + + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; + fsize->stepwise.min_width = MIN_WIDTH; + fsize->stepwise.max_width = MAX_WIDTH; + fsize->stepwise.step_width = 1; + fsize->stepwise.min_height = MIN_HEIGHT; + fsize->stepwise.max_height = MAX_HEIGHT; + fsize->stepwise.step_height = 1; + + return 0; +} + +static int unicam_mc_enum_fmt_meta_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + int i, j; + + for (i = 0, j = 0; i < ARRAY_SIZE(formats); i++) { + if (f->mbus_code && formats[i].code != f->mbus_code) + continue; + if (!formats[i].metadata_fmt) + continue; + + if (formats[i].fourcc) { + if (j == f->index) { + f->pixelformat = formats[i].fourcc; + f->type = V4L2_BUF_TYPE_META_CAPTURE; + return 0; + } + j++; + } + } + + return -EINVAL; +} + +static int unicam_mc_g_fmt_meta_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct unicam_node *node = video_drvdata(file); + + if (node->pad_id != METADATA_PAD) + return -EINVAL; + + *f = node->v_fmt; + + return 0; +} + +static int unicam_mc_try_fmt_meta_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct unicam_node *node = video_drvdata(file); + + if (node->pad_id != METADATA_PAD) + return -EINVAL; + + f->fmt.meta.dataformat = V4L2_META_FMT_SENSOR_DATA; + + return 0; +} + +static int unicam_mc_s_fmt_meta_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct unicam_node *node = video_drvdata(file); + + if (node->pad_id != METADATA_PAD) + return -EINVAL; + + unicam_mc_try_fmt_meta_cap(file, priv, f); + + node->v_fmt = *f; + + return 0; +} + +static const struct v4l2_ioctl_ops unicam_mc_ioctl_ops = { + .vidioc_querycap = unicam_querycap, + .vidioc_enum_fmt_vid_cap = unicam_mc_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = unicam_mc_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = unicam_mc_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = unicam_mc_s_fmt_vid_cap, + + .vidioc_enum_fmt_meta_cap = unicam_mc_enum_fmt_meta_cap, + .vidioc_g_fmt_meta_cap = unicam_mc_g_fmt_meta_cap, + .vidioc_try_fmt_meta_cap = unicam_mc_try_fmt_meta_cap, + .vidioc_s_fmt_meta_cap = unicam_mc_s_fmt_meta_cap, + + .vidioc_enum_framesizes = unicam_mc_enum_framesizes, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + + .vidioc_log_status = unicam_log_status, + .vidioc_subscribe_event = unicam_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static int +unicam_mc_subdev_link_validate_get_format(struct media_pad *pad, + struct v4l2_subdev_format *fmt) +{ + if (is_media_entity_v4l2_subdev(pad->entity)) { + struct v4l2_subdev *sd = + media_entity_to_v4l2_subdev(pad->entity); + + fmt->which = V4L2_SUBDEV_FORMAT_ACTIVE; + fmt->pad = pad->index; + return v4l2_subdev_call(sd, pad, get_fmt, NULL, fmt); + } + + return -EINVAL; +} + +static int unicam_mc_video_link_validate(struct media_link *link) +{ + struct video_device *vd = container_of(link->sink->entity, + struct video_device, entity); + struct unicam_node *node = container_of(vd, struct unicam_node, + video_dev); + struct unicam_device *unicam = node->dev; + struct v4l2_subdev_format source_fmt; + int ret; + + if (!media_entity_remote_source_pad_unique(link->sink->entity)) { + unicam_dbg(1, unicam, + "video node %s pad not connected\n", vd->name); + return -ENOTCONN; + } + + ret = unicam_mc_subdev_link_validate_get_format(link->source, + &source_fmt); + if (ret < 0) + return 0; + + if (node->pad_id == IMAGE_PAD) { + struct v4l2_pix_format *pix_fmt = &node->v_fmt.fmt.pix; + const struct unicam_fmt *fmt; + + if (source_fmt.format.width != pix_fmt->width || + source_fmt.format.height != pix_fmt->height) { + unicam_err(unicam, + "Wrong width or height %ux%u (remote pad set to %ux%u)\n", + pix_fmt->width, pix_fmt->height, + source_fmt.format.width, + source_fmt.format.height); + return -EINVAL; + } + + fmt = find_format_by_code(source_fmt.format.code); + + if (!fmt || (fmt->fourcc != pix_fmt->pixelformat && + fmt->repacked_fourcc != pix_fmt->pixelformat)) + return -EINVAL; + } else { + struct v4l2_meta_format *meta_fmt = &node->v_fmt.fmt.meta; + + if (source_fmt.format.width != meta_fmt->buffersize || + source_fmt.format.height != 1 || + source_fmt.format.code != MEDIA_BUS_FMT_SENSOR_DATA) { + unicam_err(unicam, + "Wrong metadata width/height/code %ux%u %08x (remote pad set to %ux%u %08x)\n", + meta_fmt->buffersize, 1, + MEDIA_BUS_FMT_SENSOR_DATA, + source_fmt.format.width, + source_fmt.format.height, + source_fmt.format.code); + return -EINVAL; + } + } + + return 0; +} + +static const struct media_entity_operations unicam_mc_entity_ops = { + .link_validate = unicam_mc_video_link_validate, +}; + +/* videobuf2 Operations */ + +static int unicam_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, + unsigned int *nplanes, + unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct unicam_node *node = vb2_get_drv_priv(vq); + struct unicam_device *dev = node->dev; + unsigned int size = node->pad_id == IMAGE_PAD ? + node->v_fmt.fmt.pix.sizeimage : + node->v_fmt.fmt.meta.buffersize; + + if (*nplanes) { + if (sizes[0] < size) { + unicam_err(dev, "sizes[0] %i < size %u\n", sizes[0], + size); + return -EINVAL; + } + size = sizes[0]; + } + + *nplanes = 1; + sizes[0] = size; + + return 0; +} + +static int unicam_buffer_prepare(struct vb2_buffer *vb) +{ + struct unicam_node *node = vb2_get_drv_priv(vb->vb2_queue); + struct unicam_device *dev = node->dev; + struct unicam_buffer *buf = to_unicam_buffer(vb); + unsigned long size; + + if (WARN_ON(!node->fmt)) + return -EINVAL; + + size = node->pad_id == IMAGE_PAD ? node->v_fmt.fmt.pix.sizeimage : + node->v_fmt.fmt.meta.buffersize; + if (vb2_plane_size(vb, 0) < size) { + unicam_err(dev, "data will not fit into plane (%lu < %lu)\n", + vb2_plane_size(vb, 0), size); + return -EINVAL; + } + + vb2_set_plane_payload(&buf->vb.vb2_buf, 0, size); + return 0; +} + +static void unicam_buffer_queue(struct vb2_buffer *vb) +{ + struct unicam_node *node = vb2_get_drv_priv(vb->vb2_queue); + struct unicam_buffer *buf = to_unicam_buffer(vb); + unsigned long flags; + + spin_lock_irqsave(&node->dma_queue_lock, flags); + list_add_tail(&buf->list, &node->dma_queue); + spin_unlock_irqrestore(&node->dma_queue_lock, flags); +} + +static void unicam_set_packing_config(struct unicam_device *dev) +{ + u32 pack, unpack; + u32 val; + + if (dev->node[IMAGE_PAD].v_fmt.fmt.pix.pixelformat == + dev->node[IMAGE_PAD].fmt->fourcc) { + unpack = UNICAM_PUM_NONE; + pack = UNICAM_PPM_NONE; + } else { + switch (dev->node[IMAGE_PAD].fmt->depth) { + case 8: + unpack = UNICAM_PUM_UNPACK8; + break; + case 10: + unpack = UNICAM_PUM_UNPACK10; + break; + case 12: + unpack = UNICAM_PUM_UNPACK12; + break; + case 14: + unpack = UNICAM_PUM_UNPACK14; + break; + case 16: + unpack = UNICAM_PUM_UNPACK16; + break; + default: + unpack = UNICAM_PUM_NONE; + break; + } + + /* Repacking is always to 16bpp */ + pack = UNICAM_PPM_PACK16; + } + + val = 0; + set_field(&val, unpack, UNICAM_PUM_MASK); + set_field(&val, pack, UNICAM_PPM_MASK); + reg_write(dev, UNICAM_IPIPE, val); +} + +static void unicam_cfg_image_id(struct unicam_device *dev) +{ + if (dev->bus_type == V4L2_MBUS_CSI2_DPHY) { + /* CSI2 mode, hardcode VC 0 for now. */ + reg_write(dev, UNICAM_IDI0, + (0 << 6) | dev->node[IMAGE_PAD].fmt->csi_dt); + } else { + /* CCP2 mode */ + reg_write(dev, UNICAM_IDI0, + 0x80 | dev->node[IMAGE_PAD].fmt->csi_dt); + } +} + +static void unicam_enable_ed(struct unicam_device *dev) +{ + u32 val = reg_read(dev, UNICAM_DCS); + + set_field(&val, 2, UNICAM_EDL_MASK); + /* Do not wrap at the end of the embedded data buffer */ + set_field(&val, 0, UNICAM_DBOB); + + reg_write(dev, UNICAM_DCS, val); +} + +static void unicam_start_rx(struct unicam_device *dev, dma_addr_t *addr) +{ + int line_int_freq = dev->node[IMAGE_PAD].v_fmt.fmt.pix.height >> 2; + unsigned int size, i; + u32 val; + + if (line_int_freq < 128) + line_int_freq = 128; + + /* Enable lane clocks */ + val = 1; + for (i = 0; i < dev->active_data_lanes; i++) + val = val << 2 | 1; + clk_write(dev, val); + + /* Basic init */ + reg_write(dev, UNICAM_CTRL, UNICAM_MEM); + + /* Enable analogue control, and leave in reset. */ + val = UNICAM_AR; + set_field(&val, 7, UNICAM_CTATADJ_MASK); + set_field(&val, 7, UNICAM_PTATADJ_MASK); + reg_write(dev, UNICAM_ANA, val); + usleep_range(1000, 2000); + + /* Come out of reset */ + reg_write_field(dev, UNICAM_ANA, 0, UNICAM_AR); + + /* Peripheral reset */ + reg_write_field(dev, UNICAM_CTRL, 1, UNICAM_CPR); + reg_write_field(dev, UNICAM_CTRL, 0, UNICAM_CPR); + + reg_write_field(dev, UNICAM_CTRL, 0, UNICAM_CPE); + + /* Enable Rx control. */ + val = reg_read(dev, UNICAM_CTRL); + if (dev->bus_type == V4L2_MBUS_CSI2_DPHY) { + set_field(&val, UNICAM_CPM_CSI2, UNICAM_CPM_MASK); + set_field(&val, UNICAM_DCM_STROBE, UNICAM_DCM_MASK); + } else { + set_field(&val, UNICAM_CPM_CCP2, UNICAM_CPM_MASK); + set_field(&val, dev->bus_flags, UNICAM_DCM_MASK); + } + /* Packet framer timeout */ + set_field(&val, 0xf, UNICAM_PFT_MASK); + set_field(&val, 128, UNICAM_OET_MASK); + reg_write(dev, UNICAM_CTRL, val); + + reg_write(dev, UNICAM_IHWIN, 0); + reg_write(dev, UNICAM_IVWIN, 0); + + /* AXI bus access QoS setup */ + val = reg_read(dev, UNICAM_PRI); + set_field(&val, 0, UNICAM_BL_MASK); + set_field(&val, 0, UNICAM_BS_MASK); + set_field(&val, 0xe, UNICAM_PP_MASK); + set_field(&val, 8, UNICAM_NP_MASK); + set_field(&val, 2, UNICAM_PT_MASK); + set_field(&val, 1, UNICAM_PE); + reg_write(dev, UNICAM_PRI, val); + + reg_write_field(dev, UNICAM_ANA, 0, UNICAM_DDL); + + val = UNICAM_FSIE | UNICAM_FEIE | UNICAM_IBOB; + set_field(&val, line_int_freq, UNICAM_LCIE_MASK); + reg_write(dev, UNICAM_ICTL, val); + reg_write(dev, UNICAM_STA, UNICAM_STA_MASK_ALL); + reg_write(dev, UNICAM_ISTA, UNICAM_ISTA_MASK_ALL); + + /* tclk_term_en */ + reg_write_field(dev, UNICAM_CLT, 2, UNICAM_CLT1_MASK); + /* tclk_settle */ + reg_write_field(dev, UNICAM_CLT, 6, UNICAM_CLT2_MASK); + /* td_term_en */ + reg_write_field(dev, UNICAM_DLT, 2, UNICAM_DLT1_MASK); + /* ths_settle */ + reg_write_field(dev, UNICAM_DLT, 6, UNICAM_DLT2_MASK); + /* trx_enable */ + reg_write_field(dev, UNICAM_DLT, 0, UNICAM_DLT3_MASK); + + reg_write_field(dev, UNICAM_CTRL, 0, UNICAM_SOE); + + /* Packet compare setup - required to avoid missing frame ends */ + val = 0; + set_field(&val, 1, UNICAM_PCE); + set_field(&val, 1, UNICAM_GI); + set_field(&val, 1, UNICAM_CPH); + set_field(&val, 0, UNICAM_PCVC_MASK); + set_field(&val, 1, UNICAM_PCDT_MASK); + reg_write(dev, UNICAM_CMP0, val); + + /* Enable clock lane and set up terminations */ + val = 0; + if (dev->bus_type == V4L2_MBUS_CSI2_DPHY) { + /* CSI2 */ + set_field(&val, 1, UNICAM_CLE); + set_field(&val, 1, UNICAM_CLLPE); + if (!(dev->bus_flags & V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK)) { + set_field(&val, 1, UNICAM_CLTRE); + set_field(&val, 1, UNICAM_CLHSE); + } + } else { + /* CCP2 */ + set_field(&val, 1, UNICAM_CLE); + set_field(&val, 1, UNICAM_CLHSE); + set_field(&val, 1, UNICAM_CLTRE); + } + reg_write(dev, UNICAM_CLK, val); + + /* + * Enable required data lanes with appropriate terminations. + * The same value needs to be written to UNICAM_DATn registers for + * the active lanes, and 0 for inactive ones. + */ + val = 0; + if (dev->bus_type == V4L2_MBUS_CSI2_DPHY) { + /* CSI2 */ + set_field(&val, 1, UNICAM_DLE); + set_field(&val, 1, UNICAM_DLLPE); + if (!(dev->bus_flags & V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK)) { + set_field(&val, 1, UNICAM_DLTRE); + set_field(&val, 1, UNICAM_DLHSE); + } + } else { + /* CCP2 */ + set_field(&val, 1, UNICAM_DLE); + set_field(&val, 1, UNICAM_DLHSE); + set_field(&val, 1, UNICAM_DLTRE); + } + reg_write(dev, UNICAM_DAT0, val); + + if (dev->active_data_lanes == 1) + val = 0; + reg_write(dev, UNICAM_DAT1, val); + + if (dev->max_data_lanes > 2) { + /* + * Registers UNICAM_DAT2 and UNICAM_DAT3 only valid if the + * instance supports more than 2 data lanes. + */ + if (dev->active_data_lanes == 2) + val = 0; + reg_write(dev, UNICAM_DAT2, val); + + if (dev->active_data_lanes == 3) + val = 0; + reg_write(dev, UNICAM_DAT3, val); + } + + reg_write(dev, UNICAM_IBLS, + dev->node[IMAGE_PAD].v_fmt.fmt.pix.bytesperline); + size = dev->node[IMAGE_PAD].v_fmt.fmt.pix.sizeimage; + unicam_wr_dma_addr(dev, addr[IMAGE_PAD], size, IMAGE_PAD); + unicam_set_packing_config(dev); + unicam_cfg_image_id(dev); + + val = reg_read(dev, UNICAM_MISC); + set_field(&val, 1, UNICAM_FL0); + set_field(&val, 1, UNICAM_FL1); + reg_write(dev, UNICAM_MISC, val); + + if (dev->node[METADATA_PAD].streaming && dev->sensor_embedded_data) { + size = dev->node[METADATA_PAD].v_fmt.fmt.meta.buffersize; + unicam_enable_ed(dev); + unicam_wr_dma_addr(dev, addr[METADATA_PAD], size, METADATA_PAD); + } + + /* Enable peripheral */ + reg_write_field(dev, UNICAM_CTRL, 1, UNICAM_CPE); + + /* Load image pointers */ + reg_write_field(dev, UNICAM_ICTL, 1, UNICAM_LIP_MASK); + + /* Load embedded data buffer pointers if needed */ + if (dev->node[METADATA_PAD].streaming && dev->sensor_embedded_data) + reg_write_field(dev, UNICAM_DCS, 1, UNICAM_LDP); +} + +static void unicam_disable(struct unicam_device *dev) +{ + /* Analogue lane control disable */ + reg_write_field(dev, UNICAM_ANA, 1, UNICAM_DDL); + + /* Stop the output engine */ + reg_write_field(dev, UNICAM_CTRL, 1, UNICAM_SOE); + + /* Disable the data lanes. */ + reg_write(dev, UNICAM_DAT0, 0); + reg_write(dev, UNICAM_DAT1, 0); + + if (dev->max_data_lanes > 2) { + reg_write(dev, UNICAM_DAT2, 0); + reg_write(dev, UNICAM_DAT3, 0); + } + + /* Peripheral reset */ + reg_write_field(dev, UNICAM_CTRL, 1, UNICAM_CPR); + usleep_range(50, 100); + reg_write_field(dev, UNICAM_CTRL, 0, UNICAM_CPR); + + /* Disable peripheral */ + reg_write_field(dev, UNICAM_CTRL, 0, UNICAM_CPE); + + /* Clear ED setup */ + reg_write(dev, UNICAM_DCS, 0); + + /* Disable all lane clocks */ + clk_write(dev, 0); +} + +static void unicam_return_buffers(struct unicam_node *node, + enum vb2_buffer_state state) +{ + struct unicam_buffer *buf, *tmp; + unsigned long flags; + + spin_lock_irqsave(&node->dma_queue_lock, flags); + list_for_each_entry_safe(buf, tmp, &node->dma_queue, list) { + list_del(&buf->list); + vb2_buffer_done(&buf->vb.vb2_buf, state); + } + + if (node->cur_frm) + vb2_buffer_done(&node->cur_frm->vb.vb2_buf, + state); + if (node->next_frm && node->cur_frm != node->next_frm) + vb2_buffer_done(&node->next_frm->vb.vb2_buf, + state); + + node->cur_frm = NULL; + node->next_frm = NULL; + spin_unlock_irqrestore(&node->dma_queue_lock, flags); +} + +static int unicam_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct unicam_node *node = vb2_get_drv_priv(vq); + struct unicam_device *dev = node->dev; + dma_addr_t buffer_addr[MAX_NODES] = { 0 }; + unsigned long flags; + unsigned int i; + int ret; + + node->streaming = true; + if (!(dev->node[IMAGE_PAD].open && dev->node[IMAGE_PAD].streaming && + (!dev->node[METADATA_PAD].open || + dev->node[METADATA_PAD].streaming))) { + /* + * Metadata pad must be enabled before image pad if it is + * wanted. + */ + unicam_dbg(3, dev, "Not all nodes are streaming yet."); + return 0; + } + + dev->sequence = 0; + ret = unicam_runtime_get(dev); + if (ret < 0) { + unicam_dbg(3, dev, "unicam_runtime_get failed\n"); + goto err_streaming; + } + + ret = media_pipeline_start(dev->node[IMAGE_PAD].video_dev.entity.pads, + &dev->node[IMAGE_PAD].pipe); + if (ret < 0) { + unicam_err(dev, "Failed to start media pipeline: %d\n", ret); + goto err_pm_put; + } + + dev->active_data_lanes = dev->max_data_lanes; + + if (dev->bus_type == V4L2_MBUS_CSI2_DPHY) { + struct v4l2_mbus_config mbus_config = { 0 }; + + ret = v4l2_subdev_call(dev->sensor, pad, get_mbus_config, + 0, &mbus_config); + if (ret < 0 && ret != -ENOIOCTLCMD) { + unicam_dbg(3, dev, "g_mbus_config failed\n"); + goto error_pipeline; + } + + dev->active_data_lanes = mbus_config.bus.mipi_csi2.num_data_lanes; + if (!dev->active_data_lanes) + dev->active_data_lanes = dev->max_data_lanes; + if (dev->active_data_lanes > dev->max_data_lanes) { + unicam_err(dev, "Device has requested %u data lanes, which is >%u configured in DT\n", + dev->active_data_lanes, + dev->max_data_lanes); + ret = -EINVAL; + goto error_pipeline; + } + } + + unicam_dbg(1, dev, "Running with %u data lanes\n", + dev->active_data_lanes); + + ret = clk_set_min_rate(dev->vpu_clock, MIN_VPU_CLOCK_RATE); + if (ret) { + unicam_err(dev, "failed to set up VPU clock\n"); + goto error_pipeline; + } + + ret = clk_prepare_enable(dev->vpu_clock); + if (ret) { + unicam_err(dev, "Failed to enable VPU clock: %d\n", ret); + goto error_pipeline; + } + + ret = clk_set_rate(dev->clock, 100 * 1000 * 1000); + if (ret) { + unicam_err(dev, "failed to set up CSI clock\n"); + goto err_vpu_clock; + } + + ret = clk_prepare_enable(dev->clock); + if (ret) { + unicam_err(dev, "Failed to enable CSI clock: %d\n", ret); + goto err_vpu_clock; + } + + for (i = 0; i < ARRAY_SIZE(dev->node); i++) { + struct unicam_buffer *buf; + + if (!dev->node[i].streaming) + continue; + + spin_lock_irqsave(&dev->node[i].dma_queue_lock, flags); + buf = list_first_entry(&dev->node[i].dma_queue, + struct unicam_buffer, list); + dev->node[i].cur_frm = buf; + dev->node[i].next_frm = buf; + list_del(&buf->list); + spin_unlock_irqrestore(&dev->node[i].dma_queue_lock, flags); + + buffer_addr[i] = + vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0); + } + + dev->frame_started = false; + unicam_start_rx(dev, buffer_addr); + + ret = v4l2_subdev_call(dev->sensor, video, s_stream, 1); + if (ret < 0) { + unicam_err(dev, "stream on failed in subdev\n"); + goto err_disable_unicam; + } + + dev->clocks_enabled = true; + return 0; + +err_disable_unicam: + unicam_disable(dev); + clk_disable_unprepare(dev->clock); +err_vpu_clock: + if (clk_set_min_rate(dev->vpu_clock, 0)) + unicam_err(dev, "failed to reset the VPU clock\n"); + clk_disable_unprepare(dev->vpu_clock); +error_pipeline: + if (node->pad_id == IMAGE_PAD) + media_pipeline_stop(dev->node[IMAGE_PAD].video_dev.entity.pads); +err_pm_put: + unicam_runtime_put(dev); +err_streaming: + unicam_return_buffers(node, VB2_BUF_STATE_QUEUED); + node->streaming = false; + + return ret; +} + +static void unicam_stop_streaming(struct vb2_queue *vq) +{ + struct unicam_node *node = vb2_get_drv_priv(vq); + struct unicam_device *dev = node->dev; + + node->streaming = false; + + if (node->pad_id == IMAGE_PAD) { + /* + * Stop streaming the sensor and disable the peripheral. + * We cannot continue streaming embedded data with the + * image pad disabled. + */ + if (v4l2_subdev_call(dev->sensor, video, s_stream, 0) < 0) + unicam_err(dev, "stream off failed in subdev\n"); + + unicam_disable(dev); + + media_pipeline_stop(node->video_dev.entity.pads); + + if (dev->clocks_enabled) { + if (clk_set_min_rate(dev->vpu_clock, 0)) + unicam_err(dev, "failed to reset the min VPU clock\n"); + + clk_disable_unprepare(dev->vpu_clock); + clk_disable_unprepare(dev->clock); + dev->clocks_enabled = false; + } + unicam_runtime_put(dev); + + } else if (node->pad_id == METADATA_PAD) { + /* + * Allow the hardware to spin in the dummy buffer. + * This is only really needed if the embedded data pad is + * disabled before the image pad. + */ + unicam_wr_dma_addr(dev, node->dummy_buf_dma_addr, 0, + METADATA_PAD); + } + + /* Clear all queued buffers for the node */ + unicam_return_buffers(node, VB2_BUF_STATE_ERROR); +} + + +static const struct vb2_ops unicam_video_qops = { + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .queue_setup = unicam_queue_setup, + .buf_prepare = unicam_buffer_prepare, + .buf_queue = unicam_buffer_queue, + .start_streaming = unicam_start_streaming, + .stop_streaming = unicam_stop_streaming, +}; + +/* + * unicam_v4l2_open : This function is based on the v4l2_fh_open helper + * function. It has been augmented to handle sensor subdevice power management, + */ +static int unicam_v4l2_open(struct file *file) +{ + struct unicam_node *node = video_drvdata(file); + struct unicam_device *dev = node->dev; + int ret; + + mutex_lock(&node->lock); + + ret = v4l2_fh_open(file); + if (ret) { + unicam_err(dev, "v4l2_fh_open failed\n"); + goto unlock; + } + + node->open++; + + if (!v4l2_fh_is_singular_file(file)) + goto unlock; + + ret = v4l2_subdev_call(dev->sensor, core, s_power, 1); + if (ret < 0 && ret != -ENOIOCTLCMD) { + v4l2_fh_release(file); + node->open--; + goto unlock; + } + + ret = 0; + +unlock: + mutex_unlock(&node->lock); + return ret; +} + +static int unicam_v4l2_release(struct file *file) +{ + struct unicam_node *node = video_drvdata(file); + struct unicam_device *dev = node->dev; + struct v4l2_subdev *sd = dev->sensor; + bool fh_singular; + int ret; + + mutex_lock(&node->lock); + + fh_singular = v4l2_fh_is_singular_file(file); + + ret = _vb2_fop_release(file, NULL); + + if (fh_singular) + v4l2_subdev_call(sd, core, s_power, 0); + + node->open--; + mutex_unlock(&node->lock); + + return ret; +} + +/* unicam capture driver file operations */ +static const struct v4l2_file_operations unicam_fops = { + .owner = THIS_MODULE, + .open = unicam_v4l2_open, + .release = unicam_v4l2_release, + .read = vb2_fop_read, + .poll = vb2_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap, +}; + +static int +unicam_async_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_connection *asd) +{ + struct unicam_device *unicam = to_unicam_device(notifier->v4l2_dev); + + if (unicam->sensor) { + unicam_info(unicam, "Rejecting subdev %s (Already set!!)", + subdev->name); + return 0; + } + + unicam->sensor = subdev; + unicam_dbg(1, unicam, "Using sensor %s for capture\n", subdev->name); + + return 0; +} + +static void unicam_release(struct kref *kref) +{ + struct unicam_device *unicam = + container_of(kref, struct unicam_device, kref); + + v4l2_ctrl_handler_free(&unicam->ctrl_handler); + media_device_cleanup(&unicam->mdev); + + if (unicam->sensor_state) + __v4l2_subdev_state_free(unicam->sensor_state); + + kfree(unicam); +} + +static void unicam_put(struct unicam_device *unicam) +{ + kref_put(&unicam->kref, unicam_release); +} + +static void unicam_get(struct unicam_device *unicam) +{ + kref_get(&unicam->kref); +} + +static void unicam_node_release(struct video_device *vdev) +{ + struct unicam_node *node = video_get_drvdata(vdev); + + unicam_put(node->dev); +} + +static int unicam_set_default_format(struct unicam_device *unicam, + struct unicam_node *node, + int pad_id, + const struct unicam_fmt **ret_fmt) +{ + struct v4l2_mbus_framefmt mbus_fmt = {0}; + const struct unicam_fmt *fmt; + int ret; + + if (pad_id == IMAGE_PAD) { + ret = __subdev_get_format(unicam, &mbus_fmt, pad_id); + if (ret) { + unicam_err(unicam, "Failed to get_format - ret %d\n", + ret); + return ret; + } + + fmt = find_format_by_code(mbus_fmt.code); + if (!fmt) { + /* + * Find the first format that the sensor and unicam both + * support + */ + fmt = get_first_supported_format(unicam); + + if (fmt) { + mbus_fmt.code = fmt->code; + ret = __subdev_set_format(unicam, &mbus_fmt, pad_id); + if (ret) + return -EINVAL; + } + } + if (mbus_fmt.field != V4L2_FIELD_NONE) { + /* Interlaced not supported - disable it now. */ + mbus_fmt.field = V4L2_FIELD_NONE; + ret = __subdev_set_format(unicam, &mbus_fmt, pad_id); + if (ret) + return -EINVAL; + } + + if (fmt) + node->v_fmt.fmt.pix.pixelformat = fmt->fourcc ? fmt->fourcc + : fmt->repacked_fourcc; + } else { + /* Fix this node format as embedded data. */ + fmt = find_format_by_code(MEDIA_BUS_FMT_SENSOR_DATA); + node->v_fmt.fmt.meta.dataformat = fmt->fourcc; + } + + *ret_fmt = fmt; + + return 0; +} + +static void unicam_mc_set_default_format(struct unicam_node *node, int pad_id) +{ + if (pad_id == IMAGE_PAD) { + struct v4l2_pix_format *pix_fmt = &node->v_fmt.fmt.pix; + + pix_fmt->width = 640; + pix_fmt->height = 480; + pix_fmt->field = V4L2_FIELD_NONE; + pix_fmt->colorspace = V4L2_COLORSPACE_SRGB; + pix_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601; + pix_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE; + pix_fmt->xfer_func = V4L2_XFER_FUNC_SRGB; + pix_fmt->pixelformat = formats[0].fourcc; + unicam_calc_format_size_bpl(node->dev, &formats[0], + &node->v_fmt); + node->v_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + node->fmt = &formats[0]; + } else { + const struct unicam_fmt *fmt; + + /* Fix this node format as embedded data. */ + fmt = find_format_by_code(MEDIA_BUS_FMT_SENSOR_DATA); + node->v_fmt.fmt.meta.dataformat = fmt->fourcc; + node->fmt = fmt; + + node->v_fmt.fmt.meta.buffersize = UNICAM_EMBEDDED_SIZE; + node->embedded_lines = 1; + node->v_fmt.type = V4L2_BUF_TYPE_META_CAPTURE; + } +} + +static int register_node(struct unicam_device *unicam, struct unicam_node *node, + enum v4l2_buf_type type, int pad_id) +{ + struct video_device *vdev; + struct vb2_queue *q; + int ret; + + node->dev = unicam; + node->pad_id = pad_id; + + if (!unicam->mc_api) { + const struct unicam_fmt *fmt; + + ret = unicam_set_default_format(unicam, node, pad_id, &fmt); + if (ret) + return ret; + node->fmt = fmt; + /* Read current subdev format */ + if (fmt) + unicam_reset_format(node); + } else { + unicam_mc_set_default_format(node, pad_id); + } + + if (!unicam->mc_api && + v4l2_subdev_has_op(unicam->sensor, video, s_std)) { + v4l2_std_id tvnorms; + + if (WARN_ON(!v4l2_subdev_has_op(unicam->sensor, video, + g_tvnorms))) + /* + * Subdevice should not advertise s_std but not + * g_tvnorms + */ + return -EINVAL; + + ret = v4l2_subdev_call(unicam->sensor, video, + g_tvnorms, &tvnorms); + if (WARN_ON(ret)) + return -EINVAL; + node->video_dev.tvnorms |= tvnorms; + } + + spin_lock_init(&node->dma_queue_lock); + mutex_init(&node->lock); + + vdev = &node->video_dev; + if (pad_id == IMAGE_PAD) { + if (!unicam->mc_api) { + /* Add controls from the subdevice */ + ret = v4l2_ctrl_add_handler(&unicam->ctrl_handler, + unicam->sensor->ctrl_handler, + NULL, + true); + if (ret < 0) + return ret; + } + + /* + * If the sensor subdevice has any controls, associate the node + * with the ctrl handler to allow access from userland. + */ + if (!list_empty(&unicam->ctrl_handler.ctrls)) + vdev->ctrl_handler = &unicam->ctrl_handler; + } + + q = &node->buffer_queue; + q->type = type; + q->io_modes = VB2_MMAP | VB2_DMABUF | VB2_READ; + q->drv_priv = node; + q->ops = &unicam_video_qops; + q->mem_ops = &vb2_dma_contig_memops; + q->buf_struct_size = sizeof(struct unicam_buffer); + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->lock = &node->lock; + q->min_queued_buffers = 1; + q->dev = &unicam->pdev->dev; + + ret = vb2_queue_init(q); + if (ret) { + unicam_err(unicam, "vb2_queue_init() failed\n"); + return ret; + } + + INIT_LIST_HEAD(&node->dma_queue); + + vdev->release = unicam_node_release; + vdev->fops = &unicam_fops; + vdev->ioctl_ops = unicam->mc_api ? &unicam_mc_ioctl_ops : + &unicam_ioctl_ops; + vdev->v4l2_dev = &unicam->v4l2_dev; + vdev->vfl_dir = VFL_DIR_RX; + vdev->queue = q; + vdev->lock = &node->lock; + vdev->device_caps = (pad_id == IMAGE_PAD) ? + V4L2_CAP_VIDEO_CAPTURE : V4L2_CAP_META_CAPTURE; + vdev->device_caps |= V4L2_CAP_READWRITE | V4L2_CAP_STREAMING; + if (unicam->mc_api) { + vdev->device_caps |= V4L2_CAP_IO_MC; + vdev->entity.ops = &unicam_mc_entity_ops; + } + + /* Define the device names */ + snprintf(vdev->name, sizeof(vdev->name), "%s-%s", UNICAM_MODULE_NAME, + pad_id == IMAGE_PAD ? "image" : "embedded"); + + video_set_drvdata(vdev, node); + if (pad_id == IMAGE_PAD) + vdev->entity.flags |= MEDIA_ENT_FL_DEFAULT; + node->pad.flags = MEDIA_PAD_FL_SINK; + media_entity_pads_init(&vdev->entity, 1, &node->pad); + + node->dummy_buf_cpu_addr = dma_alloc_coherent(&unicam->pdev->dev, + DUMMY_BUF_SIZE, + &node->dummy_buf_dma_addr, + GFP_KERNEL); + if (!node->dummy_buf_cpu_addr) { + unicam_err(unicam, "Unable to allocate dummy buffer.\n"); + return -ENOMEM; + } + if (!unicam->mc_api) { + if (pad_id == METADATA_PAD || + !v4l2_subdev_has_op(unicam->sensor, video, s_std)) { + v4l2_disable_ioctl(&node->video_dev, VIDIOC_S_STD); + v4l2_disable_ioctl(&node->video_dev, VIDIOC_G_STD); + v4l2_disable_ioctl(&node->video_dev, VIDIOC_ENUMSTD); + } + if (pad_id == METADATA_PAD || + !v4l2_subdev_has_op(unicam->sensor, video, querystd)) + v4l2_disable_ioctl(&node->video_dev, VIDIOC_QUERYSTD); + if (pad_id == METADATA_PAD || + !v4l2_subdev_has_op(unicam->sensor, pad, s_dv_timings)) { + v4l2_disable_ioctl(&node->video_dev, VIDIOC_S_EDID); + v4l2_disable_ioctl(&node->video_dev, VIDIOC_G_EDID); + v4l2_disable_ioctl(&node->video_dev, + VIDIOC_DV_TIMINGS_CAP); + v4l2_disable_ioctl(&node->video_dev, + VIDIOC_G_DV_TIMINGS); + v4l2_disable_ioctl(&node->video_dev, + VIDIOC_S_DV_TIMINGS); + v4l2_disable_ioctl(&node->video_dev, + VIDIOC_ENUM_DV_TIMINGS); + v4l2_disable_ioctl(&node->video_dev, + VIDIOC_QUERY_DV_TIMINGS); + } + if (pad_id == METADATA_PAD || + !v4l2_subdev_has_op(unicam->sensor, pad, + enum_frame_interval)) + v4l2_disable_ioctl(&node->video_dev, + VIDIOC_ENUM_FRAMEINTERVALS); + if (pad_id == METADATA_PAD || + !v4l2_subdev_has_op(unicam->sensor, pad, + get_frame_interval)) + v4l2_disable_ioctl(&node->video_dev, VIDIOC_G_PARM); + if (pad_id == METADATA_PAD || + !v4l2_subdev_has_op(unicam->sensor, pad, + set_frame_interval)) + v4l2_disable_ioctl(&node->video_dev, VIDIOC_S_PARM); + + if (pad_id == METADATA_PAD || + !v4l2_subdev_has_op(unicam->sensor, pad, + enum_frame_size)) + v4l2_disable_ioctl(&node->video_dev, + VIDIOC_ENUM_FRAMESIZES); + + if (node->pad_id == METADATA_PAD || + !v4l2_subdev_has_op(unicam->sensor, pad, set_selection)) + v4l2_disable_ioctl(&node->video_dev, + VIDIOC_S_SELECTION); + + if (node->pad_id == METADATA_PAD || + !v4l2_subdev_has_op(unicam->sensor, pad, get_selection)) + v4l2_disable_ioctl(&node->video_dev, + VIDIOC_G_SELECTION); + } + + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); + if (ret) { + unicam_err(unicam, "Unable to register video device %s\n", + vdev->name); + return ret; + } + + /* + * Acquire a reference to unicam, which will be released when the video + * device will be unregistered and userspace will have closed all open + * file handles. + */ + unicam_get(unicam); + node->registered = true; + + if (pad_id != METADATA_PAD || unicam->sensor_embedded_data) { + ret = media_create_pad_link(&unicam->sensor->entity, + node->src_pad_id, + &node->video_dev.entity, 0, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret) + unicam_err(unicam, "Unable to create pad link for %s\n", + vdev->name); + } + + return ret; +} + +static void unregister_nodes(struct unicam_device *unicam) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(unicam->node); i++) { + struct unicam_node *node = &unicam->node[i]; + + if (node->dummy_buf_cpu_addr) { + dma_free_coherent(&unicam->pdev->dev, DUMMY_BUF_SIZE, + node->dummy_buf_cpu_addr, + node->dummy_buf_dma_addr); + } + + if (node->registered) { + node->registered = false; + video_unregister_device(&node->video_dev); + } + } +} + +static int unicam_async_complete(struct v4l2_async_notifier *notifier) +{ + static struct lock_class_key key; + struct unicam_device *unicam = to_unicam_device(notifier->v4l2_dev); + unsigned int i, source_pads = 0; + int ret; + + unicam->v4l2_dev.notify = unicam_notify; + + unicam->sensor_state = __v4l2_subdev_state_alloc(unicam->sensor, + "unicam:async->lock", &key); + if (!unicam->sensor_state) + return -ENOMEM; + + for (i = 0; i < unicam->sensor->entity.num_pads; i++) { + if (unicam->sensor->entity.pads[i].flags & MEDIA_PAD_FL_SOURCE) { + if (source_pads < MAX_NODES) { + unicam->node[source_pads].src_pad_id = i; + unicam_dbg(3, unicam, "source pad %u is index %u\n", + source_pads, i); + } + source_pads++; + } + } + if (!source_pads) { + unicam_err(unicam, "No source pads on sensor.\n"); + ret = -ENODEV; + goto unregister; + } + + ret = register_node(unicam, &unicam->node[IMAGE_PAD], + V4L2_BUF_TYPE_VIDEO_CAPTURE, IMAGE_PAD); + if (ret) { + unicam_err(unicam, "Unable to register image video device.\n"); + goto unregister; + } + + if (source_pads >= 2) { + unicam->sensor_embedded_data = true; + + ret = register_node(unicam, &unicam->node[METADATA_PAD], + V4L2_BUF_TYPE_META_CAPTURE, METADATA_PAD); + if (ret) { + unicam_err(unicam, "Unable to register metadata video device.\n"); + goto unregister; + } + } + + if (unicam->mc_api) + ret = v4l2_device_register_subdev_nodes(&unicam->v4l2_dev); + else + ret = v4l2_device_register_ro_subdev_nodes(&unicam->v4l2_dev); + if (ret) { + unicam_err(unicam, "Unable to register subdev nodes.\n"); + goto unregister; + } + + /* + * Release the initial reference, all references are now owned by the + * video devices. + */ + unicam_put(unicam); + return 0; + +unregister: + unregister_nodes(unicam); + unicam_put(unicam); + + return ret; +} + +static const struct v4l2_async_notifier_operations unicam_async_ops = { + .bound = unicam_async_bound, + .complete = unicam_async_complete, +}; + +static int of_unicam_connect_subdevs(struct unicam_device *dev) +{ + struct platform_device *pdev = dev->pdev; + struct v4l2_fwnode_endpoint ep = { }; + struct device_node *ep_node; + struct device_node *sensor_node; + unsigned int lane; + int ret = -EINVAL; + + if (of_property_read_u32(pdev->dev.of_node, "brcm,num-data-lanes", + &dev->max_data_lanes) < 0) { + unicam_err(dev, "number of data lanes not set\n"); + return -EINVAL; + } + + /* Get the local endpoint and remote device. */ + ep_node = of_graph_get_next_endpoint(pdev->dev.of_node, NULL); + if (!ep_node) { + unicam_dbg(3, dev, "can't get next endpoint\n"); + return -EINVAL; + } + + unicam_dbg(3, dev, "ep_node is %pOF\n", ep_node); + + sensor_node = of_graph_get_remote_port_parent(ep_node); + if (!sensor_node) { + unicam_dbg(3, dev, "can't get remote parent\n"); + goto cleanup_exit; + } + + unicam_dbg(1, dev, "found subdevice %pOF\n", sensor_node); + + /* Parse the local endpoint and validate its configuration. */ + v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep_node), &ep); + + unicam_dbg(3, dev, "parsed local endpoint, bus_type %u\n", + ep.bus_type); + + dev->bus_type = ep.bus_type; + + switch (ep.bus_type) { + case V4L2_MBUS_CSI2_DPHY: + switch (ep.bus.mipi_csi2.num_data_lanes) { + case 1: + case 2: + case 4: + break; + + default: + unicam_err(dev, "subdevice %pOF: %u data lanes not supported\n", + sensor_node, + ep.bus.mipi_csi2.num_data_lanes); + goto cleanup_exit; + } + + for (lane = 0; lane < ep.bus.mipi_csi2.num_data_lanes; lane++) { + if (ep.bus.mipi_csi2.data_lanes[lane] != lane + 1) { + unicam_err(dev, "subdevice %pOF: data lanes reordering not supported\n", + sensor_node); + goto cleanup_exit; + } + } + + if (ep.bus.mipi_csi2.num_data_lanes > dev->max_data_lanes) { + unicam_err(dev, "subdevice requires %u data lanes when %u are supported\n", + ep.bus.mipi_csi2.num_data_lanes, + dev->max_data_lanes); + } + + dev->max_data_lanes = ep.bus.mipi_csi2.num_data_lanes; + dev->bus_flags = ep.bus.mipi_csi2.flags; + + break; + + case V4L2_MBUS_CCP2: + if (ep.bus.mipi_csi1.clock_lane != 0 || + ep.bus.mipi_csi1.data_lane != 1) { + unicam_err(dev, "subdevice %pOF: unsupported lanes configuration\n", + sensor_node); + goto cleanup_exit; + } + + dev->max_data_lanes = 1; + dev->bus_flags = ep.bus.mipi_csi1.strobe; + break; + + default: + /* Unsupported bus type */ + unicam_err(dev, "subdevice %pOF: unsupported bus type %u\n", + sensor_node, ep.bus_type); + goto cleanup_exit; + } + + unicam_dbg(3, dev, "subdevice %pOF: %s bus, %u data lanes, flags=0x%08x\n", + sensor_node, + dev->bus_type == V4L2_MBUS_CSI2_DPHY ? "CSI-2" : "CCP2", + dev->max_data_lanes, dev->bus_flags); + + /* Initialize and register the async notifier. */ + v4l2_async_nf_init(&dev->notifier, &dev->v4l2_dev); + dev->notifier.ops = &unicam_async_ops; + + dev->asd = v4l2_async_nf_add_fwnode(&dev->notifier, + of_fwnode_handle(sensor_node), + struct v4l2_async_connection); + if (IS_ERR(dev->asd)) { + unicam_err(dev, "Error adding subdevice: %d\n", ret); + goto cleanup_exit; + } + + ret = v4l2_async_nf_register(&dev->notifier); + if (ret) { + unicam_err(dev, "Error registering async notifier: %d\n", ret); + ret = -EINVAL; + } + +cleanup_exit: + of_node_put(sensor_node); + of_node_put(ep_node); + + return ret; +} + +static int unicam_probe(struct platform_device *pdev) +{ + struct unicam_device *unicam; + int ret; + + unicam = kzalloc(sizeof(*unicam), GFP_KERNEL); + if (!unicam) + return -ENOMEM; + + kref_init(&unicam->kref); + unicam->pdev = pdev; + + /* + * Adopt the current setting of the module parameter, and check if + * device tree requests it. + */ + unicam->mc_api = media_controller; + + /* Compatible is for the non-legacy version of the driver - use compatible */ + if (of_device_get_match_data(&unicam->pdev->dev)) + unicam->mc_api = true; + + if (of_property_read_bool(pdev->dev.of_node, "brcm,media-controller")) + unicam->mc_api = true; + + unicam->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(unicam->base)) { + unicam_err(unicam, "Failed to get main io block\n"); + ret = PTR_ERR(unicam->base); + goto err_unicam_put; + } + + unicam->clk_gate_base = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(unicam->clk_gate_base)) { + unicam_err(unicam, "Failed to get 2nd io block\n"); + ret = PTR_ERR(unicam->clk_gate_base); + goto err_unicam_put; + } + + unicam->clock = devm_clk_get(&pdev->dev, "lp"); + if (IS_ERR(unicam->clock)) { + unicam_err(unicam, "Failed to get lp clock\n"); + ret = PTR_ERR(unicam->clock); + goto err_unicam_put; + } + + unicam->vpu_clock = devm_clk_get(&pdev->dev, "vpu"); + if (IS_ERR(unicam->vpu_clock)) { + unicam_err(unicam, "Failed to get vpu clock\n"); + ret = PTR_ERR(unicam->vpu_clock); + goto err_unicam_put; + } + + unicam->sync_gpio = devm_gpiod_get_optional(&pdev->dev, "sync", + GPIOD_OUT_LOW); + + ret = platform_get_irq(pdev, 0); + if (ret <= 0) { + dev_err(&pdev->dev, "No IRQ resource\n"); + ret = -EINVAL; + goto err_unicam_put; + } + + ret = devm_request_irq(&pdev->dev, ret, unicam_isr, 0, + "unicam_capture0", unicam); + if (ret) { + dev_err(&pdev->dev, "Unable to request interrupt\n"); + ret = -EINVAL; + goto err_unicam_put; + } + + unicam->mdev.dev = &pdev->dev; + strscpy(unicam->mdev.model, UNICAM_MODULE_NAME, + sizeof(unicam->mdev.model)); + strscpy(unicam->mdev.serial, "", sizeof(unicam->mdev.serial)); + snprintf(unicam->mdev.bus_info, sizeof(unicam->mdev.bus_info), + "platform:%s", dev_name(&pdev->dev)); + unicam->mdev.hw_revision = 0; + + media_device_init(&unicam->mdev); + + unicam->v4l2_dev.mdev = &unicam->mdev; + + ret = v4l2_device_register(&pdev->dev, &unicam->v4l2_dev); + if (ret) { + unicam_err(unicam, + "Unable to register v4l2 device.\n"); + goto err_unicam_put; + } + + ret = media_device_register(&unicam->mdev); + if (ret < 0) { + unicam_err(unicam, + "Unable to register media-controller device.\n"); + goto err_v4l2_unregister; + } + + /* Reserve space for the controls */ + ret = v4l2_ctrl_handler_init(&unicam->ctrl_handler, 16); + if (ret < 0) + goto err_media_unregister; + + /* set the driver data in platform device */ + platform_set_drvdata(pdev, unicam); + + ret = of_unicam_connect_subdevs(unicam); + if (ret) { + dev_err(&pdev->dev, "Failed to connect subdevs\n"); + goto err_media_unregister; + } + + /* Enable the block power domain */ + pm_runtime_enable(&pdev->dev); + + return 0; + +err_media_unregister: + media_device_unregister(&unicam->mdev); +err_v4l2_unregister: + v4l2_device_unregister(&unicam->v4l2_dev); +err_unicam_put: + unicam_put(unicam); + + return ret; +} + +static void unicam_remove(struct platform_device *pdev) +{ + struct unicam_device *unicam = platform_get_drvdata(pdev); + + unicam_dbg(2, unicam, "%s\n", __func__); + + v4l2_async_nf_unregister(&unicam->notifier); + v4l2_device_unregister(&unicam->v4l2_dev); + media_device_unregister(&unicam->mdev); + unregister_nodes(unicam); + + pm_runtime_disable(&pdev->dev); +} + +static const struct of_device_id unicam_of_match[] = { + { .compatible = "brcm,bcm2835-unicam", .data = (void *)1 }, + { .compatible = "brcm,bcm2835-unicam-legacy", .data = 0 }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, unicam_of_match); + +static struct platform_driver unicam_driver = { + .probe = unicam_probe, + .remove = unicam_remove, + .driver = { + .name = UNICAM_MODULE_NAME, + .of_match_table = of_match_ptr(unicam_of_match), + }, +}; + +module_platform_driver(unicam_driver); + +MODULE_AUTHOR("Dave Stevenson <dave.stevenson@raspberrypi.com>"); +MODULE_DESCRIPTION("BCM2835 Unicam driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(UNICAM_VERSION); diff --git a/drivers/media/platform/bcm2835/vc4-regs-unicam.h b/drivers/media/platform/bcm2835/vc4-regs-unicam.h new file mode 100644 index 00000000000000..ae059a171d0fe0 --- /dev/null +++ b/drivers/media/platform/bcm2835/vc4-regs-unicam.h @@ -0,0 +1,253 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +/* + * Copyright (C) 2017-2020 Raspberry Pi Trading. + * Dave Stevenson <dave.stevenson@raspberrypi.com> + */ + +#ifndef VC4_REGS_UNICAM_H +#define VC4_REGS_UNICAM_H + +/* + * The following values are taken from files found within the code drop + * made by Broadcom for the BCM21553 Graphics Driver, predominantly in + * brcm_usrlib/dag/vmcsx/vcinclude/hardware_vc4.h. + * They have been modified to be only the register offset. + */ +#define UNICAM_CTRL 0x000 +#define UNICAM_STA 0x004 +#define UNICAM_ANA 0x008 +#define UNICAM_PRI 0x00c +#define UNICAM_CLK 0x010 +#define UNICAM_CLT 0x014 +#define UNICAM_DAT0 0x018 +#define UNICAM_DAT1 0x01c +#define UNICAM_DAT2 0x020 +#define UNICAM_DAT3 0x024 +#define UNICAM_DLT 0x028 +#define UNICAM_CMP0 0x02c +#define UNICAM_CMP1 0x030 +#define UNICAM_CAP0 0x034 +#define UNICAM_CAP1 0x038 +#define UNICAM_ICTL 0x100 +#define UNICAM_ISTA 0x104 +#define UNICAM_IDI0 0x108 +#define UNICAM_IPIPE 0x10c +#define UNICAM_IBSA0 0x110 +#define UNICAM_IBEA0 0x114 +#define UNICAM_IBLS 0x118 +#define UNICAM_IBWP 0x11c +#define UNICAM_IHWIN 0x120 +#define UNICAM_IHSTA 0x124 +#define UNICAM_IVWIN 0x128 +#define UNICAM_IVSTA 0x12c +#define UNICAM_ICC 0x130 +#define UNICAM_ICS 0x134 +#define UNICAM_IDC 0x138 +#define UNICAM_IDPO 0x13c +#define UNICAM_IDCA 0x140 +#define UNICAM_IDCD 0x144 +#define UNICAM_IDS 0x148 +#define UNICAM_DCS 0x200 +#define UNICAM_DBSA0 0x204 +#define UNICAM_DBEA0 0x208 +#define UNICAM_DBWP 0x20c +#define UNICAM_DBCTL 0x300 +#define UNICAM_IBSA1 0x304 +#define UNICAM_IBEA1 0x308 +#define UNICAM_IDI1 0x30c +#define UNICAM_DBSA1 0x310 +#define UNICAM_DBEA1 0x314 +#define UNICAM_MISC 0x400 + +/* + * The following bitmasks are from the kernel released by Broadcom + * for Android - https://android.googlesource.com/kernel/bcm/ + * The Rhea, Hawaii, and Java chips all contain the same VideoCore4 + * Unicam block as BCM2835, as defined in eg + * arch/arm/mach-rhea/include/mach/rdb_A0/brcm_rdb_cam.h and similar. + * Values reworked to use the kernel BIT and GENMASK macros. + * + * Some of the bit mnenomics have been amended to match the datasheet. + */ +/* UNICAM_CTRL Register */ +#define UNICAM_CPE BIT(0) +#define UNICAM_MEM BIT(1) +#define UNICAM_CPR BIT(2) +#define UNICAM_CPM_MASK GENMASK(3, 3) +#define UNICAM_CPM_CSI2 0 +#define UNICAM_CPM_CCP2 1 +#define UNICAM_SOE BIT(4) +#define UNICAM_DCM_MASK GENMASK(5, 5) +#define UNICAM_DCM_STROBE 0 +#define UNICAM_DCM_DATA 1 +#define UNICAM_SLS BIT(6) +#define UNICAM_PFT_MASK GENMASK(11, 8) +#define UNICAM_OET_MASK GENMASK(20, 12) + +/* UNICAM_STA Register */ +#define UNICAM_SYN BIT(0) +#define UNICAM_CS BIT(1) +#define UNICAM_SBE BIT(2) +#define UNICAM_PBE BIT(3) +#define UNICAM_HOE BIT(4) +#define UNICAM_PLE BIT(5) +#define UNICAM_SSC BIT(6) +#define UNICAM_CRCE BIT(7) +#define UNICAM_OES BIT(8) +#define UNICAM_IFO BIT(9) +#define UNICAM_OFO BIT(10) +#define UNICAM_BFO BIT(11) +#define UNICAM_DL BIT(12) +#define UNICAM_PS BIT(13) +#define UNICAM_IS BIT(14) +#define UNICAM_PI0 BIT(15) +#define UNICAM_PI1 BIT(16) +#define UNICAM_FSI_S BIT(17) +#define UNICAM_FEI_S BIT(18) +#define UNICAM_LCI_S BIT(19) +#define UNICAM_BUF0_RDY BIT(20) +#define UNICAM_BUF0_NO BIT(21) +#define UNICAM_BUF1_RDY BIT(22) +#define UNICAM_BUF1_NO BIT(23) +#define UNICAM_DI BIT(24) + +#define UNICAM_STA_MASK_ALL \ + (UNICAM_DL + \ + UNICAM_SBE + \ + UNICAM_PBE + \ + UNICAM_HOE + \ + UNICAM_PLE + \ + UNICAM_SSC + \ + UNICAM_CRCE + \ + UNICAM_IFO + \ + UNICAM_OFO + \ + UNICAM_PS + \ + UNICAM_PI0 + \ + UNICAM_PI1) + +/* UNICAM_ANA Register */ +#define UNICAM_APD BIT(0) +#define UNICAM_BPD BIT(1) +#define UNICAM_AR BIT(2) +#define UNICAM_DDL BIT(3) +#define UNICAM_CTATADJ_MASK GENMASK(7, 4) +#define UNICAM_PTATADJ_MASK GENMASK(11, 8) + +/* UNICAM_PRI Register */ +#define UNICAM_PE BIT(0) +#define UNICAM_PT_MASK GENMASK(2, 1) +#define UNICAM_NP_MASK GENMASK(7, 4) +#define UNICAM_PP_MASK GENMASK(11, 8) +#define UNICAM_BS_MASK GENMASK(15, 12) +#define UNICAM_BL_MASK GENMASK(17, 16) + +/* UNICAM_CLK Register */ +#define UNICAM_CLE BIT(0) +#define UNICAM_CLPD BIT(1) +#define UNICAM_CLLPE BIT(2) +#define UNICAM_CLHSE BIT(3) +#define UNICAM_CLTRE BIT(4) +#define UNICAM_CLAC_MASK GENMASK(8, 5) +#define UNICAM_CLSTE BIT(29) + +/* UNICAM_CLT Register */ +#define UNICAM_CLT1_MASK GENMASK(7, 0) +#define UNICAM_CLT2_MASK GENMASK(15, 8) + +/* UNICAM_DATn Registers */ +#define UNICAM_DLE BIT(0) +#define UNICAM_DLPD BIT(1) +#define UNICAM_DLLPE BIT(2) +#define UNICAM_DLHSE BIT(3) +#define UNICAM_DLTRE BIT(4) +#define UNICAM_DLSM BIT(5) +#define UNICAM_DLFO BIT(28) +#define UNICAM_DLSTE BIT(29) + +#define UNICAM_DAT_MASK_ALL (UNICAM_DLSTE + UNICAM_DLFO) + +/* UNICAM_DLT Register */ +#define UNICAM_DLT1_MASK GENMASK(7, 0) +#define UNICAM_DLT2_MASK GENMASK(15, 8) +#define UNICAM_DLT3_MASK GENMASK(23, 16) + +/* UNICAM_ICTL Register */ +#define UNICAM_FSIE BIT(0) +#define UNICAM_FEIE BIT(1) +#define UNICAM_IBOB BIT(2) +#define UNICAM_FCM BIT(3) +#define UNICAM_TFC BIT(4) +#define UNICAM_LIP_MASK GENMASK(6, 5) +#define UNICAM_LCIE_MASK GENMASK(28, 16) + +/* UNICAM_IDI0/1 Register */ +#define UNICAM_ID0_MASK GENMASK(7, 0) +#define UNICAM_ID1_MASK GENMASK(15, 8) +#define UNICAM_ID2_MASK GENMASK(23, 16) +#define UNICAM_ID3_MASK GENMASK(31, 24) + +/* UNICAM_ISTA Register */ +#define UNICAM_FSI BIT(0) +#define UNICAM_FEI BIT(1) +#define UNICAM_LCI BIT(2) + +#define UNICAM_ISTA_MASK_ALL (UNICAM_FSI + UNICAM_FEI + UNICAM_LCI) + +/* UNICAM_IPIPE Register */ +#define UNICAM_PUM_MASK GENMASK(2, 0) + /* Unpacking modes */ + #define UNICAM_PUM_NONE 0 + #define UNICAM_PUM_UNPACK6 1 + #define UNICAM_PUM_UNPACK7 2 + #define UNICAM_PUM_UNPACK8 3 + #define UNICAM_PUM_UNPACK10 4 + #define UNICAM_PUM_UNPACK12 5 + #define UNICAM_PUM_UNPACK14 6 + #define UNICAM_PUM_UNPACK16 7 +#define UNICAM_DDM_MASK GENMASK(6, 3) +#define UNICAM_PPM_MASK GENMASK(9, 7) + /* Packing modes */ + #define UNICAM_PPM_NONE 0 + #define UNICAM_PPM_PACK8 1 + #define UNICAM_PPM_PACK10 2 + #define UNICAM_PPM_PACK12 3 + #define UNICAM_PPM_PACK14 4 + #define UNICAM_PPM_PACK16 5 +#define UNICAM_DEM_MASK GENMASK(11, 10) +#define UNICAM_DEBL_MASK GENMASK(14, 12) +#define UNICAM_ICM_MASK GENMASK(16, 15) +#define UNICAM_IDM_MASK GENMASK(17, 17) + +/* UNICAM_ICC Register */ +#define UNICAM_ICFL_MASK GENMASK(4, 0) +#define UNICAM_ICFH_MASK GENMASK(9, 5) +#define UNICAM_ICST_MASK GENMASK(12, 10) +#define UNICAM_ICLT_MASK GENMASK(15, 13) +#define UNICAM_ICLL_MASK GENMASK(31, 16) + +/* UNICAM_DCS Register */ +#define UNICAM_DIE BIT(0) +#define UNICAM_DIM BIT(1) +#define UNICAM_DBOB BIT(3) +#define UNICAM_FDE BIT(4) +#define UNICAM_LDP BIT(5) +#define UNICAM_EDL_MASK GENMASK(15, 8) + +/* UNICAM_DBCTL Register */ +#define UNICAM_DBEN BIT(0) +#define UNICAM_BUF0_IE BIT(1) +#define UNICAM_BUF1_IE BIT(2) + +/* UNICAM_CMP[0,1] register */ +#define UNICAM_PCE BIT(31) +#define UNICAM_GI BIT(9) +#define UNICAM_CPH BIT(8) +#define UNICAM_PCVC_MASK GENMASK(7, 6) +#define UNICAM_PCDT_MASK GENMASK(5, 0) + +/* UNICAM_MISC register */ +#define UNICAM_FL0 BIT(6) +#define UNICAM_FL1 BIT(9) + +#endif diff --git a/drivers/media/platform/broadcom/bcm2835-unicam.c b/drivers/media/platform/broadcom/bcm2835-unicam.c index 9f81e1582a3005..b273bf62485065 100644 --- a/drivers/media/platform/broadcom/bcm2835-unicam.c +++ b/drivers/media/platform/broadcom/bcm2835-unicam.c @@ -2711,7 +2711,7 @@ static void unicam_remove(struct platform_device *pdev) } static const struct of_device_id unicam_of_match[] = { - { .compatible = "brcm,bcm2835-unicam", }, + { .compatible = "brcm,bcm2835-unicam-upstream", }, { /* sentinel */ }, }; MODULE_DEVICE_TABLE(of, unicam_of_match); diff --git a/drivers/media/platform/raspberrypi/Kconfig b/drivers/media/platform/raspberrypi/Kconfig index e928f979019e65..a1850c559dbbcc 100644 --- a/drivers/media/platform/raspberrypi/Kconfig +++ b/drivers/media/platform/raspberrypi/Kconfig @@ -3,3 +3,4 @@ comment "Raspberry Pi media platform drivers" source "drivers/media/platform/raspberrypi/pisp_be/Kconfig" +source "drivers/media/platform/raspberrypi/rp1_cfe/Kconfig" diff --git a/drivers/media/platform/raspberrypi/Makefile b/drivers/media/platform/raspberrypi/Makefile index c0d1a2dab4860c..4ce6c998927c17 100644 --- a/drivers/media/platform/raspberrypi/Makefile +++ b/drivers/media/platform/raspberrypi/Makefile @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0 obj-y += pisp_be/ +obj-y += rp1_cfe/ diff --git a/drivers/media/platform/raspberrypi/pisp_be/pisp_be.c b/drivers/media/platform/raspberrypi/pisp_be/pisp_be.c index 65ff2382cffe9e..7e035bb3890f0d 100644 --- a/drivers/media/platform/raspberrypi/pisp_be/pisp_be.c +++ b/drivers/media/platform/raspberrypi/pisp_be/pisp_be.c @@ -21,9 +21,20 @@ #include "pisp_be_formats.h" +/* Offset to use when registering the /dev/videoX node */ +#define PISPBE_VIDEO_NODE_OFFSET 20 + /* Maximum number of config buffers possible */ #define PISP_BE_NUM_CONFIG_BUFFERS VB2_MAX_FRAME +/* + * We want to support 2 independent instances allowing 2 simultaneous users + * of the ISP-BE (of course they share hardware, platform resources and mutex). + * Each such instance comprises a group of device nodes representing input + * and output queues, and a media controller device node to describe them. + */ +#define PISPBE_NUM_NODE_GROUPS 2 + #define PISPBE_NAME "pispbe" /* Some ISP-BE registers */ @@ -156,7 +167,7 @@ struct pispbe_node { struct media_pad pad; struct media_intf_devnode *intf_devnode; struct media_link *intf_link; - struct pispbe_dev *pispbe; + struct pispbe_node_group *node_group; /* Video device lock */ struct mutex node_lock; /* vb2_queue lock */ @@ -173,9 +184,27 @@ struct pispbe_node { #define NODE_NAME(node) \ (node_desc[(node)->id].ent_name + sizeof(PISPBE_NAME)) +/* + * Node group structure, which comprises all the input and output nodes that a + * single PiSP client will need, along with its own v4l2 and media devices. + */ +struct pispbe_node_group { + unsigned int id; + struct v4l2_device v4l2_dev; + struct v4l2_subdev sd; + struct pispbe_dev *pispbe; + struct media_device mdev; + struct pispbe_node node[PISPBE_NUM_NODES]; + u32 streaming_map; /* bitmap of which nodes are streaming */ + struct media_pad pad[PISPBE_NUM_NODES]; /* output pads first */ + struct pisp_be_tiles_config *config; + dma_addr_t config_dma_addr; + unsigned int sequence; +}; + /* Records details of the jobs currently running or queued on the h/w. */ struct pispbe_job { - bool valid; + struct pispbe_node_group *node_group; /* * An array of buffer pointers - remember it's source buffers first, * then captures, then metadata last. @@ -198,22 +227,13 @@ struct pispbe_job_descriptor { /* * Structure representing the entire PiSP Back End device, comprising several - * nodes which share platform resources and a mutex for the actual HW. + * nodes groups which share platform resources and a mutex for the actual HW. */ struct pispbe_dev { struct device *dev; - struct pispbe_dev *pispbe; - struct pisp_be_tiles_config *config; void __iomem *be_reg_base; struct clk *clk; - struct v4l2_device v4l2_dev; - struct v4l2_subdev sd; - struct media_device mdev; - struct media_pad pad[PISPBE_NUM_NODES]; /* output pads first */ - struct pispbe_node node[PISPBE_NUM_NODES]; - dma_addr_t config_dma_addr; - unsigned int sequence; - u32 streaming_map; + struct pispbe_node_group node_group[PISPBE_NUM_NODE_GROUPS]; struct pispbe_job queued_job, running_job; spinlock_t hw_lock; /* protects "hw_busy" flag and streaming_map */ bool hw_busy; /* non-zero if a job is queued or is being started */ @@ -348,9 +368,9 @@ static dma_addr_t pispbe_get_addr(struct pispbe_buffer *buf) return 0; } -static void pispbe_xlate_addrs(struct pispbe_dev *pispbe, - struct pispbe_job_descriptor *job, - struct pispbe_buffer *buf[PISPBE_NUM_NODES]) +static void pispbe_xlate_addrs(struct pispbe_job_descriptor *job, + struct pispbe_buffer *buf[PISPBE_NUM_NODES], + struct pispbe_node_group *node_group) { struct pispbe_hw_enables *hw_en = &job->hw_enables; struct pisp_be_tiles_config *config = job->config; @@ -366,13 +386,13 @@ static void pispbe_xlate_addrs(struct pispbe_dev *pispbe, * to 3 planes. */ ret = pispbe_get_planes_addr(addrs, buf[MAIN_INPUT_NODE], - &pispbe->node[MAIN_INPUT_NODE]); + &node_group->node[MAIN_INPUT_NODE]); if (ret <= 0) { /* * This shouldn't happen; pispbe_schedule_internal should insist * on an input. */ - dev_warn(pispbe->dev, "ISP-BE missing input\n"); + dev_warn(node_group->pispbe->dev, "ISP-BE missing input\n"); hw_en->bayer_enables = 0; hw_en->rgb_enables = 0; return; @@ -427,7 +447,7 @@ static void pispbe_xlate_addrs(struct pispbe_dev *pispbe, for (unsigned int i = 0; i < PISP_BACK_END_NUM_OUTPUTS; i++) { ret = pispbe_get_planes_addr(addrs + 7 + 3 * i, buf[OUTPUT0_NODE + i], - &pispbe->node[OUTPUT0_NODE + i]); + &node_group->node[OUTPUT0_NODE + i]); if (ret <= 0) hw_en->rgb_enables &= ~(PISP_BE_RGB_ENABLE_OUTPUT0 << i); } @@ -447,10 +467,11 @@ static void pispbe_xlate_addrs(struct pispbe_dev *pispbe, * * Returns 0 if a job has been successfully prepared, < 0 otherwise. */ -static int pispbe_prepare_job(struct pispbe_dev *pispbe, +static int pispbe_prepare_job(struct pispbe_node_group *node_group, struct pispbe_job_descriptor *job) { struct pispbe_buffer *buf[PISPBE_NUM_NODES] = {}; + struct pispbe_dev *pispbe = node_group->pispbe; unsigned int config_index; struct pispbe_node *node; unsigned long flags; @@ -460,11 +481,11 @@ static int pispbe_prepare_job(struct pispbe_dev *pispbe, memset(job, 0, sizeof(struct pispbe_job_descriptor)); if (((BIT(CONFIG_NODE) | BIT(MAIN_INPUT_NODE)) & - pispbe->streaming_map) != + node_group->streaming_map) != (BIT(CONFIG_NODE) | BIT(MAIN_INPUT_NODE))) return -ENODEV; - node = &pispbe->node[CONFIG_NODE]; + node = &node_group->node[CONFIG_NODE]; spin_lock_irqsave(&node->ready_lock, flags); buf[CONFIG_NODE] = list_first_entry_or_null(&node->ready_queue, struct pispbe_buffer, @@ -480,8 +501,8 @@ static int pispbe_prepare_job(struct pispbe_dev *pispbe, return -ENODEV; config_index = buf[CONFIG_NODE]->vb.vb2_buf.index; - job->config = &pispbe->config[config_index]; - job->tiles = pispbe->config_dma_addr + + job->config = &node_group->config[config_index]; + job->tiles = node_group->config_dma_addr + config_index * sizeof(struct pisp_be_tiles_config) + offsetof(struct pisp_be_tiles_config, tiles); @@ -498,7 +519,7 @@ static int pispbe_prepare_job(struct pispbe_dev *pispbe, continue; buf[i] = NULL; - if (!(pispbe->streaming_map & BIT(i))) + if (!(node_group->streaming_map & BIT(i))) continue; if ((!(rgb_en & PISP_BE_RGB_ENABLE_OUTPUT0) && @@ -522,7 +543,7 @@ static int pispbe_prepare_job(struct pispbe_dev *pispbe, ignore_buffers = true; } - node = &pispbe->node[i]; + node = &node_group->node[i]; /* Pull a buffer from each V4L2 queue to form the queued job */ spin_lock_irqsave(&node->ready_lock, flags); @@ -539,16 +560,16 @@ static int pispbe_prepare_job(struct pispbe_dev *pispbe, goto err_return_buffers; } - pispbe->queued_job.valid = true; + pispbe->queued_job.node_group = node_group; /* Convert buffers to DMA addresses for the hardware */ - pispbe_xlate_addrs(pispbe, job, buf); + pispbe_xlate_addrs(job, buf, node_group); return 0; err_return_buffers: for (unsigned int i = 0; i < PISPBE_NUM_NODES; i++) { - struct pispbe_node *n = &pispbe->node[i]; + struct pispbe_node *n = &node_group->node[i]; if (!buf[i]) continue; @@ -564,11 +585,12 @@ static int pispbe_prepare_job(struct pispbe_dev *pispbe, return -ENODEV; } -static void pispbe_schedule(struct pispbe_dev *pispbe, bool clear_hw_busy) +static void pispbe_schedule(struct pispbe_dev *pispbe, + struct pispbe_node_group *node_group, + bool clear_hw_busy) { struct pispbe_job_descriptor job; unsigned long flags; - int ret; spin_lock_irqsave(&pispbe->hw_lock, flags); @@ -578,40 +600,53 @@ static void pispbe_schedule(struct pispbe_dev *pispbe, bool clear_hw_busy) if (pispbe->hw_busy) goto unlock_and_return; - ret = pispbe_prepare_job(pispbe, &job); - if (ret) - goto unlock_and_return; + for (unsigned int i = 0; i < PISPBE_NUM_NODE_GROUPS; i++) { + int ret; - /* - * We can kick the job off without the hw_lock, as this can - * never run again until hw_busy is cleared, which will happen - * only when the following job has been queued and an interrupt - * is rised. - */ - pispbe->hw_busy = true; - spin_unlock_irqrestore(&pispbe->hw_lock, flags); + /* Schedule jobs only for a specific group. */ + if (node_group && &pispbe->node_group[i] != node_group) + continue; - if (job.config->num_tiles <= 0 || - job.config->num_tiles > PISP_BACK_END_NUM_TILES || - !((job.hw_enables.bayer_enables | job.hw_enables.rgb_enables) & - PISP_BE_BAYER_ENABLE_INPUT)) { /* - * Bad job. We can't let it proceed as it could lock up - * the hardware, or worse! - * - * For now, just force num_tiles to 0, which causes the - * H/W to do something bizarre but survivable. It - * increments (started,done) counters by more than 1, - * but we seem to survive... + * Prepare a job for this group, if the group is not ready + * continue and try with the next one. */ - dev_dbg(pispbe->dev, "Bad job: invalid number of tiles: %u\n", - job.config->num_tiles); - job.config->num_tiles = 0; - } + ret = pispbe_prepare_job(&pispbe->node_group[i], &job); + if (ret) + continue; + + /* + * We can kick the job off without the hw_lock, as this can + * never run again until hw_busy is cleared, which will happen + * only when the following job has been queued and an interrupt + * is rised. + */ + pispbe->hw_busy = true; + spin_unlock_irqrestore(&pispbe->hw_lock, flags); + + if (job.config->num_tiles <= 0 || + job.config->num_tiles > PISP_BACK_END_NUM_TILES || + !((job.hw_enables.bayer_enables | + job.hw_enables.rgb_enables) & + PISP_BE_BAYER_ENABLE_INPUT)) { + /* + * Bad job. We can't let it proceed as it could lock up + * the hardware, or worse! + * + * For now, just force num_tiles to 0, which causes the + * H/W to do something bizarre but survivable. It + * increments (started,done) counters by more than 1, + * but we seem to survive... + */ + dev_dbg(pispbe->dev, "Bad job: invalid number of tiles: %u\n", + job.config->num_tiles); + job.config->num_tiles = 0; + } - pispbe_queue_job(pispbe, &job); + pispbe_queue_job(pispbe, &job); - return; + return; + } unlock_and_return: /* No job has been queued, just release the lock and return. */ @@ -627,13 +662,13 @@ static void pispbe_isr_jobdone(struct pispbe_dev *pispbe, for (unsigned int i = 0; i < PISPBE_NUM_NODES; i++) { if (buf[i]) { buf[i]->vb.vb2_buf.timestamp = ts; - buf[i]->vb.sequence = pispbe->sequence; + buf[i]->vb.sequence = job->node_group->sequence; vb2_buffer_done(&buf[i]->vb.vb2_buf, VB2_BUF_STATE_DONE); } } - pispbe->sequence++; + job->node_group->sequence++; } static irqreturn_t pispbe_isr(int irq, void *dev) @@ -657,7 +692,7 @@ static irqreturn_t pispbe_isr(int irq, void *dev) * we previously saw "start" now finishes, and we then queued a new job * which we see both start and finish "simultaneously". */ - if (pispbe->running_job.valid && pispbe->done != done) { + if (pispbe->running_job.node_group && pispbe->done != done) { pispbe_isr_jobdone(pispbe, &pispbe->running_job); memset(&pispbe->running_job, 0, sizeof(pispbe->running_job)); pispbe->done++; @@ -667,7 +702,7 @@ static irqreturn_t pispbe_isr(int irq, void *dev) pispbe->started++; can_queue_another = 1; - if (pispbe->done != done && pispbe->queued_job.valid) { + if (pispbe->done != done && pispbe->queued_job.node_group) { pispbe_isr_jobdone(pispbe, &pispbe->queued_job); pispbe->done++; } else { @@ -686,17 +721,17 @@ static irqreturn_t pispbe_isr(int irq, void *dev) } /* check if there's more to do before going to sleep */ - pispbe_schedule(pispbe, can_queue_another); + pispbe_schedule(pispbe, NULL, can_queue_another); return IRQ_HANDLED; } -static int pisp_be_validate_config(struct pispbe_dev *pispbe, +static int pisp_be_validate_config(struct pispbe_node_group *node_group, struct pisp_be_tiles_config *config) { u32 bayer_enables = config->config.global.bayer_enables; u32 rgb_enables = config->config.global.rgb_enables; - struct device *dev = pispbe->dev; + struct device *dev = node_group->pispbe->dev; struct v4l2_format *fmt; unsigned int bpl, size; @@ -707,7 +742,7 @@ static int pisp_be_validate_config(struct pispbe_dev *pispbe, } /* Ensure output config strides and buffer sizes match the V4L2 formats. */ - fmt = &pispbe->node[TDN_OUTPUT_NODE].format; + fmt = &node_group->node[TDN_OUTPUT_NODE].format; if (bayer_enables & PISP_BE_BAYER_ENABLE_TDN_OUTPUT) { bpl = config->config.tdn_output_format.stride; size = bpl * config->config.tdn_output_format.height; @@ -725,7 +760,7 @@ static int pisp_be_validate_config(struct pispbe_dev *pispbe, } } - fmt = &pispbe->node[STITCH_OUTPUT_NODE].format; + fmt = &node_group->node[STITCH_OUTPUT_NODE].format; if (bayer_enables & PISP_BE_BAYER_ENABLE_STITCH_OUTPUT) { bpl = config->config.stitch_output_format.stride; size = bpl * config->config.stitch_output_format.height; @@ -751,7 +786,7 @@ static int pisp_be_validate_config(struct pispbe_dev *pispbe, PISP_IMAGE_FORMAT_WALLPAPER_ROLL) continue; /* TODO: Size checks for wallpaper formats */ - fmt = &pispbe->node[OUTPUT0_NODE + j].format; + fmt = &node_group->node[OUTPUT0_NODE + j].format; for (unsigned int i = 0; i < fmt->fmt.pix_mp.num_planes; i++) { bpl = !i ? config->config.output_format[j].image.stride : config->config.output_format[j].image.stride2; @@ -783,7 +818,7 @@ static int pispbe_node_queue_setup(struct vb2_queue *q, unsigned int *nbuffers, struct device *alloc_devs[]) { struct pispbe_node *node = vb2_get_drv_priv(q); - struct pispbe_dev *pispbe = node->pispbe; + struct pispbe_dev *pispbe = node->node_group->pispbe; unsigned int num_planes = NODE_IS_MPLANE(node) ? node->format.fmt.pix_mp.num_planes : 1; @@ -821,7 +856,7 @@ static int pispbe_node_queue_setup(struct vb2_queue *q, unsigned int *nbuffers, static int pispbe_node_buffer_prepare(struct vb2_buffer *vb) { struct pispbe_node *node = vb2_get_drv_priv(vb->vb2_queue); - struct pispbe_dev *pispbe = node->pispbe; + struct pispbe_dev *pispbe = node->node_group->pispbe; unsigned int num_planes = NODE_IS_MPLANE(node) ? node->format.fmt.pix_mp.num_planes : 1; @@ -841,12 +876,12 @@ static int pispbe_node_buffer_prepare(struct vb2_buffer *vb) } if (node->id == CONFIG_NODE) { - void *dst = &node->pispbe->config[vb->index]; + void *dst = &node->node_group->config[vb->index]; void *src = vb2_plane_vaddr(vb, 0); memcpy(dst, src, sizeof(struct pisp_be_tiles_config)); - return pisp_be_validate_config(pispbe, dst); + return pisp_be_validate_config(node->node_group, dst); } return 0; @@ -859,7 +894,8 @@ static void pispbe_node_buffer_queue(struct vb2_buffer *buf) struct pispbe_buffer *buffer = container_of(vbuf, struct pispbe_buffer, vb); struct pispbe_node *node = vb2_get_drv_priv(buf->vb2_queue); - struct pispbe_dev *pispbe = node->pispbe; + struct pispbe_node_group *node_group = node->node_group; + struct pispbe_dev *pispbe = node->node_group->pispbe; unsigned long flags; dev_dbg(pispbe->dev, "%s: for node %s\n", __func__, NODE_NAME(node)); @@ -869,15 +905,16 @@ static void pispbe_node_buffer_queue(struct vb2_buffer *buf) /* * Every time we add a buffer, check if there's now some work for the hw - * to do. + * to do, but only for this client. */ - pispbe_schedule(pispbe, false); + pispbe_schedule(node_group->pispbe, node_group, false); } static int pispbe_node_start_streaming(struct vb2_queue *q, unsigned int count) { struct pispbe_node *node = vb2_get_drv_priv(q); - struct pispbe_dev *pispbe = node->pispbe; + struct pispbe_node_group *node_group = node->node_group; + struct pispbe_dev *pispbe = node_group->pispbe; struct pispbe_buffer *buf, *tmp; unsigned long flags; int ret; @@ -887,17 +924,17 @@ static int pispbe_node_start_streaming(struct vb2_queue *q, unsigned int count) goto err_return_buffers; spin_lock_irqsave(&pispbe->hw_lock, flags); - node->pispbe->streaming_map |= BIT(node->id); - node->pispbe->sequence = 0; + node->node_group->streaming_map |= BIT(node->id); + node->node_group->sequence = 0; spin_unlock_irqrestore(&pispbe->hw_lock, flags); dev_dbg(pispbe->dev, "%s: for node %s (count %u)\n", __func__, NODE_NAME(node), count); - dev_dbg(pispbe->dev, "Nodes streaming now 0x%x\n", - node->pispbe->streaming_map); + dev_dbg(pispbe->dev, "Nodes streaming for this group now 0x%x\n", + node->node_group->streaming_map); /* Maybe we're ready to run. */ - pispbe_schedule(pispbe, false); + pispbe_schedule(node_group->pispbe, node_group, false); return 0; @@ -915,7 +952,8 @@ static int pispbe_node_start_streaming(struct vb2_queue *q, unsigned int count) static void pispbe_node_stop_streaming(struct vb2_queue *q) { struct pispbe_node *node = vb2_get_drv_priv(q); - struct pispbe_dev *pispbe = node->pispbe; + struct pispbe_node_group *node_group = node->node_group; + struct pispbe_dev *pispbe = node_group->pispbe; struct pispbe_buffer *buf; unsigned long flags; @@ -948,14 +986,14 @@ static void pispbe_node_stop_streaming(struct vb2_queue *q) vb2_wait_for_all_buffers(&node->queue); spin_lock_irqsave(&pispbe->hw_lock, flags); - pispbe->streaming_map &= ~BIT(node->id); + node_group->streaming_map &= ~BIT(node->id); spin_unlock_irqrestore(&pispbe->hw_lock, flags); pm_runtime_mark_last_busy(pispbe->dev); pm_runtime_put_autosuspend(pispbe->dev); - dev_dbg(pispbe->dev, "Nodes streaming now 0x%x\n", - pispbe->streaming_map); + dev_dbg(pispbe->dev, "Nodes streaming for this group now 0x%x\n", + node_group->streaming_map); } static const struct vb2_ops pispbe_node_queue_ops = { @@ -979,7 +1017,7 @@ static int pispbe_node_querycap(struct file *file, void *priv, struct v4l2_capability *cap) { struct pispbe_node *node = video_drvdata(file); - struct pispbe_dev *pispbe = node->pispbe; + struct pispbe_dev *pispbe = node->node_group->pispbe; strscpy(cap->driver, PISPBE_NAME, sizeof(cap->driver)); strscpy(cap->card, PISPBE_NAME, sizeof(cap->card)); @@ -995,7 +1033,7 @@ static int pispbe_node_g_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { struct pispbe_node *node = video_drvdata(file); - struct pispbe_dev *pispbe = node->pispbe; + struct pispbe_dev *pispbe = node->node_group->pispbe; if (!NODE_IS_CAPTURE(node) || NODE_IS_META(node)) { dev_dbg(pispbe->dev, @@ -1015,7 +1053,7 @@ static int pispbe_node_g_fmt_vid_out(struct file *file, void *priv, struct v4l2_format *f) { struct pispbe_node *node = video_drvdata(file); - struct pispbe_dev *pispbe = node->pispbe; + struct pispbe_dev *pispbe = node->node_group->pispbe; if (NODE_IS_CAPTURE(node) || NODE_IS_META(node)) { dev_dbg(pispbe->dev, @@ -1035,7 +1073,7 @@ static int pispbe_node_g_fmt_meta_out(struct file *file, void *priv, struct v4l2_format *f) { struct pispbe_node *node = video_drvdata(file); - struct pispbe_dev *pispbe = node->pispbe; + struct pispbe_dev *pispbe = node->node_group->pispbe; if (!NODE_IS_META(node) || NODE_IS_CAPTURE(node)) { dev_dbg(pispbe->dev, @@ -1092,7 +1130,7 @@ static void pispbe_set_plane_params(struct v4l2_format *f, static void pispbe_try_format(struct v4l2_format *f, struct pispbe_node *node) { - struct pispbe_dev *pispbe = node->pispbe; + struct pispbe_dev *pispbe = node->node_group->pispbe; u32 pixfmt = f->fmt.pix_mp.pixelformat; const struct pisp_be_format *fmt; bool is_rgb; @@ -1156,7 +1194,7 @@ static int pispbe_node_try_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { struct pispbe_node *node = video_drvdata(file); - struct pispbe_dev *pispbe = node->pispbe; + struct pispbe_dev *pispbe = node->node_group->pispbe; if (!NODE_IS_CAPTURE(node) || NODE_IS_META(node)) { dev_dbg(pispbe->dev, @@ -1174,7 +1212,7 @@ static int pispbe_node_try_fmt_vid_out(struct file *file, void *priv, struct v4l2_format *f) { struct pispbe_node *node = video_drvdata(file); - struct pispbe_dev *pispbe = node->pispbe; + struct pispbe_dev *pispbe = node->node_group->pispbe; if (!NODE_IS_OUTPUT(node) || NODE_IS_META(node)) { dev_dbg(pispbe->dev, @@ -1192,7 +1230,7 @@ static int pispbe_node_try_fmt_meta_out(struct file *file, void *priv, struct v4l2_format *f) { struct pispbe_node *node = video_drvdata(file); - struct pispbe_dev *pispbe = node->pispbe; + struct pispbe_dev *pispbe = node->node_group->pispbe; if (!NODE_IS_META(node) || NODE_IS_CAPTURE(node)) { dev_dbg(pispbe->dev, @@ -1211,7 +1249,7 @@ static int pispbe_node_s_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { struct pispbe_node *node = video_drvdata(file); - struct pispbe_dev *pispbe = node->pispbe; + struct pispbe_dev *pispbe = node->node_group->pispbe; int ret; ret = pispbe_node_try_fmt_vid_cap(file, priv, f); @@ -1234,7 +1272,7 @@ static int pispbe_node_s_fmt_vid_out(struct file *file, void *priv, struct v4l2_format *f) { struct pispbe_node *node = video_drvdata(file); - struct pispbe_dev *pispbe = node->pispbe; + struct pispbe_dev *pispbe = node->node_group->pispbe; int ret; ret = pispbe_node_try_fmt_vid_out(file, priv, f); @@ -1257,7 +1295,7 @@ static int pispbe_node_s_fmt_meta_out(struct file *file, void *priv, struct v4l2_format *f) { struct pispbe_node *node = video_drvdata(file); - struct pispbe_dev *pispbe = node->pispbe; + struct pispbe_dev *pispbe = node->node_group->pispbe; int ret; ret = pispbe_node_try_fmt_meta_out(file, priv, f); @@ -1306,7 +1344,7 @@ static int pispbe_enum_framesizes(struct file *file, void *priv, struct v4l2_frmsizeenum *fsize) { struct pispbe_node *node = video_drvdata(file); - struct pispbe_dev *pispbe = node->pispbe; + struct pispbe_dev *pispbe = node->node_group->pispbe; if (NODE_IS_META(node) || fsize->index) return -EINVAL; @@ -1391,17 +1429,19 @@ static void pispbe_node_def_fmt(struct pispbe_node *node) * Initialise a struct pispbe_node and register it as /dev/video<N> * to represent one of the PiSP Back End's input or output streams. */ -static int pispbe_init_node(struct pispbe_dev *pispbe, unsigned int id) +static int pispbe_init_node(struct pispbe_node_group *node_group, + unsigned int id) { bool output = NODE_DESC_IS_OUTPUT(&node_desc[id]); - struct pispbe_node *node = &pispbe->node[id]; + struct pispbe_node *node = &node_group->node[id]; struct media_entity *entity = &node->vfd.entity; + struct pispbe_dev *pispbe = node_group->pispbe; struct video_device *vdev = &node->vfd; struct vb2_queue *q = &node->queue; int ret; node->id = id; - node->pispbe = pispbe; + node->node_group = node_group; node->buf_type = node_desc[id].buf_type; mutex_init(&node->node_lock); @@ -1419,7 +1459,7 @@ static int pispbe_init_node(struct pispbe_dev *pispbe, unsigned int id) q->ops = &pispbe_node_queue_ops; q->buf_struct_size = sizeof(struct pispbe_buffer); q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; - q->dev = pispbe->dev; + q->dev = node->node_group->pispbe->dev; /* get V4L2 to handle node->queue locking */ q->lock = &node->queue_lock; @@ -1431,7 +1471,7 @@ static int pispbe_init_node(struct pispbe_dev *pispbe, unsigned int id) *vdev = pispbe_videodev; /* default initialization */ strscpy(vdev->name, node_desc[id].ent_name, sizeof(vdev->name)); - vdev->v4l2_dev = &pispbe->v4l2_dev; + vdev->v4l2_dev = &node_group->v4l2_dev; vdev->vfl_dir = output ? VFL_DIR_TX : VFL_DIR_RX; /* get V4L2 to serialise our ioctls */ vdev->lock = &node->node_lock; @@ -1447,7 +1487,8 @@ static int pispbe_init_node(struct pispbe_dev *pispbe, unsigned int id) goto err_unregister_queue; } - ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); + ret = video_register_device(vdev, VFL_TYPE_VIDEO, + PISPBE_VIDEO_NODE_OFFSET); if (ret) { dev_err(pispbe->dev, "Failed to register video %s device node\n", @@ -1457,11 +1498,11 @@ static int pispbe_init_node(struct pispbe_dev *pispbe, unsigned int id) video_set_drvdata(vdev, node); if (output) - ret = media_create_pad_link(entity, 0, &pispbe->sd.entity, + ret = media_create_pad_link(entity, 0, &node_group->sd.entity, id, MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED); else - ret = media_create_pad_link(&pispbe->sd.entity, id, entity, + ret = media_create_pad_link(&node_group->sd.entity, id, entity, 0, MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED); if (ret) @@ -1490,9 +1531,10 @@ static const struct v4l2_subdev_ops pispbe_sd_ops = { .pad = &pispbe_pad_ops, }; -static int pispbe_init_subdev(struct pispbe_dev *pispbe) +static int pispbe_init_subdev(struct pispbe_node_group *node_group) { - struct v4l2_subdev *sd = &pispbe->sd; + struct pispbe_dev *pispbe = node_group->pispbe; + struct v4l2_subdev *sd = &node_group->sd; int ret; v4l2_subdev_init(sd, &pispbe_sd_ops); @@ -1502,16 +1544,16 @@ static int pispbe_init_subdev(struct pispbe_dev *pispbe) strscpy(sd->name, PISPBE_NAME, sizeof(sd->name)); for (unsigned int i = 0; i < PISPBE_NUM_NODES; i++) - pispbe->pad[i].flags = + node_group->pad[i].flags = NODE_DESC_IS_OUTPUT(&node_desc[i]) ? MEDIA_PAD_FL_SINK : MEDIA_PAD_FL_SOURCE; ret = media_entity_pads_init(&sd->entity, PISPBE_NUM_NODES, - pispbe->pad); + node_group->pad); if (ret) goto error; - ret = v4l2_device_register_subdev(&pispbe->v4l2_dev, sd); + ret = v4l2_device_register_subdev(&node_group->v4l2_dev, sd); if (ret) goto error; @@ -1522,36 +1564,43 @@ static int pispbe_init_subdev(struct pispbe_dev *pispbe) return ret; } -static int pispbe_init_devices(struct pispbe_dev *pispbe) +static int pispbe_init_group(struct pispbe_dev *pispbe, unsigned int id) { + struct pispbe_node_group *node_group = &pispbe->node_group[id]; struct v4l2_device *v4l2_dev; struct media_device *mdev; unsigned int num_regist; int ret; + node_group->id = id; + node_group->pispbe = pispbe; + node_group->streaming_map = 0; + + dev_dbg(pispbe->dev, "Register nodes for group %u\n", id); + /* Register v4l2_device and media_device */ - mdev = &pispbe->mdev; - mdev->hw_revision = pispbe->hw_version; - mdev->dev = pispbe->dev; + mdev = &node_group->mdev; + mdev->hw_revision = node_group->pispbe->hw_version; + mdev->dev = node_group->pispbe->dev; strscpy(mdev->model, PISPBE_NAME, sizeof(mdev->model)); media_device_init(mdev); - v4l2_dev = &pispbe->v4l2_dev; - v4l2_dev->mdev = &pispbe->mdev; + v4l2_dev = &node_group->v4l2_dev; + v4l2_dev->mdev = &node_group->mdev; strscpy(v4l2_dev->name, PISPBE_NAME, sizeof(v4l2_dev->name)); - ret = v4l2_device_register(pispbe->dev, v4l2_dev); + ret = v4l2_device_register(pispbe->dev, &node_group->v4l2_dev); if (ret) goto err_media_dev_cleanup; /* Register the PISPBE subdevice. */ - ret = pispbe_init_subdev(pispbe); + ret = pispbe_init_subdev(node_group); if (ret) goto err_unregister_v4l2; /* Create device video nodes */ for (num_regist = 0; num_regist < PISPBE_NUM_NODES; num_regist++) { - ret = pispbe_init_node(pispbe, num_regist); + ret = pispbe_init_node(node_group, num_regist); if (ret) goto err_unregister_nodes; } @@ -1560,12 +1609,12 @@ static int pispbe_init_devices(struct pispbe_dev *pispbe) if (ret) goto err_unregister_nodes; - pispbe->config = + node_group->config = dma_alloc_coherent(pispbe->dev, sizeof(struct pisp_be_tiles_config) * PISP_BE_NUM_CONFIG_BUFFERS, - &pispbe->config_dma_addr, GFP_KERNEL); - if (!pispbe->config) { + &node_group->config_dma_addr, GFP_KERNEL); + if (!node_group->config) { dev_err(pispbe->dev, "Unable to allocate cached config buffers.\n"); ret = -ENOMEM; goto err_unregister_mdev; @@ -1577,11 +1626,11 @@ static int pispbe_init_devices(struct pispbe_dev *pispbe) media_device_unregister(mdev); err_unregister_nodes: while (num_regist-- > 0) { - video_unregister_device(&pispbe->node[num_regist].vfd); - vb2_queue_release(&pispbe->node[num_regist].queue); + video_unregister_device(&node_group->node[num_regist].vfd); + vb2_queue_release(&node_group->node[num_regist].queue); } - v4l2_device_unregister_subdev(&pispbe->sd); - media_entity_cleanup(&pispbe->sd.entity); + v4l2_device_unregister_subdev(&node_group->sd); + media_entity_cleanup(&node_group->sd.entity); err_unregister_v4l2: v4l2_device_unregister(v4l2_dev); err_media_dev_cleanup: @@ -1589,31 +1638,33 @@ static int pispbe_init_devices(struct pispbe_dev *pispbe) return ret; } -static void pispbe_destroy_devices(struct pispbe_dev *pispbe) +static void pispbe_destroy_node_group(struct pispbe_node_group *node_group) { - if (pispbe->config) { - dma_free_coherent(pispbe->dev, + struct pispbe_dev *pispbe = node_group->pispbe; + + if (node_group->config) { + dma_free_coherent(node_group->pispbe->dev, sizeof(struct pisp_be_tiles_config) * PISP_BE_NUM_CONFIG_BUFFERS, - pispbe->config, - pispbe->config_dma_addr); + node_group->config, + node_group->config_dma_addr); } dev_dbg(pispbe->dev, "Unregister from media controller\n"); - v4l2_device_unregister_subdev(&pispbe->sd); - media_entity_cleanup(&pispbe->sd.entity); - media_device_unregister(&pispbe->mdev); + v4l2_device_unregister_subdev(&node_group->sd); + media_entity_cleanup(&node_group->sd.entity); + media_device_unregister(&node_group->mdev); for (int i = PISPBE_NUM_NODES - 1; i >= 0; i--) { - video_unregister_device(&pispbe->node[i].vfd); - vb2_queue_release(&pispbe->node[i].queue); - mutex_destroy(&pispbe->node[i].node_lock); - mutex_destroy(&pispbe->node[i].queue_lock); + video_unregister_device(&node_group->node[i].vfd); + vb2_queue_release(&node_group->node[i].queue); + mutex_destroy(&node_group->node[i].node_lock); + mutex_destroy(&node_group->node[i].queue_lock); } - media_device_cleanup(&pispbe->mdev); - v4l2_device_unregister(&pispbe->v4l2_dev); + media_device_cleanup(&node_group->mdev); + v4l2_device_unregister(&node_group->v4l2_dev); } static int pispbe_runtime_suspend(struct device *dev) @@ -1681,9 +1732,13 @@ static int pispbe_hw_init(struct pispbe_dev *pispbe) return 0; } -/* Probe the ISP-BE hardware block, as a single platform device. */ +/* + * Probe the ISP-BE hardware block, as a single platform device. + * This will instantiate multiple "node groups" each with many device nodes. + */ static int pispbe_probe(struct platform_device *pdev) { + unsigned int num_groups = 0; struct pispbe_dev *pispbe; int ret; @@ -1736,17 +1791,26 @@ static int pispbe_probe(struct platform_device *pdev) if (ret) goto pm_runtime_suspend_err; - ret = pispbe_init_devices(pispbe); - if (ret) - goto disable_devs_err; + /* + * Initialise and register devices for each node_group, including media + * device + */ + for (num_groups = 0; + num_groups < PISPBE_NUM_NODE_GROUPS; + num_groups++) { + ret = pispbe_init_group(pispbe, num_groups); + if (ret) + goto disable_nodes_err; + } pm_runtime_mark_last_busy(pispbe->dev); pm_runtime_put_autosuspend(pispbe->dev); return 0; -disable_devs_err: - pispbe_destroy_devices(pispbe); +disable_nodes_err: + while (num_groups-- > 0) + pispbe_destroy_node_group(&pispbe->node_group[num_groups]); pm_runtime_suspend_err: pispbe_runtime_suspend(pispbe->dev); pm_runtime_disable_err: @@ -1760,7 +1824,8 @@ static void pispbe_remove(struct platform_device *pdev) { struct pispbe_dev *pispbe = platform_get_drvdata(pdev); - pispbe_destroy_devices(pispbe); + for (int i = PISPBE_NUM_NODE_GROUPS - 1; i >= 0; i--) + pispbe_destroy_node_group(&pispbe->node_group[i]); pispbe_runtime_suspend(pispbe->dev); pm_runtime_dont_use_autosuspend(pispbe->dev); diff --git a/drivers/media/platform/raspberrypi/pisp_be/pisp_be_formats.h b/drivers/media/platform/raspberrypi/pisp_be/pisp_be_formats.h index b5cb7b8c753162..09edc2774668b5 100644 --- a/drivers/media/platform/raspberrypi/pisp_be/pisp_be_formats.h +++ b/drivers/media/platform/raspberrypi/pisp_be/pisp_be_formats.h @@ -129,6 +129,16 @@ static const struct pisp_be_format supported_formats[] = { .colorspace_mask = V4L2_COLORSPACE_MASK_ALL_SRGB, .colorspace_default = V4L2_COLORSPACE_SMPTE170M, }, + { + .fourcc = V4L2_PIX_FMT_YUV422P, + /* 128 alignment to ensure U/V planes are 64 byte aligned. */ + .align = 128, + .bit_depth = 8, + .plane_factor = { P3(1), P3(0.5), P3(0.5) }, + .num_planes = 1, + .colorspace_mask = V4L2_COLORSPACE_MASK_ALL_SRGB, + .colorspace_default = V4L2_COLORSPACE_SMPTE170M, + }, /* Multiplane YUV formats */ { .fourcc = V4L2_PIX_FMT_YUV420M, diff --git a/drivers/media/platform/raspberrypi/rp1_cfe/Kconfig b/drivers/media/platform/raspberrypi/rp1_cfe/Kconfig new file mode 100644 index 00000000000000..8cb1255319fb7d --- /dev/null +++ b/drivers/media/platform/raspberrypi/rp1_cfe/Kconfig @@ -0,0 +1,14 @@ +# RP1 V4L2 camera support + +config VIDEO_RP1_CFE + tristate "RP1 Camera Frond End (CFE) video capture driver" + depends on VIDEO_DEV + select VIDEO_V4L2_SUBDEV_API + select MEDIA_CONTROLLER + select VIDEOBUF2_DMA_CONTIG + select V4L2_FWNODE + help + Say Y here to enable support for the RP1 Camera Front End. + + To compile this driver as a module, choose M here. The module will be + called rp1-cfe. diff --git a/drivers/media/platform/raspberrypi/rp1_cfe/Makefile b/drivers/media/platform/raspberrypi/rp1_cfe/Makefile new file mode 100644 index 00000000000000..9709d6f603e995 --- /dev/null +++ b/drivers/media/platform/raspberrypi/rp1_cfe/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for RP1 Camera Front End driver +# +rp1-cfe-objs := cfe.o csi2.o pisp_fe.o dphy.o +obj-$(CONFIG_VIDEO_RP1_CFE) += rp1-cfe.o diff --git a/drivers/media/platform/raspberrypi/rp1_cfe/cfe.c b/drivers/media/platform/raspberrypi/rp1_cfe/cfe.c new file mode 100644 index 00000000000000..e7b63495b39101 --- /dev/null +++ b/drivers/media/platform/raspberrypi/rp1_cfe/cfe.c @@ -0,0 +1,2466 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * RP1 Camera Front End Driver + * + * Copyright (C) 2021-2022 - Raspberry Pi Ltd. + * + */ + +#include <linux/atomic.h> +#include <linux/clk.h> +#include <linux/debugfs.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/of_graph.h> +#include <linux/phy/phy.h> +#include <linux/pinctrl/consumer.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/seq_file.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/videodev2.h> + +#include <media/v4l2-async.h> +#include <media/v4l2-common.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-dev.h> +#include <media/v4l2-device.h> +#include <media/v4l2-dv-timings.h> +#include <media/v4l2-event.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-ioctl.h> +#include <media/videobuf2-dma-contig.h> + +#include "cfe.h" +#include "cfe_fmts.h" +#include "csi2.h" +#include "pisp_fe.h" +#include "pisp_fe_config.h" +#include "pisp_statistics.h" + +#define CFE_MODULE_NAME "rp1-cfe" +#define CFE_VERSION "1.0" + +bool cfe_debug_verbose; +module_param_named(verbose_debug, cfe_debug_verbose, bool, 0644); +MODULE_PARM_DESC(verbose_debug, "verbose debugging messages"); + +#define cfe_dbg_verbose(fmt, arg...) \ + do { \ + if (cfe_debug_verbose) \ + dev_dbg(&cfe->pdev->dev, fmt, ##arg); \ + } while (0) +#define cfe_dbg(fmt, arg...) dev_dbg(&cfe->pdev->dev, fmt, ##arg) +#define cfe_info(fmt, arg...) dev_info(&cfe->pdev->dev, fmt, ##arg) +#define cfe_err(fmt, arg...) dev_err(&cfe->pdev->dev, fmt, ##arg) + +/* MIPICFG registers */ +#define MIPICFG_CFG 0x004 +#define MIPICFG_INTR 0x028 +#define MIPICFG_INTE 0x02c +#define MIPICFG_INTF 0x030 +#define MIPICFG_INTS 0x034 + +#define MIPICFG_CFG_SEL_CSI BIT(0) + +#define MIPICFG_INT_CSI_DMA BIT(0) +#define MIPICFG_INT_CSI_HOST BIT(2) +#define MIPICFG_INT_PISP_FE BIT(4) + +#define BPL_ALIGNMENT 16 +#define MAX_BYTESPERLINE 0xffffff00 +#define MAX_BUFFER_SIZE 0xffffff00 +/* + * Max width is therefore determined by the max stride divided by the number of + * bits per pixel. + * + * However, to avoid overflow issues let's use a 16k maximum. This lets us + * calculate 16k * 16k * 4 with 32bits. If we need higher maximums, a careful + * review and adjustment of the code is needed so that it will deal with + * overflows correctly. + */ +#define MAX_WIDTH 16384 +#define MAX_HEIGHT MAX_WIDTH +/* Define a nominal minimum image size */ +#define MIN_WIDTH 16 +#define MIN_HEIGHT 16 +/* Default size of the embedded buffer */ +#define DEFAULT_EMBEDDED_SIZE 16384 + +const struct v4l2_mbus_framefmt cfe_default_format = { + .width = 640, + .height = 480, + .code = MEDIA_BUS_FMT_SRGGB10_1X10, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_RAW, + .ycbcr_enc = V4L2_YCBCR_ENC_601, + .quantization = V4L2_QUANTIZATION_FULL_RANGE, + .xfer_func = V4L2_XFER_FUNC_NONE, +}; + +const struct v4l2_mbus_framefmt cfe_default_meta_format = { + .width = DEFAULT_EMBEDDED_SIZE, + .height = 1, + .code = MEDIA_BUS_FMT_SENSOR_DATA, + .field = V4L2_FIELD_NONE, +}; + +enum node_ids { + /* CSI2 HW output nodes first. */ + CSI2_CH0, + CSI2_CH1, + CSI2_CH2, + CSI2_CH3, + /* FE only nodes from here on. */ + FE_OUT0, + FE_OUT1, + FE_STATS, + FE_CONFIG, + NUM_NODES +}; + +struct node_description { + unsigned int id; + const char *name; + unsigned int caps; + unsigned int pad_flags; + unsigned int link_pad; +}; + +/* Must match the ordering of enum ids */ +static const struct node_description node_desc[NUM_NODES] = { + [CSI2_CH0] = { + .name = "csi2_ch0", + .caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_META_CAPTURE, + .pad_flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT, + .link_pad = CSI2_NUM_CHANNELS + 0 + }, + /* + * TODO: This node should be named "csi2_ch1" and the caps should be set + * to both video and meta capture. However, to keep compatibility with + * the current libcamera, keep the name as "embedded" and support + * only meta capture. + */ + [CSI2_CH1] = { + .name = "embedded", + .caps = V4L2_CAP_META_CAPTURE, + .pad_flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT, + .link_pad = CSI2_NUM_CHANNELS + 1 + }, + [CSI2_CH2] = { + .name = "csi2_ch2", + .caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_META_CAPTURE, + .pad_flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT, + .link_pad = CSI2_NUM_CHANNELS + 2 + }, + [CSI2_CH3] = { + .name = "csi2_ch3", + .caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_META_CAPTURE, + .pad_flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT, + .link_pad = CSI2_NUM_CHANNELS + 3 + }, + [FE_OUT0] = { + .name = "fe_image0", + .caps = V4L2_CAP_VIDEO_CAPTURE, + .pad_flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT, + .link_pad = FE_OUTPUT0_PAD + }, + [FE_OUT1] = { + .name = "fe_image1", + .caps = V4L2_CAP_VIDEO_CAPTURE, + .pad_flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT, + .link_pad = FE_OUTPUT1_PAD + }, + [FE_STATS] = { + .name = "fe_stats", + .caps = V4L2_CAP_META_CAPTURE, + .pad_flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT, + .link_pad = FE_STATS_PAD + }, + [FE_CONFIG] = { + .name = "fe_config", + .caps = V4L2_CAP_META_OUTPUT, + .pad_flags = MEDIA_PAD_FL_SOURCE | MEDIA_PAD_FL_MUST_CONNECT, + .link_pad = FE_CONFIG_PAD + }, +}; + +#define is_fe_node(node) (((node)->id) >= FE_OUT0) +#define is_csi2_node(node) (!is_fe_node(node)) + +#define node_supports_image_output(node) \ + (!!(node_desc[(node)->id].caps & V4L2_CAP_VIDEO_CAPTURE)) +#define node_supports_meta_output(node) \ + (!!(node_desc[(node)->id].caps & V4L2_CAP_META_CAPTURE)) +#define node_supports_image_input(node) \ + (!!(node_desc[(node)->id].caps & V4L2_CAP_VIDEO_OUTPUT)) +#define node_supports_meta_input(node) \ + (!!(node_desc[(node)->id].caps & V4L2_CAP_META_OUTPUT)) +#define node_supports_image(node) \ + (node_supports_image_output(node) || node_supports_image_input(node)) +#define node_supports_meta(node) \ + (node_supports_meta_output(node) || node_supports_meta_input(node)) + +#define is_image_output_node(node) \ + ((node)->buffer_queue.type == V4L2_BUF_TYPE_VIDEO_CAPTURE) +#define is_image_input_node(node) \ + ((node)->buffer_queue.type == V4L2_BUF_TYPE_VIDEO_OUTPUT) +#define is_image_node(node) \ + (is_image_output_node(node) || is_image_input_node(node)) +#define is_meta_output_node(node) \ + ((node)->buffer_queue.type == V4L2_BUF_TYPE_META_CAPTURE) +#define is_meta_input_node(node) \ + ((node)->buffer_queue.type == V4L2_BUF_TYPE_META_OUTPUT) +#define is_meta_node(node) \ + (is_meta_output_node(node) || is_meta_input_node(node)) + +/* To track state across all nodes. */ +#define NUM_STATES 5 +#define NODE_REGISTERED BIT(0) +#define NODE_ENABLED BIT(1) +#define NODE_STREAMING BIT(2) +#define FS_INT BIT(3) +#define FE_INT BIT(4) + +struct cfe_buffer { + struct vb2_v4l2_buffer vb; + struct list_head list; +}; + +struct cfe_config_buffer { + struct cfe_buffer buf; + struct pisp_fe_config config; +}; + +static inline struct cfe_buffer *to_cfe_buffer(struct vb2_buffer *vb) +{ + return container_of(vb, struct cfe_buffer, vb.vb2_buf); +} + +static inline +struct cfe_config_buffer *to_cfe_config_buffer(struct cfe_buffer *buf) +{ + return container_of(buf, struct cfe_config_buffer, buf); +} + +struct cfe_node { + unsigned int id; + /* Pointer pointing to current v4l2_buffer */ + struct cfe_buffer *cur_frm; + /* Pointer pointing to next v4l2_buffer */ + struct cfe_buffer *next_frm; + /* Used to store current pixel format */ + struct v4l2_format vid_fmt; + /* Used to store current meta format */ + struct v4l2_format meta_fmt; + /* Buffer queue used in video-buf */ + struct vb2_queue buffer_queue; + /* Queue of filled frames */ + struct list_head dma_queue; + /* lock used to access this structure */ + struct mutex lock; + /* Identifies video device for this channel */ + struct video_device video_dev; + /* Pointer to the parent handle */ + struct cfe_device *cfe; + struct media_pad pad; + unsigned int fs_count; + u64 ts; +}; + +struct cfe_device { + struct dentry *debugfs; + struct kref kref; + + /* V4l2 specific parameters */ + struct v4l2_async_connection *asd; + + /* peripheral base address */ + void __iomem *mipi_cfg_base; + + struct clk *clk; + + /* V4l2 device */ + struct v4l2_device v4l2_dev; + struct media_device mdev; + struct media_pipeline pipe; + + /* IRQ lock for node state and DMA queues */ + spinlock_t state_lock; + bool job_ready; + bool job_queued; + + /* parent device */ + struct platform_device *pdev; + /* subdevice async Notifier */ + struct v4l2_async_notifier notifier; + + /* ptr to sub device */ + struct v4l2_subdev *sensor; + + struct cfe_node node[NUM_NODES]; + DECLARE_BITMAP(node_flags, NUM_STATES * NUM_NODES); + + struct csi2_device csi2; + struct pisp_fe_device fe; + + int fe_csi2_channel; +}; + +static inline bool is_fe_enabled(struct cfe_device *cfe) +{ + return cfe->fe_csi2_channel != -1; +} + +static inline struct cfe_device *to_cfe_device(struct v4l2_device *v4l2_dev) +{ + return container_of(v4l2_dev, struct cfe_device, v4l2_dev); +} + +static inline u32 cfg_reg_read(struct cfe_device *cfe, u32 offset) +{ + return readl(cfe->mipi_cfg_base + offset); +} + +static inline void cfg_reg_write(struct cfe_device *cfe, u32 offset, u32 val) +{ + writel(val, cfe->mipi_cfg_base + offset); +} + +static bool check_state(struct cfe_device *cfe, unsigned long state, + unsigned int node_id) +{ + unsigned long bit; + + for_each_set_bit(bit, &state, sizeof(state)) { + if (!test_bit(bit + (node_id * NUM_STATES), cfe->node_flags)) + return false; + } + return true; +} + +static void set_state(struct cfe_device *cfe, unsigned long state, + unsigned int node_id) +{ + unsigned long bit; + + for_each_set_bit(bit, &state, sizeof(state)) + set_bit(bit + (node_id * NUM_STATES), cfe->node_flags); +} + +static void clear_state(struct cfe_device *cfe, unsigned long state, + unsigned int node_id) +{ + unsigned long bit; + + for_each_set_bit(bit, &state, sizeof(state)) + clear_bit(bit + (node_id * NUM_STATES), cfe->node_flags); +} + +static bool test_any_node(struct cfe_device *cfe, unsigned long cond) +{ + unsigned int i; + + for (i = 0; i < NUM_NODES; i++) { + if (check_state(cfe, cond, i)) + return true; + } + + return false; +} + +static bool test_all_nodes(struct cfe_device *cfe, unsigned long precond, + unsigned long cond) +{ + unsigned int i; + + for (i = 0; i < NUM_NODES; i++) { + if (check_state(cfe, precond, i)) { + if (!check_state(cfe, cond, i)) + return false; + } + } + + return true; +} + +static int mipi_cfg_regs_show(struct seq_file *s, void *data) +{ + struct cfe_device *cfe = s->private; + int ret; + + ret = pm_runtime_resume_and_get(&cfe->pdev->dev); + if (ret) + return ret; + +#define DUMP(reg) seq_printf(s, #reg " \t0x%08x\n", cfg_reg_read(cfe, reg)) + DUMP(MIPICFG_CFG); + DUMP(MIPICFG_INTR); + DUMP(MIPICFG_INTE); + DUMP(MIPICFG_INTF); + DUMP(MIPICFG_INTS); +#undef DUMP + + pm_runtime_put(&cfe->pdev->dev); + + return 0; +} + +static int format_show(struct seq_file *s, void *data) +{ + struct cfe_device *cfe = s->private; + unsigned int i; + + for (i = 0; i < NUM_NODES; i++) { + struct cfe_node *node = &cfe->node[i]; + unsigned long sb, state = 0; + + for (sb = 0; sb < NUM_STATES; sb++) { + if (check_state(cfe, BIT(sb), i)) + state |= BIT(sb); + } + + seq_printf(s, "\nNode %u (%s) state: 0x%lx\n", i, + node_desc[i].name, state); + + if (node_supports_image(node)) + seq_printf(s, "format: " V4L2_FOURCC_CONV " 0x%x\n" + "resolution: %ux%u\nbpl: %u\nsize: %u\n", + V4L2_FOURCC_CONV_ARGS(node->vid_fmt.fmt.pix.pixelformat), + node->vid_fmt.fmt.pix.pixelformat, + node->vid_fmt.fmt.pix.width, + node->vid_fmt.fmt.pix.height, + node->vid_fmt.fmt.pix.bytesperline, + node->vid_fmt.fmt.pix.sizeimage); + + if (node_supports_meta(node)) + seq_printf(s, "format: " V4L2_FOURCC_CONV " 0x%x\nsize: %u\n", + V4L2_FOURCC_CONV_ARGS(node->meta_fmt.fmt.meta.dataformat), + node->meta_fmt.fmt.meta.dataformat, + node->meta_fmt.fmt.meta.buffersize); + } + + return 0; +} + +DEFINE_SHOW_ATTRIBUTE(mipi_cfg_regs); +DEFINE_SHOW_ATTRIBUTE(format); + +/* Format setup functions */ +const struct cfe_fmt *find_format_by_code(u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(formats); i++) { + if (formats[i].code == code) + return &formats[i]; + } + + return NULL; +} + +const struct cfe_fmt *find_format_by_pix(u32 pixelformat) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(formats); i++) { + if (formats[i].fourcc == pixelformat) + return &formats[i]; + } + + return NULL; +} + +/* + * Given the mbus code, find the 16 bit remapped code. Returns 0 if no remap + * possible. + */ +u32 cfe_find_16bit_code(u32 code) +{ + const struct cfe_fmt *cfe_fmt; + + cfe_fmt = find_format_by_code(code); + + if (!cfe_fmt || !cfe_fmt->remap[CFE_REMAP_16BIT]) + return 0; + + cfe_fmt = find_format_by_pix(cfe_fmt->remap[CFE_REMAP_16BIT]); + if (!cfe_fmt) + return 0; + + return cfe_fmt->code; +} + +/* + * Given the mbus code, find the 8 bit compressed code. Returns 0 if no remap + * possible. + */ +u32 cfe_find_compressed_code(u32 code) +{ + const struct cfe_fmt *cfe_fmt; + + cfe_fmt = find_format_by_code(code); + + if (!cfe_fmt || !cfe_fmt->remap[CFE_REMAP_COMPRESSED]) + return 0; + + cfe_fmt = find_format_by_pix(cfe_fmt->remap[CFE_REMAP_COMPRESSED]); + if (!cfe_fmt) + return 0; + + return cfe_fmt->code; +} + +static int cfe_calc_format_size_bpl(struct cfe_device *cfe, + const struct cfe_fmt *fmt, + struct v4l2_format *f) +{ + unsigned int min_bytesperline; + + v4l_bound_align_image(&f->fmt.pix.width, MIN_WIDTH, MAX_WIDTH, 2, + &f->fmt.pix.height, MIN_HEIGHT, MAX_HEIGHT, 0, 0); + + min_bytesperline = + ALIGN((f->fmt.pix.width * fmt->depth) >> 3, BPL_ALIGNMENT); + + if (f->fmt.pix.bytesperline > min_bytesperline && + f->fmt.pix.bytesperline <= MAX_BYTESPERLINE) + f->fmt.pix.bytesperline = + ALIGN(f->fmt.pix.bytesperline, BPL_ALIGNMENT); + else + f->fmt.pix.bytesperline = min_bytesperline; + + f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline; + + cfe_dbg("%s: " V4L2_FOURCC_CONV " size: %ux%u bpl:%u img_size:%u\n", + __func__, V4L2_FOURCC_CONV_ARGS(f->fmt.pix.pixelformat), + f->fmt.pix.width, f->fmt.pix.height, + f->fmt.pix.bytesperline, f->fmt.pix.sizeimage); + + return 0; +} + +static void cfe_schedule_next_csi2_job(struct cfe_device *cfe) +{ + struct cfe_buffer *buf; + unsigned int i; + dma_addr_t addr; + + for (i = 0; i < CSI2_NUM_CHANNELS; i++) { + struct cfe_node *node = &cfe->node[i]; + unsigned int stride, size; + + if (!check_state(cfe, NODE_STREAMING, i)) + continue; + + buf = list_first_entry(&node->dma_queue, struct cfe_buffer, + list); + node->next_frm = buf; + list_del(&buf->list); + + cfe_dbg_verbose("%s: [%s] buffer:%p\n", __func__, + node_desc[node->id].name, &buf->vb.vb2_buf); + + if (is_meta_node(node)) { + size = node->meta_fmt.fmt.meta.buffersize; + stride = 0; + } else { + size = node->vid_fmt.fmt.pix.sizeimage; + stride = node->vid_fmt.fmt.pix.bytesperline; + } + + addr = vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0); + csi2_set_buffer(&cfe->csi2, node->id, addr, stride, size); + } +} + +static void cfe_schedule_next_pisp_job(struct cfe_device *cfe) +{ + struct vb2_buffer *vb2_bufs[FE_NUM_PADS] = { 0 }; + struct cfe_config_buffer *config_buf; + struct cfe_buffer *buf; + unsigned int i; + + for (i = CSI2_NUM_CHANNELS; i < NUM_NODES; i++) { + struct cfe_node *node = &cfe->node[i]; + + if (!check_state(cfe, NODE_STREAMING, i)) + continue; + + buf = list_first_entry(&node->dma_queue, struct cfe_buffer, + list); + + cfe_dbg_verbose("%s: [%s] buffer:%p\n", __func__, + node_desc[node->id].name, &buf->vb.vb2_buf); + + node->next_frm = buf; + vb2_bufs[node_desc[i].link_pad] = &buf->vb.vb2_buf; + list_del(&buf->list); + } + + config_buf = to_cfe_config_buffer(cfe->node[FE_CONFIG].next_frm); + pisp_fe_submit_job(&cfe->fe, vb2_bufs, &config_buf->config); +} + +static bool cfe_check_job_ready(struct cfe_device *cfe) +{ + unsigned int i; + + for (i = 0; i < NUM_NODES; i++) { + struct cfe_node *node = &cfe->node[i]; + + if (!check_state(cfe, NODE_ENABLED, i)) + continue; + + if (list_empty(&node->dma_queue)) { + cfe_dbg_verbose("%s: [%s] has no buffer, unable to schedule job\n", + __func__, node_desc[i].name); + return false; + } + } + + return true; +} + +static void cfe_prepare_next_job(struct cfe_device *cfe) +{ + cfe->job_queued = true; + cfe_schedule_next_csi2_job(cfe); + if (is_fe_enabled(cfe)) + cfe_schedule_next_pisp_job(cfe); + + /* Flag if another job is ready after this. */ + cfe->job_ready = cfe_check_job_ready(cfe); + + cfe_dbg_verbose("%s: end with scheduled job\n", __func__); +} + +static void cfe_process_buffer_complete(struct cfe_node *node, + enum vb2_buffer_state state) +{ + struct cfe_device *cfe = node->cfe; + + cfe_dbg_verbose("%s: [%s] buffer:%p\n", __func__, + node_desc[node->id].name, &node->cur_frm->vb.vb2_buf); + + node->cur_frm->vb.sequence = node->fs_count - 1; + vb2_buffer_done(&node->cur_frm->vb.vb2_buf, state); +} + +static void cfe_queue_event_sof(struct cfe_node *node) +{ + struct v4l2_event event = { + .type = V4L2_EVENT_FRAME_SYNC, + .u.frame_sync.frame_sequence = node->fs_count - 1, + }; + + v4l2_event_queue(&node->video_dev, &event); +} + +static void cfe_sof_isr_handler(struct cfe_node *node) +{ + struct cfe_device *cfe = node->cfe; + bool matching_fs = true; + unsigned int i; + + cfe_dbg_verbose("%s: [%s] seq %u\n", __func__, node_desc[node->id].name, + node->fs_count); + + /* + * If the sensor is producing unexpected frame event ordering over a + * sustained period of time, guard against the possibility of coming + * here and orphaning the cur_frm if it's not been dequeued already. + * Unfortunately, there is not enough hardware state to tell if this + * may have occurred. + */ + if (WARN(node->cur_frm, "%s: [%s] Orphanded frame at seq %u\n", + __func__, node_desc[node->id].name, node->fs_count)) + cfe_process_buffer_complete(node, VB2_BUF_STATE_ERROR); + + node->cur_frm = node->next_frm; + node->next_frm = NULL; + node->fs_count++; + + node->ts = ktime_get_ns(); + for (i = 0; i < NUM_NODES; i++) { + if (!check_state(cfe, NODE_STREAMING, i) || i == node->id) + continue; + /* + * This checks if any other node has seen a FS. If yes, use the + * same timestamp, eventually across all node buffers. + */ + if (cfe->node[i].fs_count >= node->fs_count) + node->ts = cfe->node[i].ts; + /* + * This checks if all other node have seen a matching FS. If + * yes, we can flag another job to be queued. + */ + if (matching_fs && cfe->node[i].fs_count != node->fs_count) + matching_fs = false; + } + + if (matching_fs) + cfe->job_queued = false; + + if (node->cur_frm) + node->cur_frm->vb.vb2_buf.timestamp = node->ts; + + set_state(cfe, FS_INT, node->id); + clear_state(cfe, FE_INT, node->id); + + if (is_image_output_node(node)) + cfe_queue_event_sof(node); +} + +static void cfe_eof_isr_handler(struct cfe_node *node) +{ + struct cfe_device *cfe = node->cfe; + + cfe_dbg_verbose("%s: [%s] seq %u\n", __func__, node_desc[node->id].name, + node->fs_count - 1); + + if (node->cur_frm) + cfe_process_buffer_complete(node, VB2_BUF_STATE_DONE); + + node->cur_frm = NULL; + set_state(cfe, FE_INT, node->id); + clear_state(cfe, FS_INT, node->id); +} + +static irqreturn_t cfe_isr(int irq, void *dev) +{ + struct cfe_device *cfe = dev; + unsigned int i; + bool sof[NUM_NODES] = {0}, eof[NUM_NODES] = {0}; + u32 sts; + + sts = cfg_reg_read(cfe, MIPICFG_INTS); + + if (sts & MIPICFG_INT_CSI_DMA) + csi2_isr(&cfe->csi2, sof, eof); + + if (sts & MIPICFG_INT_PISP_FE) + pisp_fe_isr(&cfe->fe, sof + CSI2_NUM_CHANNELS, + eof + CSI2_NUM_CHANNELS); + + spin_lock(&cfe->state_lock); + + for (i = 0; i < NUM_NODES; i++) { + struct cfe_node *node = &cfe->node[i]; + + /* + * The check_state(NODE_STREAMING) is to ensure we do not loop + * over the CSI2_CHx nodes when the FE is active since they + * generate interrupts even though the node is not streaming. + */ + if (!check_state(cfe, NODE_STREAMING, i) || + !(sof[i] || eof[i])) + continue; + + /* + * There are 3 cases where we could get FS + FE_ACK at + * the same time: + * 1) FE of the current frame, and FS of the next frame. + * 2) FS + FE of the same frame. + * 3) FE of the current frame, and FS + FE of the next + * frame. To handle this, see the sof handler below. + * + * (1) is handled implicitly by the ordering of the FE and FS + * handlers below. + */ + if (eof[i]) { + /* + * The condition below tests for (2). Run the FS handler + * first before the FE handler, both for the current + * frame. + */ + if (sof[i] && !check_state(cfe, FS_INT, i)) { + cfe_sof_isr_handler(node); + sof[i] = false; + } + + cfe_eof_isr_handler(node); + } + + if (sof[i]) { + /* + * The condition below tests for (3). In such cases, we + * come in here with FS flag set in the node state from + * the previous frame since it only gets cleared in + * eof_isr_handler(). Handle the FE for the previous + * frame first before the FS handler for the current + * frame. + */ + if (check_state(cfe, FS_INT, node->id) && + !check_state(cfe, FE_INT, node->id)) { + cfe_dbg("%s: [%s] Handling missing previous FE interrupt\n", + __func__, node_desc[node->id].name); + cfe_eof_isr_handler(node); + } + + cfe_sof_isr_handler(node); + } + + if (!cfe->job_queued && cfe->job_ready) + cfe_prepare_next_job(cfe); + } + + spin_unlock(&cfe->state_lock); + + return IRQ_HANDLED; +} + +/* + * Stream helpers + */ + +static void cfe_start_channel(struct cfe_node *node) +{ + struct cfe_device *cfe = node->cfe; + struct v4l2_subdev_state *state; + struct v4l2_mbus_framefmt *source_fmt; + const struct cfe_fmt *fmt; + unsigned long flags; + bool start_fe = is_fe_enabled(cfe) && + test_all_nodes(cfe, NODE_ENABLED, NODE_STREAMING); + + cfe_dbg("%s: [%s]\n", __func__, node_desc[node->id].name); + + state = v4l2_subdev_lock_and_get_active_state(&cfe->csi2.sd); + + if (start_fe) { + unsigned int width, height; + + WARN_ON(!is_fe_enabled(cfe)); + cfe_dbg("%s: %s using csi2 channel %d\n", + __func__, node_desc[FE_OUT0].name, + cfe->fe_csi2_channel); + + source_fmt = v4l2_subdev_state_get_format(state, + cfe->fe_csi2_channel); + fmt = find_format_by_code(source_fmt->code); + + width = source_fmt->width; + height = source_fmt->height; + + /* Must have a valid CSI2 datatype. */ + WARN_ON(!fmt->csi_dt); + + /* + * Start the associated CSI2 Channel as well. + * + * Must write to the ADDR register to latch the ctrl values + * even if we are connected to the front end. Once running, + * this is handled by the CSI2 AUTO_ARM mode. + */ + csi2_start_channel(&cfe->csi2, cfe->fe_csi2_channel, + CSI2_MODE_FE_STREAMING, + true, false, width, height); + csi2_set_buffer(&cfe->csi2, cfe->fe_csi2_channel, 0, 0, -1); + pisp_fe_start(&cfe->fe); + } + + if (is_csi2_node(node)) { + unsigned int width = 0, height = 0; + + u32 mode = CSI2_MODE_NORMAL; + + source_fmt = v4l2_subdev_state_get_format(state, + node_desc[node->id].link_pad - CSI2_NUM_CHANNELS); + fmt = find_format_by_code(source_fmt->code); + + /* Must have a valid CSI2 datatype. */ + WARN_ON(!fmt->csi_dt); + + if (is_image_output_node(node)) { + width = source_fmt->width; + height = source_fmt->height; + + if (node->vid_fmt.fmt.pix.pixelformat == + fmt->remap[CFE_REMAP_16BIT]) + mode = CSI2_MODE_REMAP; + else if (node->vid_fmt.fmt.pix.pixelformat == + fmt->remap[CFE_REMAP_COMPRESSED]) { + mode = CSI2_MODE_COMPRESSED; + csi2_set_compression(&cfe->csi2, node->id, + CSI2_COMPRESSION_DELTA, 0, + 0); + } + } + /* Unconditionally start this CSI2 channel. */ + csi2_start_channel(&cfe->csi2, node->id, + mode, + /* Auto arm */ + false, + /* Pack bytes */ + is_meta_node(node) ? true : false, + width, height); + } + + v4l2_subdev_unlock_state(state); + + spin_lock_irqsave(&cfe->state_lock, flags); + if (cfe->job_ready && test_all_nodes(cfe, NODE_ENABLED, NODE_STREAMING)) + cfe_prepare_next_job(cfe); + spin_unlock_irqrestore(&cfe->state_lock, flags); +} + +static void cfe_stop_channel(struct cfe_node *node, bool fe_stop) +{ + struct cfe_device *cfe = node->cfe; + + cfe_dbg("%s: [%s] fe_stop %u\n", __func__, + node_desc[node->id].name, fe_stop); + + if (fe_stop) { + csi2_stop_channel(&cfe->csi2, cfe->fe_csi2_channel); + pisp_fe_stop(&cfe->fe); + } + + if (is_csi2_node(node)) + csi2_stop_channel(&cfe->csi2, node->id); +} + +static void cfe_return_buffers(struct cfe_node *node, + enum vb2_buffer_state state) +{ + struct cfe_device *cfe = node->cfe; + struct cfe_buffer *buf, *tmp; + unsigned long flags; + + cfe_dbg("%s: [%s]\n", __func__, node_desc[node->id].name); + + spin_lock_irqsave(&cfe->state_lock, flags); + list_for_each_entry_safe(buf, tmp, &node->dma_queue, list) { + list_del(&buf->list); + vb2_buffer_done(&buf->vb.vb2_buf, state); + } + + if (node->cur_frm) + vb2_buffer_done(&node->cur_frm->vb.vb2_buf, state); + if (node->next_frm && node->cur_frm != node->next_frm) + vb2_buffer_done(&node->next_frm->vb.vb2_buf, state); + + node->cur_frm = NULL; + node->next_frm = NULL; + spin_unlock_irqrestore(&cfe->state_lock, flags); +} + +/* + * vb2 ops + */ + +static int cfe_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers, + unsigned int *nplanes, unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct cfe_node *node = vb2_get_drv_priv(vq); + struct cfe_device *cfe = node->cfe; + unsigned int size = is_image_node(node) ? node->vid_fmt.fmt.pix.sizeimage : + node->meta_fmt.fmt.meta.buffersize; + + cfe_dbg("%s: [%s] type:%u\n", __func__, node_desc[node->id].name, + node->buffer_queue.type); + + if (vb2_get_num_buffers(vq) + *nbuffers < 3) + *nbuffers = 3 - vb2_get_num_buffers(vq); + + if (*nplanes) { + if (sizes[0] < size) { + cfe_err("sizes[0] %i < size %u\n", sizes[0], size); + return -EINVAL; + } + size = sizes[0]; + } + + *nplanes = 1; + sizes[0] = size; + + return 0; +} + +static int cfe_buffer_prepare(struct vb2_buffer *vb) +{ + struct cfe_node *node = vb2_get_drv_priv(vb->vb2_queue); + struct cfe_device *cfe = node->cfe; + struct cfe_buffer *buf = to_cfe_buffer(vb); + unsigned long size; + + cfe_dbg_verbose("%s: [%s] buffer:%p\n", __func__, + node_desc[node->id].name, vb); + + size = is_image_node(node) ? node->vid_fmt.fmt.pix.sizeimage : + node->meta_fmt.fmt.meta.buffersize; + if (vb2_plane_size(vb, 0) < size) { + cfe_err("data will not fit into plane (%lu < %lu)\n", + vb2_plane_size(vb, 0), size); + return -EINVAL; + } + + vb2_set_plane_payload(&buf->vb.vb2_buf, 0, size); + + if (node->id == FE_CONFIG) { + struct cfe_config_buffer *b = to_cfe_config_buffer(buf); + void *addr = vb2_plane_vaddr(vb, 0); + + memcpy(&b->config, addr, sizeof(struct pisp_fe_config)); + return pisp_fe_validate_config(&cfe->fe, &b->config, + &cfe->node[FE_OUT0].vid_fmt, + &cfe->node[FE_OUT1].vid_fmt); + } + + return 0; +} + +static void cfe_buffer_queue(struct vb2_buffer *vb) +{ + struct cfe_node *node = vb2_get_drv_priv(vb->vb2_queue); + struct cfe_device *cfe = node->cfe; + struct cfe_buffer *buf = to_cfe_buffer(vb); + unsigned long flags; + + cfe_dbg_verbose("%s: [%s] buffer:%p\n", __func__, + node_desc[node->id].name, vb); + + spin_lock_irqsave(&cfe->state_lock, flags); + + list_add_tail(&buf->list, &node->dma_queue); + + if (!cfe->job_ready) + cfe->job_ready = cfe_check_job_ready(cfe); + + if (!cfe->job_queued && cfe->job_ready && + test_all_nodes(cfe, NODE_ENABLED, NODE_STREAMING)) { + cfe_dbg("Preparing job immediately for channel %u\n", + node->id); + cfe_prepare_next_job(cfe); + } + + spin_unlock_irqrestore(&cfe->state_lock, flags); +} + +static u64 sensor_link_rate(struct cfe_device *cfe) +{ + struct v4l2_mbus_framefmt *source_fmt; + struct v4l2_subdev_state *state; + struct media_entity *entity; + struct v4l2_subdev *subdev; + const struct cfe_fmt *fmt; + struct media_pad *pad; + s64 link_freq; + + state = v4l2_subdev_lock_and_get_active_state(&cfe->csi2.sd); + source_fmt = v4l2_subdev_state_get_format(state, 0); + fmt = find_format_by_code(source_fmt->code); + v4l2_subdev_unlock_state(state); + + /* + * Walk up the media graph to find either the sensor entity, or another + * entity that advertises the V4L2_CID_LINK_FREQ or V4L2_CID_PIXEL_RATE + * control through the subdev. + */ + entity = &cfe->csi2.sd.entity; + while (1) { + pad = &entity->pads[0]; + if (!(pad->flags & MEDIA_PAD_FL_SINK)) + goto err; + + pad = media_pad_remote_pad_first(pad); + if (!pad || !is_media_entity_v4l2_subdev(pad->entity)) + goto err; + + entity = pad->entity; + subdev = media_entity_to_v4l2_subdev(entity); + if (entity->function == MEDIA_ENT_F_CAM_SENSOR || + v4l2_ctrl_find(subdev->ctrl_handler, V4L2_CID_LINK_FREQ) || + v4l2_ctrl_find(subdev->ctrl_handler, V4L2_CID_PIXEL_RATE)) + break; + } + + link_freq = v4l2_get_link_freq(subdev->ctrl_handler, fmt->depth, + cfe->csi2.dphy.active_lanes * 2); + if (link_freq < 0) + goto err; + + /* x2 for DDR. */ + link_freq *= 2; + cfe_info("Using a link rate of %lld Mbps\n", link_freq / (1000 * 1000)); + return link_freq; + +err: + cfe_err("Unable to determine sensor link rate, using 999 Mbps\n"); + return 999 * 1000000UL; +} + +static int cfe_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct v4l2_mbus_config mbus_config = { 0 }; + struct cfe_node *node = vb2_get_drv_priv(vq); + struct cfe_device *cfe = node->cfe; + int ret; + + cfe_dbg("%s: [%s] begin.\n", __func__, node_desc[node->id].name); + + if (!check_state(cfe, NODE_ENABLED, node->id)) { + cfe_err("%s node link is not enabled.\n", + node_desc[node->id].name); + ret = -EINVAL; + goto err_streaming; + } + + ret = pm_runtime_resume_and_get(&cfe->pdev->dev); + if (ret < 0) { + cfe_err("pm_runtime_resume_and_get failed\n"); + goto err_streaming; + } + + /* When using the Frontend, we must enable the FE_CONFIG node. */ + if (is_fe_enabled(cfe) && + !check_state(cfe, NODE_ENABLED, cfe->node[FE_CONFIG].id)) { + cfe_err("FE enabled, but FE_CONFIG node is not\n"); + ret = -EINVAL; + goto err_pm_put; + } + + ret = media_pipeline_start(&node->pad, &cfe->pipe); + if (ret < 0) { + cfe_err("Failed to start media pipeline: %d\n", ret); + goto err_pm_put; + } + + clear_state(cfe, FS_INT | FE_INT, node->id); + set_state(cfe, NODE_STREAMING, node->id); + node->fs_count = 0; + cfe_start_channel(node); + + if (!test_all_nodes(cfe, NODE_ENABLED, NODE_STREAMING)) { + cfe_dbg("Not all nodes are set to streaming yet!\n"); + return 0; + } + + cfg_reg_write(cfe, MIPICFG_CFG, MIPICFG_CFG_SEL_CSI); + cfg_reg_write(cfe, MIPICFG_INTE, MIPICFG_INT_CSI_DMA | MIPICFG_INT_PISP_FE); + + ret = v4l2_subdev_call(cfe->sensor, pad, get_mbus_config, 0, + &mbus_config); + if (ret < 0 && ret != -ENOIOCTLCMD) { + cfe_err("g_mbus_config failed\n"); + goto err_pm_put; + } + + cfe->csi2.dphy.active_lanes = mbus_config.bus.mipi_csi2.num_data_lanes; + if (!cfe->csi2.dphy.active_lanes) + cfe->csi2.dphy.active_lanes = cfe->csi2.dphy.max_lanes; + if (cfe->csi2.dphy.active_lanes > cfe->csi2.dphy.max_lanes) { + cfe_err("Device has requested %u data lanes, which is >%u configured in DT\n", + cfe->csi2.dphy.active_lanes, cfe->csi2.dphy.max_lanes); + ret = -EINVAL; + goto err_disable_cfe; + } + + cfe_dbg("Configuring CSI-2 block - %u data lanes\n", cfe->csi2.dphy.active_lanes); + cfe->csi2.dphy.dphy_rate = sensor_link_rate(cfe) / 1000000UL; + csi2_open_rx(&cfe->csi2); + + cfe_dbg("Starting sensor streaming\n"); + ret = v4l2_subdev_call(cfe->sensor, video, s_stream, 1); + if (ret < 0) { + cfe_err("stream on failed in subdev\n"); + goto err_disable_cfe; + } + + cfe_dbg("%s: [%s] end.\n", __func__, node_desc[node->id].name); + + return 0; + +err_disable_cfe: + csi2_close_rx(&cfe->csi2); + cfe_stop_channel(node, true); + media_pipeline_stop(&node->pad); +err_pm_put: + pm_runtime_put(&cfe->pdev->dev); +err_streaming: + cfe_return_buffers(node, VB2_BUF_STATE_QUEUED); + clear_state(cfe, NODE_STREAMING, node->id); + + return ret; +} + +static void cfe_stop_streaming(struct vb2_queue *vq) +{ + struct cfe_node *node = vb2_get_drv_priv(vq); + struct cfe_device *cfe = node->cfe; + unsigned long flags; + bool fe_stop; + + cfe_dbg("%s: [%s] begin.\n", __func__, node_desc[node->id].name); + + spin_lock_irqsave(&cfe->state_lock, flags); + fe_stop = is_fe_enabled(cfe) && + test_all_nodes(cfe, NODE_ENABLED, NODE_STREAMING); + + cfe->job_ready = false; + clear_state(cfe, NODE_STREAMING, node->id); + spin_unlock_irqrestore(&cfe->state_lock, flags); + + cfe_stop_channel(node, fe_stop); + + if (!test_any_node(cfe, NODE_STREAMING)) { + /* Stop streaming the sensor and disable the peripheral. */ + if (v4l2_subdev_call(cfe->sensor, video, s_stream, 0) < 0) + cfe_err("stream off failed in subdev\n"); + + csi2_close_rx(&cfe->csi2); + + cfg_reg_write(cfe, MIPICFG_INTE, 0); + } + + media_pipeline_stop(&node->pad); + + /* Clear all queued buffers for the node */ + cfe_return_buffers(node, VB2_BUF_STATE_ERROR); + + pm_runtime_put(&cfe->pdev->dev); + + cfe_dbg("%s: [%s] end.\n", __func__, node_desc[node->id].name); +} + +static const struct vb2_ops cfe_video_qops = { + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .queue_setup = cfe_queue_setup, + .buf_prepare = cfe_buffer_prepare, + .buf_queue = cfe_buffer_queue, + .start_streaming = cfe_start_streaming, + .stop_streaming = cfe_stop_streaming, +}; + +/* + * v4l2 ioctl ops + */ + +static int cfe_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct cfe_node *node = video_drvdata(file); + struct cfe_device *cfe = node->cfe; + + strscpy(cap->driver, CFE_MODULE_NAME, sizeof(cap->driver)); + strscpy(cap->card, CFE_MODULE_NAME, sizeof(cap->card)); + + snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s", + dev_name(&cfe->pdev->dev)); + + cap->capabilities |= V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_META_CAPTURE | + V4L2_CAP_META_OUTPUT; + + return 0; +} + +static int cfe_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct cfe_node *node = video_drvdata(file); + struct cfe_device *cfe = node->cfe; + unsigned int i, j; + + if (!node_supports_image_output(node)) + return -EINVAL; + + cfe_dbg("%s: [%s]\n", __func__, node_desc[node->id].name); + + for (i = 0, j = 0; i < ARRAY_SIZE(formats); i++) { + if (f->mbus_code && formats[i].code != f->mbus_code) + continue; + + if (formats[i].flags & CFE_FORMAT_FLAG_META_OUT || + formats[i].flags & CFE_FORMAT_FLAG_META_CAP) + continue; + + if (is_fe_node(node) && + !(formats[i].flags & CFE_FORMAT_FLAG_FE_OUT)) + continue; + + if (j == f->index) { + f->pixelformat = formats[i].fourcc; + f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + return 0; + } + j++; + } + + return -EINVAL; +} + +static int cfe_g_fmt(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cfe_node *node = video_drvdata(file); + struct cfe_device *cfe = node->cfe; + + cfe_dbg("%s: [%s]\n", __func__, node_desc[node->id].name); + + if (!node_supports_image(node)) + return -EINVAL; + + *f = node->vid_fmt; + + return 0; +} + +static int try_fmt_vid_cap(struct cfe_node *node, struct v4l2_format *f) +{ + struct cfe_device *cfe = node->cfe; + const struct cfe_fmt *fmt; + + cfe_dbg("%s: [%s] %ux%u, V4L2 pix " V4L2_FOURCC_CONV "\n", + __func__, node_desc[node->id].name, + f->fmt.pix.width, f->fmt.pix.height, + V4L2_FOURCC_CONV_ARGS(f->fmt.pix.pixelformat)); + + if (!node_supports_image_output(node)) + return -EINVAL; + + /* + * Default to a format that works for both CSI2 and FE. + */ + fmt = find_format_by_pix(f->fmt.pix.pixelformat); + if (!fmt) + fmt = find_format_by_code(MEDIA_BUS_FMT_SBGGR10_1X10); + + f->fmt.pix.pixelformat = fmt->fourcc; + + if (is_fe_node(node) && fmt->remap[CFE_REMAP_16BIT]) { + f->fmt.pix.pixelformat = fmt->remap[CFE_REMAP_16BIT]; + fmt = find_format_by_pix(f->fmt.pix.pixelformat); + } + + f->fmt.pix.field = V4L2_FIELD_NONE; + + cfe_calc_format_size_bpl(cfe, fmt, f); + + return 0; +} + +static int cfe_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cfe_node *node = video_drvdata(file); + struct cfe_device *cfe = node->cfe; + struct vb2_queue *q = &node->buffer_queue; + int ret; + + cfe_dbg("%s: [%s]\n", __func__, node_desc[node->id].name); + + if (vb2_is_busy(q)) + return -EBUSY; + + ret = try_fmt_vid_cap(node, f); + if (ret) + return ret; + + node->vid_fmt = *f; + + cfe_dbg("%s: Set %ux%u, V4L2 pix " V4L2_FOURCC_CONV "\n", __func__, + node->vid_fmt.fmt.pix.width, node->vid_fmt.fmt.pix.height, + V4L2_FOURCC_CONV_ARGS(node->vid_fmt.fmt.pix.pixelformat)); + + return 0; +} + +static int cfe_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cfe_node *node = video_drvdata(file); + struct cfe_device *cfe = node->cfe; + + cfe_dbg("%s: [%s]\n", __func__, node_desc[node->id].name); + + return try_fmt_vid_cap(node, f); +} + +static int cfe_enum_fmt_meta(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct cfe_node *node = video_drvdata(file); + struct cfe_device *cfe = node->cfe; + + cfe_dbg("%s: [%s]\n", __func__, node_desc[node->id].name); + + if (!node_supports_meta(node) || f->index != 0) + return -EINVAL; + + switch (node->id) { + case CSI2_CH0...CSI2_CH3: + f->pixelformat = V4L2_META_FMT_SENSOR_DATA; + return 0; + case FE_STATS: + f->pixelformat = V4L2_META_FMT_RPI_FE_STATS; + return 0; + case FE_CONFIG: + f->pixelformat = V4L2_META_FMT_RPI_FE_CFG; + return 0; + } + + return -EINVAL; +} + +static int try_fmt_meta(struct cfe_node *node, struct v4l2_format *f) +{ + if (!node_supports_meta(node)) + return -EINVAL; + + switch (node->id) { + case CSI2_CH0...CSI2_CH3: + f->fmt.meta.dataformat = V4L2_META_FMT_SENSOR_DATA; + if (!f->fmt.meta.buffersize) + f->fmt.meta.buffersize = DEFAULT_EMBEDDED_SIZE; + f->fmt.meta.buffersize = + min_t(u32, f->fmt.meta.buffersize, MAX_BUFFER_SIZE); + f->fmt.meta.buffersize = + ALIGN(f->fmt.meta.buffersize, BPL_ALIGNMENT); + return 0; + case FE_STATS: + f->fmt.meta.dataformat = V4L2_META_FMT_RPI_FE_STATS; + f->fmt.meta.buffersize = sizeof(struct pisp_statistics); + return 0; + case FE_CONFIG: + f->fmt.meta.dataformat = V4L2_META_FMT_RPI_FE_CFG; + f->fmt.meta.buffersize = sizeof(struct pisp_fe_config); + return 0; + } + + return -EINVAL; +} + +static int cfe_g_fmt_meta(struct file *file, void *priv, struct v4l2_format *f) +{ + struct cfe_node *node = video_drvdata(file); + struct cfe_device *cfe = node->cfe; + + cfe_dbg("%s: [%s]\n", __func__, node_desc[node->id].name); + + if (!node_supports_meta(node)) + return -EINVAL; + + *f = node->meta_fmt; + + return 0; +} + +static int cfe_s_fmt_meta(struct file *file, void *priv, struct v4l2_format *f) +{ + struct cfe_node *node = video_drvdata(file); + struct cfe_device *cfe = node->cfe; + struct vb2_queue *q = &node->buffer_queue; + int ret; + + cfe_dbg("%s: [%s]\n", __func__, node_desc[node->id].name); + + if (vb2_is_busy(q)) + return -EBUSY; + + if (!node_supports_meta(node)) + return -EINVAL; + + ret = try_fmt_meta(node, f); + if (ret) + return ret; + + node->meta_fmt = *f; + + cfe_dbg("%s: Set " V4L2_FOURCC_CONV "\n", __func__, + V4L2_FOURCC_CONV_ARGS(node->meta_fmt.fmt.meta.dataformat)); + + return 0; +} + +static int cfe_try_fmt_meta(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cfe_node *node = video_drvdata(file); + struct cfe_device *cfe = node->cfe; + + cfe_dbg("%s: [%s]\n", __func__, node_desc[node->id].name); + return try_fmt_meta(node, f); +} + +static int cfe_enum_framesizes(struct file *file, void *priv, + struct v4l2_frmsizeenum *fsize) +{ + struct cfe_node *node = video_drvdata(file); + struct cfe_device *cfe = node->cfe; + const struct cfe_fmt *fmt; + + cfe_dbg("%s [%s]\n", __func__, node_desc[node->id].name); + + if (fsize->index > 0) + return -EINVAL; + + /* check for valid format */ + fmt = find_format_by_pix(fsize->pixel_format); + if (!fmt) { + cfe_dbg("Invalid pixel code: %x\n", fsize->pixel_format); + return -EINVAL; + } + + /* TODO: Do we have limits on the step_width? */ + + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; + fsize->stepwise.min_width = MIN_WIDTH; + fsize->stepwise.max_width = MAX_WIDTH; + fsize->stepwise.step_width = 2; + fsize->stepwise.min_height = MIN_HEIGHT; + fsize->stepwise.max_height = MAX_HEIGHT; + fsize->stepwise.step_height = 1; + + return 0; +} + +static int cfe_vb2_ioctl_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *p) +{ + struct video_device *vdev = video_devdata(file); + struct cfe_node *node = video_get_drvdata(vdev); + struct cfe_device *cfe = node->cfe; + int ret; + + cfe_dbg("%s: [%s] type:%u\n", __func__, node_desc[node->id].name, + p->type); + + if (p->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + p->type != V4L2_BUF_TYPE_META_CAPTURE && + p->type != V4L2_BUF_TYPE_META_OUTPUT) + return -EINVAL; + + ret = vb2_queue_change_type(vdev->queue, p->type); + if (ret) + return ret; + + return vb2_ioctl_reqbufs(file, priv, p); +} + +static int cfe_vb2_ioctl_create_bufs(struct file *file, void *priv, + struct v4l2_create_buffers *p) +{ + struct video_device *vdev = video_devdata(file); + struct cfe_node *node = video_get_drvdata(vdev); + struct cfe_device *cfe = node->cfe; + int ret; + + cfe_dbg("%s: [%s] type:%u\n", __func__, node_desc[node->id].name, + p->format.type); + + if (p->format.type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + p->format.type != V4L2_BUF_TYPE_META_CAPTURE && + p->format.type != V4L2_BUF_TYPE_META_OUTPUT) + return -EINVAL; + + ret = vb2_queue_change_type(vdev->queue, p->format.type); + if (ret) + return ret; + + return vb2_ioctl_create_bufs(file, priv, p); +} + +static int cfe_subscribe_event(struct v4l2_fh *fh, + const struct v4l2_event_subscription *sub) +{ + struct cfe_node *node = video_get_drvdata(fh->vdev); + + switch (sub->type) { + case V4L2_EVENT_FRAME_SYNC: + if (!node_supports_image_output(node)) + break; + + return v4l2_event_subscribe(fh, sub, 2, NULL); + case V4L2_EVENT_SOURCE_CHANGE: + if (!node_supports_image_output(node) && + !node_supports_meta_output(node)) + break; + + return v4l2_event_subscribe(fh, sub, 4, NULL); + } + + return v4l2_ctrl_subscribe_event(fh, sub); +} + +static const struct v4l2_ioctl_ops cfe_ioctl_ops = { + .vidioc_querycap = cfe_querycap, + .vidioc_enum_fmt_vid_cap = cfe_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = cfe_g_fmt, + .vidioc_s_fmt_vid_cap = cfe_s_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = cfe_try_fmt_vid_cap, + + .vidioc_enum_fmt_meta_cap = cfe_enum_fmt_meta, + .vidioc_g_fmt_meta_cap = cfe_g_fmt_meta, + .vidioc_s_fmt_meta_cap = cfe_s_fmt_meta, + .vidioc_try_fmt_meta_cap = cfe_try_fmt_meta, + + .vidioc_enum_fmt_meta_out = cfe_enum_fmt_meta, + .vidioc_g_fmt_meta_out = cfe_g_fmt_meta, + .vidioc_s_fmt_meta_out = cfe_s_fmt_meta, + .vidioc_try_fmt_meta_out = cfe_try_fmt_meta, + + .vidioc_enum_framesizes = cfe_enum_framesizes, + + .vidioc_reqbufs = cfe_vb2_ioctl_reqbufs, + .vidioc_create_bufs = cfe_vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + + .vidioc_subscribe_event = cfe_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static void cfe_notify(struct v4l2_subdev *sd, unsigned int notification, + void *arg) +{ + struct cfe_device *cfe = to_cfe_device(sd->v4l2_dev); + unsigned int i; + + switch (notification) { + case V4L2_DEVICE_NOTIFY_EVENT: + for (i = 0; i < NUM_NODES; i++) { + struct cfe_node *node = &cfe->node[i]; + + if (check_state(cfe, NODE_REGISTERED, i)) + continue; + + v4l2_event_queue(&node->video_dev, arg); + } + break; + default: + break; + } +} + +/* cfe capture driver file operations */ +static const struct v4l2_file_operations cfe_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .poll = vb2_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap, +}; + +static int cfe_video_link_validate(struct cfe_node *node, + struct v4l2_subdev *remote_sd, + struct v4l2_mbus_framefmt *remote_fmt) +{ + struct cfe_device *cfe = node->cfe; + + if (!remote_fmt) { + return -EINVAL; + } + + if (is_image_output_node(node)) { + struct v4l2_pix_format *pix_fmt = &node->vid_fmt.fmt.pix; + const struct cfe_fmt *fmt = NULL; + unsigned int i; + + if (remote_fmt->width != pix_fmt->width || + remote_fmt->height != pix_fmt->height) { + cfe_err("Wrong width or height %ux%u (remote pad set to %ux%u)\n", + pix_fmt->width, pix_fmt->height, + remote_fmt->width, + remote_fmt->height); + return -EINVAL; + } + + for (i = 0; i < ARRAY_SIZE(formats); i++) { + if (formats[i].code == remote_fmt->code && + formats[i].fourcc == pix_fmt->pixelformat) { + fmt = &formats[i]; + break; + } + } + if (!fmt) { + cfe_err("Format mismatch!\n"); + return -EINVAL; + } + } else if (is_csi2_node(node) && is_meta_output_node(node)) { + struct v4l2_meta_format *meta_fmt = &node->meta_fmt.fmt.meta; + const struct cfe_fmt *fmt; + u32 remote_size; + + fmt = find_format_by_code(remote_fmt->code); + if (!fmt || fmt->fourcc != meta_fmt->dataformat) { + cfe_err("Metadata format mismatch!\n"); + return -EINVAL; + } + + remote_size = DIV_ROUND_UP(remote_fmt->width * remote_fmt->height * fmt->depth, 8); + + if (remote_fmt->code != MEDIA_BUS_FMT_SENSOR_DATA) { + cfe_err("Bad metadata mbus format\n"); + return -EINVAL; + } + + if (remote_size > meta_fmt->buffersize) { + cfe_err("Metadata buffer too small: %u < %u\n", + meta_fmt->buffersize, remote_size); + return -EINVAL; + } + } + + return 0; +} + +static int cfe_video_link_validate_output(struct media_link *link) +{ + struct video_device *vd = container_of(link->source->entity, + struct video_device, entity); + struct cfe_node *node = container_of(vd, struct cfe_node, video_dev); + struct cfe_device *cfe = node->cfe; + struct v4l2_mbus_framefmt *sink_fmt; + struct v4l2_subdev_state *state; + struct v4l2_subdev *sink_sd; + int ret; + + cfe_dbg("%s: [%s] link \"%s\":%u -> \"%s\":%u\n", __func__, + node_desc[node->id].name, + link->source->entity->name, link->source->index, + link->sink->entity->name, link->sink->index); + + if (!media_entity_remote_source_pad_unique(link->source->entity)) { + cfe_err("video node %s pad not connected\n", vd->name); + return -ENOTCONN; + } + + sink_sd = media_entity_to_v4l2_subdev(link->sink->entity); + + state = v4l2_subdev_lock_and_get_active_state(sink_sd); + + sink_fmt = v4l2_subdev_state_get_format(state, link->sink->index); + + ret = cfe_video_link_validate(node, sink_sd, sink_fmt); + + v4l2_subdev_unlock_state(state); + + return ret; +} + +static int cfe_video_link_validate_capture(struct media_link *link) +{ + struct video_device *vd = container_of(link->sink->entity, + struct video_device, entity); + struct cfe_node *node = container_of(vd, struct cfe_node, video_dev); + struct cfe_device *cfe = node->cfe; + struct v4l2_mbus_framefmt *source_fmt; + struct v4l2_subdev_state *state; + struct v4l2_subdev *source_sd; + int ret; + + cfe_dbg("%s: [%s] link \"%s\":%u -> \"%s\":%u\n", __func__, + node_desc[node->id].name, + link->source->entity->name, link->source->index, + link->sink->entity->name, link->sink->index); + + if (!media_entity_remote_source_pad_unique(link->sink->entity)) { + cfe_err("video node %s pad not connected\n", vd->name); + return -ENOTCONN; + } + + source_sd = media_entity_to_v4l2_subdev(link->source->entity); + + state = v4l2_subdev_lock_and_get_active_state(source_sd); + + source_fmt = v4l2_subdev_state_get_format(state, link->source->index); + + ret = cfe_video_link_validate(node, source_sd, source_fmt); + + v4l2_subdev_unlock_state(state); + + return ret; +} + +static const struct media_entity_operations cfe_media_entity_ops_output = { + .link_validate = cfe_video_link_validate_output, +}; + +static const struct media_entity_operations cfe_media_entity_ops_capture = { + .link_validate = cfe_video_link_validate_capture, +}; + +static int cfe_video_link_notify(struct media_link *link, u32 flags, + unsigned int notification) +{ + struct media_device *mdev = link->graph_obj.mdev; + struct cfe_device *cfe = container_of(mdev, struct cfe_device, mdev); + struct media_entity *fe = &cfe->fe.sd.entity; + struct media_entity *csi2 = &cfe->csi2.sd.entity; + unsigned long lock_flags; + unsigned int i; + + if (notification != MEDIA_DEV_NOTIFY_POST_LINK_CH) + return 0; + + cfe_dbg("%s: %s[%u] -> %s[%u] 0x%x", __func__, + link->source->entity->name, link->source->index, + link->sink->entity->name, link->sink->index, flags); + + spin_lock_irqsave(&cfe->state_lock, lock_flags); + + for (i = 0; i < NUM_NODES; i++) { + if (link->sink->entity != &cfe->node[i].video_dev.entity && + link->source->entity != &cfe->node[i].video_dev.entity) + continue; + + if (link->flags & MEDIA_LNK_FL_ENABLED) + set_state(cfe, NODE_ENABLED, i); + else + clear_state(cfe, NODE_ENABLED, i); + + break; + } + + spin_unlock_irqrestore(&cfe->state_lock, lock_flags); + + if (link->source->entity != csi2) + return 0; + if (link->sink->entity != fe) + return 0; + if (link->sink->index != 0) + return 0; + + cfe->fe_csi2_channel = -1; + if (link->flags & MEDIA_LNK_FL_ENABLED) { + if (link->source->index == node_desc[CSI2_CH0].link_pad) + cfe->fe_csi2_channel = CSI2_CH0; + else if (link->source->index == node_desc[CSI2_CH1].link_pad) + cfe->fe_csi2_channel = CSI2_CH1; + else if (link->source->index == node_desc[CSI2_CH2].link_pad) + cfe->fe_csi2_channel = CSI2_CH2; + else if (link->source->index == node_desc[CSI2_CH3].link_pad) + cfe->fe_csi2_channel = CSI2_CH3; + } + + if (is_fe_enabled(cfe)) + cfe_dbg("%s: Found CSI2:%d -> FE:0 link\n", __func__, + cfe->fe_csi2_channel); + else + cfe_dbg("%s: Unable to find CSI2:x -> FE:0 link\n", __func__); + + return 0; +} + +static const struct media_device_ops cfe_media_device_ops = { + .link_notify = cfe_video_link_notify, +}; + +static void cfe_release(struct kref *kref) +{ + struct cfe_device *cfe = container_of(kref, struct cfe_device, kref); + + media_device_cleanup(&cfe->mdev); + + kfree(cfe); +} + +static void cfe_put(struct cfe_device *cfe) +{ + kref_put(&cfe->kref, cfe_release); +} + +static void cfe_get(struct cfe_device *cfe) +{ + kref_get(&cfe->kref); +} + +static void cfe_node_release(struct video_device *vdev) +{ + struct cfe_node *node = video_get_drvdata(vdev); + + cfe_put(node->cfe); +} + +static int cfe_register_node(struct cfe_device *cfe, int id) +{ + struct video_device *vdev; + const struct cfe_fmt *fmt; + struct vb2_queue *q; + struct cfe_node *node = &cfe->node[id]; + int ret; + + node->cfe = cfe; + node->id = id; + + if (node_supports_image(node)) { + if (node_supports_image_output(node)) + node->vid_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + else + node->vid_fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + + fmt = find_format_by_code(cfe_default_format.code); + if (!fmt) { + cfe_err("Failed to find format code\n"); + return -EINVAL; + } + + node->vid_fmt.fmt.pix.pixelformat = fmt->fourcc; + v4l2_fill_pix_format(&node->vid_fmt.fmt.pix, &cfe_default_format); + + ret = try_fmt_vid_cap(node, &node->vid_fmt); + if (ret) + return ret; + } + + if (node_supports_meta(node)) { + if (node_supports_meta_output(node)) + node->meta_fmt.type = V4L2_BUF_TYPE_META_CAPTURE; + else + node->meta_fmt.type = V4L2_BUF_TYPE_META_OUTPUT; + + ret = try_fmt_meta(node, &node->meta_fmt); + if (ret) + return ret; + } + + mutex_init(&node->lock); + + q = &node->buffer_queue; + q->type = node_supports_image(node) ? node->vid_fmt.type : + node->meta_fmt.type; + q->io_modes = VB2_MMAP | VB2_DMABUF; + q->drv_priv = node; + q->ops = &cfe_video_qops; + q->mem_ops = &vb2_dma_contig_memops; + q->buf_struct_size = id == FE_CONFIG ? sizeof(struct cfe_config_buffer) + : sizeof(struct cfe_buffer); + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->lock = &node->lock; + q->min_queued_buffers = 1; + q->dev = &cfe->pdev->dev; + + ret = vb2_queue_init(q); + if (ret) { + cfe_err("vb2_queue_init() failed\n"); + return ret; + } + + INIT_LIST_HEAD(&node->dma_queue); + + vdev = &node->video_dev; + vdev->release = cfe_node_release; + vdev->fops = &cfe_fops; + vdev->ioctl_ops = &cfe_ioctl_ops; + vdev->v4l2_dev = &cfe->v4l2_dev; + if ((node_supports_image_output(node) || + node_supports_meta_output(node))) { + vdev->entity.ops = &cfe_media_entity_ops_capture; + vdev->vfl_dir = VFL_DIR_RX; + } else { + vdev->entity.ops = &cfe_media_entity_ops_output; + vdev->vfl_dir = VFL_DIR_TX; + } + vdev->queue = q; + vdev->lock = &node->lock; + vdev->device_caps = node_desc[id].caps; + vdev->device_caps |= V4L2_CAP_STREAMING | V4L2_CAP_IO_MC; + + /* Define the device names */ + snprintf(vdev->name, sizeof(vdev->name), "%s-%s", CFE_MODULE_NAME, + node_desc[id].name); + + video_set_drvdata(vdev, node); + if (node->id == FE_OUT0) + vdev->entity.flags |= MEDIA_ENT_FL_DEFAULT; + node->pad.flags = node_desc[id].pad_flags; + media_entity_pads_init(&vdev->entity, 1, &node->pad); + + if (!node_supports_image(node)) { + v4l2_disable_ioctl(&node->video_dev, + VIDIOC_ENUM_FRAMEINTERVALS); + v4l2_disable_ioctl(&node->video_dev, + VIDIOC_ENUM_FRAMESIZES); + } + + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); + if (ret) { + cfe_err("Unable to register video device %s\n", vdev->name); + return ret; + } + + cfe_info("Registered [%s] node id %d successfully as /dev/video%u\n", + vdev->name, id, vdev->num); + + /* + * Acquire a reference to cfe, which will be released when the video + * device will be unregistered and userspace will have closed all open + * file handles. + */ + cfe_get(cfe); + set_state(cfe, NODE_REGISTERED, id); + + return 0; +} + +static void cfe_unregister_nodes(struct cfe_device *cfe) +{ + unsigned int i; + + for (i = 0; i < NUM_NODES; i++) { + struct cfe_node *node = &cfe->node[i]; + + if (check_state(cfe, NODE_REGISTERED, i)) { + clear_state(cfe, NODE_REGISTERED, i); + video_unregister_device(&node->video_dev); + } + } +} + +static int cfe_link_node_pads(struct cfe_device *cfe) +{ + unsigned int i, source_pad = 0; + int ret; + + for (i = 0; i < CSI2_NUM_CHANNELS; i++) { + struct cfe_node *node = &cfe->node[i]; + + if (!check_state(cfe, NODE_REGISTERED, i)) + continue; + + /* Find next source pad */ + while (source_pad < cfe->sensor->entity.num_pads && + !(cfe->sensor->entity.pads[source_pad].flags & + MEDIA_PAD_FL_SOURCE)) + source_pad++; + + if (source_pad < cfe->sensor->entity.num_pads) { + /* Sensor -> CSI2 */ + ret = media_create_pad_link(&cfe->sensor->entity, source_pad, + &cfe->csi2.sd.entity, i, + MEDIA_LNK_FL_IMMUTABLE | + MEDIA_LNK_FL_ENABLED); + if (ret) + return ret; + + /* Dealt with that source_pad, look at the next one next time */ + source_pad++; + } + + /* CSI2 channel # -> /dev/video# */ + ret = media_create_pad_link(&cfe->csi2.sd.entity, + node_desc[i].link_pad, + &node->video_dev.entity, 0, 0); + if (ret) + return ret; + + if (node_supports_image(node)) { + /* CSI2 channel # -> FE Input */ + ret = media_create_pad_link(&cfe->csi2.sd.entity, + node_desc[i].link_pad, + &cfe->fe.sd.entity, + FE_STREAM_PAD, 0); + if (ret) + return ret; + } + } + + for (; i < NUM_NODES; i++) { + struct cfe_node *node = &cfe->node[i]; + struct media_entity *src, *dst; + unsigned int src_pad, dst_pad; + + if (node_desc[i].pad_flags & MEDIA_PAD_FL_SINK) { + /* FE -> /dev/video# */ + src = &cfe->fe.sd.entity; + src_pad = node_desc[i].link_pad; + dst = &node->video_dev.entity; + dst_pad = 0; + } else { + /* /dev/video# -> FE */ + dst = &cfe->fe.sd.entity; + dst_pad = node_desc[i].link_pad; + src = &node->video_dev.entity; + src_pad = 0; + } + + ret = media_create_pad_link(src, src_pad, dst, dst_pad, 0); + if (ret) + return ret; + } + + return 0; +} + +static int cfe_probe_complete(struct cfe_device *cfe) +{ + unsigned int i; + int ret; + + cfe->v4l2_dev.notify = cfe_notify; + + for (i = 0; i < NUM_NODES; i++) { + ret = cfe_register_node(cfe, i); + if (ret) { + cfe_err("Unable to register video node %u.\n", i); + goto unregister; + } + } + + ret = cfe_link_node_pads(cfe); + if (ret) { + cfe_err("Unable to link node pads.\n"); + goto unregister; + } + + ret = v4l2_device_register_subdev_nodes(&cfe->v4l2_dev); + if (ret) { + cfe_err("Unable to register subdev nodes.\n"); + goto unregister; + } + + return 0; + +unregister: + cfe_unregister_nodes(cfe); + return ret; +} + +static int cfe_async_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_connection *asd) +{ + struct cfe_device *cfe = to_cfe_device(notifier->v4l2_dev); + + if (cfe->sensor) { + cfe_info("Rejecting subdev %s (Already set!!)", subdev->name); + return 0; + } + + cfe->sensor = subdev; + cfe_info("Using sensor %s for capture\n", subdev->name); + + return 0; +} + +static int cfe_async_complete(struct v4l2_async_notifier *notifier) +{ + struct cfe_device *cfe = to_cfe_device(notifier->v4l2_dev); + + return cfe_probe_complete(cfe); +} + +static const struct v4l2_async_notifier_operations cfe_async_ops = { + .bound = cfe_async_bound, + .complete = cfe_async_complete, +}; + +static int of_cfe_connect_subdevs(struct cfe_device *cfe) +{ + struct platform_device *pdev = cfe->pdev; + struct v4l2_fwnode_endpoint ep = { .bus_type = V4L2_MBUS_CSI2_DPHY }; + struct device_node *node = pdev->dev.of_node; + struct device_node *ep_node; + struct device_node *sensor_node; + unsigned int lane; + int ret = -EINVAL; + + /* Get the local endpoint and remote device. */ + ep_node = of_graph_get_next_endpoint(node, NULL); + if (!ep_node) { + cfe_err("can't get next endpoint\n"); + return -EINVAL; + } + + cfe_dbg("ep_node is %pOF\n", ep_node); + + sensor_node = of_graph_get_remote_port_parent(ep_node); + if (!sensor_node) { + cfe_err("can't get remote parent\n"); + goto cleanup_exit; + } + + cfe_info("found subdevice %pOF\n", sensor_node); + + /* Parse the local endpoint and validate its configuration. */ + v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep_node), &ep); + + cfe->csi2.multipacket_line = + fwnode_property_present(of_fwnode_handle(ep_node), + "multipacket-line"); + + if (ep.bus_type != V4L2_MBUS_CSI2_DPHY) { + cfe_err("endpoint node type != CSI2\n"); + return -EINVAL; + } + + for (lane = 0; lane < ep.bus.mipi_csi2.num_data_lanes; lane++) { + if (ep.bus.mipi_csi2.data_lanes[lane] != lane + 1) { + cfe_err("subdevice %pOF: data lanes reordering not supported\n", + sensor_node); + goto cleanup_exit; + } + } + + cfe->csi2.dphy.max_lanes = ep.bus.mipi_csi2.num_data_lanes; + cfe->csi2.bus_flags = ep.bus.mipi_csi2.flags; + + cfe_dbg("subdevice %pOF: %u data lanes, flags=0x%08x, multipacket_line=%u\n", + sensor_node, cfe->csi2.dphy.max_lanes, cfe->csi2.bus_flags, + cfe->csi2.multipacket_line); + + /* Initialize and register the async notifier. */ + v4l2_async_nf_init(&cfe->notifier, &cfe->v4l2_dev); + cfe->notifier.ops = &cfe_async_ops; + + cfe->asd = v4l2_async_nf_add_fwnode(&cfe->notifier, + of_fwnode_handle(sensor_node), + struct v4l2_async_connection); + if (IS_ERR(cfe->asd)) { + cfe_err("Error adding subdevice: %d\n", ret); + goto cleanup_exit; + } + + ret = v4l2_async_nf_register(&cfe->notifier); + if (ret) { + cfe_err("Error registering async notifier: %d\n", ret); + ret = -EINVAL; + } + +cleanup_exit: + of_node_put(sensor_node); + of_node_put(ep_node); + + return ret; +} + +static int cfe_probe(struct platform_device *pdev) +{ + struct cfe_device *cfe; + char debugfs_name[32]; + int ret; + + cfe = kzalloc(sizeof(*cfe), GFP_KERNEL); + if (!cfe) + return -ENOMEM; + + platform_set_drvdata(pdev, cfe); + + kref_init(&cfe->kref); + cfe->pdev = pdev; + cfe->fe_csi2_channel = -1; + spin_lock_init(&cfe->state_lock); + + cfe->csi2.base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(cfe->csi2.base)) { + dev_err(&pdev->dev, "Failed to get dma io block\n"); + ret = PTR_ERR(cfe->csi2.base); + goto err_cfe_put; + } + + cfe->csi2.dphy.base = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(cfe->csi2.dphy.base)) { + dev_err(&pdev->dev, "Failed to get host io block\n"); + ret = PTR_ERR(cfe->csi2.dphy.base); + goto err_cfe_put; + } + + cfe->mipi_cfg_base = devm_platform_ioremap_resource(pdev, 2); + if (IS_ERR(cfe->mipi_cfg_base)) { + dev_err(&pdev->dev, "Failed to get mipi cfg io block\n"); + ret = PTR_ERR(cfe->mipi_cfg_base); + goto err_cfe_put; + } + + cfe->fe.base = devm_platform_ioremap_resource(pdev, 3); + if (IS_ERR(cfe->fe.base)) { + dev_err(&pdev->dev, "Failed to get pisp fe io block\n"); + ret = PTR_ERR(cfe->fe.base); + goto err_cfe_put; + } + + ret = platform_get_irq(pdev, 0); + if (ret <= 0) { + dev_err(&pdev->dev, "No IRQ resource\n"); + ret = -EINVAL; + goto err_cfe_put; + } + + ret = devm_request_irq(&pdev->dev, ret, cfe_isr, 0, "rp1-cfe", cfe); + if (ret) { + dev_err(&pdev->dev, "Unable to request interrupt\n"); + ret = -EINVAL; + goto err_cfe_put; + } + + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); + if (ret) { + dev_err(&pdev->dev, "DMA enable failed\n"); + goto err_cfe_put; + } + + /* TODO: Enable clock only when running. */ + cfe->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(cfe->clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(cfe->clk), + "clock not found\n"); + + cfe->mdev.dev = &pdev->dev; + cfe->mdev.ops = &cfe_media_device_ops; + strscpy(cfe->mdev.model, CFE_MODULE_NAME, sizeof(cfe->mdev.model)); + strscpy(cfe->mdev.serial, "", sizeof(cfe->mdev.serial)); + snprintf(cfe->mdev.bus_info, sizeof(cfe->mdev.bus_info), "platform:%s", + dev_name(&pdev->dev)); + + media_device_init(&cfe->mdev); + + cfe->v4l2_dev.mdev = &cfe->mdev; + + ret = v4l2_device_register(&pdev->dev, &cfe->v4l2_dev); + if (ret) { + cfe_err("Unable to register v4l2 device.\n"); + goto err_cfe_put; + } + + snprintf(debugfs_name, sizeof(debugfs_name), "rp1-cfe:%s", + dev_name(&pdev->dev)); + cfe->debugfs = debugfs_create_dir(debugfs_name, NULL); + debugfs_create_file("format", 0444, cfe->debugfs, cfe, &format_fops); + debugfs_create_file("regs", 0444, cfe->debugfs, cfe, + &mipi_cfg_regs_fops); + + /* Enable the block power domain */ + pm_runtime_enable(&pdev->dev); + + ret = pm_runtime_resume_and_get(&cfe->pdev->dev); + if (ret) + goto err_runtime_disable; + + cfe->csi2.v4l2_dev = &cfe->v4l2_dev; + ret = csi2_init(&cfe->csi2, cfe->debugfs); + if (ret) { + cfe_err("Failed to init csi2 (%d)\n", ret); + goto err_runtime_put; + } + + cfe->fe.v4l2_dev = &cfe->v4l2_dev; + ret = pisp_fe_init(&cfe->fe, cfe->debugfs); + if (ret) { + cfe_err("Failed to init pisp fe (%d)\n", ret); + goto err_csi2_uninit; + } + + cfe->mdev.hw_revision = cfe->fe.hw_revision; + ret = media_device_register(&cfe->mdev); + if (ret < 0) { + cfe_err("Unable to register media-controller device.\n"); + goto err_pisp_fe_uninit; + } + + ret = of_cfe_connect_subdevs(cfe); + if (ret) { + cfe_err("Failed to connect subdevs\n"); + goto err_media_unregister; + } + + pm_runtime_put(&cfe->pdev->dev); + + return 0; + +err_media_unregister: + media_device_unregister(&cfe->mdev); +err_pisp_fe_uninit: + pisp_fe_uninit(&cfe->fe); +err_csi2_uninit: + csi2_uninit(&cfe->csi2); +err_runtime_put: + pm_runtime_put(&cfe->pdev->dev); +err_runtime_disable: + pm_runtime_disable(&pdev->dev); + debugfs_remove(cfe->debugfs); + v4l2_device_unregister(&cfe->v4l2_dev); +err_cfe_put: + cfe_put(cfe); + + return ret; +} + +static void cfe_remove(struct platform_device *pdev) +{ + struct cfe_device *cfe = platform_get_drvdata(pdev); + + debugfs_remove(cfe->debugfs); + + v4l2_async_nf_unregister(&cfe->notifier); + media_device_unregister(&cfe->mdev); + cfe_unregister_nodes(cfe); + + pisp_fe_uninit(&cfe->fe); + csi2_uninit(&cfe->csi2); + + pm_runtime_disable(&pdev->dev); + + v4l2_device_unregister(&cfe->v4l2_dev); + + cfe_put(cfe); +} + +static int cfe_runtime_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct cfe_device *cfe = platform_get_drvdata(pdev); + + clk_disable_unprepare(cfe->clk); + + return 0; +} + +static int cfe_runtime_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct cfe_device *cfe = platform_get_drvdata(pdev); + int ret; + + ret = clk_prepare_enable(cfe->clk); + if (ret) { + dev_err(dev, "Unable to enable clock\n"); + return ret; + } + + return 0; +} + +static const struct dev_pm_ops cfe_pm_ops = { + SET_RUNTIME_PM_OPS(cfe_runtime_suspend, cfe_runtime_resume, NULL) + SET_LATE_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) +}; + +static const struct of_device_id cfe_of_match[] = { + { .compatible = "raspberrypi,rp1-cfe" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, cfe_of_match); + +static struct platform_driver cfe_driver = { + .probe = cfe_probe, + .remove = cfe_remove, + .driver = { + .name = CFE_MODULE_NAME, + .of_match_table = cfe_of_match, + .pm = &cfe_pm_ops, + }, +}; + +module_platform_driver(cfe_driver); + +MODULE_AUTHOR("Naushir Patuck <naush@raspberrypi.com>"); +MODULE_DESCRIPTION("RP1 Camera Front End driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(CFE_VERSION); diff --git a/drivers/media/platform/raspberrypi/rp1_cfe/cfe.h b/drivers/media/platform/raspberrypi/rp1_cfe/cfe.h new file mode 100644 index 00000000000000..637b63a838c407 --- /dev/null +++ b/drivers/media/platform/raspberrypi/rp1_cfe/cfe.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * RP1 CFE driver. + * Copyright (c) 2021 Raspberry Pi Ltd. + * + */ +#ifndef _RP1_CFE_ +#define _RP1_CFE_ + +#include <linux/types.h> +#include <linux/media-bus-format.h> +#include <linux/videodev2.h> + +extern bool cfe_debug_verbose; + +enum cfe_remap_types { + CFE_REMAP_16BIT, + CFE_REMAP_COMPRESSED, + CFE_NUM_REMAP, +}; + +#define CFE_FORMAT_FLAG_META_OUT BIT(0) +#define CFE_FORMAT_FLAG_META_CAP BIT(1) +#define CFE_FORMAT_FLAG_FE_OUT BIT(2) + +struct cfe_fmt { + u32 fourcc; + u32 code; + u8 depth; + u8 csi_dt; + u32 remap[CFE_NUM_REMAP]; + u32 flags; +}; + +extern const struct v4l2_mbus_framefmt cfe_default_format; +extern const struct v4l2_mbus_framefmt cfe_default_meta_format; + +const struct cfe_fmt *find_format_by_code(u32 code); +const struct cfe_fmt *find_format_by_pix(u32 pixelformat); +u32 cfe_find_16bit_code(u32 code); +u32 cfe_find_compressed_code(u32 code); + +#endif diff --git a/drivers/media/platform/raspberrypi/rp1_cfe/cfe_fmts.h b/drivers/media/platform/raspberrypi/rp1_cfe/cfe_fmts.h new file mode 100644 index 00000000000000..29c807253e6428 --- /dev/null +++ b/drivers/media/platform/raspberrypi/rp1_cfe/cfe_fmts.h @@ -0,0 +1,318 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * RP1 Camera Front End formats definition + * + * Copyright (C) 2021 - Raspberry Pi Ltd. + * + */ +#ifndef _CFE_FMTS_H_ +#define _CFE_FMTS_H_ + +#include "cfe.h" +#include <media/mipi-csi2.h> + +static const struct cfe_fmt formats[] = { + /* YUV Formats */ + { + .fourcc = V4L2_PIX_FMT_YUYV, + .code = MEDIA_BUS_FMT_YUYV8_1X16, + .depth = 16, + .csi_dt = MIPI_CSI2_DT_YUV422_8B, + }, + { + .fourcc = V4L2_PIX_FMT_UYVY, + .code = MEDIA_BUS_FMT_UYVY8_1X16, + .depth = 16, + .csi_dt = MIPI_CSI2_DT_YUV422_8B, + }, + { + .fourcc = V4L2_PIX_FMT_YVYU, + .code = MEDIA_BUS_FMT_YVYU8_1X16, + .depth = 16, + .csi_dt = MIPI_CSI2_DT_YUV422_8B, + }, + { + .fourcc = V4L2_PIX_FMT_VYUY, + .code = MEDIA_BUS_FMT_VYUY8_1X16, + .depth = 16, + .csi_dt = MIPI_CSI2_DT_YUV422_8B, + }, + { + /* RGB Formats */ + .fourcc = V4L2_PIX_FMT_RGB565, /* gggbbbbb rrrrrggg */ + .code = MEDIA_BUS_FMT_RGB565_2X8_LE, + .depth = 16, + .csi_dt = MIPI_CSI2_DT_RGB565, + }, + { .fourcc = V4L2_PIX_FMT_RGB565X, /* rrrrrggg gggbbbbb */ + .code = MEDIA_BUS_FMT_RGB565_2X8_BE, + .depth = 16, + .csi_dt = MIPI_CSI2_DT_RGB565, + }, + { + .fourcc = V4L2_PIX_FMT_RGB555, /* gggbbbbb arrrrrgg */ + .code = MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE, + .depth = 16, + .csi_dt = MIPI_CSI2_DT_RGB555, + }, + { + .fourcc = V4L2_PIX_FMT_RGB555X, /* arrrrrgg gggbbbbb */ + .code = MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE, + .depth = 16, + .csi_dt = MIPI_CSI2_DT_RGB555, + }, + { + .fourcc = V4L2_PIX_FMT_RGB24, /* rgb */ + .code = MEDIA_BUS_FMT_RGB888_1X24, + .depth = 24, + .csi_dt = MIPI_CSI2_DT_RGB888, + }, + { + .fourcc = V4L2_PIX_FMT_BGR24, /* bgr */ + .code = MEDIA_BUS_FMT_BGR888_1X24, + .depth = 24, + .csi_dt = MIPI_CSI2_DT_RGB888, + }, + { + .fourcc = V4L2_PIX_FMT_RGB32, /* argb */ + .code = MEDIA_BUS_FMT_ARGB8888_1X32, + .depth = 32, + .csi_dt = 0x0, + }, + + /* Bayer Formats */ + { + .fourcc = V4L2_PIX_FMT_SBGGR8, + .code = MEDIA_BUS_FMT_SBGGR8_1X8, + .depth = 8, + .csi_dt = MIPI_CSI2_DT_RAW8, + .remap = { V4L2_PIX_FMT_SBGGR16, V4L2_PIX_FMT_PISP_COMP1_BGGR }, + }, + { + .fourcc = V4L2_PIX_FMT_SGBRG8, + .code = MEDIA_BUS_FMT_SGBRG8_1X8, + .depth = 8, + .csi_dt = MIPI_CSI2_DT_RAW8, + .remap = { V4L2_PIX_FMT_SGBRG16, V4L2_PIX_FMT_PISP_COMP1_GBRG }, + }, + { + .fourcc = V4L2_PIX_FMT_SGRBG8, + .code = MEDIA_BUS_FMT_SGRBG8_1X8, + .depth = 8, + .csi_dt = MIPI_CSI2_DT_RAW8, + .remap = { V4L2_PIX_FMT_SGRBG16, V4L2_PIX_FMT_PISP_COMP1_GRBG }, + }, + { + .fourcc = V4L2_PIX_FMT_SRGGB8, + .code = MEDIA_BUS_FMT_SRGGB8_1X8, + .depth = 8, + .csi_dt = MIPI_CSI2_DT_RAW8, + .remap = { V4L2_PIX_FMT_SRGGB16, V4L2_PIX_FMT_PISP_COMP1_RGGB }, + }, + { + .fourcc = V4L2_PIX_FMT_SBGGR10P, + .code = MEDIA_BUS_FMT_SBGGR10_1X10, + .depth = 10, + .csi_dt = MIPI_CSI2_DT_RAW10, + .remap = { V4L2_PIX_FMT_SBGGR16, V4L2_PIX_FMT_PISP_COMP1_BGGR }, + }, + { + .fourcc = V4L2_PIX_FMT_SGBRG10P, + .code = MEDIA_BUS_FMT_SGBRG10_1X10, + .depth = 10, + .csi_dt = MIPI_CSI2_DT_RAW10, + .remap = { V4L2_PIX_FMT_SGBRG16, V4L2_PIX_FMT_PISP_COMP1_GBRG }, + }, + { + .fourcc = V4L2_PIX_FMT_SGRBG10P, + .code = MEDIA_BUS_FMT_SGRBG10_1X10, + .depth = 10, + .csi_dt = MIPI_CSI2_DT_RAW10, + .remap = { V4L2_PIX_FMT_SGRBG16, V4L2_PIX_FMT_PISP_COMP1_GRBG }, + }, + { + .fourcc = V4L2_PIX_FMT_SRGGB10P, + .code = MEDIA_BUS_FMT_SRGGB10_1X10, + .depth = 10, + .csi_dt = MIPI_CSI2_DT_RAW10, + .remap = { V4L2_PIX_FMT_SRGGB16, V4L2_PIX_FMT_PISP_COMP1_RGGB }, + }, + { + .fourcc = V4L2_PIX_FMT_SBGGR12P, + .code = MEDIA_BUS_FMT_SBGGR12_1X12, + .depth = 12, + .csi_dt = MIPI_CSI2_DT_RAW12, + .remap = { V4L2_PIX_FMT_SBGGR16, V4L2_PIX_FMT_PISP_COMP1_BGGR }, + }, + { + .fourcc = V4L2_PIX_FMT_SGBRG12P, + .code = MEDIA_BUS_FMT_SGBRG12_1X12, + .depth = 12, + .csi_dt = MIPI_CSI2_DT_RAW12, + .remap = { V4L2_PIX_FMT_SGBRG16, V4L2_PIX_FMT_PISP_COMP1_GBRG }, + }, + { + .fourcc = V4L2_PIX_FMT_SGRBG12P, + .code = MEDIA_BUS_FMT_SGRBG12_1X12, + .depth = 12, + .csi_dt = MIPI_CSI2_DT_RAW12, + .remap = { V4L2_PIX_FMT_SGRBG16, V4L2_PIX_FMT_PISP_COMP1_GRBG }, + }, + { + .fourcc = V4L2_PIX_FMT_SRGGB12P, + .code = MEDIA_BUS_FMT_SRGGB12_1X12, + .depth = 12, + .csi_dt = MIPI_CSI2_DT_RAW12, + .remap = { V4L2_PIX_FMT_SRGGB16, V4L2_PIX_FMT_PISP_COMP1_RGGB }, + }, + { + .fourcc = V4L2_PIX_FMT_SBGGR14P, + .code = MEDIA_BUS_FMT_SBGGR14_1X14, + .depth = 14, + .csi_dt = MIPI_CSI2_DT_RAW14, + .remap = { V4L2_PIX_FMT_SBGGR16, V4L2_PIX_FMT_PISP_COMP1_BGGR }, + }, + { + .fourcc = V4L2_PIX_FMT_SGBRG14P, + .code = MEDIA_BUS_FMT_SGBRG14_1X14, + .depth = 14, + .csi_dt = MIPI_CSI2_DT_RAW14, + .remap = { V4L2_PIX_FMT_SGBRG16, V4L2_PIX_FMT_PISP_COMP1_GBRG }, + }, + { + .fourcc = V4L2_PIX_FMT_SGRBG14P, + .code = MEDIA_BUS_FMT_SGRBG14_1X14, + .depth = 14, + .csi_dt = MIPI_CSI2_DT_RAW14, + .remap = { V4L2_PIX_FMT_SGRBG16, V4L2_PIX_FMT_PISP_COMP1_GRBG }, + }, + { + .fourcc = V4L2_PIX_FMT_SRGGB14P, + .code = MEDIA_BUS_FMT_SRGGB14_1X14, + .depth = 14, + .csi_dt = MIPI_CSI2_DT_RAW14, + .remap = { V4L2_PIX_FMT_SRGGB16, V4L2_PIX_FMT_PISP_COMP1_RGGB }, + }, + { + .fourcc = V4L2_PIX_FMT_SBGGR16, + .code = MEDIA_BUS_FMT_SBGGR16_1X16, + .depth = 16, + .csi_dt = MIPI_CSI2_DT_RAW16, + .flags = CFE_FORMAT_FLAG_FE_OUT, + .remap = { V4L2_PIX_FMT_SBGGR16, V4L2_PIX_FMT_PISP_COMP1_BGGR }, + }, + { + .fourcc = V4L2_PIX_FMT_SGBRG16, + .code = MEDIA_BUS_FMT_SGBRG16_1X16, + .depth = 16, + .csi_dt = MIPI_CSI2_DT_RAW16, + .flags = CFE_FORMAT_FLAG_FE_OUT, + .remap = { V4L2_PIX_FMT_SGBRG16, V4L2_PIX_FMT_PISP_COMP1_GBRG }, + }, + { + .fourcc = V4L2_PIX_FMT_SGRBG16, + .code = MEDIA_BUS_FMT_SGRBG16_1X16, + .depth = 16, + .csi_dt = MIPI_CSI2_DT_RAW16, + .flags = CFE_FORMAT_FLAG_FE_OUT, + .remap = { V4L2_PIX_FMT_SGRBG16, V4L2_PIX_FMT_PISP_COMP1_GRBG }, + }, + { + .fourcc = V4L2_PIX_FMT_SRGGB16, + .code = MEDIA_BUS_FMT_SRGGB16_1X16, + .depth = 16, + .csi_dt = MIPI_CSI2_DT_RAW16, + .flags = CFE_FORMAT_FLAG_FE_OUT, + .remap = { V4L2_PIX_FMT_SRGGB16, V4L2_PIX_FMT_PISP_COMP1_RGGB }, + }, + /* PiSP Compressed Mode 1 */ + { + .fourcc = V4L2_PIX_FMT_PISP_COMP1_RGGB, + .code = MEDIA_BUS_FMT_SRGGB16_1X16, + .depth = 8, + .flags = CFE_FORMAT_FLAG_FE_OUT, + }, + { + .fourcc = V4L2_PIX_FMT_PISP_COMP1_BGGR, + .code = MEDIA_BUS_FMT_SBGGR16_1X16, + .depth = 8, + .flags = CFE_FORMAT_FLAG_FE_OUT, + }, + { + .fourcc = V4L2_PIX_FMT_PISP_COMP1_GBRG, + .code = MEDIA_BUS_FMT_SGBRG16_1X16, + .depth = 8, + .flags = CFE_FORMAT_FLAG_FE_OUT, + }, + { + .fourcc = V4L2_PIX_FMT_PISP_COMP1_GRBG, + .code = MEDIA_BUS_FMT_SGRBG16_1X16, + .depth = 8, + .flags = CFE_FORMAT_FLAG_FE_OUT, + }, + /* Greyscale format */ + { + .fourcc = V4L2_PIX_FMT_GREY, + .code = MEDIA_BUS_FMT_Y8_1X8, + .depth = 8, + .csi_dt = MIPI_CSI2_DT_RAW8, + .remap = { V4L2_PIX_FMT_Y16, V4L2_PIX_FMT_PISP_COMP1_MONO }, + }, + { + .fourcc = V4L2_PIX_FMT_Y10P, + .code = MEDIA_BUS_FMT_Y10_1X10, + .depth = 10, + .csi_dt = MIPI_CSI2_DT_RAW10, + .remap = { V4L2_PIX_FMT_Y16, V4L2_PIX_FMT_PISP_COMP1_MONO }, + }, + { + .fourcc = V4L2_PIX_FMT_Y12P, + .code = MEDIA_BUS_FMT_Y12_1X12, + .depth = 12, + .csi_dt = MIPI_CSI2_DT_RAW12, + .remap = { V4L2_PIX_FMT_Y16, V4L2_PIX_FMT_PISP_COMP1_MONO }, + }, + { + .fourcc = V4L2_PIX_FMT_Y14P, + .code = MEDIA_BUS_FMT_Y14_1X14, + .depth = 14, + .csi_dt = MIPI_CSI2_DT_RAW14, + .remap = { V4L2_PIX_FMT_Y16, V4L2_PIX_FMT_PISP_COMP1_MONO }, + }, + { + .fourcc = V4L2_PIX_FMT_Y16, + .code = MEDIA_BUS_FMT_Y16_1X16, + .depth = 16, + .csi_dt = MIPI_CSI2_DT_RAW16, + .flags = CFE_FORMAT_FLAG_FE_OUT, + .remap = { V4L2_PIX_FMT_Y16, V4L2_PIX_FMT_PISP_COMP1_MONO }, + }, + { + .fourcc = V4L2_PIX_FMT_PISP_COMP1_MONO, + .code = MEDIA_BUS_FMT_Y16_1X16, + .depth = 8, + .flags = CFE_FORMAT_FLAG_FE_OUT, + }, + /* Embedded data format */ + { + .fourcc = V4L2_META_FMT_SENSOR_DATA, + .code = MEDIA_BUS_FMT_SENSOR_DATA, + .depth = 8, + .csi_dt = MIPI_CSI2_DT_EMBEDDED_8B, + .flags = CFE_FORMAT_FLAG_META_CAP, + }, + + /* Frontend formats */ + { + .fourcc = V4L2_META_FMT_RPI_FE_CFG, + .code = MEDIA_BUS_FMT_FIXED, + .flags = CFE_FORMAT_FLAG_META_OUT, + }, + { + .fourcc = V4L2_META_FMT_RPI_FE_STATS, + .code = MEDIA_BUS_FMT_FIXED, + .flags = CFE_FORMAT_FLAG_META_CAP, + }, +}; + +#endif /* _CFE_FMTS_H_ */ diff --git a/drivers/media/platform/raspberrypi/rp1_cfe/csi2.c b/drivers/media/platform/raspberrypi/rp1_cfe/csi2.c new file mode 100644 index 00000000000000..25dc4354c67dc3 --- /dev/null +++ b/drivers/media/platform/raspberrypi/rp1_cfe/csi2.c @@ -0,0 +1,628 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * RP1 CSI-2 Driver + * + * Copyright (C) 2021 - Raspberry Pi Ltd. + * + */ + +#include <linux/delay.h> +#include <linux/moduleparam.h> +#include <linux/pm_runtime.h> +#include <linux/seq_file.h> + +#include <media/videobuf2-dma-contig.h> + +#include "csi2.h" +#include "cfe.h" + +static bool csi2_track_errors; +module_param_named(track_csi2_errors, csi2_track_errors, bool, 0); +MODULE_PARM_DESC(track_csi2_errors, "track csi-2 errors"); + +#define csi2_dbg_verbose(fmt, arg...) \ + do { \ + if (cfe_debug_verbose) \ + dev_dbg(csi2->v4l2_dev->dev, fmt, ##arg); \ + } while (0) +#define csi2_dbg(fmt, arg...) dev_dbg(csi2->v4l2_dev->dev, fmt, ##arg) +#define csi2_info(fmt, arg...) dev_info(csi2->v4l2_dev->dev, fmt, ##arg) +#define csi2_err(fmt, arg...) dev_err(csi2->v4l2_dev->dev, fmt, ##arg) + +/* CSI2-DMA registers */ +#define CSI2_STATUS 0x000 +#define CSI2_QOS 0x004 +#define CSI2_DISCARDS_OVERFLOW 0x008 +#define CSI2_DISCARDS_INACTIVE 0x00c +#define CSI2_DISCARDS_UNMATCHED 0x010 +#define CSI2_DISCARDS_LEN_LIMIT 0x014 + +#define CSI2_DISCARDS_AMOUNT_SHIFT 0 +#define CSI2_DISCARDS_AMOUNT_MASK GENMASK(23, 0) +#define CSI2_DISCARDS_DT_SHIFT 24 +#define CSI2_DISCARDS_DT_MASK GENMASK(29, 24) +#define CSI2_DISCARDS_VC_SHIFT 30 +#define CSI2_DISCARDS_VC_MASK GENMASK(31, 30) + +#define CSI2_LLEV_PANICS 0x018 +#define CSI2_ULEV_PANICS 0x01c +#define CSI2_IRQ_MASK 0x020 +#define CSI2_IRQ_MASK_IRQ_OVERFLOW BIT(0) +#define CSI2_IRQ_MASK_IRQ_DISCARD_OVERFLOW BIT(1) +#define CSI2_IRQ_MASK_IRQ_DISCARD_LENGTH_LIMIT BIT(2) +#define CSI2_IRQ_MASK_IRQ_DISCARD_UNMATCHED BIT(3) +#define CSI2_IRQ_MASK_IRQ_DISCARD_INACTIVE BIT(4) +#define CSI2_IRQ_MASK_IRQ_ALL \ + (CSI2_IRQ_MASK_IRQ_OVERFLOW | CSI2_IRQ_MASK_IRQ_DISCARD_OVERFLOW | \ + CSI2_IRQ_MASK_IRQ_DISCARD_LENGTH_LIMIT | \ + CSI2_IRQ_MASK_IRQ_DISCARD_UNMATCHED | \ + CSI2_IRQ_MASK_IRQ_DISCARD_INACTIVE) + +#define CSI2_CTRL 0x024 +#define CSI2_CH_CTRL(x) ((x) * 0x40 + 0x28) +#define CSI2_CH_ADDR0(x) ((x) * 0x40 + 0x2c) +#define CSI2_CH_ADDR1(x) ((x) * 0x40 + 0x3c) +#define CSI2_CH_STRIDE(x) ((x) * 0x40 + 0x30) +#define CSI2_CH_LENGTH(x) ((x) * 0x40 + 0x34) +#define CSI2_CH_DEBUG(x) ((x) * 0x40 + 0x38) +#define CSI2_CH_FRAME_SIZE(x) ((x) * 0x40 + 0x40) +#define CSI2_CH_COMP_CTRL(x) ((x) * 0x40 + 0x44) +#define CSI2_CH_FE_FRAME_ID(x) ((x) * 0x40 + 0x48) + +/* CSI2_STATUS */ +#define IRQ_FS(x) (BIT(0) << (x)) +#define IRQ_FE(x) (BIT(4) << (x)) +#define IRQ_FE_ACK(x) (BIT(8) << (x)) +#define IRQ_LE(x) (BIT(12) << (x)) +#define IRQ_LE_ACK(x) (BIT(16) << (x)) +#define IRQ_CH_MASK(x) (IRQ_FS(x) | IRQ_FE(x) | IRQ_FE_ACK(x) | IRQ_LE(x) | IRQ_LE_ACK(x)) +#define IRQ_OVERFLOW BIT(20) +#define IRQ_DISCARD_OVERFLOW BIT(21) +#define IRQ_DISCARD_LEN_LIMIT BIT(22) +#define IRQ_DISCARD_UNMATCHED BIT(23) +#define IRQ_DISCARD_INACTIVE BIT(24) + +/* CSI2_CTRL */ +#define EOP_IS_EOL BIT(0) + +/* CSI2_CH_CTRL */ +#define DMA_EN BIT(0) +#define FORCE BIT(3) +#define AUTO_ARM BIT(4) +#define IRQ_EN_FS BIT(13) +#define IRQ_EN_FE BIT(14) +#define IRQ_EN_FE_ACK BIT(15) +#define IRQ_EN_LE BIT(16) +#define IRQ_EN_LE_ACK BIT(17) +#define FLUSH_FE BIT(28) +#define PACK_LINE BIT(29) +#define PACK_BYTES BIT(30) +#define CH_MODE_MASK GENMASK(2, 1) +#define VC_MASK GENMASK(6, 5) +#define DT_MASK GENMASK(12, 7) +#define LC_MASK GENMASK(27, 18) + +/* CHx_COMPRESSION_CONTROL */ +#define COMP_OFFSET_MASK GENMASK(15, 0) +#define COMP_SHIFT_MASK GENMASK(19, 16) +#define COMP_MODE_MASK GENMASK(25, 24) + +static inline u32 csi2_reg_read(struct csi2_device *csi2, u32 offset) +{ + return readl(csi2->base + offset); +} + +static inline void csi2_reg_write(struct csi2_device *csi2, u32 offset, u32 val) +{ + writel(val, csi2->base + offset); + csi2_dbg_verbose("csi2: write 0x%04x -> 0x%03x\n", val, offset); +} + +static inline void set_field(u32 *valp, u32 field, u32 mask) +{ + u32 val = *valp; + + val &= ~mask; + val |= (field << __ffs(mask)) & mask; + *valp = val; +} + +static int csi2_regs_show(struct seq_file *s, void *data) +{ + struct csi2_device *csi2 = s->private; + unsigned int i; + int ret; + + ret = pm_runtime_resume_and_get(csi2->v4l2_dev->dev); + if (ret) + return ret; + +#define DUMP(reg) seq_printf(s, #reg " \t0x%08x\n", csi2_reg_read(csi2, reg)) +#define DUMP_CH(idx, reg) seq_printf(s, #reg "(%u) \t0x%08x\n", idx, csi2_reg_read(csi2, reg(idx))) + + DUMP(CSI2_STATUS); + DUMP(CSI2_DISCARDS_OVERFLOW); + DUMP(CSI2_DISCARDS_INACTIVE); + DUMP(CSI2_DISCARDS_UNMATCHED); + DUMP(CSI2_DISCARDS_LEN_LIMIT); + DUMP(CSI2_LLEV_PANICS); + DUMP(CSI2_ULEV_PANICS); + DUMP(CSI2_IRQ_MASK); + DUMP(CSI2_CTRL); + + for (i = 0; i < CSI2_NUM_CHANNELS; ++i) { + DUMP_CH(i, CSI2_CH_CTRL); + DUMP_CH(i, CSI2_CH_ADDR0); + DUMP_CH(i, CSI2_CH_ADDR1); + DUMP_CH(i, CSI2_CH_STRIDE); + DUMP_CH(i, CSI2_CH_LENGTH); + DUMP_CH(i, CSI2_CH_DEBUG); + DUMP_CH(i, CSI2_CH_FRAME_SIZE); + DUMP_CH(i, CSI2_CH_COMP_CTRL); + DUMP_CH(i, CSI2_CH_FE_FRAME_ID); + } + +#undef DUMP +#undef DUMP_CH + + pm_runtime_put(csi2->v4l2_dev->dev); + + return 0; +} + +DEFINE_SHOW_ATTRIBUTE(csi2_regs); + +static int csi2_errors_show(struct seq_file *s, void *data) +{ + struct csi2_device *csi2 = s->private; + unsigned long flags; + u32 discards_table[DISCARDS_TABLE_NUM_VCS][DISCARDS_TABLE_NUM_ENTRIES]; + u32 discards_dt_table[DISCARDS_TABLE_NUM_ENTRIES]; + u32 overflows; + + spin_lock_irqsave(&csi2->errors_lock, flags); + + memcpy(discards_table, csi2->discards_table, sizeof(discards_table)); + memcpy(discards_dt_table, csi2->discards_dt_table, + sizeof(discards_dt_table)); + overflows = csi2->overflows; + + csi2->overflows = 0; + memset(csi2->discards_table, 0, sizeof(discards_table)); + memset(csi2->discards_dt_table, 0, sizeof(discards_dt_table)); + + spin_unlock_irqrestore(&csi2->errors_lock, flags); + + seq_printf(s, "Overflows %u\n", overflows); + seq_puts(s, "Discards:\n"); + seq_puts(s, "VC OVLF LEN UNMATCHED INACTIVE\n"); + + for (unsigned int vc = 0; vc < DISCARDS_TABLE_NUM_VCS; ++vc) { + seq_printf(s, "%u %10u %10u %10u %10u\n", vc, + discards_table[vc][DISCARDS_TABLE_OVERFLOW], + discards_table[vc][DISCARDS_TABLE_LENGTH_LIMIT], + discards_table[vc][DISCARDS_TABLE_UNMATCHED], + discards_table[vc][DISCARDS_TABLE_INACTIVE]); + } + + seq_printf(s, "Last DT %10u %10u %10u %10u\n", + discards_dt_table[DISCARDS_TABLE_OVERFLOW], + discards_dt_table[DISCARDS_TABLE_LENGTH_LIMIT], + discards_dt_table[DISCARDS_TABLE_UNMATCHED], + discards_dt_table[DISCARDS_TABLE_INACTIVE]); + + return 0; +} + +DEFINE_SHOW_ATTRIBUTE(csi2_errors); + +static void csi2_isr_handle_errors(struct csi2_device *csi2, u32 status) +{ + spin_lock(&csi2->errors_lock); + + if (status & IRQ_OVERFLOW) + csi2->overflows++; + + for (unsigned int i = 0; i < DISCARDS_TABLE_NUM_ENTRIES; ++i) { + static const u32 discard_bits[] = { + IRQ_DISCARD_OVERFLOW, + IRQ_DISCARD_LEN_LIMIT, + IRQ_DISCARD_UNMATCHED, + IRQ_DISCARD_INACTIVE, + }; + static const u8 discard_regs[] = { + CSI2_DISCARDS_OVERFLOW, + CSI2_DISCARDS_LEN_LIMIT, + CSI2_DISCARDS_UNMATCHED, + CSI2_DISCARDS_INACTIVE, + }; + u32 amount; + u8 dt, vc; + u32 v; + + if (!(status & discard_bits[i])) + continue; + + v = csi2_reg_read(csi2, discard_regs[i]); + csi2_reg_write(csi2, discard_regs[i], 0); + + amount = (v & CSI2_DISCARDS_AMOUNT_MASK) >> + CSI2_DISCARDS_AMOUNT_SHIFT; + dt = (v & CSI2_DISCARDS_DT_MASK) >> CSI2_DISCARDS_DT_SHIFT; + vc = (v & CSI2_DISCARDS_VC_MASK) >> CSI2_DISCARDS_VC_SHIFT; + + csi2->discards_table[vc][i] += amount; + csi2->discards_dt_table[i] = dt; + } + + spin_unlock(&csi2->errors_lock); +} + +void csi2_isr(struct csi2_device *csi2, bool *sof, bool *eof) +{ + unsigned int i; + u32 status; + + status = csi2_reg_read(csi2, CSI2_STATUS); + csi2_dbg_verbose("ISR: STA: 0x%x\n", status); + + /* Write value back to clear the interrupts */ + csi2_reg_write(csi2, CSI2_STATUS, status); + + for (i = 0; i < CSI2_NUM_CHANNELS; i++) { + u32 dbg; + + if ((status & IRQ_CH_MASK(i)) == 0) + continue; + + dbg = csi2_reg_read(csi2, CSI2_CH_DEBUG(i)); + + csi2_dbg_verbose("ISR: [%u], %s%s%s%s%s frame: %u line: %u\n", + i, (status & IRQ_FS(i)) ? "FS " : "", + (status & IRQ_FE(i)) ? "FE " : "", + (status & IRQ_FE_ACK(i)) ? "FE_ACK " : "", + (status & IRQ_LE(i)) ? "LE " : "", + (status & IRQ_LE_ACK(i)) ? "LE_ACK " : "", + dbg >> 16, + csi2->num_lines[i] ? + ((dbg & 0xffff) % csi2->num_lines[i]) : + 0); + + sof[i] = !!(status & IRQ_FS(i)); + eof[i] = !!(status & IRQ_FE_ACK(i)); + } + + if (csi2_track_errors) + csi2_isr_handle_errors(csi2, status); +} + +void csi2_set_buffer(struct csi2_device *csi2, unsigned int channel, + dma_addr_t dmaaddr, unsigned int stride, unsigned int size) +{ + u64 addr = dmaaddr; + /* + * ADDRESS0 must be written last as it triggers the double buffering + * mechanism for all buffer registers within the hardware. + */ + addr >>= 4; + csi2_reg_write(csi2, CSI2_CH_LENGTH(channel), size >> 4); + csi2_reg_write(csi2, CSI2_CH_STRIDE(channel), stride >> 4); + csi2_reg_write(csi2, CSI2_CH_ADDR1(channel), addr >> 32); + csi2_reg_write(csi2, CSI2_CH_ADDR0(channel), addr & 0xffffffff); +} + +void csi2_set_compression(struct csi2_device *csi2, unsigned int channel, + enum csi2_compression_mode mode, unsigned int shift, + unsigned int offset) +{ + u32 compression = 0; + + set_field(&compression, COMP_OFFSET_MASK, offset); + set_field(&compression, COMP_SHIFT_MASK, shift); + set_field(&compression, COMP_MODE_MASK, mode); + csi2_reg_write(csi2, CSI2_CH_COMP_CTRL(channel), compression); +} + +static int csi2_get_vc_dt_fallback(struct csi2_device *csi2, + unsigned int channel, u8 *vc, u8 *dt) +{ + struct v4l2_subdev *sd = &csi2->sd; + struct v4l2_subdev_state *state; + struct v4l2_mbus_framefmt *fmt; + const struct cfe_fmt *cfe_fmt; + + state = v4l2_subdev_get_locked_active_state(sd); + + /* Without Streams API, the channel number matches the sink pad */ + fmt = v4l2_subdev_state_get_format(state, channel); + if (!fmt) + return -EINVAL; + + cfe_fmt = find_format_by_code(fmt->code); + if (!cfe_fmt) + return -EINVAL; + + *vc = 0; + *dt = cfe_fmt->csi_dt; + + return 0; +} + +static int csi2_get_vc_dt(struct csi2_device *csi2, unsigned int channel, + u8 *vc, u8 *dt) +{ + struct v4l2_mbus_frame_desc remote_desc; + const struct media_pad *remote_pad; + struct v4l2_subdev *source_sd; + int ret; + + /* Without Streams API, the channel number matches the sink pad */ + remote_pad = media_pad_remote_pad_first(&csi2->pad[channel]); + if (!remote_pad) + return -EPIPE; + + source_sd = media_entity_to_v4l2_subdev(remote_pad->entity); + + ret = v4l2_subdev_call(source_sd, pad, get_frame_desc, + remote_pad->index, &remote_desc); + if (ret == -ENOIOCTLCMD) { + csi2_dbg("source does not support get_frame_desc, use fallback\n"); + return csi2_get_vc_dt_fallback(csi2, channel, vc, dt); + } else if (ret) { + csi2_err("Failed to get frame descriptor\n"); + return ret; + } + + if (remote_desc.type != V4L2_MBUS_FRAME_DESC_TYPE_CSI2) { + csi2_err("Frame descriptor does not describe CSI-2 link"); + return -EINVAL; + } + + if (remote_desc.num_entries != 1) { + csi2_err("Frame descriptor does not have a single entry"); + return -EINVAL; + } + + *vc = remote_desc.entry[0].bus.csi2.vc; + *dt = remote_desc.entry[0].bus.csi2.dt; + + return 0; +} + +void csi2_start_channel(struct csi2_device *csi2, unsigned int channel, + enum csi2_mode mode, bool auto_arm, + bool pack_bytes, unsigned int width, + unsigned int height) +{ + u32 ctrl; + int ret; + u8 vc, dt; + + ret = csi2_get_vc_dt(csi2, channel, &vc, &dt); + if (ret) + return; + + csi2_dbg("%s [%u]\n", __func__, channel); + + csi2_reg_write(csi2, CSI2_CH_CTRL(channel), 0); + csi2_reg_write(csi2, CSI2_CH_DEBUG(channel), 0); + csi2_reg_write(csi2, CSI2_STATUS, IRQ_CH_MASK(channel)); + + /* Enable channel and FS/FE interrupts. */ + ctrl = DMA_EN | IRQ_EN_FS | IRQ_EN_FE_ACK | PACK_LINE; + /* PACK_BYTES ensures no striding for embedded data. */ + if (pack_bytes) + ctrl |= PACK_BYTES; + + if (auto_arm) + ctrl |= AUTO_ARM; + + if (width && height) { + set_field(&ctrl, mode, CH_MODE_MASK); + csi2_reg_write(csi2, CSI2_CH_FRAME_SIZE(channel), + (height << 16) | width); + } else { + set_field(&ctrl, 0x0, CH_MODE_MASK); + csi2_reg_write(csi2, CSI2_CH_FRAME_SIZE(channel), 0); + } + + set_field(&ctrl, vc, VC_MASK); + set_field(&ctrl, dt, DT_MASK); + csi2_reg_write(csi2, CSI2_CH_CTRL(channel), ctrl); + csi2->num_lines[channel] = height; +} + +void csi2_stop_channel(struct csi2_device *csi2, unsigned int channel) +{ + csi2_dbg("%s [%u]\n", __func__, channel); + + /* Channel disable. Use FORCE to allow stopping mid-frame. */ + csi2_reg_write(csi2, CSI2_CH_CTRL(channel), FORCE); + /* Latch the above change by writing to the ADDR0 register. */ + csi2_reg_write(csi2, CSI2_CH_ADDR0(channel), 0); + /* Write this again, the HW needs it! */ + csi2_reg_write(csi2, CSI2_CH_ADDR0(channel), 0); +} + +void csi2_open_rx(struct csi2_device *csi2) +{ + csi2_reg_write(csi2, CSI2_IRQ_MASK, + csi2_track_errors ? CSI2_IRQ_MASK_IRQ_ALL : 0); + + dphy_start(&csi2->dphy); + + csi2_reg_write(csi2, CSI2_CTRL, + csi2->multipacket_line ? 0 : EOP_IS_EOL); +} + +void csi2_close_rx(struct csi2_device *csi2) +{ + dphy_stop(&csi2->dphy); + + csi2_reg_write(csi2, CSI2_IRQ_MASK, 0); +} + +static int csi2_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_mbus_framefmt *fmt; + + for (unsigned int i = 0; i < CSI2_NUM_CHANNELS; ++i) { + const struct v4l2_mbus_framefmt *def_fmt; + + /* CSI2_CH1_EMBEDDED */ + if (i == 1) + def_fmt = &cfe_default_meta_format; + else + def_fmt = &cfe_default_format; + + fmt = v4l2_subdev_state_get_format(state, i); + *fmt = *def_fmt; + + fmt = v4l2_subdev_state_get_format(state, i + CSI2_NUM_CHANNELS); + *fmt = *def_fmt; + } + + return 0; +} + +static int csi2_pad_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + if (format->pad < CSI2_NUM_CHANNELS) { + /* + * Store the sink pad format and propagate it to the source pad. + */ + + struct v4l2_mbus_framefmt *fmt; + + fmt = v4l2_subdev_state_get_format(state, format->pad); + if (!fmt) + return -EINVAL; + + *fmt = format->format; + + fmt = v4l2_subdev_state_get_format(state, + format->pad + CSI2_NUM_CHANNELS); + if (!fmt) + return -EINVAL; + + format->format.field = V4L2_FIELD_NONE; + + *fmt = format->format; + } else { + /* + * Only allow changing the source pad mbus code. + */ + + struct v4l2_mbus_framefmt *sink_fmt, *source_fmt; + u32 sink_code; + u32 code; + + sink_fmt = v4l2_subdev_state_get_format(state, + format->pad - CSI2_NUM_CHANNELS); + if (!sink_fmt) + return -EINVAL; + + source_fmt = v4l2_subdev_state_get_format(state, format->pad); + if (!source_fmt) + return -EINVAL; + + sink_code = sink_fmt->code; + code = format->format.code; + + /* + * If the source code from the user does not match the code in + * the sink pad, check that the source code matches either the + * 16-bit version or the compressed version of the sink code. + */ + + if (code != sink_code && + (code == cfe_find_16bit_code(sink_code) || + code == cfe_find_compressed_code(sink_code))) + source_fmt->code = code; + + format->format.code = source_fmt->code; + } + + return 0; +} + +static const struct v4l2_subdev_internal_ops csi2_internal_ops = { + .init_state = csi2_init_cfg, +}; + +static const struct v4l2_subdev_pad_ops csi2_subdev_pad_ops = { + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = csi2_pad_set_fmt, + .link_validate = v4l2_subdev_link_validate_default, +}; + +static const struct media_entity_operations csi2_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static const struct v4l2_subdev_ops csi2_subdev_ops = { + .pad = &csi2_subdev_pad_ops, +}; + +int csi2_init(struct csi2_device *csi2, struct dentry *debugfs) +{ + unsigned int i, ret; + + spin_lock_init(&csi2->errors_lock); + + csi2->dphy.dev = csi2->v4l2_dev->dev; + dphy_probe(&csi2->dphy); + + debugfs_create_file("csi2_regs", 0444, debugfs, csi2, &csi2_regs_fops); + + if (csi2_track_errors) + debugfs_create_file("csi2_errors", 0444, debugfs, csi2, + &csi2_errors_fops); + + for (i = 0; i < CSI2_NUM_CHANNELS * 2; i++) + csi2->pad[i].flags = i < CSI2_NUM_CHANNELS ? + MEDIA_PAD_FL_SINK : MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&csi2->sd.entity, ARRAY_SIZE(csi2->pad), + csi2->pad); + if (ret) + return ret; + + /* Initialize subdev */ + v4l2_subdev_init(&csi2->sd, &csi2_subdev_ops); + csi2->sd.internal_ops = &csi2_internal_ops; + csi2->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + csi2->sd.entity.ops = &csi2_entity_ops; + csi2->sd.flags = V4L2_SUBDEV_FL_HAS_DEVNODE; + csi2->sd.owner = THIS_MODULE; + snprintf(csi2->sd.name, sizeof(csi2->sd.name), "csi2"); + + ret = v4l2_subdev_init_finalize(&csi2->sd); + if (ret) + goto err_entity_cleanup; + + ret = v4l2_device_register_subdev(csi2->v4l2_dev, &csi2->sd); + if (ret) { + csi2_err("Failed register csi2 subdev (%d)\n", ret); + goto err_subdev_cleanup; + } + + return 0; + +err_subdev_cleanup: + v4l2_subdev_cleanup(&csi2->sd); +err_entity_cleanup: + media_entity_cleanup(&csi2->sd.entity); + + return ret; +} + +void csi2_uninit(struct csi2_device *csi2) +{ + v4l2_device_unregister_subdev(&csi2->sd); + v4l2_subdev_cleanup(&csi2->sd); + media_entity_cleanup(&csi2->sd.entity); +} diff --git a/drivers/media/platform/raspberrypi/rp1_cfe/csi2.h b/drivers/media/platform/raspberrypi/rp1_cfe/csi2.h new file mode 100644 index 00000000000000..4fff16ee940788 --- /dev/null +++ b/drivers/media/platform/raspberrypi/rp1_cfe/csi2.h @@ -0,0 +1,90 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * RP1 CSI-2 driver. + * Copyright (c) 2021 Raspberry Pi Ltd. + * + */ +#ifndef _RP1_CSI2_ +#define _RP1_CSI2_ + +#include <linux/debugfs.h> +#include <linux/io.h> +#include <linux/types.h> +#include <media/v4l2-device.h> +#include <media/v4l2-subdev.h> + +#include "dphy.h" + +#define CSI2_NUM_CHANNELS 4 + +#define DISCARDS_TABLE_NUM_VCS 4 + +enum csi2_mode { + CSI2_MODE_NORMAL = 0, + CSI2_MODE_REMAP = 1, + CSI2_MODE_COMPRESSED = 2, + CSI2_MODE_FE_STREAMING = 3, +}; + +enum csi2_compression_mode { + CSI2_COMPRESSION_DELTA = 1, + CSI2_COMPRESSION_SIMPLE = 2, + CSI2_COMPRESSION_COMBINED = 3, +}; + +struct csi2_cfg { + u16 width; + u16 height; + u32 stride; + u32 buffer_size; +}; + +enum discards_table_index { + DISCARDS_TABLE_OVERFLOW = 0, + DISCARDS_TABLE_LENGTH_LIMIT, + DISCARDS_TABLE_UNMATCHED, + DISCARDS_TABLE_INACTIVE, + DISCARDS_TABLE_NUM_ENTRIES, +}; + +struct csi2_device { + /* Parent V4l2 device */ + struct v4l2_device *v4l2_dev; + + void __iomem *base; + + struct dphy_data dphy; + + enum v4l2_mbus_type bus_type; + unsigned int bus_flags; + bool multipacket_line; + unsigned int num_lines[CSI2_NUM_CHANNELS]; + + struct media_pad pad[CSI2_NUM_CHANNELS * 2]; + struct v4l2_subdev sd; + + /* lock for csi2 errors counters */ + spinlock_t errors_lock; + u32 overflows; + u32 discards_table[DISCARDS_TABLE_NUM_VCS][DISCARDS_TABLE_NUM_ENTRIES]; + u32 discards_dt_table[DISCARDS_TABLE_NUM_ENTRIES]; +}; + +void csi2_isr(struct csi2_device *csi2, bool *sof, bool *eof); +void csi2_set_buffer(struct csi2_device *csi2, unsigned int channel, + dma_addr_t dmaaddr, unsigned int stride, + unsigned int size); +void csi2_set_compression(struct csi2_device *csi2, unsigned int channel, + enum csi2_compression_mode mode, unsigned int shift, + unsigned int offset); +void csi2_start_channel(struct csi2_device *csi2, unsigned int channel, + enum csi2_mode mode, bool auto_arm, + bool pack_bytes, unsigned int width, + unsigned int height); +void csi2_stop_channel(struct csi2_device *csi2, unsigned int channel); +void csi2_open_rx(struct csi2_device *csi2); +void csi2_close_rx(struct csi2_device *csi2); +int csi2_init(struct csi2_device *csi2, struct dentry *debugfs); +void csi2_uninit(struct csi2_device *csi2); + +#endif diff --git a/drivers/media/platform/raspberrypi/rp1_cfe/dphy.c b/drivers/media/platform/raspberrypi/rp1_cfe/dphy.c new file mode 100644 index 00000000000000..28ea3215fff5c1 --- /dev/null +++ b/drivers/media/platform/raspberrypi/rp1_cfe/dphy.c @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * RP1 CSI-2 Driver + * + * Copyright (C) 2021 - Raspberry Pi Ltd. + * + */ + +#include <linux/delay.h> +#include <linux/dev_printk.h> +#include <linux/pm_runtime.h> + +#include "dphy.h" + +#define dphy_dbg(fmt, arg...) dev_dbg(dphy->dev, fmt, ##arg) +#define dphy_info(fmt, arg...) dev_info(dphy->dev, fmt, ##arg) +#define dphy_err(fmt, arg...) dev_err(dphy->dev, fmt, ##arg) + +/* DW dphy Host registers */ +#define VERSION 0x000 +#define N_LANES 0x004 +#define RESETN 0x008 +#define PHY_SHUTDOWNZ 0x040 +#define PHY_RSTZ 0x044 +#define PHY_RX 0x048 +#define PHY_STOPSTATE 0x04c +#define PHY_TST_CTRL0 0x050 +#define PHY_TST_CTRL1 0x054 +#define PHY2_TST_CTRL0 0x058 +#define PHY2_TST_CTRL1 0x05c + +/* DW dphy Host Transactions */ +#define DPHY_HS_RX_CTRL_LANE0_OFFSET 0x44 +#define DPHY_PLL_INPUT_DIV_OFFSET 0x17 +#define DPHY_PLL_LOOP_DIV_OFFSET 0x18 +#define DPHY_PLL_DIV_CTRL_OFFSET 0x19 + +static u32 dw_csi2_host_read(struct dphy_data *dphy, u32 offset) +{ + return readl(dphy->base + offset); +} + +static void dw_csi2_host_write(struct dphy_data *dphy, u32 offset, u32 data) +{ + writel(data, dphy->base + offset); +} + +static void set_tstclr(struct dphy_data *dphy, u32 val) +{ + u32 ctrl0 = dw_csi2_host_read(dphy, PHY_TST_CTRL0); + + dw_csi2_host_write(dphy, PHY_TST_CTRL0, (ctrl0 & ~1) | val); +} + +static void set_tstclk(struct dphy_data *dphy, u32 val) +{ + u32 ctrl0 = dw_csi2_host_read(dphy, PHY_TST_CTRL0); + + dw_csi2_host_write(dphy, PHY_TST_CTRL0, (ctrl0 & ~2) | (val << 1)); +} + +static uint8_t get_tstdout(struct dphy_data *dphy) +{ + u32 ctrl1 = dw_csi2_host_read(dphy, PHY_TST_CTRL1); + + return ((ctrl1 >> 8) & 0xff); +} + +static void set_testen(struct dphy_data *dphy, u32 val) +{ + u32 ctrl1 = dw_csi2_host_read(dphy, PHY_TST_CTRL1); + + dw_csi2_host_write(dphy, PHY_TST_CTRL1, + (ctrl1 & ~(1 << 16)) | (val << 16)); +} + +static void set_testdin(struct dphy_data *dphy, u32 val) +{ + u32 ctrl1 = dw_csi2_host_read(dphy, PHY_TST_CTRL1); + + dw_csi2_host_write(dphy, PHY_TST_CTRL1, (ctrl1 & ~0xff) | val); +} + +static uint8_t dphy_transaction(struct dphy_data *dphy, u8 test_code, + uint8_t test_data) +{ + /* See page 101 of the MIPI DPHY databook. */ + set_tstclk(dphy, 1); + set_testen(dphy, 0); + set_testdin(dphy, test_code); + set_testen(dphy, 1); + set_tstclk(dphy, 0); + set_testen(dphy, 0); + set_testdin(dphy, test_data); + set_tstclk(dphy, 1); + return get_tstdout(dphy); +} + +static void dphy_set_hsfreqrange(struct dphy_data *dphy, uint32_t mbps) +{ + /* See Table 5-1 on page 65 of dphy databook */ + static const u16 hsfreqrange_table[][2] = { + { 89, 0b000000 }, { 99, 0b010000 }, { 109, 0b100000 }, + { 129, 0b000001 }, { 139, 0b010001 }, { 149, 0b100001 }, + { 169, 0b000010 }, { 179, 0b010010 }, { 199, 0b100010 }, + { 219, 0b000011 }, { 239, 0b010011 }, { 249, 0b100011 }, + { 269, 0b000100 }, { 299, 0b010100 }, { 329, 0b000101 }, + { 359, 0b010101 }, { 399, 0b100101 }, { 449, 0b000110 }, + { 499, 0b010110 }, { 549, 0b000111 }, { 599, 0b010111 }, + { 649, 0b001000 }, { 699, 0b011000 }, { 749, 0b001001 }, + { 799, 0b011001 }, { 849, 0b101001 }, { 899, 0b111001 }, + { 949, 0b001010 }, { 999, 0b011010 }, { 1049, 0b101010 }, + { 1099, 0b111010 }, { 1149, 0b001011 }, { 1199, 0b011011 }, + { 1249, 0b101011 }, { 1299, 0b111011 }, { 1349, 0b001100 }, + { 1399, 0b011100 }, { 1449, 0b101100 }, { 1500, 0b111100 }, + }; + unsigned int i; + + if (mbps < 80 || mbps > 1500) + dphy_err("DPHY: Datarate %u Mbps out of range\n", mbps); + + for (i = 0; i < ARRAY_SIZE(hsfreqrange_table) - 1; i++) { + if (mbps <= hsfreqrange_table[i][0]) + break; + } + + dphy_transaction(dphy, DPHY_HS_RX_CTRL_LANE0_OFFSET, + hsfreqrange_table[i][1] << 1); +} + +static void dphy_init(struct dphy_data *dphy) +{ + dw_csi2_host_write(dphy, PHY_RSTZ, 0); + dw_csi2_host_write(dphy, PHY_SHUTDOWNZ, 0); + set_tstclk(dphy, 1); + set_testen(dphy, 0); + set_tstclr(dphy, 1); + usleep_range(15, 20); + set_tstclr(dphy, 0); + usleep_range(15, 20); + + dphy_set_hsfreqrange(dphy, dphy->dphy_rate); + + usleep_range(5, 10); + dw_csi2_host_write(dphy, PHY_SHUTDOWNZ, 1); + usleep_range(5, 10); + dw_csi2_host_write(dphy, PHY_RSTZ, 1); +} + +void dphy_start(struct dphy_data *dphy) +{ + dw_csi2_host_write(dphy, N_LANES, (dphy->active_lanes - 1)); + dphy_init(dphy); + dw_csi2_host_write(dphy, RESETN, 0xffffffff); + usleep_range(10, 50); +} + +void dphy_stop(struct dphy_data *dphy) +{ + /* Set only one lane (lane 0) as active (ON) */ + dw_csi2_host_write(dphy, N_LANES, 0); + dw_csi2_host_write(dphy, RESETN, 0); +} + +void dphy_probe(struct dphy_data *dphy) +{ + u32 host_ver; + u8 host_ver_major, host_ver_minor; + + host_ver = dw_csi2_host_read(dphy, VERSION); + host_ver_major = (u8)((host_ver >> 24) - '0'); + host_ver_minor = (u8)((host_ver >> 16) - '0'); + host_ver_minor = host_ver_minor * 10; + host_ver_minor += (u8)((host_ver >> 8) - '0'); + + dphy_info("DW dphy Host HW v%u.%u\n", host_ver_major, host_ver_minor); +} diff --git a/drivers/media/platform/raspberrypi/rp1_cfe/dphy.h b/drivers/media/platform/raspberrypi/rp1_cfe/dphy.h new file mode 100644 index 00000000000000..9d7a80b3f68402 --- /dev/null +++ b/drivers/media/platform/raspberrypi/rp1_cfe/dphy.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2021 Raspberry Pi Ltd. + * + */ + +#ifndef _RP1_DPHY_ +#define _RP1_DPHY_ + +#include <linux/io.h> +#include <linux/types.h> + +struct dphy_data { + struct device *dev; + + void __iomem *base; + + u32 dphy_rate; + u32 max_lanes; + u32 active_lanes; +}; + +void dphy_probe(struct dphy_data *dphy); +void dphy_start(struct dphy_data *dphy); +void dphy_stop(struct dphy_data *dphy); + +#endif diff --git a/drivers/media/platform/raspberrypi/rp1_cfe/pisp_common.h b/drivers/media/platform/raspberrypi/rp1_cfe/pisp_common.h new file mode 100644 index 00000000000000..e91aa2ed365919 --- /dev/null +++ b/drivers/media/platform/raspberrypi/rp1_cfe/pisp_common.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ +/* + * RP1 PiSP common definitions. + * + * Copyright (C) 2021 - Raspberry Pi Ltd. + * + */ +#ifndef _PISP_COMMON_H_ +#define _PISP_COMMON_H_ + +#include "pisp_types.h" + +struct pisp_bla_config { + u16 black_level_r; + u16 black_level_gr; + u16 black_level_gb; + u16 black_level_b; + u16 output_black_level; + u8 pad[2]; +}; + +struct pisp_wbg_config { + u16 gain_r; + u16 gain_g; + u16 gain_b; + u8 pad[2]; +}; + +struct pisp_compress_config { + /* value subtracted from incoming data */ + u16 offset; + u8 pad; + /* 1 => Companding; 2 => Delta (recommended); 3 => Combined (for HDR) */ + u8 mode; +}; + +struct pisp_decompress_config { + /* value added to reconstructed data */ + u16 offset; + u8 pad; + /* 1 => Companding; 2 => Delta (recommended); 3 => Combined (for HDR) */ + u8 mode; +}; + +enum pisp_axi_flags { + /* + * round down bursts to end at a 32-byte boundary, to align following + * bursts + */ + PISP_AXI_FLAG_ALIGN = 128, + /* for FE writer: force WSTRB high, to pad output to 16-byte boundary */ + PISP_AXI_FLAG_PAD = 64, + /* for FE writer: Use Output FIFO level to trigger "panic" */ + PISP_AXI_FLAG_PANIC = 32, +}; + +struct pisp_axi_config { + /* + * burst length minus one, which must be in the range 0:15; OR'd with + * flags + */ + u8 maxlen_flags; + /* { prot[2:0], cache[3:0] } fields, echoed on AXI bus */ + u8 cache_prot; + /* QoS field(s) (4x4 bits for FE writer; 4 bits for other masters) */ + u16 qos; +}; + +#endif /* _PISP_COMMON_H_ */ diff --git a/drivers/media/platform/raspberrypi/rp1_cfe/pisp_fe.c b/drivers/media/platform/raspberrypi/rp1_cfe/pisp_fe.c new file mode 100644 index 00000000000000..07bc550deea742 --- /dev/null +++ b/drivers/media/platform/raspberrypi/rp1_cfe/pisp_fe.c @@ -0,0 +1,569 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * PiSP Front End driver. + * Copyright (c) 2021 Raspberry Pi Ltd. + * + */ + +#include <linux/bitops.h> +#include <linux/delay.h> +#include <linux/moduleparam.h> +#include <linux/pm_runtime.h> +#include <linux/seq_file.h> + +#include <media/videobuf2-dma-contig.h> + +#include "pisp_fe.h" +#include "cfe.h" + +#define FE_VERSION 0x000 +#define FE_CONTROL 0x004 +#define FE_STATUS 0x008 +#define FE_FRAME_STATUS 0x00c +#define FE_ERROR_STATUS 0x010 +#define FE_OUTPUT_STATUS 0x014 +#define FE_INT_EN 0x018 +#define FE_INT_STATUS 0x01c + +/* CONTROL */ +#define FE_CONTROL_QUEUE BIT(0) +#define FE_CONTROL_ABORT BIT(1) +#define FE_CONTROL_RESET BIT(2) +#define FE_CONTROL_LATCH_REGS BIT(3) + +/* INT_EN / INT_STATUS */ +#define FE_INT_EOF BIT(0) +#define FE_INT_SOF BIT(1) +#define FE_INT_LINES0 BIT(8) +#define FE_INT_LINES1 BIT(9) +#define FE_INT_STATS BIT(16) +#define FE_INT_QREADY BIT(24) + +/* STATUS */ +#define FE_STATUS_QUEUED BIT(0) +#define FE_STATUS_WAITING BIT(1) +#define FE_STATUS_ACTIVE BIT(2) + +#define PISP_FE_CONFIG_BASE_OFFSET 0x0040 + +#define PISP_FE_ENABLE_STATS_CLUSTER \ + (PISP_FE_ENABLE_STATS_CROP | PISP_FE_ENABLE_DECIMATE | \ + PISP_FE_ENABLE_BLC | PISP_FE_ENABLE_CDAF_STATS | \ + PISP_FE_ENABLE_AWB_STATS | PISP_FE_ENABLE_RGBY | \ + PISP_FE_ENABLE_LSC | PISP_FE_ENABLE_AGC_STATS) + +#define PISP_FE_ENABLE_OUTPUT_CLUSTER(i) \ + ((PISP_FE_ENABLE_CROP0 | PISP_FE_ENABLE_DOWNSCALE0 | \ + PISP_FE_ENABLE_COMPRESS0 | PISP_FE_ENABLE_OUTPUT0) << (4 * (i))) + +struct pisp_fe_config_param { + u32 dirty_flags; + u32 dirty_flags_extra; + size_t offset; + size_t size; +}; + +static const struct pisp_fe_config_param pisp_fe_config_map[] = { + /* *_dirty_flag_extra types */ + { 0, PISP_FE_DIRTY_GLOBAL, offsetof(struct pisp_fe_config, global), + sizeof(struct pisp_fe_global_config) }, + { 0, PISP_FE_DIRTY_FLOATING, offsetof(struct pisp_fe_config, floating_stats), + sizeof(struct pisp_fe_floating_stats_config) }, + { 0, PISP_FE_DIRTY_OUTPUT_AXI, offsetof(struct pisp_fe_config, output_axi), + sizeof(struct pisp_fe_output_axi_config) }, + /* *_dirty_flag types */ + { PISP_FE_ENABLE_INPUT, 0, offsetof(struct pisp_fe_config, input), + sizeof(struct pisp_fe_input_config) }, + { PISP_FE_ENABLE_DECOMPRESS, 0, offsetof(struct pisp_fe_config, decompress), + sizeof(struct pisp_decompress_config) }, + { PISP_FE_ENABLE_DECOMPAND, 0, offsetof(struct pisp_fe_config, decompand), + sizeof(struct pisp_fe_decompand_config) }, + { PISP_FE_ENABLE_BLA, 0, offsetof(struct pisp_fe_config, bla), + sizeof(struct pisp_bla_config) }, + { PISP_FE_ENABLE_DPC, 0, offsetof(struct pisp_fe_config, dpc), + sizeof(struct pisp_fe_dpc_config) }, + { PISP_FE_ENABLE_STATS_CROP, 0, offsetof(struct pisp_fe_config, stats_crop), + sizeof(struct pisp_fe_crop_config) }, + { PISP_FE_ENABLE_BLC, 0, offsetof(struct pisp_fe_config, blc), + sizeof(struct pisp_bla_config) }, + { PISP_FE_ENABLE_CDAF_STATS, 0, offsetof(struct pisp_fe_config, cdaf_stats), + sizeof(struct pisp_fe_cdaf_stats_config) }, + { PISP_FE_ENABLE_AWB_STATS, 0, offsetof(struct pisp_fe_config, awb_stats), + sizeof(struct pisp_fe_awb_stats_config) }, + { PISP_FE_ENABLE_RGBY, 0, offsetof(struct pisp_fe_config, rgby), + sizeof(struct pisp_fe_rgby_config) }, + { PISP_FE_ENABLE_LSC, 0, offsetof(struct pisp_fe_config, lsc), + sizeof(struct pisp_fe_lsc_config) }, + { PISP_FE_ENABLE_AGC_STATS, 0, offsetof(struct pisp_fe_config, agc_stats), + sizeof(struct pisp_agc_statistics) }, + { PISP_FE_ENABLE_CROP0, 0, offsetof(struct pisp_fe_config, ch[0].crop), + sizeof(struct pisp_fe_crop_config) }, + { PISP_FE_ENABLE_DOWNSCALE0, 0, offsetof(struct pisp_fe_config, ch[0].downscale), + sizeof(struct pisp_fe_downscale_config) }, + { PISP_FE_ENABLE_COMPRESS0, 0, offsetof(struct pisp_fe_config, ch[0].compress), + sizeof(struct pisp_compress_config) }, + { PISP_FE_ENABLE_OUTPUT0, 0, offsetof(struct pisp_fe_config, ch[0].output), + sizeof(struct pisp_fe_output_config) }, + { PISP_FE_ENABLE_CROP1, 0, offsetof(struct pisp_fe_config, ch[1].crop), + sizeof(struct pisp_fe_crop_config) }, + { PISP_FE_ENABLE_DOWNSCALE1, 0, offsetof(struct pisp_fe_config, ch[1].downscale), + sizeof(struct pisp_fe_downscale_config) }, + { PISP_FE_ENABLE_COMPRESS1, 0, offsetof(struct pisp_fe_config, ch[1].compress), + sizeof(struct pisp_compress_config) }, + { PISP_FE_ENABLE_OUTPUT1, 0, offsetof(struct pisp_fe_config, ch[1].output), + sizeof(struct pisp_fe_output_config) }, +}; + +#define pisp_fe_dbg_verbose(fmt, arg...) \ + do { \ + if (cfe_debug_verbose) \ + dev_dbg(fe->v4l2_dev->dev, fmt, ##arg); \ + } while (0) +#define pisp_fe_dbg(fmt, arg...) dev_dbg(fe->v4l2_dev->dev, fmt, ##arg) +#define pisp_fe_info(fmt, arg...) dev_info(fe->v4l2_dev->dev, fmt, ##arg) +#define pisp_fe_err(fmt, arg...) dev_err(fe->v4l2_dev->dev, fmt, ##arg) + +static inline u32 pisp_fe_reg_read(struct pisp_fe_device *fe, u32 offset) +{ + return readl(fe->base + offset); +} + +static inline void pisp_fe_reg_write(struct pisp_fe_device *fe, u32 offset, + u32 val) +{ + writel(val, fe->base + offset); + pisp_fe_dbg_verbose("fe: write 0x%04x -> 0x%03x\n", val, offset); +} + +static inline void pisp_fe_reg_write_relaxed(struct pisp_fe_device *fe, u32 offset, + u32 val) +{ + writel_relaxed(val, fe->base + offset); + pisp_fe_dbg_verbose("fe: write 0x%04x -> 0x%03x\n", val, offset); +} + +static int pisp_regs_show(struct seq_file *s, void *data) +{ + struct pisp_fe_device *fe = s->private; + int ret; + + ret = pm_runtime_resume_and_get(fe->v4l2_dev->dev); + if (ret) + return ret; + + pisp_fe_reg_write(fe, FE_CONTROL, FE_CONTROL_LATCH_REGS); + +#define DUMP(reg) seq_printf(s, #reg " \t0x%08x\n", pisp_fe_reg_read(fe, reg)) + DUMP(FE_VERSION); + DUMP(FE_CONTROL); + DUMP(FE_STATUS); + DUMP(FE_FRAME_STATUS); + DUMP(FE_ERROR_STATUS); + DUMP(FE_OUTPUT_STATUS); + DUMP(FE_INT_EN); + DUMP(FE_INT_STATUS); +#undef DUMP + + pm_runtime_put(fe->v4l2_dev->dev); + + return 0; +} + +DEFINE_SHOW_ATTRIBUTE(pisp_regs); + +static void pisp_config_write(struct pisp_fe_device *fe, + struct pisp_fe_config *config, + unsigned int start_offset, + unsigned int size) +{ + const unsigned int max_offset = + offsetof(struct pisp_fe_config, ch[PISP_FE_NUM_OUTPUTS]); + unsigned int i, end_offset; + u32 *cfg = (u32 *)config; + + start_offset = min(start_offset, max_offset); + end_offset = min(start_offset + size, max_offset); + + cfg += start_offset >> 2; + for (i = start_offset; i < end_offset; i += 4, cfg++) + pisp_fe_reg_write_relaxed(fe, PISP_FE_CONFIG_BASE_OFFSET + i, + *cfg); +} + +void pisp_fe_isr(struct pisp_fe_device *fe, bool *sof, bool *eof) +{ + u32 status, int_status, out_status, frame_status, error_status; + unsigned int i; + + pisp_fe_reg_write(fe, FE_CONTROL, FE_CONTROL_LATCH_REGS); + status = pisp_fe_reg_read(fe, FE_STATUS); + out_status = pisp_fe_reg_read(fe, FE_OUTPUT_STATUS); + frame_status = pisp_fe_reg_read(fe, FE_FRAME_STATUS); + error_status = pisp_fe_reg_read(fe, FE_ERROR_STATUS); + + int_status = pisp_fe_reg_read(fe, FE_INT_STATUS); + pisp_fe_reg_write(fe, FE_INT_STATUS, int_status); + + pisp_fe_dbg_verbose("%s: status 0x%x out 0x%x frame 0x%x error 0x%x int 0x%x\n", + __func__, status, out_status, frame_status, error_status, + int_status); + + /* We do not report interrupts for the input/stream pad. */ + for (i = 0; i < FE_NUM_PADS - 1; i++) { + sof[i] = !!(int_status & FE_INT_SOF); + eof[i] = !!(int_status & FE_INT_EOF); + } +} + +static bool pisp_fe_validate_output(struct pisp_fe_config const *cfg, + unsigned int c, struct v4l2_format const *f) +{ + unsigned int wbytes; + + wbytes = cfg->ch[c].output.format.width; + if (cfg->ch[c].output.format.format & PISP_IMAGE_FORMAT_BPS_MASK) + wbytes *= 2; + + /* Check output image dimensions are nonzero and not too big */ + if (cfg->ch[c].output.format.width < 2 || + cfg->ch[c].output.format.height < 2 || + cfg->ch[c].output.format.height > f->fmt.pix.height || + cfg->ch[c].output.format.stride > f->fmt.pix.bytesperline || + wbytes > f->fmt.pix.bytesperline) + return false; + + /* Check for zero-sized crops, which could cause lockup */ + if ((cfg->global.enables & PISP_FE_ENABLE_CROP(c)) && + ((cfg->ch[c].crop.offset_x >= (cfg->input.format.width & ~1) || + cfg->ch[c].crop.offset_y >= cfg->input.format.height || + cfg->ch[c].crop.width < 2 || + cfg->ch[c].crop.height < 2))) + return false; + + if ((cfg->global.enables & PISP_FE_ENABLE_DOWNSCALE(c)) && + (cfg->ch[c].downscale.output_width < 2 || + cfg->ch[c].downscale.output_height < 2)) + return false; + + return true; +} + +static bool pisp_fe_validate_stats(struct pisp_fe_config const *cfg) +{ + /* Check for zero-sized crop, which could cause lockup */ + return (!(cfg->global.enables & PISP_FE_ENABLE_STATS_CROP) || + (cfg->stats_crop.offset_x < (cfg->input.format.width & ~1) && + cfg->stats_crop.offset_y < cfg->input.format.height && + cfg->stats_crop.width >= 2 && + cfg->stats_crop.height >= 2)); +} + +int pisp_fe_validate_config(struct pisp_fe_device *fe, + struct pisp_fe_config *cfg, + struct v4l2_format const *f0, + struct v4l2_format const *f1) +{ + unsigned int i; + + /* + * Check the input is enabled, streaming and has nonzero size; + * to avoid cases where the hardware might lock up or try to + * read inputs from memory (which this driver doesn't support). + */ + if (!(cfg->global.enables & PISP_FE_ENABLE_INPUT) || + cfg->input.streaming != 1 || cfg->input.format.width < 2 || + cfg->input.format.height < 2) { + pisp_fe_err("%s: Input config not valid", __func__); + return -EINVAL; + } + + for (i = 0; i < PISP_FE_NUM_OUTPUTS; i++) { + if (!(cfg->global.enables & PISP_FE_ENABLE_OUTPUT(i))) { + if (cfg->global.enables & + PISP_FE_ENABLE_OUTPUT_CLUSTER(i)) { + pisp_fe_err("%s: Output %u not valid", + __func__, i); + return -EINVAL; + } + continue; + } + + if (!pisp_fe_validate_output(cfg, i, i ? f1 : f0)) + return -EINVAL; + } + + if ((cfg->global.enables & PISP_FE_ENABLE_STATS_CLUSTER) && + !pisp_fe_validate_stats(cfg)) { + pisp_fe_err("%s: Stats config not valid", __func__); + return -EINVAL; + } + + return 0; +} + +void pisp_fe_submit_job(struct pisp_fe_device *fe, struct vb2_buffer **vb2_bufs, + struct pisp_fe_config *cfg) +{ + unsigned int i; + u64 addr; + u32 status; + + /* + * Check output buffers exist and outputs are correctly configured. + * If valid, set the buffer's DMA address; otherwise disable. + */ + for (i = 0; i < PISP_FE_NUM_OUTPUTS; i++) { + struct vb2_buffer *buf = vb2_bufs[FE_OUTPUT0_PAD + i]; + + if (!(cfg->global.enables & PISP_FE_ENABLE_OUTPUT(i))) + continue; + + addr = vb2_dma_contig_plane_dma_addr(buf, 0); + cfg->output_buffer[i].addr_lo = addr & 0xffffffff; + cfg->output_buffer[i].addr_hi = addr >> 32; + } + + if (vb2_bufs[FE_STATS_PAD]) { + addr = vb2_dma_contig_plane_dma_addr(vb2_bufs[FE_STATS_PAD], 0); + cfg->stats_buffer.addr_lo = addr & 0xffffffff; + cfg->stats_buffer.addr_hi = addr >> 32; + } + + /* Set up ILINES interrupts 3/4 of the way down each output */ + cfg->ch[0].output.ilines = + max(0x80u, (3u * cfg->ch[0].output.format.height) >> 2); + cfg->ch[1].output.ilines = + max(0x80u, (3u * cfg->ch[1].output.format.height) >> 2); + + /* + * The hardware must have consumed the previous config by now. + * This read of status also serves as a memory barrier before the + * sequence of relaxed writes which follow. + */ + status = pisp_fe_reg_read(fe, FE_STATUS); + pisp_fe_dbg_verbose("%s: status = 0x%x\n", __func__, status); + if (WARN_ON(status & FE_STATUS_QUEUED)) + return; + + /* + * Unconditionally write buffers, global and input parameters. + * Write cropping and output parameters whenever they are enabled. + * Selectively write other parameters that have been marked as + * changed through the dirty flags. + */ + pisp_config_write(fe, cfg, 0, + offsetof(struct pisp_fe_config, decompress)); + cfg->dirty_flags_extra &= ~PISP_FE_DIRTY_GLOBAL; + cfg->dirty_flags &= ~PISP_FE_ENABLE_INPUT; + cfg->dirty_flags |= (cfg->global.enables & + (PISP_FE_ENABLE_STATS_CROP | + PISP_FE_ENABLE_OUTPUT_CLUSTER(0) | + PISP_FE_ENABLE_OUTPUT_CLUSTER(1))); + for (i = 0; i < ARRAY_SIZE(pisp_fe_config_map); i++) { + const struct pisp_fe_config_param *p = &pisp_fe_config_map[i]; + + if (cfg->dirty_flags & p->dirty_flags || + cfg->dirty_flags_extra & p->dirty_flags_extra) + pisp_config_write(fe, cfg, p->offset, p->size); + } + + /* This final non-relaxed write serves as a memory barrier */ + pisp_fe_reg_write(fe, FE_CONTROL, FE_CONTROL_QUEUE); +} + +void pisp_fe_start(struct pisp_fe_device *fe) +{ + pisp_fe_reg_write(fe, FE_CONTROL, FE_CONTROL_RESET); + pisp_fe_reg_write(fe, FE_INT_STATUS, ~0); + pisp_fe_reg_write(fe, FE_INT_EN, FE_INT_EOF | FE_INT_SOF | FE_INT_LINES0 | FE_INT_LINES1); + fe->inframe_count = 0; +} + +void pisp_fe_stop(struct pisp_fe_device *fe) +{ + pisp_fe_reg_write(fe, FE_INT_EN, 0); + pisp_fe_reg_write(fe, FE_CONTROL, FE_CONTROL_ABORT); + usleep_range(1000, 2000); + WARN_ON(pisp_fe_reg_read(fe, FE_STATUS)); + pisp_fe_reg_write(fe, FE_INT_STATUS, ~0); +} + +static int pisp_fe_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_mbus_framefmt *fmt; + + fmt = v4l2_subdev_state_get_format(state, FE_STREAM_PAD); + *fmt = cfe_default_format; + fmt->code = MEDIA_BUS_FMT_SRGGB16_1X16; + + fmt = v4l2_subdev_state_get_format(state, FE_CONFIG_PAD); + *fmt = cfe_default_meta_format; + fmt->code = MEDIA_BUS_FMT_FIXED; + + fmt = v4l2_subdev_state_get_format(state, FE_OUTPUT0_PAD); + *fmt = cfe_default_format; + fmt->code = MEDIA_BUS_FMT_SRGGB16_1X16; + + fmt = v4l2_subdev_state_get_format(state, FE_OUTPUT1_PAD); + *fmt = cfe_default_format; + fmt->code = MEDIA_BUS_FMT_SRGGB16_1X16; + + fmt = v4l2_subdev_state_get_format(state, FE_STATS_PAD); + *fmt = cfe_default_meta_format; + fmt->code = MEDIA_BUS_FMT_FIXED; + + return 0; +} + +static int pisp_fe_pad_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *fmt; + const struct cfe_fmt *cfe_fmt; + + /* TODO: format propagation to source pads */ + /* TODO: format validation */ + + switch (format->pad) { + case FE_STREAM_PAD: + cfe_fmt = find_format_by_code(format->format.code); + if (!cfe_fmt || !(cfe_fmt->flags & CFE_FORMAT_FLAG_FE_OUT)) + cfe_fmt = find_format_by_code(MEDIA_BUS_FMT_SRGGB16_1X16); + + format->format.code = cfe_fmt->code; + format->format.field = V4L2_FIELD_NONE; + + fmt = v4l2_subdev_state_get_format(state, FE_STREAM_PAD); + *fmt = format->format; + + fmt = v4l2_subdev_state_get_format(state, FE_OUTPUT0_PAD); + *fmt = format->format; + + fmt = v4l2_subdev_state_get_format(state, FE_OUTPUT1_PAD); + *fmt = format->format; + + return 0; + + case FE_OUTPUT0_PAD: + case FE_OUTPUT1_PAD: { + /* + * TODO: we should allow scaling and cropping by allowing the + * user to set the size here. + */ + struct v4l2_mbus_framefmt *sink_fmt, *source_fmt; + u32 sink_code; + u32 code; + + sink_fmt = v4l2_subdev_state_get_format(state, FE_STREAM_PAD); + if (!sink_fmt) + return -EINVAL; + + source_fmt = v4l2_subdev_state_get_format(state, format->pad); + if (!source_fmt) + return -EINVAL; + + sink_code = sink_fmt->code; + code = format->format.code; + + /* + * If the source code from the user does not match the code in + * the sink pad, check that the source code matches the + * compressed version of the sink code. + */ + + if (code != sink_code && + code == cfe_find_compressed_code(sink_code)) + source_fmt->code = code; + + return 0; + } + + case FE_CONFIG_PAD: + case FE_STATS_PAD: + default: + return v4l2_subdev_get_fmt(sd, state, format); + } +} + +static const struct v4l2_subdev_internal_ops pisp_fe_internal_ops = { + .init_state = pisp_fe_init_cfg, +}; + +static const struct v4l2_subdev_pad_ops pisp_fe_subdev_pad_ops = { + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = pisp_fe_pad_set_fmt, + .link_validate = v4l2_subdev_link_validate_default, +}; + +static const struct media_entity_operations pisp_fe_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static const struct v4l2_subdev_ops pisp_fe_subdev_ops = { + .pad = &pisp_fe_subdev_pad_ops, +}; + +int pisp_fe_init(struct pisp_fe_device *fe, struct dentry *debugfs) +{ + int ret; + + debugfs_create_file("pisp_regs", 0444, debugfs, fe, &pisp_regs_fops); + + fe->hw_revision = pisp_fe_reg_read(fe, FE_VERSION); + pisp_fe_info("PiSP FE HW v%u.%u\n", + (fe->hw_revision >> 24) & 0xff, + (fe->hw_revision >> 20) & 0x0f); + + fe->pad[FE_STREAM_PAD].flags = + MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT; + fe->pad[FE_CONFIG_PAD].flags = MEDIA_PAD_FL_SINK; + fe->pad[FE_OUTPUT0_PAD].flags = MEDIA_PAD_FL_SOURCE; + fe->pad[FE_OUTPUT1_PAD].flags = MEDIA_PAD_FL_SOURCE; + fe->pad[FE_STATS_PAD].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&fe->sd.entity, ARRAY_SIZE(fe->pad), + fe->pad); + if (ret) + return ret; + + /* Initialize subdev */ + v4l2_subdev_init(&fe->sd, &pisp_fe_subdev_ops); + fe->sd.internal_ops = &pisp_fe_internal_ops; + fe->sd.entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER; + fe->sd.entity.ops = &pisp_fe_entity_ops; + fe->sd.entity.name = "pisp-fe"; + fe->sd.flags = V4L2_SUBDEV_FL_HAS_DEVNODE; + fe->sd.owner = THIS_MODULE; + snprintf(fe->sd.name, sizeof(fe->sd.name), "pisp-fe"); + + ret = v4l2_subdev_init_finalize(&fe->sd); + if (ret) + goto err_entity_cleanup; + + ret = v4l2_device_register_subdev(fe->v4l2_dev, &fe->sd); + if (ret) { + pisp_fe_err("Failed register pisp fe subdev (%d)\n", ret); + goto err_subdev_cleanup; + } + + /* Must be in IDLE state (STATUS == 0) here. */ + WARN_ON(pisp_fe_reg_read(fe, FE_STATUS)); + + return 0; + +err_subdev_cleanup: + v4l2_subdev_cleanup(&fe->sd); +err_entity_cleanup: + media_entity_cleanup(&fe->sd.entity); + + return ret; +} + +void pisp_fe_uninit(struct pisp_fe_device *fe) +{ + v4l2_device_unregister_subdev(&fe->sd); + v4l2_subdev_cleanup(&fe->sd); + media_entity_cleanup(&fe->sd.entity); +} diff --git a/drivers/media/platform/raspberrypi/rp1_cfe/pisp_fe.h b/drivers/media/platform/raspberrypi/rp1_cfe/pisp_fe.h new file mode 100644 index 00000000000000..12760cb55af422 --- /dev/null +++ b/drivers/media/platform/raspberrypi/rp1_cfe/pisp_fe.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * PiSP Front End driver. + * Copyright (c) 2021 Raspberry Pi Ltd. + * + */ +#ifndef _PISP_FE_H_ +#define _PISP_FE_H_ + +#include <linux/debugfs.h> +#include <linux/io.h> +#include <linux/types.h> +#include <linux/videodev2.h> + +#include <media/media-device.h> +#include <media/v4l2-device.h> +#include <media/v4l2-subdev.h> + +#include "pisp_fe_config.h" + +enum pisp_fe_pads { + FE_STREAM_PAD, + FE_CONFIG_PAD, + FE_OUTPUT0_PAD, + FE_OUTPUT1_PAD, + FE_STATS_PAD, + FE_NUM_PADS +}; + +struct pisp_fe_device { + /* Parent V4l2 device */ + struct v4l2_device *v4l2_dev; + void __iomem *base; + u32 hw_revision; + + u16 inframe_count; + struct media_pad pad[FE_NUM_PADS]; + struct v4l2_subdev sd; +}; + +void pisp_fe_isr(struct pisp_fe_device *fe, bool *sof, bool *eof); +int pisp_fe_validate_config(struct pisp_fe_device *fe, + struct pisp_fe_config *cfg, + struct v4l2_format const *f0, + struct v4l2_format const *f1); +void pisp_fe_submit_job(struct pisp_fe_device *fe, struct vb2_buffer **vb2_bufs, + struct pisp_fe_config *cfg); +void pisp_fe_start(struct pisp_fe_device *fe); +void pisp_fe_stop(struct pisp_fe_device *fe); +int pisp_fe_init(struct pisp_fe_device *fe, struct dentry *debugfs); +void pisp_fe_uninit(struct pisp_fe_device *fe); + +#endif diff --git a/drivers/media/platform/raspberrypi/rp1_cfe/pisp_fe_config.h b/drivers/media/platform/raspberrypi/rp1_cfe/pisp_fe_config.h new file mode 100644 index 00000000000000..35ffda72986fa4 --- /dev/null +++ b/drivers/media/platform/raspberrypi/rp1_cfe/pisp_fe_config.h @@ -0,0 +1,272 @@ +/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ +/* + * RP1 PiSP Front End Driver Configuration structures + * + * Copyright (C) 2021 - Raspberry Pi Ltd. + * + */ +#ifndef _PISP_FE_CONFIG_ +#define _PISP_FE_CONFIG_ + +#include "pisp_common.h" + +#include "pisp_statistics.h" + +#define PISP_FE_NUM_OUTPUTS 2 + +enum pisp_fe_enable { + PISP_FE_ENABLE_INPUT = 0x000001, + PISP_FE_ENABLE_DECOMPRESS = 0x000002, + PISP_FE_ENABLE_DECOMPAND = 0x000004, + PISP_FE_ENABLE_BLA = 0x000008, + PISP_FE_ENABLE_DPC = 0x000010, + PISP_FE_ENABLE_STATS_CROP = 0x000020, + PISP_FE_ENABLE_DECIMATE = 0x000040, + PISP_FE_ENABLE_BLC = 0x000080, + PISP_FE_ENABLE_CDAF_STATS = 0x000100, + PISP_FE_ENABLE_AWB_STATS = 0x000200, + PISP_FE_ENABLE_RGBY = 0x000400, + PISP_FE_ENABLE_LSC = 0x000800, + PISP_FE_ENABLE_AGC_STATS = 0x001000, + PISP_FE_ENABLE_CROP0 = 0x010000, + PISP_FE_ENABLE_DOWNSCALE0 = 0x020000, + PISP_FE_ENABLE_COMPRESS0 = 0x040000, + PISP_FE_ENABLE_OUTPUT0 = 0x080000, + PISP_FE_ENABLE_CROP1 = 0x100000, + PISP_FE_ENABLE_DOWNSCALE1 = 0x200000, + PISP_FE_ENABLE_COMPRESS1 = 0x400000, + PISP_FE_ENABLE_OUTPUT1 = 0x800000 +}; + +#define PISP_FE_ENABLE_CROP(i) (PISP_FE_ENABLE_CROP0 << (4 * (i))) +#define PISP_FE_ENABLE_DOWNSCALE(i) (PISP_FE_ENABLE_DOWNSCALE0 << (4 * (i))) +#define PISP_FE_ENABLE_COMPRESS(i) (PISP_FE_ENABLE_COMPRESS0 << (4 * (i))) +#define PISP_FE_ENABLE_OUTPUT(i) (PISP_FE_ENABLE_OUTPUT0 << (4 * (i))) + +/* + * We use the enable flags to show when blocks are "dirty", but we need some + * extra ones too. + */ +enum pisp_fe_dirty { + PISP_FE_DIRTY_GLOBAL = 0x0001, + PISP_FE_DIRTY_FLOATING = 0x0002, + PISP_FE_DIRTY_OUTPUT_AXI = 0x0004 +}; + +struct pisp_fe_global_config { + u32 enables; + u8 bayer_order; + u8 pad[3]; +}; + +struct pisp_fe_input_axi_config { + /* burst length minus one, in the range 0..15; OR'd with flags */ + u8 maxlen_flags; + /* { prot[2:0], cache[3:0] } fields */ + u8 cache_prot; + /* QoS (only 4 LS bits are used) */ + u16 qos; +}; + +struct pisp_fe_output_axi_config { + /* burst length minus one, in the range 0..15; OR'd with flags */ + u8 maxlen_flags; + /* { prot[2:0], cache[3:0] } fields */ + u8 cache_prot; + /* QoS (4 bitfields of 4 bits each for different panic levels) */ + u16 qos; + /* For Panic mode: Output FIFO panic threshold */ + u16 thresh; + /* For Panic mode: Output FIFO statistics throttle threshold */ + u16 throttle; +}; + +struct pisp_fe_input_config { + u8 streaming; + u8 pad[3]; + struct pisp_image_format_config format; + struct pisp_fe_input_axi_config axi; + /* Extra cycles delay before issuing each burst request */ + u8 holdoff; + u8 pad2[3]; +}; + +struct pisp_fe_output_config { + struct pisp_image_format_config format; + u16 ilines; + u8 pad[2]; +}; + +struct pisp_fe_input_buffer_config { + u32 addr_lo; + u32 addr_hi; + u16 frame_id; + u16 pad; +}; + +#define PISP_FE_DECOMPAND_LUT_SIZE 65 + +struct pisp_fe_decompand_config { + u16 lut[PISP_FE_DECOMPAND_LUT_SIZE]; + u16 pad; +}; + +struct pisp_fe_dpc_config { + u8 coeff_level; + u8 coeff_range; + u8 coeff_range2; +#define PISP_FE_DPC_FLAG_FOLDBACK 1 +#define PISP_FE_DPC_FLAG_VFLAG 2 + u8 flags; +}; + +#define PISP_FE_LSC_LUT_SIZE 16 + +struct pisp_fe_lsc_config { + u8 shift; + u8 pad0; + u16 scale; + u16 centre_x; + u16 centre_y; + u16 lut[PISP_FE_LSC_LUT_SIZE]; +}; + +struct pisp_fe_rgby_config { + u16 gain_r; + u16 gain_g; + u16 gain_b; + u8 maxflag; + u8 pad; +}; + +struct pisp_fe_agc_stats_config { + u16 offset_x; + u16 offset_y; + u16 size_x; + u16 size_y; + /* each weight only 4 bits */ + u8 weights[PISP_AGC_STATS_NUM_ZONES / 2]; + u16 row_offset_x; + u16 row_offset_y; + u16 row_size_x; + u16 row_size_y; + u8 row_shift; + u8 float_shift; + u8 pad1[2]; +}; + +struct pisp_fe_awb_stats_config { + u16 offset_x; + u16 offset_y; + u16 size_x; + u16 size_y; + u8 shift; + u8 pad[3]; + u16 r_lo; + u16 r_hi; + u16 g_lo; + u16 g_hi; + u16 b_lo; + u16 b_hi; +}; + +struct pisp_fe_floating_stats_region { + u16 offset_x; + u16 offset_y; + u16 size_x; + u16 size_y; +}; + +struct pisp_fe_floating_stats_config { + struct pisp_fe_floating_stats_region + regions[PISP_FLOATING_STATS_NUM_ZONES]; +}; + +#define PISP_FE_CDAF_NUM_WEIGHTS 8 + +struct pisp_fe_cdaf_stats_config { + u16 noise_constant; + u16 noise_slope; + u16 offset_x; + u16 offset_y; + u16 size_x; + u16 size_y; + u16 skip_x; + u16 skip_y; + u32 mode; +}; + +struct pisp_fe_stats_buffer_config { + u32 addr_lo; + u32 addr_hi; +}; + +struct pisp_fe_crop_config { + u16 offset_x; + u16 offset_y; + u16 width; + u16 height; +}; + +enum pisp_fe_downscale_flags { + DOWNSCALE_BAYER = + 1, /* downscale the four Bayer components independently... */ + DOWNSCALE_BIN = + 2 /* ...without trying to preserve their spatial relationship */ +}; + +struct pisp_fe_downscale_config { + u8 xin; + u8 xout; + u8 yin; + u8 yout; + u8 flags; /* enum pisp_fe_downscale_flags */ + u8 pad[3]; + u16 output_width; + u16 output_height; +}; + +struct pisp_fe_output_buffer_config { + u32 addr_lo; + u32 addr_hi; +}; + +/* Each of the two output channels/branches: */ +struct pisp_fe_output_branch_config { + struct pisp_fe_crop_config crop; + struct pisp_fe_downscale_config downscale; + struct pisp_compress_config compress; + struct pisp_fe_output_config output; + u32 pad; +}; + +/* And finally one to rule them all: */ +struct pisp_fe_config { + /* I/O configuration: */ + struct pisp_fe_stats_buffer_config stats_buffer; + struct pisp_fe_output_buffer_config output_buffer[PISP_FE_NUM_OUTPUTS]; + struct pisp_fe_input_buffer_config input_buffer; + /* processing configuration: */ + struct pisp_fe_global_config global; + struct pisp_fe_input_config input; + struct pisp_decompress_config decompress; + struct pisp_fe_decompand_config decompand; + struct pisp_bla_config bla; + struct pisp_fe_dpc_config dpc; + struct pisp_fe_crop_config stats_crop; + u32 spare1; /* placeholder for future decimate configuration */ + struct pisp_bla_config blc; + struct pisp_fe_rgby_config rgby; + struct pisp_fe_lsc_config lsc; + struct pisp_fe_agc_stats_config agc_stats; + struct pisp_fe_awb_stats_config awb_stats; + struct pisp_fe_cdaf_stats_config cdaf_stats; + struct pisp_fe_floating_stats_config floating_stats; + struct pisp_fe_output_axi_config output_axi; + struct pisp_fe_output_branch_config ch[PISP_FE_NUM_OUTPUTS]; + /* non-register fields: */ + u32 dirty_flags; /* these use pisp_fe_enable */ + u32 dirty_flags_extra; /* these use pisp_fe_dirty */ +}; + +#endif /* _PISP_FE_CONFIG_ */ diff --git a/drivers/media/platform/raspberrypi/rp1_cfe/pisp_statistics.h b/drivers/media/platform/raspberrypi/rp1_cfe/pisp_statistics.h new file mode 100644 index 00000000000000..d8d7fcb57fecc0 --- /dev/null +++ b/drivers/media/platform/raspberrypi/rp1_cfe/pisp_statistics.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ +/* + * RP1 PiSP Front End statistics definitions + * + * Copyright (C) 2021 - Raspberry Pi Ltd. + * + */ +#ifndef _PISP_FE_STATISTICS_H_ +#define _PISP_FE_STATISTICS_H_ + +#define PISP_FLOATING_STATS_NUM_ZONES 4 +#define PISP_AGC_STATS_NUM_BINS 1024 +#define PISP_AGC_STATS_SIZE 16 +#define PISP_AGC_STATS_NUM_ZONES (PISP_AGC_STATS_SIZE * PISP_AGC_STATS_SIZE) +#define PISP_AGC_STATS_NUM_ROW_SUMS 512 + +struct pisp_agc_statistics_zone { + u64 Y_sum; + u32 counted; + u32 pad; +}; + +struct pisp_agc_statistics { + u32 row_sums[PISP_AGC_STATS_NUM_ROW_SUMS]; + /* + * 32-bits per bin means an image (just less than) 16384x16384 pixels + * in size can weight every pixel from 0 to 15. + */ + u32 histogram[PISP_AGC_STATS_NUM_BINS]; + struct pisp_agc_statistics_zone floating[PISP_FLOATING_STATS_NUM_ZONES]; +}; + +#define PISP_AWB_STATS_SIZE 32 +#define PISP_AWB_STATS_NUM_ZONES (PISP_AWB_STATS_SIZE * PISP_AWB_STATS_SIZE) + +struct pisp_awb_statistics_zone { + u32 R_sum; + u32 G_sum; + u32 B_sum; + u32 counted; +}; + +struct pisp_awb_statistics { + struct pisp_awb_statistics_zone zones[PISP_AWB_STATS_NUM_ZONES]; + struct pisp_awb_statistics_zone floating[PISP_FLOATING_STATS_NUM_ZONES]; +}; + +#define PISP_CDAF_STATS_SIZE 8 +#define PISP_CDAF_STATS_NUM_FOMS (PISP_CDAF_STATS_SIZE * PISP_CDAF_STATS_SIZE) + +struct pisp_cdaf_statistics { + u64 foms[PISP_CDAF_STATS_NUM_FOMS]; + u64 floating[PISP_FLOATING_STATS_NUM_ZONES]; +}; + +struct pisp_statistics { + struct pisp_awb_statistics awb; + struct pisp_agc_statistics agc; + struct pisp_cdaf_statistics cdaf; +}; + +#endif /* _PISP_FE_STATISTICS_H_ */ diff --git a/drivers/media/platform/raspberrypi/rp1_cfe/pisp_types.h b/drivers/media/platform/raspberrypi/rp1_cfe/pisp_types.h new file mode 100644 index 00000000000000..93d2d3c27a5626 --- /dev/null +++ b/drivers/media/platform/raspberrypi/rp1_cfe/pisp_types.h @@ -0,0 +1,144 @@ +/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ +/* + * RP1 PiSP Front End image definitions. + * + * Copyright (C) 2021 - Raspberry Pi Ltd. + * + */ +#ifndef _PISP_FE_TYPES_H_ +#define _PISP_FE_TYPES_H_ + +/* This definition must match the format description in the hardware exactly! */ +struct pisp_image_format_config { + /* size in pixels */ + u16 width, height; + /* must match struct pisp_image_format below */ + u32 format; + s32 stride; + /* some planar image formats will need a second stride */ + s32 stride2; +}; + +static_assert(sizeof(struct pisp_image_format_config) == 16); + +enum pisp_bayer_order { + /* + * Note how bayer_order&1 tells you if G is on the even pixels of the + * checkerboard or not, and bayer_order&2 tells you if R is on the even + * rows or is swapped with B. Note that if the top (of the 8) bits is + * set, this denotes a monochrome or greyscale image, and the lower bits + * should all be ignored. + */ + PISP_BAYER_ORDER_RGGB = 0, + PISP_BAYER_ORDER_GBRG = 1, + PISP_BAYER_ORDER_BGGR = 2, + PISP_BAYER_ORDER_GRBG = 3, + PISP_BAYER_ORDER_GREYSCALE = 128 +}; + +enum pisp_image_format { + /* + * Precise values are mostly tbd. Generally these will be portmanteau + * values comprising bit fields and flags. This format must be shared + * throughout the PiSP. + */ + PISP_IMAGE_FORMAT_BPS_8 = 0x00000000, + PISP_IMAGE_FORMAT_BPS_10 = 0x00000001, + PISP_IMAGE_FORMAT_BPS_12 = 0x00000002, + PISP_IMAGE_FORMAT_BPS_16 = 0x00000003, + PISP_IMAGE_FORMAT_BPS_MASK = 0x00000003, + + PISP_IMAGE_FORMAT_PLANARITY_INTERLEAVED = 0x00000000, + PISP_IMAGE_FORMAT_PLANARITY_SEMI_PLANAR = 0x00000010, + PISP_IMAGE_FORMAT_PLANARITY_PLANAR = 0x00000020, + PISP_IMAGE_FORMAT_PLANARITY_MASK = 0x00000030, + + PISP_IMAGE_FORMAT_SAMPLING_444 = 0x00000000, + PISP_IMAGE_FORMAT_SAMPLING_422 = 0x00000100, + PISP_IMAGE_FORMAT_SAMPLING_420 = 0x00000200, + PISP_IMAGE_FORMAT_SAMPLING_MASK = 0x00000300, + + PISP_IMAGE_FORMAT_ORDER_NORMAL = 0x00000000, + PISP_IMAGE_FORMAT_ORDER_SWAPPED = 0x00001000, + + PISP_IMAGE_FORMAT_SHIFT_0 = 0x00000000, + PISP_IMAGE_FORMAT_SHIFT_1 = 0x00010000, + PISP_IMAGE_FORMAT_SHIFT_2 = 0x00020000, + PISP_IMAGE_FORMAT_SHIFT_3 = 0x00030000, + PISP_IMAGE_FORMAT_SHIFT_4 = 0x00040000, + PISP_IMAGE_FORMAT_SHIFT_5 = 0x00050000, + PISP_IMAGE_FORMAT_SHIFT_6 = 0x00060000, + PISP_IMAGE_FORMAT_SHIFT_7 = 0x00070000, + PISP_IMAGE_FORMAT_SHIFT_8 = 0x00080000, + PISP_IMAGE_FORMAT_SHIFT_MASK = 0x000f0000, + + PISP_IMAGE_FORMAT_UNCOMPRESSED = 0x00000000, + PISP_IMAGE_FORMAT_COMPRESSION_MODE_1 = 0x01000000, + PISP_IMAGE_FORMAT_COMPRESSION_MODE_2 = 0x02000000, + PISP_IMAGE_FORMAT_COMPRESSION_MODE_3 = 0x03000000, + PISP_IMAGE_FORMAT_COMPRESSION_MASK = 0x03000000, + + PISP_IMAGE_FORMAT_HOG_SIGNED = 0x04000000, + PISP_IMAGE_FORMAT_HOG_UNSIGNED = 0x08000000, + PISP_IMAGE_FORMAT_INTEGRAL_IMAGE = 0x10000000, + PISP_IMAGE_FORMAT_WALLPAPER_ROLL = 0x20000000, + PISP_IMAGE_FORMAT_THREE_CHANNEL = 0x40000000, + + /* Lastly a few specific instantiations of the above. */ + PISP_IMAGE_FORMAT_SINGLE_16 = PISP_IMAGE_FORMAT_BPS_16, + PISP_IMAGE_FORMAT_THREE_16 = + PISP_IMAGE_FORMAT_BPS_16 | PISP_IMAGE_FORMAT_THREE_CHANNEL +}; + +#define PISP_IMAGE_FORMAT_bps_8(fmt) \ + (((fmt) & PISP_IMAGE_FORMAT_BPS_MASK) == PISP_IMAGE_FORMAT_BPS_8) +#define PISP_IMAGE_FORMAT_bps_10(fmt) \ + (((fmt) & PISP_IMAGE_FORMAT_BPS_MASK) == PISP_IMAGE_FORMAT_BPS_10) +#define PISP_IMAGE_FORMAT_bps_12(fmt) \ + (((fmt) & PISP_IMAGE_FORMAT_BPS_MASK) == PISP_IMAGE_FORMAT_BPS_12) +#define PISP_IMAGE_FORMAT_bps_16(fmt) \ + (((fmt) & PISP_IMAGE_FORMAT_BPS_MASK) == PISP_IMAGE_FORMAT_BPS_16) +#define PISP_IMAGE_FORMAT_bps(fmt) \ + (((fmt) & PISP_IMAGE_FORMAT_BPS_MASK) ? \ + 8 + (2 << (((fmt) & PISP_IMAGE_FORMAT_BPS_MASK) - 1)) : \ + 8) +#define PISP_IMAGE_FORMAT_shift(fmt) \ + (((fmt) & PISP_IMAGE_FORMAT_SHIFT_MASK) / PISP_IMAGE_FORMAT_SHIFT_1) +#define PISP_IMAGE_FORMAT_three_channel(fmt) \ + ((fmt) & PISP_IMAGE_FORMAT_THREE_CHANNEL) +#define PISP_IMAGE_FORMAT_single_channel(fmt) \ + (!((fmt) & PISP_IMAGE_FORMAT_THREE_CHANNEL)) +#define PISP_IMAGE_FORMAT_compressed(fmt) \ + (((fmt) & PISP_IMAGE_FORMAT_COMPRESSION_MASK) != \ + PISP_IMAGE_FORMAT_UNCOMPRESSED) +#define PISP_IMAGE_FORMAT_sampling_444(fmt) \ + (((fmt) & PISP_IMAGE_FORMAT_SAMPLING_MASK) == \ + PISP_IMAGE_FORMAT_SAMPLING_444) +#define PISP_IMAGE_FORMAT_sampling_422(fmt) \ + (((fmt) & PISP_IMAGE_FORMAT_SAMPLING_MASK) == \ + PISP_IMAGE_FORMAT_SAMPLING_422) +#define PISP_IMAGE_FORMAT_sampling_420(fmt) \ + (((fmt) & PISP_IMAGE_FORMAT_SAMPLING_MASK) == \ + PISP_IMAGE_FORMAT_SAMPLING_420) +#define PISP_IMAGE_FORMAT_order_normal(fmt) \ + (!((fmt) & PISP_IMAGE_FORMAT_ORDER_SWAPPED)) +#define PISP_IMAGE_FORMAT_order_swapped(fmt) \ + ((fmt) & PISP_IMAGE_FORMAT_ORDER_SWAPPED) +#define PISP_IMAGE_FORMAT_interleaved(fmt) \ + (((fmt) & PISP_IMAGE_FORMAT_PLANARITY_MASK) == \ + PISP_IMAGE_FORMAT_PLANARITY_INTERLEAVED) +#define PISP_IMAGE_FORMAT_semiplanar(fmt) \ + (((fmt) & PISP_IMAGE_FORMAT_PLANARITY_MASK) == \ + PISP_IMAGE_FORMAT_PLANARITY_SEMI_PLANAR) +#define PISP_IMAGE_FORMAT_planar(fmt) \ + (((fmt) & PISP_IMAGE_FORMAT_PLANARITY_MASK) == \ + PISP_IMAGE_FORMAT_PLANARITY_PLANAR) +#define PISP_IMAGE_FORMAT_wallpaper(fmt) \ + ((fmt) & PISP_IMAGE_FORMAT_WALLPAPER_ROLL) +#define PISP_IMAGE_FORMAT_HOG(fmt) \ + ((fmt) & \ + (PISP_IMAGE_FORMAT_HOG_SIGNED | PISP_IMAGE_FORMAT_HOG_UNSIGNED)) + +#define PISP_WALLPAPER_WIDTH 128 // in bytes + +#endif /* _PISP_FE_TYPES_H_ */ diff --git a/drivers/media/platform/video-mux.c b/drivers/media/platform/video-mux.c index 31e9e92e723eb1..ccce867d570917 100644 --- a/drivers/media/platform/video-mux.c +++ b/drivers/media/platform/video-mux.c @@ -20,10 +20,27 @@ #include <media/v4l2-mc.h> #include <media/v4l2-subdev.h> +struct video_mux_asd { + struct v4l2_async_connection base; + unsigned int port; +}; + +static inline struct video_mux_asd *to_video_mux_asd(struct v4l2_async_connection *asd) +{ + return container_of(asd, struct video_mux_asd, base); +} + +struct video_mux_pad_cfg { + unsigned int num_lanes; + bool non_continuous; + struct v4l2_subdev *source; +}; + struct video_mux { struct v4l2_subdev subdev; struct v4l2_async_notifier notifier; struct media_pad *pads; + struct video_mux_pad_cfg *cfg; struct mux_control *mux; struct mutex lock; int active; @@ -52,6 +69,8 @@ static int video_mux_link_setup(struct media_entity *entity, const struct media_pad *remote, u32 flags) { struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); + struct v4l2_subdev *source_sd; + struct v4l2_subdev_state *sd_state; struct video_mux *vmux = v4l2_subdev_to_video_mux(sd); u16 source_pad = entity->num_pads - 1; int ret = 0; @@ -67,10 +86,10 @@ static int video_mux_link_setup(struct media_entity *entity, remote->entity->name, remote->index, local->entity->name, local->index, flags & MEDIA_LNK_FL_ENABLED); + sd_state = v4l2_subdev_lock_and_get_active_state(sd); mutex_lock(&vmux->lock); if (flags & MEDIA_LNK_FL_ENABLED) { - struct v4l2_subdev_state *sd_state; struct v4l2_mbus_framefmt *source_mbusformat; if (vmux->active == local->index) @@ -88,12 +107,14 @@ static int video_mux_link_setup(struct media_entity *entity, vmux->active = local->index; /* Propagate the active format to the source */ - sd_state = v4l2_subdev_lock_and_get_active_state(sd); source_mbusformat = v4l2_subdev_state_get_format(sd_state, source_pad); *source_mbusformat = *v4l2_subdev_state_get_format(sd_state, vmux->active); - v4l2_subdev_unlock_state(sd_state); + + source_sd = media_entity_to_v4l2_subdev(remote->entity); + vmux->subdev.ctrl_handler = source_sd->ctrl_handler; + } else { if (vmux->active != local->index) goto out; @@ -101,10 +122,13 @@ static int video_mux_link_setup(struct media_entity *entity, dev_dbg(sd->dev, "going inactive\n"); mux_control_deselect(vmux->mux); vmux->active = -1; + + vmux->subdev.ctrl_handler = NULL; } out: mutex_unlock(&vmux->lock); + v4l2_subdev_unlock_state(sd_state); return ret; } @@ -301,9 +325,33 @@ static int video_mux_init_state(struct v4l2_subdev *sd, return 0; } +static int video_mux_get_mbus_config(struct v4l2_subdev *sd, + unsigned int pad, + struct v4l2_mbus_config *cfg) +{ + struct video_mux *vmux = v4l2_subdev_to_video_mux(sd); + int ret; + + ret = v4l2_subdev_call(vmux->cfg[vmux->active].source, pad, get_mbus_config, + 0, cfg); + + if (ret != -ENOIOCTLCMD) + return ret; + + cfg->type = V4L2_MBUS_CSI2_DPHY; + cfg->bus.mipi_csi2.num_data_lanes = vmux->cfg[vmux->active].num_lanes; + + /* Support for non-continuous CSI-2 clock is missing in pdate mode */ + if (vmux->cfg[vmux->active].non_continuous) + cfg->bus.mipi_csi2.flags |= V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK; + + return 0; +}; + static const struct v4l2_subdev_pad_ops video_mux_pad_ops = { .get_fmt = v4l2_subdev_get_fmt, .set_fmt = video_mux_set_format, + .get_mbus_config = video_mux_get_mbus_config, }; static const struct v4l2_subdev_ops video_mux_subdev_ops = { @@ -320,6 +368,9 @@ static int video_mux_notify_bound(struct v4l2_async_notifier *notifier, struct v4l2_async_connection *asd) { struct video_mux *vmux = notifier_to_video_mux(notifier); + unsigned int port = to_video_mux_asd(asd)->port; + + vmux->cfg[port].source = sd; return v4l2_create_fwnode_links(sd, &vmux->subdev); } @@ -337,7 +388,7 @@ static int video_mux_async_register(struct video_mux *vmux, v4l2_async_subdev_nf_init(&vmux->notifier, &vmux->subdev); for (i = 0; i < num_input_pads; i++) { - struct v4l2_async_connection *asd; + struct video_mux_asd *asd; struct fwnode_handle *ep, *remote_ep; ep = fwnode_graph_get_endpoint_by_id( @@ -355,8 +406,7 @@ static int video_mux_async_register(struct video_mux *vmux, fwnode_handle_put(remote_ep); asd = v4l2_async_nf_add_fwnode_remote(&vmux->notifier, ep, - struct v4l2_async_connection); - + struct video_mux_asd); fwnode_handle_put(ep); if (IS_ERR(asd)) { @@ -365,6 +415,8 @@ static int video_mux_async_register(struct video_mux *vmux, if (ret != -EEXIST) goto err_nf_cleanup; } + + asd->port = i; } vmux->notifier.ops = &video_mux_notify_ops; @@ -390,6 +442,9 @@ static int video_mux_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; struct device *dev = &pdev->dev; + struct v4l2_fwnode_endpoint fwnode_ep = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; struct device_node *ep; struct video_mux *vmux; unsigned int num_pads = 0; @@ -437,10 +492,27 @@ static int video_mux_probe(struct platform_device *pdev) if (!vmux->pads) return -ENOMEM; - for (i = 0; i < num_pads; i++) + vmux->cfg = devm_kcalloc(dev, num_pads, sizeof(*vmux->cfg), GFP_KERNEL); + if (!vmux->cfg) + return -ENOMEM; + + for (i = 0; i < num_pads; i++) { vmux->pads[i].flags = (i < num_pads - 1) ? MEDIA_PAD_FL_SINK : MEDIA_PAD_FL_SOURCE; + ep = of_graph_get_endpoint_by_regs(pdev->dev.of_node, i, 0); + if (ep) { + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep), &fwnode_ep); + if (!ret) { + /* Get number of data lanes */ + vmux->cfg[i].num_lanes = fwnode_ep.bus.mipi_csi2.num_data_lanes; + vmux->cfg[i].non_continuous = fwnode_ep.bus.mipi_csi2.flags & + V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK; + } + of_node_put(ep); + } + } + vmux->subdev.entity.function = MEDIA_ENT_F_VID_MUX; ret = media_entity_pads_init(&vmux->subdev.entity, num_pads, vmux->pads); diff --git a/drivers/media/spi/Kconfig b/drivers/media/spi/Kconfig index 4656afae5bb467..328ea94d14f99d 100644 --- a/drivers/media/spi/Kconfig +++ b/drivers/media/spi/Kconfig @@ -9,6 +9,7 @@ menu "Media SPI Adapters" config CXD2880_SPI_DRV tristate "Sony CXD2880 SPI support" depends on DVB_CORE && SPI + select DVB_CXD2880 if MEDIA_SUBDRV_AUTOSELECT default m if !MEDIA_SUBDRV_AUTOSELECT help Choose if you would like to have SPI interface support for Sony CXD2880. diff --git a/drivers/media/usb/dvb-usb-v2/rtl28xxu.c b/drivers/media/usb/dvb-usb-v2/rtl28xxu.c index f7884bb56fccff..b0a2fd7e13aafa 100644 --- a/drivers/media/usb/dvb-usb-v2/rtl28xxu.c +++ b/drivers/media/usb/dvb-usb-v2/rtl28xxu.c @@ -1964,6 +1964,10 @@ static const struct usb_device_id rtl28xxu_id_table[] = { &rtl28xxu_props, "Compro VideoMate U650F", NULL) }, { DVB_USB_DEVICE(USB_VID_KWORLD_2, 0xd394, &rtl28xxu_props, "MaxMedia HU394-T", NULL) }, + { DVB_USB_DEVICE(USB_VID_GTEK, 0xb803 /*USB_PID_AUGUST_DVBT205*/, + &rtl28xxu_props, "August DVB-T 205", NULL) }, + { DVB_USB_DEVICE(USB_VID_GTEK, 0xa803 /*USB_PID_AUGUST_DVBT205*/, + &rtl28xxu_props, "August DVB-T 205", NULL) }, { DVB_USB_DEVICE(USB_VID_LEADTEK, 0x6a03, &rtl28xxu_props, "Leadtek WinFast DTV Dongle mini", NULL) }, { DVB_USB_DEVICE(USB_VID_GTEK, USB_PID_CPYTO_REDI_PC50A, diff --git a/drivers/media/v4l2-core/v4l2-ctrls-defs.c b/drivers/media/v4l2-core/v4l2-ctrls-defs.c index 1ea52011247acc..e7a1820c496908 100644 --- a/drivers/media/v4l2-core/v4l2-ctrls-defs.c +++ b/drivers/media/v4l2-core/v4l2-ctrls-defs.c @@ -228,6 +228,7 @@ const char * const *v4l2_ctrl_get_menu(u32 id) "Flash", "Cloudy", "Shade", + "Greyworld", NULL, }; static const char * const camera_iso_sensitivity_auto[] = { diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c index e14db67be97c50..d132f73111f69b 100644 --- a/drivers/media/v4l2-core/v4l2-ioctl.c +++ b/drivers/media/v4l2-core/v4l2-ioctl.c @@ -1378,6 +1378,8 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt) case V4L2_PIX_FMT_NV12MT: descr = "Y/UV 4:2:0 (64x32 MB, N-C)"; break; case V4L2_PIX_FMT_NV12MT_16X16: descr = "Y/UV 4:2:0 (16x16 MB, N-C)"; break; case V4L2_PIX_FMT_P012M: descr = "12-bit Y/UV 4:2:0 (N-C)"; break; + case V4L2_PIX_FMT_NV12_COL128: descr = "Y/CbCr 4:2:0 (128b cols)"; break; + case V4L2_PIX_FMT_NV12_10_COL128: descr = "10-bit Y/CbCr 4:2:0 (128b cols)"; break; case V4L2_PIX_FMT_YUV420M: descr = "Planar YUV 4:2:0 (N-C)"; break; case V4L2_PIX_FMT_YVU420M: descr = "Planar YVU 4:2:0 (N-C)"; break; case V4L2_PIX_FMT_YUV422M: descr = "Planar YUV 4:2:2 (N-C)"; break; @@ -1474,6 +1476,10 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt) case V4L2_META_FMT_GENERIC_CSI2_16: descr = "8-bit Generic Meta, 16b CSI-2"; break; case V4L2_META_FMT_GENERIC_CSI2_20: descr = "8-bit Generic Meta, 20b CSI-2"; break; case V4L2_META_FMT_GENERIC_CSI2_24: descr = "8-bit Generic Meta, 24b CSI-2"; break; + case V4L2_META_FMT_SENSOR_DATA: descr = "Sensor Ancillary Metadata"; break; + case V4L2_META_FMT_BCM2835_ISP_STATS: descr = "BCM2835 ISP Image Statistics"; break; + case V4L2_META_FMT_RPI_FE_CFG: descr = "PiSP FE Config format"; break; + case V4L2_META_FMT_RPI_FE_STATS: descr = "PiSP FE Statistics format"; break; default: /* Compressed formats */ diff --git a/drivers/media/v4l2-core/v4l2-mem2mem.c b/drivers/media/v4l2-core/v4l2-mem2mem.c index eb22d6172462da..8db37425b96f35 100644 --- a/drivers/media/v4l2-core/v4l2-mem2mem.c +++ b/drivers/media/v4l2-core/v4l2-mem2mem.c @@ -495,8 +495,6 @@ void v4l2_m2m_job_finish(struct v4l2_m2m_dev *m2m_dev, * holding capture buffers. Those should use * v4l2_m2m_buf_done_and_job_finish() instead. */ - WARN_ON(m2m_ctx->out_q_ctx.q.subsystem_flags & - VB2_V4L2_FL_SUPPORTS_M2M_HOLD_CAPTURE_BUF); spin_lock_irqsave(&m2m_dev->job_spinlock, flags); schedule_next = _v4l2_m2m_job_finish(m2m_dev, m2m_ctx); spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags); diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index f9325bcce1b94e..68612b8c686914 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -1186,6 +1186,16 @@ config MFD_SY7636A To enable support for building sub-devices as modules, choose M here. +config MFD_RASPBERRYPI_POE_HAT + tristate "Raspberry Pi PoE HAT MFD" + depends on I2C + select MFD_SIMPLE_MFD_I2C + help + This module supports the PWM fan controller found on the Raspberry Pi + POE and POE+ HAT boards, and the power supply driver on the POE+ HAT. + (Functionally it relies on MFD_SIMPLE_MFD_I2C to provide the framework + that loads the child drivers). + config MFD_RDC321X tristate "RDC R-321x southbridge" select MFD_CORE @@ -2374,6 +2384,17 @@ config MFD_INTEL_M10_BMC_PMCI additional drivers must be enabled in order to use the functionality of the device. +config MFD_RP1 + tristate "RP1 MFD driver" + depends on PCI + select MFD_CORE + help + Support for the RP1 peripheral chip. + + This driver provides support for the Raspberry Pi RP1 peripheral chip. + It is responsible for enabling the Device Tree node once the PCIe endpoint + has been configured, and handling interrupts. + config MFD_RSMU_I2C tristate "Renesas Synchronization Management Unit with I2C" depends on I2C && OF diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 2a9f91e81af836..de7e9f11b306dc 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -289,3 +289,5 @@ obj-$(CONFIG_MFD_ATC260X_I2C) += atc260x-i2c.o obj-$(CONFIG_MFD_RSMU_I2C) += rsmu_i2c.o rsmu_core.o obj-$(CONFIG_MFD_RSMU_SPI) += rsmu_spi.o rsmu_core.o + +obj-$(CONFIG_MFD_RP1) += rp1.o diff --git a/drivers/mfd/bcm2835-pm.c b/drivers/mfd/bcm2835-pm.c index 3cb2b942312112..8b31775da7b6dc 100644 --- a/drivers/mfd/bcm2835-pm.c +++ b/drivers/mfd/bcm2835-pm.c @@ -69,12 +69,30 @@ static int bcm2835_pm_get_pdata(struct platform_device *pdev, return 0; } +static const struct of_device_id bcm2835_pm_of_match[] = { + { .compatible = "brcm,bcm2835-pm-wdt", }, + { .compatible = "brcm,bcm2835-pm", }, + { .compatible = "brcm,bcm2711-pm", }, + { .compatible = "brcm,bcm2712-pm", .data = (const void *)1}, + {}, +}; +MODULE_DEVICE_TABLE(of, bcm2835_pm_of_match); + static int bcm2835_pm_probe(struct platform_device *pdev) { + const struct of_device_id *of_id; struct device *dev = &pdev->dev; struct bcm2835_pm *pm; + bool is_2712; int ret; + of_id = of_match_node(bcm2835_pm_of_match, pdev->dev.of_node); + if (!of_id) { + dev_err(&pdev->dev, "Failed to match compatible string\n"); + return -EINVAL; + } + is_2712 = !!of_id->data; + pm = devm_kzalloc(dev, sizeof(*pm), GFP_KERNEL); if (!pm) return -ENOMEM; @@ -97,21 +115,13 @@ static int bcm2835_pm_probe(struct platform_device *pdev) * bcm2835-pm binding as the key for whether we can reference * the full PM register range and support power domains. */ - if (pm->asb) + if (pm->asb || is_2712) return devm_mfd_add_devices(dev, -1, bcm2835_power_devs, ARRAY_SIZE(bcm2835_power_devs), NULL, 0, NULL); return 0; } -static const struct of_device_id bcm2835_pm_of_match[] = { - { .compatible = "brcm,bcm2835-pm-wdt", }, - { .compatible = "brcm,bcm2835-pm", }, - { .compatible = "brcm,bcm2711-pm", }, - {}, -}; -MODULE_DEVICE_TABLE(of, bcm2835_pm_of_match); - static struct platform_driver bcm2835_pm_driver = { .probe = bcm2835_pm_probe, .driver = { diff --git a/drivers/mfd/rp1.c b/drivers/mfd/rp1.c new file mode 100644 index 00000000000000..0a498a670a8173 --- /dev/null +++ b/drivers/mfd/rp1.c @@ -0,0 +1,376 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018-22 Raspberry Pi Ltd. + * All rights reserved. + */ + +#include <linux/clk.h> +#include <linux/clkdev.h> +#include <linux/clk-provider.h> +#include <linux/completion.h> +#include <linux/etherdevice.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/irqchip/chained_irq.h> +#include <linux/irqdomain.h> +#include <linux/mfd/core.h> +#include <linux/mmc/host.h> +#include <linux/module.h> +#include <linux/msi.h> +#include <linux/of_platform.h> +#include <linux/pci.h> +#include <linux/platform_device.h> +#include <linux/rp1_platform.h> +#include <linux/reset.h> +#include <linux/slab.h> + +#include <dt-bindings/mfd/rp1.h> + +/* TO DO: + * 1. Occasional shutdown crash - RP1 being closed before its children? + * 2. DT mode interrupt handling. + */ + +#define RP1_DRIVER_NAME "rp1" + +#define PCI_VENDOR_ID_RPI 0x1de4 +#define PCI_DEVICE_ID_RP1_C0 0x0001 +#define PCI_DEVICE_REV_RP1_C0 2 + +#define RP1_ACTUAL_IRQS RP1_INT_END +#define RP1_IRQS RP1_ACTUAL_IRQS + +#define RP1_SYSCLK_RATE 200000000 +#define RP1_SYSCLK_FPGA_RATE 60000000 + +// Don't want to include the whole sysinfo reg header +#define SYSINFO_CHIP_ID_OFFSET 0x00000000 +#define SYSINFO_PLATFORM_OFFSET 0x00000004 + +#define REG_RW 0x000 +#define REG_SET 0x800 +#define REG_CLR 0xc00 + +// MSIX CFG registers start at 0x8 +#define MSIX_CFG(x) (0x8 + (4 * (x))) + +#define MSIX_CFG_IACK_EN BIT(3) +#define MSIX_CFG_IACK BIT(2) +#define MSIX_CFG_TEST BIT(1) +#define MSIX_CFG_ENABLE BIT(0) + +#define INTSTATL 0x108 +#define INTSTATH 0x10c + +struct rp1_dev { + struct pci_dev *pdev; + struct device *dev; + resource_size_t bar_start; + resource_size_t bar_end; + struct clk *sys_clk; + struct irq_domain *domain; + struct irq_data *pcie_irqds[64]; + void __iomem *msix_cfg_regs; +}; + +static bool rp1_level_triggered_irq[RP1_ACTUAL_IRQS] = { 0 }; + +static struct rp1_dev *g_rp1; +static u32 g_chip_id, g_platform; + +static void dump_bar(struct pci_dev *pdev, unsigned int bar) +{ + dev_info(&pdev->dev, + "bar%d len 0x%llx, start 0x%llx, end 0x%llx, flags, 0x%lx\n", + bar, + pci_resource_len(pdev, bar), + pci_resource_start(pdev, bar), + pci_resource_end(pdev, bar), + pci_resource_flags(pdev, bar)); +} + +static void msix_cfg_set(struct rp1_dev *rp1, unsigned int hwirq, u32 value) +{ + writel(value, rp1->msix_cfg_regs + REG_SET + MSIX_CFG(hwirq)); +} + +static void msix_cfg_clr(struct rp1_dev *rp1, unsigned int hwirq, u32 value) +{ + writel(value, rp1->msix_cfg_regs + REG_CLR + MSIX_CFG(hwirq)); +} + +static void rp1_mask_irq(struct irq_data *irqd) +{ + struct rp1_dev *rp1 = irqd->domain->host_data; + struct irq_data *pcie_irqd = rp1->pcie_irqds[irqd->hwirq]; + + pci_msi_mask_irq(pcie_irqd); +} + +static void rp1_unmask_irq(struct irq_data *irqd) +{ + struct rp1_dev *rp1 = irqd->domain->host_data; + struct irq_data *pcie_irqd = rp1->pcie_irqds[irqd->hwirq]; + + pci_msi_unmask_irq(pcie_irqd); +} + +static int rp1_irq_set_type(struct irq_data *irqd, unsigned int type) +{ + struct rp1_dev *rp1 = irqd->domain->host_data; + unsigned int hwirq = (unsigned int)irqd->hwirq; + int ret = 0; + + switch (type) { + case IRQ_TYPE_LEVEL_HIGH: + dev_dbg(rp1->dev, "MSIX IACK EN for irq %d\n", hwirq); + msix_cfg_set(rp1, hwirq, MSIX_CFG_IACK_EN); + rp1_level_triggered_irq[hwirq] = true; + break; + case IRQ_TYPE_EDGE_RISING: + msix_cfg_clr(rp1, hwirq, MSIX_CFG_IACK_EN); + rp1_level_triggered_irq[hwirq] = false; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int rp1_irq_set_affinity(struct irq_data *irqd, const struct cpumask *dest, bool force) +{ + struct rp1_dev *rp1 = irqd->domain->host_data; + struct irq_data *pcie_irqd = rp1->pcie_irqds[irqd->hwirq]; + + return msi_domain_set_affinity(pcie_irqd, dest, force); +} + +static struct irq_chip rp1_irq_chip = { + .name = "rp1_irq_chip", + .irq_mask = rp1_mask_irq, + .irq_unmask = rp1_unmask_irq, + .irq_set_type = rp1_irq_set_type, + .irq_set_affinity = rp1_irq_set_affinity, +}; + +static void rp1_chained_handle_irq(struct irq_desc *desc) +{ + struct irq_chip *chip = irq_desc_get_chip(desc); + struct rp1_dev *rp1 = desc->irq_data.chip_data; + unsigned int hwirq = desc->irq_data.hwirq & 0x3f; + int new_irq; + + rp1 = g_rp1; + + chained_irq_enter(chip, desc); + + new_irq = irq_linear_revmap(rp1->domain, hwirq); + generic_handle_irq(new_irq); + if (rp1_level_triggered_irq[hwirq]) + msix_cfg_set(rp1, hwirq, MSIX_CFG_IACK); + + chained_irq_exit(chip, desc); +} + +static int rp1_irq_xlate(struct irq_domain *d, struct device_node *node, + const u32 *intspec, unsigned int intsize, + unsigned long *out_hwirq, unsigned int *out_type) +{ + struct rp1_dev *rp1 = d->host_data; + struct irq_data *pcie_irqd; + unsigned long hwirq; + int pcie_irq; + int ret; + + ret = irq_domain_xlate_twocell(d, node, intspec, intsize, + &hwirq, out_type); + if (!ret) { + pcie_irq = pci_irq_vector(rp1->pdev, hwirq); + pcie_irqd = irq_get_irq_data(pcie_irq); + rp1->pcie_irqds[hwirq] = pcie_irqd; + *out_hwirq = hwirq; + } + return ret; +} + +static int rp1_irq_activate(struct irq_domain *d, struct irq_data *irqd, + bool reserve) +{ + struct rp1_dev *rp1 = d->host_data; + struct irq_data *pcie_irqd; + + pcie_irqd = rp1->pcie_irqds[irqd->hwirq]; + msix_cfg_set(rp1, (unsigned int)irqd->hwirq, MSIX_CFG_ENABLE); + return irq_domain_activate_irq(pcie_irqd, reserve); +} + +static void rp1_irq_deactivate(struct irq_domain *d, struct irq_data *irqd) +{ + struct rp1_dev *rp1 = d->host_data; + struct irq_data *pcie_irqd; + + pcie_irqd = rp1->pcie_irqds[irqd->hwirq]; + msix_cfg_clr(rp1, (unsigned int)irqd->hwirq, MSIX_CFG_ENABLE); + return irq_domain_deactivate_irq(pcie_irqd); +} + +static const struct irq_domain_ops rp1_domain_ops = { + .xlate = rp1_irq_xlate, + .activate = rp1_irq_activate, + .deactivate = rp1_irq_deactivate, +}; + +static inline dma_addr_t rp1_io_to_phys(struct rp1_dev *rp1, unsigned int offset) +{ + return rp1->bar_start + offset; +} + +static u32 rp1_reg_read(struct rp1_dev *rp1, unsigned int base_addr, u32 offset) +{ + dma_addr_t phys = rp1_io_to_phys(rp1, base_addr); + void __iomem *regblock = ioremap(phys, 0x1000); + u32 value = readl(regblock + offset); + + iounmap(regblock); + return value; +} + +void rp1_get_platform(u32 *chip_id, u32 *platform) +{ + if (chip_id) + *chip_id = g_chip_id; + if (platform) + *platform = g_platform; +} +EXPORT_SYMBOL_GPL(rp1_get_platform); + +static int rp1_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct reset_control *reset; + struct platform_device *pcie_pdev; + struct device_node *rp1_node; + struct rp1_dev *rp1; + int err = 0; + int i; + + reset = devm_reset_control_get_optional_exclusive(&pdev->dev, NULL); + if (IS_ERR(reset)) + return PTR_ERR(reset); + reset_control_reset(reset); + + dump_bar(pdev, 0); + dump_bar(pdev, 1); + + if (pci_resource_len(pdev, 1) <= 0x10000) { + dev_err(&pdev->dev, + "Not initialised - is the firmware running?\n"); + return -EINVAL; + } + + /* enable pci device */ + err = pcim_enable_device(pdev); + if (err < 0) { + dev_err(&pdev->dev, "Enabling PCI device has failed: %d", + err); + return err; + } + + pci_set_master(pdev); + + err = pci_alloc_irq_vectors(pdev, RP1_IRQS, RP1_IRQS, + PCI_IRQ_MSIX); + if (err != RP1_IRQS) { + dev_err(&pdev->dev, "pci_alloc_irq_vectors failed - %d\n", err); + return err; + } + + rp1 = devm_kzalloc(&pdev->dev, sizeof(*rp1), GFP_KERNEL); + if (!rp1) + return -ENOMEM; + + rp1->pdev = pdev; + rp1->dev = &pdev->dev; + + pci_set_drvdata(pdev, rp1); + + rp1->bar_start = pci_resource_start(pdev, 1); + rp1->bar_end = pci_resource_end(pdev, 1); + + // Get chip id + g_chip_id = rp1_reg_read(rp1, RP1_SYSINFO_BASE, SYSINFO_CHIP_ID_OFFSET); + g_platform = rp1_reg_read(rp1, RP1_SYSINFO_BASE, SYSINFO_PLATFORM_OFFSET); + dev_info(&pdev->dev, "chip_id 0x%x%s\n", g_chip_id, + (g_platform & RP1_PLATFORM_FPGA) ? " FPGA" : ""); + if (g_chip_id != RP1_C0_CHIP_ID) { + dev_err(&pdev->dev, "wrong chip id (%x)\n", g_chip_id); + return -EINVAL; + } + + rp1_node = of_find_node_by_name(NULL, "rp1"); + if (!rp1_node) { + dev_err(&pdev->dev, "failed to find RP1 DT node\n"); + return -EINVAL; + } + + pcie_pdev = of_find_device_by_node(rp1_node->parent); + rp1->domain = irq_domain_add_linear(rp1_node, RP1_IRQS, + &rp1_domain_ops, rp1); + + g_rp1 = rp1; + + /* TODO can this go in the rp1 device tree entry? */ + rp1->msix_cfg_regs = ioremap(rp1_io_to_phys(rp1, RP1_PCIE_APBS_BASE), 0x1000); + + for (i = 0; i < RP1_IRQS; i++) { + int irq = irq_create_mapping(rp1->domain, i); + + if (irq < 0) { + dev_err(&pdev->dev, "failed to create irq mapping\n"); + return irq; + } + + irq_set_chip_data(irq, rp1); + irq_set_chip_and_handler(irq, &rp1_irq_chip, handle_level_irq); + irq_set_probe(irq); + irq_set_chained_handler(pci_irq_vector(pdev, i), + rp1_chained_handle_irq); + } + + if (rp1_node) + of_platform_populate(rp1_node, NULL, NULL, &pcie_pdev->dev); + + of_node_put(rp1_node); + + return 0; +} + +static void rp1_remove(struct pci_dev *pdev) +{ + struct rp1_dev *rp1 = pci_get_drvdata(pdev); + + mfd_remove_devices(&pdev->dev); + + clk_unregister(rp1->sys_clk); +} + +static const struct pci_device_id dev_id_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_RPI, PCI_DEVICE_ID_RP1_C0), }, + { 0, } +}; + +static struct pci_driver rp1_driver = { + .name = RP1_DRIVER_NAME, + .id_table = dev_id_table, + .probe = rp1_probe, + .remove = rp1_remove, +}; + +module_pci_driver(rp1_driver); + +MODULE_AUTHOR("Phil Elwell <phil@raspberrypi.com>"); +MODULE_DESCRIPTION("RP1 wrapper"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/simple-mfd-i2c.c b/drivers/mfd/simple-mfd-i2c.c index 6eda79533208a3..67f74304f2bfdf 100644 --- a/drivers/mfd/simple-mfd-i2c.c +++ b/drivers/mfd/simple-mfd-i2c.c @@ -29,6 +29,15 @@ static const struct regmap_config regmap_config_8r_8v = { .val_bits = 8, }; +static const struct regmap_config regmap_config_16r_8v = { + .reg_bits = 16, + .val_bits = 8, +}; + +static const struct simple_mfd_data rpi_poe_core = { + .regmap_config = ®map_config_16r_8v, +}; + static int simple_mfd_i2c_probe(struct i2c_client *i2c) { const struct simple_mfd_data *simple_mfd_data; @@ -88,6 +97,8 @@ static const struct of_device_id simple_mfd_i2c_of_match[] = { { .compatible = "silergy,sy7636a", .data = &silergy_sy7636a}, { .compatible = "maxim,max5970", .data = &maxim_max5970}, { .compatible = "maxim,max5978", .data = &maxim_max5970}, + { .compatible = "raspberrypi,poe-core", &rpi_poe_core }, + { .compatible = "raspberrypi,sensehat" }, {} }; MODULE_DEVICE_TABLE(of, simple_mfd_i2c_of_match); diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 3fe7e2a9bd294d..3cd5e67fb0d1b1 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -9,6 +9,32 @@ config SENSORS_LIS3LV02D tristate depends on INPUT +config BCM2835_SMI + tristate "Broadcom 283x Secondary Memory Interface driver" + depends on ARCH_BCM2835 + default m + help + Driver for enabling and using Broadcom's Secondary/Slow Memory Interface. + Appears as /dev/bcm2835_smi. For ioctl interface see drivers/misc/bcm2835_smi.h + +config RP1_PIO + tristate "Raspberry Pi RP1 PIO driver" + depends on FIRMWARE_RP1 || COMPILE_TEST + default n + help + Driver providing control of the Raspberry Pi PIO block, as found in + RP1. + +config WS2812_PIO_RP1 + tristate "Raspberry Pi PIO-base WS2812 driver" + depends on RP1_PIO || COMPILE_TEST + default n + help + Driver for the WS2812 (NeoPixel) LEDs using the RP1 PIO hardware. + The driver creates a character device to which rgbw pixels may be + written. Single-byte writes to offset 0 set the brightness at + runtime. + config AD525X_DPOT tristate "Analog Devices Digital Potentiometers" depends on (I2C || SPI) && SYSFS diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index a9f94525e1819d..d3f355d263b4a9 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_AD525X_DPOT) += ad525x_dpot.o obj-$(CONFIG_AD525X_DPOT_I2C) += ad525x_dpot-i2c.o obj-$(CONFIG_AD525X_DPOT_SPI) += ad525x_dpot-spi.o obj-$(CONFIG_ATMEL_SSC) += atmel-ssc.o +obj-$(CONFIG_BCM2835_SMI) += bcm2835_smi.o obj-$(CONFIG_DUMMY_IRQ) += dummy-irq.o obj-$(CONFIG_ICS932S401) += ics932s401.o obj-$(CONFIG_LKDTM) += lkdtm/ @@ -18,6 +19,8 @@ obj-$(CONFIG_PHANTOM) += phantom.o obj-$(CONFIG_RPMB) += rpmb-core.o obj-$(CONFIG_QCOM_COINCELL) += qcom-coincell.o obj-$(CONFIG_QCOM_FASTRPC) += fastrpc.o +obj-$(CONFIG_RP1_PIO) += rp1-pio.o +obj-$(CONFIG_WS2812_PIO_RP1) += ws2812-pio-rp1.o obj-$(CONFIG_SENSORS_BH1770) += bh1770glc.o obj-$(CONFIG_SENSORS_APDS990X) += apds990x.o obj-$(CONFIG_ENCLOSURE_SERVICES) += enclosure.o diff --git a/drivers/misc/bcm2835_smi.c b/drivers/misc/bcm2835_smi.c new file mode 100644 index 00000000000000..09246d5ea7d013 --- /dev/null +++ b/drivers/misc/bcm2835_smi.c @@ -0,0 +1,952 @@ +/** + * Broadcom Secondary Memory Interface driver + * + * Written by Luke Wren <luke@raspberrypi.org> + * Copyright (c) 2015, Raspberry Pi (Trading) Ltd. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The names of the above-listed copyright holders may not be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2, as published by the Free + * Software Foundation. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <linux/clk.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/pagemap.h> +#include <linux/dma-mapping.h> +#include <linux/dmaengine.h> +#include <linux/semaphore.h> +#include <linux/spinlock.h> +#include <linux/io.h> + +#define BCM2835_SMI_IMPLEMENTATION +#include <linux/broadcom/bcm2835_smi.h> + +#define DRIVER_NAME "smi-bcm2835" + +#define N_PAGES_FROM_BYTES(n) ((n + PAGE_SIZE-1) / PAGE_SIZE) + +#define DMA_WRITE_TO_MEM true +#define DMA_READ_FROM_MEM false + +struct bcm2835_smi_instance { + struct device *dev; + struct smi_settings settings; + __iomem void *smi_regs_ptr; + phys_addr_t smi_regs_busaddr; + + struct dma_chan *dma_chan; + struct dma_slave_config dma_config; + + struct bcm2835_smi_bounce_info bounce; + + struct scatterlist buffer_sgl; + + struct clk *clk; + + /* Sometimes we are called into in an atomic context (e.g. by + JFFS2 + MTD) so we can't use a mutex */ + spinlock_t transaction_lock; +}; + +/**************************************************************************** +* +* SMI peripheral setup +* +***************************************************************************/ + +static inline void write_smi_reg(struct bcm2835_smi_instance *inst, + u32 val, unsigned reg) +{ + writel(val, inst->smi_regs_ptr + reg); +} + +static inline u32 read_smi_reg(struct bcm2835_smi_instance *inst, unsigned reg) +{ + return readl(inst->smi_regs_ptr + reg); +} + +/* Token-paste macro for e.g SMIDSR_RSTROBE -> value of SMIDSR_RSTROBE_MASK */ +#define _CONCAT(x, y) x##y +#define CONCAT(x, y) _CONCAT(x, y) + +#define SET_BIT_FIELD(dest, field, bits) ((dest) = \ + ((dest) & ~CONCAT(field, _MASK)) | (((bits) << CONCAT(field, _OFFS))& \ + CONCAT(field, _MASK))) +#define GET_BIT_FIELD(src, field) (((src) & \ + CONCAT(field, _MASK)) >> CONCAT(field, _OFFS)) + +static void smi_dump_context_labelled(struct bcm2835_smi_instance *inst, + const char *label) +{ + dev_err(inst->dev, "SMI context dump: %s", label); + dev_err(inst->dev, "SMICS: 0x%08x", read_smi_reg(inst, SMICS)); + dev_err(inst->dev, "SMIL: 0x%08x", read_smi_reg(inst, SMIL)); + dev_err(inst->dev, "SMIDSR: 0x%08x", read_smi_reg(inst, SMIDSR0)); + dev_err(inst->dev, "SMIDSW: 0x%08x", read_smi_reg(inst, SMIDSW0)); + dev_err(inst->dev, "SMIDC: 0x%08x", read_smi_reg(inst, SMIDC)); + dev_err(inst->dev, "SMIFD: 0x%08x", read_smi_reg(inst, SMIFD)); + dev_err(inst->dev, " "); +} + +static inline void smi_dump_context(struct bcm2835_smi_instance *inst) +{ + smi_dump_context_labelled(inst, ""); +} + +static void smi_get_default_settings(struct bcm2835_smi_instance *inst) +{ + struct smi_settings *settings = &inst->settings; + + settings->data_width = SMI_WIDTH_16BIT; + settings->pack_data = true; + + settings->read_setup_time = 1; + settings->read_hold_time = 1; + settings->read_pace_time = 1; + settings->read_strobe_time = 3; + + settings->write_setup_time = settings->read_setup_time; + settings->write_hold_time = settings->read_hold_time; + settings->write_pace_time = settings->read_pace_time; + settings->write_strobe_time = settings->read_strobe_time; + + settings->dma_enable = true; + settings->dma_passthrough_enable = false; + settings->dma_read_thresh = 0x01; + settings->dma_write_thresh = 0x3f; + settings->dma_panic_read_thresh = 0x20; + settings->dma_panic_write_thresh = 0x20; +} + +void bcm2835_smi_set_regs_from_settings(struct bcm2835_smi_instance *inst) +{ + struct smi_settings *settings = &inst->settings; + int smidsr_temp = 0, smidsw_temp = 0, smics_temp, + smidcs_temp, smidc_temp = 0; + + spin_lock(&inst->transaction_lock); + + /* temporarily disable the peripheral: */ + smics_temp = read_smi_reg(inst, SMICS); + write_smi_reg(inst, 0, SMICS); + smidcs_temp = read_smi_reg(inst, SMIDCS); + write_smi_reg(inst, 0, SMIDCS); + + if (settings->pack_data) + smics_temp |= SMICS_PXLDAT; + else + smics_temp &= ~SMICS_PXLDAT; + + SET_BIT_FIELD(smidsr_temp, SMIDSR_RWIDTH, settings->data_width); + SET_BIT_FIELD(smidsr_temp, SMIDSR_RSETUP, settings->read_setup_time); + SET_BIT_FIELD(smidsr_temp, SMIDSR_RHOLD, settings->read_hold_time); + SET_BIT_FIELD(smidsr_temp, SMIDSR_RPACE, settings->read_pace_time); + SET_BIT_FIELD(smidsr_temp, SMIDSR_RSTROBE, settings->read_strobe_time); + write_smi_reg(inst, smidsr_temp, SMIDSR0); + + SET_BIT_FIELD(smidsw_temp, SMIDSW_WWIDTH, settings->data_width); + if (settings->data_width == SMI_WIDTH_8BIT) + smidsw_temp |= SMIDSW_WSWAP; + else + smidsw_temp &= ~SMIDSW_WSWAP; + SET_BIT_FIELD(smidsw_temp, SMIDSW_WSETUP, settings->write_setup_time); + SET_BIT_FIELD(smidsw_temp, SMIDSW_WHOLD, settings->write_hold_time); + SET_BIT_FIELD(smidsw_temp, SMIDSW_WPACE, settings->write_pace_time); + SET_BIT_FIELD(smidsw_temp, SMIDSW_WSTROBE, + settings->write_strobe_time); + write_smi_reg(inst, smidsw_temp, SMIDSW0); + + SET_BIT_FIELD(smidc_temp, SMIDC_REQR, settings->dma_read_thresh); + SET_BIT_FIELD(smidc_temp, SMIDC_REQW, settings->dma_write_thresh); + SET_BIT_FIELD(smidc_temp, SMIDC_PANICR, + settings->dma_panic_read_thresh); + SET_BIT_FIELD(smidc_temp, SMIDC_PANICW, + settings->dma_panic_write_thresh); + if (settings->dma_passthrough_enable) { + smidc_temp |= SMIDC_DMAP; + smidsr_temp |= SMIDSR_RDREQ; + write_smi_reg(inst, smidsr_temp, SMIDSR0); + smidsw_temp |= SMIDSW_WDREQ; + write_smi_reg(inst, smidsw_temp, SMIDSW0); + } else + smidc_temp &= ~SMIDC_DMAP; + if (settings->dma_enable) + smidc_temp |= SMIDC_DMAEN; + else + smidc_temp &= ~SMIDC_DMAEN; + + write_smi_reg(inst, smidc_temp, SMIDC); + + /* re-enable (if was previously enabled) */ + write_smi_reg(inst, smics_temp, SMICS); + write_smi_reg(inst, smidcs_temp, SMIDCS); + + spin_unlock(&inst->transaction_lock); +} +EXPORT_SYMBOL(bcm2835_smi_set_regs_from_settings); + +struct smi_settings *bcm2835_smi_get_settings_from_regs + (struct bcm2835_smi_instance *inst) +{ + struct smi_settings *settings = &inst->settings; + int smidsr, smidsw, smidc; + + spin_lock(&inst->transaction_lock); + + smidsr = read_smi_reg(inst, SMIDSR0); + smidsw = read_smi_reg(inst, SMIDSW0); + smidc = read_smi_reg(inst, SMIDC); + + settings->pack_data = (read_smi_reg(inst, SMICS) & SMICS_PXLDAT) ? + true : false; + + settings->data_width = GET_BIT_FIELD(smidsr, SMIDSR_RWIDTH); + settings->read_setup_time = GET_BIT_FIELD(smidsr, SMIDSR_RSETUP); + settings->read_hold_time = GET_BIT_FIELD(smidsr, SMIDSR_RHOLD); + settings->read_pace_time = GET_BIT_FIELD(smidsr, SMIDSR_RPACE); + settings->read_strobe_time = GET_BIT_FIELD(smidsr, SMIDSR_RSTROBE); + + settings->write_setup_time = GET_BIT_FIELD(smidsw, SMIDSW_WSETUP); + settings->write_hold_time = GET_BIT_FIELD(smidsw, SMIDSW_WHOLD); + settings->write_pace_time = GET_BIT_FIELD(smidsw, SMIDSW_WPACE); + settings->write_strobe_time = GET_BIT_FIELD(smidsw, SMIDSW_WSTROBE); + + settings->dma_read_thresh = GET_BIT_FIELD(smidc, SMIDC_REQR); + settings->dma_write_thresh = GET_BIT_FIELD(smidc, SMIDC_REQW); + settings->dma_panic_read_thresh = GET_BIT_FIELD(smidc, SMIDC_PANICR); + settings->dma_panic_write_thresh = GET_BIT_FIELD(smidc, SMIDC_PANICW); + settings->dma_passthrough_enable = (smidc & SMIDC_DMAP) ? true : false; + settings->dma_enable = (smidc & SMIDC_DMAEN) ? true : false; + + spin_unlock(&inst->transaction_lock); + + return settings; +} +EXPORT_SYMBOL(bcm2835_smi_get_settings_from_regs); + +static inline void smi_set_address(struct bcm2835_smi_instance *inst, + unsigned int address) +{ + int smia_temp = 0, smida_temp = 0; + + SET_BIT_FIELD(smia_temp, SMIA_ADDR, address); + SET_BIT_FIELD(smida_temp, SMIDA_ADDR, address); + + /* Write to both address registers - user doesn't care whether we're + doing programmed or direct transfers. */ + write_smi_reg(inst, smia_temp, SMIA); + write_smi_reg(inst, smida_temp, SMIDA); +} + +static void smi_setup_regs(struct bcm2835_smi_instance *inst) +{ + + dev_dbg(inst->dev, "Initialising SMI registers..."); + /* Disable the peripheral if already enabled */ + write_smi_reg(inst, 0, SMICS); + write_smi_reg(inst, 0, SMIDCS); + + smi_get_default_settings(inst); + bcm2835_smi_set_regs_from_settings(inst); + smi_set_address(inst, 0); + + write_smi_reg(inst, read_smi_reg(inst, SMICS) | SMICS_ENABLE, SMICS); + write_smi_reg(inst, read_smi_reg(inst, SMIDCS) | SMIDCS_ENABLE, + SMIDCS); +} + +/**************************************************************************** +* +* Low-level SMI access functions +* Other modules should use the exported higher-level functions e.g. +* bcm2835_smi_write_buf() unless they have a good reason to use these +* +***************************************************************************/ + +static inline uint32_t smi_read_single_word(struct bcm2835_smi_instance *inst) +{ + int timeout = 0; + + write_smi_reg(inst, SMIDCS_ENABLE, SMIDCS); + write_smi_reg(inst, SMIDCS_ENABLE | SMIDCS_START, SMIDCS); + /* Make sure things happen in the right order...*/ + mb(); + while (!(read_smi_reg(inst, SMIDCS) & SMIDCS_DONE) && + ++timeout < 10000) + ; + if (timeout < 10000) + return read_smi_reg(inst, SMIDD); + + dev_err(inst->dev, + "SMI direct read timed out (is the clock set up correctly?)"); + return 0; +} + +static inline void smi_write_single_word(struct bcm2835_smi_instance *inst, + uint32_t data) +{ + int timeout = 0; + + write_smi_reg(inst, SMIDCS_ENABLE | SMIDCS_WRITE, SMIDCS); + write_smi_reg(inst, data, SMIDD); + write_smi_reg(inst, SMIDCS_ENABLE | SMIDCS_WRITE | SMIDCS_START, + SMIDCS); + + while (!(read_smi_reg(inst, SMIDCS) & SMIDCS_DONE) && + ++timeout < 10000) + ; + if (timeout >= 10000) + dev_err(inst->dev, + "SMI direct write timed out (is the clock set up correctly?)"); +} + +/* Initiates a programmed read into the read FIFO. It is up to the caller to + * read data from the FIFO - either via paced DMA transfer, + * or polling SMICS_RXD to check whether data is available. + * SMICS_ACTIVE will go low upon completion. */ +static void smi_init_programmed_read(struct bcm2835_smi_instance *inst, + int num_transfers) +{ + int smics_temp; + + /* Disable the peripheral: */ + smics_temp = read_smi_reg(inst, SMICS) & ~(SMICS_ENABLE | SMICS_WRITE); + write_smi_reg(inst, smics_temp, SMICS); + while (read_smi_reg(inst, SMICS) & SMICS_ENABLE) + ; + + /* Program the transfer count: */ + write_smi_reg(inst, num_transfers, SMIL); + + /* re-enable and start: */ + smics_temp |= SMICS_ENABLE; + write_smi_reg(inst, smics_temp, SMICS); + smics_temp |= SMICS_CLEAR; + /* Just to be certain: */ + mb(); + while (read_smi_reg(inst, SMICS) & SMICS_ACTIVE) + ; + write_smi_reg(inst, smics_temp, SMICS); + smics_temp |= SMICS_START; + write_smi_reg(inst, smics_temp, SMICS); +} + +/* Initiates a programmed write sequence, using data from the write FIFO. + * It is up to the caller to initiate a DMA transfer before calling, + * or use another method to keep the write FIFO topped up. + * SMICS_ACTIVE will go low upon completion. + */ +static void smi_init_programmed_write(struct bcm2835_smi_instance *inst, + int num_transfers) +{ + int smics_temp; + + /* Disable the peripheral: */ + smics_temp = read_smi_reg(inst, SMICS) & ~SMICS_ENABLE; + write_smi_reg(inst, smics_temp, SMICS); + while (read_smi_reg(inst, SMICS) & SMICS_ENABLE) + ; + + /* Program the transfer count: */ + write_smi_reg(inst, num_transfers, SMIL); + + /* setup, re-enable and start: */ + smics_temp |= SMICS_WRITE | SMICS_ENABLE; + write_smi_reg(inst, smics_temp, SMICS); + smics_temp |= SMICS_START; + write_smi_reg(inst, smics_temp, SMICS); +} + +/* Initiate a read and then poll FIFO for data, reading out as it appears. */ +static void smi_read_fifo(struct bcm2835_smi_instance *inst, + uint32_t *dest, int n_bytes) +{ + if (read_smi_reg(inst, SMICS) & SMICS_RXD) { + smi_dump_context_labelled(inst, + "WARNING: read FIFO not empty at start of read call."); + while (read_smi_reg(inst, SMICS)) + ; + } + + /* Dispatch the read: */ + if (inst->settings.data_width == SMI_WIDTH_8BIT) + smi_init_programmed_read(inst, n_bytes); + else if (inst->settings.data_width == SMI_WIDTH_16BIT) + smi_init_programmed_read(inst, n_bytes / 2); + else { + dev_err(inst->dev, "Unsupported data width for read."); + return; + } + + /* Poll FIFO to keep it empty */ + while (!(read_smi_reg(inst, SMICS) & SMICS_DONE)) + if (read_smi_reg(inst, SMICS) & SMICS_RXD) + *dest++ = read_smi_reg(inst, SMID); + + /* Ensure that the FIFO is emptied */ + if (read_smi_reg(inst, SMICS) & SMICS_RXD) { + int fifo_count; + + fifo_count = GET_BIT_FIELD(read_smi_reg(inst, SMIFD), + SMIFD_FCNT); + while (fifo_count--) + *dest++ = read_smi_reg(inst, SMID); + } + + if (!(read_smi_reg(inst, SMICS) & SMICS_DONE)) + smi_dump_context_labelled(inst, + "WARNING: transaction finished but done bit not set."); + + if (read_smi_reg(inst, SMICS) & SMICS_RXD) + smi_dump_context_labelled(inst, + "WARNING: read FIFO not empty at end of read call."); + +} + +/* Initiate a write, and then keep the FIFO topped up. */ +static void smi_write_fifo(struct bcm2835_smi_instance *inst, + uint32_t *src, int n_bytes) +{ + int i, timeout = 0; + + /* Empty FIFOs if not already so */ + if (!(read_smi_reg(inst, SMICS) & SMICS_TXE)) { + smi_dump_context_labelled(inst, + "WARNING: write fifo not empty at start of write call."); + write_smi_reg(inst, read_smi_reg(inst, SMICS) | SMICS_CLEAR, + SMICS); + } + + /* Initiate the transfer */ + if (inst->settings.data_width == SMI_WIDTH_8BIT) + smi_init_programmed_write(inst, n_bytes); + else if (inst->settings.data_width == SMI_WIDTH_16BIT) + smi_init_programmed_write(inst, n_bytes / 2); + else { + dev_err(inst->dev, "Unsupported data width for write."); + return; + } + /* Fill the FIFO: */ + for (i = 0; i < (n_bytes - 1) / 4 + 1; ++i) { + while (!(read_smi_reg(inst, SMICS) & SMICS_TXD)) + ; + write_smi_reg(inst, *src++, SMID); + } + /* Busy wait... */ + while (!(read_smi_reg(inst, SMICS) & SMICS_DONE) && ++timeout < + 1000000) + ; + if (timeout >= 1000000) + smi_dump_context_labelled(inst, + "Timed out on write operation!"); + if (!(read_smi_reg(inst, SMICS) & SMICS_TXE)) + smi_dump_context_labelled(inst, + "WARNING: FIFO not empty at end of write operation."); +} + +/**************************************************************************** +* +* SMI DMA operations +* +***************************************************************************/ + +/* Disable SMI and put it into the correct direction before doing DMA setup. + Stops spurious DREQs during setup. Peripheral is re-enabled by init_*() */ +static void smi_disable(struct bcm2835_smi_instance *inst, + enum dma_transfer_direction direction) +{ + int smics_temp = read_smi_reg(inst, SMICS) & ~SMICS_ENABLE; + + if (direction == DMA_DEV_TO_MEM) + smics_temp &= ~SMICS_WRITE; + else + smics_temp |= SMICS_WRITE; + write_smi_reg(inst, smics_temp, SMICS); + while (read_smi_reg(inst, SMICS) & SMICS_ACTIVE) + ; +} + +static struct scatterlist *smi_scatterlist_from_buffer( + struct bcm2835_smi_instance *inst, + dma_addr_t buf, + size_t len, + struct scatterlist *sg) +{ + sg_init_table(sg, 1); + sg_dma_address(sg) = buf; + sg_dma_len(sg) = len; + return sg; +} + +static void smi_dma_callback_user_copy(void *param) +{ + /* Notify the bottom half that a chunk is ready for user copy */ + struct bcm2835_smi_instance *inst = + (struct bcm2835_smi_instance *)param; + + up(&inst->bounce.callback_sem); +} + +/* Creates a descriptor, assigns the given callback, and submits the + descriptor to dmaengine. Does not block - can queue up multiple + descriptors and then wait for them all to complete. + sg_len is the number of control blocks, NOT the number of bytes. + dir can be DMA_MEM_TO_DEV or DMA_DEV_TO_MEM. + callback can be NULL - in this case it is not called. */ +static inline struct dma_async_tx_descriptor *smi_dma_submit_sgl( + struct bcm2835_smi_instance *inst, + struct scatterlist *sgl, + size_t sg_len, + enum dma_transfer_direction dir, + dma_async_tx_callback callback) +{ + struct dma_async_tx_descriptor *desc; + + desc = dmaengine_prep_slave_sg(inst->dma_chan, + sgl, + sg_len, + dir, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK | + DMA_PREP_FENCE); + if (!desc) { + dev_err(inst->dev, "read_sgl: dma slave preparation failed!"); + write_smi_reg(inst, read_smi_reg(inst, SMICS) & ~SMICS_ACTIVE, + SMICS); + while (read_smi_reg(inst, SMICS) & SMICS_ACTIVE) + cpu_relax(); + write_smi_reg(inst, read_smi_reg(inst, SMICS) | SMICS_ACTIVE, + SMICS); + return NULL; + } + desc->callback = callback; + desc->callback_param = inst; + if (dmaengine_submit(desc) < 0) + return NULL; + return desc; +} + +/* NB this function blocks until the transfer is complete */ +static void +smi_dma_read_sgl(struct bcm2835_smi_instance *inst, + struct scatterlist *sgl, size_t sg_len, size_t n_bytes) +{ + struct dma_async_tx_descriptor *desc; + + /* Disable SMI and set to read before dispatching DMA - if SMI is in + * write mode and TX fifo is empty, it will generate a DREQ which may + * cause the read DMA to complete before the SMI read command is even + * dispatched! We want to dispatch DMA before SMI read so that reading + * is gapless, for logic analyser. + */ + + smi_disable(inst, DMA_DEV_TO_MEM); + + desc = smi_dma_submit_sgl(inst, sgl, sg_len, DMA_DEV_TO_MEM, NULL); + dma_async_issue_pending(inst->dma_chan); + + if (inst->settings.data_width == SMI_WIDTH_8BIT) + smi_init_programmed_read(inst, n_bytes); + else + smi_init_programmed_read(inst, n_bytes / 2); + + if (dma_wait_for_async_tx(desc) == DMA_ERROR) + smi_dump_context_labelled(inst, "DMA timeout!"); +} + +static void +smi_dma_write_sgl(struct bcm2835_smi_instance *inst, + struct scatterlist *sgl, size_t sg_len, size_t n_bytes) +{ + struct dma_async_tx_descriptor *desc; + + if (inst->settings.data_width == SMI_WIDTH_8BIT) + smi_init_programmed_write(inst, n_bytes); + else + smi_init_programmed_write(inst, n_bytes / 2); + + desc = smi_dma_submit_sgl(inst, sgl, sg_len, DMA_MEM_TO_DEV, NULL); + dma_async_issue_pending(inst->dma_chan); + + if (dma_wait_for_async_tx(desc) == DMA_ERROR) + smi_dump_context_labelled(inst, "DMA timeout!"); + else + /* Wait for SMI to finish our writes */ + while (!(read_smi_reg(inst, SMICS) & SMICS_DONE)) + cpu_relax(); +} + +ssize_t bcm2835_smi_user_dma( + struct bcm2835_smi_instance *inst, + enum dma_transfer_direction dma_dir, + char __user *user_ptr, size_t count, + struct bcm2835_smi_bounce_info **bounce) +{ + int chunk_no = 0, chunk_size, count_left = count; + struct scatterlist *sgl; + void (*init_trans_func)(struct bcm2835_smi_instance *, int); + + spin_lock(&inst->transaction_lock); + + if (dma_dir == DMA_DEV_TO_MEM) + init_trans_func = smi_init_programmed_read; + else + init_trans_func = smi_init_programmed_write; + + smi_disable(inst, dma_dir); + + sema_init(&inst->bounce.callback_sem, 0); + if (bounce) + *bounce = &inst->bounce; + while (count_left) { + chunk_size = count_left > DMA_BOUNCE_BUFFER_SIZE ? + DMA_BOUNCE_BUFFER_SIZE : count_left; + if (chunk_size == DMA_BOUNCE_BUFFER_SIZE) { + sgl = + &inst->bounce.sgl[chunk_no % DMA_BOUNCE_BUFFER_COUNT]; + } else { + sgl = smi_scatterlist_from_buffer( + inst, + inst->bounce.phys[ + chunk_no % DMA_BOUNCE_BUFFER_COUNT], + chunk_size, + &inst->buffer_sgl); + } + + if (!smi_dma_submit_sgl(inst, sgl, 1, dma_dir, + smi_dma_callback_user_copy + )) { + dev_err(inst->dev, "sgl submit failed"); + count = 0; + goto out; + } + count_left -= chunk_size; + chunk_no++; + } + dma_async_issue_pending(inst->dma_chan); + + if (inst->settings.data_width == SMI_WIDTH_8BIT) + init_trans_func(inst, count); + else if (inst->settings.data_width == SMI_WIDTH_16BIT) + init_trans_func(inst, count / 2); +out: + spin_unlock(&inst->transaction_lock); + return count; +} +EXPORT_SYMBOL(bcm2835_smi_user_dma); + + +/**************************************************************************** +* +* High level buffer transfer functions - for use by other drivers +* +***************************************************************************/ + +/* Buffer must be physically contiguous - i.e. kmalloc, not vmalloc! */ +void bcm2835_smi_write_buf( + struct bcm2835_smi_instance *inst, + const void *buf, size_t n_bytes) +{ + int odd_bytes = n_bytes & 0x3; + + n_bytes -= odd_bytes; + + spin_lock(&inst->transaction_lock); + + if (n_bytes > DMA_THRESHOLD_BYTES) { + dma_addr_t phy_addr = dma_map_single( + inst->dev, + (void *)buf, + n_bytes, + DMA_TO_DEVICE); + struct scatterlist *sgl = + smi_scatterlist_from_buffer(inst, phy_addr, n_bytes, + &inst->buffer_sgl); + + if (!sgl) { + smi_dump_context_labelled(inst, + "Error: could not create scatterlist for write!"); + goto out; + } + smi_dma_write_sgl(inst, sgl, 1, n_bytes); + + dma_unmap_single + (inst->dev, phy_addr, n_bytes, DMA_TO_DEVICE); + } else if (n_bytes) { + smi_write_fifo(inst, (uint32_t *) buf, n_bytes); + } + buf += n_bytes; + + if (inst->settings.data_width == SMI_WIDTH_8BIT) { + while (odd_bytes--) + smi_write_single_word(inst, *(uint8_t *) (buf++)); + } else { + while (odd_bytes >= 2) { + smi_write_single_word(inst, *(uint16_t *)buf); + buf += 2; + odd_bytes -= 2; + } + if (odd_bytes) { + /* Reading an odd number of bytes on a 16 bit bus is + a user bug. It's kinder to fail early and tell them + than to e.g. transparently give them the bottom byte + of a 16 bit transfer. */ + dev_err(inst->dev, + "WARNING: odd number of bytes specified for wide transfer."); + dev_err(inst->dev, + "At least one byte dropped as a result."); + dump_stack(); + } + } +out: + spin_unlock(&inst->transaction_lock); +} +EXPORT_SYMBOL(bcm2835_smi_write_buf); + +void bcm2835_smi_read_buf(struct bcm2835_smi_instance *inst, + void *buf, size_t n_bytes) +{ + + /* SMI is inherently 32-bit, which causes surprising amounts of mess + for bytes % 4 != 0. Easiest to avoid this mess altogether + by handling remainder separately. */ + int odd_bytes = n_bytes & 0x3; + + spin_lock(&inst->transaction_lock); + n_bytes -= odd_bytes; + if (n_bytes > DMA_THRESHOLD_BYTES) { + dma_addr_t phy_addr = dma_map_single(inst->dev, + buf, n_bytes, + DMA_FROM_DEVICE); + struct scatterlist *sgl = smi_scatterlist_from_buffer( + inst, phy_addr, n_bytes, + &inst->buffer_sgl); + if (!sgl) { + smi_dump_context_labelled(inst, + "Error: could not create scatterlist for read!"); + goto out; + } + smi_dma_read_sgl(inst, sgl, 1, n_bytes); + dma_unmap_single(inst->dev, phy_addr, n_bytes, DMA_FROM_DEVICE); + } else if (n_bytes) { + smi_read_fifo(inst, (uint32_t *)buf, n_bytes); + } + buf += n_bytes; + + if (inst->settings.data_width == SMI_WIDTH_8BIT) { + while (odd_bytes--) + *((uint8_t *) (buf++)) = smi_read_single_word(inst); + } else { + while (odd_bytes >= 2) { + *(uint16_t *) buf = smi_read_single_word(inst); + buf += 2; + odd_bytes -= 2; + } + if (odd_bytes) { + dev_err(inst->dev, + "WARNING: odd number of bytes specified for wide transfer."); + dev_err(inst->dev, + "At least one byte dropped as a result."); + dump_stack(); + } + } +out: + spin_unlock(&inst->transaction_lock); +} +EXPORT_SYMBOL(bcm2835_smi_read_buf); + +void bcm2835_smi_set_address(struct bcm2835_smi_instance *inst, + unsigned int address) +{ + spin_lock(&inst->transaction_lock); + smi_set_address(inst, address); + spin_unlock(&inst->transaction_lock); +} +EXPORT_SYMBOL(bcm2835_smi_set_address); + +struct bcm2835_smi_instance *bcm2835_smi_get(struct device_node *node) +{ + struct platform_device *pdev; + + if (!node) + return NULL; + + pdev = of_find_device_by_node(node); + if (!pdev) + return NULL; + + return platform_get_drvdata(pdev); +} +EXPORT_SYMBOL(bcm2835_smi_get); + +/**************************************************************************** +* +* bcm2835_smi_probe - called when the driver is loaded. +* +***************************************************************************/ + +static int bcm2835_smi_dma_setup(struct bcm2835_smi_instance *inst) +{ + int i, rv = 0; + + inst->dma_chan = dma_request_slave_channel(inst->dev, "rx-tx"); + + inst->dma_config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + inst->dma_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + inst->dma_config.src_addr = inst->smi_regs_busaddr + SMID; + inst->dma_config.dst_addr = inst->dma_config.src_addr; + /* Direction unimportant - always overridden by prep_slave_sg */ + inst->dma_config.direction = DMA_DEV_TO_MEM; + dmaengine_slave_config(inst->dma_chan, &inst->dma_config); + /* Alloc and map bounce buffers */ + for (i = 0; i < DMA_BOUNCE_BUFFER_COUNT; ++i) { + inst->bounce.buffer[i] = + dmam_alloc_coherent(inst->dev, DMA_BOUNCE_BUFFER_SIZE, + &inst->bounce.phys[i], + GFP_KERNEL); + if (!inst->bounce.buffer[i]) { + dev_err(inst->dev, "Could not allocate buffer!"); + rv = -ENOMEM; + break; + } + smi_scatterlist_from_buffer( + inst, + inst->bounce.phys[i], + DMA_BOUNCE_BUFFER_SIZE, + &inst->bounce.sgl[i] + ); + } + + return rv; +} + +static int bcm2835_smi_probe(struct platform_device *pdev) +{ + int err; + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct resource *ioresource; + struct bcm2835_smi_instance *inst; + + /* We require device tree support */ + if (!node) + return -EINVAL; + /* Allocate buffers and instance data */ + inst = devm_kzalloc(dev, sizeof(struct bcm2835_smi_instance), + GFP_KERNEL); + if (!inst) + return -ENOMEM; + + inst->dev = dev; + spin_lock_init(&inst->transaction_lock); + + inst->smi_regs_ptr = devm_platform_get_and_ioremap_resource(pdev, 0, + &ioresource); + if (IS_ERR(inst->smi_regs_ptr)) { + err = PTR_ERR(inst->smi_regs_ptr); + goto err; + } + inst->smi_regs_busaddr = ioresource->start; + + err = bcm2835_smi_dma_setup(inst); + if (err) + goto err; + + /* request clock */ + inst->clk = devm_clk_get(dev, NULL); + if (!inst->clk) + goto err; + clk_prepare_enable(inst->clk); + + /* Finally, do peripheral setup */ + smi_setup_regs(inst); + + platform_set_drvdata(pdev, inst); + + dev_info(inst->dev, "initialised"); + + return 0; +err: + kfree(inst); + return err; +} + +/**************************************************************************** +* +* bcm2835_smi_remove - called when the driver is unloaded. +* +***************************************************************************/ + +static void bcm2835_smi_remove(struct platform_device *pdev) +{ + struct bcm2835_smi_instance *inst = platform_get_drvdata(pdev); + struct device *dev = inst->dev; + + dmaengine_terminate_all(inst->dma_chan); + dma_release_channel(inst->dma_chan); + + clk_disable_unprepare(inst->clk); + + dev_info(dev, "SMI device removed - OK"); +} + +/**************************************************************************** +* +* Register the driver with device tree +* +***************************************************************************/ + +static const struct of_device_id bcm2835_smi_of_match[] = { + {.compatible = "brcm,bcm2835-smi",}, + { /* sentinel */ }, +}; + +MODULE_DEVICE_TABLE(of, bcm2835_smi_of_match); + +static struct platform_driver bcm2835_smi_driver = { + .probe = bcm2835_smi_probe, + .remove = bcm2835_smi_remove, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = bcm2835_smi_of_match, + }, +}; + +module_platform_driver(bcm2835_smi_driver); + +MODULE_ALIAS("platform:smi-bcm2835"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Device driver for BCM2835's secondary memory interface"); +MODULE_AUTHOR("Luke Wren <luke@raspberrypi.org>"); diff --git a/drivers/misc/rp1-fw-pio.h b/drivers/misc/rp1-fw-pio.h new file mode 100644 index 00000000000000..ba28cba38f84c4 --- /dev/null +++ b/drivers/misc/rp1-fw-pio.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2023 2023-2024 Raspberry Pi Ltd. + */ + +#ifndef __SOC_RP1_FIRMWARE_OPS_H__ +#define __SOC_RP1_FIRMWARE_OPS_H__ + +#include <linux/rp1-firmware.h> + +#define FOURCC_PIO RP1_FOURCC("PIO ") + +enum rp1_pio_ops { + PIO_CAN_ADD_PROGRAM, // u16 num_instrs, u16 origin -> origin + PIO_ADD_PROGRAM, // u16 num_instrs, u16 origin, u16 prog[] -> rc + PIO_REMOVE_PROGRAM, // u16 num_instrs, u16 origin + PIO_CLEAR_INSTR_MEM, // - + + PIO_SM_CLAIM, // u16 mask -> sm + PIO_SM_UNCLAIM, // u16 mask + PIO_SM_IS_CLAIMED, // u16 mask -> claimed + + PIO_SM_INIT, // u16 sm, u16 initial_pc, u32 sm_config[4] + PIO_SM_SET_CONFIG, // u16 sm, u16 rsvd, u32 sm_config[4] + PIO_SM_EXEC, // u16 sm, u16 instr, u8 blocking, u8 rsvd + PIO_SM_CLEAR_FIFOS, // u16 sm + PIO_SM_SET_CLKDIV, // u16 sm, u16 div_int, u8 div_frac, u8 rsvd + PIO_SM_SET_PINS, // u16 sm, u16 rsvd, u32 values, u32 mask + PIO_SM_SET_PINDIRS, // u16 sm, u16 rsvd, u32 dirs, u32 mask + PIO_SM_SET_ENABLED, // u16 mask, u8 enable, u8 rsvd + PIO_SM_RESTART, // u16 mask + PIO_SM_CLKDIV_RESTART, // u16 mask + PIO_SM_ENABLE_SYNC, // u16 mask + PIO_SM_PUT, // u16 sm, u8 blocking, u8 rsvd, u32 data + PIO_SM_GET, // u16 sm, u8 blocking, u8 rsvd -> u32 data + PIO_SM_SET_DMACTRL, // u16 sm, u16 is_tx, u32 ctrl + + GPIO_INIT, // u16 gpio + GPIO_SET_FUNCTION, // u16 gpio, u16 fn + GPIO_SET_PULLS, // u16 gpio, u8 up, u8 down + GPIO_SET_OUTOVER, // u16 gpio, u16 value + GPIO_SET_INOVER, // u16 gpio, u16 value + GPIO_SET_OEOVER, // u16 gpio, u16 value + GPIO_SET_INPUT_ENABLED, // u16 gpio, u16 value + GPIO_SET_DRIVE_STRENGTH, // u16 gpio, u16 value + + READ_HW, // src address, len -> data bytes + WRITE_HW, // dst address, data + + PIO_SM_FIFO_STATE, // u16 sm, u8 tx -> u16 level, u8 empty, u8 full + PIO_SM_DRAIN_TX, // u16 sm + + PIO_COUNT +}; + +#endif diff --git a/drivers/misc/rp1-pio.c b/drivers/misc/rp1-pio.c new file mode 100644 index 00000000000000..41f0a6d9c49a5e --- /dev/null +++ b/drivers/misc/rp1-pio.c @@ -0,0 +1,1389 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * PIO driver for RP1 + * + * Copyright (C) 2023-2024 Raspberry Pi Ltd. + * + * Parts of this driver are based on: + * - vcio.c, by Noralf Trønnes + * Copyright (C) 2010 Broadcom + * Copyright (C) 2015 Noralf Trønnes + * Copyright (C) 2021 Raspberry Pi (Trading) Ltd. + * - bcm2835_smi.c & bcm2835_smi_dev.c by Luke Wren + * Copyright (c) 2015 Raspberry Pi (Trading) Ltd. + */ + +#include <linux/cdev.h> +#include <linux/compat.h> +#include <linux/device.h> +#include <linux/dmaengine.h> +#include <linux/dma-mapping.h> +#include <linux/fs.h> +#include <linux/init.h> +#include <linux/ioctl.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/pio_rp1.h> +#include <linux/platform_device.h> +#include <linux/rp1-firmware.h> +#include <linux/semaphore.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/uaccess.h> +#include <uapi/misc/rp1_pio_if.h> + +#include "rp1-fw-pio.h" + +#define DRIVER_NAME "rp1-pio" + +#define RP1_PIO_SMS_COUNT 4 +#define RP1_PIO_INSTR_COUNT 32 + +#define MAX_ARG_SIZE 256 + +#define RP1_PIO_FIFO_TX0 0x00 +#define RP1_PIO_FIFO_TX1 0x04 +#define RP1_PIO_FIFO_TX2 0x08 +#define RP1_PIO_FIFO_TX3 0x0c +#define RP1_PIO_FIFO_RX0 0x10 +#define RP1_PIO_FIFO_RX1 0x14 +#define RP1_PIO_FIFO_RX2 0x18 +#define RP1_PIO_FIFO_RX3 0x1c + +#define RP1_PIO_DMACTRL_DEFAULT 0x80000104 + +#define HANDLER(_n, _f) \ + [_IOC_NR(PIO_IOC_ ## _n)] = { #_n, rp1_pio_ ## _f, _IOC_SIZE(PIO_IOC_ ## _n) } + + +#define ROUND_UP(x, y) (((x) + (y) - 1) - (((x) + (y) - 1) % (y))) + +#define DMA_BOUNCE_BUFFER_SIZE 0x1000 +#define DMA_BOUNCE_BUFFER_COUNT 4 + +struct dma_xfer_state { + struct dma_info *dma; + void (*callback)(void *param); + void *callback_param; +}; + +struct dma_buf_info { + void *buf; + dma_addr_t dma_addr; + struct scatterlist sgl; +}; + +struct dma_info { + struct semaphore buf_sem; + struct dma_chan *chan; + size_t buf_size; + size_t buf_count; + unsigned int head_idx; + unsigned int tail_idx; + struct dma_buf_info bufs[DMA_BOUNCE_BUFFER_COUNT]; +}; + +struct rp1_pio_device { + struct platform_device *pdev; + struct rp1_firmware *fw; + uint16_t fw_pio_base; + uint16_t fw_pio_count; + dev_t dev_num; + struct class *dev_class; + struct cdev cdev; + phys_addr_t phys_addr; + uint32_t claimed_sms; + uint32_t claimed_dmas; + spinlock_t lock; + struct mutex instr_mutex; + struct dma_info dma_configs[RP1_PIO_SMS_COUNT][RP1_PIO_DIR_COUNT]; + uint32_t used_instrs; + uint8_t instr_refcounts[RP1_PIO_INSTR_COUNT]; + uint16_t instrs[RP1_PIO_INSTR_COUNT]; + uint client_count; +}; + +struct rp1_pio_client { + struct rp1_pio_device *pio; + uint32_t claimed_sms; + uint32_t claimed_instrs; + uint32_t claimed_dmas; + int error; +}; + +static struct rp1_pio_device *g_pio; + +static int rp1_pio_message(struct rp1_pio_device *pio, + uint16_t op, const void *data, unsigned int data_len) +{ + uint32_t rc; + int ret; + + if (op >= pio->fw_pio_count) + return -EOPNOTSUPP; + ret = rp1_firmware_message(pio->fw, pio->fw_pio_base + op, + data, data_len, + &rc, sizeof(rc)); + if (ret == 4) + ret = rc; + return ret; +} + +static int rp1_pio_message_resp(struct rp1_pio_device *pio, + uint16_t op, const void *data, unsigned int data_len, + void *resp, void __user *userbuf, unsigned int resp_len) +{ + uint32_t resp_buf[1 + 32]; + int ret; + + if (op >= pio->fw_pio_count) + return -EOPNOTSUPP; + if (resp_len + 4 >= sizeof(resp_buf)) + return -EINVAL; + if (!resp && !userbuf) + return -EINVAL; + ret = rp1_firmware_message(pio->fw, pio->fw_pio_base + op, + data, data_len, + resp_buf, resp_len + 4); + if (ret >= 4 && !resp_buf[0]) { + ret -= 4; + if (resp) + memcpy(resp, &resp_buf[1], ret); + else if (copy_to_user(userbuf, &resp_buf[1], ret)) + ret = -EFAULT; + } else if (ret >= 0) { + ret = -EIO; + } + return ret; +} + +static int rp1_pio_read_hw(struct rp1_pio_client *client, void *param) +{ + struct rp1_pio_device *pio = client->pio; + struct rp1_access_hw_args *args = param; + + return rp1_pio_message_resp(pio, READ_HW, + args, 8, NULL, args->data, args->len); +} + +static int rp1_pio_write_hw(struct rp1_pio_client *client, void *param) +{ + struct rp1_pio_device *pio = client->pio; + struct rp1_access_hw_args *args = param; + uint32_t write_buf[32 + 1]; + int len; + + len = min(args->len, sizeof(write_buf) - 4); + write_buf[0] = args->addr; + if (copy_from_user(&write_buf[1], args->data, len)) + return -EFAULT; + return rp1_firmware_message(pio->fw, pio->fw_pio_base + WRITE_HW, + write_buf, 4 + len, NULL, 0); +} + +static int rp1_pio_find_program(struct rp1_pio_device *pio, + struct rp1_pio_add_program_args *prog) +{ + uint start, end, prog_size; + uint32_t used_mask; + uint i; + + start = (prog->origin != RP1_PIO_ORIGIN_ANY) ? prog->origin : 0; + end = (prog->origin != RP1_PIO_ORIGIN_ANY) ? prog->origin : + (RP1_PIO_INSTRUCTION_COUNT - prog->num_instrs); + prog_size = sizeof(prog->instrs[0]) * prog->num_instrs; + used_mask = (uint32_t)(~0) >> (32 - prog->num_instrs); + + /* Find the best match */ + for (i = start; i <= end; i++) { + uint32_t mask = used_mask << i; + + if ((pio->used_instrs & mask) != mask) + continue; + if (!memcmp(pio->instrs + i, prog->instrs, prog_size)) + return i; + } + + return -1; +} + +int rp1_pio_can_add_program(struct rp1_pio_client *client, void *param) +{ + struct rp1_pio_add_program_args *args = param; + struct rp1_pio_device *pio = client->pio; + int offset; + + if (args->num_instrs > RP1_PIO_INSTR_COUNT || + ((args->origin != RP1_PIO_ORIGIN_ANY) && + (args->origin >= RP1_PIO_INSTR_COUNT || + ((args->origin + args->num_instrs) > RP1_PIO_INSTR_COUNT)))) + return -EINVAL; + + mutex_lock(&pio->instr_mutex); + offset = rp1_pio_find_program(pio, args); + mutex_unlock(&pio->instr_mutex); + if (offset >= 0) + return offset; + + /* Don't send the instructions, just the header */ + return rp1_pio_message(pio, PIO_CAN_ADD_PROGRAM, args, + offsetof(struct rp1_pio_add_program_args, instrs)); +} +EXPORT_SYMBOL_GPL(rp1_pio_can_add_program); + +int rp1_pio_add_program(struct rp1_pio_client *client, void *param) +{ + struct rp1_pio_add_program_args *args = param; + struct rp1_pio_device *pio = client->pio; + int offset; + uint i; + + if (args->num_instrs > RP1_PIO_INSTR_COUNT || + ((args->origin != RP1_PIO_ORIGIN_ANY) && + (args->origin >= RP1_PIO_INSTR_COUNT || + ((args->origin + args->num_instrs) > RP1_PIO_INSTR_COUNT)))) + return -EINVAL; + + mutex_lock(&pio->instr_mutex); + offset = rp1_pio_find_program(pio, args); + if (offset < 0) + offset = rp1_pio_message(client->pio, PIO_ADD_PROGRAM, args, sizeof(*args)); + + if (offset >= 0) { + uint32_t used_mask; + uint prog_size; + + used_mask = ((uint32_t)(~0) >> (-args->num_instrs & 0x1f)) << offset; + prog_size = sizeof(args->instrs[0]) * args->num_instrs; + + if ((pio->used_instrs & used_mask) != used_mask) { + pio->used_instrs |= used_mask; + memcpy(pio->instrs + offset, args->instrs, prog_size); + } + client->claimed_instrs |= used_mask; + for (i = 0; i < args->num_instrs; i++) + pio->instr_refcounts[offset + i]++; + } + mutex_unlock(&pio->instr_mutex); + return offset; +} +EXPORT_SYMBOL_GPL(rp1_pio_add_program); + +static void rp1_pio_remove_instrs(struct rp1_pio_device *pio, uint32_t mask) +{ + struct rp1_pio_remove_program_args args; + uint i; + + mutex_lock(&pio->instr_mutex); + args.num_instrs = 0; + for (i = 0; ; i++, mask >>= 1) { + if ((mask & 1) && pio->instr_refcounts[i] && !--pio->instr_refcounts[i]) { + pio->used_instrs &= ~(1 << i); + args.num_instrs++; + } else if (args.num_instrs) { + args.origin = i - args.num_instrs; + rp1_pio_message(pio, PIO_REMOVE_PROGRAM, &args, sizeof(args)); + args.num_instrs = 0; + } + if (!mask) + break; + } + mutex_unlock(&pio->instr_mutex); +} + +int rp1_pio_remove_program(struct rp1_pio_client *client, void *param) +{ + struct rp1_pio_remove_program_args *args = param; + uint32_t used_mask; + int ret = -ENOENT; + + if (args->num_instrs > RP1_PIO_INSTR_COUNT || + args->origin >= RP1_PIO_INSTR_COUNT || + (args->origin + args->num_instrs) > RP1_PIO_INSTR_COUNT) + return -EINVAL; + + used_mask = ((uint32_t)(~0) >> (32 - args->num_instrs)) << args->origin; + if ((client->claimed_instrs & used_mask) == used_mask) { + client->claimed_instrs &= ~used_mask; + rp1_pio_remove_instrs(client->pio, used_mask); + ret = 0; + } + return ret; +} +EXPORT_SYMBOL_GPL(rp1_pio_remove_program); + +int rp1_pio_clear_instr_mem(struct rp1_pio_client *client, void *param) +{ + struct rp1_pio_device *pio = client->pio; + + mutex_lock(&pio->instr_mutex); + (void)rp1_pio_message(client->pio, PIO_CLEAR_INSTR_MEM, NULL, 0); + memset(pio->instr_refcounts, 0, sizeof(pio->instr_refcounts)); + pio->used_instrs = 0; + client->claimed_instrs = 0; + mutex_unlock(&pio->instr_mutex); + return 0; +} +EXPORT_SYMBOL_GPL(rp1_pio_clear_instr_mem); + +int rp1_pio_sm_claim(struct rp1_pio_client *client, void *param) +{ + struct rp1_pio_sm_claim_args *args = param; + struct rp1_pio_device *pio = client->pio; + int ret; + + mutex_lock(&pio->instr_mutex); + ret = rp1_pio_message(client->pio, PIO_SM_CLAIM, args, sizeof(*args)); + if (ret >= 0) { + if (args->mask) + client->claimed_sms |= args->mask; + else + client->claimed_sms |= (1 << ret); + pio->claimed_sms |= client->claimed_sms; + } + mutex_unlock(&pio->instr_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(rp1_pio_sm_claim); + +int rp1_pio_sm_unclaim(struct rp1_pio_client *client, void *param) +{ + struct rp1_pio_sm_claim_args *args = param; + struct rp1_pio_device *pio = client->pio; + + mutex_lock(&pio->instr_mutex); + (void)rp1_pio_message(client->pio, PIO_SM_UNCLAIM, args, sizeof(*args)); + client->claimed_sms &= ~args->mask; + pio->claimed_sms &= ~args->mask; + mutex_unlock(&pio->instr_mutex); + return 0; +} +EXPORT_SYMBOL_GPL(rp1_pio_sm_unclaim); + +int rp1_pio_sm_is_claimed(struct rp1_pio_client *client, void *param) +{ + struct rp1_pio_sm_claim_args *args = param; + + return rp1_pio_message(client->pio, PIO_SM_IS_CLAIMED, args, sizeof(*args)); +} +EXPORT_SYMBOL_GPL(rp1_pio_sm_is_claimed); + +int rp1_pio_sm_init(struct rp1_pio_client *client, void *param) +{ + struct rp1_pio_sm_init_args *args = param; + + return rp1_pio_message(client->pio, PIO_SM_INIT, args, sizeof(*args)); +} +EXPORT_SYMBOL_GPL(rp1_pio_sm_init); + +int rp1_pio_sm_set_config(struct rp1_pio_client *client, void *param) +{ + struct rp1_pio_sm_set_config_args *args = param; + + return rp1_pio_message(client->pio, PIO_SM_SET_CONFIG, args, sizeof(*args)); +} +EXPORT_SYMBOL_GPL(rp1_pio_sm_set_config); + +int rp1_pio_sm_exec(struct rp1_pio_client *client, void *param) +{ + struct rp1_pio_sm_exec_args *args = param; + + return rp1_pio_message(client->pio, PIO_SM_EXEC, args, sizeof(*args)); +} +EXPORT_SYMBOL_GPL(rp1_pio_sm_exec); + +int rp1_pio_sm_clear_fifos(struct rp1_pio_client *client, void *param) +{ + struct rp1_pio_sm_clear_fifos_args *args = param; + + return rp1_pio_message(client->pio, PIO_SM_CLEAR_FIFOS, args, sizeof(*args)); +} +EXPORT_SYMBOL_GPL(rp1_pio_sm_clear_fifos); + +int rp1_pio_sm_set_clkdiv(struct rp1_pio_client *client, void *param) +{ + struct rp1_pio_sm_set_clkdiv_args *args = param; + + return rp1_pio_message(client->pio, PIO_SM_SET_CLKDIV, args, sizeof(*args)); +} +EXPORT_SYMBOL_GPL(rp1_pio_sm_set_clkdiv); + +int rp1_pio_sm_set_pins(struct rp1_pio_client *client, void *param) +{ + struct rp1_pio_sm_set_pins_args *args = param; + + return rp1_pio_message(client->pio, PIO_SM_SET_PINS, args, sizeof(*args)); +} +EXPORT_SYMBOL_GPL(rp1_pio_sm_set_pins); + +int rp1_pio_sm_set_pindirs(struct rp1_pio_client *client, void *param) +{ + struct rp1_pio_sm_set_pindirs_args *args = param; + + return rp1_pio_message(client->pio, PIO_SM_SET_PINDIRS, args, sizeof(*args)); +} +EXPORT_SYMBOL_GPL(rp1_pio_sm_set_pindirs); + +int rp1_pio_sm_set_enabled(struct rp1_pio_client *client, void *param) +{ + struct rp1_pio_sm_set_enabled_args *args = param; + + return rp1_pio_message(client->pio, PIO_SM_SET_ENABLED, args, sizeof(*args)); +} +EXPORT_SYMBOL_GPL(rp1_pio_sm_set_enabled); + +int rp1_pio_sm_restart(struct rp1_pio_client *client, void *param) +{ + struct rp1_pio_sm_restart_args *args = param; + + return rp1_pio_message(client->pio, PIO_SM_RESTART, args, sizeof(*args)); +} +EXPORT_SYMBOL_GPL(rp1_pio_sm_restart); + +int rp1_pio_sm_clkdiv_restart(struct rp1_pio_client *client, void *param) +{ + struct rp1_pio_sm_restart_args *args = param; + + return rp1_pio_message(client->pio, PIO_SM_CLKDIV_RESTART, args, sizeof(*args)); +} +EXPORT_SYMBOL_GPL(rp1_pio_sm_clkdiv_restart); + +int rp1_pio_sm_enable_sync(struct rp1_pio_client *client, void *param) +{ + struct rp1_pio_sm_enable_sync_args *args = param; + + return rp1_pio_message(client->pio, PIO_SM_ENABLE_SYNC, args, sizeof(*args)); +} +EXPORT_SYMBOL_GPL(rp1_pio_sm_enable_sync); + +int rp1_pio_sm_put(struct rp1_pio_client *client, void *param) +{ + struct rp1_pio_sm_put_args *args = param; + + return rp1_pio_message(client->pio, PIO_SM_PUT, args, sizeof(*args)); +} +EXPORT_SYMBOL_GPL(rp1_pio_sm_put); + +int rp1_pio_sm_get(struct rp1_pio_client *client, void *param) +{ + struct rp1_pio_sm_get_args *args = param; + int ret; + + ret = rp1_pio_message_resp(client->pio, PIO_SM_GET, args, sizeof(*args), + &args->data, NULL, sizeof(args->data)); + if (ret >= 0) + return offsetof(struct rp1_pio_sm_get_args, data) + ret; + return ret; +} +EXPORT_SYMBOL_GPL(rp1_pio_sm_get); + +int rp1_pio_sm_set_dmactrl(struct rp1_pio_client *client, void *param) +{ + struct rp1_pio_sm_set_dmactrl_args *args = param; + + return rp1_pio_message(client->pio, PIO_SM_SET_DMACTRL, args, sizeof(*args)); +} +EXPORT_SYMBOL_GPL(rp1_pio_sm_set_dmactrl); + +int rp1_pio_sm_fifo_state(struct rp1_pio_client *client, void *param) +{ + struct rp1_pio_sm_fifo_state_args *args = param; + const int level_offset = offsetof(struct rp1_pio_sm_fifo_state_args, level); + int ret; + + ret = rp1_pio_message_resp(client->pio, PIO_SM_FIFO_STATE, args, sizeof(*args), + &args->level, NULL, sizeof(*args) - level_offset); + if (ret >= 0) + return level_offset + ret; + return ret; +} +EXPORT_SYMBOL_GPL(rp1_pio_sm_fifo_state); + +int rp1_pio_sm_drain_tx(struct rp1_pio_client *client, void *param) +{ + struct rp1_pio_sm_clear_fifos_args *args = param; + + return rp1_pio_message(client->pio, PIO_SM_DRAIN_TX, args, sizeof(*args)); +} +EXPORT_SYMBOL_GPL(rp1_pio_sm_drain_tx); + +int rp1_pio_gpio_init(struct rp1_pio_client *client, void *param) +{ + struct rp1_gpio_init_args *args = param; + + return rp1_pio_message(client->pio, GPIO_INIT, args, sizeof(*args)); +} +EXPORT_SYMBOL_GPL(rp1_pio_gpio_init); + +int rp1_pio_gpio_set_function(struct rp1_pio_client *client, void *param) +{ + struct rp1_gpio_set_function_args *args = param; + + return rp1_pio_message(client->pio, GPIO_SET_FUNCTION, args, sizeof(*args)); +} +EXPORT_SYMBOL_GPL(rp1_pio_gpio_set_function); + +int rp1_pio_gpio_set_pulls(struct rp1_pio_client *client, void *param) +{ + struct rp1_gpio_set_pulls_args *args = param; + + return rp1_pio_message(client->pio, GPIO_SET_PULLS, args, sizeof(*args)); +} +EXPORT_SYMBOL_GPL(rp1_pio_gpio_set_pulls); + +int rp1_pio_gpio_set_outover(struct rp1_pio_client *client, void *param) +{ + struct rp1_gpio_set_args *args = param; + + return rp1_pio_message(client->pio, GPIO_SET_OUTOVER, args, sizeof(*args)); +} +EXPORT_SYMBOL_GPL(rp1_pio_gpio_set_outover); + +int rp1_pio_gpio_set_inover(struct rp1_pio_client *client, void *param) +{ + struct rp1_gpio_set_args *args = param; + + return rp1_pio_message(client->pio, GPIO_SET_INOVER, args, sizeof(*args)); +} +EXPORT_SYMBOL_GPL(rp1_pio_gpio_set_inover); + +int rp1_pio_gpio_set_oeover(struct rp1_pio_client *client, void *param) +{ + struct rp1_gpio_set_args *args = param; + + return rp1_pio_message(client->pio, GPIO_SET_OEOVER, args, sizeof(*args)); +} +EXPORT_SYMBOL_GPL(rp1_pio_gpio_set_oeover); + +int rp1_pio_gpio_set_input_enabled(struct rp1_pio_client *client, void *param) +{ + struct rp1_gpio_set_args *args = param; + + return rp1_pio_message(client->pio, GPIO_SET_INPUT_ENABLED, args, sizeof(*args)); +} +EXPORT_SYMBOL_GPL(rp1_pio_gpio_set_input_enabled); + +int rp1_pio_gpio_set_drive_strength(struct rp1_pio_client *client, void *param) +{ + struct rp1_gpio_set_args *args = param; + + return rp1_pio_message(client->pio, GPIO_SET_DRIVE_STRENGTH, args, sizeof(*args)); +} +EXPORT_SYMBOL_GPL(rp1_pio_gpio_set_drive_strength); + +static void rp1_pio_sm_dma_callback(void *param) +{ + struct dma_info *dma = param; + + up(&dma->buf_sem); +} + +static void rp1_pio_sm_kernel_dma_callback(void *param) +{ + struct dma_xfer_state *dxs = param; + + dxs->dma->tail_idx++; + up(&dxs->dma->buf_sem); + + dxs->callback(dxs->callback_param); + + kfree(dxs); +} + +static void rp1_pio_sm_dma_free(struct device *dev, struct dma_info *dma) +{ + dmaengine_terminate_all(dma->chan); + while (dma->buf_count > 0) { + dma->buf_count--; + dma_free_coherent(dev, ROUND_UP(dma->buf_size, PAGE_SIZE), + dma->bufs[dma->buf_count].buf, + dma->bufs[dma->buf_count].dma_addr); + } + + dma_release_channel(dma->chan); +} + +static int rp1_pio_sm_config_xfer_internal(struct rp1_pio_client *client, uint sm, uint dir, + uint buf_size, uint buf_count) +{ + struct rp1_pio_sm_set_dmactrl_args set_dmactrl_args; + struct rp1_pio_device *pio = client->pio; + struct platform_device *pdev = pio->pdev; + struct device *dev = &pdev->dev; + struct dma_slave_config config = {}; + phys_addr_t fifo_addr; + struct dma_info *dma; + uint32_t dma_mask; + char chan_name[4]; + int ret = 0; + + if (sm >= RP1_PIO_SMS_COUNT || dir >= RP1_PIO_DIR_COUNT) + return -EINVAL; + if ((buf_count || buf_size) && + (!buf_size || (buf_size & 3) || + !buf_count || buf_count > DMA_BOUNCE_BUFFER_COUNT)) + return -EINVAL; + + dma_mask = 1 << (sm * 2 + dir); + + dma = &pio->dma_configs[sm][dir]; + + spin_lock(&pio->lock); + if (pio->claimed_dmas & dma_mask) + rp1_pio_sm_dma_free(dev, dma); + pio->claimed_dmas |= dma_mask; + client->claimed_dmas |= dma_mask; + spin_unlock(&pio->lock); + + dma->buf_size = buf_size; + /* Round up the allocations */ + buf_size = ROUND_UP(buf_size, PAGE_SIZE); + sema_init(&dma->buf_sem, 0); + + /* Allocate and configure a DMA channel */ + /* Careful - each SM FIFO has its own DREQ value */ + chan_name[0] = (dir == RP1_PIO_DIR_TO_SM) ? 't' : 'r'; + chan_name[1] = 'x'; + chan_name[2] = '0' + sm; + chan_name[3] = '\0'; + + dma->chan = dma_request_chan(dev, chan_name); + if (IS_ERR(dma->chan)) + return PTR_ERR(dma->chan); + + /* Alloc and map bounce buffers */ + for (dma->buf_count = 0; dma->buf_count < buf_count; dma->buf_count++) { + struct dma_buf_info *dbi = &dma->bufs[dma->buf_count]; + + dbi->buf = dma_alloc_coherent(dma->chan->device->dev, buf_size, + &dbi->dma_addr, GFP_KERNEL); + if (!dbi->buf) { + ret = -ENOMEM; + goto err_dma_free; + } + sg_init_table(&dbi->sgl, 1); + sg_dma_address(&dbi->sgl) = dbi->dma_addr; + } + + fifo_addr = pio->phys_addr; + fifo_addr += sm * (RP1_PIO_FIFO_TX1 - RP1_PIO_FIFO_TX0); + fifo_addr += (dir == RP1_PIO_DIR_TO_SM) ? RP1_PIO_FIFO_TX0 : RP1_PIO_FIFO_RX0; + + config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + config.src_addr = fifo_addr; + config.dst_addr = fifo_addr; + config.direction = (dir == RP1_PIO_DIR_TO_SM) ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM; + + ret = dmaengine_slave_config(dma->chan, &config); + if (ret) + goto err_dma_free; + + set_dmactrl_args.sm = sm; + set_dmactrl_args.is_tx = (dir == RP1_PIO_DIR_TO_SM); + set_dmactrl_args.ctrl = RP1_PIO_DMACTRL_DEFAULT; + if (dir == RP1_PIO_DIR_FROM_SM) + set_dmactrl_args.ctrl = (RP1_PIO_DMACTRL_DEFAULT & ~0x1f) | 1; + + ret = rp1_pio_sm_set_dmactrl(client, &set_dmactrl_args); + if (ret) + goto err_dma_free; + + return 0; + +err_dma_free: + rp1_pio_sm_dma_free(dev, dma); + + spin_lock(&pio->lock); + client->claimed_dmas &= ~dma_mask; + pio->claimed_dmas &= ~dma_mask; + spin_unlock(&pio->lock); + + return ret; +} + +static int rp1_pio_sm_config_xfer_user(struct rp1_pio_client *client, void *param) +{ + struct rp1_pio_sm_config_xfer_args *args = param; + + return rp1_pio_sm_config_xfer_internal(client, args->sm, args->dir, + args->buf_size, args->buf_count); +} + +static int rp1_pio_sm_config_xfer32_user(struct rp1_pio_client *client, void *param) +{ + struct rp1_pio_sm_config_xfer32_args *args = param; + + return rp1_pio_sm_config_xfer_internal(client, args->sm, args->dir, + args->buf_size, args->buf_count); +} + +static int rp1_pio_sm_tx_user(struct rp1_pio_device *pio, struct dma_info *dma, + const void __user *userbuf, size_t bytes) +{ + struct platform_device *pdev = pio->pdev; + struct dma_async_tx_descriptor *desc; + struct device *dev = &pdev->dev; + int ret = 0; + + /* Clean the slate - we're running synchronously */ + dma->head_idx = 0; + dma->tail_idx = 0; + + while (bytes > 0) { + size_t copy_bytes = min(bytes, dma->buf_size); + struct dma_buf_info *dbi; + + /* grab the next free buffer, waiting if they're all full */ + if (dma->head_idx - dma->tail_idx == dma->buf_count) { + if (down_timeout(&dma->buf_sem, + msecs_to_jiffies(1000))) { + dev_err(dev, "DMA bounce timed out\n"); + break; + } + dma->tail_idx++; + } + + dbi = &dma->bufs[dma->head_idx % dma->buf_count]; + + sg_dma_len(&dbi->sgl) = copy_bytes; + + ret = copy_from_user(dbi->buf, userbuf, copy_bytes); + if (ret < 0) + break; + + userbuf += copy_bytes; + + desc = dmaengine_prep_slave_sg(dma->chan, &dbi->sgl, 1, + DMA_MEM_TO_DEV, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK | + DMA_PREP_FENCE); + if (!desc) { + dev_err(dev, "DMA preparation failed\n"); + ret = -EIO; + break; + } + + desc->callback = rp1_pio_sm_dma_callback; + desc->callback_param = dma; + + /* Submit the buffer - the callback will kick the semaphore */ + ret = dmaengine_submit(desc); + if (ret < 0) + break; + ret = 0; + + dma_async_issue_pending(dma->chan); + + dma->head_idx++; + bytes -= copy_bytes; + } + + /* Block for completion */ + while (dma->tail_idx != dma->head_idx) { + if (down_timeout(&dma->buf_sem, msecs_to_jiffies(1000))) { + dev_err(dev, "DMA wait timed out\n"); + ret = -ETIMEDOUT; + break; + } + dma->tail_idx++; + } + + return ret; +} + +static int rp1_pio_sm_rx_user(struct rp1_pio_device *pio, struct dma_info *dma, + void __user *userbuf, size_t bytes) +{ + struct platform_device *pdev = pio->pdev; + struct dma_async_tx_descriptor *desc; + struct device *dev = &pdev->dev; + int ret = 0; + + /* Clean the slate - we're running synchronously */ + dma->head_idx = 0; + dma->tail_idx = 0; + + while (bytes || dma->tail_idx != dma->head_idx) { + size_t copy_bytes = min(bytes, dma->buf_size); + struct dma_buf_info *dbi; + + /* + * wait for the next RX to complete if all the buffers are + * outstanding or we're finishing up. + */ + if (!bytes || dma->head_idx - dma->tail_idx == dma->buf_count) { + if (down_timeout(&dma->buf_sem, + msecs_to_jiffies(1000))) { + dev_err(dev, "DMA wait timed out\n"); + ret = -ETIMEDOUT; + break; + } + + dbi = &dma->bufs[dma->tail_idx++ % dma->buf_count]; + ret = copy_to_user(userbuf, dbi->buf, sg_dma_len(&dbi->sgl)); + if (ret < 0) + break; + userbuf += sg_dma_len(&dbi->sgl); + + if (!bytes) + continue; + } + + dbi = &dma->bufs[dma->head_idx % dma->buf_count]; + sg_dma_len(&dbi->sgl) = copy_bytes; + desc = dmaengine_prep_slave_sg(dma->chan, &dbi->sgl, 1, + DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK | + DMA_PREP_FENCE); + if (!desc) { + dev_err(dev, "DMA preparation failed\n"); + ret = -EIO; + break; + } + + desc->callback = rp1_pio_sm_dma_callback; + desc->callback_param = dma; + + /* Submit the buffer - the callback will kick the semaphore */ + ret = dmaengine_submit(desc); + if (ret < 0) + break; + + dma_async_issue_pending(dma->chan); + + dma->head_idx++; + bytes -= copy_bytes; + } + + return ret; +} + +static int rp1_pio_sm_xfer_data32_user(struct rp1_pio_client *client, void *param) +{ + struct rp1_pio_sm_xfer_data32_args *args = param; + struct rp1_pio_device *pio = client->pio; + struct dma_info *dma; + + if (args->sm >= RP1_PIO_SMS_COUNT || args->dir >= RP1_PIO_DIR_COUNT || + !args->data_bytes || !args->data) + return -EINVAL; + + dma = &pio->dma_configs[args->sm][args->dir]; + + if (args->dir == RP1_PIO_DIR_TO_SM) + return rp1_pio_sm_tx_user(pio, dma, args->data, args->data_bytes); + else + return rp1_pio_sm_rx_user(pio, dma, args->data, args->data_bytes); +} + +static int rp1_pio_sm_xfer_data_user(struct rp1_pio_client *client, void *param) +{ + struct rp1_pio_sm_xfer_data_args *args = param; + struct rp1_pio_sm_xfer_data32_args args32; + + args32.sm = args->sm; + args32.dir = args->dir; + args32.data_bytes = args->data_bytes; + args32.data = args->data; + + return rp1_pio_sm_xfer_data32_user(client, &args32); +} + +int rp1_pio_sm_config_xfer(struct rp1_pio_client *client, uint sm, uint dir, + uint buf_size, uint buf_count) +{ + return rp1_pio_sm_config_xfer_internal(client, sm, dir, buf_size, buf_count); +} +EXPORT_SYMBOL_GPL(rp1_pio_sm_config_xfer); + +int rp1_pio_sm_xfer_data(struct rp1_pio_client *client, uint sm, uint dir, + uint data_bytes, void *data, dma_addr_t dma_addr, + void (*callback)(void *param), void *param) +{ + struct rp1_pio_device *pio = client->pio; + struct platform_device *pdev = pio->pdev; + struct dma_async_tx_descriptor *desc; + struct dma_xfer_state *dxs = NULL; + struct device *dev = &pdev->dev; + struct dma_buf_info *dbi = NULL; + struct scatterlist sg; + struct dma_info *dma; + int ret = 0; + + if (sm >= RP1_PIO_SMS_COUNT || dir >= RP1_PIO_DIR_COUNT) + return -EINVAL; + + dma = &pio->dma_configs[sm][dir]; + + if (!dma_addr) { + dxs = kmalloc(sizeof(*dxs), GFP_KERNEL); + dxs->dma = dma; + dxs->callback = callback; + dxs->callback_param = param; + callback = rp1_pio_sm_kernel_dma_callback; + param = dxs; + + if (!dma->buf_count || data_bytes > dma->buf_size) + return -EINVAL; + + /* Grab a dma buffer */ + if (dma->head_idx - dma->tail_idx == dma->buf_count) { + if (down_timeout(&dma->buf_sem, msecs_to_jiffies(1000))) { + dev_err(dev, "DMA wait timed out\n"); + return -ETIMEDOUT; + } + } + + dbi = &dma->bufs[dma->head_idx % dma->buf_count]; + dma_addr = dbi->dma_addr; + + if (dir == PIO_DIR_TO_SM) + memcpy(dbi->buf, data, data_bytes); + } + + sg_init_table(&sg, 1); + sg_dma_address(&sg) = dma_addr; + sg_dma_len(&sg) = data_bytes; + + desc = dmaengine_prep_slave_sg(dma->chan, &sg, 1, + (dir == PIO_DIR_TO_SM) ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK | + DMA_PREP_FENCE); + if (!desc) { + dev_err(dev, "DMA preparation failed\n"); + return -EIO; + } + + desc->callback = callback; + desc->callback_param = param; + + ret = dmaengine_submit(desc); + if (ret < 0) { + dev_err(dev, "dmaengine_submit failed (%d)\n", ret); + return ret; + } + + dma_async_issue_pending(dma->chan); + + return 0; +} +EXPORT_SYMBOL_GPL(rp1_pio_sm_xfer_data); + +struct handler_info { + const char *name; + int (*func)(struct rp1_pio_client *client, void *param); + int argsize; +} ioctl_handlers[] = { + HANDLER(SM_CONFIG_XFER, sm_config_xfer_user), + HANDLER(SM_XFER_DATA, sm_xfer_data_user), + HANDLER(SM_XFER_DATA32, sm_xfer_data32_user), + HANDLER(SM_CONFIG_XFER32, sm_config_xfer32_user), + + HANDLER(CAN_ADD_PROGRAM, can_add_program), + HANDLER(ADD_PROGRAM, add_program), + HANDLER(REMOVE_PROGRAM, remove_program), + HANDLER(CLEAR_INSTR_MEM, clear_instr_mem), + + HANDLER(SM_CLAIM, sm_claim), + HANDLER(SM_UNCLAIM, sm_unclaim), + HANDLER(SM_IS_CLAIMED, sm_is_claimed), + + HANDLER(SM_INIT, sm_init), + HANDLER(SM_SET_CONFIG, sm_set_config), + HANDLER(SM_EXEC, sm_exec), + HANDLER(SM_CLEAR_FIFOS, sm_clear_fifos), + HANDLER(SM_SET_CLKDIV, sm_set_clkdiv), + HANDLER(SM_SET_PINS, sm_set_pins), + HANDLER(SM_SET_PINDIRS, sm_set_pindirs), + HANDLER(SM_SET_ENABLED, sm_set_enabled), + HANDLER(SM_RESTART, sm_restart), + HANDLER(SM_CLKDIV_RESTART, sm_clkdiv_restart), + HANDLER(SM_ENABLE_SYNC, sm_enable_sync), + HANDLER(SM_PUT, sm_put), + HANDLER(SM_GET, sm_get), + HANDLER(SM_SET_DMACTRL, sm_set_dmactrl), + HANDLER(SM_FIFO_STATE, sm_fifo_state), + HANDLER(SM_DRAIN_TX, sm_drain_tx), + + HANDLER(GPIO_INIT, gpio_init), + HANDLER(GPIO_SET_FUNCTION, gpio_set_function), + HANDLER(GPIO_SET_PULLS, gpio_set_pulls), + HANDLER(GPIO_SET_OUTOVER, gpio_set_outover), + HANDLER(GPIO_SET_INOVER, gpio_set_inover), + HANDLER(GPIO_SET_OEOVER, gpio_set_oeover), + HANDLER(GPIO_SET_INPUT_ENABLED, gpio_set_input_enabled), + HANDLER(GPIO_SET_DRIVE_STRENGTH, gpio_set_drive_strength), + + HANDLER(READ_HW, read_hw), + HANDLER(WRITE_HW, write_hw), +}; + +struct rp1_pio_client *rp1_pio_open(void) +{ + struct rp1_pio_client *client; + + if (!g_pio) + return ERR_PTR(-ENOENT); + + client = kzalloc(sizeof(*client), GFP_KERNEL); + if (!client) + return ERR_PTR(-ENOMEM); + + client->pio = g_pio; + + return client; +} +EXPORT_SYMBOL_GPL(rp1_pio_open); + +void rp1_pio_close(struct rp1_pio_client *client) +{ + struct rp1_pio_device *pio = client->pio; + uint claimed_dmas = client->claimed_dmas; + int i; + + /* Free any allocated resources */ + + for (i = 0; claimed_dmas; i++) { + uint mask = (1 << i); + + if (claimed_dmas & mask) { + struct dma_info *dma = &pio->dma_configs[i >> 1][i & 1]; + + claimed_dmas &= ~mask; + rp1_pio_sm_dma_free(&pio->pdev->dev, dma); + } + } + + spin_lock(&pio->lock); + pio->claimed_dmas &= ~client->claimed_dmas; + spin_unlock(&pio->lock); + + if (client->claimed_sms) { + struct rp1_pio_sm_set_enabled_args se_args = { + .mask = client->claimed_sms, .enable = 0 + }; + struct rp1_pio_sm_claim_args uc_args = { + .mask = client->claimed_sms + }; + + rp1_pio_sm_set_enabled(client, &se_args); + rp1_pio_sm_unclaim(client, &uc_args); + } + + if (client->claimed_instrs) + rp1_pio_remove_instrs(pio, client->claimed_instrs); + + /* Reinitialise the SM? */ + + kfree(client); +} +EXPORT_SYMBOL_GPL(rp1_pio_close); + +void rp1_pio_set_error(struct rp1_pio_client *client, int err) +{ + client->error = err; +} +EXPORT_SYMBOL_GPL(rp1_pio_set_error); + +int rp1_pio_get_error(const struct rp1_pio_client *client) +{ + return client->error; +} +EXPORT_SYMBOL_GPL(rp1_pio_get_error); + +void rp1_pio_clear_error(struct rp1_pio_client *client) +{ + client->error = 0; +} +EXPORT_SYMBOL_GPL(rp1_pio_clear_error); + +static int rp1_pio_file_open(struct inode *inode, struct file *filp) +{ + struct rp1_pio_client *client; + + client = rp1_pio_open(); + if (IS_ERR(client)) + return PTR_ERR(client); + + filp->private_data = client; + + return 0; +} + +static int rp1_pio_file_release(struct inode *inode, struct file *filp) +{ + struct rp1_pio_client *client = filp->private_data; + + rp1_pio_close(client); + + return 0; +} + +static long rp1_pio_ioctl(struct file *filp, unsigned int ioctl_num, + unsigned long ioctl_param) +{ + struct rp1_pio_client *client = filp->private_data; + struct device *dev = &client->pio->pdev->dev; + void __user *argp = (void __user *)ioctl_param; + int nr = _IOC_NR(ioctl_num); + int sz = _IOC_SIZE(ioctl_num); + struct handler_info *hdlr = &ioctl_handlers[nr]; + uint32_t argbuf[MAX_ARG_SIZE/sizeof(uint32_t)]; + int ret; + + if (nr >= ARRAY_SIZE(ioctl_handlers) || !hdlr->func) { + dev_err(dev, "unknown ioctl: %x\n", ioctl_num); + return -EOPNOTSUPP; + } + + if (sz != hdlr->argsize) { + dev_err(dev, "wrong %s argsize (expected %d, got %d)\n", + hdlr->name, hdlr->argsize, sz); + return -EINVAL; + } + + if (copy_from_user(argbuf, argp, sz)) + return -EFAULT; + + ret = (hdlr->func)(client, argbuf); + dev_dbg(dev, "%s: %s -> %d\n", __func__, hdlr->name, ret); + if (ret > 0) { + if (copy_to_user(argp, argbuf, ret)) + ret = -EFAULT; + } + + return ret; +} + +#ifdef CONFIG_COMPAT + +struct rp1_pio_sm_xfer_data_args_compat { + uint16_t sm; + uint16_t dir; + uint16_t data_bytes; + compat_uptr_t data; +}; + +struct rp1_pio_sm_xfer_data32_args_compat { + uint16_t sm; + uint16_t dir; + uint32_t data_bytes; + compat_uptr_t data; +}; + +struct rp1_access_hw_args_compat { + uint32_t addr; + uint32_t len; + compat_uptr_t data; +}; + +#define PIO_IOC_SM_XFER_DATA_COMPAT \ + _IOW(PIO_IOC_MAGIC, 1, struct rp1_pio_sm_xfer_data_args_compat) +#define PIO_IOC_SM_XFER_DATA32_COMPAT \ + _IOW(PIO_IOC_MAGIC, 2, struct rp1_pio_sm_xfer_data32_args_compat) +#define PIO_IOC_READ_HW_COMPAT _IOW(PIO_IOC_MAGIC, 8, struct rp1_access_hw_args_compat) +#define PIO_IOC_WRITE_HW_COMPAT _IOW(PIO_IOC_MAGIC, 9, struct rp1_access_hw_args_compat) + +static long rp1_pio_compat_ioctl(struct file *filp, unsigned int ioctl_num, + unsigned long ioctl_param) +{ + struct rp1_pio_client *client = filp->private_data; + + switch (ioctl_num) { + case PIO_IOC_SM_XFER_DATA_COMPAT: + { + struct rp1_pio_sm_xfer_data_args_compat compat_param; + struct rp1_pio_sm_xfer_data_args param; + + if (copy_from_user(&compat_param, compat_ptr(ioctl_param), sizeof(compat_param))) + return -EFAULT; + param.sm = compat_param.sm; + param.dir = compat_param.dir; + param.data_bytes = compat_param.data_bytes; + param.data = compat_ptr(compat_param.data); + return rp1_pio_sm_xfer_data_user(client, ¶m); + } + case PIO_IOC_SM_XFER_DATA32_COMPAT: + { + struct rp1_pio_sm_xfer_data32_args_compat compat_param; + struct rp1_pio_sm_xfer_data32_args param; + + if (copy_from_user(&compat_param, compat_ptr(ioctl_param), sizeof(compat_param))) + return -EFAULT; + param.sm = compat_param.sm; + param.dir = compat_param.dir; + param.data_bytes = compat_param.data_bytes; + param.data = compat_ptr(compat_param.data); + return rp1_pio_sm_xfer_data32_user(client, ¶m); + } + + case PIO_IOC_READ_HW_COMPAT: + case PIO_IOC_WRITE_HW_COMPAT: + { + struct rp1_access_hw_args_compat compat_param; + struct rp1_access_hw_args param; + + if (copy_from_user(&compat_param, compat_ptr(ioctl_param), sizeof(compat_param))) + return -EFAULT; + param.addr = compat_param.addr; + param.len = compat_param.len; + param.data = compat_ptr(compat_param.data); + if (ioctl_num == PIO_IOC_READ_HW_COMPAT) + return rp1_pio_read_hw(client, ¶m); + else + return rp1_pio_write_hw(client, ¶m); + } + default: + return rp1_pio_ioctl(filp, ioctl_num, ioctl_param); + } +} +#else +#define rp1_pio_compat_ioctl NULL +#endif + +const struct file_operations rp1_pio_fops = { + .owner = THIS_MODULE, + .open = rp1_pio_file_open, + .release = rp1_pio_file_release, + .unlocked_ioctl = rp1_pio_ioctl, + .compat_ioctl = rp1_pio_compat_ioctl, +}; + +static int rp1_pio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *ioresource; + struct rp1_pio_device *pio; + struct rp1_firmware *fw; + uint32_t op_count = 0; + uint32_t op_base = 0; + struct device *cdev; + char dev_name[16]; + void *p; + int ret; + int i; + + /* Run-time check for a build-time misconfiguration */ + for (i = 0; i < ARRAY_SIZE(ioctl_handlers); i++) { + struct handler_info *hdlr = &ioctl_handlers[i]; + + if (WARN_ON(hdlr->argsize > MAX_ARG_SIZE)) + return -EINVAL; + } + + pdev->id = of_alias_get_id(pdev->dev.of_node, "pio"); + if (pdev->id < 0) + return dev_err_probe(dev, pdev->id, "alias is missing\n"); + + fw = devm_rp1_firmware_get(dev, dev->of_node); + if (!fw) + return dev_err_probe(dev, -EPROBE_DEFER, "failed to find RP1 firmware driver\n"); + if (IS_ERR(fw)) { + dev_warn(dev, "failed to contact RP1 firmware\n"); + return PTR_ERR(fw); + } + ret = rp1_firmware_get_feature(fw, FOURCC_PIO, &op_base, &op_count); + if (ret < 0) + return ret; + + pio = devm_kzalloc(&pdev->dev, sizeof(*pio), GFP_KERNEL); + if (!pio) + return -ENOMEM; + + platform_set_drvdata(pdev, pio); + pio->fw_pio_base = op_base; + pio->fw_pio_count = op_count; + pio->pdev = pdev; + pio->fw = fw; + spin_lock_init(&pio->lock); + mutex_init(&pio->instr_mutex); + + p = devm_platform_get_and_ioremap_resource(pdev, 0, &ioresource); + if (IS_ERR(p)) + return PTR_ERR(p); + + pio->phys_addr = ioresource->start; + + ret = alloc_chrdev_region(&pio->dev_num, 0, 1, DRIVER_NAME); + if (ret < 0) { + dev_err(dev, "alloc_chrdev_region failed (rc=%d)\n", ret); + goto out_err; + } + + pio->dev_class = class_create(DRIVER_NAME); + if (IS_ERR(pio->dev_class)) { + ret = PTR_ERR(pio->dev_class); + dev_err(dev, "class_create failed (err %d)\n", ret); + goto out_unregister; + } + + cdev_init(&pio->cdev, &rp1_pio_fops); + ret = cdev_add(&pio->cdev, pio->dev_num, 1); + if (ret) { + dev_err(dev, "cdev_add failed (err %d)\n", ret); + goto out_class_destroy; + } + + sprintf(dev_name, "pio%d", pdev->id); + cdev = device_create(pio->dev_class, NULL, pio->dev_num, NULL, dev_name); + if (IS_ERR(cdev)) { + ret = PTR_ERR(cdev); + dev_err(dev, "%s: device_create failed (err %d)\n", __func__, ret); + goto out_cdev_del; + } + + g_pio = pio; + + dev_info(dev, "Created instance as %s\n", dev_name); + return 0; + +out_cdev_del: + cdev_del(&pio->cdev); + +out_class_destroy: + class_destroy(pio->dev_class); + +out_unregister: + unregister_chrdev_region(pio->dev_num, 1); + +out_err: + return ret; +} + +static void rp1_pio_remove(struct platform_device *pdev) +{ + struct rp1_pio_device *pio = platform_get_drvdata(pdev); + + /* There should be no clients */ + + if (g_pio == pio) + g_pio = NULL; + + device_destroy(pio->dev_class, pio->dev_num); + cdev_del(&pio->cdev); + class_destroy(pio->dev_class); + unregister_chrdev_region(pio->dev_num, 1); +} + +static const struct of_device_id rp1_pio_ids[] = { + { .compatible = "raspberrypi,rp1-pio" }, + { } +}; +MODULE_DEVICE_TABLE(of, rp1_pio_ids); + +static struct platform_driver rp1_pio_driver = { + .driver = { + .name = "rp1-pio", + .of_match_table = of_match_ptr(rp1_pio_ids), + }, + .probe = rp1_pio_probe, + .remove_new = rp1_pio_remove, + .shutdown = rp1_pio_remove, +}; + +module_platform_driver(rp1_pio_driver); + +MODULE_DESCRIPTION("PIO controller driver for Raspberry Pi RP1"); +MODULE_AUTHOR("Phil Elwell"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/ws2812-pio-rp1.c b/drivers/misc/ws2812-pio-rp1.c new file mode 100644 index 00000000000000..ee840c6b33122e --- /dev/null +++ b/drivers/misc/ws2812-pio-rp1.c @@ -0,0 +1,507 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Raspberry Pi PIO-based WS2812 driver + * + * Copyright (C) 2014-2024 Raspberry Pi Ltd. + * + * Author: Phil Elwell (phil@raspberrypi.com) + * + * Based on the ws2812 driver by Gordon Hollingworth <gordon@raspberrypi.com> + */ + +#include <linux/cdev.h> +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/fcntl.h> +#include <linux/file.h> +#include <linux/fs.h> +#include <linux/pio_rp1.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/gpio/consumer.h> + +#define DRIVER_NAME "ws2812-pio-rp1" +#define MAX_INSTANCES 4 + +#define RESET_US 50 +#define PIXEL_BYTES 4 + +struct ws2812_pio_rp1_state { + struct device *dev; + struct gpio_desc *gpiod; + struct gpio_desc *power_gpiod; + uint gpio; + PIO pio; + uint sm; + uint offset; + + u8 *buffer; + u8 *pixbuf; + u32 pixbuf_size; + u32 write_end; + + u8 brightness; + u32 invert; + u32 num_leds; + u32 xfer_end_us; + bool is_rgbw; + struct delayed_work deferred_work; + + struct completion dma_completion; + struct cdev cdev; + dev_t dev_num; + const char *dev_name; +}; + +static DEFINE_MUTEX(ws2812_pio_mutex); +static DEFINE_IDA(ws2812_pio_ida); +static long ws2812_pio_ref_count; +static struct class *ws2812_pio_class; +static dev_t ws2812_pio_dev_num; +/* + * WS2812B gamma correction + * GammaE=255*(res/255).^(1/.45) + * From: http://rgb-123.com/ws2812-color-output/ + */ + +static const u8 ws2812_gamma[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, + 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, + 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 11, 11, + 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, + 19, 19, 20, 21, 21, 22, 22, 23, 23, 24, 25, 25, 26, 27, 27, 28, + 29, 29, 30, 31, 31, 32, 33, 34, 34, 35, 36, 37, 37, 38, 39, 40, + 40, 41, 42, 43, 44, 45, 46, 46, 47, 48, 49, 50, 51, 52, 53, 54, + 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, + 71, 72, 73, 74, 76, 77, 78, 79, 80, 81, 83, 84, 85, 86, 88, 89, + 90, 91, 93, 94, 95, 96, 98, 99, 100, 102, 103, 104, 106, 107, 109, 110, + 111, 113, 114, 116, 117, 119, 120, 121, 123, 124, 126, 128, 129, 131, 132, 134, + 135, 137, 138, 140, 142, 143, 145, 146, 148, 150, 151, 153, 155, 157, 158, 160, + 162, 163, 165, 167, 169, 170, 172, 174, 176, 178, 179, 181, 183, 185, 187, 189, + 191, 193, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, + 222, 224, 227, 229, 231, 233, 235, 237, 239, 241, 244, 246, 248, 250, 252, 255 +}; + +// ------ // +// ws2812 // +// ------ // + +#define ws2812_wrap_target 0 +#define ws2812_wrap 3 + +#define ws2812_T1 3 +#define ws2812_T2 4 +#define ws2812_T3 3 + +static const uint16_t ws2812_program_instructions[] = { + // .wrap_target + 0x6221, // 0: out x, 1 side 0 [2] + 0x1223, // 1: jmp !x, 3 side 1 [2] + 0x1300, // 2: jmp 0 side 1 [3] + 0xa342, // 3: nop side 0 [3] + // .wrap +}; + +static const struct pio_program ws2812_program = { + .instructions = ws2812_program_instructions, + .length = 4, + .origin = -1, +}; + +static inline pio_sm_config ws2812_program_get_default_config(uint offset) +{ + pio_sm_config c = pio_get_default_sm_config(); + + sm_config_set_wrap(&c, offset + ws2812_wrap_target, offset + ws2812_wrap); + sm_config_set_sideset(&c, 1, false, false); + return c; +} + +static inline void ws2812_program_init(PIO pio, uint sm, uint offset, uint pin, uint freq, + bool rgbw) +{ + int cycles_per_bit = ws2812_T1 + ws2812_T2 + ws2812_T3; + struct fp24_8 div; + pio_sm_config c; + + pio_gpio_init(pio, pin); + pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true); + c = ws2812_program_get_default_config(offset); + sm_config_set_sideset_pins(&c, pin); + sm_config_set_out_shift(&c, false, true, rgbw ? 32 : 24); + sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); + div = make_fp24_8(clock_get_hz(clk_sys), freq * cycles_per_bit); + sm_config_set_clkdiv(&c, div); + pio_sm_init(pio, sm, offset, &c); + pio_sm_set_enabled(pio, sm, true); +} + +static uint8_t ws2812_apply_gamma(uint8_t brightness, uint8_t val) +{ + int bright; + + if (!val) + return 0; + bright = (val * brightness) / 255; + return ws2812_gamma[bright]; +} + +static inline uint8_t *rgbw_u32(const struct ws2812_pio_rp1_state *state, + uint8_t r, uint8_t g, uint8_t b, uint8_t w, uint8_t *p) +{ + p[0] = ws2812_apply_gamma(state->brightness, w); + p[1] = ws2812_apply_gamma(state->brightness, b); + p[2] = ws2812_apply_gamma(state->brightness, r); + p[3] = ws2812_apply_gamma(state->brightness, g); + return p + 4; +} + +static void ws2812_dma_complete(void *param) +{ + struct ws2812_pio_rp1_state *state = param; + + complete(&state->dma_completion); +} + +static void ws2812_update_leds(struct ws2812_pio_rp1_state *state, uint length) +{ + init_completion(&state->dma_completion); + if (!pio_sm_xfer_data(state->pio, state->sm, PIO_DIR_TO_SM, length, state->buffer, 0, + (void (*)(void *))ws2812_dma_complete, state)) { + wait_for_completion(&state->dma_completion); + usleep_range(RESET_US, RESET_US + 100); + } +} + +static void ws2812_clear_leds(struct ws2812_pio_rp1_state *state) +{ + uint8_t *p_buffer; + uint length; + int i; + + p_buffer = state->buffer; + for (i = 0; i < state->num_leds; i++) + p_buffer = rgbw_u32(state, 0, 0, 0, 0, p_buffer); + + length = (void *)p_buffer - (void *)state->buffer; + + ws2812_update_leds(state, length); +} + +/* + * Function to write the RGB buffer to the WS2812 leds, the input buffer + * contains a sequence of up to num_leds RGB32 integers, these are then + * gamma-corrected before being sent to the PIO state machine. + */ + +static ssize_t ws2812_pio_rp1_write(struct file *filp, const char __user *buf, size_t count, + loff_t *ppos) +{ + struct ws2812_pio_rp1_state *state; + uint32_t pixbuf_size; + unsigned long delay; + loff_t pos = *ppos; + int err = 0; + + state = (struct ws2812_pio_rp1_state *)filp->private_data; + pixbuf_size = state->pixbuf_size; + + if (pos > pixbuf_size) + return -EFBIG; + + if (count > pixbuf_size) { + err = -EFBIG; + count = pixbuf_size; + } + + if (pos + count > pixbuf_size) { + if (!err) + err = -ENOSPC; + + count = pixbuf_size - pos; + } + + if (!pos && count == 1) { + if (copy_from_user(&state->brightness, buf, 1)) + return -EFAULT; + } else { + if (copy_from_user(state->pixbuf + pos, buf, count)) + return -EFAULT; + pos += count; + state->write_end = (u32)pos; + } + + *ppos = pos; + + delay = (state->write_end == pixbuf_size) ? 0 : HZ / 20; + schedule_delayed_work(&state->deferred_work, delay); + + return err ? err : count; +} + +static void ws2812_pio_rp1_deferred_work(struct work_struct *work) +{ + struct ws2812_pio_rp1_state *state = + container_of(work, struct ws2812_pio_rp1_state, deferred_work.work); + uint8_t *p_buffer; + uint32_t *p_rgb; + int blank_bytes; + uint length; + int i; + + blank_bytes = state->pixbuf_size - state->write_end; + if (blank_bytes > 0) + memset(state->pixbuf + state->write_end, 0, blank_bytes); + + p_rgb = (uint32_t *)state->pixbuf; + p_buffer = state->buffer; + + for (i = 0; i < state->num_leds; i++) { + uint32_t rgbw_pix = *(p_rgb++); + + p_buffer = rgbw_u32(state, + (uint8_t)(rgbw_pix >> 0), + (uint8_t)(rgbw_pix >> 8), + (uint8_t)(rgbw_pix >> 16), + (uint8_t)(rgbw_pix >> 24), + p_buffer); + } + + length = (void *)p_buffer - (void *)state->buffer; + + ws2812_update_leds(state, length); +} + +static int ws2812_pio_rp1_open(struct inode *inode, struct file *file) +{ + struct ws2812_pio_rp1_state *state; + + state = container_of(inode->i_cdev, struct ws2812_pio_rp1_state, cdev); + file->private_data = state; + + return 0; +} + +const struct file_operations ws2812_pio_rp1_fops = { + .owner = THIS_MODULE, + .write = ws2812_pio_rp1_write, + .open = ws2812_pio_rp1_open, +}; + +/* + * Probe function + */ +static int ws2812_pio_rp1_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct of_phandle_args of_args = { 0 }; + struct ws2812_pio_rp1_state *state; + struct device *dev = &pdev->dev; + struct device *char_dev; + const char *dev_name; + uint32_t brightness; + bool is_rp1; + int minor; + int ret; + + state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL); + if (IS_ERR(state)) + return PTR_ERR(state); + + state->dev = dev; + + platform_set_drvdata(pdev, state); + + ret = of_property_read_u32(np, "rpi,num-leds", &state->num_leds); + if (ret) + return dev_err_probe(dev, ret, "Could not get num-leds\n"); + + brightness = 255; + of_property_read_u32(np, "rpi,brightness", &brightness); + state->brightness = min(brightness, 255); + + state->pixbuf_size = state->num_leds * PIXEL_BYTES; + + state->is_rgbw = of_property_read_bool(np, "rpi,rgbw"); + state->gpiod = devm_gpiod_get(dev, "leds", GPIOD_ASIS); + if (IS_ERR(state->gpiod)) + return dev_err_probe(dev, PTR_ERR(state->gpiod), + "Could not get a gpio\n"); + + /* This must be an RP1 GPIO in the first bank, and retrieve the offset. */ + /* Unfortunately I think this has to be done by parsing the gpios property */ + + /* This really shouldn't fail, given that we have a gpiod */ + if (of_parse_phandle_with_args(np, "leds-gpios", "#gpio-cells", 0, &of_args)) + return dev_err_probe(dev, -EINVAL, + "Can't find gpio declaration\n"); + + is_rp1 = of_device_is_compatible(of_args.np, "raspberrypi,rp1-gpio"); + of_node_put(of_args.np); + if (!is_rp1 || of_args.args_count != 2) + return dev_err_probe(dev, -EINVAL, + "Not an RP1 gpio\n"); + + state->gpio = of_args.args[0]; + + state->pixbuf = devm_kmalloc(dev, state->pixbuf_size, GFP_KERNEL); + if (state->pixbuf == NULL) + return -ENOMEM; + + state->buffer = devm_kmalloc(dev, state->num_leds * PIXEL_BYTES, GFP_KERNEL); + if (state->buffer == NULL) + return -ENOMEM; + + ret = of_property_read_string(np, "dev-name", &dev_name); + if (ret) { + pr_err("Failed to read 'dev-name' property\n"); + return ret; + } + + state->pio = pio_open(); + if (IS_ERR(state->pio)) + return dev_err_probe(dev, PTR_ERR(state->pio), + "Could not open PIO\n"); + + state->sm = pio_claim_unused_sm(state->pio, false); + if ((int)state->sm < 0) { + dev_err(dev, "No free PIO SM\n"); + ret = -EBUSY; + goto fail_pio; + } + + state->offset = pio_add_program(state->pio, &ws2812_program); + if (state->offset == PIO_ORIGIN_ANY) { + dev_err(dev, "Not enough PIO program space\n"); + ret = -EBUSY; + goto fail_pio; + } + + pio_sm_config_xfer(state->pio, state->sm, PIO_DIR_TO_SM, state->num_leds * sizeof(int), 1); + + pio_sm_clear_fifos(state->pio, state->sm); + pio_sm_set_clkdiv(state->pio, state->sm, make_fp24_8(1, 1)); + ws2812_program_init(state->pio, state->sm, state->offset, state->gpio, 800000, + state->is_rgbw); + + mutex_lock(&ws2812_pio_mutex); + + if (!ws2812_pio_ref_count) { + ret = alloc_chrdev_region(&ws2812_pio_dev_num, 0, MAX_INSTANCES, DRIVER_NAME); + if (ret < 0) { + dev_err(dev, "alloc_chrdev_region failed (rc=%d)\n", ret); + goto fail_mutex; + } + + ws2812_pio_class = class_create(DRIVER_NAME); + if (IS_ERR(ws2812_pio_class)) { + pr_err("Unable to create class " DRIVER_NAME "\n"); + ret = PTR_ERR(ws2812_pio_class); + goto fail_chrdev; + } + } + + ws2812_pio_ref_count++; + + minor = ida_alloc_range(&ws2812_pio_ida, 0, MAX_INSTANCES - 1, GFP_KERNEL); + if (minor < 0) { + pr_err("No free instances\n"); + ret = minor; + goto fail_class; + + } + + mutex_unlock(&ws2812_pio_mutex); + + state->dev_num = MKDEV(MAJOR(ws2812_pio_dev_num), minor); + state->dev_name = devm_kasprintf(dev, GFP_KERNEL, dev_name, minor); + + char_dev = device_create(ws2812_pio_class, NULL, state->dev_num, NULL, state->dev_name); + + if (IS_ERR(char_dev)) { + pr_err("Unable to create device %s\n", state->dev_name); + ret = PTR_ERR(char_dev); + goto fail_ida; + } + + state->cdev.owner = THIS_MODULE; + cdev_init(&state->cdev, &ws2812_pio_rp1_fops); + + ret = cdev_add(&state->cdev, state->dev_num, 1); + if (ret) { + pr_err("cdev_add failed\n"); + goto fail_device; + } + + INIT_DELAYED_WORK(&state->deferred_work, ws2812_pio_rp1_deferred_work); + + ws2812_clear_leds(state); + + dev_info(&pdev->dev, "Instantiated %d LEDs on GPIO %d as /dev/%s\n", + state->num_leds, state->gpio, state->dev_name); + + return 0; + +fail_device: + device_destroy(ws2812_pio_class, state->dev_num); +fail_ida: + mutex_lock(&ws2812_pio_mutex); + ida_free(&ws2812_pio_ida, minor); +fail_class: + ws2812_pio_ref_count--; + if (ws2812_pio_ref_count) + goto fail_mutex; + class_destroy(ws2812_pio_class); +fail_chrdev: + unregister_chrdev_region(ws2812_pio_dev_num, MAX_INSTANCES); +fail_mutex: + mutex_unlock(&ws2812_pio_mutex); +fail_pio: + pio_close(state->pio); + + return ret; +} + +static void ws2812_pio_rp1_remove(struct platform_device *pdev) +{ + struct ws2812_pio_rp1_state *state = platform_get_drvdata(pdev); + + cancel_delayed_work(&state->deferred_work); + platform_set_drvdata(pdev, NULL); + + cdev_del(&state->cdev); + device_destroy(ws2812_pio_class, state->dev_num); + + mutex_lock(&ws2812_pio_mutex); + ida_free(&ws2812_pio_ida, MINOR(state->dev_num)); + ws2812_pio_ref_count--; + if (!ws2812_pio_ref_count) { + class_destroy(ws2812_pio_class); + unregister_chrdev_region(ws2812_pio_dev_num, MAX_INSTANCES); + } + mutex_unlock(&ws2812_pio_mutex); + + pio_close(state->pio); +} + +static const struct of_device_id ws2812_pio_rp1_match[] = { + { .compatible = "raspberrypi,ws2812-pio-rp1" }, + { } +}; +MODULE_DEVICE_TABLE(of, ws2812_pio_rp1_match); + +static struct platform_driver ws2812_pio_rp1_driver = { + .driver = { + .name = "ws2812-pio-rp1", + .of_match_table = ws2812_pio_rp1_match, + }, + .probe = ws2812_pio_rp1_probe, + .remove_new = ws2812_pio_rp1_remove, +}; +module_platform_driver(ws2812_pio_rp1_driver); + +MODULE_DESCRIPTION("WS2812 PIO RP1 driver"); +MODULE_AUTHOR("Phil Elwell"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mmc/core/block.c b/drivers/mmc/core/block.c index 1d08009f2bd83f..15e476684cd3f7 100644 --- a/drivers/mmc/core/block.c +++ b/drivers/mmc/core/block.c @@ -218,6 +218,13 @@ static DEFINE_MUTEX(open_lock); module_param(perdev_minors, int, 0444); MODULE_PARM_DESC(perdev_minors, "Minors numbers to allocate per device"); +/* + * Allow quirks to be overridden for the current card + */ +static char *card_quirks; +module_param(card_quirks, charp, 0644); +MODULE_PARM_DESC(card_quirks, "Force the use of the indicated quirks (a bitfield)"); + static inline int mmc_blk_part_switch(struct mmc_card *card, unsigned int part_type); static void mmc_blk_rw_rq_prep(struct mmc_queue_req *mqrq, @@ -927,7 +934,10 @@ static int mmc_blk_part_switch_pre(struct mmc_card *card, if ((part_type & mask) == rpmb) { if (card->ext_csd.cmdq_en) { - ret = mmc_cmdq_disable(card); + if (mmc_card_sd(card)) + ret = mmc_sd_cmdq_disable(card); + else + ret = mmc_cmdq_disable(card); if (ret) return ret; } @@ -946,8 +956,12 @@ static int mmc_blk_part_switch_post(struct mmc_card *card, if ((part_type & mask) == rpmb) { mmc_retune_unpause(card->host); - if (card->reenable_cmdq && !card->ext_csd.cmdq_en) - ret = mmc_cmdq_enable(card); + if (card->reenable_cmdq && !card->ext_csd.cmdq_en) { + if (mmc_card_sd(card)) + ret = mmc_sd_cmdq_enable(card); + else + ret = mmc_cmdq_enable(card); + } } return ret; @@ -1158,7 +1172,10 @@ static void mmc_blk_issue_drv_op(struct mmc_queue *mq, struct request *req) switch (mq_rq->drv_op) { case MMC_DRV_OP_IOCTL: if (card->ext_csd.cmdq_en) { - ret = mmc_cmdq_disable(card); + if (mmc_card_sd(card)) + ret = mmc_sd_cmdq_disable(card); + else + ret = mmc_cmdq_disable(card); if (ret) break; } @@ -1176,8 +1193,12 @@ static void mmc_blk_issue_drv_op(struct mmc_queue *mq, struct request *req) /* Always switch back to main area after RPMB access */ if (rpmb_ioctl) mmc_blk_part_switch(card, 0); - else if (card->reenable_cmdq && !card->ext_csd.cmdq_en) - mmc_cmdq_enable(card); + else if (card->reenable_cmdq && !card->ext_csd.cmdq_en) { + if (mmc_card_sd(card)) + mmc_sd_cmdq_enable(card); + else + mmc_cmdq_enable(card); + } break; case MMC_DRV_OP_BOOT_WP: ret = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_BOOT_WP, @@ -1218,12 +1239,26 @@ static void mmc_blk_issue_erase_rq(struct mmc_queue *mq, struct request *req, unsigned int from, nr; int err = 0; blk_status_t status = BLK_STS_OK; + bool restart_cmdq = false; if (!mmc_can_erase(card)) { status = BLK_STS_NOTSUPP; goto fail; } + /* + * Only Discard ops are supported with SD cards in CQ mode + * (SD Physical Spec v9.00 4.19.2) + */ + if (mmc_card_sd(card) && card->ext_csd.cmdq_en && erase_arg != SD_DISCARD_ARG) { + restart_cmdq = true; + err = mmc_sd_cmdq_disable(card); + if (err) { + status = BLK_STS_IOERR; + goto fail; + } + } + from = blk_rq_pos(req); nr = blk_rq_sectors(req); @@ -1244,6 +1279,11 @@ static void mmc_blk_issue_erase_rq(struct mmc_queue *mq, struct request *req, status = BLK_STS_IOERR; else mmc_blk_reset_success(md, type); + + if (restart_cmdq) + err = mmc_sd_cmdq_enable(card); + if (err) + status = BLK_STS_IOERR; fail: blk_mq_end_request(req, status); } @@ -1565,6 +1605,7 @@ static void mmc_blk_cqe_complete_rq(struct mmc_queue *mq, struct request *req) struct request_queue *q = req->q; struct mmc_host *host = mq->card->host; enum mmc_issue_type issue_type = mmc_issue_type(mq, req); + bool write = req_op(req) == REQ_OP_WRITE; unsigned long flags; bool put_card; int err; @@ -1596,6 +1637,8 @@ static void mmc_blk_cqe_complete_rq(struct mmc_queue *mq, struct request *req) spin_lock_irqsave(&mq->lock, flags); + if (write) + mq->pending_writes--; mq->in_flight[issue_type] -= 1; put_card = (mmc_tot_in_flight(mq) == 0); @@ -2004,7 +2047,7 @@ static void mmc_blk_mq_rw_recovery(struct mmc_queue *mq, struct request *req) return; } - if (rq_data_dir(req) == READ && brq->data.blocks > + if (0 && rq_data_dir(req) == READ && brq->data.blocks > queue_physical_block_size(mq->queue) >> 9) { /* Read one (native) sector at a time */ mmc_blk_read_single(mq, req); @@ -2112,6 +2155,8 @@ static void mmc_blk_mq_complete_rq(struct mmc_queue *mq, struct request *req) struct mmc_queue_req *mqrq = req_to_mmc_queue_req(req); unsigned int nr_bytes = mqrq->brq.data.bytes_xfered; + if (req_op(req) == REQ_OP_WRITE) + mq->pending_writes--; if (nr_bytes) { if (blk_update_request(req, BLK_STS_OK, nr_bytes)) blk_mq_requeue_request(req, true); @@ -2206,13 +2251,17 @@ static void mmc_blk_mq_poll_completion(struct mmc_queue *mq, mmc_blk_urgent_bkops(mq, mqrq); } -static void mmc_blk_mq_dec_in_flight(struct mmc_queue *mq, enum mmc_issue_type issue_type) +static void mmc_blk_mq_dec_in_flight(struct mmc_queue *mq, enum mmc_issue_type issue_type, + bool write) { unsigned long flags; bool put_card; spin_lock_irqsave(&mq->lock, flags); + if (write) + mq->pending_writes--; + mq->in_flight[issue_type] -= 1; put_card = (mmc_tot_in_flight(mq) == 0); @@ -2227,6 +2276,7 @@ static void mmc_blk_mq_post_req(struct mmc_queue *mq, struct request *req, bool can_sleep) { enum mmc_issue_type issue_type = mmc_issue_type(mq, req); + bool write = req_op(req) == REQ_OP_WRITE; struct mmc_queue_req *mqrq = req_to_mmc_queue_req(req); struct mmc_request *mrq = &mqrq->brq.mrq; struct mmc_host *host = mq->card->host; @@ -2246,7 +2296,7 @@ static void mmc_blk_mq_post_req(struct mmc_queue *mq, struct request *req, blk_mq_complete_request(req); } - mmc_blk_mq_dec_in_flight(mq, issue_type); + mmc_blk_mq_dec_in_flight(mq, issue_type, write); } void mmc_blk_mq_recovery(struct mmc_queue *mq) @@ -3255,6 +3305,8 @@ static int mmc_blk_probe(struct mmc_card *card) { struct mmc_blk_data *md; int ret = 0; + char quirk_str[24]; + char cap_str[10]; /* * Check that the card supports the command class(es) we need. @@ -3262,7 +3314,16 @@ static int mmc_blk_probe(struct mmc_card *card) if (!(card->csd.cmdclass & CCC_BLOCK_READ)) return -ENODEV; - mmc_fixup_device(card, mmc_blk_fixups); + if (card_quirks) { + unsigned long quirks; + if (kstrtoul(card_quirks, 0, &quirks) == 0) + card->quirks = (unsigned int)quirks; + else + pr_err("mmc_block: Invalid card_quirks parameter '%s'\n", + card_quirks); + } + else + mmc_fixup_device(card, mmc_blk_fixups); card->complete_wq = alloc_workqueue("mmc_complete", WQ_MEM_RECLAIM | WQ_HIGHPRI, 0); @@ -3277,6 +3338,17 @@ static int mmc_blk_probe(struct mmc_card *card) goto out_free; } + string_get_size((u64)get_capacity(md->disk), 512, STRING_UNITS_2, + cap_str, sizeof(cap_str)); + if (card->quirks) + snprintf(quirk_str, sizeof(quirk_str), + " (quirks 0x%08x)", card->quirks); + else + quirk_str[0] = '\0'; + pr_info("%s: %s %s %s%s%s\n", + md->disk->disk_name, mmc_card_id(card), mmc_card_name(card), + cap_str, md->read_only ? " (ro)" : "", quirk_str); + ret = mmc_blk_alloc_parts(card, md); if (ret) goto out; diff --git a/drivers/mmc/core/bus.c b/drivers/mmc/core/bus.c index 4f3a26676ccb86..27e2ff70165d4c 100644 --- a/drivers/mmc/core/bus.c +++ b/drivers/mmc/core/bus.c @@ -266,6 +266,8 @@ static void mmc_release_card(struct device *dev) sdio_free_common_cis(card); + kfree(card->ext_reg_buf); + kfree(card->info); kfree(card); diff --git a/drivers/mmc/core/card.h b/drivers/mmc/core/card.h index 3205feb1e8ff6a..273bdb5b49b8f3 100644 --- a/drivers/mmc/core/card.h +++ b/drivers/mmc/core/card.h @@ -88,10 +88,12 @@ struct mmc_fixup { #define CID_MANFID_GIGASTONE 0x12 #define CID_MANFID_MICRON 0x13 #define CID_MANFID_SAMSUNG 0x15 +#define CID_MANFID_SAMSUNG_SD 0x1b #define CID_MANFID_APACER 0x27 #define CID_MANFID_KINGSTON 0x70 #define CID_MANFID_HYNIX 0x90 #define CID_MANFID_KINGSTON_SD 0x9F +#define CID_MANFID_LONGSYS_SD 0xAD #define CID_MANFID_NUMONYX 0xFE #define END_FIXUP { NULL } @@ -294,4 +296,9 @@ static inline int mmc_card_broken_sd_poweroff_notify(const struct mmc_card *c) return c->quirks & MMC_QUIRK_BROKEN_SD_POWEROFF_NOTIFY; } +static inline int mmc_card_working_sd_cq(const struct mmc_card *c) +{ + return c->quirks & MMC_QUIRK_WORKING_SD_CQ; +} + #endif diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 327029f5c59b79..95d40fd263698c 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -455,6 +455,7 @@ int mmc_cqe_start_req(struct mmc_host *host, struct mmc_request *mrq) goto out_err; trace_mmc_request_start(host, mrq); + led_trigger_event(host->led, LED_FULL); return 0; @@ -542,10 +543,18 @@ int mmc_cqe_recovery(struct mmc_host *host) * Recovery is expected seldom, if at all, but it reduces performance, * so make sure it is not completely silent. */ - pr_warn("%s: running CQE recovery\n", mmc_hostname(host)); + pr_warn_ratelimited("%s: running CQE recovery\n", mmc_hostname(host)); host->cqe_ops->cqe_recovery_start(host); + err = mmc_detect_card_removed(host); + if (err) { + host->cqe_ops->cqe_recovery_finish(host); + host->cqe_ops->cqe_off(host); + mmc_retune_release(host); + return err; + } + memset(&cmd, 0, sizeof(cmd)); cmd.opcode = MMC_STOP_TRANSMISSION; cmd.flags = MMC_RSP_R1B | MMC_CMD_AC; @@ -556,7 +565,11 @@ int mmc_cqe_recovery(struct mmc_host *host) mmc_poll_for_busy(host->card, MMC_CQE_RECOVERY_TIMEOUT, true, MMC_BUSY_IO); memset(&cmd, 0, sizeof(cmd)); - cmd.opcode = MMC_CMDQ_TASK_MGMT; + if (mmc_card_sd(host->card)) + cmd.opcode = SD_CMDQ_TASK_MGMT; + else + cmd.opcode = MMC_CMDQ_TASK_MGMT; + cmd.arg = 1; /* Discard entire queue */ cmd.flags = MMC_RSP_R1B | MMC_CMD_AC; cmd.flags &= ~MMC_RSP_CRC; /* Ignore CRC */ @@ -1818,7 +1831,8 @@ EXPORT_SYMBOL(mmc_erase); int mmc_can_erase(struct mmc_card *card) { - if (card->csd.cmdclass & CCC_ERASE && card->erase_size) + if (card->csd.cmdclass & CCC_ERASE && card->erase_size && + !(card->quirks & MMC_QUIRK_ERASE_BROKEN)) return 1; return 0; } diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index 48bda70145ee68..018b77ade4c690 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -272,7 +272,7 @@ EXPORT_SYMBOL(mmc_of_parse_clk_phase); int mmc_of_parse(struct mmc_host *host) { struct device *dev = host->parent; - u32 bus_width, drv_type, cd_debounce_delay_ms; + u32 bus_width, drv_type, cd_debounce_delay_ms, cq_allow; int ret; if (!dev || !dev_fwnode(dev)) @@ -407,6 +407,15 @@ int mmc_of_parse(struct mmc_host *host) host->caps2 &= ~(MMC_CAP2_HS400_1_8V | MMC_CAP2_HS400_1_2V | MMC_CAP2_HS400_ES); + cq_allow = 0; + /* + * Downstream property - if a u32 and 2 instead of a bool, + * trust most A2 SD cards claiming CQ support. + */ + device_property_read_u32(dev, "supports-cqe", &cq_allow); + if (cq_allow == 2) + host->caps2 |= MMC_CAP2_SD_CQE_PERMISSIVE; + /* Must be after "non-removable" check */ if (device_property_read_u32(dev, "fixed-emmc-driver-type", &drv_type) == 0) { if (host->caps & MMC_CAP_NONREMOVABLE) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 6a23be214543db..851701b1126681 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1657,6 +1657,7 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, card->ocr = ocr; card->type = MMC_TYPE_MMC; card->rca = 1; + card->max_posted_writes = 1; memcpy(card->raw_cid, cid, sizeof(card->raw_cid)); } @@ -1915,13 +1916,14 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, host->cqe_enabled = true; if (card->ext_csd.cmdq_en) { - pr_info("%s: Command Queue Engine enabled\n", - mmc_hostname(host)); + pr_info("%s: Command Queue Engine enabled, %u tags\n", + mmc_hostname(host), card->ext_csd.cmdq_depth); } else { host->hsq_enabled = true; pr_info("%s: Host Software Queue enabled\n", mmc_hostname(host)); } + card->max_posted_writes = card->ext_csd.cmdq_depth; } } diff --git a/drivers/mmc/core/queue.c b/drivers/mmc/core/queue.c index 4d684426191204..6d99da3a22fb26 100644 --- a/drivers/mmc/core/queue.c +++ b/drivers/mmc/core/queue.c @@ -266,6 +266,11 @@ static blk_status_t mmc_mq_queue_rq(struct blk_mq_hw_ctx *hctx, spin_unlock_irq(&mq->lock); return BLK_STS_RESOURCE; } + if (!host->hsq_enabled && host->cqe_enabled && req_op(req) == REQ_OP_WRITE && + mq->pending_writes >= card->max_posted_writes) { + spin_unlock_irq(&mq->lock); + return BLK_STS_RESOURCE; + } break; default: /* @@ -282,6 +287,8 @@ static blk_status_t mmc_mq_queue_rq(struct blk_mq_hw_ctx *hctx, /* Parallel dispatch of requests is not supported at the moment */ mq->busy = true; + if (req_op(req) == REQ_OP_WRITE) + mq->pending_writes++; mq->in_flight[issue_type] += 1; get_card = (mmc_tot_in_flight(mq) == 1); cqe_retune_ok = (mmc_cqe_qcnt(mq) == 1); @@ -321,6 +328,8 @@ static blk_status_t mmc_mq_queue_rq(struct blk_mq_hw_ctx *hctx, bool put_card = false; spin_lock_irq(&mq->lock); + if (req_op(req) == REQ_OP_WRITE) + mq->pending_writes--; mq->in_flight[issue_type] -= 1; if (mmc_tot_in_flight(mq) == 0) put_card = true; diff --git a/drivers/mmc/core/queue.h b/drivers/mmc/core/queue.h index 1498840a4ea008..39180d97911b0e 100644 --- a/drivers/mmc/core/queue.h +++ b/drivers/mmc/core/queue.h @@ -79,6 +79,7 @@ struct mmc_queue { struct request_queue *queue; spinlock_t lock; int in_flight[MMC_ISSUE_MAX]; + int pending_writes; unsigned int cqe_busy; #define MMC_CQE_DCMD_BUSY BIT(0) bool busy; diff --git a/drivers/mmc/core/quirks.h b/drivers/mmc/core/quirks.h index 89b512905be140..9f8f5e121bc3fc 100644 --- a/drivers/mmc/core/quirks.h +++ b/drivers/mmc/core/quirks.h @@ -18,10 +18,22 @@ static const struct mmc_fixup __maybe_unused mmc_sd_fixups[] = { /* * Kingston Canvas Go! Plus microSD cards never finish SD cache flush. - * This has so far only been observed on cards from 11/2019, while new - * cards from 2023/05 do not exhibit this behavior. + * This has been observed on cards from 2019/11 and 2021/11, while new + * cards from 2023/05 and 2024/08 do not exhibit this behavior. */ - _FIXUP_EXT("SD64G", CID_MANFID_KINGSTON_SD, 0x5449, 2019, 11, + _FIXUP_EXT(CID_NAME_ANY, CID_MANFID_KINGSTON_SD, 0x5449, 2019, CID_MONTH_ANY, + 0, -1ull, SDIO_ANY_ID, SDIO_ANY_ID, add_quirk_sd, + MMC_QUIRK_BROKEN_SD_CACHE, EXT_CSD_REV_ANY), + + _FIXUP_EXT(CID_NAME_ANY, CID_MANFID_KINGSTON_SD, 0x5449, 2020, CID_MONTH_ANY, + 0, -1ull, SDIO_ANY_ID, SDIO_ANY_ID, add_quirk_sd, + MMC_QUIRK_BROKEN_SD_CACHE, EXT_CSD_REV_ANY), + + _FIXUP_EXT(CID_NAME_ANY, CID_MANFID_KINGSTON_SD, 0x5449, 2021, CID_MONTH_ANY, + 0, -1ull, SDIO_ANY_ID, SDIO_ANY_ID, add_quirk_sd, + MMC_QUIRK_BROKEN_SD_CACHE, EXT_CSD_REV_ANY), + + _FIXUP_EXT(CID_NAME_ANY, CID_MANFID_KINGSTON_SD, 0x5449, 2022, CID_MONTH_ANY, 0, -1ull, SDIO_ANY_ID, SDIO_ANY_ID, add_quirk_sd, MMC_QUIRK_BROKEN_SD_CACHE, EXT_CSD_REV_ANY), @@ -34,6 +46,32 @@ static const struct mmc_fixup __maybe_unused mmc_sd_fixups[] = { MMC_QUIRK_BROKEN_SD_CACHE | MMC_QUIRK_BROKEN_SD_POWEROFF_NOTIFY, EXT_CSD_REV_ANY), + /* + * Samsung Pro Plus/EVO Plus/Pro Ultimate SD cards (2023) claim to cache + * flush OK, but become unresponsive afterwards. + */ + _FIXUP_EXT(CID_NAME_ANY, CID_MANFID_SAMSUNG_SD, 0x534d, 2023, CID_MONTH_ANY, + 0, -1ull, SDIO_ANY_ID, SDIO_ANY_ID, add_quirk_sd, + MMC_QUIRK_BROKEN_SD_CACHE, EXT_CSD_REV_ANY), + + /* + * Early Sandisk Extreme and Extreme Pro A2 cards never finish SD cache + * flush in CQ mode. Latest card date this was seen on is 10/2020. + */ + _FIXUP_EXT(CID_NAME_ANY, CID_MANFID_SANDISK_SD, 0x5344, 2019, CID_MONTH_ANY, + 0, -1ull, SDIO_ANY_ID, SDIO_ANY_ID, add_quirk_sd, + MMC_QUIRK_BROKEN_SD_CACHE, EXT_CSD_REV_ANY), + + _FIXUP_EXT(CID_NAME_ANY, CID_MANFID_SANDISK_SD, 0x5344, 2020, CID_MONTH_ANY, + 0, -1ull, SDIO_ANY_ID, SDIO_ANY_ID, add_quirk_sd, + MMC_QUIRK_BROKEN_SD_CACHE, EXT_CSD_REV_ANY), + + /* SD A2 allow-list - only trust CQ on these cards */ + /* Raspberry Pi A2 cards */ + _FIXUP_EXT(CID_NAME_ANY, CID_MANFID_LONGSYS_SD, 0x4c53, CID_YEAR_ANY, CID_MONTH_ANY, + cid_rev(1, 0, 0, 0), -1ull, SDIO_ANY_ID, SDIO_ANY_ID, add_quirk_sd, + MMC_QUIRK_WORKING_SD_CQ, EXT_CSD_REV_ANY), + END_FIXUP }; @@ -143,6 +181,23 @@ static const struct mmc_fixup __maybe_unused mmc_blk_fixups[] = { MMC_FIXUP(CID_NAME_ANY, CID_MANFID_SANDISK_SD, 0x5344, add_quirk_sd, MMC_QUIRK_BROKEN_SD_DISCARD), + /* + * On some Kingston SD cards, multiple erases of less than 64 + * sectors can cause corruption. + */ + MMC_FIXUP("SD16G", 0x41, 0x3432, add_quirk, MMC_QUIRK_ERASE_BROKEN), + MMC_FIXUP("SD32G", 0x41, 0x3432, add_quirk, MMC_QUIRK_ERASE_BROKEN), + MMC_FIXUP("SD64G", 0x41, 0x3432, add_quirk, MMC_QUIRK_ERASE_BROKEN), + + /* + * Larger Integral SD cards using rebranded Phison controllers trash + * nearby flash blocks after erases. + */ + MMC_FIXUP("SD64G", 0x27, 0x5048, add_quirk, MMC_QUIRK_ERASE_BROKEN), + MMC_FIXUP("SD128", 0x27, 0x5048, add_quirk, MMC_QUIRK_ERASE_BROKEN), + MMC_FIXUP("SD256", 0x27, 0x5048, add_quirk, MMC_QUIRK_ERASE_BROKEN), + MMC_FIXUP("SD512", 0x27, 0x5048, add_quirk, MMC_QUIRK_ERASE_BROKEN), + END_FIXUP }; diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index 63915541c0e494..ea42fa2ef4a6e8 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -710,7 +710,8 @@ MMC_DEV_ATTR(oemid, "0x%04x\n", card->cid.oemid); MMC_DEV_ATTR(serial, "0x%08x\n", card->cid.serial); MMC_DEV_ATTR(ocr, "0x%08x\n", card->ocr); MMC_DEV_ATTR(rca, "0x%04x\n", card->rca); - +MMC_DEV_ATTR(ext_perf, "%02x\n", card->ext_perf.feature_support); +MMC_DEV_ATTR(ext_power, "%02x\n", card->ext_power.feature_support); static ssize_t mmc_dsr_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -772,6 +773,8 @@ static struct attribute *sd_std_attrs[] = { &dev_attr_ocr.attr, &dev_attr_rca.attr, &dev_attr_dsr.attr, + &dev_attr_ext_perf.attr, + &dev_attr_ext_power.attr, NULL, }; @@ -1011,98 +1014,16 @@ static bool mmc_sd_card_using_v18(struct mmc_card *card) (SD_MODE_UHS_SDR50 | SD_MODE_UHS_SDR104 | SD_MODE_UHS_DDR50); } -static int sd_write_ext_reg(struct mmc_card *card, u8 fno, u8 page, u16 offset, - u8 reg_data) -{ - struct mmc_host *host = card->host; - struct mmc_request mrq = {}; - struct mmc_command cmd = {}; - struct mmc_data data = {}; - struct scatterlist sg; - u8 *reg_buf; - - reg_buf = kzalloc(512, GFP_KERNEL); - if (!reg_buf) - return -ENOMEM; - - mrq.cmd = &cmd; - mrq.data = &data; - - /* - * Arguments of CMD49: - * [31:31] MIO (0 = memory). - * [30:27] FNO (function number). - * [26:26] MW - mask write mode (0 = disable). - * [25:18] page number. - * [17:9] offset address. - * [8:0] length (0 = 1 byte). - */ - cmd.arg = fno << 27 | page << 18 | offset << 9; - - /* The first byte in the buffer is the data to be written. */ - reg_buf[0] = reg_data; - - data.flags = MMC_DATA_WRITE; - data.blksz = 512; - data.blocks = 1; - data.sg = &sg; - data.sg_len = 1; - sg_init_one(&sg, reg_buf, 512); - - cmd.opcode = SD_WRITE_EXTR_SINGLE; - cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC; - - mmc_set_data_timeout(&data, card); - mmc_wait_for_req(host, &mrq); - - kfree(reg_buf); - - /* - * Note that, the SD card is allowed to signal busy on DAT0 up to 1s - * after the CMD49. Although, let's leave this to be managed by the - * caller. - */ - - if (cmd.error) - return cmd.error; - if (data.error) - return data.error; - - return 0; -} - -static int sd_read_ext_reg(struct mmc_card *card, u8 fno, u8 page, - u16 offset, u16 len, u8 *reg_buf) -{ - u32 cmd_args; - - /* - * Command arguments of CMD48: - * [31:31] MIO (0 = memory). - * [30:27] FNO (function number). - * [26:26] reserved (0). - * [25:18] page number. - * [17:9] offset address. - * [8:0] length (0 = 1 byte, 1ff = 512 bytes). - */ - cmd_args = fno << 27 | page << 18 | offset << 9 | (len -1); - - return mmc_send_adtc_data(card, card->host, SD_READ_EXTR_SINGLE, - cmd_args, reg_buf, 512); -} - static int sd_parse_ext_reg_power(struct mmc_card *card, u8 fno, u8 page, u16 offset) { int err; u8 *reg_buf; - reg_buf = kzalloc(512, GFP_KERNEL); - if (!reg_buf) - return -ENOMEM; + reg_buf = card->ext_reg_buf; /* Read the extension register for power management function. */ - err = sd_read_ext_reg(card, fno, page, offset, 512, reg_buf); + err = mmc_sd_read_ext_reg(card, fno, page, offset, 512, reg_buf); if (err) { pr_warn("%s: error %d reading PM func of ext reg\n", mmc_hostname(card->host), err); @@ -1129,7 +1050,6 @@ static int sd_parse_ext_reg_power(struct mmc_card *card, u8 fno, u8 page, card->ext_power.offset = offset; out: - kfree(reg_buf); return err; } @@ -1139,11 +1059,9 @@ static int sd_parse_ext_reg_perf(struct mmc_card *card, u8 fno, u8 page, int err; u8 *reg_buf; - reg_buf = kzalloc(512, GFP_KERNEL); - if (!reg_buf) - return -ENOMEM; + reg_buf = card->ext_reg_buf; - err = sd_read_ext_reg(card, fno, page, offset, 512, reg_buf); + err = mmc_sd_read_ext_reg(card, fno, page, offset, 512, reg_buf); if (err) { pr_warn("%s: error %d reading PERF func of ext reg\n", mmc_hostname(card->host), err); @@ -1169,16 +1087,34 @@ static int sd_parse_ext_reg_perf(struct mmc_card *card, u8 fno, u8 page, if ((reg_buf[4] & BIT(0)) && !mmc_card_broken_sd_cache(card)) card->ext_perf.feature_support |= SD_EXT_PERF_CACHE; - /* Command queue support indicated via queue depth bits (0 to 4). */ - if (reg_buf[6] & 0x1f) + /* + * Command queue support indicated via queue depth bits (0 to 4). + * Qualify this with the other mandatory required features. + */ + if (reg_buf[6] & 0x1f && card->ext_power.feature_support & SD_EXT_POWER_OFF_NOTIFY && + card->ext_perf.feature_support & SD_EXT_PERF_CACHE) { card->ext_perf.feature_support |= SD_EXT_PERF_CMD_QUEUE; + card->ext_csd.cmdq_depth = reg_buf[6] & 0x1f; + card->ext_csd.cmdq_support = true; + pr_debug("%s: Command Queue supported depth %u\n", + mmc_hostname(card->host), + card->ext_csd.cmdq_depth); + /* + * If CQ is enabled, there is a contract between host and card such that + * VDD will be maintained and removed only if a power off notification + * is provided. An SD card in an accessible slot means surprise removal + * is a possibility. As a middle ground, keep the default maximum of 1 + * posted write unless the card is "hardwired". + */ + if (!mmc_card_is_removable(card->host)) + card->max_posted_writes = card->ext_csd.cmdq_depth; + } card->ext_perf.fno = fno; card->ext_perf.page = page; card->ext_perf.offset = offset; out: - kfree(reg_buf); return err; } @@ -1233,7 +1169,7 @@ static int sd_parse_ext_reg(struct mmc_card *card, u8 *gen_info_buf, return 0; } -static int sd_read_ext_regs(struct mmc_card *card) +static int mmc_sd_read_ext_regs(struct mmc_card *card) { int err, i; u8 num_ext, *gen_info_buf; @@ -1245,15 +1181,21 @@ static int sd_read_ext_regs(struct mmc_card *card) if (!(card->scr.cmds & SD_SCR_CMD48_SUPPORT)) return 0; - gen_info_buf = kzalloc(512, GFP_KERNEL); + gen_info_buf = kzalloc(1024, GFP_KERNEL); if (!gen_info_buf) return -ENOMEM; + card->ext_reg_buf = kzalloc(512, GFP_KERNEL); + if (!card->ext_reg_buf) { + err = -ENOMEM; + goto out; + } + /* * Read 512 bytes of general info, which is found at function number 0, * at page 0 and with no offset. */ - err = sd_read_ext_reg(card, 0, 0, 0, 512, gen_info_buf); + err = mmc_sd_read_ext_reg(card, 0, 0, 0, 512, gen_info_buf); if (err) { pr_err("%s: error %d reading general info of SD ext reg\n", mmc_hostname(card->host), err); @@ -1270,14 +1212,23 @@ static int sd_read_ext_regs(struct mmc_card *card) num_ext = gen_info_buf[4]; /* - * We only support revision 0 and limit it to 512 bytes for simplicity. + * We only support revision 0 and up to the spec-defined maximum of 1K. * No matter what, let's return zero to allow us to continue using the * card, even if we can't support the features from the SD function * extensions registers. */ - if (rev != 0 || len > 512) { - pr_warn("%s: non-supported SD ext reg layout\n", - mmc_hostname(card->host)); + if (rev != 0 || len > 1024) { + pr_warn("%s: non-supported SD ext reg layout rev %u length %u\n", + mmc_hostname(card->host), rev, len); + goto out; + } + + /* If the General Information block spills into the next page, read the rest */ + if (len > 512) + err = mmc_sd_read_ext_reg(card, 0, 1, 0, 512, &gen_info_buf[512]); + if (err) { + pr_err("%s: error %d reading page 1 of general info of SD ext reg\n", + mmc_hostname(card->host), err); goto out; } @@ -1315,9 +1266,7 @@ static int sd_flush_cache(struct mmc_host *host) if (!sd_cache_enabled(host)) return 0; - reg_buf = kzalloc(512, GFP_KERNEL); - if (!reg_buf) - return -ENOMEM; + reg_buf = card->ext_reg_buf; /* * Set Flush Cache at bit 0 in the performance enhancement register at @@ -1327,7 +1276,7 @@ static int sd_flush_cache(struct mmc_host *host) page = card->ext_perf.page; offset = card->ext_perf.offset + 261; - err = sd_write_ext_reg(card, fno, page, offset, BIT(0)); + err = mmc_sd_write_ext_reg(card, fno, page, offset, BIT(0)); if (err) { pr_warn("%s: error %d writing Cache Flush bit\n", mmc_hostname(host), err); @@ -1343,7 +1292,7 @@ static int sd_flush_cache(struct mmc_host *host) * Read the Flush Cache bit. The card shall reset it, to confirm that * it's has completed the flushing of the cache. */ - err = sd_read_ext_reg(card, fno, page, offset, 1, reg_buf); + err = mmc_sd_read_ext_reg(card, fno, page, offset, 1, reg_buf); if (err) { pr_warn("%s: error %d reading Cache Flush bit\n", mmc_hostname(host), err); @@ -1353,26 +1302,20 @@ static int sd_flush_cache(struct mmc_host *host) if (reg_buf[0] & BIT(0)) err = -ETIMEDOUT; out: - kfree(reg_buf); return err; } static int sd_enable_cache(struct mmc_card *card) { - u8 *reg_buf; int err; card->ext_perf.feature_enabled &= ~SD_EXT_PERF_CACHE; - reg_buf = kzalloc(512, GFP_KERNEL); - if (!reg_buf) - return -ENOMEM; - /* * Set Cache Enable at bit 0 in the performance enhancement register at * 260 bytes offset. */ - err = sd_write_ext_reg(card, card->ext_perf.fno, card->ext_perf.page, + err = mmc_sd_write_ext_reg(card, card->ext_perf.fno, card->ext_perf.page, card->ext_perf.offset + 260, BIT(0)); if (err) { pr_warn("%s: error %d writing Cache Enable bit\n", @@ -1386,7 +1329,6 @@ static int sd_enable_cache(struct mmc_card *card) card->ext_perf.feature_enabled |= SD_EXT_PERF_CACHE; out: - kfree(reg_buf); return err; } @@ -1429,6 +1371,7 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, card->ocr = ocr; card->type = MMC_TYPE_SD; + card->max_posted_writes = 1; memcpy(card->raw_cid, cid, sizeof(card->raw_cid)); } @@ -1546,7 +1489,7 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, cont: if (!oldcard) { /* Read/parse the extension registers. */ - err = sd_read_ext_regs(card); + err = mmc_sd_read_ext_regs(card); if (err) goto free_card; } @@ -1558,13 +1501,45 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, goto free_card; } + /* Disallow command queueing on unvetted cards unless overridden */ + if (!(host->caps2 & MMC_CAP2_SD_CQE_PERMISSIVE) && !mmc_card_working_sd_cq(card)) + card->ext_csd.cmdq_support = false; + + /* Enable command queueing if supported */ + if (card->ext_csd.cmdq_support && host->caps2 & MMC_CAP2_CQE) { + /* + * Right now the MMC block layer uses DCMDs to issue + * cache-flush commands specific to eMMC devices. + * Turning off DCMD support avoids generating Illegal Command + * errors on SD, and flushing is instead done synchronously + * by mmc_blk_issue_flush(). + */ + host->caps2 &= ~MMC_CAP2_CQE_DCMD; + err = mmc_sd_cmdq_enable(card); + if (err && err != -EBADMSG) + goto free_card; + if (err) { + pr_warn("%s: Enabling CMDQ failed\n", + mmc_hostname(card->host)); + card->ext_csd.cmdq_support = false; + card->ext_csd.cmdq_depth = 0; + } + } + card->reenable_cmdq = card->ext_csd.cmdq_en; + if (host->cqe_ops && !host->cqe_enabled) { err = host->cqe_ops->cqe_enable(host, card); if (!err) { host->cqe_enabled = true; - host->hsq_enabled = true; - pr_info("%s: Host Software Queue enabled\n", - mmc_hostname(host)); + + if (card->ext_csd.cmdq_en) { + pr_info("%s: Command Queue Engine enabled, %u tags\n", + mmc_hostname(host), card->ext_csd.cmdq_depth); + } else { + host->hsq_enabled = true; + pr_info("%s: Host Software Queue enabled\n", + mmc_hostname(host)); + } } } @@ -1645,7 +1620,7 @@ static int sd_busy_poweroff_notify_cb(void *cb_data, bool *busy) * one byte offset and is one byte long. The Power Off Notification * Ready is bit 0. */ - err = sd_read_ext_reg(card, card->ext_power.fno, card->ext_power.page, + err = mmc_sd_read_ext_reg(card, card->ext_power.fno, card->ext_power.page, card->ext_power.offset + 1, 1, data->reg_buf); if (err) { pr_warn("%s: error %d reading status reg of PM func\n", @@ -1671,7 +1646,7 @@ static int sd_poweroff_notify(struct mmc_card *card) * Set the Power Off Notification bit in the power management settings * register at 2 bytes offset. */ - err = sd_write_ext_reg(card, card->ext_power.fno, card->ext_power.page, + err = mmc_sd_write_ext_reg(card, card->ext_power.fno, card->ext_power.page, card->ext_power.offset + 2, BIT(0)); if (err) { pr_warn("%s: error %d writing Power Off Notify bit\n", diff --git a/drivers/mmc/core/sd_ops.c b/drivers/mmc/core/sd_ops.c index f93c392040ae7a..47a1903268b2e0 100644 --- a/drivers/mmc/core/sd_ops.c +++ b/drivers/mmc/core/sd_ops.c @@ -8,6 +8,7 @@ #include <linux/slab.h> #include <linux/types.h> #include <linux/export.h> +#include <linux/ktime.h> #include <linux/scatterlist.h> #include <linux/mmc/host.h> @@ -393,3 +394,136 @@ int mmc_app_sd_status(struct mmc_card *card, void *ssr) return 0; } + + +int mmc_sd_write_ext_reg(struct mmc_card *card, u8 fno, u8 page, u16 offset, + u8 reg_data) +{ + struct mmc_host *host = card->host; + struct mmc_request mrq = {}; + struct mmc_command cmd = {}; + struct mmc_data data = {}; + struct scatterlist sg; + u8 *reg_buf; + + reg_buf = card->ext_reg_buf; + memset(reg_buf, 0, 512); + + mrq.cmd = &cmd; + mrq.data = &data; + + /* + * Arguments of CMD49: + * [31:31] MIO (0 = memory). + * [30:27] FNO (function number). + * [26:26] MW - mask write mode (0 = disable). + * [25:18] page number. + * [17:9] offset address. + * [8:0] length (0 = 1 byte). + */ + cmd.arg = fno << 27 | page << 18 | offset << 9; + + /* The first byte in the buffer is the data to be written. */ + reg_buf[0] = reg_data; + + data.flags = MMC_DATA_WRITE; + data.blksz = 512; + data.blocks = 1; + data.sg = &sg; + data.sg_len = 1; + sg_init_one(&sg, reg_buf, 512); + + cmd.opcode = SD_WRITE_EXTR_SINGLE; + cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC; + + mmc_set_data_timeout(&data, card); + mmc_wait_for_req(host, &mrq); + + /* + * Note that, the SD card is allowed to signal busy on DAT0 up to 1s + * after the CMD49. Although, let's leave this to be managed by the + * caller. + */ + + if (cmd.error) + return cmd.error; + if (data.error) + return data.error; + + return 0; +} + +int mmc_sd_read_ext_reg(struct mmc_card *card, u8 fno, u8 page, + u16 offset, u16 len, u8 *reg_buf) +{ + u32 cmd_args; + + /* + * Command arguments of CMD48: + * [31:31] MIO (0 = memory). + * [30:27] FNO (function number). + * [26:26] reserved (0). + * [25:18] page number. + * [17:9] offset address. + * [8:0] length (0 = 1 byte, 1ff = 512 bytes). + */ + cmd_args = fno << 27 | page << 18 | offset << 9 | (len - 1); + + return mmc_send_adtc_data(card, card->host, SD_READ_EXTR_SINGLE, + cmd_args, reg_buf, 512); +} + +static int mmc_sd_cmdq_switch(struct mmc_card *card, bool enable) +{ + int err; + u8 reg = 0; + u8 *reg_buf = card->ext_reg_buf; + ktime_t timeout; + /* + * SD offers two command queueing modes - sequential (in-order) and + * voluntary (out-of-order). Apps Class A2 performance is only + * guaranteed for voluntary CQ (bit 1 = 0), so use that in preference + * to sequential. + */ + if (enable) + reg = BIT(0); + + /* Performance enhancement register byte 262 controls command queueing */ + err = mmc_sd_write_ext_reg(card, card->ext_perf.fno, card->ext_perf.page, + card->ext_perf.offset + 262, reg); + if (err) + goto out; + + /* Poll the register - cards may have a lazy init/deinit sequence. */ + timeout = ktime_add_ms(ktime_get(), 10); + while (1) { + err = mmc_sd_read_ext_reg(card, card->ext_perf.fno, card->ext_perf.page, + card->ext_perf.offset + 262, 1, reg_buf); + if (err) + break; + if ((reg_buf[0] & BIT(0)) == reg) + break; + if (ktime_after(ktime_get(), timeout)) { + err = -EBADMSG; + break; + } + usleep_range(100, 200); + } +out: + if (!err) + card->ext_csd.cmdq_en = enable; + + return err; +} + +int mmc_sd_cmdq_enable(struct mmc_card *card) +{ + return mmc_sd_cmdq_switch(card, true); +} +EXPORT_SYMBOL_GPL(mmc_sd_cmdq_enable); + +int mmc_sd_cmdq_disable(struct mmc_card *card) +{ + return mmc_sd_cmdq_switch(card, false); +} +EXPORT_SYMBOL_GPL(mmc_sd_cmdq_disable); diff --git a/drivers/mmc/core/sd_ops.h b/drivers/mmc/core/sd_ops.h index 7667fc223b7484..1b8138368a81db 100644 --- a/drivers/mmc/core/sd_ops.h +++ b/drivers/mmc/core/sd_ops.h @@ -21,6 +21,12 @@ int mmc_send_relative_addr(struct mmc_host *host, unsigned int *rca); int mmc_app_send_scr(struct mmc_card *card); int mmc_app_sd_status(struct mmc_card *card, void *ssr); int mmc_app_cmd(struct mmc_host *host, struct mmc_card *card); +int mmc_sd_cmdq_enable(struct mmc_card *card); +int mmc_sd_cmdq_disable(struct mmc_card *card); +int mmc_sd_write_ext_reg(struct mmc_card *card, u8 fno, u8 page, u16 offset, + u8 reg_data); +int mmc_sd_read_ext_reg(struct mmc_card *card, u8 fno, u8 page, + u16 offset, u16 len, u8 *reg_buf); #endif diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 7199cb0bd0b9e7..7309899953e156 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -5,6 +5,46 @@ comment "MMC/SD/SDIO Host Controller Drivers" +config MMC_BCM2835_MMC + tristate "MMC support on BCM2835" + depends on MACH_BCM2708 || MACH_BCM2709 || ARCH_BCM2835 + help + This selects the MMC Interface on BCM2835. + + If you have a controller with this interface, say Y or M here. + + If unsure, say N. + +config MMC_BCM2835_DMA + bool "DMA support on BCM2835 Arasan controller" + depends on MMC_BCM2835_MMC + help + Enable DMA support on the Arasan SDHCI controller in Broadcom 2708 + based chips. + + If unsure, say N. + +config MMC_BCM2835_PIO_DMA_BARRIER + int "Block count limit for PIO transfers" + depends on MMC_BCM2835_MMC && MMC_BCM2835_DMA + range 0 256 + default 2 + help + The inclusive limit in bytes under which PIO will be used instead of DMA + + If unsure, say 2 here. + +config MMC_BCM2835_SDHOST + tristate "Support for the SDHost controller on BCM2708/9" + depends on ARCH_BCM2835 + select MMC_HSQ + help + This selects the SDHost controller on BCM2835/6. + + If you have a controller with this interface, say Y or M here. + + If unsure, say N. + config MMC_DEBUG bool "MMC host drivers debugging" depends on MMC != n @@ -1031,6 +1071,7 @@ config MMC_SDHCI_BRCMSTB depends on ARCH_BRCMSTB || ARCH_BCM2835 || BMIPS_GENERIC || COMPILE_TEST depends on MMC_SDHCI_PLTFM select MMC_CQHCI + select OF_DYNAMIC default ARCH_BRCMSTB || BMIPS_GENERIC help This selects support for the SDIO/SD/MMC Host Controller on diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index 3ccffebbe59b91..89443b08b16f5a 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -22,6 +22,8 @@ obj-$(CONFIG_MMC_SDHCI_F_SDH30) += sdhci_f_sdh30.o obj-$(CONFIG_MMC_SDHCI_MILBEAUT) += sdhci-milbeaut.o obj-$(CONFIG_MMC_SDHCI_SPEAR) += sdhci-spear.o obj-$(CONFIG_MMC_SDHCI_AM654) += sdhci_am654.o +obj-$(CONFIG_MMC_BCM2835_MMC) += bcm2835-mmc.o +obj-$(CONFIG_MMC_BCM2835_SDHOST) += bcm2835-sdhost.o obj-$(CONFIG_MMC_WBSD) += wbsd.o obj-$(CONFIG_MMC_AU1X) += au1xmmc.o obj-$(CONFIG_MMC_ALCOR) += alcor.o diff --git a/drivers/mmc/host/bcm2835-mmc.c b/drivers/mmc/host/bcm2835-mmc.c new file mode 100644 index 00000000000000..e24e6bec329e08 --- /dev/null +++ b/drivers/mmc/host/bcm2835-mmc.c @@ -0,0 +1,1560 @@ +/* + * BCM2835 MMC host driver. + * + * Author: Gellert Weisz <gellert@raspberrypi.org> + * Copyright 2014 + * + * Based on + * sdhci-bcm2708.c by Broadcom + * sdhci-bcm2835.c by Stephen Warren and Oleksandr Tymoshenko + * sdhci.c and sdhci-pci.c by Pierre Ossman + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/mmc/mmc.h> +#include <linux/mmc/host.h> +#include <linux/mmc/sd.h> +#include <linux/scatterlist.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/blkdev.h> +#include <linux/dmaengine.h> +#include <linux/dma-mapping.h> +#include <linux/of_dma.h> +#include <linux/swiotlb.h> + +#include "sdhci.h" + + +#define DRIVER_NAME "mmc-bcm2835" + +#define DBG(f, x...) \ +pr_debug(DRIVER_NAME " [%s()]: " f, __func__, ## x) + +#ifndef CONFIG_MMC_BCM2835_DMA + #define FORCE_PIO +#endif + + +/* the inclusive limit in bytes under which PIO will be used instead of DMA */ +#ifdef CONFIG_MMC_BCM2835_PIO_DMA_BARRIER +#define PIO_DMA_BARRIER CONFIG_MMC_BCM2835_PIO_DMA_BARRIER +#else +#define PIO_DMA_BARRIER 00 +#endif + +#define MIN_FREQ 400000 +#define TIMEOUT_VAL 0xE +#define BCM2835_SDHCI_WRITE_DELAY(f) (((2 * 1000000) / f) + 1) + + +unsigned mmc_debug; +unsigned mmc_debug2; + +struct bcm2835_host { + spinlock_t lock; + + void __iomem *ioaddr; + u32 bus_addr; + + struct mmc_host *mmc; + + u32 timeout; + + int clock; /* Current clock speed */ + u8 pwr; /* Current voltage */ + + unsigned int max_clk; /* Max possible freq */ + unsigned int timeout_clk; /* Timeout freq (KHz) */ + unsigned int clk_mul; /* Clock Muliplier value */ + + struct tasklet_struct finish_tasklet; /* Tasklet structures */ + + struct timer_list timer; /* Timer for timeouts */ + + struct sg_mapping_iter sg_miter; /* SG state for PIO */ + unsigned int blocks; /* remaining PIO blocks */ + + int irq; /* Device IRQ */ + + + u32 ier; /* cached registers */ + + struct mmc_request *mrq; /* Current request */ + struct mmc_command *cmd; /* Current command */ + struct mmc_data *data; /* Current data request */ + unsigned int data_early:1; /* Data finished before cmd */ + + wait_queue_head_t buf_ready_int; /* Waitqueue for Buffer Read Ready interrupt */ + + u32 shadow; + + /*DMA part*/ + struct dma_chan *dma_chan_rxtx; /* DMA channel for reads and writes */ + struct dma_slave_config dma_cfg_rx; + struct dma_slave_config dma_cfg_tx; + struct dma_async_tx_descriptor *tx_desc; /* descriptor */ + + bool have_dma; + bool use_dma; + bool wait_for_dma; + /*end of DMA part*/ + + int max_delay; /* maximum length of time spent waiting */ + + int flags; /* Host attributes */ +#define SDHCI_REQ_USE_DMA (1<<2) /* Use DMA for this req. */ +#define SDHCI_DEVICE_DEAD (1<<3) /* Device unresponsive */ +#define SDHCI_AUTO_CMD12 (1<<6) /* Auto CMD12 support */ +#define SDHCI_AUTO_CMD23 (1<<7) /* Auto CMD23 support */ +#define SDHCI_SDIO_IRQ_ENABLED (1<<9) /* SDIO irq enabled */ + + u32 overclock_50; /* frequency to use when 50MHz is requested (in MHz) */ + u32 max_overclock; /* Highest reported */ +}; + + +static inline void bcm2835_mmc_writel(struct bcm2835_host *host, u32 val, int reg, int from) +{ + unsigned delay; + lockdep_assert_held_once(&host->lock); + writel(val, host->ioaddr + reg); + udelay(BCM2835_SDHCI_WRITE_DELAY(max(host->clock, MIN_FREQ))); + + delay = ((mmc_debug >> 16) & 0xf) << ((mmc_debug >> 20) & 0xf); + if (delay && !((1<<from) & mmc_debug2)) + udelay(delay); +} + +static inline void mmc_raw_writel(struct bcm2835_host *host, u32 val, int reg) +{ + unsigned delay; + lockdep_assert_held_once(&host->lock); + writel(val, host->ioaddr + reg); + + delay = ((mmc_debug >> 24) & 0xf) << ((mmc_debug >> 28) & 0xf); + if (delay) + udelay(delay); +} + +static inline u32 bcm2835_mmc_readl(struct bcm2835_host *host, int reg) +{ + lockdep_assert_held_once(&host->lock); + return readl(host->ioaddr + reg); +} + +static inline void bcm2835_mmc_writew(struct bcm2835_host *host, u16 val, int reg) +{ + u32 oldval = (reg == SDHCI_COMMAND) ? host->shadow : + bcm2835_mmc_readl(host, reg & ~3); + u32 word_num = (reg >> 1) & 1; + u32 word_shift = word_num * 16; + u32 mask = 0xffff << word_shift; + u32 newval = (oldval & ~mask) | (val << word_shift); + + if (reg == SDHCI_TRANSFER_MODE) + host->shadow = newval; + else + bcm2835_mmc_writel(host, newval, reg & ~3, 0); + +} + +static inline void bcm2835_mmc_writeb(struct bcm2835_host *host, u8 val, int reg) +{ + u32 oldval = bcm2835_mmc_readl(host, reg & ~3); + u32 byte_num = reg & 3; + u32 byte_shift = byte_num * 8; + u32 mask = 0xff << byte_shift; + u32 newval = (oldval & ~mask) | (val << byte_shift); + + bcm2835_mmc_writel(host, newval, reg & ~3, 1); +} + + +static inline u16 bcm2835_mmc_readw(struct bcm2835_host *host, int reg) +{ + u32 val = bcm2835_mmc_readl(host, (reg & ~3)); + u32 word_num = (reg >> 1) & 1; + u32 word_shift = word_num * 16; + u32 word = (val >> word_shift) & 0xffff; + + return word; +} + +static inline u8 bcm2835_mmc_readb(struct bcm2835_host *host, int reg) +{ + u32 val = bcm2835_mmc_readl(host, (reg & ~3)); + u32 byte_num = reg & 3; + u32 byte_shift = byte_num * 8; + u32 byte = (val >> byte_shift) & 0xff; + + return byte; +} + +static void bcm2835_mmc_unsignal_irqs(struct bcm2835_host *host, u32 clear) +{ + u32 ier; + + ier = bcm2835_mmc_readl(host, SDHCI_SIGNAL_ENABLE); + ier &= ~clear; + /* change which requests generate IRQs - makes no difference to + the content of SDHCI_INT_STATUS, or the need to acknowledge IRQs */ + bcm2835_mmc_writel(host, ier, SDHCI_SIGNAL_ENABLE, 2); +} + + +static void bcm2835_mmc_dumpregs(struct bcm2835_host *host) +{ + pr_debug(DRIVER_NAME ": =========== REGISTER DUMP (%s)===========\n", + mmc_hostname(host->mmc)); + + pr_debug(DRIVER_NAME ": Sys addr: 0x%08x | Version: 0x%08x\n", + bcm2835_mmc_readl(host, SDHCI_DMA_ADDRESS), + bcm2835_mmc_readw(host, SDHCI_HOST_VERSION)); + pr_debug(DRIVER_NAME ": Blk size: 0x%08x | Blk cnt: 0x%08x\n", + bcm2835_mmc_readw(host, SDHCI_BLOCK_SIZE), + bcm2835_mmc_readw(host, SDHCI_BLOCK_COUNT)); + pr_debug(DRIVER_NAME ": Argument: 0x%08x | Trn mode: 0x%08x\n", + bcm2835_mmc_readl(host, SDHCI_ARGUMENT), + bcm2835_mmc_readw(host, SDHCI_TRANSFER_MODE)); + pr_debug(DRIVER_NAME ": Present: 0x%08x | Host ctl: 0x%08x\n", + bcm2835_mmc_readl(host, SDHCI_PRESENT_STATE), + bcm2835_mmc_readb(host, SDHCI_HOST_CONTROL)); + pr_debug(DRIVER_NAME ": Power: 0x%08x | Blk gap: 0x%08x\n", + bcm2835_mmc_readb(host, SDHCI_POWER_CONTROL), + bcm2835_mmc_readb(host, SDHCI_BLOCK_GAP_CONTROL)); + pr_debug(DRIVER_NAME ": Wake-up: 0x%08x | Clock: 0x%08x\n", + bcm2835_mmc_readb(host, SDHCI_WAKE_UP_CONTROL), + bcm2835_mmc_readw(host, SDHCI_CLOCK_CONTROL)); + pr_debug(DRIVER_NAME ": Timeout: 0x%08x | Int stat: 0x%08x\n", + bcm2835_mmc_readb(host, SDHCI_TIMEOUT_CONTROL), + bcm2835_mmc_readl(host, SDHCI_INT_STATUS)); + pr_debug(DRIVER_NAME ": Int enab: 0x%08x | Sig enab: 0x%08x\n", + bcm2835_mmc_readl(host, SDHCI_INT_ENABLE), + bcm2835_mmc_readl(host, SDHCI_SIGNAL_ENABLE)); + pr_debug(DRIVER_NAME ": AC12 err: 0x%08x | Slot int: 0x%08x\n", + bcm2835_mmc_readw(host, SDHCI_AUTO_CMD_STATUS), + bcm2835_mmc_readw(host, SDHCI_SLOT_INT_STATUS)); + pr_debug(DRIVER_NAME ": Caps: 0x%08x | Caps_1: 0x%08x\n", + bcm2835_mmc_readl(host, SDHCI_CAPABILITIES), + bcm2835_mmc_readl(host, SDHCI_CAPABILITIES_1)); + pr_debug(DRIVER_NAME ": Cmd: 0x%08x | Max curr: 0x%08x\n", + bcm2835_mmc_readw(host, SDHCI_COMMAND), + bcm2835_mmc_readl(host, SDHCI_MAX_CURRENT)); + pr_debug(DRIVER_NAME ": Host ctl2: 0x%08x\n", + bcm2835_mmc_readw(host, SDHCI_HOST_CONTROL2)); + + pr_debug(DRIVER_NAME ": ===========================================\n"); +} + + +static void bcm2835_mmc_reset(struct bcm2835_host *host, u8 mask) +{ + unsigned long timeout; + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + bcm2835_mmc_writeb(host, mask, SDHCI_SOFTWARE_RESET); + + if (mask & SDHCI_RESET_ALL) + host->clock = 0; + + /* Wait max 100 ms */ + timeout = 100; + + /* hw clears the bit when it's done */ + while (bcm2835_mmc_readb(host, SDHCI_SOFTWARE_RESET) & mask) { + if (timeout == 0) { + pr_err("%s: Reset 0x%x never completed.\n", + mmc_hostname(host->mmc), (int)mask); + bcm2835_mmc_dumpregs(host); + return; + } + timeout--; + spin_unlock_irqrestore(&host->lock, flags); + mdelay(1); + spin_lock_irqsave(&host->lock, flags); + } + + if (100-timeout > 10 && 100-timeout > host->max_delay) { + host->max_delay = 100-timeout; + pr_warn("Warning: MMC controller hung for %d ms\n", host->max_delay); + } + spin_unlock_irqrestore(&host->lock, flags); +} + +static void bcm2835_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios); + +static void bcm2835_mmc_init(struct bcm2835_host *host, int soft) +{ + unsigned long flags; + if (soft) + bcm2835_mmc_reset(host, SDHCI_RESET_CMD|SDHCI_RESET_DATA); + else + bcm2835_mmc_reset(host, SDHCI_RESET_ALL); + + host->ier = SDHCI_INT_BUS_POWER | SDHCI_INT_DATA_END_BIT | + SDHCI_INT_DATA_CRC | SDHCI_INT_DATA_TIMEOUT | + SDHCI_INT_INDEX | SDHCI_INT_END_BIT | SDHCI_INT_CRC | + SDHCI_INT_TIMEOUT | SDHCI_INT_DATA_END | + SDHCI_INT_RESPONSE; + + spin_lock_irqsave(&host->lock, flags); + bcm2835_mmc_writel(host, host->ier, SDHCI_INT_ENABLE, 3); + bcm2835_mmc_writel(host, host->ier, SDHCI_SIGNAL_ENABLE, 3); + spin_unlock_irqrestore(&host->lock, flags); + + if (soft) { + /* force clock reconfiguration */ + host->clock = 0; + bcm2835_mmc_set_ios(host->mmc, &host->mmc->ios); + } +} + + + +static void bcm2835_mmc_finish_data(struct bcm2835_host *host); + +static void bcm2835_mmc_dma_complete(void *param) +{ + struct bcm2835_host *host = param; + struct dma_chan *dma_chan; + unsigned long flags; + u32 dir_data; + + spin_lock_irqsave(&host->lock, flags); + + host->use_dma = false; + + if (host->data) { + dma_chan = host->dma_chan_rxtx; + if (host->data->flags & MMC_DATA_WRITE) + dir_data = DMA_TO_DEVICE; + else + dir_data = DMA_FROM_DEVICE; + dma_unmap_sg(dma_chan->device->dev, + host->data->sg, host->data->sg_len, + dir_data); + if (! (host->data->flags & MMC_DATA_WRITE)) + bcm2835_mmc_finish_data(host); + } else if (host->wait_for_dma) { + host->wait_for_dma = false; + tasklet_schedule(&host->finish_tasklet); + } + + spin_unlock_irqrestore(&host->lock, flags); +} + +static void bcm2835_bcm2835_mmc_read_block_pio(struct bcm2835_host *host) +{ + unsigned long flags; + size_t blksize, len, chunk; + + u32 scratch = 0; + u8 *buf; + + blksize = host->data->blksz; + chunk = 0; + + local_irq_save(flags); + + while (blksize) { + if (!sg_miter_next(&host->sg_miter)) + BUG(); + + len = min(host->sg_miter.length, blksize); + + blksize -= len; + host->sg_miter.consumed = len; + + buf = host->sg_miter.addr; + + while (len) { + if (chunk == 0) { + scratch = bcm2835_mmc_readl(host, SDHCI_BUFFER); + chunk = 4; + } + + *buf = scratch & 0xFF; + + buf++; + scratch >>= 8; + chunk--; + len--; + } + } + + sg_miter_stop(&host->sg_miter); + + local_irq_restore(flags); +} + +static void bcm2835_bcm2835_mmc_write_block_pio(struct bcm2835_host *host) +{ + unsigned long flags; + size_t blksize, len, chunk; + u32 scratch; + u8 *buf; + + blksize = host->data->blksz; + chunk = 0; + chunk = 0; + scratch = 0; + + local_irq_save(flags); + + while (blksize) { + if (!sg_miter_next(&host->sg_miter)) + BUG(); + + len = min(host->sg_miter.length, blksize); + + blksize -= len; + host->sg_miter.consumed = len; + + buf = host->sg_miter.addr; + + while (len) { + scratch |= (u32)*buf << (chunk * 8); + + buf++; + chunk++; + len--; + + if ((chunk == 4) || ((len == 0) && (blksize == 0))) { + mmc_raw_writel(host, scratch, SDHCI_BUFFER); + chunk = 0; + scratch = 0; + } + } + } + + sg_miter_stop(&host->sg_miter); + + local_irq_restore(flags); +} + + +static void bcm2835_mmc_transfer_pio(struct bcm2835_host *host) +{ + u32 mask; + + BUG_ON(!host->data); + + if (host->blocks == 0) + return; + + if (host->data->flags & MMC_DATA_READ) + mask = SDHCI_DATA_AVAILABLE; + else + mask = SDHCI_SPACE_AVAILABLE; + + while (bcm2835_mmc_readl(host, SDHCI_PRESENT_STATE) & mask) { + + if (host->data->flags & MMC_DATA_READ) + bcm2835_bcm2835_mmc_read_block_pio(host); + else + bcm2835_bcm2835_mmc_write_block_pio(host); + + host->blocks--; + + /* QUIRK used in sdhci.c removes the 'if' */ + /* but it seems this is unnecessary */ + if (host->blocks == 0) + break; + + + } +} + + +static void bcm2835_mmc_transfer_dma(struct bcm2835_host *host) +{ + u32 len, dir_data, dir_slave; + struct dma_async_tx_descriptor *desc = NULL; + struct dma_chan *dma_chan; + + + WARN_ON(!host->data); + + if (!host->data) + return; + + if (host->blocks == 0) + return; + + dma_chan = host->dma_chan_rxtx; + if (host->data->flags & MMC_DATA_READ) { + dir_data = DMA_FROM_DEVICE; + dir_slave = DMA_DEV_TO_MEM; + } else { + dir_data = DMA_TO_DEVICE; + dir_slave = DMA_MEM_TO_DEV; + } + + /* The parameters have already been validated, so this will not fail */ + (void)dmaengine_slave_config(dma_chan, + (dir_data == DMA_FROM_DEVICE) ? + &host->dma_cfg_rx : + &host->dma_cfg_tx); + + BUG_ON(!dma_chan->device); + BUG_ON(!dma_chan->device->dev); + BUG_ON(!host->data->sg); + + len = dma_map_sg(dma_chan->device->dev, host->data->sg, + host->data->sg_len, dir_data); + if (len > 0) { + desc = dmaengine_prep_slave_sg(dma_chan, host->data->sg, + len, dir_slave, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + } else { + dev_err(mmc_dev(host->mmc), "dma_map_sg returned zero length\n"); + } + if (desc) { + unsigned long flags; + spin_lock_irqsave(&host->lock, flags); + bcm2835_mmc_unsignal_irqs(host, SDHCI_INT_DATA_AVAIL | + SDHCI_INT_SPACE_AVAIL); + host->tx_desc = desc; + desc->callback = bcm2835_mmc_dma_complete; + desc->callback_param = host; + spin_unlock_irqrestore(&host->lock, flags); + dmaengine_submit(desc); + dma_async_issue_pending(dma_chan); + } else { + dma_unmap_sg(dma_chan->device->dev, host->data->sg, len, dir_data); + } + +} + + + +static void bcm2835_mmc_set_transfer_irqs(struct bcm2835_host *host) +{ + u32 pio_irqs = SDHCI_INT_DATA_AVAIL | SDHCI_INT_SPACE_AVAIL; + u32 dma_irqs = SDHCI_INT_DMA_END | SDHCI_INT_ADMA_ERROR; + + if (host->use_dma) + host->ier = (host->ier & ~pio_irqs) | dma_irqs; + else + host->ier = (host->ier & ~dma_irqs) | pio_irqs; + + bcm2835_mmc_writel(host, host->ier, SDHCI_INT_ENABLE, 4); + bcm2835_mmc_writel(host, host->ier, SDHCI_SIGNAL_ENABLE, 4); +} + + +static void bcm2835_mmc_prepare_data(struct bcm2835_host *host, struct mmc_command *cmd) +{ + u8 count; + struct mmc_data *data = cmd->data; + + WARN_ON(host->data); + + if (data || (cmd->flags & MMC_RSP_BUSY)) { + count = TIMEOUT_VAL; + bcm2835_mmc_writeb(host, count, SDHCI_TIMEOUT_CONTROL); + } + + if (!data) + return; + + /* Sanity checks */ + BUG_ON(data->blksz * data->blocks > 524288); + BUG_ON(data->blksz > host->mmc->max_blk_size); + BUG_ON(data->blocks > 65535); + + host->data = data; + host->data_early = 0; + host->data->bytes_xfered = 0; + + + if (!(host->flags & SDHCI_REQ_USE_DMA)) { + int flags; + + flags = SG_MITER_ATOMIC; + if (host->data->flags & MMC_DATA_READ) + flags |= SG_MITER_TO_SG; + else + flags |= SG_MITER_FROM_SG; + sg_miter_start(&host->sg_miter, data->sg, data->sg_len, flags); + host->blocks = data->blocks; + } + + host->use_dma = host->have_dma && data->blocks > PIO_DMA_BARRIER; + + bcm2835_mmc_set_transfer_irqs(host); + + /* Set the DMA boundary value and block size */ + bcm2835_mmc_writew(host, SDHCI_MAKE_BLKSZ(SDHCI_DEFAULT_BOUNDARY_ARG, + data->blksz), SDHCI_BLOCK_SIZE); + bcm2835_mmc_writew(host, data->blocks, SDHCI_BLOCK_COUNT); + + BUG_ON(!host->data); +} + +static void bcm2835_mmc_set_transfer_mode(struct bcm2835_host *host, + struct mmc_command *cmd) +{ + u16 mode; + struct mmc_data *data = cmd->data; + + if (data == NULL) { + /* clear Auto CMD settings for no data CMDs */ + mode = bcm2835_mmc_readw(host, SDHCI_TRANSFER_MODE); + bcm2835_mmc_writew(host, mode & ~(SDHCI_TRNS_AUTO_CMD12 | + SDHCI_TRNS_AUTO_CMD23), SDHCI_TRANSFER_MODE); + return; + } + + WARN_ON(!host->data); + + mode = SDHCI_TRNS_BLK_CNT_EN; + + if ((mmc_op_multi(cmd->opcode) || data->blocks > 1)) { + mode |= SDHCI_TRNS_MULTI; + + /* + * If we are sending CMD23, CMD12 never gets sent + * on successful completion (so no Auto-CMD12). + */ + if (!host->mrq->sbc && (host->flags & SDHCI_AUTO_CMD12)) + mode |= SDHCI_TRNS_AUTO_CMD12; + else if (host->mrq->sbc && (host->flags & SDHCI_AUTO_CMD23)) { + mode |= SDHCI_TRNS_AUTO_CMD23; + bcm2835_mmc_writel(host, host->mrq->sbc->arg, SDHCI_ARGUMENT2, 5); + } + } + + if (data->flags & MMC_DATA_READ) + mode |= SDHCI_TRNS_READ; + if (host->flags & SDHCI_REQ_USE_DMA) + mode |= SDHCI_TRNS_DMA; + + bcm2835_mmc_writew(host, mode, SDHCI_TRANSFER_MODE); +} + +static void bcm2835_mmc_send_command(struct bcm2835_host *host, struct mmc_command *cmd) +{ + int flags; + u32 mask; + unsigned long timeout; + + WARN_ON(host->cmd); + + /* Wait max 10 ms */ + timeout = 1000; + + mask = SDHCI_CMD_INHIBIT; + if ((cmd->data != NULL) || (cmd->flags & MMC_RSP_BUSY)) + mask |= SDHCI_DATA_INHIBIT; + + /* We shouldn't wait for data inihibit for stop commands, even + though they might use busy signaling */ + if (host->mrq->data && (cmd == host->mrq->data->stop)) + mask &= ~SDHCI_DATA_INHIBIT; + + while (bcm2835_mmc_readl(host, SDHCI_PRESENT_STATE) & mask) { + if (timeout == 0) { + pr_err("%s: Controller never released inhibit bit(s).\n", + mmc_hostname(host->mmc)); + bcm2835_mmc_dumpregs(host); + cmd->error = -EIO; + tasklet_schedule(&host->finish_tasklet); + return; + } + timeout--; + udelay(10); + } + + if ((1000-timeout)/100 > 1 && (1000-timeout)/100 > host->max_delay) { + host->max_delay = (1000-timeout)/100; + pr_warn("Warning: MMC controller hung for %d ms\n", host->max_delay); + } + + timeout = jiffies; + if (!cmd->data && cmd->busy_timeout > 9000) + timeout += DIV_ROUND_UP(cmd->busy_timeout, 1000) * HZ + HZ; + else + timeout += 10 * HZ; + mod_timer(&host->timer, timeout); + + host->cmd = cmd; + host->use_dma = false; + + bcm2835_mmc_prepare_data(host, cmd); + + bcm2835_mmc_writel(host, cmd->arg, SDHCI_ARGUMENT, 6); + + bcm2835_mmc_set_transfer_mode(host, cmd); + + if ((cmd->flags & MMC_RSP_136) && (cmd->flags & MMC_RSP_BUSY)) { + pr_err("%s: Unsupported response type!\n", + mmc_hostname(host->mmc)); + cmd->error = -EINVAL; + tasklet_schedule(&host->finish_tasklet); + return; + } + + if (!(cmd->flags & MMC_RSP_PRESENT)) + flags = SDHCI_CMD_RESP_NONE; + else if (cmd->flags & MMC_RSP_136) + flags = SDHCI_CMD_RESP_LONG; + else if (cmd->flags & MMC_RSP_BUSY) + flags = SDHCI_CMD_RESP_SHORT_BUSY; + else + flags = SDHCI_CMD_RESP_SHORT; + + if (cmd->flags & MMC_RSP_CRC) + flags |= SDHCI_CMD_CRC; + if (cmd->flags & MMC_RSP_OPCODE) + flags |= SDHCI_CMD_INDEX; + + if (cmd->data) + flags |= SDHCI_CMD_DATA; + + bcm2835_mmc_writew(host, SDHCI_MAKE_CMD(cmd->opcode, flags), SDHCI_COMMAND); +} + + +static void bcm2835_mmc_finish_data(struct bcm2835_host *host) +{ + struct mmc_data *data; + + BUG_ON(!host->data); + + data = host->data; + host->data = NULL; + + if (data->error) + data->bytes_xfered = 0; + else + data->bytes_xfered = data->blksz * data->blocks; + + /* + * Need to send CMD12 if - + * a) open-ended multiblock transfer (no CMD23) + * b) error in multiblock transfer + */ + if (data->stop && + (data->error || + !host->mrq->sbc)) { + + /* + * The controller needs a reset of internal state machines + * upon error conditions. + */ + if (data->error) { + bcm2835_mmc_reset(host, SDHCI_RESET_CMD); + bcm2835_mmc_reset(host, SDHCI_RESET_DATA); + } + + bcm2835_mmc_send_command(host, data->stop); + } else if (host->use_dma) { + host->wait_for_dma = true; + } else { + tasklet_schedule(&host->finish_tasklet); + } +} + +static void bcm2835_mmc_finish_command(struct bcm2835_host *host) +{ + int i; + + BUG_ON(host->cmd == NULL); + + if (host->cmd->flags & MMC_RSP_PRESENT) { + if (host->cmd->flags & MMC_RSP_136) { + /* CRC is stripped so we need to do some shifting. */ + for (i = 0; i < 4; i++) { + host->cmd->resp[i] = bcm2835_mmc_readl(host, + SDHCI_RESPONSE + (3-i)*4) << 8; + if (i != 3) + host->cmd->resp[i] |= + bcm2835_mmc_readb(host, + SDHCI_RESPONSE + (3-i)*4-1); + } + } else { + host->cmd->resp[0] = bcm2835_mmc_readl(host, SDHCI_RESPONSE); + } + } + + host->cmd->error = 0; + + /* Finished CMD23, now send actual command. */ + if (host->cmd == host->mrq->sbc) { + host->cmd = NULL; + bcm2835_mmc_send_command(host, host->mrq->cmd); + + if (host->mrq->cmd->data && host->use_dma) { + /* DMA transfer starts now, PIO starts after interrupt */ + bcm2835_mmc_transfer_dma(host); + } + } else { + + /* Processed actual command. */ + if (host->data && host->data_early) + bcm2835_mmc_finish_data(host); + + if (!host->cmd->data) + tasklet_schedule(&host->finish_tasklet); + + host->cmd = NULL; + } +} + + +static void bcm2835_mmc_timeout_timer(struct timer_list *t) +{ + struct bcm2835_host *host = from_timer(host, t, timer); + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + + if (host->mrq) { + pr_err("%s: Timeout waiting for hardware interrupt.\n", + mmc_hostname(host->mmc)); + bcm2835_mmc_dumpregs(host); + + if (host->data) { + host->data->error = -ETIMEDOUT; + bcm2835_mmc_finish_data(host); + } else { + if (host->cmd) + host->cmd->error = -ETIMEDOUT; + else + host->mrq->cmd->error = -ETIMEDOUT; + + tasklet_schedule(&host->finish_tasklet); + } + } + + spin_unlock_irqrestore(&host->lock, flags); +} + + +static void bcm2835_mmc_enable_sdio_irq_nolock(struct bcm2835_host *host, int enable) +{ + if (!(host->flags & SDHCI_DEVICE_DEAD)) { + if (enable) + host->ier |= SDHCI_INT_CARD_INT; + else + host->ier &= ~SDHCI_INT_CARD_INT; + + bcm2835_mmc_writel(host, host->ier, SDHCI_INT_ENABLE, 7); + bcm2835_mmc_writel(host, host->ier, SDHCI_SIGNAL_ENABLE, 7); + } +} + +static void bcm2835_mmc_enable_sdio_irq(struct mmc_host *mmc, int enable) +{ + struct bcm2835_host *host = mmc_priv(mmc); + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + if (enable) + host->flags |= SDHCI_SDIO_IRQ_ENABLED; + else + host->flags &= ~SDHCI_SDIO_IRQ_ENABLED; + + bcm2835_mmc_enable_sdio_irq_nolock(host, enable); + spin_unlock_irqrestore(&host->lock, flags); +} + +static void bcm2835_mmc_cmd_irq(struct bcm2835_host *host, u32 intmask) +{ + + BUG_ON(intmask == 0); + + if (!host->cmd) { + pr_err("%s: Got command interrupt 0x%08x even " + "though no command operation was in progress.\n", + mmc_hostname(host->mmc), (unsigned)intmask); + bcm2835_mmc_dumpregs(host); + return; + } + + if (intmask & SDHCI_INT_TIMEOUT) + host->cmd->error = -ETIMEDOUT; + else if (intmask & (SDHCI_INT_CRC | SDHCI_INT_END_BIT | + SDHCI_INT_INDEX)) { + host->cmd->error = -EILSEQ; + } + + if (host->cmd->error) { + tasklet_schedule(&host->finish_tasklet); + return; + } + + if (intmask & SDHCI_INT_RESPONSE) + bcm2835_mmc_finish_command(host); + +} + +static void bcm2835_mmc_data_irq(struct bcm2835_host *host, u32 intmask) +{ + struct dma_chan *dma_chan; + u32 dir_data; + + BUG_ON(intmask == 0); + + if (!host->data) { + /* + * The "data complete" interrupt is also used to + * indicate that a busy state has ended. See comment + * above in sdhci_cmd_irq(). + */ + if (host->cmd && (host->cmd->flags & MMC_RSP_BUSY)) { + if (intmask & SDHCI_INT_DATA_END) { + bcm2835_mmc_finish_command(host); + return; + } + } + + pr_debug("%s: Got data interrupt 0x%08x even " + "though no data operation was in progress.\n", + mmc_hostname(host->mmc), (unsigned)intmask); + bcm2835_mmc_dumpregs(host); + + return; + } + + if (intmask & SDHCI_INT_DATA_TIMEOUT) + host->data->error = -ETIMEDOUT; + else if (intmask & SDHCI_INT_DATA_END_BIT) + host->data->error = -EILSEQ; + else if ((intmask & SDHCI_INT_DATA_CRC) && + SDHCI_GET_CMD(bcm2835_mmc_readw(host, SDHCI_COMMAND)) + != MMC_BUS_TEST_R) + host->data->error = -EILSEQ; + + if (host->use_dma) { + if (host->data->flags & MMC_DATA_WRITE) { + /* IRQ handled here */ + + dma_chan = host->dma_chan_rxtx; + dir_data = DMA_TO_DEVICE; + dma_unmap_sg(dma_chan->device->dev, + host->data->sg, host->data->sg_len, + dir_data); + + bcm2835_mmc_finish_data(host); + } + + } else { + if (host->data->error) + bcm2835_mmc_finish_data(host); + else { + if (intmask & (SDHCI_INT_DATA_AVAIL | SDHCI_INT_SPACE_AVAIL)) + bcm2835_mmc_transfer_pio(host); + + if (intmask & SDHCI_INT_DATA_END) { + if (host->cmd) { + /* + * Data managed to finish before the + * command completed. Make sure we do + * things in the proper order. + */ + host->data_early = 1; + } else { + bcm2835_mmc_finish_data(host); + } + } + } + } +} + + +static irqreturn_t bcm2835_mmc_irq(int irq, void *dev_id) +{ + irqreturn_t result = IRQ_NONE; + struct bcm2835_host *host = dev_id; + u32 intmask, mask, unexpected = 0; + int max_loops = 16; + + spin_lock(&host->lock); + + intmask = bcm2835_mmc_readl(host, SDHCI_INT_STATUS); + + if (!intmask || intmask == 0xffffffff) { + result = IRQ_NONE; + goto out; + } + + do { + /* Clear selected interrupts. */ + mask = intmask & (SDHCI_INT_CMD_MASK | SDHCI_INT_DATA_MASK | + SDHCI_INT_BUS_POWER); + bcm2835_mmc_writel(host, mask, SDHCI_INT_STATUS, 8); + + + if (intmask & SDHCI_INT_CMD_MASK) + bcm2835_mmc_cmd_irq(host, intmask & SDHCI_INT_CMD_MASK); + + if (intmask & SDHCI_INT_DATA_MASK) + bcm2835_mmc_data_irq(host, intmask & SDHCI_INT_DATA_MASK); + + if (intmask & SDHCI_INT_BUS_POWER) + pr_err("%s: Card is consuming too much power!\n", + mmc_hostname(host->mmc)); + + if (intmask & SDHCI_INT_CARD_INT) { + bcm2835_mmc_enable_sdio_irq_nolock(host, false); + sdio_signal_irq(host->mmc); + } + + intmask &= ~(SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE | + SDHCI_INT_CMD_MASK | SDHCI_INT_DATA_MASK | + SDHCI_INT_ERROR | SDHCI_INT_BUS_POWER | + SDHCI_INT_CARD_INT); + + if (intmask) { + unexpected |= intmask; + bcm2835_mmc_writel(host, intmask, SDHCI_INT_STATUS, 9); + } + + if (result == IRQ_NONE) + result = IRQ_HANDLED; + + intmask = bcm2835_mmc_readl(host, SDHCI_INT_STATUS); + } while (intmask && --max_loops); +out: + spin_unlock(&host->lock); + + if (unexpected) { + pr_err("%s: Unexpected interrupt 0x%08x.\n", + mmc_hostname(host->mmc), unexpected); + bcm2835_mmc_dumpregs(host); + } + + return result; +} + + +static void bcm2835_mmc_ack_sdio_irq(struct mmc_host *mmc) +{ + struct bcm2835_host *host = mmc_priv(mmc); + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + if (host->flags & SDHCI_SDIO_IRQ_ENABLED) + bcm2835_mmc_enable_sdio_irq_nolock(host, true); + spin_unlock_irqrestore(&host->lock, flags); +} + +static void bcm2835_mmc_set_clock(struct bcm2835_host *host, unsigned int clock) +{ + int div = 0; /* Initialized for compiler warning */ + int real_div = div, clk_mul = 1; + u16 clk = 0; + unsigned long timeout; + unsigned int input_clock = clock; + + if (host->overclock_50 && (clock == 50000000)) + clock = host->overclock_50 * 1000000 + 999999; + + host->mmc->actual_clock = 0; + + bcm2835_mmc_writew(host, 0, SDHCI_CLOCK_CONTROL); + + if (clock == 0) + return; + + /* Version 3.00 divisors must be a multiple of 2. */ + if (host->max_clk <= clock) + div = 1; + else { + for (div = 2; div < SDHCI_MAX_DIV_SPEC_300; + div += 2) { + if ((host->max_clk / div) <= clock) + break; + } + } + + real_div = div; + div >>= 1; + + if (real_div) + clock = (host->max_clk * clk_mul) / real_div; + host->mmc->actual_clock = clock; + + if ((clock > input_clock) && (clock > host->max_overclock)) { + pr_warn("%s: Overclocking to %dHz\n", + mmc_hostname(host->mmc), clock); + host->max_overclock = clock; + } + + clk |= (div & SDHCI_DIV_MASK) << SDHCI_DIVIDER_SHIFT; + clk |= ((div & SDHCI_DIV_HI_MASK) >> SDHCI_DIV_MASK_LEN) + << SDHCI_DIVIDER_HI_SHIFT; + clk |= SDHCI_CLOCK_INT_EN; + bcm2835_mmc_writew(host, clk, SDHCI_CLOCK_CONTROL); + + /* Wait max 20 ms */ + timeout = 20; + while (!((clk = bcm2835_mmc_readw(host, SDHCI_CLOCK_CONTROL)) + & SDHCI_CLOCK_INT_STABLE)) { + if (timeout == 0) { + pr_err("%s: Internal clock never " + "stabilised.\n", mmc_hostname(host->mmc)); + bcm2835_mmc_dumpregs(host); + return; + } + timeout--; + mdelay(1); + } + + if (20-timeout > 10 && 20-timeout > host->max_delay) { + host->max_delay = 20-timeout; + pr_warn("Warning: MMC controller hung for %d ms\n", host->max_delay); + } + + clk |= SDHCI_CLOCK_CARD_EN; + bcm2835_mmc_writew(host, clk, SDHCI_CLOCK_CONTROL); +} + +static void bcm2835_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq) +{ + struct bcm2835_host *host; + unsigned long flags; + + host = mmc_priv(mmc); + + spin_lock_irqsave(&host->lock, flags); + + WARN_ON(host->mrq != NULL); + + host->mrq = mrq; + + if (mrq->sbc && !(host->flags & SDHCI_AUTO_CMD23)) + bcm2835_mmc_send_command(host, mrq->sbc); + else + bcm2835_mmc_send_command(host, mrq->cmd); + + spin_unlock_irqrestore(&host->lock, flags); + + if (!(mrq->sbc && !(host->flags & SDHCI_AUTO_CMD23)) && mrq->cmd->data && host->use_dma) { + /* DMA transfer starts now, PIO starts after interrupt */ + bcm2835_mmc_transfer_dma(host); + } +} + + +static void bcm2835_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + + struct bcm2835_host *host = mmc_priv(mmc); + unsigned long flags; + u8 ctrl; + u16 clk, ctrl_2; + + pr_debug("bcm2835_mmc_set_ios: clock %d, pwr %d, bus_width %d, timing %d, vdd %d, drv_type %d\n", + ios->clock, ios->power_mode, ios->bus_width, + ios->timing, ios->signal_voltage, ios->drv_type); + + spin_lock_irqsave(&host->lock, flags); + + if (!ios->clock || ios->clock != host->clock) { + bcm2835_mmc_set_clock(host, ios->clock); + host->clock = ios->clock; + } + + if (host->pwr != SDHCI_POWER_330) { + host->pwr = SDHCI_POWER_330; + bcm2835_mmc_writeb(host, SDHCI_POWER_330 | SDHCI_POWER_ON, SDHCI_POWER_CONTROL); + } + + ctrl = bcm2835_mmc_readb(host, SDHCI_HOST_CONTROL); + + /* set bus width */ + ctrl &= ~SDHCI_CTRL_8BITBUS; + if (ios->bus_width == MMC_BUS_WIDTH_4) + ctrl |= SDHCI_CTRL_4BITBUS; + else + ctrl &= ~SDHCI_CTRL_4BITBUS; + + ctrl &= ~SDHCI_CTRL_HISPD; /* NO_HISPD_BIT */ + + + bcm2835_mmc_writeb(host, ctrl, SDHCI_HOST_CONTROL); + /* + * We only need to set Driver Strength if the + * preset value enable is not set. + */ + ctrl_2 = bcm2835_mmc_readw(host, SDHCI_HOST_CONTROL2); + ctrl_2 &= ~SDHCI_CTRL_DRV_TYPE_MASK; + if (ios->drv_type == MMC_SET_DRIVER_TYPE_A) + ctrl_2 |= SDHCI_CTRL_DRV_TYPE_A; + else if (ios->drv_type == MMC_SET_DRIVER_TYPE_C) + ctrl_2 |= SDHCI_CTRL_DRV_TYPE_C; + + bcm2835_mmc_writew(host, ctrl_2, SDHCI_HOST_CONTROL2); + + /* Reset SD Clock Enable */ + clk = bcm2835_mmc_readw(host, SDHCI_CLOCK_CONTROL); + clk &= ~SDHCI_CLOCK_CARD_EN; + bcm2835_mmc_writew(host, clk, SDHCI_CLOCK_CONTROL); + + /* Re-enable SD Clock */ + bcm2835_mmc_set_clock(host, host->clock); + bcm2835_mmc_writeb(host, ctrl, SDHCI_HOST_CONTROL); + + spin_unlock_irqrestore(&host->lock, flags); +} + + +static struct mmc_host_ops bcm2835_ops = { + .request = bcm2835_mmc_request, + .set_ios = bcm2835_mmc_set_ios, + .enable_sdio_irq = bcm2835_mmc_enable_sdio_irq, + .ack_sdio_irq = bcm2835_mmc_ack_sdio_irq, +}; + + +static void bcm2835_mmc_tasklet_finish(unsigned long param) +{ + struct bcm2835_host *host; + unsigned long flags; + struct mmc_request *mrq; + + host = (struct bcm2835_host *)param; + + spin_lock_irqsave(&host->lock, flags); + + /* + * If this tasklet gets rescheduled while running, it will + * be run again afterwards but without any active request. + */ + if (!host->mrq) { + spin_unlock_irqrestore(&host->lock, flags); + return; + } + + del_timer(&host->timer); + + mrq = host->mrq; + + /* + * The controller needs a reset of internal state machines + * upon error conditions. + */ + if (!(host->flags & SDHCI_DEVICE_DEAD) && + ((mrq->cmd && mrq->cmd->error) || + (mrq->data && (mrq->data->error || + (mrq->data->stop && mrq->data->stop->error))))) { + + spin_unlock_irqrestore(&host->lock, flags); + bcm2835_mmc_reset(host, SDHCI_RESET_CMD); + bcm2835_mmc_reset(host, SDHCI_RESET_DATA); + spin_lock_irqsave(&host->lock, flags); + } + + host->mrq = NULL; + host->cmd = NULL; + host->data = NULL; + + spin_unlock_irqrestore(&host->lock, flags); + mmc_request_done(host->mmc, mrq); +} + + + +static int bcm2835_mmc_add_host(struct bcm2835_host *host) +{ + struct mmc_host *mmc = host->mmc; + struct device *dev = mmc->parent; +#ifndef FORCE_PIO + struct dma_slave_config cfg; +#endif + int ret; + + bcm2835_mmc_reset(host, SDHCI_RESET_ALL); + + host->clk_mul = 0; + + if (!mmc->f_max || mmc->f_max > host->max_clk) + mmc->f_max = host->max_clk; + mmc->f_min = host->max_clk / SDHCI_MAX_DIV_SPEC_300; + + /* SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK */ + host->timeout_clk = mmc->f_max / 1000; + mmc->max_busy_timeout = (1 << 27) / host->timeout_clk; + + /* host controller capabilities */ + mmc->caps |= MMC_CAP_CMD23 | MMC_CAP_NEEDS_POLL | + MMC_CAP_SDIO_IRQ | MMC_CAP_SD_HIGHSPEED | + MMC_CAP_MMC_HIGHSPEED; + + mmc->caps2 |= MMC_CAP2_SDIO_IRQ_NOTHREAD; + + host->flags = SDHCI_AUTO_CMD23; + + dev_info(dev, "mmc_debug:%x mmc_debug2:%x\n", mmc_debug, mmc_debug2); +#ifdef FORCE_PIO + dev_info(dev, "Forcing PIO mode\n"); + host->have_dma = false; +#else + if (IS_ERR_OR_NULL(host->dma_chan_rxtx)) { + dev_err(dev, "%s: Unable to initialise DMA channel. Falling back to PIO\n", + DRIVER_NAME); + host->have_dma = false; + } else { + dev_info(dev, "DMA channel allocated"); + + cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + + /* Validate the slave configurations */ + + cfg.direction = DMA_MEM_TO_DEV; + cfg.src_addr = 0; + cfg.dst_addr = host->bus_addr + SDHCI_BUFFER; + + ret = dmaengine_slave_config(host->dma_chan_rxtx, &cfg); + + if (ret == 0) { + host->dma_cfg_tx = cfg; + + cfg.direction = DMA_DEV_TO_MEM; + cfg.src_addr = host->bus_addr + SDHCI_BUFFER; + cfg.dst_addr = 0; + + ret = dmaengine_slave_config(host->dma_chan_rxtx, &cfg); + } + + if (ret == 0) { + host->dma_cfg_rx = cfg; + + host->have_dma = true; + } else { + pr_err("%s: unable to configure DMA channel. " + "Falling back to PIO\n", + mmc_hostname(mmc)); + dma_release_channel(host->dma_chan_rxtx); + host->dma_chan_rxtx = NULL; + host->have_dma = false; + } + } +#endif + mmc->max_segs = 128; + mmc->max_req_size = min_t(size_t, 524288, dma_max_mapping_size(dev)); + mmc->max_seg_size = mmc->max_req_size; + mmc->max_blk_size = 512; + mmc->max_blk_count = 65535; + + /* report supported voltage ranges */ + mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; + + tasklet_init(&host->finish_tasklet, + bcm2835_mmc_tasklet_finish, (unsigned long)host); + + timer_setup(&host->timer, bcm2835_mmc_timeout_timer, 0); + init_waitqueue_head(&host->buf_ready_int); + + bcm2835_mmc_init(host, 0); + ret = request_irq(host->irq, bcm2835_mmc_irq, IRQF_SHARED, + mmc_hostname(mmc), host); + if (ret) { + dev_err(dev, "Failed to request IRQ %d: %d\n", host->irq, ret); + goto untasklet; + } + + ret = mmc_add_host(mmc); + if (ret) { + dev_err(dev, "could not add MMC host\n"); + goto free_irq; + } + + return 0; + +free_irq: + free_irq(host->irq, host); +untasklet: + tasklet_kill(&host->finish_tasklet); + + return ret; +} + +static int bcm2835_mmc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct clk *clk; + struct resource *iomem; + struct bcm2835_host *host; + struct mmc_host *mmc; + int ret; + + mmc = mmc_alloc_host(sizeof(*host), dev); + if (!mmc) + return -ENOMEM; + + mmc->ops = &bcm2835_ops; + host = mmc_priv(mmc); + host->mmc = mmc; + host->timeout = msecs_to_jiffies(1000); + spin_lock_init(&host->lock); + + host->ioaddr = devm_platform_get_and_ioremap_resource(pdev, 0, &iomem); + if (IS_ERR(host->ioaddr)) { + ret = PTR_ERR(host->ioaddr); + goto err; + } + + host->bus_addr = iomem->start; + +#ifndef FORCE_PIO + if (node) { + host->dma_chan_rxtx = dma_request_slave_channel(dev, "rx-tx"); + if (!host->dma_chan_rxtx) + host->dma_chan_rxtx = + dma_request_slave_channel(dev, "tx"); + if (!host->dma_chan_rxtx) + host->dma_chan_rxtx = + dma_request_slave_channel(dev, "rx"); + } else { + dma_cap_mask_t mask; + + dma_cap_zero(mask); + /* we don't care about the channel, any would work */ + dma_cap_set(DMA_SLAVE, mask); + host->dma_chan_rxtx = dma_request_channel(mask, NULL, NULL); + } +#endif + clk = devm_clk_get(dev, NULL); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + if (ret == -EPROBE_DEFER) + dev_info(dev, "could not get clk, deferring probe\n"); + else + dev_err(dev, "could not get clk\n"); + goto err; + } + + host->max_clk = clk_get_rate(clk); + + host->irq = platform_get_irq(pdev, 0); + if (host->irq <= 0) { + dev_err(dev, "get IRQ failed\n"); + ret = -EINVAL; + goto err; + } + + if (node) { + ret = mmc_of_parse(mmc); + if (ret) + goto err; + + /* Read any custom properties */ + of_property_read_u32(node, + "brcm,overclock-50", + &host->overclock_50); + } else { + mmc->caps |= MMC_CAP_4_BIT_DATA; + } + + ret = bcm2835_mmc_add_host(host); + if (ret) + goto err; + + platform_set_drvdata(pdev, host); + + return 0; +err: + if (host->dma_chan_rxtx) + dma_release_channel(host->dma_chan_rxtx); + mmc_free_host(mmc); + + return ret; +} + +static void bcm2835_mmc_remove(struct platform_device *pdev) +{ + struct bcm2835_host *host = platform_get_drvdata(pdev); + unsigned long flags; + int dead; + u32 scratch; + + dead = 0; + scratch = bcm2835_mmc_readl(host, SDHCI_INT_STATUS); + if (scratch == (u32)-1) + dead = 1; + + + if (dead) { + spin_lock_irqsave(&host->lock, flags); + + host->flags |= SDHCI_DEVICE_DEAD; + + if (host->mrq) { + pr_err("%s: Controller removed during " + " transfer!\n", mmc_hostname(host->mmc)); + + host->mrq->cmd->error = -ENOMEDIUM; + tasklet_schedule(&host->finish_tasklet); + } + + spin_unlock_irqrestore(&host->lock, flags); + } + + mmc_remove_host(host->mmc); + + if (!dead) + bcm2835_mmc_reset(host, SDHCI_RESET_ALL); + + free_irq(host->irq, host); + + del_timer_sync(&host->timer); + + tasklet_kill(&host->finish_tasklet); + + if (host->dma_chan_rxtx) + dma_release_channel(host->dma_chan_rxtx); + + mmc_free_host(host->mmc); +} + + +static const struct of_device_id bcm2835_mmc_match[] = { + { .compatible = "brcm,bcm2835-mmc" }, + { } +}; +MODULE_DEVICE_TABLE(of, bcm2835_mmc_match); + + + +static struct platform_driver bcm2835_mmc_driver = { + .probe = bcm2835_mmc_probe, + .remove = bcm2835_mmc_remove, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = bcm2835_mmc_match, + }, +}; +module_platform_driver(bcm2835_mmc_driver); + +module_param(mmc_debug, uint, 0644); +module_param(mmc_debug2, uint, 0644); +MODULE_ALIAS("platform:mmc-bcm2835"); +MODULE_DESCRIPTION("BCM2835 SDHCI driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Gellert Weisz"); diff --git a/drivers/mmc/host/bcm2835-sdhost.c b/drivers/mmc/host/bcm2835-sdhost.c new file mode 100644 index 00000000000000..f47d78f9668595 --- /dev/null +++ b/drivers/mmc/host/bcm2835-sdhost.c @@ -0,0 +1,2219 @@ +/* + * BCM2835 SD host driver. + * + * Author: Phil Elwell <phil@raspberrypi.org> + * Copyright (C) 2015-2016 Raspberry Pi (Trading) Ltd. + * + * Based on + * mmc-bcm2835.c by Gellert Weisz + * which is, in turn, based on + * sdhci-bcm2708.c by Broadcom + * sdhci-bcm2835.c by Stephen Warren and Oleksandr Tymoshenko + * sdhci.c and sdhci-pci.c by Pierre Ossman + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#define FIFO_READ_THRESHOLD 4 +#define FIFO_WRITE_THRESHOLD 4 +#define ALLOW_CMD23_READ 1 +#define ALLOW_CMD23_WRITE 0 +#define ENABLE_LOG 1 +#define SDDATA_FIFO_PIO_BURST 8 +#define CMD_DALLY_US 1 + +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/mmc/mmc.h> +#include <linux/mmc/host.h> +#include <linux/mmc/sd.h> +#include <linux/mmc/sdio.h> +#include <linux/scatterlist.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/blkdev.h> +#include <linux/dmaengine.h> +#include <linux/dma-mapping.h> +#include <linux/of_dma.h> +#include <linux/time.h> +#include <linux/workqueue.h> +#include <linux/interrupt.h> +#include <linux/highmem.h> +#include <soc/bcm2835/raspberrypi-firmware.h> + +/* For mmc_card_blockaddr */ +#include "../core/card.h" +#include "mmc_hsq.h" + +#define DRIVER_NAME "sdhost-bcm2835" + +#define SDCMD 0x00 /* Command to SD card - 16 R/W */ +#define SDARG 0x04 /* Argument to SD card - 32 R/W */ +#define SDTOUT 0x08 /* Start value for timeout counter - 32 R/W */ +#define SDCDIV 0x0c /* Start value for clock divider - 11 R/W */ +#define SDRSP0 0x10 /* SD card response (31:0) - 32 R */ +#define SDRSP1 0x14 /* SD card response (63:32) - 32 R */ +#define SDRSP2 0x18 /* SD card response (95:64) - 32 R */ +#define SDRSP3 0x1c /* SD card response (127:96) - 32 R */ +#define SDHSTS 0x20 /* SD host status - 11 R */ +#define SDVDD 0x30 /* SD card power control - 1 R/W */ +#define SDEDM 0x34 /* Emergency Debug Mode - 13 R/W */ +#define SDHCFG 0x38 /* Host configuration - 2 R/W */ +#define SDHBCT 0x3c /* Host byte count (debug) - 32 R/W */ +#define SDDATA 0x40 /* Data to/from SD card - 32 R/W */ +#define SDHBLC 0x50 /* Host block count (SDIO/SDHC) - 9 R/W */ + +#define SDCMD_NEW_FLAG 0x8000 +#define SDCMD_FAIL_FLAG 0x4000 +#define SDCMD_BUSYWAIT 0x800 +#define SDCMD_NO_RESPONSE 0x400 +#define SDCMD_LONG_RESPONSE 0x200 +#define SDCMD_WRITE_CMD 0x80 +#define SDCMD_READ_CMD 0x40 +#define SDCMD_CMD_MASK 0x3f + +#define SDCDIV_MAX_CDIV 0x7ff + +#define SDHSTS_BUSY_IRPT 0x400 +#define SDHSTS_BLOCK_IRPT 0x200 +#define SDHSTS_SDIO_IRPT 0x100 +#define SDHSTS_REW_TIME_OUT 0x80 +#define SDHSTS_CMD_TIME_OUT 0x40 +#define SDHSTS_CRC16_ERROR 0x20 +#define SDHSTS_CRC7_ERROR 0x10 +#define SDHSTS_FIFO_ERROR 0x08 +/* Reserved */ +/* Reserved */ +#define SDHSTS_DATA_FLAG 0x01 + +#define SDHSTS_TRANSFER_ERROR_MASK (SDHSTS_CRC7_ERROR|SDHSTS_CRC16_ERROR|SDHSTS_REW_TIME_OUT|SDHSTS_FIFO_ERROR) +#define SDHSTS_ERROR_MASK (SDHSTS_CMD_TIME_OUT|SDHSTS_TRANSFER_ERROR_MASK) + +#define SDHCFG_BUSY_IRPT_EN (1<<10) +#define SDHCFG_BLOCK_IRPT_EN (1<<8) +#define SDHCFG_SDIO_IRPT_EN (1<<5) +#define SDHCFG_DATA_IRPT_EN (1<<4) +#define SDHCFG_SLOW_CARD (1<<3) +#define SDHCFG_WIDE_EXT_BUS (1<<2) +#define SDHCFG_WIDE_INT_BUS (1<<1) +#define SDHCFG_REL_CMD_LINE (1<<0) + +#define SDEDM_FORCE_DATA_MODE (1<<19) +#define SDEDM_CLOCK_PULSE (1<<20) +#define SDEDM_BYPASS (1<<21) + +#define SDEDM_WRITE_THRESHOLD_SHIFT 9 +#define SDEDM_READ_THRESHOLD_SHIFT 14 +#define SDEDM_THRESHOLD_MASK 0x1f + +#define SDEDM_FSM_MASK 0xf +#define SDEDM_FSM_IDENTMODE 0x0 +#define SDEDM_FSM_DATAMODE 0x1 +#define SDEDM_FSM_READDATA 0x2 +#define SDEDM_FSM_WRITEDATA 0x3 +#define SDEDM_FSM_READWAIT 0x4 +#define SDEDM_FSM_READCRC 0x5 +#define SDEDM_FSM_WRITECRC 0x6 +#define SDEDM_FSM_WRITEWAIT1 0x7 +#define SDEDM_FSM_POWERDOWN 0x8 +#define SDEDM_FSM_POWERUP 0x9 +#define SDEDM_FSM_WRITESTART1 0xa +#define SDEDM_FSM_WRITESTART2 0xb +#define SDEDM_FSM_GENPULSES 0xc +#define SDEDM_FSM_WRITEWAIT2 0xd +#define SDEDM_FSM_STARTPOWDOWN 0xf + +#define SDDATA_FIFO_WORDS 16 + +#define USE_CMD23_FLAGS ((ALLOW_CMD23_READ * MMC_DATA_READ) | \ + (ALLOW_CMD23_WRITE * MMC_DATA_WRITE)) + +#define MHZ 1000000 + + +struct bcm2835_host { + spinlock_t lock; + + struct rpi_firmware *fw; + + void __iomem *ioaddr; + phys_addr_t bus_addr; + + struct mmc_host *mmc; + + u32 pio_timeout; /* In jiffies */ + + int clock; /* Current clock speed */ + + bool slow_card; /* Force 11-bit divisor */ + + unsigned int max_clk; /* Max possible freq */ + + struct tasklet_struct finish_tasklet; /* Tasklet structures */ + + struct work_struct cmd_wait_wq; /* Workqueue function */ + + struct timer_list timer; /* Timer for timeouts */ + + struct sg_mapping_iter sg_miter; /* SG state for PIO */ + unsigned int blocks; /* remaining PIO blocks */ + + int irq; /* Device IRQ */ + + u32 cmd_quick_poll_retries; + u32 ns_per_fifo_word; + + /* cached registers */ + u32 hcfg; + u32 cdiv; + + struct mmc_request *mrq; /* Current request */ + struct mmc_command *cmd; /* Current command */ + struct mmc_data *data; /* Current data request */ + unsigned int data_complete:1; /* Data finished before cmd */ + + unsigned int flush_fifo:1; /* Drain the fifo when finishing */ + + unsigned int use_busy:1; /* Wait for busy interrupt */ + + unsigned int use_sbc:1; /* Send CMD23 */ + + unsigned int debug:1; /* Enable debug output */ + unsigned int firmware_sets_cdiv:1; /* Let the firmware manage the clock */ + unsigned int reset_clock:1; /* Reset the clock fore the next request */ + + /*DMA part*/ + struct dma_chan *dma_chan_rxtx; /* DMA channel for reads and writes */ + struct dma_chan *dma_chan; /* Channel in use */ + struct dma_slave_config dma_cfg_rx; + struct dma_slave_config dma_cfg_tx; + struct dma_async_tx_descriptor *dma_desc; + u32 dma_dir; + u32 drain_words; + struct page *drain_page; + u32 drain_offset; + + bool allow_dma; + bool use_dma; + /*end of DMA part*/ + + int max_delay; /* maximum length of time spent waiting */ + struct timespec64 stop_time; /* when the last stop was issued */ + u32 delay_after_stop; /* minimum time between stop and subsequent data transfer */ + u32 delay_after_this_stop; /* minimum time between this stop and subsequent data transfer */ + u32 user_overclock_50; /* User's preferred frequency to use when 50MHz is requested (in MHz) */ + u32 overclock_50; /* frequency to use when 50MHz is requested (in MHz) */ + u32 overclock; /* Current frequency if overclocked, else zero */ + u32 pio_limit; /* Maximum block count for PIO (0 = always DMA) */ + + u32 sectors; /* Cached card size in sectors */ +}; + +#if ENABLE_LOG + +struct log_entry_struct { + char event[4]; + u32 timestamp; + u32 param1; + u32 param2; +}; + +typedef struct log_entry_struct LOG_ENTRY_T; + +LOG_ENTRY_T *sdhost_log_buf; +dma_addr_t sdhost_log_addr; +static u32 sdhost_log_idx; +static spinlock_t log_lock; +static void __iomem *timer_base; + +#define LOG_ENTRIES (256*1) +#define LOG_SIZE (sizeof(LOG_ENTRY_T)*LOG_ENTRIES) + +static void log_init(struct device *dev) +{ + struct device_node *np; + + spin_lock_init(&log_lock); + + np = of_find_compatible_node(NULL, NULL, + "brcm,bcm2835-system-timer"); + timer_base = of_iomap(np, 0); + + if (timer_base) { + sdhost_log_buf = dma_alloc_coherent(dev, LOG_SIZE, &sdhost_log_addr, + GFP_KERNEL); + if (sdhost_log_buf) + pr_info("sdhost: log_buf @ %p (%llx)\n", + sdhost_log_buf, (u64)sdhost_log_addr); + else + pr_err("sdhost: failed to allocate log buf\n"); + } else { + pr_err("sdhost: failed to remap timer - wrong dtb?\n"); + } +} + +static void log_event_impl(const char *event, u32 param1, u32 param2) +{ + if (sdhost_log_buf) { + LOG_ENTRY_T *entry; + unsigned long flags; + + spin_lock_irqsave(&log_lock, flags); + + entry = sdhost_log_buf + sdhost_log_idx; + memcpy(entry->event, event, 4); + entry->timestamp = (readl(timer_base + 4) & 0x3fffffff) + + (smp_processor_id()<<30); + entry->param1 = param1; + entry->param2 = param2; + sdhost_log_idx = (sdhost_log_idx + 1) % LOG_ENTRIES; + + spin_unlock_irqrestore(&log_lock, flags); + } +} + +static void log_dump(void) +{ + if (sdhost_log_buf) { + LOG_ENTRY_T *entry; + unsigned long flags; + int idx; + + spin_lock_irqsave(&log_lock, flags); + + idx = sdhost_log_idx; + do { + entry = sdhost_log_buf + idx; + if (entry->event[0] != '\0') + pr_info("[%08x] %.4s %x %x\n", + entry->timestamp, + entry->event, + entry->param1, + entry->param2); + idx = (idx + 1) % LOG_ENTRIES; + } while (idx != sdhost_log_idx); + + spin_unlock_irqrestore(&log_lock, flags); + } +} + +#define log_event(event, param1, param2) log_event_impl(event, (u32)(uintptr_t)param1, (u32)(uintptr_t)param2) + +#else + +#define log_init(x) (void)0 +#define log_event(event, param1, param2) (void)0 +#define log_dump() (void)0 + +#endif + +static inline void bcm2835_sdhost_write(struct bcm2835_host *host, u32 val, int reg) +{ + writel(val, host->ioaddr + reg); +} + +static inline u32 bcm2835_sdhost_read(struct bcm2835_host *host, int reg) +{ + return readl(host->ioaddr + reg); +} + +static inline u32 bcm2835_sdhost_read_relaxed(struct bcm2835_host *host, int reg) +{ + return readl_relaxed(host->ioaddr + reg); +} + +static void bcm2835_sdhost_dumpcmd(struct bcm2835_host *host, + struct mmc_command *cmd, + const char *label) +{ + if (cmd) + pr_info("%s:%c%s op %d arg 0x%x flags 0x%x - resp %08x %08x %08x %08x, err %d\n", + mmc_hostname(host->mmc), + (cmd == host->cmd) ? '>' : ' ', + label, cmd->opcode, cmd->arg, cmd->flags, + cmd->resp[0], cmd->resp[1], cmd->resp[2], cmd->resp[3], + cmd->error); +} + +static void bcm2835_sdhost_dumpregs(struct bcm2835_host *host) +{ + if (host->mrq) + { + bcm2835_sdhost_dumpcmd(host, host->mrq->sbc, "sbc"); + bcm2835_sdhost_dumpcmd(host, host->mrq->cmd, "cmd"); + if (host->mrq->data) + pr_info("%s: data blocks %x blksz %x - err %d\n", + mmc_hostname(host->mmc), + host->mrq->data->blocks, + host->mrq->data->blksz, + host->mrq->data->error); + bcm2835_sdhost_dumpcmd(host, host->mrq->stop, "stop"); + } + + pr_info("%s: =========== REGISTER DUMP ===========\n", + mmc_hostname(host->mmc)); + + pr_info("%s: SDCMD 0x%08x\n", + mmc_hostname(host->mmc), + bcm2835_sdhost_read(host, SDCMD)); + pr_info("%s: SDARG 0x%08x\n", + mmc_hostname(host->mmc), + bcm2835_sdhost_read(host, SDARG)); + pr_info("%s: SDTOUT 0x%08x\n", + mmc_hostname(host->mmc), + bcm2835_sdhost_read(host, SDTOUT)); + pr_info("%s: SDCDIV 0x%08x\n", + mmc_hostname(host->mmc), + bcm2835_sdhost_read(host, SDCDIV)); + pr_info("%s: SDRSP0 0x%08x\n", + mmc_hostname(host->mmc), + bcm2835_sdhost_read(host, SDRSP0)); + pr_info("%s: SDRSP1 0x%08x\n", + mmc_hostname(host->mmc), + bcm2835_sdhost_read(host, SDRSP1)); + pr_info("%s: SDRSP2 0x%08x\n", + mmc_hostname(host->mmc), + bcm2835_sdhost_read(host, SDRSP2)); + pr_info("%s: SDRSP3 0x%08x\n", + mmc_hostname(host->mmc), + bcm2835_sdhost_read(host, SDRSP3)); + pr_info("%s: SDHSTS 0x%08x\n", + mmc_hostname(host->mmc), + bcm2835_sdhost_read(host, SDHSTS)); + pr_info("%s: SDVDD 0x%08x\n", + mmc_hostname(host->mmc), + bcm2835_sdhost_read(host, SDVDD)); + pr_info("%s: SDEDM 0x%08x\n", + mmc_hostname(host->mmc), + bcm2835_sdhost_read(host, SDEDM)); + pr_info("%s: SDHCFG 0x%08x\n", + mmc_hostname(host->mmc), + bcm2835_sdhost_read(host, SDHCFG)); + pr_info("%s: SDHBCT 0x%08x\n", + mmc_hostname(host->mmc), + bcm2835_sdhost_read(host, SDHBCT)); + pr_info("%s: SDHBLC 0x%08x\n", + mmc_hostname(host->mmc), + bcm2835_sdhost_read(host, SDHBLC)); + + pr_info("%s: ===========================================\n", + mmc_hostname(host->mmc)); +} + +static void bcm2835_sdhost_set_power(struct bcm2835_host *host, bool on) +{ + bcm2835_sdhost_write(host, on ? 1 : 0, SDVDD); +} + +static void bcm2835_sdhost_reset_internal(struct bcm2835_host *host) +{ + u32 temp; + + if (host->debug) + pr_info("%s: reset\n", mmc_hostname(host->mmc)); + + bcm2835_sdhost_set_power(host, false); + + bcm2835_sdhost_write(host, 0, SDCMD); + bcm2835_sdhost_write(host, 0, SDARG); + bcm2835_sdhost_write(host, 0xf00000, SDTOUT); + bcm2835_sdhost_write(host, 0, SDCDIV); + bcm2835_sdhost_write(host, 0x7f8, SDHSTS); /* Write 1s to clear */ + bcm2835_sdhost_write(host, 0, SDHCFG); + bcm2835_sdhost_write(host, 0, SDHBCT); + bcm2835_sdhost_write(host, 0, SDHBLC); + + /* Limit fifo usage due to silicon bug */ + temp = bcm2835_sdhost_read(host, SDEDM); + temp &= ~((SDEDM_THRESHOLD_MASK<<SDEDM_READ_THRESHOLD_SHIFT) | + (SDEDM_THRESHOLD_MASK<<SDEDM_WRITE_THRESHOLD_SHIFT)); + temp |= (FIFO_READ_THRESHOLD << SDEDM_READ_THRESHOLD_SHIFT) | + (FIFO_WRITE_THRESHOLD << SDEDM_WRITE_THRESHOLD_SHIFT); + bcm2835_sdhost_write(host, temp, SDEDM); + mdelay(10); + bcm2835_sdhost_set_power(host, true); + mdelay(10); + host->clock = 0; + host->sectors = 0; + bcm2835_sdhost_write(host, host->hcfg, SDHCFG); + bcm2835_sdhost_write(host, SDCDIV_MAX_CDIV, SDCDIV); +} + +#if 0 // todo fix +static void bcm2835_sdhost_reset(struct mmc_host *mmc) +{ + struct bcm2835_host *host = mmc_priv(mmc); + unsigned long flags; + spin_lock_irqsave(&host->lock, flags); + log_event("RST<", 0, 0); + + bcm2835_sdhost_reset_internal(host); + + spin_unlock_irqrestore(&host->lock, flags); +} +#endif + +static void bcm2835_sdhost_set_ios(struct mmc_host *mmc, struct mmc_ios *ios); + +static void bcm2835_sdhost_init(struct bcm2835_host *host, int soft) +{ + pr_debug("bcm2835_sdhost_init(%d)\n", soft); + + /* Set interrupt enables */ + host->hcfg = SDHCFG_BUSY_IRPT_EN; + + bcm2835_sdhost_reset_internal(host); + + if (soft) { + /* force clock reconfiguration */ + host->clock = 0; + bcm2835_sdhost_set_ios(host->mmc, &host->mmc->ios); + } +} + +static void bcm2835_sdhost_wait_transfer_complete(struct bcm2835_host *host) +{ + int timediff; + u32 alternate_idle; + u32 edm; + + alternate_idle = (host->mrq->data->flags & MMC_DATA_READ) ? + SDEDM_FSM_READWAIT : SDEDM_FSM_WRITESTART1; + + edm = bcm2835_sdhost_read(host, SDEDM); + + log_event("WTC<", edm, 0); + + timediff = 0; + + while (1) { + u32 fsm = edm & SDEDM_FSM_MASK; + if ((fsm == SDEDM_FSM_IDENTMODE) || + (fsm == SDEDM_FSM_DATAMODE)) + break; + if (fsm == alternate_idle) { + bcm2835_sdhost_write(host, + edm | SDEDM_FORCE_DATA_MODE, + SDEDM); + break; + } + + timediff++; + if (timediff == 100000) { + pr_err("%s: wait_transfer_complete - still waiting after %d retries\n", + mmc_hostname(host->mmc), + timediff); + log_dump(); + bcm2835_sdhost_dumpregs(host); + host->mrq->data->error = -ETIMEDOUT; + log_event("WTC!", edm, 0); + return; + } + cpu_relax(); + edm = bcm2835_sdhost_read(host, SDEDM); + } + log_event("WTC>", edm, 0); +} + +static void bcm2835_sdhost_finish_data(struct bcm2835_host *host); + +static void bcm2835_sdhost_dma_complete(void *param) +{ + struct bcm2835_host *host = param; + struct mmc_data *data = host->data; + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + log_event("DMA<", host->data, bcm2835_sdhost_read(host, SDHSTS)); + log_event("DMA ", bcm2835_sdhost_read(host, SDCMD), + bcm2835_sdhost_read(host, SDEDM)); + + if (host->dma_chan) { + dma_unmap_sg(host->dma_chan->device->dev, + data->sg, data->sg_len, + host->dma_dir); + + host->dma_chan = NULL; + } + + if (host->drain_words) { + void *page; + u32 *buf; + + if (host->drain_offset & PAGE_MASK) { + host->drain_page += host->drain_offset >> PAGE_SHIFT; + host->drain_offset &= ~PAGE_MASK; + } + + page = kmap_atomic(host->drain_page); + buf = page + host->drain_offset; + + while (host->drain_words) { + u32 edm = bcm2835_sdhost_read(host, SDEDM); + if ((edm >> 4) & 0x1f) + *(buf++) = bcm2835_sdhost_read(host, + SDDATA); + host->drain_words--; + } + + kunmap_atomic(page); + } + + bcm2835_sdhost_finish_data(host); + + log_event("DMA>", host->data, 0); + spin_unlock_irqrestore(&host->lock, flags); +} + +static void bcm2835_sdhost_read_block_pio(struct bcm2835_host *host) +{ + unsigned long flags; + size_t blksize, len; + u32 *buf; + unsigned long wait_max; + + blksize = host->data->blksz; + + wait_max = jiffies + msecs_to_jiffies(host->pio_timeout); + + local_irq_save(flags); + + while (blksize) { + int copy_words; + u32 hsts = 0; + + if (!sg_miter_next(&host->sg_miter)) { + host->data->error = -EINVAL; + break; + } + + len = min(host->sg_miter.length, blksize); + if (len % 4) { + host->data->error = -EINVAL; + break; + } + + blksize -= len; + host->sg_miter.consumed = len; + + buf = (u32 *)host->sg_miter.addr; + + copy_words = len/4; + + while (copy_words) { + int burst_words, words; + u32 edm; + + burst_words = SDDATA_FIFO_PIO_BURST; + if (burst_words > copy_words) + burst_words = copy_words; + edm = bcm2835_sdhost_read(host, SDEDM); + words = ((edm >> 4) & 0x1f); + + if (words < burst_words) { + int fsm_state = (edm & SDEDM_FSM_MASK); + if ((fsm_state != SDEDM_FSM_READDATA) && + (fsm_state != SDEDM_FSM_READWAIT) && + (fsm_state != SDEDM_FSM_READCRC)) { + hsts = bcm2835_sdhost_read(host, + SDHSTS); + pr_info("%s: fsm %x, hsts %x\n", + mmc_hostname(host->mmc), + fsm_state, hsts); + if (hsts & SDHSTS_ERROR_MASK) + break; + } + + if (time_after(jiffies, wait_max)) { + pr_err("%s: PIO read timeout - EDM %x\n", + mmc_hostname(host->mmc), + edm); + hsts = SDHSTS_REW_TIME_OUT; + break; + } + ndelay((burst_words - words) * + host->ns_per_fifo_word); + continue; + } else if (words > copy_words) { + words = copy_words; + } + + copy_words -= words; + + while (words) { + *(buf++) = bcm2835_sdhost_read(host, SDDATA); + words--; + } + } + + if (hsts & SDHSTS_ERROR_MASK) + break; + } + + sg_miter_stop(&host->sg_miter); + + local_irq_restore(flags); +} + +static void bcm2835_sdhost_write_block_pio(struct bcm2835_host *host) +{ + unsigned long flags; + size_t blksize, len; + u32 *buf; + unsigned long wait_max; + + blksize = host->data->blksz; + + wait_max = jiffies + msecs_to_jiffies(host->pio_timeout); + + local_irq_save(flags); + + while (blksize) { + int copy_words; + u32 hsts = 0; + + if (!sg_miter_next(&host->sg_miter)) { + host->data->error = -EINVAL; + break; + } + + len = min(host->sg_miter.length, blksize); + if (len % 4) { + host->data->error = -EINVAL; + break; + } + + blksize -= len; + host->sg_miter.consumed = len; + + buf = (u32 *)host->sg_miter.addr; + + copy_words = len/4; + + while (copy_words) { + int burst_words, words; + u32 edm; + + burst_words = SDDATA_FIFO_PIO_BURST; + if (burst_words > copy_words) + burst_words = copy_words; + edm = bcm2835_sdhost_read(host, SDEDM); + words = SDDATA_FIFO_WORDS - ((edm >> 4) & 0x1f); + + if (words < burst_words) { + int fsm_state = (edm & SDEDM_FSM_MASK); + if ((fsm_state != SDEDM_FSM_WRITEDATA) && + (fsm_state != SDEDM_FSM_WRITESTART1) && + (fsm_state != SDEDM_FSM_WRITESTART2)) { + hsts = bcm2835_sdhost_read(host, + SDHSTS); + pr_info("%s: fsm %x, hsts %x\n", + mmc_hostname(host->mmc), + fsm_state, hsts); + if (hsts & SDHSTS_ERROR_MASK) + break; + } + + if (time_after(jiffies, wait_max)) { + pr_err("%s: PIO write timeout - EDM %x\n", + mmc_hostname(host->mmc), + edm); + hsts = SDHSTS_REW_TIME_OUT; + break; + } + ndelay((burst_words - words) * + host->ns_per_fifo_word); + continue; + } else if (words > copy_words) { + words = copy_words; + } + + copy_words -= words; + + while (words) { + bcm2835_sdhost_write(host, *(buf++), SDDATA); + words--; + } + } + + if (hsts & SDHSTS_ERROR_MASK) + break; + } + + sg_miter_stop(&host->sg_miter); + + local_irq_restore(flags); +} + +static void bcm2835_sdhost_transfer_pio(struct bcm2835_host *host) +{ + u32 sdhsts; + bool is_read; + BUG_ON(!host->data); + log_event("XFP<", host->data, host->blocks); + + is_read = (host->data->flags & MMC_DATA_READ) != 0; + if (is_read) + bcm2835_sdhost_read_block_pio(host); + else + bcm2835_sdhost_write_block_pio(host); + + sdhsts = bcm2835_sdhost_read(host, SDHSTS); + if (sdhsts & (SDHSTS_CRC16_ERROR | + SDHSTS_CRC7_ERROR | + SDHSTS_FIFO_ERROR)) { + pr_err("%s: %s transfer error - HSTS %x\n", + mmc_hostname(host->mmc), + is_read ? "read" : "write", + sdhsts); + host->data->error = -EILSEQ; + } else if ((sdhsts & (SDHSTS_CMD_TIME_OUT | + SDHSTS_REW_TIME_OUT))) { + pr_err("%s: %s timeout error - HSTS %x\n", + mmc_hostname(host->mmc), + is_read ? "read" : "write", + sdhsts); + host->data->error = -ETIMEDOUT; + } + log_event("XFP>", host->data, host->blocks); +} + +static void bcm2835_sdhost_prepare_dma(struct bcm2835_host *host, + struct mmc_data *data) +{ + int len, dir_data, dir_slave; + struct dma_async_tx_descriptor *desc = NULL; + struct dma_chan *dma_chan; + + log_event("PRD<", data, 0); + pr_debug("bcm2835_sdhost_prepare_dma()\n"); + + dma_chan = host->dma_chan_rxtx; + if (data->flags & MMC_DATA_READ) { + dir_data = DMA_FROM_DEVICE; + dir_slave = DMA_DEV_TO_MEM; + } else { + dir_data = DMA_TO_DEVICE; + dir_slave = DMA_MEM_TO_DEV; + } + log_event("PRD1", dma_chan, 0); + + BUG_ON(!dma_chan->device); + BUG_ON(!dma_chan->device->dev); + BUG_ON(!data->sg); + + /* The block doesn't manage the FIFO DREQs properly for multi-block + transfers, so don't attempt to DMA the final few words. + Unfortunately this requires the final sg entry to be trimmed. + N.B. This code demands that the overspill is contained in + a single sg entry. + */ + + host->drain_words = 0; + if ((data->blocks > 1) && (dir_data == DMA_FROM_DEVICE)) { + struct scatterlist *sg; + u32 len; + int i; + + len = min((u32)(FIFO_READ_THRESHOLD - 1) * 4, + (u32)data->blocks * data->blksz); + + for_each_sg(data->sg, sg, data->sg_len, i) { + if (sg_is_last(sg)) { + BUG_ON(sg->length < len); + sg->length -= len; + host->drain_page = sg_page(sg); + host->drain_offset = sg->offset + sg->length; + } + } + host->drain_words = len/4; + } + + /* The parameters have already been validated, so this will not fail */ + (void)dmaengine_slave_config(dma_chan, + (dir_data == DMA_FROM_DEVICE) ? + &host->dma_cfg_rx : + &host->dma_cfg_tx); + + len = dma_map_sg(dma_chan->device->dev, data->sg, data->sg_len, + dir_data); + + log_event("PRD2", len, 0); + if (len > 0) + desc = dmaengine_prep_slave_sg(dma_chan, data->sg, + len, dir_slave, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + log_event("PRD3", desc, 0); + + if (desc) { + desc->callback = bcm2835_sdhost_dma_complete; + desc->callback_param = host; + host->dma_desc = desc; + host->dma_chan = dma_chan; + host->dma_dir = dir_data; + } + log_event("PDM>", data, 0); +} + +static void bcm2835_sdhost_start_dma(struct bcm2835_host *host) +{ + log_event("SDMA", host->data, host->dma_chan); + dmaengine_submit(host->dma_desc); + dma_async_issue_pending(host->dma_chan); +} + +static void bcm2835_sdhost_set_transfer_irqs(struct bcm2835_host *host) +{ + u32 all_irqs = SDHCFG_DATA_IRPT_EN | SDHCFG_BLOCK_IRPT_EN | + SDHCFG_BUSY_IRPT_EN; + if (host->dma_desc) + host->hcfg = (host->hcfg & ~all_irqs) | + SDHCFG_BUSY_IRPT_EN; + else + host->hcfg = (host->hcfg & ~all_irqs) | + SDHCFG_DATA_IRPT_EN | + SDHCFG_BUSY_IRPT_EN; + + bcm2835_sdhost_write(host, host->hcfg, SDHCFG); +} + +static void bcm2835_sdhost_prepare_data(struct bcm2835_host *host, struct mmc_command *cmd) +{ + struct mmc_data *data = cmd->data; + + WARN_ON(host->data); + + host->data = data; + if (!data) + return; + + /* Sanity checks */ + BUG_ON(data->blksz * data->blocks > 524288); + BUG_ON(data->blksz > host->mmc->max_blk_size); + BUG_ON(data->blocks > 65535); + + host->data_complete = 0; + host->flush_fifo = 0; + host->data->bytes_xfered = 0; + + if (!host->sectors && host->mmc->card) { + struct mmc_card *card = host->mmc->card; + if (!mmc_card_sd(card) && mmc_card_blockaddr(card)) { + /* + * The EXT_CSD sector count is in number of 512 byte + * sectors. + */ + host->sectors = card->ext_csd.sectors; + } else { + /* + * The CSD capacity field is in units of read_blkbits. + * set_capacity takes units of 512 bytes. + */ + host->sectors = card->csd.capacity << + (card->csd.read_blkbits - 9); + } + } + + if (!host->dma_desc) { + /* Use PIO */ + int flags = SG_MITER_ATOMIC; + + if (data->flags & MMC_DATA_READ) + flags |= SG_MITER_TO_SG; + else + flags |= SG_MITER_FROM_SG; + sg_miter_start(&host->sg_miter, data->sg, data->sg_len, flags); + host->blocks = data->blocks; + } + + bcm2835_sdhost_set_transfer_irqs(host); + + bcm2835_sdhost_write(host, data->blksz, SDHBCT); + bcm2835_sdhost_write(host, data->blocks, SDHBLC); + + BUG_ON(!host->data); +} + +static bool bcm2835_sdhost_send_command(struct bcm2835_host *host, + struct mmc_command *cmd) +{ + u32 sdcmd, sdhsts; + unsigned long timeout; + int delay; + + WARN_ON(host->cmd); + log_event("CMD<", cmd->opcode, cmd->arg); + + if (cmd->data) + pr_debug("%s: send_command %d 0x%x " + "(flags 0x%x) - %s %d*%d\n", + mmc_hostname(host->mmc), + cmd->opcode, cmd->arg, cmd->flags, + (cmd->data->flags & MMC_DATA_READ) ? + "read" : "write", cmd->data->blocks, + cmd->data->blksz); + else + pr_debug("%s: send_command %d 0x%x (flags 0x%x)\n", + mmc_hostname(host->mmc), + cmd->opcode, cmd->arg, cmd->flags); + + /* Wait max 100 ms */ + timeout = 10000; + + while (bcm2835_sdhost_read(host, SDCMD) & SDCMD_NEW_FLAG) { + if (timeout == 0) { + pr_warn("%s: previous command never completed.\n", + mmc_hostname(host->mmc)); + if (host->debug) + bcm2835_sdhost_dumpregs(host); + cmd->error = -EILSEQ; + tasklet_schedule(&host->finish_tasklet); + return false; + } + timeout--; + udelay(10); + } + + delay = (10000 - timeout)/100; + if (delay > host->max_delay) { + host->max_delay = delay; + pr_warn("%s: controller hung for %d ms\n", + mmc_hostname(host->mmc), + host->max_delay); + } + + timeout = jiffies; + if (!cmd->data && cmd->busy_timeout > 9000) + timeout += DIV_ROUND_UP(cmd->busy_timeout, 1000) * HZ + HZ; + else + timeout += 10 * HZ; + mod_timer(&host->timer, timeout); + + host->cmd = cmd; + + /* Clear any error flags */ + sdhsts = bcm2835_sdhost_read(host, SDHSTS); + if (sdhsts & SDHSTS_ERROR_MASK) + bcm2835_sdhost_write(host, sdhsts, SDHSTS); + + if ((cmd->flags & MMC_RSP_136) && (cmd->flags & MMC_RSP_BUSY)) { + pr_err("%s: unsupported response type!\n", + mmc_hostname(host->mmc)); + cmd->error = -EINVAL; + tasklet_schedule(&host->finish_tasklet); + return false; + } + + bcm2835_sdhost_prepare_data(host, cmd); + + bcm2835_sdhost_write(host, cmd->arg, SDARG); + + sdcmd = cmd->opcode & SDCMD_CMD_MASK; + + host->use_busy = 0; + if (!(cmd->flags & MMC_RSP_PRESENT)) { + sdcmd |= SDCMD_NO_RESPONSE; + } else { + if (cmd->flags & MMC_RSP_136) + sdcmd |= SDCMD_LONG_RESPONSE; + if (cmd->flags & MMC_RSP_BUSY) { + sdcmd |= SDCMD_BUSYWAIT; + host->use_busy = 1; + } + } + + if (cmd->data) { + log_event("CMDD", cmd->data->blocks, cmd->data->blksz); + if (host->delay_after_this_stop) { + struct timespec64 now; + int time_since_stop; + + ktime_get_real_ts64(&now); + time_since_stop = now.tv_sec - host->stop_time.tv_sec; + if (time_since_stop < 2) { + /* Possibly less than one second */ + time_since_stop = time_since_stop * 1000000 + + (now.tv_nsec - host->stop_time.tv_nsec)/1000; + if (time_since_stop < + host->delay_after_this_stop) + udelay(host->delay_after_this_stop - + time_since_stop); + } + } + + host->delay_after_this_stop = host->delay_after_stop; + if ((cmd->data->flags & MMC_DATA_READ) && !host->use_sbc) { + /* See if read crosses one of the hazardous sectors */ + u32 first_blk, last_blk; + + /* Intentionally include the following sector because + without CMD23/SBC the read may run on. */ + first_blk = host->mrq->cmd->arg; + last_blk = first_blk + cmd->data->blocks; + + if (((last_blk >= (host->sectors - 64)) && + (first_blk <= (host->sectors - 64))) || + ((last_blk >= (host->sectors - 32)) && + (first_blk <= (host->sectors - 32)))) { + host->delay_after_this_stop = + max(250u, host->delay_after_stop); + } + } + + if (cmd->data->flags & MMC_DATA_WRITE) + sdcmd |= SDCMD_WRITE_CMD; + if (cmd->data->flags & MMC_DATA_READ) + sdcmd |= SDCMD_READ_CMD; + } + + bcm2835_sdhost_write(host, sdcmd | SDCMD_NEW_FLAG, SDCMD); + + return true; +} + +static void bcm2835_sdhost_finish_command(struct bcm2835_host *host, + unsigned long *irq_flags); +static void bcm2835_sdhost_transfer_complete(struct bcm2835_host *host); + +static void bcm2835_sdhost_finish_data(struct bcm2835_host *host) +{ + struct mmc_data *data; + + data = host->data; + BUG_ON(!data); + + log_event("FDA<", host->mrq, host->cmd); + pr_debug("finish_data(error %d, stop %d, sbc %d)\n", + data->error, data->stop ? 1 : 0, + host->mrq->sbc ? 1 : 0); + + host->hcfg &= ~(SDHCFG_DATA_IRPT_EN | SDHCFG_BLOCK_IRPT_EN); + bcm2835_sdhost_write(host, host->hcfg, SDHCFG); + + data->bytes_xfered = data->error ? 0 : (data->blksz * data->blocks); + + host->data_complete = 1; + + if (host->cmd) { + /* + * Data managed to finish before the + * command completed. Make sure we do + * things in the proper order. + */ + pr_debug("Finished early - HSTS %x\n", + bcm2835_sdhost_read(host, SDHSTS)); + } + else + bcm2835_sdhost_transfer_complete(host); + log_event("FDA>", host->mrq, host->cmd); +} + +static void bcm2835_sdhost_transfer_complete(struct bcm2835_host *host) +{ + struct mmc_data *data; + + BUG_ON(host->cmd); + BUG_ON(!host->data); + BUG_ON(!host->data_complete); + + data = host->data; + host->data = NULL; + + log_event("TCM<", data, data->error); + pr_debug("transfer_complete(error %d, stop %d)\n", + data->error, data->stop ? 1 : 0); + + /* + * Need to send CMD12 if - + * a) open-ended multiblock transfer (no CMD23) + * b) error in multiblock transfer + */ + if (host->mrq->stop && (data->error || !host->use_sbc)) { + if (bcm2835_sdhost_send_command(host, host->mrq->stop)) { + /* No busy, so poll for completion */ + if (!host->use_busy) + bcm2835_sdhost_finish_command(host, NULL); + + if (host->delay_after_this_stop) + ktime_get_real_ts64(&host->stop_time); + } + } else { + bcm2835_sdhost_wait_transfer_complete(host); + tasklet_schedule(&host->finish_tasklet); + } + log_event("TCM>", data, 0); +} + +/* If irq_flags is valid, the caller is in a thread context and is allowed + to sleep */ +static void bcm2835_sdhost_finish_command(struct bcm2835_host *host, + unsigned long *irq_flags) +{ + u32 sdcmd; + u32 retries; +#ifdef DEBUG + struct timespec64 before, after; + int timediff = 0; +#endif + + log_event("FCM<", host->mrq, host->cmd); + pr_debug("finish_command(%x)\n", bcm2835_sdhost_read(host, SDCMD)); + + BUG_ON(!host->cmd || !host->mrq); + + /* Poll quickly at first */ + + retries = host->cmd_quick_poll_retries; + if (!retries) { + /* Work out how many polls take 1us by timing 10us */ + struct timespec64 start, now; + int us_diff; + + retries = 1; + do { + int i; + + retries *= 2; + + ktime_get_real_ts64(&start); + + for (i = 0; i < retries; i++) { + cpu_relax(); + sdcmd = bcm2835_sdhost_read(host, SDCMD); + } + + ktime_get_real_ts64(&now); + us_diff = (now.tv_sec - start.tv_sec) * 1000000 + + (now.tv_nsec - start.tv_nsec)/1000; + } while (us_diff < 10); + + host->cmd_quick_poll_retries = ((retries * us_diff + 9)*CMD_DALLY_US)/10 + 1; + retries = 1; // We've already waited long enough this time + } + + for (sdcmd = bcm2835_sdhost_read(host, SDCMD); + (sdcmd & SDCMD_NEW_FLAG) && retries; + retries--) { + cpu_relax(); + sdcmd = bcm2835_sdhost_read(host, SDCMD); + } + + if (!retries) { + unsigned long wait_max; + + if (!irq_flags) { + /* Schedule the work */ + log_event("CWWQ", 0, 0); + schedule_work(&host->cmd_wait_wq); + return; + } + + /* Wait max 100 ms */ + wait_max = jiffies + msecs_to_jiffies(100); + while (time_before(jiffies, wait_max)) { + spin_unlock_irqrestore(&host->lock, *irq_flags); + usleep_range(1, 10); + spin_lock_irqsave(&host->lock, *irq_flags); + sdcmd = bcm2835_sdhost_read(host, SDCMD); + if (!(sdcmd & SDCMD_NEW_FLAG)) + break; + } + } + + /* Check for errors */ + if (sdcmd & SDCMD_NEW_FLAG) { + if (host->debug) { + pr_err("%s: command %d never completed.\n", + mmc_hostname(host->mmc), host->cmd->opcode); + bcm2835_sdhost_dumpregs(host); + } + host->cmd->error = -EILSEQ; + tasklet_schedule(&host->finish_tasklet); + return; + } else if (sdcmd & SDCMD_FAIL_FLAG) { + u32 sdhsts = bcm2835_sdhost_read(host, SDHSTS); + + /* Clear the errors */ + bcm2835_sdhost_write(host, SDHSTS_ERROR_MASK, SDHSTS); + + if (host->debug) + pr_info("%s: error detected - CMD %x, HSTS %03x, EDM %x\n", + mmc_hostname(host->mmc), sdcmd, sdhsts, + bcm2835_sdhost_read(host, SDEDM)); + + if ((sdhsts & SDHSTS_CRC7_ERROR) && + (host->cmd->opcode == 1)) { + if (host->debug) + pr_info("%s: ignoring CRC7 error for CMD1\n", + mmc_hostname(host->mmc)); + } else { + u32 edm, fsm; + + if (sdhsts & SDHSTS_CMD_TIME_OUT) { + if (host->debug) + pr_warn("%s: command %d timeout\n", + mmc_hostname(host->mmc), + host->cmd->opcode); + host->cmd->error = -ETIMEDOUT; + } else { + pr_warn("%s: unexpected command %d error\n", + mmc_hostname(host->mmc), + host->cmd->opcode); + host->cmd->error = -EILSEQ; + } + + edm = readl(host->ioaddr + SDEDM); + fsm = edm & SDEDM_FSM_MASK; + if (fsm == SDEDM_FSM_READWAIT || + fsm == SDEDM_FSM_WRITESTART1) + writel(edm | SDEDM_FORCE_DATA_MODE, + host->ioaddr + SDEDM); + tasklet_schedule(&host->finish_tasklet); + return; + } + } + + if (host->cmd->flags & MMC_RSP_PRESENT) { + if (host->cmd->flags & MMC_RSP_136) { + int i; + for (i = 0; i < 4; i++) + host->cmd->resp[3 - i] = bcm2835_sdhost_read(host, SDRSP0 + i*4); + pr_debug("%s: finish_command %08x %08x %08x %08x\n", + mmc_hostname(host->mmc), + host->cmd->resp[0], host->cmd->resp[1], host->cmd->resp[2], host->cmd->resp[3]); + log_event("RSP ", host->cmd->resp[0], host->cmd->resp[1]); + } else { + host->cmd->resp[0] = bcm2835_sdhost_read(host, SDRSP0); + pr_debug("%s: finish_command %08x\n", + mmc_hostname(host->mmc), + host->cmd->resp[0]); + log_event("RSP ", host->cmd->resp[0], 0); + } + } + + if (host->cmd == host->mrq->sbc) { + /* Finished CMD23, now send actual command. */ + host->cmd = NULL; + if (bcm2835_sdhost_send_command(host, host->mrq->cmd)) { + if (host->data && host->dma_desc) + /* DMA transfer starts now, PIO starts after irq */ + bcm2835_sdhost_start_dma(host); + + if (!host->use_busy) + bcm2835_sdhost_finish_command(host, NULL); + } + } else if (host->cmd == host->mrq->stop) { + /* Finished CMD12 */ + tasklet_schedule(&host->finish_tasklet); + } else { + /* Processed actual command. */ + host->cmd = NULL; + if (!host->data) + tasklet_schedule(&host->finish_tasklet); + else if (host->data_complete) + bcm2835_sdhost_transfer_complete(host); + } + log_event("FCM>", host->mrq, host->cmd); +} + +static void bcm2835_sdhost_timeout(struct timer_list *t) +{ + struct bcm2835_host *host = from_timer(host, t, timer); + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + log_event("TIM<", 0, 0); + + if (host->mrq) { + pr_err("%s: timeout waiting for hardware interrupt.\n", + mmc_hostname(host->mmc)); + log_dump(); + bcm2835_sdhost_dumpregs(host); + + if (host->data) { + host->data->error = -ETIMEDOUT; + bcm2835_sdhost_finish_data(host); + } else { + if (host->cmd) + host->cmd->error = -ETIMEDOUT; + else + host->mrq->cmd->error = -ETIMEDOUT; + + pr_debug("timeout_timer tasklet_schedule\n"); + tasklet_schedule(&host->finish_tasklet); + } + } + + spin_unlock_irqrestore(&host->lock, flags); +} + +static void bcm2835_sdhost_busy_irq(struct bcm2835_host *host, u32 intmask) +{ + log_event("IRQB", host->cmd, intmask); + if (!host->cmd) { + pr_err("%s: got command busy interrupt 0x%08x even " + "though no command operation was in progress.\n", + mmc_hostname(host->mmc), (unsigned)intmask); + bcm2835_sdhost_dumpregs(host); + return; + } + + if (!host->use_busy) { + pr_err("%s: got command busy interrupt 0x%08x even " + "though not expecting one.\n", + mmc_hostname(host->mmc), (unsigned)intmask); + bcm2835_sdhost_dumpregs(host); + return; + } + host->use_busy = 0; + + if (intmask & SDHSTS_ERROR_MASK) + { + pr_err("sdhost_busy_irq: intmask %x, data %p\n", intmask, host->mrq->data); + if (intmask & SDHSTS_CRC7_ERROR) + host->cmd->error = -EILSEQ; + else if (intmask & (SDHSTS_CRC16_ERROR | + SDHSTS_FIFO_ERROR)) { + if (host->mrq->data) + host->mrq->data->error = -EILSEQ; + else + host->cmd->error = -EILSEQ; + } else if (intmask & SDHSTS_REW_TIME_OUT) { + if (host->mrq->data) + host->mrq->data->error = -ETIMEDOUT; + else + host->cmd->error = -ETIMEDOUT; + } else if (intmask & SDHSTS_CMD_TIME_OUT) + host->cmd->error = -ETIMEDOUT; + + if (host->debug) { + log_dump(); + bcm2835_sdhost_dumpregs(host); + } + } + else + bcm2835_sdhost_finish_command(host, NULL); +} + +static void bcm2835_sdhost_data_irq(struct bcm2835_host *host, u32 intmask) +{ + /* There are no dedicated data/space available interrupt + status bits, so it is necessary to use the single shared + data/space available FIFO status bits. It is therefore not + an error to get here when there is no data transfer in + progress. */ + log_event("IRQD", host->data, intmask); + if (!host->data) + return; + + if (intmask & (SDHSTS_CRC16_ERROR | + SDHSTS_FIFO_ERROR | + SDHSTS_REW_TIME_OUT)) { + if (intmask & (SDHSTS_CRC16_ERROR | + SDHSTS_FIFO_ERROR)) + host->data->error = -EILSEQ; + else + host->data->error = -ETIMEDOUT; + + if (host->debug) { + log_dump(); + bcm2835_sdhost_dumpregs(host); + } + } + + if (host->data->error) { + bcm2835_sdhost_finish_data(host); + } else if (host->data->flags & MMC_DATA_WRITE) { + /* Use the block interrupt for writes after the first block */ + host->hcfg &= ~(SDHCFG_DATA_IRPT_EN); + host->hcfg |= SDHCFG_BLOCK_IRPT_EN; + bcm2835_sdhost_write(host, host->hcfg, SDHCFG); + bcm2835_sdhost_transfer_pio(host); + } else { + bcm2835_sdhost_transfer_pio(host); + host->blocks--; + if ((host->blocks == 0) || host->data->error) + bcm2835_sdhost_finish_data(host); + } +} + +static void bcm2835_sdhost_block_irq(struct bcm2835_host *host, u32 intmask) +{ + log_event("IRQK", host->data, intmask); + if (!host->data) { + pr_err("%s: got block interrupt 0x%08x even " + "though no data operation was in progress.\n", + mmc_hostname(host->mmc), (unsigned)intmask); + bcm2835_sdhost_dumpregs(host); + return; + } + + if (intmask & (SDHSTS_CRC16_ERROR | + SDHSTS_FIFO_ERROR | + SDHSTS_REW_TIME_OUT)) { + if (intmask & (SDHSTS_CRC16_ERROR | + SDHSTS_FIFO_ERROR)) + host->data->error = -EILSEQ; + else + host->data->error = -ETIMEDOUT; + + if (host->debug) { + log_dump(); + bcm2835_sdhost_dumpregs(host); + } + } + + if (!host->dma_desc) { + BUG_ON(!host->blocks); + if (host->data->error || (--host->blocks == 0)) { + bcm2835_sdhost_finish_data(host); + } else { + bcm2835_sdhost_transfer_pio(host); + } + } else if (host->data->flags & MMC_DATA_WRITE) { + bcm2835_sdhost_finish_data(host); + } +} + +static irqreturn_t bcm2835_sdhost_irq(int irq, void *dev_id) +{ + irqreturn_t result = IRQ_NONE; + struct bcm2835_host *host = dev_id; + u32 intmask; + + spin_lock(&host->lock); + + intmask = bcm2835_sdhost_read(host, SDHSTS); + log_event("IRQ<", intmask, 0); + + bcm2835_sdhost_write(host, + SDHSTS_BUSY_IRPT | + SDHSTS_BLOCK_IRPT | + SDHSTS_SDIO_IRPT | + SDHSTS_DATA_FLAG, + SDHSTS); + + if (intmask & SDHSTS_BLOCK_IRPT) { + bcm2835_sdhost_block_irq(host, intmask); + result = IRQ_HANDLED; + } + + if (intmask & SDHSTS_BUSY_IRPT) { + bcm2835_sdhost_busy_irq(host, intmask); + result = IRQ_HANDLED; + } + + /* There is no true data interrupt status bit, so it is + necessary to qualify the data flag with the interrupt + enable bit */ + if ((intmask & SDHSTS_DATA_FLAG) && + (host->hcfg & SDHCFG_DATA_IRPT_EN)) { + bcm2835_sdhost_data_irq(host, intmask); + result = IRQ_HANDLED; + } + + log_event("IRQ>", bcm2835_sdhost_read(host, SDHSTS), 0); + spin_unlock(&host->lock); + + return result; +} + +static void bcm2835_sdhost_set_clock(struct bcm2835_host *host, unsigned int clock) +{ + int div = 0; /* Initialized for compiler warning */ + unsigned int input_clock = clock; + unsigned long flags; + + if (host->debug) + pr_info("%s: set_clock(%d)\n", mmc_hostname(host->mmc), clock); + + if (host->overclock_50 && (clock == 50*MHZ)) + clock = host->overclock_50 * MHZ + (MHZ - 1); + + /* The SDCDIV register has 11 bits, and holds (div - 2). + But in data mode the max is 50MHz wihout a minimum, and only the + bottom 3 bits are used. Since the switch over is automatic (unless + we have marked the card as slow...), chosen values have to make + sense in both modes. + Ident mode must be 100-400KHz, so can range check the requested + clock. CMD15 must be used to return to data mode, so this can be + monitored. + + clock 250MHz -> 0->125MHz, 1->83.3MHz, 2->62.5MHz, 3->50.0MHz + 4->41.7MHz, 5->35.7MHz, 6->31.3MHz, 7->27.8MHz + + 623->400KHz/27.8MHz + reset value (507)->491159/50MHz + + BUT, the 3-bit clock divisor in data mode is too small if the + core clock is higher than 250MHz, so instead use the SLOW_CARD + configuration bit to force the use of the ident clock divisor + at all times. + */ + + host->mmc->actual_clock = 0; + + if (host->firmware_sets_cdiv) { + u32 msg[3] = { clock, 0, 0 }; + + rpi_firmware_property(host->fw, + RPI_FIRMWARE_SET_SDHOST_CLOCK, + &msg, sizeof(msg)); + + clock = max(msg[1], msg[2]); + spin_lock_irqsave(&host->lock, flags); + } else { + spin_lock_irqsave(&host->lock, flags); + if (clock < 100000) { + /* Can't stop the clock, but make it as slow as + * possible to show willing + */ + host->cdiv = SDCDIV_MAX_CDIV; + bcm2835_sdhost_write(host, host->cdiv, SDCDIV); + spin_unlock_irqrestore(&host->lock, flags); + return; + } + + div = host->max_clk / clock; + if (div < 2) + div = 2; + if ((host->max_clk / div) > clock) + div++; + div -= 2; + + if (div > SDCDIV_MAX_CDIV) + div = SDCDIV_MAX_CDIV; + + clock = host->max_clk / (div + 2); + + host->cdiv = div; + bcm2835_sdhost_write(host, host->cdiv, SDCDIV); + + if (host->debug) + pr_info("%s: clock=%d -> max_clk=%d, cdiv=%x " + "(actual clock %d)\n", + mmc_hostname(host->mmc), input_clock, + host->max_clk, host->cdiv, + clock); + } + + /* Calibrate some delays */ + + host->ns_per_fifo_word = (1000000000/clock) * + ((host->mmc->caps & MMC_CAP_4_BIT_DATA) ? 8 : 32); + + if (input_clock == 50 * MHZ) { + if (clock > input_clock) { + /* Save the closest value, to make it easier + to reduce in the event of error */ + host->overclock_50 = (clock/MHZ); + + if (clock != host->overclock) { + pr_info("%s: overclocking to %dHz\n", + mmc_hostname(host->mmc), clock); + host->overclock = clock; + } + } else if (host->overclock) { + host->overclock = 0; + if (clock == 50 * MHZ) + pr_warn("%s: cancelling overclock\n", + mmc_hostname(host->mmc)); + } + } else if (input_clock == 0) { + /* Reset the preferred overclock when the clock is stopped. + * This always happens during initialisation. */ + host->overclock_50 = host->user_overclock_50; + host->overclock = 0; + } + + /* Set the timeout to 500ms */ + bcm2835_sdhost_write(host, clock/2, SDTOUT); + + host->mmc->actual_clock = clock; + host->clock = input_clock; + host->reset_clock = 0; + + spin_unlock_irqrestore(&host->lock, flags); +} + +static void bcm2835_sdhost_request(struct mmc_host *mmc, struct mmc_request *mrq) +{ + struct bcm2835_host *host; + unsigned long flags; + u32 edm, fsm; + + host = mmc_priv(mmc); + + if (host->debug) { + struct mmc_command *cmd = mrq->cmd; + BUG_ON(!cmd); + if (cmd->data) + pr_info("%s: cmd %d 0x%x (flags 0x%x) - %s %d*%d\n", + mmc_hostname(mmc), + cmd->opcode, cmd->arg, cmd->flags, + (cmd->data->flags & MMC_DATA_READ) ? + "read" : "write", cmd->data->blocks, + cmd->data->blksz); + else + pr_info("%s: cmd %d 0x%x (flags 0x%x)\n", + mmc_hostname(mmc), + cmd->opcode, cmd->arg, cmd->flags); + } + + /* Reset the error statuses in case this is a retry */ + if (mrq->sbc) + mrq->sbc->error = 0; + if (mrq->cmd) + mrq->cmd->error = 0; + if (mrq->data) + mrq->data->error = 0; + if (mrq->stop) + mrq->stop->error = 0; + + if (mrq->data && !is_power_of_2(mrq->data->blksz)) { + pr_err("%s: unsupported block size (%d bytes)\n", + mmc_hostname(mmc), mrq->data->blksz); + mrq->cmd->error = -EINVAL; + mmc_request_done(mmc, mrq); + return; + } + + if (host->use_dma && mrq->data && + (mrq->data->blocks > host->pio_limit)) + bcm2835_sdhost_prepare_dma(host, mrq->data); + + if (host->reset_clock) + bcm2835_sdhost_set_clock(host, host->clock); + + spin_lock_irqsave(&host->lock, flags); + + WARN_ON(host->mrq != NULL); + host->mrq = mrq; + + edm = bcm2835_sdhost_read(host, SDEDM); + fsm = edm & SDEDM_FSM_MASK; + + log_event("REQ<", mrq, edm); + if ((fsm != SDEDM_FSM_IDENTMODE) && + (fsm != SDEDM_FSM_DATAMODE)) { + log_event("REQ!", mrq, edm); + if (host->debug) { + pr_warn("%s: previous command (%d) not complete (EDM %x)\n", + mmc_hostname(host->mmc), + bcm2835_sdhost_read(host, SDCMD) & SDCMD_CMD_MASK, + edm); + log_dump(); + bcm2835_sdhost_dumpregs(host); + } + mrq->cmd->error = -EILSEQ; + tasklet_schedule(&host->finish_tasklet); + spin_unlock_irqrestore(&host->lock, flags); + return; + } + + host->use_sbc = !!mrq->sbc && + (host->mrq->data->flags & USE_CMD23_FLAGS); + if (host->use_sbc) { + if (bcm2835_sdhost_send_command(host, mrq->sbc)) { + if (!host->use_busy) + bcm2835_sdhost_finish_command(host, &flags); + } + } else if (bcm2835_sdhost_send_command(host, mrq->cmd)) { + if (host->data && host->dma_desc) + /* DMA transfer starts now, PIO starts after irq */ + bcm2835_sdhost_start_dma(host); + + if (!host->use_busy) + bcm2835_sdhost_finish_command(host, &flags); + } + + log_event("CMD ", mrq->cmd->opcode, + mrq->data ? (u32)mrq->data->blksz : 0); + + log_event("REQ>", mrq, 0); + spin_unlock_irqrestore(&host->lock, flags); +} + +static void bcm2835_sdhost_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + + struct bcm2835_host *host = mmc_priv(mmc); + unsigned long flags; + + if (host->debug) + pr_info("%s: ios clock %d, pwr %d, bus_width %d, " + "timing %d, vdd %d, drv_type %d\n", + mmc_hostname(mmc), + ios->clock, ios->power_mode, ios->bus_width, + ios->timing, ios->signal_voltage, ios->drv_type); + + spin_lock_irqsave(&host->lock, flags); + + log_event("IOS<", ios->clock, 0); + + /* set bus width */ + host->hcfg &= ~SDHCFG_WIDE_EXT_BUS; + if (ios->bus_width == MMC_BUS_WIDTH_4) + host->hcfg |= SDHCFG_WIDE_EXT_BUS; + + host->hcfg |= SDHCFG_WIDE_INT_BUS; + + /* Disable clever clock switching, to cope with fast core clocks */ + host->hcfg |= SDHCFG_SLOW_CARD; + + bcm2835_sdhost_write(host, host->hcfg, SDHCFG); + + spin_unlock_irqrestore(&host->lock, flags); + + if (!ios->clock || ios->clock != host->clock) + bcm2835_sdhost_set_clock(host, ios->clock); +} + +static struct mmc_host_ops bcm2835_sdhost_ops = { + .request = bcm2835_sdhost_request, + .set_ios = bcm2835_sdhost_set_ios, +// todo:fix .hw_reset = bcm2835_sdhost_reset, +}; + +static void bcm2835_sdhost_cmd_wait_work(struct work_struct *work) +{ + struct bcm2835_host *host; + unsigned long flags; + + host = container_of(work, struct bcm2835_host, cmd_wait_wq); + + spin_lock_irqsave(&host->lock, flags); + + log_event("CWK<", host->cmd, host->mrq); + + /* + * If this tasklet gets rescheduled while running, it will + * be run again afterwards but without any active request. + */ + if (!host->mrq) { + spin_unlock_irqrestore(&host->lock, flags); + return; + } + + bcm2835_sdhost_finish_command(host, &flags); + + log_event("CWK>", host->cmd, 0); + + spin_unlock_irqrestore(&host->lock, flags); +} + +static void bcm2835_sdhost_tasklet_finish(unsigned long param) +{ + struct bcm2835_host *host; + unsigned long flags; + struct mmc_request *mrq; + struct dma_chan *terminate_chan = NULL; + + host = (struct bcm2835_host *)param; + + spin_lock_irqsave(&host->lock, flags); + + log_event("TSK<", host->mrq, 0); + /* + * If this tasklet gets rescheduled while running, it will + * be run again afterwards but without any active request. + */ + if (!host->mrq) { + spin_unlock_irqrestore(&host->lock, flags); + return; + } + + del_timer(&host->timer); + + mrq = host->mrq; + + /* Drop the overclock after any data corruption, or after any + * error while overclocked. Ignore errors for status commands, + * as they are likely when a card is ejected. */ + if (host->overclock) { + if ((mrq->cmd && mrq->cmd->error && + (mrq->cmd->opcode != MMC_SEND_STATUS)) || + (mrq->data && mrq->data->error) || + (mrq->stop && mrq->stop->error) || + (mrq->sbc && mrq->sbc->error)) { + host->overclock_50--; + pr_warn("%s: reducing overclock due to errors\n", + mmc_hostname(host->mmc)); + host->reset_clock = 1; + mrq->cmd->error = -ETIMEDOUT; + mrq->cmd->retries = 1; + } + } + + host->mrq = NULL; + host->cmd = NULL; + host->data = NULL; + + host->dma_desc = NULL; + terminate_chan = host->dma_chan; + host->dma_chan = NULL; + + spin_unlock_irqrestore(&host->lock, flags); + + if (terminate_chan) + { + int err = dmaengine_terminate_all(terminate_chan); + if (err) + pr_err("%s: failed to terminate DMA (%d)\n", + mmc_hostname(host->mmc), err); + } + + /* The SDHOST block doesn't report any errors for a disconnected + interface. All cards and SDIO devices should report some supported + voltage range, so a zero response to SEND_OP_COND, IO_SEND_OP_COND + or APP_SEND_OP_COND can be treated as an error. */ + if (((mrq->cmd->opcode == MMC_SEND_OP_COND) || + (mrq->cmd->opcode == SD_IO_SEND_OP_COND) || + (mrq->cmd->opcode == SD_APP_OP_COND)) && + (mrq->cmd->error == 0) && + (mrq->cmd->resp[0] == 0)) { + mrq->cmd->error = -ETIMEDOUT; + if (host->debug) + pr_info("%s: faking timeout due to zero OCR\n", + mmc_hostname(host->mmc)); + } + + if (!mmc_hsq_finalize_request(host->mmc, mrq)) + mmc_request_done(host->mmc, mrq); + log_event("TSK>", mrq, 0); +} + +static int bcm2835_sdhost_add_host(struct platform_device *pdev) +{ + struct bcm2835_host *host = platform_get_drvdata(pdev); + struct mmc_host *mmc; + struct mmc_hsq *hsq; + struct dma_slave_config cfg; + char pio_limit_string[20]; + int ret; + + mmc = host->mmc; + + if (!mmc->f_max || mmc->f_max > host->max_clk) + mmc->f_max = host->max_clk; + mmc->f_min = host->max_clk / SDCDIV_MAX_CDIV; + + mmc->max_busy_timeout = (~(unsigned int)0)/(mmc->f_max/1000); + + pr_debug("f_max %d, f_min %d, max_busy_timeout %d\n", + mmc->f_max, mmc->f_min, mmc->max_busy_timeout); + + /* host controller capabilities */ + mmc->caps |= + MMC_CAP_SD_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED | + MMC_CAP_NEEDS_POLL | MMC_CAP_HW_RESET | + ((ALLOW_CMD23_READ|ALLOW_CMD23_WRITE) * MMC_CAP_CMD23); + + spin_lock_init(&host->lock); + + if (host->allow_dma) { + if (IS_ERR_OR_NULL(host->dma_chan_rxtx)) { + pr_err("%s: unable to initialise DMA channel. " + "Falling back to PIO\n", + mmc_hostname(mmc)); + host->use_dma = false; + } else { + cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + + /* Validate the slave configurations */ + + cfg.direction = DMA_MEM_TO_DEV; + cfg.src_addr = 0; + cfg.dst_addr = host->bus_addr + SDDATA; + + ret = dmaengine_slave_config(host->dma_chan_rxtx, &cfg); + + if (ret == 0) { + host->dma_cfg_tx = cfg; + + cfg.direction = DMA_DEV_TO_MEM; + cfg.src_addr = host->bus_addr + SDDATA; + cfg.dst_addr = 0; + + ret = dmaengine_slave_config(host->dma_chan_rxtx, &cfg); + } + + if (ret == 0) { + host->dma_cfg_rx = cfg; + + host->use_dma = true; + } else { + pr_err("%s: unable to configure DMA channel. " + "Falling back to PIO\n", + mmc_hostname(mmc)); + dma_release_channel(host->dma_chan_rxtx); + host->dma_chan_rxtx = NULL; + host->use_dma = false; + } + } + } else { + host->use_dma = false; + } + + mmc->max_segs = 128; + mmc->max_req_size = min_t(size_t, 524288, dma_max_mapping_size(&pdev->dev)); + mmc->max_seg_size = mmc->max_req_size; + mmc->max_blk_size = 512; + mmc->max_blk_count = 65535; + + /* report supported voltage ranges */ + mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; + + tasklet_init(&host->finish_tasklet, + bcm2835_sdhost_tasklet_finish, (unsigned long)host); + + INIT_WORK(&host->cmd_wait_wq, bcm2835_sdhost_cmd_wait_work); + + timer_setup(&host->timer, bcm2835_sdhost_timeout, 0); + + bcm2835_sdhost_init(host, 0); + + ret = request_irq(host->irq, bcm2835_sdhost_irq, 0 /*IRQF_SHARED*/, + mmc_hostname(mmc), host); + if (ret) { + pr_err("%s: failed to request IRQ %d: %d\n", + mmc_hostname(mmc), host->irq, ret); + goto untasklet; + } + + hsq = devm_kzalloc(&pdev->dev, sizeof(*hsq), GFP_KERNEL); + if (!hsq) { + ret = -ENOMEM; + goto free_irq; + } + + ret = mmc_hsq_init(hsq, host->mmc); + if (ret) + goto free_irq; + + mmc_add_host(mmc); + + pio_limit_string[0] = '\0'; + if (host->use_dma && (host->pio_limit > 0)) + sprintf(pio_limit_string, " (>%d)", host->pio_limit); + pr_info("%s: %s loaded - DMA %s%s\n", + mmc_hostname(mmc), DRIVER_NAME, + host->use_dma ? "enabled" : "disabled", + pio_limit_string); + + return 0; + +free_irq: + free_irq(host->irq, host); + +untasklet: + tasklet_kill(&host->finish_tasklet); + + return ret; +} + +static int bcm2835_sdhost_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct clk *clk; + struct resource *iomem; + struct bcm2835_host *host; + struct mmc_host *mmc; + u32 msg[3]; + int ret; + + pr_debug("bcm2835_sdhost_probe\n"); + mmc = mmc_alloc_host(sizeof(*host), dev); + if (!mmc) + return -ENOMEM; + + mmc->ops = &bcm2835_sdhost_ops; + host = mmc_priv(mmc); + host->mmc = mmc; + host->pio_timeout = msecs_to_jiffies(500); + host->pio_limit = 1; + host->max_delay = 1; /* Warn if over 1ms */ + host->allow_dma = 1; + spin_lock_init(&host->lock); + + host->ioaddr = devm_platform_get_and_ioremap_resource(pdev, 0, &iomem); + if (IS_ERR(host->ioaddr)) { + ret = PTR_ERR(host->ioaddr); + goto err; + } + + host->bus_addr = iomem->start; + + if (node) { + /* Read any custom properties */ + of_property_read_u32(node, + "brcm,delay-after-stop", + &host->delay_after_stop); + of_property_read_u32(node, + "brcm,overclock-50", + &host->user_overclock_50); + of_property_read_u32(node, + "brcm,pio-limit", + &host->pio_limit); + host->allow_dma = + !of_property_read_bool(node, "brcm,force-pio"); + host->debug = of_property_read_bool(node, "brcm,debug"); + } + + host->dma_chan = NULL; + host->dma_desc = NULL; + + /* Formally recognise the other way of disabling DMA */ + if (host->pio_limit == 0x7fffffff) + host->allow_dma = false; + + if (host->allow_dma) { + if (node) { + host->dma_chan_rxtx = + dma_request_slave_channel(dev, "rx-tx"); + if (!host->dma_chan_rxtx) + host->dma_chan_rxtx = + dma_request_slave_channel(dev, "tx"); + if (!host->dma_chan_rxtx) + host->dma_chan_rxtx = + dma_request_slave_channel(dev, "rx"); + } else { + dma_cap_mask_t mask; + + dma_cap_zero(mask); + /* we don't care about the channel, any would work */ + dma_cap_set(DMA_SLAVE, mask); + host->dma_chan_rxtx = + dma_request_channel(mask, NULL, NULL); + } + } + + clk = devm_clk_get(dev, NULL); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + if (ret == -EPROBE_DEFER) + dev_info(dev, "could not get clk, deferring probe\n"); + else + dev_err(dev, "could not get clk\n"); + goto err; + } + + host->fw = rpi_firmware_get( + of_parse_phandle(dev->of_node, "firmware", 0)); + if (!host->fw) { + ret = -EPROBE_DEFER; + goto err; + } + + host->max_clk = clk_get_rate(clk); + + host->irq = platform_get_irq(pdev, 0); + if (host->irq <= 0) { + dev_err(dev, "get IRQ failed\n"); + ret = -EINVAL; + goto err; + } + + pr_debug(" - max_clk %lx, irq %d\n", + (unsigned long)host->max_clk, + (int)host->irq); + + log_init(dev); + + if (node) + mmc_of_parse(mmc); + else + mmc->caps |= MMC_CAP_4_BIT_DATA; + + msg[0] = 0; + msg[1] = ~0; + msg[2] = ~0; + + rpi_firmware_property(host->fw, + RPI_FIRMWARE_SET_SDHOST_CLOCK, + &msg, sizeof(msg)); + + host->firmware_sets_cdiv = (msg[1] != ~0); + + platform_set_drvdata(pdev, host); + + ret = bcm2835_sdhost_add_host(pdev); + if (ret) + goto err; + + pr_debug("bcm2835_sdhost_probe -> OK\n"); + + return 0; + +err: + pr_debug("bcm2835_sdhost_probe -> err %d\n", ret); + if (host->fw) + rpi_firmware_put(host->fw); + if (host->dma_chan_rxtx) + dma_release_channel(host->dma_chan_rxtx); + mmc_free_host(mmc); + + return ret; +} + +static void bcm2835_sdhost_remove(struct platform_device *pdev) +{ + struct bcm2835_host *host = platform_get_drvdata(pdev); + + pr_debug("bcm2835_sdhost_remove\n"); + + mmc_remove_host(host->mmc); + + bcm2835_sdhost_set_power(host, false); + + free_irq(host->irq, host); + + del_timer_sync(&host->timer); + + tasklet_kill(&host->finish_tasklet); + rpi_firmware_put(host->fw); + if (host->dma_chan_rxtx) + dma_release_channel(host->dma_chan_rxtx); + mmc_free_host(host->mmc); + platform_set_drvdata(pdev, NULL); + + pr_debug("bcm2835_sdhost_remove - OK\n"); +} + +static const struct of_device_id bcm2835_sdhost_match[] = { + { .compatible = "brcm,bcm2835-sdhost" }, + { } +}; +MODULE_DEVICE_TABLE(of, bcm2835_sdhost_match); + +static struct platform_driver bcm2835_sdhost_driver = { + .probe = bcm2835_sdhost_probe, + .remove = bcm2835_sdhost_remove, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = bcm2835_sdhost_match, + }, +}; +module_platform_driver(bcm2835_sdhost_driver); + +MODULE_ALIAS("platform:sdhost-bcm2835"); +MODULE_DESCRIPTION("BCM2835 SDHost driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Phil Elwell"); diff --git a/drivers/mmc/host/bcm2835.c b/drivers/mmc/host/bcm2835.c index 35d8fdea668b91..bc4a33c35f1768 100644 --- a/drivers/mmc/host/bcm2835.c +++ b/drivers/mmc/host/bcm2835.c @@ -38,7 +38,6 @@ #include <linux/io.h> #include <linux/iopoll.h> #include <linux/module.h> -#include <linux/of_address.h> #include <linux/of_irq.h> #include <linux/platform_device.h> #include <linux/scatterlist.h> @@ -49,6 +48,8 @@ #include <linux/mmc/mmc.h> #include <linux/mmc/sd.h> +#include <soc/bcm2835/raspberrypi-firmware.h> + #define SDCMD 0x00 /* Command to SD card - 16 R/W */ #define SDARG 0x04 /* Argument to SD card - 32 R/W */ #define SDTOUT 0x08 /* Start value for timeout counter - 32 R/W */ @@ -187,6 +188,14 @@ struct bcm2835_host { struct page *drain_page; u32 drain_offset; bool use_dma; + + /* Downstream additions */ + struct rpi_firmware *fw; + u32 pio_limit; /* Maximum block count for PIO (0 = always DMA) */ + u32 user_overclock_50; /* User's preferred overclock frequency */ + u32 overclock_50; /* Freq to use when 50MHz is requested (MHz) */ + u32 overclock; /* Current frequency if overclocked, else zero */ + bool reset_clock:1; /* Reset the clock for the next request */ }; static void bcm2835_dumpcmd(struct bcm2835_host *host, struct mmc_command *cmd, @@ -241,8 +250,11 @@ static void bcm2835_dumpregs(struct bcm2835_host *host) static void bcm2835_reset_internal(struct bcm2835_host *host) { + u32 cdiv = host->cdiv; u32 temp; + if (!cdiv) + cdiv = readl(host->ioaddr + SDCDIV); writel(SDVDD_POWER_OFF, host->ioaddr + SDVDD); writel(0, host->ioaddr + SDCMD); writel(0, host->ioaddr + SDARG); @@ -263,9 +275,8 @@ static void bcm2835_reset_internal(struct bcm2835_host *host) msleep(20); writel(SDVDD_POWER_ON, host->ioaddr + SDVDD); msleep(20); - host->clock = 0; writel(host->hcfg, host->ioaddr + SDHCFG); - writel(host->cdiv, host->ioaddr + SDCDIV); + writel(cdiv, host->ioaddr + SDCDIV); } static void bcm2835_reset(struct mmc_host *mmc) @@ -596,6 +607,25 @@ static void bcm2835_finish_request(struct bcm2835_host *host) mrq = host->mrq; + /* Drop the overclock after any data corruption, or after any + * error while overclocked. Ignore errors for status commands, + * as they are likely when a card is ejected. + */ + if (host->overclock) { + if ((mrq->cmd && mrq->cmd->error && + (mrq->cmd->opcode != MMC_SEND_STATUS)) || + (mrq->data && mrq->data->error) || + (mrq->stop && mrq->stop->error) || + (mrq->sbc && mrq->sbc->error)) { + host->overclock_50--; + dev_warn(&host->pdev->dev, + "reducing overclock due to errors\n"); + host->reset_clock = 1; + mrq->cmd->error = -ETIMEDOUT; + mrq->cmd->retries = 1; + } + } + host->mrq = NULL; host->cmd = NULL; host->data = NULL; @@ -1092,8 +1122,13 @@ static void bcm2835_dma_complete_work(struct work_struct *work) static void bcm2835_set_clock(struct bcm2835_host *host, unsigned int clock) { struct mmc_host *mmc = mmc_from_priv(host); + const unsigned int input_clock = clock; + const unsigned int MHZ = 1000000; int div; + if (host->overclock_50 && (clock == 50*MHZ)) + clock = host->overclock_50 * MHZ + (MHZ - 1); + /* The SDCDIV register has 11 bits, and holds (div - 2). But * in data mode the max is 50MHz wihout a minimum, and only * the bottom 3 bits are used. Since the switch over is @@ -1115,38 +1150,78 @@ static void bcm2835_set_clock(struct bcm2835_host *host, unsigned int clock) * clock divisor at all times. */ - if (clock < 100000) { - /* Can't stop the clock, but make it as slow as possible - * to show willing - */ - host->cdiv = SDCDIV_MAX_CDIV; - writel(host->cdiv, host->ioaddr + SDCDIV); - return; - } + if (host->fw) { + u32 msg[3] = { clock, 0, 0 }; - div = host->max_clk / clock; - if (div < 2) - div = 2; - if ((host->max_clk / div) > clock) - div++; - div -= 2; + rpi_firmware_property(host->fw, + RPI_FIRMWARE_SET_SDHOST_CLOCK, + &msg, sizeof(msg)); - if (div > SDCDIV_MAX_CDIV) - div = SDCDIV_MAX_CDIV; + clock = max(msg[1], msg[2]); + host->cdiv = 0; + } else { + if (clock < 100000) { + /* Can't stop the clock, but make it as slow as possible + * to show willing + */ + host->cdiv = SDCDIV_MAX_CDIV; + writel(host->cdiv, host->ioaddr + SDCDIV); + return; + } - clock = host->max_clk / (div + 2); - mmc->actual_clock = clock; + div = host->max_clk / clock; + if (div < 2) + div = 2; + if ((host->max_clk / div) > clock) + div++; + div -= 2; + + if (div > SDCDIV_MAX_CDIV) + div = SDCDIV_MAX_CDIV; + + clock = host->max_clk / (div + 2); + + host->cdiv = div; + writel(host->cdiv, host->ioaddr + SDCDIV); + } /* Calibrate some delays */ host->ns_per_fifo_word = (1000000000 / clock) * ((mmc->caps & MMC_CAP_4_BIT_DATA) ? 8 : 32); - host->cdiv = div; - writel(host->cdiv, host->ioaddr + SDCDIV); + if (input_clock == 50 * MHZ) { + if (clock > input_clock) { + /* Save the closest value, to make it easier + * to reduce in the event of error + */ + host->overclock_50 = (clock/MHZ); + + if (clock != host->overclock) { + pr_info("%s: overclocking to %dHz\n", + mmc_hostname(mmc), clock); + host->overclock = clock; + } + } else if (host->overclock) { + host->overclock = 0; + if (clock == 50 * MHZ) + pr_warn("%s: cancelling overclock\n", + mmc_hostname(mmc)); + } + } else if (input_clock == 0) { + /* Reset the preferred overclock when the clock is stopped. + * This always happens during initialisation. + */ + host->overclock_50 = host->user_overclock_50; + host->overclock = 0; + } /* Set the timeout to 500ms */ - writel(mmc->actual_clock / 2, host->ioaddr + SDTOUT); + writel(clock / 2, host->ioaddr + SDTOUT); + + mmc->actual_clock = clock; + host->clock = input_clock; + host->reset_clock = 0; } static void bcm2835_request(struct mmc_host *mmc, struct mmc_request *mrq) @@ -1176,6 +1251,9 @@ static void bcm2835_request(struct mmc_host *mmc, struct mmc_request *mrq) return; } + if (host->reset_clock) + bcm2835_set_clock(host, host->clock); + mutex_lock(&host->mutex); WARN_ON(host->mrq); @@ -1199,7 +1277,7 @@ static void bcm2835_request(struct mmc_host *mmc, struct mmc_request *mrq) return; } - if (host->use_dma && mrq->data && (mrq->data->blocks > PIO_THRESHOLD)) + if (host->use_dma && mrq->data && (mrq->data->blocks > host->pio_limit)) bcm2835_prepare_dma(host, mrq->data); host->use_sbc = !!mrq->sbc && host->mrq->data && @@ -1334,8 +1412,8 @@ static int bcm2835_add_host(struct bcm2835_host *host) } pio_limit_string[0] = '\0'; - if (host->use_dma && (PIO_THRESHOLD > 0)) - sprintf(pio_limit_string, " (>%d)", PIO_THRESHOLD); + if (host->use_dma && (host->pio_limit > 0)) + sprintf(pio_limit_string, " (>%d)", host->pio_limit); dev_info(dev, "loaded - DMA %s%s\n", host->use_dma ? "enabled" : "disabled", pio_limit_string); @@ -1345,10 +1423,13 @@ static int bcm2835_add_host(struct bcm2835_host *host) static int bcm2835_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct clk *clk; + struct device_node *node = dev->of_node; struct bcm2835_host *host; + struct rpi_firmware *fw; + struct resource *iomem; + bool allow_dma = true; struct mmc_host *mmc; - const __be32 *regaddr_p; + struct clk *clk; int ret; dev_dbg(dev, "%s\n", __func__); @@ -1361,24 +1442,31 @@ static int bcm2835_probe(struct platform_device *pdev) host->pdev = pdev; spin_lock_init(&host->lock); - host->ioaddr = devm_platform_ioremap_resource(pdev, 0); + host->ioaddr = devm_platform_get_and_ioremap_resource(pdev, 0, &iomem); if (IS_ERR(host->ioaddr)) { ret = PTR_ERR(host->ioaddr); goto err; } - /* Parse OF address directly to get the physical address for - * DMA to our registers. - */ - regaddr_p = of_get_address(pdev->dev.of_node, 0, NULL, NULL); - if (!regaddr_p) { - dev_err(dev, "Can't get phys address\n"); - ret = -EINVAL; - goto err; + host->phys_addr = iomem->start; + host->pio_limit = PIO_THRESHOLD; + + if (node) { + /* Read any custom properties */ + of_property_read_u32(node, + "brcm,overclock-50", + &host->user_overclock_50); + of_property_read_u32(node, + "brcm,pio-limit", + &host->pio_limit); + allow_dma = + !of_property_read_bool(node, "brcm,force-pio"); + + /* Formally recognise the other way of disabling DMA */ + if (host->pio_limit == 0x7fffffff) + allow_dma = false; } - host->phys_addr = be32_to_cpup(regaddr_p); - host->dma_chan = NULL; host->dma_desc = NULL; @@ -1400,6 +1488,24 @@ static int bcm2835_probe(struct platform_device *pdev) goto err; } + fw = rpi_firmware_get( + of_parse_phandle(dev->of_node, "firmware", 0)); + if (fw) { + u32 msg[3]; + + msg[0] = 0; + msg[1] = ~0; + msg[2] = ~0; + + rpi_firmware_property(fw, RPI_FIRMWARE_SET_SDHOST_CLOCK, + &msg, sizeof(msg)); + + if (msg[1] != ~0) + host->fw = fw; + else + rpi_firmware_put(fw); + } + host->max_clk = clk_get_rate(clk); host->irq = platform_get_irq(pdev, 0); diff --git a/drivers/mmc/host/cqhci-core.c b/drivers/mmc/host/cqhci-core.c index 178277d90c31c5..78e80bc574eb83 100644 --- a/drivers/mmc/host/cqhci-core.c +++ b/drivers/mmc/host/cqhci-core.c @@ -388,9 +388,11 @@ static void cqhci_off(struct mmc_host *mmc) err = readx_poll_timeout(cqhci_read_ctl, cq_host, reg, reg & CQHCI_HALT, 0, CQHCI_OFF_TIMEOUT); - if (err < 0) + if (err < 0) { pr_err("%s: cqhci: CQE stuck on\n", mmc_hostname(mmc)); - else + /* eMMC v5.1 B.2.8 recommends writing 0 to CQHCI_CTL if stuck */ + cqhci_writel(cq_host, 0, CQHCI_CTL); + } else pr_debug("%s: cqhci: CQE off\n", mmc_hostname(mmc)); if (cq_host->ops->post_disable) @@ -980,8 +982,11 @@ static bool cqhci_halt(struct mmc_host *mmc, unsigned int timeout) ret = cqhci_halted(cq_host); - if (!ret) + if (!ret) { pr_warn("%s: cqhci: Failed to halt\n", mmc_hostname(mmc)); + /* eMMC v5.1 B.2.8 recommends writing 0 to CQHCI_CTL if stuck */ + cqhci_writel(cq_host, 0, CQHCI_CTL); + } return ret; } diff --git a/drivers/mmc/host/sdhci-brcmstb.c b/drivers/mmc/host/sdhci-brcmstb.c index 031a4b514d16bd..1f267c9336d467 100644 --- a/drivers/mmc/host/sdhci-brcmstb.c +++ b/drivers/mmc/host/sdhci-brcmstb.c @@ -12,6 +12,8 @@ #include <linux/of.h> #include <linux/bitops.h> #include <linux/delay.h> +#include <linux/pinctrl/consumer.h> +#include <linux/regulator/consumer.h> #include "sdhci-cqhci.h" #include "sdhci-pltfm.h" @@ -28,29 +30,38 @@ #define BRCMSTB_PRIV_FLAGS_HAS_CQE BIT(0) #define BRCMSTB_PRIV_FLAGS_GATE_CLOCK BIT(1) +#define BRCMSTB_PRIV_FLAGS_HAS_SD_EXPRESS BIT(2) #define SDHCI_ARASAN_CQE_BASE_ADDR 0x200 #define SDIO_CFG_CQ_CAPABILITY 0x4c -#define SDIO_CFG_CQ_CAPABILITY_FMUL GENMASK(13, 12) +#define SDIO_CFG_CQ_CAPABILITY_FMUL_SHIFT 12 #define SDIO_CFG_CTRL 0x0 #define SDIO_CFG_CTRL_SDCD_N_TEST_EN BIT(31) #define SDIO_CFG_CTRL_SDCD_N_TEST_LEV BIT(30) +#define SDIO_CFG_SD_PIN_SEL 0x44 +#define SDIO_CFG_SD_PIN_SEL_MASK 0x3 +#define SDIO_CFG_SD_PIN_SEL_SD BIT(1) +#define SDIO_CFG_SD_PIN_SEL_MMC BIT(0) + #define SDIO_CFG_MAX_50MHZ_MODE 0x1ac #define SDIO_CFG_MAX_50MHZ_MODE_STRAP_OVERRIDE BIT(31) #define SDIO_CFG_MAX_50MHZ_MODE_ENABLE BIT(0) -#define MMC_CAP_HSE_MASK (MMC_CAP2_HSX00_1_2V | MMC_CAP2_HSX00_1_8V) -/* Select all SD UHS type I SDR speed above 50MB/s */ -#define MMC_CAP_UHS_I_SDR_MASK (MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_SDR104) - struct sdhci_brcmstb_priv { void __iomem *cfg_regs; unsigned int flags; struct clk *base_clk; u32 base_freq_hz; + struct regulator *sde_1v8; + struct device_node *sde_pcie; + void *__iomem sde_ioaddr; + void *__iomem sde_ioaddr2; + struct pinctrl *pinctrl; + struct pinctrl_state *pins_default; + struct pinctrl_state *pins_sdex; }; struct brcmstb_match_priv { @@ -141,6 +152,42 @@ static void sdhci_brcmstb_hs400es(struct mmc_host *mmc, struct mmc_ios *ios) writel(reg, host->ioaddr + SDHCI_VENDOR); } +static void sdhci_bcm2712_set_clock(struct sdhci_host *host, unsigned int clock) +{ + u16 clk; + u32 reg; + bool is_emmc_rate = false; + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_brcmstb_priv *brcmstb_priv = sdhci_pltfm_priv(pltfm_host); + + host->mmc->actual_clock = 0; + + sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL); + + switch (host->mmc->ios.timing) { + case MMC_TIMING_MMC_HS400: + case MMC_TIMING_MMC_HS200: + case MMC_TIMING_MMC_DDR52: + case MMC_TIMING_MMC_HS: + is_emmc_rate = true; + break; + } + + reg = readl(brcmstb_priv->cfg_regs + SDIO_CFG_SD_PIN_SEL); + reg &= ~SDIO_CFG_SD_PIN_SEL_MASK; + if (is_emmc_rate) + reg |= SDIO_CFG_SD_PIN_SEL_MMC; + else + reg |= SDIO_CFG_SD_PIN_SEL_SD; + writel(reg, brcmstb_priv->cfg_regs + SDIO_CFG_SD_PIN_SEL); + + if (clock == 0) + return; + + clk = sdhci_calc_clk(host, clock, &host->mmc->actual_clock); + sdhci_enable_clk(host, clk); +} + static void sdhci_brcmstb_set_clock(struct sdhci_host *host, unsigned int clock) { u16 clk; @@ -156,6 +203,17 @@ static void sdhci_brcmstb_set_clock(struct sdhci_host *host, unsigned int clock) sdhci_enable_clk(host, clk); } +static void sdhci_brcmstb_set_power(struct sdhci_host *host, unsigned char mode, + unsigned short vdd) +{ + if (!IS_ERR(host->mmc->supply.vmmc)) { + struct mmc_host *mmc = host->mmc; + + mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, vdd); + } + sdhci_set_power_noreg(host, mode, vdd); +} + static void sdhci_brcmstb_set_uhs_signaling(struct sdhci_host *host, unsigned int timing) { @@ -185,21 +243,41 @@ static void sdhci_brcmstb_set_uhs_signaling(struct sdhci_host *host, sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2); } +static void sdhci_bcm2712_hs400_downgrade(struct mmc_host *mmc) +{ + struct sdhci_host *host = mmc_priv(mmc); + /* + * The eMMC PHY and its internal controller parses and validates + * the uhs_mode, divisor, pin_sel, and sampling clock select + * output from the SD controller. It will refuse to update its + * config if HS timings are selected while the clock is >52MHz. + * so bump the clock down now before card/controller setup is + * performed. + */ + sdhci_bcm2712_set_clock(host, 52000000); +} + static void sdhci_brcmstb_cfginit_2712(struct sdhci_host *host) { struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct sdhci_brcmstb_priv *brcmstb_priv = sdhci_pltfm_priv(pltfm_host); u32 reg; + u32 uhs_mask = (MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_SDR104); + u32 hsemmc_mask = (MMC_CAP2_HS200_1_8V_SDR | MMC_CAP2_HS200_1_2V_SDR | + MMC_CAP2_HS400_1_8V | MMC_CAP2_HS400_1_2V); + u32 base_clk_mhz; /* * If we support a speed that requires tuning, * then select the delay line PHY as the clock source. */ - if ((host->mmc->caps & MMC_CAP_UHS_I_SDR_MASK) || (host->mmc->caps2 & MMC_CAP_HSE_MASK)) { + if ((host->mmc->caps & uhs_mask) || (host->mmc->caps2 & hsemmc_mask)) { reg = readl(brcmstb_priv->cfg_regs + SDIO_CFG_MAX_50MHZ_MODE); reg &= ~SDIO_CFG_MAX_50MHZ_MODE_ENABLE; reg |= SDIO_CFG_MAX_50MHZ_MODE_STRAP_OVERRIDE; writel(reg, brcmstb_priv->cfg_regs + SDIO_CFG_MAX_50MHZ_MODE); + + host->mmc_host_ops.hs400_downgrade = sdhci_bcm2712_hs400_downgrade; } if ((host->mmc->caps & MMC_CAP_NONREMOVABLE) || @@ -210,6 +288,109 @@ static void sdhci_brcmstb_cfginit_2712(struct sdhci_host *host) reg |= SDIO_CFG_CTRL_SDCD_N_TEST_EN; writel(reg, brcmstb_priv->cfg_regs + SDIO_CFG_CTRL); } + + /* Guesstimate the timer frequency (controller base clock) */ + base_clk_mhz = max_t(u32, clk_get_rate(pltfm_host->clk) / (1000 * 1000), 1); + reg = (3 << SDIO_CFG_CQ_CAPABILITY_FMUL_SHIFT) | base_clk_mhz; + writel(reg, brcmstb_priv->cfg_regs + SDIO_CFG_CQ_CAPABILITY); +} + +static int bcm2712_init_sd_express(struct sdhci_host *host, struct mmc_ios *ios) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_brcmstb_priv *brcmstb_priv = sdhci_pltfm_priv(pltfm_host); + struct device *dev = host->mmc->parent; + u32 ctrl_val; + u32 present_state; + int ret; + + if (!brcmstb_priv->sde_ioaddr || !brcmstb_priv->sde_ioaddr2) + return -EINVAL; + + if (!brcmstb_priv->pinctrl) + return -EINVAL; + + /* Turn off the SD clock first */ + sdhci_set_clock(host, 0); + + /* Disable SD DAT0-3 pulls */ + pinctrl_select_state(brcmstb_priv->pinctrl, brcmstb_priv->pins_sdex); + + ctrl_val = readl(brcmstb_priv->sde_ioaddr); + dev_dbg(dev, "ctrl_val 1 %08x\n", ctrl_val); + + /* Tri-state the SD pins */ + ctrl_val |= 0x1ff8; + writel(ctrl_val, brcmstb_priv->sde_ioaddr); + dev_dbg(dev, "ctrl_val 1->%08x (%08x)\n", ctrl_val, readl(brcmstb_priv->sde_ioaddr)); + /* Let voltages settle */ + udelay(100); + + /* Enable the PCIe sideband pins */ + ctrl_val &= ~0x6000; + writel(ctrl_val, brcmstb_priv->sde_ioaddr); + dev_dbg(dev, "ctrl_val 1->%08x (%08x)\n", ctrl_val, readl(brcmstb_priv->sde_ioaddr)); + /* Let voltages settle */ + udelay(100); + + /* Turn on the 1v8 VDD2 regulator */ + ret = regulator_enable(brcmstb_priv->sde_1v8); + if (ret) + return ret; + + /* Wait for Tpvcrl */ + msleep(1); + + /* Sample DAT2 (CLKREQ#) - if low, card is in PCIe mode */ + present_state = sdhci_readl(host, SDHCI_PRESENT_STATE); + present_state = (present_state & SDHCI_DATA_LVL_MASK) >> SDHCI_DATA_LVL_SHIFT; + dev_dbg(dev, "state = 0x%08x\n", present_state); + + if (present_state & BIT(2)) { + dev_err(dev, "DAT2 still high, abandoning SDex switch\n"); + return -ENODEV; + } + + /* Turn on the LCPLL PTEST mux */ + ctrl_val = readl(brcmstb_priv->sde_ioaddr2 + 20); // misc5 + ctrl_val &= ~(0x7 << 7); + ctrl_val |= 3 << 7; + writel(ctrl_val, brcmstb_priv->sde_ioaddr2 + 20); + dev_dbg(dev, "misc 5->%08x (%08x)\n", ctrl_val, readl(brcmstb_priv->sde_ioaddr2 + 20)); + + /* PTEST diff driver enable */ + ctrl_val = readl(brcmstb_priv->sde_ioaddr2); + ctrl_val |= BIT(21); + writel(ctrl_val, brcmstb_priv->sde_ioaddr2); + + dev_dbg(dev, "misc 0->%08x (%08x)\n", ctrl_val, readl(brcmstb_priv->sde_ioaddr2)); + + /* Wait for more than the minimum Tpvpgl time */ + msleep(100); + + if (brcmstb_priv->sde_pcie) { + struct of_changeset changeset; + static struct property okay_property = { + .name = "status", + .value = "okay", + .length = 5, + }; + + /* Enable the pcie controller */ + of_changeset_init(&changeset); + ret = of_changeset_update_property(&changeset, + brcmstb_priv->sde_pcie, + &okay_property); + if (ret) { + dev_err(dev, "%s: failed to update property - %d\n", __func__, + ret); + return -ENODEV; + } + ret = of_changeset_apply(&changeset); + } + + dev_dbg(dev, "%s -> %d\n", __func__, ret); + return ret; } static void sdhci_brcmstb_dumpregs(struct mmc_host *mmc) @@ -220,6 +401,7 @@ static void sdhci_brcmstb_dumpregs(struct mmc_host *mmc) static void sdhci_brcmstb_cqe_enable(struct mmc_host *mmc) { struct sdhci_host *host = mmc_priv(mmc); + struct cqhci_host *cq_host = mmc->cqe_private; u32 reg; reg = sdhci_readl(host, SDHCI_PRESENT_STATE); @@ -229,6 +411,22 @@ static void sdhci_brcmstb_cqe_enable(struct mmc_host *mmc) } sdhci_cqe_enable(mmc); + + /* + * The controller resets this register to a very short default interval + * whenever CQHCI is disabled. + * + * For removable cards CBC needs to be clear or card removal can hang + * the CQE. In polling mode, a CIT of 0x4000 "cycles" seems to produce the best + * throughput. + * + * For nonremovable cards, the specification default of CBC=1 CIT=0x1000 + * suffices. + */ + if (mmc->caps & MMC_CAP_NONREMOVABLE) + cqhci_writel(cq_host, 0x00011000, CQHCI_SSC1); + else + cqhci_writel(cq_host, 0x00004000, CQHCI_SSC1); } static const struct cqhci_host_ops sdhci_brcmstb_cqhci_ops = { @@ -245,11 +443,12 @@ static struct sdhci_ops sdhci_brcmstb_ops = { }; static struct sdhci_ops sdhci_brcmstb_ops_2712 = { - .set_clock = sdhci_set_clock, - .set_power = sdhci_set_power_and_bus_voltage, + .set_clock = sdhci_bcm2712_set_clock, + .set_power = sdhci_brcmstb_set_power, .set_bus_width = sdhci_set_bus_width, - .reset = sdhci_reset, + .reset = brcmstb_reset, .set_uhs_signaling = sdhci_set_uhs_signaling, + .init_sd_express = bcm2712_init_sd_express, }; static struct sdhci_ops sdhci_brcmstb_ops_7216 = { @@ -267,6 +466,8 @@ static struct sdhci_ops sdhci_brcmstb_ops_74165b0 = { }; static const struct brcmstb_match_priv match_priv_2712 = { + .flags = BRCMSTB_MATCH_FLAGS_USE_CARD_BUSY, + .hs400es = sdhci_brcmstb_hs400es, .cfginit = sdhci_brcmstb_cfginit_2712, .ops = &sdhci_brcmstb_ops_2712, }; @@ -370,8 +571,10 @@ static int sdhci_brcmstb_probe(struct platform_device *pdev) struct sdhci_pltfm_host *pltfm_host; const struct of_device_id *match; struct sdhci_brcmstb_priv *priv; - u32 actual_clock_mhz; + u32 actual_clock_mhz, cqe; struct sdhci_host *host; + struct resource *iomem; + bool no_pinctrl = false; struct clk *clk; struct clk *base_clk = NULL; int res; @@ -394,12 +597,21 @@ static int sdhci_brcmstb_probe(struct platform_device *pdev) return PTR_ERR(host); pltfm_host = sdhci_priv(host); + pltfm_host->clk = clk; + priv = sdhci_pltfm_priv(pltfm_host); - if (device_property_read_bool(&pdev->dev, "supports-cqe")) { + cqe = 0; + device_property_read_u32(&pdev->dev, "supports-cqe", &cqe); + if (cqe > 0) { priv->flags |= BRCMSTB_PRIV_FLAGS_HAS_CQE; match_priv->ops->irq = sdhci_brcmstb_cqhci_irq; } + priv->sde_pcie = of_parse_phandle(pdev->dev.of_node, + "sde-pcie", 0); + if (priv->sde_pcie) + priv->flags |= BRCMSTB_PRIV_FLAGS_HAS_SD_EXPRESS; + /* Map in the non-standard CFG registers */ priv->cfg_regs = devm_platform_get_and_ioremap_resource(pdev, 1, NULL); if (IS_ERR(priv->cfg_regs)) { @@ -412,6 +624,43 @@ static int sdhci_brcmstb_probe(struct platform_device *pdev) if (res) goto err; + priv->sde_1v8 = devm_regulator_get_optional(&pdev->dev, "sde-1v8"); + if (IS_ERR(priv->sde_1v8)) + priv->flags &= ~BRCMSTB_PRIV_FLAGS_HAS_SD_EXPRESS; + + iomem = platform_get_resource(pdev, IORESOURCE_MEM, 2); + if (iomem) { + priv->sde_ioaddr = devm_ioremap_resource(&pdev->dev, iomem); + if (IS_ERR(priv->sde_ioaddr)) + priv->sde_ioaddr = NULL; + } + + iomem = platform_get_resource(pdev, IORESOURCE_MEM, 3); + if (iomem) { + priv->sde_ioaddr2 = devm_ioremap_resource(&pdev->dev, iomem); + if (IS_ERR(priv->sde_ioaddr2)) + priv->sde_ioaddr = NULL; + } + + priv->pinctrl = devm_pinctrl_get(&pdev->dev); + if (IS_ERR(priv->pinctrl)) { + no_pinctrl = true; + } + priv->pins_default = pinctrl_lookup_state(priv->pinctrl, "default"); + if (IS_ERR(priv->pins_default)) { + dev_dbg(&pdev->dev, "No pinctrl default state\n"); + no_pinctrl = true; + } + priv->pins_sdex = pinctrl_lookup_state(priv->pinctrl, "sd-express"); + if (IS_ERR(priv->pins_sdex)) { + dev_dbg(&pdev->dev, "No pinctrl sd-express state\n"); + no_pinctrl = true; + } + if (no_pinctrl || !priv->sde_ioaddr || !priv->sde_ioaddr2) { + priv->pinctrl = NULL; + priv->flags &= ~BRCMSTB_PRIV_FLAGS_HAS_SD_EXPRESS; + } + /* * Automatic clock gating does not work for SD cards that may * voltage switch so only enable it for non-removable devices. @@ -428,6 +677,10 @@ static int sdhci_brcmstb_probe(struct platform_device *pdev) (host->mmc->caps2 & MMC_CAP2_HS400_ES)) host->mmc_host_ops.hs400_enhanced_strobe = match_priv->hs400es; + if (host->ops->init_sd_express && + (priv->flags & BRCMSTB_PRIV_FLAGS_HAS_SD_EXPRESS)) + host->mmc->caps2 |= MMC_CAP2_SD_EXP; + if (match_priv->cfginit) match_priv->cfginit(host); @@ -481,7 +734,6 @@ static int sdhci_brcmstb_probe(struct platform_device *pdev) if (res) goto err; - pltfm_host->clk = clk; return res; err: diff --git a/drivers/mmc/host/sdhci-iproc.c b/drivers/mmc/host/sdhci-iproc.c index 10235fdff246f6..f014437cb911c6 100644 --- a/drivers/mmc/host/sdhci-iproc.c +++ b/drivers/mmc/host/sdhci-iproc.c @@ -198,6 +198,7 @@ static const struct sdhci_ops sdhci_iproc_32only_ops = { .write_b = sdhci_iproc_writeb, .set_clock = sdhci_set_clock, .get_max_clock = sdhci_iproc_get_max_clock, + .set_power = sdhci_set_power_and_bus_voltage, .set_bus_width = sdhci_set_bus_width, .reset = sdhci_reset, .set_uhs_signaling = sdhci_set_uhs_signaling, diff --git a/drivers/mmc/host/sdhci-of-dwcmshc.c b/drivers/mmc/host/sdhci-of-dwcmshc.c index 8fd80dac11bfdf..e5b89c96641f9a 100644 --- a/drivers/mmc/host/sdhci-of-dwcmshc.c +++ b/drivers/mmc/host/sdhci-of-dwcmshc.c @@ -220,6 +220,7 @@ struct rk35xx_priv { struct dwcmshc_priv { struct clk *bus_clk; + struct clk *sdio_clk; int vendor_specific_area1; /* P_VENDOR_SPECIFIC_AREA1 reg */ int vendor_specific_area2; /* P_VENDOR_SPECIFIC_AREA2 reg */ @@ -288,6 +289,17 @@ static void dwcmshc_adma_write_desc(struct sdhci_host *host, void **desc, sdhci_adma_write_desc(host, desc, addr, len, cmd); } +static void dwcmshc_set_clock(struct sdhci_host *host, unsigned int clock) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct dwcmshc_priv *priv = sdhci_pltfm_priv(pltfm_host); + + if (priv->sdio_clk) + clk_set_rate(priv->sdio_clk, clock); + + sdhci_set_clock(host, clock); +} + static unsigned int dwcmshc_get_max_clock(struct sdhci_host *host) { struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); @@ -1114,10 +1126,11 @@ static int sg2042_init(struct device *dev, struct sdhci_host *host, } static const struct sdhci_ops sdhci_dwcmshc_ops = { - .set_clock = sdhci_set_clock, + .set_clock = dwcmshc_set_clock, .set_bus_width = sdhci_set_bus_width, .set_uhs_signaling = dwcmshc_set_uhs_signaling, .get_max_clock = dwcmshc_get_max_clock, + .get_timeout_clock = sdhci_pltfm_clk_get_timeout_clock, .reset = sdhci_reset, .adma_write_desc = dwcmshc_adma_write_desc, .irq = dwcmshc_cqe_irq_handler, @@ -1190,8 +1203,10 @@ static const struct sdhci_ops sdhci_dwcmshc_sg2042_ops = { static const struct dwcmshc_pltfm_data sdhci_dwcmshc_pdata = { .pdata = { .ops = &sdhci_dwcmshc_ops, - .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN, - .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN, + .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN | + SDHCI_QUIRK_BROKEN_CARD_DETECTION, + .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN | + SDHCI_QUIRK2_BROKEN_HS200, }, }; @@ -1206,13 +1221,26 @@ static const struct dwcmshc_pltfm_data sdhci_dwcmshc_bf3_pdata = { }; #endif +static const struct sdhci_pltfm_data sdhci_dwcmshc_rp1_pdata = { + .ops = &sdhci_dwcmshc_ops, + .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN | + SDHCI_QUIRK_BROKEN_CARD_DETECTION, + .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN | + SDHCI_QUIRK2_BROKEN_HS200 | + SDHCI_QUIRK2_SPURIOUS_INT_RESP, +}; + static const struct dwcmshc_pltfm_data sdhci_dwcmshc_rk35xx_pdata = { .pdata = { .ops = &sdhci_dwcmshc_rk35xx_ops, .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN | SDHCI_QUIRK_BROKEN_TIMEOUT_VAL, .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN | - SDHCI_QUIRK2_CLOCK_DIV_ZERO_BROKEN, + SDHCI_QUIRK2_CLOCK_DIV_ZERO_BROKEN | + SDHCI_QUIRK2_NO_SDR50 | + SDHCI_QUIRK2_NO_SDR104 | + SDHCI_QUIRK2_NO_SDR25, + }, .init = dwcmshc_rk35xx_init, .postinit = dwcmshc_rk35xx_postinit, @@ -1312,6 +1340,10 @@ static void dwcmshc_cqhci_init(struct sdhci_host *host, struct platform_device * } static const struct of_device_id sdhci_dwcmshc_dt_ids[] = { + { + .compatible = "raspberrypi,rp1-dwcmshc", + .data = &sdhci_dwcmshc_rp1_pdata, + }, { .compatible = "rockchip,rk3588-dwcmshc", .data = &sdhci_dwcmshc_rk35xx_pdata, @@ -1401,13 +1433,32 @@ static int dwcmshc_probe(struct platform_device *pdev) priv->bus_clk = devm_clk_get(dev, "bus"); if (!IS_ERR(priv->bus_clk)) clk_prepare_enable(priv->bus_clk); + + pltfm_host->timeout_clk = devm_clk_get(dev, "timeout"); + if (!IS_ERR(pltfm_host->timeout_clk)) + err = clk_prepare_enable(pltfm_host->timeout_clk); + if (err) + goto free_pltfm; + + priv->sdio_clk = devm_clk_get_optional(&pdev->dev, "sdio"); + } + + pltfm_host->timeout_clk = devm_clk_get(&pdev->dev, "timeout"); + if (IS_ERR(pltfm_host->timeout_clk)) { + err = PTR_ERR(pltfm_host->timeout_clk); + dev_err(&pdev->dev, "failed to get timeout clk: %d\n", err); + goto free_pltfm; } + err = clk_prepare_enable(pltfm_host->timeout_clk); + if (err) + goto free_pltfm; err = mmc_of_parse(host->mmc); if (err) goto err_clk; sdhci_get_of_property(pdev); + sdhci_enable_v4_mode(host); priv->vendor_specific_area1 = sdhci_readl(host, DWCMSHC_P_VENDOR_AREA1) & DWCMSHC_AREA1_MASK; @@ -1467,6 +1518,7 @@ static int dwcmshc_probe(struct platform_device *pdev) pm_runtime_put_noidle(dev); err_clk: clk_disable_unprepare(pltfm_host->clk); + clk_disable_unprepare(pltfm_host->timeout_clk); clk_disable_unprepare(priv->bus_clk); clk_bulk_disable_unprepare(priv->num_other_clks, priv->other_clks); free_pltfm: diff --git a/drivers/mmc/host/sdhci-pltfm.c b/drivers/mmc/host/sdhci-pltfm.c index 62753d72198ab2..6035afd36c171c 100644 --- a/drivers/mmc/host/sdhci-pltfm.c +++ b/drivers/mmc/host/sdhci-pltfm.c @@ -32,6 +32,14 @@ unsigned int sdhci_pltfm_clk_get_max_clock(struct sdhci_host *host) } EXPORT_SYMBOL_GPL(sdhci_pltfm_clk_get_max_clock); +unsigned int sdhci_pltfm_clk_get_timeout_clock(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + + return clk_get_rate(pltfm_host->timeout_clk); +} +EXPORT_SYMBOL_GPL(sdhci_pltfm_clk_get_timeout_clock); + static const struct sdhci_ops sdhci_pltfm_ops = { .set_clock = sdhci_set_clock, .set_bus_width = sdhci_set_bus_width, diff --git a/drivers/mmc/host/sdhci-pltfm.h b/drivers/mmc/host/sdhci-pltfm.h index b81d5b0fd6161a..4eb77191c421fb 100644 --- a/drivers/mmc/host/sdhci-pltfm.h +++ b/drivers/mmc/host/sdhci-pltfm.h @@ -20,6 +20,7 @@ struct sdhci_pltfm_data { struct sdhci_pltfm_host { struct clk *clk; + struct clk *timeout_clk; /* migrate from sdhci_of_host */ unsigned int clock; @@ -106,6 +107,8 @@ extern void sdhci_pltfm_remove(struct platform_device *pdev); extern unsigned int sdhci_pltfm_clk_get_max_clock(struct sdhci_host *host); +extern unsigned int sdhci_pltfm_clk_get_timeout_clock(struct sdhci_host *host); + static inline void *sdhci_pltfm_priv(struct sdhci_pltfm_host *host) { return host->private; diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 4b91c9e9663575..9bb360efe5b857 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -40,7 +40,7 @@ pr_debug("%s: " DRIVER_NAME ": " f, mmc_hostname(host->mmc), ## x) #define SDHCI_DUMP(f, x...) \ - pr_err("%s: " DRIVER_NAME ": " f, mmc_hostname(host->mmc), ## x) + pr_debug("%s: " DRIVER_NAME ": " f, mmc_hostname(host->mmc), ## x) #define MAX_TUNING_LOOP 40 @@ -1081,7 +1081,7 @@ static void sdhci_initialize_data(struct sdhci_host *host, WARN_ON(host->data); /* Sanity checks */ - BUG_ON(data->blksz * data->blocks > 524288); + BUG_ON(data->blksz * data->blocks > host->mmc->max_req_size); BUG_ON(data->blksz > host->mmc->max_blk_size); BUG_ON(data->blocks > 65535); @@ -1713,6 +1713,12 @@ static bool sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) if (host->use_external_dma) sdhci_external_dma_pre_transfer(host, cmd); + if (host->quirks2 & SDHCI_QUIRK2_SPURIOUS_INT_RESP) { + host->ier |= SDHCI_INT_RESPONSE; + sdhci_writel(host, host->ier, SDHCI_INT_ENABLE); + sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE); + } + sdhci_writew(host, SDHCI_MAKE_CMD(cmd->opcode, flags), SDHCI_COMMAND); return true; @@ -3032,6 +3038,15 @@ static void sdhci_card_event(struct mmc_host *mmc) spin_unlock_irqrestore(&host->lock, flags); } +static int sdhci_init_sd_express(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct sdhci_host *host = mmc_priv(mmc); + + if (!host->ops->init_sd_express) + return -EOPNOTSUPP; + return host->ops->init_sd_express(host, ios); +} + static const struct mmc_host_ops sdhci_ops = { .request = sdhci_request, .post_req = sdhci_post_req, @@ -3047,6 +3062,7 @@ static const struct mmc_host_ops sdhci_ops = { .execute_tuning = sdhci_execute_tuning, .card_event = sdhci_card_event, .card_busy = sdhci_card_busy, + .init_sd_express = sdhci_init_sd_express, }; /*****************************************************************************\ @@ -3194,7 +3210,7 @@ static void sdhci_timeout_timer(struct timer_list *t) spin_lock_irqsave(&host->lock, flags); if (host->cmd && !sdhci_data_line_cmd(host->cmd)) { - pr_err("%s: Timeout waiting for hardware cmd interrupt.\n", + pr_debug("%s: Timeout waiting for hardware cmd interrupt.\n", mmc_hostname(host->mmc)); sdhci_err_stats_inc(host, REQ_TIMEOUT); sdhci_dumpregs(host); @@ -3217,7 +3233,7 @@ static void sdhci_timeout_data_timer(struct timer_list *t) if (host->data || host->data_cmd || (host->cmd && sdhci_data_line_cmd(host->cmd))) { - pr_err("%s: Timeout waiting for hardware interrupt.\n", + pr_debug("%s: Timeout waiting for hardware interrupt.\n", mmc_hostname(host->mmc)); sdhci_err_stats_inc(host, REQ_TIMEOUT); sdhci_dumpregs(host); @@ -3281,6 +3297,11 @@ static void sdhci_cmd_irq(struct sdhci_host *host, u32 intmask, u32 *intmask_p) if (intmask & SDHCI_INT_TIMEOUT) { host->cmd->error = -ETIMEDOUT; sdhci_err_stats_inc(host, CMD_TIMEOUT); + if (host->quirks2 & SDHCI_QUIRK2_SPURIOUS_INT_RESP) { + host->ier &= ~SDHCI_INT_RESPONSE; + sdhci_writel(host, host->ier, SDHCI_INT_ENABLE); + sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE); + } } else { host->cmd->error = -EILSEQ; if (!mmc_op_tuning(host->cmd->opcode)) @@ -4565,6 +4586,15 @@ int sdhci_setup_host(struct sdhci_host *host) !(host->quirks2 & SDHCI_QUIRK2_BROKEN_DDR50)) mmc->caps |= MMC_CAP_UHS_DDR50; + if (host->quirks2 & SDHCI_QUIRK2_NO_SDR25) + mmc->caps &= ~MMC_CAP_UHS_SDR25; + + if (host->quirks2 & SDHCI_QUIRK2_NO_SDR50) + mmc->caps &= ~MMC_CAP_UHS_SDR50; + + if (host->quirks2 & SDHCI_QUIRK2_NO_SDR104) + mmc->caps &= ~MMC_CAP_UHS_SDR104; + /* Does the host need tuning for SDR50? */ if (host->caps1 & SDHCI_USE_SDR50_TUNING) host->flags |= SDHCI_SDR50_NEEDS_TUNING; @@ -4679,11 +4709,16 @@ int sdhci_setup_host(struct sdhci_host *host) spin_lock_init(&host->lock); /* - * Maximum number of sectors in one transfer. Limited by SDMA boundary - * size (512KiB). Note some tuning modes impose a 4MiB limit, but this - * is less anyway. + * Maximum number of sectors in one transfer. + * 4MiB is preferred for multi-descriptor DMA as a) card sequential + * write speeds are only guaranteed with a 4MiB write length and + * b) most tuning modes require a 4MiB limit. + * SDMA has a 512KiB boundary size. */ - mmc->max_req_size = 524288; + if (host->flags & SDHCI_USE_ADMA) + mmc->max_req_size = SZ_4M; + else + mmc->max_req_size = SZ_512K; /* * Maximum number of segments. Depends on if the hardware diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index f531b617f28d77..34a3bedb827578 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -485,6 +485,14 @@ struct sdhci_host { /* Issue CMD and DATA reset together */ #define SDHCI_QUIRK2_ISSUE_CMD_DAT_RESET_TOGETHER (1<<19) +/* Quirks to ignore a speed if a that speed is unreliable */ +#define SDHCI_QUIRK2_NO_SDR25 (1<<19) +#define SDHCI_QUIRK2_NO_SDR50 (1<<20) +#define SDHCI_QUIRK2_NO_SDR104 (1<<21) + +/* Command timeouts may generate a trailing INT_RESPONSE later */ +#define SDHCI_QUIRK2_SPURIOUS_INT_RESP (1<<31) + int irq; /* Device IRQ */ void __iomem *ioaddr; /* Mapped address */ phys_addr_t mapbase; /* physical address base */ @@ -667,6 +675,7 @@ struct sdhci_ops { void (*request_done)(struct sdhci_host *host, struct mmc_request *mrq); void (*dump_vendor_regs)(struct sdhci_host *host); + int (*init_sd_express)(struct sdhci_host *host, struct mmc_ios *ios); }; #ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c index f7be886570d887..d49a3e06b9e3e0 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c @@ -67,6 +67,12 @@ /* Forward declarations */ static void bcmgenet_set_rx_mode(struct net_device *dev); +static bool skip_umac_reset = false; +module_param(skip_umac_reset, bool, 0444); +MODULE_PARM_DESC(skip_umac_reset, "Skip UMAC reset step"); +static bool eee = true; +module_param(eee, bool, 0444); +MODULE_PARM_DESC(eee, "Enable EEE (default Y)"); static inline void bcmgenet_writel(u32 value, void __iomem *offset) { @@ -2493,6 +2499,11 @@ static void reset_umac(struct bcmgenet_priv *priv) bcmgenet_rbuf_ctrl_set(priv, 0); udelay(10); + if (skip_umac_reset) { + pr_warn("Skipping UMAC reset\n"); + return; + } + /* issue soft reset and disable MAC while updating its registers */ spin_lock_bh(&priv->reg_lock); bcmgenet_umac_writel(priv, CMD_SW_RESET, UMAC_CMD); @@ -2664,7 +2675,7 @@ static void bcmgenet_init_tx_ring(struct bcmgenet_priv *priv, bcmgenet_tdma_ring_writel(priv, index, 0, TDMA_PROD_INDEX); bcmgenet_tdma_ring_writel(priv, index, 0, TDMA_CONS_INDEX); - bcmgenet_tdma_ring_writel(priv, index, 1, DMA_MBUF_DONE_THRESH); + bcmgenet_tdma_ring_writel(priv, index, 10, DMA_MBUF_DONE_THRESH); /* Disable rate control for now */ bcmgenet_tdma_ring_writel(priv, index, flow_period_val, TDMA_FLOW_PERIOD); @@ -3422,6 +3433,17 @@ static int bcmgenet_open(struct net_device *dev) bcmgenet_phy_pause_set(dev, priv->rx_pause, priv->tx_pause); + if (!eee) { + struct ethtool_keee eee_data; + + ret = bcmgenet_get_eee(dev, &eee_data); + if (ret == 0) { + eee_data.eee_enabled = 0; + bcmgenet_set_eee(dev, &eee_data); + netdev_warn(dev, "EEE disabled\n"); + } + } + bcmgenet_netif_start(dev); netif_tx_start_all_queues(dev); @@ -4139,9 +4161,12 @@ static int bcmgenet_probe(struct platform_device *pdev) netif_set_real_num_rx_queues(priv->dev, priv->hw_params->rx_queues + 1); /* Set default coalescing parameters */ - for (i = 0; i < priv->hw_params->rx_queues; i++) + for (i = 0; i < priv->hw_params->rx_queues; i++) { priv->rx_rings[i].rx_max_coalesced_frames = 1; + priv->rx_rings[i].rx_coalesce_usecs = 50; + } priv->rx_rings[DESC_INDEX].rx_max_coalesced_frames = 1; + priv->rx_rings[DESC_INDEX].rx_coalesce_usecs = 50; /* libphy will determine the link state */ netif_carrier_off(dev); diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.h b/drivers/net/ethernet/broadcom/genet/bcmgenet.h index 43b923c48b14f4..0429cd78c5b4b9 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.h +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.h @@ -31,7 +31,7 @@ #define ENET_PAD 8 #define ENET_MAX_MTU_SIZE (ETH_DATA_LEN + ETH_HLEN + VLAN_HLEN + \ ENET_BRCM_TAG_LEN + ETH_FCS_LEN + ENET_PAD) -#define DMA_MAX_BURST_LENGTH 0x10 +#define DMA_MAX_BURST_LENGTH 0x08 /* misc. configuration */ #define MAX_NUM_OF_FS_RULES 16 diff --git a/drivers/net/ethernet/broadcom/genet/bcmmii.c b/drivers/net/ethernet/broadcom/genet/bcmmii.c index c4a3698cef66f6..56398027972adb 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmmii.c +++ b/drivers/net/ethernet/broadcom/genet/bcmmii.c @@ -304,14 +304,14 @@ int bcmgenet_mii_probe(struct net_device *dev) struct device_node *dn = kdev->of_node; phy_interface_t phy_iface = priv->phy_interface; struct phy_device *phydev; - u32 phy_flags = PHY_BRCM_AUTO_PWRDWN_ENABLE | - PHY_BRCM_DIS_TXCRXC_NOENRGY | - PHY_BRCM_IDDQ_SUSPEND; + u32 phy_flags = 0; int ret; /* Communicate the integrated PHY revision */ if (priv->internal_phy) phy_flags = priv->gphy_rev; + else + phy_flags = PHY_BRCM_AUTO_PWRDWN_ENABLE; /* This is an ugly quirk but we have not been correctly interpreting * the phy_interface values and we have done that across different diff --git a/drivers/net/ethernet/cadence/macb.h b/drivers/net/ethernet/cadence/macb.h index 5740c98d8c9f03..8e0143f71aa472 100644 --- a/drivers/net/ethernet/cadence/macb.h +++ b/drivers/net/ethernet/cadence/macb.h @@ -86,6 +86,8 @@ #define GEM_PBUFRXCUT 0x0044 /* RX Partial Store and Forward */ #define GEM_JML 0x0048 /* Jumbo Max Length */ #define GEM_HS_MAC_CONFIG 0x0050 /* GEM high speed config */ +#define GEM_AMP 0x0054 /* AXI Max Pipeline */ +#define GEM_INTMOD 0x005c /* Interrupt moderation */ #define GEM_HRB 0x0080 /* Hash Bottom */ #define GEM_HRT 0x0084 /* Hash Top */ #define GEM_SA1B 0x0088 /* Specific1 Bottom */ @@ -348,6 +350,21 @@ #define GEM_ADDR64_OFFSET 30 /* Address bus width - 64b or 32b */ #define GEM_ADDR64_SIZE 1 +/* Bitfields in AMP */ +#define GEM_AR2R_MAX_PIPE_OFFSET 0 /* Maximum number of outstanding AXI read requests */ +#define GEM_AR2R_MAX_PIPE_SIZE 8 +#define GEM_AW2W_MAX_PIPE_OFFSET 8 /* Maximum number of outstanding AXI write requests */ +#define GEM_AW2W_MAX_PIPE_SIZE 8 +#define GEM_AW2B_FILL_OFFSET 16 /* Select wether the max AW2W transactions operates between: */ +#define GEM_AW2B_FILL_AW2W 0 /* 0: the AW to W AXI channel */ +#define GEM_AW2B_FILL_AW2B 1 /* 1: AW to B channel */ +#define GEM_AW2B_FILL_SIZE 1 + +/* Bitfields in INTMOD */ +#define GEM_RX_MODERATION_OFFSET 0 /* RX interrupt moderation */ +#define GEM_RX_MODERATION_SIZE 8 +#define GEM_TX_MODERATION_OFFSET 16 /* TX interrupt moderation */ +#define GEM_TX_MODERATION_SIZE 8 /* Bitfields in PBUFRXCUT */ #define GEM_ENCUTTHRU_OFFSET 31 /* Enable RX partial store and forward */ @@ -813,6 +830,7 @@ }) #define MACB_READ_NSR(bp) macb_readl(bp, NSR) +#define MACB_READ_TSR(bp) macb_readl(bp, TSR) /* struct macb_dma_desc - Hardware DMA descriptor * @addr: DMA address of data buffer @@ -1229,6 +1247,7 @@ struct macb_queue { dma_addr_t tx_ring_dma; struct work_struct tx_error_task; bool txubr_pending; + bool tx_pending; struct napi_struct napi_tx; dma_addr_t rx_ring_dma; @@ -1294,9 +1313,15 @@ struct macb { u32 caps; unsigned int dma_burst_length; + u8 aw2w_max_pipe; + u8 ar2r_max_pipe; + bool use_aw2b_fill; phy_interface_t phy_interface; + struct gpio_desc *phy_reset_gpio; + int phy_reset_ms; + /* AT91RM9200 transmit queue (1 on wire + 1 queued) */ struct macb_tx_skb rm9200_txq[2]; unsigned int max_tx_length; diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index 56901280ba0472..56e4d6e0da529a 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -41,6 +41,9 @@ #include <linux/inetdevice.h> #include "macb.h" +static unsigned int txdelay = 35; +module_param(txdelay, uint, 0644); + /* This structure is only used for MACB on SiFive FU540 devices */ struct sifive_fu540_macb_mgmt { void __iomem *reg; @@ -334,7 +337,7 @@ static int macb_mdio_wait_for_idle(struct macb *bp) u32 val; return readx_poll_timeout(MACB_READ_NSR, bp, val, val & MACB_BIT(IDLE), - 1, MACB_MDIO_TIMEOUT); + 100, MACB_MDIO_TIMEOUT); } static int macb_mdio_read_c22(struct mii_bus *bus, int mii_id, int regnum) @@ -493,6 +496,19 @@ static int macb_mdio_write_c45(struct mii_bus *bus, int mii_id, return status; } +static int macb_mdio_reset(struct mii_bus *bus) +{ + struct macb *bp = bus->priv; + + if (bp->phy_reset_gpio) { + gpiod_set_value_cansleep(bp->phy_reset_gpio, 1); + msleep(bp->phy_reset_ms); + gpiod_set_value_cansleep(bp->phy_reset_gpio, 0); + } + + return 0; +} + static void macb_init_buffers(struct macb *bp) { struct macb_queue *queue; @@ -977,6 +993,7 @@ static int macb_mii_init(struct macb *bp) bp->mii_bus->write = &macb_mdio_write_c22; bp->mii_bus->read_c45 = &macb_mdio_read_c45; bp->mii_bus->write_c45 = &macb_mdio_write_c45; + bp->mii_bus->reset = &macb_mdio_reset; snprintf(bp->mii_bus->id, MII_BUS_ID_SIZE, "%s-%x", bp->pdev->name, bp->pdev->id); bp->mii_bus->priv = bp; @@ -1648,6 +1665,11 @@ static int macb_rx(struct macb_queue *queue, struct napi_struct *napi, macb_init_rx_ring(queue); queue_writel(queue, RBQP, queue->rx_ring_dma); +#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT + if (bp->hw_dma_cap & HW_DMA_CAP_64B) + macb_writel(bp, RBQPH, + upper_32_bits(queue->rx_ring_dma)); +#endif macb_writel(bp, NCR, ctrl | MACB_BIT(RE)); @@ -1948,8 +1970,9 @@ static irqreturn_t macb_interrupt(int irq, void *dev_id) queue_writel(queue, ISR, MACB_BIT(TCOMP) | MACB_BIT(TXUBR)); - if (status & MACB_BIT(TXUBR)) { + if (status & MACB_BIT(TXUBR) || queue->tx_pending) { queue->txubr_pending = true; + queue->tx_pending = 0; wmb(); // ensure softirq can see update } @@ -2402,6 +2425,11 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, struct net_device *dev) skb_tx_timestamp(skb); spin_lock_irq(&bp->lock); + + /* TSTART write might get dropped, so make the IRQ retrigger a buffer read */ + if (macb_readl(bp, TSR) & MACB_BIT(TGO)) + queue->tx_pending = 1; + macb_writel(bp, NCR, macb_readl(bp, NCR) | MACB_BIT(TSTART)); spin_unlock_irq(&bp->lock); @@ -2808,6 +2836,37 @@ static void macb_configure_dma(struct macb *bp) } } +static void gem_init_axi(struct macb *bp) +{ + u32 amp; + + /* AXI pipeline setup - don't touch values unless specified in device + * tree. Some hardware could have reset values > 1. + */ + amp = gem_readl(bp, AMP); + + if (bp->use_aw2b_fill) + amp = GEM_BFINS(AW2B_FILL, bp->use_aw2b_fill, amp); + if (bp->aw2w_max_pipe) + amp = GEM_BFINS(AW2W_MAX_PIPE, bp->aw2w_max_pipe, amp); + if (bp->ar2r_max_pipe) + amp = GEM_BFINS(AR2R_MAX_PIPE, bp->ar2r_max_pipe, amp); + + gem_writel(bp, AMP, amp); +} + +static void gem_init_intmod(struct macb *bp) +{ + unsigned int throttle; + u32 intmod = 0; + + /* Use sensible interrupt moderation thresholds (50us rx and tx) */ + throttle = (1000 * 50) / 800; + intmod = GEM_BFINS(TX_MODERATION, throttle, intmod); + intmod = GEM_BFINS(RX_MODERATION, throttle, intmod); + gem_writel(bp, INTMOD, intmod); +} + static void macb_init_hw(struct macb *bp) { u32 config; @@ -2836,6 +2895,11 @@ static void macb_init_hw(struct macb *bp) if (bp->caps & MACB_CAPS_JUMBO) bp->rx_frm_len_mask = MACB_RX_JFRMLEN_MASK; + if (macb_is_gem(bp)) { + gem_init_axi(bp); + gem_init_intmod(bp); + } + macb_configure_dma(bp); /* Enable RX partial store and forward and set watermark */ @@ -3197,6 +3261,52 @@ static void gem_get_ethtool_strings(struct net_device *dev, u32 sset, u8 *p) } } +static int gem_set_coalesce(struct net_device *dev, + struct ethtool_coalesce *ec, + struct kernel_ethtool_coalesce *kernel_coal, + struct netlink_ext_ack *extack) +{ + struct macb *bp = netdev_priv(dev); + unsigned int tx_throttle; + unsigned int rx_throttle; + u32 intmod = 0; + + /* GEM has simple IRQ throttling support. RX and TX interrupts + * are separately moderated on 800ns quantums, with no support + * for frame coalescing. + */ + + /* Max is 255 * 0.8us = 204us. Zero implies no moderation. */ + if (ec->rx_coalesce_usecs > 204 || ec->tx_coalesce_usecs > 204) + return -EINVAL; + + tx_throttle = (1000 * ec->tx_coalesce_usecs) / 800; + rx_throttle = (1000 * ec->rx_coalesce_usecs) / 800; + + intmod = GEM_BFINS(TX_MODERATION, tx_throttle, intmod); + intmod = GEM_BFINS(RX_MODERATION, rx_throttle, intmod); + + gem_writel(bp, INTMOD, intmod); + + return 0; +} + +static int gem_get_coalesce(struct net_device *dev, + struct ethtool_coalesce *ec, + struct kernel_ethtool_coalesce *kernel_coal, + struct netlink_ext_ack *extack) +{ + struct macb *bp = netdev_priv(dev); + u32 intmod; + + intmod = gem_readl(bp, INTMOD); + + ec->tx_coalesce_usecs = (GEM_BFEXT(TX_MODERATION, intmod) * 800) / 1000; + ec->rx_coalesce_usecs = (GEM_BFEXT(RX_MODERATION, intmod) * 800) / 1000; + + return 0; +} + static struct net_device_stats *macb_get_stats(struct net_device *dev) { struct macb *bp = netdev_priv(dev); @@ -3779,6 +3889,8 @@ static const struct ethtool_ops macb_ethtool_ops = { }; static const struct ethtool_ops gem_ethtool_ops = { + .supported_coalesce_params = ETHTOOL_COALESCE_RX_USECS | + ETHTOOL_COALESCE_TX_USECS, .get_regs_len = macb_get_regs_len, .get_regs = macb_get_regs, .get_wol = macb_get_wol, @@ -3788,6 +3900,8 @@ static const struct ethtool_ops gem_ethtool_ops = { .get_ethtool_stats = gem_get_ethtool_stats, .get_strings = gem_get_ethtool_strings, .get_sset_count = gem_get_sset_count, + .get_coalesce = gem_get_coalesce, + .set_coalesce = gem_set_coalesce, .get_link_ksettings = macb_get_link_ksettings, .set_link_ksettings = macb_set_link_ksettings, .get_ringparam = macb_get_ringparam, @@ -4958,6 +5072,17 @@ static const struct macb_config versal_config = { .usrio = &macb_default_usrio, }; +static const struct macb_config raspberrypi_rp1_config = { + .caps = MACB_CAPS_GIGABIT_MODE_AVAILABLE | MACB_CAPS_CLK_HW_CHG | + MACB_CAPS_JUMBO | + MACB_CAPS_GEM_HAS_PTP, + .dma_burst_length = 16, + .clk_init = macb_clk_init, + .init = macb_init, + .usrio = &macb_default_usrio, + .jumbo_max_len = 10240, +}; + static const struct of_device_id macb_dt_ids[] = { { .compatible = "cdns,at91sam9260-macb", .data = &at91sam9260_config }, { .compatible = "cdns,macb" }, @@ -4978,6 +5103,7 @@ static const struct of_device_id macb_dt_ids[] = { { .compatible = "microchip,mpfs-macb", .data = &mpfs_config }, { .compatible = "microchip,sama7g5-gem", .data = &sama7g5_gem_config }, { .compatible = "microchip,sama7g5-emac", .data = &sama7g5_emac_config }, + { .compatible = "raspberrypi,rp1-gem", .data = &raspberrypi_rp1_config }, { .compatible = "xlnx,zynqmp-gem", .data = &zynqmp_config}, { .compatible = "xlnx,zynq-gem", .data = &zynq_config }, { .compatible = "xlnx,versal-gem", .data = &versal_config}, @@ -5109,6 +5235,11 @@ static int macb_probe(struct platform_device *pdev) } } } + + device_property_read_u8(&pdev->dev, "cdns,aw2w-max-pipe", &bp->aw2w_max_pipe); + device_property_read_u8(&pdev->dev, "cdns,ar2r-max-pipe", &bp->ar2r_max_pipe); + bp->use_aw2b_fill = device_property_read_bool(&pdev->dev, "cdns,use-aw2b-fill"); + spin_lock_init(&bp->lock); /* setup capabilities */ @@ -5164,6 +5295,21 @@ static int macb_probe(struct platform_device *pdev) else bp->phy_interface = interface; + /* optional PHY reset-related properties */ + bp->phy_reset_gpio = devm_gpiod_get_optional(&pdev->dev, "phy-reset", + GPIOD_OUT_LOW); + if (IS_ERR(bp->phy_reset_gpio)) { + dev_err(&pdev->dev, "Failed to obtain phy-reset gpio\n"); + err = PTR_ERR(bp->phy_reset_gpio); + goto err_out_free_netdev; + } + + bp->phy_reset_ms = 10; + of_property_read_u32(np, "phy-reset-duration", &bp->phy_reset_ms); + /* A sane reset duration should not be longer than 1s */ + if (bp->phy_reset_ms > 1000) + bp->phy_reset_ms = 1000; + /* IP specific init */ err = init(pdev); if (err) @@ -5238,6 +5384,19 @@ static void macb_remove(struct platform_device *pdev) } } +static void macb_shutdown(struct platform_device *pdev) +{ + struct net_device *dev; + + dev = platform_get_drvdata(pdev); + + rtnl_lock(); + netif_device_detach(dev); + if (netif_running(dev)) + dev_close(dev); + rtnl_unlock(); +} + static int __maybe_unused macb_suspend(struct device *dev) { struct net_device *netdev = dev_get_drvdata(dev); @@ -5491,6 +5650,7 @@ static const struct dev_pm_ops macb_pm_ops = { static struct platform_driver macb_driver = { .probe = macb_probe, .remove_new = macb_remove, + .shutdown = macb_shutdown, .driver = { .name = "macb", .of_match_table = of_match_ptr(macb_dt_ids), diff --git a/drivers/net/phy/bcm-phy-ptp.c b/drivers/net/phy/bcm-phy-ptp.c index 208e8f561e0696..b9abf814d7fd25 100644 --- a/drivers/net/phy/bcm-phy-ptp.c +++ b/drivers/net/phy/bcm-phy-ptp.c @@ -913,6 +913,18 @@ struct bcm_ptp_private *bcm_ptp_probe(struct phy_device *phydev) switch (BRCM_PHY_MODEL(phydev)) { case PHY_ID_BCM54210E: break; +#ifdef PHY_ID_BCM54213PE + case PHY_ID_BCM54213PE: + switch (phydev->mdio.addr) { + case 0: // CM4 - this is a BCM54210PE which supports PTP + break; + case 1: // 4B - this is a BCM54213PE which doesn't + return NULL; + default: // Unknown - assume it's BCM54210PE + break; + } + break; +#endif default: return NULL; } diff --git a/drivers/net/phy/broadcom.c b/drivers/net/phy/broadcom.c index ddded162c44c13..f778309c9f3fc7 100644 --- a/drivers/net/phy/broadcom.c +++ b/drivers/net/phy/broadcom.c @@ -126,6 +126,11 @@ static int bcm54210e_config_init(struct phy_device *phydev) return 0; } +static int bcm54213pe_config_init(struct phy_device *phydev) +{ + return bcm54210e_config_init(phydev); +} + static int bcm54612e_config_init(struct phy_device *phydev) { int reg; @@ -297,7 +302,8 @@ static void bcm54xx_adjust_rxrefclk(struct phy_device *phydev) BRCM_PHY_MODEL(phydev) != PHY_ID_BCM50610M && BRCM_PHY_MODEL(phydev) != PHY_ID_BCM54210E && BRCM_PHY_MODEL(phydev) != PHY_ID_BCM54810 && - BRCM_PHY_MODEL(phydev) != PHY_ID_BCM54811) + BRCM_PHY_MODEL(phydev) != PHY_ID_BCM54811 && + BRCM_PHY_MODEL(phydev) != PHY_ID_BCM54213PE) return; val = bcm_phy_read_shadow(phydev, BCM54XX_SHD_SCR3); @@ -430,6 +436,9 @@ static int bcm54811_config_init(struct phy_device *phydev) static int bcm54xx_config_init(struct phy_device *phydev) { int reg, err, val; + u32 led_modes[] = {BCM_LED_MULTICOLOR_LINK_ACT, + BCM_LED_MULTICOLOR_LINK}; + struct device_node *np = phydev->mdio.dev.of_node; reg = phy_read(phydev, MII_BCM54XX_ECR); if (reg < 0) @@ -454,6 +463,9 @@ static int bcm54xx_config_init(struct phy_device *phydev) (phydev->dev_flags & PHY_BRCM_CLEAR_RGMII_MODE)) bcm_phy_write_shadow(phydev, BCM54XX_SHD_RGMII_MODE, 0); + if (of_property_read_bool(np, "brcm,powerdown-enable")) + phydev->dev_flags |= PHY_BRCM_AUTO_PWRDWN_ENABLE; + bcm54xx_adjust_rxrefclk(phydev); switch (BRCM_PHY_MODEL(phydev)) { @@ -467,6 +479,9 @@ static int bcm54xx_config_init(struct phy_device *phydev) case PHY_ID_BCM54612E: err = bcm54612e_config_init(phydev); break; + case PHY_ID_BCM54213PE: + err = bcm54213pe_config_init(phydev); + break; case PHY_ID_BCM54616S: err = bcm54616s_config_init(phydev); break; @@ -488,10 +503,10 @@ static int bcm54xx_config_init(struct phy_device *phydev) bcm54xx_phydsp_config(phydev); + of_property_read_u32_array(np, "led-modes", led_modes, 2); + /* For non-SFP setups, encode link speed into LED1 and LED3 pair * (green/amber). - * Also flash these two LEDs on activity. This means configuring - * them for MULTICOLOR and encoding link/activity into them. * Don't do this for devices on an SFP module, since some of these * use the LED outputs to control the SFP LOS signal, and changing * these settings will cause LOS to malfunction. @@ -500,10 +515,15 @@ static int bcm54xx_config_init(struct phy_device *phydev) val = BCM54XX_SHD_LEDS1_LED1(BCM_LED_SRC_MULTICOLOR1) | BCM54XX_SHD_LEDS1_LED3(BCM_LED_SRC_MULTICOLOR1); bcm_phy_write_shadow(phydev, BCM54XX_SHD_LEDS1, val); + /* BCM54210PE controls two extra LEDs with the next register. + * Make them shadow the first pair of LEDs - useful on CM4 which + * uses LED3 for ETH_LEDY instead of LED1. + */ + bcm_phy_write_shadow(phydev, BCM54XX_SHD_LEDS1 + 1, val); val = BCM_LED_MULTICOLOR_IN_PHASE | - BCM54XX_SHD_LEDS1_LED1(BCM_LED_MULTICOLOR_LINK_ACT) | - BCM54XX_SHD_LEDS1_LED3(BCM_LED_MULTICOLOR_LINK_ACT); + BCM54XX_SHD_LEDS1_LED1(led_modes[0]) | + BCM54XX_SHD_LEDS1_LED3(led_modes[1]); bcm_phy_write_exp(phydev, BCM_EXP_MULTICOLOR, val); } @@ -1436,7 +1456,7 @@ static struct phy_driver broadcom_drivers[] = { .link_change_notify = bcm54xx_link_change_notify, }, { .phy_id = PHY_ID_BCM54210E, - .phy_id_mask = 0xfffffff0, + .phy_id_mask = 0xffffffff, .name = "Broadcom BCM54210E", /* PHY_GBIT_FEATURES */ .flags = PHY_ALWAYS_CALL_SUSPEND, @@ -1453,6 +1473,19 @@ static struct phy_driver broadcom_drivers[] = { .get_wol = bcm54xx_phy_get_wol, .set_wol = bcm54xx_phy_set_wol, .led_brightness_set = bcm_phy_led_brightness_set, +}, { + .phy_id = PHY_ID_BCM54213PE, + .phy_id_mask = 0xffffffff, + .name = "Broadcom BCM54213PE", + /* PHY_GBIT_FEATURES */ + .get_sset_count = bcm_phy_get_sset_count, + .get_strings = bcm_phy_get_strings, + .get_stats = bcm54xx_get_stats, + .probe = bcm54xx_phy_probe, + .config_init = bcm54xx_config_init, + .config_intr = bcm_phy_config_intr, + .suspend = bcm54xx_suspend, + .resume = bcm54xx_resume, }, { .phy_id = PHY_ID_BCM5461, .phy_id_mask = 0xfffffff0, @@ -1720,7 +1753,8 @@ module_phy_driver(broadcom_drivers); static struct mdio_device_id __maybe_unused broadcom_tbl[] = { { PHY_ID_BCM5411, 0xfffffff0 }, { PHY_ID_BCM5421, 0xfffffff0 }, - { PHY_ID_BCM54210E, 0xfffffff0 }, + { PHY_ID_BCM54210E, 0xffffffff }, + { PHY_ID_BCM54213PE, 0xffffffff }, { PHY_ID_BCM5461, 0xfffffff0 }, { PHY_ID_BCM54612E, 0xfffffff0 }, { PHY_ID_BCM54616S, 0xfffffff0 }, diff --git a/drivers/net/phy/microchip.c b/drivers/net/phy/microchip.c index 691969a4910f2b..ae3d8d2485114a 100644 --- a/drivers/net/phy/microchip.c +++ b/drivers/net/phy/microchip.c @@ -239,6 +239,7 @@ static int lan88xx_probe(struct phy_device *phydev) struct device *dev = &phydev->mdio.dev; struct lan88xx_priv *priv; u32 led_modes[4]; + u32 downshift_after = 0; int len; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); @@ -268,6 +269,32 @@ static int lan88xx_probe(struct phy_device *phydev) return -EINVAL; } + if (!of_property_read_u32(dev->of_node, + "microchip,downshift-after", + &downshift_after)) { + u32 mask = LAN78XX_PHY_CTRL3_DOWNSHIFT_CTRL_MASK; + u32 val= LAN78XX_PHY_CTRL3_AUTO_DOWNSHIFT; + + switch (downshift_after) { + case 2: val |= LAN78XX_PHY_CTRL3_DOWNSHIFT_CTRL_2; + break; + case 3: val |= LAN78XX_PHY_CTRL3_DOWNSHIFT_CTRL_3; + break; + case 4: val |= LAN78XX_PHY_CTRL3_DOWNSHIFT_CTRL_4; + break; + case 5: val |= LAN78XX_PHY_CTRL3_DOWNSHIFT_CTRL_5; + break; + case 0: // Disable completely + mask = LAN78XX_PHY_CTRL3_AUTO_DOWNSHIFT; + val = 0; + break; + default: + return -EINVAL; + } + (void)phy_modify_paged(phydev, 1, LAN78XX_PHY_CTRL3, + mask, val); + } + /* these values can be used to identify internal PHY */ priv->chip_id = phy_read_mmd(phydev, 3, LAN88XX_MMD3_CHIP_ID); priv->chip_rev = phy_read_mmd(phydev, 3, LAN88XX_MMD3_CHIP_REV); diff --git a/drivers/net/usb/lan78xx.c b/drivers/net/usb/lan78xx.c index 531b1b6a37d190..1f7b441b7d22d5 100644 --- a/drivers/net/usb/lan78xx.c +++ b/drivers/net/usb/lan78xx.c @@ -604,6 +604,20 @@ static int lan78xx_alloc_tx_resources(struct lan78xx_net *dev) dev->n_tx_urbs, dev->tx_urb_size, dev); } +/* TSO seems to be having some issue with Selective Acknowledge (SACK) that + * results in lost data never being retransmitted. + * Disable it by default now, but adds a module parameter to enable it for + * debug purposes (the full cause is not currently understood). + */ +static bool enable_tso; +module_param(enable_tso, bool, 0644); +MODULE_PARM_DESC(enable_tso, "Enables TCP segmentation offload"); + +#define INT_URB_MICROFRAMES_PER_MS 8 +static int int_urb_interval_ms = 8; +module_param(int_urb_interval_ms, int, 0); +MODULE_PARM_DESC(int_urb_interval_ms, "Override usb interrupt urb interval"); + static int lan78xx_read_reg(struct lan78xx_net *dev, u32 index, u32 *data) { u32 *buf; @@ -1421,6 +1435,9 @@ static int lan78xx_link_reset(struct lan78xx_net *dev) if (unlikely(ret < 0)) return ret; + /* Acknowledge any pending PHY interrupt, lest it be the last */ + phy_read(phydev, LAN88XX_INT_STS); + mutex_lock(&phydev->lock); phy_read_status(phydev); link = phydev->link; @@ -1685,14 +1702,11 @@ static int lan78xx_get_eee(struct net_device *net, struct ethtool_keee *edata) if (ret < 0) goto exit; - ret = lan78xx_read_reg(dev, MAC_CR, &buf); - if (buf & MAC_CR_EEE_EN_) { - /* EEE_TX_LPI_REQ_DLY & tx_lpi_timer are same uSec unit */ - ret = lan78xx_read_reg(dev, EEE_TX_LPI_REQ_DLY, &buf); + ret = lan78xx_read_reg(dev, EEE_TX_LPI_REQ_DLY, &buf); + if (ret >= 0) edata->tx_lpi_timer = buf; - } else { + else edata->tx_lpi_timer = 0; - } ret = 0; exit: @@ -1918,6 +1932,7 @@ static const struct ethtool_ops lan78xx_ethtool_ops = { .set_link_ksettings = lan78xx_set_link_ksettings, .get_regs_len = lan78xx_get_regs_len, .get_regs = lan78xx_get_regs, + .get_ts_info = ethtool_op_get_ts_info, }; static void lan78xx_init_mac_address(struct lan78xx_net *dev) @@ -2404,7 +2419,26 @@ static int lan78xx_phy_init(struct lan78xx_net *dev) mii_adv_to_linkmode_adv_t(fc, mii_adv); linkmode_or(phydev->advertising, fc, phydev->advertising); - phy_support_eee(phydev); + if (of_property_read_bool(phydev->mdio.dev.of_node, + "microchip,eee-enabled")) { + struct ethtool_keee edata; + memset(&edata, 0, sizeof(edata)); + + linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, + edata.advertised); + linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT, + edata.advertised); + + edata.eee_enabled = true; + edata.tx_lpi_enabled = true; + if (of_property_read_u32(phydev->mdio.dev.of_node, + "microchip,tx-lpi-timer", + &edata.tx_lpi_timer)) + edata.tx_lpi_timer = 600; /* non-aggressive */ + (void)lan78xx_set_eee(dev->net, &edata); + + phy_support_eee(phydev); + } if (phydev->mdio.dev.of_node) { u32 reg; @@ -2879,6 +2913,11 @@ static int lan78xx_reset(struct lan78xx_net *dev) int ret; u32 buf; u8 sig; + bool has_eeprom; + bool has_otp; + + has_eeprom = !lan78xx_read_eeprom(dev, 0, 0, NULL); + has_otp = !lan78xx_read_otp(dev, 0, 0, NULL); ret = lan78xx_read_reg(dev, HW_CFG, &buf); if (ret < 0) @@ -2945,6 +2984,10 @@ static int lan78xx_reset(struct lan78xx_net *dev) buf |= HW_CFG_CLK125_EN_; buf |= HW_CFG_REFCLK25_EN_; + /* If no valid EEPROM and no valid OTP, enable the LEDs by default */ + if (!has_eeprom && !has_otp) + buf |= HW_CFG_LED0_EN_ | HW_CFG_LED1_EN_; + ret = lan78xx_write_reg(dev, HW_CFG, buf); if (ret < 0) return ret; @@ -3047,6 +3090,9 @@ static int lan78xx_reset(struct lan78xx_net *dev) buf |= MAC_CR_AUTO_DUPLEX_ | MAC_CR_AUTO_SPEED_; } } + /* If no valid EEPROM and no valid OTP, enable AUTO negotiation */ + if (!has_eeprom && !has_otp) + buf |= MAC_CR_AUTO_DUPLEX_ | MAC_CR_AUTO_SPEED_; ret = lan78xx_write_reg(dev, MAC_CR, buf); if (ret < 0) return ret; @@ -3444,8 +3490,14 @@ static int lan78xx_bind(struct lan78xx_net *dev, struct usb_interface *intf) if (DEFAULT_RX_CSUM_ENABLE) dev->net->features |= NETIF_F_RXCSUM; - if (DEFAULT_TSO_CSUM_ENABLE) - dev->net->features |= NETIF_F_TSO | NETIF_F_TSO6 | NETIF_F_SG; + if (DEFAULT_TSO_CSUM_ENABLE) { + dev->net->features |= NETIF_F_SG; + /* Use module parameter to control TCP segmentation offload as + * it appears to cause issues. + */ + if (enable_tso) + dev->net->features |= NETIF_F_TSO | NETIF_F_TSO6; + } if (DEFAULT_VLAN_RX_OFFLOAD) dev->net->features |= NETIF_F_HW_VLAN_CTAG_RX; @@ -4415,7 +4467,13 @@ static int lan78xx_probe(struct usb_interface *intf, if (ret < 0) goto out4; - period = ep_intr->desc.bInterval; + if (int_urb_interval_ms <= 0) + period = ep_intr->desc.bInterval; + else + period = int_urb_interval_ms * INT_URB_MICROFRAMES_PER_MS; + + netif_notice(dev, probe, netdev, "int urb period %d\n", period); + maxp = usb_maxpacket(dev->udev, dev->pipe_intr); dev->urb_intr = usb_alloc_urb(0, GFP_KERNEL); diff --git a/drivers/net/usb/smsc95xx.c b/drivers/net/usb/smsc95xx.c index 8e82184be5e7d9..737a1734e4516e 100644 --- a/drivers/net/usb/smsc95xx.c +++ b/drivers/net/usb/smsc95xx.c @@ -79,6 +79,14 @@ static bool turbo_mode = true; module_param(turbo_mode, bool, 0644); MODULE_PARM_DESC(turbo_mode, "Enable multiple frames per Rx transaction"); +static int packetsize = 2560; +module_param(packetsize, int, 0644); +MODULE_PARM_DESC(packetsize, "Override the RX URB packet size"); + +static char *macaddr = ":"; +module_param(macaddr, charp, 0); +MODULE_PARM_DESC(macaddr, "MAC address"); + static int __must_check smsc95xx_read_reg(struct usbnet *dev, u32 index, u32 *data) { @@ -801,6 +809,21 @@ static int smsc95xx_ioctl(struct net_device *netdev, struct ifreq *rq, int cmd) return phy_mii_ioctl(netdev->phydev, rq, cmd); } +/* Check the macaddr module parameter for a MAC address */ +static int smsc95xx_macaddr_param(struct usbnet *dev, struct net_device *nd) +{ + u8 mtbl[ETH_ALEN]; + + if (mac_pton(macaddr, mtbl)) { + netif_dbg(dev, ifup, dev->net, + "Overriding MAC address with: %pM\n", mtbl); + dev_addr_mod(nd, 0, mtbl, ETH_ALEN); + return 0; + } else { + return -EINVAL; + } +} + static void smsc95xx_init_mac_address(struct usbnet *dev) { u8 addr[ETH_ALEN]; @@ -824,6 +847,14 @@ static void smsc95xx_init_mac_address(struct usbnet *dev) } } + /* Check module parameters */ + if (smsc95xx_macaddr_param(dev, dev->net) == 0) { + if (is_valid_ether_addr(dev->net->dev_addr)) { + netif_dbg(dev, ifup, dev->net, "MAC address read from module parameter\n"); + return; + } + } + /* no useful static MAC address found. generate a random one */ eth_hw_addr_random(dev->net); netif_dbg(dev, ifup, dev->net, "MAC address set to eth_random_addr\n"); @@ -932,13 +963,13 @@ static int smsc95xx_reset(struct usbnet *dev) if (!turbo_mode) { burst_cap = 0; - dev->rx_urb_size = MAX_SINGLE_PACKET_SIZE; + dev->rx_urb_size = packetsize ? packetsize : MAX_SINGLE_PACKET_SIZE; } else if (dev->udev->speed == USB_SPEED_HIGH) { - burst_cap = DEFAULT_HS_BURST_CAP_SIZE / HS_USB_PKT_SIZE; - dev->rx_urb_size = DEFAULT_HS_BURST_CAP_SIZE; + dev->rx_urb_size = packetsize ? packetsize : DEFAULT_HS_BURST_CAP_SIZE; + burst_cap = dev->rx_urb_size / HS_USB_PKT_SIZE; } else { - burst_cap = DEFAULT_FS_BURST_CAP_SIZE / FS_USB_PKT_SIZE; - dev->rx_urb_size = DEFAULT_FS_BURST_CAP_SIZE; + dev->rx_urb_size = packetsize ? packetsize : DEFAULT_FS_BURST_CAP_SIZE; + burst_cap = dev->rx_urb_size / FS_USB_PKT_SIZE; } netif_dbg(dev, ifup, dev->net, "rx_urb_size=%ld\n", diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h index fe31051a9e11b1..8c3613ff23f5f3 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h @@ -298,7 +298,7 @@ void brcmf_rx_event(struct device *dev, struct sk_buff *rxp); int brcmf_alloc(struct device *dev, struct brcmf_mp_device *settings); /* Indication from bus module regarding presence/insertion of dongle. */ -int brcmf_attach(struct device *dev); +int brcmf_attach(struct device *dev, bool start_bus); /* Indication from bus module regarding removal/absence of dongle */ void brcmf_detach(struct device *dev); void brcmf_free(struct device *dev); diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c index 349aa3439502cb..0a6a8726194d2e 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c @@ -9,6 +9,7 @@ #include <linux/etherdevice.h> #include <linux/module.h> #include <linux/vmalloc.h> +#include <linux/ctype.h> #include <net/cfg80211.h> #include <net/netlink.h> #include <uapi/linux/if_arp.h> @@ -89,6 +90,9 @@ #define BRCMF_PS_MAX_TIMEOUT_MS 2000 +#define MGMT_AUTH_FRAME_DWELL_TIME 4000 +#define MGMT_AUTH_FRAME_WAIT_TIME (MGMT_AUTH_FRAME_DWELL_TIME + 100) + /* Dump obss definitions */ #define ACS_MSRMNT_DELAY 80 #define CHAN_NOISE_DUMMY (-80) @@ -1944,14 +1948,18 @@ static s32 brcmf_set_wpa_version(struct net_device *ndev, s32 val = 0; s32 err = 0; - if (sme->crypto.wpa_versions & NL80211_WPA_VERSION_1) + if (sme->crypto.wpa_versions & NL80211_WPA_VERSION_1) { val = WPA_AUTH_PSK | WPA_AUTH_UNSPECIFIED; - else if (sme->crypto.wpa_versions & NL80211_WPA_VERSION_2) - val = WPA2_AUTH_PSK | WPA2_AUTH_UNSPECIFIED; - else if (sme->crypto.wpa_versions & NL80211_WPA_VERSION_3) + } else if (sme->crypto.wpa_versions & NL80211_WPA_VERSION_2) { + if (sme->crypto.akm_suites[0] == WLAN_AKM_SUITE_SAE) + val = WPA3_AUTH_SAE_PSK; + else + val = WPA2_AUTH_PSK | WPA2_AUTH_UNSPECIFIED; + } else if (sme->crypto.wpa_versions & NL80211_WPA_VERSION_3) { val = WPA3_AUTH_SAE_PSK; - else + } else { val = WPA_AUTH_DISABLED; + } brcmf_dbg(CONN, "setting wpa_auth to 0x%0x\n", val); err = brcmf_fil_bsscfg_int_set(ifp, "wpa_auth", val); if (err) { @@ -2162,6 +2170,10 @@ brcmf_set_key_mgmt(struct net_device *ndev, struct cfg80211_connect_params *sme) if (sme->crypto.sae_pwd) { brcmf_dbg(INFO, "using SAE offload\n"); profile->use_fwsup = BRCMF_PROFILE_FWSUP_SAE; + } else if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_FWSUP) && + brcmf_feat_is_enabled(ifp, BRCMF_FEAT_SAE_EXT)) { + brcmf_dbg(INFO, "using EXTSAE with PSK offload\n"); + profile->use_fwsup = BRCMF_PROFILE_FWSUP_PSK; } break; case WLAN_AKM_SUITE_FT_OVER_SAE: @@ -2217,7 +2229,7 @@ brcmf_set_key_mgmt(struct net_device *ndev, struct cfg80211_connect_params *sme) brcmf_fil_bsscfg_int_set(netdev_priv(ndev), "mfp", mfp); skip_mfp_config: - brcmf_dbg(CONN, "setting wpa_auth to %d\n", val); + brcmf_dbg(CONN, "setting wpa_auth to 0x%0x\n", val); err = brcmf_fil_bsscfg_int_set(netdev_priv(ndev), "wpa_auth", val); if (err) { bphy_err(drvr, "could not set wpa_auth (%d)\n", err); @@ -2462,43 +2474,52 @@ brcmf_cfg80211_connect(struct wiphy *wiphy, struct net_device *ndev, goto done; } - if (sme->crypto.psk && - profile->use_fwsup != BRCMF_PROFILE_FWSUP_SAE) { - if (WARN_ON(profile->use_fwsup != BRCMF_PROFILE_FWSUP_NONE)) { - err = -EINVAL; - goto done; + if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_FWSUP)) { + if (sme->crypto.psk) { + if ((profile->use_fwsup != BRCMF_PROFILE_FWSUP_SAE) && + (profile->use_fwsup != BRCMF_PROFILE_FWSUP_PSK)) { + if (WARN_ON(profile->use_fwsup != + BRCMF_PROFILE_FWSUP_NONE)) { + err = -EINVAL; + goto done; + } + brcmf_dbg(INFO, "using PSK offload\n"); + profile->use_fwsup = BRCMF_PROFILE_FWSUP_PSK; + } + } else if (profile->use_fwsup != BRCMF_PROFILE_FWSUP_1X) { + profile->use_fwsup = BRCMF_PROFILE_FWSUP_NONE; } - brcmf_dbg(INFO, "using PSK offload\n"); - profile->use_fwsup = BRCMF_PROFILE_FWSUP_PSK; - } - if (profile->use_fwsup != BRCMF_PROFILE_FWSUP_NONE) { - /* enable firmware supplicant for this interface */ - err = brcmf_fil_iovar_int_set(ifp, "sup_wpa", 1); - if (err < 0) { - bphy_err(drvr, "failed to enable fw supplicant\n"); - goto done; + if (profile->use_fwsup != BRCMF_PROFILE_FWSUP_NONE) { + /* enable firmware supplicant for this interface */ + err = brcmf_fil_iovar_int_set(ifp, "sup_wpa", 1); + if (err < 0) { + bphy_err(drvr, "failed to enable fw supplicant\n"); + goto done; + } + } else { + err = brcmf_fil_iovar_int_set(ifp, "sup_wpa", 0); } - } - if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_PSK) - err = brcmf_set_pmk(ifp, sme->crypto.psk, - BRCMF_WSEC_MAX_PSK_LEN); - else if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_SAE) { - /* clean up user-space RSNE */ - err = brcmf_fil_iovar_data_set(ifp, "wpaie", NULL, 0); - if (err) { - bphy_err(drvr, "failed to clean up user-space RSNE\n"); - goto done; - } - err = brcmf_fwvid_set_sae_password(ifp, &sme->crypto); - if (!err && sme->crypto.psk) + if ((profile->use_fwsup == BRCMF_PROFILE_FWSUP_PSK) && + sme->crypto.psk) { err = brcmf_set_pmk(ifp, sme->crypto.psk, BRCMF_WSEC_MAX_PSK_LEN); + } else if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_SAE) { + /* clean up user-space RSNE */ + err = brcmf_fil_iovar_data_set(ifp, "wpaie", NULL, 0); + if (err) { + bphy_err(drvr, "failed to clean up user-space RSNE\n"); + goto done; + } + err = brcmf_fwvid_set_sae_password(ifp, &sme->crypto); + if (!err && sme->crypto.psk) + err = brcmf_set_pmk(ifp, sme->crypto.psk, + BRCMF_WSEC_MAX_PSK_LEN); + } + if (err) + goto done; } - if (err) - goto done; - /* Join with specific BSSID and cached SSID * If SSID is zero join based on BSSID only */ @@ -3299,7 +3320,7 @@ brcmf_cfg80211_set_power_mgmt(struct wiphy *wiphy, struct net_device *ndev, brcmf_dbg(INFO, "Do not enable power save for P2P clients\n"); pm = PM_OFF; } - brcmf_dbg(INFO, "power save %s\n", (pm ? "enabled" : "disabled")); + brcmf_info("power save %s\n", (pm ? "enabled" : "disabled")); err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_PM, pm); if (err) { @@ -3309,6 +3330,7 @@ brcmf_cfg80211_set_power_mgmt(struct wiphy *wiphy, struct net_device *ndev, bphy_err(drvr, "error (%d)\n", err); } + timeout = 2000; /* 2000ms - the maximum */ err = brcmf_fil_iovar_int_set(ifp, "pm2_sleep_ret", min_t(u32, timeout, BRCMF_PS_MAX_TIMEOUT_MS)); if (err) @@ -5517,9 +5539,12 @@ brcmf_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, s32 ie_len; struct brcmf_fil_action_frame_le *action_frame; struct brcmf_fil_af_params_le *af_params; - bool ack; + bool ack = false; s32 chan_nr; u32 freq; + struct brcmf_mf_params_le *mf_params; + u32 mf_params_len; + s32 timeout; brcmf_dbg(TRACE, "Enter\n"); @@ -5600,6 +5625,71 @@ brcmf_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, cfg80211_mgmt_tx_status(wdev, *cookie, buf, len, ack, GFP_KERNEL); kfree(af_params); + } else if (ieee80211_is_auth(mgmt->frame_control)) { + reinit_completion(&vif->mgmt_tx); + clear_bit(BRCMF_MGMT_TX_ACK, &vif->mgmt_tx_status); + clear_bit(BRCMF_MGMT_TX_NOACK, &vif->mgmt_tx_status); + clear_bit(BRCMF_MGMT_TX_OFF_CHAN_COMPLETED, + &vif->mgmt_tx_status); + + mf_params_len = offsetof(struct brcmf_mf_params_le, data) + + (len - DOT11_MGMT_HDR_LEN); + mf_params = kzalloc(mf_params_len, GFP_KERNEL); + if (!mf_params) { + err = -ENOMEM; + goto exit; + } + + mf_params->dwell_time = cpu_to_le32(MGMT_AUTH_FRAME_DWELL_TIME); + mf_params->len = cpu_to_le16(len - DOT11_MGMT_HDR_LEN); + mf_params->frame_control = mgmt->frame_control; + + if (chan) + freq = chan->center_freq; + else + brcmf_fil_cmd_int_get(vif->ifp, BRCMF_C_GET_CHANNEL, + &freq); + chan_nr = ieee80211_frequency_to_channel(freq); + mf_params->channel = cpu_to_le32(chan_nr); + memcpy(&mf_params->da[0], &mgmt->da[0], ETH_ALEN); + memcpy(&mf_params->bssid[0], &mgmt->bssid[0], ETH_ALEN); + mf_params->packet_id = cpu_to_le32(*cookie); + memcpy(mf_params->data, &buf[DOT11_MGMT_HDR_LEN], + le16_to_cpu(mf_params->len)); + + brcmf_dbg(TRACE, "Auth frame, cookie=%d, fc=%04x, len=%d, channel=%d\n", + le32_to_cpu(mf_params->packet_id), + le16_to_cpu(mf_params->frame_control), + le16_to_cpu(mf_params->len), + le32_to_cpu(mf_params->channel)); + + vif->mgmt_tx_id = le32_to_cpu(mf_params->packet_id); + set_bit(BRCMF_MGMT_TX_SEND_FRAME, &vif->mgmt_tx_status); + + err = brcmf_fil_bsscfg_data_set(vif->ifp, "mgmt_frame", + mf_params, mf_params_len); + if (err) { + bphy_err(drvr, "Failed to send Auth frame: err=%d\n", + err); + goto tx_status; + } + + timeout = + wait_for_completion_timeout(&vif->mgmt_tx, + MGMT_AUTH_FRAME_WAIT_TIME); + if (test_bit(BRCMF_MGMT_TX_ACK, &vif->mgmt_tx_status)) { + brcmf_dbg(TRACE, "TX Auth frame operation is success\n"); + ack = true; + } else { + bphy_err(drvr, "TX Auth frame operation is failed: status=%ld)\n", + vif->mgmt_tx_status); + } + +tx_status: + cfg80211_mgmt_tx_status(wdev, *cookie, buf, len, ack, + GFP_KERNEL); + kfree(mf_params); + } else { brcmf_dbg(TRACE, "Unhandled, fc=%04x!!\n", mgmt->frame_control); brcmf_dbg_hex_dump(true, buf, len, "payload, len=%zu\n", len); @@ -5923,6 +6013,40 @@ static int brcmf_cfg80211_del_pmk(struct wiphy *wiphy, struct net_device *dev, return brcmf_set_pmk(ifp, NULL, 0); } +static int +brcmf_cfg80211_external_auth(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_external_auth_params *params) +{ + struct brcmf_if *ifp; + struct brcmf_pub *drvr; + struct brcmf_auth_req_status_le auth_status; + int ret = 0; + + brcmf_dbg(TRACE, "Enter\n"); + + ifp = netdev_priv(dev); + drvr = ifp->drvr; + if (params->status == WLAN_STATUS_SUCCESS) { + auth_status.flags = cpu_to_le16(BRCMF_EXTAUTH_SUCCESS); + } else { + bphy_err(drvr, "External authentication failed: status=%d\n", + params->status); + auth_status.flags = cpu_to_le16(BRCMF_EXTAUTH_FAIL); + } + + memcpy(auth_status.peer_mac, params->bssid, ETH_ALEN); + auth_status.ssid_len = cpu_to_le32(min_t(u8, params->ssid.ssid_len, + IEEE80211_MAX_SSID_LEN)); + memcpy(auth_status.ssid, params->ssid.ssid, auth_status.ssid_len); + + ret = brcmf_fil_iovar_data_set(ifp, "auth_status", &auth_status, + sizeof(auth_status)); + if (ret < 0) + bphy_err(drvr, "auth_status iovar failed: ret=%d\n", ret); + + return ret; +} + static struct cfg80211_ops brcmf_cfg80211_ops = { .add_virtual_intf = brcmf_cfg80211_add_iface, .del_virtual_intf = brcmf_cfg80211_del_iface, @@ -5970,6 +6094,7 @@ static struct cfg80211_ops brcmf_cfg80211_ops = { .update_connect_params = brcmf_cfg80211_update_conn_params, .set_pmk = brcmf_cfg80211_set_pmk, .del_pmk = brcmf_cfg80211_del_pmk, + .external_auth = brcmf_cfg80211_external_auth, }; struct cfg80211_ops *brcmf_cfg80211_get_ops(struct brcmf_mp_device *settings) @@ -6016,6 +6141,7 @@ struct brcmf_cfg80211_vif *brcmf_alloc_vif(struct brcmf_cfg80211_info *cfg, vif->mbss = mbss; } + init_completion(&vif->mgmt_tx); list_add_tail(&vif->list, &cfg->vif_list); return vif; } @@ -6707,6 +6833,122 @@ static s32 brcmf_notify_vif_event(struct brcmf_if *ifp, return -EINVAL; } +static s32 +brcmf_notify_ext_auth_request(struct brcmf_if *ifp, + const struct brcmf_event_msg *e, void *data) +{ + struct brcmf_pub *drvr = ifp->drvr; + struct cfg80211_external_auth_params params; + struct brcmf_auth_req_status_le *auth_req = + (struct brcmf_auth_req_status_le *)data; + s32 err = 0; + + brcmf_dbg(INFO, "Enter: event %s (%d) received\n", + brcmf_fweh_event_name(e->event_code), e->event_code); + + if (e->datalen < sizeof(*auth_req)) { + bphy_err(drvr, "Event %s (%d) data too small. Ignore\n", + brcmf_fweh_event_name(e->event_code), e->event_code); + return -EINVAL; + } + + memset(¶ms, 0, sizeof(params)); + params.action = NL80211_EXTERNAL_AUTH_START; + params.key_mgmt_suite = ntohl(WLAN_AKM_SUITE_SAE); + params.status = WLAN_STATUS_SUCCESS; + params.ssid.ssid_len = min_t(u32, 32, le32_to_cpu(auth_req->ssid_len)); + memcpy(params.ssid.ssid, auth_req->ssid, params.ssid.ssid_len); + memcpy(params.bssid, auth_req->peer_mac, ETH_ALEN); + + err = cfg80211_external_auth_request(ifp->ndev, ¶ms, GFP_ATOMIC); + if (err) + bphy_err(drvr, "Ext Auth request to supplicant failed (%d)\n", + err); + + return err; +} + +static s32 +brcmf_notify_auth_frame_rx(struct brcmf_if *ifp, + const struct brcmf_event_msg *e, void *data) +{ + struct brcmf_pub *drvr = ifp->drvr; + struct brcmf_cfg80211_info *cfg = drvr->config; + struct wireless_dev *wdev; + u32 mgmt_frame_len = e->datalen - sizeof(struct brcmf_rx_mgmt_data); + struct brcmf_rx_mgmt_data *rxframe = (struct brcmf_rx_mgmt_data *)data; + u8 *frame = (u8 *)(rxframe + 1); + struct brcmu_chan ch; + struct ieee80211_mgmt *mgmt_frame; + s32 freq; + + brcmf_dbg(INFO, "Enter: event %s (%d) received\n", + brcmf_fweh_event_name(e->event_code), e->event_code); + + if (e->datalen < sizeof(*rxframe)) { + bphy_err(drvr, "Event %s (%d) data too small. Ignore\n", + brcmf_fweh_event_name(e->event_code), e->event_code); + return -EINVAL; + } + + wdev = &ifp->vif->wdev; + WARN_ON(!wdev); + + ch.chspec = be16_to_cpu(rxframe->chanspec); + cfg->d11inf.decchspec(&ch); + + mgmt_frame = kzalloc(mgmt_frame_len, GFP_KERNEL); + if (!mgmt_frame) + return -ENOMEM; + + mgmt_frame->frame_control = cpu_to_le16(IEEE80211_STYPE_AUTH); + memcpy(mgmt_frame->da, ifp->mac_addr, ETH_ALEN); + memcpy(mgmt_frame->sa, e->addr, ETH_ALEN); + brcmf_fil_cmd_data_get(ifp, BRCMF_C_GET_BSSID, mgmt_frame->bssid, + ETH_ALEN); + frame += offsetof(struct ieee80211_mgmt, u); + memcpy(&mgmt_frame->u, frame, + mgmt_frame_len - offsetof(struct ieee80211_mgmt, u)); + + freq = ieee80211_channel_to_frequency(ch.control_ch_num, + ch.band == BRCMU_CHAN_BAND_2G ? + NL80211_BAND_2GHZ : + NL80211_BAND_5GHZ); + + cfg80211_rx_mgmt(wdev, freq, 0, (u8 *)mgmt_frame, mgmt_frame_len, + NL80211_RXMGMT_FLAG_EXTERNAL_AUTH); + kfree(mgmt_frame); + return 0; +} + +static s32 +brcmf_notify_mgmt_tx_status(struct brcmf_if *ifp, + const struct brcmf_event_msg *e, void *data) +{ + struct brcmf_cfg80211_vif *vif = ifp->vif; + u32 *packet_id = (u32 *)data; + + brcmf_dbg(INFO, "Enter: event %s (%d), status=%d\n", + brcmf_fweh_event_name(e->event_code), e->event_code, + e->status); + + if (!test_bit(BRCMF_MGMT_TX_SEND_FRAME, &vif->mgmt_tx_status) || + (*packet_id != vif->mgmt_tx_id)) + return 0; + + if (e->event_code == BRCMF_E_MGMT_FRAME_TXSTATUS) { + if (e->status == BRCMF_E_STATUS_SUCCESS) + set_bit(BRCMF_MGMT_TX_ACK, &vif->mgmt_tx_status); + else + set_bit(BRCMF_MGMT_TX_NOACK, &vif->mgmt_tx_status); + } else { + set_bit(BRCMF_MGMT_TX_OFF_CHAN_COMPLETED, &vif->mgmt_tx_status); + } + + complete(&vif->mgmt_tx); + return 0; +} + static void brcmf_init_conf(struct brcmf_cfg80211_conf *conf) { conf->frag_threshold = (u32)-1; @@ -6751,7 +6993,16 @@ static void brcmf_register_event_handlers(struct brcmf_cfg80211_info *cfg) brcmf_p2p_notify_action_tx_complete); brcmf_fweh_register(cfg->pub, BRCMF_E_PSK_SUP, brcmf_notify_connect_status); - brcmf_fweh_register(cfg->pub, BRCMF_E_RSSI, brcmf_notify_rssi); + brcmf_fweh_register(cfg->pub, BRCMF_E_RSSI, + brcmf_notify_rssi); + brcmf_fweh_register(cfg->pub, BRCMF_E_EXT_AUTH_REQ, + brcmf_notify_ext_auth_request); + brcmf_fweh_register(cfg->pub, BRCMF_E_EXT_AUTH_FRAME_RX, + brcmf_notify_auth_frame_rx); + brcmf_fweh_register(cfg->pub, BRCMF_E_MGMT_FRAME_TXSTATUS, + brcmf_notify_mgmt_tx_status); + brcmf_fweh_register(cfg->pub, BRCMF_E_MGMT_FRAME_OFF_CHAN_COMPLETE, + brcmf_notify_mgmt_tx_status); } static void brcmf_deinit_priv_mem(struct brcmf_cfg80211_info *cfg) @@ -7338,6 +7589,7 @@ brcmf_txrx_stypes[NUM_NL80211_IFTYPES] = { [NL80211_IFTYPE_STATION] = { .tx = 0xffff, .rx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_AUTH >> 4) | BIT(IEEE80211_STYPE_PROBE_REQ >> 4) }, [NL80211_IFTYPE_P2P_CLIENT] = { @@ -7643,6 +7895,8 @@ static int brcmf_setup_wiphy(struct wiphy *wiphy, struct brcmf_if *ifp) wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_SAE_OFFLOAD_AP); } + if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_SAE_EXT)) + wiphy->features |= NL80211_FEATURE_SAE; wiphy->mgmt_stypes = brcmf_txrx_stypes; wiphy->max_remain_on_channel_duration = 5000; if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_PNO)) { @@ -8184,31 +8438,45 @@ static void brcmf_cfg80211_reg_notifier(struct wiphy *wiphy, struct brcmf_if *ifp = brcmf_get_ifp(cfg->pub, 0); struct brcmf_pub *drvr = cfg->pub; struct brcmf_fil_country_le ccreq; + char *alpha2; s32 err; int i; - /* The country code gets set to "00" by default at boot, ignore */ - if (req->alpha2[0] == '0' && req->alpha2[1] == '0') + err = brcmf_fil_iovar_data_get(ifp, "country", &ccreq, sizeof(ccreq)); + if (err) { + bphy_err(drvr, "Country code iovar returned err = %d\n", err); return; + } + + /* The country code gets set to "00" by default at boot - substitute + * any saved ccode from the nvram file unless there is a valid code + * already set. + */ + alpha2 = req->alpha2; + if (alpha2[0] == '0' && alpha2[1] == '0') { + extern char saved_ccode[2]; + + if ((isupper(ccreq.country_abbrev[0]) && + isupper(ccreq.country_abbrev[1])) || + !saved_ccode[0]) + return; + alpha2 = saved_ccode; + pr_debug("brcmfmac: substituting saved ccode %c%c\n", + alpha2[0], alpha2[1]); + } /* ignore non-ISO3166 country codes */ for (i = 0; i < 2; i++) - if (req->alpha2[i] < 'A' || req->alpha2[i] > 'Z') { + if (alpha2[i] < 'A' || alpha2[i] > 'Z') { bphy_err(drvr, "not an ISO3166 code (0x%02x 0x%02x)\n", - req->alpha2[0], req->alpha2[1]); + alpha2[0], alpha2[1]); return; } brcmf_dbg(TRACE, "Enter: initiator=%d, alpha=%c%c\n", req->initiator, - req->alpha2[0], req->alpha2[1]); - - err = brcmf_fil_iovar_data_get(ifp, "country", &ccreq, sizeof(ccreq)); - if (err) { - bphy_err(drvr, "Country code iovar returned err = %d\n", err); - return; - } + alpha2[0], alpha2[1]); - err = brcmf_translate_country_code(ifp->drvr, req->alpha2, &ccreq); + err = brcmf_translate_country_code(ifp->drvr, alpha2, &ccreq); if (err) return; diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h index dc3a6a537507d1..f6573da57dc035 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h @@ -178,6 +178,21 @@ enum brcmf_vif_status { BRCMF_VIF_STATUS_ASSOC_SUCCESS, }; +/** + * enum brcmf_mgmt_tx_status - mgmt frame tx status + * + * @BRCMF_MGMT_TX_ACK: mgmt frame acked + * @BRCMF_MGMT_TX_NOACK: mgmt frame not acked + * @BRCMF_MGMT_TX_OFF_CHAN_COMPLETED: off-channel complete + * @BRCMF_MGMT_TX_SEND_FRAME: mgmt frame tx is in progres + */ +enum brcmf_mgmt_tx_status { + BRCMF_MGMT_TX_ACK, + BRCMF_MGMT_TX_NOACK, + BRCMF_MGMT_TX_OFF_CHAN_COMPLETED, + BRCMF_MGMT_TX_SEND_FRAME +}; + /** * struct vif_saved_ie - holds saved IEs for a virtual interface. * @@ -224,6 +239,9 @@ struct brcmf_cfg80211_vif { unsigned long sme_state; struct vif_saved_ie saved_ie; struct list_head list; + struct completion mgmt_tx; + unsigned long mgmt_tx_status; + u32 mgmt_tx_id; u16 mgmt_rx_reg; bool mbss; int is_11d; diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.c index b24faae35873dc..85c00b8dae421c 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.c @@ -20,6 +20,8 @@ #include "of.h" #include "firmware.h" #include "chip.h" +#include "fweh.h" +#include <brcm_hw_ids.h> MODULE_AUTHOR("Broadcom Corporation"); MODULE_DESCRIPTION("Broadcom 802.11 wireless LAN fullmac driver."); @@ -274,6 +276,8 @@ int brcmf_c_preinit_dcmds(struct brcmf_if *ifp) char *clmver; char *ptr; s32 err; + struct eventmsgs_ext *eventmask_msg = NULL; + u8 msglen; if (is_valid_ether_addr(ifp->mac_addr)) { /* set mac address */ @@ -433,6 +437,41 @@ int brcmf_c_preinit_dcmds(struct brcmf_if *ifp) goto done; } + /* Enable event_msg_ext specific to 43012 chip */ + if (bus->chip == CY_CC_43012_CHIP_ID) { + /* Program event_msg_ext to support event larger than 128 */ + msglen = (roundup(fweh->num_event_codes, NBBY) / NBBY) + + EVENTMSGS_EXT_STRUCT_SIZE; + /* Allocate buffer for eventmask_msg */ + eventmask_msg = kzalloc(msglen, GFP_KERNEL); + if (!eventmask_msg) { + err = -ENOMEM; + goto done; + } + + /* Read the current programmed event_msgs_ext */ + eventmask_msg->ver = EVENTMSGS_VER; + eventmask_msg->len = roundup(fweh->num_event_codes, NBBY) / NBBY; + err = brcmf_fil_iovar_data_get(ifp, "event_msgs_ext", + eventmask_msg, + msglen); + + /* Enable ULP event */ + brcmf_dbg(EVENT, "enable event ULP\n"); + setbit(eventmask_msg->mask, BRCMF_E_ULP); + + /* Write updated Event mask */ + eventmask_msg->ver = EVENTMSGS_VER; + eventmask_msg->command = EVENTMSGS_SET_MASK; + eventmask_msg->len = (roundup(fweh->num_event_codes, NBBY) / NBBY); + + err = brcmf_fil_iovar_data_set(ifp, "event_msgs_ext", + eventmask_msg, msglen); + if (err) { + brcmf_err("Set event_msgs_ext error (%d)\n", err); + goto done; + } + } /* Setup default scan channel time */ err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_SCAN_CHANNEL_TIME, BRCMF_DEFAULT_SCAN_CHANNEL_TIME); diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c index 20ab9b1eea2836..f4011f7502e13e 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c @@ -1322,7 +1322,7 @@ int brcmf_alloc(struct device *dev, struct brcmf_mp_device *settings) return 0; } -int brcmf_attach(struct device *dev) +int brcmf_attach(struct device *dev, bool start_bus) { struct brcmf_bus *bus_if = dev_get_drvdata(dev); struct brcmf_pub *drvr = bus_if->drvr; @@ -1363,10 +1363,13 @@ int brcmf_attach(struct device *dev) brcmf_fweh_register(drvr, BRCMF_E_PSM_WATCHDOG, brcmf_psm_watchdog_notify); - ret = brcmf_bus_started(drvr, drvr->ops); - if (ret != 0) { - bphy_err(drvr, "dongle is not responding: err=%d\n", ret); - goto fail; + if (start_bus) { + ret = brcmf_bus_started(drvr, drvr->ops); + if (ret != 0) { + bphy_err(drvr, "dongle is not responding: err=%d\n", + ret); + goto fail; + } } return 0; diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/debug.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/debug.h index 9bb5f709d41a27..f3804d6f87689c 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/debug.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/debug.h @@ -29,6 +29,7 @@ #define BRCMF_MSGBUF_VAL 0x00040000 #define BRCMF_PCIE_VAL 0x00080000 #define BRCMF_FWCON_VAL 0x00100000 +#define BRCMF_ULP_VAL 0x00200000 /* set default print format */ #undef pr_fmt @@ -67,7 +68,12 @@ void __brcmf_err(struct brcmf_bus *bus, const char *func, const char *fmt, ...); #if defined(DEBUG) || defined(CONFIG_BRCM_TRACING) /* For debug/tracing purposes treat info messages as errors */ -#define brcmf_info brcmf_err +// #define brcmf_info brcmf_err + +#define brcmf_info(fmt, ...) \ + do { \ + pr_info("%s: " fmt, __func__, ##__VA_ARGS__); \ + } while (0) __printf(3, 4) void __brcmf_dbg(u32 level, const char *func, const char *fmt, ...); diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c index 0d9ae197fa1ec3..e8ca741372286b 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c @@ -44,6 +44,8 @@ static const struct brcmf_feat_fwcap brcmf_fwcap_map[] = { { BRCMF_FEAT_DOT11H, "802.11h" }, { BRCMF_FEAT_SAE, "sae" }, { BRCMF_FEAT_FWAUTH, "idauth" }, + { BRCMF_FEAT_SAE_EXT, "sae_ext" }, + { BRCMF_FEAT_SAE_EXT, "extsae" }, }; #ifdef DEBUG @@ -240,7 +242,14 @@ static void brcmf_feat_firmware_capabilities(struct brcmf_if *ifp) brcmf_dbg(INFO, "[ %s]\n", caps); for (i = 0; i < ARRAY_SIZE(brcmf_fwcap_map); i++) { - if (strnstr(caps, brcmf_fwcap_map[i].fwcap_id, sizeof(caps))) { + const char *match = strnstr(caps, brcmf_fwcap_map[i].fwcap_id, sizeof(caps)); + if (match) { + char endc; + if (match != caps && match[-1] != ' ') + continue; + endc = match[strlen(brcmf_fwcap_map[i].fwcap_id)]; + if (endc != '\0' && endc != ' ') + continue; id = brcmf_fwcap_map[i].feature; brcmf_dbg(INFO, "enabling feature: %s\n", brcmf_feat_names[id]); diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h index 7f4f0b3e4a7b4a..e2fc7c9dffdba5 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h @@ -30,6 +30,7 @@ * SAE: simultaneous authentication of equals * FWAUTH: Firmware authenticator * DUMP_OBSS: Firmware has capable to dump obss info to support ACS + * SAE_EXT: SAE be handled by userspace supplicant * SCAN_V2: Version 2 scan params */ #define BRCMF_FEAT_LIST \ @@ -55,6 +56,7 @@ BRCMF_FEAT_DEF(SAE) \ BRCMF_FEAT_DEF(FWAUTH) \ BRCMF_FEAT_DEF(DUMP_OBSS) \ + BRCMF_FEAT_DEF(SAE_EXT) \ BRCMF_FEAT_DEF(SCAN_V2) \ BRCMF_FEAT_DEF(PMKID_V2) \ BRCMF_FEAT_DEF(PMKID_V3) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/firmware.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/firmware.c index 83f8ed7d00f96c..d6f267932e07b3 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/firmware.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/firmware.c @@ -10,6 +10,7 @@ #include <linux/firmware.h> #include <linux/module.h> #include <linux/bcm47xx_nvram.h> +#include <linux/ctype.h> #include "debug.h" #include "firmware.h" @@ -32,6 +33,8 @@ enum nvram_parser_state { END }; +char saved_ccode[2] = {}; + /** * struct nvram_parser - internal info for parser. * @@ -562,11 +565,27 @@ static int brcmf_fw_request_nvram_done(const struct firmware *fw, void *ctx) goto fail; } - if (data) + if (data) { + char *ccode = strnstr((char *)data, "ccode=", data_len); + /* Ensure this is a whole token */ + if (ccode && ((void *)ccode == (void *)data || isspace(ccode[-1]))) { + /* Comment out the line */ + ccode[0] = '#'; + ccode += 6; + if (isupper(ccode[0]) && isupper(ccode[1]) && + isspace(ccode[2])) { + pr_debug("brcmfmac: intercepting ccode=%c%c\n", + ccode[0], ccode[1]); + saved_ccode[0] = ccode[0]; + saved_ccode[1] = ccode[1]; + } + }; + nvram = brcmf_fw_nvram_strip(data, data_len, &nvram_length, fwctx->req->domain_nr, fwctx->req->bus_nr, fwctx->dev); + } if (free_bcm47xx_nvram) bcm47xx_nvram_release_contents(data); diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.c index f0b6a7607f1604..ef491a290f6c67 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.c @@ -432,6 +432,8 @@ int brcmf_fweh_activate_events(struct brcmf_if *ifp) { struct brcmf_fweh_info *fweh = ifp->drvr->fweh; enum brcmf_fweh_event_code code; + struct eventmsgs_ext *eventmask_msg; + u32 msglen; int i, err; memset(fweh->event_mask, 0, fweh->event_mask_len); @@ -444,15 +446,32 @@ int brcmf_fweh_activate_events(struct brcmf_if *ifp) } } + msglen = EVENTMSGS_EXT_STRUCT_SIZE + fweh->event_mask_len; + eventmask_msg = kzalloc(msglen, GFP_KERNEL); + if (!eventmask_msg) + return -ENOMEM; + /* want to handle IF event as well */ brcmf_dbg(EVENT, "enable event IF\n"); setbit(fweh->event_mask, BRCMF_E_IF); + eventmask_msg->ver = EVENTMSGS_VER; + eventmask_msg->command = EVENTMSGS_SET_MASK; + eventmask_msg->len = fweh->event_mask_len; + memcpy(eventmask_msg->mask, fweh->event_mask, fweh->event_mask_len); + + err = brcmf_fil_iovar_data_set(ifp, "event_msgs_ext", eventmask_msg, + msglen); + if (!err) + goto end; + err = brcmf_fil_iovar_data_set(ifp, "event_msgs", fweh->event_mask, fweh->event_mask_len); if (err) bphy_err(fweh->drvr, "Set event_msgs error (%d)\n", err); +end: + kfree(eventmask_msg); return err; } diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.h index eed439b840109f..d29442c47951ea 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.h @@ -94,7 +94,12 @@ struct brcmf_cfg80211_info; BRCMF_ENUM_DEF(FIFO_CREDIT_MAP, 74) \ BRCMF_ENUM_DEF(ACTION_FRAME_RX, 75) \ BRCMF_ENUM_DEF(TDLS_PEER_EVENT, 92) \ - BRCMF_ENUM_DEF(BCMC_CREDIT_SUPPORT, 127) + BRCMF_ENUM_DEF(BCMC_CREDIT_SUPPORT, 127) \ + BRCMF_ENUM_DEF(ULP, 146) \ + BRCMF_ENUM_DEF(EXT_AUTH_REQ, 187) \ + BRCMF_ENUM_DEF(EXT_AUTH_FRAME_RX, 188) \ + BRCMF_ENUM_DEF(MGMT_FRAME_TXSTATUS, 189) \ + BRCMF_ENUM_DEF(MGMT_FRAME_OFF_CHAN_COMPLETE, 190) #define BRCMF_ENUM_DEF(id, val) \ BRCMF_E_##id = (val), @@ -280,6 +285,28 @@ struct brcmf_if_event { u8 role; }; +enum event_msgs_ext_command { + EVENTMSGS_NONE = 0, + EVENTMSGS_SET_BIT = 1, + EVENTMSGS_RESET_BIT = 2, + EVENTMSGS_SET_MASK = 3 +}; + +#define EVENTMSGS_VER 1 +#define EVENTMSGS_EXT_STRUCT_SIZE offsetof(struct eventmsgs_ext, mask[0]) + +/* len- for SET it would be mask size from the application to the firmware */ +/* for GET it would be actual firmware mask size */ +/* maxgetsize - is only used for GET. indicate max mask size that the */ +/* application can read from the firmware */ +struct eventmsgs_ext { + u8 ver; + u8 command; + u8 len; + u8 maxgetsize; + u8 mask[1]; +}; + typedef int (*brcmf_fweh_handler_t)(struct brcmf_if *ifp, const struct brcmf_event_msg *evtmsg, void *data); diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h index e74a23e11830c1..86539bef6b8843 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h @@ -178,6 +178,11 @@ #define BRCMF_PMKSA_VER_3 3 #define BRCMF_PMKSA_NO_EXPIRY 0xffffffff +#define BRCMF_EXTAUTH_START 1 +#define BRCMF_EXTAUTH_ABORT 2 +#define BRCMF_EXTAUTH_FAIL 3 +#define BRCMF_EXTAUTH_SUCCESS 4 + /* MAX_CHUNK_LEN is the maximum length for data passing to firmware in each * ioctl. It is relatively small because firmware has small maximum size input * playload restriction for ioctls. @@ -598,6 +603,47 @@ struct brcmf_wsec_sae_pwd_le { u8 key[BRCMF_WSEC_MAX_SAE_PASSWORD_LEN]; }; +/** + * struct brcmf_auth_req_status_le - external auth request and status update + * + * @flags: flags for external auth status + * @peer_mac: peer MAC address + * @ssid_len: length of ssid + * @ssid: ssid characters + */ +struct brcmf_auth_req_status_le { + __le16 flags; + u8 peer_mac[ETH_ALEN]; + __le32 ssid_len; + u8 ssid[IEEE80211_MAX_SSID_LEN]; + u8 pmkid[WLAN_PMKID_LEN]; +}; + +/** + * struct brcmf_mf_params_le - management frame parameters for mgmt_frame iovar + * + * @version: version of the iovar + * @dwell_time: dwell duration in ms + * @len: length of frame data + * @frame_control: frame control + * @channel: channel + * @da: peer MAC address + * @bssid: BSS network identifier + * @packet_id: packet identifier + * @data: frame data + */ +struct brcmf_mf_params_le { + __le32 version; + __le32 dwell_time; + __le16 len; + __le16 frame_control; + __le16 channel; + u8 da[ETH_ALEN]; + u8 bssid[ETH_ALEN]; + __le32 packet_id; + u8 data[1]; +}; + /* Used to get specific STA parameters */ struct brcmf_scb_val_le { __le32 val; diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c index 6e0c90f4718b58..7949f78c61e13b 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c @@ -1281,6 +1281,10 @@ static s32 brcmf_p2p_abort_action_frame(struct brcmf_cfg80211_info *cfg) brcmf_dbg(TRACE, "Enter\n"); vif = p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif; + + if (!vif) + vif = p2p->bss_idx[P2PAPI_BSSCFG_PRIMARY].vif; + err = brcmf_fil_bsscfg_data_set(vif->ifp, "actframe_abort", &int_val, sizeof(s32)); if (err) @@ -1826,6 +1830,7 @@ bool brcmf_p2p_send_action_frame(struct brcmf_cfg80211_info *cfg, /* validate channel and p2p ies */ if (config_af_params.search_channel && IS_P2P_SOCIAL_CHANNEL(le32_to_cpu(af_params->channel)) && + p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif && p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif->saved_ie.probe_req_ie_len) { afx_hdl = &p2p->afx_hdl; afx_hdl->peer_listen_chan = le32_to_cpu(af_params->channel); diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c index 5dee54819fbdfb..4c38f3eb9e7a07 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c @@ -2207,7 +2207,7 @@ static void brcmf_pcie_setup(struct device *dev, int ret, init_waitqueue_head(&devinfo->mbdata_resp_wait); - ret = brcmf_attach(&devinfo->pdev->dev); + ret = brcmf_attach(&devinfo->pdev->dev, true); if (ret) goto fail; diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c index 7b936668c1b66d..f5f2ef07d2e2fc 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c @@ -35,9 +35,12 @@ #include "core.h" #include "common.h" #include "bcdc.h" +#include "debug.h" +#include "fwil.h" #define DCMD_RESP_TIMEOUT msecs_to_jiffies(2500) #define CTL_DONE_TIMEOUT msecs_to_jiffies(2500) +#define ULP_HUDI_PROC_DONE_TIME msecs_to_jiffies(2500) /* watermark expressed in number of words */ #define DEFAULT_F2_WATERMARK 0x8 @@ -325,7 +328,16 @@ struct rte_console { #define KSO_WAIT_US 50 #define MAX_KSO_ATTEMPTS (PMU_MAX_TRANSITION_DLY/KSO_WAIT_US) -#define BRCMF_SDIO_MAX_ACCESS_ERRORS 5 +#define BRCMF_SDIO_MAX_ACCESS_ERRORS 20 + +static void brcmf_sdio_firmware_callback(struct device *dev, int err, + struct brcmf_fw_request *fwreq); +static struct brcmf_fw_request * + brcmf_sdio_prepare_fw_request(struct brcmf_sdio *bus); +static int brcmf_sdio_f2_ready(struct brcmf_sdio *bus); +static int brcmf_ulp_event_notify(struct brcmf_if *ifp, + const struct brcmf_event_msg *evtmsg, + void *data); #ifdef DEBUG /* Device console log buffer state */ @@ -609,6 +621,7 @@ BRCMF_FW_DEF(4329, "brcmfmac4329-sdio"); BRCMF_FW_DEF(4330, "brcmfmac4330-sdio"); BRCMF_FW_DEF(4334, "brcmfmac4334-sdio"); BRCMF_FW_DEF(43340, "brcmfmac43340-sdio"); +BRCMF_FW_DEF(43341, "brcmfmac43341-sdio"); BRCMF_FW_DEF(4335, "brcmfmac4335-sdio"); BRCMF_FW_DEF(43362, "brcmfmac43362-sdio"); BRCMF_FW_DEF(4339, "brcmfmac4339-sdio"); @@ -641,7 +654,7 @@ static const struct brcmf_firmware_mapping brcmf_sdio_fwnames[] = { BRCMF_FW_ENTRY(BRCM_CC_4330_CHIP_ID, 0xFFFFFFFF, 4330), BRCMF_FW_ENTRY(BRCM_CC_4334_CHIP_ID, 0xFFFFFFFF, 4334), BRCMF_FW_ENTRY(BRCM_CC_43340_CHIP_ID, 0xFFFFFFFF, 43340), - BRCMF_FW_ENTRY(BRCM_CC_43341_CHIP_ID, 0xFFFFFFFF, 43340), + BRCMF_FW_ENTRY(BRCM_CC_43341_CHIP_ID, 0xFFFFFFFF, 43341), BRCMF_FW_ENTRY(BRCM_CC_4335_CHIP_ID, 0xFFFFFFFF, 4335), BRCMF_FW_ENTRY(BRCM_CC_43362_CHIP_ID, 0xFFFFFFFE, 43362), BRCMF_FW_ENTRY(BRCM_CC_4339_CHIP_ID, 0xFFFFFFFF, 4339), @@ -1104,7 +1117,7 @@ static void brcmf_sdio_get_console_addr(struct brcmf_sdio *bus) } #endif /* DEBUG */ -static u32 brcmf_sdio_hostmail(struct brcmf_sdio *bus) +static u32 brcmf_sdio_hostmail(struct brcmf_sdio *bus, u32 *hmbd) { struct brcmf_sdio_dev *sdiod = bus->sdiodev; struct brcmf_core *core = bus->sdio_core; @@ -1193,6 +1206,9 @@ static u32 brcmf_sdio_hostmail(struct brcmf_sdio *bus) HMB_DATA_FCDATA_MASK | HMB_DATA_VERSION_MASK)) brcmf_err("Unknown mailbox data content: 0x%02x\n", hmb_data); + /* Populate hmb_data if argument is passed for DS1 check later */ + if (hmbd) + *hmbd = hmb_data; return intstatus; } @@ -2579,6 +2595,182 @@ static int brcmf_sdio_intr_rstatus(struct brcmf_sdio *bus) return ret; } +/* This Function is used to retrieve important + * details from dongle related to ULP mode Mostly + * values/SHM details that will be vary depending + * on the firmware branches + */ +static void +brcmf_sdio_ulp_preinit(struct device *dev) +{ + struct brcmf_bus *bus_if = dev_get_drvdata(dev); + struct brcmf_sdio_dev *sdiodev = bus_if->bus_priv.sdio; + struct brcmf_if *ifp = bus_if->drvr->iflist[0]; + + brcmf_dbg(ULP, "Enter\n"); + + /* Query ulp_sdioctrl iovar to get the ULP related SHM offsets */ + brcmf_fil_iovar_data_get(ifp, "ulp_sdioctrl", + &sdiodev->fmac_ulp.ulp_shm_offset, + sizeof(sdiodev->fmac_ulp.ulp_shm_offset)); + + sdiodev->ulp = false; + + brcmf_dbg(ULP, "m_ulp_ctrl_sdio[%x] m_ulp_wakeevt_ind [%x]\n", + M_DS1_CTRL_SDIO(sdiodev->fmac_ulp), + M_WAKEEVENT_IND(sdiodev->fmac_ulp)); + brcmf_dbg(ULP, "m_ulp_wakeind [%x]\n", + M_ULP_WAKE_IND(sdiodev->fmac_ulp)); +} + +/* Reinitialize ARM because In DS1 mode ARM got off */ +static int +brcmf_sdio_ulp_reinit_fw(struct brcmf_sdio *bus) +{ + struct brcmf_sdio_dev *sdiodev = bus->sdiodev; + struct brcmf_fw_request *fwreq; + int err = 0; + + /* After firmware redownload tx/rx seq are reset accordingly + * these values are reset on FMAC side tx_max is initially set to 4, + * which later is updated by FW. + */ + bus->tx_seq = 0; + bus->rx_seq = 0; + bus->tx_max = 4; + + fwreq = brcmf_sdio_prepare_fw_request(bus); + if (!fwreq) + return -ENOMEM; + + err = brcmf_fw_get_firmwares(sdiodev->dev, fwreq, + brcmf_sdio_firmware_callback); + if (err != 0) { + brcmf_err("async firmware request failed: %d\n", err); + kfree(fwreq); + } + + return err; +} + +/* Check if device is in DS1 mode and handshake with ULP UCODE */ +static bool +brcmf_sdio_ulp_pre_redownload_check(struct brcmf_sdio *bus, u32 hmb_data) +{ + struct brcmf_sdio_dev *sdiod = bus->sdiodev; + int err = 0; + u32 value = 0; + u32 val32, ulp_wake_ind, wowl_wake_ind; + int reg_addr; + unsigned long timeout; + struct brcmf_ulp *fmac_ulp = &bus->sdiodev->fmac_ulp; + int i = 0; + + /* If any host mail box data is present, ignore DS1 exit sequence */ + if (hmb_data) + return false; + /* Skip if DS1 Exit is already in progress + * This can happen if firmware download is taking more time + */ + if (fmac_ulp->ulp_state == FMAC_ULP_TRIGGERED) + return false; + + value = brcmf_sdiod_func0_rb(sdiod, SDIO_CCCR_IOEx, &err); + + if (value == SDIO_FUNC_ENABLE_1) { + brcmf_dbg(ULP, "GOT THE INTERRUPT FROM UCODE\n"); + sdiod->ulp = true; + fmac_ulp->ulp_state = FMAC_ULP_TRIGGERED; + ulp_wake_ind = D11SHM_RDW(sdiod, + M_ULP_WAKE_IND(sdiod->fmac_ulp), + &err); + wowl_wake_ind = D11SHM_RDW(sdiod, + M_WAKEEVENT_IND(sdiod->fmac_ulp), + &err); + + brcmf_dbg(ULP, "wowl_wake_ind: 0x%08x, ulp_wake_ind: 0x%08x state %s\n", + wowl_wake_ind, ulp_wake_ind, (fmac_ulp->ulp_state) ? + "DS1 Exit Triggered" : "IDLE State"); + + if (wowl_wake_ind || ulp_wake_ind) { + /* RX wake Don't do anything. + * Just bail out and re-download firmware. + */ + /* Print out PHY TX error block when bit 9 set */ + if ((ulp_wake_ind & C_DS1_PHY_TXERR) && + M_DS1_PHYTX_ERR_BLK(sdiod->fmac_ulp)) { + brcmf_err("Dump PHY TX Error SHM Locations\n"); + for (i = 0; i < PHYTX_ERR_BLK_SIZE; i++) { + pr_err("0x%x", + D11SHM_RDW(sdiod, + (M_DS1_PHYTX_ERR_BLK(sdiod->fmac_ulp) + + (i * 2)), &err)); + } + brcmf_err("\n"); + } + } else { + /* TX wake negotiate with MAC */ + brcmf_dbg(ULP, "M_DS1_CTRL_SDIO: 0x%08x\n", + (u32)D11SHM_RDW(sdiod, + M_DS1_CTRL_SDIO(sdiod->fmac_ulp), + &err)); + val32 = D11SHM_RD(sdiod, + M_DS1_CTRL_SDIO(sdiod->fmac_ulp), + &err); + D11SHM_WR(sdiod, M_DS1_CTRL_SDIO(sdiod->fmac_ulp), + val32, (C_DS1_CTRL_SDIO_DS1_EXIT | + C_DS1_CTRL_REQ_VALID), &err); + val32 = D11REG_RD(sdiod, D11_MACCONTROL_REG, &err); + val32 = val32 | D11_MACCONTROL_REG_WAKE; + D11REG_WR(sdiod, D11_MACCONTROL_REG, val32, &err); + + /* Poll for PROC_DONE to be set by ucode */ + value = D11SHM_RDW(sdiod, + M_DS1_CTRL_SDIO(sdiod->fmac_ulp), + &err); + /* Wait here (polling) for C_DS1_CTRL_PROC_DONE */ + timeout = jiffies + ULP_HUDI_PROC_DONE_TIME; + while (!(value & C_DS1_CTRL_PROC_DONE)) { + value = D11SHM_RDW(sdiod, + M_DS1_CTRL_SDIO(sdiod->fmac_ulp), + &err); + if (time_after(jiffies, timeout)) + break; + usleep_range(1000, 2000); + } + brcmf_dbg(ULP, "M_DS1_CTRL_SDIO: 0x%08x\n", + (u32)D11SHM_RDW(sdiod, + M_DS1_CTRL_SDIO(sdiod->fmac_ulp), &err)); + value = D11SHM_RDW(sdiod, + M_DS1_CTRL_SDIO(sdiod->fmac_ulp), + &err); + if (!(value & C_DS1_CTRL_PROC_DONE)) { + brcmf_err("Timeout Failed to enter DS1 Exit state!\n"); + return false; + } + } + + ulp_wake_ind = D11SHM_RDW(sdiod, + M_ULP_WAKE_IND(sdiod->fmac_ulp), + &err); + wowl_wake_ind = D11SHM_RDW(sdiod, + M_WAKEEVENT_IND(sdiod->fmac_ulp), + &err); + brcmf_dbg(ULP, "wowl_wake_ind: 0x%08x, ulp_wake_ind: 0x%08x\n", + wowl_wake_ind, ulp_wake_ind); + reg_addr = CORE_CC_REG( + brcmf_chip_get_pmu(bus->ci)->base, min_res_mask); + brcmf_sdiod_writel(sdiod, reg_addr, + DEFAULT_43012_MIN_RES_MASK, &err); + if (err) + brcmf_err("min_res_mask failed\n"); + + return true; + } + + return false; +} + static void brcmf_sdio_dpc(struct brcmf_sdio *bus) { struct brcmf_sdio_dev *sdiod = bus->sdiodev; @@ -2650,8 +2842,11 @@ static void brcmf_sdio_dpc(struct brcmf_sdio *bus) /* Handle host mailbox indication */ if (intstatus & I_HMB_HOST_INT) { + u32 hmb_data = 0; intstatus &= ~I_HMB_HOST_INT; - intstatus |= brcmf_sdio_hostmail(bus); + intstatus |= brcmf_sdio_hostmail(bus, &hmb_data); + if (brcmf_sdio_ulp_pre_redownload_check(bus, hmb_data)) + brcmf_sdio_ulp_reinit_fw(bus); } sdio_release_host(bus->sdiodev->func1); @@ -2696,7 +2891,7 @@ static void brcmf_sdio_dpc(struct brcmf_sdio *bus) brcmf_sdio_clrintr(bus); if (bus->ctrl_frame_stat && (bus->clkstate == CLK_AVAIL) && - txctl_ok(bus)) { + txctl_ok(bus) && brcmf_sdio_f2_ready(bus)) { sdio_claim_host(bus->sdiodev->func1); if (bus->ctrl_frame_stat) { err = brcmf_sdio_tx_ctrlframe(bus, bus->ctrl_frame_buf, @@ -3566,6 +3761,10 @@ static int brcmf_sdio_bus_preinit(struct device *dev) if (err < 0) goto done; + /* initialize SHM address from firmware for DS1 */ + if (!bus->sdiodev->ulp) + brcmf_sdio_ulp_preinit(dev); + bus->tx_hdrlen = SDPCM_HWHDR_LEN + SDPCM_SWHDR_LEN; if (sdiodev->sg_support) { bus->txglom = false; @@ -4214,7 +4413,7 @@ static void brcmf_sdio_firmware_callback(struct device *dev, int err, u8 saveclk, bpreq; u8 devctl; - brcmf_dbg(TRACE, "Enter: dev=%s, err=%d\n", dev_name(dev), err); + brcmf_dbg(ULP, "Enter: dev=%s, err=%d\n", dev_name(dev), err); if (err) goto fail; @@ -4391,12 +4590,25 @@ static void brcmf_sdio_firmware_callback(struct device *dev, int err, } /* Attach to the common layer, reserve hdr space */ - err = brcmf_attach(sdiod->dev); + err = brcmf_attach(sdiod->dev, !bus->sdiodev->ulp); if (err != 0) { brcmf_err("brcmf_attach failed\n"); goto free; } + /* Register for ULP events */ + if (sdiod->func1->device == SDIO_DEVICE_ID_BROADCOM_CYPRESS_43012) + brcmf_fweh_register(bus_if->drvr, BRCMF_E_ULP, + brcmf_ulp_event_notify); + + if (bus->sdiodev->ulp) { + /* For ULP, after firmware redownload complete + * set ULP state to IDLE + */ + if (bus->sdiodev->fmac_ulp.ulp_state == FMAC_ULP_TRIGGERED) + bus->sdiodev->fmac_ulp.ulp_state = FMAC_ULP_IDLE; + } + /* ready */ return; @@ -4639,3 +4851,40 @@ int brcmf_sdio_sleep(struct brcmf_sdio *bus, bool sleep) return ret; } + +/* Check F2 Ready bit before sending data to Firmware */ +static int +brcmf_sdio_f2_ready(struct brcmf_sdio *bus) +{ + int ret = -1; + int iordy_status = 0; + + sdio_claim_host(bus->sdiodev->func1); + /* Read the status of IOR2 */ + iordy_status = brcmf_sdiod_func0_rb(bus->sdiodev, SDIO_CCCR_IORx, NULL); + + sdio_release_host(bus->sdiodev->func1); + ret = iordy_status & SDIO_FUNC_ENABLE_2; + return ret; +} + +static int brcmf_ulp_event_notify(struct brcmf_if *ifp, + const struct brcmf_event_msg *evtmsg, + void *data) +{ + int err = 0; + struct brcmf_bus *bus_if = ifp->drvr->bus_if; + struct brcmf_sdio_dev *sdiodev; + struct brcmf_sdio *bus; + struct brcmf_ulp_event *ulp_event = (struct brcmf_ulp_event *)data; + + sdiodev = bus_if->bus_priv.sdio; + bus = sdiodev->bus; + + brcmf_dbg(ULP, "Chip went to DS1 state : action %d\n", + ulp_event->ulp_dongle_action); + if (ulp_event->ulp_dongle_action == FMAC_ULP_ENTRY) + bus->sdiodev->fmac_ulp.ulp_state = FMAC_ULP_ENTRY_RECV; + + return err; +} diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.h index 0d18ed15b4032a..aadf251d544070 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.h @@ -165,6 +165,35 @@ struct brcmf_sdreg { struct brcmf_sdio; struct brcmf_sdiod_freezer; +/* ULP SHM Offsets info */ +struct ulp_shm_info { + u32 m_ulp_ctrl_sdio; + u32 m_ulp_wakeevt_ind; + u32 m_ulp_wakeind; + u32 m_ulp_phytxblk; +}; + +/* FMAC ULP state machine */ +#define FMAC_ULP_IDLE (0) +#define FMAC_ULP_ENTRY_RECV (1) +#define FMAC_ULP_TRIGGERED (2) + +/* BRCMF_E_ULP event data */ +#define FMAC_ULP_EVENT_VERSION 1 +#define FMAC_ULP_DISABLE_CONSOLE 1 /* Disable console */ +#define FMAC_ULP_UCODE_DOWNLOAD 2 /* Download ULP ucode file */ +#define FMAC_ULP_ENTRY 3 /* Inform ulp entry to Host */ + +struct brcmf_ulp { + uint ulp_state; + struct ulp_shm_info ulp_shm_offset; +}; + +struct brcmf_ulp_event { + u16 version; + u16 ulp_dongle_action; +}; + struct brcmf_sdio_dev { struct sdio_func *func1; struct sdio_func *func2; @@ -193,6 +222,8 @@ struct brcmf_sdio_dev { enum brcmf_sdiod_state state; struct brcmf_sdiod_freezer *freezer; const struct firmware *clm_fw; + struct brcmf_ulp fmac_ulp; + bool ulp; }; /* sdio core registers */ @@ -367,4 +398,83 @@ void brcmf_sdio_wowl_config(struct device *dev, bool enabled); int brcmf_sdio_sleep(struct brcmf_sdio *bus, bool sleep); void brcmf_sdio_trigger_dpc(struct brcmf_sdio *bus); +/* SHM offsets */ +#define M_DS1_CTRL_SDIO(ptr) ((ptr).ulp_shm_offset.m_ulp_ctrl_sdio) +#define M_WAKEEVENT_IND(ptr) ((ptr).ulp_shm_offset.m_ulp_wakeevt_ind) +#define M_ULP_WAKE_IND(ptr) ((ptr).ulp_shm_offset.m_ulp_wakeind) +#define M_DS1_PHYTX_ERR_BLK(ptr) ((ptr).ulp_shm_offset.m_ulp_phytxblk) + +#define D11_BASE_ADDR 0x18001000 +#define D11_AXI_BASE_ADDR 0xE8000000 +#define D11_SHM_BASE_ADDR (D11_AXI_BASE_ADDR + 0x4000) + +#define D11REG_ADDR(offset) (D11_BASE_ADDR + (offset)) +#define D11IHR_ADDR(offset) (D11_AXI_BASE_ADDR + 0x400 + (2 * (offset))) +#define D11SHM_ADDR(offset) (D11_SHM_BASE_ADDR + (offset)) + +/* MacControl register */ +#define D11_MACCONTROL_REG D11REG_ADDR(0x120) +#define D11_MACCONTROL_REG_WAKE 0x4000000 + +/* HUDI Sequence SHM bits */ +#define C_DS1_CTRL_SDIO_DS1_SLEEP 0x1 +#define C_DS1_CTRL_SDIO_MAC_ON 0x2 +#define C_DS1_CTRL_SDIO_RADIO_PHY_ON 0x4 +#define C_DS1_CTRL_SDIO_DS1_EXIT 0x8 +#define C_DS1_CTRL_PROC_DONE 0x100 +#define C_DS1_CTRL_REQ_VALID 0x200 + +/* M_ULP_WAKEIND bits */ +#define C_WATCHDOG_EXPIRY BIT(0) +#define C_FCBS_ERROR BIT(1) +#define C_RETX_FAILURE BIT(2) +#define C_HOST_WAKEUP BIT(3) +#define C_INVALID_FCBS_BLOCK BIT(4) +#define C_HUDI_DS1_EXIT BIT(5) +#define C_LOB_SLEEP BIT(6) +#define C_DS1_PHY_TXERR BIT(9) +#define C_DS1_WAKE_TIMER BIT(10) + +#define PHYTX_ERR_BLK_SIZE 18 +#define D11SHM_FIRST2BYTE_MASK 0xFFFF0000 +#define D11SHM_SECOND2BYTE_MASK 0x0000FFFF +#define D11SHM_2BYTE_SHIFT 16 + +#define D11SHM_RD(sdh, offset, ret) \ + brcmf_sdiod_readl(sdh, D11SHM_ADDR(offset), ret) + +/* SHM Read is motified based on SHM 4 byte alignment as SHM size is 2 bytes and + * 2 byte is currently not working on FMAC + * If SHM address is not 4 byte aligned, then right shift by 16 + * otherwise, mask the first two MSB bytes + * Suppose data in address 7260 is 0x440002 and it is 4 byte aligned + * Correct SHM value is 0x2 for this SHM offset and next SHM value is 0x44 + */ +#define D11SHM_RDW(sdh, offset, ret) \ + ((offset % 4) ? \ + (brcmf_sdiod_readl(sdh, D11SHM_ADDR(offset), ret) \ + >> D11SHM_2BYTE_SHIFT) : \ + (brcmf_sdiod_readl(sdh, D11SHM_ADDR(offset), ret) \ + & D11SHM_SECOND2BYTE_MASK)) + +/* SHM is of size 2 bytes, 4 bytes write will overwrite other SHM's + * First read 4 bytes and then clear the required two bytes based on + * 4 byte alignment, then update the required value and write the + * 4 byte value now + */ +#define D11SHM_WR(sdh, offset, val, mask, ret) \ + do { \ + if ((offset) % 4) \ + val = (val & D11SHM_SECOND2BYTE_MASK) | \ + ((mask) << D11SHM_2BYTE_SHIFT); \ + else \ + val = (mask) | (val & D11SHM_FIRST2BYTE_MASK); \ + brcmf_sdiod_writel(sdh, D11SHM_ADDR(offset), val, ret); \ + } while (0) +#define D11REG_WR(sdh, addr, val, ret) \ + brcmf_sdiod_writel(sdh, addr, val, ret) + +#define D11REG_RD(sdh, addr, ret) \ + brcmf_sdiod_readl(sdh, addr, ret) + #endif /* BRCMFMAC_SDIO_H */ diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/usb.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/usb.c index 8afbf529c74503..bf2ec5d3f493fd 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/usb.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/usb.c @@ -1200,7 +1200,7 @@ static void brcmf_usb_probe_phase2(struct device *dev, int ret, goto error; /* Attach to the common driver interface */ - ret = brcmf_attach(devinfo->dev); + ret = brcmf_attach(devinfo->dev, true); if (ret) goto error; @@ -1277,7 +1277,7 @@ static int brcmf_usb_probe_cb(struct brcmf_usbdev_info *devinfo, ret = brcmf_alloc(devinfo->dev, devinfo->settings); if (ret) goto fail; - ret = brcmf_attach(devinfo->dev); + ret = brcmf_attach(devinfo->dev, true); if (ret) goto fail; /* we are done */ diff --git a/drivers/net/wireless/broadcom/brcm80211/include/chipcommon.h b/drivers/net/wireless/broadcom/brcm80211/include/chipcommon.h index 0340bba968688f..090a75bcd728a1 100644 --- a/drivers/net/wireless/broadcom/brcm80211/include/chipcommon.h +++ b/drivers/net/wireless/broadcom/brcm80211/include/chipcommon.h @@ -308,4 +308,6 @@ struct chipcregs { */ #define PMU_MAX_TRANSITION_DLY 15000 +#define DEFAULT_43012_MIN_RES_MASK 0x0f8bfe77 + #endif /* _SBCHIPC_H */ diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c index cc74682dc0d4e9..951e4551b34ff9 100644 --- a/drivers/nvme/host/pci.c +++ b/drivers/nvme/host/pci.c @@ -1969,6 +1969,7 @@ static void nvme_free_host_mem(struct nvme_dev *dev) dev->nr_host_mem_descs = 0; } +#if 0 static int __nvme_alloc_host_mem(struct nvme_dev *dev, u64 preferred, u32 chunk_size) { @@ -2037,9 +2038,11 @@ static int __nvme_alloc_host_mem(struct nvme_dev *dev, u64 preferred, dev->host_mem_descs = NULL; return -ENOMEM; } +#endif static int nvme_alloc_host_mem(struct nvme_dev *dev, u64 min, u64 preferred) { +#if 0 u64 min_chunk = min_t(u64, preferred, PAGE_SIZE * MAX_ORDER_NR_PAGES); u64 hmminds = max_t(u32, dev->ctrl.hmminds * 4096, PAGE_SIZE * 2); u64 chunk_size; @@ -2052,6 +2055,7 @@ static int nvme_alloc_host_mem(struct nvme_dev *dev, u64 min, u64 preferred) nvme_free_host_mem(dev); } } +#endif return -ENOMEM; } diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig index d2c384f58028dc..c033ce7b769dfe 100644 --- a/drivers/nvmem/Kconfig +++ b/drivers/nvmem/Kconfig @@ -40,6 +40,18 @@ config NVMEM_APPLE_EFUSES This driver can also be built as a module. If so, the module will be called nvmem-apple-efuses. +config NVMEM_RASPBERRYPI_OTP + tristate "Raspberry Pi OTP support" + depends on (ARCH_BCM && RASPBERRYPI_FIRMWARE) || COMPILE_TEST + default ARCH_BCM && RASPBERRYPI_FIRMWARE + help + Say y here to enable support for accessing OTP on Raspberry Pi boards. + These are used to store non-volatile information such as serial number, + board revision and customer stored data. + + This driver can also be built as a module. If so, the module will + be called nvmem-raspberrypi-otp. + config NVMEM_BCM_OCOTP tristate "Broadcom On-Chip OTP Controller support" depends on ARCH_BCM_IPROC || COMPILE_TEST diff --git a/drivers/nvmem/Makefile b/drivers/nvmem/Makefile index cdd01fbf1313b5..27a03c856fd915 100644 --- a/drivers/nvmem/Makefile +++ b/drivers/nvmem/Makefile @@ -12,6 +12,8 @@ obj-y += layouts/ # Devices obj-$(CONFIG_NVMEM_APPLE_EFUSES) += nvmem-apple-efuses.o nvmem-apple-efuses-y := apple-efuses.o +obj-$(CONFIG_NVMEM_RASPBERRYPI_OTP) += nvmem-raspberrypi-otp.o +nvmem-raspberrypi-otp-y := raspberrypi-otp.o obj-$(CONFIG_NVMEM_BCM_OCOTP) += nvmem-bcm-ocotp.o nvmem-bcm-ocotp-y := bcm-ocotp.o obj-$(CONFIG_NVMEM_BRCM_NVRAM) += nvmem_brcm_nvram.o diff --git a/drivers/nvmem/raspberrypi-otp.c b/drivers/nvmem/raspberrypi-otp.c new file mode 100644 index 00000000000000..a7c735e9e96400 --- /dev/null +++ b/drivers/nvmem/raspberrypi-otp.c @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/** + * raspberrypi-otp.c + * + * nvmem driver using firmware mailbox to access otp + * + * Copyright (c) 2024, Raspberry Pi Ltd. + */ + +#include <linux/nvmem-provider.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <soc/bcm2835/raspberrypi-firmware.h> + +struct rpi_otp_priv { + struct rpi_firmware *fw; + u32 block; +}; + +#define MAX_ROWS 192 + +#define RPI_FIRMWARE_GET_USER_OTP 0x00030024 +#define RPI_FIRMWARE_SET_USER_OTP 0x00038024 + +static int rpi_otp_read(void *context, unsigned int offset, void *val, + size_t bytes) +{ + struct rpi_otp_priv *priv = context; + int words = bytes / sizeof(u32); + int index = offset / sizeof(u32); + u32 data[3 + MAX_ROWS] = {priv->block, index, words}; + int err = 0; + + if (words > MAX_ROWS) + return -EINVAL; + + err = rpi_firmware_property(priv->fw, RPI_FIRMWARE_GET_USER_OTP, + &data, sizeof(data)); + if (err == 0) + memcpy(val, data + 3, bytes); + else + memset(val, 0xee, bytes); + return err; +} + +static int rpi_otp_write(void *context, unsigned int offset, void *val, + size_t bytes) +{ + struct rpi_otp_priv *priv = context; + int words = bytes / sizeof(u32); + int index = offset / sizeof(u32); + u32 data[3 + MAX_ROWS] = {priv->block, index, words}; + + if (bytes > MAX_ROWS * sizeof(u32)) + return -EINVAL; + + memcpy(data + 3, val, bytes); + return rpi_firmware_property(priv->fw, RPI_FIRMWARE_SET_USER_OTP, + &data, sizeof(data)); +} + +static int rpi_otp_probe(struct platform_device *pdev) +{ + struct rpi_otp_priv *priv; + struct nvmem_config config = { + .dev = &pdev->dev, + .reg_read = rpi_otp_read, + .reg_write = rpi_otp_write, + .stride = sizeof(u32), + .word_size = sizeof(u32), + .type = NVMEM_TYPE_OTP, + .root_only = true, + }; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct device_node *fw_node; + struct rpi_firmware *fw; + u32 reg[2]; + const char *pname; + + if (of_property_read_u32_array(np, "reg", reg, ARRAY_SIZE(reg))) { + dev_err(dev, "Failed to parse \"reg\" property\n"); + return -EINVAL; + } + + pname = of_get_property(np, "name", NULL); + if (!pname) { + dev_err(dev, "Failed to parse \"name\" property\n"); + return -ENOENT; + } + + config.name = pname; + config.size = reg[1] * sizeof(u32); + config.read_only = !of_property_read_bool(np, "rw"); + + fw_node = of_parse_phandle(np, "firmware", 0); + if (!fw_node) { + dev_err(dev, "Missing firmware node\n"); + return -ENOENT; + } + + fw = rpi_firmware_get(fw_node); + if (!fw) + return -EPROBE_DEFER; + + priv = devm_kzalloc(config.dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->fw = fw; + priv->block = reg[0]; + config.priv = priv; + + return PTR_ERR_OR_ZERO(devm_nvmem_register(config.dev, &config)); +} + +static const struct of_device_id rpi_otp_of_match[] = { + { .compatible = "raspberrypi,rpi-otp", }, + {} +}; + +MODULE_DEVICE_TABLE(of, rpi_otp_of_match); + +static struct platform_driver rpi_otp_driver = { + .driver = { + .name = "rpi_otp", + .of_match_table = rpi_otp_of_match, + }, + .probe = rpi_otp_probe, +}; + +module_platform_driver(rpi_otp_driver); + +MODULE_AUTHOR("Dom Cobley <popcornmix@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/of/Kconfig b/drivers/of/Kconfig index 0e2d608c3e207d..c634bd8a084327 100644 --- a/drivers/of/Kconfig +++ b/drivers/of/Kconfig @@ -120,4 +120,15 @@ config OF_OVERLAY_KUNIT_TEST config OF_NUMA bool +config OF_DMA_DEFAULT_COHERENT + # arches should select this if DMA is coherent by default for OF devices + bool + +config OF_CONFIGFS + bool "Device Tree Overlay ConfigFS interface" + select CONFIGFS_FS + select OF_OVERLAY + help + Enable a simple user-space driven DT overlay interface. + endif # OF diff --git a/drivers/of/Makefile b/drivers/of/Makefile index 379a0afcbdc0bf..f294b36207f80c 100644 --- a/drivers/of/Makefile +++ b/drivers/of/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 obj-y = base.o cpu.o device.o module.o platform.o property.o obj-$(CONFIG_OF_KOBJ) += kobj.o +obj-$(CONFIG_OF_CONFIGFS) += configfs.o obj-$(CONFIG_OF_DYNAMIC) += dynamic.o obj-$(CONFIG_OF_FLATTREE) += fdt.o empty_root.dtb.o obj-$(CONFIG_OF_EARLY_FLATTREE) += fdt_address.o diff --git a/drivers/of/configfs.c b/drivers/of/configfs.c new file mode 100644 index 00000000000000..0e6bccb8ff2ef5 --- /dev/null +++ b/drivers/of/configfs.c @@ -0,0 +1,277 @@ +/* + * Configfs entries for device-tree + * + * Copyright (C) 2013 - Pantelis Antoniou <panto@antoniou-consulting.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ +#include <linux/ctype.h> +#include <linux/cpu.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_fdt.h> +#include <linux/spinlock.h> +#include <linux/slab.h> +#include <linux/proc_fs.h> +#include <linux/configfs.h> +#include <linux/types.h> +#include <linux/stat.h> +#include <linux/limits.h> +#include <linux/file.h> +#include <linux/vmalloc.h> +#include <linux/firmware.h> +#include <linux/sizes.h> + +#include "of_private.h" + +struct cfs_overlay_item { + struct config_item item; + + char path[PATH_MAX]; + + const struct firmware *fw; + struct device_node *overlay; + int ov_id; + + void *dtbo; + int dtbo_size; +}; + +static inline struct cfs_overlay_item *to_cfs_overlay_item( + struct config_item *item) +{ + return item ? container_of(item, struct cfs_overlay_item, item) : NULL; +} + +static ssize_t cfs_overlay_item_path_show(struct config_item *item, + char *page) +{ + struct cfs_overlay_item *overlay = to_cfs_overlay_item(item); + return sprintf(page, "%s\n", overlay->path); +} + +static ssize_t cfs_overlay_item_path_store(struct config_item *item, + const char *page, size_t count) +{ + struct cfs_overlay_item *overlay = to_cfs_overlay_item(item); + const char *p = page; + char *s; + int err; + + /* if it's set do not allow changes */ + if (overlay->path[0] != '\0' || overlay->dtbo_size > 0) + return -EPERM; + + /* copy to path buffer (and make sure it's always zero terminated */ + count = snprintf(overlay->path, sizeof(overlay->path) - 1, "%s", p); + overlay->path[sizeof(overlay->path) - 1] = '\0'; + + /* strip trailing newlines */ + s = overlay->path + strlen(overlay->path); + while (s > overlay->path && *--s == '\n') + *s = '\0'; + + pr_debug("%s: path is '%s'\n", __func__, overlay->path); + + err = request_firmware(&overlay->fw, overlay->path, NULL); + if (err != 0) + goto out_err; + + err = of_overlay_fdt_apply((void *)overlay->fw->data, + (u32)overlay->fw->size, &overlay->ov_id, NULL); + if (err != 0) + goto out_err; + + return count; + +out_err: + + release_firmware(overlay->fw); + overlay->fw = NULL; + + overlay->path[0] = '\0'; + return err; +} + +static ssize_t cfs_overlay_item_status_show(struct config_item *item, + char *page) +{ + struct cfs_overlay_item *overlay = to_cfs_overlay_item(item); + + return sprintf(page, "%s\n", + overlay->ov_id > 0 ? "applied" : "unapplied"); +} + +CONFIGFS_ATTR(cfs_overlay_item_, path); +CONFIGFS_ATTR_RO(cfs_overlay_item_, status); + +static struct configfs_attribute *cfs_overlay_attrs[] = { + &cfs_overlay_item_attr_path, + &cfs_overlay_item_attr_status, + NULL, +}; + +static ssize_t cfs_overlay_item_dtbo_read(struct config_item *item, + void *buf, size_t max_count) +{ + struct cfs_overlay_item *overlay = to_cfs_overlay_item(item); + + pr_debug("%s: buf=%p max_count=%zu\n", __func__, + buf, max_count); + + if (overlay->dtbo == NULL) + return 0; + + /* copy if buffer provided */ + if (buf != NULL) { + /* the buffer must be large enough */ + if (overlay->dtbo_size > max_count) + return -ENOSPC; + + memcpy(buf, overlay->dtbo, overlay->dtbo_size); + } + + return overlay->dtbo_size; +} + +static ssize_t cfs_overlay_item_dtbo_write(struct config_item *item, + const void *buf, size_t count) +{ + struct cfs_overlay_item *overlay = to_cfs_overlay_item(item); + int err; + + /* if it's set do not allow changes */ + if (overlay->path[0] != '\0' || overlay->dtbo_size > 0) + return -EPERM; + + /* copy the contents */ + overlay->dtbo = kmemdup(buf, count, GFP_KERNEL); + if (overlay->dtbo == NULL) + return -ENOMEM; + + overlay->dtbo_size = count; + + err = of_overlay_fdt_apply(overlay->dtbo, overlay->dtbo_size, + &overlay->ov_id, NULL); + if (err != 0) + goto out_err; + + return count; + +out_err: + kfree(overlay->dtbo); + overlay->dtbo = NULL; + overlay->dtbo_size = 0; + overlay->ov_id = 0; + + return err; +} + +CONFIGFS_BIN_ATTR(cfs_overlay_item_, dtbo, NULL, SZ_1M); + +static struct configfs_bin_attribute *cfs_overlay_bin_attrs[] = { + &cfs_overlay_item_attr_dtbo, + NULL, +}; + +static void cfs_overlay_release(struct config_item *item) +{ + struct cfs_overlay_item *overlay = to_cfs_overlay_item(item); + + if (overlay->ov_id > 0) + of_overlay_remove(&overlay->ov_id); + if (overlay->fw) + release_firmware(overlay->fw); + /* kfree with NULL is safe */ + kfree(overlay->dtbo); + kfree(overlay); +} + +static struct configfs_item_operations cfs_overlay_item_ops = { + .release = cfs_overlay_release, +}; + +static struct config_item_type cfs_overlay_type = { + .ct_item_ops = &cfs_overlay_item_ops, + .ct_attrs = cfs_overlay_attrs, + .ct_bin_attrs = cfs_overlay_bin_attrs, + .ct_owner = THIS_MODULE, +}; + +static struct config_item *cfs_overlay_group_make_item( + struct config_group *group, const char *name) +{ + struct cfs_overlay_item *overlay; + + overlay = kzalloc(sizeof(*overlay), GFP_KERNEL); + if (!overlay) + return ERR_PTR(-ENOMEM); + + config_item_init_type_name(&overlay->item, name, &cfs_overlay_type); + return &overlay->item; +} + +static void cfs_overlay_group_drop_item(struct config_group *group, + struct config_item *item) +{ + struct cfs_overlay_item *overlay = to_cfs_overlay_item(item); + + config_item_put(&overlay->item); +} + +static struct configfs_group_operations overlays_ops = { + .make_item = cfs_overlay_group_make_item, + .drop_item = cfs_overlay_group_drop_item, +}; + +static struct config_item_type overlays_type = { + .ct_group_ops = &overlays_ops, + .ct_owner = THIS_MODULE, +}; + +static struct configfs_group_operations of_cfs_ops = { + /* empty - we don't allow anything to be created */ +}; + +static struct config_item_type of_cfs_type = { + .ct_group_ops = &of_cfs_ops, + .ct_owner = THIS_MODULE, +}; + +struct config_group of_cfs_overlay_group; + +static struct configfs_subsystem of_cfs_subsys = { + .su_group = { + .cg_item = { + .ci_namebuf = "device-tree", + .ci_type = &of_cfs_type, + }, + }, + .su_mutex = __MUTEX_INITIALIZER(of_cfs_subsys.su_mutex), +}; + +static int __init of_cfs_init(void) +{ + int ret; + + pr_info("%s\n", __func__); + + config_group_init(&of_cfs_subsys.su_group); + config_group_init_type_name(&of_cfs_overlay_group, "overlays", + &overlays_type); + configfs_add_default_group(&of_cfs_overlay_group, + &of_cfs_subsys.su_group); + + ret = configfs_register_subsystem(&of_cfs_subsys); + if (ret != 0) { + pr_err("%s: failed to register subsys\n", __func__); + goto out; + } + pr_info("%s: OK\n", __func__); +out: + return ret; +} +late_initcall(of_cfs_init); diff --git a/drivers/of/overlay.c b/drivers/of/overlay.c index cbdecccca09745..1ebe2ea15f12d7 100644 --- a/drivers/of/overlay.c +++ b/drivers/of/overlay.c @@ -241,6 +241,8 @@ static struct property *dup_and_fixup_symbol_prop( if (!target_path) return NULL; target_path_len = strlen(target_path); + if (!strcmp(target_path, "/")) + target_path_len = 0; new_prop = kzalloc(sizeof(*new_prop), GFP_KERNEL); if (!new_prop) diff --git a/drivers/pci/controller/pcie-brcmstb.c b/drivers/pci/controller/pcie-brcmstb.c index 9321280f6edbab..4917c74617844c 100644 --- a/drivers/pci/controller/pcie-brcmstb.c +++ b/drivers/pci/controller/pcie-brcmstb.c @@ -48,6 +48,15 @@ #define PCIE_RC_CFG_PRIV1_LINK_CAPABILITY 0x04dc #define PCIE_RC_CFG_PRIV1_LINK_CAPABILITY_ASPM_SUPPORT_MASK 0xc00 +#define PCIE_RC_TL_VDM_CTL0 0x0a20 +#define PCIE_RC_TL_VDM_CTL0_VDM_ENABLED_MASK 0x10000 +#define PCIE_RC_TL_VDM_CTL0_VDM_IGNORETAG_MASK 0x20000 +#define PCIE_RC_TL_VDM_CTL0_VDM_IGNOREVNDRID_MASK 0x40000 + +#define PCIE_RC_TL_VDM_CTL1 0x0a0c +#define PCIE_RC_TL_VDM_CTL1_VDM_VNDRID0_MASK 0x0000ffff +#define PCIE_RC_TL_VDM_CTL1_VDM_VNDRID1_MASK 0xffff0000 + #define PCIE_RC_CFG_PRIV1_ROOT_CAP 0x4f8 #define PCIE_RC_CFG_PRIV1_ROOT_CAP_L1SS_MODE_MASK 0xf8 @@ -55,6 +64,10 @@ #define PCIE_RC_DL_MDIO_WR_DATA 0x1104 #define PCIE_RC_DL_MDIO_RD_DATA 0x1108 +#define PCIE_RC_PL_PHY_CTL_15 0x184c +#define PCIE_RC_PL_PHY_CTL_15_DIS_PLL_PD_MASK 0x400000 +#define PCIE_RC_PL_PHY_CTL_15_PM_CLK_PERIOD_MASK 0xff + #define PCIE_MISC_MISC_CTRL 0x4008 #define PCIE_MISC_MISC_CTRL_PCIE_RCB_64B_MODE_MASK 0x80 #define PCIE_MISC_MISC_CTRL_PCIE_RCB_MPS_MODE_MASK 0x400 @@ -85,9 +98,15 @@ #define PCIE_BRCM_MAX_INBOUND_WINS 16 #define PCIE_MISC_RC_BAR1_CONFIG_LO 0x402c #define PCIE_MISC_RC_BAR1_CONFIG_LO_SIZE_MASK 0x1f +#define PCIE_MISC_RC_BAR1_CONFIG_HI 0x4030 -#define PCIE_MISC_RC_BAR4_CONFIG_LO 0x40d4 +#define PCIE_MISC_RC_BAR2_CONFIG_LO 0x4034 +#define PCIE_MISC_RC_BAR2_CONFIG_LO_SIZE_MASK 0x1f +#define PCIE_MISC_RC_BAR2_CONFIG_HI 0x4038 +#define PCIE_MISC_RC_BAR3_CONFIG_LO 0x403c +#define PCIE_MISC_RC_BAR3_CONFIG_LO_SIZE_MASK 0x1f +#define PCIE_MISC_RC_BAR3_CONFIG_HI 0x4040 #define PCIE_MISC_MSI_BAR_CONFIG_LO 0x4044 #define PCIE_MISC_MSI_BAR_CONFIG_HI 0x4048 @@ -96,12 +115,15 @@ #define PCIE_MISC_MSI_DATA_CONFIG_VAL_32 0xffe06540 #define PCIE_MISC_MSI_DATA_CONFIG_VAL_8 0xfff86540 +#define PCIE_MISC_RC_CONFIG_RETRY_TIMEOUT 0x405c + #define PCIE_MISC_PCIE_CTRL 0x4064 #define PCIE_MISC_PCIE_CTRL_PCIE_L23_REQUEST_MASK 0x1 #define PCIE_MISC_PCIE_CTRL_PCIE_PERSTB_MASK 0x4 #define PCIE_MISC_PCIE_STATUS 0x4068 #define PCIE_MISC_PCIE_STATUS_PCIE_PORT_MASK 0x80 +#define PCIE_MISC_PCIE_STATUS_PCIE_PORT_MASK_2712 0x40 #define PCIE_MISC_PCIE_STATUS_PCIE_DL_ACTIVE_MASK 0x20 #define PCIE_MISC_PCIE_STATUS_PCIE_PHYLINKUP_MASK 0x10 #define PCIE_MISC_PCIE_STATUS_PCIE_LINK_IN_L23_MASK 0x40 @@ -127,6 +149,7 @@ PCIE_MISC_CPU_2_PCIE_MEM_WIN0_LIMIT_HI + ((win) * 8) #define PCIE_MISC_HARD_PCIE_HARD_DEBUG_CLKREQ_DEBUG_ENABLE_MASK 0x2 +#define PCIE_MISC_HARD_PCIE_HARD_DEBUG_PERST_ASSERT_MASK 0x8 #define PCIE_MISC_HARD_PCIE_HARD_DEBUG_L1SS_ENABLE_MASK 0x200000 #define PCIE_MISC_HARD_PCIE_HARD_DEBUG_SERDES_IDDQ_MASK 0x08000000 #define PCIE_BMIPS_MISC_HARD_PCIE_HARD_DEBUG_SERDES_IDDQ_MASK 0x00800000 @@ -134,10 +157,74 @@ (PCIE_MISC_HARD_PCIE_HARD_DEBUG_CLKREQ_DEBUG_ENABLE_MASK | \ PCIE_MISC_HARD_PCIE_HARD_DEBUG_L1SS_ENABLE_MASK) + #define PCIE_MISC_UBUS_BAR1_CONFIG_REMAP 0x40ac #define PCIE_MISC_UBUS_BAR1_CONFIG_REMAP_ACCESS_EN_MASK BIT(0) #define PCIE_MISC_UBUS_BAR4_CONFIG_REMAP 0x410c +#define PCIE_MISC_CTRL_1 0x40A0 +#define PCIE_MISC_CTRL_1_OUTBOUND_TC_MASK 0xf +#define PCIE_MISC_CTRL_1_OUTBOUND_NO_SNOOP_MASK BIT(3) +#define PCIE_MISC_CTRL_1_OUTBOUND_RO_MASK BIT(4) +#define PCIE_MISC_CTRL_1_EN_VDM_QOS_CONTROL_MASK BIT(5) + +#define PCIE_MISC_UBUS_CTRL 0x40a4 +#define PCIE_MISC_UBUS_CTRL_UBUS_PCIE_REPLY_ERR_DIS_MASK BIT(13) +#define PCIE_MISC_UBUS_CTRL_UBUS_PCIE_REPLY_DECERR_DIS_MASK BIT(19) + +#define PCIE_MISC_UBUS_TIMEOUT 0x40A8 + +#define PCIE_MISC_UBUS_BAR1_CONFIG_REMAP 0x40ac +#define PCIE_MISC_UBUS_BAR1_CONFIG_REMAP_ACCESS_ENABLE_MASK BIT(0) +#define PCIE_MISC_UBUS_BAR1_CONFIG_REMAP_HI 0x40b0 + +#define PCIE_MISC_UBUS_BAR2_CONFIG_REMAP 0x40b4 +#define PCIE_MISC_UBUS_BAR2_CONFIG_REMAP_ACCESS_ENABLE_MASK BIT(0) + +/* Additional RC BARs */ +#define PCIE_MISC_RC_BAR_CONFIG_LO_SIZE_MASK 0x1f +#define PCIE_MISC_RC_BAR4_CONFIG_LO 0x40d4 +#define PCIE_MISC_RC_BAR4_CONFIG_HI 0x40d8 +/* ... */ +#define PCIE_MISC_RC_BAR10_CONFIG_LO 0x4104 +#define PCIE_MISC_RC_BAR10_CONFIG_HI 0x4108 + +#define PCIE_MISC_UBUS_BAR_CONFIG_REMAP_ENABLE 0x1 +#define PCIE_MISC_UBUS_BAR_CONFIG_REMAP_LO_MASK 0xfffff000 +#define PCIE_MISC_UBUS_BAR_CONFIG_REMAP_HI_MASK 0xff +#define PCIE_MISC_UBUS_BAR4_CONFIG_REMAP_LO 0x410c +#define PCIE_MISC_UBUS_BAR4_CONFIG_REMAP_HI 0x4110 +/* ... */ +#define PCIE_MISC_UBUS_BAR10_CONFIG_REMAP_LO 0x413c +#define PCIE_MISC_UBUS_BAR10_CONFIG_REMAP_HI 0x4140 + +/* AXI priority forwarding - automatic level-based */ +#define PCIE_MISC_TC_QUEUE_TO_QOS_MAP(x) (0x4160 - (x) * 4) +/* Defined in quarter-fullness */ +#define QUEUE_THRESHOLD_34_TO_QOS_MAP_SHIFT 12 +#define QUEUE_THRESHOLD_23_TO_QOS_MAP_SHIFT 8 +#define QUEUE_THRESHOLD_12_TO_QOS_MAP_SHIFT 4 +#define QUEUE_THRESHOLD_01_TO_QOS_MAP_SHIFT 0 +#define QUEUE_THRESHOLD_MASK 0xf + +/* VDM messages indexing TCs to AXI priorities */ +/* Indexes 8-15 */ +#define PCIE_MISC_VDM_PRIORITY_TO_QOS_MAP_HI 0x4164 +/* Indexes 0-7 */ +#define PCIE_MISC_VDM_PRIORITY_TO_QOS_MAP_LO 0x4168 +#define VDM_PRIORITY_TO_QOS_MAP_SHIFT(x) (4 * (x)) +#define VDM_PRIORITY_TO_QOS_MAP_MASK 0xf + +#define PCIE_MISC_AXI_INTF_CTRL 0x416C +#define AXI_EN_RCLK_QOS_ARRAY_FIX BIT(13) +#define AXI_EN_QOS_UPDATE_TIMING_FIX BIT(12) +#define AXI_DIS_QOS_GATING_IN_MASTER BIT(11) +#define AXI_REQFIFO_EN_QOS_PROPAGATION BIT(7) +#define AXI_BRIDGE_LOW_LATENCY_MODE BIT(6) +#define AXI_MASTER_MAX_OUTSTANDING_REQUESTS_MASK 0x3f + +#define PCIE_MISC_AXI_READ_ERROR_DATA 0x4170 + #define PCIE_MSI_INTR2_BASE 0x4500 /* Offsets from INTR2_CPU and MSI_INTR2 BASE offsets */ @@ -226,6 +313,7 @@ enum pcie_soc_base { BCM7425, BCM7435, BCM7712, + BCM2712, }; struct inbound_win { @@ -257,7 +345,7 @@ struct brcm_msi { struct mutex lock; /* guards the alloc/free operations */ u64 target_addr; int irq; - DECLARE_BITMAP(used, BRCM_INT_PCI_MSI_NR); + DECLARE_BITMAP(used, 64); bool legacy; /* Some chips have MSIs in bits [31..24] of a shared register. */ int legacy_shift; @@ -291,6 +379,10 @@ struct brcm_pcie { bool ep_wakeup_capable; bool has_phy; u8 num_inbound_wins; + bool l1ss; + bool rcb_mps_mode; + u32 qos_map; + u32 tperst_clk_ms; }; static inline bool is_bmips(const struct brcm_pcie *pcie) @@ -309,8 +401,8 @@ static int brcm_pcie_encode_ibar_size(u64 size) if (log2_in >= 12 && log2_in <= 15) /* Covers 4KB to 32KB (inclusive) */ return (log2_in - 12) + 0x1c; - else if (log2_in >= 16 && log2_in <= 35) - /* Covers 64KB to 32GB, (inclusive) */ + else if (log2_in >= 16 && log2_in <= 36) + /* Covers 64KB to 64GB, (inclusive) */ return log2_in - 15; /* Something is awry so disable */ return 0; @@ -399,12 +491,43 @@ static int brcm_pcie_set_ssc(struct brcm_pcie *pcie) return ssc && pll ? 0 : -EIO; } +static void brcm_pcie_munge_pll(struct brcm_pcie *pcie) +{ + //print "MDIO block 0x1600 written per Dannys instruction" + //tmp = pcie_mdio_write(phyad, &h16&, &h50b9&) + //tmp = pcie_mdio_write(phyad, &h17&, &hbd1a&) + //tmp = pcie_mdio_write(phyad, &h1b&, &h5030&) + //tmp = pcie_mdio_write(phyad, &h1e&, &h0007&) + + u32 tmp; + int ret, i; + u8 regs[] = { 0x16, 0x17, 0x18, 0x19, 0x1b, 0x1c, 0x1e }; + u16 data[] = { 0x50b9, 0xbda1, 0x0094, 0x97b4, 0x5030, 0x5030, 0x0007 }; + + ret = brcm_pcie_mdio_write(pcie->base, MDIO_PORT0, SET_ADDR_OFFSET, + 0x1600); + for (i = 0; i < ARRAY_SIZE(regs); i++) { + brcm_pcie_mdio_read(pcie->base, MDIO_PORT0, regs[i], &tmp); + dev_dbg(pcie->dev, "PCIE MDIO pre_refclk 0x%02x = 0x%04x\n", + regs[i], tmp); + } + for (i = 0; i < ARRAY_SIZE(regs); i++) { + brcm_pcie_mdio_write(pcie->base, MDIO_PORT0, regs[i], data[i]); + brcm_pcie_mdio_read(pcie->base, MDIO_PORT0, regs[i], &tmp); + dev_dbg(pcie->dev, "PCIE MDIO post_refclk 0x%02x = 0x%04x\n", + regs[i], tmp); + } + usleep_range(100, 200); +} + /* Limits operation to a specific generation (1, 2, or 3) */ static void brcm_pcie_set_gen(struct brcm_pcie *pcie, int gen) { u16 lnkctl2 = readw(pcie->base + BRCM_PCIE_CAP_REGS + PCI_EXP_LNKCTL2); u32 lnkcap = readl(pcie->base + BRCM_PCIE_CAP_REGS + PCI_EXP_LNKCAP); + dev_info(pcie->dev, "Forcing gen %d\n", pcie->gen); + lnkcap = (lnkcap & ~PCI_EXP_LNKCAP_SLS) | gen; writel(lnkcap, pcie->base + BRCM_PCIE_CAP_REGS + PCI_EXP_LNKCAP); @@ -456,6 +579,73 @@ static void brcm_pcie_set_outbound_win(struct brcm_pcie *pcie, writel(tmp, pcie->base + PCIE_MEM_WIN0_LIMIT_HI(win)); } +static void brcm_pcie_set_tc_qos(struct brcm_pcie *pcie) +{ + int i; + u32 reg; + + if (pcie->soc_base != BCM2712) + return; + + /* Disable broken QOS forwarding search. Set chicken bits for 2712D0 */ + reg = readl(pcie->base + PCIE_MISC_AXI_INTF_CTRL); + reg &= ~AXI_REQFIFO_EN_QOS_PROPAGATION; + reg |= AXI_EN_RCLK_QOS_ARRAY_FIX | AXI_EN_QOS_UPDATE_TIMING_FIX | + AXI_DIS_QOS_GATING_IN_MASTER; + writel(reg, pcie->base + PCIE_MISC_AXI_INTF_CTRL); + + /* + * If the QOS_UPDATE_TIMING_FIX bit is Reserved-0, then this is a + * 2712C1 chip, or a single-lane RC. Use the best-effort alternative + * which is to partially throttle AXI requests in-flight to the SDC. + */ + reg = readl(pcie->base + PCIE_MISC_AXI_INTF_CTRL); + if (!(reg & AXI_EN_QOS_UPDATE_TIMING_FIX)) { + reg &= ~AXI_MASTER_MAX_OUTSTANDING_REQUESTS_MASK; + reg |= 15; + writel(reg, pcie->base + PCIE_MISC_AXI_INTF_CTRL); + } + + /* Disable VDM reception by default - QoS map defaults to 0 */ + reg = readl(pcie->base + PCIE_MISC_CTRL_1); + reg &= ~PCIE_MISC_CTRL_1_EN_VDM_QOS_CONTROL_MASK; + writel(reg, pcie->base + PCIE_MISC_CTRL_1); + + if (!of_property_read_u32(pcie->np, "brcm,fifo-qos-map", &pcie->qos_map)) { + /* + * Backpressure mode - bottom 4 nibbles are QoS for each + * quartile of FIFO level. Each TC gets the same map, because + * this mode is intended for nonrealtime EPs. + */ + + pcie->qos_map &= 0x0000ffff; + for (i = 0; i < 8; i++) + writel(pcie->qos_map, pcie->base + PCIE_MISC_TC_QUEUE_TO_QOS_MAP(i)); + + return; + } + + if (!of_property_read_u32(pcie->np, "brcm,vdm-qos-map", &pcie->qos_map)) { + + reg = readl(pcie->base + PCIE_MISC_CTRL_1); + reg |= PCIE_MISC_CTRL_1_EN_VDM_QOS_CONTROL_MASK; + writel(reg, pcie->base + PCIE_MISC_CTRL_1); + + /* No forwarding means no point separating panic priorities from normal */ + writel(pcie->qos_map, pcie->base + PCIE_MISC_VDM_PRIORITY_TO_QOS_MAP_LO); + writel(pcie->qos_map, pcie->base + PCIE_MISC_VDM_PRIORITY_TO_QOS_MAP_HI); + + /* Match Vendor ID of 0 */ + writel(0, pcie->base + PCIE_RC_TL_VDM_CTL1); + /* Forward VDMs to priority interface - at least the rx counters work */ + reg = readl(pcie->base + PCIE_RC_TL_VDM_CTL0); + reg |= PCIE_RC_TL_VDM_CTL0_VDM_ENABLED_MASK | + PCIE_RC_TL_VDM_CTL0_VDM_IGNORETAG_MASK | + PCIE_RC_TL_VDM_CTL0_VDM_IGNOREVNDRID_MASK; + writel(reg, pcie->base + PCIE_RC_TL_VDM_CTL0); + } +} + static struct irq_chip brcm_msi_irq_chip = { .name = "BRCM STB PCIe MSI", .irq_ack = irq_chip_ack_parent, @@ -464,8 +654,8 @@ static struct irq_chip brcm_msi_irq_chip = { }; static struct msi_domain_info brcm_msi_domain_info = { - .flags = MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | - MSI_FLAG_NO_AFFINITY | MSI_FLAG_MULTI_PCI_MSI, + .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | + MSI_FLAG_NO_AFFINITY | MSI_FLAG_MULTI_PCI_MSI | MSI_FLAG_PCI_MSIX), .chip = &brcm_msi_irq_chip, }; @@ -484,10 +674,23 @@ static void brcm_pcie_msi_isr(struct irq_desc *desc) status = readl(msi->intr_base + MSI_INT_STATUS); status >>= msi->legacy_shift; - for_each_set_bit(bit, &status, msi->nr) { - int ret; - ret = generic_handle_domain_irq(msi->inner_domain, bit); - if (ret) + for_each_set_bit(bit, &status, BRCM_INT_PCI_MSI_NR/*msi->nr*/) { + unsigned long virq; + bool found = false; + + virq = irq_find_mapping(msi->inner_domain, bit); + if (virq) { + found = true; + dev_dbg(dev, "MSI -> %ld\n", virq); + generic_handle_irq(virq); + } + virq = irq_find_mapping(msi->inner_domain, bit + 32); + if (virq) { + found = true; + dev_dbg(dev, "MSI -> %ld\n", virq); + generic_handle_irq(virq); + } + if (!found) dev_dbg(dev, "unexpected MSI\n"); } @@ -500,13 +703,13 @@ static void brcm_msi_compose_msi_msg(struct irq_data *data, struct msi_msg *msg) msg->address_lo = lower_32_bits(msi->target_addr); msg->address_hi = upper_32_bits(msi->target_addr); - msg->data = (0xffff & PCIE_MISC_MSI_DATA_CONFIG_VAL_32) | data->hwirq; + msg->data = (0xffff & PCIE_MISC_MSI_DATA_CONFIG_VAL_32) | (data->hwirq & 0x1f); } static void brcm_msi_ack_irq(struct irq_data *data) { struct brcm_msi *msi = irq_data_get_irq_chip_data(data); - const int shift_amt = data->hwirq + msi->legacy_shift; + const int shift_amt = (data->hwirq & 0x1f) + msi->legacy_shift; writel(1 << shift_amt, msi->intr_base + MSI_INT_CLR); } @@ -666,7 +869,7 @@ static int brcm_pcie_enable_msi(struct brcm_pcie *pcie) msi->legacy_shift = 24; } else { msi->intr_base = msi->base + PCIE_MSI_INTR2_BASE; - msi->nr = BRCM_INT_PCI_MSI_NR; + msi->nr = 64; //BRCM_INT_PCI_MSI_NR; msi->legacy_shift = 0; } @@ -688,6 +891,9 @@ static bool brcm_pcie_rc_mode(struct brcm_pcie *pcie) void __iomem *base = pcie->base; u32 val = readl(base + PCIE_MISC_PCIE_STATUS); + if (pcie->soc_base == BCM2712) + return !!FIELD_GET(PCIE_MISC_PCIE_STATUS_PCIE_PORT_MASK_2712, val) | 1; //XXX + return !!FIELD_GET(PCIE_MISC_PCIE_STATUS_PCIE_PORT_MASK, val); } @@ -780,6 +986,21 @@ static int brcm_pcie_bridge_sw_init_set_7278(struct brcm_pcie *pcie, u32 val) return 0; } +static int brcm_pcie_bridge_sw_init_set_2712(struct brcm_pcie *pcie, u32 val) +{ + int ret; + + if (WARN_ONCE(!pcie->bridge_reset, "missing bridge reset controller\n")) + return -EINVAL; + + if (val) + ret = reset_control_assert(pcie->bridge_reset); + else + ret = reset_control_deassert(pcie->bridge_reset); + + return ret; +} + static int brcm_pcie_perst_set_4908(struct brcm_pcie *pcie, u32 val) { int ret; @@ -810,6 +1031,18 @@ static int brcm_pcie_perst_set_7278(struct brcm_pcie *pcie, u32 val) return 0; } +static int brcm_pcie_perst_set_2712(struct brcm_pcie *pcie, u32 val) +{ + u32 tmp; + + /* Perst bit has moved and assert value is 0 */ + tmp = readl(pcie->base + PCIE_MISC_PCIE_CTRL); + u32p_replace_bits(&tmp, !val, PCIE_MISC_PCIE_CTRL_PCIE_PERSTB_MASK); + writel(tmp, pcie->base + PCIE_MISC_PCIE_CTRL); + + return 0; +} + static int brcm_pcie_perst_set_generic(struct brcm_pcie *pcie, u32 val) { u32 tmp; @@ -821,6 +1054,8 @@ static int brcm_pcie_perst_set_generic(struct brcm_pcie *pcie, u32 val) return 0; } + +#if 0 static void add_inbound_win(struct inbound_win *b, u8 *count, u64 size, u64 cpu_addr, u64 pci_offset) { @@ -1023,17 +1258,137 @@ static void set_inbound_win_registers(struct brcm_pcie *pcie, } } } +#endif + +static int brcm_pcie_get_rc_bar2_size_and_offset(struct brcm_pcie *pcie, + u64 *rc_bar2_size, + u64 *rc_bar2_offset) +{ + struct pci_host_bridge *bridge = pci_host_bridge_from_priv(pcie); + struct resource_entry *entry; + struct device *dev = pcie->dev; + u64 lowest_pcie_addr = ~(u64)0; + int ret, i = 0; + u64 size = 0; + + resource_list_for_each_entry(entry, &bridge->dma_ranges) { + u64 pcie_beg = entry->res->start - entry->offset; + + size += entry->res->end - entry->res->start + 1; + if (pcie_beg < lowest_pcie_addr) + lowest_pcie_addr = pcie_beg; + if (pcie->soc_base == BCM2711 || pcie->soc_base == BCM2712) + break; // Only consider the first entry + } + + if (lowest_pcie_addr == ~(u64)0) { + dev_err(dev, "DT node has no dma-ranges\n"); + return -EINVAL; + } + + ret = of_property_read_variable_u64_array(pcie->np, "brcm,scb-sizes", pcie->memc_size, 1, + PCIE_BRCM_MAX_MEMC); + + if (ret <= 0) { + /* Make an educated guess */ + pcie->num_memc = 1; + pcie->memc_size[0] = 1ULL << fls64(size - 1); + } else { + pcie->num_memc = ret; + } + + /* Each memc is viewed through a "port" that is a power of 2 */ + for (i = 0, size = 0; i < pcie->num_memc; i++) + size += pcie->memc_size[i]; + + /* System memory starts at this address in PCIe-space */ + *rc_bar2_offset = lowest_pcie_addr; + /* The sum of all memc views must also be a power of 2 */ + *rc_bar2_size = 1ULL << fls64(size - 1); + + /* + * We validate the inbound memory view even though we should trust + * whatever the device-tree provides. This is because of an HW issue on + * early Raspberry Pi 4's revisions (bcm2711). It turns out its + * firmware has to dynamically edit dma-ranges due to a bug on the + * PCIe controller integration, which prohibits any access above the + * lower 3GB of memory. Given this, we decided to keep the dma-ranges + * in check, avoiding hard to debug device-tree related issues in the + * future: + * + * The PCIe host controller by design must set the inbound viewport to + * be a contiguous arrangement of all of the system's memory. In + * addition, its size mut be a power of two. To further complicate + * matters, the viewport must start on a pcie-address that is aligned + * on a multiple of its size. If a portion of the viewport does not + * represent system memory -- e.g. 3GB of memory requires a 4GB + * viewport -- we can map the outbound memory in or after 3GB and even + * though the viewport will overlap the outbound memory the controller + * will know to send outbound memory downstream and everything else + * upstream. + * + * For example: + * + * - The best-case scenario, memory up to 3GB, is to place the inbound + * region in the first 4GB of pcie-space, as some legacy devices can + * only address 32bits. We would also like to put the MSI under 4GB + * as well, since some devices require a 32bit MSI target address. + * + * - If the system memory is 4GB or larger we cannot start the inbound + * region at location 0 (since we have to allow some space for + * outbound memory @ 3GB). So instead it will start at the 1x + * multiple of its size + */ + if (!*rc_bar2_size || (*rc_bar2_offset & (*rc_bar2_size - 1)) || + (*rc_bar2_offset < SZ_4G && *rc_bar2_offset > SZ_2G)) { + dev_err(dev, "Invalid rc_bar2_offset/size: size 0x%llx, off 0x%llx\n", + *rc_bar2_size, *rc_bar2_offset); + return -EINVAL; + } + + return 0; +} + +static int brcm_pcie_get_rc_bar_n(struct brcm_pcie *pcie, + int idx, + u64 *rc_bar_cpu, + u64 *rc_bar_size, + u64 *rc_bar_pci) +{ + struct pci_host_bridge *bridge = pci_host_bridge_from_priv(pcie); + struct resource_entry *entry; + int i = 0; + + resource_list_for_each_entry(entry, &bridge->dma_ranges) { + if (i == idx) { + *rc_bar_cpu = entry->res->start; + *rc_bar_size = entry->res->end - entry->res->start + 1; + *rc_bar_pci = entry->res->start - entry->offset; + return 0; + } + + i++; + } + + return -EINVAL; +} static int brcm_pcie_setup(struct brcm_pcie *pcie) { +#if 0 struct inbound_win inbound_wins[PCIE_BRCM_MAX_INBOUND_WINS]; +#endif + u64 rc_bar2_offset, rc_bar2_size; void __iomem *base = pcie->base; struct pci_host_bridge *bridge; struct resource_entry *entry; u32 tmp, burst, aspm_support; u8 num_out_wins = 0; +#if 0 int num_inbound_wins = 0; +#endif int memc, ret; + int count, i; /* Reset the bridge */ ret = pcie->bridge_sw_init_set(pcie, 1); @@ -1065,6 +1420,17 @@ static int brcm_pcie_setup(struct brcm_pcie *pcie) /* Wait for SerDes to be stable */ usleep_range(100, 200); + if (pcie->soc_base == BCM2712) { + /* Allow a 54MHz (xosc) refclk source */ + brcm_pcie_munge_pll(pcie); + /* Fix for L1SS errata */ + tmp = readl(base + PCIE_RC_PL_PHY_CTL_15); + tmp &= ~PCIE_RC_PL_PHY_CTL_15_PM_CLK_PERIOD_MASK; + /* PM clock period is 18.52ns (round down) */ + tmp |= 0x12; + writel(tmp, base + PCIE_RC_PL_PHY_CTL_15); + } + /* * SCB_MAX_BURST_SIZE is a two bit field. For GENERIC chips it * is encoded as 0=128, 1=256, 2=512, 3=Rsvd, for BCM7278 it @@ -1074,6 +1440,8 @@ static int brcm_pcie_setup(struct brcm_pcie *pcie) burst = 0x1; /* 256 bytes */ else if (pcie->soc_base == BCM2711) burst = 0x0; /* 128 bytes */ + else if (pcie->soc_base == BCM2712) + burst = 0x1; /* 128 bytes */ else if (pcie->soc_base == BCM7278) burst = 0x3; /* 512 bytes */ else @@ -1081,27 +1449,38 @@ static int brcm_pcie_setup(struct brcm_pcie *pcie) /* * Set SCB_MAX_BURST_SIZE, CFG_READ_UR_MODE, SCB_ACCESS_EN, - * RCB_MPS_MODE, RCB_64B_MODE + * RCB_MPS_MODE */ tmp = readl(base + PCIE_MISC_MISC_CTRL); u32p_replace_bits(&tmp, 1, PCIE_MISC_MISC_CTRL_SCB_ACCESS_EN_MASK); u32p_replace_bits(&tmp, 1, PCIE_MISC_MISC_CTRL_CFG_READ_UR_MODE_MASK); u32p_replace_bits(&tmp, burst, PCIE_MISC_MISC_CTRL_MAX_BURST_SIZE_MASK); - u32p_replace_bits(&tmp, 1, PCIE_MISC_MISC_CTRL_PCIE_RCB_MPS_MODE_MASK); - u32p_replace_bits(&tmp, 1, PCIE_MISC_MISC_CTRL_PCIE_RCB_64B_MODE_MASK); + if (pcie->rcb_mps_mode) + u32p_replace_bits(&tmp, 1, PCIE_MISC_MISC_CTRL_PCIE_RCB_MPS_MODE_MASK); writel(tmp, base + PCIE_MISC_MISC_CTRL); - num_inbound_wins = brcm_pcie_get_inbound_wins(pcie, inbound_wins); - if (num_inbound_wins < 0) - return num_inbound_wins; + brcm_pcie_set_tc_qos(pcie); - set_inbound_win_registers(pcie, inbound_wins, num_inbound_wins); + ret = brcm_pcie_get_rc_bar2_size_and_offset(pcie, &rc_bar2_size, + &rc_bar2_offset); + if (ret) + return ret; + + tmp = lower_32_bits(rc_bar2_offset); + u32p_replace_bits(&tmp, brcm_pcie_encode_ibar_size(rc_bar2_size), + PCIE_MISC_RC_BAR2_CONFIG_LO_SIZE_MASK); + writel(tmp, base + PCIE_MISC_RC_BAR2_CONFIG_LO); + writel(upper_32_bits(rc_bar2_offset), + base + PCIE_MISC_RC_BAR2_CONFIG_HI); if (!brcm_pcie_rc_mode(pcie)) { dev_err(pcie->dev, "PCIe RC controller misconfigured as Endpoint\n"); return -EINVAL; } + tmp = readl(base + PCIE_MISC_UBUS_BAR2_CONFIG_REMAP); + u32p_replace_bits(&tmp, 1, PCIE_MISC_UBUS_BAR2_CONFIG_REMAP_ACCESS_ENABLE_MASK); + writel(tmp, base + PCIE_MISC_UBUS_BAR2_CONFIG_REMAP); tmp = readl(base + PCIE_MISC_MISC_CTRL); for (memc = 0; memc < pcie->num_memc; memc++) { u32 scb_size_val = ilog2(pcie->memc_size[memc]) - 15; @@ -1115,6 +1494,29 @@ static int brcm_pcie_setup(struct brcm_pcie *pcie) } writel(tmp, base + PCIE_MISC_MISC_CTRL); + if (pcie->soc_base == BCM2712) { + /* Suppress AXI error responses and return 1s for read failures */ + tmp = readl(base + PCIE_MISC_UBUS_CTRL); + u32p_replace_bits(&tmp, 1, PCIE_MISC_UBUS_CTRL_UBUS_PCIE_REPLY_ERR_DIS_MASK); + u32p_replace_bits(&tmp, 1, PCIE_MISC_UBUS_CTRL_UBUS_PCIE_REPLY_DECERR_DIS_MASK); + writel(tmp, base + PCIE_MISC_UBUS_CTRL); + writel(0xffffffff, base + PCIE_MISC_AXI_READ_ERROR_DATA); + + /* + * Adjust timeouts. The UBUS timeout also affects CRS + * completion retries, as the request will get terminated if + * either timeout expires, so both have to be a large value + * (in clocks of 750MHz). + * Set UBUS timeout to 250ms, then set RC config retry timeout + * to be ~240ms. + * + * Setting CRSVis=1 will stop the core from blocking on a CRS + * response, but does require the device to be well-behaved... + */ + writel(0xB2D0000, base + PCIE_MISC_UBUS_TIMEOUT); + writel(0xABA0000, base + PCIE_MISC_RC_CONFIG_RETRY_TIMEOUT); + } + /* * We ideally want the MSI target address to be located in the 32bit * addressable memory area. Some devices might depend on it. This is @@ -1122,22 +1524,58 @@ static int brcm_pcie_setup(struct brcm_pcie *pcie) * 4GB or when the inbound area is smaller than 4GB (taking into * account the rounding-up we're forced to perform). */ - if (inbound_wins[2].pci_offset >= SZ_4G || - (inbound_wins[2].size + inbound_wins[2].pci_offset) < SZ_4G) + if (rc_bar2_offset >= SZ_4G || (rc_bar2_size + rc_bar2_offset) < SZ_4G) pcie->msi_target_addr = BRCM_MSI_TARGET_ADDR_LT_4GB; else pcie->msi_target_addr = BRCM_MSI_TARGET_ADDR_GT_4GB; + /* disable the PCIe->GISB memory window (RC_BAR1) */ + tmp = readl(base + PCIE_MISC_RC_BAR1_CONFIG_LO); + tmp &= ~PCIE_MISC_RC_BAR1_CONFIG_LO_SIZE_MASK; + writel(tmp, base + PCIE_MISC_RC_BAR1_CONFIG_LO); - /* Don't advertise L0s capability if 'aspm-no-l0s' */ - aspm_support = PCIE_LINK_STATE_L1; + /* disable the PCIe->SCB memory window (RC_BAR3) */ + tmp = readl(base + PCIE_MISC_RC_BAR3_CONFIG_LO); + tmp &= ~PCIE_MISC_RC_BAR3_CONFIG_LO_SIZE_MASK; + writel(tmp, base + PCIE_MISC_RC_BAR3_CONFIG_LO); + + /* Always advertise L1 capability */ + aspm_support = BIT(1); + /* Advertise L0s capability unless 'aspm-no-l0s' is set */ if (!of_property_read_bool(pcie->np, "aspm-no-l0s")) - aspm_support |= PCIE_LINK_STATE_L0S; + aspm_support |= BIT(0); tmp = readl(base + PCIE_RC_CFG_PRIV1_LINK_CAPABILITY); u32p_replace_bits(&tmp, aspm_support, PCIE_RC_CFG_PRIV1_LINK_CAPABILITY_ASPM_SUPPORT_MASK); writel(tmp, base + PCIE_RC_CFG_PRIV1_LINK_CAPABILITY); + /* program additional inbound windows (RC_BAR4..RC_BAR10) */ + count = (pcie->soc_base == BCM2712) ? 7 : 0; + for (i = 0; i < count; i++) { + u64 bar_cpu, bar_size, bar_pci; + + ret = brcm_pcie_get_rc_bar_n(pcie, 1 + i, &bar_cpu, &bar_size, + &bar_pci); + if (ret) + break; + + tmp = lower_32_bits(bar_pci); + u32p_replace_bits(&tmp, brcm_pcie_encode_ibar_size(bar_size), + PCIE_MISC_RC_BAR_CONFIG_LO_SIZE_MASK); + writel(tmp, base + PCIE_MISC_RC_BAR4_CONFIG_LO + i * 8); + writel(upper_32_bits(bar_pci), + base + PCIE_MISC_RC_BAR4_CONFIG_HI + i * 8); + + tmp = upper_32_bits(bar_cpu) & + PCIE_MISC_UBUS_BAR_CONFIG_REMAP_HI_MASK; + writel(tmp, + base + PCIE_MISC_UBUS_BAR4_CONFIG_REMAP_HI + i * 8); + tmp = lower_32_bits(bar_cpu) & + PCIE_MISC_UBUS_BAR_CONFIG_REMAP_LO_MASK; + writel(tmp | PCIE_MISC_UBUS_BAR_CONFIG_REMAP_ENABLE, + base + PCIE_MISC_UBUS_BAR4_CONFIG_REMAP_LO + i * 8); + } + /* * For config space accesses on the RC, show the right class for * a PCIe-PCIe bridge (the default setting is to be EP mode). @@ -1199,7 +1637,7 @@ static void brcm_extend_rbus_timeout(struct brcm_pcie *pcie) u32 timeout_us = 4000000; /* 4 seconds, our setting for L1SS */ /* 7712 does not have this (RGR1) timer */ - if (pcie->soc_base == BCM7712) + if (pcie->soc_base == BCM7712 || pcie->soc_base == BCM2712) return; /* Each unit in timeout register is 1/216,000,000 seconds */ @@ -1274,12 +1712,35 @@ static int brcm_pcie_start_link(struct brcm_pcie *pcie) void __iomem *base = pcie->base; u16 nlw, cls, lnksta; bool ssc_good = false; + u32 tmp; + u16 tmp16; int ret, i; + if (pcie->gen) + brcm_pcie_set_gen(pcie, pcie->gen); + /* Unassert the fundamental reset */ - ret = pcie->perst_set(pcie, 0); - if (ret) - return ret; + if (pcie->tperst_clk_ms) { + /* + * Increase Tperst_clk time by forcing PERST# output low while + * the internal reset is released, so the PLL generates stable + * refclk output further in advance of PERST# deassertion. + */ + tmp = readl(base + HARD_DEBUG(pcie)); + u32p_replace_bits(&tmp, 1, PCIE_MISC_HARD_PCIE_HARD_DEBUG_PERST_ASSERT_MASK); + writel(tmp, base + HARD_DEBUG(pcie)); + + pcie->perst_set(pcie, 0); + msleep(pcie->tperst_clk_ms); + + tmp = readl(base + HARD_DEBUG(pcie)); + u32p_replace_bits(&tmp, 0, PCIE_MISC_HARD_PCIE_HARD_DEBUG_PERST_ASSERT_MASK); + writel(tmp, base + HARD_DEBUG(pcie)); + } else { + ret = pcie->perst_set(pcie, 0); + if (ret) + return ret; + } /* * Wait for 100ms after PERST# deassertion; see PCIe CEM specification @@ -1302,9 +1763,6 @@ static int brcm_pcie_start_link(struct brcm_pcie *pcie) brcm_config_clkreq(pcie); - if (pcie->gen) - brcm_pcie_set_gen(pcie, pcie->gen); - if (pcie->ssc) { ret = brcm_pcie_set_ssc(pcie); if (ret == 0) @@ -1320,6 +1778,16 @@ static int brcm_pcie_start_link(struct brcm_pcie *pcie) pci_speed_string(pcie_link_speed[cls]), nlw, ssc_good ? "(SSC)" : "(!SSC)"); + /* + * RootCtl bits are reset by perst_n, which undoes pci_enable_crs() + * called prior to pci_add_new_bus() during probe. Re-enable here. + */ + tmp16 = readw(base + BRCM_PCIE_CAP_REGS + PCI_EXP_RTCAP); + if (tmp16 & PCI_EXP_RTCAP_CRSVIS) { + tmp16 = readw(base + BRCM_PCIE_CAP_REGS + PCI_EXP_RTCTL); + u16p_replace_bits(&tmp16, 1, PCI_EXP_RTCTL_CRSSVE); + writew(tmp16, base + BRCM_PCIE_CAP_REGS + PCI_EXP_RTCTL); + } return 0; } @@ -1493,6 +1961,12 @@ static int brcm_pcie_turn_off(struct brcm_pcie *pcie) u32p_replace_bits(&tmp, 1, PCIE_MISC_HARD_PCIE_HARD_DEBUG_SERDES_IDDQ_MASK); writel(tmp, base + HARD_DEBUG(pcie)); + /* + * Shutting down this bridge on pcie1 means accesses to rescal block + * will hang the chip if another RC wants to assert/deassert rescal. + */ + if (pcie->soc_base == BCM2712) + return 0; /* Shutdown PCIe bridge */ ret = pcie->bridge_sw_init_set(pcie, 1); @@ -1688,6 +2162,13 @@ static const int pcie_offsets_bcm7712[] = { [PCIE_INTR2_CPU_BASE] = 0x4400, }; +static const int pcie_offsets_bcm2712[] = { + [EXT_CFG_INDEX] = 0x9000, + [EXT_CFG_DATA] = 0x9004, + [PCIE_HARD_DEBUG] = 0x4304, + [PCIE_INTR2_CPU_BASE] = 0x4400, +}; + static const struct pcie_cfg_data generic_cfg = { .offsets = pcie_offsets, .soc_base = GENERIC, @@ -1753,6 +2234,13 @@ static const struct pcie_cfg_data bcm7712_cfg = { .num_inbound_wins = 10, }; +static const struct pcie_cfg_data bcm2712_cfg = { + .offsets = pcie_offsets_bcm2712, + .perst_set = brcm_pcie_perst_set_2712, + .bridge_sw_init_set = brcm_pcie_bridge_sw_init_set_2712, + .soc_base = BCM2712, +}; + static const struct of_device_id brcm_pcie_match[] = { { .compatible = "brcm,bcm2711-pcie", .data = &bcm2711_cfg }, { .compatible = "brcm,bcm4908-pcie", .data = &bcm4908_cfg }, @@ -1763,6 +2251,7 @@ static const struct of_device_id brcm_pcie_match[] = { { .compatible = "brcm,bcm7435-pcie", .data = &bcm7435_cfg }, { .compatible = "brcm,bcm7445-pcie", .data = &generic_cfg }, { .compatible = "brcm,bcm7712-pcie", .data = &bcm7712_cfg }, + { .compatible = "brcm,bcm2712-pcie", .data = &bcm2712_cfg }, {}, }; @@ -1822,6 +2311,9 @@ static int brcm_pcie_probe(struct platform_device *pdev) pcie->gen = (ret < 0) ? 0 : ret; pcie->ssc = of_property_read_bool(np, "brcm,enable-ssc"); + pcie->l1ss = of_property_read_bool(np, "brcm,enable-l1ss"); + pcie->rcb_mps_mode = of_property_read_bool(np, "brcm,enable-mps-rcb"); + of_property_read_u32(np, "brcm,tperst-clk-ms", &pcie->tperst_clk_ms); pcie->rescal = devm_reset_control_get_optional_shared(&pdev->dev, "rescal"); if (IS_ERR(pcie->rescal)) @@ -1895,6 +2387,33 @@ static int brcm_pcie_probe(struct platform_device *pdev) dev_err(pcie->dev, "probe of internal MSI failed"); goto fail; } + } else if (pci_msi_enabled() && msi_np != pcie->np) { + /* Use RC_BAR1 for MIP access */ + u64 msi_pci_addr; + u64 msi_phys_addr; + + if (of_property_read_u64(msi_np, "brcm,msi-pci-addr", &msi_pci_addr)) { + dev_err(pcie->dev, "Unable to find MSI PCI address\n"); + ret = -EINVAL; + goto fail; + } + + if (of_property_read_u64(msi_np, "reg", &msi_phys_addr)) { + dev_err(pcie->dev, "Unable to find MSI physical address\n"); + ret = -EINVAL; + goto fail; + } + + writel(lower_32_bits(msi_pci_addr) | brcm_pcie_encode_ibar_size(0x1000), + pcie->base + PCIE_MISC_RC_BAR1_CONFIG_LO); + writel(upper_32_bits(msi_pci_addr), + pcie->base + PCIE_MISC_RC_BAR1_CONFIG_HI); + + writel(lower_32_bits(msi_phys_addr) | + PCIE_MISC_UBUS_BAR1_CONFIG_REMAP_ACCESS_ENABLE_MASK, + pcie->base + PCIE_MISC_UBUS_BAR1_CONFIG_REMAP); + writel(upper_32_bits(msi_phys_addr), + pcie->base + PCIE_MISC_UBUS_BAR1_CONFIG_REMAP_HI); } bridge->ops = pcie->soc_base == BCM7425 ? &brcm7425_pcie_ops : &brcm_pcie_ops; diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index ebb0c1d5cae255..04bc2a63df52fb 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -991,9 +991,6 @@ static int pci_register_host_bridge(struct pci_host_bridge *bridge) else pr_info("PCI host bridge to bus %s\n", name); - if (nr_node_ids > 1 && pcibus_to_node(bus) == NUMA_NO_NODE) - dev_warn(&bus->dev, "Unknown NUMA node; performance will be reduced\n"); - /* Check if the boot configuration by FW needs to be preserved */ bridge->preserve_config = pci_preserve_config(bridge); diff --git a/drivers/perf/Kconfig b/drivers/perf/Kconfig index bab8ba64162ffd..09bb65a4dc753f 100644 --- a/drivers/perf/Kconfig +++ b/drivers/perf/Kconfig @@ -251,6 +251,14 @@ config ALIBABA_UNCORE_DRW_PMU Support for Driveway PMU events monitoring on Yitian 710 DDR Sub-system. +config RPI_AXIPERF + depends on ARCH_BCM2835 + tristate "RaspberryPi AXI Performance monitors" + default n + help + Say y if you want to use Raspberry Pi AXI performance monitors, m if + you want to build it as a module. + source "drivers/perf/hisilicon/Kconfig" config MARVELL_CN10K_DDR_PMU diff --git a/drivers/perf/Makefile b/drivers/perf/Makefile index 8268f38e42c5ae..6318355bbf13ef 100644 --- a/drivers/perf/Makefile +++ b/drivers/perf/Makefile @@ -32,3 +32,4 @@ obj-$(CONFIG_DWC_PCIE_PMU) += dwc_pcie_pmu.o obj-$(CONFIG_ARM_CORESIGHT_PMU_ARCH_SYSTEM_PMU) += arm_cspmu/ obj-$(CONFIG_MESON_DDR_PMU) += amlogic/ obj-$(CONFIG_CXL_PMU) += cxl_pmu.o +obj-$(CONFIG_RPI_AXIPERF) += raspberrypi_axi_monitor.o diff --git a/drivers/perf/raspberrypi_axi_monitor.c b/drivers/perf/raspberrypi_axi_monitor.c new file mode 100644 index 00000000000000..b5ad12a2a9ab47 --- /dev/null +++ b/drivers/perf/raspberrypi_axi_monitor.c @@ -0,0 +1,826 @@ +/* + * raspberrypi_axi_monitor.c + * + * Author: james.hughes@raspberrypi.org + * + * Raspberry Pi AXI performance counters. + * + * Copyright (C) 2017 Raspberry Pi Trading 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 <linux/debugfs.h> +#include <linux/devcoredump.h> +#include <linux/device.h> +#include <linux/kthread.h> +#include <linux/module.h> +#include <linux/netdevice.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/platform_device.h> + +#include <soc/bcm2835/raspberrypi-firmware.h> + +#define NUM_MONITORS 2 +#define NUM_BUS_WATCHERS_PER_MONITOR 3 + +#define SYSTEM_MONITOR 0 +#define VPU_MONITOR 1 + +#define MAX_BUSES 16 +#define DEFAULT_SAMPLE_TIME 100 + +#define NUM_BUS_WATCHER_RESULTS 11 + +struct bus_watcher_data { + union { + u32 results[NUM_BUS_WATCHER_RESULTS]; + struct { + u32 atrans; + u32 atwait; + u32 amax; + u32 wtrans; + u32 wtwait; + u32 wmax; + u32 rtrans; + u32 rtwait; + u32 rmax; + u32 rpend; + u32 ratrans; + }; + }; +}; + + +struct rpi_axiperf { + struct platform_device *dev; + struct dentry *root_folder; + + struct task_struct *monitor_thread; + struct mutex lock; + + struct rpi_firmware *firmware; + + /* Sample time spent on for each bus */ + int sample_time; + + /* chip specific bus config */ + const struct bwconfig_config *config; + + /* Now storage for the per monitor settings and the resulting + * performance figures + */ + struct { + /* Bit field of buses we want to monitor */ + int bus_enabled; + /* Bit field of buses to filter by */ + int bus_filter; + /* The current buses being monitored on this monitor */ + int current_bus[NUM_BUS_WATCHERS_PER_MONITOR]; + /* The last bus monitored on this monitor */ + int last_monitored; + + /* Set true if this mailbox must use the mailbox interface + * rather than access registers directly. + */ + int use_mailbox_interface; + + /* Current result values */ + struct bus_watcher_data results[MAX_BUSES]; + + struct dentry *debugfs_entry; + void __iomem *base_address; + + } monitor[NUM_MONITORS]; + +}; + +static struct rpi_axiperf *state; + +/* Two monitors, System and VPU, each with the following register sets. + * Each monitor can only monitor one bus at a time, so we time share them, + * giving each bus 100ms (default, settable via debugfs) of time on its + * associated monitor + * Record results from the three Bus watchers per monitor and push to the sysfs + */ + +/* general registers */ +const int GEN_CTRL; + +const int GEN_CTL_ENABLE_BIT = BIT(0); +const int GEN_CTL_RESET_BIT = BIT(1); +const int GEN_CTL_WATCH_BIT = BIT(2); + +/* Bus watcher registers */ +const int BW_PITCH = 0x40; + +const int BW0_CTRL = 0x40; +const int BW1_CTRL = 0x80; +const int BW2_CTRL = 0xc0; + +const int BW_ATRANS_OFFSET = 0x04; +const int BW_ATWAIT_OFFSET = 0x08; +const int BW_AMAX_OFFSET = 0x0c; +const int BW_WTRANS_OFFSET = 0x10; +const int BW_WTWAIT_OFFSET = 0x14; +const int BW_WMAX_OFFSET = 0x18; +const int BW_RTRANS_OFFSET = 0x1c; +const int BW_RTWAIT_OFFSET = 0x20; +const int BW_RMAX_OFFSET = 0x24; + +const int BW_CTRL_RESET_BIT = BIT(31); +const int BW_CTRL_ENABLE_BIT = BIT(30); +const int BW_CTRL_ENABLE_ID_FILTER_BIT = BIT(29); +const int BW_CTRL_LIMIT_HALT_BIT = BIT(28); + +const int BW_CTRL_SOURCE_SHIFT = 8; +const int BW_CTRL_SOURCE_MASK = GENMASK(12, 8); // 5 bits +const int BW_CTRL_BUS_WATCH_SHIFT; +const int BW_CTRL_BUS_WATCH_MASK = GENMASK(5, 0); // 6 bits +const int BW_CTRL_BUS_FILTER_SHIFT = 8; + +static const char *bus_filter_strings[] = { + "", + "CORE0_V", + "ICACHE0", + "DCACHE0", + "CORE1_V", + "ICACHE1", + "DCACHE1", + "L2_MAIN", + "HOST_PORT", + "HOST_PORT2", + "HVS", + "ISP", + "VIDEO_DCT", + "VIDEO_SD2AXI", + "CAM0", + "CAM1", + "DMA0", + "DMA1", + "DMA2_VPU", + "JPEG", + "VIDEO_CME", + "TRANSPOSER", + "VIDEO_FME", + "CCP2TX", + "USB", + "V3D0", + "V3D1", + "V3D2", + "AVE", + "DEBUG", + "CPU", + "M30" +}; + +static const char * const bus_filter_strings_2711[] = { + "AIO", + "CORE0_V", + "ICACHE0", + "DCACHE0", + "CORE1_V", + "ICACHE1", + "DCACHE1", + "L2_MAIN", + "ARGON", + "PCIE", + "HVS", + "ISP", + "VIDEO_DCT", + "VIDEO_SD2AXI", + "CAM0", + "CAM1", + "DMA0", + "DMA1", + "DMA2", + "JPEG", + "VIDEO_CME", + "TRANSPOSER", + "VIDEO_FME", + "GIGE", + "USB", + "V3D0", + "V3D1", + "V3D2", + "GISB_AXI", + "DEBUG", + "ARM", + "EMMCSTB", +}; + +static const char * const bus_filter_strings_2712[] = { + "", + "VPU_UC0", + "VPU_IC0", + "VPU_DC0", + "VPU_UC1", + "VPU_IC1", + "VPU_DC1", + "VPU_L2", + "DMA2", + "VPU_DEBUG", + "ARM", + "DMA0", + "DMA1", + "RAAGA", + "BBSI", + "PCIE0", + "PCIE1", + "PCIE2", + "UMR", + "SAGE", + "HVDP", + "BSP", + "HVS", + "HVS_WMK", + "MOP0", + "MOP1", + "MBVN", + "DSI", + "XPT", + "EMMC0", + "GENET", + "USB", + "ARGON", + "UNICAM", + "PISP", + "PISPFE", + "JPEG", + "EMMC1", + "EMMC2", + "TRC", + "BSTM0", + "BSTM1", + "BSTM0_SEC", + "BSTM1_SEC", + "AIO", + "MAP", + "SYS_DMA", + "MMUCACHE0", + "MMUCACHE1", + "MPUCACHE0", + "MPUCACHE1", +}; + +static const char *system_bus_string[] = { + "DMA_L2", + "TRANS", + "JPEG", + "SYSTEM_UC", + "DMA_UC", + "SYSTEM_L2", + "CCP2TX", + "MPHI_RX", + "MPHI_TX", + "HVS", + "H264", + "ISP", + "V3D", + "PERIPHERAL", + "CPU_UC", + "CPU_L2" +}; + +static const char * const system_bus_string_2711[] = { + "DMA_L2", + "TRANS", + "JPEG", + "VPU_UC", + "DMA_UC", + "SYSTEM_L2", + "HVS", + "ARGON", + "H264", + "PERIPHERAL", + "ARM_UC", + "ARM_L2", +}; + +static const char * const system_bus_string_2712[] = { + "VPU_UC", + "DISPLAY_TOP", + "V3D", + "ARM", + "XPT", + "BSTM_TOP", + "PCIE_01", + "ARGON_TOP", + "ARB3", + "SRC", + "HVDP", + "PER", + "SYSTEM_L2", +}; + +static const char *vpu_bus_string[] = { + "VPU1_D_L2", + "VPU0_D_L2", + "VPU1_I_L2", + "VPU0_I_L2", + "SYSTEM_L2", + "L2_FLUSH", + "DMA_L2", + "VPU1_D_UC", + "VPU0_D_UC", + "VPU1_I_UC", + "VPU0_I_UC", + "SYSTEM_UC", + "L2_OUT", + "DMA_UC", + "SDRAM", + "L2_IN" +}; + +static const char * const vpu_bus_string_2711[] = { + "VPU1_D_L2", + "VPU0_D_L2", + "VPU1_I_L2", + "VPU0_I_L2", + "SYSTEM_L2", + "DMA_L2", + "VPU1_D_UC", + "VPU0_D_UC", + "VPU1_I_UC", + "VPU0_I_UC", + "VPU_UC", + "L2_OUT", + "DMA_UC", + "L2_IN" +}; + +static const char * const vpu_bus_string_2712[] = { + "VPU1_D_L2", + "VPU0_D_L2", + "VPU1_I_L2", + "VPU0_I_L2", + "SYSTEM_L2", + "DMA_L2", + "VPU1_D_UC", + "VPU0_D_UC", + "VPU1_I_UC", + "VPU0_I_UC", + "VPU_UC", + "L2_OUT", + "DMA_UC", + "L2_IN" +}; + +struct bwconfig_config { + const char * const *bus_filter_strings; + const int num_bus_filters; + const char * const *system_bus_string; + const int num_system_buses; + const char * const *vpu_bus_string; + const int num_vpu_buses; +}; + +static const struct bwconfig_config config_2835 = { + bus_filter_strings, ARRAY_SIZE(bus_filter_strings), + system_bus_string, ARRAY_SIZE(system_bus_string), + vpu_bus_string, ARRAY_SIZE(vpu_bus_string), +}; + +static const struct bwconfig_config config_2711 = { + bus_filter_strings_2711, ARRAY_SIZE(bus_filter_strings_2711), + system_bus_string_2711, ARRAY_SIZE(system_bus_string_2711), + vpu_bus_string_2711, ARRAY_SIZE(vpu_bus_string_2711), +}; + +static const struct bwconfig_config config_2712 = { + bus_filter_strings_2712, ARRAY_SIZE(bus_filter_strings_2712), + system_bus_string_2712, ARRAY_SIZE(system_bus_string_2712), + vpu_bus_string_2712, ARRAY_SIZE(vpu_bus_string_2712), +}; + +static const char *monitor_name[] = { + "System", + "VPU" +}; + +static inline void write_reg(int monitor, int reg, u32 value) +{ + writel(value, state->monitor[monitor].base_address + reg); +} + +static inline u32 read_reg(int monitor, u32 reg) +{ + return readl(state->monitor[monitor].base_address + reg); +} + +static void read_bus_watcher(int monitor, int watcher, u32 *results) +{ + if (state->monitor[monitor].use_mailbox_interface) { + /* We have NUM_BUS_WATCHER_RESULTS results, plus the overheads + * of start address and length + */ + u32 tmp[NUM_BUS_WATCHER_RESULTS+2]; + int err; + + tmp[0] = (u32)(uintptr_t)(state->monitor[monitor].base_address + watcher + + BW_ATRANS_OFFSET); + tmp[1] = NUM_BUS_WATCHER_RESULTS; + + err = rpi_firmware_property(state->firmware, + RPI_FIRMWARE_GET_PERIPH_REG, + tmp, sizeof(tmp)); + + if (err < 0 || tmp[1] != NUM_BUS_WATCHER_RESULTS) + dev_err_once(&state->dev->dev, + "Failed to read bus watcher"); + else + memcpy(results, &tmp[2], + NUM_BUS_WATCHER_RESULTS * sizeof(u32)); + } else { + int i; + void __iomem *addr = state->monitor[monitor].base_address + + watcher + BW_ATRANS_OFFSET; + for (i = 0; i < NUM_BUS_WATCHER_RESULTS; i++, addr += 4) + *results++ = readl(addr); + } +} + +static void set_monitor_control(int monitor, u32 set) +{ + if (state->monitor[monitor].use_mailbox_interface) { + u32 tmp[3] = {(u32)(uintptr_t)(state->monitor[monitor].base_address + + GEN_CTRL), 1, set}; + int err = rpi_firmware_property(state->firmware, + RPI_FIRMWARE_SET_PERIPH_REG, + tmp, sizeof(tmp)); + + if (err < 0 || tmp[1] != 1) + dev_err_once(&state->dev->dev, + "Failed to set monitor control"); + } else + write_reg(monitor, GEN_CTRL, set); +} + +static void set_bus_watcher_control(int monitor, int watcher, u32 set) +{ + if (state->monitor[monitor].use_mailbox_interface) { + u32 tmp[3] = {(u32)(uintptr_t)(state->monitor[monitor].base_address + + watcher), 1, set}; + int err = rpi_firmware_property(state->firmware, + RPI_FIRMWARE_SET_PERIPH_REG, + tmp, sizeof(tmp)); + if (err < 0 || tmp[1] != 1) + dev_err_once(&state->dev->dev, + "Failed to set bus watcher control"); + } else + write_reg(monitor, watcher, set); +} + +static void monitor(struct rpi_axiperf *state) +{ + int monitor, num_buses[NUM_MONITORS]; + + mutex_lock(&state->lock); + + for (monitor = 0; monitor < NUM_MONITORS; monitor++) { + typeof(state->monitor[0]) *mon = &(state->monitor[monitor]); + + /* Anything enabled? */ + if (mon->bus_enabled == 0) { + /* No, disable all monitoring for this monitor */ + set_monitor_control(monitor, GEN_CTL_RESET_BIT); + } else { + int i; + + /* Find out how many busses we want to monitor, and + * spread our 3 actual monitors over them + */ + num_buses[monitor] = hweight32(mon->bus_enabled); + num_buses[monitor] = min(num_buses[monitor], + NUM_BUS_WATCHERS_PER_MONITOR); + + for (i = 0; i < num_buses[monitor]; i++) { + int bus_control; + + do { + mon->last_monitored++; + mon->last_monitored &= 0xf; + } while ((mon->bus_enabled & + (1 << mon->last_monitored)) == 0); + + mon->current_bus[i] = mon->last_monitored; + + /* Reset the counters */ + set_bus_watcher_control(monitor, + BW0_CTRL + + i*BW_PITCH, + BW_CTRL_RESET_BIT); + + bus_control = BW_CTRL_ENABLE_BIT | + mon->current_bus[i]; + + if (mon->bus_filter) { + bus_control |= + BW_CTRL_ENABLE_ID_FILTER_BIT; + bus_control |= + ((mon->bus_filter & 0x1f) + << BW_CTRL_BUS_FILTER_SHIFT); + } + + // Start capture + set_bus_watcher_control(monitor, + BW0_CTRL + i*BW_PITCH, + bus_control); + } + } + + /* start monitoring */ + set_monitor_control(monitor, GEN_CTL_ENABLE_BIT | GEN_CTL_WATCH_BIT); + } + + mutex_unlock(&state->lock); + + msleep(state->sample_time); + + /* Now read the results */ + + mutex_lock(&state->lock); + for (monitor = 0; monitor < NUM_MONITORS; monitor++) { + typeof(state->monitor[0]) *mon = &(state->monitor[monitor]); + + /* Anything enabled? */ + if (mon->bus_enabled == 0) { + /* No, disable all monitoring for this monitor */ + set_monitor_control(monitor, 0); + } else { + int i; + + for (i = 0; i < num_buses[monitor]; i++) { + int bus = mon->current_bus[i]; + + read_bus_watcher(monitor, + BW0_CTRL + i*BW_PITCH, + (u32 *)&mon->results[bus].results); + } + } + } + mutex_unlock(&state->lock); +} + +static int monitor_thread(void *data) +{ + struct rpi_axiperf *state = data; + + while (1) { + monitor(state); + + if (kthread_should_stop()) + return 0; + } + return 0; +} + +static ssize_t myreader(struct file *fp, char __user *user_buffer, + size_t count, loff_t *position) +{ +#define INIT_BUFF_SIZE 2048 + + int i; + int idx = (int)(uintptr_t)(fp->private_data); + int num_buses, cnt; + char *string_buffer; + int buff_size = INIT_BUFF_SIZE; + char *p; + typeof(state->monitor[0]) *mon = &(state->monitor[idx]); + const struct bwconfig_config *config = state->config; + + if (idx < 0 || idx > NUM_MONITORS) + idx = 0; + + num_buses = idx == SYSTEM_MONITOR ? config->num_system_buses : config->num_vpu_buses; + + string_buffer = kmalloc(buff_size, GFP_KERNEL); + + if (!string_buffer) { + dev_err(&state->dev->dev, + "Failed temporary string allocation\n"); + return 0; + } + + p = string_buffer; + + mutex_lock(&state->lock); + + if (mon->bus_filter) { + int filt = min(mon->bus_filter & 0x1f, config->num_bus_filters); + + cnt = snprintf(p, buff_size, + "\nMonitoring transactions from %s only\n", + config->bus_filter_strings[filt]); + p += cnt; + buff_size -= cnt; + } + + cnt = snprintf(p, buff_size, " Bus | Atrans Atwait AMax Wtrans Wtwait WMax Rtrans Rtwait RMax RPend RAtrans\n" + "===========================================================================================================================\n"); + + if (cnt >= buff_size) + goto done; + + p += cnt; + buff_size -= cnt; + +#define M(x) ((x) >= 1000000000 ? (x)/1000000 : (x) >= 1000 ? (x)/1000 : (x)) +#define N(x) ((x) >= 1000000000 ? 'M' : (x) >= 1000 ? 'K' : ' ') + + for (i = 0; i < num_buses; i++) { + if (mon->bus_enabled & (1 << i)) { + typeof(mon->results[0]) *res = &(mon->results[i]); + + cnt = snprintf(p, buff_size, + "%11s | %8u%c %8u%c %8u%c %8u%c %8u%c %8u%c %8u%c %8u%c %8u%c %8u%c %8u%c\n", + idx == SYSTEM_MONITOR ? + config->system_bus_string[i] : + config->vpu_bus_string[i], + M(res->atrans), N(res->atrans), + M(res->atwait), N(res->atwait), + M(res->amax), N(res->amax), + M(res->wtrans), N(res->wtrans), + M(res->wtwait), N(res->wtwait), + M(res->wmax), N(res->wmax), + M(res->rtrans), N(res->rtrans), + M(res->rtwait), N(res->rtwait), + M(res->rmax), N(res->rmax), + M(res->rpend), N(res->rpend), + M(res->ratrans), N(res->ratrans) + ); + if (cnt >= buff_size) + goto done; + + p += cnt; + buff_size -= cnt; + } + } + + mutex_unlock(&state->lock); + +done: + + /* did the last string entry exceeed our buffer size? ie out of string + * buffer space. Null terminate, use what we have. + */ + if (cnt >= buff_size) { + buff_size = 0; + string_buffer[INIT_BUFF_SIZE] = 0; + } + + cnt = simple_read_from_buffer(user_buffer, count, position, + string_buffer, + INIT_BUFF_SIZE - buff_size); + + kfree(string_buffer); + + return cnt; +} + +static ssize_t mywriter(struct file *fp, const char __user *user_buffer, + size_t count, loff_t *position) +{ + int idx = (int)(uintptr_t)(fp->private_data); + + if (idx < 0 || idx > NUM_MONITORS) + idx = 0; + + /* At the moment, this does nothing, but in the future it could be + * used to reset counters etc + */ + return count; +} + +static const struct file_operations fops_debug = { + .read = myreader, + .write = mywriter, + .open = simple_open +}; + +static int rpi_axiperf_probe(struct platform_device *pdev) +{ + int ret = 0, i; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct device_node *fw_node; + + state = kzalloc(sizeof(struct rpi_axiperf), GFP_KERNEL); + if (!state) + return -ENOMEM; + + state->config = of_device_get_match_data(dev); + if (!state->config) + return -EINVAL; + + /* Get the firmware handle for future rpi-firmware-xxx calls */ + fw_node = of_parse_phandle(np, "firmware", 0); + if (!fw_node) { + dev_err(dev, "Missing firmware node\n"); + return -ENOENT; + } + + state->firmware = rpi_firmware_get(fw_node); + if (!state->firmware) + return -EPROBE_DEFER; + + /* Special case for the VPU monitor, we must use the mailbox interface + * as it is not accessible from the ARM address space. + */ + state->monitor[VPU_MONITOR].use_mailbox_interface = 1; + state->monitor[SYSTEM_MONITOR].use_mailbox_interface = 0; + + for (i = 0; i < NUM_MONITORS; i++) { + if (state->monitor[i].use_mailbox_interface) { + of_property_read_u32_index(np, "reg", i*2, + (u32 *)(&state->monitor[i].base_address)); + } else { + struct resource *resource = + platform_get_resource(pdev, IORESOURCE_MEM, i); + + state->monitor[i].base_address = + devm_ioremap_resource(&pdev->dev, resource); + } + + if (IS_ERR(state->monitor[i].base_address)) + return PTR_ERR(state->monitor[i].base_address); + + /* Enable all buses by default */ + state->monitor[i].bus_enabled = 0xffff; + } + + state->dev = pdev; + platform_set_drvdata(pdev, state); + + state->sample_time = DEFAULT_SAMPLE_TIME; + + /* Set up all the debugfs stuff */ + state->root_folder = debugfs_create_dir(KBUILD_MODNAME, NULL); + + for (i = 0; i < NUM_MONITORS; i++) { + state->monitor[i].debugfs_entry = + debugfs_create_dir(monitor_name[i], state->root_folder); + if (IS_ERR(state->monitor[i].debugfs_entry)) + state->monitor[i].debugfs_entry = NULL; + + debugfs_create_file("data", 0444, + state->monitor[i].debugfs_entry, + (void *)(uintptr_t)i, &fops_debug); + debugfs_create_u32("enable", 0644, + state->monitor[i].debugfs_entry, + &state->monitor[i].bus_enabled); + debugfs_create_u32("filter", 0644, + state->monitor[i].debugfs_entry, + &state->monitor[i].bus_filter); + debugfs_create_u32("sample_time", 0644, + state->monitor[i].debugfs_entry, + &state->sample_time); + } + + mutex_init(&state->lock); + + state->monitor_thread = kthread_run(monitor_thread, state, + "rpi-axiperfmon"); + + return ret; + +} + +static void rpi_axiperf_remove(struct platform_device *dev) +{ + kthread_stop(state->monitor_thread); + + debugfs_remove_recursive(state->root_folder); + state->root_folder = NULL; +} + +static const struct of_device_id rpi_axiperf_match[] = { + { .compatible = "brcm,bcm2835-axiperf", + .data = &config_2835 }, + { .compatible = "brcm,bcm2711-axiperf", + .data = &config_2711 }, + { .compatible = "brcm,bcm2712-axiperf", + .data = &config_2712 }, + {}, +}; +MODULE_DEVICE_TABLE(of, rpi_axiperf_match); + +static struct platform_driver rpi_axiperf_driver = { + .probe = rpi_axiperf_probe, + .remove = rpi_axiperf_remove, + .driver = { + .name = "rpi-bcm2835-axiperf", + .of_match_table = of_match_ptr(rpi_axiperf_match), + }, +}; + +module_platform_driver(rpi_axiperf_driver); + +/* Module information */ +MODULE_AUTHOR("James Hughes <james.hughes@raspberrypi.org>"); +MODULE_DESCRIPTION("RPI AXI Performance monitor driver"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/phy/broadcom/Kconfig b/drivers/phy/broadcom/Kconfig index 1d89a2fd9b79b3..a67ac49a0d59b7 100644 --- a/drivers/phy/broadcom/Kconfig +++ b/drivers/phy/broadcom/Kconfig @@ -93,7 +93,7 @@ config PHY_BRCM_SATA config PHY_BRCM_USB tristate "Broadcom STB USB PHY driver" - depends on ARCH_BCMBCA || ARCH_BRCMSTB || COMPILE_TEST + depends on ARCH_BCMBCA || ARCH_BRCMSTB || ARCH_BCM2835 || COMPILE_TEST depends on OF select GENERIC_PHY select SOC_BRCMSTB if ARCH_BRCMSTB diff --git a/drivers/phy/broadcom/phy-brcm-usb-init-synopsys.c b/drivers/phy/broadcom/phy-brcm-usb-init-synopsys.c index dc452610934add..b377eefc6dedd8 100644 --- a/drivers/phy/broadcom/phy-brcm-usb-init-synopsys.c +++ b/drivers/phy/broadcom/phy-brcm-usb-init-synopsys.c @@ -347,6 +347,36 @@ static void usb_init_common_7216(struct brcm_usb_init_params *params) usb_init_common(params); } +static void usb_init_common_2712(struct brcm_usb_init_params *params) +{ + void __iomem *ctrl = params->regs[BRCM_REGS_CTRL]; + void __iomem *bdc_ec = params->regs[BRCM_REGS_BDC_EC]; + u32 reg; + + if (params->syscon_piarbctl) + syscon_piarbctl_init(params->syscon_piarbctl); + + USB_CTRL_UNSET(ctrl, USB_PM, USB_PWRDN); + + usb_wake_enable_7211b0(params, false); + + usb_init_common(params); + + /* + * The BDC controller will get occasional failures with + * the default "Read Transaction Size" of 6 (1024 bytes). + * Set it to 4 (256 bytes). + */ + if ((params->supported_port_modes != USB_CTLR_MODE_HOST) && bdc_ec) { + reg = brcm_usb_readl(bdc_ec + BDC_EC_AXIRDA); + reg &= ~BDC_EC_AXIRDA_RTS_MASK; + reg |= (0x4 << BDC_EC_AXIRDA_RTS_SHIFT); + brcm_usb_writel(reg, bdc_ec + BDC_EC_AXIRDA); + } + + usb2_eye_fix_7211b0(params); +} + static void usb_init_xhci(struct brcm_usb_init_params *params) { pr_debug("%s\n", __func__); @@ -392,6 +422,18 @@ static void usb_uninit_common_7211b0(struct brcm_usb_init_params *params) } +static void usb_uninit_common_2712(struct brcm_usb_init_params *params) +{ + void __iomem *ctrl = params->regs[BRCM_REGS_CTRL]; + + if (params->wake_enabled) { + USB_CTRL_SET(ctrl, TEST_PORT_CTL, TPOUT_SEL_PME_GEN); + usb_wake_enable_7211b0(params, true); + } else { + USB_CTRL_SET(ctrl, USB_PM, USB_PWRDN); + } +} + static void usb_uninit_xhci(struct brcm_usb_init_params *params) { @@ -446,6 +488,16 @@ static const struct brcm_usb_init_ops bcm7211b0_ops = { .set_dual_select = usb_set_dual_select, }; +static const struct brcm_usb_init_ops bcm2712_ops = { + .init_ipp = usb_init_ipp, + .init_common = usb_init_common_2712, + .init_xhci = usb_init_xhci, + .uninit_common = usb_uninit_common_2712, + .uninit_xhci = usb_uninit_xhci, + .get_dual_select = usb_get_dual_select, + .set_dual_select = usb_set_dual_select, +}; + void brcm_usb_dvr_init_7216(struct brcm_usb_init_params *params) { @@ -463,3 +515,10 @@ void brcm_usb_dvr_init_7211b0(struct brcm_usb_init_params *params) params->family_name = "7211"; params->ops = &bcm7211b0_ops; } + +void brcm_usb_dvr_init_2712(struct brcm_usb_init_params *params) +{ + params->family_name = "2712"; + params->ops = &bcm2712_ops; + params->suspend_with_clocks = true; +} diff --git a/drivers/phy/broadcom/phy-brcm-usb-init.h b/drivers/phy/broadcom/phy-brcm-usb-init.h index c1a88f5cd4cd8e..67083474126d1a 100644 --- a/drivers/phy/broadcom/phy-brcm-usb-init.h +++ b/drivers/phy/broadcom/phy-brcm-usb-init.h @@ -70,12 +70,14 @@ struct brcm_usb_init_params { const struct brcm_usb_init_ops *ops; struct regmap *syscon_piarbctl; bool wake_enabled; + bool suspend_with_clocks; }; void brcm_usb_dvr_init_4908(struct brcm_usb_init_params *params); void brcm_usb_dvr_init_7445(struct brcm_usb_init_params *params); void brcm_usb_dvr_init_7216(struct brcm_usb_init_params *params); void brcm_usb_dvr_init_7211b0(struct brcm_usb_init_params *params); +void brcm_usb_dvr_init_2712(struct brcm_usb_init_params *params); static inline u32 brcm_usb_readl(void __iomem *addr) { diff --git a/drivers/phy/broadcom/phy-brcm-usb.c b/drivers/phy/broadcom/phy-brcm-usb.c index ad2eec0956016d..342ed464a363b9 100644 --- a/drivers/phy/broadcom/phy-brcm-usb.c +++ b/drivers/phy/broadcom/phy-brcm-usb.c @@ -75,7 +75,7 @@ struct brcm_usb_phy_data { }; static s8 *node_reg_names[BRCM_REGS_MAX] = { - "crtl", "xhci_ec", "xhci_gbl", "usb_phy", "usb_mdio", "bdc_ec" + "ctrl", "xhci_ec", "xhci_gbl", "usb_phy", "usb_mdio", "bdc_ec" }; static int brcm_pm_notifier(struct notifier_block *notifier, @@ -315,6 +315,18 @@ static const struct match_chip_info chip_info_7211b0 = { .optional_reg = BRCM_REGS_BDC_EC, }; +static const struct match_chip_info chip_info_2712 = { + .init_func = &brcm_usb_dvr_init_2712, + .required_regs = { + BRCM_REGS_CTRL, + BRCM_REGS_XHCI_EC, + BRCM_REGS_XHCI_GBL, + BRCM_REGS_USB_MDIO, + -1, + }, + .optional_reg = BRCM_REGS_BDC_EC, +}; + static const struct match_chip_info chip_info_7445 = { .init_func = &brcm_usb_dvr_init_7445, .required_regs = { @@ -337,6 +349,10 @@ static const struct of_device_id brcm_usb_dt_ids[] = { .compatible = "brcm,bcm7211-usb-phy", .data = &chip_info_7211b0, }, + { + .compatible = "brcm,bcm2712-usb-phy", + .data = &chip_info_2712, + }, { .compatible = "brcm,brcmstb-usb-phy", .data = &chip_info_7445, diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig index 354536de564b67..70fc024385ef06 100644 --- a/drivers/pinctrl/Kconfig +++ b/drivers/pinctrl/Kconfig @@ -587,6 +587,13 @@ config PINCTRL_MLXBF3 each pin. This driver can also be built as a module called pinctrl-mlxbf3. +config PINCTRL_RP1 + bool "Pinctrl driver for RP1" + select PINMUX + select PINCONF + select GENERIC_PINCONF + select GPIOLIB_IRQCHIP + source "drivers/pinctrl/actions/Kconfig" source "drivers/pinctrl/aspeed/Kconfig" source "drivers/pinctrl/bcm/Kconfig" diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile index 97823f52b972a3..2df5ae86e458b0 100644 --- a/drivers/pinctrl/Makefile +++ b/drivers/pinctrl/Makefile @@ -47,6 +47,7 @@ obj-$(CONFIG_PINCTRL_PIC32) += pinctrl-pic32.o obj-$(CONFIG_PINCTRL_PISTACHIO) += pinctrl-pistachio.o obj-$(CONFIG_PINCTRL_RK805) += pinctrl-rk805.o obj-$(CONFIG_PINCTRL_ROCKCHIP) += pinctrl-rockchip.o +obj-$(CONFIG_PINCTRL_RP1) += pinctrl-rp1.o obj-$(CONFIG_PINCTRL_SCMI) += pinctrl-scmi.o obj-$(CONFIG_PINCTRL_SINGLE) += pinctrl-single.o obj-$(CONFIG_PINCTRL_ST) += pinctrl-st.o diff --git a/drivers/pinctrl/bcm/Kconfig b/drivers/pinctrl/bcm/Kconfig index 35b51ce4298e25..f2ce009999e780 100644 --- a/drivers/pinctrl/bcm/Kconfig +++ b/drivers/pinctrl/bcm/Kconfig @@ -3,6 +3,15 @@ # Broadcom pinctrl drivers # +config PINCTRL_BCM2712 + bool "Broadcom BCM2712 PINCONF driver" + depends on OF && (ARCH_BCM2835 || ARCH_BRCMSTB || COMPILE_TEST) + select PINMUX + select PINCONF + select GENERIC_PINCONF + help + Say Y here to enable the Broadcom BCM2712 PINCONF driver. + config PINCTRL_BCM281XX bool "Broadcom BCM281xx pinctrl driver" depends on OF && (ARCH_BCM_MOBILE || COMPILE_TEST) diff --git a/drivers/pinctrl/bcm/Makefile b/drivers/pinctrl/bcm/Makefile index 82b868ec14716d..d298e478582966 100644 --- a/drivers/pinctrl/bcm/Makefile +++ b/drivers/pinctrl/bcm/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 # Broadcom pinctrl support +obj-$(CONFIG_PINCTRL_BCM2712) += pinctrl-bcm2712.o obj-$(CONFIG_PINCTRL_BCM281XX) += pinctrl-bcm281xx.o obj-$(CONFIG_PINCTRL_BCM2835) += pinctrl-bcm2835.o obj-$(CONFIG_PINCTRL_BCM4908) += pinctrl-bcm4908.o diff --git a/drivers/pinctrl/bcm/pinctrl-bcm2712.c b/drivers/pinctrl/bcm/pinctrl-bcm2712.c new file mode 100644 index 00000000000000..c349bcf05391ae --- /dev/null +++ b/drivers/pinctrl/bcm/pinctrl-bcm2712.c @@ -0,0 +1,1247 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Broadcom BCM2712 GPIO units (pinctrl only) + * + * Copyright (C) 2021-3 Raspberry Pi Ltd. + * Copyright (C) 2012 Chris Boot, Simon Arlott, Stephen Warren + * + * Based heavily on the BCM2835 GPIO & pinctrl driver, which was inspired by: + * pinctrl-nomadik.c, please see original file for copyright information + * pinctrl-tegra.c, please see original file for copyright information + */ + +#include <linux/bitmap.h> +#include <linux/bug.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/of_address.h> +#include <linux/of.h> +#include <linux/pinctrl/consumer.h> +#include <linux/pinctrl/machine.h> +#include <linux/pinctrl/pinconf.h> +#include <linux/pinctrl/pinctrl.h> +#include <linux/pinctrl/pinmux.h> +#include <linux/pinctrl/pinconf-generic.h> +#include <linux/platform_device.h> +#include <linux/seq_file.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/types.h> + +#define MODULE_NAME "pinctrl-bcm2712" + +/* Register offsets */ + +#define BCM2712_PULL_NONE 0 +#define BCM2712_PULL_DOWN 1 +#define BCM2712_PULL_UP 2 +#define BCM2712_PULL_MASK 0x3 + +#define BCM2712_FSEL_COUNT 9 +#define BCM2712_FSEL_MASK 0xf + +#define FUNC(f) \ + [func_##f] = #f +#define PIN(i, f1, f2, f3, f4, f5, f6, f7, f8) \ + [i] = { \ + .funcs = { \ + func_##f1, \ + func_##f2, \ + func_##f3, \ + func_##f4, \ + func_##f5, \ + func_##f6, \ + func_##f7, \ + func_##f8, \ + }, \ + } + +#define MUX_BIT_VALID 0x8000 +#define REG_BIT_INVALID 0xffff + +#define BIT_TO_REG(b) (((b) >> 5) << 2) +#define BIT_TO_SHIFT(b) ((b) & 0x1f) + +#define MUX_BIT(mr, mb) (MUX_BIT_VALID + ((mr)*4)*8 + (mb)*4) +#define GPIO_REGS(n, mr, mb, pr, pb) \ + [n] = { MUX_BIT(mr, mb), ((pr)*4)*8 + (pb)*2 } + +#define EMMC_REGS(n, pr, pb) \ + [n] = { 0, ((pr)*4)*8 + (pb)*2 } + +#define AGPIO_REGS(n, mr, mb, pr, pb) \ + [n] = { MUX_BIT(mr, mb), ((pr)*4)*8 + (pb)*2 } + +#define SGPIO_REGS(n, mr, mb) \ + [n+32] = { MUX_BIT(mr, mb), REG_BIT_INVALID } + +#define GPIO_PIN(a) PINCTRL_PIN(a, "gpio" #a) +#define AGPIO_PIN(a) PINCTRL_PIN(a, "aon_gpio" #a) +#define SGPIO_PIN(a) PINCTRL_PIN(a+32, "aon_sgpio" #a) + +struct pin_regs { + u16 mux_bit; + u16 pad_bit; +}; + +struct bcm2712_pinctrl { + struct device *dev; + void __iomem *base; + struct pinctrl_dev *pctl_dev; + struct pinctrl_desc pctl_desc; + const struct pin_regs *pin_regs; + const struct bcm2712_pin_funcs *pin_funcs; + const char *const *gpio_groups; + struct pinctrl_gpio_range gpio_range; + spinlock_t lock; +}; + +struct bcm_plat_data { + const struct pinctrl_desc *pctl_desc; + const struct pinctrl_gpio_range *gpio_range; + const struct pin_regs *pin_regs; + const struct bcm2712_pin_funcs *pin_funcs; +}; + +struct bcm2712_pin_funcs { + u8 funcs[BCM2712_FSEL_COUNT - 1]; +}; + +enum bcm2712_funcs { + func_gpio, + func_alt1, + func_alt2, + func_alt3, + func_alt4, + func_alt5, + func_alt6, + func_alt7, + func_alt8, + func_aon_cpu_standbyb, + func_aon_fp_4sec_resetb, + func_aon_gpclk, + func_aon_pwm, + func_arm_jtag, + func_aud_fs_clk0, + func_avs_pmu_bsc, + func_bsc_m0, + func_bsc_m1, + func_bsc_m2, + func_bsc_m3, + func_clk_observe, + func_ctl_hdmi_5v, + func_enet0, + func_enet0_mii, + func_enet0_rgmii, + func_ext_sc_clk, + func_fl0, + func_fl1, + func_gpclk0, + func_gpclk1, + func_gpclk2, + func_hdmi_tx0_auto_i2c, + func_hdmi_tx0_bsc, + func_hdmi_tx1_auto_i2c, + func_hdmi_tx1_bsc, + func_i2s_in, + func_i2s_out, + func_ir_in, + func_mtsif, + func_mtsif_alt, + func_mtsif_alt1, + func_pdm, + func_pkt, + func_pm_led_out, + func_sc0, + func_sd0, + func_sd2, + func_sd_card_a, + func_sd_card_b, + func_sd_card_c, + func_sd_card_d, + func_sd_card_e, + func_sd_card_f, + func_sd_card_g, + func_spdif_out, + func_spi_m, + func_spi_s, + func_sr_edm_sense, + func_te0, + func_te1, + func_tsio, + func_uart0, + func_uart1, + func_uart2, + func_usb_pwr, + func_usb_vbus, + func_uui, + func_vc_i2c0, + func_vc_i2c3, + func_vc_i2c4, + func_vc_i2c5, + func_vc_i2csl, + func_vc_pcm, + func_vc_pwm0, + func_vc_pwm1, + func_vc_spi0, + func_vc_spi3, + func_vc_spi4, + func_vc_spi5, + func_vc_uart0, + func_vc_uart2, + func_vc_uart3, + func_vc_uart4, + func__, + func_count = func__ +}; + +static const struct pin_regs bcm2712_c0_gpio_pin_regs[] = { + GPIO_REGS(0, 0, 0, 7, 7), + GPIO_REGS(1, 0, 1, 7, 8), + GPIO_REGS(2, 0, 2, 7, 9), + GPIO_REGS(3, 0, 3, 7, 10), + GPIO_REGS(4, 0, 4, 7, 11), + GPIO_REGS(5, 0, 5, 7, 12), + GPIO_REGS(6, 0, 6, 7, 13), + GPIO_REGS(7, 0, 7, 7, 14), + GPIO_REGS(8, 1, 0, 8, 0), + GPIO_REGS(9, 1, 1, 8, 1), + GPIO_REGS(10, 1, 2, 8, 2), + GPIO_REGS(11, 1, 3, 8, 3), + GPIO_REGS(12, 1, 4, 8, 4), + GPIO_REGS(13, 1, 5, 8, 5), + GPIO_REGS(14, 1, 6, 8, 6), + GPIO_REGS(15, 1, 7, 8, 7), + GPIO_REGS(16, 2, 0, 8, 8), + GPIO_REGS(17, 2, 1, 8, 9), + GPIO_REGS(18, 2, 2, 8, 10), + GPIO_REGS(19, 2, 3, 8, 11), + GPIO_REGS(20, 2, 4, 8, 12), + GPIO_REGS(21, 2, 5, 8, 13), + GPIO_REGS(22, 2, 6, 8, 14), + GPIO_REGS(23, 2, 7, 9, 0), + GPIO_REGS(24, 3, 0, 9, 1), + GPIO_REGS(25, 3, 1, 9, 2), + GPIO_REGS(26, 3, 2, 9, 3), + GPIO_REGS(27, 3, 3, 9, 4), + GPIO_REGS(28, 3, 4, 9, 5), + GPIO_REGS(29, 3, 5, 9, 6), + GPIO_REGS(30, 3, 6, 9, 7), + GPIO_REGS(31, 3, 7, 9, 8), + GPIO_REGS(32, 4, 0, 9, 9), + GPIO_REGS(33, 4, 1, 9, 10), + GPIO_REGS(34, 4, 2, 9, 11), + GPIO_REGS(35, 4, 3, 9, 12), + GPIO_REGS(36, 4, 4, 9, 13), + GPIO_REGS(37, 4, 5, 9, 14), + GPIO_REGS(38, 4, 6, 10, 0), + GPIO_REGS(39, 4, 7, 10, 1), + GPIO_REGS(40, 5, 0, 10, 2), + GPIO_REGS(41, 5, 1, 10, 3), + GPIO_REGS(42, 5, 2, 10, 4), + GPIO_REGS(43, 5, 3, 10, 5), + GPIO_REGS(44, 5, 4, 10, 6), + GPIO_REGS(45, 5, 5, 10, 7), + GPIO_REGS(46, 5, 6, 10, 8), + GPIO_REGS(47, 5, 7, 10, 9), + GPIO_REGS(48, 6, 0, 10, 10), + GPIO_REGS(49, 6, 1, 10, 11), + GPIO_REGS(50, 6, 2, 10, 12), + GPIO_REGS(51, 6, 3, 10, 13), + GPIO_REGS(52, 6, 4, 10, 14), + GPIO_REGS(53, 6, 5, 11, 0), + EMMC_REGS(54, 11, 1), /* EMMC_CMD */ + EMMC_REGS(55, 11, 2), /* EMMC_DS */ + EMMC_REGS(56, 11, 3), /* EMMC_CLK */ + EMMC_REGS(57, 11, 4), /* EMMC_DAT0 */ + EMMC_REGS(58, 11, 5), /* EMMC_DAT1 */ + EMMC_REGS(59, 11, 6), /* EMMC_DAT2 */ + EMMC_REGS(60, 11, 7), /* EMMC_DAT3 */ + EMMC_REGS(61, 11, 8), /* EMMC_DAT4 */ + EMMC_REGS(62, 11, 9), /* EMMC_DAT5 */ + EMMC_REGS(63, 11, 10), /* EMMC_DAT6 */ + EMMC_REGS(64, 11, 11), /* EMMC_DAT7 */ +}; + +static struct pin_regs bcm2712_c0_aon_gpio_pin_regs[] = { + AGPIO_REGS(0, 3, 0, 6, 10), + AGPIO_REGS(1, 3, 1, 6, 11), + AGPIO_REGS(2, 3, 2, 6, 12), + AGPIO_REGS(3, 3, 3, 6, 13), + AGPIO_REGS(4, 3, 4, 6, 14), + AGPIO_REGS(5, 3, 5, 7, 0), + AGPIO_REGS(6, 3, 6, 7, 1), + AGPIO_REGS(7, 3, 7, 7, 2), + AGPIO_REGS(8, 4, 0, 7, 3), + AGPIO_REGS(9, 4, 1, 7, 4), + AGPIO_REGS(10, 4, 2, 7, 5), + AGPIO_REGS(11, 4, 3, 7, 6), + AGPIO_REGS(12, 4, 4, 7, 7), + AGPIO_REGS(13, 4, 5, 7, 8), + AGPIO_REGS(14, 4, 6, 7, 9), + AGPIO_REGS(15, 4, 7, 7, 10), + AGPIO_REGS(16, 5, 0, 7, 11), + SGPIO_REGS(0, 0, 0), + SGPIO_REGS(1, 0, 1), + SGPIO_REGS(2, 0, 2), + SGPIO_REGS(3, 0, 3), + SGPIO_REGS(4, 1, 0), + SGPIO_REGS(5, 2, 0), +}; + +static const struct pinctrl_pin_desc bcm2712_c0_gpio_pins[] = { + GPIO_PIN(0), + GPIO_PIN(1), + GPIO_PIN(2), + GPIO_PIN(3), + GPIO_PIN(4), + GPIO_PIN(5), + GPIO_PIN(6), + GPIO_PIN(7), + GPIO_PIN(8), + GPIO_PIN(9), + GPIO_PIN(10), + GPIO_PIN(11), + GPIO_PIN(12), + GPIO_PIN(13), + GPIO_PIN(14), + GPIO_PIN(15), + GPIO_PIN(16), + GPIO_PIN(17), + GPIO_PIN(18), + GPIO_PIN(19), + GPIO_PIN(20), + GPIO_PIN(21), + GPIO_PIN(22), + GPIO_PIN(23), + GPIO_PIN(24), + GPIO_PIN(25), + GPIO_PIN(26), + GPIO_PIN(27), + GPIO_PIN(28), + GPIO_PIN(29), + GPIO_PIN(30), + GPIO_PIN(31), + GPIO_PIN(32), + GPIO_PIN(33), + GPIO_PIN(34), + GPIO_PIN(35), + GPIO_PIN(36), + GPIO_PIN(37), + GPIO_PIN(38), + GPIO_PIN(39), + GPIO_PIN(40), + GPIO_PIN(41), + GPIO_PIN(42), + GPIO_PIN(43), + GPIO_PIN(44), + GPIO_PIN(45), + GPIO_PIN(46), + GPIO_PIN(47), + GPIO_PIN(48), + GPIO_PIN(49), + GPIO_PIN(50), + GPIO_PIN(51), + GPIO_PIN(52), + GPIO_PIN(53), + PINCTRL_PIN(54, "emmc_cmd"), + PINCTRL_PIN(55, "emmc_ds"), + PINCTRL_PIN(56, "emmc_clk"), + PINCTRL_PIN(57, "emmc_dat0"), + PINCTRL_PIN(58, "emmc_dat1"), + PINCTRL_PIN(59, "emmc_dat2"), + PINCTRL_PIN(60, "emmc_dat3"), + PINCTRL_PIN(61, "emmc_dat4"), + PINCTRL_PIN(62, "emmc_dat5"), + PINCTRL_PIN(63, "emmc_dat6"), + PINCTRL_PIN(64, "emmc_dat7"), +}; + +static struct pinctrl_pin_desc bcm2712_c0_aon_gpio_pins[] = { + AGPIO_PIN(0), + AGPIO_PIN(1), + AGPIO_PIN(2), + AGPIO_PIN(3), + AGPIO_PIN(4), + AGPIO_PIN(5), + AGPIO_PIN(6), + AGPIO_PIN(7), + AGPIO_PIN(8), + AGPIO_PIN(9), + AGPIO_PIN(10), + AGPIO_PIN(11), + AGPIO_PIN(12), + AGPIO_PIN(13), + AGPIO_PIN(14), + AGPIO_PIN(15), + AGPIO_PIN(16), + SGPIO_PIN(0), + SGPIO_PIN(1), + SGPIO_PIN(2), + SGPIO_PIN(3), + SGPIO_PIN(4), + SGPIO_PIN(5), +}; + +static const struct pin_regs bcm2712_d0_gpio_pin_regs[] = { + GPIO_REGS(1, 0, 0, 4, 5), + GPIO_REGS(2, 0, 1, 4, 6), + GPIO_REGS(3, 0, 2, 4, 7), + GPIO_REGS(4, 0, 3, 4, 8), + GPIO_REGS(10, 0, 4, 4, 9), + GPIO_REGS(11, 0, 5, 4, 10), + GPIO_REGS(12, 0, 6, 4, 11), + GPIO_REGS(13, 0, 7, 4, 12), + GPIO_REGS(14, 1, 0, 4, 13), + GPIO_REGS(15, 1, 1, 4, 14), + GPIO_REGS(18, 1, 2, 5, 0), + GPIO_REGS(19, 1, 3, 5, 1), + GPIO_REGS(20, 1, 4, 5, 2), + GPIO_REGS(21, 1, 5, 5, 3), + GPIO_REGS(22, 1, 6, 5, 4), + GPIO_REGS(23, 1, 7, 5, 5), + GPIO_REGS(24, 2, 0, 5, 6), + GPIO_REGS(25, 2, 1, 5, 7), + GPIO_REGS(26, 2, 2, 5, 8), + GPIO_REGS(27, 2, 3, 5, 9), + GPIO_REGS(28, 2, 4, 5, 10), + GPIO_REGS(29, 2, 5, 5, 11), + GPIO_REGS(30, 2, 6, 5, 12), + GPIO_REGS(31, 2, 7, 5, 13), + GPIO_REGS(32, 3, 0, 5, 14), + GPIO_REGS(33, 3, 1, 6, 0), + GPIO_REGS(34, 3, 2, 6, 1), + GPIO_REGS(35, 3, 3, 6, 2), + EMMC_REGS(36, 6, 3), /* EMMC_CMD */ + EMMC_REGS(37, 6, 4), /* EMMC_DS */ + EMMC_REGS(38, 6, 5), /* EMMC_CLK */ + EMMC_REGS(39, 6, 6), /* EMMC_DAT0 */ + EMMC_REGS(40, 6, 7), /* EMMC_DAT1 */ + EMMC_REGS(41, 6, 8), /* EMMC_DAT2 */ + EMMC_REGS(42, 6, 9), /* EMMC_DAT3 */ + EMMC_REGS(43, 6, 10), /* EMMC_DAT4 */ + EMMC_REGS(44, 6, 11), /* EMMC_DAT5 */ + EMMC_REGS(45, 6, 12), /* EMMC_DAT6 */ + EMMC_REGS(46, 6, 13), /* EMMC_DAT7 */ +}; + +static struct pin_regs bcm2712_d0_aon_gpio_pin_regs[] = { + AGPIO_REGS(0, 3, 0, 5, 9), + AGPIO_REGS(1, 3, 1, 5, 10), + AGPIO_REGS(2, 3, 2, 5, 11), + AGPIO_REGS(3, 3, 3, 5, 12), + AGPIO_REGS(4, 3, 4, 5, 13), + AGPIO_REGS(5, 3, 5, 5, 14), + AGPIO_REGS(6, 3, 6, 6, 0), + AGPIO_REGS(8, 3, 7, 6, 1), + AGPIO_REGS(9, 4, 0, 6, 2), + AGPIO_REGS(12, 4, 1, 6, 3), + AGPIO_REGS(13, 4, 2, 6, 4), + AGPIO_REGS(14, 4, 3, 6, 5), + SGPIO_REGS(0, 0, 0), + SGPIO_REGS(1, 0, 1), + SGPIO_REGS(2, 0, 2), + SGPIO_REGS(3, 0, 3), + SGPIO_REGS(4, 1, 0), + SGPIO_REGS(5, 2, 0), +}; + +static const struct pinctrl_pin_desc bcm2712_d0_gpio_pins[] = { + GPIO_PIN(1), + GPIO_PIN(2), + GPIO_PIN(3), + GPIO_PIN(4), + GPIO_PIN(10), + GPIO_PIN(11), + GPIO_PIN(12), + GPIO_PIN(13), + GPIO_PIN(14), + GPIO_PIN(15), + GPIO_PIN(18), + GPIO_PIN(19), + GPIO_PIN(20), + GPIO_PIN(21), + GPIO_PIN(22), + GPIO_PIN(23), + GPIO_PIN(24), + GPIO_PIN(25), + GPIO_PIN(26), + GPIO_PIN(27), + GPIO_PIN(28), + GPIO_PIN(29), + GPIO_PIN(30), + GPIO_PIN(31), + GPIO_PIN(32), + GPIO_PIN(33), + GPIO_PIN(34), + GPIO_PIN(35), + PINCTRL_PIN(36, "emmc_cmd"), + PINCTRL_PIN(37, "emmc_ds"), + PINCTRL_PIN(38, "emmc_clk"), + PINCTRL_PIN(39, "emmc_dat0"), + PINCTRL_PIN(40, "emmc_dat1"), + PINCTRL_PIN(41, "emmc_dat2"), + PINCTRL_PIN(42, "emmc_dat3"), + PINCTRL_PIN(43, "emmc_dat4"), + PINCTRL_PIN(44, "emmc_dat5"), + PINCTRL_PIN(45, "emmc_dat6"), + PINCTRL_PIN(46, "emmc_dat7"), +}; + +static struct pinctrl_pin_desc bcm2712_d0_aon_gpio_pins[] = { + AGPIO_PIN(0), + AGPIO_PIN(1), + AGPIO_PIN(2), + AGPIO_PIN(3), + AGPIO_PIN(4), + AGPIO_PIN(5), + AGPIO_PIN(6), + AGPIO_PIN(8), + AGPIO_PIN(9), + AGPIO_PIN(12), + AGPIO_PIN(13), + AGPIO_PIN(14), + SGPIO_PIN(0), + SGPIO_PIN(1), + SGPIO_PIN(2), + SGPIO_PIN(3), + SGPIO_PIN(4), + SGPIO_PIN(5), +}; + +static const char * const bcm2712_func_names[] = { + FUNC(gpio), + FUNC(alt1), + FUNC(alt2), + FUNC(alt3), + FUNC(alt4), + FUNC(alt5), + FUNC(alt6), + FUNC(alt7), + FUNC(alt8), + FUNC(aon_cpu_standbyb), + FUNC(aon_fp_4sec_resetb), + FUNC(aon_gpclk), + FUNC(aon_pwm), + FUNC(arm_jtag), + FUNC(aud_fs_clk0), + FUNC(avs_pmu_bsc), + FUNC(bsc_m0), + FUNC(bsc_m1), + FUNC(bsc_m2), + FUNC(bsc_m3), + FUNC(clk_observe), + FUNC(ctl_hdmi_5v), + FUNC(enet0), + FUNC(enet0_mii), + FUNC(enet0_rgmii), + FUNC(ext_sc_clk), + FUNC(fl0), + FUNC(fl1), + FUNC(gpclk0), + FUNC(gpclk1), + FUNC(gpclk2), + FUNC(hdmi_tx0_auto_i2c), + FUNC(hdmi_tx0_bsc), + FUNC(hdmi_tx1_auto_i2c), + FUNC(hdmi_tx1_bsc), + FUNC(i2s_in), + FUNC(i2s_out), + FUNC(ir_in), + FUNC(mtsif), + FUNC(mtsif_alt), + FUNC(mtsif_alt1), + FUNC(pdm), + FUNC(pkt), + FUNC(pm_led_out), + FUNC(sc0), + FUNC(sd0), + FUNC(sd2), + FUNC(sd_card_a), + FUNC(sd_card_b), + FUNC(sd_card_c), + FUNC(sd_card_d), + FUNC(sd_card_e), + FUNC(sd_card_f), + FUNC(sd_card_g), + FUNC(spdif_out), + FUNC(spi_m), + FUNC(spi_s), + FUNC(sr_edm_sense), + FUNC(te0), + FUNC(te1), + FUNC(tsio), + FUNC(uart0), + FUNC(uart1), + FUNC(uart2), + FUNC(usb_pwr), + FUNC(usb_vbus), + FUNC(uui), + FUNC(vc_i2c0), + FUNC(vc_i2c3), + FUNC(vc_i2c4), + FUNC(vc_i2c5), + FUNC(vc_i2csl), + FUNC(vc_pcm), + FUNC(vc_pwm0), + FUNC(vc_pwm1), + FUNC(vc_spi0), + FUNC(vc_spi3), + FUNC(vc_spi4), + FUNC(vc_spi5), + FUNC(vc_uart0), + FUNC(vc_uart2), + FUNC(vc_uart3), + FUNC(vc_uart4), +}; + +static const struct bcm2712_pin_funcs bcm2712_c0_aon_gpio_pin_funcs[] = { + PIN(0, ir_in, vc_spi0, vc_uart3, vc_i2c3, te0, vc_i2c0, _, _), + PIN(1, vc_pwm0, vc_spi0, vc_uart3, vc_i2c3, te1, aon_pwm, vc_i2c0, vc_pwm1), + PIN(2, vc_pwm0, vc_spi0, vc_uart3, ctl_hdmi_5v, fl0, aon_pwm, ir_in, vc_pwm1), + PIN(3, ir_in, vc_spi0, vc_uart3, aon_fp_4sec_resetb, fl1, sd_card_g, aon_gpclk, _), + PIN(4, gpclk0, vc_spi0, vc_i2csl, aon_gpclk, pm_led_out, aon_pwm, sd_card_g, vc_pwm0), + PIN(5, gpclk1, ir_in, vc_i2csl, clk_observe, aon_pwm, sd_card_g, vc_pwm0, _), + PIN(6, uart1, vc_uart4, gpclk2, ctl_hdmi_5v, vc_uart0, vc_spi3, _, _), + PIN(7, uart1, vc_uart4, gpclk0, aon_pwm, vc_uart0, vc_spi3, _, _), + PIN(8, uart1, vc_uart4, vc_i2csl, ctl_hdmi_5v, vc_uart0, vc_spi3, _, _), + PIN(9, uart1, vc_uart4, vc_i2csl, aon_pwm, vc_uart0, vc_spi3, _, _), + PIN(10, tsio, ctl_hdmi_5v, sc0, spdif_out, vc_spi5, usb_pwr, aon_gpclk, sd_card_f), + PIN(11, tsio, uart0, sc0, aud_fs_clk0, vc_spi5, usb_vbus, vc_uart2, sd_card_f), + PIN(12, tsio, uart0, vc_uart0, tsio, vc_spi5, usb_pwr, vc_uart2, sd_card_f), + PIN(13, bsc_m1, uart0, vc_uart0, uui, vc_spi5, arm_jtag, vc_uart2, vc_i2c3), + PIN(14, bsc_m1, uart0, vc_uart0, uui, vc_spi5, arm_jtag, vc_uart2, vc_i2c3), + PIN(15, ir_in, aon_fp_4sec_resetb, vc_uart0, pm_led_out, ctl_hdmi_5v, aon_pwm, aon_gpclk, _), + PIN(16, aon_cpu_standbyb, gpclk0, pm_led_out, ctl_hdmi_5v, vc_pwm0, usb_pwr, aud_fs_clk0, _), +}; + +static const struct bcm2712_pin_funcs bcm2712_c0_aon_sgpio_pin_funcs[] = { + PIN(0, hdmi_tx0_bsc, hdmi_tx0_auto_i2c, bsc_m0, vc_i2c0, _, _, _, _), + PIN(1, hdmi_tx0_bsc, hdmi_tx0_auto_i2c, bsc_m0, vc_i2c0, _, _, _, _), + PIN(2, hdmi_tx1_bsc, hdmi_tx1_auto_i2c, bsc_m1, vc_i2c4, ctl_hdmi_5v, _, _, _), + PIN(3, hdmi_tx1_bsc, hdmi_tx1_auto_i2c, bsc_m1, vc_i2c4, _, _, _, _), + PIN(4, avs_pmu_bsc, bsc_m2, vc_i2c5, ctl_hdmi_5v, _, _, _, _), + PIN(5, avs_pmu_bsc, bsc_m2, vc_i2c5, _, _, _, _, _), +}; + +static const struct bcm2712_pin_funcs bcm2712_c0_gpio_pin_funcs[] = { + PIN(0, bsc_m3, vc_i2c0, gpclk0, enet0, vc_pwm1, vc_spi0, ir_in, _), + PIN(1, bsc_m3, vc_i2c0, gpclk1, enet0, vc_pwm1, sr_edm_sense, vc_spi0, vc_uart3), + PIN(2, pdm, i2s_in, gpclk2, vc_spi4, pkt, vc_spi0, vc_uart3, _), + PIN(3, pdm, i2s_in, vc_spi4, pkt, vc_spi0, vc_uart3, _, _), + PIN(4, pdm, i2s_in, arm_jtag, vc_spi4, pkt, vc_spi0, vc_uart3, _), + PIN(5, pdm, vc_i2c3, arm_jtag, sd_card_e, vc_spi4, pkt, vc_pcm, vc_i2c5), + PIN(6, pdm, vc_i2c3, arm_jtag, sd_card_e, vc_spi4, pkt, vc_pcm, vc_i2c5), + PIN(7, i2s_out, spdif_out, arm_jtag, sd_card_e, vc_i2c3, enet0_rgmii, vc_pcm, vc_spi4), + PIN(8, i2s_out, aud_fs_clk0, arm_jtag, sd_card_e, vc_i2c3, enet0_mii, vc_pcm, vc_spi4), + PIN(9, i2s_out, aud_fs_clk0, arm_jtag, sd_card_e, enet0_mii, sd_card_c, vc_spi4, _), + PIN(10, bsc_m3, mtsif_alt1, i2s_in, i2s_out, vc_spi5, enet0_mii, sd_card_c, vc_spi4), + PIN(11, bsc_m3, mtsif_alt1, i2s_in, i2s_out, vc_spi5, enet0_mii, sd_card_c, vc_spi4), + PIN(12, spi_s, mtsif_alt1, i2s_in, i2s_out, vc_spi5, vc_i2csl, sd0, sd_card_d), + PIN(13, spi_s, mtsif_alt1, i2s_out, usb_vbus, vc_spi5, vc_i2csl, sd0, sd_card_d), + PIN(14, spi_s, vc_i2csl, enet0_rgmii, arm_jtag, vc_spi5, vc_pwm0, vc_i2c4, sd_card_d), + PIN(15, spi_s, vc_i2csl, vc_spi3, arm_jtag, vc_pwm0, vc_i2c4, gpclk0, _), + PIN(16, sd_card_b, i2s_out, vc_spi3, i2s_in, sd0, enet0_rgmii, gpclk1, _), + PIN(17, sd_card_b, i2s_out, vc_spi3, i2s_in, ext_sc_clk, sd0, enet0_rgmii, gpclk2), + PIN(18, sd_card_b, i2s_out, vc_spi3, i2s_in, sd0, enet0_rgmii, vc_pwm1, _), + PIN(19, sd_card_b, usb_pwr, vc_spi3, pkt, spdif_out, sd0, ir_in, vc_pwm1), + PIN(20, sd_card_b, uui, vc_uart0, arm_jtag, uart2, usb_pwr, vc_pcm, vc_uart4), + PIN(21, usb_pwr, uui, vc_uart0, arm_jtag, uart2, sd_card_b, vc_pcm, vc_uart4), + PIN(22, usb_pwr, enet0, vc_uart0, mtsif, uart2, usb_vbus, vc_pcm, vc_i2c5), + PIN(23, usb_vbus, enet0, vc_uart0, mtsif, uart2, i2s_out, vc_pcm, vc_i2c5), + PIN(24, mtsif, pkt, uart0, enet0_rgmii, enet0_rgmii, vc_i2c4, vc_uart3, _), + PIN(25, mtsif, pkt, sc0, uart0, enet0_rgmii, enet0_rgmii, vc_i2c4, vc_uart3), + PIN(26, mtsif, pkt, sc0, uart0, enet0_rgmii, vc_uart4, vc_spi5, _), + PIN(27, mtsif, pkt, sc0, uart0, enet0_rgmii, vc_uart4, vc_spi5, _), + PIN(28, mtsif, pkt, sc0, enet0_rgmii, vc_uart4, vc_spi5, _, _), + PIN(29, mtsif, pkt, sc0, enet0_rgmii, vc_uart4, vc_spi5, _, _), + PIN(30, mtsif, pkt, sc0, sd2, enet0_rgmii, gpclk0, vc_pwm0, _), + PIN(31, mtsif, pkt, sc0, sd2, enet0_rgmii, vc_spi3, vc_pwm0, _), + PIN(32, mtsif, pkt, sc0, sd2, enet0_rgmii, vc_spi3, vc_uart3, _), + PIN(33, mtsif, pkt, sd2, enet0_rgmii, vc_spi3, vc_uart3, _, _), + PIN(34, mtsif, pkt, ext_sc_clk, sd2, enet0_rgmii, vc_spi3, vc_i2c5, _), + PIN(35, mtsif, pkt, sd2, enet0_rgmii, vc_spi3, vc_i2c5, _, _), + PIN(36, sd0, mtsif, sc0, i2s_in, vc_uart3, vc_uart2, _, _), + PIN(37, sd0, mtsif, sc0, vc_spi0, i2s_in, vc_uart3, vc_uart2, _), + PIN(38, sd0, mtsif_alt, sc0, vc_spi0, i2s_in, vc_uart3, vc_uart2, _), + PIN(39, sd0, mtsif_alt, sc0, vc_spi0, vc_uart3, vc_uart2, _, _), + PIN(40, sd0, mtsif_alt, sc0, vc_spi0, bsc_m3, _, _, _), + PIN(41, sd0, mtsif_alt, sc0, vc_spi0, bsc_m3, _, _, _), + PIN(42, vc_spi0, mtsif_alt, vc_i2c0, sd_card_a, mtsif_alt1, arm_jtag, pdm, spi_m), + PIN(43, vc_spi0, mtsif_alt, vc_i2c0, sd_card_a, mtsif_alt1, arm_jtag, pdm, spi_m), + PIN(44, vc_spi0, mtsif_alt, enet0, sd_card_a, mtsif_alt1, arm_jtag, pdm, spi_m), + PIN(45, vc_spi0, mtsif_alt, enet0, sd_card_a, mtsif_alt1, arm_jtag, pdm, spi_m), + PIN(46, vc_spi0, mtsif_alt, sd_card_a, mtsif_alt1, arm_jtag, pdm, spi_m, _), + PIN(47, enet0, mtsif_alt, i2s_out, mtsif_alt1, arm_jtag, _, _, _), + PIN(48, sc0, usb_pwr, spdif_out, mtsif, _, _, _, _), + PIN(49, sc0, usb_pwr, aud_fs_clk0, mtsif, _, _, _, _), + PIN(50, sc0, usb_vbus, sc0, _, _, _, _, _), + PIN(51, sc0, enet0, sc0, sr_edm_sense, _, _, _, _), + PIN(52, sc0, enet0, vc_pwm1, _, _, _, _, _), + PIN(53, sc0, enet0_rgmii, ext_sc_clk, _, _, _, _, _), +}; + +static const struct bcm2712_pin_funcs bcm2712_d0_aon_gpio_pin_funcs[] = { + PIN(0, ir_in, vc_spi0, vc_uart0, vc_i2c3, uart0, vc_i2c0, _, _), + PIN(1, vc_pwm0, vc_spi0, vc_uart0, vc_i2c3, uart0, aon_pwm, vc_i2c0, vc_pwm1), + PIN(2, vc_pwm0, vc_spi0, vc_uart0, ctl_hdmi_5v, uart0, aon_pwm, ir_in, vc_pwm1), + PIN(3, ir_in, vc_spi0, vc_uart0, uart0, sd_card_g, aon_gpclk, _, _), + PIN(4, gpclk0, vc_spi0, pm_led_out, aon_pwm, sd_card_g, vc_pwm0, _, _), + PIN(5, gpclk1, ir_in, aon_pwm, sd_card_g, vc_pwm0, _, _, _), + PIN(6, uart1, vc_uart2, ctl_hdmi_5v, gpclk2, vc_spi3, _, _, _), + PIN(7, _, _, _, _, _, _, _, _), + PIN(8, uart1, vc_uart2, ctl_hdmi_5v, vc_spi0, vc_spi3, _, _, _), + PIN(9, uart1, vc_uart2, vc_uart0, aon_pwm, vc_spi0, vc_uart2, vc_spi3, _), + PIN(10, _, _, _, _, _, _, _, _), + PIN(11, _, _, _, _, _, _, _, _), + PIN(12, uart1, vc_uart2, vc_uart0, vc_spi0, usb_pwr, vc_uart2, vc_spi3, _), + PIN(13, bsc_m1, vc_uart0, uui, vc_spi0, arm_jtag, vc_uart2, vc_i2c3, _), + PIN(14, bsc_m1, aon_gpclk, vc_uart0, uui, vc_spi0, arm_jtag, vc_uart2, vc_i2c3), +}; + +static const struct bcm2712_pin_funcs bcm2712_d0_aon_sgpio_pin_funcs[] = { + PIN(0, hdmi_tx0_bsc, hdmi_tx0_auto_i2c, bsc_m0, vc_i2c0, _, _, _, _), + PIN(1, hdmi_tx0_bsc, hdmi_tx0_auto_i2c, bsc_m0, vc_i2c0, _, _, _, _), + PIN(2, hdmi_tx1_bsc, hdmi_tx1_auto_i2c, bsc_m1, vc_i2c0, ctl_hdmi_5v, _, _, _), + PIN(3, hdmi_tx1_bsc, hdmi_tx1_auto_i2c, bsc_m1, vc_i2c0, _, _, _, _), + PIN(4, avs_pmu_bsc, bsc_m2, vc_i2c3, ctl_hdmi_5v, _, _, _, _), + PIN(5, avs_pmu_bsc, bsc_m2, vc_i2c3, _, _, _, _, _), +}; + +static const struct bcm2712_pin_funcs bcm2712_d0_gpio_pin_funcs[] = { + PIN(1, vc_i2c0, usb_pwr, gpclk0, sd_card_e, vc_spi3, sr_edm_sense, vc_spi0, vc_uart0), + PIN(2, vc_i2c0, usb_pwr, gpclk1, sd_card_e, vc_spi3, clk_observe, vc_spi0, vc_uart0), + PIN(3, vc_i2c3, usb_vbus, gpclk2, sd_card_e, vc_spi3, vc_spi0, vc_uart0, _), + PIN(4, vc_i2c3, vc_pwm1, vc_spi3, sd_card_e, vc_spi3, vc_spi0, vc_uart0, _), + PIN(10, bsc_m3, vc_pwm1, vc_spi3, sd_card_e, vc_spi3, gpclk0, _, _), + PIN(11, bsc_m3, vc_spi3, clk_observe, sd_card_c, gpclk1, _, _, _), + PIN(12, spi_s, vc_spi3, sd_card_c, sd_card_d, _, _, _, _), + PIN(13, spi_s, vc_spi3, sd_card_c, sd_card_d, _, _, _, _), + PIN(14, spi_s, uui, arm_jtag, vc_pwm0, vc_i2c0, sd_card_d, _, _), + PIN(15, spi_s, uui, arm_jtag, vc_pwm0, vc_i2c0, gpclk0, _, _), + PIN(18, sd_card_f, vc_pwm1, _, _, _, _, _, _), + PIN(19, sd_card_f, usb_pwr, vc_pwm1, _, _, _, _, _), + PIN(20, vc_i2c3, uui, vc_uart0, arm_jtag, vc_uart2, _, _, _), + PIN(21, vc_i2c3, uui, vc_uart0, arm_jtag, vc_uart2, _, _, _), + PIN(22, sd_card_f, vc_uart0, vc_i2c3, _, _, _, _, _), + PIN(23, vc_uart0, vc_i2c3, _, _, _, _, _, _), + PIN(24, sd_card_b, vc_spi0, arm_jtag, uart0, usb_pwr, vc_uart2, vc_uart0, _), + PIN(25, sd_card_b, vc_spi0, arm_jtag, uart0, usb_pwr, vc_uart2, vc_uart0, _), + PIN(26, sd_card_b, vc_spi0, arm_jtag, uart0, usb_vbus, vc_uart2, vc_spi0, _), + PIN(27, sd_card_b, vc_spi0, arm_jtag, uart0, vc_uart2, vc_spi0, _, _), + PIN(28, sd_card_b, vc_spi0, arm_jtag, vc_i2c0, vc_spi0, _, _, _), + PIN(29, arm_jtag, vc_i2c0, vc_spi0, _, _, _, _, _), + PIN(30, sd2, gpclk0, vc_pwm0, _, _, _, _, _), + PIN(31, sd2, vc_spi3, vc_pwm0, _, _, _, _, _), + PIN(32, sd2, vc_spi3, vc_uart3, _, _, _, _, _), + PIN(33, sd2, vc_spi3, vc_uart3, _, _, _, _, _), + PIN(34, sd2, vc_spi3, vc_i2c5, _, _, _, _, _), + PIN(35, sd2, vc_spi3, vc_i2c5, _, _, _, _, _), +}; + +static inline u32 bcm2712_reg_rd(struct bcm2712_pinctrl *pc, unsigned reg) +{ + return readl(pc->base + reg); +} + +static inline void bcm2712_reg_wr(struct bcm2712_pinctrl *pc, unsigned reg, + u32 val) +{ + writel(val, pc->base + reg); +} + +static enum bcm2712_funcs bcm2712_pinctrl_fsel_get( + struct bcm2712_pinctrl *pc, unsigned pin) +{ + u32 bit = pc->pin_regs[pin].mux_bit; + enum bcm2712_funcs func; + int fsel; + u32 val; + + if (!bit) + return func_gpio; + bit &= ~MUX_BIT_VALID; + + val = bcm2712_reg_rd(pc, BIT_TO_REG(bit)); + fsel = (val >> BIT_TO_SHIFT(bit)) & BCM2712_FSEL_MASK; + func = pc->pin_funcs[pin].funcs[fsel]; + if (func >= func_count) + func = (enum bcm2712_funcs)fsel; + + dev_dbg(pc->dev, "get %04x: %08x (%u => %s)\n", + BIT_TO_REG(bit), val, pin, + bcm2712_func_names[func]); + + return func; +} + +static void bcm2712_pinctrl_fsel_set( + struct bcm2712_pinctrl *pc, unsigned pin, + enum bcm2712_funcs func) +{ + u32 bit = pc->pin_regs[pin].mux_bit, val; + const u8 *pin_funcs; + unsigned long flags; + int fsel; + int cur; + int i; + + if (!bit || func >= func_count) + return; + bit &= ~MUX_BIT_VALID; + + fsel = BCM2712_FSEL_COUNT; + + if (func >= BCM2712_FSEL_COUNT) { + /* Convert to an fsel number */ + pin_funcs = pc->pin_funcs[pin].funcs; + for (i = 1; i < BCM2712_FSEL_COUNT; i++) { + if (pin_funcs[i - 1] == func) { + fsel = i; + break; + } + } + } else { + fsel = (enum bcm2712_funcs)func; + } + if (fsel >= BCM2712_FSEL_COUNT) + return; + + spin_lock_irqsave(&pc->lock, flags); + + val = bcm2712_reg_rd(pc, BIT_TO_REG(bit)); + cur = (val >> BIT_TO_SHIFT(bit)) & BCM2712_FSEL_MASK; + + dev_dbg(pc->dev, "read %04x: %08x (%u => %s)\n", + BIT_TO_REG(bit), val, pin, + bcm2712_func_names[cur]); + + if (cur != fsel) { + val &= ~(BCM2712_FSEL_MASK << BIT_TO_SHIFT(bit)); + val |= fsel << BIT_TO_SHIFT(bit); + + dev_dbg(pc->dev, "write %04x: %08x (%u <= %s)\n", + BIT_TO_REG(bit), val, pin, + bcm2712_func_names[fsel]); + bcm2712_reg_wr(pc, BIT_TO_REG(bit), val); + } + + spin_unlock_irqrestore(&pc->lock, flags); +} + +static int bcm2712_pctl_get_groups_count(struct pinctrl_dev *pctldev) +{ + struct bcm2712_pinctrl *pc = pinctrl_dev_get_drvdata(pctldev); + + return pc->pctl_desc.npins; +} + +static const char *bcm2712_pctl_get_group_name(struct pinctrl_dev *pctldev, + unsigned selector) +{ + struct bcm2712_pinctrl *pc = pinctrl_dev_get_drvdata(pctldev); + + return pc->gpio_groups[selector]; +} + +static int bcm2712_pctl_get_group_pins(struct pinctrl_dev *pctldev, + unsigned selector, + const unsigned **pins, + unsigned *num_pins) +{ + struct bcm2712_pinctrl *pc = pinctrl_dev_get_drvdata(pctldev); + + *pins = &pc->pctl_desc.pins[selector].number; + *num_pins = 1; + + return 0; +} + +static void bcm2712_pctl_pin_dbg_show(struct pinctrl_dev *pctldev, + struct seq_file *s, + unsigned offset) +{ + struct bcm2712_pinctrl *pc = pinctrl_dev_get_drvdata(pctldev); + enum bcm2712_funcs fsel = bcm2712_pinctrl_fsel_get(pc, offset); + const char *fname = bcm2712_func_names[fsel]; + + seq_printf(s, "function %s", fname); +} + +static void bcm2712_pctl_dt_free_map(struct pinctrl_dev *pctldev, + struct pinctrl_map *maps, unsigned num_maps) +{ + int i; + + for (i = 0; i < num_maps; i++) + if (maps[i].type == PIN_MAP_TYPE_CONFIGS_PIN) + kfree(maps[i].data.configs.configs); + + kfree(maps); +} + +static const struct pinctrl_ops bcm2712_pctl_ops = { + .get_groups_count = bcm2712_pctl_get_groups_count, + .get_group_name = bcm2712_pctl_get_group_name, + .get_group_pins = bcm2712_pctl_get_group_pins, + .pin_dbg_show = bcm2712_pctl_pin_dbg_show, + .dt_node_to_map = pinconf_generic_dt_node_to_map_all, + .dt_free_map = bcm2712_pctl_dt_free_map, +}; + +static int bcm2712_pmx_free(struct pinctrl_dev *pctldev, + unsigned offset) +{ + struct bcm2712_pinctrl *pc = pinctrl_dev_get_drvdata(pctldev); + + /* disable by setting to GPIO */ + bcm2712_pinctrl_fsel_set(pc, offset, func_gpio); + return 0; +} + +static int bcm2712_pmx_get_functions_count(struct pinctrl_dev *pctldev) +{ + return func_count; +} + +static const char *bcm2712_pmx_get_function_name(struct pinctrl_dev *pctldev, + unsigned selector) +{ + return (selector < func_count) ? bcm2712_func_names[selector] : NULL; +} + +static int bcm2712_pmx_get_function_groups(struct pinctrl_dev *pctldev, + unsigned selector, + const char * const **groups, + unsigned * const num_groups) +{ + struct bcm2712_pinctrl *pc = pinctrl_dev_get_drvdata(pctldev); + /* every pin can do every function */ + *groups = pc->gpio_groups; + *num_groups = pc->pctl_desc.npins; + + return 0; +} + +static int bcm2712_pmx_set(struct pinctrl_dev *pctldev, + unsigned func_selector, + unsigned group_selector) +{ + struct bcm2712_pinctrl *pc = pinctrl_dev_get_drvdata(pctldev); + const struct pinctrl_desc *pctldesc = &pc->pctl_desc; + const struct pinctrl_pin_desc *pindesc; + + if (group_selector >= pctldesc->npins) + return -EINVAL; + pindesc = &pctldesc->pins[group_selector]; + bcm2712_pinctrl_fsel_set(pc, pindesc->number, func_selector); + + return 0; +} +static int bcm2712_pmx_gpio_request_enable(struct pinctrl_dev *pctldev, + struct pinctrl_gpio_range *range, + unsigned pin) +{ + struct bcm2712_pinctrl *pc = pinctrl_dev_get_drvdata(pctldev); + + bcm2712_pinctrl_fsel_set(pc, pin, func_gpio); + + return 0; +} + +static void bcm2712_pmx_gpio_disable_free(struct pinctrl_dev *pctldev, + struct pinctrl_gpio_range *range, + unsigned offset) +{ + struct bcm2712_pinctrl *pc = pinctrl_dev_get_drvdata(pctldev); + + /* disable by setting to GPIO */ + bcm2712_pinctrl_fsel_set(pc, offset, func_gpio); +} + +static const struct pinmux_ops bcm2712_pmx_ops = { + .free = bcm2712_pmx_free, + .get_functions_count = bcm2712_pmx_get_functions_count, + .get_function_name = bcm2712_pmx_get_function_name, + .get_function_groups = bcm2712_pmx_get_function_groups, + .set_mux = bcm2712_pmx_set, + .gpio_request_enable = bcm2712_pmx_gpio_request_enable, + .gpio_disable_free = bcm2712_pmx_gpio_disable_free, +}; + +static unsigned int bcm2712_pull_config_get(struct bcm2712_pinctrl *pc, + unsigned int pin) +{ + u32 bit = pc->pin_regs[pin].pad_bit, val; + + if (unlikely(bit == REG_BIT_INVALID)) + return BCM2712_PULL_NONE; + + val = bcm2712_reg_rd(pc, BIT_TO_REG(bit)); + return (val >> BIT_TO_SHIFT(bit)) & BCM2712_PULL_MASK; +} + +static void bcm2712_pull_config_set(struct bcm2712_pinctrl *pc, + unsigned int pin, unsigned int arg) +{ + u32 bit = pc->pin_regs[pin].pad_bit, val; + unsigned long flags; + + if (unlikely(bit == REG_BIT_INVALID)) { + dev_warn(pc->dev, "can't set pulls for %s\n", pc->gpio_groups[pin]); + return; + } + + spin_lock_irqsave(&pc->lock, flags); + + val = bcm2712_reg_rd(pc, BIT_TO_REG(bit)); + val &= ~(BCM2712_PULL_MASK << BIT_TO_SHIFT(bit)); + val |= (arg << BIT_TO_SHIFT(bit)); + bcm2712_reg_wr(pc, BIT_TO_REG(bit), val); + + spin_unlock_irqrestore(&pc->lock, flags); +} + +static int bcm2712_pinconf_get(struct pinctrl_dev *pctldev, + unsigned pin, unsigned long *config) +{ + struct bcm2712_pinctrl *pc = pinctrl_dev_get_drvdata(pctldev); + enum pin_config_param param = pinconf_to_config_param(*config); + u32 arg; + + switch (param) { + case PIN_CONFIG_BIAS_DISABLE: + arg = (bcm2712_pull_config_get(pc, pin) == BCM2712_PULL_NONE); + break; + case PIN_CONFIG_BIAS_PULL_DOWN: + arg = (bcm2712_pull_config_get(pc, pin) == BCM2712_PULL_DOWN); + break; + case PIN_CONFIG_BIAS_PULL_UP: + arg = (bcm2712_pull_config_get(pc, pin) == BCM2712_PULL_UP); + break; + default: + return -ENOTSUPP; + } + + *config = pinconf_to_config_packed(param, arg); + + return 0; +} + +static int bcm2712_pinconf_set(struct pinctrl_dev *pctldev, + unsigned int pin, unsigned long *configs, + unsigned int num_configs) +{ + struct bcm2712_pinctrl *pc = pinctrl_dev_get_drvdata(pctldev); + u32 param, arg; + int i; + + for (i = 0; i < num_configs; i++) { + param = pinconf_to_config_param(configs[i]); + arg = pinconf_to_config_argument(configs[i]); + + switch (param) { + case PIN_CONFIG_BIAS_DISABLE: + bcm2712_pull_config_set(pc, pin, BCM2712_PULL_NONE); + break; + case PIN_CONFIG_BIAS_PULL_DOWN: + bcm2712_pull_config_set(pc, pin, BCM2712_PULL_DOWN); + break; + case PIN_CONFIG_BIAS_PULL_UP: + bcm2712_pull_config_set(pc, pin, BCM2712_PULL_UP); + break; + default: + return -ENOTSUPP; + } + } /* for each config */ + + return 0; +} + +static const struct pinconf_ops bcm2712_pinconf_ops = { + .is_generic = true, + .pin_config_get = bcm2712_pinconf_get, + .pin_config_set = bcm2712_pinconf_set, +}; + +static const struct pinctrl_desc bcm2712_c0_pinctrl_desc = { + .name = "pinctrl-bcm2712", + .pins = bcm2712_c0_gpio_pins, + .npins = ARRAY_SIZE(bcm2712_c0_gpio_pins), + .pctlops = &bcm2712_pctl_ops, + .pmxops = &bcm2712_pmx_ops, + .confops = &bcm2712_pinconf_ops, + .owner = THIS_MODULE, +}; + +static const struct pinctrl_desc bcm2712_c0_aon_pinctrl_desc = { + .name = "aon-pinctrl-bcm2712", + .pins = bcm2712_c0_aon_gpio_pins, + .npins = ARRAY_SIZE(bcm2712_c0_aon_gpio_pins), + .pctlops = &bcm2712_pctl_ops, + .pmxops = &bcm2712_pmx_ops, + .confops = &bcm2712_pinconf_ops, + .owner = THIS_MODULE, +}; + +static const struct pinctrl_desc bcm2712_d0_pinctrl_desc = { + .name = "pinctrl-bcm2712", + .pins = bcm2712_d0_gpio_pins, + .npins = ARRAY_SIZE(bcm2712_d0_gpio_pins), + .pctlops = &bcm2712_pctl_ops, + .pmxops = &bcm2712_pmx_ops, + .confops = &bcm2712_pinconf_ops, + .owner = THIS_MODULE, +}; + +static const struct pinctrl_desc bcm2712_d0_aon_pinctrl_desc = { + .name = "aon-pinctrl-bcm2712", + .pins = bcm2712_d0_aon_gpio_pins, + .npins = ARRAY_SIZE(bcm2712_d0_aon_gpio_pins), + .pctlops = &bcm2712_pctl_ops, + .pmxops = &bcm2712_pmx_ops, + .confops = &bcm2712_pinconf_ops, + .owner = THIS_MODULE, +}; + +static const struct pinctrl_gpio_range bcm2712_c0_pinctrl_gpio_range = { + .name = "pinctrl-bcm2712", + .npins = ARRAY_SIZE(bcm2712_c0_gpio_pins), +}; + +static const struct pinctrl_gpio_range bcm2712_c0_aon_pinctrl_gpio_range = { + .name = "aon-pinctrl-bcm2712", + .npins = ARRAY_SIZE(bcm2712_c0_aon_gpio_pins), +}; + +static const struct pinctrl_gpio_range bcm2712_d0_pinctrl_gpio_range = { + .name = "pinctrl-bcm2712", + .npins = ARRAY_SIZE(bcm2712_d0_gpio_pins), +}; + +static const struct pinctrl_gpio_range bcm2712_d0_aon_pinctrl_gpio_range = { + .name = "aon-pinctrl-bcm2712", + .npins = ARRAY_SIZE(bcm2712_d0_aon_gpio_pins), +}; + +static const struct bcm_plat_data bcm2712_c0_plat_data = { + .pctl_desc = &bcm2712_c0_pinctrl_desc, + .gpio_range = &bcm2712_c0_pinctrl_gpio_range, + .pin_regs = bcm2712_c0_gpio_pin_regs, + .pin_funcs = bcm2712_c0_gpio_pin_funcs, +}; + +static const struct bcm_plat_data bcm2712_c0_aon_plat_data = { + .pctl_desc = &bcm2712_c0_aon_pinctrl_desc, + .gpio_range = &bcm2712_c0_aon_pinctrl_gpio_range, + .pin_regs = bcm2712_c0_aon_gpio_pin_regs, + .pin_funcs = bcm2712_c0_aon_gpio_pin_funcs, +}; + +static const struct bcm_plat_data bcm2712_d0_plat_data = { + .pctl_desc = &bcm2712_d0_pinctrl_desc, + .gpio_range = &bcm2712_d0_pinctrl_gpio_range, + .pin_regs = bcm2712_d0_gpio_pin_regs, + .pin_funcs = bcm2712_d0_gpio_pin_funcs, +}; + +static const struct bcm_plat_data bcm2712_d0_aon_plat_data = { + .pctl_desc = &bcm2712_d0_aon_pinctrl_desc, + .gpio_range = &bcm2712_d0_aon_pinctrl_gpio_range, + .pin_regs = bcm2712_d0_aon_gpio_pin_regs, + .pin_funcs = bcm2712_d0_aon_gpio_pin_funcs, +}; + +static const struct of_device_id bcm2712_pinctrl_match[] = { + { + .compatible = "brcm,bcm2712-pinctrl", + .data = &bcm2712_c0_plat_data, + }, + { + .compatible = "brcm,bcm2712-aon-pinctrl", + .data = &bcm2712_c0_aon_plat_data, + }, + + { + .compatible = "brcm,bcm2712c0-pinctrl", + .data = &bcm2712_c0_plat_data, + }, + { + .compatible = "brcm,bcm2712c0-aon-pinctrl", + .data = &bcm2712_c0_aon_plat_data, + }, + + { + .compatible = "brcm,bcm2712d0-pinctrl", + .data = &bcm2712_d0_plat_data, + }, + { + .compatible = "brcm,bcm2712d0-aon-pinctrl", + .data = &bcm2712_d0_aon_plat_data, + }, + {} +}; + +static int bcm2712_pinctrl_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + const struct bcm_plat_data *pdata; + const struct of_device_id *match; + struct bcm2712_pinctrl *pc; + const char **names; + int num_pins, i; + + match = of_match_node(bcm2712_pinctrl_match, np); + if (!match) + return -EINVAL; + pdata = match->data; + + pc = devm_kzalloc(dev, sizeof(*pc), GFP_KERNEL); + if (!pc) + return -ENOMEM; + + platform_set_drvdata(pdev, pc); + pc->dev = dev; + spin_lock_init(&pc->lock); + + pc->base = devm_of_iomap(dev, np, 0, NULL); + if (IS_ERR(pc->base)) { + dev_err(dev, "could not get IO memory\n"); + return PTR_ERR(pc->base); + } + + pc->pctl_desc = *pdata->pctl_desc; + num_pins = pc->pctl_desc.npins; + names = devm_kmalloc_array(dev, num_pins, sizeof(const char *), + GFP_KERNEL); + if (!names) + return -ENOMEM; + for (i = 0; i < num_pins; i++) + names[i] = pc->pctl_desc.pins[i].name; + pc->gpio_groups = names; + pc->pin_regs = pdata->pin_regs; + pc->pin_funcs = pdata->pin_funcs; + pc->pctl_dev = devm_pinctrl_register(dev, &pc->pctl_desc, pc); + if (IS_ERR(pc->pctl_dev)) + return PTR_ERR(pc->pctl_dev); + + pc->gpio_range = *pdata->gpio_range; + pinctrl_add_gpio_range(pc->pctl_dev, &pc->gpio_range); + + return 0; +} + +static struct platform_driver bcm2712_pinctrl_driver = { + .probe = bcm2712_pinctrl_probe, + .driver = { + .name = MODULE_NAME, + .of_match_table = bcm2712_pinctrl_match, + .suppress_bind_attrs = true, + }, +}; +builtin_platform_driver(bcm2712_pinctrl_driver); diff --git a/drivers/pinctrl/bcm/pinctrl-bcm2835.c b/drivers/pinctrl/bcm/pinctrl-bcm2835.c index cc1fe0555e196a..0414a1c9600953 100644 --- a/drivers/pinctrl/bcm/pinctrl-bcm2835.c +++ b/drivers/pinctrl/bcm/pinctrl-bcm2835.c @@ -245,7 +245,7 @@ static const char * const irq_type_names[] = { [IRQ_TYPE_LEVEL_LOW] = "level-low", }; -static bool persist_gpio_outputs; +static bool persist_gpio_outputs = true; module_param(persist_gpio_outputs, bool, 0444); MODULE_PARM_DESC(persist_gpio_outputs, "Enable GPIO_OUT persistence when pin is freed"); @@ -425,15 +425,32 @@ static void bcm2835_gpio_irq_handle_bank(struct bcm2835_pinctrl *pc, unsigned long events; unsigned offset; unsigned gpio; + u32 levs, levs2; events = bcm2835_gpio_rd(pc, GPEDS0 + bank * 4); + levs = bcm2835_gpio_rd(pc, GPLEV0 + bank * 4); events &= mask; events &= pc->enabled_irq_map[bank]; + bcm2835_gpio_wr(pc, GPEDS0 + bank * 4, events); + +retry: for_each_set_bit(offset, &events, 32) { gpio = (32 * bank) + offset; generic_handle_domain_irq(pc->gpio_chip.irq.domain, gpio); } + events = bcm2835_gpio_rd(pc, GPEDS0 + bank * 4); + levs2 = bcm2835_gpio_rd(pc, GPLEV0 + bank * 4); + + events |= levs2 & ~levs & bcm2835_gpio_rd(pc, GPREN0 + bank * 4); + events |= ~levs2 & levs & bcm2835_gpio_rd(pc, GPFEN0 + bank * 4); + events &= mask; + events &= pc->enabled_irq_map[bank]; + if (events) { + bcm2835_gpio_wr(pc, GPEDS0 + bank * 4, events); + levs = levs2; + goto retry; + } } static void bcm2835_gpio_irq_handler(struct irq_desc *desc) @@ -673,11 +690,7 @@ static int bcm2835_gpio_irq_set_type(struct irq_data *data, unsigned int type) static void bcm2835_gpio_irq_ack(struct irq_data *data) { - struct gpio_chip *chip = irq_data_get_irq_chip_data(data); - struct bcm2835_pinctrl *pc = gpiochip_get_data(chip); - unsigned gpio = irqd_to_hwirq(data); - - bcm2835_gpio_set_bit(pc, GPEDS0, gpio); + /* Nothing to do - the main interrupt handler includes the ACK */ } static int bcm2835_gpio_irq_set_wake(struct irq_data *data, unsigned int on) @@ -1423,7 +1436,7 @@ static int bcm2835_pinctrl_probe(struct platform_device *pdev) girq->default_type = IRQ_TYPE_NONE; girq->handler = handle_level_irq; - err = gpiochip_add_data(&pc->gpio_chip, pc); + err = devm_gpiochip_add_data(dev, &pc->gpio_chip, pc); if (err) { dev_err(dev, "could not add GPIO chip\n"); goto out_remove; diff --git a/drivers/pinctrl/pinctrl-rp1.c b/drivers/pinctrl/pinctrl-rp1.c new file mode 100644 index 00000000000000..c3e9b83865e54f --- /dev/null +++ b/drivers/pinctrl/pinctrl-rp1.c @@ -0,0 +1,1695 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for Raspberry Pi RP1 GPIO unit (pinctrl + GPIO) + * + * Copyright (C) 2023 Raspberry Pi Ltd. + * + * This driver is inspired by: + * pinctrl-bcm2835.c, please see original file for copyright information + */ + +#include <linux/bitmap.h> +#include <linux/bitops.h> +#include <linux/bug.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/gpio/driver.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/irqdesc.h> +#include <linux/init.h> +#include <linux/of_address.h> +#include <linux/of.h> +#include <linux/of_irq.h> +#include <linux/pinctrl/consumer.h> +#include <linux/pinctrl/machine.h> +#include <linux/pinctrl/pinconf.h> +#include <linux/pinctrl/pinctrl.h> +#include <linux/pinctrl/pinmux.h> +#include <linux/pinctrl/pinconf-generic.h> +#include <linux/platform_device.h> +#include <linux/seq_file.h> +#include <linux/spinlock.h> +#include <linux/types.h> +#include "core.h" +#include "pinconf.h" +#include "pinctrl-utils.h" + +#define MODULE_NAME "pinctrl-rp1" +#define RP1_NUM_GPIOS 54 +#define RP1_NUM_BANKS 3 + +#define RP1_RW_OFFSET 0x0000 +#define RP1_XOR_OFFSET 0x1000 +#define RP1_SET_OFFSET 0x2000 +#define RP1_CLR_OFFSET 0x3000 + +#define RP1_GPIO_STATUS 0x0000 +#define RP1_GPIO_CTRL 0x0004 + +#define RP1_GPIO_PCIE_INTE 0x011c +#define RP1_GPIO_PCIE_INTS 0x0124 + +#define RP1_GPIO_EVENTS_SHIFT_RAW 20 +#define RP1_GPIO_STATUS_FALLING BIT(20) +#define RP1_GPIO_STATUS_RISING BIT(21) +#define RP1_GPIO_STATUS_LOW BIT(22) +#define RP1_GPIO_STATUS_HIGH BIT(23) + +#define RP1_GPIO_EVENTS_SHIFT_FILTERED 24 +#define RP1_GPIO_STATUS_F_FALLING BIT(24) +#define RP1_GPIO_STATUS_F_RISING BIT(25) +#define RP1_GPIO_STATUS_F_LOW BIT(26) +#define RP1_GPIO_STATUS_F_HIGH BIT(27) + +#define RP1_GPIO_CTRL_FUNCSEL_LSB 0 +#define RP1_GPIO_CTRL_FUNCSEL_MASK 0x0000001f +#define RP1_GPIO_CTRL_OUTOVER_LSB 12 +#define RP1_GPIO_CTRL_OUTOVER_MASK 0x00003000 +#define RP1_GPIO_CTRL_OEOVER_LSB 14 +#define RP1_GPIO_CTRL_OEOVER_MASK 0x0000c000 +#define RP1_GPIO_CTRL_INOVER_LSB 16 +#define RP1_GPIO_CTRL_INOVER_MASK 0x00030000 +#define RP1_GPIO_CTRL_IRQEN_FALLING BIT(20) +#define RP1_GPIO_CTRL_IRQEN_RISING BIT(21) +#define RP1_GPIO_CTRL_IRQEN_LOW BIT(22) +#define RP1_GPIO_CTRL_IRQEN_HIGH BIT(23) +#define RP1_GPIO_CTRL_IRQEN_F_FALLING BIT(24) +#define RP1_GPIO_CTRL_IRQEN_F_RISING BIT(25) +#define RP1_GPIO_CTRL_IRQEN_F_LOW BIT(26) +#define RP1_GPIO_CTRL_IRQEN_F_HIGH BIT(27) +#define RP1_GPIO_CTRL_IRQRESET BIT(28) +#define RP1_GPIO_CTRL_IRQOVER_LSB 30 +#define RP1_GPIO_CTRL_IRQOVER_MASK 0xc0000000 + +#define RP1_INT_EDGE_FALLING BIT(0) +#define RP1_INT_EDGE_RISING BIT(1) +#define RP1_INT_LEVEL_LOW BIT(2) +#define RP1_INT_LEVEL_HIGH BIT(3) +#define RP1_INT_MASK 0xf + +#define RP1_INT_EDGE_BOTH (RP1_INT_EDGE_FALLING | \ + RP1_INT_EDGE_RISING) +#define RP1_PUD_OFF 0 +#define RP1_PUD_DOWN 1 +#define RP1_PUD_UP 2 + +#define RP1_FSEL_COUNT 9 + +#define RP1_FSEL_ALT0 0x00 +#define RP1_FSEL_GPIO 0x05 +#define RP1_FSEL_NONE 0x09 +#define RP1_FSEL_NONE_HW 0x1f + +#define RP1_DIR_OUTPUT 0 +#define RP1_DIR_INPUT 1 + +#define RP1_OUTOVER_PERI 0 +#define RP1_OUTOVER_INVPERI 1 +#define RP1_OUTOVER_LOW 2 +#define RP1_OUTOVER_HIGH 3 + +#define RP1_OEOVER_PERI 0 +#define RP1_OEOVER_INVPERI 1 +#define RP1_OEOVER_DISABLE 2 +#define RP1_OEOVER_ENABLE 3 + +#define RP1_INOVER_PERI 0 +#define RP1_INOVER_INVPERI 1 +#define RP1_INOVER_LOW 2 +#define RP1_INOVER_HIGH 3 + +#define RP1_RIO_OUT 0x00 +#define RP1_RIO_OE 0x04 +#define RP1_RIO_IN 0x08 + +#define RP1_PAD_SLEWFAST_MASK 0x00000001 +#define RP1_PAD_SLEWFAST_LSB 0 +#define RP1_PAD_SCHMITT_MASK 0x00000002 +#define RP1_PAD_SCHMITT_LSB 1 +#define RP1_PAD_PULL_MASK 0x0000000c +#define RP1_PAD_PULL_LSB 2 +#define RP1_PAD_DRIVE_MASK 0x00000030 +#define RP1_PAD_DRIVE_LSB 4 +#define RP1_PAD_IN_ENABLE_MASK 0x00000040 +#define RP1_PAD_IN_ENABLE_LSB 6 +#define RP1_PAD_OUT_DISABLE_MASK 0x00000080 +#define RP1_PAD_OUT_DISABLE_LSB 7 + +#define RP1_PAD_DRIVE_2MA 0x00000000 +#define RP1_PAD_DRIVE_4MA 0x00000010 +#define RP1_PAD_DRIVE_8MA 0x00000020 +#define RP1_PAD_DRIVE_12MA 0x00000030 + +#define FLD_GET(r, f) (((r) & (f ## _MASK)) >> (f ## _LSB)) +#define FLD_SET(r, f, v) r = (((r) & ~(f ## _MASK)) | ((v) << (f ## _LSB))) + +#define FUNC(f) \ + [func_##f] = #f +#define RP1_MAX_FSEL 8 +#define PIN(i, f0, f1, f2, f3, f4, f5, f6, f7, f8) \ + [i] = { \ + .funcs = { \ + func_##f0, \ + func_##f1, \ + func_##f2, \ + func_##f3, \ + func_##f4, \ + func_##f5, \ + func_##f6, \ + func_##f7, \ + func_##f8, \ + }, \ + } + +#define LEGACY_MAP(n, f0, f1, f2, f3, f4, f5) \ + [n] = { \ + func_gpio, \ + func_gpio, \ + func_##f5, \ + func_##f4, \ + func_##f0, \ + func_##f1, \ + func_##f2, \ + func_##f3, \ + } + +struct rp1_iobank_desc { + int min_gpio; + int num_gpios; + int gpio_offset; + int inte_offset; + int ints_offset; + int rio_offset; + int pads_offset; +}; + +struct rp1_pin_info { + u8 num; + u8 bank; + u8 offset; + u8 fsel; + u8 irq_type; + + void __iomem *gpio; + void __iomem *rio; + void __iomem *inte; + void __iomem *ints; + void __iomem *pad; + void __iomem *dummy; +}; + +enum funcs { + func_alt0, + func_alt1, + func_alt2, + func_alt3, + func_alt4, + func_gpio, + func_alt6, + func_alt7, + func_alt8, + func_none, + func_aaud, + func_dcd0, + func_dpi, + func_dsi0_te_ext, + func_dsi1_te_ext, + func_dsr0, + func_dtr0, + func_gpclk0, + func_gpclk1, + func_gpclk2, + func_gpclk3, + func_gpclk4, + func_gpclk5, + func_i2c0, + func_i2c1, + func_i2c2, + func_i2c3, + func_i2c4, + func_i2c5, + func_i2c6, + func_i2s0, + func_i2s1, + func_i2s2, + func_ir, + func_mic, + func_pcie_clkreq_n, + func_pio, + func_proc_rio, + func_pwm0, + func_pwm1, + func_ri0, + func_sd0, + func_sd1, + func_spi0, + func_spi1, + func_spi2, + func_spi3, + func_spi4, + func_spi5, + func_spi6, + func_spi7, + func_spi8, + func_uart0, + func_uart1, + func_uart2, + func_uart3, + func_uart4, + func_uart5, + func_vbus0, + func_vbus1, + func_vbus2, + func_vbus3, + func__, + func_count = func__, + func_invalid = func__, +}; + +struct rp1_pin_funcs { + u8 funcs[RP1_FSEL_COUNT]; +}; + +struct rp1_pinctrl { + struct device *dev; + void __iomem *gpio_base; + void __iomem *rio_base; + void __iomem *pads_base; + void __iomem *dummy_base; + int irq[RP1_NUM_BANKS]; + struct rp1_pin_info pins[RP1_NUM_GPIOS]; + + struct pinctrl_dev *pctl_dev; + struct gpio_chip gpio_chip; + struct pinctrl_gpio_range gpio_range; + + raw_spinlock_t irq_lock[RP1_NUM_BANKS]; +}; + +const struct rp1_iobank_desc rp1_iobanks[RP1_NUM_BANKS] = { + /* gpio inte ints rio pads */ + { 0, 28, 0x0000, 0x011c, 0x0124, 0x0000, 0x0004 }, + { 28, 6, 0x4000, 0x411c, 0x4124, 0x4000, 0x4004 }, + { 34, 20, 0x8000, 0x811c, 0x8124, 0x8000, 0x8004 }, +}; + +/* pins are just named GPIO0..GPIO53 */ +#define RP1_GPIO_PIN(a) PINCTRL_PIN(a, "gpio" #a) +static struct pinctrl_pin_desc rp1_gpio_pins[] = { + RP1_GPIO_PIN(0), + RP1_GPIO_PIN(1), + RP1_GPIO_PIN(2), + RP1_GPIO_PIN(3), + RP1_GPIO_PIN(4), + RP1_GPIO_PIN(5), + RP1_GPIO_PIN(6), + RP1_GPIO_PIN(7), + RP1_GPIO_PIN(8), + RP1_GPIO_PIN(9), + RP1_GPIO_PIN(10), + RP1_GPIO_PIN(11), + RP1_GPIO_PIN(12), + RP1_GPIO_PIN(13), + RP1_GPIO_PIN(14), + RP1_GPIO_PIN(15), + RP1_GPIO_PIN(16), + RP1_GPIO_PIN(17), + RP1_GPIO_PIN(18), + RP1_GPIO_PIN(19), + RP1_GPIO_PIN(20), + RP1_GPIO_PIN(21), + RP1_GPIO_PIN(22), + RP1_GPIO_PIN(23), + RP1_GPIO_PIN(24), + RP1_GPIO_PIN(25), + RP1_GPIO_PIN(26), + RP1_GPIO_PIN(27), + RP1_GPIO_PIN(28), + RP1_GPIO_PIN(29), + RP1_GPIO_PIN(30), + RP1_GPIO_PIN(31), + RP1_GPIO_PIN(32), + RP1_GPIO_PIN(33), + RP1_GPIO_PIN(34), + RP1_GPIO_PIN(35), + RP1_GPIO_PIN(36), + RP1_GPIO_PIN(37), + RP1_GPIO_PIN(38), + RP1_GPIO_PIN(39), + RP1_GPIO_PIN(40), + RP1_GPIO_PIN(41), + RP1_GPIO_PIN(42), + RP1_GPIO_PIN(43), + RP1_GPIO_PIN(44), + RP1_GPIO_PIN(45), + RP1_GPIO_PIN(46), + RP1_GPIO_PIN(47), + RP1_GPIO_PIN(48), + RP1_GPIO_PIN(49), + RP1_GPIO_PIN(50), + RP1_GPIO_PIN(51), + RP1_GPIO_PIN(52), + RP1_GPIO_PIN(53), +}; + +/* one pin per group */ +static const char * const rp1_gpio_groups[] = { + "gpio0", + "gpio1", + "gpio2", + "gpio3", + "gpio4", + "gpio5", + "gpio6", + "gpio7", + "gpio8", + "gpio9", + "gpio10", + "gpio11", + "gpio12", + "gpio13", + "gpio14", + "gpio15", + "gpio16", + "gpio17", + "gpio18", + "gpio19", + "gpio20", + "gpio21", + "gpio22", + "gpio23", + "gpio24", + "gpio25", + "gpio26", + "gpio27", + "gpio28", + "gpio29", + "gpio30", + "gpio31", + "gpio32", + "gpio33", + "gpio34", + "gpio35", + "gpio36", + "gpio37", + "gpio38", + "gpio39", + "gpio40", + "gpio41", + "gpio42", + "gpio43", + "gpio44", + "gpio45", + "gpio46", + "gpio47", + "gpio48", + "gpio49", + "gpio50", + "gpio51", + "gpio52", + "gpio53", +}; + +static const char * const rp1_func_names[] = { + FUNC(alt0), + FUNC(alt1), + FUNC(alt2), + FUNC(alt3), + FUNC(alt4), + FUNC(gpio), + FUNC(alt6), + FUNC(alt7), + FUNC(alt8), + FUNC(none), + FUNC(aaud), + FUNC(dcd0), + FUNC(dpi), + FUNC(dsi0_te_ext), + FUNC(dsi1_te_ext), + FUNC(dsr0), + FUNC(dtr0), + FUNC(gpclk0), + FUNC(gpclk1), + FUNC(gpclk2), + FUNC(gpclk3), + FUNC(gpclk4), + FUNC(gpclk5), + FUNC(i2c0), + FUNC(i2c1), + FUNC(i2c2), + FUNC(i2c3), + FUNC(i2c4), + FUNC(i2c5), + FUNC(i2c6), + FUNC(i2s0), + FUNC(i2s1), + FUNC(i2s2), + FUNC(ir), + FUNC(mic), + FUNC(pcie_clkreq_n), + FUNC(pio), + FUNC(proc_rio), + FUNC(pwm0), + FUNC(pwm1), + FUNC(ri0), + FUNC(sd0), + FUNC(sd1), + FUNC(spi0), + FUNC(spi1), + FUNC(spi2), + FUNC(spi3), + FUNC(spi4), + FUNC(spi5), + FUNC(spi6), + FUNC(spi7), + FUNC(spi8), + FUNC(uart0), + FUNC(uart1), + FUNC(uart2), + FUNC(uart3), + FUNC(uart4), + FUNC(uart5), + FUNC(vbus0), + FUNC(vbus1), + FUNC(vbus2), + FUNC(vbus3), + [func_invalid] = "?" +}; + +static const struct rp1_pin_funcs rp1_gpio_pin_funcs[] = { + PIN(0, spi0, dpi, uart1, i2c0, _, gpio, proc_rio, pio, spi2), + PIN(1, spi0, dpi, uart1, i2c0, _, gpio, proc_rio, pio, spi2), + PIN(2, spi0, dpi, uart1, i2c1, ir, gpio, proc_rio, pio, spi2), + PIN(3, spi0, dpi, uart1, i2c1, ir, gpio, proc_rio, pio, spi2), + PIN(4, gpclk0, dpi, uart2, i2c2, ri0, gpio, proc_rio, pio, spi3), + PIN(5, gpclk1, dpi, uart2, i2c2, dtr0, gpio, proc_rio, pio, spi3), + PIN(6, gpclk2, dpi, uart2, i2c3, dcd0, gpio, proc_rio, pio, spi3), + PIN(7, spi0, dpi, uart2, i2c3, dsr0, gpio, proc_rio, pio, spi3), + PIN(8, spi0, dpi, uart3, i2c0, _, gpio, proc_rio, pio, spi4), + PIN(9, spi0, dpi, uart3, i2c0, _, gpio, proc_rio, pio, spi4), + PIN(10, spi0, dpi, uart3, i2c1, _, gpio, proc_rio, pio, spi4), + PIN(11, spi0, dpi, uart3, i2c1, _, gpio, proc_rio, pio, spi4), + PIN(12, pwm0, dpi, uart4, i2c2, aaud, gpio, proc_rio, pio, spi5), + PIN(13, pwm0, dpi, uart4, i2c2, aaud, gpio, proc_rio, pio, spi5), + PIN(14, pwm0, dpi, uart4, i2c3, uart0, gpio, proc_rio, pio, spi5), + PIN(15, pwm0, dpi, uart4, i2c3, uart0, gpio, proc_rio, pio, spi5), + PIN(16, spi1, dpi, dsi0_te_ext, _, uart0, gpio, proc_rio, pio, _), + PIN(17, spi1, dpi, dsi1_te_ext, _, uart0, gpio, proc_rio, pio, _), + PIN(18, spi1, dpi, i2s0, pwm0, i2s1, gpio, proc_rio, pio, gpclk1), + PIN(19, spi1, dpi, i2s0, pwm0, i2s1, gpio, proc_rio, pio, _), + PIN(20, spi1, dpi, i2s0, gpclk0, i2s1, gpio, proc_rio, pio, _), + PIN(21, spi1, dpi, i2s0, gpclk1, i2s1, gpio, proc_rio, pio, _), + PIN(22, sd0, dpi, i2s0, i2c3, i2s1, gpio, proc_rio, pio, _), + PIN(23, sd0, dpi, i2s0, i2c3, i2s1, gpio, proc_rio, pio, _), + PIN(24, sd0, dpi, i2s0, _, i2s1, gpio, proc_rio, pio, spi2), + PIN(25, sd0, dpi, i2s0, mic, i2s1, gpio, proc_rio, pio, spi3), + PIN(26, sd0, dpi, i2s0, mic, i2s1, gpio, proc_rio, pio, spi5), + PIN(27, sd0, dpi, i2s0, mic, i2s1, gpio, proc_rio, pio, spi1), + PIN(28, sd1, i2c4, i2s2, spi6, vbus0, gpio, proc_rio, _, _), + PIN(29, sd1, i2c4, i2s2, spi6, vbus0, gpio, proc_rio, _, _), + PIN(30, sd1, i2c5, i2s2, spi6, uart5, gpio, proc_rio, _, _), + PIN(31, sd1, i2c5, i2s2, spi6, uart5, gpio, proc_rio, _, _), + PIN(32, sd1, gpclk3, i2s2, spi6, uart5, gpio, proc_rio, _, _), + PIN(33, sd1, gpclk4, i2s2, spi6, uart5, gpio, proc_rio, _, _), + PIN(34, pwm1, gpclk3, vbus0, i2c4, mic, gpio, proc_rio, _, _), + PIN(35, spi8, pwm1, vbus0, i2c4, mic, gpio, proc_rio, _, _), + PIN(36, spi8, uart5, pcie_clkreq_n, i2c5, mic, gpio, proc_rio, _, _), + PIN(37, spi8, uart5, mic, i2c5, pcie_clkreq_n, gpio, proc_rio, _, _), + PIN(38, spi8, uart5, mic, i2c6, aaud, gpio, proc_rio, dsi0_te_ext, _), + PIN(39, spi8, uart5, mic, i2c6, aaud, gpio, proc_rio, dsi1_te_ext, _), + PIN(40, pwm1, uart5, i2c4, spi6, aaud, gpio, proc_rio, _, _), + PIN(41, pwm1, uart5, i2c4, spi6, aaud, gpio, proc_rio, _, _), + PIN(42, gpclk5, uart5, vbus1, spi6, i2s2, gpio, proc_rio, _, _), + PIN(43, gpclk4, uart5, vbus1, spi6, i2s2, gpio, proc_rio, _, _), + PIN(44, gpclk5, i2c5, pwm1, spi6, i2s2, gpio, proc_rio, _, _), + PIN(45, pwm1, i2c5, spi7, spi6, i2s2, gpio, proc_rio, _, _), + PIN(46, gpclk3, i2c4, spi7, mic, i2s2, gpio, proc_rio, dsi0_te_ext, _), + PIN(47, gpclk5, i2c4, spi7, mic, i2s2, gpio, proc_rio, dsi1_te_ext, _), + PIN(48, pwm1, pcie_clkreq_n, spi7, mic, uart5, gpio, proc_rio, _, _), + PIN(49, spi8, spi7, i2c5, aaud, uart5, gpio, proc_rio, _, _), + PIN(50, spi8, spi7, i2c5, aaud, vbus2, gpio, proc_rio, _, _), + PIN(51, spi8, spi7, i2c6, aaud, vbus2, gpio, proc_rio, _, _), + PIN(52, spi8, _, i2c6, aaud, vbus3, gpio, proc_rio, _, _), + PIN(53, spi8, spi7, _, pcie_clkreq_n, vbus3, gpio, proc_rio, _, _), +}; + +static const u8 legacy_fsel_map[][8] = { + LEGACY_MAP(0, i2c0, _, dpi, spi2, uart1, _), + LEGACY_MAP(1, i2c0, _, dpi, spi2, uart1, _), + LEGACY_MAP(2, i2c1, _, dpi, spi2, uart1, _), + LEGACY_MAP(3, i2c1, _, dpi, spi2, uart1, _), + LEGACY_MAP(4, gpclk0, _, dpi, spi3, uart2, i2c2), + LEGACY_MAP(5, gpclk1, _, dpi, spi3, uart2, i2c2), + LEGACY_MAP(6, gpclk2, _, dpi, spi3, uart2, i2c3), + LEGACY_MAP(7, spi0, _, dpi, spi3, uart2, i2c3), + LEGACY_MAP(8, spi0, _, dpi, _, uart3, i2c0), + LEGACY_MAP(9, spi0, _, dpi, _, uart3, i2c0), + LEGACY_MAP(10, spi0, _, dpi, _, uart3, i2c1), + LEGACY_MAP(11, spi0, _, dpi, _, uart3, i2c1), + LEGACY_MAP(12, pwm0, _, dpi, spi5, uart4, i2c2), + LEGACY_MAP(13, pwm0, _, dpi, spi5, uart4, i2c2), + LEGACY_MAP(14, uart0, _, dpi, spi5, uart4, _), + LEGACY_MAP(15, uart0, _, dpi, spi5, uart4, _), + LEGACY_MAP(16, _, _, dpi, uart0, spi1, _), + LEGACY_MAP(17, _, _, dpi, uart0, spi1, _), + LEGACY_MAP(18, i2s0, _, dpi, _, spi1, pwm0), + LEGACY_MAP(19, i2s0, _, dpi, _, spi1, pwm0), + LEGACY_MAP(20, i2s0, _, dpi, _, spi1, gpclk0), + LEGACY_MAP(21, i2s0, _, dpi, _, spi1, gpclk1), + LEGACY_MAP(22, sd0, _, dpi, _, _, i2c3), + LEGACY_MAP(23, sd0, _, dpi, _, _, i2c3), + LEGACY_MAP(24, sd0, _, dpi, _, _, spi2), + LEGACY_MAP(25, sd0, _, dpi, _, _, spi3), + LEGACY_MAP(26, sd0, _, dpi, _, _, spi5), + LEGACY_MAP(27, sd0, _, dpi, _, _, _), +}; + +static const char * const irq_type_names[] = { + [IRQ_TYPE_NONE] = "none", + [IRQ_TYPE_EDGE_RISING] = "edge-rising", + [IRQ_TYPE_EDGE_FALLING] = "edge-falling", + [IRQ_TYPE_EDGE_BOTH] = "edge-both", + [IRQ_TYPE_LEVEL_HIGH] = "level-high", + [IRQ_TYPE_LEVEL_LOW] = "level-low", +}; + +static bool persist_gpio_outputs = true; +module_param(persist_gpio_outputs, bool, 0644); +MODULE_PARM_DESC(persist_gpio_outputs, "Enable GPIO_OUT persistence when pin is freed"); + +static bool pace_pin_updates = true; +module_param(pace_pin_updates, bool, 0644); +MODULE_PARM_DESC(pace_pin_updates, "Update pin states with guaranteed monotonicity if PCIe ASPM is enabled"); + +static inline void rp1_pin_writel(u32 val, void __iomem *dummy, void __iomem *reg) +{ + unsigned long flags; + + local_irq_save(flags); + /* + * Issuing 6 pipelined writes to the RC's Slot Control register will stall the + * peripheral bus inside 2712 if the link is in L1. This acts as a lightweight + * "fence" operation preventing back-to-back writes arriving at RP1 on a wake. + */ + if (dummy) { + writel_relaxed(0, dummy); + writel_relaxed(0, dummy); + writel_relaxed(0, dummy); + writel_relaxed(0, dummy); + writel_relaxed(0, dummy); + writel_relaxed(0, dummy); + } + writel_relaxed(val, reg); + local_irq_restore(flags); +} + +static inline u32 rp1_pin_readl(const void __iomem *ioaddr) +{ + /* + * Prior posted writes may not yet have been emitted by the CPU - do a store-flush + * before reading GPIO state, as this will serialise writes versus the next issued read. + */ + __dma_wmb(); + return readl(ioaddr); +} + +static int rp1_pinconf_set(struct pinctrl_dev *pctldev, + unsigned int offset, unsigned long *configs, + unsigned int num_configs); + +static struct rp1_pin_info *rp1_get_pin(struct gpio_chip *chip, + unsigned int offset) +{ + struct rp1_pinctrl *pc = gpiochip_get_data(chip); + + if (pc && offset < RP1_NUM_GPIOS) + return &pc->pins[offset]; + return NULL; +} + +static struct rp1_pin_info *rp1_get_pin_pctl(struct pinctrl_dev *pctldev, + unsigned int offset) +{ + struct rp1_pinctrl *pc = pinctrl_dev_get_drvdata(pctldev); + + if (pc && offset < RP1_NUM_GPIOS) + return &pc->pins[offset]; + return NULL; +} + +static void rp1_pad_update(struct rp1_pin_info *pin, u32 clr, u32 set) +{ + u32 padctrl = rp1_pin_readl(pin->pad); + + padctrl &= ~clr; + padctrl |= set; + + rp1_pin_writel(padctrl, pin->dummy, pin->pad); +} + +static void rp1_input_enable(struct rp1_pin_info *pin, int value) +{ + rp1_pad_update(pin, RP1_PAD_IN_ENABLE_MASK, + value ? RP1_PAD_IN_ENABLE_MASK : 0); +} + +static void rp1_output_enable(struct rp1_pin_info *pin, int value) +{ + rp1_pad_update(pin, RP1_PAD_OUT_DISABLE_MASK, + value ? 0 : RP1_PAD_OUT_DISABLE_MASK); +} + +static u32 rp1_get_fsel(struct rp1_pin_info *pin) +{ + u32 ctrl = rp1_pin_readl(pin->gpio + RP1_GPIO_CTRL); + u32 oeover = FLD_GET(ctrl, RP1_GPIO_CTRL_OEOVER); + u32 fsel = FLD_GET(ctrl, RP1_GPIO_CTRL_FUNCSEL); + + if (oeover != RP1_OEOVER_PERI || fsel >= RP1_FSEL_COUNT) + fsel = RP1_FSEL_NONE; + + return fsel; +} + +static void rp1_set_fsel(struct rp1_pin_info *pin, u32 fsel) +{ + u32 ctrl = rp1_pin_readl(pin->gpio + RP1_GPIO_CTRL); + + if (fsel >= RP1_FSEL_COUNT) + fsel = RP1_FSEL_NONE_HW; + + rp1_input_enable(pin, 1); + rp1_output_enable(pin, 1); + + if (fsel == RP1_FSEL_NONE) { + FLD_SET(ctrl, RP1_GPIO_CTRL_OEOVER, RP1_OEOVER_DISABLE); + } else { + FLD_SET(ctrl, RP1_GPIO_CTRL_OUTOVER, RP1_OUTOVER_PERI); + FLD_SET(ctrl, RP1_GPIO_CTRL_OEOVER, RP1_OEOVER_PERI); + } + FLD_SET(ctrl, RP1_GPIO_CTRL_FUNCSEL, fsel); + rp1_pin_writel(ctrl, pin->dummy, pin->gpio + RP1_GPIO_CTRL); +} + +static int rp1_get_dir(struct rp1_pin_info *pin) +{ + return !(rp1_pin_readl(pin->rio + RP1_RIO_OE) & (1 << pin->offset)) ? + RP1_DIR_INPUT : RP1_DIR_OUTPUT; +} + +static void rp1_set_dir(struct rp1_pin_info *pin, bool is_input) +{ + int offset = is_input ? RP1_CLR_OFFSET : RP1_SET_OFFSET; + + rp1_pin_writel(1 << pin->offset, pin->dummy, pin->rio + RP1_RIO_OE + offset); +} + +static int rp1_get_value(struct rp1_pin_info *pin) +{ + return !!(rp1_pin_readl(pin->rio + RP1_RIO_IN) & (1 << pin->offset)); +} + +static void rp1_set_value(struct rp1_pin_info *pin, int value) +{ + /* Assume the pin is already an output */ + rp1_pin_writel(1 << pin->offset, pin->dummy, + pin->rio + RP1_RIO_OUT + (value ? RP1_SET_OFFSET : RP1_CLR_OFFSET)); +} + +static int rp1_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + struct rp1_pin_info *pin = rp1_get_pin(chip, offset); + int ret; + + if (!pin) + return -EINVAL; + ret = rp1_get_value(pin); + return ret; +} + +static void rp1_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ + struct rp1_pin_info *pin = rp1_get_pin(chip, offset); + + if (pin) + rp1_set_value(pin, value); +} + +static int rp1_gpio_get_direction(struct gpio_chip *chip, unsigned int offset) +{ + struct rp1_pin_info *pin = rp1_get_pin(chip, offset); + u32 fsel; + + if (!pin) + return -EINVAL; + fsel = rp1_get_fsel(pin); + if (fsel != RP1_FSEL_GPIO) + return -EINVAL; + return (rp1_get_dir(pin) == RP1_DIR_OUTPUT) ? + GPIO_LINE_DIRECTION_OUT : + GPIO_LINE_DIRECTION_IN; +} + +static int rp1_gpio_direction_input(struct gpio_chip *chip, unsigned offset) +{ + struct rp1_pin_info *pin = rp1_get_pin(chip, offset); + + if (!pin) + return -EINVAL; + rp1_set_dir(pin, RP1_DIR_INPUT); + rp1_set_fsel(pin, RP1_FSEL_GPIO); + return 0; +} + +static int rp1_gpio_direction_output(struct gpio_chip *chip, unsigned offset, + int value) +{ + struct rp1_pin_info *pin = rp1_get_pin(chip, offset); + + if (!pin) + return -EINVAL; + rp1_set_value(pin, value); + rp1_set_dir(pin, RP1_DIR_OUTPUT); + rp1_set_fsel(pin, RP1_FSEL_GPIO); + return 0; +} + +static int rp1_gpio_set_config(struct gpio_chip *gc, unsigned offset, + unsigned long config) +{ + struct rp1_pinctrl *pc = gpiochip_get_data(gc); + unsigned long configs[] = { config }; + + return rp1_pinconf_set(pc->pctl_dev, offset, configs, + ARRAY_SIZE(configs)); +} + +static const struct gpio_chip rp1_gpio_chip = { + .label = MODULE_NAME, + .owner = THIS_MODULE, + .request = gpiochip_generic_request, + .free = gpiochip_generic_free, + .direction_input = rp1_gpio_direction_input, + .direction_output = rp1_gpio_direction_output, + .get_direction = rp1_gpio_get_direction, + .get = rp1_gpio_get, + .set = rp1_gpio_set, + .base = -1, + .set_config = rp1_gpio_set_config, + .ngpio = RP1_NUM_GPIOS, + .can_sleep = false, +}; + +static void rp1_gpio_irq_handler(struct irq_desc *desc) +{ + struct gpio_chip *chip = irq_desc_get_handler_data(desc); + struct rp1_pinctrl *pc = gpiochip_get_data(chip); + struct irq_chip *host_chip = irq_desc_get_chip(desc); + const struct rp1_iobank_desc *bank; + int irq = irq_desc_get_irq(desc); + unsigned long ints; + int b; + + if (pc->irq[0] == irq) + bank = &rp1_iobanks[0]; + else if (pc->irq[1] == irq) + bank = &rp1_iobanks[1]; + else + bank = &rp1_iobanks[2]; + + chained_irq_enter(host_chip, desc); + + ints = readl(pc->gpio_base + bank->ints_offset); + for_each_set_bit(b, &ints, 32) { + struct rp1_pin_info *pin = rp1_get_pin(chip, bank->min_gpio + b); + + writel(RP1_GPIO_CTRL_IRQRESET, + pin->gpio + RP1_SET_OFFSET + RP1_GPIO_CTRL); + generic_handle_irq(irq_linear_revmap(pc->gpio_chip.irq.domain, + bank->min_gpio + b)); + } + + chained_irq_exit(host_chip, desc); +} + +static void rp1_gpio_irq_config(struct rp1_pin_info *pin, bool enable) +{ + writel(1 << pin->offset, + pin->inte + (enable ? RP1_SET_OFFSET : RP1_CLR_OFFSET)); + if (!enable) + /* Clear any latched events */ + writel(RP1_GPIO_CTRL_IRQRESET, + pin->gpio + RP1_SET_OFFSET + RP1_GPIO_CTRL); +} + +static void rp1_gpio_irq_enable(struct irq_data *data) +{ + struct gpio_chip *chip = irq_data_get_irq_chip_data(data); + unsigned gpio = irqd_to_hwirq(data); + struct rp1_pin_info *pin = rp1_get_pin(chip, gpio); + + rp1_gpio_irq_config(pin, true); +} + +static void rp1_gpio_irq_disable(struct irq_data *data) +{ + struct gpio_chip *chip = irq_data_get_irq_chip_data(data); + unsigned gpio = irqd_to_hwirq(data); + struct rp1_pin_info *pin = rp1_get_pin(chip, gpio); + + rp1_gpio_irq_config(pin, false); +} + +static int rp1_irq_set_type(struct rp1_pin_info *pin, unsigned int type) +{ + u32 irq_flags; + + switch (type) { + case IRQ_TYPE_NONE: + irq_flags = 0; + break; + case IRQ_TYPE_EDGE_RISING: + irq_flags = RP1_INT_EDGE_RISING; + break; + case IRQ_TYPE_EDGE_FALLING: + irq_flags = RP1_INT_EDGE_FALLING; + break; + case IRQ_TYPE_EDGE_BOTH: + irq_flags = RP1_INT_EDGE_RISING | RP1_INT_EDGE_FALLING; + break; + case IRQ_TYPE_LEVEL_HIGH: + irq_flags = RP1_INT_LEVEL_HIGH; + break; + case IRQ_TYPE_LEVEL_LOW: + irq_flags = RP1_INT_LEVEL_LOW; + break; + + default: + return -EINVAL; + } + + /* Clear the event enables */ + writel(RP1_INT_MASK << RP1_GPIO_EVENTS_SHIFT_RAW, + pin->gpio + RP1_CLR_OFFSET + RP1_GPIO_CTRL); + /* Clear any latched events */ + writel(RP1_GPIO_CTRL_IRQRESET, + pin->gpio + RP1_SET_OFFSET + RP1_GPIO_CTRL); + /* Enable the events that are needed */ + writel(irq_flags << RP1_GPIO_EVENTS_SHIFT_RAW, + pin->gpio + RP1_SET_OFFSET + RP1_GPIO_CTRL); + pin->irq_type = type; + + return 0; +} + +static int rp1_gpio_irq_set_type(struct irq_data *data, unsigned int type) +{ + struct gpio_chip *chip = irq_data_get_irq_chip_data(data); + struct rp1_pinctrl *pc = gpiochip_get_data(chip); + unsigned gpio = irqd_to_hwirq(data); + struct rp1_pin_info *pin = rp1_get_pin(chip, gpio); + int bank = pin->bank; + unsigned long flags; + int ret; + + raw_spin_lock_irqsave(&pc->irq_lock[bank], flags); + + ret = rp1_irq_set_type(pin, type); + if (!ret) { + if (type & IRQ_TYPE_EDGE_BOTH) + irq_set_handler_locked(data, handle_edge_irq); + else + irq_set_handler_locked(data, handle_level_irq); + } + + raw_spin_unlock_irqrestore(&pc->irq_lock[bank], flags); + + return ret; +} + +static void rp1_gpio_irq_ack(struct irq_data *data) +{ + struct gpio_chip *chip = irq_data_get_irq_chip_data(data); + unsigned gpio = irqd_to_hwirq(data); + struct rp1_pin_info *pin = rp1_get_pin(chip, gpio); + + /* Clear any latched events */ + writel(RP1_GPIO_CTRL_IRQRESET, pin->gpio + RP1_SET_OFFSET + RP1_GPIO_CTRL); +} + +static int rp1_gpio_irq_set_affinity(struct irq_data *data, const struct cpumask *dest, bool force) +{ + struct gpio_chip *chip = irq_data_get_irq_chip_data(data); + struct rp1_pinctrl *pc = gpiochip_get_data(chip); + const struct rp1_iobank_desc *bank; + struct irq_data *parent_data = NULL; + int i; + + for (i = 0; i < 3; i++) { + bank = &rp1_iobanks[i]; + if (data->hwirq >= bank->min_gpio && + data->hwirq < bank->min_gpio + bank->num_gpios) { + parent_data = irq_get_irq_data(pc->irq[i]); + break; + } + } + + if (parent_data && parent_data->chip->irq_set_affinity) + return parent_data->chip->irq_set_affinity(parent_data, dest, force); + + return -EINVAL; +} + +static struct irq_chip rp1_gpio_irq_chip = { + .name = MODULE_NAME, + .irq_enable = rp1_gpio_irq_enable, + .irq_disable = rp1_gpio_irq_disable, + .irq_set_type = rp1_gpio_irq_set_type, + .irq_ack = rp1_gpio_irq_ack, + .irq_mask = rp1_gpio_irq_disable, + .irq_unmask = rp1_gpio_irq_enable, + .irq_set_affinity = rp1_gpio_irq_set_affinity, + .flags = IRQCHIP_IMMUTABLE, +}; + +static int rp1_pctl_get_groups_count(struct pinctrl_dev *pctldev) +{ + return ARRAY_SIZE(rp1_gpio_groups); +} + +static const char *rp1_pctl_get_group_name(struct pinctrl_dev *pctldev, + unsigned selector) +{ + return rp1_gpio_groups[selector]; +} + +static enum funcs rp1_get_fsel_func(unsigned pin, unsigned fsel) +{ + if (pin < RP1_NUM_GPIOS) { + if (fsel < RP1_FSEL_COUNT) + return rp1_gpio_pin_funcs[pin].funcs[fsel]; + else if (fsel == RP1_FSEL_NONE) + return func_none; + } + return func_invalid; +} + +static int rp1_pctl_get_group_pins(struct pinctrl_dev *pctldev, + unsigned selector, + const unsigned **pins, + unsigned *num_pins) +{ + *pins = &rp1_gpio_pins[selector].number; + *num_pins = 1; + + return 0; +} + +static void rp1_pctl_pin_dbg_show(struct pinctrl_dev *pctldev, + struct seq_file *s, + unsigned offset) +{ + struct rp1_pinctrl *pc = pinctrl_dev_get_drvdata(pctldev); + struct gpio_chip *chip = &pc->gpio_chip; + struct rp1_pin_info *pin = rp1_get_pin_pctl(pctldev, offset); + u32 fsel = rp1_get_fsel(pin); + enum funcs func = rp1_get_fsel_func(offset, fsel); + int value = rp1_get_value(pin); + int irq = irq_find_mapping(chip->irq.domain, offset); + + seq_printf(s, "function %s (%s) in %s; irq %d (%s)", + rp1_func_names[fsel], rp1_func_names[func], + value ? "hi" : "lo", + irq, irq_type_names[pin->irq_type]); +} + +static void rp1_pctl_dt_free_map(struct pinctrl_dev *pctldev, + struct pinctrl_map *maps, unsigned num_maps) +{ + int i; + + for (i = 0; i < num_maps; i++) + if (maps[i].type == PIN_MAP_TYPE_CONFIGS_PIN) + kfree(maps[i].data.configs.configs); + + kfree(maps); +} + +static int rp1_pctl_legacy_map_func(struct rp1_pinctrl *pc, + struct device_node *np, u32 pin, u32 fnum, + struct pinctrl_map *maps, + unsigned int *num_maps) +{ + struct pinctrl_map *map = &maps[*num_maps]; + enum funcs func; + + if (fnum >= ARRAY_SIZE(legacy_fsel_map[0])) { + dev_err(pc->dev, "%pOF: invalid brcm,function %d\n", np, fnum); + return -EINVAL; + } + + if (pin < ARRAY_SIZE(legacy_fsel_map)) { + func = legacy_fsel_map[pin][fnum]; + } else if (fnum < 2) { + func = func_gpio; + } else { + dev_err(pc->dev, "%pOF: invalid brcm,pins value %d\n", + np, pin); + return -EINVAL; + } + + if (func == func_invalid) { + dev_err(pc->dev, "%pOF: brcm,function %d not supported on pin %d\n", + np, fnum, pin); + } + + map->type = PIN_MAP_TYPE_MUX_GROUP; + map->data.mux.group = rp1_gpio_groups[pin]; + map->data.mux.function = rp1_func_names[func]; + (*num_maps)++; + + return 0; +} + +static int rp1_pctl_legacy_map_pull(struct rp1_pinctrl *pc, + struct device_node *np, u32 pin, u32 pull, + struct pinctrl_map *maps, + unsigned int *num_maps) +{ + struct pinctrl_map *map = &maps[*num_maps]; + enum pin_config_param param; + unsigned long *configs; + + switch (pull) { + case RP1_PUD_OFF: + param = PIN_CONFIG_BIAS_DISABLE; + break; + case RP1_PUD_DOWN: + param = PIN_CONFIG_BIAS_PULL_DOWN; + break; + case RP1_PUD_UP: + param = PIN_CONFIG_BIAS_PULL_UP; + break; + default: + dev_err(pc->dev, "%pOF: invalid brcm,pull %d\n", np, pull); + return -EINVAL; + } + + configs = kzalloc(sizeof(*configs), GFP_KERNEL); + if (!configs) + return -ENOMEM; + + configs[0] = pinconf_to_config_packed(param, 0); + map->type = PIN_MAP_TYPE_CONFIGS_PIN; + map->data.configs.group_or_pin = rp1_gpio_pins[pin].name; + map->data.configs.configs = configs; + map->data.configs.num_configs = 1; + (*num_maps)++; + + return 0; +} + +static int rp1_pctl_dt_node_to_map(struct pinctrl_dev *pctldev, + struct device_node *np, + struct pinctrl_map **map, + unsigned int *num_maps) +{ + struct rp1_pinctrl *pc = pinctrl_dev_get_drvdata(pctldev); + struct property *pins, *funcs, *pulls; + int num_pins, num_funcs, num_pulls, maps_per_pin; + struct pinctrl_map *maps; + unsigned long *configs = NULL; + const char *function = NULL; + unsigned int reserved_maps; + int num_configs = 0; + int i, err; + u32 pin, func, pull; + + /* Check for legacy pin declaration */ + pins = of_find_property(np, "brcm,pins", NULL); + + if (!pins) /* Assume generic bindings in this node */ + return pinconf_generic_dt_node_to_map_all(pctldev, np, map, num_maps); + + funcs = of_find_property(np, "brcm,function", NULL); + if (!funcs) + of_property_read_string(np, "function", &function); + + pulls = of_find_property(np, "brcm,pull", NULL); + if (!pulls) + pinconf_generic_parse_dt_config(np, pctldev, &configs, &num_configs); + + if (!function && !funcs && !num_configs && !pulls) { + dev_err(pc->dev, + "%pOF: no function, brcm,function, brcm,pull, etc.\n", + np); + return -EINVAL; + } + + num_pins = pins->length / 4; + num_funcs = funcs ? (funcs->length / 4) : 0; + num_pulls = pulls ? (pulls->length / 4) : 0; + + if (num_funcs > 1 && num_funcs != num_pins) { + dev_err(pc->dev, + "%pOF: brcm,function must have 1 or %d entries\n", + np, num_pins); + return -EINVAL; + } + + if (num_pulls > 1 && num_pulls != num_pins) { + dev_err(pc->dev, + "%pOF: brcm,pull must have 1 or %d entries\n", + np, num_pins); + return -EINVAL; + } + + maps_per_pin = 0; + if (function || num_funcs) + maps_per_pin++; + if (num_configs || num_pulls) + maps_per_pin++; + reserved_maps = num_pins * maps_per_pin; + maps = kcalloc(reserved_maps, sizeof(*maps), GFP_KERNEL); + if (!maps) + return -ENOMEM; + + *num_maps = 0; + + for (i = 0; i < num_pins; i++) { + err = of_property_read_u32_index(np, "brcm,pins", i, &pin); + if (err) + goto out; + if (num_funcs) { + err = of_property_read_u32_index(np, "brcm,function", + (num_funcs > 1) ? i : 0, + &func); + if (err) + goto out; + err = rp1_pctl_legacy_map_func(pc, np, pin, func, + maps, num_maps); + } else if (function) { + err = pinctrl_utils_add_map_mux(pctldev, &maps, + &reserved_maps, num_maps, + rp1_gpio_groups[pin], + function); + } + + if (err) + goto out; + + if (num_pulls) { + err = of_property_read_u32_index(np, "brcm,pull", + (num_pulls > 1) ? i : 0, + &pull); + if (err) + goto out; + err = rp1_pctl_legacy_map_pull(pc, np, pin, pull, + maps, num_maps); + } else if (num_configs) { + err = pinctrl_utils_add_map_configs(pctldev, &maps, + &reserved_maps, num_maps, + rp1_gpio_groups[pin], + configs, num_configs, + PIN_MAP_TYPE_CONFIGS_PIN); + } + + if (err) + goto out; + } + + *map = maps; + + return 0; + +out: + rp1_pctl_dt_free_map(pctldev, maps, reserved_maps); + return err; +} + +static const struct pinctrl_ops rp1_pctl_ops = { + .get_groups_count = rp1_pctl_get_groups_count, + .get_group_name = rp1_pctl_get_group_name, + .get_group_pins = rp1_pctl_get_group_pins, + .pin_dbg_show = rp1_pctl_pin_dbg_show, + .dt_node_to_map = rp1_pctl_dt_node_to_map, + .dt_free_map = rp1_pctl_dt_free_map, +}; + +static int rp1_pmx_free(struct pinctrl_dev *pctldev, unsigned offset) +{ + struct rp1_pin_info *pin = rp1_get_pin_pctl(pctldev, offset); + u32 fsel = rp1_get_fsel(pin); + + /* Return all pins to GPIO_IN, unless persist_gpio_outputs is set */ + if (persist_gpio_outputs && fsel == RP1_FSEL_GPIO) + return 0; + + rp1_set_dir(pin, RP1_DIR_INPUT); + rp1_set_fsel(pin, RP1_FSEL_GPIO); + + return 0; +} + +static int rp1_pmx_get_functions_count(struct pinctrl_dev *pctldev) +{ + return func_count; +} + +static const char *rp1_pmx_get_function_name(struct pinctrl_dev *pctldev, + unsigned selector) +{ + return (selector < func_count) ? rp1_func_names[selector] : NULL; +} + +static int rp1_pmx_get_function_groups(struct pinctrl_dev *pctldev, + unsigned selector, + const char * const **groups, + unsigned * const num_groups) +{ + /* every pin can do every function */ + *groups = rp1_gpio_groups; + *num_groups = ARRAY_SIZE(rp1_gpio_groups); + + return 0; +} + +static int rp1_pmx_set(struct pinctrl_dev *pctldev, unsigned func_selector, + unsigned group_selector) +{ + struct rp1_pin_info *pin = rp1_get_pin_pctl(pctldev, group_selector); + const u8 *pin_funcs; + int fsel; + + /* func_selector is an enum funcs, so needs translation */ + + if (func_selector >= RP1_FSEL_COUNT) { + /* Convert to an fsel number */ + pin_funcs = rp1_gpio_pin_funcs[pin->num].funcs; + for (fsel = 0; fsel < RP1_FSEL_COUNT; fsel++) { + if (pin_funcs[fsel] == func_selector) + break; + } + } else { + fsel = (int)func_selector; + } + + if (fsel >= RP1_FSEL_COUNT && fsel != RP1_FSEL_NONE) + return -EINVAL; + + rp1_set_fsel(pin, fsel); + + return 0; +} + +static void rp1_pmx_gpio_disable_free(struct pinctrl_dev *pctldev, + struct pinctrl_gpio_range *range, + unsigned offset) +{ + (void)rp1_pmx_free(pctldev, offset); +} + +static int rp1_pmx_gpio_set_direction(struct pinctrl_dev *pctldev, + struct pinctrl_gpio_range *range, + unsigned offset, + bool input) +{ + struct rp1_pin_info *pin = rp1_get_pin_pctl(pctldev, offset); + + rp1_set_dir(pin, input); + rp1_set_fsel(pin, RP1_FSEL_GPIO); + + return 0; +} + +static const struct pinmux_ops rp1_pmx_ops = { + .free = rp1_pmx_free, + .get_functions_count = rp1_pmx_get_functions_count, + .get_function_name = rp1_pmx_get_function_name, + .get_function_groups = rp1_pmx_get_function_groups, + .set_mux = rp1_pmx_set, + .gpio_disable_free = rp1_pmx_gpio_disable_free, + .gpio_set_direction = rp1_pmx_gpio_set_direction, +}; + +static void rp1_pull_config_set(struct rp1_pin_info *pin, unsigned int arg) +{ + u32 padctrl = rp1_pin_readl(pin->pad); + + FLD_SET(padctrl, RP1_PAD_PULL, arg & 0x3); + + writel(padctrl, pin->pad); +} + +/* Generic pinconf methods */ + +static int rp1_pinconf_set(struct pinctrl_dev *pctldev, unsigned int offset, + unsigned long *configs, unsigned int num_configs) +{ + struct rp1_pin_info *pin = rp1_get_pin_pctl(pctldev, offset); + u32 param, arg; + int i; + + if (!pin) + return -EINVAL; + + for (i = 0; i < num_configs; i++) { + param = pinconf_to_config_param(configs[i]); + arg = pinconf_to_config_argument(configs[i]); + + switch (param) { + case PIN_CONFIG_BIAS_DISABLE: + rp1_pull_config_set(pin, RP1_PUD_OFF); + break; + + case PIN_CONFIG_BIAS_PULL_DOWN: + rp1_pull_config_set(pin, RP1_PUD_DOWN); + break; + + case PIN_CONFIG_BIAS_PULL_UP: + rp1_pull_config_set(pin, RP1_PUD_UP); + break; + + case PIN_CONFIG_INPUT_ENABLE: + rp1_input_enable(pin, arg); + break; + + case PIN_CONFIG_OUTPUT_ENABLE: + rp1_output_enable(pin, arg); + break; + + case PIN_CONFIG_OUTPUT: + rp1_set_value(pin, arg); + rp1_set_dir(pin, RP1_DIR_OUTPUT); + rp1_set_fsel(pin, RP1_FSEL_GPIO); + break; + + case PIN_CONFIG_INPUT_SCHMITT_ENABLE: + rp1_pad_update(pin, RP1_PAD_SCHMITT_MASK, + arg ? RP1_PAD_SCHMITT_MASK : 0); + break; + + case PIN_CONFIG_SLEW_RATE: + rp1_pad_update(pin, RP1_PAD_SLEWFAST_MASK, + arg ? RP1_PAD_SLEWFAST_MASK : 0); + break; + + case PIN_CONFIG_DRIVE_STRENGTH: + switch (arg) { + case 2: + arg = RP1_PAD_DRIVE_2MA; + break; + case 4: + arg = RP1_PAD_DRIVE_4MA; + break; + case 8: + arg = RP1_PAD_DRIVE_8MA; + break; + case 12: + arg = RP1_PAD_DRIVE_12MA; + break; + default: + return -ENOTSUPP; + } + rp1_pad_update(pin, RP1_PAD_DRIVE_MASK, arg); + break; + + default: + return -ENOTSUPP; + + } /* switch param type */ + } /* for each config */ + + return 0; +} + +static int rp1_pinconf_get(struct pinctrl_dev *pctldev, unsigned offset, + unsigned long *config) +{ + struct rp1_pin_info *pin = rp1_get_pin_pctl(pctldev, offset); + enum pin_config_param param = pinconf_to_config_param(*config); + u32 padctrl; + u32 arg; + + if (!pin) + return -EINVAL; + + padctrl = rp1_pin_readl(pin->pad); + + switch (param) { + case PIN_CONFIG_INPUT_ENABLE: + arg = !!(padctrl & RP1_PAD_IN_ENABLE_MASK); + break; + case PIN_CONFIG_OUTPUT_ENABLE: + arg = !(padctrl & RP1_PAD_OUT_DISABLE_MASK); + break; + case PIN_CONFIG_INPUT_SCHMITT_ENABLE: + arg = !!(padctrl & RP1_PAD_SCHMITT_MASK); + break; + case PIN_CONFIG_SLEW_RATE: + arg = !!(padctrl & RP1_PAD_SLEWFAST_MASK); + break; + case PIN_CONFIG_DRIVE_STRENGTH: + switch (padctrl & RP1_PAD_DRIVE_MASK) { + case RP1_PAD_DRIVE_2MA: + arg = 2; + break; + case RP1_PAD_DRIVE_4MA: + arg = 4; + break; + case RP1_PAD_DRIVE_8MA: + arg = 8; + break; + case RP1_PAD_DRIVE_12MA: + arg = 12; + break; + } + break; + case PIN_CONFIG_BIAS_DISABLE: + arg = ((padctrl & RP1_PAD_PULL_MASK) == (RP1_PUD_OFF << RP1_PAD_PULL_LSB)); + break; + case PIN_CONFIG_BIAS_PULL_DOWN: + arg = ((padctrl & RP1_PAD_PULL_MASK) == (RP1_PUD_DOWN << RP1_PAD_PULL_LSB)); + break; + + case PIN_CONFIG_BIAS_PULL_UP: + arg = ((padctrl & RP1_PAD_PULL_MASK) == (RP1_PUD_UP << RP1_PAD_PULL_LSB)); + break; + default: + return -ENOTSUPP; + } + + *config = pinconf_to_config_packed(param, arg); + + return 0; +} + +static const struct pinconf_ops rp1_pinconf_ops = { + .is_generic = true, + .pin_config_get = rp1_pinconf_get, + .pin_config_set = rp1_pinconf_set, +}; + +static struct pinctrl_desc rp1_pinctrl_desc = { + .name = MODULE_NAME, + .pins = rp1_gpio_pins, + .npins = ARRAY_SIZE(rp1_gpio_pins), + .pctlops = &rp1_pctl_ops, + .pmxops = &rp1_pmx_ops, + .confops = &rp1_pinconf_ops, + .owner = THIS_MODULE, +}; + +static struct pinctrl_gpio_range rp1_pinctrl_gpio_range = { + .name = MODULE_NAME, + .npins = RP1_NUM_GPIOS, +}; + +static const struct of_device_id rp1_pinctrl_match[] = { + { + .compatible = "raspberrypi,rp1-gpio", + .data = &rp1_pinconf_ops, + }, + {} +}; + +static inline void __iomem *devm_auto_iomap(struct platform_device *pdev, + unsigned int index) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + + if (np) + return devm_of_iomap(dev, np, (int)index, NULL); + else + return devm_platform_ioremap_resource(pdev, index); +} + +static int rp1_pinctrl_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct device_node *rp1_node = NULL; + struct rp1_pinctrl *pc; + struct gpio_irq_chip *girq; + int err, i; + + BUILD_BUG_ON(ARRAY_SIZE(rp1_gpio_pins) != RP1_NUM_GPIOS); + BUILD_BUG_ON(ARRAY_SIZE(rp1_gpio_groups) != RP1_NUM_GPIOS); + + pc = devm_kzalloc(dev, sizeof(*pc), GFP_KERNEL); + if (!pc) + return -ENOMEM; + + platform_set_drvdata(pdev, pc); + pc->dev = dev; + + pc->gpio_base = devm_auto_iomap(pdev, 0); + if (IS_ERR(pc->gpio_base)) { + dev_err(dev, "could not get GPIO IO memory\n"); + return PTR_ERR(pc->gpio_base); + } + + pc->rio_base = devm_auto_iomap(pdev, 1); + if (IS_ERR(pc->rio_base)) { + dev_err(dev, "could not get RIO IO memory\n"); + return PTR_ERR(pc->rio_base); + } + + pc->pads_base = devm_auto_iomap(pdev, 2); + if (IS_ERR(pc->pads_base)) { + dev_err(dev, "could not get PADS IO memory\n"); + return PTR_ERR(pc->pads_base); + } + + pc->gpio_chip = rp1_gpio_chip; + pc->gpio_chip.parent = dev; + + /* + * Workaround for the vagaries of PCIe on BCM2712 + * + * If the link to RP1 is in L1, then the BRCMSTB RC will buffer many + * outbound writes - and generate write responses for them, despite the + * fact that the link is not yet active. This has the effect of compressing + * multiple writes to GPIOs together, destroying any pacing that an application + * may require in the 1-10us range. + * + * The RC Slot Control configuration register is special. It emits a + * MsgD for every write to it, will stall further writes until the message + * goes out on the wire. This can be (ab)used to force CPU stalls when the + * link is inactive, at the cost of a small amount of downstream bandwidth + * and some 200ns of added latency for each write. + * + * Several back-to-back configuration writes are necessary to "fill the pipe", + * otherwise the outbound MAC can consume a pending MMIO write and reorder + * it with respect to the config writes - undoing the intent. + * + * of_iomap() is used directly here as the address overlaps with the RC driver's + * usage. + */ + rp1_node = of_find_node_by_name(NULL, "rp1"); + if (!rp1_node) + dev_err(&pdev->dev, "failed to find RP1 DT node\n"); + else if (pace_pin_updates && + of_device_is_compatible(rp1_node->parent, "brcm,bcm2712-pcie")) { + pc->dummy_base = of_iomap(rp1_node->parent, 0); + if (IS_ERR(pc->dummy_base)) { + dev_warn(&pdev->dev, "could not map bcm2712 root complex registers\n"); + pc->dummy_base = NULL; + } + } + + for (i = 0; i < RP1_NUM_BANKS; i++) { + const struct rp1_iobank_desc *bank = &rp1_iobanks[i]; + int j; + + for (j = 0; j < bank->num_gpios; j++) { + struct rp1_pin_info *pin = + &pc->pins[bank->min_gpio + j]; + + pin->num = bank->min_gpio + j; + pin->bank = i; + pin->offset = j; + + pin->gpio = pc->gpio_base + bank->gpio_offset + + j * sizeof(u32) * 2; + pin->inte = pc->gpio_base + bank->inte_offset; + pin->ints = pc->gpio_base + bank->ints_offset; + pin->rio = pc->rio_base + bank->rio_offset; + pin->pad = pc->pads_base + bank->pads_offset + + j * sizeof(u32); + pin->dummy = pc->dummy_base ? pc->dummy_base + 0xc0 : NULL; + } + + raw_spin_lock_init(&pc->irq_lock[i]); + } + + pc->pctl_dev = devm_pinctrl_register(dev, &rp1_pinctrl_desc, pc); + if (IS_ERR(pc->pctl_dev)) { + err = PTR_ERR(pc->pctl_dev); + goto out_iounmap; + } + + girq = &pc->gpio_chip.irq; + girq->chip = &rp1_gpio_irq_chip; + girq->parent_handler = rp1_gpio_irq_handler; + girq->num_parents = RP1_NUM_BANKS; + girq->parents = pc->irq; + + /* + * Use the same handler for all groups: this is necessary + * since we use one gpiochip to cover all lines - the + * irq handler then needs to figure out which group and + * bank that was firing the IRQ and look up the per-group + * and bank data. + */ + for (i = 0; i < RP1_NUM_BANKS; i++) { + pc->irq[i] = irq_of_parse_and_map(np, i); + if (!pc->irq[i]) { + girq->num_parents = i; + break; + } + } + + girq->default_type = IRQ_TYPE_NONE; + girq->handler = handle_level_irq; + + err = devm_gpiochip_add_data(dev, &pc->gpio_chip, pc); + if (err) { + dev_err(dev, "could not add GPIO chip\n"); + goto out_iounmap; + } + + pc->gpio_range = rp1_pinctrl_gpio_range; + pc->gpio_range.base = pc->gpio_chip.base; + pc->gpio_range.gc = &pc->gpio_chip; + pinctrl_add_gpio_range(pc->pctl_dev, &pc->gpio_range); + + return 0; + +out_iounmap: + if (pc->dummy_base) + iounmap(pc->dummy_base); + return err; +} + +static void rp1_pinctrl_remove(struct platform_device *pdev) +{ + struct rp1_pinctrl *pc = platform_get_drvdata(pdev); + + if (pc->dummy_base) + iounmap(pc->dummy_base); +} + +static struct platform_driver rp1_pinctrl_driver = { + .probe = rp1_pinctrl_probe, + .remove_new = rp1_pinctrl_remove, + .driver = { + .name = MODULE_NAME, + .of_match_table = rp1_pinctrl_match, + .suppress_bind_attrs = true, + }, +}; +builtin_platform_driver(rp1_pinctrl_driver); diff --git a/drivers/pmdomain/bcm/bcm2835-power.c b/drivers/pmdomain/bcm/bcm2835-power.c index d2f0233cb6206d..5812f2a355d495 100644 --- a/drivers/pmdomain/bcm/bcm2835-power.c +++ b/drivers/pmdomain/bcm/bcm2835-power.c @@ -79,6 +79,7 @@ #define PM_IMAGE 0x108 #define PM_GRAFX 0x10c #define PM_PROC 0x110 +#define PM_GRAFX_2712 0x304 #define PM_ENAB BIT(12) #define PM_ISPRSTN BIT(8) #define PM_H264RSTN BIT(7) @@ -381,6 +382,9 @@ static int bcm2835_power_pd_power_on(struct generic_pm_domain *domain) return bcm2835_power_power_on(pd, PM_GRAFX); case BCM2835_POWER_DOMAIN_GRAFX_V3D: + if (!power->asb) + return bcm2835_asb_power_on(pd, PM_GRAFX_2712, + 0, 0, PM_V3DRSTN); return bcm2835_asb_power_on(pd, PM_GRAFX, ASB_V3D_M_CTRL, ASB_V3D_S_CTRL, PM_V3DRSTN); @@ -447,6 +451,9 @@ static int bcm2835_power_pd_power_off(struct generic_pm_domain *domain) return bcm2835_power_power_off(pd, PM_GRAFX); case BCM2835_POWER_DOMAIN_GRAFX_V3D: + if (!power->asb) + return bcm2835_asb_power_off(pd, PM_GRAFX_2712, + 0, 0, PM_V3DRSTN); return bcm2835_asb_power_off(pd, PM_GRAFX, ASB_V3D_M_CTRL, ASB_V3D_S_CTRL, PM_V3DRSTN); @@ -642,19 +649,21 @@ static int bcm2835_power_probe(struct platform_device *pdev) power->asb = pm->asb; power->rpivid_asb = pm->rpivid_asb; - id = readl(power->asb + ASB_AXI_BRDG_ID); - if (id != BCM2835_BRDG_ID /* "BRDG" */) { - dev_err(dev, "ASB register ID returned 0x%08x\n", id); - return -ENODEV; - } - - if (power->rpivid_asb) { - id = readl(power->rpivid_asb + ASB_AXI_BRDG_ID); + if (power->asb) { + id = readl(power->asb + ASB_AXI_BRDG_ID); if (id != BCM2835_BRDG_ID /* "BRDG" */) { - dev_err(dev, "RPiVid ASB register ID returned 0x%08x\n", - id); + dev_err(dev, "ASB register ID returned 0x%08x\n", id); return -ENODEV; } + + if (power->rpivid_asb) { + id = readl(power->rpivid_asb + ASB_AXI_BRDG_ID); + if (id != BCM2835_BRDG_ID /* "BRDG" */) { + dev_err(dev, "RPiVid ASB register ID returned 0x%08x\n", + id); + return -ENODEV; + } + } } power->pd_xlate.domains = devm_kcalloc(dev, diff --git a/drivers/power/reset/gpio-poweroff.c b/drivers/power/reset/gpio-poweroff.c index 52cfeee2cb2844..770adf9b2dc917 100644 --- a/drivers/power/reset/gpio-poweroff.c +++ b/drivers/power/reset/gpio-poweroff.c @@ -44,7 +44,7 @@ static int gpio_poweroff_do_poweroff(struct sys_off_data *data) /* give it some time */ mdelay(gpio_poweroff->timeout_ms); - WARN_ON(1); + //WARN_ON(1); return NOTIFY_DONE; } @@ -55,6 +55,7 @@ static int gpio_poweroff_probe(struct platform_device *pdev) bool input = false; enum gpiod_flags flags; int priority = SYS_OFF_PRIO_DEFAULT; + bool export = false; int ret; gpio_poweroff = devm_kzalloc(&pdev->dev, sizeof(*gpio_poweroff), GFP_KERNEL); @@ -86,10 +87,18 @@ static int gpio_poweroff_probe(struct platform_device *pdev) if (IS_ERR(gpio_poweroff->reset_gpio)) return PTR_ERR(gpio_poweroff->reset_gpio); + export = device_property_read_bool(&pdev->dev, "export"); + if (export) { + gpiod_export(gpio_poweroff->reset_gpio, true); + gpiod_export_link(&pdev->dev, "poweroff-gpio", gpio_poweroff->reset_gpio); + } + ret = devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_POWER_OFF, priority, gpio_poweroff_do_poweroff, gpio_poweroff); - if (ret) + if (ret) { + gpiod_unexport(gpio_poweroff->reset_gpio); return dev_err_probe(&pdev->dev, ret, "Cannot register poweroff handler\n"); + } return 0; } diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index bcfa63fb9f1e20..fc2fef63a5f3d6 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -28,6 +28,13 @@ config POWER_SUPPLY_HWMON Say 'Y' here if you want power supplies to have hwmon sysfs interface too. +config RPI_POE_POWER + tristate "Raspberry Pi PoE+ HAT power supply driver" + depends on RASPBERRYPI_FIRMWARE + help + Say Y here to enable support for Raspberry Pi PoE+ (Power over Ethernet + Plus) HAT current measurement. + config APM_POWER tristate "APM emulation for class batteries" depends on APM_EMULATION diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index 8dcb4154531718..be3bc820b1226f 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_POWER_SUPPLY) += power_supply.o obj-$(CONFIG_POWER_SUPPLY_HWMON) += power_supply_hwmon.o obj-$(CONFIG_GENERIC_ADC_BATTERY) += generic-adc-battery.o +obj-$(CONFIG_RPI_POE_POWER) += rpi_poe_power.o obj-$(CONFIG_APM_POWER) += apm_power.o obj-$(CONFIG_AXP20X_POWER) += axp20x_usb_power.o obj-$(CONFIG_IP5XXX_POWER) += ip5xxx_power.o diff --git a/drivers/power/supply/rpi_poe_power.c b/drivers/power/supply/rpi_poe_power.c new file mode 100644 index 00000000000000..e96f98c39f0e2e --- /dev/null +++ b/drivers/power/supply/rpi_poe_power.c @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * rpi-poe-power.c - Raspberry Pi PoE+ HAT power supply driver. + * + * Copyright (C) 2019 Raspberry Pi (Trading) Ltd. + * Based on axp20x_ac_power.c by Quentin Schulz <quentin.schulz@free-electrons.com> + * + * Author: Serge Schneider <serge@raspberrypi.org> + */ + +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/regmap.h> +#include <soc/bcm2835/raspberrypi-firmware.h> + +#define RPI_POE_FW_BASE_REG 0x2 + +#define RPI_POE_ADC_REG 0x0 +#define RPI_POE_FLAG_REG 0x2 + +#define RPI_POE_FLAG_AT BIT(0) +#define RPI_POE_FLAG_OC BIT(1) + +#define RPI_POE_CURRENT_AF_MAX (2500 * 1000) +#define RPI_POE_CURRENT_AT_MAX (5000 * 1000) + +#define DRVNAME "rpi-poe-power-supply" + +struct rpi_poe_power_supply_ctx { + struct rpi_firmware *fw; + + struct regmap *regmap; + u32 offset; + + struct power_supply *supply; +}; + +struct fw_tag_data_s { + u32 reg; + u32 val; + u32 ret; +}; + +static int write_reg(struct rpi_poe_power_supply_ctx *ctx, u32 reg, u32 *val) +{ + struct fw_tag_data_s fw_tag_data = { + .reg = reg + RPI_POE_FW_BASE_REG, + .val = *val + }; + int ret; + + if (ctx->fw) { + ret = rpi_firmware_property(ctx->fw, RPI_FIRMWARE_SET_POE_HAT_VAL, + &fw_tag_data, sizeof(fw_tag_data)); + if (!ret && fw_tag_data.ret) + ret = -EIO; + } else { + ret = regmap_write(ctx->regmap, ctx->offset + reg, *val); + } + + return ret; +} + +static int read_reg(struct rpi_poe_power_supply_ctx *ctx, u32 reg, u32 *val) +{ + struct fw_tag_data_s fw_tag_data = { + .reg = reg + RPI_POE_FW_BASE_REG, + .val = *val + }; + u32 value; + int ret; + + if (ctx->fw) { + ret = rpi_firmware_property(ctx->fw, RPI_FIRMWARE_GET_POE_HAT_VAL, + &fw_tag_data, sizeof(fw_tag_data)); + if (!ret && fw_tag_data.ret) + ret = -EIO; + *val = fw_tag_data.val; + } else { + ret = regmap_read(ctx->regmap, ctx->offset + reg, &value); + if (!ret) { + *val = value; + ret = regmap_read(ctx->regmap, ctx->offset + reg + 1, &value); + *val |= value << 8; + } + } + + return ret; +} + +static int rpi_poe_power_supply_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *r_val) +{ + struct rpi_poe_power_supply_ctx *ctx = power_supply_get_drvdata(psy); + int ret; + unsigned int val = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_HEALTH: + ret = read_reg(ctx, RPI_POE_FLAG_REG, &val); + if (ret) + return ret; + + if (val & RPI_POE_FLAG_OC) { + r_val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + val = RPI_POE_FLAG_OC; + ret = write_reg(ctx, RPI_POE_FLAG_REG, &val); + if (ret) + return ret; + return 0; + } + + r_val->intval = POWER_SUPPLY_HEALTH_GOOD; + return 0; + + case POWER_SUPPLY_PROP_ONLINE: + ret = read_reg(ctx, RPI_POE_ADC_REG, &val); + if (ret) + return ret; + + r_val->intval = (val > 5); + return 0; + + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = read_reg(ctx, RPI_POE_ADC_REG, &val); + if (ret) + return ret; + val = (val * 3300)/9821; + r_val->intval = val * 1000; + return 0; + + case POWER_SUPPLY_PROP_CURRENT_MAX: + ret = read_reg(ctx, RPI_POE_FLAG_REG, &val); + if (ret) + return ret; + + if (val & RPI_POE_FLAG_AT) + r_val->intval = RPI_POE_CURRENT_AT_MAX; + else + r_val->intval = RPI_POE_CURRENT_AF_MAX; + return 0; + + default: + return -EINVAL; + } + + return -EINVAL; +} + +static enum power_supply_property rpi_poe_power_supply_properties[] = { + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CURRENT_MAX, +}; + +static const struct power_supply_desc rpi_poe_power_supply_desc = { + .name = "rpi-poe", + .type = POWER_SUPPLY_TYPE_MAINS, + .properties = rpi_poe_power_supply_properties, + .num_properties = ARRAY_SIZE(rpi_poe_power_supply_properties), + .get_property = rpi_poe_power_supply_get_property, +}; + +static int rpi_poe_power_supply_probe(struct platform_device *pdev) +{ + struct power_supply_config psy_cfg = {}; + struct rpi_poe_power_supply_ctx *ctx; + struct device_node *fw_node; + u32 revision; + + if (!of_device_is_available(pdev->dev.of_node)) + return -ENODEV; + + ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + if (pdev->dev.parent) + ctx->regmap = dev_get_regmap(pdev->dev.parent, NULL); + + if (ctx->regmap) { + if (device_property_read_u32(&pdev->dev, "reg", &ctx->offset)) + return -EINVAL; + } else { + fw_node = of_parse_phandle(pdev->dev.of_node, "firmware", 0); + if (!fw_node) { + dev_err(&pdev->dev, "Missing firmware node\n"); + return -ENOENT; + } + + ctx->fw = rpi_firmware_get(fw_node); + if (!ctx->fw) + return -EPROBE_DEFER; + if (rpi_firmware_property(ctx->fw, + RPI_FIRMWARE_GET_FIRMWARE_REVISION, + &revision, sizeof(revision))) { + dev_err(&pdev->dev, "Failed to get firmware revision\n"); + return -ENOENT; + } + if (revision < 0x60af72e8) { + dev_err(&pdev->dev, "Unsupported firmware\n"); + return -ENOENT; + } + } + + platform_set_drvdata(pdev, ctx); + + psy_cfg.of_node = pdev->dev.of_node; + psy_cfg.drv_data = ctx; + + ctx->supply = devm_power_supply_register(&pdev->dev, + &rpi_poe_power_supply_desc, + &psy_cfg); + if (IS_ERR(ctx->supply)) + return PTR_ERR(ctx->supply); + + return 0; +} + +static const struct of_device_id of_rpi_poe_power_supply_match[] = { + { .compatible = "raspberrypi,rpi-poe-power-supply", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, of_rpi_poe_power_supply_match); + +static struct platform_driver rpi_poe_power_supply_driver = { + .probe = rpi_poe_power_supply_probe, + .driver = { + .name = DRVNAME, + .of_match_table = of_rpi_poe_power_supply_match + }, +}; + +module_platform_driver(rpi_poe_power_supply_driver); + +MODULE_AUTHOR("Serge Schneider <serge@raspberrypi.org>"); +MODULE_ALIAS("platform:" DRVNAME); +MODULE_DESCRIPTION("Raspberry Pi PoE+ HAT power supply driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pps/clients/pps-gpio.c b/drivers/pps/clients/pps-gpio.c index 93e662912b5313..4434ec8454d59f 100644 --- a/drivers/pps/clients/pps-gpio.c +++ b/drivers/pps/clients/pps-gpio.c @@ -113,6 +113,9 @@ static int pps_gpio_setup(struct device *dev) data->assert_falling_edge = device_property_read_bool(dev, "assert-falling-edge"); + data->capture_clear = + device_property_read_bool(dev, "capture-clear"); + data->echo_pin = devm_gpiod_get_optional(dev, "echo", GPIOD_OUT_LOW); if (IS_ERR(data->echo_pin)) return dev_err_probe(dev, PTR_ERR(data->echo_pin), diff --git a/drivers/pps/pps.c b/drivers/pps/pps.c index 6a02245ea35fec..d11431ad143d3a 100644 --- a/drivers/pps/pps.c +++ b/drivers/pps/pps.c @@ -249,12 +249,13 @@ static long pps_cdev_ioctl(struct file *file, static long pps_cdev_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { - struct pps_device *pps = file->private_data; - void __user *uarg = (void __user *) arg; cmd = _IOC(_IOC_DIR(cmd), _IOC_TYPE(cmd), _IOC_NR(cmd), sizeof(void *)); +#ifdef CONFIG_X86_64 if (cmd == PPS_FETCH) { + struct pps_device *pps = file->private_data; + void __user *uarg = (void __user *) arg; struct pps_fdata_compat compat; struct pps_fdata fdata; int err; @@ -289,6 +290,7 @@ static long pps_cdev_compat_ioctl(struct file *file, return copy_to_user(uarg, &compat, sizeof(struct pps_fdata_compat)) ? -EFAULT : 0; } +#endif return pps_cdev_ioctl(file, cmd, arg); } diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 0915c1e7df16d4..c337409828a81b 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -491,6 +491,17 @@ config PWM_PCA9685 To compile this driver as a module, choose M here: the module will be called pwm-pca9685. +config PWM_PIO_RP1 + tristate "RP1 PIO PWM support" + depends on FIRMWARE_RP1 || COMPILE_TEST + help + This is a PWM framework driver for Raspberry Pi 5, using the PIO + hardware of RP1 to provide PWM functionality. Supports up to 4 + instances on GPIOs in bank 0. + + To compile this driver as a module, choose M here: the module + will be called pwm-pio-rp1. + config PWM_PXA tristate "PXA PWM support" depends on ARCH_PXA || ARCH_MMP || COMPILE_TEST @@ -510,6 +521,15 @@ config PWM_RASPBERRYPI_POE Enable Raspberry Pi firmware controller PWM bus used to control the official RPI PoE hat +config PWM_RP1 + tristate "RP1 PWM support" + depends on ARCH_BCM2835 || COMPILE_TEST + help + PWM framework driver for Raspberry Pi RP1 controller + + To compile this driver as a module, choose M here: the module + will be called pwm-rp1. + config PWM_RCAR tristate "Renesas R-Car PWM support" depends on ARCH_RENESAS || COMPILE_TEST diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 9081e0c0e9e097..11f59e12149fd6 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -44,8 +44,10 @@ obj-$(CONFIG_PWM_MXS) += pwm-mxs.o obj-$(CONFIG_PWM_NTXEC) += pwm-ntxec.o obj-$(CONFIG_PWM_OMAP_DMTIMER) += pwm-omap-dmtimer.o obj-$(CONFIG_PWM_PCA9685) += pwm-pca9685.o +obj-$(CONFIG_PWM_PIO_RP1) += pwm-pio-rp1.o obj-$(CONFIG_PWM_PXA) += pwm-pxa.o obj-$(CONFIG_PWM_RASPBERRYPI_POE) += pwm-raspberrypi-poe.o +obj-$(CONFIG_PWM_RP1) += pwm-rp1.o obj-$(CONFIG_PWM_RCAR) += pwm-rcar.o obj-$(CONFIG_PWM_RENESAS_TPU) += pwm-renesas-tpu.o obj-$(CONFIG_PWM_ROCKCHIP) += pwm-rockchip.o diff --git a/drivers/pwm/pwm-pio-rp1.c b/drivers/pwm/pwm-pio-rp1.c new file mode 100644 index 00000000000000..0e2c0d79fc6988 --- /dev/null +++ b/drivers/pwm/pwm-pio-rp1.c @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Raspberry Pi PIO PWM. + * + * Copyright (C) 2024 Raspberry Pi Ltd. + * + * Author: Phil Elwell (phil@raspberrypi.com) + * + * Based on the pwm-rp1 driver by: + * Naushir Patuck <naush@raspberrypi.com> + * and on the pwm-gpio driver by: + * Vincent Whitchurch <vincent.whitchurch@axis.com> + */ + +#include <linux/err.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/pio_rp1.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> + +struct pwm_pio_rp1 { + struct pwm_chip chip; + struct device *dev; + struct gpio_desc *gpiod; + struct mutex mutex; + PIO pio; + uint sm; + uint offset; + uint gpio; + uint32_t period; /* In SM cycles */ + uint32_t duty_cycle; /* In SM cycles */ + enum pwm_polarity polarity; + bool enabled; +}; + +/* Generated from pwm.pio by pioasm */ +#define pwm_wrap_target 0 +#define pwm_wrap 6 +#define pwm_loop_ticks 3 + +static const uint16_t pwm_program_instructions[] = { + // .wrap_target + 0x9080, // 0: pull noblock side 0 + 0xa027, // 1: mov x, osr + 0xa046, // 2: mov y, isr + 0x00a5, // 3: jmp x != y, 5 + 0x1806, // 4: jmp 6 side 1 + 0xa042, // 5: nop + 0x0083, // 6: jmp y--, 3 + // .wrap +}; + +static const struct pio_program pwm_program = { + .instructions = pwm_program_instructions, + .length = 7, + .origin = -1, +}; + +static unsigned int pwm_pio_resolution __read_mostly; + +static inline pio_sm_config pwm_program_get_default_config(uint offset) +{ + pio_sm_config c = pio_get_default_sm_config(); + + sm_config_set_wrap(&c, offset + pwm_wrap_target, offset + pwm_wrap); + sm_config_set_sideset(&c, 2, true, false); + return c; +} + +static inline void pwm_program_init(PIO pio, uint sm, uint offset, uint pin) +{ + pio_gpio_init(pio, pin); + + pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true); + pio_sm_config c = pwm_program_get_default_config(offset); + + sm_config_set_sideset_pins(&c, pin); + pio_sm_init(pio, sm, offset, &c); +} + +/* Write `period` to the input shift register - must be disabled */ +static void pio_pwm_set_period(PIO pio, uint sm, uint32_t period) +{ + pio_sm_put_blocking(pio, sm, period); + pio_sm_exec(pio, sm, pio_encode_pull(false, false)); + pio_sm_exec(pio, sm, pio_encode_out(pio_isr, 32)); +} + +/* Write `level` to TX FIFO. State machine will copy this into X. */ +static void pio_pwm_set_level(PIO pio, uint sm, uint32_t level) +{ + pio_sm_put_blocking(pio, sm, level); +} + +static int pwm_pio_rp1_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct pwm_pio_rp1 *ppwm = container_of(chip, struct pwm_pio_rp1, chip); + uint32_t new_duty_cycle; + uint32_t new_period; + + if (state->duty_cycle && state->duty_cycle < pwm_pio_resolution) + return -EINVAL; + + if (state->duty_cycle != state->period && + (state->period - state->duty_cycle < pwm_pio_resolution)) + return -EINVAL; + + new_period = state->period / pwm_pio_resolution; + new_duty_cycle = state->duty_cycle / pwm_pio_resolution; + + mutex_lock(&ppwm->mutex); + + if ((ppwm->enabled && !state->enabled) || new_period != ppwm->period) { + pio_sm_set_enabled(ppwm->pio, ppwm->sm, false); + ppwm->enabled = false; + } + + if (new_period != ppwm->period) { + pio_pwm_set_period(ppwm->pio, ppwm->sm, new_period); + ppwm->period = new_period; + } + + if (state->enabled && new_duty_cycle != ppwm->duty_cycle) { + pio_pwm_set_level(ppwm->pio, ppwm->sm, new_duty_cycle); + ppwm->duty_cycle = new_duty_cycle; + } + + if (state->polarity != ppwm->polarity) { + pio_gpio_set_outover(ppwm->pio, ppwm->gpio, + (state->polarity == PWM_POLARITY_INVERSED) ? + GPIO_OVERRIDE_INVERT : GPIO_OVERRIDE_NORMAL); + ppwm->polarity = state->polarity; + } + + if (!ppwm->enabled && state->enabled) { + pio_sm_set_enabled(ppwm->pio, ppwm->sm, true); + ppwm->enabled = true; + } + + mutex_unlock(&ppwm->mutex); + + return 0; +} + +static const struct pwm_ops pwm_pio_rp1_ops = { + .apply = pwm_pio_rp1_apply, +}; + +static int pwm_pio_rp1_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct of_phandle_args of_args = { 0 }; + struct device *dev = &pdev->dev; + struct pwm_pio_rp1 *ppwm; + struct pwm_chip *chip; + bool is_rp1; + + chip = devm_pwmchip_alloc(dev, 1, sizeof(*ppwm)); + if (IS_ERR(chip)) + return PTR_ERR(chip); + + ppwm = pwmchip_get_drvdata(chip); + + mutex_init(&ppwm->mutex); + + ppwm->gpiod = devm_gpiod_get(dev, NULL, GPIOD_ASIS); + /* Need to check that this is an RP1 GPIO in the first bank, and retrieve the offset */ + /* Unfortunately I think this has to be done by parsing the gpios property */ + if (IS_ERR(ppwm->gpiod)) + return dev_err_probe(dev, PTR_ERR(ppwm->gpiod), + "could not get a gpio\n"); + + /* This really shouldn't fail, given that we have a gpiod */ + if (of_parse_phandle_with_args(np, "gpios", "#gpio-cells", 0, &of_args)) + return dev_err_probe(dev, -EINVAL, + "can't find gpio declaration\n"); + + is_rp1 = of_device_is_compatible(of_args.np, "raspberrypi,rp1-gpio"); + of_node_put(of_args.np); + if (!is_rp1 || of_args.args_count != 2) + return dev_err_probe(dev, -EINVAL, + "not an RP1 gpio\n"); + + ppwm->gpio = of_args.args[0]; + + ppwm->pio = pio_open(); + if (IS_ERR(ppwm->pio)) + return dev_err_probe(dev, PTR_ERR(ppwm->pio), + "%pfw: could not open PIO\n", + dev_fwnode(dev)); + + ppwm->sm = pio_claim_unused_sm(ppwm->pio, false); + if ((int)ppwm->sm < 0) { + pio_close(ppwm->pio); + return dev_err_probe(dev, -EBUSY, + "%pfw: no free PIO SM\n", + dev_fwnode(dev)); + } + + ppwm->offset = pio_add_program(ppwm->pio, &pwm_program); + if (ppwm->offset == PIO_ORIGIN_ANY) { + pio_close(ppwm->pio); + return dev_err_probe(dev, -EBUSY, + "%pfw: not enough PIO program space\n", + dev_fwnode(dev)); + } + + pwm_program_init(ppwm->pio, ppwm->sm, ppwm->offset, ppwm->gpio); + + pwm_pio_resolution = (1000u * 1000 * 1000 * pwm_loop_ticks) / clock_get_hz(clk_sys); + + chip->ops = &pwm_pio_rp1_ops; + chip->atomic = true; + chip->npwm = 1; + + platform_set_drvdata(pdev, ppwm); + + return devm_pwmchip_add(dev, chip); +} + +static void pwm_pio_rp1_remove(struct platform_device *pdev) +{ + struct pwm_pio_rp1 *ppwm = platform_get_drvdata(pdev); + + pio_close(ppwm->pio); +} + +static const struct of_device_id pwm_pio_rp1_dt_ids[] = { + { .compatible = "raspberrypi,pwm-pio-rp1" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, pwm_pio_rp1_dt_ids); + +static struct platform_driver pwm_pio_rp1_driver = { + .driver = { + .name = "pwm-pio-rp1", + .of_match_table = pwm_pio_rp1_dt_ids, + }, + .probe = pwm_pio_rp1_probe, + .remove_new = pwm_pio_rp1_remove, +}; +module_platform_driver(pwm_pio_rp1_driver); + +MODULE_DESCRIPTION("PWM PIO RP1 driver"); +MODULE_AUTHOR("Phil Elwell"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-raspberrypi-poe.c b/drivers/pwm/pwm-raspberrypi-poe.c index 8921e7ea2ceaab..19ff0726f78ea3 100644 --- a/drivers/pwm/pwm-raspberrypi-poe.c +++ b/drivers/pwm/pwm-raspberrypi-poe.c @@ -16,6 +16,7 @@ #include <linux/of.h> #include <linux/platform_device.h> #include <linux/pwm.h> +#include <linux/regmap.h> #include <soc/bcm2835/raspberrypi-firmware.h> #include <dt-bindings/pwm/raspberrypi,firmware-poe-pwm.h> @@ -27,6 +28,10 @@ struct raspberrypi_pwm { struct rpi_firmware *firmware; + + struct regmap *regmap; + u32 offset; + unsigned int duty_cycle; }; @@ -42,7 +47,7 @@ struct raspberrypi_pwm *raspberrypi_pwm_from_chip(struct pwm_chip *chip) return pwmchip_get_drvdata(chip); } -static int raspberrypi_pwm_set_property(struct rpi_firmware *firmware, +static int raspberrypi_pwm_set_property(struct raspberrypi_pwm *pwm, u32 reg, u32 val) { struct raspberrypi_pwm_prop msg = { @@ -51,17 +56,19 @@ static int raspberrypi_pwm_set_property(struct rpi_firmware *firmware, }; int ret; - ret = rpi_firmware_property(firmware, RPI_FIRMWARE_SET_POE_HAT_VAL, - &msg, sizeof(msg)); - if (ret) - return ret; - if (msg.ret) - return -EIO; + if (pwm->firmware) { + ret = rpi_firmware_property(pwm->firmware, RPI_FIRMWARE_SET_POE_HAT_VAL, + &msg, sizeof(msg)); + if (!ret && msg.ret) + ret = -EIO; + } else { + ret = regmap_write(pwm->regmap, pwm->offset + reg, val); + } - return 0; + return ret; } -static int raspberrypi_pwm_get_property(struct rpi_firmware *firmware, +static int raspberrypi_pwm_get_property(struct raspberrypi_pwm *pwm, u32 reg, u32 *val) { struct raspberrypi_pwm_prop msg = { @@ -69,16 +76,17 @@ static int raspberrypi_pwm_get_property(struct rpi_firmware *firmware, }; int ret; - ret = rpi_firmware_property(firmware, RPI_FIRMWARE_GET_POE_HAT_VAL, - &msg, sizeof(msg)); - if (ret) - return ret; - if (msg.ret) - return -EIO; - - *val = le32_to_cpu(msg.val); + if (pwm->firmware) { + ret = rpi_firmware_property(pwm->firmware, RPI_FIRMWARE_GET_POE_HAT_VAL, + &msg, sizeof(msg)); + if (!ret && msg.ret) + ret = -EIO; + *val = le32_to_cpu(msg.val); + } else { + ret = regmap_read(pwm->regmap, pwm->offset + reg, val); + } - return 0; + return ret; } static int raspberrypi_pwm_get_state(struct pwm_chip *chip, @@ -118,7 +126,7 @@ static int raspberrypi_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, if (duty_cycle == rpipwm->duty_cycle) return 0; - ret = raspberrypi_pwm_set_property(rpipwm->firmware, RPI_PWM_CUR_DUTY_REG, + ret = raspberrypi_pwm_set_property(rpipwm, RPI_PWM_CUR_DUTY_REG, duty_cycle); if (ret) { dev_err(pwmchip_parent(chip), "Failed to set duty cycle: %pe\n", @@ -145,28 +153,41 @@ static int raspberrypi_pwm_probe(struct platform_device *pdev) struct raspberrypi_pwm *rpipwm; int ret; - firmware_node = of_get_parent(dev->of_node); - if (!firmware_node) { - dev_err(dev, "Missing firmware node\n"); - return -ENOENT; - } - - firmware = devm_rpi_firmware_get(&pdev->dev, firmware_node); - of_node_put(firmware_node); - if (!firmware) - return dev_err_probe(dev, -EPROBE_DEFER, - "Failed to get firmware handle\n"); - chip = devm_pwmchip_alloc(&pdev->dev, RASPBERRYPI_FIRMWARE_PWM_NUM, sizeof(*rpipwm)); if (IS_ERR(chip)) return PTR_ERR(chip); rpipwm = raspberrypi_pwm_from_chip(chip); - rpipwm->firmware = firmware; + if (!rpipwm) + return -ENOMEM; + + if (pdev->dev.parent) + rpipwm->regmap = dev_get_regmap(pdev->dev.parent, NULL); + + if (rpipwm->regmap) { + ret = device_property_read_u32(&pdev->dev, "reg", &rpipwm->offset); + if (ret) + return -EINVAL; + } else { + firmware_node = of_get_parent(dev->of_node); + if (!firmware_node) { + dev_err(dev, "Missing firmware node\n"); + return -ENOENT; + } + + firmware = devm_rpi_firmware_get(&pdev->dev, firmware_node); + of_node_put(firmware_node); + if (!firmware) + return dev_err_probe(dev, -EPROBE_DEFER, + "Failed to get firmware handle\n"); + + rpipwm->firmware = firmware; + } + chip->ops = &raspberrypi_pwm_ops; - ret = raspberrypi_pwm_get_property(rpipwm->firmware, RPI_PWM_CUR_DUTY_REG, + ret = raspberrypi_pwm_get_property(rpipwm, RPI_PWM_CUR_DUTY_REG, &rpipwm->duty_cycle); if (ret) { dev_err(dev, "Failed to get duty cycle: %pe\n", ERR_PTR(ret)); @@ -178,6 +199,7 @@ static int raspberrypi_pwm_probe(struct platform_device *pdev) static const struct of_device_id raspberrypi_pwm_of_match[] = { { .compatible = "raspberrypi,firmware-poe-pwm", }, + { .compatible = "raspberrypi,poe-pwm", }, { } }; MODULE_DEVICE_TABLE(of, raspberrypi_pwm_of_match); diff --git a/drivers/pwm/pwm-rp1.c b/drivers/pwm/pwm-rp1.c new file mode 100644 index 00000000000000..fcf20ded80cf87 --- /dev/null +++ b/drivers/pwm/pwm-rp1.c @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * pwm-rp1.c + * + * Raspberry Pi RP1 PWM. + * + * Copyright © 2023 Raspberry Pi Ltd. + * + * Author: Naushir Patuck (naush@raspberrypi.com) + * + * Based on the pwm-bcm2835 driver by: + * Bart Tanghe <bart.tanghe@thomasmore.be> + */ + +#include <linux/bitops.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> + +#define PWM_GLOBAL_CTRL 0x000 +#define PWM_CHANNEL_CTRL(x) (0x014 + ((x) * 16)) +#define PWM_RANGE(x) (0x018 + ((x) * 16)) +#define PWM_DUTY(x) (0x020 + ((x) * 16)) + +/* 8:FIFO_POP_MASK + 0:Trailing edge M/S modulation */ +#define PWM_CHANNEL_DEFAULT (BIT(8) + BIT(0)) +#define PWM_CHANNEL_ENABLE(x) BIT(x) +#define PWM_POLARITY BIT(3) +#define SET_UPDATE BIT(31) +#define PWM_MODE_MASK GENMASK(1, 0) + +struct rp1_pwm { + void __iomem *base; + struct clk *clk; +}; + +static inline struct rp1_pwm *to_rp1_pwm(struct pwm_chip *chip) +{ + return pwmchip_get_drvdata(chip); +} + +static void rp1_pwm_apply_config(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct rp1_pwm *pc = to_rp1_pwm(chip); + u32 value; + + value = readl(pc->base + PWM_GLOBAL_CTRL); + value |= SET_UPDATE; + writel(value, pc->base + PWM_GLOBAL_CTRL); +} + +static int rp1_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct rp1_pwm *pc = to_rp1_pwm(chip); + + writel(PWM_CHANNEL_DEFAULT, pc->base + PWM_CHANNEL_CTRL(pwm->hwpwm)); + return 0; +} + +static void rp1_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct rp1_pwm *pc = to_rp1_pwm(chip); + u32 value; + + value = readl(pc->base + PWM_CHANNEL_CTRL(pwm->hwpwm)); + value &= ~PWM_MODE_MASK; + writel(value, pc->base + PWM_CHANNEL_CTRL(pwm->hwpwm)); + rp1_pwm_apply_config(chip, pwm); +} + +static int rp1_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct rp1_pwm *pc = to_rp1_pwm(chip); + unsigned long clk_rate = clk_get_rate(pc->clk); + unsigned long clk_period; + u32 value; + + if (!clk_rate) { + dev_err(&chip->dev, "failed to get clock rate\n"); + return -EINVAL; + } + + /* set period */ + clk_period = DIV_ROUND_CLOSEST(NSEC_PER_SEC, clk_rate); + + writel(DIV_ROUND_CLOSEST(state->duty_cycle, clk_period), + pc->base + PWM_DUTY(pwm->hwpwm)); + + /* set duty cycle */ + writel(DIV_ROUND_CLOSEST(state->period, clk_period), + pc->base + PWM_RANGE(pwm->hwpwm)); + + /* set polarity */ + value = readl(pc->base + PWM_CHANNEL_CTRL(pwm->hwpwm)); + if (state->polarity == PWM_POLARITY_NORMAL) + value &= ~PWM_POLARITY; + else + value |= PWM_POLARITY; + writel(value, pc->base + PWM_CHANNEL_CTRL(pwm->hwpwm)); + + /* enable/disable */ + value = readl(pc->base + PWM_GLOBAL_CTRL); + if (state->enabled) + value |= PWM_CHANNEL_ENABLE(pwm->hwpwm); + else + value &= ~PWM_CHANNEL_ENABLE(pwm->hwpwm); + writel(value, pc->base + PWM_GLOBAL_CTRL); + + rp1_pwm_apply_config(chip, pwm); + + return 0; +} + +static const struct pwm_ops rp1_pwm_ops = { + .request = rp1_pwm_request, + .free = rp1_pwm_free, + .apply = rp1_pwm_apply, +}; + +static int rp1_pwm_probe(struct platform_device *pdev) +{ + struct pwm_chip *chip; + struct rp1_pwm *pc; + int ret; + + chip = devm_pwmchip_alloc(&pdev->dev, 4, sizeof(*pc)); + + if (IS_ERR(chip)) + return PTR_ERR(chip); + + pc = to_rp1_pwm(chip); + + pc->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(pc->base)) + return PTR_ERR(pc->base); + + pc->clk = devm_clk_get_enabled(&pdev->dev, NULL); + if (IS_ERR(pc->clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(pc->clk), + "clock not found\n"); + + chip->ops = &rp1_pwm_ops; + chip->of_xlate = of_pwm_xlate_with_flags; + + ret = devm_pwmchip_add(&pdev->dev, chip); + if (ret < 0) + goto add_fail; + + return 0; + +add_fail: + clk_disable_unprepare(pc->clk); + return ret; +} + +static void rp1_pwm_remove(struct platform_device *pdev) +{ + struct rp1_pwm *pc = platform_get_drvdata(pdev); + + clk_disable_unprepare(pc->clk); +} + +static const struct of_device_id rp1_pwm_of_match[] = { + { .compatible = "raspberrypi,rp1-pwm" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, rp1_pwm_of_match); + +static struct platform_driver rp1_pwm_driver = { + .driver = { + .name = "rpi-pwm", + .of_match_table = rp1_pwm_of_match, + }, + .probe = rp1_pwm_probe, + .remove = rp1_pwm_remove, +}; +module_platform_driver(rp1_pwm_driver); + +MODULE_AUTHOR("Naushir Patuck <naush@raspberrypi.com"); +MODULE_DESCRIPTION("RP1 PWM driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 39297f7d817719..0ed0a76612988f 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -1142,6 +1142,16 @@ config REGULATOR_RASPBERRYPI_TOUCHSCREEN_ATTINY touchscreen unit. The regulator is used to enable power to the TC358762, display and to control backlight. +config REGULATOR_RASPBERRYPI_TOUCHSCREEN_V2 + tristate "Raspberry Pi 7-inch touchscreen panel V2 regulator" + depends on BACKLIGHT_CLASS_DEVICE + depends on I2C + select REGMAP_I2C + help + This driver supports regulator on the V2 Raspberry Pi + touchscreen unit. The regulator is used to enable power to the + display and to control backlight. + config REGULATOR_RC5T583 tristate "RICOH RC5T583 Power regulators" depends on MFD_RC5T583 diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 3d5a803dce8a05..d46b77eeab4610 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -135,6 +135,7 @@ obj-$(CONFIG_REGULATOR_PCAP) += pcap-regulator.o obj-$(CONFIG_REGULATOR_PCF50633) += pcf50633-regulator.o obj-$(CONFIG_REGULATOR_RAA215300) += raa215300.o obj-$(CONFIG_REGULATOR_RASPBERRYPI_TOUCHSCREEN_ATTINY) += rpi-panel-attiny-regulator.o +obj-$(CONFIG_REGULATOR_RASPBERRYPI_TOUCHSCREEN_V2) += rpi-panel-v2-regulator.o obj-$(CONFIG_REGULATOR_RC5T583) += rc5t583-regulator.o obj-$(CONFIG_REGULATOR_RK808) += rk808-regulator.o obj-$(CONFIG_REGULATOR_RN5T618) += rn5t618-regulator.o diff --git a/drivers/regulator/rpi-panel-attiny-regulator.c b/drivers/regulator/rpi-panel-attiny-regulator.c index 6c3b6bfac961d7..a1ac963dfe5d0d 100644 --- a/drivers/regulator/rpi-panel-attiny-regulator.c +++ b/drivers/regulator/rpi-panel-attiny-regulator.c @@ -143,24 +143,8 @@ static int attiny_lcd_power_disable(struct regulator_dev *rdev) static int attiny_lcd_power_is_enabled(struct regulator_dev *rdev) { struct attiny_lcd *state = rdev_get_drvdata(rdev); - unsigned int data; - int ret, i; - - mutex_lock(&state->lock); - for (i = 0; i < 10; i++) { - ret = regmap_read(rdev->regmap, REG_PORTC, &data); - if (!ret) - break; - usleep_range(10000, 12000); - } - - mutex_unlock(&state->lock); - - if (ret < 0) - return ret; - - return data & PC_RST_BRIDGE_N; + return state->port_states[REG_PORTC - REG_PORTA] & PC_RST_BRIDGE_N; } static const struct regulator_init_data attiny_regulator_default = { @@ -245,39 +229,6 @@ static void attiny_gpio_set(struct gpio_chip *gc, unsigned int off, int val) mutex_unlock(&state->lock); } -static int attiny_i2c_read(struct i2c_client *client, u8 reg, unsigned int *buf) -{ - struct i2c_msg msgs[1]; - u8 addr_buf[1] = { reg }; - u8 data_buf[1] = { 0, }; - int ret; - - /* Write register address */ - msgs[0].addr = client->addr; - msgs[0].flags = 0; - msgs[0].len = ARRAY_SIZE(addr_buf); - msgs[0].buf = addr_buf; - - ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); - if (ret != ARRAY_SIZE(msgs)) - return -EIO; - - usleep_range(5000, 10000); - - /* Read data from register */ - msgs[0].addr = client->addr; - msgs[0].flags = I2C_M_RD; - msgs[0].len = 1; - msgs[0].buf = data_buf; - - ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); - if (ret != ARRAY_SIZE(msgs)) - return -EIO; - - *buf = data_buf[0]; - return 0; -} - /* * I2C driver interface functions */ @@ -289,7 +240,6 @@ static int attiny_i2c_probe(struct i2c_client *i2c) struct regulator_dev *rdev; struct attiny_lcd *state; struct regmap *regmap; - unsigned int data; int ret; state = devm_kzalloc(&i2c->dev, sizeof(*state), GFP_KERNEL); @@ -307,22 +257,6 @@ static int attiny_i2c_probe(struct i2c_client *i2c) goto error; } - ret = attiny_i2c_read(i2c, REG_ID, &data); - if (ret < 0) { - dev_err(&i2c->dev, "Failed to read REG_ID reg: %d\n", ret); - goto error; - } - - switch (data) { - case 0xde: /* ver 1 */ - case 0xc3: /* ver 2 */ - break; - default: - dev_err(&i2c->dev, "Unknown Atmel firmware revision: 0x%02x\n", data); - ret = -ENODEV; - goto error; - } - regmap_write(regmap, REG_POWERON, 0); msleep(30); regmap_write(regmap, REG_PWM, 0); @@ -386,6 +320,14 @@ static void attiny_i2c_remove(struct i2c_client *client) mutex_destroy(&state->lock); } +static void attiny_i2c_shutdown(struct i2c_client *client) +{ + struct attiny_lcd *state = i2c_get_clientdata(client); + + regmap_write(state->regmap, REG_PWM, 0); + regmap_write(state->regmap, REG_POWERON, 0); +} + static const struct of_device_id attiny_dt_ids[] = { { .compatible = "raspberrypi,7inch-touchscreen-panel-regulator" }, {}, @@ -400,6 +342,7 @@ static struct i2c_driver attiny_regulator_driver = { }, .probe = attiny_i2c_probe, .remove = attiny_i2c_remove, + .shutdown = attiny_i2c_shutdown, }; module_i2c_driver(attiny_regulator_driver); diff --git a/drivers/regulator/rpi-panel-v2-regulator.c b/drivers/regulator/rpi-panel-v2-regulator.c new file mode 100644 index 00000000000000..919d59749ee4f8 --- /dev/null +++ b/drivers/regulator/rpi-panel-v2-regulator.c @@ -0,0 +1,240 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2022 Raspberry Pi Ltd. + * + * Based on rpi-panel-attiny-regulator.c by Marek Vasut <marex@denx.de> + */ + +#include <linux/backlight.h> +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/gpio/driver.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/regulator/driver.h> + +/* I2C registers of the microcontroller. */ +#define REG_ID 0x01 +#define REG_POWERON 0x02 +#define REG_PWM 0x03 + +// bits for poweron register +#define LCD_RESET_BIT BIT(0) +#define CTP_RESET_BIT BIT(1) + +//bits for the PWM register +#define PWM_BL_ENABLE BIT(7) +#define PWM_VALUE GENMASK(4, 0) + +#define NUM_GPIO 2 /* Treat LCD_RESET and CTP_RESET as GPIOs */ + +struct rpi_panel_v2_lcd { + /* lock to serialise overall accesses to the Atmel */ + struct mutex lock; + struct regmap *regmap; + u8 poweron_state; + + struct gpio_chip gc; +}; + +static const struct regmap_config rpi_panel_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = REG_PWM, +}; + +static int rpi_panel_v2_gpio_get_direction(struct gpio_chip *gc, unsigned int off) +{ + return GPIO_LINE_DIRECTION_OUT; +} + +static void rpi_panel_v2_gpio_set(struct gpio_chip *gc, unsigned int off, int val) +{ + struct rpi_panel_v2_lcd *state = gpiochip_get_data(gc); + u8 last_val; + + if (off >= NUM_GPIO) + return; + + mutex_lock(&state->lock); + + last_val = state->poweron_state; + if (val) + last_val |= (1 << off); + else + last_val &= ~(1 << off); + + state->poweron_state = last_val; + + regmap_write(state->regmap, REG_POWERON, last_val); + + mutex_unlock(&state->lock); +} + +static int rpi_panel_v2_update_status(struct backlight_device *bl) +{ + struct regmap *regmap = bl_get_data(bl); + int brightness = bl->props.brightness; + + if (bl->props.power != FB_BLANK_UNBLANK || + bl->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK)) + brightness = 0; + + return regmap_write(regmap, REG_PWM, brightness | PWM_BL_ENABLE); +} + +static const struct backlight_ops rpi_panel_v2_bl = { + .update_status = rpi_panel_v2_update_status, +}; + +static int rpi_panel_v2_i2c_read(struct i2c_client *client, u8 reg, unsigned int *buf) +{ + struct i2c_msg msgs[1]; + u8 addr_buf[1] = { reg }; + u8 data_buf[1] = { 0, }; + int ret; + + /* Write register address */ + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = ARRAY_SIZE(addr_buf); + msgs[0].buf = addr_buf; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + usleep_range(5000, 10000); + + /* Read data from register */ + msgs[0].addr = client->addr; + msgs[0].flags = I2C_M_RD; + msgs[0].len = 1; + msgs[0].buf = data_buf; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + *buf = data_buf[0]; + return 0; +} + +/* + * I2C driver interface functions + */ +static int rpi_panel_v2_i2c_probe(struct i2c_client *i2c) +{ + struct backlight_properties props = { }; + struct backlight_device *bl; + struct rpi_panel_v2_lcd *state; + struct regmap *regmap; + unsigned int data; + int ret; + + state = devm_kzalloc(&i2c->dev, sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + mutex_init(&state->lock); + i2c_set_clientdata(i2c, state); + + regmap = devm_regmap_init_i2c(i2c, &rpi_panel_regmap_config); + if (IS_ERR(regmap)) { + ret = PTR_ERR(regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); + goto error; + } + + ret = rpi_panel_v2_i2c_read(i2c, REG_ID, &data); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to read REG_ID reg: %d\n", ret); + goto error; + } + + switch (data & 0x0f) { + case 0x01: /* 7 inch */ + case 0x04: /* 7 inch - old */ + case 0x08: /* 5 inch - old */ + case 0x09: /* 5 inch */ + break; + default: + dev_err(&i2c->dev, "Unknown revision: 0x%02x\n", + data & 0x0f); + ret = -ENODEV; + goto error; + } + + regmap_write(regmap, REG_POWERON, 0); + + state->regmap = regmap; + state->gc.parent = &i2c->dev; + state->gc.label = i2c->name; + state->gc.owner = THIS_MODULE; + state->gc.base = -1; + state->gc.ngpio = NUM_GPIO; + + state->gc.set = rpi_panel_v2_gpio_set; + state->gc.get_direction = rpi_panel_v2_gpio_get_direction; + state->gc.can_sleep = true; + + ret = devm_gpiochip_add_data(&i2c->dev, &state->gc, state); + if (ret) { + dev_err(&i2c->dev, "Failed to create gpiochip: %d\n", ret); + goto error; + } + + props.type = BACKLIGHT_RAW; + props.max_brightness = PWM_VALUE; + bl = devm_backlight_device_register(&i2c->dev, dev_name(&i2c->dev), + &i2c->dev, regmap, &rpi_panel_v2_bl, + &props); + if (IS_ERR(bl)) + return PTR_ERR(bl); + + bl->props.brightness = PWM_VALUE; + + return 0; + +error: + mutex_destroy(&state->lock); + return ret; +} + +static void rpi_panel_v2_i2c_remove(struct i2c_client *client) +{ + struct rpi_panel_v2_lcd *state = i2c_get_clientdata(client); + + mutex_destroy(&state->lock); +} + +static void rpi_panel_v2_i2c_shutdown(struct i2c_client *client) +{ + struct rpi_panel_v2_lcd *state = i2c_get_clientdata(client); + + regmap_write(state->regmap, REG_PWM, 0); + regmap_write(state->regmap, REG_POWERON, 0); +} + +static const struct of_device_id rpi_panel_v2_dt_ids[] = { + { .compatible = "raspberrypi,v2-touchscreen-panel-regulator" }, + {}, +}; +MODULE_DEVICE_TABLE(of, rpi_panel_v2_dt_ids); + +static struct i2c_driver rpi_panel_v2_regulator_driver = { + .driver = { + .name = "rpi_touchscreen_v2", + .of_match_table = of_match_ptr(rpi_panel_v2_dt_ids), + }, + .probe = rpi_panel_v2_i2c_probe, + .remove = rpi_panel_v2_i2c_remove, + .shutdown = rpi_panel_v2_i2c_shutdown, +}; + +module_i2c_driver(rpi_panel_v2_regulator_driver); + +MODULE_AUTHOR("Dave Stevenson <dave.stevenson@raspberrypi.com>"); +MODULE_DESCRIPTION("Regulator device driver for Raspberry Pi 7-inch V2 touchscreen"); +MODULE_LICENSE("GPL"); diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig index 5484a65f66b953..bce9924838b6ed 100644 --- a/drivers/reset/Kconfig +++ b/drivers/reset/Kconfig @@ -51,7 +51,7 @@ config RESET_BERLIN config RESET_BRCMSTB tristate "Broadcom STB reset controller" - depends on ARCH_BRCMSTB || COMPILE_TEST + depends on ARCH_BRCMSTB || ARCH_BCM2835 || COMPILE_TEST default ARCH_BRCMSTB help This enables the reset controller driver for Broadcom STB SoCs using diff --git a/drivers/reset/reset-brcmstb-rescal.c b/drivers/reset/reset-brcmstb-rescal.c index 823317772bacfa..89c1cae675a093 100644 --- a/drivers/reset/reset-brcmstb-rescal.c +++ b/drivers/reset/reset-brcmstb-rescal.c @@ -20,6 +20,7 @@ struct brcm_rescal_reset { struct reset_controller_dev rcdev; }; +/* Also doubles a deassert */ static int brcm_rescal_reset_set(struct reset_controller_dev *rcdev, unsigned long id) { @@ -52,6 +53,13 @@ static int brcm_rescal_reset_set(struct reset_controller_dev *rcdev, return 0; } +/* A dummy function - deassert/reset does all the work */ +static int brcm_rescal_reset_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return 0; +} + static int brcm_rescal_reset_xlate(struct reset_controller_dev *rcdev, const struct of_phandle_args *reset_spec) { @@ -61,6 +69,8 @@ static int brcm_rescal_reset_xlate(struct reset_controller_dev *rcdev, static const struct reset_control_ops brcm_rescal_reset_ops = { .reset = brcm_rescal_reset_set, + .deassert = brcm_rescal_reset_set, + .assert = brcm_rescal_reset_assert, }; static int brcm_rescal_reset_probe(struct platform_device *pdev) diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 66eb1122248b65..36a56d647dfdfd 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -223,6 +223,17 @@ config RTC_DRV_AC100 This driver can also be built as a module. If so, the module will be called rtc-ac100. +config RTC_DRV_RPI + tristate "Raspberry Pi RTC" + depends on ARCH_BRCMSTB || COMPILE_TEST + default ARCH_BRCMSTB + help + If you say yes here you get support for the RTC found on + Raspberry Pi devices. + + This driver can also be built as a module. If so, the module + will be called rtc-rpi. + config RTC_DRV_BRCMSTB tristate "Broadcom STB wake-timer" depends on ARCH_BRCMSTB || BMIPS_GENERIC || COMPILE_TEST diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index f62340ecc5348d..47adbf011f1c1a 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -143,6 +143,7 @@ obj-$(CONFIG_RTC_DRV_RC5T583) += rtc-rc5t583.o obj-$(CONFIG_RTC_DRV_RC5T619) += rtc-rc5t619.o obj-$(CONFIG_RTC_DRV_RK808) += rtc-rk808.o obj-$(CONFIG_RTC_DRV_RP5C01) += rtc-rp5c01.o +obj-$(CONFIG_RTC_DRV_RPI) += rtc-rpi.o obj-$(CONFIG_RTC_DRV_RS5C313) += rtc-rs5c313.o obj-$(CONFIG_RTC_DRV_RS5C348) += rtc-rs5c348.o obj-$(CONFIG_RTC_DRV_RS5C372) += rtc-rs5c372.o diff --git a/drivers/rtc/rtc-ds3232.c b/drivers/rtc/rtc-ds3232.c index dd37b055693c0c..1ce653fcc0cf4d 100644 --- a/drivers/rtc/rtc-ds3232.c +++ b/drivers/rtc/rtc-ds3232.c @@ -701,9 +701,16 @@ static int ds3234_probe(struct spi_device *spi) return ds3232_probe(&spi->dev, regmap, spi->irq, "ds3234"); } +static const __maybe_unused struct of_device_id ds3234_of_match[] = { + { .compatible = "dallas,ds3234" }, + { } +}; +MODULE_DEVICE_TABLE(of, ds3234_of_match); + static struct spi_driver ds3234_driver = { .driver = { .name = "ds3234", + .of_match_table = of_match_ptr(ds3234_of_match), }, .probe = ds3234_probe, }; diff --git a/drivers/rtc/rtc-pcf2123.c b/drivers/rtc/rtc-pcf2123.c index e714661e61a916..89cda4dea7f816 100644 --- a/drivers/rtc/rtc-pcf2123.c +++ b/drivers/rtc/rtc-pcf2123.c @@ -479,3 +479,4 @@ module_spi_driver(pcf2123_driver); MODULE_AUTHOR("Chris Verges <chrisv@cyberswitching.com>"); MODULE_DESCRIPTION("NXP PCF2123 RTC driver"); MODULE_LICENSE("GPL"); +MODULE_ALIAS("spi:rtc-pcf2123"); diff --git a/drivers/rtc/rtc-pcf8523.c b/drivers/rtc/rtc-pcf8523.c index 2c63c0ffd05a1a..0125000fa722a2 100644 --- a/drivers/rtc/rtc-pcf8523.c +++ b/drivers/rtc/rtc-pcf8523.c @@ -100,6 +100,7 @@ static int pcf8523_rtc_read_time(struct device *dev, struct rtc_time *tm) { struct pcf8523 *pcf8523 = dev_get_drvdata(dev); u8 regs[10]; + u32 value; int err; err = regmap_bulk_read(pcf8523->regmap, PCF8523_REG_CONTROL1, regs, @@ -107,9 +108,36 @@ static int pcf8523_rtc_read_time(struct device *dev, struct rtc_time *tm) if (err < 0) return err; - if ((regs[0] & PCF8523_CONTROL1_STOP) || (regs[3] & PCF8523_SECONDS_OS)) + if (regs[PCF8523_REG_CONTROL1] & PCF8523_CONTROL1_STOP) return -EINVAL; + if (regs[PCF8523_REG_SECONDS] & PCF8523_SECONDS_OS) { + /* + * If the oscillator was stopped, try to clear the flag. Upon + * power-up the flag is always set, but if we cannot clear it + * the oscillator isn't running properly for some reason. The + * sensible thing therefore is to return an error, signalling + * that the clock cannot be assumed to be correct. + */ + + regs[PCF8523_REG_SECONDS] &= ~PCF8523_SECONDS_OS; + + err = regmap_write(pcf8523->regmap, PCF8523_REG_SECONDS, + regs[PCF8523_REG_SECONDS]); + if (err < 0) + return err; + + err = regmap_read(pcf8523->regmap, PCF8523_REG_SECONDS, + &value); + if (err < 0) + return err; + + if (value & PCF8523_SECONDS_OS) + return -EAGAIN; + + regs[PCF8523_REG_SECONDS] = value; + } + tm->tm_sec = bcd2bin(regs[3] & 0x7f); tm->tm_min = bcd2bin(regs[4] & 0x7f); tm->tm_hour = bcd2bin(regs[5] & 0x3f); diff --git a/drivers/rtc/rtc-rpi.c b/drivers/rtc/rtc-rpi.c new file mode 100644 index 00000000000000..006012333e7891 --- /dev/null +++ b/drivers/rtc/rtc-rpi.c @@ -0,0 +1,277 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/** + * rtc-rpi.c + * + * RTC driver using firmware mailbox + * Supports battery backed RTC and wake alarms + * + * Based on rtc-meson-vrtc by Neil Armstrong + * + * Copyright (c) 2023, Raspberry Pi Ltd. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/rtc.h> +#include <linux/of.h> +#include <soc/bcm2835/raspberrypi-firmware.h> + +struct rpi_rtc_data { + struct rtc_device *rtc; + struct rpi_firmware *fw; + u32 bbat_vchg_microvolts; +}; + +#define RPI_FIRMWARE_GET_RTC_REG 0x00030087 +#define RPI_FIRMWARE_SET_RTC_REG 0x00038087 + +enum { + RTC_TIME, + RTC_ALARM, + RTC_ALARM_PENDING, + RTC_ALARM_ENABLE, + RTC_BBAT_CHG_VOLTS, + RTC_BBAT_CHG_VOLTS_MIN, + RTC_BBAT_CHG_VOLTS_MAX, + RTC_BBAT_VOLTS +}; + +static int rpi_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct rpi_rtc_data *vrtc = dev_get_drvdata(dev); + u32 data[2] = {RTC_TIME}; + int err; + + err = rpi_firmware_property(vrtc->fw, RPI_FIRMWARE_GET_RTC_REG, + &data, sizeof(data)); + rtc_time64_to_tm(data[1], tm); + return err; +} + +static int rpi_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct rpi_rtc_data *vrtc = dev_get_drvdata(dev); + u32 data[2] = {RTC_TIME, rtc_tm_to_time64(tm)}; + + return rpi_firmware_property(vrtc->fw, RPI_FIRMWARE_SET_RTC_REG, + &data, sizeof(data)); +} + +static int rpi_rtc_alarm_irq_is_enabled(struct device *dev, unsigned char *enabled) +{ + struct rpi_rtc_data *vrtc = dev_get_drvdata(dev); + u32 data[2] = {RTC_ALARM_ENABLE}; + s32 err = 0; + + err = rpi_firmware_property(vrtc->fw, RPI_FIRMWARE_GET_RTC_REG, + &data, sizeof(data)); + *enabled = data[1] & 0x1; + return err; +} + +static int rpi_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled) +{ + struct rpi_rtc_data *vrtc = dev_get_drvdata(dev); + u32 data[2] = {RTC_ALARM_ENABLE, enabled}; + + return rpi_firmware_property(vrtc->fw, RPI_FIRMWARE_SET_RTC_REG, + &data, sizeof(data)); +} + +static int rpi_rtc_alarm_clear_pending(struct device *dev) +{ + struct rpi_rtc_data *vrtc = dev_get_drvdata(dev); + u32 data[2] = {RTC_ALARM_PENDING, 1}; + + return rpi_firmware_property(vrtc->fw, RPI_FIRMWARE_SET_RTC_REG, + &data, sizeof(data)); +} + +static int rpi_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + struct rpi_rtc_data *vrtc = dev_get_drvdata(dev); + u32 data[2] = {RTC_ALARM}; + s32 err = 0; + + err = rpi_rtc_alarm_irq_is_enabled(dev, &alarm->enabled); + if (!err) + err = rpi_firmware_property(vrtc->fw, RPI_FIRMWARE_GET_RTC_REG, + &data, sizeof(data)); + rtc_time64_to_tm(data[1], &alarm->time); + + return err; +} + +static int rpi_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + struct rpi_rtc_data *vrtc = dev_get_drvdata(dev); + u32 data[2] = {RTC_ALARM, rtc_tm_to_time64(&alarm->time)}; + int err; + + err = rpi_firmware_property(vrtc->fw, RPI_FIRMWARE_SET_RTC_REG, + &data, sizeof(data)); + + if (err == 0) + err = rpi_rtc_alarm_irq_enable(dev, alarm->enabled); + + return err; +} + +static const struct rtc_class_ops rpi_rtc_ops = { + .read_time = rpi_rtc_read_time, + .set_time = rpi_rtc_set_time, + .read_alarm = rpi_rtc_read_alarm, + .set_alarm = rpi_rtc_set_alarm, + .alarm_irq_enable = rpi_rtc_alarm_irq_enable, +}; + +static int rpi_rtc_set_charge_voltage(struct device *dev) +{ + struct rpi_rtc_data *vrtc = dev_get_drvdata(dev); + u32 data[2] = {RTC_BBAT_CHG_VOLTS, vrtc->bbat_vchg_microvolts}; + int err; + + err = rpi_firmware_property(vrtc->fw, RPI_FIRMWARE_SET_RTC_REG, + &data, sizeof(data)); + + if (err) + dev_err(dev, "failed to set trickle charge voltage to %uuV: %d\n", + vrtc->bbat_vchg_microvolts, err); + else if (vrtc->bbat_vchg_microvolts) + dev_info(dev, "trickle charging enabled at %uuV\n", + vrtc->bbat_vchg_microvolts); + + return err; +} + +static ssize_t rpi_rtc_print_uint_reg(struct device *dev, char *buf, u32 reg) +{ + struct rpi_rtc_data *vrtc = dev_get_drvdata(dev->parent); + u32 data[2] = {reg, 0}; + int ret = 0; + + ret = rpi_firmware_property(vrtc->fw, RPI_FIRMWARE_GET_RTC_REG, + &data, sizeof(data)); + if (ret < 0) + return ret; + + return sprintf(buf, "%u\n", data[1]); +} + +static ssize_t charging_voltage_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return rpi_rtc_print_uint_reg(dev, buf, RTC_BBAT_CHG_VOLTS); +} +static DEVICE_ATTR_RO(charging_voltage); + +static ssize_t charging_voltage_min_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return rpi_rtc_print_uint_reg(dev, buf, RTC_BBAT_CHG_VOLTS_MIN); +} +static DEVICE_ATTR_RO(charging_voltage_min); + +static ssize_t charging_voltage_max_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return rpi_rtc_print_uint_reg(dev, buf, RTC_BBAT_CHG_VOLTS_MAX); +} +static DEVICE_ATTR_RO(charging_voltage_max); + +static ssize_t battery_voltage_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return rpi_rtc_print_uint_reg(dev, buf, RTC_BBAT_VOLTS); +} +static DEVICE_ATTR_RO(battery_voltage); + +static struct attribute *rpi_rtc_attrs[] = { + &dev_attr_charging_voltage.attr, + &dev_attr_charging_voltage_min.attr, + &dev_attr_charging_voltage_max.attr, + &dev_attr_battery_voltage.attr, + NULL +}; + +static const struct attribute_group rpi_rtc_sysfs_files = { + .attrs = rpi_rtc_attrs, +}; + +static int rpi_rtc_probe(struct platform_device *pdev) +{ + struct rpi_rtc_data *vrtc; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct device_node *fw_node; + struct rpi_firmware *fw; + int ret; + + fw_node = of_parse_phandle(np, "firmware", 0); + if (!fw_node) { + dev_err(dev, "Missing firmware node\n"); + return -ENOENT; + } + + fw = rpi_firmware_get(fw_node); + if (!fw) + return -EPROBE_DEFER; + + vrtc = devm_kzalloc(&pdev->dev, sizeof(*vrtc), GFP_KERNEL); + if (!vrtc) + return -ENOMEM; + + vrtc->fw = fw; + + device_init_wakeup(&pdev->dev, 1); + + platform_set_drvdata(pdev, vrtc); + + vrtc->rtc = devm_rtc_allocate_device(&pdev->dev); + if (IS_ERR(vrtc->rtc)) + return PTR_ERR(vrtc->rtc); + + set_bit(RTC_FEATURE_ALARM_WAKEUP_ONLY, vrtc->rtc->features); + clear_bit(RTC_FEATURE_UPDATE_INTERRUPT, vrtc->rtc->features); + + vrtc->rtc->ops = &rpi_rtc_ops; + ret = rtc_add_group(vrtc->rtc, &rpi_rtc_sysfs_files); + if (ret) + return ret; + + rpi_rtc_alarm_clear_pending(dev); + + /* + * Optionally enable trickle charging - if the property isn't + * present (or set to zero), trickle charging is disabled. + */ + of_property_read_u32(np, "trickle-charge-microvolt", + &vrtc->bbat_vchg_microvolts); + + rpi_rtc_set_charge_voltage(dev); + + return devm_rtc_register_device(vrtc->rtc); +} + +static const struct of_device_id rpi_rtc_dt_match[] = { + { .compatible = "raspberrypi,rpi-rtc"}, + {}, +}; +MODULE_DEVICE_TABLE(of, rpi_rtc_dt_match); + +static struct platform_driver rpi_rtc_driver = { + .probe = rpi_rtc_probe, + .driver = { + .name = "rpi-rtc", + .of_match_table = rpi_rtc_dt_match, + }, +}; + +module_platform_driver(rpi_rtc_driver); + +MODULE_DESCRIPTION("Raspberry Pi RTC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/rtc/rtc-rv3028.c b/drivers/rtc/rtc-rv3028.c index 2f001c59c61d54..51f542d823355b 100644 --- a/drivers/rtc/rtc-rv3028.c +++ b/drivers/rtc/rtc-rv3028.c @@ -858,16 +858,17 @@ static const struct regmap_config regmap_config = { static u8 rv3028_set_trickle_charger(struct rv3028_data *rv3028, struct i2c_client *client) { - int ret, val_old, val; + int ret, val_old, val, val_mask; u32 ohms, chargeable; + u32 bsm; ret = regmap_read(rv3028->regmap, RV3028_BACKUP, &val_old); if (ret < 0) return ret; /* mask out only trickle charger bits */ - val_old = val_old & (RV3028_BACKUP_TCE | RV3028_BACKUP_TCR_MASK); - val = val_old; + val_mask = RV3028_BACKUP_TCE | RV3028_BACKUP_TCR_MASK; + val = val_old & val_mask; /* setup trickle charger */ if (!device_property_read_u32(&client->dev, "trickle-resistor-ohms", @@ -902,10 +903,21 @@ static u8 rv3028_set_trickle_charger(struct rv3028_data *rv3028, } } + /* setup backup switchover mode */ + if (!device_property_read_u32(&client->dev, + "backup-switchover-mode", + &bsm)) { + if (bsm <= 3) { + val_mask |= RV3028_BACKUP_BSM; + val |= (u8)(bsm << 2); + } else { + dev_warn(&client->dev, "invalid backup switchover mode value\n"); + } + } + /* only update EEPROM if changes are necessary */ - if (val_old != val) { - ret = rv3028_update_cfg(rv3028, RV3028_BACKUP, RV3028_BACKUP_TCE | - RV3028_BACKUP_TCR_MASK, val); + if ((val_old & val_mask) != val) { + ret = rv3028_update_cfg(rv3028, RV3028_BACKUP, val_mask, val); if (ret) return ret; } diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 82379721740480..f1ccfea2184bc0 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -873,6 +873,18 @@ config SPI_RB4XX help SPI controller driver for the Mikrotik RB4xx series boards. +config SPI_RP2040_GPIO_BRIDGE + tristate "Raspberry Pi RP2040 GPIO Bridge" + depends on I2C && SPI && GPIOLIB + help + Support for the Raspberry Pi RP2040 GPIO bridge. + + This driver provides support for the Raspberry Pi PR2040 GPIO bridge. + It can be used as a GPIO expander and a Tx-only SPI master. + + Optionally, this driver is able to take advantage of Raspberry Pi RP1 + GPIOs to achieve faster than I2C data transfer rates. + config SPI_RPCIF tristate "Renesas RPC-IF SPI driver" depends on RENESAS_RPCIF diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index a9b1bc259b68d1..274861550d487f 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -119,6 +119,7 @@ obj-$(CONFIG_SPI_ROCKCHIP) += spi-rockchip.o obj-$(CONFIG_SPI_ROCKCHIP_SFC) += spi-rockchip-sfc.o obj-$(CONFIG_SPI_RB4XX) += spi-rb4xx.o obj-$(CONFIG_MACH_REALTEK_RTL) += spi-realtek-rtl.o +obj-$(CONFIG_SPI_RP2040_GPIO_BRIDGE) += spi-rp2040-gpio-bridge.o obj-$(CONFIG_SPI_RPCIF) += spi-rpc-if.o obj-$(CONFIG_SPI_RSPI) += spi-rspi.o obj-$(CONFIG_SPI_RZV2M_CSI) += spi-rzv2m-csi.o diff --git a/drivers/spi/spi-bcm2835.c b/drivers/spi/spi-bcm2835.c index e1b9b12357877f..1489caefea5cf0 100644 --- a/drivers/spi/spi-bcm2835.c +++ b/drivers/spi/spi-bcm2835.c @@ -119,6 +119,7 @@ MODULE_PARM_DESC(polling_limit_us, */ struct bcm2835_spi { void __iomem *regs; + phys_addr_t phys_addr; struct clk *clk; struct gpio_desc *cs_gpio; unsigned long clk_hz; @@ -891,19 +892,8 @@ static int bcm2835_dma_init(struct spi_controller *ctlr, struct device *dev, struct bcm2835_spi *bs) { struct dma_slave_config slave_config; - const __be32 *addr; - dma_addr_t dma_reg_base; int ret; - /* base address in dma-space */ - addr = of_get_address(ctlr->dev.of_node, 0, NULL, NULL); - if (!addr) { - dev_err(dev, "could not get DMA-register address - not using dma mode\n"); - /* Fall back to interrupt mode */ - return 0; - } - dma_reg_base = be32_to_cpup(addr); - /* get tx/rx dma */ ctlr->dma_tx = dma_request_chan(dev, "tx"); if (IS_ERR(ctlr->dma_tx)) { @@ -925,7 +915,7 @@ static int bcm2835_dma_init(struct spi_controller *ctlr, struct device *dev, * or, in case of an RX-only transfer, cyclically copies from the zero * page to the FIFO using a preallocated, reusable descriptor. */ - slave_config.dst_addr = (u32)(dma_reg_base + BCM2835_SPI_FIFO); + slave_config.dst_addr = bs->phys_addr + BCM2835_SPI_FIFO; slave_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; ret = dmaengine_slave_config(ctlr->dma_tx, &slave_config); @@ -964,9 +954,9 @@ static int bcm2835_dma_init(struct spi_controller *ctlr, struct device *dev, * RX FIFO or, in case of a TX-only transfer, cyclically writes a * precalculated value to the CS register to clear the RX FIFO. */ - slave_config.src_addr = (u32)(dma_reg_base + BCM2835_SPI_FIFO); + slave_config.src_addr = bs->phys_addr + BCM2835_SPI_FIFO; slave_config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; - slave_config.dst_addr = (u32)(dma_reg_base + BCM2835_SPI_CS); + slave_config.dst_addr = bs->phys_addr + BCM2835_SPI_CS; slave_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; ret = dmaengine_slave_config(ctlr->dma_rx, &slave_config); @@ -1059,6 +1049,16 @@ static int bcm2835_spi_transfer_one(struct spi_controller *ctlr, unsigned long hz_per_byte, byte_limit; u32 cs = target->prepare_cs; + if (unlikely(!tfr->len)) { + static int warned; + + if (!warned) + dev_warn(&spi->dev, + "zero-length SPI transfer ignored\n"); + warned = 1; + return 0; + } + /* set clock */ spi_hz = tfr->speed_hz; @@ -1225,6 +1225,7 @@ static int bcm2835_spi_setup(struct spi_device *spi) struct bcm2835_spi *bs = spi_controller_get_devdata(ctlr); struct bcm2835_spidev *target = spi_get_ctldata(spi); struct gpiod_lookup_table *lookup __free(kfree) = NULL; + int len; int ret; u32 cs; @@ -1290,6 +1291,10 @@ static int bcm2835_spi_setup(struct spi_device *spi) goto err_cleanup; } + /* Skip forced CS conversion if controller has an empty cs-gpios property */ + if (of_find_property(ctlr->dev.of_node, "cs-gpios", &len) && len == 0) + return 0; + /* * TODO: The code below is a slightly better alternative to the utter * abuse of the GPIO API that I found here before. It creates a @@ -1336,6 +1341,7 @@ static int bcm2835_spi_probe(struct platform_device *pdev) { struct spi_controller *ctlr; struct bcm2835_spi *bs; + struct resource *iomem; int err; ctlr = devm_spi_alloc_host(&pdev->dev, sizeof(*bs)); @@ -1359,10 +1365,11 @@ static int bcm2835_spi_probe(struct platform_device *pdev) bs = spi_controller_get_devdata(ctlr); bs->ctlr = ctlr; - bs->regs = devm_platform_ioremap_resource(pdev, 0); + bs->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &iomem); if (IS_ERR(bs->regs)) return PTR_ERR(bs->regs); + bs->phys_addr = iomem->start; bs->clk = devm_clk_get_enabled(&pdev->dev, NULL); if (IS_ERR(bs->clk)) return dev_err_probe(&pdev->dev, PTR_ERR(bs->clk), diff --git a/drivers/spi/spi-dw-core.c b/drivers/spi/spi-dw-core.c index 431788dd848cea..5ee7f1fd11df88 100644 --- a/drivers/spi/spi-dw-core.c +++ b/drivers/spi/spi-dw-core.c @@ -100,7 +100,8 @@ void dw_spi_set_cs(struct spi_device *spi, bool enable) * support active-high or active-low CS level. */ if (cs_high == enable) - dw_writel(dws, DW_SPI_SER, BIT(spi_get_chipselect(spi, 0))); + dw_writel(dws, DW_SPI_SER, + BIT(spi_get_csgpiod(spi, 0) ? 0 : spi_get_chipselect(spi, 0))); else dw_writel(dws, DW_SPI_SER, 0); } @@ -201,7 +202,18 @@ int dw_spi_check_status(struct dw_spi *dws, bool raw) /* Generically handle the erroneous situation */ if (ret) { - dw_spi_reset_chip(dws); + /* + * Forcibly halting the controller can cause DMA to hang. + * Defer to dw_spi_handle_err outside of interrupt context + * and mask further interrupts for the current transfer. + */ + if (dws->dma_mapped) { + dw_spi_mask_intr(dws, 0xff); + dw_readl(dws, DW_SPI_ICR); + } else { + dw_spi_reset_chip(dws); + } + if (dws->host->cur_msg) dws->host->cur_msg->status = ret; } @@ -210,6 +222,32 @@ int dw_spi_check_status(struct dw_spi *dws, bool raw) } EXPORT_SYMBOL_NS_GPL(dw_spi_check_status, SPI_DW_CORE); +static inline bool dw_spi_ctlr_busy(struct dw_spi *dws) +{ + return dw_readl(dws, DW_SPI_SR) & DW_SPI_SR_BUSY; +} + +static enum hrtimer_restart dw_spi_hrtimer_handler(struct hrtimer *hr) +{ + struct dw_spi *dws = container_of(hr, struct dw_spi, hrtimer); + + if (!dw_spi_ctlr_busy(dws)) { + spi_finalize_current_transfer(dws->host); + return HRTIMER_NORESTART; + } + + if (!dws->idle_wait_retries) { + dev_err(&dws->host->dev, "controller stuck at busy\n"); + spi_finalize_current_transfer(dws->host); + return HRTIMER_NORESTART; + } + + dws->idle_wait_retries--; + hrtimer_forward_now(hr, dws->idle_wait_interval); + + return HRTIMER_RESTART; +} + static irqreturn_t dw_spi_transfer_handler(struct dw_spi *dws) { u16 irq_status = dw_readl(dws, DW_SPI_ISR); @@ -226,12 +264,32 @@ static irqreturn_t dw_spi_transfer_handler(struct dw_spi *dws) * final stage of the transfer. By doing so we'll get the next IRQ * right when the leftover incoming data is received. */ - dw_reader(dws); - if (!dws->rx_len) { - dw_spi_mask_intr(dws, 0xff); - spi_finalize_current_transfer(dws->host); - } else if (dws->rx_len <= dw_readl(dws, DW_SPI_RXFTLR)) { - dw_writel(dws, DW_SPI_RXFTLR, dws->rx_len - 1); + if (dws->rx_len) { + dw_reader(dws); + if (!dws->rx_len) { + dw_spi_mask_intr(dws, 0xff); + spi_finalize_current_transfer(dws->host); + } else if (dws->rx_len <= dw_readl(dws, DW_SPI_RXFTLR)) { + dw_writel(dws, DW_SPI_RXFTLR, dws->rx_len - 1); + } + } else if (!dws->tx_len) { + dw_spi_mask_intr(dws, DW_SPI_INT_TXEI); + if (dw_spi_ctlr_busy(dws)) { + ktime_t period = ns_to_ktime(DIV_ROUND_UP(NSEC_PER_SEC, dws->current_freq)); + + /* + * Make the initial wait an underestimate of how long the transfer + * should take, then poll rapidly to reduce the delay + */ + hrtimer_start(&dws->hrtimer, + period * (8 * dws->n_bytes - 1), + HRTIMER_MODE_REL); + dws->idle_wait_retries = 10; + dws->idle_wait_interval = period; + } else { + spi_finalize_current_transfer(dws->host); + } + return IRQ_HANDLED; } /* @@ -241,8 +299,12 @@ static irqreturn_t dw_spi_transfer_handler(struct dw_spi *dws) */ if (irq_status & DW_SPI_INT_TXEI) { dw_writer(dws); - if (!dws->tx_len) - dw_spi_mask_intr(dws, DW_SPI_INT_TXEI); + if (!dws->tx_len) { + if (dws->rx_len) + dw_spi_mask_intr(dws, DW_SPI_INT_TXEI); + else + dw_writel(dws, DW_SPI_TXFTLR, 0); + } } return IRQ_HANDLED; @@ -337,7 +399,7 @@ void dw_spi_update_config(struct dw_spi *dws, struct spi_device *spi, dw_writel(dws, DW_SPI_CTRLR1, cfg->ndf ? cfg->ndf - 1 : 0); /* Note DW APB SSI clock divider doesn't support odd numbers */ - clk_div = (DIV_ROUND_UP(dws->max_freq, cfg->freq) + 1) & 0xfffe; + clk_div = min(DIV_ROUND_UP(dws->max_freq, cfg->freq) + 1, 0xfffe) & 0xfffe; speed_hz = dws->max_freq / clk_div; if (dws->current_freq != speed_hz) { @@ -363,15 +425,18 @@ static void dw_spi_irq_setup(struct dw_spi *dws) * will be adjusted at the final stage of the IRQ-based SPI transfer * execution so not to lose the leftover of the incoming data. */ - level = min_t(unsigned int, dws->fifo_len / 2, dws->tx_len); + level = min_t(unsigned int, dws->fifo_len / 2, dws->tx_len ? dws->tx_len : dws->rx_len); dw_writel(dws, DW_SPI_TXFTLR, level); dw_writel(dws, DW_SPI_RXFTLR, level - 1); dws->transfer_handler = dw_spi_transfer_handler; - imask = DW_SPI_INT_TXEI | DW_SPI_INT_TXOI | - DW_SPI_INT_RXUI | DW_SPI_INT_RXOI | DW_SPI_INT_RXFI; + imask = DW_SPI_INT_TXEI | DW_SPI_INT_TXOI; + if (dws->rx_len) + imask |= DW_SPI_INT_RXUI | DW_SPI_INT_RXOI | DW_SPI_INT_RXFI; dw_spi_umask_intr(dws, imask); + if (!dws->tx_len) + dw_writel(dws, DW_SPI_DR, 0); } /* @@ -394,18 +459,23 @@ static int dw_spi_poll_transfer(struct dw_spi *dws, delay.unit = SPI_DELAY_UNIT_SCK; nbits = dws->n_bytes * BITS_PER_BYTE; + if (!dws->tx_len) + dw_writel(dws, DW_SPI_DR, 0); + do { - dw_writer(dws); + if (dws->tx_len) + dw_writer(dws); delay.value = nbits * (dws->rx_len - dws->tx_len); spi_delay_exec(&delay, transfer); - dw_reader(dws); + if (dws->rx_len) + dw_reader(dws); ret = dw_spi_check_status(dws, true); if (ret) return ret; - } while (dws->rx_len); + } while (dws->rx_len || dws->tx_len || dw_spi_ctlr_busy(dws)); return 0; } @@ -420,6 +490,7 @@ static int dw_spi_transfer_one(struct spi_controller *host, .dfs = transfer->bits_per_word, .freq = transfer->speed_hz, }; + int buswidth; int ret; dws->dma_mapped = 0; @@ -429,6 +500,23 @@ static int dw_spi_transfer_one(struct spi_controller *host, dws->rx = transfer->rx_buf; dws->rx_len = dws->tx_len; + if (!dws->rx) { + dws->rx_len = 0; + cfg.tmode = DW_SPI_CTRLR0_TMOD_TO; + } + + if (!dws->rx) { + dws->rx_len = 0; + cfg.tmode = DW_SPI_CTRLR0_TMOD_TO; + } + if (!dws->tx) { + dws->tx_len = 0; + cfg.tmode = DW_SPI_CTRLR0_TMOD_RO; + cfg.ndf = dws->rx_len; + } + buswidth = transfer->rx_buf ? transfer->rx_nbits : + (transfer->tx_buf ? transfer->tx_nbits : 1); + /* Ensure the data above is visible for all CPUs */ smp_mb(); @@ -607,11 +695,6 @@ static int dw_spi_write_then_read(struct dw_spi *dws, struct spi_device *spi) return 0; } -static inline bool dw_spi_ctlr_busy(struct dw_spi *dws) -{ - return dw_readl(dws, DW_SPI_SR) & DW_SPI_SR_BUSY; -} - static int dw_spi_wait_mem_op_done(struct dw_spi *dws) { int retry = DW_SPI_WAIT_RETRIES; @@ -959,10 +1042,12 @@ int dw_spi_add_host(struct device *dev, struct dw_spi *dws) dev_warn(dev, "DMA init failed\n"); } else { host->can_dma = dws->dma_ops->can_dma; - host->flags |= SPI_CONTROLLER_MUST_TX; } } + hrtimer_init(&dws->hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); + dws->hrtimer.function = dw_spi_hrtimer_handler; + ret = spi_register_controller(host); if (ret) { dev_err_probe(dev, ret, "problem registering spi host\n"); @@ -988,6 +1073,7 @@ void dw_spi_remove_host(struct dw_spi *dws) { dw_spi_debugfs_remove(dws); + hrtimer_cancel(&dws->hrtimer); spi_unregister_controller(dws->host); if (dws->dma_ops && dws->dma_ops->dma_exit) diff --git a/drivers/spi/spi-dw-dma.c b/drivers/spi/spi-dw-dma.c index f4c209e5f52baa..41b2fec8afae6e 100644 --- a/drivers/spi/spi-dw-dma.c +++ b/drivers/spi/spi-dw-dma.c @@ -6,6 +6,7 @@ */ #include <linux/completion.h> +#include <linux/delay.h> #include <linux/dma-mapping.h> #include <linux/dmaengine.h> #include <linux/irqreturn.h> @@ -303,6 +304,12 @@ static int dw_spi_dma_wait_tx_done(struct dw_spi *dws, return -EIO; } + if (!xfer->rx_buf) { + delay.value = dws->n_bytes * BITS_PER_BYTE; + while (dw_readl(dws, DW_SPI_SR) & DW_SPI_SR_BUSY) + spi_delay_exec(&delay, xfer); + } + return 0; } @@ -329,7 +336,6 @@ static int dw_spi_dma_config_tx(struct dw_spi *dws) txconf.direction = DMA_MEM_TO_DEV; txconf.dst_addr = dws->dma_addr; txconf.dst_maxburst = dws->txburst; - txconf.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; txconf.dst_addr_width = dw_spi_dma_convert_width(dws->n_bytes); txconf.device_fc = false; @@ -430,7 +436,6 @@ static int dw_spi_dma_config_rx(struct dw_spi *dws) rxconf.direction = DMA_DEV_TO_MEM; rxconf.src_addr = dws->dma_addr; rxconf.src_maxburst = dws->rxburst; - rxconf.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; rxconf.src_addr_width = dw_spi_dma_convert_width(dws->n_bytes); rxconf.device_fc = false; @@ -470,13 +475,12 @@ static int dw_spi_dma_setup(struct dw_spi *dws, struct spi_transfer *xfer) u16 imr, dma_ctrl; int ret; - if (!xfer->tx_buf) - return -EINVAL; - /* Setup DMA channels */ - ret = dw_spi_dma_config_tx(dws); - if (ret) - return ret; + if (xfer->tx_buf) { + ret = dw_spi_dma_config_tx(dws); + if (ret) + return ret; + } if (xfer->rx_buf) { ret = dw_spi_dma_config_rx(dws); @@ -485,13 +489,17 @@ static int dw_spi_dma_setup(struct dw_spi *dws, struct spi_transfer *xfer) } /* Set the DMA handshaking interface */ - dma_ctrl = DW_SPI_DMACR_TDMAE; + dma_ctrl = 0; + if (xfer->tx_buf) + dma_ctrl |= DW_SPI_DMACR_TDMAE; if (xfer->rx_buf) dma_ctrl |= DW_SPI_DMACR_RDMAE; dw_writel(dws, DW_SPI_DMACR, dma_ctrl); /* Set the interrupt mask */ - imr = DW_SPI_INT_TXOI; + imr = 0; + if (xfer->tx_buf) + imr |= DW_SPI_INT_TXOI; if (xfer->rx_buf) imr |= DW_SPI_INT_RXUI | DW_SPI_INT_RXOI; dw_spi_umask_intr(dws, imr); @@ -508,15 +516,16 @@ static int dw_spi_dma_transfer_all(struct dw_spi *dws, { int ret; - /* Submit the DMA Tx transfer */ - ret = dw_spi_dma_submit_tx(dws, xfer->tx_sg.sgl, xfer->tx_sg.nents); - if (ret) - goto err_clear_dmac; + /* Submit the DMA Tx transfer if required */ + if (xfer->tx_buf) { + ret = dw_spi_dma_submit_tx(dws, xfer->tx_sg.sgl, xfer->tx_sg.nents); + if (ret) + goto err_clear_dmac; + } /* Submit the DMA Rx transfer if required */ if (xfer->rx_buf) { - ret = dw_spi_dma_submit_rx(dws, xfer->rx_sg.sgl, - xfer->rx_sg.nents); + ret = dw_spi_dma_submit_rx(dws, xfer->rx_sg.sgl, xfer->rx_sg.nents); if (ret) goto err_clear_dmac; @@ -524,7 +533,15 @@ static int dw_spi_dma_transfer_all(struct dw_spi *dws, dma_async_issue_pending(dws->rxchan); } - dma_async_issue_pending(dws->txchan); + if (xfer->tx_buf) { + dma_async_issue_pending(dws->txchan); + } else { + /* Pause to allow DMA channel to fetch RX descriptor */ + usleep_range(5, 10); + + /* Write something to the TX FIFO to start the transfer */ + dw_writel(dws, DW_SPI_DR, 0); + } ret = dw_spi_dma_wait(dws, xfer->len, xfer->effective_speed_hz); diff --git a/drivers/spi/spi-dw-mmio.c b/drivers/spi/spi-dw-mmio.c index 819907e332c4b0..f5f24af5c4fc8a 100644 --- a/drivers/spi/spi-dw-mmio.c +++ b/drivers/spi/spi-dw-mmio.c @@ -20,6 +20,7 @@ #include <linux/property.h> #include <linux/regmap.h> #include <linux/reset.h> +#include <linux/interrupt.h> #include "spi-dw.h" @@ -341,8 +342,11 @@ static int dw_spi_mmio_probe(struct platform_device *pdev) dws->paddr = mem->start; dws->irq = platform_get_irq(pdev, 0); - if (dws->irq < 0) - return dws->irq; /* -ENXIO */ + if (dws->irq < 0) { + if (dws->irq != -ENXIO) + return dws->irq; /* -ENXIO */ + dws->irq = IRQ_NOTCONNECTED; + } dwsmmio->clk = devm_clk_get_enabled(&pdev->dev, NULL); if (IS_ERR(dwsmmio->clk)) diff --git a/drivers/spi/spi-dw.h b/drivers/spi/spi-dw.h index fc267c6437ae09..92e54a51fc46d8 100644 --- a/drivers/spi/spi-dw.h +++ b/drivers/spi/spi-dw.h @@ -180,6 +180,9 @@ struct dw_spi { u32 current_freq; /* frequency in hz */ u32 cur_rx_sample_dly; u32 def_rx_sample_dly_ns; + struct hrtimer hrtimer; + ktime_t idle_wait_interval; + int idle_wait_retries; /* Custom memory operations */ struct spi_controller_mem_ops mem_ops; diff --git a/drivers/spi/spi-gpio.c b/drivers/spi/spi-gpio.c index 4f192e013cd6f2..139641fd2329c2 100644 --- a/drivers/spi/spi-gpio.c +++ b/drivers/spi/spi-gpio.c @@ -11,6 +11,7 @@ #include <linux/module.h> #include <linux/platform_device.h> #include <linux/property.h> +#include <linux/delay.h> #include <linux/spi/spi.h> #include <linux/spi/spi_bitbang.h> @@ -35,6 +36,8 @@ struct spi_gpio { struct gpio_desc *miso; struct gpio_desc *mosi; struct gpio_desc **cs_gpios; + bool sck_idle_input; + bool cs_dont_invert; }; /*----------------------------------------------------------------------*/ @@ -108,12 +111,18 @@ static inline int getmiso(const struct spi_device *spi) } /* - * NOTE: this clocks "as fast as we can". It "should" be a function of the - * requested device clock. Software overhead means we usually have trouble - * reaching even one Mbit/sec (except when we can inline bitops), so for now - * we'll just assume we never need additional per-bit slowdowns. + * Generic bit-banged GPIO SPI might free-run at something in the range + * 1Mbps ~ 10Mbps (depending on the platform), and some SPI devices may + * need to be clocked at a lower rate. ndelay() is often implemented by + * udelay() with rounding up, so do the delay only for nsecs >= 500 + * (<= 1Mbps). The conditional test adds a small overhead. */ -#define spidelay(nsecs) do {} while (0) + +static inline void spidelay(unsigned long nsecs) +{ + if (nsecs >= 500) + ndelay(nsecs); +} #include "spi-bitbang-txrx.h" @@ -224,16 +233,29 @@ static void spi_gpio_chipselect(struct spi_device *spi, int is_active) struct spi_gpio *spi_gpio = spi_to_spi_gpio(spi); /* set initial clock line level */ - if (is_active) - gpiod_set_value_cansleep(spi_gpio->sck, spi->mode & SPI_CPOL); + if (is_active) { + if (spi_gpio->sck_idle_input) + gpiod_direction_output(spi_gpio->sck, spi->mode & SPI_CPOL); + else + gpiod_set_value_cansleep(spi_gpio->sck, spi->mode & SPI_CPOL); + } - /* Drive chip select line, if we have one */ + /* + * Drive chip select line, if we have one. + * SPI chip selects are normally active-low, but when + * cs_dont_invert is set, we assume their polarity is + * controlled by the GPIO, and write '1' to assert. + */ if (spi_gpio->cs_gpios) { struct gpio_desc *cs = spi_gpio->cs_gpios[spi_get_chipselect(spi, 0)]; + int val = ((spi->mode & SPI_CS_HIGH) || spi_gpio->cs_dont_invert) ? + is_active : !is_active; - /* SPI chip selects are normally active-low */ - gpiod_set_value_cansleep(cs, (spi->mode & SPI_CS_HIGH) ? is_active : !is_active); + gpiod_set_value_cansleep(cs, val); } + + if (spi_gpio->sck_idle_input && !is_active) + gpiod_direction_input(spi_gpio->sck); } static void spi_gpio_set_mosi_idle(struct spi_device *spi) @@ -253,11 +275,14 @@ static int spi_gpio_setup(struct spi_device *spi) /* * The CS GPIOs have already been * initialized from the descriptor lookup. + * Here we set them to the non-asserted state. */ if (spi_gpio->cs_gpios) { cs = spi_gpio->cs_gpios[spi_get_chipselect(spi, 0)]; if (!spi->controller_state && cs) { - ret = gpiod_direction_output(cs, !(spi->mode & SPI_CS_HIGH)); + ret = gpiod_direction_output(cs, + !((spi->mode & SPI_CS_HIGH) || + spi_gpio->cs_dont_invert)); if (ret) return ret; } @@ -329,17 +354,48 @@ static int spi_gpio_request(struct device *dev, struct spi_gpio *spi_gpio) if (IS_ERR(spi_gpio->miso)) return PTR_ERR(spi_gpio->miso); + spi_gpio->sck_idle_input = device_property_read_bool(dev, "sck-idle-input"); spi_gpio->sck = devm_gpiod_get(dev, "sck", GPIOD_OUT_LOW); return PTR_ERR_OR_ZERO(spi_gpio->sck); } +/* + * In order to implement "sck-idle-input" (which requires SCK + * direction and CS level to be switched in a particular order), + * we need to control GPIO chip selects from within this driver. + */ + +static int spi_gpio_probe_get_cs_gpios(struct device *dev, + struct spi_controller *master, + bool gpio_defines_polarity) +{ + int i; + struct spi_gpio *spi_gpio = spi_controller_get_devdata(master); + + spi_gpio->cs_dont_invert = gpio_defines_polarity; + spi_gpio->cs_gpios = devm_kcalloc(dev, master->num_chipselect, + sizeof(*spi_gpio->cs_gpios), + GFP_KERNEL); + if (!spi_gpio->cs_gpios) + return -ENOMEM; + + for (i = 0; i < master->num_chipselect; i++) { + spi_gpio->cs_gpios[i] = + devm_gpiod_get_index(dev, "cs", i, + gpio_defines_polarity ? + GPIOD_OUT_LOW : GPIOD_OUT_HIGH); + if (IS_ERR(spi_gpio->cs_gpios[i])) + return PTR_ERR(spi_gpio->cs_gpios[i]); + } + + return 0; +} + static int spi_gpio_probe_pdata(struct platform_device *pdev, struct spi_controller *host) { struct device *dev = &pdev->dev; struct spi_gpio_platform_data *pdata = dev_get_platdata(dev); - struct spi_gpio *spi_gpio = spi_controller_get_devdata(host); - int i; #ifdef GENERIC_BITBANG if (!pdata || !pdata->num_chipselect) @@ -351,20 +407,7 @@ static int spi_gpio_probe_pdata(struct platform_device *pdev, */ host->num_chipselect = pdata->num_chipselect ?: 1; - spi_gpio->cs_gpios = devm_kcalloc(dev, host->num_chipselect, - sizeof(*spi_gpio->cs_gpios), - GFP_KERNEL); - if (!spi_gpio->cs_gpios) - return -ENOMEM; - - for (i = 0; i < host->num_chipselect; i++) { - spi_gpio->cs_gpios[i] = devm_gpiod_get_index(dev, "cs", i, - GPIOD_OUT_HIGH); - if (IS_ERR(spi_gpio->cs_gpios[i])) - return PTR_ERR(spi_gpio->cs_gpios[i]); - } - - return 0; + return spi_gpio_probe_get_cs_gpios(dev, host, false); } static int spi_gpio_probe(struct platform_device *pdev) diff --git a/drivers/spi/spi-rp2040-gpio-bridge.c b/drivers/spi/spi-rp2040-gpio-bridge.c new file mode 100644 index 00000000000000..304e34f1dbf227 --- /dev/null +++ b/drivers/spi/spi-rp2040-gpio-bridge.c @@ -0,0 +1,1249 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * RP2040 GPIO Bridge + * + * Copyright (C) 2023, 2024, Raspberry Pi Ltd + */ + +#include <crypto/hash.h> +#include <linux/debugfs.h> +#include <linux/crypto.h> +#include <linux/delay.h> +#include <linux/firmware.h> +#include <linux/gpio/driver.h> +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/minmax.h> +#include <linux/of_address.h> +#include <linux/pm_runtime.h> +#include <linux/spi/spi.h> +#include <linux/stddef.h> +#include <linux/types.h> + +#define MODULE_NAME "rp2040-gpio-bridge" + +#define I2C_RETRIES 4U + +#define ONE_KIB 1024U +#define MD5_SUFFIX_SIZE 9U + +#define RP2040_GBDG_FLASH_BLOCK_SIZE (8U * ONE_KIB) +#define RP2040_GBDG_BLOCK_SIZE (RP2040_GBDG_FLASH_BLOCK_SIZE - MD5_SUFFIX_SIZE) + +/* + * 1MiB transfer size is an arbitrary limit + * Max value is 4173330 (using a single manifest) + */ +#define MAX_TRANSFER_SIZE (1024U * ONE_KIB) + +#define HALF_BUFFER (4U * ONE_KIB) + +#define STATUS_SIZE 4 +#define MD5_DIGEST_SIZE 16 +#define VERSION_SIZE 4 +#define ID_SIZE 8 +#define TOTAL_RD_HDR_SIZE \ + (STATUS_SIZE + MD5_DIGEST_SIZE + VERSION_SIZE + ID_SIZE) + +struct rp2040_gbdg_device_info { + u8 md5[MD5_DIGEST_SIZE]; + u64 id; + u32 version; + u32 status; +}; + +static_assert(sizeof(struct rp2040_gbdg_device_info) == TOTAL_RD_HDR_SIZE); + +#define MANIFEST_UNIT_SIZE 16 +static_assert(MD5_DIGEST_SIZE == MANIFEST_UNIT_SIZE); +#define MANIFEST_HEADER_UNITS 1 +#define MANIFEST_DATA_UNITS \ + DIV_ROUND_UP(MAX_TRANSFER_SIZE, RP2040_GBDG_BLOCK_SIZE) + +#define STATUS_BUSY 0x01 + +#define DIRECT_PREFIX 0x00 +#define DIRECT_CMD_CS 0x07 +#define DIRECT_CMD_EMIT 0x08 + +#define WRITE_DATA_PREFIX 0x80 +#define WRITE_DATA_PREFIX_SIZE 1 + +#define FIXED_SIZE_CMD_PREFIX 0x81 + +#define WRITE_DATA_UPPER_PREFIX 0x82 +#define WRITE_DATA_UPPER_PREFIX_SIZE 1 + +#define NUM_GPIO 24 + +enum rp2040_gbdg_fixed_size_commands { + /* 10-byte commands */ + CMD_SAVE_CACHE = 0x07, + CMD_SEND_RB = 0x08, + CMD_GPIO_ST_CL = 0x0b, + CMD_GPIO_OE = 0x0c, + CMD_DAT_RECV = 0x0d, + CMD_DAT_EMIT = 0x0e, + /* 18-byte commands */ + CMD_READ_CSUM = 0x11, + CMD_SEND_MANI = 0x13, +}; + +struct rp2040_gbdg { + struct spi_controller *controller; + + struct dentry *debugfs; + size_t transfer_progress; + + struct i2c_client *client; + struct crypto_shash *shash; + struct shash_desc *shash_desc; + + struct regulator *regulator; + + struct gpio_chip gc; + u32 gpio_requested; + u32 gpio_direction; + + bool fast_xfer_requires_i2c_lock; + struct gpio_descs *fast_xfer_gpios; + u32 fast_xfer_recv_gpio_base; + u8 fast_xfer_data_index; + u8 fast_xfer_clock_index; + void __iomem *gpio_base; + void __iomem *rio_base; + + bool bypass_cache; + + u8 buffer[2 + HALF_BUFFER]; + u8 manifest_prep[(MANIFEST_HEADER_UNITS + MANIFEST_DATA_UNITS) * + MANIFEST_UNIT_SIZE]; +}; + +static int rp2040_gbdg_gpio_dir_in(struct gpio_chip *gc, unsigned int offset); +static void rp2040_gbdg_gpio_set(struct gpio_chip *gc, unsigned int offset, + int value); +static int rp2040_gbdg_fast_xfer(struct rp2040_gbdg *priv_data, const u8 *data, + size_t len); + +static int rp2040_gbdg_rp1_calc_offsets(u8 gpio, size_t *bank_offset, + u8 *shift_offset) +{ + if (!bank_offset || !shift_offset || gpio >= 54) + return -EINVAL; + if (gpio < 28) { + *bank_offset = 0x0000; + *shift_offset = gpio; + } else if (gpio < 34) { + *bank_offset = 0x4000; + *shift_offset = gpio - 28; + } else { + *bank_offset = 0x8000; + *shift_offset = gpio - 34; + } + + return 0; +} + +static int rp2040_gbdg_calc_mux_offset(u8 gpio, size_t *offset) +{ + size_t bank_offset; + u8 shift_offset; + int ret; + + ret = rp2040_gbdg_rp1_calc_offsets(gpio, &bank_offset, &shift_offset); + if (ret) + return ret; + *offset = bank_offset + shift_offset * 8 + 0x4; + + return 0; +} + +static int rp2040_gbdg_rp1_read_mux(struct rp2040_gbdg *priv_data, u8 gpio, + u32 *data) +{ + size_t offset; + int ret; + + ret = rp2040_gbdg_calc_mux_offset(gpio, &offset); + if (ret) + return ret; + + *data = readl(priv_data->gpio_base + offset); + + return 0; +} + +static int rp2040_gbdg_rp1_write_mux(struct rp2040_gbdg *priv_data, u8 gpio, + u32 val) +{ + size_t offset; + int ret; + + ret = rp2040_gbdg_calc_mux_offset(gpio, &offset); + if (ret) + return ret; + + writel(val, priv_data->gpio_base + offset); + + return 0; +} + +static size_t rp2040_gbdg_max_transfer_size(struct spi_device *spi) +{ + return MAX_TRANSFER_SIZE; +} + +static int rp2040_gbdg_get_device_info(struct i2c_client *client, + struct rp2040_gbdg_device_info *info) +{ + u8 buf[TOTAL_RD_HDR_SIZE]; + u8 retries = I2C_RETRIES; + u8 *read_pos = buf; + size_t field_size; + int ret; + + do { + ret = i2c_master_recv(client, buf, sizeof(buf)); + if (!retries--) + break; + } while (ret == -ETIMEDOUT); + + if (ret != sizeof(buf)) + return ret < 0 ? ret : -EIO; + + field_size = sizeof_field(struct rp2040_gbdg_device_info, status); + memcpy(&info->status, read_pos, field_size); + read_pos += field_size; + + field_size = sizeof_field(struct rp2040_gbdg_device_info, md5); + memcpy(&info->md5, read_pos, field_size); + read_pos += field_size; + + field_size = sizeof_field(struct rp2040_gbdg_device_info, version); + memcpy(&info->version, read_pos, field_size); + read_pos += field_size; + + field_size = sizeof_field(struct rp2040_gbdg_device_info, id); + memcpy(&info->id, read_pos, field_size); + + return 0; +} + +static int rp2040_gbdg_poll_device_info(struct i2c_client *client, + struct rp2040_gbdg_device_info *info) +{ + struct rp2040_gbdg_device_info itnl; + int ret; + + itnl.status = STATUS_BUSY; + + while (itnl.status & STATUS_BUSY) { + ret = rp2040_gbdg_get_device_info(client, &itnl); + if (ret) + return ret; + } + memcpy(info, &itnl, sizeof(itnl)); + + return 0; +} + +static int rp2040_gbdg_get_buffer_hash(struct i2c_client *client, u8 *md5) +{ + struct rp2040_gbdg_device_info info; + int ret; + + ret = rp2040_gbdg_poll_device_info(client, &info); + if (ret) + return ret; + + memcpy(md5, info.md5, MD5_DIGEST_SIZE); + + return 0; +} + +static int rp2040_gbdg_wait_until_free(struct i2c_client *client, u8 *status) +{ + struct rp2040_gbdg_device_info info; + int ret; + + ret = rp2040_gbdg_poll_device_info(client, &info); + if (ret) + return ret; + + if (status) + *status = info.status; + + return 0; +} + +static int rp2040_gbdg_i2c_send(struct i2c_client *client, const u8 *buf, + size_t len) +{ + u8 retries = I2C_RETRIES; + int ret; + + ret = rp2040_gbdg_wait_until_free(client, NULL); + if (ret) { + dev_err(&client->dev, + "%s() rp2040_gbdg_wait_until_free failed\n", __func__); + return ret; + } + + do { + ret = i2c_master_send(client, buf, len); + if (!retries--) + break; + } while (ret == -ETIMEDOUT); + + if (ret != len) { + dev_err(&client->dev, "%s() i2c_master_send returned %d\n", + __func__, ret); + return ret < 0 ? ret : -EIO; + } + + return 0; +} + +static int rp2040_gbdg_10byte_cmd(struct i2c_client *client, u8 cmd, u32 addr, + u32 len) +{ + u8 buffer[10]; + + buffer[0] = FIXED_SIZE_CMD_PREFIX; + buffer[1] = cmd; + memcpy(&buffer[2], &addr, sizeof(addr)); + memcpy(&buffer[6], &len, sizeof(len)); + + return rp2040_gbdg_i2c_send(client, buffer, sizeof(buffer)); +} + +static int rp2040_gbdg_18byte_cmd(struct i2c_client *client, u8 cmd, + const u8 *digest) +{ + u8 buffer[18]; + + buffer[0] = FIXED_SIZE_CMD_PREFIX; + buffer[1] = cmd; + memcpy(&buffer[2], digest, MD5_DIGEST_SIZE); + + return rp2040_gbdg_i2c_send(client, buffer, sizeof(buffer)); +} + +static int rp2040_gbdg_block_hash(struct rp2040_gbdg *priv_data, const u8 *data, + size_t len, u8 *out) +{ + size_t remaining = RP2040_GBDG_BLOCK_SIZE; + size_t pad; + int ret; + + static const u8 padding[64] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + }; + + if (len > RP2040_GBDG_BLOCK_SIZE) { + return -EMSGSIZE; + } else if (len == RP2040_GBDG_BLOCK_SIZE) { + return crypto_shash_digest(priv_data->shash_desc, data, len, + out); + } else { + ret = crypto_shash_init(priv_data->shash_desc); + if (ret) + return ret; + + ret = crypto_shash_update(priv_data->shash_desc, data, len); + if (ret) + return ret; + remaining -= len; + + /* Pad up-to a 64-byte boundary, unless that takes us over. */ + pad = round_up(len, 64); + if (pad != len && pad < RP2040_GBDG_BLOCK_SIZE) { + ret = crypto_shash_update(priv_data->shash_desc, + padding, pad - len); + if (ret) + return ret; + remaining -= (pad - len); + } + + /* Pad up-to RP2040_GBDG_BLOCK_SIZE in, preferably, 64-byte chunks */ + while (remaining) { + pad = min_t(size_t, remaining, (size_t)64U); + ret = crypto_shash_update(priv_data->shash_desc, + padding, pad); + if (ret) + return ret; + remaining -= pad; + } + return crypto_shash_final(priv_data->shash_desc, out); + } +} + +static int rp2040_gbdg_set_remote_buffer_fast(struct rp2040_gbdg *priv_data, + const u8 *data, unsigned int len) +{ + struct i2c_client *client = priv_data->client; + int ret; + + if (len > RP2040_GBDG_BLOCK_SIZE) + return -EMSGSIZE; + if (!priv_data->fast_xfer_gpios) + return -EIO; + + ret = rp2040_gbdg_10byte_cmd(client, CMD_DAT_RECV, + priv_data->fast_xfer_recv_gpio_base, len); + if (ret) { + dev_err(&client->dev, "%s() failed to enter fast data mode\n", + __func__); + return ret; + } + + return rp2040_gbdg_fast_xfer(priv_data, data, len); +} + +static int rp2040_gbdg_set_remote_buffer_i2c(struct rp2040_gbdg *priv_data, + const u8 *data, unsigned int len) +{ + struct i2c_client *client = priv_data->client; + unsigned int write_len; + int ret; + + if (len > RP2040_GBDG_BLOCK_SIZE) + return -EMSGSIZE; + + priv_data->buffer[0] = WRITE_DATA_PREFIX; + write_len = min(len, HALF_BUFFER); + memcpy(&priv_data->buffer[1], data, write_len); + + ret = rp2040_gbdg_i2c_send(client, priv_data->buffer, write_len + 1); + if (ret) + return ret; + + len -= write_len; + data += write_len; + + if (!len) + return 0; + + priv_data->buffer[0] = WRITE_DATA_UPPER_PREFIX; + memcpy(&priv_data->buffer[1], data, len); + ret = rp2040_gbdg_i2c_send(client, priv_data->buffer, len + 1); + + return ret; +} + +static int rp2040_gbdg_set_remote_buffer(struct rp2040_gbdg *priv_data, + const u8 *data, unsigned int len) +{ + if (priv_data->fast_xfer_gpios) + return rp2040_gbdg_set_remote_buffer_fast(priv_data, data, len); + else + return rp2040_gbdg_set_remote_buffer_i2c(priv_data, data, len); +} + +/* Loads data by checksum if available or resorts to sending byte-by-byte */ +static int rp2040_gbdg_load_block_remote(struct rp2040_gbdg *priv_data, + const void *data, unsigned int len, + u8 *digest, bool persist) +{ + u8 ascii_digest[MD5_DIGEST_SIZE * 2 + 1] = { 0 }; + struct i2c_client *client = priv_data->client; + u8 remote_digest[MD5_DIGEST_SIZE]; + u8 local_digest[MD5_DIGEST_SIZE]; + int ret; + + if (len > RP2040_GBDG_BLOCK_SIZE) + return -EMSGSIZE; + + ret = rp2040_gbdg_block_hash(priv_data, data, len, local_digest); + if (ret) + return ret; + + if (digest) + memcpy(digest, local_digest, MD5_DIGEST_SIZE); + + /* Check if the RP2040 has the data already */ + ret = rp2040_gbdg_18byte_cmd(client, CMD_READ_CSUM, local_digest); + if (ret) + return ret; + + ret = rp2040_gbdg_get_buffer_hash(client, remote_digest); + if (ret) + return ret; + + if (memcmp(local_digest, remote_digest, MD5_DIGEST_SIZE)) { + bin2hex(ascii_digest, local_digest, MD5_DIGEST_SIZE); + dev_info(&client->dev, "%s() device missing data: %s\n", + __func__, ascii_digest); + /* + * N.B. We're fine to send (the potentially shorter) transfer->len + * number of bytes here as the RP2040 will pad with 0xFF up to buffer + * size once we stop sending. + */ + ret = rp2040_gbdg_set_remote_buffer(priv_data, data, len); + if (ret) + return ret; + + /* Make sure the data actually arrived. */ + ret = rp2040_gbdg_get_buffer_hash(client, remote_digest); + if (memcmp(local_digest, remote_digest, MD5_DIGEST_SIZE)) { + dev_err(&priv_data->client->dev, + "%s() unable to send data to device\n", + __func__); + return -EREMOTEIO; + } + + if (persist) { + dev_info(&client->dev, + "%s() sent missing data to device, saving\n", + __func__); + ret = rp2040_gbdg_10byte_cmd(client, CMD_SAVE_CACHE, 0, + 0); + if (ret) + return ret; + } + } + + return 0; +} + +static int rp2040_gbdg_transfer_block(struct rp2040_gbdg *priv_data, + const void *data, unsigned int len) +{ + struct i2c_client *client = priv_data->client; + int ret; + + if (len > RP2040_GBDG_BLOCK_SIZE) + return -EMSGSIZE; + + ret = rp2040_gbdg_load_block_remote(priv_data, data, len, NULL, true); + if (ret) + return ret; + + /* Remote rambuffer now has correct contents, send it */ + ret = rp2040_gbdg_10byte_cmd(client, CMD_SEND_RB, 0, len); + if (ret) + return ret; + + /* + * Wait for data to have actually completed sending as we may be de-asserting CS too quickly + * otherwise. + */ + ret = rp2040_gbdg_wait_until_free(client, NULL); + if (ret) + return ret; + + return 0; +} + +static int rp2040_gbdg_transfer_manifest(struct rp2040_gbdg *priv_data, + const u8 *data, unsigned int len) +{ + struct i2c_client *client = priv_data->client; + static const char magic[] = "DATA_MANFST"; + unsigned int remaining = len; + const u32 data_length = len; + u8 digest[MD5_DIGEST_SIZE]; + u8 *digest_write_pos; + u8 status; + int ret; + + memcpy(priv_data->manifest_prep, magic, sizeof(magic)); + memcpy(priv_data->manifest_prep + sizeof(magic), &data_length, + sizeof(data_length)); + digest_write_pos = + priv_data->manifest_prep + sizeof(magic) + sizeof(data_length); + + while (remaining) { + unsigned int size = min(remaining, RP2040_GBDG_BLOCK_SIZE); + + ret = rp2040_gbdg_block_hash(priv_data, data, size, + digest_write_pos); + if (ret) + return ret; + + remaining -= size; + data += size; + digest_write_pos += MD5_DIGEST_SIZE; + } + + ret = rp2040_gbdg_load_block_remote( + priv_data, priv_data->manifest_prep, + digest_write_pos - priv_data->manifest_prep, digest, true); + if (ret) + return ret; + + dev_info(&client->dev, "%s() issue CMD_SEND_MANI\n", __func__); + ret = rp2040_gbdg_18byte_cmd(client, CMD_SEND_MANI, digest); + if (ret) + return ret; + + ret = rp2040_gbdg_wait_until_free(client, &status); + if (ret) + return ret; + + dev_info(&client->dev, "%s() SEND_MANI response: %02x\n", __func__, + status); + + return status; +} + +/* Precondition: correctly initialised fast_xfer_*, gpio_base, rio_base */ +static int rp2040_gbdg_fast_xfer(struct rp2040_gbdg *priv_data, const u8 *data, + size_t len) +{ + struct i2c_client *client = priv_data->client; + void __iomem *clock_toggle; + void __iomem *data_set; + size_t clock_bank; + size_t data_bank; + u8 clock_offset; + u8 data_offset; + u32 clock_mux; + u32 data_mux; + + if (priv_data->fast_xfer_requires_i2c_lock) + i2c_lock_bus(client->adapter, I2C_LOCK_ROOT_ADAPTER); + + rp2040_gbdg_rp1_read_mux(priv_data, priv_data->fast_xfer_data_index, + &data_mux); + rp2040_gbdg_rp1_read_mux(priv_data, priv_data->fast_xfer_clock_index, + &clock_mux); + + gpiod_direction_output(priv_data->fast_xfer_gpios->desc[0], 1); + + rp2040_gbdg_rp1_calc_offsets(priv_data->fast_xfer_data_index, + &data_bank, &data_offset); + rp2040_gbdg_rp1_calc_offsets(priv_data->fast_xfer_clock_index, + &clock_bank, &clock_offset); + + data_set = priv_data->rio_base + data_bank + 0x2000; /* SET offset */ + clock_toggle = + priv_data->rio_base + clock_bank + 0x1000; /* XOR offset */ + + while (len--) { + /* MSB first ordering */ + u32 d = ~(*data++) << 4U; + /* + * Clock out each bit of data, LSB first + * (DDR, achieves approx 5 Mbps) + */ + for (size_t i = 0; i < 8; i++) { + /* Branchless set/clr data */ + writel(1 << data_offset, + data_set + ((d <<= 1) & 0x1000) /* CLR offset */ + ); + + /* Toggle the clock */ + writel(1 << clock_offset, clock_toggle); + } + } + + rp2040_gbdg_rp1_write_mux(priv_data, priv_data->fast_xfer_data_index, + data_mux); + rp2040_gbdg_rp1_write_mux(priv_data, priv_data->fast_xfer_clock_index, + clock_mux); + + if (priv_data->fast_xfer_requires_i2c_lock) + i2c_unlock_bus(client->adapter, I2C_LOCK_ROOT_ADAPTER); + + return 0; +} + +static int rp2040_gbdg_transfer_bypass(struct rp2040_gbdg *priv_data, + const u8 *data, unsigned int length) +{ + int ret; + u8 *buf; + + if (priv_data->fast_xfer_gpios) { + ret = rp2040_gbdg_10byte_cmd( + priv_data->client, CMD_DAT_EMIT, + priv_data->fast_xfer_recv_gpio_base, length); + return ret ? ret : + rp2040_gbdg_fast_xfer(priv_data, data, length); + } + + buf = priv_data->buffer; + + while (length) { + unsigned int xfer = min(length, HALF_BUFFER); + + buf[0] = DIRECT_PREFIX; + buf[1] = DIRECT_CMD_EMIT; + memcpy(&buf[2], data, xfer); + ret = rp2040_gbdg_i2c_send(priv_data->client, buf, xfer + 2); + if (ret) + return ret; + length -= xfer; + data += xfer; + } + + return 0; +} + +static int rp2040_gbdg_transfer_cached(struct rp2040_gbdg *priv_data, + const u8 *data, unsigned int length) +{ + int ret; + + /* + * Caching mechanism divides data into '8KiB - 9' (8183 byte) + * 'RP2040_GBDG_BLOCK_SIZE' blocks. + * + * If there's a large amount of data to send, instead, attempt to make use + * of a manifest. + */ + if (length > (2 * RP2040_GBDG_BLOCK_SIZE)) { + if (!rp2040_gbdg_transfer_manifest(priv_data, data, length)) + return 0; + } + + priv_data->transfer_progress = 0; + while (length) { + unsigned int xfer = min(length, RP2040_GBDG_BLOCK_SIZE); + + ret = rp2040_gbdg_transfer_block(priv_data, data, xfer); + if (ret) + return ret; + length -= xfer; + data += xfer; + priv_data->transfer_progress += xfer; + } + priv_data->transfer_progress = 0; + + return 0; +} + +static int rp2040_gbdg_transfer_one(struct spi_controller *ctlr, + struct spi_device *spi, + struct spi_transfer *transfer) +{ + /* All transfers are performed in a synchronous manner. As such, return '0' + * on success or -ve on failure. (Returning +ve indicates async xfer) + */ + + struct rp2040_gbdg *priv_data = spi_controller_get_devdata(ctlr); + + if (priv_data->bypass_cache) { + return rp2040_gbdg_transfer_bypass(priv_data, transfer->tx_buf, + transfer->len); + } else { + return rp2040_gbdg_transfer_cached(priv_data, transfer->tx_buf, + transfer->len); + } +} + +static void rp2040_gbdg_set_cs(struct spi_device *spi, bool enable) +{ + static const char disable_cs[] = { DIRECT_PREFIX, DIRECT_CMD_CS, 0x00 }; + static const char enable_cs[] = { DIRECT_PREFIX, DIRECT_CMD_CS, 0x10 }; + struct rp2040_gbdg *p_data; + + p_data = spi_controller_get_devdata(spi->controller); + + /* + * 'enable' is inverted and instead describes the logic level of an + * active-low CS. + */ + rp2040_gbdg_i2c_send(p_data->client, enable ? disable_cs : enable_cs, + 3); +} + +static int rp2040_gbdg_gpio_request(struct gpio_chip *gc, unsigned int offset) +{ + struct rp2040_gbdg *priv_data = gpiochip_get_data(gc); + u32 pattern; + int ret; + + if (offset >= NUM_GPIO) + return -EINVAL; + + pattern = (1 << (offset + 8)); + if (pattern & priv_data->gpio_requested) + return -EBUSY; + + /* Resume if previously no gpio requested */ + if (!priv_data->gpio_requested) { + ret = pm_runtime_resume_and_get(&priv_data->client->dev); + if (ret) { + dev_err(&priv_data->client->dev, + "%s(%u) unable to resume\n", __func__, offset); + return ret; + } + } + + priv_data->gpio_requested |= pattern; + + return 0; +} + +static void rp2040_gbdg_gpio_free(struct gpio_chip *gc, unsigned int offset) +{ + struct rp2040_gbdg *priv_data = gpiochip_get_data(gc); + u32 pattern; + int ret; + + if (offset >= NUM_GPIO || !priv_data->gpio_requested) + return; + + pattern = (1 << (offset + 8)); + + priv_data->gpio_requested &= ~pattern; + rp2040_gbdg_gpio_dir_in(gc, offset); + rp2040_gbdg_gpio_set(gc, offset, 0); + + if (!priv_data->gpio_requested) { + ret = pm_runtime_put_autosuspend(&priv_data->client->dev); + if (ret) { + dev_err(&priv_data->client->dev, + "%s(%u) unable to put_autosuspend\n", __func__, + offset); + } + } +} + +static int rp2040_gbdg_gpio_get_direction(struct gpio_chip *gc, + unsigned int offset) +{ + struct rp2040_gbdg *priv_data = gpiochip_get_data(gc); + + if (offset >= NUM_GPIO) + return -EINVAL; + + return (priv_data->gpio_direction & (1 << (offset + 8))) ? + GPIO_LINE_DIRECTION_IN : + GPIO_LINE_DIRECTION_OUT; +} + +static int rp2040_gbdg_gpio_dir_in(struct gpio_chip *gc, unsigned int offset) +{ + struct rp2040_gbdg *priv_data = gpiochip_get_data(gc); + struct i2c_client *client = priv_data->client; + + if (offset >= NUM_GPIO) + return -EINVAL; + + priv_data->gpio_direction |= (1 << (offset + 8)); + + return rp2040_gbdg_10byte_cmd(client, CMD_GPIO_OE, + ~priv_data->gpio_direction, + priv_data->gpio_direction); +} + +static int rp2040_gbdg_gpio_dir_out(struct gpio_chip *gc, unsigned int offset, + int value) +{ + struct rp2040_gbdg *priv_data = gpiochip_get_data(gc); + struct i2c_client *client = priv_data->client; + u32 pattern; + int ret; + + if (offset >= NUM_GPIO) + return -EINVAL; + + pattern = (1 << (offset + 8)); + + ret = rp2040_gbdg_10byte_cmd(client, CMD_GPIO_ST_CL, + value ? pattern : 0, !value ? pattern : 0); + if (ret) { + dev_err(&client->dev, "%s(%u, %d) could not ST_CL\n", __func__, + offset, value); + return ret; + } + + priv_data->gpio_direction &= ~pattern; + ret = rp2040_gbdg_10byte_cmd(client, CMD_GPIO_OE, + ~priv_data->gpio_direction, + priv_data->gpio_direction); + + return ret; +} + +static int rp2040_gbdg_gpio_get(struct gpio_chip *gc, unsigned int offset) +{ + struct rp2040_gbdg *priv_data = gpiochip_get_data(gc); + struct i2c_client *client = priv_data->client; + struct rp2040_gbdg_device_info info; + int ret; + + if (offset >= NUM_GPIO) + return -EINVAL; + + ret = rp2040_gbdg_get_device_info(client, &info); + if (ret) + return ret; + + return info.status & (1 << (offset + 8)) ? 1 : 0; +} + +static void rp2040_gbdg_gpio_set(struct gpio_chip *gc, unsigned int offset, + int value) +{ + struct rp2040_gbdg *priv_data = gpiochip_get_data(gc); + struct i2c_client *client = priv_data->client; + u32 pattern; + + if (offset >= NUM_GPIO) + return; + + pattern = (1 << (offset + 8)); + rp2040_gbdg_10byte_cmd(client, CMD_GPIO_ST_CL, value ? pattern : 0, + !value ? pattern : 0); +} + +static int rp2040_gbdg_get_regulator(struct device *dev, + struct rp2040_gbdg *rp2040_gbdg) +{ + struct regulator *reg = devm_regulator_get(dev, "power"); + + if (IS_ERR(reg)) + return PTR_ERR(reg); + + rp2040_gbdg->regulator = reg; + + return 0; +} + +static void rp2040_gbdg_parse_dt(struct rp2040_gbdg *rp2040_gbdg) +{ + struct i2c_client *client = rp2040_gbdg->client; + struct of_phandle_args of_args[2] = { 0 }; + struct device *dev = &client->dev; + struct device_node *dn; + + rp2040_gbdg->bypass_cache = + of_property_read_bool(client->dev.of_node, "bypass-cache"); + + /* Optionally configure fast_xfer if RP1 is being used */ + if (of_parse_phandle_with_args(client->dev.of_node, "fast_xfer-gpios", + "#gpio-cells", 0, &of_args[0]) || + of_parse_phandle_with_args(client->dev.of_node, "fast_xfer-gpios", + "#gpio-cells", 1, &of_args[1])) { + dev_info(dev, "Could not parse fast_xfer-gpios phandles\n"); + goto node_put; + } + + if (of_args[0].np != of_args[1].np) { + dev_info( + dev, + "fast_xfer-gpios are not provided by the same controller\n"); + goto node_put; + } + dn = of_args[0].np; + if (!of_device_is_compatible(dn, "raspberrypi,rp1-gpio")) { + dev_info(dev, "fast_xfer-gpios controller is not an rp1\n"); + goto node_put; + } + if (of_args[0].args_count != 2 || of_args[1].args_count != 2) { + dev_info(dev, "of_args count is %d\n", of_args[0].args_count); + goto node_put; + } + + if (of_property_read_u32_index( + client->dev.of_node, "fast_xfer_recv_gpio_base", 0, + &rp2040_gbdg->fast_xfer_recv_gpio_base)) { + dev_info(dev, "Could not read fast_xfer_recv_gpio_base\n"); + goto node_put; + } + + rp2040_gbdg->fast_xfer_gpios = + devm_gpiod_get_array_optional(dev, "fast_xfer", GPIOD_ASIS); + if (IS_ERR_OR_NULL(rp2040_gbdg->fast_xfer_gpios)) { + rp2040_gbdg->fast_xfer_gpios = NULL; + dev_info(dev, "Could not acquire fast_xfer-gpios\n"); + goto node_put; + } + + rp2040_gbdg->fast_xfer_data_index = of_args[0].args[0]; + rp2040_gbdg->fast_xfer_clock_index = of_args[1].args[0]; + rp2040_gbdg->fast_xfer_requires_i2c_lock = of_property_read_bool( + client->dev.of_node, "fast_xfer_requires_i2c_lock"); + + rp2040_gbdg->gpio_base = of_iomap(dn, 0); + if (IS_ERR_OR_NULL(rp2040_gbdg->gpio_base)) { + dev_info(&client->dev, "%s() unable to map gpio_base\n", + __func__); + rp2040_gbdg->gpio_base = NULL; + devm_gpiod_put_array(dev, rp2040_gbdg->fast_xfer_gpios); + rp2040_gbdg->fast_xfer_gpios = NULL; + goto node_put; + } + + rp2040_gbdg->rio_base = of_iomap(dn, 1); + if (IS_ERR_OR_NULL(rp2040_gbdg->rio_base)) { + dev_info(&client->dev, "%s() unable to map rio_base\n", + __func__); + rp2040_gbdg->rio_base = NULL; + iounmap(rp2040_gbdg->gpio_base); + rp2040_gbdg->gpio_base = NULL; + devm_gpiod_put_array(dev, rp2040_gbdg->fast_xfer_gpios); + rp2040_gbdg->fast_xfer_gpios = NULL; + goto node_put; + } + + /* + * fast_xfer mode requires first data bit to be clocked on a rising + * edge. Configure as output-low here before fast_xfer mode is entered. + */ + gpiod_direction_output(rp2040_gbdg->fast_xfer_gpios->desc[1], 0); +node_put: + if (of_args[0].np) + of_node_put(of_args[0].np); + if (of_args[1].np) + of_node_put(of_args[1].np); +} + +static int rp2040_gbdg_power_off(struct rp2040_gbdg *rp2040_gbdg) +{ + struct device *dev = &rp2040_gbdg->client->dev; + int ret; + + ret = regulator_disable(rp2040_gbdg->regulator); + if (ret) { + dev_err(dev, "%s: Could not disable regulator\n", __func__); + return ret; + } + + return 0; +} + +static int rp2040_gbdg_power_on(struct rp2040_gbdg *rp2040_gbdg) +{ + struct device *dev = &rp2040_gbdg->client->dev; + int ret; + + ret = regulator_enable(rp2040_gbdg->regulator); + if (ret) { + dev_err(dev, "%s: Could not enable regulator\n", __func__); + return ret; + } + + return 0; +} + +static int transfer_progress_show(struct seq_file *s, void *data) +{ + struct rp2040_gbdg *rp2040_gbdg = s->private; + + seq_printf(s, "%zu\n", rp2040_gbdg->transfer_progress); + return 0; +} + +DEFINE_SHOW_ATTRIBUTE(transfer_progress); + +static int rp2040_gbdg_probe(struct i2c_client *client) +{ + struct rp2040_gbdg_device_info info; + struct spi_controller *controller; + struct device *dev = &client->dev; + struct rp2040_gbdg *rp2040_gbdg; + struct device_node *np; + char debugfs_name[128]; + int ret; + + np = dev->of_node; + + controller = devm_spi_alloc_master(dev, sizeof(struct rp2040_gbdg)); + if (!controller) + return dev_err_probe(dev, ENOMEM, + "could not alloc spi controller\n"); + + rp2040_gbdg = spi_controller_get_devdata(controller); + i2c_set_clientdata(client, rp2040_gbdg); + rp2040_gbdg->controller = controller; + rp2040_gbdg->client = client; + + ret = rp2040_gbdg_get_regulator(dev, rp2040_gbdg); + if (ret < 0) + return dev_err_probe(dev, ret, "Cannot get regulator\n"); + + ret = rp2040_gbdg_power_on(rp2040_gbdg); + if (ret) + return dev_err_probe(dev, ret, "Could not power on device\n"); + + pm_runtime_set_active(dev); + pm_runtime_get_noresume(dev); + pm_runtime_enable(dev); + pm_runtime_set_autosuspend_delay(dev, 1000); + pm_runtime_use_autosuspend(dev); + + ret = rp2040_gbdg_get_device_info(client, &info); + if (ret) { + dev_err(dev, "Could not get device info\n"); + goto err_pm; + } + + dev_info(dev, "%s() found dev ID: %llx, fw ver. %u\n", __func__, + info.id, info.version); + + rp2040_gbdg->shash = crypto_alloc_shash("md5", 0, 0); + if (IS_ERR(rp2040_gbdg->shash)) { + ret = PTR_ERR(rp2040_gbdg->shash); + dev_err(dev, "Could not allocate shash\n"); + goto err_pm; + } + + if (crypto_shash_digestsize(rp2040_gbdg->shash) != MD5_DIGEST_SIZE) { + ret = -EINVAL; + dev_err(dev, "error: Unexpected hash digest size\n"); + goto err_shash; + } + + rp2040_gbdg->shash_desc = + devm_kmalloc(dev, + sizeof(struct shash_desc) + + crypto_shash_descsize(rp2040_gbdg->shash), + 0); + + if (!rp2040_gbdg->shash_desc) { + ret = -ENOMEM; + dev_err(dev, + "error: Could not allocate memory for shash_desc\n"); + goto err_shash; + } + rp2040_gbdg->shash_desc->tfm = rp2040_gbdg->shash; + + controller->bus_num = -1; + controller->num_chipselect = 1; + controller->mode_bits = SPI_CPOL | SPI_CPHA; + controller->bits_per_word_mask = SPI_BPW_MASK(8); + controller->min_speed_hz = 35000000; + controller->max_speed_hz = 35000000; + controller->max_transfer_size = rp2040_gbdg_max_transfer_size; + controller->max_message_size = rp2040_gbdg_max_transfer_size; + controller->transfer_one = rp2040_gbdg_transfer_one; + controller->set_cs = rp2040_gbdg_set_cs; + + controller->dev.of_node = np; + controller->auto_runtime_pm = true; + + ret = devm_spi_register_controller(dev, controller); + if (ret) { + dev_err(dev, "error: Could not register SPI controller\n"); + goto err_shash; + } + + memset(&rp2040_gbdg->gc, 0, sizeof(struct gpio_chip)); + rp2040_gbdg->gc.parent = dev; + rp2040_gbdg->gc.label = MODULE_NAME; + rp2040_gbdg->gc.owner = THIS_MODULE; + rp2040_gbdg->gc.base = -1; + rp2040_gbdg->gc.ngpio = NUM_GPIO; + + rp2040_gbdg->gc.request = rp2040_gbdg_gpio_request; + rp2040_gbdg->gc.free = rp2040_gbdg_gpio_free; + rp2040_gbdg->gc.get_direction = rp2040_gbdg_gpio_get_direction; + rp2040_gbdg->gc.direction_input = rp2040_gbdg_gpio_dir_in; + rp2040_gbdg->gc.direction_output = rp2040_gbdg_gpio_dir_out; + rp2040_gbdg->gc.get = rp2040_gbdg_gpio_get; + rp2040_gbdg->gc.set = rp2040_gbdg_gpio_set; + rp2040_gbdg->gc.can_sleep = true; + + rp2040_gbdg->gpio_requested = 0; + + /* Coming out of reset, all GPIOs are inputs */ + rp2040_gbdg->gpio_direction = ~0; + + ret = devm_gpiochip_add_data(dev, &rp2040_gbdg->gc, rp2040_gbdg); + if (ret) { + dev_err(dev, "error: Could not add data to gpiochip\n"); + goto err_shash; + } + + rp2040_gbdg_parse_dt(rp2040_gbdg); + + snprintf(debugfs_name, sizeof(debugfs_name), "rp2040-spi:%s", + dev_name(dev)); + rp2040_gbdg->debugfs = debugfs_create_dir(debugfs_name, NULL); + debugfs_create_file("transfer_progress", 0444, rp2040_gbdg->debugfs, + rp2040_gbdg, &transfer_progress_fops); + + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + return 0; + +err_shash: + crypto_free_shash(rp2040_gbdg->shash); +err_pm: + pm_runtime_disable(dev); + pm_runtime_put_noidle(dev); + rp2040_gbdg_power_off(rp2040_gbdg); + + return ret; +} + +static void rp2040_gbdg_remove(struct i2c_client *client) +{ + struct rp2040_gbdg *priv_data = i2c_get_clientdata(client); + + crypto_free_shash(priv_data->shash); + + if (priv_data->gpio_base) { + iounmap(priv_data->gpio_base); + priv_data->gpio_base = NULL; + } + if (priv_data->rio_base) { + iounmap(priv_data->rio_base); + priv_data->rio_base = NULL; + } + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) + rp2040_gbdg_power_off(priv_data); + pm_runtime_set_suspended(&client->dev); +} + +static const struct i2c_device_id rp2040_gbdg_id[] = { + { "rp2040-gpio-bridge", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, rp2040_gbdg_id); + +static const struct of_device_id rp2040_gbdg_of_match[] = { + { .compatible = "raspberrypi,rp2040-gpio-bridge" }, + {}, +}; +MODULE_DEVICE_TABLE(of, rp2040_gbdg_of_match); + +static int rp2040_gbdg_runtime_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + + return rp2040_gbdg_power_off(i2c_get_clientdata(client)); +} + +static int rp2040_gbdg_runtime_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + + return rp2040_gbdg_power_on(i2c_get_clientdata(client)); +} + +static const struct dev_pm_ops rp2040_gbdg_pm_ops = { SET_RUNTIME_PM_OPS( + rp2040_gbdg_runtime_suspend, rp2040_gbdg_runtime_resume, NULL) }; + +static struct i2c_driver rp2040_gbdg_driver = { + .driver = { + .name = MODULE_NAME, + .of_match_table = of_match_ptr(rp2040_gbdg_of_match), + .pm = &rp2040_gbdg_pm_ops, + }, + .probe = rp2040_gbdg_probe, + .remove = rp2040_gbdg_remove, + .id_table = rp2040_gbdg_id, +}; + +module_i2c_driver(rp2040_gbdg_driver); + +MODULE_AUTHOR("Richard Oliver <richard.oliver@raspberrypi.com>"); +MODULE_DESCRIPTION("Raspberry Pi RP2040 GPIO Bridge"); +MODULE_LICENSE("GPL"); +MODULE_SOFTDEP("pre: md5"); diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index 0f3e6e2c24743c..6279bc713e6904 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -3897,6 +3897,7 @@ static int spi_set_cs_timing(struct spi_device *spi) */ int spi_setup(struct spi_device *spi) { + struct spi_controller *ctlr = spi->controller; unsigned bad_bits, ugly_bits; int status; @@ -3923,6 +3924,14 @@ int spi_setup(struct spi_device *spi) "setup: MOSI configured to idle low and high at the same time.\n"); return -EINVAL; } + + if (ctlr->use_gpio_descriptors && ctlr->cs_gpiods && + ctlr->cs_gpiods[spi->chip_select[0]] && !(spi->mode & SPI_CS_HIGH)) { + dev_dbg(&spi->dev, + "setup: forcing CS_HIGH (use_gpio_descriptors)\n"); + spi->mode |= SPI_CS_HIGH; + } + /* * Help drivers fail *cleanly* when they need options * that aren't supported with their current controller. diff --git a/drivers/spi/spidev.c b/drivers/spi/spidev.c index 653f82984216c3..89d2a4a4e5ad25 100644 --- a/drivers/spi/spidev.c +++ b/drivers/spi/spidev.c @@ -428,7 +428,7 @@ spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) } if (ctlr->use_gpio_descriptors && spi_get_csgpiod(spi, 0)) - tmp |= SPI_CS_HIGH; + { /*tmp |= SPI_CS_HIGH;*/ } tmp |= spi->mode & ~SPI_MODE_MASK; spi->mode = tmp & SPI_MODE_USER_MASK; @@ -711,6 +711,7 @@ static const struct spi_device_id spidev_spi_ids[] = { { .name = "spi-authenta" }, { .name = "em3581" }, { .name = "si3210" }, + { .name = "spidev" }, {}, }; MODULE_DEVICE_TABLE(spi, spidev_spi_ids); @@ -721,7 +722,7 @@ MODULE_DEVICE_TABLE(spi, spidev_spi_ids); */ static int spidev_of_check(struct device *dev) { - if (device_property_match_string(dev, "compatible", "spidev") < 0) + if (1 || device_property_match_string(dev, "compatible", "spidev") < 0) return 0; dev_err(dev, "spidev listed directly in DT is not supported\n"); diff --git a/drivers/staging/fbtft/fb_st7735r.c b/drivers/staging/fbtft/fb_st7735r.c index 9670a8989b9175..a04365d1c75e2c 100644 --- a/drivers/staging/fbtft/fb_st7735r.c +++ b/drivers/staging/fbtft/fb_st7735r.c @@ -16,6 +16,10 @@ #define DEFAULT_GAMMA "0F 1A 0F 18 2F 28 20 22 1F 1B 23 37 00 07 02 10\n" \ "0F 1B 0F 17 33 2C 29 2E 30 30 39 3F 00 07 03 10" +#define ADAFRUIT18_GAMMA \ + "02 1c 07 12 37 32 29 2d 29 25 2B 39 00 01 03 10\n" \ + "03 1d 07 06 2E 2C 29 2D 2E 2E 37 3F 00 00 02 10" + static const s16 default_init_sequence[] = { -1, MIPI_DCS_SOFT_RESET, -2, 150, /* delay */ @@ -94,6 +98,14 @@ static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye) write_reg(par, MIPI_DCS_WRITE_MEMORY_START); } +static void adafruit18_green_tab_set_addr_win(struct fbtft_par *par, + int xs, int ys, int xe, int ye) +{ + write_reg(par, 0x2A, 0, xs + 2, 0, xe + 2); + write_reg(par, 0x2B, 0, ys + 1, 0, ye + 1); + write_reg(par, 0x2C); +} + #define MY BIT(7) #define MX BIT(6) #define MV BIT(5) @@ -174,12 +186,36 @@ static struct fbtft_display display = { }, }; -FBTFT_REGISTER_DRIVER(DRVNAME, "sitronix,st7735r", &display); +static int variant_adafruit18(struct fbtft_display *display) +{ + display->gamma = ADAFRUIT18_GAMMA; + return 0; +} + +static int variant_adafruit18_green(struct fbtft_display *display) +{ + display->gamma = ADAFRUIT18_GAMMA; + display->fbtftops.set_addr_win = adafruit18_green_tab_set_addr_win; + return 0; +} + +FBTFT_REGISTER_DRIVER_START(&display) +FBTFT_COMPATIBLE("sitronix,st7735r") +FBTFT_COMPATIBLE("fbtft,sainsmart18") +FBTFT_VARIANT_COMPATIBLE("fbtft,adafruit18", variant_adafruit18) +FBTFT_VARIANT_COMPATIBLE("fbtft,adafruit18_green", variant_adafruit18_green) +FBTFT_REGISTER_DRIVER_END(DRVNAME, &display); MODULE_ALIAS("spi:" DRVNAME); MODULE_ALIAS("platform:" DRVNAME); MODULE_ALIAS("spi:st7735r"); MODULE_ALIAS("platform:st7735r"); +MODULE_ALIAS("spi:sainsmart18"); +MODULE_ALIAS("platform:sainsmart"); +MODULE_ALIAS("spi:adafruit18"); +MODULE_ALIAS("platform:adafruit18"); +MODULE_ALIAS("spi:adafruit18_green"); +MODULE_ALIAS("platform:adafruit18_green"); MODULE_DESCRIPTION("FB driver for the ST7735R LCD Controller"); MODULE_AUTHOR("Noralf Tronnes"); diff --git a/drivers/staging/fbtft/fb_st7789v.c b/drivers/staging/fbtft/fb_st7789v.c index 861a154144e661..ddc1452c29534e 100644 --- a/drivers/staging/fbtft/fb_st7789v.c +++ b/drivers/staging/fbtft/fb_st7789v.c @@ -75,6 +75,11 @@ enum st7789v_command { static struct completion panel_te; /* completion for panel TE line */ static int irq_te; /* Linux IRQ for LCD TE line */ +static u32 col_offset = 0; +static u32 row_offset = 0; +static u8 col_hack_fix_offset = 0; +static short x_offset = 0; +static short y_offset = 0; static irqreturn_t panel_te_handler(int irq, void *data) { @@ -261,6 +266,22 @@ static int write_vmem(struct fbtft_par *par, size_t offset, size_t len) return ret; } +static void minipitft13_set_addr_win(struct fbtft_par *par, int xs, int ys, + int xe, int ye) +{ + xs += x_offset; + xe += x_offset; + ys += y_offset; + ye += y_offset; + write_reg(par, MIPI_DCS_SET_COLUMN_ADDRESS, + xs >> 8, xs & 0xFF, xe >> 8, xe & 0xFF); + + write_reg(par, MIPI_DCS_SET_PAGE_ADDRESS, + ys >> 8, ys & 0xFF, ye >> 8, ye & 0xFF); + + write_reg(par, MIPI_DCS_WRITE_MEMORY_START); +} + /** * set_var() - apply LCD properties like rotation and BGR mode * @@ -271,20 +292,32 @@ static int write_vmem(struct fbtft_par *par, size_t offset, size_t len) static int set_var(struct fbtft_par *par) { u8 madctl_par = 0; + struct fbtft_display *display = &par->pdata->display; + u32 width = display->width; + u32 height = display->height; if (par->bgr) madctl_par |= MADCTL_BGR; switch (par->info->var.rotate) { case 0: + x_offset = 0; + y_offset = 0; break; case 90: madctl_par |= (MADCTL_MV | MADCTL_MY); + x_offset = (320 - height) - row_offset; + y_offset = (240 - width) - col_offset; break; case 180: madctl_par |= (MADCTL_MX | MADCTL_MY); + x_offset = (240 - width) - col_offset + col_hack_fix_offset; + // hack tweak to account for extra pixel width to make even + y_offset = (320 - height) - row_offset; break; case 270: madctl_par |= (MADCTL_MV | MADCTL_MX); + x_offset = row_offset; + y_offset = col_offset; break; default: return -EINVAL; @@ -382,7 +415,16 @@ static struct fbtft_display display = { }, }; -FBTFT_REGISTER_DRIVER(DRVNAME, "sitronix,st7789v", &display); +static int variant_minipitft13(struct fbtft_display *display) +{ + display->fbtftops.set_addr_win = minipitft13_set_addr_win; + return 0; +} + +FBTFT_REGISTER_DRIVER_START(&display) +FBTFT_COMPATIBLE("sitronix,st7789v") +FBTFT_VARIANT_COMPATIBLE("fbtft,minipitft13", variant_minipitft13) +FBTFT_REGISTER_DRIVER_END(DRVNAME, &display); MODULE_ALIAS("spi:" DRVNAME); MODULE_ALIAS("platform:" DRVNAME); diff --git a/drivers/staging/fbtft/fbtft-core.c b/drivers/staging/fbtft/fbtft-core.c index 4cfa494243b983..b655fc34986983 100644 --- a/drivers/staging/fbtft/fbtft-core.c +++ b/drivers/staging/fbtft/fbtft-core.c @@ -24,6 +24,8 @@ #include <linux/platform_device.h> #include <linux/property.h> #include <linux/spinlock.h> +#include <linux/of.h> +#include <linux/of_device.h> #include <video/mipi_display.h> @@ -1131,6 +1133,7 @@ static struct fbtft_platform_data *fbtft_properties_read(struct device *dev) * @display: Display properties * @sdev: SPI device * @pdev: Platform device + * @dt_ids: Compatible string table * * Allocates, initializes and registers a framebuffer * @@ -1140,12 +1143,15 @@ static struct fbtft_platform_data *fbtft_properties_read(struct device *dev) */ int fbtft_probe_common(struct fbtft_display *display, struct spi_device *sdev, - struct platform_device *pdev) + struct platform_device *pdev, + const struct of_device_id *dt_ids) { struct device *dev; struct fb_info *info; struct fbtft_par *par; struct fbtft_platform_data *pdata; + const struct of_device_id *match; + int (*variant)(struct fbtft_display *); int ret; if (sdev) @@ -1158,6 +1164,14 @@ int fbtft_probe_common(struct fbtft_display *display, pdata = fbtft_properties_read(dev); if (IS_ERR(pdata)) return PTR_ERR(pdata); + match = of_match_device(dt_ids, dev); + if (match && match->data) { + /* apply the variant */ + variant = match->data; + ret = (*variant)(display); + if (ret) + return ret; + } } info = fbtft_framebuffer_alloc(display, dev, pdata); diff --git a/drivers/staging/fbtft/fbtft.h b/drivers/staging/fbtft/fbtft.h index 3e00a26a29d5c3..7a8fdc487756ea 100644 --- a/drivers/staging/fbtft/fbtft.h +++ b/drivers/staging/fbtft/fbtft.h @@ -253,7 +253,8 @@ void fbtft_register_backlight(struct fbtft_par *par); void fbtft_unregister_backlight(struct fbtft_par *par); int fbtft_init_display(struct fbtft_par *par); int fbtft_probe_common(struct fbtft_display *display, struct spi_device *sdev, - struct platform_device *pdev); + struct platform_device *pdev, + const struct of_device_id *dt_ids); void fbtft_remove_common(struct device *dev, struct fb_info *info); /* fbtft-io.c */ @@ -274,42 +275,25 @@ void fbtft_write_reg8_bus9(struct fbtft_par *par, int len, ...); void fbtft_write_reg16_bus8(struct fbtft_par *par, int len, ...); void fbtft_write_reg16_bus16(struct fbtft_par *par, int len, ...); -#define FBTFT_DT_TABLE(_compatible) \ -static const struct of_device_id dt_ids[] = { \ - { .compatible = _compatible }, \ - {}, \ -}; \ -MODULE_DEVICE_TABLE(of, dt_ids); - -#define FBTFT_SPI_DRIVER(_name, _compatible, _display, _spi_ids) \ - \ -static int fbtft_driver_probe_spi(struct spi_device *spi) \ -{ \ - return fbtft_probe_common(_display, spi, NULL); \ -} \ - \ -static void fbtft_driver_remove_spi(struct spi_device *spi) \ -{ \ - struct fb_info *info = spi_get_drvdata(spi); \ - \ - fbtft_remove_common(&spi->dev, info); \ -} \ - \ -static struct spi_driver fbtft_driver_spi_driver = { \ - .driver = { \ - .name = _name, \ - .of_match_table = dt_ids, \ - }, \ - .id_table = _spi_ids, \ - .probe = fbtft_driver_probe_spi, \ - .remove = fbtft_driver_remove_spi, \ -}; - -#define FBTFT_REGISTER_DRIVER(_name, _compatible, _display) \ +#define FBTFT_REGISTER_DRIVER_START(_display) \ + \ +static const struct of_device_id dt_ids[]; \ + \ +static int fbtft_driver_probe_spi(struct spi_device *spi) \ +{ \ + return fbtft_probe_common(_display, spi, NULL, dt_ids); \ +} \ + \ +static void fbtft_driver_remove_spi(struct spi_device *spi) \ +{ \ + struct fb_info *info = spi_get_drvdata(spi); \ + \ + fbtft_remove_common(&spi->dev, info); \ +} \ \ static int fbtft_driver_probe_pdev(struct platform_device *pdev) \ { \ - return fbtft_probe_common(_display, NULL, pdev); \ + return fbtft_probe_common(_display, NULL, pdev, dt_ids); \ } \ \ static void fbtft_driver_remove_pdev(struct platform_device *pdev) \ @@ -319,9 +303,30 @@ static void fbtft_driver_remove_pdev(struct platform_device *pdev) \ fbtft_remove_common(&pdev->dev, info); \ } \ \ -FBTFT_DT_TABLE(_compatible) \ +static const struct of_device_id dt_ids[] = { + +#define FBTFT_COMPATIBLE(_compatible) \ + { .compatible = _compatible }, + +#define FBTFT_VARIANT_COMPATIBLE(_compatible, _variant) \ + { .compatible = _compatible, .data = _variant }, + +#define FBTFT_REGISTER_DRIVER_END(_name, _display) \ + \ + {}, \ +}; \ \ -FBTFT_SPI_DRIVER(_name, _compatible, _display, NULL) \ +MODULE_DEVICE_TABLE(of, dt_ids); \ + \ + \ +static struct spi_driver fbtft_driver_spi_driver = { \ + .driver = { \ + .name = _name, \ + .of_match_table = dt_ids, \ + }, \ + .probe = fbtft_driver_probe_spi, \ + .remove = fbtft_driver_remove_spi, \ +}; \ \ static struct platform_driver fbtft_driver_platform_driver = { \ .driver = { \ @@ -357,18 +362,49 @@ module_exit(fbtft_driver_module_exit); #define FBTFT_REGISTER_SPI_DRIVER(_name, _comp_vend, _comp_dev, _display) \ \ -FBTFT_DT_TABLE(_comp_vend "," _comp_dev) \ +static const struct of_device_id dt_ids[] = { \ + { .compatible = _comp_vend "," _comp_dev }, \ + {}, \ +}; \ + \ +static int fbtft_driver_probe_spi(struct spi_device *spi) \ +{ \ + return fbtft_probe_common(_display, spi, NULL, dt_ids); \ +} \ + \ +static void fbtft_driver_remove_spi(struct spi_device *spi) \ +{ \ + struct fb_info *info = spi_get_drvdata(spi); \ + \ + fbtft_remove_common(&spi->dev, info); \ +} \ + \ +MODULE_DEVICE_TABLE(of, dt_ids); \ \ static const struct spi_device_id spi_ids[] = { \ { .name = _comp_dev }, \ {}, \ }; \ + \ MODULE_DEVICE_TABLE(spi, spi_ids); \ \ -FBTFT_SPI_DRIVER(_name, _comp_vend "," _comp_dev, _display, spi_ids) \ +static struct spi_driver fbtft_driver_spi_driver = { \ + .driver = { \ + .name = _name, \ + .of_match_table = dt_ids, \ + }, \ + .id_table = spi_ids, \ + .probe = fbtft_driver_probe_spi, \ + .remove = fbtft_driver_remove_spi, \ +}; \ \ module_spi_driver(fbtft_driver_spi_driver); +#define FBTFT_REGISTER_DRIVER(_name, _compatible, _display) \ + FBTFT_REGISTER_DRIVER_START(_display) \ + FBTFT_COMPATIBLE(_compatible) \ + FBTFT_REGISTER_DRIVER_END(_name, _display) + /* Debug macros */ /* shorthand debug levels */ diff --git a/drivers/staging/media/Kconfig b/drivers/staging/media/Kconfig index 554c2e475ce3bf..76d1aaf0c6b03c 100644 --- a/drivers/staging/media/Kconfig +++ b/drivers/staging/media/Kconfig @@ -36,6 +36,8 @@ source "drivers/staging/media/omap4iss/Kconfig" source "drivers/staging/media/rkvdec/Kconfig" +source "drivers/staging/media/rpivid/Kconfig" + source "drivers/staging/media/starfive/Kconfig" source "drivers/staging/media/sunxi/Kconfig" diff --git a/drivers/staging/media/Makefile b/drivers/staging/media/Makefile index dcaeeca0ee6d72..2eef2e38fc8ad7 100644 --- a/drivers/staging/media/Makefile +++ b/drivers/staging/media/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_VIDEO_MAX96712) += max96712/ obj-$(CONFIG_VIDEO_MESON_VDEC) += meson/vdec/ obj-$(CONFIG_VIDEO_OMAP4) += omap4iss/ obj-$(CONFIG_VIDEO_ROCKCHIP_VDEC) += rkvdec/ +obj-$(CONFIG_VIDEO_RPIVID) += rpivid/ obj-$(CONFIG_VIDEO_STARFIVE_CAMSS) += starfive/ obj-$(CONFIG_VIDEO_SUNXI) += sunxi/ obj-$(CONFIG_VIDEO_TEGRA) += tegra-video/ diff --git a/drivers/staging/media/rpivid/Kconfig b/drivers/staging/media/rpivid/Kconfig new file mode 100644 index 00000000000000..f9a8a4491301f2 --- /dev/null +++ b/drivers/staging/media/rpivid/Kconfig @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0 + +config VIDEO_RPIVID + tristate "Rpi H265 driver" + depends on VIDEO_DEV && VIDEO_DEV + depends on OF + select MEDIA_CONTROLLER + select MEDIA_CONTROLLER_REQUEST_API + select VIDEOBUF2_DMA_CONTIG + select V4L2_MEM2MEM_DEV + help + Support for the Rpi H265 h/w decoder. + + To compile this driver as a module, choose M here: the module + will be called rpivid-hevc. + diff --git a/drivers/staging/media/rpivid/Makefile b/drivers/staging/media/rpivid/Makefile new file mode 100644 index 00000000000000..990257052b0726 --- /dev/null +++ b/drivers/staging/media/rpivid/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_VIDEO_RPIVID) += rpivid-hevc.o + +rpivid-hevc-y = rpivid.o rpivid_video.o rpivid_dec.o \ + rpivid_hw.o rpivid_h265.o diff --git a/drivers/staging/media/rpivid/rpivid.c b/drivers/staging/media/rpivid/rpivid.c new file mode 100644 index 00000000000000..c68a94c8d002df --- /dev/null +++ b/drivers/staging/media/rpivid/rpivid.c @@ -0,0 +1,464 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Raspberry Pi HEVC driver + * + * Copyright (C) 2020 Raspberry Pi (Trading) Ltd + * + * Based on the Cedrus VPU driver, that is: + * + * Copyright (C) 2016 Florent Revest <florent.revest@free-electrons.com> + * Copyright (C) 2018 Paul Kocialkowski <paul.kocialkowski@bootlin.com> + * Copyright (C) 2018 Bootlin + */ + +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/of.h> + +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-mem2mem.h> + +#include "rpivid.h" +#include "rpivid_video.h" +#include "rpivid_hw.h" +#include "rpivid_dec.h" + +/* + * Default /dev/videoN node number. + * Deliberately avoid the very low numbers as these are often taken by webcams + * etc, and simple apps tend to only go for /dev/video0. + */ +static int video_nr = 19; +module_param(video_nr, int, 0644); +MODULE_PARM_DESC(video_nr, "decoder video device number"); + +static const struct rpivid_control rpivid_ctrls[] = { + { + .cfg = { + .id = V4L2_CID_STATELESS_HEVC_SPS, + .ops = &rpivid_hevc_sps_ctrl_ops, + }, + .required = false, + }, + { + .cfg = { + .id = V4L2_CID_STATELESS_HEVC_PPS, + .ops = &rpivid_hevc_pps_ctrl_ops, + }, + .required = false, + }, + { + .cfg = { + .id = V4L2_CID_STATELESS_HEVC_SCALING_MATRIX, + }, + .required = false, + }, + { + .cfg = { + .id = V4L2_CID_STATELESS_HEVC_DECODE_PARAMS, + }, + .required = true, + }, + { + .cfg = { + .name = "Slice param array", + .id = V4L2_CID_STATELESS_HEVC_SLICE_PARAMS, + .type = V4L2_CTRL_TYPE_HEVC_SLICE_PARAMS, + .flags = V4L2_CTRL_FLAG_DYNAMIC_ARRAY, + .dims = { 0x1000 }, + }, + .required = true, + }, + { + .cfg = { + .id = V4L2_CID_STATELESS_HEVC_DECODE_MODE, + .min = V4L2_STATELESS_HEVC_DECODE_MODE_SLICE_BASED, + .max = V4L2_STATELESS_HEVC_DECODE_MODE_SLICE_BASED, + .def = V4L2_STATELESS_HEVC_DECODE_MODE_SLICE_BASED, + }, + .required = false, + }, + { + .cfg = { + .id = V4L2_CID_STATELESS_HEVC_START_CODE, + .min = V4L2_STATELESS_HEVC_START_CODE_NONE, + .max = V4L2_STATELESS_HEVC_START_CODE_ANNEX_B, + .def = V4L2_STATELESS_HEVC_START_CODE_NONE, + }, + .required = false, + }, +}; + +#define rpivid_ctrls_COUNT ARRAY_SIZE(rpivid_ctrls) + +struct v4l2_ctrl *rpivid_find_ctrl(struct rpivid_ctx *ctx, u32 id) +{ + unsigned int i; + + for (i = 0; ctx->ctrls[i]; i++) + if (ctx->ctrls[i]->id == id) + return ctx->ctrls[i]; + + return NULL; +} + +void *rpivid_find_control_data(struct rpivid_ctx *ctx, u32 id) +{ + struct v4l2_ctrl *const ctrl = rpivid_find_ctrl(ctx, id); + + return !ctrl ? NULL : ctrl->p_cur.p; +} + +static int rpivid_init_ctrls(struct rpivid_dev *dev, struct rpivid_ctx *ctx) +{ + struct v4l2_ctrl_handler *hdl = &ctx->hdl; + struct v4l2_ctrl *ctrl; + unsigned int ctrl_size; + unsigned int i; + + v4l2_ctrl_handler_init(hdl, rpivid_ctrls_COUNT); + if (hdl->error) { + v4l2_err(&dev->v4l2_dev, + "Failed to initialize control handler\n"); + return hdl->error; + } + + ctrl_size = sizeof(ctrl) * rpivid_ctrls_COUNT + 1; + + ctx->ctrls = kzalloc(ctrl_size, GFP_KERNEL); + if (!ctx->ctrls) + return -ENOMEM; + + for (i = 0; i < rpivid_ctrls_COUNT; i++) { + ctrl = v4l2_ctrl_new_custom(hdl, &rpivid_ctrls[i].cfg, + ctx); + if (hdl->error) { + v4l2_err(&dev->v4l2_dev, + "Failed to create new custom control id=%#x\n", + rpivid_ctrls[i].cfg.id); + + v4l2_ctrl_handler_free(hdl); + kfree(ctx->ctrls); + return hdl->error; + } + + ctx->ctrls[i] = ctrl; + } + + ctx->fh.ctrl_handler = hdl; + v4l2_ctrl_handler_setup(hdl); + + return 0; +} + +static int rpivid_request_validate(struct media_request *req) +{ + struct media_request_object *obj; + struct v4l2_ctrl_handler *parent_hdl, *hdl; + struct rpivid_ctx *ctx = NULL; + struct v4l2_ctrl *ctrl_test; + unsigned int count; + unsigned int i; + + list_for_each_entry(obj, &req->objects, list) { + struct vb2_buffer *vb; + + if (vb2_request_object_is_buffer(obj)) { + vb = container_of(obj, struct vb2_buffer, req_obj); + ctx = vb2_get_drv_priv(vb->vb2_queue); + + break; + } + } + + if (!ctx) + return -ENOENT; + + count = vb2_request_buffer_cnt(req); + if (!count) { + v4l2_info(&ctx->dev->v4l2_dev, + "No buffer was provided with the request\n"); + return -ENOENT; + } else if (count > 1) { + v4l2_info(&ctx->dev->v4l2_dev, + "More than one buffer was provided with the request\n"); + return -EINVAL; + } + + parent_hdl = &ctx->hdl; + + hdl = v4l2_ctrl_request_hdl_find(req, parent_hdl); + if (!hdl) { + v4l2_info(&ctx->dev->v4l2_dev, "Missing codec control(s)\n"); + return -ENOENT; + } + + for (i = 0; i < rpivid_ctrls_COUNT; i++) { + if (!rpivid_ctrls[i].required) + continue; + + ctrl_test = + v4l2_ctrl_request_hdl_ctrl_find(hdl, + rpivid_ctrls[i].cfg.id); + if (!ctrl_test) { + v4l2_info(&ctx->dev->v4l2_dev, + "Missing required codec control\n"); + v4l2_ctrl_request_hdl_put(hdl); + return -ENOENT; + } + } + + v4l2_ctrl_request_hdl_put(hdl); + + return vb2_request_validate(req); +} + +static int rpivid_open(struct file *file) +{ + struct rpivid_dev *dev = video_drvdata(file); + struct rpivid_ctx *ctx = NULL; + int ret; + + if (mutex_lock_interruptible(&dev->dev_mutex)) + return -ERESTARTSYS; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) { + mutex_unlock(&dev->dev_mutex); + ret = -ENOMEM; + goto err_unlock; + } + + mutex_init(&ctx->ctx_mutex); + + v4l2_fh_init(&ctx->fh, video_devdata(file)); + file->private_data = &ctx->fh; + ctx->dev = dev; + + ret = rpivid_init_ctrls(dev, ctx); + if (ret) + goto err_free; + + ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(dev->m2m_dev, ctx, + &rpivid_queue_init); + if (IS_ERR(ctx->fh.m2m_ctx)) { + ret = PTR_ERR(ctx->fh.m2m_ctx); + goto err_ctrls; + } + + /* The only bit of format info that we can guess now is H265 src + * Everything else we need more info for + */ + rpivid_prepare_src_format(&ctx->src_fmt); + + v4l2_fh_add(&ctx->fh); + + mutex_unlock(&dev->dev_mutex); + + return 0; + +err_ctrls: + v4l2_ctrl_handler_free(&ctx->hdl); +err_free: + mutex_destroy(&ctx->ctx_mutex); + kfree(ctx); +err_unlock: + mutex_unlock(&dev->dev_mutex); + + return ret; +} + +static int rpivid_release(struct file *file) +{ + struct rpivid_dev *dev = video_drvdata(file); + struct rpivid_ctx *ctx = container_of(file->private_data, + struct rpivid_ctx, fh); + + mutex_lock(&dev->dev_mutex); + + v4l2_fh_del(&ctx->fh); + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); + + v4l2_ctrl_handler_free(&ctx->hdl); + kfree(ctx->ctrls); + + v4l2_fh_exit(&ctx->fh); + mutex_destroy(&ctx->ctx_mutex); + + kfree(ctx); + + mutex_unlock(&dev->dev_mutex); + + return 0; +} + +static const struct v4l2_file_operations rpivid_fops = { + .owner = THIS_MODULE, + .open = rpivid_open, + .release = rpivid_release, + .poll = v4l2_m2m_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = v4l2_m2m_fop_mmap, +}; + +static const struct video_device rpivid_video_device = { + .name = RPIVID_NAME, + .vfl_dir = VFL_DIR_M2M, + .fops = &rpivid_fops, + .ioctl_ops = &rpivid_ioctl_ops, + .minor = -1, + .release = video_device_release_empty, + .device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING, +}; + +static const struct v4l2_m2m_ops rpivid_m2m_ops = { + .device_run = rpivid_device_run, +}; + +static const struct media_device_ops rpivid_m2m_media_ops = { + .req_validate = rpivid_request_validate, + .req_queue = v4l2_m2m_request_queue, +}; + +static int rpivid_probe(struct platform_device *pdev) +{ + struct rpivid_dev *dev; + struct video_device *vfd; + int ret; + + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + dev->vfd = rpivid_video_device; + dev->dev = &pdev->dev; + dev->pdev = pdev; + + ret = 0; + ret = rpivid_hw_probe(dev); + if (ret) { + dev_err(&pdev->dev, "Failed to probe hardware\n"); + return ret; + } + + dev->dec_ops = &rpivid_dec_ops_h265; + + mutex_init(&dev->dev_mutex); + + ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev); + if (ret) { + dev_err(&pdev->dev, "Failed to register V4L2 device\n"); + return ret; + } + + vfd = &dev->vfd; + vfd->lock = &dev->dev_mutex; + vfd->v4l2_dev = &dev->v4l2_dev; + + snprintf(vfd->name, sizeof(vfd->name), "%s", rpivid_video_device.name); + video_set_drvdata(vfd, dev); + + ret = dma_set_mask_and_coherent(dev->dev, DMA_BIT_MASK(36)); + if (ret) { + v4l2_err(&dev->v4l2_dev, + "Failed dma_set_mask_and_coherent\n"); + goto err_v4l2; + } + + dev->m2m_dev = v4l2_m2m_init(&rpivid_m2m_ops); + if (IS_ERR(dev->m2m_dev)) { + v4l2_err(&dev->v4l2_dev, + "Failed to initialize V4L2 M2M device\n"); + ret = PTR_ERR(dev->m2m_dev); + + goto err_v4l2; + } + + dev->mdev.dev = &pdev->dev; + strscpy(dev->mdev.model, RPIVID_NAME, sizeof(dev->mdev.model)); + strscpy(dev->mdev.bus_info, "platform:" RPIVID_NAME, + sizeof(dev->mdev.bus_info)); + + media_device_init(&dev->mdev); + dev->mdev.ops = &rpivid_m2m_media_ops; + dev->v4l2_dev.mdev = &dev->mdev; + + ret = video_register_device(vfd, VFL_TYPE_VIDEO, video_nr); + if (ret) { + v4l2_err(&dev->v4l2_dev, "Failed to register video device\n"); + goto err_m2m; + } + + v4l2_info(&dev->v4l2_dev, + "Device registered as /dev/video%d\n", vfd->num); + + ret = v4l2_m2m_register_media_controller(dev->m2m_dev, vfd, + MEDIA_ENT_F_PROC_VIDEO_DECODER); + if (ret) { + v4l2_err(&dev->v4l2_dev, + "Failed to initialize V4L2 M2M media controller\n"); + goto err_video; + } + + ret = media_device_register(&dev->mdev); + if (ret) { + v4l2_err(&dev->v4l2_dev, "Failed to register media device\n"); + goto err_m2m_mc; + } + + platform_set_drvdata(pdev, dev); + + return 0; + +err_m2m_mc: + v4l2_m2m_unregister_media_controller(dev->m2m_dev); +err_video: + video_unregister_device(&dev->vfd); +err_m2m: + v4l2_m2m_release(dev->m2m_dev); +err_v4l2: + v4l2_device_unregister(&dev->v4l2_dev); + + return ret; +} + +static void rpivid_remove(struct platform_device *pdev) +{ + struct rpivid_dev *dev = platform_get_drvdata(pdev); + + if (media_devnode_is_registered(dev->mdev.devnode)) { + media_device_unregister(&dev->mdev); + v4l2_m2m_unregister_media_controller(dev->m2m_dev); + media_device_cleanup(&dev->mdev); + } + + v4l2_m2m_release(dev->m2m_dev); + video_unregister_device(&dev->vfd); + v4l2_device_unregister(&dev->v4l2_dev); + + rpivid_hw_remove(dev); +} + +static const struct of_device_id rpivid_dt_match[] = { + { + .compatible = "raspberrypi,rpivid-vid-decoder", + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, rpivid_dt_match); + +static struct platform_driver rpivid_driver = { + .probe = rpivid_probe, + .remove = rpivid_remove, + .driver = { + .name = RPIVID_NAME, + .of_match_table = of_match_ptr(rpivid_dt_match), + }, +}; +module_platform_driver(rpivid_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("John Cox <jc@kynesim.co.uk>"); +MODULE_DESCRIPTION("Raspberry Pi HEVC V4L2 driver"); diff --git a/drivers/staging/media/rpivid/rpivid.h b/drivers/staging/media/rpivid/rpivid.h new file mode 100644 index 00000000000000..9d6c2adb331b5b --- /dev/null +++ b/drivers/staging/media/rpivid/rpivid.h @@ -0,0 +1,203 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Raspberry Pi HEVC driver + * + * Copyright (C) 2020 Raspberry Pi (Trading) Ltd + * + * Based on the Cedrus VPU driver, that is: + * + * Copyright (C) 2016 Florent Revest <florent.revest@free-electrons.com> + * Copyright (C) 2018 Paul Kocialkowski <paul.kocialkowski@bootlin.com> + * Copyright (C) 2018 Bootlin + */ + +#ifndef _RPIVID_H_ +#define _RPIVID_H_ + +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-mem2mem.h> +#include <media/videobuf2-v4l2.h> +#include <media/videobuf2-dma-contig.h> + +#define OPT_DEBUG_POLL_IRQ 0 + +#define RPIVID_DEC_ENV_COUNT 6 +#define RPIVID_P1BUF_COUNT 3 +#define RPIVID_P2BUF_COUNT 3 + +#define RPIVID_NAME "rpivid" + +#define RPIVID_CAPABILITY_UNTILED BIT(0) +#define RPIVID_CAPABILITY_H265_DEC BIT(1) + +#define RPIVID_QUIRK_NO_DMA_OFFSET BIT(0) + +enum rpivid_irq_status { + RPIVID_IRQ_NONE, + RPIVID_IRQ_ERROR, + RPIVID_IRQ_OK, +}; + +struct rpivid_control { + struct v4l2_ctrl_config cfg; + unsigned char required:1; +}; + +struct rpivid_h265_run { + u32 slice_ents; + const struct v4l2_ctrl_hevc_sps *sps; + const struct v4l2_ctrl_hevc_pps *pps; + const struct v4l2_ctrl_hevc_decode_params *dec; + const struct v4l2_ctrl_hevc_slice_params *slice_params; + const struct v4l2_ctrl_hevc_scaling_matrix *scaling_matrix; +}; + +struct rpivid_run { + struct vb2_v4l2_buffer *src; + struct vb2_v4l2_buffer *dst; + + struct rpivid_h265_run h265; +}; + +struct rpivid_buffer { + struct v4l2_m2m_buffer m2m_buf; +}; + +struct rpivid_dec_state; +struct rpivid_dec_env; + +struct rpivid_gptr { + size_t size; + __u8 *ptr; + dma_addr_t addr; + unsigned long attrs; +}; + +struct rpivid_dev; +typedef void (*rpivid_irq_callback)(struct rpivid_dev *dev, void *ctx); + +struct rpivid_q_aux; +#define RPIVID_AUX_ENT_COUNT VB2_MAX_FRAME + +struct rpivid_ctx { + struct v4l2_fh fh; + struct rpivid_dev *dev; + + struct v4l2_pix_format_mplane src_fmt; + struct v4l2_pix_format_mplane dst_fmt; + int dst_fmt_set; + + int src_stream_on; + int dst_stream_on; + + // fatal_err is set if an error has occurred s.t. decode cannot + // continue (such as running out of CMA) + int fatal_err; + + /* Lock for queue operations */ + struct mutex ctx_mutex; + + struct v4l2_ctrl_handler hdl; + struct v4l2_ctrl **ctrls; + + /* Decode state - stateless decoder my *** */ + /* state contains stuff that is only needed in phase0 + * it could be held in dec_env but that would be wasteful + */ + struct rpivid_dec_state *state; + struct rpivid_dec_env *dec0; + + /* Spinlock protecting dec_free */ + spinlock_t dec_lock; + struct rpivid_dec_env *dec_free; + + struct rpivid_dec_env *dec_pool; + + unsigned int p1idx; + atomic_t p1out; + struct rpivid_gptr bitbufs[RPIVID_P1BUF_COUNT]; + + /* *** Should be in dev *** */ + unsigned int p2idx; + struct rpivid_gptr pu_bufs[RPIVID_P2BUF_COUNT]; + struct rpivid_gptr coeff_bufs[RPIVID_P2BUF_COUNT]; + + /* Spinlock protecting aux_free */ + spinlock_t aux_lock; + struct rpivid_q_aux *aux_free; + + struct rpivid_q_aux *aux_ents[RPIVID_AUX_ENT_COUNT]; + + unsigned int colmv_stride; + unsigned int colmv_picsize; +}; + +struct rpivid_dec_ops { + void (*setup)(struct rpivid_ctx *ctx, struct rpivid_run *run); + int (*start)(struct rpivid_ctx *ctx); + void (*stop)(struct rpivid_ctx *ctx); + void (*trigger)(struct rpivid_ctx *ctx); +}; + +struct rpivid_variant { + unsigned int capabilities; + unsigned int quirks; + unsigned int mod_rate; +}; + +struct rpivid_hw_irq_ent; + +#define RPIVID_ICTL_ENABLE_UNLIMITED (-1) + +struct rpivid_hw_irq_ctrl { + /* Spinlock protecting claim and tail */ + spinlock_t lock; + struct rpivid_hw_irq_ent *claim; + struct rpivid_hw_irq_ent *tail; + + /* Ent for pending irq - also prevents sched */ + struct rpivid_hw_irq_ent *irq; + /* Non-zero => do not start a new job - outer layer sched pending */ + int no_sched; + /* Enable count. -1 always OK, 0 do not sched, +ve shed & count down */ + int enable; + /* Thread CB requested */ + bool thread_reqed; +}; + +struct rpivid_dev { + struct v4l2_device v4l2_dev; + struct video_device vfd; + struct media_device mdev; + struct media_pad pad[2]; + struct platform_device *pdev; + struct device *dev; + struct v4l2_m2m_dev *m2m_dev; + const struct rpivid_dec_ops *dec_ops; + + /* Device file mutex */ + struct mutex dev_mutex; + + void __iomem *base_irq; + void __iomem *base_h265; + + struct clk *clock; + unsigned long max_clock_rate; + + int cache_align; + + struct rpivid_hw_irq_ctrl ic_active1; + struct rpivid_hw_irq_ctrl ic_active2; +}; + +extern const struct rpivid_dec_ops rpivid_dec_ops_h265; +extern const struct v4l2_ctrl_ops rpivid_hevc_sps_ctrl_ops; +extern const struct v4l2_ctrl_ops rpivid_hevc_pps_ctrl_ops; + +struct v4l2_ctrl *rpivid_find_ctrl(struct rpivid_ctx *ctx, u32 id); +void *rpivid_find_control_data(struct rpivid_ctx *ctx, u32 id); + +#endif diff --git a/drivers/staging/media/rpivid/rpivid_dec.c b/drivers/staging/media/rpivid/rpivid_dec.c new file mode 100644 index 00000000000000..e51408dabbdb99 --- /dev/null +++ b/drivers/staging/media/rpivid/rpivid_dec.c @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Raspberry Pi HEVC driver + * + * Copyright (C) 2020 Raspberry Pi (Trading) Ltd + * + * Based on the Cedrus VPU driver, that is: + * + * Copyright (C) 2016 Florent Revest <florent.revest@free-electrons.com> + * Copyright (C) 2018 Paul Kocialkowski <paul.kocialkowski@bootlin.com> + * Copyright (C) 2018 Bootlin + */ + +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-event.h> +#include <media/v4l2-mem2mem.h> + +#include "rpivid.h" +#include "rpivid_dec.h" + +void rpivid_device_run(void *priv) +{ + struct rpivid_ctx *const ctx = priv; + struct rpivid_dev *const dev = ctx->dev; + struct rpivid_run run = {}; + struct media_request *src_req; + + run.src = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + run.dst = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + + if (!run.src || !run.dst) { + v4l2_err(&dev->v4l2_dev, "%s: Missing buffer: src=%p, dst=%p\n", + __func__, run.src, run.dst); + goto fail; + } + + /* Apply request(s) controls */ + src_req = run.src->vb2_buf.req_obj.req; + if (!src_req) { + v4l2_err(&dev->v4l2_dev, "%s: Missing request\n", __func__); + goto fail; + } + + v4l2_ctrl_request_setup(src_req, &ctx->hdl); + + switch (ctx->src_fmt.pixelformat) { + case V4L2_PIX_FMT_HEVC_SLICE: + { + const struct v4l2_ctrl *ctrl; + + run.h265.sps = + rpivid_find_control_data(ctx, + V4L2_CID_STATELESS_HEVC_SPS); + run.h265.pps = + rpivid_find_control_data(ctx, + V4L2_CID_STATELESS_HEVC_PPS); + run.h265.dec = + rpivid_find_control_data(ctx, + V4L2_CID_STATELESS_HEVC_DECODE_PARAMS); + + ctrl = rpivid_find_ctrl(ctx, + V4L2_CID_STATELESS_HEVC_SLICE_PARAMS); + if (!ctrl || !ctrl->elems) { + v4l2_err(&dev->v4l2_dev, "%s: Missing slice params\n", + __func__); + goto fail; + } + run.h265.slice_ents = ctrl->elems; + run.h265.slice_params = ctrl->p_cur.p; + + run.h265.scaling_matrix = + rpivid_find_control_data(ctx, + V4L2_CID_STATELESS_HEVC_SCALING_MATRIX); + break; + } + + default: + break; + } + + v4l2_m2m_buf_copy_metadata(run.src, run.dst, true); + + dev->dec_ops->setup(ctx, &run); + + /* Complete request(s) controls */ + v4l2_ctrl_request_complete(src_req, &ctx->hdl); + + dev->dec_ops->trigger(ctx); + return; + +fail: + /* We really shouldn't get here but tidy up what we can */ + v4l2_m2m_buf_done_and_job_finish(dev->m2m_dev, ctx->fh.m2m_ctx, + VB2_BUF_STATE_ERROR); +} diff --git a/drivers/staging/media/rpivid/rpivid_dec.h b/drivers/staging/media/rpivid/rpivid_dec.h new file mode 100644 index 00000000000000..8f15bb6406abc6 --- /dev/null +++ b/drivers/staging/media/rpivid/rpivid_dec.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Raspberry Pi HEVC driver + * + * Copyright (C) 2020 Raspberry Pi (Trading) Ltd + * + * Based on the Cedrus VPU driver, that is: + * + * Copyright (C) 2016 Florent Revest <florent.revest@free-electrons.com> + * Copyright (C) 2018 Paul Kocialkowski <paul.kocialkowski@bootlin.com> + * Copyright (C) 2018 Bootlin + */ + +#ifndef _RPIVID_DEC_H_ +#define _RPIVID_DEC_H_ + +void rpivid_device_run(void *priv); + +#endif diff --git a/drivers/staging/media/rpivid/rpivid_h265.c b/drivers/staging/media/rpivid/rpivid_h265.c new file mode 100644 index 00000000000000..566f65caf2a92d --- /dev/null +++ b/drivers/staging/media/rpivid/rpivid_h265.c @@ -0,0 +1,2712 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Raspberry Pi HEVC driver + * + * Copyright (C) 2020 Raspberry Pi (Trading) Ltd + * + * Based on the Cedrus VPU driver, that is: + * + * Copyright (C) 2016 Florent Revest <florent.revest@free-electrons.com> + * Copyright (C) 2018 Paul Kocialkowski <paul.kocialkowski@bootlin.com> + * Copyright (C) 2018 Bootlin + */ + +#include <linux/delay.h> +#include <linux/types.h> + +#include <media/videobuf2-dma-contig.h> + +#include "rpivid.h" +#include "rpivid_hw.h" +#include "rpivid_video.h" + +#define DEBUG_TRACE_P1_CMD 0 +#define DEBUG_TRACE_EXECUTION 0 + +#define USE_REQUEST_PIN 1 + +#if DEBUG_TRACE_EXECUTION +#define xtrace_in(dev_, de_)\ + v4l2_info(&(dev_)->v4l2_dev, "%s[%d]: in\n", __func__,\ + (de_) == NULL ? -1 : (de_)->decode_order) +#define xtrace_ok(dev_, de_)\ + v4l2_info(&(dev_)->v4l2_dev, "%s[%d]: ok\n", __func__,\ + (de_) == NULL ? -1 : (de_)->decode_order) +#define xtrace_fin(dev_, de_)\ + v4l2_info(&(dev_)->v4l2_dev, "%s[%d]: finish\n", __func__,\ + (de_) == NULL ? -1 : (de_)->decode_order) +#define xtrace_fail(dev_, de_)\ + v4l2_info(&(dev_)->v4l2_dev, "%s[%d]: FAIL\n", __func__,\ + (de_) == NULL ? -1 : (de_)->decode_order) +#else +#define xtrace_in(dev_, de_) +#define xtrace_ok(dev_, de_) +#define xtrace_fin(dev_, de_) +#define xtrace_fail(dev_, de_) +#endif + +enum hevc_slice_type { + HEVC_SLICE_B = 0, + HEVC_SLICE_P = 1, + HEVC_SLICE_I = 2, +}; + +enum hevc_layer { L0 = 0, L1 = 1 }; + +static int gptr_alloc(struct rpivid_dev *const dev, struct rpivid_gptr *gptr, + size_t size, unsigned long attrs) +{ + gptr->size = size; + gptr->attrs = attrs; + gptr->addr = 0; + gptr->ptr = dma_alloc_attrs(dev->dev, gptr->size, &gptr->addr, + GFP_KERNEL, gptr->attrs); + return !gptr->ptr ? -ENOMEM : 0; +} + +static void gptr_free(struct rpivid_dev *const dev, + struct rpivid_gptr *const gptr) +{ + if (gptr->ptr) + dma_free_attrs(dev->dev, gptr->size, gptr->ptr, gptr->addr, + gptr->attrs); + gptr->size = 0; + gptr->ptr = NULL; + gptr->addr = 0; + gptr->attrs = 0; +} + +/* Realloc but do not copy + * + * Frees then allocs. + * If the alloc fails then it attempts to re-allocote the old size + * On error then check gptr->ptr to determine if anything is currently + * allocated. + */ +static int gptr_realloc_new(struct rpivid_dev * const dev, + struct rpivid_gptr * const gptr, size_t size) +{ + const size_t old_size = gptr->size; + + if (size == gptr->size) + return 0; + + if (gptr->ptr) + dma_free_attrs(dev->dev, gptr->size, gptr->ptr, + gptr->addr, gptr->attrs); + + gptr->addr = 0; + gptr->size = size; + gptr->ptr = dma_alloc_attrs(dev->dev, gptr->size, + &gptr->addr, GFP_KERNEL, gptr->attrs); + + if (!gptr->ptr) { + gptr->addr = 0; + gptr->size = old_size; + gptr->ptr = dma_alloc_attrs(dev->dev, gptr->size, + &gptr->addr, GFP_KERNEL, gptr->attrs); + if (!gptr->ptr) { + gptr->size = 0; + gptr->addr = 0; + gptr->attrs = 0; + } + return -ENOMEM; + } + + return 0; +} + +static size_t next_size(const size_t x) +{ + return rpivid_round_up_size(x + 1); +} + +#define NUM_SCALING_FACTORS 4064 /* Not a typo = 0xbe0 + 0x400 */ + +#define AXI_BASE64 0 + +#define PROB_BACKUP ((20 << 12) + (20 << 6) + (0 << 0)) +#define PROB_RELOAD ((20 << 12) + (20 << 0) + (0 << 6)) + +#define HEVC_MAX_REFS V4L2_HEVC_DPB_ENTRIES_NUM_MAX + +////////////////////////////////////////////////////////////////////////////// + +struct rpi_cmd { + u32 addr; + u32 data; +} __packed; + +struct rpivid_q_aux { + unsigned int refcount; + unsigned int q_index; + struct rpivid_q_aux *next; + struct rpivid_gptr col; +}; + +////////////////////////////////////////////////////////////////////////////// + +enum rpivid_decode_state { + RPIVID_DECODE_SLICE_START, + RPIVID_DECODE_SLICE_CONTINUE, + RPIVID_DECODE_ERROR_CONTINUE, + RPIVID_DECODE_ERROR_DONE, + RPIVID_DECODE_PHASE1, + RPIVID_DECODE_END, +}; + +struct rpivid_dec_env { + struct rpivid_ctx *ctx; + struct rpivid_dec_env *next; + + enum rpivid_decode_state state; + unsigned int decode_order; + int p1_status; /* P1 status - what to realloc */ + + struct rpi_cmd *cmd_fifo; + unsigned int cmd_len, cmd_max; + unsigned int num_slice_msgs; + unsigned int pic_width_in_ctbs_y; + unsigned int pic_height_in_ctbs_y; + unsigned int dpbno_col; + u32 reg_slicestart; + int collocated_from_l0_flag; + /* + * Last CTB/Tile X,Y processed by (wpp_)entry_point + * Could be in _state as P0 only but needs updating where _state + * is const + */ + unsigned int entry_ctb_x; + unsigned int entry_ctb_y; + unsigned int entry_tile_x; + unsigned int entry_tile_y; + unsigned int entry_qp; + u32 entry_slice; + + u32 rpi_config2; + u32 rpi_framesize; + u32 rpi_currpoc; + + struct vb2_v4l2_buffer *frame_buf; // Detached dest buffer + struct vb2_v4l2_buffer *src_buf; // Detached src buffer + unsigned int frame_c_offset; + unsigned int frame_stride; + dma_addr_t frame_addr; + dma_addr_t ref_addrs[16]; + struct rpivid_q_aux *frame_aux; + struct rpivid_q_aux *col_aux; + + dma_addr_t cmd_addr; + size_t cmd_size; + + dma_addr_t pu_base_vc; + dma_addr_t coeff_base_vc; + u32 pu_stride; + u32 coeff_stride; + + struct rpivid_gptr *bit_copy_gptr; + size_t bit_copy_len; + +#define SLICE_MSGS_MAX (2 * HEVC_MAX_REFS * 8 + 3) + u16 slice_msgs[SLICE_MSGS_MAX]; + u8 scaling_factors[NUM_SCALING_FACTORS]; + +#if USE_REQUEST_PIN + struct media_request *req_pin; +#else + struct media_request_object *req_obj; +#endif + struct rpivid_hw_irq_ent irq_ent; +}; + +#define member_size(type, member) sizeof(((type *)0)->member) + +struct rpivid_dec_state { + struct v4l2_ctrl_hevc_sps sps; + struct v4l2_ctrl_hevc_pps pps; + + // Helper vars & tables derived from sps/pps + unsigned int log2_ctb_size; /* log2 width of a CTB */ + unsigned int ctb_width; /* Width in CTBs */ + unsigned int ctb_height; /* Height in CTBs */ + unsigned int ctb_size; /* Pic area in CTBs */ + unsigned int tile_width; /* Width in tiles */ + unsigned int tile_height; /* Height in tiles */ + + int *col_bd; + int *row_bd; + int *ctb_addr_rs_to_ts; + int *ctb_addr_ts_to_rs; + + // Aux starage for DPB + // Hold refs + struct rpivid_q_aux *ref_aux[HEVC_MAX_REFS]; + struct rpivid_q_aux *frame_aux; + + // Slice vars + unsigned int slice_idx; + bool slice_temporal_mvp; /* Slice flag but constant for frame */ + bool use_aux; + bool mk_aux; + + // Temp vars per run - don't actually need to persist + u8 *src_buf; + dma_addr_t src_addr; + const struct v4l2_ctrl_hevc_slice_params *sh; + const struct v4l2_ctrl_hevc_decode_params *dec; + unsigned int nb_refs[2]; + unsigned int slice_qp; + unsigned int max_num_merge_cand; // 0 if I-slice + bool dependent_slice_segment_flag; + + unsigned int start_ts; /* slice_segment_addr -> ts */ + unsigned int start_ctb_x; /* CTB X,Y of start_ts */ + unsigned int start_ctb_y; + unsigned int prev_ctb_x; /* CTB X,Y of start_ts - 1 */ + unsigned int prev_ctb_y; +}; + +#if !USE_REQUEST_PIN +static void dst_req_obj_release(struct media_request_object *object) +{ + kfree(object); +} + +static const struct media_request_object_ops dst_req_obj_ops = { + .release = dst_req_obj_release, +}; +#endif + +static inline int clip_int(const int x, const int lo, const int hi) +{ + return x < lo ? lo : x > hi ? hi : x; +} + +////////////////////////////////////////////////////////////////////////////// +// Phase 1 command and bit FIFOs + +#if DEBUG_TRACE_P1_CMD +static int p1_z; +#endif + +static int cmds_check_space(struct rpivid_dec_env *const de, unsigned int n) +{ + struct rpi_cmd *a; + unsigned int newmax; + + if (n > 0x100000) { + v4l2_err(&de->ctx->dev->v4l2_dev, + "%s: n %u implausible\n", __func__, n); + return -ENOMEM; + } + + if (de->cmd_len + n <= de->cmd_max) + return 0; + + newmax = roundup_pow_of_two(de->cmd_len + n); + + a = krealloc(de->cmd_fifo, newmax * sizeof(struct rpi_cmd), + GFP_KERNEL); + if (!a) { + v4l2_err(&de->ctx->dev->v4l2_dev, + "Failed cmd buffer realloc from %u to %u\n", + de->cmd_max, newmax); + return -ENOMEM; + } + v4l2_info(&de->ctx->dev->v4l2_dev, + "cmd buffer realloc from %u to %u\n", de->cmd_max, newmax); + + de->cmd_fifo = a; + de->cmd_max = newmax; + return 0; +} + +// ???? u16 addr - put in u32 +static void p1_apb_write(struct rpivid_dec_env *const de, const u16 addr, + const u32 data) +{ + if (de->cmd_len >= de->cmd_max) { + v4l2_err(&de->ctx->dev->v4l2_dev, + "%s: Overflow @ %d\n", __func__, de->cmd_len); + return; + } + + de->cmd_fifo[de->cmd_len].addr = addr; + de->cmd_fifo[de->cmd_len].data = data; + +#if DEBUG_TRACE_P1_CMD + if (++p1_z < 256) { + v4l2_info(&de->ctx->dev->v4l2_dev, "[%02x] %x %x\n", + de->cmd_len, addr, data); + } +#endif + de->cmd_len++; +} + +static int ctb_to_tile(unsigned int ctb, unsigned int *bd, int num) +{ + int i; + + for (i = 1; ctb >= bd[i]; i++) + ; // bd[] has num+1 elements; bd[0]=0; + return i - 1; +} + +static unsigned int ctb_to_tile_x(const struct rpivid_dec_state *const s, + const unsigned int ctb_x) +{ + return ctb_to_tile(ctb_x, s->col_bd, s->tile_width); +} + +static unsigned int ctb_to_tile_y(const struct rpivid_dec_state *const s, + const unsigned int ctb_y) +{ + return ctb_to_tile(ctb_y, s->row_bd, s->tile_height); +} + +static void aux_q_free(struct rpivid_ctx *const ctx, + struct rpivid_q_aux *const aq) +{ + struct rpivid_dev *const dev = ctx->dev; + + gptr_free(dev, &aq->col); + kfree(aq); +} + +static struct rpivid_q_aux *aux_q_alloc(struct rpivid_ctx *const ctx, + const unsigned int q_index) +{ + struct rpivid_dev *const dev = ctx->dev; + struct rpivid_q_aux *const aq = kzalloc(sizeof(*aq), GFP_KERNEL); + + if (!aq) + return NULL; + + if (gptr_alloc(dev, &aq->col, ctx->colmv_picsize, + DMA_ATTR_FORCE_CONTIGUOUS | DMA_ATTR_NO_KERNEL_MAPPING)) + goto fail; + + /* + * Spinlock not required as called in P0 only and + * aux checks done by _new + */ + aq->refcount = 1; + aq->q_index = q_index; + ctx->aux_ents[q_index] = aq; + return aq; + +fail: + kfree(aq); + return NULL; +} + +static struct rpivid_q_aux *aux_q_new(struct rpivid_ctx *const ctx, + const unsigned int q_index) +{ + struct rpivid_q_aux *aq; + unsigned long lockflags; + + spin_lock_irqsave(&ctx->aux_lock, lockflags); + /* + * If we already have this allocated to a slot then use that + * and assume that it will all work itself out in the pipeline + */ + if ((aq = ctx->aux_ents[q_index]) != NULL) { + ++aq->refcount; + } else if ((aq = ctx->aux_free) != NULL) { + ctx->aux_free = aq->next; + aq->next = NULL; + aq->refcount = 1; + aq->q_index = q_index; + ctx->aux_ents[q_index] = aq; + } + spin_unlock_irqrestore(&ctx->aux_lock, lockflags); + + if (!aq) + aq = aux_q_alloc(ctx, q_index); + + return aq; +} + +static struct rpivid_q_aux *aux_q_ref_idx(struct rpivid_ctx *const ctx, + const int q_index) +{ + unsigned long lockflags; + struct rpivid_q_aux *aq; + + spin_lock_irqsave(&ctx->aux_lock, lockflags); + if ((aq = ctx->aux_ents[q_index]) != NULL) + ++aq->refcount; + spin_unlock_irqrestore(&ctx->aux_lock, lockflags); + + return aq; +} + +static struct rpivid_q_aux *aux_q_ref(struct rpivid_ctx *const ctx, + struct rpivid_q_aux *const aq) +{ + if (aq) { + unsigned long lockflags; + + spin_lock_irqsave(&ctx->aux_lock, lockflags); + + ++aq->refcount; + + spin_unlock_irqrestore(&ctx->aux_lock, lockflags); + } + return aq; +} + +static void aux_q_release(struct rpivid_ctx *const ctx, + struct rpivid_q_aux **const paq) +{ + struct rpivid_q_aux *const aq = *paq; + unsigned long lockflags; + + if (!aq) + return; + + *paq = NULL; + + spin_lock_irqsave(&ctx->aux_lock, lockflags); + if (--aq->refcount == 0) { + aq->next = ctx->aux_free; + ctx->aux_free = aq; + ctx->aux_ents[aq->q_index] = NULL; + aq->q_index = ~0U; + } + spin_unlock_irqrestore(&ctx->aux_lock, lockflags); +} + +static void aux_q_init(struct rpivid_ctx *const ctx) +{ + spin_lock_init(&ctx->aux_lock); + ctx->aux_free = NULL; +} + +static void aux_q_uninit(struct rpivid_ctx *const ctx) +{ + struct rpivid_q_aux *aq; + + ctx->colmv_picsize = 0; + ctx->colmv_stride = 0; + while ((aq = ctx->aux_free) != NULL) { + ctx->aux_free = aq->next; + aux_q_free(ctx, aq); + } +} + +////////////////////////////////////////////////////////////////////////////// + +/* + * Initialisation process for context variables (CABAC init) + * see H.265 9.3.2.2 + * + * N.B. If comparing with FFmpeg note that this h/w uses slightly different + * offsets to FFmpegs array + */ + +/* Actual number of values */ +#define RPI_PROB_VALS 154U +/* Rounded up as we copy words */ +#define RPI_PROB_ARRAY_SIZE ((154 + 3) & ~3) + +/* Initialiser values - see tables H.265 9-4 through 9-42 */ +static const u8 prob_init[3][156] = { + { + 153, 200, 139, 141, 157, 154, 154, 154, 154, 154, 184, 154, 154, + 154, 184, 63, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, + 154, 154, 154, 153, 138, 138, 111, 141, 94, 138, 182, 154, 154, + 154, 140, 92, 137, 138, 140, 152, 138, 139, 153, 74, 149, 92, + 139, 107, 122, 152, 140, 179, 166, 182, 140, 227, 122, 197, 110, + 110, 124, 125, 140, 153, 125, 127, 140, 109, 111, 143, 127, 111, + 79, 108, 123, 63, 110, 110, 124, 125, 140, 153, 125, 127, 140, + 109, 111, 143, 127, 111, 79, 108, 123, 63, 91, 171, 134, 141, + 138, 153, 136, 167, 152, 152, 139, 139, 111, 111, 125, 110, 110, + 94, 124, 108, 124, 107, 125, 141, 179, 153, 125, 107, 125, 141, + 179, 153, 125, 107, 125, 141, 179, 153, 125, 140, 139, 182, 182, + 152, 136, 152, 136, 153, 136, 139, 111, 136, 139, 111, 0, 0, + }, + { + 153, 185, 107, 139, 126, 197, 185, 201, 154, 149, 154, 139, 154, + 154, 154, 152, 110, 122, 95, 79, 63, 31, 31, 153, 153, 168, + 140, 198, 79, 124, 138, 94, 153, 111, 149, 107, 167, 154, 154, + 154, 154, 196, 196, 167, 154, 152, 167, 182, 182, 134, 149, 136, + 153, 121, 136, 137, 169, 194, 166, 167, 154, 167, 137, 182, 125, + 110, 94, 110, 95, 79, 125, 111, 110, 78, 110, 111, 111, 95, + 94, 108, 123, 108, 125, 110, 94, 110, 95, 79, 125, 111, 110, + 78, 110, 111, 111, 95, 94, 108, 123, 108, 121, 140, 61, 154, + 107, 167, 91, 122, 107, 167, 139, 139, 155, 154, 139, 153, 139, + 123, 123, 63, 153, 166, 183, 140, 136, 153, 154, 166, 183, 140, + 136, 153, 154, 166, 183, 140, 136, 153, 154, 170, 153, 123, 123, + 107, 121, 107, 121, 167, 151, 183, 140, 151, 183, 140, 0, 0, + }, + { + 153, 160, 107, 139, 126, 197, 185, 201, 154, 134, 154, 139, 154, + 154, 183, 152, 154, 137, 95, 79, 63, 31, 31, 153, 153, 168, + 169, 198, 79, 224, 167, 122, 153, 111, 149, 92, 167, 154, 154, + 154, 154, 196, 167, 167, 154, 152, 167, 182, 182, 134, 149, 136, + 153, 121, 136, 122, 169, 208, 166, 167, 154, 152, 167, 182, 125, + 110, 124, 110, 95, 94, 125, 111, 111, 79, 125, 126, 111, 111, + 79, 108, 123, 93, 125, 110, 124, 110, 95, 94, 125, 111, 111, + 79, 125, 126, 111, 111, 79, 108, 123, 93, 121, 140, 61, 154, + 107, 167, 91, 107, 107, 167, 139, 139, 170, 154, 139, 153, 139, + 123, 123, 63, 124, 166, 183, 140, 136, 153, 154, 166, 183, 140, + 136, 153, 154, 166, 183, 140, 136, 153, 154, 170, 153, 138, 138, + 122, 121, 122, 121, 167, 151, 183, 140, 151, 183, 140, 0, 0, + }, +}; + +#define CMDS_WRITE_PROB ((RPI_PROB_ARRAY_SIZE / 4) + 1) +static void write_prob(struct rpivid_dec_env *const de, + const struct rpivid_dec_state *const s) +{ + u8 dst[RPI_PROB_ARRAY_SIZE]; + + const unsigned int init_type = + ((s->sh->flags & V4L2_HEVC_SLICE_PARAMS_FLAG_CABAC_INIT) != 0 && + s->sh->slice_type != HEVC_SLICE_I) ? + s->sh->slice_type + 1 : + 2 - s->sh->slice_type; + const u8 *p = prob_init[init_type]; + const int q = clip_int(s->slice_qp, 0, 51); + unsigned int i; + + for (i = 0; i < RPI_PROB_VALS; i++) { + int init_value = p[i]; + int m = (init_value >> 4) * 5 - 45; + int n = ((init_value & 15) << 3) - 16; + int pre = 2 * (((m * q) >> 4) + n) - 127; + + pre ^= pre >> 31; + if (pre > 124) + pre = 124 + (pre & 1); + dst[i] = pre; + } + for (i = RPI_PROB_VALS; i != RPI_PROB_ARRAY_SIZE; ++i) + dst[i] = 0; + + for (i = 0; i < RPI_PROB_ARRAY_SIZE; i += 4) + p1_apb_write(de, 0x1000 + i, + dst[i] + (dst[i + 1] << 8) + (dst[i + 2] << 16) + + (dst[i + 3] << 24)); + + /* + * Having written the prob array back it up + * This is not always needed but is a small overhead that simplifies + * (and speeds up) some multi-tile & WPP scenarios + * There are no scenarios where having written a prob we ever want + * a previous (non-initial) state back + */ + p1_apb_write(de, RPI_TRANSFER, PROB_BACKUP); +} + +#define CMDS_WRITE_SCALING_FACTORS NUM_SCALING_FACTORS +static void write_scaling_factors(struct rpivid_dec_env *const de) +{ + int i; + const u8 *p = (u8 *)de->scaling_factors; + + for (i = 0; i < NUM_SCALING_FACTORS; i += 4, p += 4) + p1_apb_write(de, 0x2000 + i, + p[0] + (p[1] << 8) + (p[2] << 16) + (p[3] << 24)); +} + +static inline __u32 dma_to_axi_addr(dma_addr_t a) +{ + return (__u32)(a >> 6); +} + +#define CMDS_WRITE_BITSTREAM 4 +static int write_bitstream(struct rpivid_dec_env *const de, + const struct rpivid_dec_state *const s) +{ + // Note that FFmpeg V4L2 does not remove emulation prevention bytes, + // so this is matched in the configuration here. + // Whether that is the correct behaviour or not is not clear in the + // spec. + const int rpi_use_emu = 1; + unsigned int offset = s->sh->data_byte_offset; + const unsigned int len = (s->sh->bit_size + 7) / 8 - offset; + dma_addr_t addr; + + if (s->src_addr != 0) { + addr = s->src_addr + offset; + } else { + if (len + de->bit_copy_len > de->bit_copy_gptr->size) { + v4l2_warn(&de->ctx->dev->v4l2_dev, + "Bit copy buffer overflow: size=%zu, offset=%zu, len=%u\n", + de->bit_copy_gptr->size, + de->bit_copy_len, len); + return -ENOMEM; + } + memcpy(de->bit_copy_gptr->ptr + de->bit_copy_len, + s->src_buf + offset, len); + addr = de->bit_copy_gptr->addr + de->bit_copy_len; + de->bit_copy_len += (len + 63) & ~63; + } + offset = addr & 63; + + p1_apb_write(de, RPI_BFBASE, dma_to_axi_addr(addr)); + p1_apb_write(de, RPI_BFNUM, len); + p1_apb_write(de, RPI_BFCONTROL, offset + (1 << 7)); // Stop + p1_apb_write(de, RPI_BFCONTROL, offset + (rpi_use_emu << 6)); + return 0; +} + +////////////////////////////////////////////////////////////////////////////// + +/* + * The slice constant part of the slice register - width and height need to + * be ORed in later as they are per-tile / WPP-row + */ +static u32 slice_reg_const(const struct rpivid_dec_state *const s) +{ + u32 x = (s->max_num_merge_cand << 0) | + (s->nb_refs[L0] << 4) | + (s->nb_refs[L1] << 8) | + (s->sh->slice_type << 12); + + if (s->sh->flags & V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_SAO_LUMA) + x |= BIT(14); + if (s->sh->flags & V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_SAO_CHROMA) + x |= BIT(15); + if (s->sh->slice_type == HEVC_SLICE_B && + (s->sh->flags & V4L2_HEVC_SLICE_PARAMS_FLAG_MVD_L1_ZERO)) + x |= BIT(16); + + return x; +} + +////////////////////////////////////////////////////////////////////////////// + +#define CMDS_NEW_SLICE_SEGMENT (4 + CMDS_WRITE_SCALING_FACTORS) +static void new_slice_segment(struct rpivid_dec_env *const de, + const struct rpivid_dec_state *const s) +{ + const struct v4l2_ctrl_hevc_sps *const sps = &s->sps; + const struct v4l2_ctrl_hevc_pps *const pps = &s->pps; + + p1_apb_write(de, + RPI_SPS0, + ((sps->log2_min_luma_coding_block_size_minus3 + 3) << 0) | + (s->log2_ctb_size << 4) | + ((sps->log2_min_luma_transform_block_size_minus2 + 2) + << 8) | + ((sps->log2_min_luma_transform_block_size_minus2 + 2 + + sps->log2_diff_max_min_luma_transform_block_size) + << 12) | + ((sps->bit_depth_luma_minus8 + 8) << 16) | + ((sps->bit_depth_chroma_minus8 + 8) << 20) | + (sps->max_transform_hierarchy_depth_intra << 24) | + (sps->max_transform_hierarchy_depth_inter << 28)); + + p1_apb_write(de, + RPI_SPS1, + ((sps->pcm_sample_bit_depth_luma_minus1 + 1) << 0) | + ((sps->pcm_sample_bit_depth_chroma_minus1 + 1) << 4) | + ((sps->log2_min_pcm_luma_coding_block_size_minus3 + 3) + << 8) | + ((sps->log2_min_pcm_luma_coding_block_size_minus3 + 3 + + sps->log2_diff_max_min_pcm_luma_coding_block_size) + << 12) | + (((sps->flags & V4L2_HEVC_SPS_FLAG_SEPARATE_COLOUR_PLANE) ? + 0 : sps->chroma_format_idc) << 16) | + ((!!(sps->flags & V4L2_HEVC_SPS_FLAG_AMP_ENABLED)) << 18) | + ((!!(sps->flags & V4L2_HEVC_SPS_FLAG_PCM_ENABLED)) << 19) | + ((!!(sps->flags & V4L2_HEVC_SPS_FLAG_SCALING_LIST_ENABLED)) + << 20) | + ((!!(sps->flags & + V4L2_HEVC_SPS_FLAG_STRONG_INTRA_SMOOTHING_ENABLED)) + << 21)); + + p1_apb_write(de, + RPI_PPS, + ((s->log2_ctb_size - pps->diff_cu_qp_delta_depth) << 0) | + ((!!(pps->flags & V4L2_HEVC_PPS_FLAG_CU_QP_DELTA_ENABLED)) + << 4) | + ((!!(pps->flags & + V4L2_HEVC_PPS_FLAG_TRANSQUANT_BYPASS_ENABLED)) + << 5) | + ((!!(pps->flags & V4L2_HEVC_PPS_FLAG_TRANSFORM_SKIP_ENABLED)) + << 6) | + ((!!(pps->flags & + V4L2_HEVC_PPS_FLAG_SIGN_DATA_HIDING_ENABLED)) + << 7) | + (((pps->pps_cb_qp_offset + s->sh->slice_cb_qp_offset) & 255) + << 8) | + (((pps->pps_cr_qp_offset + s->sh->slice_cr_qp_offset) & 255) + << 16) | + ((!!(pps->flags & + V4L2_HEVC_PPS_FLAG_CONSTRAINED_INTRA_PRED)) + << 24)); + + if (!s->start_ts && + (sps->flags & V4L2_HEVC_SPS_FLAG_SCALING_LIST_ENABLED) != 0) + write_scaling_factors(de); + + if (!s->dependent_slice_segment_flag) { + int ctb_col = s->sh->slice_segment_addr % + de->pic_width_in_ctbs_y; + int ctb_row = s->sh->slice_segment_addr / + de->pic_width_in_ctbs_y; + + de->reg_slicestart = (ctb_col << 0) + (ctb_row << 16); + } + + p1_apb_write(de, RPI_SLICESTART, de->reg_slicestart); +} + +////////////////////////////////////////////////////////////////////////////// +// Slice messages + +static void msg_slice(struct rpivid_dec_env *const de, const u16 msg) +{ + de->slice_msgs[de->num_slice_msgs++] = msg; +} + +#define CMDS_PROGRAM_SLICECMDS (1 + SLICE_MSGS_MAX) +static void program_slicecmds(struct rpivid_dec_env *const de, + const int sliceid) +{ + int i; + + p1_apb_write(de, RPI_SLICECMDS, de->num_slice_msgs + (sliceid << 8)); + + for (i = 0; i < de->num_slice_msgs; i++) + p1_apb_write(de, 0x4000 + 4 * i, de->slice_msgs[i] & 0xffff); +} + +// NoBackwardPredictionFlag 8.3.5 +// Simply checks POCs +static int has_backward(const struct v4l2_hevc_dpb_entry *const dpb, + const __u8 *const idx, const unsigned int n, + const s32 cur_poc) +{ + unsigned int i; + + for (i = 0; i < n; ++i) { + if (cur_poc < dpb[idx[i]].pic_order_cnt_val) + return 0; + } + return 1; +} + +static void pre_slice_decode(struct rpivid_dec_env *const de, + const struct rpivid_dec_state *const s) +{ + const struct v4l2_ctrl_hevc_slice_params *const sh = s->sh; + const struct v4l2_ctrl_hevc_decode_params *const dec = s->dec; + int weighted_pred_flag, idx; + u16 cmd_slice; + unsigned int collocated_from_l0_flag; + + de->num_slice_msgs = 0; + + cmd_slice = 0; + if (sh->slice_type == HEVC_SLICE_I) + cmd_slice = 1; + if (sh->slice_type == HEVC_SLICE_P) + cmd_slice = 2; + if (sh->slice_type == HEVC_SLICE_B) + cmd_slice = 3; + + cmd_slice |= (s->nb_refs[L0] << 2) | (s->nb_refs[L1] << 6) | + (s->max_num_merge_cand << 11); + + collocated_from_l0_flag = + !s->slice_temporal_mvp || + sh->slice_type != HEVC_SLICE_B || + (sh->flags & V4L2_HEVC_SLICE_PARAMS_FLAG_COLLOCATED_FROM_L0); + cmd_slice |= collocated_from_l0_flag << 14; + + if (sh->slice_type == HEVC_SLICE_P || sh->slice_type == HEVC_SLICE_B) { + // Flag to say all reference pictures are from the past + const int no_backward_pred_flag = + has_backward(dec->dpb, sh->ref_idx_l0, s->nb_refs[L0], + sh->slice_pic_order_cnt) && + has_backward(dec->dpb, sh->ref_idx_l1, s->nb_refs[L1], + sh->slice_pic_order_cnt); + cmd_slice |= no_backward_pred_flag << 10; + msg_slice(de, cmd_slice); + + if (s->slice_temporal_mvp) { + const __u8 *const rpl = collocated_from_l0_flag ? + sh->ref_idx_l0 : sh->ref_idx_l1; + de->dpbno_col = rpl[sh->collocated_ref_idx]; + //v4l2_info(&de->ctx->dev->v4l2_dev, + // "L0=%d col_ref_idx=%d, + // dpb_no=%d\n", collocated_from_l0_flag, + // sh->collocated_ref_idx, de->dpbno_col); + } + + // Write reference picture descriptions + weighted_pred_flag = + sh->slice_type == HEVC_SLICE_P ? + !!(s->pps.flags & V4L2_HEVC_PPS_FLAG_WEIGHTED_PRED) : + !!(s->pps.flags & V4L2_HEVC_PPS_FLAG_WEIGHTED_BIPRED); + + for (idx = 0; idx < s->nb_refs[L0]; ++idx) { + unsigned int dpb_no = sh->ref_idx_l0[idx]; + //v4l2_info(&de->ctx->dev->v4l2_dev, + // "L0[%d]=dpb[%d]\n", idx, dpb_no); + + msg_slice(de, + dpb_no | + ((dec->dpb[dpb_no].flags & + V4L2_HEVC_DPB_ENTRY_LONG_TERM_REFERENCE) ? + (1 << 4) : 0) | + (weighted_pred_flag ? (3 << 5) : 0)); + msg_slice(de, dec->dpb[dpb_no].pic_order_cnt_val & 0xffff); + + if (weighted_pred_flag) { + const struct v4l2_hevc_pred_weight_table + *const w = &sh->pred_weight_table; + const int luma_weight_denom = + (1 << w->luma_log2_weight_denom); + const unsigned int chroma_log2_weight_denom = + (w->luma_log2_weight_denom + + w->delta_chroma_log2_weight_denom); + const int chroma_weight_denom = + (1 << chroma_log2_weight_denom); + + msg_slice(de, + w->luma_log2_weight_denom | + (((w->delta_luma_weight_l0[idx] + + luma_weight_denom) & 0x1ff) + << 3)); + msg_slice(de, w->luma_offset_l0[idx] & 0xff); + msg_slice(de, + chroma_log2_weight_denom | + (((w->delta_chroma_weight_l0[idx][0] + + chroma_weight_denom) & 0x1ff) + << 3)); + msg_slice(de, + w->chroma_offset_l0[idx][0] & 0xff); + msg_slice(de, + chroma_log2_weight_denom | + (((w->delta_chroma_weight_l0[idx][1] + + chroma_weight_denom) & 0x1ff) + << 3)); + msg_slice(de, + w->chroma_offset_l0[idx][1] & 0xff); + } + } + + for (idx = 0; idx < s->nb_refs[L1]; ++idx) { + unsigned int dpb_no = sh->ref_idx_l1[idx]; + //v4l2_info(&de->ctx->dev->v4l2_dev, + // "L1[%d]=dpb[%d]\n", idx, dpb_no); + msg_slice(de, + dpb_no | + ((dec->dpb[dpb_no].flags & + V4L2_HEVC_DPB_ENTRY_LONG_TERM_REFERENCE) ? + (1 << 4) : 0) | + (weighted_pred_flag ? (3 << 5) : 0)); + msg_slice(de, dec->dpb[dpb_no].pic_order_cnt_val & 0xffff); + if (weighted_pred_flag) { + const struct v4l2_hevc_pred_weight_table + *const w = &sh->pred_weight_table; + const int luma_weight_denom = + (1 << w->luma_log2_weight_denom); + const unsigned int chroma_log2_weight_denom = + (w->luma_log2_weight_denom + + w->delta_chroma_log2_weight_denom); + const int chroma_weight_denom = + (1 << chroma_log2_weight_denom); + + msg_slice(de, + w->luma_log2_weight_denom | + (((w->delta_luma_weight_l1[idx] + + luma_weight_denom) & 0x1ff) << 3)); + msg_slice(de, w->luma_offset_l1[idx] & 0xff); + msg_slice(de, + chroma_log2_weight_denom | + (((w->delta_chroma_weight_l1[idx][0] + + chroma_weight_denom) & 0x1ff) + << 3)); + msg_slice(de, + w->chroma_offset_l1[idx][0] & 0xff); + msg_slice(de, + chroma_log2_weight_denom | + (((w->delta_chroma_weight_l1[idx][1] + + chroma_weight_denom) & 0x1ff) + << 3)); + msg_slice(de, + w->chroma_offset_l1[idx][1] & 0xff); + } + } + } else { + msg_slice(de, cmd_slice); + } + + msg_slice(de, + (sh->slice_beta_offset_div2 & 15) | + ((sh->slice_tc_offset_div2 & 15) << 4) | + ((sh->flags & + V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_DEBLOCKING_FILTER_DISABLED) ? + 1 << 8 : 0) | + ((sh->flags & + V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_LOOP_FILTER_ACROSS_SLICES_ENABLED) ? + 1 << 9 : 0) | + ((s->pps.flags & + V4L2_HEVC_PPS_FLAG_LOOP_FILTER_ACROSS_TILES_ENABLED) ? + 1 << 10 : 0)); + + msg_slice(de, ((sh->slice_cr_qp_offset & 31) << 5) + + (sh->slice_cb_qp_offset & 31)); // CMD_QPOFF +} + +#define CMDS_WRITE_SLICE 1 +static void write_slice(struct rpivid_dec_env *const de, + const struct rpivid_dec_state *const s, + const u32 slice_const, + const unsigned int ctb_col, + const unsigned int ctb_row) +{ + const unsigned int cs = (1 << s->log2_ctb_size); + const unsigned int w_last = s->sps.pic_width_in_luma_samples & (cs - 1); + const unsigned int h_last = s->sps.pic_height_in_luma_samples & (cs - 1); + + p1_apb_write(de, RPI_SLICE, + slice_const | + ((ctb_col + 1 < s->ctb_width || !w_last ? + cs : w_last) << 17) | + ((ctb_row + 1 < s->ctb_height || !h_last ? + cs : h_last) << 24)); +} + +#define PAUSE_MODE_WPP 1 +#define PAUSE_MODE_TILE 0xffff + +/* + * N.B. This can be called to fill in data from the previous slice so must not + * use any state data that may change from slice to slice (e.g. qp) + */ +#define CMDS_NEW_ENTRY_POINT (6 + CMDS_WRITE_SLICE) +static void new_entry_point(struct rpivid_dec_env *const de, + const struct rpivid_dec_state *const s, + const bool do_bte, + const bool reset_qp_y, + const u32 pause_mode, + const unsigned int tile_x, + const unsigned int tile_y, + const unsigned int ctb_col, + const unsigned int ctb_row, + const unsigned int slice_qp, + const u32 slice_const) +{ + const unsigned int endx = s->col_bd[tile_x + 1] - 1; + const unsigned int endy = (pause_mode == PAUSE_MODE_WPP) ? + ctb_row : s->row_bd[tile_y + 1] - 1; + + p1_apb_write(de, RPI_TILESTART, + s->col_bd[tile_x] | (s->row_bd[tile_y] << 16)); + p1_apb_write(de, RPI_TILEEND, endx | (endy << 16)); + + if (do_bte) + p1_apb_write(de, RPI_BEGINTILEEND, endx | (endy << 16)); + + write_slice(de, s, slice_const, endx, endy); + + if (reset_qp_y) { + unsigned int sps_qp_bd_offset = + 6 * s->sps.bit_depth_luma_minus8; + + p1_apb_write(de, RPI_QP, sps_qp_bd_offset + slice_qp); + } + + p1_apb_write(de, RPI_MODE, + pause_mode | + ((endx == s->ctb_width - 1) << 17) | + ((endy == s->ctb_height - 1) << 18)); + + p1_apb_write(de, RPI_CONTROL, (ctb_col << 0) | (ctb_row << 16)); + + de->entry_tile_x = tile_x; + de->entry_tile_y = tile_y; + de->entry_ctb_x = ctb_col; + de->entry_ctb_y = ctb_row; + de->entry_qp = slice_qp; + de->entry_slice = slice_const; +} + +////////////////////////////////////////////////////////////////////////////// +// Wavefront mode + +#define CMDS_WPP_PAUSE 4 +static void wpp_pause(struct rpivid_dec_env *const de, int ctb_row) +{ + p1_apb_write(de, RPI_STATUS, (ctb_row << 18) | 0x25); + p1_apb_write(de, RPI_TRANSFER, PROB_BACKUP); + p1_apb_write(de, RPI_MODE, + ctb_row == de->pic_height_in_ctbs_y - 1 ? + 0x70000 : 0x30000); + p1_apb_write(de, RPI_CONTROL, (ctb_row << 16) + 2); +} + +#define CMDS_WPP_ENTRY_FILL_1 (CMDS_WPP_PAUSE + 2 + CMDS_NEW_ENTRY_POINT) +static int wpp_entry_fill(struct rpivid_dec_env *const de, + const struct rpivid_dec_state *const s, + const unsigned int last_y) +{ + int rv; + const unsigned int last_x = s->ctb_width - 1; + + rv = cmds_check_space(de, CMDS_WPP_ENTRY_FILL_1 * + (last_y - de->entry_ctb_y)); + if (rv) + return rv; + + while (de->entry_ctb_y < last_y) { + /* wpp_entry_x/y set by wpp_entry_point */ + if (s->ctb_width > 2) + wpp_pause(de, de->entry_ctb_y); + p1_apb_write(de, RPI_STATUS, + (de->entry_ctb_y << 18) | (last_x << 5) | 2); + + /* if width == 1 then the saved state is the init one */ + if (s->ctb_width == 2) + p1_apb_write(de, RPI_TRANSFER, PROB_BACKUP); + else + p1_apb_write(de, RPI_TRANSFER, PROB_RELOAD); + + new_entry_point(de, s, false, true, PAUSE_MODE_WPP, + 0, 0, 0, de->entry_ctb_y + 1, + de->entry_qp, de->entry_slice); + } + return 0; +} + +static int wpp_end_previous_slice(struct rpivid_dec_env *const de, + const struct rpivid_dec_state *const s) +{ + int rv; + + rv = wpp_entry_fill(de, s, s->prev_ctb_y); + if (rv) + return rv; + + rv = cmds_check_space(de, CMDS_WPP_PAUSE + 2); + if (rv) + return rv; + + if (de->entry_ctb_x < 2 && + (de->entry_ctb_y < s->start_ctb_y || s->start_ctb_x > 2) && + s->ctb_width > 2) + wpp_pause(de, s->prev_ctb_y); + p1_apb_write(de, RPI_STATUS, + 1 | (s->prev_ctb_x << 5) | (s->prev_ctb_y << 18)); + if (s->start_ctb_x == 2 || + (s->ctb_width == 2 && de->entry_ctb_y < s->start_ctb_y)) + p1_apb_write(de, RPI_TRANSFER, PROB_BACKUP); + return 0; +} + +/* Only main profile supported so WPP => !Tiles which makes some of the + * next chunk code simpler + */ +static int wpp_decode_slice(struct rpivid_dec_env *const de, + const struct rpivid_dec_state *const s, + bool last_slice) +{ + bool reset_qp_y = true; + const bool indep = !s->dependent_slice_segment_flag; + int rv; + + if (s->start_ts) { + rv = wpp_end_previous_slice(de, s); + if (rv) + return rv; + } + pre_slice_decode(de, s); + + rv = cmds_check_space(de, + CMDS_WRITE_BITSTREAM + + CMDS_WRITE_PROB + + CMDS_PROGRAM_SLICECMDS + + CMDS_NEW_SLICE_SEGMENT + + CMDS_NEW_ENTRY_POINT); + if (rv) + return rv; + + rv = write_bitstream(de, s); + if (rv) + return rv; + + if (!s->start_ts || indep || s->ctb_width == 1) + write_prob(de, s); + else if (!s->start_ctb_x) + p1_apb_write(de, RPI_TRANSFER, PROB_RELOAD); + else + reset_qp_y = false; + + program_slicecmds(de, s->slice_idx); + new_slice_segment(de, s); + new_entry_point(de, s, indep, reset_qp_y, PAUSE_MODE_WPP, + 0, 0, s->start_ctb_x, s->start_ctb_y, + s->slice_qp, slice_reg_const(s)); + + if (last_slice) { + rv = wpp_entry_fill(de, s, s->ctb_height - 1); + if (rv) + return rv; + + rv = cmds_check_space(de, CMDS_WPP_PAUSE + 1); + if (rv) + return rv; + + if (de->entry_ctb_x < 2 && s->ctb_width > 2) + wpp_pause(de, s->ctb_height - 1); + + p1_apb_write(de, RPI_STATUS, + 1 | ((s->ctb_width - 1) << 5) | + ((s->ctb_height - 1) << 18)); + } + return 0; +} + +////////////////////////////////////////////////////////////////////////////// +// Tiles mode + +// Guarantees 1 cmd entry free on exit +static int tile_entry_fill(struct rpivid_dec_env *const de, + const struct rpivid_dec_state *const s, + const unsigned int last_tile_x, + const unsigned int last_tile_y) +{ + while (de->entry_tile_y < last_tile_y || + (de->entry_tile_y == last_tile_y && + de->entry_tile_x < last_tile_x)) { + int rv; + unsigned int t_x = de->entry_tile_x; + unsigned int t_y = de->entry_tile_y; + const unsigned int last_x = s->col_bd[t_x + 1] - 1; + const unsigned int last_y = s->row_bd[t_y + 1] - 1; + + // One more than needed here + rv = cmds_check_space(de, CMDS_NEW_ENTRY_POINT + 3); + if (rv) + return rv; + + p1_apb_write(de, RPI_STATUS, + 2 | (last_x << 5) | (last_y << 18)); + p1_apb_write(de, RPI_TRANSFER, PROB_RELOAD); + + // Inc tile + if (++t_x >= s->tile_width) { + t_x = 0; + ++t_y; + } + + new_entry_point(de, s, false, true, PAUSE_MODE_TILE, + t_x, t_y, s->col_bd[t_x], s->row_bd[t_y], + de->entry_qp, de->entry_slice); + } + return 0; +} + +/* + * Write STATUS register with expected end CTU address of previous slice + */ +static int end_previous_slice(struct rpivid_dec_env *const de, + const struct rpivid_dec_state *const s) +{ + int rv; + + rv = tile_entry_fill(de, s, + ctb_to_tile_x(s, s->prev_ctb_x), + ctb_to_tile_y(s, s->prev_ctb_y)); + if (rv) + return rv; + + p1_apb_write(de, RPI_STATUS, + 1 | (s->prev_ctb_x << 5) | (s->prev_ctb_y << 18)); + return 0; +} + +static int decode_slice(struct rpivid_dec_env *const de, + const struct rpivid_dec_state *const s, + bool last_slice) +{ + bool reset_qp_y; + unsigned int tile_x = ctb_to_tile_x(s, s->start_ctb_x); + unsigned int tile_y = ctb_to_tile_y(s, s->start_ctb_y); + int rv; + + if (s->start_ts) { + rv = end_previous_slice(de, s); + if (rv) + return rv; + } + + rv = cmds_check_space(de, + CMDS_WRITE_BITSTREAM + + CMDS_WRITE_PROB + + CMDS_PROGRAM_SLICECMDS + + CMDS_NEW_SLICE_SEGMENT + + CMDS_NEW_ENTRY_POINT); + if (rv) + return rv; + + pre_slice_decode(de, s); + rv = write_bitstream(de, s); + if (rv) + return rv; + + reset_qp_y = !s->start_ts || + !s->dependent_slice_segment_flag || + tile_x != ctb_to_tile_x(s, s->prev_ctb_x) || + tile_y != ctb_to_tile_y(s, s->prev_ctb_y); + if (reset_qp_y) + write_prob(de, s); + + program_slicecmds(de, s->slice_idx); + new_slice_segment(de, s); + new_entry_point(de, s, !s->dependent_slice_segment_flag, reset_qp_y, + PAUSE_MODE_TILE, + tile_x, tile_y, s->start_ctb_x, s->start_ctb_y, + s->slice_qp, slice_reg_const(s)); + + /* + * If this is the last slice then fill in the other tile entries + * now, otherwise this will be done at the start of the next slice + * when it will be known where this slice finishes + */ + if (last_slice) { + rv = tile_entry_fill(de, s, + s->tile_width - 1, + s->tile_height - 1); + if (rv) + return rv; + p1_apb_write(de, RPI_STATUS, + 1 | ((s->ctb_width - 1) << 5) | + ((s->ctb_height - 1) << 18)); + } + return 0; +} + +////////////////////////////////////////////////////////////////////////////// +// Scaling factors + +static void expand_scaling_list(const unsigned int size_id, + u8 *const dst0, + const u8 *const src0, uint8_t dc) +{ + u8 *d; + unsigned int x, y; + + switch (size_id) { + case 0: + memcpy(dst0, src0, 16); + break; + case 1: + memcpy(dst0, src0, 64); + break; + case 2: + d = dst0; + + for (y = 0; y != 16; y++) { + const u8 *s = src0 + (y >> 1) * 8; + + for (x = 0; x != 8; ++x) { + *d++ = *s; + *d++ = *s++; + } + } + dst0[0] = dc; + break; + default: + d = dst0; + + for (y = 0; y != 32; y++) { + const u8 *s = src0 + (y >> 2) * 8; + + for (x = 0; x != 8; ++x) { + *d++ = *s; + *d++ = *s; + *d++ = *s; + *d++ = *s++; + } + } + dst0[0] = dc; + break; + } +} + +static void populate_scaling_factors(const struct rpivid_run *const run, + struct rpivid_dec_env *const de, + const struct rpivid_dec_state *const s) +{ + const struct v4l2_ctrl_hevc_scaling_matrix *const sl = + run->h265.scaling_matrix; + // Array of constants for scaling factors + static const u32 scaling_factor_offsets[4][6] = { + // MID0 MID1 MID2 MID3 MID4 MID5 + // SID0 (4x4) + { 0x0000, 0x0010, 0x0020, 0x0030, 0x0040, 0x0050 }, + // SID1 (8x8) + { 0x0060, 0x00A0, 0x00E0, 0x0120, 0x0160, 0x01A0 }, + // SID2 (16x16) + { 0x01E0, 0x02E0, 0x03E0, 0x04E0, 0x05E0, 0x06E0 }, + // SID3 (32x32) + { 0x07E0, 0x0BE0, 0x0000, 0x0000, 0x0000, 0x0000 } + }; + + unsigned int mid; + + for (mid = 0; mid < 6; mid++) + expand_scaling_list(0, de->scaling_factors + + scaling_factor_offsets[0][mid], + sl->scaling_list_4x4[mid], 0); + for (mid = 0; mid < 6; mid++) + expand_scaling_list(1, de->scaling_factors + + scaling_factor_offsets[1][mid], + sl->scaling_list_8x8[mid], 0); + for (mid = 0; mid < 6; mid++) + expand_scaling_list(2, de->scaling_factors + + scaling_factor_offsets[2][mid], + sl->scaling_list_16x16[mid], + sl->scaling_list_dc_coef_16x16[mid]); + for (mid = 0; mid < 2; mid++) + expand_scaling_list(3, de->scaling_factors + + scaling_factor_offsets[3][mid], + sl->scaling_list_32x32[mid], + sl->scaling_list_dc_coef_32x32[mid]); +} + +static void free_ps_info(struct rpivid_dec_state *const s) +{ + kfree(s->ctb_addr_rs_to_ts); + s->ctb_addr_rs_to_ts = NULL; + kfree(s->ctb_addr_ts_to_rs); + s->ctb_addr_ts_to_rs = NULL; + + kfree(s->col_bd); + s->col_bd = NULL; + kfree(s->row_bd); + s->row_bd = NULL; +} + +static unsigned int tile_width(const struct rpivid_dec_state *const s, + const unsigned int t_x) +{ + return s->col_bd[t_x + 1] - s->col_bd[t_x]; +} + +static unsigned int tile_height(const struct rpivid_dec_state *const s, + const unsigned int t_y) +{ + return s->row_bd[t_y + 1] - s->row_bd[t_y]; +} + +static void fill_rs_to_ts(struct rpivid_dec_state *const s) +{ + unsigned int ts = 0; + unsigned int t_y; + unsigned int tr_rs = 0; + + for (t_y = 0; t_y != s->tile_height; ++t_y) { + const unsigned int t_h = tile_height(s, t_y); + unsigned int t_x; + unsigned int tc_rs = tr_rs; + + for (t_x = 0; t_x != s->tile_width; ++t_x) { + const unsigned int t_w = tile_width(s, t_x); + unsigned int y; + unsigned int rs = tc_rs; + + for (y = 0; y != t_h; ++y) { + unsigned int x; + + for (x = 0; x != t_w; ++x) { + s->ctb_addr_rs_to_ts[rs + x] = ts; + s->ctb_addr_ts_to_rs[ts] = rs + x; + ++ts; + } + rs += s->ctb_width; + } + tc_rs += t_w; + } + tr_rs += t_h * s->ctb_width; + } +} + +static int updated_ps(struct rpivid_dec_state *const s) +{ + unsigned int i; + + free_ps_info(s); + + // Inferred parameters + s->log2_ctb_size = s->sps.log2_min_luma_coding_block_size_minus3 + 3 + + s->sps.log2_diff_max_min_luma_coding_block_size; + + s->ctb_width = (s->sps.pic_width_in_luma_samples + + (1 << s->log2_ctb_size) - 1) >> + s->log2_ctb_size; + s->ctb_height = (s->sps.pic_height_in_luma_samples + + (1 << s->log2_ctb_size) - 1) >> + s->log2_ctb_size; + s->ctb_size = s->ctb_width * s->ctb_height; + + // Inferred parameters + + s->ctb_addr_rs_to_ts = kmalloc_array(s->ctb_size, + sizeof(*s->ctb_addr_rs_to_ts), + GFP_KERNEL); + if (!s->ctb_addr_rs_to_ts) + goto fail; + s->ctb_addr_ts_to_rs = kmalloc_array(s->ctb_size, + sizeof(*s->ctb_addr_ts_to_rs), + GFP_KERNEL); + if (!s->ctb_addr_ts_to_rs) + goto fail; + + if (!(s->pps.flags & V4L2_HEVC_PPS_FLAG_TILES_ENABLED)) { + s->tile_width = 1; + s->tile_height = 1; + } else { + s->tile_width = s->pps.num_tile_columns_minus1 + 1; + s->tile_height = s->pps.num_tile_rows_minus1 + 1; + } + + s->col_bd = kmalloc((s->tile_width + 1) * sizeof(*s->col_bd), + GFP_KERNEL); + if (!s->col_bd) + goto fail; + s->row_bd = kmalloc((s->tile_height + 1) * sizeof(*s->row_bd), + GFP_KERNEL); + if (!s->row_bd) + goto fail; + + s->col_bd[0] = 0; + for (i = 1; i < s->tile_width; i++) + s->col_bd[i] = s->col_bd[i - 1] + + s->pps.column_width_minus1[i - 1] + 1; + s->col_bd[s->tile_width] = s->ctb_width; + + s->row_bd[0] = 0; + for (i = 1; i < s->tile_height; i++) + s->row_bd[i] = s->row_bd[i - 1] + + s->pps.row_height_minus1[i - 1] + 1; + s->row_bd[s->tile_height] = s->ctb_height; + + fill_rs_to_ts(s); + return 0; + +fail: + free_ps_info(s); + /* Set invalid to force reload */ + s->sps.pic_width_in_luma_samples = 0; + return -ENOMEM; +} + +static int write_cmd_buffer(struct rpivid_dev *const dev, + struct rpivid_dec_env *const de, + const struct rpivid_dec_state *const s) +{ + const size_t cmd_size = ALIGN(de->cmd_len * sizeof(de->cmd_fifo[0]), + dev->cache_align); + + de->cmd_addr = dma_map_single(dev->dev, de->cmd_fifo, + cmd_size, DMA_TO_DEVICE); + if (dma_mapping_error(dev->dev, de->cmd_addr)) { + v4l2_err(&dev->v4l2_dev, + "Map cmd buffer (%zu): FAILED\n", cmd_size); + return -ENOMEM; + } + de->cmd_size = cmd_size; + return 0; +} + +static void setup_colmv(struct rpivid_ctx *const ctx, struct rpivid_run *run, + struct rpivid_dec_state *const s) +{ + ctx->colmv_stride = ALIGN(s->sps.pic_width_in_luma_samples, 64); + ctx->colmv_picsize = ctx->colmv_stride * + (ALIGN(s->sps.pic_height_in_luma_samples, 64) >> 4); +} + +// Can be called from irq context +static struct rpivid_dec_env *dec_env_new(struct rpivid_ctx *const ctx) +{ + struct rpivid_dec_env *de; + unsigned long lock_flags; + + spin_lock_irqsave(&ctx->dec_lock, lock_flags); + + de = ctx->dec_free; + if (de) { + ctx->dec_free = de->next; + de->next = NULL; + de->state = RPIVID_DECODE_SLICE_START; + } + + spin_unlock_irqrestore(&ctx->dec_lock, lock_flags); + return de; +} + +// Can be called from irq context +static void dec_env_delete(struct rpivid_dec_env *const de) +{ + struct rpivid_ctx * const ctx = de->ctx; + unsigned long lock_flags; + + if (de->cmd_size) { + dma_unmap_single(ctx->dev->dev, de->cmd_addr, de->cmd_size, + DMA_TO_DEVICE); + de->cmd_size = 0; + } + + aux_q_release(ctx, &de->frame_aux); + aux_q_release(ctx, &de->col_aux); + + spin_lock_irqsave(&ctx->dec_lock, lock_flags); + + de->state = RPIVID_DECODE_END; + de->next = ctx->dec_free; + ctx->dec_free = de; + + spin_unlock_irqrestore(&ctx->dec_lock, lock_flags); +} + +static void dec_env_uninit(struct rpivid_ctx *const ctx) +{ + unsigned int i; + + if (ctx->dec_pool) { + for (i = 0; i != RPIVID_DEC_ENV_COUNT; ++i) { + struct rpivid_dec_env *const de = ctx->dec_pool + i; + + kfree(de->cmd_fifo); + } + + kfree(ctx->dec_pool); + } + + ctx->dec_pool = NULL; + ctx->dec_free = NULL; +} + +static int dec_env_init(struct rpivid_ctx *const ctx) +{ + unsigned int i; + + ctx->dec_pool = kzalloc(sizeof(*ctx->dec_pool) * RPIVID_DEC_ENV_COUNT, + GFP_KERNEL); + if (!ctx->dec_pool) + return -1; + + spin_lock_init(&ctx->dec_lock); + + // Build free chain + ctx->dec_free = ctx->dec_pool; + for (i = 0; i != RPIVID_DEC_ENV_COUNT - 1; ++i) + ctx->dec_pool[i].next = ctx->dec_pool + i + 1; + + // Fill in other bits + for (i = 0; i != RPIVID_DEC_ENV_COUNT; ++i) { + struct rpivid_dec_env *const de = ctx->dec_pool + i; + + de->ctx = ctx; + de->decode_order = i; +// de->cmd_max = 1024; + de->cmd_max = 8096; + de->cmd_fifo = kmalloc_array(de->cmd_max, + sizeof(struct rpi_cmd), + GFP_KERNEL); + if (!de->cmd_fifo) + goto fail; + } + + return 0; + +fail: + dec_env_uninit(ctx); + return -1; +} + +// Assume that we get exactly the same DPB for every slice +// it makes no real sense otherwise +#if V4L2_HEVC_DPB_ENTRIES_NUM_MAX > 16 +#error HEVC_DPB_ENTRIES > h/w slots +#endif + +static u32 mk_config2(const struct rpivid_dec_state *const s) +{ + const struct v4l2_ctrl_hevc_sps *const sps = &s->sps; + const struct v4l2_ctrl_hevc_pps *const pps = &s->pps; + u32 c; + // BitDepthY + c = (sps->bit_depth_luma_minus8 + 8) << 0; + // BitDepthC + c |= (sps->bit_depth_chroma_minus8 + 8) << 4; + // BitDepthY + if (sps->bit_depth_luma_minus8) + c |= BIT(8); + // BitDepthC + if (sps->bit_depth_chroma_minus8) + c |= BIT(9); + c |= s->log2_ctb_size << 10; + if (pps->flags & V4L2_HEVC_PPS_FLAG_CONSTRAINED_INTRA_PRED) + c |= BIT(13); + if (sps->flags & V4L2_HEVC_SPS_FLAG_STRONG_INTRA_SMOOTHING_ENABLED) + c |= BIT(14); + if (s->mk_aux) + c |= BIT(15); /* Write motion vectors to external memory */ + c |= (pps->log2_parallel_merge_level_minus2 + 2) << 16; + if (s->slice_temporal_mvp) + c |= BIT(19); + if (sps->flags & V4L2_HEVC_SPS_FLAG_PCM_LOOP_FILTER_DISABLED) + c |= BIT(20); + c |= (pps->pps_cb_qp_offset & 31) << 21; + c |= (pps->pps_cr_qp_offset & 31) << 26; + return c; +} + +static inline bool is_ref_unit_type(const unsigned int nal_unit_type) +{ + /* From Table 7-1 + * True for 1, 3, 5, 7, 9, 11, 13, 15 + */ + return (nal_unit_type & ~0xe) != 0; +} + +static void rpivid_h265_setup(struct rpivid_ctx *ctx, struct rpivid_run *run) +{ + struct rpivid_dev *const dev = ctx->dev; + const struct v4l2_ctrl_hevc_decode_params *const dec = + run->h265.dec; + /* sh0 used where slice header contents should be constant over all + * slices, or first slice of frame + */ + const struct v4l2_ctrl_hevc_slice_params *const sh0 = + run->h265.slice_params; + struct rpivid_q_aux *dpb_q_aux[V4L2_HEVC_DPB_ENTRIES_NUM_MAX]; + struct rpivid_dec_state *const s = ctx->state; + struct vb2_queue *vq; + struct rpivid_dec_env *de = ctx->dec0; + unsigned int prev_rs; + unsigned int i; + int rv; + bool slice_temporal_mvp; + bool frame_end; + + xtrace_in(dev, de); + s->sh = NULL; // Avoid use until in the slice loop + + frame_end = + ((run->src->flags & V4L2_BUF_FLAG_M2M_HOLD_CAPTURE_BUF) == 0); + + slice_temporal_mvp = (sh0->flags & + V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_TEMPORAL_MVP_ENABLED); + + if (de && de->state != RPIVID_DECODE_END) { + switch (de->state) { + case RPIVID_DECODE_SLICE_CONTINUE: + // Expected state + break; + default: + v4l2_err(&dev->v4l2_dev, "%s: Unexpected state: %d\n", + __func__, de->state); + fallthrough; + case RPIVID_DECODE_ERROR_CONTINUE: + // Uncleared error - fail now + goto fail; + } + + if (s->slice_temporal_mvp != slice_temporal_mvp) { + v4l2_warn(&dev->v4l2_dev, + "Slice Temporal MVP non-constant\n"); + goto fail; + } + } else { + /* Frame start */ + unsigned int ctb_size_y; + bool sps_changed = false; + + if (!is_sps_set(run->h265.sps)) { + v4l2_warn(&dev->v4l2_dev, "SPS never set\n"); + goto fail; + } + // Can't check for PPS easily as all 0's looks valid to me + + if (memcmp(&s->sps, run->h265.sps, sizeof(s->sps)) != 0) { + /* SPS changed */ + v4l2_info(&dev->v4l2_dev, "SPS changed\n"); + memcpy(&s->sps, run->h265.sps, sizeof(s->sps)); + sps_changed = true; + } + if (sps_changed || + memcmp(&s->pps, run->h265.pps, sizeof(s->pps)) != 0) { + /* SPS changed */ + v4l2_info(&dev->v4l2_dev, "PPS changed\n"); + memcpy(&s->pps, run->h265.pps, sizeof(s->pps)); + + /* Recalc stuff as required */ + rv = updated_ps(s); + if (rv) + goto fail; + } + + de = dec_env_new(ctx); + if (!de) { + v4l2_err(&dev->v4l2_dev, + "Failed to find free decode env\n"); + goto fail; + } + ctx->dec0 = de; + + ctb_size_y = + 1U << (s->sps.log2_min_luma_coding_block_size_minus3 + + 3 + + s->sps.log2_diff_max_min_luma_coding_block_size); + + de->pic_width_in_ctbs_y = + (s->sps.pic_width_in_luma_samples + ctb_size_y - 1) / + ctb_size_y; // 7-15 + de->pic_height_in_ctbs_y = + (s->sps.pic_height_in_luma_samples + ctb_size_y - 1) / + ctb_size_y; // 7-17 + de->cmd_len = 0; + de->dpbno_col = ~0U; + + de->bit_copy_gptr = ctx->bitbufs + ctx->p1idx; + de->bit_copy_len = 0; + + de->frame_c_offset = ctx->dst_fmt.height * 128; + de->frame_stride = ctx->dst_fmt.plane_fmt[0].bytesperline * 128; + de->frame_addr = + vb2_dma_contig_plane_dma_addr(&run->dst->vb2_buf, 0); + de->frame_aux = NULL; + + if (s->sps.bit_depth_luma_minus8 != + s->sps.bit_depth_chroma_minus8) { + v4l2_warn(&dev->v4l2_dev, + "Chroma depth (%d) != Luma depth (%d)\n", + s->sps.bit_depth_chroma_minus8 + 8, + s->sps.bit_depth_luma_minus8 + 8); + goto fail; + } + if (s->sps.bit_depth_luma_minus8 == 0) { + if (ctx->dst_fmt.pixelformat != + V4L2_PIX_FMT_NV12_COL128) { + v4l2_err(&dev->v4l2_dev, + "Pixel format %#x != NV12_COL128 for 8-bit output", + ctx->dst_fmt.pixelformat); + goto fail; + } + } else if (s->sps.bit_depth_luma_minus8 == 2) { + if (ctx->dst_fmt.pixelformat != + V4L2_PIX_FMT_NV12_10_COL128) { + v4l2_err(&dev->v4l2_dev, + "Pixel format %#x != NV12_10_COL128 for 10-bit output", + ctx->dst_fmt.pixelformat); + goto fail; + } + } else { + v4l2_warn(&dev->v4l2_dev, + "Luma depth (%d) unsupported\n", + s->sps.bit_depth_luma_minus8 + 8); + goto fail; + } + if (run->dst->vb2_buf.num_planes != 1) { + v4l2_warn(&dev->v4l2_dev, "Capture planes (%d) != 1\n", + run->dst->vb2_buf.num_planes); + goto fail; + } + if (run->dst->planes[0].length < + ctx->dst_fmt.plane_fmt[0].sizeimage) { + v4l2_warn(&dev->v4l2_dev, + "Capture plane[0] length (%d) < sizeimage (%d)\n", + run->dst->planes[0].length, + ctx->dst_fmt.plane_fmt[0].sizeimage); + goto fail; + } + + // Fill in ref planes with our address s.t. if we mess + // up refs somehow then we still have a valid address + // entry + for (i = 0; i != 16; ++i) + de->ref_addrs[i] = de->frame_addr; + + /* + * Stash initial temporal_mvp flag + * This must be the same for all pic slices (7.4.7.1) + */ + s->slice_temporal_mvp = slice_temporal_mvp; + + /* + * Need Aux ents for all (ref) DPB ents if temporal MV could + * be enabled for any pic + */ + s->use_aux = ((s->sps.flags & + V4L2_HEVC_SPS_FLAG_SPS_TEMPORAL_MVP_ENABLED) != 0); + s->mk_aux = s->use_aux && + (s->sps.sps_max_sub_layers_minus1 >= sh0->nuh_temporal_id_plus1 || + is_ref_unit_type(sh0->nal_unit_type)); + + // Phase 2 reg pre-calc + de->rpi_config2 = mk_config2(s); + de->rpi_framesize = (s->sps.pic_height_in_luma_samples << 16) | + s->sps.pic_width_in_luma_samples; + de->rpi_currpoc = sh0->slice_pic_order_cnt; + + if (s->sps.flags & + V4L2_HEVC_SPS_FLAG_SPS_TEMPORAL_MVP_ENABLED) { + setup_colmv(ctx, run, s); + } + + s->slice_idx = 0; + + if (sh0->slice_segment_addr != 0) { + v4l2_warn(&dev->v4l2_dev, + "New frame but segment_addr=%d\n", + sh0->slice_segment_addr); + goto fail; + } + + /* Allocate a bitbuf if we need one - don't need one if single + * slice as we can use the src buf directly + */ + if (!frame_end && !de->bit_copy_gptr->ptr) { + size_t bits_alloc; + bits_alloc = rpivid_bit_buf_size(s->sps.pic_width_in_luma_samples, + s->sps.pic_height_in_luma_samples, + s->sps.bit_depth_luma_minus8); + + if (gptr_alloc(dev, de->bit_copy_gptr, + bits_alloc, + DMA_ATTR_FORCE_CONTIGUOUS) != 0) { + v4l2_err(&dev->v4l2_dev, + "Unable to alloc buf (%zu) for bit copy\n", + bits_alloc); + goto fail; + } + v4l2_info(&dev->v4l2_dev, + "Alloc buf (%zu) for bit copy OK\n", + bits_alloc); + } + } + + // Either map src buffer or use directly + s->src_addr = 0; + s->src_buf = NULL; + + if (frame_end) + s->src_addr = vb2_dma_contig_plane_dma_addr(&run->src->vb2_buf, + 0); + if (!s->src_addr) + s->src_buf = vb2_plane_vaddr(&run->src->vb2_buf, 0); + if (!s->src_addr && !s->src_buf) { + v4l2_err(&dev->v4l2_dev, "Failed to map src buffer\n"); + goto fail; + } + + // Pre calc a few things + s->dec = dec; + for (i = 0; i != run->h265.slice_ents; ++i) { + const struct v4l2_ctrl_hevc_slice_params *const sh = sh0 + i; + const bool last_slice = frame_end && i + 1 == run->h265.slice_ents; + + s->sh = sh; + + if (run->src->planes[0].bytesused < (sh->bit_size + 7) / 8) { + v4l2_warn(&dev->v4l2_dev, + "Bit size %d > bytesused %d\n", + sh->bit_size, run->src->planes[0].bytesused); + goto fail; + } + if (sh->data_byte_offset >= sh->bit_size / 8) { + v4l2_warn(&dev->v4l2_dev, + "Bit size %u < Byte offset %u * 8\n", + sh->bit_size, sh->data_byte_offset); + goto fail; + } + + s->slice_qp = 26 + s->pps.init_qp_minus26 + sh->slice_qp_delta; + s->max_num_merge_cand = sh->slice_type == HEVC_SLICE_I ? + 0 : + (5 - sh->five_minus_max_num_merge_cand); + s->dependent_slice_segment_flag = + ((sh->flags & + V4L2_HEVC_SLICE_PARAMS_FLAG_DEPENDENT_SLICE_SEGMENT) != 0); + + s->nb_refs[0] = (sh->slice_type == HEVC_SLICE_I) ? + 0 : + sh->num_ref_idx_l0_active_minus1 + 1; + s->nb_refs[1] = (sh->slice_type != HEVC_SLICE_B) ? + 0 : + sh->num_ref_idx_l1_active_minus1 + 1; + + if (s->sps.flags & V4L2_HEVC_SPS_FLAG_SCALING_LIST_ENABLED) + populate_scaling_factors(run, de, s); + + /* Calc all the random coord info to avoid repeated conversion in/out */ + s->start_ts = s->ctb_addr_rs_to_ts[sh->slice_segment_addr]; + s->start_ctb_x = sh->slice_segment_addr % de->pic_width_in_ctbs_y; + s->start_ctb_y = sh->slice_segment_addr / de->pic_width_in_ctbs_y; + /* Last CTB of previous slice */ + prev_rs = !s->start_ts ? 0 : s->ctb_addr_ts_to_rs[s->start_ts - 1]; + s->prev_ctb_x = prev_rs % de->pic_width_in_ctbs_y; + s->prev_ctb_y = prev_rs / de->pic_width_in_ctbs_y; + + if ((s->pps.flags & V4L2_HEVC_PPS_FLAG_ENTROPY_CODING_SYNC_ENABLED)) + rv = wpp_decode_slice(de, s, last_slice); + else + rv = decode_slice(de, s, last_slice); + if (rv) + goto fail; + + ++s->slice_idx; + } + + if (!frame_end) { + xtrace_ok(dev, de); + return; + } + + // Frame end + memset(dpb_q_aux, 0, + sizeof(*dpb_q_aux) * V4L2_HEVC_DPB_ENTRIES_NUM_MAX); + + // Locate ref frames + // At least in the current implementation this is constant across all + // slices. If this changes we will need idx mapping code. + // Uses sh so here rather than trigger + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, + V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + + if (!vq) { + v4l2_err(&dev->v4l2_dev, "VQ gone!\n"); + goto fail; + } + + // v4l2_info(&dev->v4l2_dev, "rpivid_h265_end of frame\n"); + if (write_cmd_buffer(dev, de, s)) + goto fail; + + for (i = 0; i < dec->num_active_dpb_entries; ++i) { + struct vb2_buffer *buf = vb2_find_buffer(vq, dec->dpb[i].timestamp); + if (!buf) { + v4l2_warn(&dev->v4l2_dev, + "Missing DPB ent %d, timestamp=%lld\n", + i, (long long)dec->dpb[i].timestamp); + continue; + } + + if (s->use_aux) { + int buffer_index = buf->index; + dpb_q_aux[i] = aux_q_ref_idx(ctx, buffer_index); + if (!dpb_q_aux[i]) + v4l2_warn(&dev->v4l2_dev, + "Missing DPB AUX ent %d, timestamp=%lld, index=%d\n", + i, (long long)dec->dpb[i].timestamp, + buffer_index); + } + + de->ref_addrs[i] = + vb2_dma_contig_plane_dma_addr(buf, 0); + } + + // Move DPB from temp + for (i = 0; i != V4L2_HEVC_DPB_ENTRIES_NUM_MAX; ++i) { + aux_q_release(ctx, &s->ref_aux[i]); + s->ref_aux[i] = dpb_q_aux[i]; + } + // Unref the old frame aux too - it is either in the DPB or not + // now + aux_q_release(ctx, &s->frame_aux); + + if (s->mk_aux) { + s->frame_aux = aux_q_new(ctx, run->dst->vb2_buf.index); + + if (!s->frame_aux) { + v4l2_err(&dev->v4l2_dev, + "Failed to obtain aux storage for frame\n"); + goto fail; + } + + de->frame_aux = aux_q_ref(ctx, s->frame_aux); + } + + if (de->dpbno_col != ~0U) { + if (de->dpbno_col >= dec->num_active_dpb_entries) { + v4l2_err(&dev->v4l2_dev, + "Col ref index %d >= %d\n", + de->dpbno_col, + dec->num_active_dpb_entries); + } else { + // Standard requires that the col pic is + // constant for the duration of the pic + // (text of collocated_ref_idx in H265-2 2018 + // 7.4.7.1) + + // Spot the collocated ref in passing + de->col_aux = aux_q_ref(ctx, + dpb_q_aux[de->dpbno_col]); + + if (!de->col_aux) { + v4l2_warn(&dev->v4l2_dev, + "Missing DPB ent for col\n"); + // Probably need to abort if this fails + // as P2 may explode on bad data + goto fail; + } + } + } + + de->state = RPIVID_DECODE_PHASE1; + xtrace_ok(dev, de); + return; + +fail: + if (de) + // Actual error reporting happens in Trigger + de->state = frame_end ? RPIVID_DECODE_ERROR_DONE : + RPIVID_DECODE_ERROR_CONTINUE; + xtrace_fail(dev, de); +} + +////////////////////////////////////////////////////////////////////////////// +// Handle PU and COEFF stream overflow + +// Returns: +// -1 Phase 1 decode error +// 0 OK +// >0 Out of space (bitmask) + +#define STATUS_COEFF_EXHAUSTED 8 +#define STATUS_PU_EXHAUSTED 16 + +static int check_status(const struct rpivid_dev *const dev) +{ + const u32 cfstatus = apb_read(dev, RPI_CFSTATUS); + const u32 cfnum = apb_read(dev, RPI_CFNUM); + u32 status = apb_read(dev, RPI_STATUS); + + // Handle PU and COEFF stream overflow + + // this is the definition of successful completion of phase 1 + // it assures that status register is zero and all blocks in each tile + // have completed + if (cfstatus == cfnum) + return 0; //No error + + status &= (STATUS_PU_EXHAUSTED | STATUS_COEFF_EXHAUSTED); + if (status) + return status; + + return -1; +} + +static void phase2_cb(struct rpivid_dev *const dev, void *v) +{ + struct rpivid_dec_env *const de = v; + + xtrace_in(dev, de); + + /* Done with buffers - allow new P1 */ + rpivid_hw_irq_active1_enable_claim(dev, 1); + + v4l2_m2m_buf_done(de->frame_buf, VB2_BUF_STATE_DONE); + de->frame_buf = NULL; + +#if USE_REQUEST_PIN + media_request_unpin(de->req_pin); + de->req_pin = NULL; +#else + media_request_object_complete(de->req_obj); + de->req_obj = NULL; +#endif + + xtrace_ok(dev, de); + dec_env_delete(de); +} + +static void phase2_claimed(struct rpivid_dev *const dev, void *v) +{ + struct rpivid_dec_env *const de = v; + unsigned int i; + + xtrace_in(dev, de); + + apb_write_vc_addr(dev, RPI_PURBASE, de->pu_base_vc); + apb_write_vc_len(dev, RPI_PURSTRIDE, de->pu_stride); + apb_write_vc_addr(dev, RPI_COEFFRBASE, de->coeff_base_vc); + apb_write_vc_len(dev, RPI_COEFFRSTRIDE, de->coeff_stride); + + apb_write_vc_addr(dev, RPI_OUTYBASE, de->frame_addr); + apb_write_vc_addr(dev, RPI_OUTCBASE, + de->frame_addr + de->frame_c_offset); + apb_write_vc_len(dev, RPI_OUTYSTRIDE, de->frame_stride); + apb_write_vc_len(dev, RPI_OUTCSTRIDE, de->frame_stride); + + // v4l2_info(&dev->v4l2_dev, "Frame: Y=%llx, C=%llx, Stride=%x\n", + // de->frame_addr, de->frame_addr + de->frame_c_offset, + // de->frame_stride); + + for (i = 0; i < 16; i++) { + // Strides are in fact unused but fill in anyway + apb_write_vc_addr(dev, 0x9000 + 16 * i, de->ref_addrs[i]); + apb_write_vc_len(dev, 0x9004 + 16 * i, de->frame_stride); + apb_write_vc_addr(dev, 0x9008 + 16 * i, + de->ref_addrs[i] + de->frame_c_offset); + apb_write_vc_len(dev, 0x900C + 16 * i, de->frame_stride); + } + + apb_write(dev, RPI_CONFIG2, de->rpi_config2); + apb_write(dev, RPI_FRAMESIZE, de->rpi_framesize); + apb_write(dev, RPI_CURRPOC, de->rpi_currpoc); + // v4l2_info(&dev->v4l2_dev, "Config2=%#x, FrameSize=%#x, POC=%#x\n", + // de->rpi_config2, de->rpi_framesize, de->rpi_currpoc); + + // collocated reads/writes + apb_write_vc_len(dev, RPI_COLSTRIDE, + de->ctx->colmv_stride); // Read vals + apb_write_vc_len(dev, RPI_MVSTRIDE, + de->ctx->colmv_stride); // Write vals + apb_write_vc_addr(dev, RPI_MVBASE, + !de->frame_aux ? 0 : de->frame_aux->col.addr); + apb_write_vc_addr(dev, RPI_COLBASE, + !de->col_aux ? 0 : de->col_aux->col.addr); + + //v4l2_info(&dev->v4l2_dev, + // "Mv=%llx, Col=%llx, Stride=%x, Buf=%llx->%llx\n", + // de->rpi_mvbase, de->rpi_colbase, de->ctx->colmv_stride, + // de->ctx->colmvbuf.addr, de->ctx->colmvbuf.addr + + // de->ctx->colmvbuf.size); + + rpivid_hw_irq_active2_irq(dev, &de->irq_ent, phase2_cb, de); + + apb_write_final(dev, RPI_NUMROWS, de->pic_height_in_ctbs_y); + + xtrace_ok(dev, de); +} + +static void phase1_claimed(struct rpivid_dev *const dev, void *v); + +// release any and all objects associated with de +// and reenable phase 1 if required +static void phase1_err_fin(struct rpivid_dev *const dev, + struct rpivid_ctx *const ctx, + struct rpivid_dec_env *const de) +{ + /* Return all detached buffers */ + if (de->src_buf) + v4l2_m2m_buf_done(de->src_buf, VB2_BUF_STATE_ERROR); + de->src_buf = NULL; + if (de->frame_buf) + v4l2_m2m_buf_done(de->frame_buf, VB2_BUF_STATE_ERROR); + de->frame_buf = NULL; +#if USE_REQUEST_PIN + if (de->req_pin) + media_request_unpin(de->req_pin); + de->req_pin = NULL; +#else + if (de->req_obj) + media_request_object_complete(de->req_obj); + de->req_obj = NULL; +#endif + + dec_env_delete(de); + + /* Reenable phase 0 if we were blocking */ + if (atomic_add_return(-1, &ctx->p1out) >= RPIVID_P1BUF_COUNT - 1) + v4l2_m2m_job_finish(dev->m2m_dev, ctx->fh.m2m_ctx); + + /* Done with P1-P2 buffers - allow new P1 */ + rpivid_hw_irq_active1_enable_claim(dev, 1); +} + +static void phase1_thread(struct rpivid_dev *const dev, void *v) +{ + struct rpivid_dec_env *const de = v; + struct rpivid_ctx *const ctx = de->ctx; + + struct rpivid_gptr *const pu_gptr = ctx->pu_bufs + ctx->p2idx; + struct rpivid_gptr *const coeff_gptr = ctx->coeff_bufs + ctx->p2idx; + + xtrace_in(dev, de); + + if (de->p1_status & STATUS_PU_EXHAUSTED) { + if (gptr_realloc_new(dev, pu_gptr, next_size(pu_gptr->size))) { + v4l2_err(&dev->v4l2_dev, + "%s: PU realloc (%zx) failed\n", + __func__, pu_gptr->size); + goto fail; + } + v4l2_info(&dev->v4l2_dev, "%s: PU realloc (%zx) OK\n", + __func__, pu_gptr->size); + } + + if (de->p1_status & STATUS_COEFF_EXHAUSTED) { + if (gptr_realloc_new(dev, coeff_gptr, + next_size(coeff_gptr->size))) { + v4l2_err(&dev->v4l2_dev, + "%s: Coeff realloc (%zx) failed\n", + __func__, coeff_gptr->size); + goto fail; + } + v4l2_info(&dev->v4l2_dev, "%s: Coeff realloc (%zx) OK\n", + __func__, coeff_gptr->size); + } + + phase1_claimed(dev, de); + xtrace_ok(dev, de); + return; + +fail: + if (!pu_gptr->addr || !coeff_gptr->addr) { + v4l2_err(&dev->v4l2_dev, + "%s: Fatal: failed to reclaim old alloc\n", + __func__); + ctx->fatal_err = 1; + } + xtrace_fail(dev, de); + phase1_err_fin(dev, ctx, de); +} + +/* Always called in irq context (this is good) */ +static void phase1_cb(struct rpivid_dev *const dev, void *v) +{ + struct rpivid_dec_env *const de = v; + struct rpivid_ctx *const ctx = de->ctx; + + xtrace_in(dev, de); + + de->p1_status = check_status(dev); + + if (de->p1_status != 0) { + v4l2_info(&dev->v4l2_dev, "%s: Post wait: %#x\n", + __func__, de->p1_status); + + if (de->p1_status < 0) + goto fail; + + /* Need to realloc - push onto a thread rather than IRQ */ + rpivid_hw_irq_active1_thread(dev, &de->irq_ent, + phase1_thread, de); + return; + } + + v4l2_m2m_buf_done(de->src_buf, VB2_BUF_STATE_DONE); + de->src_buf = NULL; + + /* All phase1 error paths done - it is safe to inc p2idx */ + ctx->p2idx = + (ctx->p2idx + 1 >= RPIVID_P2BUF_COUNT) ? 0 : ctx->p2idx + 1; + + /* Renable the next setup if we were blocking */ + if (atomic_add_return(-1, &ctx->p1out) >= RPIVID_P1BUF_COUNT - 1) { + xtrace_fin(dev, de); + v4l2_m2m_job_finish(dev->m2m_dev, ctx->fh.m2m_ctx); + } + + rpivid_hw_irq_active2_claim(dev, &de->irq_ent, phase2_claimed, de); + + xtrace_ok(dev, de); + return; + +fail: + xtrace_fail(dev, de); + phase1_err_fin(dev, ctx, de); +} + +static void phase1_claimed(struct rpivid_dev *const dev, void *v) +{ + struct rpivid_dec_env *const de = v; + struct rpivid_ctx *const ctx = de->ctx; + + const struct rpivid_gptr * const pu_gptr = ctx->pu_bufs + ctx->p2idx; + const struct rpivid_gptr * const coeff_gptr = ctx->coeff_bufs + + ctx->p2idx; + + xtrace_in(dev, de); + + if (ctx->fatal_err) + goto fail; + + de->pu_base_vc = pu_gptr->addr; + de->pu_stride = + ALIGN_DOWN(pu_gptr->size / de->pic_height_in_ctbs_y, 64); + + de->coeff_base_vc = coeff_gptr->addr; + de->coeff_stride = + ALIGN_DOWN(coeff_gptr->size / de->pic_height_in_ctbs_y, 64); + + /* phase1_claimed blocked until cb_phase1 completed so p2idx inc + * in cb_phase1 after error detection + */ + + apb_write_vc_addr(dev, RPI_PUWBASE, de->pu_base_vc); + apb_write_vc_len(dev, RPI_PUWSTRIDE, de->pu_stride); + apb_write_vc_addr(dev, RPI_COEFFWBASE, de->coeff_base_vc); + apb_write_vc_len(dev, RPI_COEFFWSTRIDE, de->coeff_stride); + + // Trigger command FIFO + apb_write(dev, RPI_CFNUM, de->cmd_len); + + // Claim irq + rpivid_hw_irq_active1_irq(dev, &de->irq_ent, phase1_cb, de); + + // And start the h/w + apb_write_vc_addr_final(dev, RPI_CFBASE, de->cmd_addr); + + xtrace_ok(dev, de); + return; + +fail: + xtrace_fail(dev, de); + phase1_err_fin(dev, ctx, de); +} + +static void dec_state_delete(struct rpivid_ctx *const ctx) +{ + unsigned int i; + struct rpivid_dec_state *const s = ctx->state; + + if (!s) + return; + ctx->state = NULL; + + free_ps_info(s); + + for (i = 0; i != HEVC_MAX_REFS; ++i) + aux_q_release(ctx, &s->ref_aux[i]); + aux_q_release(ctx, &s->frame_aux); + + kfree(s); +} + +struct irq_sync { + atomic_t done; + wait_queue_head_t wq; + struct rpivid_hw_irq_ent irq_ent; +}; + +static void phase2_sync_claimed(struct rpivid_dev *const dev, void *v) +{ + struct irq_sync *const sync = v; + + atomic_set(&sync->done, 1); + wake_up(&sync->wq); +} + +static void phase1_sync_claimed(struct rpivid_dev *const dev, void *v) +{ + struct irq_sync *const sync = v; + + rpivid_hw_irq_active1_enable_claim(dev, 1); + rpivid_hw_irq_active2_claim(dev, &sync->irq_ent, phase2_sync_claimed, sync); +} + +/* Sync with IRQ operations + * + * Claims phase1 and phase2 in turn and waits for the phase2 claim so any + * pending IRQ ops will have completed by the time this returns + * + * phase1 has counted enables so must reenable once claimed + * phase2 has unlimited enables + */ +static void irq_sync(struct rpivid_dev *const dev) +{ + struct irq_sync sync; + + atomic_set(&sync.done, 0); + init_waitqueue_head(&sync.wq); + + rpivid_hw_irq_active1_claim(dev, &sync.irq_ent, phase1_sync_claimed, &sync); + wait_event(sync.wq, atomic_read(&sync.done)); +} + +static void h265_ctx_uninit(struct rpivid_dev *const dev, struct rpivid_ctx *ctx) +{ + unsigned int i; + + dec_env_uninit(ctx); + dec_state_delete(ctx); + + // dec_env & state must be killed before this to release the buffer to + // the free pool + aux_q_uninit(ctx); + + for (i = 0; i != ARRAY_SIZE(ctx->bitbufs); ++i) + gptr_free(dev, ctx->bitbufs + i); + for (i = 0; i != ARRAY_SIZE(ctx->pu_bufs); ++i) + gptr_free(dev, ctx->pu_bufs + i); + for (i = 0; i != ARRAY_SIZE(ctx->coeff_bufs); ++i) + gptr_free(dev, ctx->coeff_bufs + i); +} + +static void rpivid_h265_stop(struct rpivid_ctx *ctx) +{ + struct rpivid_dev *const dev = ctx->dev; + + v4l2_info(&dev->v4l2_dev, "%s\n", __func__); + + irq_sync(dev); + h265_ctx_uninit(dev, ctx); +} + +static int rpivid_h265_start(struct rpivid_ctx *ctx) +{ + struct rpivid_dev *const dev = ctx->dev; + unsigned int i; + + unsigned int w = ctx->dst_fmt.width; + unsigned int h = ctx->dst_fmt.height; + unsigned int wxh; + size_t pu_alloc; + size_t coeff_alloc; + +#if DEBUG_TRACE_P1_CMD + p1_z = 0; +#endif + + // Generate a sanitised WxH for memory alloc + // Assume HD if unset + if (w == 0) + w = 1920; + if (w > 4096) + w = 4096; + if (h == 0) + h = 1088; + if (h > 4096) + h = 4096; + wxh = w * h; + + v4l2_info(&dev->v4l2_dev, "%s: (%dx%d)\n", __func__, + ctx->dst_fmt.width, ctx->dst_fmt.height); + + ctx->fatal_err = 0; + ctx->dec0 = NULL; + ctx->state = kzalloc(sizeof(*ctx->state), GFP_KERNEL); + if (!ctx->state) { + v4l2_err(&dev->v4l2_dev, "Failed to allocate decode state\n"); + goto fail; + } + + if (dec_env_init(ctx) != 0) { + v4l2_err(&dev->v4l2_dev, "Failed to allocate decode envs\n"); + goto fail; + } + + // Finger in the air PU & Coeff alloc + // Will be realloced if too small + coeff_alloc = rpivid_round_up_size(wxh); + pu_alloc = rpivid_round_up_size(wxh / 4); + for (i = 0; i != ARRAY_SIZE(ctx->pu_bufs); ++i) { + // Don't actually need a kernel mapping here + if (gptr_alloc(dev, ctx->pu_bufs + i, pu_alloc, + DMA_ATTR_NO_KERNEL_MAPPING)) { + v4l2_err(&dev->v4l2_dev, + "Failed to alloc %#zx PU%d buffer\n", + pu_alloc, i); + goto fail; + } + if (gptr_alloc(dev, ctx->coeff_bufs + i, coeff_alloc, + DMA_ATTR_NO_KERNEL_MAPPING)) { + v4l2_err(&dev->v4l2_dev, + "Failed to alloc %#zx Coeff%d buffer\n", + pu_alloc, i); + goto fail; + } + } + aux_q_init(ctx); + + return 0; + +fail: + h265_ctx_uninit(dev, ctx); + return -ENOMEM; +} + +static void rpivid_h265_trigger(struct rpivid_ctx *ctx) +{ + struct rpivid_dev *const dev = ctx->dev; + struct rpivid_dec_env *const de = ctx->dec0; + + xtrace_in(dev, de); + + switch (!de ? RPIVID_DECODE_ERROR_CONTINUE : de->state) { + case RPIVID_DECODE_SLICE_START: + de->state = RPIVID_DECODE_SLICE_CONTINUE; + fallthrough; + case RPIVID_DECODE_SLICE_CONTINUE: + v4l2_m2m_buf_done_and_job_finish(dev->m2m_dev, ctx->fh.m2m_ctx, + VB2_BUF_STATE_DONE); + xtrace_ok(dev, de); + break; + + default: + v4l2_err(&dev->v4l2_dev, "%s: Unexpected state: %d\n", __func__, + de->state); + fallthrough; + case RPIVID_DECODE_ERROR_DONE: + ctx->dec0 = NULL; + dec_env_delete(de); + fallthrough; + case RPIVID_DECODE_ERROR_CONTINUE: + xtrace_fin(dev, de); + v4l2_m2m_buf_done_and_job_finish(dev->m2m_dev, ctx->fh.m2m_ctx, + VB2_BUF_STATE_ERROR); + break; + + case RPIVID_DECODE_PHASE1: + ctx->dec0 = NULL; + +#if !USE_REQUEST_PIN + /* Alloc a new request object - needs to be alloced dynamically + * as the media request will release it some random time after + * it is completed + */ + de->req_obj = kmalloc(sizeof(*de->req_obj), GFP_KERNEL); + if (!de->req_obj) { + xtrace_fail(dev, de); + dec_env_delete(de); + v4l2_m2m_buf_done_and_job_finish(dev->m2m_dev, + ctx->fh.m2m_ctx, + VB2_BUF_STATE_ERROR); + break; + } + media_request_object_init(de->req_obj); +#warning probably needs to _get the req obj too +#endif + ctx->p1idx = (ctx->p1idx + 1 >= RPIVID_P1BUF_COUNT) ? + 0 : ctx->p1idx + 1; + + /* We know we have src & dst so no need to test */ + de->src_buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + de->frame_buf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + +#if USE_REQUEST_PIN + de->req_pin = de->src_buf->vb2_buf.req_obj.req; + media_request_pin(de->req_pin); +#else + media_request_object_bind(de->src_buf->vb2_buf.req_obj.req, + &dst_req_obj_ops, de, false, + de->req_obj); +#endif + + /* We could get rid of the src buffer here if we've already + * copied it, but we don't copy the last buffer unless it + * didn't return a contig dma addr and that shouldn't happen + */ + + /* Enable the next setup if our Q isn't too big */ + if (atomic_add_return(1, &ctx->p1out) < RPIVID_P1BUF_COUNT) { + xtrace_fin(dev, de); + v4l2_m2m_job_finish(dev->m2m_dev, ctx->fh.m2m_ctx); + } + + rpivid_hw_irq_active1_claim(dev, &de->irq_ent, phase1_claimed, + de); + xtrace_ok(dev, de); + break; + } +} + +const struct rpivid_dec_ops rpivid_dec_ops_h265 = { + .setup = rpivid_h265_setup, + .start = rpivid_h265_start, + .stop = rpivid_h265_stop, + .trigger = rpivid_h265_trigger, +}; + +static int try_ctrl_sps(struct v4l2_ctrl *ctrl) +{ + const struct v4l2_ctrl_hevc_sps *const sps = ctrl->p_new.p_hevc_sps; + struct rpivid_ctx *const ctx = ctrl->priv; + struct rpivid_dev *const dev = ctx->dev; + + if (sps->chroma_format_idc != 1) { + v4l2_warn(&dev->v4l2_dev, + "Chroma format (%d) unsupported\n", + sps->chroma_format_idc); + return -EINVAL; + } + + if (sps->bit_depth_luma_minus8 != 0 && + sps->bit_depth_luma_minus8 != 2) { + v4l2_warn(&dev->v4l2_dev, + "Luma depth (%d) unsupported\n", + sps->bit_depth_luma_minus8 + 8); + return -EINVAL; + } + + if (sps->bit_depth_luma_minus8 != sps->bit_depth_chroma_minus8) { + v4l2_warn(&dev->v4l2_dev, + "Chroma depth (%d) != Luma depth (%d)\n", + sps->bit_depth_chroma_minus8 + 8, + sps->bit_depth_luma_minus8 + 8); + return -EINVAL; + } + + if (!sps->pic_width_in_luma_samples || + !sps->pic_height_in_luma_samples || + sps->pic_width_in_luma_samples > 4096 || + sps->pic_height_in_luma_samples > 4096) { + v4l2_warn(&dev->v4l2_dev, + "Bad sps width (%u) x height (%u)\n", + sps->pic_width_in_luma_samples, + sps->pic_height_in_luma_samples); + return -EINVAL; + } + + if (!ctx->dst_fmt_set) + return 0; + + if ((sps->bit_depth_luma_minus8 == 0 && + ctx->dst_fmt.pixelformat != V4L2_PIX_FMT_NV12_COL128) || + (sps->bit_depth_luma_minus8 == 2 && + ctx->dst_fmt.pixelformat != V4L2_PIX_FMT_NV12_10_COL128)) { + v4l2_warn(&dev->v4l2_dev, + "SPS luma depth %d does not match capture format\n", + sps->bit_depth_luma_minus8 + 8); + return -EINVAL; + } + + if (sps->pic_width_in_luma_samples > ctx->dst_fmt.width || + sps->pic_height_in_luma_samples > ctx->dst_fmt.height) { + v4l2_warn(&dev->v4l2_dev, + "SPS size (%dx%d) > capture size (%d,%d)\n", + sps->pic_width_in_luma_samples, + sps->pic_height_in_luma_samples, + ctx->dst_fmt.width, + ctx->dst_fmt.height); + return -EINVAL; + } + + return 0; +} + +const struct v4l2_ctrl_ops rpivid_hevc_sps_ctrl_ops = { + .try_ctrl = try_ctrl_sps, +}; + +static int try_ctrl_pps(struct v4l2_ctrl *ctrl) +{ + const struct v4l2_ctrl_hevc_pps *const pps = ctrl->p_new.p_hevc_pps; + struct rpivid_ctx *const ctx = ctrl->priv; + struct rpivid_dev *const dev = ctx->dev; + + if ((pps->flags & + V4L2_HEVC_PPS_FLAG_ENTROPY_CODING_SYNC_ENABLED) && + (pps->flags & + V4L2_HEVC_PPS_FLAG_TILES_ENABLED) && + (pps->num_tile_columns_minus1 || pps->num_tile_rows_minus1)) { + v4l2_warn(&dev->v4l2_dev, + "WPP + Tiles not supported\n"); + return -EINVAL; + } + + return 0; +} + +const struct v4l2_ctrl_ops rpivid_hevc_pps_ctrl_ops = { + .try_ctrl = try_ctrl_pps, +}; + diff --git a/drivers/staging/media/rpivid/rpivid_hw.c b/drivers/staging/media/rpivid/rpivid_hw.c new file mode 100644 index 00000000000000..1026fa6b8b04f4 --- /dev/null +++ b/drivers/staging/media/rpivid/rpivid_hw.c @@ -0,0 +1,383 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Raspberry Pi HEVC driver + * + * Copyright (C) 2020 Raspberry Pi (Trading) Ltd + * + * Based on the Cedrus VPU driver, that is: + * + * Copyright (C) 2016 Florent Revest <florent.revest@free-electrons.com> + * Copyright (C) 2018 Paul Kocialkowski <paul.kocialkowski@bootlin.com> + * Copyright (C) 2018 Bootlin + */ +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/of_reserved_mem.h> +#include <linux/of_device.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/reset.h> + +#include <media/videobuf2-core.h> +#include <media/v4l2-mem2mem.h> + +#include <soc/bcm2835/raspberrypi-firmware.h> + +#include "rpivid.h" +#include "rpivid_hw.h" + +static void pre_irq(struct rpivid_dev *dev, struct rpivid_hw_irq_ent *ient, + rpivid_irq_callback cb, void *v, + struct rpivid_hw_irq_ctrl *ictl) +{ + unsigned long flags; + + if (ictl->irq) { + v4l2_err(&dev->v4l2_dev, "Attempt to claim IRQ when already claimed\n"); + return; + } + + ient->cb = cb; + ient->v = v; + + spin_lock_irqsave(&ictl->lock, flags); + ictl->irq = ient; + ictl->no_sched++; + spin_unlock_irqrestore(&ictl->lock, flags); +} + +/* Should be called from inside ictl->lock */ +static inline bool sched_enabled(const struct rpivid_hw_irq_ctrl * const ictl) +{ + return ictl->no_sched <= 0 && ictl->enable; +} + +/* Should be called from inside ictl->lock & after checking sched_enabled() */ +static inline void set_claimed(struct rpivid_hw_irq_ctrl * const ictl) +{ + if (ictl->enable > 0) + --ictl->enable; + ictl->no_sched = 1; +} + +/* Should be called from inside ictl->lock */ +static struct rpivid_hw_irq_ent *get_sched(struct rpivid_hw_irq_ctrl * const ictl) +{ + struct rpivid_hw_irq_ent *ient; + + if (!sched_enabled(ictl)) + return NULL; + + ient = ictl->claim; + if (!ient) + return NULL; + ictl->claim = ient->next; + + set_claimed(ictl); + return ient; +} + +/* Run a callback & check to see if there is anything else to run */ +static void sched_cb(struct rpivid_dev * const dev, + struct rpivid_hw_irq_ctrl * const ictl, + struct rpivid_hw_irq_ent *ient) +{ + while (ient) { + unsigned long flags; + + ient->cb(dev, ient->v); + + spin_lock_irqsave(&ictl->lock, flags); + + /* Always dec no_sched after cb exec - must have been set + * on entry to cb + */ + --ictl->no_sched; + ient = get_sched(ictl); + + spin_unlock_irqrestore(&ictl->lock, flags); + } +} + +/* Should only ever be called from its own IRQ cb so no lock required */ +static void pre_thread(struct rpivid_dev *dev, + struct rpivid_hw_irq_ent *ient, + rpivid_irq_callback cb, void *v, + struct rpivid_hw_irq_ctrl *ictl) +{ + ient->cb = cb; + ient->v = v; + ictl->irq = ient; + ictl->thread_reqed = true; + ictl->no_sched++; /* This is unwound in do_thread */ +} + +// Called in irq context +static void do_irq(struct rpivid_dev * const dev, + struct rpivid_hw_irq_ctrl * const ictl) +{ + struct rpivid_hw_irq_ent *ient; + unsigned long flags; + + spin_lock_irqsave(&ictl->lock, flags); + ient = ictl->irq; + ictl->irq = NULL; + spin_unlock_irqrestore(&ictl->lock, flags); + + sched_cb(dev, ictl, ient); +} + +static void do_claim(struct rpivid_dev * const dev, + struct rpivid_hw_irq_ent *ient, + const rpivid_irq_callback cb, void * const v, + struct rpivid_hw_irq_ctrl * const ictl) +{ + unsigned long flags; + + ient->next = NULL; + ient->cb = cb; + ient->v = v; + + spin_lock_irqsave(&ictl->lock, flags); + + if (ictl->claim) { + // If we have a Q then add to end + ictl->tail->next = ient; + ictl->tail = ient; + ient = NULL; + } else if (!sched_enabled(ictl)) { + // Empty Q but other activity in progress so Q + ictl->claim = ient; + ictl->tail = ient; + ient = NULL; + } else { + // Nothing else going on - schedule immediately and + // prevent anything else scheduling claims + set_claimed(ictl); + } + + spin_unlock_irqrestore(&ictl->lock, flags); + + sched_cb(dev, ictl, ient); +} + +/* Enable n claims. + * n < 0 set to unlimited (default on init) + * n = 0 if previously unlimited then disable otherwise nop + * n > 0 if previously unlimited then set to n enables + * otherwise add n enables + * The enable count is automatically decremented every time a claim is run + */ +static void do_enable_claim(struct rpivid_dev * const dev, + int n, + struct rpivid_hw_irq_ctrl * const ictl) +{ + unsigned long flags; + struct rpivid_hw_irq_ent *ient; + + spin_lock_irqsave(&ictl->lock, flags); + ictl->enable = n < 0 ? -1 : ictl->enable <= 0 ? n : ictl->enable + n; + ient = get_sched(ictl); + spin_unlock_irqrestore(&ictl->lock, flags); + + sched_cb(dev, ictl, ient); +} + +static void ictl_init(struct rpivid_hw_irq_ctrl * const ictl, int enables) +{ + spin_lock_init(&ictl->lock); + ictl->claim = NULL; + ictl->tail = NULL; + ictl->irq = NULL; + ictl->no_sched = 0; + ictl->enable = enables; + ictl->thread_reqed = false; +} + +static void ictl_uninit(struct rpivid_hw_irq_ctrl * const ictl) +{ + // Nothing to do +} + +#if !OPT_DEBUG_POLL_IRQ +static irqreturn_t rpivid_irq_irq(int irq, void *data) +{ + struct rpivid_dev * const dev = data; + __u32 ictrl; + + ictrl = irq_read(dev, ARG_IC_ICTRL); + if (!(ictrl & ARG_IC_ICTRL_ALL_IRQ_MASK)) { + v4l2_warn(&dev->v4l2_dev, "IRQ but no IRQ bits set\n"); + return IRQ_NONE; + } + + // Cancel any/all irqs + irq_write(dev, ARG_IC_ICTRL, ictrl & ~ARG_IC_ICTRL_SET_ZERO_MASK); + + // Service Active2 before Active1 so Phase 1 can transition to Phase 2 + // without delay + if (ictrl & ARG_IC_ICTRL_ACTIVE2_INT_SET) + do_irq(dev, &dev->ic_active2); + if (ictrl & ARG_IC_ICTRL_ACTIVE1_INT_SET) + do_irq(dev, &dev->ic_active1); + + return dev->ic_active1.thread_reqed || dev->ic_active2.thread_reqed ? + IRQ_WAKE_THREAD : IRQ_HANDLED; +} + +static void do_thread(struct rpivid_dev * const dev, + struct rpivid_hw_irq_ctrl *const ictl) +{ + unsigned long flags; + struct rpivid_hw_irq_ent *ient = NULL; + + spin_lock_irqsave(&ictl->lock, flags); + + if (ictl->thread_reqed) { + ient = ictl->irq; + ictl->thread_reqed = false; + ictl->irq = NULL; + } + + spin_unlock_irqrestore(&ictl->lock, flags); + + sched_cb(dev, ictl, ient); +} + +static irqreturn_t rpivid_irq_thread(int irq, void *data) +{ + struct rpivid_dev * const dev = data; + + do_thread(dev, &dev->ic_active1); + do_thread(dev, &dev->ic_active2); + + return IRQ_HANDLED; +} +#endif + +/* May only be called from Active1 CB + * IRQs should not be expected until execution continues in the cb + */ +void rpivid_hw_irq_active1_thread(struct rpivid_dev *dev, + struct rpivid_hw_irq_ent *ient, + rpivid_irq_callback thread_cb, void *ctx) +{ + pre_thread(dev, ient, thread_cb, ctx, &dev->ic_active1); +} + +void rpivid_hw_irq_active1_enable_claim(struct rpivid_dev *dev, + int n) +{ + do_enable_claim(dev, n, &dev->ic_active1); +} + +void rpivid_hw_irq_active1_claim(struct rpivid_dev *dev, + struct rpivid_hw_irq_ent *ient, + rpivid_irq_callback ready_cb, void *ctx) +{ + do_claim(dev, ient, ready_cb, ctx, &dev->ic_active1); +} + +void rpivid_hw_irq_active1_irq(struct rpivid_dev *dev, + struct rpivid_hw_irq_ent *ient, + rpivid_irq_callback irq_cb, void *ctx) +{ + pre_irq(dev, ient, irq_cb, ctx, &dev->ic_active1); +} + +void rpivid_hw_irq_active2_claim(struct rpivid_dev *dev, + struct rpivid_hw_irq_ent *ient, + rpivid_irq_callback ready_cb, void *ctx) +{ + do_claim(dev, ient, ready_cb, ctx, &dev->ic_active2); +} + +void rpivid_hw_irq_active2_irq(struct rpivid_dev *dev, + struct rpivid_hw_irq_ent *ient, + rpivid_irq_callback irq_cb, void *ctx) +{ + pre_irq(dev, ient, irq_cb, ctx, &dev->ic_active2); +} + +int rpivid_hw_probe(struct rpivid_dev *dev) +{ + struct rpi_firmware *firmware; + struct device_node *node; + struct resource *res; + __u32 irq_stat; + int irq_dec; + int ret = 0; + + ictl_init(&dev->ic_active1, RPIVID_P2BUF_COUNT); + ictl_init(&dev->ic_active2, RPIVID_ICTL_ENABLE_UNLIMITED); + + res = platform_get_resource_byname(dev->pdev, IORESOURCE_MEM, "intc"); + if (!res) + return -ENODEV; + + dev->base_irq = devm_ioremap(dev->dev, res->start, resource_size(res)); + if (IS_ERR(dev->base_irq)) + return PTR_ERR(dev->base_irq); + + res = platform_get_resource_byname(dev->pdev, IORESOURCE_MEM, "hevc"); + if (!res) + return -ENODEV; + + dev->base_h265 = devm_ioremap(dev->dev, res->start, resource_size(res)); + if (IS_ERR(dev->base_h265)) + return PTR_ERR(dev->base_h265); + + dev->clock = devm_clk_get(&dev->pdev->dev, "hevc"); + if (IS_ERR(dev->clock)) + return PTR_ERR(dev->clock); + + node = rpi_firmware_find_node(); + if (!node) + return -EINVAL; + + firmware = rpi_firmware_get(node); + of_node_put(node); + if (!firmware) + return -EPROBE_DEFER; + + dev->max_clock_rate = rpi_firmware_clk_get_max_rate(firmware, + RPI_FIRMWARE_HEVC_CLK_ID); + rpi_firmware_put(firmware); + + dev->cache_align = dma_get_cache_alignment(); + + // Disable IRQs & reset anything pending + irq_write(dev, 0, + ARG_IC_ICTRL_ACTIVE1_EN_SET | ARG_IC_ICTRL_ACTIVE2_EN_SET); + irq_stat = irq_read(dev, 0); + irq_write(dev, 0, irq_stat); + +#if !OPT_DEBUG_POLL_IRQ + irq_dec = platform_get_irq(dev->pdev, 0); + if (irq_dec <= 0) + return irq_dec; + ret = devm_request_threaded_irq(dev->dev, irq_dec, + rpivid_irq_irq, + rpivid_irq_thread, + 0, dev_name(dev->dev), dev); + if (ret) { + dev_err(dev->dev, "Failed to request IRQ - %d\n", ret); + + return ret; + } +#endif + return ret; +} + +void rpivid_hw_remove(struct rpivid_dev *dev) +{ + // IRQ auto freed on unload so no need to do it here + // ioremap auto freed on unload + ictl_uninit(&dev->ic_active1); + ictl_uninit(&dev->ic_active2); +} + diff --git a/drivers/staging/media/rpivid/rpivid_hw.h b/drivers/staging/media/rpivid/rpivid_hw.h new file mode 100644 index 00000000000000..ec73a2332b73f0 --- /dev/null +++ b/drivers/staging/media/rpivid/rpivid_hw.h @@ -0,0 +1,303 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Raspberry Pi HEVC driver + * + * Copyright (C) 2020 Raspberry Pi (Trading) Ltd + * + * Based on the Cedrus VPU driver, that is: + * + * Copyright (C) 2016 Florent Revest <florent.revest@free-electrons.com> + * Copyright (C) 2018 Paul Kocialkowski <paul.kocialkowski@bootlin.com> + * Copyright (C) 2018 Bootlin + */ + +#ifndef _RPIVID_HW_H_ +#define _RPIVID_HW_H_ + +struct rpivid_hw_irq_ent { + struct rpivid_hw_irq_ent *next; + rpivid_irq_callback cb; + void *v; +}; + +/* Phase 1 Register offsets */ + +#define RPI_SPS0 0 +#define RPI_SPS1 4 +#define RPI_PPS 8 +#define RPI_SLICE 12 +#define RPI_TILESTART 16 +#define RPI_TILEEND 20 +#define RPI_SLICESTART 24 +#define RPI_MODE 28 +#define RPI_LEFT0 32 +#define RPI_LEFT1 36 +#define RPI_LEFT2 40 +#define RPI_LEFT3 44 +#define RPI_QP 48 +#define RPI_CONTROL 52 +#define RPI_STATUS 56 +#define RPI_VERSION 60 +#define RPI_BFBASE 64 +#define RPI_BFNUM 68 +#define RPI_BFCONTROL 72 +#define RPI_BFSTATUS 76 +#define RPI_PUWBASE 80 +#define RPI_PUWSTRIDE 84 +#define RPI_COEFFWBASE 88 +#define RPI_COEFFWSTRIDE 92 +#define RPI_SLICECMDS 96 +#define RPI_BEGINTILEEND 100 +#define RPI_TRANSFER 104 +#define RPI_CFBASE 108 +#define RPI_CFNUM 112 +#define RPI_CFSTATUS 116 + +/* Phase 2 Register offsets */ + +#define RPI_PURBASE 0x8000 +#define RPI_PURSTRIDE 0x8004 +#define RPI_COEFFRBASE 0x8008 +#define RPI_COEFFRSTRIDE 0x800C +#define RPI_NUMROWS 0x8010 +#define RPI_CONFIG2 0x8014 +#define RPI_OUTYBASE 0x8018 +#define RPI_OUTYSTRIDE 0x801C +#define RPI_OUTCBASE 0x8020 +#define RPI_OUTCSTRIDE 0x8024 +#define RPI_STATUS2 0x8028 +#define RPI_FRAMESIZE 0x802C +#define RPI_MVBASE 0x8030 +#define RPI_MVSTRIDE 0x8034 +#define RPI_COLBASE 0x8038 +#define RPI_COLSTRIDE 0x803C +#define RPI_CURRPOC 0x8040 + +/* + * Write a general register value + * Order is unimportant + */ +static inline void apb_write(const struct rpivid_dev * const dev, + const unsigned int offset, const u32 val) +{ + writel_relaxed(val, dev->base_h265 + offset); +} + +/* Write the final register value that actually starts the phase */ +static inline void apb_write_final(const struct rpivid_dev * const dev, + const unsigned int offset, const u32 val) +{ + writel(val, dev->base_h265 + offset); +} + +static inline u32 apb_read(const struct rpivid_dev * const dev, + const unsigned int offset) +{ + return readl(dev->base_h265 + offset); +} + +static inline void irq_write(const struct rpivid_dev * const dev, + const unsigned int offset, const u32 val) +{ + writel(val, dev->base_irq + offset); +} + +static inline u32 irq_read(const struct rpivid_dev * const dev, + const unsigned int offset) +{ + return readl(dev->base_irq + offset); +} + +static inline void apb_write_vc_addr(const struct rpivid_dev * const dev, + const unsigned int offset, + const dma_addr_t a) +{ + apb_write(dev, offset, (u32)(a >> 6)); +} + +static inline void apb_write_vc_addr_final(const struct rpivid_dev * const dev, + const unsigned int offset, + const dma_addr_t a) +{ + apb_write_final(dev, offset, (u32)(a >> 6)); +} + +static inline void apb_write_vc_len(const struct rpivid_dev * const dev, + const unsigned int offset, + const unsigned int x) +{ + apb_write(dev, offset, (x + 63) >> 6); +} + +/* *ARG_IC_ICTRL - Interrupt control for ARGON Core* + * Offset (byte space) = 40'h2b10000 + * Physical Address (byte space) = 40'h7eb10000 + * Verilog Macro Address = `ARG_IC_REG_START + `ARGON_INTCTRL_ICTRL + * Reset Value = 32'b100x100x_100xxxxx_xxxxxxx0_x100x100 + * Access = RW (32-bit only) + * Interrupt control logic for ARGON Core. + */ +#define ARG_IC_ICTRL 0 + +/* acc=LWC ACTIVE1_INT FIELD ACCESS: LWC + * + * Interrupt 1 + * This is set and held when an hevc_active1 interrupt edge is detected + * The polarity of the edge is set by the ACTIVE1_EDGE field + * Write a 1 to this bit to clear down the latched interrupt + * The latched interrupt is only enabled out onto the interrupt line if + * ACTIVE1_EN is set + * Reset value is *0* decimal. + */ +#define ARG_IC_ICTRL_ACTIVE1_INT_SET BIT(0) + +/* ACTIVE1_EDGE Sets the polarity of the interrupt edge detection logic + * This logic detects edges of the hevc_active1 line from the argon core + * 0 = negedge, 1 = posedge + * Reset value is *0* decimal. + */ +#define ARG_IC_ICTRL_ACTIVE1_EDGE_SET BIT(1) + +/* ACTIVE1_EN Enables ACTIVE1_INT out onto the argon interrupt line. + * If this isn't set, the interrupt logic will work but no interrupt will be + * set to the interrupt controller + * Reset value is *1* decimal. + * + * [JC] The above appears to be a lie - if unset then b0 is never set + */ +#define ARG_IC_ICTRL_ACTIVE1_EN_SET BIT(2) + +/* acc=RO ACTIVE1_STATUS FIELD ACCESS: RO + * + * The current status of the hevc_active1 signal + */ +#define ARG_IC_ICTRL_ACTIVE1_STATUS_SET BIT(3) + +/* acc=LWC ACTIVE2_INT FIELD ACCESS: LWC + * + * Interrupt 2 + * This is set and held when an hevc_active2 interrupt edge is detected + * The polarity of the edge is set by the ACTIVE2_EDGE field + * Write a 1 to this bit to clear down the latched interrupt + * The latched interrupt is only enabled out onto the interrupt line if + * ACTIVE2_EN is set + * Reset value is *0* decimal. + */ +#define ARG_IC_ICTRL_ACTIVE2_INT_SET BIT(4) + +/* ACTIVE2_EDGE Sets the polarity of the interrupt edge detection logic + * This logic detects edges of the hevc_active2 line from the argon core + * 0 = negedge, 1 = posedge + * Reset value is *0* decimal. + */ +#define ARG_IC_ICTRL_ACTIVE2_EDGE_SET BIT(5) + +/* ACTIVE2_EN Enables ACTIVE2_INT out onto the argon interrupt line. + * If this isn't set, the interrupt logic will work but no interrupt will be + * set to the interrupt controller + * Reset value is *1* decimal. + */ +#define ARG_IC_ICTRL_ACTIVE2_EN_SET BIT(6) + +/* acc=RO ACTIVE2_STATUS FIELD ACCESS: RO + * + * The current status of the hevc_active2 signal + */ +#define ARG_IC_ICTRL_ACTIVE2_STATUS_SET BIT(7) + +/* TEST_INT Forces the argon int high for test purposes. + * Reset value is *0* decimal. + */ +#define ARG_IC_ICTRL_TEST_INT BIT(8) +#define ARG_IC_ICTRL_SPARE BIT(9) + +/* acc=RO VP9_INTERRUPT_STATUS FIELD ACCESS: RO + * + * The current status of the vp9_interrupt signal + */ +#define ARG_IC_ICTRL_VP9_INTERRUPT_STATUS BIT(10) + +/* AIO_INT_ENABLE 1 = Or the AIO int in with the Argon int so the VPU can see + * it + * 0 = the AIO int is masked. (It should still be connected to the GIC though). + */ +#define ARG_IC_ICTRL_AIO_INT_ENABLE BIT(20) +#define ARG_IC_ICTRL_H264_ACTIVE_INT BIT(21) +#define ARG_IC_ICTRL_H264_ACTIVE_EDGE BIT(22) +#define ARG_IC_ICTRL_H264_ACTIVE_EN BIT(23) +#define ARG_IC_ICTRL_H264_ACTIVE_STATUS BIT(24) +#define ARG_IC_ICTRL_H264_INTERRUPT_INT BIT(25) +#define ARG_IC_ICTRL_H264_INTERRUPT_EDGE BIT(26) +#define ARG_IC_ICTRL_H264_INTERRUPT_EN BIT(27) + +/* acc=RO H264_INTERRUPT_STATUS FIELD ACCESS: RO + * + * The current status of the h264_interrupt signal + */ +#define ARG_IC_ICTRL_H264_INTERRUPT_STATUS BIT(28) + +/* acc=LWC VP9_INTERRUPT_INT FIELD ACCESS: LWC + * + * Interrupt 1 + * This is set and held when an vp9_interrupt interrupt edge is detected + * The polarity of the edge is set by the VP9_INTERRUPT_EDGE field + * Write a 1 to this bit to clear down the latched interrupt + * The latched interrupt is only enabled out onto the interrupt line if + * VP9_INTERRUPT_EN is set + * Reset value is *0* decimal. + */ +#define ARG_IC_ICTRL_VP9_INTERRUPT_INT BIT(29) + +/* VP9_INTERRUPT_EDGE Sets the polarity of the interrupt edge detection logic + * This logic detects edges of the vp9_interrupt line from the argon h264 core + * 0 = negedge, 1 = posedge + * Reset value is *0* decimal. + */ +#define ARG_IC_ICTRL_VP9_INTERRUPT_EDGE BIT(30) + +/* VP9_INTERRUPT_EN Enables VP9_INTERRUPT_INT out onto the argon interrupt line. + * If this isn't set, the interrupt logic will work but no interrupt will be + * set to the interrupt controller + * Reset value is *1* decimal. + */ +#define ARG_IC_ICTRL_VP9_INTERRUPT_EN BIT(31) + +/* Bits 19:12, 11 reserved - read ?, write 0 */ +#define ARG_IC_ICTRL_SET_ZERO_MASK ((0xff << 12) | BIT(11)) + +/* All IRQ bits */ +#define ARG_IC_ICTRL_ALL_IRQ_MASK (\ + ARG_IC_ICTRL_VP9_INTERRUPT_INT |\ + ARG_IC_ICTRL_H264_INTERRUPT_INT |\ + ARG_IC_ICTRL_ACTIVE1_INT_SET |\ + ARG_IC_ICTRL_ACTIVE2_INT_SET) + +/* Regulate claim Q */ +void rpivid_hw_irq_active1_enable_claim(struct rpivid_dev *dev, + int n); +/* Auto release once all CBs called */ +void rpivid_hw_irq_active1_claim(struct rpivid_dev *dev, + struct rpivid_hw_irq_ent *ient, + rpivid_irq_callback ready_cb, void *ctx); +/* May only be called in claim cb */ +void rpivid_hw_irq_active1_irq(struct rpivid_dev *dev, + struct rpivid_hw_irq_ent *ient, + rpivid_irq_callback irq_cb, void *ctx); +/* May only be called in irq cb */ +void rpivid_hw_irq_active1_thread(struct rpivid_dev *dev, + struct rpivid_hw_irq_ent *ient, + rpivid_irq_callback thread_cb, void *ctx); + +/* Auto release once all CBs called */ +void rpivid_hw_irq_active2_claim(struct rpivid_dev *dev, + struct rpivid_hw_irq_ent *ient, + rpivid_irq_callback ready_cb, void *ctx); +/* May only be called in claim cb */ +void rpivid_hw_irq_active2_irq(struct rpivid_dev *dev, + struct rpivid_hw_irq_ent *ient, + rpivid_irq_callback irq_cb, void *ctx); + +int rpivid_hw_probe(struct rpivid_dev *dev); +void rpivid_hw_remove(struct rpivid_dev *dev); + +#endif diff --git a/drivers/staging/media/rpivid/rpivid_video.c b/drivers/staging/media/rpivid/rpivid_video.c new file mode 100644 index 00000000000000..55e31eda8120d9 --- /dev/null +++ b/drivers/staging/media/rpivid/rpivid_video.c @@ -0,0 +1,691 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Raspberry Pi HEVC driver + * + * Copyright (C) 2020 Raspberry Pi (Trading) Ltd + * + * Based on the Cedrus VPU driver, that is: + * + * Copyright (C) 2016 Florent Revest <florent.revest@free-electrons.com> + * Copyright (C) 2018 Paul Kocialkowski <paul.kocialkowski@bootlin.com> + * Copyright (C) 2018 Bootlin + */ + +#include <media/videobuf2-dma-contig.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-event.h> +#include <media/v4l2-mem2mem.h> + +#include "rpivid.h" +#include "rpivid_hw.h" +#include "rpivid_video.h" +#include "rpivid_dec.h" + +#define RPIVID_DECODE_SRC BIT(0) +#define RPIVID_DECODE_DST BIT(1) + +#define RPIVID_MIN_WIDTH 16U +#define RPIVID_MIN_HEIGHT 16U +#define RPIVID_DEFAULT_WIDTH 1920U +#define RPIVID_DEFAULT_HEIGHT 1088U +#define RPIVID_MAX_WIDTH 4096U +#define RPIVID_MAX_HEIGHT 4096U + +static inline struct rpivid_ctx *rpivid_file2ctx(struct file *file) +{ + return container_of(file->private_data, struct rpivid_ctx, fh); +} + +/* constrain x to y,y*2 */ +static inline unsigned int constrain2x(unsigned int x, unsigned int y) +{ + return (x < y) ? + y : + (x > y * 2) ? y : x; +} + +size_t rpivid_round_up_size(const size_t x) +{ + /* Admit no size < 256 */ + const unsigned int n = x < 256 ? 8 : ilog2(x); + + return x >= (3 << n) ? 4 << n : (3 << n); +} + +size_t rpivid_bit_buf_size(unsigned int w, unsigned int h, unsigned int bits_minus8) +{ + const size_t wxh = w * h; + size_t bits_alloc; + + /* Annex A gives a min compression of 2 @ lvl 3.1 + * (wxh <= 983040) and min 4 thereafter but avoid + * the odity of 983041 having a lower limit than + * 983040. + * Multiply by 3/2 for 4:2:0 + */ + bits_alloc = wxh < 983040 ? wxh * 3 / 4 : + wxh < 983040 * 2 ? 983040 * 3 / 4 : + wxh * 3 / 8; + /* Allow for bit depth */ + bits_alloc += (bits_alloc * bits_minus8) / 8; + return rpivid_round_up_size(bits_alloc); +} + +void rpivid_prepare_src_format(struct v4l2_pix_format_mplane *pix_fmt) +{ + size_t size; + u32 w; + u32 h; + + w = pix_fmt->width; + h = pix_fmt->height; + if (!w || !h) { + w = RPIVID_DEFAULT_WIDTH; + h = RPIVID_DEFAULT_HEIGHT; + } + if (w > RPIVID_MAX_WIDTH) + w = RPIVID_MAX_WIDTH; + if (h > RPIVID_MAX_HEIGHT) + h = RPIVID_MAX_HEIGHT; + + if (!pix_fmt->plane_fmt[0].sizeimage || + pix_fmt->plane_fmt[0].sizeimage > SZ_32M) { + /* Unspecified or way too big - pick max for size */ + size = rpivid_bit_buf_size(w, h, 2); + } + /* Set a minimum */ + size = max_t(u32, SZ_4K, pix_fmt->plane_fmt[0].sizeimage); + + pix_fmt->pixelformat = V4L2_PIX_FMT_HEVC_SLICE; + pix_fmt->width = w; + pix_fmt->height = h; + pix_fmt->num_planes = 1; + pix_fmt->field = V4L2_FIELD_NONE; + /* Zero bytes per line for encoded source. */ + pix_fmt->plane_fmt[0].bytesperline = 0; + pix_fmt->plane_fmt[0].sizeimage = size; +} + +/* Take any pix_format and make it valid */ +static void rpivid_prepare_dst_format(struct v4l2_pix_format_mplane *pix_fmt) +{ + unsigned int width = pix_fmt->width; + unsigned int height = pix_fmt->height; + unsigned int sizeimage = pix_fmt->plane_fmt[0].sizeimage; + unsigned int bytesperline = pix_fmt->plane_fmt[0].bytesperline; + + if (!width) + width = RPIVID_DEFAULT_WIDTH; + if (width > RPIVID_MAX_WIDTH) + width = RPIVID_MAX_WIDTH; + if (!height) + height = RPIVID_DEFAULT_HEIGHT; + if (height > RPIVID_MAX_HEIGHT) + height = RPIVID_MAX_HEIGHT; + + /* For column formats set bytesperline to column height (stride2) */ + switch (pix_fmt->pixelformat) { + default: + pix_fmt->pixelformat = V4L2_PIX_FMT_NV12_COL128; + fallthrough; + case V4L2_PIX_FMT_NV12_COL128: + /* Width rounds up to columns */ + width = ALIGN(width, 128); + + /* 16 aligned height - not sure we even need that */ + height = ALIGN(height, 16); + /* column height + * Accept suggested shape if at least min & < 2 * min + */ + bytesperline = constrain2x(bytesperline, height * 3 / 2); + + /* image size + * Again allow plausible variation in case added padding is + * required + */ + sizeimage = constrain2x(sizeimage, bytesperline * width); + break; + + case V4L2_PIX_FMT_NV12_10_COL128: + /* width in pixels (3 pels = 4 bytes) rounded to 128 byte + * columns + */ + width = ALIGN(((width + 2) / 3), 32) * 3; + + /* 16-aligned height. */ + height = ALIGN(height, 16); + + /* column height + * Accept suggested shape if at least min & < 2 * min + */ + bytesperline = constrain2x(bytesperline, height * 3 / 2); + + /* image size + * Again allow plausible variation in case added padding is + * required + */ + sizeimage = constrain2x(sizeimage, + bytesperline * width * 4 / 3); + break; + } + + pix_fmt->width = width; + pix_fmt->height = height; + + pix_fmt->field = V4L2_FIELD_NONE; + pix_fmt->plane_fmt[0].bytesperline = bytesperline; + pix_fmt->plane_fmt[0].sizeimage = sizeimage; + pix_fmt->num_planes = 1; +} + +static int rpivid_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, RPIVID_NAME, sizeof(cap->driver)); + strscpy(cap->card, RPIVID_NAME, sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), + "platform:%s", RPIVID_NAME); + + return 0; +} + +static int rpivid_enum_fmt_vid_out(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + // Input formats + + // H.265 Slice only currently + if (f->index == 0) { + f->pixelformat = V4L2_PIX_FMT_HEVC_SLICE; + return 0; + } + + return -EINVAL; +} + +static int rpivid_hevc_validate_sps(const struct v4l2_ctrl_hevc_sps * const sps) +{ + const unsigned int ctb_log2_size_y = + sps->log2_min_luma_coding_block_size_minus3 + 3 + + sps->log2_diff_max_min_luma_coding_block_size; + const unsigned int min_tb_log2_size_y = + sps->log2_min_luma_transform_block_size_minus2 + 2; + const unsigned int max_tb_log2_size_y = min_tb_log2_size_y + + sps->log2_diff_max_min_luma_transform_block_size; + + /* Local limitations */ + if (sps->pic_width_in_luma_samples < 32 || + sps->pic_width_in_luma_samples > 4096) + return 0; + if (sps->pic_height_in_luma_samples < 32 || + sps->pic_height_in_luma_samples > 4096) + return 0; + if (!(sps->bit_depth_luma_minus8 == 0 || + sps->bit_depth_luma_minus8 == 2)) + return 0; + if (sps->bit_depth_luma_minus8 != sps->bit_depth_chroma_minus8) + return 0; + if (sps->chroma_format_idc != 1) + return 0; + + /* Limits from H.265 7.4.3.2.1 */ + if (sps->log2_max_pic_order_cnt_lsb_minus4 > 12) + return 0; + if (sps->sps_max_dec_pic_buffering_minus1 > 15) + return 0; + if (sps->sps_max_num_reorder_pics > + sps->sps_max_dec_pic_buffering_minus1) + return 0; + if (ctb_log2_size_y > 6) + return 0; + if (max_tb_log2_size_y > 5) + return 0; + if (max_tb_log2_size_y > ctb_log2_size_y) + return 0; + if (sps->max_transform_hierarchy_depth_inter > + (ctb_log2_size_y - min_tb_log2_size_y)) + return 0; + if (sps->max_transform_hierarchy_depth_intra > + (ctb_log2_size_y - min_tb_log2_size_y)) + return 0; + /* Check pcm stuff */ + if (sps->num_short_term_ref_pic_sets > 64) + return 0; + if (sps->num_long_term_ref_pics_sps > 32) + return 0; + return 1; +} + +static u32 pixelformat_from_sps(const struct v4l2_ctrl_hevc_sps * const sps, + const int index) +{ + u32 pf = 0; + + if (!is_sps_set(sps) || !rpivid_hevc_validate_sps(sps)) { + /* Treat this as an error? For now return both */ + if (index == 0) + pf = V4L2_PIX_FMT_NV12_COL128; + else if (index == 1) + pf = V4L2_PIX_FMT_NV12_10_COL128; + } else if (index == 0) { + if (sps->bit_depth_luma_minus8 == 0) + pf = V4L2_PIX_FMT_NV12_COL128; + else if (sps->bit_depth_luma_minus8 == 2) + pf = V4L2_PIX_FMT_NV12_10_COL128; + } + + return pf; +} + +static struct v4l2_pix_format_mplane +rpivid_hevc_default_dst_fmt(struct rpivid_ctx * const ctx) +{ + const struct v4l2_ctrl_hevc_sps * const sps = + rpivid_find_control_data(ctx, V4L2_CID_STATELESS_HEVC_SPS); + struct v4l2_pix_format_mplane pix_fmt; + + memset(&pix_fmt, 0, sizeof(pix_fmt)); + if (is_sps_set(sps)) { + pix_fmt.width = sps->pic_width_in_luma_samples; + pix_fmt.height = sps->pic_height_in_luma_samples; + pix_fmt.pixelformat = pixelformat_from_sps(sps, 0); + } + + rpivid_prepare_dst_format(&pix_fmt); + return pix_fmt; +} + +static u32 rpivid_hevc_get_dst_pixelformat(struct rpivid_ctx * const ctx, + const int index) +{ + const struct v4l2_ctrl_hevc_sps * const sps = + rpivid_find_control_data(ctx, V4L2_CID_STATELESS_HEVC_SPS); + + return pixelformat_from_sps(sps, index); +} + +static int rpivid_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct rpivid_ctx * const ctx = rpivid_file2ctx(file); + + const u32 pf = rpivid_hevc_get_dst_pixelformat(ctx, f->index); + + if (pf == 0) + return -EINVAL; + + f->pixelformat = pf; + return 0; +} + +/* + * get dst format - sets it to default if otherwise unset + * returns a pointer to the struct as a convienience + */ +static struct v4l2_pix_format_mplane *get_dst_fmt(struct rpivid_ctx *const ctx) +{ + if (!ctx->dst_fmt_set) + ctx->dst_fmt = rpivid_hevc_default_dst_fmt(ctx); + return &ctx->dst_fmt; +} + +static int rpivid_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct rpivid_ctx *ctx = rpivid_file2ctx(file); + + f->fmt.pix_mp = *get_dst_fmt(ctx); + return 0; +} + +static int rpivid_g_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct rpivid_ctx *ctx = rpivid_file2ctx(file); + + f->fmt.pix_mp = ctx->src_fmt; + return 0; +} + +static inline void copy_color(struct v4l2_pix_format_mplane *d, + const struct v4l2_pix_format_mplane *s) +{ + d->colorspace = s->colorspace; + d->xfer_func = s->xfer_func; + d->ycbcr_enc = s->ycbcr_enc; + d->quantization = s->quantization; +} + +static int rpivid_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct rpivid_ctx *ctx = rpivid_file2ctx(file); + const struct v4l2_ctrl_hevc_sps * const sps = + rpivid_find_control_data(ctx, V4L2_CID_STATELESS_HEVC_SPS); + u32 pixelformat; + int i; + + for (i = 0; (pixelformat = pixelformat_from_sps(sps, i)) != 0; i++) { + if (f->fmt.pix_mp.pixelformat == pixelformat) + break; + } + + // We don't have any way of finding out colourspace so believe + // anything we are told - take anything set in src as a default + if (f->fmt.pix_mp.colorspace == V4L2_COLORSPACE_DEFAULT) + copy_color(&f->fmt.pix_mp, &ctx->src_fmt); + + f->fmt.pix_mp.pixelformat = pixelformat; + rpivid_prepare_dst_format(&f->fmt.pix_mp); + return 0; +} + +static int rpivid_try_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + rpivid_prepare_src_format(&f->fmt.pix_mp); + return 0; +} + +static int rpivid_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct rpivid_ctx *ctx = rpivid_file2ctx(file); + struct vb2_queue *vq; + int ret; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (vb2_is_busy(vq)) + return -EBUSY; + + ret = rpivid_try_fmt_vid_cap(file, priv, f); + if (ret) + return ret; + + ctx->dst_fmt = f->fmt.pix_mp; + ctx->dst_fmt_set = 1; + + return 0; +} + +static int rpivid_s_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct rpivid_ctx *ctx = rpivid_file2ctx(file); + struct vb2_queue *vq; + int ret; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (vb2_is_busy(vq)) + return -EBUSY; + + ret = rpivid_try_fmt_vid_out(file, priv, f); + if (ret) + return ret; + + ctx->src_fmt = f->fmt.pix_mp; + ctx->dst_fmt_set = 0; // Setting src invalidates dst + + vq->subsystem_flags |= + VB2_V4L2_FL_SUPPORTS_M2M_HOLD_CAPTURE_BUF; + + /* Propagate colorspace information to capture. */ + copy_color(&ctx->dst_fmt, &f->fmt.pix_mp); + return 0; +} + +const struct v4l2_ioctl_ops rpivid_ioctl_ops = { + .vidioc_querycap = rpivid_querycap, + + .vidioc_enum_fmt_vid_cap = rpivid_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap_mplane = rpivid_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap_mplane = rpivid_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap_mplane = rpivid_s_fmt_vid_cap, + + .vidioc_enum_fmt_vid_out = rpivid_enum_fmt_vid_out, + .vidioc_g_fmt_vid_out_mplane = rpivid_g_fmt_vid_out, + .vidioc_try_fmt_vid_out_mplane = rpivid_try_fmt_vid_out, + .vidioc_s_fmt_vid_out_mplane = rpivid_s_fmt_vid_out, + + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, + .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, + + .vidioc_try_decoder_cmd = v4l2_m2m_ioctl_stateless_try_decoder_cmd, + .vidioc_decoder_cmd = v4l2_m2m_ioctl_stateless_decoder_cmd, + + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static int rpivid_queue_setup(struct vb2_queue *vq, unsigned int *nbufs, + unsigned int *nplanes, unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct rpivid_ctx *ctx = vb2_get_drv_priv(vq); + struct v4l2_pix_format_mplane *pix_fmt; + + if (V4L2_TYPE_IS_OUTPUT(vq->type)) + pix_fmt = &ctx->src_fmt; + else + pix_fmt = get_dst_fmt(ctx); + + if (*nplanes) { + if (sizes[0] < pix_fmt->plane_fmt[0].sizeimage) + return -EINVAL; + } else { + sizes[0] = pix_fmt->plane_fmt[0].sizeimage; + *nplanes = 1; + } + + return 0; +} + +static void rpivid_queue_cleanup(struct vb2_queue *vq, u32 state) +{ + struct rpivid_ctx *ctx = vb2_get_drv_priv(vq); + struct vb2_v4l2_buffer *vbuf; + + for (;;) { + if (V4L2_TYPE_IS_OUTPUT(vq->type)) + vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + else + vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + + if (!vbuf) + return; + + v4l2_ctrl_request_complete(vbuf->vb2_buf.req_obj.req, + &ctx->hdl); + v4l2_m2m_buf_done(vbuf, state); + } +} + +static int rpivid_buf_out_validate(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + + vbuf->field = V4L2_FIELD_NONE; + return 0; +} + +static int rpivid_buf_prepare(struct vb2_buffer *vb) +{ + struct vb2_queue *vq = vb->vb2_queue; + struct rpivid_ctx *ctx = vb2_get_drv_priv(vq); + struct v4l2_pix_format_mplane *pix_fmt; + + if (V4L2_TYPE_IS_OUTPUT(vq->type)) + pix_fmt = &ctx->src_fmt; + else + pix_fmt = &ctx->dst_fmt; + + if (vb2_plane_size(vb, 0) < pix_fmt->plane_fmt[0].sizeimage) + return -EINVAL; + + vb2_set_plane_payload(vb, 0, pix_fmt->plane_fmt[0].sizeimage); + + return 0; +} + +/* Only stops the clock if streaom off on both output & capture */ +static void stop_clock(struct rpivid_dev *dev, struct rpivid_ctx *ctx) +{ + if (ctx->src_stream_on || + ctx->dst_stream_on) + return; + + clk_set_min_rate(dev->clock, 0); + clk_disable_unprepare(dev->clock); +} + +/* Always starts the clock if it isn't already on this ctx */ +static int start_clock(struct rpivid_dev *dev, struct rpivid_ctx *ctx) +{ + int rv; + + rv = clk_set_min_rate(dev->clock, dev->max_clock_rate); + if (rv) { + dev_err(dev->dev, "Failed to set clock rate\n"); + return rv; + } + + rv = clk_prepare_enable(dev->clock); + if (rv) { + dev_err(dev->dev, "Failed to enable clock\n"); + return rv; + } + + return 0; +} + +static int rpivid_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct rpivid_ctx *ctx = vb2_get_drv_priv(vq); + struct rpivid_dev *dev = ctx->dev; + int ret = 0; + + if (!V4L2_TYPE_IS_OUTPUT(vq->type)) { + ctx->dst_stream_on = 1; + goto ok; + } + + if (ctx->src_fmt.pixelformat != V4L2_PIX_FMT_HEVC_SLICE) { + ret = -EINVAL; + goto fail_cleanup; + } + + if (ctx->src_stream_on) + goto ok; + + ret = start_clock(dev, ctx); + if (ret) + goto fail_cleanup; + + if (dev->dec_ops->start) + ret = dev->dec_ops->start(ctx); + if (ret) + goto fail_stop_clock; + + ctx->src_stream_on = 1; +ok: + return 0; + +fail_stop_clock: + stop_clock(dev, ctx); +fail_cleanup: + v4l2_err(&dev->v4l2_dev, "%s: qtype=%d: FAIL\n", __func__, vq->type); + rpivid_queue_cleanup(vq, VB2_BUF_STATE_QUEUED); + return ret; +} + +static void rpivid_stop_streaming(struct vb2_queue *vq) +{ + struct rpivid_ctx *ctx = vb2_get_drv_priv(vq); + struct rpivid_dev *dev = ctx->dev; + + if (V4L2_TYPE_IS_OUTPUT(vq->type)) { + ctx->src_stream_on = 0; + if (dev->dec_ops->stop) + dev->dec_ops->stop(ctx); + } else { + ctx->dst_stream_on = 0; + } + + rpivid_queue_cleanup(vq, VB2_BUF_STATE_ERROR); + + vb2_wait_for_all_buffers(vq); + + stop_clock(dev, ctx); +} + +static void rpivid_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct rpivid_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); +} + +static void rpivid_buf_request_complete(struct vb2_buffer *vb) +{ + struct rpivid_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + + v4l2_ctrl_request_complete(vb->req_obj.req, &ctx->hdl); +} + +static struct vb2_ops rpivid_qops = { + .queue_setup = rpivid_queue_setup, + .buf_prepare = rpivid_buf_prepare, + .buf_queue = rpivid_buf_queue, + .buf_out_validate = rpivid_buf_out_validate, + .buf_request_complete = rpivid_buf_request_complete, + .start_streaming = rpivid_start_streaming, + .stop_streaming = rpivid_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +int rpivid_queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct rpivid_ctx *ctx = priv; + int ret; + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + src_vq->io_modes = VB2_MMAP | VB2_DMABUF; + src_vq->drv_priv = ctx; + src_vq->buf_struct_size = sizeof(struct rpivid_buffer); + src_vq->ops = &rpivid_qops; + src_vq->mem_ops = &vb2_dma_contig_memops; + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->lock = &ctx->ctx_mutex; + src_vq->dev = ctx->dev->dev; + src_vq->supports_requests = true; + src_vq->requires_requests = true; + + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + dst_vq->io_modes = VB2_MMAP | VB2_DMABUF; + dst_vq->drv_priv = ctx; + dst_vq->buf_struct_size = sizeof(struct rpivid_buffer); + dst_vq->min_queued_buffers = 1; + dst_vq->ops = &rpivid_qops; + dst_vq->mem_ops = &vb2_dma_contig_memops; + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->lock = &ctx->ctx_mutex; + dst_vq->dev = ctx->dev->dev; + + return vb2_queue_init(dst_vq); +} diff --git a/drivers/staging/media/rpivid/rpivid_video.h b/drivers/staging/media/rpivid/rpivid_video.h new file mode 100644 index 00000000000000..9db3a1968682f1 --- /dev/null +++ b/drivers/staging/media/rpivid/rpivid_video.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Raspberry Pi HEVC driver + * + * Copyright (C) 2020 Raspberry Pi (Trading) Ltd + * + * Based on the Cedrus VPU driver, that is: + * + * Copyright (C) 2016 Florent Revest <florent.revest@free-electrons.com> + * Copyright (C) 2018 Paul Kocialkowski <paul.kocialkowski@bootlin.com> + * Copyright (C) 2018 Bootlin + */ + +#ifndef _RPIVID_VIDEO_H_ +#define _RPIVID_VIDEO_H_ + +struct rpivid_format { + u32 pixelformat; + u32 directions; + unsigned int capabilities; +}; + +static inline int is_sps_set(const struct v4l2_ctrl_hevc_sps * const sps) +{ + return sps && sps->pic_width_in_luma_samples != 0; +} + +extern const struct v4l2_ioctl_ops rpivid_ioctl_ops; + +int rpivid_queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq); + +size_t rpivid_bit_buf_size(unsigned int w, unsigned int h, unsigned int bits_minus8); +size_t rpivid_round_up_size(const size_t x); + +void rpivid_prepare_src_format(struct v4l2_pix_format_mplane *pix_fmt); + +#endif diff --git a/drivers/staging/vc04_services/Kconfig b/drivers/staging/vc04_services/Kconfig index ccc8e15886482e..d3e43aa8a9e253 100644 --- a/drivers/staging/vc04_services/Kconfig +++ b/drivers/staging/vc04_services/Kconfig @@ -50,6 +50,10 @@ source "drivers/staging/vc04_services/bcm2835-audio/Kconfig" source "drivers/staging/vc04_services/bcm2835-camera/Kconfig" +source "drivers/staging/vc04_services/vc-sm-cma/Kconfig" +source "drivers/staging/vc04_services/bcm2835-codec/Kconfig" +source "drivers/staging/vc04_services/bcm2835-isp/Kconfig" + source "drivers/staging/vc04_services/vchiq-mmal/Kconfig" endif diff --git a/drivers/staging/vc04_services/Makefile b/drivers/staging/vc04_services/Makefile index dad3789522b804..49baf05fcf1ebd 100644 --- a/drivers/staging/vc04_services/Makefile +++ b/drivers/staging/vc04_services/Makefile @@ -14,4 +14,7 @@ endif obj-$(CONFIG_SND_BCM2835) += bcm2835-audio/ obj-$(CONFIG_VIDEO_BCM2835) += bcm2835-camera/ obj-$(CONFIG_BCM2835_VCHIQ_MMAL) += vchiq-mmal/ +obj-$(CONFIG_BCM_VC_SM_CMA) += vc-sm-cma/ +obj-$(CONFIG_VIDEO_CODEC_BCM2835) += bcm2835-codec/ +obj-$(CONFIG_VIDEO_ISP_BCM2835) += bcm2835-isp/ diff --git a/drivers/staging/vc04_services/bcm2835-audio/bcm2835-pcm.c b/drivers/staging/vc04_services/bcm2835-audio/bcm2835-pcm.c index 68e8d491a7ec8e..29e773fdd7ad0c 100644 --- a/drivers/staging/vc04_services/bcm2835-audio/bcm2835-pcm.c +++ b/drivers/staging/vc04_services/bcm2835-audio/bcm2835-pcm.c @@ -321,10 +321,11 @@ static const struct snd_pcm_ops snd_bcm2835_playback_spdif_ops = { /* create a pcm device */ int snd_bcm2835_new_pcm(struct bcm2835_chip *chip, const char *name, - int idx, enum snd_bcm2835_route route, + enum snd_bcm2835_route route, u32 numchannels, bool spdif) { struct snd_pcm *pcm; + int idx = chip->index++; int err; err = snd_pcm_new(chip->card, name, idx, numchannels, 0, &pcm); diff --git a/drivers/staging/vc04_services/bcm2835-audio/bcm2835.c b/drivers/staging/vc04_services/bcm2835-audio/bcm2835.c index b74cb104e9de00..0b2e0e8b2eec37 100644 --- a/drivers/staging/vc04_services/bcm2835-audio/bcm2835.c +++ b/drivers/staging/vc04_services/bcm2835-audio/bcm2835.c @@ -5,11 +5,13 @@ #include <linux/init.h> #include <linux/slab.h> #include <linux/module.h> +#include <linux/of.h> #include "../interface/vchiq_arm/vchiq_bus.h" #include "bcm2835.h" +#include <soc/bcm2835/raspberrypi-firmware.h> -static bool enable_hdmi; +static bool enable_hdmi, enable_hdmi0, enable_hdmi1; static bool enable_headphones = true; static int num_channels = MAX_SUBSTREAMS; @@ -65,14 +67,13 @@ static int bcm2835_audio_dual_newpcm(struct bcm2835_chip *chip, u32 numchannels) { int err; - - err = snd_bcm2835_new_pcm(chip, name, 0, route, + err = snd_bcm2835_new_pcm(chip, name, route, numchannels, false); if (err) return err; - err = snd_bcm2835_new_pcm(chip, "IEC958", 1, route, 1, true); + err = snd_bcm2835_new_pcm(chip, name, route, 1, true); if (err) return err; @@ -84,20 +85,33 @@ static int bcm2835_audio_simple_newpcm(struct bcm2835_chip *chip, enum snd_bcm2835_route route, u32 numchannels) { - return snd_bcm2835_new_pcm(chip, name, 0, route, numchannels, false); + return snd_bcm2835_new_pcm(chip, name, route, numchannels, false); } -static struct bcm2835_audio_driver bcm2835_audio_hdmi = { +static struct bcm2835_audio_driver bcm2835_audio_hdmi0 = { + .driver = { + .name = "bcm2835_hdmi", + .owner = THIS_MODULE, + }, + .shortname = "bcm2835 HDMI 1", + .longname = "bcm2835 HDMI 1", + .minchannels = 1, + .newpcm = bcm2835_audio_dual_newpcm, + .newctl = snd_bcm2835_new_hdmi_ctl, + .route = AUDIO_DEST_HDMI0 +}; + +static struct bcm2835_audio_driver bcm2835_audio_hdmi1 = { .driver = { .name = "bcm2835_hdmi", .owner = THIS_MODULE, }, - .shortname = "bcm2835 HDMI", - .longname = "bcm2835 HDMI", + .shortname = "bcm2835 HDMI 2", + .longname = "bcm2835 HDMI 2", .minchannels = 1, .newpcm = bcm2835_audio_dual_newpcm, .newctl = snd_bcm2835_new_hdmi_ctl, - .route = AUDIO_DEST_HDMI + .route = AUDIO_DEST_HDMI1 }; static struct bcm2835_audio_driver bcm2835_audio_headphones = { @@ -120,8 +134,12 @@ struct bcm2835_audio_drivers { static struct bcm2835_audio_drivers children_devices[] = { { - .audio_driver = &bcm2835_audio_hdmi, - .is_enabled = &enable_hdmi, + .audio_driver = &bcm2835_audio_hdmi0, + .is_enabled = &enable_hdmi0, + }, + { + .audio_driver = &bcm2835_audio_hdmi1, + .is_enabled = &enable_hdmi1, }, { .audio_driver = &bcm2835_audio_headphones, @@ -268,10 +286,70 @@ static int snd_add_child_devices(struct device *device, u32 numchans) return 0; } +static void set_hdmi_enables(struct device *dev) +{ + struct device_node *firmware_node; + struct rpi_firmware *firmware = NULL; + u32 num_displays, i, display_id; + int ret; + + firmware_node = of_find_compatible_node(NULL, NULL, + "raspberrypi,bcm2835-firmware"); + if (firmware_node) { + firmware = rpi_firmware_get(firmware_node); + of_node_put(firmware_node); + } + + if (!firmware) { + dev_err(dev, "Failed to get fw structure\n"); + return; + } + + ret = rpi_firmware_property(firmware, + RPI_FIRMWARE_FRAMEBUFFER_GET_NUM_DISPLAYS, + &num_displays, sizeof(u32)); + if (ret) { + dev_err(dev, "Failed to get fw property NUM_DISPLAYS\n"); + goto out_rpi_fw_put; + } + + for (i = 0; i < num_displays; i++) { + display_id = i; + ret = rpi_firmware_property(firmware, + RPI_FIRMWARE_FRAMEBUFFER_GET_DISPLAY_ID, + &display_id, sizeof(display_id)); + if (ret) { + dev_err(dev, "Failed to get fw property DISPLAY_ID " + "(i = %d)\n", i); + } else { + if (display_id == 2) + enable_hdmi0 = true; + if (display_id == 7) + enable_hdmi1 = true; + } + } + + if (!enable_hdmi0 && enable_hdmi1) { + /* Swap them over and reassign route. This means + * that if we only have one connected, it is always named + * HDMI1, irrespective of if its on port HDMI0 or HDMI1. + * This should match with the naming of HDMI ports in DRM + */ + enable_hdmi0 = true; + enable_hdmi1 = false; + bcm2835_audio_hdmi0.route = AUDIO_DEST_HDMI1; + } + +out_rpi_fw_put: + rpi_firmware_put(firmware); + return; +} + static int snd_bcm2835_alsa_probe(struct vchiq_device *device) { struct device *dev = &device->dev; int err; + u32 disable_headphones = 0; err = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); if (err) { @@ -285,6 +363,17 @@ static int snd_bcm2835_alsa_probe(struct vchiq_device *device) num_channels); } + if (enable_hdmi && + !of_property_read_bool(dev->of_node, "brcm,disable-hdmi")) + set_hdmi_enables(dev); + + if (enable_headphones) { + of_property_read_u32(dev->of_node, + "brcm,disable-headphones", + &disable_headphones); + enable_headphones = !disable_headphones; + } + err = bcm2835_devm_add_vchi_ctx(dev); if (err) return err; diff --git a/drivers/staging/vc04_services/bcm2835-audio/bcm2835.h b/drivers/staging/vc04_services/bcm2835-audio/bcm2835.h index 49ec5b496edb4b..8e0aa1b5a58afb 100644 --- a/drivers/staging/vc04_services/bcm2835-audio/bcm2835.h +++ b/drivers/staging/vc04_services/bcm2835-audio/bcm2835.h @@ -34,7 +34,8 @@ enum { enum snd_bcm2835_route { AUDIO_DEST_AUTO = 0, AUDIO_DEST_HEADPHONES = 1, - AUDIO_DEST_HDMI = 2, + AUDIO_DEST_HDMI0 = 2, + AUDIO_DEST_HDMI1 = 3, AUDIO_DEST_MAX, }; @@ -59,6 +60,7 @@ struct bcm2835_chip { int volume; int dest; int mute; + int index; unsigned int opened; unsigned int spdif_status; @@ -85,7 +87,7 @@ struct bcm2835_alsa_stream { }; int snd_bcm2835_new_pcm(struct bcm2835_chip *chip, const char *name, - int idx, enum snd_bcm2835_route route, + enum snd_bcm2835_route route, u32 numchannels, bool spdif); int snd_bcm2835_new_hdmi_ctl(struct bcm2835_chip *chip); diff --git a/drivers/staging/vc04_services/bcm2835-camera/bcm2835-camera.c b/drivers/staging/vc04_services/bcm2835-camera/bcm2835-camera.c index deec33f63bcf82..7746af21c7295c 100644 --- a/drivers/staging/vc04_services/bcm2835-camera/bcm2835-camera.c +++ b/drivers/staging/vc04_services/bcm2835-camera/bcm2835-camera.c @@ -177,7 +177,7 @@ static struct mmal_fmt formats[] = { .ybbp = 1, .remove_padding = true, }, { - .fourcc = V4L2_PIX_FMT_BGR32, + .fourcc = V4L2_PIX_FMT_BGRX32, .mmal = MMAL_ENCODING_BGRA, .depth = 32, .mmal_component = COMP_CAMERA, @@ -1445,6 +1445,7 @@ static const struct v4l2_ioctl_ops camera0_ioctl_ops = { .vidioc_querybuf = vb2_ioctl_querybuf, .vidioc_qbuf = vb2_ioctl_qbuf, .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, .vidioc_enum_framesizes = vidioc_enum_framesizes, .vidioc_enum_frameintervals = vidioc_enum_frameintervals, .vidioc_g_parm = vidioc_g_parm, @@ -1726,6 +1727,12 @@ static int mmal_init(struct bcm2835_mmal_dev *dev) MMAL_PARAMETER_MINIMISE_FRAGMENTATION, &enable, sizeof(enable)); + + /* Enable inserting headers into the first frame */ + vchiq_mmal_port_parameter_set(dev->instance, + &dev->component[COMP_VIDEO_ENCODE]->control, + MMAL_PARAMETER_VIDEO_ENCODE_HEADERS_WITH_FRAME, + &enable, sizeof(enable)); } ret = bcm2835_mmal_set_all_camera_controls(dev); if (ret < 0) { @@ -1924,7 +1931,7 @@ static int bcm2835_mmal_probe(struct vchiq_device *device) q = &dev->capture.vb_vidq; memset(q, 0, sizeof(*q)); q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ; + q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ | VB2_DMABUF; q->drv_priv = dev; q->buf_struct_size = sizeof(struct vb2_mmal_buffer); q->ops = &bcm2835_mmal_video_qops; diff --git a/drivers/staging/vc04_services/bcm2835-camera/bcm2835-camera.h b/drivers/staging/vc04_services/bcm2835-camera/bcm2835-camera.h index 0f0c6f7a376434..f27cc8e55c801e 100644 --- a/drivers/staging/vc04_services/bcm2835-camera/bcm2835-camera.h +++ b/drivers/staging/vc04_services/bcm2835-camera/bcm2835-camera.h @@ -13,7 +13,7 @@ * core driver device */ -#define V4L2_CTRL_COUNT 29 /* number of v4l controls */ +#define V4L2_CTRL_COUNT 32 /* number of v4l controls */ enum { COMP_CAMERA = 0, diff --git a/drivers/staging/vc04_services/bcm2835-camera/controls.c b/drivers/staging/vc04_services/bcm2835-camera/controls.c index 6bce45925bf1f9..40753c8fd5f392 100644 --- a/drivers/staging/vc04_services/bcm2835-camera/controls.c +++ b/drivers/staging/vc04_services/bcm2835-camera/controls.c @@ -468,6 +468,10 @@ static int ctrl_set_awb_mode(struct bcm2835_mmal_dev *dev, case V4L2_WHITE_BALANCE_SHADE: u32_value = MMAL_PARAM_AWBMODE_SHADE; break; + + case V4L2_WHITE_BALANCE_GREYWORLD: + u32_value = MMAL_PARAM_AWBMODE_GREYWORLD; + break; } return vchiq_mmal_port_parameter_set(dev->instance, control, @@ -700,6 +704,8 @@ static int ctrl_set_video_encode_profile_level(struct bcm2835_mmal_dev *dev, case V4L2_MPEG_VIDEO_H264_LEVEL_3_1: case V4L2_MPEG_VIDEO_H264_LEVEL_3_2: case V4L2_MPEG_VIDEO_H264_LEVEL_4_0: + case V4L2_MPEG_VIDEO_H264_LEVEL_4_1: + case V4L2_MPEG_VIDEO_H264_LEVEL_4_2: dev->capture.enc_level = ctrl->val; break; default: @@ -765,6 +771,17 @@ static int ctrl_set_video_encode_profile_level(struct bcm2835_mmal_dev *dev, case V4L2_MPEG_VIDEO_H264_LEVEL_4_0: param.level = MMAL_VIDEO_LEVEL_H264_4; break; + /* + * Note that the hardware spec is level 4.0. Achieving levels + * above that depend on exactly the resolution and frame rate + * being requested. + */ + case V4L2_MPEG_VIDEO_H264_LEVEL_4_1: + param.level = MMAL_VIDEO_LEVEL_H264_41; + break; + case V4L2_MPEG_VIDEO_H264_LEVEL_4_2: + param.level = MMAL_VIDEO_LEVEL_H264_42; + break; default: /* Should never get here */ break; @@ -1046,8 +1063,8 @@ static const struct bcm2835_mmal_v4l2_ctrl v4l2_ctrls[V4L2_CTRL_COUNT] = { { .id = V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE, .type = MMAL_CONTROL_TYPE_STD_MENU, - .min = ~0x3ff, - .max = V4L2_WHITE_BALANCE_SHADE, + .min = ~0x7ff, + .max = V4L2_WHITE_BALANCE_GREYWORLD, .def = V4L2_WHITE_BALANCE_AUTO, .step = 0, .imenu = NULL, @@ -1214,8 +1231,10 @@ static const struct bcm2835_mmal_v4l2_ctrl v4l2_ctrls[V4L2_CTRL_COUNT] = { BIT(V4L2_MPEG_VIDEO_H264_LEVEL_3_0) | BIT(V4L2_MPEG_VIDEO_H264_LEVEL_3_1) | BIT(V4L2_MPEG_VIDEO_H264_LEVEL_3_2) | - BIT(V4L2_MPEG_VIDEO_H264_LEVEL_4_0)), - .max = V4L2_MPEG_VIDEO_H264_LEVEL_4_0, + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_4_0) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_4_1) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_4_2)), + .max = V4L2_MPEG_VIDEO_H264_LEVEL_4_2, .def = V4L2_MPEG_VIDEO_H264_LEVEL_4_0, .step = 1, .imenu = NULL, @@ -1245,6 +1264,39 @@ static const struct bcm2835_mmal_v4l2_ctrl v4l2_ctrls[V4L2_CTRL_COUNT] = { .mmal_id = MMAL_PARAMETER_INTRAPERIOD, .setter = ctrl_set_video_encode_param_output, }, + { + .id = V4L2_CID_MPEG_VIDEO_H264_MIN_QP, + .type = MMAL_CONTROL_TYPE_STD, + .min = 0, + .max = 51, + .def = 0, + .step = 1, + .imenu = NULL, + .mmal_id = MMAL_PARAMETER_VIDEO_ENCODE_MIN_QUANT, + .setter = ctrl_set_video_encode_param_output, + }, + { + .id = V4L2_CID_MPEG_VIDEO_H264_MAX_QP, + .type = MMAL_CONTROL_TYPE_STD, + .min = 0, + .max = 51, + .def = 0, + .step = 1, + .imenu = NULL, + .mmal_id = MMAL_PARAMETER_VIDEO_ENCODE_MAX_QUANT, + .setter = ctrl_set_video_encode_param_output, + }, + { + .id = V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME, + .type = MMAL_CONTROL_TYPE_STD, + .min = 0, + .max = 0, + .def = 0, + .step = 0, + .imenu = NULL, + .mmal_id = MMAL_PARAMETER_VIDEO_REQUEST_I_FRAME, + .setter = ctrl_set_video_encode_param_output, + }, }; int bcm2835_mmal_set_all_camera_controls(struct bcm2835_mmal_dev *dev) diff --git a/drivers/staging/vc04_services/bcm2835-codec/Kconfig b/drivers/staging/vc04_services/bcm2835-codec/Kconfig new file mode 100644 index 00000000000000..761c8ba4b40fc3 --- /dev/null +++ b/drivers/staging/vc04_services/bcm2835-codec/Kconfig @@ -0,0 +1,11 @@ +config VIDEO_CODEC_BCM2835 + tristate "BCM2835 Video codec support" + depends on MEDIA_SUPPORT && MEDIA_CONTROLLER + depends on VIDEO_DEV && (ARCH_BCM2835 || COMPILE_TEST) + select BCM2835_VCHIQ_MMAL + select VIDEOBUF2_DMA_CONTIG + select V4L2_MEM2MEM_DEV + help + Say Y here to enable the V4L2 video codecs for + Broadcom BCM2835 SoC. This operates over the VCHIQ interface + to a service running on VideoCore. diff --git a/drivers/staging/vc04_services/bcm2835-codec/Makefile b/drivers/staging/vc04_services/bcm2835-codec/Makefile new file mode 100644 index 00000000000000..b2fe5ebb4a5a0e --- /dev/null +++ b/drivers/staging/vc04_services/bcm2835-codec/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +bcm2835-codec-objs := bcm2835-v4l2-codec.o + +obj-$(CONFIG_VIDEO_CODEC_BCM2835) += bcm2835-codec.o + +ccflags-y += \ + -D__VCCOREVER__=0x04000000 diff --git a/drivers/staging/vc04_services/bcm2835-codec/TODO b/drivers/staging/vc04_services/bcm2835-codec/TODO new file mode 100644 index 00000000000000..bc27a04ee9bd92 --- /dev/null +++ b/drivers/staging/vc04_services/bcm2835-codec/TODO @@ -0,0 +1 @@ +No issues. Depends on VCHIQ which is in staging. \ No newline at end of file diff --git a/drivers/staging/vc04_services/bcm2835-codec/bcm2835-v4l2-codec.c b/drivers/staging/vc04_services/bcm2835-codec/bcm2835-v4l2-codec.c new file mode 100644 index 00000000000000..de8f68b66c3830 --- /dev/null +++ b/drivers/staging/vc04_services/bcm2835-codec/bcm2835-v4l2-codec.c @@ -0,0 +1,4026 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * A v4l2-mem2mem device that wraps the video codec MMAL component. + * + * Copyright 2018 Raspberry Pi (Trading) Ltd. + * Author: Dave Stevenson (dave.stevenson@raspberrypi.com) + * + * Loosely based on the vim2m virtual driver by Pawel Osciak + * Copyright (c) 2009-2010 Samsung Electronics Co., Ltd. + * Pawel Osciak, <pawel@osciak.com> + * Marek Szyprowski, <m.szyprowski@samsung.com> + * + * Whilst this driver uses the v4l2_mem2mem framework, it does not need the + * scheduling aspects, so will always take the buffers, pass them to the VPU, + * and then signal the job as complete. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the + * License, or (at your option) any later version + */ +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/timer.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/syscalls.h> + +#include <media/v4l2-mem2mem.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-event.h> +#include <media/videobuf2-dma-contig.h> + +#include "../interface/vchiq_arm/vchiq_bus.h" + +#include "../vchiq-mmal/mmal-encodings.h" +#include "../vchiq-mmal/mmal-msg.h" +#include "../vchiq-mmal/mmal-parameters.h" +#include "../vchiq-mmal/mmal-vchiq.h" + +MODULE_IMPORT_NS(DMA_BUF); + +/* + * Default /dev/videoN node numbers for decode and encode. + * Deliberately avoid the very low numbers as these are often taken by webcams + * etc, and simple apps tend to only go for /dev/video0. + */ +static int decode_video_nr = 10; +module_param(decode_video_nr, int, 0644); +MODULE_PARM_DESC(decode_video_nr, "decoder video device number"); + +static int encode_video_nr = 11; +module_param(encode_video_nr, int, 0644); +MODULE_PARM_DESC(encode_video_nr, "encoder video device number"); + +static int isp_video_nr = 12; +module_param(isp_video_nr, int, 0644); +MODULE_PARM_DESC(isp_video_nr, "isp video device number"); + +static int deinterlace_video_nr = 18; +module_param(deinterlace_video_nr, int, 0644); +MODULE_PARM_DESC(deinterlace_video_nr, "deinterlace video device number"); + +static int encode_image_nr = 31; +module_param(encode_image_nr, int, 0644); +MODULE_PARM_DESC(encode_image_nr, "encoder image device number"); + +/* + * Workaround for GStreamer v4l2convert component not considering Bayer formats + * as raw, and therefore not considering a V4L2 device that supports them as + * a suitable candidate. + */ +static bool disable_bayer; +module_param(disable_bayer, bool, 0644); +MODULE_PARM_DESC(disable_bayer, "Disable support for Bayer formats"); + +static unsigned int debug; +module_param(debug, uint, 0644); +MODULE_PARM_DESC(debug, "activates debug info (0-3)"); + +static bool advanced_deinterlace = true; +module_param(advanced_deinterlace, bool, 0644); +MODULE_PARM_DESC(advanced_deinterlace, "Use advanced deinterlace"); + +static int field_override; +module_param(field_override, int, 0644); +MODULE_PARM_DESC(field_override, "force TB(8)/BT(9) field"); + +enum bcm2835_codec_role { + DECODE, + ENCODE, + ISP, + DEINTERLACE, + ENCODE_IMAGE, + NUM_ROLES +}; + +static const char * const roles[] = { + "decode", + "encode", + "isp", + "image_fx", + "encode_image", +}; + +static const char * const components[] = { + "ril.video_decode", + "ril.video_encode", + "ril.isp", + "ril.image_fx", + "ril.image_encode", +}; + +/* Timeout for stop_streaming to allow all buffers to return */ +#define COMPLETE_TIMEOUT (2 * HZ) + +#define MIN_W 32 +#define MIN_H 32 +#define MAX_W_CODEC 1920 +#define MAX_H_CODEC 1920 +#define MAX_W_ISP 16384 +#define MAX_H_ISP 16384 +#define BPL_ALIGN 32 +/* + * The decoder spec supports the V4L2_EVENT_SOURCE_CHANGE event, but the docs + * seem to want it to always be generated on startup, which prevents the client + * from configuring the CAPTURE queue based on any parsing it has already done + * which may save time and allow allocation of CAPTURE buffers early. Surely + * SOURCE_CHANGE means something has changed, not just "always notify". + * + * For those clients that don't set the CAPTURE resolution, adopt a default + * resolution that is seriously unlikely to be correct, therefore almost + * guaranteed to get the SOURCE_CHANGE event. + */ +#define DEFAULT_WIDTH 32 +#define DEFAULT_HEIGHT 32 +/* + * The unanswered question - what is the maximum size of a compressed frame? + * V4L2 mandates that the encoded frame must fit in a single buffer. Sizing + * that buffer is a compromise between wasting memory and risking not fitting. + * The 1080P version of Big Buck Bunny has some frames that exceed 512kB. + * Adopt a moderately arbitrary split at 720P for switching between 512 and + * 768kB buffers. + */ +#define DEF_COMP_BUF_SIZE_GREATER_720P (768 << 10) +#define DEF_COMP_BUF_SIZE_720P_OR_LESS (512 << 10) +/* JPEG image can be very large. For paranoid reasons 4MB is used */ +#define DEF_COMP_BUF_SIZE_JPEG (4096 << 10) + +/* Flags that indicate a format can be used for capture/output */ +#define MEM2MEM_CAPTURE BIT(0) +#define MEM2MEM_OUTPUT BIT(1) + +#define MEM2MEM_NAME "bcm2835-codec" + +struct bcm2835_codec_fmt { + u32 fourcc; + int depth; + u8 bytesperline_align[NUM_ROLES]; + u32 flags; + u32 mmal_fmt; + int size_multiplier_x2; + bool is_bayer; +}; + +static const struct bcm2835_codec_fmt supported_formats[] = { + { + /* YUV formats */ + .fourcc = V4L2_PIX_FMT_YUV420, + .depth = 8, + .bytesperline_align = { 32, 64, 64, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_I420, + .size_multiplier_x2 = 3, + }, { + .fourcc = V4L2_PIX_FMT_YVU420, + .depth = 8, + .bytesperline_align = { 32, 64, 64, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_YV12, + .size_multiplier_x2 = 3, + }, { + .fourcc = V4L2_PIX_FMT_NV12, + .depth = 8, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_NV12, + .size_multiplier_x2 = 3, + }, { + .fourcc = V4L2_PIX_FMT_NV21, + .depth = 8, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_NV21, + .size_multiplier_x2 = 3, + }, { + .fourcc = V4L2_PIX_FMT_RGB565, + .depth = 16, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_RGB16, + .size_multiplier_x2 = 2, + }, { + .fourcc = V4L2_PIX_FMT_YUYV, + .depth = 16, + .bytesperline_align = { 64, 64, 64, 64, 64 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_YUYV, + .size_multiplier_x2 = 2, + }, { + .fourcc = V4L2_PIX_FMT_UYVY, + .depth = 16, + .bytesperline_align = { 64, 64, 64, 64, 64 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_UYVY, + .size_multiplier_x2 = 2, + }, { + .fourcc = V4L2_PIX_FMT_YVYU, + .depth = 16, + .bytesperline_align = { 64, 64, 64, 64, 64 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_YVYU, + .size_multiplier_x2 = 2, + }, { + .fourcc = V4L2_PIX_FMT_VYUY, + .depth = 16, + .bytesperline_align = { 64, 64, 64, 64, 64 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_VYUY, + .size_multiplier_x2 = 2, + }, { + .fourcc = V4L2_PIX_FMT_NV12_COL128, + .depth = 8, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_YUVUV128, + .size_multiplier_x2 = 3, + }, { + /* RGB formats */ + .fourcc = V4L2_PIX_FMT_RGB24, + .depth = 24, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_RGB24, + .size_multiplier_x2 = 2, + }, { + .fourcc = V4L2_PIX_FMT_BGR24, + .depth = 24, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_BGR24, + .size_multiplier_x2 = 2, + }, { + .fourcc = V4L2_PIX_FMT_BGR32, + .depth = 32, + .bytesperline_align = { 64, 64, 64, 64, 64 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_BGRA, + .size_multiplier_x2 = 2, + }, { + .fourcc = V4L2_PIX_FMT_RGBA32, + .depth = 32, + .bytesperline_align = { 64, 64, 64, 64, 64 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_RGBA, + .size_multiplier_x2 = 2, + }, { + /* Bayer formats */ + /* 8 bit */ + .fourcc = V4L2_PIX_FMT_SRGGB8, + .depth = 8, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_BAYER_SRGGB8, + .size_multiplier_x2 = 2, + .is_bayer = true, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR8, + .depth = 8, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_BAYER_SBGGR8, + .size_multiplier_x2 = 2, + .is_bayer = true, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG8, + .depth = 8, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_BAYER_SGRBG8, + .size_multiplier_x2 = 2, + .is_bayer = true, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG8, + .depth = 8, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_BAYER_SGBRG8, + .size_multiplier_x2 = 2, + .is_bayer = true, + }, { + /* 10 bit */ + .fourcc = V4L2_PIX_FMT_SRGGB10P, + .depth = 10, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_BAYER_SRGGB10P, + .size_multiplier_x2 = 2, + .is_bayer = true, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR10P, + .depth = 10, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_BAYER_SBGGR10P, + .size_multiplier_x2 = 2, + .is_bayer = true, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG10P, + .depth = 10, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_BAYER_SGRBG10P, + .size_multiplier_x2 = 2, + .is_bayer = true, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG10P, + .depth = 10, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_BAYER_SGBRG10P, + .size_multiplier_x2 = 2, + .is_bayer = true, + }, { + /* 12 bit */ + .fourcc = V4L2_PIX_FMT_SRGGB12P, + .depth = 12, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_BAYER_SRGGB12P, + .size_multiplier_x2 = 2, + .is_bayer = true, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR12P, + .depth = 12, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_BAYER_SBGGR12P, + .size_multiplier_x2 = 2, + .is_bayer = true, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG12P, + .depth = 12, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_BAYER_SGRBG12P, + .size_multiplier_x2 = 2, + .is_bayer = true, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG12P, + .depth = 12, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_BAYER_SGBRG12P, + .size_multiplier_x2 = 2, + .is_bayer = true, + }, { + /* 14 bit */ + .fourcc = V4L2_PIX_FMT_SRGGB14P, + .depth = 14, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_BAYER_SRGGB14P, + .size_multiplier_x2 = 2, + .is_bayer = true, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR14P, + .depth = 14, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_BAYER_SBGGR14P, + .size_multiplier_x2 = 2, + .is_bayer = true, + + }, { + .fourcc = V4L2_PIX_FMT_SGRBG14P, + .depth = 14, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_BAYER_SGRBG14P, + .size_multiplier_x2 = 2, + .is_bayer = true, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG14P, + .depth = 14, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_BAYER_SGBRG14P, + .size_multiplier_x2 = 2, + .is_bayer = true, + }, { + /* 16 bit */ + .fourcc = V4L2_PIX_FMT_SRGGB16, + .depth = 16, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_BAYER_SRGGB16, + .size_multiplier_x2 = 2, + .is_bayer = true, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR16, + .depth = 16, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_BAYER_SBGGR16, + .size_multiplier_x2 = 2, + .is_bayer = true, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG16, + .depth = 16, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_BAYER_SGRBG16, + .size_multiplier_x2 = 2, + .is_bayer = true, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG16, + .depth = 16, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_BAYER_SGBRG16, + .size_multiplier_x2 = 2, + .is_bayer = true, + }, { + /* Bayer formats unpacked to 16bpp */ + /* 10 bit */ + .fourcc = V4L2_PIX_FMT_SRGGB10, + .depth = 16, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_BAYER_SRGGB10, + .size_multiplier_x2 = 2, + .is_bayer = true, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR10, + .depth = 16, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_BAYER_SBGGR10, + .size_multiplier_x2 = 2, + .is_bayer = true, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG10, + .depth = 16, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_BAYER_SGRBG10, + .size_multiplier_x2 = 2, + .is_bayer = true, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG10, + .depth = 16, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_BAYER_SGBRG10, + .size_multiplier_x2 = 2, + .is_bayer = true, + }, { + /* 12 bit */ + .fourcc = V4L2_PIX_FMT_SRGGB12, + .depth = 16, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_BAYER_SRGGB12, + .size_multiplier_x2 = 2, + .is_bayer = true, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR12, + .depth = 16, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_BAYER_SBGGR12, + .size_multiplier_x2 = 2, + .is_bayer = true, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG12, + .depth = 16, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_BAYER_SGRBG12, + .size_multiplier_x2 = 2, + .is_bayer = true, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG12, + .depth = 16, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_BAYER_SGBRG12, + .size_multiplier_x2 = 2, + .is_bayer = true, + }, { + /* 14 bit */ + .fourcc = V4L2_PIX_FMT_SRGGB14, + .depth = 16, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_BAYER_SRGGB14, + .size_multiplier_x2 = 2, + .is_bayer = true, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR14, + .depth = 16, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_BAYER_SBGGR14, + .size_multiplier_x2 = 2, + .is_bayer = true, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG14, + .depth = 16, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_BAYER_SGRBG14, + .size_multiplier_x2 = 2, + .is_bayer = true, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG14, + .depth = 16, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_BAYER_SGBRG14, + .size_multiplier_x2 = 2, + .is_bayer = true, + }, { + /* Monochrome MIPI formats */ + /* 8 bit */ + .fourcc = V4L2_PIX_FMT_GREY, + .depth = 8, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_GREY, + .size_multiplier_x2 = 2, + }, { + /* 10 bit */ + .fourcc = V4L2_PIX_FMT_Y10P, + .depth = 10, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_Y10P, + .size_multiplier_x2 = 2, + }, { + /* 12 bit */ + .fourcc = V4L2_PIX_FMT_Y12P, + .depth = 12, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_Y12P, + .size_multiplier_x2 = 2, + }, { + /* 14 bit */ + .fourcc = V4L2_PIX_FMT_Y14P, + .depth = 14, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_Y14P, + .size_multiplier_x2 = 2, + }, { + /* 16 bit */ + .fourcc = V4L2_PIX_FMT_Y16, + .depth = 16, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_Y16, + .size_multiplier_x2 = 2, + }, { + /* 10 bit as 16bpp */ + .fourcc = V4L2_PIX_FMT_Y10, + .depth = 16, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_Y10, + .size_multiplier_x2 = 2, + }, { + /* 12 bit as 16bpp */ + .fourcc = V4L2_PIX_FMT_Y12, + .depth = 16, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_Y12, + .size_multiplier_x2 = 2, + }, { + /* 14 bit as 16bpp */ + .fourcc = V4L2_PIX_FMT_Y14, + .depth = 16, + .bytesperline_align = { 32, 32, 32, 32, 32 }, + .flags = 0, + .mmal_fmt = MMAL_ENCODING_Y14, + .size_multiplier_x2 = 2, + }, { + /* Compressed formats */ + .fourcc = V4L2_PIX_FMT_H264, + .depth = 0, + .flags = V4L2_FMT_FLAG_COMPRESSED, + .mmal_fmt = MMAL_ENCODING_H264, + }, { + .fourcc = V4L2_PIX_FMT_JPEG, + .depth = 0, + .flags = V4L2_FMT_FLAG_COMPRESSED, + .mmal_fmt = MMAL_ENCODING_JPEG, + }, { + .fourcc = V4L2_PIX_FMT_MJPEG, + .depth = 0, + .flags = V4L2_FMT_FLAG_COMPRESSED, + .mmal_fmt = MMAL_ENCODING_MJPEG, + }, { + .fourcc = V4L2_PIX_FMT_MPEG4, + .depth = 0, + .flags = V4L2_FMT_FLAG_COMPRESSED, + .mmal_fmt = MMAL_ENCODING_MP4V, + }, { + .fourcc = V4L2_PIX_FMT_H263, + .depth = 0, + .flags = V4L2_FMT_FLAG_COMPRESSED, + .mmal_fmt = MMAL_ENCODING_H263, + }, { + .fourcc = V4L2_PIX_FMT_MPEG2, + .depth = 0, + .flags = V4L2_FMT_FLAG_COMPRESSED, + .mmal_fmt = MMAL_ENCODING_MP2V, + }, { + .fourcc = V4L2_PIX_FMT_VC1_ANNEX_G, + .depth = 0, + .flags = V4L2_FMT_FLAG_COMPRESSED, + .mmal_fmt = MMAL_ENCODING_WVC1, + } +}; + +struct bcm2835_codec_fmt_list { + struct bcm2835_codec_fmt *list; + unsigned int num_entries; +}; + +struct m2m_mmal_buffer { + struct v4l2_m2m_buffer m2m; + struct mmal_buffer mmal; +}; + +/* Per-queue, driver-specific private data */ +struct bcm2835_codec_q_data { + /* + * These parameters should be treated as gospel, with everything else + * being determined from them. + */ + /* Buffer width/height */ + unsigned int bytesperline; + unsigned int height; + /* Crop size used for selection handling */ + unsigned int crop_width; + unsigned int crop_height; + bool selection_set; + struct v4l2_fract aspect_ratio; + enum v4l2_field field; + + unsigned int sizeimage; + unsigned int sequence; + struct bcm2835_codec_fmt *fmt; + + /* One extra buffer header so we can send an EOS. */ + struct m2m_mmal_buffer eos_buffer; + bool eos_buffer_in_use; /* debug only */ +}; + +struct bcm2835_codec_dev { + struct vchiq_device *device; + + /* v4l2 devices */ + struct v4l2_device v4l2_dev; + struct video_device vfd; + /* mutex for the v4l2 device */ + struct mutex dev_mutex; + atomic_t num_inst; + + /* allocated mmal instance and components */ + enum bcm2835_codec_role role; + /* The list of formats supported on input and output queues. */ + struct bcm2835_codec_fmt_list supported_fmts[2]; + + /* + * Max size supported varies based on role. Store during + * bcm2835_codec_create for use later. + */ + unsigned int max_w; + unsigned int max_h; + + struct vchiq_mmal_instance *instance; + + struct v4l2_m2m_dev *m2m_dev; +}; + +struct bcm2835_codec_ctx { + struct v4l2_fh fh; + struct bcm2835_codec_dev *dev; + + struct v4l2_ctrl_handler hdl; + struct v4l2_ctrl *gop_size; + + struct vchiq_mmal_component *component; + bool component_enabled; + + enum v4l2_colorspace colorspace; + enum v4l2_ycbcr_encoding ycbcr_enc; + enum v4l2_xfer_func xfer_func; + enum v4l2_quantization quant; + + int hflip; + int vflip; + + /* Source and destination queue data */ + struct bcm2835_codec_q_data q_data[2]; + s32 bitrate; + unsigned int framerate_num; + unsigned int framerate_denom; + + bool aborting; + int num_ip_buffers; + int num_op_buffers; + struct completion frame_cmplt; +}; + +struct bcm2835_codec_driver { + struct vchiq_device *device; + struct media_device mdev; + + struct bcm2835_codec_dev *encode; + struct bcm2835_codec_dev *decode; + struct bcm2835_codec_dev *isp; + struct bcm2835_codec_dev *deinterlace; + struct bcm2835_codec_dev *encode_image; +}; + +enum { + V4L2_M2M_SRC = 0, + V4L2_M2M_DST = 1, +}; + +static const struct bcm2835_codec_fmt *get_fmt(u32 mmal_fmt) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(supported_formats); i++) { + if (supported_formats[i].mmal_fmt == mmal_fmt && + (!disable_bayer || !supported_formats[i].is_bayer)) + return &supported_formats[i]; + } + return NULL; +} + +static inline +struct bcm2835_codec_fmt_list *get_format_list(struct bcm2835_codec_dev *dev, + bool capture) +{ + return &dev->supported_fmts[capture ? 1 : 0]; +} + +static +struct bcm2835_codec_fmt *get_default_format(struct bcm2835_codec_dev *dev, + bool capture) +{ + return &dev->supported_fmts[capture ? 1 : 0].list[0]; +} + +static +struct bcm2835_codec_fmt *find_format_pix_fmt(u32 pix_fmt, + struct bcm2835_codec_dev *dev, + bool capture) +{ + struct bcm2835_codec_fmt *fmt; + unsigned int k; + struct bcm2835_codec_fmt_list *fmts = + &dev->supported_fmts[capture ? 1 : 0]; + + for (k = 0; k < fmts->num_entries; k++) { + fmt = &fmts->list[k]; + if (fmt->fourcc == pix_fmt) + break; + } + if (k == fmts->num_entries) + return NULL; + + return &fmts->list[k]; +} + +static inline +struct bcm2835_codec_fmt *find_format(struct v4l2_format *f, + struct bcm2835_codec_dev *dev, + bool capture) +{ + return find_format_pix_fmt(f->fmt.pix_mp.pixelformat, dev, capture); +} + +static inline struct bcm2835_codec_ctx *file2ctx(struct file *file) +{ + return container_of(file->private_data, struct bcm2835_codec_ctx, fh); +} + +static struct bcm2835_codec_q_data *get_q_data(struct bcm2835_codec_ctx *ctx, + enum v4l2_buf_type type) +{ + switch (type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + return &ctx->q_data[V4L2_M2M_SRC]; + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + return &ctx->q_data[V4L2_M2M_DST]; + default: + v4l2_err(&ctx->dev->v4l2_dev, "%s: Invalid queue type %u\n", + __func__, type); + break; + } + return NULL; +} + +static struct vchiq_mmal_port *get_port_data(struct bcm2835_codec_ctx *ctx, + enum v4l2_buf_type type) +{ + if (!ctx->component) + return NULL; + + switch (type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + return &ctx->component->input[0]; + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + return &ctx->component->output[0]; + default: + v4l2_err(&ctx->dev->v4l2_dev, "%s: Invalid queue type %u\n", + __func__, type); + break; + } + return NULL; +} + +/* + * mem2mem callbacks + */ + +/* + * job_ready() - check whether an instance is ready to be scheduled to run + */ +static int job_ready(void *priv) +{ + struct bcm2835_codec_ctx *ctx = priv; + + if (!v4l2_m2m_num_src_bufs_ready(ctx->fh.m2m_ctx) && + !v4l2_m2m_num_dst_bufs_ready(ctx->fh.m2m_ctx)) + return 0; + + return 1; +} + +static void job_abort(void *priv) +{ + struct bcm2835_codec_ctx *ctx = priv; + + v4l2_dbg(1, debug, &ctx->dev->v4l2_dev, "%s\n", __func__); + /* Will cancel the transaction in the next interrupt handler */ + ctx->aborting = 1; +} + +static inline unsigned int get_sizeimage(int bpl, int width, int height, + struct bcm2835_codec_fmt *fmt) +{ + if (fmt->flags & V4L2_FMT_FLAG_COMPRESSED) { + if (fmt->fourcc == V4L2_PIX_FMT_JPEG) + return DEF_COMP_BUF_SIZE_JPEG; + + if (width * height > 1280 * 720) + return DEF_COMP_BUF_SIZE_GREATER_720P; + else + return DEF_COMP_BUF_SIZE_720P_OR_LESS; + } + + if (fmt->fourcc != V4L2_PIX_FMT_NV12_COL128) + return (bpl * height * fmt->size_multiplier_x2) >> 1; + + /* + * V4L2_PIX_FMT_NV12_COL128 is 128 pixel wide columns. + * bytesperline is the column stride in lines, so multiply by + * the number of columns and 128. + */ + return (ALIGN(width, 128) * bpl); +} + +static inline unsigned int get_bytesperline(int width, int height, + struct bcm2835_codec_fmt *fmt, + enum bcm2835_codec_role role) +{ + if (fmt->fourcc != V4L2_PIX_FMT_NV12_COL128) + return ALIGN((width * fmt->depth) >> 3, + fmt->bytesperline_align[role]); + + /* + * V4L2_PIX_FMT_NV12_COL128 passes the column stride in lines via + * bytesperline. + * The minimum value for this is sufficient for the base luma and chroma + * with no padding. + */ + return (height * 3) >> 1; +} + +static void setup_mmal_port_format(struct bcm2835_codec_ctx *ctx, + struct bcm2835_codec_q_data *q_data, + struct vchiq_mmal_port *port) +{ + port->format.encoding = q_data->fmt->mmal_fmt; + port->format.flags = 0; + + if (!(q_data->fmt->flags & V4L2_FMT_FLAG_COMPRESSED)) { + if (q_data->fmt->mmal_fmt != MMAL_ENCODING_YUVUV128) { + /* Raw image format - set width/height */ + port->es.video.width = (q_data->bytesperline << 3) / + q_data->fmt->depth; + port->es.video.height = q_data->height; + port->es.video.crop.width = q_data->crop_width; + port->es.video.crop.height = q_data->crop_height; + } else { + /* NV12_COL128 / YUVUV128 column format */ + /* Column stride in lines */ + port->es.video.width = q_data->bytesperline; + port->es.video.height = q_data->height; + port->es.video.crop.width = q_data->crop_width; + port->es.video.crop.height = q_data->crop_height; + port->format.flags = MMAL_ES_FORMAT_FLAG_COL_FMTS_WIDTH_IS_COL_STRIDE; + } + port->es.video.frame_rate.numerator = ctx->framerate_num; + port->es.video.frame_rate.denominator = ctx->framerate_denom; + } else { + /* Compressed format - leave resolution as 0 for decode */ + if (ctx->dev->role == DECODE) { + port->es.video.width = 0; + port->es.video.height = 0; + port->es.video.crop.width = 0; + port->es.video.crop.height = 0; + } else { + port->es.video.width = q_data->crop_width; + port->es.video.height = q_data->height; + port->es.video.crop.width = q_data->crop_width; + port->es.video.crop.height = q_data->crop_height; + port->format.bitrate = ctx->bitrate; + port->es.video.frame_rate.numerator = ctx->framerate_num; + port->es.video.frame_rate.denominator = ctx->framerate_denom; + } + } + port->es.video.crop.x = 0; + port->es.video.crop.y = 0; + + port->current_buffer.size = q_data->sizeimage; +}; + +static void ip_buffer_cb(struct vchiq_mmal_instance *instance, + struct vchiq_mmal_port *port, int status, + struct mmal_buffer *mmal_buf) +{ + struct bcm2835_codec_ctx *ctx = port->cb_ctx/*, *curr_ctx*/; + struct m2m_mmal_buffer *buf = + container_of(mmal_buf, struct m2m_mmal_buffer, mmal); + + v4l2_dbg(2, debug, &ctx->dev->v4l2_dev, "%s: port %p buf %p length %lu, flags %x\n", + __func__, port, mmal_buf, mmal_buf->length, + mmal_buf->mmal_flags); + + if (buf == &ctx->q_data[V4L2_M2M_SRC].eos_buffer) { + /* Do we need to add lcoking to prevent multiple submission of + * the EOS, and therefore handle mutliple return here? + */ + v4l2_dbg(1, debug, &ctx->dev->v4l2_dev, "%s: eos buffer returned.\n", + __func__); + ctx->q_data[V4L2_M2M_SRC].eos_buffer_in_use = false; + return; + } + + if (status) { + /* error in transfer */ + if (buf) + /* there was a buffer with the error so return it */ + vb2_buffer_done(&buf->m2m.vb.vb2_buf, + VB2_BUF_STATE_ERROR); + return; + } + if (mmal_buf->cmd) { + v4l2_err(&ctx->dev->v4l2_dev, "%s: Not expecting cmd msgs on ip callback - %08x\n", + __func__, mmal_buf->cmd); + /* + * CHECKME: Should we return here. The buffer shouldn't have a + * message context or vb2 buf associated. + */ + } + + v4l2_dbg(3, debug, &ctx->dev->v4l2_dev, "%s: no error. Return buffer %p\n", + __func__, &buf->m2m.vb.vb2_buf); + vb2_buffer_done(&buf->m2m.vb.vb2_buf, + port->enabled ? VB2_BUF_STATE_DONE : + VB2_BUF_STATE_QUEUED); + + ctx->num_ip_buffers++; + v4l2_dbg(2, debug, &ctx->dev->v4l2_dev, "%s: done %d input buffers\n", + __func__, ctx->num_ip_buffers); + + if (!port->enabled && atomic_read(&port->buffers_with_vpu)) + complete(&ctx->frame_cmplt); +} + +static void queue_res_chg_event(struct bcm2835_codec_ctx *ctx) +{ + static const struct v4l2_event ev_src_ch = { + .type = V4L2_EVENT_SOURCE_CHANGE, + .u.src_change.changes = + V4L2_EVENT_SRC_CH_RESOLUTION, + }; + + v4l2_event_queue_fh(&ctx->fh, &ev_src_ch); +} + +static void send_eos_event(struct bcm2835_codec_ctx *ctx) +{ + static const struct v4l2_event ev = { + .type = V4L2_EVENT_EOS, + }; + + v4l2_dbg(1, debug, &ctx->dev->v4l2_dev, "Sending EOS event\n"); + + v4l2_event_queue_fh(&ctx->fh, &ev); +} + +static void color_mmal2v4l(struct bcm2835_codec_ctx *ctx, u32 encoding, + u32 color_space) +{ + int is_rgb; + + switch (encoding) { + case MMAL_ENCODING_I420: + case MMAL_ENCODING_YV12: + case MMAL_ENCODING_NV12: + case MMAL_ENCODING_NV21: + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_YVYU: + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_VYUY: + /* YUV based colourspaces */ + switch (color_space) { + case MMAL_COLOR_SPACE_ITUR_BT601: + ctx->colorspace = V4L2_COLORSPACE_SMPTE170M; + break; + + case MMAL_COLOR_SPACE_ITUR_BT709: + ctx->colorspace = V4L2_COLORSPACE_REC709; + break; + default: + break; + } + break; + default: + /* RGB based colourspaces */ + ctx->colorspace = V4L2_COLORSPACE_SRGB; + break; + } + ctx->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(ctx->colorspace); + ctx->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(ctx->colorspace); + is_rgb = ctx->colorspace == V4L2_COLORSPACE_SRGB; + ctx->quant = V4L2_MAP_QUANTIZATION_DEFAULT(is_rgb, ctx->colorspace, + ctx->ycbcr_enc); +} + +static void handle_fmt_changed(struct bcm2835_codec_ctx *ctx, + struct mmal_buffer *mmal_buf) +{ + struct bcm2835_codec_q_data *q_data; + struct mmal_msg_event_format_changed *format = + (struct mmal_msg_event_format_changed *)mmal_buf->buffer; + struct mmal_parameter_video_interlace_type interlace; + int interlace_size = sizeof(interlace); + struct vb2_queue *vq; + int ret; + + v4l2_dbg(1, debug, &ctx->dev->v4l2_dev, "%s: Format changed: buff size min %u, rec %u, buff num min %u, rec %u\n", + __func__, + format->buffer_size_min, + format->buffer_size_recommended, + format->buffer_num_min, + format->buffer_num_recommended + ); + if (format->format.type != MMAL_ES_TYPE_VIDEO) { + v4l2_dbg(1, debug, &ctx->dev->v4l2_dev, "%s: Format changed but not video %u\n", + __func__, format->format.type); + return; + } + v4l2_dbg(1, debug, &ctx->dev->v4l2_dev, "%s: Format changed to %ux%u, crop %ux%u, colourspace %08X\n", + __func__, format->es.video.width, format->es.video.height, + format->es.video.crop.width, format->es.video.crop.height, + format->es.video.color_space); + + q_data = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + v4l2_dbg(1, debug, &ctx->dev->v4l2_dev, "%s: Format was %ux%u, crop %ux%u\n", + __func__, q_data->bytesperline, q_data->height, + q_data->crop_width, q_data->crop_height); + + q_data->crop_width = format->es.video.crop.width; + q_data->crop_height = format->es.video.crop.height; + /* + * Stop S_FMT updating crop_height should it be unaligned. + * Client can still update the crop region via S_SELECTION should it + * really want to, but the decoder is likely to complain that the + * format then doesn't match. + */ + q_data->selection_set = true; + q_data->bytesperline = get_bytesperline(format->es.video.width, + format->es.video.height, + q_data->fmt, ctx->dev->role); + + q_data->height = format->es.video.height; + q_data->sizeimage = format->buffer_size_min; + if (format->es.video.color_space) + color_mmal2v4l(ctx, format->format.encoding, + format->es.video.color_space); + + q_data->aspect_ratio.numerator = format->es.video.par.numerator; + q_data->aspect_ratio.denominator = format->es.video.par.denominator; + + ret = vchiq_mmal_port_parameter_get(ctx->dev->instance, + &ctx->component->output[0], + MMAL_PARAMETER_VIDEO_INTERLACE_TYPE, + &interlace, + &interlace_size); + if (!ret) { + switch (interlace.mode) { + case MMAL_INTERLACE_PROGRESSIVE: + default: + q_data->field = V4L2_FIELD_NONE; + break; + case MMAL_INTERLACE_FIELDS_INTERLEAVED_UPPER_FIRST: + q_data->field = V4L2_FIELD_INTERLACED_TB; + break; + case MMAL_INTERLACE_FIELDS_INTERLEAVED_LOWER_FIRST: + q_data->field = V4L2_FIELD_INTERLACED_BT; + break; + } + v4l2_dbg(1, debug, &ctx->dev->v4l2_dev, "%s: interlace mode %u, v4l2 field %u\n", + __func__, interlace.mode, q_data->field); + } else { + q_data->field = V4L2_FIELD_NONE; + } + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + if (vq->streaming) + vq->last_buffer_dequeued = true; + + queue_res_chg_event(ctx); +} + +static void op_buffer_cb(struct vchiq_mmal_instance *instance, + struct vchiq_mmal_port *port, int status, + struct mmal_buffer *mmal_buf) +{ + struct bcm2835_codec_ctx *ctx = port->cb_ctx; + enum vb2_buffer_state buf_state = VB2_BUF_STATE_DONE; + struct m2m_mmal_buffer *buf; + struct vb2_v4l2_buffer *vb2; + + v4l2_dbg(2, debug, &ctx->dev->v4l2_dev, + "%s: status:%d, buf:%p, length:%lu, flags %04x, pts %lld\n", + __func__, status, mmal_buf, mmal_buf->length, + mmal_buf->mmal_flags, mmal_buf->pts); + + buf = container_of(mmal_buf, struct m2m_mmal_buffer, mmal); + vb2 = &buf->m2m.vb; + + if (status) { + /* error in transfer */ + if (vb2) { + /* there was a buffer with the error so return it */ + vb2_buffer_done(&vb2->vb2_buf, VB2_BUF_STATE_ERROR); + } + return; + } + + if (mmal_buf->cmd) { + switch (mmal_buf->cmd) { + case MMAL_EVENT_FORMAT_CHANGED: + { + handle_fmt_changed(ctx, mmal_buf); + break; + } + default: + v4l2_err(&ctx->dev->v4l2_dev, "%s: Unexpected event on output callback - %08x\n", + __func__, mmal_buf->cmd); + break; + } + return; + } + + v4l2_dbg(3, debug, &ctx->dev->v4l2_dev, "%s: length %lu, flags %x, idx %u\n", + __func__, mmal_buf->length, mmal_buf->mmal_flags, + vb2->vb2_buf.index); + + if (mmal_buf->length == 0) { + /* stream ended, or buffer being returned during disable. */ + v4l2_dbg(2, debug, &ctx->dev->v4l2_dev, "%s: Empty buffer - flags %04x", + __func__, mmal_buf->mmal_flags); + if (!(mmal_buf->mmal_flags & MMAL_BUFFER_HEADER_FLAG_EOS)) { + if (!port->enabled) { + vb2_buffer_done(&vb2->vb2_buf, VB2_BUF_STATE_QUEUED); + if (atomic_read(&port->buffers_with_vpu)) + complete(&ctx->frame_cmplt); + } else { + vchiq_mmal_submit_buffer(ctx->dev->instance, + &ctx->component->output[0], + mmal_buf); + } + return; + } + } + if (mmal_buf->mmal_flags & MMAL_BUFFER_HEADER_FLAG_EOS) { + /* EOS packet from the VPU */ + send_eos_event(ctx); + vb2->flags |= V4L2_BUF_FLAG_LAST; + } + + if (mmal_buf->mmal_flags & MMAL_BUFFER_HEADER_FLAG_CORRUPTED) + buf_state = VB2_BUF_STATE_ERROR; + + /* vb2 timestamps in nsecs, mmal in usecs */ + vb2->vb2_buf.timestamp = mmal_buf->pts * 1000; + + vb2_set_plane_payload(&vb2->vb2_buf, 0, mmal_buf->length); + switch (mmal_buf->mmal_flags & + (MMAL_BUFFER_HEADER_VIDEO_FLAG_INTERLACED | + MMAL_BUFFER_HEADER_VIDEO_FLAG_TOP_FIELD_FIRST)) { + case 0: + case MMAL_BUFFER_HEADER_VIDEO_FLAG_TOP_FIELD_FIRST: /* Bogus */ + vb2->field = V4L2_FIELD_NONE; + break; + case MMAL_BUFFER_HEADER_VIDEO_FLAG_INTERLACED: + vb2->field = V4L2_FIELD_INTERLACED_BT; + break; + case (MMAL_BUFFER_HEADER_VIDEO_FLAG_INTERLACED | + MMAL_BUFFER_HEADER_VIDEO_FLAG_TOP_FIELD_FIRST): + vb2->field = V4L2_FIELD_INTERLACED_TB; + break; + } + + if (mmal_buf->mmal_flags & MMAL_BUFFER_HEADER_FLAG_KEYFRAME) + vb2->flags |= V4L2_BUF_FLAG_KEYFRAME; + + vb2_buffer_done(&vb2->vb2_buf, buf_state); + ctx->num_op_buffers++; + + v4l2_dbg(2, debug, &ctx->dev->v4l2_dev, "%s: done %d output buffers\n", + __func__, ctx->num_op_buffers); + + if (!port->enabled && atomic_read(&port->buffers_with_vpu)) + complete(&ctx->frame_cmplt); +} + +/* vb2_to_mmal_buffer() - converts vb2 buffer header to MMAL + * + * Copies all the required fields from a VB2 buffer to the MMAL buffer header, + * ready for sending to the VPU. + */ +static void vb2_to_mmal_buffer(struct m2m_mmal_buffer *buf, + struct vb2_v4l2_buffer *vb2) +{ + u64 pts; + + buf->mmal.mmal_flags = 0; + if (vb2->flags & V4L2_BUF_FLAG_KEYFRAME) + buf->mmal.mmal_flags |= MMAL_BUFFER_HEADER_FLAG_KEYFRAME; + + /* + * Adding this means that the data must be framed correctly as one frame + * per buffer. The underlying decoder has no such requirement, but it + * will reduce latency as the bistream parser will be kicked immediately + * to parse the frame, rather than relying on its own heuristics for + * when to wake up. + */ + buf->mmal.mmal_flags |= MMAL_BUFFER_HEADER_FLAG_FRAME_END; + + buf->mmal.length = vb2->vb2_buf.planes[0].bytesused; + /* + * Minor ambiguity in the V4L2 spec as to whether passing in a 0 length + * buffer, or one with V4L2_BUF_FLAG_LAST set denotes end of stream. + * Handle either. + */ + if (!buf->mmal.length || vb2->flags & V4L2_BUF_FLAG_LAST) + buf->mmal.mmal_flags |= MMAL_BUFFER_HEADER_FLAG_EOS; + + /* vb2 timestamps in nsecs, mmal in usecs */ + pts = vb2->vb2_buf.timestamp; + do_div(pts, 1000); + buf->mmal.pts = pts; + buf->mmal.dts = MMAL_TIME_UNKNOWN; + + switch (field_override ? field_override : vb2->field) { + default: + case V4L2_FIELD_NONE: + break; + case V4L2_FIELD_INTERLACED_BT: + buf->mmal.mmal_flags |= MMAL_BUFFER_HEADER_VIDEO_FLAG_INTERLACED; + break; + case V4L2_FIELD_INTERLACED_TB: + buf->mmal.mmal_flags |= MMAL_BUFFER_HEADER_VIDEO_FLAG_INTERLACED | + MMAL_BUFFER_HEADER_VIDEO_FLAG_TOP_FIELD_FIRST; + break; + } +} + +/* device_run() - prepares and starts the device + * + * This simulates all the immediate preparations required before starting + * a device. This will be called by the framework when it decides to schedule + * a particular instance. + */ +static void device_run(void *priv) +{ + struct bcm2835_codec_ctx *ctx = priv; + struct bcm2835_codec_dev *dev = ctx->dev; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + struct m2m_mmal_buffer *src_m2m_buf = NULL, *dst_m2m_buf = NULL; + struct v4l2_m2m_buffer *m2m; + int ret; + + v4l2_dbg(3, debug, &ctx->dev->v4l2_dev, "%s: off we go\n", __func__); + + if (ctx->fh.m2m_ctx->out_q_ctx.q.streaming) { + src_buf = v4l2_m2m_buf_remove(&ctx->fh.m2m_ctx->out_q_ctx); + if (src_buf) { + m2m = container_of(src_buf, struct v4l2_m2m_buffer, vb); + src_m2m_buf = container_of(m2m, struct m2m_mmal_buffer, + m2m); + vb2_to_mmal_buffer(src_m2m_buf, src_buf); + + ret = vchiq_mmal_submit_buffer(dev->instance, + &ctx->component->input[0], + &src_m2m_buf->mmal); + v4l2_dbg(3, debug, &ctx->dev->v4l2_dev, + "%s: Submitted ip buffer len %lu, pts %llu, flags %04x\n", + __func__, src_m2m_buf->mmal.length, + src_m2m_buf->mmal.pts, + src_m2m_buf->mmal.mmal_flags); + if (ret) + v4l2_err(&ctx->dev->v4l2_dev, + "%s: Failed submitting ip buffer\n", + __func__); + } + } + + if (ctx->fh.m2m_ctx->cap_q_ctx.q.streaming) { + dst_buf = v4l2_m2m_buf_remove(&ctx->fh.m2m_ctx->cap_q_ctx); + if (dst_buf) { + m2m = container_of(dst_buf, struct v4l2_m2m_buffer, vb); + dst_m2m_buf = container_of(m2m, struct m2m_mmal_buffer, + m2m); + vb2_to_mmal_buffer(dst_m2m_buf, dst_buf); + + v4l2_dbg(3, debug, &ctx->dev->v4l2_dev, + "%s: Submitted op buffer\n", __func__); + ret = vchiq_mmal_submit_buffer(dev->instance, + &ctx->component->output[0], + &dst_m2m_buf->mmal); + if (ret) + v4l2_err(&ctx->dev->v4l2_dev, + "%s: Failed submitting op buffer\n", + __func__); + } + } + + v4l2_dbg(3, debug, &ctx->dev->v4l2_dev, "%s: Submitted src %p, dst %p\n", + __func__, src_m2m_buf, dst_m2m_buf); + + /* Complete the job here. */ + v4l2_m2m_job_finish(ctx->dev->m2m_dev, ctx->fh.m2m_ctx); +} + +/* + * video ioctls + */ +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct bcm2835_codec_dev *dev = video_drvdata(file); + + strncpy(cap->driver, MEM2MEM_NAME, sizeof(cap->driver) - 1); + strncpy(cap->card, dev->vfd.name, sizeof(cap->card) - 1); + snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s", + MEM2MEM_NAME); + return 0; +} + +static int enum_fmt(struct v4l2_fmtdesc *f, struct bcm2835_codec_ctx *ctx, + bool capture) +{ + struct bcm2835_codec_fmt *fmt; + struct bcm2835_codec_fmt_list *fmts = + get_format_list(ctx->dev, capture); + + if (f->index < fmts->num_entries) { + /* Format found */ + fmt = &fmts->list[f->index]; + f->pixelformat = fmt->fourcc; + f->flags = fmt->flags; + return 0; + } + + /* Format not found */ + return -EINVAL; +} + +static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct bcm2835_codec_ctx *ctx = file2ctx(file); + + return enum_fmt(f, ctx, true); +} + +static int vidioc_enum_fmt_vid_out(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct bcm2835_codec_ctx *ctx = file2ctx(file); + + return enum_fmt(f, ctx, false); +} + +static int vidioc_g_fmt(struct bcm2835_codec_ctx *ctx, struct v4l2_format *f) +{ + struct vb2_queue *vq; + struct bcm2835_codec_q_data *q_data; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + q_data = get_q_data(ctx, f->type); + + f->fmt.pix_mp.width = q_data->crop_width; + f->fmt.pix_mp.height = q_data->height; + f->fmt.pix_mp.pixelformat = q_data->fmt->fourcc; + f->fmt.pix_mp.field = q_data->field; + f->fmt.pix_mp.colorspace = ctx->colorspace; + f->fmt.pix_mp.plane_fmt[0].sizeimage = q_data->sizeimage; + f->fmt.pix_mp.plane_fmt[0].bytesperline = q_data->bytesperline; + f->fmt.pix_mp.num_planes = 1; + f->fmt.pix_mp.ycbcr_enc = ctx->ycbcr_enc; + f->fmt.pix_mp.quantization = ctx->quant; + f->fmt.pix_mp.xfer_func = ctx->xfer_func; + + memset(f->fmt.pix_mp.plane_fmt[0].reserved, 0, + sizeof(f->fmt.pix_mp.plane_fmt[0].reserved)); + + return 0; +} + +static int vidioc_g_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + return vidioc_g_fmt(file2ctx(file), f); +} + +static int vidioc_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + return vidioc_g_fmt(file2ctx(file), f); +} + +static int vidioc_try_fmt(struct bcm2835_codec_ctx *ctx, struct v4l2_format *f, + struct bcm2835_codec_fmt *fmt) +{ + unsigned int sizeimage, min_bytesperline; + + /* + * The V4L2 specification requires the driver to correct the format + * struct if any of the dimensions is unsupported + */ + if (f->fmt.pix_mp.width > ctx->dev->max_w) + f->fmt.pix_mp.width = ctx->dev->max_w; + if (f->fmt.pix_mp.height > ctx->dev->max_h) + f->fmt.pix_mp.height = ctx->dev->max_h; + + if (!(fmt->flags & V4L2_FMT_FLAG_COMPRESSED)) { + /* Only clip min w/h on capture. Treat 0x0 as unknown. */ + if (f->fmt.pix_mp.width < MIN_W) + f->fmt.pix_mp.width = MIN_W; + if (f->fmt.pix_mp.height < MIN_H) + f->fmt.pix_mp.height = MIN_H; + + /* + * For decoders and image encoders the buffer must have + * a vertical alignment of 16 lines. + * The selection will reflect any cropping rectangle when only + * some of the pixels are active. + */ + if (ctx->dev->role == DECODE || ctx->dev->role == ENCODE_IMAGE) + f->fmt.pix_mp.height = ALIGN(f->fmt.pix_mp.height, 16); + } + f->fmt.pix_mp.num_planes = 1; + min_bytesperline = get_bytesperline(f->fmt.pix_mp.width, + f->fmt.pix_mp.height, + fmt, ctx->dev->role); + if (f->fmt.pix_mp.plane_fmt[0].bytesperline < min_bytesperline) + f->fmt.pix_mp.plane_fmt[0].bytesperline = min_bytesperline; + f->fmt.pix_mp.plane_fmt[0].bytesperline = + ALIGN(f->fmt.pix_mp.plane_fmt[0].bytesperline, + fmt->bytesperline_align[ctx->dev->role]); + + sizeimage = get_sizeimage(f->fmt.pix_mp.plane_fmt[0].bytesperline, + f->fmt.pix_mp.width, f->fmt.pix_mp.height, + fmt); + /* + * Drivers must set sizeimage for uncompressed formats + * Compressed formats allow the client to request an alternate + * size for the buffer. + */ + if (!(fmt->flags & V4L2_FMT_FLAG_COMPRESSED) || + f->fmt.pix_mp.plane_fmt[0].sizeimage < sizeimage) + f->fmt.pix_mp.plane_fmt[0].sizeimage = sizeimage; + + memset(f->fmt.pix_mp.plane_fmt[0].reserved, 0, + sizeof(f->fmt.pix_mp.plane_fmt[0].reserved)); + + if (ctx->dev->role == DECODE || ctx->dev->role == DEINTERLACE) { + switch (f->fmt.pix_mp.field) { + /* + * All of this is pretty much guesswork as we'll set the + * interlace format correctly come format changed, and signal + * it appropriately on each buffer. + */ + default: + case V4L2_FIELD_NONE: + case V4L2_FIELD_ANY: + f->fmt.pix_mp.field = V4L2_FIELD_NONE; + break; + case V4L2_FIELD_INTERLACED: + f->fmt.pix_mp.field = V4L2_FIELD_INTERLACED; + break; + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + case V4L2_FIELD_INTERLACED_TB: + f->fmt.pix_mp.field = V4L2_FIELD_INTERLACED_TB; + break; + case V4L2_FIELD_INTERLACED_BT: + f->fmt.pix_mp.field = V4L2_FIELD_INTERLACED_BT; + break; + } + } else { + f->fmt.pix_mp.field = V4L2_FIELD_NONE; + } + + return 0; +} + +static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct bcm2835_codec_fmt *fmt; + struct bcm2835_codec_ctx *ctx = file2ctx(file); + + fmt = find_format(f, ctx->dev, true); + if (!fmt) { + f->fmt.pix_mp.pixelformat = get_default_format(ctx->dev, + true)->fourcc; + fmt = find_format(f, ctx->dev, true); + } + + return vidioc_try_fmt(ctx, f, fmt); +} + +static int vidioc_try_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct bcm2835_codec_fmt *fmt; + struct bcm2835_codec_ctx *ctx = file2ctx(file); + + fmt = find_format(f, ctx->dev, false); + if (!fmt) { + f->fmt.pix_mp.pixelformat = get_default_format(ctx->dev, + false)->fourcc; + fmt = find_format(f, ctx->dev, false); + } + + if (!f->fmt.pix_mp.colorspace) + f->fmt.pix_mp.colorspace = ctx->colorspace; + + return vidioc_try_fmt(ctx, f, fmt); +} + +static int vidioc_s_fmt(struct bcm2835_codec_ctx *ctx, struct v4l2_format *f, + unsigned int requested_height) +{ + struct bcm2835_codec_q_data *q_data; + struct vb2_queue *vq; + struct vchiq_mmal_port *port; + bool update_capture_port = false; + bool reenable_port = false; + int ret; + + v4l2_dbg(1, debug, &ctx->dev->v4l2_dev, "Setting format for type %d, wxh: %dx%d, fmt: %08x, size %u\n", + f->type, f->fmt.pix_mp.width, f->fmt.pix_mp.height, + f->fmt.pix_mp.pixelformat, + f->fmt.pix_mp.plane_fmt[0].sizeimage); + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + q_data = get_q_data(ctx, f->type); + if (!q_data) + return -EINVAL; + + if (vb2_is_busy(vq)) { + v4l2_err(&ctx->dev->v4l2_dev, "%s queue busy\n", __func__); + return -EBUSY; + } + + q_data->fmt = find_format(f, ctx->dev, + f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + q_data->crop_width = f->fmt.pix_mp.width; + q_data->height = f->fmt.pix_mp.height; + if (!q_data->selection_set || + (q_data->fmt->flags & V4L2_FMT_FLAG_COMPRESSED)) + q_data->crop_height = requested_height; + + /* + * Copying the behaviour of vicodec which retains a single set of + * colorspace parameters for both input and output. + */ + ctx->colorspace = f->fmt.pix_mp.colorspace; + ctx->xfer_func = f->fmt.pix_mp.xfer_func; + ctx->ycbcr_enc = f->fmt.pix_mp.ycbcr_enc; + ctx->quant = f->fmt.pix_mp.quantization; + + q_data->field = f->fmt.pix_mp.field; + + /* All parameters should have been set correctly by try_fmt */ + q_data->bytesperline = f->fmt.pix_mp.plane_fmt[0].bytesperline; + q_data->sizeimage = f->fmt.pix_mp.plane_fmt[0].sizeimage; + + v4l2_dbg(1, debug, &ctx->dev->v4l2_dev, "Calculated bpl as %u, size %u\n", + q_data->bytesperline, q_data->sizeimage); + + if ((ctx->dev->role == DECODE || ctx->dev->role == ENCODE_IMAGE) && + q_data->fmt->flags & V4L2_FMT_FLAG_COMPRESSED && + q_data->crop_width && q_data->height) { + /* + * On the decoder or image encoder, if provided with + * a resolution on the input side, then replicate that + * to the output side. + * GStreamer appears not to support V4L2_EVENT_SOURCE_CHANGE, + * nor set up a resolution on the output side, therefore + * we can't decode anything at a resolution other than the + * default one. + */ + struct bcm2835_codec_q_data *q_data_dst = + &ctx->q_data[V4L2_M2M_DST]; + + q_data_dst->crop_width = q_data->crop_width; + q_data_dst->crop_height = q_data->crop_height; + q_data_dst->height = ALIGN(q_data->crop_height, 16); + + q_data_dst->bytesperline = + get_bytesperline(f->fmt.pix_mp.width, + f->fmt.pix_mp.height, + q_data_dst->fmt, ctx->dev->role); + q_data_dst->sizeimage = get_sizeimage(q_data_dst->bytesperline, + q_data_dst->crop_width, + q_data_dst->height, + q_data_dst->fmt); + update_capture_port = true; + } + + /* If we have a component then setup the port as well */ + port = get_port_data(ctx, vq->type); + if (!port) + return 0; + + if (port->enabled) { + unsigned int num_buffers; + + /* + * This should only ever happen with DECODE and the MMAL output + * port that has been enabled for resolution changed events. + * In this case no buffers have been allocated or sent to the + * component, so warn on that. + */ + WARN_ON(ctx->dev->role != DECODE || + f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE || + atomic_read(&port->buffers_with_vpu)); + + /* + * Disable will reread the port format, so retain buffer count. + */ + num_buffers = port->current_buffer.num; + + ret = vchiq_mmal_port_disable(ctx->dev->instance, port); + if (ret) + v4l2_err(&ctx->dev->v4l2_dev, "%s: Error disabling port update buffer count, ret %d\n", + __func__, ret); + + port->current_buffer.num = num_buffers; + + reenable_port = true; + } + + setup_mmal_port_format(ctx, q_data, port); + ret = vchiq_mmal_port_set_format(ctx->dev->instance, port); + if (ret) { + v4l2_err(&ctx->dev->v4l2_dev, "%s: Failed vchiq_mmal_port_set_format on port, ret %d\n", + __func__, ret); + ret = -EINVAL; + } + + if (q_data->sizeimage < port->minimum_buffer.size) { + v4l2_err(&ctx->dev->v4l2_dev, "%s: Current buffer size of %u < min buf size %u - driver mismatch to MMAL\n", + __func__, q_data->sizeimage, + port->minimum_buffer.size); + } + + if (reenable_port) { + ret = vchiq_mmal_port_enable(ctx->dev->instance, + port, + op_buffer_cb); + if (ret) + v4l2_err(&ctx->dev->v4l2_dev, "%s: Failed enabling o/p port, ret %d\n", + __func__, ret); + } + v4l2_dbg(1, debug, &ctx->dev->v4l2_dev, "Set format for type %d, wxh: %dx%d, fmt: %08x, size %u\n", + f->type, q_data->crop_width, q_data->height, + q_data->fmt->fourcc, q_data->sizeimage); + + if (update_capture_port) { + struct vchiq_mmal_port *port_dst = &ctx->component->output[0]; + struct bcm2835_codec_q_data *q_data_dst = + &ctx->q_data[V4L2_M2M_DST]; + + setup_mmal_port_format(ctx, q_data_dst, port_dst); + ret = vchiq_mmal_port_set_format(ctx->dev->instance, port_dst); + if (ret) { + v4l2_err(&ctx->dev->v4l2_dev, "%s: Failed vchiq_mmal_port_set_format on output port, ret %d\n", + __func__, ret); + ret = -EINVAL; + } + } + return ret; +} + +static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + unsigned int height = f->fmt.pix_mp.height; + int ret; + + ret = vidioc_try_fmt_vid_cap(file, priv, f); + if (ret) + return ret; + + return vidioc_s_fmt(file2ctx(file), f, height); +} + +static int vidioc_s_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + unsigned int height = f->fmt.pix_mp.height; + int ret; + + ret = vidioc_try_fmt_vid_out(file, priv, f); + if (ret) + return ret; + + ret = vidioc_s_fmt(file2ctx(file), f, height); + return ret; +} + +static int vidioc_g_selection(struct file *file, void *priv, + struct v4l2_selection *s) +{ + struct bcm2835_codec_ctx *ctx = file2ctx(file); + struct bcm2835_codec_q_data *q_data; + + /* + * The selection API takes V4L2_BUF_TYPE_VIDEO_CAPTURE and + * V4L2_BUF_TYPE_VIDEO_OUTPUT, even if the device implements the MPLANE + * API. The V4L2 core will have converted the MPLANE variants to + * non-MPLANE. + * Open code this instead of using get_q_data in this case. + */ + switch (s->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + /* CAPTURE on encoder is not valid. */ + if (ctx->dev->role == ENCODE || ctx->dev->role == ENCODE_IMAGE) + return -EINVAL; + q_data = &ctx->q_data[V4L2_M2M_DST]; + break; + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + /* OUTPUT on deoder is not valid. */ + if (ctx->dev->role == DECODE) + return -EINVAL; + q_data = &ctx->q_data[V4L2_M2M_SRC]; + break; + default: + return -EINVAL; + } + + switch (ctx->dev->role) { + case DECODE: + switch (s->target) { + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + case V4L2_SEL_TGT_COMPOSE: + s->r.left = 0; + s->r.top = 0; + s->r.width = q_data->crop_width; + s->r.height = q_data->crop_height; + break; + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + s->r.left = 0; + s->r.top = 0; + s->r.width = q_data->crop_width; + s->r.height = q_data->crop_height; + break; + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_CROP_DEFAULT: + s->r.left = 0; + s->r.top = 0; + s->r.width = (q_data->bytesperline << 3) / + q_data->fmt->depth; + s->r.height = q_data->height; + break; + default: + return -EINVAL; + } + break; + case ENCODE: + case ENCODE_IMAGE: + switch (s->target) { + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + s->r.top = 0; + s->r.left = 0; + s->r.width = q_data->bytesperline; + s->r.height = q_data->height; + break; + case V4L2_SEL_TGT_CROP: + s->r.top = 0; + s->r.left = 0; + s->r.width = q_data->crop_width; + s->r.height = q_data->crop_height; + break; + default: + return -EINVAL; + } + break; + case ISP: + case DEINTERLACE: + if (s->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + switch (s->target) { + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + case V4L2_SEL_TGT_COMPOSE: + s->r.left = 0; + s->r.top = 0; + s->r.width = q_data->crop_width; + s->r.height = q_data->crop_height; + break; + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + s->r.left = 0; + s->r.top = 0; + s->r.width = q_data->crop_width; + s->r.height = q_data->crop_height; + break; + default: + return -EINVAL; + } + } else { + /* must be V4L2_BUF_TYPE_VIDEO_OUTPUT */ + switch (s->target) { + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + s->r.top = 0; + s->r.left = 0; + s->r.width = q_data->bytesperline; + s->r.height = q_data->height; + break; + case V4L2_SEL_TGT_CROP: + s->r.top = 0; + s->r.left = 0; + s->r.width = q_data->crop_width; + s->r.height = q_data->crop_height; + break; + default: + return -EINVAL; + } + } + break; + case NUM_ROLES: + break; + } + + return 0; +} + +static int vidioc_s_selection(struct file *file, void *priv, + struct v4l2_selection *s) +{ + struct bcm2835_codec_ctx *ctx = file2ctx(file); + struct bcm2835_codec_q_data *q_data = NULL; + struct vchiq_mmal_port *port = NULL; + int ret; + + /* + * The selection API takes V4L2_BUF_TYPE_VIDEO_CAPTURE and + * V4L2_BUF_TYPE_VIDEO_OUTPUT, even if the device implements the MPLANE + * API. The V4L2 core will have converted the MPLANE variants to + * non-MPLANE. + * + * Open code this instead of using get_q_data in this case. + */ + switch (s->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + /* CAPTURE on encoder is not valid. */ + if (ctx->dev->role == ENCODE || ctx->dev->role == ENCODE_IMAGE) + return -EINVAL; + q_data = &ctx->q_data[V4L2_M2M_DST]; + if (ctx->component) + port = &ctx->component->output[0]; + break; + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + /* OUTPUT on deoder is not valid. */ + if (ctx->dev->role == DECODE) + return -EINVAL; + q_data = &ctx->q_data[V4L2_M2M_SRC]; + if (ctx->component) + port = &ctx->component->input[0]; + break; + default: + return -EINVAL; + } + + v4l2_dbg(1, debug, &ctx->dev->v4l2_dev, "%s: ctx %p, type %d, q_data %p, target %d, rect x/y %d/%d, w/h %ux%u\n", + __func__, ctx, s->type, q_data, s->target, s->r.left, s->r.top, + s->r.width, s->r.height); + + switch (ctx->dev->role) { + case DECODE: + switch (s->target) { + case V4L2_SEL_TGT_COMPOSE: + /* Accept cropped image */ + s->r.left = 0; + s->r.top = 0; + s->r.width = min(s->r.width, q_data->crop_width); + s->r.height = min(s->r.height, q_data->height); + q_data->crop_width = s->r.width; + q_data->crop_height = s->r.height; + q_data->selection_set = true; + break; + default: + return -EINVAL; + } + break; + case ENCODE: + case ENCODE_IMAGE: + switch (s->target) { + case V4L2_SEL_TGT_CROP: + /* Only support crop from (0,0) */ + s->r.top = 0; + s->r.left = 0; + s->r.width = min(s->r.width, q_data->crop_width); + s->r.height = min(s->r.height, q_data->height); + q_data->crop_width = s->r.width; + q_data->crop_height = s->r.height; + q_data->selection_set = true; + break; + default: + return -EINVAL; + } + break; + case ISP: + case DEINTERLACE: + if (s->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + switch (s->target) { + case V4L2_SEL_TGT_COMPOSE: + /* Accept cropped image */ + s->r.left = 0; + s->r.top = 0; + s->r.width = min(s->r.width, q_data->crop_width); + s->r.height = min(s->r.height, q_data->height); + q_data->crop_width = s->r.width; + q_data->crop_height = s->r.height; + q_data->selection_set = true; + break; + default: + return -EINVAL; + } + break; + } else { + /* must be V4L2_BUF_TYPE_VIDEO_OUTPUT */ + switch (s->target) { + case V4L2_SEL_TGT_CROP: + /* Only support crop from (0,0) */ + s->r.top = 0; + s->r.left = 0; + s->r.width = min(s->r.width, q_data->crop_width); + s->r.height = min(s->r.height, q_data->height); + q_data->crop_width = s->r.width; + q_data->crop_height = s->r.height; + q_data->selection_set = true; + break; + default: + return -EINVAL; + } + break; + } + case NUM_ROLES: + break; + } + + if (!port) + return 0; + + setup_mmal_port_format(ctx, q_data, port); + ret = vchiq_mmal_port_set_format(ctx->dev->instance, port); + if (ret) { + v4l2_err(&ctx->dev->v4l2_dev, "%s: Failed vchiq_mmal_port_set_format on port, ret %d\n", + __func__, ret); + return -EINVAL; + } + + return 0; +} + +static int vidioc_s_parm(struct file *file, void *priv, + struct v4l2_streamparm *parm) +{ + struct bcm2835_codec_ctx *ctx = file2ctx(file); + struct bcm2835_codec_q_data *q_data = &ctx->q_data[V4L2_M2M_DST]; + struct vchiq_mmal_port *port; + int ret; + + if (parm->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + return -EINVAL; + + if (!parm->parm.output.timeperframe.denominator || + !parm->parm.output.timeperframe.numerator) + return -EINVAL; + + ctx->framerate_num = + parm->parm.output.timeperframe.denominator; + ctx->framerate_denom = + parm->parm.output.timeperframe.numerator; + + parm->parm.output.capability = V4L2_CAP_TIMEPERFRAME; + + /* + * If we have a component then setup the port as well. + * NB Framerate is passed to MMAL via the DST port, whilst V4L2 uses the + * OUTPUT queue. + */ + port = get_port_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + if (!port) + return 0; + + if (port->enabled) { + struct s32_fract frame_rate = { ctx->framerate_num, + ctx->framerate_denom }; + + ret = vchiq_mmal_port_parameter_set(ctx->dev->instance, + &ctx->component->output[0], + MMAL_PARAMETER_VIDEO_FRAME_RATE, + &frame_rate, + sizeof(frame_rate)); + + return ret; + } + + setup_mmal_port_format(ctx, q_data, port); + ret = vchiq_mmal_port_set_format(ctx->dev->instance, port); + if (ret) { + v4l2_err(&ctx->dev->v4l2_dev, "%s: Failed vchiq_mmal_port_set_format on port, ret %d\n", + __func__, ret); + ret = -EINVAL; + } + + if (q_data->sizeimage < port->minimum_buffer.size) { + v4l2_err(&ctx->dev->v4l2_dev, "%s: Current buffer size of %u < min buf size %u - driver mismatch to MMAL\n", + __func__, q_data->sizeimage, + port->minimum_buffer.size); + ret = -EINVAL; + } + + return ret; +} + +static int vidioc_g_parm(struct file *file, void *priv, + struct v4l2_streamparm *parm) +{ + struct bcm2835_codec_ctx *ctx = file2ctx(file); + + if (parm->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + return -EINVAL; + + parm->parm.output.capability = V4L2_CAP_TIMEPERFRAME; + parm->parm.output.timeperframe.denominator = + ctx->framerate_num; + parm->parm.output.timeperframe.numerator = + ctx->framerate_denom; + + return 0; +} + +static int vidioc_g_pixelaspect(struct file *file, void *fh, int type, + struct v4l2_fract *f) +{ + struct bcm2835_codec_ctx *ctx = file2ctx(file); + + /* + * The selection API takes V4L2_BUF_TYPE_VIDEO_CAPTURE and + * V4L2_BUF_TYPE_VIDEO_OUTPUT, even if the device implements the MPLANE + * API. The V4L2 core will have converted the MPLANE variants to + * non-MPLANE. + * Open code this instead of using get_q_data in this case. + */ + if (ctx->dev->role != DECODE) + return -ENOIOCTLCMD; + + if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + *f = ctx->q_data[V4L2_M2M_DST].aspect_ratio; + + return 0; +} + +static int vidioc_subscribe_evt(struct v4l2_fh *fh, + const struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_EOS: + return v4l2_event_subscribe(fh, sub, 2, NULL); + case V4L2_EVENT_SOURCE_CHANGE: + return v4l2_src_change_event_subscribe(fh, sub); + default: + return v4l2_ctrl_subscribe_event(fh, sub); + } +} + +static int bcm2835_codec_set_level_profile(struct bcm2835_codec_ctx *ctx, + struct v4l2_ctrl *ctrl) +{ + struct mmal_parameter_video_profile param; + int param_size = sizeof(param); + int ret; + + /* + * Level and Profile are set via the same MMAL parameter. + * Retrieve the current settings and amend the one that has changed. + */ + ret = vchiq_mmal_port_parameter_get(ctx->dev->instance, + &ctx->component->output[0], + MMAL_PARAMETER_PROFILE, + ¶m, + ¶m_size); + if (ret) + return ret; + + switch (ctrl->id) { + case V4L2_CID_MPEG_VIDEO_H264_PROFILE: + switch (ctrl->val) { + case V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE: + param.profile = MMAL_VIDEO_PROFILE_H264_BASELINE; + break; + case V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE: + param.profile = + MMAL_VIDEO_PROFILE_H264_CONSTRAINED_BASELINE; + break; + case V4L2_MPEG_VIDEO_H264_PROFILE_MAIN: + param.profile = MMAL_VIDEO_PROFILE_H264_MAIN; + break; + case V4L2_MPEG_VIDEO_H264_PROFILE_HIGH: + param.profile = MMAL_VIDEO_PROFILE_H264_HIGH; + break; + default: + /* Should never get here */ + break; + } + break; + + case V4L2_CID_MPEG_VIDEO_H264_LEVEL: + switch (ctrl->val) { + case V4L2_MPEG_VIDEO_H264_LEVEL_1_0: + param.level = MMAL_VIDEO_LEVEL_H264_1; + break; + case V4L2_MPEG_VIDEO_H264_LEVEL_1B: + param.level = MMAL_VIDEO_LEVEL_H264_1b; + break; + case V4L2_MPEG_VIDEO_H264_LEVEL_1_1: + param.level = MMAL_VIDEO_LEVEL_H264_11; + break; + case V4L2_MPEG_VIDEO_H264_LEVEL_1_2: + param.level = MMAL_VIDEO_LEVEL_H264_12; + break; + case V4L2_MPEG_VIDEO_H264_LEVEL_1_3: + param.level = MMAL_VIDEO_LEVEL_H264_13; + break; + case V4L2_MPEG_VIDEO_H264_LEVEL_2_0: + param.level = MMAL_VIDEO_LEVEL_H264_2; + break; + case V4L2_MPEG_VIDEO_H264_LEVEL_2_1: + param.level = MMAL_VIDEO_LEVEL_H264_21; + break; + case V4L2_MPEG_VIDEO_H264_LEVEL_2_2: + param.level = MMAL_VIDEO_LEVEL_H264_22; + break; + case V4L2_MPEG_VIDEO_H264_LEVEL_3_0: + param.level = MMAL_VIDEO_LEVEL_H264_3; + break; + case V4L2_MPEG_VIDEO_H264_LEVEL_3_1: + param.level = MMAL_VIDEO_LEVEL_H264_31; + break; + case V4L2_MPEG_VIDEO_H264_LEVEL_3_2: + param.level = MMAL_VIDEO_LEVEL_H264_32; + break; + case V4L2_MPEG_VIDEO_H264_LEVEL_4_0: + param.level = MMAL_VIDEO_LEVEL_H264_4; + break; + /* + * Note that the hardware spec is level 4.0. Levels above that + * are there for correctly encoding the headers and may not + * be able to keep up with real-time. + */ + case V4L2_MPEG_VIDEO_H264_LEVEL_4_1: + param.level = MMAL_VIDEO_LEVEL_H264_41; + break; + case V4L2_MPEG_VIDEO_H264_LEVEL_4_2: + param.level = MMAL_VIDEO_LEVEL_H264_42; + break; + case V4L2_MPEG_VIDEO_H264_LEVEL_5_0: + param.level = MMAL_VIDEO_LEVEL_H264_5; + break; + case V4L2_MPEG_VIDEO_H264_LEVEL_5_1: + param.level = MMAL_VIDEO_LEVEL_H264_51; + break; + default: + /* Should never get here */ + break; + } + } + ret = vchiq_mmal_port_parameter_set(ctx->dev->instance, + &ctx->component->output[0], + MMAL_PARAMETER_PROFILE, + ¶m, + param_size); + + return ret; +} + +static int bcm2835_codec_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct bcm2835_codec_ctx *ctx = + container_of(ctrl->handler, struct bcm2835_codec_ctx, hdl); + int ret = 0; + + if (ctrl->flags & V4L2_CTRL_FLAG_READ_ONLY) + return 0; + + switch (ctrl->id) { + case V4L2_CID_MPEG_VIDEO_BITRATE: + ctx->bitrate = ctrl->val; + if (!ctx->component) + break; + + ret = vchiq_mmal_port_parameter_set(ctx->dev->instance, + &ctx->component->output[0], + MMAL_PARAMETER_VIDEO_BIT_RATE, + &ctrl->val, + sizeof(ctrl->val)); + break; + + case V4L2_CID_MPEG_VIDEO_BITRATE_MODE: { + u32 bitrate_mode; + + if (!ctx->component) + break; + + switch (ctrl->val) { + default: + case V4L2_MPEG_VIDEO_BITRATE_MODE_VBR: + bitrate_mode = MMAL_VIDEO_RATECONTROL_VARIABLE; + break; + case V4L2_MPEG_VIDEO_BITRATE_MODE_CBR: + bitrate_mode = MMAL_VIDEO_RATECONTROL_CONSTANT; + break; + } + + ret = vchiq_mmal_port_parameter_set(ctx->dev->instance, + &ctx->component->output[0], + MMAL_PARAMETER_RATECONTROL, + &bitrate_mode, + sizeof(bitrate_mode)); + break; + } + case V4L2_CID_MPEG_VIDEO_REPEAT_SEQ_HEADER: + if (!ctx->component) + break; + + ret = vchiq_mmal_port_parameter_set(ctx->dev->instance, + &ctx->component->output[0], + MMAL_PARAMETER_VIDEO_ENCODE_INLINE_HEADER, + &ctrl->val, + sizeof(ctrl->val)); + break; + + case V4L2_CID_MPEG_VIDEO_HEADER_MODE: + if (!ctx->component) + break; + + ret = vchiq_mmal_port_parameter_set(ctx->dev->instance, + &ctx->component->output[0], + MMAL_PARAMETER_VIDEO_ENCODE_HEADERS_WITH_FRAME, + &ctrl->val, + sizeof(ctrl->val)); + break; + + case V4L2_CID_MPEG_VIDEO_H264_I_PERIOD: + /* + * Incorrect initial implementation meant that H264_I_PERIOD + * was implemented to control intra-I period. As the MMAL + * encoder never produces I-frames that aren't IDR frames, it + * should actually have been GOP_SIZE. + * Support both controls, but writing to H264_I_PERIOD will + * update GOP_SIZE. + */ + __v4l2_ctrl_s_ctrl(ctx->gop_size, ctrl->val); + fallthrough; + case V4L2_CID_MPEG_VIDEO_GOP_SIZE: + if (!ctx->component) + break; + + ret = vchiq_mmal_port_parameter_set(ctx->dev->instance, + &ctx->component->output[0], + MMAL_PARAMETER_INTRAPERIOD, + &ctrl->val, + sizeof(ctrl->val)); + break; + + case V4L2_CID_MPEG_VIDEO_H264_PROFILE: + case V4L2_CID_MPEG_VIDEO_H264_LEVEL: + if (!ctx->component) + break; + + ret = bcm2835_codec_set_level_profile(ctx, ctrl); + break; + + case V4L2_CID_MPEG_VIDEO_H264_MIN_QP: + if (!ctx->component) + break; + + ret = vchiq_mmal_port_parameter_set(ctx->dev->instance, + &ctx->component->output[0], + MMAL_PARAMETER_VIDEO_ENCODE_MIN_QUANT, + &ctrl->val, + sizeof(ctrl->val)); + break; + + case V4L2_CID_MPEG_VIDEO_H264_MAX_QP: + if (!ctx->component) + break; + + ret = vchiq_mmal_port_parameter_set(ctx->dev->instance, + &ctx->component->output[0], + MMAL_PARAMETER_VIDEO_ENCODE_MAX_QUANT, + &ctrl->val, + sizeof(ctrl->val)); + break; + + case V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME: { + u32 mmal_bool = 1; + + if (!ctx->component) + break; + + ret = vchiq_mmal_port_parameter_set(ctx->dev->instance, + &ctx->component->output[0], + MMAL_PARAMETER_VIDEO_REQUEST_I_FRAME, + &mmal_bool, + sizeof(mmal_bool)); + break; + } + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: { + u32 u32_value; + + if (ctrl->id == V4L2_CID_HFLIP) + ctx->hflip = ctrl->val; + else + ctx->vflip = ctrl->val; + + if (!ctx->component) + break; + + if (ctx->hflip && ctx->vflip) + u32_value = MMAL_PARAM_MIRROR_BOTH; + else if (ctx->hflip) + u32_value = MMAL_PARAM_MIRROR_HORIZONTAL; + else if (ctx->vflip) + u32_value = MMAL_PARAM_MIRROR_VERTICAL; + else + u32_value = MMAL_PARAM_MIRROR_NONE; + + ret = vchiq_mmal_port_parameter_set(ctx->dev->instance, + &ctx->component->input[0], + MMAL_PARAMETER_MIRROR, + &u32_value, + sizeof(u32_value)); + break; + } + case V4L2_CID_MPEG_VIDEO_B_FRAMES: + ret = 0; + break; + + case V4L2_CID_JPEG_COMPRESSION_QUALITY: + if (!ctx->component) + break; + + ret = vchiq_mmal_port_parameter_set(ctx->dev->instance, + &ctx->component->output[0], + MMAL_PARAMETER_JPEG_Q_FACTOR, + &ctrl->val, + sizeof(ctrl->val)); + break; + + default: + v4l2_err(&ctx->dev->v4l2_dev, "Invalid control %08x\n", ctrl->id); + return -EINVAL; + } + + if (ret) + v4l2_err(&ctx->dev->v4l2_dev, "Failed setting ctrl %08x, ret %d\n", + ctrl->id, ret); + return ret ? -EINVAL : 0; +} + +static const struct v4l2_ctrl_ops bcm2835_codec_ctrl_ops = { + .s_ctrl = bcm2835_codec_s_ctrl, +}; + +static int vidioc_try_decoder_cmd(struct file *file, void *priv, + struct v4l2_decoder_cmd *cmd) +{ + struct bcm2835_codec_ctx *ctx = file2ctx(file); + + if (ctx->dev->role != DECODE) + return -EINVAL; + + switch (cmd->cmd) { + case V4L2_DEC_CMD_STOP: + if (cmd->flags & V4L2_DEC_CMD_STOP_TO_BLACK) { + v4l2_err(&ctx->dev->v4l2_dev, "%s: DEC cmd->flags=%u stop to black not supported", + __func__, cmd->flags); + return -EINVAL; + } + break; + case V4L2_DEC_CMD_START: + break; + default: + return -EINVAL; + } + return 0; +} + +static int vidioc_decoder_cmd(struct file *file, void *priv, + struct v4l2_decoder_cmd *cmd) +{ + struct bcm2835_codec_ctx *ctx = file2ctx(file); + struct bcm2835_codec_q_data *q_data = &ctx->q_data[V4L2_M2M_SRC]; + struct vb2_queue *dst_vq; + int ret; + + v4l2_dbg(2, debug, &ctx->dev->v4l2_dev, "%s, cmd %u", __func__, + cmd->cmd); + ret = vidioc_try_decoder_cmd(file, priv, cmd); + if (ret) + return ret; + + switch (cmd->cmd) { + case V4L2_DEC_CMD_STOP: + if (q_data->eos_buffer_in_use) + v4l2_err(&ctx->dev->v4l2_dev, "EOS buffers already in use\n"); + q_data->eos_buffer_in_use = true; + + q_data->eos_buffer.mmal.buffer_size = 0; + q_data->eos_buffer.mmal.length = 0; + q_data->eos_buffer.mmal.mmal_flags = + MMAL_BUFFER_HEADER_FLAG_EOS; + q_data->eos_buffer.mmal.pts = 0; + q_data->eos_buffer.mmal.dts = 0; + + if (!ctx->component) + break; + + ret = vchiq_mmal_submit_buffer(ctx->dev->instance, + &ctx->component->input[0], + &q_data->eos_buffer.mmal); + if (ret) + v4l2_err(&ctx->dev->v4l2_dev, + "%s: EOS buffer submit failed %d\n", + __func__, ret); + + break; + + case V4L2_DEC_CMD_START: + dst_vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, + V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + vb2_clear_last_buffer_dequeued(dst_vq); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int vidioc_try_encoder_cmd(struct file *file, void *priv, + struct v4l2_encoder_cmd *cmd) +{ + switch (cmd->cmd) { + case V4L2_ENC_CMD_STOP: + break; + + case V4L2_ENC_CMD_START: + /* Do we need to do anything here? */ + break; + default: + return -EINVAL; + } + return 0; +} + +static int vidioc_encoder_cmd(struct file *file, void *priv, + struct v4l2_encoder_cmd *cmd) +{ + struct bcm2835_codec_ctx *ctx = file2ctx(file); + struct bcm2835_codec_q_data *q_data = &ctx->q_data[V4L2_M2M_SRC]; + int ret; + + v4l2_dbg(2, debug, &ctx->dev->v4l2_dev, "%s, cmd %u", __func__, + cmd->cmd); + ret = vidioc_try_encoder_cmd(file, priv, cmd); + if (ret) + return ret; + + switch (cmd->cmd) { + case V4L2_ENC_CMD_STOP: + if (q_data->eos_buffer_in_use) + v4l2_err(&ctx->dev->v4l2_dev, "EOS buffers already in use\n"); + q_data->eos_buffer_in_use = true; + + q_data->eos_buffer.mmal.buffer_size = 0; + q_data->eos_buffer.mmal.length = 0; + q_data->eos_buffer.mmal.mmal_flags = + MMAL_BUFFER_HEADER_FLAG_EOS; + q_data->eos_buffer.mmal.pts = 0; + q_data->eos_buffer.mmal.dts = 0; + + if (!ctx->component) + break; + + ret = vchiq_mmal_submit_buffer(ctx->dev->instance, + &ctx->component->input[0], + &q_data->eos_buffer.mmal); + if (ret) + v4l2_err(&ctx->dev->v4l2_dev, + "%s: EOS buffer submit failed %d\n", + __func__, ret); + + break; + case V4L2_ENC_CMD_START: + /* Do we need to do anything here? */ + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int vidioc_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + struct bcm2835_codec_ctx *ctx = file2ctx(file); + struct bcm2835_codec_fmt *fmt; + + fmt = find_format_pix_fmt(fsize->pixel_format, file2ctx(file)->dev, + true); + if (!fmt) + fmt = find_format_pix_fmt(fsize->pixel_format, + file2ctx(file)->dev, + false); + + if (!fmt) + return -EINVAL; + + if (fsize->index) + return -EINVAL; + + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; + + fsize->stepwise.min_width = MIN_W; + fsize->stepwise.max_width = ctx->dev->max_w; + fsize->stepwise.step_width = 2; + fsize->stepwise.min_height = MIN_H; + fsize->stepwise.max_height = ctx->dev->max_h; + fsize->stepwise.step_height = 2; + + return 0; +} + +static const struct v4l2_ioctl_ops bcm2835_codec_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap_mplane = vidioc_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap_mplane = vidioc_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap_mplane = vidioc_s_fmt_vid_cap, + + .vidioc_enum_fmt_vid_out = vidioc_enum_fmt_vid_out, + .vidioc_g_fmt_vid_out_mplane = vidioc_g_fmt_vid_out, + .vidioc_try_fmt_vid_out_mplane = vidioc_try_fmt_vid_out, + .vidioc_s_fmt_vid_out_mplane = vidioc_s_fmt_vid_out, + + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, + .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, + + .vidioc_g_selection = vidioc_g_selection, + .vidioc_s_selection = vidioc_s_selection, + + .vidioc_g_parm = vidioc_g_parm, + .vidioc_s_parm = vidioc_s_parm, + + .vidioc_g_pixelaspect = vidioc_g_pixelaspect, + + .vidioc_subscribe_event = vidioc_subscribe_evt, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + + .vidioc_decoder_cmd = vidioc_decoder_cmd, + .vidioc_try_decoder_cmd = vidioc_try_decoder_cmd, + .vidioc_encoder_cmd = vidioc_encoder_cmd, + .vidioc_try_encoder_cmd = vidioc_try_encoder_cmd, + .vidioc_enum_framesizes = vidioc_enum_framesizes, +}; + +static int bcm2835_codec_create_component(struct bcm2835_codec_ctx *ctx) +{ + struct bcm2835_codec_dev *dev = ctx->dev; + unsigned int enable = 1; + int ret; + + ret = vchiq_mmal_component_init(dev->instance, components[dev->role], + &ctx->component); + if (ret < 0) { + v4l2_err(&dev->v4l2_dev, "%s: failed to create component %s\n", + __func__, components[dev->role]); + return -ENOMEM; + } + + vchiq_mmal_port_parameter_set(dev->instance, &ctx->component->input[0], + MMAL_PARAMETER_ZERO_COPY, &enable, + sizeof(enable)); + vchiq_mmal_port_parameter_set(dev->instance, &ctx->component->output[0], + MMAL_PARAMETER_ZERO_COPY, &enable, + sizeof(enable)); + + if (dev->role == DECODE) { + /* + * Disable firmware option that ensures decoded timestamps + * always increase. + */ + enable = 0; + vchiq_mmal_port_parameter_set(dev->instance, + &ctx->component->output[0], + MMAL_PARAMETER_VIDEO_VALIDATE_TIMESTAMPS, + &enable, + sizeof(enable)); + /* + * Enable firmware option to stop on colourspace and pixel + * aspect ratio changed + */ + enable = 1; + vchiq_mmal_port_parameter_set(dev->instance, + &ctx->component->control, + MMAL_PARAMETER_VIDEO_STOP_ON_PAR_COLOUR_CHANGE, + &enable, + sizeof(enable)); + } else if (dev->role == DEINTERLACE) { + /* Select the default deinterlace algorithm. */ + int half_framerate = 0; + int default_frame_interval = -1; /* don't interpolate */ + int frame_type = 5; /* 0=progressive, 3=TFF, 4=BFF, 5=see frame */ + int use_qpus = 0; + enum mmal_parameter_imagefx effect = + advanced_deinterlace && ctx->q_data[V4L2_M2M_SRC].crop_width <= 800 ? + MMAL_PARAM_IMAGEFX_DEINTERLACE_ADV : + MMAL_PARAM_IMAGEFX_DEINTERLACE_FAST; + struct mmal_parameter_imagefx_parameters params = { + .effect = effect, + .num_effect_params = 4, + .effect_parameter = { frame_type, + default_frame_interval, + half_framerate, + use_qpus }, + }; + + vchiq_mmal_port_parameter_set(dev->instance, + &ctx->component->output[0], + MMAL_PARAMETER_IMAGE_EFFECT_PARAMETERS, + ¶ms, + sizeof(params)); + + } else if (dev->role == ENCODE) { + enable = 0; + vchiq_mmal_port_parameter_set(dev->instance, + &ctx->component->control, + MMAL_PARAMETER_VIDEO_ENCODE_HEADER_ON_OPEN, + &enable, + sizeof(enable)); + } else if (dev->role == ENCODE_IMAGE) { + enable = 0; + vchiq_mmal_port_parameter_set(dev->instance, + &ctx->component->control, + MMAL_PARAMETER_EXIF_DISABLE, + &enable, + sizeof(enable)); + enable = 1; + vchiq_mmal_port_parameter_set(dev->instance, + &ctx->component->output[0], + MMAL_PARAMETER_JPEG_IJG_SCALING, + &enable, + sizeof(enable)); + } + + setup_mmal_port_format(ctx, &ctx->q_data[V4L2_M2M_SRC], + &ctx->component->input[0]); + ctx->component->input[0].cb_ctx = ctx; + + setup_mmal_port_format(ctx, &ctx->q_data[V4L2_M2M_DST], + &ctx->component->output[0]); + ctx->component->output[0].cb_ctx = ctx; + + ret = vchiq_mmal_port_set_format(dev->instance, + &ctx->component->input[0]); + if (ret < 0) { + v4l2_dbg(1, debug, &dev->v4l2_dev, + "%s: vchiq_mmal_port_set_format ip port failed\n", + __func__); + goto destroy_component; + } + + ret = vchiq_mmal_port_set_format(dev->instance, + &ctx->component->output[0]); + if (ret < 0) { + v4l2_dbg(1, debug, &dev->v4l2_dev, + "%s: vchiq_mmal_port_set_format op port failed\n", + __func__); + goto destroy_component; + } + + if (dev->role == ENCODE || dev->role == ENCODE_IMAGE) { + u32 param = 1; + + if (ctx->q_data[V4L2_M2M_SRC].sizeimage < + ctx->component->output[0].minimum_buffer.size) + v4l2_err(&dev->v4l2_dev, "buffer size mismatch sizeimage %u < min size %u\n", + ctx->q_data[V4L2_M2M_SRC].sizeimage, + ctx->component->output[0].minimum_buffer.size); + + if (dev->role == ENCODE) { + /* Enable SPS Timing header so framerate information is encoded + * in the H264 header. + */ + vchiq_mmal_port_parameter_set(ctx->dev->instance, + &ctx->component->output[0], + MMAL_PARAMETER_VIDEO_ENCODE_SPS_TIMING, + ¶m, sizeof(param)); + + /* Enable inserting headers into the first frame */ + vchiq_mmal_port_parameter_set(ctx->dev->instance, + &ctx->component->control, + MMAL_PARAMETER_VIDEO_ENCODE_HEADERS_WITH_FRAME, + ¶m, sizeof(param)); + /* + * Avoid fragmenting the buffers over multiple frames (unless + * the frame is bigger than the whole buffer) + */ + vchiq_mmal_port_parameter_set(ctx->dev->instance, + &ctx->component->control, + MMAL_PARAMETER_MINIMISE_FRAGMENTATION, + ¶m, sizeof(param)); + } + } else { + if (ctx->q_data[V4L2_M2M_DST].sizeimage < + ctx->component->output[0].minimum_buffer.size) + v4l2_err(&dev->v4l2_dev, "buffer size mismatch sizeimage %u < min size %u\n", + ctx->q_data[V4L2_M2M_DST].sizeimage, + ctx->component->output[0].minimum_buffer.size); + } + + /* Now we have a component we can set all the ctrls */ + ret = v4l2_ctrl_handler_setup(&ctx->hdl); + + v4l2_dbg(2, debug, &dev->v4l2_dev, "%s: component created as %s\n", + __func__, components[dev->role]); + + return 0; + +destroy_component: + vchiq_mmal_component_finalise(ctx->dev->instance, ctx->component); + ctx->component = NULL; + + return ret; +} + +/* + * Queue operations + */ + +static int bcm2835_codec_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, + unsigned int *nplanes, + unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct bcm2835_codec_ctx *ctx = vb2_get_drv_priv(vq); + struct bcm2835_codec_q_data *q_data; + struct vchiq_mmal_port *port; + unsigned int size; + + q_data = get_q_data(ctx, vq->type); + if (!q_data) + return -EINVAL; + + if (!ctx->component) + if (bcm2835_codec_create_component(ctx)) + return -EINVAL; + + port = get_port_data(ctx, vq->type); + + size = q_data->sizeimage; + + if (*nplanes) + return sizes[0] < size ? -EINVAL : 0; + + *nplanes = 1; + + sizes[0] = size; + port->current_buffer.size = size; + + if (*nbuffers < port->minimum_buffer.num) + *nbuffers = port->minimum_buffer.num; + + /* + * The VPU uses this number to allocate a pool of headers at port_enable. + * We can't increase it later, so use of CREATE_BUFS is going to result + * in bad things happening. Adopt worst-case allocation, and add one + * buffer to take an EOS + */ + port->current_buffer.num = VB2_MAX_FRAME + 1; + + return 0; +} + +static int bcm2835_codec_mmal_buf_cleanup(struct mmal_buffer *mmal_buf) +{ + mmal_vchi_buffer_cleanup(mmal_buf); + + if (mmal_buf->dma_buf) { + dma_buf_put(mmal_buf->dma_buf); + mmal_buf->dma_buf = NULL; + } + + return 0; +} + +static int bcm2835_codec_buf_init(struct vb2_buffer *vb) +{ + struct bcm2835_codec_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vb2 = to_vb2_v4l2_buffer(vb); + struct v4l2_m2m_buffer *m2m = container_of(vb2, struct v4l2_m2m_buffer, + vb); + struct m2m_mmal_buffer *buf = container_of(m2m, struct m2m_mmal_buffer, + m2m); + + v4l2_dbg(2, debug, &ctx->dev->v4l2_dev, "%s: ctx:%p, vb %p\n", + __func__, ctx, vb); + buf->mmal.buffer = vb2_plane_vaddr(&buf->m2m.vb.vb2_buf, 0); + buf->mmal.buffer_size = vb2_plane_size(&buf->m2m.vb.vb2_buf, 0); + + mmal_vchi_buffer_init(ctx->dev->instance, &buf->mmal); + + return 0; +} + +static int bcm2835_codec_buf_prepare(struct vb2_buffer *vb) +{ + struct bcm2835_codec_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct bcm2835_codec_q_data *q_data; + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct v4l2_m2m_buffer *m2m = container_of(vbuf, struct v4l2_m2m_buffer, + vb); + struct m2m_mmal_buffer *buf = container_of(m2m, struct m2m_mmal_buffer, + m2m); + struct dma_buf *dma_buf; + int ret; + + v4l2_dbg(4, debug, &ctx->dev->v4l2_dev, "%s: type: %d ptr %p\n", + __func__, vb->vb2_queue->type, vb); + + q_data = get_q_data(ctx, vb->vb2_queue->type); + if (V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type)) { + if (vbuf->field == V4L2_FIELD_ANY) + vbuf->field = V4L2_FIELD_NONE; + } + + if (vb2_plane_size(vb, 0) < q_data->sizeimage) { + v4l2_dbg(1, debug, &ctx->dev->v4l2_dev, "%s data will not fit into plane (%lu < %lu)\n", + __func__, vb2_plane_size(vb, 0), + (long)q_data->sizeimage); + return -EINVAL; + } + + if (!V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type)) + vb2_set_plane_payload(vb, 0, q_data->sizeimage); + + switch (vb->memory) { + case VB2_MEMORY_DMABUF: + dma_buf = dma_buf_get(vb->planes[0].m.fd); + + if (dma_buf != buf->mmal.dma_buf) { + /* dmabuf either hasn't already been mapped, or it has + * changed. + */ + if (buf->mmal.dma_buf) { + v4l2_err(&ctx->dev->v4l2_dev, + "%s Buffer changed - why did the core not call cleanup?\n", + __func__); + bcm2835_codec_mmal_buf_cleanup(&buf->mmal); + } + + buf->mmal.dma_buf = dma_buf; + } else { + /* We already have a reference count on the dmabuf, so + * release the one we acquired above. + */ + dma_buf_put(dma_buf); + } + ret = 0; + break; + case VB2_MEMORY_MMAP: + /* + * We want to do this at init, but vb2_core_expbuf checks that + * the index < q->num_buffers, and q->num_buffers only gets + * updated once all the buffers are allocated. + */ + if (!buf->mmal.dma_buf) { + ret = vb2_core_expbuf_dmabuf(vb->vb2_queue, + vb->vb2_queue->type, + vb, 0, O_CLOEXEC, + &buf->mmal.dma_buf); + if (ret) + v4l2_err(&ctx->dev->v4l2_dev, + "%s: Failed to expbuf idx %d, ret %d\n", + __func__, vb->index, ret); + } else { + ret = 0; + } + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static void bcm2835_codec_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct bcm2835_codec_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + + v4l2_dbg(4, debug, &ctx->dev->v4l2_dev, "%s: type: %d ptr %p vbuf->flags %u, seq %u, bytesused %u\n", + __func__, vb->vb2_queue->type, vb, vbuf->flags, vbuf->sequence, + vb->planes[0].bytesused); + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); +} + +static void bcm2835_codec_buffer_cleanup(struct vb2_buffer *vb) +{ + struct bcm2835_codec_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vb2 = to_vb2_v4l2_buffer(vb); + struct v4l2_m2m_buffer *m2m = container_of(vb2, struct v4l2_m2m_buffer, + vb); + struct m2m_mmal_buffer *buf = container_of(m2m, struct m2m_mmal_buffer, + m2m); + + v4l2_dbg(2, debug, &ctx->dev->v4l2_dev, "%s: ctx:%p, vb %p\n", + __func__, ctx, vb); + + bcm2835_codec_mmal_buf_cleanup(&buf->mmal); +} + +static void bcm2835_codec_flush_buffers(struct bcm2835_codec_ctx *ctx, + struct vchiq_mmal_port *port) +{ + int ret; + + if (atomic_read(&port->buffers_with_vpu)) { + v4l2_dbg(1, debug, &ctx->dev->v4l2_dev, "%s: Waiting for buffers to be returned - %d outstanding\n", + __func__, atomic_read(&port->buffers_with_vpu)); + ret = wait_for_completion_timeout(&ctx->frame_cmplt, + COMPLETE_TIMEOUT); + if (ret <= 0) { + v4l2_err(&ctx->dev->v4l2_dev, "%s: Timeout waiting for buffers to be returned - %d outstanding\n", + __func__, + atomic_read(&port->buffers_with_vpu)); + } + } +} +static int bcm2835_codec_start_streaming(struct vb2_queue *q, + unsigned int count) +{ + struct bcm2835_codec_ctx *ctx = vb2_get_drv_priv(q); + struct bcm2835_codec_dev *dev = ctx->dev; + struct bcm2835_codec_q_data *q_data = get_q_data(ctx, q->type); + struct vchiq_mmal_port *port = get_port_data(ctx, q->type); + int ret = 0; + + v4l2_dbg(1, debug, &ctx->dev->v4l2_dev, "%s: type: %d count %d\n", + __func__, q->type, count); + q_data->sequence = 0; + + if (!ctx->component_enabled) { + ret = vchiq_mmal_component_enable(dev->instance, + ctx->component); + if (ret) + v4l2_err(&ctx->dev->v4l2_dev, "%s: Failed enabling component, ret %d\n", + __func__, ret); + ctx->component_enabled = true; + } + + if (port->enabled) { + unsigned int num_buffers; + + init_completion(&ctx->frame_cmplt); + + /* + * This should only ever happen with DECODE and the MMAL output + * port that has been enabled for resolution changed events. + * In this case no buffers have been allocated or sent to the + * component, so warn on that. + */ + WARN_ON(ctx->dev->role != DECODE); + WARN_ON(q->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + WARN_ON(atomic_read(&port->buffers_with_vpu)); + + /* + * Disable will reread the port format, so retain buffer count. + */ + num_buffers = port->current_buffer.num; + + ret = vchiq_mmal_port_disable(dev->instance, port); + if (ret) + v4l2_err(&ctx->dev->v4l2_dev, "%s: Error disabling port update buffer count, ret %d\n", + __func__, ret); + bcm2835_codec_flush_buffers(ctx, port); + port->current_buffer.num = num_buffers; + } + + if (count < port->minimum_buffer.num) + count = port->minimum_buffer.num; + + if (port->current_buffer.num < count + 1) { + v4l2_dbg(2, debug, &ctx->dev->v4l2_dev, "%s: ctx:%p, buffer count changed %u to %u\n", + __func__, ctx, port->current_buffer.num, count + 1); + + port->current_buffer.num = count + 1; + ret = vchiq_mmal_port_set_format(dev->instance, port); + if (ret) + v4l2_err(&ctx->dev->v4l2_dev, "%s: Error updating buffer count, ret %d\n", + __func__, ret); + } + + if (dev->role == DECODE && + q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE && + !ctx->component->output[0].enabled) { + /* + * Decode needs to enable the MMAL output/V4L2 CAPTURE + * port at this point too so that we have everything + * set up for dynamic resolution changes. + */ + ret = vchiq_mmal_port_enable(dev->instance, + &ctx->component->output[0], + op_buffer_cb); + if (ret) + v4l2_err(&ctx->dev->v4l2_dev, "%s: Failed enabling o/p port, ret %d\n", + __func__, ret); + } + + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + /* + * Create the EOS buffer. + * We only need the MMAL part, and want to NOT attach a memory + * buffer to it as it should only take flags. + */ + memset(&q_data->eos_buffer, 0, sizeof(q_data->eos_buffer)); + mmal_vchi_buffer_init(dev->instance, + &q_data->eos_buffer.mmal); + q_data->eos_buffer_in_use = false; + + ret = vchiq_mmal_port_enable(dev->instance, + port, + ip_buffer_cb); + if (ret) + v4l2_err(&ctx->dev->v4l2_dev, "%s: Failed enabling i/p port, ret %d\n", + __func__, ret); + } else { + if (!port->enabled) { + ret = vchiq_mmal_port_enable(dev->instance, + port, + op_buffer_cb); + if (ret) + v4l2_err(&ctx->dev->v4l2_dev, "%s: Failed enabling o/p port, ret %d\n", + __func__, ret); + } + } + v4l2_dbg(1, debug, &ctx->dev->v4l2_dev, "%s: Done, ret %d\n", + __func__, ret); + return ret; +} + +static void bcm2835_codec_stop_streaming(struct vb2_queue *q) +{ + struct bcm2835_codec_ctx *ctx = vb2_get_drv_priv(q); + struct bcm2835_codec_dev *dev = ctx->dev; + struct bcm2835_codec_q_data *q_data = get_q_data(ctx, q->type); + struct vchiq_mmal_port *port = get_port_data(ctx, q->type); + struct vb2_v4l2_buffer *vbuf; + int ret; + + v4l2_dbg(1, debug, &ctx->dev->v4l2_dev, "%s: type: %d - return buffers\n", + __func__, q->type); + + init_completion(&ctx->frame_cmplt); + + /* Clear out all buffers held by m2m framework */ + for (;;) { + if (V4L2_TYPE_IS_OUTPUT(q->type)) + vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + else + vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + if (!vbuf) + break; + v4l2_dbg(1, debug, &ctx->dev->v4l2_dev, "%s: return buffer %p\n", + __func__, vbuf); + + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_QUEUED); + } + + /* Disable MMAL port - this will flush buffers back */ + ret = vchiq_mmal_port_disable(dev->instance, port); + if (ret) + v4l2_err(&ctx->dev->v4l2_dev, "%s: Failed disabling %s port, ret %d\n", + __func__, V4L2_TYPE_IS_OUTPUT(q->type) ? "i/p" : "o/p", + ret); + + bcm2835_codec_flush_buffers(ctx, port); + + if (dev->role == DECODE && + q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE && + ctx->component->input[0].enabled) { + /* + * For decode we need to keep the MMAL output port enabled for + * resolution changed events whenever the input is enabled. + */ + ret = vchiq_mmal_port_enable(dev->instance, + &ctx->component->output[0], + op_buffer_cb); + if (ret) + v4l2_err(&ctx->dev->v4l2_dev, "%s: Failed enabling o/p port, ret %d\n", + __func__, ret); + } + + /* If both ports disabled, then disable the component */ + if (ctx->component_enabled && + !ctx->component->input[0].enabled && + !ctx->component->output[0].enabled) { + ret = vchiq_mmal_component_disable(dev->instance, + ctx->component); + if (ret) + v4l2_err(&ctx->dev->v4l2_dev, "%s: Failed enabling component, ret %d\n", + __func__, ret); + ctx->component_enabled = false; + } + + if (V4L2_TYPE_IS_OUTPUT(q->type)) + mmal_vchi_buffer_cleanup(&q_data->eos_buffer.mmal); + + v4l2_dbg(1, debug, &ctx->dev->v4l2_dev, "%s: done\n", __func__); +} + +static const struct vb2_ops bcm2835_codec_qops = { + .queue_setup = bcm2835_codec_queue_setup, + .buf_init = bcm2835_codec_buf_init, + .buf_prepare = bcm2835_codec_buf_prepare, + .buf_queue = bcm2835_codec_buf_queue, + .buf_cleanup = bcm2835_codec_buffer_cleanup, + .start_streaming = bcm2835_codec_start_streaming, + .stop_streaming = bcm2835_codec_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct bcm2835_codec_ctx *ctx = priv; + int ret; + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + src_vq->io_modes = VB2_MMAP | VB2_DMABUF; + src_vq->drv_priv = ctx; + src_vq->buf_struct_size = sizeof(struct m2m_mmal_buffer); + src_vq->ops = &bcm2835_codec_qops; + src_vq->mem_ops = &vb2_dma_contig_memops; + src_vq->dev = &ctx->dev->device->dev; + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->lock = &ctx->dev->dev_mutex; + + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + dst_vq->io_modes = VB2_MMAP | VB2_DMABUF; + dst_vq->drv_priv = ctx; + dst_vq->buf_struct_size = sizeof(struct m2m_mmal_buffer); + dst_vq->ops = &bcm2835_codec_qops; + dst_vq->mem_ops = &vb2_dma_contig_memops; + dst_vq->dev = &ctx->dev->device->dev; + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->lock = &ctx->dev->dev_mutex; + + return vb2_queue_init(dst_vq); +} + +static void dec_add_profile_ctrls(struct bcm2835_codec_dev *const dev, + struct v4l2_ctrl_handler *const hdl) +{ + struct v4l2_ctrl *ctrl; + unsigned int i; + const struct bcm2835_codec_fmt_list *const list = &dev->supported_fmts[0]; + + for (i = 0; i < list->num_entries; ++i) { + switch (list->list[i].fourcc) { + case V4L2_PIX_FMT_H264: + ctrl = v4l2_ctrl_new_std_menu(hdl, &bcm2835_codec_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_LEVEL, + V4L2_MPEG_VIDEO_H264_LEVEL_5_1, + ~(BIT(V4L2_MPEG_VIDEO_H264_LEVEL_1_0) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_1B) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_1_1) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_1_2) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_1_3) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_2_0) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_2_1) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_2_2) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_3_0) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_3_1) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_3_2) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_4_0) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_4_1) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_4_2) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_5_0) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_5_1)), + V4L2_MPEG_VIDEO_H264_LEVEL_4_0); + ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + ctrl = v4l2_ctrl_new_std_menu(hdl, &bcm2835_codec_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_PROFILE, + V4L2_MPEG_VIDEO_H264_PROFILE_HIGH, + ~(BIT(V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE) | + BIT(V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE) | + BIT(V4L2_MPEG_VIDEO_H264_PROFILE_MAIN) | + BIT(V4L2_MPEG_VIDEO_H264_PROFILE_HIGH)), + V4L2_MPEG_VIDEO_H264_PROFILE_HIGH); + ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + break; + case V4L2_PIX_FMT_MPEG2: + ctrl = v4l2_ctrl_new_std_menu(hdl, &bcm2835_codec_ctrl_ops, + V4L2_CID_MPEG_VIDEO_MPEG2_LEVEL, + V4L2_MPEG_VIDEO_MPEG2_LEVEL_HIGH, + ~(BIT(V4L2_MPEG_VIDEO_MPEG2_LEVEL_LOW) | + BIT(V4L2_MPEG_VIDEO_MPEG2_LEVEL_MAIN) | + BIT(V4L2_MPEG_VIDEO_MPEG2_LEVEL_HIGH_1440) | + BIT(V4L2_MPEG_VIDEO_MPEG2_LEVEL_HIGH)), + V4L2_MPEG_VIDEO_MPEG2_LEVEL_MAIN); + ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + ctrl = v4l2_ctrl_new_std_menu(hdl, &bcm2835_codec_ctrl_ops, + V4L2_CID_MPEG_VIDEO_MPEG2_PROFILE, + V4L2_MPEG_VIDEO_MPEG2_PROFILE_MAIN, + ~(BIT(V4L2_MPEG_VIDEO_MPEG2_PROFILE_SIMPLE) | + BIT(V4L2_MPEG_VIDEO_MPEG2_PROFILE_MAIN)), + V4L2_MPEG_VIDEO_MPEG2_PROFILE_MAIN); + ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + break; + case V4L2_PIX_FMT_MPEG4: + ctrl = v4l2_ctrl_new_std_menu(hdl, &bcm2835_codec_ctrl_ops, + V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL, + V4L2_MPEG_VIDEO_MPEG4_LEVEL_5, + ~(BIT(V4L2_MPEG_VIDEO_MPEG4_LEVEL_0) | + BIT(V4L2_MPEG_VIDEO_MPEG4_LEVEL_0B) | + BIT(V4L2_MPEG_VIDEO_MPEG4_LEVEL_1) | + BIT(V4L2_MPEG_VIDEO_MPEG4_LEVEL_2) | + BIT(V4L2_MPEG_VIDEO_MPEG4_LEVEL_3) | + BIT(V4L2_MPEG_VIDEO_MPEG4_LEVEL_3B) | + BIT(V4L2_MPEG_VIDEO_MPEG4_LEVEL_4) | + BIT(V4L2_MPEG_VIDEO_MPEG4_LEVEL_5)), + V4L2_MPEG_VIDEO_MPEG4_LEVEL_4); + ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + ctrl = v4l2_ctrl_new_std_menu(hdl, &bcm2835_codec_ctrl_ops, + V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE, + V4L2_MPEG_VIDEO_MPEG4_PROFILE_ADVANCED_SIMPLE, + ~(BIT(V4L2_MPEG_VIDEO_MPEG4_PROFILE_SIMPLE) | + BIT(V4L2_MPEG_VIDEO_MPEG4_PROFILE_ADVANCED_SIMPLE)), + V4L2_MPEG_VIDEO_MPEG4_PROFILE_ADVANCED_SIMPLE); + ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + break; + /* No profiles defined by V4L2 */ + case V4L2_PIX_FMT_H263: + case V4L2_PIX_FMT_JPEG: + case V4L2_PIX_FMT_MJPEG: + case V4L2_PIX_FMT_VC1_ANNEX_G: + default: + break; + } + } +} + +/* + * File operations + */ +static int bcm2835_codec_open(struct file *file) +{ + struct bcm2835_codec_dev *dev = video_drvdata(file); + struct bcm2835_codec_ctx *ctx = NULL; + struct v4l2_ctrl_handler *hdl; + int rc = 0; + + if (mutex_lock_interruptible(&dev->dev_mutex)) { + v4l2_err(&dev->v4l2_dev, "Mutex fail\n"); + return -ERESTARTSYS; + } + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) { + rc = -ENOMEM; + goto open_unlock; + } + + ctx->q_data[V4L2_M2M_SRC].fmt = get_default_format(dev, false); + ctx->q_data[V4L2_M2M_DST].fmt = get_default_format(dev, true); + + ctx->q_data[V4L2_M2M_SRC].crop_width = DEFAULT_WIDTH; + ctx->q_data[V4L2_M2M_SRC].crop_height = DEFAULT_HEIGHT; + ctx->q_data[V4L2_M2M_SRC].height = DEFAULT_HEIGHT; + ctx->q_data[V4L2_M2M_SRC].bytesperline = + get_bytesperline(DEFAULT_WIDTH, DEFAULT_HEIGHT, + ctx->q_data[V4L2_M2M_SRC].fmt, + dev->role); + ctx->q_data[V4L2_M2M_SRC].sizeimage = + get_sizeimage(ctx->q_data[V4L2_M2M_SRC].bytesperline, + ctx->q_data[V4L2_M2M_SRC].crop_width, + ctx->q_data[V4L2_M2M_SRC].height, + ctx->q_data[V4L2_M2M_SRC].fmt); + ctx->q_data[V4L2_M2M_SRC].field = V4L2_FIELD_NONE; + + ctx->q_data[V4L2_M2M_DST].crop_width = DEFAULT_WIDTH; + ctx->q_data[V4L2_M2M_DST].crop_height = DEFAULT_HEIGHT; + ctx->q_data[V4L2_M2M_DST].height = DEFAULT_HEIGHT; + ctx->q_data[V4L2_M2M_DST].bytesperline = + get_bytesperline(DEFAULT_WIDTH, DEFAULT_HEIGHT, + ctx->q_data[V4L2_M2M_DST].fmt, + dev->role); + ctx->q_data[V4L2_M2M_DST].sizeimage = + get_sizeimage(ctx->q_data[V4L2_M2M_DST].bytesperline, + ctx->q_data[V4L2_M2M_DST].crop_width, + ctx->q_data[V4L2_M2M_DST].height, + ctx->q_data[V4L2_M2M_DST].fmt); + ctx->q_data[V4L2_M2M_DST].aspect_ratio.numerator = 1; + ctx->q_data[V4L2_M2M_DST].aspect_ratio.denominator = 1; + ctx->q_data[V4L2_M2M_DST].field = V4L2_FIELD_NONE; + + ctx->colorspace = V4L2_COLORSPACE_REC709; + ctx->bitrate = 10 * 1000 * 1000; + + ctx->framerate_num = 30; + ctx->framerate_denom = 1; + + /* Initialise V4L2 contexts */ + v4l2_fh_init(&ctx->fh, video_devdata(file)); + file->private_data = &ctx->fh; + ctx->dev = dev; + hdl = &ctx->hdl; + switch (dev->role) { + case ENCODE: + { + /* Encode controls */ + v4l2_ctrl_handler_init(hdl, 13); + + v4l2_ctrl_new_std_menu(hdl, &bcm2835_codec_ctrl_ops, + V4L2_CID_MPEG_VIDEO_BITRATE_MODE, + V4L2_MPEG_VIDEO_BITRATE_MODE_CBR, 0, + V4L2_MPEG_VIDEO_BITRATE_MODE_VBR); + v4l2_ctrl_new_std(hdl, &bcm2835_codec_ctrl_ops, + V4L2_CID_MPEG_VIDEO_BITRATE, + 25 * 1000, 25 * 1000 * 1000, + 25 * 1000, 10 * 1000 * 1000); + v4l2_ctrl_new_std_menu(hdl, &bcm2835_codec_ctrl_ops, + V4L2_CID_MPEG_VIDEO_HEADER_MODE, + V4L2_MPEG_VIDEO_HEADER_MODE_JOINED_WITH_1ST_FRAME, + 0, V4L2_MPEG_VIDEO_HEADER_MODE_JOINED_WITH_1ST_FRAME); + v4l2_ctrl_new_std(hdl, &bcm2835_codec_ctrl_ops, + V4L2_CID_MPEG_VIDEO_REPEAT_SEQ_HEADER, + 0, 1, + 1, 0); + v4l2_ctrl_new_std(hdl, &bcm2835_codec_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_I_PERIOD, + 0, 0x7FFFFFFF, + 1, 60); + v4l2_ctrl_new_std_menu(hdl, &bcm2835_codec_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_LEVEL, + V4L2_MPEG_VIDEO_H264_LEVEL_5_1, + ~(BIT(V4L2_MPEG_VIDEO_H264_LEVEL_1_0) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_1B) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_1_1) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_1_2) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_1_3) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_2_0) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_2_1) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_2_2) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_3_0) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_3_1) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_3_2) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_4_0) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_4_1) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_4_2) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_5_0) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_5_1)), + V4L2_MPEG_VIDEO_H264_LEVEL_4_0); + v4l2_ctrl_new_std_menu(hdl, &bcm2835_codec_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_PROFILE, + V4L2_MPEG_VIDEO_H264_PROFILE_HIGH, + ~(BIT(V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE) | + BIT(V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE) | + BIT(V4L2_MPEG_VIDEO_H264_PROFILE_MAIN) | + BIT(V4L2_MPEG_VIDEO_H264_PROFILE_HIGH)), + V4L2_MPEG_VIDEO_H264_PROFILE_HIGH); + v4l2_ctrl_new_std(hdl, &bcm2835_codec_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_MIN_QP, + 0, 51, + 1, 20); + v4l2_ctrl_new_std(hdl, &bcm2835_codec_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_MAX_QP, + 0, 51, + 1, 51); + v4l2_ctrl_new_std(hdl, &bcm2835_codec_ctrl_ops, + V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME, + 0, 0, 0, 0); + v4l2_ctrl_new_std(hdl, &bcm2835_codec_ctrl_ops, + V4L2_CID_MPEG_VIDEO_B_FRAMES, + 0, 0, + 1, 0); + ctx->gop_size = v4l2_ctrl_new_std(hdl, &bcm2835_codec_ctrl_ops, + V4L2_CID_MPEG_VIDEO_GOP_SIZE, + 0, 0x7FFFFFFF, 1, 60); + if (hdl->error) { + rc = hdl->error; + goto free_ctrl_handler; + } + ctx->fh.ctrl_handler = hdl; + v4l2_ctrl_handler_setup(hdl); + } + break; + case DECODE: + { + v4l2_ctrl_handler_init(hdl, 1 + dev->supported_fmts[0].num_entries * 2); + + v4l2_ctrl_new_std(hdl, &bcm2835_codec_ctrl_ops, + V4L2_CID_MIN_BUFFERS_FOR_CAPTURE, + 1, 1, 1, 1); + dec_add_profile_ctrls(dev, hdl); + if (hdl->error) { + rc = hdl->error; + goto free_ctrl_handler; + } + ctx->fh.ctrl_handler = hdl; + v4l2_ctrl_handler_setup(hdl); + } + break; + case ISP: + { + v4l2_ctrl_handler_init(hdl, 2); + + v4l2_ctrl_new_std(hdl, &bcm2835_codec_ctrl_ops, + V4L2_CID_HFLIP, + 1, 0, 1, 0); + v4l2_ctrl_new_std(hdl, &bcm2835_codec_ctrl_ops, + V4L2_CID_VFLIP, + 1, 0, 1, 0); + if (hdl->error) { + rc = hdl->error; + goto free_ctrl_handler; + } + ctx->fh.ctrl_handler = hdl; + v4l2_ctrl_handler_setup(hdl); + } + break; + case DEINTERLACE: + { + v4l2_ctrl_handler_init(hdl, 0); + } + break; + case ENCODE_IMAGE: + { + /* Encode image controls */ + v4l2_ctrl_handler_init(hdl, 1); + + v4l2_ctrl_new_std(hdl, &bcm2835_codec_ctrl_ops, + V4L2_CID_JPEG_COMPRESSION_QUALITY, + 1, 100, + 1, 80); + if (hdl->error) { + rc = hdl->error; + goto free_ctrl_handler; + } + ctx->fh.ctrl_handler = hdl; + v4l2_ctrl_handler_setup(hdl); + } + break; + case NUM_ROLES: + break; + } + + ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(dev->m2m_dev, ctx, &queue_init); + + if (IS_ERR(ctx->fh.m2m_ctx)) { + rc = PTR_ERR(ctx->fh.m2m_ctx); + + goto free_ctrl_handler; + } + + /* Set both queues as buffered as we have buffering in the VPU. That + * means that we will be scheduled whenever either an input or output + * buffer is available (otherwise one of each are required). + */ + v4l2_m2m_set_src_buffered(ctx->fh.m2m_ctx, true); + v4l2_m2m_set_dst_buffered(ctx->fh.m2m_ctx, true); + + ctx->fh.m2m_ctx->ignore_cap_streaming = true; + + v4l2_fh_add(&ctx->fh); + atomic_inc(&dev->num_inst); + + mutex_unlock(&dev->dev_mutex); + return 0; + +free_ctrl_handler: + v4l2_ctrl_handler_free(hdl); + kfree(ctx); +open_unlock: + mutex_unlock(&dev->dev_mutex); + return rc; +} + +static int bcm2835_codec_release(struct file *file) +{ + struct bcm2835_codec_dev *dev = video_drvdata(file); + struct bcm2835_codec_ctx *ctx = file2ctx(file); + + v4l2_dbg(1, debug, &dev->v4l2_dev, "%s: Releasing instance %p\n", + __func__, ctx); + + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + v4l2_ctrl_handler_free(&ctx->hdl); + mutex_lock(&dev->dev_mutex); + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); + + if (ctx->component) + vchiq_mmal_component_finalise(dev->instance, ctx->component); + + mutex_unlock(&dev->dev_mutex); + kfree(ctx); + + atomic_dec(&dev->num_inst); + + return 0; +} + +static const struct v4l2_file_operations bcm2835_codec_fops = { + .owner = THIS_MODULE, + .open = bcm2835_codec_open, + .release = bcm2835_codec_release, + .poll = v4l2_m2m_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = v4l2_m2m_fop_mmap, +}; + +static const struct video_device bcm2835_codec_videodev = { + .name = MEM2MEM_NAME, + .vfl_dir = VFL_DIR_M2M, + .fops = &bcm2835_codec_fops, + .ioctl_ops = &bcm2835_codec_ioctl_ops, + .minor = -1, + .release = video_device_release_empty, +}; + +static const struct v4l2_m2m_ops m2m_ops = { + .device_run = device_run, + .job_ready = job_ready, + .job_abort = job_abort, +}; + +/* Size of the array to provide to the VPU when asking for the list of supported + * formats. + * The ISP component currently advertises 62 input formats, so add a small + * overhead on that. + */ +#define MAX_SUPPORTED_ENCODINGS 70 + +/* Populate dev->supported_fmts with the formats supported by those ports. */ +static int bcm2835_codec_get_supported_fmts(struct bcm2835_codec_dev *dev) +{ + struct bcm2835_codec_fmt *list; + struct vchiq_mmal_component *component; + u32 fourccs[MAX_SUPPORTED_ENCODINGS]; + u32 param_size = sizeof(fourccs); + unsigned int i, j, num_encodings; + int ret; + + ret = vchiq_mmal_component_init(dev->instance, components[dev->role], + &component); + if (ret < 0) { + v4l2_err(&dev->v4l2_dev, "%s: failed to create component %s\n", + __func__, components[dev->role]); + return -ENOMEM; + } + + ret = vchiq_mmal_port_parameter_get(dev->instance, + &component->input[0], + MMAL_PARAMETER_SUPPORTED_ENCODINGS, + &fourccs, + ¶m_size); + + if (ret) { + if (ret == MMAL_MSG_STATUS_ENOSPC) { + v4l2_err(&dev->v4l2_dev, + "%s: port has more encodings than we provided space for. Some are dropped (%zu vs %u).\n", + __func__, param_size / sizeof(u32), + MAX_SUPPORTED_ENCODINGS); + num_encodings = MAX_SUPPORTED_ENCODINGS; + } else { + v4l2_err(&dev->v4l2_dev, "%s: get_param ret %u.\n", + __func__, ret); + ret = -EINVAL; + goto destroy_component; + } + } else { + num_encodings = param_size / sizeof(u32); + } + + /* Assume at this stage that all encodings will be supported in V4L2. + * Any that aren't supported will waste a very small amount of memory. + */ + list = devm_kzalloc(&dev->device->dev, + sizeof(struct bcm2835_codec_fmt) * num_encodings, + GFP_KERNEL); + if (!list) { + ret = -ENOMEM; + goto destroy_component; + } + dev->supported_fmts[0].list = list; + + for (i = 0, j = 0; i < num_encodings; i++) { + const struct bcm2835_codec_fmt *fmt = get_fmt(fourccs[i]); + + if (fmt) { + list[j] = *fmt; + j++; + } + } + dev->supported_fmts[0].num_entries = j; + + param_size = sizeof(fourccs); + ret = vchiq_mmal_port_parameter_get(dev->instance, + &component->output[0], + MMAL_PARAMETER_SUPPORTED_ENCODINGS, + &fourccs, + ¶m_size); + + if (ret) { + if (ret == MMAL_MSG_STATUS_ENOSPC) { + v4l2_err(&dev->v4l2_dev, + "%s: port has more encodings than we provided space for. Some are dropped (%zu vs %u).\n", + __func__, param_size / sizeof(u32), + MAX_SUPPORTED_ENCODINGS); + num_encodings = MAX_SUPPORTED_ENCODINGS; + } else { + ret = -EINVAL; + goto destroy_component; + } + } else { + num_encodings = param_size / sizeof(u32); + } + /* Assume at this stage that all encodings will be supported in V4L2. */ + list = devm_kzalloc(&dev->device->dev, + sizeof(struct bcm2835_codec_fmt) * num_encodings, + GFP_KERNEL); + if (!list) { + ret = -ENOMEM; + goto destroy_component; + } + dev->supported_fmts[1].list = list; + + for (i = 0, j = 0; i < num_encodings; i++) { + const struct bcm2835_codec_fmt *fmt = get_fmt(fourccs[i]); + + if (fmt) { + list[j] = *fmt; + j++; + } + } + dev->supported_fmts[1].num_entries = j; + + ret = 0; + +destroy_component: + vchiq_mmal_component_finalise(dev->instance, component); + + return ret; +} + +static int bcm2835_codec_create(struct bcm2835_codec_driver *drv, + struct bcm2835_codec_dev **new_dev, + enum bcm2835_codec_role role) +{ + struct vchiq_device *device = drv->device; + struct bcm2835_codec_dev *dev; + struct video_device *vfd; + int function; + int video_nr; + int ret; + + dev = devm_kzalloc(&device->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + dev->device = device; + + dev->role = role; + + ret = vchiq_mmal_init(&device->dev, &dev->instance); + if (ret) + return ret; + + ret = bcm2835_codec_get_supported_fmts(dev); + if (ret) + goto vchiq_finalise; + + atomic_set(&dev->num_inst, 0); + mutex_init(&dev->dev_mutex); + + /* Initialise the video device */ + dev->vfd = bcm2835_codec_videodev; + + vfd = &dev->vfd; + vfd->lock = &dev->dev_mutex; + vfd->v4l2_dev = &dev->v4l2_dev; + vfd->device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING; + vfd->v4l2_dev->mdev = &drv->mdev; + + ret = v4l2_device_register(&device->dev, &dev->v4l2_dev); + if (ret) + goto vchiq_finalise; + + dev->max_w = MAX_W_CODEC; + dev->max_h = MAX_H_CODEC; + + switch (role) { + case DECODE: + v4l2_disable_ioctl(vfd, VIDIOC_ENCODER_CMD); + v4l2_disable_ioctl(vfd, VIDIOC_TRY_ENCODER_CMD); + v4l2_disable_ioctl(vfd, VIDIOC_S_PARM); + v4l2_disable_ioctl(vfd, VIDIOC_G_PARM); + function = MEDIA_ENT_F_PROC_VIDEO_DECODER; + video_nr = decode_video_nr; + break; + case ENCODE: + v4l2_disable_ioctl(vfd, VIDIOC_DECODER_CMD); + v4l2_disable_ioctl(vfd, VIDIOC_TRY_DECODER_CMD); + function = MEDIA_ENT_F_PROC_VIDEO_ENCODER; + video_nr = encode_video_nr; + break; + case ISP: + v4l2_disable_ioctl(vfd, VIDIOC_DECODER_CMD); + v4l2_disable_ioctl(vfd, VIDIOC_TRY_DECODER_CMD); + v4l2_disable_ioctl(vfd, VIDIOC_S_PARM); + v4l2_disable_ioctl(vfd, VIDIOC_G_PARM); + function = MEDIA_ENT_F_PROC_VIDEO_SCALER; + video_nr = isp_video_nr; + dev->max_w = MAX_W_ISP; + dev->max_h = MAX_H_ISP; + break; + case DEINTERLACE: + v4l2_disable_ioctl(vfd, VIDIOC_DECODER_CMD); + v4l2_disable_ioctl(vfd, VIDIOC_TRY_DECODER_CMD); + v4l2_disable_ioctl(vfd, VIDIOC_S_PARM); + v4l2_disable_ioctl(vfd, VIDIOC_G_PARM); + function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER; + video_nr = deinterlace_video_nr; + break; + case ENCODE_IMAGE: + v4l2_disable_ioctl(vfd, VIDIOC_DECODER_CMD); + v4l2_disable_ioctl(vfd, VIDIOC_TRY_DECODER_CMD); + function = MEDIA_ENT_F_PROC_VIDEO_ENCODER; + video_nr = encode_image_nr; + break; + default: + ret = -EINVAL; + goto unreg_dev; + } + + ret = video_register_device(vfd, VFL_TYPE_VIDEO, video_nr); + if (ret) { + v4l2_err(&dev->v4l2_dev, "Failed to register video device\n"); + goto unreg_dev; + } + + video_set_drvdata(vfd, dev); + snprintf(vfd->name, sizeof(vfd->name), "%s-%s", + bcm2835_codec_videodev.name, roles[role]); + v4l2_info(&dev->v4l2_dev, "Device registered as /dev/video%d\n", + vfd->num); + + *new_dev = dev; + + dev->m2m_dev = v4l2_m2m_init(&m2m_ops); + if (IS_ERR(dev->m2m_dev)) { + v4l2_err(&dev->v4l2_dev, "Failed to init mem2mem device\n"); + ret = PTR_ERR(dev->m2m_dev); + goto err_m2m; + } + + ret = v4l2_m2m_register_media_controller(dev->m2m_dev, vfd, function); + if (ret) + goto err_m2m; + + v4l2_info(&dev->v4l2_dev, "Loaded V4L2 %s\n", + roles[role]); + return 0; + +err_m2m: + v4l2_m2m_release(dev->m2m_dev); + video_unregister_device(&dev->vfd); +unreg_dev: + v4l2_device_unregister(&dev->v4l2_dev); +vchiq_finalise: + vchiq_mmal_finalise(dev->instance); + return ret; +} + +static int bcm2835_codec_destroy(struct bcm2835_codec_dev *dev) +{ + if (!dev) + return -ENODEV; + + v4l2_info(&dev->v4l2_dev, "Removing " MEM2MEM_NAME ", %s\n", + roles[dev->role]); + v4l2_m2m_unregister_media_controller(dev->m2m_dev); + v4l2_m2m_release(dev->m2m_dev); + video_unregister_device(&dev->vfd); + v4l2_device_unregister(&dev->v4l2_dev); + vchiq_mmal_finalise(dev->instance); + + return 0; +} + +static int bcm2835_codec_probe(struct vchiq_device *device) +{ + struct bcm2835_codec_driver *drv; + struct media_device *mdev; + int ret = 0; + + ret = dma_set_mask_and_coherent(&device->dev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(&device->dev, "dma_set_mask_and_coherent failed: %d\n", + ret); + return ret; + } + + drv = devm_kzalloc(&device->dev, sizeof(*drv), GFP_KERNEL); + if (!drv) + return -ENOMEM; + + drv->device = device; + mdev = &drv->mdev; + mdev->dev = &device->dev; + + strscpy(mdev->model, bcm2835_codec_videodev.name, sizeof(mdev->model)); + strscpy(mdev->serial, "0000", sizeof(mdev->serial)); + snprintf(mdev->bus_info, sizeof(mdev->bus_info), "vchiq:%s", + MEM2MEM_NAME); + + /* This should return the vgencmd version information or such .. */ + mdev->hw_revision = 1; + media_device_init(mdev); + + ret = bcm2835_codec_create(drv, &drv->decode, DECODE); + if (ret) + goto out; + + ret = bcm2835_codec_create(drv, &drv->encode, ENCODE); + if (ret) + goto out; + + ret = bcm2835_codec_create(drv, &drv->isp, ISP); + if (ret) + goto out; + + ret = bcm2835_codec_create(drv, &drv->deinterlace, DEINTERLACE); + if (ret) + goto out; + + ret = bcm2835_codec_create(drv, &drv->encode_image, ENCODE_IMAGE); + if (ret) + goto out; + + /* Register the media device node */ + if (media_device_register(mdev) < 0) + goto out; + + vchiq_set_drvdata(device, drv); + + return 0; + +out: + if (drv->encode_image) { + bcm2835_codec_destroy(drv->encode_image); + drv->encode_image = NULL; + } + if (drv->deinterlace) { + bcm2835_codec_destroy(drv->deinterlace); + drv->deinterlace = NULL; + } + if (drv->isp) { + bcm2835_codec_destroy(drv->isp); + drv->isp = NULL; + } + if (drv->encode) { + bcm2835_codec_destroy(drv->encode); + drv->encode = NULL; + } + if (drv->decode) { + bcm2835_codec_destroy(drv->decode); + drv->decode = NULL; + } + return ret; +} + +static void bcm2835_codec_remove(struct vchiq_device *device) +{ + struct bcm2835_codec_driver *drv = vchiq_get_drvdata(device); + + media_device_unregister(&drv->mdev); + + bcm2835_codec_destroy(drv->encode_image); + + bcm2835_codec_destroy(drv->deinterlace); + + bcm2835_codec_destroy(drv->isp); + + bcm2835_codec_destroy(drv->encode); + + bcm2835_codec_destroy(drv->decode); + + media_device_cleanup(&drv->mdev); +} + +static struct vchiq_driver bcm2835_v4l2_codec_driver = { + .probe = bcm2835_codec_probe, + .remove = bcm2835_codec_remove, + .driver = { + .name = "bcm2835-codec", + .owner = THIS_MODULE, + }, +}; + +module_vchiq_driver(bcm2835_v4l2_codec_driver); + +MODULE_DESCRIPTION("BCM2835 codec V4L2 driver"); +MODULE_AUTHOR("Dave Stevenson, <dave.stevenson@raspberrypi.com>"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("0.0.1"); +MODULE_ALIAS("vchiq:bcm2835-codec"); diff --git a/drivers/staging/vc04_services/bcm2835-isp/Kconfig b/drivers/staging/vc04_services/bcm2835-isp/Kconfig new file mode 100644 index 00000000000000..6222799ebe16ab --- /dev/null +++ b/drivers/staging/vc04_services/bcm2835-isp/Kconfig @@ -0,0 +1,14 @@ +config VIDEO_ISP_BCM2835 + tristate "BCM2835 ISP support" + depends on MEDIA_SUPPORT + depends on VIDEO_DEV && (ARCH_BCM2835 || COMPILE_TEST) + depends on MEDIA_CONTROLLER + select BCM2835_VCHIQ_MMAL + select VIDEOBUF2_DMA_CONTIG + help + This is the V4L2 driver for the Broadcom BCM2835 ISP hardware. + This operates over the VCHIQ interface to a service running on + VideoCore. + + To compile this driver as a module, choose M here: the module + will be called bcm2835-isp. diff --git a/drivers/staging/vc04_services/bcm2835-isp/Makefile b/drivers/staging/vc04_services/bcm2835-isp/Makefile new file mode 100644 index 00000000000000..1994f12db3fc9c --- /dev/null +++ b/drivers/staging/vc04_services/bcm2835-isp/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +bcm2835-isp-objs := bcm2835-v4l2-isp.o + +obj-$(CONFIG_VIDEO_ISP_BCM2835) += bcm2835-isp.o + +ccflags-y += \ + -D__VCCOREVER__=0x04000000 diff --git a/drivers/staging/vc04_services/bcm2835-isp/bcm2835-isp-ctrls.h b/drivers/staging/vc04_services/bcm2835-isp/bcm2835-isp-ctrls.h new file mode 100644 index 00000000000000..172605718cdfb4 --- /dev/null +++ b/drivers/staging/vc04_services/bcm2835-isp/bcm2835-isp-ctrls.h @@ -0,0 +1,72 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Broadcom BCM2835 ISP driver + * + * Copyright © 2019-2020 Raspberry Pi (Trading) Ltd. + * + * Author: Naushir Patuck (naush@raspberrypi.com) + * + */ + +#ifndef BCM2835_ISP_CTRLS +#define BCM2835_ISP_CTRLS + +#include <linux/bcm2835-isp.h> + +struct bcm2835_isp_custom_ctrl { + const char *name; + u32 id; + u32 size; + u32 flags; +}; + +static const struct bcm2835_isp_custom_ctrl custom_ctrls[] = { + { + .name = "Colour Correction Matrix", + .id = V4L2_CID_USER_BCM2835_ISP_CC_MATRIX, + .size = sizeof(struct bcm2835_isp_custom_ccm), + .flags = 0 + }, { + .name = "Lens Shading", + .id = V4L2_CID_USER_BCM2835_ISP_LENS_SHADING, + .size = sizeof(struct bcm2835_isp_lens_shading), + .flags = V4L2_CTRL_FLAG_EXECUTE_ON_WRITE + }, { + .name = "Black Level", + .id = V4L2_CID_USER_BCM2835_ISP_BLACK_LEVEL, + .size = sizeof(struct bcm2835_isp_black_level), + .flags = 0 + }, { + .name = "Green Equalisation", + .id = V4L2_CID_USER_BCM2835_ISP_GEQ, + .size = sizeof(struct bcm2835_isp_geq), + .flags = 0 + }, { + .name = "Gamma", + .id = V4L2_CID_USER_BCM2835_ISP_GAMMA, + .size = sizeof(struct bcm2835_isp_gamma), + .flags = 0 + }, { + .name = "Sharpen", + .id = V4L2_CID_USER_BCM2835_ISP_SHARPEN, + .size = sizeof(struct bcm2835_isp_sharpen), + .flags = 0 + }, { + .name = "Denoise", + .id = V4L2_CID_USER_BCM2835_ISP_DENOISE, + .size = sizeof(struct bcm2835_isp_denoise), + .flags = 0 + }, { + .name = "Colour Denoise", + .id = V4L2_CID_USER_BCM2835_ISP_CDN, + .size = sizeof(struct bcm2835_isp_cdn), + .flags = 0 + }, { + .name = "Defective Pixel Correction", + .id = V4L2_CID_USER_BCM2835_ISP_DPC, + .size = sizeof(struct bcm2835_isp_dpc), + .flags = 0 + } +}; + +#endif diff --git a/drivers/staging/vc04_services/bcm2835-isp/bcm2835-isp-fmts.h b/drivers/staging/vc04_services/bcm2835-isp/bcm2835-isp-fmts.h new file mode 100644 index 00000000000000..5c99d453f09214 --- /dev/null +++ b/drivers/staging/vc04_services/bcm2835-isp/bcm2835-isp-fmts.h @@ -0,0 +1,558 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Broadcom BCM2835 ISP driver + * + * Copyright © 2019-2020 Raspberry Pi (Trading) Ltd. + * + * Author: Naushir Patuck (naush@raspberrypi.com) + * + */ + +#ifndef BCM2835_ISP_FMTS +#define BCM2835_ISP_FMTS + +#include <linux/videodev2.h> +#include "../vchiq-mmal/mmal-encodings.h" + +struct bcm2835_isp_fmt { + u32 fourcc; + int depth; + int bytesperline_align; + u32 mmal_fmt; + int size_multiplier_x2; + u32 colorspace_mask; + enum v4l2_colorspace colorspace_default; + unsigned int step_size; +}; + +#define V4L2_COLORSPACE_MASK(colorspace) BIT(colorspace) + +#define V4L2_COLORSPACE_MASK_JPEG V4L2_COLORSPACE_MASK(V4L2_COLORSPACE_JPEG) +#define V4L2_COLORSPACE_MASK_SMPTE170M V4L2_COLORSPACE_MASK(V4L2_COLORSPACE_SMPTE170M) +#define V4L2_COLORSPACE_MASK_REC709 V4L2_COLORSPACE_MASK(V4L2_COLORSPACE_REC709) +#define V4L2_COLORSPACE_MASK_SRGB V4L2_COLORSPACE_MASK(V4L2_COLORSPACE_SRGB) +#define V4L2_COLORSPACE_MASK_RAW V4L2_COLORSPACE_MASK(V4L2_COLORSPACE_RAW) + +/* + * All three colour spaces JPEG, SMPTE170M and REC709 are fundamentally sRGB + * underneath (as near as makes no difference to us), just with different YCbCr + * encodings. Therefore the ISP can generate sRGB on its main output and any of + * the others on its low resolution output. Applications should, when using both + * outputs, program the colour spaces on them to be the same, matching whatever + * is requested for the low resolution output, even if the main output is + * producing an RGB format. In turn this requires us to allow all these colour + * spaces for every YUV/RGB output format. + */ +#define V4L2_COLORSPACE_MASK_ALL_SRGB (V4L2_COLORSPACE_MASK_JPEG | \ + V4L2_COLORSPACE_MASK_SRGB | \ + V4L2_COLORSPACE_MASK_SMPTE170M | \ + V4L2_COLORSPACE_MASK_REC709) + +static const struct bcm2835_isp_fmt supported_formats[] = { + { + /* YUV formats */ + .fourcc = V4L2_PIX_FMT_YUV420, + .depth = 8, + .bytesperline_align = 64, + .mmal_fmt = MMAL_ENCODING_I420, + .size_multiplier_x2 = 3, + .colorspace_mask = V4L2_COLORSPACE_MASK_ALL_SRGB, + .colorspace_default = V4L2_COLORSPACE_JPEG, + .step_size = 2, + }, { + .fourcc = V4L2_PIX_FMT_YVU420, + .depth = 8, + .bytesperline_align = 64, + .mmal_fmt = MMAL_ENCODING_YV12, + .size_multiplier_x2 = 3, + .colorspace_mask = V4L2_COLORSPACE_MASK_ALL_SRGB, + .colorspace_default = V4L2_COLORSPACE_SMPTE170M, + .step_size = 2, + }, { + .fourcc = V4L2_PIX_FMT_NV12, + .depth = 8, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_NV12, + .size_multiplier_x2 = 3, + .colorspace_mask = V4L2_COLORSPACE_MASK_ALL_SRGB, + .colorspace_default = V4L2_COLORSPACE_SMPTE170M, + .step_size = 2, + }, { + .fourcc = V4L2_PIX_FMT_NV21, + .depth = 8, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_NV21, + .size_multiplier_x2 = 3, + .colorspace_mask = V4L2_COLORSPACE_MASK_ALL_SRGB, + .colorspace_default = V4L2_COLORSPACE_SMPTE170M, + .step_size = 2, + }, { + .fourcc = V4L2_PIX_FMT_YUYV, + .depth = 16, + .bytesperline_align = 64, + .mmal_fmt = MMAL_ENCODING_YUYV, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_ALL_SRGB, + .colorspace_default = V4L2_COLORSPACE_SMPTE170M, + .step_size = 2, + }, { + .fourcc = V4L2_PIX_FMT_UYVY, + .depth = 16, + .bytesperline_align = 64, + .mmal_fmt = MMAL_ENCODING_UYVY, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_ALL_SRGB, + .colorspace_default = V4L2_COLORSPACE_SMPTE170M, + .step_size = 2, + }, { + .fourcc = V4L2_PIX_FMT_YVYU, + .depth = 16, + .bytesperline_align = 64, + .mmal_fmt = MMAL_ENCODING_YVYU, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_ALL_SRGB, + .colorspace_default = V4L2_COLORSPACE_SMPTE170M, + .step_size = 2, + }, { + .fourcc = V4L2_PIX_FMT_VYUY, + .depth = 16, + .bytesperline_align = 64, + .mmal_fmt = MMAL_ENCODING_VYUY, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_ALL_SRGB, + .colorspace_default = V4L2_COLORSPACE_SMPTE170M, + .step_size = 2, + }, { + /* RGB formats */ + .fourcc = V4L2_PIX_FMT_RGB24, + .depth = 24, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_RGB24, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_ALL_SRGB, + .colorspace_default = V4L2_COLORSPACE_SRGB, + .step_size = 1, + }, { + .fourcc = V4L2_PIX_FMT_RGB565, + .depth = 16, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_RGB16, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_ALL_SRGB, + .colorspace_default = V4L2_COLORSPACE_SRGB, + .step_size = 1, + }, { + .fourcc = V4L2_PIX_FMT_BGR24, + .depth = 24, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_BGR24, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_ALL_SRGB, + .colorspace_default = V4L2_COLORSPACE_SRGB, + .step_size = 1, + }, { + .fourcc = V4L2_PIX_FMT_XBGR32, + .depth = 32, + .bytesperline_align = 64, + .mmal_fmt = MMAL_ENCODING_BGRA, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_ALL_SRGB, + .colorspace_default = V4L2_COLORSPACE_SRGB, + .step_size = 1, + }, { + .fourcc = V4L2_PIX_FMT_RGBX32, + .depth = 32, + .bytesperline_align = 64, + .mmal_fmt = MMAL_ENCODING_RGBA, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_ALL_SRGB, + .colorspace_default = V4L2_COLORSPACE_SRGB, + .step_size = 1, + }, { + /* Bayer formats */ + /* 8 bit */ + .fourcc = V4L2_PIX_FMT_SRGGB8, + .depth = 8, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_BAYER_SRGGB8, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_RAW, + .colorspace_default = V4L2_COLORSPACE_RAW, + .step_size = 2, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR8, + .depth = 8, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_BAYER_SBGGR8, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_RAW, + .colorspace_default = V4L2_COLORSPACE_RAW, + .step_size = 2, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG8, + .depth = 8, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_BAYER_SGRBG8, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_RAW, + .colorspace_default = V4L2_COLORSPACE_RAW, + .step_size = 2, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG8, + .depth = 8, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_BAYER_SGBRG8, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_RAW, + .colorspace_default = V4L2_COLORSPACE_RAW, + .step_size = 2, + }, { + /* 10 bit */ + .fourcc = V4L2_PIX_FMT_SRGGB10P, + .depth = 10, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_BAYER_SRGGB10P, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_RAW, + .colorspace_default = V4L2_COLORSPACE_RAW, + .step_size = 2, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR10P, + .depth = 10, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_BAYER_SBGGR10P, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_RAW, + .colorspace_default = V4L2_COLORSPACE_RAW, + .step_size = 2, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG10P, + .depth = 10, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_BAYER_SGRBG10P, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_RAW, + .colorspace_default = V4L2_COLORSPACE_RAW, + .step_size = 2, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG10P, + .depth = 10, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_BAYER_SGBRG10P, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_RAW, + .colorspace_default = V4L2_COLORSPACE_RAW, + .step_size = 2, + }, { + /* 12 bit */ + .fourcc = V4L2_PIX_FMT_SRGGB12P, + .depth = 12, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_BAYER_SRGGB12P, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_RAW, + .colorspace_default = V4L2_COLORSPACE_RAW, + .step_size = 2, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR12P, + .depth = 12, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_BAYER_SBGGR12P, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_RAW, + .colorspace_default = V4L2_COLORSPACE_RAW, + .step_size = 2, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG12P, + .depth = 12, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_BAYER_SGRBG12P, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_RAW, + .colorspace_default = V4L2_COLORSPACE_RAW, + .step_size = 2, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG12P, + .depth = 12, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_BAYER_SGBRG12P, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_RAW, + .colorspace_default = V4L2_COLORSPACE_RAW, + .step_size = 2, + }, { + /* 14 bit */ + .fourcc = V4L2_PIX_FMT_SRGGB14P, + .depth = 14, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_BAYER_SRGGB14P, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_RAW, + .colorspace_default = V4L2_COLORSPACE_RAW, + .step_size = 2, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR14P, + .depth = 14, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_BAYER_SBGGR14P, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_RAW, + .colorspace_default = V4L2_COLORSPACE_RAW, + .step_size = 2, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG14P, + .depth = 14, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_BAYER_SGRBG14P, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_RAW, + .colorspace_default = V4L2_COLORSPACE_RAW, + .step_size = 2, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG14P, + .depth = 14, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_BAYER_SGBRG14P, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_RAW, + .colorspace_default = V4L2_COLORSPACE_RAW, + .step_size = 2, + }, { + /* 16 bit */ + .fourcc = V4L2_PIX_FMT_SRGGB16, + .depth = 16, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_BAYER_SRGGB16, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_RAW, + .colorspace_default = V4L2_COLORSPACE_RAW, + .step_size = 2, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR16, + .depth = 16, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_BAYER_SBGGR16, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_RAW, + .colorspace_default = V4L2_COLORSPACE_RAW, + .step_size = 2, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG16, + .depth = 16, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_BAYER_SGRBG16, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_RAW, + .colorspace_default = V4L2_COLORSPACE_RAW, + .step_size = 2, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG16, + .depth = 16, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_BAYER_SGBRG16, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_RAW, + .colorspace_default = V4L2_COLORSPACE_RAW, + .step_size = 2, + }, { + /* Bayer formats unpacked to 16bpp */ + /* 10 bit */ + .fourcc = V4L2_PIX_FMT_SRGGB10, + .depth = 16, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_BAYER_SRGGB10, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_RAW, + .colorspace_default = V4L2_COLORSPACE_RAW, + .step_size = 2, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR10, + .depth = 16, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_BAYER_SBGGR10, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_RAW, + .colorspace_default = V4L2_COLORSPACE_RAW, + .step_size = 2, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG10, + .depth = 16, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_BAYER_SGRBG10, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_RAW, + .colorspace_default = V4L2_COLORSPACE_RAW, + .step_size = 2, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG10, + .depth = 16, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_BAYER_SGBRG10, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_RAW, + .colorspace_default = V4L2_COLORSPACE_RAW, + .step_size = 2, + }, { + /* 12 bit */ + .fourcc = V4L2_PIX_FMT_SRGGB12, + .depth = 16, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_BAYER_SRGGB12, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_RAW, + .colorspace_default = V4L2_COLORSPACE_RAW, + .step_size = 2, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR12, + .depth = 16, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_BAYER_SBGGR12, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_RAW, + .colorspace_default = V4L2_COLORSPACE_RAW, + .step_size = 2, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG12, + .depth = 16, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_BAYER_SGRBG12, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_RAW, + .colorspace_default = V4L2_COLORSPACE_RAW, + .step_size = 2, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG12, + .depth = 16, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_BAYER_SGBRG12, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_RAW, + .colorspace_default = V4L2_COLORSPACE_RAW, + .step_size = 2, + }, { + /* 14 bit */ + .fourcc = V4L2_PIX_FMT_SRGGB14, + .depth = 16, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_BAYER_SRGGB14, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_RAW, + .colorspace_default = V4L2_COLORSPACE_RAW, + .step_size = 2, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR14, + .depth = 16, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_BAYER_SBGGR14, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_RAW, + .colorspace_default = V4L2_COLORSPACE_RAW, + .step_size = 2, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG14, + .depth = 16, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_BAYER_SGRBG14, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_RAW, + .colorspace_default = V4L2_COLORSPACE_RAW, + .step_size = 2, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG14, + .depth = 16, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_BAYER_SGBRG14, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_RAW, + .colorspace_default = V4L2_COLORSPACE_RAW, + .step_size = 2, + }, { + /* Monochrome MIPI formats */ + /* 8 bit */ + .fourcc = V4L2_PIX_FMT_GREY, + .depth = 8, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_GREY, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_RAW, + .colorspace_default = V4L2_COLORSPACE_RAW, + .step_size = 2, + }, { + /* 10 bit */ + .fourcc = V4L2_PIX_FMT_Y10P, + .depth = 10, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_Y10P, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_RAW, + .colorspace_default = V4L2_COLORSPACE_RAW, + .step_size = 2, + }, { + /* 12 bit */ + .fourcc = V4L2_PIX_FMT_Y12P, + .depth = 12, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_Y12P, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_RAW, + .colorspace_default = V4L2_COLORSPACE_RAW, + .step_size = 2, + }, { + /* 14 bit */ + .fourcc = V4L2_PIX_FMT_Y14P, + .depth = 14, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_Y14P, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_RAW, + .colorspace_default = V4L2_COLORSPACE_RAW, + .step_size = 2, + }, { + /* 16 bit */ + .fourcc = V4L2_PIX_FMT_Y16, + .depth = 16, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_Y16, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_RAW, + .colorspace_default = V4L2_COLORSPACE_RAW, + .step_size = 2, + }, { + /* 10 bit as 16bpp */ + .fourcc = V4L2_PIX_FMT_Y10, + .depth = 16, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_Y10, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_RAW, + .colorspace_default = V4L2_COLORSPACE_RAW, + .step_size = 2, + }, { + /* 12 bit as 16bpp */ + .fourcc = V4L2_PIX_FMT_Y12, + .depth = 16, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_Y12, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_RAW, + .colorspace_default = V4L2_COLORSPACE_RAW, + .step_size = 2, + }, { + /* 14 bit as 16bpp */ + .fourcc = V4L2_PIX_FMT_Y14, + .depth = 16, + .bytesperline_align = 32, + .mmal_fmt = MMAL_ENCODING_Y14, + .size_multiplier_x2 = 2, + .colorspace_mask = V4L2_COLORSPACE_MASK_RAW, + .colorspace_default = V4L2_COLORSPACE_RAW, + .step_size = 2, + }, { + .fourcc = V4L2_META_FMT_BCM2835_ISP_STATS, + .depth = 8, + .mmal_fmt = MMAL_ENCODING_BRCM_STATS, + /* The rest are not valid fields for stats. */ + } +}; + +#endif diff --git a/drivers/staging/vc04_services/bcm2835-isp/bcm2835-v4l2-isp.c b/drivers/staging/vc04_services/bcm2835-isp/bcm2835-v4l2-isp.c new file mode 100644 index 00000000000000..6c3c4fa1f34459 --- /dev/null +++ b/drivers/staging/vc04_services/bcm2835-isp/bcm2835-v4l2-isp.c @@ -0,0 +1,1839 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Broadcom BCM2835 ISP driver + * + * Copyright © 2019-2020 Raspberry Pi (Trading) Ltd. + * + * Author: Naushir Patuck (naush@raspberrypi.com) + * + */ + +#include <linux/module.h> + +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-event.h> +#include <media/v4l2-ioctl.h> +#include <media/videobuf2-dma-contig.h> + +#include "../interface/vchiq_arm/vchiq_bus.h" + +#include "../vchiq-mmal/mmal-msg.h" +#include "../vchiq-mmal/mmal-parameters.h" +#include "../vchiq-mmal/mmal-vchiq.h" + +#include "../vc-sm-cma/vc_sm_knl.h" + +#include "bcm2835-isp-ctrls.h" +#include "bcm2835-isp-fmts.h" + +/* + * We want to instantiate 2 independent instances allowing 2 simultaneous users + * of the ISP hardware. + */ +#define BCM2835_ISP_NUM_INSTANCES 2 + +MODULE_IMPORT_NS(DMA_BUF); + +static unsigned int debug; +module_param(debug, uint, 0644); +MODULE_PARM_DESC(debug, "activates debug info"); + +static unsigned int video_nr[BCM2835_ISP_NUM_INSTANCES] = { 13, 20 }; +module_param_array(video_nr, uint, NULL, 0644); +MODULE_PARM_DESC(video_nr, "base video device numbers"); + +#define BCM2835_ISP_NAME "bcm2835-isp" +#define BCM2835_ISP_ENTITY_NAME_LEN 32 + +#define BCM2835_ISP_NUM_OUTPUTS 1 +#define BCM2835_ISP_NUM_CAPTURES 2 +#define BCM2835_ISP_NUM_METADATA 1 + +#define BCM2835_ISP_NUM_NODES \ + (BCM2835_ISP_NUM_OUTPUTS + BCM2835_ISP_NUM_CAPTURES + \ + BCM2835_ISP_NUM_METADATA) + +/* Default frame dimension of 1280 pixels. */ +#define DEFAULT_DIM 1280U +/* + * Maximum frame dimension of 16384 pixels. Even though the ISP runs in tiles, + * have a sensible limit so that we do not create an excessive number of tiles + * to process. + */ +#define MAX_DIM 16384U +/* + * Minimum frame dimension of 64 pixels. Anything lower, and the tiling + * algorithm may not be able to cope when applying filter context. + */ +#define MIN_DIM 64U + +/* Timeout for stop_streaming to allow all buffers to return */ +#define COMPLETE_TIMEOUT (2 * HZ) + +/* Per-queue, driver-specific private data */ +struct bcm2835_isp_q_data { + /* + * These parameters should be treated as gospel, with everything else + * being determined from them. + */ + unsigned int bytesperline; + unsigned int width; + unsigned int height; + unsigned int sizeimage; + enum v4l2_colorspace colorspace; + const struct bcm2835_isp_fmt *fmt; +}; + +/* + * Structure to describe a single node /dev/video<N> which represents a single + * input or output queue to the ISP device. + */ +struct bcm2835_isp_node { + int vfl_dir; + unsigned int id; + const char *name; + struct vchiq_mmal_port *port; + struct video_device vfd; + struct media_pad pad; + struct media_intf_devnode *intf_devnode; + struct media_link *intf_link; + struct mutex lock; /* top level device node lock */ + struct mutex queue_lock; + + struct vb2_queue queue; + unsigned int sequence; + + /* The list of formats supported on the node. */ + struct bcm2835_isp_fmt const **supported_fmts; + unsigned int num_supported_fmts; + + struct bcm2835_isp_q_data q_data; + + /* Parent device structure */ + struct bcm2835_isp_dev *dev; + + bool registered; + bool media_node_registered; +}; + +/* + * Structure representing the entire ISP device, comprising several input and + * output nodes /dev/video<N>. + */ +struct bcm2835_isp_dev { + struct v4l2_device v4l2_dev; + struct device *dev; + struct v4l2_ctrl_handler ctrl_handler; + struct media_device mdev; + struct media_entity entity; + bool media_device_registered; + bool media_entity_registered; + struct vchiq_mmal_instance *mmal_instance; + struct vchiq_mmal_component *component; + struct completion frame_cmplt; + + struct bcm2835_isp_node node[BCM2835_ISP_NUM_NODES]; + struct media_pad pad[BCM2835_ISP_NUM_NODES]; + atomic_t num_streaming; + + /* Image pipeline controls. */ + int r_gain; + int b_gain; + struct dma_buf *last_ls_dmabuf; + struct mmal_parameter_lens_shading_v2 ls; +}; + +struct bcm2835_isp_buffer { + struct vb2_v4l2_buffer vb; + struct mmal_buffer mmal; +}; + +static +inline struct bcm2835_isp_dev *node_get_dev(struct bcm2835_isp_node *node) +{ + return node->dev; +} + +static inline bool node_is_output(struct bcm2835_isp_node *node) +{ + return node->queue.type == V4L2_BUF_TYPE_VIDEO_OUTPUT; +} + +static inline bool node_is_capture(struct bcm2835_isp_node *node) +{ + return node->queue.type == V4L2_BUF_TYPE_VIDEO_CAPTURE; +} + +static inline bool node_is_stats(struct bcm2835_isp_node *node) +{ + return node->queue.type == V4L2_BUF_TYPE_META_CAPTURE; +} + +static inline enum v4l2_buf_type index_to_queue_type(int index) +{ + if (index < BCM2835_ISP_NUM_OUTPUTS) + return V4L2_BUF_TYPE_VIDEO_OUTPUT; + else if (index < BCM2835_ISP_NUM_OUTPUTS + BCM2835_ISP_NUM_CAPTURES) + return V4L2_BUF_TYPE_VIDEO_CAPTURE; + else + return V4L2_BUF_TYPE_META_CAPTURE; +} + +static int set_isp_param(struct bcm2835_isp_node *node, u32 parameter, + void *value, u32 value_size) +{ + struct bcm2835_isp_dev *dev = node_get_dev(node); + + return vchiq_mmal_port_parameter_set(dev->mmal_instance, node->port, + parameter, value, value_size); +} + +static int set_wb_gains(struct bcm2835_isp_node *node) +{ + struct bcm2835_isp_dev *dev = node_get_dev(node); + struct mmal_parameter_awbgains gains = { + .r_gain = { dev->r_gain, 1000 }, + .b_gain = { dev->b_gain, 1000 } + }; + + return set_isp_param(node, MMAL_PARAMETER_CUSTOM_AWB_GAINS, + &gains, sizeof(gains)); +} + +static int set_digital_gain(struct bcm2835_isp_node *node, uint32_t gain) +{ + struct s32_fract digital_gain = { + .numerator = gain, + .denominator = 1000 + }; + + return set_isp_param(node, MMAL_PARAMETER_DIGITAL_GAIN, + &digital_gain, sizeof(digital_gain)); +} + +static const struct bcm2835_isp_fmt *get_fmt(u32 mmal_fmt) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(supported_formats); i++) { + if (supported_formats[i].mmal_fmt == mmal_fmt) + return &supported_formats[i]; + } + return NULL; +} + +static const +struct bcm2835_isp_fmt *find_format_by_fourcc(unsigned int fourcc, + struct bcm2835_isp_node *node) +{ + const struct bcm2835_isp_fmt *fmt; + unsigned int i; + + for (i = 0; i < node->num_supported_fmts; i++) { + fmt = node->supported_fmts[i]; + if (fmt->fourcc == fourcc) + return fmt; + } + + return NULL; +} + +static const +struct bcm2835_isp_fmt *find_format(struct v4l2_format *f, + struct bcm2835_isp_node *node) +{ + return find_format_by_fourcc(node_is_stats(node) ? + f->fmt.meta.dataformat : + f->fmt.pix.pixelformat, + node); +} + +/* vb2_to_mmal_buffer() - converts vb2 buffer header to MMAL + * + * Copies all the required fields from a VB2 buffer to the MMAL buffer header, + * ready for sending to the VPU. + */ +static void vb2_to_mmal_buffer(struct mmal_buffer *buf, + struct vb2_v4l2_buffer *vb2) +{ + u64 pts; + + buf->mmal_flags = 0; + if (vb2->flags & V4L2_BUF_FLAG_KEYFRAME) + buf->mmal_flags |= MMAL_BUFFER_HEADER_FLAG_KEYFRAME; + + /* Data must be framed correctly as one frame per buffer. */ + buf->mmal_flags |= MMAL_BUFFER_HEADER_FLAG_FRAME_END; + + buf->length = vb2->vb2_buf.planes[0].bytesused; + /* + * Minor ambiguity in the V4L2 spec as to whether passing in a 0 length + * buffer, or one with V4L2_BUF_FLAG_LAST set denotes end of stream. + * Handle either. + */ + if (!buf->length || vb2->flags & V4L2_BUF_FLAG_LAST) + buf->mmal_flags |= MMAL_BUFFER_HEADER_FLAG_EOS; + + /* vb2 timestamps in nsecs, mmal in usecs */ + pts = vb2->vb2_buf.timestamp; + do_div(pts, 1000); + buf->pts = pts; + buf->dts = MMAL_TIME_UNKNOWN; +} + +static void mmal_buffer_cb(struct vchiq_mmal_instance *instance, + struct vchiq_mmal_port *port, int status, + struct mmal_buffer *mmal_buf) +{ + struct bcm2835_isp_buffer *q_buf; + struct bcm2835_isp_node *node = port->cb_ctx; + struct bcm2835_isp_dev *dev = node_get_dev(node); + struct vb2_v4l2_buffer *vb2; + + q_buf = container_of(mmal_buf, struct bcm2835_isp_buffer, mmal); + vb2 = &q_buf->vb; + v4l2_dbg(2, debug, &dev->v4l2_dev, + "%s: port:%s[%d], status:%d, buf:%p, dmabuf:%p, length:%lu, flags %u, pts %lld\n", + __func__, node_is_output(node) ? "input" : "output", node->id, + status, mmal_buf, mmal_buf->dma_buf, mmal_buf->length, + mmal_buf->mmal_flags, mmal_buf->pts); + + if (mmal_buf->cmd) + v4l2_err(&dev->v4l2_dev, + "%s: Unexpected event on output callback - %08x\n", + __func__, mmal_buf->cmd); + + if (status) { + /* error in transfer */ + if (vb2) { + /* there was a buffer with the error so return it */ + vb2_buffer_done(&vb2->vb2_buf, VB2_BUF_STATE_ERROR); + } + return; + } + + /* vb2 timestamps in nsecs, mmal in usecs */ + vb2->vb2_buf.timestamp = mmal_buf->pts * 1000; + vb2->sequence = node->sequence++; + vb2_set_plane_payload(&vb2->vb2_buf, 0, mmal_buf->length); + vb2_buffer_done(&vb2->vb2_buf, VB2_BUF_STATE_DONE); + + if (!port->enabled) + complete(&dev->frame_cmplt); +} + +struct colorspace_translation { + enum v4l2_colorspace v4l2_value; + u32 mmal_value; +}; + +static u32 translate_color_space(enum v4l2_colorspace color_space) +{ + static const struct colorspace_translation translations[] = { + { V4L2_COLORSPACE_DEFAULT, MMAL_COLOR_SPACE_UNKNOWN }, + { V4L2_COLORSPACE_SMPTE170M, MMAL_COLOR_SPACE_ITUR_BT601 }, + { V4L2_COLORSPACE_SMPTE240M, MMAL_COLOR_SPACE_SMPTE240M }, + { V4L2_COLORSPACE_REC709, MMAL_COLOR_SPACE_ITUR_BT709 }, + /* V4L2_COLORSPACE_BT878 unavailable */ + { V4L2_COLORSPACE_470_SYSTEM_M, MMAL_COLOR_SPACE_BT470_2_M }, + { V4L2_COLORSPACE_470_SYSTEM_BG, MMAL_COLOR_SPACE_BT470_2_BG }, + { V4L2_COLORSPACE_JPEG, MMAL_COLOR_SPACE_JPEG_JFIF }, + /* + * We don't have an encoding for SRGB as such, but VideoCore + * will do the right thing if it gets "unknown". + */ + { V4L2_COLORSPACE_SRGB, MMAL_COLOR_SPACE_UNKNOWN }, + /* V4L2_COLORSPACE_OPRGB unavailable */ + /* V4L2_COLORSPACE_BT2020 unavailable */ + /* V4L2_COLORSPACE_RAW unavailable */ + /* V4L2_COLORSPACE_DCI_P3 unavailable */ + }; + + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(translations); i++) { + if (color_space == translations[i].v4l2_value) + return translations[i].mmal_value; + } + + return MMAL_COLOR_SPACE_UNKNOWN; +} + +static void setup_mmal_port_format(struct bcm2835_isp_node *node, + struct vchiq_mmal_port *port) +{ + struct bcm2835_isp_q_data *q_data = &node->q_data; + + port->format.encoding = q_data->fmt->mmal_fmt; + /* Raw image format - set width/height */ + port->es.video.width = (q_data->bytesperline << 3) / q_data->fmt->depth; + port->es.video.height = q_data->height; + port->es.video.crop.width = q_data->width; + port->es.video.crop.height = q_data->height; + port->es.video.crop.x = 0; + port->es.video.crop.y = 0; + port->es.video.color_space = translate_color_space(q_data->colorspace); +}; + +static int setup_mmal_port(struct bcm2835_isp_node *node) +{ + struct bcm2835_isp_dev *dev = node_get_dev(node); + unsigned int enable = 1; + int ret; + + v4l2_dbg(2, debug, &dev->v4l2_dev, "%s: setup %s[%d]\n", __func__, + node->name, node->id); + + vchiq_mmal_port_parameter_set(dev->mmal_instance, node->port, + MMAL_PARAMETER_ZERO_COPY, &enable, + sizeof(enable)); + setup_mmal_port_format(node, node->port); + ret = vchiq_mmal_port_set_format(dev->mmal_instance, node->port); + if (ret < 0) { + v4l2_dbg(1, debug, &dev->v4l2_dev, + "%s: vchiq_mmal_port_set_format failed\n", + __func__); + return ret; + } + + if (node->q_data.sizeimage < node->port->minimum_buffer.size) { + v4l2_err(&dev->v4l2_dev, + "buffer size mismatch sizeimage %u < min size %u\n", + node->q_data.sizeimage, + node->port->minimum_buffer.size); + return -EINVAL; + } + + return 0; +} + +static int bcm2835_isp_mmal_buf_cleanup(struct mmal_buffer *mmal_buf) +{ + mmal_vchi_buffer_cleanup(mmal_buf); + + if (mmal_buf->dma_buf) { + dma_buf_put(mmal_buf->dma_buf); + mmal_buf->dma_buf = NULL; + } + + return 0; +} + +static int bcm2835_isp_node_queue_setup(struct vb2_queue *q, + unsigned int *nbuffers, + unsigned int *nplanes, + unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct bcm2835_isp_node *node = vb2_get_drv_priv(q); + unsigned int size; + + if (setup_mmal_port(node)) + return -EINVAL; + + size = node->q_data.sizeimage; + if (size == 0) { + v4l2_info(&node_get_dev(node)->v4l2_dev, + "%s: Image size unset in queue_setup for node %s[%d]\n", + __func__, node->name, node->id); + return -EINVAL; + } + + if (*nplanes) + return sizes[0] < size ? -EINVAL : 0; + + *nplanes = 1; + sizes[0] = size; + + node->port->current_buffer.size = size; + + if (*nbuffers < node->port->minimum_buffer.num) + *nbuffers = node->port->minimum_buffer.num; + + node->port->current_buffer.num = *nbuffers; + + v4l2_dbg(2, debug, &node_get_dev(node)->v4l2_dev, + "%s: Image size %u, nbuffers %u for node %s[%d]\n", + __func__, sizes[0], *nbuffers, node->name, node->id); + return 0; +} + +static int bcm2835_isp_buf_init(struct vb2_buffer *vb) +{ + struct bcm2835_isp_node *node = vb2_get_drv_priv(vb->vb2_queue); + struct bcm2835_isp_dev *dev = node_get_dev(node); + struct vb2_v4l2_buffer *vb2 = to_vb2_v4l2_buffer(vb); + struct bcm2835_isp_buffer *buf = + container_of(vb2, struct bcm2835_isp_buffer, vb); + + v4l2_dbg(3, debug, &dev->v4l2_dev, "%s: vb %p\n", __func__, vb); + + buf->mmal.buffer = vb2_plane_vaddr(&buf->vb.vb2_buf, 0); + buf->mmal.buffer_size = vb2_plane_size(&buf->vb.vb2_buf, 0); + mmal_vchi_buffer_init(dev->mmal_instance, &buf->mmal); + return 0; +} + +static int bcm2835_isp_buf_prepare(struct vb2_buffer *vb) +{ + struct bcm2835_isp_node *node = vb2_get_drv_priv(vb->vb2_queue); + struct bcm2835_isp_dev *dev = node_get_dev(node); + struct vb2_v4l2_buffer *vb2 = to_vb2_v4l2_buffer(vb); + struct bcm2835_isp_buffer *buf = + container_of(vb2, struct bcm2835_isp_buffer, vb); + struct dma_buf *dma_buf; + int ret; + + v4l2_dbg(3, debug, &dev->v4l2_dev, "%s: type: %d ptr %p\n", + __func__, vb->vb2_queue->type, vb); + + if (V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type)) { + if (vb2->field == V4L2_FIELD_ANY) + vb2->field = V4L2_FIELD_NONE; + if (vb2->field != V4L2_FIELD_NONE) { + v4l2_err(&dev->v4l2_dev, + "%s field isn't supported\n", __func__); + return -EINVAL; + } + } + + if (vb2_plane_size(vb, 0) < node->q_data.sizeimage) { + v4l2_err(&dev->v4l2_dev, + "%s data will not fit into plane (%lu < %lu)\n", + __func__, vb2_plane_size(vb, 0), + (long)node->q_data.sizeimage); + return -EINVAL; + } + + if (!V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type)) + vb2_set_plane_payload(vb, 0, node->q_data.sizeimage); + + switch (vb->memory) { + case VB2_MEMORY_DMABUF: + dma_buf = dma_buf_get(vb->planes[0].m.fd); + + if (dma_buf != buf->mmal.dma_buf) { + /* + * dmabuf either hasn't already been mapped, or it has + * changed. + */ + if (buf->mmal.dma_buf) { + v4l2_err(&dev->v4l2_dev, + "%s Buffer changed - why did the core not call cleanup?\n", + __func__); + bcm2835_isp_mmal_buf_cleanup(&buf->mmal); + } + + buf->mmal.dma_buf = dma_buf; + } else { + /* + * Already have a reference to the buffer, so release it + * here. + */ + dma_buf_put(dma_buf); + } + ret = 0; + break; + case VB2_MEMORY_MMAP: + /* + * We want to do this at init, but vb2_core_expbuf checks that + * the index < q->num_buffers, and q->num_buffers only gets + * updated once all the buffers are allocated. + */ + if (!buf->mmal.dma_buf) { + ret = vb2_core_expbuf_dmabuf(vb->vb2_queue, + vb->vb2_queue->type, + vb, 0, O_CLOEXEC, + &buf->mmal.dma_buf); + v4l2_dbg(3, debug, &dev->v4l2_dev, + "%s: exporting ptr %p to dmabuf %p\n", + __func__, vb, buf->mmal.dma_buf); + if (ret) + v4l2_err(&dev->v4l2_dev, + "%s: Failed to expbuf idx %d, ret %d\n", + __func__, vb->index, ret); + } else { + ret = 0; + } + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static void bcm2835_isp_node_buffer_queue(struct vb2_buffer *buf) +{ + struct bcm2835_isp_node *node = vb2_get_drv_priv(buf->vb2_queue); + struct vb2_v4l2_buffer *vbuf = + container_of(buf, struct vb2_v4l2_buffer, vb2_buf); + struct bcm2835_isp_buffer *buffer = + container_of(vbuf, struct bcm2835_isp_buffer, vb); + struct bcm2835_isp_dev *dev = node_get_dev(node); + + v4l2_dbg(3, debug, &dev->v4l2_dev, "%s: node %s[%d], buffer %p\n", + __func__, node->name, node->id, buffer); + + vb2_to_mmal_buffer(&buffer->mmal, &buffer->vb); + v4l2_dbg(3, debug, &dev->v4l2_dev, + "%s: node %s[%d] - submitting mmal dmabuf %p\n", __func__, + node->name, node->id, buffer->mmal.dma_buf); + vchiq_mmal_submit_buffer(dev->mmal_instance, node->port, &buffer->mmal); +} + +static void bcm2835_isp_buffer_cleanup(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vb2 = to_vb2_v4l2_buffer(vb); + struct bcm2835_isp_buffer *buffer = + container_of(vb2, struct bcm2835_isp_buffer, vb); + + bcm2835_isp_mmal_buf_cleanup(&buffer->mmal); +} + +static int bcm2835_isp_node_start_streaming(struct vb2_queue *q, + unsigned int count) +{ + struct bcm2835_isp_node *node = vb2_get_drv_priv(q); + struct bcm2835_isp_dev *dev = node_get_dev(node); + int ret; + + v4l2_dbg(1, debug, &dev->v4l2_dev, "%s: node %s[%d] (count %u)\n", + __func__, node->name, node->id, count); + + ret = vchiq_mmal_component_enable(dev->mmal_instance, dev->component); + if (ret) { + v4l2_err(&dev->v4l2_dev, "%s: Failed enabling component, ret %d\n", + __func__, ret); + return -EIO; + } + + node->sequence = 0; + node->port->cb_ctx = node; + ret = vchiq_mmal_port_enable(dev->mmal_instance, node->port, + mmal_buffer_cb); + if (!ret) + atomic_inc(&dev->num_streaming); + else + v4l2_err(&dev->v4l2_dev, + "%s: Failed enabling port, ret %d\n", __func__, ret); + + return ret; +} + +static void bcm2835_isp_node_stop_streaming(struct vb2_queue *q) +{ + struct bcm2835_isp_node *node = vb2_get_drv_priv(q); + struct bcm2835_isp_dev *dev = node_get_dev(node); + int ret; + + v4l2_dbg(1, debug, &dev->v4l2_dev, "%s: node %s[%d], mmal port %p\n", + __func__, node->name, node->id, node->port); + + init_completion(&dev->frame_cmplt); + + /* Disable MMAL port - this will flush buffers back */ + ret = vchiq_mmal_port_disable(dev->mmal_instance, node->port); + if (ret) + v4l2_err(&dev->v4l2_dev, + "%s: Failed disabling %s port, ret %d\n", __func__, + node_is_output(node) ? "i/p" : "o/p", + ret); + + while (atomic_read(&node->port->buffers_with_vpu)) { + v4l2_dbg(1, debug, &dev->v4l2_dev, + "%s: Waiting for buffers to be returned - %d outstanding\n", + __func__, atomic_read(&node->port->buffers_with_vpu)); + ret = wait_for_completion_timeout(&dev->frame_cmplt, + COMPLETE_TIMEOUT); + if (ret <= 0) { + v4l2_err(&dev->v4l2_dev, + "%s: Timeout waiting for buffers to be returned - %d outstanding\n", + __func__, + atomic_read(&node->port->buffers_with_vpu)); + break; + } + } + + atomic_dec(&dev->num_streaming); + /* If all ports disabled, then disable the component */ + if (atomic_read(&dev->num_streaming) == 0) { + /* + * The ISP component on the firmware has a reference to the + * dmabuf handle for the lens shading table. Pass a null handle + * to remove that reference now. + */ + memset(&dev->ls, 0, sizeof(dev->ls)); + /* Must set a valid grid size for the FW */ + dev->ls.grid_cell_size = 16; + set_isp_param(&dev->node[0], + MMAL_PARAMETER_LENS_SHADING_OVERRIDE, + &dev->ls, sizeof(dev->ls)); + dev->last_ls_dmabuf = NULL; + + ret = vchiq_mmal_component_disable(dev->mmal_instance, + dev->component); + if (ret) { + v4l2_err(&dev->v4l2_dev, + "%s: Failed disabling component, ret %d\n", + __func__, ret); + } + } + + /* + * Simply wait for any vb2 buffers to finish. We could take steps to + * make them complete more quickly if we care, or even return them + * ourselves. + */ + vb2_wait_for_all_buffers(&node->queue); +} + +static const struct vb2_ops bcm2835_isp_node_queue_ops = { + .queue_setup = bcm2835_isp_node_queue_setup, + .buf_init = bcm2835_isp_buf_init, + .buf_prepare = bcm2835_isp_buf_prepare, + .buf_queue = bcm2835_isp_node_buffer_queue, + .buf_cleanup = bcm2835_isp_buffer_cleanup, + .start_streaming = bcm2835_isp_node_start_streaming, + .stop_streaming = bcm2835_isp_node_stop_streaming, +}; + +static const +struct bcm2835_isp_fmt *get_default_format(struct bcm2835_isp_node *node) +{ + return node->supported_fmts[0]; +} + +static inline unsigned int get_bytesperline(int width, + const struct bcm2835_isp_fmt *fmt) +{ + /* GPU aligns 24bpp images to a multiple of 32 pixels (not bytes). */ + if (fmt->depth == 24) + return ALIGN(width, 32) * 3; + else + return ALIGN((width * fmt->depth) >> 3, fmt->bytesperline_align); +} + +static inline unsigned int get_sizeimage(int bpl, int width, int height, + const struct bcm2835_isp_fmt *fmt) +{ + return (bpl * height * fmt->size_multiplier_x2) >> 1; +} + +static int map_ls_table(struct bcm2835_isp_dev *dev, struct dma_buf *dmabuf, + const struct bcm2835_isp_lens_shading *v4l2_ls) +{ + void *vcsm_handle; + int ret; + + if (IS_ERR_OR_NULL(dmabuf)) + return -EINVAL; + + /* + * struct bcm2835_isp_lens_shading and struct + * mmal_parameter_lens_shading_v2 match so that we can do a + * simple memcpy here. + * Only the dmabuf to the actual table needs any manipulation. + */ + memcpy(&dev->ls, v4l2_ls, sizeof(dev->ls)); + ret = vc_sm_cma_import_dmabuf(dmabuf, &vcsm_handle); + if (ret) { + dma_buf_put(dmabuf); + return ret; + } + + dev->ls.mem_handle_table = vc_sm_cma_int_handle(vcsm_handle); + dev->last_ls_dmabuf = dmabuf; + + vc_sm_cma_free(vcsm_handle); + + return 0; +} + +static int bcm2835_isp_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct bcm2835_isp_dev *dev = + container_of(ctrl->handler, struct bcm2835_isp_dev, ctrl_handler); + struct bcm2835_isp_node *node = &dev->node[0]; + int ret = 0; + + /* + * The ISP firmware driver will ensure these settings are applied on + * a frame boundary, so we are safe to write them as they come in. + * + * Note that the bcm2835_isp_* param structures are identical to the + * mmal-parameters.h definitions. This avoids the need for unnecessary + * field-by-field copying between structures. + */ + switch (ctrl->id) { + case V4L2_CID_RED_BALANCE: + dev->r_gain = ctrl->val; + ret = set_wb_gains(node); + break; + case V4L2_CID_BLUE_BALANCE: + dev->b_gain = ctrl->val; + ret = set_wb_gains(node); + break; + case V4L2_CID_DIGITAL_GAIN: + ret = set_digital_gain(node, ctrl->val); + break; + case V4L2_CID_USER_BCM2835_ISP_CC_MATRIX: + ret = set_isp_param(node, MMAL_PARAMETER_CUSTOM_CCM, + ctrl->p_new.p_u8, + sizeof(struct bcm2835_isp_custom_ccm)); + break; + case V4L2_CID_USER_BCM2835_ISP_LENS_SHADING: + { + struct bcm2835_isp_lens_shading *v4l2_ls; + + v4l2_ls = (struct bcm2835_isp_lens_shading *)ctrl->p_new.p_u8; + struct dma_buf *dmabuf = dma_buf_get(v4l2_ls->dmabuf); + + if (dmabuf != dev->last_ls_dmabuf) + ret = map_ls_table(dev, dmabuf, v4l2_ls); + + if (!ret && dev->ls.mem_handle_table) + /* + * The VPU will take a reference on the vcsm handle, + * which in turn will retain a reference on the dmabuf. + * This code can therefore safely release all + * references to the buffer. + */ + ret = + set_isp_param(node, + MMAL_PARAMETER_LENS_SHADING_OVERRIDE, + &dev->ls, sizeof(dev->ls)); + else + ret = -EINVAL; + + dma_buf_put(dmabuf); + break; + } + case V4L2_CID_USER_BCM2835_ISP_BLACK_LEVEL: + ret = set_isp_param(node, MMAL_PARAMETER_BLACK_LEVEL, + ctrl->p_new.p_u8, + sizeof(struct bcm2835_isp_black_level)); + break; + case V4L2_CID_USER_BCM2835_ISP_GEQ: + ret = set_isp_param(node, MMAL_PARAMETER_GEQ, + ctrl->p_new.p_u8, + sizeof(struct bcm2835_isp_geq)); + break; + case V4L2_CID_USER_BCM2835_ISP_GAMMA: + ret = set_isp_param(node, MMAL_PARAMETER_GAMMA, + ctrl->p_new.p_u8, + sizeof(struct bcm2835_isp_gamma)); + break; + case V4L2_CID_USER_BCM2835_ISP_DENOISE: + ret = set_isp_param(node, MMAL_PARAMETER_DENOISE, + ctrl->p_new.p_u8, + sizeof(struct bcm2835_isp_denoise)); + break; + case V4L2_CID_USER_BCM2835_ISP_CDN: + ret = set_isp_param(node, MMAL_PARAMETER_CDN, + ctrl->p_new.p_u8, + sizeof(struct bcm2835_isp_cdn)); + break; + case V4L2_CID_USER_BCM2835_ISP_SHARPEN: + ret = set_isp_param(node, MMAL_PARAMETER_SHARPEN, + ctrl->p_new.p_u8, + sizeof(struct bcm2835_isp_sharpen)); + break; + case V4L2_CID_USER_BCM2835_ISP_DPC: + ret = set_isp_param(node, MMAL_PARAMETER_DPC, + ctrl->p_new.p_u8, + sizeof(struct bcm2835_isp_dpc)); + break; + default: + v4l2_info(&dev->v4l2_dev, "Unrecognised control\n"); + ret = -EINVAL; + } + + if (ret) { + v4l2_err(&dev->v4l2_dev, "%s: Failed setting ctrl \"%s\" (%08x), err %d\n", + __func__, ctrl->name, ctrl->id, ret); + ret = -EIO; + } + + return ret; +} + +static const struct v4l2_ctrl_ops bcm2835_isp_ctrl_ops = { + .s_ctrl = bcm2835_isp_s_ctrl, +}; + +static const struct v4l2_file_operations bcm2835_isp_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .poll = vb2_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap +}; + +static int populate_qdata_fmt(struct v4l2_format *f, + struct bcm2835_isp_node *node) +{ + struct bcm2835_isp_dev *dev = node_get_dev(node); + struct bcm2835_isp_q_data *q_data = &node->q_data; + int ret; + + if (!node_is_stats(node)) { + v4l2_dbg(1, debug, &dev->v4l2_dev, + "%s: Setting pix format for type %d, wxh: %ux%u, fmt: %08x, size %u\n", + __func__, f->type, f->fmt.pix.width, f->fmt.pix.height, + f->fmt.pix.pixelformat, f->fmt.pix.sizeimage); + + q_data->fmt = find_format(f, node); + q_data->width = f->fmt.pix.width; + q_data->height = f->fmt.pix.height; + q_data->height = f->fmt.pix.height; + + /* All parameters should have been set correctly by try_fmt */ + q_data->bytesperline = f->fmt.pix.bytesperline; + q_data->sizeimage = f->fmt.pix.sizeimage; + + /* We must indicate which of the allowed colour spaces we have. */ + q_data->colorspace = f->fmt.pix.colorspace; + } else { + v4l2_dbg(1, debug, &dev->v4l2_dev, + "%s: Setting meta format for fmt: %08x, size %u\n", + __func__, f->fmt.meta.dataformat, + f->fmt.meta.buffersize); + + q_data->fmt = find_format(f, node); + q_data->width = 0; + q_data->height = 0; + q_data->bytesperline = 0; + q_data->sizeimage = f->fmt.meta.buffersize; + + /* This won't mean anything for metadata, but may as well fill it in. */ + q_data->colorspace = V4L2_COLORSPACE_DEFAULT; + } + + v4l2_dbg(1, debug, &dev->v4l2_dev, + "%s: Calculated bpl as %u, size %u\n", __func__, + q_data->bytesperline, q_data->sizeimage); + + setup_mmal_port_format(node, node->port); + ret = vchiq_mmal_port_set_format(dev->mmal_instance, node->port); + if (ret) { + v4l2_err(&dev->v4l2_dev, + "%s: Failed vchiq_mmal_port_set_format on port, ret %d\n", + __func__, ret); + ret = -EINVAL; + } + + if (q_data->sizeimage < node->port->minimum_buffer.size) { + v4l2_err(&dev->v4l2_dev, + "%s: Current buffer size of %u < min buf size %u - driver mismatch to MMAL\n", + __func__, + q_data->sizeimage, + node->port->minimum_buffer.size); + } + + v4l2_dbg(1, debug, &dev->v4l2_dev, + "%s: Set format for type %d, wxh: %dx%d, fmt: %08x, size %u\n", + __func__, f->type, q_data->width, q_data->height, + q_data->fmt->fourcc, q_data->sizeimage); + + return ret; +} + +static int bcm2835_isp_node_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, BCM2835_ISP_NAME, sizeof(cap->driver)); + strscpy(cap->card, BCM2835_ISP_NAME, sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s", + BCM2835_ISP_NAME); + + return 0; +} + +static int bcm2835_isp_node_g_fmt(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct bcm2835_isp_node *node = video_drvdata(file); + + if (f->type != node->queue.type) + return -EINVAL; + + if (node_is_stats(node)) { + f->fmt.meta.dataformat = V4L2_META_FMT_BCM2835_ISP_STATS; + f->fmt.meta.buffersize = + node->port->minimum_buffer.size; + } else { + struct bcm2835_isp_q_data *q_data = &node->q_data; + + f->fmt.pix.width = q_data->width; + f->fmt.pix.height = q_data->height; + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.pixelformat = q_data->fmt->fourcc; + f->fmt.pix.bytesperline = q_data->bytesperline; + f->fmt.pix.sizeimage = q_data->sizeimage; + f->fmt.pix.colorspace = q_data->colorspace; + } + + return 0; +} + +static int bcm2835_isp_node_enum_fmt(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct bcm2835_isp_node *node = video_drvdata(file); + + if (f->type != node->queue.type) + return -EINVAL; + + if (f->index < node->num_supported_fmts) { + /* Format found */ + f->pixelformat = node->supported_fmts[f->index]->fourcc; + f->flags = 0; + return 0; + } + + return -EINVAL; +} + +static int bcm2835_isp_enum_framesizes(struct file *file, void *priv, + struct v4l2_frmsizeenum *fsize) +{ + struct bcm2835_isp_node *node = video_drvdata(file); + struct bcm2835_isp_dev *dev = node_get_dev(node); + const struct bcm2835_isp_fmt *fmt; + + if (node_is_stats(node) || fsize->index) + return -EINVAL; + + fmt = find_format_by_fourcc(fsize->pixel_format, node); + if (!fmt) { + v4l2_err(&dev->v4l2_dev, "Invalid pixel code: %x\n", + fsize->pixel_format); + return -EINVAL; + } + + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; + fsize->stepwise.min_width = MIN_DIM; + fsize->stepwise.max_width = MAX_DIM; + fsize->stepwise.step_width = fmt->step_size; + + fsize->stepwise.min_height = MIN_DIM; + fsize->stepwise.max_height = MAX_DIM; + fsize->stepwise.step_height = fmt->step_size; + + return 0; +} + +static int bcm2835_isp_node_try_fmt(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct bcm2835_isp_node *node = video_drvdata(file); + const struct bcm2835_isp_fmt *fmt; + + if (f->type != node->queue.type) + return -EINVAL; + + fmt = find_format(f, node); + if (!fmt) + fmt = get_default_format(node); + + if (!node_is_stats(node)) { + int is_rgb; + + f->fmt.pix.width = max(min(f->fmt.pix.width, MAX_DIM), + MIN_DIM); + f->fmt.pix.height = max(min(f->fmt.pix.height, MAX_DIM), + MIN_DIM); + + f->fmt.pix.pixelformat = fmt->fourcc; + + /* + * Fill in the actual colour space when the requested one was + * not supported. This also catches the case when the "default" + * colour space was requested (as that's never in the mask). + */ + if (!(V4L2_COLORSPACE_MASK(f->fmt.pix.colorspace) & fmt->colorspace_mask)) + f->fmt.pix.colorspace = fmt->colorspace_default; + /* In all cases, we only support the defaults for these: */ + f->fmt.pix.ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(f->fmt.pix.colorspace); + f->fmt.pix.xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(f->fmt.pix.colorspace); + /* RAW counts as sRGB here so that we get full range. */ + is_rgb = f->fmt.pix.colorspace == V4L2_COLORSPACE_SRGB || + f->fmt.pix.colorspace == V4L2_COLORSPACE_RAW; + f->fmt.pix.quantization = V4L2_MAP_QUANTIZATION_DEFAULT(is_rgb, f->fmt.pix.colorspace, + f->fmt.pix.ycbcr_enc); + + /* Respect any stride value (suitably aligned) that was requested. */ + f->fmt.pix.bytesperline = max(get_bytesperline(f->fmt.pix.width, fmt), + ALIGN(f->fmt.pix.bytesperline, + fmt->bytesperline_align)); + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.sizeimage = + get_sizeimage(f->fmt.pix.bytesperline, f->fmt.pix.width, + f->fmt.pix.height, fmt); + } else { + f->fmt.meta.dataformat = fmt->fourcc; + f->fmt.meta.buffersize = node->port->minimum_buffer.size; + } + + return 0; +} + +static int bcm2835_isp_node_s_fmt(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct bcm2835_isp_node *node = video_drvdata(file); + int ret; + + if (f->type != node->queue.type) + return -EINVAL; + + ret = bcm2835_isp_node_try_fmt(file, priv, f); + if (ret) + return ret; + + v4l2_dbg(1, debug, &node_get_dev(node)->v4l2_dev, + "%s: Set format for node %s[%d]\n", + __func__, node->name, node->id); + + return populate_qdata_fmt(f, node); +} + +static int bcm2835_isp_node_s_selection(struct file *file, void *fh, + struct v4l2_selection *s) +{ + struct mmal_parameter_crop crop; + struct bcm2835_isp_node *node = video_drvdata(file); + struct bcm2835_isp_dev *dev = node_get_dev(node); + + /* This return value is required fro V4L2 compliance. */ + if (node_is_stats(node)) + return -ENOTTY; + + if (!s->r.width || !s->r.height) + return -EINVAL; + + /* We can only set crop on the input. */ + switch (s->target) { + case V4L2_SEL_TGT_CROP: + /* + * Adjust the crop window if it goes outside of the frame + * dimensions. + */ + s->r.left = min((unsigned int)max(s->r.left, 0), + node->q_data.width - MIN_DIM); + s->r.top = min((unsigned int)max(s->r.top, 0), + node->q_data.height - MIN_DIM); + s->r.width = max(min(s->r.width, + node->q_data.width - s->r.left), MIN_DIM); + s->r.height = max(min(s->r.height, + node->q_data.height - s->r.top), MIN_DIM); + break; + case V4L2_SEL_TGT_CROP_DEFAULT: + /* Default (i.e. no) crop window. */ + s->r.left = 0; + s->r.top = 0; + s->r.width = node->q_data.width; + s->r.height = node->q_data.height; + break; + default: + return -EINVAL; + } + + crop.rect.x = s->r.left; + crop.rect.y = s->r.top; + crop.rect.width = s->r.width; + crop.rect.height = s->r.height; + + return vchiq_mmal_port_parameter_set(dev->mmal_instance, node->port, + MMAL_PARAMETER_CROP, + &crop, sizeof(crop)); +} + +static int bcm2835_isp_node_g_selection(struct file *file, void *fh, + struct v4l2_selection *s) +{ + struct mmal_parameter_crop crop; + struct bcm2835_isp_node *node = video_drvdata(file); + struct bcm2835_isp_dev *dev = node_get_dev(node); + u32 crop_size = sizeof(crop); + int ret; + + /* We can only return out an input crop. */ + switch (s->target) { + case V4L2_SEL_TGT_CROP: + ret = vchiq_mmal_port_parameter_get(dev->mmal_instance, + node->port, + MMAL_PARAMETER_CROP, + &crop, &crop_size); + if (!ret) { + s->r.left = crop.rect.x; + s->r.top = crop.rect.y; + s->r.width = crop.rect.width; + s->r.height = crop.rect.height; + } + break; + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + /* Default (i.e. no) crop window. */ + s->r.left = 0; + s->r.top = 0; + s->r.width = node->q_data.width; + s->r.height = node->q_data.height; + ret = 0; + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int bcm3285_isp_subscribe_event(struct v4l2_fh *fh, + const struct v4l2_event_subscription *s) +{ + switch (s->type) { + /* Cannot change source parameters dynamically at runtime. */ + case V4L2_EVENT_SOURCE_CHANGE: + return -EINVAL; + case V4L2_EVENT_CTRL: + return v4l2_ctrl_subscribe_event(fh, s); + default: + return v4l2_event_subscribe(fh, s, 4, NULL); + } +} + +static const struct v4l2_ioctl_ops bcm2835_isp_node_ioctl_ops = { + .vidioc_querycap = bcm2835_isp_node_querycap, + .vidioc_g_fmt_vid_cap = bcm2835_isp_node_g_fmt, + .vidioc_g_fmt_vid_out = bcm2835_isp_node_g_fmt, + .vidioc_g_fmt_meta_cap = bcm2835_isp_node_g_fmt, + .vidioc_s_fmt_vid_cap = bcm2835_isp_node_s_fmt, + .vidioc_s_fmt_vid_out = bcm2835_isp_node_s_fmt, + .vidioc_s_fmt_meta_cap = bcm2835_isp_node_s_fmt, + .vidioc_try_fmt_vid_cap = bcm2835_isp_node_try_fmt, + .vidioc_try_fmt_vid_out = bcm2835_isp_node_try_fmt, + .vidioc_try_fmt_meta_cap = bcm2835_isp_node_try_fmt, + .vidioc_s_selection = bcm2835_isp_node_s_selection, + .vidioc_g_selection = bcm2835_isp_node_g_selection, + + .vidioc_enum_fmt_vid_cap = bcm2835_isp_node_enum_fmt, + .vidioc_enum_fmt_vid_out = bcm2835_isp_node_enum_fmt, + .vidioc_enum_fmt_meta_cap = bcm2835_isp_node_enum_fmt, + .vidioc_enum_framesizes = bcm2835_isp_enum_framesizes, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + + .vidioc_subscribe_event = bcm3285_isp_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +/* + * Size of the array to provide to the VPU when asking for the list of supported + * formats. + * + * The ISP component currently advertises 62 input formats, so add a small + * overhead on that. Should the component advertise more formats then the excess + * will be dropped and a warning logged. + */ +#define MAX_SUPPORTED_ENCODINGS 70 + +/* Populate node->supported_fmts with the formats supported by those ports. */ +static int bcm2835_isp_get_supported_fmts(struct bcm2835_isp_node *node) +{ + struct bcm2835_isp_dev *dev = node_get_dev(node); + struct bcm2835_isp_fmt const **list; + unsigned int i, j, num_encodings; + u32 fourccs[MAX_SUPPORTED_ENCODINGS]; + u32 param_size = sizeof(fourccs); + int ret; + + ret = vchiq_mmal_port_parameter_get(dev->mmal_instance, node->port, + MMAL_PARAMETER_SUPPORTED_ENCODINGS, + &fourccs, ¶m_size); + + if (ret) { + if (ret == MMAL_MSG_STATUS_ENOSPC) { + v4l2_err(&dev->v4l2_dev, + "%s: port has more encodings than we provided space for. Some are dropped (%zu vs %u).\n", + __func__, param_size / sizeof(u32), + MAX_SUPPORTED_ENCODINGS); + num_encodings = MAX_SUPPORTED_ENCODINGS; + } else { + v4l2_err(&dev->v4l2_dev, "%s: get_param ret %u.\n", + __func__, ret); + return -EINVAL; + } + } else { + num_encodings = param_size / sizeof(u32); + } + + /* + * Assume at this stage that all encodings will be supported in V4L2. + * Any that aren't supported will waste a very small amount of memory. + */ + list = devm_kzalloc(dev->dev, + sizeof(struct bcm2835_isp_fmt *) * num_encodings, + GFP_KERNEL); + if (!list) + return -ENOMEM; + node->supported_fmts = list; + + for (i = 0, j = 0; i < num_encodings; i++) { + const struct bcm2835_isp_fmt *fmt = get_fmt(fourccs[i]); + + if (fmt) { + list[j] = fmt; + j++; + } + } + node->num_supported_fmts = j; + + return 0; +} + +/* + * Register a device node /dev/video<N> to go along with one of the ISP's input + * or output nodes. + */ +static int register_node(struct bcm2835_isp_dev *dev, + unsigned int instance, + struct bcm2835_isp_node *node, + int index) +{ + struct video_device *vfd; + struct vb2_queue *queue; + int ret; + + mutex_init(&node->lock); + mutex_init(&node->queue_lock); + + node->dev = dev; + vfd = &node->vfd; + queue = &node->queue; + queue->type = index_to_queue_type(index); + /* + * Setup the node type-specific params. + * + * Only the OUTPUT node can set controls and crop windows. However, + * we must allow the s/g_selection ioctl on the stats node as v4l2 + * compliance expects it to return a -ENOTTY, and the framework + * does not handle it if the ioctl is disabled. + */ + switch (queue->type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + vfd->device_caps = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING; + node->id = index; + node->vfl_dir = VFL_DIR_TX; + node->name = "output"; + node->port = &dev->component->input[node->id]; + break; + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + vfd->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; + /* First Capture node starts at id 0, etc. */ + node->id = index - BCM2835_ISP_NUM_OUTPUTS; + node->vfl_dir = VFL_DIR_RX; + node->name = "capture"; + node->port = &dev->component->output[node->id]; + v4l2_disable_ioctl(&node->vfd, VIDIOC_S_CTRL); + v4l2_disable_ioctl(&node->vfd, VIDIOC_S_SELECTION); + v4l2_disable_ioctl(&node->vfd, VIDIOC_G_SELECTION); + break; + case V4L2_BUF_TYPE_META_CAPTURE: + vfd->device_caps = V4L2_CAP_META_CAPTURE | V4L2_CAP_STREAMING; + node->id = index - BCM2835_ISP_NUM_OUTPUTS; + node->vfl_dir = VFL_DIR_RX; + node->name = "stats"; + node->port = &dev->component->output[node->id]; + v4l2_disable_ioctl(&node->vfd, VIDIOC_S_CTRL); + v4l2_disable_ioctl(&node->vfd, VIDIOC_S_SELECTION); + v4l2_disable_ioctl(&node->vfd, VIDIOC_G_SELECTION); + break; + } + + /* We use the selection API instead of the old crop API. */ + v4l2_disable_ioctl(vfd, VIDIOC_CROPCAP); + v4l2_disable_ioctl(vfd, VIDIOC_G_CROP); + v4l2_disable_ioctl(vfd, VIDIOC_S_CROP); + + ret = bcm2835_isp_get_supported_fmts(node); + if (ret) + return ret; + + /* Initialise the video node. */ + vfd->vfl_type = VFL_TYPE_VIDEO; + vfd->fops = &bcm2835_isp_fops, + vfd->ioctl_ops = &bcm2835_isp_node_ioctl_ops, + vfd->minor = -1, + vfd->release = video_device_release_empty, + vfd->queue = &node->queue; + vfd->lock = &node->lock; + vfd->v4l2_dev = &dev->v4l2_dev; + vfd->vfl_dir = node->vfl_dir; + + node->q_data.fmt = get_default_format(node); + node->q_data.width = DEFAULT_DIM; + node->q_data.height = DEFAULT_DIM; + node->q_data.bytesperline = + get_bytesperline(DEFAULT_DIM, node->q_data.fmt); + node->q_data.sizeimage = node_is_stats(node) ? + node->port->recommended_buffer.size : + get_sizeimage(node->q_data.bytesperline, + node->q_data.width, + node->q_data.height, + node->q_data.fmt); + node->q_data.colorspace = node->q_data.fmt->colorspace_default; + + queue->io_modes = VB2_MMAP | VB2_DMABUF; + queue->drv_priv = node; + queue->ops = &bcm2835_isp_node_queue_ops; + queue->mem_ops = &vb2_dma_contig_memops; + queue->buf_struct_size = sizeof(struct bcm2835_isp_buffer); + queue->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + queue->dev = dev->dev; + queue->lock = &node->queue_lock; + + ret = vb2_queue_init(queue); + if (ret < 0) { + v4l2_info(&dev->v4l2_dev, "vb2_queue_init failed\n"); + return ret; + } + + /* Set some controls and defaults, but only on the VIDEO_OUTPUT node. */ + if (node_is_output(node)) { + unsigned int i; + + /* Use this ctrl template to assign custom ISP ctrls. */ + struct v4l2_ctrl_config ctrl_template = { + .ops = &bcm2835_isp_ctrl_ops, + .type = V4L2_CTRL_TYPE_U8, + .def = 0, + .min = 0x00, + .max = 0xff, + .step = 1, + }; + + /* 3 standard controls, and an array of custom controls */ + ret = v4l2_ctrl_handler_init(&dev->ctrl_handler, + 3 + ARRAY_SIZE(custom_ctrls)); + if (ret) { + v4l2_err(&dev->v4l2_dev, "ctrl_handler init failed (%d)\n", + ret); + goto queue_cleanup; + } + + dev->r_gain = 1000; + dev->b_gain = 1000; + + v4l2_ctrl_new_std(&dev->ctrl_handler, &bcm2835_isp_ctrl_ops, + V4L2_CID_RED_BALANCE, 1, 0xffff, 1, + dev->r_gain); + + v4l2_ctrl_new_std(&dev->ctrl_handler, &bcm2835_isp_ctrl_ops, + V4L2_CID_BLUE_BALANCE, 1, 0xffff, 1, + dev->b_gain); + + v4l2_ctrl_new_std(&dev->ctrl_handler, &bcm2835_isp_ctrl_ops, + V4L2_CID_DIGITAL_GAIN, 1, 0xffff, 1, 1000); + + for (i = 0; i < ARRAY_SIZE(custom_ctrls); i++) { + ctrl_template.name = custom_ctrls[i].name; + ctrl_template.id = custom_ctrls[i].id; + ctrl_template.dims[0] = custom_ctrls[i].size; + ctrl_template.flags = custom_ctrls[i].flags; + v4l2_ctrl_new_custom(&dev->ctrl_handler, + &ctrl_template, NULL); + } + + node->vfd.ctrl_handler = &dev->ctrl_handler; + if (dev->ctrl_handler.error) { + ret = dev->ctrl_handler.error; + v4l2_err(&dev->v4l2_dev, "controls init failed (%d)\n", + ret); + v4l2_ctrl_handler_free(&dev->ctrl_handler); + goto ctrl_cleanup; + } + } + + /* Define the device names */ + snprintf(vfd->name, sizeof(node->vfd.name), "%s-%s%d", BCM2835_ISP_NAME, + node->name, node->id); + + ret = video_register_device(vfd, VFL_TYPE_VIDEO, video_nr[instance]); + if (ret) { + v4l2_err(&dev->v4l2_dev, + "Failed to register video %s[%d] device node\n", + node->name, node->id); + goto ctrl_cleanup; + } + + node->registered = true; + video_set_drvdata(vfd, node); + + v4l2_info(&dev->v4l2_dev, + "Device node %s[%d] registered as /dev/video%d\n", + node->name, node->id, vfd->num); + + return 0; + +ctrl_cleanup: + if (node_is_output(node)) + v4l2_ctrl_handler_free(&dev->ctrl_handler); +queue_cleanup: + vb2_queue_release(&node->queue); + return ret; +} + +/* Unregister one of the /dev/video<N> nodes associated with the ISP. */ +static void bcm2835_unregister_node(struct bcm2835_isp_node *node) +{ + struct bcm2835_isp_dev *dev = node_get_dev(node); + + v4l2_info(&dev->v4l2_dev, + "Unregistering node %s[%d] device node /dev/video%d\n", + node->name, node->id, node->vfd.num); + + if (node->registered) { + video_unregister_device(&node->vfd); + if (node_is_output(node)) + v4l2_ctrl_handler_free(&dev->ctrl_handler); + vb2_queue_release(&node->queue); + } + + /* + * node->supported_fmts.list is free'd automatically + * as a managed resource. + */ + node->supported_fmts = NULL; + node->num_supported_fmts = 0; + node->vfd.ctrl_handler = NULL; + node->registered = false; +} + +static void media_controller_unregister(struct bcm2835_isp_dev *dev) +{ + unsigned int i; + + v4l2_info(&dev->v4l2_dev, "Unregister from media controller\n"); + + if (dev->media_device_registered) { + media_device_unregister(&dev->mdev); + media_device_cleanup(&dev->mdev); + dev->media_device_registered = false; + } + + kfree(dev->entity.name); + dev->entity.name = NULL; + + if (dev->media_entity_registered) { + media_device_unregister_entity(&dev->entity); + dev->media_entity_registered = false; + } + + for (i = 0; i < BCM2835_ISP_NUM_NODES; i++) { + struct bcm2835_isp_node *node = &dev->node[i]; + + if (node->media_node_registered) { + media_remove_intf_links(node->intf_link->intf); + media_entity_remove_links(&dev->node[i].vfd.entity); + media_devnode_remove(node->intf_devnode); + media_device_unregister_entity(&node->vfd.entity); + kfree(node->vfd.entity.name); + } + node->media_node_registered = false; + } + + dev->v4l2_dev.mdev = NULL; +} + +static int media_controller_register_node(struct bcm2835_isp_dev *dev, int num) +{ + struct bcm2835_isp_node *node = &dev->node[num]; + struct media_entity *entity = &node->vfd.entity; + int output = node_is_output(node); + char *name; + int ret; + + v4l2_info(&dev->v4l2_dev, + "Register %s node %d with media controller\n", + output ? "output" : "capture", num); + entity->obj_type = MEDIA_ENTITY_TYPE_VIDEO_DEVICE; + entity->function = MEDIA_ENT_F_IO_V4L; + entity->info.dev.major = VIDEO_MAJOR; + entity->info.dev.minor = node->vfd.minor; + name = kmalloc(BCM2835_ISP_ENTITY_NAME_LEN, GFP_KERNEL); + if (!name) { + ret = -ENOMEM; + goto error_no_mem; + } + snprintf(name, BCM2835_ISP_ENTITY_NAME_LEN, "%s0-%s%d", + BCM2835_ISP_NAME, output ? "output" : "capture", num); + entity->name = name; + node->pad.flags = output ? MEDIA_PAD_FL_SOURCE : MEDIA_PAD_FL_SINK; + ret = media_entity_pads_init(entity, 1, &node->pad); + if (ret) + goto error_pads_init; + ret = media_device_register_entity(&dev->mdev, entity); + if (ret) + goto error_register_entity; + + node->intf_devnode = media_devnode_create(&dev->mdev, + MEDIA_INTF_T_V4L_VIDEO, 0, + VIDEO_MAJOR, node->vfd.minor); + if (!node->intf_devnode) { + ret = -ENOMEM; + goto error_devnode_create; + } + + node->intf_link = media_create_intf_link(entity, + &node->intf_devnode->intf, + MEDIA_LNK_FL_IMMUTABLE | + MEDIA_LNK_FL_ENABLED); + if (!node->intf_link) { + ret = -ENOMEM; + goto error_create_intf_link; + } + + if (output) + ret = media_create_pad_link(entity, 0, &dev->entity, num, + MEDIA_LNK_FL_IMMUTABLE | + MEDIA_LNK_FL_ENABLED); + else + ret = media_create_pad_link(&dev->entity, num, entity, 0, + MEDIA_LNK_FL_IMMUTABLE | + MEDIA_LNK_FL_ENABLED); + if (ret) + goto error_create_pad_link; + + dev->node[num].media_node_registered = true; + return 0; + +error_create_pad_link: + media_remove_intf_links(&node->intf_devnode->intf); +error_create_intf_link: + media_devnode_remove(node->intf_devnode); +error_devnode_create: + media_device_unregister_entity(&node->vfd.entity); +error_register_entity: +error_pads_init: + kfree(entity->name); + entity->name = NULL; +error_no_mem: + if (ret) + v4l2_info(&dev->v4l2_dev, "Error registering node\n"); + + return ret; +} + +static int media_controller_register(struct bcm2835_isp_dev *dev) +{ + char *name; + unsigned int i; + int ret; + + v4l2_dbg(2, debug, &dev->v4l2_dev, "Registering with media controller\n"); + dev->mdev.dev = dev->dev; + strscpy(dev->mdev.model, "bcm2835-isp", + sizeof(dev->mdev.model)); + strscpy(dev->mdev.bus_info, "platform:bcm2835-isp", + sizeof(dev->mdev.bus_info)); + media_device_init(&dev->mdev); + dev->v4l2_dev.mdev = &dev->mdev; + + v4l2_dbg(2, debug, &dev->v4l2_dev, "Register entity for nodes\n"); + + name = kmalloc(BCM2835_ISP_ENTITY_NAME_LEN, GFP_KERNEL); + if (!name) { + ret = -ENOMEM; + goto done; + } + snprintf(name, BCM2835_ISP_ENTITY_NAME_LEN, "bcm2835_isp0"); + dev->entity.name = name; + dev->entity.obj_type = MEDIA_ENTITY_TYPE_BASE; + dev->entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER; + + for (i = 0; i < BCM2835_ISP_NUM_NODES; i++) { + dev->pad[i].flags = node_is_output(&dev->node[i]) ? + MEDIA_PAD_FL_SINK : MEDIA_PAD_FL_SOURCE; + } + + ret = media_entity_pads_init(&dev->entity, BCM2835_ISP_NUM_NODES, + dev->pad); + if (ret) + goto done; + + ret = media_device_register_entity(&dev->mdev, &dev->entity); + if (ret) + goto done; + + dev->media_entity_registered = true; + for (i = 0; i < BCM2835_ISP_NUM_NODES; i++) { + ret = media_controller_register_node(dev, i); + if (ret) + goto done; + } + + ret = media_device_register(&dev->mdev); + if (!ret) + dev->media_device_registered = true; +done: + return ret; +} + +static void bcm2835_isp_remove_instance(struct bcm2835_isp_dev *dev) +{ + unsigned int i; + + media_controller_unregister(dev); + + for (i = 0; i < BCM2835_ISP_NUM_NODES; i++) + bcm2835_unregister_node(&dev->node[i]); + + v4l2_device_unregister(&dev->v4l2_dev); + + if (dev->component) + vchiq_mmal_component_finalise(dev->mmal_instance, + dev->component); + + vchiq_mmal_finalise(dev->mmal_instance); +} + +static int bcm2835_isp_probe_instance(struct vchiq_device *device, + struct bcm2835_isp_dev **dev_int, + unsigned int instance) +{ + struct bcm2835_isp_dev *dev; + unsigned int i; + int ret; + + dev = devm_kzalloc(&device->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + *dev_int = dev; + dev->dev = &device->dev; + + ret = v4l2_device_register(&device->dev, &dev->v4l2_dev); + if (ret) + return ret; + + ret = vchiq_mmal_init(&device->dev, &dev->mmal_instance); + if (ret) { + v4l2_device_unregister(&dev->v4l2_dev); + return ret; + } + + ret = vchiq_mmal_component_init(dev->mmal_instance, "ril.isp", + &dev->component); + if (ret) { + v4l2_err(&dev->v4l2_dev, + "%s: failed to create ril.isp component\n", __func__); + return ret; + } + + if (dev->component->inputs < BCM2835_ISP_NUM_OUTPUTS || + dev->component->outputs < BCM2835_ISP_NUM_CAPTURES + + BCM2835_ISP_NUM_METADATA) { + v4l2_err(&dev->v4l2_dev, + "%s: ril.isp returned %d i/p (%d expected), %d o/p (%d expected) ports\n", + __func__, dev->component->inputs, + BCM2835_ISP_NUM_OUTPUTS, + dev->component->outputs, + BCM2835_ISP_NUM_CAPTURES + BCM2835_ISP_NUM_METADATA); + return -EINVAL; + } + + atomic_set(&dev->num_streaming, 0); + + for (i = 0; i < BCM2835_ISP_NUM_NODES; i++) { + struct bcm2835_isp_node *node = &dev->node[i]; + + ret = register_node(dev, instance, node, i); + if (ret) + return ret; + } + + ret = media_controller_register(dev); + if (ret) + return ret; + + return 0; +} + +static void bcm2835_isp_remove(struct vchiq_device *device) +{ + struct bcm2835_isp_dev **bcm2835_isp_instances; + unsigned int i; + + bcm2835_isp_instances = vchiq_get_drvdata(device); + for (i = 0; i < BCM2835_ISP_NUM_INSTANCES; i++) { + if (bcm2835_isp_instances[i]) + bcm2835_isp_remove_instance(bcm2835_isp_instances[i]); + } +} + +static int bcm2835_isp_probe(struct vchiq_device *device) +{ + struct bcm2835_isp_dev **bcm2835_isp_instances; + unsigned int i; + int ret; + + ret = dma_set_mask_and_coherent(&device->dev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(&device->dev, "dma_set_mask_and_coherent failed: %d\n", + ret); + return ret; + } + + bcm2835_isp_instances = devm_kzalloc(&device->dev, + sizeof(bcm2835_isp_instances) * + BCM2835_ISP_NUM_INSTANCES, + GFP_KERNEL); + if (!bcm2835_isp_instances) + return -ENOMEM; + + vchiq_set_drvdata(device, bcm2835_isp_instances); + + for (i = 0; i < BCM2835_ISP_NUM_INSTANCES; i++) { + ret = bcm2835_isp_probe_instance(device, + &bcm2835_isp_instances[i], i); + if (ret) + goto error; + } + + dev_info(&device->dev, "Loaded V4L2 %s\n", BCM2835_ISP_NAME); + return 0; + +error: + bcm2835_isp_remove(device); + + return ret; +} + +static struct vchiq_driver bcm2835_isp_drv = { + .probe = bcm2835_isp_probe, + .remove = bcm2835_isp_remove, + .driver = { + .name = BCM2835_ISP_NAME, + }, +}; + +module_vchiq_driver(bcm2835_isp_drv); + +MODULE_DESCRIPTION("BCM2835 ISP driver"); +MODULE_AUTHOR("Naushir Patuck <naush@raspberrypi.com>"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("vchiq:bcm2835-isp"); diff --git a/drivers/staging/vc04_services/include/linux/broadcom/vc_sm_cma_ioctl.h b/drivers/staging/vc04_services/include/linux/broadcom/vc_sm_cma_ioctl.h new file mode 100644 index 00000000000000..107460ad1be34e --- /dev/null +++ b/drivers/staging/vc04_services/include/linux/broadcom/vc_sm_cma_ioctl.h @@ -0,0 +1,114 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* + * Copyright 2019 Raspberry Pi (Trading) Ltd. All rights reserved. + * + * Based on vmcs_sm_ioctl.h Copyright Broadcom Corporation. + */ + +#ifndef __VC_SM_CMA_IOCTL_H +#define __VC_SM_CMA_IOCTL_H + +/* ---- Include Files ---------------------------------------------------- */ + +#if defined(__KERNEL__) +#include <linux/types.h> /* Needed for standard types */ +#else +#include <stdint.h> +#endif + +#include <linux/ioctl.h> + +/* ---- Constants and Types ---------------------------------------------- */ + +#define VC_SM_CMA_RESOURCE_NAME 32 +#define VC_SM_CMA_RESOURCE_NAME_DEFAULT "sm-host-resource" + +/* Type define used to create unique IOCTL number */ +#define VC_SM_CMA_MAGIC_TYPE 'J' + +/* IOCTL commands on /dev/vc-sm-cma */ +enum vc_sm_cma_cmd_e { + VC_SM_CMA_CMD_ALLOC = 0x5A, /* Start at 0x5A arbitrarily */ + + VC_SM_CMA_CMD_IMPORT_DMABUF, + + VC_SM_CMA_CMD_CLEAN_INVALID2, + + VC_SM_CMA_CMD_LAST /* Do not delete */ +}; + +/* Cache type supported, conveniently matches the user space definition in + * user-vcsm.h. + */ +enum vc_sm_cma_cache_e { + VC_SM_CMA_CACHE_NONE, + VC_SM_CMA_CACHE_HOST, + VC_SM_CMA_CACHE_VC, + VC_SM_CMA_CACHE_BOTH, +}; + +/* IOCTL Data structures */ +struct vc_sm_cma_ioctl_alloc { + /* user -> kernel */ + __u32 size; + __u32 num; + __u32 cached; /* enum vc_sm_cma_cache_e */ + __u32 pad; + __u8 name[VC_SM_CMA_RESOURCE_NAME]; + + /* kernel -> user */ + __s32 handle; + __u32 vc_handle; + __u64 dma_addr; +}; + +struct vc_sm_cma_ioctl_import_dmabuf { + /* user -> kernel */ + __s32 dmabuf_fd; + __u32 cached; /* enum vc_sm_cma_cache_e */ + __u8 name[VC_SM_CMA_RESOURCE_NAME]; + + /* kernel -> user */ + __s32 handle; + __u32 vc_handle; + __u32 size; + __u32 pad; + __u64 dma_addr; +}; + +/* + * Cache functions to be set to struct vc_sm_cma_ioctl_clean_invalid2 + * invalidate_mode. + */ +#define VC_SM_CACHE_OP_NOP 0x00 +#define VC_SM_CACHE_OP_INV 0x01 +#define VC_SM_CACHE_OP_CLEAN 0x02 +#define VC_SM_CACHE_OP_FLUSH 0x03 + +struct vc_sm_cma_ioctl_clean_invalid2 { + __u32 op_count; + __u32 pad; + struct vc_sm_cma_ioctl_clean_invalid_block { + __u32 invalidate_mode; + __u32 block_count; + void * __user start_address; + __u32 block_size; + __u32 inter_block_stride; + } s[0]; +}; + +/* IOCTL numbers */ +#define VC_SM_CMA_IOCTL_MEM_ALLOC\ + _IOR(VC_SM_CMA_MAGIC_TYPE, VC_SM_CMA_CMD_ALLOC,\ + struct vc_sm_cma_ioctl_alloc) + +#define VC_SM_CMA_IOCTL_MEM_IMPORT_DMABUF\ + _IOR(VC_SM_CMA_MAGIC_TYPE, VC_SM_CMA_CMD_IMPORT_DMABUF,\ + struct vc_sm_cma_ioctl_import_dmabuf) + +#define VC_SM_CMA_IOCTL_MEM_CLEAN_INVALID2\ + _IOR(VC_SM_CMA_MAGIC_TYPE, VC_SM_CMA_CMD_CLEAN_INVALID2,\ + struct vc_sm_cma_ioctl_clean_invalid2) + +#endif /* __VC_SM_CMA_IOCTL_H */ diff --git a/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.c b/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.c index 5fab33adf58ed0..b0c0f000dacfcb 100644 --- a/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.c +++ b/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.c @@ -20,9 +20,11 @@ #include <linux/completion.h> #include <linux/list.h> #include <linux/of.h> +#include <linux/of_platform.h> #include <linux/platform_device.h> #include <linux/compat.h> #include <linux/dma-mapping.h> +#include <linux/dmapool.h> #include <linux/rcupdate.h> #include <linux/delay.h> #include <linux/slab.h> @@ -52,6 +54,8 @@ #define ARM_DS_ACTIVE BIT(2) +#define VCHIQ_DMA_POOL_SIZE PAGE_SIZE + /* Override the default prefix, which would be vchiq_arm (from the filename) */ #undef MODULE_PARAM_PREFIX #define MODULE_PARAM_PREFIX DEVICE_NAME "." @@ -66,6 +70,9 @@ */ static struct vchiq_device *bcm2835_audio; static struct vchiq_device *bcm2835_camera; +static struct vchiq_device *bcm2835_codec; +static struct vchiq_device *bcm2835_isp; +static struct vchiq_device *vcsm_cma; static const struct vchiq_platform_info bcm2835_info = { .cache_line_size = 32, @@ -75,6 +82,11 @@ static const struct vchiq_platform_info bcm2836_info = { .cache_line_size = 64, }; +static const struct vchiq_platform_info bcm2711_info = { + .cache_line_size = 64, + .use_36bit_addrs = true, +}; + struct vchiq_arm_state { /* Keepalive-related data */ struct task_struct *ka_thread; @@ -113,6 +125,7 @@ struct vchiq_pagelist_info { struct pagelist *pagelist; size_t pagelist_buffer_size; dma_addr_t dma_addr; + bool is_from_pool; enum dma_data_direction dma_dir; unsigned int num_pages; unsigned int pages_need_release; @@ -121,6 +134,10 @@ struct vchiq_pagelist_info { unsigned int scatterlist_mapped; }; +static struct dma_pool *g_dma_pool; +static unsigned int g_use_36bit_addrs = 0; +static struct device *g_dma_dev; + static int vchiq_blocking_bulk_transfer(struct vchiq_instance *instance, unsigned int handle, void *data, unsigned int size, enum vchiq_bulk_dir dir); @@ -150,15 +167,20 @@ static void cleanup_pagelistinfo(struct vchiq_instance *instance, struct vchiq_pagelist_info *pagelistinfo) { if (pagelistinfo->scatterlist_mapped) { - dma_unmap_sg(instance->state->dev, pagelistinfo->scatterlist, + dma_unmap_sg(g_dma_dev, pagelistinfo->scatterlist, pagelistinfo->num_pages, pagelistinfo->dma_dir); } if (pagelistinfo->pages_need_release) unpin_user_pages(pagelistinfo->pages, pagelistinfo->num_pages); - dma_free_coherent(instance->state->dev, pagelistinfo->pagelist_buffer_size, - pagelistinfo->pagelist, pagelistinfo->dma_addr); + if (pagelistinfo->is_from_pool) { + dma_pool_free(g_dma_pool, pagelistinfo->pagelist, + pagelistinfo->dma_addr); + } else { + dma_free_coherent(instance->state->dev, pagelistinfo->pagelist_buffer_size, + pagelistinfo->pagelist, pagelistinfo->dma_addr); + } } static inline bool @@ -244,6 +266,7 @@ create_pagelist(struct vchiq_instance *instance, char *buf, char __user *ubuf, u32 *addrs; unsigned int num_pages, offset, i, k; int actual_pages; + bool is_from_pool; size_t pagelist_size; struct scatterlist *scatterlist, *sg; int dma_buffers; @@ -275,8 +298,14 @@ create_pagelist(struct vchiq_instance *instance, char *buf, char __user *ubuf, /* Allocate enough storage to hold the page pointers and the page * list */ - pagelist = dma_alloc_coherent(instance->state->dev, pagelist_size, &dma_addr, - GFP_KERNEL); + if (pagelist_size > VCHIQ_DMA_POOL_SIZE) { + pagelist = dma_alloc_coherent(instance->state->dev, pagelist_size, &dma_addr, + GFP_KERNEL); + is_from_pool = false; + } else { + pagelist = dma_pool_alloc(g_dma_pool, GFP_KERNEL, &dma_addr); + is_from_pool = true; + } dev_dbg(instance->state->dev, "arm: %pK\n", pagelist); @@ -297,6 +326,7 @@ create_pagelist(struct vchiq_instance *instance, char *buf, char __user *ubuf, pagelistinfo->pagelist = pagelist; pagelistinfo->pagelist_buffer_size = pagelist_size; pagelistinfo->dma_addr = dma_addr; + pagelistinfo->is_from_pool = is_from_pool; pagelistinfo->dma_dir = (type == PAGELIST_WRITE) ? DMA_TO_DEVICE : DMA_FROM_DEVICE; pagelistinfo->num_pages = num_pages; @@ -362,7 +392,7 @@ create_pagelist(struct vchiq_instance *instance, char *buf, char __user *ubuf, count -= len; } - dma_buffers = dma_map_sg(instance->state->dev, + dma_buffers = dma_map_sg(g_dma_dev, scatterlist, num_pages, pagelistinfo->dma_dir); @@ -376,22 +406,58 @@ create_pagelist(struct vchiq_instance *instance, char *buf, char __user *ubuf, /* Combine adjacent blocks for performance */ k = 0; - for_each_sg(scatterlist, sg, dma_buffers, i) { - unsigned int len = sg_dma_len(sg); - dma_addr_t addr = sg_dma_address(sg); - - /* Note: addrs is the address + page_count - 1 - * The firmware expects blocks after the first to be page- - * aligned and a multiple of the page size - */ - WARN_ON(len == 0); - WARN_ON(i && (i != (dma_buffers - 1)) && (len & ~PAGE_MASK)); - WARN_ON(i && (addr & ~PAGE_MASK)); - if (is_adjacent_block(addrs, addr, k)) - addrs[k - 1] += ((len + PAGE_SIZE - 1) >> PAGE_SHIFT); - else - addrs[k++] = (addr & PAGE_MASK) | - (((len + PAGE_SIZE - 1) >> PAGE_SHIFT) - 1); + if (g_use_36bit_addrs) { + for_each_sg(scatterlist, sg, dma_buffers, i) { + unsigned int len = sg_dma_len(sg); + dma_addr_t addr = sg_dma_address(sg); + u32 page_id = (u32)((addr >> 4) & ~0xff); + u32 sg_pages = (len + PAGE_SIZE - 1) >> PAGE_SHIFT; + + /* Note: addrs is the address + page_count - 1 + * The firmware expects blocks after the first to be page- + * aligned and a multiple of the page size + */ + WARN_ON(len == 0); + WARN_ON(i && + (i != (dma_buffers - 1)) && (len & ~PAGE_MASK)); + WARN_ON(i && (addr & ~PAGE_MASK)); + WARN_ON(upper_32_bits(addr) > 0xf); + + if (k > 0 && + ((addrs[k - 1] & ~0xff) + + (((addrs[k - 1] & 0xff) + 1) << 8) + == page_id)) { + u32 inc_pages = min(sg_pages, + 0xff - (addrs[k - 1] & 0xff)); + addrs[k - 1] += inc_pages; + page_id += inc_pages << 8; + sg_pages -= inc_pages; + } + while (sg_pages) { + u32 inc_pages = min(sg_pages, 0x100u); + addrs[k++] = page_id | (inc_pages - 1); + page_id += inc_pages << 8; + sg_pages -= inc_pages; + } + } + } else { + for_each_sg(scatterlist, sg, dma_buffers, i) { + unsigned int len = sg_dma_len(sg); + dma_addr_t addr = sg_dma_address(sg); + + /* Note: addrs is the address + page_count - 1 + * The firmware expects blocks after the first to be page- + * aligned and a multiple of the page size + */ + WARN_ON(len == 0); + WARN_ON(i && (i != (dma_buffers - 1)) && (len & ~PAGE_MASK)); + WARN_ON(i && (addr & ~PAGE_MASK)); + if (is_adjacent_block(addrs, addr, k)) + addrs[k - 1] += ((len + PAGE_SIZE - 1) >> PAGE_SHIFT); + else + addrs[k++] = (addr & PAGE_MASK) | + (((len + PAGE_SIZE - 1) >> PAGE_SHIFT) - 1); + } } /* Partial cache lines (fragments) require special measures */ @@ -437,7 +503,7 @@ free_pagelist(struct vchiq_instance *instance, struct vchiq_pagelist_info *pagel * NOTE: dma_unmap_sg must be called before the * cpu can touch any of the data/pages. */ - dma_unmap_sg(instance->state->dev, pagelistinfo->scatterlist, + dma_unmap_sg(g_dma_dev, pagelistinfo->scatterlist, pagelistinfo->num_pages, pagelistinfo->dma_dir); pagelistinfo->scatterlist_mapped = 0; @@ -492,6 +558,7 @@ free_pagelist(struct vchiq_instance *instance, struct vchiq_pagelist_info *pagel static int vchiq_platform_init(struct platform_device *pdev, struct vchiq_state *state) { struct device *dev = &pdev->dev; + struct device *dma_dev = NULL; struct vchiq_drv_mgmt *drv_mgmt = platform_get_drvdata(pdev); struct rpi_firmware *fw = drv_mgmt->fw; struct vchiq_slot_zero *vchiq_slot_zero; @@ -512,6 +579,24 @@ static int vchiq_platform_init(struct platform_device *pdev, struct vchiq_state drv_mgmt->fragments_size = 2 * drv_mgmt->info->cache_line_size; + if (drv_mgmt->info->use_36bit_addrs) { + struct device_node *dma_node = + of_find_compatible_node(NULL, NULL, "brcm,bcm2711-dma"); + + if (dma_node) { + struct platform_device *pdev; + + pdev = of_find_device_by_node(dma_node); + if (pdev) + dma_dev = &pdev->dev; + of_node_put(dma_node); + g_use_36bit_addrs = true; + } else { + dev_err(dev, "40-bit DMA controller not found\n"); + return -EINVAL; + } + } + /* Allocate space for the channels in coherent memory */ slot_mem_size = PAGE_ALIGN(TOTAL_SLOTS * VCHIQ_SLOT_SIZE); frag_mem_size = PAGE_ALIGN(drv_mgmt->fragments_size * MAX_FRAGMENTS); @@ -579,6 +664,15 @@ static int vchiq_platform_init(struct platform_device *pdev, struct vchiq_state return -ENXIO; } + g_dma_dev = dma_dev ?: dev; + g_dma_pool = dmam_pool_create("vchiq_scatter_pool", dev, + VCHIQ_DMA_POOL_SIZE, + drv_mgmt->info->cache_line_size, 0); + if (!g_dma_pool) { + dev_err(dev, "failed to create dma pool"); + return -ENOMEM; + } + dev_dbg(&pdev->dev, "arm: vchiq_init - done (slots %pK, phys %pad)\n", vchiq_slot_zero, &slot_phys); @@ -1709,6 +1803,7 @@ void vchiq_platform_conn_state_changed(struct vchiq_state *state, static const struct of_device_id vchiq_of_match[] = { { .compatible = "brcm,bcm2835-vchiq", .data = &bcm2835_info }, { .compatible = "brcm,bcm2836-vchiq", .data = &bcm2836_info }, + { .compatible = "brcm,bcm2711-vchiq", .data = &bcm2711_info }, {}, }; MODULE_DEVICE_TABLE(of, vchiq_of_match); @@ -1760,8 +1855,11 @@ static int vchiq_probe(struct platform_device *pdev) goto error_exit; } + vcsm_cma = vchiq_device_register(&pdev->dev, "vcsm-cma"); + bcm2835_codec = vchiq_device_register(&pdev->dev, "bcm2835-codec"); bcm2835_audio = vchiq_device_register(&pdev->dev, "bcm2835-audio"); bcm2835_camera = vchiq_device_register(&pdev->dev, "bcm2835-camera"); + bcm2835_isp = vchiq_device_register(&pdev->dev, "bcm2835-isp"); return 0; @@ -1776,8 +1874,11 @@ static void vchiq_remove(struct platform_device *pdev) struct vchiq_drv_mgmt *mgmt = dev_get_drvdata(&pdev->dev); struct vchiq_arm_state *arm_state; + vchiq_device_unregister(bcm2835_isp); vchiq_device_unregister(bcm2835_audio); vchiq_device_unregister(bcm2835_camera); + vchiq_device_unregister(bcm2835_codec); + vchiq_device_unregister(vcsm_cma); vchiq_debugfs_deinit(); vchiq_deregister_chrdev(); diff --git a/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.h b/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.h index b402aac333d9b9..f2d11ecf90e097 100644 --- a/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.h +++ b/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.h @@ -32,6 +32,7 @@ enum USE_TYPE_E { struct vchiq_platform_info { unsigned int cache_line_size; + bool use_36bit_addrs; }; struct vchiq_drv_mgmt { diff --git a/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_bus.h b/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_bus.h index 9de179b39f85e5..6eff6b0bf59956 100644 --- a/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_bus.h +++ b/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_bus.h @@ -37,6 +37,16 @@ static inline struct vchiq_driver *to_vchiq_driver(struct device_driver *d) return container_of(d, struct vchiq_driver, driver); } +static inline void *vchiq_get_drvdata(const struct vchiq_device *device) +{ + return dev_get_drvdata(&device->dev); +} + +static inline void vchiq_set_drvdata(struct vchiq_device *device, void *data) +{ + dev_set_drvdata(&device->dev, data); +} + extern const struct bus_type vchiq_bus_type; struct vchiq_device * diff --git a/drivers/staging/vc04_services/vc-sm-cma/Kconfig b/drivers/staging/vc04_services/vc-sm-cma/Kconfig new file mode 100644 index 00000000000000..d812021385a0ff --- /dev/null +++ b/drivers/staging/vc04_services/vc-sm-cma/Kconfig @@ -0,0 +1,10 @@ +config BCM_VC_SM_CMA + tristate "VideoCore Shared Memory (CMA) driver" + select BCM2835_VCHIQ + select RBTREE + select DMA_SHARED_BUFFER + help + Say Y here to enable the shared memory interface that + supports sharing dmabufs with VideoCore. + This operates over the VCHIQ interface to a service + running on VideoCore. diff --git a/drivers/staging/vc04_services/vc-sm-cma/Makefile b/drivers/staging/vc04_services/vc-sm-cma/Makefile new file mode 100644 index 00000000000000..bb80a16fdf480c --- /dev/null +++ b/drivers/staging/vc04_services/vc-sm-cma/Makefile @@ -0,0 +1,7 @@ +ccflags-y += \ + -D__VCCOREVER__=0 + +vc-sm-cma-$(CONFIG_BCM_VC_SM_CMA) := \ + vc_sm.o vc_sm_cma_vchi.o + +obj-$(CONFIG_BCM_VC_SM_CMA) += vc-sm-cma.o diff --git a/drivers/staging/vc04_services/vc-sm-cma/TODO b/drivers/staging/vc04_services/vc-sm-cma/TODO new file mode 100644 index 00000000000000..ac9b5f8a738951 --- /dev/null +++ b/drivers/staging/vc04_services/vc-sm-cma/TODO @@ -0,0 +1 @@ +No currently outstanding tasks except some clean-up. diff --git a/drivers/staging/vc04_services/vc-sm-cma/vc_sm.c b/drivers/staging/vc04_services/vc-sm-cma/vc_sm.c new file mode 100644 index 00000000000000..7dbc88dab4aec8 --- /dev/null +++ b/drivers/staging/vc04_services/vc-sm-cma/vc_sm.c @@ -0,0 +1,1625 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * VideoCore Shared Memory driver using CMA. + * + * Copyright: 2018, Raspberry Pi (Trading) Ltd + * Dave Stevenson <dave.stevenson@raspberrypi.org> + * + * Based on vmcs_sm driver from Broadcom Corporation for some API, + * and taking some code for buffer allocation and dmabuf handling from + * videobuf2. + * + * + * This driver has 3 main uses: + * 1) Allocating buffers for the kernel or userspace that can be shared with the + * VPU. + * 2) Importing dmabufs from elsewhere for sharing with the VPU. + * 3) Allocating buffers for use by the VPU. + * + * In the first and second cases the native handle is a dmabuf. Releasing the + * resource inherently comes from releasing the dmabuf, and this will trigger + * unmapping on the VPU. The underlying allocation and our buffer structure are + * retained until the VPU has confirmed that it has finished with it. + * + * For the VPU allocations the VPU is responsible for triggering the release, + * and therefore the released message decrements the dma_buf refcount (with the + * VPU mapping having already been marked as released). + */ + +/* ---- Include Files ----------------------------------------------------- */ +#include <linux/cdev.h> +#include <linux/device.h> +#include <linux/debugfs.h> +#include <linux/dma-mapping.h> +#include <linux/dma-buf.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/mm.h> +#include <linux/of_device.h> +#include <linux/proc_fs.h> +#include <linux/slab.h> +#include <linux/seq_file.h> +#include <linux/syscalls.h> +#include <linux/types.h> +#include <asm/cacheflush.h> + +#include "../interface/vchiq_arm/vchiq_arm.h" +#include "../interface/vchiq_arm/vchiq_bus.h" +#include "vc_sm_cma_vchi.h" + +#include "vc_sm.h" +#include "vc_sm_knl.h" +#include "../include/linux/broadcom/vc_sm_cma_ioctl.h" + +MODULE_IMPORT_NS(DMA_BUF); + +/* ---- Private Constants and Types --------------------------------------- */ + +#define DEVICE_NAME "vcsm-cma" +#define DEVICE_MINOR 0 + +#define VC_SM_RESOURCE_NAME_DEFAULT "sm-host-resource" + +#define VC_SM_DIR_ROOT_NAME "vcsm-cma" +#define VC_SM_STATE "state" + +/* Private file data associated with each opened device. */ +struct vc_sm_privdata_t { + pid_t pid; /* PID of creator. */ + + int restart_sys; /* Tracks restart on interrupt. */ + enum vc_sm_msg_type int_action; /* Interrupted action. */ + u32 int_trans_id; /* Interrupted transaction. */ +}; + +typedef int (*VC_SM_SHOW) (struct seq_file *s, void *v); +struct sm_pde_t { + VC_SM_SHOW show; /* Debug fs function hookup. */ + struct dentry *dir_entry; /* Debug fs directory entry. */ + void *priv_data; /* Private data */ +}; + +/* Global state information. */ +struct sm_state_t { + struct vchiq_device *device; + + struct miscdevice misc_dev; + + struct sm_instance *sm_handle; /* Handle for videocore service. */ + + spinlock_t kernelid_map_lock; /* Spinlock protecting kernelid_map */ + struct idr kernelid_map; + + struct mutex map_lock; /* Global map lock. */ + struct list_head buffer_list; /* List of buffer. */ + + struct vc_sm_privdata_t *data_knl; /* Kernel internal data tracking. */ + struct vc_sm_privdata_t *vpu_allocs; /* All allocations from the VPU */ + struct dentry *dir_root; /* Debug fs entries root. */ + struct sm_pde_t dir_state; /* Debug fs entries state sub-tree. */ + + bool require_released_callback; /* VPU will send a released msg when it + * has finished with a resource. + */ + u32 int_trans_id; /* Interrupted transaction. */ + struct vchiq_instance *vchiq_instance; +}; + +struct vc_sm_dma_buf_attachment { + struct device *dev; + struct sg_table sg_table; + struct list_head list; + enum dma_data_direction dma_dir; +}; + +/* ---- Private Variables ----------------------------------------------- */ + +static struct sm_state_t *sm_state; +static int sm_inited; + +/* ---- Private Function Prototypes -------------------------------------- */ + +/* ---- Private Functions ------------------------------------------------ */ + +static int get_kernel_id(struct vc_sm_buffer *buffer) +{ + int handle; + + spin_lock(&sm_state->kernelid_map_lock); + handle = idr_alloc(&sm_state->kernelid_map, buffer, 0, 0, GFP_KERNEL); + spin_unlock(&sm_state->kernelid_map_lock); + + return handle; +} + +static struct vc_sm_buffer *lookup_kernel_id(int handle) +{ + struct vc_sm_buffer *buffer; + + spin_lock(&sm_state->kernelid_map_lock); + buffer = idr_find(&sm_state->kernelid_map, handle); + spin_unlock(&sm_state->kernelid_map_lock); + + return buffer; +} + +static void free_kernel_id(int handle) +{ + spin_lock(&sm_state->kernelid_map_lock); + idr_remove(&sm_state->kernelid_map, handle); + spin_unlock(&sm_state->kernelid_map_lock); +} + +static int vc_sm_cma_seq_file_show(struct seq_file *s, void *v) +{ + struct sm_pde_t *sm_pde; + + sm_pde = (struct sm_pde_t *)(s->private); + + if (sm_pde && sm_pde->show) + sm_pde->show(s, v); + + return 0; +} + +static int vc_sm_cma_single_open(struct inode *inode, struct file *file) +{ + return single_open(file, vc_sm_cma_seq_file_show, inode->i_private); +} + +static const struct file_operations vc_sm_cma_debug_fs_fops = { + .open = vc_sm_cma_single_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int vc_sm_cma_global_state_show(struct seq_file *s, void *v) +{ + struct vc_sm_buffer *resource = NULL; + int resource_count = 0; + + if (!sm_state) + return 0; + + seq_printf(s, "\nVC-ServiceHandle %p\n", sm_state->sm_handle); + + /* Log all applicable mapping(s). */ + + mutex_lock(&sm_state->map_lock); + seq_puts(s, "\nResources\n"); + if (!list_empty(&sm_state->buffer_list)) { + list_for_each_entry(resource, &sm_state->buffer_list, + global_buffer_list) { + resource_count++; + + seq_printf(s, "\nResource %p\n", + resource); + seq_printf(s, " NAME %s\n", + resource->name); + seq_printf(s, " SIZE %zu\n", + resource->size); + seq_printf(s, " DMABUF %p\n", + resource->dma_buf); + if (resource->imported) { + seq_printf(s, " ATTACH %p\n", + resource->import.attach); + seq_printf(s, " SGT %p\n", + resource->import.sgt); + } else { + seq_printf(s, " SGT %p\n", + resource->alloc.sg_table); + } + seq_printf(s, " DMA_ADDR %pad\n", + &resource->dma_addr); + seq_printf(s, " VC_HANDLE %08x\n", + resource->vc_handle); + seq_printf(s, " VC_MAPPING %d\n", + resource->vpu_state); + } + } + seq_printf(s, "\n\nTotal resource count: %d\n\n", resource_count); + + mutex_unlock(&sm_state->map_lock); + + return 0; +} + +/* + * Adds a buffer to the private data list which tracks all the allocated + * data. + */ +static void vc_sm_add_resource(struct vc_sm_privdata_t *privdata, + struct vc_sm_buffer *buffer) +{ + mutex_lock(&sm_state->map_lock); + list_add(&buffer->global_buffer_list, &sm_state->buffer_list); + mutex_unlock(&sm_state->map_lock); + + pr_debug("[%s]: added buffer %p (name %s, size %zu)\n", + __func__, buffer, buffer->name, buffer->size); +} + +/* + * Cleans up imported dmabuf. + * Should be called with mutex held. + */ +static void vc_sm_clean_up_dmabuf(struct vc_sm_buffer *buffer) +{ + if (!buffer->imported) + return; + + /* Handle cleaning up imported dmabufs */ + if (buffer->import.sgt) { + dma_buf_unmap_attachment(buffer->import.attach, + buffer->import.sgt, + DMA_BIDIRECTIONAL); + buffer->import.sgt = NULL; + } + if (buffer->import.attach) { + dma_buf_detach(buffer->import.dma_buf, buffer->import.attach); + buffer->import.attach = NULL; + } +} + +/* + * Instructs VPU to decrement the refcount on a buffer. + */ +static void vc_sm_vpu_free(struct vc_sm_buffer *buffer) +{ + if (buffer->vc_handle && buffer->vpu_state == VPU_MAPPED) { + struct vc_sm_free_t free = { buffer->vc_handle, 0 }; + int status = vc_sm_cma_vchi_free(sm_state->sm_handle, &free, + &sm_state->int_trans_id); + if (status != 0 && status != -EINTR) { + pr_err("[%s]: failed to free memory on videocore (status: %u, trans_id: %u)\n", + __func__, status, sm_state->int_trans_id); + } + + if (sm_state->require_released_callback) { + /* Need to wait for the VPU to confirm the free. */ + + /* Retain a reference on this until the VPU has + * released it + */ + buffer->vpu_state = VPU_UNMAPPING; + } else { + buffer->vpu_state = VPU_NOT_MAPPED; + buffer->vc_handle = 0; + } + } +} + +/* + * Release an allocation. + * All refcounting is done via the dma buf object. + * + * Must be called with the mutex held. The function will either release the + * mutex (if defering the release) or destroy it. The caller must therefore not + * reuse the buffer on return. + */ +static void vc_sm_release_resource(struct vc_sm_buffer *buffer) +{ + pr_debug("[%s]: buffer %p (name %s, size %zu), imported %u\n", + __func__, buffer, buffer->name, buffer->size, + buffer->imported); + + if (buffer->vc_handle) { + /* We've sent the unmap request but not had the response. */ + pr_debug("[%s]: Waiting for VPU unmap response on %p\n", + __func__, buffer); + goto defer; + } + if (buffer->in_use) { + /* dmabuf still in use - we await the release */ + pr_debug("[%s]: buffer %p is still in use\n", __func__, buffer); + goto defer; + } + + /* Release the allocation (whether imported dmabuf or CMA allocation) */ + if (buffer->imported) { + if (buffer->import.dma_buf) + dma_buf_put(buffer->import.dma_buf); + else + pr_err("%s: Imported dmabuf already been put for buf %p\n", + __func__, buffer); + buffer->import.dma_buf = NULL; + } else { + dma_free_coherent(&sm_state->device->dev, buffer->size, + buffer->cookie, buffer->dma_addr); + } + + /* Free our buffer. Start by removing it from the list */ + mutex_lock(&sm_state->map_lock); + list_del(&buffer->global_buffer_list); + mutex_unlock(&sm_state->map_lock); + + pr_debug("%s: Release our allocation - done\n", __func__); + mutex_unlock(&buffer->lock); + + mutex_destroy(&buffer->lock); + + kfree(buffer); + return; + +defer: + mutex_unlock(&buffer->lock); +} + +/* Create support for private data tracking. */ +static struct vc_sm_privdata_t *vc_sm_cma_create_priv_data(pid_t id) +{ + char alloc_name[32]; + struct vc_sm_privdata_t *file_data = NULL; + + /* Allocate private structure. */ + file_data = kzalloc(sizeof(*file_data), GFP_KERNEL); + + if (!file_data) + return NULL; + + snprintf(alloc_name, sizeof(alloc_name), "%d", id); + + file_data->pid = id; + + return file_data; +} + +/* Dma buf operations for use with our own allocations */ + +static int vc_sm_dma_buf_attach(struct dma_buf *dmabuf, + struct dma_buf_attachment *attachment) + +{ + struct vc_sm_dma_buf_attachment *a; + struct sg_table *sgt; + struct vc_sm_buffer *buf = dmabuf->priv; + struct scatterlist *rd, *wr; + int ret, i; + + a = kzalloc(sizeof(*a), GFP_KERNEL); + if (!a) + return -ENOMEM; + + pr_debug("%s dmabuf %p attachment %p\n", __func__, dmabuf, attachment); + + mutex_lock(&buf->lock); + + INIT_LIST_HEAD(&a->list); + + sgt = &a->sg_table; + + /* Copy the buf->base_sgt scatter list to the attachment, as we can't + * map the same scatter list to multiple attachments at the same time. + */ + ret = sg_alloc_table(sgt, buf->alloc.sg_table->orig_nents, GFP_KERNEL); + if (ret) { + kfree(a); + return -ENOMEM; + } + + rd = buf->alloc.sg_table->sgl; + wr = sgt->sgl; + for (i = 0; i < sgt->orig_nents; ++i) { + sg_set_page(wr, sg_page(rd), rd->length, rd->offset); + rd = sg_next(rd); + wr = sg_next(wr); + } + + a->dma_dir = DMA_NONE; + attachment->priv = a; + + list_add(&a->list, &buf->attachments); + mutex_unlock(&buf->lock); + + return 0; +} + +static void vc_sm_dma_buf_detach(struct dma_buf *dmabuf, + struct dma_buf_attachment *attachment) +{ + struct vc_sm_dma_buf_attachment *a = attachment->priv; + struct vc_sm_buffer *buf = dmabuf->priv; + struct sg_table *sgt; + + pr_debug("%s dmabuf %p attachment %p\n", __func__, dmabuf, attachment); + if (!a) + return; + + sgt = &a->sg_table; + + /* release the scatterlist cache */ + if (a->dma_dir != DMA_NONE) + dma_unmap_sg(attachment->dev, sgt->sgl, sgt->orig_nents, + a->dma_dir); + sg_free_table(sgt); + + mutex_lock(&buf->lock); + list_del(&a->list); + mutex_unlock(&buf->lock); + + kfree(a); +} + +static struct sg_table *vc_sm_map_dma_buf(struct dma_buf_attachment *attachment, + enum dma_data_direction direction) +{ + struct vc_sm_dma_buf_attachment *a = attachment->priv; + /* stealing dmabuf mutex to serialize map/unmap operations */ + struct sg_table *table; + + pr_debug("%s attachment %p\n", __func__, attachment); + table = &a->sg_table; + + /* return previously mapped sg table */ + if (a->dma_dir == direction) { + return table; + } + + /* release any previous cache */ + if (a->dma_dir != DMA_NONE) { + dma_unmap_sg(attachment->dev, table->sgl, table->orig_nents, + a->dma_dir); + a->dma_dir = DMA_NONE; + } + + /* mapping to the client with new direction */ + table->nents = dma_map_sg(attachment->dev, table->sgl, + table->orig_nents, direction); + if (!table->nents) { + pr_err("failed to map scatterlist\n"); + return ERR_PTR(-EIO); + } + + a->dma_dir = direction; + + pr_debug("%s attachment %p\n", __func__, attachment); + return table; +} + +static void vc_sm_unmap_dma_buf(struct dma_buf_attachment *attachment, + struct sg_table *table, + enum dma_data_direction direction) +{ + pr_debug("%s attachment %p\n", __func__, attachment); + dma_unmap_sg(attachment->dev, table->sgl, table->nents, direction); +} + +static int vc_sm_dmabuf_mmap(struct dma_buf *dmabuf, struct vm_area_struct *vma) +{ + struct vc_sm_buffer *buf = dmabuf->priv; + int ret; + + pr_debug("%s dmabuf %p, buf %p, vm_start %08lX\n", __func__, dmabuf, + buf, vma->vm_start); + + /* now map it to userspace */ + vma->vm_pgoff = 0; + + ret = dma_mmap_coherent(&sm_state->device->dev, vma, buf->cookie, + buf->dma_addr, buf->size); + + if (ret) { + pr_err("Remapping memory failed, error: %d\n", ret); + return ret; + } + + vm_flags_reset(vma, vma->vm_flags | VM_DONTEXPAND | VM_DONTDUMP); + + if (ret) + pr_err("%s: failure mapping buffer to userspace\n", + __func__); + + return ret; +} + +static void vc_sm_dma_buf_release(struct dma_buf *dmabuf) +{ + struct vc_sm_buffer *buffer; + + if (!dmabuf) + return; + + buffer = (struct vc_sm_buffer *)dmabuf->priv; + + mutex_lock(&buffer->lock); + + pr_debug("%s dmabuf %p, buffer %p\n", __func__, dmabuf, buffer); + + buffer->in_use = false; + + /* Unmap on the VPU */ + vc_sm_vpu_free(buffer); + pr_debug("%s vpu_free done\n", __func__); + + /* Unmap our dma_buf object (the vc_sm_buffer remains until released + * on the VPU). + */ + vc_sm_clean_up_dmabuf(buffer); + pr_debug("%s clean_up dmabuf done\n", __func__); + + /* buffer->lock will be destroyed by vc_sm_release_resource if finished + * with, otherwise unlocked. Do NOT unlock here. + */ + vc_sm_release_resource(buffer); + pr_debug("%s done\n", __func__); +} + +static int vc_sm_dma_buf_begin_cpu_access(struct dma_buf *dmabuf, + enum dma_data_direction direction) +{ + struct vc_sm_buffer *buf; + struct vc_sm_dma_buf_attachment *a; + + if (!dmabuf) + return -EFAULT; + + buf = dmabuf->priv; + if (!buf) + return -EFAULT; + + mutex_lock(&buf->lock); + + list_for_each_entry(a, &buf->attachments, list) { + dma_sync_sg_for_cpu(a->dev, a->sg_table.sgl, + a->sg_table.nents, direction); + } + mutex_unlock(&buf->lock); + + return 0; +} + +static int vc_sm_dma_buf_end_cpu_access(struct dma_buf *dmabuf, + enum dma_data_direction direction) +{ + struct vc_sm_buffer *buf; + struct vc_sm_dma_buf_attachment *a; + + if (!dmabuf) + return -EFAULT; + buf = dmabuf->priv; + if (!buf) + return -EFAULT; + + mutex_lock(&buf->lock); + + list_for_each_entry(a, &buf->attachments, list) { + dma_sync_sg_for_device(a->dev, a->sg_table.sgl, + a->sg_table.nents, direction); + } + mutex_unlock(&buf->lock); + + return 0; +} + +static const struct dma_buf_ops dma_buf_ops = { + .map_dma_buf = vc_sm_map_dma_buf, + .unmap_dma_buf = vc_sm_unmap_dma_buf, + .mmap = vc_sm_dmabuf_mmap, + .release = vc_sm_dma_buf_release, + .attach = vc_sm_dma_buf_attach, + .detach = vc_sm_dma_buf_detach, + .begin_cpu_access = vc_sm_dma_buf_begin_cpu_access, + .end_cpu_access = vc_sm_dma_buf_end_cpu_access, +}; + +/* Dma_buf operations for chaining through to an imported dma_buf */ + +static +int vc_sm_import_dma_buf_attach(struct dma_buf *dmabuf, + struct dma_buf_attachment *attachment) +{ + struct vc_sm_buffer *buf = dmabuf->priv; + + if (!buf->imported) + return -EINVAL; + return buf->import.dma_buf->ops->attach(buf->import.dma_buf, + attachment); +} + +static +void vc_sm_import_dma_buf_detatch(struct dma_buf *dmabuf, + struct dma_buf_attachment *attachment) +{ + struct vc_sm_buffer *buf = dmabuf->priv; + + if (!buf->imported) + return; + buf->import.dma_buf->ops->detach(buf->import.dma_buf, attachment); +} + +static +struct sg_table *vc_sm_import_map_dma_buf(struct dma_buf_attachment *attachment, + enum dma_data_direction direction) +{ + struct vc_sm_buffer *buf = attachment->dmabuf->priv; + + if (!buf->imported) + return NULL; + return buf->import.dma_buf->ops->map_dma_buf(attachment, + direction); +} + +static +void vc_sm_import_unmap_dma_buf(struct dma_buf_attachment *attachment, + struct sg_table *table, + enum dma_data_direction direction) +{ + struct vc_sm_buffer *buf = attachment->dmabuf->priv; + + if (!buf->imported) + return; + buf->import.dma_buf->ops->unmap_dma_buf(attachment, table, direction); +} + +static +int vc_sm_import_dmabuf_mmap(struct dma_buf *dmabuf, struct vm_area_struct *vma) +{ + struct vc_sm_buffer *buf = dmabuf->priv; + + pr_debug("%s: mmap dma_buf %p, buf %p, imported db %p\n", __func__, + dmabuf, buf, buf->import.dma_buf); + if (!buf->imported) { + pr_err("%s: mmap dma_buf %p- not an imported buffer\n", + __func__, dmabuf); + return -EINVAL; + } + return buf->import.dma_buf->ops->mmap(buf->import.dma_buf, vma); +} + +static +int vc_sm_import_dma_buf_begin_cpu_access(struct dma_buf *dmabuf, + enum dma_data_direction direction) +{ + struct vc_sm_buffer *buf = dmabuf->priv; + + if (!buf->imported) + return -EINVAL; + return buf->import.dma_buf->ops->begin_cpu_access(buf->import.dma_buf, + direction); +} + +static +int vc_sm_import_dma_buf_end_cpu_access(struct dma_buf *dmabuf, + enum dma_data_direction direction) +{ + struct vc_sm_buffer *buf = dmabuf->priv; + + if (!buf->imported) + return -EINVAL; + return buf->import.dma_buf->ops->end_cpu_access(buf->import.dma_buf, + direction); +} + +static const struct dma_buf_ops dma_buf_import_ops = { + .map_dma_buf = vc_sm_import_map_dma_buf, + .unmap_dma_buf = vc_sm_import_unmap_dma_buf, + .mmap = vc_sm_import_dmabuf_mmap, + .release = vc_sm_dma_buf_release, + .attach = vc_sm_import_dma_buf_attach, + .detach = vc_sm_import_dma_buf_detatch, + .begin_cpu_access = vc_sm_import_dma_buf_begin_cpu_access, + .end_cpu_access = vc_sm_import_dma_buf_end_cpu_access, +}; + +/* Import a dma_buf to be shared with VC. */ +static int +vc_sm_cma_import_dmabuf_internal(struct vc_sm_privdata_t *private, + struct dma_buf *dma_buf, + int fd, + struct dma_buf **imported_buf) +{ + DEFINE_DMA_BUF_EXPORT_INFO(exp_info); + struct vc_sm_buffer *buffer = NULL; + struct vc_sm_import import = { }; + struct vc_sm_import_result result = { }; + struct dma_buf_attachment *attach = NULL; + struct sg_table *sgt = NULL; + dma_addr_t dma_addr; + u32 cache_alias; + int ret = 0; + int status; + + /* Setup our allocation parameters */ + pr_debug("%s: importing dma_buf %p/fd %d\n", __func__, dma_buf, fd); + + if (fd < 0) + get_dma_buf(dma_buf); + else + dma_buf = dma_buf_get(fd); + + if (!dma_buf) + return -EINVAL; + + attach = dma_buf_attach(dma_buf, &sm_state->device->dev); + if (IS_ERR(attach)) { + ret = PTR_ERR(attach); + goto error; + } + + sgt = dma_buf_map_attachment(attach, DMA_BIDIRECTIONAL); + if (IS_ERR(sgt)) { + ret = PTR_ERR(sgt); + goto error; + } + + /* Verify that the address block is contiguous */ + if (sgt->nents != 1) { + ret = -ENOMEM; + goto error; + } + + /* Allocate local buffer to track this allocation. */ + buffer = kzalloc(sizeof(*buffer), GFP_KERNEL); + if (!buffer) { + ret = -ENOMEM; + goto error; + } + + import.type = VC_SM_ALLOC_NON_CACHED; + dma_addr = sg_dma_address(sgt->sgl); + import.addr = (u32)dma_addr; + cache_alias = import.addr & 0xC0000000; + if (cache_alias != 0xC0000000 && cache_alias != 0x80000000) { + pr_err("%s: Expecting an uncached alias for dma_addr %pad\n", + __func__, &dma_addr); + /* Note that this assumes we're on >= Pi2, but it implies a + * DT configuration error. + */ + import.addr |= 0xC0000000; + } + import.size = sg_dma_len(sgt->sgl); + import.allocator = current->tgid; + import.kernel_id = get_kernel_id(buffer); + + memcpy(import.name, VC_SM_RESOURCE_NAME_DEFAULT, + sizeof(VC_SM_RESOURCE_NAME_DEFAULT)); + + pr_debug("[%s]: attempt to import \"%s\" data - type %u, addr %pad, size %u, kernel_id %08x.\n", + __func__, import.name, import.type, &dma_addr, import.size, import.kernel_id); + + /* Allocate the videocore buffer. */ + status = vc_sm_cma_vchi_import(sm_state->sm_handle, &import, &result, + &sm_state->int_trans_id); + if (status == -EINTR) { + pr_debug("[%s]: requesting import memory action restart (trans_id: %u)\n", + __func__, sm_state->int_trans_id); + ret = -ERESTARTSYS; + private->restart_sys = -EINTR; + private->int_action = VC_SM_MSG_TYPE_IMPORT; + goto error; + } else if (status || !result.res_handle) { + pr_debug("[%s]: failed to import memory on videocore (status: %u, trans_id: %u)\n", + __func__, status, sm_state->int_trans_id); + ret = -ENOMEM; + goto error; + } + + mutex_init(&buffer->lock); + INIT_LIST_HEAD(&buffer->attachments); + memcpy(buffer->name, import.name, + min(sizeof(buffer->name), sizeof(import.name) - 1)); + + /* Keep track of the buffer we created. */ + buffer->private = private; + buffer->vc_handle = result.res_handle; + buffer->size = import.size; + buffer->vpu_state = VPU_MAPPED; + + buffer->imported = true; + buffer->import.dma_buf = dma_buf; + + buffer->import.attach = attach; + buffer->import.sgt = sgt; + buffer->dma_addr = dma_addr; + buffer->in_use = true; + buffer->kernel_id = import.kernel_id; + + /* + * We're done - we need to export a new dmabuf chaining through most + * functions, but enabling us to release our own internal references + * here. + */ + exp_info.ops = &dma_buf_import_ops; + exp_info.size = import.size; + exp_info.flags = O_RDWR; + exp_info.priv = buffer; + + buffer->dma_buf = dma_buf_export(&exp_info); + if (IS_ERR(buffer->dma_buf)) { + ret = PTR_ERR(buffer->dma_buf); + goto error; + } + + vc_sm_add_resource(private, buffer); + + *imported_buf = buffer->dma_buf; + + return 0; + +error: + if (result.res_handle) { + struct vc_sm_free_t free = { result.res_handle, 0 }; + + vc_sm_cma_vchi_free(sm_state->sm_handle, &free, + &sm_state->int_trans_id); + } + free_kernel_id(import.kernel_id); + kfree(buffer); + if (sgt) + dma_buf_unmap_attachment(attach, sgt, DMA_BIDIRECTIONAL); + if (attach) + dma_buf_detach(dma_buf, attach); + dma_buf_put(dma_buf); + return ret; +} + +static int vc_sm_cma_vpu_alloc(u32 size, u32 align, const char *name, + u32 mem_handle, struct vc_sm_buffer **ret_buffer) +{ + DEFINE_DMA_BUF_EXPORT_INFO(exp_info); + struct vc_sm_buffer *buffer = NULL; + struct sg_table *sgt; + int aligned_size; + int ret = 0; + + /* Align to the user requested align */ + aligned_size = ALIGN(size, align); + /* and then to a page boundary */ + aligned_size = PAGE_ALIGN(aligned_size); + + if (!aligned_size) + return -EINVAL; + + /* Allocate local buffer to track this allocation. */ + buffer = kzalloc(sizeof(*buffer), GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + mutex_init(&buffer->lock); + /* Acquire the mutex as vc_sm_release_resource will release it in the + * error path. + */ + mutex_lock(&buffer->lock); + + buffer->cookie = dma_alloc_coherent(&sm_state->device->dev, + aligned_size, &buffer->dma_addr, + GFP_KERNEL); + if (!buffer->cookie) { + pr_err("[%s]: dma_alloc_coherent alloc of %d bytes failed\n", + __func__, aligned_size); + ret = -ENOMEM; + goto error; + } + + pr_debug("[%s]: alloc of %d bytes success\n", + __func__, aligned_size); + + sgt = kmalloc(sizeof(*sgt), GFP_KERNEL); + if (!sgt) { + ret = -ENOMEM; + goto error; + } + + ret = dma_get_sgtable(&sm_state->device->dev, sgt, buffer->cookie, + buffer->dma_addr, buffer->size); + if (ret < 0) { + pr_err("failed to get scatterlist from DMA API\n"); + kfree(sgt); + ret = -ENOMEM; + goto error; + } + buffer->alloc.sg_table = sgt; + + INIT_LIST_HEAD(&buffer->attachments); + + memcpy(buffer->name, name, + min(sizeof(buffer->name), strlen(name))); + + exp_info.ops = &dma_buf_ops; + exp_info.size = aligned_size; + exp_info.flags = O_RDWR; + exp_info.priv = buffer; + + buffer->dma_buf = dma_buf_export(&exp_info); + if (IS_ERR(buffer->dma_buf)) { + ret = PTR_ERR(buffer->dma_buf); + goto error; + } + buffer->dma_addr = (u32)sg_dma_address(buffer->alloc.sg_table->sgl); + if ((buffer->dma_addr & 0xC0000000) != 0xC0000000) { + pr_warn_once("%s: Expecting an uncached alias for dma_addr %pad\n", + __func__, &buffer->dma_addr); + buffer->dma_addr |= 0xC0000000; + } + buffer->private = sm_state->vpu_allocs; + + buffer->vc_handle = mem_handle; + buffer->vpu_state = VPU_MAPPED; + buffer->vpu_allocated = 1; + buffer->size = size; + /* + * Create an ID that will be passed along with our message so + * that when we service the release reply, we can look up which + * resource is being released. + */ + buffer->kernel_id = get_kernel_id(buffer); + + vc_sm_add_resource(sm_state->vpu_allocs, buffer); + + mutex_unlock(&buffer->lock); + + *ret_buffer = buffer; + return 0; +error: + if (buffer) + vc_sm_release_resource(buffer); + return ret; +} + +static void +vc_sm_vpu_event(struct sm_instance *instance, struct vc_sm_result_t *reply, + int reply_len) +{ + switch (reply->trans_id & ~0x80000000) { + case VC_SM_MSG_TYPE_CLIENT_VERSION: + { + /* Acknowledge that the firmware supports the version command */ + pr_debug("%s: firmware acked version msg. Require release cb\n", + __func__); + sm_state->require_released_callback = true; + } + break; + case VC_SM_MSG_TYPE_RELEASED: + { + struct vc_sm_released *release = (struct vc_sm_released *)reply; + struct vc_sm_buffer *buffer = + lookup_kernel_id(release->kernel_id); + if (!buffer) { + pr_err("%s: VC released a buffer that is already released, kernel_id %d\n", + __func__, release->kernel_id); + break; + } + mutex_lock(&buffer->lock); + + pr_debug("%s: Released addr %08x, size %u, id %08x, mem_handle %08x\n", + __func__, release->addr, release->size, + release->kernel_id, release->vc_handle); + + buffer->vc_handle = 0; + buffer->vpu_state = VPU_NOT_MAPPED; + free_kernel_id(release->kernel_id); + + if (buffer->vpu_allocated) { + /* VPU allocation, so release the dmabuf which will + * trigger the clean up. + */ + mutex_unlock(&buffer->lock); + dma_buf_put(buffer->dma_buf); + } else { + vc_sm_release_resource(buffer); + } + } + break; + case VC_SM_MSG_TYPE_VC_MEM_REQUEST: + { + struct vc_sm_buffer *buffer = NULL; + struct vc_sm_vc_mem_request *req = + (struct vc_sm_vc_mem_request *)reply; + struct vc_sm_vc_mem_request_result reply; + int ret; + + pr_debug("%s: Request %u bytes of memory, align %d name %s, trans_id %08x\n", + __func__, req->size, req->align, req->name, + req->trans_id); + ret = vc_sm_cma_vpu_alloc(req->size, req->align, req->name, + req->vc_handle, &buffer); + + reply.trans_id = req->trans_id; + if (!ret) { + reply.addr = buffer->dma_addr; + reply.kernel_id = buffer->kernel_id; + pr_debug("%s: Allocated resource buffer %p, addr %pad\n", + __func__, buffer, &buffer->dma_addr); + } else { + pr_err("%s: Allocation failed size %u, name %s, vc_handle %u\n", + __func__, req->size, req->name, req->vc_handle); + reply.addr = 0; + reply.kernel_id = 0; + } + vc_sm_vchi_client_vc_mem_req_reply(sm_state->sm_handle, &reply, + &sm_state->int_trans_id); + break; + } + break; + default: + pr_err("%s: Unknown vpu cmd %x\n", __func__, reply->trans_id); + break; + } +} + +/* Userspace handling */ +/* + * Open the device. Creates a private state to help track all allocation + * associated with this device. + */ +static int vc_sm_cma_open(struct inode *inode, struct file *file) +{ + /* Make sure the device was started properly. */ + if (!sm_state) { + pr_err("[%s]: invalid device\n", __func__); + return -EPERM; + } + + file->private_data = vc_sm_cma_create_priv_data(current->tgid); + if (!file->private_data) { + pr_err("[%s]: failed to create data tracker\n", __func__); + + return -ENOMEM; + } + + return 0; +} + +/* + * Close the vcsm-cma device. + * All allocations are file descriptors to the dmabuf objects, so we will get + * the clean up request on those as those are cleaned up. + */ +static int vc_sm_cma_release(struct inode *inode, struct file *file) +{ + struct vc_sm_privdata_t *file_data = + (struct vc_sm_privdata_t *)file->private_data; + int ret = 0; + + /* Make sure the device was started properly. */ + if (!sm_state || !file_data) { + pr_err("[%s]: invalid device\n", __func__); + ret = -EPERM; + goto out; + } + + pr_debug("[%s]: using private data %p\n", __func__, file_data); + + /* Terminate the private data. */ + kfree(file_data); + +out: + return ret; +} + +/* + * Allocate a shared memory handle and block. + * Allocation is from CMA, and then imported into the VPU mappings. + */ +static int vc_sm_cma_ioctl_alloc(struct vc_sm_privdata_t *private, + struct vc_sm_cma_ioctl_alloc *ioparam) +{ + DEFINE_DMA_BUF_EXPORT_INFO(exp_info); + struct vc_sm_buffer *buffer = NULL; + struct vc_sm_import import = { 0 }; + struct vc_sm_import_result result = { 0 }; + struct dma_buf *dmabuf = NULL; + struct sg_table *sgt; + int aligned_size; + int ret = 0; + int status; + int fd = -1; + + aligned_size = PAGE_ALIGN(ioparam->size); + + if (!aligned_size) + return -EINVAL; + + /* Allocate local buffer to track this allocation. */ + buffer = kzalloc(sizeof(*buffer), GFP_KERNEL); + if (!buffer) { + ret = -ENOMEM; + goto error; + } + + buffer->cookie = dma_alloc_coherent(&sm_state->device->dev, + aligned_size, + &buffer->dma_addr, + GFP_KERNEL); + if (!buffer->cookie) { + pr_err("[%s]: dma_alloc_coherent alloc of %d bytes failed\n", + __func__, aligned_size); + ret = -ENOMEM; + goto error; + } + + import.type = VC_SM_ALLOC_NON_CACHED; + import.allocator = current->tgid; + + if (*ioparam->name) + memcpy(import.name, ioparam->name, sizeof(import.name) - 1); + else + memcpy(import.name, VC_SM_RESOURCE_NAME_DEFAULT, + sizeof(VC_SM_RESOURCE_NAME_DEFAULT)); + + mutex_init(&buffer->lock); + INIT_LIST_HEAD(&buffer->attachments); + memcpy(buffer->name, import.name, + min(sizeof(buffer->name), sizeof(import.name) - 1)); + + exp_info.ops = &dma_buf_ops; + exp_info.size = aligned_size; + exp_info.flags = O_RDWR; + exp_info.priv = buffer; + + dmabuf = dma_buf_export(&exp_info); + if (IS_ERR(dmabuf)) { + ret = PTR_ERR(dmabuf); + goto error; + } + buffer->dma_buf = dmabuf; + + import.addr = buffer->dma_addr; + import.size = aligned_size; + import.kernel_id = get_kernel_id(buffer); + + /* Wrap it into a videocore buffer. */ + status = vc_sm_cma_vchi_import(sm_state->sm_handle, &import, &result, + &sm_state->int_trans_id); + if (status == -EINTR) { + pr_debug("[%s]: requesting import memory action restart (trans_id: %u)\n", + __func__, sm_state->int_trans_id); + ret = -ERESTARTSYS; + private->restart_sys = -EINTR; + private->int_action = VC_SM_MSG_TYPE_IMPORT; + goto error; + } else if (status || !result.res_handle) { + pr_err("[%s]: failed to import memory on videocore (status: %u, trans_id: %u)\n", + __func__, status, sm_state->int_trans_id); + ret = -ENOMEM; + goto error; + } + + /* Keep track of the buffer we created. */ + buffer->private = private; + buffer->vc_handle = result.res_handle; + buffer->size = import.size; + buffer->vpu_state = VPU_MAPPED; + buffer->kernel_id = import.kernel_id; + + sgt = kmalloc(sizeof(*sgt), GFP_KERNEL); + if (!sgt) { + ret = -ENOMEM; + goto error; + } + + ret = dma_get_sgtable(&sm_state->device->dev, sgt, buffer->cookie, + buffer->dma_addr, buffer->size); + if (ret < 0) { + /* FIXME: error handling */ + pr_err("failed to get scatterlist from DMA API\n"); + kfree(sgt); + ret = -ENOMEM; + goto error; + } + buffer->alloc.sg_table = sgt; + + fd = dma_buf_fd(dmabuf, O_CLOEXEC); + if (fd < 0) + goto error; + + vc_sm_add_resource(private, buffer); + + pr_debug("[%s]: Added resource as fd %d, buffer %p, private %p, dma_addr %pad\n", + __func__, fd, buffer, private, &buffer->dma_addr); + + /* We're done */ + ioparam->handle = fd; + ioparam->vc_handle = buffer->vc_handle; + ioparam->dma_addr = buffer->dma_addr; + return 0; + +error: + pr_err("[%s]: something failed - cleanup. ret %d\n", __func__, ret); + + if (dmabuf) { + /* dmabuf has been exported, therefore allow dmabuf cleanup to + * deal with this + */ + dma_buf_put(dmabuf); + } else { + /* No dmabuf, therefore just free the buffer here */ + if (buffer->cookie) + dma_free_coherent(&sm_state->device->dev, buffer->size, + buffer->cookie, buffer->dma_addr); + kfree(buffer); + } + return ret; +} + +static long vc_sm_cma_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int ret = 0; + unsigned int cmdnr = _IOC_NR(cmd); + struct vc_sm_privdata_t *file_data = + (struct vc_sm_privdata_t *)file->private_data; + + /* Validate we can work with this device. */ + if (!sm_state || !file_data) { + pr_err("[%s]: invalid device\n", __func__); + return -EPERM; + } + + /* Action is a re-post of a previously interrupted action? */ + if (file_data->restart_sys == -EINTR) { + pr_debug("[%s]: clean up of action %u (trans_id: %u) following EINTR\n", + __func__, file_data->int_action, + file_data->int_trans_id); + + file_data->restart_sys = 0; + } + + /* Now process the command. */ + switch (cmdnr) { + /* New memory allocation. + */ + case VC_SM_CMA_CMD_ALLOC: + { + struct vc_sm_cma_ioctl_alloc ioparam; + + /* Get the parameter data. */ + if (copy_from_user + (&ioparam, (void *)arg, sizeof(ioparam)) != 0) { + pr_err("[%s]: failed to copy-from-user for cmd %x\n", + __func__, cmdnr); + ret = -EFAULT; + break; + } + + ret = vc_sm_cma_ioctl_alloc(file_data, &ioparam); + if (!ret && + (copy_to_user((void *)arg, &ioparam, + sizeof(ioparam)) != 0)) { + /* FIXME: Release allocation */ + pr_err("[%s]: failed to copy-to-user for cmd %x\n", + __func__, cmdnr); + ret = -EFAULT; + } + break; + } + + case VC_SM_CMA_CMD_IMPORT_DMABUF: + { + struct vc_sm_cma_ioctl_import_dmabuf ioparam; + struct dma_buf *new_dmabuf; + + /* Get the parameter data. */ + if (copy_from_user + (&ioparam, (void *)arg, sizeof(ioparam)) != 0) { + pr_err("[%s]: failed to copy-from-user for cmd %x\n", + __func__, cmdnr); + ret = -EFAULT; + break; + } + + ret = vc_sm_cma_import_dmabuf_internal(file_data, + NULL, + ioparam.dmabuf_fd, + &new_dmabuf); + + if (!ret) { + struct vc_sm_buffer *buf = new_dmabuf->priv; + + ioparam.size = buf->size; + ioparam.handle = dma_buf_fd(new_dmabuf, + O_CLOEXEC); + ioparam.vc_handle = buf->vc_handle; + ioparam.dma_addr = buf->dma_addr; + + if (ioparam.handle < 0 || + (copy_to_user((void *)arg, &ioparam, + sizeof(ioparam)) != 0)) { + dma_buf_put(new_dmabuf); + /* FIXME: Release allocation */ + ret = -EFAULT; + } + } + break; + } + + default: + pr_debug("[%s]: cmd %x tgid %u, owner %u\n", __func__, cmdnr, + current->tgid, file_data->pid); + + ret = -EINVAL; + break; + } + + return ret; +} + +#ifdef CONFIG_COMPAT +struct vc_sm_cma_ioctl_clean_invalid2_32 { + u32 op_count; + struct vc_sm_cma_ioctl_clean_invalid_block_32 { + u16 invalidate_mode; + u16 block_count; + compat_uptr_t start_address; + u32 block_size; + u32 inter_block_stride; + } s[0]; +}; + +#define VC_SM_CMA_CMD_CLEAN_INVALID2_32\ + _IOR(VC_SM_CMA_MAGIC_TYPE, VC_SM_CMA_CMD_CLEAN_INVALID2,\ + struct vc_sm_cma_ioctl_clean_invalid2_32) + +static long vc_sm_cma_compat_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + switch (cmd) { + case VC_SM_CMA_CMD_CLEAN_INVALID2_32: + /* FIXME */ + return -EINVAL; + + default: + return vc_sm_cma_ioctl(file, cmd, arg); + } +} +#endif + +/* Device operations that we managed in this driver. */ +static const struct file_operations vc_sm_ops = { + .owner = THIS_MODULE, + .unlocked_ioctl = vc_sm_cma_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = vc_sm_cma_compat_ioctl, +#endif + .open = vc_sm_cma_open, + .release = vc_sm_cma_release, +}; + +/* Driver load/unload functions */ +/* Videocore connected. */ +static void vc_sm_connected_init(void) +{ + int ret; + struct vc_sm_version version; + struct vc_sm_result_t version_result; + + /* + * Digging the vchiq_drv_mgmt, so low here and through a global seems + * suspicious. + * + * The callbacks should be able to pass a parameter or context. + */ + struct vchiq_drv_mgmt *mgmt = dev_get_drvdata(sm_state->device->dev.parent); + + pr_info("[%s]: start\n", __func__); + + /* + * Initialize and create a VCHI connection for the shared memory service + * running on videocore. + */ + ret = vchiq_initialise(&mgmt->state, &sm_state->vchiq_instance); + if (ret) { + pr_err("[%s]: failed to initialise VCHI instance (ret=%d)\n", + __func__, ret); + + return; + } + + ret = vchiq_connect(sm_state->vchiq_instance); + if (ret) { + pr_err("[%s]: failed to connect VCHI instance (ret=%d)\n", + __func__, ret); + + return; + } + + /* Initialize an instance of the shared memory service. */ + sm_state->sm_handle = vc_sm_cma_vchi_init(sm_state->vchiq_instance, 1, + vc_sm_vpu_event); + if (!sm_state->sm_handle) { + pr_err("[%s]: failed to initialize shared memory service\n", + __func__); + + return; + } + + /* Create a debug fs directory entry (root). */ + sm_state->dir_root = debugfs_create_dir(VC_SM_DIR_ROOT_NAME, NULL); + + sm_state->dir_state.show = &vc_sm_cma_global_state_show; + sm_state->dir_state.dir_entry = + debugfs_create_file(VC_SM_STATE, 0444, sm_state->dir_root, + &sm_state->dir_state, + &vc_sm_cma_debug_fs_fops); + + INIT_LIST_HEAD(&sm_state->buffer_list); + + /* Create a shared memory device. */ + sm_state->misc_dev.minor = MISC_DYNAMIC_MINOR; + sm_state->misc_dev.name = DEVICE_NAME; + sm_state->misc_dev.fops = &vc_sm_ops; + sm_state->misc_dev.parent = NULL; + /* Temporarily set as 666 until udev rules have been sorted */ + sm_state->misc_dev.mode = 0666; + ret = misc_register(&sm_state->misc_dev); + if (ret) { + pr_err("vcsm-cma: failed to register misc device.\n"); + goto err_remove_debugfs; + } + + sm_state->data_knl = vc_sm_cma_create_priv_data(0); + if (!sm_state->data_knl) { + pr_err("[%s]: failed to create kernel private data tracker\n", + __func__); + goto err_remove_misc_dev; + } + + version.version = 2; + ret = vc_sm_cma_vchi_client_version(sm_state->sm_handle, &version, + &version_result, + &sm_state->int_trans_id); + if (ret) { + pr_err("[%s]: Failed to send version request %d\n", __func__, + ret); + } + + /* Done! */ + sm_inited = 1; + pr_info("[%s]: installed successfully\n", __func__); + return; + +err_remove_misc_dev: + misc_deregister(&sm_state->misc_dev); +err_remove_debugfs: + debugfs_remove_recursive(sm_state->dir_root); + vc_sm_cma_vchi_stop(sm_state->vchiq_instance, &sm_state->sm_handle); +} + +/* Driver loading. */ +static int bcm2835_vc_sm_cma_probe(struct vchiq_device *device) +{ + int err; + + pr_info("%s: Videocore shared memory driver\n", __func__); + + err = dma_set_mask_and_coherent(&device->dev, DMA_BIT_MASK(32)); + if (err) { + dev_err(&device->dev, "dma_set_mask_and_coherent failed: %d\n", + err); + return err; + } + + sm_state = devm_kzalloc(&device->dev, sizeof(*sm_state), GFP_KERNEL); + if (!sm_state) + return -ENOMEM; + sm_state->device = device; + mutex_init(&sm_state->map_lock); + + spin_lock_init(&sm_state->kernelid_map_lock); + idr_init_base(&sm_state->kernelid_map, 1); + + device->dev.dma_parms = devm_kzalloc(&device->dev, + sizeof(*device->dev.dma_parms), + GFP_KERNEL); + /* dma_set_max_seg_size checks if dma_parms is NULL. */ + dma_set_max_seg_size(&device->dev, 0x3FFFFFFF); + + vchiq_add_connected_callback(device, vc_sm_connected_init); + return 0; +} + +/* Driver unloading. */ +static void bcm2835_vc_sm_cma_remove(struct vchiq_device *device) +{ + pr_debug("[%s]: start\n", __func__); + if (sm_inited) { + misc_deregister(&sm_state->misc_dev); + + /* Remove all proc entries. */ + debugfs_remove_recursive(sm_state->dir_root); + + /* Stop the videocore shared memory service. */ + vc_sm_cma_vchi_stop(sm_state->vchiq_instance, &sm_state->sm_handle); + } + + if (sm_state) { + idr_destroy(&sm_state->kernelid_map); + + /* Free the memory for the state structure. */ + mutex_destroy(&sm_state->map_lock); + } + + pr_debug("[%s]: end\n", __func__); +} + +/* Kernel API calls */ +/* Get an internal resource handle mapped from the external one. */ +int vc_sm_cma_int_handle(void *handle) +{ + struct dma_buf *dma_buf = (struct dma_buf *)handle; + struct vc_sm_buffer *buf; + + /* Validate we can work with this device. */ + if (!sm_state || !handle) { + pr_err("[%s]: invalid input\n", __func__); + return 0; + } + + buf = (struct vc_sm_buffer *)dma_buf->priv; + return buf->vc_handle; +} +EXPORT_SYMBOL_GPL(vc_sm_cma_int_handle); + +/* Free a previously allocated shared memory handle and block. */ +int vc_sm_cma_free(void *handle) +{ + struct dma_buf *dma_buf = (struct dma_buf *)handle; + + /* Validate we can work with this device. */ + if (!sm_state || !handle) { + pr_err("[%s]: invalid input\n", __func__); + return -EPERM; + } + + pr_debug("%s: handle %p/dmabuf %p\n", __func__, handle, dma_buf); + + dma_buf_put(dma_buf); + + return 0; +} +EXPORT_SYMBOL_GPL(vc_sm_cma_free); + +/* Import a dmabuf to be shared with VC. */ +int vc_sm_cma_import_dmabuf(struct dma_buf *src_dmabuf, void **handle) +{ + struct dma_buf *new_dma_buf; + int ret; + + /* Validate we can work with this device. */ + if (!sm_state || !src_dmabuf || !handle) { + pr_err("[%s]: invalid input\n", __func__); + return -EPERM; + } + + ret = vc_sm_cma_import_dmabuf_internal(sm_state->data_knl, src_dmabuf, + -1, &new_dma_buf); + + if (!ret) { + pr_debug("%s: imported to ptr %p\n", __func__, new_dma_buf); + + /* Assign valid handle at this time.*/ + *handle = new_dma_buf; + } else { + /* + * succeeded in importing the dma_buf, but then + * failed to look it up again. How? + * Release the fd again. + */ + pr_err("%s: imported vc_sm_cma_get_buffer failed %d\n", + __func__, ret); + } + + return ret; +} +EXPORT_SYMBOL_GPL(vc_sm_cma_import_dmabuf); + +static struct vchiq_driver bcm2835_vcsm_cma_driver = { + .probe = bcm2835_vc_sm_cma_probe, + .remove = bcm2835_vc_sm_cma_remove, + .driver = { + .name = DEVICE_NAME, + .owner = THIS_MODULE, + }, +}; + +module_vchiq_driver(bcm2835_vcsm_cma_driver); + +MODULE_AUTHOR("Dave Stevenson"); +MODULE_DESCRIPTION("VideoCore CMA Shared Memory Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("vcsm-cma"); diff --git a/drivers/staging/vc04_services/vc-sm-cma/vc_sm.h b/drivers/staging/vc04_services/vc-sm-cma/vc_sm.h new file mode 100644 index 00000000000000..2f0dc7045da650 --- /dev/null +++ b/drivers/staging/vc04_services/vc-sm-cma/vc_sm.h @@ -0,0 +1,84 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* + * VideoCore Shared Memory driver using CMA. + * + * Copyright: 2018, Raspberry Pi (Trading) Ltd + * + */ + +#ifndef VC_SM_H +#define VC_SM_H + +#include <linux/device.h> +#include <linux/dma-direction.h> +#include <linux/kref.h> +#include <linux/mm_types.h> +#include <linux/mutex.h> +#include <linux/rbtree.h> +#include <linux/sched.h> +#include <linux/shrinker.h> +#include <linux/types.h> +#include <linux/miscdevice.h> + +#define VC_SM_MAX_NAME_LEN 32 + +enum vc_sm_vpu_mapping_state { + VPU_NOT_MAPPED, + VPU_MAPPED, + VPU_UNMAPPING +}; + +struct vc_sm_alloc_data { + unsigned long num_pages; + void *priv_virt; + struct sg_table *sg_table; +}; + +struct vc_sm_imported { + struct dma_buf *dma_buf; + struct dma_buf_attachment *attach; + struct sg_table *sgt; +}; + +struct vc_sm_buffer { + struct list_head global_buffer_list; /* Global list of buffers. */ + + /* Index in the kernel_id idr so that we can find the + * mmal_msg_context again when servicing the VCHI reply. + */ + int kernel_id; + + size_t size; + + /* Lock over all the following state for this buffer */ + struct mutex lock; + struct list_head attachments; + + char name[VC_SM_MAX_NAME_LEN]; + + bool in_use:1; /* Kernel is still using this resource */ + bool imported:1; /* Imported dmabuf */ + + enum vc_sm_vpu_mapping_state vpu_state; + u32 vc_handle; /* VideoCore handle for this buffer */ + int vpu_allocated; /* + * The VPU made this allocation. Release the + * local dma_buf when the VPU releases the + * resource. + */ + + /* DMABUF related fields */ + struct dma_buf *dma_buf; + dma_addr_t dma_addr; + void *cookie; + + struct vc_sm_privdata_t *private; + + union { + struct vc_sm_alloc_data alloc; + struct vc_sm_imported import; + }; +}; + +#endif diff --git a/drivers/staging/vc04_services/vc-sm-cma/vc_sm_cma_vchi.c b/drivers/staging/vc04_services/vc-sm-cma/vc_sm_cma_vchi.c new file mode 100644 index 00000000000000..ddfef55d289d4a --- /dev/null +++ b/drivers/staging/vc04_services/vc-sm-cma/vc_sm_cma_vchi.c @@ -0,0 +1,511 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * VideoCore Shared Memory CMA allocator + * + * Copyright: 2018, Raspberry Pi (Trading) Ltd + * Copyright 2011-2012 Broadcom Corporation. All rights reserved. + * + * Based on vmcs_sm driver from Broadcom Corporation. + * + */ + +/* ---- Include Files ----------------------------------------------------- */ +#include <linux/completion.h> +#include <linux/kernel.h> +#include <linux/kthread.h> +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/semaphore.h> +#include <linux/slab.h> +#include <linux/types.h> + +#include "vc_sm_cma_vchi.h" + +#define VC_SM_VER 1 +#define VC_SM_MIN_VER 0 + +/* ---- Private Constants and Types -------------------------------------- */ + +/* Command blocks come from a pool */ +#define SM_MAX_NUM_CMD_RSP_BLKS 32 + +/* The number of supported connections */ +#define SM_MAX_NUM_CONNECTIONS 3 + +struct sm_cmd_rsp_blk { + struct list_head head; /* To create lists */ + /* To be signaled when the response is there */ + struct completion cmplt; + + u32 id; + u16 length; + + u8 msg[VC_SM_MAX_MSG_LEN]; + + uint32_t wait:1; + uint32_t sent:1; + uint32_t alloc:1; + +}; + +struct sm_instance { + u32 num_connections; + unsigned int service_handle[SM_MAX_NUM_CONNECTIONS]; + struct task_struct *io_thread; + struct completion io_cmplt; + + vpu_event_cb vpu_event; + + /* Mutex over the following lists */ + struct mutex lock; + u32 trans_id; + struct list_head cmd_list; + struct list_head rsp_list; + struct list_head dead_list; + + struct sm_cmd_rsp_blk free_blk[SM_MAX_NUM_CMD_RSP_BLKS]; + + /* Mutex over the free_list */ + struct mutex free_lock; + struct list_head free_list; + + struct semaphore free_sema; + struct vchiq_instance *vchiq_instance; +}; + +/* ---- Private Variables ------------------------------------------------ */ + +/* ---- Private Function Prototypes -------------------------------------- */ + +/* ---- Private Functions ------------------------------------------------ */ +static int +bcm2835_vchi_msg_queue(struct vchiq_instance *vchiq_instance, unsigned int handle, + void *data, + unsigned int size) +{ + return vchiq_queue_kernel_message(vchiq_instance, handle, data, size); +} + +static struct +sm_cmd_rsp_blk *vc_vchi_cmd_create(struct sm_instance *instance, + enum vc_sm_msg_type id, void *msg, + u32 size, int wait) +{ + struct sm_cmd_rsp_blk *blk; + struct vc_sm_msg_hdr_t *hdr; + + if (down_interruptible(&instance->free_sema)) { + blk = kmalloc(sizeof(*blk), GFP_KERNEL); + if (!blk) + return NULL; + + blk->alloc = 1; + init_completion(&blk->cmplt); + } else { + mutex_lock(&instance->free_lock); + blk = + list_first_entry(&instance->free_list, + struct sm_cmd_rsp_blk, head); + list_del(&blk->head); + mutex_unlock(&instance->free_lock); + } + + blk->sent = 0; + blk->wait = wait; + blk->length = sizeof(*hdr) + size; + + hdr = (struct vc_sm_msg_hdr_t *)blk->msg; + hdr->type = id; + mutex_lock(&instance->lock); + instance->trans_id++; + /* + * Retain the top bit for identifying asynchronous events, or VPU cmds. + */ + instance->trans_id &= ~0x80000000; + hdr->trans_id = instance->trans_id; + blk->id = instance->trans_id; + mutex_unlock(&instance->lock); + + if (size) + memcpy(hdr->body, msg, size); + + return blk; +} + +static void +vc_vchi_cmd_delete(struct sm_instance *instance, struct sm_cmd_rsp_blk *blk) +{ + if (blk->alloc) { + kfree(blk); + return; + } + + mutex_lock(&instance->free_lock); + list_add(&blk->head, &instance->free_list); + mutex_unlock(&instance->free_lock); + up(&instance->free_sema); +} + +static void vc_sm_cma_vchi_rx_ack(struct sm_instance *instance, + struct sm_cmd_rsp_blk *cmd, + struct vc_sm_result_t *reply, + u32 reply_len) +{ + mutex_lock(&instance->lock); + list_for_each_entry(cmd, + &instance->rsp_list, + head) { + if (cmd->id == reply->trans_id) + break; + } + mutex_unlock(&instance->lock); + + if (&cmd->head == &instance->rsp_list) { + //pr_debug("%s: received response %u, throw away...", + pr_err("%s: received response %u, throw away...", + __func__, + reply->trans_id); + } else if (reply_len > sizeof(cmd->msg)) { + pr_err("%s: reply too big (%u) %u, throw away...", + __func__, reply_len, + reply->trans_id); + } else { + memcpy(cmd->msg, reply, + reply_len); + complete(&cmd->cmplt); + } +} + +static int vc_sm_cma_vchi_videocore_io(void *arg) +{ + struct sm_instance *instance = arg; + struct sm_cmd_rsp_blk *cmd = NULL, *cmd_tmp; + struct vc_sm_result_t *reply; + struct vchiq_header *header; + s32 status; + int svc_use = 1; + + while (1) { + if (svc_use) + vchiq_release_service(instance->vchiq_instance, instance->service_handle[0]); + svc_use = 0; + + if (wait_for_completion_interruptible(&instance->io_cmplt)) + continue; + vchiq_use_service(instance->vchiq_instance, instance->service_handle[0]); + svc_use = 1; + + do { + /* + * Get new command and move it to response list + */ + mutex_lock(&instance->lock); + if (list_empty(&instance->cmd_list)) { + /* no more commands to process */ + mutex_unlock(&instance->lock); + break; + } + cmd = list_first_entry(&instance->cmd_list, + struct sm_cmd_rsp_blk, head); + list_move(&cmd->head, &instance->rsp_list); + cmd->sent = 1; + mutex_unlock(&instance->lock); + /* Send the command */ + status = + bcm2835_vchi_msg_queue(instance->vchiq_instance, + instance->service_handle[0], + cmd->msg, cmd->length); + if (status) { + pr_err("%s: failed to queue message (%d)", + __func__, status); + } + + /* If no reply is needed then we're done */ + if (!cmd->wait) { + mutex_lock(&instance->lock); + list_del(&cmd->head); + mutex_unlock(&instance->lock); + vc_vchi_cmd_delete(instance, cmd); + continue; + } + + if (status) { + complete(&cmd->cmplt); + continue; + } + + } while (1); + + while ((header = vchiq_msg_hold(instance->vchiq_instance, + instance->service_handle[0]))) { + reply = (struct vc_sm_result_t *)header->data; + if (reply->trans_id & 0x80000000) { + /* Async event or cmd from the VPU */ + if (instance->vpu_event) + instance->vpu_event(instance, reply, + header->size); + } else { + vc_sm_cma_vchi_rx_ack(instance, cmd, reply, + header->size); + } + + vchiq_release_message(instance->vchiq_instance, + instance->service_handle[0], + header); + } + + /* Go through the dead list and free them */ + mutex_lock(&instance->lock); + list_for_each_entry_safe(cmd, cmd_tmp, &instance->dead_list, + head) { + list_del(&cmd->head); + vc_vchi_cmd_delete(instance, cmd); + } + mutex_unlock(&instance->lock); + } + + return 0; +} + +static int vc_sm_cma_vchi_callback(struct vchiq_instance *vchiq_instance, + enum vchiq_reason reason, + struct vchiq_header *header, + unsigned int handle, void *userdata) +{ + struct sm_instance *instance = vchiq_get_service_userdata(vchiq_instance, handle); + + switch (reason) { + case VCHIQ_MESSAGE_AVAILABLE: + vchiq_msg_queue_push(vchiq_instance, handle, header); + complete(&instance->io_cmplt); + break; + + case VCHIQ_SERVICE_CLOSED: + pr_info("%s: service CLOSED!!", __func__); + break; + + default: + break; + } + + return 0; +} + +struct sm_instance *vc_sm_cma_vchi_init(struct vchiq_instance *vchiq_instance, + unsigned int num_connections, + vpu_event_cb vpu_event) +{ + u32 i; + struct sm_instance *instance; + int status; + + pr_debug("%s: start", __func__); + + if (num_connections > SM_MAX_NUM_CONNECTIONS) { + pr_err("%s: unsupported number of connections %u (max=%u)", + __func__, num_connections, SM_MAX_NUM_CONNECTIONS); + + goto err_null; + } + /* Allocate memory for this instance */ + instance = kzalloc(sizeof(*instance), GFP_KERNEL); + + /* Misc initialisations */ + mutex_init(&instance->lock); + init_completion(&instance->io_cmplt); + INIT_LIST_HEAD(&instance->cmd_list); + INIT_LIST_HEAD(&instance->rsp_list); + INIT_LIST_HEAD(&instance->dead_list); + INIT_LIST_HEAD(&instance->free_list); + sema_init(&instance->free_sema, SM_MAX_NUM_CMD_RSP_BLKS); + mutex_init(&instance->free_lock); + for (i = 0; i < SM_MAX_NUM_CMD_RSP_BLKS; i++) { + init_completion(&instance->free_blk[i].cmplt); + list_add(&instance->free_blk[i].head, &instance->free_list); + } + + instance->vchiq_instance = vchiq_instance; + + /* Open the VCHI service connections */ + instance->num_connections = num_connections; + for (i = 0; i < num_connections; i++) { + struct vchiq_service_params_kernel params = { + .version = VC_SM_VER, + .version_min = VC_SM_MIN_VER, + .fourcc = VCHIQ_MAKE_FOURCC('S', 'M', 'E', 'M'), + .callback = vc_sm_cma_vchi_callback, + .userdata = instance, + }; + + status = vchiq_open_service(vchiq_instance, ¶ms, + &instance->service_handle[i]); + if (status) { + pr_err("%s: failed to open VCHI service (%d)", + __func__, status); + + goto err_close_services; + } + } + /* Create the thread which takes care of all io to/from videoocore. */ + instance->io_thread = kthread_create(&vc_sm_cma_vchi_videocore_io, + (void *)instance, "SMIO"); + if (!instance->io_thread) { + pr_err("%s: failed to create SMIO thread", __func__); + + goto err_close_services; + } + instance->vpu_event = vpu_event; + set_user_nice(instance->io_thread, -10); + wake_up_process(instance->io_thread); + + pr_debug("%s: success - instance %p", __func__, instance); + return instance; + +err_close_services: + for (i = 0; i < instance->num_connections; i++) { + if (instance->service_handle[i]) + vchiq_close_service(vchiq_instance, instance->service_handle[i]); + } + kfree(instance); +err_null: + pr_debug("%s: FAILED", __func__); + return NULL; +} + +int vc_sm_cma_vchi_stop(struct vchiq_instance *vchiq_instance, struct sm_instance **handle) +{ + struct sm_instance *instance; + u32 i; + + if (!handle) { + pr_err("%s: invalid pointer to handle %p", __func__, handle); + goto lock; + } + + if (!*handle) { + pr_err("%s: invalid handle %p", __func__, *handle); + goto lock; + } + + instance = *handle; + + /* Close all VCHI service connections */ + for (i = 0; i < instance->num_connections; i++) { + vchiq_use_service(vchiq_instance, instance->service_handle[i]); + vchiq_close_service(vchiq_instance, instance->service_handle[i]); + } + + kfree(instance); + + *handle = NULL; + return 0; + +lock: + return -EINVAL; +} + +static int vc_sm_cma_vchi_send_msg(struct sm_instance *handle, + enum vc_sm_msg_type msg_id, void *msg, + u32 msg_size, void *result, u32 result_size, + u32 *cur_trans_id, u8 wait_reply) +{ + int status = 0; + struct sm_instance *instance = handle; + struct sm_cmd_rsp_blk *cmd_blk; + + if (!handle) { + pr_err("%s: invalid handle", __func__); + return -EINVAL; + } + if (!msg) { + pr_err("%s: invalid msg pointer", __func__); + return -EINVAL; + } + + cmd_blk = + vc_vchi_cmd_create(instance, msg_id, msg, msg_size, wait_reply); + if (!cmd_blk) { + pr_err("[%s]: failed to allocate global tracking resource", + __func__); + return -ENOMEM; + } + + if (cur_trans_id) + *cur_trans_id = cmd_blk->id; + + mutex_lock(&instance->lock); + list_add_tail(&cmd_blk->head, &instance->cmd_list); + mutex_unlock(&instance->lock); + complete(&instance->io_cmplt); + + if (!wait_reply) + /* We're done */ + return 0; + + /* Wait for the response */ + if (wait_for_completion_interruptible(&cmd_blk->cmplt)) { + mutex_lock(&instance->lock); + if (!cmd_blk->sent) { + list_del(&cmd_blk->head); + mutex_unlock(&instance->lock); + vc_vchi_cmd_delete(instance, cmd_blk); + return -ENXIO; + } + + list_move(&cmd_blk->head, &instance->dead_list); + mutex_unlock(&instance->lock); + complete(&instance->io_cmplt); + return -EINTR; /* We're done */ + } + + if (result && result_size) { + memcpy(result, cmd_blk->msg, result_size); + } else { + struct vc_sm_result_t *res = + (struct vc_sm_result_t *)cmd_blk->msg; + status = (res->success == 0) ? 0 : -ENXIO; + } + + mutex_lock(&instance->lock); + list_del(&cmd_blk->head); + mutex_unlock(&instance->lock); + vc_vchi_cmd_delete(instance, cmd_blk); + return status; +} + +int vc_sm_cma_vchi_free(struct sm_instance *handle, struct vc_sm_free_t *msg, + u32 *cur_trans_id) +{ + return vc_sm_cma_vchi_send_msg(handle, VC_SM_MSG_TYPE_FREE, + msg, sizeof(*msg), 0, 0, cur_trans_id, 0); +} + +int vc_sm_cma_vchi_import(struct sm_instance *handle, struct vc_sm_import *msg, + struct vc_sm_import_result *result, u32 *cur_trans_id) +{ + return vc_sm_cma_vchi_send_msg(handle, VC_SM_MSG_TYPE_IMPORT, + msg, sizeof(*msg), result, sizeof(*result), + cur_trans_id, 1); +} + +int vc_sm_cma_vchi_client_version(struct sm_instance *handle, + struct vc_sm_version *msg, + struct vc_sm_result_t *result, + u32 *cur_trans_id) +{ + return vc_sm_cma_vchi_send_msg(handle, VC_SM_MSG_TYPE_CLIENT_VERSION, + //msg, sizeof(*msg), result, sizeof(*result), + //cur_trans_id, 1); + msg, sizeof(*msg), NULL, 0, + cur_trans_id, 0); +} + +int vc_sm_vchi_client_vc_mem_req_reply(struct sm_instance *handle, + struct vc_sm_vc_mem_request_result *msg, + uint32_t *cur_trans_id) +{ + return vc_sm_cma_vchi_send_msg(handle, + VC_SM_MSG_TYPE_VC_MEM_REQUEST_REPLY, + msg, sizeof(*msg), 0, 0, cur_trans_id, + 0); +} diff --git a/drivers/staging/vc04_services/vc-sm-cma/vc_sm_cma_vchi.h b/drivers/staging/vc04_services/vc-sm-cma/vc_sm_cma_vchi.h new file mode 100644 index 00000000000000..5dd23ad408fc1a --- /dev/null +++ b/drivers/staging/vc04_services/vc-sm-cma/vc_sm_cma_vchi.h @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* + * VideoCore Shared Memory CMA allocator + * + * Copyright: 2018, Raspberry Pi (Trading) Ltd + * Copyright 2011-2012 Broadcom Corporation. All rights reserved. + * + * Based on vmcs_sm driver from Broadcom Corporation. + * + */ + +#ifndef __VC_SM_CMA_VCHI_H__INCLUDED__ +#define __VC_SM_CMA_VCHI_H__INCLUDED__ + +#include "../include/linux/raspberrypi/vchiq.h" + +#include "vc_sm_defs.h" + +/* + * Forward declare. + */ +struct sm_instance; + +typedef void (*vpu_event_cb)(struct sm_instance *instance, + struct vc_sm_result_t *reply, int reply_len); + +/* + * Initialize the shared memory service, opens up vchi connection to talk to it. + */ +struct sm_instance *vc_sm_cma_vchi_init(struct vchiq_instance *vchi_instance, + unsigned int num_connections, + vpu_event_cb vpu_event); + +/* + * Terminates the shared memory service. + */ +int vc_sm_cma_vchi_stop(struct vchiq_instance *vchi_instance, struct sm_instance **handle); + +/* + * Ask the shared memory service to free up some memory that was previously + * allocated by the vc_sm_cma_vchi_alloc function call. + */ +int vc_sm_cma_vchi_free(struct sm_instance *handle, struct vc_sm_free_t *msg, + u32 *cur_trans_id); + +/* + * Import a contiguous block of memory and wrap it in a GPU MEM_HANDLE_T. + */ +int vc_sm_cma_vchi_import(struct sm_instance *handle, struct vc_sm_import *msg, + struct vc_sm_import_result *result, + u32 *cur_trans_id); + +int vc_sm_cma_vchi_client_version(struct sm_instance *handle, + struct vc_sm_version *msg, + struct vc_sm_result_t *result, + u32 *cur_trans_id); + +int vc_sm_vchi_client_vc_mem_req_reply(struct sm_instance *handle, + struct vc_sm_vc_mem_request_result *msg, + uint32_t *cur_trans_id); + +#endif /* __VC_SM_CMA_VCHI_H__INCLUDED__ */ diff --git a/drivers/staging/vc04_services/vc-sm-cma/vc_sm_defs.h b/drivers/staging/vc04_services/vc-sm-cma/vc_sm_defs.h new file mode 100644 index 00000000000000..4e6354000dfdc2 --- /dev/null +++ b/drivers/staging/vc04_services/vc-sm-cma/vc_sm_defs.h @@ -0,0 +1,297 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* + * VideoCore Shared Memory CMA allocator + * + * Copyright: 2018, Raspberry Pi (Trading) Ltd + * + * Based on vc_sm_defs.h from the vmcs_sm driver Copyright Broadcom Corporation. + * All IPC messages are copied across to this file, even if the vc-sm-cma + * driver is not currently using them. + * + **************************************************************************** + */ + +#ifndef __VC_SM_DEFS_H__INCLUDED__ +#define __VC_SM_DEFS_H__INCLUDED__ + +/* Maximum message length */ +#define VC_SM_MAX_MSG_LEN (sizeof(union vc_sm_msg_union_t) + \ + sizeof(struct vc_sm_msg_hdr_t)) +#define VC_SM_MAX_RSP_LEN (sizeof(union vc_sm_msg_union_t)) + +/* Resource name maximum size */ +#define VC_SM_RESOURCE_NAME 32 + +/* + * Version to be reported to the VPU + * VPU assumes 0 (aka 1) which does not require the released callback, nor + * expect the client to handle VC_MEM_REQUESTS. + * Version 2 requires the released callback, and must support VC_MEM_REQUESTS. + */ +#define VC_SM_PROTOCOL_VERSION 2 + +enum vc_sm_msg_type { + /* Message types supported for HOST->VC direction */ + + /* Allocate shared memory block */ + VC_SM_MSG_TYPE_ALLOC, + /* Lock allocated shared memory block */ + VC_SM_MSG_TYPE_LOCK, + /* Unlock allocated shared memory block */ + VC_SM_MSG_TYPE_UNLOCK, + /* Unlock allocated shared memory block, do not answer command */ + VC_SM_MSG_TYPE_UNLOCK_NOANS, + /* Free shared memory block */ + VC_SM_MSG_TYPE_FREE, + /* Resize a shared memory block */ + VC_SM_MSG_TYPE_RESIZE, + /* Walk the allocated shared memory block(s) */ + VC_SM_MSG_TYPE_WALK_ALLOC, + + /* A previously applied action will need to be reverted */ + VC_SM_MSG_TYPE_ACTION_CLEAN, + + /* + * Import a physical address and wrap into a MEM_HANDLE_T. + * Release with VC_SM_MSG_TYPE_FREE. + */ + VC_SM_MSG_TYPE_IMPORT, + /* + *Tells VC the protocol version supported by this client. + * 2 supports the async/cmd messages from the VPU for final release + * of memory, and for VC allocations. + */ + VC_SM_MSG_TYPE_CLIENT_VERSION, + /* Response to VC request for memory */ + VC_SM_MSG_TYPE_VC_MEM_REQUEST_REPLY, + + /* + * Asynchronous/cmd messages supported for VC->HOST direction. + * Signalled by setting the top bit in vc_sm_result_t trans_id. + */ + + /* + * VC has finished with an imported memory allocation. + * Release any Linux reference counts on the underlying block. + */ + VC_SM_MSG_TYPE_RELEASED, + /* VC request for memory */ + VC_SM_MSG_TYPE_VC_MEM_REQUEST, + + VC_SM_MSG_TYPE_MAX +}; + +/* Type of memory to be allocated */ +enum vc_sm_alloc_type_t { + VC_SM_ALLOC_CACHED, + VC_SM_ALLOC_NON_CACHED, +}; + +/* Message header for all messages in HOST->VC direction */ +struct vc_sm_msg_hdr_t { + u32 type; + u32 trans_id; + u8 body[0]; + +}; + +/* Request to allocate memory (HOST->VC) */ +struct vc_sm_alloc_t { + /* type of memory to allocate */ + enum vc_sm_alloc_type_t type; + /* byte amount of data to allocate per unit */ + u32 base_unit; + /* number of unit to allocate */ + u32 num_unit; + /* alignment to be applied on allocation */ + u32 alignment; + /* identity of who allocated this block */ + u32 allocator; + /* resource name (for easier tracking on vc side) */ + char name[VC_SM_RESOURCE_NAME]; + +}; + +/* Result of a requested memory allocation (VC->HOST) */ +struct vc_sm_alloc_result_t { + /* Transaction identifier */ + u32 trans_id; + + /* Resource handle */ + u32 res_handle; + /* Pointer to resource buffer */ + u32 res_mem; + /* Resource base size (bytes) */ + u32 res_base_size; + /* Resource number */ + u32 res_num; + +}; + +/* Request to free a previously allocated memory (HOST->VC) */ +struct vc_sm_free_t { + /* Resource handle (returned from alloc) */ + u32 res_handle; + /* Resource buffer (returned from alloc) */ + u32 res_mem; + +}; + +/* Request to lock a previously allocated memory (HOST->VC) */ +struct vc_sm_lock_unlock_t { + /* Resource handle (returned from alloc) */ + u32 res_handle; + /* Resource buffer (returned from alloc) */ + u32 res_mem; + +}; + +/* Request to resize a previously allocated memory (HOST->VC) */ +struct vc_sm_resize_t { + /* Resource handle (returned from alloc) */ + u32 res_handle; + /* Resource buffer (returned from alloc) */ + u32 res_mem; + /* Resource *new* size requested (bytes) */ + u32 res_new_size; + +}; + +/* Result of a requested memory lock (VC->HOST) */ +struct vc_sm_lock_result_t { + /* Transaction identifier */ + u32 trans_id; + + /* Resource handle */ + u32 res_handle; + /* Pointer to resource buffer */ + u32 res_mem; + /* + * Pointer to former resource buffer if the memory + * was reallocated + */ + u32 res_old_mem; + +}; + +/* Generic result for a request (VC->HOST) */ +struct vc_sm_result_t { + /* Transaction identifier */ + u32 trans_id; + + s32 success; + +}; + +/* Request to revert a previously applied action (HOST->VC) */ +struct vc_sm_action_clean_t { + /* Action of interest */ + enum vc_sm_msg_type res_action; + /* Transaction identifier for the action of interest */ + u32 action_trans_id; + +}; + +/* Request to remove all data associated with a given allocator (HOST->VC) */ +struct vc_sm_free_all_t { + /* Allocator identifier */ + u32 allocator; +}; + +/* Request to import memory (HOST->VC) */ +struct vc_sm_import { + /* type of memory to allocate */ + enum vc_sm_alloc_type_t type; + /* pointer to the VC (ie physical) address of the allocated memory */ + u32 addr; + /* size of buffer */ + u32 size; + /* opaque handle returned in RELEASED messages */ + u32 kernel_id; + /* Allocator identifier */ + u32 allocator; + /* resource name (for easier tracking on vc side) */ + char name[VC_SM_RESOURCE_NAME]; +}; + +/* Result of a requested memory import (VC->HOST) */ +struct vc_sm_import_result { + /* Transaction identifier */ + u32 trans_id; + + /* Resource handle */ + u32 res_handle; +}; + +/* Notification that VC has finished with an allocation (VC->HOST) */ +struct vc_sm_released { + /* cmd type / trans_id */ + u32 cmd; + + /* pointer to the VC (ie physical) address of the allocated memory */ + u32 addr; + /* size of buffer */ + u32 size; + /* opaque handle returned in RELEASED messages */ + u32 kernel_id; + u32 vc_handle; +}; + +/* + * Client informing VC as to the protocol version it supports. + * >=2 requires the released callback, and supports VC asking for memory. + * Failure means that the firmware doesn't support this call, and therefore the + * client should either fail, or NOT rely on getting the released callback. + */ +struct vc_sm_version { + u32 version; +}; + +/* Request FROM VideoCore for some memory */ +struct vc_sm_vc_mem_request { + /* cmd type */ + u32 cmd; + + /* trans_id (from VPU) */ + u32 trans_id; + /* size of buffer */ + u32 size; + /* alignment of buffer */ + u32 align; + /* resource name (for easier tracking) */ + char name[VC_SM_RESOURCE_NAME]; + /* VPU handle for the resource */ + u32 vc_handle; +}; + +/* Response from the kernel to provide the VPU with some memory */ +struct vc_sm_vc_mem_request_result { + /* Transaction identifier for the VPU */ + u32 trans_id; + /* pointer to the physical address of the allocated memory */ + u32 addr; + /* opaque handle returned in RELEASED messages */ + u32 kernel_id; +}; + +/* Union of ALL messages */ +union vc_sm_msg_union_t { + struct vc_sm_alloc_t alloc; + struct vc_sm_alloc_result_t alloc_result; + struct vc_sm_free_t free; + struct vc_sm_lock_unlock_t lock_unlock; + struct vc_sm_action_clean_t action_clean; + struct vc_sm_resize_t resize; + struct vc_sm_lock_result_t lock_result; + struct vc_sm_result_t result; + struct vc_sm_free_all_t free_all; + struct vc_sm_import import; + struct vc_sm_import_result import_result; + struct vc_sm_version version; + struct vc_sm_released released; + struct vc_sm_vc_mem_request vc_request; + struct vc_sm_vc_mem_request_result vc_request_result; +}; + +#endif /* __VC_SM_DEFS_H__INCLUDED__ */ diff --git a/drivers/staging/vc04_services/vc-sm-cma/vc_sm_knl.h b/drivers/staging/vc04_services/vc-sm-cma/vc_sm_knl.h new file mode 100644 index 00000000000000..988fdd967922b7 --- /dev/null +++ b/drivers/staging/vc04_services/vc-sm-cma/vc_sm_knl.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* + * VideoCore Shared Memory CMA allocator + * + * Copyright: 2018, Raspberry Pi (Trading) Ltd + * + * Based on vc_sm_defs.h from the vmcs_sm driver Copyright Broadcom Corporation. + * + */ + +#ifndef __VC_SM_KNL_H__INCLUDED__ +#define __VC_SM_KNL_H__INCLUDED__ + +#if !defined(__KERNEL__) +#error "This interface is for kernel use only..." +#endif + +/* Free a previously allocated or imported shared memory handle and block. */ +int vc_sm_cma_free(void *handle); + +/* Get an internal resource handle mapped from the external one. */ +int vc_sm_cma_int_handle(void *handle); + +/* Import a block of memory into the GPU space. */ +int vc_sm_cma_import_dmabuf(struct dma_buf *dmabuf, void **handle); + +#endif /* __VC_SM_KNL_H__INCLUDED__ */ diff --git a/drivers/staging/vc04_services/vchiq-mmal/Kconfig b/drivers/staging/vc04_services/vchiq-mmal/Kconfig index c99525a0bb4525..5df9198cdab178 100644 --- a/drivers/staging/vc04_services/vchiq-mmal/Kconfig +++ b/drivers/staging/vc04_services/vchiq-mmal/Kconfig @@ -1,6 +1,7 @@ config BCM2835_VCHIQ_MMAL tristate "BCM2835 MMAL VCHIQ service" - depends on BCM2835_VCHIQ + select BCM2835_VCHIQ + select BCM_VC_SM_CMA help Enables the MMAL API over VCHIQ interface as used for the majority of the multimedia services on VideoCore. diff --git a/drivers/staging/vc04_services/vchiq-mmal/mmal-common.h b/drivers/staging/vc04_services/vchiq-mmal/mmal-common.h index b33129403a3034..a643cad54b1200 100644 --- a/drivers/staging/vc04_services/vchiq-mmal/mmal-common.h +++ b/drivers/staging/vc04_services/vchiq-mmal/mmal-common.h @@ -50,6 +50,11 @@ struct mmal_buffer { struct mmal_msg_context *msg_context; + struct dma_buf *dma_buf;/* Exported dmabuf fd from videobuf2 */ + void *vcsm_handle; /* VCSM handle having imported the dmabuf */ + u32 vc_handle; /* VC handle to that dmabuf */ + + u32 cmd; /* MMAL command. 0=data. */ unsigned long length; u32 mmal_flags; s64 dts; diff --git a/drivers/staging/vc04_services/vchiq-mmal/mmal-encodings.h b/drivers/staging/vc04_services/vchiq-mmal/mmal-encodings.h index e15ae7b24f73fd..d8d7ec5b962cb1 100644 --- a/drivers/staging/vc04_services/vchiq-mmal/mmal-encodings.h +++ b/drivers/staging/vc04_services/vchiq-mmal/mmal-encodings.h @@ -69,10 +69,76 @@ */ #define MMAL_ENCODING_OPAQUE MMAL_FOURCC('O', 'P', 'Q', 'V') +/* Bayer formats + * FourCC values copied from V4L2 where defined. + */ +/* 8 bit per pixel Bayer formats. */ +#define MMAL_ENCODING_BAYER_SBGGR8 MMAL_FOURCC('B', 'A', '8', '1') +#define MMAL_ENCODING_BAYER_SGBRG8 MMAL_FOURCC('G', 'B', 'R', 'G') +#define MMAL_ENCODING_BAYER_SGRBG8 MMAL_FOURCC('G', 'R', 'B', 'G') +#define MMAL_ENCODING_BAYER_SRGGB8 MMAL_FOURCC('R', 'G', 'G', 'B') + +/* 10 bit per pixel packed Bayer formats. */ +#define MMAL_ENCODING_BAYER_SBGGR10P MMAL_FOURCC('p', 'B', 'A', 'A') +#define MMAL_ENCODING_BAYER_SGRBG10P MMAL_FOURCC('p', 'g', 'A', 'A') +#define MMAL_ENCODING_BAYER_SGBRG10P MMAL_FOURCC('p', 'G', 'A', 'A') +#define MMAL_ENCODING_BAYER_SRGGB10P MMAL_FOURCC('p', 'R', 'A', 'A') + +/* 12 bit per pixel packed Bayer formats. */ +#define MMAL_ENCODING_BAYER_SBGGR12P MMAL_FOURCC('p', 'B', '1', '2') +#define MMAL_ENCODING_BAYER_SGRBG12P MMAL_FOURCC('p', 'g', '1', '2') +#define MMAL_ENCODING_BAYER_SGBRG12P MMAL_FOURCC('p', 'G', '1', '2') +#define MMAL_ENCODING_BAYER_SRGGB12P MMAL_FOURCC('p', 'R', '1', '2') + +//14 bit per pixel Bayer formats. +#define MMAL_ENCODING_BAYER_SBGGR14P MMAL_FOURCC('p', 'B', 'E', 'E') +#define MMAL_ENCODING_BAYER_SGBRG14P MMAL_FOURCC('p', 'G', 'E', 'E') +#define MMAL_ENCODING_BAYER_SGRBG14P MMAL_FOURCC('p', 'g', 'E', 'E') +#define MMAL_ENCODING_BAYER_SRGGB14P MMAL_FOURCC('p', 'R', 'E', 'E') + +/* 16 bit per pixel Bayer formats. */ +#define MMAL_ENCODING_BAYER_SBGGR16 MMAL_FOURCC('B', 'G', '1', '6') +#define MMAL_ENCODING_BAYER_SGBRG16 MMAL_FOURCC('G', 'B', '1', '6') +#define MMAL_ENCODING_BAYER_SGRBG16 MMAL_FOURCC('G', 'R', '1', '6') +#define MMAL_ENCODING_BAYER_SRGGB16 MMAL_FOURCC('R', 'G', '1', '6') + +/* 10 bit per pixel unpacked (16bit) Bayer formats. */ +#define MMAL_ENCODING_BAYER_SBGGR10 MMAL_FOURCC('B', 'G', '1', '0') +#define MMAL_ENCODING_BAYER_SGRBG10 MMAL_FOURCC('B', 'A', '1', '0') +#define MMAL_ENCODING_BAYER_SGBRG10 MMAL_FOURCC('G', 'B', '1', '0') +#define MMAL_ENCODING_BAYER_SRGGB10 MMAL_FOURCC('R', 'G', '1', '0') + +/* 12 bit per pixel unpacked (16bit) Bayer formats */ +#define MMAL_ENCODING_BAYER_SBGGR12 MMAL_FOURCC('B', 'G', '1', '2') +#define MMAL_ENCODING_BAYER_SGRBG12 MMAL_FOURCC('B', 'A', '1', '2') +#define MMAL_ENCODING_BAYER_SGBRG12 MMAL_FOURCC('G', 'B', '1', '2') +#define MMAL_ENCODING_BAYER_SRGGB12 MMAL_FOURCC('R', 'G', '1', '2') + +/* 14 bit per pixel unpacked (16bit) Bayer formats */ +#define MMAL_ENCODING_BAYER_SBGGR14 MMAL_FOURCC('B', 'G', '1', '4') +#define MMAL_ENCODING_BAYER_SGBRG14 MMAL_FOURCC('G', 'B', '1', '4') +#define MMAL_ENCODING_BAYER_SGRBG14 MMAL_FOURCC('G', 'R', '1', '4') +#define MMAL_ENCODING_BAYER_SRGGB14 MMAL_FOURCC('R', 'G', '1', '4') + +/* MIPI packed monochrome images */ +#define MMAL_ENCODING_GREY MMAL_FOURCC('G', 'R', 'E', 'Y') +#define MMAL_ENCODING_Y10P MMAL_FOURCC('Y', '1', '0', 'P') +#define MMAL_ENCODING_Y12P MMAL_FOURCC('Y', '1', '2', 'P') +#define MMAL_ENCODING_Y14P MMAL_FOURCC('Y', '1', '4', 'P') +#define MMAL_ENCODING_Y16 MMAL_FOURCC('Y', '1', '6', ' ') +/* Unpacked monochrome formats (16bit per sample, but only N LSBs used) */ +#define MMAL_ENCODING_Y10 MMAL_FOURCC('Y', '1', '0', ' ') +#define MMAL_ENCODING_Y12 MMAL_FOURCC('Y', '1', '2', ' ') +#define MMAL_ENCODING_Y14 MMAL_FOURCC('Y', '1', '4', ' ') + /** An EGL image handle */ #define MMAL_ENCODING_EGL_IMAGE MMAL_FOURCC('E', 'G', 'L', 'I') +/** ISP image statistics format + */ +#define MMAL_ENCODING_BRCM_STATS MMAL_FOURCC('S', 'T', 'A', 'T') + /* }@ */ /** \name Pre-defined audio encodings */ diff --git a/drivers/staging/vc04_services/vchiq-mmal/mmal-msg-format.h b/drivers/staging/vc04_services/vchiq-mmal/mmal-msg-format.h index 5569876d8c7d60..e8f5ca85a7c418 100644 --- a/drivers/staging/vc04_services/vchiq-mmal/mmal-msg-format.h +++ b/drivers/staging/vc04_services/vchiq-mmal/mmal-msg-format.h @@ -53,6 +53,16 @@ union mmal_es_specific_format { struct mmal_subpicture_format subpicture; }; +/* The elementary stream will already be framed */ +#define MMAL_ES_FORMAT_FLAG_FRAMED BIT(0) +/* + * For column formats we ideally want to pass in the column stride. This hasn't + * been the past behaviour, so require a new flag to be set should + * es->video.width be the column stride (in lines) instead of an ignored width + * value. + */ +#define MMAL_ES_FORMAT_FLAG_COL_FMTS_WIDTH_IS_COL_STRIDE BIT(1) + /* Definition of an elementary stream format (MMAL_ES_FORMAT_T) */ struct mmal_es_format_local { u32 type; /* enum mmal_es_type */ diff --git a/drivers/staging/vc04_services/vchiq-mmal/mmal-msg.h b/drivers/staging/vc04_services/vchiq-mmal/mmal-msg.h index 471413248a1400..baf37254645a1c 100644 --- a/drivers/staging/vc04_services/vchiq-mmal/mmal-msg.h +++ b/drivers/staging/vc04_services/vchiq-mmal/mmal-msg.h @@ -253,6 +253,25 @@ struct mmal_msg_port_action_reply { /* Signals that a buffer failed to be transmitted */ #define MMAL_BUFFER_HEADER_FLAG_TRANSMISSION_FAILED BIT(10) +/* Video buffer header flags + * videobufferheaderflags + * The following flags describe properties of a video buffer header. + * As there is no collision with the MMAL_BUFFER_HEADER_FLAGS_ defines, these + * flags will also be present in the MMAL_BUFFER_HEADER_T flags field. + */ +#define MMAL_BUFFER_HEADER_FLAG_FORMAT_SPECIFIC_START_BIT 16 +#define MMAL_BUFFER_HEADER_FLAG_FORMAT_SPECIFIC_START \ + (1 << MMAL_BUFFER_HEADER_FLAG_FORMAT_SPECIFIC_START_BIT) +/* Signals an interlaced video frame */ +#define MMAL_BUFFER_HEADER_VIDEO_FLAG_INTERLACED \ + (MMAL_BUFFER_HEADER_FLAG_FORMAT_SPECIFIC_START << 0) +/* + * Signals that the top field of the current interlaced frame should be + * displayed first + */ +#define MMAL_BUFFER_HEADER_VIDEO_FLAG_TOP_FIELD_FIRST \ + (MMAL_BUFFER_HEADER_FLAG_FORMAT_SPECIFIC_START << 1) + struct mmal_driver_buffer { u32 magic; u32 component_handle; @@ -346,6 +365,41 @@ struct mmal_msg_port_parameter_get_reply { /* event messages */ #define MMAL_WORKER_EVENT_SPACE 256 +/* Four CC's for events */ +#define MMAL_FOURCC(a, b, c, d) ((a) | (b << 8) | (c << 16) | (d << 24)) + +#define MMAL_EVENT_ERROR MMAL_FOURCC('E', 'R', 'R', 'O') +#define MMAL_EVENT_EOS MMAL_FOURCC('E', 'E', 'O', 'S') +#define MMAL_EVENT_FORMAT_CHANGED MMAL_FOURCC('E', 'F', 'C', 'H') +#define MMAL_EVENT_PARAMETER_CHANGED MMAL_FOURCC('E', 'P', 'C', 'H') + +/* Structs for each of the event message payloads */ +struct mmal_msg_event_eos { + u32 port_type; /**< Type of port that received the end of stream */ + u32 port_index; /**< Index of port that received the end of stream */ +}; + +/** Format changed event data. */ +struct mmal_msg_event_format_changed { + /* Minimum size of buffers the port requires */ + u32 buffer_size_min; + /* Minimum number of buffers the port requires */ + u32 buffer_num_min; + /* Size of buffers the port recommends for optimal performance. + * A value of zero means no special recommendation. + */ + u32 buffer_size_recommended; + /* Number of buffers the port recommends for optimal + * performance. A value of zero means no special recommendation. + */ + u32 buffer_num_recommended; + + u32 es_ptr; + struct mmal_es_format format; + union mmal_es_specific_format es; + u8 extradata[MMAL_FORMAT_EXTRADATA_MAX_SIZE]; +}; + struct mmal_msg_event_to_host { u32 client_component; /* component context */ diff --git a/drivers/staging/vc04_services/vchiq-mmal/mmal-parameters.h b/drivers/staging/vc04_services/vchiq-mmal/mmal-parameters.h index a0cdd28101f2dd..825daadf2fea14 100644 --- a/drivers/staging/vc04_services/vchiq-mmal/mmal-parameters.h +++ b/drivers/staging/vc04_services/vchiq-mmal/mmal-parameters.h @@ -223,6 +223,66 @@ enum mmal_parameter_camera_type { MMAL_PARAMETER_SHUTTER_SPEED, /**< Takes a @ref MMAL_PARAMETER_AWB_GAINS_T */ MMAL_PARAMETER_CUSTOM_AWB_GAINS, + /**< Takes a @ref MMAL_PARAMETER_CAMERA_SETTINGS_T */ + MMAL_PARAMETER_CAMERA_SETTINGS, + /**< Takes a @ref MMAL_PARAMETER_PRIVACY_INDICATOR_T */ + MMAL_PARAMETER_PRIVACY_INDICATOR, + /**< Takes a @ref MMAL_PARAMETER_BOOLEAN_T */ + MMAL_PARAMETER_VIDEO_DENOISE, + /**< Takes a @ref MMAL_PARAMETER_BOOLEAN_T */ + MMAL_PARAMETER_STILLS_DENOISE, + /**< Takes a @ref MMAL_PARAMETER_CAMERA_ANNOTATE_T */ + MMAL_PARAMETER_ANNOTATE, + /**< Takes a @ref MMAL_PARAMETER_STEREOSCOPIC_MODE_T */ + MMAL_PARAMETER_STEREOSCOPIC_MODE, + /**< Takes a @ref MMAL_PARAMETER_CAMERA_INTERFACE_T */ + MMAL_PARAMETER_CAMERA_INTERFACE, + /**< Takes a @ref MMAL_PARAMETER_CAMERA_CLOCKING_MODE_T */ + MMAL_PARAMETER_CAMERA_CLOCKING_MODE, + /**< Takes a @ref MMAL_PARAMETER_CAMERA_RX_CONFIG_T */ + MMAL_PARAMETER_CAMERA_RX_CONFIG, + /**< Takes a @ref MMAL_PARAMETER_CAMERA_RX_TIMING_T */ + MMAL_PARAMETER_CAMERA_RX_TIMING, + /**< Takes a @ref MMAL_PARAMETER_UINT32_T */ + MMAL_PARAMETER_DPF_CONFIG, + + /* 0x50 */ + /**< Takes a @ref MMAL_PARAMETER_UINT32_T */ + MMAL_PARAMETER_JPEG_RESTART_INTERVAL, + /**< Takes a @ref MMAL_PARAMETER_UINT32_T */ + MMAL_PARAMETER_CAMERA_ISP_BLOCK_OVERRIDE, + /**< Takes a @ref MMAL_PARAMETER_LENS_SHADING_T */ + MMAL_PARAMETER_LENS_SHADING_OVERRIDE, + /**< Takes a @ref MMAL_PARAMETER_UINT32_T */ + MMAL_PARAMETER_BLACK_LEVEL, + /**< Takes a @ref MMAL_PARAMETER_RESIZE_T */ + MMAL_PARAMETER_RESIZE_PARAMS, + /**< Takes a @ref MMAL_PARAMETER_CROP_T */ + MMAL_PARAMETER_CROP, + /**< Takes a @ref MMAL_PARAMETER_INT32_T */ + MMAL_PARAMETER_OUTPUT_SHIFT, + /**< Takes a @ref MMAL_PARAMETER_INT32_T */ + MMAL_PARAMETER_CCM_SHIFT, + /**< Takes a @ref MMAL_PARAMETER_CUSTOM_CCM_T */ + MMAL_PARAMETER_CUSTOM_CCM, + /**< Takes a @ref MMAL_PARAMETER_RATIONAL_T */ + MMAL_PARAMETER_ANALOG_GAIN, + /**< Takes a @ref MMAL_PARAMETER_RATIONAL_T */ + MMAL_PARAMETER_DIGITAL_GAIN, + /**< Takes a @ref MMAL_PARAMETER_DENOISE_T */ + MMAL_PARAMETER_DENOISE, + /**< Takes a @ref MMAL_PARAMETER_SHARPEN_T */ + MMAL_PARAMETER_SHARPEN, + /**< Takes a @ref MMAL_PARAMETER_GEQ_T */ + MMAL_PARAMETER_GEQ, + /**< Tales a @ref MMAP_PARAMETER_DPC_T */ + MMAL_PARAMETER_DPC, + /**< Tales a @ref MMAP_PARAMETER_GAMMA_T */ + MMAL_PARAMETER_GAMMA, + /**< Takes a @ref MMAL_PARAMETER_CDN_T */ + MMAL_PARAMETER_CDN, + /**< Takes a @ref MMAL_PARAMETER_BOOLEAN_T */ + MMAL_PARAMETER_JPEG_IJG_SCALING, }; enum mmal_parameter_camera_config_timestamp_mode { @@ -310,6 +370,7 @@ enum mmal_parameter_awbmode { MMAL_PARAM_AWBMODE_INCANDESCENT, MMAL_PARAM_AWBMODE_FLASH, MMAL_PARAM_AWBMODE_HORIZON, + MMAL_PARAM_AWBMODE_GREYWORLD, }; enum mmal_parameter_imagefx { @@ -336,6 +397,9 @@ enum mmal_parameter_imagefx { MMAL_PARAM_IMAGEFX_COLOURPOINT, MMAL_PARAM_IMAGEFX_COLOURBALANCE, MMAL_PARAM_IMAGEFX_CARTOON, + MMAL_PARAM_IMAGEFX_DEINTERLACE_DOUBLE, + MMAL_PARAM_IMAGEFX_DEINTERLACE_ADV, + MMAL_PARAM_IMAGEFX_DEINTERLACE_FAST, }; enum MMAL_PARAM_FLICKERAVOID { @@ -577,7 +641,49 @@ enum mmal_parameter_video_type { MMAL_PARAMETER_VIDEO_ENCODE_H264_LOW_DELAY_HRD_FLAG, /**< @ref MMAL_PARAMETER_BOOLEAN_T */ - MMAL_PARAMETER_VIDEO_ENCODE_INLINE_HEADER + MMAL_PARAMETER_VIDEO_ENCODE_INLINE_HEADER, + + /**< Take a @ref MMAL_PARAMETER_BOOLEAN_T. */ + MMAL_PARAMETER_VIDEO_ENCODE_SEI_ENABLE, + + /**< Take a @ref MMAL_PARAMETER_BOOLEAN_T. */ + MMAL_PARAMETER_VIDEO_ENCODE_INLINE_VECTORS, + + /**< Take a @ref MMAL_PARAMETER_VIDEO_RENDER_STATS_T. */ + MMAL_PARAMETER_VIDEO_RENDER_STATS, + + /**< Take a @ref MMAL_PARAMETER_VIDEO_INTERLACE_TYPE_T. */ + MMAL_PARAMETER_VIDEO_INTERLACE_TYPE, + + /**< Takes a @ref MMAL_PARAMETER_BOOLEAN_T */ + MMAL_PARAMETER_VIDEO_INTERPOLATE_TIMESTAMPS, + + /**< Takes a @ref MMAL_PARAMETER_BOOLEAN_T */ + MMAL_PARAMETER_VIDEO_ENCODE_SPS_TIMING, + + /**< Takes a @ref MMAL_PARAMETER_UINT32_T */ + MMAL_PARAMETER_VIDEO_MAX_NUM_CALLBACKS, + + /**< Takes a @ref MMAL_PARAMETER_SOURCE_PATTERN_T */ + MMAL_PARAMETER_VIDEO_SOURCE_PATTERN, + + /**< Takes a @ref MMAL_PARAMETER_BOOLEAN_T */ + MMAL_PARAMETER_VIDEO_ENCODE_SEPARATE_NAL_BUFS, + + /**< Takes a @ref MMAL_PARAMETER_UINT32_T */ + MMAL_PARAMETER_VIDEO_DROPPABLE_PFRAME_LENGTH, + + /**< Take a @ref MMAL_PARAMETER_VIDEO_STALL_T */ + MMAL_PARAMETER_VIDEO_STALL_THRESHOLD, + + /**< Take a @ref MMAL_PARAMETER_BOOLEAN_T */ + MMAL_PARAMETER_VIDEO_ENCODE_HEADERS_WITH_FRAME, + + /**< Take a @ref MMAL_PARAMETER_BOOLEAN_T */ + MMAL_PARAMETER_VIDEO_VALIDATE_TIMESTAMPS, + + /**< Takes a @ref MMAL_PARAMETER_BOOLEAN_T */ + MMAL_PARAMETER_VIDEO_STOP_ON_PAR_COLOUR_CHANGE, }; /** Valid mirror modes */ @@ -708,6 +814,43 @@ struct mmal_parameter_displayregion { u32 alpha; }; +enum mmal_interlace_type { + /* The data is not interlaced, it is progressive scan */ + MMAL_INTERLACE_PROGRESSIVE, + /* + * The data is interlaced, fields sent separately in temporal order, with + * upper field first + */ + MMAL_INTERLACE_FIELD_SINGLE_UPPER_FIRST, + /* + * The data is interlaced, fields sent separately in temporal order, with + * lower field first + */ + MMAL_INTERLACE_FIELD_SINGLE_LOWER_FIRST, + /* + * The data is interlaced, two fields sent together line interleaved, + * with the upper field temporally earlier + */ + MMAL_INTERLACE_FIELDS_INTERLEAVED_UPPER_FIRST, + /* + * The data is interlaced, two fields sent together line interleaved, + * with the lower field temporally earlier + */ + MMAL_INTERLACE_FIELDS_INTERLEAVED_LOWER_FIRST, + /* + * The stream may contain a mixture of progressive and interlaced + * frames + */ + MMAL_INTERLACE_MIXED, + + MMAL_INTERLACE_DUMMY = 0x7FFFFFFF +}; + +struct mmal_parameter_video_interlace_type { + enum mmal_interlace_type mode; /* The interlace type of the content */ + u32 bRepeatFirstField; /* Whether to repeat the first field */ +}; + #define MMAL_MAX_IMAGEFX_PARAMETERS 5 struct mmal_parameter_imagefx_parameters { @@ -746,7 +889,113 @@ struct mmal_parameter_camera_info { struct mmal_parameter_camera_info_camera cameras[MMAL_PARAMETER_CAMERA_INFO_MAX_CAMERAS]; struct mmal_parameter_camera_info_flash - flashes[MMAL_PARAMETER_CAMERA_INFO_MAX_FLASHES]; + flashes[MMAL_PARAMETER_CAMERA_INFO_MAX_FLASHES]; +}; + +struct mmal_parameter_ccm { + struct s32_fract ccm[3][3]; + s32 offsets[3]; +}; + +struct mmal_parameter_custom_ccm { + u32 enabled; /**< Enable the custom CCM. */ + struct mmal_parameter_ccm ccm; /**< CCM to be used. */ +}; + +struct mmal_parameter_lens_shading { + u32 enabled; + u32 grid_cell_size; + u32 grid_width; + u32 grid_stride; + u32 grid_height; + u32 mem_handle_table; + u32 ref_transform; +}; + +enum mmal_parameter_ls_gain_format_type { + MMAL_PARAMETER_LS_GAIN_FORMAT_TYPE_U0P8_1 = 0, + MMAL_PARAMETER_LS_GAIN_FORMAT_TYPE_U1P7_0 = 1, + MMAL_PARAMETER_LS_GAIN_FORMAT_TYPE_U1P7_1 = 2, + MMAL_PARAMETER_LS_GAIN_FORMAT_TYPE_U2P6_0 = 3, + MMAL_PARAMETER_LS_GAIN_FORMAT_TYPE_U2P6_1 = 4, + MMAL_PARAMETER_LS_GAIN_FORMAT_TYPE_U3P5_0 = 5, + MMAL_PARAMETER_LS_GAIN_FORMAT_TYPE_U3P5_1 = 6, + MMAL_PARAMETER_LS_GAIN_FORMAT_TYPE_U4P10 = 7, + MMAL_PARAMETER_LS_GAIN_FORMAT_TYPE_DUMMY = 0x7FFFFFFF +}; + +struct mmal_parameter_lens_shading_v2 { + u32 enabled; + u32 grid_cell_size; + u32 grid_width; + u32 grid_stride; + u32 grid_height; + u32 mem_handle_table; + u32 ref_transform; + u32 corner_sampled; + enum mmal_parameter_ls_gain_format_type gain_format; +}; + +struct mmal_parameter_black_level { + u32 enabled; + u16 black_level_r; + u16 black_level_g; + u16 black_level_b; + u8 pad_[2]; /* Unused */ +}; + +struct mmal_parameter_geq { + u32 enabled; + u32 offset; + struct s32_fract slope; +}; + +#define MMAL_NUM_GAMMA_PTS 33 +struct mmal_parameter_gamma { + u32 enabled; + u16 x[MMAL_NUM_GAMMA_PTS]; + u16 y[MMAL_NUM_GAMMA_PTS]; +}; + +enum mmal_parameter_cdn_mode { + MMAL_PARAM_CDN_FAST = 0, + MMAL_PARAM_CDN_HIGH_QUALITY = 1, + MMAL_PARAM_CDN_DUMMY = 0x7FFFFFFF +}; + +struct mmal_parameter_colour_denoise { + u32 enabled; + enum mmal_parameter_cdn_mode mode; +}; + +struct mmal_parameter_denoise { + u32 enabled; + u32 constant; + struct s32_fract slope; + struct s32_fract strength; +}; + +struct mmal_parameter_sharpen { + u32 enabled; + struct s32_fract threshold; + struct s32_fract strength; + struct s32_fract limit; +}; + +enum mmal_dpc_mode { + MMAL_DPC_MODE_OFF = 0, + MMAL_DPC_MODE_NORMAL = 1, + MMAL_DPC_MODE_STRONG = 2, + MMAL_DPC_MODE_MAX = 0x7FFFFFFF, +}; + +struct mmal_parameter_dpc { + u32 enabled; + u32 strength; +}; + +struct mmal_parameter_crop { + struct vchiq_mmal_rect rect; }; #endif diff --git a/drivers/staging/vc04_services/vchiq-mmal/mmal-vchiq.c b/drivers/staging/vc04_services/vchiq-mmal/mmal-vchiq.c index 67489c334f7b2d..8a5f0d32ed2d69 100644 --- a/drivers/staging/vc04_services/vchiq-mmal/mmal-vchiq.c +++ b/drivers/staging/vc04_services/vchiq-mmal/mmal-vchiq.c @@ -28,9 +28,22 @@ #include "../include/linux/raspberrypi/vchiq.h" #include "../interface/vchiq_arm/vchiq_arm.h" #include "mmal-common.h" +#include "mmal-parameters.h" #include "mmal-vchiq.h" #include "mmal-msg.h" +#include "../vc-sm-cma/vc_sm_knl.h" + +#define pr_dbg_lvl(__level, __debug, __fmt, __arg...) \ + do { \ + if (__debug >= (__level)) \ + printk(KERN_DEBUG __fmt, ##__arg); \ + } while (0) + +static unsigned int debug; +module_param(debug, uint, 0644); +MODULE_PARM_DESC(debug, "activates debug info (0-3)"); + /* * maximum number of components supported. * This matches the maximum permitted by default on the VPU @@ -144,6 +157,8 @@ struct mmal_msg_context { /* Presentation and Decode timestamps */ s64 pts; s64 dts; + /* MMAL buffer command flag */ + u32 cmd; int status; /* context status */ @@ -231,18 +246,6 @@ release_msg_context(struct mmal_msg_context *msg_context) kfree(msg_context); } -/* deals with receipt of event to host message */ -static void event_to_host_cb(struct vchiq_mmal_instance *instance, - struct mmal_msg *msg, u32 msg_len) -{ - pr_debug("unhandled event\n"); - pr_debug("component:%u port type:%d num:%d cmd:0x%x length:%d\n", - msg->u.event_to_host.client_component, - msg->u.event_to_host.port_type, - msg->u.event_to_host.port_num, - msg->u.event_to_host.cmd, msg->u.event_to_host.length); -} - /* workqueue scheduled callback * * we do this because it is important we do not call any other vchiq @@ -264,13 +267,18 @@ static void buffer_work_cb(struct work_struct *work) buffer->mmal_flags = msg_context->u.bulk.mmal_flags; buffer->dts = msg_context->u.bulk.dts; buffer->pts = msg_context->u.bulk.pts; + buffer->cmd = msg_context->u.bulk.cmd; - atomic_dec(&msg_context->u.bulk.port->buffers_with_vpu); + if (!buffer->cmd) + atomic_dec(&msg_context->u.bulk.port->buffers_with_vpu); msg_context->u.bulk.port->buffer_cb(msg_context->u.bulk.instance, msg_context->u.bulk.port, msg_context->u.bulk.status, msg_context->u.bulk.buffer); + + if (buffer->cmd) + mutex_unlock(&msg_context->u.bulk.port->event_context_mutex); } /* workqueue scheduled callback to handle receiving buffers @@ -348,6 +356,7 @@ static int bulk_receive(struct vchiq_mmal_instance *instance, msg_context->u.bulk.buffer_used = rd_len; msg_context->u.bulk.dts = msg->u.buffer_from_host.buffer_header.dts; msg_context->u.bulk.pts = msg->u.buffer_from_host.buffer_header.pts; + msg_context->u.bulk.cmd = msg->u.buffer_from_host.buffer_header.cmd; queue_work(msg_context->instance->bulk_wq, &msg_context->u.bulk.buffer_to_host_work); @@ -382,7 +391,8 @@ buffer_from_host(struct vchiq_mmal_instance *instance, if (!port->enabled) return -EINVAL; - pr_debug("instance:%u buffer:%p\n", instance->service_handle, buf); + pr_dbg_lvl(3, debug, "instance:%u buffer:%p\n", + instance->service_handle, buf); /* get context */ if (!buf->msg_context) { @@ -421,14 +431,27 @@ buffer_from_host(struct vchiq_mmal_instance *instance, /* buffer header */ m.u.buffer_from_host.buffer_header.cmd = 0; - m.u.buffer_from_host.buffer_header.data = - (u32)(unsigned long)buf->buffer; + if (port->zero_copy) { + m.u.buffer_from_host.buffer_header.data = buf->vc_handle; + } else { + m.u.buffer_from_host.buffer_header.data = + (u32)(unsigned long)buf->buffer; + } + m.u.buffer_from_host.buffer_header.alloc_size = buf->buffer_size; - m.u.buffer_from_host.buffer_header.length = 0; /* nothing used yet */ - m.u.buffer_from_host.buffer_header.offset = 0; /* no offset */ - m.u.buffer_from_host.buffer_header.flags = 0; /* no flags */ - m.u.buffer_from_host.buffer_header.pts = MMAL_TIME_UNKNOWN; - m.u.buffer_from_host.buffer_header.dts = MMAL_TIME_UNKNOWN; + if (port->type == MMAL_PORT_TYPE_OUTPUT) { + m.u.buffer_from_host.buffer_header.length = 0; + m.u.buffer_from_host.buffer_header.offset = 0; + m.u.buffer_from_host.buffer_header.flags = 0; + m.u.buffer_from_host.buffer_header.pts = MMAL_TIME_UNKNOWN; + m.u.buffer_from_host.buffer_header.dts = MMAL_TIME_UNKNOWN; + } else { + m.u.buffer_from_host.buffer_header.length = buf->length; + m.u.buffer_from_host.buffer_header.offset = 0; + m.u.buffer_from_host.buffer_header.flags = buf->mmal_flags; + m.u.buffer_from_host.buffer_header.pts = buf->pts; + m.u.buffer_from_host.buffer_header.dts = buf->dts; + } /* clear buffer type specific data */ memset(&m.u.buffer_from_host.buffer_header_type_specific, 0, @@ -450,6 +473,103 @@ buffer_from_host(struct vchiq_mmal_instance *instance, return ret; } +/* deals with receipt of event to host message */ +static void event_to_host_cb(struct vchiq_mmal_instance *instance, + struct mmal_msg *msg, u32 msg_len) +{ + int comp_idx = msg->u.event_to_host.client_component; + struct vchiq_mmal_component *component = + &instance->component[comp_idx]; + struct vchiq_mmal_port *port = NULL; + struct mmal_msg_context *msg_context; + u32 port_num = msg->u.event_to_host.port_num; + + if (msg->u.buffer_from_host.drvbuf.magic == MMAL_MAGIC) { + pr_err("%s: MMAL_MSG_TYPE_BUFFER_TO_HOST with bad magic\n", + __func__); + return; + } + + switch (msg->u.event_to_host.port_type) { + case MMAL_PORT_TYPE_CONTROL: + if (port_num) { + pr_err("%s: port_num of %u >= number of ports 1", + __func__, port_num); + return; + } + port = &component->control; + break; + case MMAL_PORT_TYPE_INPUT: + if (port_num >= component->inputs) { + pr_err("%s: port_num of %u >= number of ports %u", + __func__, port_num, + port_num >= component->inputs); + return; + } + port = &component->input[port_num]; + break; + case MMAL_PORT_TYPE_OUTPUT: + if (port_num >= component->outputs) { + pr_err("%s: port_num of %u >= number of ports %u", + __func__, port_num, + port_num >= component->outputs); + return; + } + port = &component->output[port_num]; + break; + case MMAL_PORT_TYPE_CLOCK: + if (port_num >= component->clocks) { + pr_err("%s: port_num of %u >= number of ports %u", + __func__, port_num, + port_num >= component->clocks); + return; + } + port = &component->clock[port_num]; + break; + default: + break; + } + + if (!mutex_trylock(&port->event_context_mutex)) { + pr_err("dropping event 0x%x\n", msg->u.event_to_host.cmd); + return; + } + msg_context = port->event_context; + + if (msg->h.status != MMAL_MSG_STATUS_SUCCESS) { + /* message reception had an error */ + //pr_warn + pr_err("%s: error %d in reply\n", __func__, msg->h.status); + + msg_context->u.bulk.status = msg->h.status; + } else if (msg->u.event_to_host.length > MMAL_WORKER_EVENT_SPACE) { + /* data is not in message, queue a bulk receive */ + pr_err("%s: payload not in message - bulk receive??! NOT SUPPORTED\n", + __func__); + msg_context->u.bulk.status = -1; + } else { + memcpy(msg_context->u.bulk.buffer->buffer, + msg->u.event_to_host.data, + msg->u.event_to_host.length); + + msg_context->u.bulk.buffer_used = + msg->u.event_to_host.length; + + msg_context->u.bulk.mmal_flags = 0; + msg_context->u.bulk.dts = MMAL_TIME_UNKNOWN; + msg_context->u.bulk.pts = MMAL_TIME_UNKNOWN; + msg_context->u.bulk.cmd = msg->u.event_to_host.cmd; + + pr_dbg_lvl(3, debug, "event component:%u port type:%d num:%d cmd:0x%x length:%d\n", + msg->u.event_to_host.client_component, + msg->u.event_to_host.port_type, + msg->u.event_to_host.port_num, + msg->u.event_to_host.cmd, msg->u.event_to_host.length); + } + + schedule_work(&msg_context->u.bulk.work); +} + /* deals with receipt of buffer to host message */ static void buffer_to_host_cb(struct vchiq_mmal_instance *instance, struct mmal_msg *msg, u32 msg_len) @@ -457,8 +577,8 @@ static void buffer_to_host_cb(struct vchiq_mmal_instance *instance, struct mmal_msg_context *msg_context; u32 handle; - pr_debug("%s: instance:%p msg:%p msg_len:%d\n", - __func__, instance, msg, msg_len); + pr_dbg_lvl(3, debug, "%s: instance:%p msg:%p msg_len:%d\n", + __func__, instance, msg, msg_len); if (msg->u.buffer_from_host.drvbuf.magic == MMAL_MAGIC) { handle = msg->u.buffer_from_host.drvbuf.client_context; @@ -483,6 +603,22 @@ static void buffer_to_host_cb(struct vchiq_mmal_instance *instance, msg_context->u.bulk.status = msg->h.status; + } else if (msg->u.buffer_from_host.is_zero_copy) { + /* + * Zero copy buffer, so nothing to do. + * Copy buffer info and make callback. + */ + msg_context->u.bulk.buffer_used = + msg->u.buffer_from_host.buffer_header.length; + msg_context->u.bulk.mmal_flags = + msg->u.buffer_from_host.buffer_header.flags; + msg_context->u.bulk.dts = + msg->u.buffer_from_host.buffer_header.dts; + msg_context->u.bulk.pts = + msg->u.buffer_from_host.buffer_header.pts; + msg_context->u.bulk.cmd = + msg->u.buffer_from_host.buffer_header.cmd; + } else if (msg->u.buffer_from_host.buffer_header.length == 0) { /* empty buffer */ if (msg->u.buffer_from_host.buffer_header.flags & @@ -712,39 +848,42 @@ static int send_synchronous_mmal_msg(struct vchiq_mmal_instance *instance, static void dump_port_info(struct vchiq_mmal_port *port) { - pr_debug("port handle:0x%x enabled:%d\n", port->handle, port->enabled); + pr_dbg_lvl(3, debug, "port handle:0x%x enabled:%d\n", port->handle, + port->enabled); - pr_debug("buffer minimum num:%d size:%d align:%d\n", - port->minimum_buffer.num, - port->minimum_buffer.size, port->minimum_buffer.alignment); + pr_dbg_lvl(3, debug, "buffer minimum num:%d size:%d align:%d\n", + port->minimum_buffer.num, + port->minimum_buffer.size, port->minimum_buffer.alignment); - pr_debug("buffer recommended num:%d size:%d align:%d\n", - port->recommended_buffer.num, - port->recommended_buffer.size, - port->recommended_buffer.alignment); + pr_dbg_lvl(3, debug, "buffer recommended num:%d size:%d align:%d\n", + port->recommended_buffer.num, + port->recommended_buffer.size, + port->recommended_buffer.alignment); - pr_debug("buffer current values num:%d size:%d align:%d\n", - port->current_buffer.num, - port->current_buffer.size, port->current_buffer.alignment); + pr_dbg_lvl(3, debug, "buffer current values num:%d size:%d align:%d\n", + port->current_buffer.num, + port->current_buffer.size, port->current_buffer.alignment); - pr_debug("elementary stream: type:%d encoding:0x%x variant:0x%x\n", - port->format.type, - port->format.encoding, port->format.encoding_variant); + pr_dbg_lvl(3, debug, "elementary stream: type:%d encoding:0x%x variant:0x%x\n", + port->format.type, + port->format.encoding, port->format.encoding_variant); - pr_debug(" bitrate:%d flags:0x%x\n", - port->format.bitrate, port->format.flags); + pr_dbg_lvl(3, debug, " bitrate:%d flags:0x%x\n", + port->format.bitrate, port->format.flags); if (port->format.type == MMAL_ES_TYPE_VIDEO) { - pr_debug - ("es video format: width:%d height:%d colourspace:0x%x\n", + pr_dbg_lvl(3, debug, + "es video format: width:%d height:%d colourspace:0x%x\n", port->es.video.width, port->es.video.height, port->es.video.color_space); - pr_debug(" : crop xywh %d,%d,%d,%d\n", + pr_dbg_lvl(3, debug, + " : crop xywh %d,%d,%d,%d\n", port->es.video.crop.x, port->es.video.crop.y, port->es.video.crop.width, port->es.video.crop.height); - pr_debug(" : framerate %d/%d aspect %d/%d\n", + pr_dbg_lvl(3, debug, + " : framerate %d/%d aspect %d/%d\n", port->es.video.frame_rate.numerator, port->es.video.frame_rate.denominator, port->es.video.par.numerator, port->es.video.par.denominator); @@ -778,7 +917,7 @@ static int port_info_set(struct vchiq_mmal_instance *instance, struct mmal_msg *rmsg; struct vchiq_header *rmsg_handle; - pr_debug("setting port info port %p\n", port); + pr_dbg_lvl(1, debug, "setting port info port %p\n", port); if (!port) return -1; dump_port_info(port); @@ -821,8 +960,8 @@ static int port_info_set(struct vchiq_mmal_instance *instance, /* return operation status */ ret = -rmsg->u.port_info_get_reply.status; - pr_debug("%s:result:%d component:0x%x port:%d\n", __func__, ret, - port->component->handle, port->handle); + pr_dbg_lvl(1, debug, "%s:result:%d component:0x%x port:%d\n", __func__, + ret, port->component->handle, port->handle); release_msg: vchiq_release_message(instance->vchiq_instance, instance->service_handle, rmsg_handle); @@ -912,13 +1051,13 @@ static int port_info_get(struct vchiq_mmal_instance *instance, rmsg->u.port_info_get_reply.extradata, port->format.extradata_size); - pr_debug("received port info\n"); + pr_dbg_lvl(1, debug, "received port info\n"); dump_port_info(port); release_msg: - pr_debug("%s:result:%d component:0x%x port:%d\n", - __func__, ret, port->component->handle, port->handle); + pr_dbg_lvl(1, debug, "%s:result:%d component:0x%x port:%d\n", + __func__, ret, port->component->handle, port->handle); vchiq_release_message(instance->vchiq_instance, instance->service_handle, rmsg_handle); @@ -964,9 +1103,9 @@ static int create_component(struct vchiq_mmal_instance *instance, component->outputs = rmsg->u.component_create_reply.output_num; component->clocks = rmsg->u.component_create_reply.clock_num; - pr_debug("Component handle:0x%x in:%d out:%d clock:%d\n", - component->handle, - component->inputs, component->outputs, component->clocks); + pr_dbg_lvl(2, debug, "Component handle:0x%x in:%d out:%d clock:%d\n", + component->handle, + component->inputs, component->outputs, component->clocks); release_msg: vchiq_release_message(instance->vchiq_instance, instance->service_handle, rmsg_handle); @@ -1135,10 +1274,9 @@ static int port_action_port(struct vchiq_mmal_instance *instance, ret = -rmsg->u.port_action_reply.status; - pr_debug("%s:result:%d component:0x%x port:%d action:%s(%d)\n", - __func__, - ret, port->component->handle, port->handle, - port_action_type_names[action_type], action_type); + pr_dbg_lvl(2, debug, "%s:result:%d component:0x%x port:%d action:%s(%d)\n", + __func__, ret, port->component->handle, port->handle, + port_action_type_names[action_type], action_type); release_msg: vchiq_release_message(instance->vchiq_instance, instance->service_handle, rmsg_handle); @@ -1182,11 +1320,11 @@ static int port_action_handle(struct vchiq_mmal_instance *instance, ret = -rmsg->u.port_action_reply.status; - pr_debug("%s:result:%d component:0x%x port:%d action:%s(%d) connect component:0x%x connect port:%d\n", - __func__, - ret, port->component->handle, port->handle, - port_action_type_names[action_type], - action_type, connect_component_handle, connect_port_handle); + pr_dbg_lvl(2, debug, + "%s:result:%d component:0x%x port:%d action:%s(%d) connect component:0x%x connect port:%d\n", + __func__, ret, port->component->handle, port->handle, + port_action_type_names[action_type], + action_type, connect_component_handle, connect_port_handle); release_msg: vchiq_release_message(instance->vchiq_instance, instance->service_handle, rmsg_handle); @@ -1225,9 +1363,9 @@ static int port_parameter_set(struct vchiq_mmal_instance *instance, ret = -rmsg->u.port_parameter_set_reply.status; - pr_debug("%s:result:%d component:0x%x port:%d parameter:%d\n", - __func__, - ret, port->component->handle, port->handle, parameter_id); + pr_dbg_lvl(1, debug, "%s:result:%d component:0x%x port:%d parameter:%d\n", + __func__, ret, port->component->handle, port->handle, + parameter_id); release_msg: vchiq_release_message(instance->vchiq_instance, instance->service_handle, rmsg_handle); @@ -1285,8 +1423,9 @@ static int port_parameter_get(struct vchiq_mmal_instance *instance, /* Always report the size of the returned parameter to the caller */ *value_size = rmsg->u.port_parameter_get_reply.size; - pr_debug("%s:result:%d component:0x%x port:%d parameter:%d\n", __func__, - ret, port->component->handle, port->handle, parameter_id); + pr_dbg_lvl(1, debug, "%s:result:%d component:0x%x port:%d parameter:%d\n", + __func__, ret, port->component->handle, port->handle, + parameter_id); release_msg: vchiq_release_message(instance->vchiq_instance, instance->service_handle, rmsg_handle); @@ -1331,6 +1470,7 @@ static int port_disable(struct vchiq_mmal_instance *instance, mmalbuf->mmal_flags = 0; mmalbuf->dts = MMAL_TIME_UNKNOWN; mmalbuf->pts = MMAL_TIME_UNKNOWN; + mmalbuf->cmd = 0; port->buffer_cb(instance, port, 0, mmalbuf); } @@ -1362,6 +1502,8 @@ static int port_enable(struct vchiq_mmal_instance *instance, port->enabled = true; + atomic_set(&port->buffers_with_vpu, 0); + if (port->buffer_cb) { /* send buffer headers to videocore */ hdr_count = 1; @@ -1427,6 +1569,9 @@ int vchiq_mmal_port_parameter_set(struct vchiq_mmal_instance *instance, mutex_unlock(&instance->vchiq_mutex); + if (parameter == MMAL_PARAMETER_ZERO_COPY && !ret) + port->zero_copy = !!(*(bool *)value); + return ret; } EXPORT_SYMBOL_GPL(vchiq_mmal_port_parameter_set); @@ -1539,7 +1684,7 @@ int vchiq_mmal_port_connect_tunnel(struct vchiq_mmal_instance *instance, if (!dst) { /* do not make new connection */ ret = 0; - pr_debug("not making new connection\n"); + pr_dbg_lvl(3, debug, "not making new connection\n"); goto release_unlock; } @@ -1557,14 +1702,14 @@ int vchiq_mmal_port_connect_tunnel(struct vchiq_mmal_instance *instance, /* set new format */ ret = port_info_set(instance, dst); if (ret) { - pr_debug("setting port info failed\n"); + pr_dbg_lvl(1, debug, "setting port info failed\n"); goto release_unlock; } /* read what has actually been set */ ret = port_info_get(instance, dst); if (ret) { - pr_debug("read back port info failed\n"); + pr_dbg_lvl(1, debug, "read back port info failed\n"); goto release_unlock; } @@ -1573,9 +1718,9 @@ int vchiq_mmal_port_connect_tunnel(struct vchiq_mmal_instance *instance, MMAL_MSG_PORT_ACTION_TYPE_CONNECT, dst->component->handle, dst->handle); if (ret < 0) { - pr_debug("connecting port %d:%d to %d:%d failed\n", - src->component->handle, src->handle, - dst->component->handle, dst->handle); + pr_dbg_lvl(2, debug, "connecting port %d:%d to %d:%d failed\n", + src->component->handle, src->handle, + dst->component->handle, dst->handle); goto release_unlock; } src->connected = dst; @@ -1595,6 +1740,32 @@ int vchiq_mmal_submit_buffer(struct vchiq_mmal_instance *instance, unsigned long flags = 0; int ret; + /* + * We really want to do this in mmal_vchi_buffer_init but can't as + * videobuf2 won't let us have the dmabuf there. + */ + if (port->zero_copy && buffer->dma_buf && !buffer->vcsm_handle) { + pr_dbg_lvl(2, debug, "%s: import dmabuf %p\n", + __func__, buffer->dma_buf); + ret = vc_sm_cma_import_dmabuf(buffer->dma_buf, + &buffer->vcsm_handle); + if (ret) { + pr_err("%s: vc_sm_import_dmabuf_fd failed, ret %d\n", + __func__, ret); + return ret; + } + + buffer->vc_handle = vc_sm_cma_int_handle(buffer->vcsm_handle); + if (!buffer->vc_handle) { + pr_err("%s: vc_sm_int_handle failed %d\n", + __func__, ret); + vc_sm_cma_free(buffer->vcsm_handle); + return ret; + } + pr_dbg_lvl(2, debug, "%s: import dmabuf %p - got vc handle %08X\n", + __func__, buffer->dma_buf, buffer->vc_handle); + } + ret = buffer_from_host(instance, port, buffer); if (ret == -EINVAL) { /* Port is disabled. Queue for when it is enabled. */ @@ -1628,10 +1799,74 @@ int mmal_vchi_buffer_cleanup(struct mmal_buffer *buf) release_msg_context(msg_context); buf->msg_context = NULL; + if (buf->vcsm_handle) { + int ret; + + pr_dbg_lvl(2, debug, "%s: vc_sm_cma_free on handle %p\n", __func__, + buf->vcsm_handle); + ret = vc_sm_cma_free(buf->vcsm_handle); + if (ret) + pr_err("%s: vcsm_free failed, ret %d\n", __func__, ret); + buf->vcsm_handle = 0; + } return 0; } EXPORT_SYMBOL_GPL(mmal_vchi_buffer_cleanup); +static void init_event_context(struct vchiq_mmal_instance *instance, + struct vchiq_mmal_port *port) +{ + struct mmal_msg_context *ctx = get_msg_context(instance); + + mutex_init(&port->event_context_mutex); + + port->event_context = ctx; + ctx->u.bulk.instance = instance; + ctx->u.bulk.port = port; + ctx->u.bulk.buffer = + kzalloc(sizeof(*ctx->u.bulk.buffer), GFP_KERNEL); + if (!ctx->u.bulk.buffer) + goto release_msg_context; + ctx->u.bulk.buffer->buffer = kzalloc(MMAL_WORKER_EVENT_SPACE, + GFP_KERNEL); + if (!ctx->u.bulk.buffer->buffer) + goto release_buffer; + + INIT_WORK(&ctx->u.bulk.work, buffer_work_cb); + return; + +release_buffer: + kfree(ctx->u.bulk.buffer); +release_msg_context: + release_msg_context(ctx); +} + +static void free_event_context(struct vchiq_mmal_port *port) +{ + struct mmal_msg_context *ctx = port->event_context; + + if (!ctx) + return; + + kfree(ctx->u.bulk.buffer->buffer); + kfree(ctx->u.bulk.buffer); + release_msg_context(ctx); + port->event_context = NULL; +} + +static void release_all_event_contexts(struct vchiq_mmal_component *component) +{ + int idx; + + for (idx = 0; idx < component->inputs; idx++) + free_event_context(&component->input[idx]); + for (idx = 0; idx < component->outputs; idx++) + free_event_context(&component->output[idx]); + for (idx = 0; idx < component->clocks; idx++) + free_event_context(&component->clock[idx]); + free_event_context(&component->control); +} + /* Initialise a mmal component and its ports * */ @@ -1681,6 +1916,7 @@ int vchiq_mmal_component_init(struct vchiq_mmal_instance *instance, ret = port_info_get(instance, &component->control); if (ret < 0) goto release_component; + init_event_context(instance, &component->control); for (idx = 0; idx < component->inputs; idx++) { component->input[idx].type = MMAL_PORT_TYPE_INPUT; @@ -1691,6 +1927,7 @@ int vchiq_mmal_component_init(struct vchiq_mmal_instance *instance, ret = port_info_get(instance, &component->input[idx]); if (ret < 0) goto release_component; + init_event_context(instance, &component->input[idx]); } for (idx = 0; idx < component->outputs; idx++) { @@ -1702,6 +1939,7 @@ int vchiq_mmal_component_init(struct vchiq_mmal_instance *instance, ret = port_info_get(instance, &component->output[idx]); if (ret < 0) goto release_component; + init_event_context(instance, &component->output[idx]); } for (idx = 0; idx < component->clocks; idx++) { @@ -1713,6 +1951,7 @@ int vchiq_mmal_component_init(struct vchiq_mmal_instance *instance, ret = port_info_get(instance, &component->clock[idx]); if (ret < 0) goto release_component; + init_event_context(instance, &component->clock[idx]); } *component_out = component; @@ -1723,6 +1962,7 @@ int vchiq_mmal_component_init(struct vchiq_mmal_instance *instance, release_component: destroy_component(instance, component); + release_all_event_contexts(component); unlock: if (component) component->in_use = false; @@ -1750,6 +1990,8 @@ int vchiq_mmal_component_finalise(struct vchiq_mmal_instance *instance, component->in_use = false; + release_all_event_contexts(component); + mutex_unlock(&instance->vchiq_mutex); return ret; @@ -1774,7 +2016,7 @@ int vchiq_mmal_component_enable(struct vchiq_mmal_instance *instance, ret = enable_component(instance, component); if (ret == 0) - component->enabled = true; + component->enabled = 1; mutex_unlock(&instance->vchiq_mutex); diff --git a/drivers/staging/vc04_services/vchiq-mmal/mmal-vchiq.h b/drivers/staging/vc04_services/vchiq-mmal/mmal-vchiq.h index 97abe4bdcfc5f6..56233f54c3cb04 100644 --- a/drivers/staging/vc04_services/vchiq-mmal/mmal-vchiq.h +++ b/drivers/staging/vc04_services/vchiq-mmal/mmal-vchiq.h @@ -49,6 +49,7 @@ typedef void (*vchiq_mmal_buffer_cb)(struct vchiq_mmal_instance *instance, struct vchiq_mmal_port { bool enabled; + u32 zero_copy:1; u32 handle; u32 type; /* port type, cached to use on port info set */ u32 index; /* port index, cached to use on port info set */ @@ -79,6 +80,10 @@ struct vchiq_mmal_port { vchiq_mmal_buffer_cb buffer_cb; /* callback context */ void *cb_ctx; + + /* ensure serialised use of the one event context structure */ + struct mutex event_context_mutex; + struct mmal_msg_context *event_context; }; struct vchiq_mmal_component { diff --git a/drivers/thermal/broadcom/bcm2711_thermal.c b/drivers/thermal/broadcom/bcm2711_thermal.c index 03ac2d02e9d40a..de581c31a8f7a3 100644 --- a/drivers/thermal/broadcom/bcm2711_thermal.c +++ b/drivers/thermal/broadcom/bcm2711_thermal.c @@ -92,7 +92,7 @@ static int bcm2711_thermal_probe(struct platform_device *pdev) &bcm2711_thermal_of_ops); if (IS_ERR(thermal)) { ret = PTR_ERR(thermal); - dev_err(dev, "could not register sensor: %d\n", ret); + dev_err_probe(dev, ret, "could not register sensor: %d\n", ret); return ret; } diff --git a/drivers/thermal/gov_step_wise.c b/drivers/thermal/gov_step_wise.c index fd5527188cf91a..671845b11b5d66 100644 --- a/drivers/thermal/gov_step_wise.c +++ b/drivers/thermal/gov_step_wise.c @@ -17,11 +17,11 @@ #include "thermal_core.h" /* - * If the temperature is higher than a trip point, + * If the temperature is higher than a hysteresis temperature, * a. if the trend is THERMAL_TREND_RAISING, use higher cooling * state for this trip point * b. if the trend is THERMAL_TREND_DROPPING, do nothing - * If the temperature is lower than a trip point, + * If the temperature is lower than a hysteresis temperature, * a. if the trend is THERMAL_TREND_RAISING, do nothing * b. if the trend is THERMAL_TREND_DROPPING, use lower cooling * state for this trip point, if the cooling state already @@ -73,14 +73,18 @@ static void thermal_zone_trip_update(struct thermal_zone_device *tz, int trip_id = thermal_zone_trip_id(tz, trip); struct thermal_instance *instance; bool throttle = false; + int hyst_temp; if (tz->temperature >= trip_threshold) { throttle = true; trace_thermal_zone_trip(tz, trip_id, trip->type); } - dev_dbg(&tz->device, "Trip%d[type=%d,temp=%d]:trend=%d,throttle=%d\n", - trip_id, trip->type, trip_threshold, trend, throttle); + hyst_temp = trip->temperature - trip->hysteresis; + + dev_dbg(&tz->device, + "Trip%d[type=%d,temp=%d,hyst=%d]:trend=%d,throttle=%d\n", + trip_id, trip->type, trip->temperature, hyst_temp, trend, throttle); list_for_each_entry(instance, &tz->thermal_instances, tz_node) { int old_target; @@ -89,6 +93,18 @@ static void thermal_zone_trip_update(struct thermal_zone_device *tz, continue; old_target = instance->target; + throttle = false; + /* + * Lower the mitigation only if the temperature + * goes below the hysteresis temperature. + */ + if (tz->temperature >= trip->temperature || + (tz->temperature >= hyst_temp && + old_target == instance->upper)) { + throttle = true; + trace_thermal_zone_trip(tz, trip_id, trip->type); + } + instance->target = get_target_state(instance, trend, throttle); dev_dbg(&instance->cdev->device, "old_target=%d, target=%ld\n", diff --git a/drivers/tty/serial/8250/8250.h b/drivers/tty/serial/8250/8250.h index 10a706fe4b247d..4c7b7187197de6 100644 --- a/drivers/tty/serial/8250/8250.h +++ b/drivers/tty/serial/8250/8250.h @@ -92,6 +92,7 @@ struct serial8250_config { #define UART_BUG_NOMSR BIT(2) /* UART has buggy MSR status bits (Au1x00) */ #define UART_BUG_THRE BIT(3) /* UART has buggy THRE reassertion */ #define UART_BUG_TXRACE BIT(5) /* UART Tx fails to set remote DR */ +#define UART_BUG_NOMSI BIT(6) /* UART has no modem status interrupt */ /* Module parameters */ #define UART_NR CONFIG_SERIAL_8250_NR_UARTS diff --git a/drivers/tty/serial/8250/8250_bcm2835aux.c b/drivers/tty/serial/8250/8250_bcm2835aux.c index d7a0f271263a93..4a4b117d244e57 100644 --- a/drivers/tty/serial/8250/8250_bcm2835aux.c +++ b/drivers/tty/serial/8250/8250_bcm2835aux.c @@ -101,6 +101,7 @@ static int bcm2835aux_serial_probe(struct platform_device *pdev) up.port.flags = UPF_FIXED_PORT | UPF_FIXED_TYPE | UPF_SKIP_TEST | UPF_IOREMAP; up.port.rs485_config = serial8250_em485_config; up.port.rs485_supported = serial8250_em485_supported; + up.bugs |= UART_BUG_NOMSI; up.rs485_start_tx = bcm2835aux_rs485_start_tx; up.rs485_stop_tx = bcm2835aux_rs485_stop_tx; @@ -156,6 +157,13 @@ static int bcm2835aux_serial_probe(struct platform_device *pdev) */ up.port.uartclk *= 2; + /* The clock is only queried at probe time, which means we get one shot + * at this. A zero clock is never going to work and is almost certainly + * due to a parent not being ready, so prefer to defer. + */ + if (!up.port.uartclk) + return -EPROBE_DEFER; + /* register the port */ ret = serial8250_register_8250_port(&up); if (ret < 0) { diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c index 68baf75bdadc42..4b9258698c30a4 100644 --- a/drivers/tty/serial/8250/8250_core.c +++ b/drivers/tty/serial/8250/8250_core.c @@ -212,6 +212,18 @@ static void serial8250_timeout(struct timer_list *t) mod_timer(&up->timer, jiffies + uart_poll_timeout(&up->port)); } +static void serial8250_cts_poll_timeout(struct timer_list *t) +{ + struct uart_8250_port *up = from_timer(up, t, timer); + unsigned long flags; + + spin_lock_irqsave(&up->port.lock, flags); + serial8250_modem_status(up); + spin_unlock_irqrestore(&up->port.lock, flags); + if (up->port.hw_stopped) + mod_timer(&up->timer, jiffies + 1); +} + static void serial8250_backup_timeout(struct timer_list *t) { struct uart_8250_port *up = from_timer(up, t, timer); @@ -275,6 +287,9 @@ static void univ8250_setup_timer(struct uart_8250_port *up) uart_poll_timeout(port) + HZ / 5); } + if (up->bugs & UART_BUG_NOMSI) + up->timer.function = serial8250_cts_poll_timeout; + /* * If the "interrupt" for this port doesn't correspond with any * hardware interrupt, we use a timer-based system. The original diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c index c1376727642a71..b0cc06a0193842 100644 --- a/drivers/tty/serial/8250/8250_port.c +++ b/drivers/tty/serial/8250/8250_port.c @@ -1510,6 +1510,9 @@ static void serial8250_stop_tx(struct uart_port *port) serial_icr_write(up, UART_ACR, up->acr); } serial8250_rpm_put(up); + + if (port->hw_stopped && (up->bugs & UART_BUG_NOMSI)) + mod_timer(&up->timer, jiffies + 1); } static inline void __start_tx(struct uart_port *port) @@ -1623,6 +1626,9 @@ static void serial8250_start_tx(struct uart_port *port) /* Port locked to synchronize UART_IER access against the console. */ lockdep_assert_held_once(&port->lock); + if (up->bugs & UART_BUG_NOMSI) + del_timer(&up->timer); + if (!port->x_char && kfifo_is_empty(&port->state->port.xmit_fifo)) return; @@ -1851,6 +1857,9 @@ unsigned int serial8250_modem_status(struct uart_8250_port *up) uart_handle_cts_change(port, status & UART_MSR_CTS); wake_up_interruptible(&port->state->port.delta_msr_wait); + } else if (up->bugs & UART_BUG_NOMSI && port->hw_stopped && + status & UART_MSR_CTS) { + uart_handle_cts_change(port, status & UART_MSR_CTS); } return status; diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index 28e4beeabf8f37..6b42a59c80c607 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -1600,6 +1600,17 @@ config SERIAL_ESP32_ACM snippet may be used: earlycon=esp32s3acm,mmio32,0x60038000 +config SERIAL_RPI_FW + tristate "Raspberry Pi Firmware software UART support" + depends on RASPBERRYPI_FIRMWARE || COMPILE_TEST + select SERIAL_CORE + help + This selects the Raspberry Pi firmware UART. This is a bit-bashed + implementation running on the Raspbery Pi VPU core. + This is not supported on Raspberry Pi 5 or newer platforms. + + If unsure, say N. + endmenu config SERIAL_MCTRL_GPIO diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile index 6ff74f0a9530c4..49b23cc3457f43 100644 --- a/drivers/tty/serial/Makefile +++ b/drivers/tty/serial/Makefile @@ -71,6 +71,7 @@ obj-$(CONFIG_SERIAL_QCOM_GENI) += qcom_geni_serial.o obj-$(CONFIG_SERIAL_QE) += ucc_uart.o obj-$(CONFIG_SERIAL_RDA) += rda-uart.o obj-$(CONFIG_SERIAL_RP2) += rp2.o +obj-$(CONFIG_SERIAL_RPI_FW) += rpi-fw-uart.o obj-$(CONFIG_SERIAL_SA1100) += sa1100.o obj-$(CONFIG_SERIAL_SAMSUNG) += samsung_tty.o obj-$(CONFIG_SERIAL_SB1250_DUART) += sb1250-duart.o diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index 9529a512cbd40f..dab2c72c7abb0c 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -152,6 +152,20 @@ static const struct vendor_data vendor_sbsa = { .fixed_options = true, }; +static struct vendor_data vendor_arm_axi = { + .reg_offset = pl011_std_offsets, + .ifls = UART011_IFLS_RX4_8 | UART011_IFLS_TX4_8, + .fr_busy = UART01x_FR_BUSY, + .fr_dsr = UART01x_FR_DSR, + .fr_cts = UART01x_FR_CTS, + .fr_ri = UART011_FR_RI, + .oversampling = false, + .dma_threshold = false, + .cts_event_workaround = false, + .always_enabled = false, + .fixed_options = false, +}; + #ifdef CONFIG_ACPI_SPCR_TABLE static const struct vendor_data vendor_qdt_qdf2400_e44 = { .reg_offset = pl011_std_offsets, @@ -451,6 +465,7 @@ static void pl011_dma_probe(struct uart_amba_port *uap) .src_addr = uap->port.mapbase + pl011_reg_to_offset(uap, REG_DR), .src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE, + .dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE, .direction = DMA_DEV_TO_MEM, .src_maxburst = uap->fifosize >> 2, .device_fc = false, @@ -470,6 +485,12 @@ static void pl011_dma_probe(struct uart_amba_port *uap) "RX DMA disabled - no residue processing\n"); return; } + /* + * DMA controllers with smaller burst capabilities than 1/4 + * the FIFO depth will leave more bytes than expected in the + * RX FIFO if mismatched. + */ + rx_conf.src_maxburst = min(caps.max_burst, rx_conf.src_maxburst); } dmaengine_slave_config(chan, &rx_conf); uap->dmarx.chan = chan; @@ -1433,6 +1454,7 @@ static bool pl011_tx_char(struct uart_amba_port *uap, unsigned char c, return false; /* unable to transmit character */ pl011_write(c, uap, REG_DR); + mb(); uap->port.icount.tx++; return true; @@ -1465,6 +1487,10 @@ static bool pl011_tx_chars(struct uart_amba_port *uap, bool from_irq) if (likely(from_irq) && count-- == 0) break; + if (likely(from_irq) && count == 0 && + pl011_read(uap, REG_FR) & UART01x_FR_TXFF) + break; + if (!kfifo_peek(&tport->xmit_fifo, &c)) break; @@ -2782,6 +2808,11 @@ static int pl011_probe(struct amba_device *dev, const struct amba_id *id) if (IS_ERR(uap->clk)) return PTR_ERR(uap->clk); + if (of_property_read_bool(dev->dev.of_node, "cts-event-workaround")) { + vendor->cts_event_workaround = true; + dev_info(&dev->dev, "cts_event_workaround enabled\n"); + } + uap->reg_offset = vendor->reg_offset; uap->vendor = vendor; uap->fifosize = vendor->get_fifosize(dev); @@ -2954,6 +2985,87 @@ static struct platform_driver arm_sbsa_uart_platform_driver = { }, }; +static int pl011_axi_probe(struct platform_device *pdev) +{ + struct uart_amba_port *uap; + struct vendor_data *vendor = &vendor_arm_axi; + struct resource *r; + unsigned int periphid; + int portnr, ret, irq; + + portnr = pl011_find_free_port(); + if (portnr < 0) + return portnr; + + uap = devm_kzalloc(&pdev->dev, sizeof(struct uart_amba_port), + GFP_KERNEL); + if (!uap) + return -ENOMEM; + + uap->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(uap->clk)) + return PTR_ERR(uap->clk); + + if (of_property_read_bool(pdev->dev.of_node, "cts-event-workaround")) { + vendor->cts_event_workaround = true; + dev_info(&pdev->dev, "cts_event_workaround enabled\n"); + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + periphid = 0x00241011; /* A safe default */ + of_property_read_u32(pdev->dev.of_node, "arm,primecell-periphid", + &periphid); + + uap->reg_offset = vendor->reg_offset; + uap->vendor = vendor; + uap->fifosize = (AMBA_REV_BITS(periphid) < 3) ? 16 : 32; + uap->port.iotype = vendor->access_32b ? UPIO_MEM32 : UPIO_MEM; + uap->port.irq = irq; + uap->port.ops = &amba_pl011_pops; + uap->port.rs485_config = pl011_rs485_config; + uap->port.rs485_supported = pl011_rs485_supported; + + snprintf(uap->type, sizeof(uap->type), "PL011 AXI"); + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + ret = pl011_setup_port(&pdev->dev, uap, r, portnr); + if (ret) + return ret; + + platform_set_drvdata(pdev, uap); + + return pl011_register_port(uap); +} + +static void pl011_axi_remove(struct platform_device *pdev) +{ + struct uart_amba_port *uap = platform_get_drvdata(pdev); + + uart_remove_one_port(&amba_reg, &uap->port); + pl011_unregister_port(uap); +} + +static const struct of_device_id pl011_axi_of_match[] = { + { .compatible = "arm,pl011-axi" }, + {}, +}; +MODULE_DEVICE_TABLE(of, pl011_axi_of_match); + +static struct platform_driver pl011_axi_platform_driver = { + .probe = pl011_axi_probe, + .remove = pl011_axi_remove, + .driver = { + .name = "pl011-axi", + .pm = &pl011_dev_pm_ops, + .of_match_table = of_match_ptr(pl011_axi_of_match), + .suppress_bind_attrs = IS_BUILTIN(CONFIG_SERIAL_AMBA_PL011), + }, +}; + static const struct amba_id pl011_ids[] = { { .id = 0x00041011, @@ -2987,12 +3099,15 @@ static int __init pl011_init(void) if (platform_driver_register(&arm_sbsa_uart_platform_driver)) pr_warn("could not register SBSA UART platform driver\n"); + if (platform_driver_register(&pl011_axi_platform_driver)) + pr_warn("could not register PL011 AXI platform driver\n"); return amba_driver_register(&pl011_driver); } static void __exit pl011_exit(void) { platform_driver_unregister(&arm_sbsa_uart_platform_driver); + platform_driver_unregister(&pl011_axi_platform_driver); amba_driver_unregister(&pl011_driver); } diff --git a/drivers/tty/serial/rpi-fw-uart.c b/drivers/tty/serial/rpi-fw-uart.c new file mode 100644 index 00000000000000..b6d3b68927f418 --- /dev/null +++ b/drivers/tty/serial/rpi-fw-uart.c @@ -0,0 +1,562 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2024, Raspberry Pi Ltd. All rights reserved. + */ + +#include <linux/console.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/gpio/consumer.h> +#include <linux/platform_device.h> +#include <linux/serial.h> +#include <linux/serial_core.h> +#include <linux/slab.h> +#include <linux/tty.h> +#include <linux/tty_flip.h> +#include <soc/bcm2835/raspberrypi-firmware.h> +#include <linux/dma-mapping.h> + +#define RPI_FW_UART_RX_FIFO_RD 0xb0 +#define RPI_FW_UART_RX_FIFO_WR 0xb4 +#define RPI_FW_UART_TX_FIFO_RD 0xb8 +#define RPI_FW_UART_TX_FIFO_WR 0xbc + +#define RPI_FW_UART_FIFO_SIZE 32 +#define RPI_FW_UART_FIFO_SIZE_MASK (RPI_FW_UART_FIFO_SIZE - 1) + +#define RPI_FW_UART_MIN_VERSION 3 + +struct rpi_fw_uart_params { + u32 start; + u32 baud; + u32 data_bits; + u32 stop_bits; + u32 gpio_rx; + u32 gpio_tx; + u32 flags; + u32 fifosize; + u32 rx_buffer; + u32 tx_buffer; + u32 version; + u32 fifo_reg_base; +}; + +struct rpi_fw_uart { + struct uart_driver driver; + struct uart_port port; + struct rpi_firmware *firmware; + struct gpio_desc *rx_gpiod; + struct gpio_desc *tx_gpiod; + unsigned int rx_gpio; + unsigned int tx_gpio; + unsigned int baud; + unsigned int data_bits; + unsigned int stop_bits; + unsigned char __iomem *base; + size_t dma_buffer_size; + + struct hrtimer trigger_start_rx; + ktime_t rx_poll_delay; + void *rx_buffer; + dma_addr_t rx_buffer_dma_addr; + int rx_stop; + + void *tx_buffer; + dma_addr_t tx_buffer_dma_addr; +}; + +static unsigned int rpi_fw_uart_tx_is_full(struct uart_port *port) +{ + struct rpi_fw_uart *rfu = container_of(port, struct rpi_fw_uart, port); + u32 rd, wr; + + rd = readl(rfu->base + RPI_FW_UART_TX_FIFO_RD); + wr = readl(rfu->base + RPI_FW_UART_TX_FIFO_WR); + return ((wr + 1) & RPI_FW_UART_FIFO_SIZE_MASK) == rd; +} + +static unsigned int rpi_fw_uart_tx_is_empty(struct uart_port *port) +{ + struct rpi_fw_uart *rfu = container_of(port, struct rpi_fw_uart, port); + u32 rd, wr; + + if (!rfu->tx_buffer) + return 1; + + rd = readl(rfu->base + RPI_FW_UART_TX_FIFO_RD); + wr = readl(rfu->base + RPI_FW_UART_TX_FIFO_WR); + + return rd == wr; +} + +static unsigned int rpi_fw_uart_rx_is_empty(struct uart_port *port) +{ + struct rpi_fw_uart *rfu = container_of(port, struct rpi_fw_uart, port); + u32 rd, wr; + + if (!rfu->rx_buffer) + return 1; + + rd = readl(rfu->base + RPI_FW_UART_RX_FIFO_RD); + wr = readl(rfu->base + RPI_FW_UART_RX_FIFO_WR); + + return rd == wr; +} + +static unsigned int rpi_fw_uart_tx_empty(struct uart_port *port) +{ + return rpi_fw_uart_tx_is_empty(port) ? TIOCSER_TEMT : 0; +} + +static void rpi_fw_uart_set_mctrl(struct uart_port *port, unsigned int mctrl) +{ + /* + * No hardware flow control, firmware automatically configures + * TX to output high and RX to input low. + */ + dev_dbg(port->dev, "%s mctrl %u\n", __func__, mctrl); +} + +static unsigned int rpi_fw_uart_get_mctrl(struct uart_port *port) +{ + /* No hardware flow control */ + return TIOCM_CTS; +} + +static void rpi_fw_uart_stop(struct uart_port *port) +{ + struct rpi_fw_uart_params msg = {.start = 0}; + struct rpi_fw_uart *rfu = container_of(port, struct rpi_fw_uart, port); + + hrtimer_cancel(&rfu->trigger_start_rx); + + if (rpi_firmware_property(rfu->firmware, + RPI_FIRMWARE_SET_SW_UART, + &msg, sizeof(msg))) + dev_warn(port->dev, + "Failed to shutdown rpi-fw uart. Firmware not configured?"); +} + +static void rpi_fw_uart_stop_tx(struct uart_port *port) +{ + /* No supported by the current firmware APIs. */ +} + +static void rpi_fw_uart_stop_rx(struct uart_port *port) +{ + struct rpi_fw_uart *rfu = container_of(port, struct rpi_fw_uart, port); + + rfu->rx_stop = 1; +} + +static unsigned int rpi_fw_write(struct uart_port *port, const char *s, + unsigned int count) +{ + struct rpi_fw_uart *rfu = container_of(port, struct rpi_fw_uart, port); + u8 *out = rfu->tx_buffer; + unsigned int consumed = 0; + + while (consumed < count && !rpi_fw_uart_tx_is_full(port)) { + u32 wp = readl(rfu->base + RPI_FW_UART_TX_FIFO_WR) + & RPI_FW_UART_FIFO_SIZE_MASK; + out[wp] = s[consumed++]; + wp = (wp + 1) & RPI_FW_UART_FIFO_SIZE_MASK; + writel(wp, rfu->base + RPI_FW_UART_TX_FIFO_WR); + } + return consumed; +} + +/* Called with port.lock taken */ +static void rpi_fw_uart_start_tx(struct uart_port *port) +{ + struct tty_port *tport = &port->state->port; + + for (;;) { + unsigned int consumed; + unsigned char *tail; + unsigned int count; + + count = kfifo_out_linear_ptr(&tport->xmit_fifo, &tail, port->fifosize); + if (!count) + break; + + consumed = rpi_fw_write(port, tail, count); + uart_xmit_advance(port, consumed); + } + uart_write_wakeup(port); +} + +/* Called with port.lock taken */ +static void rpi_fw_uart_start_rx(struct uart_port *port) +{ + struct tty_port *tty_port = &port->state->port; + struct rpi_fw_uart *rfu = container_of(port, struct rpi_fw_uart, port); + int count = 0; + + /* + * RX is polled, read up to a full buffer of data before trying again + * so that this can be interrupted if the firmware is filling the + * buffer too fast + */ + while (!rpi_fw_uart_rx_is_empty(port) && count < port->fifosize) { + const u8 *in = rfu->rx_buffer; + u32 rp = readl(rfu->base + RPI_FW_UART_RX_FIFO_RD) + & RPI_FW_UART_FIFO_SIZE_MASK; + + tty_insert_flip_char(tty_port, in[rp], TTY_NORMAL); + rp = (rp + 1) & RPI_FW_UART_FIFO_SIZE_MASK; + writel(rp, rfu->base + RPI_FW_UART_RX_FIFO_RD); + count++; + } + if (count) + tty_flip_buffer_push(tty_port); +} + +static enum hrtimer_restart rpi_fw_uart_trigger_rx(struct hrtimer *t) +{ + unsigned long flags; + struct rpi_fw_uart *rfu = container_of(t, struct rpi_fw_uart, + trigger_start_rx); + + spin_lock_irqsave(&rfu->port.lock, flags); + if (rfu->rx_stop) { + spin_unlock_irqrestore(&rfu->port.lock, flags); + return HRTIMER_NORESTART; + } + + rpi_fw_uart_start_rx(&rfu->port); + spin_unlock_irqrestore(&rfu->port.lock, flags); + hrtimer_forward_now(t, rfu->rx_poll_delay); + return HRTIMER_RESTART; +} + +static void rpi_fw_uart_break_ctl(struct uart_port *port, int ctl) +{ + dev_dbg(port->dev, "%s ctl %d\n", __func__, ctl); +} + +static int rpi_fw_uart_configure(struct uart_port *port) +{ + struct rpi_fw_uart *rfu = container_of(port, struct rpi_fw_uart, port); + struct rpi_fw_uart_params msg; + unsigned long flags; + int rc; + + rpi_fw_uart_stop(port); + + memset(&msg, 0, sizeof(msg)); + msg.start = 1; + msg.gpio_rx = rfu->rx_gpio; + msg.gpio_tx = rfu->tx_gpio; + msg.data_bits = rfu->data_bits; + msg.stop_bits = rfu->stop_bits; + msg.baud = rfu->baud; + msg.fifosize = RPI_FW_UART_FIFO_SIZE; + msg.rx_buffer = (u32) rfu->rx_buffer_dma_addr; + msg.tx_buffer = (u32) rfu->tx_buffer_dma_addr; + + rfu->rx_poll_delay = ms_to_ktime(50); + + /* + * Reconfigures the firmware UART with the new settings. On the first + * call retrieve the addresses of the FIFO buffers. The buffers are + * allocated at startup and are not de-allocated. + * NB rpi_firmware_property can block + */ + rc = rpi_firmware_property(rfu->firmware, + RPI_FIRMWARE_SET_SW_UART, + &msg, sizeof(msg)); + if (rc) + goto fail; + + rc = rpi_firmware_property(rfu->firmware, + RPI_FIRMWARE_GET_SW_UART, + &msg, sizeof(msg)); + if (rc) + goto fail; + + dev_dbg(port->dev, "version %08x, reg addr %x\n", msg.version, + msg.fifo_reg_base); + + dev_dbg(port->dev, "started %d baud %u data %u stop %u rx %u tx %u flags %u fifosize %u\n", + msg.start, msg.baud, msg.data_bits, msg.stop_bits, + msg.gpio_rx, msg.gpio_tx, msg.flags, msg.fifosize); + + if (msg.fifosize != port->fifosize) { + dev_err(port->dev, "Expected fifo size %u actual %u", + port->fifosize, msg.fifosize); + rc = -EINVAL; + goto fail; + } + + if (!msg.start) { + dev_err(port->dev, "Firmware service not running\n"); + rc = -EINVAL; + } + + spin_lock_irqsave(&rfu->port.lock, flags); + rfu->rx_stop = 0; + hrtimer_start(&rfu->trigger_start_rx, + rfu->rx_poll_delay, HRTIMER_MODE_REL); + spin_unlock_irqrestore(&rfu->port.lock, flags); + return 0; +fail: + dev_err(port->dev, "Failed to configure rpi-fw uart. Firmware not configured?"); + return rc; +} + +static void rpi_fw_uart_free_buffers(struct uart_port *port) +{ + struct rpi_fw_uart *rfu = container_of(port, struct rpi_fw_uart, port); + + if (rfu->rx_buffer) + dma_free_coherent(port->dev, rfu->dma_buffer_size, + rfu->rx_buffer, GFP_ATOMIC); + + if (rfu->tx_buffer) + dma_free_coherent(port->dev, rfu->dma_buffer_size, + rfu->tx_buffer, GFP_ATOMIC); + + rfu->rx_buffer = NULL; + rfu->tx_buffer = NULL; + rfu->rx_buffer_dma_addr = 0; + rfu->tx_buffer_dma_addr = 0; +} + +static int rpi_fw_uart_alloc_buffers(struct uart_port *port) +{ + struct rpi_fw_uart *rfu = container_of(port, struct rpi_fw_uart, port); + + if (rfu->tx_buffer) + return 0; + + rfu->dma_buffer_size = PAGE_ALIGN(RPI_FW_UART_FIFO_SIZE); + + rfu->rx_buffer = dma_alloc_coherent(port->dev, rfu->dma_buffer_size, + &rfu->rx_buffer_dma_addr, GFP_ATOMIC); + + if (!rfu->rx_buffer) + goto alloc_fail; + + rfu->tx_buffer = dma_alloc_coherent(port->dev, rfu->dma_buffer_size, + &rfu->tx_buffer_dma_addr, GFP_ATOMIC); + + if (!rfu->tx_buffer) + goto alloc_fail; + + dev_dbg(port->dev, "alloc-buffers %p %x %p %x\n", + rfu->rx_buffer, (u32) rfu->rx_buffer_dma_addr, + rfu->tx_buffer, (u32) rfu->tx_buffer_dma_addr); + return 0; + +alloc_fail: + dev_err(port->dev, "%s uart buffer allocation failed\n", __func__); + rpi_fw_uart_free_buffers(port); + return -ENOMEM; +} + +static int rpi_fw_uart_startup(struct uart_port *port) +{ + int rc; + + rc = rpi_fw_uart_alloc_buffers(port); + if (rc) + dev_err(port->dev, "Failed to start\n"); + return rc; +} + +static void rpi_fw_uart_shutdown(struct uart_port *port) +{ + rpi_fw_uart_stop(port); + rpi_fw_uart_free_buffers(port); +} + +static void rpi_fw_uart_set_termios(struct uart_port *port, + struct ktermios *new, + const struct ktermios *old) +{ + struct rpi_fw_uart *rfu = + container_of(port, struct rpi_fw_uart, port); + rfu->baud = uart_get_baud_rate(port, new, old, 50, 115200); + rfu->stop_bits = (new->c_cflag & CSTOPB) ? 2 : 1; + + rpi_fw_uart_configure(port); +} + +static const struct uart_ops rpi_fw_uart_ops = { + .tx_empty = rpi_fw_uart_tx_empty, + .set_mctrl = rpi_fw_uart_set_mctrl, + .get_mctrl = rpi_fw_uart_get_mctrl, + .stop_rx = rpi_fw_uart_stop_rx, + .stop_tx = rpi_fw_uart_stop_tx, + .start_tx = rpi_fw_uart_start_tx, + .break_ctl = rpi_fw_uart_break_ctl, + .startup = rpi_fw_uart_startup, + .shutdown = rpi_fw_uart_shutdown, + .set_termios = rpi_fw_uart_set_termios, +}; + +static int rpi_fw_uart_get_gpio_offset(struct device *dev, const char *name) +{ + struct of_phandle_args of_args = { 0 }; + bool is_bcm28xx; + + /* This really shouldn't fail, given that we have a gpiod */ + if (of_parse_phandle_with_args(dev->of_node, name, "#gpio-cells", 0, &of_args)) + return dev_err_probe(dev, -EINVAL, "can't find gpio declaration\n"); + + is_bcm28xx = of_device_is_compatible(of_args.np, "brcm,bcm2835-gpio") || + of_device_is_compatible(of_args.np, "brcm,bcm2711-gpio"); + of_node_put(of_args.np); + if (!is_bcm28xx || of_args.args_count != 2) + return dev_err_probe(dev, -EINVAL, "not a BCM28xx gpio\n"); + + return of_args.args[0]; +} + +static int rpi_fw_uart_probe(struct platform_device *pdev) +{ + struct device_node *firmware_node; + struct device *dev = &pdev->dev; + struct rpi_firmware *firmware; + struct uart_port *port; + struct rpi_fw_uart *rfu; + struct rpi_fw_uart_params msg; + int version_major; + int err; + + dev_dbg(dev, "%s of_node %p\n", __func__, dev->of_node); + + /* + * We can be probed either through the an old-fashioned + * platform device registration or through a DT node that is a + * child of the firmware node. Handle both cases. + */ + if (dev->of_node) + firmware_node = of_parse_phandle(dev->of_node, "firmware", 0); + else + firmware_node = of_find_compatible_node(NULL, NULL, + "raspberrypi,bcm2835-firmware"); + if (!firmware_node) { + dev_err(dev, "Missing firmware node\n"); + return -ENOENT; + } + + firmware = devm_rpi_firmware_get(dev, firmware_node); + of_node_put(firmware_node); + if (!firmware) + return -EPROBE_DEFER; + + rfu = devm_kzalloc(dev, sizeof(*rfu), GFP_KERNEL); + if (!rfu) + return -ENOMEM; + + rfu->firmware = firmware; + + err = rpi_firmware_property(rfu->firmware, RPI_FIRMWARE_GET_SW_UART, + &msg, sizeof(msg)); + if (err) { + dev_err(dev, "VC firmware does not support rpi-fw-uart\n"); + return err; + } + + version_major = msg.version >> 16; + if (msg.version < RPI_FW_UART_MIN_VERSION) { + dev_err(dev, "rpi-fw-uart fw version %d is too old min version %d\n", + version_major, RPI_FW_UART_MIN_VERSION); + return -EINVAL; + } + + rfu->rx_gpiod = devm_gpiod_get(dev, "rx", GPIOD_IN); + if (IS_ERR(rfu->rx_gpiod)) + return PTR_ERR(rfu->rx_gpiod); + + rfu->tx_gpiod = devm_gpiod_get(dev, "tx", GPIOD_OUT_HIGH); + if (IS_ERR(rfu->tx_gpiod)) + return PTR_ERR(rfu->tx_gpiod); + + rfu->rx_gpio = rpi_fw_uart_get_gpio_offset(dev, "rx-gpios"); + if (rfu->rx_gpio < 0) + return rfu->rx_gpio; + rfu->tx_gpio = rpi_fw_uart_get_gpio_offset(dev, "tx-gpios"); + if (rfu->tx_gpio < 0) + return rfu->tx_gpio; + + rfu->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(rfu->base)) + return PTR_ERR(rfu->base); + + /* setup the driver */ + rfu->driver.owner = THIS_MODULE; + rfu->driver.driver_name = "ttyRFU"; + rfu->driver.dev_name = "ttyRFU"; + rfu->driver.nr = 1; + rfu->data_bits = 8; + + /* RX is polled */ + hrtimer_init(&rfu->trigger_start_rx, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + rfu->trigger_start_rx.function = rpi_fw_uart_trigger_rx; + + err = uart_register_driver(&rfu->driver); + if (err) { + dev_err(dev, "failed to register UART driver: %d\n", + err); + return err; + } + + /* setup the port */ + port = &rfu->port; + spin_lock_init(&port->lock); + port->dev = &pdev->dev; + port->type = PORT_RPI_FW; + port->ops = &rpi_fw_uart_ops; + port->fifosize = RPI_FW_UART_FIFO_SIZE; + port->iotype = UPIO_MEM; + port->flags = UPF_BOOT_AUTOCONF; + port->private_data = rfu; + + err = uart_add_one_port(&rfu->driver, port); + if (err) { + dev_err(dev, "failed to add UART port: %d\n", err); + goto unregister_uart; + } + platform_set_drvdata(pdev, rfu); + + dev_info(dev, "version %d.%d gpios tx %u rx %u\n", + msg.version >> 16, msg.version & 0xffff, + rfu->tx_gpio, rfu->rx_gpio); + return 0; + +unregister_uart: + uart_unregister_driver(&rfu->driver); + + return err; +} + +static void rpi_fw_uart_remove(struct platform_device *pdev) +{ + struct rpi_fw_uart *rfu = platform_get_drvdata(pdev); + + uart_remove_one_port(&rfu->driver, &rfu->port); + uart_unregister_driver(&rfu->driver); +} + +static const struct of_device_id rpi_fw_match[] = { + { .compatible = "raspberrypi,firmware-uart" }, + { } +}; +MODULE_DEVICE_TABLE(of, rpi_fw_match); + +static struct platform_driver rpi_fw_driver = { + .driver = { + .name = "rpi_fw-uart", + .of_match_table = rpi_fw_match, + }, + .probe = rpi_fw_uart_probe, + .remove = rpi_fw_uart_remove, +}; +module_platform_driver(rpi_fw_driver); + +MODULE_AUTHOR("Tim Gover <tim.gover@rasberrypi.com>"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Raspberry Pi Firmware Software UART driver"); diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c index 6a0a1cce3a897f..e1938b95cf76dd 100644 --- a/drivers/tty/serial/sc16is7xx.c +++ b/drivers/tty/serial/sc16is7xx.c @@ -821,6 +821,8 @@ static bool sc16is7xx_port_irq(struct sc16is7xx_port *s, int portno) if (rxlen) sc16is7xx_handle_rx(port, rxlen, iir); + else + rc = false; break; /* CTSRTS interrupt comes only when CTS goes inactive */ case SC16IS7XX_IIR_CTSRTS_SRC: @@ -1206,6 +1208,9 @@ static int sc16is7xx_startup(struct uart_port *port) SC16IS7XX_IER_MSI_BIT; sc16is7xx_port_write(port, SC16IS7XX_IER_REG, val); + /* Initialize the Modem Control signals to current status */ + one->old_mctrl = sc16is7xx_get_hwmctrl(port); + /* Enable modem status polling */ uart_port_lock_irqsave(port, &flags); sc16is7xx_enable_ms(port); @@ -1473,7 +1478,7 @@ static int sc16is7xx_setup_mctrl_ports(struct sc16is7xx_port *s, } static const struct serial_rs485 sc16is7xx_rs485_supported = { - .flags = SER_RS485_ENABLED | SER_RS485_RTS_AFTER_SEND, + .flags = SER_RS485_ENABLED | SER_RS485_RTS_ON_SEND | SER_RS485_RTS_AFTER_SEND, .delay_rts_before_send = 1, .delay_rts_after_send = 1, /* Not supported but keep returning -EINVAL */ }; diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile index 949eca0adebea3..80af388baede2e 100644 --- a/drivers/usb/Makefile +++ b/drivers/usb/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_USB_COMMON) += common/ obj-$(CONFIG_USB) += core/ obj-$(CONFIG_USB_SUPPORT) += phy/ +obj-$(CONFIG_USB_DWCOTG) += host/ obj-$(CONFIG_USB_DWC3) += dwc3/ obj-$(CONFIG_USB_DWC2) += dwc2/ obj-$(CONFIG_USB_ISP1760) += isp1760/ diff --git a/drivers/usb/core/generic.c b/drivers/usb/core/generic.c index b134bff5c3fe3e..f2d342d80bc0e2 100644 --- a/drivers/usb/core/generic.c +++ b/drivers/usb/core/generic.c @@ -206,6 +206,7 @@ int usb_choose_configuration(struct usb_device *udev) dev_warn(&udev->dev, "no configuration chosen from %d choice%s\n", num_configs, plural(num_configs)); + dev_warn(&udev->dev, "No support over %dmA\n", udev->bus_mA); } return i; } diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 0b2490347b9fe7..989c0ab5b6c21e 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -1953,6 +1953,16 @@ int usb_hcd_alloc_bandwidth(struct usb_device *udev, return ret; } +void usb_hcd_fixup_endpoint(struct usb_device *udev, + struct usb_host_endpoint *ep, int interval) +{ + struct usb_hcd *hcd; + + hcd = bus_to_hcd(udev->bus); + if (hcd->driver->fixup_endpoint) + hcd->driver->fixup_endpoint(hcd, udev, ep, interval); +} + /* Disables the endpoint: synchronizes with the hcd to make sure all * endpoint state is gone from hardware. usb_hcd_flush_endpoint() must * have been called previously. Use for set_configuration, set_interface, diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 906daf423cb02b..94006165e76c49 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -5754,7 +5754,7 @@ static void port_event(struct usb_hub *hub, int port1) port_dev->over_current_count++; port_over_current_notify(port_dev); - dev_dbg(&port_dev->dev, "over-current change #%u\n", + dev_notice(&port_dev->dev, "over-current change #%u\n", port_dev->over_current_count); usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_OVER_CURRENT); diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c index d2b2787be4092e..94df7eadf33004 100644 --- a/drivers/usb/core/message.c +++ b/drivers/usb/core/message.c @@ -1267,6 +1267,21 @@ static void remove_intf_ep_devs(struct usb_interface *intf) intf->ep_devs_created = 0; } +void usb_fixup_endpoint(struct usb_device *dev, int epaddr, int interval) +{ + unsigned int epnum = epaddr & USB_ENDPOINT_NUMBER_MASK; + struct usb_host_endpoint *ep; + + if (usb_endpoint_out(epaddr)) + ep = dev->ep_out[epnum]; + else + ep = dev->ep_in[epnum]; + + if (ep && usb_endpoint_xfer_int(&ep->desc)) + usb_hcd_fixup_endpoint(dev, ep, interval); +} +EXPORT_SYMBOL_GPL(usb_fixup_endpoint); + /** * usb_disable_endpoint -- Disable an endpoint by address * @dev: the device whose endpoint is being disabled @@ -2180,6 +2195,85 @@ int usb_set_configuration(struct usb_device *dev, int configuration) if (cp->string == NULL && !(dev->quirks & USB_QUIRK_CONFIG_INTF_STRINGS)) cp->string = usb_cache_string(dev, cp->desc.iConfiguration); +/* Uncomment this define to enable the HS Electrical Test support */ +#define DWC_HS_ELECT_TST 1 +#ifdef DWC_HS_ELECT_TST + /* Here we implement the HS Electrical Test support. The + * tester uses a vendor ID of 0x1A0A to indicate we should + * run a special test sequence. The product ID tells us + * which sequence to run. We invoke the test sequence by + * sending a non-standard SetFeature command to our root + * hub port. Our dwc_otg_hcd_hub_control() routine will + * recognize the command and perform the desired test + * sequence. + */ + if (dev->descriptor.idVendor == 0x1A0A) { + /* HSOTG Electrical Test */ + dev_warn(&dev->dev, "VID from HSOTG Electrical Test Fixture\n"); + + if (dev->bus && dev->bus->root_hub) { + struct usb_device *hdev = dev->bus->root_hub; + dev_warn(&dev->dev, "Got PID 0x%x\n", dev->descriptor.idProduct); + + switch (dev->descriptor.idProduct) { + case 0x0101: /* TEST_SE0_NAK */ + dev_warn(&dev->dev, "TEST_SE0_NAK\n"); + usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0), + USB_REQ_SET_FEATURE, USB_RT_PORT, + USB_PORT_FEAT_TEST, 0x300, NULL, 0, HZ); + break; + + case 0x0102: /* TEST_J */ + dev_warn(&dev->dev, "TEST_J\n"); + usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0), + USB_REQ_SET_FEATURE, USB_RT_PORT, + USB_PORT_FEAT_TEST, 0x100, NULL, 0, HZ); + break; + + case 0x0103: /* TEST_K */ + dev_warn(&dev->dev, "TEST_K\n"); + usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0), + USB_REQ_SET_FEATURE, USB_RT_PORT, + USB_PORT_FEAT_TEST, 0x200, NULL, 0, HZ); + break; + + case 0x0104: /* TEST_PACKET */ + dev_warn(&dev->dev, "TEST_PACKET\n"); + usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0), + USB_REQ_SET_FEATURE, USB_RT_PORT, + USB_PORT_FEAT_TEST, 0x400, NULL, 0, HZ); + break; + + case 0x0105: /* TEST_FORCE_ENABLE */ + dev_warn(&dev->dev, "TEST_FORCE_ENABLE\n"); + usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0), + USB_REQ_SET_FEATURE, USB_RT_PORT, + USB_PORT_FEAT_TEST, 0x500, NULL, 0, HZ); + break; + + case 0x0106: /* HS_HOST_PORT_SUSPEND_RESUME */ + dev_warn(&dev->dev, "HS_HOST_PORT_SUSPEND_RESUME\n"); + usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0), + USB_REQ_SET_FEATURE, USB_RT_PORT, + USB_PORT_FEAT_TEST, 0x600, NULL, 0, 40 * HZ); + break; + + case 0x0107: /* SINGLE_STEP_GET_DEVICE_DESCRIPTOR setup */ + dev_warn(&dev->dev, "SINGLE_STEP_GET_DEVICE_DESCRIPTOR setup\n"); + usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0), + USB_REQ_SET_FEATURE, USB_RT_PORT, + USB_PORT_FEAT_TEST, 0x700, NULL, 0, 40 * HZ); + break; + + case 0x0108: /* SINGLE_STEP_GET_DEVICE_DESCRIPTOR execute */ + dev_warn(&dev->dev, "SINGLE_STEP_GET_DEVICE_DESCRIPTOR execute\n"); + usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0), + USB_REQ_SET_FEATURE, USB_RT_PORT, + USB_PORT_FEAT_TEST, 0x800, NULL, 0, 40 * HZ); + } + } + } +#endif /* DWC_HS_ELECT_TST */ /* Now that the interfaces are installed, re-enable LPM. */ usb_unlocked_enable_lpm(dev); diff --git a/drivers/usb/core/otg_productlist.h b/drivers/usb/core/otg_productlist.h index db67df29fb2b10..b16e528859a80f 100644 --- a/drivers/usb/core/otg_productlist.h +++ b/drivers/usb/core/otg_productlist.h @@ -11,33 +11,82 @@ static struct usb_device_id productlist_table[] = { /* hubs are optional in OTG, but very handy ... */ +#define CERT_WITHOUT_HUBS +#if defined(CERT_WITHOUT_HUBS) +{ USB_DEVICE( 0x0000, 0x0000 ), }, /* Root HUB Only*/ +#else { USB_DEVICE_INFO(USB_CLASS_HUB, 0, 0), }, { USB_DEVICE_INFO(USB_CLASS_HUB, 0, 1), }, +{ USB_DEVICE_INFO(USB_CLASS_HUB, 0, 2), }, +#endif #ifdef CONFIG_USB_PRINTER /* ignoring nonstatic linkage! */ /* FIXME actually, printers are NOT supposed to use device classes; * they're supposed to use interface classes... */ -{ USB_DEVICE_INFO(7, 1, 1) }, -{ USB_DEVICE_INFO(7, 1, 2) }, -{ USB_DEVICE_INFO(7, 1, 3) }, +//{ USB_DEVICE_INFO(7, 1, 1) }, +//{ USB_DEVICE_INFO(7, 1, 2) }, +//{ USB_DEVICE_INFO(7, 1, 3) }, #endif #ifdef CONFIG_USB_NET_CDCETHER /* Linux-USB CDC Ethernet gadget */ -{ USB_DEVICE(0x0525, 0xa4a1), }, +//{ USB_DEVICE(0x0525, 0xa4a1), }, /* Linux-USB CDC Ethernet + RNDIS gadget */ -{ USB_DEVICE(0x0525, 0xa4a2), }, +//{ USB_DEVICE(0x0525, 0xa4a2), }, #endif #if IS_ENABLED(CONFIG_USB_TEST) /* gadget zero, for testing */ -{ USB_DEVICE(0x0525, 0xa4a0), }, +//{ USB_DEVICE(0x0525, 0xa4a0), }, #endif +/* OPT Tester */ +{ USB_DEVICE( 0x1a0a, 0x0101 ), }, /* TEST_SE0_NAK */ +{ USB_DEVICE( 0x1a0a, 0x0102 ), }, /* Test_J */ +{ USB_DEVICE( 0x1a0a, 0x0103 ), }, /* Test_K */ +{ USB_DEVICE( 0x1a0a, 0x0104 ), }, /* Test_PACKET */ +{ USB_DEVICE( 0x1a0a, 0x0105 ), }, /* Test_FORCE_ENABLE */ +{ USB_DEVICE( 0x1a0a, 0x0106 ), }, /* HS_PORT_SUSPEND_RESUME */ +{ USB_DEVICE( 0x1a0a, 0x0107 ), }, /* SINGLE_STEP_GET_DESCRIPTOR setup */ +{ USB_DEVICE( 0x1a0a, 0x0108 ), }, /* SINGLE_STEP_GET_DESCRIPTOR execute */ + +/* Sony cameras */ +{ USB_DEVICE_VER(0x054c,0x0010,0x0410, 0x0500), }, + +/* Memory Devices */ +//{ USB_DEVICE( 0x0781, 0x5150 ), }, /* SanDisk */ +//{ USB_DEVICE( 0x05DC, 0x0080 ), }, /* Lexar */ +//{ USB_DEVICE( 0x4146, 0x9281 ), }, /* IOMEGA */ +//{ USB_DEVICE( 0x067b, 0x2507 ), }, /* Hammer 20GB External HD */ +{ USB_DEVICE( 0x0EA0, 0x2168 ), }, /* Ours Technology Inc. (BUFFALO ClipDrive)*/ +//{ USB_DEVICE( 0x0457, 0x0150 ), }, /* Silicon Integrated Systems Corp. */ + +/* HP Printers */ +//{ USB_DEVICE( 0x03F0, 0x1102 ), }, /* HP Photosmart 245 */ +//{ USB_DEVICE( 0x03F0, 0x1302 ), }, /* HP Photosmart 370 Series */ + +/* Speakers */ +//{ USB_DEVICE( 0x0499, 0x3002 ), }, /* YAMAHA YST-MS35D USB Speakers */ +//{ USB_DEVICE( 0x0672, 0x1041 ), }, /* Labtec USB Headset */ + { } /* Terminating entry */ }; +static inline void report_errors(struct usb_device *dev) +{ + /* OTG MESSAGE: report errors here, customize to match your product */ + dev_info(&dev->dev, "device Vendor:%04x Product:%04x is not supported\n", + le16_to_cpu(dev->descriptor.idVendor), + le16_to_cpu(dev->descriptor.idProduct)); + if (USB_CLASS_HUB == dev->descriptor.bDeviceClass){ + dev_printk(KERN_CRIT, &dev->dev, "Unsupported Hub Topology\n"); + } else { + dev_printk(KERN_CRIT, &dev->dev, "Attached Device is not Supported\n"); + } +} + + static int is_targeted(struct usb_device *dev) { struct usb_device_id *id = productlist_table; @@ -87,16 +136,57 @@ static int is_targeted(struct usb_device *dev) continue; return 1; - } + /* NOTE: can't use usb_match_id() since interface caches + * aren't set up yet. this is cut/paste from that code. + */ + for (id = productlist_table; id->match_flags; id++) { +#ifdef DEBUG + dev_dbg(&dev->dev, + "ID: V:%04x P:%04x DC:%04x SC:%04x PR:%04x \n", + id->idVendor, + id->idProduct, + id->bDeviceClass, + id->bDeviceSubClass, + id->bDeviceProtocol); +#endif - /* add other match criteria here ... */ + if ((id->match_flags & USB_DEVICE_ID_MATCH_VENDOR) && + id->idVendor != le16_to_cpu(dev->descriptor.idVendor)) + continue; + if ((id->match_flags & USB_DEVICE_ID_MATCH_PRODUCT) && + id->idProduct != le16_to_cpu(dev->descriptor.idProduct)) + continue; - /* OTG MESSAGE: report errors here, customize to match your product */ - dev_err(&dev->dev, "device v%04x p%04x is not supported\n", - le16_to_cpu(dev->descriptor.idVendor), - le16_to_cpu(dev->descriptor.idProduct)); + /* No need to test id->bcdDevice_lo != 0, since 0 is never + greater than any unsigned number. */ + if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_LO) && + (id->bcdDevice_lo > le16_to_cpu(dev->descriptor.bcdDevice))) + continue; + + if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_HI) && + (id->bcdDevice_hi < le16_to_cpu(dev->descriptor.bcdDevice))) + continue; + + if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_CLASS) && + (id->bDeviceClass != dev->descriptor.bDeviceClass)) + continue; + + if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_SUBCLASS) && + (id->bDeviceSubClass != dev->descriptor.bDeviceSubClass)) + continue; + + if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_PROTOCOL) && + (id->bDeviceProtocol != dev->descriptor.bDeviceProtocol)) + continue; + + return 1; + } + } + + /* add other match criteria here ... */ + report_errors(dev); return 0; } diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 244e3e04e1ad74..4ed43c3a4ec76e 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -1304,6 +1304,24 @@ static void dwc3_config_threshold(struct dwc3 *dwc) } } +static void dwc3_set_axi_pipe_limit(struct dwc3 *dwc) +{ + struct device *dev = dwc->dev; + u32 cfg; + + if (!dwc->axi_pipe_limit) + return; + if (dwc->axi_pipe_limit > 16) { + dev_err(dev, "Invalid axi_pipe_limit property\n"); + return; + } + cfg = dwc3_readl(dwc->regs, DWC3_GSBUSCFG1); + cfg &= ~DWC3_GSBUSCFG1_PIPETRANSLIMIT(15); + cfg |= DWC3_GSBUSCFG1_PIPETRANSLIMIT(dwc->axi_pipe_limit - 1); + + dwc3_writel(dwc->regs, DWC3_GSBUSCFG1, cfg); +} + /** * dwc3_core_init - Low-level initialization of DWC3 Core * @dwc: Pointer to our controller context structure @@ -1371,6 +1389,8 @@ static int dwc3_core_init(struct dwc3 *dwc) dwc3_config_soc_bus(dwc); + dwc3_set_axi_pipe_limit(dwc); + ret = dwc3_phy_power_on(dwc); if (ret) goto err_exit_phy; @@ -1444,12 +1464,21 @@ static int dwc3_core_init(struct dwc3 *dwc) if (dwc->dis_tx_ipgap_linecheck_quirk) reg |= DWC3_GUCTL1_TX_IPGAP_LINECHECK_DIS; + if (dwc->enh_nak_fs_quirk) + reg |= DWC3_GUCTL1_NAK_PER_ENH_FS; + + if (dwc->enh_nak_hs_quirk) + reg |= DWC3_GUCTL1_NAK_PER_ENH_HS; + if (dwc->parkmode_disable_ss_quirk) reg |= DWC3_GUCTL1_PARKMODE_DISABLE_SS; if (dwc->parkmode_disable_hs_quirk) reg |= DWC3_GUCTL1_PARKMODE_DISABLE_HS; + if (dwc->parkmode_disable_fsls_quirk) + reg |= DWC3_GUCTL1_PARKMODE_DISABLE_FSLS; + if (DWC3_VER_IS_WITHIN(DWC3, 290A, ANY)) { if (dwc->maximum_speed == USB_SPEED_FULL || dwc->maximum_speed == USB_SPEED_HIGH) @@ -1475,6 +1504,24 @@ static int dwc3_core_init(struct dwc3 *dwc) dwc3_writel(dwc->regs, DWC3_LLUCTL, reg); } + if (DWC3_IP_IS(DWC3) && dwc->dr_mode == USB_DR_MODE_HOST) { + u8 tx_thr_num = dwc->tx_thr_num_pkt_prd; + u8 tx_maxburst = dwc->tx_max_burst_prd; + + if (tx_thr_num && tx_maxburst) { + reg = dwc3_readl(dwc->regs, DWC3_GTXTHRCFG); + reg |= DWC3_GTXTHRCFG_PKTCNTSEL; + + reg &= ~DWC3_GTXTHRCFG_TXPKTCNT(~0); + reg |= DWC3_GTXTHRCFG_TXPKTCNT(tx_thr_num); + + reg &= ~DWC3_GTXTHRCFG_MAXTXBURSTSIZE(~0); + reg |= DWC3_GTXTHRCFG_MAXTXBURSTSIZE(tx_maxburst); + + dwc3_writel(dwc->regs, DWC3_GTXTHRCFG, reg); + } + } + return 0; err_power_off_phy: @@ -1660,6 +1707,7 @@ static void dwc3_get_properties(struct dwc3 *dwc) u8 tx_thr_num_pkt_prd = 0; u8 tx_max_burst_prd = 0; u8 tx_fifo_resize_max_num; + u8 axi_pipe_limit; /* default to highest possible threshold */ lpm_nyet_threshold = 0xf; @@ -1680,6 +1728,9 @@ static void dwc3_get_properties(struct dwc3 *dwc) */ tx_fifo_resize_max_num = 6; + /* Default to 0 (don't override hardware defaults) */ + axi_pipe_limit = 0; + dwc->maximum_speed = usb_get_maximum_speed(dev); dwc->max_ssp_rate = usb_get_maximum_ssp_rate(dev); dwc->dr_mode = usb_get_dr_mode(dev); @@ -1770,10 +1821,16 @@ static void dwc3_get_properties(struct dwc3 *dwc) "snps,resume-hs-terminations"); dwc->ulpi_ext_vbus_drv = device_property_read_bool(dev, "snps,ulpi-ext-vbus-drv"); + dwc->enh_nak_fs_quirk = device_property_read_bool(dev, + "snps,enhanced-nak-fs-quirk"); + dwc->enh_nak_hs_quirk = device_property_read_bool(dev, + "snps,enhanced-nak-hs-quirk"); dwc->parkmode_disable_ss_quirk = device_property_read_bool(dev, "snps,parkmode-disable-ss-quirk"); dwc->parkmode_disable_hs_quirk = device_property_read_bool(dev, "snps,parkmode-disable-hs-quirk"); + dwc->parkmode_disable_fsls_quirk = device_property_read_bool(dev, + "snps,parkmode-disable-fsls-quirk"); dwc->gfladj_refclk_lpm_sel = device_property_read_bool(dev, "snps,gfladj-refclk-lpm-sel-quirk"); @@ -1794,6 +1851,9 @@ static void dwc3_get_properties(struct dwc3 *dwc) dwc->dis_split_quirk = device_property_read_bool(dev, "snps,dis-split-quirk"); + device_property_read_u8(dev, "snps,axi-pipe-limit", + &axi_pipe_limit); + dwc->lpm_nyet_threshold = lpm_nyet_threshold; dwc->tx_de_emphasis = tx_de_emphasis; @@ -1811,6 +1871,8 @@ static void dwc3_get_properties(struct dwc3 *dwc) dwc->tx_thr_num_pkt_prd = tx_thr_num_pkt_prd; dwc->tx_max_burst_prd = tx_max_burst_prd; + dwc->axi_pipe_limit = axi_pipe_limit; + dwc->imod_interval = 0; dwc->tx_fifo_resize_max_num = tx_fifo_resize_max_num; @@ -2173,6 +2235,12 @@ static int dwc3_probe(struct platform_device *pdev) if (IS_ERR(dwc->usb_psy)) return dev_err_probe(dev, PTR_ERR(dwc->usb_psy), "couldn't get usb power supply\n"); + if (!dwc->sysdev_is_parent) { + ret = dma_set_mask_and_coherent(dwc->sysdev, DMA_BIT_MASK(64)); + if (ret) + return ret; + } + dwc->reset = devm_reset_control_array_get_optional_shared(dev); if (IS_ERR(dwc->reset)) { ret = PTR_ERR(dwc->reset); diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 0e91a227507fff..50de5792f147bc 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -198,6 +198,9 @@ #define DWC3_GSBUSCFG0_REQINFO(n) (((n) & 0xffff) << 16) #define DWC3_GSBUSCFG0_REQINFO_UNSPECIFIED 0xffffffff +/* Global SoC Bus Configuration Register 1 */ +#define DWC3_GSBUSCFG1_PIPETRANSLIMIT(n) (((n) & 0xf) << 8) + /* Global Debug LSP MUX Select */ #define DWC3_GDBGLSPMUX_ENDBC BIT(15) /* Host only */ #define DWC3_GDBGLSPMUX_HOSTSELECT(n) ((n) & 0x3fff) @@ -279,8 +282,11 @@ #define DWC3_GUCTL1_TX_IPGAP_LINECHECK_DIS BIT(28) #define DWC3_GUCTL1_DEV_FORCE_20_CLK_FOR_30_CLK BIT(26) #define DWC3_GUCTL1_DEV_L1_EXIT_BY_HW BIT(24) +#define DWC3_GUCTL1_NAK_PER_ENH_FS BIT(19) +#define DWC3_GUCTL1_NAK_PER_ENH_HS BIT(18) #define DWC3_GUCTL1_PARKMODE_DISABLE_SS BIT(17) #define DWC3_GUCTL1_PARKMODE_DISABLE_HS BIT(16) +#define DWC3_GUCTL1_PARKMODE_DISABLE_FSLS BIT(15) #define DWC3_GUCTL1_RESUME_OPMODE_HS_HOST BIT(10) /* Global Status Register */ @@ -1087,6 +1093,7 @@ struct dwc3_scratchpad_array { * @tx_max_burst_prd: max periodic ESS transmit burst size * @tx_fifo_resize_max_num: max number of fifos allocated during txfifo resize * @clear_stall_protocol: endpoint number that requires a delayed status phase + * @axi_max_pipe: set to override the maximum number of pipelined AXI transfers * @hsphy_interface: "utmi" or "ulpi" * @connected: true when we're connected to a host, false otherwise * @softconnect: true when gadget connect is called, false when disconnect runs @@ -1138,10 +1145,14 @@ struct dwc3_scratchpad_array { * generation after resume from suspend. * @ulpi_ext_vbus_drv: Set to confiure the upli chip to drives CPEN pin * VBUS with an external supply. - * @parkmode_disable_ss_quirk: set if we need to disable all SuperSpeed - * instances in park mode. - * @parkmode_disable_hs_quirk: set if we need to disable all HishSpeed - * instances in park mode. + * @enh_nak_fs_quirk: Set to schedule more handshakes to Async FS endpoints. + * @enh_nak_hs_quirk: Set to schedule more handshakes to Async HS endpoints. + * @parkmode_disable_ss_quirk: If set, disable park mode feature for all + * Superspeed instances. + * @parkmode_disable_hs_quirk: If set, disable park mode feature for all + * Highspeed instances. + * @parkmode_disable_fsls_quirk: If set, disable park mode feature for all + * Full/Lowspeed instances. * @gfladj_refclk_lpm_sel: set if we need to enable SOF/ITP counter * running based on ref_clk * @tx_de_emphasis_quirk: set if we enable Tx de-emphasis quirk @@ -1334,6 +1345,7 @@ struct dwc3 { u8 tx_max_burst_prd; u8 tx_fifo_resize_max_num; u8 clear_stall_protocol; + u8 axi_pipe_limit; const char *hsphy_interface; @@ -1375,8 +1387,11 @@ struct dwc3 { unsigned dis_tx_ipgap_linecheck_quirk:1; unsigned resume_hs_terminations:1; unsigned ulpi_ext_vbus_drv:1; + unsigned enh_nak_fs_quirk:1; + unsigned enh_nak_hs_quirk:1; unsigned parkmode_disable_ss_quirk:1; unsigned parkmode_disable_hs_quirk:1; + unsigned parkmode_disable_fsls_quirk:1; unsigned gfladj_refclk_lpm_sel:1; unsigned tx_de_emphasis_quirk:1; diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c index e0533cee6870ba..04cf82ab7eaf2e 100644 --- a/drivers/usb/dwc3/host.c +++ b/drivers/usb/dwc3/host.c @@ -126,10 +126,12 @@ static int dwc3_host_get_irq(struct dwc3 *dwc) int dwc3_host_init(struct dwc3 *dwc) { + struct platform_device *pdev = to_platform_device(dwc->dev); struct property_entry props[6]; struct platform_device *xhci; int ret, irq; int prop_idx = 0; + int id; /* * Some platforms need to power off all Root hub ports immediately after DWC3 set to host @@ -141,7 +143,12 @@ int dwc3_host_init(struct dwc3 *dwc) if (irq < 0) return irq; - xhci = platform_device_alloc("xhci-hcd", PLATFORM_DEVID_AUTO); + id = of_alias_get_id(pdev->dev.of_node, "usb"); + if (id >= 0) + xhci = platform_device_alloc("xhci-hcd", id); + else + xhci = platform_device_alloc("xhci-hcd", PLATFORM_DEVID_AUTO); + if (!xhci) { dev_err(dwc->dev, "couldn't allocate xHCI device\n"); return -ENOMEM; diff --git a/drivers/usb/gadget/file_storage.c b/drivers/usb/gadget/file_storage.c new file mode 100644 index 00000000000000..a896d73f7a9336 --- /dev/null +++ b/drivers/usb/gadget/file_storage.c @@ -0,0 +1,3676 @@ +/* + * file_storage.c -- File-backed USB Storage Gadget, for USB development + * + * Copyright (C) 2003-2008 Alan Stern + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The names of the above-listed copyright holders may not be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") as published by the Free Software + * Foundation, either version 2 of that License or (at your option) any + * later version. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +/* + * The File-backed Storage Gadget acts as a USB Mass Storage device, + * appearing to the host as a disk drive or as a CD-ROM drive. In addition + * to providing an example of a genuinely useful gadget driver for a USB + * device, it also illustrates a technique of double-buffering for increased + * throughput. Last but not least, it gives an easy way to probe the + * behavior of the Mass Storage drivers in a USB host. + * + * Backing storage is provided by a regular file or a block device, specified + * by the "file" module parameter. Access can be limited to read-only by + * setting the optional "ro" module parameter. (For CD-ROM emulation, + * access is always read-only.) The gadget will indicate that it has + * removable media if the optional "removable" module parameter is set. + * + * The gadget supports the Control-Bulk (CB), Control-Bulk-Interrupt (CBI), + * and Bulk-Only (also known as Bulk-Bulk-Bulk or BBB) transports, selected + * by the optional "transport" module parameter. It also supports the + * following protocols: RBC (0x01), ATAPI or SFF-8020i (0x02), QIC-157 (0c03), + * UFI (0x04), SFF-8070i (0x05), and transparent SCSI (0x06), selected by + * the optional "protocol" module parameter. In addition, the default + * Vendor ID, Product ID, release number and serial number can be overridden. + * + * There is support for multiple logical units (LUNs), each of which has + * its own backing file. The number of LUNs can be set using the optional + * "luns" module parameter (anywhere from 1 to 8), and the corresponding + * files are specified using comma-separated lists for "file" and "ro". + * The default number of LUNs is taken from the number of "file" elements; + * it is 1 if "file" is not given. If "removable" is not set then a backing + * file must be specified for each LUN. If it is set, then an unspecified + * or empty backing filename means the LUN's medium is not loaded. Ideally + * each LUN would be settable independently as a disk drive or a CD-ROM + * drive, but currently all LUNs have to be the same type. The CD-ROM + * emulation includes a single data track and no audio tracks; hence there + * need be only one backing file per LUN. + * + * Requirements are modest; only a bulk-in and a bulk-out endpoint are + * needed (an interrupt-out endpoint is also needed for CBI). The memory + * requirement amounts to two 16K buffers, size configurable by a parameter. + * Support is included for both full-speed and high-speed operation. + * + * Note that the driver is slightly non-portable in that it assumes a + * single memory/DMA buffer will be useable for bulk-in, bulk-out, and + * interrupt-in endpoints. With most device controllers this isn't an + * issue, but there may be some with hardware restrictions that prevent + * a buffer from being used by more than one endpoint. + * + * Module options: + * + * file=filename[,filename...] + * Required if "removable" is not set, names of + * the files or block devices used for + * backing storage + * serial=HHHH... Required serial number (string of hex chars) + * ro=b[,b...] Default false, booleans for read-only access + * removable Default false, boolean for removable media + * luns=N Default N = number of filenames, number of + * LUNs to support + * nofua=b[,b...] Default false, booleans for ignore FUA flag + * in SCSI WRITE(10,12) commands + * stall Default determined according to the type of + * USB device controller (usually true), + * boolean to permit the driver to halt + * bulk endpoints + * cdrom Default false, boolean for whether to emulate + * a CD-ROM drive + * transport=XXX Default BBB, transport name (CB, CBI, or BBB) + * protocol=YYY Default SCSI, protocol name (RBC, 8020 or + * ATAPI, QIC, UFI, 8070, or SCSI; + * also 1 - 6) + * vendor=0xVVVV Default 0x0525 (NetChip), USB Vendor ID + * product=0xPPPP Default 0xa4a5 (FSG), USB Product ID + * release=0xRRRR Override the USB release number (bcdDevice) + * buflen=N Default N=16384, buffer size used (will be + * rounded down to a multiple of + * PAGE_CACHE_SIZE) + * + * If CONFIG_USB_FILE_STORAGE_TEST is not set, only the "file", "serial", "ro", + * "removable", "luns", "nofua", "stall", and "cdrom" options are available; + * default values are used for everything else. + * + * The pathnames of the backing files and the ro settings are available in + * the attribute files "file", "nofua", and "ro" in the lun<n> subdirectory of + * the gadget's sysfs directory. If the "removable" option is set, writing to + * these files will simulate ejecting/loading the medium (writing an empty + * line means eject) and adjusting a write-enable tab. Changes to the ro + * setting are not allowed when the medium is loaded or if CD-ROM emulation + * is being used. + * + * This gadget driver is heavily based on "Gadget Zero" by David Brownell. + * The driver's SCSI command interface was based on the "Information + * technology - Small Computer System Interface - 2" document from + * X3T9.2 Project 375D, Revision 10L, 7-SEP-93, available at + * <http://www.t10.org/ftp/t10/drafts/s2/s2-r10l.pdf>. The single exception + * is opcode 0x23 (READ FORMAT CAPACITIES), which was based on the + * "Universal Serial Bus Mass Storage Class UFI Command Specification" + * document, Revision 1.0, December 14, 1998, available at + * <http://www.usb.org/developers/devclass_docs/usbmass-ufi10.pdf>. + */ + + +/* + * Driver Design + * + * The FSG driver is fairly straightforward. There is a main kernel + * thread that handles most of the work. Interrupt routines field + * callbacks from the controller driver: bulk- and interrupt-request + * completion notifications, endpoint-0 events, and disconnect events. + * Completion events are passed to the main thread by wakeup calls. Many + * ep0 requests are handled at interrupt time, but SetInterface, + * SetConfiguration, and device reset requests are forwarded to the + * thread in the form of "exceptions" using SIGUSR1 signals (since they + * should interrupt any ongoing file I/O operations). + * + * The thread's main routine implements the standard command/data/status + * parts of a SCSI interaction. It and its subroutines are full of tests + * for pending signals/exceptions -- all this polling is necessary since + * the kernel has no setjmp/longjmp equivalents. (Maybe this is an + * indication that the driver really wants to be running in userspace.) + * An important point is that so long as the thread is alive it keeps an + * open reference to the backing file. This will prevent unmounting + * the backing file's underlying filesystem and could cause problems + * during system shutdown, for example. To prevent such problems, the + * thread catches INT, TERM, and KILL signals and converts them into + * an EXIT exception. + * + * In normal operation the main thread is started during the gadget's + * fsg_bind() callback and stopped during fsg_unbind(). But it can also + * exit when it receives a signal, and there's no point leaving the + * gadget running when the thread is dead. So just before the thread + * exits, it deregisters the gadget driver. This makes things a little + * tricky: The driver is deregistered at two places, and the exiting + * thread can indirectly call fsg_unbind() which in turn can tell the + * thread to exit. The first problem is resolved through the use of the + * REGISTERED atomic bitflag; the driver will only be deregistered once. + * The second problem is resolved by having fsg_unbind() check + * fsg->state; it won't try to stop the thread if the state is already + * FSG_STATE_TERMINATED. + * + * To provide maximum throughput, the driver uses a circular pipeline of + * buffer heads (struct fsg_buffhd). In principle the pipeline can be + * arbitrarily long; in practice the benefits don't justify having more + * than 2 stages (i.e., double buffering). But it helps to think of the + * pipeline as being a long one. Each buffer head contains a bulk-in and + * a bulk-out request pointer (since the buffer can be used for both + * output and input -- directions always are given from the host's + * point of view) as well as a pointer to the buffer and various state + * variables. + * + * Use of the pipeline follows a simple protocol. There is a variable + * (fsg->next_buffhd_to_fill) that points to the next buffer head to use. + * At any time that buffer head may still be in use from an earlier + * request, so each buffer head has a state variable indicating whether + * it is EMPTY, FULL, or BUSY. Typical use involves waiting for the + * buffer head to be EMPTY, filling the buffer either by file I/O or by + * USB I/O (during which the buffer head is BUSY), and marking the buffer + * head FULL when the I/O is complete. Then the buffer will be emptied + * (again possibly by USB I/O, during which it is marked BUSY) and + * finally marked EMPTY again (possibly by a completion routine). + * + * A module parameter tells the driver to avoid stalling the bulk + * endpoints wherever the transport specification allows. This is + * necessary for some UDCs like the SuperH, which cannot reliably clear a + * halt on a bulk endpoint. However, under certain circumstances the + * Bulk-only specification requires a stall. In such cases the driver + * will halt the endpoint and set a flag indicating that it should clear + * the halt in software during the next device reset. Hopefully this + * will permit everything to work correctly. Furthermore, although the + * specification allows the bulk-out endpoint to halt when the host sends + * too much data, implementing this would cause an unavoidable race. + * The driver will always use the "no-stall" approach for OUT transfers. + * + * One subtle point concerns sending status-stage responses for ep0 + * requests. Some of these requests, such as device reset, can involve + * interrupting an ongoing file I/O operation, which might take an + * arbitrarily long time. During that delay the host might give up on + * the original ep0 request and issue a new one. When that happens the + * driver should not notify the host about completion of the original + * request, as the host will no longer be waiting for it. So the driver + * assigns to each ep0 request a unique tag, and it keeps track of the + * tag value of the request associated with a long-running exception + * (device-reset, interface-change, or configuration-change). When the + * exception handler is finished, the status-stage response is submitted + * only if the current ep0 request tag is equal to the exception request + * tag. Thus only the most recently received ep0 request will get a + * status-stage response. + * + * Warning: This driver source file is too long. It ought to be split up + * into a header file plus about 3 separate .c files, to handle the details + * of the Gadget, USB Mass Storage, and SCSI protocols. + */ + + +/* #define VERBOSE_DEBUG */ +/* #define DUMP_MSGS */ + + +#include <linux/blkdev.h> +#include <linux/completion.h> +#include <linux/dcache.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/fcntl.h> +#include <linux/file.h> +#include <linux/fs.h> +#include <linux/kref.h> +#include <linux/kthread.h> +#include <linux/limits.h> +#include <linux/module.h> +#include <linux/rwsem.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/string.h> +#include <linux/freezer.h> +#include <linux/utsname.h> + +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> + +#include "gadget_chips.h" + + + +/* + * Kbuild is not very cooperative with respect to linking separately + * compiled library objects into one module. So for now we won't use + * separate compilation ... ensuring init/exit sections work to shrink + * the runtime footprint, and giving us at least some parts of what + * a "gcc --combine ... part1.c part2.c part3.c ... " build would. + */ +#include "usbstring.c" +#include "config.c" +#include "epautoconf.c" + +/*-------------------------------------------------------------------------*/ + +#define DRIVER_DESC "File-backed Storage Gadget" +#define DRIVER_NAME "g_file_storage" +#define DRIVER_VERSION "1 September 2010" + +static char fsg_string_manufacturer[64]; +static const char fsg_string_product[] = DRIVER_DESC; +static const char fsg_string_config[] = "Self-powered"; +static const char fsg_string_interface[] = "Mass Storage"; + + +#include "storage_common.c" + + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Alan Stern"); +MODULE_LICENSE("Dual BSD/GPL"); + +/* + * This driver assumes self-powered hardware and has no way for users to + * trigger remote wakeup. It uses autoconfiguration to select endpoints + * and endpoint addresses. + */ + + +/*-------------------------------------------------------------------------*/ + + +/* Encapsulate the module parameter settings */ + +static struct { + char *file[FSG_MAX_LUNS]; + char *serial; + bool ro[FSG_MAX_LUNS]; + bool nofua[FSG_MAX_LUNS]; + unsigned int num_filenames; + unsigned int num_ros; + unsigned int num_nofuas; + unsigned int nluns; + + bool removable; + bool can_stall; + bool cdrom; + + char *transport_parm; + char *protocol_parm; + unsigned short vendor; + unsigned short product; + unsigned short release; + unsigned int buflen; + + int transport_type; + char *transport_name; + int protocol_type; + char *protocol_name; + +} mod_data = { // Default values + .transport_parm = "BBB", + .protocol_parm = "SCSI", + .removable = 0, + .can_stall = 1, + .cdrom = 0, + .vendor = FSG_VENDOR_ID, + .product = FSG_PRODUCT_ID, + .release = 0xffff, // Use controller chip type + .buflen = 16384, + }; + + +module_param_array_named(file, mod_data.file, charp, &mod_data.num_filenames, + S_IRUGO); +MODULE_PARM_DESC(file, "names of backing files or devices"); + +module_param_named(serial, mod_data.serial, charp, S_IRUGO); +MODULE_PARM_DESC(serial, "USB serial number"); + +module_param_array_named(ro, mod_data.ro, bool, &mod_data.num_ros, S_IRUGO); +MODULE_PARM_DESC(ro, "true to force read-only"); + +module_param_array_named(nofua, mod_data.nofua, bool, &mod_data.num_nofuas, + S_IRUGO); +MODULE_PARM_DESC(nofua, "true to ignore SCSI WRITE(10,12) FUA bit"); + +module_param_named(luns, mod_data.nluns, uint, S_IRUGO); +MODULE_PARM_DESC(luns, "number of LUNs"); + +module_param_named(removable, mod_data.removable, bool, S_IRUGO); +MODULE_PARM_DESC(removable, "true to simulate removable media"); + +module_param_named(stall, mod_data.can_stall, bool, S_IRUGO); +MODULE_PARM_DESC(stall, "false to prevent bulk stalls"); + +module_param_named(cdrom, mod_data.cdrom, bool, S_IRUGO); +MODULE_PARM_DESC(cdrom, "true to emulate cdrom instead of disk"); + +/* In the non-TEST version, only the module parameters listed above + * are available. */ +#ifdef CONFIG_USB_FILE_STORAGE_TEST + +module_param_named(transport, mod_data.transport_parm, charp, S_IRUGO); +MODULE_PARM_DESC(transport, "type of transport (BBB, CBI, or CB)"); + +module_param_named(protocol, mod_data.protocol_parm, charp, S_IRUGO); +MODULE_PARM_DESC(protocol, "type of protocol (RBC, 8020, QIC, UFI, " + "8070, or SCSI)"); + +module_param_named(vendor, mod_data.vendor, ushort, S_IRUGO); +MODULE_PARM_DESC(vendor, "USB Vendor ID"); + +module_param_named(product, mod_data.product, ushort, S_IRUGO); +MODULE_PARM_DESC(product, "USB Product ID"); + +module_param_named(release, mod_data.release, ushort, S_IRUGO); +MODULE_PARM_DESC(release, "USB release number"); + +module_param_named(buflen, mod_data.buflen, uint, S_IRUGO); +MODULE_PARM_DESC(buflen, "I/O buffer size"); + +#endif /* CONFIG_USB_FILE_STORAGE_TEST */ + + +/* + * These definitions will permit the compiler to avoid generating code for + * parts of the driver that aren't used in the non-TEST version. Even gcc + * can recognize when a test of a constant expression yields a dead code + * path. + */ + +#ifdef CONFIG_USB_FILE_STORAGE_TEST + +#define transport_is_bbb() (mod_data.transport_type == USB_PR_BULK) +#define transport_is_cbi() (mod_data.transport_type == USB_PR_CBI) +#define protocol_is_scsi() (mod_data.protocol_type == USB_SC_SCSI) + +#else + +#define transport_is_bbb() 1 +#define transport_is_cbi() 0 +#define protocol_is_scsi() 1 + +#endif /* CONFIG_USB_FILE_STORAGE_TEST */ + + +/*-------------------------------------------------------------------------*/ + + +struct fsg_dev { + /* lock protects: state, all the req_busy's, and cbbuf_cmnd */ + spinlock_t lock; + struct usb_gadget *gadget; + + /* filesem protects: backing files in use */ + struct rw_semaphore filesem; + + /* reference counting: wait until all LUNs are released */ + struct kref ref; + + struct usb_ep *ep0; // Handy copy of gadget->ep0 + struct usb_request *ep0req; // For control responses + unsigned int ep0_req_tag; + const char *ep0req_name; + + struct usb_request *intreq; // For interrupt responses + int intreq_busy; + struct fsg_buffhd *intr_buffhd; + + unsigned int bulk_out_maxpacket; + enum fsg_state state; // For exception handling + unsigned int exception_req_tag; + + u8 config, new_config; + + unsigned int running : 1; + unsigned int bulk_in_enabled : 1; + unsigned int bulk_out_enabled : 1; + unsigned int intr_in_enabled : 1; + unsigned int phase_error : 1; + unsigned int short_packet_received : 1; + unsigned int bad_lun_okay : 1; + + unsigned long atomic_bitflags; +#define REGISTERED 0 +#define IGNORE_BULK_OUT 1 +#define SUSPENDED 2 + + struct usb_ep *bulk_in; + struct usb_ep *bulk_out; + struct usb_ep *intr_in; + + struct fsg_buffhd *next_buffhd_to_fill; + struct fsg_buffhd *next_buffhd_to_drain; + + int thread_wakeup_needed; + struct completion thread_notifier; + struct task_struct *thread_task; + + int cmnd_size; + u8 cmnd[MAX_COMMAND_SIZE]; + enum data_direction data_dir; + u32 data_size; + u32 data_size_from_cmnd; + u32 tag; + unsigned int lun; + u32 residue; + u32 usb_amount_left; + + /* The CB protocol offers no way for a host to know when a command + * has completed. As a result the next command may arrive early, + * and we will still have to handle it. For that reason we need + * a buffer to store new commands when using CB (or CBI, which + * does not oblige a host to wait for command completion either). */ + int cbbuf_cmnd_size; + u8 cbbuf_cmnd[MAX_COMMAND_SIZE]; + + unsigned int nluns; + struct fsg_lun *luns; + struct fsg_lun *curlun; + /* Must be the last entry */ + struct fsg_buffhd buffhds[]; +}; + +typedef void (*fsg_routine_t)(struct fsg_dev *); + +static int exception_in_progress(struct fsg_dev *fsg) +{ + return (fsg->state > FSG_STATE_IDLE); +} + +/* Make bulk-out requests be divisible by the maxpacket size */ +static void set_bulk_out_req_length(struct fsg_dev *fsg, + struct fsg_buffhd *bh, unsigned int length) +{ + unsigned int rem; + + bh->bulk_out_intended_length = length; + rem = length % fsg->bulk_out_maxpacket; + if (rem > 0) + length += fsg->bulk_out_maxpacket - rem; + bh->outreq->length = length; +} + +static struct fsg_dev *the_fsg; +static struct usb_gadget_driver fsg_driver; + + +/*-------------------------------------------------------------------------*/ + +static int fsg_set_halt(struct fsg_dev *fsg, struct usb_ep *ep) +{ + const char *name; + + if (ep == fsg->bulk_in) + name = "bulk-in"; + else if (ep == fsg->bulk_out) + name = "bulk-out"; + else + name = ep->name; + DBG(fsg, "%s set halt\n", name); + return usb_ep_set_halt(ep); +} + + +/*-------------------------------------------------------------------------*/ + +/* + * DESCRIPTORS ... most are static, but strings and (full) configuration + * descriptors are built on demand. Also the (static) config and interface + * descriptors are adjusted during fsg_bind(). + */ + +/* There is only one configuration. */ +#define CONFIG_VALUE 1 + +static struct usb_device_descriptor +device_desc = { + .bLength = sizeof device_desc, + .bDescriptorType = USB_DT_DEVICE, + + .bcdUSB = cpu_to_le16(0x0200), + .bDeviceClass = USB_CLASS_PER_INTERFACE, + + /* The next three values can be overridden by module parameters */ + .idVendor = cpu_to_le16(FSG_VENDOR_ID), + .idProduct = cpu_to_le16(FSG_PRODUCT_ID), + .bcdDevice = cpu_to_le16(0xffff), + + .iManufacturer = FSG_STRING_MANUFACTURER, + .iProduct = FSG_STRING_PRODUCT, + .iSerialNumber = FSG_STRING_SERIAL, + .bNumConfigurations = 1, +}; + +static struct usb_config_descriptor +config_desc = { + .bLength = sizeof config_desc, + .bDescriptorType = USB_DT_CONFIG, + + /* wTotalLength computed by usb_gadget_config_buf() */ + .bNumInterfaces = 1, + .bConfigurationValue = CONFIG_VALUE, + .iConfiguration = FSG_STRING_CONFIG, + .bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER, + .bMaxPower = CONFIG_USB_GADGET_VBUS_DRAW / 2, +}; + + +static struct usb_qualifier_descriptor +dev_qualifier = { + .bLength = sizeof dev_qualifier, + .bDescriptorType = USB_DT_DEVICE_QUALIFIER, + + .bcdUSB = cpu_to_le16(0x0200), + .bDeviceClass = USB_CLASS_PER_INTERFACE, + + .bNumConfigurations = 1, +}; + +static int populate_bos(struct fsg_dev *fsg, u8 *buf) +{ + memcpy(buf, &fsg_bos_desc, USB_DT_BOS_SIZE); + buf += USB_DT_BOS_SIZE; + + memcpy(buf, &fsg_ext_cap_desc, USB_DT_USB_EXT_CAP_SIZE); + buf += USB_DT_USB_EXT_CAP_SIZE; + + memcpy(buf, &fsg_ss_cap_desc, USB_DT_USB_SS_CAP_SIZE); + + return USB_DT_BOS_SIZE + USB_DT_USB_SS_CAP_SIZE + + USB_DT_USB_EXT_CAP_SIZE; +} + +/* + * Config descriptors must agree with the code that sets configurations + * and with code managing interfaces and their altsettings. They must + * also handle different speeds and other-speed requests. + */ +static int populate_config_buf(struct usb_gadget *gadget, + u8 *buf, u8 type, unsigned index) +{ + enum usb_device_speed speed = gadget->speed; + int len; + const struct usb_descriptor_header **function; + + if (index > 0) + return -EINVAL; + + if (gadget_is_dualspeed(gadget) && type == USB_DT_OTHER_SPEED_CONFIG) + speed = (USB_SPEED_FULL + USB_SPEED_HIGH) - speed; + function = gadget_is_dualspeed(gadget) && speed == USB_SPEED_HIGH + ? (const struct usb_descriptor_header **)fsg_hs_function + : (const struct usb_descriptor_header **)fsg_fs_function; + + /* for now, don't advertise srp-only devices */ + if (!gadget_is_otg(gadget)) + function++; + + len = usb_gadget_config_buf(&config_desc, buf, EP0_BUFSIZE, function); + ((struct usb_config_descriptor *) buf)->bDescriptorType = type; + return len; +} + + +/*-------------------------------------------------------------------------*/ + +/* These routines may be called in process context or in_irq */ + +/* Caller must hold fsg->lock */ +static void wakeup_thread(struct fsg_dev *fsg) +{ + /* Tell the main thread that something has happened */ + fsg->thread_wakeup_needed = 1; + if (fsg->thread_task) + wake_up_process(fsg->thread_task); +} + + +static void raise_exception(struct fsg_dev *fsg, enum fsg_state new_state) +{ + unsigned long flags; + + /* Do nothing if a higher-priority exception is already in progress. + * If a lower-or-equal priority exception is in progress, preempt it + * and notify the main thread by sending it a signal. */ + spin_lock_irqsave(&fsg->lock, flags); + if (fsg->state <= new_state) { + fsg->exception_req_tag = fsg->ep0_req_tag; + fsg->state = new_state; + if (fsg->thread_task) + send_sig_info(SIGUSR1, SEND_SIG_FORCED, + fsg->thread_task); + } + spin_unlock_irqrestore(&fsg->lock, flags); +} + + +/*-------------------------------------------------------------------------*/ + +/* The disconnect callback and ep0 routines. These always run in_irq, + * except that ep0_queue() is called in the main thread to acknowledge + * completion of various requests: set config, set interface, and + * Bulk-only device reset. */ + +static void fsg_disconnect(struct usb_gadget *gadget) +{ + struct fsg_dev *fsg = get_gadget_data(gadget); + + DBG(fsg, "disconnect or port reset\n"); + raise_exception(fsg, FSG_STATE_DISCONNECT); +} + + +static int ep0_queue(struct fsg_dev *fsg) +{ + int rc; + + rc = usb_ep_queue(fsg->ep0, fsg->ep0req, GFP_ATOMIC); + if (rc != 0 && rc != -ESHUTDOWN) { + + /* We can't do much more than wait for a reset */ + WARNING(fsg, "error in submission: %s --> %d\n", + fsg->ep0->name, rc); + } + return rc; +} + +static void ep0_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct fsg_dev *fsg = ep->driver_data; + + if (req->actual > 0) + dump_msg(fsg, fsg->ep0req_name, req->buf, req->actual); + if (req->status || req->actual != req->length) + DBG(fsg, "%s --> %d, %u/%u\n", __func__, + req->status, req->actual, req->length); + if (req->status == -ECONNRESET) // Request was cancelled + usb_ep_fifo_flush(ep); + + if (req->status == 0 && req->context) + ((fsg_routine_t) (req->context))(fsg); +} + + +/*-------------------------------------------------------------------------*/ + +/* Bulk and interrupt endpoint completion handlers. + * These always run in_irq. */ + +static void bulk_in_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct fsg_dev *fsg = ep->driver_data; + struct fsg_buffhd *bh = req->context; + + if (req->status || req->actual != req->length) + DBG(fsg, "%s --> %d, %u/%u\n", __func__, + req->status, req->actual, req->length); + if (req->status == -ECONNRESET) // Request was cancelled + usb_ep_fifo_flush(ep); + + /* Hold the lock while we update the request and buffer states */ + smp_wmb(); + spin_lock(&fsg->lock); + bh->inreq_busy = 0; + bh->state = BUF_STATE_EMPTY; + wakeup_thread(fsg); + spin_unlock(&fsg->lock); +} + +static void bulk_out_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct fsg_dev *fsg = ep->driver_data; + struct fsg_buffhd *bh = req->context; + + dump_msg(fsg, "bulk-out", req->buf, req->actual); + if (req->status || req->actual != bh->bulk_out_intended_length) + DBG(fsg, "%s --> %d, %u/%u\n", __func__, + req->status, req->actual, + bh->bulk_out_intended_length); + if (req->status == -ECONNRESET) // Request was cancelled + usb_ep_fifo_flush(ep); + + /* Hold the lock while we update the request and buffer states */ + smp_wmb(); + spin_lock(&fsg->lock); + bh->outreq_busy = 0; + bh->state = BUF_STATE_FULL; + wakeup_thread(fsg); + spin_unlock(&fsg->lock); +} + + +#ifdef CONFIG_USB_FILE_STORAGE_TEST +static void intr_in_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct fsg_dev *fsg = ep->driver_data; + struct fsg_buffhd *bh = req->context; + + if (req->status || req->actual != req->length) + DBG(fsg, "%s --> %d, %u/%u\n", __func__, + req->status, req->actual, req->length); + if (req->status == -ECONNRESET) // Request was cancelled + usb_ep_fifo_flush(ep); + + /* Hold the lock while we update the request and buffer states */ + smp_wmb(); + spin_lock(&fsg->lock); + fsg->intreq_busy = 0; + bh->state = BUF_STATE_EMPTY; + wakeup_thread(fsg); + spin_unlock(&fsg->lock); +} + +#else +static void intr_in_complete(struct usb_ep *ep, struct usb_request *req) +{} +#endif /* CONFIG_USB_FILE_STORAGE_TEST */ + + +/*-------------------------------------------------------------------------*/ + +/* Ep0 class-specific handlers. These always run in_irq. */ + +#ifdef CONFIG_USB_FILE_STORAGE_TEST +static void received_cbi_adsc(struct fsg_dev *fsg, struct fsg_buffhd *bh) +{ + struct usb_request *req = fsg->ep0req; + static u8 cbi_reset_cmnd[6] = { + SEND_DIAGNOSTIC, 4, 0xff, 0xff, 0xff, 0xff}; + + /* Error in command transfer? */ + if (req->status || req->length != req->actual || + req->actual < 6 || req->actual > MAX_COMMAND_SIZE) { + + /* Not all controllers allow a protocol stall after + * receiving control-out data, but we'll try anyway. */ + fsg_set_halt(fsg, fsg->ep0); + return; // Wait for reset + } + + /* Is it the special reset command? */ + if (req->actual >= sizeof cbi_reset_cmnd && + memcmp(req->buf, cbi_reset_cmnd, + sizeof cbi_reset_cmnd) == 0) { + + /* Raise an exception to stop the current operation + * and reinitialize our state. */ + DBG(fsg, "cbi reset request\n"); + raise_exception(fsg, FSG_STATE_RESET); + return; + } + + VDBG(fsg, "CB[I] accept device-specific command\n"); + spin_lock(&fsg->lock); + + /* Save the command for later */ + if (fsg->cbbuf_cmnd_size) + WARNING(fsg, "CB[I] overwriting previous command\n"); + fsg->cbbuf_cmnd_size = req->actual; + memcpy(fsg->cbbuf_cmnd, req->buf, fsg->cbbuf_cmnd_size); + + wakeup_thread(fsg); + spin_unlock(&fsg->lock); +} + +#else +static void received_cbi_adsc(struct fsg_dev *fsg, struct fsg_buffhd *bh) +{} +#endif /* CONFIG_USB_FILE_STORAGE_TEST */ + + +static int class_setup_req(struct fsg_dev *fsg, + const struct usb_ctrlrequest *ctrl) +{ + struct usb_request *req = fsg->ep0req; + int value = -EOPNOTSUPP; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + + if (!fsg->config) + return value; + + /* Handle Bulk-only class-specific requests */ + if (transport_is_bbb()) { + switch (ctrl->bRequest) { + + case US_BULK_RESET_REQUEST: + if (ctrl->bRequestType != (USB_DIR_OUT | + USB_TYPE_CLASS | USB_RECIP_INTERFACE)) + break; + if (w_index != 0 || w_value != 0 || w_length != 0) { + value = -EDOM; + break; + } + + /* Raise an exception to stop the current operation + * and reinitialize our state. */ + DBG(fsg, "bulk reset request\n"); + raise_exception(fsg, FSG_STATE_RESET); + value = DELAYED_STATUS; + break; + + case US_BULK_GET_MAX_LUN: + if (ctrl->bRequestType != (USB_DIR_IN | + USB_TYPE_CLASS | USB_RECIP_INTERFACE)) + break; + if (w_index != 0 || w_value != 0 || w_length != 1) { + value = -EDOM; + break; + } + VDBG(fsg, "get max LUN\n"); + *(u8 *) req->buf = fsg->nluns - 1; + value = 1; + break; + } + } + + /* Handle CBI class-specific requests */ + else { + switch (ctrl->bRequest) { + + case USB_CBI_ADSC_REQUEST: + if (ctrl->bRequestType != (USB_DIR_OUT | + USB_TYPE_CLASS | USB_RECIP_INTERFACE)) + break; + if (w_index != 0 || w_value != 0) { + value = -EDOM; + break; + } + if (w_length > MAX_COMMAND_SIZE) { + value = -EOVERFLOW; + break; + } + value = w_length; + fsg->ep0req->context = received_cbi_adsc; + break; + } + } + + if (value == -EOPNOTSUPP) + VDBG(fsg, + "unknown class-specific control req " + "%02x.%02x v%04x i%04x l%u\n", + ctrl->bRequestType, ctrl->bRequest, + le16_to_cpu(ctrl->wValue), w_index, w_length); + return value; +} + + +/*-------------------------------------------------------------------------*/ + +/* Ep0 standard request handlers. These always run in_irq. */ + +static int standard_setup_req(struct fsg_dev *fsg, + const struct usb_ctrlrequest *ctrl) +{ + struct usb_request *req = fsg->ep0req; + int value = -EOPNOTSUPP; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + + /* Usually this just stores reply data in the pre-allocated ep0 buffer, + * but config change events will also reconfigure hardware. */ + switch (ctrl->bRequest) { + + case USB_REQ_GET_DESCRIPTOR: + if (ctrl->bRequestType != (USB_DIR_IN | USB_TYPE_STANDARD | + USB_RECIP_DEVICE)) + break; + switch (w_value >> 8) { + + case USB_DT_DEVICE: + VDBG(fsg, "get device descriptor\n"); + device_desc.bMaxPacketSize0 = fsg->ep0->maxpacket; + value = sizeof device_desc; + memcpy(req->buf, &device_desc, value); + break; + case USB_DT_DEVICE_QUALIFIER: + VDBG(fsg, "get device qualifier\n"); + if (!gadget_is_dualspeed(fsg->gadget) || + fsg->gadget->speed == USB_SPEED_SUPER) + break; + /* + * Assume ep0 uses the same maxpacket value for both + * speeds + */ + dev_qualifier.bMaxPacketSize0 = fsg->ep0->maxpacket; + value = sizeof dev_qualifier; + memcpy(req->buf, &dev_qualifier, value); + break; + + case USB_DT_OTHER_SPEED_CONFIG: + VDBG(fsg, "get other-speed config descriptor\n"); + if (!gadget_is_dualspeed(fsg->gadget) || + fsg->gadget->speed == USB_SPEED_SUPER) + break; + goto get_config; + case USB_DT_CONFIG: + VDBG(fsg, "get configuration descriptor\n"); +get_config: + value = populate_config_buf(fsg->gadget, + req->buf, + w_value >> 8, + w_value & 0xff); + break; + + case USB_DT_STRING: + VDBG(fsg, "get string descriptor\n"); + + /* wIndex == language code */ + value = usb_gadget_get_string(&fsg_stringtab, + w_value & 0xff, req->buf); + break; + + case USB_DT_BOS: + VDBG(fsg, "get bos descriptor\n"); + + if (gadget_is_superspeed(fsg->gadget)) + value = populate_bos(fsg, req->buf); + break; + } + + break; + + /* One config, two speeds */ + case USB_REQ_SET_CONFIGURATION: + if (ctrl->bRequestType != (USB_DIR_OUT | USB_TYPE_STANDARD | + USB_RECIP_DEVICE)) + break; + VDBG(fsg, "set configuration\n"); + if (w_value == CONFIG_VALUE || w_value == 0) { + fsg->new_config = w_value; + + /* Raise an exception to wipe out previous transaction + * state (queued bufs, etc) and set the new config. */ + raise_exception(fsg, FSG_STATE_CONFIG_CHANGE); + value = DELAYED_STATUS; + } + break; + case USB_REQ_GET_CONFIGURATION: + if (ctrl->bRequestType != (USB_DIR_IN | USB_TYPE_STANDARD | + USB_RECIP_DEVICE)) + break; + VDBG(fsg, "get configuration\n"); + *(u8 *) req->buf = fsg->config; + value = 1; + break; + + case USB_REQ_SET_INTERFACE: + if (ctrl->bRequestType != (USB_DIR_OUT| USB_TYPE_STANDARD | + USB_RECIP_INTERFACE)) + break; + if (fsg->config && w_index == 0) { + + /* Raise an exception to wipe out previous transaction + * state (queued bufs, etc) and install the new + * interface altsetting. */ + raise_exception(fsg, FSG_STATE_INTERFACE_CHANGE); + value = DELAYED_STATUS; + } + break; + case USB_REQ_GET_INTERFACE: + if (ctrl->bRequestType != (USB_DIR_IN | USB_TYPE_STANDARD | + USB_RECIP_INTERFACE)) + break; + if (!fsg->config) + break; + if (w_index != 0) { + value = -EDOM; + break; + } + VDBG(fsg, "get interface\n"); + *(u8 *) req->buf = 0; + value = 1; + break; + + default: + VDBG(fsg, + "unknown control req %02x.%02x v%04x i%04x l%u\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, le16_to_cpu(ctrl->wLength)); + } + + return value; +} + + +static int fsg_setup(struct usb_gadget *gadget, + const struct usb_ctrlrequest *ctrl) +{ + struct fsg_dev *fsg = get_gadget_data(gadget); + int rc; + int w_length = le16_to_cpu(ctrl->wLength); + + ++fsg->ep0_req_tag; // Record arrival of a new request + fsg->ep0req->context = NULL; + fsg->ep0req->length = 0; + dump_msg(fsg, "ep0-setup", (u8 *) ctrl, sizeof(*ctrl)); + + if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_CLASS) + rc = class_setup_req(fsg, ctrl); + else + rc = standard_setup_req(fsg, ctrl); + + /* Respond with data/status or defer until later? */ + if (rc >= 0 && rc != DELAYED_STATUS) { + rc = min(rc, w_length); + fsg->ep0req->length = rc; + fsg->ep0req->zero = rc < w_length; + fsg->ep0req_name = (ctrl->bRequestType & USB_DIR_IN ? + "ep0-in" : "ep0-out"); + rc = ep0_queue(fsg); + } + + /* Device either stalls (rc < 0) or reports success */ + return rc; +} + + +/*-------------------------------------------------------------------------*/ + +/* All the following routines run in process context */ + + +/* Use this for bulk or interrupt transfers, not ep0 */ +static void start_transfer(struct fsg_dev *fsg, struct usb_ep *ep, + struct usb_request *req, int *pbusy, + enum fsg_buffer_state *state) +{ + int rc; + + if (ep == fsg->bulk_in) + dump_msg(fsg, "bulk-in", req->buf, req->length); + else if (ep == fsg->intr_in) + dump_msg(fsg, "intr-in", req->buf, req->length); + + spin_lock_irq(&fsg->lock); + *pbusy = 1; + *state = BUF_STATE_BUSY; + spin_unlock_irq(&fsg->lock); + rc = usb_ep_queue(ep, req, GFP_KERNEL); + if (rc != 0) { + *pbusy = 0; + *state = BUF_STATE_EMPTY; + + /* We can't do much more than wait for a reset */ + + /* Note: currently the net2280 driver fails zero-length + * submissions if DMA is enabled. */ + if (rc != -ESHUTDOWN && !(rc == -EOPNOTSUPP && + req->length == 0)) + WARNING(fsg, "error in submission: %s --> %d\n", + ep->name, rc); + } +} + + +static int sleep_thread(struct fsg_dev *fsg) +{ + int rc = 0; + + /* Wait until a signal arrives or we are woken up */ + for (;;) { + try_to_freeze(); + set_current_state(TASK_INTERRUPTIBLE); + if (signal_pending(current)) { + rc = -EINTR; + break; + } + if (fsg->thread_wakeup_needed) + break; + schedule(); + } + __set_current_state(TASK_RUNNING); + fsg->thread_wakeup_needed = 0; + return rc; +} + + +/*-------------------------------------------------------------------------*/ + +static int do_read(struct fsg_dev *fsg) +{ + struct fsg_lun *curlun = fsg->curlun; + u32 lba; + struct fsg_buffhd *bh; + int rc; + u32 amount_left; + loff_t file_offset, file_offset_tmp; + unsigned int amount; + ssize_t nread; + + /* Get the starting Logical Block Address and check that it's + * not too big */ + if (fsg->cmnd[0] == READ_6) + lba = get_unaligned_be24(&fsg->cmnd[1]); + else { + lba = get_unaligned_be32(&fsg->cmnd[2]); + + /* We allow DPO (Disable Page Out = don't save data in the + * cache) and FUA (Force Unit Access = don't read from the + * cache), but we don't implement them. */ + if ((fsg->cmnd[1] & ~0x18) != 0) { + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + } + if (lba >= curlun->num_sectors) { + curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + return -EINVAL; + } + file_offset = ((loff_t) lba) << curlun->blkbits; + + /* Carry out the file reads */ + amount_left = fsg->data_size_from_cmnd; + if (unlikely(amount_left == 0)) + return -EIO; // No default reply + + for (;;) { + + /* Figure out how much we need to read: + * Try to read the remaining amount. + * But don't read more than the buffer size. + * And don't try to read past the end of the file. + */ + amount = min((unsigned int) amount_left, mod_data.buflen); + amount = min((loff_t) amount, + curlun->file_length - file_offset); + + /* Wait for the next buffer to become available */ + bh = fsg->next_buffhd_to_fill; + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(fsg); + if (rc) + return rc; + } + + /* If we were asked to read past the end of file, + * end with an empty buffer. */ + if (amount == 0) { + curlun->sense_data = + SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + curlun->sense_data_info = file_offset >> curlun->blkbits; + curlun->info_valid = 1; + bh->inreq->length = 0; + bh->state = BUF_STATE_FULL; + break; + } + + /* Perform the read */ + file_offset_tmp = file_offset; + nread = vfs_read(curlun->filp, + (char __user *) bh->buf, + amount, &file_offset_tmp); + VLDBG(curlun, "file read %u @ %llu -> %d\n", amount, + (unsigned long long) file_offset, + (int) nread); + if (signal_pending(current)) + return -EINTR; + + if (nread < 0) { + LDBG(curlun, "error in file read: %d\n", + (int) nread); + nread = 0; + } else if (nread < amount) { + LDBG(curlun, "partial file read: %d/%u\n", + (int) nread, amount); + nread = round_down(nread, curlun->blksize); + } + file_offset += nread; + amount_left -= nread; + fsg->residue -= nread; + + /* Except at the end of the transfer, nread will be + * equal to the buffer size, which is divisible by the + * bulk-in maxpacket size. + */ + bh->inreq->length = nread; + bh->state = BUF_STATE_FULL; + + /* If an error occurred, report it and its position */ + if (nread < amount) { + curlun->sense_data = SS_UNRECOVERED_READ_ERROR; + curlun->sense_data_info = file_offset >> curlun->blkbits; + curlun->info_valid = 1; + break; + } + + if (amount_left == 0) + break; // No more left to read + + /* Send this buffer and go read some more */ + bh->inreq->zero = 0; + start_transfer(fsg, fsg->bulk_in, bh->inreq, + &bh->inreq_busy, &bh->state); + fsg->next_buffhd_to_fill = bh->next; + } + + return -EIO; // No default reply +} + + +/*-------------------------------------------------------------------------*/ + +static int do_write(struct fsg_dev *fsg) +{ + struct fsg_lun *curlun = fsg->curlun; + u32 lba; + struct fsg_buffhd *bh; + int get_some_more; + u32 amount_left_to_req, amount_left_to_write; + loff_t usb_offset, file_offset, file_offset_tmp; + unsigned int amount; + ssize_t nwritten; + int rc; + + if (curlun->ro) { + curlun->sense_data = SS_WRITE_PROTECTED; + return -EINVAL; + } + spin_lock(&curlun->filp->f_lock); + curlun->filp->f_flags &= ~O_SYNC; // Default is not to wait + spin_unlock(&curlun->filp->f_lock); + + /* Get the starting Logical Block Address and check that it's + * not too big */ + if (fsg->cmnd[0] == WRITE_6) + lba = get_unaligned_be24(&fsg->cmnd[1]); + else { + lba = get_unaligned_be32(&fsg->cmnd[2]); + + /* We allow DPO (Disable Page Out = don't save data in the + * cache) and FUA (Force Unit Access = write directly to the + * medium). We don't implement DPO; we implement FUA by + * performing synchronous output. */ + if ((fsg->cmnd[1] & ~0x18) != 0) { + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + /* FUA */ + if (!curlun->nofua && (fsg->cmnd[1] & 0x08)) { + spin_lock(&curlun->filp->f_lock); + curlun->filp->f_flags |= O_DSYNC; + spin_unlock(&curlun->filp->f_lock); + } + } + if (lba >= curlun->num_sectors) { + curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + return -EINVAL; + } + + /* Carry out the file writes */ + get_some_more = 1; + file_offset = usb_offset = ((loff_t) lba) << curlun->blkbits; + amount_left_to_req = amount_left_to_write = fsg->data_size_from_cmnd; + + while (amount_left_to_write > 0) { + + /* Queue a request for more data from the host */ + bh = fsg->next_buffhd_to_fill; + if (bh->state == BUF_STATE_EMPTY && get_some_more) { + + /* Figure out how much we want to get: + * Try to get the remaining amount, + * but not more than the buffer size. + */ + amount = min(amount_left_to_req, mod_data.buflen); + + /* Beyond the end of the backing file? */ + if (usb_offset >= curlun->file_length) { + get_some_more = 0; + curlun->sense_data = + SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + curlun->sense_data_info = usb_offset >> curlun->blkbits; + curlun->info_valid = 1; + continue; + } + + /* Get the next buffer */ + usb_offset += amount; + fsg->usb_amount_left -= amount; + amount_left_to_req -= amount; + if (amount_left_to_req == 0) + get_some_more = 0; + + /* Except at the end of the transfer, amount will be + * equal to the buffer size, which is divisible by + * the bulk-out maxpacket size. + */ + set_bulk_out_req_length(fsg, bh, amount); + start_transfer(fsg, fsg->bulk_out, bh->outreq, + &bh->outreq_busy, &bh->state); + fsg->next_buffhd_to_fill = bh->next; + continue; + } + + /* Write the received data to the backing file */ + bh = fsg->next_buffhd_to_drain; + if (bh->state == BUF_STATE_EMPTY && !get_some_more) + break; // We stopped early + if (bh->state == BUF_STATE_FULL) { + smp_rmb(); + fsg->next_buffhd_to_drain = bh->next; + bh->state = BUF_STATE_EMPTY; + + /* Did something go wrong with the transfer? */ + if (bh->outreq->status != 0) { + curlun->sense_data = SS_COMMUNICATION_FAILURE; + curlun->sense_data_info = file_offset >> curlun->blkbits; + curlun->info_valid = 1; + break; + } + + amount = bh->outreq->actual; + if (curlun->file_length - file_offset < amount) { + LERROR(curlun, + "write %u @ %llu beyond end %llu\n", + amount, (unsigned long long) file_offset, + (unsigned long long) curlun->file_length); + amount = curlun->file_length - file_offset; + } + + /* Don't accept excess data. The spec doesn't say + * what to do in this case. We'll ignore the error. + */ + amount = min(amount, bh->bulk_out_intended_length); + + /* Don't write a partial block */ + amount = round_down(amount, curlun->blksize); + if (amount == 0) + goto empty_write; + + /* Perform the write */ + file_offset_tmp = file_offset; + nwritten = vfs_write(curlun->filp, + (char __user *) bh->buf, + amount, &file_offset_tmp); + VLDBG(curlun, "file write %u @ %llu -> %d\n", amount, + (unsigned long long) file_offset, + (int) nwritten); + if (signal_pending(current)) + return -EINTR; // Interrupted! + + if (nwritten < 0) { + LDBG(curlun, "error in file write: %d\n", + (int) nwritten); + nwritten = 0; + } else if (nwritten < amount) { + LDBG(curlun, "partial file write: %d/%u\n", + (int) nwritten, amount); + nwritten = round_down(nwritten, curlun->blksize); + } + file_offset += nwritten; + amount_left_to_write -= nwritten; + fsg->residue -= nwritten; + + /* If an error occurred, report it and its position */ + if (nwritten < amount) { + curlun->sense_data = SS_WRITE_ERROR; + curlun->sense_data_info = file_offset >> curlun->blkbits; + curlun->info_valid = 1; + break; + } + + empty_write: + /* Did the host decide to stop early? */ + if (bh->outreq->actual < bh->bulk_out_intended_length) { + fsg->short_packet_received = 1; + break; + } + continue; + } + + /* Wait for something to happen */ + rc = sleep_thread(fsg); + if (rc) + return rc; + } + + return -EIO; // No default reply +} + + +/*-------------------------------------------------------------------------*/ + +static int do_synchronize_cache(struct fsg_dev *fsg) +{ + struct fsg_lun *curlun = fsg->curlun; + int rc; + + /* We ignore the requested LBA and write out all file's + * dirty data buffers. */ + rc = fsg_lun_fsync_sub(curlun); + if (rc) + curlun->sense_data = SS_WRITE_ERROR; + return 0; +} + + +/*-------------------------------------------------------------------------*/ + +static void invalidate_sub(struct fsg_lun *curlun) +{ + struct file *filp = curlun->filp; + struct inode *inode = filp->f_path.dentry->d_inode; + unsigned long rc; + + rc = invalidate_mapping_pages(inode->i_mapping, 0, -1); + VLDBG(curlun, "invalidate_mapping_pages -> %ld\n", rc); +} + +static int do_verify(struct fsg_dev *fsg) +{ + struct fsg_lun *curlun = fsg->curlun; + u32 lba; + u32 verification_length; + struct fsg_buffhd *bh = fsg->next_buffhd_to_fill; + loff_t file_offset, file_offset_tmp; + u32 amount_left; + unsigned int amount; + ssize_t nread; + + /* Get the starting Logical Block Address and check that it's + * not too big */ + lba = get_unaligned_be32(&fsg->cmnd[2]); + if (lba >= curlun->num_sectors) { + curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + return -EINVAL; + } + + /* We allow DPO (Disable Page Out = don't save data in the + * cache) but we don't implement it. */ + if ((fsg->cmnd[1] & ~0x10) != 0) { + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + + verification_length = get_unaligned_be16(&fsg->cmnd[7]); + if (unlikely(verification_length == 0)) + return -EIO; // No default reply + + /* Prepare to carry out the file verify */ + amount_left = verification_length << curlun->blkbits; + file_offset = ((loff_t) lba) << curlun->blkbits; + + /* Write out all the dirty buffers before invalidating them */ + fsg_lun_fsync_sub(curlun); + if (signal_pending(current)) + return -EINTR; + + invalidate_sub(curlun); + if (signal_pending(current)) + return -EINTR; + + /* Just try to read the requested blocks */ + while (amount_left > 0) { + + /* Figure out how much we need to read: + * Try to read the remaining amount, but not more than + * the buffer size. + * And don't try to read past the end of the file. + */ + amount = min((unsigned int) amount_left, mod_data.buflen); + amount = min((loff_t) amount, + curlun->file_length - file_offset); + if (amount == 0) { + curlun->sense_data = + SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + curlun->sense_data_info = file_offset >> curlun->blkbits; + curlun->info_valid = 1; + break; + } + + /* Perform the read */ + file_offset_tmp = file_offset; + nread = vfs_read(curlun->filp, + (char __user *) bh->buf, + amount, &file_offset_tmp); + VLDBG(curlun, "file read %u @ %llu -> %d\n", amount, + (unsigned long long) file_offset, + (int) nread); + if (signal_pending(current)) + return -EINTR; + + if (nread < 0) { + LDBG(curlun, "error in file verify: %d\n", + (int) nread); + nread = 0; + } else if (nread < amount) { + LDBG(curlun, "partial file verify: %d/%u\n", + (int) nread, amount); + nread = round_down(nread, curlun->blksize); + } + if (nread == 0) { + curlun->sense_data = SS_UNRECOVERED_READ_ERROR; + curlun->sense_data_info = file_offset >> curlun->blkbits; + curlun->info_valid = 1; + break; + } + file_offset += nread; + amount_left -= nread; + } + return 0; +} + + +/*-------------------------------------------------------------------------*/ + +static int do_inquiry(struct fsg_dev *fsg, struct fsg_buffhd *bh) +{ + u8 *buf = (u8 *) bh->buf; + + static char vendor_id[] = "Linux "; + static char product_disk_id[] = "File-Stor Gadget"; + static char product_cdrom_id[] = "File-CD Gadget "; + + if (!fsg->curlun) { // Unsupported LUNs are okay + fsg->bad_lun_okay = 1; + memset(buf, 0, 36); + buf[0] = 0x7f; // Unsupported, no device-type + buf[4] = 31; // Additional length + return 36; + } + + memset(buf, 0, 8); + buf[0] = (mod_data.cdrom ? TYPE_ROM : TYPE_DISK); + if (mod_data.removable) + buf[1] = 0x80; + buf[2] = 2; // ANSI SCSI level 2 + buf[3] = 2; // SCSI-2 INQUIRY data format + buf[4] = 31; // Additional length + // No special options + sprintf(buf + 8, "%-8s%-16s%04x", vendor_id, + (mod_data.cdrom ? product_cdrom_id : + product_disk_id), + mod_data.release); + return 36; +} + + +static int do_request_sense(struct fsg_dev *fsg, struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = fsg->curlun; + u8 *buf = (u8 *) bh->buf; + u32 sd, sdinfo; + int valid; + + /* + * From the SCSI-2 spec., section 7.9 (Unit attention condition): + * + * If a REQUEST SENSE command is received from an initiator + * with a pending unit attention condition (before the target + * generates the contingent allegiance condition), then the + * target shall either: + * a) report any pending sense data and preserve the unit + * attention condition on the logical unit, or, + * b) report the unit attention condition, may discard any + * pending sense data, and clear the unit attention + * condition on the logical unit for that initiator. + * + * FSG normally uses option a); enable this code to use option b). + */ +#if 0 + if (curlun && curlun->unit_attention_data != SS_NO_SENSE) { + curlun->sense_data = curlun->unit_attention_data; + curlun->unit_attention_data = SS_NO_SENSE; + } +#endif + + if (!curlun) { // Unsupported LUNs are okay + fsg->bad_lun_okay = 1; + sd = SS_LOGICAL_UNIT_NOT_SUPPORTED; + sdinfo = 0; + valid = 0; + } else { + sd = curlun->sense_data; + sdinfo = curlun->sense_data_info; + valid = curlun->info_valid << 7; + curlun->sense_data = SS_NO_SENSE; + curlun->sense_data_info = 0; + curlun->info_valid = 0; + } + + memset(buf, 0, 18); + buf[0] = valid | 0x70; // Valid, current error + buf[2] = SK(sd); + put_unaligned_be32(sdinfo, &buf[3]); /* Sense information */ + buf[7] = 18 - 8; // Additional sense length + buf[12] = ASC(sd); + buf[13] = ASCQ(sd); + return 18; +} + + +static int do_read_capacity(struct fsg_dev *fsg, struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = fsg->curlun; + u32 lba = get_unaligned_be32(&fsg->cmnd[2]); + int pmi = fsg->cmnd[8]; + u8 *buf = (u8 *) bh->buf; + + /* Check the PMI and LBA fields */ + if (pmi > 1 || (pmi == 0 && lba != 0)) { + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + + put_unaligned_be32(curlun->num_sectors - 1, &buf[0]); + /* Max logical block */ + put_unaligned_be32(curlun->blksize, &buf[4]); /* Block length */ + return 8; +} + + +static int do_read_header(struct fsg_dev *fsg, struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = fsg->curlun; + int msf = fsg->cmnd[1] & 0x02; + u32 lba = get_unaligned_be32(&fsg->cmnd[2]); + u8 *buf = (u8 *) bh->buf; + + if ((fsg->cmnd[1] & ~0x02) != 0) { /* Mask away MSF */ + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + if (lba >= curlun->num_sectors) { + curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + return -EINVAL; + } + + memset(buf, 0, 8); + buf[0] = 0x01; /* 2048 bytes of user data, rest is EC */ + store_cdrom_address(&buf[4], msf, lba); + return 8; +} + + +static int do_read_toc(struct fsg_dev *fsg, struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = fsg->curlun; + int msf = fsg->cmnd[1] & 0x02; + int start_track = fsg->cmnd[6]; + u8 *buf = (u8 *) bh->buf; + + if ((fsg->cmnd[1] & ~0x02) != 0 || /* Mask away MSF */ + start_track > 1) { + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + + memset(buf, 0, 20); + buf[1] = (20-2); /* TOC data length */ + buf[2] = 1; /* First track number */ + buf[3] = 1; /* Last track number */ + buf[5] = 0x16; /* Data track, copying allowed */ + buf[6] = 0x01; /* Only track is number 1 */ + store_cdrom_address(&buf[8], msf, 0); + + buf[13] = 0x16; /* Lead-out track is data */ + buf[14] = 0xAA; /* Lead-out track number */ + store_cdrom_address(&buf[16], msf, curlun->num_sectors); + return 20; +} + + +static int do_mode_sense(struct fsg_dev *fsg, struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = fsg->curlun; + int mscmnd = fsg->cmnd[0]; + u8 *buf = (u8 *) bh->buf; + u8 *buf0 = buf; + int pc, page_code; + int changeable_values, all_pages; + int valid_page = 0; + int len, limit; + + if ((fsg->cmnd[1] & ~0x08) != 0) { // Mask away DBD + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + pc = fsg->cmnd[2] >> 6; + page_code = fsg->cmnd[2] & 0x3f; + if (pc == 3) { + curlun->sense_data = SS_SAVING_PARAMETERS_NOT_SUPPORTED; + return -EINVAL; + } + changeable_values = (pc == 1); + all_pages = (page_code == 0x3f); + + /* Write the mode parameter header. Fixed values are: default + * medium type, no cache control (DPOFUA), and no block descriptors. + * The only variable value is the WriteProtect bit. We will fill in + * the mode data length later. */ + memset(buf, 0, 8); + if (mscmnd == MODE_SENSE) { + buf[2] = (curlun->ro ? 0x80 : 0x00); // WP, DPOFUA + buf += 4; + limit = 255; + } else { // MODE_SENSE_10 + buf[3] = (curlun->ro ? 0x80 : 0x00); // WP, DPOFUA + buf += 8; + limit = 65535; // Should really be mod_data.buflen + } + + /* No block descriptors */ + + /* The mode pages, in numerical order. The only page we support + * is the Caching page. */ + if (page_code == 0x08 || all_pages) { + valid_page = 1; + buf[0] = 0x08; // Page code + buf[1] = 10; // Page length + memset(buf+2, 0, 10); // None of the fields are changeable + + if (!changeable_values) { + buf[2] = 0x04; // Write cache enable, + // Read cache not disabled + // No cache retention priorities + put_unaligned_be16(0xffff, &buf[4]); + /* Don't disable prefetch */ + /* Minimum prefetch = 0 */ + put_unaligned_be16(0xffff, &buf[8]); + /* Maximum prefetch */ + put_unaligned_be16(0xffff, &buf[10]); + /* Maximum prefetch ceiling */ + } + buf += 12; + } + + /* Check that a valid page was requested and the mode data length + * isn't too long. */ + len = buf - buf0; + if (!valid_page || len > limit) { + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + + /* Store the mode data length */ + if (mscmnd == MODE_SENSE) + buf0[0] = len - 1; + else + put_unaligned_be16(len - 2, buf0); + return len; +} + + +static int do_start_stop(struct fsg_dev *fsg) +{ + struct fsg_lun *curlun = fsg->curlun; + int loej, start; + + if (!mod_data.removable) { + curlun->sense_data = SS_INVALID_COMMAND; + return -EINVAL; + } + + // int immed = fsg->cmnd[1] & 0x01; + loej = fsg->cmnd[4] & 0x02; + start = fsg->cmnd[4] & 0x01; + +#ifdef CONFIG_USB_FILE_STORAGE_TEST + if ((fsg->cmnd[1] & ~0x01) != 0 || // Mask away Immed + (fsg->cmnd[4] & ~0x03) != 0) { // Mask LoEj, Start + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + + if (!start) { + + /* Are we allowed to unload the media? */ + if (curlun->prevent_medium_removal) { + LDBG(curlun, "unload attempt prevented\n"); + curlun->sense_data = SS_MEDIUM_REMOVAL_PREVENTED; + return -EINVAL; + } + if (loej) { // Simulate an unload/eject + up_read(&fsg->filesem); + down_write(&fsg->filesem); + fsg_lun_close(curlun); + up_write(&fsg->filesem); + down_read(&fsg->filesem); + } + } else { + + /* Our emulation doesn't support mounting; the medium is + * available for use as soon as it is loaded. */ + if (!fsg_lun_is_open(curlun)) { + curlun->sense_data = SS_MEDIUM_NOT_PRESENT; + return -EINVAL; + } + } +#endif + return 0; +} + + +static int do_prevent_allow(struct fsg_dev *fsg) +{ + struct fsg_lun *curlun = fsg->curlun; + int prevent; + + if (!mod_data.removable) { + curlun->sense_data = SS_INVALID_COMMAND; + return -EINVAL; + } + + prevent = fsg->cmnd[4] & 0x01; + if ((fsg->cmnd[4] & ~0x01) != 0) { // Mask away Prevent + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + + if (curlun->prevent_medium_removal && !prevent) + fsg_lun_fsync_sub(curlun); + curlun->prevent_medium_removal = prevent; + return 0; +} + + +static int do_read_format_capacities(struct fsg_dev *fsg, + struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = fsg->curlun; + u8 *buf = (u8 *) bh->buf; + + buf[0] = buf[1] = buf[2] = 0; + buf[3] = 8; // Only the Current/Maximum Capacity Descriptor + buf += 4; + + put_unaligned_be32(curlun->num_sectors, &buf[0]); + /* Number of blocks */ + put_unaligned_be32(curlun->blksize, &buf[4]); /* Block length */ + buf[4] = 0x02; /* Current capacity */ + return 12; +} + + +static int do_mode_select(struct fsg_dev *fsg, struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = fsg->curlun; + + /* We don't support MODE SELECT */ + curlun->sense_data = SS_INVALID_COMMAND; + return -EINVAL; +} + + +/*-------------------------------------------------------------------------*/ + +static int halt_bulk_in_endpoint(struct fsg_dev *fsg) +{ + int rc; + + rc = fsg_set_halt(fsg, fsg->bulk_in); + if (rc == -EAGAIN) + VDBG(fsg, "delayed bulk-in endpoint halt\n"); + while (rc != 0) { + if (rc != -EAGAIN) { + WARNING(fsg, "usb_ep_set_halt -> %d\n", rc); + rc = 0; + break; + } + + /* Wait for a short time and then try again */ + if (msleep_interruptible(100) != 0) + return -EINTR; + rc = usb_ep_set_halt(fsg->bulk_in); + } + return rc; +} + +static int wedge_bulk_in_endpoint(struct fsg_dev *fsg) +{ + int rc; + + DBG(fsg, "bulk-in set wedge\n"); + rc = usb_ep_set_wedge(fsg->bulk_in); + if (rc == -EAGAIN) + VDBG(fsg, "delayed bulk-in endpoint wedge\n"); + while (rc != 0) { + if (rc != -EAGAIN) { + WARNING(fsg, "usb_ep_set_wedge -> %d\n", rc); + rc = 0; + break; + } + + /* Wait for a short time and then try again */ + if (msleep_interruptible(100) != 0) + return -EINTR; + rc = usb_ep_set_wedge(fsg->bulk_in); + } + return rc; +} + +static int throw_away_data(struct fsg_dev *fsg) +{ + struct fsg_buffhd *bh; + u32 amount; + int rc; + + while ((bh = fsg->next_buffhd_to_drain)->state != BUF_STATE_EMPTY || + fsg->usb_amount_left > 0) { + + /* Throw away the data in a filled buffer */ + if (bh->state == BUF_STATE_FULL) { + smp_rmb(); + bh->state = BUF_STATE_EMPTY; + fsg->next_buffhd_to_drain = bh->next; + + /* A short packet or an error ends everything */ + if (bh->outreq->actual < bh->bulk_out_intended_length || + bh->outreq->status != 0) { + raise_exception(fsg, FSG_STATE_ABORT_BULK_OUT); + return -EINTR; + } + continue; + } + + /* Try to submit another request if we need one */ + bh = fsg->next_buffhd_to_fill; + if (bh->state == BUF_STATE_EMPTY && fsg->usb_amount_left > 0) { + amount = min(fsg->usb_amount_left, + (u32) mod_data.buflen); + + /* Except at the end of the transfer, amount will be + * equal to the buffer size, which is divisible by + * the bulk-out maxpacket size. + */ + set_bulk_out_req_length(fsg, bh, amount); + start_transfer(fsg, fsg->bulk_out, bh->outreq, + &bh->outreq_busy, &bh->state); + fsg->next_buffhd_to_fill = bh->next; + fsg->usb_amount_left -= amount; + continue; + } + + /* Otherwise wait for something to happen */ + rc = sleep_thread(fsg); + if (rc) + return rc; + } + return 0; +} + + +static int finish_reply(struct fsg_dev *fsg) +{ + struct fsg_buffhd *bh = fsg->next_buffhd_to_fill; + int rc = 0; + + switch (fsg->data_dir) { + case DATA_DIR_NONE: + break; // Nothing to send + + /* If we don't know whether the host wants to read or write, + * this must be CB or CBI with an unknown command. We mustn't + * try to send or receive any data. So stall both bulk pipes + * if we can and wait for a reset. */ + case DATA_DIR_UNKNOWN: + if (mod_data.can_stall) { + fsg_set_halt(fsg, fsg->bulk_out); + rc = halt_bulk_in_endpoint(fsg); + } + break; + + /* All but the last buffer of data must have already been sent */ + case DATA_DIR_TO_HOST: + if (fsg->data_size == 0) + ; // Nothing to send + + /* If there's no residue, simply send the last buffer */ + else if (fsg->residue == 0) { + bh->inreq->zero = 0; + start_transfer(fsg, fsg->bulk_in, bh->inreq, + &bh->inreq_busy, &bh->state); + fsg->next_buffhd_to_fill = bh->next; + } + + /* There is a residue. For CB and CBI, simply mark the end + * of the data with a short packet. However, if we are + * allowed to stall, there was no data at all (residue == + * data_size), and the command failed (invalid LUN or + * sense data is set), then halt the bulk-in endpoint + * instead. */ + else if (!transport_is_bbb()) { + if (mod_data.can_stall && + fsg->residue == fsg->data_size && + (!fsg->curlun || fsg->curlun->sense_data != SS_NO_SENSE)) { + bh->state = BUF_STATE_EMPTY; + rc = halt_bulk_in_endpoint(fsg); + } else { + bh->inreq->zero = 1; + start_transfer(fsg, fsg->bulk_in, bh->inreq, + &bh->inreq_busy, &bh->state); + fsg->next_buffhd_to_fill = bh->next; + } + } + + /* + * For Bulk-only, mark the end of the data with a short + * packet. If we are allowed to stall, halt the bulk-in + * endpoint. (Note: This violates the Bulk-Only Transport + * specification, which requires us to pad the data if we + * don't halt the endpoint. Presumably nobody will mind.) + */ + else { + bh->inreq->zero = 1; + start_transfer(fsg, fsg->bulk_in, bh->inreq, + &bh->inreq_busy, &bh->state); + fsg->next_buffhd_to_fill = bh->next; + if (mod_data.can_stall) + rc = halt_bulk_in_endpoint(fsg); + } + break; + + /* We have processed all we want from the data the host has sent. + * There may still be outstanding bulk-out requests. */ + case DATA_DIR_FROM_HOST: + if (fsg->residue == 0) + ; // Nothing to receive + + /* Did the host stop sending unexpectedly early? */ + else if (fsg->short_packet_received) { + raise_exception(fsg, FSG_STATE_ABORT_BULK_OUT); + rc = -EINTR; + } + + /* We haven't processed all the incoming data. Even though + * we may be allowed to stall, doing so would cause a race. + * The controller may already have ACK'ed all the remaining + * bulk-out packets, in which case the host wouldn't see a + * STALL. Not realizing the endpoint was halted, it wouldn't + * clear the halt -- leading to problems later on. */ +#if 0 + else if (mod_data.can_stall) { + fsg_set_halt(fsg, fsg->bulk_out); + raise_exception(fsg, FSG_STATE_ABORT_BULK_OUT); + rc = -EINTR; + } +#endif + + /* We can't stall. Read in the excess data and throw it + * all away. */ + else + rc = throw_away_data(fsg); + break; + } + return rc; +} + + +static int send_status(struct fsg_dev *fsg) +{ + struct fsg_lun *curlun = fsg->curlun; + struct fsg_buffhd *bh; + int rc; + u8 status = US_BULK_STAT_OK; + u32 sd, sdinfo = 0; + + /* Wait for the next buffer to become available */ + bh = fsg->next_buffhd_to_fill; + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(fsg); + if (rc) + return rc; + } + + if (curlun) { + sd = curlun->sense_data; + sdinfo = curlun->sense_data_info; + } else if (fsg->bad_lun_okay) + sd = SS_NO_SENSE; + else + sd = SS_LOGICAL_UNIT_NOT_SUPPORTED; + + if (fsg->phase_error) { + DBG(fsg, "sending phase-error status\n"); + status = US_BULK_STAT_PHASE; + sd = SS_INVALID_COMMAND; + } else if (sd != SS_NO_SENSE) { + DBG(fsg, "sending command-failure status\n"); + status = US_BULK_STAT_FAIL; + VDBG(fsg, " sense data: SK x%02x, ASC x%02x, ASCQ x%02x;" + " info x%x\n", + SK(sd), ASC(sd), ASCQ(sd), sdinfo); + } + + if (transport_is_bbb()) { + struct bulk_cs_wrap *csw = bh->buf; + + /* Store and send the Bulk-only CSW */ + csw->Signature = cpu_to_le32(US_BULK_CS_SIGN); + csw->Tag = fsg->tag; + csw->Residue = cpu_to_le32(fsg->residue); + csw->Status = status; + + bh->inreq->length = US_BULK_CS_WRAP_LEN; + bh->inreq->zero = 0; + start_transfer(fsg, fsg->bulk_in, bh->inreq, + &bh->inreq_busy, &bh->state); + + } else if (mod_data.transport_type == USB_PR_CB) { + + /* Control-Bulk transport has no status phase! */ + return 0; + + } else { // USB_PR_CBI + struct interrupt_data *buf = bh->buf; + + /* Store and send the Interrupt data. UFI sends the ASC + * and ASCQ bytes. Everything else sends a Type (which + * is always 0) and the status Value. */ + if (mod_data.protocol_type == USB_SC_UFI) { + buf->bType = ASC(sd); + buf->bValue = ASCQ(sd); + } else { + buf->bType = 0; + buf->bValue = status; + } + fsg->intreq->length = CBI_INTERRUPT_DATA_LEN; + + fsg->intr_buffhd = bh; // Point to the right buffhd + fsg->intreq->buf = bh->inreq->buf; + fsg->intreq->context = bh; + start_transfer(fsg, fsg->intr_in, fsg->intreq, + &fsg->intreq_busy, &bh->state); + } + + fsg->next_buffhd_to_fill = bh->next; + return 0; +} + + +/*-------------------------------------------------------------------------*/ + +/* Check whether the command is properly formed and whether its data size + * and direction agree with the values we already have. */ +static int check_command(struct fsg_dev *fsg, int cmnd_size, + enum data_direction data_dir, unsigned int mask, + int needs_medium, const char *name) +{ + int i; + int lun = fsg->cmnd[1] >> 5; + static const char dirletter[4] = {'u', 'o', 'i', 'n'}; + char hdlen[20]; + struct fsg_lun *curlun; + + /* Adjust the expected cmnd_size for protocol encapsulation padding. + * Transparent SCSI doesn't pad. */ + if (protocol_is_scsi()) + ; + + /* There's some disagreement as to whether RBC pads commands or not. + * We'll play it safe and accept either form. */ + else if (mod_data.protocol_type == USB_SC_RBC) { + if (fsg->cmnd_size == 12) + cmnd_size = 12; + + /* All the other protocols pad to 12 bytes */ + } else + cmnd_size = 12; + + hdlen[0] = 0; + if (fsg->data_dir != DATA_DIR_UNKNOWN) + sprintf(hdlen, ", H%c=%u", dirletter[(int) fsg->data_dir], + fsg->data_size); + VDBG(fsg, "SCSI command: %s; Dc=%d, D%c=%u; Hc=%d%s\n", + name, cmnd_size, dirletter[(int) data_dir], + fsg->data_size_from_cmnd, fsg->cmnd_size, hdlen); + + /* We can't reply at all until we know the correct data direction + * and size. */ + if (fsg->data_size_from_cmnd == 0) + data_dir = DATA_DIR_NONE; + if (fsg->data_dir == DATA_DIR_UNKNOWN) { // CB or CBI + fsg->data_dir = data_dir; + fsg->data_size = fsg->data_size_from_cmnd; + + } else { // Bulk-only + if (fsg->data_size < fsg->data_size_from_cmnd) { + + /* Host data size < Device data size is a phase error. + * Carry out the command, but only transfer as much + * as we are allowed. */ + fsg->data_size_from_cmnd = fsg->data_size; + fsg->phase_error = 1; + } + } + fsg->residue = fsg->usb_amount_left = fsg->data_size; + + /* Conflicting data directions is a phase error */ + if (fsg->data_dir != data_dir && fsg->data_size_from_cmnd > 0) { + fsg->phase_error = 1; + return -EINVAL; + } + + /* Verify the length of the command itself */ + if (cmnd_size != fsg->cmnd_size) { + + /* Special case workaround: There are plenty of buggy SCSI + * implementations. Many have issues with cbw->Length + * field passing a wrong command size. For those cases we + * always try to work around the problem by using the length + * sent by the host side provided it is at least as large + * as the correct command length. + * Examples of such cases would be MS-Windows, which issues + * REQUEST SENSE with cbw->Length == 12 where it should + * be 6, and xbox360 issuing INQUIRY, TEST UNIT READY and + * REQUEST SENSE with cbw->Length == 10 where it should + * be 6 as well. + */ + if (cmnd_size <= fsg->cmnd_size) { + DBG(fsg, "%s is buggy! Expected length %d " + "but we got %d\n", name, + cmnd_size, fsg->cmnd_size); + cmnd_size = fsg->cmnd_size; + } else { + fsg->phase_error = 1; + return -EINVAL; + } + } + + /* Check that the LUN values are consistent */ + if (transport_is_bbb()) { + if (fsg->lun != lun) + DBG(fsg, "using LUN %d from CBW, " + "not LUN %d from CDB\n", + fsg->lun, lun); + } + + /* Check the LUN */ + curlun = fsg->curlun; + if (curlun) { + if (fsg->cmnd[0] != REQUEST_SENSE) { + curlun->sense_data = SS_NO_SENSE; + curlun->sense_data_info = 0; + curlun->info_valid = 0; + } + } else { + fsg->bad_lun_okay = 0; + + /* INQUIRY and REQUEST SENSE commands are explicitly allowed + * to use unsupported LUNs; all others may not. */ + if (fsg->cmnd[0] != INQUIRY && + fsg->cmnd[0] != REQUEST_SENSE) { + DBG(fsg, "unsupported LUN %d\n", fsg->lun); + return -EINVAL; + } + } + + /* If a unit attention condition exists, only INQUIRY and + * REQUEST SENSE commands are allowed; anything else must fail. */ + if (curlun && curlun->unit_attention_data != SS_NO_SENSE && + fsg->cmnd[0] != INQUIRY && + fsg->cmnd[0] != REQUEST_SENSE) { + curlun->sense_data = curlun->unit_attention_data; + curlun->unit_attention_data = SS_NO_SENSE; + return -EINVAL; + } + + /* Check that only command bytes listed in the mask are non-zero */ + fsg->cmnd[1] &= 0x1f; // Mask away the LUN + for (i = 1; i < cmnd_size; ++i) { + if (fsg->cmnd[i] && !(mask & (1 << i))) { + if (curlun) + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + } + + /* If the medium isn't mounted and the command needs to access + * it, return an error. */ + if (curlun && !fsg_lun_is_open(curlun) && needs_medium) { + curlun->sense_data = SS_MEDIUM_NOT_PRESENT; + return -EINVAL; + } + + return 0; +} + +/* wrapper of check_command for data size in blocks handling */ +static int check_command_size_in_blocks(struct fsg_dev *fsg, int cmnd_size, + enum data_direction data_dir, unsigned int mask, + int needs_medium, const char *name) +{ + if (fsg->curlun) + fsg->data_size_from_cmnd <<= fsg->curlun->blkbits; + return check_command(fsg, cmnd_size, data_dir, + mask, needs_medium, name); +} + +static int do_scsi_command(struct fsg_dev *fsg) +{ + struct fsg_buffhd *bh; + int rc; + int reply = -EINVAL; + int i; + static char unknown[16]; + + dump_cdb(fsg); + + /* Wait for the next buffer to become available for data or status */ + bh = fsg->next_buffhd_to_drain = fsg->next_buffhd_to_fill; + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(fsg); + if (rc) + return rc; + } + fsg->phase_error = 0; + fsg->short_packet_received = 0; + + down_read(&fsg->filesem); // We're using the backing file + switch (fsg->cmnd[0]) { + + case INQUIRY: + fsg->data_size_from_cmnd = fsg->cmnd[4]; + if ((reply = check_command(fsg, 6, DATA_DIR_TO_HOST, + (1<<4), 0, + "INQUIRY")) == 0) + reply = do_inquiry(fsg, bh); + break; + + case MODE_SELECT: + fsg->data_size_from_cmnd = fsg->cmnd[4]; + if ((reply = check_command(fsg, 6, DATA_DIR_FROM_HOST, + (1<<1) | (1<<4), 0, + "MODE SELECT(6)")) == 0) + reply = do_mode_select(fsg, bh); + break; + + case MODE_SELECT_10: + fsg->data_size_from_cmnd = get_unaligned_be16(&fsg->cmnd[7]); + if ((reply = check_command(fsg, 10, DATA_DIR_FROM_HOST, + (1<<1) | (3<<7), 0, + "MODE SELECT(10)")) == 0) + reply = do_mode_select(fsg, bh); + break; + + case MODE_SENSE: + fsg->data_size_from_cmnd = fsg->cmnd[4]; + if ((reply = check_command(fsg, 6, DATA_DIR_TO_HOST, + (1<<1) | (1<<2) | (1<<4), 0, + "MODE SENSE(6)")) == 0) + reply = do_mode_sense(fsg, bh); + break; + + case MODE_SENSE_10: + fsg->data_size_from_cmnd = get_unaligned_be16(&fsg->cmnd[7]); + if ((reply = check_command(fsg, 10, DATA_DIR_TO_HOST, + (1<<1) | (1<<2) | (3<<7), 0, + "MODE SENSE(10)")) == 0) + reply = do_mode_sense(fsg, bh); + break; + + case ALLOW_MEDIUM_REMOVAL: + fsg->data_size_from_cmnd = 0; + if ((reply = check_command(fsg, 6, DATA_DIR_NONE, + (1<<4), 0, + "PREVENT-ALLOW MEDIUM REMOVAL")) == 0) + reply = do_prevent_allow(fsg); + break; + + case READ_6: + i = fsg->cmnd[4]; + fsg->data_size_from_cmnd = (i == 0) ? 256 : i; + if ((reply = check_command_size_in_blocks(fsg, 6, + DATA_DIR_TO_HOST, + (7<<1) | (1<<4), 1, + "READ(6)")) == 0) + reply = do_read(fsg); + break; + + case READ_10: + fsg->data_size_from_cmnd = get_unaligned_be16(&fsg->cmnd[7]); + if ((reply = check_command_size_in_blocks(fsg, 10, + DATA_DIR_TO_HOST, + (1<<1) | (0xf<<2) | (3<<7), 1, + "READ(10)")) == 0) + reply = do_read(fsg); + break; + + case READ_12: + fsg->data_size_from_cmnd = get_unaligned_be32(&fsg->cmnd[6]); + if ((reply = check_command_size_in_blocks(fsg, 12, + DATA_DIR_TO_HOST, + (1<<1) | (0xf<<2) | (0xf<<6), 1, + "READ(12)")) == 0) + reply = do_read(fsg); + break; + + case READ_CAPACITY: + fsg->data_size_from_cmnd = 8; + if ((reply = check_command(fsg, 10, DATA_DIR_TO_HOST, + (0xf<<2) | (1<<8), 1, + "READ CAPACITY")) == 0) + reply = do_read_capacity(fsg, bh); + break; + + case READ_HEADER: + if (!mod_data.cdrom) + goto unknown_cmnd; + fsg->data_size_from_cmnd = get_unaligned_be16(&fsg->cmnd[7]); + if ((reply = check_command(fsg, 10, DATA_DIR_TO_HOST, + (3<<7) | (0x1f<<1), 1, + "READ HEADER")) == 0) + reply = do_read_header(fsg, bh); + break; + + case READ_TOC: + if (!mod_data.cdrom) + goto unknown_cmnd; + fsg->data_size_from_cmnd = get_unaligned_be16(&fsg->cmnd[7]); + if ((reply = check_command(fsg, 10, DATA_DIR_TO_HOST, + (7<<6) | (1<<1), 1, + "READ TOC")) == 0) + reply = do_read_toc(fsg, bh); + break; + + case READ_FORMAT_CAPACITIES: + fsg->data_size_from_cmnd = get_unaligned_be16(&fsg->cmnd[7]); + if ((reply = check_command(fsg, 10, DATA_DIR_TO_HOST, + (3<<7), 1, + "READ FORMAT CAPACITIES")) == 0) + reply = do_read_format_capacities(fsg, bh); + break; + + case REQUEST_SENSE: + fsg->data_size_from_cmnd = fsg->cmnd[4]; + if ((reply = check_command(fsg, 6, DATA_DIR_TO_HOST, + (1<<4), 0, + "REQUEST SENSE")) == 0) + reply = do_request_sense(fsg, bh); + break; + + case START_STOP: + fsg->data_size_from_cmnd = 0; + if ((reply = check_command(fsg, 6, DATA_DIR_NONE, + (1<<1) | (1<<4), 0, + "START-STOP UNIT")) == 0) + reply = do_start_stop(fsg); + break; + + case SYNCHRONIZE_CACHE: + fsg->data_size_from_cmnd = 0; + if ((reply = check_command(fsg, 10, DATA_DIR_NONE, + (0xf<<2) | (3<<7), 1, + "SYNCHRONIZE CACHE")) == 0) + reply = do_synchronize_cache(fsg); + break; + + case TEST_UNIT_READY: + fsg->data_size_from_cmnd = 0; + reply = check_command(fsg, 6, DATA_DIR_NONE, + 0, 1, + "TEST UNIT READY"); + break; + + /* Although optional, this command is used by MS-Windows. We + * support a minimal version: BytChk must be 0. */ + case VERIFY: + fsg->data_size_from_cmnd = 0; + if ((reply = check_command(fsg, 10, DATA_DIR_NONE, + (1<<1) | (0xf<<2) | (3<<7), 1, + "VERIFY")) == 0) + reply = do_verify(fsg); + break; + + case WRITE_6: + i = fsg->cmnd[4]; + fsg->data_size_from_cmnd = (i == 0) ? 256 : i; + if ((reply = check_command_size_in_blocks(fsg, 6, + DATA_DIR_FROM_HOST, + (7<<1) | (1<<4), 1, + "WRITE(6)")) == 0) + reply = do_write(fsg); + break; + + case WRITE_10: + fsg->data_size_from_cmnd = get_unaligned_be16(&fsg->cmnd[7]); + if ((reply = check_command_size_in_blocks(fsg, 10, + DATA_DIR_FROM_HOST, + (1<<1) | (0xf<<2) | (3<<7), 1, + "WRITE(10)")) == 0) + reply = do_write(fsg); + break; + + case WRITE_12: + fsg->data_size_from_cmnd = get_unaligned_be32(&fsg->cmnd[6]); + if ((reply = check_command_size_in_blocks(fsg, 12, + DATA_DIR_FROM_HOST, + (1<<1) | (0xf<<2) | (0xf<<6), 1, + "WRITE(12)")) == 0) + reply = do_write(fsg); + break; + + /* Some mandatory commands that we recognize but don't implement. + * They don't mean much in this setting. It's left as an exercise + * for anyone interested to implement RESERVE and RELEASE in terms + * of Posix locks. */ + case FORMAT_UNIT: + case RELEASE: + case RESERVE: + case SEND_DIAGNOSTIC: + // Fall through + + default: + unknown_cmnd: + fsg->data_size_from_cmnd = 0; + sprintf(unknown, "Unknown x%02x", fsg->cmnd[0]); + if ((reply = check_command(fsg, fsg->cmnd_size, + DATA_DIR_UNKNOWN, ~0, 0, unknown)) == 0) { + fsg->curlun->sense_data = SS_INVALID_COMMAND; + reply = -EINVAL; + } + break; + } + up_read(&fsg->filesem); + + if (reply == -EINTR || signal_pending(current)) + return -EINTR; + + /* Set up the single reply buffer for finish_reply() */ + if (reply == -EINVAL) + reply = 0; // Error reply length + if (reply >= 0 && fsg->data_dir == DATA_DIR_TO_HOST) { + reply = min((u32) reply, fsg->data_size_from_cmnd); + bh->inreq->length = reply; + bh->state = BUF_STATE_FULL; + fsg->residue -= reply; + } // Otherwise it's already set + + return 0; +} + + +/*-------------------------------------------------------------------------*/ + +static int received_cbw(struct fsg_dev *fsg, struct fsg_buffhd *bh) +{ + struct usb_request *req = bh->outreq; + struct bulk_cb_wrap *cbw = req->buf; + + /* Was this a real packet? Should it be ignored? */ + if (req->status || test_bit(IGNORE_BULK_OUT, &fsg->atomic_bitflags)) + return -EINVAL; + + /* Is the CBW valid? */ + if (req->actual != US_BULK_CB_WRAP_LEN || + cbw->Signature != cpu_to_le32( + US_BULK_CB_SIGN)) { + DBG(fsg, "invalid CBW: len %u sig 0x%x\n", + req->actual, + le32_to_cpu(cbw->Signature)); + + /* The Bulk-only spec says we MUST stall the IN endpoint + * (6.6.1), so it's unavoidable. It also says we must + * retain this state until the next reset, but there's + * no way to tell the controller driver it should ignore + * Clear-Feature(HALT) requests. + * + * We aren't required to halt the OUT endpoint; instead + * we can simply accept and discard any data received + * until the next reset. */ + wedge_bulk_in_endpoint(fsg); + set_bit(IGNORE_BULK_OUT, &fsg->atomic_bitflags); + return -EINVAL; + } + + /* Is the CBW meaningful? */ + if (cbw->Lun >= FSG_MAX_LUNS || cbw->Flags & ~US_BULK_FLAG_IN || + cbw->Length <= 0 || cbw->Length > MAX_COMMAND_SIZE) { + DBG(fsg, "non-meaningful CBW: lun = %u, flags = 0x%x, " + "cmdlen %u\n", + cbw->Lun, cbw->Flags, cbw->Length); + + /* We can do anything we want here, so let's stall the + * bulk pipes if we are allowed to. */ + if (mod_data.can_stall) { + fsg_set_halt(fsg, fsg->bulk_out); + halt_bulk_in_endpoint(fsg); + } + return -EINVAL; + } + + /* Save the command for later */ + fsg->cmnd_size = cbw->Length; + memcpy(fsg->cmnd, cbw->CDB, fsg->cmnd_size); + if (cbw->Flags & US_BULK_FLAG_IN) + fsg->data_dir = DATA_DIR_TO_HOST; + else + fsg->data_dir = DATA_DIR_FROM_HOST; + fsg->data_size = le32_to_cpu(cbw->DataTransferLength); + if (fsg->data_size == 0) + fsg->data_dir = DATA_DIR_NONE; + fsg->lun = cbw->Lun; + fsg->tag = cbw->Tag; + return 0; +} + + +static int get_next_command(struct fsg_dev *fsg) +{ + struct fsg_buffhd *bh; + int rc = 0; + + if (transport_is_bbb()) { + + /* Wait for the next buffer to become available */ + bh = fsg->next_buffhd_to_fill; + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(fsg); + if (rc) + return rc; + } + + /* Queue a request to read a Bulk-only CBW */ + set_bulk_out_req_length(fsg, bh, US_BULK_CB_WRAP_LEN); + start_transfer(fsg, fsg->bulk_out, bh->outreq, + &bh->outreq_busy, &bh->state); + + /* We will drain the buffer in software, which means we + * can reuse it for the next filling. No need to advance + * next_buffhd_to_fill. */ + + /* Wait for the CBW to arrive */ + while (bh->state != BUF_STATE_FULL) { + rc = sleep_thread(fsg); + if (rc) + return rc; + } + smp_rmb(); + rc = received_cbw(fsg, bh); + bh->state = BUF_STATE_EMPTY; + + } else { // USB_PR_CB or USB_PR_CBI + + /* Wait for the next command to arrive */ + while (fsg->cbbuf_cmnd_size == 0) { + rc = sleep_thread(fsg); + if (rc) + return rc; + } + + /* Is the previous status interrupt request still busy? + * The host is allowed to skip reading the status, + * so we must cancel it. */ + if (fsg->intreq_busy) + usb_ep_dequeue(fsg->intr_in, fsg->intreq); + + /* Copy the command and mark the buffer empty */ + fsg->data_dir = DATA_DIR_UNKNOWN; + spin_lock_irq(&fsg->lock); + fsg->cmnd_size = fsg->cbbuf_cmnd_size; + memcpy(fsg->cmnd, fsg->cbbuf_cmnd, fsg->cmnd_size); + fsg->cbbuf_cmnd_size = 0; + spin_unlock_irq(&fsg->lock); + + /* Use LUN from the command */ + fsg->lun = fsg->cmnd[1] >> 5; + } + + /* Update current lun */ + if (fsg->lun >= 0 && fsg->lun < fsg->nluns) + fsg->curlun = &fsg->luns[fsg->lun]; + else + fsg->curlun = NULL; + + return rc; +} + + +/*-------------------------------------------------------------------------*/ + +static int enable_endpoint(struct fsg_dev *fsg, struct usb_ep *ep, + const struct usb_endpoint_descriptor *d) +{ + int rc; + + ep->driver_data = fsg; + ep->desc = d; + rc = usb_ep_enable(ep); + if (rc) + ERROR(fsg, "can't enable %s, result %d\n", ep->name, rc); + return rc; +} + +static int alloc_request(struct fsg_dev *fsg, struct usb_ep *ep, + struct usb_request **preq) +{ + *preq = usb_ep_alloc_request(ep, GFP_ATOMIC); + if (*preq) + return 0; + ERROR(fsg, "can't allocate request for %s\n", ep->name); + return -ENOMEM; +} + +/* + * Reset interface setting and re-init endpoint state (toggle etc). + * Call with altsetting < 0 to disable the interface. The only other + * available altsetting is 0, which enables the interface. + */ +static int do_set_interface(struct fsg_dev *fsg, int altsetting) +{ + int rc = 0; + int i; + const struct usb_endpoint_descriptor *d; + + if (fsg->running) + DBG(fsg, "reset interface\n"); + +reset: + /* Deallocate the requests */ + for (i = 0; i < fsg_num_buffers; ++i) { + struct fsg_buffhd *bh = &fsg->buffhds[i]; + + if (bh->inreq) { + usb_ep_free_request(fsg->bulk_in, bh->inreq); + bh->inreq = NULL; + } + if (bh->outreq) { + usb_ep_free_request(fsg->bulk_out, bh->outreq); + bh->outreq = NULL; + } + } + if (fsg->intreq) { + usb_ep_free_request(fsg->intr_in, fsg->intreq); + fsg->intreq = NULL; + } + + /* Disable the endpoints */ + if (fsg->bulk_in_enabled) { + usb_ep_disable(fsg->bulk_in); + fsg->bulk_in_enabled = 0; + } + if (fsg->bulk_out_enabled) { + usb_ep_disable(fsg->bulk_out); + fsg->bulk_out_enabled = 0; + } + if (fsg->intr_in_enabled) { + usb_ep_disable(fsg->intr_in); + fsg->intr_in_enabled = 0; + } + + fsg->running = 0; + if (altsetting < 0 || rc != 0) + return rc; + + DBG(fsg, "set interface %d\n", altsetting); + + /* Enable the endpoints */ + d = fsg_ep_desc(fsg->gadget, + &fsg_fs_bulk_in_desc, &fsg_hs_bulk_in_desc, + &fsg_ss_bulk_in_desc); + if ((rc = enable_endpoint(fsg, fsg->bulk_in, d)) != 0) + goto reset; + fsg->bulk_in_enabled = 1; + + d = fsg_ep_desc(fsg->gadget, + &fsg_fs_bulk_out_desc, &fsg_hs_bulk_out_desc, + &fsg_ss_bulk_out_desc); + if ((rc = enable_endpoint(fsg, fsg->bulk_out, d)) != 0) + goto reset; + fsg->bulk_out_enabled = 1; + fsg->bulk_out_maxpacket = usb_endpoint_maxp(d); + clear_bit(IGNORE_BULK_OUT, &fsg->atomic_bitflags); + + if (transport_is_cbi()) { + d = fsg_ep_desc(fsg->gadget, + &fsg_fs_intr_in_desc, &fsg_hs_intr_in_desc, + &fsg_ss_intr_in_desc); + if ((rc = enable_endpoint(fsg, fsg->intr_in, d)) != 0) + goto reset; + fsg->intr_in_enabled = 1; + } + + /* Allocate the requests */ + for (i = 0; i < fsg_num_buffers; ++i) { + struct fsg_buffhd *bh = &fsg->buffhds[i]; + + if ((rc = alloc_request(fsg, fsg->bulk_in, &bh->inreq)) != 0) + goto reset; + if ((rc = alloc_request(fsg, fsg->bulk_out, &bh->outreq)) != 0) + goto reset; + bh->inreq->buf = bh->outreq->buf = bh->buf; + bh->inreq->context = bh->outreq->context = bh; + bh->inreq->complete = bulk_in_complete; + bh->outreq->complete = bulk_out_complete; + } + if (transport_is_cbi()) { + if ((rc = alloc_request(fsg, fsg->intr_in, &fsg->intreq)) != 0) + goto reset; + fsg->intreq->complete = intr_in_complete; + } + + fsg->running = 1; + for (i = 0; i < fsg->nluns; ++i) + fsg->luns[i].unit_attention_data = SS_RESET_OCCURRED; + return rc; +} + + +/* + * Change our operational configuration. This code must agree with the code + * that returns config descriptors, and with interface altsetting code. + * + * It's also responsible for power management interactions. Some + * configurations might not work with our current power sources. + * For now we just assume the gadget is always self-powered. + */ +static int do_set_config(struct fsg_dev *fsg, u8 new_config) +{ + int rc = 0; + + /* Disable the single interface */ + if (fsg->config != 0) { + DBG(fsg, "reset config\n"); + fsg->config = 0; + rc = do_set_interface(fsg, -1); + } + + /* Enable the interface */ + if (new_config != 0) { + fsg->config = new_config; + if ((rc = do_set_interface(fsg, 0)) != 0) + fsg->config = 0; // Reset on errors + else + INFO(fsg, "%s config #%d\n", + usb_speed_string(fsg->gadget->speed), + fsg->config); + } + return rc; +} + + +/*-------------------------------------------------------------------------*/ + +static void handle_exception(struct fsg_dev *fsg) +{ + siginfo_t info; + int sig; + int i; + int num_active; + struct fsg_buffhd *bh; + enum fsg_state old_state; + u8 new_config; + struct fsg_lun *curlun; + unsigned int exception_req_tag; + int rc; + + /* Clear the existing signals. Anything but SIGUSR1 is converted + * into a high-priority EXIT exception. */ + for (;;) { + sig = dequeue_signal_lock(current, ¤t->blocked, &info); + if (!sig) + break; + if (sig != SIGUSR1) { + if (fsg->state < FSG_STATE_EXIT) + DBG(fsg, "Main thread exiting on signal\n"); + raise_exception(fsg, FSG_STATE_EXIT); + } + } + + /* Cancel all the pending transfers */ + if (fsg->intreq_busy) + usb_ep_dequeue(fsg->intr_in, fsg->intreq); + for (i = 0; i < fsg_num_buffers; ++i) { + bh = &fsg->buffhds[i]; + if (bh->inreq_busy) + usb_ep_dequeue(fsg->bulk_in, bh->inreq); + if (bh->outreq_busy) + usb_ep_dequeue(fsg->bulk_out, bh->outreq); + } + + /* Wait until everything is idle */ + for (;;) { + num_active = fsg->intreq_busy; + for (i = 0; i < fsg_num_buffers; ++i) { + bh = &fsg->buffhds[i]; + num_active += bh->inreq_busy + bh->outreq_busy; + } + if (num_active == 0) + break; + if (sleep_thread(fsg)) + return; + } + + /* Clear out the controller's fifos */ + if (fsg->bulk_in_enabled) + usb_ep_fifo_flush(fsg->bulk_in); + if (fsg->bulk_out_enabled) + usb_ep_fifo_flush(fsg->bulk_out); + if (fsg->intr_in_enabled) + usb_ep_fifo_flush(fsg->intr_in); + + /* Reset the I/O buffer states and pointers, the SCSI + * state, and the exception. Then invoke the handler. */ + spin_lock_irq(&fsg->lock); + + for (i = 0; i < fsg_num_buffers; ++i) { + bh = &fsg->buffhds[i]; + bh->state = BUF_STATE_EMPTY; + } + fsg->next_buffhd_to_fill = fsg->next_buffhd_to_drain = + &fsg->buffhds[0]; + + exception_req_tag = fsg->exception_req_tag; + new_config = fsg->new_config; + old_state = fsg->state; + + if (old_state == FSG_STATE_ABORT_BULK_OUT) + fsg->state = FSG_STATE_STATUS_PHASE; + else { + for (i = 0; i < fsg->nluns; ++i) { + curlun = &fsg->luns[i]; + curlun->prevent_medium_removal = 0; + curlun->sense_data = curlun->unit_attention_data = + SS_NO_SENSE; + curlun->sense_data_info = 0; + curlun->info_valid = 0; + } + fsg->state = FSG_STATE_IDLE; + } + spin_unlock_irq(&fsg->lock); + + /* Carry out any extra actions required for the exception */ + switch (old_state) { + default: + break; + + case FSG_STATE_ABORT_BULK_OUT: + send_status(fsg); + spin_lock_irq(&fsg->lock); + if (fsg->state == FSG_STATE_STATUS_PHASE) + fsg->state = FSG_STATE_IDLE; + spin_unlock_irq(&fsg->lock); + break; + + case FSG_STATE_RESET: + /* In case we were forced against our will to halt a + * bulk endpoint, clear the halt now. (The SuperH UDC + * requires this.) */ + if (test_and_clear_bit(IGNORE_BULK_OUT, &fsg->atomic_bitflags)) + usb_ep_clear_halt(fsg->bulk_in); + + if (transport_is_bbb()) { + if (fsg->ep0_req_tag == exception_req_tag) + ep0_queue(fsg); // Complete the status stage + + } else if (transport_is_cbi()) + send_status(fsg); // Status by interrupt pipe + + /* Technically this should go here, but it would only be + * a waste of time. Ditto for the INTERFACE_CHANGE and + * CONFIG_CHANGE cases. */ + // for (i = 0; i < fsg->nluns; ++i) + // fsg->luns[i].unit_attention_data = SS_RESET_OCCURRED; + break; + + case FSG_STATE_INTERFACE_CHANGE: + rc = do_set_interface(fsg, 0); + if (fsg->ep0_req_tag != exception_req_tag) + break; + if (rc != 0) // STALL on errors + fsg_set_halt(fsg, fsg->ep0); + else // Complete the status stage + ep0_queue(fsg); + break; + + case FSG_STATE_CONFIG_CHANGE: + rc = do_set_config(fsg, new_config); + if (fsg->ep0_req_tag != exception_req_tag) + break; + if (rc != 0) // STALL on errors + fsg_set_halt(fsg, fsg->ep0); + else // Complete the status stage + ep0_queue(fsg); + break; + + case FSG_STATE_DISCONNECT: + for (i = 0; i < fsg->nluns; ++i) + fsg_lun_fsync_sub(fsg->luns + i); + do_set_config(fsg, 0); // Unconfigured state + break; + + case FSG_STATE_EXIT: + case FSG_STATE_TERMINATED: + do_set_config(fsg, 0); // Free resources + spin_lock_irq(&fsg->lock); + fsg->state = FSG_STATE_TERMINATED; // Stop the thread + spin_unlock_irq(&fsg->lock); + break; + } +} + + +/*-------------------------------------------------------------------------*/ + +static int fsg_main_thread(void *fsg_) +{ + struct fsg_dev *fsg = fsg_; + + /* Allow the thread to be killed by a signal, but set the signal mask + * to block everything but INT, TERM, KILL, and USR1. */ + allow_signal(SIGINT); + allow_signal(SIGTERM); + allow_signal(SIGKILL); + allow_signal(SIGUSR1); + + /* Allow the thread to be frozen */ + set_freezable(); + + /* Arrange for userspace references to be interpreted as kernel + * pointers. That way we can pass a kernel pointer to a routine + * that expects a __user pointer and it will work okay. */ + set_fs(get_ds()); + + /* The main loop */ + while (fsg->state != FSG_STATE_TERMINATED) { + if (exception_in_progress(fsg) || signal_pending(current)) { + handle_exception(fsg); + continue; + } + + if (!fsg->running) { + sleep_thread(fsg); + continue; + } + + if (get_next_command(fsg)) + continue; + + spin_lock_irq(&fsg->lock); + if (!exception_in_progress(fsg)) + fsg->state = FSG_STATE_DATA_PHASE; + spin_unlock_irq(&fsg->lock); + + if (do_scsi_command(fsg) || finish_reply(fsg)) + continue; + + spin_lock_irq(&fsg->lock); + if (!exception_in_progress(fsg)) + fsg->state = FSG_STATE_STATUS_PHASE; + spin_unlock_irq(&fsg->lock); + + if (send_status(fsg)) + continue; + + spin_lock_irq(&fsg->lock); + if (!exception_in_progress(fsg)) + fsg->state = FSG_STATE_IDLE; + spin_unlock_irq(&fsg->lock); + } + + spin_lock_irq(&fsg->lock); + fsg->thread_task = NULL; + spin_unlock_irq(&fsg->lock); + + /* If we are exiting because of a signal, unregister the + * gadget driver. */ + if (test_and_clear_bit(REGISTERED, &fsg->atomic_bitflags)) + usb_gadget_unregister_driver(&fsg_driver); + + /* Let the unbind and cleanup routines know the thread has exited */ + complete_and_exit(&fsg->thread_notifier, 0); +} + + +/*-------------------------------------------------------------------------*/ + + +/* The write permissions and store_xxx pointers are set in fsg_bind() */ +static DEVICE_ATTR(ro, 0444, fsg_show_ro, NULL); +static DEVICE_ATTR(nofua, 0644, fsg_show_nofua, NULL); +static DEVICE_ATTR(file, 0444, fsg_show_file, NULL); + + +/*-------------------------------------------------------------------------*/ + +static void fsg_release(struct kref *ref) +{ + struct fsg_dev *fsg = container_of(ref, struct fsg_dev, ref); + + kfree(fsg->luns); + kfree(fsg); +} + +static void lun_release(struct device *dev) +{ + struct rw_semaphore *filesem = dev_get_drvdata(dev); + struct fsg_dev *fsg = + container_of(filesem, struct fsg_dev, filesem); + + kref_put(&fsg->ref, fsg_release); +} + +static void /* __init_or_exit */ fsg_unbind(struct usb_gadget *gadget) +{ + struct fsg_dev *fsg = get_gadget_data(gadget); + int i; + struct fsg_lun *curlun; + struct usb_request *req = fsg->ep0req; + + DBG(fsg, "unbind\n"); + clear_bit(REGISTERED, &fsg->atomic_bitflags); + + /* If the thread isn't already dead, tell it to exit now */ + if (fsg->state != FSG_STATE_TERMINATED) { + raise_exception(fsg, FSG_STATE_EXIT); + wait_for_completion(&fsg->thread_notifier); + + /* The cleanup routine waits for this completion also */ + complete(&fsg->thread_notifier); + } + + /* Unregister the sysfs attribute files and the LUNs */ + for (i = 0; i < fsg->nluns; ++i) { + curlun = &fsg->luns[i]; + if (curlun->registered) { + device_remove_file(&curlun->dev, &dev_attr_nofua); + device_remove_file(&curlun->dev, &dev_attr_ro); + device_remove_file(&curlun->dev, &dev_attr_file); + fsg_lun_close(curlun); + device_unregister(&curlun->dev); + curlun->registered = 0; + } + } + + /* Free the data buffers */ + for (i = 0; i < fsg_num_buffers; ++i) + kfree(fsg->buffhds[i].buf); + + /* Free the request and buffer for endpoint 0 */ + if (req) { + kfree(req->buf); + usb_ep_free_request(fsg->ep0, req); + } + + set_gadget_data(gadget, NULL); +} + + +static int __init check_parameters(struct fsg_dev *fsg) +{ + int prot; + int gcnum; + + /* Store the default values */ + mod_data.transport_type = USB_PR_BULK; + mod_data.transport_name = "Bulk-only"; + mod_data.protocol_type = USB_SC_SCSI; + mod_data.protocol_name = "Transparent SCSI"; + + /* Some peripheral controllers are known not to be able to + * halt bulk endpoints correctly. If one of them is present, + * disable stalls. + */ + if (gadget_is_at91(fsg->gadget)) + mod_data.can_stall = 0; + + if (mod_data.release == 0xffff) { // Parameter wasn't set + gcnum = usb_gadget_controller_number(fsg->gadget); + if (gcnum >= 0) + mod_data.release = 0x0300 + gcnum; + else { + WARNING(fsg, "controller '%s' not recognized\n", + fsg->gadget->name); + mod_data.release = 0x0399; + } + } + + prot = simple_strtol(mod_data.protocol_parm, NULL, 0); + +#ifdef CONFIG_USB_FILE_STORAGE_TEST + if (strnicmp(mod_data.transport_parm, "BBB", 10) == 0) { + ; // Use default setting + } else if (strnicmp(mod_data.transport_parm, "CB", 10) == 0) { + mod_data.transport_type = USB_PR_CB; + mod_data.transport_name = "Control-Bulk"; + } else if (strnicmp(mod_data.transport_parm, "CBI", 10) == 0) { + mod_data.transport_type = USB_PR_CBI; + mod_data.transport_name = "Control-Bulk-Interrupt"; + } else { + ERROR(fsg, "invalid transport: %s\n", mod_data.transport_parm); + return -EINVAL; + } + + if (strnicmp(mod_data.protocol_parm, "SCSI", 10) == 0 || + prot == USB_SC_SCSI) { + ; // Use default setting + } else if (strnicmp(mod_data.protocol_parm, "RBC", 10) == 0 || + prot == USB_SC_RBC) { + mod_data.protocol_type = USB_SC_RBC; + mod_data.protocol_name = "RBC"; + } else if (strnicmp(mod_data.protocol_parm, "8020", 4) == 0 || + strnicmp(mod_data.protocol_parm, "ATAPI", 10) == 0 || + prot == USB_SC_8020) { + mod_data.protocol_type = USB_SC_8020; + mod_data.protocol_name = "8020i (ATAPI)"; + } else if (strnicmp(mod_data.protocol_parm, "QIC", 3) == 0 || + prot == USB_SC_QIC) { + mod_data.protocol_type = USB_SC_QIC; + mod_data.protocol_name = "QIC-157"; + } else if (strnicmp(mod_data.protocol_parm, "UFI", 10) == 0 || + prot == USB_SC_UFI) { + mod_data.protocol_type = USB_SC_UFI; + mod_data.protocol_name = "UFI"; + } else if (strnicmp(mod_data.protocol_parm, "8070", 4) == 0 || + prot == USB_SC_8070) { + mod_data.protocol_type = USB_SC_8070; + mod_data.protocol_name = "8070i"; + } else { + ERROR(fsg, "invalid protocol: %s\n", mod_data.protocol_parm); + return -EINVAL; + } + + mod_data.buflen &= PAGE_CACHE_MASK; + if (mod_data.buflen <= 0) { + ERROR(fsg, "invalid buflen\n"); + return -ETOOSMALL; + } + +#endif /* CONFIG_USB_FILE_STORAGE_TEST */ + + /* Serial string handling. + * On a real device, the serial string would be loaded + * from permanent storage. */ + if (mod_data.serial) { + const char *ch; + unsigned len = 0; + + /* Sanity check : + * The CB[I] specification limits the serial string to + * 12 uppercase hexadecimal characters. + * BBB need at least 12 uppercase hexadecimal characters, + * with a maximum of 126. */ + for (ch = mod_data.serial; *ch; ++ch) { + ++len; + if ((*ch < '0' || *ch > '9') && + (*ch < 'A' || *ch > 'F')) { /* not uppercase hex */ + WARNING(fsg, + "Invalid serial string character: %c\n", + *ch); + goto no_serial; + } + } + if (len > 126 || + (mod_data.transport_type == USB_PR_BULK && len < 12) || + (mod_data.transport_type != USB_PR_BULK && len > 12)) { + WARNING(fsg, "Invalid serial string length!\n"); + goto no_serial; + } + fsg_strings[FSG_STRING_SERIAL - 1].s = mod_data.serial; + } else { + WARNING(fsg, "No serial-number string provided!\n"); + no_serial: + device_desc.iSerialNumber = 0; + } + + return 0; +} + + +static int __init fsg_bind(struct usb_gadget *gadget) +{ + struct fsg_dev *fsg = the_fsg; + int rc; + int i; + struct fsg_lun *curlun; + struct usb_ep *ep; + struct usb_request *req; + char *pathbuf, *p; + + fsg->gadget = gadget; + set_gadget_data(gadget, fsg); + fsg->ep0 = gadget->ep0; + fsg->ep0->driver_data = fsg; + + if ((rc = check_parameters(fsg)) != 0) + goto out; + + if (mod_data.removable) { // Enable the store_xxx attributes + dev_attr_file.attr.mode = 0644; + dev_attr_file.store = fsg_store_file; + if (!mod_data.cdrom) { + dev_attr_ro.attr.mode = 0644; + dev_attr_ro.store = fsg_store_ro; + } + } + + /* Only for removable media? */ + dev_attr_nofua.attr.mode = 0644; + dev_attr_nofua.store = fsg_store_nofua; + + /* Find out how many LUNs there should be */ + i = mod_data.nluns; + if (i == 0) + i = max(mod_data.num_filenames, 1u); + if (i > FSG_MAX_LUNS) { + ERROR(fsg, "invalid number of LUNs: %d\n", i); + rc = -EINVAL; + goto out; + } + + /* Create the LUNs, open their backing files, and register the + * LUN devices in sysfs. */ + fsg->luns = kzalloc(i * sizeof(struct fsg_lun), GFP_KERNEL); + if (!fsg->luns) { + rc = -ENOMEM; + goto out; + } + fsg->nluns = i; + + for (i = 0; i < fsg->nluns; ++i) { + curlun = &fsg->luns[i]; + curlun->cdrom = !!mod_data.cdrom; + curlun->ro = mod_data.cdrom || mod_data.ro[i]; + curlun->initially_ro = curlun->ro; + curlun->removable = mod_data.removable; + curlun->nofua = mod_data.nofua[i]; + curlun->dev.release = lun_release; + curlun->dev.parent = &gadget->dev; + curlun->dev.driver = &fsg_driver.driver; + dev_set_drvdata(&curlun->dev, &fsg->filesem); + dev_set_name(&curlun->dev,"%s-lun%d", + dev_name(&gadget->dev), i); + + kref_get(&fsg->ref); + rc = device_register(&curlun->dev); + if (rc) { + INFO(fsg, "failed to register LUN%d: %d\n", i, rc); + put_device(&curlun->dev); + goto out; + } + curlun->registered = 1; + + rc = device_create_file(&curlun->dev, &dev_attr_ro); + if (rc) + goto out; + rc = device_create_file(&curlun->dev, &dev_attr_nofua); + if (rc) + goto out; + rc = device_create_file(&curlun->dev, &dev_attr_file); + if (rc) + goto out; + + if (mod_data.file[i] && *mod_data.file[i]) { + rc = fsg_lun_open(curlun, mod_data.file[i]); + if (rc) + goto out; + } else if (!mod_data.removable) { + ERROR(fsg, "no file given for LUN%d\n", i); + rc = -EINVAL; + goto out; + } + } + + /* Find all the endpoints we will use */ + usb_ep_autoconfig_reset(gadget); + ep = usb_ep_autoconfig(gadget, &fsg_fs_bulk_in_desc); + if (!ep) + goto autoconf_fail; + ep->driver_data = fsg; // claim the endpoint + fsg->bulk_in = ep; + + ep = usb_ep_autoconfig(gadget, &fsg_fs_bulk_out_desc); + if (!ep) + goto autoconf_fail; + ep->driver_data = fsg; // claim the endpoint + fsg->bulk_out = ep; + + if (transport_is_cbi()) { + ep = usb_ep_autoconfig(gadget, &fsg_fs_intr_in_desc); + if (!ep) + goto autoconf_fail; + ep->driver_data = fsg; // claim the endpoint + fsg->intr_in = ep; + } + + /* Fix up the descriptors */ + device_desc.idVendor = cpu_to_le16(mod_data.vendor); + device_desc.idProduct = cpu_to_le16(mod_data.product); + device_desc.bcdDevice = cpu_to_le16(mod_data.release); + + i = (transport_is_cbi() ? 3 : 2); // Number of endpoints + fsg_intf_desc.bNumEndpoints = i; + fsg_intf_desc.bInterfaceSubClass = mod_data.protocol_type; + fsg_intf_desc.bInterfaceProtocol = mod_data.transport_type; + fsg_fs_function[i + FSG_FS_FUNCTION_PRE_EP_ENTRIES] = NULL; + + if (gadget_is_dualspeed(gadget)) { + fsg_hs_function[i + FSG_HS_FUNCTION_PRE_EP_ENTRIES] = NULL; + + /* Assume endpoint addresses are the same for both speeds */ + fsg_hs_bulk_in_desc.bEndpointAddress = + fsg_fs_bulk_in_desc.bEndpointAddress; + fsg_hs_bulk_out_desc.bEndpointAddress = + fsg_fs_bulk_out_desc.bEndpointAddress; + fsg_hs_intr_in_desc.bEndpointAddress = + fsg_fs_intr_in_desc.bEndpointAddress; + } + + if (gadget_is_superspeed(gadget)) { + unsigned max_burst; + + fsg_ss_function[i + FSG_SS_FUNCTION_PRE_EP_ENTRIES] = NULL; + + /* Calculate bMaxBurst, we know packet size is 1024 */ + max_burst = min_t(unsigned, mod_data.buflen / 1024, 15); + + /* Assume endpoint addresses are the same for both speeds */ + fsg_ss_bulk_in_desc.bEndpointAddress = + fsg_fs_bulk_in_desc.bEndpointAddress; + fsg_ss_bulk_in_comp_desc.bMaxBurst = max_burst; + + fsg_ss_bulk_out_desc.bEndpointAddress = + fsg_fs_bulk_out_desc.bEndpointAddress; + fsg_ss_bulk_out_comp_desc.bMaxBurst = max_burst; + } + + if (gadget_is_otg(gadget)) + fsg_otg_desc.bmAttributes |= USB_OTG_HNP; + + rc = -ENOMEM; + + /* Allocate the request and buffer for endpoint 0 */ + fsg->ep0req = req = usb_ep_alloc_request(fsg->ep0, GFP_KERNEL); + if (!req) + goto out; + req->buf = kmalloc(EP0_BUFSIZE, GFP_KERNEL); + if (!req->buf) + goto out; + req->complete = ep0_complete; + + /* Allocate the data buffers */ + for (i = 0; i < fsg_num_buffers; ++i) { + struct fsg_buffhd *bh = &fsg->buffhds[i]; + + /* Allocate for the bulk-in endpoint. We assume that + * the buffer will also work with the bulk-out (and + * interrupt-in) endpoint. */ + bh->buf = kmalloc(mod_data.buflen, GFP_KERNEL); + if (!bh->buf) + goto out; + bh->next = bh + 1; + } + fsg->buffhds[fsg_num_buffers - 1].next = &fsg->buffhds[0]; + + /* This should reflect the actual gadget power source */ + usb_gadget_set_selfpowered(gadget); + + snprintf(fsg_string_manufacturer, sizeof fsg_string_manufacturer, + "%s %s with %s", + init_utsname()->sysname, init_utsname()->release, + gadget->name); + + fsg->thread_task = kthread_create(fsg_main_thread, fsg, + "file-storage-gadget"); + if (IS_ERR(fsg->thread_task)) { + rc = PTR_ERR(fsg->thread_task); + goto out; + } + + INFO(fsg, DRIVER_DESC ", version: " DRIVER_VERSION "\n"); + INFO(fsg, "NOTE: This driver is deprecated. " + "Consider using g_mass_storage instead.\n"); + INFO(fsg, "Number of LUNs=%d\n", fsg->nluns); + + pathbuf = kmalloc(PATH_MAX, GFP_KERNEL); + for (i = 0; i < fsg->nluns; ++i) { + curlun = &fsg->luns[i]; + if (fsg_lun_is_open(curlun)) { + p = NULL; + if (pathbuf) { + p = d_path(&curlun->filp->f_path, + pathbuf, PATH_MAX); + if (IS_ERR(p)) + p = NULL; + } + LINFO(curlun, "ro=%d, nofua=%d, file: %s\n", + curlun->ro, curlun->nofua, (p ? p : "(error)")); + } + } + kfree(pathbuf); + + DBG(fsg, "transport=%s (x%02x)\n", + mod_data.transport_name, mod_data.transport_type); + DBG(fsg, "protocol=%s (x%02x)\n", + mod_data.protocol_name, mod_data.protocol_type); + DBG(fsg, "VendorID=x%04x, ProductID=x%04x, Release=x%04x\n", + mod_data.vendor, mod_data.product, mod_data.release); + DBG(fsg, "removable=%d, stall=%d, cdrom=%d, buflen=%u\n", + mod_data.removable, mod_data.can_stall, + mod_data.cdrom, mod_data.buflen); + DBG(fsg, "I/O thread pid: %d\n", task_pid_nr(fsg->thread_task)); + + set_bit(REGISTERED, &fsg->atomic_bitflags); + + /* Tell the thread to start working */ + wake_up_process(fsg->thread_task); + return 0; + +autoconf_fail: + ERROR(fsg, "unable to autoconfigure all endpoints\n"); + rc = -ENOTSUPP; + +out: + fsg->state = FSG_STATE_TERMINATED; // The thread is dead + fsg_unbind(gadget); + complete(&fsg->thread_notifier); + return rc; +} + + +/*-------------------------------------------------------------------------*/ + +static void fsg_suspend(struct usb_gadget *gadget) +{ + struct fsg_dev *fsg = get_gadget_data(gadget); + + DBG(fsg, "suspend\n"); + set_bit(SUSPENDED, &fsg->atomic_bitflags); +} + +static void fsg_resume(struct usb_gadget *gadget) +{ + struct fsg_dev *fsg = get_gadget_data(gadget); + + DBG(fsg, "resume\n"); + clear_bit(SUSPENDED, &fsg->atomic_bitflags); +} + + +/*-------------------------------------------------------------------------*/ + +static struct usb_gadget_driver fsg_driver = { + .max_speed = USB_SPEED_SUPER, + .function = (char *) fsg_string_product, + .unbind = fsg_unbind, + .disconnect = fsg_disconnect, + .setup = fsg_setup, + .suspend = fsg_suspend, + .resume = fsg_resume, + + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + // .release = ... + // .suspend = ... + // .resume = ... + }, +}; + + +static int __init fsg_alloc(void) +{ + struct fsg_dev *fsg; + + fsg = kzalloc(sizeof *fsg + + fsg_num_buffers * sizeof *(fsg->buffhds), GFP_KERNEL); + + if (!fsg) + return -ENOMEM; + spin_lock_init(&fsg->lock); + init_rwsem(&fsg->filesem); + kref_init(&fsg->ref); + init_completion(&fsg->thread_notifier); + + the_fsg = fsg; + return 0; +} + + +static int __init fsg_init(void) +{ + int rc; + struct fsg_dev *fsg; + + rc = fsg_num_buffers_validate(); + if (rc != 0) + return rc; + + if ((rc = fsg_alloc()) != 0) + return rc; + fsg = the_fsg; + if ((rc = usb_gadget_probe_driver(&fsg_driver, fsg_bind)) != 0) + kref_put(&fsg->ref, fsg_release); + return rc; +} +module_init(fsg_init); + + +static void __exit fsg_cleanup(void) +{ + struct fsg_dev *fsg = the_fsg; + + /* Unregister the driver iff the thread hasn't already done so */ + if (test_and_clear_bit(REGISTERED, &fsg->atomic_bitflags)) + usb_gadget_unregister_driver(&fsg_driver); + + /* Wait for the thread to finish up */ + wait_for_completion(&fsg->thread_notifier); + + kref_put(&fsg->ref, fsg_release); +} +module_exit(fsg_cleanup); diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index d011d6c753edfc..9455a3c26e7c3d 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -678,6 +678,16 @@ config USB_RENESAS_USBHS_HCD To compile this driver as a module, choose M here: the module will be called renesas-usbhs. +config USB_DWCOTG + bool "Synopsis DWC host support" + depends on USB=y && (FIQ || ARM64) + help + The Synopsis DWC controller is a dual-role + host/peripheral/OTG ("On The Go") USB controllers. + + Enable this option to support this IP in host controller mode. + If unsure, say N. + config USB_HCD_BCMA tristate "BCMA usb host driver" depends on BCMA diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index be4e5245c52fe9..929af76224d526 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -77,6 +77,7 @@ obj-$(CONFIG_USB_XHCI_TEGRA) += xhci-tegra.o obj-$(CONFIG_USB_SL811_HCD) += sl811-hcd.o obj-$(CONFIG_USB_SL811_CS) += sl811_cs.o obj-$(CONFIG_USB_R8A66597_HCD) += r8a66597-hcd.o +obj-$(CONFIG_USB_DWCOTG) += dwc_otg/ dwc_common_port/ obj-$(CONFIG_USB_FSL_USB2) += fsl-mph-dr-of.o obj-$(CONFIG_USB_EHCI_FSL) += fsl-mph-dr-of.o obj-$(CONFIG_USB_EHCI_FSL) += ehci-fsl.o diff --git a/drivers/usb/host/dwc_common_port/Makefile b/drivers/usb/host/dwc_common_port/Makefile new file mode 100644 index 00000000000000..f10d466d1aea86 --- /dev/null +++ b/drivers/usb/host/dwc_common_port/Makefile @@ -0,0 +1,58 @@ +# +# Makefile for DWC_common library +# + +ifneq ($(KERNELRELEASE),) + +ccflags-y += -DDWC_LINUX +#ccflags-y += -DDEBUG +#ccflags-y += -DDWC_DEBUG_REGS +#ccflags-y += -DDWC_DEBUG_MEMORY + +ccflags-y += -DDWC_LIBMODULE +ccflags-y += -DDWC_CCLIB +#ccflags-y += -DDWC_CRYPTOLIB +ccflags-y += -DDWC_NOTIFYLIB +ccflags-y += -DDWC_UTFLIB + +obj-$(CONFIG_USB_DWCOTG) += dwc_common_port_lib.o +dwc_common_port_lib-objs := dwc_cc.o dwc_modpow.o dwc_dh.o \ + dwc_crypto.o dwc_notifier.o \ + dwc_common_linux.o dwc_mem.o + +kernrelwd := $(subst ., ,$(KERNELRELEASE)) +kernrel3 := $(word 1,$(kernrelwd)).$(word 2,$(kernrelwd)).$(word 3,$(kernrelwd)) + +ifneq ($(kernrel3),2.6.20) +# grayg - I only know that we use ccflags-y in 2.6.31 actually +ccflags-y += $(CPPFLAGS) +endif + +else + +#ifeq ($(KDIR),) +#$(error Must give "KDIR=/path/to/kernel/source" on command line or in environment) +#endif + +ifeq ($(ARCH),) +$(error Must give "ARCH=<arch>" on command line or in environment. Also, if \ + cross-compiling, must give "CROSS_COMPILE=/path/to/compiler/plus/tool-prefix-") +endif + +ifeq ($(DOXYGEN),) +DOXYGEN := doxygen +endif + +default: + $(MAKE) -C$(KDIR) M=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules + +docs: $(wildcard *.[hc]) doc/doxygen.cfg + $(DOXYGEN) doc/doxygen.cfg + +tags: $(wildcard *.[hc]) + $(CTAGS) -e $(wildcard *.[hc]) $(wildcard linux/*.[hc]) $(wildcard $(KDIR)/include/linux/usb*.h) + +endif + +clean: + rm -rf *.o *.ko .*.cmd *.mod.c .*.o.d .*.o.tmp modules.order Module.markers Module.symvers .tmp_versions/ diff --git a/drivers/usb/host/dwc_common_port/Makefile.fbsd b/drivers/usb/host/dwc_common_port/Makefile.fbsd new file mode 100644 index 00000000000000..45db9915b9d31f --- /dev/null +++ b/drivers/usb/host/dwc_common_port/Makefile.fbsd @@ -0,0 +1,17 @@ +CFLAGS += -I/sys/i386/compile/GENERIC -I/sys/i386/include -I/usr/include +CFLAGS += -DDWC_FREEBSD +CFLAGS += -DDEBUG +#CFLAGS += -DDWC_DEBUG_REGS +#CFLAGS += -DDWC_DEBUG_MEMORY + +#CFLAGS += -DDWC_LIBMODULE +#CFLAGS += -DDWC_CCLIB +#CFLAGS += -DDWC_CRYPTOLIB +#CFLAGS += -DDWC_NOTIFYLIB +#CFLAGS += -DDWC_UTFLIB + +KMOD = dwc_common_port_lib +SRCS = dwc_cc.c dwc_modpow.c dwc_dh.c dwc_crypto.c dwc_notifier.c \ + dwc_common_fbsd.c dwc_mem.c + +.include <bsd.kmod.mk> diff --git a/drivers/usb/host/dwc_common_port/Makefile.linux b/drivers/usb/host/dwc_common_port/Makefile.linux new file mode 100644 index 00000000000000..0cef7b461bd508 --- /dev/null +++ b/drivers/usb/host/dwc_common_port/Makefile.linux @@ -0,0 +1,49 @@ +# +# Makefile for DWC_common library +# +ifneq ($(KERNELRELEASE),) + +ccflags-y += -DDWC_LINUX +#ccflags-y += -DDEBUG +#ccflags-y += -DDWC_DEBUG_REGS +#ccflags-y += -DDWC_DEBUG_MEMORY + +ccflags-y += -DDWC_LIBMODULE +ccflags-y += -DDWC_CCLIB +ccflags-y += -DDWC_CRYPTOLIB +ccflags-y += -DDWC_NOTIFYLIB +ccflags-y += -DDWC_UTFLIB + +obj-m := dwc_common_port_lib.o +dwc_common_port_lib-objs := dwc_cc.o dwc_modpow.o dwc_dh.o \ + dwc_crypto.o dwc_notifier.o \ + dwc_common_linux.o dwc_mem.o + +else + +ifeq ($(KDIR),) +$(error Must give "KDIR=/path/to/kernel/source" on command line or in environment) +endif + +ifeq ($(ARCH),) +$(error Must give "ARCH=<arch>" on command line or in environment. Also, if \ + cross-compiling, must give "CROSS_COMPILE=/path/to/compiler/plus/tool-prefix-") +endif + +ifeq ($(DOXYGEN),) +DOXYGEN := doxygen +endif + +default: + $(MAKE) -C$(KDIR) M=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules + +docs: $(wildcard *.[hc]) doc/doxygen.cfg + $(DOXYGEN) doc/doxygen.cfg + +tags: $(wildcard *.[hc]) + $(CTAGS) -e $(wildcard *.[hc]) $(wildcard linux/*.[hc]) $(wildcard $(KDIR)/include/linux/usb*.h) + +endif + +clean: + rm -rf *.o *.ko .*.cmd *.mod.c .*.o.d .*.o.tmp modules.order Module.markers Module.symvers .tmp_versions/ diff --git a/drivers/usb/host/dwc_common_port/changes.txt b/drivers/usb/host/dwc_common_port/changes.txt new file mode 100644 index 00000000000000..f6839f92c2760d --- /dev/null +++ b/drivers/usb/host/dwc_common_port/changes.txt @@ -0,0 +1,174 @@ + +dwc_read_reg32() and friends now take an additional parameter, a pointer to an +IO context struct. The IO context struct should live in an os-dependent struct +in your driver. As an example, the dwc_usb3 driver has an os-dependent struct +named 'os_dep' embedded in the main device struct. So there these calls look +like this: + + dwc_read_reg32(&usb3_dev->os_dep.ioctx, &pcd->dev_global_regs->dcfg); + + dwc_write_reg32(&usb3_dev->os_dep.ioctx, + &pcd->dev_global_regs->dcfg, 0); + +Note that for the existing Linux driver ports, it is not necessary to actually +define the 'ioctx' member in the os-dependent struct. Since Linux does not +require an IO context, its macros for dwc_read_reg32() and friends do not +use the context pointer, so it is optimized away by the compiler. But it is +necessary to add the pointer parameter to all of the call sites, to be ready +for any future ports (such as FreeBSD) which do require an IO context. + + +Similarly, dwc_alloc(), dwc_alloc_atomic(), dwc_strdup(), and dwc_free() now +take an additional parameter, a pointer to a memory context. Examples: + + addr = dwc_alloc(&usb3_dev->os_dep.memctx, size); + + dwc_free(&usb3_dev->os_dep.memctx, addr); + +Again, for the Linux ports, it is not necessary to actually define the memctx +member, but it is necessary to add the pointer parameter to all of the call +sites. + + +Same for dwc_dma_alloc() and dwc_dma_free(). Examples: + + virt_addr = dwc_dma_alloc(&usb3_dev->os_dep.dmactx, size, &phys_addr); + + dwc_dma_free(&usb3_dev->os_dep.dmactx, size, virt_addr, phys_addr); + + +Same for dwc_mutex_alloc() and dwc_mutex_free(). Examples: + + mutex = dwc_mutex_alloc(&usb3_dev->os_dep.mtxctx); + + dwc_mutex_free(&usb3_dev->os_dep.mtxctx, mutex); + + +Same for dwc_spinlock_alloc() and dwc_spinlock_free(). Examples: + + lock = dwc_spinlock_alloc(&usb3_dev->osdep.splctx); + + dwc_spinlock_free(&usb3_dev->osdep.splctx, lock); + + +Same for dwc_timer_alloc(). Example: + + timer = dwc_timer_alloc(&usb3_dev->os_dep.tmrctx, "dwc_usb3_tmr1", + cb_func, cb_data); + + +Same for dwc_waitq_alloc(). Example: + + waitq = dwc_waitq_alloc(&usb3_dev->os_dep.wtqctx); + + +Same for dwc_thread_run(). Example: + + thread = dwc_thread_run(&usb3_dev->os_dep.thdctx, func, + "dwc_usb3_thd1", data); + + +Same for dwc_workq_alloc(). Example: + + workq = dwc_workq_alloc(&usb3_dev->osdep.wkqctx, "dwc_usb3_wkq1"); + + +Same for dwc_task_alloc(). Example: + + task = dwc_task_alloc(&usb3_dev->os_dep.tskctx, "dwc_usb3_tsk1", + cb_func, cb_data); + + +In addition to the context pointer additions, a few core functions have had +other changes made to their parameters: + +The 'flags' parameter to dwc_spinlock_irqsave() and dwc_spinunlock_irqrestore() +has been changed from a uint64_t to a dwc_irqflags_t. + +dwc_thread_should_stop() now takes a 'dwc_thread_t *' parameter, because the +FreeBSD equivalent of that function requires it. + +And, in addition to the context pointer, dwc_task_alloc() also adds a +'char *name' parameter, to be consistent with dwc_thread_run() and +dwc_workq_alloc(), and because the FreeBSD equivalent of that function +requires a unique name. + + +Here is a complete list of the core functions that now take a pointer to a +context as their first parameter: + + dwc_read_reg32 + dwc_read_reg64 + dwc_write_reg32 + dwc_write_reg64 + dwc_modify_reg32 + dwc_modify_reg64 + dwc_alloc + dwc_alloc_atomic + dwc_strdup + dwc_free + dwc_dma_alloc + dwc_dma_free + dwc_mutex_alloc + dwc_mutex_free + dwc_spinlock_alloc + dwc_spinlock_free + dwc_timer_alloc + dwc_waitq_alloc + dwc_thread_run + dwc_workq_alloc + dwc_task_alloc Also adds a 'char *name' as its 2nd parameter + +And here are the core functions that have other changes to their parameters: + + dwc_spinlock_irqsave 'flags' param is now a 'dwc_irqflags_t *' + dwc_spinunlock_irqrestore 'flags' param is now a 'dwc_irqflags_t' + dwc_thread_should_stop Adds a 'dwc_thread_t *' parameter + + + +The changes to the core functions also require some of the other library +functions to change: + + dwc_cc_if_alloc() and dwc_cc_if_free() now take a 'void *memctx' + (for memory allocation) as the 1st param and a 'void *mtxctx' + (for mutex allocation) as the 2nd param. + + dwc_cc_clear(), dwc_cc_add(), dwc_cc_change(), dwc_cc_remove(), + dwc_cc_data_for_save(), and dwc_cc_restore_from_data() now take a + 'void *memctx' as the 1st param. + + dwc_dh_modpow(), dwc_dh_pk(), and dwc_dh_derive_keys() now take a + 'void *memctx' as the 1st param. + + dwc_modpow() now takes a 'void *memctx' as the 1st param. + + dwc_alloc_notification_manager() now takes a 'void *memctx' as the + 1st param and a 'void *wkqctx' (for work queue allocation) as the 2nd + param, and also now returns an integer value that is non-zero if + allocation of its data structures or work queue fails. + + dwc_register_notifier() now takes a 'void *memctx' as the 1st param. + + dwc_memory_debug_start() now takes a 'void *mem_ctx' as the first + param, and also now returns an integer value that is non-zero if + allocation of its data structures fails. + + + +Other miscellaneous changes: + +The DEBUG_MEMORY and DEBUG_REGS #define's have been renamed to +DWC_DEBUG_MEMORY and DWC_DEBUG_REGS. + +The following #define's have been added to allow selectively compiling library +features: + + DWC_CCLIB + DWC_CRYPTOLIB + DWC_NOTIFYLIB + DWC_UTFLIB + +A DWC_LIBMODULE #define has also been added. If this is not defined, then the +module code in dwc_common_linux.c is not compiled in. This allows linking the +library code directly into a driver module, instead of as a standalone module. diff --git a/drivers/usb/host/dwc_common_port/doc/doxygen.cfg b/drivers/usb/host/dwc_common_port/doc/doxygen.cfg new file mode 100644 index 00000000000000..89aa887af29dfc --- /dev/null +++ b/drivers/usb/host/dwc_common_port/doc/doxygen.cfg @@ -0,0 +1,270 @@ +# Doxyfile 1.4.5 + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- +PROJECT_NAME = "Synopsys DWC Portability and Common Library for UWB" +PROJECT_NUMBER = +OUTPUT_DIRECTORY = doc +CREATE_SUBDIRS = NO +OUTPUT_LANGUAGE = English +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the +ALWAYS_DETAILED_SEC = YES +INLINE_INHERITED_MEMB = NO +FULL_PATH_NAMES = NO +STRIP_FROM_PATH = .. +STRIP_FROM_INC_PATH = +SHORT_NAMES = NO +JAVADOC_AUTOBRIEF = YES +MULTILINE_CPP_IS_BRIEF = NO +DETAILS_AT_TOP = YES +INHERIT_DOCS = YES +SEPARATE_MEMBER_PAGES = NO +TAB_SIZE = 8 +ALIASES = +OPTIMIZE_OUTPUT_FOR_C = YES +OPTIMIZE_OUTPUT_JAVA = NO +BUILTIN_STL_SUPPORT = NO +DISTRIBUTE_GROUP_DOC = NO +SUBGROUPING = NO +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- +EXTRACT_ALL = NO +EXTRACT_PRIVATE = NO +EXTRACT_STATIC = YES +EXTRACT_LOCAL_CLASSES = NO +EXTRACT_LOCAL_METHODS = NO +HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_CLASSES = NO +HIDE_FRIEND_COMPOUNDS = NO +HIDE_IN_BODY_DOCS = NO +INTERNAL_DOCS = NO +CASE_SENSE_NAMES = YES +HIDE_SCOPE_NAMES = NO +SHOW_INCLUDE_FILES = NO +INLINE_INFO = YES +SORT_MEMBER_DOCS = NO +SORT_BRIEF_DOCS = NO +SORT_BY_SCOPE_NAME = NO +GENERATE_TODOLIST = YES +GENERATE_TESTLIST = YES +GENERATE_BUGLIST = YES +GENERATE_DEPRECATEDLIST= YES +ENABLED_SECTIONS = +MAX_INITIALIZER_LINES = 30 +SHOW_USED_FILES = YES +SHOW_DIRECTORIES = YES +FILE_VERSION_FILTER = +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- +QUIET = YES +WARNINGS = YES +WARN_IF_UNDOCUMENTED = NO +WARN_IF_DOC_ERROR = YES +WARN_NO_PARAMDOC = YES +WARN_FORMAT = "$file:$line: $text" +WARN_LOGFILE = +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- +INPUT = . +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.d \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.idl \ + *.odl \ + *.cs \ + *.php \ + *.php3 \ + *.inc \ + *.m \ + *.mm \ + *.dox \ + *.py \ + *.C \ + *.CC \ + *.C++ \ + *.II \ + *.I++ \ + *.H \ + *.HH \ + *.H++ \ + *.CS \ + *.PHP \ + *.PHP3 \ + *.M \ + *.MM \ + *.PY +RECURSIVE = NO +EXCLUDE = +EXCLUDE_SYMLINKS = NO +EXCLUDE_PATTERNS = +EXAMPLE_PATH = +EXAMPLE_PATTERNS = * +EXAMPLE_RECURSIVE = NO +IMAGE_PATH = +INPUT_FILTER = +FILTER_PATTERNS = +FILTER_SOURCE_FILES = NO +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- +SOURCE_BROWSER = NO +INLINE_SOURCES = NO +STRIP_CODE_COMMENTS = YES +REFERENCED_BY_RELATION = YES +REFERENCES_RELATION = YES +USE_HTAGS = NO +VERBATIM_HEADERS = NO +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- +ALPHABETICAL_INDEX = NO +COLS_IN_ALPHA_INDEX = 5 +IGNORE_PREFIX = +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- +GENERATE_HTML = YES +HTML_OUTPUT = html +HTML_FILE_EXTENSION = .html +HTML_HEADER = +HTML_FOOTER = +HTML_STYLESHEET = +HTML_ALIGN_MEMBERS = YES +GENERATE_HTMLHELP = NO +CHM_FILE = +HHC_LOCATION = +GENERATE_CHI = NO +BINARY_TOC = NO +TOC_EXPAND = NO +DISABLE_INDEX = NO +ENUM_VALUES_PER_LINE = 4 +GENERATE_TREEVIEW = YES +TREEVIEW_WIDTH = 250 +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- +GENERATE_LATEX = NO +LATEX_OUTPUT = latex +LATEX_CMD_NAME = latex +MAKEINDEX_CMD_NAME = makeindex +COMPACT_LATEX = NO +PAPER_TYPE = a4wide +EXTRA_PACKAGES = +LATEX_HEADER = +PDF_HYPERLINKS = NO +USE_PDFLATEX = NO +LATEX_BATCHMODE = NO +LATEX_HIDE_INDICES = NO +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- +GENERATE_RTF = NO +RTF_OUTPUT = rtf +COMPACT_RTF = NO +RTF_HYPERLINKS = NO +RTF_STYLESHEET_FILE = +RTF_EXTENSIONS_FILE = +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- +GENERATE_MAN = NO +MAN_OUTPUT = man +MAN_EXTENSION = .3 +MAN_LINKS = NO +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- +GENERATE_XML = NO +XML_OUTPUT = xml +XML_SCHEMA = +XML_DTD = +XML_PROGRAMLISTING = YES +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- +GENERATE_AUTOGEN_DEF = NO +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- +GENERATE_PERLMOD = NO +PERLMOD_LATEX = NO +PERLMOD_PRETTY = YES +PERLMOD_MAKEVAR_PREFIX = +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = NO +EXPAND_ONLY_PREDEF = NO +SEARCH_INCLUDES = YES +INCLUDE_PATH = +INCLUDE_FILE_PATTERNS = +PREDEFINED = DEBUG DEBUG_MEMORY +EXPAND_AS_DEFINED = +SKIP_FUNCTION_MACROS = YES +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- +TAGFILES = +GENERATE_TAGFILE = +ALLEXTERNALS = NO +EXTERNAL_GROUPS = YES +PERL_PATH = /usr/bin/perl +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- +CLASS_DIAGRAMS = YES +HIDE_UNDOC_RELATIONS = YES +HAVE_DOT = NO +CLASS_GRAPH = YES +COLLABORATION_GRAPH = YES +GROUP_GRAPHS = YES +UML_LOOK = NO +TEMPLATE_RELATIONS = NO +INCLUDE_GRAPH = NO +INCLUDED_BY_GRAPH = YES +CALL_GRAPH = NO +GRAPHICAL_HIERARCHY = YES +DIRECTORY_GRAPH = YES +DOT_IMAGE_FORMAT = png +DOT_PATH = +DOTFILE_DIRS = +MAX_DOT_GRAPH_DEPTH = 1000 +DOT_TRANSPARENT = NO +DOT_MULTI_TARGETS = NO +GENERATE_LEGEND = YES +DOT_CLEANUP = YES +#--------------------------------------------------------------------------- +# Configuration::additions related to the search engine +#--------------------------------------------------------------------------- +SEARCHENGINE = NO diff --git a/drivers/usb/host/dwc_common_port/dwc_cc.c b/drivers/usb/host/dwc_common_port/dwc_cc.c new file mode 100644 index 00000000000000..5ec2ae28698c15 --- /dev/null +++ b/drivers/usb/host/dwc_common_port/dwc_cc.c @@ -0,0 +1,532 @@ +/* ========================================================================= + * $File: //dwh/usb_iip/dev/software/dwc_common_port_2/dwc_cc.c $ + * $Revision: #4 $ + * $Date: 2010/11/04 $ + * $Change: 1621692 $ + * + * Synopsys Portability Library Software and documentation + * (hereinafter, "Software") is an Unsupported proprietary work of + * Synopsys, Inc. unless otherwise expressly agreed to in writing + * between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product + * under any End User Software License Agreement or Agreement for + * Licensed Product with Synopsys or any supplement thereto. You are + * permitted to use and redistribute this Software in source and binary + * forms, with or without modification, provided that redistributions + * of source code must retain this notice. You may not view, use, + * disclose, copy or distribute this file or any information contained + * herein except pursuant to this license grant from Synopsys. If you + * do not agree with this notice, including the disclaimer below, then + * you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" + * BASIS AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE HEREBY DISCLAIMED. IN NO EVENT SHALL + * SYNOPSYS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================= */ +#ifdef DWC_CCLIB + +#include "dwc_cc.h" + +typedef struct dwc_cc +{ + uint32_t uid; + uint8_t chid[16]; + uint8_t cdid[16]; + uint8_t ck[16]; + uint8_t *name; + uint8_t length; + DWC_CIRCLEQ_ENTRY(dwc_cc) list_entry; +} dwc_cc_t; + +DWC_CIRCLEQ_HEAD(context_list, dwc_cc); + +/** The main structure for CC management. */ +struct dwc_cc_if +{ + dwc_mutex_t *mutex; + char *filename; + + unsigned is_host:1; + + dwc_notifier_t *notifier; + + struct context_list list; +}; + +#ifdef DEBUG +static inline void dump_bytes(char *name, uint8_t *bytes, int len) +{ + int i; + DWC_PRINTF("%s: ", name); + for (i=0; i<len; i++) { + DWC_PRINTF("%02x ", bytes[i]); + } + DWC_PRINTF("\n"); +} +#else +#define dump_bytes(x...) +#endif + +static dwc_cc_t *alloc_cc(void *mem_ctx, uint8_t *name, uint32_t length) +{ + dwc_cc_t *cc = dwc_alloc(mem_ctx, sizeof(dwc_cc_t)); + if (!cc) { + return NULL; + } + DWC_MEMSET(cc, 0, sizeof(dwc_cc_t)); + + if (name) { + cc->length = length; + cc->name = dwc_alloc(mem_ctx, length); + if (!cc->name) { + dwc_free(mem_ctx, cc); + return NULL; + } + + DWC_MEMCPY(cc->name, name, length); + } + + return cc; +} + +static void free_cc(void *mem_ctx, dwc_cc_t *cc) +{ + if (cc->name) { + dwc_free(mem_ctx, cc->name); + } + dwc_free(mem_ctx, cc); +} + +static uint32_t next_uid(dwc_cc_if_t *cc_if) +{ + uint32_t uid = 0; + dwc_cc_t *cc; + DWC_CIRCLEQ_FOREACH(cc, &cc_if->list, list_entry) { + if (cc->uid > uid) { + uid = cc->uid; + } + } + + if (uid == 0) { + uid = 255; + } + + return uid + 1; +} + +static dwc_cc_t *cc_find(dwc_cc_if_t *cc_if, uint32_t uid) +{ + dwc_cc_t *cc; + DWC_CIRCLEQ_FOREACH(cc, &cc_if->list, list_entry) { + if (cc->uid == uid) { + return cc; + } + } + return NULL; +} + +static unsigned int cc_data_size(dwc_cc_if_t *cc_if) +{ + unsigned int size = 0; + dwc_cc_t *cc; + DWC_CIRCLEQ_FOREACH(cc, &cc_if->list, list_entry) { + size += (48 + 1); + if (cc->name) { + size += cc->length; + } + } + return size; +} + +static uint32_t cc_match_chid(dwc_cc_if_t *cc_if, uint8_t *chid) +{ + uint32_t uid = 0; + dwc_cc_t *cc; + + DWC_CIRCLEQ_FOREACH(cc, &cc_if->list, list_entry) { + if (DWC_MEMCMP(cc->chid, chid, 16) == 0) { + uid = cc->uid; + break; + } + } + return uid; +} +static uint32_t cc_match_cdid(dwc_cc_if_t *cc_if, uint8_t *cdid) +{ + uint32_t uid = 0; + dwc_cc_t *cc; + + DWC_CIRCLEQ_FOREACH(cc, &cc_if->list, list_entry) { + if (DWC_MEMCMP(cc->cdid, cdid, 16) == 0) { + uid = cc->uid; + break; + } + } + return uid; +} + +/* Internal cc_add */ +static int32_t cc_add(void *mem_ctx, dwc_cc_if_t *cc_if, uint8_t *chid, + uint8_t *cdid, uint8_t *ck, uint8_t *name, uint8_t length) +{ + dwc_cc_t *cc; + uint32_t uid; + + if (cc_if->is_host) { + uid = cc_match_cdid(cc_if, cdid); + } + else { + uid = cc_match_chid(cc_if, chid); + } + + if (uid) { + DWC_DEBUGC("Replacing previous connection context id=%d name=%p name_len=%d", uid, name, length); + cc = cc_find(cc_if, uid); + } + else { + cc = alloc_cc(mem_ctx, name, length); + cc->uid = next_uid(cc_if); + DWC_CIRCLEQ_INSERT_TAIL(&cc_if->list, cc, list_entry); + } + + DWC_MEMCPY(&(cc->chid[0]), chid, 16); + DWC_MEMCPY(&(cc->cdid[0]), cdid, 16); + DWC_MEMCPY(&(cc->ck[0]), ck, 16); + + DWC_DEBUGC("Added connection context id=%d name=%p name_len=%d", cc->uid, name, length); + dump_bytes("CHID", cc->chid, 16); + dump_bytes("CDID", cc->cdid, 16); + dump_bytes("CK", cc->ck, 16); + return cc->uid; +} + +/* Internal cc_clear */ +static void cc_clear(void *mem_ctx, dwc_cc_if_t *cc_if) +{ + while (!DWC_CIRCLEQ_EMPTY(&cc_if->list)) { + dwc_cc_t *cc = DWC_CIRCLEQ_FIRST(&cc_if->list); + DWC_CIRCLEQ_REMOVE_INIT(&cc_if->list, cc, list_entry); + free_cc(mem_ctx, cc); + } +} + +dwc_cc_if_t *dwc_cc_if_alloc(void *mem_ctx, void *mtx_ctx, + dwc_notifier_t *notifier, unsigned is_host) +{ + dwc_cc_if_t *cc_if = NULL; + + /* Allocate a common_cc_if structure */ + cc_if = dwc_alloc(mem_ctx, sizeof(dwc_cc_if_t)); + + if (!cc_if) + return NULL; + +#if (defined(DWC_LINUX) && defined(CONFIG_DEBUG_MUTEXES)) + DWC_MUTEX_ALLOC_LINUX_DEBUG(cc_if->mutex); +#else + cc_if->mutex = dwc_mutex_alloc(mtx_ctx); +#endif + if (!cc_if->mutex) { + dwc_free(mem_ctx, cc_if); + return NULL; + } + + DWC_CIRCLEQ_INIT(&cc_if->list); + cc_if->is_host = is_host; + cc_if->notifier = notifier; + return cc_if; +} + +void dwc_cc_if_free(void *mem_ctx, void *mtx_ctx, dwc_cc_if_t *cc_if) +{ +#if (defined(DWC_LINUX) && defined(CONFIG_DEBUG_MUTEXES)) + DWC_MUTEX_FREE(cc_if->mutex); +#else + dwc_mutex_free(mtx_ctx, cc_if->mutex); +#endif + cc_clear(mem_ctx, cc_if); + dwc_free(mem_ctx, cc_if); +} + +static void cc_changed(dwc_cc_if_t *cc_if) +{ + if (cc_if->notifier) { + dwc_notify(cc_if->notifier, DWC_CC_LIST_CHANGED_NOTIFICATION, cc_if); + } +} + +void dwc_cc_clear(void *mem_ctx, dwc_cc_if_t *cc_if) +{ + DWC_MUTEX_LOCK(cc_if->mutex); + cc_clear(mem_ctx, cc_if); + DWC_MUTEX_UNLOCK(cc_if->mutex); + cc_changed(cc_if); +} + +int32_t dwc_cc_add(void *mem_ctx, dwc_cc_if_t *cc_if, uint8_t *chid, + uint8_t *cdid, uint8_t *ck, uint8_t *name, uint8_t length) +{ + uint32_t uid; + + DWC_MUTEX_LOCK(cc_if->mutex); + uid = cc_add(mem_ctx, cc_if, chid, cdid, ck, name, length); + DWC_MUTEX_UNLOCK(cc_if->mutex); + cc_changed(cc_if); + + return uid; +} + +void dwc_cc_change(void *mem_ctx, dwc_cc_if_t *cc_if, int32_t id, uint8_t *chid, + uint8_t *cdid, uint8_t *ck, uint8_t *name, uint8_t length) +{ + dwc_cc_t* cc; + + DWC_DEBUGC("Change connection context %d", id); + + DWC_MUTEX_LOCK(cc_if->mutex); + cc = cc_find(cc_if, id); + if (!cc) { + DWC_ERROR("Uid %d not found in cc list\n", id); + DWC_MUTEX_UNLOCK(cc_if->mutex); + return; + } + + if (chid) { + DWC_MEMCPY(&(cc->chid[0]), chid, 16); + } + if (cdid) { + DWC_MEMCPY(&(cc->cdid[0]), cdid, 16); + } + if (ck) { + DWC_MEMCPY(&(cc->ck[0]), ck, 16); + } + + if (name) { + if (cc->name) { + dwc_free(mem_ctx, cc->name); + } + cc->name = dwc_alloc(mem_ctx, length); + if (!cc->name) { + DWC_ERROR("Out of memory in dwc_cc_change()\n"); + DWC_MUTEX_UNLOCK(cc_if->mutex); + return; + } + cc->length = length; + DWC_MEMCPY(cc->name, name, length); + } + + DWC_MUTEX_UNLOCK(cc_if->mutex); + + cc_changed(cc_if); + + DWC_DEBUGC("Changed connection context id=%d\n", id); + dump_bytes("New CHID", cc->chid, 16); + dump_bytes("New CDID", cc->cdid, 16); + dump_bytes("New CK", cc->ck, 16); +} + +void dwc_cc_remove(void *mem_ctx, dwc_cc_if_t *cc_if, int32_t id) +{ + dwc_cc_t *cc; + + DWC_DEBUGC("Removing connection context %d", id); + + DWC_MUTEX_LOCK(cc_if->mutex); + cc = cc_find(cc_if, id); + if (!cc) { + DWC_ERROR("Uid %d not found in cc list\n", id); + DWC_MUTEX_UNLOCK(cc_if->mutex); + return; + } + + DWC_CIRCLEQ_REMOVE_INIT(&cc_if->list, cc, list_entry); + DWC_MUTEX_UNLOCK(cc_if->mutex); + free_cc(mem_ctx, cc); + + cc_changed(cc_if); +} + +uint8_t *dwc_cc_data_for_save(void *mem_ctx, dwc_cc_if_t *cc_if, unsigned int *length) +{ + uint8_t *buf, *x; + uint8_t zero = 0; + dwc_cc_t *cc; + + DWC_MUTEX_LOCK(cc_if->mutex); + *length = cc_data_size(cc_if); + if (!(*length)) { + DWC_MUTEX_UNLOCK(cc_if->mutex); + return NULL; + } + + DWC_DEBUGC("Creating data for saving (length=%d)", *length); + + buf = dwc_alloc(mem_ctx, *length); + if (!buf) { + *length = 0; + DWC_MUTEX_UNLOCK(cc_if->mutex); + return NULL; + } + + x = buf; + DWC_CIRCLEQ_FOREACH(cc, &cc_if->list, list_entry) { + DWC_MEMCPY(x, cc->chid, 16); + x += 16; + DWC_MEMCPY(x, cc->cdid, 16); + x += 16; + DWC_MEMCPY(x, cc->ck, 16); + x += 16; + if (cc->name) { + DWC_MEMCPY(x, &cc->length, 1); + x += 1; + DWC_MEMCPY(x, cc->name, cc->length); + x += cc->length; + } + else { + DWC_MEMCPY(x, &zero, 1); + x += 1; + } + } + DWC_MUTEX_UNLOCK(cc_if->mutex); + + return buf; +} + +void dwc_cc_restore_from_data(void *mem_ctx, dwc_cc_if_t *cc_if, uint8_t *data, uint32_t length) +{ + uint8_t name_length; + uint8_t *name; + uint8_t *chid; + uint8_t *cdid; + uint8_t *ck; + uint32_t i = 0; + + DWC_MUTEX_LOCK(cc_if->mutex); + cc_clear(mem_ctx, cc_if); + + while (i < length) { + chid = &data[i]; + i += 16; + cdid = &data[i]; + i += 16; + ck = &data[i]; + i += 16; + + name_length = data[i]; + i ++; + + if (name_length) { + name = &data[i]; + i += name_length; + } + else { + name = NULL; + } + + /* check to see if we haven't overflown the buffer */ + if (i > length) { + DWC_ERROR("Data format error while attempting to load CCs " + "(nlen=%d, iter=%d, buflen=%d).\n", name_length, i, length); + break; + } + + cc_add(mem_ctx, cc_if, chid, cdid, ck, name, name_length); + } + DWC_MUTEX_UNLOCK(cc_if->mutex); + + cc_changed(cc_if); +} + +uint32_t dwc_cc_match_chid(dwc_cc_if_t *cc_if, uint8_t *chid) +{ + uint32_t uid = 0; + + DWC_MUTEX_LOCK(cc_if->mutex); + uid = cc_match_chid(cc_if, chid); + DWC_MUTEX_UNLOCK(cc_if->mutex); + return uid; +} +uint32_t dwc_cc_match_cdid(dwc_cc_if_t *cc_if, uint8_t *cdid) +{ + uint32_t uid = 0; + + DWC_MUTEX_LOCK(cc_if->mutex); + uid = cc_match_cdid(cc_if, cdid); + DWC_MUTEX_UNLOCK(cc_if->mutex); + return uid; +} + +uint8_t *dwc_cc_ck(dwc_cc_if_t *cc_if, int32_t id) +{ + uint8_t *ck = NULL; + dwc_cc_t *cc; + + DWC_MUTEX_LOCK(cc_if->mutex); + cc = cc_find(cc_if, id); + if (cc) { + ck = cc->ck; + } + DWC_MUTEX_UNLOCK(cc_if->mutex); + + return ck; + +} + +uint8_t *dwc_cc_chid(dwc_cc_if_t *cc_if, int32_t id) +{ + uint8_t *retval = NULL; + dwc_cc_t *cc; + + DWC_MUTEX_LOCK(cc_if->mutex); + cc = cc_find(cc_if, id); + if (cc) { + retval = cc->chid; + } + DWC_MUTEX_UNLOCK(cc_if->mutex); + + return retval; +} + +uint8_t *dwc_cc_cdid(dwc_cc_if_t *cc_if, int32_t id) +{ + uint8_t *retval = NULL; + dwc_cc_t *cc; + + DWC_MUTEX_LOCK(cc_if->mutex); + cc = cc_find(cc_if, id); + if (cc) { + retval = cc->cdid; + } + DWC_MUTEX_UNLOCK(cc_if->mutex); + + return retval; +} + +uint8_t *dwc_cc_name(dwc_cc_if_t *cc_if, int32_t id, uint8_t *length) +{ + uint8_t *retval = NULL; + dwc_cc_t *cc; + + DWC_MUTEX_LOCK(cc_if->mutex); + *length = 0; + cc = cc_find(cc_if, id); + if (cc) { + *length = cc->length; + retval = cc->name; + } + DWC_MUTEX_UNLOCK(cc_if->mutex); + + return retval; +} + +#endif /* DWC_CCLIB */ diff --git a/drivers/usb/host/dwc_common_port/dwc_cc.h b/drivers/usb/host/dwc_common_port/dwc_cc.h new file mode 100644 index 00000000000000..f86e6f21792b99 --- /dev/null +++ b/drivers/usb/host/dwc_common_port/dwc_cc.h @@ -0,0 +1,224 @@ +/* ========================================================================= + * $File: //dwh/usb_iip/dev/software/dwc_common_port_2/dwc_cc.h $ + * $Revision: #4 $ + * $Date: 2010/09/28 $ + * $Change: 1596182 $ + * + * Synopsys Portability Library Software and documentation + * (hereinafter, "Software") is an Unsupported proprietary work of + * Synopsys, Inc. unless otherwise expressly agreed to in writing + * between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product + * under any End User Software License Agreement or Agreement for + * Licensed Product with Synopsys or any supplement thereto. You are + * permitted to use and redistribute this Software in source and binary + * forms, with or without modification, provided that redistributions + * of source code must retain this notice. You may not view, use, + * disclose, copy or distribute this file or any information contained + * herein except pursuant to this license grant from Synopsys. If you + * do not agree with this notice, including the disclaimer below, then + * you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" + * BASIS AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE HEREBY DISCLAIMED. IN NO EVENT SHALL + * SYNOPSYS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================= */ +#ifndef _DWC_CC_H_ +#define _DWC_CC_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/** @file + * + * This file defines the Context Context library. + * + * The main data structure is dwc_cc_if_t which is returned by either the + * dwc_cc_if_alloc function or returned by the module to the user via a provided + * function. The data structure is opaque and should only be manipulated via the + * functions provied in this API. + * + * It manages a list of connection contexts and operations can be performed to + * add, remove, query, search, and change, those contexts. Additionally, + * a dwc_notifier_t object can be requested from the manager so that + * the user can be notified whenever the context list has changed. + */ + +#include "dwc_os.h" +#include "dwc_list.h" +#include "dwc_notifier.h" + + +/* Notifications */ +#define DWC_CC_LIST_CHANGED_NOTIFICATION "DWC_CC_LIST_CHANGED_NOTIFICATION" + +struct dwc_cc_if; +typedef struct dwc_cc_if dwc_cc_if_t; + + +/** @name Connection Context Operations */ +/** @{ */ + +/** This function allocates memory for a dwc_cc_if_t structure, initializes + * fields to default values, and returns a pointer to the structure or NULL on + * error. */ +extern dwc_cc_if_t *dwc_cc_if_alloc(void *mem_ctx, void *mtx_ctx, + dwc_notifier_t *notifier, unsigned is_host); + +/** Frees the memory for the specified CC structure allocated from + * dwc_cc_if_alloc(). */ +extern void dwc_cc_if_free(void *mem_ctx, void *mtx_ctx, dwc_cc_if_t *cc_if); + +/** Removes all contexts from the connection context list */ +extern void dwc_cc_clear(void *mem_ctx, dwc_cc_if_t *cc_if); + +/** Adds a connection context (CHID, CK, CDID, Name) to the connection context list. + * If a CHID already exists, the CK and name are overwritten. Statistics are + * not overwritten. + * + * @param cc_if The cc_if structure. + * @param chid A pointer to the 16-byte CHID. This value will be copied. + * @param ck A pointer to the 16-byte CK. This value will be copied. + * @param cdid A pointer to the 16-byte CDID. This value will be copied. + * @param name An optional host friendly name as defined in the association model + * spec. Must be a UTF16-LE unicode string. Can be NULL to indicated no name. + * @param length The length othe unicode string. + * @return A unique identifier used to refer to this context that is valid for + * as long as this context is still in the list. */ +extern int32_t dwc_cc_add(void *mem_ctx, dwc_cc_if_t *cc_if, uint8_t *chid, + uint8_t *cdid, uint8_t *ck, uint8_t *name, + uint8_t length); + +/** Changes the CHID, CK, CDID, or Name values of a connection context in the + * list, preserving any accumulated statistics. This would typically be called + * if the host decideds to change the context with a SET_CONNECTION request. + * + * @param cc_if The cc_if structure. + * @param id The identifier of the connection context. + * @param chid A pointer to the 16-byte CHID. This value will be copied. NULL + * indicates no change. + * @param cdid A pointer to the 16-byte CDID. This value will be copied. NULL + * indicates no change. + * @param ck A pointer to the 16-byte CK. This value will be copied. NULL + * indicates no change. + * @param name Host friendly name UTF16-LE. NULL indicates no change. + * @param length Length of name. */ +extern void dwc_cc_change(void *mem_ctx, dwc_cc_if_t *cc_if, int32_t id, + uint8_t *chid, uint8_t *cdid, uint8_t *ck, + uint8_t *name, uint8_t length); + +/** Remove the specified connection context. + * @param cc_if The cc_if structure. + * @param id The identifier of the connection context to remove. */ +extern void dwc_cc_remove(void *mem_ctx, dwc_cc_if_t *cc_if, int32_t id); + +/** Get a binary block of data for the connection context list and attributes. + * This data can be used by the OS specific driver to save the connection + * context list into non-volatile memory. + * + * @param cc_if The cc_if structure. + * @param length Return the length of the data buffer. + * @return A pointer to the data buffer. The memory for this buffer should be + * freed with DWC_FREE() after use. */ +extern uint8_t *dwc_cc_data_for_save(void *mem_ctx, dwc_cc_if_t *cc_if, + unsigned int *length); + +/** Restore the connection context list from the binary data that was previously + * returned from a call to dwc_cc_data_for_save. This can be used by the OS specific + * driver to load a connection context list from non-volatile memory. + * + * @param cc_if The cc_if structure. + * @param data The data bytes as returned from dwc_cc_data_for_save. + * @param length The length of the data. */ +extern void dwc_cc_restore_from_data(void *mem_ctx, dwc_cc_if_t *cc_if, + uint8_t *data, unsigned int length); + +/** Find the connection context from the specified CHID. + * + * @param cc_if The cc_if structure. + * @param chid A pointer to the CHID data. + * @return A non-zero identifier of the connection context if the CHID matches. + * Otherwise returns 0. */ +extern uint32_t dwc_cc_match_chid(dwc_cc_if_t *cc_if, uint8_t *chid); + +/** Find the connection context from the specified CDID. + * + * @param cc_if The cc_if structure. + * @param cdid A pointer to the CDID data. + * @return A non-zero identifier of the connection context if the CHID matches. + * Otherwise returns 0. */ +extern uint32_t dwc_cc_match_cdid(dwc_cc_if_t *cc_if, uint8_t *cdid); + +/** Retrieve the CK from the specified connection context. + * + * @param cc_if The cc_if structure. + * @param id The identifier of the connection context. + * @return A pointer to the CK data. The memory does not need to be freed. */ +extern uint8_t *dwc_cc_ck(dwc_cc_if_t *cc_if, int32_t id); + +/** Retrieve the CHID from the specified connection context. + * + * @param cc_if The cc_if structure. + * @param id The identifier of the connection context. + * @return A pointer to the CHID data. The memory does not need to be freed. */ +extern uint8_t *dwc_cc_chid(dwc_cc_if_t *cc_if, int32_t id); + +/** Retrieve the CDID from the specified connection context. + * + * @param cc_if The cc_if structure. + * @param id The identifier of the connection context. + * @return A pointer to the CDID data. The memory does not need to be freed. */ +extern uint8_t *dwc_cc_cdid(dwc_cc_if_t *cc_if, int32_t id); + +extern uint8_t *dwc_cc_name(dwc_cc_if_t *cc_if, int32_t id, uint8_t *length); + +/** Checks a buffer for non-zero. + * @param id A pointer to a 16 byte buffer. + * @return true if the 16 byte value is non-zero. */ +static inline unsigned dwc_assoc_is_not_zero_id(uint8_t *id) { + int i; + for (i=0; i<16; i++) { + if (id[i]) return 1; + } + return 0; +} + +/** Checks a buffer for zero. + * @param id A pointer to a 16 byte buffer. + * @return true if the 16 byte value is zero. */ +static inline unsigned dwc_assoc_is_zero_id(uint8_t *id) { + return !dwc_assoc_is_not_zero_id(id); +} + +/** Prints an ASCII representation for the 16-byte chid, cdid, or ck, into + * buffer. */ +static inline int dwc_print_id_string(char *buffer, uint8_t *id) { + char *ptr = buffer; + int i; + for (i=0; i<16; i++) { + ptr += DWC_SPRINTF(ptr, "%02x", id[i]); + if (i < 15) { + ptr += DWC_SPRINTF(ptr, " "); + } + } + return ptr - buffer; +} + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* _DWC_CC_H_ */ diff --git a/drivers/usb/host/dwc_common_port/dwc_common_fbsd.c b/drivers/usb/host/dwc_common_port/dwc_common_fbsd.c new file mode 100644 index 00000000000000..6dd04b58f8f6c6 --- /dev/null +++ b/drivers/usb/host/dwc_common_port/dwc_common_fbsd.c @@ -0,0 +1,1308 @@ +#include "dwc_os.h" +#include "dwc_list.h" + +#ifdef DWC_CCLIB +# include "dwc_cc.h" +#endif + +#ifdef DWC_CRYPTOLIB +# include "dwc_modpow.h" +# include "dwc_dh.h" +# include "dwc_crypto.h" +#endif + +#ifdef DWC_NOTIFYLIB +# include "dwc_notifier.h" +#endif + +/* OS-Level Implementations */ + +/* This is the FreeBSD 7.0 kernel implementation of the DWC platform library. */ + + +/* MISC */ + +void *DWC_MEMSET(void *dest, uint8_t byte, uint32_t size) +{ + return memset(dest, byte, size); +} + +void *DWC_MEMCPY(void *dest, void const *src, uint32_t size) +{ + return memcpy(dest, src, size); +} + +void *DWC_MEMMOVE(void *dest, void *src, uint32_t size) +{ + bcopy(src, dest, size); + return dest; +} + +int DWC_MEMCMP(void *m1, void *m2, uint32_t size) +{ + return memcmp(m1, m2, size); +} + +int DWC_STRNCMP(void *s1, void *s2, uint32_t size) +{ + return strncmp(s1, s2, size); +} + +int DWC_STRCMP(void *s1, void *s2) +{ + return strcmp(s1, s2); +} + +int DWC_STRLEN(char const *str) +{ + return strlen(str); +} + +char *DWC_STRCPY(char *to, char const *from) +{ + return strcpy(to, from); +} + +char *DWC_STRDUP(char const *str) +{ + int len = DWC_STRLEN(str) + 1; + char *new = DWC_ALLOC_ATOMIC(len); + + if (!new) { + return NULL; + } + + DWC_MEMCPY(new, str, len); + return new; +} + +int DWC_ATOI(char *str, int32_t *value) +{ + char *end = NULL; + + *value = strtol(str, &end, 0); + if (*end == '\0') { + return 0; + } + + return -1; +} + +int DWC_ATOUI(char *str, uint32_t *value) +{ + char *end = NULL; + + *value = strtoul(str, &end, 0); + if (*end == '\0') { + return 0; + } + + return -1; +} + + +#ifdef DWC_UTFLIB +/* From usbstring.c */ + +int DWC_UTF8_TO_UTF16LE(uint8_t const *s, uint16_t *cp, unsigned len) +{ + int count = 0; + u8 c; + u16 uchar; + + /* this insists on correct encodings, though not minimal ones. + * BUT it currently rejects legit 4-byte UTF-8 code points, + * which need surrogate pairs. (Unicode 3.1 can use them.) + */ + while (len != 0 && (c = (u8) *s++) != 0) { + if (unlikely(c & 0x80)) { + // 2-byte sequence: + // 00000yyyyyxxxxxx = 110yyyyy 10xxxxxx + if ((c & 0xe0) == 0xc0) { + uchar = (c & 0x1f) << 6; + + c = (u8) *s++; + if ((c & 0xc0) != 0xc0) + goto fail; + c &= 0x3f; + uchar |= c; + + // 3-byte sequence (most CJKV characters): + // zzzzyyyyyyxxxxxx = 1110zzzz 10yyyyyy 10xxxxxx + } else if ((c & 0xf0) == 0xe0) { + uchar = (c & 0x0f) << 12; + + c = (u8) *s++; + if ((c & 0xc0) != 0xc0) + goto fail; + c &= 0x3f; + uchar |= c << 6; + + c = (u8) *s++; + if ((c & 0xc0) != 0xc0) + goto fail; + c &= 0x3f; + uchar |= c; + + /* no bogus surrogates */ + if (0xd800 <= uchar && uchar <= 0xdfff) + goto fail; + + // 4-byte sequence (surrogate pairs, currently rare): + // 11101110wwwwzzzzyy + 110111yyyyxxxxxx + // = 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx + // (uuuuu = wwww + 1) + // FIXME accept the surrogate code points (only) + } else + goto fail; + } else + uchar = c; + put_unaligned (cpu_to_le16 (uchar), cp++); + count++; + len--; + } + return count; +fail: + return -1; +} + +#endif /* DWC_UTFLIB */ + + +/* dwc_debug.h */ + +dwc_bool_t DWC_IN_IRQ(void) +{ +// return in_irq(); + return 0; +} + +dwc_bool_t DWC_IN_BH(void) +{ +// return in_softirq(); + return 0; +} + +void DWC_VPRINTF(char *format, va_list args) +{ + vprintf(format, args); +} + +int DWC_VSNPRINTF(char *str, int size, char *format, va_list args) +{ + return vsnprintf(str, size, format, args); +} + +void DWC_PRINTF(char *format, ...) +{ + va_list args; + + va_start(args, format); + DWC_VPRINTF(format, args); + va_end(args); +} + +int DWC_SPRINTF(char *buffer, char *format, ...) +{ + int retval; + va_list args; + + va_start(args, format); + retval = vsprintf(buffer, format, args); + va_end(args); + return retval; +} + +int DWC_SNPRINTF(char *buffer, int size, char *format, ...) +{ + int retval; + va_list args; + + va_start(args, format); + retval = vsnprintf(buffer, size, format, args); + va_end(args); + return retval; +} + +void __DWC_WARN(char *format, ...) +{ + va_list args; + + va_start(args, format); + DWC_VPRINTF(format, args); + va_end(args); +} + +void __DWC_ERROR(char *format, ...) +{ + va_list args; + + va_start(args, format); + DWC_VPRINTF(format, args); + va_end(args); +} + +void DWC_EXCEPTION(char *format, ...) +{ + va_list args; + + va_start(args, format); + DWC_VPRINTF(format, args); + va_end(args); +// BUG_ON(1); ??? +} + +#ifdef DEBUG +void __DWC_DEBUG(char *format, ...) +{ + va_list args; + + va_start(args, format); + DWC_VPRINTF(format, args); + va_end(args); +} +#endif + + +/* dwc_mem.h */ + +#if 0 +dwc_pool_t *DWC_DMA_POOL_CREATE(uint32_t size, + uint32_t align, + uint32_t alloc) +{ + struct dma_pool *pool = dma_pool_create("Pool", NULL, + size, align, alloc); + return (dwc_pool_t *)pool; +} + +void DWC_DMA_POOL_DESTROY(dwc_pool_t *pool) +{ + dma_pool_destroy((struct dma_pool *)pool); +} + +void *DWC_DMA_POOL_ALLOC(dwc_pool_t *pool, uint64_t *dma_addr) +{ +// return dma_pool_alloc((struct dma_pool *)pool, GFP_KERNEL, dma_addr); + return dma_pool_alloc((struct dma_pool *)pool, M_WAITOK, dma_addr); +} + +void *DWC_DMA_POOL_ZALLOC(dwc_pool_t *pool, uint64_t *dma_addr) +{ + void *vaddr = DWC_DMA_POOL_ALLOC(pool, dma_addr); + memset(..); +} + +void DWC_DMA_POOL_FREE(dwc_pool_t *pool, void *vaddr, void *daddr) +{ + dma_pool_free(pool, vaddr, daddr); +} +#endif + +static void dmamap_cb(void *arg, bus_dma_segment_t *segs, int nseg, int error) +{ + if (error) + return; + *(bus_addr_t *)arg = segs[0].ds_addr; +} + +void *__DWC_DMA_ALLOC(void *dma_ctx, uint32_t size, dwc_dma_t *dma_addr) +{ + dwc_dmactx_t *dma = (dwc_dmactx_t *)dma_ctx; + int error; + + error = bus_dma_tag_create( +#if __FreeBSD_version >= 700000 + bus_get_dma_tag(dma->dev), /* parent */ +#else + NULL, /* parent */ +#endif + 4, 0, /* alignment, bounds */ + BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ + BUS_SPACE_MAXADDR, /* highaddr */ + NULL, NULL, /* filter, filterarg */ + size, /* maxsize */ + 1, /* nsegments */ + size, /* maxsegsize */ + 0, /* flags */ + NULL, /* lockfunc */ + NULL, /* lockarg */ + &dma->dma_tag); + if (error) { + device_printf(dma->dev, "%s: bus_dma_tag_create failed: %d\n", + __func__, error); + goto fail_0; + } + + error = bus_dmamem_alloc(dma->dma_tag, &dma->dma_vaddr, + BUS_DMA_NOWAIT | BUS_DMA_COHERENT, &dma->dma_map); + if (error) { + device_printf(dma->dev, "%s: bus_dmamem_alloc(%ju) failed: %d\n", + __func__, (uintmax_t)size, error); + goto fail_1; + } + + dma->dma_paddr = 0; + error = bus_dmamap_load(dma->dma_tag, dma->dma_map, dma->dma_vaddr, size, + dmamap_cb, &dma->dma_paddr, BUS_DMA_NOWAIT); + if (error || dma->dma_paddr == 0) { + device_printf(dma->dev, "%s: bus_dmamap_load failed: %d\n", + __func__, error); + goto fail_2; + } + + *dma_addr = dma->dma_paddr; + return dma->dma_vaddr; + +fail_2: + bus_dmamap_unload(dma->dma_tag, dma->dma_map); +fail_1: + bus_dmamem_free(dma->dma_tag, dma->dma_vaddr, dma->dma_map); + bus_dma_tag_destroy(dma->dma_tag); +fail_0: + dma->dma_map = NULL; + dma->dma_tag = NULL; + + return NULL; +} + +void __DWC_DMA_FREE(void *dma_ctx, uint32_t size, void *virt_addr, dwc_dma_t dma_addr) +{ + dwc_dmactx_t *dma = (dwc_dmactx_t *)dma_ctx; + + if (dma->dma_tag == NULL) + return; + if (dma->dma_map != NULL) { + bus_dmamap_sync(dma->dma_tag, dma->dma_map, + BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); + bus_dmamap_unload(dma->dma_tag, dma->dma_map); + bus_dmamem_free(dma->dma_tag, dma->dma_vaddr, dma->dma_map); + dma->dma_map = NULL; + } + + bus_dma_tag_destroy(dma->dma_tag); + dma->dma_tag = NULL; +} + +void *__DWC_ALLOC(void *mem_ctx, uint32_t size) +{ + return malloc(size, M_DEVBUF, M_WAITOK | M_ZERO); +} + +void *__DWC_ALLOC_ATOMIC(void *mem_ctx, uint32_t size) +{ + return malloc(size, M_DEVBUF, M_NOWAIT | M_ZERO); +} + +void __DWC_FREE(void *mem_ctx, void *addr) +{ + free(addr, M_DEVBUF); +} + + +#ifdef DWC_CRYPTOLIB +/* dwc_crypto.h */ + +void DWC_RANDOM_BYTES(uint8_t *buffer, uint32_t length) +{ + get_random_bytes(buffer, length); +} + +int DWC_AES_CBC(uint8_t *message, uint32_t messagelen, uint8_t *key, uint32_t keylen, uint8_t iv[16], uint8_t *out) +{ + struct crypto_blkcipher *tfm; + struct blkcipher_desc desc; + struct scatterlist sgd; + struct scatterlist sgs; + + tfm = crypto_alloc_blkcipher("cbc(aes)", 0, CRYPTO_ALG_ASYNC); + if (tfm == NULL) { + printk("failed to load transform for aes CBC\n"); + return -1; + } + + crypto_blkcipher_setkey(tfm, key, keylen); + crypto_blkcipher_set_iv(tfm, iv, 16); + + sg_init_one(&sgd, out, messagelen); + sg_init_one(&sgs, message, messagelen); + + desc.tfm = tfm; + desc.flags = 0; + + if (crypto_blkcipher_encrypt(&desc, &sgd, &sgs, messagelen)) { + crypto_free_blkcipher(tfm); + DWC_ERROR("AES CBC encryption failed"); + return -1; + } + + crypto_free_blkcipher(tfm); + return 0; +} + +int DWC_SHA256(uint8_t *message, uint32_t len, uint8_t *out) +{ + struct crypto_hash *tfm; + struct hash_desc desc; + struct scatterlist sg; + + tfm = crypto_alloc_hash("sha256", 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(tfm)) { + DWC_ERROR("Failed to load transform for sha256: %ld", PTR_ERR(tfm)); + return 0; + } + desc.tfm = tfm; + desc.flags = 0; + + sg_init_one(&sg, message, len); + crypto_hash_digest(&desc, &sg, len, out); + crypto_free_hash(tfm); + + return 1; +} + +int DWC_HMAC_SHA256(uint8_t *message, uint32_t messagelen, + uint8_t *key, uint32_t keylen, uint8_t *out) +{ + struct crypto_hash *tfm; + struct hash_desc desc; + struct scatterlist sg; + + tfm = crypto_alloc_hash("hmac(sha256)", 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(tfm)) { + DWC_ERROR("Failed to load transform for hmac(sha256): %ld", PTR_ERR(tfm)); + return 0; + } + desc.tfm = tfm; + desc.flags = 0; + + sg_init_one(&sg, message, messagelen); + crypto_hash_setkey(tfm, key, keylen); + crypto_hash_digest(&desc, &sg, messagelen, out); + crypto_free_hash(tfm); + + return 1; +} + +#endif /* DWC_CRYPTOLIB */ + + +/* Byte Ordering Conversions */ + +uint32_t DWC_CPU_TO_LE32(uint32_t *p) +{ +#ifdef __LITTLE_ENDIAN + return *p; +#else + uint8_t *u_p = (uint8_t *)p; + + return (u_p[3] | (u_p[2] << 8) | (u_p[1] << 16) | (u_p[0] << 24)); +#endif +} + +uint32_t DWC_CPU_TO_BE32(uint32_t *p) +{ +#ifdef __BIG_ENDIAN + return *p; +#else + uint8_t *u_p = (uint8_t *)p; + + return (u_p[3] | (u_p[2] << 8) | (u_p[1] << 16) | (u_p[0] << 24)); +#endif +} + +uint32_t DWC_LE32_TO_CPU(uint32_t *p) +{ +#ifdef __LITTLE_ENDIAN + return *p; +#else + uint8_t *u_p = (uint8_t *)p; + + return (u_p[3] | (u_p[2] << 8) | (u_p[1] << 16) | (u_p[0] << 24)); +#endif +} + +uint32_t DWC_BE32_TO_CPU(uint32_t *p) +{ +#ifdef __BIG_ENDIAN + return *p; +#else + uint8_t *u_p = (uint8_t *)p; + + return (u_p[3] | (u_p[2] << 8) | (u_p[1] << 16) | (u_p[0] << 24)); +#endif +} + +uint16_t DWC_CPU_TO_LE16(uint16_t *p) +{ +#ifdef __LITTLE_ENDIAN + return *p; +#else + uint8_t *u_p = (uint8_t *)p; + return (u_p[1] | (u_p[0] << 8)); +#endif +} + +uint16_t DWC_CPU_TO_BE16(uint16_t *p) +{ +#ifdef __BIG_ENDIAN + return *p; +#else + uint8_t *u_p = (uint8_t *)p; + return (u_p[1] | (u_p[0] << 8)); +#endif +} + +uint16_t DWC_LE16_TO_CPU(uint16_t *p) +{ +#ifdef __LITTLE_ENDIAN + return *p; +#else + uint8_t *u_p = (uint8_t *)p; + return (u_p[1] | (u_p[0] << 8)); +#endif +} + +uint16_t DWC_BE16_TO_CPU(uint16_t *p) +{ +#ifdef __BIG_ENDIAN + return *p; +#else + uint8_t *u_p = (uint8_t *)p; + return (u_p[1] | (u_p[0] << 8)); +#endif +} + + +/* Registers */ + +uint32_t DWC_READ_REG32(void *io_ctx, uint32_t volatile *reg) +{ + dwc_ioctx_t *io = (dwc_ioctx_t *)io_ctx; + bus_size_t ior = (bus_size_t)reg; + + return bus_space_read_4(io->iot, io->ioh, ior); +} + +#if 0 +uint64_t DWC_READ_REG64(void *io_ctx, uint64_t volatile *reg) +{ + dwc_ioctx_t *io = (dwc_ioctx_t *)io_ctx; + bus_size_t ior = (bus_size_t)reg; + + return bus_space_read_8(io->iot, io->ioh, ior); +} +#endif + +void DWC_WRITE_REG32(void *io_ctx, uint32_t volatile *reg, uint32_t value) +{ + dwc_ioctx_t *io = (dwc_ioctx_t *)io_ctx; + bus_size_t ior = (bus_size_t)reg; + + bus_space_write_4(io->iot, io->ioh, ior, value); +} + +#if 0 +void DWC_WRITE_REG64(void *io_ctx, uint64_t volatile *reg, uint64_t value) +{ + dwc_ioctx_t *io = (dwc_ioctx_t *)io_ctx; + bus_size_t ior = (bus_size_t)reg; + + bus_space_write_8(io->iot, io->ioh, ior, value); +} +#endif + +void DWC_MODIFY_REG32(void *io_ctx, uint32_t volatile *reg, uint32_t clear_mask, + uint32_t set_mask) +{ + dwc_ioctx_t *io = (dwc_ioctx_t *)io_ctx; + bus_size_t ior = (bus_size_t)reg; + + bus_space_write_4(io->iot, io->ioh, ior, + (bus_space_read_4(io->iot, io->ioh, ior) & + ~clear_mask) | set_mask); +} + +#if 0 +void DWC_MODIFY_REG64(void *io_ctx, uint64_t volatile *reg, uint64_t clear_mask, + uint64_t set_mask) +{ + dwc_ioctx_t *io = (dwc_ioctx_t *)io_ctx; + bus_size_t ior = (bus_size_t)reg; + + bus_space_write_8(io->iot, io->ioh, ior, + (bus_space_read_8(io->iot, io->ioh, ior) & + ~clear_mask) | set_mask); +} +#endif + + +/* Locking */ + +dwc_spinlock_t *DWC_SPINLOCK_ALLOC(void) +{ + struct mtx *sl = DWC_ALLOC(sizeof(*sl)); + + if (!sl) { + DWC_ERROR("Cannot allocate memory for spinlock"); + return NULL; + } + + mtx_init(sl, "dw3spn", NULL, MTX_SPIN); + return (dwc_spinlock_t *)sl; +} + +void DWC_SPINLOCK_FREE(dwc_spinlock_t *lock) +{ + struct mtx *sl = (struct mtx *)lock; + + mtx_destroy(sl); + DWC_FREE(sl); +} + +void DWC_SPINLOCK(dwc_spinlock_t *lock) +{ + mtx_lock_spin((struct mtx *)lock); // ??? +} + +void DWC_SPINUNLOCK(dwc_spinlock_t *lock) +{ + mtx_unlock_spin((struct mtx *)lock); // ??? +} + +void DWC_SPINLOCK_IRQSAVE(dwc_spinlock_t *lock, dwc_irqflags_t *flags) +{ + mtx_lock_spin((struct mtx *)lock); +} + +void DWC_SPINUNLOCK_IRQRESTORE(dwc_spinlock_t *lock, dwc_irqflags_t flags) +{ + mtx_unlock_spin((struct mtx *)lock); +} + +dwc_mutex_t *DWC_MUTEX_ALLOC(void) +{ + struct mtx *m; + dwc_mutex_t *mutex = (dwc_mutex_t *)DWC_ALLOC(sizeof(struct mtx)); + + if (!mutex) { + DWC_ERROR("Cannot allocate memory for mutex"); + return NULL; + } + + m = (struct mtx *)mutex; + mtx_init(m, "dw3mtx", NULL, MTX_DEF); + return mutex; +} + +#if (defined(DWC_LINUX) && defined(CONFIG_DEBUG_MUTEXES)) +#else +void DWC_MUTEX_FREE(dwc_mutex_t *mutex) +{ + mtx_destroy((struct mtx *)mutex); + DWC_FREE(mutex); +} +#endif + +void DWC_MUTEX_LOCK(dwc_mutex_t *mutex) +{ + struct mtx *m = (struct mtx *)mutex; + + mtx_lock(m); +} + +int DWC_MUTEX_TRYLOCK(dwc_mutex_t *mutex) +{ + struct mtx *m = (struct mtx *)mutex; + + return mtx_trylock(m); +} + +void DWC_MUTEX_UNLOCK(dwc_mutex_t *mutex) +{ + struct mtx *m = (struct mtx *)mutex; + + mtx_unlock(m); +} + + +/* Timing */ + +void DWC_UDELAY(uint32_t usecs) +{ + DELAY(usecs); +} + +void DWC_MDELAY(uint32_t msecs) +{ + do { + DELAY(1000); + } while (--msecs); +} + +void DWC_MSLEEP(uint32_t msecs) +{ + struct timeval tv; + + tv.tv_sec = msecs / 1000; + tv.tv_usec = (msecs - tv.tv_sec * 1000) * 1000; + pause("dw3slp", tvtohz(&tv)); +} + +uint32_t DWC_TIME(void) +{ + struct timeval tv; + + microuptime(&tv); // or getmicrouptime? (less precise, but faster) + return tv.tv_sec * 1000 + tv.tv_usec / 1000; +} + + +/* Timers */ + +struct dwc_timer { + struct callout t; + char *name; + dwc_spinlock_t *lock; + dwc_timer_callback_t cb; + void *data; +}; + +dwc_timer_t *DWC_TIMER_ALLOC(char *name, dwc_timer_callback_t cb, void *data) +{ + dwc_timer_t *t = DWC_ALLOC(sizeof(*t)); + + if (!t) { + DWC_ERROR("Cannot allocate memory for timer"); + return NULL; + } + + callout_init(&t->t, 1); + + t->name = DWC_STRDUP(name); + if (!t->name) { + DWC_ERROR("Cannot allocate memory for timer->name"); + goto no_name; + } + + t->lock = DWC_SPINLOCK_ALLOC(); + if (!t->lock) { + DWC_ERROR("Cannot allocate memory for lock"); + goto no_lock; + } + + t->cb = cb; + t->data = data; + + return t; + + no_lock: + DWC_FREE(t->name); + no_name: + DWC_FREE(t); + + return NULL; +} + +void DWC_TIMER_FREE(dwc_timer_t *timer) +{ + callout_stop(&timer->t); + DWC_SPINLOCK_FREE(timer->lock); + DWC_FREE(timer->name); + DWC_FREE(timer); +} + +void DWC_TIMER_SCHEDULE(dwc_timer_t *timer, uint32_t time) +{ + struct timeval tv; + + tv.tv_sec = time / 1000; + tv.tv_usec = (time - tv.tv_sec * 1000) * 1000; + callout_reset(&timer->t, tvtohz(&tv), timer->cb, timer->data); +} + +void DWC_TIMER_CANCEL(dwc_timer_t *timer) +{ + callout_stop(&timer->t); +} + + +/* Wait Queues */ + +struct dwc_waitq { + struct mtx lock; + int abort; +}; + +dwc_waitq_t *DWC_WAITQ_ALLOC(void) +{ + dwc_waitq_t *wq = DWC_ALLOC(sizeof(*wq)); + + if (!wq) { + DWC_ERROR("Cannot allocate memory for waitqueue"); + return NULL; + } + + mtx_init(&wq->lock, "dw3wtq", NULL, MTX_DEF); + wq->abort = 0; + + return wq; +} + +void DWC_WAITQ_FREE(dwc_waitq_t *wq) +{ + mtx_destroy(&wq->lock); + DWC_FREE(wq); +} + +int32_t DWC_WAITQ_WAIT(dwc_waitq_t *wq, dwc_waitq_condition_t cond, void *data) +{ +// intrmask_t ipl; + int result = 0; + + mtx_lock(&wq->lock); +// ipl = splbio(); + + /* Skip the sleep if already aborted or triggered */ + if (!wq->abort && !cond(data)) { +// splx(ipl); + result = msleep(wq, &wq->lock, PCATCH, "dw3wat", 0); // infinite timeout +// ipl = splbio(); + } + + if (result == ERESTART) { // signaled - restart + result = -DWC_E_RESTART; + + } else if (result == EINTR) { // signaled - interrupt + result = -DWC_E_ABORT; + + } else if (wq->abort) { + result = -DWC_E_ABORT; + + } else { + result = 0; + } + + wq->abort = 0; +// splx(ipl); + mtx_unlock(&wq->lock); + return result; +} + +int32_t DWC_WAITQ_WAIT_TIMEOUT(dwc_waitq_t *wq, dwc_waitq_condition_t cond, + void *data, int32_t msecs) +{ + struct timeval tv, tv1, tv2; +// intrmask_t ipl; + int result = 0; + + tv.tv_sec = msecs / 1000; + tv.tv_usec = (msecs - tv.tv_sec * 1000) * 1000; + + mtx_lock(&wq->lock); +// ipl = splbio(); + + /* Skip the sleep if already aborted or triggered */ + if (!wq->abort && !cond(data)) { +// splx(ipl); + getmicrouptime(&tv1); + result = msleep(wq, &wq->lock, PCATCH, "dw3wto", tvtohz(&tv)); + getmicrouptime(&tv2); +// ipl = splbio(); + } + + if (result == 0) { // awoken + if (wq->abort) { + result = -DWC_E_ABORT; + } else { + tv2.tv_usec -= tv1.tv_usec; + if (tv2.tv_usec < 0) { + tv2.tv_usec += 1000000; + tv2.tv_sec--; + } + + tv2.tv_sec -= tv1.tv_sec; + result = tv2.tv_sec * 1000 + tv2.tv_usec / 1000; + result = msecs - result; + if (result <= 0) + result = 1; + } + } else if (result == ERESTART) { // signaled - restart + result = -DWC_E_RESTART; + + } else if (result == EINTR) { // signaled - interrupt + result = -DWC_E_ABORT; + + } else { // timed out + result = -DWC_E_TIMEOUT; + } + + wq->abort = 0; +// splx(ipl); + mtx_unlock(&wq->lock); + return result; +} + +void DWC_WAITQ_TRIGGER(dwc_waitq_t *wq) +{ + wakeup(wq); +} + +void DWC_WAITQ_ABORT(dwc_waitq_t *wq) +{ +// intrmask_t ipl; + + mtx_lock(&wq->lock); +// ipl = splbio(); + wq->abort = 1; + wakeup(wq); +// splx(ipl); + mtx_unlock(&wq->lock); +} + + +/* Threading */ + +struct dwc_thread { + struct proc *proc; + int abort; +}; + +dwc_thread_t *DWC_THREAD_RUN(dwc_thread_function_t func, char *name, void *data) +{ + int retval; + dwc_thread_t *thread = DWC_ALLOC(sizeof(*thread)); + + if (!thread) { + return NULL; + } + + thread->abort = 0; + retval = kthread_create((void (*)(void *))func, data, &thread->proc, + RFPROC | RFNOWAIT, 0, "%s", name); + if (retval) { + DWC_FREE(thread); + return NULL; + } + + return thread; +} + +int DWC_THREAD_STOP(dwc_thread_t *thread) +{ + int retval; + + thread->abort = 1; + retval = tsleep(&thread->abort, 0, "dw3stp", 60 * hz); + + if (retval == 0) { + /* DWC_THREAD_EXIT() will free the thread struct */ + return 0; + } + + /* NOTE: We leak the thread struct if thread doesn't die */ + + if (retval == EWOULDBLOCK) { + return -DWC_E_TIMEOUT; + } + + return -DWC_E_UNKNOWN; +} + +dwc_bool_t DWC_THREAD_SHOULD_STOP(dwc_thread_t *thread) +{ + return thread->abort; +} + +void DWC_THREAD_EXIT(dwc_thread_t *thread) +{ + wakeup(&thread->abort); + DWC_FREE(thread); + kthread_exit(0); +} + + +/* tasklets + - Runs in interrupt context (cannot sleep) + - Each tasklet runs on a single CPU [ How can we ensure this on FreeBSD? Does it matter? ] + - Different tasklets can be running simultaneously on different CPUs [ shouldn't matter ] + */ +struct dwc_tasklet { + struct task t; + dwc_tasklet_callback_t cb; + void *data; +}; + +static void tasklet_callback(void *data, int pending) // what to do with pending ??? +{ + dwc_tasklet_t *task = (dwc_tasklet_t *)data; + + task->cb(task->data); +} + +dwc_tasklet_t *DWC_TASK_ALLOC(char *name, dwc_tasklet_callback_t cb, void *data) +{ + dwc_tasklet_t *task = DWC_ALLOC(sizeof(*task)); + + if (task) { + task->cb = cb; + task->data = data; + TASK_INIT(&task->t, 0, tasklet_callback, task); + } else { + DWC_ERROR("Cannot allocate memory for tasklet"); + } + + return task; +} + +void DWC_TASK_FREE(dwc_tasklet_t *task) +{ + taskqueue_drain(taskqueue_fast, &task->t); // ??? + DWC_FREE(task); +} + +void DWC_TASK_SCHEDULE(dwc_tasklet_t *task) +{ + /* Uses predefined system queue */ + taskqueue_enqueue_fast(taskqueue_fast, &task->t); +} + + +/* workqueues + - Runs in process context (can sleep) + */ +typedef struct work_container { + dwc_work_callback_t cb; + void *data; + dwc_workq_t *wq; + char *name; + int hz; + +#ifdef DEBUG + DWC_CIRCLEQ_ENTRY(work_container) entry; +#endif + struct task task; +} work_container_t; + +#ifdef DEBUG +DWC_CIRCLEQ_HEAD(work_container_queue, work_container); +#endif + +struct dwc_workq { + struct taskqueue *taskq; + dwc_spinlock_t *lock; + dwc_waitq_t *waitq; + int pending; + +#ifdef DEBUG + struct work_container_queue entries; +#endif +}; + +static void do_work(void *data, int pending) // what to do with pending ??? +{ + work_container_t *container = (work_container_t *)data; + dwc_workq_t *wq = container->wq; + dwc_irqflags_t flags; + + if (container->hz) { + pause("dw3wrk", container->hz); + } + + container->cb(container->data); + DWC_DEBUG("Work done: %s, container=%p", container->name, container); + + DWC_SPINLOCK_IRQSAVE(wq->lock, &flags); + +#ifdef DEBUG + DWC_CIRCLEQ_REMOVE(&wq->entries, container, entry); +#endif + if (container->name) + DWC_FREE(container->name); + DWC_FREE(container); + wq->pending--; + DWC_SPINUNLOCK_IRQRESTORE(wq->lock, flags); + DWC_WAITQ_TRIGGER(wq->waitq); +} + +static int work_done(void *data) +{ + dwc_workq_t *workq = (dwc_workq_t *)data; + + return workq->pending == 0; +} + +int DWC_WORKQ_WAIT_WORK_DONE(dwc_workq_t *workq, int timeout) +{ + return DWC_WAITQ_WAIT_TIMEOUT(workq->waitq, work_done, workq, timeout); +} + +dwc_workq_t *DWC_WORKQ_ALLOC(char *name) +{ + dwc_workq_t *wq = DWC_ALLOC(sizeof(*wq)); + + if (!wq) { + DWC_ERROR("Cannot allocate memory for workqueue"); + return NULL; + } + + wq->taskq = taskqueue_create(name, M_NOWAIT, taskqueue_thread_enqueue, &wq->taskq); + if (!wq->taskq) { + DWC_ERROR("Cannot allocate memory for taskqueue"); + goto no_taskq; + } + + wq->pending = 0; + + wq->lock = DWC_SPINLOCK_ALLOC(); + if (!wq->lock) { + DWC_ERROR("Cannot allocate memory for spinlock"); + goto no_lock; + } + + wq->waitq = DWC_WAITQ_ALLOC(); + if (!wq->waitq) { + DWC_ERROR("Cannot allocate memory for waitqueue"); + goto no_waitq; + } + + taskqueue_start_threads(&wq->taskq, 1, PWAIT, "%s taskq", "dw3tsk"); + +#ifdef DEBUG + DWC_CIRCLEQ_INIT(&wq->entries); +#endif + return wq; + + no_waitq: + DWC_SPINLOCK_FREE(wq->lock); + no_lock: + taskqueue_free(wq->taskq); + no_taskq: + DWC_FREE(wq); + + return NULL; +} + +void DWC_WORKQ_FREE(dwc_workq_t *wq) +{ +#ifdef DEBUG + dwc_irqflags_t flags; + + DWC_SPINLOCK_IRQSAVE(wq->lock, &flags); + + if (wq->pending != 0) { + struct work_container *container; + + DWC_ERROR("Destroying work queue with pending work"); + + DWC_CIRCLEQ_FOREACH(container, &wq->entries, entry) { + DWC_ERROR("Work %s still pending", container->name); + } + } + + DWC_SPINUNLOCK_IRQRESTORE(wq->lock, flags); +#endif + DWC_WAITQ_FREE(wq->waitq); + DWC_SPINLOCK_FREE(wq->lock); + taskqueue_free(wq->taskq); + DWC_FREE(wq); +} + +void DWC_WORKQ_SCHEDULE(dwc_workq_t *wq, dwc_work_callback_t cb, void *data, + char *format, ...) +{ + dwc_irqflags_t flags; + work_container_t *container; + static char name[128]; + va_list args; + + va_start(args, format); + DWC_VSNPRINTF(name, 128, format, args); + va_end(args); + + DWC_SPINLOCK_IRQSAVE(wq->lock, &flags); + wq->pending++; + DWC_SPINUNLOCK_IRQRESTORE(wq->lock, flags); + DWC_WAITQ_TRIGGER(wq->waitq); + + container = DWC_ALLOC_ATOMIC(sizeof(*container)); + if (!container) { + DWC_ERROR("Cannot allocate memory for container"); + return; + } + + container->name = DWC_STRDUP(name); + if (!container->name) { + DWC_ERROR("Cannot allocate memory for container->name"); + DWC_FREE(container); + return; + } + + container->cb = cb; + container->data = data; + container->wq = wq; + container->hz = 0; + + DWC_DEBUG("Queueing work: %s, container=%p", container->name, container); + + TASK_INIT(&container->task, 0, do_work, container); + +#ifdef DEBUG + DWC_CIRCLEQ_INSERT_TAIL(&wq->entries, container, entry); +#endif + taskqueue_enqueue_fast(wq->taskq, &container->task); +} + +void DWC_WORKQ_SCHEDULE_DELAYED(dwc_workq_t *wq, dwc_work_callback_t cb, + void *data, uint32_t time, char *format, ...) +{ + dwc_irqflags_t flags; + work_container_t *container; + static char name[128]; + struct timeval tv; + va_list args; + + va_start(args, format); + DWC_VSNPRINTF(name, 128, format, args); + va_end(args); + + DWC_SPINLOCK_IRQSAVE(wq->lock, &flags); + wq->pending++; + DWC_SPINUNLOCK_IRQRESTORE(wq->lock, flags); + DWC_WAITQ_TRIGGER(wq->waitq); + + container = DWC_ALLOC_ATOMIC(sizeof(*container)); + if (!container) { + DWC_ERROR("Cannot allocate memory for container"); + return; + } + + container->name = DWC_STRDUP(name); + if (!container->name) { + DWC_ERROR("Cannot allocate memory for container->name"); + DWC_FREE(container); + return; + } + + container->cb = cb; + container->data = data; + container->wq = wq; + + tv.tv_sec = time / 1000; + tv.tv_usec = (time - tv.tv_sec * 1000) * 1000; + container->hz = tvtohz(&tv); + + DWC_DEBUG("Queueing work: %s, container=%p", container->name, container); + + TASK_INIT(&container->task, 0, do_work, container); + +#ifdef DEBUG + DWC_CIRCLEQ_INSERT_TAIL(&wq->entries, container, entry); +#endif + taskqueue_enqueue_fast(wq->taskq, &container->task); +} + +int DWC_WORKQ_PENDING(dwc_workq_t *wq) +{ + return wq->pending; +} diff --git a/drivers/usb/host/dwc_common_port/dwc_common_linux.c b/drivers/usb/host/dwc_common_port/dwc_common_linux.c new file mode 100644 index 00000000000000..673b3b75755d8c --- /dev/null +++ b/drivers/usb/host/dwc_common_port/dwc_common_linux.c @@ -0,0 +1,1409 @@ +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/kthread.h> + +#ifdef DWC_CCLIB +# include "dwc_cc.h" +#endif + +#ifdef DWC_CRYPTOLIB +# include "dwc_modpow.h" +# include "dwc_dh.h" +# include "dwc_crypto.h" +#endif + +#ifdef DWC_NOTIFYLIB +# include "dwc_notifier.h" +#endif + +/* OS-Level Implementations */ + +/* This is the Linux kernel implementation of the DWC platform library. */ +#include <linux/moduleparam.h> +#include <linux/ctype.h> +#include <linux/crypto.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/cdev.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/jiffies.h> +#include <linux/list.h> +#include <linux/pci.h> +#include <linux/random.h> +#include <linux/scatterlist.h> +#include <linux/slab.h> +#include <linux/stat.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/usb.h> + +#include <linux/version.h> + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24) +# include <linux/usb/gadget.h> +#else +# include <linux/usb_gadget.h> +#endif + +#include <asm/io.h> +#include <asm/page.h> +#include <asm/uaccess.h> +#include <linux/unaligned.h> + +#include "dwc_os.h" +#include "dwc_list.h" + + +/* MISC */ + +void *DWC_MEMSET(void *dest, uint8_t byte, uint32_t size) +{ + return memset(dest, byte, size); +} + +void *DWC_MEMCPY(void *dest, void const *src, uint32_t size) +{ + return memcpy(dest, src, size); +} + +void *DWC_MEMMOVE(void *dest, void *src, uint32_t size) +{ + return memmove(dest, src, size); +} + +int DWC_MEMCMP(void *m1, void *m2, uint32_t size) +{ + return memcmp(m1, m2, size); +} + +int DWC_STRNCMP(void *s1, void *s2, uint32_t size) +{ + return strncmp(s1, s2, size); +} + +int DWC_STRCMP(void *s1, void *s2) +{ + return strcmp(s1, s2); +} + +int DWC_STRLEN(char const *str) +{ + return strlen(str); +} + +char *DWC_STRCPY(char *to, char const *from) +{ + return strcpy(to, from); +} + +char *DWC_STRDUP(char const *str) +{ + int len = DWC_STRLEN(str) + 1; + char *new = DWC_ALLOC_ATOMIC(len); + + if (!new) { + return NULL; + } + + DWC_MEMCPY(new, str, len); + return new; +} + +int DWC_ATOI(const char *str, int32_t *value) +{ + char *end = NULL; + + *value = simple_strtol(str, &end, 0); + if (*end == '\0') { + return 0; + } + + return -1; +} + +int DWC_ATOUI(const char *str, uint32_t *value) +{ + char *end = NULL; + + *value = simple_strtoul(str, &end, 0); + if (*end == '\0') { + return 0; + } + + return -1; +} + + +#ifdef DWC_UTFLIB +/* From usbstring.c */ + +int DWC_UTF8_TO_UTF16LE(uint8_t const *s, uint16_t *cp, unsigned len) +{ + int count = 0; + u8 c; + u16 uchar; + + /* this insists on correct encodings, though not minimal ones. + * BUT it currently rejects legit 4-byte UTF-8 code points, + * which need surrogate pairs. (Unicode 3.1 can use them.) + */ + while (len != 0 && (c = (u8) *s++) != 0) { + if (unlikely(c & 0x80)) { + // 2-byte sequence: + // 00000yyyyyxxxxxx = 110yyyyy 10xxxxxx + if ((c & 0xe0) == 0xc0) { + uchar = (c & 0x1f) << 6; + + c = (u8) *s++; + if ((c & 0xc0) != 0xc0) + goto fail; + c &= 0x3f; + uchar |= c; + + // 3-byte sequence (most CJKV characters): + // zzzzyyyyyyxxxxxx = 1110zzzz 10yyyyyy 10xxxxxx + } else if ((c & 0xf0) == 0xe0) { + uchar = (c & 0x0f) << 12; + + c = (u8) *s++; + if ((c & 0xc0) != 0xc0) + goto fail; + c &= 0x3f; + uchar |= c << 6; + + c = (u8) *s++; + if ((c & 0xc0) != 0xc0) + goto fail; + c &= 0x3f; + uchar |= c; + + /* no bogus surrogates */ + if (0xd800 <= uchar && uchar <= 0xdfff) + goto fail; + + // 4-byte sequence (surrogate pairs, currently rare): + // 11101110wwwwzzzzyy + 110111yyyyxxxxxx + // = 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx + // (uuuuu = wwww + 1) + // FIXME accept the surrogate code points (only) + } else + goto fail; + } else + uchar = c; + put_unaligned (cpu_to_le16 (uchar), cp++); + count++; + len--; + } + return count; +fail: + return -1; +} +#endif /* DWC_UTFLIB */ + + +/* dwc_debug.h */ + +dwc_bool_t DWC_IN_IRQ(void) +{ + return in_irq(); +} + +dwc_bool_t DWC_IN_BH(void) +{ + return in_softirq(); +} + +void DWC_VPRINTF(char *format, va_list args) +{ + vprintk(format, args); +} + +int DWC_VSNPRINTF(char *str, int size, char *format, va_list args) +{ + return vsnprintf(str, size, format, args); +} + +void DWC_PRINTF(char *format, ...) +{ + va_list args; + + va_start(args, format); + DWC_VPRINTF(format, args); + va_end(args); +} + +int DWC_SPRINTF(char *buffer, char *format, ...) +{ + int retval; + va_list args; + + va_start(args, format); + retval = vsprintf(buffer, format, args); + va_end(args); + return retval; +} + +int DWC_SNPRINTF(char *buffer, int size, char *format, ...) +{ + int retval; + va_list args; + + va_start(args, format); + retval = vsnprintf(buffer, size, format, args); + va_end(args); + return retval; +} + +void __DWC_WARN(char *format, ...) +{ + va_list args; + + va_start(args, format); + DWC_PRINTF(KERN_WARNING); + DWC_VPRINTF(format, args); + va_end(args); +} + +void __DWC_ERROR(char *format, ...) +{ + va_list args; + + va_start(args, format); + DWC_PRINTF(KERN_ERR); + DWC_VPRINTF(format, args); + va_end(args); +} + +void DWC_EXCEPTION(char *format, ...) +{ + va_list args; + + va_start(args, format); + DWC_PRINTF(KERN_ERR); + DWC_VPRINTF(format, args); + va_end(args); + BUG_ON(1); +} + +#ifdef DEBUG +void __DWC_DEBUG(char *format, ...) +{ + va_list args; + + va_start(args, format); + DWC_PRINTF(KERN_DEBUG); + DWC_VPRINTF(format, args); + va_end(args); +} +#endif + + +/* dwc_mem.h */ + +#if 0 +dwc_pool_t *DWC_DMA_POOL_CREATE(uint32_t size, + uint32_t align, + uint32_t alloc) +{ + struct dma_pool *pool = dma_pool_create("Pool", NULL, + size, align, alloc); + return (dwc_pool_t *)pool; +} + +void DWC_DMA_POOL_DESTROY(dwc_pool_t *pool) +{ + dma_pool_destroy((struct dma_pool *)pool); +} + +void *DWC_DMA_POOL_ALLOC(dwc_pool_t *pool, uint64_t *dma_addr) +{ + return dma_pool_alloc((struct dma_pool *)pool, GFP_KERNEL, dma_addr); +} + +void *DWC_DMA_POOL_ZALLOC(dwc_pool_t *pool, uint64_t *dma_addr) +{ + void *vaddr = DWC_DMA_POOL_ALLOC(pool, dma_addr); + memset(..); +} + +void DWC_DMA_POOL_FREE(dwc_pool_t *pool, void *vaddr, void *daddr) +{ + dma_pool_free(pool, vaddr, daddr); +} +#endif + +void *__DWC_DMA_ALLOC(void *dma_ctx, uint32_t size, dwc_dma_t *dma_addr) +{ + return dma_alloc_coherent(dma_ctx, size, dma_addr, GFP_KERNEL | GFP_DMA32); +} + +void *__DWC_DMA_ALLOC_ATOMIC(void *dma_ctx, uint32_t size, dwc_dma_t *dma_addr) +{ + return dma_alloc_coherent(dma_ctx, size, dma_addr, GFP_ATOMIC); +} + +void __DWC_DMA_FREE(void *dma_ctx, uint32_t size, void *virt_addr, dwc_dma_t dma_addr) +{ + dma_free_coherent(dma_ctx, size, virt_addr, dma_addr); +} + +void *__DWC_ALLOC(void *mem_ctx, uint32_t size) +{ + return kzalloc(size, GFP_KERNEL); +} + +void *__DWC_ALLOC_ATOMIC(void *mem_ctx, uint32_t size) +{ + return kzalloc(size, GFP_ATOMIC); +} + +void __DWC_FREE(void *mem_ctx, void *addr) +{ + kfree(addr); +} + + +#ifdef DWC_CRYPTOLIB +/* dwc_crypto.h */ + +void DWC_RANDOM_BYTES(uint8_t *buffer, uint32_t length) +{ + get_random_bytes(buffer, length); +} + +int DWC_AES_CBC(uint8_t *message, uint32_t messagelen, uint8_t *key, uint32_t keylen, uint8_t iv[16], uint8_t *out) +{ + struct crypto_blkcipher *tfm; + struct blkcipher_desc desc; + struct scatterlist sgd; + struct scatterlist sgs; + + tfm = crypto_alloc_blkcipher("cbc(aes)", 0, CRYPTO_ALG_ASYNC); + if (tfm == NULL) { + printk("failed to load transform for aes CBC\n"); + return -1; + } + + crypto_blkcipher_setkey(tfm, key, keylen); + crypto_blkcipher_set_iv(tfm, iv, 16); + + sg_init_one(&sgd, out, messagelen); + sg_init_one(&sgs, message, messagelen); + + desc.tfm = tfm; + desc.flags = 0; + + if (crypto_blkcipher_encrypt(&desc, &sgd, &sgs, messagelen)) { + crypto_free_blkcipher(tfm); + DWC_ERROR("AES CBC encryption failed"); + return -1; + } + + crypto_free_blkcipher(tfm); + return 0; +} + +int DWC_SHA256(uint8_t *message, uint32_t len, uint8_t *out) +{ + struct crypto_hash *tfm; + struct hash_desc desc; + struct scatterlist sg; + + tfm = crypto_alloc_hash("sha256", 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(tfm)) { + DWC_ERROR("Failed to load transform for sha256: %ld\n", PTR_ERR(tfm)); + return 0; + } + desc.tfm = tfm; + desc.flags = 0; + + sg_init_one(&sg, message, len); + crypto_hash_digest(&desc, &sg, len, out); + crypto_free_hash(tfm); + + return 1; +} + +int DWC_HMAC_SHA256(uint8_t *message, uint32_t messagelen, + uint8_t *key, uint32_t keylen, uint8_t *out) +{ + struct crypto_hash *tfm; + struct hash_desc desc; + struct scatterlist sg; + + tfm = crypto_alloc_hash("hmac(sha256)", 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(tfm)) { + DWC_ERROR("Failed to load transform for hmac(sha256): %ld\n", PTR_ERR(tfm)); + return 0; + } + desc.tfm = tfm; + desc.flags = 0; + + sg_init_one(&sg, message, messagelen); + crypto_hash_setkey(tfm, key, keylen); + crypto_hash_digest(&desc, &sg, messagelen, out); + crypto_free_hash(tfm); + + return 1; +} +#endif /* DWC_CRYPTOLIB */ + + +/* Byte Ordering Conversions */ + +uint32_t DWC_CPU_TO_LE32(uint32_t *p) +{ +#ifdef __LITTLE_ENDIAN + return *p; +#else + uint8_t *u_p = (uint8_t *)p; + + return (u_p[3] | (u_p[2] << 8) | (u_p[1] << 16) | (u_p[0] << 24)); +#endif +} + +uint32_t DWC_CPU_TO_BE32(uint32_t *p) +{ +#ifdef __BIG_ENDIAN + return *p; +#else + uint8_t *u_p = (uint8_t *)p; + + return (u_p[3] | (u_p[2] << 8) | (u_p[1] << 16) | (u_p[0] << 24)); +#endif +} + +uint32_t DWC_LE32_TO_CPU(uint32_t *p) +{ +#ifdef __LITTLE_ENDIAN + return *p; +#else + uint8_t *u_p = (uint8_t *)p; + + return (u_p[3] | (u_p[2] << 8) | (u_p[1] << 16) | (u_p[0] << 24)); +#endif +} + +uint32_t DWC_BE32_TO_CPU(uint32_t *p) +{ +#ifdef __BIG_ENDIAN + return *p; +#else + uint8_t *u_p = (uint8_t *)p; + + return (u_p[3] | (u_p[2] << 8) | (u_p[1] << 16) | (u_p[0] << 24)); +#endif +} + +uint16_t DWC_CPU_TO_LE16(uint16_t *p) +{ +#ifdef __LITTLE_ENDIAN + return *p; +#else + uint8_t *u_p = (uint8_t *)p; + return (u_p[1] | (u_p[0] << 8)); +#endif +} + +uint16_t DWC_CPU_TO_BE16(uint16_t *p) +{ +#ifdef __BIG_ENDIAN + return *p; +#else + uint8_t *u_p = (uint8_t *)p; + return (u_p[1] | (u_p[0] << 8)); +#endif +} + +uint16_t DWC_LE16_TO_CPU(uint16_t *p) +{ +#ifdef __LITTLE_ENDIAN + return *p; +#else + uint8_t *u_p = (uint8_t *)p; + return (u_p[1] | (u_p[0] << 8)); +#endif +} + +uint16_t DWC_BE16_TO_CPU(uint16_t *p) +{ +#ifdef __BIG_ENDIAN + return *p; +#else + uint8_t *u_p = (uint8_t *)p; + return (u_p[1] | (u_p[0] << 8)); +#endif +} + + +/* Registers */ + +uint32_t DWC_READ_REG32(uint32_t volatile *reg) +{ + return readl(reg); +} + +#if 0 +uint64_t DWC_READ_REG64(uint64_t volatile *reg) +{ +} +#endif + +void DWC_WRITE_REG32(uint32_t volatile *reg, uint32_t value) +{ + writel(value, reg); +} + +#if 0 +void DWC_WRITE_REG64(uint64_t volatile *reg, uint64_t value) +{ +} +#endif + +void DWC_MODIFY_REG32(uint32_t volatile *reg, uint32_t clear_mask, uint32_t set_mask) +{ + writel((readl(reg) & ~clear_mask) | set_mask, reg); +} + +#if 0 +void DWC_MODIFY_REG64(uint64_t volatile *reg, uint64_t clear_mask, uint64_t set_mask) +{ +} +#endif + + +/* Locking */ + +dwc_spinlock_t *DWC_SPINLOCK_ALLOC(void) +{ + spinlock_t *sl = (spinlock_t *)1; + +#if defined(CONFIG_PREEMPT) || defined(CONFIG_SMP) + sl = DWC_ALLOC(sizeof(*sl)); + if (!sl) { + DWC_ERROR("Cannot allocate memory for spinlock\n"); + return NULL; + } + + spin_lock_init(sl); +#endif + return (dwc_spinlock_t *)sl; +} + +void DWC_SPINLOCK_FREE(dwc_spinlock_t *lock) +{ +#if defined(CONFIG_PREEMPT) || defined(CONFIG_SMP) + DWC_FREE(lock); +#endif +} + +void DWC_SPINLOCK(dwc_spinlock_t *lock) +{ +#if defined(CONFIG_PREEMPT) || defined(CONFIG_SMP) + spin_lock((spinlock_t *)lock); +#endif +} + +void DWC_SPINUNLOCK(dwc_spinlock_t *lock) +{ +#if defined(CONFIG_PREEMPT) || defined(CONFIG_SMP) + spin_unlock((spinlock_t *)lock); +#endif +} + +void DWC_SPINLOCK_IRQSAVE(dwc_spinlock_t *lock, dwc_irqflags_t *flags) +{ + dwc_irqflags_t f; + +#if defined(CONFIG_PREEMPT) || defined(CONFIG_SMP) + spin_lock_irqsave((spinlock_t *)lock, f); +#else + local_irq_save(f); +#endif + *flags = f; +} + +void DWC_SPINUNLOCK_IRQRESTORE(dwc_spinlock_t *lock, dwc_irqflags_t flags) +{ +#if defined(CONFIG_PREEMPT) || defined(CONFIG_SMP) + spin_unlock_irqrestore((spinlock_t *)lock, flags); +#else + local_irq_restore(flags); +#endif +} + +dwc_mutex_t *DWC_MUTEX_ALLOC(void) +{ + struct mutex *m; + dwc_mutex_t *mutex = (dwc_mutex_t *)DWC_ALLOC(sizeof(struct mutex)); + + if (!mutex) { + DWC_ERROR("Cannot allocate memory for mutex\n"); + return NULL; + } + + m = (struct mutex *)mutex; + mutex_init(m); + return mutex; +} + +#if (defined(DWC_LINUX) && defined(CONFIG_DEBUG_MUTEXES)) +#else +void DWC_MUTEX_FREE(dwc_mutex_t *mutex) +{ + mutex_destroy((struct mutex *)mutex); + DWC_FREE(mutex); +} +#endif + +void DWC_MUTEX_LOCK(dwc_mutex_t *mutex) +{ + struct mutex *m = (struct mutex *)mutex; + mutex_lock(m); +} + +int DWC_MUTEX_TRYLOCK(dwc_mutex_t *mutex) +{ + struct mutex *m = (struct mutex *)mutex; + return mutex_trylock(m); +} + +void DWC_MUTEX_UNLOCK(dwc_mutex_t *mutex) +{ + struct mutex *m = (struct mutex *)mutex; + mutex_unlock(m); +} + + +/* Timing */ + +void DWC_UDELAY(uint32_t usecs) +{ + udelay(usecs); +} + +void DWC_MDELAY(uint32_t msecs) +{ + mdelay(msecs); +} + +void DWC_MSLEEP(uint32_t msecs) +{ + msleep(msecs); +} + +uint32_t DWC_TIME(void) +{ + return jiffies_to_msecs(jiffies); +} + + +/* Timers */ + +struct dwc_timer { + struct timer_list t; + char *name; + dwc_timer_callback_t cb; + void *data; + uint8_t scheduled; + dwc_spinlock_t *lock; +}; + +static void timer_callback(struct timer_list *tt) +{ + dwc_timer_t *timer = from_timer(timer, tt, t); + dwc_irqflags_t flags; + + DWC_SPINLOCK_IRQSAVE(timer->lock, &flags); + timer->scheduled = 0; + DWC_SPINUNLOCK_IRQRESTORE(timer->lock, flags); + DWC_DEBUGC("Timer %s callback", timer->name); + timer->cb(timer->data); +} + +dwc_timer_t *DWC_TIMER_ALLOC(char *name, dwc_timer_callback_t cb, void *data) +{ + dwc_timer_t *t = DWC_ALLOC(sizeof(*t)); + + if (!t) { + DWC_ERROR("Cannot allocate memory for timer"); + return NULL; + } + + t->name = DWC_STRDUP(name); + if (!t->name) { + DWC_ERROR("Cannot allocate memory for timer->name"); + goto no_name; + } + +#if (defined(DWC_LINUX) && defined(CONFIG_DEBUG_SPINLOCK)) + DWC_SPINLOCK_ALLOC_LINUX_DEBUG(t->lock); +#else + t->lock = DWC_SPINLOCK_ALLOC(); +#endif + if (!t->lock) { + DWC_ERROR("Cannot allocate memory for lock"); + goto no_lock; + } + + t->scheduled = 0; + t->t.expires = jiffies; + timer_setup(&t->t, timer_callback, 0); + + t->cb = cb; + t->data = data; + + return t; + + no_lock: + DWC_FREE(t->name); + no_name: + DWC_FREE(t); + return NULL; +} + +void DWC_TIMER_FREE(dwc_timer_t *timer) +{ + dwc_irqflags_t flags; + + DWC_SPINLOCK_IRQSAVE(timer->lock, &flags); + + if (timer->scheduled) { + del_timer(&timer->t); + timer->scheduled = 0; + } + + DWC_SPINUNLOCK_IRQRESTORE(timer->lock, flags); + DWC_SPINLOCK_FREE(timer->lock); + DWC_FREE(timer->name); + DWC_FREE(timer); +} + +void DWC_TIMER_SCHEDULE(dwc_timer_t *timer, uint32_t time) +{ + dwc_irqflags_t flags; + + DWC_SPINLOCK_IRQSAVE(timer->lock, &flags); + + if (!timer->scheduled) { + timer->scheduled = 1; + DWC_DEBUGC("Scheduling timer %s to expire in +%d msec", timer->name, time); + timer->t.expires = jiffies + msecs_to_jiffies(time); + add_timer(&timer->t); + } else { + DWC_DEBUGC("Modifying timer %s to expire in +%d msec", timer->name, time); + mod_timer(&timer->t, jiffies + msecs_to_jiffies(time)); + } + + DWC_SPINUNLOCK_IRQRESTORE(timer->lock, flags); +} + +void DWC_TIMER_CANCEL(dwc_timer_t *timer) +{ + del_timer(&timer->t); +} + + +/* Wait Queues */ + +struct dwc_waitq { + wait_queue_head_t queue; + int abort; +}; + +dwc_waitq_t *DWC_WAITQ_ALLOC(void) +{ + dwc_waitq_t *wq = DWC_ALLOC(sizeof(*wq)); + + if (!wq) { + DWC_ERROR("Cannot allocate memory for waitqueue\n"); + return NULL; + } + + init_waitqueue_head(&wq->queue); + wq->abort = 0; + return wq; +} + +void DWC_WAITQ_FREE(dwc_waitq_t *wq) +{ + DWC_FREE(wq); +} + +int32_t DWC_WAITQ_WAIT(dwc_waitq_t *wq, dwc_waitq_condition_t cond, void *data) +{ + int result = wait_event_interruptible(wq->queue, + cond(data) || wq->abort); + if (result == -ERESTARTSYS) { + wq->abort = 0; + return -DWC_E_RESTART; + } + + if (wq->abort == 1) { + wq->abort = 0; + return -DWC_E_ABORT; + } + + wq->abort = 0; + + if (result == 0) { + return 0; + } + + return -DWC_E_UNKNOWN; +} + +int32_t DWC_WAITQ_WAIT_TIMEOUT(dwc_waitq_t *wq, dwc_waitq_condition_t cond, + void *data, int32_t msecs) +{ + int32_t tmsecs; + int result = wait_event_interruptible_timeout(wq->queue, + cond(data) || wq->abort, + msecs_to_jiffies(msecs)); + if (result == -ERESTARTSYS) { + wq->abort = 0; + return -DWC_E_RESTART; + } + + if (wq->abort == 1) { + wq->abort = 0; + return -DWC_E_ABORT; + } + + wq->abort = 0; + + if (result > 0) { + tmsecs = jiffies_to_msecs(result); + if (!tmsecs) { + return 1; + } + + return tmsecs; + } + + if (result == 0) { + return -DWC_E_TIMEOUT; + } + + return -DWC_E_UNKNOWN; +} + +void DWC_WAITQ_TRIGGER(dwc_waitq_t *wq) +{ + wq->abort = 0; + wake_up_interruptible(&wq->queue); +} + +void DWC_WAITQ_ABORT(dwc_waitq_t *wq) +{ + wq->abort = 1; + wake_up_interruptible(&wq->queue); +} + + +/* Threading */ + +dwc_thread_t *DWC_THREAD_RUN(dwc_thread_function_t func, char *name, void *data) +{ + struct task_struct *thread = kthread_run(func, data, name); + + if (thread == ERR_PTR(-ENOMEM)) { + return NULL; + } + + return (dwc_thread_t *)thread; +} + +int DWC_THREAD_STOP(dwc_thread_t *thread) +{ + return kthread_stop((struct task_struct *)thread); +} + +dwc_bool_t DWC_THREAD_SHOULD_STOP(void) +{ + return kthread_should_stop(); +} + + +/* tasklets + - run in interrupt context (cannot sleep) + - each tasklet runs on a single CPU + - different tasklets can be running simultaneously on different CPUs + */ +struct dwc_tasklet { + struct tasklet_struct t; + dwc_tasklet_callback_t cb; + void *data; +}; + +static void tasklet_callback(unsigned long data) +{ + dwc_tasklet_t *t = (dwc_tasklet_t *)data; + t->cb(t->data); +} + +dwc_tasklet_t *DWC_TASK_ALLOC(char *name, dwc_tasklet_callback_t cb, void *data) +{ + dwc_tasklet_t *t = DWC_ALLOC(sizeof(*t)); + + if (t) { + t->cb = cb; + t->data = data; + tasklet_init(&t->t, tasklet_callback, (unsigned long)t); + } else { + DWC_ERROR("Cannot allocate memory for tasklet\n"); + } + + return t; +} + +void DWC_TASK_FREE(dwc_tasklet_t *task) +{ + DWC_FREE(task); +} + +void DWC_TASK_SCHEDULE(dwc_tasklet_t *task) +{ + tasklet_schedule(&task->t); +} + +void DWC_TASK_HI_SCHEDULE(dwc_tasklet_t *task) +{ + tasklet_hi_schedule(&task->t); +} + + +/* workqueues + - run in process context (can sleep) + */ +typedef struct work_container { + dwc_work_callback_t cb; + void *data; + dwc_workq_t *wq; + char *name; + +#ifdef DEBUG + DWC_CIRCLEQ_ENTRY(work_container) entry; +#endif + struct delayed_work work; +} work_container_t; + +#ifdef DEBUG +DWC_CIRCLEQ_HEAD(work_container_queue, work_container); +#endif + +struct dwc_workq { + struct workqueue_struct *wq; + dwc_spinlock_t *lock; + dwc_waitq_t *waitq; + int pending; + +#ifdef DEBUG + struct work_container_queue entries; +#endif +}; + +static void do_work(struct work_struct *work) +{ + dwc_irqflags_t flags; + struct delayed_work *dw = container_of(work, struct delayed_work, work); + work_container_t *container = container_of(dw, struct work_container, work); + dwc_workq_t *wq = container->wq; + + container->cb(container->data); + +#ifdef DEBUG + DWC_CIRCLEQ_REMOVE(&wq->entries, container, entry); +#endif + DWC_DEBUGC("Work done: %s, container=%p", container->name, container); + if (container->name) { + DWC_FREE(container->name); + } + DWC_FREE(container); + + DWC_SPINLOCK_IRQSAVE(wq->lock, &flags); + wq->pending--; + DWC_SPINUNLOCK_IRQRESTORE(wq->lock, flags); + DWC_WAITQ_TRIGGER(wq->waitq); +} + +static int work_done(void *data) +{ + dwc_workq_t *workq = (dwc_workq_t *)data; + return workq->pending == 0; +} + +int DWC_WORKQ_WAIT_WORK_DONE(dwc_workq_t *workq, int timeout) +{ + return DWC_WAITQ_WAIT_TIMEOUT(workq->waitq, work_done, workq, timeout); +} + +dwc_workq_t *DWC_WORKQ_ALLOC(char *name) +{ + dwc_workq_t *wq = DWC_ALLOC(sizeof(*wq)); + + if (!wq) { + return NULL; + } + + wq->wq = create_singlethread_workqueue(name); + if (!wq->wq) { + goto no_wq; + } + + wq->pending = 0; + +#if (defined(DWC_LINUX) && defined(CONFIG_DEBUG_SPINLOCK)) + DWC_SPINLOCK_ALLOC_LINUX_DEBUG(wq->lock); +#else + wq->lock = DWC_SPINLOCK_ALLOC(); +#endif + if (!wq->lock) { + goto no_lock; + } + + wq->waitq = DWC_WAITQ_ALLOC(); + if (!wq->waitq) { + goto no_waitq; + } + +#ifdef DEBUG + DWC_CIRCLEQ_INIT(&wq->entries); +#endif + return wq; + + no_waitq: + DWC_SPINLOCK_FREE(wq->lock); + no_lock: + destroy_workqueue(wq->wq); + no_wq: + DWC_FREE(wq); + + return NULL; +} + +void DWC_WORKQ_FREE(dwc_workq_t *wq) +{ +#ifdef DEBUG + if (wq->pending != 0) { + struct work_container *wc; + DWC_ERROR("Destroying work queue with pending work"); + DWC_CIRCLEQ_FOREACH(wc, &wq->entries, entry) { + DWC_ERROR("Work %s still pending", wc->name); + } + } +#endif + destroy_workqueue(wq->wq); + DWC_SPINLOCK_FREE(wq->lock); + DWC_WAITQ_FREE(wq->waitq); + DWC_FREE(wq); +} + +void DWC_WORKQ_SCHEDULE(dwc_workq_t *wq, dwc_work_callback_t cb, void *data, + char *format, ...) +{ + dwc_irqflags_t flags; + work_container_t *container; + static char name[128]; + va_list args; + + va_start(args, format); + DWC_VSNPRINTF(name, 128, format, args); + va_end(args); + + DWC_SPINLOCK_IRQSAVE(wq->lock, &flags); + wq->pending++; + DWC_SPINUNLOCK_IRQRESTORE(wq->lock, flags); + DWC_WAITQ_TRIGGER(wq->waitq); + + container = DWC_ALLOC_ATOMIC(sizeof(*container)); + if (!container) { + DWC_ERROR("Cannot allocate memory for container\n"); + return; + } + + container->name = DWC_STRDUP(name); + if (!container->name) { + DWC_ERROR("Cannot allocate memory for container->name\n"); + DWC_FREE(container); + return; + } + + container->cb = cb; + container->data = data; + container->wq = wq; + DWC_DEBUGC("Queueing work: %s, container=%p", container->name, container); + INIT_WORK(&container->work.work, do_work); + +#ifdef DEBUG + DWC_CIRCLEQ_INSERT_TAIL(&wq->entries, container, entry); +#endif + queue_work(wq->wq, &container->work.work); +} + +void DWC_WORKQ_SCHEDULE_DELAYED(dwc_workq_t *wq, dwc_work_callback_t cb, + void *data, uint32_t time, char *format, ...) +{ + dwc_irqflags_t flags; + work_container_t *container; + static char name[128]; + va_list args; + + va_start(args, format); + DWC_VSNPRINTF(name, 128, format, args); + va_end(args); + + DWC_SPINLOCK_IRQSAVE(wq->lock, &flags); + wq->pending++; + DWC_SPINUNLOCK_IRQRESTORE(wq->lock, flags); + DWC_WAITQ_TRIGGER(wq->waitq); + + container = DWC_ALLOC_ATOMIC(sizeof(*container)); + if (!container) { + DWC_ERROR("Cannot allocate memory for container\n"); + return; + } + + container->name = DWC_STRDUP(name); + if (!container->name) { + DWC_ERROR("Cannot allocate memory for container->name\n"); + DWC_FREE(container); + return; + } + + container->cb = cb; + container->data = data; + container->wq = wq; + DWC_DEBUGC("Queueing work: %s, container=%p", container->name, container); + INIT_DELAYED_WORK(&container->work, do_work); + +#ifdef DEBUG + DWC_CIRCLEQ_INSERT_TAIL(&wq->entries, container, entry); +#endif + queue_delayed_work(wq->wq, &container->work, msecs_to_jiffies(time)); +} + +int DWC_WORKQ_PENDING(dwc_workq_t *wq) +{ + return wq->pending; +} + + +#ifdef DWC_LIBMODULE + +#ifdef DWC_CCLIB +/* CC */ +EXPORT_SYMBOL(dwc_cc_if_alloc); +EXPORT_SYMBOL(dwc_cc_if_free); +EXPORT_SYMBOL(dwc_cc_clear); +EXPORT_SYMBOL(dwc_cc_add); +EXPORT_SYMBOL(dwc_cc_remove); +EXPORT_SYMBOL(dwc_cc_change); +EXPORT_SYMBOL(dwc_cc_data_for_save); +EXPORT_SYMBOL(dwc_cc_restore_from_data); +EXPORT_SYMBOL(dwc_cc_match_chid); +EXPORT_SYMBOL(dwc_cc_match_cdid); +EXPORT_SYMBOL(dwc_cc_ck); +EXPORT_SYMBOL(dwc_cc_chid); +EXPORT_SYMBOL(dwc_cc_cdid); +EXPORT_SYMBOL(dwc_cc_name); +#endif /* DWC_CCLIB */ + +#ifdef DWC_CRYPTOLIB +# ifndef CONFIG_MACH_IPMATE +/* Modpow */ +EXPORT_SYMBOL(dwc_modpow); + +/* DH */ +EXPORT_SYMBOL(dwc_dh_modpow); +EXPORT_SYMBOL(dwc_dh_derive_keys); +EXPORT_SYMBOL(dwc_dh_pk); +# endif /* CONFIG_MACH_IPMATE */ + +/* Crypto */ +EXPORT_SYMBOL(dwc_wusb_aes_encrypt); +EXPORT_SYMBOL(dwc_wusb_cmf); +EXPORT_SYMBOL(dwc_wusb_prf); +EXPORT_SYMBOL(dwc_wusb_fill_ccm_nonce); +EXPORT_SYMBOL(dwc_wusb_gen_nonce); +EXPORT_SYMBOL(dwc_wusb_gen_key); +EXPORT_SYMBOL(dwc_wusb_gen_mic); +#endif /* DWC_CRYPTOLIB */ + +/* Notification */ +#ifdef DWC_NOTIFYLIB +EXPORT_SYMBOL(dwc_alloc_notification_manager); +EXPORT_SYMBOL(dwc_free_notification_manager); +EXPORT_SYMBOL(dwc_register_notifier); +EXPORT_SYMBOL(dwc_unregister_notifier); +EXPORT_SYMBOL(dwc_add_observer); +EXPORT_SYMBOL(dwc_remove_observer); +EXPORT_SYMBOL(dwc_notify); +#endif + +/* Memory Debugging Routines */ +#ifdef DWC_DEBUG_MEMORY +EXPORT_SYMBOL(dwc_alloc_debug); +EXPORT_SYMBOL(dwc_alloc_atomic_debug); +EXPORT_SYMBOL(dwc_free_debug); +EXPORT_SYMBOL(dwc_dma_alloc_debug); +EXPORT_SYMBOL(dwc_dma_free_debug); +#endif + +EXPORT_SYMBOL(DWC_MEMSET); +EXPORT_SYMBOL(DWC_MEMCPY); +EXPORT_SYMBOL(DWC_MEMMOVE); +EXPORT_SYMBOL(DWC_MEMCMP); +EXPORT_SYMBOL(DWC_STRNCMP); +EXPORT_SYMBOL(DWC_STRCMP); +EXPORT_SYMBOL(DWC_STRLEN); +EXPORT_SYMBOL(DWC_STRCPY); +EXPORT_SYMBOL(DWC_STRDUP); +EXPORT_SYMBOL(DWC_ATOI); +EXPORT_SYMBOL(DWC_ATOUI); + +#ifdef DWC_UTFLIB +EXPORT_SYMBOL(DWC_UTF8_TO_UTF16LE); +#endif /* DWC_UTFLIB */ + +EXPORT_SYMBOL(DWC_IN_IRQ); +EXPORT_SYMBOL(DWC_IN_BH); +EXPORT_SYMBOL(DWC_VPRINTF); +EXPORT_SYMBOL(DWC_VSNPRINTF); +EXPORT_SYMBOL(DWC_PRINTF); +EXPORT_SYMBOL(DWC_SPRINTF); +EXPORT_SYMBOL(DWC_SNPRINTF); +EXPORT_SYMBOL(__DWC_WARN); +EXPORT_SYMBOL(__DWC_ERROR); +EXPORT_SYMBOL(DWC_EXCEPTION); + +#ifdef DEBUG +EXPORT_SYMBOL(__DWC_DEBUG); +#endif + +EXPORT_SYMBOL(__DWC_DMA_ALLOC); +EXPORT_SYMBOL(__DWC_DMA_ALLOC_ATOMIC); +EXPORT_SYMBOL(__DWC_DMA_FREE); +EXPORT_SYMBOL(__DWC_ALLOC); +EXPORT_SYMBOL(__DWC_ALLOC_ATOMIC); +EXPORT_SYMBOL(__DWC_FREE); + +#ifdef DWC_CRYPTOLIB +EXPORT_SYMBOL(DWC_RANDOM_BYTES); +EXPORT_SYMBOL(DWC_AES_CBC); +EXPORT_SYMBOL(DWC_SHA256); +EXPORT_SYMBOL(DWC_HMAC_SHA256); +#endif + +EXPORT_SYMBOL(DWC_CPU_TO_LE32); +EXPORT_SYMBOL(DWC_CPU_TO_BE32); +EXPORT_SYMBOL(DWC_LE32_TO_CPU); +EXPORT_SYMBOL(DWC_BE32_TO_CPU); +EXPORT_SYMBOL(DWC_CPU_TO_LE16); +EXPORT_SYMBOL(DWC_CPU_TO_BE16); +EXPORT_SYMBOL(DWC_LE16_TO_CPU); +EXPORT_SYMBOL(DWC_BE16_TO_CPU); +EXPORT_SYMBOL(DWC_READ_REG32); +EXPORT_SYMBOL(DWC_WRITE_REG32); +EXPORT_SYMBOL(DWC_MODIFY_REG32); + +#if 0 +EXPORT_SYMBOL(DWC_READ_REG64); +EXPORT_SYMBOL(DWC_WRITE_REG64); +EXPORT_SYMBOL(DWC_MODIFY_REG64); +#endif + +EXPORT_SYMBOL(DWC_SPINLOCK_ALLOC); +EXPORT_SYMBOL(DWC_SPINLOCK_FREE); +EXPORT_SYMBOL(DWC_SPINLOCK); +EXPORT_SYMBOL(DWC_SPINUNLOCK); +EXPORT_SYMBOL(DWC_SPINLOCK_IRQSAVE); +EXPORT_SYMBOL(DWC_SPINUNLOCK_IRQRESTORE); +EXPORT_SYMBOL(DWC_MUTEX_ALLOC); + +#if (!defined(DWC_LINUX) || !defined(CONFIG_DEBUG_MUTEXES)) +EXPORT_SYMBOL(DWC_MUTEX_FREE); +#endif + +EXPORT_SYMBOL(DWC_MUTEX_LOCK); +EXPORT_SYMBOL(DWC_MUTEX_TRYLOCK); +EXPORT_SYMBOL(DWC_MUTEX_UNLOCK); +EXPORT_SYMBOL(DWC_UDELAY); +EXPORT_SYMBOL(DWC_MDELAY); +EXPORT_SYMBOL(DWC_MSLEEP); +EXPORT_SYMBOL(DWC_TIME); +EXPORT_SYMBOL(DWC_TIMER_ALLOC); +EXPORT_SYMBOL(DWC_TIMER_FREE); +EXPORT_SYMBOL(DWC_TIMER_SCHEDULE); +EXPORT_SYMBOL(DWC_TIMER_CANCEL); +EXPORT_SYMBOL(DWC_WAITQ_ALLOC); +EXPORT_SYMBOL(DWC_WAITQ_FREE); +EXPORT_SYMBOL(DWC_WAITQ_WAIT); +EXPORT_SYMBOL(DWC_WAITQ_WAIT_TIMEOUT); +EXPORT_SYMBOL(DWC_WAITQ_TRIGGER); +EXPORT_SYMBOL(DWC_WAITQ_ABORT); +EXPORT_SYMBOL(DWC_THREAD_RUN); +EXPORT_SYMBOL(DWC_THREAD_STOP); +EXPORT_SYMBOL(DWC_THREAD_SHOULD_STOP); +EXPORT_SYMBOL(DWC_TASK_ALLOC); +EXPORT_SYMBOL(DWC_TASK_FREE); +EXPORT_SYMBOL(DWC_TASK_SCHEDULE); +EXPORT_SYMBOL(DWC_WORKQ_WAIT_WORK_DONE); +EXPORT_SYMBOL(DWC_WORKQ_ALLOC); +EXPORT_SYMBOL(DWC_WORKQ_FREE); +EXPORT_SYMBOL(DWC_WORKQ_SCHEDULE); +EXPORT_SYMBOL(DWC_WORKQ_SCHEDULE_DELAYED); +EXPORT_SYMBOL(DWC_WORKQ_PENDING); + +static int dwc_common_port_init_module(void) +{ + int result = 0; + + printk(KERN_DEBUG "Module dwc_common_port init\n" ); + +#ifdef DWC_DEBUG_MEMORY + result = dwc_memory_debug_start(NULL); + if (result) { + printk(KERN_ERR + "dwc_memory_debug_start() failed with error %d\n", + result); + return result; + } +#endif + +#ifdef DWC_NOTIFYLIB + result = dwc_alloc_notification_manager(NULL, NULL); + if (result) { + printk(KERN_ERR + "dwc_alloc_notification_manager() failed with error %d\n", + result); + return result; + } +#endif + return result; +} + +static void dwc_common_port_exit_module(void) +{ + printk(KERN_DEBUG "Module dwc_common_port exit\n" ); + +#ifdef DWC_NOTIFYLIB + dwc_free_notification_manager(); +#endif + +#ifdef DWC_DEBUG_MEMORY + dwc_memory_debug_stop(); +#endif +} + +module_init(dwc_common_port_init_module); +module_exit(dwc_common_port_exit_module); + +MODULE_DESCRIPTION("DWC Common Library - Portable version"); +MODULE_AUTHOR("Synopsys Inc."); +MODULE_LICENSE ("GPL"); + +#endif /* DWC_LIBMODULE */ diff --git a/drivers/usb/host/dwc_common_port/dwc_common_nbsd.c b/drivers/usb/host/dwc_common_port/dwc_common_nbsd.c new file mode 100644 index 00000000000000..49b07e17226451 --- /dev/null +++ b/drivers/usb/host/dwc_common_port/dwc_common_nbsd.c @@ -0,0 +1,1275 @@ +#include "dwc_os.h" +#include "dwc_list.h" + +#ifdef DWC_CCLIB +# include "dwc_cc.h" +#endif + +#ifdef DWC_CRYPTOLIB +# include "dwc_modpow.h" +# include "dwc_dh.h" +# include "dwc_crypto.h" +#endif + +#ifdef DWC_NOTIFYLIB +# include "dwc_notifier.h" +#endif + +/* OS-Level Implementations */ + +/* This is the NetBSD 4.0.1 kernel implementation of the DWC platform library. */ + + +/* MISC */ + +void *DWC_MEMSET(void *dest, uint8_t byte, uint32_t size) +{ + return memset(dest, byte, size); +} + +void *DWC_MEMCPY(void *dest, void const *src, uint32_t size) +{ + return memcpy(dest, src, size); +} + +void *DWC_MEMMOVE(void *dest, void *src, uint32_t size) +{ + bcopy(src, dest, size); + return dest; +} + +int DWC_MEMCMP(void *m1, void *m2, uint32_t size) +{ + return memcmp(m1, m2, size); +} + +int DWC_STRNCMP(void *s1, void *s2, uint32_t size) +{ + return strncmp(s1, s2, size); +} + +int DWC_STRCMP(void *s1, void *s2) +{ + return strcmp(s1, s2); +} + +int DWC_STRLEN(char const *str) +{ + return strlen(str); +} + +char *DWC_STRCPY(char *to, char const *from) +{ + return strcpy(to, from); +} + +char *DWC_STRDUP(char const *str) +{ + int len = DWC_STRLEN(str) + 1; + char *new = DWC_ALLOC_ATOMIC(len); + + if (!new) { + return NULL; + } + + DWC_MEMCPY(new, str, len); + return new; +} + +int DWC_ATOI(char *str, int32_t *value) +{ + char *end = NULL; + + /* NetBSD doesn't have 'strtol' in the kernel, but 'strtoul' + * should be equivalent on 2's complement machines + */ + *value = strtoul(str, &end, 0); + if (*end == '\0') { + return 0; + } + + return -1; +} + +int DWC_ATOUI(char *str, uint32_t *value) +{ + char *end = NULL; + + *value = strtoul(str, &end, 0); + if (*end == '\0') { + return 0; + } + + return -1; +} + + +#ifdef DWC_UTFLIB +/* From usbstring.c */ + +int DWC_UTF8_TO_UTF16LE(uint8_t const *s, uint16_t *cp, unsigned len) +{ + int count = 0; + u8 c; + u16 uchar; + + /* this insists on correct encodings, though not minimal ones. + * BUT it currently rejects legit 4-byte UTF-8 code points, + * which need surrogate pairs. (Unicode 3.1 can use them.) + */ + while (len != 0 && (c = (u8) *s++) != 0) { + if (unlikely(c & 0x80)) { + // 2-byte sequence: + // 00000yyyyyxxxxxx = 110yyyyy 10xxxxxx + if ((c & 0xe0) == 0xc0) { + uchar = (c & 0x1f) << 6; + + c = (u8) *s++; + if ((c & 0xc0) != 0xc0) + goto fail; + c &= 0x3f; + uchar |= c; + + // 3-byte sequence (most CJKV characters): + // zzzzyyyyyyxxxxxx = 1110zzzz 10yyyyyy 10xxxxxx + } else if ((c & 0xf0) == 0xe0) { + uchar = (c & 0x0f) << 12; + + c = (u8) *s++; + if ((c & 0xc0) != 0xc0) + goto fail; + c &= 0x3f; + uchar |= c << 6; + + c = (u8) *s++; + if ((c & 0xc0) != 0xc0) + goto fail; + c &= 0x3f; + uchar |= c; + + /* no bogus surrogates */ + if (0xd800 <= uchar && uchar <= 0xdfff) + goto fail; + + // 4-byte sequence (surrogate pairs, currently rare): + // 11101110wwwwzzzzyy + 110111yyyyxxxxxx + // = 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx + // (uuuuu = wwww + 1) + // FIXME accept the surrogate code points (only) + } else + goto fail; + } else + uchar = c; + put_unaligned (cpu_to_le16 (uchar), cp++); + count++; + len--; + } + return count; +fail: + return -1; +} + +#endif /* DWC_UTFLIB */ + + +/* dwc_debug.h */ + +dwc_bool_t DWC_IN_IRQ(void) +{ +// return in_irq(); + return 0; +} + +dwc_bool_t DWC_IN_BH(void) +{ +// return in_softirq(); + return 0; +} + +void DWC_VPRINTF(char *format, va_list args) +{ + vprintf(format, args); +} + +int DWC_VSNPRINTF(char *str, int size, char *format, va_list args) +{ + return vsnprintf(str, size, format, args); +} + +void DWC_PRINTF(char *format, ...) +{ + va_list args; + + va_start(args, format); + DWC_VPRINTF(format, args); + va_end(args); +} + +int DWC_SPRINTF(char *buffer, char *format, ...) +{ + int retval; + va_list args; + + va_start(args, format); + retval = vsprintf(buffer, format, args); + va_end(args); + return retval; +} + +int DWC_SNPRINTF(char *buffer, int size, char *format, ...) +{ + int retval; + va_list args; + + va_start(args, format); + retval = vsnprintf(buffer, size, format, args); + va_end(args); + return retval; +} + +void __DWC_WARN(char *format, ...) +{ + va_list args; + + va_start(args, format); + DWC_VPRINTF(format, args); + va_end(args); +} + +void __DWC_ERROR(char *format, ...) +{ + va_list args; + + va_start(args, format); + DWC_VPRINTF(format, args); + va_end(args); +} + +void DWC_EXCEPTION(char *format, ...) +{ + va_list args; + + va_start(args, format); + DWC_VPRINTF(format, args); + va_end(args); +// BUG_ON(1); ??? +} + +#ifdef DEBUG +void __DWC_DEBUG(char *format, ...) +{ + va_list args; + + va_start(args, format); + DWC_VPRINTF(format, args); + va_end(args); +} +#endif + + +/* dwc_mem.h */ + +#if 0 +dwc_pool_t *DWC_DMA_POOL_CREATE(uint32_t size, + uint32_t align, + uint32_t alloc) +{ + struct dma_pool *pool = dma_pool_create("Pool", NULL, + size, align, alloc); + return (dwc_pool_t *)pool; +} + +void DWC_DMA_POOL_DESTROY(dwc_pool_t *pool) +{ + dma_pool_destroy((struct dma_pool *)pool); +} + +void *DWC_DMA_POOL_ALLOC(dwc_pool_t *pool, uint64_t *dma_addr) +{ +// return dma_pool_alloc((struct dma_pool *)pool, GFP_KERNEL, dma_addr); + return dma_pool_alloc((struct dma_pool *)pool, M_WAITOK, dma_addr); +} + +void *DWC_DMA_POOL_ZALLOC(dwc_pool_t *pool, uint64_t *dma_addr) +{ + void *vaddr = DWC_DMA_POOL_ALLOC(pool, dma_addr); + memset(..); +} + +void DWC_DMA_POOL_FREE(dwc_pool_t *pool, void *vaddr, void *daddr) +{ + dma_pool_free(pool, vaddr, daddr); +} +#endif + +void *__DWC_DMA_ALLOC(void *dma_ctx, uint32_t size, dwc_dma_t *dma_addr) +{ + dwc_dmactx_t *dma = (dwc_dmactx_t *)dma_ctx; + int error; + + error = bus_dmamem_alloc(dma->dma_tag, size, 1, size, dma->segs, + sizeof(dma->segs) / sizeof(dma->segs[0]), + &dma->nsegs, BUS_DMA_NOWAIT); + if (error) { + printf("%s: bus_dmamem_alloc(%ju) failed: %d\n", __func__, + (uintmax_t)size, error); + goto fail_0; + } + + error = bus_dmamem_map(dma->dma_tag, dma->segs, dma->nsegs, size, + (caddr_t *)&dma->dma_vaddr, + BUS_DMA_NOWAIT | BUS_DMA_COHERENT); + if (error) { + printf("%s: bus_dmamem_map failed: %d\n", __func__, error); + goto fail_1; + } + + error = bus_dmamap_create(dma->dma_tag, size, 1, size, 0, + BUS_DMA_NOWAIT, &dma->dma_map); + if (error) { + printf("%s: bus_dmamap_create failed: %d\n", __func__, error); + goto fail_2; + } + + error = bus_dmamap_load(dma->dma_tag, dma->dma_map, dma->dma_vaddr, + size, NULL, BUS_DMA_NOWAIT); + if (error) { + printf("%s: bus_dmamap_load failed: %d\n", __func__, error); + goto fail_3; + } + + dma->dma_paddr = (bus_addr_t)dma->segs[0].ds_addr; + *dma_addr = dma->dma_paddr; + return dma->dma_vaddr; + +fail_3: + bus_dmamap_destroy(dma->dma_tag, dma->dma_map); +fail_2: + bus_dmamem_unmap(dma->dma_tag, dma->dma_vaddr, size); +fail_1: + bus_dmamem_free(dma->dma_tag, dma->segs, dma->nsegs); +fail_0: + dma->dma_map = NULL; + dma->dma_vaddr = NULL; + dma->nsegs = 0; + + return NULL; +} + +void __DWC_DMA_FREE(void *dma_ctx, uint32_t size, void *virt_addr, dwc_dma_t dma_addr) +{ + dwc_dmactx_t *dma = (dwc_dmactx_t *)dma_ctx; + + if (dma->dma_map != NULL) { + bus_dmamap_sync(dma->dma_tag, dma->dma_map, 0, size, + BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); + bus_dmamap_unload(dma->dma_tag, dma->dma_map); + bus_dmamap_destroy(dma->dma_tag, dma->dma_map); + bus_dmamem_unmap(dma->dma_tag, dma->dma_vaddr, size); + bus_dmamem_free(dma->dma_tag, dma->segs, dma->nsegs); + dma->dma_paddr = 0; + dma->dma_map = NULL; + dma->dma_vaddr = NULL; + dma->nsegs = 0; + } +} + +void *__DWC_ALLOC(void *mem_ctx, uint32_t size) +{ + return malloc(size, M_DEVBUF, M_WAITOK | M_ZERO); +} + +void *__DWC_ALLOC_ATOMIC(void *mem_ctx, uint32_t size) +{ + return malloc(size, M_DEVBUF, M_NOWAIT | M_ZERO); +} + +void __DWC_FREE(void *mem_ctx, void *addr) +{ + free(addr, M_DEVBUF); +} + + +#ifdef DWC_CRYPTOLIB +/* dwc_crypto.h */ + +void DWC_RANDOM_BYTES(uint8_t *buffer, uint32_t length) +{ + get_random_bytes(buffer, length); +} + +int DWC_AES_CBC(uint8_t *message, uint32_t messagelen, uint8_t *key, uint32_t keylen, uint8_t iv[16], uint8_t *out) +{ + struct crypto_blkcipher *tfm; + struct blkcipher_desc desc; + struct scatterlist sgd; + struct scatterlist sgs; + + tfm = crypto_alloc_blkcipher("cbc(aes)", 0, CRYPTO_ALG_ASYNC); + if (tfm == NULL) { + printk("failed to load transform for aes CBC\n"); + return -1; + } + + crypto_blkcipher_setkey(tfm, key, keylen); + crypto_blkcipher_set_iv(tfm, iv, 16); + + sg_init_one(&sgd, out, messagelen); + sg_init_one(&sgs, message, messagelen); + + desc.tfm = tfm; + desc.flags = 0; + + if (crypto_blkcipher_encrypt(&desc, &sgd, &sgs, messagelen)) { + crypto_free_blkcipher(tfm); + DWC_ERROR("AES CBC encryption failed"); + return -1; + } + + crypto_free_blkcipher(tfm); + return 0; +} + +int DWC_SHA256(uint8_t *message, uint32_t len, uint8_t *out) +{ + struct crypto_hash *tfm; + struct hash_desc desc; + struct scatterlist sg; + + tfm = crypto_alloc_hash("sha256", 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(tfm)) { + DWC_ERROR("Failed to load transform for sha256: %ld", PTR_ERR(tfm)); + return 0; + } + desc.tfm = tfm; + desc.flags = 0; + + sg_init_one(&sg, message, len); + crypto_hash_digest(&desc, &sg, len, out); + crypto_free_hash(tfm); + + return 1; +} + +int DWC_HMAC_SHA256(uint8_t *message, uint32_t messagelen, + uint8_t *key, uint32_t keylen, uint8_t *out) +{ + struct crypto_hash *tfm; + struct hash_desc desc; + struct scatterlist sg; + + tfm = crypto_alloc_hash("hmac(sha256)", 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(tfm)) { + DWC_ERROR("Failed to load transform for hmac(sha256): %ld", PTR_ERR(tfm)); + return 0; + } + desc.tfm = tfm; + desc.flags = 0; + + sg_init_one(&sg, message, messagelen); + crypto_hash_setkey(tfm, key, keylen); + crypto_hash_digest(&desc, &sg, messagelen, out); + crypto_free_hash(tfm); + + return 1; +} + +#endif /* DWC_CRYPTOLIB */ + + +/* Byte Ordering Conversions */ + +uint32_t DWC_CPU_TO_LE32(uint32_t *p) +{ +#ifdef __LITTLE_ENDIAN + return *p; +#else + uint8_t *u_p = (uint8_t *)p; + + return (u_p[3] | (u_p[2] << 8) | (u_p[1] << 16) | (u_p[0] << 24)); +#endif +} + +uint32_t DWC_CPU_TO_BE32(uint32_t *p) +{ +#ifdef __BIG_ENDIAN + return *p; +#else + uint8_t *u_p = (uint8_t *)p; + + return (u_p[3] | (u_p[2] << 8) | (u_p[1] << 16) | (u_p[0] << 24)); +#endif +} + +uint32_t DWC_LE32_TO_CPU(uint32_t *p) +{ +#ifdef __LITTLE_ENDIAN + return *p; +#else + uint8_t *u_p = (uint8_t *)p; + + return (u_p[3] | (u_p[2] << 8) | (u_p[1] << 16) | (u_p[0] << 24)); +#endif +} + +uint32_t DWC_BE32_TO_CPU(uint32_t *p) +{ +#ifdef __BIG_ENDIAN + return *p; +#else + uint8_t *u_p = (uint8_t *)p; + + return (u_p[3] | (u_p[2] << 8) | (u_p[1] << 16) | (u_p[0] << 24)); +#endif +} + +uint16_t DWC_CPU_TO_LE16(uint16_t *p) +{ +#ifdef __LITTLE_ENDIAN + return *p; +#else + uint8_t *u_p = (uint8_t *)p; + return (u_p[1] | (u_p[0] << 8)); +#endif +} + +uint16_t DWC_CPU_TO_BE16(uint16_t *p) +{ +#ifdef __BIG_ENDIAN + return *p; +#else + uint8_t *u_p = (uint8_t *)p; + return (u_p[1] | (u_p[0] << 8)); +#endif +} + +uint16_t DWC_LE16_TO_CPU(uint16_t *p) +{ +#ifdef __LITTLE_ENDIAN + return *p; +#else + uint8_t *u_p = (uint8_t *)p; + return (u_p[1] | (u_p[0] << 8)); +#endif +} + +uint16_t DWC_BE16_TO_CPU(uint16_t *p) +{ +#ifdef __BIG_ENDIAN + return *p; +#else + uint8_t *u_p = (uint8_t *)p; + return (u_p[1] | (u_p[0] << 8)); +#endif +} + + +/* Registers */ + +uint32_t DWC_READ_REG32(void *io_ctx, uint32_t volatile *reg) +{ + dwc_ioctx_t *io = (dwc_ioctx_t *)io_ctx; + bus_size_t ior = (bus_size_t)reg; + + return bus_space_read_4(io->iot, io->ioh, ior); +} + +#if 0 +uint64_t DWC_READ_REG64(void *io_ctx, uint64_t volatile *reg) +{ + dwc_ioctx_t *io = (dwc_ioctx_t *)io_ctx; + bus_size_t ior = (bus_size_t)reg; + + return bus_space_read_8(io->iot, io->ioh, ior); +} +#endif + +void DWC_WRITE_REG32(void *io_ctx, uint32_t volatile *reg, uint32_t value) +{ + dwc_ioctx_t *io = (dwc_ioctx_t *)io_ctx; + bus_size_t ior = (bus_size_t)reg; + + bus_space_write_4(io->iot, io->ioh, ior, value); +} + +#if 0 +void DWC_WRITE_REG64(void *io_ctx, uint64_t volatile *reg, uint64_t value) +{ + dwc_ioctx_t *io = (dwc_ioctx_t *)io_ctx; + bus_size_t ior = (bus_size_t)reg; + + bus_space_write_8(io->iot, io->ioh, ior, value); +} +#endif + +void DWC_MODIFY_REG32(void *io_ctx, uint32_t volatile *reg, uint32_t clear_mask, + uint32_t set_mask) +{ + dwc_ioctx_t *io = (dwc_ioctx_t *)io_ctx; + bus_size_t ior = (bus_size_t)reg; + + bus_space_write_4(io->iot, io->ioh, ior, + (bus_space_read_4(io->iot, io->ioh, ior) & + ~clear_mask) | set_mask); +} + +#if 0 +void DWC_MODIFY_REG64(void *io_ctx, uint64_t volatile *reg, uint64_t clear_mask, + uint64_t set_mask) +{ + dwc_ioctx_t *io = (dwc_ioctx_t *)io_ctx; + bus_size_t ior = (bus_size_t)reg; + + bus_space_write_8(io->iot, io->ioh, ior, + (bus_space_read_8(io->iot, io->ioh, ior) & + ~clear_mask) | set_mask); +} +#endif + + +/* Locking */ + +dwc_spinlock_t *DWC_SPINLOCK_ALLOC(void) +{ + struct simplelock *sl = DWC_ALLOC(sizeof(*sl)); + + if (!sl) { + DWC_ERROR("Cannot allocate memory for spinlock"); + return NULL; + } + + simple_lock_init(sl); + return (dwc_spinlock_t *)sl; +} + +void DWC_SPINLOCK_FREE(dwc_spinlock_t *lock) +{ + struct simplelock *sl = (struct simplelock *)lock; + + DWC_FREE(sl); +} + +void DWC_SPINLOCK(dwc_spinlock_t *lock) +{ + simple_lock((struct simplelock *)lock); +} + +void DWC_SPINUNLOCK(dwc_spinlock_t *lock) +{ + simple_unlock((struct simplelock *)lock); +} + +void DWC_SPINLOCK_IRQSAVE(dwc_spinlock_t *lock, dwc_irqflags_t *flags) +{ + simple_lock((struct simplelock *)lock); + *flags = splbio(); +} + +void DWC_SPINUNLOCK_IRQRESTORE(dwc_spinlock_t *lock, dwc_irqflags_t flags) +{ + splx(flags); + simple_unlock((struct simplelock *)lock); +} + +dwc_mutex_t *DWC_MUTEX_ALLOC(void) +{ + dwc_mutex_t *mutex = DWC_ALLOC(sizeof(struct lock)); + + if (!mutex) { + DWC_ERROR("Cannot allocate memory for mutex"); + return NULL; + } + + lockinit((struct lock *)mutex, 0, "dw3mtx", 0, 0); + return mutex; +} + +#if (defined(DWC_LINUX) && defined(CONFIG_DEBUG_MUTEXES)) +#else +void DWC_MUTEX_FREE(dwc_mutex_t *mutex) +{ + DWC_FREE(mutex); +} +#endif + +void DWC_MUTEX_LOCK(dwc_mutex_t *mutex) +{ + lockmgr((struct lock *)mutex, LK_EXCLUSIVE, NULL); +} + +int DWC_MUTEX_TRYLOCK(dwc_mutex_t *mutex) +{ + int status; + + status = lockmgr((struct lock *)mutex, LK_EXCLUSIVE | LK_NOWAIT, NULL); + return status == 0; +} + +void DWC_MUTEX_UNLOCK(dwc_mutex_t *mutex) +{ + lockmgr((struct lock *)mutex, LK_RELEASE, NULL); +} + + +/* Timing */ + +void DWC_UDELAY(uint32_t usecs) +{ + DELAY(usecs); +} + +void DWC_MDELAY(uint32_t msecs) +{ + do { + DELAY(1000); + } while (--msecs); +} + +void DWC_MSLEEP(uint32_t msecs) +{ + struct timeval tv; + + tv.tv_sec = msecs / 1000; + tv.tv_usec = (msecs - tv.tv_sec * 1000) * 1000; + tsleep(&tv, 0, "dw3slp", tvtohz(&tv)); +} + +uint32_t DWC_TIME(void) +{ + struct timeval tv; + + microuptime(&tv); // or getmicrouptime? (less precise, but faster) + return tv.tv_sec * 1000 + tv.tv_usec / 1000; +} + + +/* Timers */ + +struct dwc_timer { + struct callout t; + char *name; + dwc_spinlock_t *lock; + dwc_timer_callback_t cb; + void *data; +}; + +dwc_timer_t *DWC_TIMER_ALLOC(char *name, dwc_timer_callback_t cb, void *data) +{ + dwc_timer_t *t = DWC_ALLOC(sizeof(*t)); + + if (!t) { + DWC_ERROR("Cannot allocate memory for timer"); + return NULL; + } + + callout_init(&t->t); + + t->name = DWC_STRDUP(name); + if (!t->name) { + DWC_ERROR("Cannot allocate memory for timer->name"); + goto no_name; + } + + t->lock = DWC_SPINLOCK_ALLOC(); + if (!t->lock) { + DWC_ERROR("Cannot allocate memory for timer->lock"); + goto no_lock; + } + + t->cb = cb; + t->data = data; + + return t; + + no_lock: + DWC_FREE(t->name); + no_name: + DWC_FREE(t); + + return NULL; +} + +void DWC_TIMER_FREE(dwc_timer_t *timer) +{ + callout_stop(&timer->t); + DWC_SPINLOCK_FREE(timer->lock); + DWC_FREE(timer->name); + DWC_FREE(timer); +} + +void DWC_TIMER_SCHEDULE(dwc_timer_t *timer, uint32_t time) +{ + struct timeval tv; + + tv.tv_sec = time / 1000; + tv.tv_usec = (time - tv.tv_sec * 1000) * 1000; + callout_reset(&timer->t, tvtohz(&tv), timer->cb, timer->data); +} + +void DWC_TIMER_CANCEL(dwc_timer_t *timer) +{ + callout_stop(&timer->t); +} + + +/* Wait Queues */ + +struct dwc_waitq { + struct simplelock lock; + int abort; +}; + +dwc_waitq_t *DWC_WAITQ_ALLOC(void) +{ + dwc_waitq_t *wq = DWC_ALLOC(sizeof(*wq)); + + if (!wq) { + DWC_ERROR("Cannot allocate memory for waitqueue"); + return NULL; + } + + simple_lock_init(&wq->lock); + wq->abort = 0; + + return wq; +} + +void DWC_WAITQ_FREE(dwc_waitq_t *wq) +{ + DWC_FREE(wq); +} + +int32_t DWC_WAITQ_WAIT(dwc_waitq_t *wq, dwc_waitq_condition_t cond, void *data) +{ + int ipl; + int result = 0; + + simple_lock(&wq->lock); + ipl = splbio(); + + /* Skip the sleep if already aborted or triggered */ + if (!wq->abort && !cond(data)) { + splx(ipl); + result = ltsleep(wq, PCATCH, "dw3wat", 0, &wq->lock); // infinite timeout + ipl = splbio(); + } + + if (result == 0) { // awoken + if (wq->abort) { + wq->abort = 0; + result = -DWC_E_ABORT; + } else { + result = 0; + } + + splx(ipl); + simple_unlock(&wq->lock); + } else { + wq->abort = 0; + splx(ipl); + simple_unlock(&wq->lock); + + if (result == ERESTART) { // signaled - restart + result = -DWC_E_RESTART; + } else { // signaled - must be EINTR + result = -DWC_E_ABORT; + } + } + + return result; +} + +int32_t DWC_WAITQ_WAIT_TIMEOUT(dwc_waitq_t *wq, dwc_waitq_condition_t cond, + void *data, int32_t msecs) +{ + struct timeval tv, tv1, tv2; + int ipl; + int result = 0; + + tv.tv_sec = msecs / 1000; + tv.tv_usec = (msecs - tv.tv_sec * 1000) * 1000; + + simple_lock(&wq->lock); + ipl = splbio(); + + /* Skip the sleep if already aborted or triggered */ + if (!wq->abort && !cond(data)) { + splx(ipl); + getmicrouptime(&tv1); + result = ltsleep(wq, PCATCH, "dw3wto", tvtohz(&tv), &wq->lock); + getmicrouptime(&tv2); + ipl = splbio(); + } + + if (result == 0) { // awoken + if (wq->abort) { + wq->abort = 0; + splx(ipl); + simple_unlock(&wq->lock); + result = -DWC_E_ABORT; + } else { + splx(ipl); + simple_unlock(&wq->lock); + + tv2.tv_usec -= tv1.tv_usec; + if (tv2.tv_usec < 0) { + tv2.tv_usec += 1000000; + tv2.tv_sec--; + } + + tv2.tv_sec -= tv1.tv_sec; + result = tv2.tv_sec * 1000 + tv2.tv_usec / 1000; + result = msecs - result; + if (result <= 0) + result = 1; + } + } else { + wq->abort = 0; + splx(ipl); + simple_unlock(&wq->lock); + + if (result == ERESTART) { // signaled - restart + result = -DWC_E_RESTART; + + } else if (result == EINTR) { // signaled - interrupt + result = -DWC_E_ABORT; + + } else { // timed out + result = -DWC_E_TIMEOUT; + } + } + + return result; +} + +void DWC_WAITQ_TRIGGER(dwc_waitq_t *wq) +{ + wakeup(wq); +} + +void DWC_WAITQ_ABORT(dwc_waitq_t *wq) +{ + int ipl; + + simple_lock(&wq->lock); + ipl = splbio(); + wq->abort = 1; + wakeup(wq); + splx(ipl); + simple_unlock(&wq->lock); +} + + +/* Threading */ + +struct dwc_thread { + struct proc *proc; + int abort; +}; + +dwc_thread_t *DWC_THREAD_RUN(dwc_thread_function_t func, char *name, void *data) +{ + int retval; + dwc_thread_t *thread = DWC_ALLOC(sizeof(*thread)); + + if (!thread) { + return NULL; + } + + thread->abort = 0; + retval = kthread_create1((void (*)(void *))func, data, &thread->proc, + "%s", name); + if (retval) { + DWC_FREE(thread); + return NULL; + } + + return thread; +} + +int DWC_THREAD_STOP(dwc_thread_t *thread) +{ + int retval; + + thread->abort = 1; + retval = tsleep(&thread->abort, 0, "dw3stp", 60 * hz); + + if (retval == 0) { + /* DWC_THREAD_EXIT() will free the thread struct */ + return 0; + } + + /* NOTE: We leak the thread struct if thread doesn't die */ + + if (retval == EWOULDBLOCK) { + return -DWC_E_TIMEOUT; + } + + return -DWC_E_UNKNOWN; +} + +dwc_bool_t DWC_THREAD_SHOULD_STOP(dwc_thread_t *thread) +{ + return thread->abort; +} + +void DWC_THREAD_EXIT(dwc_thread_t *thread) +{ + wakeup(&thread->abort); + DWC_FREE(thread); + kthread_exit(0); +} + +/* tasklets + - Runs in interrupt context (cannot sleep) + - Each tasklet runs on a single CPU + - Different tasklets can be running simultaneously on different CPUs + [ On NetBSD there is no corresponding mechanism, drivers don't have bottom- + halves. So we just call the callback directly from DWC_TASK_SCHEDULE() ] + */ +struct dwc_tasklet { + dwc_tasklet_callback_t cb; + void *data; +}; + +static void tasklet_callback(void *data) +{ + dwc_tasklet_t *task = (dwc_tasklet_t *)data; + + task->cb(task->data); +} + +dwc_tasklet_t *DWC_TASK_ALLOC(char *name, dwc_tasklet_callback_t cb, void *data) +{ + dwc_tasklet_t *task = DWC_ALLOC(sizeof(*task)); + + if (task) { + task->cb = cb; + task->data = data; + } else { + DWC_ERROR("Cannot allocate memory for tasklet"); + } + + return task; +} + +void DWC_TASK_FREE(dwc_tasklet_t *task) +{ + DWC_FREE(task); +} + +void DWC_TASK_SCHEDULE(dwc_tasklet_t *task) +{ + tasklet_callback(task); +} + + +/* workqueues + - Runs in process context (can sleep) + */ +typedef struct work_container { + dwc_work_callback_t cb; + void *data; + dwc_workq_t *wq; + char *name; + int hz; + struct work task; +} work_container_t; + +struct dwc_workq { + struct workqueue *taskq; + dwc_spinlock_t *lock; + dwc_waitq_t *waitq; + int pending; + struct work_container *container; +}; + +static void do_work(struct work *task, void *data) +{ + dwc_workq_t *wq = (dwc_workq_t *)data; + work_container_t *container = wq->container; + dwc_irqflags_t flags; + + if (container->hz) { + tsleep(container, 0, "dw3wrk", container->hz); + } + + container->cb(container->data); + DWC_DEBUG("Work done: %s, container=%p", container->name, container); + + DWC_SPINLOCK_IRQSAVE(wq->lock, &flags); + if (container->name) + DWC_FREE(container->name); + DWC_FREE(container); + wq->pending--; + DWC_SPINUNLOCK_IRQRESTORE(wq->lock, flags); + DWC_WAITQ_TRIGGER(wq->waitq); +} + +static int work_done(void *data) +{ + dwc_workq_t *workq = (dwc_workq_t *)data; + + return workq->pending == 0; +} + +int DWC_WORKQ_WAIT_WORK_DONE(dwc_workq_t *workq, int timeout) +{ + return DWC_WAITQ_WAIT_TIMEOUT(workq->waitq, work_done, workq, timeout); +} + +dwc_workq_t *DWC_WORKQ_ALLOC(char *name) +{ + int result; + dwc_workq_t *wq = DWC_ALLOC(sizeof(*wq)); + + if (!wq) { + DWC_ERROR("Cannot allocate memory for workqueue"); + return NULL; + } + + result = workqueue_create(&wq->taskq, name, do_work, wq, 0 /*PWAIT*/, + IPL_BIO, 0); + if (result) { + DWC_ERROR("Cannot create workqueue"); + goto no_taskq; + } + + wq->pending = 0; + + wq->lock = DWC_SPINLOCK_ALLOC(); + if (!wq->lock) { + DWC_ERROR("Cannot allocate memory for spinlock"); + goto no_lock; + } + + wq->waitq = DWC_WAITQ_ALLOC(); + if (!wq->waitq) { + DWC_ERROR("Cannot allocate memory for waitqueue"); + goto no_waitq; + } + + return wq; + + no_waitq: + DWC_SPINLOCK_FREE(wq->lock); + no_lock: + workqueue_destroy(wq->taskq); + no_taskq: + DWC_FREE(wq); + + return NULL; +} + +void DWC_WORKQ_FREE(dwc_workq_t *wq) +{ +#ifdef DEBUG + dwc_irqflags_t flags; + + DWC_SPINLOCK_IRQSAVE(wq->lock, &flags); + + if (wq->pending != 0) { + struct work_container *container = wq->container; + + DWC_ERROR("Destroying work queue with pending work"); + + if (container && container->name) { + DWC_ERROR("Work %s still pending", container->name); + } + } + + DWC_SPINUNLOCK_IRQRESTORE(wq->lock, flags); +#endif + DWC_WAITQ_FREE(wq->waitq); + DWC_SPINLOCK_FREE(wq->lock); + workqueue_destroy(wq->taskq); + DWC_FREE(wq); +} + +void DWC_WORKQ_SCHEDULE(dwc_workq_t *wq, dwc_work_callback_t cb, void *data, + char *format, ...) +{ + dwc_irqflags_t flags; + work_container_t *container; + static char name[128]; + va_list args; + + va_start(args, format); + DWC_VSNPRINTF(name, 128, format, args); + va_end(args); + + DWC_SPINLOCK_IRQSAVE(wq->lock, &flags); + wq->pending++; + DWC_SPINUNLOCK_IRQRESTORE(wq->lock, flags); + DWC_WAITQ_TRIGGER(wq->waitq); + + container = DWC_ALLOC_ATOMIC(sizeof(*container)); + if (!container) { + DWC_ERROR("Cannot allocate memory for container"); + return; + } + + container->name = DWC_STRDUP(name); + if (!container->name) { + DWC_ERROR("Cannot allocate memory for container->name"); + DWC_FREE(container); + return; + } + + container->cb = cb; + container->data = data; + container->wq = wq; + container->hz = 0; + wq->container = container; + + DWC_DEBUG("Queueing work: %s, container=%p", container->name, container); + workqueue_enqueue(wq->taskq, &container->task); +} + +void DWC_WORKQ_SCHEDULE_DELAYED(dwc_workq_t *wq, dwc_work_callback_t cb, + void *data, uint32_t time, char *format, ...) +{ + dwc_irqflags_t flags; + work_container_t *container; + static char name[128]; + struct timeval tv; + va_list args; + + va_start(args, format); + DWC_VSNPRINTF(name, 128, format, args); + va_end(args); + + DWC_SPINLOCK_IRQSAVE(wq->lock, &flags); + wq->pending++; + DWC_SPINUNLOCK_IRQRESTORE(wq->lock, flags); + DWC_WAITQ_TRIGGER(wq->waitq); + + container = DWC_ALLOC_ATOMIC(sizeof(*container)); + if (!container) { + DWC_ERROR("Cannot allocate memory for container"); + return; + } + + container->name = DWC_STRDUP(name); + if (!container->name) { + DWC_ERROR("Cannot allocate memory for container->name"); + DWC_FREE(container); + return; + } + + container->cb = cb; + container->data = data; + container->wq = wq; + tv.tv_sec = time / 1000; + tv.tv_usec = (time - tv.tv_sec * 1000) * 1000; + container->hz = tvtohz(&tv); + wq->container = container; + + DWC_DEBUG("Queueing work: %s, container=%p", container->name, container); + workqueue_enqueue(wq->taskq, &container->task); +} + +int DWC_WORKQ_PENDING(dwc_workq_t *wq) +{ + return wq->pending; +} diff --git a/drivers/usb/host/dwc_common_port/dwc_crypto.c b/drivers/usb/host/dwc_common_port/dwc_crypto.c new file mode 100644 index 00000000000000..3b0353296148f6 --- /dev/null +++ b/drivers/usb/host/dwc_common_port/dwc_crypto.c @@ -0,0 +1,308 @@ +/* ========================================================================= + * $File: //dwh/usb_iip/dev/software/dwc_common_port_2/dwc_crypto.c $ + * $Revision: #5 $ + * $Date: 2010/09/28 $ + * $Change: 1596182 $ + * + * Synopsys Portability Library Software and documentation + * (hereinafter, "Software") is an Unsupported proprietary work of + * Synopsys, Inc. unless otherwise expressly agreed to in writing + * between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product + * under any End User Software License Agreement or Agreement for + * Licensed Product with Synopsys or any supplement thereto. You are + * permitted to use and redistribute this Software in source and binary + * forms, with or without modification, provided that redistributions + * of source code must retain this notice. You may not view, use, + * disclose, copy or distribute this file or any information contained + * herein except pursuant to this license grant from Synopsys. If you + * do not agree with this notice, including the disclaimer below, then + * you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" + * BASIS AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE HEREBY DISCLAIMED. IN NO EVENT SHALL + * SYNOPSYS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================= */ + +/** @file + * This file contains the WUSB cryptographic routines. + */ + +#ifdef DWC_CRYPTOLIB + +#include "dwc_crypto.h" +#include "usb.h" + +#ifdef DEBUG +static inline void dump_bytes(char *name, uint8_t *bytes, int len) +{ + int i; + DWC_PRINTF("%s: ", name); + for (i=0; i<len; i++) { + DWC_PRINTF("%02x ", bytes[i]); + } + DWC_PRINTF("\n"); +} +#else +#define dump_bytes(x...) +#endif + +/* Display a block */ +void show_block(const u8 *blk, const char *prefix, const char *suffix, int a) +{ +#ifdef DWC_DEBUG_CRYPTO + int i, blksize = 16; + + DWC_DEBUG("%s", prefix); + + if (suffix == NULL) { + suffix = "\n"; + blksize = a; + } + + for (i = 0; i < blksize; i++) + DWC_PRINT("%02x%s", *blk++, ((i & 3) == 3) ? " " : " "); + DWC_PRINT(suffix); +#endif +} + +/** + * Encrypts an array of bytes using the AES encryption engine. + * If <code>dst</code> == <code>src</code>, then the bytes will be encrypted + * in-place. + * + * @return 0 on success, negative error code on error. + */ +int dwc_wusb_aes_encrypt(u8 *src, u8 *key, u8 *dst) +{ + u8 block_t[16]; + DWC_MEMSET(block_t, 0, 16); + + return DWC_AES_CBC(src, 16, key, 16, block_t, dst); +} + +/** + * The CCM-MAC-FUNCTION described in section 6.5 of the WUSB spec. + * This function takes a data string and returns the encrypted CBC + * Counter-mode MIC. + * + * @param key The 128-bit symmetric key. + * @param nonce The CCM nonce. + * @param label The unique 14-byte ASCII text label. + * @param bytes The byte array to be encrypted. + * @param len Length of the byte array. + * @param result Byte array to receive the 8-byte encrypted MIC. + */ +void dwc_wusb_cmf(u8 *key, u8 *nonce, + char *label, u8 *bytes, int len, u8 *result) +{ + u8 block_m[16]; + u8 block_x[16]; + u8 block_t[8]; + int idx, blkNum; + u16 la = (u16)(len + 14); + + /* Set the AES-128 key */ + //dwc_aes_setkey(tfm, key, 16); + + /* Fill block B0 from flags = 0x59, N, and l(m) = 0 */ + block_m[0] = 0x59; + for (idx = 0; idx < 13; idx++) + block_m[idx + 1] = nonce[idx]; + block_m[14] = 0; + block_m[15] = 0; + + /* Produce the CBC IV */ + dwc_wusb_aes_encrypt(block_m, key, block_x); + show_block(block_m, "CBC IV in: ", "\n", 0); + show_block(block_x, "CBC IV out:", "\n", 0); + + /* Fill block B1 from l(a) = Blen + 14, and A */ + block_x[0] ^= (u8)(la >> 8); + block_x[1] ^= (u8)la; + for (idx = 0; idx < 14; idx++) + block_x[idx + 2] ^= label[idx]; + show_block(block_x, "After xor: ", "b1\n", 16); + + dwc_wusb_aes_encrypt(block_x, key, block_x); + show_block(block_x, "After AES: ", "b1\n", 16); + + idx = 0; + blkNum = 0; + + /* Fill remaining blocks with B */ + while (len-- > 0) { + block_x[idx] ^= *bytes++; + if (++idx >= 16) { + idx = 0; + show_block(block_x, "After xor: ", "\n", blkNum); + dwc_wusb_aes_encrypt(block_x, key, block_x); + show_block(block_x, "After AES: ", "\n", blkNum); + blkNum++; + } + } + + /* Handle partial last block */ + if (idx > 0) { + show_block(block_x, "After xor: ", "\n", blkNum); + dwc_wusb_aes_encrypt(block_x, key, block_x); + show_block(block_x, "After AES: ", "\n", blkNum); + } + + /* Save the MIC tag */ + DWC_MEMCPY(block_t, block_x, 8); + show_block(block_t, "MIC tag : ", NULL, 8); + + /* Fill block A0 from flags = 0x01, N, and counter = 0 */ + block_m[0] = 0x01; + block_m[14] = 0; + block_m[15] = 0; + + /* Encrypt the counter */ + dwc_wusb_aes_encrypt(block_m, key, block_x); + show_block(block_x, "CTR[MIC] : ", NULL, 8); + + /* XOR with MIC tag */ + for (idx = 0; idx < 8; idx++) { + block_t[idx] ^= block_x[idx]; + } + + /* Return result to caller */ + DWC_MEMCPY(result, block_t, 8); + show_block(result, "CCM-MIC : ", NULL, 8); + +} + +/** + * The PRF function described in section 6.5 of the WUSB spec. This function + * concatenates MIC values returned from dwc_cmf() to create a value of + * the requested length. + * + * @param prf_len Length of the PRF function in bits (64, 128, or 256). + * @param key, nonce, label, bytes, len Same as for dwc_cmf(). + * @param result Byte array to receive the result. + */ +void dwc_wusb_prf(int prf_len, u8 *key, + u8 *nonce, char *label, u8 *bytes, int len, u8 *result) +{ + int i; + + nonce[0] = 0; + for (i = 0; i < prf_len >> 6; i++, nonce[0]++) { + dwc_wusb_cmf(key, nonce, label, bytes, len, result); + result += 8; + } +} + +/** + * Fills in CCM Nonce per the WUSB spec. + * + * @param[in] haddr Host address. + * @param[in] daddr Device address. + * @param[in] tkid Session Key(PTK) identifier. + * @param[out] nonce Pointer to where the CCM Nonce output is to be written. + */ +void dwc_wusb_fill_ccm_nonce(uint16_t haddr, uint16_t daddr, uint8_t *tkid, + uint8_t *nonce) +{ + + DWC_DEBUG("%s %x %x\n", __func__, daddr, haddr); + + DWC_MEMSET(&nonce[0], 0, 16); + + DWC_MEMCPY(&nonce[6], tkid, 3); + nonce[9] = daddr & 0xFF; + nonce[10] = (daddr >> 8) & 0xFF; + nonce[11] = haddr & 0xFF; + nonce[12] = (haddr >> 8) & 0xFF; + + dump_bytes("CCM nonce", nonce, 16); +} + +/** + * Generates a 16-byte cryptographic-grade random number for the Host/Device + * Nonce. + */ +void dwc_wusb_gen_nonce(uint16_t addr, uint8_t *nonce) +{ + uint8_t inonce[16]; + uint32_t temp[4]; + + /* Fill in the Nonce */ + DWC_MEMSET(&inonce[0], 0, sizeof(inonce)); + inonce[9] = addr & 0xFF; + inonce[10] = (addr >> 8) & 0xFF; + inonce[11] = inonce[9]; + inonce[12] = inonce[10]; + + /* Collect "randomness samples" */ + DWC_RANDOM_BYTES((uint8_t *)temp, 16); + + dwc_wusb_prf_128((uint8_t *)temp, nonce, + "Random Numbers", (uint8_t *)temp, sizeof(temp), + nonce); +} + +/** + * Generates the Session Key (PTK) and Key Confirmation Key (KCK) per the + * WUSB spec. + * + * @param[in] ccm_nonce Pointer to CCM Nonce. + * @param[in] mk Master Key to derive the session from + * @param[in] hnonce Pointer to Host Nonce. + * @param[in] dnonce Pointer to Device Nonce. + * @param[out] kck Pointer to where the KCK output is to be written. + * @param[out] ptk Pointer to where the PTK output is to be written. + */ +void dwc_wusb_gen_key(uint8_t *ccm_nonce, uint8_t *mk, uint8_t *hnonce, + uint8_t *dnonce, uint8_t *kck, uint8_t *ptk) +{ + uint8_t idata[32]; + uint8_t odata[32]; + + dump_bytes("ck", mk, 16); + dump_bytes("hnonce", hnonce, 16); + dump_bytes("dnonce", dnonce, 16); + + /* The data is the HNonce and DNonce concatenated */ + DWC_MEMCPY(&idata[0], hnonce, 16); + DWC_MEMCPY(&idata[16], dnonce, 16); + + dwc_wusb_prf_256(mk, ccm_nonce, "Pair-wise keys", idata, 32, odata); + + /* Low 16 bytes of the result is the KCK, high 16 is the PTK */ + DWC_MEMCPY(kck, &odata[0], 16); + DWC_MEMCPY(ptk, &odata[16], 16); + + dump_bytes("kck", kck, 16); + dump_bytes("ptk", ptk, 16); +} + +/** + * Generates the Message Integrity Code over the Handshake data per the + * WUSB spec. + * + * @param ccm_nonce Pointer to CCM Nonce. + * @param kck Pointer to Key Confirmation Key. + * @param data Pointer to Handshake data to be checked. + * @param mic Pointer to where the MIC output is to be written. + */ +void dwc_wusb_gen_mic(uint8_t *ccm_nonce, uint8_t *kck, + uint8_t *data, uint8_t *mic) +{ + + dwc_wusb_prf_64(kck, ccm_nonce, "out-of-bandMIC", + data, WUSB_HANDSHAKE_LEN_FOR_MIC, mic); +} + +#endif /* DWC_CRYPTOLIB */ diff --git a/drivers/usb/host/dwc_common_port/dwc_crypto.h b/drivers/usb/host/dwc_common_port/dwc_crypto.h new file mode 100644 index 00000000000000..26fcddcfe9ba4b --- /dev/null +++ b/drivers/usb/host/dwc_common_port/dwc_crypto.h @@ -0,0 +1,111 @@ +/* ========================================================================= + * $File: //dwh/usb_iip/dev/software/dwc_common_port_2/dwc_crypto.h $ + * $Revision: #3 $ + * $Date: 2010/09/28 $ + * $Change: 1596182 $ + * + * Synopsys Portability Library Software and documentation + * (hereinafter, "Software") is an Unsupported proprietary work of + * Synopsys, Inc. unless otherwise expressly agreed to in writing + * between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product + * under any End User Software License Agreement or Agreement for + * Licensed Product with Synopsys or any supplement thereto. You are + * permitted to use and redistribute this Software in source and binary + * forms, with or without modification, provided that redistributions + * of source code must retain this notice. You may not view, use, + * disclose, copy or distribute this file or any information contained + * herein except pursuant to this license grant from Synopsys. If you + * do not agree with this notice, including the disclaimer below, then + * you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" + * BASIS AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE HEREBY DISCLAIMED. IN NO EVENT SHALL + * SYNOPSYS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================= */ + +#ifndef _DWC_CRYPTO_H_ +#define _DWC_CRYPTO_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/** @file + * + * This file contains declarations for the WUSB Cryptographic routines as + * defined in the WUSB spec. They are only to be used internally by the DWC UWB + * modules. + */ + +#include "dwc_os.h" + +int dwc_wusb_aes_encrypt(u8 *src, u8 *key, u8 *dst); + +void dwc_wusb_cmf(u8 *key, u8 *nonce, + char *label, u8 *bytes, int len, u8 *result); +void dwc_wusb_prf(int prf_len, u8 *key, + u8 *nonce, char *label, u8 *bytes, int len, u8 *result); + +/** + * The PRF-64 function described in section 6.5 of the WUSB spec. + * + * @param key, nonce, label, bytes, len, result Same as for dwc_prf(). + */ +static inline void dwc_wusb_prf_64(u8 *key, u8 *nonce, + char *label, u8 *bytes, int len, u8 *result) +{ + dwc_wusb_prf(64, key, nonce, label, bytes, len, result); +} + +/** + * The PRF-128 function described in section 6.5 of the WUSB spec. + * + * @param key, nonce, label, bytes, len, result Same as for dwc_prf(). + */ +static inline void dwc_wusb_prf_128(u8 *key, u8 *nonce, + char *label, u8 *bytes, int len, u8 *result) +{ + dwc_wusb_prf(128, key, nonce, label, bytes, len, result); +} + +/** + * The PRF-256 function described in section 6.5 of the WUSB spec. + * + * @param key, nonce, label, bytes, len, result Same as for dwc_prf(). + */ +static inline void dwc_wusb_prf_256(u8 *key, u8 *nonce, + char *label, u8 *bytes, int len, u8 *result) +{ + dwc_wusb_prf(256, key, nonce, label, bytes, len, result); +} + + +void dwc_wusb_fill_ccm_nonce(uint16_t haddr, uint16_t daddr, uint8_t *tkid, + uint8_t *nonce); +void dwc_wusb_gen_nonce(uint16_t addr, + uint8_t *nonce); + +void dwc_wusb_gen_key(uint8_t *ccm_nonce, uint8_t *mk, + uint8_t *hnonce, uint8_t *dnonce, + uint8_t *kck, uint8_t *ptk); + + +void dwc_wusb_gen_mic(uint8_t *ccm_nonce, uint8_t + *kck, uint8_t *data, uint8_t *mic); + +#ifdef __cplusplus +} +#endif + +#endif /* _DWC_CRYPTO_H_ */ diff --git a/drivers/usb/host/dwc_common_port/dwc_dh.c b/drivers/usb/host/dwc_common_port/dwc_dh.c new file mode 100644 index 00000000000000..2b429a32aaf090 --- /dev/null +++ b/drivers/usb/host/dwc_common_port/dwc_dh.c @@ -0,0 +1,291 @@ +/* ========================================================================= + * $File: //dwh/usb_iip/dev/software/dwc_common_port_2/dwc_dh.c $ + * $Revision: #3 $ + * $Date: 2010/09/28 $ + * $Change: 1596182 $ + * + * Synopsys Portability Library Software and documentation + * (hereinafter, "Software") is an Unsupported proprietary work of + * Synopsys, Inc. unless otherwise expressly agreed to in writing + * between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product + * under any End User Software License Agreement or Agreement for + * Licensed Product with Synopsys or any supplement thereto. You are + * permitted to use and redistribute this Software in source and binary + * forms, with or without modification, provided that redistributions + * of source code must retain this notice. You may not view, use, + * disclose, copy or distribute this file or any information contained + * herein except pursuant to this license grant from Synopsys. If you + * do not agree with this notice, including the disclaimer below, then + * you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" + * BASIS AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE HEREBY DISCLAIMED. IN NO EVENT SHALL + * SYNOPSYS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================= */ +#ifdef DWC_CRYPTOLIB + +#ifndef CONFIG_MACH_IPMATE + +#include "dwc_dh.h" +#include "dwc_modpow.h" + +#ifdef DEBUG +/* This function prints out a buffer in the format described in the Association + * Model specification. */ +static void dh_dump(char *str, void *_num, int len) +{ + uint8_t *num = _num; + int i; + DWC_PRINTF("%s\n", str); + for (i = 0; i < len; i ++) { + DWC_PRINTF("%02x", num[i]); + if (((i + 1) % 2) == 0) DWC_PRINTF(" "); + if (((i + 1) % 26) == 0) DWC_PRINTF("\n"); + } + + DWC_PRINTF("\n"); +} +#else +#define dh_dump(_x...) do {; } while(0) +#endif + +/* Constant g value */ +static __u32 dh_g[] = { + 0x02000000, +}; + +/* Constant p value */ +static __u32 dh_p[] = { + 0xFFFFFFFF, 0xFFFFFFFF, 0xA2DA0FC9, 0x34C26821, 0x8B62C6C4, 0xD11CDC80, 0x084E0229, 0x74CC678A, + 0xA6BE0B02, 0x229B133B, 0x79084A51, 0xDD04348E, 0xB31995EF, 0x1B433ACD, 0x6D0A2B30, 0x37145FF2, + 0x6D35E14F, 0x45C2516D, 0x76B585E4, 0xC67E5E62, 0xE9424CF4, 0x6BED37A6, 0xB65CFF0B, 0xEDB706F4, + 0xFB6B38EE, 0xA59F895A, 0x11249FAE, 0xE61F4B7C, 0x51662849, 0x3D5BE4EC, 0xB87C00C2, 0x05BF63A1, + 0x3648DA98, 0x9AD3551C, 0xA83F1669, 0x5FCF24FD, 0x235D6583, 0x96ADA3DC, 0x56F3621C, 0xBB528520, + 0x0729D59E, 0x6D969670, 0x4E350C67, 0x0498BC4A, 0x086C74F1, 0x7C2118CA, 0x465E9032, 0x3BCE362E, + 0x2C779EE3, 0x03860E18, 0xA283279B, 0x8FA207EC, 0xF05DC5B5, 0xC9524C6F, 0xF6CB2BDE, 0x18175895, + 0x7C499539, 0xE56A95EA, 0x1826D215, 0x1005FA98, 0x5A8E7215, 0x2DC4AA8A, 0x0D1733AD, 0x337A5004, + 0xAB2155A8, 0x64BA1CDF, 0x0485FBEC, 0x0AEFDB58, 0x5771EA8A, 0x7D0C065D, 0x850F97B3, 0xC7E4E1A6, + 0x8CAEF5AB, 0xD73309DB, 0xE0948C1E, 0x9D61254A, 0x26D2E3CE, 0x6BEED21A, 0x06FA2FF1, 0x64088AD9, + 0x730276D8, 0x646AC83E, 0x182B1F52, 0x0C207B17, 0x5717E1BB, 0x6C5D617A, 0xC0880977, 0xE246D9BA, + 0xA04FE208, 0x31ABE574, 0xFC5BDB43, 0x8E10FDE0, 0x20D1824B, 0xCAD23AA9, 0xFFFFFFFF, 0xFFFFFFFF, +}; + +static void dh_swap_bytes(void *_in, void *_out, uint32_t len) +{ + uint8_t *in = _in; + uint8_t *out = _out; + int i; + for (i=0; i<len; i++) { + out[i] = in[len-1-i]; + } +} + +/* Computes the modular exponentiation (num^exp % mod). num, exp, and mod are + * big endian numbers of size len, in bytes. Each len value must be a multiple + * of 4. */ +int dwc_dh_modpow(void *mem_ctx, void *num, uint32_t num_len, + void *exp, uint32_t exp_len, + void *mod, uint32_t mod_len, + void *out) +{ + /* modpow() takes little endian numbers. AM uses big-endian. This + * function swaps bytes of numbers before passing onto modpow. */ + + int retval = 0; + uint32_t *result; + + uint32_t *bignum_num = dwc_alloc(mem_ctx, num_len + 4); + uint32_t *bignum_exp = dwc_alloc(mem_ctx, exp_len + 4); + uint32_t *bignum_mod = dwc_alloc(mem_ctx, mod_len + 4); + + dh_swap_bytes(num, &bignum_num[1], num_len); + bignum_num[0] = num_len / 4; + + dh_swap_bytes(exp, &bignum_exp[1], exp_len); + bignum_exp[0] = exp_len / 4; + + dh_swap_bytes(mod, &bignum_mod[1], mod_len); + bignum_mod[0] = mod_len / 4; + + result = dwc_modpow(mem_ctx, bignum_num, bignum_exp, bignum_mod); + if (!result) { + retval = -1; + goto dh_modpow_nomem; + } + + dh_swap_bytes(&result[1], out, result[0] * 4); + dwc_free(mem_ctx, result); + + dh_modpow_nomem: + dwc_free(mem_ctx, bignum_num); + dwc_free(mem_ctx, bignum_exp); + dwc_free(mem_ctx, bignum_mod); + return retval; +} + + +int dwc_dh_pk(void *mem_ctx, uint8_t nd, uint8_t *exp, uint8_t *pk, uint8_t *hash) +{ + int retval; + uint8_t m3[385]; + +#ifndef DH_TEST_VECTORS + DWC_RANDOM_BYTES(exp, 32); +#endif + + /* Compute the pkd */ + if ((retval = dwc_dh_modpow(mem_ctx, dh_g, 4, + exp, 32, + dh_p, 384, pk))) { + return retval; + } + + m3[384] = nd; + DWC_MEMCPY(&m3[0], pk, 384); + DWC_SHA256(m3, 385, hash); + + dh_dump("PK", pk, 384); + dh_dump("SHA-256(M3)", hash, 32); + return 0; +} + +int dwc_dh_derive_keys(void *mem_ctx, uint8_t nd, uint8_t *pkh, uint8_t *pkd, + uint8_t *exp, int is_host, + char *dd, uint8_t *ck, uint8_t *kdk) +{ + int retval; + uint8_t mv[784]; + uint8_t sha_result[32]; + uint8_t dhkey[384]; + uint8_t shared_secret[384]; + char *message; + uint32_t vd; + + uint8_t *pk; + + if (is_host) { + pk = pkd; + } + else { + pk = pkh; + } + + if ((retval = dwc_dh_modpow(mem_ctx, pk, 384, + exp, 32, + dh_p, 384, shared_secret))) { + return retval; + } + dh_dump("Shared Secret", shared_secret, 384); + + DWC_SHA256(shared_secret, 384, dhkey); + dh_dump("DHKEY", dhkey, 384); + + DWC_MEMCPY(&mv[0], pkd, 384); + DWC_MEMCPY(&mv[384], pkh, 384); + DWC_MEMCPY(&mv[768], "displayed digest", 16); + dh_dump("MV", mv, 784); + + DWC_SHA256(mv, 784, sha_result); + dh_dump("SHA-256(MV)", sha_result, 32); + dh_dump("First 32-bits of SHA-256(MV)", sha_result, 4); + + dh_swap_bytes(sha_result, &vd, 4); +#ifdef DEBUG + DWC_PRINTF("Vd (decimal) = %d\n", vd); +#endif + + switch (nd) { + case 2: + vd = vd % 100; + DWC_SPRINTF(dd, "%02d", vd); + break; + case 3: + vd = vd % 1000; + DWC_SPRINTF(dd, "%03d", vd); + break; + case 4: + vd = vd % 10000; + DWC_SPRINTF(dd, "%04d", vd); + break; + } +#ifdef DEBUG + DWC_PRINTF("Display Digits: %s\n", dd); +#endif + + message = "connection key"; + DWC_HMAC_SHA256(message, DWC_STRLEN(message), dhkey, 32, sha_result); + dh_dump("HMAC(SHA-256, DHKey, connection key)", sha_result, 32); + DWC_MEMCPY(ck, sha_result, 16); + + message = "key derivation key"; + DWC_HMAC_SHA256(message, DWC_STRLEN(message), dhkey, 32, sha_result); + dh_dump("HMAC(SHA-256, DHKey, key derivation key)", sha_result, 32); + DWC_MEMCPY(kdk, sha_result, 32); + + return 0; +} + + +#ifdef DH_TEST_VECTORS + +static __u8 dh_a[] = { + 0x44, 0x00, 0x51, 0xd6, + 0xf0, 0xb5, 0x5e, 0xa9, + 0x67, 0xab, 0x31, 0xc6, + 0x8a, 0x8b, 0x5e, 0x37, + 0xd9, 0x10, 0xda, 0xe0, + 0xe2, 0xd4, 0x59, 0xa4, + 0x86, 0x45, 0x9c, 0xaa, + 0xdf, 0x36, 0x75, 0x16, +}; + +static __u8 dh_b[] = { + 0x5d, 0xae, 0xc7, 0x86, + 0x79, 0x80, 0xa3, 0x24, + 0x8c, 0xe3, 0x57, 0x8f, + 0xc7, 0x5f, 0x1b, 0x0f, + 0x2d, 0xf8, 0x9d, 0x30, + 0x6f, 0xa4, 0x52, 0xcd, + 0xe0, 0x7a, 0x04, 0x8a, + 0xde, 0xd9, 0x26, 0x56, +}; + +void dwc_run_dh_test_vectors(void *mem_ctx) +{ + uint8_t pkd[384]; + uint8_t pkh[384]; + uint8_t hashd[32]; + uint8_t hashh[32]; + uint8_t ck[16]; + uint8_t kdk[32]; + char dd[5]; + + DWC_PRINTF("\n\n\nDH_TEST_VECTORS\n\n"); + + /* compute the PKd and SHA-256(PKd || Nd) */ + DWC_PRINTF("Computing PKd\n"); + dwc_dh_pk(mem_ctx, 2, dh_a, pkd, hashd); + + /* compute the PKd and SHA-256(PKh || Nd) */ + DWC_PRINTF("Computing PKh\n"); + dwc_dh_pk(mem_ctx, 2, dh_b, pkh, hashh); + + /* compute the dhkey */ + dwc_dh_derive_keys(mem_ctx, 2, pkh, pkd, dh_a, 0, dd, ck, kdk); +} +#endif /* DH_TEST_VECTORS */ + +#endif /* !CONFIG_MACH_IPMATE */ + +#endif /* DWC_CRYPTOLIB */ diff --git a/drivers/usb/host/dwc_common_port/dwc_dh.h b/drivers/usb/host/dwc_common_port/dwc_dh.h new file mode 100644 index 00000000000000..25c1cc0d588a44 --- /dev/null +++ b/drivers/usb/host/dwc_common_port/dwc_dh.h @@ -0,0 +1,106 @@ +/* ========================================================================= + * $File: //dwh/usb_iip/dev/software/dwc_common_port_2/dwc_dh.h $ + * $Revision: #4 $ + * $Date: 2010/09/28 $ + * $Change: 1596182 $ + * + * Synopsys Portability Library Software and documentation + * (hereinafter, "Software") is an Unsupported proprietary work of + * Synopsys, Inc. unless otherwise expressly agreed to in writing + * between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product + * under any End User Software License Agreement or Agreement for + * Licensed Product with Synopsys or any supplement thereto. You are + * permitted to use and redistribute this Software in source and binary + * forms, with or without modification, provided that redistributions + * of source code must retain this notice. You may not view, use, + * disclose, copy or distribute this file or any information contained + * herein except pursuant to this license grant from Synopsys. If you + * do not agree with this notice, including the disclaimer below, then + * you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" + * BASIS AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE HEREBY DISCLAIMED. IN NO EVENT SHALL + * SYNOPSYS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================= */ +#ifndef _DWC_DH_H_ +#define _DWC_DH_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "dwc_os.h" + +/** @file + * + * This file defines the common functions on device and host for performing + * numeric association as defined in the WUSB spec. They are only to be + * used internally by the DWC UWB modules. */ + +extern int dwc_dh_sha256(uint8_t *message, uint32_t len, uint8_t *out); +extern int dwc_dh_hmac_sha256(uint8_t *message, uint32_t messagelen, + uint8_t *key, uint32_t keylen, + uint8_t *out); +extern int dwc_dh_modpow(void *mem_ctx, void *num, uint32_t num_len, + void *exp, uint32_t exp_len, + void *mod, uint32_t mod_len, + void *out); + +/** Computes PKD or PKH, and SHA-256(PKd || Nd) + * + * PK = g^exp mod p. + * + * Input: + * Nd = Number of digits on the device. + * + * Output: + * exp = A 32-byte buffer to be filled with a randomly generated number. + * used as either A or B. + * pk = A 384-byte buffer to be filled with the PKH or PKD. + * hash = A 32-byte buffer to be filled with SHA-256(PK || ND). + */ +extern int dwc_dh_pk(void *mem_ctx, uint8_t nd, uint8_t *exp, uint8_t *pkd, uint8_t *hash); + +/** Computes the DHKEY, and VD. + * + * If called from host, then it will comput DHKEY=PKD^exp % p. + * If called from device, then it will comput DHKEY=PKH^exp % p. + * + * Input: + * pkd = The PKD value. + * pkh = The PKH value. + * exp = The A value (if device) or B value (if host) generated in dwc_wudev_dh_pk. + * is_host = Set to non zero if a WUSB host is calling this function. + * + * Output: + + * dd = A pointer to an buffer to be set to the displayed digits string to be shown + * to the user. This buffer should be at 5 bytes long to hold 4 digits plus a + * null termination character. This buffer can be used directly for display. + * ck = A 16-byte buffer to be filled with the CK. + * kdk = A 32-byte buffer to be filled with the KDK. + */ +extern int dwc_dh_derive_keys(void *mem_ctx, uint8_t nd, uint8_t *pkh, uint8_t *pkd, + uint8_t *exp, int is_host, + char *dd, uint8_t *ck, uint8_t *kdk); + +#ifdef DH_TEST_VECTORS +extern void dwc_run_dh_test_vectors(void); +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* _DWC_DH_H_ */ diff --git a/drivers/usb/host/dwc_common_port/dwc_list.h b/drivers/usb/host/dwc_common_port/dwc_list.h new file mode 100644 index 00000000000000..4ce560df0cae63 --- /dev/null +++ b/drivers/usb/host/dwc_common_port/dwc_list.h @@ -0,0 +1,594 @@ +/* $OpenBSD: queue.h,v 1.26 2004/05/04 16:59:32 grange Exp $ */ +/* $NetBSD: queue.h,v 1.11 1996/05/16 05:17:14 mycroft Exp $ */ + +/* + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)queue.h 8.5 (Berkeley) 8/20/94 + */ + +#ifndef _DWC_LIST_H_ +#define _DWC_LIST_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/** @file + * + * This file defines linked list operations. It is derived from BSD with + * only the MACRO names being prefixed with DWC_. This is because a few of + * these names conflict with those on Linux. For documentation on use, see the + * inline comments in the source code. The original license for this source + * code applies and is preserved in the dwc_list.h source file. + */ + +/* + * This file defines five types of data structures: singly-linked lists, + * lists, simple queues, tail queues, and circular queues. + * + * + * A singly-linked list is headed by a single forward pointer. The elements + * are singly linked for minimum space and pointer manipulation overhead at + * the expense of O(n) removal for arbitrary elements. New elements can be + * added to the list after an existing element or at the head of the list. + * Elements being removed from the head of the list should use the explicit + * macro for this purpose for optimum efficiency. A singly-linked list may + * only be traversed in the forward direction. Singly-linked lists are ideal + * for applications with large datasets and few or no removals or for + * implementing a LIFO queue. + * + * A list is headed by a single forward pointer (or an array of forward + * pointers for a hash table header). The elements are doubly linked + * so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before + * or after an existing element or at the head of the list. A list + * may only be traversed in the forward direction. + * + * A simple queue is headed by a pair of pointers, one the head of the + * list and the other to the tail of the list. The elements are singly + * linked to save space, so elements can only be removed from the + * head of the list. New elements can be added to the list before or after + * an existing element, at the head of the list, or at the end of the + * list. A simple queue may only be traversed in the forward direction. + * + * A tail queue is headed by a pair of pointers, one to the head of the + * list and the other to the tail of the list. The elements are doubly + * linked so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before or + * after an existing element, at the head of the list, or at the end of + * the list. A tail queue may be traversed in either direction. + * + * A circle queue is headed by a pair of pointers, one to the head of the + * list and the other to the tail of the list. The elements are doubly + * linked so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before or after + * an existing element, at the head of the list, or at the end of the list. + * A circle queue may be traversed in either direction, but has a more + * complex end of list detection. + * + * For details on the use of these macros, see the queue(3) manual page. + */ + +/* + * Double-linked List. + */ + +typedef struct dwc_list_link { + struct dwc_list_link *next; + struct dwc_list_link *prev; +} dwc_list_link_t; + +#define DWC_LIST_INIT(link) do { \ + (link)->next = (link); \ + (link)->prev = (link); \ +} while (0) + +#define DWC_LIST_FIRST(link) ((link)->next) +#define DWC_LIST_LAST(link) ((link)->prev) +#define DWC_LIST_END(link) (link) +#define DWC_LIST_NEXT(link) ((link)->next) +#define DWC_LIST_PREV(link) ((link)->prev) +#define DWC_LIST_EMPTY(link) \ + (DWC_LIST_FIRST(link) == DWC_LIST_END(link)) +#define DWC_LIST_ENTRY(link, type, field) \ + (type *)((uint8_t *)(link) - (size_t)(&((type *)0)->field)) + +#if 0 +#define DWC_LIST_INSERT_HEAD(list, link) do { \ + (link)->next = (list)->next; \ + (link)->prev = (list); \ + (list)->next->prev = (link); \ + (list)->next = (link); \ +} while (0) + +#define DWC_LIST_INSERT_TAIL(list, link) do { \ + (link)->next = (list); \ + (link)->prev = (list)->prev; \ + (list)->prev->next = (link); \ + (list)->prev = (link); \ +} while (0) +#else +#define DWC_LIST_INSERT_HEAD(list, link) do { \ + dwc_list_link_t *__next__ = (list)->next; \ + __next__->prev = (link); \ + (link)->next = __next__; \ + (link)->prev = (list); \ + (list)->next = (link); \ +} while (0) + +#define DWC_LIST_INSERT_TAIL(list, link) do { \ + dwc_list_link_t *__prev__ = (list)->prev; \ + (list)->prev = (link); \ + (link)->next = (list); \ + (link)->prev = __prev__; \ + __prev__->next = (link); \ +} while (0) +#endif + +#if 0 +static inline void __list_add(struct list_head *new, + struct list_head *prev, + struct list_head *next) +{ + next->prev = new; + new->next = next; + new->prev = prev; + prev->next = new; +} + +static inline void list_add(struct list_head *new, struct list_head *head) +{ + __list_add(new, head, head->next); +} + +static inline void list_add_tail(struct list_head *new, struct list_head *head) +{ + __list_add(new, head->prev, head); +} + +static inline void __list_del(struct list_head * prev, struct list_head * next) +{ + next->prev = prev; + prev->next = next; +} + +static inline void list_del(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + entry->next = LIST_POISON1; + entry->prev = LIST_POISON2; +} +#endif + +#define DWC_LIST_REMOVE(link) do { \ + (link)->next->prev = (link)->prev; \ + (link)->prev->next = (link)->next; \ +} while (0) + +#define DWC_LIST_REMOVE_INIT(link) do { \ + DWC_LIST_REMOVE(link); \ + DWC_LIST_INIT(link); \ +} while (0) + +#define DWC_LIST_MOVE_HEAD(list, link) do { \ + DWC_LIST_REMOVE(link); \ + DWC_LIST_INSERT_HEAD(list, link); \ +} while (0) + +#define DWC_LIST_MOVE_TAIL(list, link) do { \ + DWC_LIST_REMOVE(link); \ + DWC_LIST_INSERT_TAIL(list, link); \ +} while (0) + +#define DWC_LIST_FOREACH(var, list) \ + for((var) = DWC_LIST_FIRST(list); \ + (var) != DWC_LIST_END(list); \ + (var) = DWC_LIST_NEXT(var)) + +#define DWC_LIST_FOREACH_SAFE(var, var2, list) \ + for((var) = DWC_LIST_FIRST(list), (var2) = DWC_LIST_NEXT(var); \ + (var) != DWC_LIST_END(list); \ + (var) = (var2), (var2) = DWC_LIST_NEXT(var2)) + +#define DWC_LIST_FOREACH_REVERSE(var, list) \ + for((var) = DWC_LIST_LAST(list); \ + (var) != DWC_LIST_END(list); \ + (var) = DWC_LIST_PREV(var)) + +/* + * Singly-linked List definitions. + */ +#define DWC_SLIST_HEAD(name, type) \ +struct name { \ + struct type *slh_first; /* first element */ \ +} + +#define DWC_SLIST_HEAD_INITIALIZER(head) \ + { NULL } + +#define DWC_SLIST_ENTRY(type) \ +struct { \ + struct type *sle_next; /* next element */ \ +} + +/* + * Singly-linked List access methods. + */ +#define DWC_SLIST_FIRST(head) ((head)->slh_first) +#define DWC_SLIST_END(head) NULL +#define DWC_SLIST_EMPTY(head) (SLIST_FIRST(head) == SLIST_END(head)) +#define DWC_SLIST_NEXT(elm, field) ((elm)->field.sle_next) + +#define DWC_SLIST_FOREACH(var, head, field) \ + for((var) = SLIST_FIRST(head); \ + (var) != SLIST_END(head); \ + (var) = SLIST_NEXT(var, field)) + +#define DWC_SLIST_FOREACH_PREVPTR(var, varp, head, field) \ + for((varp) = &SLIST_FIRST((head)); \ + ((var) = *(varp)) != SLIST_END(head); \ + (varp) = &SLIST_NEXT((var), field)) + +/* + * Singly-linked List functions. + */ +#define DWC_SLIST_INIT(head) { \ + SLIST_FIRST(head) = SLIST_END(head); \ +} + +#define DWC_SLIST_INSERT_AFTER(slistelm, elm, field) do { \ + (elm)->field.sle_next = (slistelm)->field.sle_next; \ + (slistelm)->field.sle_next = (elm); \ +} while (0) + +#define DWC_SLIST_INSERT_HEAD(head, elm, field) do { \ + (elm)->field.sle_next = (head)->slh_first; \ + (head)->slh_first = (elm); \ +} while (0) + +#define DWC_SLIST_REMOVE_NEXT(head, elm, field) do { \ + (elm)->field.sle_next = (elm)->field.sle_next->field.sle_next; \ +} while (0) + +#define DWC_SLIST_REMOVE_HEAD(head, field) do { \ + (head)->slh_first = (head)->slh_first->field.sle_next; \ +} while (0) + +#define DWC_SLIST_REMOVE(head, elm, type, field) do { \ + if ((head)->slh_first == (elm)) { \ + SLIST_REMOVE_HEAD((head), field); \ + } \ + else { \ + struct type *curelm = (head)->slh_first; \ + while( curelm->field.sle_next != (elm) ) \ + curelm = curelm->field.sle_next; \ + curelm->field.sle_next = \ + curelm->field.sle_next->field.sle_next; \ + } \ +} while (0) + +/* + * Simple queue definitions. + */ +#define DWC_SIMPLEQ_HEAD(name, type) \ +struct name { \ + struct type *sqh_first; /* first element */ \ + struct type **sqh_last; /* addr of last next element */ \ +} + +#define DWC_SIMPLEQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).sqh_first } + +#define DWC_SIMPLEQ_ENTRY(type) \ +struct { \ + struct type *sqe_next; /* next element */ \ +} + +/* + * Simple queue access methods. + */ +#define DWC_SIMPLEQ_FIRST(head) ((head)->sqh_first) +#define DWC_SIMPLEQ_END(head) NULL +#define DWC_SIMPLEQ_EMPTY(head) (SIMPLEQ_FIRST(head) == SIMPLEQ_END(head)) +#define DWC_SIMPLEQ_NEXT(elm, field) ((elm)->field.sqe_next) + +#define DWC_SIMPLEQ_FOREACH(var, head, field) \ + for((var) = SIMPLEQ_FIRST(head); \ + (var) != SIMPLEQ_END(head); \ + (var) = SIMPLEQ_NEXT(var, field)) + +/* + * Simple queue functions. + */ +#define DWC_SIMPLEQ_INIT(head) do { \ + (head)->sqh_first = NULL; \ + (head)->sqh_last = &(head)->sqh_first; \ +} while (0) + +#define DWC_SIMPLEQ_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.sqe_next = (head)->sqh_first) == NULL) \ + (head)->sqh_last = &(elm)->field.sqe_next; \ + (head)->sqh_first = (elm); \ +} while (0) + +#define DWC_SIMPLEQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.sqe_next = NULL; \ + *(head)->sqh_last = (elm); \ + (head)->sqh_last = &(elm)->field.sqe_next; \ +} while (0) + +#define DWC_SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ + if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\ + (head)->sqh_last = &(elm)->field.sqe_next; \ + (listelm)->field.sqe_next = (elm); \ +} while (0) + +#define DWC_SIMPLEQ_REMOVE_HEAD(head, field) do { \ + if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \ + (head)->sqh_last = &(head)->sqh_first; \ +} while (0) + +/* + * Tail queue definitions. + */ +#define DWC_TAILQ_HEAD(name, type) \ +struct name { \ + struct type *tqh_first; /* first element */ \ + struct type **tqh_last; /* addr of last next element */ \ +} + +#define DWC_TAILQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).tqh_first } + +#define DWC_TAILQ_ENTRY(type) \ +struct { \ + struct type *tqe_next; /* next element */ \ + struct type **tqe_prev; /* address of previous next element */ \ +} + +/* + * tail queue access methods + */ +#define DWC_TAILQ_FIRST(head) ((head)->tqh_first) +#define DWC_TAILQ_END(head) NULL +#define DWC_TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) +#define DWC_TAILQ_LAST(head, headname) \ + (*(((struct headname *)((head)->tqh_last))->tqh_last)) +/* XXX */ +#define DWC_TAILQ_PREV(elm, headname, field) \ + (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) +#define DWC_TAILQ_EMPTY(head) \ + (DWC_TAILQ_FIRST(head) == DWC_TAILQ_END(head)) + +#define DWC_TAILQ_FOREACH(var, head, field) \ + for ((var) = DWC_TAILQ_FIRST(head); \ + (var) != DWC_TAILQ_END(head); \ + (var) = DWC_TAILQ_NEXT(var, field)) + +#define DWC_TAILQ_FOREACH_REVERSE(var, head, headname, field) \ + for ((var) = DWC_TAILQ_LAST(head, headname); \ + (var) != DWC_TAILQ_END(head); \ + (var) = DWC_TAILQ_PREV(var, headname, field)) + +/* + * Tail queue functions. + */ +#define DWC_TAILQ_INIT(head) do { \ + (head)->tqh_first = NULL; \ + (head)->tqh_last = &(head)->tqh_first; \ +} while (0) + +#define DWC_TAILQ_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \ + (head)->tqh_first->field.tqe_prev = \ + &(elm)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm)->field.tqe_next; \ + (head)->tqh_first = (elm); \ + (elm)->field.tqe_prev = &(head)->tqh_first; \ +} while (0) + +#define DWC_TAILQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.tqe_next = NULL; \ + (elm)->field.tqe_prev = (head)->tqh_last; \ + *(head)->tqh_last = (elm); \ + (head)->tqh_last = &(elm)->field.tqe_next; \ +} while (0) + +#define DWC_TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ + if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\ + (elm)->field.tqe_next->field.tqe_prev = \ + &(elm)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm)->field.tqe_next; \ + (listelm)->field.tqe_next = (elm); \ + (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \ +} while (0) + +#define DWC_TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ + (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ + (elm)->field.tqe_next = (listelm); \ + *(listelm)->field.tqe_prev = (elm); \ + (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \ +} while (0) + +#define DWC_TAILQ_REMOVE(head, elm, field) do { \ + if (((elm)->field.tqe_next) != NULL) \ + (elm)->field.tqe_next->field.tqe_prev = \ + (elm)->field.tqe_prev; \ + else \ + (head)->tqh_last = (elm)->field.tqe_prev; \ + *(elm)->field.tqe_prev = (elm)->field.tqe_next; \ +} while (0) + +#define DWC_TAILQ_REPLACE(head, elm, elm2, field) do { \ + if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL) \ + (elm2)->field.tqe_next->field.tqe_prev = \ + &(elm2)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm2)->field.tqe_next; \ + (elm2)->field.tqe_prev = (elm)->field.tqe_prev; \ + *(elm2)->field.tqe_prev = (elm2); \ +} while (0) + +/* + * Circular queue definitions. + */ +#define DWC_CIRCLEQ_HEAD(name, type) \ +struct name { \ + struct type *cqh_first; /* first element */ \ + struct type *cqh_last; /* last element */ \ +} + +#define DWC_CIRCLEQ_HEAD_INITIALIZER(head) \ + { DWC_CIRCLEQ_END(&head), DWC_CIRCLEQ_END(&head) } + +#define DWC_CIRCLEQ_ENTRY(type) \ +struct { \ + struct type *cqe_next; /* next element */ \ + struct type *cqe_prev; /* previous element */ \ +} + +/* + * Circular queue access methods + */ +#define DWC_CIRCLEQ_FIRST(head) ((head)->cqh_first) +#define DWC_CIRCLEQ_LAST(head) ((head)->cqh_last) +#define DWC_CIRCLEQ_END(head) ((void *)(head)) +#define DWC_CIRCLEQ_NEXT(elm, field) ((elm)->field.cqe_next) +#define DWC_CIRCLEQ_PREV(elm, field) ((elm)->field.cqe_prev) +#define DWC_CIRCLEQ_EMPTY(head) \ + (DWC_CIRCLEQ_FIRST(head) == DWC_CIRCLEQ_END(head)) + +#define DWC_CIRCLEQ_EMPTY_ENTRY(elm, field) (((elm)->field.cqe_next == NULL) && ((elm)->field.cqe_prev == NULL)) + +#define DWC_CIRCLEQ_FOREACH(var, head, field) \ + for((var) = DWC_CIRCLEQ_FIRST(head); \ + (var) != DWC_CIRCLEQ_END(head); \ + (var) = DWC_CIRCLEQ_NEXT(var, field)) + +#define DWC_CIRCLEQ_FOREACH_SAFE(var, var2, head, field) \ + for((var) = DWC_CIRCLEQ_FIRST(head), var2 = DWC_CIRCLEQ_NEXT(var, field); \ + (var) != DWC_CIRCLEQ_END(head); \ + (var) = var2, var2 = DWC_CIRCLEQ_NEXT(var, field)) + +#define DWC_CIRCLEQ_FOREACH_REVERSE(var, head, field) \ + for((var) = DWC_CIRCLEQ_LAST(head); \ + (var) != DWC_CIRCLEQ_END(head); \ + (var) = DWC_CIRCLEQ_PREV(var, field)) + +/* + * Circular queue functions. + */ +#define DWC_CIRCLEQ_INIT(head) do { \ + (head)->cqh_first = DWC_CIRCLEQ_END(head); \ + (head)->cqh_last = DWC_CIRCLEQ_END(head); \ +} while (0) + +#define DWC_CIRCLEQ_INIT_ENTRY(elm, field) do { \ + (elm)->field.cqe_next = NULL; \ + (elm)->field.cqe_prev = NULL; \ +} while (0) + +#define DWC_CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ + (elm)->field.cqe_next = (listelm)->field.cqe_next; \ + (elm)->field.cqe_prev = (listelm); \ + if ((listelm)->field.cqe_next == DWC_CIRCLEQ_END(head)) \ + (head)->cqh_last = (elm); \ + else \ + (listelm)->field.cqe_next->field.cqe_prev = (elm); \ + (listelm)->field.cqe_next = (elm); \ +} while (0) + +#define DWC_CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do { \ + (elm)->field.cqe_next = (listelm); \ + (elm)->field.cqe_prev = (listelm)->field.cqe_prev; \ + if ((listelm)->field.cqe_prev == DWC_CIRCLEQ_END(head)) \ + (head)->cqh_first = (elm); \ + else \ + (listelm)->field.cqe_prev->field.cqe_next = (elm); \ + (listelm)->field.cqe_prev = (elm); \ +} while (0) + +#define DWC_CIRCLEQ_INSERT_HEAD(head, elm, field) do { \ + (elm)->field.cqe_next = (head)->cqh_first; \ + (elm)->field.cqe_prev = DWC_CIRCLEQ_END(head); \ + if ((head)->cqh_last == DWC_CIRCLEQ_END(head)) \ + (head)->cqh_last = (elm); \ + else \ + (head)->cqh_first->field.cqe_prev = (elm); \ + (head)->cqh_first = (elm); \ +} while (0) + +#define DWC_CIRCLEQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.cqe_next = DWC_CIRCLEQ_END(head); \ + (elm)->field.cqe_prev = (head)->cqh_last; \ + if ((head)->cqh_first == DWC_CIRCLEQ_END(head)) \ + (head)->cqh_first = (elm); \ + else \ + (head)->cqh_last->field.cqe_next = (elm); \ + (head)->cqh_last = (elm); \ +} while (0) + +#define DWC_CIRCLEQ_REMOVE(head, elm, field) do { \ + if ((elm)->field.cqe_next == DWC_CIRCLEQ_END(head)) \ + (head)->cqh_last = (elm)->field.cqe_prev; \ + else \ + (elm)->field.cqe_next->field.cqe_prev = \ + (elm)->field.cqe_prev; \ + if ((elm)->field.cqe_prev == DWC_CIRCLEQ_END(head)) \ + (head)->cqh_first = (elm)->field.cqe_next; \ + else \ + (elm)->field.cqe_prev->field.cqe_next = \ + (elm)->field.cqe_next; \ +} while (0) + +#define DWC_CIRCLEQ_REMOVE_INIT(head, elm, field) do { \ + DWC_CIRCLEQ_REMOVE(head, elm, field); \ + DWC_CIRCLEQ_INIT_ENTRY(elm, field); \ +} while (0) + +#define DWC_CIRCLEQ_REPLACE(head, elm, elm2, field) do { \ + if (((elm2)->field.cqe_next = (elm)->field.cqe_next) == \ + DWC_CIRCLEQ_END(head)) \ + (head).cqh_last = (elm2); \ + else \ + (elm2)->field.cqe_next->field.cqe_prev = (elm2); \ + if (((elm2)->field.cqe_prev = (elm)->field.cqe_prev) == \ + DWC_CIRCLEQ_END(head)) \ + (head).cqh_first = (elm2); \ + else \ + (elm2)->field.cqe_prev->field.cqe_next = (elm2); \ +} while (0) + +#ifdef __cplusplus +} +#endif + +#endif /* _DWC_LIST_H_ */ diff --git a/drivers/usb/host/dwc_common_port/dwc_mem.c b/drivers/usb/host/dwc_common_port/dwc_mem.c new file mode 100644 index 00000000000000..ad645ff1ba7e06 --- /dev/null +++ b/drivers/usb/host/dwc_common_port/dwc_mem.c @@ -0,0 +1,245 @@ +/* Memory Debugging */ +#ifdef DWC_DEBUG_MEMORY + +#include "dwc_os.h" +#include "dwc_list.h" + +struct allocation { + void *addr; + void *ctx; + char *func; + int line; + uint32_t size; + int dma; + DWC_CIRCLEQ_ENTRY(allocation) entry; +}; + +DWC_CIRCLEQ_HEAD(allocation_queue, allocation); + +struct allocation_manager { + void *mem_ctx; + struct allocation_queue allocations; + + /* statistics */ + int num; + int num_freed; + int num_active; + uint32_t total; + uint32_t cur; + uint32_t max; +}; + +static struct allocation_manager *manager = NULL; + +static int add_allocation(void *ctx, uint32_t size, char const *func, int line, void *addr, + int dma) +{ + struct allocation *a; + + DWC_ASSERT(manager != NULL, "manager not allocated"); + + a = __DWC_ALLOC_ATOMIC(manager->mem_ctx, sizeof(*a)); + if (!a) { + return -DWC_E_NO_MEMORY; + } + + a->func = __DWC_ALLOC_ATOMIC(manager->mem_ctx, DWC_STRLEN(func) + 1); + if (!a->func) { + __DWC_FREE(manager->mem_ctx, a); + return -DWC_E_NO_MEMORY; + } + + DWC_MEMCPY(a->func, func, DWC_STRLEN(func) + 1); + a->addr = addr; + a->ctx = ctx; + a->line = line; + a->size = size; + a->dma = dma; + DWC_CIRCLEQ_INSERT_TAIL(&manager->allocations, a, entry); + + /* Update stats */ + manager->num++; + manager->num_active++; + manager->total += size; + manager->cur += size; + + if (manager->max < manager->cur) { + manager->max = manager->cur; + } + + return 0; +} + +static struct allocation *find_allocation(void *ctx, void *addr) +{ + struct allocation *a; + + DWC_CIRCLEQ_FOREACH(a, &manager->allocations, entry) { + if (a->ctx == ctx && a->addr == addr) { + return a; + } + } + + return NULL; +} + +static void free_allocation(void *ctx, void *addr, char const *func, int line) +{ + struct allocation *a = find_allocation(ctx, addr); + + if (!a) { + DWC_ASSERT(0, + "Free of address %p that was never allocated or already freed %s:%d", + addr, func, line); + return; + } + + DWC_CIRCLEQ_REMOVE(&manager->allocations, a, entry); + + manager->num_active--; + manager->num_freed++; + manager->cur -= a->size; + __DWC_FREE(manager->mem_ctx, a->func); + __DWC_FREE(manager->mem_ctx, a); +} + +int dwc_memory_debug_start(void *mem_ctx) +{ + DWC_ASSERT(manager == NULL, "Memory debugging has already started\n"); + + if (manager) { + return -DWC_E_BUSY; + } + + manager = __DWC_ALLOC(mem_ctx, sizeof(*manager)); + if (!manager) { + return -DWC_E_NO_MEMORY; + } + + DWC_CIRCLEQ_INIT(&manager->allocations); + manager->mem_ctx = mem_ctx; + manager->num = 0; + manager->num_freed = 0; + manager->num_active = 0; + manager->total = 0; + manager->cur = 0; + manager->max = 0; + + return 0; +} + +void dwc_memory_debug_stop(void) +{ + struct allocation *a; + + dwc_memory_debug_report(); + + DWC_CIRCLEQ_FOREACH(a, &manager->allocations, entry) { + DWC_ERROR("Memory leaked from %s:%d\n", a->func, a->line); + free_allocation(a->ctx, a->addr, NULL, -1); + } + + __DWC_FREE(manager->mem_ctx, manager); +} + +void dwc_memory_debug_report(void) +{ + struct allocation *a; + + DWC_PRINTF("\n\n\n----------------- Memory Debugging Report -----------------\n\n"); + DWC_PRINTF("Num Allocations = %d\n", manager->num); + DWC_PRINTF("Freed = %d\n", manager->num_freed); + DWC_PRINTF("Active = %d\n", manager->num_active); + DWC_PRINTF("Current Memory Used = %d\n", manager->cur); + DWC_PRINTF("Total Memory Used = %d\n", manager->total); + DWC_PRINTF("Maximum Memory Used at Once = %d\n", manager->max); + DWC_PRINTF("Unfreed allocations:\n"); + + DWC_CIRCLEQ_FOREACH(a, &manager->allocations, entry) { + DWC_PRINTF(" addr=%p, size=%d from %s:%d, DMA=%d\n", + a->addr, a->size, a->func, a->line, a->dma); + } +} + +/* The replacement functions */ +void *dwc_alloc_debug(void *mem_ctx, uint32_t size, char const *func, int line) +{ + void *addr = __DWC_ALLOC(mem_ctx, size); + + if (!addr) { + return NULL; + } + + if (add_allocation(mem_ctx, size, func, line, addr, 0)) { + __DWC_FREE(mem_ctx, addr); + return NULL; + } + + return addr; +} + +void *dwc_alloc_atomic_debug(void *mem_ctx, uint32_t size, char const *func, + int line) +{ + void *addr = __DWC_ALLOC_ATOMIC(mem_ctx, size); + + if (!addr) { + return NULL; + } + + if (add_allocation(mem_ctx, size, func, line, addr, 0)) { + __DWC_FREE(mem_ctx, addr); + return NULL; + } + + return addr; +} + +void dwc_free_debug(void *mem_ctx, void *addr, char const *func, int line) +{ + free_allocation(mem_ctx, addr, func, line); + __DWC_FREE(mem_ctx, addr); +} + +void *dwc_dma_alloc_debug(void *dma_ctx, uint32_t size, dwc_dma_t *dma_addr, + char const *func, int line) +{ + void *addr = __DWC_DMA_ALLOC(dma_ctx, size, dma_addr); + + if (!addr) { + return NULL; + } + + if (add_allocation(dma_ctx, size, func, line, addr, 1)) { + __DWC_DMA_FREE(dma_ctx, size, addr, *dma_addr); + return NULL; + } + + return addr; +} + +void *dwc_dma_alloc_atomic_debug(void *dma_ctx, uint32_t size, + dwc_dma_t *dma_addr, char const *func, int line) +{ + void *addr = __DWC_DMA_ALLOC_ATOMIC(dma_ctx, size, dma_addr); + + if (!addr) { + return NULL; + } + + if (add_allocation(dma_ctx, size, func, line, addr, 1)) { + __DWC_DMA_FREE(dma_ctx, size, addr, *dma_addr); + return NULL; + } + + return addr; +} + +void dwc_dma_free_debug(void *dma_ctx, uint32_t size, void *virt_addr, + dwc_dma_t dma_addr, char const *func, int line) +{ + free_allocation(dma_ctx, virt_addr, func, line); + __DWC_DMA_FREE(dma_ctx, size, virt_addr, dma_addr); +} + +#endif /* DWC_DEBUG_MEMORY */ diff --git a/drivers/usb/host/dwc_common_port/dwc_modpow.c b/drivers/usb/host/dwc_common_port/dwc_modpow.c new file mode 100644 index 00000000000000..20045381208a31 --- /dev/null +++ b/drivers/usb/host/dwc_common_port/dwc_modpow.c @@ -0,0 +1,636 @@ +/* Bignum routines adapted from PUTTY sources. PuTTY copyright notice follows. + * + * PuTTY is copyright 1997-2007 Simon Tatham. + * + * Portions copyright Robert de Bath, Joris van Rantwijk, Delian + * Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas Barry, + * Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa, Markus + * Kuhn, and CORE SDI S.A. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE + * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#ifdef DWC_CRYPTOLIB + +#ifndef CONFIG_MACH_IPMATE + +#include "dwc_modpow.h" + +#define BIGNUM_INT_MASK 0xFFFFFFFFUL +#define BIGNUM_TOP_BIT 0x80000000UL +#define BIGNUM_INT_BITS 32 + + +static void *snmalloc(void *mem_ctx, size_t n, size_t size) +{ + void *p; + size *= n; + if (size == 0) size = 1; + p = dwc_alloc(mem_ctx, size); + return p; +} + +#define snewn(ctx, n, type) ((type *)snmalloc((ctx), (n), sizeof(type))) +#define sfree dwc_free + +/* + * Usage notes: + * * Do not call the DIVMOD_WORD macro with expressions such as array + * subscripts, as some implementations object to this (see below). + * * Note that none of the division methods below will cope if the + * quotient won't fit into BIGNUM_INT_BITS. Callers should be careful + * to avoid this case. + * If this condition occurs, in the case of the x86 DIV instruction, + * an overflow exception will occur, which (according to a correspondent) + * will manifest on Windows as something like + * 0xC0000095: Integer overflow + * The C variant won't give the right answer, either. + */ + +#define MUL_WORD(w1, w2) ((BignumDblInt)w1 * w2) + +#if defined __GNUC__ && defined __i386__ +#define DIVMOD_WORD(q, r, hi, lo, w) \ + __asm__("div %2" : \ + "=d" (r), "=a" (q) : \ + "r" (w), "d" (hi), "a" (lo)) +#else +#define DIVMOD_WORD(q, r, hi, lo, w) do { \ + BignumDblInt n = (((BignumDblInt)hi) << BIGNUM_INT_BITS) | lo; \ + q = n / w; \ + r = n % w; \ +} while (0) +#endif + +// q = n / w; +// r = n % w; + +#define BIGNUM_INT_BYTES (BIGNUM_INT_BITS / 8) + +#define BIGNUM_INTERNAL + +static Bignum newbn(void *mem_ctx, int length) +{ + Bignum b = snewn(mem_ctx, length + 1, BignumInt); + //if (!b) + //abort(); /* FIXME */ + DWC_MEMSET(b, 0, (length + 1) * sizeof(*b)); + b[0] = length; + return b; +} + +void freebn(void *mem_ctx, Bignum b) +{ + /* + * Burn the evidence, just in case. + */ + DWC_MEMSET(b, 0, sizeof(b[0]) * (b[0] + 1)); + sfree(mem_ctx, b); +} + +/* + * Compute c = a * b. + * Input is in the first len words of a and b. + * Result is returned in the first 2*len words of c. + */ +static void internal_mul(BignumInt *a, BignumInt *b, + BignumInt *c, int len) +{ + int i, j; + BignumDblInt t; + + for (j = 0; j < 2 * len; j++) + c[j] = 0; + + for (i = len - 1; i >= 0; i--) { + t = 0; + for (j = len - 1; j >= 0; j--) { + t += MUL_WORD(a[i], (BignumDblInt) b[j]); + t += (BignumDblInt) c[i + j + 1]; + c[i + j + 1] = (BignumInt) t; + t = t >> BIGNUM_INT_BITS; + } + c[i] = (BignumInt) t; + } +} + +static void internal_add_shifted(BignumInt *number, + unsigned n, int shift) +{ + int word = 1 + (shift / BIGNUM_INT_BITS); + int bshift = shift % BIGNUM_INT_BITS; + BignumDblInt addend; + + addend = (BignumDblInt)n << bshift; + + while (addend) { + addend += number[word]; + number[word] = (BignumInt) addend & BIGNUM_INT_MASK; + addend >>= BIGNUM_INT_BITS; + word++; + } +} + +/* + * Compute a = a % m. + * Input in first alen words of a and first mlen words of m. + * Output in first alen words of a + * (of which first alen-mlen words will be zero). + * The MSW of m MUST have its high bit set. + * Quotient is accumulated in the `quotient' array, which is a Bignum + * rather than the internal bigendian format. Quotient parts are shifted + * left by `qshift' before adding into quot. + */ +static void internal_mod(BignumInt *a, int alen, + BignumInt *m, int mlen, + BignumInt *quot, int qshift) +{ + BignumInt m0, m1; + unsigned int h; + int i, k; + + m0 = m[0]; + if (mlen > 1) + m1 = m[1]; + else + m1 = 0; + + for (i = 0; i <= alen - mlen; i++) { + BignumDblInt t; + unsigned int q, r, c, ai1; + + if (i == 0) { + h = 0; + } else { + h = a[i - 1]; + a[i - 1] = 0; + } + + if (i == alen - 1) + ai1 = 0; + else + ai1 = a[i + 1]; + + /* Find q = h:a[i] / m0 */ + if (h >= m0) { + /* + * Special case. + * + * To illustrate it, suppose a BignumInt is 8 bits, and + * we are dividing (say) A1:23:45:67 by A1:B2:C3. Then + * our initial division will be 0xA123 / 0xA1, which + * will give a quotient of 0x100 and a divide overflow. + * However, the invariants in this division algorithm + * are not violated, since the full number A1:23:... is + * _less_ than the quotient prefix A1:B2:... and so the + * following correction loop would have sorted it out. + * + * In this situation we set q to be the largest + * quotient we _can_ stomach (0xFF, of course). + */ + q = BIGNUM_INT_MASK; + } else { + /* Macro doesn't want an array subscript expression passed + * into it (see definition), so use a temporary. */ + BignumInt tmplo = a[i]; + DIVMOD_WORD(q, r, h, tmplo, m0); + + /* Refine our estimate of q by looking at + h:a[i]:a[i+1] / m0:m1 */ + t = MUL_WORD(m1, q); + if (t > ((BignumDblInt) r << BIGNUM_INT_BITS) + ai1) { + q--; + t -= m1; + r = (r + m0) & BIGNUM_INT_MASK; /* overflow? */ + if (r >= (BignumDblInt) m0 && + t > ((BignumDblInt) r << BIGNUM_INT_BITS) + ai1) q--; + } + } + + /* Subtract q * m from a[i...] */ + c = 0; + for (k = mlen - 1; k >= 0; k--) { + t = MUL_WORD(q, m[k]); + t += c; + c = (unsigned)(t >> BIGNUM_INT_BITS); + if ((BignumInt) t > a[i + k]) + c++; + a[i + k] -= (BignumInt) t; + } + + /* Add back m in case of borrow */ + if (c != h) { + t = 0; + for (k = mlen - 1; k >= 0; k--) { + t += m[k]; + t += a[i + k]; + a[i + k] = (BignumInt) t; + t = t >> BIGNUM_INT_BITS; + } + q--; + } + if (quot) + internal_add_shifted(quot, q, qshift + BIGNUM_INT_BITS * (alen - mlen - i)); + } +} + +/* + * Compute p % mod. + * The most significant word of mod MUST be non-zero. + * We assume that the result array is the same size as the mod array. + * We optionally write out a quotient if `quotient' is non-NULL. + * We can avoid writing out the result if `result' is NULL. + */ +void bigdivmod(void *mem_ctx, Bignum p, Bignum mod, Bignum result, Bignum quotient) +{ + BignumInt *n, *m; + int mshift; + int plen, mlen, i, j; + + /* Allocate m of size mlen, copy mod to m */ + /* We use big endian internally */ + mlen = mod[0]; + m = snewn(mem_ctx, mlen, BignumInt); + //if (!m) + //abort(); /* FIXME */ + for (j = 0; j < mlen; j++) + m[j] = mod[mod[0] - j]; + + /* Shift m left to make msb bit set */ + for (mshift = 0; mshift < BIGNUM_INT_BITS-1; mshift++) + if ((m[0] << mshift) & BIGNUM_TOP_BIT) + break; + if (mshift) { + for (i = 0; i < mlen - 1; i++) + m[i] = (m[i] << mshift) | (m[i + 1] >> (BIGNUM_INT_BITS - mshift)); + m[mlen - 1] = m[mlen - 1] << mshift; + } + + plen = p[0]; + /* Ensure plen > mlen */ + if (plen <= mlen) + plen = mlen + 1; + + /* Allocate n of size plen, copy p to n */ + n = snewn(mem_ctx, plen, BignumInt); + //if (!n) + //abort(); /* FIXME */ + for (j = 0; j < plen; j++) + n[j] = 0; + for (j = 1; j <= (int)p[0]; j++) + n[plen - j] = p[j]; + + /* Main computation */ + internal_mod(n, plen, m, mlen, quotient, mshift); + + /* Fixup result in case the modulus was shifted */ + if (mshift) { + for (i = plen - mlen - 1; i < plen - 1; i++) + n[i] = (n[i] << mshift) | (n[i + 1] >> (BIGNUM_INT_BITS - mshift)); + n[plen - 1] = n[plen - 1] << mshift; + internal_mod(n, plen, m, mlen, quotient, 0); + for (i = plen - 1; i >= plen - mlen; i--) + n[i] = (n[i] >> mshift) | (n[i - 1] << (BIGNUM_INT_BITS - mshift)); + } + + /* Copy result to buffer */ + if (result) { + for (i = 1; i <= (int)result[0]; i++) { + int j = plen - i; + result[i] = j >= 0 ? n[j] : 0; + } + } + + /* Free temporary arrays */ + for (i = 0; i < mlen; i++) + m[i] = 0; + sfree(mem_ctx, m); + for (i = 0; i < plen; i++) + n[i] = 0; + sfree(mem_ctx, n); +} + +/* + * Simple remainder. + */ +Bignum bigmod(void *mem_ctx, Bignum a, Bignum b) +{ + Bignum r = newbn(mem_ctx, b[0]); + bigdivmod(mem_ctx, a, b, r, NULL); + return r; +} + +/* + * Compute (base ^ exp) % mod. + */ +Bignum dwc_modpow(void *mem_ctx, Bignum base_in, Bignum exp, Bignum mod) +{ + BignumInt *a, *b, *n, *m; + int mshift; + int mlen, i, j; + Bignum base, result; + + /* + * The most significant word of mod needs to be non-zero. It + * should already be, but let's make sure. + */ + //assert(mod[mod[0]] != 0); + + /* + * Make sure the base is smaller than the modulus, by reducing + * it modulo the modulus if not. + */ + base = bigmod(mem_ctx, base_in, mod); + + /* Allocate m of size mlen, copy mod to m */ + /* We use big endian internally */ + mlen = mod[0]; + m = snewn(mem_ctx, mlen, BignumInt); + //if (!m) + //abort(); /* FIXME */ + for (j = 0; j < mlen; j++) + m[j] = mod[mod[0] - j]; + + /* Shift m left to make msb bit set */ + for (mshift = 0; mshift < BIGNUM_INT_BITS - 1; mshift++) + if ((m[0] << mshift) & BIGNUM_TOP_BIT) + break; + if (mshift) { + for (i = 0; i < mlen - 1; i++) + m[i] = + (m[i] << mshift) | (m[i + 1] >> + (BIGNUM_INT_BITS - mshift)); + m[mlen - 1] = m[mlen - 1] << mshift; + } + + /* Allocate n of size mlen, copy base to n */ + n = snewn(mem_ctx, mlen, BignumInt); + //if (!n) + //abort(); /* FIXME */ + i = mlen - base[0]; + for (j = 0; j < i; j++) + n[j] = 0; + for (j = 0; j < base[0]; j++) + n[i + j] = base[base[0] - j]; + + /* Allocate a and b of size 2*mlen. Set a = 1 */ + a = snewn(mem_ctx, 2 * mlen, BignumInt); + //if (!a) + //abort(); /* FIXME */ + b = snewn(mem_ctx, 2 * mlen, BignumInt); + //if (!b) + //abort(); /* FIXME */ + for (i = 0; i < 2 * mlen; i++) + a[i] = 0; + a[2 * mlen - 1] = 1; + + /* Skip leading zero bits of exp. */ + i = 0; + j = BIGNUM_INT_BITS - 1; + while (i < exp[0] && (exp[exp[0] - i] & (1 << j)) == 0) { + j--; + if (j < 0) { + i++; + j = BIGNUM_INT_BITS - 1; + } + } + + /* Main computation */ + while (i < exp[0]) { + while (j >= 0) { + internal_mul(a + mlen, a + mlen, b, mlen); + internal_mod(b, mlen * 2, m, mlen, NULL, 0); + if ((exp[exp[0] - i] & (1 << j)) != 0) { + internal_mul(b + mlen, n, a, mlen); + internal_mod(a, mlen * 2, m, mlen, NULL, 0); + } else { + BignumInt *t; + t = a; + a = b; + b = t; + } + j--; + } + i++; + j = BIGNUM_INT_BITS - 1; + } + + /* Fixup result in case the modulus was shifted */ + if (mshift) { + for (i = mlen - 1; i < 2 * mlen - 1; i++) + a[i] = + (a[i] << mshift) | (a[i + 1] >> + (BIGNUM_INT_BITS - mshift)); + a[2 * mlen - 1] = a[2 * mlen - 1] << mshift; + internal_mod(a, mlen * 2, m, mlen, NULL, 0); + for (i = 2 * mlen - 1; i >= mlen; i--) + a[i] = + (a[i] >> mshift) | (a[i - 1] << + (BIGNUM_INT_BITS - mshift)); + } + + /* Copy result to buffer */ + result = newbn(mem_ctx, mod[0]); + for (i = 0; i < mlen; i++) + result[result[0] - i] = a[i + mlen]; + while (result[0] > 1 && result[result[0]] == 0) + result[0]--; + + /* Free temporary arrays */ + for (i = 0; i < 2 * mlen; i++) + a[i] = 0; + sfree(mem_ctx, a); + for (i = 0; i < 2 * mlen; i++) + b[i] = 0; + sfree(mem_ctx, b); + for (i = 0; i < mlen; i++) + m[i] = 0; + sfree(mem_ctx, m); + for (i = 0; i < mlen; i++) + n[i] = 0; + sfree(mem_ctx, n); + + freebn(mem_ctx, base); + + return result; +} + + +#ifdef UNITTEST + +static __u32 dh_p[] = { + 96, + 0xFFFFFFFF, + 0xFFFFFFFF, + 0xA93AD2CA, + 0x4B82D120, + 0xE0FD108E, + 0x43DB5BFC, + 0x74E5AB31, + 0x08E24FA0, + 0xBAD946E2, + 0x770988C0, + 0x7A615D6C, + 0xBBE11757, + 0x177B200C, + 0x521F2B18, + 0x3EC86A64, + 0xD8760273, + 0xD98A0864, + 0xF12FFA06, + 0x1AD2EE6B, + 0xCEE3D226, + 0x4A25619D, + 0x1E8C94E0, + 0xDB0933D7, + 0xABF5AE8C, + 0xA6E1E4C7, + 0xB3970F85, + 0x5D060C7D, + 0x8AEA7157, + 0x58DBEF0A, + 0xECFB8504, + 0xDF1CBA64, + 0xA85521AB, + 0x04507A33, + 0xAD33170D, + 0x8AAAC42D, + 0x15728E5A, + 0x98FA0510, + 0x15D22618, + 0xEA956AE5, + 0x3995497C, + 0x95581718, + 0xDE2BCBF6, + 0x6F4C52C9, + 0xB5C55DF0, + 0xEC07A28F, + 0x9B2783A2, + 0x180E8603, + 0xE39E772C, + 0x2E36CE3B, + 0x32905E46, + 0xCA18217C, + 0xF1746C08, + 0x4ABC9804, + 0x670C354E, + 0x7096966D, + 0x9ED52907, + 0x208552BB, + 0x1C62F356, + 0xDCA3AD96, + 0x83655D23, + 0xFD24CF5F, + 0x69163FA8, + 0x1C55D39A, + 0x98DA4836, + 0xA163BF05, + 0xC2007CB8, + 0xECE45B3D, + 0x49286651, + 0x7C4B1FE6, + 0xAE9F2411, + 0x5A899FA5, + 0xEE386BFB, + 0xF406B7ED, + 0x0BFF5CB6, + 0xA637ED6B, + 0xF44C42E9, + 0x625E7EC6, + 0xE485B576, + 0x6D51C245, + 0x4FE1356D, + 0xF25F1437, + 0x302B0A6D, + 0xCD3A431B, + 0xEF9519B3, + 0x8E3404DD, + 0x514A0879, + 0x3B139B22, + 0x020BBEA6, + 0x8A67CC74, + 0x29024E08, + 0x80DC1CD1, + 0xC4C6628B, + 0x2168C234, + 0xC90FDAA2, + 0xFFFFFFFF, + 0xFFFFFFFF, +}; + +static __u32 dh_a[] = { + 8, + 0xdf367516, + 0x86459caa, + 0xe2d459a4, + 0xd910dae0, + 0x8a8b5e37, + 0x67ab31c6, + 0xf0b55ea9, + 0x440051d6, +}; + +static __u32 dh_b[] = { + 8, + 0xded92656, + 0xe07a048a, + 0x6fa452cd, + 0x2df89d30, + 0xc75f1b0f, + 0x8ce3578f, + 0x7980a324, + 0x5daec786, +}; + +static __u32 dh_g[] = { + 1, + 2, +}; + +int main(void) +{ + int i; + __u32 *k; + k = dwc_modpow(NULL, dh_g, dh_a, dh_p); + + printf("\n\n"); + for (i=0; i<k[0]; i++) { + __u32 word32 = k[k[0] - i]; + __u16 l = word32 & 0xffff; + __u16 m = (word32 & 0xffff0000) >> 16; + printf("%04x %04x ", m, l); + if (!((i + 1)%13)) printf("\n"); + } + printf("\n\n"); + + if ((k[0] == 0x60) && (k[1] == 0x28e490e5) && (k[0x60] == 0x5a0d3d4e)) { + printf("PASS\n\n"); + } + else { + printf("FAIL\n\n"); + } + +} + +#endif /* UNITTEST */ + +#endif /* CONFIG_MACH_IPMATE */ + +#endif /*DWC_CRYPTOLIB */ diff --git a/drivers/usb/host/dwc_common_port/dwc_modpow.h b/drivers/usb/host/dwc_common_port/dwc_modpow.h new file mode 100644 index 00000000000000..64f00c276e71bf --- /dev/null +++ b/drivers/usb/host/dwc_common_port/dwc_modpow.h @@ -0,0 +1,34 @@ +/* + * dwc_modpow.h + * See dwc_modpow.c for license and changes + */ +#ifndef _DWC_MODPOW_H +#define _DWC_MODPOW_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "dwc_os.h" + +/** @file + * + * This file defines the module exponentiation function which is only used + * internally by the DWC UWB modules for calculation of PKs during numeric + * association. The routine is taken from the PUTTY, an open source terminal + * emulator. The PUTTY License is preserved in the dwc_modpow.c file. + * + */ + +typedef uint32_t BignumInt; +typedef uint64_t BignumDblInt; +typedef BignumInt *Bignum; + +/* Compute modular exponentiaion */ +extern Bignum dwc_modpow(void *mem_ctx, Bignum base_in, Bignum exp, Bignum mod); + +#ifdef __cplusplus +} +#endif + +#endif /* _LINUX_BIGNUM_H */ diff --git a/drivers/usb/host/dwc_common_port/dwc_notifier.c b/drivers/usb/host/dwc_common_port/dwc_notifier.c new file mode 100644 index 00000000000000..8b3772afe11d1d --- /dev/null +++ b/drivers/usb/host/dwc_common_port/dwc_notifier.c @@ -0,0 +1,319 @@ +#ifdef DWC_NOTIFYLIB + +#include "dwc_notifier.h" +#include "dwc_list.h" + +typedef struct dwc_observer { + void *observer; + dwc_notifier_callback_t callback; + void *data; + char *notification; + DWC_CIRCLEQ_ENTRY(dwc_observer) list_entry; +} observer_t; + +DWC_CIRCLEQ_HEAD(observer_queue, dwc_observer); + +typedef struct dwc_notifier { + void *mem_ctx; + void *object; + struct observer_queue observers; + DWC_CIRCLEQ_ENTRY(dwc_notifier) list_entry; +} notifier_t; + +DWC_CIRCLEQ_HEAD(notifier_queue, dwc_notifier); + +typedef struct manager { + void *mem_ctx; + void *wkq_ctx; + dwc_workq_t *wq; +// dwc_mutex_t *mutex; + struct notifier_queue notifiers; +} manager_t; + +static manager_t *manager = NULL; + +static int create_manager(void *mem_ctx, void *wkq_ctx) +{ + manager = dwc_alloc(mem_ctx, sizeof(manager_t)); + if (!manager) { + return -DWC_E_NO_MEMORY; + } + + DWC_CIRCLEQ_INIT(&manager->notifiers); + + manager->wq = dwc_workq_alloc(wkq_ctx, "DWC Notification WorkQ"); + if (!manager->wq) { + return -DWC_E_NO_MEMORY; + } + + return 0; +} + +static void free_manager(void) +{ + dwc_workq_free(manager->wq); + + /* All notifiers must have unregistered themselves before this module + * can be removed. Hitting this assertion indicates a programmer + * error. */ + DWC_ASSERT(DWC_CIRCLEQ_EMPTY(&manager->notifiers), + "Notification manager being freed before all notifiers have been removed"); + dwc_free(manager->mem_ctx, manager); +} + +#ifdef DEBUG +static void dump_manager(void) +{ + notifier_t *n; + observer_t *o; + + DWC_ASSERT(manager, "Notification manager not found"); + + DWC_DEBUG("List of all notifiers and observers:\n"); + DWC_CIRCLEQ_FOREACH(n, &manager->notifiers, list_entry) { + DWC_DEBUG("Notifier %p has observers:\n", n->object); + DWC_CIRCLEQ_FOREACH(o, &n->observers, list_entry) { + DWC_DEBUG(" %p watching %s\n", o->observer, o->notification); + } + } +} +#else +#define dump_manager(...) +#endif + +static observer_t *alloc_observer(void *mem_ctx, void *observer, char *notification, + dwc_notifier_callback_t callback, void *data) +{ + observer_t *new_observer = dwc_alloc(mem_ctx, sizeof(observer_t)); + + if (!new_observer) { + return NULL; + } + + DWC_CIRCLEQ_INIT_ENTRY(new_observer, list_entry); + new_observer->observer = observer; + new_observer->notification = notification; + new_observer->callback = callback; + new_observer->data = data; + return new_observer; +} + +static void free_observer(void *mem_ctx, observer_t *observer) +{ + dwc_free(mem_ctx, observer); +} + +static notifier_t *alloc_notifier(void *mem_ctx, void *object) +{ + notifier_t *notifier; + + if (!object) { + return NULL; + } + + notifier = dwc_alloc(mem_ctx, sizeof(notifier_t)); + if (!notifier) { + return NULL; + } + + DWC_CIRCLEQ_INIT(¬ifier->observers); + DWC_CIRCLEQ_INIT_ENTRY(notifier, list_entry); + + notifier->mem_ctx = mem_ctx; + notifier->object = object; + return notifier; +} + +static void free_notifier(notifier_t *notifier) +{ + observer_t *observer; + + DWC_CIRCLEQ_FOREACH(observer, ¬ifier->observers, list_entry) { + free_observer(notifier->mem_ctx, observer); + } + + dwc_free(notifier->mem_ctx, notifier); +} + +static notifier_t *find_notifier(void *object) +{ + notifier_t *notifier; + + DWC_ASSERT(manager, "Notification manager not found"); + + if (!object) { + return NULL; + } + + DWC_CIRCLEQ_FOREACH(notifier, &manager->notifiers, list_entry) { + if (notifier->object == object) { + return notifier; + } + } + + return NULL; +} + +int dwc_alloc_notification_manager(void *mem_ctx, void *wkq_ctx) +{ + return create_manager(mem_ctx, wkq_ctx); +} + +void dwc_free_notification_manager(void) +{ + free_manager(); +} + +dwc_notifier_t *dwc_register_notifier(void *mem_ctx, void *object) +{ + notifier_t *notifier; + + DWC_ASSERT(manager, "Notification manager not found"); + + notifier = find_notifier(object); + if (notifier) { + DWC_ERROR("Notifier %p is already registered\n", object); + return NULL; + } + + notifier = alloc_notifier(mem_ctx, object); + if (!notifier) { + return NULL; + } + + DWC_CIRCLEQ_INSERT_TAIL(&manager->notifiers, notifier, list_entry); + + DWC_INFO("Notifier %p registered", object); + dump_manager(); + + return notifier; +} + +void dwc_unregister_notifier(dwc_notifier_t *notifier) +{ + DWC_ASSERT(manager, "Notification manager not found"); + + if (!DWC_CIRCLEQ_EMPTY(¬ifier->observers)) { + observer_t *o; + + DWC_ERROR("Notifier %p has active observers when removing\n", notifier->object); + DWC_CIRCLEQ_FOREACH(o, ¬ifier->observers, list_entry) { + DWC_DEBUGC(" %p watching %s\n", o->observer, o->notification); + } + + DWC_ASSERT(DWC_CIRCLEQ_EMPTY(¬ifier->observers), + "Notifier %p has active observers when removing", notifier); + } + + DWC_CIRCLEQ_REMOVE_INIT(&manager->notifiers, notifier, list_entry); + free_notifier(notifier); + + DWC_INFO("Notifier unregistered"); + dump_manager(); +} + +/* Add an observer to observe the notifier for a particular state, event, or notification. */ +int dwc_add_observer(void *observer, void *object, char *notification, + dwc_notifier_callback_t callback, void *data) +{ + notifier_t *notifier = find_notifier(object); + observer_t *new_observer; + + if (!notifier) { + DWC_ERROR("Notifier %p is not found when adding observer\n", object); + return -DWC_E_INVALID; + } + + new_observer = alloc_observer(notifier->mem_ctx, observer, notification, callback, data); + if (!new_observer) { + return -DWC_E_NO_MEMORY; + } + + DWC_CIRCLEQ_INSERT_TAIL(¬ifier->observers, new_observer, list_entry); + + DWC_INFO("Added observer %p to notifier %p observing notification %s, callback=%p, data=%p", + observer, object, notification, callback, data); + + dump_manager(); + return 0; +} + +int dwc_remove_observer(void *observer) +{ + notifier_t *n; + + DWC_ASSERT(manager, "Notification manager not found"); + + DWC_CIRCLEQ_FOREACH(n, &manager->notifiers, list_entry) { + observer_t *o; + observer_t *o2; + + DWC_CIRCLEQ_FOREACH_SAFE(o, o2, &n->observers, list_entry) { + if (o->observer == observer) { + DWC_CIRCLEQ_REMOVE_INIT(&n->observers, o, list_entry); + DWC_INFO("Removing observer %p from notifier %p watching notification %s:", + o->observer, n->object, o->notification); + free_observer(n->mem_ctx, o); + } + } + } + + dump_manager(); + return 0; +} + +typedef struct callback_data { + void *mem_ctx; + dwc_notifier_callback_t cb; + void *observer; + void *data; + void *object; + char *notification; + void *notification_data; +} cb_data_t; + +static void cb_task(void *data) +{ + cb_data_t *cb = (cb_data_t *)data; + + cb->cb(cb->object, cb->notification, cb->observer, cb->notification_data, cb->data); + dwc_free(cb->mem_ctx, cb); +} + +void dwc_notify(dwc_notifier_t *notifier, char *notification, void *notification_data) +{ + observer_t *o; + + DWC_ASSERT(manager, "Notification manager not found"); + + DWC_CIRCLEQ_FOREACH(o, ¬ifier->observers, list_entry) { + int len = DWC_STRLEN(notification); + + if (DWC_STRLEN(o->notification) != len) { + continue; + } + + if (DWC_STRNCMP(o->notification, notification, len) == 0) { + cb_data_t *cb_data = dwc_alloc(notifier->mem_ctx, sizeof(cb_data_t)); + + if (!cb_data) { + DWC_ERROR("Failed to allocate callback data\n"); + return; + } + + cb_data->mem_ctx = notifier->mem_ctx; + cb_data->cb = o->callback; + cb_data->observer = o->observer; + cb_data->data = o->data; + cb_data->object = notifier->object; + cb_data->notification = notification; + cb_data->notification_data = notification_data; + DWC_DEBUGC("Observer found %p for notification %s\n", o->observer, notification); + DWC_WORKQ_SCHEDULE(manager->wq, cb_task, cb_data, + "Notify callback from %p for Notification %s, to observer %p", + cb_data->object, notification, cb_data->observer); + } + } +} + +#endif /* DWC_NOTIFYLIB */ diff --git a/drivers/usb/host/dwc_common_port/dwc_notifier.h b/drivers/usb/host/dwc_common_port/dwc_notifier.h new file mode 100644 index 00000000000000..4a8cdfe565b1fc --- /dev/null +++ b/drivers/usb/host/dwc_common_port/dwc_notifier.h @@ -0,0 +1,122 @@ + +#ifndef __DWC_NOTIFIER_H__ +#define __DWC_NOTIFIER_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "dwc_os.h" + +/** @file + * + * A simple implementation of the Observer pattern. Any "module" can + * register as an observer or notifier. The notion of "module" is abstract and + * can mean anything used to identify either an observer or notifier. Usually + * it will be a pointer to a data structure which contains some state, ie an + * object. + * + * Before any notifiers can be added, the global notification manager must be + * brought up with dwc_alloc_notification_manager(). + * dwc_free_notification_manager() will bring it down and free all resources. + * These would typically be called upon module load and unload. The + * notification manager is a single global instance that handles all registered + * observable modules and observers so this should be done only once. + * + * A module can be observable by using Notifications to publicize some general + * information about it's state or operation. It does not care who listens, or + * even if anyone listens, or what they do with the information. The observable + * modules do not need to know any information about it's observers or their + * interface, or their state or data. + * + * Any module can register to emit Notifications. It should publish a list of + * notifications that it can emit and their behavior, such as when they will get + * triggered, and what information will be provided to the observer. Then it + * should register itself as an observable module. See dwc_register_notifier(). + * + * Any module can observe any observable, registered module, provided it has a + * handle to the other module and knows what notifications to observe. See + * dwc_add_observer(). + * + * A function of type dwc_notifier_callback_t is called whenever a notification + * is triggered with one or more observers observing it. This function is + * called in it's own process so it may sleep or block if needed. It is + * guaranteed to be called sometime after the notification has occurred and will + * be called once per each time the notification is triggered. It will NOT be + * called in the same process context used to trigger the notification. + * + * @section Limitiations + * + * Keep in mind that Notifications that can be triggered in rapid sucession may + * schedule too many processes too handle. Be aware of this limitation when + * designing to use notifications, and only add notifications for appropriate + * observable information. + * + * Also Notification callbacks are not synchronous. If you need to synchronize + * the behavior between module/observer you must use other means. And perhaps + * that will mean Notifications are not the proper solution. + */ + +struct dwc_notifier; +typedef struct dwc_notifier dwc_notifier_t; + +/** The callback function must be of this type. + * + * @param object This is the object that is being observed. + * @param notification This is the notification that was triggered. + * @param observer This is the observer + * @param notification_data This is notification-specific data that the notifier + * has included in this notification. The value of this should be published in + * the documentation of the observable module with the notifications. + * @param user_data This is any custom data that the observer provided when + * adding itself as an observer to the notification. */ +typedef void (*dwc_notifier_callback_t)(void *object, char *notification, void *observer, + void *notification_data, void *user_data); + +/** Brings up the notification manager. */ +extern int dwc_alloc_notification_manager(void *mem_ctx, void *wkq_ctx); +/** Brings down the notification manager. */ +extern void dwc_free_notification_manager(void); + +/** This function registers an observable module. A dwc_notifier_t object is + * returned to the observable module. This is an opaque object that is used by + * the observable module to trigger notifications. This object should only be + * accessible to functions that are authorized to trigger notifications for this + * module. Observers do not need this object. */ +extern dwc_notifier_t *dwc_register_notifier(void *mem_ctx, void *object); + +/** This function unregisters an observable module. All observers have to be + * removed prior to unregistration. */ +extern void dwc_unregister_notifier(dwc_notifier_t *notifier); + +/** Add a module as an observer to the observable module. The observable module + * needs to have previously registered with the notification manager. + * + * @param observer The observer module + * @param object The module to observe + * @param notification The notification to observe + * @param callback The callback function to call + * @param user_data Any additional user data to pass into the callback function */ +extern int dwc_add_observer(void *observer, void *object, char *notification, + dwc_notifier_callback_t callback, void *user_data); + +/** Removes the specified observer from all notifications that it is currently + * observing. */ +extern int dwc_remove_observer(void *observer); + +/** This function triggers a Notification. It should be called by the + * observable module, or any module or library which the observable module + * allows to trigger notification on it's behalf. Such as the dwc_cc_t. + * + * dwc_notify is a non-blocking function. Callbacks are scheduled called in + * their own process context for each trigger. Callbacks can be blocking. + * dwc_notify can be called from interrupt context if needed. + * + */ +void dwc_notify(dwc_notifier_t *notifier, char *notification, void *notification_data); + +#ifdef __cplusplus +} +#endif + +#endif /* __DWC_NOTIFIER_H__ */ diff --git a/drivers/usb/host/dwc_common_port/dwc_os.h b/drivers/usb/host/dwc_common_port/dwc_os.h new file mode 100644 index 00000000000000..7a4052964e95bc --- /dev/null +++ b/drivers/usb/host/dwc_common_port/dwc_os.h @@ -0,0 +1,1275 @@ +/* ========================================================================= + * $File: //dwh/usb_iip/dev/software/dwc_common_port_2/dwc_os.h $ + * $Revision: #14 $ + * $Date: 2010/11/04 $ + * $Change: 1621695 $ + * + * Synopsys Portability Library Software and documentation + * (hereinafter, "Software") is an Unsupported proprietary work of + * Synopsys, Inc. unless otherwise expressly agreed to in writing + * between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product + * under any End User Software License Agreement or Agreement for + * Licensed Product with Synopsys or any supplement thereto. You are + * permitted to use and redistribute this Software in source and binary + * forms, with or without modification, provided that redistributions + * of source code must retain this notice. You may not view, use, + * disclose, copy or distribute this file or any information contained + * herein except pursuant to this license grant from Synopsys. If you + * do not agree with this notice, including the disclaimer below, then + * you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" + * BASIS AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE HEREBY DISCLAIMED. IN NO EVENT SHALL + * SYNOPSYS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================= */ +#ifndef _DWC_OS_H_ +#define _DWC_OS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/** @file + * + * DWC portability library, low level os-wrapper functions + * + */ + +/* These basic types need to be defined by some OS header file or custom header + * file for your specific target architecture. + * + * uint8_t, int8_t, uint16_t, int16_t, uint32_t, int32_t, uint64_t, int64_t + * + * Any custom or alternate header file must be added and enabled here. + */ + +#ifdef DWC_LINUX +# include <linux/types.h> +# ifdef CONFIG_DEBUG_MUTEXES +# include <linux/mutex.h> +# endif +# include <linux/spinlock.h> +# include <linux/errno.h> +#endif + +#if defined(DWC_FREEBSD) || defined(DWC_NETBSD) +# include <os_dep.h> +#endif + + +/** @name Primitive Types and Values */ + +/** We define a boolean type for consistency. Can be either YES or NO */ +typedef uint8_t dwc_bool_t; +#define YES 1 +#define NO 0 + +#ifdef DWC_LINUX + +/** @name Error Codes */ +#define DWC_E_INVALID EINVAL +#define DWC_E_NO_MEMORY ENOMEM +#define DWC_E_NO_DEVICE ENODEV +#define DWC_E_NOT_SUPPORTED EOPNOTSUPP +#define DWC_E_TIMEOUT ETIMEDOUT +#define DWC_E_BUSY EBUSY +#define DWC_E_AGAIN EAGAIN +#define DWC_E_RESTART ERESTART +#define DWC_E_ABORT ECONNABORTED +#define DWC_E_SHUTDOWN ESHUTDOWN +#define DWC_E_NO_DATA ENODATA +#define DWC_E_DISCONNECT ECONNRESET +#define DWC_E_UNKNOWN EINVAL +#define DWC_E_NO_STREAM_RES ENOSR +#define DWC_E_COMMUNICATION ECOMM +#define DWC_E_OVERFLOW EOVERFLOW +#define DWC_E_PROTOCOL EPROTO +#define DWC_E_IN_PROGRESS EINPROGRESS +#define DWC_E_PIPE EPIPE +#define DWC_E_IO EIO +#define DWC_E_NO_SPACE ENOSPC + +#else + +/** @name Error Codes */ +#define DWC_E_INVALID 1001 +#define DWC_E_NO_MEMORY 1002 +#define DWC_E_NO_DEVICE 1003 +#define DWC_E_NOT_SUPPORTED 1004 +#define DWC_E_TIMEOUT 1005 +#define DWC_E_BUSY 1006 +#define DWC_E_AGAIN 1007 +#define DWC_E_RESTART 1008 +#define DWC_E_ABORT 1009 +#define DWC_E_SHUTDOWN 1010 +#define DWC_E_NO_DATA 1011 +#define DWC_E_DISCONNECT 2000 +#define DWC_E_UNKNOWN 3000 +#define DWC_E_NO_STREAM_RES 4001 +#define DWC_E_COMMUNICATION 4002 +#define DWC_E_OVERFLOW 4003 +#define DWC_E_PROTOCOL 4004 +#define DWC_E_IN_PROGRESS 4005 +#define DWC_E_PIPE 4006 +#define DWC_E_IO 4007 +#define DWC_E_NO_SPACE 4008 + +#endif + + +/** @name Tracing/Logging Functions + * + * These function provide the capability to add tracing, debugging, and error + * messages, as well exceptions as assertions. The WUDEV uses these + * extensively. These could be logged to the main console, the serial port, an + * internal buffer, etc. These functions could also be no-op if they are too + * expensive on your system. By default undefining the DEBUG macro already + * no-ops some of these functions. */ + +/** Returns non-zero if in interrupt context. */ +extern dwc_bool_t DWC_IN_IRQ(void); +#define dwc_in_irq DWC_IN_IRQ + +/** Returns "IRQ" if DWC_IN_IRQ is true. */ +static inline char *dwc_irq(void) { + return DWC_IN_IRQ() ? "IRQ" : ""; +} + +/** Returns non-zero if in bottom-half context. */ +extern dwc_bool_t DWC_IN_BH(void); +#define dwc_in_bh DWC_IN_BH + +/** Returns "BH" if DWC_IN_BH is true. */ +static inline char *dwc_bh(void) { + return DWC_IN_BH() ? "BH" : ""; +} + +/** + * A vprintf() clone. Just call vprintf if you've got it. + */ +extern void DWC_VPRINTF(char *format, va_list args); +#define dwc_vprintf DWC_VPRINTF + +/** + * A vsnprintf() clone. Just call vprintf if you've got it. + */ +extern int DWC_VSNPRINTF(char *str, int size, char *format, va_list args); +#define dwc_vsnprintf DWC_VSNPRINTF + +/** + * printf() clone. Just call printf if you've go it. + */ +extern void DWC_PRINTF(char *format, ...) +/* This provides compiler level static checking of the parameters if you're + * using GCC. */ +#ifdef __GNUC__ + __attribute__ ((format(printf, 1, 2))); +#else + ; +#endif +#define dwc_printf DWC_PRINTF + +/** + * sprintf() clone. Just call sprintf if you've got it. + */ +extern int DWC_SPRINTF(char *string, char *format, ...) +#ifdef __GNUC__ + __attribute__ ((format(printf, 2, 3))); +#else + ; +#endif +#define dwc_sprintf DWC_SPRINTF + +/** + * snprintf() clone. Just call snprintf if you've got it. + */ +extern int DWC_SNPRINTF(char *string, int size, char *format, ...) +#ifdef __GNUC__ + __attribute__ ((format(printf, 3, 4))); +#else + ; +#endif +#define dwc_snprintf DWC_SNPRINTF + +/** + * Prints a WARNING message. On systems that don't differentiate between + * warnings and regular log messages, just print it. Indicates that something + * may be wrong with the driver. Works like printf(). + * + * Use the DWC_WARN macro to call this function. + */ +extern void __DWC_WARN(char *format, ...) +#ifdef __GNUC__ + __attribute__ ((format(printf, 1, 2))); +#else + ; +#endif + +/** + * Prints an error message. On systems that don't differentiate between errors + * and regular log messages, just print it. Indicates that something went wrong + * with the driver. Works like printf(). + * + * Use the DWC_ERROR macro to call this function. + */ +extern void __DWC_ERROR(char *format, ...) +#ifdef __GNUC__ + __attribute__ ((format(printf, 1, 2))); +#else + ; +#endif + +/** + * Prints an exception error message and takes some user-defined action such as + * print out a backtrace or trigger a breakpoint. Indicates that something went + * abnormally wrong with the driver such as programmer error, or other + * exceptional condition. It should not be ignored so even on systems without + * printing capability, some action should be taken to notify the developer of + * it. Works like printf(). + */ +extern void DWC_EXCEPTION(char *format, ...) +#ifdef __GNUC__ + __attribute__ ((format(printf, 1, 2))); +#else + ; +#endif +#define dwc_exception DWC_EXCEPTION + +#ifndef DWC_OTG_DEBUG_LEV +#define DWC_OTG_DEBUG_LEV 0 +#endif + +#ifdef DEBUG +/** + * Prints out a debug message. Used for logging/trace messages. + * + * Use the DWC_DEBUG macro to call this function + */ +extern void __DWC_DEBUG(char *format, ...) +#ifdef __GNUC__ + __attribute__ ((format(printf, 1, 2))); +#else + ; +#endif +#else +#define __DWC_DEBUG printk +#endif + +/** + * Prints out a Debug message. + */ +#define DWC_DEBUG(_format, _args...) __DWC_DEBUG("DEBUG:%s:%s: " _format "\n", \ + __func__, dwc_irq(), ## _args) +#define dwc_debug DWC_DEBUG +/** + * Prints out a Debug message if enabled at compile time. + */ +#if DWC_OTG_DEBUG_LEV > 0 +#define DWC_DEBUGC(_format, _args...) DWC_DEBUG(_format, ##_args ) +#else +#define DWC_DEBUGC(_format, _args...) +#endif +#define dwc_debugc DWC_DEBUGC +/** + * Prints out an informative message. + */ +#define DWC_INFO(_format, _args...) DWC_PRINTF("INFO:%s: " _format "\n", \ + dwc_irq(), ## _args) +#define dwc_info DWC_INFO +/** + * Prints out an informative message if enabled at compile time. + */ +#if DWC_OTG_DEBUG_LEV > 1 +#define DWC_INFOC(_format, _args...) DWC_INFO(_format, ##_args ) +#else +#define DWC_INFOC(_format, _args...) +#endif +#define dwc_infoc DWC_INFOC +/** + * Prints out a warning message. + */ +#define DWC_WARN(_format, _args...) __DWC_WARN("WARN:%s:%s:%d: " _format "\n", \ + dwc_irq(), __func__, __LINE__, ## _args) +#define dwc_warn DWC_WARN +/** + * Prints out an error message. + */ +#define DWC_ERROR(_format, _args...) __DWC_ERROR("ERROR:%s:%s:%d: " _format "\n", \ + dwc_irq(), __func__, __LINE__, ## _args) +#define dwc_error DWC_ERROR + +#define DWC_PROTO_ERROR(_format, _args...) __DWC_WARN("ERROR:%s:%s:%d: " _format "\n", \ + dwc_irq(), __func__, __LINE__, ## _args) +#define dwc_proto_error DWC_PROTO_ERROR + +#ifdef DEBUG +/** Prints out a exception error message if the _expr expression fails. Disabled + * if DEBUG is not enabled. */ +#define DWC_ASSERT(_expr, _format, _args...) do { \ + if (!(_expr)) { DWC_EXCEPTION("%s:%s:%d: " _format "\n", dwc_irq(), \ + __FILE__, __LINE__, ## _args); } \ + } while (0) +#else +#define DWC_ASSERT(_x...) +#endif +#define dwc_assert DWC_ASSERT + + +/** @name Byte Ordering + * The following functions are for conversions between processor's byte ordering + * and specific ordering you want. + */ + +/** Converts 32 bit data in CPU byte ordering to little endian. */ +extern uint32_t DWC_CPU_TO_LE32(uint32_t *p); +#define dwc_cpu_to_le32 DWC_CPU_TO_LE32 + +/** Converts 32 bit data in CPU byte orderint to big endian. */ +extern uint32_t DWC_CPU_TO_BE32(uint32_t *p); +#define dwc_cpu_to_be32 DWC_CPU_TO_BE32 + +/** Converts 32 bit little endian data to CPU byte ordering. */ +extern uint32_t DWC_LE32_TO_CPU(uint32_t *p); +#define dwc_le32_to_cpu DWC_LE32_TO_CPU + +/** Converts 32 bit big endian data to CPU byte ordering. */ +extern uint32_t DWC_BE32_TO_CPU(uint32_t *p); +#define dwc_be32_to_cpu DWC_BE32_TO_CPU + +/** Converts 16 bit data in CPU byte ordering to little endian. */ +extern uint16_t DWC_CPU_TO_LE16(uint16_t *p); +#define dwc_cpu_to_le16 DWC_CPU_TO_LE16 + +/** Converts 16 bit data in CPU byte orderint to big endian. */ +extern uint16_t DWC_CPU_TO_BE16(uint16_t *p); +#define dwc_cpu_to_be16 DWC_CPU_TO_BE16 + +/** Converts 16 bit little endian data to CPU byte ordering. */ +extern uint16_t DWC_LE16_TO_CPU(uint16_t *p); +#define dwc_le16_to_cpu DWC_LE16_TO_CPU + +/** Converts 16 bit bi endian data to CPU byte ordering. */ +extern uint16_t DWC_BE16_TO_CPU(uint16_t *p); +#define dwc_be16_to_cpu DWC_BE16_TO_CPU + + +/** @name Register Read/Write + * + * The following six functions should be implemented to read/write registers of + * 32-bit and 64-bit sizes. All modules use this to read/write register values. + * The reg value is a pointer to the register calculated from the void *base + * variable passed into the driver when it is started. */ + +#ifdef DWC_LINUX +/* Linux doesn't need any extra parameters for register read/write, so we + * just throw away the IO context parameter. + */ +/** Reads the content of a 32-bit register. */ +extern uint32_t DWC_READ_REG32(uint32_t volatile *reg); +#define dwc_read_reg32(_ctx_,_reg_) DWC_READ_REG32(_reg_) + +/** Reads the content of a 64-bit register. */ +extern uint64_t DWC_READ_REG64(uint64_t volatile *reg); +#define dwc_read_reg64(_ctx_,_reg_) DWC_READ_REG64(_reg_) + +/** Writes to a 32-bit register. */ +extern void DWC_WRITE_REG32(uint32_t volatile *reg, uint32_t value); +#define dwc_write_reg32(_ctx_,_reg_,_val_) DWC_WRITE_REG32(_reg_, _val_) + +/** Writes to a 64-bit register. */ +extern void DWC_WRITE_REG64(uint64_t volatile *reg, uint64_t value); +#define dwc_write_reg64(_ctx_,_reg_,_val_) DWC_WRITE_REG64(_reg_, _val_) + +/** + * Modify bit values in a register. Using the + * algorithm: (reg_contents & ~clear_mask) | set_mask. + */ +extern void DWC_MODIFY_REG32(uint32_t volatile *reg, uint32_t clear_mask, uint32_t set_mask); +#define dwc_modify_reg32(_ctx_,_reg_,_cmsk_,_smsk_) DWC_MODIFY_REG32(_reg_,_cmsk_,_smsk_) +extern void DWC_MODIFY_REG64(uint64_t volatile *reg, uint64_t clear_mask, uint64_t set_mask); +#define dwc_modify_reg64(_ctx_,_reg_,_cmsk_,_smsk_) DWC_MODIFY_REG64(_reg_,_cmsk_,_smsk_) + +#endif /* DWC_LINUX */ + +#if defined(DWC_FREEBSD) || defined(DWC_NETBSD) +typedef struct dwc_ioctx { + struct device *dev; + bus_space_tag_t iot; + bus_space_handle_t ioh; +} dwc_ioctx_t; + +/** BSD needs two extra parameters for register read/write, so we pass + * them in using the IO context parameter. + */ +/** Reads the content of a 32-bit register. */ +extern uint32_t DWC_READ_REG32(void *io_ctx, uint32_t volatile *reg); +#define dwc_read_reg32 DWC_READ_REG32 + +/** Reads the content of a 64-bit register. */ +extern uint64_t DWC_READ_REG64(void *io_ctx, uint64_t volatile *reg); +#define dwc_read_reg64 DWC_READ_REG64 + +/** Writes to a 32-bit register. */ +extern void DWC_WRITE_REG32(void *io_ctx, uint32_t volatile *reg, uint32_t value); +#define dwc_write_reg32 DWC_WRITE_REG32 + +/** Writes to a 64-bit register. */ +extern void DWC_WRITE_REG64(void *io_ctx, uint64_t volatile *reg, uint64_t value); +#define dwc_write_reg64 DWC_WRITE_REG64 + +/** + * Modify bit values in a register. Using the + * algorithm: (reg_contents & ~clear_mask) | set_mask. + */ +extern void DWC_MODIFY_REG32(void *io_ctx, uint32_t volatile *reg, uint32_t clear_mask, uint32_t set_mask); +#define dwc_modify_reg32 DWC_MODIFY_REG32 +extern void DWC_MODIFY_REG64(void *io_ctx, uint64_t volatile *reg, uint64_t clear_mask, uint64_t set_mask); +#define dwc_modify_reg64 DWC_MODIFY_REG64 + +#endif /* DWC_FREEBSD || DWC_NETBSD */ + +/** @cond */ + +/** @name Some convenience MACROS used internally. Define DWC_DEBUG_REGS to log the + * register writes. */ + +#ifdef DWC_LINUX + +# ifdef DWC_DEBUG_REGS + +#define dwc_define_read_write_reg_n(_reg,_container_type) \ +static inline uint32_t dwc_read_##_reg##_n(_container_type *container, int num) { \ + return DWC_READ_REG32(&container->regs->_reg[num]); \ +} \ +static inline void dwc_write_##_reg##_n(_container_type *container, int num, uint32_t data) { \ + DWC_DEBUG("WRITING %8s[%d]: %p: %08x", #_reg, num, \ + &(((uint32_t*)container->regs->_reg)[num]), data); \ + DWC_WRITE_REG32(&(((uint32_t*)container->regs->_reg)[num]), data); \ +} + +#define dwc_define_read_write_reg(_reg,_container_type) \ +static inline uint32_t dwc_read_##_reg(_container_type *container) { \ + return DWC_READ_REG32(&container->regs->_reg); \ +} \ +static inline void dwc_write_##_reg(_container_type *container, uint32_t data) { \ + DWC_DEBUG("WRITING %11s: %p: %08x", #_reg, &container->regs->_reg, data); \ + DWC_WRITE_REG32(&container->regs->_reg, data); \ +} + +# else /* DWC_DEBUG_REGS */ + +#define dwc_define_read_write_reg_n(_reg,_container_type) \ +static inline uint32_t dwc_read_##_reg##_n(_container_type *container, int num) { \ + return DWC_READ_REG32(&container->regs->_reg[num]); \ +} \ +static inline void dwc_write_##_reg##_n(_container_type *container, int num, uint32_t data) { \ + DWC_WRITE_REG32(&(((uint32_t*)container->regs->_reg)[num]), data); \ +} + +#define dwc_define_read_write_reg(_reg,_container_type) \ +static inline uint32_t dwc_read_##_reg(_container_type *container) { \ + return DWC_READ_REG32(&container->regs->_reg); \ +} \ +static inline void dwc_write_##_reg(_container_type *container, uint32_t data) { \ + DWC_WRITE_REG32(&container->regs->_reg, data); \ +} + +# endif /* DWC_DEBUG_REGS */ + +#endif /* DWC_LINUX */ + +#if defined(DWC_FREEBSD) || defined(DWC_NETBSD) + +# ifdef DWC_DEBUG_REGS + +#define dwc_define_read_write_reg_n(_reg,_container_type) \ +static inline uint32_t dwc_read_##_reg##_n(void *io_ctx, _container_type *container, int num) { \ + return DWC_READ_REG32(io_ctx, &container->regs->_reg[num]); \ +} \ +static inline void dwc_write_##_reg##_n(void *io_ctx, _container_type *container, int num, uint32_t data) { \ + DWC_DEBUG("WRITING %8s[%d]: %p: %08x", #_reg, num, \ + &(((uint32_t*)container->regs->_reg)[num]), data); \ + DWC_WRITE_REG32(io_ctx, &(((uint32_t*)container->regs->_reg)[num]), data); \ +} + +#define dwc_define_read_write_reg(_reg,_container_type) \ +static inline uint32_t dwc_read_##_reg(void *io_ctx, _container_type *container) { \ + return DWC_READ_REG32(io_ctx, &container->regs->_reg); \ +} \ +static inline void dwc_write_##_reg(void *io_ctx, _container_type *container, uint32_t data) { \ + DWC_DEBUG("WRITING %11s: %p: %08x", #_reg, &container->regs->_reg, data); \ + DWC_WRITE_REG32(io_ctx, &container->regs->_reg, data); \ +} + +# else /* DWC_DEBUG_REGS */ + +#define dwc_define_read_write_reg_n(_reg,_container_type) \ +static inline uint32_t dwc_read_##_reg##_n(void *io_ctx, _container_type *container, int num) { \ + return DWC_READ_REG32(io_ctx, &container->regs->_reg[num]); \ +} \ +static inline void dwc_write_##_reg##_n(void *io_ctx, _container_type *container, int num, uint32_t data) { \ + DWC_WRITE_REG32(io_ctx, &(((uint32_t*)container->regs->_reg)[num]), data); \ +} + +#define dwc_define_read_write_reg(_reg,_container_type) \ +static inline uint32_t dwc_read_##_reg(void *io_ctx, _container_type *container) { \ + return DWC_READ_REG32(io_ctx, &container->regs->_reg); \ +} \ +static inline void dwc_write_##_reg(void *io_ctx, _container_type *container, uint32_t data) { \ + DWC_WRITE_REG32(io_ctx, &container->regs->_reg, data); \ +} + +# endif /* DWC_DEBUG_REGS */ + +#endif /* DWC_FREEBSD || DWC_NETBSD */ + +/** @endcond */ + + +#ifdef DWC_CRYPTOLIB +/** @name Crypto Functions + * + * These are the low-level cryptographic functions used by the driver. */ + +/** Perform AES CBC */ +extern int DWC_AES_CBC(uint8_t *message, uint32_t messagelen, uint8_t *key, uint32_t keylen, uint8_t iv[16], uint8_t *out); +#define dwc_aes_cbc DWC_AES_CBC + +/** Fill the provided buffer with random bytes. These should be cryptographic grade random numbers. */ +extern void DWC_RANDOM_BYTES(uint8_t *buffer, uint32_t length); +#define dwc_random_bytes DWC_RANDOM_BYTES + +/** Perform the SHA-256 hash function */ +extern int DWC_SHA256(uint8_t *message, uint32_t len, uint8_t *out); +#define dwc_sha256 DWC_SHA256 + +/** Calculated the HMAC-SHA256 */ +extern int DWC_HMAC_SHA256(uint8_t *message, uint32_t messagelen, uint8_t *key, uint32_t keylen, uint8_t *out); +#define dwc_hmac_sha256 DWC_HMAC_SHA256 + +#endif /* DWC_CRYPTOLIB */ + + +/** @name Memory Allocation + * + * These function provide access to memory allocation. There are only 2 DMA + * functions and 3 Regular memory functions that need to be implemented. None + * of the memory debugging routines need to be implemented. The allocation + * routines all ZERO the contents of the memory. + * + * Defining DWC_DEBUG_MEMORY turns on memory debugging and statistic gathering. + * This checks for memory leaks, keeping track of alloc/free pairs. It also + * keeps track of how much memory the driver is using at any given time. */ + +#define DWC_PAGE_SIZE 4096 +#define DWC_PAGE_OFFSET(addr) (((uint32_t)addr) & 0xfff) +#define DWC_PAGE_ALIGNED(addr) ((((uint32_t)addr) & 0xfff) == 0) + +#define DWC_INVALID_DMA_ADDR 0x0 + +#ifdef DWC_LINUX +/** Type for a DMA address */ +typedef dma_addr_t dwc_dma_t; +#endif + +#if defined(DWC_FREEBSD) || defined(DWC_NETBSD) +typedef bus_addr_t dwc_dma_t; +#endif + +#ifdef DWC_FREEBSD +typedef struct dwc_dmactx { + struct device *dev; + bus_dma_tag_t dma_tag; + bus_dmamap_t dma_map; + bus_addr_t dma_paddr; + void *dma_vaddr; +} dwc_dmactx_t; +#endif + +#ifdef DWC_NETBSD +typedef struct dwc_dmactx { + struct device *dev; + bus_dma_tag_t dma_tag; + bus_dmamap_t dma_map; + bus_dma_segment_t segs[1]; + int nsegs; + bus_addr_t dma_paddr; + void *dma_vaddr; +} dwc_dmactx_t; +#endif + +/* @todo these functions will be added in the future */ +#if 0 +/** + * Creates a DMA pool from which you can allocate DMA buffers. Buffers + * allocated from this pool will be guaranteed to meet the size, alignment, and + * boundary requirements specified. + * + * @param[in] size Specifies the size of the buffers that will be allocated from + * this pool. + * @param[in] align Specifies the byte alignment requirements of the buffers + * allocated from this pool. Must be a power of 2. + * @param[in] boundary Specifies the N-byte boundary that buffers allocated from + * this pool must not cross. + * + * @returns A pointer to an internal opaque structure which is not to be + * accessed outside of these library functions. Use this handle to specify + * which pools to allocate/free DMA buffers from and also to destroy the pool, + * when you are done with it. + */ +extern dwc_pool_t *DWC_DMA_POOL_CREATE(uint32_t size, uint32_t align, uint32_t boundary); + +/** + * Destroy a DMA pool. All buffers allocated from that pool must be freed first. + */ +extern void DWC_DMA_POOL_DESTROY(dwc_pool_t *pool); + +/** + * Allocate a buffer from the specified DMA pool and zeros its contents. + */ +extern void *DWC_DMA_POOL_ALLOC(dwc_pool_t *pool, uint64_t *dma_addr); + +/** + * Free a previously allocated buffer from the DMA pool. + */ +extern void DWC_DMA_POOL_FREE(dwc_pool_t *pool, void *vaddr, void *daddr); +#endif + +/** Allocates a DMA capable buffer and zeroes its contents. */ +extern void *__DWC_DMA_ALLOC(void *dma_ctx, uint32_t size, dwc_dma_t *dma_addr); + +/** Allocates a DMA capable buffer and zeroes its contents in atomic contest */ +extern void *__DWC_DMA_ALLOC_ATOMIC(void *dma_ctx, uint32_t size, dwc_dma_t *dma_addr); + +/** Frees a previously allocated buffer. */ +extern void __DWC_DMA_FREE(void *dma_ctx, uint32_t size, void *virt_addr, dwc_dma_t dma_addr); + +/** Allocates a block of memory and zeroes its contents. */ +extern void *__DWC_ALLOC(void *mem_ctx, uint32_t size); + +/** Allocates a block of memory and zeroes its contents, in an atomic manner + * which can be used inside interrupt context. The size should be sufficiently + * small, a few KB at most, such that failures are not likely to occur. Can just call + * __DWC_ALLOC if it is atomic. */ +extern void *__DWC_ALLOC_ATOMIC(void *mem_ctx, uint32_t size); + +/** Frees a previously allocated buffer. */ +extern void __DWC_FREE(void *mem_ctx, void *addr); + +#ifndef DWC_DEBUG_MEMORY + +#define DWC_ALLOC(_size_) __DWC_ALLOC(NULL, _size_) +#define DWC_ALLOC_ATOMIC(_size_) __DWC_ALLOC_ATOMIC(NULL, _size_) +#define DWC_FREE(_addr_) __DWC_FREE(NULL, _addr_) + +# ifdef DWC_LINUX +#define DWC_DMA_ALLOC(_dev, _size_, _dma_) __DWC_DMA_ALLOC(_dev, _size_, _dma_) +#define DWC_DMA_ALLOC_ATOMIC(_dev, _size_, _dma_) __DWC_DMA_ALLOC_ATOMIC(_dev, _size_, _dma_) +#define DWC_DMA_FREE(_dev, _size_,_virt_, _dma_) __DWC_DMA_FREE(_dev, _size_, _virt_, _dma_) +# endif + +# if defined(DWC_FREEBSD) || defined(DWC_NETBSD) +#define DWC_DMA_ALLOC __DWC_DMA_ALLOC +#define DWC_DMA_FREE __DWC_DMA_FREE +# endif +extern void *dwc_dma_alloc_atomic_debug(uint32_t size, dwc_dma_t *dma_addr, char const *func, int line); + +#else /* DWC_DEBUG_MEMORY */ + +extern void *dwc_alloc_debug(void *mem_ctx, uint32_t size, char const *func, int line); +extern void *dwc_alloc_atomic_debug(void *mem_ctx, uint32_t size, char const *func, int line); +extern void dwc_free_debug(void *mem_ctx, void *addr, char const *func, int line); +extern void *dwc_dma_alloc_debug(void *dma_ctx, uint32_t size, dwc_dma_t *dma_addr, + char const *func, int line); +extern void *dwc_dma_alloc_atomic_debug(void *dma_ctx, uint32_t size, dwc_dma_t *dma_addr, + char const *func, int line); +extern void dwc_dma_free_debug(void *dma_ctx, uint32_t size, void *virt_addr, + dwc_dma_t dma_addr, char const *func, int line); + +extern int dwc_memory_debug_start(void *mem_ctx); +extern void dwc_memory_debug_stop(void); +extern void dwc_memory_debug_report(void); + +#define DWC_ALLOC(_size_) dwc_alloc_debug(NULL, _size_, __func__, __LINE__) +#define DWC_ALLOC_ATOMIC(_size_) dwc_alloc_atomic_debug(NULL, _size_, \ + __func__, __LINE__) +#define DWC_FREE(_addr_) dwc_free_debug(NULL, _addr_, __func__, __LINE__) + +# ifdef DWC_LINUX +#define DWC_DMA_ALLOC(_dev, _size_, _dma_) \ + dwc_dma_alloc_debug(_dev, _size_, _dma_, __func__, __LINE__) +#define DWC_DMA_ALLOC_ATOMIC(_dev, _size_, _dma_) \ + dwc_dma_alloc_atomic_debug(_dev, _size_, _dma_, __func__, __LINE__) +#define DWC_DMA_FREE(_dev, _size_, _virt_, _dma_) \ + dwc_dma_free_debug(_dev, _size_, _virt_, _dma_, __func__, __LINE__) +# endif + +# if defined(DWC_FREEBSD) || defined(DWC_NETBSD) +#define DWC_DMA_ALLOC(_ctx_,_size_,_dma_) dwc_dma_alloc_debug(_ctx_, _size_, \ + _dma_, __func__, __LINE__) +#define DWC_DMA_FREE(_ctx_,_size_,_virt_,_dma_) dwc_dma_free_debug(_ctx_, _size_, \ + _virt_, _dma_, __func__, __LINE__) +# endif + +#endif /* DWC_DEBUG_MEMORY */ + +#define dwc_alloc(_ctx_,_size_) DWC_ALLOC(_size_) +#define dwc_alloc_atomic(_ctx_,_size_) DWC_ALLOC_ATOMIC(_size_) +#define dwc_free(_ctx_,_addr_) DWC_FREE(_addr_) + +#ifdef DWC_LINUX +/* Linux doesn't need any extra parameters for DMA buffer allocation, so we + * just throw away the DMA context parameter. + */ +#define dwc_dma_alloc(_ctx_,_size_,_dma_) DWC_DMA_ALLOC(_size_, _dma_) +#define dwc_dma_alloc_atomic(_ctx_,_size_,_dma_) DWC_DMA_ALLOC_ATOMIC(_size_, _dma_) +#define dwc_dma_free(_ctx_,_size_,_virt_,_dma_) DWC_DMA_FREE(_size_, _virt_, _dma_) +#endif + +#if defined(DWC_FREEBSD) || defined(DWC_NETBSD) +/** BSD needs several extra parameters for DMA buffer allocation, so we pass + * them in using the DMA context parameter. + */ +#define dwc_dma_alloc DWC_DMA_ALLOC +#define dwc_dma_free DWC_DMA_FREE +#endif + + +/** @name Memory and String Processing */ + +/** memset() clone */ +extern void *DWC_MEMSET(void *dest, uint8_t byte, uint32_t size); +#define dwc_memset DWC_MEMSET + +/** memcpy() clone */ +extern void *DWC_MEMCPY(void *dest, void const *src, uint32_t size); +#define dwc_memcpy DWC_MEMCPY + +/** memmove() clone */ +extern void *DWC_MEMMOVE(void *dest, void *src, uint32_t size); +#define dwc_memmove DWC_MEMMOVE + +/** memcmp() clone */ +extern int DWC_MEMCMP(void *m1, void *m2, uint32_t size); +#define dwc_memcmp DWC_MEMCMP + +/** strcmp() clone */ +extern int DWC_STRCMP(void *s1, void *s2); +#define dwc_strcmp DWC_STRCMP + +/** strncmp() clone */ +extern int DWC_STRNCMP(void *s1, void *s2, uint32_t size); +#define dwc_strncmp DWC_STRNCMP + +/** strlen() clone, for NULL terminated ASCII strings */ +extern int DWC_STRLEN(char const *str); +#define dwc_strlen DWC_STRLEN + +/** strcpy() clone, for NULL terminated ASCII strings */ +extern char *DWC_STRCPY(char *to, const char *from); +#define dwc_strcpy DWC_STRCPY + +/** strdup() clone. If you wish to use memory allocation debugging, this + * implementation of strdup should use the DWC_* memory routines instead of + * calling a predefined strdup. Otherwise the memory allocated by this routine + * will not be seen by the debugging routines. */ +extern char *DWC_STRDUP(char const *str); +#define dwc_strdup(_ctx_,_str_) DWC_STRDUP(_str_) + +/** NOT an atoi() clone. Read the description carefully. Returns an integer + * converted from the string str in base 10 unless the string begins with a "0x" + * in which case it is base 16. String must be a NULL terminated sequence of + * ASCII characters and may optionally begin with whitespace, a + or -, and a + * "0x" prefix if base 16. The remaining characters must be valid digits for + * the number and end with a NULL character. If any invalid characters are + * encountered or it returns with a negative error code and the results of the + * conversion are undefined. On sucess it returns 0. Overflow conditions are + * undefined. An example implementation using atoi() can be referenced from the + * Linux implementation. */ +extern int DWC_ATOI(const char *str, int32_t *value); +#define dwc_atoi DWC_ATOI + +/** Same as above but for unsigned. */ +extern int DWC_ATOUI(const char *str, uint32_t *value); +#define dwc_atoui DWC_ATOUI + +#ifdef DWC_UTFLIB +/** This routine returns a UTF16LE unicode encoded string from a UTF8 string. */ +extern int DWC_UTF8_TO_UTF16LE(uint8_t const *utf8string, uint16_t *utf16string, unsigned len); +#define dwc_utf8_to_utf16le DWC_UTF8_TO_UTF16LE +#endif + + +/** @name Wait queues + * + * Wait queues provide a means of synchronizing between threads or processes. A + * process can block on a waitq if some condition is not true, waiting for it to + * become true. When the waitq is triggered all waiting process will get + * unblocked and the condition will be check again. Waitqs should be triggered + * every time a condition can potentially change.*/ +struct dwc_waitq; + +/** Type for a waitq */ +typedef struct dwc_waitq dwc_waitq_t; + +/** The type of waitq condition callback function. This is called every time + * condition is evaluated. */ +typedef int (*dwc_waitq_condition_t)(void *data); + +/** Allocate a waitq */ +extern dwc_waitq_t *DWC_WAITQ_ALLOC(void); +#define dwc_waitq_alloc(_ctx_) DWC_WAITQ_ALLOC() + +/** Free a waitq */ +extern void DWC_WAITQ_FREE(dwc_waitq_t *wq); +#define dwc_waitq_free DWC_WAITQ_FREE + +/** Check the condition and if it is false, block on the waitq. When unblocked, check the + * condition again. The function returns when the condition becomes true. The return value + * is 0 on condition true, DWC_WAITQ_ABORTED on abort or killed, or DWC_WAITQ_UNKNOWN on error. */ +extern int32_t DWC_WAITQ_WAIT(dwc_waitq_t *wq, dwc_waitq_condition_t cond, void *data); +#define dwc_waitq_wait DWC_WAITQ_WAIT + +/** Check the condition and if it is false, block on the waitq. When unblocked, + * check the condition again. The function returns when the condition become + * true or the timeout has passed. The return value is 0 on condition true or + * DWC_TIMED_OUT on timeout, or DWC_WAITQ_ABORTED, or DWC_WAITQ_UNKNOWN on + * error. */ +extern int32_t DWC_WAITQ_WAIT_TIMEOUT(dwc_waitq_t *wq, dwc_waitq_condition_t cond, + void *data, int32_t msecs); +#define dwc_waitq_wait_timeout DWC_WAITQ_WAIT_TIMEOUT + +/** Trigger a waitq, unblocking all processes. This should be called whenever a condition + * has potentially changed. */ +extern void DWC_WAITQ_TRIGGER(dwc_waitq_t *wq); +#define dwc_waitq_trigger DWC_WAITQ_TRIGGER + +/** Unblock all processes waiting on the waitq with an ABORTED result. */ +extern void DWC_WAITQ_ABORT(dwc_waitq_t *wq); +#define dwc_waitq_abort DWC_WAITQ_ABORT + + +/** @name Threads + * + * A thread must be explicitly stopped. It must check DWC_THREAD_SHOULD_STOP + * whenever it is woken up, and then return. The DWC_THREAD_STOP function + * returns the value from the thread. + */ + +struct dwc_thread; + +/** Type for a thread */ +typedef struct dwc_thread dwc_thread_t; + +/** The thread function */ +typedef int (*dwc_thread_function_t)(void *data); + +/** Create a thread and start it running the thread_function. Returns a handle + * to the thread */ +extern dwc_thread_t *DWC_THREAD_RUN(dwc_thread_function_t func, char *name, void *data); +#define dwc_thread_run(_ctx_,_func_,_name_,_data_) DWC_THREAD_RUN(_func_, _name_, _data_) + +/** Stops a thread. Return the value returned by the thread. Or will return + * DWC_ABORT if the thread never started. */ +extern int DWC_THREAD_STOP(dwc_thread_t *thread); +#define dwc_thread_stop DWC_THREAD_STOP + +/** Signifies to the thread that it must stop. */ +#ifdef DWC_LINUX +/* Linux doesn't need any parameters for kthread_should_stop() */ +extern dwc_bool_t DWC_THREAD_SHOULD_STOP(void); +#define dwc_thread_should_stop(_thrd_) DWC_THREAD_SHOULD_STOP() + +/* No thread_exit function in Linux */ +#define dwc_thread_exit(_thrd_) +#endif + +#if defined(DWC_FREEBSD) || defined(DWC_NETBSD) +/** BSD needs the thread pointer for kthread_suspend_check() */ +extern dwc_bool_t DWC_THREAD_SHOULD_STOP(dwc_thread_t *thread); +#define dwc_thread_should_stop DWC_THREAD_SHOULD_STOP + +/** The thread must call this to exit. */ +extern void DWC_THREAD_EXIT(dwc_thread_t *thread); +#define dwc_thread_exit DWC_THREAD_EXIT +#endif + + +/** @name Work queues + * + * Workqs are used to queue a callback function to be called at some later time, + * in another thread. */ +struct dwc_workq; + +/** Type for a workq */ +typedef struct dwc_workq dwc_workq_t; + +/** The type of the callback function to be called. */ +typedef void (*dwc_work_callback_t)(void *data); + +/** Allocate a workq */ +extern dwc_workq_t *DWC_WORKQ_ALLOC(char *name); +#define dwc_workq_alloc(_ctx_,_name_) DWC_WORKQ_ALLOC(_name_) + +/** Free a workq. All work must be completed before being freed. */ +extern void DWC_WORKQ_FREE(dwc_workq_t *workq); +#define dwc_workq_free DWC_WORKQ_FREE + +/** Schedule a callback on the workq, passing in data. The function will be + * scheduled at some later time. */ +extern void DWC_WORKQ_SCHEDULE(dwc_workq_t *workq, dwc_work_callback_t cb, + void *data, char *format, ...) +#ifdef __GNUC__ + __attribute__ ((format(printf, 4, 5))); +#else + ; +#endif +#define dwc_workq_schedule DWC_WORKQ_SCHEDULE + +/** Schedule a callback on the workq, that will be called until at least + * given number miliseconds have passed. */ +extern void DWC_WORKQ_SCHEDULE_DELAYED(dwc_workq_t *workq, dwc_work_callback_t cb, + void *data, uint32_t time, char *format, ...) +#ifdef __GNUC__ + __attribute__ ((format(printf, 5, 6))); +#else + ; +#endif +#define dwc_workq_schedule_delayed DWC_WORKQ_SCHEDULE_DELAYED + +/** The number of processes in the workq */ +extern int DWC_WORKQ_PENDING(dwc_workq_t *workq); +#define dwc_workq_pending DWC_WORKQ_PENDING + +/** Blocks until all the work in the workq is complete or timed out. Returns < + * 0 on timeout. */ +extern int DWC_WORKQ_WAIT_WORK_DONE(dwc_workq_t *workq, int timeout); +#define dwc_workq_wait_work_done DWC_WORKQ_WAIT_WORK_DONE + + +/** @name Tasklets + * + */ +struct dwc_tasklet; + +/** Type for a tasklet */ +typedef struct dwc_tasklet dwc_tasklet_t; + +/** The type of the callback function to be called */ +typedef void (*dwc_tasklet_callback_t)(void *data); + +/** Allocates a tasklet */ +extern dwc_tasklet_t *DWC_TASK_ALLOC(char *name, dwc_tasklet_callback_t cb, void *data); +#define dwc_task_alloc(_ctx_,_name_,_cb_,_data_) DWC_TASK_ALLOC(_name_, _cb_, _data_) + +/** Frees a tasklet */ +extern void DWC_TASK_FREE(dwc_tasklet_t *task); +#define dwc_task_free DWC_TASK_FREE + +/** Schedules a tasklet to run */ +extern void DWC_TASK_SCHEDULE(dwc_tasklet_t *task); +#define dwc_task_schedule DWC_TASK_SCHEDULE + +extern void DWC_TASK_HI_SCHEDULE(dwc_tasklet_t *task); +#define dwc_task_hi_schedule DWC_TASK_HI_SCHEDULE + +/** @name Timer + * + * Callbacks must be small and atomic. + */ +struct dwc_timer; + +/** Type for a timer */ +typedef struct dwc_timer dwc_timer_t; + +/** The type of the callback function to be called */ +typedef void (*dwc_timer_callback_t)(void *data); + +/** Allocates a timer */ +extern dwc_timer_t *DWC_TIMER_ALLOC(char *name, dwc_timer_callback_t cb, void *data); +#define dwc_timer_alloc(_ctx_,_name_,_cb_,_data_) DWC_TIMER_ALLOC(_name_,_cb_,_data_) + +/** Frees a timer */ +extern void DWC_TIMER_FREE(dwc_timer_t *timer); +#define dwc_timer_free DWC_TIMER_FREE + +/** Schedules the timer to run at time ms from now. And will repeat at every + * repeat_interval msec therafter + * + * Modifies a timer that is still awaiting execution to a new expiration time. + * The mod_time is added to the old time. */ +extern void DWC_TIMER_SCHEDULE(dwc_timer_t *timer, uint32_t time); +#define dwc_timer_schedule DWC_TIMER_SCHEDULE + +/** Disables the timer from execution. */ +extern void DWC_TIMER_CANCEL(dwc_timer_t *timer); +#define dwc_timer_cancel DWC_TIMER_CANCEL + + +/** @name Spinlocks + * + * These locks are used when the work between the lock/unlock is atomic and + * short. Interrupts are also disabled during the lock/unlock and thus they are + * suitable to lock between interrupt/non-interrupt context. They also lock + * between processes if you have multiple CPUs or Preemption. If you don't have + * multiple CPUS or Preemption, then the you can simply implement the + * DWC_SPINLOCK and DWC_SPINUNLOCK to disable and enable interrupts. Because + * the work between the lock/unlock is atomic, the process context will never + * change, and so you never have to lock between processes. */ + +struct dwc_spinlock; + +/** Type for a spinlock */ +typedef struct dwc_spinlock dwc_spinlock_t; + +/** Type for the 'flags' argument to spinlock funtions */ +typedef unsigned long dwc_irqflags_t; + +/** Returns an initialized lock variable. This function should allocate and + * initialize the OS-specific data structure used for locking. This data + * structure is to be used for the DWC_LOCK and DWC_UNLOCK functions and should + * be freed by the DWC_FREE_LOCK when it is no longer used. + * + * For Linux Spinlock Debugging make it macro because the debugging routines use + * the symbol name to determine recursive locking. Using a wrapper function + * makes it falsely think recursive locking occurs. */ +#if defined(DWC_LINUX) && defined(CONFIG_DEBUG_SPINLOCK) +#define DWC_SPINLOCK_ALLOC_LINUX_DEBUG(lock) ({ \ + lock = DWC_ALLOC(sizeof(spinlock_t)); \ + if (lock) { \ + spin_lock_init((spinlock_t *)lock); \ + } \ +}) +#else +extern dwc_spinlock_t *DWC_SPINLOCK_ALLOC(void); +#define dwc_spinlock_alloc(_ctx_) DWC_SPINLOCK_ALLOC() +#endif + +/** Frees an initialized lock variable. */ +extern void DWC_SPINLOCK_FREE(dwc_spinlock_t *lock); +#define dwc_spinlock_free(_ctx_,_lock_) DWC_SPINLOCK_FREE(_lock_) + +/** Disables interrupts and blocks until it acquires the lock. + * + * @param lock Pointer to the spinlock. + * @param flags Unsigned long for irq flags storage. + */ +extern void DWC_SPINLOCK_IRQSAVE(dwc_spinlock_t *lock, dwc_irqflags_t *flags); +#define dwc_spinlock_irqsave DWC_SPINLOCK_IRQSAVE + +/** Re-enables the interrupt and releases the lock. + * + * @param lock Pointer to the spinlock. + * @param flags Unsigned long for irq flags storage. Must be the same as was + * passed into DWC_LOCK. + */ +extern void DWC_SPINUNLOCK_IRQRESTORE(dwc_spinlock_t *lock, dwc_irqflags_t flags); +#define dwc_spinunlock_irqrestore DWC_SPINUNLOCK_IRQRESTORE + +/** Blocks until it acquires the lock. + * + * @param lock Pointer to the spinlock. + */ +extern void DWC_SPINLOCK(dwc_spinlock_t *lock); +#define dwc_spinlock DWC_SPINLOCK + +/** Releases the lock. + * + * @param lock Pointer to the spinlock. + */ +extern void DWC_SPINUNLOCK(dwc_spinlock_t *lock); +#define dwc_spinunlock DWC_SPINUNLOCK + + +/** @name Mutexes + * + * Unlike spinlocks Mutexes lock only between processes and the work between the + * lock/unlock CAN block, therefore it CANNOT be called from interrupt context. + */ + +struct dwc_mutex; + +/** Type for a mutex */ +typedef struct dwc_mutex dwc_mutex_t; + +/* For Linux Mutex Debugging make it inline because the debugging routines use + * the symbol to determine recursive locking. This makes it falsely think + * recursive locking occurs. */ +#if defined(DWC_LINUX) && defined(CONFIG_DEBUG_MUTEXES) +#define DWC_MUTEX_ALLOC_LINUX_DEBUG(__mutexp) ({ \ + __mutexp = (dwc_mutex_t *)DWC_ALLOC(sizeof(struct mutex)); \ + mutex_init((struct mutex *)__mutexp); \ +}) +#endif + +/** Allocate a mutex */ +extern dwc_mutex_t *DWC_MUTEX_ALLOC(void); +#define dwc_mutex_alloc(_ctx_) DWC_MUTEX_ALLOC() + +/* For memory leak debugging when using Linux Mutex Debugging */ +#if defined(DWC_LINUX) && defined(CONFIG_DEBUG_MUTEXES) +#define DWC_MUTEX_FREE(__mutexp) do { \ + mutex_destroy((struct mutex *)__mutexp); \ + DWC_FREE(__mutexp); \ +} while(0) +#else +/** Free a mutex */ +extern void DWC_MUTEX_FREE(dwc_mutex_t *mutex); +#define dwc_mutex_free(_ctx_,_mutex_) DWC_MUTEX_FREE(_mutex_) +#endif + +/** Lock a mutex */ +extern void DWC_MUTEX_LOCK(dwc_mutex_t *mutex); +#define dwc_mutex_lock DWC_MUTEX_LOCK + +/** Non-blocking lock returns 1 on successful lock. */ +extern int DWC_MUTEX_TRYLOCK(dwc_mutex_t *mutex); +#define dwc_mutex_trylock DWC_MUTEX_TRYLOCK + +/** Unlock a mutex */ +extern void DWC_MUTEX_UNLOCK(dwc_mutex_t *mutex); +#define dwc_mutex_unlock DWC_MUTEX_UNLOCK + + +/** @name Time */ + +/** Microsecond delay. + * + * @param usecs Microseconds to delay. + */ +extern void DWC_UDELAY(uint32_t usecs); +#define dwc_udelay DWC_UDELAY + +/** Millisecond delay. + * + * @param msecs Milliseconds to delay. + */ +extern void DWC_MDELAY(uint32_t msecs); +#define dwc_mdelay DWC_MDELAY + +/** Non-busy waiting. + * Sleeps for specified number of milliseconds. + * + * @param msecs Milliseconds to sleep. + */ +extern void DWC_MSLEEP(uint32_t msecs); +#define dwc_msleep DWC_MSLEEP + +/** + * Returns number of milliseconds since boot. + */ +extern uint32_t DWC_TIME(void); +#define dwc_time DWC_TIME + + + + +/* @mainpage DWC Portability and Common Library + * + * This is the documentation for the DWC Portability and Common Library. + * + * @section intro Introduction + * + * The DWC Portability library consists of wrapper calls and data structures to + * all low-level functions which are typically provided by the OS. The WUDEV + * driver uses only these functions. In order to port the WUDEV driver, only + * the functions in this library need to be re-implemented, with the same + * behavior as documented here. + * + * The Common library consists of higher level functions, which rely only on + * calling the functions from the DWC Portability library. These common + * routines are shared across modules. Some of the common libraries need to be + * used directly by the driver programmer when porting WUDEV. Such as the + * parameter and notification libraries. + * + * @section low Portability Library OS Wrapper Functions + * + * Any function starting with DWC and in all CAPS is a low-level OS-wrapper that + * needs to be implemented when porting, for example DWC_MUTEX_ALLOC(). All of + * these functions are included in the dwc_os.h file. + * + * There are many functions here covering a wide array of OS services. Please + * see dwc_os.h for details, and implementation notes for each function. + * + * @section common Common Library Functions + * + * Any function starting with dwc and in all lowercase is a common library + * routine. These functions have a portable implementation and do not need to + * be reimplemented when porting. The common routines can be used by any + * driver, and some must be used by the end user to control the drivers. For + * example, you must use the Parameter common library in order to set the + * parameters in the WUDEV module. + * + * The common libraries consist of the following: + * + * - Connection Contexts - Used internally and can be used by end-user. See dwc_cc.h + * - Parameters - Used internally and can be used by end-user. See dwc_params.h + * - Notifications - Used internally and can be used by end-user. See dwc_notifier.h + * - Lists - Used internally and can be used by end-user. See dwc_list.h + * - Memory Debugging - Used internally and can be used by end-user. See dwc_os.h + * - Modpow - Used internally only. See dwc_modpow.h + * - DH - Used internally only. See dwc_dh.h + * - Crypto - Used internally only. See dwc_crypto.h + * + * + * @section prereq Prerequistes For dwc_os.h + * @subsection types Data Types + * + * The dwc_os.h file assumes that several low-level data types are pre defined for the + * compilation environment. These data types are: + * + * - uint8_t - unsigned 8-bit data type + * - int8_t - signed 8-bit data type + * - uint16_t - unsigned 16-bit data type + * - int16_t - signed 16-bit data type + * - uint32_t - unsigned 32-bit data type + * - int32_t - signed 32-bit data type + * - uint64_t - unsigned 64-bit data type + * - int64_t - signed 64-bit data type + * + * Ensure that these are defined before using dwc_os.h. The easiest way to do + * that is to modify the top of the file to include the appropriate header. + * This is already done for the Linux environment. If the DWC_LINUX macro is + * defined, the correct header will be added. A standard header <stdint.h> is + * also used for environments where standard C headers are available. + * + * @subsection stdarg Variable Arguments + * + * Variable arguments are provided by a standard C header <stdarg.h>. it is + * available in Both the Linux and ANSI C enviornment. An equivalent must be + * provided in your enviornment in order to use dwc_os.h with the debug and + * tracing message functionality. + * + * @subsection thread Threading + * + * WUDEV Core must be run on an operating system that provides for multiple + * threads/processes. Threading can be implemented in many ways, even in + * embedded systems without an operating system. At the bare minimum, the + * system should be able to start any number of processes at any time to handle + * special work. It need not be a pre-emptive system. Process context can + * change upon a call to a blocking function. The hardware interrupt context + * that calls the module's ISR() function must be differentiable from process + * context, even if your processes are impemented via a hardware interrupt. + * Further locking mechanism between process must exist (or be implemented), and + * process context must have a way to disable interrupts for a period of time to + * lock them out. If all of this exists, the functions in dwc_os.h related to + * threading should be able to be implemented with the defined behavior. + * + */ + +#ifdef __cplusplus +} +#endif + +#endif /* _DWC_OS_H_ */ diff --git a/drivers/usb/host/dwc_common_port/usb.h b/drivers/usb/host/dwc_common_port/usb.h new file mode 100644 index 00000000000000..b1cedb1876b7fa --- /dev/null +++ b/drivers/usb/host/dwc_common_port/usb.h @@ -0,0 +1,275 @@ +/* + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* Modified by Synopsys, Inc, 12/12/2007 */ + + +#ifndef _USB_H_ +#define _USB_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * The USB records contain some unaligned little-endian word + * components. The U[SG]ETW macros take care of both the alignment + * and endian problem and should always be used to access non-byte + * values. + */ +typedef u_int8_t uByte; +typedef u_int8_t uWord[2]; +typedef u_int8_t uDWord[4]; + +#define UGETW(w) ((w)[0] | ((w)[1] << 8)) +#define USETW(w,v) ((w)[0] = (u_int8_t)(v), (w)[1] = (u_int8_t)((v) >> 8)) +#define UGETDW(w) ((w)[0] | ((w)[1] << 8) | ((w)[2] << 16) | ((w)[3] << 24)) +#define USETDW(w,v) ((w)[0] = (u_int8_t)(v), \ + (w)[1] = (u_int8_t)((v) >> 8), \ + (w)[2] = (u_int8_t)((v) >> 16), \ + (w)[3] = (u_int8_t)((v) >> 24)) + +#define UPACKED __attribute__((__packed__)) + +typedef struct { + uByte bmRequestType; + uByte bRequest; + uWord wValue; + uWord wIndex; + uWord wLength; +} UPACKED usb_device_request_t; + +#define UT_GET_DIR(a) ((a) & 0x80) +#define UT_WRITE 0x00 +#define UT_READ 0x80 + +#define UT_GET_TYPE(a) ((a) & 0x60) +#define UT_STANDARD 0x00 +#define UT_CLASS 0x20 +#define UT_VENDOR 0x40 + +#define UT_GET_RECIPIENT(a) ((a) & 0x1f) +#define UT_DEVICE 0x00 +#define UT_INTERFACE 0x01 +#define UT_ENDPOINT 0x02 +#define UT_OTHER 0x03 + +/* Requests */ +#define UR_GET_STATUS 0x00 +#define USTAT_STANDARD_STATUS 0x00 +#define WUSTAT_WUSB_FEATURE 0x01 +#define WUSTAT_CHANNEL_INFO 0x02 +#define WUSTAT_RECEIVED_DATA 0x03 +#define WUSTAT_MAS_AVAILABILITY 0x04 +#define WUSTAT_CURRENT_TRANSMIT_POWER 0x05 +#define UR_CLEAR_FEATURE 0x01 +#define UR_SET_FEATURE 0x03 +#define UR_SET_AND_TEST_FEATURE 0x0c +#define UR_SET_ADDRESS 0x05 +#define UR_GET_DESCRIPTOR 0x06 +#define UDESC_DEVICE 0x01 +#define UDESC_CONFIG 0x02 +#define UDESC_STRING 0x03 +#define UDESC_INTERFACE 0x04 +#define UDESC_ENDPOINT 0x05 +#define UDESC_SS_USB_COMPANION 0x30 +#define UDESC_DEVICE_QUALIFIER 0x06 +#define UDESC_OTHER_SPEED_CONFIGURATION 0x07 +#define UDESC_INTERFACE_POWER 0x08 +#define UDESC_OTG 0x09 +#define WUDESC_SECURITY 0x0c +#define WUDESC_KEY 0x0d +#define WUD_GET_KEY_INDEX(_wValue_) ((_wValue_) & 0xf) +#define WUD_GET_KEY_TYPE(_wValue_) (((_wValue_) & 0x30) >> 4) +#define WUD_KEY_TYPE_ASSOC 0x01 +#define WUD_KEY_TYPE_GTK 0x02 +#define WUD_GET_KEY_ORIGIN(_wValue_) (((_wValue_) & 0x40) >> 6) +#define WUD_KEY_ORIGIN_HOST 0x00 +#define WUD_KEY_ORIGIN_DEVICE 0x01 +#define WUDESC_ENCRYPTION_TYPE 0x0e +#define WUDESC_BOS 0x0f +#define WUDESC_DEVICE_CAPABILITY 0x10 +#define WUDESC_WIRELESS_ENDPOINT_COMPANION 0x11 +#define UDESC_BOS 0x0f +#define UDESC_DEVICE_CAPABILITY 0x10 +#define UDESC_CS_DEVICE 0x21 /* class specific */ +#define UDESC_CS_CONFIG 0x22 +#define UDESC_CS_STRING 0x23 +#define UDESC_CS_INTERFACE 0x24 +#define UDESC_CS_ENDPOINT 0x25 +#define UDESC_HUB 0x29 +#define UR_SET_DESCRIPTOR 0x07 +#define UR_GET_CONFIG 0x08 +#define UR_SET_CONFIG 0x09 +#define UR_GET_INTERFACE 0x0a +#define UR_SET_INTERFACE 0x0b +#define UR_SYNCH_FRAME 0x0c +#define WUR_SET_ENCRYPTION 0x0d +#define WUR_GET_ENCRYPTION 0x0e +#define WUR_SET_HANDSHAKE 0x0f +#define WUR_GET_HANDSHAKE 0x10 +#define WUR_SET_CONNECTION 0x11 +#define WUR_SET_SECURITY_DATA 0x12 +#define WUR_GET_SECURITY_DATA 0x13 +#define WUR_SET_WUSB_DATA 0x14 +#define WUDATA_DRPIE_INFO 0x01 +#define WUDATA_TRANSMIT_DATA 0x02 +#define WUDATA_TRANSMIT_PARAMS 0x03 +#define WUDATA_RECEIVE_PARAMS 0x04 +#define WUDATA_TRANSMIT_POWER 0x05 +#define WUR_LOOPBACK_DATA_WRITE 0x15 +#define WUR_LOOPBACK_DATA_READ 0x16 +#define WUR_SET_INTERFACE_DS 0x17 + +/* Feature numbers */ +#define UF_ENDPOINT_HALT 0 +#define UF_DEVICE_REMOTE_WAKEUP 1 +#define UF_TEST_MODE 2 +#define UF_DEVICE_B_HNP_ENABLE 3 +#define UF_DEVICE_A_HNP_SUPPORT 4 +#define UF_DEVICE_A_ALT_HNP_SUPPORT 5 +#define WUF_WUSB 3 +#define WUF_TX_DRPIE 0x0 +#define WUF_DEV_XMIT_PACKET 0x1 +#define WUF_COUNT_PACKETS 0x2 +#define WUF_CAPTURE_PACKETS 0x3 +#define UF_FUNCTION_SUSPEND 0 +#define UF_U1_ENABLE 48 +#define UF_U2_ENABLE 49 +#define UF_LTM_ENABLE 50 + +/* Class requests from the USB 2.0 hub spec, table 11-15 */ +#define UCR_CLEAR_HUB_FEATURE (0x2000 | UR_CLEAR_FEATURE) +#define UCR_CLEAR_PORT_FEATURE (0x2300 | UR_CLEAR_FEATURE) +#define UCR_GET_HUB_DESCRIPTOR (0xa000 | UR_GET_DESCRIPTOR) +#define UCR_GET_HUB_STATUS (0xa000 | UR_GET_STATUS) +#define UCR_GET_PORT_STATUS (0xa300 | UR_GET_STATUS) +#define UCR_SET_HUB_FEATURE (0x2000 | UR_SET_FEATURE) +#define UCR_SET_PORT_FEATURE (0x2300 | UR_SET_FEATURE) +#define UCR_SET_AND_TEST_PORT_FEATURE (0xa300 | UR_SET_AND_TEST_FEATURE) + +#ifdef _MSC_VER +#include <pshpack1.h> +#endif + +typedef struct { + uByte bLength; + uByte bDescriptorType; + uByte bEndpointAddress; +#define UE_GET_DIR(a) ((a) & 0x80) +#define UE_SET_DIR(a,d) ((a) | (((d)&1) << 7)) +#define UE_DIR_IN 0x80 +#define UE_DIR_OUT 0x00 +#define UE_ADDR 0x0f +#define UE_GET_ADDR(a) ((a) & UE_ADDR) + uByte bmAttributes; +#define UE_XFERTYPE 0x03 +#define UE_CONTROL 0x00 +#define UE_ISOCHRONOUS 0x01 +#define UE_BULK 0x02 +#define UE_INTERRUPT 0x03 +#define UE_GET_XFERTYPE(a) ((a) & UE_XFERTYPE) +#define UE_ISO_TYPE 0x0c +#define UE_ISO_ASYNC 0x04 +#define UE_ISO_ADAPT 0x08 +#define UE_ISO_SYNC 0x0c +#define UE_GET_ISO_TYPE(a) ((a) & UE_ISO_TYPE) + uWord wMaxPacketSize; + uByte bInterval; +} UPACKED usb_endpoint_descriptor_t; +#define USB_ENDPOINT_DESCRIPTOR_SIZE 7 + +/* Hub specific request */ +#define UR_GET_BUS_STATE 0x02 +#define UR_CLEAR_TT_BUFFER 0x08 +#define UR_RESET_TT 0x09 +#define UR_GET_TT_STATE 0x0a +#define UR_STOP_TT 0x0b + +/* Hub features */ +#define UHF_C_HUB_LOCAL_POWER 0 +#define UHF_C_HUB_OVER_CURRENT 1 +#define UHF_PORT_CONNECTION 0 +#define UHF_PORT_ENABLE 1 +#define UHF_PORT_SUSPEND 2 +#define UHF_PORT_OVER_CURRENT 3 +#define UHF_PORT_RESET 4 +#define UHF_PORT_L1 5 +#define UHF_PORT_POWER 8 +#define UHF_PORT_LOW_SPEED 9 +#define UHF_PORT_HIGH_SPEED 10 +#define UHF_C_PORT_CONNECTION 16 +#define UHF_C_PORT_ENABLE 17 +#define UHF_C_PORT_SUSPEND 18 +#define UHF_C_PORT_OVER_CURRENT 19 +#define UHF_C_PORT_RESET 20 +#define UHF_C_PORT_L1 23 +#define UHF_PORT_TEST 21 +#define UHF_PORT_INDICATOR 22 + +typedef struct { + uByte bDescLength; + uByte bDescriptorType; + uByte bNbrPorts; + uWord wHubCharacteristics; +#define UHD_PWR 0x0003 +#define UHD_PWR_GANGED 0x0000 +#define UHD_PWR_INDIVIDUAL 0x0001 +#define UHD_PWR_NO_SWITCH 0x0002 +#define UHD_COMPOUND 0x0004 +#define UHD_OC 0x0018 +#define UHD_OC_GLOBAL 0x0000 +#define UHD_OC_INDIVIDUAL 0x0008 +#define UHD_OC_NONE 0x0010 +#define UHD_TT_THINK 0x0060 +#define UHD_TT_THINK_8 0x0000 +#define UHD_TT_THINK_16 0x0020 +#define UHD_TT_THINK_24 0x0040 +#define UHD_TT_THINK_32 0x0060 +#define UHD_PORT_IND 0x0080 + uByte bPwrOn2PwrGood; /* delay in 2 ms units */ +#define UHD_PWRON_FACTOR 2 + uByte bHubContrCurrent; + uByte DeviceRemovable[32]; /* max 255 ports */ +#define UHD_NOT_REMOV(desc, i) \ + (((desc)->DeviceRemovable[(i)/8] >> ((i) % 8)) & 1) + /* deprecated */ uByte PortPowerCtrlMask[1]; +} UPACKED usb_hub_descriptor_t; +#define USB_HUB_DESCRIPTOR_SIZE 9 /* includes deprecated PortPowerCtrlMask */ + +#ifdef _MSC_VER +#include <poppack.h> +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* _USB_H_ */ diff --git a/drivers/usb/host/dwc_otg/Makefile b/drivers/usb/host/dwc_otg/Makefile new file mode 100644 index 00000000000000..8cbe3e684f26a8 --- /dev/null +++ b/drivers/usb/host/dwc_otg/Makefile @@ -0,0 +1,86 @@ +# +# Makefile for DWC_otg Highspeed USB controller driver +# + +ifneq ($(KERNELRELEASE),) + +# Use the BUS_INTERFACE variable to compile the software for either +# PCI(PCI_INTERFACE) or LM(LM_INTERFACE) bus. +ifeq ($(BUS_INTERFACE),) +# BUS_INTERFACE = -DPCI_INTERFACE +# BUS_INTERFACE = -DLM_INTERFACE + BUS_INTERFACE = -DPLATFORM_INTERFACE +endif + +#ccflags-y += -DDEBUG +#ccflags-y += -DDWC_OTG_DEBUGLEV=1 # reduce common debug msgs + +# Use one of the following flags to compile the software in host-only or +# device-only mode. +#ccflags-y += -DDWC_HOST_ONLY +#ccflags-y += -DDWC_DEVICE_ONLY + +ccflags-y += -Dlinux -DDWC_HS_ELECT_TST +#ccflags-y += -DDWC_EN_ISOC +ccflags-y += -I$(srctree)/drivers/usb/host/dwc_common_port +#ccflags-y += -I$(PORTLIB) +ccflags-y += -DDWC_LINUX +ccflags-y += $(CFI) +ccflags-y += $(BUS_INTERFACE) +#ccflags-y += -DDWC_DEV_SRPCAP +CFLAGS_dwc_otg_fiq_fsm.o += -fno-stack-protector + +obj-$(CONFIG_USB_DWCOTG) += dwc_otg.o + +dwc_otg-objs := dwc_otg_driver.o dwc_otg_attr.o +dwc_otg-objs += dwc_otg_cil.o dwc_otg_cil_intr.o +dwc_otg-objs += dwc_otg_pcd_linux.o dwc_otg_pcd.o dwc_otg_pcd_intr.o +dwc_otg-objs += dwc_otg_hcd.o dwc_otg_hcd_linux.o dwc_otg_hcd_intr.o dwc_otg_hcd_queue.o dwc_otg_hcd_ddma.o +dwc_otg-objs += dwc_otg_adp.o +dwc_otg-objs += dwc_otg_fiq_fsm.o +ifneq ($(CONFIG_ARM64),y) +dwc_otg-objs += dwc_otg_fiq_stub.o +endif + +ifneq ($(CFI),) +dwc_otg-objs += dwc_otg_cfi.o +endif + +kernrelwd := $(subst ., ,$(KERNELRELEASE)) +kernrel3 := $(word 1,$(kernrelwd)).$(word 2,$(kernrelwd)).$(word 3,$(kernrelwd)) + +ifneq ($(kernrel3),2.6.20) +ccflags-y += $(CPPFLAGS) +endif + +else + +PWD := $(shell pwd) +PORTLIB := $(PWD)/../dwc_common_port + +# Command paths +CTAGS := $(CTAGS) +DOXYGEN := $(DOXYGEN) + +default: portlib + $(MAKE) -C$(KDIR) M=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules + +install: default + $(MAKE) -C$(KDIR) M=$(PORTLIB) modules_install + $(MAKE) -C$(KDIR) M=$(PWD) modules_install + +portlib: + $(MAKE) -C$(KDIR) M=$(PORTLIB) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules + cp $(PORTLIB)/Module.symvers $(PWD)/ + +docs: $(wildcard *.[hc]) doc/doxygen.cfg + $(DOXYGEN) doc/doxygen.cfg + +tags: $(wildcard *.[hc]) + $(CTAGS) -e $(wildcard *.[hc]) $(wildcard linux/*.[hc]) $(wildcard $(KDIR)/include/linux/usb*.h) + + +clean: + rm -rf *.o *.ko .*cmd *.mod.c .tmp_versions Module.symvers + +endif diff --git a/drivers/usb/host/dwc_otg/doc/doxygen.cfg b/drivers/usb/host/dwc_otg/doc/doxygen.cfg new file mode 100644 index 00000000000000..712b057ef7c293 --- /dev/null +++ b/drivers/usb/host/dwc_otg/doc/doxygen.cfg @@ -0,0 +1,224 @@ +# Doxyfile 1.3.9.1 + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- +PROJECT_NAME = "DesignWare USB 2.0 OTG Controller (DWC_otg) Device Driver" +PROJECT_NUMBER = v3.00a +OUTPUT_DIRECTORY = ./doc/ +CREATE_SUBDIRS = NO +OUTPUT_LANGUAGE = English +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the +ALWAYS_DETAILED_SEC = NO +INLINE_INHERITED_MEMB = NO +FULL_PATH_NAMES = NO +STRIP_FROM_PATH = +STRIP_FROM_INC_PATH = +SHORT_NAMES = NO +JAVADOC_AUTOBRIEF = YES +MULTILINE_CPP_IS_BRIEF = NO +INHERIT_DOCS = YES +DISTRIBUTE_GROUP_DOC = NO +TAB_SIZE = 8 +ALIASES = +OPTIMIZE_OUTPUT_FOR_C = YES +OPTIMIZE_OUTPUT_JAVA = NO +SUBGROUPING = YES +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- +EXTRACT_ALL = NO +EXTRACT_PRIVATE = YES +EXTRACT_STATIC = YES +EXTRACT_LOCAL_CLASSES = YES +EXTRACT_LOCAL_METHODS = NO +HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_CLASSES = NO +HIDE_FRIEND_COMPOUNDS = NO +HIDE_IN_BODY_DOCS = NO +INTERNAL_DOCS = NO +CASE_SENSE_NAMES = NO +HIDE_SCOPE_NAMES = NO +SHOW_INCLUDE_FILES = YES +INLINE_INFO = YES +SORT_MEMBER_DOCS = NO +SORT_BRIEF_DOCS = NO +SORT_BY_SCOPE_NAME = NO +GENERATE_TODOLIST = YES +GENERATE_TESTLIST = YES +GENERATE_BUGLIST = YES +GENERATE_DEPRECATEDLIST= YES +ENABLED_SECTIONS = +MAX_INITIALIZER_LINES = 30 +SHOW_USED_FILES = YES +SHOW_DIRECTORIES = YES +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- +QUIET = YES +WARNINGS = YES +WARN_IF_UNDOCUMENTED = NO +WARN_IF_DOC_ERROR = YES +WARN_FORMAT = "$file:$line: $text" +WARN_LOGFILE = +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- +INPUT = . +FILE_PATTERNS = *.c \ + *.h \ + ./linux/*.c \ + ./linux/*.h +RECURSIVE = NO +EXCLUDE = ./test/ \ + ./dwc_otg/.AppleDouble/ +EXCLUDE_SYMLINKS = YES +EXCLUDE_PATTERNS = *.mod.* +EXAMPLE_PATH = +EXAMPLE_PATTERNS = * +EXAMPLE_RECURSIVE = NO +IMAGE_PATH = +INPUT_FILTER = +FILTER_PATTERNS = +FILTER_SOURCE_FILES = NO +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- +SOURCE_BROWSER = YES +INLINE_SOURCES = NO +STRIP_CODE_COMMENTS = YES +REFERENCED_BY_RELATION = NO +REFERENCES_RELATION = NO +VERBATIM_HEADERS = NO +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- +ALPHABETICAL_INDEX = NO +COLS_IN_ALPHA_INDEX = 5 +IGNORE_PREFIX = +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- +GENERATE_HTML = YES +HTML_OUTPUT = html +HTML_FILE_EXTENSION = .html +HTML_HEADER = +HTML_FOOTER = +HTML_STYLESHEET = +HTML_ALIGN_MEMBERS = YES +GENERATE_HTMLHELP = NO +CHM_FILE = +HHC_LOCATION = +GENERATE_CHI = NO +BINARY_TOC = NO +TOC_EXPAND = NO +DISABLE_INDEX = NO +ENUM_VALUES_PER_LINE = 4 +GENERATE_TREEVIEW = YES +TREEVIEW_WIDTH = 250 +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- +GENERATE_LATEX = NO +LATEX_OUTPUT = latex +LATEX_CMD_NAME = latex +MAKEINDEX_CMD_NAME = makeindex +COMPACT_LATEX = NO +PAPER_TYPE = a4wide +EXTRA_PACKAGES = +LATEX_HEADER = +PDF_HYPERLINKS = NO +USE_PDFLATEX = NO +LATEX_BATCHMODE = NO +LATEX_HIDE_INDICES = NO +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- +GENERATE_RTF = NO +RTF_OUTPUT = rtf +COMPACT_RTF = NO +RTF_HYPERLINKS = NO +RTF_STYLESHEET_FILE = +RTF_EXTENSIONS_FILE = +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- +GENERATE_MAN = NO +MAN_OUTPUT = man +MAN_EXTENSION = .3 +MAN_LINKS = NO +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- +GENERATE_XML = NO +XML_OUTPUT = xml +XML_SCHEMA = +XML_DTD = +XML_PROGRAMLISTING = YES +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- +GENERATE_AUTOGEN_DEF = NO +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- +GENERATE_PERLMOD = NO +PERLMOD_LATEX = NO +PERLMOD_PRETTY = YES +PERLMOD_MAKEVAR_PREFIX = +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = YES +EXPAND_ONLY_PREDEF = YES +SEARCH_INCLUDES = YES +INCLUDE_PATH = +INCLUDE_FILE_PATTERNS = +PREDEFINED = DEVICE_ATTR DWC_EN_ISOC +EXPAND_AS_DEFINED = DWC_OTG_DEVICE_ATTR_BITFIELD_SHOW DWC_OTG_DEVICE_ATTR_BITFIELD_STORE DWC_OTG_DEVICE_ATTR_BITFIELD_RW DWC_OTG_DEVICE_ATTR_BITFIELD_RO DWC_OTG_DEVICE_ATTR_REG_SHOW DWC_OTG_DEVICE_ATTR_REG_STORE DWC_OTG_DEVICE_ATTR_REG32_RW DWC_OTG_DEVICE_ATTR_REG32_RO DWC_EN_ISOC +SKIP_FUNCTION_MACROS = NO +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- +TAGFILES = +GENERATE_TAGFILE = +ALLEXTERNALS = NO +EXTERNAL_GROUPS = YES +PERL_PATH = /usr/bin/perl +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- +CLASS_DIAGRAMS = YES +HIDE_UNDOC_RELATIONS = YES +HAVE_DOT = NO +CLASS_GRAPH = YES +COLLABORATION_GRAPH = YES +UML_LOOK = NO +TEMPLATE_RELATIONS = NO +INCLUDE_GRAPH = YES +INCLUDED_BY_GRAPH = YES +CALL_GRAPH = NO +GRAPHICAL_HIERARCHY = YES +DOT_IMAGE_FORMAT = png +DOT_PATH = +DOTFILE_DIRS = +MAX_DOT_GRAPH_DEPTH = 1000 +GENERATE_LEGEND = YES +DOT_CLEANUP = YES +#--------------------------------------------------------------------------- +# Configuration::additions related to the search engine +#--------------------------------------------------------------------------- +SEARCHENGINE = NO diff --git a/drivers/usb/host/dwc_otg/dummy_audio.c b/drivers/usb/host/dwc_otg/dummy_audio.c new file mode 100644 index 00000000000000..d1d03da60e380e --- /dev/null +++ b/drivers/usb/host/dwc_otg/dummy_audio.c @@ -0,0 +1,1574 @@ +/* + * zero.c -- Gadget Zero, for USB development + * + * Copyright (C) 2003-2004 David Brownell + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The names of the above-listed copyright holders may not be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") as published by the Free Software + * Foundation, either version 2 of that License or (at your option) any + * later version. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +/* + * Gadget Zero only needs two bulk endpoints, and is an example of how you + * can write a hardware-agnostic gadget driver running inside a USB device. + * + * Hardware details are visible (see CONFIG_USB_ZERO_* below) but don't + * affect most of the driver. + * + * Use it with the Linux host/master side "usbtest" driver to get a basic + * functional test of your device-side usb stack, or with "usb-skeleton". + * + * It supports two similar configurations. One sinks whatever the usb host + * writes, and in return sources zeroes. The other loops whatever the host + * writes back, so the host can read it. Module options include: + * + * buflen=N default N=4096, buffer size used + * qlen=N default N=32, how many buffers in the loopback queue + * loopdefault default false, list loopback config first + * + * Many drivers will only have one configuration, letting them be much + * simpler if they also don't support high speed operation (like this + * driver does). + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/ioport.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/smp_lock.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/timer.h> +#include <linux/list.h> +#include <linux/interrupt.h> +#include <linux/uts.h> +#include <linux/version.h> +#include <linux/device.h> +#include <linux/moduleparam.h> +#include <linux/proc_fs.h> + +#include <asm/byteorder.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/system.h> +#include <linux/unaligned.h> + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,21) +# include <linux/usb/ch9.h> +#else +# include <linux/usb_ch9.h> +#endif + +#include <linux/usb_gadget.h> + + +/*-------------------------------------------------------------------------*/ +/*-------------------------------------------------------------------------*/ + + +static int utf8_to_utf16le(const char *s, u16 *cp, unsigned len) +{ + int count = 0; + u8 c; + u16 uchar; + + /* this insists on correct encodings, though not minimal ones. + * BUT it currently rejects legit 4-byte UTF-8 code points, + * which need surrogate pairs. (Unicode 3.1 can use them.) + */ + while (len != 0 && (c = (u8) *s++) != 0) { + if (unlikely(c & 0x80)) { + // 2-byte sequence: + // 00000yyyyyxxxxxx = 110yyyyy 10xxxxxx + if ((c & 0xe0) == 0xc0) { + uchar = (c & 0x1f) << 6; + + c = (u8) *s++; + if ((c & 0xc0) != 0xc0) + goto fail; + c &= 0x3f; + uchar |= c; + + // 3-byte sequence (most CJKV characters): + // zzzzyyyyyyxxxxxx = 1110zzzz 10yyyyyy 10xxxxxx + } else if ((c & 0xf0) == 0xe0) { + uchar = (c & 0x0f) << 12; + + c = (u8) *s++; + if ((c & 0xc0) != 0xc0) + goto fail; + c &= 0x3f; + uchar |= c << 6; + + c = (u8) *s++; + if ((c & 0xc0) != 0xc0) + goto fail; + c &= 0x3f; + uchar |= c; + + /* no bogus surrogates */ + if (0xd800 <= uchar && uchar <= 0xdfff) + goto fail; + + // 4-byte sequence (surrogate pairs, currently rare): + // 11101110wwwwzzzzyy + 110111yyyyxxxxxx + // = 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx + // (uuuuu = wwww + 1) + // FIXME accept the surrogate code points (only) + + } else + goto fail; + } else + uchar = c; + put_unaligned (cpu_to_le16 (uchar), cp++); + count++; + len--; + } + return count; +fail: + return -1; +} + + +/** + * usb_gadget_get_string - fill out a string descriptor + * @table: of c strings encoded using UTF-8 + * @id: string id, from low byte of wValue in get string descriptor + * @buf: at least 256 bytes + * + * Finds the UTF-8 string matching the ID, and converts it into a + * string descriptor in utf16-le. + * Returns length of descriptor (always even) or negative errno + * + * If your driver needs stings in multiple languages, you'll probably + * "switch (wIndex) { ... }" in your ep0 string descriptor logic, + * using this routine after choosing which set of UTF-8 strings to use. + * Note that US-ASCII is a strict subset of UTF-8; any string bytes with + * the eighth bit set will be multibyte UTF-8 characters, not ISO-8859/1 + * characters (which are also widely used in C strings). + */ +int +usb_gadget_get_string (struct usb_gadget_strings *table, int id, u8 *buf) +{ + struct usb_string *s; + int len; + + /* descriptor 0 has the language id */ + if (id == 0) { + buf [0] = 4; + buf [1] = USB_DT_STRING; + buf [2] = (u8) table->language; + buf [3] = (u8) (table->language >> 8); + return 4; + } + for (s = table->strings; s && s->s; s++) + if (s->id == id) + break; + + /* unrecognized: stall. */ + if (!s || !s->s) + return -EINVAL; + + /* string descriptors have length, tag, then UTF16-LE text */ + len = min ((size_t) 126, strlen (s->s)); + memset (buf + 2, 0, 2 * len); /* zero all the bytes */ + len = utf8_to_utf16le(s->s, (u16 *)&buf[2], len); + if (len < 0) + return -EINVAL; + buf [0] = (len + 1) * 2; + buf [1] = USB_DT_STRING; + return buf [0]; +} + + +/*-------------------------------------------------------------------------*/ +/*-------------------------------------------------------------------------*/ + + +/** + * usb_descriptor_fillbuf - fill buffer with descriptors + * @buf: Buffer to be filled + * @buflen: Size of buf + * @src: Array of descriptor pointers, terminated by null pointer. + * + * Copies descriptors into the buffer, returning the length or a + * negative error code if they can't all be copied. Useful when + * assembling descriptors for an associated set of interfaces used + * as part of configuring a composite device; or in other cases where + * sets of descriptors need to be marshaled. + */ +int +usb_descriptor_fillbuf(void *buf, unsigned buflen, + const struct usb_descriptor_header **src) +{ + u8 *dest = buf; + + if (!src) + return -EINVAL; + + /* fill buffer from src[] until null descriptor ptr */ + for (; 0 != *src; src++) { + unsigned len = (*src)->bLength; + + if (len > buflen) + return -EINVAL; + memcpy(dest, *src, len); + buflen -= len; + dest += len; + } + return dest - (u8 *)buf; +} + + +/** + * usb_gadget_config_buf - builts a complete configuration descriptor + * @config: Header for the descriptor, including characteristics such + * as power requirements and number of interfaces. + * @desc: Null-terminated vector of pointers to the descriptors (interface, + * endpoint, etc) defining all functions in this device configuration. + * @buf: Buffer for the resulting configuration descriptor. + * @length: Length of buffer. If this is not big enough to hold the + * entire configuration descriptor, an error code will be returned. + * + * This copies descriptors into the response buffer, building a descriptor + * for that configuration. It returns the buffer length or a negative + * status code. The config.wTotalLength field is set to match the length + * of the result, but other descriptor fields (including power usage and + * interface count) must be set by the caller. + * + * Gadget drivers could use this when constructing a config descriptor + * in response to USB_REQ_GET_DESCRIPTOR. They will need to patch the + * resulting bDescriptorType value if USB_DT_OTHER_SPEED_CONFIG is needed. + */ +int usb_gadget_config_buf( + const struct usb_config_descriptor *config, + void *buf, + unsigned length, + const struct usb_descriptor_header **desc +) +{ + struct usb_config_descriptor *cp = buf; + int len; + + /* config descriptor first */ + if (length < USB_DT_CONFIG_SIZE || !desc) + return -EINVAL; + *cp = *config; + + /* then interface/endpoint/class/vendor/... */ + len = usb_descriptor_fillbuf(USB_DT_CONFIG_SIZE + (u8*)buf, + length - USB_DT_CONFIG_SIZE, desc); + if (len < 0) + return len; + len += USB_DT_CONFIG_SIZE; + if (len > 0xffff) + return -EINVAL; + + /* patch up the config descriptor */ + cp->bLength = USB_DT_CONFIG_SIZE; + cp->bDescriptorType = USB_DT_CONFIG; + cp->wTotalLength = cpu_to_le16(len); + cp->bmAttributes |= USB_CONFIG_ATT_ONE; + return len; +} + +/*-------------------------------------------------------------------------*/ +/*-------------------------------------------------------------------------*/ + + +#define RBUF_LEN (1024*1024) +static int rbuf_start; +static int rbuf_len; +static __u8 rbuf[RBUF_LEN]; + +/*-------------------------------------------------------------------------*/ + +#define DRIVER_VERSION "St Patrick's Day 2004" + +static const char shortname [] = "zero"; +static const char longname [] = "YAMAHA YST-MS35D USB Speaker "; + +static const char source_sink [] = "source and sink data"; +static const char loopback [] = "loop input to output"; + +/*-------------------------------------------------------------------------*/ + +/* + * driver assumes self-powered hardware, and + * has no way for users to trigger remote wakeup. + * + * this version autoconfigures as much as possible, + * which is reasonable for most "bulk-only" drivers. + */ +static const char *EP_IN_NAME; /* source */ +static const char *EP_OUT_NAME; /* sink */ + +/*-------------------------------------------------------------------------*/ + +/* big enough to hold our biggest descriptor */ +#define USB_BUFSIZ 512 + +struct zero_dev { + spinlock_t lock; + struct usb_gadget *gadget; + struct usb_request *req; /* for control responses */ + + /* when configured, we have one of two configs: + * - source data (in to host) and sink it (out from host) + * - or loop it back (out from host back in to host) + */ + u8 config; + struct usb_ep *in_ep, *out_ep; + + /* autoresume timer */ + struct timer_list resume; +}; + +#define xprintk(d,level,fmt,args...) \ + dev_printk(level , &(d)->gadget->dev , fmt , ## args) + +#ifdef DEBUG +#define DBG(dev,fmt,args...) \ + xprintk(dev , KERN_DEBUG , fmt , ## args) +#else +#define DBG(dev,fmt,args...) \ + do { } while (0) +#endif /* DEBUG */ + +#ifdef VERBOSE +#define VDBG DBG +#else +#define VDBG(dev,fmt,args...) \ + do { } while (0) +#endif /* VERBOSE */ + +#define ERROR(dev,fmt,args...) \ + xprintk(dev , KERN_ERR , fmt , ## args) +#define WARN(dev,fmt,args...) \ + xprintk(dev , KERN_WARNING , fmt , ## args) +#define INFO(dev,fmt,args...) \ + xprintk(dev , KERN_INFO , fmt , ## args) + +/*-------------------------------------------------------------------------*/ + +static unsigned buflen = 4096; +static unsigned qlen = 32; +static unsigned pattern = 0; + +module_param (buflen, uint, S_IRUGO|S_IWUSR); +module_param (qlen, uint, S_IRUGO|S_IWUSR); +module_param (pattern, uint, S_IRUGO|S_IWUSR); + +/* + * if it's nonzero, autoresume says how many seconds to wait + * before trying to wake up the host after suspend. + */ +static unsigned autoresume = 0; +module_param (autoresume, uint, 0); + +/* + * Normally the "loopback" configuration is second (index 1) so + * it's not the default. Here's where to change that order, to + * work better with hosts where config changes are problematic. + * Or controllers (like superh) that only support one config. + */ +static int loopdefault = 0; + +module_param (loopdefault, bool, S_IRUGO|S_IWUSR); + +/*-------------------------------------------------------------------------*/ + +/* Thanks to NetChip Technologies for donating this product ID. + * + * DO NOT REUSE THESE IDs with a protocol-incompatible driver!! Ever!! + * Instead: allocate your own, using normal USB-IF procedures. + */ +#ifndef CONFIG_USB_ZERO_HNPTEST +#define DRIVER_VENDOR_NUM 0x0525 /* NetChip */ +#define DRIVER_PRODUCT_NUM 0xa4a0 /* Linux-USB "Gadget Zero" */ +#else +#define DRIVER_VENDOR_NUM 0x1a0a /* OTG test device IDs */ +#define DRIVER_PRODUCT_NUM 0xbadd +#endif + +/*-------------------------------------------------------------------------*/ + +/* + * DESCRIPTORS ... most are static, but strings and (full) + * configuration descriptors are built on demand. + */ + +/* +#define STRING_MANUFACTURER 25 +#define STRING_PRODUCT 42 +#define STRING_SERIAL 101 +*/ +#define STRING_MANUFACTURER 1 +#define STRING_PRODUCT 2 +#define STRING_SERIAL 3 + +#define STRING_SOURCE_SINK 250 +#define STRING_LOOPBACK 251 + +/* + * This device advertises two configurations; these numbers work + * on a pxa250 as well as more flexible hardware. + */ +#define CONFIG_SOURCE_SINK 3 +#define CONFIG_LOOPBACK 2 + +/* +static struct usb_device_descriptor +device_desc = { + .bLength = sizeof device_desc, + .bDescriptorType = USB_DT_DEVICE, + + .bcdUSB = __constant_cpu_to_le16 (0x0200), + .bDeviceClass = USB_CLASS_VENDOR_SPEC, + + .idVendor = __constant_cpu_to_le16 (DRIVER_VENDOR_NUM), + .idProduct = __constant_cpu_to_le16 (DRIVER_PRODUCT_NUM), + .iManufacturer = STRING_MANUFACTURER, + .iProduct = STRING_PRODUCT, + .iSerialNumber = STRING_SERIAL, + .bNumConfigurations = 2, +}; +*/ +static struct usb_device_descriptor +device_desc = { + .bLength = sizeof device_desc, + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = __constant_cpu_to_le16 (0x0100), + .bDeviceClass = USB_CLASS_PER_INTERFACE, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + .bMaxPacketSize0 = 64, + .bcdDevice = __constant_cpu_to_le16 (0x0100), + .idVendor = __constant_cpu_to_le16 (0x0499), + .idProduct = __constant_cpu_to_le16 (0x3002), + .iManufacturer = STRING_MANUFACTURER, + .iProduct = STRING_PRODUCT, + .iSerialNumber = STRING_SERIAL, + .bNumConfigurations = 1, +}; + +static struct usb_config_descriptor +z_config = { + .bLength = sizeof z_config, + .bDescriptorType = USB_DT_CONFIG, + + /* compute wTotalLength on the fly */ + .bNumInterfaces = 2, + .bConfigurationValue = 1, + .iConfiguration = 0, + .bmAttributes = 0x40, + .bMaxPower = 0, /* self-powered */ +}; + + +static struct usb_otg_descriptor +otg_descriptor = { + .bLength = sizeof otg_descriptor, + .bDescriptorType = USB_DT_OTG, + + .bmAttributes = USB_OTG_SRP, +}; + +/* one interface in each configuration */ +#ifdef CONFIG_USB_GADGET_DUALSPEED + +/* + * usb 2.0 devices need to expose both high speed and full speed + * descriptors, unless they only run at full speed. + * + * that means alternate endpoint descriptors (bigger packets) + * and a "device qualifier" ... plus more construction options + * for the config descriptor. + */ + +static struct usb_qualifier_descriptor +dev_qualifier = { + .bLength = sizeof dev_qualifier, + .bDescriptorType = USB_DT_DEVICE_QUALIFIER, + + .bcdUSB = __constant_cpu_to_le16 (0x0200), + .bDeviceClass = USB_CLASS_VENDOR_SPEC, + + .bNumConfigurations = 2, +}; + + +struct usb_cs_as_general_descriptor { + __u8 bLength; + __u8 bDescriptorType; + + __u8 bDescriptorSubType; + __u8 bTerminalLink; + __u8 bDelay; + __u16 wFormatTag; +} __attribute__ ((packed)); + +struct usb_cs_as_format_descriptor { + __u8 bLength; + __u8 bDescriptorType; + + __u8 bDescriptorSubType; + __u8 bFormatType; + __u8 bNrChannels; + __u8 bSubframeSize; + __u8 bBitResolution; + __u8 bSamfreqType; + __u8 tLowerSamFreq[3]; + __u8 tUpperSamFreq[3]; +} __attribute__ ((packed)); + +static const struct usb_interface_descriptor +z_audio_control_if_desc = { + .bLength = sizeof z_audio_control_if_desc, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bAlternateSetting = 0, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = 0x1, + .bInterfaceProtocol = 0, + .iInterface = 0, +}; + +static const struct usb_interface_descriptor +z_audio_if_desc = { + .bLength = sizeof z_audio_if_desc, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 1, + .bAlternateSetting = 0, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = 0x2, + .bInterfaceProtocol = 0, + .iInterface = 0, +}; + +static const struct usb_interface_descriptor +z_audio_if_desc2 = { + .bLength = sizeof z_audio_if_desc, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 1, + .bAlternateSetting = 1, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = 0x2, + .bInterfaceProtocol = 0, + .iInterface = 0, +}; + +static const struct usb_cs_as_general_descriptor +z_audio_cs_as_if_desc = { + .bLength = 7, + .bDescriptorType = 0x24, + + .bDescriptorSubType = 0x01, + .bTerminalLink = 0x01, + .bDelay = 0x0, + .wFormatTag = __constant_cpu_to_le16 (0x0001) +}; + + +static const struct usb_cs_as_format_descriptor +z_audio_cs_as_format_desc = { + .bLength = 0xe, + .bDescriptorType = 0x24, + + .bDescriptorSubType = 2, + .bFormatType = 1, + .bNrChannels = 1, + .bSubframeSize = 1, + .bBitResolution = 8, + .bSamfreqType = 0, + .tLowerSamFreq = {0x7e, 0x13, 0x00}, + .tUpperSamFreq = {0xe2, 0xd6, 0x00}, +}; + +static const struct usb_endpoint_descriptor +z_iso_ep = { + .bLength = 0x09, + .bDescriptorType = 0x05, + .bEndpointAddress = 0x04, + .bmAttributes = 0x09, + .wMaxPacketSize = 0x0038, + .bInterval = 0x01, + .bRefresh = 0x00, + .bSynchAddress = 0x00, +}; + +static char z_iso_ep2[] = {0x07, 0x25, 0x01, 0x00, 0x02, 0x00, 0x02}; + +// 9 bytes +static char z_ac_interface_header_desc[] = +{ 0x09, 0x24, 0x01, 0x00, 0x01, 0x2b, 0x00, 0x01, 0x01 }; + +// 12 bytes +static char z_0[] = {0x0c, 0x24, 0x02, 0x01, 0x01, 0x01, 0x00, 0x02, + 0x03, 0x00, 0x00, 0x00}; +// 13 bytes +static char z_1[] = {0x0d, 0x24, 0x06, 0x02, 0x01, 0x02, 0x15, 0x00, + 0x02, 0x00, 0x02, 0x00, 0x00}; +// 9 bytes +static char z_2[] = {0x09, 0x24, 0x03, 0x03, 0x01, 0x03, 0x00, 0x02, + 0x00}; + +static char za_0[] = {0x09, 0x04, 0x01, 0x02, 0x01, 0x01, 0x02, 0x00, + 0x00}; + +static char za_1[] = {0x07, 0x24, 0x01, 0x01, 0x00, 0x01, 0x00}; + +static char za_2[] = {0x0e, 0x24, 0x02, 0x01, 0x02, 0x01, 0x08, 0x00, + 0x7e, 0x13, 0x00, 0xe2, 0xd6, 0x00}; + +static char za_3[] = {0x09, 0x05, 0x04, 0x09, 0x70, 0x00, 0x01, 0x00, + 0x00}; + +static char za_4[] = {0x07, 0x25, 0x01, 0x00, 0x02, 0x00, 0x02}; + +static char za_5[] = {0x09, 0x04, 0x01, 0x03, 0x01, 0x01, 0x02, 0x00, + 0x00}; + +static char za_6[] = {0x07, 0x24, 0x01, 0x01, 0x00, 0x01, 0x00}; + +static char za_7[] = {0x0e, 0x24, 0x02, 0x01, 0x01, 0x02, 0x10, 0x00, + 0x7e, 0x13, 0x00, 0xe2, 0xd6, 0x00}; + +static char za_8[] = {0x09, 0x05, 0x04, 0x09, 0x70, 0x00, 0x01, 0x00, + 0x00}; + +static char za_9[] = {0x07, 0x25, 0x01, 0x00, 0x02, 0x00, 0x02}; + +static char za_10[] = {0x09, 0x04, 0x01, 0x04, 0x01, 0x01, 0x02, 0x00, + 0x00}; + +static char za_11[] = {0x07, 0x24, 0x01, 0x01, 0x00, 0x01, 0x00}; + +static char za_12[] = {0x0e, 0x24, 0x02, 0x01, 0x02, 0x02, 0x10, 0x00, + 0x73, 0x13, 0x00, 0xe2, 0xd6, 0x00}; + +static char za_13[] = {0x09, 0x05, 0x04, 0x09, 0xe0, 0x00, 0x01, 0x00, + 0x00}; + +static char za_14[] = {0x07, 0x25, 0x01, 0x00, 0x02, 0x00, 0x02}; + +static char za_15[] = {0x09, 0x04, 0x01, 0x05, 0x01, 0x01, 0x02, 0x00, + 0x00}; + +static char za_16[] = {0x07, 0x24, 0x01, 0x01, 0x00, 0x01, 0x00}; + +static char za_17[] = {0x0e, 0x24, 0x02, 0x01, 0x01, 0x03, 0x14, 0x00, + 0x7e, 0x13, 0x00, 0xe2, 0xd6, 0x00}; + +static char za_18[] = {0x09, 0x05, 0x04, 0x09, 0xa8, 0x00, 0x01, 0x00, + 0x00}; + +static char za_19[] = {0x07, 0x25, 0x01, 0x00, 0x02, 0x00, 0x02}; + +static char za_20[] = {0x09, 0x04, 0x01, 0x06, 0x01, 0x01, 0x02, 0x00, + 0x00}; + +static char za_21[] = {0x07, 0x24, 0x01, 0x01, 0x00, 0x01, 0x00}; + +static char za_22[] = {0x0e, 0x24, 0x02, 0x01, 0x02, 0x03, 0x14, 0x00, + 0x7e, 0x13, 0x00, 0xe2, 0xd6, 0x00}; + +static char za_23[] = {0x09, 0x05, 0x04, 0x09, 0x50, 0x01, 0x01, 0x00, + 0x00}; + +static char za_24[] = {0x07, 0x25, 0x01, 0x00, 0x02, 0x00, 0x02}; + + + +static const struct usb_descriptor_header *z_function [] = { + (struct usb_descriptor_header *) &z_audio_control_if_desc, + (struct usb_descriptor_header *) &z_ac_interface_header_desc, + (struct usb_descriptor_header *) &z_0, + (struct usb_descriptor_header *) &z_1, + (struct usb_descriptor_header *) &z_2, + (struct usb_descriptor_header *) &z_audio_if_desc, + (struct usb_descriptor_header *) &z_audio_if_desc2, + (struct usb_descriptor_header *) &z_audio_cs_as_if_desc, + (struct usb_descriptor_header *) &z_audio_cs_as_format_desc, + (struct usb_descriptor_header *) &z_iso_ep, + (struct usb_descriptor_header *) &z_iso_ep2, + (struct usb_descriptor_header *) &za_0, + (struct usb_descriptor_header *) &za_1, + (struct usb_descriptor_header *) &za_2, + (struct usb_descriptor_header *) &za_3, + (struct usb_descriptor_header *) &za_4, + (struct usb_descriptor_header *) &za_5, + (struct usb_descriptor_header *) &za_6, + (struct usb_descriptor_header *) &za_7, + (struct usb_descriptor_header *) &za_8, + (struct usb_descriptor_header *) &za_9, + (struct usb_descriptor_header *) &za_10, + (struct usb_descriptor_header *) &za_11, + (struct usb_descriptor_header *) &za_12, + (struct usb_descriptor_header *) &za_13, + (struct usb_descriptor_header *) &za_14, + (struct usb_descriptor_header *) &za_15, + (struct usb_descriptor_header *) &za_16, + (struct usb_descriptor_header *) &za_17, + (struct usb_descriptor_header *) &za_18, + (struct usb_descriptor_header *) &za_19, + (struct usb_descriptor_header *) &za_20, + (struct usb_descriptor_header *) &za_21, + (struct usb_descriptor_header *) &za_22, + (struct usb_descriptor_header *) &za_23, + (struct usb_descriptor_header *) &za_24, + NULL, +}; + +/* maxpacket and other transfer characteristics vary by speed. */ +#define ep_desc(g,hs,fs) (((g)->speed==USB_SPEED_HIGH)?(hs):(fs)) + +#else + +/* if there's no high speed support, maxpacket doesn't change. */ +#define ep_desc(g,hs,fs) fs + +#endif /* !CONFIG_USB_GADGET_DUALSPEED */ + +static char manufacturer [40]; +//static char serial [40]; +static char serial [] = "Ser 00 em"; + +/* static strings, in UTF-8 */ +static struct usb_string strings [] = { + { STRING_MANUFACTURER, manufacturer, }, + { STRING_PRODUCT, longname, }, + { STRING_SERIAL, serial, }, + { STRING_LOOPBACK, loopback, }, + { STRING_SOURCE_SINK, source_sink, }, + { } /* end of list */ +}; + +static struct usb_gadget_strings stringtab = { + .language = 0x0409, /* en-us */ + .strings = strings, +}; + +/* + * config descriptors are also handcrafted. these must agree with code + * that sets configurations, and with code managing interfaces and their + * altsettings. other complexity may come from: + * + * - high speed support, including "other speed config" rules + * - multiple configurations + * - interfaces with alternate settings + * - embedded class or vendor-specific descriptors + * + * this handles high speed, and has a second config that could as easily + * have been an alternate interface setting (on most hardware). + * + * NOTE: to demonstrate (and test) more USB capabilities, this driver + * should include an altsetting to test interrupt transfers, including + * high bandwidth modes at high speed. (Maybe work like Intel's test + * device?) + */ +static int +config_buf (struct usb_gadget *gadget, u8 *buf, u8 type, unsigned index) +{ + int len; + const struct usb_descriptor_header **function; + + function = z_function; + len = usb_gadget_config_buf (&z_config, buf, USB_BUFSIZ, function); + if (len < 0) + return len; + ((struct usb_config_descriptor *) buf)->bDescriptorType = type; + return len; +} + +/*-------------------------------------------------------------------------*/ + +static struct usb_request * +alloc_ep_req (struct usb_ep *ep, unsigned length) +{ + struct usb_request *req; + + req = usb_ep_alloc_request (ep, GFP_ATOMIC); + if (req) { + req->length = length; + req->buf = usb_ep_alloc_buffer (ep, length, + &req->dma, GFP_ATOMIC); + if (!req->buf) { + usb_ep_free_request (ep, req); + req = NULL; + } + } + return req; +} + +static void free_ep_req (struct usb_ep *ep, struct usb_request *req) +{ + if (req->buf) + usb_ep_free_buffer (ep, req->buf, req->dma, req->length); + usb_ep_free_request (ep, req); +} + +/*-------------------------------------------------------------------------*/ + +/* optionally require specific source/sink data patterns */ + +static int +check_read_data ( + struct zero_dev *dev, + struct usb_ep *ep, + struct usb_request *req +) +{ + unsigned i; + u8 *buf = req->buf; + + for (i = 0; i < req->actual; i++, buf++) { + switch (pattern) { + /* all-zeroes has no synchronization issues */ + case 0: + if (*buf == 0) + continue; + break; + /* mod63 stays in sync with short-terminated transfers, + * or otherwise when host and gadget agree on how large + * each usb transfer request should be. resync is done + * with set_interface or set_config. + */ + case 1: + if (*buf == (u8)(i % 63)) + continue; + break; + } + ERROR (dev, "bad OUT byte, buf [%d] = %d\n", i, *buf); + usb_ep_set_halt (ep); + return -EINVAL; + } + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static void zero_reset_config (struct zero_dev *dev) +{ + if (dev->config == 0) + return; + + DBG (dev, "reset config\n"); + + /* just disable endpoints, forcing completion of pending i/o. + * all our completion handlers free their requests in this case. + */ + if (dev->in_ep) { + usb_ep_disable (dev->in_ep); + dev->in_ep = NULL; + } + if (dev->out_ep) { + usb_ep_disable (dev->out_ep); + dev->out_ep = NULL; + } + dev->config = 0; + del_timer (&dev->resume); +} + +#define _write(f, buf, sz) (f->f_op->write(f, buf, sz, &f->f_pos)) + +static void +zero_isoc_complete (struct usb_ep *ep, struct usb_request *req) +{ + struct zero_dev *dev = ep->driver_data; + int status = req->status; + int i, j; + + switch (status) { + + case 0: /* normal completion? */ + //printk ("\nzero ---------------> isoc normal completion %d bytes\n", req->actual); + for (i=0, j=rbuf_start; i<req->actual; i++) { + //printk ("%02x ", ((__u8*)req->buf)[i]); + rbuf[j] = ((__u8*)req->buf)[i]; + j++; + if (j >= RBUF_LEN) j=0; + } + rbuf_start = j; + //printk ("\n\n"); + + if (rbuf_len < RBUF_LEN) { + rbuf_len += req->actual; + if (rbuf_len > RBUF_LEN) { + rbuf_len = RBUF_LEN; + } + } + + break; + + /* this endpoint is normally active while we're configured */ + case -ECONNABORTED: /* hardware forced ep reset */ + case -ECONNRESET: /* request dequeued */ + case -ESHUTDOWN: /* disconnect from host */ + VDBG (dev, "%s gone (%d), %d/%d\n", ep->name, status, + req->actual, req->length); + if (ep == dev->out_ep) + check_read_data (dev, ep, req); + free_ep_req (ep, req); + return; + + case -EOVERFLOW: /* buffer overrun on read means that + * we didn't provide a big enough + * buffer. + */ + default: +#if 1 + DBG (dev, "%s complete --> %d, %d/%d\n", ep->name, + status, req->actual, req->length); +#endif + case -EREMOTEIO: /* short read */ + break; + } + + status = usb_ep_queue (ep, req, GFP_ATOMIC); + if (status) { + ERROR (dev, "kill %s: resubmit %d bytes --> %d\n", + ep->name, req->length, status); + usb_ep_set_halt (ep); + /* FIXME recover later ... somehow */ + } +} + +static struct usb_request * +zero_start_isoc_ep (struct usb_ep *ep, int gfp_flags) +{ + struct usb_request *req; + int status; + + req = alloc_ep_req (ep, 512); + if (!req) + return NULL; + + req->complete = zero_isoc_complete; + + status = usb_ep_queue (ep, req, gfp_flags); + if (status) { + struct zero_dev *dev = ep->driver_data; + + ERROR (dev, "start %s --> %d\n", ep->name, status); + free_ep_req (ep, req); + req = NULL; + } + + return req; +} + +/* change our operational config. this code must agree with the code + * that returns config descriptors, and altsetting code. + * + * it's also responsible for power management interactions. some + * configurations might not work with our current power sources. + * + * note that some device controller hardware will constrain what this + * code can do, perhaps by disallowing more than one configuration or + * by limiting configuration choices (like the pxa2xx). + */ +static int +zero_set_config (struct zero_dev *dev, unsigned number, int gfp_flags) +{ + int result = 0; + struct usb_gadget *gadget = dev->gadget; + const struct usb_endpoint_descriptor *d; + struct usb_ep *ep; + + if (number == dev->config) + return 0; + + zero_reset_config (dev); + + gadget_for_each_ep (ep, gadget) { + + if (strcmp (ep->name, "ep4") == 0) { + + d = (struct usb_endpoint_descripter *)&za_23; // isoc ep desc for audio i/f alt setting 6 + result = usb_ep_enable (ep, d); + + if (result == 0) { + ep->driver_data = dev; + dev->in_ep = ep; + + if (zero_start_isoc_ep (ep, gfp_flags) != 0) { + + dev->in_ep = ep; + continue; + } + + usb_ep_disable (ep); + result = -EIO; + } + } + + } + + dev->config = number; + return result; +} + +/*-------------------------------------------------------------------------*/ + +static void zero_setup_complete (struct usb_ep *ep, struct usb_request *req) +{ + if (req->status || req->actual != req->length) + DBG ((struct zero_dev *) ep->driver_data, + "setup complete --> %d, %d/%d\n", + req->status, req->actual, req->length); +} + +/* + * The setup() callback implements all the ep0 functionality that's + * not handled lower down, in hardware or the hardware driver (like + * device and endpoint feature flags, and their status). It's all + * housekeeping for the gadget function we're implementing. Most of + * the work is in config-specific setup. + */ +static int +zero_setup (struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) +{ + struct zero_dev *dev = get_gadget_data (gadget); + struct usb_request *req = dev->req; + int value = -EOPNOTSUPP; + + /* usually this stores reply data in the pre-allocated ep0 buffer, + * but config change events will reconfigure hardware. + */ + req->zero = 0; + switch (ctrl->bRequest) { + + case USB_REQ_GET_DESCRIPTOR: + + switch (ctrl->wValue >> 8) { + + case USB_DT_DEVICE: + value = min (ctrl->wLength, (u16) sizeof device_desc); + memcpy (req->buf, &device_desc, value); + break; +#ifdef CONFIG_USB_GADGET_DUALSPEED + case USB_DT_DEVICE_QUALIFIER: + if (!gadget->is_dualspeed) + break; + value = min (ctrl->wLength, (u16) sizeof dev_qualifier); + memcpy (req->buf, &dev_qualifier, value); + break; + + case USB_DT_OTHER_SPEED_CONFIG: + if (!gadget->is_dualspeed) + break; + // FALLTHROUGH +#endif /* CONFIG_USB_GADGET_DUALSPEED */ + case USB_DT_CONFIG: + value = config_buf (gadget, req->buf, + ctrl->wValue >> 8, + ctrl->wValue & 0xff); + if (value >= 0) + value = min (ctrl->wLength, (u16) value); + break; + + case USB_DT_STRING: + /* wIndex == language code. + * this driver only handles one language, you can + * add string tables for other languages, using + * any UTF-8 characters + */ + value = usb_gadget_get_string (&stringtab, + ctrl->wValue & 0xff, req->buf); + if (value >= 0) { + value = min (ctrl->wLength, (u16) value); + } + break; + } + break; + + /* currently two configs, two speeds */ + case USB_REQ_SET_CONFIGURATION: + if (ctrl->bRequestType != 0) + goto unknown; + + spin_lock (&dev->lock); + value = zero_set_config (dev, ctrl->wValue, GFP_ATOMIC); + spin_unlock (&dev->lock); + break; + case USB_REQ_GET_CONFIGURATION: + if (ctrl->bRequestType != USB_DIR_IN) + goto unknown; + *(u8 *)req->buf = dev->config; + value = min (ctrl->wLength, (u16) 1); + break; + + /* until we add altsetting support, or other interfaces, + * only 0/0 are possible. pxa2xx only supports 0/0 (poorly) + * and already killed pending endpoint I/O. + */ + case USB_REQ_SET_INTERFACE: + + if (ctrl->bRequestType != USB_RECIP_INTERFACE) + goto unknown; + spin_lock (&dev->lock); + if (dev->config) { + u8 config = dev->config; + + /* resets interface configuration, forgets about + * previous transaction state (queued bufs, etc) + * and re-inits endpoint state (toggle etc) + * no response queued, just zero status == success. + * if we had more than one interface we couldn't + * use this "reset the config" shortcut. + */ + zero_reset_config (dev); + zero_set_config (dev, config, GFP_ATOMIC); + value = 0; + } + spin_unlock (&dev->lock); + break; + case USB_REQ_GET_INTERFACE: + if ((ctrl->bRequestType == 0x21) && (ctrl->wIndex == 0x02)) { + value = ctrl->wLength; + break; + } + else { + if (ctrl->bRequestType != (USB_DIR_IN|USB_RECIP_INTERFACE)) + goto unknown; + if (!dev->config) + break; + if (ctrl->wIndex != 0) { + value = -EDOM; + break; + } + *(u8 *)req->buf = 0; + value = min (ctrl->wLength, (u16) 1); + } + break; + + /* + * These are the same vendor-specific requests supported by + * Intel's USB 2.0 compliance test devices. We exceed that + * device spec by allowing multiple-packet requests. + */ + case 0x5b: /* control WRITE test -- fill the buffer */ + if (ctrl->bRequestType != (USB_DIR_OUT|USB_TYPE_VENDOR)) + goto unknown; + if (ctrl->wValue || ctrl->wIndex) + break; + /* just read that many bytes into the buffer */ + if (ctrl->wLength > USB_BUFSIZ) + break; + value = ctrl->wLength; + break; + case 0x5c: /* control READ test -- return the buffer */ + if (ctrl->bRequestType != (USB_DIR_IN|USB_TYPE_VENDOR)) + goto unknown; + if (ctrl->wValue || ctrl->wIndex) + break; + /* expect those bytes are still in the buffer; send back */ + if (ctrl->wLength > USB_BUFSIZ + || ctrl->wLength != req->length) + break; + value = ctrl->wLength; + break; + + case 0x01: // SET_CUR + case 0x02: + case 0x03: + case 0x04: + case 0x05: + value = ctrl->wLength; + break; + case 0x81: + switch (ctrl->wValue) { + case 0x0201: + case 0x0202: + ((u8*)req->buf)[0] = 0x00; + ((u8*)req->buf)[1] = 0xe3; + break; + case 0x0300: + case 0x0500: + ((u8*)req->buf)[0] = 0x00; + break; + } + //((u8*)req->buf)[0] = 0x81; + //((u8*)req->buf)[1] = 0x81; + value = ctrl->wLength; + break; + case 0x82: + switch (ctrl->wValue) { + case 0x0201: + case 0x0202: + ((u8*)req->buf)[0] = 0x00; + ((u8*)req->buf)[1] = 0xc3; + break; + case 0x0300: + case 0x0500: + ((u8*)req->buf)[0] = 0x00; + break; + } + //((u8*)req->buf)[0] = 0x82; + //((u8*)req->buf)[1] = 0x82; + value = ctrl->wLength; + break; + case 0x83: + switch (ctrl->wValue) { + case 0x0201: + case 0x0202: + ((u8*)req->buf)[0] = 0x00; + ((u8*)req->buf)[1] = 0x00; + break; + case 0x0300: + ((u8*)req->buf)[0] = 0x60; + break; + case 0x0500: + ((u8*)req->buf)[0] = 0x18; + break; + } + //((u8*)req->buf)[0] = 0x83; + //((u8*)req->buf)[1] = 0x83; + value = ctrl->wLength; + break; + case 0x84: + switch (ctrl->wValue) { + case 0x0201: + case 0x0202: + ((u8*)req->buf)[0] = 0x00; + ((u8*)req->buf)[1] = 0x01; + break; + case 0x0300: + case 0x0500: + ((u8*)req->buf)[0] = 0x08; + break; + } + //((u8*)req->buf)[0] = 0x84; + //((u8*)req->buf)[1] = 0x84; + value = ctrl->wLength; + break; + case 0x85: + ((u8*)req->buf)[0] = 0x85; + ((u8*)req->buf)[1] = 0x85; + value = ctrl->wLength; + break; + + + default: +unknown: + printk("unknown control req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + ctrl->wValue, ctrl->wIndex, ctrl->wLength); + } + + /* respond with data transfer before status phase? */ + if (value >= 0) { + req->length = value; + req->zero = value < ctrl->wLength + && (value % gadget->ep0->maxpacket) == 0; + value = usb_ep_queue (gadget->ep0, req, GFP_ATOMIC); + if (value < 0) { + DBG (dev, "ep_queue < 0 --> %d\n", value); + req->status = 0; + zero_setup_complete (gadget->ep0, req); + } + } + + /* device either stalls (value < 0) or reports success */ + return value; +} + +static void +zero_disconnect (struct usb_gadget *gadget) +{ + struct zero_dev *dev = get_gadget_data (gadget); + unsigned long flags; + + spin_lock_irqsave (&dev->lock, flags); + zero_reset_config (dev); + + /* a more significant application might have some non-usb + * activities to quiesce here, saving resources like power + * or pushing the notification up a network stack. + */ + spin_unlock_irqrestore (&dev->lock, flags); + + /* next we may get setup() calls to enumerate new connections; + * or an unbind() during shutdown (including removing module). + */ +} + +static void +zero_autoresume (unsigned long _dev) +{ + struct zero_dev *dev = (struct zero_dev *) _dev; + int status; + + /* normally the host would be woken up for something + * more significant than just a timer firing... + */ + if (dev->gadget->speed != USB_SPEED_UNKNOWN) { + status = usb_gadget_wakeup (dev->gadget); + DBG (dev, "wakeup --> %d\n", status); + } +} + +/*-------------------------------------------------------------------------*/ + +static void +zero_unbind (struct usb_gadget *gadget) +{ + struct zero_dev *dev = get_gadget_data (gadget); + + DBG (dev, "unbind\n"); + + /* we've already been disconnected ... no i/o is active */ + if (dev->req) + free_ep_req (gadget->ep0, dev->req); + del_timer_sync (&dev->resume); + kfree (dev); + set_gadget_data (gadget, NULL); +} + +static int +zero_bind (struct usb_gadget *gadget) +{ + struct zero_dev *dev; + //struct usb_ep *ep; + + printk("binding\n"); + /* + * DRIVER POLICY CHOICE: you may want to do this differently. + * One thing to avoid is reusing a bcdDevice revision code + * with different host-visible configurations or behavior + * restrictions -- using ep1in/ep2out vs ep1out/ep3in, etc + */ + //device_desc.bcdDevice = __constant_cpu_to_le16 (0x0201); + + + /* ok, we made sense of the hardware ... */ + dev = kzalloc (sizeof *dev, SLAB_KERNEL); + if (!dev) + return -ENOMEM; + spin_lock_init (&dev->lock); + dev->gadget = gadget; + set_gadget_data (gadget, dev); + + /* preallocate control response and buffer */ + dev->req = usb_ep_alloc_request (gadget->ep0, GFP_KERNEL); + if (!dev->req) + goto enomem; + dev->req->buf = usb_ep_alloc_buffer (gadget->ep0, USB_BUFSIZ, + &dev->req->dma, GFP_KERNEL); + if (!dev->req->buf) + goto enomem; + + dev->req->complete = zero_setup_complete; + + device_desc.bMaxPacketSize0 = gadget->ep0->maxpacket; + +#ifdef CONFIG_USB_GADGET_DUALSPEED + /* assume ep0 uses the same value for both speeds ... */ + dev_qualifier.bMaxPacketSize0 = device_desc.bMaxPacketSize0; + + /* and that all endpoints are dual-speed */ + //hs_source_desc.bEndpointAddress = fs_source_desc.bEndpointAddress; + //hs_sink_desc.bEndpointAddress = fs_sink_desc.bEndpointAddress; +#endif + + usb_gadget_set_selfpowered (gadget); + + init_timer (&dev->resume); + dev->resume.function = zero_autoresume; + dev->resume.data = (unsigned long) dev; + + gadget->ep0->driver_data = dev; + + INFO (dev, "%s, version: " DRIVER_VERSION "\n", longname); + INFO (dev, "using %s, OUT %s IN %s\n", gadget->name, + EP_OUT_NAME, EP_IN_NAME); + + snprintf (manufacturer, sizeof manufacturer, + UTS_SYSNAME " " UTS_RELEASE " with %s", + gadget->name); + + return 0; + +enomem: + zero_unbind (gadget); + return -ENOMEM; +} + +/*-------------------------------------------------------------------------*/ + +static void +zero_suspend (struct usb_gadget *gadget) +{ + struct zero_dev *dev = get_gadget_data (gadget); + + if (gadget->speed == USB_SPEED_UNKNOWN) + return; + + if (autoresume) { + mod_timer (&dev->resume, jiffies + (HZ * autoresume)); + DBG (dev, "suspend, wakeup in %d seconds\n", autoresume); + } else + DBG (dev, "suspend\n"); +} + +static void +zero_resume (struct usb_gadget *gadget) +{ + struct zero_dev *dev = get_gadget_data (gadget); + + DBG (dev, "resume\n"); + del_timer (&dev->resume); +} + + +/*-------------------------------------------------------------------------*/ + +static struct usb_gadget_driver zero_driver = { +#ifdef CONFIG_USB_GADGET_DUALSPEED + .speed = USB_SPEED_HIGH, +#else + .speed = USB_SPEED_FULL, +#endif + .function = (char *) longname, + .bind = zero_bind, + .unbind = zero_unbind, + + .setup = zero_setup, + .disconnect = zero_disconnect, + + .suspend = zero_suspend, + .resume = zero_resume, + + .driver = { + .name = (char *) shortname, + // .shutdown = ... + // .suspend = ... + // .resume = ... + }, +}; + +MODULE_AUTHOR ("David Brownell"); +MODULE_LICENSE ("Dual BSD/GPL"); + +static struct proc_dir_entry *pdir, *pfile; + +static int isoc_read_data (char *page, char **start, + off_t off, int count, + int *eof, void *data) +{ + int i; + static int c = 0; + static int done = 0; + static int s = 0; + +/* + printk ("\ncount: %d\n", count); + printk ("rbuf_start: %d\n", rbuf_start); + printk ("rbuf_len: %d\n", rbuf_len); + printk ("off: %d\n", off); + printk ("start: %p\n\n", *start); +*/ + if (done) { + c = 0; + done = 0; + *eof = 1; + return 0; + } + + if (c == 0) { + if (rbuf_len == RBUF_LEN) + s = rbuf_start; + else s = 0; + } + + for (i=0; i<count && c<rbuf_len; i++, c++) { + page[i] = rbuf[(c+s) % RBUF_LEN]; + } + *start = page; + + if (c >= rbuf_len) { + *eof = 1; + done = 1; + } + + + return i; +} + +static int __init init (void) +{ + + int retval = 0; + + pdir = proc_mkdir("isoc_test", NULL); + if(pdir == NULL) { + retval = -ENOMEM; + printk("Error creating dir\n"); + goto done; + } + pdir->owner = THIS_MODULE; + + pfile = create_proc_read_entry("isoc_data", + 0444, pdir, + isoc_read_data, + NULL); + if (pfile == NULL) { + retval = -ENOMEM; + printk("Error creating file\n"); + goto no_file; + } + pfile->owner = THIS_MODULE; + + return usb_gadget_register_driver (&zero_driver); + + no_file: + remove_proc_entry("isoc_data", NULL); + done: + return retval; +} +module_init (init); + +static void __exit cleanup (void) +{ + + usb_gadget_unregister_driver (&zero_driver); + + remove_proc_entry("isoc_data", pdir); + remove_proc_entry("isoc_test", NULL); +} +module_exit (cleanup); diff --git a/drivers/usb/host/dwc_otg/dwc_cfi_common.h b/drivers/usb/host/dwc_otg/dwc_cfi_common.h new file mode 100644 index 00000000000000..7770e201ad3bd8 --- /dev/null +++ b/drivers/usb/host/dwc_otg/dwc_cfi_common.h @@ -0,0 +1,142 @@ +/* ========================================================================== + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ + +#if !defined(__DWC_CFI_COMMON_H__) +#define __DWC_CFI_COMMON_H__ + +//#include <linux/types.h> + +/** + * @file + * + * This file contains the CFI specific common constants, interfaces + * (functions and macros) and structures for Linux. No PCD specific + * data structure or definition is to be included in this file. + * + */ + +/** This is a request for all Core Features */ +#define VEN_CORE_GET_FEATURES 0xB1 + +/** This is a request to get the value of a specific Core Feature */ +#define VEN_CORE_GET_FEATURE 0xB2 + +/** This command allows the host to set the value of a specific Core Feature */ +#define VEN_CORE_SET_FEATURE 0xB3 + +/** This command allows the host to set the default values of + * either all or any specific Core Feature + */ +#define VEN_CORE_RESET_FEATURES 0xB4 + +/** This command forces the PCD to write the deferred values of a Core Features */ +#define VEN_CORE_ACTIVATE_FEATURES 0xB5 + +/** This request reads a DWORD value from a register at the specified offset */ +#define VEN_CORE_READ_REGISTER 0xB6 + +/** This request writes a DWORD value into a register at the specified offset */ +#define VEN_CORE_WRITE_REGISTER 0xB7 + +/** This structure is the header of the Core Features dataset returned to + * the Host + */ +struct cfi_all_features_header { +/** The features header structure length is */ +#define CFI_ALL_FEATURES_HDR_LEN 8 + /** + * The total length of the features dataset returned to the Host + */ + uint16_t wTotalLen; + + /** + * CFI version number inBinary-Coded Decimal (i.e., 1.00 is 100H). + * This field identifies the version of the CFI Specification with which + * the device is compliant. + */ + uint16_t wVersion; + + /** The ID of the Core */ + uint16_t wCoreID; +#define CFI_CORE_ID_UDC 1 +#define CFI_CORE_ID_OTG 2 +#define CFI_CORE_ID_WUDEV 3 + + /** Number of features returned by VEN_CORE_GET_FEATURES request */ + uint16_t wNumFeatures; +} UPACKED; + +typedef struct cfi_all_features_header cfi_all_features_header_t; + +/** This structure is a header of the Core Feature descriptor dataset returned to + * the Host after the VEN_CORE_GET_FEATURES request + */ +struct cfi_feature_desc_header { +#define CFI_FEATURE_DESC_HDR_LEN 8 + + /** The feature ID */ + uint16_t wFeatureID; + + /** Length of this feature descriptor in bytes - including the + * length of the feature name string + */ + uint16_t wLength; + + /** The data length of this feature in bytes */ + uint16_t wDataLength; + + /** + * Attributes of this features + * D0: Access rights + * 0 - Read/Write + * 1 - Read only + */ + uint8_t bmAttributes; +#define CFI_FEATURE_ATTR_RO 1 +#define CFI_FEATURE_ATTR_RW 0 + + /** Length of the feature name in bytes */ + uint8_t bNameLen; + + /** The feature name buffer */ + //uint8_t *name; +} UPACKED; + +typedef struct cfi_feature_desc_header cfi_feature_desc_header_t; + +/** + * This structure describes a NULL terminated string referenced by its id field. + * It is very similar to usb_string structure but has the id field type set to 16-bit. + */ +struct cfi_string { + uint16_t id; + const uint8_t *s; +}; +typedef struct cfi_string cfi_string_t; + +#endif diff --git a/drivers/usb/host/dwc_otg/dwc_otg_adp.c b/drivers/usb/host/dwc_otg/dwc_otg_adp.c new file mode 100644 index 00000000000000..cb49aedd51f6fb --- /dev/null +++ b/drivers/usb/host/dwc_otg/dwc_otg_adp.c @@ -0,0 +1,842 @@ +/* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_adp.c $ + * $Revision: #12 $ + * $Date: 2011/10/26 $ + * $Change: 1873028 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ + +#include "dwc_os.h" +#include "dwc_otg_regs.h" +#include "dwc_otg_cil.h" +#include "dwc_otg_adp.h" + +/** @file + * + * This file contains the most of the Attach Detect Protocol implementation for + * the driver to support OTG Rev2.0. + * + */ + +void dwc_otg_adp_write_reg(dwc_otg_core_if_t * core_if, uint32_t value) +{ + adpctl_data_t adpctl; + + adpctl.d32 = value; + adpctl.b.ar = 0x2; + + DWC_WRITE_REG32(&core_if->core_global_regs->adpctl, adpctl.d32); + + while (adpctl.b.ar) { + adpctl.d32 = DWC_READ_REG32(&core_if->core_global_regs->adpctl); + } + +} + +/** + * Function is called to read ADP registers + */ +uint32_t dwc_otg_adp_read_reg(dwc_otg_core_if_t * core_if) +{ + adpctl_data_t adpctl; + + adpctl.d32 = 0; + adpctl.b.ar = 0x1; + + DWC_WRITE_REG32(&core_if->core_global_regs->adpctl, adpctl.d32); + + while (adpctl.b.ar) { + adpctl.d32 = DWC_READ_REG32(&core_if->core_global_regs->adpctl); + } + + return adpctl.d32; +} + +/** + * Function is called to read ADPCTL register and filter Write-clear bits + */ +static uint32_t dwc_otg_adp_read_reg_filter(dwc_otg_core_if_t * core_if) +{ + adpctl_data_t adpctl; + + adpctl.d32 = dwc_otg_adp_read_reg(core_if); + adpctl.b.adp_tmout_int = 0; + adpctl.b.adp_prb_int = 0; + adpctl.b.adp_tmout_int = 0; + + return adpctl.d32; +} + +static void adp_sense_timeout(void *ptr) +{ + dwc_otg_core_if_t *core_if = (dwc_otg_core_if_t *) ptr; + core_if->adp.sense_timer_started = 0; + DWC_PRINTF("ADP SENSE TIMEOUT\n"); + if (core_if->adp_enable) { + dwc_otg_adp_sense_stop(core_if); + dwc_otg_adp_probe_start(core_if); + } +} + +/** + * This function is called when the ADP vbus timer expires. Timeout is 1.1s. + */ +static void adp_vbuson_timeout(void *ptr) +{ + gpwrdn_data_t gpwrdn; + dwc_otg_core_if_t *core_if = (dwc_otg_core_if_t *) ptr; + hprt0_data_t hprt0 = {.d32 = 0 }; + pcgcctl_data_t pcgcctl = {.d32 = 0 }; + DWC_PRINTF("%s: 1.1 seconds expire after turning on VBUS\n",__FUNCTION__); + if (core_if) { + core_if->adp.vbuson_timer_started = 0; + /* Turn off vbus */ + hprt0.b.prtpwr = 1; + DWC_MODIFY_REG32(core_if->host_if->hprt0, hprt0.d32, 0); + gpwrdn.d32 = 0; + + /* Power off the core */ + if (core_if->power_down == 2) { + /* Enable Wakeup Logic */ +// gpwrdn.b.wkupactiv = 1; + gpwrdn.b.pmuactv = 0; + gpwrdn.b.pwrdnrstn = 1; + gpwrdn.b.pwrdnclmp = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, + gpwrdn.d32); + + /* Suspend the Phy Clock */ + pcgcctl.b.stoppclk = 1; + DWC_MODIFY_REG32(core_if->pcgcctl, 0, pcgcctl.d32); + + /* Switch on VDD */ +// gpwrdn.b.wkupactiv = 1; + gpwrdn.b.pmuactv = 1; + gpwrdn.b.pwrdnrstn = 1; + gpwrdn.b.pwrdnclmp = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, + gpwrdn.d32); + } else { + /* Enable Power Down Logic */ + gpwrdn.b.pmuintsel = 1; + gpwrdn.b.pmuactv = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, gpwrdn.d32); + } + + /* Power off the core */ + if (core_if->power_down == 2) { + gpwrdn.d32 = 0; + gpwrdn.b.pwrdnswtch = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, + gpwrdn.d32, 0); + } + + /* Unmask SRP detected interrupt from Power Down Logic */ + gpwrdn.d32 = 0; + gpwrdn.b.srp_det_msk = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, gpwrdn.d32); + + dwc_otg_adp_probe_start(core_if); + dwc_otg_dump_global_registers(core_if); + dwc_otg_dump_host_registers(core_if); + } + +} + +/** + * Start the ADP Initial Probe timer to detect if Port Connected interrupt is + * not asserted within 1.1 seconds. + * + * @param core_if the pointer to core_if strucure. + */ +static void dwc_otg_adp_vbuson_timer_start(dwc_otg_core_if_t * core_if) +{ + core_if->adp.vbuson_timer_started = 1; + if (core_if->adp.vbuson_timer) + { + DWC_PRINTF("SCHEDULING VBUSON TIMER\n"); + /* 1.1 secs + 60ms necessary for cil_hcd_start*/ + DWC_TIMER_SCHEDULE(core_if->adp.vbuson_timer, 1160); + } else { + DWC_WARN("VBUSON_TIMER = %p\n",core_if->adp.vbuson_timer); + } +} + +#if 0 +/** + * Masks all DWC OTG core interrupts + * + */ +static void mask_all_interrupts(dwc_otg_core_if_t * core_if) +{ + int i; + gahbcfg_data_t ahbcfg = {.d32 = 0 }; + + /* Mask Host Interrupts */ + + /* Clear and disable HCINTs */ + for (i = 0; i < core_if->core_params->host_channels; i++) { + DWC_WRITE_REG32(&core_if->host_if->hc_regs[i]->hcintmsk, 0); + DWC_WRITE_REG32(&core_if->host_if->hc_regs[i]->hcint, 0xFFFFFFFF); + + } + + /* Clear and disable HAINT */ + DWC_WRITE_REG32(&core_if->host_if->host_global_regs->haintmsk, 0x0000); + DWC_WRITE_REG32(&core_if->host_if->host_global_regs->haint, 0xFFFFFFFF); + + /* Mask Device Interrupts */ + if (!core_if->multiproc_int_enable) { + /* Clear and disable IN Endpoint interrupts */ + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->diepmsk, 0); + for (i = 0; i <= core_if->dev_if->num_in_eps; i++) { + DWC_WRITE_REG32(&core_if->dev_if->in_ep_regs[i]-> + diepint, 0xFFFFFFFF); + } + + /* Clear and disable OUT Endpoint interrupts */ + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->doepmsk, 0); + for (i = 0; i <= core_if->dev_if->num_out_eps; i++) { + DWC_WRITE_REG32(&core_if->dev_if->out_ep_regs[i]-> + doepint, 0xFFFFFFFF); + } + + /* Clear and disable DAINT */ + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->daint, + 0xFFFFFFFF); + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->daintmsk, 0); + } else { + for (i = 0; i < core_if->dev_if->num_in_eps; ++i) { + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs-> + diepeachintmsk[i], 0); + DWC_WRITE_REG32(&core_if->dev_if->in_ep_regs[i]-> + diepint, 0xFFFFFFFF); + } + + for (i = 0; i < core_if->dev_if->num_out_eps; ++i) { + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs-> + doepeachintmsk[i], 0); + DWC_WRITE_REG32(&core_if->dev_if->out_ep_regs[i]-> + doepint, 0xFFFFFFFF); + } + + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->deachintmsk, + 0); + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->deachint, + 0xFFFFFFFF); + + } + + /* Disable interrupts */ + ahbcfg.b.glblintrmsk = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gahbcfg, ahbcfg.d32, 0); + + /* Disable all interrupts. */ + DWC_WRITE_REG32(&core_if->core_global_regs->gintmsk, 0); + + /* Clear any pending interrupts */ + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, 0xFFFFFFFF); + + /* Clear any pending OTG Interrupts */ + DWC_WRITE_REG32(&core_if->core_global_regs->gotgint, 0xFFFFFFFF); +} + +/** + * Unmask Port Connection Detected interrupt + * + */ +static void unmask_conn_det_intr(dwc_otg_core_if_t * core_if) +{ + gintmsk_data_t gintmsk = {.d32 = 0,.b.portintr = 1 }; + + DWC_WRITE_REG32(&core_if->core_global_regs->gintmsk, gintmsk.d32); +} +#endif + +/** + * Starts the ADP Probing + * + * @param core_if the pointer to core_if structure. + */ +uint32_t dwc_otg_adp_probe_start(dwc_otg_core_if_t * core_if) +{ + + adpctl_data_t adpctl = {.d32 = 0}; + gpwrdn_data_t gpwrdn; +#if 0 + adpctl_data_t adpctl_int = {.d32 = 0, .b.adp_prb_int = 1, + .b.adp_sns_int = 1, b.adp_tmout_int}; +#endif + dwc_otg_disable_global_interrupts(core_if); + DWC_PRINTF("ADP Probe Start\n"); + core_if->adp.probe_enabled = 1; + + adpctl.b.adpres = 1; + dwc_otg_adp_write_reg(core_if, adpctl.d32); + + while (adpctl.b.adpres) { + adpctl.d32 = dwc_otg_adp_read_reg(core_if); + } + + adpctl.d32 = 0; + gpwrdn.d32 = DWC_READ_REG32(&core_if->core_global_regs->gpwrdn); + + /* In Host mode unmask SRP detected interrupt */ + gpwrdn.d32 = 0; + gpwrdn.b.sts_chngint_msk = 1; + if (!gpwrdn.b.idsts) { + gpwrdn.b.srp_det_msk = 1; + } + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, gpwrdn.d32); + + adpctl.b.adp_tmout_int_msk = 1; + adpctl.b.adp_prb_int_msk = 1; + adpctl.b.prb_dschg = 1; + adpctl.b.prb_delta = 1; + adpctl.b.prb_per = 1; + adpctl.b.adpen = 1; + adpctl.b.enaprb = 1; + + dwc_otg_adp_write_reg(core_if, adpctl.d32); + DWC_PRINTF("ADP Probe Finish\n"); + return 0; +} + +/** + * Starts the ADP Sense timer to detect if ADP Sense interrupt is not asserted + * within 3 seconds. + * + * @param core_if the pointer to core_if strucure. + */ +static void dwc_otg_adp_sense_timer_start(dwc_otg_core_if_t * core_if) +{ + core_if->adp.sense_timer_started = 1; + DWC_TIMER_SCHEDULE(core_if->adp.sense_timer, 3000 /* 3 secs */ ); +} + +/** + * Starts the ADP Sense + * + * @param core_if the pointer to core_if strucure. + */ +uint32_t dwc_otg_adp_sense_start(dwc_otg_core_if_t * core_if) +{ + adpctl_data_t adpctl; + + DWC_PRINTF("ADP Sense Start\n"); + + /* Unmask ADP sense interrupt and mask all other from the core */ + adpctl.d32 = dwc_otg_adp_read_reg_filter(core_if); + adpctl.b.adp_sns_int_msk = 1; + dwc_otg_adp_write_reg(core_if, adpctl.d32); + dwc_otg_disable_global_interrupts(core_if); // vahrama + + /* Set ADP reset bit*/ + adpctl.d32 = dwc_otg_adp_read_reg_filter(core_if); + adpctl.b.adpres = 1; + dwc_otg_adp_write_reg(core_if, adpctl.d32); + + while (adpctl.b.adpres) { + adpctl.d32 = dwc_otg_adp_read_reg(core_if); + } + + adpctl.b.adpres = 0; + adpctl.b.adpen = 1; + adpctl.b.enasns = 1; + dwc_otg_adp_write_reg(core_if, adpctl.d32); + + dwc_otg_adp_sense_timer_start(core_if); + + return 0; +} + +/** + * Stops the ADP Probing + * + * @param core_if the pointer to core_if strucure. + */ +uint32_t dwc_otg_adp_probe_stop(dwc_otg_core_if_t * core_if) +{ + + adpctl_data_t adpctl; + DWC_PRINTF("Stop ADP probe\n"); + core_if->adp.probe_enabled = 0; + core_if->adp.probe_counter = 0; + adpctl.d32 = dwc_otg_adp_read_reg(core_if); + + adpctl.b.adpen = 0; + adpctl.b.adp_prb_int = 1; + adpctl.b.adp_tmout_int = 1; + adpctl.b.adp_sns_int = 1; + dwc_otg_adp_write_reg(core_if, adpctl.d32); + + return 0; +} + +/** + * Stops the ADP Sensing + * + * @param core_if the pointer to core_if strucure. + */ +uint32_t dwc_otg_adp_sense_stop(dwc_otg_core_if_t * core_if) +{ + adpctl_data_t adpctl; + + core_if->adp.sense_enabled = 0; + + adpctl.d32 = dwc_otg_adp_read_reg_filter(core_if); + adpctl.b.enasns = 0; + adpctl.b.adp_sns_int = 1; + dwc_otg_adp_write_reg(core_if, adpctl.d32); + + return 0; +} + +/** + * Called to turn on the VBUS after initial ADP probe in host mode. + * If port power was already enabled in cil_hcd_start function then + * only schedule a timer. + * + * @param core_if the pointer to core_if structure. + */ +static void dwc_otg_adp_turnon_vbus(dwc_otg_core_if_t * core_if) +{ + hprt0_data_t hprt0 = {.d32 = 0 }; + hprt0.d32 = dwc_otg_read_hprt0(core_if); + DWC_PRINTF("Turn on VBUS for 1.1s, port power is %d\n", hprt0.b.prtpwr); + + if (hprt0.b.prtpwr == 0) { + hprt0.b.prtpwr = 1; + //DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + } + + dwc_otg_adp_vbuson_timer_start(core_if); +} + +/** + * Called right after driver is loaded + * to perform initial actions for ADP + * + * @param core_if the pointer to core_if structure. + * @param is_host - flag for current mode of operation either from GINTSTS or GPWRDN + */ +void dwc_otg_adp_start(dwc_otg_core_if_t * core_if, uint8_t is_host) +{ + gpwrdn_data_t gpwrdn; + + DWC_PRINTF("ADP Initial Start\n"); + core_if->adp.adp_started = 1; + + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, 0xFFFFFFFF); + dwc_otg_disable_global_interrupts(core_if); + if (is_host) { + DWC_PRINTF("HOST MODE\n"); + /* Enable Power Down Logic Interrupt*/ + gpwrdn.d32 = 0; + gpwrdn.b.pmuintsel = 1; + gpwrdn.b.pmuactv = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, gpwrdn.d32); + /* Initialize first ADP probe to obtain Ramp Time value */ + core_if->adp.initial_probe = 1; + dwc_otg_adp_probe_start(core_if); + } else { + gotgctl_data_t gotgctl; + gotgctl.d32 = DWC_READ_REG32(&core_if->core_global_regs->gotgctl); + DWC_PRINTF("DEVICE MODE\n"); + if (gotgctl.b.bsesvld == 0) { + /* Enable Power Down Logic Interrupt*/ + gpwrdn.d32 = 0; + DWC_PRINTF("VBUS is not valid - start ADP probe\n"); + gpwrdn.b.pmuintsel = 1; + gpwrdn.b.pmuactv = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, gpwrdn.d32); + core_if->adp.initial_probe = 1; + dwc_otg_adp_probe_start(core_if); + } else { + DWC_PRINTF("VBUS is valid - initialize core as a Device\n"); + core_if->op_state = B_PERIPHERAL; + dwc_otg_core_init(core_if); + dwc_otg_enable_global_interrupts(core_if); + cil_pcd_start(core_if); + dwc_otg_dump_global_registers(core_if); + dwc_otg_dump_dev_registers(core_if); + } + } +} + +void dwc_otg_adp_init(dwc_otg_core_if_t * core_if) +{ + core_if->adp.adp_started = 0; + core_if->adp.initial_probe = 0; + core_if->adp.probe_timer_values[0] = -1; + core_if->adp.probe_timer_values[1] = -1; + core_if->adp.probe_enabled = 0; + core_if->adp.sense_enabled = 0; + core_if->adp.sense_timer_started = 0; + core_if->adp.vbuson_timer_started = 0; + core_if->adp.probe_counter = 0; + core_if->adp.gpwrdn = 0; + core_if->adp.attached = DWC_OTG_ADP_UNKOWN; + /* Initialize timers */ + core_if->adp.sense_timer = + DWC_TIMER_ALLOC("ADP SENSE TIMER", adp_sense_timeout, core_if); + core_if->adp.vbuson_timer = + DWC_TIMER_ALLOC("ADP VBUS ON TIMER", adp_vbuson_timeout, core_if); + if (!core_if->adp.sense_timer || !core_if->adp.vbuson_timer) + { + DWC_ERROR("Could not allocate memory for ADP timers\n"); + } +} + +void dwc_otg_adp_remove(dwc_otg_core_if_t * core_if) +{ + gpwrdn_data_t gpwrdn = { .d32 = 0 }; + gpwrdn.b.pmuintsel = 1; + gpwrdn.b.pmuactv = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + + if (core_if->adp.probe_enabled) + dwc_otg_adp_probe_stop(core_if); + if (core_if->adp.sense_enabled) + dwc_otg_adp_sense_stop(core_if); + if (core_if->adp.sense_timer_started) + DWC_TIMER_CANCEL(core_if->adp.sense_timer); + if (core_if->adp.vbuson_timer_started) + DWC_TIMER_CANCEL(core_if->adp.vbuson_timer); + DWC_TIMER_FREE(core_if->adp.sense_timer); + DWC_TIMER_FREE(core_if->adp.vbuson_timer); +} + +///////////////////////////////////////////////////////////////////// +////////////// ADP Interrupt Handlers /////////////////////////////// +///////////////////////////////////////////////////////////////////// +/** + * This function sets Ramp Timer values + */ +static uint32_t set_timer_value(dwc_otg_core_if_t * core_if, uint32_t val) +{ + if (core_if->adp.probe_timer_values[0] == -1) { + core_if->adp.probe_timer_values[0] = val; + core_if->adp.probe_timer_values[1] = -1; + return 1; + } else { + core_if->adp.probe_timer_values[1] = + core_if->adp.probe_timer_values[0]; + core_if->adp.probe_timer_values[0] = val; + return 0; + } +} + +/** + * This function compares Ramp Timer values + */ +static uint32_t compare_timer_values(dwc_otg_core_if_t * core_if) +{ + uint32_t diff; + if (core_if->adp.probe_timer_values[0]>=core_if->adp.probe_timer_values[1]) + diff = core_if->adp.probe_timer_values[0]-core_if->adp.probe_timer_values[1]; + else + diff = core_if->adp.probe_timer_values[1]-core_if->adp.probe_timer_values[0]; + if(diff < 2) { + return 0; + } else { + return 1; + } +} + +/** + * This function handles ADP Probe Interrupts + */ +static int32_t dwc_otg_adp_handle_prb_intr(dwc_otg_core_if_t * core_if, + uint32_t val) +{ + adpctl_data_t adpctl = {.d32 = 0 }; + gpwrdn_data_t gpwrdn, temp; + adpctl.d32 = val; + + temp.d32 = DWC_READ_REG32(&core_if->core_global_regs->gpwrdn); + core_if->adp.probe_counter++; + core_if->adp.gpwrdn = DWC_READ_REG32(&core_if->core_global_regs->gpwrdn); + if (adpctl.b.rtim == 0 && !temp.b.idsts){ + DWC_PRINTF("RTIM value is 0\n"); + goto exit; + } + if (set_timer_value(core_if, adpctl.b.rtim) && + core_if->adp.initial_probe) { + core_if->adp.initial_probe = 0; + dwc_otg_adp_probe_stop(core_if); + gpwrdn.d32 = 0; + gpwrdn.b.pmuactv = 1; + gpwrdn.b.pmuintsel = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, 0xFFFFFFFF); + + /* check which value is for device mode and which for Host mode */ + if (!temp.b.idsts) { /* considered host mode value is 0 */ + /* + * Turn on VBUS after initial ADP probe. + */ + core_if->op_state = A_HOST; + dwc_otg_enable_global_interrupts(core_if); + DWC_SPINUNLOCK(core_if->lock); + cil_hcd_start(core_if); + dwc_otg_adp_turnon_vbus(core_if); + DWC_SPINLOCK(core_if->lock); + } else { + /* + * Initiate SRP after initial ADP probe. + */ + dwc_otg_enable_global_interrupts(core_if); + dwc_otg_initiate_srp(core_if); + } + } else if (core_if->adp.probe_counter > 2){ + gpwrdn.d32 = DWC_READ_REG32(&core_if->core_global_regs->gpwrdn); + if (compare_timer_values(core_if)) { + DWC_PRINTF("Difference in timer values !!! \n"); +// core_if->adp.attached = DWC_OTG_ADP_ATTACHED; + dwc_otg_adp_probe_stop(core_if); + + /* Power on the core */ + if (core_if->power_down == 2) { + gpwrdn.b.pwrdnswtch = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs-> + gpwrdn, 0, gpwrdn.d32); + } + + /* check which value is for device mode and which for Host mode */ + if (!temp.b.idsts) { /* considered host mode value is 0 */ + /* Disable Interrupt from Power Down Logic */ + gpwrdn.d32 = 0; + gpwrdn.b.pmuintsel = 1; + gpwrdn.b.pmuactv = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs-> + gpwrdn, gpwrdn.d32, 0); + + /* + * Initialize the Core for Host mode. + */ + core_if->op_state = A_HOST; + dwc_otg_core_init(core_if); + dwc_otg_enable_global_interrupts(core_if); + cil_hcd_start(core_if); + } else { + gotgctl_data_t gotgctl; + /* Mask SRP detected interrupt from Power Down Logic */ + gpwrdn.d32 = 0; + gpwrdn.b.srp_det_msk = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs-> + gpwrdn, gpwrdn.d32, 0); + + /* Disable Power Down Logic */ + gpwrdn.d32 = 0; + gpwrdn.b.pmuintsel = 1; + gpwrdn.b.pmuactv = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs-> + gpwrdn, gpwrdn.d32, 0); + + /* + * Initialize the Core for Device mode. + */ + core_if->op_state = B_PERIPHERAL; + dwc_otg_core_init(core_if); + dwc_otg_enable_global_interrupts(core_if); + cil_pcd_start(core_if); + + gotgctl.d32 = DWC_READ_REG32(&core_if->core_global_regs->gotgctl); + if (!gotgctl.b.bsesvld) { + dwc_otg_initiate_srp(core_if); + } + } + } + if (core_if->power_down == 2) { + if (gpwrdn.b.bsessvld) { + /* Mask SRP detected interrupt from Power Down Logic */ + gpwrdn.d32 = 0; + gpwrdn.b.srp_det_msk = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + + /* Disable Power Down Logic */ + gpwrdn.d32 = 0; + gpwrdn.b.pmuactv = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + + /* + * Initialize the Core for Device mode. + */ + core_if->op_state = B_PERIPHERAL; + dwc_otg_core_init(core_if); + dwc_otg_enable_global_interrupts(core_if); + cil_pcd_start(core_if); + } + } + } +exit: + /* Clear interrupt */ + adpctl.d32 = dwc_otg_adp_read_reg(core_if); + adpctl.b.adp_prb_int = 1; + dwc_otg_adp_write_reg(core_if, adpctl.d32); + + return 0; +} + +/** + * This function hadles ADP Sense Interrupt + */ +static int32_t dwc_otg_adp_handle_sns_intr(dwc_otg_core_if_t * core_if) +{ + adpctl_data_t adpctl; + /* Stop ADP Sense timer */ + DWC_TIMER_CANCEL(core_if->adp.sense_timer); + + /* Restart ADP Sense timer */ + dwc_otg_adp_sense_timer_start(core_if); + + /* Clear interrupt */ + adpctl.d32 = dwc_otg_adp_read_reg(core_if); + adpctl.b.adp_sns_int = 1; + dwc_otg_adp_write_reg(core_if, adpctl.d32); + + return 0; +} + +/** + * This function handles ADP Probe Interrupts + */ +static int32_t dwc_otg_adp_handle_prb_tmout_intr(dwc_otg_core_if_t * core_if, + uint32_t val) +{ + adpctl_data_t adpctl = {.d32 = 0 }; + adpctl.d32 = val; + set_timer_value(core_if, adpctl.b.rtim); + + /* Clear interrupt */ + adpctl.d32 = dwc_otg_adp_read_reg(core_if); + adpctl.b.adp_tmout_int = 1; + dwc_otg_adp_write_reg(core_if, adpctl.d32); + + return 0; +} + +/** + * ADP Interrupt handler. + * + */ +int32_t dwc_otg_adp_handle_intr(dwc_otg_core_if_t * core_if) +{ + int retval = 0; + adpctl_data_t adpctl = {.d32 = 0}; + + adpctl.d32 = dwc_otg_adp_read_reg(core_if); + DWC_PRINTF("ADPCTL = %08x\n",adpctl.d32); + + if (adpctl.b.adp_sns_int & adpctl.b.adp_sns_int_msk) { + DWC_PRINTF("ADP Sense interrupt\n"); + retval |= dwc_otg_adp_handle_sns_intr(core_if); + } + if (adpctl.b.adp_tmout_int & adpctl.b.adp_tmout_int_msk) { + DWC_PRINTF("ADP timeout interrupt\n"); + retval |= dwc_otg_adp_handle_prb_tmout_intr(core_if, adpctl.d32); + } + if (adpctl.b.adp_prb_int & adpctl.b.adp_prb_int_msk) { + DWC_PRINTF("ADP Probe interrupt\n"); + adpctl.b.adp_prb_int = 1; + retval |= dwc_otg_adp_handle_prb_intr(core_if, adpctl.d32); + } + + DWC_PRINTF("RETURN FROM ADP ISR\n"); + + return retval; +} + +/** + * + * @param core_if Programming view of DWC_otg controller. + */ +int32_t dwc_otg_adp_handle_srp_intr(dwc_otg_core_if_t * core_if) +{ + +#ifndef DWC_HOST_ONLY + hprt0_data_t hprt0; + gpwrdn_data_t gpwrdn; + DWC_DEBUGPL(DBG_ANY, "++ Power Down Logic Session Request Interrupt++\n"); + + gpwrdn.d32 = DWC_READ_REG32(&core_if->core_global_regs->gpwrdn); + /* check which value is for device mode and which for Host mode */ + if (!gpwrdn.b.idsts) { /* considered host mode value is 0 */ + DWC_PRINTF("SRP: Host mode\n"); + + if (core_if->adp_enable) { + dwc_otg_adp_probe_stop(core_if); + + /* Power on the core */ + if (core_if->power_down == 2) { + gpwrdn.b.pwrdnswtch = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs-> + gpwrdn, 0, gpwrdn.d32); + } + + core_if->op_state = A_HOST; + dwc_otg_core_init(core_if); + dwc_otg_enable_global_interrupts(core_if); + cil_hcd_start(core_if); + } + + /* Turn on the port power bit. */ + hprt0.d32 = dwc_otg_read_hprt0(core_if); + hprt0.b.prtpwr = 1; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + + /* Start the Connection timer. So a message can be displayed + * if connect does not occur within 10 seconds. */ + cil_hcd_session_start(core_if); + } else { + DWC_PRINTF("SRP: Device mode %s\n", __FUNCTION__); + if (core_if->adp_enable) { + dwc_otg_adp_probe_stop(core_if); + + /* Power on the core */ + if (core_if->power_down == 2) { + gpwrdn.b.pwrdnswtch = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs-> + gpwrdn, 0, gpwrdn.d32); + } + + gpwrdn.d32 = 0; + gpwrdn.b.pmuactv = 0; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, + gpwrdn.d32); + + core_if->op_state = B_PERIPHERAL; + dwc_otg_core_init(core_if); + dwc_otg_enable_global_interrupts(core_if); + cil_pcd_start(core_if); + } + } +#endif + return 1; +} diff --git a/drivers/usb/host/dwc_otg/dwc_otg_adp.h b/drivers/usb/host/dwc_otg/dwc_otg_adp.h new file mode 100644 index 00000000000000..4110b25d2002ed --- /dev/null +++ b/drivers/usb/host/dwc_otg/dwc_otg_adp.h @@ -0,0 +1,80 @@ +/* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_adp.h $ + * $Revision: #7 $ + * $Date: 2011/10/24 $ + * $Change: 1871159 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ + +#ifndef __DWC_OTG_ADP_H__ +#define __DWC_OTG_ADP_H__ + +/** + * @file + * + * This file contains the Attach Detect Protocol interfaces and defines + * (functions) and structures for Linux. + * + */ + +#define DWC_OTG_ADP_UNATTACHED 0 +#define DWC_OTG_ADP_ATTACHED 1 +#define DWC_OTG_ADP_UNKOWN 2 + +typedef struct dwc_otg_adp { + uint32_t adp_started; + uint32_t initial_probe; + int32_t probe_timer_values[2]; + uint32_t probe_enabled; + uint32_t sense_enabled; + dwc_timer_t *sense_timer; + uint32_t sense_timer_started; + dwc_timer_t *vbuson_timer; + uint32_t vbuson_timer_started; + uint32_t attached; + uint32_t probe_counter; + uint32_t gpwrdn; +} dwc_otg_adp_t; + +/** + * Attach Detect Protocol functions + */ + +extern void dwc_otg_adp_write_reg(dwc_otg_core_if_t * core_if, uint32_t value); +extern uint32_t dwc_otg_adp_read_reg(dwc_otg_core_if_t * core_if); +extern uint32_t dwc_otg_adp_probe_start(dwc_otg_core_if_t * core_if); +extern uint32_t dwc_otg_adp_sense_start(dwc_otg_core_if_t * core_if); +extern uint32_t dwc_otg_adp_probe_stop(dwc_otg_core_if_t * core_if); +extern uint32_t dwc_otg_adp_sense_stop(dwc_otg_core_if_t * core_if); +extern void dwc_otg_adp_start(dwc_otg_core_if_t * core_if, uint8_t is_host); +extern void dwc_otg_adp_init(dwc_otg_core_if_t * core_if); +extern void dwc_otg_adp_remove(dwc_otg_core_if_t * core_if); +extern int32_t dwc_otg_adp_handle_intr(dwc_otg_core_if_t * core_if); +extern int32_t dwc_otg_adp_handle_srp_intr(dwc_otg_core_if_t * core_if); + +#endif //__DWC_OTG_ADP_H__ diff --git a/drivers/usb/host/dwc_otg/dwc_otg_attr.c b/drivers/usb/host/dwc_otg/dwc_otg_attr.c new file mode 100644 index 00000000000000..2f8ea77c3892b6 --- /dev/null +++ b/drivers/usb/host/dwc_otg/dwc_otg_attr.c @@ -0,0 +1,1212 @@ +/* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_attr.c $ + * $Revision: #44 $ + * $Date: 2010/11/29 $ + * $Change: 1636033 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ + +/** @file + * + * The diagnostic interface will provide access to the controller for + * bringing up the hardware and testing. The Linux driver attributes + * feature will be used to provide the Linux Diagnostic + * Interface. These attributes are accessed through sysfs. + */ + +/** @page "Linux Module Attributes" + * + * The Linux module attributes feature is used to provide the Linux + * Diagnostic Interface. These attributes are accessed through sysfs. + * The diagnostic interface will provide access to the controller for + * bringing up the hardware and testing. + + The following table shows the attributes. + <table> + <tr> + <td><b> Name</b></td> + <td><b> Description</b></td> + <td><b> Access</b></td> + </tr> + + <tr> + <td> mode </td> + <td> Returns the current mode: 0 for device mode, 1 for host mode</td> + <td> Read</td> + </tr> + + <tr> + <td> hnpcapable </td> + <td> Gets or sets the "HNP-capable" bit in the Core USB Configuraton Register. + Read returns the current value.</td> + <td> Read/Write</td> + </tr> + + <tr> + <td> srpcapable </td> + <td> Gets or sets the "SRP-capable" bit in the Core USB Configuraton Register. + Read returns the current value.</td> + <td> Read/Write</td> + </tr> + + <tr> + <td> hsic_connect </td> + <td> Gets or sets the "HSIC-Connect" bit in the GLPMCFG Register. + Read returns the current value.</td> + <td> Read/Write</td> + </tr> + + <tr> + <td> inv_sel_hsic </td> + <td> Gets or sets the "Invert Select HSIC" bit in the GLPMFG Register. + Read returns the current value.</td> + <td> Read/Write</td> + </tr> + + <tr> + <td> hnp </td> + <td> Initiates the Host Negotiation Protocol. Read returns the status.</td> + <td> Read/Write</td> + </tr> + + <tr> + <td> srp </td> + <td> Initiates the Session Request Protocol. Read returns the status.</td> + <td> Read/Write</td> + </tr> + + <tr> + <td> buspower </td> + <td> Gets or sets the Power State of the bus (0 - Off or 1 - On)</td> + <td> Read/Write</td> + </tr> + + <tr> + <td> bussuspend </td> + <td> Suspends the USB bus.</td> + <td> Read/Write</td> + </tr> + + <tr> + <td> busconnected </td> + <td> Gets the connection status of the bus</td> + <td> Read</td> + </tr> + + <tr> + <td> gotgctl </td> + <td> Gets or sets the Core Control Status Register.</td> + <td> Read/Write</td> + </tr> + + <tr> + <td> gusbcfg </td> + <td> Gets or sets the Core USB Configuration Register</td> + <td> Read/Write</td> + </tr> + + <tr> + <td> grxfsiz </td> + <td> Gets or sets the Receive FIFO Size Register</td> + <td> Read/Write</td> + </tr> + + <tr> + <td> gnptxfsiz </td> + <td> Gets or sets the non-periodic Transmit Size Register</td> + <td> Read/Write</td> + </tr> + + <tr> + <td> gpvndctl </td> + <td> Gets or sets the PHY Vendor Control Register</td> + <td> Read/Write</td> + </tr> + + <tr> + <td> ggpio </td> + <td> Gets the value in the lower 16-bits of the General Purpose IO Register + or sets the upper 16 bits.</td> + <td> Read/Write</td> + </tr> + + <tr> + <td> guid </td> + <td> Gets or sets the value of the User ID Register</td> + <td> Read/Write</td> + </tr> + + <tr> + <td> gsnpsid </td> + <td> Gets the value of the Synopsys ID Regester</td> + <td> Read</td> + </tr> + + <tr> + <td> devspeed </td> + <td> Gets or sets the device speed setting in the DCFG register</td> + <td> Read/Write</td> + </tr> + + <tr> + <td> enumspeed </td> + <td> Gets the device enumeration Speed.</td> + <td> Read</td> + </tr> + + <tr> + <td> hptxfsiz </td> + <td> Gets the value of the Host Periodic Transmit FIFO</td> + <td> Read</td> + </tr> + + <tr> + <td> hprt0 </td> + <td> Gets or sets the value in the Host Port Control and Status Register</td> + <td> Read/Write</td> + </tr> + + <tr> + <td> regoffset </td> + <td> Sets the register offset for the next Register Access</td> + <td> Read/Write</td> + </tr> + + <tr> + <td> regvalue </td> + <td> Gets or sets the value of the register at the offset in the regoffset attribute.</td> + <td> Read/Write</td> + </tr> + + <tr> + <td> remote_wakeup </td> + <td> On read, shows the status of Remote Wakeup. On write, initiates a remote + wakeup of the host. When bit 0 is 1 and Remote Wakeup is enabled, the Remote + Wakeup signalling bit in the Device Control Register is set for 1 + milli-second.</td> + <td> Read/Write</td> + </tr> + + <tr> + <td> rem_wakeup_pwrdn </td> + <td> On read, shows the status core - hibernated or not. On write, initiates + a remote wakeup of the device from Hibernation. </td> + <td> Read/Write</td> + </tr> + + <tr> + <td> mode_ch_tim_en </td> + <td> This bit is used to enable or disable the host core to wait for 200 PHY + clock cycles at the end of Resume to change the opmode signal to the PHY to 00 + after Suspend or LPM. </td> + <td> Read/Write</td> + </tr> + + <tr> + <td> fr_interval </td> + <td> On read, shows the value of HFIR Frame Interval. On write, dynamically + reload HFIR register during runtime. The application can write a value to this + register only after the Port Enable bit of the Host Port Control and Status + register (HPRT.PrtEnaPort) has been set </td> + <td> Read/Write</td> + </tr> + + <tr> + <td> disconnect_us </td> + <td> On read, shows the status of disconnect_device_us. On write, sets disconnect_us + which causes soft disconnect for 100us. Applicable only for device mode of operation.</td> + <td> Read/Write</td> + </tr> + + <tr> + <td> regdump </td> + <td> Dumps the contents of core registers.</td> + <td> Read</td> + </tr> + + <tr> + <td> spramdump </td> + <td> Dumps the contents of core registers.</td> + <td> Read</td> + </tr> + + <tr> + <td> hcddump </td> + <td> Dumps the current HCD state.</td> + <td> Read</td> + </tr> + + <tr> + <td> hcd_frrem </td> + <td> Shows the average value of the Frame Remaining + field in the Host Frame Number/Frame Remaining register when an SOF interrupt + occurs. This can be used to determine the average interrupt latency. Also + shows the average Frame Remaining value for start_transfer and the "a" and + "b" sample points. The "a" and "b" sample points may be used during debugging + bto determine how long it takes to execute a section of the HCD code.</td> + <td> Read</td> + </tr> + + <tr> + <td> rd_reg_test </td> + <td> Displays the time required to read the GNPTXFSIZ register many times + (the output shows the number of times the register is read). + <td> Read</td> + </tr> + + <tr> + <td> wr_reg_test </td> + <td> Displays the time required to write the GNPTXFSIZ register many times + (the output shows the number of times the register is written). + <td> Read</td> + </tr> + + <tr> + <td> lpm_response </td> + <td> Gets or sets lpm_response mode. Applicable only in device mode. + <td> Write</td> + </tr> + + <tr> + <td> sleep_status </td> + <td> Shows sleep status of device. + <td> Read</td> + </tr> + + </table> + + Example usage: + To get the current mode: + cat /sys/devices/lm0/mode + + To power down the USB: + echo 0 > /sys/devices/lm0/buspower + */ + +#include "dwc_otg_os_dep.h" +#include "dwc_os.h" +#include "dwc_otg_driver.h" +#include "dwc_otg_attr.h" +#include "dwc_otg_core_if.h" +#include "dwc_otg_pcd_if.h" +#include "dwc_otg_hcd_if.h" + +/* + * MACROs for defining sysfs attribute + */ +#ifdef LM_INTERFACE + +#define DWC_OTG_DEVICE_ATTR_BITFIELD_SHOW(_otg_attr_name_,_string_) \ +static ssize_t _otg_attr_name_##_show (struct device *_dev, struct device_attribute *attr, char *buf) \ +{ \ + struct lm_device *lm_dev = container_of(_dev, struct lm_device, dev); \ + dwc_otg_device_t *otg_dev = lm_get_drvdata(lm_dev); \ + uint32_t val; \ + val = dwc_otg_get_##_otg_attr_name_ (otg_dev->core_if); \ + return sprintf (buf, "%s = 0x%x\n", _string_, val); \ +} +#define DWC_OTG_DEVICE_ATTR_BITFIELD_STORE(_otg_attr_name_,_string_) \ +static ssize_t _otg_attr_name_##_store (struct device *_dev, struct device_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + struct lm_device *lm_dev = container_of(_dev, struct lm_device, dev); \ + dwc_otg_device_t *otg_dev = lm_get_drvdata(lm_dev); \ + uint32_t set = simple_strtoul(buf, NULL, 16); \ + dwc_otg_set_##_otg_attr_name_(otg_dev->core_if, set);\ + return count; \ +} + +#elif defined(PCI_INTERFACE) + +#define DWC_OTG_DEVICE_ATTR_BITFIELD_SHOW(_otg_attr_name_,_string_) \ +static ssize_t _otg_attr_name_##_show (struct device *_dev, struct device_attribute *attr, char *buf) \ +{ \ + dwc_otg_device_t *otg_dev = dev_get_drvdata(_dev); \ + uint32_t val; \ + val = dwc_otg_get_##_otg_attr_name_ (otg_dev->core_if); \ + return sprintf (buf, "%s = 0x%x\n", _string_, val); \ +} +#define DWC_OTG_DEVICE_ATTR_BITFIELD_STORE(_otg_attr_name_,_string_) \ +static ssize_t _otg_attr_name_##_store (struct device *_dev, struct device_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + dwc_otg_device_t *otg_dev = dev_get_drvdata(_dev); \ + uint32_t set = simple_strtoul(buf, NULL, 16); \ + dwc_otg_set_##_otg_attr_name_(otg_dev->core_if, set);\ + return count; \ +} + +#elif defined(PLATFORM_INTERFACE) + +#define DWC_OTG_DEVICE_ATTR_BITFIELD_SHOW(_otg_attr_name_,_string_) \ +static ssize_t _otg_attr_name_##_show (struct device *_dev, struct device_attribute *attr, char *buf) \ +{ \ + struct platform_device *platform_dev = \ + container_of(_dev, struct platform_device, dev); \ + dwc_otg_device_t *otg_dev = platform_get_drvdata(platform_dev); \ + uint32_t val; \ + DWC_PRINTF("%s(%p) -> platform_dev %p, otg_dev %p\n", \ + __func__, _dev, platform_dev, otg_dev); \ + val = dwc_otg_get_##_otg_attr_name_ (otg_dev->core_if); \ + return sprintf (buf, "%s = 0x%x\n", _string_, val); \ +} +#define DWC_OTG_DEVICE_ATTR_BITFIELD_STORE(_otg_attr_name_,_string_) \ +static ssize_t _otg_attr_name_##_store (struct device *_dev, struct device_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + struct platform_device *platform_dev = container_of(_dev, struct platform_device, dev); \ + dwc_otg_device_t *otg_dev = platform_get_drvdata(platform_dev); \ + uint32_t set = simple_strtoul(buf, NULL, 16); \ + dwc_otg_set_##_otg_attr_name_(otg_dev->core_if, set);\ + return count; \ +} +#endif + +/* + * MACROs for defining sysfs attribute for 32-bit registers + */ +#ifdef LM_INTERFACE +#define DWC_OTG_DEVICE_ATTR_REG_SHOW(_otg_attr_name_,_string_) \ +static ssize_t _otg_attr_name_##_show (struct device *_dev, struct device_attribute *attr, char *buf) \ +{ \ + struct lm_device *lm_dev = container_of(_dev, struct lm_device, dev); \ + dwc_otg_device_t *otg_dev = lm_get_drvdata(lm_dev); \ + uint32_t val; \ + val = dwc_otg_get_##_otg_attr_name_ (otg_dev->core_if); \ + return sprintf (buf, "%s = 0x%08x\n", _string_, val); \ +} +#define DWC_OTG_DEVICE_ATTR_REG_STORE(_otg_attr_name_,_string_) \ +static ssize_t _otg_attr_name_##_store (struct device *_dev, struct device_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + struct lm_device *lm_dev = container_of(_dev, struct lm_device, dev); \ + dwc_otg_device_t *otg_dev = lm_get_drvdata(lm_dev); \ + uint32_t val = simple_strtoul(buf, NULL, 16); \ + dwc_otg_set_##_otg_attr_name_ (otg_dev->core_if, val); \ + return count; \ +} +#elif defined(PCI_INTERFACE) +#define DWC_OTG_DEVICE_ATTR_REG_SHOW(_otg_attr_name_,_string_) \ +static ssize_t _otg_attr_name_##_show (struct device *_dev, struct device_attribute *attr, char *buf) \ +{ \ + dwc_otg_device_t *otg_dev = dev_get_drvdata(_dev); \ + uint32_t val; \ + val = dwc_otg_get_##_otg_attr_name_ (otg_dev->core_if); \ + return sprintf (buf, "%s = 0x%08x\n", _string_, val); \ +} +#define DWC_OTG_DEVICE_ATTR_REG_STORE(_otg_attr_name_,_string_) \ +static ssize_t _otg_attr_name_##_store (struct device *_dev, struct device_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + dwc_otg_device_t *otg_dev = dev_get_drvdata(_dev); \ + uint32_t val = simple_strtoul(buf, NULL, 16); \ + dwc_otg_set_##_otg_attr_name_ (otg_dev->core_if, val); \ + return count; \ +} + +#elif defined(PLATFORM_INTERFACE) +#include "dwc_otg_dbg.h" +#define DWC_OTG_DEVICE_ATTR_REG_SHOW(_otg_attr_name_,_string_) \ +static ssize_t _otg_attr_name_##_show (struct device *_dev, struct device_attribute *attr, char *buf) \ +{ \ + struct platform_device *platform_dev = container_of(_dev, struct platform_device, dev); \ + dwc_otg_device_t *otg_dev = platform_get_drvdata(platform_dev); \ + uint32_t val; \ + DWC_PRINTF("%s(%p) -> platform_dev %p, otg_dev %p\n", \ + __func__, _dev, platform_dev, otg_dev); \ + val = dwc_otg_get_##_otg_attr_name_ (otg_dev->core_if); \ + return sprintf (buf, "%s = 0x%08x\n", _string_, val); \ +} +#define DWC_OTG_DEVICE_ATTR_REG_STORE(_otg_attr_name_,_string_) \ +static ssize_t _otg_attr_name_##_store (struct device *_dev, struct device_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + struct platform_device *platform_dev = container_of(_dev, struct platform_device, dev); \ + dwc_otg_device_t *otg_dev = platform_get_drvdata(platform_dev); \ + uint32_t val = simple_strtoul(buf, NULL, 16); \ + dwc_otg_set_##_otg_attr_name_ (otg_dev->core_if, val); \ + return count; \ +} + +#endif + +#define DWC_OTG_DEVICE_ATTR_BITFIELD_RW(_otg_attr_name_,_string_) \ +DWC_OTG_DEVICE_ATTR_BITFIELD_SHOW(_otg_attr_name_,_string_) \ +DWC_OTG_DEVICE_ATTR_BITFIELD_STORE(_otg_attr_name_,_string_) \ +DEVICE_ATTR(_otg_attr_name_,0644,_otg_attr_name_##_show,_otg_attr_name_##_store); + +#define DWC_OTG_DEVICE_ATTR_BITFIELD_RO(_otg_attr_name_,_string_) \ +DWC_OTG_DEVICE_ATTR_BITFIELD_SHOW(_otg_attr_name_,_string_) \ +DEVICE_ATTR(_otg_attr_name_,0444,_otg_attr_name_##_show,NULL); + +#define DWC_OTG_DEVICE_ATTR_REG32_RW(_otg_attr_name_,_addr_,_string_) \ +DWC_OTG_DEVICE_ATTR_REG_SHOW(_otg_attr_name_,_string_) \ +DWC_OTG_DEVICE_ATTR_REG_STORE(_otg_attr_name_,_string_) \ +DEVICE_ATTR(_otg_attr_name_,0644,_otg_attr_name_##_show,_otg_attr_name_##_store); + +#define DWC_OTG_DEVICE_ATTR_REG32_RO(_otg_attr_name_,_addr_,_string_) \ +DWC_OTG_DEVICE_ATTR_REG_SHOW(_otg_attr_name_,_string_) \ +DEVICE_ATTR(_otg_attr_name_,0444,_otg_attr_name_##_show,NULL); + +/** @name Functions for Show/Store of Attributes */ +/**@{*/ + +/** + * Helper function returning the otg_device structure of the given device + */ +static dwc_otg_device_t *dwc_otg_drvdev(struct device *_dev) +{ + dwc_otg_device_t *otg_dev; + DWC_OTG_GETDRVDEV(otg_dev, _dev); + return otg_dev; +} + +/** + * Show the register offset of the Register Access. + */ +static ssize_t regoffset_show(struct device *_dev, + struct device_attribute *attr, char *buf) +{ + dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev); + return snprintf(buf, sizeof("0xFFFFFFFF\n") + 1, "0x%08x\n", + otg_dev->os_dep.reg_offset); +} + +/** + * Set the register offset for the next Register Access Read/Write + */ +static ssize_t regoffset_store(struct device *_dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev); + uint32_t offset = simple_strtoul(buf, NULL, 16); +#if defined(LM_INTERFACE) || defined(PLATFORM_INTERFACE) + if (offset < SZ_256K) { +#elif defined(PCI_INTERFACE) + if (offset < 0x00040000) { +#endif + otg_dev->os_dep.reg_offset = offset; + } else { + dev_err(_dev, "invalid offset\n"); + } + + return count; +} + +DEVICE_ATTR(regoffset, S_IRUGO | S_IWUSR, regoffset_show, regoffset_store); + +/** + * Show the value of the register at the offset in the reg_offset + * attribute. + */ +static ssize_t regvalue_show(struct device *_dev, + struct device_attribute *attr, char *buf) +{ + dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev); + uint32_t val; + volatile uint32_t *addr; + + if (otg_dev->os_dep.reg_offset != 0xFFFFFFFF && 0 != otg_dev->os_dep.base) { + /* Calculate the address */ + addr = (uint32_t *) (otg_dev->os_dep.reg_offset + + (uint8_t *) otg_dev->os_dep.base); + val = DWC_READ_REG32(addr); + return snprintf(buf, + sizeof("Reg@0xFFFFFFFF = 0xFFFFFFFF\n") + 1, + "Reg@0x%06x = 0x%08x\n", otg_dev->os_dep.reg_offset, + val); + } else { + dev_err(_dev, "Invalid offset (0x%0x)\n", otg_dev->os_dep.reg_offset); + return sprintf(buf, "invalid offset\n"); + } +} + +/** + * Store the value in the register at the offset in the reg_offset + * attribute. + * + */ +static ssize_t regvalue_store(struct device *_dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev); + volatile uint32_t *addr; + uint32_t val = simple_strtoul(buf, NULL, 16); + //dev_dbg(_dev, "Offset=0x%08x Val=0x%08x\n", otg_dev->reg_offset, val); + if (otg_dev->os_dep.reg_offset != 0xFFFFFFFF && 0 != otg_dev->os_dep.base) { + /* Calculate the address */ + addr = (uint32_t *) (otg_dev->os_dep.reg_offset + + (uint8_t *) otg_dev->os_dep.base); + DWC_WRITE_REG32(addr, val); + } else { + dev_err(_dev, "Invalid Register Offset (0x%08x)\n", + otg_dev->os_dep.reg_offset); + } + return count; +} + +DEVICE_ATTR(regvalue, S_IRUGO | S_IWUSR, regvalue_show, regvalue_store); + +/* + * Attributes + */ +DWC_OTG_DEVICE_ATTR_BITFIELD_RO(mode, "Mode"); +DWC_OTG_DEVICE_ATTR_BITFIELD_RW(hnpcapable, "HNPCapable"); +DWC_OTG_DEVICE_ATTR_BITFIELD_RW(srpcapable, "SRPCapable"); +DWC_OTG_DEVICE_ATTR_BITFIELD_RW(hsic_connect, "HSIC Connect"); +DWC_OTG_DEVICE_ATTR_BITFIELD_RW(inv_sel_hsic, "Invert Select HSIC"); + +//DWC_OTG_DEVICE_ATTR_BITFIELD_RW(buspower,&(otg_dev->core_if->core_global_regs->gotgctl),(1<<8),8,"Mode"); +//DWC_OTG_DEVICE_ATTR_BITFIELD_RW(bussuspend,&(otg_dev->core_if->core_global_regs->gotgctl),(1<<8),8,"Mode"); +DWC_OTG_DEVICE_ATTR_BITFIELD_RO(busconnected, "Bus Connected"); + +DWC_OTG_DEVICE_ATTR_REG32_RW(gotgctl, 0, "GOTGCTL"); +DWC_OTG_DEVICE_ATTR_REG32_RW(gusbcfg, + &(otg_dev->core_if->core_global_regs->gusbcfg), + "GUSBCFG"); +DWC_OTG_DEVICE_ATTR_REG32_RW(grxfsiz, + &(otg_dev->core_if->core_global_regs->grxfsiz), + "GRXFSIZ"); +DWC_OTG_DEVICE_ATTR_REG32_RW(gnptxfsiz, + &(otg_dev->core_if->core_global_regs->gnptxfsiz), + "GNPTXFSIZ"); +DWC_OTG_DEVICE_ATTR_REG32_RW(gpvndctl, + &(otg_dev->core_if->core_global_regs->gpvndctl), + "GPVNDCTL"); +DWC_OTG_DEVICE_ATTR_REG32_RW(ggpio, + &(otg_dev->core_if->core_global_regs->ggpio), + "GGPIO"); +DWC_OTG_DEVICE_ATTR_REG32_RW(guid, &(otg_dev->core_if->core_global_regs->guid), + "GUID"); +DWC_OTG_DEVICE_ATTR_REG32_RO(gsnpsid, + &(otg_dev->core_if->core_global_regs->gsnpsid), + "GSNPSID"); +DWC_OTG_DEVICE_ATTR_BITFIELD_RW(devspeed, "Device Speed"); +DWC_OTG_DEVICE_ATTR_BITFIELD_RO(enumspeed, "Device Enumeration Speed"); + +DWC_OTG_DEVICE_ATTR_REG32_RO(hptxfsiz, + &(otg_dev->core_if->core_global_regs->hptxfsiz), + "HPTXFSIZ"); +DWC_OTG_DEVICE_ATTR_REG32_RW(hprt0, otg_dev->core_if->host_if->hprt0, "HPRT0"); + +/** + * @todo Add code to initiate the HNP. + */ +/** + * Show the HNP status bit + */ +static ssize_t hnp_show(struct device *_dev, + struct device_attribute *attr, char *buf) +{ + dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev); + return sprintf(buf, "HstNegScs = 0x%x\n", + dwc_otg_get_hnpstatus(otg_dev->core_if)); +} + +/** + * Set the HNP Request bit + */ +static ssize_t hnp_store(struct device *_dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev); + uint32_t in = simple_strtoul(buf, NULL, 16); + dwc_otg_set_hnpreq(otg_dev->core_if, in); + return count; +} + +DEVICE_ATTR(hnp, 0644, hnp_show, hnp_store); + +/** + * @todo Add code to initiate the SRP. + */ +/** + * Show the SRP status bit + */ +static ssize_t srp_show(struct device *_dev, + struct device_attribute *attr, char *buf) +{ +#ifndef DWC_HOST_ONLY + dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev); + return sprintf(buf, "SesReqScs = 0x%x\n", + dwc_otg_get_srpstatus(otg_dev->core_if)); +#else + return sprintf(buf, "Host Only Mode!\n"); +#endif +} + +/** + * Set the SRP Request bit + */ +static ssize_t srp_store(struct device *_dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ +#ifndef DWC_HOST_ONLY + dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev); + dwc_otg_pcd_initiate_srp(otg_dev->pcd); +#endif + return count; +} + +DEVICE_ATTR(srp, 0644, srp_show, srp_store); + +/** + * @todo Need to do more for power on/off? + */ +/** + * Show the Bus Power status + */ +static ssize_t buspower_show(struct device *_dev, + struct device_attribute *attr, char *buf) +{ + dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev); + return sprintf(buf, "Bus Power = 0x%x\n", + dwc_otg_get_prtpower(otg_dev->core_if)); +} + +/** + * Set the Bus Power status + */ +static ssize_t buspower_store(struct device *_dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev); + uint32_t on = simple_strtoul(buf, NULL, 16); + dwc_otg_set_prtpower(otg_dev->core_if, on); + return count; +} + +DEVICE_ATTR(buspower, 0644, buspower_show, buspower_store); + +/** + * @todo Need to do more for suspend? + */ +/** + * Show the Bus Suspend status + */ +static ssize_t bussuspend_show(struct device *_dev, + struct device_attribute *attr, char *buf) +{ + dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev); + return sprintf(buf, "Bus Suspend = 0x%x\n", + dwc_otg_get_prtsuspend(otg_dev->core_if)); +} + +/** + * Set the Bus Suspend status + */ +static ssize_t bussuspend_store(struct device *_dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev); + uint32_t in = simple_strtoul(buf, NULL, 16); + dwc_otg_set_prtsuspend(otg_dev->core_if, in); + return count; +} + +DEVICE_ATTR(bussuspend, 0644, bussuspend_show, bussuspend_store); + +/** + * Show the Mode Change Ready Timer status + */ +static ssize_t mode_ch_tim_en_show(struct device *_dev, + struct device_attribute *attr, char *buf) +{ + dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev); + return sprintf(buf, "Mode Change Ready Timer Enable = 0x%x\n", + dwc_otg_get_mode_ch_tim(otg_dev->core_if)); +} + +/** + * Set the Mode Change Ready Timer status + */ +static ssize_t mode_ch_tim_en_store(struct device *_dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev); + uint32_t in = simple_strtoul(buf, NULL, 16); + dwc_otg_set_mode_ch_tim(otg_dev->core_if, in); + return count; +} + +DEVICE_ATTR(mode_ch_tim_en, 0644, mode_ch_tim_en_show, mode_ch_tim_en_store); + +/** + * Show the value of HFIR Frame Interval bitfield + */ +static ssize_t fr_interval_show(struct device *_dev, + struct device_attribute *attr, char *buf) +{ + dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev); + return sprintf(buf, "Frame Interval = 0x%x\n", + dwc_otg_get_fr_interval(otg_dev->core_if)); +} + +/** + * Set the HFIR Frame Interval value + */ +static ssize_t fr_interval_store(struct device *_dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev); + uint32_t in = simple_strtoul(buf, NULL, 10); + dwc_otg_set_fr_interval(otg_dev->core_if, in); + return count; +} + +DEVICE_ATTR(fr_interval, 0644, fr_interval_show, fr_interval_store); + +/** + * Show the status of Remote Wakeup. + */ +static ssize_t remote_wakeup_show(struct device *_dev, + struct device_attribute *attr, char *buf) +{ +#ifndef DWC_HOST_ONLY + dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev); + + return sprintf(buf, + "Remote Wakeup Sig = %d Enabled = %d LPM Remote Wakeup = %d\n", + dwc_otg_get_remotewakesig(otg_dev->core_if), + dwc_otg_pcd_get_rmwkup_enable(otg_dev->pcd), + dwc_otg_get_lpm_remotewakeenabled(otg_dev->core_if)); +#else + return sprintf(buf, "Host Only Mode!\n"); +#endif /* DWC_HOST_ONLY */ +} + +/** + * Initiate a remote wakeup of the host. The Device control register + * Remote Wakeup Signal bit is written if the PCD Remote wakeup enable + * flag is set. + * + */ +static ssize_t remote_wakeup_store(struct device *_dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ +#ifndef DWC_HOST_ONLY + dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev); + uint32_t val = simple_strtoul(buf, NULL, 16); + + if (val & 1) { + dwc_otg_pcd_remote_wakeup(otg_dev->pcd, 1); + } else { + dwc_otg_pcd_remote_wakeup(otg_dev->pcd, 0); + } +#endif /* DWC_HOST_ONLY */ + return count; +} + +DEVICE_ATTR(remote_wakeup, S_IRUGO | S_IWUSR, remote_wakeup_show, + remote_wakeup_store); + +/** + * Show the whether core is hibernated or not. + */ +static ssize_t rem_wakeup_pwrdn_show(struct device *_dev, + struct device_attribute *attr, char *buf) +{ +#ifndef DWC_HOST_ONLY + dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev); + + if (dwc_otg_get_core_state(otg_dev->core_if)) { + DWC_PRINTF("Core is in hibernation\n"); + } else { + DWC_PRINTF("Core is not in hibernation\n"); + } +#endif /* DWC_HOST_ONLY */ + return 0; +} + +extern int dwc_otg_device_hibernation_restore(dwc_otg_core_if_t * core_if, + int rem_wakeup, int reset); + +/** + * Initiate a remote wakeup of the device to exit from hibernation. + */ +static ssize_t rem_wakeup_pwrdn_store(struct device *_dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ +#ifndef DWC_HOST_ONLY + dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev); + dwc_otg_device_hibernation_restore(otg_dev->core_if, 1, 0); +#endif + return count; +} + +DEVICE_ATTR(rem_wakeup_pwrdn, S_IRUGO | S_IWUSR, rem_wakeup_pwrdn_show, + rem_wakeup_pwrdn_store); + +static ssize_t disconnect_us(struct device *_dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + +#ifndef DWC_HOST_ONLY + dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev); + uint32_t val = simple_strtoul(buf, NULL, 16); + DWC_PRINTF("The Passed value is %04x\n", val); + + dwc_otg_pcd_disconnect_us(otg_dev->pcd, 50); + +#endif /* DWC_HOST_ONLY */ + return count; +} + +DEVICE_ATTR(disconnect_us, S_IWUSR, 0, disconnect_us); + +/** + * Dump global registers and either host or device registers (depending on the + * current mode of the core). + */ +static ssize_t regdump_show(struct device *_dev, + struct device_attribute *attr, char *buf) +{ + dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev); + + dwc_otg_dump_global_registers(otg_dev->core_if); + if (dwc_otg_is_host_mode(otg_dev->core_if)) { + dwc_otg_dump_host_registers(otg_dev->core_if); + } else { + dwc_otg_dump_dev_registers(otg_dev->core_if); + + } + return sprintf(buf, "Register Dump\n"); +} + +DEVICE_ATTR(regdump, S_IRUGO, regdump_show, 0); + +/** + * Dump global registers and either host or device registers (depending on the + * current mode of the core). + */ +static ssize_t spramdump_show(struct device *_dev, + struct device_attribute *attr, char *buf) +{ +#if 0 + dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev); + + dwc_otg_dump_spram(otg_dev->core_if); +#endif + + return sprintf(buf, "SPRAM Dump\n"); +} + +DEVICE_ATTR(spramdump, S_IRUGO, spramdump_show, 0); + +/** + * Dump the current hcd state. + */ +static ssize_t hcddump_show(struct device *_dev, + struct device_attribute *attr, char *buf) +{ +#ifndef DWC_DEVICE_ONLY + dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev); + dwc_otg_hcd_dump_state(otg_dev->hcd); +#endif /* DWC_DEVICE_ONLY */ + return sprintf(buf, "HCD Dump\n"); +} + +DEVICE_ATTR(hcddump, S_IRUGO, hcddump_show, 0); + +/** + * Dump the average frame remaining at SOF. This can be used to + * determine average interrupt latency. Frame remaining is also shown for + * start transfer and two additional sample points. + */ +static ssize_t hcd_frrem_show(struct device *_dev, + struct device_attribute *attr, char *buf) +{ +#ifndef DWC_DEVICE_ONLY + dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev); + + dwc_otg_hcd_dump_frrem(otg_dev->hcd); +#endif /* DWC_DEVICE_ONLY */ + return sprintf(buf, "HCD Dump Frame Remaining\n"); +} + +DEVICE_ATTR(hcd_frrem, S_IRUGO, hcd_frrem_show, 0); + +/** + * Displays the time required to read the GNPTXFSIZ register many times (the + * output shows the number of times the register is read). + */ +#define RW_REG_COUNT 10000000 +#define MSEC_PER_JIFFIE 1000/HZ +static ssize_t rd_reg_test_show(struct device *_dev, + struct device_attribute *attr, char *buf) +{ + dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev); + int i; + int time; + int start_jiffies; + + printk("HZ %d, MSEC_PER_JIFFIE %d, loops_per_jiffy %lu\n", + HZ, MSEC_PER_JIFFIE, loops_per_jiffy); + start_jiffies = jiffies; + for (i = 0; i < RW_REG_COUNT; i++) { + dwc_otg_get_gnptxfsiz(otg_dev->core_if); + } + time = jiffies - start_jiffies; + return sprintf(buf, + "Time to read GNPTXFSIZ reg %d times: %d msecs (%d jiffies)\n", + RW_REG_COUNT, time * MSEC_PER_JIFFIE, time); +} + +DEVICE_ATTR(rd_reg_test, S_IRUGO, rd_reg_test_show, 0); + +/** + * Displays the time required to write the GNPTXFSIZ register many times (the + * output shows the number of times the register is written). + */ +static ssize_t wr_reg_test_show(struct device *_dev, + struct device_attribute *attr, char *buf) +{ + dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev); + uint32_t reg_val; + int i; + int time; + int start_jiffies; + + printk("HZ %d, MSEC_PER_JIFFIE %d, loops_per_jiffy %lu\n", + HZ, MSEC_PER_JIFFIE, loops_per_jiffy); + reg_val = dwc_otg_get_gnptxfsiz(otg_dev->core_if); + start_jiffies = jiffies; + for (i = 0; i < RW_REG_COUNT; i++) { + dwc_otg_set_gnptxfsiz(otg_dev->core_if, reg_val); + } + time = jiffies - start_jiffies; + return sprintf(buf, + "Time to write GNPTXFSIZ reg %d times: %d msecs (%d jiffies)\n", + RW_REG_COUNT, time * MSEC_PER_JIFFIE, time); +} + +DEVICE_ATTR(wr_reg_test, S_IRUGO, wr_reg_test_show, 0); + +#ifdef CONFIG_USB_DWC_OTG_LPM + +/** +* Show the lpm_response attribute. +*/ +static ssize_t lpmresp_show(struct device *_dev, + struct device_attribute *attr, char *buf) +{ + dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev); + + if (!dwc_otg_get_param_lpm_enable(otg_dev->core_if)) + return sprintf(buf, "** LPM is DISABLED **\n"); + + if (!dwc_otg_is_device_mode(otg_dev->core_if)) { + return sprintf(buf, "** Current mode is not device mode\n"); + } + return sprintf(buf, "lpm_response = %d\n", + dwc_otg_get_lpmresponse(otg_dev->core_if)); +} + +/** +* Store the lpm_response attribute. +*/ +static ssize_t lpmresp_store(struct device *_dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev); + uint32_t val = simple_strtoul(buf, NULL, 16); + + if (!dwc_otg_get_param_lpm_enable(otg_dev->core_if)) { + return 0; + } + + if (!dwc_otg_is_device_mode(otg_dev->core_if)) { + return 0; + } + + dwc_otg_set_lpmresponse(otg_dev->core_if, val); + return count; +} + +DEVICE_ATTR(lpm_response, S_IRUGO | S_IWUSR, lpmresp_show, lpmresp_store); + +/** +* Show the sleep_status attribute. +*/ +static ssize_t sleepstatus_show(struct device *_dev, + struct device_attribute *attr, char *buf) +{ + dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev); + return sprintf(buf, "Sleep Status = %d\n", + dwc_otg_get_lpm_portsleepstatus(otg_dev->core_if)); +} + +/** + * Store the sleep_status attribure. + */ +static ssize_t sleepstatus_store(struct device *_dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev); + dwc_otg_core_if_t *core_if = otg_dev->core_if; + + if (dwc_otg_get_lpm_portsleepstatus(otg_dev->core_if)) { + if (dwc_otg_is_host_mode(core_if)) { + + DWC_PRINTF("Host initiated resume\n"); + dwc_otg_set_prtresume(otg_dev->core_if, 1); + } + } + + return count; +} + +DEVICE_ATTR(sleep_status, S_IRUGO | S_IWUSR, sleepstatus_show, + sleepstatus_store); + +#endif /* CONFIG_USB_DWC_OTG_LPM_ENABLE */ + +/**@}*/ + +/** + * Create the device files + */ +void dwc_otg_attr_create( +#ifdef LM_INTERFACE + struct lm_device *dev +#elif defined(PCI_INTERFACE) + struct pci_dev *dev +#elif defined(PLATFORM_INTERFACE) + struct platform_device *dev +#endif + ) +{ + int error; + + error = device_create_file(&dev->dev, &dev_attr_regoffset); + error = device_create_file(&dev->dev, &dev_attr_regvalue); + error = device_create_file(&dev->dev, &dev_attr_mode); + error = device_create_file(&dev->dev, &dev_attr_hnpcapable); + error = device_create_file(&dev->dev, &dev_attr_srpcapable); + error = device_create_file(&dev->dev, &dev_attr_hsic_connect); + error = device_create_file(&dev->dev, &dev_attr_inv_sel_hsic); + error = device_create_file(&dev->dev, &dev_attr_hnp); + error = device_create_file(&dev->dev, &dev_attr_srp); + error = device_create_file(&dev->dev, &dev_attr_buspower); + error = device_create_file(&dev->dev, &dev_attr_bussuspend); + error = device_create_file(&dev->dev, &dev_attr_mode_ch_tim_en); + error = device_create_file(&dev->dev, &dev_attr_fr_interval); + error = device_create_file(&dev->dev, &dev_attr_busconnected); + error = device_create_file(&dev->dev, &dev_attr_gotgctl); + error = device_create_file(&dev->dev, &dev_attr_gusbcfg); + error = device_create_file(&dev->dev, &dev_attr_grxfsiz); + error = device_create_file(&dev->dev, &dev_attr_gnptxfsiz); + error = device_create_file(&dev->dev, &dev_attr_gpvndctl); + error = device_create_file(&dev->dev, &dev_attr_ggpio); + error = device_create_file(&dev->dev, &dev_attr_guid); + error = device_create_file(&dev->dev, &dev_attr_gsnpsid); + error = device_create_file(&dev->dev, &dev_attr_devspeed); + error = device_create_file(&dev->dev, &dev_attr_enumspeed); + error = device_create_file(&dev->dev, &dev_attr_hptxfsiz); + error = device_create_file(&dev->dev, &dev_attr_hprt0); + error = device_create_file(&dev->dev, &dev_attr_remote_wakeup); + error = device_create_file(&dev->dev, &dev_attr_rem_wakeup_pwrdn); + error = device_create_file(&dev->dev, &dev_attr_disconnect_us); + error = device_create_file(&dev->dev, &dev_attr_regdump); + error = device_create_file(&dev->dev, &dev_attr_spramdump); + error = device_create_file(&dev->dev, &dev_attr_hcddump); + error = device_create_file(&dev->dev, &dev_attr_hcd_frrem); + error = device_create_file(&dev->dev, &dev_attr_rd_reg_test); + error = device_create_file(&dev->dev, &dev_attr_wr_reg_test); +#ifdef CONFIG_USB_DWC_OTG_LPM + error = device_create_file(&dev->dev, &dev_attr_lpm_response); + error = device_create_file(&dev->dev, &dev_attr_sleep_status); +#endif +} + +/** + * Remove the device files + */ +void dwc_otg_attr_remove( +#ifdef LM_INTERFACE + struct lm_device *dev +#elif defined(PCI_INTERFACE) + struct pci_dev *dev +#elif defined(PLATFORM_INTERFACE) + struct platform_device *dev +#endif + ) +{ + device_remove_file(&dev->dev, &dev_attr_regoffset); + device_remove_file(&dev->dev, &dev_attr_regvalue); + device_remove_file(&dev->dev, &dev_attr_mode); + device_remove_file(&dev->dev, &dev_attr_hnpcapable); + device_remove_file(&dev->dev, &dev_attr_srpcapable); + device_remove_file(&dev->dev, &dev_attr_hsic_connect); + device_remove_file(&dev->dev, &dev_attr_inv_sel_hsic); + device_remove_file(&dev->dev, &dev_attr_hnp); + device_remove_file(&dev->dev, &dev_attr_srp); + device_remove_file(&dev->dev, &dev_attr_buspower); + device_remove_file(&dev->dev, &dev_attr_bussuspend); + device_remove_file(&dev->dev, &dev_attr_mode_ch_tim_en); + device_remove_file(&dev->dev, &dev_attr_fr_interval); + device_remove_file(&dev->dev, &dev_attr_busconnected); + device_remove_file(&dev->dev, &dev_attr_gotgctl); + device_remove_file(&dev->dev, &dev_attr_gusbcfg); + device_remove_file(&dev->dev, &dev_attr_grxfsiz); + device_remove_file(&dev->dev, &dev_attr_gnptxfsiz); + device_remove_file(&dev->dev, &dev_attr_gpvndctl); + device_remove_file(&dev->dev, &dev_attr_ggpio); + device_remove_file(&dev->dev, &dev_attr_guid); + device_remove_file(&dev->dev, &dev_attr_gsnpsid); + device_remove_file(&dev->dev, &dev_attr_devspeed); + device_remove_file(&dev->dev, &dev_attr_enumspeed); + device_remove_file(&dev->dev, &dev_attr_hptxfsiz); + device_remove_file(&dev->dev, &dev_attr_hprt0); + device_remove_file(&dev->dev, &dev_attr_remote_wakeup); + device_remove_file(&dev->dev, &dev_attr_rem_wakeup_pwrdn); + device_remove_file(&dev->dev, &dev_attr_disconnect_us); + device_remove_file(&dev->dev, &dev_attr_regdump); + device_remove_file(&dev->dev, &dev_attr_spramdump); + device_remove_file(&dev->dev, &dev_attr_hcddump); + device_remove_file(&dev->dev, &dev_attr_hcd_frrem); + device_remove_file(&dev->dev, &dev_attr_rd_reg_test); + device_remove_file(&dev->dev, &dev_attr_wr_reg_test); +#ifdef CONFIG_USB_DWC_OTG_LPM + device_remove_file(&dev->dev, &dev_attr_lpm_response); + device_remove_file(&dev->dev, &dev_attr_sleep_status); +#endif +} diff --git a/drivers/usb/host/dwc_otg/dwc_otg_attr.h b/drivers/usb/host/dwc_otg/dwc_otg_attr.h new file mode 100644 index 00000000000000..e10b67f97c5220 --- /dev/null +++ b/drivers/usb/host/dwc_otg/dwc_otg_attr.h @@ -0,0 +1,89 @@ +/* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_attr.h $ + * $Revision: #13 $ + * $Date: 2010/06/21 $ + * $Change: 1532021 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ + +#if !defined(__DWC_OTG_ATTR_H__) +#define __DWC_OTG_ATTR_H__ + +/** @file + * This file contains the interface to the Linux device attributes. + */ +extern struct device_attribute dev_attr_regoffset; +extern struct device_attribute dev_attr_regvalue; + +extern struct device_attribute dev_attr_mode; +extern struct device_attribute dev_attr_hnpcapable; +extern struct device_attribute dev_attr_srpcapable; +extern struct device_attribute dev_attr_hnp; +extern struct device_attribute dev_attr_srp; +extern struct device_attribute dev_attr_buspower; +extern struct device_attribute dev_attr_bussuspend; +extern struct device_attribute dev_attr_mode_ch_tim_en; +extern struct device_attribute dev_attr_fr_interval; +extern struct device_attribute dev_attr_busconnected; +extern struct device_attribute dev_attr_gotgctl; +extern struct device_attribute dev_attr_gusbcfg; +extern struct device_attribute dev_attr_grxfsiz; +extern struct device_attribute dev_attr_gnptxfsiz; +extern struct device_attribute dev_attr_gpvndctl; +extern struct device_attribute dev_attr_ggpio; +extern struct device_attribute dev_attr_guid; +extern struct device_attribute dev_attr_gsnpsid; +extern struct device_attribute dev_attr_devspeed; +extern struct device_attribute dev_attr_enumspeed; +extern struct device_attribute dev_attr_hptxfsiz; +extern struct device_attribute dev_attr_hprt0; +#ifdef CONFIG_USB_DWC_OTG_LPM +extern struct device_attribute dev_attr_lpm_response; +extern struct device_attribute devi_attr_sleep_status; +#endif + +void dwc_otg_attr_create( +#ifdef LM_INTERFACE + struct lm_device *dev +#elif defined(PCI_INTERFACE) + struct pci_dev *dev +#elif defined(PLATFORM_INTERFACE) + struct platform_device *dev +#endif + ); + +void dwc_otg_attr_remove( +#ifdef LM_INTERFACE + struct lm_device *dev +#elif defined(PCI_INTERFACE) + struct pci_dev *dev +#elif defined(PLATFORM_INTERFACE) + struct platform_device *dev +#endif + ); +#endif diff --git a/drivers/usb/host/dwc_otg/dwc_otg_cfi.c b/drivers/usb/host/dwc_otg/dwc_otg_cfi.c new file mode 100644 index 00000000000000..e73016ef863d2f --- /dev/null +++ b/drivers/usb/host/dwc_otg/dwc_otg_cfi.c @@ -0,0 +1,1874 @@ +/* ========================================================================== + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ + +/** @file + * + * This file contains the most of the CFI(Core Feature Interface) + * implementation for the OTG. + */ + +#ifdef DWC_UTE_CFI + +#include "dwc_otg_pcd.h" +#include "dwc_otg_cfi.h" + +/** This definition should actually migrate to the Portability Library */ +#define DWC_CONSTANT_CPU_TO_LE16(x) (x) + +static int cfi_core_features_buf(uint8_t * buf, uint16_t buflen); +static int cfi_get_feature_value(uint8_t * buf, uint16_t buflen, + struct dwc_otg_pcd *pcd, + struct cfi_usb_ctrlrequest *ctrl_req); +static int cfi_set_feature_value(struct dwc_otg_pcd *pcd); +static int cfi_ep_get_sg_val(uint8_t * buf, struct dwc_otg_pcd *pcd, + struct cfi_usb_ctrlrequest *req); +static int cfi_ep_get_concat_val(uint8_t * buf, struct dwc_otg_pcd *pcd, + struct cfi_usb_ctrlrequest *req); +static int cfi_ep_get_align_val(uint8_t * buf, struct dwc_otg_pcd *pcd, + struct cfi_usb_ctrlrequest *req); +static int cfi_preproc_reset(struct dwc_otg_pcd *pcd, + struct cfi_usb_ctrlrequest *req); +static void cfi_free_ep_bs_dyn_data(cfi_ep_t * cfiep); + +static uint16_t get_dfifo_size(dwc_otg_core_if_t * core_if); +static int32_t get_rxfifo_size(dwc_otg_core_if_t * core_if, uint16_t wValue); +static int32_t get_txfifo_size(struct dwc_otg_pcd *pcd, uint16_t wValue); + +static uint8_t resize_fifos(dwc_otg_core_if_t * core_if); + +/** This is the header of the all features descriptor */ +static cfi_all_features_header_t all_props_desc_header = { + .wVersion = DWC_CONSTANT_CPU_TO_LE16(0x100), + .wCoreID = DWC_CONSTANT_CPU_TO_LE16(CFI_CORE_ID_OTG), + .wNumFeatures = DWC_CONSTANT_CPU_TO_LE16(9), +}; + +/** This is an array of statically allocated feature descriptors */ +static cfi_feature_desc_header_t prop_descs[] = { + + /* FT_ID_DMA_MODE */ + { + .wFeatureID = DWC_CONSTANT_CPU_TO_LE16(FT_ID_DMA_MODE), + .bmAttributes = CFI_FEATURE_ATTR_RW, + .wDataLength = DWC_CONSTANT_CPU_TO_LE16(1), + }, + + /* FT_ID_DMA_BUFFER_SETUP */ + { + .wFeatureID = DWC_CONSTANT_CPU_TO_LE16(FT_ID_DMA_BUFFER_SETUP), + .bmAttributes = CFI_FEATURE_ATTR_RW, + .wDataLength = DWC_CONSTANT_CPU_TO_LE16(6), + }, + + /* FT_ID_DMA_BUFF_ALIGN */ + { + .wFeatureID = DWC_CONSTANT_CPU_TO_LE16(FT_ID_DMA_BUFF_ALIGN), + .bmAttributes = CFI_FEATURE_ATTR_RW, + .wDataLength = DWC_CONSTANT_CPU_TO_LE16(2), + }, + + /* FT_ID_DMA_CONCAT_SETUP */ + { + .wFeatureID = DWC_CONSTANT_CPU_TO_LE16(FT_ID_DMA_CONCAT_SETUP), + .bmAttributes = CFI_FEATURE_ATTR_RW, + //.wDataLength = DWC_CONSTANT_CPU_TO_LE16(6), + }, + + /* FT_ID_DMA_CIRCULAR */ + { + .wFeatureID = DWC_CONSTANT_CPU_TO_LE16(FT_ID_DMA_CIRCULAR), + .bmAttributes = CFI_FEATURE_ATTR_RW, + .wDataLength = DWC_CONSTANT_CPU_TO_LE16(6), + }, + + /* FT_ID_THRESHOLD_SETUP */ + { + .wFeatureID = DWC_CONSTANT_CPU_TO_LE16(FT_ID_THRESHOLD_SETUP), + .bmAttributes = CFI_FEATURE_ATTR_RW, + .wDataLength = DWC_CONSTANT_CPU_TO_LE16(6), + }, + + /* FT_ID_DFIFO_DEPTH */ + { + .wFeatureID = DWC_CONSTANT_CPU_TO_LE16(FT_ID_DFIFO_DEPTH), + .bmAttributes = CFI_FEATURE_ATTR_RO, + .wDataLength = DWC_CONSTANT_CPU_TO_LE16(2), + }, + + /* FT_ID_TX_FIFO_DEPTH */ + { + .wFeatureID = DWC_CONSTANT_CPU_TO_LE16(FT_ID_TX_FIFO_DEPTH), + .bmAttributes = CFI_FEATURE_ATTR_RW, + .wDataLength = DWC_CONSTANT_CPU_TO_LE16(2), + }, + + /* FT_ID_RX_FIFO_DEPTH */ + { + .wFeatureID = DWC_CONSTANT_CPU_TO_LE16(FT_ID_RX_FIFO_DEPTH), + .bmAttributes = CFI_FEATURE_ATTR_RW, + .wDataLength = DWC_CONSTANT_CPU_TO_LE16(2), + } +}; + +/** The table of feature names */ +cfi_string_t prop_name_table[] = { + {FT_ID_DMA_MODE, "dma_mode"}, + {FT_ID_DMA_BUFFER_SETUP, "buffer_setup"}, + {FT_ID_DMA_BUFF_ALIGN, "buffer_align"}, + {FT_ID_DMA_CONCAT_SETUP, "concat_setup"}, + {FT_ID_DMA_CIRCULAR, "buffer_circular"}, + {FT_ID_THRESHOLD_SETUP, "threshold_setup"}, + {FT_ID_DFIFO_DEPTH, "dfifo_depth"}, + {FT_ID_TX_FIFO_DEPTH, "txfifo_depth"}, + {FT_ID_RX_FIFO_DEPTH, "rxfifo_depth"}, + {} +}; + +/************************************************************************/ + +/** + * Returns the name of the feature by its ID + * or NULL if no featute ID matches. + * + */ +const uint8_t *get_prop_name(uint16_t prop_id, int *len) +{ + cfi_string_t *pstr; + *len = 0; + + for (pstr = prop_name_table; pstr && pstr->s; pstr++) { + if (pstr->id == prop_id) { + *len = DWC_STRLEN(pstr->s); + return pstr->s; + } + } + return NULL; +} + +/** + * This function handles all CFI specific control requests. + * + * Return a negative value to stall the DCE. + */ +int cfi_setup(struct dwc_otg_pcd *pcd, struct cfi_usb_ctrlrequest *ctrl) +{ + int retval = 0; + dwc_otg_pcd_ep_t *ep = NULL; + cfiobject_t *cfi = pcd->cfi; + struct dwc_otg_core_if *coreif = GET_CORE_IF(pcd); + uint16_t wLen = DWC_LE16_TO_CPU(&ctrl->wLength); + uint16_t wValue = DWC_LE16_TO_CPU(&ctrl->wValue); + uint16_t wIndex = DWC_LE16_TO_CPU(&ctrl->wIndex); + uint32_t regaddr = 0; + uint32_t regval = 0; + + /* Save this Control Request in the CFI object. + * The data field will be assigned in the data stage completion CB function. + */ + cfi->ctrl_req = *ctrl; + cfi->ctrl_req.data = NULL; + + cfi->need_gadget_att = 0; + cfi->need_status_in_complete = 0; + + switch (ctrl->bRequest) { + case VEN_CORE_GET_FEATURES: + retval = cfi_core_features_buf(cfi->buf_in.buf, CFI_IN_BUF_LEN); + if (retval >= 0) { + //dump_msg(cfi->buf_in.buf, retval); + ep = &pcd->ep0; + + retval = min((uint16_t) retval, wLen); + /* Transfer this buffer to the host through the EP0-IN EP */ + ep->dwc_ep.dma_addr = cfi->buf_in.addr; + ep->dwc_ep.start_xfer_buff = cfi->buf_in.buf; + ep->dwc_ep.xfer_buff = cfi->buf_in.buf; + ep->dwc_ep.xfer_len = retval; + ep->dwc_ep.xfer_count = 0; + ep->dwc_ep.sent_zlp = 0; + ep->dwc_ep.total_len = ep->dwc_ep.xfer_len; + + pcd->ep0_pending = 1; + dwc_otg_ep0_start_transfer(coreif, &ep->dwc_ep); + } + retval = 0; + break; + + case VEN_CORE_GET_FEATURE: + CFI_INFO("VEN_CORE_GET_FEATURE\n"); + retval = cfi_get_feature_value(cfi->buf_in.buf, CFI_IN_BUF_LEN, + pcd, ctrl); + if (retval >= 0) { + ep = &pcd->ep0; + + retval = min((uint16_t) retval, wLen); + /* Transfer this buffer to the host through the EP0-IN EP */ + ep->dwc_ep.dma_addr = cfi->buf_in.addr; + ep->dwc_ep.start_xfer_buff = cfi->buf_in.buf; + ep->dwc_ep.xfer_buff = cfi->buf_in.buf; + ep->dwc_ep.xfer_len = retval; + ep->dwc_ep.xfer_count = 0; + ep->dwc_ep.sent_zlp = 0; + ep->dwc_ep.total_len = ep->dwc_ep.xfer_len; + + pcd->ep0_pending = 1; + dwc_otg_ep0_start_transfer(coreif, &ep->dwc_ep); + } + CFI_INFO("VEN_CORE_GET_FEATURE=%d\n", retval); + dump_msg(cfi->buf_in.buf, retval); + break; + + case VEN_CORE_SET_FEATURE: + CFI_INFO("VEN_CORE_SET_FEATURE\n"); + /* Set up an XFER to get the data stage of the control request, + * which is the new value of the feature to be modified. + */ + ep = &pcd->ep0; + ep->dwc_ep.is_in = 0; + ep->dwc_ep.dma_addr = cfi->buf_out.addr; + ep->dwc_ep.start_xfer_buff = cfi->buf_out.buf; + ep->dwc_ep.xfer_buff = cfi->buf_out.buf; + ep->dwc_ep.xfer_len = wLen; + ep->dwc_ep.xfer_count = 0; + ep->dwc_ep.sent_zlp = 0; + ep->dwc_ep.total_len = ep->dwc_ep.xfer_len; + + pcd->ep0_pending = 1; + /* Read the control write's data stage */ + dwc_otg_ep0_start_transfer(coreif, &ep->dwc_ep); + retval = 0; + break; + + case VEN_CORE_RESET_FEATURES: + CFI_INFO("VEN_CORE_RESET_FEATURES\n"); + cfi->need_gadget_att = 1; + cfi->need_status_in_complete = 1; + retval = cfi_preproc_reset(pcd, ctrl); + CFI_INFO("VEN_CORE_RESET_FEATURES = (%d)\n", retval); + break; + + case VEN_CORE_ACTIVATE_FEATURES: + CFI_INFO("VEN_CORE_ACTIVATE_FEATURES\n"); + break; + + case VEN_CORE_READ_REGISTER: + CFI_INFO("VEN_CORE_READ_REGISTER\n"); + /* wValue optionally contains the HI WORD of the register offset and + * wIndex contains the LOW WORD of the register offset + */ + if (wValue == 0) { + /* @TODO - MAS - fix the access to the base field */ + regaddr = 0; + //regaddr = (uint32_t) pcd->otg_dev->os_dep.base; + //GET_CORE_IF(pcd)->co + regaddr |= wIndex; + } else { + regaddr = (wValue << 16) | wIndex; + } + + /* Read a 32-bit value of the memory at the regaddr */ + regval = DWC_READ_REG32((uint32_t *) regaddr); + + ep = &pcd->ep0; + dwc_memcpy(cfi->buf_in.buf, ®val, sizeof(uint32_t)); + ep->dwc_ep.is_in = 1; + ep->dwc_ep.dma_addr = cfi->buf_in.addr; + ep->dwc_ep.start_xfer_buff = cfi->buf_in.buf; + ep->dwc_ep.xfer_buff = cfi->buf_in.buf; + ep->dwc_ep.xfer_len = wLen; + ep->dwc_ep.xfer_count = 0; + ep->dwc_ep.sent_zlp = 0; + ep->dwc_ep.total_len = ep->dwc_ep.xfer_len; + + pcd->ep0_pending = 1; + dwc_otg_ep0_start_transfer(coreif, &ep->dwc_ep); + cfi->need_gadget_att = 0; + retval = 0; + break; + + case VEN_CORE_WRITE_REGISTER: + CFI_INFO("VEN_CORE_WRITE_REGISTER\n"); + /* Set up an XFER to get the data stage of the control request, + * which is the new value of the register to be modified. + */ + ep = &pcd->ep0; + ep->dwc_ep.is_in = 0; + ep->dwc_ep.dma_addr = cfi->buf_out.addr; + ep->dwc_ep.start_xfer_buff = cfi->buf_out.buf; + ep->dwc_ep.xfer_buff = cfi->buf_out.buf; + ep->dwc_ep.xfer_len = wLen; + ep->dwc_ep.xfer_count = 0; + ep->dwc_ep.sent_zlp = 0; + ep->dwc_ep.total_len = ep->dwc_ep.xfer_len; + + pcd->ep0_pending = 1; + /* Read the control write's data stage */ + dwc_otg_ep0_start_transfer(coreif, &ep->dwc_ep); + retval = 0; + break; + + default: + retval = -DWC_E_NOT_SUPPORTED; + break; + } + + return retval; +} + +/** + * This function prepares the core features descriptors and copies its + * raw representation into the buffer <buf>. + * + * The buffer structure is as follows: + * all_features_header (8 bytes) + * features_#1 (8 bytes + feature name string length) + * features_#2 (8 bytes + feature name string length) + * ..... + * features_#n - where n=the total count of feature descriptors + */ +static int cfi_core_features_buf(uint8_t * buf, uint16_t buflen) +{ + cfi_feature_desc_header_t *prop_hdr = prop_descs; + cfi_feature_desc_header_t *prop; + cfi_all_features_header_t *all_props_hdr = &all_props_desc_header; + cfi_all_features_header_t *tmp; + uint8_t *tmpbuf = buf; + const uint8_t *pname = NULL; + int i, j, namelen = 0, totlen; + + /* Prepare and copy the core features into the buffer */ + CFI_INFO("%s:\n", __func__); + + tmp = (cfi_all_features_header_t *) tmpbuf; + *tmp = *all_props_hdr; + tmpbuf += CFI_ALL_FEATURES_HDR_LEN; + + j = sizeof(prop_descs) / sizeof(cfi_all_features_header_t); + for (i = 0; i < j; i++, prop_hdr++) { + pname = get_prop_name(prop_hdr->wFeatureID, &namelen); + prop = (cfi_feature_desc_header_t *) tmpbuf; + *prop = *prop_hdr; + + prop->bNameLen = namelen; + prop->wLength = + DWC_CONSTANT_CPU_TO_LE16(CFI_FEATURE_DESC_HDR_LEN + + namelen); + + tmpbuf += CFI_FEATURE_DESC_HDR_LEN; + dwc_memcpy(tmpbuf, pname, namelen); + tmpbuf += namelen; + } + + totlen = tmpbuf - buf; + + if (totlen > 0) { + tmp = (cfi_all_features_header_t *) buf; + tmp->wTotalLen = DWC_CONSTANT_CPU_TO_LE16(totlen); + } + + return totlen; +} + +/** + * This function releases all the dynamic memory in the CFI object. + */ +static void cfi_release(cfiobject_t * cfiobj) +{ + cfi_ep_t *cfiep; + dwc_list_link_t *tmp; + + CFI_INFO("%s\n", __func__); + + if (cfiobj->buf_in.buf) { + DWC_DMA_FREE(CFI_IN_BUF_LEN, cfiobj->buf_in.buf, + cfiobj->buf_in.addr); + cfiobj->buf_in.buf = NULL; + } + + if (cfiobj->buf_out.buf) { + DWC_DMA_FREE(CFI_OUT_BUF_LEN, cfiobj->buf_out.buf, + cfiobj->buf_out.addr); + cfiobj->buf_out.buf = NULL; + } + + /* Free the Buffer Setup values for each EP */ + //list_for_each_entry(cfiep, &cfiobj->active_eps, lh) { + DWC_LIST_FOREACH(tmp, &cfiobj->active_eps) { + cfiep = DWC_LIST_ENTRY(tmp, struct cfi_ep, lh); + cfi_free_ep_bs_dyn_data(cfiep); + } +} + +/** + * This function frees the dynamically allocated EP buffer setup data. + */ +static void cfi_free_ep_bs_dyn_data(cfi_ep_t * cfiep) +{ + if (cfiep->bm_sg) { + DWC_FREE(cfiep->bm_sg); + cfiep->bm_sg = NULL; + } + + if (cfiep->bm_align) { + DWC_FREE(cfiep->bm_align); + cfiep->bm_align = NULL; + } + + if (cfiep->bm_concat) { + if (NULL != cfiep->bm_concat->wTxBytes) { + DWC_FREE(cfiep->bm_concat->wTxBytes); + cfiep->bm_concat->wTxBytes = NULL; + } + DWC_FREE(cfiep->bm_concat); + cfiep->bm_concat = NULL; + } +} + +/** + * This function initializes the default values of the features + * for a specific endpoint and should be called only once when + * the EP is enabled first time. + */ +static int cfi_ep_init_defaults(struct dwc_otg_pcd *pcd, cfi_ep_t * cfiep) +{ + int retval = 0; + + cfiep->bm_sg = DWC_ALLOC(sizeof(ddma_sg_buffer_setup_t)); + if (NULL == cfiep->bm_sg) { + CFI_INFO("Failed to allocate memory for SG feature value\n"); + return -DWC_E_NO_MEMORY; + } + dwc_memset(cfiep->bm_sg, 0, sizeof(ddma_sg_buffer_setup_t)); + + /* For the Concatenation feature's default value we do not allocate + * memory for the wTxBytes field - it will be done in the set_feature_value + * request handler. + */ + cfiep->bm_concat = DWC_ALLOC(sizeof(ddma_concat_buffer_setup_t)); + if (NULL == cfiep->bm_concat) { + CFI_INFO + ("Failed to allocate memory for CONCATENATION feature value\n"); + DWC_FREE(cfiep->bm_sg); + return -DWC_E_NO_MEMORY; + } + dwc_memset(cfiep->bm_concat, 0, sizeof(ddma_concat_buffer_setup_t)); + + cfiep->bm_align = DWC_ALLOC(sizeof(ddma_align_buffer_setup_t)); + if (NULL == cfiep->bm_align) { + CFI_INFO + ("Failed to allocate memory for Alignment feature value\n"); + DWC_FREE(cfiep->bm_sg); + DWC_FREE(cfiep->bm_concat); + return -DWC_E_NO_MEMORY; + } + dwc_memset(cfiep->bm_align, 0, sizeof(ddma_align_buffer_setup_t)); + + return retval; +} + +/** + * The callback function that notifies the CFI on the activation of + * an endpoint in the PCD. The following steps are done in this function: + * + * Create a dynamically allocated cfi_ep_t object (a CFI wrapper to the PCD's + * active endpoint) + * Create MAX_DMA_DESCS_PER_EP count DMA Descriptors for the EP + * Set the Buffer Mode to standard + * Initialize the default values for all EP modes (SG, Circular, Concat, Align) + * Add the cfi_ep_t object to the list of active endpoints in the CFI object + */ +static int cfi_ep_enable(struct cfiobject *cfi, struct dwc_otg_pcd *pcd, + struct dwc_otg_pcd_ep *ep) +{ + cfi_ep_t *cfiep; + int retval = -DWC_E_NOT_SUPPORTED; + + CFI_INFO("%s: epname=%s; epnum=0x%02x\n", __func__, + "EP_" /*ep->ep.name */ , ep->desc->bEndpointAddress); + /* MAS - Check whether this endpoint already is in the list */ + cfiep = get_cfi_ep_by_pcd_ep(cfi, ep); + + if (NULL == cfiep) { + /* Allocate a cfi_ep_t object */ + cfiep = DWC_ALLOC(sizeof(cfi_ep_t)); + if (NULL == cfiep) { + CFI_INFO + ("Unable to allocate memory for <cfiep> in function %s\n", + __func__); + return -DWC_E_NO_MEMORY; + } + dwc_memset(cfiep, 0, sizeof(cfi_ep_t)); + + /* Save the dwc_otg_pcd_ep pointer in the cfiep object */ + cfiep->ep = ep; + + /* Allocate the DMA Descriptors chain of MAX_DMA_DESCS_PER_EP count */ + ep->dwc_ep.descs = + DWC_DMA_ALLOC(MAX_DMA_DESCS_PER_EP * + sizeof(dwc_otg_dma_desc_t), + &ep->dwc_ep.descs_dma_addr); + + if (NULL == ep->dwc_ep.descs) { + DWC_FREE(cfiep); + return -DWC_E_NO_MEMORY; + } + + DWC_LIST_INIT(&cfiep->lh); + + /* Set the buffer mode to BM_STANDARD. It will be modified + * when building descriptors for a specific buffer mode */ + ep->dwc_ep.buff_mode = BM_STANDARD; + + /* Create and initialize the default values for this EP's Buffer modes */ + if ((retval = cfi_ep_init_defaults(pcd, cfiep)) < 0) + return retval; + + /* Add the cfi_ep_t object to the CFI object's list of active endpoints */ + DWC_LIST_INSERT_TAIL(&cfi->active_eps, &cfiep->lh); + retval = 0; + } else { /* The sought EP already is in the list */ + CFI_INFO("%s: The sought EP already is in the list\n", + __func__); + } + + return retval; +} + +/** + * This function is called when the data stage of a 3-stage Control Write request + * is complete. + * + */ +static int cfi_ctrl_write_complete(struct cfiobject *cfi, + struct dwc_otg_pcd *pcd) +{ + uint32_t addr, reg_value; + uint16_t wIndex, wValue; + uint8_t bRequest; + uint8_t *buf = cfi->buf_out.buf; + //struct usb_ctrlrequest *ctrl_req = &cfi->ctrl_req_saved; + struct cfi_usb_ctrlrequest *ctrl_req = &cfi->ctrl_req; + int retval = -DWC_E_NOT_SUPPORTED; + + CFI_INFO("%s\n", __func__); + + bRequest = ctrl_req->bRequest; + wIndex = DWC_CONSTANT_CPU_TO_LE16(ctrl_req->wIndex); + wValue = DWC_CONSTANT_CPU_TO_LE16(ctrl_req->wValue); + + /* + * Save the pointer to the data stage in the ctrl_req's <data> field. + * The request should be already saved in the command stage by now. + */ + ctrl_req->data = cfi->buf_out.buf; + cfi->need_status_in_complete = 0; + cfi->need_gadget_att = 0; + + switch (bRequest) { + case VEN_CORE_WRITE_REGISTER: + /* The buffer contains raw data of the new value for the register */ + reg_value = *((uint32_t *) buf); + if (wValue == 0) { + addr = 0; + //addr = (uint32_t) pcd->otg_dev->os_dep.base; + addr += wIndex; + } else { + addr = (wValue << 16) | wIndex; + } + + //writel(reg_value, addr); + + retval = 0; + cfi->need_status_in_complete = 1; + break; + + case VEN_CORE_SET_FEATURE: + /* The buffer contains raw data of the new value of the feature */ + retval = cfi_set_feature_value(pcd); + if (retval < 0) + return retval; + + cfi->need_status_in_complete = 1; + break; + + default: + break; + } + + return retval; +} + +/** + * This function builds the DMA descriptors for the SG buffer mode. + */ +static void cfi_build_sg_descs(struct cfiobject *cfi, cfi_ep_t * cfiep, + dwc_otg_pcd_request_t * req) +{ + struct dwc_otg_pcd_ep *ep = cfiep->ep; + ddma_sg_buffer_setup_t *sgval = cfiep->bm_sg; + struct dwc_otg_dma_desc *desc = cfiep->ep->dwc_ep.descs; + struct dwc_otg_dma_desc *desc_last = cfiep->ep->dwc_ep.descs; + dma_addr_t buff_addr = req->dma; + int i; + uint32_t txsize, off; + + txsize = sgval->wSize; + off = sgval->bOffset; + +// CFI_INFO("%s: %s TXSIZE=0x%08x; OFFSET=0x%08x\n", +// __func__, cfiep->ep->ep.name, txsize, off); + + for (i = 0; i < sgval->bCount; i++) { + desc->status.b.bs = BS_HOST_BUSY; + desc->buf = buff_addr; + desc->status.b.l = 0; + desc->status.b.ioc = 0; + desc->status.b.sp = 0; + desc->status.b.bytes = txsize; + desc->status.b.bs = BS_HOST_READY; + + /* Set the next address of the buffer */ + buff_addr += txsize + off; + desc_last = desc; + desc++; + } + + /* Set the last, ioc and sp bits on the Last DMA Descriptor */ + desc_last->status.b.l = 1; + desc_last->status.b.ioc = 1; + desc_last->status.b.sp = ep->dwc_ep.sent_zlp; + /* Save the last DMA descriptor pointer */ + cfiep->dma_desc_last = desc_last; + cfiep->desc_count = sgval->bCount; +} + +/** + * This function builds the DMA descriptors for the Concatenation buffer mode. + */ +static void cfi_build_concat_descs(struct cfiobject *cfi, cfi_ep_t * cfiep, + dwc_otg_pcd_request_t * req) +{ + struct dwc_otg_pcd_ep *ep = cfiep->ep; + ddma_concat_buffer_setup_t *concatval = cfiep->bm_concat; + struct dwc_otg_dma_desc *desc = cfiep->ep->dwc_ep.descs; + struct dwc_otg_dma_desc *desc_last = cfiep->ep->dwc_ep.descs; + dma_addr_t buff_addr = req->dma; + int i; + uint16_t *txsize; + + txsize = concatval->wTxBytes; + + for (i = 0; i < concatval->hdr.bDescCount; i++) { + desc->buf = buff_addr; + desc->status.b.bs = BS_HOST_BUSY; + desc->status.b.l = 0; + desc->status.b.ioc = 0; + desc->status.b.sp = 0; + desc->status.b.bytes = *txsize; + desc->status.b.bs = BS_HOST_READY; + + txsize++; + /* Set the next address of the buffer */ + buff_addr += UGETW(ep->desc->wMaxPacketSize); + desc_last = desc; + desc++; + } + + /* Set the last, ioc and sp bits on the Last DMA Descriptor */ + desc_last->status.b.l = 1; + desc_last->status.b.ioc = 1; + desc_last->status.b.sp = ep->dwc_ep.sent_zlp; + cfiep->dma_desc_last = desc_last; + cfiep->desc_count = concatval->hdr.bDescCount; +} + +/** + * This function builds the DMA descriptors for the Circular buffer mode + */ +static void cfi_build_circ_descs(struct cfiobject *cfi, cfi_ep_t * cfiep, + dwc_otg_pcd_request_t * req) +{ + /* @todo: MAS - add implementation when this feature needs to be tested */ +} + +/** + * This function builds the DMA descriptors for the Alignment buffer mode + */ +static void cfi_build_align_descs(struct cfiobject *cfi, cfi_ep_t * cfiep, + dwc_otg_pcd_request_t * req) +{ + struct dwc_otg_pcd_ep *ep = cfiep->ep; + ddma_align_buffer_setup_t *alignval = cfiep->bm_align; + struct dwc_otg_dma_desc *desc = cfiep->ep->dwc_ep.descs; + dma_addr_t buff_addr = req->dma; + + desc->status.b.bs = BS_HOST_BUSY; + desc->status.b.l = 1; + desc->status.b.ioc = 1; + desc->status.b.sp = ep->dwc_ep.sent_zlp; + desc->status.b.bytes = req->length; + /* Adjust the buffer alignment */ + desc->buf = (buff_addr + alignval->bAlign); + desc->status.b.bs = BS_HOST_READY; + cfiep->dma_desc_last = desc; + cfiep->desc_count = 1; +} + +/** + * This function builds the DMA descriptors chain for different modes of the + * buffer setup of an endpoint. + */ +static void cfi_build_descriptors(struct cfiobject *cfi, + struct dwc_otg_pcd *pcd, + struct dwc_otg_pcd_ep *ep, + dwc_otg_pcd_request_t * req) +{ + cfi_ep_t *cfiep; + + /* Get the cfiep by the dwc_otg_pcd_ep */ + cfiep = get_cfi_ep_by_pcd_ep(cfi, ep); + if (NULL == cfiep) { + CFI_INFO("%s: Unable to find a matching active endpoint\n", + __func__); + return; + } + + cfiep->xfer_len = req->length; + + /* Iterate through all the DMA descriptors */ + switch (cfiep->ep->dwc_ep.buff_mode) { + case BM_SG: + cfi_build_sg_descs(cfi, cfiep, req); + break; + + case BM_CONCAT: + cfi_build_concat_descs(cfi, cfiep, req); + break; + + case BM_CIRCULAR: + cfi_build_circ_descs(cfi, cfiep, req); + break; + + case BM_ALIGN: + cfi_build_align_descs(cfi, cfiep, req); + break; + + default: + break; + } +} + +/** + * Allocate DMA buffer for different Buffer modes. + */ +static void *cfi_ep_alloc_buf(struct cfiobject *cfi, struct dwc_otg_pcd *pcd, + struct dwc_otg_pcd_ep *ep, dma_addr_t * dma, + unsigned size, gfp_t flags) +{ + return DWC_DMA_ALLOC(size, dma); +} + +/** + * This function initializes the CFI object. + */ +int init_cfi(cfiobject_t * cfiobj) +{ + CFI_INFO("%s\n", __func__); + + /* Allocate a buffer for IN XFERs */ + cfiobj->buf_in.buf = + DWC_DMA_ALLOC(CFI_IN_BUF_LEN, &cfiobj->buf_in.addr); + if (NULL == cfiobj->buf_in.buf) { + CFI_INFO("Unable to allocate buffer for INs\n"); + return -DWC_E_NO_MEMORY; + } + + /* Allocate a buffer for OUT XFERs */ + cfiobj->buf_out.buf = + DWC_DMA_ALLOC(CFI_OUT_BUF_LEN, &cfiobj->buf_out.addr); + if (NULL == cfiobj->buf_out.buf) { + CFI_INFO("Unable to allocate buffer for OUT\n"); + return -DWC_E_NO_MEMORY; + } + + /* Initialize the callback function pointers */ + cfiobj->ops.release = cfi_release; + cfiobj->ops.ep_enable = cfi_ep_enable; + cfiobj->ops.ctrl_write_complete = cfi_ctrl_write_complete; + cfiobj->ops.build_descriptors = cfi_build_descriptors; + cfiobj->ops.ep_alloc_buf = cfi_ep_alloc_buf; + + /* Initialize the list of active endpoints in the CFI object */ + DWC_LIST_INIT(&cfiobj->active_eps); + + return 0; +} + +/** + * This function reads the required feature's current value into the buffer + * + * @retval: Returns negative as error, or the data length of the feature + */ +static int cfi_get_feature_value(uint8_t * buf, uint16_t buflen, + struct dwc_otg_pcd *pcd, + struct cfi_usb_ctrlrequest *ctrl_req) +{ + int retval = -DWC_E_NOT_SUPPORTED; + struct dwc_otg_core_if *coreif = GET_CORE_IF(pcd); + uint16_t dfifo, rxfifo, txfifo; + + switch (ctrl_req->wIndex) { + /* Whether the DDMA is enabled or not */ + case FT_ID_DMA_MODE: + *buf = (coreif->dma_enable && coreif->dma_desc_enable) ? 1 : 0; + retval = 1; + break; + + case FT_ID_DMA_BUFFER_SETUP: + retval = cfi_ep_get_sg_val(buf, pcd, ctrl_req); + break; + + case FT_ID_DMA_BUFF_ALIGN: + retval = cfi_ep_get_align_val(buf, pcd, ctrl_req); + break; + + case FT_ID_DMA_CONCAT_SETUP: + retval = cfi_ep_get_concat_val(buf, pcd, ctrl_req); + break; + + case FT_ID_DMA_CIRCULAR: + CFI_INFO("GetFeature value (FT_ID_DMA_CIRCULAR)\n"); + break; + + case FT_ID_THRESHOLD_SETUP: + CFI_INFO("GetFeature value (FT_ID_THRESHOLD_SETUP)\n"); + break; + + case FT_ID_DFIFO_DEPTH: + dfifo = get_dfifo_size(coreif); + *((uint16_t *) buf) = dfifo; + retval = sizeof(uint16_t); + break; + + case FT_ID_TX_FIFO_DEPTH: + retval = get_txfifo_size(pcd, ctrl_req->wValue); + if (retval >= 0) { + txfifo = retval; + *((uint16_t *) buf) = txfifo; + retval = sizeof(uint16_t); + } + break; + + case FT_ID_RX_FIFO_DEPTH: + retval = get_rxfifo_size(coreif, ctrl_req->wValue); + if (retval >= 0) { + rxfifo = retval; + *((uint16_t *) buf) = rxfifo; + retval = sizeof(uint16_t); + } + break; + } + + return retval; +} + +/** + * This function resets the SG for the specified EP to its default value + */ +static int cfi_reset_sg_val(cfi_ep_t * cfiep) +{ + dwc_memset(cfiep->bm_sg, 0, sizeof(ddma_sg_buffer_setup_t)); + return 0; +} + +/** + * This function resets the Alignment for the specified EP to its default value + */ +static int cfi_reset_align_val(cfi_ep_t * cfiep) +{ + dwc_memset(cfiep->bm_sg, 0, sizeof(ddma_sg_buffer_setup_t)); + return 0; +} + +/** + * This function resets the Concatenation for the specified EP to its default value + * This function will also set the value of the wTxBytes field to NULL after + * freeing the memory previously allocated for this field. + */ +static int cfi_reset_concat_val(cfi_ep_t * cfiep) +{ + /* First we need to free the wTxBytes field */ + if (cfiep->bm_concat->wTxBytes) { + DWC_FREE(cfiep->bm_concat->wTxBytes); + cfiep->bm_concat->wTxBytes = NULL; + } + + dwc_memset(cfiep->bm_concat, 0, sizeof(ddma_concat_buffer_setup_t)); + return 0; +} + +/** + * This function resets all the buffer setups of the specified endpoint + */ +static int cfi_ep_reset_all_setup_vals(cfi_ep_t * cfiep) +{ + cfi_reset_sg_val(cfiep); + cfi_reset_align_val(cfiep); + cfi_reset_concat_val(cfiep); + return 0; +} + +static int cfi_handle_reset_fifo_val(struct dwc_otg_pcd *pcd, uint8_t ep_addr, + uint8_t rx_rst, uint8_t tx_rst) +{ + int retval = -DWC_E_INVALID; + uint16_t tx_siz[15]; + uint16_t rx_siz = 0; + dwc_otg_pcd_ep_t *ep = NULL; + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + dwc_otg_core_params_t *params = GET_CORE_IF(pcd)->core_params; + + if (rx_rst) { + rx_siz = params->dev_rx_fifo_size; + params->dev_rx_fifo_size = GET_CORE_IF(pcd)->init_rxfsiz; + } + + if (tx_rst) { + if (ep_addr == 0) { + int i; + + for (i = 0; i < core_if->hwcfg4.b.num_in_eps; i++) { + tx_siz[i] = + core_if->core_params->dev_tx_fifo_size[i]; + core_if->core_params->dev_tx_fifo_size[i] = + core_if->init_txfsiz[i]; + } + } else { + + ep = get_ep_by_addr(pcd, ep_addr); + + if (NULL == ep) { + CFI_INFO + ("%s: Unable to get the endpoint addr=0x%02x\n", + __func__, ep_addr); + return -DWC_E_INVALID; + } + + tx_siz[0] = + params->dev_tx_fifo_size[ep->dwc_ep.tx_fifo_num - + 1]; + params->dev_tx_fifo_size[ep->dwc_ep.tx_fifo_num - 1] = + GET_CORE_IF(pcd)->init_txfsiz[ep-> + dwc_ep.tx_fifo_num - + 1]; + } + } + + if (resize_fifos(GET_CORE_IF(pcd))) { + retval = 0; + } else { + CFI_INFO + ("%s: Error resetting the feature Reset All(FIFO size)\n", + __func__); + if (rx_rst) { + params->dev_rx_fifo_size = rx_siz; + } + + if (tx_rst) { + if (ep_addr == 0) { + int i; + for (i = 0; i < core_if->hwcfg4.b.num_in_eps; + i++) { + core_if-> + core_params->dev_tx_fifo_size[i] = + tx_siz[i]; + } + } else { + params->dev_tx_fifo_size[ep-> + dwc_ep.tx_fifo_num - + 1] = tx_siz[0]; + } + } + retval = -DWC_E_INVALID; + } + return retval; +} + +static int cfi_handle_reset_all(struct dwc_otg_pcd *pcd, uint8_t addr) +{ + int retval = 0; + cfi_ep_t *cfiep; + cfiobject_t *cfi = pcd->cfi; + dwc_list_link_t *tmp; + + retval = cfi_handle_reset_fifo_val(pcd, addr, 1, 1); + if (retval < 0) { + return retval; + } + + /* If the EP address is known then reset the features for only that EP */ + if (addr) { + cfiep = get_cfi_ep_by_addr(pcd->cfi, addr); + if (NULL == cfiep) { + CFI_INFO("%s: Error getting the EP address 0x%02x\n", + __func__, addr); + return -DWC_E_INVALID; + } + retval = cfi_ep_reset_all_setup_vals(cfiep); + cfiep->ep->dwc_ep.buff_mode = BM_STANDARD; + } + /* Otherwise (wValue == 0), reset all features of all EP's */ + else { + /* Traverse all the active EP's and reset the feature(s) value(s) */ + //list_for_each_entry(cfiep, &cfi->active_eps, lh) { + DWC_LIST_FOREACH(tmp, &cfi->active_eps) { + cfiep = DWC_LIST_ENTRY(tmp, struct cfi_ep, lh); + retval = cfi_ep_reset_all_setup_vals(cfiep); + cfiep->ep->dwc_ep.buff_mode = BM_STANDARD; + if (retval < 0) { + CFI_INFO + ("%s: Error resetting the feature Reset All\n", + __func__); + return retval; + } + } + } + return retval; +} + +static int cfi_handle_reset_dma_buff_setup(struct dwc_otg_pcd *pcd, + uint8_t addr) +{ + int retval = 0; + cfi_ep_t *cfiep; + cfiobject_t *cfi = pcd->cfi; + dwc_list_link_t *tmp; + + /* If the EP address is known then reset the features for only that EP */ + if (addr) { + cfiep = get_cfi_ep_by_addr(pcd->cfi, addr); + if (NULL == cfiep) { + CFI_INFO("%s: Error getting the EP address 0x%02x\n", + __func__, addr); + return -DWC_E_INVALID; + } + retval = cfi_reset_sg_val(cfiep); + } + /* Otherwise (wValue == 0), reset all features of all EP's */ + else { + /* Traverse all the active EP's and reset the feature(s) value(s) */ + //list_for_each_entry(cfiep, &cfi->active_eps, lh) { + DWC_LIST_FOREACH(tmp, &cfi->active_eps) { + cfiep = DWC_LIST_ENTRY(tmp, struct cfi_ep, lh); + retval = cfi_reset_sg_val(cfiep); + if (retval < 0) { + CFI_INFO + ("%s: Error resetting the feature Buffer Setup\n", + __func__); + return retval; + } + } + } + return retval; +} + +static int cfi_handle_reset_concat_val(struct dwc_otg_pcd *pcd, uint8_t addr) +{ + int retval = 0; + cfi_ep_t *cfiep; + cfiobject_t *cfi = pcd->cfi; + dwc_list_link_t *tmp; + + /* If the EP address is known then reset the features for only that EP */ + if (addr) { + cfiep = get_cfi_ep_by_addr(pcd->cfi, addr); + if (NULL == cfiep) { + CFI_INFO("%s: Error getting the EP address 0x%02x\n", + __func__, addr); + return -DWC_E_INVALID; + } + retval = cfi_reset_concat_val(cfiep); + } + /* Otherwise (wValue == 0), reset all features of all EP's */ + else { + /* Traverse all the active EP's and reset the feature(s) value(s) */ + //list_for_each_entry(cfiep, &cfi->active_eps, lh) { + DWC_LIST_FOREACH(tmp, &cfi->active_eps) { + cfiep = DWC_LIST_ENTRY(tmp, struct cfi_ep, lh); + retval = cfi_reset_concat_val(cfiep); + if (retval < 0) { + CFI_INFO + ("%s: Error resetting the feature Concatenation Value\n", + __func__); + return retval; + } + } + } + return retval; +} + +static int cfi_handle_reset_align_val(struct dwc_otg_pcd *pcd, uint8_t addr) +{ + int retval = 0; + cfi_ep_t *cfiep; + cfiobject_t *cfi = pcd->cfi; + dwc_list_link_t *tmp; + + /* If the EP address is known then reset the features for only that EP */ + if (addr) { + cfiep = get_cfi_ep_by_addr(pcd->cfi, addr); + if (NULL == cfiep) { + CFI_INFO("%s: Error getting the EP address 0x%02x\n", + __func__, addr); + return -DWC_E_INVALID; + } + retval = cfi_reset_align_val(cfiep); + } + /* Otherwise (wValue == 0), reset all features of all EP's */ + else { + /* Traverse all the active EP's and reset the feature(s) value(s) */ + //list_for_each_entry(cfiep, &cfi->active_eps, lh) { + DWC_LIST_FOREACH(tmp, &cfi->active_eps) { + cfiep = DWC_LIST_ENTRY(tmp, struct cfi_ep, lh); + retval = cfi_reset_align_val(cfiep); + if (retval < 0) { + CFI_INFO + ("%s: Error resetting the feature Aliignment Value\n", + __func__); + return retval; + } + } + } + return retval; + +} + +static int cfi_preproc_reset(struct dwc_otg_pcd *pcd, + struct cfi_usb_ctrlrequest *req) +{ + int retval = 0; + + switch (req->wIndex) { + case 0: + /* Reset all features */ + retval = cfi_handle_reset_all(pcd, req->wValue & 0xff); + break; + + case FT_ID_DMA_BUFFER_SETUP: + /* Reset the SG buffer setup */ + retval = + cfi_handle_reset_dma_buff_setup(pcd, req->wValue & 0xff); + break; + + case FT_ID_DMA_CONCAT_SETUP: + /* Reset the Concatenation buffer setup */ + retval = cfi_handle_reset_concat_val(pcd, req->wValue & 0xff); + break; + + case FT_ID_DMA_BUFF_ALIGN: + /* Reset the Alignment buffer setup */ + retval = cfi_handle_reset_align_val(pcd, req->wValue & 0xff); + break; + + case FT_ID_TX_FIFO_DEPTH: + retval = + cfi_handle_reset_fifo_val(pcd, req->wValue & 0xff, 0, 1); + pcd->cfi->need_gadget_att = 0; + break; + + case FT_ID_RX_FIFO_DEPTH: + retval = cfi_handle_reset_fifo_val(pcd, 0, 1, 0); + pcd->cfi->need_gadget_att = 0; + break; + default: + break; + } + return retval; +} + +/** + * This function sets a new value for the SG buffer setup. + */ +static int cfi_ep_set_sg_val(uint8_t * buf, struct dwc_otg_pcd *pcd) +{ + uint8_t inaddr, outaddr; + cfi_ep_t *epin, *epout; + ddma_sg_buffer_setup_t *psgval; + uint32_t desccount, size; + + CFI_INFO("%s\n", __func__); + + psgval = (ddma_sg_buffer_setup_t *) buf; + desccount = (uint32_t) psgval->bCount; + size = (uint32_t) psgval->wSize; + + /* Check the DMA descriptor count */ + if ((desccount > MAX_DMA_DESCS_PER_EP) || (desccount == 0)) { + CFI_INFO + ("%s: The count of DMA Descriptors should be between 1 and %d\n", + __func__, MAX_DMA_DESCS_PER_EP); + return -DWC_E_INVALID; + } + + /* Check the DMA descriptor count */ + + if (size == 0) { + + CFI_INFO("%s: The transfer size should be at least 1 byte\n", + __func__); + + return -DWC_E_INVALID; + + } + + inaddr = psgval->bInEndpointAddress; + outaddr = psgval->bOutEndpointAddress; + + epin = get_cfi_ep_by_addr(pcd->cfi, inaddr); + epout = get_cfi_ep_by_addr(pcd->cfi, outaddr); + + if (NULL == epin || NULL == epout) { + CFI_INFO + ("%s: Unable to get the endpoints inaddr=0x%02x outaddr=0x%02x\n", + __func__, inaddr, outaddr); + return -DWC_E_INVALID; + } + + epin->ep->dwc_ep.buff_mode = BM_SG; + dwc_memcpy(epin->bm_sg, psgval, sizeof(ddma_sg_buffer_setup_t)); + + epout->ep->dwc_ep.buff_mode = BM_SG; + dwc_memcpy(epout->bm_sg, psgval, sizeof(ddma_sg_buffer_setup_t)); + + return 0; +} + +/** + * This function sets a new value for the buffer Alignment setup. + */ +static int cfi_ep_set_alignment_val(uint8_t * buf, struct dwc_otg_pcd *pcd) +{ + cfi_ep_t *ep; + uint8_t addr; + ddma_align_buffer_setup_t *palignval; + + palignval = (ddma_align_buffer_setup_t *) buf; + addr = palignval->bEndpointAddress; + + ep = get_cfi_ep_by_addr(pcd->cfi, addr); + + if (NULL == ep) { + CFI_INFO("%s: Unable to get the endpoint addr=0x%02x\n", + __func__, addr); + return -DWC_E_INVALID; + } + + ep->ep->dwc_ep.buff_mode = BM_ALIGN; + dwc_memcpy(ep->bm_align, palignval, sizeof(ddma_align_buffer_setup_t)); + + return 0; +} + +/** + * This function sets a new value for the Concatenation buffer setup. + */ +static int cfi_ep_set_concat_val(uint8_t * buf, struct dwc_otg_pcd *pcd) +{ + uint8_t addr; + cfi_ep_t *ep; + struct _ddma_concat_buffer_setup_hdr *pConcatValHdr; + uint16_t *pVals; + uint32_t desccount; + int i; + uint16_t mps; + + pConcatValHdr = (struct _ddma_concat_buffer_setup_hdr *)buf; + desccount = (uint32_t) pConcatValHdr->bDescCount; + pVals = (uint16_t *) (buf + BS_CONCAT_VAL_HDR_LEN); + + /* Check the DMA descriptor count */ + if (desccount > MAX_DMA_DESCS_PER_EP) { + CFI_INFO("%s: Maximum DMA Descriptor count should be %d\n", + __func__, MAX_DMA_DESCS_PER_EP); + return -DWC_E_INVALID; + } + + addr = pConcatValHdr->bEndpointAddress; + ep = get_cfi_ep_by_addr(pcd->cfi, addr); + if (NULL == ep) { + CFI_INFO("%s: Unable to get the endpoint addr=0x%02x\n", + __func__, addr); + return -DWC_E_INVALID; + } + + mps = UGETW(ep->ep->desc->wMaxPacketSize); + +#if 0 + for (i = 0; i < desccount; i++) { + CFI_INFO("%s: wTxSize[%d]=0x%04x\n", __func__, i, pVals[i]); + } + CFI_INFO("%s: epname=%s; mps=%d\n", __func__, ep->ep->ep.name, mps); +#endif + + /* Check the wTxSizes to be less than or equal to the mps */ + for (i = 0; i < desccount; i++) { + if (pVals[i] > mps) { + CFI_INFO + ("%s: ERROR - the wTxSize[%d] should be <= MPS (wTxSize=%d)\n", + __func__, i, pVals[i]); + return -DWC_E_INVALID; + } + } + + ep->ep->dwc_ep.buff_mode = BM_CONCAT; + dwc_memcpy(ep->bm_concat, pConcatValHdr, BS_CONCAT_VAL_HDR_LEN); + + /* Free the previously allocated storage for the wTxBytes */ + if (ep->bm_concat->wTxBytes) { + DWC_FREE(ep->bm_concat->wTxBytes); + } + + /* Allocate a new storage for the wTxBytes field */ + ep->bm_concat->wTxBytes = + DWC_ALLOC(sizeof(uint16_t) * pConcatValHdr->bDescCount); + if (NULL == ep->bm_concat->wTxBytes) { + CFI_INFO("%s: Unable to allocate memory\n", __func__); + return -DWC_E_NO_MEMORY; + } + + /* Copy the new values into the wTxBytes filed */ + dwc_memcpy(ep->bm_concat->wTxBytes, buf + BS_CONCAT_VAL_HDR_LEN, + sizeof(uint16_t) * pConcatValHdr->bDescCount); + + return 0; +} + +/** + * This function calculates the total of all FIFO sizes + * + * @param core_if Programming view of DWC_otg controller + * + * @return The total of data FIFO sizes. + * + */ +static uint16_t get_dfifo_size(dwc_otg_core_if_t * core_if) +{ + dwc_otg_core_params_t *params = core_if->core_params; + uint16_t dfifo_total = 0; + int i; + + /* The shared RxFIFO size */ + dfifo_total = + params->dev_rx_fifo_size + params->dev_nperio_tx_fifo_size; + + /* Add up each TxFIFO size to the total */ + for (i = 0; i < core_if->hwcfg4.b.num_in_eps; i++) { + dfifo_total += params->dev_tx_fifo_size[i]; + } + + return dfifo_total; +} + +/** + * This function returns Rx FIFO size + * + * @param core_if Programming view of DWC_otg controller + * + * @return The total of data FIFO sizes. + * + */ +static int32_t get_rxfifo_size(dwc_otg_core_if_t * core_if, uint16_t wValue) +{ + switch (wValue >> 8) { + case 0: + return (core_if->pwron_rxfsiz < + 32768) ? core_if->pwron_rxfsiz : 32768; + break; + case 1: + return core_if->core_params->dev_rx_fifo_size; + break; + default: + return -DWC_E_INVALID; + break; + } +} + +/** + * This function returns Tx FIFO size for IN EP + * + * @param core_if Programming view of DWC_otg controller + * + * @return The total of data FIFO sizes. + * + */ +static int32_t get_txfifo_size(struct dwc_otg_pcd *pcd, uint16_t wValue) +{ + dwc_otg_pcd_ep_t *ep; + + ep = get_ep_by_addr(pcd, wValue & 0xff); + + if (NULL == ep) { + CFI_INFO("%s: Unable to get the endpoint addr=0x%02x\n", + __func__, wValue & 0xff); + return -DWC_E_INVALID; + } + + if (!ep->dwc_ep.is_in) { + CFI_INFO + ("%s: No Tx FIFO assingned to the Out endpoint addr=0x%02x\n", + __func__, wValue & 0xff); + return -DWC_E_INVALID; + } + + switch (wValue >> 8) { + case 0: + return (GET_CORE_IF(pcd)->pwron_txfsiz + [ep->dwc_ep.tx_fifo_num - 1] < + 768) ? GET_CORE_IF(pcd)->pwron_txfsiz[ep-> + dwc_ep.tx_fifo_num + - 1] : 32768; + break; + case 1: + return GET_CORE_IF(pcd)->core_params-> + dev_tx_fifo_size[ep->dwc_ep.num - 1]; + break; + default: + return -DWC_E_INVALID; + break; + } +} + +/** + * This function checks if the submitted combination of + * device mode FIFO sizes is possible or not. + * + * @param core_if Programming view of DWC_otg controller + * + * @return 1 if possible, 0 otherwise. + * + */ +static uint8_t check_fifo_sizes(dwc_otg_core_if_t * core_if) +{ + uint16_t dfifo_actual = 0; + dwc_otg_core_params_t *params = core_if->core_params; + uint16_t start_addr = 0; + int i; + + dfifo_actual = + params->dev_rx_fifo_size + params->dev_nperio_tx_fifo_size; + + for (i = 0; i < core_if->hwcfg4.b.num_in_eps; i++) { + dfifo_actual += params->dev_tx_fifo_size[i]; + } + + if (dfifo_actual > core_if->total_fifo_size) { + return 0; + } + + if (params->dev_rx_fifo_size > 32768 || params->dev_rx_fifo_size < 16) + return 0; + + if (params->dev_nperio_tx_fifo_size > 32768 + || params->dev_nperio_tx_fifo_size < 16) + return 0; + + for (i = 0; i < core_if->hwcfg4.b.num_in_eps; i++) { + + if (params->dev_tx_fifo_size[i] > 768 + || params->dev_tx_fifo_size[i] < 4) + return 0; + } + + if (params->dev_rx_fifo_size > core_if->pwron_rxfsiz) + return 0; + start_addr = params->dev_rx_fifo_size; + + if (params->dev_nperio_tx_fifo_size > core_if->pwron_gnptxfsiz) + return 0; + start_addr += params->dev_nperio_tx_fifo_size; + + for (i = 0; i < core_if->hwcfg4.b.num_in_eps; i++) { + + if (params->dev_tx_fifo_size[i] > core_if->pwron_txfsiz[i]) + return 0; + start_addr += params->dev_tx_fifo_size[i]; + } + + return 1; +} + +/** + * This function resizes Device mode FIFOs + * + * @param core_if Programming view of DWC_otg controller + * + * @return 1 if successful, 0 otherwise + * + */ +static uint8_t resize_fifos(dwc_otg_core_if_t * core_if) +{ + int i = 0; + dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs; + dwc_otg_core_params_t *params = core_if->core_params; + uint32_t rx_fifo_size; + fifosize_data_t nptxfifosize; + fifosize_data_t txfifosize[15]; + + uint32_t rx_fsz_bak; + uint32_t nptxfsz_bak; + uint32_t txfsz_bak[15]; + + uint16_t start_address; + uint8_t retval = 1; + + if (!check_fifo_sizes(core_if)) { + return 0; + } + + /* Configure data FIFO sizes */ + if (core_if->hwcfg2.b.dynamic_fifo && params->enable_dynamic_fifo) { + rx_fsz_bak = DWC_READ_REG32(&global_regs->grxfsiz); + rx_fifo_size = params->dev_rx_fifo_size; + DWC_WRITE_REG32(&global_regs->grxfsiz, rx_fifo_size); + + /* + * Tx FIFOs These FIFOs are numbered from 1 to 15. + * Indexes of the FIFO size module parameters in the + * dev_tx_fifo_size array and the FIFO size registers in + * the dtxfsiz array run from 0 to 14. + */ + + /* Non-periodic Tx FIFO */ + nptxfsz_bak = DWC_READ_REG32(&global_regs->gnptxfsiz); + nptxfifosize.b.depth = params->dev_nperio_tx_fifo_size; + start_address = params->dev_rx_fifo_size; + nptxfifosize.b.startaddr = start_address; + + DWC_WRITE_REG32(&global_regs->gnptxfsiz, nptxfifosize.d32); + + start_address += nptxfifosize.b.depth; + + for (i = 0; i < core_if->hwcfg4.b.num_in_eps; i++) { + txfsz_bak[i] = DWC_READ_REG32(&global_regs->dtxfsiz[i]); + + txfifosize[i].b.depth = params->dev_tx_fifo_size[i]; + txfifosize[i].b.startaddr = start_address; + DWC_WRITE_REG32(&global_regs->dtxfsiz[i], + txfifosize[i].d32); + + start_address += txfifosize[i].b.depth; + } + + /** Check if register values are set correctly */ + if (rx_fifo_size != DWC_READ_REG32(&global_regs->grxfsiz)) { + retval = 0; + } + + if (nptxfifosize.d32 != DWC_READ_REG32(&global_regs->gnptxfsiz)) { + retval = 0; + } + + for (i = 0; i < core_if->hwcfg4.b.num_in_eps; i++) { + if (txfifosize[i].d32 != + DWC_READ_REG32(&global_regs->dtxfsiz[i])) { + retval = 0; + } + } + + /** If register values are not set correctly, reset old values */ + if (retval == 0) { + DWC_WRITE_REG32(&global_regs->grxfsiz, rx_fsz_bak); + + /* Non-periodic Tx FIFO */ + DWC_WRITE_REG32(&global_regs->gnptxfsiz, nptxfsz_bak); + + for (i = 0; i < core_if->hwcfg4.b.num_in_eps; i++) { + DWC_WRITE_REG32(&global_regs->dtxfsiz[i], + txfsz_bak[i]); + } + } + } else { + return 0; + } + + /* Flush the FIFOs */ + dwc_otg_flush_tx_fifo(core_if, 0x10); /* all Tx FIFOs */ + dwc_otg_flush_rx_fifo(core_if); + + return retval; +} + +/** + * This function sets a new value for the buffer Alignment setup. + */ +static int cfi_ep_set_tx_fifo_val(uint8_t * buf, dwc_otg_pcd_t * pcd) +{ + int retval; + uint32_t fsiz; + uint16_t size; + uint16_t ep_addr; + dwc_otg_pcd_ep_t *ep; + dwc_otg_core_params_t *params = GET_CORE_IF(pcd)->core_params; + tx_fifo_size_setup_t *ptxfifoval; + + ptxfifoval = (tx_fifo_size_setup_t *) buf; + ep_addr = ptxfifoval->bEndpointAddress; + size = ptxfifoval->wDepth; + + ep = get_ep_by_addr(pcd, ep_addr); + + CFI_INFO + ("%s: Set Tx FIFO size: endpoint addr=0x%02x, depth=%d, FIFO Num=%d\n", + __func__, ep_addr, size, ep->dwc_ep.tx_fifo_num); + + if (NULL == ep) { + CFI_INFO("%s: Unable to get the endpoint addr=0x%02x\n", + __func__, ep_addr); + return -DWC_E_INVALID; + } + + fsiz = params->dev_tx_fifo_size[ep->dwc_ep.tx_fifo_num - 1]; + params->dev_tx_fifo_size[ep->dwc_ep.tx_fifo_num - 1] = size; + + if (resize_fifos(GET_CORE_IF(pcd))) { + retval = 0; + } else { + CFI_INFO + ("%s: Error setting the feature Tx FIFO Size for EP%d\n", + __func__, ep_addr); + params->dev_tx_fifo_size[ep->dwc_ep.tx_fifo_num - 1] = fsiz; + retval = -DWC_E_INVALID; + } + + return retval; +} + +/** + * This function sets a new value for the buffer Alignment setup. + */ +static int cfi_set_rx_fifo_val(uint8_t * buf, dwc_otg_pcd_t * pcd) +{ + int retval; + uint32_t fsiz; + uint16_t size; + dwc_otg_core_params_t *params = GET_CORE_IF(pcd)->core_params; + rx_fifo_size_setup_t *prxfifoval; + + prxfifoval = (rx_fifo_size_setup_t *) buf; + size = prxfifoval->wDepth; + + fsiz = params->dev_rx_fifo_size; + params->dev_rx_fifo_size = size; + + if (resize_fifos(GET_CORE_IF(pcd))) { + retval = 0; + } else { + CFI_INFO("%s: Error setting the feature Rx FIFO Size\n", + __func__); + params->dev_rx_fifo_size = fsiz; + retval = -DWC_E_INVALID; + } + + return retval; +} + +/** + * This function reads the SG of an EP's buffer setup into the buffer buf + */ +static int cfi_ep_get_sg_val(uint8_t * buf, struct dwc_otg_pcd *pcd, + struct cfi_usb_ctrlrequest *req) +{ + int retval = -DWC_E_INVALID; + uint8_t addr; + cfi_ep_t *ep; + + /* The Low Byte of the wValue contains a non-zero address of the endpoint */ + addr = req->wValue & 0xFF; + if (addr == 0) /* The address should be non-zero */ + return retval; + + ep = get_cfi_ep_by_addr(pcd->cfi, addr); + if (NULL == ep) { + CFI_INFO("%s: Unable to get the endpoint address(0x%02x)\n", + __func__, addr); + return retval; + } + + dwc_memcpy(buf, ep->bm_sg, BS_SG_VAL_DESC_LEN); + retval = BS_SG_VAL_DESC_LEN; + return retval; +} + +/** + * This function reads the Concatenation value of an EP's buffer mode into + * the buffer buf + */ +static int cfi_ep_get_concat_val(uint8_t * buf, struct dwc_otg_pcd *pcd, + struct cfi_usb_ctrlrequest *req) +{ + int retval = -DWC_E_INVALID; + uint8_t addr; + cfi_ep_t *ep; + uint8_t desc_count; + + /* The Low Byte of the wValue contains a non-zero address of the endpoint */ + addr = req->wValue & 0xFF; + if (addr == 0) /* The address should be non-zero */ + return retval; + + ep = get_cfi_ep_by_addr(pcd->cfi, addr); + if (NULL == ep) { + CFI_INFO("%s: Unable to get the endpoint address(0x%02x)\n", + __func__, addr); + return retval; + } + + /* Copy the header to the buffer */ + dwc_memcpy(buf, ep->bm_concat, BS_CONCAT_VAL_HDR_LEN); + /* Advance the buffer pointer by the header size */ + buf += BS_CONCAT_VAL_HDR_LEN; + + desc_count = ep->bm_concat->hdr.bDescCount; + /* Copy alll the wTxBytes to the buffer */ + dwc_memcpy(buf, ep->bm_concat->wTxBytes, sizeof(uid16_t) * desc_count); + + retval = BS_CONCAT_VAL_HDR_LEN + sizeof(uid16_t) * desc_count; + return retval; +} + +/** + * This function reads the buffer Alignment value of an EP's buffer mode into + * the buffer buf + * + * @return The total number of bytes copied to the buffer or negative error code. + */ +static int cfi_ep_get_align_val(uint8_t * buf, struct dwc_otg_pcd *pcd, + struct cfi_usb_ctrlrequest *req) +{ + int retval = -DWC_E_INVALID; + uint8_t addr; + cfi_ep_t *ep; + + /* The Low Byte of the wValue contains a non-zero address of the endpoint */ + addr = req->wValue & 0xFF; + if (addr == 0) /* The address should be non-zero */ + return retval; + + ep = get_cfi_ep_by_addr(pcd->cfi, addr); + if (NULL == ep) { + CFI_INFO("%s: Unable to get the endpoint address(0x%02x)\n", + __func__, addr); + return retval; + } + + dwc_memcpy(buf, ep->bm_align, BS_ALIGN_VAL_HDR_LEN); + retval = BS_ALIGN_VAL_HDR_LEN; + + return retval; +} + +/** + * This function sets a new value for the specified feature + * + * @param pcd A pointer to the PCD object + * + * @return 0 if successful, negative error code otherwise to stall the DCE. + */ +static int cfi_set_feature_value(struct dwc_otg_pcd *pcd) +{ + int retval = -DWC_E_NOT_SUPPORTED; + uint16_t wIndex, wValue; + uint8_t bRequest; + struct dwc_otg_core_if *coreif; + cfiobject_t *cfi = pcd->cfi; + struct cfi_usb_ctrlrequest *ctrl_req; + uint8_t *buf; + ctrl_req = &cfi->ctrl_req; + + buf = pcd->cfi->ctrl_req.data; + + coreif = GET_CORE_IF(pcd); + bRequest = ctrl_req->bRequest; + wIndex = DWC_CONSTANT_CPU_TO_LE16(ctrl_req->wIndex); + wValue = DWC_CONSTANT_CPU_TO_LE16(ctrl_req->wValue); + + /* See which feature is to be modified */ + switch (wIndex) { + case FT_ID_DMA_BUFFER_SETUP: + /* Modify the feature */ + if ((retval = cfi_ep_set_sg_val(buf, pcd)) < 0) + return retval; + + /* And send this request to the gadget */ + cfi->need_gadget_att = 1; + break; + + case FT_ID_DMA_BUFF_ALIGN: + if ((retval = cfi_ep_set_alignment_val(buf, pcd)) < 0) + return retval; + cfi->need_gadget_att = 1; + break; + + case FT_ID_DMA_CONCAT_SETUP: + /* Modify the feature */ + if ((retval = cfi_ep_set_concat_val(buf, pcd)) < 0) + return retval; + cfi->need_gadget_att = 1; + break; + + case FT_ID_DMA_CIRCULAR: + CFI_INFO("FT_ID_DMA_CIRCULAR\n"); + break; + + case FT_ID_THRESHOLD_SETUP: + CFI_INFO("FT_ID_THRESHOLD_SETUP\n"); + break; + + case FT_ID_DFIFO_DEPTH: + CFI_INFO("FT_ID_DFIFO_DEPTH\n"); + break; + + case FT_ID_TX_FIFO_DEPTH: + CFI_INFO("FT_ID_TX_FIFO_DEPTH\n"); + if ((retval = cfi_ep_set_tx_fifo_val(buf, pcd)) < 0) + return retval; + cfi->need_gadget_att = 0; + break; + + case FT_ID_RX_FIFO_DEPTH: + CFI_INFO("FT_ID_RX_FIFO_DEPTH\n"); + if ((retval = cfi_set_rx_fifo_val(buf, pcd)) < 0) + return retval; + cfi->need_gadget_att = 0; + break; + } + + return retval; +} + +#endif //DWC_UTE_CFI diff --git a/drivers/usb/host/dwc_otg/dwc_otg_cfi.h b/drivers/usb/host/dwc_otg/dwc_otg_cfi.h new file mode 100644 index 00000000000000..55fd337a283c39 --- /dev/null +++ b/drivers/usb/host/dwc_otg/dwc_otg_cfi.h @@ -0,0 +1,320 @@ +/* ========================================================================== + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ + +#if !defined(__DWC_OTG_CFI_H__) +#define __DWC_OTG_CFI_H__ + +#include "dwc_otg_pcd.h" +#include "dwc_cfi_common.h" + +/** + * @file + * This file contains the CFI related OTG PCD specific common constants, + * interfaces(functions and macros) and data structures.The CFI Protocol is an + * optional interface for internal testing purposes that a DUT may implement to + * support testing of configurable features. + * + */ + +struct dwc_otg_pcd; +struct dwc_otg_pcd_ep; + +/** OTG CFI Features (properties) ID constants */ +/** This is a request for all Core Features */ +#define FT_ID_DMA_MODE 0x0001 +#define FT_ID_DMA_BUFFER_SETUP 0x0002 +#define FT_ID_DMA_BUFF_ALIGN 0x0003 +#define FT_ID_DMA_CONCAT_SETUP 0x0004 +#define FT_ID_DMA_CIRCULAR 0x0005 +#define FT_ID_THRESHOLD_SETUP 0x0006 +#define FT_ID_DFIFO_DEPTH 0x0007 +#define FT_ID_TX_FIFO_DEPTH 0x0008 +#define FT_ID_RX_FIFO_DEPTH 0x0009 + +/**********************************************************/ +#define CFI_INFO_DEF + +#ifdef CFI_INFO_DEF +#define CFI_INFO(fmt...) DWC_PRINTF("CFI: " fmt); +#else +#define CFI_INFO(fmt...) +#endif + +#define min(x,y) ({ \ + x < y ? x : y; }) + +#define max(x,y) ({ \ + x > y ? x : y; }) + +/** + * Descriptor DMA SG Buffer setup structure (SG buffer). This structure is + * also used for setting up a buffer for Circular DDMA. + */ +struct _ddma_sg_buffer_setup { +#define BS_SG_VAL_DESC_LEN 6 + /* The OUT EP address */ + uint8_t bOutEndpointAddress; + /* The IN EP address */ + uint8_t bInEndpointAddress; + /* Number of bytes to put between transfer segments (must be DWORD boundaries) */ + uint8_t bOffset; + /* The number of transfer segments (a DMA descriptors per each segment) */ + uint8_t bCount; + /* Size (in byte) of each transfer segment */ + uint16_t wSize; +} __attribute__ ((packed)); +typedef struct _ddma_sg_buffer_setup ddma_sg_buffer_setup_t; + +/** Descriptor DMA Concatenation Buffer setup structure */ +struct _ddma_concat_buffer_setup_hdr { +#define BS_CONCAT_VAL_HDR_LEN 4 + /* The endpoint for which the buffer is to be set up */ + uint8_t bEndpointAddress; + /* The count of descriptors to be used */ + uint8_t bDescCount; + /* The total size of the transfer */ + uint16_t wSize; +} __attribute__ ((packed)); +typedef struct _ddma_concat_buffer_setup_hdr ddma_concat_buffer_setup_hdr_t; + +/** Descriptor DMA Concatenation Buffer setup structure */ +struct _ddma_concat_buffer_setup { + /* The SG header */ + ddma_concat_buffer_setup_hdr_t hdr; + + /* The XFER sizes pointer (allocated dynamically) */ + uint16_t *wTxBytes; +} __attribute__ ((packed)); +typedef struct _ddma_concat_buffer_setup ddma_concat_buffer_setup_t; + +/** Descriptor DMA Alignment Buffer setup structure */ +struct _ddma_align_buffer_setup { +#define BS_ALIGN_VAL_HDR_LEN 2 + uint8_t bEndpointAddress; + uint8_t bAlign; +} __attribute__ ((packed)); +typedef struct _ddma_align_buffer_setup ddma_align_buffer_setup_t; + +/** Transmit FIFO Size setup structure */ +struct _tx_fifo_size_setup { + uint8_t bEndpointAddress; + uint16_t wDepth; +} __attribute__ ((packed)); +typedef struct _tx_fifo_size_setup tx_fifo_size_setup_t; + +/** Transmit FIFO Size setup structure */ +struct _rx_fifo_size_setup { + uint16_t wDepth; +} __attribute__ ((packed)); +typedef struct _rx_fifo_size_setup rx_fifo_size_setup_t; + +/** + * struct cfi_usb_ctrlrequest - the CFI implementation of the struct usb_ctrlrequest + * This structure encapsulates the standard usb_ctrlrequest and adds a pointer + * to the data returned in the data stage of a 3-stage Control Write requests. + */ +struct cfi_usb_ctrlrequest { + uint8_t bRequestType; + uint8_t bRequest; + uint16_t wValue; + uint16_t wIndex; + uint16_t wLength; + uint8_t *data; +} UPACKED; + +/*---------------------------------------------------------------------------*/ + +/** + * The CFI wrapper of the enabled and activated dwc_otg_pcd_ep structures. + * This structure is used to store the buffer setup data for any + * enabled endpoint in the PCD. + */ +struct cfi_ep { + /* Entry for the list container */ + dwc_list_link_t lh; + /* Pointer to the active PCD endpoint structure */ + struct dwc_otg_pcd_ep *ep; + /* The last descriptor in the chain of DMA descriptors of the endpoint */ + struct dwc_otg_dma_desc *dma_desc_last; + /* The SG feature value */ + ddma_sg_buffer_setup_t *bm_sg; + /* The Circular feature value */ + ddma_sg_buffer_setup_t *bm_circ; + /* The Concatenation feature value */ + ddma_concat_buffer_setup_t *bm_concat; + /* The Alignment feature value */ + ddma_align_buffer_setup_t *bm_align; + /* XFER length */ + uint32_t xfer_len; + /* + * Count of DMA descriptors currently used. + * The total should not exceed the MAX_DMA_DESCS_PER_EP value + * defined in the dwc_otg_cil.h + */ + uint32_t desc_count; +}; +typedef struct cfi_ep cfi_ep_t; + +typedef struct cfi_dma_buff { +#define CFI_IN_BUF_LEN 1024 +#define CFI_OUT_BUF_LEN 1024 + dma_addr_t addr; + uint8_t *buf; +} cfi_dma_buff_t; + +struct cfiobject; + +/** + * This is the interface for the CFI operations. + * + * @param ep_enable Called when any endpoint is enabled and activated. + * @param release Called when the CFI object is released and it needs to correctly + * deallocate the dynamic memory + * @param ctrl_write_complete Called when the data stage of the request is complete + */ +typedef struct cfi_ops { + int (*ep_enable) (struct cfiobject * cfi, struct dwc_otg_pcd * pcd, + struct dwc_otg_pcd_ep * ep); + void *(*ep_alloc_buf) (struct cfiobject * cfi, struct dwc_otg_pcd * pcd, + struct dwc_otg_pcd_ep * ep, dma_addr_t * dma, + unsigned size, gfp_t flags); + void (*release) (struct cfiobject * cfi); + int (*ctrl_write_complete) (struct cfiobject * cfi, + struct dwc_otg_pcd * pcd); + void (*build_descriptors) (struct cfiobject * cfi, + struct dwc_otg_pcd * pcd, + struct dwc_otg_pcd_ep * ep, + dwc_otg_pcd_request_t * req); +} cfi_ops_t; + +struct cfiobject { + cfi_ops_t ops; + struct dwc_otg_pcd *pcd; + struct usb_gadget *gadget; + + /* Buffers used to send/receive CFI-related request data */ + cfi_dma_buff_t buf_in; + cfi_dma_buff_t buf_out; + + /* CFI specific Control request wrapper */ + struct cfi_usb_ctrlrequest ctrl_req; + + /* The list of active EP's in the PCD of type cfi_ep_t */ + dwc_list_link_t active_eps; + + /* This flag shall control the propagation of a specific request + * to the gadget's processing routines. + * 0 - no gadget handling + * 1 - the gadget needs to know about this request (w/o completing a status + * phase - just return a 0 to the _setup callback) + */ + uint8_t need_gadget_att; + + /* Flag indicating whether the status IN phase needs to be + * completed by the PCD + */ + uint8_t need_status_in_complete; +}; +typedef struct cfiobject cfiobject_t; + +#define DUMP_MSG + +#if defined(DUMP_MSG) +static inline void dump_msg(const u8 * buf, unsigned int length) +{ + unsigned int start, num, i; + char line[52], *p; + + if (length >= 512) + return; + + start = 0; + while (length > 0) { + num = min(length, 16u); + p = line; + for (i = 0; i < num; ++i) { + if (i == 8) + *p++ = ' '; + DWC_SPRINTF(p, " %02x", buf[i]); + p += 3; + } + *p = 0; + DWC_DEBUG("%6x: %s\n", start, line); + buf += num; + start += num; + length -= num; + } +} +#else +static inline void dump_msg(const u8 * buf, unsigned int length) +{ +} +#endif + +/** + * This function returns a pointer to cfi_ep_t object with the addr address. + */ +static inline struct cfi_ep *get_cfi_ep_by_addr(struct cfiobject *cfi, + uint8_t addr) +{ + struct cfi_ep *pcfiep; + dwc_list_link_t *tmp; + + DWC_LIST_FOREACH(tmp, &cfi->active_eps) { + pcfiep = DWC_LIST_ENTRY(tmp, struct cfi_ep, lh); + + if (pcfiep->ep->desc->bEndpointAddress == addr) { + return pcfiep; + } + } + + return NULL; +} + +/** + * This function returns a pointer to cfi_ep_t object that matches + * the dwc_otg_pcd_ep object. + */ +static inline struct cfi_ep *get_cfi_ep_by_pcd_ep(struct cfiobject *cfi, + struct dwc_otg_pcd_ep *ep) +{ + struct cfi_ep *pcfiep = NULL; + dwc_list_link_t *tmp; + + DWC_LIST_FOREACH(tmp, &cfi->active_eps) { + pcfiep = DWC_LIST_ENTRY(tmp, struct cfi_ep, lh); + if (pcfiep->ep == ep) { + return pcfiep; + } + } + return NULL; +} + +int cfi_setup(struct dwc_otg_pcd *pcd, struct cfi_usb_ctrlrequest *ctrl); + +#endif /* (__DWC_OTG_CFI_H__) */ diff --git a/drivers/usb/host/dwc_otg/dwc_otg_cil.c b/drivers/usb/host/dwc_otg/dwc_otg_cil.c new file mode 100644 index 00000000000000..b3dd2ad5237c0c --- /dev/null +++ b/drivers/usb/host/dwc_otg/dwc_otg_cil.c @@ -0,0 +1,7121 @@ +/* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_cil.c $ + * $Revision: #191 $ + * $Date: 2012/08/10 $ + * $Change: 2047372 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ + +/** @file + * + * The Core Interface Layer provides basic services for accessing and + * managing the DWC_otg hardware. These services are used by both the + * Host Controller Driver and the Peripheral Controller Driver. + * + * The CIL manages the memory map for the core so that the HCD and PCD + * don't have to do this separately. It also handles basic tasks like + * reading/writing the registers and data FIFOs in the controller. + * Some of the data access functions provide encapsulation of several + * operations required to perform a task, such as writing multiple + * registers to start a transfer. Finally, the CIL performs basic + * services that are not specific to either the host or device modes + * of operation. These services include management of the OTG Host + * Negotiation Protocol (HNP) and Session Request Protocol (SRP). A + * Diagnostic API is also provided to allow testing of the controller + * hardware. + * + * The Core Interface Layer has the following requirements: + * - Provides basic controller operations. + * - Minimal use of OS services. + * - The OS services used will be abstracted by using inline functions + * or macros. + * + */ + +#include "dwc_os.h" +#include "dwc_otg_regs.h" +#include "dwc_otg_cil.h" +#include "dwc_otg_os_dep.h" +#include "dwc_otg_hcd_if.h" + +extern bool cil_force_host; + +static int dwc_otg_setup_params(dwc_otg_core_if_t * core_if); + +/** + * This function is called to initialize the DWC_otg CSR data + * structures. The register addresses in the device and host + * structures are initialized from the base address supplied by the + * caller. The calling function must make the OS calls to get the + * base address of the DWC_otg controller registers. The core_params + * argument holds the parameters that specify how the core should be + * configured. + * + * @param reg_base_addr Base address of DWC_otg core registers + * + */ +dwc_otg_core_if_t *dwc_otg_cil_init(const uint32_t * reg_base_addr) +{ + dwc_otg_core_if_t *core_if = 0; + dwc_otg_dev_if_t *dev_if = 0; + dwc_otg_host_if_t *host_if = 0; + uint8_t *reg_base = (uint8_t *) reg_base_addr; + int i = 0; + + DWC_DEBUGPL(DBG_CILV, "%s(%p)\n", __func__, reg_base_addr); + + core_if = DWC_ALLOC(sizeof(dwc_otg_core_if_t)); + + if (core_if == NULL) { + DWC_DEBUGPL(DBG_CIL, + "Allocation of dwc_otg_core_if_t failed\n"); + return 0; + } + core_if->core_global_regs = (dwc_otg_core_global_regs_t *) reg_base; + + /* + * Allocate the Device Mode structures. + */ + dev_if = DWC_ALLOC(sizeof(dwc_otg_dev_if_t)); + + if (dev_if == NULL) { + DWC_DEBUGPL(DBG_CIL, "Allocation of dwc_otg_dev_if_t failed\n"); + DWC_FREE(core_if); + return 0; + } + + dev_if->dev_global_regs = + (dwc_otg_device_global_regs_t *) (reg_base + + DWC_DEV_GLOBAL_REG_OFFSET); + + for (i = 0; i < MAX_EPS_CHANNELS; i++) { + dev_if->in_ep_regs[i] = (dwc_otg_dev_in_ep_regs_t *) + (reg_base + DWC_DEV_IN_EP_REG_OFFSET + + (i * DWC_EP_REG_OFFSET)); + + dev_if->out_ep_regs[i] = (dwc_otg_dev_out_ep_regs_t *) + (reg_base + DWC_DEV_OUT_EP_REG_OFFSET + + (i * DWC_EP_REG_OFFSET)); + DWC_DEBUGPL(DBG_CILV, "in_ep_regs[%d]->diepctl=%p\n", + i, &dev_if->in_ep_regs[i]->diepctl); + DWC_DEBUGPL(DBG_CILV, "out_ep_regs[%d]->doepctl=%p\n", + i, &dev_if->out_ep_regs[i]->doepctl); + } + + dev_if->speed = 0; // unknown + + core_if->dev_if = dev_if; + + /* + * Allocate the Host Mode structures. + */ + host_if = DWC_ALLOC(sizeof(dwc_otg_host_if_t)); + + if (host_if == NULL) { + DWC_DEBUGPL(DBG_CIL, + "Allocation of dwc_otg_host_if_t failed\n"); + DWC_FREE(dev_if); + DWC_FREE(core_if); + return 0; + } + + host_if->host_global_regs = (dwc_otg_host_global_regs_t *) + (reg_base + DWC_OTG_HOST_GLOBAL_REG_OFFSET); + + host_if->hprt0 = + (uint32_t *) (reg_base + DWC_OTG_HOST_PORT_REGS_OFFSET); + + for (i = 0; i < MAX_EPS_CHANNELS; i++) { + host_if->hc_regs[i] = (dwc_otg_hc_regs_t *) + (reg_base + DWC_OTG_HOST_CHAN_REGS_OFFSET + + (i * DWC_OTG_CHAN_REGS_OFFSET)); + DWC_DEBUGPL(DBG_CILV, "hc_reg[%d]->hcchar=%p\n", + i, &host_if->hc_regs[i]->hcchar); + } + + host_if->num_host_channels = MAX_EPS_CHANNELS; + core_if->host_if = host_if; + + for (i = 0; i < MAX_EPS_CHANNELS; i++) { + core_if->data_fifo[i] = + (uint32_t *) (reg_base + DWC_OTG_DATA_FIFO_OFFSET + + (i * DWC_OTG_DATA_FIFO_SIZE)); + DWC_DEBUGPL(DBG_CILV, "data_fifo[%d]=0x%08lx\n", + i, (unsigned long)core_if->data_fifo[i]); + } + + core_if->pcgcctl = (uint32_t *) (reg_base + DWC_OTG_PCGCCTL_OFFSET); + + /* Initiate lx_state to L3 disconnected state */ + core_if->lx_state = DWC_OTG_L3; + /* + * Store the contents of the hardware configuration registers here for + * easy access later. + */ + core_if->hwcfg1.d32 = + DWC_READ_REG32(&core_if->core_global_regs->ghwcfg1); + core_if->hwcfg2.d32 = + DWC_READ_REG32(&core_if->core_global_regs->ghwcfg2); + core_if->hwcfg3.d32 = + DWC_READ_REG32(&core_if->core_global_regs->ghwcfg3); + core_if->hwcfg4.d32 = + DWC_READ_REG32(&core_if->core_global_regs->ghwcfg4); + + /* Force host mode to get HPTXFSIZ exact power on value */ + { + gusbcfg_data_t gusbcfg = {.d32 = 0 }; + gusbcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->gusbcfg); + gusbcfg.b.force_host_mode = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->gusbcfg, gusbcfg.d32); + dwc_mdelay(100); + core_if->hptxfsiz.d32 = + DWC_READ_REG32(&core_if->core_global_regs->hptxfsiz); + gusbcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->gusbcfg); + if (cil_force_host) + gusbcfg.b.force_host_mode = 1; + else + gusbcfg.b.force_host_mode = 0; + DWC_WRITE_REG32(&core_if->core_global_regs->gusbcfg, gusbcfg.d32); + dwc_mdelay(100); + } + + DWC_DEBUGPL(DBG_CILV, "hwcfg1=%08x\n", core_if->hwcfg1.d32); + DWC_DEBUGPL(DBG_CILV, "hwcfg2=%08x\n", core_if->hwcfg2.d32); + DWC_DEBUGPL(DBG_CILV, "hwcfg3=%08x\n", core_if->hwcfg3.d32); + DWC_DEBUGPL(DBG_CILV, "hwcfg4=%08x\n", core_if->hwcfg4.d32); + + core_if->hcfg.d32 = + DWC_READ_REG32(&core_if->host_if->host_global_regs->hcfg); + core_if->dcfg.d32 = + DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dcfg); + + DWC_DEBUGPL(DBG_CILV, "hcfg=%08x\n", core_if->hcfg.d32); + DWC_DEBUGPL(DBG_CILV, "dcfg=%08x\n", core_if->dcfg.d32); + + DWC_DEBUGPL(DBG_CILV, "op_mode=%0x\n", core_if->hwcfg2.b.op_mode); + DWC_DEBUGPL(DBG_CILV, "arch=%0x\n", core_if->hwcfg2.b.architecture); + DWC_DEBUGPL(DBG_CILV, "num_dev_ep=%d\n", core_if->hwcfg2.b.num_dev_ep); + DWC_DEBUGPL(DBG_CILV, "num_host_chan=%d\n", + core_if->hwcfg2.b.num_host_chan); + DWC_DEBUGPL(DBG_CILV, "nonperio_tx_q_depth=0x%0x\n", + core_if->hwcfg2.b.nonperio_tx_q_depth); + DWC_DEBUGPL(DBG_CILV, "host_perio_tx_q_depth=0x%0x\n", + core_if->hwcfg2.b.host_perio_tx_q_depth); + DWC_DEBUGPL(DBG_CILV, "dev_token_q_depth=0x%0x\n", + core_if->hwcfg2.b.dev_token_q_depth); + + DWC_DEBUGPL(DBG_CILV, "Total FIFO SZ=%d\n", + core_if->hwcfg3.b.dfifo_depth); + DWC_DEBUGPL(DBG_CILV, "xfer_size_cntr_width=%0x\n", + core_if->hwcfg3.b.xfer_size_cntr_width); + + /* + * Set the SRP sucess bit for FS-I2c + */ + core_if->srp_success = 0; + core_if->srp_timer_started = 0; + + /* + * Create new workqueue and init works + */ + core_if->wq_otg = DWC_WORKQ_ALLOC("dwc_otg"); + if (core_if->wq_otg == 0) { + DWC_WARN("DWC_WORKQ_ALLOC failed\n"); + DWC_FREE(host_if); + DWC_FREE(dev_if); + DWC_FREE(core_if); + return 0; + } + + core_if->snpsid = DWC_READ_REG32(&core_if->core_global_regs->gsnpsid); + + DWC_PRINTF("Core Release: %x.%x%x%x\n", + (core_if->snpsid >> 12 & 0xF), + (core_if->snpsid >> 8 & 0xF), + (core_if->snpsid >> 4 & 0xF), (core_if->snpsid & 0xF)); + + core_if->wkp_timer = DWC_TIMER_ALLOC("Wake Up Timer", + w_wakeup_detected, core_if); + if (core_if->wkp_timer == 0) { + DWC_WARN("DWC_TIMER_ALLOC failed\n"); + DWC_FREE(host_if); + DWC_FREE(dev_if); + DWC_WORKQ_FREE(core_if->wq_otg); + DWC_FREE(core_if); + return 0; + } + + if (dwc_otg_setup_params(core_if)) { + DWC_WARN("Error while setting core params\n"); + } + + core_if->hibernation_suspend = 0; + + /** ADP initialization */ + dwc_otg_adp_init(core_if); + + return core_if; +} + +/** + * This function frees the structures allocated by dwc_otg_cil_init(). + * + * @param core_if The core interface pointer returned from + * dwc_otg_cil_init(). + * + */ +void dwc_otg_cil_remove(dwc_otg_core_if_t * core_if) +{ + dctl_data_t dctl = {.d32 = 0 }; + DWC_DEBUGPL(DBG_CILV, "%s(%p)\n", __func__, core_if); + + /* Disable all interrupts */ + DWC_MODIFY_REG32(&core_if->core_global_regs->gahbcfg, 1, 0); + DWC_WRITE_REG32(&core_if->core_global_regs->gintmsk, 0); + + dctl.b.sftdiscon = 1; + if (core_if->snpsid >= OTG_CORE_REV_3_00a) { + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->dctl, 0, + dctl.d32); + } + + if (core_if->wq_otg) { + DWC_WORKQ_WAIT_WORK_DONE(core_if->wq_otg, 500); + DWC_WORKQ_FREE(core_if->wq_otg); + } + if (core_if->dev_if) { + DWC_FREE(core_if->dev_if); + } + if (core_if->host_if) { + DWC_FREE(core_if->host_if); + } + + /** Remove ADP Stuff */ + dwc_otg_adp_remove(core_if); + if (core_if->core_params) { + DWC_FREE(core_if->core_params); + } + if (core_if->wkp_timer) { + DWC_TIMER_FREE(core_if->wkp_timer); + } + if (core_if->srp_timer) { + DWC_TIMER_FREE(core_if->srp_timer); + } + DWC_FREE(core_if); +} + +/** + * This function enables the controller's Global Interrupt in the AHB Config + * register. + * + * @param core_if Programming view of DWC_otg controller. + */ +void dwc_otg_enable_global_interrupts(dwc_otg_core_if_t * core_if) +{ + gahbcfg_data_t ahbcfg = {.d32 = 0 }; + ahbcfg.b.glblintrmsk = 1; /* Enable interrupts */ + DWC_MODIFY_REG32(&core_if->core_global_regs->gahbcfg, 0, ahbcfg.d32); +} + +/** + * This function disables the controller's Global Interrupt in the AHB Config + * register. + * + * @param core_if Programming view of DWC_otg controller. + */ +void dwc_otg_disable_global_interrupts(dwc_otg_core_if_t * core_if) +{ + gahbcfg_data_t ahbcfg = {.d32 = 0 }; + ahbcfg.b.glblintrmsk = 1; /* Disable interrupts */ + DWC_MODIFY_REG32(&core_if->core_global_regs->gahbcfg, ahbcfg.d32, 0); +} + +/** + * This function initializes the commmon interrupts, used in both + * device and host modes. + * + * @param core_if Programming view of the DWC_otg controller + * + */ +static void dwc_otg_enable_common_interrupts(dwc_otg_core_if_t * core_if) +{ + dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs; + gintmsk_data_t intr_mask = {.d32 = 0 }; + + /* Clear any pending OTG Interrupts */ + DWC_WRITE_REG32(&global_regs->gotgint, 0xFFFFFFFF); + + /* Clear any pending interrupts */ + DWC_WRITE_REG32(&global_regs->gintsts, 0xFFFFFFFF); + + /* + * Enable the interrupts in the GINTMSK. + */ + intr_mask.b.modemismatch = 1; + intr_mask.b.otgintr = 1; + + if (!core_if->dma_enable) { + intr_mask.b.rxstsqlvl = 1; + } + + intr_mask.b.conidstschng = 1; + intr_mask.b.wkupintr = 1; + intr_mask.b.disconnect = 0; + intr_mask.b.usbsuspend = 1; + intr_mask.b.sessreqintr = 1; +#ifdef CONFIG_USB_DWC_OTG_LPM + if (core_if->core_params->lpm_enable) { + intr_mask.b.lpmtranrcvd = 1; + } +#endif + DWC_WRITE_REG32(&global_regs->gintmsk, intr_mask.d32); +} + +/* + * The restore operation is modified to support Synopsys Emulated Powerdown and + * Hibernation. This function is for exiting from Device mode hibernation by + * Host Initiated Resume/Reset and Device Initiated Remote-Wakeup. + * @param core_if Programming view of DWC_otg controller. + * @param rem_wakeup - indicates whether resume is initiated by Device or Host. + * @param reset - indicates whether resume is initiated by Reset. + */ +int dwc_otg_device_hibernation_restore(dwc_otg_core_if_t * core_if, + int rem_wakeup, int reset) +{ + gpwrdn_data_t gpwrdn = {.d32 = 0 }; + pcgcctl_data_t pcgcctl = {.d32 = 0 }; + dctl_data_t dctl = {.d32 = 0 }; + + int timeout = 2000; + + if (!core_if->hibernation_suspend) { + DWC_PRINTF("Already exited from Hibernation\n"); + return 1; + } + + DWC_DEBUGPL(DBG_PCD, "%s called\n", __FUNCTION__); + /* Switch-on voltage to the core */ + gpwrdn.b.pwrdnswtch = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + dwc_udelay(10); + + /* Reset core */ + gpwrdn.d32 = 0; + gpwrdn.b.pwrdnrstn = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + dwc_udelay(10); + + /* Assert Restore signal */ + gpwrdn.d32 = 0; + gpwrdn.b.restore = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, gpwrdn.d32); + dwc_udelay(10); + + /* Disable power clamps */ + gpwrdn.d32 = 0; + gpwrdn.b.pwrdnclmp = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + + if (rem_wakeup) { + dwc_udelay(70); + } + + /* Deassert Reset core */ + gpwrdn.d32 = 0; + gpwrdn.b.pwrdnrstn = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, gpwrdn.d32); + dwc_udelay(10); + + /* Disable PMU interrupt */ + gpwrdn.d32 = 0; + gpwrdn.b.pmuintsel = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + + /* Mask interrupts from gpwrdn */ + gpwrdn.d32 = 0; + gpwrdn.b.connect_det_msk = 1; + gpwrdn.b.srp_det_msk = 1; + gpwrdn.b.disconn_det_msk = 1; + gpwrdn.b.rst_det_msk = 1; + gpwrdn.b.lnstchng_msk = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + + /* Indicates that we are going out from hibernation */ + core_if->hibernation_suspend = 0; + + /* + * Set Restore Essential Regs bit in PCGCCTL register, restore_mode = 1 + * indicates restore from remote_wakeup + */ + restore_essential_regs(core_if, rem_wakeup, 0); + + /* + * Wait a little for seeing new value of variable hibernation_suspend if + * Restore done interrupt received before polling + */ + dwc_udelay(10); + + if (core_if->hibernation_suspend == 0) { + /* + * Wait For Restore_done Interrupt. This mechanism of polling the + * interrupt is introduced to avoid any possible race conditions + */ + do { + gintsts_data_t gintsts; + gintsts.d32 = + DWC_READ_REG32(&core_if->core_global_regs->gintsts); + if (gintsts.b.restoredone) { + gintsts.d32 = 0; + gintsts.b.restoredone = 1; + DWC_WRITE_REG32(&core_if->core_global_regs-> + gintsts, gintsts.d32); + DWC_PRINTF("Restore Done Interrupt seen\n"); + break; + } + dwc_udelay(10); + } while (--timeout); + if (!timeout) { + DWC_PRINTF("Restore Done interrupt wasn't generated here\n"); + } + } + /* Clear all pending interupts */ + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, 0xFFFFFFFF); + + /* De-assert Restore */ + gpwrdn.d32 = 0; + gpwrdn.b.restore = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + dwc_udelay(10); + + if (!rem_wakeup) { + pcgcctl.d32 = 0; + pcgcctl.b.rstpdwnmodule = 1; + DWC_MODIFY_REG32(core_if->pcgcctl, pcgcctl.d32, 0); + } + + /* Restore GUSBCFG and DCFG */ + DWC_WRITE_REG32(&core_if->core_global_regs->gusbcfg, + core_if->gr_backup->gusbcfg_local); + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->dcfg, + core_if->dr_backup->dcfg); + + /* De-assert Wakeup Logic */ + gpwrdn.d32 = 0; + gpwrdn.b.pmuactv = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + dwc_udelay(10); + + if (!rem_wakeup) { + /* Set Device programming done bit */ + dctl.b.pwronprgdone = 1; + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->dctl, 0, dctl.d32); + } else { + /* Start Remote Wakeup Signaling */ + dctl.d32 = core_if->dr_backup->dctl; + dctl.b.rmtwkupsig = 1; + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->dctl, dctl.d32); + } + + dwc_mdelay(2); + /* Clear all pending interupts */ + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, 0xFFFFFFFF); + + /* Restore global registers */ + dwc_otg_restore_global_regs(core_if); + /* Restore device global registers */ + dwc_otg_restore_dev_regs(core_if, rem_wakeup); + + if (rem_wakeup) { + dwc_mdelay(7); + dctl.d32 = 0; + dctl.b.rmtwkupsig = 1; + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->dctl, dctl.d32, 0); + } + + core_if->hibernation_suspend = 0; + /* The core will be in ON STATE */ + core_if->lx_state = DWC_OTG_L0; + DWC_PRINTF("Hibernation recovery completes here\n"); + + return 1; +} + +/* + * The restore operation is modified to support Synopsys Emulated Powerdown and + * Hibernation. This function is for exiting from Host mode hibernation by + * Host Initiated Resume/Reset and Device Initiated Remote-Wakeup. + * @param core_if Programming view of DWC_otg controller. + * @param rem_wakeup - indicates whether resume is initiated by Device or Host. + * @param reset - indicates whether resume is initiated by Reset. + */ +int dwc_otg_host_hibernation_restore(dwc_otg_core_if_t * core_if, + int rem_wakeup, int reset) +{ + gpwrdn_data_t gpwrdn = {.d32 = 0 }; + hprt0_data_t hprt0 = {.d32 = 0 }; + + int timeout = 2000; + + DWC_DEBUGPL(DBG_HCD, "%s called\n", __FUNCTION__); + /* Switch-on voltage to the core */ + gpwrdn.b.pwrdnswtch = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + dwc_udelay(10); + + /* Reset core */ + gpwrdn.d32 = 0; + gpwrdn.b.pwrdnrstn = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + dwc_udelay(10); + + /* Assert Restore signal */ + gpwrdn.d32 = 0; + gpwrdn.b.restore = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, gpwrdn.d32); + dwc_udelay(10); + + /* Disable power clamps */ + gpwrdn.d32 = 0; + gpwrdn.b.pwrdnclmp = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + + if (!rem_wakeup) { + dwc_udelay(50); + } + + /* Deassert Reset core */ + gpwrdn.d32 = 0; + gpwrdn.b.pwrdnrstn = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, gpwrdn.d32); + dwc_udelay(10); + + /* Disable PMU interrupt */ + gpwrdn.d32 = 0; + gpwrdn.b.pmuintsel = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + + gpwrdn.d32 = 0; + gpwrdn.b.connect_det_msk = 1; + gpwrdn.b.srp_det_msk = 1; + gpwrdn.b.disconn_det_msk = 1; + gpwrdn.b.rst_det_msk = 1; + gpwrdn.b.lnstchng_msk = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + + /* Indicates that we are going out from hibernation */ + core_if->hibernation_suspend = 0; + + /* Set Restore Essential Regs bit in PCGCCTL register */ + restore_essential_regs(core_if, rem_wakeup, 1); + + /* Wait a little for seeing new value of variable hibernation_suspend if + * Restore done interrupt received before polling */ + dwc_udelay(10); + + if (core_if->hibernation_suspend == 0) { + /* Wait For Restore_done Interrupt. This mechanism of polling the + * interrupt is introduced to avoid any possible race conditions + */ + do { + gintsts_data_t gintsts; + gintsts.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintsts); + if (gintsts.b.restoredone) { + gintsts.d32 = 0; + gintsts.b.restoredone = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, gintsts.d32); + DWC_DEBUGPL(DBG_HCD,"Restore Done Interrupt seen\n"); + break; + } + dwc_udelay(10); + } while (--timeout); + if (!timeout) { + DWC_WARN("Restore Done interrupt wasn't generated\n"); + } + } + + /* Set the flag's value to 0 again after receiving restore done interrupt */ + core_if->hibernation_suspend = 0; + + /* This step is not described in functional spec but if not wait for this + * delay, mismatch interrupts occurred because just after restore core is + * in Device mode(gintsts.curmode == 0) */ + dwc_mdelay(100); + + /* Clear all pending interrupts */ + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, 0xFFFFFFFF); + + /* De-assert Restore */ + gpwrdn.d32 = 0; + gpwrdn.b.restore = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + dwc_udelay(10); + + /* Restore GUSBCFG and HCFG */ + DWC_WRITE_REG32(&core_if->core_global_regs->gusbcfg, + core_if->gr_backup->gusbcfg_local); + DWC_WRITE_REG32(&core_if->host_if->host_global_regs->hcfg, + core_if->hr_backup->hcfg_local); + + /* De-assert Wakeup Logic */ + gpwrdn.d32 = 0; + gpwrdn.b.pmuactv = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + dwc_udelay(10); + + /* Start the Resume operation by programming HPRT0 */ + hprt0.d32 = core_if->hr_backup->hprt0_local; + hprt0.b.prtpwr = 1; + hprt0.b.prtena = 0; + hprt0.b.prtsusp = 0; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + + DWC_PRINTF("Resume Starts Now\n"); + if (!reset) { // Indicates it is Resume Operation + hprt0.d32 = core_if->hr_backup->hprt0_local; + hprt0.b.prtres = 1; + hprt0.b.prtpwr = 1; + hprt0.b.prtena = 0; + hprt0.b.prtsusp = 0; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + + if (!rem_wakeup) + hprt0.b.prtres = 0; + /* Wait for Resume time and then program HPRT again */ + dwc_mdelay(100); + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + + } else { // Indicates it is Reset Operation + hprt0.d32 = core_if->hr_backup->hprt0_local; + hprt0.b.prtrst = 1; + hprt0.b.prtpwr = 1; + hprt0.b.prtena = 0; + hprt0.b.prtsusp = 0; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + /* Wait for Reset time and then program HPRT again */ + dwc_mdelay(60); + hprt0.b.prtrst = 0; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + } + /* Clear all interrupt status */ + hprt0.d32 = dwc_otg_read_hprt0(core_if); + hprt0.b.prtconndet = 1; + hprt0.b.prtenchng = 1; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + + /* Clear all pending interupts */ + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, 0xFFFFFFFF); + + /* Restore global registers */ + dwc_otg_restore_global_regs(core_if); + /* Restore host global registers */ + dwc_otg_restore_host_regs(core_if, reset); + + /* The core will be in ON STATE */ + core_if->lx_state = DWC_OTG_L0; + DWC_PRINTF("Hibernation recovery is complete here\n"); + return 0; +} + +/** Saves some register values into system memory. */ +int dwc_otg_save_global_regs(dwc_otg_core_if_t * core_if) +{ + struct dwc_otg_global_regs_backup *gr; + int i; + + gr = core_if->gr_backup; + if (!gr) { + gr = DWC_ALLOC(sizeof(*gr)); + if (!gr) { + return -DWC_E_NO_MEMORY; + } + core_if->gr_backup = gr; + } + + gr->gotgctl_local = DWC_READ_REG32(&core_if->core_global_regs->gotgctl); + gr->gintmsk_local = DWC_READ_REG32(&core_if->core_global_regs->gintmsk); + gr->gahbcfg_local = DWC_READ_REG32(&core_if->core_global_regs->gahbcfg); + gr->gusbcfg_local = DWC_READ_REG32(&core_if->core_global_regs->gusbcfg); + gr->grxfsiz_local = DWC_READ_REG32(&core_if->core_global_regs->grxfsiz); + gr->gnptxfsiz_local = DWC_READ_REG32(&core_if->core_global_regs->gnptxfsiz); + gr->hptxfsiz_local = DWC_READ_REG32(&core_if->core_global_regs->hptxfsiz); +#ifdef CONFIG_USB_DWC_OTG_LPM + gr->glpmcfg_local = DWC_READ_REG32(&core_if->core_global_regs->glpmcfg); +#endif + gr->gi2cctl_local = DWC_READ_REG32(&core_if->core_global_regs->gi2cctl); + gr->pcgcctl_local = DWC_READ_REG32(core_if->pcgcctl); + gr->gdfifocfg_local = + DWC_READ_REG32(&core_if->core_global_regs->gdfifocfg); + for (i = 0; i < MAX_EPS_CHANNELS; i++) { + gr->dtxfsiz_local[i] = + DWC_READ_REG32(&(core_if->core_global_regs->dtxfsiz[i])); + } + + DWC_DEBUGPL(DBG_ANY, "===========Backing Global registers==========\n"); + DWC_DEBUGPL(DBG_ANY, "Backed up gotgctl = %08x\n", gr->gotgctl_local); + DWC_DEBUGPL(DBG_ANY, "Backed up gintmsk = %08x\n", gr->gintmsk_local); + DWC_DEBUGPL(DBG_ANY, "Backed up gahbcfg = %08x\n", gr->gahbcfg_local); + DWC_DEBUGPL(DBG_ANY, "Backed up gusbcfg = %08x\n", gr->gusbcfg_local); + DWC_DEBUGPL(DBG_ANY, "Backed up grxfsiz = %08x\n", gr->grxfsiz_local); + DWC_DEBUGPL(DBG_ANY, "Backed up gnptxfsiz = %08x\n", + gr->gnptxfsiz_local); + DWC_DEBUGPL(DBG_ANY, "Backed up hptxfsiz = %08x\n", + gr->hptxfsiz_local); +#ifdef CONFIG_USB_DWC_OTG_LPM + DWC_DEBUGPL(DBG_ANY, "Backed up glpmcfg = %08x\n", gr->glpmcfg_local); +#endif + DWC_DEBUGPL(DBG_ANY, "Backed up gi2cctl = %08x\n", gr->gi2cctl_local); + DWC_DEBUGPL(DBG_ANY, "Backed up pcgcctl = %08x\n", gr->pcgcctl_local); + DWC_DEBUGPL(DBG_ANY,"Backed up gdfifocfg = %08x\n",gr->gdfifocfg_local); + + return 0; +} + +int dwc_otg_save_dev_regs(dwc_otg_core_if_t * core_if) +{ + struct dwc_otg_dev_regs_backup *dr; + int i; + + dr = core_if->dr_backup; + if (!dr) { + dr = DWC_ALLOC(sizeof(*dr)); + if (!dr) { + return -DWC_E_NO_MEMORY; + } + core_if->dr_backup = dr; + } + + dr->dcfg = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dcfg); + dr->dctl = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dctl); + dr->daintmsk = + DWC_READ_REG32(&core_if->dev_if->dev_global_regs->daintmsk); + dr->diepmsk = + DWC_READ_REG32(&core_if->dev_if->dev_global_regs->diepmsk); + dr->doepmsk = + DWC_READ_REG32(&core_if->dev_if->dev_global_regs->doepmsk); + + for (i = 0; i < core_if->dev_if->num_in_eps; ++i) { + dr->diepctl[i] = + DWC_READ_REG32(&core_if->dev_if->in_ep_regs[i]->diepctl); + dr->dieptsiz[i] = + DWC_READ_REG32(&core_if->dev_if->in_ep_regs[i]->dieptsiz); + dr->diepdma[i] = + DWC_READ_REG32(&core_if->dev_if->in_ep_regs[i]->diepdma); + } + + DWC_DEBUGPL(DBG_ANY, + "=============Backing Host registers==============\n"); + DWC_DEBUGPL(DBG_ANY, "Backed up dcfg = %08x\n", dr->dcfg); + DWC_DEBUGPL(DBG_ANY, "Backed up dctl = %08x\n", dr->dctl); + DWC_DEBUGPL(DBG_ANY, "Backed up daintmsk = %08x\n", + dr->daintmsk); + DWC_DEBUGPL(DBG_ANY, "Backed up diepmsk = %08x\n", dr->diepmsk); + DWC_DEBUGPL(DBG_ANY, "Backed up doepmsk = %08x\n", dr->doepmsk); + for (i = 0; i < core_if->dev_if->num_in_eps; ++i) { + DWC_DEBUGPL(DBG_ANY, "Backed up diepctl[%d] = %08x\n", i, + dr->diepctl[i]); + DWC_DEBUGPL(DBG_ANY, "Backed up dieptsiz[%d] = %08x\n", + i, dr->dieptsiz[i]); + DWC_DEBUGPL(DBG_ANY, "Backed up diepdma[%d] = %08x\n", i, + dr->diepdma[i]); + } + + return 0; +} + +int dwc_otg_save_host_regs(dwc_otg_core_if_t * core_if) +{ + struct dwc_otg_host_regs_backup *hr; + int i; + + hr = core_if->hr_backup; + if (!hr) { + hr = DWC_ALLOC(sizeof(*hr)); + if (!hr) { + return -DWC_E_NO_MEMORY; + } + core_if->hr_backup = hr; + } + + hr->hcfg_local = + DWC_READ_REG32(&core_if->host_if->host_global_regs->hcfg); + hr->haintmsk_local = + DWC_READ_REG32(&core_if->host_if->host_global_regs->haintmsk); + for (i = 0; i < dwc_otg_get_param_host_channels(core_if); ++i) { + hr->hcintmsk_local[i] = + DWC_READ_REG32(&core_if->host_if->hc_regs[i]->hcintmsk); + } + hr->hprt0_local = DWC_READ_REG32(core_if->host_if->hprt0); + hr->hfir_local = + DWC_READ_REG32(&core_if->host_if->host_global_regs->hfir); + + DWC_DEBUGPL(DBG_ANY, + "=============Backing Host registers===============\n"); + DWC_DEBUGPL(DBG_ANY, "Backed up hcfg = %08x\n", + hr->hcfg_local); + DWC_DEBUGPL(DBG_ANY, "Backed up haintmsk = %08x\n", hr->haintmsk_local); + for (i = 0; i < dwc_otg_get_param_host_channels(core_if); ++i) { + DWC_DEBUGPL(DBG_ANY, "Backed up hcintmsk[%02d]=%08x\n", i, + hr->hcintmsk_local[i]); + } + DWC_DEBUGPL(DBG_ANY, "Backed up hprt0 = %08x\n", + hr->hprt0_local); + DWC_DEBUGPL(DBG_ANY, "Backed up hfir = %08x\n", + hr->hfir_local); + + return 0; +} + +int dwc_otg_restore_global_regs(dwc_otg_core_if_t *core_if) +{ + struct dwc_otg_global_regs_backup *gr; + int i; + + gr = core_if->gr_backup; + if (!gr) { + return -DWC_E_INVALID; + } + + DWC_WRITE_REG32(&core_if->core_global_regs->gotgctl, gr->gotgctl_local); + DWC_WRITE_REG32(&core_if->core_global_regs->gintmsk, gr->gintmsk_local); + DWC_WRITE_REG32(&core_if->core_global_regs->gusbcfg, gr->gusbcfg_local); + DWC_WRITE_REG32(&core_if->core_global_regs->gahbcfg, gr->gahbcfg_local); + DWC_WRITE_REG32(&core_if->core_global_regs->grxfsiz, gr->grxfsiz_local); + DWC_WRITE_REG32(&core_if->core_global_regs->gnptxfsiz, + gr->gnptxfsiz_local); + DWC_WRITE_REG32(&core_if->core_global_regs->hptxfsiz, + gr->hptxfsiz_local); + DWC_WRITE_REG32(&core_if->core_global_regs->gdfifocfg, + gr->gdfifocfg_local); + for (i = 0; i < MAX_EPS_CHANNELS; i++) { + DWC_WRITE_REG32(&core_if->core_global_regs->dtxfsiz[i], + gr->dtxfsiz_local[i]); + } + + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, 0xFFFFFFFF); + DWC_WRITE_REG32(core_if->host_if->hprt0, 0x0000100A); + DWC_WRITE_REG32(&core_if->core_global_regs->gahbcfg, + (gr->gahbcfg_local)); + return 0; +} + +int dwc_otg_restore_dev_regs(dwc_otg_core_if_t * core_if, int rem_wakeup) +{ + struct dwc_otg_dev_regs_backup *dr; + int i; + + dr = core_if->dr_backup; + + if (!dr) { + return -DWC_E_INVALID; + } + + if (!rem_wakeup) { + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->dctl, + dr->dctl); + } + + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->daintmsk, dr->daintmsk); + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->diepmsk, dr->diepmsk); + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->doepmsk, dr->doepmsk); + + for (i = 0; i < core_if->dev_if->num_in_eps; ++i) { + DWC_WRITE_REG32(&core_if->dev_if->in_ep_regs[i]->dieptsiz, dr->dieptsiz[i]); + DWC_WRITE_REG32(&core_if->dev_if->in_ep_regs[i]->diepdma, dr->diepdma[i]); + DWC_WRITE_REG32(&core_if->dev_if->in_ep_regs[i]->diepctl, dr->diepctl[i]); + } + + return 0; +} + +int dwc_otg_restore_host_regs(dwc_otg_core_if_t * core_if, int reset) +{ + struct dwc_otg_host_regs_backup *hr; + int i; + hr = core_if->hr_backup; + + if (!hr) { + return -DWC_E_INVALID; + } + + DWC_WRITE_REG32(&core_if->host_if->host_global_regs->hcfg, hr->hcfg_local); + //if (!reset) + //{ + // DWC_WRITE_REG32(&core_if->host_if->host_global_regs->hfir, hr->hfir_local); + //} + + DWC_WRITE_REG32(&core_if->host_if->host_global_regs->haintmsk, + hr->haintmsk_local); + for (i = 0; i < dwc_otg_get_param_host_channels(core_if); ++i) { + DWC_WRITE_REG32(&core_if->host_if->hc_regs[i]->hcintmsk, + hr->hcintmsk_local[i]); + } + + return 0; +} + +int restore_lpm_i2c_regs(dwc_otg_core_if_t * core_if) +{ + struct dwc_otg_global_regs_backup *gr; + + gr = core_if->gr_backup; + + /* Restore values for LPM and I2C */ +#ifdef CONFIG_USB_DWC_OTG_LPM + DWC_WRITE_REG32(&core_if->core_global_regs->glpmcfg, gr->glpmcfg_local); +#endif + DWC_WRITE_REG32(&core_if->core_global_regs->gi2cctl, gr->gi2cctl_local); + + return 0; +} + +int restore_essential_regs(dwc_otg_core_if_t * core_if, int rmode, int is_host) +{ + struct dwc_otg_global_regs_backup *gr; + pcgcctl_data_t pcgcctl = {.d32 = 0 }; + gahbcfg_data_t gahbcfg = {.d32 = 0 }; + gusbcfg_data_t gusbcfg = {.d32 = 0 }; + gintmsk_data_t gintmsk = {.d32 = 0 }; + + /* Restore LPM and I2C registers */ + restore_lpm_i2c_regs(core_if); + + /* Set PCGCCTL to 0 */ + DWC_WRITE_REG32(core_if->pcgcctl, 0x00000000); + + gr = core_if->gr_backup; + /* Load restore values for [31:14] bits */ + DWC_WRITE_REG32(core_if->pcgcctl, + ((gr->pcgcctl_local & 0xffffc000) | 0x00020000)); + + /* Umnask global Interrupt in GAHBCFG and restore it */ + gahbcfg.d32 = gr->gahbcfg_local; + gahbcfg.b.glblintrmsk = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->gahbcfg, gahbcfg.d32); + + /* Clear all pending interupts */ + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, 0xFFFFFFFF); + + /* Unmask restore done interrupt */ + gintmsk.b.restoredone = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->gintmsk, gintmsk.d32); + + /* Restore GUSBCFG and HCFG/DCFG */ + gusbcfg.d32 = core_if->gr_backup->gusbcfg_local; + DWC_WRITE_REG32(&core_if->core_global_regs->gusbcfg, gusbcfg.d32); + + if (is_host) { + hcfg_data_t hcfg = {.d32 = 0 }; + hcfg.d32 = core_if->hr_backup->hcfg_local; + DWC_WRITE_REG32(&core_if->host_if->host_global_regs->hcfg, + hcfg.d32); + + /* Load restore values for [31:14] bits */ + pcgcctl.d32 = gr->pcgcctl_local & 0xffffc000; + pcgcctl.d32 = gr->pcgcctl_local | 0x00020000; + + if (rmode) + pcgcctl.b.restoremode = 1; + DWC_WRITE_REG32(core_if->pcgcctl, pcgcctl.d32); + dwc_udelay(10); + + /* Load restore values for [31:14] bits and set EssRegRestored bit */ + pcgcctl.d32 = gr->pcgcctl_local | 0xffffc000; + pcgcctl.d32 = gr->pcgcctl_local & 0xffffc000; + pcgcctl.b.ess_reg_restored = 1; + if (rmode) + pcgcctl.b.restoremode = 1; + DWC_WRITE_REG32(core_if->pcgcctl, pcgcctl.d32); + } else { + dcfg_data_t dcfg = {.d32 = 0 }; + dcfg.d32 = core_if->dr_backup->dcfg; + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->dcfg, dcfg.d32); + + /* Load restore values for [31:14] bits */ + pcgcctl.d32 = gr->pcgcctl_local & 0xffffc000; + pcgcctl.d32 = gr->pcgcctl_local | 0x00020000; + if (!rmode) { + pcgcctl.d32 |= 0x208; + } + DWC_WRITE_REG32(core_if->pcgcctl, pcgcctl.d32); + dwc_udelay(10); + + /* Load restore values for [31:14] bits */ + pcgcctl.d32 = gr->pcgcctl_local & 0xffffc000; + pcgcctl.d32 = gr->pcgcctl_local | 0x00020000; + pcgcctl.b.ess_reg_restored = 1; + if (!rmode) + pcgcctl.d32 |= 0x208; + DWC_WRITE_REG32(core_if->pcgcctl, pcgcctl.d32); + } + + return 0; +} + +/** + * Initializes the FSLSPClkSel field of the HCFG register depending on the PHY + * type. + */ +static void init_fslspclksel(dwc_otg_core_if_t * core_if) +{ + uint32_t val; + hcfg_data_t hcfg; + + if (((core_if->hwcfg2.b.hs_phy_type == 2) && + (core_if->hwcfg2.b.fs_phy_type == 1) && + (core_if->core_params->ulpi_fs_ls)) || + (core_if->core_params->phy_type == DWC_PHY_TYPE_PARAM_FS)) { + /* Full speed PHY */ + val = DWC_HCFG_48_MHZ; + } else { + /* High speed PHY running at full speed or high speed */ + val = DWC_HCFG_30_60_MHZ; + } + + DWC_DEBUGPL(DBG_CIL, "Initializing HCFG.FSLSPClkSel to 0x%1x\n", val); + hcfg.d32 = DWC_READ_REG32(&core_if->host_if->host_global_regs->hcfg); + hcfg.b.fslspclksel = val; + DWC_WRITE_REG32(&core_if->host_if->host_global_regs->hcfg, hcfg.d32); +} + +/** + * Initializes the DevSpd field of the DCFG register depending on the PHY type + * and the enumeration speed of the device. + */ +static void init_devspd(dwc_otg_core_if_t * core_if) +{ + uint32_t val; + dcfg_data_t dcfg; + + if (((core_if->hwcfg2.b.hs_phy_type == 2) && + (core_if->hwcfg2.b.fs_phy_type == 1) && + (core_if->core_params->ulpi_fs_ls)) || + (core_if->core_params->phy_type == DWC_PHY_TYPE_PARAM_FS)) { + /* Full speed PHY */ + val = 0x3; + } else if (core_if->core_params->speed == DWC_SPEED_PARAM_FULL) { + /* High speed PHY running at full speed */ + val = 0x1; + } else { + /* High speed PHY running at high speed */ + val = 0x0; + } + + DWC_DEBUGPL(DBG_CIL, "Initializing DCFG.DevSpd to 0x%1x\n", val); + + dcfg.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dcfg); + dcfg.b.devspd = val; + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->dcfg, dcfg.d32); +} + +/** + * This function calculates the number of IN EPS + * using GHWCFG1 and GHWCFG2 registers values + * + * @param core_if Programming view of the DWC_otg controller + */ +static uint32_t calc_num_in_eps(dwc_otg_core_if_t * core_if) +{ + uint32_t num_in_eps = 0; + uint32_t num_eps = core_if->hwcfg2.b.num_dev_ep; + uint32_t hwcfg1 = core_if->hwcfg1.d32 >> 3; + uint32_t num_tx_fifos = core_if->hwcfg4.b.num_in_eps; + int i; + + for (i = 0; i < num_eps; ++i) { + if (!(hwcfg1 & 0x1)) + num_in_eps++; + + hwcfg1 >>= 2; + } + + if (core_if->hwcfg4.b.ded_fifo_en) { + num_in_eps = + (num_in_eps > num_tx_fifos) ? num_tx_fifos : num_in_eps; + } + + return num_in_eps; +} + +/** + * This function calculates the number of OUT EPS + * using GHWCFG1 and GHWCFG2 registers values + * + * @param core_if Programming view of the DWC_otg controller + */ +static uint32_t calc_num_out_eps(dwc_otg_core_if_t * core_if) +{ + uint32_t num_out_eps = 0; + uint32_t num_eps = core_if->hwcfg2.b.num_dev_ep; + uint32_t hwcfg1 = core_if->hwcfg1.d32 >> 2; + int i; + + for (i = 0; i < num_eps; ++i) { + if (!(hwcfg1 & 0x1)) + num_out_eps++; + + hwcfg1 >>= 2; + } + return num_out_eps; +} + +/** + * This function initializes the DWC_otg controller registers and + * prepares the core for device mode or host mode operation. + * + * @param core_if Programming view of the DWC_otg controller + * + */ +void dwc_otg_core_init(dwc_otg_core_if_t * core_if) +{ + int i = 0; + dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs; + dwc_otg_dev_if_t *dev_if = core_if->dev_if; + gahbcfg_data_t ahbcfg = {.d32 = 0 }; + gusbcfg_data_t usbcfg = {.d32 = 0 }; + gi2cctl_data_t i2cctl = {.d32 = 0 }; + + DWC_DEBUGPL(DBG_CILV, "dwc_otg_core_init(%p) regs at %p\n", + core_if, global_regs); + + /* Common Initialization */ + usbcfg.d32 = DWC_READ_REG32(&global_regs->gusbcfg); + + /* Program the ULPI External VBUS bit if needed */ + usbcfg.b.ulpi_ext_vbus_drv = + (core_if->core_params->phy_ulpi_ext_vbus == + DWC_PHY_ULPI_EXTERNAL_VBUS) ? 1 : 0; + + /* Set external TS Dline pulsing */ + usbcfg.b.term_sel_dl_pulse = + (core_if->core_params->ts_dline == 1) ? 1 : 0; + DWC_WRITE_REG32(&global_regs->gusbcfg, usbcfg.d32); + + /* Reset the Controller */ + dwc_otg_core_reset(core_if); + + core_if->adp_enable = core_if->core_params->adp_supp_enable; + core_if->power_down = core_if->core_params->power_down; + core_if->otg_sts = 0; + + /* Initialize parameters from Hardware configuration registers. */ + dev_if->num_in_eps = calc_num_in_eps(core_if); + dev_if->num_out_eps = calc_num_out_eps(core_if); + + DWC_DEBUGPL(DBG_CIL, "num_dev_perio_in_ep=%d\n", + core_if->hwcfg4.b.num_dev_perio_in_ep); + + for (i = 0; i < core_if->hwcfg4.b.num_dev_perio_in_ep; i++) { + dev_if->perio_tx_fifo_size[i] = + DWC_READ_REG32(&global_regs->dtxfsiz[i]) >> 16; + DWC_DEBUGPL(DBG_CIL, "Periodic Tx FIFO SZ #%d=0x%0x\n", + i, dev_if->perio_tx_fifo_size[i]); + } + + for (i = 0; i < core_if->hwcfg4.b.num_in_eps; i++) { + dev_if->tx_fifo_size[i] = + DWC_READ_REG32(&global_regs->dtxfsiz[i]) >> 16; + DWC_DEBUGPL(DBG_CIL, "Tx FIFO SZ #%d=0x%0x\n", + i, dev_if->tx_fifo_size[i]); + } + + core_if->total_fifo_size = core_if->hwcfg3.b.dfifo_depth; + core_if->rx_fifo_size = DWC_READ_REG32(&global_regs->grxfsiz); + core_if->nperio_tx_fifo_size = + DWC_READ_REG32(&global_regs->gnptxfsiz) >> 16; + + DWC_DEBUGPL(DBG_CIL, "Total FIFO SZ=%d\n", core_if->total_fifo_size); + DWC_DEBUGPL(DBG_CIL, "Rx FIFO SZ=%d\n", core_if->rx_fifo_size); + DWC_DEBUGPL(DBG_CIL, "NP Tx FIFO SZ=%d\n", + core_if->nperio_tx_fifo_size); + + /* This programming sequence needs to happen in FS mode before any other + * programming occurs */ + if ((core_if->core_params->speed == DWC_SPEED_PARAM_FULL) && + (core_if->core_params->phy_type == DWC_PHY_TYPE_PARAM_FS)) { + /* If FS mode with FS PHY */ + + /* core_init() is now called on every switch so only call the + * following for the first time through. */ + if (!core_if->phy_init_done) { + core_if->phy_init_done = 1; + DWC_DEBUGPL(DBG_CIL, "FS_PHY detected\n"); + usbcfg.d32 = DWC_READ_REG32(&global_regs->gusbcfg); + usbcfg.b.physel = 1; + DWC_WRITE_REG32(&global_regs->gusbcfg, usbcfg.d32); + + /* Reset after a PHY select */ + dwc_otg_core_reset(core_if); + } + + /* Program DCFG.DevSpd or HCFG.FSLSPclkSel to 48Mhz in FS. Also + * do this on HNP Dev/Host mode switches (done in dev_init and + * host_init). */ + if (dwc_otg_is_host_mode(core_if)) { + init_fslspclksel(core_if); + } else { + init_devspd(core_if); + } + + if (core_if->core_params->i2c_enable) { + DWC_DEBUGPL(DBG_CIL, "FS_PHY Enabling I2c\n"); + /* Program GUSBCFG.OtgUtmifsSel to I2C */ + usbcfg.d32 = DWC_READ_REG32(&global_regs->gusbcfg); + usbcfg.b.otgutmifssel = 1; + DWC_WRITE_REG32(&global_regs->gusbcfg, usbcfg.d32); + + /* Program GI2CCTL.I2CEn */ + i2cctl.d32 = DWC_READ_REG32(&global_regs->gi2cctl); + i2cctl.b.i2cdevaddr = 1; + i2cctl.b.i2cen = 0; + DWC_WRITE_REG32(&global_regs->gi2cctl, i2cctl.d32); + i2cctl.b.i2cen = 1; + DWC_WRITE_REG32(&global_regs->gi2cctl, i2cctl.d32); + } + + } /* endif speed == DWC_SPEED_PARAM_FULL */ + else { + /* High speed PHY. */ + if (!core_if->phy_init_done) { + core_if->phy_init_done = 1; + /* HS PHY parameters. These parameters are preserved + * during soft reset so only program the first time. Do + * a soft reset immediately after setting phyif. */ + + if (core_if->core_params->phy_type == 2) { + /* ULPI interface */ + usbcfg.b.ulpi_utmi_sel = 1; + usbcfg.b.phyif = 0; + usbcfg.b.ddrsel = + core_if->core_params->phy_ulpi_ddr; + } else if (core_if->core_params->phy_type == 1) { + /* UTMI+ interface */ + usbcfg.b.ulpi_utmi_sel = 0; + if (core_if->core_params->phy_utmi_width == 16) { + usbcfg.b.phyif = 1; + + } else { + usbcfg.b.phyif = 0; + } + } else { + DWC_ERROR("FS PHY TYPE\n"); + } + DWC_WRITE_REG32(&global_regs->gusbcfg, usbcfg.d32); + /* Reset after setting the PHY parameters */ + dwc_otg_core_reset(core_if); + } + } + + if ((core_if->hwcfg2.b.hs_phy_type == 2) && + (core_if->hwcfg2.b.fs_phy_type == 1) && + (core_if->core_params->ulpi_fs_ls)) { + DWC_DEBUGPL(DBG_CIL, "Setting ULPI FSLS\n"); + usbcfg.d32 = DWC_READ_REG32(&global_regs->gusbcfg); + usbcfg.b.ulpi_fsls = 1; + usbcfg.b.ulpi_clk_sus_m = 1; + DWC_WRITE_REG32(&global_regs->gusbcfg, usbcfg.d32); + } else { + usbcfg.d32 = DWC_READ_REG32(&global_regs->gusbcfg); + usbcfg.b.ulpi_fsls = 0; + usbcfg.b.ulpi_clk_sus_m = 0; + DWC_WRITE_REG32(&global_regs->gusbcfg, usbcfg.d32); + } + + /* Program the GAHBCFG Register. */ + switch (core_if->hwcfg2.b.architecture) { + + case DWC_SLAVE_ONLY_ARCH: + DWC_DEBUGPL(DBG_CIL, "Slave Only Mode\n"); + ahbcfg.b.nptxfemplvl_txfemplvl = + DWC_GAHBCFG_TXFEMPTYLVL_HALFEMPTY; + ahbcfg.b.ptxfemplvl = DWC_GAHBCFG_TXFEMPTYLVL_HALFEMPTY; + core_if->dma_enable = 0; + core_if->dma_desc_enable = 0; + break; + + case DWC_EXT_DMA_ARCH: + DWC_DEBUGPL(DBG_CIL, "External DMA Mode\n"); + { + uint8_t brst_sz = core_if->core_params->dma_burst_size; + ahbcfg.b.hburstlen = 0; + while (brst_sz > 1) { + ahbcfg.b.hburstlen++; + brst_sz >>= 1; + } + } + core_if->dma_enable = (core_if->core_params->dma_enable != 0); + core_if->dma_desc_enable = + (core_if->core_params->dma_desc_enable != 0); + break; + + case DWC_INT_DMA_ARCH: + DWC_DEBUGPL(DBG_CIL, "Internal DMA Mode\n"); + /* Old value was DWC_GAHBCFG_INT_DMA_BURST_INCR - done for + Host mode ISOC in issue fix - vahrama */ + /* Broadcom had altered to (1<<3)|(0<<0) - WRESP=1, max 4 beats */ + ahbcfg.b.hburstlen = (1<<3)|(0<<0);//DWC_GAHBCFG_INT_DMA_BURST_INCR4; + core_if->dma_enable = (core_if->core_params->dma_enable != 0); + core_if->dma_desc_enable = + (core_if->core_params->dma_desc_enable != 0); + break; + + } + if (core_if->dma_enable) { + if (core_if->dma_desc_enable) { + DWC_PRINTF("Using Descriptor DMA mode\n"); + } else { + DWC_PRINTF("Using Buffer DMA mode\n"); + + } + } else { + DWC_PRINTF("Using Slave mode\n"); + core_if->dma_desc_enable = 0; + } + + if (core_if->core_params->ahb_single) { + ahbcfg.b.ahbsingle = 1; + } + + ahbcfg.b.dmaenable = core_if->dma_enable; + DWC_WRITE_REG32(&global_regs->gahbcfg, ahbcfg.d32); + + core_if->en_multiple_tx_fifo = core_if->hwcfg4.b.ded_fifo_en; + + core_if->pti_enh_enable = core_if->core_params->pti_enable != 0; + core_if->multiproc_int_enable = core_if->core_params->mpi_enable; + DWC_PRINTF("Periodic Transfer Interrupt Enhancement - %s\n", + ((core_if->pti_enh_enable) ? "enabled" : "disabled")); + DWC_PRINTF("Multiprocessor Interrupt Enhancement - %s\n", + ((core_if->multiproc_int_enable) ? "enabled" : "disabled")); + + /* + * Program the GUSBCFG register. + */ + usbcfg.d32 = DWC_READ_REG32(&global_regs->gusbcfg); + + switch (core_if->hwcfg2.b.op_mode) { + case DWC_MODE_HNP_SRP_CAPABLE: + usbcfg.b.hnpcap = (core_if->core_params->otg_cap == + DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE); + usbcfg.b.srpcap = (core_if->core_params->otg_cap != + DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE); + break; + + case DWC_MODE_SRP_ONLY_CAPABLE: + usbcfg.b.hnpcap = 0; + usbcfg.b.srpcap = (core_if->core_params->otg_cap != + DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE); + break; + + case DWC_MODE_NO_HNP_SRP_CAPABLE: + usbcfg.b.hnpcap = 0; + usbcfg.b.srpcap = 0; + break; + + case DWC_MODE_SRP_CAPABLE_DEVICE: + usbcfg.b.hnpcap = 0; + usbcfg.b.srpcap = (core_if->core_params->otg_cap != + DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE); + break; + + case DWC_MODE_NO_SRP_CAPABLE_DEVICE: + usbcfg.b.hnpcap = 0; + usbcfg.b.srpcap = 0; + break; + + case DWC_MODE_SRP_CAPABLE_HOST: + usbcfg.b.hnpcap = 0; + usbcfg.b.srpcap = (core_if->core_params->otg_cap != + DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE); + break; + + case DWC_MODE_NO_SRP_CAPABLE_HOST: + usbcfg.b.hnpcap = 0; + usbcfg.b.srpcap = 0; + break; + } + + DWC_WRITE_REG32(&global_regs->gusbcfg, usbcfg.d32); + +#ifdef CONFIG_USB_DWC_OTG_LPM + if (core_if->core_params->lpm_enable) { + glpmcfg_data_t lpmcfg = {.d32 = 0 }; + + /* To enable LPM support set lpm_cap_en bit */ + lpmcfg.b.lpm_cap_en = 1; + + /* Make AppL1Res ACK */ + lpmcfg.b.appl_resp = 1; + + /* Retry 3 times */ + lpmcfg.b.retry_count = 3; + + DWC_MODIFY_REG32(&core_if->core_global_regs->glpmcfg, + 0, lpmcfg.d32); + + } +#endif + if (core_if->core_params->ic_usb_cap) { + gusbcfg_data_t gusbcfg = {.d32 = 0 }; + gusbcfg.b.ic_usb_cap = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gusbcfg, + 0, gusbcfg.d32); + } + { + gotgctl_data_t gotgctl = {.d32 = 0 }; + gotgctl.b.otgver = core_if->core_params->otg_ver; + DWC_MODIFY_REG32(&core_if->core_global_regs->gotgctl, 0, + gotgctl.d32); + /* Set OTG version supported */ + core_if->otg_ver = core_if->core_params->otg_ver; + DWC_PRINTF("OTG VER PARAM: %d, OTG VER FLAG: %d\n", + core_if->core_params->otg_ver, core_if->otg_ver); + } + + + /* Enable common interrupts */ + dwc_otg_enable_common_interrupts(core_if); + + /* Do device or host intialization based on mode during PCD + * and HCD initialization */ + if (dwc_otg_is_host_mode(core_if)) { + DWC_DEBUGPL(DBG_ANY, "Host Mode\n"); + core_if->op_state = A_HOST; + } else { + DWC_DEBUGPL(DBG_ANY, "Device Mode\n"); + core_if->op_state = B_PERIPHERAL; +#ifdef DWC_DEVICE_ONLY + dwc_otg_core_dev_init(core_if); +#endif + } +} + +/** + * This function enables the Device mode interrupts. + * + * @param core_if Programming view of DWC_otg controller + */ +void dwc_otg_enable_device_interrupts(dwc_otg_core_if_t * core_if) +{ + gintmsk_data_t intr_mask = {.d32 = 0 }; + dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs; + + DWC_DEBUGPL(DBG_CIL, "%s()\n", __func__); + + /* Disable all interrupts. */ + DWC_WRITE_REG32(&global_regs->gintmsk, 0); + + /* Clear any pending interrupts */ + DWC_WRITE_REG32(&global_regs->gintsts, 0xFFFFFFFF); + + /* Enable the common interrupts */ + dwc_otg_enable_common_interrupts(core_if); + + /* Enable interrupts */ + intr_mask.b.usbreset = 1; + intr_mask.b.enumdone = 1; + /* Disable Disconnect interrupt in Device mode */ + intr_mask.b.disconnect = 0; + + if (!core_if->multiproc_int_enable) { + intr_mask.b.inepintr = 1; + intr_mask.b.outepintr = 1; + } + + intr_mask.b.erlysuspend = 1; + + if (core_if->en_multiple_tx_fifo == 0) { + intr_mask.b.epmismatch = 1; + } + + //intr_mask.b.incomplisoout = 1; + intr_mask.b.incomplisoin = 1; + +/* Enable the ignore frame number for ISOC xfers - MAS */ +/* Disable to support high bandwith ISOC transfers - manukz */ +#if 0 +#ifdef DWC_UTE_PER_IO + if (core_if->dma_enable) { + if (core_if->dma_desc_enable) { + dctl_data_t dctl1 = {.d32 = 0 }; + dctl1.b.ifrmnum = 1; + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs-> + dctl, 0, dctl1.d32); + DWC_DEBUG("----Enabled Ignore frame number (0x%08x)", + DWC_READ_REG32(&core_if->dev_if-> + dev_global_regs->dctl)); + } + } +#endif +#endif +#ifdef DWC_EN_ISOC + if (core_if->dma_enable) { + if (core_if->dma_desc_enable == 0) { + if (core_if->pti_enh_enable) { + dctl_data_t dctl = {.d32 = 0 }; + dctl.b.ifrmnum = 1; + DWC_MODIFY_REG32(&core_if-> + dev_if->dev_global_regs->dctl, + 0, dctl.d32); + } else { + intr_mask.b.incomplisoin = 1; + intr_mask.b.incomplisoout = 1; + } + } + } else { + intr_mask.b.incomplisoin = 1; + intr_mask.b.incomplisoout = 1; + } +#endif /* DWC_EN_ISOC */ + + /** @todo NGS: Should this be a module parameter? */ +#ifdef USE_PERIODIC_EP + intr_mask.b.isooutdrop = 1; + intr_mask.b.eopframe = 1; + intr_mask.b.incomplisoin = 1; + intr_mask.b.incomplisoout = 1; +#endif + + DWC_MODIFY_REG32(&global_regs->gintmsk, intr_mask.d32, intr_mask.d32); + + DWC_DEBUGPL(DBG_CIL, "%s() gintmsk=%0x\n", __func__, + DWC_READ_REG32(&global_regs->gintmsk)); +} + +/** + * This function initializes the DWC_otg controller registers for + * device mode. + * + * @param core_if Programming view of DWC_otg controller + * + */ +void dwc_otg_core_dev_init(dwc_otg_core_if_t * core_if) +{ + int i; + dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs; + dwc_otg_dev_if_t *dev_if = core_if->dev_if; + dwc_otg_core_params_t *params = core_if->core_params; + dcfg_data_t dcfg = {.d32 = 0 }; + depctl_data_t diepctl = {.d32 = 0 }; + grstctl_t resetctl = {.d32 = 0 }; + uint32_t rx_fifo_size; + fifosize_data_t nptxfifosize; + fifosize_data_t txfifosize; + dthrctl_data_t dthrctl; + fifosize_data_t ptxfifosize; + uint16_t rxfsiz, nptxfsiz; + gdfifocfg_data_t gdfifocfg = {.d32 = 0 }; + hwcfg3_data_t hwcfg3 = {.d32 = 0 }; + + /* Restart the Phy Clock */ + DWC_WRITE_REG32(core_if->pcgcctl, 0); + + /* Device configuration register */ + init_devspd(core_if); + dcfg.d32 = DWC_READ_REG32(&dev_if->dev_global_regs->dcfg); + dcfg.b.descdma = (core_if->dma_desc_enable) ? 1 : 0; + dcfg.b.perfrint = DWC_DCFG_FRAME_INTERVAL_80; + /* Enable Device OUT NAK in case of DDMA mode*/ + if (core_if->core_params->dev_out_nak) { + dcfg.b.endevoutnak = 1; + } + + if (core_if->core_params->cont_on_bna) { + dctl_data_t dctl = {.d32 = 0 }; + dctl.b.encontonbna = 1; + DWC_MODIFY_REG32(&dev_if->dev_global_regs->dctl, 0, dctl.d32); + } + + + DWC_WRITE_REG32(&dev_if->dev_global_regs->dcfg, dcfg.d32); + + /* Configure data FIFO sizes */ + if (core_if->hwcfg2.b.dynamic_fifo && params->enable_dynamic_fifo) { + DWC_DEBUGPL(DBG_CIL, "Total FIFO Size=%d\n", + core_if->total_fifo_size); + DWC_DEBUGPL(DBG_CIL, "Rx FIFO Size=%d\n", + params->dev_rx_fifo_size); + DWC_DEBUGPL(DBG_CIL, "NP Tx FIFO Size=%d\n", + params->dev_nperio_tx_fifo_size); + + /* Rx FIFO */ + DWC_DEBUGPL(DBG_CIL, "initial grxfsiz=%08x\n", + DWC_READ_REG32(&global_regs->grxfsiz)); + +#ifdef DWC_UTE_CFI + core_if->pwron_rxfsiz = DWC_READ_REG32(&global_regs->grxfsiz); + core_if->init_rxfsiz = params->dev_rx_fifo_size; +#endif + rx_fifo_size = params->dev_rx_fifo_size; + DWC_WRITE_REG32(&global_regs->grxfsiz, rx_fifo_size); + + DWC_DEBUGPL(DBG_CIL, "new grxfsiz=%08x\n", + DWC_READ_REG32(&global_regs->grxfsiz)); + + /** Set Periodic Tx FIFO Mask all bits 0 */ + core_if->p_tx_msk = 0; + + /** Set Tx FIFO Mask all bits 0 */ + core_if->tx_msk = 0; + + if (core_if->en_multiple_tx_fifo == 0) { + /* Non-periodic Tx FIFO */ + DWC_DEBUGPL(DBG_CIL, "initial gnptxfsiz=%08x\n", + DWC_READ_REG32(&global_regs->gnptxfsiz)); + + nptxfifosize.b.depth = params->dev_nperio_tx_fifo_size; + nptxfifosize.b.startaddr = params->dev_rx_fifo_size; + + DWC_WRITE_REG32(&global_regs->gnptxfsiz, + nptxfifosize.d32); + + DWC_DEBUGPL(DBG_CIL, "new gnptxfsiz=%08x\n", + DWC_READ_REG32(&global_regs->gnptxfsiz)); + + /**@todo NGS: Fix Periodic FIFO Sizing! */ + /* + * Periodic Tx FIFOs These FIFOs are numbered from 1 to 15. + * Indexes of the FIFO size module parameters in the + * dev_perio_tx_fifo_size array and the FIFO size registers in + * the dptxfsiz array run from 0 to 14. + */ + /** @todo Finish debug of this */ + ptxfifosize.b.startaddr = + nptxfifosize.b.startaddr + nptxfifosize.b.depth; + for (i = 0; i < core_if->hwcfg4.b.num_dev_perio_in_ep; i++) { + ptxfifosize.b.depth = + params->dev_perio_tx_fifo_size[i]; + DWC_DEBUGPL(DBG_CIL, + "initial dtxfsiz[%d]=%08x\n", i, + DWC_READ_REG32(&global_regs->dtxfsiz + [i])); + DWC_WRITE_REG32(&global_regs->dtxfsiz[i], + ptxfifosize.d32); + DWC_DEBUGPL(DBG_CIL, "new dtxfsiz[%d]=%08x\n", + i, + DWC_READ_REG32(&global_regs->dtxfsiz + [i])); + ptxfifosize.b.startaddr += ptxfifosize.b.depth; + } + } else { + /* + * Tx FIFOs These FIFOs are numbered from 1 to 15. + * Indexes of the FIFO size module parameters in the + * dev_tx_fifo_size array and the FIFO size registers in + * the dtxfsiz array run from 0 to 14. + */ + + /* Non-periodic Tx FIFO */ + DWC_DEBUGPL(DBG_CIL, "initial gnptxfsiz=%08x\n", + DWC_READ_REG32(&global_regs->gnptxfsiz)); + +#ifdef DWC_UTE_CFI + core_if->pwron_gnptxfsiz = + (DWC_READ_REG32(&global_regs->gnptxfsiz) >> 16); + core_if->init_gnptxfsiz = + params->dev_nperio_tx_fifo_size; +#endif + nptxfifosize.b.depth = params->dev_nperio_tx_fifo_size; + nptxfifosize.b.startaddr = params->dev_rx_fifo_size; + + DWC_WRITE_REG32(&global_regs->gnptxfsiz, + nptxfifosize.d32); + + DWC_DEBUGPL(DBG_CIL, "new gnptxfsiz=%08x\n", + DWC_READ_REG32(&global_regs->gnptxfsiz)); + + txfifosize.b.startaddr = + nptxfifosize.b.startaddr + nptxfifosize.b.depth; + + for (i = 0; i < core_if->hwcfg4.b.num_in_eps; i++) { + + txfifosize.b.depth = + params->dev_tx_fifo_size[i]; + + DWC_DEBUGPL(DBG_CIL, + "initial dtxfsiz[%d]=%08x\n", + i, + DWC_READ_REG32(&global_regs->dtxfsiz + [i])); + +#ifdef DWC_UTE_CFI + core_if->pwron_txfsiz[i] = + (DWC_READ_REG32 + (&global_regs->dtxfsiz[i]) >> 16); + core_if->init_txfsiz[i] = + params->dev_tx_fifo_size[i]; +#endif + DWC_WRITE_REG32(&global_regs->dtxfsiz[i], + txfifosize.d32); + + DWC_DEBUGPL(DBG_CIL, + "new dtxfsiz[%d]=%08x\n", + i, + DWC_READ_REG32(&global_regs->dtxfsiz + [i])); + + txfifosize.b.startaddr += txfifosize.b.depth; + } + if (core_if->snpsid <= OTG_CORE_REV_2_94a) { + /* Calculating DFIFOCFG for Device mode to include RxFIFO and NPTXFIFO */ + gdfifocfg.d32 = DWC_READ_REG32(&global_regs->gdfifocfg); + hwcfg3.d32 = DWC_READ_REG32(&global_regs->ghwcfg3); + gdfifocfg.b.gdfifocfg = (DWC_READ_REG32(&global_regs->ghwcfg3) >> 16); + DWC_WRITE_REG32(&global_regs->gdfifocfg, gdfifocfg.d32); + rxfsiz = (DWC_READ_REG32(&global_regs->grxfsiz) & 0x0000ffff); + nptxfsiz = (DWC_READ_REG32(&global_regs->gnptxfsiz) >> 16); + gdfifocfg.b.epinfobase = rxfsiz + nptxfsiz; + DWC_WRITE_REG32(&global_regs->gdfifocfg, gdfifocfg.d32); + } + } + + /* Flush the FIFOs */ + dwc_otg_flush_tx_fifo(core_if, 0x10); /* all Tx FIFOs */ + dwc_otg_flush_rx_fifo(core_if); + + /* Flush the Learning Queue. */ + resetctl.b.intknqflsh = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->grstctl, resetctl.d32); + + if (!core_if->core_params->en_multiple_tx_fifo && core_if->dma_enable) { + core_if->start_predict = 0; + for (i = 0; i<= core_if->dev_if->num_in_eps; ++i) { + core_if->nextep_seq[i] = 0xff; // 0xff - EP not active + } + core_if->nextep_seq[0] = 0; + core_if->first_in_nextep_seq = 0; + diepctl.d32 = DWC_READ_REG32(&dev_if->in_ep_regs[0]->diepctl); + diepctl.b.nextep = 0; + DWC_WRITE_REG32(&dev_if->in_ep_regs[0]->diepctl, diepctl.d32); + + /* Update IN Endpoint Mismatch Count by active IN NP EP count + 1 */ + dcfg.d32 = DWC_READ_REG32(&dev_if->dev_global_regs->dcfg); + dcfg.b.epmscnt = 2; + DWC_WRITE_REG32(&dev_if->dev_global_regs->dcfg, dcfg.d32); + + DWC_DEBUGPL(DBG_CILV,"%s first_in_nextep_seq= %2d; nextep_seq[]:\n", + __func__, core_if->first_in_nextep_seq); + for (i=0; i <= core_if->dev_if->num_in_eps; i++) { + DWC_DEBUGPL(DBG_CILV, "%2d ", core_if->nextep_seq[i]); + } + DWC_DEBUGPL(DBG_CILV,"\n"); + } + + /* Clear all pending Device Interrupts */ + /** @todo - if the condition needed to be checked + * or in any case all pending interrutps should be cleared? + */ + if (core_if->multiproc_int_enable) { + for (i = 0; i < core_if->dev_if->num_in_eps; ++i) { + DWC_WRITE_REG32(&dev_if-> + dev_global_regs->diepeachintmsk[i], 0); + } + } + + for (i = 0; i < core_if->dev_if->num_out_eps; ++i) { + DWC_WRITE_REG32(&dev_if-> + dev_global_regs->doepeachintmsk[i], 0); + } + + DWC_WRITE_REG32(&dev_if->dev_global_regs->deachint, 0xFFFFFFFF); + DWC_WRITE_REG32(&dev_if->dev_global_regs->deachintmsk, 0); + } else { + DWC_WRITE_REG32(&dev_if->dev_global_regs->diepmsk, 0); + DWC_WRITE_REG32(&dev_if->dev_global_regs->doepmsk, 0); + DWC_WRITE_REG32(&dev_if->dev_global_regs->daint, 0xFFFFFFFF); + DWC_WRITE_REG32(&dev_if->dev_global_regs->daintmsk, 0); + } + + for (i = 0; i <= dev_if->num_in_eps; i++) { + depctl_data_t depctl; + depctl.d32 = DWC_READ_REG32(&dev_if->in_ep_regs[i]->diepctl); + if (depctl.b.epena) { + depctl.d32 = 0; + depctl.b.epdis = 1; + depctl.b.snak = 1; + } else { + depctl.d32 = 0; + } + + DWC_WRITE_REG32(&dev_if->in_ep_regs[i]->diepctl, depctl.d32); + + DWC_WRITE_REG32(&dev_if->in_ep_regs[i]->dieptsiz, 0); + DWC_WRITE_REG32(&dev_if->in_ep_regs[i]->diepdma, 0); + DWC_WRITE_REG32(&dev_if->in_ep_regs[i]->diepint, 0xFF); + } + + for (i = 0; i <= dev_if->num_out_eps; i++) { + depctl_data_t depctl; + depctl.d32 = DWC_READ_REG32(&dev_if->out_ep_regs[i]->doepctl); + if (depctl.b.epena) { + dctl_data_t dctl = {.d32 = 0 }; + gintmsk_data_t gintsts = {.d32 = 0 }; + doepint_data_t doepint = {.d32 = 0 }; + dctl.b.sgoutnak = 1; + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->dctl, 0, dctl.d32); + do { + dwc_udelay(10); + gintsts.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintsts); + } while (!gintsts.b.goutnakeff); + gintsts.d32 = 0; + gintsts.b.goutnakeff = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, gintsts.d32); + + depctl.d32 = 0; + depctl.b.epdis = 1; + depctl.b.snak = 1; + DWC_WRITE_REG32(&core_if->dev_if->out_ep_regs[i]->doepctl, depctl.d32); + do { + dwc_udelay(10); + doepint.d32 = DWC_READ_REG32(&core_if->dev_if-> + out_ep_regs[i]->doepint); + } while (!doepint.b.epdisabled); + + doepint.b.epdisabled = 1; + DWC_WRITE_REG32(&core_if->dev_if->out_ep_regs[i]->doepint, doepint.d32); + + dctl.d32 = 0; + dctl.b.cgoutnak = 1; + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->dctl, 0, dctl.d32); + } else { + depctl.d32 = 0; + } + + DWC_WRITE_REG32(&dev_if->out_ep_regs[i]->doepctl, depctl.d32); + + DWC_WRITE_REG32(&dev_if->out_ep_regs[i]->doeptsiz, 0); + DWC_WRITE_REG32(&dev_if->out_ep_regs[i]->doepdma, 0); + DWC_WRITE_REG32(&dev_if->out_ep_regs[i]->doepint, 0xFF); + } + + if (core_if->en_multiple_tx_fifo && core_if->dma_enable) { + dev_if->non_iso_tx_thr_en = params->thr_ctl & 0x1; + dev_if->iso_tx_thr_en = (params->thr_ctl >> 1) & 0x1; + dev_if->rx_thr_en = (params->thr_ctl >> 2) & 0x1; + + dev_if->rx_thr_length = params->rx_thr_length; + dev_if->tx_thr_length = params->tx_thr_length; + + dev_if->setup_desc_index = 0; + + dthrctl.d32 = 0; + dthrctl.b.non_iso_thr_en = dev_if->non_iso_tx_thr_en; + dthrctl.b.iso_thr_en = dev_if->iso_tx_thr_en; + dthrctl.b.tx_thr_len = dev_if->tx_thr_length; + dthrctl.b.rx_thr_en = dev_if->rx_thr_en; + dthrctl.b.rx_thr_len = dev_if->rx_thr_length; + dthrctl.b.ahb_thr_ratio = params->ahb_thr_ratio; + + DWC_WRITE_REG32(&dev_if->dev_global_regs->dtknqr3_dthrctl, + dthrctl.d32); + + DWC_DEBUGPL(DBG_CIL, + "Non ISO Tx Thr - %d\nISO Tx Thr - %d\nRx Thr - %d\nTx Thr Len - %d\nRx Thr Len - %d\n", + dthrctl.b.non_iso_thr_en, dthrctl.b.iso_thr_en, + dthrctl.b.rx_thr_en, dthrctl.b.tx_thr_len, + dthrctl.b.rx_thr_len); + + } + + dwc_otg_enable_device_interrupts(core_if); + + { + diepmsk_data_t msk = {.d32 = 0 }; + msk.b.txfifoundrn = 1; + if (core_if->multiproc_int_enable) { + DWC_MODIFY_REG32(&dev_if->dev_global_regs-> + diepeachintmsk[0], msk.d32, msk.d32); + } else { + DWC_MODIFY_REG32(&dev_if->dev_global_regs->diepmsk, + msk.d32, msk.d32); + } + } + + if (core_if->multiproc_int_enable) { + /* Set NAK on Babble */ + dctl_data_t dctl = {.d32 = 0 }; + dctl.b.nakonbble = 1; + DWC_MODIFY_REG32(&dev_if->dev_global_regs->dctl, 0, dctl.d32); + } + + if (core_if->snpsid >= OTG_CORE_REV_2_94a) { + dctl_data_t dctl = {.d32 = 0 }; + dctl.d32 = DWC_READ_REG32(&dev_if->dev_global_regs->dctl); + dctl.b.sftdiscon = 0; + DWC_WRITE_REG32(&dev_if->dev_global_regs->dctl, dctl.d32); + } +} + +/** + * This function enables the Host mode interrupts. + * + * @param core_if Programming view of DWC_otg controller + */ +void dwc_otg_enable_host_interrupts(dwc_otg_core_if_t * core_if) +{ + dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs; + gintmsk_data_t intr_mask = {.d32 = 0 }; + + DWC_DEBUGPL(DBG_CIL, "%s(%p)\n", __func__, core_if); + + /* Disable all interrupts. */ + DWC_WRITE_REG32(&global_regs->gintmsk, 0); + + /* Clear any pending interrupts. */ + DWC_WRITE_REG32(&global_regs->gintsts, 0xFFFFFFFF); + + /* Enable the common interrupts */ + dwc_otg_enable_common_interrupts(core_if); + + /* + * Enable host mode interrupts without disturbing common + * interrupts. + */ + + intr_mask.b.disconnect = 1; + intr_mask.b.portintr = 1; + intr_mask.b.hcintr = 1; + + DWC_MODIFY_REG32(&global_regs->gintmsk, intr_mask.d32, intr_mask.d32); +} + +/** + * This function disables the Host Mode interrupts. + * + * @param core_if Programming view of DWC_otg controller + */ +void dwc_otg_disable_host_interrupts(dwc_otg_core_if_t * core_if) +{ + dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs; + gintmsk_data_t intr_mask = {.d32 = 0 }; + + DWC_DEBUGPL(DBG_CILV, "%s()\n", __func__); + + /* + * Disable host mode interrupts without disturbing common + * interrupts. + */ + intr_mask.b.sofintr = 1; + intr_mask.b.portintr = 1; + intr_mask.b.hcintr = 1; + intr_mask.b.ptxfempty = 1; + intr_mask.b.nptxfempty = 1; + + DWC_MODIFY_REG32(&global_regs->gintmsk, intr_mask.d32, 0); +} + +/** + * This function initializes the DWC_otg controller registers for + * host mode. + * + * This function flushes the Tx and Rx FIFOs and it flushes any entries in the + * request queues. Host channels are reset to ensure that they are ready for + * performing transfers. + * + * @param core_if Programming view of DWC_otg controller + * + */ +void dwc_otg_core_host_init(dwc_otg_core_if_t * core_if) +{ + dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs; + dwc_otg_host_if_t *host_if = core_if->host_if; + dwc_otg_core_params_t *params = core_if->core_params; + hprt0_data_t hprt0 = {.d32 = 0 }; + fifosize_data_t nptxfifosize; + fifosize_data_t ptxfifosize; + uint16_t rxfsiz, nptxfsiz, hptxfsiz; + gdfifocfg_data_t gdfifocfg = {.d32 = 0 }; + int i; + hcchar_data_t hcchar; + hcfg_data_t hcfg; + hfir_data_t hfir; + dwc_otg_hc_regs_t *hc_regs; + int num_channels; + gotgctl_data_t gotgctl = {.d32 = 0 }; + + DWC_DEBUGPL(DBG_CILV, "%s(%p)\n", __func__, core_if); + + /* Restart the Phy Clock */ + DWC_WRITE_REG32(core_if->pcgcctl, 0); + + /* Initialize Host Configuration Register */ + init_fslspclksel(core_if); + if (core_if->core_params->speed == DWC_SPEED_PARAM_FULL) { + hcfg.d32 = DWC_READ_REG32(&host_if->host_global_regs->hcfg); + hcfg.b.fslssupp = 1; + DWC_WRITE_REG32(&host_if->host_global_regs->hcfg, hcfg.d32); + + } + + /* This bit allows dynamic reloading of the HFIR register + * during runtime. This bit needs to be programmed during + * initial configuration and its value must not be changed + * during runtime.*/ + if (core_if->core_params->reload_ctl == 1) { + hfir.d32 = DWC_READ_REG32(&host_if->host_global_regs->hfir); + hfir.b.hfirrldctrl = 1; + DWC_WRITE_REG32(&host_if->host_global_regs->hfir, hfir.d32); + } + + if (core_if->core_params->dma_desc_enable) { + uint8_t op_mode = core_if->hwcfg2.b.op_mode; + if (! + (core_if->hwcfg4.b.desc_dma + && (core_if->snpsid >= OTG_CORE_REV_2_90a) + && ((op_mode == DWC_HWCFG2_OP_MODE_HNP_SRP_CAPABLE_OTG) + || (op_mode == DWC_HWCFG2_OP_MODE_SRP_ONLY_CAPABLE_OTG) + || (op_mode == + DWC_HWCFG2_OP_MODE_NO_HNP_SRP_CAPABLE_OTG) + || (op_mode == DWC_HWCFG2_OP_MODE_SRP_CAPABLE_HOST) + || (op_mode == + DWC_HWCFG2_OP_MODE_NO_SRP_CAPABLE_HOST)))) { + + DWC_ERROR("Host can't operate in Descriptor DMA mode.\n" + "Either core version is below 2.90a or " + "GHWCFG2, GHWCFG4 registers' values do not allow Descriptor DMA in host mode.\n" + "To run the driver in Buffer DMA host mode set dma_desc_enable " + "module parameter to 0.\n"); + return; + } + hcfg.d32 = DWC_READ_REG32(&host_if->host_global_regs->hcfg); + hcfg.b.descdma = 1; + DWC_WRITE_REG32(&host_if->host_global_regs->hcfg, hcfg.d32); + } + + /* Configure data FIFO sizes */ + if (core_if->hwcfg2.b.dynamic_fifo && params->enable_dynamic_fifo) { + DWC_DEBUGPL(DBG_CIL, "Total FIFO Size=%d\n", + core_if->total_fifo_size); + DWC_DEBUGPL(DBG_CIL, "Rx FIFO Size=%d\n", + params->host_rx_fifo_size); + DWC_DEBUGPL(DBG_CIL, "NP Tx FIFO Size=%d\n", + params->host_nperio_tx_fifo_size); + DWC_DEBUGPL(DBG_CIL, "P Tx FIFO Size=%d\n", + params->host_perio_tx_fifo_size); + + /* Rx FIFO */ + DWC_DEBUGPL(DBG_CIL, "initial grxfsiz=%08x\n", + DWC_READ_REG32(&global_regs->grxfsiz)); + DWC_WRITE_REG32(&global_regs->grxfsiz, + params->host_rx_fifo_size); + DWC_DEBUGPL(DBG_CIL, "new grxfsiz=%08x\n", + DWC_READ_REG32(&global_regs->grxfsiz)); + + /* Non-periodic Tx FIFO */ + DWC_DEBUGPL(DBG_CIL, "initial gnptxfsiz=%08x\n", + DWC_READ_REG32(&global_regs->gnptxfsiz)); + nptxfifosize.b.depth = params->host_nperio_tx_fifo_size; + nptxfifosize.b.startaddr = params->host_rx_fifo_size; + DWC_WRITE_REG32(&global_regs->gnptxfsiz, nptxfifosize.d32); + DWC_DEBUGPL(DBG_CIL, "new gnptxfsiz=%08x\n", + DWC_READ_REG32(&global_regs->gnptxfsiz)); + + /* Periodic Tx FIFO */ + DWC_DEBUGPL(DBG_CIL, "initial hptxfsiz=%08x\n", + DWC_READ_REG32(&global_regs->hptxfsiz)); + ptxfifosize.b.depth = params->host_perio_tx_fifo_size; + ptxfifosize.b.startaddr = + nptxfifosize.b.startaddr + nptxfifosize.b.depth; + DWC_WRITE_REG32(&global_regs->hptxfsiz, ptxfifosize.d32); + DWC_DEBUGPL(DBG_CIL, "new hptxfsiz=%08x\n", + DWC_READ_REG32(&global_regs->hptxfsiz)); + + if (core_if->en_multiple_tx_fifo + && core_if->snpsid <= OTG_CORE_REV_2_94a) { + /* Global DFIFOCFG calculation for Host mode - include RxFIFO, NPTXFIFO and HPTXFIFO */ + gdfifocfg.d32 = DWC_READ_REG32(&global_regs->gdfifocfg); + rxfsiz = (DWC_READ_REG32(&global_regs->grxfsiz) & 0x0000ffff); + nptxfsiz = (DWC_READ_REG32(&global_regs->gnptxfsiz) >> 16); + hptxfsiz = (DWC_READ_REG32(&global_regs->hptxfsiz) >> 16); + gdfifocfg.b.epinfobase = rxfsiz + nptxfsiz + hptxfsiz; + DWC_WRITE_REG32(&global_regs->gdfifocfg, gdfifocfg.d32); + } + } + + /* TODO - check this */ + /* Clear Host Set HNP Enable in the OTG Control Register */ + gotgctl.b.hstsethnpen = 1; + DWC_MODIFY_REG32(&global_regs->gotgctl, gotgctl.d32, 0); + /* Make sure the FIFOs are flushed. */ + dwc_otg_flush_tx_fifo(core_if, 0x10 /* all TX FIFOs */ ); + dwc_otg_flush_rx_fifo(core_if); + + /* Clear Host Set HNP Enable in the OTG Control Register */ + gotgctl.b.hstsethnpen = 1; + DWC_MODIFY_REG32(&global_regs->gotgctl, gotgctl.d32, 0); + + if (!core_if->core_params->dma_desc_enable) { + /* Flush out any leftover queued requests. */ + num_channels = core_if->core_params->host_channels; + + for (i = 0; i < num_channels; i++) { + hc_regs = core_if->host_if->hc_regs[i]; + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + hcchar.b.chen = 0; + hcchar.b.chdis = 1; + hcchar.b.epdir = 0; + DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32); + } + + /* Halt all channels to put them into a known state. */ + for (i = 0; i < num_channels; i++) { + int count = 0; + hc_regs = core_if->host_if->hc_regs[i]; + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + hcchar.b.chen = 1; + hcchar.b.chdis = 1; + hcchar.b.epdir = 0; + DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32); + DWC_DEBUGPL(DBG_HCDV, "%s: Halt channel %d regs %p\n", __func__, i, hc_regs); + do { + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + if (++count > 1000) { + DWC_ERROR + ("%s: Unable to clear halt on channel %d (timeout HCCHAR 0x%X @%p)\n", + __func__, i, hcchar.d32, &hc_regs->hcchar); + break; + } + dwc_udelay(1); + } while (hcchar.b.chen); + } + } + + /* Turn on the vbus power. */ + DWC_PRINTF("Init: Port Power? op_state=%d\n", core_if->op_state); + if (core_if->op_state == A_HOST) { + hprt0.d32 = dwc_otg_read_hprt0(core_if); + DWC_PRINTF("Init: Power Port (%d)\n", hprt0.b.prtpwr); + if (hprt0.b.prtpwr == 0) { + hprt0.b.prtpwr = 1; + DWC_WRITE_REG32(host_if->hprt0, hprt0.d32); + } + } + + dwc_otg_enable_host_interrupts(core_if); +} + +/** + * Prepares a host channel for transferring packets to/from a specific + * endpoint. The HCCHARn register is set up with the characteristics specified + * in _hc. Host channel interrupts that may need to be serviced while this + * transfer is in progress are enabled. + * + * @param core_if Programming view of DWC_otg controller + * @param hc Information needed to initialize the host channel + */ +void dwc_otg_hc_init(dwc_otg_core_if_t * core_if, dwc_hc_t * hc) +{ + hcintmsk_data_t hc_intr_mask; + hcchar_data_t hcchar; + hcsplt_data_t hcsplt; + + uint8_t hc_num = hc->hc_num; + dwc_otg_host_if_t *host_if = core_if->host_if; + dwc_otg_hc_regs_t *hc_regs = host_if->hc_regs[hc_num]; + + /* Clear old interrupt conditions for this host channel. */ + hc_intr_mask.d32 = 0xFFFFFFFF; + hc_intr_mask.b.reserved14_31 = 0; + DWC_WRITE_REG32(&hc_regs->hcint, hc_intr_mask.d32); + + /* Enable channel interrupts required for this transfer. */ + hc_intr_mask.d32 = 0; + hc_intr_mask.b.chhltd = 1; + if (core_if->dma_enable) { + /* For Descriptor DMA mode core halts the channel on AHB error. Interrupt is not required */ + if (!core_if->dma_desc_enable) + hc_intr_mask.b.ahberr = 1; + else { + if (hc->ep_type == DWC_OTG_EP_TYPE_ISOC) + hc_intr_mask.b.xfercompl = 1; + } + + if (hc->error_state && !hc->do_split && + hc->ep_type != DWC_OTG_EP_TYPE_ISOC) { + hc_intr_mask.b.ack = 1; + if (hc->ep_is_in) { + hc_intr_mask.b.datatglerr = 1; + if (hc->ep_type != DWC_OTG_EP_TYPE_INTR) { + hc_intr_mask.b.nak = 1; + } + } + } + } else { + switch (hc->ep_type) { + case DWC_OTG_EP_TYPE_CONTROL: + case DWC_OTG_EP_TYPE_BULK: + hc_intr_mask.b.xfercompl = 1; + hc_intr_mask.b.stall = 1; + hc_intr_mask.b.xacterr = 1; + hc_intr_mask.b.datatglerr = 1; + if (hc->ep_is_in) { + hc_intr_mask.b.bblerr = 1; + } else { + hc_intr_mask.b.nak = 1; + hc_intr_mask.b.nyet = 1; + if (hc->do_ping) { + hc_intr_mask.b.ack = 1; + } + } + + if (hc->do_split) { + hc_intr_mask.b.nak = 1; + if (hc->complete_split) { + hc_intr_mask.b.nyet = 1; + } else { + hc_intr_mask.b.ack = 1; + } + } + + if (hc->error_state) { + hc_intr_mask.b.ack = 1; + } + break; + case DWC_OTG_EP_TYPE_INTR: + hc_intr_mask.b.xfercompl = 1; + hc_intr_mask.b.nak = 1; + hc_intr_mask.b.stall = 1; + hc_intr_mask.b.xacterr = 1; + hc_intr_mask.b.datatglerr = 1; + hc_intr_mask.b.frmovrun = 1; + + if (hc->ep_is_in) { + hc_intr_mask.b.bblerr = 1; + } + if (hc->error_state) { + hc_intr_mask.b.ack = 1; + } + if (hc->do_split) { + if (hc->complete_split) { + hc_intr_mask.b.nyet = 1; + } else { + hc_intr_mask.b.ack = 1; + } + } + break; + case DWC_OTG_EP_TYPE_ISOC: + hc_intr_mask.b.xfercompl = 1; + hc_intr_mask.b.frmovrun = 1; + hc_intr_mask.b.ack = 1; + + if (hc->ep_is_in) { + hc_intr_mask.b.xacterr = 1; + hc_intr_mask.b.bblerr = 1; + } + break; + } + } + DWC_WRITE_REG32(&hc_regs->hcintmsk, hc_intr_mask.d32); + + /* + * Program the HCCHARn register with the endpoint characteristics for + * the current transfer. + */ + hcchar.d32 = 0; + hcchar.b.devaddr = hc->dev_addr; + hcchar.b.epnum = hc->ep_num; + hcchar.b.epdir = hc->ep_is_in; + hcchar.b.lspddev = (hc->speed == DWC_OTG_EP_SPEED_LOW); + hcchar.b.eptype = hc->ep_type; + hcchar.b.mps = hc->max_packet; + + DWC_WRITE_REG32(&host_if->hc_regs[hc_num]->hcchar, hcchar.d32); + + DWC_DEBUGPL(DBG_HCDV, "%s: Channel %d, Dev Addr %d, EP #%d\n", + __func__, hc->hc_num, hcchar.b.devaddr, hcchar.b.epnum); + DWC_DEBUGPL(DBG_HCDV, " Is In %d, Is Low Speed %d, EP Type %d, " + "Max Pkt %d, Multi Cnt %d\n", + hcchar.b.epdir, hcchar.b.lspddev, hcchar.b.eptype, + hcchar.b.mps, hcchar.b.multicnt); + + /* + * Program the HCSPLIT register for SPLITs + */ + hcsplt.d32 = 0; + if (hc->do_split) { + DWC_DEBUGPL(DBG_HCDV, "Programming HC %d with split --> %s\n", + hc->hc_num, + hc->complete_split ? "CSPLIT" : "SSPLIT"); + hcsplt.b.compsplt = hc->complete_split; + hcsplt.b.xactpos = hc->xact_pos; + hcsplt.b.hubaddr = hc->hub_addr; + hcsplt.b.prtaddr = hc->port_addr; + DWC_DEBUGPL(DBG_HCDV, "\t comp split %d\n", hc->complete_split); + DWC_DEBUGPL(DBG_HCDV, "\t xact pos %d\n", hc->xact_pos); + DWC_DEBUGPL(DBG_HCDV, "\t hub addr %d\n", hc->hub_addr); + DWC_DEBUGPL(DBG_HCDV, "\t port addr %d\n", hc->port_addr); + DWC_DEBUGPL(DBG_HCDV, "\t is_in %d\n", hc->ep_is_in); + DWC_DEBUGPL(DBG_HCDV, "\t Max Pkt: %d\n", hcchar.b.mps); + DWC_DEBUGPL(DBG_HCDV, "\t xferlen: %d\n", hc->xfer_len); + } + DWC_WRITE_REG32(&host_if->hc_regs[hc_num]->hcsplt, hcsplt.d32); + +} + +/** + * Attempts to halt a host channel. This function should only be called in + * Slave mode or to abort a transfer in either Slave mode or DMA mode. Under + * normal circumstances in DMA mode, the controller halts the channel when the + * transfer is complete or a condition occurs that requires application + * intervention. + * + * In slave mode, checks for a free request queue entry, then sets the Channel + * Enable and Channel Disable bits of the Host Channel Characteristics + * register of the specified channel to intiate the halt. If there is no free + * request queue entry, sets only the Channel Disable bit of the HCCHARn + * register to flush requests for this channel. In the latter case, sets a + * flag to indicate that the host channel needs to be halted when a request + * queue slot is open. + * + * In DMA mode, always sets the Channel Enable and Channel Disable bits of the + * HCCHARn register. The controller ensures there is space in the request + * queue before submitting the halt request. + * + * Some time may elapse before the core flushes any posted requests for this + * host channel and halts. The Channel Halted interrupt handler completes the + * deactivation of the host channel. + * + * @param core_if Controller register interface. + * @param hc Host channel to halt. + * @param halt_status Reason for halting the channel. + */ +void dwc_otg_hc_halt(dwc_otg_core_if_t * core_if, + dwc_hc_t * hc, dwc_otg_halt_status_e halt_status) +{ + gnptxsts_data_t nptxsts; + hptxsts_data_t hptxsts; + hcchar_data_t hcchar; + dwc_otg_hc_regs_t *hc_regs; + dwc_otg_core_global_regs_t *global_regs; + dwc_otg_host_global_regs_t *host_global_regs; + + hc_regs = core_if->host_if->hc_regs[hc->hc_num]; + global_regs = core_if->core_global_regs; + host_global_regs = core_if->host_if->host_global_regs; + + DWC_ASSERT(!(halt_status == DWC_OTG_HC_XFER_NO_HALT_STATUS), + "halt_status = %d\n", halt_status); + + if (halt_status == DWC_OTG_HC_XFER_URB_DEQUEUE || + halt_status == DWC_OTG_HC_XFER_AHB_ERR) { + /* + * Disable all channel interrupts except Ch Halted. The QTD + * and QH state associated with this transfer has been cleared + * (in the case of URB_DEQUEUE), so the channel needs to be + * shut down carefully to prevent crashes. + */ + hcintmsk_data_t hcintmsk; + hcintmsk.d32 = 0; + hcintmsk.b.chhltd = 1; + DWC_WRITE_REG32(&hc_regs->hcintmsk, hcintmsk.d32); + + /* + * Make sure no other interrupts besides halt are currently + * pending. Handling another interrupt could cause a crash due + * to the QTD and QH state. + */ + DWC_WRITE_REG32(&hc_regs->hcint, ~hcintmsk.d32); + + /* + * Make sure the halt status is set to URB_DEQUEUE or AHB_ERR + * even if the channel was already halted for some other + * reason. + */ + hc->halt_status = halt_status; + + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + if (hcchar.b.chen == 0) { + /* + * The channel is either already halted or it hasn't + * started yet. In DMA mode, the transfer may halt if + * it finishes normally or a condition occurs that + * requires driver intervention. Don't want to halt + * the channel again. In either Slave or DMA mode, + * it's possible that the transfer has been assigned + * to a channel, but not started yet when an URB is + * dequeued. Don't want to halt a channel that hasn't + * started yet. + */ + return; + } + } + if (hc->halt_pending) { + /* + * A halt has already been issued for this channel. This might + * happen when a transfer is aborted by a higher level in + * the stack. + */ +#ifdef DEBUG + DWC_PRINTF + ("*** %s: Channel %d, _hc->halt_pending already set ***\n", + __func__, hc->hc_num); + +#endif + return; + } + + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + + /* No need to set the bit in DDMA for disabling the channel */ + //TODO check it everywhere channel is disabled + if (!core_if->core_params->dma_desc_enable) + hcchar.b.chen = 1; + hcchar.b.chdis = 1; + + if (!core_if->dma_enable) { + /* Check for space in the request queue to issue the halt. */ + if (hc->ep_type == DWC_OTG_EP_TYPE_CONTROL || + hc->ep_type == DWC_OTG_EP_TYPE_BULK) { + nptxsts.d32 = DWC_READ_REG32(&global_regs->gnptxsts); + if (nptxsts.b.nptxqspcavail == 0) { + hcchar.b.chen = 0; + } + } else { + hptxsts.d32 = + DWC_READ_REG32(&host_global_regs->hptxsts); + if ((hptxsts.b.ptxqspcavail == 0) + || (core_if->queuing_high_bandwidth)) { + hcchar.b.chen = 0; + } + } + } + DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32); + + hc->halt_status = halt_status; + + if (hcchar.b.chen) { + hc->halt_pending = 1; + hc->halt_on_queue = 0; + } else { + hc->halt_on_queue = 1; + } + + DWC_DEBUGPL(DBG_HCDV, "%s: Channel %d\n", __func__, hc->hc_num); + DWC_DEBUGPL(DBG_HCDV, " hcchar: 0x%08x\n", hcchar.d32); + DWC_DEBUGPL(DBG_HCDV, " halt_pending: %d\n", hc->halt_pending); + DWC_DEBUGPL(DBG_HCDV, " halt_on_queue: %d\n", hc->halt_on_queue); + DWC_DEBUGPL(DBG_HCDV, " halt_status: %d\n", hc->halt_status); + + return; +} + +/** + * Clears the transfer state for a host channel. This function is normally + * called after a transfer is done and the host channel is being released. + * + * @param core_if Programming view of DWC_otg controller. + * @param hc Identifies the host channel to clean up. + */ +void dwc_otg_hc_cleanup(dwc_otg_core_if_t * core_if, dwc_hc_t * hc) +{ + dwc_otg_hc_regs_t *hc_regs; + + hc->xfer_started = 0; + + /* + * Clear channel interrupt enables and any unhandled channel interrupt + * conditions. + */ + hc_regs = core_if->host_if->hc_regs[hc->hc_num]; + DWC_WRITE_REG32(&hc_regs->hcintmsk, 0); + DWC_WRITE_REG32(&hc_regs->hcint, 0xFFFFFFFF); +#ifdef DEBUG + DWC_TIMER_CANCEL(core_if->hc_xfer_timer[hc->hc_num]); +#endif +} + +/** + * Sets the channel property that indicates in which frame a periodic transfer + * should occur. This is always set to the _next_ frame. This function has no + * effect on non-periodic transfers. + * + * @param core_if Programming view of DWC_otg controller. + * @param hc Identifies the host channel to set up and its properties. + * @param hcchar Current value of the HCCHAR register for the specified host + * channel. + */ +static inline void hc_set_even_odd_frame(dwc_otg_core_if_t * core_if, + dwc_hc_t * hc, hcchar_data_t * hcchar) +{ + if (hc->ep_type == DWC_OTG_EP_TYPE_INTR || + hc->ep_type == DWC_OTG_EP_TYPE_ISOC) { + hfnum_data_t hfnum; + hfnum.d32 = + DWC_READ_REG32(&core_if->host_if->host_global_regs->hfnum); + + /* 1 if _next_ frame is odd, 0 if it's even */ + hcchar->b.oddfrm = (hfnum.b.frnum & 0x1) ? 0 : 1; +#ifdef DEBUG + if (hc->ep_type == DWC_OTG_EP_TYPE_INTR && hc->do_split + && !hc->complete_split) { + switch (hfnum.b.frnum & 0x7) { + case 7: + core_if->hfnum_7_samples++; + core_if->hfnum_7_frrem_accum += hfnum.b.frrem; + break; + case 0: + core_if->hfnum_0_samples++; + core_if->hfnum_0_frrem_accum += hfnum.b.frrem; + break; + default: + core_if->hfnum_other_samples++; + core_if->hfnum_other_frrem_accum += + hfnum.b.frrem; + break; + } + } +#endif + } +} + +#ifdef DEBUG +void hc_xfer_timeout(void *ptr) +{ + hc_xfer_info_t *xfer_info = NULL; + int hc_num = 0; + + if (ptr) + xfer_info = (hc_xfer_info_t *) ptr; + + if (!xfer_info->hc) { + DWC_ERROR("xfer_info->hc = %p\n", xfer_info->hc); + return; + } + + hc_num = xfer_info->hc->hc_num; + DWC_WARN("%s: timeout on channel %d\n", __func__, hc_num); + DWC_WARN(" start_hcchar_val 0x%08x\n", + xfer_info->core_if->start_hcchar_val[hc_num]); +} +#endif + +void ep_xfer_timeout(void *ptr) +{ + ep_xfer_info_t *xfer_info = NULL; + int ep_num = 0; + dctl_data_t dctl = {.d32 = 0 }; + gintsts_data_t gintsts = {.d32 = 0 }; + gintmsk_data_t gintmsk = {.d32 = 0 }; + + if (ptr) + xfer_info = (ep_xfer_info_t *) ptr; + + if (!xfer_info->ep) { + DWC_ERROR("xfer_info->ep = %p\n", xfer_info->ep); + return; + } + + ep_num = xfer_info->ep->num; + DWC_WARN("%s: timeout on endpoit %d\n", __func__, ep_num); + /* Put the sate to 2 as it was time outed */ + xfer_info->state = 2; + + dctl.d32 = + DWC_READ_REG32(&xfer_info->core_if->dev_if->dev_global_regs->dctl); + gintsts.d32 = + DWC_READ_REG32(&xfer_info->core_if->core_global_regs->gintsts); + gintmsk.d32 = + DWC_READ_REG32(&xfer_info->core_if->core_global_regs->gintmsk); + + if (!gintmsk.b.goutnakeff) { + /* Unmask it */ + gintmsk.b.goutnakeff = 1; + DWC_WRITE_REG32(&xfer_info->core_if->core_global_regs->gintmsk, + gintmsk.d32); + + } + + if (!gintsts.b.goutnakeff) { + dctl.b.sgoutnak = 1; + } + DWC_WRITE_REG32(&xfer_info->core_if->dev_if->dev_global_regs->dctl, + dctl.d32); + +} + +static void set_pid_isoc(dwc_hc_t * hc) +{ + /* Set up the initial PID for the transfer. */ + if (hc->speed == DWC_OTG_EP_SPEED_HIGH) { + if (hc->ep_is_in) { + if (hc->multi_count == 1) { + hc->data_pid_start = DWC_OTG_HC_PID_DATA0; + } else if (hc->multi_count == 2) { + hc->data_pid_start = DWC_OTG_HC_PID_DATA1; + } else { + hc->data_pid_start = DWC_OTG_HC_PID_DATA2; + } + } else { + if (hc->multi_count == 1) { + hc->data_pid_start = DWC_OTG_HC_PID_DATA0; + } else { + hc->data_pid_start = DWC_OTG_HC_PID_MDATA; + } + } + } else { + hc->data_pid_start = DWC_OTG_HC_PID_DATA0; + } +} + +/** + * This function does the setup for a data transfer for a host channel and + * starts the transfer. May be called in either Slave mode or DMA mode. In + * Slave mode, the caller must ensure that there is sufficient space in the + * request queue and Tx Data FIFO. + * + * For an OUT transfer in Slave mode, it loads a data packet into the + * appropriate FIFO. If necessary, additional data packets will be loaded in + * the Host ISR. + * + * For an IN transfer in Slave mode, a data packet is requested. The data + * packets are unloaded from the Rx FIFO in the Host ISR. If necessary, + * additional data packets are requested in the Host ISR. + * + * For a PING transfer in Slave mode, the Do Ping bit is set in the HCTSIZ + * register along with a packet count of 1 and the channel is enabled. This + * causes a single PING transaction to occur. Other fields in HCTSIZ are + * simply set to 0 since no data transfer occurs in this case. + * + * For a PING transfer in DMA mode, the HCTSIZ register is initialized with + * all the information required to perform the subsequent data transfer. In + * addition, the Do Ping bit is set in the HCTSIZ register. In this case, the + * controller performs the entire PING protocol, then starts the data + * transfer. + * + * @param core_if Programming view of DWC_otg controller. + * @param hc Information needed to initialize the host channel. The xfer_len + * value may be reduced to accommodate the max widths of the XferSize and + * PktCnt fields in the HCTSIZn register. The multi_count value may be changed + * to reflect the final xfer_len value. + */ +void dwc_otg_hc_start_transfer(dwc_otg_core_if_t * core_if, dwc_hc_t * hc) +{ + hcchar_data_t hcchar; + hctsiz_data_t hctsiz; + uint16_t num_packets; + uint32_t max_hc_xfer_size = core_if->core_params->max_transfer_size; + uint16_t max_hc_pkt_count = core_if->core_params->max_packet_count; + dwc_otg_hc_regs_t *hc_regs = core_if->host_if->hc_regs[hc->hc_num]; + + hctsiz.d32 = 0; + + if (hc->do_ping) { + if (!core_if->dma_enable) { + dwc_otg_hc_do_ping(core_if, hc); + hc->xfer_started = 1; + return; + } else { + hctsiz.b.dopng = 1; + } + } + + if (hc->do_split) { + num_packets = 1; + + if (hc->complete_split && !hc->ep_is_in) { + /* For CSPLIT OUT Transfer, set the size to 0 so the + * core doesn't expect any data written to the FIFO */ + hc->xfer_len = 0; + } else if (hc->ep_is_in || (hc->xfer_len > hc->max_packet)) { + hc->xfer_len = hc->max_packet; + } else if (!hc->ep_is_in && (hc->xfer_len > 188)) { + hc->xfer_len = 188; + } + + hctsiz.b.xfersize = hc->xfer_len; + } else { + /* + * Ensure that the transfer length and packet count will fit + * in the widths allocated for them in the HCTSIZn register. + */ + if (hc->ep_type == DWC_OTG_EP_TYPE_INTR || + hc->ep_type == DWC_OTG_EP_TYPE_ISOC) { + /* + * Make sure the transfer size is no larger than one + * (micro)frame's worth of data. (A check was done + * when the periodic transfer was accepted to ensure + * that a (micro)frame's worth of data can be + * programmed into a channel.) + */ + uint32_t max_periodic_len = + hc->multi_count * hc->max_packet; + if (hc->xfer_len > max_periodic_len) { + hc->xfer_len = max_periodic_len; + } else { + } + } else if (hc->xfer_len > max_hc_xfer_size) { + /* Make sure that xfer_len is a multiple of max packet size. */ + hc->xfer_len = max_hc_xfer_size - hc->max_packet + 1; + } + + if (hc->xfer_len > 0) { + num_packets = + (hc->xfer_len + hc->max_packet - + 1) / hc->max_packet; + if (num_packets > max_hc_pkt_count) { + num_packets = max_hc_pkt_count; + hc->xfer_len = num_packets * hc->max_packet; + } + } else { + /* Need 1 packet for transfer length of 0. */ + num_packets = 1; + } + + if (hc->ep_is_in) { + /* Always program an integral # of max packets for IN transfers. */ + hc->xfer_len = num_packets * hc->max_packet; + } + + if (hc->ep_type == DWC_OTG_EP_TYPE_INTR || + hc->ep_type == DWC_OTG_EP_TYPE_ISOC) { + /* + * Make sure that the multi_count field matches the + * actual transfer length. + */ + hc->multi_count = num_packets; + } + + if (hc->ep_type == DWC_OTG_EP_TYPE_ISOC) + set_pid_isoc(hc); + + hctsiz.b.xfersize = hc->xfer_len; + } + + hc->start_pkt_count = num_packets; + hctsiz.b.pktcnt = num_packets; + hctsiz.b.pid = hc->data_pid_start; + DWC_WRITE_REG32(&hc_regs->hctsiz, hctsiz.d32); + + DWC_DEBUGPL(DBG_HCDV, "%s: Channel %d\n", __func__, hc->hc_num); + DWC_DEBUGPL(DBG_HCDV, " Xfer Size: %d\n", hctsiz.b.xfersize); + DWC_DEBUGPL(DBG_HCDV, " Num Pkts: %d\n", hctsiz.b.pktcnt); + DWC_DEBUGPL(DBG_HCDV, " Start PID: %d\n", hctsiz.b.pid); + + if (core_if->dma_enable) { + dwc_dma_t dma_addr; + if (hc->align_buff) { + dma_addr = hc->align_buff; + } else { + dma_addr = ((unsigned long)hc->xfer_buff & 0xffffffff); + } + DWC_WRITE_REG32(&hc_regs->hcdma, dma_addr); + } + + /* Start the split */ + if (hc->do_split) { + hcsplt_data_t hcsplt; + hcsplt.d32 = DWC_READ_REG32(&hc_regs->hcsplt); + hcsplt.b.spltena = 1; + DWC_WRITE_REG32(&hc_regs->hcsplt, hcsplt.d32); + } + + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + hcchar.b.multicnt = hc->multi_count; + hc_set_even_odd_frame(core_if, hc, &hcchar); +#ifdef DEBUG + core_if->start_hcchar_val[hc->hc_num] = hcchar.d32; + if (hcchar.b.chdis) { + DWC_WARN("%s: chdis set, channel %d, hcchar 0x%08x\n", + __func__, hc->hc_num, hcchar.d32); + } +#endif + + /* Set host channel enable after all other setup is complete. */ + hcchar.b.chen = 1; + hcchar.b.chdis = 0; + DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32); + + hc->xfer_started = 1; + hc->requests++; + + if (!core_if->dma_enable && !hc->ep_is_in && hc->xfer_len > 0) { + /* Load OUT packet into the appropriate Tx FIFO. */ + dwc_otg_hc_write_packet(core_if, hc); + } +#ifdef DEBUG + if (hc->ep_type != DWC_OTG_EP_TYPE_INTR) { + DWC_DEBUGPL(DBG_HCDV, "transfer %d from core_if %p\n", + hc->hc_num, core_if);//GRAYG + core_if->hc_xfer_info[hc->hc_num].core_if = core_if; + core_if->hc_xfer_info[hc->hc_num].hc = hc; + + /* Start a timer for this transfer. */ + DWC_TIMER_SCHEDULE(core_if->hc_xfer_timer[hc->hc_num], 10000); + } +#endif +} + +/** + * This function does the setup for a data transfer for a host channel + * and starts the transfer in Descriptor DMA mode. + * + * Initializes HCTSIZ register. For a PING transfer the Do Ping bit is set. + * Sets PID and NTD values. For periodic transfers + * initializes SCHED_INFO field with micro-frame bitmap. + * + * Initializes HCDMA register with descriptor list address and CTD value + * then starts the transfer via enabling the channel. + * + * @param core_if Programming view of DWC_otg controller. + * @param hc Information needed to initialize the host channel. + */ +void dwc_otg_hc_start_transfer_ddma(dwc_otg_core_if_t * core_if, dwc_hc_t * hc) +{ + dwc_otg_hc_regs_t *hc_regs = core_if->host_if->hc_regs[hc->hc_num]; + hcchar_data_t hcchar; + hctsiz_data_t hctsiz; + hcdma_data_t hcdma; + + hctsiz.d32 = 0; + + if (hc->do_ping) + hctsiz.b_ddma.dopng = 1; + + if (hc->ep_type == DWC_OTG_EP_TYPE_ISOC) + set_pid_isoc(hc); + + /* Packet Count and Xfer Size are not used in Descriptor DMA mode */ + hctsiz.b_ddma.pid = hc->data_pid_start; + hctsiz.b_ddma.ntd = hc->ntd - 1; /* 0 - 1 descriptor, 1 - 2 descriptors, etc. */ + hctsiz.b_ddma.schinfo = hc->schinfo; /* Non-zero only for high-speed interrupt endpoints */ + + DWC_DEBUGPL(DBG_HCDV, "%s: Channel %d\n", __func__, hc->hc_num); + DWC_DEBUGPL(DBG_HCDV, " Start PID: %d\n", hctsiz.b.pid); + DWC_DEBUGPL(DBG_HCDV, " NTD: %d\n", hctsiz.b_ddma.ntd); + + DWC_WRITE_REG32(&hc_regs->hctsiz, hctsiz.d32); + + hcdma.d32 = 0; + hcdma.b.dma_addr = ((uint32_t) hc->desc_list_addr) >> 11; + + /* Always start from first descriptor. */ + hcdma.b.ctd = 0; + DWC_WRITE_REG32(&hc_regs->hcdma, hcdma.d32); + + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + hcchar.b.multicnt = hc->multi_count; + +#ifdef DEBUG + core_if->start_hcchar_val[hc->hc_num] = hcchar.d32; + if (hcchar.b.chdis) { + DWC_WARN("%s: chdis set, channel %d, hcchar 0x%08x\n", + __func__, hc->hc_num, hcchar.d32); + } +#endif + + /* Set host channel enable after all other setup is complete. */ + hcchar.b.chen = 1; + hcchar.b.chdis = 0; + + DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32); + + hc->xfer_started = 1; + hc->requests++; + +#ifdef DEBUG + if ((hc->ep_type != DWC_OTG_EP_TYPE_INTR) + && (hc->ep_type != DWC_OTG_EP_TYPE_ISOC)) { + DWC_DEBUGPL(DBG_HCDV, "DMA transfer %d from core_if %p\n", + hc->hc_num, core_if);//GRAYG + core_if->hc_xfer_info[hc->hc_num].core_if = core_if; + core_if->hc_xfer_info[hc->hc_num].hc = hc; + /* Start a timer for this transfer. */ + DWC_TIMER_SCHEDULE(core_if->hc_xfer_timer[hc->hc_num], 10000); + } +#endif + +} + +/** + * This function continues a data transfer that was started by previous call + * to <code>dwc_otg_hc_start_transfer</code>. The caller must ensure there is + * sufficient space in the request queue and Tx Data FIFO. This function + * should only be called in Slave mode. In DMA mode, the controller acts + * autonomously to complete transfers programmed to a host channel. + * + * For an OUT transfer, a new data packet is loaded into the appropriate FIFO + * if there is any data remaining to be queued. For an IN transfer, another + * data packet is always requested. For the SETUP phase of a control transfer, + * this function does nothing. + * + * @return 1 if a new request is queued, 0 if no more requests are required + * for this transfer. + */ +int dwc_otg_hc_continue_transfer(dwc_otg_core_if_t * core_if, dwc_hc_t * hc) +{ + DWC_DEBUGPL(DBG_HCDV, "%s: Channel %d\n", __func__, hc->hc_num); + + if (hc->do_split) { + /* SPLITs always queue just once per channel */ + return 0; + } else if (hc->data_pid_start == DWC_OTG_HC_PID_SETUP) { + /* SETUPs are queued only once since they can't be NAKed. */ + return 0; + } else if (hc->ep_is_in) { + /* + * Always queue another request for other IN transfers. If + * back-to-back INs are issued and NAKs are received for both, + * the driver may still be processing the first NAK when the + * second NAK is received. When the interrupt handler clears + * the NAK interrupt for the first NAK, the second NAK will + * not be seen. So we can't depend on the NAK interrupt + * handler to requeue a NAKed request. Instead, IN requests + * are issued each time this function is called. When the + * transfer completes, the extra requests for the channel will + * be flushed. + */ + hcchar_data_t hcchar; + dwc_otg_hc_regs_t *hc_regs = + core_if->host_if->hc_regs[hc->hc_num]; + + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + hc_set_even_odd_frame(core_if, hc, &hcchar); + hcchar.b.chen = 1; + hcchar.b.chdis = 0; + DWC_DEBUGPL(DBG_HCDV, " IN xfer: hcchar = 0x%08x\n", + hcchar.d32); + DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32); + hc->requests++; + return 1; + } else { + /* OUT transfers. */ + if (hc->xfer_count < hc->xfer_len) { + if (hc->ep_type == DWC_OTG_EP_TYPE_INTR || + hc->ep_type == DWC_OTG_EP_TYPE_ISOC) { + hcchar_data_t hcchar; + dwc_otg_hc_regs_t *hc_regs; + hc_regs = core_if->host_if->hc_regs[hc->hc_num]; + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + hc_set_even_odd_frame(core_if, hc, &hcchar); + } + + /* Load OUT packet into the appropriate Tx FIFO. */ + dwc_otg_hc_write_packet(core_if, hc); + hc->requests++; + return 1; + } else { + return 0; + } + } +} + +/** + * Starts a PING transfer. This function should only be called in Slave mode. + * The Do Ping bit is set in the HCTSIZ register, then the channel is enabled. + */ +void dwc_otg_hc_do_ping(dwc_otg_core_if_t * core_if, dwc_hc_t * hc) +{ + hcchar_data_t hcchar; + hctsiz_data_t hctsiz; + dwc_otg_hc_regs_t *hc_regs = core_if->host_if->hc_regs[hc->hc_num]; + + DWC_DEBUGPL(DBG_HCDV, "%s: Channel %d\n", __func__, hc->hc_num); + + hctsiz.d32 = 0; + hctsiz.b.dopng = 1; + hctsiz.b.pktcnt = 1; + DWC_WRITE_REG32(&hc_regs->hctsiz, hctsiz.d32); + + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + hcchar.b.chen = 1; + hcchar.b.chdis = 0; + DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32); +} + +/* + * This function writes a packet into the Tx FIFO associated with the Host + * Channel. For a channel associated with a non-periodic EP, the non-periodic + * Tx FIFO is written. For a channel associated with a periodic EP, the + * periodic Tx FIFO is written. This function should only be called in Slave + * mode. + * + * Upon return the xfer_buff and xfer_count fields in _hc are incremented by + * then number of bytes written to the Tx FIFO. + */ +void dwc_otg_hc_write_packet(dwc_otg_core_if_t * core_if, dwc_hc_t * hc) +{ + uint32_t i; + uint32_t remaining_count; + uint32_t byte_count; + uint32_t dword_count; + + uint32_t *data_buff = (uint32_t *) (hc->xfer_buff); + uint32_t *data_fifo = core_if->data_fifo[hc->hc_num]; + + remaining_count = hc->xfer_len - hc->xfer_count; + if (remaining_count > hc->max_packet) { + byte_count = hc->max_packet; + } else { + byte_count = remaining_count; + } + + dword_count = (byte_count + 3) / 4; + + if ((((unsigned long)data_buff) & 0x3) == 0) { + /* xfer_buff is DWORD aligned. */ + for (i = 0; i < dword_count; i++, data_buff++) { + DWC_WRITE_REG32(data_fifo, *data_buff); + } + } else { + /* xfer_buff is not DWORD aligned. */ + for (i = 0; i < dword_count; i++, data_buff++) { + uint32_t data; + data = + (data_buff[0] | data_buff[1] << 8 | data_buff[2] << + 16 | data_buff[3] << 24); + DWC_WRITE_REG32(data_fifo, data); + } + } + + hc->xfer_count += byte_count; + hc->xfer_buff += byte_count; +} + +/** + * Gets the current USB frame number. This is the frame number from the last + * SOF packet. + */ +uint32_t dwc_otg_get_frame_number(dwc_otg_core_if_t * core_if) +{ + dsts_data_t dsts; + dsts.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dsts); + + /* read current frame/microframe number from DSTS register */ + return dsts.b.soffn; +} + +/** + * Calculates and gets the frame Interval value of HFIR register according PHY + * type and speed.The application can modify a value of HFIR register only after + * the Port Enable bit of the Host Port Control and Status register + * (HPRT.PrtEnaPort) has been set. +*/ + +uint32_t calc_frame_interval(dwc_otg_core_if_t * core_if) +{ + gusbcfg_data_t usbcfg; + hwcfg2_data_t hwcfg2; + hprt0_data_t hprt0; + int clock = 60; // default value + usbcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->gusbcfg); + hwcfg2.d32 = DWC_READ_REG32(&core_if->core_global_regs->ghwcfg2); + hprt0.d32 = DWC_READ_REG32(core_if->host_if->hprt0); + if (!usbcfg.b.physel && usbcfg.b.ulpi_utmi_sel && !usbcfg.b.phyif) + clock = 60; + if (usbcfg.b.physel && hwcfg2.b.fs_phy_type == 3) + clock = 48; + if (!usbcfg.b.phylpwrclksel && !usbcfg.b.physel && + !usbcfg.b.ulpi_utmi_sel && usbcfg.b.phyif) + clock = 30; + if (!usbcfg.b.phylpwrclksel && !usbcfg.b.physel && + !usbcfg.b.ulpi_utmi_sel && !usbcfg.b.phyif) + clock = 60; + if (usbcfg.b.phylpwrclksel && !usbcfg.b.physel && + !usbcfg.b.ulpi_utmi_sel && usbcfg.b.phyif) + clock = 48; + if (usbcfg.b.physel && !usbcfg.b.phyif && hwcfg2.b.fs_phy_type == 2) + clock = 48; + if (usbcfg.b.physel && hwcfg2.b.fs_phy_type == 1) + clock = 48; + if (hprt0.b.prtspd == 0) + /* High speed case */ + return 125 * clock - 1; + else + /* FS/LS case */ + return 1000 * clock - 1; +} + +/** + * This function reads a setup packet from the Rx FIFO into the destination + * buffer. This function is called from the Rx Status Queue Level (RxStsQLvl) + * Interrupt routine when a SETUP packet has been received in Slave mode. + * + * @param core_if Programming view of DWC_otg controller. + * @param dest Destination buffer for packet data. + */ +void dwc_otg_read_setup_packet(dwc_otg_core_if_t * core_if, uint32_t * dest) +{ + device_grxsts_data_t status; + /* Get the 8 bytes of a setup transaction data */ + + /* Pop 2 DWORDS off the receive data FIFO into memory */ + dest[0] = DWC_READ_REG32(core_if->data_fifo[0]); + dest[1] = DWC_READ_REG32(core_if->data_fifo[0]); + if (core_if->snpsid >= OTG_CORE_REV_3_00a) { + status.d32 = + DWC_READ_REG32(&core_if->core_global_regs->grxstsp); + DWC_DEBUGPL(DBG_ANY, + "EP:%d BCnt:%d " "pktsts:%x Frame:%d(0x%0x)\n", + status.b.epnum, status.b.bcnt, status.b.pktsts, + status.b.fn, status.b.fn); + } +} + +/** + * This function enables EP0 OUT to receive SETUP packets and configures EP0 + * IN for transmitting packets. It is normally called when the + * "Enumeration Done" interrupt occurs. + * + * @param core_if Programming view of DWC_otg controller. + * @param ep The EP0 data. + */ +void dwc_otg_ep0_activate(dwc_otg_core_if_t * core_if, dwc_ep_t * ep) +{ + dwc_otg_dev_if_t *dev_if = core_if->dev_if; + dsts_data_t dsts; + depctl_data_t diepctl; + depctl_data_t doepctl; + dctl_data_t dctl = {.d32 = 0 }; + + ep->stp_rollover = 0; + /* Read the Device Status and Endpoint 0 Control registers */ + dsts.d32 = DWC_READ_REG32(&dev_if->dev_global_regs->dsts); + diepctl.d32 = DWC_READ_REG32(&dev_if->in_ep_regs[0]->diepctl); + doepctl.d32 = DWC_READ_REG32(&dev_if->out_ep_regs[0]->doepctl); + + /* Set the MPS of the IN EP based on the enumeration speed */ + switch (dsts.b.enumspd) { + case DWC_DSTS_ENUMSPD_HS_PHY_30MHZ_OR_60MHZ: + case DWC_DSTS_ENUMSPD_FS_PHY_30MHZ_OR_60MHZ: + case DWC_DSTS_ENUMSPD_FS_PHY_48MHZ: + diepctl.b.mps = DWC_DEP0CTL_MPS_64; + break; + case DWC_DSTS_ENUMSPD_LS_PHY_6MHZ: + diepctl.b.mps = DWC_DEP0CTL_MPS_8; + break; + } + + DWC_WRITE_REG32(&dev_if->in_ep_regs[0]->diepctl, diepctl.d32); + + /* Enable OUT EP for receive */ + if (core_if->snpsid <= OTG_CORE_REV_2_94a) { + doepctl.b.epena = 1; + DWC_WRITE_REG32(&dev_if->out_ep_regs[0]->doepctl, doepctl.d32); + } +#ifdef VERBOSE + DWC_DEBUGPL(DBG_PCDV, "doepctl0=%0x\n", + DWC_READ_REG32(&dev_if->out_ep_regs[0]->doepctl)); + DWC_DEBUGPL(DBG_PCDV, "diepctl0=%0x\n", + DWC_READ_REG32(&dev_if->in_ep_regs[0]->diepctl)); +#endif + dctl.b.cgnpinnak = 1; + + DWC_MODIFY_REG32(&dev_if->dev_global_regs->dctl, dctl.d32, dctl.d32); + DWC_DEBUGPL(DBG_PCDV, "dctl=%0x\n", + DWC_READ_REG32(&dev_if->dev_global_regs->dctl)); + +} + +/** + * This function activates an EP. The Device EP control register for + * the EP is configured as defined in the ep structure. Note: This + * function is not used for EP0. + * + * @param core_if Programming view of DWC_otg controller. + * @param ep The EP to activate. + */ +void dwc_otg_ep_activate(dwc_otg_core_if_t * core_if, dwc_ep_t * ep) +{ + dwc_otg_dev_if_t *dev_if = core_if->dev_if; + depctl_data_t depctl; + volatile uint32_t *addr; + daint_data_t daintmsk = {.d32 = 0 }; + dcfg_data_t dcfg; + uint8_t i; + + DWC_DEBUGPL(DBG_PCDV, "%s() EP%d-%s\n", __func__, ep->num, + (ep->is_in ? "IN" : "OUT")); + +#ifdef DWC_UTE_PER_IO + ep->xiso_frame_num = 0xFFFFFFFF; + ep->xiso_active_xfers = 0; + ep->xiso_queued_xfers = 0; +#endif + /* Read DEPCTLn register */ + if (ep->is_in == 1) { + addr = &dev_if->in_ep_regs[ep->num]->diepctl; + daintmsk.ep.in = 1 << ep->num; + } else { + addr = &dev_if->out_ep_regs[ep->num]->doepctl; + daintmsk.ep.out = 1 << ep->num; + } + + /* If the EP is already active don't change the EP Control + * register. */ + depctl.d32 = DWC_READ_REG32(addr); + if (!depctl.b.usbactep) { + depctl.b.mps = ep->maxpacket; + depctl.b.eptype = ep->type; + depctl.b.txfnum = ep->tx_fifo_num; + + if (ep->type == DWC_OTG_EP_TYPE_ISOC) { + depctl.b.setd0pid = 1; // ??? + } else { + depctl.b.setd0pid = 1; + } + depctl.b.usbactep = 1; + + /* Update nextep_seq array and EPMSCNT in DCFG*/ + if (!(depctl.b.eptype & 1) && (ep->is_in == 1)) { // NP IN EP + for (i = 0; i <= core_if->dev_if->num_in_eps; i++) { + if (core_if->nextep_seq[i] == core_if->first_in_nextep_seq) + break; + } + core_if->nextep_seq[i] = ep->num; + core_if->nextep_seq[ep->num] = core_if->first_in_nextep_seq; + depctl.b.nextep = core_if->nextep_seq[ep->num]; + dcfg.d32 = DWC_READ_REG32(&dev_if->dev_global_regs->dcfg); + dcfg.b.epmscnt++; + DWC_WRITE_REG32(&dev_if->dev_global_regs->dcfg, dcfg.d32); + + DWC_DEBUGPL(DBG_PCDV, + "%s first_in_nextep_seq= %2d; nextep_seq[]:\n", + __func__, core_if->first_in_nextep_seq); + for (i=0; i <= core_if->dev_if->num_in_eps; i++) { + DWC_DEBUGPL(DBG_PCDV, "%2d\n", + core_if->nextep_seq[i]); + } + + } + + + DWC_WRITE_REG32(addr, depctl.d32); + DWC_DEBUGPL(DBG_PCDV, "DEPCTL=%08x\n", DWC_READ_REG32(addr)); + } + + /* Enable the Interrupt for this EP */ + if (core_if->multiproc_int_enable) { + if (ep->is_in == 1) { + diepmsk_data_t diepmsk = {.d32 = 0 }; + diepmsk.b.xfercompl = 1; + diepmsk.b.timeout = 1; + diepmsk.b.epdisabled = 1; + diepmsk.b.ahberr = 1; + diepmsk.b.intknepmis = 1; + if (!core_if->en_multiple_tx_fifo && core_if->dma_enable) + diepmsk.b.intknepmis = 0; + diepmsk.b.txfifoundrn = 1; //????? + if (ep->type == DWC_OTG_EP_TYPE_ISOC) { + diepmsk.b.nak = 1; + } + + + +/* + if (core_if->dma_desc_enable) { + diepmsk.b.bna = 1; + } +*/ +/* + if (core_if->dma_enable) { + doepmsk.b.nak = 1; + } +*/ + DWC_WRITE_REG32(&dev_if->dev_global_regs-> + diepeachintmsk[ep->num], diepmsk.d32); + + } else { + doepmsk_data_t doepmsk = {.d32 = 0 }; + doepmsk.b.xfercompl = 1; + doepmsk.b.ahberr = 1; + doepmsk.b.epdisabled = 1; + if (ep->type == DWC_OTG_EP_TYPE_ISOC) + doepmsk.b.outtknepdis = 1; + +/* + + if (core_if->dma_desc_enable) { + doepmsk.b.bna = 1; + } +*/ +/* + doepmsk.b.babble = 1; + doepmsk.b.nyet = 1; + doepmsk.b.nak = 1; +*/ + DWC_WRITE_REG32(&dev_if->dev_global_regs-> + doepeachintmsk[ep->num], doepmsk.d32); + } + DWC_MODIFY_REG32(&dev_if->dev_global_regs->deachintmsk, + 0, daintmsk.d32); + } else { + if (ep->type == DWC_OTG_EP_TYPE_ISOC) { + if (ep->is_in) { + diepmsk_data_t diepmsk = {.d32 = 0 }; + diepmsk.b.nak = 1; + DWC_MODIFY_REG32(&dev_if->dev_global_regs->diepmsk, 0, diepmsk.d32); + } else { + doepmsk_data_t doepmsk = {.d32 = 0 }; + doepmsk.b.outtknepdis = 1; + DWC_MODIFY_REG32(&dev_if->dev_global_regs->doepmsk, 0, doepmsk.d32); + } + } + DWC_MODIFY_REG32(&dev_if->dev_global_regs->daintmsk, + 0, daintmsk.d32); + } + + DWC_DEBUGPL(DBG_PCDV, "DAINTMSK=%0x\n", + DWC_READ_REG32(&dev_if->dev_global_regs->daintmsk)); + + ep->stall_clear_flag = 0; + + return; +} + +/** + * This function deactivates an EP. This is done by clearing the USB Active + * EP bit in the Device EP control register. Note: This function is not used + * for EP0. EP0 cannot be deactivated. + * + * @param core_if Programming view of DWC_otg controller. + * @param ep The EP to deactivate. + */ +void dwc_otg_ep_deactivate(dwc_otg_core_if_t * core_if, dwc_ep_t * ep) +{ + depctl_data_t depctl = {.d32 = 0 }; + volatile uint32_t *addr; + daint_data_t daintmsk = {.d32 = 0 }; + dcfg_data_t dcfg; + uint8_t i = 0; + +#ifdef DWC_UTE_PER_IO + ep->xiso_frame_num = 0xFFFFFFFF; + ep->xiso_active_xfers = 0; + ep->xiso_queued_xfers = 0; +#endif + + /* Read DEPCTLn register */ + if (ep->is_in == 1) { + addr = &core_if->dev_if->in_ep_regs[ep->num]->diepctl; + daintmsk.ep.in = 1 << ep->num; + } else { + addr = &core_if->dev_if->out_ep_regs[ep->num]->doepctl; + daintmsk.ep.out = 1 << ep->num; + } + + depctl.d32 = DWC_READ_REG32(addr); + + depctl.b.usbactep = 0; + + /* Update nextep_seq array and EPMSCNT in DCFG*/ + if (!(depctl.b.eptype & 1) && ep->is_in == 1) { // NP EP IN + for (i = 0; i <= core_if->dev_if->num_in_eps; i++) { + if (core_if->nextep_seq[i] == ep->num) + break; + } + core_if->nextep_seq[i] = core_if->nextep_seq[ep->num]; + if (core_if->first_in_nextep_seq == ep->num) + core_if->first_in_nextep_seq = i; + core_if->nextep_seq[ep->num] = 0xff; + depctl.b.nextep = 0; + dcfg.d32 = + DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dcfg); + dcfg.b.epmscnt--; + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->dcfg, + dcfg.d32); + + DWC_DEBUGPL(DBG_PCDV, + "%s first_in_nextep_seq= %2d; nextep_seq[]:\n", + __func__, core_if->first_in_nextep_seq); + for (i=0; i <= core_if->dev_if->num_in_eps; i++) { + DWC_DEBUGPL(DBG_PCDV, "%2d\n", core_if->nextep_seq[i]); + } + } + + if (ep->is_in == 1) + depctl.b.txfnum = 0; + + if (core_if->dma_desc_enable) + depctl.b.epdis = 1; + + DWC_WRITE_REG32(addr, depctl.d32); + depctl.d32 = DWC_READ_REG32(addr); + if (core_if->dma_enable && ep->type == DWC_OTG_EP_TYPE_ISOC + && depctl.b.epena) { + depctl_data_t depctl = {.d32 = 0}; + if (ep->is_in) { + diepint_data_t diepint = {.d32 = 0}; + + depctl.b.snak = 1; + DWC_WRITE_REG32(&core_if->dev_if->in_ep_regs[ep->num]-> + diepctl, depctl.d32); + do { + dwc_udelay(10); + diepint.d32 = + DWC_READ_REG32(&core_if-> + dev_if->in_ep_regs[ep->num]-> + diepint); + } while (!diepint.b.inepnakeff); + diepint.b.inepnakeff = 1; + DWC_WRITE_REG32(&core_if->dev_if->in_ep_regs[ep->num]-> + diepint, diepint.d32); + depctl.d32 = 0; + depctl.b.epdis = 1; + DWC_WRITE_REG32(&core_if->dev_if->in_ep_regs[ep->num]-> + diepctl, depctl.d32); + do { + dwc_udelay(10); + diepint.d32 = + DWC_READ_REG32(&core_if-> + dev_if->in_ep_regs[ep->num]-> + diepint); + } while (!diepint.b.epdisabled); + diepint.b.epdisabled = 1; + DWC_WRITE_REG32(&core_if->dev_if->in_ep_regs[ep->num]-> + diepint, diepint.d32); + } else { + dctl_data_t dctl = {.d32 = 0}; + gintmsk_data_t gintsts = {.d32 = 0}; + doepint_data_t doepint = {.d32 = 0}; + dctl.b.sgoutnak = 1; + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs-> + dctl, 0, dctl.d32); + do { + dwc_udelay(10); + gintsts.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintsts); + } while (!gintsts.b.goutnakeff); + gintsts.d32 = 0; + gintsts.b.goutnakeff = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, gintsts.d32); + + depctl.d32 = 0; + depctl.b.epdis = 1; + depctl.b.snak = 1; + DWC_WRITE_REG32(&core_if->dev_if->out_ep_regs[ep->num]->doepctl, depctl.d32); + do + { + dwc_udelay(10); + doepint.d32 = DWC_READ_REG32(&core_if->dev_if-> + out_ep_regs[ep->num]->doepint); + } while (!doepint.b.epdisabled); + + doepint.b.epdisabled = 1; + DWC_WRITE_REG32(&core_if->dev_if->out_ep_regs[ep->num]->doepint, doepint.d32); + + dctl.d32 = 0; + dctl.b.cgoutnak = 1; + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->dctl, 0, dctl.d32); + } + } + + /* Disable the Interrupt for this EP */ + if (core_if->multiproc_int_enable) { + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->deachintmsk, + daintmsk.d32, 0); + + if (ep->is_in == 1) { + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs-> + diepeachintmsk[ep->num], 0); + } else { + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs-> + doepeachintmsk[ep->num], 0); + } + } else { + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->daintmsk, + daintmsk.d32, 0); + } + +} + +/** + * This function initializes dma descriptor chain. + * + * @param core_if Programming view of DWC_otg controller. + * @param ep The EP to start the transfer on. + */ +static void init_dma_desc_chain(dwc_otg_core_if_t * core_if, dwc_ep_t * ep) +{ + dwc_otg_dev_dma_desc_t *dma_desc; + uint32_t offset; + uint32_t xfer_est; + int i; + unsigned maxxfer_local, total_len; + + if (!ep->is_in && ep->type == DWC_OTG_EP_TYPE_INTR && + (ep->maxpacket%4)) { + maxxfer_local = ep->maxpacket; + total_len = ep->xfer_len; + } else { + maxxfer_local = ep->maxxfer; + total_len = ep->total_len; + } + + ep->desc_cnt = (total_len / maxxfer_local) + + ((total_len % maxxfer_local) ? 1 : 0); + + if (!ep->desc_cnt) + ep->desc_cnt = 1; + + if (ep->desc_cnt > MAX_DMA_DESC_CNT) + ep->desc_cnt = MAX_DMA_DESC_CNT; + + dma_desc = ep->desc_addr; + if (maxxfer_local == ep->maxpacket) { + if ((total_len % maxxfer_local) && + (total_len/maxxfer_local < MAX_DMA_DESC_CNT)) { + xfer_est = (ep->desc_cnt - 1) * maxxfer_local + + (total_len % maxxfer_local); + } else + xfer_est = ep->desc_cnt * maxxfer_local; + } else + xfer_est = total_len; + offset = 0; + for (i = 0; i < ep->desc_cnt; ++i) { + /** DMA Descriptor Setup */ + if (xfer_est > maxxfer_local) { + dma_desc->status.b.bs = BS_HOST_BUSY; + dma_desc->status.b.l = 0; + dma_desc->status.b.ioc = 0; + dma_desc->status.b.sp = 0; + dma_desc->status.b.bytes = maxxfer_local; + dma_desc->buf = ep->dma_addr + offset; + dma_desc->status.b.sts = 0; + dma_desc->status.b.bs = BS_HOST_READY; + + xfer_est -= maxxfer_local; + offset += maxxfer_local; + } else { + dma_desc->status.b.bs = BS_HOST_BUSY; + dma_desc->status.b.l = 1; + dma_desc->status.b.ioc = 1; + if (ep->is_in) { + dma_desc->status.b.sp = + (xfer_est % + ep->maxpacket) ? 1 : ((ep-> + sent_zlp) ? 1 : 0); + dma_desc->status.b.bytes = xfer_est; + } else { + if (maxxfer_local == ep->maxpacket) + dma_desc->status.b.bytes = xfer_est; + else + dma_desc->status.b.bytes = + xfer_est + ((4 - (xfer_est & 0x3)) & 0x3); + } + + dma_desc->buf = ep->dma_addr + offset; + dma_desc->status.b.sts = 0; + dma_desc->status.b.bs = BS_HOST_READY; + } + dma_desc++; + } +} +/** + * This function is called when to write ISOC data into appropriate dedicated + * periodic FIFO. + */ +static int32_t write_isoc_tx_fifo(dwc_otg_core_if_t * core_if, dwc_ep_t * dwc_ep) +{ + dwc_otg_dev_if_t *dev_if = core_if->dev_if; + dwc_otg_dev_in_ep_regs_t *ep_regs; + dtxfsts_data_t txstatus = {.d32 = 0 }; + uint32_t len = 0; + int epnum = dwc_ep->num; + int dwords; + + DWC_DEBUGPL(DBG_PCD, "Dedicated TxFifo Empty: %d \n", epnum); + + ep_regs = core_if->dev_if->in_ep_regs[epnum]; + + len = dwc_ep->xfer_len - dwc_ep->xfer_count; + + if (len > dwc_ep->maxpacket) { + len = dwc_ep->maxpacket; + } + + dwords = (len + 3) / 4; + + /* While there is space in the queue and space in the FIFO and + * More data to tranfer, Write packets to the Tx FIFO */ + txstatus.d32 = DWC_READ_REG32(&dev_if->in_ep_regs[epnum]->dtxfsts); + DWC_DEBUGPL(DBG_PCDV, "b4 dtxfsts[%d]=0x%08x\n", epnum, txstatus.d32); + + while (txstatus.b.txfspcavail > dwords && + dwc_ep->xfer_count < dwc_ep->xfer_len && dwc_ep->xfer_len != 0) { + /* Write the FIFO */ + dwc_otg_ep_write_packet(core_if, dwc_ep, 0); + + len = dwc_ep->xfer_len - dwc_ep->xfer_count; + if (len > dwc_ep->maxpacket) { + len = dwc_ep->maxpacket; + } + + dwords = (len + 3) / 4; + txstatus.d32 = + DWC_READ_REG32(&dev_if->in_ep_regs[epnum]->dtxfsts); + DWC_DEBUGPL(DBG_PCDV, "dtxfsts[%d]=0x%08x\n", epnum, + txstatus.d32); + } + + DWC_DEBUGPL(DBG_PCDV, "b4 dtxfsts[%d]=0x%08x\n", epnum, + DWC_READ_REG32(&dev_if->in_ep_regs[epnum]->dtxfsts)); + + return 1; +} +/** + * This function does the setup for a data transfer for an EP and + * starts the transfer. For an IN transfer, the packets will be + * loaded into the appropriate Tx FIFO in the ISR. For OUT transfers, + * the packets are unloaded from the Rx FIFO in the ISR. the ISR. + * + * @param core_if Programming view of DWC_otg controller. + * @param ep The EP to start the transfer on. + */ + +void dwc_otg_ep_start_transfer(dwc_otg_core_if_t * core_if, dwc_ep_t * ep) +{ + depctl_data_t depctl; + deptsiz_data_t deptsiz; + gintmsk_data_t intr_mask = {.d32 = 0 }; + + DWC_DEBUGPL((DBG_PCDV | DBG_CILV), "%s()\n", __func__); + DWC_DEBUGPL(DBG_PCD, "ep%d-%s xfer_len=%d xfer_cnt=%d " + "xfer_buff=%p start_xfer_buff=%p, total_len = %d\n", + ep->num, (ep->is_in ? "IN" : "OUT"), ep->xfer_len, + ep->xfer_count, ep->xfer_buff, ep->start_xfer_buff, + ep->total_len); + /* IN endpoint */ + if (ep->is_in == 1) { + dwc_otg_dev_in_ep_regs_t *in_regs = + core_if->dev_if->in_ep_regs[ep->num]; + + gnptxsts_data_t gtxstatus; + + gtxstatus.d32 = + DWC_READ_REG32(&core_if->core_global_regs->gnptxsts); + + if (core_if->en_multiple_tx_fifo == 0 + && gtxstatus.b.nptxqspcavail == 0 && !core_if->dma_enable) { +#ifdef DEBUG + DWC_PRINTF("TX Queue Full (0x%0x)\n", gtxstatus.d32); +#endif + return; + } + + depctl.d32 = DWC_READ_REG32(&(in_regs->diepctl)); + deptsiz.d32 = DWC_READ_REG32(&(in_regs->dieptsiz)); + + if (ep->maxpacket > ep->maxxfer / MAX_PKT_CNT) + ep->xfer_len += (ep->maxxfer < (ep->total_len - ep->xfer_len)) ? + ep->maxxfer : (ep->total_len - ep->xfer_len); + else + ep->xfer_len += (MAX_PKT_CNT * ep->maxpacket < (ep->total_len - ep->xfer_len)) ? + MAX_PKT_CNT * ep->maxpacket : (ep->total_len - ep->xfer_len); + + + /* Zero Length Packet? */ + if ((ep->xfer_len - ep->xfer_count) == 0) { + deptsiz.b.xfersize = 0; + deptsiz.b.pktcnt = 1; + } else { + /* Program the transfer size and packet count + * as follows: xfersize = N * maxpacket + + * short_packet pktcnt = N + (short_packet + * exist ? 1 : 0) + */ + deptsiz.b.xfersize = ep->xfer_len - ep->xfer_count; + deptsiz.b.pktcnt = + (ep->xfer_len - ep->xfer_count - 1 + + ep->maxpacket) / ep->maxpacket; + if (deptsiz.b.pktcnt > MAX_PKT_CNT) { + deptsiz.b.pktcnt = MAX_PKT_CNT; + deptsiz.b.xfersize = deptsiz.b.pktcnt * ep->maxpacket; + } + if (ep->type == DWC_OTG_EP_TYPE_ISOC) + deptsiz.b.mc = deptsiz.b.pktcnt; + } + + /* Write the DMA register */ + if (core_if->dma_enable) { + if (core_if->dma_desc_enable == 0) { + if (ep->type != DWC_OTG_EP_TYPE_ISOC) + deptsiz.b.mc = 1; + DWC_WRITE_REG32(&in_regs->dieptsiz, + deptsiz.d32); + DWC_WRITE_REG32(&(in_regs->diepdma), + (uint32_t) ep->dma_addr); + } else { +#ifdef DWC_UTE_CFI + /* The descriptor chain should be already initialized by now */ + if (ep->buff_mode != BM_STANDARD) { + DWC_WRITE_REG32(&in_regs->diepdma, + ep->descs_dma_addr); + } else { +#endif + init_dma_desc_chain(core_if, ep); + /** DIEPDMAn Register write */ + DWC_WRITE_REG32(&in_regs->diepdma, + ep->dma_desc_addr); +#ifdef DWC_UTE_CFI + } +#endif + } + } else { + DWC_WRITE_REG32(&in_regs->dieptsiz, deptsiz.d32); + if (ep->type != DWC_OTG_EP_TYPE_ISOC) { + /** + * Enable the Non-Periodic Tx FIFO empty interrupt, + * or the Tx FIFO epmty interrupt in dedicated Tx FIFO mode, + * the data will be written into the fifo by the ISR. + */ + if (core_if->en_multiple_tx_fifo == 0) { + intr_mask.b.nptxfempty = 1; + DWC_MODIFY_REG32 + (&core_if->core_global_regs->gintmsk, + intr_mask.d32, intr_mask.d32); + } else { + /* Enable the Tx FIFO Empty Interrupt for this EP */ + if (ep->xfer_len > 0) { + uint32_t fifoemptymsk = 0; + fifoemptymsk = 1 << ep->num; + DWC_MODIFY_REG32 + (&core_if->dev_if->dev_global_regs->dtknqr4_fifoemptymsk, + 0, fifoemptymsk); + + } + } + } else { + write_isoc_tx_fifo(core_if, ep); + } + } + if (!core_if->core_params->en_multiple_tx_fifo && core_if->dma_enable) + depctl.b.nextep = core_if->nextep_seq[ep->num]; + + if (ep->type == DWC_OTG_EP_TYPE_ISOC) { + dsts_data_t dsts = {.d32 = 0}; + if (ep->bInterval == 1) { + dsts.d32 = + DWC_READ_REG32(&core_if->dev_if-> + dev_global_regs->dsts); + ep->frame_num = dsts.b.soffn + ep->bInterval; + if (ep->frame_num > 0x3FFF) { + ep->frm_overrun = 1; + ep->frame_num &= 0x3FFF; + } else + ep->frm_overrun = 0; + if (ep->frame_num & 0x1) { + depctl.b.setd1pid = 1; + } else { + depctl.b.setd0pid = 1; + } + } + } + /* EP enable, IN data in FIFO */ + depctl.b.cnak = 1; + depctl.b.epena = 1; + DWC_WRITE_REG32(&in_regs->diepctl, depctl.d32); + + } else { + /* OUT endpoint */ + dwc_otg_dev_out_ep_regs_t *out_regs = + core_if->dev_if->out_ep_regs[ep->num]; + + depctl.d32 = DWC_READ_REG32(&(out_regs->doepctl)); + deptsiz.d32 = DWC_READ_REG32(&(out_regs->doeptsiz)); + + if (!core_if->dma_desc_enable) { + if (ep->maxpacket > ep->maxxfer / MAX_PKT_CNT) + ep->xfer_len += (ep->maxxfer < (ep->total_len - ep->xfer_len)) ? + ep->maxxfer : (ep->total_len - ep->xfer_len); + else + ep->xfer_len += (MAX_PKT_CNT * ep->maxpacket < (ep->total_len + - ep->xfer_len)) ? MAX_PKT_CNT * ep->maxpacket : (ep->total_len - ep->xfer_len); + } + + /* Program the transfer size and packet count as follows: + * + * pktcnt = N + * xfersize = N * maxpacket + */ + if ((ep->xfer_len - ep->xfer_count) == 0) { + /* Zero Length Packet */ + deptsiz.b.xfersize = ep->maxpacket; + deptsiz.b.pktcnt = 1; + } else { + deptsiz.b.pktcnt = + (ep->xfer_len - ep->xfer_count + + (ep->maxpacket - 1)) / ep->maxpacket; + if (deptsiz.b.pktcnt > MAX_PKT_CNT) { + deptsiz.b.pktcnt = MAX_PKT_CNT; + } + if (!core_if->dma_desc_enable) { + ep->xfer_len = + deptsiz.b.pktcnt * ep->maxpacket + ep->xfer_count; + } + deptsiz.b.xfersize = ep->xfer_len - ep->xfer_count; + } + + DWC_DEBUGPL(DBG_PCDV, "ep%d xfersize=%d pktcnt=%d\n", + ep->num, deptsiz.b.xfersize, deptsiz.b.pktcnt); + + if (core_if->dma_enable) { + if (!core_if->dma_desc_enable) { + DWC_WRITE_REG32(&out_regs->doeptsiz, + deptsiz.d32); + + DWC_WRITE_REG32(&(out_regs->doepdma), + (uint32_t) ep->dma_addr); + } else { +#ifdef DWC_UTE_CFI + /* The descriptor chain should be already initialized by now */ + if (ep->buff_mode != BM_STANDARD) { + DWC_WRITE_REG32(&out_regs->doepdma, + ep->descs_dma_addr); + } else { +#endif + /** This is used for interrupt out transfers*/ + if (!ep->xfer_len) + ep->xfer_len = ep->total_len; + init_dma_desc_chain(core_if, ep); + + if (core_if->core_params->dev_out_nak) { + if (ep->type == DWC_OTG_EP_TYPE_BULK) { + deptsiz.b.pktcnt = (ep->total_len + + (ep->maxpacket - 1)) / ep->maxpacket; + deptsiz.b.xfersize = ep->total_len; + /* Remember initial value of doeptsiz */ + core_if->start_doeptsiz_val[ep->num] = deptsiz.d32; + DWC_WRITE_REG32(&out_regs->doeptsiz, + deptsiz.d32); + } + } + /** DOEPDMAn Register write */ + DWC_WRITE_REG32(&out_regs->doepdma, + ep->dma_desc_addr); +#ifdef DWC_UTE_CFI + } +#endif + } + } else { + DWC_WRITE_REG32(&out_regs->doeptsiz, deptsiz.d32); + } + + if (ep->type == DWC_OTG_EP_TYPE_ISOC) { + dsts_data_t dsts = {.d32 = 0}; + if (ep->bInterval == 1) { + dsts.d32 = + DWC_READ_REG32(&core_if->dev_if-> + dev_global_regs->dsts); + ep->frame_num = dsts.b.soffn + ep->bInterval; + if (ep->frame_num > 0x3FFF) { + ep->frm_overrun = 1; + ep->frame_num &= 0x3FFF; + } else + ep->frm_overrun = 0; + + if (ep->frame_num & 0x1) { + depctl.b.setd1pid = 1; + } else { + depctl.b.setd0pid = 1; + } + } + } + + /* EP enable */ + depctl.b.cnak = 1; + depctl.b.epena = 1; + + DWC_WRITE_REG32(&out_regs->doepctl, depctl.d32); + + DWC_DEBUGPL(DBG_PCD, "DOEPCTL=%08x DOEPTSIZ=%08x\n", + DWC_READ_REG32(&out_regs->doepctl), + DWC_READ_REG32(&out_regs->doeptsiz)); + DWC_DEBUGPL(DBG_PCD, "DAINTMSK=%08x GINTMSK=%08x\n", + DWC_READ_REG32(&core_if->dev_if->dev_global_regs-> + daintmsk), + DWC_READ_REG32(&core_if->core_global_regs-> + gintmsk)); + + /* Timer is scheduling only for out bulk transfers for + * "Device DDMA OUT NAK Enhancement" feature to inform user + * about received data payload in case of timeout + */ + if (core_if->core_params->dev_out_nak) { + if (ep->type == DWC_OTG_EP_TYPE_BULK) { + core_if->ep_xfer_info[ep->num].core_if = core_if; + core_if->ep_xfer_info[ep->num].ep = ep; + core_if->ep_xfer_info[ep->num].state = 1; + + /* Start a timer for this transfer. */ + DWC_TIMER_SCHEDULE(core_if->ep_xfer_timer[ep->num], 10000); + } + } + } +} + +/** + * This function setup a zero length transfer in Buffer DMA and + * Slave modes for usb requests with zero field set + * + * @param core_if Programming view of DWC_otg controller. + * @param ep The EP to start the transfer on. + * + */ +void dwc_otg_ep_start_zl_transfer(dwc_otg_core_if_t * core_if, dwc_ep_t * ep) +{ + + depctl_data_t depctl; + deptsiz_data_t deptsiz; + gintmsk_data_t intr_mask = {.d32 = 0 }; + + DWC_DEBUGPL((DBG_PCDV | DBG_CILV), "%s()\n", __func__); + DWC_PRINTF("zero length transfer is called\n"); + + /* IN endpoint */ + if (ep->is_in == 1) { + dwc_otg_dev_in_ep_regs_t *in_regs = + core_if->dev_if->in_ep_regs[ep->num]; + + depctl.d32 = DWC_READ_REG32(&(in_regs->diepctl)); + deptsiz.d32 = DWC_READ_REG32(&(in_regs->dieptsiz)); + + deptsiz.b.xfersize = 0; + deptsiz.b.pktcnt = 1; + + /* Write the DMA register */ + if (core_if->dma_enable) { + if (core_if->dma_desc_enable == 0) { + deptsiz.b.mc = 1; + DWC_WRITE_REG32(&in_regs->dieptsiz, + deptsiz.d32); + DWC_WRITE_REG32(&(in_regs->diepdma), + (uint32_t) ep->dma_addr); + } + } else { + DWC_WRITE_REG32(&in_regs->dieptsiz, deptsiz.d32); + /** + * Enable the Non-Periodic Tx FIFO empty interrupt, + * or the Tx FIFO epmty interrupt in dedicated Tx FIFO mode, + * the data will be written into the fifo by the ISR. + */ + if (core_if->en_multiple_tx_fifo == 0) { + intr_mask.b.nptxfempty = 1; + DWC_MODIFY_REG32(&core_if-> + core_global_regs->gintmsk, + intr_mask.d32, intr_mask.d32); + } else { + /* Enable the Tx FIFO Empty Interrupt for this EP */ + if (ep->xfer_len > 0) { + uint32_t fifoemptymsk = 0; + fifoemptymsk = 1 << ep->num; + DWC_MODIFY_REG32(&core_if-> + dev_if->dev_global_regs->dtknqr4_fifoemptymsk, + 0, fifoemptymsk); + } + } + } + + if (!core_if->core_params->en_multiple_tx_fifo && core_if->dma_enable) + depctl.b.nextep = core_if->nextep_seq[ep->num]; + /* EP enable, IN data in FIFO */ + depctl.b.cnak = 1; + depctl.b.epena = 1; + DWC_WRITE_REG32(&in_regs->diepctl, depctl.d32); + + } else { + /* OUT endpoint */ + dwc_otg_dev_out_ep_regs_t *out_regs = + core_if->dev_if->out_ep_regs[ep->num]; + + depctl.d32 = DWC_READ_REG32(&(out_regs->doepctl)); + deptsiz.d32 = DWC_READ_REG32(&(out_regs->doeptsiz)); + + /* Zero Length Packet */ + deptsiz.b.xfersize = ep->maxpacket; + deptsiz.b.pktcnt = 1; + + if (core_if->dma_enable) { + if (!core_if->dma_desc_enable) { + DWC_WRITE_REG32(&out_regs->doeptsiz, + deptsiz.d32); + + DWC_WRITE_REG32(&(out_regs->doepdma), + (uint32_t) ep->dma_addr); + } + } else { + DWC_WRITE_REG32(&out_regs->doeptsiz, deptsiz.d32); + } + + /* EP enable */ + depctl.b.cnak = 1; + depctl.b.epena = 1; + + DWC_WRITE_REG32(&out_regs->doepctl, depctl.d32); + + } +} + +/** + * This function does the setup for a data transfer for EP0 and starts + * the transfer. For an IN transfer, the packets will be loaded into + * the appropriate Tx FIFO in the ISR. For OUT transfers, the packets are + * unloaded from the Rx FIFO in the ISR. + * + * @param core_if Programming view of DWC_otg controller. + * @param ep The EP0 data. + */ +void dwc_otg_ep0_start_transfer(dwc_otg_core_if_t * core_if, dwc_ep_t * ep) +{ + depctl_data_t depctl; + deptsiz0_data_t deptsiz; + gintmsk_data_t intr_mask = {.d32 = 0 }; + dwc_otg_dev_dma_desc_t *dma_desc; + + DWC_DEBUGPL(DBG_PCD, "ep%d-%s xfer_len=%d xfer_cnt=%d " + "xfer_buff=%p start_xfer_buff=%p \n", + ep->num, (ep->is_in ? "IN" : "OUT"), ep->xfer_len, + ep->xfer_count, ep->xfer_buff, ep->start_xfer_buff); + + ep->total_len = ep->xfer_len; + + /* IN endpoint */ + if (ep->is_in == 1) { + dwc_otg_dev_in_ep_regs_t *in_regs = + core_if->dev_if->in_ep_regs[0]; + + gnptxsts_data_t gtxstatus; + + if (core_if->snpsid >= OTG_CORE_REV_3_00a) { + depctl.d32 = DWC_READ_REG32(&in_regs->diepctl); + if (depctl.b.epena) + return; + } + + gtxstatus.d32 = + DWC_READ_REG32(&core_if->core_global_regs->gnptxsts); + + /* If dedicated FIFO every time flush fifo before enable ep*/ + if (core_if->en_multiple_tx_fifo && core_if->snpsid >= OTG_CORE_REV_3_00a) + dwc_otg_flush_tx_fifo(core_if, ep->tx_fifo_num); + + if (core_if->en_multiple_tx_fifo == 0 + && gtxstatus.b.nptxqspcavail == 0 + && !core_if->dma_enable) { +#ifdef DEBUG + deptsiz.d32 = DWC_READ_REG32(&in_regs->dieptsiz); + DWC_DEBUGPL(DBG_PCD, "DIEPCTL0=%0x\n", + DWC_READ_REG32(&in_regs->diepctl)); + DWC_DEBUGPL(DBG_PCD, "DIEPTSIZ0=%0x (sz=%d, pcnt=%d)\n", + deptsiz.d32, + deptsiz.b.xfersize, deptsiz.b.pktcnt); + DWC_PRINTF("TX Queue or FIFO Full (0x%0x)\n", + gtxstatus.d32); +#endif + return; + } + + depctl.d32 = DWC_READ_REG32(&in_regs->diepctl); + deptsiz.d32 = DWC_READ_REG32(&in_regs->dieptsiz); + + /* Zero Length Packet? */ + if (ep->xfer_len == 0) { + deptsiz.b.xfersize = 0; + deptsiz.b.pktcnt = 1; + } else { + /* Program the transfer size and packet count + * as follows: xfersize = N * maxpacket + + * short_packet pktcnt = N + (short_packet + * exist ? 1 : 0) + */ + if (ep->xfer_len > ep->maxpacket) { + ep->xfer_len = ep->maxpacket; + deptsiz.b.xfersize = ep->maxpacket; + } else { + deptsiz.b.xfersize = ep->xfer_len; + } + deptsiz.b.pktcnt = 1; + + } + DWC_DEBUGPL(DBG_PCDV, + "IN len=%d xfersize=%d pktcnt=%d [%08x]\n", + ep->xfer_len, deptsiz.b.xfersize, deptsiz.b.pktcnt, + deptsiz.d32); + + /* Write the DMA register */ + if (core_if->dma_enable) { + if (core_if->dma_desc_enable == 0) { + DWC_WRITE_REG32(&in_regs->dieptsiz, + deptsiz.d32); + + DWC_WRITE_REG32(&(in_regs->diepdma), + (uint32_t) ep->dma_addr); + } else { + dma_desc = core_if->dev_if->in_desc_addr; + + /** DMA Descriptor Setup */ + dma_desc->status.b.bs = BS_HOST_BUSY; + dma_desc->status.b.l = 1; + dma_desc->status.b.ioc = 1; + dma_desc->status.b.sp = + (ep->xfer_len == ep->maxpacket) ? 0 : 1; + dma_desc->status.b.bytes = ep->xfer_len; + dma_desc->buf = ep->dma_addr; + dma_desc->status.b.sts = 0; + dma_desc->status.b.bs = BS_HOST_READY; + + /** DIEPDMA0 Register write */ + DWC_WRITE_REG32(&in_regs->diepdma, + core_if-> + dev_if->dma_in_desc_addr); + } + } else { + DWC_WRITE_REG32(&in_regs->dieptsiz, deptsiz.d32); + } + + if (!core_if->core_params->en_multiple_tx_fifo && core_if->dma_enable) + depctl.b.nextep = core_if->nextep_seq[ep->num]; + /* EP enable, IN data in FIFO */ + depctl.b.cnak = 1; + depctl.b.epena = 1; + DWC_WRITE_REG32(&in_regs->diepctl, depctl.d32); + + /** + * Enable the Non-Periodic Tx FIFO empty interrupt, the + * data will be written into the fifo by the ISR. + */ + if (!core_if->dma_enable) { + if (core_if->en_multiple_tx_fifo == 0) { + intr_mask.b.nptxfempty = 1; + DWC_MODIFY_REG32(&core_if-> + core_global_regs->gintmsk, + intr_mask.d32, intr_mask.d32); + } else { + /* Enable the Tx FIFO Empty Interrupt for this EP */ + if (ep->xfer_len > 0) { + uint32_t fifoemptymsk = 0; + fifoemptymsk |= 1 << ep->num; + DWC_MODIFY_REG32(&core_if-> + dev_if->dev_global_regs->dtknqr4_fifoemptymsk, + 0, fifoemptymsk); + } + } + } + } else { + /* OUT endpoint */ + dwc_otg_dev_out_ep_regs_t *out_regs = + core_if->dev_if->out_ep_regs[0]; + + depctl.d32 = DWC_READ_REG32(&out_regs->doepctl); + deptsiz.d32 = DWC_READ_REG32(&out_regs->doeptsiz); + + /* Program the transfer size and packet count as follows: + * xfersize = N * (maxpacket + 4 - (maxpacket % 4)) + * pktcnt = N */ + /* Zero Length Packet */ + deptsiz.b.xfersize = ep->maxpacket; + deptsiz.b.pktcnt = 1; + if (core_if->snpsid >= OTG_CORE_REV_3_00a) + deptsiz.b.supcnt = 3; + + DWC_DEBUGPL(DBG_PCDV, "len=%d xfersize=%d pktcnt=%d\n", + ep->xfer_len, deptsiz.b.xfersize, deptsiz.b.pktcnt); + + if (core_if->dma_enable) { + if (!core_if->dma_desc_enable) { + DWC_WRITE_REG32(&out_regs->doeptsiz, + deptsiz.d32); + + DWC_WRITE_REG32(&(out_regs->doepdma), + (uint32_t) ep->dma_addr); + } else { + dma_desc = core_if->dev_if->out_desc_addr; + + /** DMA Descriptor Setup */ + dma_desc->status.b.bs = BS_HOST_BUSY; + if (core_if->snpsid >= OTG_CORE_REV_3_00a) { + dma_desc->status.b.mtrf = 0; + dma_desc->status.b.sr = 0; + } + dma_desc->status.b.l = 1; + dma_desc->status.b.ioc = 1; + dma_desc->status.b.bytes = ep->maxpacket; + dma_desc->buf = ep->dma_addr; + dma_desc->status.b.sts = 0; + dma_desc->status.b.bs = BS_HOST_READY; + + /** DOEPDMA0 Register write */ + DWC_WRITE_REG32(&out_regs->doepdma, + core_if->dev_if-> + dma_out_desc_addr); + } + } else { + DWC_WRITE_REG32(&out_regs->doeptsiz, deptsiz.d32); + } + + /* EP enable */ + depctl.b.cnak = 1; + depctl.b.epena = 1; + DWC_WRITE_REG32(&(out_regs->doepctl), depctl.d32); + } +} + +/** + * This function continues control IN transfers started by + * dwc_otg_ep0_start_transfer, when the transfer does not fit in a + * single packet. NOTE: The DIEPCTL0/DOEPCTL0 registers only have one + * bit for the packet count. + * + * @param core_if Programming view of DWC_otg controller. + * @param ep The EP0 data. + */ +void dwc_otg_ep0_continue_transfer(dwc_otg_core_if_t * core_if, dwc_ep_t * ep) +{ + depctl_data_t depctl; + deptsiz0_data_t deptsiz; + gintmsk_data_t intr_mask = {.d32 = 0 }; + dwc_otg_dev_dma_desc_t *dma_desc; + + if (ep->is_in == 1) { + dwc_otg_dev_in_ep_regs_t *in_regs = + core_if->dev_if->in_ep_regs[0]; + gnptxsts_data_t tx_status = {.d32 = 0 }; + + tx_status.d32 = + DWC_READ_REG32(&core_if->core_global_regs->gnptxsts); + /** @todo Should there be check for room in the Tx + * Status Queue. If not remove the code above this comment. */ + + depctl.d32 = DWC_READ_REG32(&in_regs->diepctl); + deptsiz.d32 = DWC_READ_REG32(&in_regs->dieptsiz); + + /* Program the transfer size and packet count + * as follows: xfersize = N * maxpacket + + * short_packet pktcnt = N + (short_packet + * exist ? 1 : 0) + */ + + if (core_if->dma_desc_enable == 0) { + deptsiz.b.xfersize = + (ep->total_len - ep->xfer_count) > + ep->maxpacket ? ep->maxpacket : (ep->total_len - + ep->xfer_count); + deptsiz.b.pktcnt = 1; + if (core_if->dma_enable == 0) { + ep->xfer_len += deptsiz.b.xfersize; + } else { + ep->xfer_len = deptsiz.b.xfersize; + } + DWC_WRITE_REG32(&in_regs->dieptsiz, deptsiz.d32); + } else { + ep->xfer_len = + (ep->total_len - ep->xfer_count) > + ep->maxpacket ? ep->maxpacket : (ep->total_len - + ep->xfer_count); + + dma_desc = core_if->dev_if->in_desc_addr; + + /** DMA Descriptor Setup */ + dma_desc->status.b.bs = BS_HOST_BUSY; + dma_desc->status.b.l = 1; + dma_desc->status.b.ioc = 1; + dma_desc->status.b.sp = + (ep->xfer_len == ep->maxpacket) ? 0 : 1; + dma_desc->status.b.bytes = ep->xfer_len; + dma_desc->buf = ep->dma_addr; + dma_desc->status.b.sts = 0; + dma_desc->status.b.bs = BS_HOST_READY; + + /** DIEPDMA0 Register write */ + DWC_WRITE_REG32(&in_regs->diepdma, + core_if->dev_if->dma_in_desc_addr); + } + + DWC_DEBUGPL(DBG_PCDV, + "IN len=%d xfersize=%d pktcnt=%d [%08x]\n", + ep->xfer_len, deptsiz.b.xfersize, deptsiz.b.pktcnt, + deptsiz.d32); + + /* Write the DMA register */ + if (core_if->hwcfg2.b.architecture == DWC_INT_DMA_ARCH) { + if (core_if->dma_desc_enable == 0) + DWC_WRITE_REG32(&(in_regs->diepdma), + (uint32_t) ep->dma_addr); + } + if (!core_if->core_params->en_multiple_tx_fifo && core_if->dma_enable) + depctl.b.nextep = core_if->nextep_seq[ep->num]; + /* EP enable, IN data in FIFO */ + depctl.b.cnak = 1; + depctl.b.epena = 1; + DWC_WRITE_REG32(&in_regs->diepctl, depctl.d32); + + /** + * Enable the Non-Periodic Tx FIFO empty interrupt, the + * data will be written into the fifo by the ISR. + */ + if (!core_if->dma_enable) { + if (core_if->en_multiple_tx_fifo == 0) { + /* First clear it from GINTSTS */ + intr_mask.b.nptxfempty = 1; + DWC_MODIFY_REG32(&core_if-> + core_global_regs->gintmsk, + intr_mask.d32, intr_mask.d32); + + } else { + /* Enable the Tx FIFO Empty Interrupt for this EP */ + if (ep->xfer_len > 0) { + uint32_t fifoemptymsk = 0; + fifoemptymsk |= 1 << ep->num; + DWC_MODIFY_REG32(&core_if-> + dev_if->dev_global_regs->dtknqr4_fifoemptymsk, + 0, fifoemptymsk); + } + } + } + } else { + dwc_otg_dev_out_ep_regs_t *out_regs = + core_if->dev_if->out_ep_regs[0]; + + depctl.d32 = DWC_READ_REG32(&out_regs->doepctl); + deptsiz.d32 = DWC_READ_REG32(&out_regs->doeptsiz); + + /* Program the transfer size and packet count + * as follows: xfersize = N * maxpacket + + * short_packet pktcnt = N + (short_packet + * exist ? 1 : 0) + */ + deptsiz.b.xfersize = ep->maxpacket; + deptsiz.b.pktcnt = 1; + + if (core_if->dma_desc_enable == 0) { + DWC_WRITE_REG32(&out_regs->doeptsiz, deptsiz.d32); + } else { + dma_desc = core_if->dev_if->out_desc_addr; + + /** DMA Descriptor Setup */ + dma_desc->status.b.bs = BS_HOST_BUSY; + dma_desc->status.b.l = 1; + dma_desc->status.b.ioc = 1; + dma_desc->status.b.bytes = ep->maxpacket; + dma_desc->buf = ep->dma_addr; + dma_desc->status.b.sts = 0; + dma_desc->status.b.bs = BS_HOST_READY; + + /** DOEPDMA0 Register write */ + DWC_WRITE_REG32(&out_regs->doepdma, + core_if->dev_if->dma_out_desc_addr); + } + + DWC_DEBUGPL(DBG_PCDV, + "IN len=%d xfersize=%d pktcnt=%d [%08x]\n", + ep->xfer_len, deptsiz.b.xfersize, deptsiz.b.pktcnt, + deptsiz.d32); + + /* Write the DMA register */ + if (core_if->hwcfg2.b.architecture == DWC_INT_DMA_ARCH) { + if (core_if->dma_desc_enable == 0) + DWC_WRITE_REG32(&(out_regs->doepdma), + (uint32_t) ep->dma_addr); + + } + + /* EP enable, IN data in FIFO */ + depctl.b.cnak = 1; + depctl.b.epena = 1; + DWC_WRITE_REG32(&out_regs->doepctl, depctl.d32); + + } +} + +#ifdef DEBUG +void dump_msg(const u8 * buf, unsigned int length) +{ + unsigned int start, num, i; + char line[52], *p; + + if (length >= 512) + return; + start = 0; + while (length > 0) { + num = length < 16u ? length : 16u; + p = line; + for (i = 0; i < num; ++i) { + if (i == 8) + *p++ = ' '; + DWC_SPRINTF(p, " %02x", buf[i]); + p += 3; + } + *p = 0; + DWC_PRINTF("%6x: %s\n", start, line); + buf += num; + start += num; + length -= num; + } +} +#else +static inline void dump_msg(const u8 * buf, unsigned int length) +{ +} +#endif + +/** + * This function writes a packet into the Tx FIFO associated with the + * EP. For non-periodic EPs the non-periodic Tx FIFO is written. For + * periodic EPs the periodic Tx FIFO associated with the EP is written + * with all packets for the next micro-frame. + * + * @param core_if Programming view of DWC_otg controller. + * @param ep The EP to write packet for. + * @param dma Indicates if DMA is being used. + */ +void dwc_otg_ep_write_packet(dwc_otg_core_if_t * core_if, dwc_ep_t * ep, + int dma) +{ + /** + * The buffer is padded to DWORD on a per packet basis in + * slave/dma mode if the MPS is not DWORD aligned. The last + * packet, if short, is also padded to a multiple of DWORD. + * + * ep->xfer_buff always starts DWORD aligned in memory and is a + * multiple of DWORD in length + * + * ep->xfer_len can be any number of bytes + * + * ep->xfer_count is a multiple of ep->maxpacket until the last + * packet + * + * FIFO access is DWORD */ + + uint32_t i; + uint32_t byte_count; + uint32_t dword_count; + uint32_t *fifo; + uint32_t *data_buff = (uint32_t *) ep->xfer_buff; + + DWC_DEBUGPL((DBG_PCDV | DBG_CILV), "%s(%p,%p)\n", __func__, core_if, + ep); + if (ep->xfer_count >= ep->xfer_len) { + DWC_WARN("%s() No data for EP%d!!!\n", __func__, ep->num); + return; + } + + /* Find the byte length of the packet either short packet or MPS */ + if ((ep->xfer_len - ep->xfer_count) < ep->maxpacket) { + byte_count = ep->xfer_len - ep->xfer_count; + } else { + byte_count = ep->maxpacket; + } + + /* Find the DWORD length, padded by extra bytes as neccessary if MPS + * is not a multiple of DWORD */ + dword_count = (byte_count + 3) / 4; + +#ifdef VERBOSE + dump_msg(ep->xfer_buff, byte_count); +#endif + + /**@todo NGS Where are the Periodic Tx FIFO addresses + * intialized? What should this be? */ + + fifo = core_if->data_fifo[ep->num]; + + DWC_DEBUGPL((DBG_PCDV | DBG_CILV), "fifo=%p buff=%p *p=%08x bc=%d\n", + fifo, data_buff, *data_buff, byte_count); + + if (!dma) { + for (i = 0; i < dword_count; i++, data_buff++) { + DWC_WRITE_REG32(fifo, *data_buff); + } + } + + ep->xfer_count += byte_count; + ep->xfer_buff += byte_count; + ep->dma_addr += byte_count; +} + +/** + * Set the EP STALL. + * + * @param core_if Programming view of DWC_otg controller. + * @param ep The EP to set the stall on. + */ +void dwc_otg_ep_set_stall(dwc_otg_core_if_t * core_if, dwc_ep_t * ep) +{ + depctl_data_t depctl; + volatile uint32_t *depctl_addr; + + DWC_DEBUGPL(DBG_PCD, "%s ep%d-%s\n", __func__, ep->num, + (ep->is_in ? "IN" : "OUT")); + + if (ep->is_in == 1) { + depctl_addr = &(core_if->dev_if->in_ep_regs[ep->num]->diepctl); + depctl.d32 = DWC_READ_REG32(depctl_addr); + + /* set the disable and stall bits */ + if (depctl.b.epena) { + depctl.b.epdis = 1; + } + depctl.b.stall = 1; + DWC_WRITE_REG32(depctl_addr, depctl.d32); + } else { + depctl_addr = &(core_if->dev_if->out_ep_regs[ep->num]->doepctl); + depctl.d32 = DWC_READ_REG32(depctl_addr); + + /* set the stall bit */ + depctl.b.stall = 1; + DWC_WRITE_REG32(depctl_addr, depctl.d32); + } + + DWC_DEBUGPL(DBG_PCD, "DEPCTL=%0x\n", DWC_READ_REG32(depctl_addr)); + + return; +} + +/** + * Clear the EP STALL. + * + * @param core_if Programming view of DWC_otg controller. + * @param ep The EP to clear stall from. + */ +void dwc_otg_ep_clear_stall(dwc_otg_core_if_t * core_if, dwc_ep_t * ep) +{ + depctl_data_t depctl; + volatile uint32_t *depctl_addr; + + DWC_DEBUGPL(DBG_PCD, "%s ep%d-%s\n", __func__, ep->num, + (ep->is_in ? "IN" : "OUT")); + + if (ep->is_in == 1) { + depctl_addr = &(core_if->dev_if->in_ep_regs[ep->num]->diepctl); + } else { + depctl_addr = &(core_if->dev_if->out_ep_regs[ep->num]->doepctl); + } + + depctl.d32 = DWC_READ_REG32(depctl_addr); + + /* clear the stall bits */ + depctl.b.stall = 0; + + /* + * USB Spec 9.4.5: For endpoints using data toggle, regardless + * of whether an endpoint has the Halt feature set, a + * ClearFeature(ENDPOINT_HALT) request always results in the + * data toggle being reinitialized to DATA0. + */ + if (ep->type == DWC_OTG_EP_TYPE_INTR || + ep->type == DWC_OTG_EP_TYPE_BULK) { + depctl.b.setd0pid = 1; /* DATA0 */ + } + + DWC_WRITE_REG32(depctl_addr, depctl.d32); + DWC_DEBUGPL(DBG_PCD, "DEPCTL=%0x\n", DWC_READ_REG32(depctl_addr)); + return; +} + +/** + * This function reads a packet from the Rx FIFO into the destination + * buffer. To read SETUP data use dwc_otg_read_setup_packet. + * + * @param core_if Programming view of DWC_otg controller. + * @param dest Destination buffer for the packet. + * @param bytes Number of bytes to copy to the destination. + */ +void dwc_otg_read_packet(dwc_otg_core_if_t * core_if, + uint8_t * dest, uint16_t bytes) +{ + int i; + int word_count = (bytes + 3) / 4; + + volatile uint32_t *fifo = core_if->data_fifo[0]; + uint32_t *data_buff = (uint32_t *) dest; + + /** + * @todo Account for the case where _dest is not dword aligned. This + * requires reading data from the FIFO into a uint32_t temp buffer, + * then moving it into the data buffer. + */ + + DWC_DEBUGPL((DBG_PCDV | DBG_CILV), "%s(%p,%p,%d)\n", __func__, + core_if, dest, bytes); + + for (i = 0; i < word_count; i++, data_buff++) { + *data_buff = DWC_READ_REG32(fifo); + } + + return; +} + +/** + * This functions reads the device registers and prints them + * + * @param core_if Programming view of DWC_otg controller. + */ +void dwc_otg_dump_dev_registers(dwc_otg_core_if_t * core_if) +{ + int i; + volatile uint32_t *addr; + + DWC_PRINTF("Device Global Registers\n"); + addr = &core_if->dev_if->dev_global_regs->dcfg; + DWC_PRINTF("DCFG @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + addr = &core_if->dev_if->dev_global_regs->dctl; + DWC_PRINTF("DCTL @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + addr = &core_if->dev_if->dev_global_regs->dsts; + DWC_PRINTF("DSTS @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + addr = &core_if->dev_if->dev_global_regs->diepmsk; + DWC_PRINTF("DIEPMSK @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->dev_if->dev_global_regs->doepmsk; + DWC_PRINTF("DOEPMSK @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->dev_if->dev_global_regs->daint; + DWC_PRINTF("DAINT @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->dev_if->dev_global_regs->daintmsk; + DWC_PRINTF("DAINTMSK @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->dev_if->dev_global_regs->dtknqr1; + DWC_PRINTF("DTKNQR1 @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + if (core_if->hwcfg2.b.dev_token_q_depth > 6) { + addr = &core_if->dev_if->dev_global_regs->dtknqr2; + DWC_PRINTF("DTKNQR2 @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + } + + addr = &core_if->dev_if->dev_global_regs->dvbusdis; + DWC_PRINTF("DVBUSID @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + + addr = &core_if->dev_if->dev_global_regs->dvbuspulse; + DWC_PRINTF("DVBUSPULSE @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + + addr = &core_if->dev_if->dev_global_regs->dtknqr3_dthrctl; + DWC_PRINTF("DTKNQR3_DTHRCTL @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + + if (core_if->hwcfg2.b.dev_token_q_depth > 22) { + addr = &core_if->dev_if->dev_global_regs->dtknqr4_fifoemptymsk; + DWC_PRINTF("DTKNQR4 @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + } + + addr = &core_if->dev_if->dev_global_regs->dtknqr4_fifoemptymsk; + DWC_PRINTF("FIFOEMPMSK @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + + if (core_if->hwcfg2.b.multi_proc_int) { + + addr = &core_if->dev_if->dev_global_regs->deachint; + DWC_PRINTF("DEACHINT @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + addr = &core_if->dev_if->dev_global_regs->deachintmsk; + DWC_PRINTF("DEACHINTMSK @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + + for (i = 0; i <= core_if->dev_if->num_in_eps; i++) { + addr = + &core_if->dev_if-> + dev_global_regs->diepeachintmsk[i]; + DWC_PRINTF("DIEPEACHINTMSK[%d] @0x%08lX : 0x%08X\n", + i, (unsigned long)addr, + DWC_READ_REG32(addr)); + } + + for (i = 0; i <= core_if->dev_if->num_out_eps; i++) { + addr = + &core_if->dev_if-> + dev_global_regs->doepeachintmsk[i]; + DWC_PRINTF("DOEPEACHINTMSK[%d] @0x%08lX : 0x%08X\n", + i, (unsigned long)addr, + DWC_READ_REG32(addr)); + } + } + + for (i = 0; i <= core_if->dev_if->num_in_eps; i++) { + DWC_PRINTF("Device IN EP %d Registers\n", i); + addr = &core_if->dev_if->in_ep_regs[i]->diepctl; + DWC_PRINTF("DIEPCTL @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + addr = &core_if->dev_if->in_ep_regs[i]->diepint; + DWC_PRINTF("DIEPINT @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + addr = &core_if->dev_if->in_ep_regs[i]->dieptsiz; + DWC_PRINTF("DIETSIZ @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + addr = &core_if->dev_if->in_ep_regs[i]->diepdma; + DWC_PRINTF("DIEPDMA @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + addr = &core_if->dev_if->in_ep_regs[i]->dtxfsts; + DWC_PRINTF("DTXFSTS @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + addr = &core_if->dev_if->in_ep_regs[i]->diepdmab; + DWC_PRINTF("DIEPDMAB @0x%08lX : 0x%08X\n", + (unsigned long)addr, 0 /*DWC_READ_REG32(addr) */ ); + } + + for (i = 0; i <= core_if->dev_if->num_out_eps; i++) { + DWC_PRINTF("Device OUT EP %d Registers\n", i); + addr = &core_if->dev_if->out_ep_regs[i]->doepctl; + DWC_PRINTF("DOEPCTL @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + addr = &core_if->dev_if->out_ep_regs[i]->doepint; + DWC_PRINTF("DOEPINT @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + addr = &core_if->dev_if->out_ep_regs[i]->doeptsiz; + DWC_PRINTF("DOETSIZ @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + addr = &core_if->dev_if->out_ep_regs[i]->doepdma; + DWC_PRINTF("DOEPDMA @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + if (core_if->dma_enable) { /* Don't access this register in SLAVE mode */ + addr = &core_if->dev_if->out_ep_regs[i]->doepdmab; + DWC_PRINTF("DOEPDMAB @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + } + + } +} + +/** + * This functions reads the SPRAM and prints its content + * + * @param core_if Programming view of DWC_otg controller. + */ +void dwc_otg_dump_spram(dwc_otg_core_if_t * core_if) +{ + volatile uint8_t *addr, *start_addr, *end_addr; + + DWC_PRINTF("SPRAM Data:\n"); + start_addr = (void *)core_if->core_global_regs; + DWC_PRINTF("Base Address: 0x%8lX\n", (unsigned long)start_addr); + start_addr += 0x00028000; + end_addr = (void *)core_if->core_global_regs; + end_addr += 0x000280e0; + + for (addr = start_addr; addr < end_addr; addr += 16) { + DWC_PRINTF + ("0x%8lX:\t%2X %2X %2X %2X %2X %2X %2X %2X %2X %2X %2X %2X %2X %2X %2X %2X\n", + (unsigned long)addr, addr[0], addr[1], addr[2], addr[3], + addr[4], addr[5], addr[6], addr[7], addr[8], addr[9], + addr[10], addr[11], addr[12], addr[13], addr[14], addr[15] + ); + } + + return; +} + +/** + * This function reads the host registers and prints them + * + * @param core_if Programming view of DWC_otg controller. + */ +void dwc_otg_dump_host_registers(dwc_otg_core_if_t * core_if) +{ + int i; + volatile uint32_t *addr; + + DWC_PRINTF("Host Global Registers\n"); + addr = &core_if->host_if->host_global_regs->hcfg; + DWC_PRINTF("HCFG @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + addr = &core_if->host_if->host_global_regs->hfir; + DWC_PRINTF("HFIR @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + addr = &core_if->host_if->host_global_regs->hfnum; + DWC_PRINTF("HFNUM @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->host_if->host_global_regs->hptxsts; + DWC_PRINTF("HPTXSTS @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->host_if->host_global_regs->haint; + DWC_PRINTF("HAINT @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->host_if->host_global_regs->haintmsk; + DWC_PRINTF("HAINTMSK @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + if (core_if->dma_desc_enable) { + addr = &core_if->host_if->host_global_regs->hflbaddr; + DWC_PRINTF("HFLBADDR @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + } + + addr = core_if->host_if->hprt0; + DWC_PRINTF("HPRT0 @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + + for (i = 0; i < core_if->core_params->host_channels; i++) { + DWC_PRINTF("Host Channel %d Specific Registers\n", i); + addr = &core_if->host_if->hc_regs[i]->hcchar; + DWC_PRINTF("HCCHAR @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + addr = &core_if->host_if->hc_regs[i]->hcsplt; + DWC_PRINTF("HCSPLT @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + addr = &core_if->host_if->hc_regs[i]->hcint; + DWC_PRINTF("HCINT @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + addr = &core_if->host_if->hc_regs[i]->hcintmsk; + DWC_PRINTF("HCINTMSK @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + addr = &core_if->host_if->hc_regs[i]->hctsiz; + DWC_PRINTF("HCTSIZ @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + addr = &core_if->host_if->hc_regs[i]->hcdma; + DWC_PRINTF("HCDMA @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + if (core_if->dma_desc_enable) { + addr = &core_if->host_if->hc_regs[i]->hcdmab; + DWC_PRINTF("HCDMAB @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + } + + } + return; +} + +/** + * This function reads the core global registers and prints them + * + * @param core_if Programming view of DWC_otg controller. + */ +void dwc_otg_dump_global_registers(dwc_otg_core_if_t * core_if) +{ + int i, ep_num; + volatile uint32_t *addr; + char *txfsiz; + + DWC_PRINTF("Core Global Registers\n"); + addr = &core_if->core_global_regs->gotgctl; + DWC_PRINTF("GOTGCTL @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->gotgint; + DWC_PRINTF("GOTGINT @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->gahbcfg; + DWC_PRINTF("GAHBCFG @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->gusbcfg; + DWC_PRINTF("GUSBCFG @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->grstctl; + DWC_PRINTF("GRSTCTL @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->gintsts; + DWC_PRINTF("GINTSTS @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->gintmsk; + DWC_PRINTF("GINTMSK @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->grxstsr; + DWC_PRINTF("GRXSTSR @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->grxfsiz; + DWC_PRINTF("GRXFSIZ @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->gnptxfsiz; + DWC_PRINTF("GNPTXFSIZ @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->gnptxsts; + DWC_PRINTF("GNPTXSTS @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->gi2cctl; + DWC_PRINTF("GI2CCTL @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->gpvndctl; + DWC_PRINTF("GPVNDCTL @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->ggpio; + DWC_PRINTF("GGPIO @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->guid; + DWC_PRINTF("GUID @0x%08lX : 0x%08X\n", + (unsigned long)addr, DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->gsnpsid; + DWC_PRINTF("GSNPSID @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->ghwcfg1; + DWC_PRINTF("GHWCFG1 @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->ghwcfg2; + DWC_PRINTF("GHWCFG2 @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->ghwcfg3; + DWC_PRINTF("GHWCFG3 @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->ghwcfg4; + DWC_PRINTF("GHWCFG4 @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->glpmcfg; + DWC_PRINTF("GLPMCFG @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->gpwrdn; + DWC_PRINTF("GPWRDN @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->gdfifocfg; + DWC_PRINTF("GDFIFOCFG @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + addr = &core_if->core_global_regs->adpctl; + DWC_PRINTF("ADPCTL @0x%08lX : 0x%08X\n", (unsigned long)addr, + dwc_otg_adp_read_reg(core_if)); + addr = &core_if->core_global_regs->hptxfsiz; + DWC_PRINTF("HPTXFSIZ @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); + + if (core_if->en_multiple_tx_fifo == 0) { + ep_num = core_if->hwcfg4.b.num_dev_perio_in_ep; + txfsiz = "DPTXFSIZ"; + } else { + ep_num = core_if->hwcfg4.b.num_in_eps; + txfsiz = "DIENPTXF"; + } + for (i = 0; i < ep_num; i++) { + addr = &core_if->core_global_regs->dtxfsiz[i]; + DWC_PRINTF("%s[%d] @0x%08lX : 0x%08X\n", txfsiz, i + 1, + (unsigned long)addr, DWC_READ_REG32(addr)); + } + addr = core_if->pcgcctl; + DWC_PRINTF("PCGCCTL @0x%08lX : 0x%08X\n", (unsigned long)addr, + DWC_READ_REG32(addr)); +} + +/** + * Flush a Tx FIFO. + * + * @param core_if Programming view of DWC_otg controller. + * @param num Tx FIFO to flush. + */ +void dwc_otg_flush_tx_fifo(dwc_otg_core_if_t * core_if, const int num) +{ + dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs; + volatile grstctl_t greset = {.d32 = 0 }; + int count = 0; + + DWC_DEBUGPL((DBG_CIL | DBG_PCDV), "Flush Tx FIFO %d\n", num); + + greset.b.txfflsh = 1; + greset.b.txfnum = num; + DWC_WRITE_REG32(&global_regs->grstctl, greset.d32); + + do { + greset.d32 = DWC_READ_REG32(&global_regs->grstctl); + if (++count > 10000) { + DWC_WARN("%s() HANG! GRSTCTL=%0x GNPTXSTS=0x%08x\n", + __func__, greset.d32, + DWC_READ_REG32(&global_regs->gnptxsts)); + break; + } + dwc_udelay(1); + } while (greset.b.txfflsh == 1); + + /* Wait for 3 PHY Clocks */ + dwc_udelay(1); +} + +/** + * Flush Rx FIFO. + * + * @param core_if Programming view of DWC_otg controller. + */ +void dwc_otg_flush_rx_fifo(dwc_otg_core_if_t * core_if) +{ + dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs; + volatile grstctl_t greset = {.d32 = 0 }; + int count = 0; + + DWC_DEBUGPL((DBG_CIL | DBG_PCDV), "%s\n", __func__); + /* + * + */ + greset.b.rxfflsh = 1; + DWC_WRITE_REG32(&global_regs->grstctl, greset.d32); + + do { + greset.d32 = DWC_READ_REG32(&global_regs->grstctl); + if (++count > 10000) { + DWC_WARN("%s() HANG! GRSTCTL=%0x\n", __func__, + greset.d32); + break; + } + dwc_udelay(1); + } while (greset.b.rxfflsh == 1); + + /* Wait for 3 PHY Clocks */ + dwc_udelay(1); +} + +/** + * Do core a soft reset of the core. Be careful with this because it + * resets all the internal state machines of the core. + */ +void dwc_otg_core_reset(dwc_otg_core_if_t * core_if) +{ + dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs; + volatile grstctl_t greset = {.d32 = 0 }; + int count = 0; + + DWC_DEBUGPL(DBG_CILV, "%s\n", __func__); + /* Wait for AHB master IDLE state. */ + do { + dwc_udelay(10); + greset.d32 = DWC_READ_REG32(&global_regs->grstctl); + if (++count > 100000) { + DWC_WARN("%s() HANG! AHB Idle GRSTCTL=%0x\n", __func__, + greset.d32); + return; + } + } + while (greset.b.ahbidle == 0); + + /* Core Soft Reset */ + count = 0; + greset.b.csftrst = 1; + DWC_WRITE_REG32(&global_regs->grstctl, greset.d32); + do { + greset.d32 = DWC_READ_REG32(&global_regs->grstctl); + if (++count > 10000) { + DWC_WARN("%s() HANG! Soft Reset GRSTCTL=%0x\n", + __func__, greset.d32); + break; + } + dwc_udelay(1); + } + while (greset.b.csftrst == 1); + + /* Wait for 3 PHY Clocks */ + dwc_mdelay(100); +} + +uint8_t dwc_otg_is_device_mode(dwc_otg_core_if_t * _core_if) +{ + return (dwc_otg_mode(_core_if) != DWC_HOST_MODE); +} + +uint8_t dwc_otg_is_host_mode(dwc_otg_core_if_t * _core_if) +{ + return (dwc_otg_mode(_core_if) == DWC_HOST_MODE); +} + +/** + * Register HCD callbacks. The callbacks are used to start and stop + * the HCD for interrupt processing. + * + * @param core_if Programming view of DWC_otg controller. + * @param cb the HCD callback structure. + * @param p pointer to be passed to callback function (usb_hcd*). + */ +void dwc_otg_cil_register_hcd_callbacks(dwc_otg_core_if_t * core_if, + dwc_otg_cil_callbacks_t * cb, void *p) +{ + core_if->hcd_cb = cb; + cb->p = p; +} + +/** + * Register PCD callbacks. The callbacks are used to start and stop + * the PCD for interrupt processing. + * + * @param core_if Programming view of DWC_otg controller. + * @param cb the PCD callback structure. + * @param p pointer to be passed to callback function (pcd*). + */ +void dwc_otg_cil_register_pcd_callbacks(dwc_otg_core_if_t * core_if, + dwc_otg_cil_callbacks_t * cb, void *p) +{ + core_if->pcd_cb = cb; + cb->p = p; +} + +#ifdef DWC_EN_ISOC + +/** + * This function writes isoc data per 1 (micro)frame into tx fifo + * + * @param core_if Programming view of DWC_otg controller. + * @param ep The EP to start the transfer on. + * + */ +void write_isoc_frame_data(dwc_otg_core_if_t * core_if, dwc_ep_t * ep) +{ + dwc_otg_dev_in_ep_regs_t *ep_regs; + dtxfsts_data_t txstatus = {.d32 = 0 }; + uint32_t len = 0; + uint32_t dwords; + + ep->xfer_len = ep->data_per_frame; + ep->xfer_count = 0; + + ep_regs = core_if->dev_if->in_ep_regs[ep->num]; + + len = ep->xfer_len - ep->xfer_count; + + if (len > ep->maxpacket) { + len = ep->maxpacket; + } + + dwords = (len + 3) / 4; + + /* While there is space in the queue and space in the FIFO and + * More data to tranfer, Write packets to the Tx FIFO */ + txstatus.d32 = + DWC_READ_REG32(&core_if->dev_if->in_ep_regs[ep->num]->dtxfsts); + DWC_DEBUGPL(DBG_PCDV, "b4 dtxfsts[%d]=0x%08x\n", ep->num, txstatus.d32); + + while (txstatus.b.txfspcavail > dwords && + ep->xfer_count < ep->xfer_len && ep->xfer_len != 0) { + /* Write the FIFO */ + dwc_otg_ep_write_packet(core_if, ep, 0); + + len = ep->xfer_len - ep->xfer_count; + if (len > ep->maxpacket) { + len = ep->maxpacket; + } + + dwords = (len + 3) / 4; + txstatus.d32 = + DWC_READ_REG32(&core_if->dev_if->in_ep_regs[ep->num]-> + dtxfsts); + DWC_DEBUGPL(DBG_PCDV, "dtxfsts[%d]=0x%08x\n", ep->num, + txstatus.d32); + } +} + +/** + * This function initializes a descriptor chain for Isochronous transfer + * + * @param core_if Programming view of DWC_otg controller. + * @param ep The EP to start the transfer on. + * + */ +void dwc_otg_iso_ep_start_frm_transfer(dwc_otg_core_if_t * core_if, + dwc_ep_t * ep) +{ + deptsiz_data_t deptsiz = {.d32 = 0 }; + depctl_data_t depctl = {.d32 = 0 }; + dsts_data_t dsts = {.d32 = 0 }; + volatile uint32_t *addr; + + if (ep->is_in) { + addr = &core_if->dev_if->in_ep_regs[ep->num]->diepctl; + } else { + addr = &core_if->dev_if->out_ep_regs[ep->num]->doepctl; + } + + ep->xfer_len = ep->data_per_frame; + ep->xfer_count = 0; + ep->xfer_buff = ep->cur_pkt_addr; + ep->dma_addr = ep->cur_pkt_dma_addr; + + if (ep->is_in) { + /* Program the transfer size and packet count + * as follows: xfersize = N * maxpacket + + * short_packet pktcnt = N + (short_packet + * exist ? 1 : 0) + */ + deptsiz.b.xfersize = ep->xfer_len; + deptsiz.b.pktcnt = + (ep->xfer_len - 1 + ep->maxpacket) / ep->maxpacket; + deptsiz.b.mc = deptsiz.b.pktcnt; + DWC_WRITE_REG32(&core_if->dev_if->in_ep_regs[ep->num]->dieptsiz, + deptsiz.d32); + + /* Write the DMA register */ + if (core_if->dma_enable) { + DWC_WRITE_REG32(& + (core_if->dev_if->in_ep_regs[ep->num]-> + diepdma), (uint32_t) ep->dma_addr); + } + } else { + deptsiz.b.pktcnt = + (ep->xfer_len + (ep->maxpacket - 1)) / ep->maxpacket; + deptsiz.b.xfersize = deptsiz.b.pktcnt * ep->maxpacket; + + DWC_WRITE_REG32(&core_if->dev_if-> + out_ep_regs[ep->num]->doeptsiz, deptsiz.d32); + + if (core_if->dma_enable) { + DWC_WRITE_REG32(& + (core_if->dev_if-> + out_ep_regs[ep->num]->doepdma), + (uint32_t) ep->dma_addr); + } + } + + /** Enable endpoint, clear nak */ + + depctl.d32 = 0; + if (ep->bInterval == 1) { + dsts.d32 = + DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dsts); + ep->next_frame = dsts.b.soffn + ep->bInterval; + + if (ep->next_frame & 0x1) { + depctl.b.setd1pid = 1; + } else { + depctl.b.setd0pid = 1; + } + } else { + ep->next_frame += ep->bInterval; + + if (ep->next_frame & 0x1) { + depctl.b.setd1pid = 1; + } else { + depctl.b.setd0pid = 1; + } + } + depctl.b.epena = 1; + depctl.b.cnak = 1; + + DWC_MODIFY_REG32(addr, 0, depctl.d32); + depctl.d32 = DWC_READ_REG32(addr); + + if (ep->is_in && core_if->dma_enable == 0) { + write_isoc_frame_data(core_if, ep); + } + +} +#endif /* DWC_EN_ISOC */ + +static void dwc_otg_set_uninitialized(int32_t * p, int size) +{ + int i; + for (i = 0; i < size; i++) { + p[i] = -1; + } +} + +static int dwc_otg_param_initialized(int32_t val) +{ + return val != -1; +} + +static int dwc_otg_setup_params(dwc_otg_core_if_t * core_if) +{ + int i; + core_if->core_params = DWC_ALLOC(sizeof(*core_if->core_params)); + if (!core_if->core_params) { + return -DWC_E_NO_MEMORY; + } + dwc_otg_set_uninitialized((int32_t *) core_if->core_params, + sizeof(*core_if->core_params) / + sizeof(int32_t)); + DWC_PRINTF("Setting default values for core params\n"); + dwc_otg_set_param_otg_cap(core_if, dwc_param_otg_cap_default); + dwc_otg_set_param_dma_enable(core_if, dwc_param_dma_enable_default); + dwc_otg_set_param_dma_desc_enable(core_if, + dwc_param_dma_desc_enable_default); + dwc_otg_set_param_opt(core_if, dwc_param_opt_default); + dwc_otg_set_param_dma_burst_size(core_if, + dwc_param_dma_burst_size_default); + dwc_otg_set_param_host_support_fs_ls_low_power(core_if, + dwc_param_host_support_fs_ls_low_power_default); + dwc_otg_set_param_enable_dynamic_fifo(core_if, + dwc_param_enable_dynamic_fifo_default); + dwc_otg_set_param_data_fifo_size(core_if, + dwc_param_data_fifo_size_default); + dwc_otg_set_param_dev_rx_fifo_size(core_if, + dwc_param_dev_rx_fifo_size_default); + dwc_otg_set_param_dev_nperio_tx_fifo_size(core_if, + dwc_param_dev_nperio_tx_fifo_size_default); + dwc_otg_set_param_host_rx_fifo_size(core_if, + dwc_param_host_rx_fifo_size_default); + dwc_otg_set_param_host_nperio_tx_fifo_size(core_if, + dwc_param_host_nperio_tx_fifo_size_default); + dwc_otg_set_param_host_perio_tx_fifo_size(core_if, + dwc_param_host_perio_tx_fifo_size_default); + dwc_otg_set_param_max_transfer_size(core_if, + dwc_param_max_transfer_size_default); + dwc_otg_set_param_max_packet_count(core_if, + dwc_param_max_packet_count_default); + dwc_otg_set_param_host_channels(core_if, + dwc_param_host_channels_default); + dwc_otg_set_param_dev_endpoints(core_if, + dwc_param_dev_endpoints_default); + dwc_otg_set_param_phy_type(core_if, dwc_param_phy_type_default); + dwc_otg_set_param_speed(core_if, dwc_param_speed_default); + dwc_otg_set_param_host_ls_low_power_phy_clk(core_if, + dwc_param_host_ls_low_power_phy_clk_default); + dwc_otg_set_param_phy_ulpi_ddr(core_if, dwc_param_phy_ulpi_ddr_default); + dwc_otg_set_param_phy_ulpi_ext_vbus(core_if, + dwc_param_phy_ulpi_ext_vbus_default); + dwc_otg_set_param_phy_utmi_width(core_if, + dwc_param_phy_utmi_width_default); + dwc_otg_set_param_ts_dline(core_if, dwc_param_ts_dline_default); + dwc_otg_set_param_i2c_enable(core_if, dwc_param_i2c_enable_default); + dwc_otg_set_param_ulpi_fs_ls(core_if, dwc_param_ulpi_fs_ls_default); + dwc_otg_set_param_en_multiple_tx_fifo(core_if, + dwc_param_en_multiple_tx_fifo_default); + for (i = 0; i < 15; i++) { + dwc_otg_set_param_dev_perio_tx_fifo_size(core_if, + dwc_param_dev_perio_tx_fifo_size_default, + i); + } + + for (i = 0; i < 15; i++) { + dwc_otg_set_param_dev_tx_fifo_size(core_if, + dwc_param_dev_tx_fifo_size_default, + i); + } + dwc_otg_set_param_thr_ctl(core_if, dwc_param_thr_ctl_default); + dwc_otg_set_param_mpi_enable(core_if, dwc_param_mpi_enable_default); + dwc_otg_set_param_pti_enable(core_if, dwc_param_pti_enable_default); + dwc_otg_set_param_lpm_enable(core_if, dwc_param_lpm_enable_default); + dwc_otg_set_param_ic_usb_cap(core_if, dwc_param_ic_usb_cap_default); + dwc_otg_set_param_tx_thr_length(core_if, + dwc_param_tx_thr_length_default); + dwc_otg_set_param_rx_thr_length(core_if, + dwc_param_rx_thr_length_default); + dwc_otg_set_param_ahb_thr_ratio(core_if, + dwc_param_ahb_thr_ratio_default); + dwc_otg_set_param_power_down(core_if, dwc_param_power_down_default); + dwc_otg_set_param_reload_ctl(core_if, dwc_param_reload_ctl_default); + dwc_otg_set_param_dev_out_nak(core_if, dwc_param_dev_out_nak_default); + dwc_otg_set_param_cont_on_bna(core_if, dwc_param_cont_on_bna_default); + dwc_otg_set_param_ahb_single(core_if, dwc_param_ahb_single_default); + dwc_otg_set_param_otg_ver(core_if, dwc_param_otg_ver_default); + dwc_otg_set_param_adp_enable(core_if, dwc_param_adp_enable_default); + DWC_PRINTF("Finished setting default values for core params\n"); + + return 0; +} + +uint8_t dwc_otg_is_dma_enable(dwc_otg_core_if_t * core_if) +{ + return core_if->dma_enable; +} + +/* Checks if the parameter is outside of its valid range of values */ +#define DWC_OTG_PARAM_TEST(_param_, _low_, _high_) \ + (((_param_) < (_low_)) || \ + ((_param_) > (_high_))) + +/* Parameter access functions */ +int dwc_otg_set_param_otg_cap(dwc_otg_core_if_t * core_if, int32_t val) +{ + int valid; + int retval = 0; + if (DWC_OTG_PARAM_TEST(val, 0, 2)) { + DWC_WARN("Wrong value for otg_cap parameter\n"); + DWC_WARN("otg_cap parameter must be 0,1 or 2\n"); + retval = -DWC_E_INVALID; + goto out; + } + + valid = 1; + switch (val) { + case DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE: + if (core_if->hwcfg2.b.op_mode != + DWC_HWCFG2_OP_MODE_HNP_SRP_CAPABLE_OTG) + valid = 0; + break; + case DWC_OTG_CAP_PARAM_SRP_ONLY_CAPABLE: + if ((core_if->hwcfg2.b.op_mode != + DWC_HWCFG2_OP_MODE_HNP_SRP_CAPABLE_OTG) + && (core_if->hwcfg2.b.op_mode != + DWC_HWCFG2_OP_MODE_SRP_ONLY_CAPABLE_OTG) + && (core_if->hwcfg2.b.op_mode != + DWC_HWCFG2_OP_MODE_SRP_CAPABLE_DEVICE) + && (core_if->hwcfg2.b.op_mode != + DWC_HWCFG2_OP_MODE_SRP_CAPABLE_HOST)) { + valid = 0; + } + break; + case DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE: + /* always valid */ + break; + } + if (!valid) { + if (dwc_otg_param_initialized(core_if->core_params->otg_cap)) { + DWC_ERROR + ("%d invalid for otg_cap paremter. Check HW configuration.\n", + val); + } + val = + (((core_if->hwcfg2.b.op_mode == + DWC_HWCFG2_OP_MODE_HNP_SRP_CAPABLE_OTG) + || (core_if->hwcfg2.b.op_mode == + DWC_HWCFG2_OP_MODE_SRP_ONLY_CAPABLE_OTG) + || (core_if->hwcfg2.b.op_mode == + DWC_HWCFG2_OP_MODE_SRP_CAPABLE_DEVICE) + || (core_if->hwcfg2.b.op_mode == + DWC_HWCFG2_OP_MODE_SRP_CAPABLE_HOST)) ? + DWC_OTG_CAP_PARAM_SRP_ONLY_CAPABLE : + DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE); + retval = -DWC_E_INVALID; + } + + core_if->core_params->otg_cap = val; +out: + return retval; +} + +int32_t dwc_otg_get_param_otg_cap(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->otg_cap; +} + +int dwc_otg_set_param_opt(dwc_otg_core_if_t * core_if, int32_t val) +{ + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN("Wrong value for opt parameter\n"); + return -DWC_E_INVALID; + } + core_if->core_params->opt = val; + return 0; +} + +int32_t dwc_otg_get_param_opt(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->opt; +} + +int dwc_otg_set_param_dma_enable(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN("Wrong value for dma enable\n"); + return -DWC_E_INVALID; + } + + if ((val == 1) && (core_if->hwcfg2.b.architecture == 0)) { + if (dwc_otg_param_initialized(core_if->core_params->dma_enable)) { + DWC_ERROR + ("%d invalid for dma_enable paremter. Check HW configuration.\n", + val); + } + val = 0; + retval = -DWC_E_INVALID; + } + + core_if->core_params->dma_enable = val; + if (val == 0) { + dwc_otg_set_param_dma_desc_enable(core_if, 0); + } + return retval; +} + +int32_t dwc_otg_get_param_dma_enable(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->dma_enable; +} + +int dwc_otg_set_param_dma_desc_enable(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN("Wrong value for dma_enable\n"); + DWC_WARN("dma_desc_enable must be 0 or 1\n"); + return -DWC_E_INVALID; + } + + if ((val == 1) + && ((dwc_otg_get_param_dma_enable(core_if) == 0) + || (core_if->hwcfg4.b.desc_dma == 0))) { + if (dwc_otg_param_initialized + (core_if->core_params->dma_desc_enable)) { + DWC_ERROR + ("%d invalid for dma_desc_enable paremter. Check HW configuration.\n", + val); + } + val = 0; + retval = -DWC_E_INVALID; + } + core_if->core_params->dma_desc_enable = val; + return retval; +} + +int32_t dwc_otg_get_param_dma_desc_enable(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->dma_desc_enable; +} + +int dwc_otg_set_param_host_support_fs_ls_low_power(dwc_otg_core_if_t * core_if, + int32_t val) +{ + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN("Wrong value for host_support_fs_low_power\n"); + DWC_WARN("host_support_fs_low_power must be 0 or 1\n"); + return -DWC_E_INVALID; + } + core_if->core_params->host_support_fs_ls_low_power = val; + return 0; +} + +int32_t dwc_otg_get_param_host_support_fs_ls_low_power(dwc_otg_core_if_t * + core_if) +{ + return core_if->core_params->host_support_fs_ls_low_power; +} + +int dwc_otg_set_param_enable_dynamic_fifo(dwc_otg_core_if_t * core_if, + int32_t val) +{ + int retval = 0; + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN("Wrong value for enable_dynamic_fifo\n"); + DWC_WARN("enable_dynamic_fifo must be 0 or 1\n"); + return -DWC_E_INVALID; + } + + if ((val == 1) && (core_if->hwcfg2.b.dynamic_fifo == 0)) { + if (dwc_otg_param_initialized + (core_if->core_params->enable_dynamic_fifo)) { + DWC_ERROR + ("%d invalid for enable_dynamic_fifo paremter. Check HW configuration.\n", + val); + } + val = 0; + retval = -DWC_E_INVALID; + } + core_if->core_params->enable_dynamic_fifo = val; + return retval; +} + +int32_t dwc_otg_get_param_enable_dynamic_fifo(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->enable_dynamic_fifo; +} + +int dwc_otg_set_param_data_fifo_size(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + if (DWC_OTG_PARAM_TEST(val, 32, 32768)) { + DWC_WARN("Wrong value for data_fifo_size\n"); + DWC_WARN("data_fifo_size must be 32-32768\n"); + return -DWC_E_INVALID; + } + + if (val > core_if->hwcfg3.b.dfifo_depth) { + if (dwc_otg_param_initialized + (core_if->core_params->data_fifo_size)) { + DWC_ERROR + ("%d invalid for data_fifo_size parameter. Check HW configuration.\n", + val); + } + val = core_if->hwcfg3.b.dfifo_depth; + retval = -DWC_E_INVALID; + } + + core_if->core_params->data_fifo_size = val; + return retval; +} + +int32_t dwc_otg_get_param_data_fifo_size(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->data_fifo_size; +} + +int dwc_otg_set_param_dev_rx_fifo_size(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + if (DWC_OTG_PARAM_TEST(val, 16, 32768)) { + DWC_WARN("Wrong value for dev_rx_fifo_size\n"); + DWC_WARN("dev_rx_fifo_size must be 16-32768\n"); + return -DWC_E_INVALID; + } + + if (val > DWC_READ_REG32(&core_if->core_global_regs->grxfsiz)) { + if (dwc_otg_param_initialized(core_if->core_params->dev_rx_fifo_size)) { + DWC_WARN("%d invalid for dev_rx_fifo_size parameter\n", val); + } + val = DWC_READ_REG32(&core_if->core_global_regs->grxfsiz); + retval = -DWC_E_INVALID; + } + + core_if->core_params->dev_rx_fifo_size = val; + return retval; +} + +int32_t dwc_otg_get_param_dev_rx_fifo_size(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->dev_rx_fifo_size; +} + +int dwc_otg_set_param_dev_nperio_tx_fifo_size(dwc_otg_core_if_t * core_if, + int32_t val) +{ + int retval = 0; + + if (DWC_OTG_PARAM_TEST(val, 16, 32768)) { + DWC_WARN("Wrong value for dev_nperio_tx_fifo\n"); + DWC_WARN("dev_nperio_tx_fifo must be 16-32768\n"); + return -DWC_E_INVALID; + } + + if (val > (DWC_READ_REG32(&core_if->core_global_regs->gnptxfsiz) >> 16)) { + if (dwc_otg_param_initialized + (core_if->core_params->dev_nperio_tx_fifo_size)) { + DWC_ERROR + ("%d invalid for dev_nperio_tx_fifo_size. Check HW configuration.\n", + val); + } + val = + (DWC_READ_REG32(&core_if->core_global_regs->gnptxfsiz) >> + 16); + retval = -DWC_E_INVALID; + } + + core_if->core_params->dev_nperio_tx_fifo_size = val; + return retval; +} + +int32_t dwc_otg_get_param_dev_nperio_tx_fifo_size(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->dev_nperio_tx_fifo_size; +} + +int dwc_otg_set_param_host_rx_fifo_size(dwc_otg_core_if_t * core_if, + int32_t val) +{ + int retval = 0; + + if (DWC_OTG_PARAM_TEST(val, 16, 32768)) { + DWC_WARN("Wrong value for host_rx_fifo_size\n"); + DWC_WARN("host_rx_fifo_size must be 16-32768\n"); + return -DWC_E_INVALID; + } + + if (val > DWC_READ_REG32(&core_if->core_global_regs->grxfsiz)) { + if (dwc_otg_param_initialized + (core_if->core_params->host_rx_fifo_size)) { + DWC_ERROR + ("%d invalid for host_rx_fifo_size. Check HW configuration.\n", + val); + } + val = DWC_READ_REG32(&core_if->core_global_regs->grxfsiz); + retval = -DWC_E_INVALID; + } + + core_if->core_params->host_rx_fifo_size = val; + return retval; + +} + +int32_t dwc_otg_get_param_host_rx_fifo_size(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->host_rx_fifo_size; +} + +int dwc_otg_set_param_host_nperio_tx_fifo_size(dwc_otg_core_if_t * core_if, + int32_t val) +{ + int retval = 0; + + if (DWC_OTG_PARAM_TEST(val, 16, 32768)) { + DWC_WARN("Wrong value for host_nperio_tx_fifo_size\n"); + DWC_WARN("host_nperio_tx_fifo_size must be 16-32768\n"); + return -DWC_E_INVALID; + } + + if (val > (DWC_READ_REG32(&core_if->core_global_regs->gnptxfsiz) >> 16)) { + if (dwc_otg_param_initialized + (core_if->core_params->host_nperio_tx_fifo_size)) { + DWC_ERROR + ("%d invalid for host_nperio_tx_fifo_size. Check HW configuration.\n", + val); + } + val = + (DWC_READ_REG32(&core_if->core_global_regs->gnptxfsiz) >> + 16); + retval = -DWC_E_INVALID; + } + + core_if->core_params->host_nperio_tx_fifo_size = val; + return retval; +} + +int32_t dwc_otg_get_param_host_nperio_tx_fifo_size(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->host_nperio_tx_fifo_size; +} + +int dwc_otg_set_param_host_perio_tx_fifo_size(dwc_otg_core_if_t * core_if, + int32_t val) +{ + int retval = 0; + if (DWC_OTG_PARAM_TEST(val, 16, 32768)) { + DWC_WARN("Wrong value for host_perio_tx_fifo_size\n"); + DWC_WARN("host_perio_tx_fifo_size must be 16-32768\n"); + return -DWC_E_INVALID; + } + + if (val > ((core_if->hptxfsiz.d32) >> 16)) { + if (dwc_otg_param_initialized + (core_if->core_params->host_perio_tx_fifo_size)) { + DWC_ERROR + ("%d invalid for host_perio_tx_fifo_size. Check HW configuration.\n", + val); + } + val = (core_if->hptxfsiz.d32) >> 16; + retval = -DWC_E_INVALID; + } + + core_if->core_params->host_perio_tx_fifo_size = val; + return retval; +} + +int32_t dwc_otg_get_param_host_perio_tx_fifo_size(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->host_perio_tx_fifo_size; +} + +int dwc_otg_set_param_max_transfer_size(dwc_otg_core_if_t * core_if, + int32_t val) +{ + int retval = 0; + + if (DWC_OTG_PARAM_TEST(val, 2047, 524288)) { + DWC_WARN("Wrong value for max_transfer_size\n"); + DWC_WARN("max_transfer_size must be 2047-524288\n"); + return -DWC_E_INVALID; + } + + if (val >= (1 << (core_if->hwcfg3.b.xfer_size_cntr_width + 11))) { + if (dwc_otg_param_initialized + (core_if->core_params->max_transfer_size)) { + DWC_ERROR + ("%d invalid for max_transfer_size. Check HW configuration.\n", + val); + } + val = + ((1 << (core_if->hwcfg3.b.packet_size_cntr_width + 11)) - + 1); + retval = -DWC_E_INVALID; + } + + core_if->core_params->max_transfer_size = val; + return retval; +} + +int32_t dwc_otg_get_param_max_transfer_size(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->max_transfer_size; +} + +int dwc_otg_set_param_max_packet_count(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + + if (DWC_OTG_PARAM_TEST(val, 15, 511)) { + DWC_WARN("Wrong value for max_packet_count\n"); + DWC_WARN("max_packet_count must be 15-511\n"); + return -DWC_E_INVALID; + } + + if (val > (1 << (core_if->hwcfg3.b.packet_size_cntr_width + 4))) { + if (dwc_otg_param_initialized + (core_if->core_params->max_packet_count)) { + DWC_ERROR + ("%d invalid for max_packet_count. Check HW configuration.\n", + val); + } + val = + ((1 << (core_if->hwcfg3.b.packet_size_cntr_width + 4)) - 1); + retval = -DWC_E_INVALID; + } + + core_if->core_params->max_packet_count = val; + return retval; +} + +int32_t dwc_otg_get_param_max_packet_count(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->max_packet_count; +} + +int dwc_otg_set_param_host_channels(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + + if (DWC_OTG_PARAM_TEST(val, 1, 16)) { + DWC_WARN("Wrong value for host_channels\n"); + DWC_WARN("host_channels must be 1-16\n"); + return -DWC_E_INVALID; + } + + if (val > (core_if->hwcfg2.b.num_host_chan + 1)) { + if (dwc_otg_param_initialized + (core_if->core_params->host_channels)) { + DWC_ERROR + ("%d invalid for host_channels. Check HW configurations.\n", + val); + } + val = (core_if->hwcfg2.b.num_host_chan + 1); + retval = -DWC_E_INVALID; + } + + core_if->core_params->host_channels = val; + return retval; +} + +int32_t dwc_otg_get_param_host_channels(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->host_channels; +} + +int dwc_otg_set_param_dev_endpoints(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + + if (DWC_OTG_PARAM_TEST(val, 1, 15)) { + DWC_WARN("Wrong value for dev_endpoints\n"); + DWC_WARN("dev_endpoints must be 1-15\n"); + return -DWC_E_INVALID; + } + + if (val > (core_if->hwcfg2.b.num_dev_ep)) { + if (dwc_otg_param_initialized + (core_if->core_params->dev_endpoints)) { + DWC_ERROR + ("%d invalid for dev_endpoints. Check HW configurations.\n", + val); + } + val = core_if->hwcfg2.b.num_dev_ep; + retval = -DWC_E_INVALID; + } + + core_if->core_params->dev_endpoints = val; + return retval; +} + +int32_t dwc_otg_get_param_dev_endpoints(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->dev_endpoints; +} + +int dwc_otg_set_param_phy_type(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + int valid = 0; + + if (DWC_OTG_PARAM_TEST(val, 0, 2)) { + DWC_WARN("Wrong value for phy_type\n"); + DWC_WARN("phy_type must be 0,1 or 2\n"); + return -DWC_E_INVALID; + } +#ifndef NO_FS_PHY_HW_CHECKS + if ((val == DWC_PHY_TYPE_PARAM_UTMI) && + ((core_if->hwcfg2.b.hs_phy_type == 1) || + (core_if->hwcfg2.b.hs_phy_type == 3))) { + valid = 1; + } else if ((val == DWC_PHY_TYPE_PARAM_ULPI) && + ((core_if->hwcfg2.b.hs_phy_type == 2) || + (core_if->hwcfg2.b.hs_phy_type == 3))) { + valid = 1; + } else if ((val == DWC_PHY_TYPE_PARAM_FS) && + (core_if->hwcfg2.b.fs_phy_type == 1)) { + valid = 1; + } + if (!valid) { + if (dwc_otg_param_initialized(core_if->core_params->phy_type)) { + DWC_ERROR + ("%d invalid for phy_type. Check HW configurations.\n", + val); + } + if (core_if->hwcfg2.b.hs_phy_type) { + if ((core_if->hwcfg2.b.hs_phy_type == 3) || + (core_if->hwcfg2.b.hs_phy_type == 1)) { + val = DWC_PHY_TYPE_PARAM_UTMI; + } else { + val = DWC_PHY_TYPE_PARAM_ULPI; + } + } + retval = -DWC_E_INVALID; + } +#endif + core_if->core_params->phy_type = val; + return retval; +} + +int32_t dwc_otg_get_param_phy_type(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->phy_type; +} + +int dwc_otg_set_param_speed(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN("Wrong value for speed parameter\n"); + DWC_WARN("max_speed parameter must be 0 or 1\n"); + return -DWC_E_INVALID; + } + if ((val == 0) + && dwc_otg_get_param_phy_type(core_if) == DWC_PHY_TYPE_PARAM_FS) { + if (dwc_otg_param_initialized(core_if->core_params->speed)) { + DWC_ERROR + ("%d invalid for speed paremter. Check HW configuration.\n", + val); + } + val = + (dwc_otg_get_param_phy_type(core_if) == + DWC_PHY_TYPE_PARAM_FS ? 1 : 0); + retval = -DWC_E_INVALID; + } + core_if->core_params->speed = val; + return retval; +} + +int32_t dwc_otg_get_param_speed(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->speed; +} + +int dwc_otg_set_param_host_ls_low_power_phy_clk(dwc_otg_core_if_t * core_if, + int32_t val) +{ + int retval = 0; + + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN + ("Wrong value for host_ls_low_power_phy_clk parameter\n"); + DWC_WARN("host_ls_low_power_phy_clk must be 0 or 1\n"); + return -DWC_E_INVALID; + } + + if ((val == DWC_HOST_LS_LOW_POWER_PHY_CLK_PARAM_48MHZ) + && (dwc_otg_get_param_phy_type(core_if) == DWC_PHY_TYPE_PARAM_FS)) { + if (dwc_otg_param_initialized + (core_if->core_params->host_ls_low_power_phy_clk)) { + DWC_ERROR + ("%d invalid for host_ls_low_power_phy_clk. Check HW configuration.\n", + val); + } + val = + (dwc_otg_get_param_phy_type(core_if) == + DWC_PHY_TYPE_PARAM_FS) ? + DWC_HOST_LS_LOW_POWER_PHY_CLK_PARAM_6MHZ : + DWC_HOST_LS_LOW_POWER_PHY_CLK_PARAM_48MHZ; + retval = -DWC_E_INVALID; + } + + core_if->core_params->host_ls_low_power_phy_clk = val; + return retval; +} + +int32_t dwc_otg_get_param_host_ls_low_power_phy_clk(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->host_ls_low_power_phy_clk; +} + +int dwc_otg_set_param_phy_ulpi_ddr(dwc_otg_core_if_t * core_if, int32_t val) +{ + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN("Wrong value for phy_ulpi_ddr\n"); + DWC_WARN("phy_upli_ddr must be 0 or 1\n"); + return -DWC_E_INVALID; + } + + core_if->core_params->phy_ulpi_ddr = val; + return 0; +} + +int32_t dwc_otg_get_param_phy_ulpi_ddr(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->phy_ulpi_ddr; +} + +int dwc_otg_set_param_phy_ulpi_ext_vbus(dwc_otg_core_if_t * core_if, + int32_t val) +{ + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN("Wrong valaue for phy_ulpi_ext_vbus\n"); + DWC_WARN("phy_ulpi_ext_vbus must be 0 or 1\n"); + return -DWC_E_INVALID; + } + + core_if->core_params->phy_ulpi_ext_vbus = val; + return 0; +} + +int32_t dwc_otg_get_param_phy_ulpi_ext_vbus(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->phy_ulpi_ext_vbus; +} + +int dwc_otg_set_param_phy_utmi_width(dwc_otg_core_if_t * core_if, int32_t val) +{ + if (DWC_OTG_PARAM_TEST(val, 8, 8) && DWC_OTG_PARAM_TEST(val, 16, 16)) { + DWC_WARN("Wrong valaue for phy_utmi_width\n"); + DWC_WARN("phy_utmi_width must be 8 or 16\n"); + return -DWC_E_INVALID; + } + + core_if->core_params->phy_utmi_width = val; + return 0; +} + +int32_t dwc_otg_get_param_phy_utmi_width(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->phy_utmi_width; +} + +int dwc_otg_set_param_ulpi_fs_ls(dwc_otg_core_if_t * core_if, int32_t val) +{ + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN("Wrong valaue for ulpi_fs_ls\n"); + DWC_WARN("ulpi_fs_ls must be 0 or 1\n"); + return -DWC_E_INVALID; + } + + core_if->core_params->ulpi_fs_ls = val; + return 0; +} + +int32_t dwc_otg_get_param_ulpi_fs_ls(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->ulpi_fs_ls; +} + +int dwc_otg_set_param_ts_dline(dwc_otg_core_if_t * core_if, int32_t val) +{ + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN("Wrong valaue for ts_dline\n"); + DWC_WARN("ts_dline must be 0 or 1\n"); + return -DWC_E_INVALID; + } + + core_if->core_params->ts_dline = val; + return 0; +} + +int32_t dwc_otg_get_param_ts_dline(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->ts_dline; +} + +int dwc_otg_set_param_i2c_enable(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN("Wrong valaue for i2c_enable\n"); + DWC_WARN("i2c_enable must be 0 or 1\n"); + return -DWC_E_INVALID; + } +#ifndef NO_FS_PHY_HW_CHECK + if (val == 1 && core_if->hwcfg3.b.i2c == 0) { + if (dwc_otg_param_initialized(core_if->core_params->i2c_enable)) { + DWC_ERROR + ("%d invalid for i2c_enable. Check HW configuration.\n", + val); + } + val = 0; + retval = -DWC_E_INVALID; + } +#endif + + core_if->core_params->i2c_enable = val; + return retval; +} + +int32_t dwc_otg_get_param_i2c_enable(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->i2c_enable; +} + +int dwc_otg_set_param_dev_perio_tx_fifo_size(dwc_otg_core_if_t * core_if, + int32_t val, int fifo_num) +{ + int retval = 0; + + if (DWC_OTG_PARAM_TEST(val, 4, 768)) { + DWC_WARN("Wrong value for dev_perio_tx_fifo_size\n"); + DWC_WARN("dev_perio_tx_fifo_size must be 4-768\n"); + return -DWC_E_INVALID; + } + + if (val > + (DWC_READ_REG32(&core_if->core_global_regs->dtxfsiz[fifo_num]))) { + if (dwc_otg_param_initialized + (core_if->core_params->dev_perio_tx_fifo_size[fifo_num])) { + DWC_ERROR + ("`%d' invalid for parameter `dev_perio_fifo_size_%d'. Check HW configuration.\n", + val, fifo_num); + } + val = (DWC_READ_REG32(&core_if->core_global_regs->dtxfsiz[fifo_num])); + retval = -DWC_E_INVALID; + } + + core_if->core_params->dev_perio_tx_fifo_size[fifo_num] = val; + return retval; +} + +int32_t dwc_otg_get_param_dev_perio_tx_fifo_size(dwc_otg_core_if_t * core_if, + int fifo_num) +{ + return core_if->core_params->dev_perio_tx_fifo_size[fifo_num]; +} + +int dwc_otg_set_param_en_multiple_tx_fifo(dwc_otg_core_if_t * core_if, + int32_t val) +{ + int retval = 0; + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN("Wrong valaue for en_multiple_tx_fifo,\n"); + DWC_WARN("en_multiple_tx_fifo must be 0 or 1\n"); + return -DWC_E_INVALID; + } + + if (val == 1 && core_if->hwcfg4.b.ded_fifo_en == 0) { + if (dwc_otg_param_initialized + (core_if->core_params->en_multiple_tx_fifo)) { + DWC_ERROR + ("%d invalid for parameter en_multiple_tx_fifo. Check HW configuration.\n", + val); + } + val = 0; + retval = -DWC_E_INVALID; + } + + core_if->core_params->en_multiple_tx_fifo = val; + return retval; +} + +int32_t dwc_otg_get_param_en_multiple_tx_fifo(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->en_multiple_tx_fifo; +} + +int dwc_otg_set_param_dev_tx_fifo_size(dwc_otg_core_if_t * core_if, int32_t val, + int fifo_num) +{ + int retval = 0; + + if (DWC_OTG_PARAM_TEST(val, 4, 768)) { + DWC_WARN("Wrong value for dev_tx_fifo_size\n"); + DWC_WARN("dev_tx_fifo_size must be 4-768\n"); + return -DWC_E_INVALID; + } + + if (val > + (DWC_READ_REG32(&core_if->core_global_regs->dtxfsiz[fifo_num]))) { + if (dwc_otg_param_initialized + (core_if->core_params->dev_tx_fifo_size[fifo_num])) { + DWC_ERROR + ("`%d' invalid for parameter `dev_tx_fifo_size_%d'. Check HW configuration.\n", + val, fifo_num); + } + val = (DWC_READ_REG32(&core_if->core_global_regs->dtxfsiz[fifo_num])); + retval = -DWC_E_INVALID; + } + + core_if->core_params->dev_tx_fifo_size[fifo_num] = val; + return retval; +} + +int32_t dwc_otg_get_param_dev_tx_fifo_size(dwc_otg_core_if_t * core_if, + int fifo_num) +{ + return core_if->core_params->dev_tx_fifo_size[fifo_num]; +} + +int dwc_otg_set_param_thr_ctl(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + + if (DWC_OTG_PARAM_TEST(val, 0, 7)) { + DWC_WARN("Wrong value for thr_ctl\n"); + DWC_WARN("thr_ctl must be 0-7\n"); + return -DWC_E_INVALID; + } + + if ((val != 0) && + (!dwc_otg_get_param_dma_enable(core_if) || + !core_if->hwcfg4.b.ded_fifo_en)) { + if (dwc_otg_param_initialized(core_if->core_params->thr_ctl)) { + DWC_ERROR + ("%d invalid for parameter thr_ctl. Check HW configuration.\n", + val); + } + val = 0; + retval = -DWC_E_INVALID; + } + + core_if->core_params->thr_ctl = val; + return retval; +} + +static int32_t dwc_otg_get_param_thr_ctl(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->thr_ctl; +} + +int dwc_otg_set_param_lpm_enable(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN("Wrong value for lpm_enable\n"); + DWC_WARN("lpm_enable must be 0 or 1\n"); + return -DWC_E_INVALID; + } + + if (val && !core_if->hwcfg3.b.otg_lpm_en) { + if (dwc_otg_param_initialized(core_if->core_params->lpm_enable)) { + DWC_ERROR + ("%d invalid for parameter lpm_enable. Check HW configuration.\n", + val); + } + val = 0; + retval = -DWC_E_INVALID; + } + + core_if->core_params->lpm_enable = val; + return retval; +} + +int32_t dwc_otg_get_param_lpm_enable(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->lpm_enable; +} + +int dwc_otg_set_param_tx_thr_length(dwc_otg_core_if_t * core_if, int32_t val) +{ + if (DWC_OTG_PARAM_TEST(val, 8, 128)) { + DWC_WARN("Wrong valaue for tx_thr_length\n"); + DWC_WARN("tx_thr_length must be 8 - 128\n"); + return -DWC_E_INVALID; + } + + core_if->core_params->tx_thr_length = val; + return 0; +} + +static int32_t dwc_otg_get_param_tx_thr_length(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->tx_thr_length; +} + +int dwc_otg_set_param_rx_thr_length(dwc_otg_core_if_t * core_if, int32_t val) +{ + if (DWC_OTG_PARAM_TEST(val, 8, 128)) { + DWC_WARN("Wrong valaue for rx_thr_length\n"); + DWC_WARN("rx_thr_length must be 8 - 128\n"); + return -DWC_E_INVALID; + } + + core_if->core_params->rx_thr_length = val; + return 0; +} + +int dwc_otg_set_param_dma_burst_size(dwc_otg_core_if_t * core_if, int32_t val) +{ + if (DWC_OTG_PARAM_TEST(val, 1, 1) && + DWC_OTG_PARAM_TEST(val, 4, 4) && + DWC_OTG_PARAM_TEST(val, 8, 8) && + DWC_OTG_PARAM_TEST(val, 16, 16) && + DWC_OTG_PARAM_TEST(val, 32, 32) && + DWC_OTG_PARAM_TEST(val, 64, 64) && + DWC_OTG_PARAM_TEST(val, 128, 128) && + DWC_OTG_PARAM_TEST(val, 256, 256)) { + DWC_WARN("`%d' invalid for parameter `dma_burst_size'\n", val); + return -DWC_E_INVALID; + } + core_if->core_params->dma_burst_size = val; + return 0; +} + +int32_t dwc_otg_get_param_dma_burst_size(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->dma_burst_size; +} + +int dwc_otg_set_param_pti_enable(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN("`%d' invalid for parameter `pti_enable'\n", val); + return -DWC_E_INVALID; + } + if (val && (core_if->snpsid < OTG_CORE_REV_2_72a)) { + if (dwc_otg_param_initialized(core_if->core_params->pti_enable)) { + DWC_ERROR + ("%d invalid for parameter pti_enable. Check HW configuration.\n", + val); + } + retval = -DWC_E_INVALID; + val = 0; + } + core_if->core_params->pti_enable = val; + return retval; +} + +int32_t dwc_otg_get_param_pti_enable(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->pti_enable; +} + +int dwc_otg_set_param_mpi_enable(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN("`%d' invalid for parameter `mpi_enable'\n", val); + return -DWC_E_INVALID; + } + if (val && (core_if->hwcfg2.b.multi_proc_int == 0)) { + if (dwc_otg_param_initialized(core_if->core_params->mpi_enable)) { + DWC_ERROR + ("%d invalid for parameter mpi_enable. Check HW configuration.\n", + val); + } + retval = -DWC_E_INVALID; + val = 0; + } + core_if->core_params->mpi_enable = val; + return retval; +} + +int32_t dwc_otg_get_param_mpi_enable(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->mpi_enable; +} + +int dwc_otg_set_param_adp_enable(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN("`%d' invalid for parameter `adp_enable'\n", val); + return -DWC_E_INVALID; + } + if (val && (core_if->hwcfg3.b.adp_supp == 0)) { + if (dwc_otg_param_initialized + (core_if->core_params->adp_supp_enable)) { + DWC_ERROR + ("%d invalid for parameter adp_enable. Check HW configuration.\n", + val); + } + retval = -DWC_E_INVALID; + val = 0; + } + core_if->core_params->adp_supp_enable = val; + /*Set OTG version 2.0 in case of enabling ADP*/ + if (val) + dwc_otg_set_param_otg_ver(core_if, 1); + + return retval; +} + +int32_t dwc_otg_get_param_adp_enable(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->adp_supp_enable; +} + +int dwc_otg_set_param_ic_usb_cap(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN("`%d' invalid for parameter `ic_usb_cap'\n", val); + DWC_WARN("ic_usb_cap must be 0 or 1\n"); + return -DWC_E_INVALID; + } + + if (val && (core_if->hwcfg2.b.otg_enable_ic_usb == 0)) { + if (dwc_otg_param_initialized(core_if->core_params->ic_usb_cap)) { + DWC_ERROR + ("%d invalid for parameter ic_usb_cap. Check HW configuration.\n", + val); + } + retval = -DWC_E_INVALID; + val = 0; + } + core_if->core_params->ic_usb_cap = val; + return retval; +} + +int32_t dwc_otg_get_param_ic_usb_cap(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->ic_usb_cap; +} + +int dwc_otg_set_param_ahb_thr_ratio(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + int valid = 1; + + if (DWC_OTG_PARAM_TEST(val, 0, 3)) { + DWC_WARN("`%d' invalid for parameter `ahb_thr_ratio'\n", val); + DWC_WARN("ahb_thr_ratio must be 0 - 3\n"); + return -DWC_E_INVALID; + } + + if (val + && (core_if->snpsid < OTG_CORE_REV_2_81a + || !dwc_otg_get_param_thr_ctl(core_if))) { + valid = 0; + } else if (val + && ((dwc_otg_get_param_tx_thr_length(core_if) / (1 << val)) < + 4)) { + valid = 0; + } + if (valid == 0) { + if (dwc_otg_param_initialized + (core_if->core_params->ahb_thr_ratio)) { + DWC_ERROR + ("%d invalid for parameter ahb_thr_ratio. Check HW configuration.\n", + val); + } + retval = -DWC_E_INVALID; + val = 0; + } + + core_if->core_params->ahb_thr_ratio = val; + return retval; +} + +int32_t dwc_otg_get_param_ahb_thr_ratio(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->ahb_thr_ratio; +} + +int dwc_otg_set_param_power_down(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + int valid = 1; + hwcfg4_data_t hwcfg4 = {.d32 = 0 }; + hwcfg4.d32 = DWC_READ_REG32(&core_if->core_global_regs->ghwcfg4); + + if (DWC_OTG_PARAM_TEST(val, 0, 3)) { + DWC_WARN("`%d' invalid for parameter `power_down'\n", val); + DWC_WARN("power_down must be 0 - 2\n"); + return -DWC_E_INVALID; + } + + if ((val == 2) && (core_if->snpsid < OTG_CORE_REV_2_91a)) { + valid = 0; + } + if ((val == 3) + && ((core_if->snpsid < OTG_CORE_REV_3_00a) + || (hwcfg4.b.xhiber == 0))) { + valid = 0; + } + if (valid == 0) { + if (dwc_otg_param_initialized(core_if->core_params->power_down)) { + DWC_ERROR + ("%d invalid for parameter power_down. Check HW configuration.\n", + val); + } + retval = -DWC_E_INVALID; + val = 0; + } + core_if->core_params->power_down = val; + return retval; +} + +int32_t dwc_otg_get_param_power_down(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->power_down; +} + +int dwc_otg_set_param_reload_ctl(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + int valid = 1; + + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN("`%d' invalid for parameter `reload_ctl'\n", val); + DWC_WARN("reload_ctl must be 0 or 1\n"); + return -DWC_E_INVALID; + } + + if ((val == 1) && (core_if->snpsid < OTG_CORE_REV_2_92a)) { + valid = 0; + } + if (valid == 0) { + if (dwc_otg_param_initialized(core_if->core_params->reload_ctl)) { + DWC_ERROR("%d invalid for parameter reload_ctl." + "Check HW configuration.\n", val); + } + retval = -DWC_E_INVALID; + val = 0; + } + core_if->core_params->reload_ctl = val; + return retval; +} + +int32_t dwc_otg_get_param_reload_ctl(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->reload_ctl; +} + +int dwc_otg_set_param_dev_out_nak(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + int valid = 1; + + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN("`%d' invalid for parameter `dev_out_nak'\n", val); + DWC_WARN("dev_out_nak must be 0 or 1\n"); + return -DWC_E_INVALID; + } + + if ((val == 1) && ((core_if->snpsid < OTG_CORE_REV_2_93a) || + !(core_if->core_params->dma_desc_enable))) { + valid = 0; + } + if (valid == 0) { + if (dwc_otg_param_initialized(core_if->core_params->dev_out_nak)) { + DWC_ERROR("%d invalid for parameter dev_out_nak." + "Check HW configuration.\n", val); + } + retval = -DWC_E_INVALID; + val = 0; + } + core_if->core_params->dev_out_nak = val; + return retval; +} + +int32_t dwc_otg_get_param_dev_out_nak(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->dev_out_nak; +} + +int dwc_otg_set_param_cont_on_bna(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + int valid = 1; + + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN("`%d' invalid for parameter `cont_on_bna'\n", val); + DWC_WARN("cont_on_bna must be 0 or 1\n"); + return -DWC_E_INVALID; + } + + if ((val == 1) && ((core_if->snpsid < OTG_CORE_REV_2_94a) || + !(core_if->core_params->dma_desc_enable))) { + valid = 0; + } + if (valid == 0) { + if (dwc_otg_param_initialized(core_if->core_params->cont_on_bna)) { + DWC_ERROR("%d invalid for parameter cont_on_bna." + "Check HW configuration.\n", val); + } + retval = -DWC_E_INVALID; + val = 0; + } + core_if->core_params->cont_on_bna = val; + return retval; +} + +int32_t dwc_otg_get_param_cont_on_bna(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->cont_on_bna; +} + +int dwc_otg_set_param_ahb_single(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + int valid = 1; + + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN("`%d' invalid for parameter `ahb_single'\n", val); + DWC_WARN("ahb_single must be 0 or 1\n"); + return -DWC_E_INVALID; + } + + if ((val == 1) && (core_if->snpsid < OTG_CORE_REV_2_94a)) { + valid = 0; + } + if (valid == 0) { + if (dwc_otg_param_initialized(core_if->core_params->ahb_single)) { + DWC_ERROR("%d invalid for parameter ahb_single." + "Check HW configuration.\n", val); + } + retval = -DWC_E_INVALID; + val = 0; + } + core_if->core_params->ahb_single = val; + return retval; +} + +int32_t dwc_otg_get_param_ahb_single(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->ahb_single; +} + +int dwc_otg_set_param_otg_ver(dwc_otg_core_if_t * core_if, int32_t val) +{ + int retval = 0; + + if (DWC_OTG_PARAM_TEST(val, 0, 1)) { + DWC_WARN("`%d' invalid for parameter `otg_ver'\n", val); + DWC_WARN + ("otg_ver must be 0(for OTG 1.3 support) or 1(for OTG 2.0 support)\n"); + return -DWC_E_INVALID; + } + + core_if->core_params->otg_ver = val; + return retval; +} + +int32_t dwc_otg_get_param_otg_ver(dwc_otg_core_if_t * core_if) +{ + return core_if->core_params->otg_ver; +} + +uint32_t dwc_otg_get_hnpstatus(dwc_otg_core_if_t * core_if) +{ + gotgctl_data_t otgctl; + otgctl.d32 = DWC_READ_REG32(&core_if->core_global_regs->gotgctl); + return otgctl.b.hstnegscs; +} + +uint32_t dwc_otg_get_srpstatus(dwc_otg_core_if_t * core_if) +{ + gotgctl_data_t otgctl; + otgctl.d32 = DWC_READ_REG32(&core_if->core_global_regs->gotgctl); + return otgctl.b.sesreqscs; +} + +void dwc_otg_set_hnpreq(dwc_otg_core_if_t * core_if, uint32_t val) +{ + if(core_if->otg_ver == 0) { + gotgctl_data_t otgctl; + otgctl.d32 = DWC_READ_REG32(&core_if->core_global_regs->gotgctl); + otgctl.b.hnpreq = val; + DWC_WRITE_REG32(&core_if->core_global_regs->gotgctl, otgctl.d32); + } else { + core_if->otg_sts = val; + } +} + +uint32_t dwc_otg_get_gsnpsid(dwc_otg_core_if_t * core_if) +{ + return core_if->snpsid; +} + +uint32_t dwc_otg_get_mode(dwc_otg_core_if_t * core_if) +{ + gintsts_data_t gintsts; + gintsts.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintsts); + return gintsts.b.curmode; +} + +uint32_t dwc_otg_get_hnpcapable(dwc_otg_core_if_t * core_if) +{ + gusbcfg_data_t usbcfg; + usbcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->gusbcfg); + return usbcfg.b.hnpcap; +} + +void dwc_otg_set_hnpcapable(dwc_otg_core_if_t * core_if, uint32_t val) +{ + gusbcfg_data_t usbcfg; + usbcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->gusbcfg); + usbcfg.b.hnpcap = val; + DWC_WRITE_REG32(&core_if->core_global_regs->gusbcfg, usbcfg.d32); +} + +uint32_t dwc_otg_get_srpcapable(dwc_otg_core_if_t * core_if) +{ + gusbcfg_data_t usbcfg; + usbcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->gusbcfg); + return usbcfg.b.srpcap; +} + +void dwc_otg_set_srpcapable(dwc_otg_core_if_t * core_if, uint32_t val) +{ + gusbcfg_data_t usbcfg; + usbcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->gusbcfg); + usbcfg.b.srpcap = val; + DWC_WRITE_REG32(&core_if->core_global_regs->gusbcfg, usbcfg.d32); +} + +uint32_t dwc_otg_get_devspeed(dwc_otg_core_if_t * core_if) +{ + dcfg_data_t dcfg; + /* originally: dcfg.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dcfg); */ + + dcfg.d32 = -1; //GRAYG + DWC_DEBUGPL(DBG_CILV, "%s - core_if(%p)\n", __func__, core_if); + if (NULL == core_if) + DWC_ERROR("reg request with NULL core_if\n"); + DWC_DEBUGPL(DBG_CILV, "%s - core_if(%p)->dev_if(%p)\n", __func__, + core_if, core_if->dev_if); + if (NULL == core_if->dev_if) + DWC_ERROR("reg request with NULL dev_if\n"); + DWC_DEBUGPL(DBG_CILV, "%s - core_if(%p)->dev_if(%p)->" + "dev_global_regs(%p)\n", __func__, + core_if, core_if->dev_if, + core_if->dev_if->dev_global_regs); + if (NULL == core_if->dev_if->dev_global_regs) + DWC_ERROR("reg request with NULL dev_global_regs\n"); + else { + DWC_DEBUGPL(DBG_CILV, "%s - &core_if(%p)->dev_if(%p)->" + "dev_global_regs(%p)->dcfg = %p\n", __func__, + core_if, core_if->dev_if, + core_if->dev_if->dev_global_regs, + &core_if->dev_if->dev_global_regs->dcfg); + dcfg.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dcfg); + } + return dcfg.b.devspd; +} + +void dwc_otg_set_devspeed(dwc_otg_core_if_t * core_if, uint32_t val) +{ + dcfg_data_t dcfg; + dcfg.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dcfg); + dcfg.b.devspd = val; + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->dcfg, dcfg.d32); +} + +uint32_t dwc_otg_get_busconnected(dwc_otg_core_if_t * core_if) +{ + hprt0_data_t hprt0; + hprt0.d32 = DWC_READ_REG32(core_if->host_if->hprt0); + return hprt0.b.prtconnsts; +} + +uint32_t dwc_otg_get_enumspeed(dwc_otg_core_if_t * core_if) +{ + dsts_data_t dsts; + dsts.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dsts); + return dsts.b.enumspd; +} + +uint32_t dwc_otg_get_prtpower(dwc_otg_core_if_t * core_if) +{ + hprt0_data_t hprt0; + hprt0.d32 = DWC_READ_REG32(core_if->host_if->hprt0); + return hprt0.b.prtpwr; + +} + +uint32_t dwc_otg_get_core_state(dwc_otg_core_if_t * core_if) +{ + return core_if->hibernation_suspend; +} + +void dwc_otg_set_prtpower(dwc_otg_core_if_t * core_if, uint32_t val) +{ + hprt0_data_t hprt0; + hprt0.d32 = dwc_otg_read_hprt0(core_if); + hprt0.b.prtpwr = val; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); +} + +uint32_t dwc_otg_get_prtsuspend(dwc_otg_core_if_t * core_if) +{ + hprt0_data_t hprt0; + hprt0.d32 = DWC_READ_REG32(core_if->host_if->hprt0); + return hprt0.b.prtsusp; + +} + +void dwc_otg_set_prtsuspend(dwc_otg_core_if_t * core_if, uint32_t val) +{ + hprt0_data_t hprt0; + hprt0.d32 = dwc_otg_read_hprt0(core_if); + hprt0.b.prtsusp = val; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); +} + +uint32_t dwc_otg_get_fr_interval(dwc_otg_core_if_t * core_if) +{ + hfir_data_t hfir; + hfir.d32 = DWC_READ_REG32(&core_if->host_if->host_global_regs->hfir); + return hfir.b.frint; + +} + +void dwc_otg_set_fr_interval(dwc_otg_core_if_t * core_if, uint32_t val) +{ + hfir_data_t hfir; + uint32_t fram_int; + fram_int = calc_frame_interval(core_if); + hfir.d32 = DWC_READ_REG32(&core_if->host_if->host_global_regs->hfir); + if (!core_if->core_params->reload_ctl) { + DWC_WARN("\nCannot reload HFIR register.HFIR.HFIRRldCtrl bit is" + "not set to 1.\nShould load driver with reload_ctl=1" + " module parameter\n"); + return; + } + switch (fram_int) { + case 3750: + if ((val < 3350) || (val > 4150)) { + DWC_WARN("HFIR interval for HS core and 30 MHz" + "clock freq should be from 3350 to 4150\n"); + return; + } + break; + case 30000: + if ((val < 26820) || (val > 33180)) { + DWC_WARN("HFIR interval for FS/LS core and 30 MHz" + "clock freq should be from 26820 to 33180\n"); + return; + } + break; + case 6000: + if ((val < 5360) || (val > 6640)) { + DWC_WARN("HFIR interval for HS core and 48 MHz" + "clock freq should be from 5360 to 6640\n"); + return; + } + break; + case 48000: + if ((val < 42912) || (val > 53088)) { + DWC_WARN("HFIR interval for FS/LS core and 48 MHz" + "clock freq should be from 42912 to 53088\n"); + return; + } + break; + case 7500: + if ((val < 6700) || (val > 8300)) { + DWC_WARN("HFIR interval for HS core and 60 MHz" + "clock freq should be from 6700 to 8300\n"); + return; + } + break; + case 60000: + if ((val < 53640) || (val > 65536)) { + DWC_WARN("HFIR interval for FS/LS core and 60 MHz" + "clock freq should be from 53640 to 65536\n"); + return; + } + break; + default: + DWC_WARN("Unknown frame interval\n"); + return; + break; + + } + hfir.b.frint = val; + DWC_WRITE_REG32(&core_if->host_if->host_global_regs->hfir, hfir.d32); +} + +uint32_t dwc_otg_get_mode_ch_tim(dwc_otg_core_if_t * core_if) +{ + hcfg_data_t hcfg; + hcfg.d32 = DWC_READ_REG32(&core_if->host_if->host_global_regs->hcfg); + return hcfg.b.modechtimen; + +} + +void dwc_otg_set_mode_ch_tim(dwc_otg_core_if_t * core_if, uint32_t val) +{ + hcfg_data_t hcfg; + hcfg.d32 = DWC_READ_REG32(&core_if->host_if->host_global_regs->hcfg); + hcfg.b.modechtimen = val; + DWC_WRITE_REG32(&core_if->host_if->host_global_regs->hcfg, hcfg.d32); +} + +void dwc_otg_set_prtresume(dwc_otg_core_if_t * core_if, uint32_t val) +{ + hprt0_data_t hprt0; + hprt0.d32 = dwc_otg_read_hprt0(core_if); + hprt0.b.prtres = val; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); +} + +uint32_t dwc_otg_get_remotewakesig(dwc_otg_core_if_t * core_if) +{ + dctl_data_t dctl; + dctl.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dctl); + return dctl.b.rmtwkupsig; +} + +uint32_t dwc_otg_get_lpm_portsleepstatus(dwc_otg_core_if_t * core_if) +{ + glpmcfg_data_t lpmcfg; + lpmcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->glpmcfg); + + DWC_ASSERT(! + ((core_if->lx_state == DWC_OTG_L1) ^ lpmcfg.b.prt_sleep_sts), + "lx_state = %d, lmpcfg.prt_sleep_sts = %d\n", + core_if->lx_state, lpmcfg.b.prt_sleep_sts); + + return lpmcfg.b.prt_sleep_sts; +} + +uint32_t dwc_otg_get_lpm_remotewakeenabled(dwc_otg_core_if_t * core_if) +{ + glpmcfg_data_t lpmcfg; + lpmcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->glpmcfg); + return lpmcfg.b.rem_wkup_en; +} + +uint32_t dwc_otg_get_lpmresponse(dwc_otg_core_if_t * core_if) +{ + glpmcfg_data_t lpmcfg; + lpmcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->glpmcfg); + return lpmcfg.b.appl_resp; +} + +void dwc_otg_set_lpmresponse(dwc_otg_core_if_t * core_if, uint32_t val) +{ + glpmcfg_data_t lpmcfg; + lpmcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->glpmcfg); + lpmcfg.b.appl_resp = val; + DWC_WRITE_REG32(&core_if->core_global_regs->glpmcfg, lpmcfg.d32); +} + +uint32_t dwc_otg_get_hsic_connect(dwc_otg_core_if_t * core_if) +{ + glpmcfg_data_t lpmcfg; + lpmcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->glpmcfg); + return lpmcfg.b.hsic_connect; +} + +void dwc_otg_set_hsic_connect(dwc_otg_core_if_t * core_if, uint32_t val) +{ + glpmcfg_data_t lpmcfg; + lpmcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->glpmcfg); + lpmcfg.b.hsic_connect = val; + DWC_WRITE_REG32(&core_if->core_global_regs->glpmcfg, lpmcfg.d32); +} + +uint32_t dwc_otg_get_inv_sel_hsic(dwc_otg_core_if_t * core_if) +{ + glpmcfg_data_t lpmcfg; + lpmcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->glpmcfg); + return lpmcfg.b.inv_sel_hsic; + +} + +void dwc_otg_set_inv_sel_hsic(dwc_otg_core_if_t * core_if, uint32_t val) +{ + glpmcfg_data_t lpmcfg; + lpmcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->glpmcfg); + lpmcfg.b.inv_sel_hsic = val; + DWC_WRITE_REG32(&core_if->core_global_regs->glpmcfg, lpmcfg.d32); +} + +uint32_t dwc_otg_get_gotgctl(dwc_otg_core_if_t * core_if) +{ + return DWC_READ_REG32(&core_if->core_global_regs->gotgctl); +} + +void dwc_otg_set_gotgctl(dwc_otg_core_if_t * core_if, uint32_t val) +{ + DWC_WRITE_REG32(&core_if->core_global_regs->gotgctl, val); +} + +uint32_t dwc_otg_get_gusbcfg(dwc_otg_core_if_t * core_if) +{ + return DWC_READ_REG32(&core_if->core_global_regs->gusbcfg); +} + +void dwc_otg_set_gusbcfg(dwc_otg_core_if_t * core_if, uint32_t val) +{ + DWC_WRITE_REG32(&core_if->core_global_regs->gusbcfg, val); +} + +uint32_t dwc_otg_get_grxfsiz(dwc_otg_core_if_t * core_if) +{ + return DWC_READ_REG32(&core_if->core_global_regs->grxfsiz); +} + +void dwc_otg_set_grxfsiz(dwc_otg_core_if_t * core_if, uint32_t val) +{ + DWC_WRITE_REG32(&core_if->core_global_regs->grxfsiz, val); +} + +uint32_t dwc_otg_get_gnptxfsiz(dwc_otg_core_if_t * core_if) +{ + return DWC_READ_REG32(&core_if->core_global_regs->gnptxfsiz); +} + +void dwc_otg_set_gnptxfsiz(dwc_otg_core_if_t * core_if, uint32_t val) +{ + DWC_WRITE_REG32(&core_if->core_global_regs->gnptxfsiz, val); +} + +uint32_t dwc_otg_get_gpvndctl(dwc_otg_core_if_t * core_if) +{ + return DWC_READ_REG32(&core_if->core_global_regs->gpvndctl); +} + +void dwc_otg_set_gpvndctl(dwc_otg_core_if_t * core_if, uint32_t val) +{ + DWC_WRITE_REG32(&core_if->core_global_regs->gpvndctl, val); +} + +uint32_t dwc_otg_get_ggpio(dwc_otg_core_if_t * core_if) +{ + return DWC_READ_REG32(&core_if->core_global_regs->ggpio); +} + +void dwc_otg_set_ggpio(dwc_otg_core_if_t * core_if, uint32_t val) +{ + DWC_WRITE_REG32(&core_if->core_global_regs->ggpio, val); +} + +uint32_t dwc_otg_get_hprt0(dwc_otg_core_if_t * core_if) +{ + return DWC_READ_REG32(core_if->host_if->hprt0); + +} + +void dwc_otg_set_hprt0(dwc_otg_core_if_t * core_if, uint32_t val) +{ + DWC_WRITE_REG32(core_if->host_if->hprt0, val); +} + +uint32_t dwc_otg_get_guid(dwc_otg_core_if_t * core_if) +{ + return DWC_READ_REG32(&core_if->core_global_regs->guid); +} + +void dwc_otg_set_guid(dwc_otg_core_if_t * core_if, uint32_t val) +{ + DWC_WRITE_REG32(&core_if->core_global_regs->guid, val); +} + +uint32_t dwc_otg_get_hptxfsiz(dwc_otg_core_if_t * core_if) +{ + return DWC_READ_REG32(&core_if->core_global_regs->hptxfsiz); +} + +uint16_t dwc_otg_get_otg_version(dwc_otg_core_if_t * core_if) +{ + return ((core_if->otg_ver == 1) ? (uint16_t)0x0200 : (uint16_t)0x0103); +} + +/** + * Start the SRP timer to detect when the SRP does not complete within + * 6 seconds. + * + * @param core_if the pointer to core_if strucure. + */ +static void dwc_otg_pcd_start_srp_timer(dwc_otg_core_if_t * core_if) +{ + core_if->srp_timer_started = 1; + DWC_TIMER_SCHEDULE(core_if->srp_timer, 6000 /* 6 secs */ ); +} + +void dwc_otg_initiate_srp(dwc_otg_core_if_t * core_if) +{ + uint32_t *addr = (uint32_t *) & (core_if->core_global_regs->gotgctl); + gotgctl_data_t mem; + gotgctl_data_t val; + + val.d32 = DWC_READ_REG32(addr); + if (val.b.sesreq) { + DWC_ERROR("Session Request Already active!\n"); + return; + } + + DWC_INFO("Session Request Initated\n"); //NOTICE + mem.d32 = DWC_READ_REG32(addr); + mem.b.sesreq = 1; + DWC_WRITE_REG32(addr, mem.d32); + + /* Start the SRP timer */ + dwc_otg_pcd_start_srp_timer(core_if); + return; +} diff --git a/drivers/usb/host/dwc_otg/dwc_otg_cil.h b/drivers/usb/host/dwc_otg/dwc_otg_cil.h new file mode 100644 index 00000000000000..79dbf8374f023e --- /dev/null +++ b/drivers/usb/host/dwc_otg/dwc_otg_cil.h @@ -0,0 +1,1464 @@ +/* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_cil.h $ + * $Revision: #123 $ + * $Date: 2012/08/10 $ + * $Change: 2047372 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ + +#if !defined(__DWC_CIL_H__) +#define __DWC_CIL_H__ + +#include "dwc_list.h" +#include "dwc_otg_dbg.h" +#include "dwc_otg_regs.h" + +#include "dwc_otg_core_if.h" +#include "dwc_otg_adp.h" + +/** + * @file + * This file contains the interface to the Core Interface Layer. + */ + +#ifdef DWC_UTE_CFI + +#define MAX_DMA_DESCS_PER_EP 256 + +/** + * Enumeration for the data buffer mode + */ +typedef enum _data_buffer_mode { + BM_STANDARD = 0, /* data buffer is in normal mode */ + BM_SG = 1, /* data buffer uses the scatter/gather mode */ + BM_CONCAT = 2, /* data buffer uses the concatenation mode */ + BM_CIRCULAR = 3, /* data buffer uses the circular DMA mode */ + BM_ALIGN = 4 /* data buffer is in buffer alignment mode */ +} data_buffer_mode_e; +#endif //DWC_UTE_CFI + +/** Macros defined for DWC OTG HW Release version */ + +#define OTG_CORE_REV_2_60a 0x4F54260A +#define OTG_CORE_REV_2_71a 0x4F54271A +#define OTG_CORE_REV_2_72a 0x4F54272A +#define OTG_CORE_REV_2_80a 0x4F54280A +#define OTG_CORE_REV_2_81a 0x4F54281A +#define OTG_CORE_REV_2_90a 0x4F54290A +#define OTG_CORE_REV_2_91a 0x4F54291A +#define OTG_CORE_REV_2_92a 0x4F54292A +#define OTG_CORE_REV_2_93a 0x4F54293A +#define OTG_CORE_REV_2_94a 0x4F54294A +#define OTG_CORE_REV_3_00a 0x4F54300A + +/** + * Information for each ISOC packet. + */ +typedef struct iso_pkt_info { + uint32_t offset; + uint32_t length; + int32_t status; +} iso_pkt_info_t; + +/** + * The <code>dwc_ep</code> structure represents the state of a single + * endpoint when acting in device mode. It contains the data items + * needed for an endpoint to be activated and transfer packets. + */ +typedef struct dwc_ep { + /** EP number used for register address lookup */ + uint8_t num; + /** EP direction 0 = OUT */ + unsigned is_in:1; + /** EP active. */ + unsigned active:1; + + /** + * Periodic Tx FIFO # for IN EPs For INTR EP set to 0 to use non-periodic + * Tx FIFO. If dedicated Tx FIFOs are enabled Tx FIFO # FOR IN EPs*/ + unsigned tx_fifo_num:4; + /** EP type: 0 - Control, 1 - ISOC, 2 - BULK, 3 - INTR */ + unsigned type:2; +#define DWC_OTG_EP_TYPE_CONTROL 0 +#define DWC_OTG_EP_TYPE_ISOC 1 +#define DWC_OTG_EP_TYPE_BULK 2 +#define DWC_OTG_EP_TYPE_INTR 3 + + /** DATA start PID for INTR and BULK EP */ + unsigned data_pid_start:1; + /** Frame (even/odd) for ISOC EP */ + unsigned even_odd_frame:1; + /** Max Packet bytes */ + unsigned maxpacket:11; + + /** Max Transfer size */ + uint32_t maxxfer; + + /** @name Transfer state */ + /** @{ */ + + /** + * Pointer to the beginning of the transfer buffer -- do not modify + * during transfer. + */ + + dwc_dma_t dma_addr; + + dwc_dma_t dma_desc_addr; + dwc_otg_dev_dma_desc_t *desc_addr; + + uint8_t *start_xfer_buff; + /** pointer to the transfer buffer */ + uint8_t *xfer_buff; + /** Number of bytes to transfer */ + unsigned xfer_len:19; + /** Number of bytes transferred. */ + unsigned xfer_count:19; + /** Sent ZLP */ + unsigned sent_zlp:1; + /** Total len for control transfer */ + unsigned total_len:19; + + /** stall clear flag */ + unsigned stall_clear_flag:1; + + /** SETUP pkt cnt rollover flag for EP0 out*/ + unsigned stp_rollover; + +#ifdef DWC_UTE_CFI + /* The buffer mode */ + data_buffer_mode_e buff_mode; + + /* The chain of DMA descriptors. + * MAX_DMA_DESCS_PER_EP will be allocated for each active EP. + */ + dwc_otg_dma_desc_t *descs; + + /* The DMA address of the descriptors chain start */ + dma_addr_t descs_dma_addr; + /** This variable stores the length of the last enqueued request */ + uint32_t cfi_req_len; +#endif //DWC_UTE_CFI + +/** Max DMA Descriptor count for any EP */ +#define MAX_DMA_DESC_CNT 256 + /** Allocated DMA Desc count */ + uint32_t desc_cnt; + + /** bInterval */ + uint32_t bInterval; + /** Next frame num to setup next ISOC transfer */ + uint32_t frame_num; + /** Indicates SOF number overrun in DSTS */ + uint8_t frm_overrun; + +#ifdef DWC_UTE_PER_IO + /** Next frame num for which will be setup DMA Desc */ + uint32_t xiso_frame_num; + /** bInterval */ + uint32_t xiso_bInterval; + /** Count of currently active transfers - shall be either 0 or 1 */ + int xiso_active_xfers; + int xiso_queued_xfers; +#endif +#ifdef DWC_EN_ISOC + /** + * Variables specific for ISOC EPs + * + */ + /** DMA addresses of ISOC buffers */ + dwc_dma_t dma_addr0; + dwc_dma_t dma_addr1; + + dwc_dma_t iso_dma_desc_addr; + dwc_otg_dev_dma_desc_t *iso_desc_addr; + + /** pointer to the transfer buffers */ + uint8_t *xfer_buff0; + uint8_t *xfer_buff1; + + /** number of ISOC Buffer is processing */ + uint32_t proc_buf_num; + /** Interval of ISOC Buffer processing */ + uint32_t buf_proc_intrvl; + /** Data size for regular frame */ + uint32_t data_per_frame; + + /* todo - pattern data support is to be implemented in the future */ + /** Data size for pattern frame */ + uint32_t data_pattern_frame; + /** Frame number of pattern data */ + uint32_t sync_frame; + + /** bInterval */ + uint32_t bInterval; + /** ISO Packet number per frame */ + uint32_t pkt_per_frm; + /** Next frame num for which will be setup DMA Desc */ + uint32_t next_frame; + /** Number of packets per buffer processing */ + uint32_t pkt_cnt; + /** Info for all isoc packets */ + iso_pkt_info_t *pkt_info; + /** current pkt number */ + uint32_t cur_pkt; + /** current pkt number */ + uint8_t *cur_pkt_addr; + /** current pkt number */ + uint32_t cur_pkt_dma_addr; +#endif /* DWC_EN_ISOC */ + +/** @} */ +} dwc_ep_t; + +/* + * Reasons for halting a host channel. + */ +typedef enum dwc_otg_halt_status { + DWC_OTG_HC_XFER_NO_HALT_STATUS, + DWC_OTG_HC_XFER_COMPLETE, + DWC_OTG_HC_XFER_URB_COMPLETE, + DWC_OTG_HC_XFER_ACK, + DWC_OTG_HC_XFER_NAK, + DWC_OTG_HC_XFER_NYET, + DWC_OTG_HC_XFER_STALL, + DWC_OTG_HC_XFER_XACT_ERR, + DWC_OTG_HC_XFER_FRAME_OVERRUN, + DWC_OTG_HC_XFER_BABBLE_ERR, + DWC_OTG_HC_XFER_DATA_TOGGLE_ERR, + DWC_OTG_HC_XFER_AHB_ERR, + DWC_OTG_HC_XFER_PERIODIC_INCOMPLETE, + DWC_OTG_HC_XFER_URB_DEQUEUE +} dwc_otg_halt_status_e; + +/** + * Host channel descriptor. This structure represents the state of a single + * host channel when acting in host mode. It contains the data items needed to + * transfer packets to an endpoint via a host channel. + */ +typedef struct dwc_hc { + /** Host channel number used for register address lookup */ + uint8_t hc_num; + + /** Device to access */ + unsigned dev_addr:7; + + /** EP to access */ + unsigned ep_num:4; + + /** EP direction. 0: OUT, 1: IN */ + unsigned ep_is_in:1; + + /** + * EP speed. + * One of the following values: + * - DWC_OTG_EP_SPEED_LOW + * - DWC_OTG_EP_SPEED_FULL + * - DWC_OTG_EP_SPEED_HIGH + */ + unsigned speed:2; +#define DWC_OTG_EP_SPEED_LOW 0 +#define DWC_OTG_EP_SPEED_FULL 1 +#define DWC_OTG_EP_SPEED_HIGH 2 + + /** + * Endpoint type. + * One of the following values: + * - DWC_OTG_EP_TYPE_CONTROL: 0 + * - DWC_OTG_EP_TYPE_ISOC: 1 + * - DWC_OTG_EP_TYPE_BULK: 2 + * - DWC_OTG_EP_TYPE_INTR: 3 + */ + unsigned ep_type:2; + + /** Max packet size in bytes */ + unsigned max_packet:11; + + /** + * PID for initial transaction. + * 0: DATA0,<br> + * 1: DATA2,<br> + * 2: DATA1,<br> + * 3: MDATA (non-Control EP), + * SETUP (Control EP) + */ + unsigned data_pid_start:2; +#define DWC_OTG_HC_PID_DATA0 0 +#define DWC_OTG_HC_PID_DATA2 1 +#define DWC_OTG_HC_PID_DATA1 2 +#define DWC_OTG_HC_PID_MDATA 3 +#define DWC_OTG_HC_PID_SETUP 3 + + /** Number of periodic transactions per (micro)frame */ + unsigned multi_count:2; + + /** @name Transfer State */ + /** @{ */ + + /** Pointer to the current transfer buffer position. */ + uint8_t *xfer_buff; + /** + * In Buffer DMA mode this buffer will be used + * if xfer_buff is not DWORD aligned. + */ + dwc_dma_t align_buff; + /** Total number of bytes to transfer. */ + uint32_t xfer_len; + /** Number of bytes transferred so far. */ + uint32_t xfer_count; + /** Packet count at start of transfer.*/ + uint16_t start_pkt_count; + + /** + * Flag to indicate whether the transfer has been started. Set to 1 if + * it has been started, 0 otherwise. + */ + uint8_t xfer_started; + + /** + * Set to 1 to indicate that a PING request should be issued on this + * channel. If 0, process normally. + */ + uint8_t do_ping; + + /** + * Set to 1 to indicate that the error count for this transaction is + * non-zero. Set to 0 if the error count is 0. + */ + uint8_t error_state; + + /** + * Set to 1 to indicate that this channel should be halted the next + * time a request is queued for the channel. This is necessary in + * slave mode if no request queue space is available when an attempt + * is made to halt the channel. + */ + uint8_t halt_on_queue; + + /** + * Set to 1 if the host channel has been halted, but the core is not + * finished flushing queued requests. Otherwise 0. + */ + uint8_t halt_pending; + + /** + * Reason for halting the host channel. + */ + dwc_otg_halt_status_e halt_status; + + /* + * Split settings for the host channel + */ + uint8_t do_split; /**< Enable split for the channel */ + uint8_t complete_split; /**< Enable complete split */ + uint8_t hub_addr; /**< Address of high speed hub */ + + uint8_t port_addr; /**< Port of the low/full speed device */ + /** Split transaction position + * One of the following values: + * - DWC_HCSPLIT_XACTPOS_MID + * - DWC_HCSPLIT_XACTPOS_BEGIN + * - DWC_HCSPLIT_XACTPOS_END + * - DWC_HCSPLIT_XACTPOS_ALL */ + uint8_t xact_pos; + + /** Set when the host channel does a short read. */ + uint8_t short_read; + + /** + * Number of requests issued for this channel since it was assigned to + * the current transfer (not counting PINGs). + */ + uint8_t requests; + + /** + * Queue Head for the transfer being processed by this channel. + */ + struct dwc_otg_qh *qh; + + /** @} */ + + /** Entry in list of host channels. */ + DWC_CIRCLEQ_ENTRY(dwc_hc) hc_list_entry; + + /** @name Descriptor DMA support */ + /** @{ */ + + /** Number of Transfer Descriptors */ + uint16_t ntd; + + /** Descriptor List DMA address */ + dwc_dma_t desc_list_addr; + + /** Scheduling micro-frame bitmap. */ + uint8_t schinfo; + + /** @} */ +} dwc_hc_t; + +/** + * The following parameters may be specified when starting the module. These + * parameters define how the DWC_otg controller should be configured. + */ +typedef struct dwc_otg_core_params { + int32_t opt; + + /** + * Specifies the OTG capabilities. The driver will automatically + * detect the value for this parameter if none is specified. + * 0 - HNP and SRP capable (default) + * 1 - SRP Only capable + * 2 - No HNP/SRP capable + */ + int32_t otg_cap; + + /** + * Specifies whether to use slave or DMA mode for accessing the data + * FIFOs. The driver will automatically detect the value for this + * parameter if none is specified. + * 0 - Slave + * 1 - DMA (default, if available) + */ + int32_t dma_enable; + + /** + * When DMA mode is enabled specifies whether to use address DMA or DMA + * Descriptor mode for accessing the data FIFOs in device mode. The driver + * will automatically detect the value for this if none is specified. + * 0 - address DMA + * 1 - DMA Descriptor(default, if available) + */ + int32_t dma_desc_enable; + /** The DMA Burst size (applicable only for External DMA + * Mode). 1, 4, 8 16, 32, 64, 128, 256 (default 32) + */ + int32_t dma_burst_size; /* Translate this to GAHBCFG values */ + + /** + * Specifies the maximum speed of operation in host and device mode. + * The actual speed depends on the speed of the attached device and + * the value of phy_type. The actual speed depends on the speed of the + * attached device. + * 0 - High Speed (default) + * 1 - Full Speed + */ + int32_t speed; + /** Specifies whether low power mode is supported when attached + * to a Full Speed or Low Speed device in host mode. + * 0 - Don't support low power mode (default) + * 1 - Support low power mode + */ + int32_t host_support_fs_ls_low_power; + + /** Specifies the PHY clock rate in low power mode when connected to a + * Low Speed device in host mode. This parameter is applicable only if + * HOST_SUPPORT_FS_LS_LOW_POWER is enabled. If PHY_TYPE is set to FS + * then defaults to 6 MHZ otherwise 48 MHZ. + * + * 0 - 48 MHz + * 1 - 6 MHz + */ + int32_t host_ls_low_power_phy_clk; + + /** + * 0 - Use cC FIFO size parameters + * 1 - Allow dynamic FIFO sizing (default) + */ + int32_t enable_dynamic_fifo; + + /** Total number of 4-byte words in the data FIFO memory. This + * memory includes the Rx FIFO, non-periodic Tx FIFO, and periodic + * Tx FIFOs. + * 32 to 32768 (default 8192) + * Note: The total FIFO memory depth in the FPGA configuration is 8192. + */ + int32_t data_fifo_size; + + /** Number of 4-byte words in the Rx FIFO in device mode when dynamic + * FIFO sizing is enabled. + * 16 to 32768 (default 1064) + */ + int32_t dev_rx_fifo_size; + + /** Number of 4-byte words in the non-periodic Tx FIFO in device mode + * when dynamic FIFO sizing is enabled. + * 16 to 32768 (default 1024) + */ + int32_t dev_nperio_tx_fifo_size; + + /** Number of 4-byte words in each of the periodic Tx FIFOs in device + * mode when dynamic FIFO sizing is enabled. + * 4 to 768 (default 256) + */ + uint32_t dev_perio_tx_fifo_size[MAX_PERIO_FIFOS]; + + /** Number of 4-byte words in the Rx FIFO in host mode when dynamic + * FIFO sizing is enabled. + * 16 to 32768 (default 1024) + */ + int32_t host_rx_fifo_size; + + /** Number of 4-byte words in the non-periodic Tx FIFO in host mode + * when Dynamic FIFO sizing is enabled in the core. + * 16 to 32768 (default 1024) + */ + int32_t host_nperio_tx_fifo_size; + + /** Number of 4-byte words in the host periodic Tx FIFO when dynamic + * FIFO sizing is enabled. + * 16 to 32768 (default 1024) + */ + int32_t host_perio_tx_fifo_size; + + /** The maximum transfer size supported in bytes. + * 2047 to 65,535 (default 65,535) + */ + int32_t max_transfer_size; + + /** The maximum number of packets in a transfer. + * 15 to 511 (default 511) + */ + int32_t max_packet_count; + + /** The number of host channel registers to use. + * 1 to 16 (default 12) + * Note: The FPGA configuration supports a maximum of 12 host channels. + */ + int32_t host_channels; + + /** The number of endpoints in addition to EP0 available for device + * mode operations. + * 1 to 15 (default 6 IN and OUT) + * Note: The FPGA configuration supports a maximum of 6 IN and OUT + * endpoints in addition to EP0. + */ + int32_t dev_endpoints; + + /** + * Specifies the type of PHY interface to use. By default, the driver + * will automatically detect the phy_type. + * + * 0 - Full Speed PHY + * 1 - UTMI+ (default) + * 2 - ULPI + */ + int32_t phy_type; + + /** + * Specifies the UTMI+ Data Width. This parameter is + * applicable for a PHY_TYPE of UTMI+ or ULPI. (For a ULPI + * PHY_TYPE, this parameter indicates the data width between + * the MAC and the ULPI Wrapper.) Also, this parameter is + * applicable only if the OTG_HSPHY_WIDTH cC parameter was set + * to "8 and 16 bits", meaning that the core has been + * configured to work at either data path width. + * + * 8 or 16 bits (default 16) + */ + int32_t phy_utmi_width; + + /** + * Specifies whether the ULPI operates at double or single + * data rate. This parameter is only applicable if PHY_TYPE is + * ULPI. + * + * 0 - single data rate ULPI interface with 8 bit wide data + * bus (default) + * 1 - double data rate ULPI interface with 4 bit wide data + * bus + */ + int32_t phy_ulpi_ddr; + + /** + * Specifies whether to use the internal or external supply to + * drive the vbus with a ULPI phy. + */ + int32_t phy_ulpi_ext_vbus; + + /** + * Specifies whether to use the I2Cinterface for full speed PHY. This + * parameter is only applicable if PHY_TYPE is FS. + * 0 - No (default) + * 1 - Yes + */ + int32_t i2c_enable; + + int32_t ulpi_fs_ls; + + int32_t ts_dline; + + /** + * Specifies whether dedicated transmit FIFOs are + * enabled for non periodic IN endpoints in device mode + * 0 - No + * 1 - Yes + */ + int32_t en_multiple_tx_fifo; + + /** Number of 4-byte words in each of the Tx FIFOs in device + * mode when dynamic FIFO sizing is enabled. + * 4 to 768 (default 256) + */ + uint32_t dev_tx_fifo_size[MAX_TX_FIFOS]; + + /** Thresholding enable flag- + * bit 0 - enable non-ISO Tx thresholding + * bit 1 - enable ISO Tx thresholding + * bit 2 - enable Rx thresholding + */ + uint32_t thr_ctl; + + /** Thresholding length for Tx + * FIFOs in 32 bit DWORDs + */ + uint32_t tx_thr_length; + + /** Thresholding length for Rx + * FIFOs in 32 bit DWORDs + */ + uint32_t rx_thr_length; + + /** + * Specifies whether LPM (Link Power Management) support is enabled + */ + int32_t lpm_enable; + + /** Per Transfer Interrupt + * mode enable flag + * 1 - Enabled + * 0 - Disabled + */ + int32_t pti_enable; + + /** Multi Processor Interrupt + * mode enable flag + * 1 - Enabled + * 0 - Disabled + */ + int32_t mpi_enable; + + /** IS_USB Capability + * 1 - Enabled + * 0 - Disabled + */ + int32_t ic_usb_cap; + + /** AHB Threshold Ratio + * 2'b00 AHB Threshold = MAC Threshold + * 2'b01 AHB Threshold = 1/2 MAC Threshold + * 2'b10 AHB Threshold = 1/4 MAC Threshold + * 2'b11 AHB Threshold = 1/8 MAC Threshold + */ + int32_t ahb_thr_ratio; + + /** ADP Support + * 1 - Enabled + * 0 - Disabled + */ + int32_t adp_supp_enable; + + /** HFIR Reload Control + * 0 - The HFIR cannot be reloaded dynamically. + * 1 - Allow dynamic reloading of the HFIR register during runtime. + */ + int32_t reload_ctl; + + /** DCFG: Enable device Out NAK + * 0 - The core does not set NAK after Bulk Out transfer complete. + * 1 - The core sets NAK after Bulk OUT transfer complete. + */ + int32_t dev_out_nak; + + /** DCFG: Enable Continue on BNA + * After receiving BNA interrupt the core disables the endpoint,when the + * endpoint is re-enabled by the application the core starts processing + * 0 - from the DOEPDMA descriptor + * 1 - from the descriptor which received the BNA. + */ + int32_t cont_on_bna; + + /** GAHBCFG: AHB Single Support + * This bit when programmed supports SINGLE transfers for remainder + * data in a transfer for DMA mode of operation. + * 0 - in this case the remainder data will be sent using INCR burst size. + * 1 - in this case the remainder data will be sent using SINGLE burst size. + */ + int32_t ahb_single; + + /** Core Power down mode + * 0 - No Power Down is enabled + * 1 - Reserved + * 2 - Complete Power Down (Hibernation) + */ + int32_t power_down; + + /** OTG revision supported + * 0 - OTG 1.3 revision + * 1 - OTG 2.0 revision + */ + int32_t otg_ver; + +} dwc_otg_core_params_t; + +#ifdef DEBUG +struct dwc_otg_core_if; +typedef struct hc_xfer_info { + struct dwc_otg_core_if *core_if; + dwc_hc_t *hc; +} hc_xfer_info_t; +#endif + +typedef struct ep_xfer_info { + struct dwc_otg_core_if *core_if; + dwc_ep_t *ep; + uint8_t state; +} ep_xfer_info_t; +/* + * Device States + */ +typedef enum dwc_otg_lx_state { + /** On state */ + DWC_OTG_L0, + /** LPM sleep state*/ + DWC_OTG_L1, + /** USB suspend state*/ + DWC_OTG_L2, + /** Off state*/ + DWC_OTG_L3 +} dwc_otg_lx_state_e; + +struct dwc_otg_global_regs_backup { + uint32_t gotgctl_local; + uint32_t gintmsk_local; + uint32_t gahbcfg_local; + uint32_t gusbcfg_local; + uint32_t grxfsiz_local; + uint32_t gnptxfsiz_local; +#ifdef CONFIG_USB_DWC_OTG_LPM + uint32_t glpmcfg_local; +#endif + uint32_t gi2cctl_local; + uint32_t hptxfsiz_local; + uint32_t pcgcctl_local; + uint32_t gdfifocfg_local; + uint32_t dtxfsiz_local[MAX_EPS_CHANNELS]; + uint32_t gpwrdn_local; + uint32_t xhib_pcgcctl; + uint32_t xhib_gpwrdn; +}; + +struct dwc_otg_host_regs_backup { + uint32_t hcfg_local; + uint32_t haintmsk_local; + uint32_t hcintmsk_local[MAX_EPS_CHANNELS]; + uint32_t hprt0_local; + uint32_t hfir_local; +}; + +struct dwc_otg_dev_regs_backup { + uint32_t dcfg; + uint32_t dctl; + uint32_t daintmsk; + uint32_t diepmsk; + uint32_t doepmsk; + uint32_t diepctl[MAX_EPS_CHANNELS]; + uint32_t dieptsiz[MAX_EPS_CHANNELS]; + uint32_t diepdma[MAX_EPS_CHANNELS]; +}; +/** + * The <code>dwc_otg_core_if</code> structure contains information needed to manage + * the DWC_otg controller acting in either host or device mode. It + * represents the programming view of the controller as a whole. + */ +struct dwc_otg_core_if { + /** Parameters that define how the core should be configured.*/ + dwc_otg_core_params_t *core_params; + + /** Core Global registers starting at offset 000h. */ + dwc_otg_core_global_regs_t *core_global_regs; + + /** Device-specific information */ + dwc_otg_dev_if_t *dev_if; + /** Host-specific information */ + dwc_otg_host_if_t *host_if; + + /** Value from SNPSID register */ + uint32_t snpsid; + + /* + * Set to 1 if the core PHY interface bits in USBCFG have been + * initialized. + */ + uint8_t phy_init_done; + + /* + * SRP Success flag, set by srp success interrupt in FS I2C mode + */ + uint8_t srp_success; + uint8_t srp_timer_started; + /** Timer for SRP. If it expires before SRP is successful + * clear the SRP. */ + dwc_timer_t *srp_timer; + +#ifdef DWC_DEV_SRPCAP + /* This timer is needed to power on the hibernated host core if SRP is not + * initiated on connected SRP capable device for limited period of time + */ + uint8_t pwron_timer_started; + dwc_timer_t *pwron_timer; +#endif + /* Common configuration information */ + /** Power and Clock Gating Control Register */ + volatile uint32_t *pcgcctl; +#define DWC_OTG_PCGCCTL_OFFSET 0xE00 + + /** Push/pop addresses for endpoints or host channels.*/ + uint32_t *data_fifo[MAX_EPS_CHANNELS]; +#define DWC_OTG_DATA_FIFO_OFFSET 0x1000 +#define DWC_OTG_DATA_FIFO_SIZE 0x1000 + + /** Total RAM for FIFOs (Bytes) */ + uint16_t total_fifo_size; + /** Size of Rx FIFO (Bytes) */ + uint16_t rx_fifo_size; + /** Size of Non-periodic Tx FIFO (Bytes) */ + uint16_t nperio_tx_fifo_size; + + /** 1 if DMA is enabled, 0 otherwise. */ + uint8_t dma_enable; + + /** 1 if DMA descriptor is enabled, 0 otherwise. */ + uint8_t dma_desc_enable; + + /** 1 if PTI Enhancement mode is enabled, 0 otherwise. */ + uint8_t pti_enh_enable; + + /** 1 if MPI Enhancement mode is enabled, 0 otherwise. */ + uint8_t multiproc_int_enable; + + /** 1 if dedicated Tx FIFOs are enabled, 0 otherwise. */ + uint8_t en_multiple_tx_fifo; + + /** Set to 1 if multiple packets of a high-bandwidth transfer is in + * process of being queued */ + uint8_t queuing_high_bandwidth; + + /** Hardware Configuration -- stored here for convenience.*/ + hwcfg1_data_t hwcfg1; + hwcfg2_data_t hwcfg2; + hwcfg3_data_t hwcfg3; + hwcfg4_data_t hwcfg4; + fifosize_data_t hptxfsiz; + + /** Host and Device Configuration -- stored here for convenience.*/ + hcfg_data_t hcfg; + dcfg_data_t dcfg; + + /** The operational State, during transations + * (a_host>>a_peripherial and b_device=>b_host) this may not + * match the core but allows the software to determine + * transitions. + */ + uint8_t op_state; + + /** + * Set to 1 if the HCD needs to be restarted on a session request + * interrupt. This is required if no connector ID status change has + * occurred since the HCD was last disconnected. + */ + uint8_t restart_hcd_on_session_req; + + /** HCD callbacks */ + /** A-Device is a_host */ +#define A_HOST (1) + /** A-Device is a_suspend */ +#define A_SUSPEND (2) + /** A-Device is a_peripherial */ +#define A_PERIPHERAL (3) + /** B-Device is operating as a Peripheral. */ +#define B_PERIPHERAL (4) + /** B-Device is operating as a Host. */ +#define B_HOST (5) + + /** HCD callbacks */ + struct dwc_otg_cil_callbacks *hcd_cb; + /** PCD callbacks */ + struct dwc_otg_cil_callbacks *pcd_cb; + + /** Device mode Periodic Tx FIFO Mask */ + uint32_t p_tx_msk; + /** Device mode Periodic Tx FIFO Mask */ + uint32_t tx_msk; + + /** Workqueue object used for handling several interrupts */ + dwc_workq_t *wq_otg; + + /** Timer object used for handling "Wakeup Detected" Interrupt */ + dwc_timer_t *wkp_timer; + /** This arrays used for debug purposes for DEV OUT NAK enhancement */ + uint32_t start_doeptsiz_val[MAX_EPS_CHANNELS]; + ep_xfer_info_t ep_xfer_info[MAX_EPS_CHANNELS]; + dwc_timer_t *ep_xfer_timer[MAX_EPS_CHANNELS]; +#ifdef DEBUG + uint32_t start_hcchar_val[MAX_EPS_CHANNELS]; + + hc_xfer_info_t hc_xfer_info[MAX_EPS_CHANNELS]; + dwc_timer_t *hc_xfer_timer[MAX_EPS_CHANNELS]; + + uint32_t hfnum_7_samples; + uint64_t hfnum_7_frrem_accum; + uint32_t hfnum_0_samples; + uint64_t hfnum_0_frrem_accum; + uint32_t hfnum_other_samples; + uint64_t hfnum_other_frrem_accum; +#endif + +#ifdef DWC_UTE_CFI + uint16_t pwron_rxfsiz; + uint16_t pwron_gnptxfsiz; + uint16_t pwron_txfsiz[15]; + + uint16_t init_rxfsiz; + uint16_t init_gnptxfsiz; + uint16_t init_txfsiz[15]; +#endif + + /** Lx state of device */ + dwc_otg_lx_state_e lx_state; + + /** Saved Core Global registers */ + struct dwc_otg_global_regs_backup *gr_backup; + /** Saved Host registers */ + struct dwc_otg_host_regs_backup *hr_backup; + /** Saved Device registers */ + struct dwc_otg_dev_regs_backup *dr_backup; + + /** Power Down Enable */ + uint32_t power_down; + + /** ADP support Enable */ + uint32_t adp_enable; + + /** ADP structure object */ + dwc_otg_adp_t adp; + + /** hibernation/suspend flag */ + int hibernation_suspend; + + /** Device mode extended hibernation flag */ + int xhib; + + /** OTG revision supported */ + uint32_t otg_ver; + + /** OTG status flag used for HNP polling */ + uint8_t otg_sts; + + /** Pointer to either hcd->lock or pcd->lock */ + dwc_spinlock_t *lock; + + /** Start predict NextEP based on Learning Queue if equal 1, + * also used as counter of disabled NP IN EP's */ + uint8_t start_predict; + + /** NextEp sequence, including EP0: nextep_seq[] = EP if non-periodic and + * active, 0xff otherwise */ + uint8_t nextep_seq[MAX_EPS_CHANNELS]; + + /** Index of fisrt EP in nextep_seq array which should be re-enabled **/ + uint8_t first_in_nextep_seq; + + /** Frame number while entering to ISR - needed for ISOCs **/ + uint32_t frame_num; + +}; + +#ifdef DEBUG +/* + * This function is called when transfer is timed out. + */ +extern void hc_xfer_timeout(void *ptr); +#endif + +/* + * This function is called when transfer is timed out on endpoint. + */ +extern void ep_xfer_timeout(void *ptr); + +/* + * The following functions are functions for works + * using during handling some interrupts + */ +extern void w_conn_id_status_change(void *p); + +extern void w_wakeup_detected(void *p); + +/** Saves global register values into system memory. */ +extern int dwc_otg_save_global_regs(dwc_otg_core_if_t * core_if); +/** Saves device register values into system memory. */ +extern int dwc_otg_save_dev_regs(dwc_otg_core_if_t * core_if); +/** Saves host register values into system memory. */ +extern int dwc_otg_save_host_regs(dwc_otg_core_if_t * core_if); +/** Restore global register values. */ +extern int dwc_otg_restore_global_regs(dwc_otg_core_if_t * core_if); +/** Restore host register values. */ +extern int dwc_otg_restore_host_regs(dwc_otg_core_if_t * core_if, int reset); +/** Restore device register values. */ +extern int dwc_otg_restore_dev_regs(dwc_otg_core_if_t * core_if, + int rem_wakeup); +extern int restore_lpm_i2c_regs(dwc_otg_core_if_t * core_if); +extern int restore_essential_regs(dwc_otg_core_if_t * core_if, int rmode, + int is_host); + +extern int dwc_otg_host_hibernation_restore(dwc_otg_core_if_t * core_if, + int restore_mode, int reset); +extern int dwc_otg_device_hibernation_restore(dwc_otg_core_if_t * core_if, + int rem_wakeup, int reset); + +/* + * The following functions support initialization of the CIL driver component + * and the DWC_otg controller. + */ +extern void dwc_otg_core_host_init(dwc_otg_core_if_t * _core_if); +extern void dwc_otg_core_dev_init(dwc_otg_core_if_t * _core_if); + +/** @name Device CIL Functions + * The following functions support managing the DWC_otg controller in device + * mode. + */ +/**@{*/ +extern void dwc_otg_wakeup(dwc_otg_core_if_t * _core_if); +extern void dwc_otg_read_setup_packet(dwc_otg_core_if_t * _core_if, + uint32_t * _dest); +extern uint32_t dwc_otg_get_frame_number(dwc_otg_core_if_t * _core_if); +extern void dwc_otg_ep0_activate(dwc_otg_core_if_t * _core_if, dwc_ep_t * _ep); +extern void dwc_otg_ep_activate(dwc_otg_core_if_t * _core_if, dwc_ep_t * _ep); +extern void dwc_otg_ep_deactivate(dwc_otg_core_if_t * _core_if, dwc_ep_t * _ep); +extern void dwc_otg_ep_start_transfer(dwc_otg_core_if_t * _core_if, + dwc_ep_t * _ep); +extern void dwc_otg_ep_start_zl_transfer(dwc_otg_core_if_t * _core_if, + dwc_ep_t * _ep); +extern void dwc_otg_ep0_start_transfer(dwc_otg_core_if_t * _core_if, + dwc_ep_t * _ep); +extern void dwc_otg_ep0_continue_transfer(dwc_otg_core_if_t * _core_if, + dwc_ep_t * _ep); +extern void dwc_otg_ep_write_packet(dwc_otg_core_if_t * _core_if, + dwc_ep_t * _ep, int _dma); +extern void dwc_otg_ep_set_stall(dwc_otg_core_if_t * _core_if, dwc_ep_t * _ep); +extern void dwc_otg_ep_clear_stall(dwc_otg_core_if_t * _core_if, + dwc_ep_t * _ep); +extern void dwc_otg_enable_device_interrupts(dwc_otg_core_if_t * _core_if); + +#ifdef DWC_EN_ISOC +extern void dwc_otg_iso_ep_start_frm_transfer(dwc_otg_core_if_t * core_if, + dwc_ep_t * ep); +extern void dwc_otg_iso_ep_start_buf_transfer(dwc_otg_core_if_t * core_if, + dwc_ep_t * ep); +#endif /* DWC_EN_ISOC */ +/**@}*/ + +/** @name Host CIL Functions + * The following functions support managing the DWC_otg controller in host + * mode. + */ +/**@{*/ +extern void dwc_otg_hc_init(dwc_otg_core_if_t * _core_if, dwc_hc_t * _hc); +extern void dwc_otg_hc_halt(dwc_otg_core_if_t * _core_if, + dwc_hc_t * _hc, dwc_otg_halt_status_e _halt_status); +extern void dwc_otg_hc_cleanup(dwc_otg_core_if_t * _core_if, dwc_hc_t * _hc); +extern void dwc_otg_hc_start_transfer(dwc_otg_core_if_t * _core_if, + dwc_hc_t * _hc); +extern int dwc_otg_hc_continue_transfer(dwc_otg_core_if_t * _core_if, + dwc_hc_t * _hc); +extern void dwc_otg_hc_do_ping(dwc_otg_core_if_t * _core_if, dwc_hc_t * _hc); +extern void dwc_otg_hc_write_packet(dwc_otg_core_if_t * _core_if, + dwc_hc_t * _hc); +extern void dwc_otg_enable_host_interrupts(dwc_otg_core_if_t * _core_if); +extern void dwc_otg_disable_host_interrupts(dwc_otg_core_if_t * _core_if); + +extern void dwc_otg_hc_start_transfer_ddma(dwc_otg_core_if_t * core_if, + dwc_hc_t * hc); + +extern uint32_t calc_frame_interval(dwc_otg_core_if_t * core_if); + +/* Macro used to clear one channel interrupt */ +#define clear_hc_int(_hc_regs_, _intr_) \ +do { \ + hcint_data_t hcint_clear = {.d32 = 0}; \ + hcint_clear.b._intr_ = 1; \ + DWC_WRITE_REG32(&(_hc_regs_)->hcint, hcint_clear.d32); \ +} while (0) + +/* + * Macro used to disable one channel interrupt. Channel interrupts are + * disabled when the channel is halted or released by the interrupt handler. + * There is no need to handle further interrupts of that type until the + * channel is re-assigned. In fact, subsequent handling may cause crashes + * because the channel structures are cleaned up when the channel is released. + */ +#define disable_hc_int(_hc_regs_, _intr_) \ +do { \ + hcintmsk_data_t hcintmsk = {.d32 = 0}; \ + hcintmsk.b._intr_ = 1; \ + DWC_MODIFY_REG32(&(_hc_regs_)->hcintmsk, hcintmsk.d32, 0); \ +} while (0) + +/** + * This function Reads HPRT0 in preparation to modify. It keeps the + * WC bits 0 so that if they are read as 1, they won't clear when you + * write it back + */ +static inline uint32_t dwc_otg_read_hprt0(dwc_otg_core_if_t * _core_if) +{ + hprt0_data_t hprt0; + hprt0.d32 = DWC_READ_REG32(_core_if->host_if->hprt0); + hprt0.b.prtena = 0; + hprt0.b.prtconndet = 0; + hprt0.b.prtenchng = 0; + hprt0.b.prtovrcurrchng = 0; + return hprt0.d32; +} + +/**@}*/ + +/** @name Common CIL Functions + * The following functions support managing the DWC_otg controller in either + * device or host mode. + */ +/**@{*/ + +extern void dwc_otg_read_packet(dwc_otg_core_if_t * core_if, + uint8_t * dest, uint16_t bytes); + +extern void dwc_otg_flush_tx_fifo(dwc_otg_core_if_t * _core_if, const int _num); +extern void dwc_otg_flush_rx_fifo(dwc_otg_core_if_t * _core_if); +extern void dwc_otg_core_reset(dwc_otg_core_if_t * _core_if); + +/** + * This function returns the Core Interrupt register. + */ +static inline uint32_t dwc_otg_read_core_intr(dwc_otg_core_if_t * core_if) +{ + return (DWC_READ_REG32(&core_if->core_global_regs->gintsts) & + DWC_READ_REG32(&core_if->core_global_regs->gintmsk)); +} + +/** + * This function returns the OTG Interrupt register. + */ +static inline uint32_t dwc_otg_read_otg_intr(dwc_otg_core_if_t * core_if) +{ + return (DWC_READ_REG32(&core_if->core_global_regs->gotgint)); +} + +/** + * This function reads the Device All Endpoints Interrupt register and + * returns the IN endpoint interrupt bits. + */ +static inline uint32_t dwc_otg_read_dev_all_in_ep_intr(dwc_otg_core_if_t * + core_if) +{ + + uint32_t v; + + if (core_if->multiproc_int_enable) { + v = DWC_READ_REG32(&core_if->dev_if-> + dev_global_regs->deachint) & + DWC_READ_REG32(&core_if-> + dev_if->dev_global_regs->deachintmsk); + } else { + v = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->daint) & + DWC_READ_REG32(&core_if->dev_if->dev_global_regs->daintmsk); + } + return (v & 0xffff); +} + +/** + * This function reads the Device All Endpoints Interrupt register and + * returns the OUT endpoint interrupt bits. + */ +static inline uint32_t dwc_otg_read_dev_all_out_ep_intr(dwc_otg_core_if_t * + core_if) +{ + uint32_t v; + + if (core_if->multiproc_int_enable) { + v = DWC_READ_REG32(&core_if->dev_if-> + dev_global_regs->deachint) & + DWC_READ_REG32(&core_if-> + dev_if->dev_global_regs->deachintmsk); + } else { + v = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->daint) & + DWC_READ_REG32(&core_if->dev_if->dev_global_regs->daintmsk); + } + + return ((v & 0xffff0000) >> 16); +} + +/** + * This function returns the Device IN EP Interrupt register + */ +static inline uint32_t dwc_otg_read_dev_in_ep_intr(dwc_otg_core_if_t * core_if, + dwc_ep_t * ep) +{ + dwc_otg_dev_if_t *dev_if = core_if->dev_if; + uint32_t v, msk, emp; + + if (core_if->multiproc_int_enable) { + msk = + DWC_READ_REG32(&dev_if-> + dev_global_regs->diepeachintmsk[ep->num]); + emp = + DWC_READ_REG32(&dev_if-> + dev_global_regs->dtknqr4_fifoemptymsk); + msk |= ((emp >> ep->num) & 0x1) << 7; + v = DWC_READ_REG32(&dev_if->in_ep_regs[ep->num]->diepint) & msk; + } else { + msk = DWC_READ_REG32(&dev_if->dev_global_regs->diepmsk); + emp = + DWC_READ_REG32(&dev_if-> + dev_global_regs->dtknqr4_fifoemptymsk); + msk |= ((emp >> ep->num) & 0x1) << 7; + v = DWC_READ_REG32(&dev_if->in_ep_regs[ep->num]->diepint) & msk; + } + + return v; +} + +/** + * This function returns the Device OUT EP Interrupt register + */ +static inline uint32_t dwc_otg_read_dev_out_ep_intr(dwc_otg_core_if_t * + _core_if, dwc_ep_t * _ep) +{ + dwc_otg_dev_if_t *dev_if = _core_if->dev_if; + uint32_t v; + doepmsk_data_t msk = {.d32 = 0 }; + + if (_core_if->multiproc_int_enable) { + msk.d32 = + DWC_READ_REG32(&dev_if-> + dev_global_regs->doepeachintmsk[_ep->num]); + if (_core_if->pti_enh_enable) { + msk.b.pktdrpsts = 1; + } + v = DWC_READ_REG32(&dev_if-> + out_ep_regs[_ep->num]->doepint) & msk.d32; + } else { + msk.d32 = DWC_READ_REG32(&dev_if->dev_global_regs->doepmsk); + if (_core_if->pti_enh_enable) { + msk.b.pktdrpsts = 1; + } + v = DWC_READ_REG32(&dev_if-> + out_ep_regs[_ep->num]->doepint) & msk.d32; + } + return v; +} + +/** + * This function returns the Host All Channel Interrupt register + */ +static inline uint32_t dwc_otg_read_host_all_channels_intr(dwc_otg_core_if_t * + _core_if) +{ + return (DWC_READ_REG32(&_core_if->host_if->host_global_regs->haint)); +} + +static inline uint32_t dwc_otg_read_host_channel_intr(dwc_otg_core_if_t * + _core_if, dwc_hc_t * _hc) +{ + return (DWC_READ_REG32 + (&_core_if->host_if->hc_regs[_hc->hc_num]->hcint)); +} + +/** + * This function returns the mode of the operation, host or device. + * + * @return 0 - Device Mode, 1 - Host Mode + */ +static inline uint32_t dwc_otg_mode(dwc_otg_core_if_t * _core_if) +{ + return (DWC_READ_REG32(&_core_if->core_global_regs->gintsts) & 0x1); +} + +/**@}*/ + +/** + * DWC_otg CIL callback structure. This structure allows the HCD and + * PCD to register functions used for starting and stopping the PCD + * and HCD for role change on for a DRD. + */ +typedef struct dwc_otg_cil_callbacks { + /** Start function for role change */ + int (*start) (void *_p); + /** Stop Function for role change */ + int (*stop) (void *_p); + /** Disconnect Function for role change */ + int (*disconnect) (void *_p); + /** Resume/Remote wakeup Function */ + int (*resume_wakeup) (void *_p); + /** Suspend function */ + int (*suspend) (void *_p); + /** Session Start (SRP) */ + int (*session_start) (void *_p); +#ifdef CONFIG_USB_DWC_OTG_LPM + /** Sleep (switch to L0 state) */ + int (*sleep) (void *_p); +#endif + /** Pointer passed to start() and stop() */ + void *p; +} dwc_otg_cil_callbacks_t; + +extern void dwc_otg_cil_register_pcd_callbacks(dwc_otg_core_if_t * _core_if, + dwc_otg_cil_callbacks_t * _cb, + void *_p); +extern void dwc_otg_cil_register_hcd_callbacks(dwc_otg_core_if_t * _core_if, + dwc_otg_cil_callbacks_t * _cb, + void *_p); + +void dwc_otg_initiate_srp(dwc_otg_core_if_t * core_if); + +////////////////////////////////////////////////////////////////////// +/** Start the HCD. Helper function for using the HCD callbacks. + * + * @param core_if Programming view of DWC_otg controller. + */ +static inline void cil_hcd_start(dwc_otg_core_if_t * core_if) +{ + if (core_if->hcd_cb && core_if->hcd_cb->start) { + core_if->hcd_cb->start(core_if->hcd_cb->p); + } +} + +/** Stop the HCD. Helper function for using the HCD callbacks. + * + * @param core_if Programming view of DWC_otg controller. + */ +static inline void cil_hcd_stop(dwc_otg_core_if_t * core_if) +{ + if (core_if->hcd_cb && core_if->hcd_cb->stop) { + core_if->hcd_cb->stop(core_if->hcd_cb->p); + } +} + +/** Disconnect the HCD. Helper function for using the HCD callbacks. + * + * @param core_if Programming view of DWC_otg controller. + */ +static inline void cil_hcd_disconnect(dwc_otg_core_if_t * core_if) +{ + if (core_if->hcd_cb && core_if->hcd_cb->disconnect) { + core_if->hcd_cb->disconnect(core_if->hcd_cb->p); + } +} + +/** Inform the HCD the a New Session has begun. Helper function for + * using the HCD callbacks. + * + * @param core_if Programming view of DWC_otg controller. + */ +static inline void cil_hcd_session_start(dwc_otg_core_if_t * core_if) +{ + if (core_if->hcd_cb && core_if->hcd_cb->session_start) { + core_if->hcd_cb->session_start(core_if->hcd_cb->p); + } +} + +#ifdef CONFIG_USB_DWC_OTG_LPM +/** + * Inform the HCD about LPM sleep. + * Helper function for using the HCD callbacks. + * + * @param core_if Programming view of DWC_otg controller. + */ +static inline void cil_hcd_sleep(dwc_otg_core_if_t * core_if) +{ + if (core_if->hcd_cb && core_if->hcd_cb->sleep) { + core_if->hcd_cb->sleep(core_if->hcd_cb->p); + } +} +#endif + +/** Resume the HCD. Helper function for using the HCD callbacks. + * + * @param core_if Programming view of DWC_otg controller. + */ +static inline void cil_hcd_resume(dwc_otg_core_if_t * core_if) +{ + if (core_if->hcd_cb && core_if->hcd_cb->resume_wakeup) { + core_if->hcd_cb->resume_wakeup(core_if->hcd_cb->p); + } +} + +/** Start the PCD. Helper function for using the PCD callbacks. + * + * @param core_if Programming view of DWC_otg controller. + */ +static inline void cil_pcd_start(dwc_otg_core_if_t * core_if) +{ + if (core_if->pcd_cb && core_if->pcd_cb->start) { + core_if->pcd_cb->start(core_if->pcd_cb->p); + } +} + +/** Stop the PCD. Helper function for using the PCD callbacks. + * + * @param core_if Programming view of DWC_otg controller. + */ +static inline void cil_pcd_stop(dwc_otg_core_if_t * core_if) +{ + if (core_if->pcd_cb && core_if->pcd_cb->stop) { + core_if->pcd_cb->stop(core_if->pcd_cb->p); + } +} + +/** Suspend the PCD. Helper function for using the PCD callbacks. + * + * @param core_if Programming view of DWC_otg controller. + */ +static inline void cil_pcd_suspend(dwc_otg_core_if_t * core_if) +{ + if (core_if->pcd_cb && core_if->pcd_cb->suspend) { + core_if->pcd_cb->suspend(core_if->pcd_cb->p); + } +} + +/** Resume the PCD. Helper function for using the PCD callbacks. + * + * @param core_if Programming view of DWC_otg controller. + */ +static inline void cil_pcd_resume(dwc_otg_core_if_t * core_if) +{ + if (core_if->pcd_cb && core_if->pcd_cb->resume_wakeup) { + core_if->pcd_cb->resume_wakeup(core_if->pcd_cb->p); + } +} + +////////////////////////////////////////////////////////////////////// + +#endif diff --git a/drivers/usb/host/dwc_otg/dwc_otg_cil_intr.c b/drivers/usb/host/dwc_otg/dwc_otg_cil_intr.c new file mode 100644 index 00000000000000..57a9fe4e5ee0c5 --- /dev/null +++ b/drivers/usb/host/dwc_otg/dwc_otg_cil_intr.c @@ -0,0 +1,1578 @@ +/* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_cil_intr.c $ + * $Revision: #32 $ + * $Date: 2012/08/10 $ + * $Change: 2047372 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ + +/** @file + * + * The Core Interface Layer provides basic services for accessing and + * managing the DWC_otg hardware. These services are used by both the + * Host Controller Driver and the Peripheral Controller Driver. + * + * This file contains the Common Interrupt handlers. + */ +#include "dwc_os.h" +#include "dwc_otg_regs.h" +#include "dwc_otg_cil.h" +#include "dwc_otg_driver.h" +#include "dwc_otg_pcd.h" +#include "dwc_otg_hcd.h" + +#ifdef DEBUG +inline const char *op_state_str(dwc_otg_core_if_t * core_if) +{ + return (core_if->op_state == A_HOST ? "a_host" : + (core_if->op_state == A_SUSPEND ? "a_suspend" : + (core_if->op_state == A_PERIPHERAL ? "a_peripheral" : + (core_if->op_state == B_PERIPHERAL ? "b_peripheral" : + (core_if->op_state == B_HOST ? "b_host" : "unknown"))))); +} +#endif + +/** This function will log a debug message + * + * @param core_if Programming view of DWC_otg controller. + */ +static int32_t dwc_otg_handle_mode_mismatch_intr(dwc_otg_core_if_t * core_if) +{ + gintsts_data_t gintsts; + DWC_WARN("Mode Mismatch Interrupt: currently in %s mode\n", + dwc_otg_mode(core_if) ? "Host" : "Device"); + + /* Clear interrupt */ + gintsts.d32 = 0; + gintsts.b.modemismatch = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, gintsts.d32); + return 1; +} + +/** + * This function handles the OTG Interrupts. It reads the OTG + * Interrupt Register (GOTGINT) to determine what interrupt has + * occurred. + * + * @param core_if Programming view of DWC_otg controller. + */ +static int32_t dwc_otg_handle_otg_intr(dwc_otg_core_if_t * core_if) +{ + dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs; + gotgint_data_t gotgint; + gotgctl_data_t gotgctl; + gintmsk_data_t gintmsk; + gpwrdn_data_t gpwrdn; + + gotgint.d32 = DWC_READ_REG32(&global_regs->gotgint); + gotgctl.d32 = DWC_READ_REG32(&global_regs->gotgctl); + DWC_DEBUGPL(DBG_CIL, "++OTG Interrupt gotgint=%0x [%s]\n", gotgint.d32, + op_state_str(core_if)); + + if (gotgint.b.sesenddet) { + DWC_DEBUGPL(DBG_ANY, " ++OTG Interrupt: " + "Session End Detected++ (%s)\n", + op_state_str(core_if)); + gotgctl.d32 = DWC_READ_REG32(&global_regs->gotgctl); + + if (core_if->op_state == B_HOST) { + cil_pcd_start(core_if); + core_if->op_state = B_PERIPHERAL; + } else { + /* If not B_HOST and Device HNP still set. HNP + * Did not succeed!*/ + if (gotgctl.b.devhnpen) { + DWC_DEBUGPL(DBG_ANY, "Session End Detected\n"); + __DWC_ERROR("Device Not Connected/Responding!\n"); + } + + /* If Session End Detected the B-Cable has + * been disconnected. */ + /* Reset PCD and Gadget driver to a + * clean state. */ + core_if->lx_state = DWC_OTG_L0; + DWC_SPINUNLOCK(core_if->lock); + cil_pcd_stop(core_if); + DWC_SPINLOCK(core_if->lock); + + if (core_if->adp_enable) { + if (core_if->power_down == 2) { + gpwrdn.d32 = 0; + gpwrdn.b.pwrdnswtch = 1; + DWC_MODIFY_REG32(&core_if-> + core_global_regs-> + gpwrdn, gpwrdn.d32, 0); + } + + gpwrdn.d32 = 0; + gpwrdn.b.pmuintsel = 1; + gpwrdn.b.pmuactv = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs-> + gpwrdn, 0, gpwrdn.d32); + + dwc_otg_adp_sense_start(core_if); + } + } + + gotgctl.d32 = 0; + gotgctl.b.devhnpen = 1; + DWC_MODIFY_REG32(&global_regs->gotgctl, gotgctl.d32, 0); + } + if (gotgint.b.sesreqsucstschng) { + DWC_DEBUGPL(DBG_ANY, " ++OTG Interrupt: " + "Session Reqeust Success Status Change++\n"); + gotgctl.d32 = DWC_READ_REG32(&global_regs->gotgctl); + if (gotgctl.b.sesreqscs) { + + if ((core_if->core_params->phy_type == + DWC_PHY_TYPE_PARAM_FS) && (core_if->core_params->i2c_enable)) { + core_if->srp_success = 1; + } else { + DWC_SPINUNLOCK(core_if->lock); + cil_pcd_resume(core_if); + DWC_SPINLOCK(core_if->lock); + /* Clear Session Request */ + gotgctl.d32 = 0; + gotgctl.b.sesreq = 1; + DWC_MODIFY_REG32(&global_regs->gotgctl, + gotgctl.d32, 0); + } + } + } + if (gotgint.b.hstnegsucstschng) { + /* Print statements during the HNP interrupt handling + * can cause it to fail.*/ + gotgctl.d32 = DWC_READ_REG32(&global_regs->gotgctl); + /* WA for 3.00a- HW is not setting cur_mode, even sometimes + * this does not help*/ + if (core_if->snpsid >= OTG_CORE_REV_3_00a) + dwc_udelay(100); + if (gotgctl.b.hstnegscs) { + if (dwc_otg_is_host_mode(core_if)) { + core_if->op_state = B_HOST; + /* + * Need to disable SOF interrupt immediately. + * When switching from device to host, the PCD + * interrupt handler won't handle the + * interrupt if host mode is already set. The + * HCD interrupt handler won't get called if + * the HCD state is HALT. This means that the + * interrupt does not get handled and Linux + * complains loudly. + */ + gintmsk.d32 = 0; + gintmsk.b.sofintr = 1; + DWC_MODIFY_REG32(&global_regs->gintmsk, + gintmsk.d32, 0); + /* Call callback function with spin lock released */ + DWC_SPINUNLOCK(core_if->lock); + cil_pcd_stop(core_if); + /* + * Initialize the Core for Host mode. + */ + cil_hcd_start(core_if); + DWC_SPINLOCK(core_if->lock); + core_if->op_state = B_HOST; + } + } else { + gotgctl.d32 = 0; + gotgctl.b.hnpreq = 1; + gotgctl.b.devhnpen = 1; + DWC_MODIFY_REG32(&global_regs->gotgctl, gotgctl.d32, 0); + DWC_DEBUGPL(DBG_ANY, "HNP Failed\n"); + __DWC_ERROR("Device Not Connected/Responding\n"); + } + } + if (gotgint.b.hstnegdet) { + /* The disconnect interrupt is set at the same time as + * Host Negotiation Detected. During the mode + * switch all interrupts are cleared so the disconnect + * interrupt handler will not get executed. + */ + DWC_DEBUGPL(DBG_ANY, " ++OTG Interrupt: " + "Host Negotiation Detected++ (%s)\n", + (dwc_otg_is_host_mode(core_if) ? "Host" : + "Device")); + if (dwc_otg_is_device_mode(core_if)) { + DWC_DEBUGPL(DBG_ANY, "a_suspend->a_peripheral (%d)\n", + core_if->op_state); + DWC_SPINUNLOCK(core_if->lock); + cil_hcd_disconnect(core_if); + cil_pcd_start(core_if); + DWC_SPINLOCK(core_if->lock); + core_if->op_state = A_PERIPHERAL; + } else { + /* + * Need to disable SOF interrupt immediately. When + * switching from device to host, the PCD interrupt + * handler won't handle the interrupt if host mode is + * already set. The HCD interrupt handler won't get + * called if the HCD state is HALT. This means that + * the interrupt does not get handled and Linux + * complains loudly. + */ + gintmsk.d32 = 0; + gintmsk.b.sofintr = 1; + DWC_MODIFY_REG32(&global_regs->gintmsk, gintmsk.d32, 0); + DWC_SPINUNLOCK(core_if->lock); + cil_pcd_stop(core_if); + cil_hcd_start(core_if); + DWC_SPINLOCK(core_if->lock); + core_if->op_state = A_HOST; + } + } + if (gotgint.b.adevtoutchng) { + DWC_DEBUGPL(DBG_ANY, " ++OTG Interrupt: " + "A-Device Timeout Change++\n"); + } + if (gotgint.b.debdone) { + DWC_DEBUGPL(DBG_ANY, " ++OTG Interrupt: " "Debounce Done++\n"); + } + + /* Clear GOTGINT */ + DWC_WRITE_REG32(&core_if->core_global_regs->gotgint, gotgint.d32); + + return 1; +} + +void w_conn_id_status_change(void *p) +{ + dwc_otg_core_if_t *core_if = p; + uint32_t count = 0; + gotgctl_data_t gotgctl = {.d32 = 0 }; + + gotgctl.d32 = DWC_READ_REG32(&core_if->core_global_regs->gotgctl); + DWC_DEBUGPL(DBG_CIL, "gotgctl=%0x\n", gotgctl.d32); + DWC_DEBUGPL(DBG_CIL, "gotgctl.b.conidsts=%d\n", gotgctl.b.conidsts); + + /* B-Device connector (Device Mode) */ + if (gotgctl.b.conidsts) { + /* Wait for switch to device mode. */ + while (!dwc_otg_is_device_mode(core_if)) { + DWC_PRINTF("Waiting for Peripheral Mode, Mode=%s\n", + (dwc_otg_is_host_mode(core_if) ? "Host" : + "Peripheral")); + dwc_mdelay(100); + if (++count > 10000) + break; + } + DWC_ASSERT(++count < 10000, + "Connection id status change timed out"); + core_if->op_state = B_PERIPHERAL; + dwc_otg_core_init(core_if); + dwc_otg_enable_global_interrupts(core_if); + cil_pcd_start(core_if); + } else { + /* A-Device connector (Host Mode) */ + while (!dwc_otg_is_host_mode(core_if)) { + DWC_PRINTF("Waiting for Host Mode, Mode=%s\n", + (dwc_otg_is_host_mode(core_if) ? "Host" : + "Peripheral")); + dwc_mdelay(100); + if (++count > 10000) + break; + } + DWC_ASSERT(++count < 10000, + "Connection id status change timed out"); + core_if->op_state = A_HOST; + /* + * Initialize the Core for Host mode. + */ + dwc_otg_core_init(core_if); + dwc_otg_enable_global_interrupts(core_if); + cil_hcd_start(core_if); + } +} + +/** + * This function handles the Connector ID Status Change Interrupt. It + * reads the OTG Interrupt Register (GOTCTL) to determine whether this + * is a Device to Host Mode transition or a Host Mode to Device + * Transition. + * + * This only occurs when the cable is connected/removed from the PHY + * connector. + * + * @param core_if Programming view of DWC_otg controller. + */ +static int32_t dwc_otg_handle_conn_id_status_change_intr(dwc_otg_core_if_t * core_if) +{ + + /* + * Need to disable SOF interrupt immediately. If switching from device + * to host, the PCD interrupt handler won't handle the interrupt if + * host mode is already set. The HCD interrupt handler won't get + * called if the HCD state is HALT. This means that the interrupt does + * not get handled and Linux complains loudly. + */ + gintmsk_data_t gintmsk = {.d32 = 0 }; + gintsts_data_t gintsts = {.d32 = 0 }; + + gintmsk.b.sofintr = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gintmsk, gintmsk.d32, 0); + + DWC_DEBUGPL(DBG_CIL, + " ++Connector ID Status Change Interrupt++ (%s)\n", + (dwc_otg_is_host_mode(core_if) ? "Host" : "Device")); + + DWC_SPINUNLOCK(core_if->lock); + + /* + * Need to schedule a work, as there are possible DELAY function calls + * Release lock before scheduling workq as it holds spinlock during scheduling + */ + + DWC_WORKQ_SCHEDULE(core_if->wq_otg, w_conn_id_status_change, + core_if, "connection id status change"); + DWC_SPINLOCK(core_if->lock); + + /* Set flag and clear interrupt */ + gintsts.b.conidstschng = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, gintsts.d32); + + return 1; +} + +/** + * This interrupt indicates that a device is initiating the Session + * Request Protocol to request the host to turn on bus power so a new + * session can begin. The handler responds by turning on bus power. If + * the DWC_otg controller is in low power mode, the handler brings the + * controller out of low power mode before turning on bus power. + * + * @param core_if Programming view of DWC_otg controller. + */ +static int32_t dwc_otg_handle_session_req_intr(dwc_otg_core_if_t * core_if) +{ + gintsts_data_t gintsts; + +#ifndef DWC_HOST_ONLY + DWC_DEBUGPL(DBG_ANY, "++Session Request Interrupt++\n"); + + if (dwc_otg_is_device_mode(core_if)) { + DWC_PRINTF("SRP: Device mode\n"); + } else { + hprt0_data_t hprt0; + DWC_PRINTF("SRP: Host mode\n"); + + /* Turn on the port power bit. */ + hprt0.d32 = dwc_otg_read_hprt0(core_if); + hprt0.b.prtpwr = 1; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + + /* Start the Connection timer. So a message can be displayed + * if connect does not occur within 10 seconds. */ + cil_hcd_session_start(core_if); + } +#endif + + /* Clear interrupt */ + gintsts.d32 = 0; + gintsts.b.sessreqintr = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, gintsts.d32); + + return 1; +} + +void w_wakeup_detected(void *p) +{ + dwc_otg_core_if_t *core_if = (dwc_otg_core_if_t *) p; + /* + * Clear the Resume after 70ms. (Need 20 ms minimum. Use 70 ms + * so that OPT tests pass with all PHYs). + */ + hprt0_data_t hprt0 = {.d32 = 0 }; +#if 0 + pcgcctl_data_t pcgcctl = {.d32 = 0 }; + /* Restart the Phy Clock */ + pcgcctl.b.stoppclk = 1; + DWC_MODIFY_REG32(core_if->pcgcctl, pcgcctl.d32, 0); + dwc_udelay(10); +#endif //0 + hprt0.d32 = dwc_otg_read_hprt0(core_if); + DWC_DEBUGPL(DBG_ANY, "Resume: HPRT0=%0x\n", hprt0.d32); +// dwc_mdelay(70); + hprt0.b.prtres = 0; /* Resume */ + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + DWC_DEBUGPL(DBG_ANY, "Clear Resume: HPRT0=%0x\n", + DWC_READ_REG32(core_if->host_if->hprt0)); + + cil_hcd_resume(core_if); + + /** Change to L0 state*/ + core_if->lx_state = DWC_OTG_L0; +} + +/** + * This interrupt indicates that the DWC_otg controller has detected a + * resume or remote wakeup sequence. If the DWC_otg controller is in + * low power mode, the handler must brings the controller out of low + * power mode. The controller automatically begins resume + * signaling. The handler schedules a time to stop resume signaling. + */ +static int32_t dwc_otg_handle_wakeup_detected_intr(dwc_otg_core_if_t * core_if) +{ + gintsts_data_t gintsts; + + DWC_DEBUGPL(DBG_ANY, + "++Resume and Remote Wakeup Detected Interrupt++\n"); + + DWC_PRINTF("%s lxstate = %d\n", __func__, core_if->lx_state); + + if (dwc_otg_is_device_mode(core_if)) { + dctl_data_t dctl = {.d32 = 0 }; + DWC_DEBUGPL(DBG_PCD, "DSTS=0x%0x\n", + DWC_READ_REG32(&core_if->dev_if->dev_global_regs-> + dsts)); + if (core_if->lx_state == DWC_OTG_L2) { +#ifdef PARTIAL_POWER_DOWN + if (core_if->hwcfg4.b.power_optimiz) { + pcgcctl_data_t power = {.d32 = 0 }; + + power.d32 = DWC_READ_REG32(core_if->pcgcctl); + DWC_DEBUGPL(DBG_CIL, "PCGCCTL=%0x\n", + power.d32); + + power.b.stoppclk = 0; + DWC_WRITE_REG32(core_if->pcgcctl, power.d32); + + power.b.pwrclmp = 0; + DWC_WRITE_REG32(core_if->pcgcctl, power.d32); + + power.b.rstpdwnmodule = 0; + DWC_WRITE_REG32(core_if->pcgcctl, power.d32); + } +#endif + /* Clear the Remote Wakeup Signaling */ + dctl.b.rmtwkupsig = 1; + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs-> + dctl, dctl.d32, 0); + + DWC_SPINUNLOCK(core_if->lock); + if (core_if->pcd_cb && core_if->pcd_cb->resume_wakeup) { + core_if->pcd_cb->resume_wakeup(core_if->pcd_cb->p); + } + DWC_SPINLOCK(core_if->lock); + } else { + glpmcfg_data_t lpmcfg; + lpmcfg.d32 = + DWC_READ_REG32(&core_if->core_global_regs->glpmcfg); + lpmcfg.b.hird_thres &= (~(1 << 4)); + DWC_WRITE_REG32(&core_if->core_global_regs->glpmcfg, + lpmcfg.d32); + } + /** Change to L0 state*/ + core_if->lx_state = DWC_OTG_L0; + } else { + if (core_if->lx_state != DWC_OTG_L1) { + pcgcctl_data_t pcgcctl = {.d32 = 0 }; + + /* Restart the Phy Clock */ + pcgcctl.b.stoppclk = 1; + DWC_MODIFY_REG32(core_if->pcgcctl, pcgcctl.d32, 0); + DWC_TIMER_SCHEDULE(core_if->wkp_timer, 71); + } else { + /** Change to L0 state*/ + core_if->lx_state = DWC_OTG_L0; + } + } + + /* Clear interrupt */ + gintsts.d32 = 0; + gintsts.b.wkupintr = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, gintsts.d32); + + return 1; +} + +/** + * This interrupt indicates that the Wakeup Logic has detected a + * Device disconnect. + */ +static int32_t dwc_otg_handle_pwrdn_disconnect_intr(dwc_otg_core_if_t *core_if) +{ + gpwrdn_data_t gpwrdn = { .d32 = 0 }; + gpwrdn_data_t gpwrdn_temp = { .d32 = 0 }; + gpwrdn_temp.d32 = DWC_READ_REG32(&core_if->core_global_regs->gpwrdn); + + DWC_PRINTF("%s called\n", __FUNCTION__); + + if (!core_if->hibernation_suspend) { + DWC_PRINTF("Already exited from Hibernation\n"); + return 1; + } + + /* Switch on the voltage to the core */ + gpwrdn.b.pwrdnswtch = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + dwc_udelay(10); + + /* Reset the core */ + gpwrdn.d32 = 0; + gpwrdn.b.pwrdnrstn = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + dwc_udelay(10); + + /* Disable power clamps*/ + gpwrdn.d32 = 0; + gpwrdn.b.pwrdnclmp = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + + /* Remove reset the core signal */ + gpwrdn.d32 = 0; + gpwrdn.b.pwrdnrstn = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, gpwrdn.d32); + dwc_udelay(10); + + /* Disable PMU interrupt */ + gpwrdn.d32 = 0; + gpwrdn.b.pmuintsel = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + + core_if->hibernation_suspend = 0; + + /* Disable PMU */ + gpwrdn.d32 = 0; + gpwrdn.b.pmuactv = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + dwc_udelay(10); + + if (gpwrdn_temp.b.idsts) { + core_if->op_state = B_PERIPHERAL; + dwc_otg_core_init(core_if); + dwc_otg_enable_global_interrupts(core_if); + cil_pcd_start(core_if); + } else { + core_if->op_state = A_HOST; + dwc_otg_core_init(core_if); + dwc_otg_enable_global_interrupts(core_if); + cil_hcd_start(core_if); + } + + return 1; +} + +/** + * This interrupt indicates that the Wakeup Logic has detected a + * remote wakeup sequence. + */ +static int32_t dwc_otg_handle_pwrdn_wakeup_detected_intr(dwc_otg_core_if_t * core_if) +{ + gpwrdn_data_t gpwrdn = {.d32 = 0 }; + DWC_DEBUGPL(DBG_ANY, + "++Powerdown Remote Wakeup Detected Interrupt++\n"); + + if (!core_if->hibernation_suspend) { + DWC_PRINTF("Already exited from Hibernation\n"); + return 1; + } + + gpwrdn.d32 = DWC_READ_REG32(&core_if->core_global_regs->gpwrdn); + if (gpwrdn.b.idsts) { // Device Mode + if ((core_if->power_down == 2) + && (core_if->hibernation_suspend == 1)) { + dwc_otg_device_hibernation_restore(core_if, 0, 0); + } + } else { + if ((core_if->power_down == 2) + && (core_if->hibernation_suspend == 1)) { + dwc_otg_host_hibernation_restore(core_if, 1, 0); + } + } + return 1; +} + +static int32_t dwc_otg_handle_pwrdn_idsts_change(dwc_otg_device_t *otg_dev) +{ + gpwrdn_data_t gpwrdn = {.d32 = 0 }; + gpwrdn_data_t gpwrdn_temp = {.d32 = 0 }; + dwc_otg_core_if_t *core_if = otg_dev->core_if; + + DWC_DEBUGPL(DBG_ANY, "%s called\n", __FUNCTION__); + gpwrdn_temp.d32 = DWC_READ_REG32(&core_if->core_global_regs->gpwrdn); + if (core_if->power_down == 2) { + if (!core_if->hibernation_suspend) { + DWC_PRINTF("Already exited from Hibernation\n"); + return 1; + } + DWC_DEBUGPL(DBG_ANY, "Exit from hibernation on ID sts change\n"); + /* Switch on the voltage to the core */ + gpwrdn.b.pwrdnswtch = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + dwc_udelay(10); + + /* Reset the core */ + gpwrdn.d32 = 0; + gpwrdn.b.pwrdnrstn = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + dwc_udelay(10); + + /* Disable power clamps */ + gpwrdn.d32 = 0; + gpwrdn.b.pwrdnclmp = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + + /* Remove reset the core signal */ + gpwrdn.d32 = 0; + gpwrdn.b.pwrdnrstn = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, gpwrdn.d32); + dwc_udelay(10); + + /* Disable PMU interrupt */ + gpwrdn.d32 = 0; + gpwrdn.b.pmuintsel = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + + /*Indicates that we are exiting from hibernation */ + core_if->hibernation_suspend = 0; + + /* Disable PMU */ + gpwrdn.d32 = 0; + gpwrdn.b.pmuactv = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + dwc_udelay(10); + + gpwrdn.d32 = core_if->gr_backup->gpwrdn_local; + if (gpwrdn.b.dis_vbus == 1) { + gpwrdn.d32 = 0; + gpwrdn.b.dis_vbus = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + } + + if (gpwrdn_temp.b.idsts) { + core_if->op_state = B_PERIPHERAL; + dwc_otg_core_init(core_if); + dwc_otg_enable_global_interrupts(core_if); + cil_pcd_start(core_if); + } else { + core_if->op_state = A_HOST; + dwc_otg_core_init(core_if); + dwc_otg_enable_global_interrupts(core_if); + cil_hcd_start(core_if); + } + } + + if (core_if->adp_enable) { + uint8_t is_host = 0; + DWC_SPINUNLOCK(core_if->lock); + /* Change the core_if's lock to hcd/pcd lock depend on mode? */ +#ifndef DWC_HOST_ONLY + if (gpwrdn_temp.b.idsts) + core_if->lock = otg_dev->pcd->lock; +#endif +#ifndef DWC_DEVICE_ONLY + if (!gpwrdn_temp.b.idsts) { + core_if->lock = otg_dev->hcd->lock; + is_host = 1; + } +#endif + DWC_PRINTF("RESTART ADP\n"); + if (core_if->adp.probe_enabled) + dwc_otg_adp_probe_stop(core_if); + if (core_if->adp.sense_enabled) + dwc_otg_adp_sense_stop(core_if); + if (core_if->adp.sense_timer_started) + DWC_TIMER_CANCEL(core_if->adp.sense_timer); + if (core_if->adp.vbuson_timer_started) + DWC_TIMER_CANCEL(core_if->adp.vbuson_timer); + core_if->adp.probe_timer_values[0] = -1; + core_if->adp.probe_timer_values[1] = -1; + core_if->adp.sense_timer_started = 0; + core_if->adp.vbuson_timer_started = 0; + core_if->adp.probe_counter = 0; + core_if->adp.gpwrdn = 0; + + /* Disable PMU and restart ADP */ + gpwrdn_temp.d32 = 0; + gpwrdn_temp.b.pmuactv = 1; + gpwrdn_temp.b.pmuintsel = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + DWC_PRINTF("Check point 1\n"); + dwc_mdelay(110); + dwc_otg_adp_start(core_if, is_host); + DWC_SPINLOCK(core_if->lock); + } + + + return 1; +} + +static int32_t dwc_otg_handle_pwrdn_session_change(dwc_otg_core_if_t * core_if) +{ + gpwrdn_data_t gpwrdn = {.d32 = 0 }; + int32_t otg_cap_param = core_if->core_params->otg_cap; + DWC_DEBUGPL(DBG_ANY, "%s called\n", __FUNCTION__); + + gpwrdn.d32 = DWC_READ_REG32(&core_if->core_global_regs->gpwrdn); + if (core_if->power_down == 2) { + if (!core_if->hibernation_suspend) { + DWC_PRINTF("Already exited from Hibernation\n"); + return 1; + } + + if ((otg_cap_param != DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE || + otg_cap_param != DWC_OTG_CAP_PARAM_SRP_ONLY_CAPABLE) && + gpwrdn.b.bsessvld == 0) { + /* Save gpwrdn register for further usage if stschng interrupt */ + core_if->gr_backup->gpwrdn_local = + DWC_READ_REG32(&core_if->core_global_regs->gpwrdn); + /*Exit from ISR and wait for stschng interrupt with bsessvld = 1 */ + return 1; + } + + /* Switch on the voltage to the core */ + gpwrdn.d32 = 0; + gpwrdn.b.pwrdnswtch = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + dwc_udelay(10); + + /* Reset the core */ + gpwrdn.d32 = 0; + gpwrdn.b.pwrdnrstn = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + dwc_udelay(10); + + /* Disable power clamps */ + gpwrdn.d32 = 0; + gpwrdn.b.pwrdnclmp = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + + /* Remove reset the core signal */ + gpwrdn.d32 = 0; + gpwrdn.b.pwrdnrstn = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, gpwrdn.d32); + dwc_udelay(10); + + /* Disable PMU interrupt */ + gpwrdn.d32 = 0; + gpwrdn.b.pmuintsel = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + dwc_udelay(10); + + /*Indicates that we are exiting from hibernation */ + core_if->hibernation_suspend = 0; + + /* Disable PMU */ + gpwrdn.d32 = 0; + gpwrdn.b.pmuactv = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + dwc_udelay(10); + + core_if->op_state = B_PERIPHERAL; + dwc_otg_core_init(core_if); + dwc_otg_enable_global_interrupts(core_if); + cil_pcd_start(core_if); + + if (otg_cap_param == DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE || + otg_cap_param == DWC_OTG_CAP_PARAM_SRP_ONLY_CAPABLE) { + /* + * Initiate SRP after initial ADP probe. + */ + dwc_otg_initiate_srp(core_if); + } + } + + return 1; +} +/** + * This interrupt indicates that the Wakeup Logic has detected a + * status change either on IDDIG or BSessVld. + */ +static uint32_t dwc_otg_handle_pwrdn_stschng_intr(dwc_otg_device_t *otg_dev) +{ + uint32_t retval = 0; + gpwrdn_data_t gpwrdn = {.d32 = 0 }; + gpwrdn_data_t gpwrdn_temp = {.d32 = 0 }; + dwc_otg_core_if_t *core_if = otg_dev->core_if; + + DWC_PRINTF("%s called\n", __FUNCTION__); + + if (core_if->power_down == 2) { + if (core_if->hibernation_suspend <= 0) { + DWC_PRINTF("Already exited from Hibernation\n"); + return 1; + } else + gpwrdn_temp.d32 = core_if->gr_backup->gpwrdn_local; + + } else { + gpwrdn_temp.d32 = core_if->adp.gpwrdn; + } + + gpwrdn.d32 = DWC_READ_REG32(&core_if->core_global_regs->gpwrdn); + + if (gpwrdn.b.idsts ^ gpwrdn_temp.b.idsts) { + retval = dwc_otg_handle_pwrdn_idsts_change(otg_dev); + } else if (gpwrdn.b.bsessvld ^ gpwrdn_temp.b.bsessvld) { + retval = dwc_otg_handle_pwrdn_session_change(core_if); + } + + return retval; +} + +/** + * This interrupt indicates that the Wakeup Logic has detected a + * SRP. + */ +static int32_t dwc_otg_handle_pwrdn_srp_intr(dwc_otg_core_if_t * core_if) +{ + gpwrdn_data_t gpwrdn = {.d32 = 0 }; + + DWC_PRINTF("%s called\n", __FUNCTION__); + + if (!core_if->hibernation_suspend) { + DWC_PRINTF("Already exited from Hibernation\n"); + return 1; + } +#ifdef DWC_DEV_SRPCAP + if (core_if->pwron_timer_started) { + core_if->pwron_timer_started = 0; + DWC_TIMER_CANCEL(core_if->pwron_timer); + } +#endif + + /* Switch on the voltage to the core */ + gpwrdn.b.pwrdnswtch = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + dwc_udelay(10); + + /* Reset the core */ + gpwrdn.d32 = 0; + gpwrdn.b.pwrdnrstn = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + dwc_udelay(10); + + /* Disable power clamps */ + gpwrdn.d32 = 0; + gpwrdn.b.pwrdnclmp = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + + /* Remove reset the core signal */ + gpwrdn.d32 = 0; + gpwrdn.b.pwrdnrstn = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, gpwrdn.d32); + dwc_udelay(10); + + /* Disable PMU interrupt */ + gpwrdn.d32 = 0; + gpwrdn.b.pmuintsel = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + + /* Indicates that we are exiting from hibernation */ + core_if->hibernation_suspend = 0; + + /* Disable PMU */ + gpwrdn.d32 = 0; + gpwrdn.b.pmuactv = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + dwc_udelay(10); + + /* Programm Disable VBUS to 0 */ + gpwrdn.d32 = 0; + gpwrdn.b.dis_vbus = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + + /*Initialize the core as Host */ + core_if->op_state = A_HOST; + dwc_otg_core_init(core_if); + dwc_otg_enable_global_interrupts(core_if); + cil_hcd_start(core_if); + + return 1; +} + +/** + * This interrupt indicates that a device has been disconnected from + * the root port. + */ +static int32_t dwc_otg_handle_disconnect_intr(dwc_otg_core_if_t * core_if) +{ + gintsts_data_t gintsts; + + DWC_DEBUGPL(DBG_ANY, "++Disconnect Detected Interrupt++ (%s) %s\n", + (dwc_otg_is_host_mode(core_if) ? "Host" : "Device"), + op_state_str(core_if)); + +/** @todo Consolidate this if statement. */ +#ifndef DWC_HOST_ONLY + if (core_if->op_state == B_HOST) { + /* If in device mode Disconnect and stop the HCD, then + * start the PCD. */ + DWC_SPINUNLOCK(core_if->lock); + cil_hcd_disconnect(core_if); + cil_pcd_start(core_if); + DWC_SPINLOCK(core_if->lock); + core_if->op_state = B_PERIPHERAL; + } else if (dwc_otg_is_device_mode(core_if)) { + gotgctl_data_t gotgctl = {.d32 = 0 }; + gotgctl.d32 = + DWC_READ_REG32(&core_if->core_global_regs->gotgctl); + if (gotgctl.b.hstsethnpen == 1) { + /* Do nothing, if HNP in process the OTG + * interrupt "Host Negotiation Detected" + * interrupt will do the mode switch. + */ + } else if (gotgctl.b.devhnpen == 0) { + /* If in device mode Disconnect and stop the HCD, then + * start the PCD. */ + DWC_SPINUNLOCK(core_if->lock); + cil_hcd_disconnect(core_if); + cil_pcd_start(core_if); + DWC_SPINLOCK(core_if->lock); + core_if->op_state = B_PERIPHERAL; + } else { + DWC_DEBUGPL(DBG_ANY, "!a_peripheral && !devhnpen\n"); + } + } else { + if (core_if->op_state == A_HOST) { + /* A-Cable still connected but device disconnected. */ + DWC_SPINUNLOCK(core_if->lock); + cil_hcd_disconnect(core_if); + DWC_SPINLOCK(core_if->lock); + if (core_if->adp_enable) { + gpwrdn_data_t gpwrdn = { .d32 = 0 }; + cil_hcd_stop(core_if); + /* Enable Power Down Logic */ + gpwrdn.b.pmuintsel = 1; + gpwrdn.b.pmuactv = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs-> + gpwrdn, 0, gpwrdn.d32); + dwc_otg_adp_probe_start(core_if); + + /* Power off the core */ + if (core_if->power_down == 2) { + gpwrdn.d32 = 0; + gpwrdn.b.pwrdnswtch = 1; + DWC_MODIFY_REG32 + (&core_if->core_global_regs->gpwrdn, + gpwrdn.d32, 0); + } + } + } + } +#endif + /* Change to L3(OFF) state */ + core_if->lx_state = DWC_OTG_L3; + + gintsts.d32 = 0; + gintsts.b.disconnect = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, gintsts.d32); + return 1; +} + +/** + * This interrupt indicates that SUSPEND state has been detected on + * the USB. + * + * For HNP the USB Suspend interrupt signals the change from + * "a_peripheral" to "a_host". + * + * When power management is enabled the core will be put in low power + * mode. + */ +static int32_t dwc_otg_handle_usb_suspend_intr(dwc_otg_core_if_t * core_if) +{ + dsts_data_t dsts; + gintsts_data_t gintsts; + dcfg_data_t dcfg; + + DWC_DEBUGPL(DBG_ANY, "USB SUSPEND\n"); + + if (dwc_otg_is_device_mode(core_if)) { + /* Check the Device status register to determine if the Suspend + * state is active. */ + dsts.d32 = + DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dsts); + DWC_DEBUGPL(DBG_PCD, "DSTS=0x%0x\n", dsts.d32); + DWC_DEBUGPL(DBG_PCD, "DSTS.Suspend Status=%d " + "HWCFG4.power Optimize=%d\n", + dsts.b.suspsts, core_if->hwcfg4.b.power_optimiz); + +#ifdef PARTIAL_POWER_DOWN +/** @todo Add a module parameter for power management. */ + + if (dsts.b.suspsts && core_if->hwcfg4.b.power_optimiz) { + pcgcctl_data_t power = {.d32 = 0 }; + DWC_DEBUGPL(DBG_CIL, "suspend\n"); + + power.b.pwrclmp = 1; + DWC_WRITE_REG32(core_if->pcgcctl, power.d32); + + power.b.rstpdwnmodule = 1; + DWC_MODIFY_REG32(core_if->pcgcctl, 0, power.d32); + + power.b.stoppclk = 1; + DWC_MODIFY_REG32(core_if->pcgcctl, 0, power.d32); + + } else { + DWC_DEBUGPL(DBG_ANY, "disconnect?\n"); + } +#endif + /* PCD callback for suspend. Release the lock inside of callback function */ + cil_pcd_suspend(core_if); + if (core_if->power_down == 2) + { + dcfg.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dcfg); + DWC_DEBUGPL(DBG_ANY,"lx_state = %08x\n",core_if->lx_state); + DWC_DEBUGPL(DBG_ANY," device address = %08d\n",dcfg.b.devaddr); + + if (core_if->lx_state != DWC_OTG_L3 && dcfg.b.devaddr) { + pcgcctl_data_t pcgcctl = {.d32 = 0 }; + gpwrdn_data_t gpwrdn = {.d32 = 0 }; + gusbcfg_data_t gusbcfg = {.d32 = 0 }; + + /* Change to L2(suspend) state */ + core_if->lx_state = DWC_OTG_L2; + + /* Clear interrupt in gintsts */ + gintsts.d32 = 0; + gintsts.b.usbsuspend = 1; + DWC_WRITE_REG32(&core_if->core_global_regs-> + gintsts, gintsts.d32); + DWC_PRINTF("Start of hibernation completed\n"); + dwc_otg_save_global_regs(core_if); + dwc_otg_save_dev_regs(core_if); + + gusbcfg.d32 = + DWC_READ_REG32(&core_if->core_global_regs-> + gusbcfg); + if (gusbcfg.b.ulpi_utmi_sel == 1) { + /* ULPI interface */ + /* Suspend the Phy Clock */ + pcgcctl.d32 = 0; + pcgcctl.b.stoppclk = 1; + DWC_MODIFY_REG32(core_if->pcgcctl, 0, + pcgcctl.d32); + dwc_udelay(10); + gpwrdn.b.pmuactv = 1; + DWC_MODIFY_REG32(&core_if-> + core_global_regs-> + gpwrdn, 0, gpwrdn.d32); + } else { + /* UTMI+ Interface */ + gpwrdn.b.pmuactv = 1; + DWC_MODIFY_REG32(&core_if-> + core_global_regs-> + gpwrdn, 0, gpwrdn.d32); + dwc_udelay(10); + pcgcctl.b.stoppclk = 1; + DWC_MODIFY_REG32(core_if->pcgcctl, 0, + pcgcctl.d32); + dwc_udelay(10); + } + + /* Set flag to indicate that we are in hibernation */ + core_if->hibernation_suspend = 1; + /* Enable interrupts from wake up logic */ + gpwrdn.d32 = 0; + gpwrdn.b.pmuintsel = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs-> + gpwrdn, 0, gpwrdn.d32); + dwc_udelay(10); + + /* Unmask device mode interrupts in GPWRDN */ + gpwrdn.d32 = 0; + gpwrdn.b.rst_det_msk = 1; + gpwrdn.b.lnstchng_msk = 1; + gpwrdn.b.sts_chngint_msk = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs-> + gpwrdn, 0, gpwrdn.d32); + dwc_udelay(10); + + /* Enable Power Down Clamp */ + gpwrdn.d32 = 0; + gpwrdn.b.pwrdnclmp = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs-> + gpwrdn, 0, gpwrdn.d32); + dwc_udelay(10); + + /* Switch off VDD */ + gpwrdn.d32 = 0; + gpwrdn.b.pwrdnswtch = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs-> + gpwrdn, 0, gpwrdn.d32); + + /* Save gpwrdn register for further usage if stschng interrupt */ + core_if->gr_backup->gpwrdn_local = + DWC_READ_REG32(&core_if->core_global_regs->gpwrdn); + DWC_PRINTF("Hibernation completed\n"); + + return 1; + } + } else if (core_if->power_down == 3) { + pcgcctl_data_t pcgcctl = {.d32 = 0 }; + dcfg.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dcfg); + DWC_DEBUGPL(DBG_ANY, "lx_state = %08x\n",core_if->lx_state); + DWC_DEBUGPL(DBG_ANY, " device address = %08d\n",dcfg.b.devaddr); + + if (core_if->lx_state != DWC_OTG_L3 && dcfg.b.devaddr) { + DWC_DEBUGPL(DBG_ANY, "Start entering to extended hibernation\n"); + core_if->xhib = 1; + + /* Clear interrupt in gintsts */ + gintsts.d32 = 0; + gintsts.b.usbsuspend = 1; + DWC_WRITE_REG32(&core_if->core_global_regs-> + gintsts, gintsts.d32); + + dwc_otg_save_global_regs(core_if); + dwc_otg_save_dev_regs(core_if); + + /* Wait for 10 PHY clocks */ + dwc_udelay(10); + + /* Program GPIO register while entering to xHib */ + DWC_WRITE_REG32(&core_if->core_global_regs->ggpio, 0x1); + + pcgcctl.b.enbl_extnd_hiber = 1; + DWC_MODIFY_REG32(core_if->pcgcctl, 0, pcgcctl.d32); + DWC_MODIFY_REG32(core_if->pcgcctl, 0, pcgcctl.d32); + + pcgcctl.d32 = 0; + pcgcctl.b.extnd_hiber_pwrclmp = 1; + DWC_MODIFY_REG32(core_if->pcgcctl, 0, pcgcctl.d32); + + pcgcctl.d32 = 0; + pcgcctl.b.extnd_hiber_switch = 1; + core_if->gr_backup->xhib_gpwrdn = DWC_READ_REG32(&core_if->core_global_regs->gpwrdn); + core_if->gr_backup->xhib_pcgcctl = DWC_READ_REG32(core_if->pcgcctl) | pcgcctl.d32; + DWC_MODIFY_REG32(core_if->pcgcctl, 0, pcgcctl.d32); + + DWC_DEBUGPL(DBG_ANY, "Finished entering to extended hibernation\n"); + + return 1; + } + } + } else { + if (core_if->op_state == A_PERIPHERAL) { + DWC_DEBUGPL(DBG_ANY, "a_peripheral->a_host\n"); + /* Clear the a_peripheral flag, back to a_host. */ + DWC_SPINUNLOCK(core_if->lock); + cil_pcd_stop(core_if); + cil_hcd_start(core_if); + DWC_SPINLOCK(core_if->lock); + core_if->op_state = A_HOST; + } + } + + /* Change to L2(suspend) state */ + core_if->lx_state = DWC_OTG_L2; + + /* Clear interrupt */ + gintsts.d32 = 0; + gintsts.b.usbsuspend = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, gintsts.d32); + + return 1; +} + +static int32_t dwc_otg_handle_xhib_exit_intr(dwc_otg_core_if_t * core_if) +{ + gpwrdn_data_t gpwrdn = {.d32 = 0 }; + pcgcctl_data_t pcgcctl = {.d32 = 0 }; + gahbcfg_data_t gahbcfg = {.d32 = 0 }; + + dwc_udelay(10); + + /* Program GPIO register while entering to xHib */ + DWC_WRITE_REG32(&core_if->core_global_regs->ggpio, 0x0); + + pcgcctl.d32 = core_if->gr_backup->xhib_pcgcctl; + pcgcctl.b.extnd_hiber_pwrclmp = 0; + DWC_WRITE_REG32(core_if->pcgcctl, pcgcctl.d32); + dwc_udelay(10); + + gpwrdn.d32 = core_if->gr_backup->xhib_gpwrdn; + gpwrdn.b.restore = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32); + dwc_udelay(10); + + restore_lpm_i2c_regs(core_if); + + pcgcctl.d32 = core_if->gr_backup->pcgcctl_local & (0x3FFFF << 14); + pcgcctl.b.max_xcvrselect = 1; + pcgcctl.b.ess_reg_restored = 0; + pcgcctl.b.extnd_hiber_switch = 0; + pcgcctl.b.extnd_hiber_pwrclmp = 0; + pcgcctl.b.enbl_extnd_hiber = 1; + DWC_WRITE_REG32(core_if->pcgcctl, pcgcctl.d32); + + gahbcfg.d32 = core_if->gr_backup->gahbcfg_local; + gahbcfg.b.glblintrmsk = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->gahbcfg, gahbcfg.d32); + + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, 0xFFFFFFFF); + DWC_WRITE_REG32(&core_if->core_global_regs->gintmsk, 0x1 << 16); + + DWC_WRITE_REG32(&core_if->core_global_regs->gusbcfg, + core_if->gr_backup->gusbcfg_local); + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->dcfg, + core_if->dr_backup->dcfg); + + pcgcctl.d32 = 0; + pcgcctl.d32 = core_if->gr_backup->pcgcctl_local & (0x3FFFF << 14); + pcgcctl.b.max_xcvrselect = 1; + pcgcctl.d32 |= 0x608; + DWC_WRITE_REG32(core_if->pcgcctl, pcgcctl.d32); + dwc_udelay(10); + + pcgcctl.d32 = 0; + pcgcctl.d32 = core_if->gr_backup->pcgcctl_local & (0x3FFFF << 14); + pcgcctl.b.max_xcvrselect = 1; + pcgcctl.b.ess_reg_restored = 1; + pcgcctl.b.enbl_extnd_hiber = 1; + pcgcctl.b.rstpdwnmodule = 1; + pcgcctl.b.restoremode = 1; + DWC_WRITE_REG32(core_if->pcgcctl, pcgcctl.d32); + + DWC_DEBUGPL(DBG_ANY, "%s called\n", __FUNCTION__); + + return 1; +} + +#ifdef CONFIG_USB_DWC_OTG_LPM +/** + * This function hadles LPM transaction received interrupt. + */ +static int32_t dwc_otg_handle_lpm_intr(dwc_otg_core_if_t * core_if) +{ + glpmcfg_data_t lpmcfg; + gintsts_data_t gintsts; + + if (!core_if->core_params->lpm_enable) { + DWC_PRINTF("Unexpected LPM interrupt\n"); + } + + lpmcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->glpmcfg); + DWC_PRINTF("LPM config register = 0x%08x\n", lpmcfg.d32); + + if (dwc_otg_is_host_mode(core_if)) { + cil_hcd_sleep(core_if); + } else { + lpmcfg.b.hird_thres |= (1 << 4); + DWC_WRITE_REG32(&core_if->core_global_regs->glpmcfg, + lpmcfg.d32); + } + + /* Examine prt_sleep_sts after TL1TokenTetry period max (10 us) */ + dwc_udelay(10); + lpmcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->glpmcfg); + if (lpmcfg.b.prt_sleep_sts) { + /* Save the current state */ + core_if->lx_state = DWC_OTG_L1; + } + + /* Clear interrupt */ + gintsts.d32 = 0; + gintsts.b.lpmtranrcvd = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, gintsts.d32); + return 1; +} +#endif /* CONFIG_USB_DWC_OTG_LPM */ + +/** + * This function returns the Core Interrupt register. + */ +static inline uint32_t dwc_otg_read_common_intr(dwc_otg_core_if_t * core_if, gintmsk_data_t *reenable_gintmsk, dwc_otg_hcd_t *hcd) +{ + gahbcfg_data_t gahbcfg = {.d32 = 0 }; + gintsts_data_t gintsts; + gintmsk_data_t gintmsk; + gintmsk_data_t gintmsk_common = {.d32 = 0 }; + gintmsk_common.b.wkupintr = 1; + gintmsk_common.b.sessreqintr = 1; + gintmsk_common.b.conidstschng = 1; + gintmsk_common.b.otgintr = 1; + gintmsk_common.b.modemismatch = 1; + gintmsk_common.b.disconnect = 1; + gintmsk_common.b.usbsuspend = 1; +#ifdef CONFIG_USB_DWC_OTG_LPM + gintmsk_common.b.lpmtranrcvd = 1; +#endif + gintmsk_common.b.restoredone = 1; + if(dwc_otg_is_device_mode(core_if)) + { + /** @todo: The port interrupt occurs while in device + * mode. Added code to CIL to clear the interrupt for now! + */ + gintmsk_common.b.portintr = 1; + } + if(fiq_enable) { + local_fiq_disable(); + fiq_fsm_spin_lock(&hcd->fiq_state->lock); + gintsts.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintsts); + gintmsk.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintmsk); + /* Pull in the interrupts that the FIQ has masked */ + gintmsk.d32 |= ~(hcd->fiq_state->gintmsk_saved.d32); + gintmsk.d32 |= gintmsk_common.d32; + /* for the upstairs function to reenable - have to read it here in case FIQ triggers again */ + reenable_gintmsk->d32 = gintmsk.d32; + fiq_fsm_spin_unlock(&hcd->fiq_state->lock); + local_fiq_enable(); + } else { + gintsts.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintsts); + gintmsk.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintmsk); + } + + gahbcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->gahbcfg); + +#ifdef DEBUG + /* if any common interrupts set */ + if (gintsts.d32 & gintmsk_common.d32) { + DWC_DEBUGPL(DBG_ANY, "common_intr: gintsts=%08x gintmsk=%08x\n", + gintsts.d32, gintmsk.d32); + } +#endif + if (!fiq_enable){ + if (gahbcfg.b.glblintrmsk) + return ((gintsts.d32 & gintmsk.d32) & gintmsk_common.d32); + else + return 0; + } else { + /* Our IRQ kicker is no longer the USB hardware, it's the MPHI interface. + * Can't trust the global interrupt mask bit in this case. + */ + return ((gintsts.d32 & gintmsk.d32) & gintmsk_common.d32); + } + +} + +/* MACRO for clearing interupt bits in GPWRDN register */ +#define CLEAR_GPWRDN_INTR(__core_if,__intr) \ +do { \ + gpwrdn_data_t gpwrdn = {.d32=0}; \ + gpwrdn.b.__intr = 1; \ + DWC_MODIFY_REG32(&__core_if->core_global_regs->gpwrdn, \ + 0, gpwrdn.d32); \ +} while (0) + +/** + * Common interrupt handler. + * + * The common interrupts are those that occur in both Host and Device mode. + * This handler handles the following interrupts: + * - Mode Mismatch Interrupt + * - Disconnect Interrupt + * - OTG Interrupt + * - Connector ID Status Change Interrupt + * - Session Request Interrupt. + * - Resume / Remote Wakeup Detected Interrupt. + * - LPM Transaction Received Interrupt + * - ADP Transaction Received Interrupt + * + */ +int32_t dwc_otg_handle_common_intr(void *dev) +{ + int retval = 0; + gintsts_data_t gintsts; + gintmsk_data_t gintmsk_reenable = { .d32 = 0 }; + gpwrdn_data_t gpwrdn = {.d32 = 0 }; + dwc_otg_device_t *otg_dev = dev; + dwc_otg_core_if_t *core_if = otg_dev->core_if; + gpwrdn.d32 = DWC_READ_REG32(&core_if->core_global_regs->gpwrdn); + if (dwc_otg_is_device_mode(core_if)) + core_if->frame_num = dwc_otg_get_frame_number(core_if); + + if (core_if->lock) + DWC_SPINLOCK(core_if->lock); + + if (core_if->power_down == 3 && core_if->xhib == 1) { + DWC_DEBUGPL(DBG_ANY, "Exiting from xHIB state\n"); + retval |= dwc_otg_handle_xhib_exit_intr(core_if); + core_if->xhib = 2; + if (core_if->lock) + DWC_SPINUNLOCK(core_if->lock); + + return retval; + } + + if (core_if->hibernation_suspend <= 0) { + /* read_common will have to poke the FIQ's saved mask. We must then clear this mask at the end + * of this handler - god only knows why it's done like this + */ + gintsts.d32 = dwc_otg_read_common_intr(core_if, &gintmsk_reenable, otg_dev->hcd); + + if (gintsts.b.modemismatch) { + retval |= dwc_otg_handle_mode_mismatch_intr(core_if); + } + if (gintsts.b.otgintr) { + retval |= dwc_otg_handle_otg_intr(core_if); + } + if (gintsts.b.conidstschng) { + retval |= + dwc_otg_handle_conn_id_status_change_intr(core_if); + } + if (gintsts.b.disconnect) { + retval |= dwc_otg_handle_disconnect_intr(core_if); + } + if (gintsts.b.sessreqintr) { + retval |= dwc_otg_handle_session_req_intr(core_if); + } + if (gintsts.b.wkupintr) { + retval |= dwc_otg_handle_wakeup_detected_intr(core_if); + } + if (gintsts.b.usbsuspend) { + retval |= dwc_otg_handle_usb_suspend_intr(core_if); + } +#ifdef CONFIG_USB_DWC_OTG_LPM + if (gintsts.b.lpmtranrcvd) { + retval |= dwc_otg_handle_lpm_intr(core_if); + } +#endif + if (gintsts.b.restoredone) { + gintsts.d32 = 0; + if (core_if->power_down == 2) + core_if->hibernation_suspend = -1; + else if (core_if->power_down == 3 && core_if->xhib == 2) { + gpwrdn_data_t gpwrdn = {.d32 = 0 }; + pcgcctl_data_t pcgcctl = {.d32 = 0 }; + dctl_data_t dctl = {.d32 = 0 }; + + DWC_WRITE_REG32(&core_if->core_global_regs-> + gintsts, 0xFFFFFFFF); + + DWC_DEBUGPL(DBG_ANY, + "RESTORE DONE generated\n"); + + gpwrdn.b.restore = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + dwc_udelay(10); + + pcgcctl.b.rstpdwnmodule = 1; + DWC_MODIFY_REG32(core_if->pcgcctl, pcgcctl.d32, 0); + + DWC_WRITE_REG32(&core_if->core_global_regs->gusbcfg, core_if->gr_backup->gusbcfg_local); + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->dcfg, core_if->dr_backup->dcfg); + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->dctl, core_if->dr_backup->dctl); + dwc_udelay(50); + + dctl.b.pwronprgdone = 1; + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->dctl, 0, dctl.d32); + dwc_udelay(10); + + dwc_otg_restore_global_regs(core_if); + dwc_otg_restore_dev_regs(core_if, 0); + + dctl.d32 = 0; + dctl.b.pwronprgdone = 1; + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->dctl, dctl.d32, 0); + dwc_udelay(10); + + pcgcctl.d32 = 0; + pcgcctl.b.enbl_extnd_hiber = 1; + DWC_MODIFY_REG32(core_if->pcgcctl, pcgcctl.d32, 0); + + /* The core will be in ON STATE */ + core_if->lx_state = DWC_OTG_L0; + core_if->xhib = 0; + + DWC_SPINUNLOCK(core_if->lock); + if (core_if->pcd_cb && core_if->pcd_cb->resume_wakeup) { + core_if->pcd_cb->resume_wakeup(core_if->pcd_cb->p); + } + DWC_SPINLOCK(core_if->lock); + + } + + gintsts.b.restoredone = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts,gintsts.d32); + DWC_PRINTF(" --Restore done interrupt received-- \n"); + retval |= 1; + } + if (gintsts.b.portintr && dwc_otg_is_device_mode(core_if)) { + /* The port interrupt occurs while in device mode with HPRT0 + * Port Enable/Disable. + */ + gintsts.d32 = 0; + gintsts.b.portintr = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts,gintsts.d32); + retval |= 1; + gintmsk_reenable.b.portintr = 1; + + } + /* Did we actually handle anything? if so, unmask the interrupt */ +// fiq_print(FIQDBG_INT, otg_dev->hcd->fiq_state, "CILOUT %1d", retval); +// fiq_print(FIQDBG_INT, otg_dev->hcd->fiq_state, "%08x", gintsts.d32); +// fiq_print(FIQDBG_INT, otg_dev->hcd->fiq_state, "%08x", gintmsk_reenable.d32); + if (retval && fiq_enable) { + DWC_WRITE_REG32(&core_if->core_global_regs->gintmsk, gintmsk_reenable.d32); + } + + } else { + DWC_DEBUGPL(DBG_ANY, "gpwrdn=%08x\n", gpwrdn.d32); + + if (gpwrdn.b.disconn_det && gpwrdn.b.disconn_det_msk) { + CLEAR_GPWRDN_INTR(core_if, disconn_det); + if (gpwrdn.b.linestate == 0) { + dwc_otg_handle_pwrdn_disconnect_intr(core_if); + } else { + DWC_PRINTF("Disconnect detected while linestate is not 0\n"); + } + + retval |= 1; + } + if (gpwrdn.b.lnstschng && gpwrdn.b.lnstchng_msk) { + CLEAR_GPWRDN_INTR(core_if, lnstschng); + /* remote wakeup from hibernation */ + if (gpwrdn.b.linestate == 2 || gpwrdn.b.linestate == 1) { + dwc_otg_handle_pwrdn_wakeup_detected_intr(core_if); + } else { + DWC_PRINTF("gpwrdn.linestate = %d\n", gpwrdn.b.linestate); + } + retval |= 1; + } + if (gpwrdn.b.rst_det && gpwrdn.b.rst_det_msk) { + CLEAR_GPWRDN_INTR(core_if, rst_det); + if (gpwrdn.b.linestate == 0) { + DWC_PRINTF("Reset detected\n"); + retval |= dwc_otg_device_hibernation_restore(core_if, 0, 1); + } + } + if (gpwrdn.b.srp_det && gpwrdn.b.srp_det_msk) { + CLEAR_GPWRDN_INTR(core_if, srp_det); + dwc_otg_handle_pwrdn_srp_intr(core_if); + retval |= 1; + } + } + /* Handle ADP interrupt here */ + if (gpwrdn.b.adp_int) { + DWC_PRINTF("ADP interrupt\n"); + CLEAR_GPWRDN_INTR(core_if, adp_int); + dwc_otg_adp_handle_intr(core_if); + retval |= 1; + } + if (gpwrdn.b.sts_chngint && gpwrdn.b.sts_chngint_msk) { + DWC_PRINTF("STS CHNG interrupt asserted\n"); + CLEAR_GPWRDN_INTR(core_if, sts_chngint); + dwc_otg_handle_pwrdn_stschng_intr(otg_dev); + + retval |= 1; + } + if (core_if->lock) + DWC_SPINUNLOCK(core_if->lock); + return retval; +} diff --git a/drivers/usb/host/dwc_otg/dwc_otg_core_if.h b/drivers/usb/host/dwc_otg/dwc_otg_core_if.h new file mode 100644 index 00000000000000..4138fd173337dd --- /dev/null +++ b/drivers/usb/host/dwc_otg/dwc_otg_core_if.h @@ -0,0 +1,705 @@ +/* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_core_if.h $ + * $Revision: #13 $ + * $Date: 2012/08/10 $ + * $Change: 2047372 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ +#if !defined(__DWC_CORE_IF_H__) +#define __DWC_CORE_IF_H__ + +#include "dwc_os.h" + +/** @file + * This file defines DWC_OTG Core API + */ + +struct dwc_otg_core_if; +typedef struct dwc_otg_core_if dwc_otg_core_if_t; + +/** Maximum number of Periodic FIFOs */ +#define MAX_PERIO_FIFOS 15 +/** Maximum number of Periodic FIFOs */ +#define MAX_TX_FIFOS 15 + +/** Maximum number of Endpoints/HostChannels */ +#define MAX_EPS_CHANNELS 16 + +extern dwc_otg_core_if_t *dwc_otg_cil_init(const uint32_t * _reg_base_addr); +extern void dwc_otg_core_init(dwc_otg_core_if_t * _core_if); +extern void dwc_otg_cil_remove(dwc_otg_core_if_t * _core_if); + +extern void dwc_otg_enable_global_interrupts(dwc_otg_core_if_t * _core_if); +extern void dwc_otg_disable_global_interrupts(dwc_otg_core_if_t * _core_if); + +extern uint8_t dwc_otg_is_device_mode(dwc_otg_core_if_t * _core_if); +extern uint8_t dwc_otg_is_host_mode(dwc_otg_core_if_t * _core_if); + +extern uint8_t dwc_otg_is_dma_enable(dwc_otg_core_if_t * core_if); + +/** This function should be called on every hardware interrupt. */ +extern int32_t dwc_otg_handle_common_intr(void *otg_dev); + +/** @name OTG Core Parameters */ +/** @{ */ + +/** + * Specifies the OTG capabilities. The driver will automatically + * detect the value for this parameter if none is specified. + * 0 - HNP and SRP capable (default) + * 1 - SRP Only capable + * 2 - No HNP/SRP capable + */ +extern int dwc_otg_set_param_otg_cap(dwc_otg_core_if_t * core_if, int32_t val); +extern int32_t dwc_otg_get_param_otg_cap(dwc_otg_core_if_t * core_if); +#define DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE 0 +#define DWC_OTG_CAP_PARAM_SRP_ONLY_CAPABLE 1 +#define DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE 2 +#define dwc_param_otg_cap_default DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE + +extern int dwc_otg_set_param_opt(dwc_otg_core_if_t * core_if, int32_t val); +extern int32_t dwc_otg_get_param_opt(dwc_otg_core_if_t * core_if); +#define dwc_param_opt_default 1 + +/** + * Specifies whether to use slave or DMA mode for accessing the data + * FIFOs. The driver will automatically detect the value for this + * parameter if none is specified. + * 0 - Slave + * 1 - DMA (default, if available) + */ +extern int dwc_otg_set_param_dma_enable(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_dma_enable(dwc_otg_core_if_t * core_if); +#define dwc_param_dma_enable_default 1 + +/** + * When DMA mode is enabled specifies whether to use + * address DMA or DMA Descritor mode for accessing the data + * FIFOs in device mode. The driver will automatically detect + * the value for this parameter if none is specified. + * 0 - address DMA + * 1 - DMA Descriptor(default, if available) + */ +extern int dwc_otg_set_param_dma_desc_enable(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_dma_desc_enable(dwc_otg_core_if_t * core_if); +//#define dwc_param_dma_desc_enable_default 1 +#define dwc_param_dma_desc_enable_default 0 // Broadcom BCM2708 + +/** The DMA Burst size (applicable only for External DMA + * Mode). 1, 4, 8 16, 32, 64, 128, 256 (default 32) + */ +extern int dwc_otg_set_param_dma_burst_size(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_dma_burst_size(dwc_otg_core_if_t * core_if); +#define dwc_param_dma_burst_size_default 32 + +/** + * Specifies the maximum speed of operation in host and device mode. + * The actual speed depends on the speed of the attached device and + * the value of phy_type. The actual speed depends on the speed of the + * attached device. + * 0 - High Speed (default) + * 1 - Full Speed + */ +extern int dwc_otg_set_param_speed(dwc_otg_core_if_t * core_if, int32_t val); +extern int32_t dwc_otg_get_param_speed(dwc_otg_core_if_t * core_if); +#define dwc_param_speed_default 0 +#define DWC_SPEED_PARAM_HIGH 0 +#define DWC_SPEED_PARAM_FULL 1 + +/** Specifies whether low power mode is supported when attached + * to a Full Speed or Low Speed device in host mode. + * 0 - Don't support low power mode (default) + * 1 - Support low power mode + */ +extern int dwc_otg_set_param_host_support_fs_ls_low_power(dwc_otg_core_if_t * + core_if, int32_t val); +extern int32_t dwc_otg_get_param_host_support_fs_ls_low_power(dwc_otg_core_if_t + * core_if); +#define dwc_param_host_support_fs_ls_low_power_default 0 + +/** Specifies the PHY clock rate in low power mode when connected to a + * Low Speed device in host mode. This parameter is applicable only if + * HOST_SUPPORT_FS_LS_LOW_POWER is enabled. If PHY_TYPE is set to FS + * then defaults to 6 MHZ otherwise 48 MHZ. + * + * 0 - 48 MHz + * 1 - 6 MHz + */ +extern int dwc_otg_set_param_host_ls_low_power_phy_clk(dwc_otg_core_if_t * + core_if, int32_t val); +extern int32_t dwc_otg_get_param_host_ls_low_power_phy_clk(dwc_otg_core_if_t * + core_if); +#define dwc_param_host_ls_low_power_phy_clk_default 0 +#define DWC_HOST_LS_LOW_POWER_PHY_CLK_PARAM_48MHZ 0 +#define DWC_HOST_LS_LOW_POWER_PHY_CLK_PARAM_6MHZ 1 + +/** + * 0 - Use cC FIFO size parameters + * 1 - Allow dynamic FIFO sizing (default) + */ +extern int dwc_otg_set_param_enable_dynamic_fifo(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_enable_dynamic_fifo(dwc_otg_core_if_t * + core_if); +#define dwc_param_enable_dynamic_fifo_default 1 + +/** Total number of 4-byte words in the data FIFO memory. This + * memory includes the Rx FIFO, non-periodic Tx FIFO, and periodic + * Tx FIFOs. + * 32 to 32768 (default 8192) + * Note: The total FIFO memory depth in the FPGA configuration is 8192. + */ +extern int dwc_otg_set_param_data_fifo_size(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_data_fifo_size(dwc_otg_core_if_t * core_if); +//#define dwc_param_data_fifo_size_default 8192 +#define dwc_param_data_fifo_size_default 0xFF0 // Broadcom BCM2708 + +/** Number of 4-byte words in the Rx FIFO in device mode when dynamic + * FIFO sizing is enabled. + * 16 to 32768 (default 1064) + */ +extern int dwc_otg_set_param_dev_rx_fifo_size(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_dev_rx_fifo_size(dwc_otg_core_if_t * core_if); +#define dwc_param_dev_rx_fifo_size_default 1064 + +/** Number of 4-byte words in the non-periodic Tx FIFO in device mode + * when dynamic FIFO sizing is enabled. + * 16 to 32768 (default 1024) + */ +extern int dwc_otg_set_param_dev_nperio_tx_fifo_size(dwc_otg_core_if_t * + core_if, int32_t val); +extern int32_t dwc_otg_get_param_dev_nperio_tx_fifo_size(dwc_otg_core_if_t * + core_if); +#define dwc_param_dev_nperio_tx_fifo_size_default 1024 + +/** Number of 4-byte words in each of the periodic Tx FIFOs in device + * mode when dynamic FIFO sizing is enabled. + * 4 to 768 (default 256) + */ +extern int dwc_otg_set_param_dev_perio_tx_fifo_size(dwc_otg_core_if_t * core_if, + int32_t val, int fifo_num); +extern int32_t dwc_otg_get_param_dev_perio_tx_fifo_size(dwc_otg_core_if_t * + core_if, int fifo_num); +#define dwc_param_dev_perio_tx_fifo_size_default 256 + +/** Number of 4-byte words in the Rx FIFO in host mode when dynamic + * FIFO sizing is enabled. + * 16 to 32768 (default 1024) + */ +extern int dwc_otg_set_param_host_rx_fifo_size(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_host_rx_fifo_size(dwc_otg_core_if_t * core_if); +//#define dwc_param_host_rx_fifo_size_default 1024 +#define dwc_param_host_rx_fifo_size_default 774 // Broadcom BCM2708 + +/** Number of 4-byte words in the non-periodic Tx FIFO in host mode + * when Dynamic FIFO sizing is enabled in the core. + * 16 to 32768 (default 1024) + */ +extern int dwc_otg_set_param_host_nperio_tx_fifo_size(dwc_otg_core_if_t * + core_if, int32_t val); +extern int32_t dwc_otg_get_param_host_nperio_tx_fifo_size(dwc_otg_core_if_t * + core_if); +//#define dwc_param_host_nperio_tx_fifo_size_default 1024 +#define dwc_param_host_nperio_tx_fifo_size_default 0x100 // Broadcom BCM2708 + +/** Number of 4-byte words in the host periodic Tx FIFO when dynamic + * FIFO sizing is enabled. + * 16 to 32768 (default 1024) + */ +extern int dwc_otg_set_param_host_perio_tx_fifo_size(dwc_otg_core_if_t * + core_if, int32_t val); +extern int32_t dwc_otg_get_param_host_perio_tx_fifo_size(dwc_otg_core_if_t * + core_if); +//#define dwc_param_host_perio_tx_fifo_size_default 1024 +#define dwc_param_host_perio_tx_fifo_size_default 0x200 // Broadcom BCM2708 + +/** The maximum transfer size supported in bytes. + * 2047 to 65,535 (default 65,535) + */ +extern int dwc_otg_set_param_max_transfer_size(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_max_transfer_size(dwc_otg_core_if_t * core_if); +#define dwc_param_max_transfer_size_default 65535 + +/** The maximum number of packets in a transfer. + * 15 to 511 (default 511) + */ +extern int dwc_otg_set_param_max_packet_count(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_max_packet_count(dwc_otg_core_if_t * core_if); +#define dwc_param_max_packet_count_default 511 + +/** The number of host channel registers to use. + * 1 to 16 (default 12) + * Note: The FPGA configuration supports a maximum of 12 host channels. + */ +extern int dwc_otg_set_param_host_channels(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_host_channels(dwc_otg_core_if_t * core_if); +//#define dwc_param_host_channels_default 12 +#define dwc_param_host_channels_default 8 // Broadcom BCM2708 + +/** The number of endpoints in addition to EP0 available for device + * mode operations. + * 1 to 15 (default 6 IN and OUT) + * Note: The FPGA configuration supports a maximum of 6 IN and OUT + * endpoints in addition to EP0. + */ +extern int dwc_otg_set_param_dev_endpoints(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_dev_endpoints(dwc_otg_core_if_t * core_if); +#define dwc_param_dev_endpoints_default 6 + +/** + * Specifies the type of PHY interface to use. By default, the driver + * will automatically detect the phy_type. + * + * 0 - Full Speed PHY + * 1 - UTMI+ (default) + * 2 - ULPI + */ +extern int dwc_otg_set_param_phy_type(dwc_otg_core_if_t * core_if, int32_t val); +extern int32_t dwc_otg_get_param_phy_type(dwc_otg_core_if_t * core_if); +#define DWC_PHY_TYPE_PARAM_FS 0 +#define DWC_PHY_TYPE_PARAM_UTMI 1 +#define DWC_PHY_TYPE_PARAM_ULPI 2 +#define dwc_param_phy_type_default DWC_PHY_TYPE_PARAM_UTMI + +/** + * Specifies the UTMI+ Data Width. This parameter is + * applicable for a PHY_TYPE of UTMI+ or ULPI. (For a ULPI + * PHY_TYPE, this parameter indicates the data width between + * the MAC and the ULPI Wrapper.) Also, this parameter is + * applicable only if the OTG_HSPHY_WIDTH cC parameter was set + * to "8 and 16 bits", meaning that the core has been + * configured to work at either data path width. + * + * 8 or 16 bits (default 16) + */ +extern int dwc_otg_set_param_phy_utmi_width(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_phy_utmi_width(dwc_otg_core_if_t * core_if); +//#define dwc_param_phy_utmi_width_default 16 +#define dwc_param_phy_utmi_width_default 8 // Broadcom BCM2708 + +/** + * Specifies whether the ULPI operates at double or single + * data rate. This parameter is only applicable if PHY_TYPE is + * ULPI. + * + * 0 - single data rate ULPI interface with 8 bit wide data + * bus (default) + * 1 - double data rate ULPI interface with 4 bit wide data + * bus + */ +extern int dwc_otg_set_param_phy_ulpi_ddr(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_phy_ulpi_ddr(dwc_otg_core_if_t * core_if); +#define dwc_param_phy_ulpi_ddr_default 0 + +/** + * Specifies whether to use the internal or external supply to + * drive the vbus with a ULPI phy. + */ +extern int dwc_otg_set_param_phy_ulpi_ext_vbus(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_phy_ulpi_ext_vbus(dwc_otg_core_if_t * core_if); +#define DWC_PHY_ULPI_INTERNAL_VBUS 0 +#define DWC_PHY_ULPI_EXTERNAL_VBUS 1 +#define dwc_param_phy_ulpi_ext_vbus_default DWC_PHY_ULPI_INTERNAL_VBUS + +/** + * Specifies whether to use the I2Cinterface for full speed PHY. This + * parameter is only applicable if PHY_TYPE is FS. + * 0 - No (default) + * 1 - Yes + */ +extern int dwc_otg_set_param_i2c_enable(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_i2c_enable(dwc_otg_core_if_t * core_if); +#define dwc_param_i2c_enable_default 0 + +extern int dwc_otg_set_param_ulpi_fs_ls(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_ulpi_fs_ls(dwc_otg_core_if_t * core_if); +#define dwc_param_ulpi_fs_ls_default 0 + +extern int dwc_otg_set_param_ts_dline(dwc_otg_core_if_t * core_if, int32_t val); +extern int32_t dwc_otg_get_param_ts_dline(dwc_otg_core_if_t * core_if); +#define dwc_param_ts_dline_default 0 + +/** + * Specifies whether dedicated transmit FIFOs are + * enabled for non periodic IN endpoints in device mode + * 0 - No + * 1 - Yes + */ +extern int dwc_otg_set_param_en_multiple_tx_fifo(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_en_multiple_tx_fifo(dwc_otg_core_if_t * + core_if); +#define dwc_param_en_multiple_tx_fifo_default 1 + +/** Number of 4-byte words in each of the Tx FIFOs in device + * mode when dynamic FIFO sizing is enabled. + * 4 to 768 (default 256) + */ +extern int dwc_otg_set_param_dev_tx_fifo_size(dwc_otg_core_if_t * core_if, + int fifo_num, int32_t val); +extern int32_t dwc_otg_get_param_dev_tx_fifo_size(dwc_otg_core_if_t * core_if, + int fifo_num); +#define dwc_param_dev_tx_fifo_size_default 768 + +/** Thresholding enable flag- + * bit 0 - enable non-ISO Tx thresholding + * bit 1 - enable ISO Tx thresholding + * bit 2 - enable Rx thresholding + */ +extern int dwc_otg_set_param_thr_ctl(dwc_otg_core_if_t * core_if, int32_t val); +extern int32_t dwc_otg_get_thr_ctl(dwc_otg_core_if_t * core_if, int fifo_num); +#define dwc_param_thr_ctl_default 0 + +/** Thresholding length for Tx + * FIFOs in 32 bit DWORDs + */ +extern int dwc_otg_set_param_tx_thr_length(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_tx_thr_length(dwc_otg_core_if_t * core_if); +#define dwc_param_tx_thr_length_default 64 + +/** Thresholding length for Rx + * FIFOs in 32 bit DWORDs + */ +extern int dwc_otg_set_param_rx_thr_length(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_rx_thr_length(dwc_otg_core_if_t * core_if); +#define dwc_param_rx_thr_length_default 64 + +/** + * Specifies whether LPM (Link Power Management) support is enabled + */ +extern int dwc_otg_set_param_lpm_enable(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_lpm_enable(dwc_otg_core_if_t * core_if); +#define dwc_param_lpm_enable_default 1 + +/** + * Specifies whether PTI enhancement is enabled + */ +extern int dwc_otg_set_param_pti_enable(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_pti_enable(dwc_otg_core_if_t * core_if); +#define dwc_param_pti_enable_default 0 + +/** + * Specifies whether MPI enhancement is enabled + */ +extern int dwc_otg_set_param_mpi_enable(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_mpi_enable(dwc_otg_core_if_t * core_if); +#define dwc_param_mpi_enable_default 0 + +/** + * Specifies whether ADP capability is enabled + */ +extern int dwc_otg_set_param_adp_enable(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_adp_enable(dwc_otg_core_if_t * core_if); +#define dwc_param_adp_enable_default 0 + +/** + * Specifies whether IC_USB capability is enabled + */ + +extern int dwc_otg_set_param_ic_usb_cap(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_ic_usb_cap(dwc_otg_core_if_t * core_if); +#define dwc_param_ic_usb_cap_default 0 + +extern int dwc_otg_set_param_ahb_thr_ratio(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_ahb_thr_ratio(dwc_otg_core_if_t * core_if); +#define dwc_param_ahb_thr_ratio_default 0 + +extern int dwc_otg_set_param_power_down(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_power_down(dwc_otg_core_if_t * core_if); +#define dwc_param_power_down_default 0 + +extern int dwc_otg_set_param_reload_ctl(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_reload_ctl(dwc_otg_core_if_t * core_if); +#define dwc_param_reload_ctl_default 0 + +extern int dwc_otg_set_param_dev_out_nak(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_dev_out_nak(dwc_otg_core_if_t * core_if); +#define dwc_param_dev_out_nak_default 0 + +extern int dwc_otg_set_param_cont_on_bna(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_cont_on_bna(dwc_otg_core_if_t * core_if); +#define dwc_param_cont_on_bna_default 0 + +extern int dwc_otg_set_param_ahb_single(dwc_otg_core_if_t * core_if, + int32_t val); +extern int32_t dwc_otg_get_param_ahb_single(dwc_otg_core_if_t * core_if); +#define dwc_param_ahb_single_default 0 + +extern int dwc_otg_set_param_otg_ver(dwc_otg_core_if_t * core_if, int32_t val); +extern int32_t dwc_otg_get_param_otg_ver(dwc_otg_core_if_t * core_if); +#define dwc_param_otg_ver_default 0 + +/** @} */ + +/** @name Access to registers and bit-fields */ + +/** + * Dump core registers and SPRAM + */ +extern void dwc_otg_dump_dev_registers(dwc_otg_core_if_t * _core_if); +extern void dwc_otg_dump_spram(dwc_otg_core_if_t * _core_if); +extern void dwc_otg_dump_host_registers(dwc_otg_core_if_t * _core_if); +extern void dwc_otg_dump_global_registers(dwc_otg_core_if_t * _core_if); + +/** + * Get host negotiation status. + */ +extern uint32_t dwc_otg_get_hnpstatus(dwc_otg_core_if_t * core_if); + +/** + * Get srp status + */ +extern uint32_t dwc_otg_get_srpstatus(dwc_otg_core_if_t * core_if); + +/** + * Set hnpreq bit in the GOTGCTL register. + */ +extern void dwc_otg_set_hnpreq(dwc_otg_core_if_t * core_if, uint32_t val); + +/** + * Get Content of SNPSID register. + */ +extern uint32_t dwc_otg_get_gsnpsid(dwc_otg_core_if_t * core_if); + +/** + * Get current mode. + * Returns 0 if in device mode, and 1 if in host mode. + */ +extern uint32_t dwc_otg_get_mode(dwc_otg_core_if_t * core_if); + +/** + * Get value of hnpcapable field in the GUSBCFG register + */ +extern uint32_t dwc_otg_get_hnpcapable(dwc_otg_core_if_t * core_if); +/** + * Set value of hnpcapable field in the GUSBCFG register + */ +extern void dwc_otg_set_hnpcapable(dwc_otg_core_if_t * core_if, uint32_t val); + +/** + * Get value of srpcapable field in the GUSBCFG register + */ +extern uint32_t dwc_otg_get_srpcapable(dwc_otg_core_if_t * core_if); +/** + * Set value of srpcapable field in the GUSBCFG register + */ +extern void dwc_otg_set_srpcapable(dwc_otg_core_if_t * core_if, uint32_t val); + +/** + * Get value of devspeed field in the DCFG register + */ +extern uint32_t dwc_otg_get_devspeed(dwc_otg_core_if_t * core_if); +/** + * Set value of devspeed field in the DCFG register + */ +extern void dwc_otg_set_devspeed(dwc_otg_core_if_t * core_if, uint32_t val); + +/** + * Get the value of busconnected field from the HPRT0 register + */ +extern uint32_t dwc_otg_get_busconnected(dwc_otg_core_if_t * core_if); + +/** + * Gets the device enumeration Speed. + */ +extern uint32_t dwc_otg_get_enumspeed(dwc_otg_core_if_t * core_if); + +/** + * Get value of prtpwr field from the HPRT0 register + */ +extern uint32_t dwc_otg_get_prtpower(dwc_otg_core_if_t * core_if); + +/** + * Get value of flag indicating core state - hibernated or not + */ +extern uint32_t dwc_otg_get_core_state(dwc_otg_core_if_t * core_if); + +/** + * Set value of prtpwr field from the HPRT0 register + */ +extern void dwc_otg_set_prtpower(dwc_otg_core_if_t * core_if, uint32_t val); + +/** + * Get value of prtsusp field from the HPRT0 regsiter + */ +extern uint32_t dwc_otg_get_prtsuspend(dwc_otg_core_if_t * core_if); +/** + * Set value of prtpwr field from the HPRT0 register + */ +extern void dwc_otg_set_prtsuspend(dwc_otg_core_if_t * core_if, uint32_t val); + +/** + * Get value of ModeChTimEn field from the HCFG regsiter + */ +extern uint32_t dwc_otg_get_mode_ch_tim(dwc_otg_core_if_t * core_if); +/** + * Set value of ModeChTimEn field from the HCFG regsiter + */ +extern void dwc_otg_set_mode_ch_tim(dwc_otg_core_if_t * core_if, uint32_t val); + +/** + * Get value of Fram Interval field from the HFIR regsiter + */ +extern uint32_t dwc_otg_get_fr_interval(dwc_otg_core_if_t * core_if); +/** + * Set value of Frame Interval field from the HFIR regsiter + */ +extern void dwc_otg_set_fr_interval(dwc_otg_core_if_t * core_if, uint32_t val); + +/** + * Set value of prtres field from the HPRT0 register + *FIXME Remove? + */ +extern void dwc_otg_set_prtresume(dwc_otg_core_if_t * core_if, uint32_t val); + +/** + * Get value of rmtwkupsig bit in DCTL register + */ +extern uint32_t dwc_otg_get_remotewakesig(dwc_otg_core_if_t * core_if); + +/** + * Get value of prt_sleep_sts field from the GLPMCFG register + */ +extern uint32_t dwc_otg_get_lpm_portsleepstatus(dwc_otg_core_if_t * core_if); + +/** + * Get value of rem_wkup_en field from the GLPMCFG register + */ +extern uint32_t dwc_otg_get_lpm_remotewakeenabled(dwc_otg_core_if_t * core_if); + +/** + * Get value of appl_resp field from the GLPMCFG register + */ +extern uint32_t dwc_otg_get_lpmresponse(dwc_otg_core_if_t * core_if); +/** + * Set value of appl_resp field from the GLPMCFG register + */ +extern void dwc_otg_set_lpmresponse(dwc_otg_core_if_t * core_if, uint32_t val); + +/** + * Get value of hsic_connect field from the GLPMCFG register + */ +extern uint32_t dwc_otg_get_hsic_connect(dwc_otg_core_if_t * core_if); +/** + * Set value of hsic_connect field from the GLPMCFG register + */ +extern void dwc_otg_set_hsic_connect(dwc_otg_core_if_t * core_if, uint32_t val); + +/** + * Get value of inv_sel_hsic field from the GLPMCFG register. + */ +extern uint32_t dwc_otg_get_inv_sel_hsic(dwc_otg_core_if_t * core_if); +/** + * Set value of inv_sel_hsic field from the GLPMFG register. + */ +extern void dwc_otg_set_inv_sel_hsic(dwc_otg_core_if_t * core_if, uint32_t val); + +/* + * Some functions for accessing registers + */ + +/** + * GOTGCTL register + */ +extern uint32_t dwc_otg_get_gotgctl(dwc_otg_core_if_t * core_if); +extern void dwc_otg_set_gotgctl(dwc_otg_core_if_t * core_if, uint32_t val); + +/** + * GUSBCFG register + */ +extern uint32_t dwc_otg_get_gusbcfg(dwc_otg_core_if_t * core_if); +extern void dwc_otg_set_gusbcfg(dwc_otg_core_if_t * core_if, uint32_t val); + +/** + * GRXFSIZ register + */ +extern uint32_t dwc_otg_get_grxfsiz(dwc_otg_core_if_t * core_if); +extern void dwc_otg_set_grxfsiz(dwc_otg_core_if_t * core_if, uint32_t val); + +/** + * GNPTXFSIZ register + */ +extern uint32_t dwc_otg_get_gnptxfsiz(dwc_otg_core_if_t * core_if); +extern void dwc_otg_set_gnptxfsiz(dwc_otg_core_if_t * core_if, uint32_t val); + +extern uint32_t dwc_otg_get_gpvndctl(dwc_otg_core_if_t * core_if); +extern void dwc_otg_set_gpvndctl(dwc_otg_core_if_t * core_if, uint32_t val); + +/** + * GGPIO register + */ +extern uint32_t dwc_otg_get_ggpio(dwc_otg_core_if_t * core_if); +extern void dwc_otg_set_ggpio(dwc_otg_core_if_t * core_if, uint32_t val); + +/** + * GUID register + */ +extern uint32_t dwc_otg_get_guid(dwc_otg_core_if_t * core_if); +extern void dwc_otg_set_guid(dwc_otg_core_if_t * core_if, uint32_t val); + +/** + * HPRT0 register + */ +extern uint32_t dwc_otg_get_hprt0(dwc_otg_core_if_t * core_if); +extern void dwc_otg_set_hprt0(dwc_otg_core_if_t * core_if, uint32_t val); + +/** + * GHPTXFSIZE + */ +extern uint32_t dwc_otg_get_hptxfsiz(dwc_otg_core_if_t * core_if); + +/** @} */ + +#endif /* __DWC_CORE_IF_H__ */ diff --git a/drivers/usb/host/dwc_otg/dwc_otg_dbg.h b/drivers/usb/host/dwc_otg/dwc_otg_dbg.h new file mode 100644 index 00000000000000..ccc24e010e449c --- /dev/null +++ b/drivers/usb/host/dwc_otg/dwc_otg_dbg.h @@ -0,0 +1,117 @@ +/* ========================================================================== + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ + +#ifndef __DWC_OTG_DBG_H__ +#define __DWC_OTG_DBG_H__ + +/** @file + * This file defines debug levels. + * Debugging support vanishes in non-debug builds. + */ + +/** + * The Debug Level bit-mask variable. + */ +extern uint32_t g_dbg_lvl; +/** + * Set the Debug Level variable. + */ +static inline uint32_t SET_DEBUG_LEVEL(const uint32_t new) +{ + uint32_t old = g_dbg_lvl; + g_dbg_lvl = new; + return old; +} + +#define DBG_USER (0x1) +/** When debug level has the DBG_CIL bit set, display CIL Debug messages. */ +#define DBG_CIL (0x2) +/** When debug level has the DBG_CILV bit set, display CIL Verbose debug + * messages */ +#define DBG_CILV (0x20) +/** When debug level has the DBG_PCD bit set, display PCD (Device) debug + * messages */ +#define DBG_PCD (0x4) +/** When debug level has the DBG_PCDV set, display PCD (Device) Verbose debug + * messages */ +#define DBG_PCDV (0x40) +/** When debug level has the DBG_HCD bit set, display Host debug messages */ +#define DBG_HCD (0x8) +/** When debug level has the DBG_HCDV bit set, display Verbose Host debug + * messages */ +#define DBG_HCDV (0x80) +/** When debug level has the DBG_HCD_URB bit set, display enqueued URBs in host + * mode. */ +#define DBG_HCD_URB (0x800) +/** When debug level has the DBG_HCDI bit set, display host interrupt + * messages. */ +#define DBG_HCDI (0x1000) + +/** When debug level has any bit set, display debug messages */ +#define DBG_ANY (0xFF) + +/** All debug messages off */ +#define DBG_OFF 0 + +/** Prefix string for DWC_DEBUG print macros. */ +#define USB_DWC "DWC_otg: " + +/** + * Print a debug message when the Global debug level variable contains + * the bit defined in <code>lvl</code>. + * + * @param[in] lvl - Debug level, use one of the DBG_ constants above. + * @param[in] x - like printf + * + * Example:<p> + * <code> + * DWC_DEBUGPL( DBG_ANY, "%s(%p)\n", __func__, _reg_base_addr); + * </code> + * <br> + * results in:<br> + * <code> + * usb-DWC_otg: dwc_otg_cil_init(ca867000) + * </code> + */ +#ifdef DEBUG + +# define DWC_DEBUGPL(lvl, x...) do{ if ((lvl)&g_dbg_lvl)__DWC_DEBUG(USB_DWC x ); }while(0) +# define DWC_DEBUGP(x...) DWC_DEBUGPL(DBG_ANY, x ) + +# define CHK_DEBUG_LEVEL(level) ((level) & g_dbg_lvl) + +#else + +# define DWC_DEBUGPL(lvl, x...) do{}while(0) +# define DWC_DEBUGP(x...) + +# define CHK_DEBUG_LEVEL(level) (0) + +#endif /*DEBUG*/ +#endif diff --git a/drivers/usb/host/dwc_otg/dwc_otg_driver.c b/drivers/usb/host/dwc_otg/dwc_otg_driver.c new file mode 100644 index 00000000000000..b3e72bffafa1c5 --- /dev/null +++ b/drivers/usb/host/dwc_otg/dwc_otg_driver.c @@ -0,0 +1,1725 @@ +/* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_driver.c $ + * $Revision: #92 $ + * $Date: 2012/08/10 $ + * $Change: 2047372 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ + +/** @file + * The dwc_otg_driver module provides the initialization and cleanup entry + * points for the DWC_otg driver. This module will be dynamically installed + * after Linux is booted using the insmod command. When the module is + * installed, the dwc_otg_driver_init function is called. When the module is + * removed (using rmmod), the dwc_otg_driver_cleanup function is called. + * + * This module also defines a data structure for the dwc_otg_driver, which is + * used in conjunction with the standard ARM lm_device structure. These + * structures allow the OTG driver to comply with the standard Linux driver + * model in which devices and drivers are registered with a bus driver. This + * has the benefit that Linux can expose attributes of the driver and device + * in its special sysfs file system. Users can then read or write files in + * this file system to perform diagnostics on the driver components or the + * device. + */ + +#include "dwc_otg_os_dep.h" +#include "dwc_os.h" +#include "dwc_otg_dbg.h" +#include "dwc_otg_driver.h" +#include "dwc_otg_attr.h" +#include "dwc_otg_core_if.h" +#include "dwc_otg_pcd_if.h" +#include "dwc_otg_hcd_if.h" +#include "dwc_otg_fiq_fsm.h" +#include "dwc_otg_adp.h" + +#define DWC_DRIVER_VERSION "3.00a 10-AUG-2012" +#define DWC_DRIVER_DESC "HS OTG USB Controller driver" + +bool microframe_schedule=true; + +static const char dwc_driver_name[] = "dwc_otg"; + +/*-------------------------------------------------------------------------*/ +/* Encapsulate the module parameter settings */ + +struct dwc_otg_driver_module_params { + int32_t opt; + int32_t otg_cap; + int32_t dma_enable; + int32_t dma_desc_enable; + int32_t dma_burst_size; + int32_t speed; + int32_t host_support_fs_ls_low_power; + int32_t host_ls_low_power_phy_clk; + int32_t enable_dynamic_fifo; + int32_t data_fifo_size; + int32_t dev_rx_fifo_size; + int32_t dev_nperio_tx_fifo_size; + uint32_t dev_perio_tx_fifo_size[MAX_PERIO_FIFOS]; + int32_t host_rx_fifo_size; + int32_t host_nperio_tx_fifo_size; + int32_t host_perio_tx_fifo_size; + int32_t max_transfer_size; + int32_t max_packet_count; + int32_t host_channels; + int32_t dev_endpoints; + int32_t phy_type; + int32_t phy_utmi_width; + int32_t phy_ulpi_ddr; + int32_t phy_ulpi_ext_vbus; + int32_t i2c_enable; + int32_t ulpi_fs_ls; + int32_t ts_dline; + int32_t en_multiple_tx_fifo; + uint32_t dev_tx_fifo_size[MAX_TX_FIFOS]; + uint32_t thr_ctl; + uint32_t tx_thr_length; + uint32_t rx_thr_length; + int32_t pti_enable; + int32_t mpi_enable; + int32_t lpm_enable; + int32_t ic_usb_cap; + int32_t ahb_thr_ratio; + int32_t power_down; + int32_t reload_ctl; + int32_t dev_out_nak; + int32_t cont_on_bna; + int32_t ahb_single; + int32_t otg_ver; + int32_t adp_enable; +}; + +static struct dwc_otg_driver_module_params dwc_otg_module_params = { + .opt = -1, + .otg_cap = -1, + .dma_enable = -1, + .dma_desc_enable = -1, + .dma_burst_size = -1, + .speed = -1, + .host_support_fs_ls_low_power = -1, + .host_ls_low_power_phy_clk = -1, + .enable_dynamic_fifo = -1, + .data_fifo_size = -1, + .dev_rx_fifo_size = -1, + .dev_nperio_tx_fifo_size = -1, + .dev_perio_tx_fifo_size = { + /* dev_perio_tx_fifo_size_1 */ + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1 + /* 15 */ + }, + .host_rx_fifo_size = -1, + .host_nperio_tx_fifo_size = -1, + .host_perio_tx_fifo_size = -1, + .max_transfer_size = -1, + .max_packet_count = -1, + .host_channels = -1, + .dev_endpoints = -1, + .phy_type = -1, + .phy_utmi_width = -1, + .phy_ulpi_ddr = -1, + .phy_ulpi_ext_vbus = -1, + .i2c_enable = -1, + .ulpi_fs_ls = -1, + .ts_dline = -1, + .en_multiple_tx_fifo = -1, + .dev_tx_fifo_size = { + /* dev_tx_fifo_size */ + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1 + /* 15 */ + }, + .thr_ctl = -1, + .tx_thr_length = -1, + .rx_thr_length = -1, + .pti_enable = -1, + .mpi_enable = -1, + .lpm_enable = 0, + .ic_usb_cap = -1, + .ahb_thr_ratio = -1, + .power_down = -1, + .reload_ctl = -1, + .dev_out_nak = -1, + .cont_on_bna = -1, + .ahb_single = -1, + .otg_ver = -1, + .adp_enable = -1, +}; + +//Global variable to switch the fiq fix on or off +bool fiq_enable = 1; +// Global variable to enable the split transaction fix +bool fiq_fsm_enable = true; +//Bulk split-transaction NAK holdoff in microframes +uint16_t nak_holdoff = 8; + +//Force host mode during CIL re-init +bool cil_force_host = true; + +unsigned short fiq_fsm_mask = 0x0F; + +unsigned short int_ep_interval_min = 0; +/** + * This function shows the Driver Version. + */ +static ssize_t version_show(struct device_driver *dev, char *buf) +{ + return snprintf(buf, sizeof(DWC_DRIVER_VERSION) + 2, "%s\n", + DWC_DRIVER_VERSION); +} + +static DRIVER_ATTR_RO(version); + +/** + * Global Debug Level Mask. + */ +uint32_t g_dbg_lvl = 0; /* OFF */ + +/** + * This function shows the driver Debug Level. + */ +static ssize_t debuglevel_show(struct device_driver *drv, char *buf) +{ + return sprintf(buf, "0x%0x\n", g_dbg_lvl); +} + +/** + * This function stores the driver Debug Level. + */ +static ssize_t debuglevel_store(struct device_driver *drv, const char *buf, + size_t count) +{ + g_dbg_lvl = simple_strtoul(buf, NULL, 16); + return count; +} + +static DRIVER_ATTR_RW(debuglevel); + +/** + * This function is called during module intialization + * to pass module parameters to the DWC_OTG CORE. + */ +static int set_parameters(dwc_otg_core_if_t * core_if) +{ + int retval = 0; + int i; + + if (dwc_otg_module_params.otg_cap != -1) { + retval += + dwc_otg_set_param_otg_cap(core_if, + dwc_otg_module_params.otg_cap); + } + if (dwc_otg_module_params.dma_enable != -1) { + retval += + dwc_otg_set_param_dma_enable(core_if, + dwc_otg_module_params. + dma_enable); + } + if (dwc_otg_module_params.dma_desc_enable != -1) { + retval += + dwc_otg_set_param_dma_desc_enable(core_if, + dwc_otg_module_params. + dma_desc_enable); + } + if (dwc_otg_module_params.opt != -1) { + retval += + dwc_otg_set_param_opt(core_if, dwc_otg_module_params.opt); + } + if (dwc_otg_module_params.dma_burst_size != -1) { + retval += + dwc_otg_set_param_dma_burst_size(core_if, + dwc_otg_module_params. + dma_burst_size); + } + if (dwc_otg_module_params.host_support_fs_ls_low_power != -1) { + retval += + dwc_otg_set_param_host_support_fs_ls_low_power(core_if, + dwc_otg_module_params. + host_support_fs_ls_low_power); + } + if (dwc_otg_module_params.enable_dynamic_fifo != -1) { + retval += + dwc_otg_set_param_enable_dynamic_fifo(core_if, + dwc_otg_module_params. + enable_dynamic_fifo); + } + if (dwc_otg_module_params.data_fifo_size != -1) { + retval += + dwc_otg_set_param_data_fifo_size(core_if, + dwc_otg_module_params. + data_fifo_size); + } + if (dwc_otg_module_params.dev_rx_fifo_size != -1) { + retval += + dwc_otg_set_param_dev_rx_fifo_size(core_if, + dwc_otg_module_params. + dev_rx_fifo_size); + } + if (dwc_otg_module_params.dev_nperio_tx_fifo_size != -1) { + retval += + dwc_otg_set_param_dev_nperio_tx_fifo_size(core_if, + dwc_otg_module_params. + dev_nperio_tx_fifo_size); + } + if (dwc_otg_module_params.host_rx_fifo_size != -1) { + retval += + dwc_otg_set_param_host_rx_fifo_size(core_if, + dwc_otg_module_params.host_rx_fifo_size); + } + if (dwc_otg_module_params.host_nperio_tx_fifo_size != -1) { + retval += + dwc_otg_set_param_host_nperio_tx_fifo_size(core_if, + dwc_otg_module_params. + host_nperio_tx_fifo_size); + } + if (dwc_otg_module_params.host_perio_tx_fifo_size != -1) { + retval += + dwc_otg_set_param_host_perio_tx_fifo_size(core_if, + dwc_otg_module_params. + host_perio_tx_fifo_size); + } + if (dwc_otg_module_params.max_transfer_size != -1) { + retval += + dwc_otg_set_param_max_transfer_size(core_if, + dwc_otg_module_params. + max_transfer_size); + } + if (dwc_otg_module_params.max_packet_count != -1) { + retval += + dwc_otg_set_param_max_packet_count(core_if, + dwc_otg_module_params. + max_packet_count); + } + if (dwc_otg_module_params.host_channels != -1) { + retval += + dwc_otg_set_param_host_channels(core_if, + dwc_otg_module_params. + host_channels); + } + if (dwc_otg_module_params.dev_endpoints != -1) { + retval += + dwc_otg_set_param_dev_endpoints(core_if, + dwc_otg_module_params. + dev_endpoints); + } + if (dwc_otg_module_params.phy_type != -1) { + retval += + dwc_otg_set_param_phy_type(core_if, + dwc_otg_module_params.phy_type); + } + if (dwc_otg_module_params.speed != -1) { + retval += + dwc_otg_set_param_speed(core_if, + dwc_otg_module_params.speed); + } + if (dwc_otg_module_params.host_ls_low_power_phy_clk != -1) { + retval += + dwc_otg_set_param_host_ls_low_power_phy_clk(core_if, + dwc_otg_module_params. + host_ls_low_power_phy_clk); + } + if (dwc_otg_module_params.phy_ulpi_ddr != -1) { + retval += + dwc_otg_set_param_phy_ulpi_ddr(core_if, + dwc_otg_module_params. + phy_ulpi_ddr); + } + if (dwc_otg_module_params.phy_ulpi_ext_vbus != -1) { + retval += + dwc_otg_set_param_phy_ulpi_ext_vbus(core_if, + dwc_otg_module_params. + phy_ulpi_ext_vbus); + } + if (dwc_otg_module_params.phy_utmi_width != -1) { + retval += + dwc_otg_set_param_phy_utmi_width(core_if, + dwc_otg_module_params. + phy_utmi_width); + } + if (dwc_otg_module_params.ulpi_fs_ls != -1) { + retval += + dwc_otg_set_param_ulpi_fs_ls(core_if, + dwc_otg_module_params.ulpi_fs_ls); + } + if (dwc_otg_module_params.ts_dline != -1) { + retval += + dwc_otg_set_param_ts_dline(core_if, + dwc_otg_module_params.ts_dline); + } + if (dwc_otg_module_params.i2c_enable != -1) { + retval += + dwc_otg_set_param_i2c_enable(core_if, + dwc_otg_module_params. + i2c_enable); + } + if (dwc_otg_module_params.en_multiple_tx_fifo != -1) { + retval += + dwc_otg_set_param_en_multiple_tx_fifo(core_if, + dwc_otg_module_params. + en_multiple_tx_fifo); + } + for (i = 0; i < 15; i++) { + if (dwc_otg_module_params.dev_perio_tx_fifo_size[i] != -1) { + retval += + dwc_otg_set_param_dev_perio_tx_fifo_size(core_if, + dwc_otg_module_params. + dev_perio_tx_fifo_size + [i], i); + } + } + + for (i = 0; i < 15; i++) { + if (dwc_otg_module_params.dev_tx_fifo_size[i] != -1) { + retval += dwc_otg_set_param_dev_tx_fifo_size(core_if, + dwc_otg_module_params. + dev_tx_fifo_size + [i], i); + } + } + if (dwc_otg_module_params.thr_ctl != -1) { + retval += + dwc_otg_set_param_thr_ctl(core_if, + dwc_otg_module_params.thr_ctl); + } + if (dwc_otg_module_params.mpi_enable != -1) { + retval += + dwc_otg_set_param_mpi_enable(core_if, + dwc_otg_module_params. + mpi_enable); + } + if (dwc_otg_module_params.pti_enable != -1) { + retval += + dwc_otg_set_param_pti_enable(core_if, + dwc_otg_module_params. + pti_enable); + } + if (dwc_otg_module_params.lpm_enable != -1) { + retval += + dwc_otg_set_param_lpm_enable(core_if, + dwc_otg_module_params. + lpm_enable); + } + if (dwc_otg_module_params.ic_usb_cap != -1) { + retval += + dwc_otg_set_param_ic_usb_cap(core_if, + dwc_otg_module_params. + ic_usb_cap); + } + if (dwc_otg_module_params.tx_thr_length != -1) { + retval += + dwc_otg_set_param_tx_thr_length(core_if, + dwc_otg_module_params.tx_thr_length); + } + if (dwc_otg_module_params.rx_thr_length != -1) { + retval += + dwc_otg_set_param_rx_thr_length(core_if, + dwc_otg_module_params. + rx_thr_length); + } + if (dwc_otg_module_params.ahb_thr_ratio != -1) { + retval += + dwc_otg_set_param_ahb_thr_ratio(core_if, + dwc_otg_module_params.ahb_thr_ratio); + } + if (dwc_otg_module_params.power_down != -1) { + retval += + dwc_otg_set_param_power_down(core_if, + dwc_otg_module_params.power_down); + } + if (dwc_otg_module_params.reload_ctl != -1) { + retval += + dwc_otg_set_param_reload_ctl(core_if, + dwc_otg_module_params.reload_ctl); + } + + if (dwc_otg_module_params.dev_out_nak != -1) { + retval += + dwc_otg_set_param_dev_out_nak(core_if, + dwc_otg_module_params.dev_out_nak); + } + + if (dwc_otg_module_params.cont_on_bna != -1) { + retval += + dwc_otg_set_param_cont_on_bna(core_if, + dwc_otg_module_params.cont_on_bna); + } + + if (dwc_otg_module_params.ahb_single != -1) { + retval += + dwc_otg_set_param_ahb_single(core_if, + dwc_otg_module_params.ahb_single); + } + + if (dwc_otg_module_params.otg_ver != -1) { + retval += + dwc_otg_set_param_otg_ver(core_if, + dwc_otg_module_params.otg_ver); + } + if (dwc_otg_module_params.adp_enable != -1) { + retval += + dwc_otg_set_param_adp_enable(core_if, + dwc_otg_module_params. + adp_enable); + } + return retval; +} + +/** + * This function is the top level interrupt handler for the Common + * (Device and host modes) interrupts. + */ +static irqreturn_t dwc_otg_common_irq(int irq, void *dev) +{ + int32_t retval = IRQ_NONE; + + retval = dwc_otg_handle_common_intr(dev); + if (retval != 0) { + S3C2410X_CLEAR_EINTPEND(); + } + return IRQ_RETVAL(retval); +} + +/** + * This function is called when a lm_device is unregistered with the + * dwc_otg_driver. This happens, for example, when the rmmod command is + * executed. The device may or may not be electrically present. If it is + * present, the driver stops device processing. Any resources used on behalf + * of this device are freed. + * + * @param _dev + */ +#ifdef LM_INTERFACE +#define REM_RETVAL(n) +static void dwc_otg_driver_remove( struct lm_device *_dev ) +{ dwc_otg_device_t *otg_dev = lm_get_drvdata(_dev); +#elif defined(PCI_INTERFACE) +#define REM_RETVAL(n) +static void dwc_otg_driver_remove( struct pci_dev *_dev ) +{ dwc_otg_device_t *otg_dev = pci_get_drvdata(_dev); +#elif defined(PLATFORM_INTERFACE) +#define REM_RETVAL(n) n +static void dwc_otg_driver_remove( struct platform_device *_dev ) +{ dwc_otg_device_t *otg_dev = platform_get_drvdata(_dev); +#endif + + DWC_DEBUGPL(DBG_ANY, "%s(%p) otg_dev %p\n", __func__, _dev, otg_dev); + + if (!otg_dev) { + /* Memory allocation for the dwc_otg_device failed. */ + DWC_DEBUGPL(DBG_ANY, "%s: otg_dev NULL!\n", __func__); + } +#ifndef DWC_DEVICE_ONLY + if (otg_dev->hcd) { + hcd_remove(_dev); + } else { + DWC_DEBUGPL(DBG_ANY, "%s: otg_dev->hcd NULL!\n", __func__); + } +#endif + +#ifndef DWC_HOST_ONLY + if (otg_dev->pcd) { + pcd_remove(_dev); + } else { + DWC_DEBUGPL(DBG_ANY, "%s: otg_dev->pcd NULL!\n", __func__); + } +#endif + /* + * Free the IRQ + */ + if (otg_dev->common_irq_installed) { + free_irq(otg_dev->os_dep.irq_num, otg_dev); + } else { + DWC_DEBUGPL(DBG_ANY, "%s: There is no installed irq!\n", __func__); + } + + if (otg_dev->core_if) { + dwc_otg_cil_remove(otg_dev->core_if); + } else { + DWC_DEBUGPL(DBG_ANY, "%s: otg_dev->core_if NULL!\n", __func__); + } + + /* + * Remove the device attributes + */ + dwc_otg_attr_remove(_dev); + + /* + * Return the memory. + */ + if (otg_dev->os_dep.base) { + iounmap(otg_dev->os_dep.base); + } + DWC_FREE(otg_dev); + + /* + * Clear the drvdata pointer. + */ +#ifdef LM_INTERFACE + lm_set_drvdata(_dev, 0); +#elif defined(PCI_INTERFACE) + release_mem_region(otg_dev->os_dep.rsrc_start, + otg_dev->os_dep.rsrc_len); + pci_set_drvdata(_dev, 0); +#elif defined(PLATFORM_INTERFACE) + platform_set_drvdata(_dev, 0); +#endif +} + +/** + * This function is called when an lm_device is bound to a + * dwc_otg_driver. It creates the driver components required to + * control the device (CIL, HCD, and PCD) and it initializes the + * device. The driver components are stored in a dwc_otg_device + * structure. A reference to the dwc_otg_device is saved in the + * lm_device. This allows the driver to access the dwc_otg_device + * structure on subsequent calls to driver methods for this device. + * + * @param _dev Bus device + */ +static int dwc_otg_driver_probe( +#ifdef LM_INTERFACE + struct lm_device *_dev +#elif defined(PCI_INTERFACE) + struct pci_dev *_dev, + const struct pci_device_id *id +#elif defined(PLATFORM_INTERFACE) + struct platform_device *_dev +#endif + ) +{ + int retval = 0; + dwc_otg_device_t *dwc_otg_device; + int devirq; + + dev_dbg(&_dev->dev, "dwc_otg_driver_probe(%p)\n", _dev); +#ifdef LM_INTERFACE + dev_dbg(&_dev->dev, "start=0x%08x\n", (unsigned)_dev->resource.start); +#elif defined(PCI_INTERFACE) + if (!id) { + DWC_ERROR("Invalid pci_device_id %p", id); + return -EINVAL; + } + + if (!_dev || (pci_enable_device(_dev) < 0)) { + DWC_ERROR("Invalid pci_device %p", _dev); + return -ENODEV; + } + dev_dbg(&_dev->dev, "start=0x%08x\n", (unsigned)pci_resource_start(_dev,0)); + /* other stuff needed as well? */ + +#elif defined(PLATFORM_INTERFACE) + dev_dbg(&_dev->dev, "start=0x%08x (len 0x%x)\n", + (unsigned)_dev->resource->start, + (unsigned)(_dev->resource->end - _dev->resource->start)); +#endif + + dwc_otg_device = DWC_ALLOC(sizeof(dwc_otg_device_t)); + + if (!dwc_otg_device) { + dev_err(&_dev->dev, "kmalloc of dwc_otg_device failed\n"); + return -ENOMEM; + } + + memset(dwc_otg_device, 0, sizeof(*dwc_otg_device)); + dwc_otg_device->os_dep.reg_offset = 0xFFFFFFFF; + dwc_otg_device->os_dep.platformdev = _dev; + + /* + * Map the DWC_otg Core memory into virtual address space. + */ +#ifdef LM_INTERFACE + dwc_otg_device->os_dep.base = ioremap(_dev->resource.start, SZ_256K); + + if (!dwc_otg_device->os_dep.base) { + dev_err(&_dev->dev, "ioremap() failed\n"); + DWC_FREE(dwc_otg_device); + return -ENOMEM; + } + dev_dbg(&_dev->dev, "base=0x%08x\n", + (unsigned)dwc_otg_device->os_dep.base); +#elif defined(PCI_INTERFACE) + _dev->current_state = PCI_D0; + _dev->dev.power.power_state = PMSG_ON; + + if (!_dev->irq) { + DWC_ERROR("Found HC with no IRQ. Check BIOS/PCI %s setup!", + pci_name(_dev)); + iounmap(dwc_otg_device->os_dep.base); + DWC_FREE(dwc_otg_device); + return -ENODEV; + } + + dwc_otg_device->os_dep.rsrc_start = pci_resource_start(_dev, 0); + dwc_otg_device->os_dep.rsrc_len = pci_resource_len(_dev, 0); + DWC_DEBUGPL(DBG_ANY, "PCI resource: start=%08x, len=%08x\n", + (unsigned)dwc_otg_device->os_dep.rsrc_start, + (unsigned)dwc_otg_device->os_dep.rsrc_len); + if (!request_mem_region + (dwc_otg_device->os_dep.rsrc_start, dwc_otg_device->os_dep.rsrc_len, + "dwc_otg")) { + dev_dbg(&_dev->dev, "error requesting memory\n"); + iounmap(dwc_otg_device->os_dep.base); + DWC_FREE(dwc_otg_device); + return -EFAULT; + } + + dwc_otg_device->os_dep.base = + ioremap(dwc_otg_device->os_dep.rsrc_start, + dwc_otg_device->os_dep.rsrc_len); + if (dwc_otg_device->os_dep.base == NULL) { + dev_dbg(&_dev->dev, "error mapping memory\n"); + release_mem_region(dwc_otg_device->os_dep.rsrc_start, + dwc_otg_device->os_dep.rsrc_len); + iounmap(dwc_otg_device->os_dep.base); + DWC_FREE(dwc_otg_device); + return -EFAULT; + } + dev_dbg(&_dev->dev, "base=0x%p (before adjust) \n", + dwc_otg_device->os_dep.base); + dwc_otg_device->os_dep.base = (char *)dwc_otg_device->os_dep.base; + dev_dbg(&_dev->dev, "base=0x%p (after adjust) \n", + dwc_otg_device->os_dep.base); + dev_dbg(&_dev->dev, "%s: mapped PA 0x%x to VA 0x%p\n", __func__, + (unsigned)dwc_otg_device->os_dep.rsrc_start, + dwc_otg_device->os_dep.base); + + pci_set_master(_dev); + pci_set_drvdata(_dev, dwc_otg_device); +#elif defined(PLATFORM_INTERFACE) + DWC_DEBUGPL(DBG_ANY,"Platform resource: start=%08x, len=%08x\n", + _dev->resource->start, + _dev->resource->end - _dev->resource->start + 1); +#if 1 + if (!request_mem_region(_dev->resource[0].start, + _dev->resource[0].end - _dev->resource[0].start + 1, + "dwc_otg")) { + dev_dbg(&_dev->dev, "error reserving mapped memory\n"); + retval = -EFAULT; + goto fail; + } + + dwc_otg_device->os_dep.base = ioremap(_dev->resource[0].start, + _dev->resource[0].end - + _dev->resource[0].start+1); + if (fiq_enable) + { + if (!request_mem_region(_dev->resource[1].start, + _dev->resource[1].end - _dev->resource[1].start + 1, + "dwc_otg")) { + dev_dbg(&_dev->dev, "error reserving mapped memory\n"); + retval = -EFAULT; + goto fail; + } + + dwc_otg_device->os_dep.mphi_base = ioremap(_dev->resource[1].start, + _dev->resource[1].end - + _dev->resource[1].start + 1); + dwc_otg_device->os_dep.use_swirq = (_dev->resource[1].end - _dev->resource[1].start) == 0x200; + } + +#else + { + struct map_desc desc = { + .virtual = IO_ADDRESS((unsigned)_dev->resource->start), + .pfn = __phys_to_pfn((unsigned)_dev->resource->start), + .length = SZ_128K, + .type = MT_DEVICE + }; + iotable_init(&desc, 1); + dwc_otg_device->os_dep.base = (void *)desc.virtual; + } +#endif + if (!dwc_otg_device->os_dep.base) { + dev_err(&_dev->dev, "ioremap() failed\n"); + retval = -ENOMEM; + goto fail; + } +#endif + + /* + * Initialize driver data to point to the global DWC_otg + * Device structure. + */ +#ifdef LM_INTERFACE + lm_set_drvdata(_dev, dwc_otg_device); +#elif defined(PLATFORM_INTERFACE) + platform_set_drvdata(_dev, dwc_otg_device); +#endif + dev_dbg(&_dev->dev, "dwc_otg_device=0x%p\n", dwc_otg_device); + + dwc_otg_device->core_if = dwc_otg_cil_init(dwc_otg_device->os_dep.base); + DWC_DEBUGPL(DBG_HCDV, "probe of device %p given core_if %p\n", + dwc_otg_device, dwc_otg_device->core_if);//GRAYG + + if (!dwc_otg_device->core_if) { + dev_err(&_dev->dev, "CIL initialization failed!\n"); + retval = -ENOMEM; + goto fail; + } + + dev_dbg(&_dev->dev, "Calling get_gsnpsid\n"); + /* + * Attempt to ensure this device is really a DWC_otg Controller. + * Read and verify the SNPSID register contents. The value should be + * 0x45F42XXX or 0x45F42XXX, which corresponds to either "OT2" or "OTG3", + * as in "OTG version 2.XX" or "OTG version 3.XX". + */ + + if (((dwc_otg_get_gsnpsid(dwc_otg_device->core_if) & 0xFFFFF000) != 0x4F542000) && + ((dwc_otg_get_gsnpsid(dwc_otg_device->core_if) & 0xFFFFF000) != 0x4F543000)) { + dev_err(&_dev->dev, "Bad value for SNPSID: 0x%08x\n", + dwc_otg_get_gsnpsid(dwc_otg_device->core_if)); + retval = -EINVAL; + goto fail; + } + + /* + * Validate parameter values. + */ + dev_dbg(&_dev->dev, "Calling set_parameters\n"); + if (set_parameters(dwc_otg_device->core_if)) { + retval = -EINVAL; + goto fail; + } + + /* + * Create Device Attributes in sysfs + */ + dev_dbg(&_dev->dev, "Calling attr_create\n"); + dwc_otg_attr_create(_dev); + + /* + * Disable the global interrupt until all the interrupt + * handlers are installed. + */ + dev_dbg(&_dev->dev, "Calling disable_global_interrupts\n"); + dwc_otg_disable_global_interrupts(dwc_otg_device->core_if); + + /* + * Install the interrupt handler for the common interrupts before + * enabling common interrupts in core_init below. + */ + +#if defined(PLATFORM_INTERFACE) + devirq = platform_get_irq_byname(_dev, fiq_enable ? "soft" : "usb"); + if (devirq < 0) + devirq = platform_get_irq(_dev, fiq_enable ? 0 : 1); +#else + devirq = _dev->irq; +#endif + DWC_DEBUGPL(DBG_CIL, "registering (common) handler for irq%d\n", + devirq); + dev_dbg(&_dev->dev, "Calling request_irq(%d)\n", devirq); + retval = request_irq(devirq, dwc_otg_common_irq, + IRQF_SHARED, + "dwc_otg", dwc_otg_device); + if (retval) { + DWC_ERROR("request of irq%d failed\n", devirq); + retval = -EBUSY; + goto fail; + } else { + dwc_otg_device->common_irq_installed = 1; + } + dwc_otg_device->os_dep.irq_num = devirq; + dwc_otg_device->os_dep.fiq_num = -EINVAL; + if (fiq_enable) { + int devfiq = platform_get_irq_byname(_dev, "usb"); + if (devfiq < 0) + devfiq = platform_get_irq(_dev, 1); + dwc_otg_device->os_dep.fiq_num = devfiq; + } + +#ifndef IRQF_TRIGGER_LOW +#if defined(LM_INTERFACE) || defined(PLATFORM_INTERFACE) + dev_dbg(&_dev->dev, "Calling set_irq_type\n"); + set_irq_type(devirq, +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,30)) + IRQT_LOW +#else + IRQ_TYPE_LEVEL_LOW +#endif + ); +#endif +#endif /*IRQF_TRIGGER_LOW*/ + + /* + * Initialize the DWC_otg core. + */ + dev_dbg(&_dev->dev, "Calling dwc_otg_core_init\n"); + dwc_otg_core_init(dwc_otg_device->core_if); + +#ifndef DWC_HOST_ONLY + /* + * Initialize the PCD + */ + dev_dbg(&_dev->dev, "Calling pcd_init\n"); + retval = pcd_init(_dev); + if (retval != 0) { + DWC_ERROR("pcd_init failed\n"); + dwc_otg_device->pcd = NULL; + goto fail; + } +#endif +#ifndef DWC_DEVICE_ONLY + /* + * Initialize the HCD + */ + dev_dbg(&_dev->dev, "Calling hcd_init\n"); + retval = hcd_init(_dev); + if (retval != 0) { + DWC_ERROR("hcd_init failed\n"); + dwc_otg_device->hcd = NULL; + goto fail; + } +#endif + /* Recover from drvdata having been overwritten by hcd_init() */ +#ifdef LM_INTERFACE + lm_set_drvdata(_dev, dwc_otg_device); +#elif defined(PLATFORM_INTERFACE) + platform_set_drvdata(_dev, dwc_otg_device); +#elif defined(PCI_INTERFACE) + pci_set_drvdata(_dev, dwc_otg_device); + dwc_otg_device->os_dep.pcidev = _dev; +#endif + + /* + * Enable the global interrupt after all the interrupt + * handlers are installed if there is no ADP support else + * perform initial actions required for Internal ADP logic. + */ + if (!dwc_otg_get_param_adp_enable(dwc_otg_device->core_if)) { + dev_dbg(&_dev->dev, "Calling enable_global_interrupts\n"); + dwc_otg_enable_global_interrupts(dwc_otg_device->core_if); + dev_dbg(&_dev->dev, "Done\n"); + } else + dwc_otg_adp_start(dwc_otg_device->core_if, + dwc_otg_is_host_mode(dwc_otg_device->core_if)); + + return 0; + +fail: + dwc_otg_driver_remove(_dev); + return retval; +} + +/** + * This structure defines the methods to be called by a bus driver + * during the lifecycle of a device on that bus. Both drivers and + * devices are registered with a bus driver. The bus driver matches + * devices to drivers based on information in the device and driver + * structures. + * + * The probe function is called when the bus driver matches a device + * to this driver. The remove function is called when a device is + * unregistered with the bus driver. + */ +#ifdef LM_INTERFACE +static struct lm_driver dwc_otg_driver = { + .drv = {.name = (char *)dwc_driver_name,}, + .probe = dwc_otg_driver_probe, + .remove = dwc_otg_driver_remove, + // 'suspend' and 'resume' absent +}; +#elif defined(PCI_INTERFACE) +static const struct pci_device_id pci_ids[] = { { + PCI_DEVICE(0x16c3, 0xabcd), + .driver_data = + (unsigned long)0xdeadbeef, + }, { /* end: all zeroes */ } +}; + +MODULE_DEVICE_TABLE(pci, pci_ids); + +/* pci driver glue; this is a "new style" PCI driver module */ +static struct pci_driver dwc_otg_driver = { + .name = "dwc_otg", + .id_table = pci_ids, + + .probe = dwc_otg_driver_probe, + .remove = dwc_otg_driver_remove, + + .driver = { + .name = (char *)dwc_driver_name, + }, +}; +#elif defined(PLATFORM_INTERFACE) +static struct platform_device_id platform_ids[] = { + { + .name = "bcm2708_usb", + .driver_data = (kernel_ulong_t) 0xdeadbeef, + }, + { /* end: all zeroes */ } +}; +MODULE_DEVICE_TABLE(platform, platform_ids); + +static const struct of_device_id dwc_otg_of_match_table[] = { + { .compatible = "brcm,bcm2708-usb", }, + {}, +}; +MODULE_DEVICE_TABLE(of, dwc_otg_of_match_table); + +static struct platform_driver dwc_otg_driver = { + .driver = { + .name = (char *)dwc_driver_name, + .of_match_table = dwc_otg_of_match_table, + }, + .id_table = platform_ids, + + .probe = dwc_otg_driver_probe, + .remove = dwc_otg_driver_remove, + // no 'shutdown', 'suspend', 'resume', 'suspend_late' or 'resume_early' +}; +#endif + +/** + * This function is called when the dwc_otg_driver is installed with the + * insmod command. It registers the dwc_otg_driver structure with the + * appropriate bus driver. This will cause the dwc_otg_driver_probe function + * to be called. In addition, the bus driver will automatically expose + * attributes defined for the device and driver in the special sysfs file + * system. + * + * @return + */ +static int __init dwc_otg_driver_init(void) +{ + int retval = 0; + int error; + struct device_driver *drv; + + if(fiq_fsm_enable && !fiq_enable) { + printk(KERN_WARNING "dwc_otg: fiq_fsm_enable was set without fiq_enable! Correcting.\n"); + fiq_enable = 1; + } + + printk(KERN_INFO "%s: version %s (%s bus)\n", dwc_driver_name, + DWC_DRIVER_VERSION, +#ifdef LM_INTERFACE + "logicmodule"); + retval = lm_driver_register(&dwc_otg_driver); + drv = &dwc_otg_driver.drv; +#elif defined(PCI_INTERFACE) + "pci"); + retval = pci_register_driver(&dwc_otg_driver); + drv = &dwc_otg_driver.driver; +#elif defined(PLATFORM_INTERFACE) + "platform"); + retval = platform_driver_register(&dwc_otg_driver); + drv = &dwc_otg_driver.driver; +#endif + if (retval < 0) { + printk(KERN_ERR "%s retval=%d\n", __func__, retval); + return retval; + } + printk(KERN_DEBUG "dwc_otg: FIQ %s\n", fiq_enable ? "enabled":"disabled"); + printk(KERN_DEBUG "dwc_otg: NAK holdoff %s\n", nak_holdoff ? "enabled":"disabled"); + printk(KERN_DEBUG "dwc_otg: FIQ split-transaction FSM %s\n", fiq_fsm_enable ? "enabled":"disabled"); + + error = driver_create_file(drv, &driver_attr_version); +#ifdef DEBUG + error = driver_create_file(drv, &driver_attr_debuglevel); +#endif + return retval; +} + +module_init(dwc_otg_driver_init); + +/** + * This function is called when the driver is removed from the kernel + * with the rmmod command. The driver unregisters itself with its bus + * driver. + * + */ +static void __exit dwc_otg_driver_cleanup(void) +{ + printk(KERN_DEBUG "dwc_otg_driver_cleanup()\n"); + +#ifdef LM_INTERFACE + driver_remove_file(&dwc_otg_driver.drv, &driver_attr_debuglevel); + driver_remove_file(&dwc_otg_driver.drv, &driver_attr_version); + lm_driver_unregister(&dwc_otg_driver); +#elif defined(PCI_INTERFACE) + driver_remove_file(&dwc_otg_driver.driver, &driver_attr_debuglevel); + driver_remove_file(&dwc_otg_driver.driver, &driver_attr_version); + pci_unregister_driver(&dwc_otg_driver); +#elif defined(PLATFORM_INTERFACE) + driver_remove_file(&dwc_otg_driver.driver, &driver_attr_debuglevel); + driver_remove_file(&dwc_otg_driver.driver, &driver_attr_version); + platform_driver_unregister(&dwc_otg_driver); +#endif + + printk(KERN_INFO "%s module removed\n", dwc_driver_name); +} + +module_exit(dwc_otg_driver_cleanup); + +MODULE_DESCRIPTION(DWC_DRIVER_DESC); +MODULE_AUTHOR("Synopsys Inc."); +MODULE_LICENSE("GPL"); + +module_param_named(otg_cap, dwc_otg_module_params.otg_cap, int, 0444); +MODULE_PARM_DESC(otg_cap, "OTG Capabilities 0=HNP&SRP 1=SRP Only 2=None"); +module_param_named(opt, dwc_otg_module_params.opt, int, 0444); +MODULE_PARM_DESC(opt, "OPT Mode"); +module_param_named(dma_enable, dwc_otg_module_params.dma_enable, int, 0444); +MODULE_PARM_DESC(dma_enable, "DMA Mode 0=Slave 1=DMA enabled"); + +module_param_named(dma_desc_enable, dwc_otg_module_params.dma_desc_enable, int, + 0444); +MODULE_PARM_DESC(dma_desc_enable, + "DMA Desc Mode 0=Address DMA 1=DMA Descriptor enabled"); + +module_param_named(dma_burst_size, dwc_otg_module_params.dma_burst_size, int, + 0444); +MODULE_PARM_DESC(dma_burst_size, + "DMA Burst Size 1, 4, 8, 16, 32, 64, 128, 256"); +module_param_named(speed, dwc_otg_module_params.speed, int, 0444); +MODULE_PARM_DESC(speed, "Speed 0=High Speed 1=Full Speed"); +module_param_named(host_support_fs_ls_low_power, + dwc_otg_module_params.host_support_fs_ls_low_power, int, + 0444); +MODULE_PARM_DESC(host_support_fs_ls_low_power, + "Support Low Power w/FS or LS 0=Support 1=Don't Support"); +module_param_named(host_ls_low_power_phy_clk, + dwc_otg_module_params.host_ls_low_power_phy_clk, int, 0444); +MODULE_PARM_DESC(host_ls_low_power_phy_clk, + "Low Speed Low Power Clock 0=48Mhz 1=6Mhz"); +module_param_named(enable_dynamic_fifo, + dwc_otg_module_params.enable_dynamic_fifo, int, 0444); +MODULE_PARM_DESC(enable_dynamic_fifo, "0=cC Setting 1=Allow Dynamic Sizing"); +module_param_named(data_fifo_size, dwc_otg_module_params.data_fifo_size, int, + 0444); +MODULE_PARM_DESC(data_fifo_size, + "Total number of words in the data FIFO memory 32-32768"); +module_param_named(dev_rx_fifo_size, dwc_otg_module_params.dev_rx_fifo_size, + int, 0444); +MODULE_PARM_DESC(dev_rx_fifo_size, "Number of words in the Rx FIFO 16-32768"); +module_param_named(dev_nperio_tx_fifo_size, + dwc_otg_module_params.dev_nperio_tx_fifo_size, int, 0444); +MODULE_PARM_DESC(dev_nperio_tx_fifo_size, + "Number of words in the non-periodic Tx FIFO 16-32768"); +module_param_named(dev_perio_tx_fifo_size_1, + dwc_otg_module_params.dev_perio_tx_fifo_size[0], int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_1, + "Number of words in the periodic Tx FIFO 4-768"); +module_param_named(dev_perio_tx_fifo_size_2, + dwc_otg_module_params.dev_perio_tx_fifo_size[1], int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_2, + "Number of words in the periodic Tx FIFO 4-768"); +module_param_named(dev_perio_tx_fifo_size_3, + dwc_otg_module_params.dev_perio_tx_fifo_size[2], int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_3, + "Number of words in the periodic Tx FIFO 4-768"); +module_param_named(dev_perio_tx_fifo_size_4, + dwc_otg_module_params.dev_perio_tx_fifo_size[3], int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_4, + "Number of words in the periodic Tx FIFO 4-768"); +module_param_named(dev_perio_tx_fifo_size_5, + dwc_otg_module_params.dev_perio_tx_fifo_size[4], int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_5, + "Number of words in the periodic Tx FIFO 4-768"); +module_param_named(dev_perio_tx_fifo_size_6, + dwc_otg_module_params.dev_perio_tx_fifo_size[5], int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_6, + "Number of words in the periodic Tx FIFO 4-768"); +module_param_named(dev_perio_tx_fifo_size_7, + dwc_otg_module_params.dev_perio_tx_fifo_size[6], int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_7, + "Number of words in the periodic Tx FIFO 4-768"); +module_param_named(dev_perio_tx_fifo_size_8, + dwc_otg_module_params.dev_perio_tx_fifo_size[7], int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_8, + "Number of words in the periodic Tx FIFO 4-768"); +module_param_named(dev_perio_tx_fifo_size_9, + dwc_otg_module_params.dev_perio_tx_fifo_size[8], int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_9, + "Number of words in the periodic Tx FIFO 4-768"); +module_param_named(dev_perio_tx_fifo_size_10, + dwc_otg_module_params.dev_perio_tx_fifo_size[9], int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_10, + "Number of words in the periodic Tx FIFO 4-768"); +module_param_named(dev_perio_tx_fifo_size_11, + dwc_otg_module_params.dev_perio_tx_fifo_size[10], int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_11, + "Number of words in the periodic Tx FIFO 4-768"); +module_param_named(dev_perio_tx_fifo_size_12, + dwc_otg_module_params.dev_perio_tx_fifo_size[11], int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_12, + "Number of words in the periodic Tx FIFO 4-768"); +module_param_named(dev_perio_tx_fifo_size_13, + dwc_otg_module_params.dev_perio_tx_fifo_size[12], int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_13, + "Number of words in the periodic Tx FIFO 4-768"); +module_param_named(dev_perio_tx_fifo_size_14, + dwc_otg_module_params.dev_perio_tx_fifo_size[13], int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_14, + "Number of words in the periodic Tx FIFO 4-768"); +module_param_named(dev_perio_tx_fifo_size_15, + dwc_otg_module_params.dev_perio_tx_fifo_size[14], int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_15, + "Number of words in the periodic Tx FIFO 4-768"); +module_param_named(host_rx_fifo_size, dwc_otg_module_params.host_rx_fifo_size, + int, 0444); +MODULE_PARM_DESC(host_rx_fifo_size, "Number of words in the Rx FIFO 16-32768"); +module_param_named(host_nperio_tx_fifo_size, + dwc_otg_module_params.host_nperio_tx_fifo_size, int, 0444); +MODULE_PARM_DESC(host_nperio_tx_fifo_size, + "Number of words in the non-periodic Tx FIFO 16-32768"); +module_param_named(host_perio_tx_fifo_size, + dwc_otg_module_params.host_perio_tx_fifo_size, int, 0444); +MODULE_PARM_DESC(host_perio_tx_fifo_size, + "Number of words in the host periodic Tx FIFO 16-32768"); +module_param_named(max_transfer_size, dwc_otg_module_params.max_transfer_size, + int, 0444); +/** @todo Set the max to 512K, modify checks */ +MODULE_PARM_DESC(max_transfer_size, + "The maximum transfer size supported in bytes 2047-65535"); +module_param_named(max_packet_count, dwc_otg_module_params.max_packet_count, + int, 0444); +MODULE_PARM_DESC(max_packet_count, + "The maximum number of packets in a transfer 15-511"); +module_param_named(host_channels, dwc_otg_module_params.host_channels, int, + 0444); +MODULE_PARM_DESC(host_channels, + "The number of host channel registers to use 1-16"); +module_param_named(dev_endpoints, dwc_otg_module_params.dev_endpoints, int, + 0444); +MODULE_PARM_DESC(dev_endpoints, + "The number of endpoints in addition to EP0 available for device mode 1-15"); +module_param_named(phy_type, dwc_otg_module_params.phy_type, int, 0444); +MODULE_PARM_DESC(phy_type, "0=Reserved 1=UTMI+ 2=ULPI"); +module_param_named(phy_utmi_width, dwc_otg_module_params.phy_utmi_width, int, + 0444); +MODULE_PARM_DESC(phy_utmi_width, "Specifies the UTMI+ Data Width 8 or 16 bits"); +module_param_named(phy_ulpi_ddr, dwc_otg_module_params.phy_ulpi_ddr, int, 0444); +MODULE_PARM_DESC(phy_ulpi_ddr, + "ULPI at double or single data rate 0=Single 1=Double"); +module_param_named(phy_ulpi_ext_vbus, dwc_otg_module_params.phy_ulpi_ext_vbus, + int, 0444); +MODULE_PARM_DESC(phy_ulpi_ext_vbus, + "ULPI PHY using internal or external vbus 0=Internal"); +module_param_named(i2c_enable, dwc_otg_module_params.i2c_enable, int, 0444); +MODULE_PARM_DESC(i2c_enable, "FS PHY Interface"); +module_param_named(ulpi_fs_ls, dwc_otg_module_params.ulpi_fs_ls, int, 0444); +MODULE_PARM_DESC(ulpi_fs_ls, "ULPI PHY FS/LS mode only"); +module_param_named(ts_dline, dwc_otg_module_params.ts_dline, int, 0444); +MODULE_PARM_DESC(ts_dline, "Term select Dline pulsing for all PHYs"); +module_param_named(debug, g_dbg_lvl, int, 0444); +MODULE_PARM_DESC(debug, ""); + +module_param_named(en_multiple_tx_fifo, + dwc_otg_module_params.en_multiple_tx_fifo, int, 0444); +MODULE_PARM_DESC(en_multiple_tx_fifo, + "Dedicated Non Periodic Tx FIFOs 0=disabled 1=enabled"); +module_param_named(dev_tx_fifo_size_1, + dwc_otg_module_params.dev_tx_fifo_size[0], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_1, "Number of words in the Tx FIFO 4-768"); +module_param_named(dev_tx_fifo_size_2, + dwc_otg_module_params.dev_tx_fifo_size[1], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_2, "Number of words in the Tx FIFO 4-768"); +module_param_named(dev_tx_fifo_size_3, + dwc_otg_module_params.dev_tx_fifo_size[2], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_3, "Number of words in the Tx FIFO 4-768"); +module_param_named(dev_tx_fifo_size_4, + dwc_otg_module_params.dev_tx_fifo_size[3], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_4, "Number of words in the Tx FIFO 4-768"); +module_param_named(dev_tx_fifo_size_5, + dwc_otg_module_params.dev_tx_fifo_size[4], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_5, "Number of words in the Tx FIFO 4-768"); +module_param_named(dev_tx_fifo_size_6, + dwc_otg_module_params.dev_tx_fifo_size[5], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_6, "Number of words in the Tx FIFO 4-768"); +module_param_named(dev_tx_fifo_size_7, + dwc_otg_module_params.dev_tx_fifo_size[6], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_7, "Number of words in the Tx FIFO 4-768"); +module_param_named(dev_tx_fifo_size_8, + dwc_otg_module_params.dev_tx_fifo_size[7], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_8, "Number of words in the Tx FIFO 4-768"); +module_param_named(dev_tx_fifo_size_9, + dwc_otg_module_params.dev_tx_fifo_size[8], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_9, "Number of words in the Tx FIFO 4-768"); +module_param_named(dev_tx_fifo_size_10, + dwc_otg_module_params.dev_tx_fifo_size[9], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_10, "Number of words in the Tx FIFO 4-768"); +module_param_named(dev_tx_fifo_size_11, + dwc_otg_module_params.dev_tx_fifo_size[10], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_11, "Number of words in the Tx FIFO 4-768"); +module_param_named(dev_tx_fifo_size_12, + dwc_otg_module_params.dev_tx_fifo_size[11], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_12, "Number of words in the Tx FIFO 4-768"); +module_param_named(dev_tx_fifo_size_13, + dwc_otg_module_params.dev_tx_fifo_size[12], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_13, "Number of words in the Tx FIFO 4-768"); +module_param_named(dev_tx_fifo_size_14, + dwc_otg_module_params.dev_tx_fifo_size[13], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_14, "Number of words in the Tx FIFO 4-768"); +module_param_named(dev_tx_fifo_size_15, + dwc_otg_module_params.dev_tx_fifo_size[14], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_15, "Number of words in the Tx FIFO 4-768"); + +module_param_named(thr_ctl, dwc_otg_module_params.thr_ctl, int, 0444); +MODULE_PARM_DESC(thr_ctl, + "Thresholding enable flag bit 0 - non ISO Tx thr., 1 - ISO Tx thr., 2 - Rx thr.- bit 0=disabled 1=enabled"); +module_param_named(tx_thr_length, dwc_otg_module_params.tx_thr_length, int, + 0444); +MODULE_PARM_DESC(tx_thr_length, "Tx Threshold length in 32 bit DWORDs"); +module_param_named(rx_thr_length, dwc_otg_module_params.rx_thr_length, int, + 0444); +MODULE_PARM_DESC(rx_thr_length, "Rx Threshold length in 32 bit DWORDs"); + +module_param_named(pti_enable, dwc_otg_module_params.pti_enable, int, 0444); +module_param_named(mpi_enable, dwc_otg_module_params.mpi_enable, int, 0444); +module_param_named(lpm_enable, dwc_otg_module_params.lpm_enable, int, 0444); +MODULE_PARM_DESC(lpm_enable, "LPM Enable 0=LPM Disabled 1=LPM Enabled"); +module_param_named(ic_usb_cap, dwc_otg_module_params.ic_usb_cap, int, 0444); +MODULE_PARM_DESC(ic_usb_cap, + "IC_USB Capability 0=IC_USB Disabled 1=IC_USB Enabled"); +module_param_named(ahb_thr_ratio, dwc_otg_module_params.ahb_thr_ratio, int, + 0444); +MODULE_PARM_DESC(ahb_thr_ratio, "AHB Threshold Ratio"); +module_param_named(power_down, dwc_otg_module_params.power_down, int, 0444); +MODULE_PARM_DESC(power_down, "Power Down Mode"); +module_param_named(reload_ctl, dwc_otg_module_params.reload_ctl, int, 0444); +MODULE_PARM_DESC(reload_ctl, "HFIR Reload Control"); +module_param_named(dev_out_nak, dwc_otg_module_params.dev_out_nak, int, 0444); +MODULE_PARM_DESC(dev_out_nak, "Enable Device OUT NAK"); +module_param_named(cont_on_bna, dwc_otg_module_params.cont_on_bna, int, 0444); +MODULE_PARM_DESC(cont_on_bna, "Enable Enable Continue on BNA"); +module_param_named(ahb_single, dwc_otg_module_params.ahb_single, int, 0444); +MODULE_PARM_DESC(ahb_single, "Enable AHB Single Support"); +module_param_named(adp_enable, dwc_otg_module_params.adp_enable, int, 0444); +MODULE_PARM_DESC(adp_enable, "ADP Enable 0=ADP Disabled 1=ADP Enabled"); +module_param_named(otg_ver, dwc_otg_module_params.otg_ver, int, 0444); +MODULE_PARM_DESC(otg_ver, "OTG revision supported 0=OTG 1.3 1=OTG 2.0"); +module_param(microframe_schedule, bool, 0444); +MODULE_PARM_DESC(microframe_schedule, "Enable the microframe scheduler"); + +module_param(fiq_enable, bool, 0444); +MODULE_PARM_DESC(fiq_enable, "Enable the FIQ"); +module_param(nak_holdoff, ushort, 0644); +MODULE_PARM_DESC(nak_holdoff, "Throttle duration for bulk split-transaction endpoints on a NAK. Default 8"); +module_param(fiq_fsm_enable, bool, 0444); +MODULE_PARM_DESC(fiq_fsm_enable, "Enable the FIQ to perform split transactions as defined by fiq_fsm_mask"); +module_param(fiq_fsm_mask, ushort, 0444); +MODULE_PARM_DESC(fiq_fsm_mask, "Bitmask of transactions to perform in the FIQ.\n" + "Bit 0 : Non-periodic split transactions\n" + "Bit 1 : Periodic split transactions\n" + "Bit 2 : High-speed multi-transfer isochronous\n" + "All other bits should be set 0."); +module_param(int_ep_interval_min, ushort, 0644); +MODULE_PARM_DESC(int_ep_interval_min, "Clamp high-speed Interrupt endpoints to a minimum polling interval.\n" + "0..1 = Use endpoint default\n" + "2..n = Minimum interval n microframes. Use powers of 2.\n"); + +module_param(cil_force_host, bool, 0644); +MODULE_PARM_DESC(cil_force_host, "On a connector-ID status change, " + "force Host Mode regardless of OTG state."); + +/** @page "Module Parameters" + * + * The following parameters may be specified when starting the module. + * These parameters define how the DWC_otg controller should be + * configured. Parameter values are passed to the CIL initialization + * function dwc_otg_cil_init + * + * Example: <code>modprobe dwc_otg speed=1 otg_cap=1</code> + * + + <table> + <tr><td>Parameter Name</td><td>Meaning</td></tr> + + <tr> + <td>otg_cap</td> + <td>Specifies the OTG capabilities. The driver will automatically detect the + value for this parameter if none is specified. + - 0: HNP and SRP capable (default, if available) + - 1: SRP Only capable + - 2: No HNP/SRP capable + </td></tr> + + <tr> + <td>dma_enable</td> + <td>Specifies whether to use slave or DMA mode for accessing the data FIFOs. + The driver will automatically detect the value for this parameter if none is + specified. + - 0: Slave + - 1: DMA (default, if available) + </td></tr> + + <tr> + <td>dma_burst_size</td> + <td>The DMA Burst size (applicable only for External DMA Mode). + - Values: 1, 4, 8 16, 32, 64, 128, 256 (default 32) + </td></tr> + + <tr> + <td>speed</td> + <td>Specifies the maximum speed of operation in host and device mode. The + actual speed depends on the speed of the attached device and the value of + phy_type. + - 0: High Speed (default) + - 1: Full Speed + </td></tr> + + <tr> + <td>host_support_fs_ls_low_power</td> + <td>Specifies whether low power mode is supported when attached to a Full + Speed or Low Speed device in host mode. + - 0: Don't support low power mode (default) + - 1: Support low power mode + </td></tr> + + <tr> + <td>host_ls_low_power_phy_clk</td> + <td>Specifies the PHY clock rate in low power mode when connected to a Low + Speed device in host mode. This parameter is applicable only if + HOST_SUPPORT_FS_LS_LOW_POWER is enabled. + - 0: 48 MHz (default) + - 1: 6 MHz + </td></tr> + + <tr> + <td>enable_dynamic_fifo</td> + <td> Specifies whether FIFOs may be resized by the driver software. + - 0: Use cC FIFO size parameters + - 1: Allow dynamic FIFO sizing (default) + </td></tr> + + <tr> + <td>data_fifo_size</td> + <td>Total number of 4-byte words in the data FIFO memory. This memory + includes the Rx FIFO, non-periodic Tx FIFO, and periodic Tx FIFOs. + - Values: 32 to 32768 (default 8192) + + Note: The total FIFO memory depth in the FPGA configuration is 8192. + </td></tr> + + <tr> + <td>dev_rx_fifo_size</td> + <td>Number of 4-byte words in the Rx FIFO in device mode when dynamic + FIFO sizing is enabled. + - Values: 16 to 32768 (default 1064) + </td></tr> + + <tr> + <td>dev_nperio_tx_fifo_size</td> + <td>Number of 4-byte words in the non-periodic Tx FIFO in device mode when + dynamic FIFO sizing is enabled. + - Values: 16 to 32768 (default 1024) + </td></tr> + + <tr> + <td>dev_perio_tx_fifo_size_n (n = 1 to 15)</td> + <td>Number of 4-byte words in each of the periodic Tx FIFOs in device mode + when dynamic FIFO sizing is enabled. + - Values: 4 to 768 (default 256) + </td></tr> + + <tr> + <td>host_rx_fifo_size</td> + <td>Number of 4-byte words in the Rx FIFO in host mode when dynamic FIFO + sizing is enabled. + - Values: 16 to 32768 (default 1024) + </td></tr> + + <tr> + <td>host_nperio_tx_fifo_size</td> + <td>Number of 4-byte words in the non-periodic Tx FIFO in host mode when + dynamic FIFO sizing is enabled in the core. + - Values: 16 to 32768 (default 1024) + </td></tr> + + <tr> + <td>host_perio_tx_fifo_size</td> + <td>Number of 4-byte words in the host periodic Tx FIFO when dynamic FIFO + sizing is enabled. + - Values: 16 to 32768 (default 1024) + </td></tr> + + <tr> + <td>max_transfer_size</td> + <td>The maximum transfer size supported in bytes. + - Values: 2047 to 65,535 (default 65,535) + </td></tr> + + <tr> + <td>max_packet_count</td> + <td>The maximum number of packets in a transfer. + - Values: 15 to 511 (default 511) + </td></tr> + + <tr> + <td>host_channels</td> + <td>The number of host channel registers to use. + - Values: 1 to 16 (default 12) + + Note: The FPGA configuration supports a maximum of 12 host channels. + </td></tr> + + <tr> + <td>dev_endpoints</td> + <td>The number of endpoints in addition to EP0 available for device mode + operations. + - Values: 1 to 15 (default 6 IN and OUT) + + Note: The FPGA configuration supports a maximum of 6 IN and OUT endpoints in + addition to EP0. + </td></tr> + + <tr> + <td>phy_type</td> + <td>Specifies the type of PHY interface to use. By default, the driver will + automatically detect the phy_type. + - 0: Full Speed + - 1: UTMI+ (default, if available) + - 2: ULPI + </td></tr> + + <tr> + <td>phy_utmi_width</td> + <td>Specifies the UTMI+ Data Width. This parameter is applicable for a + phy_type of UTMI+. Also, this parameter is applicable only if the + OTG_HSPHY_WIDTH cC parameter was set to "8 and 16 bits", meaning that the + core has been configured to work at either data path width. + - Values: 8 or 16 bits (default 16) + </td></tr> + + <tr> + <td>phy_ulpi_ddr</td> + <td>Specifies whether the ULPI operates at double or single data rate. This + parameter is only applicable if phy_type is ULPI. + - 0: single data rate ULPI interface with 8 bit wide data bus (default) + - 1: double data rate ULPI interface with 4 bit wide data bus + </td></tr> + + <tr> + <td>i2c_enable</td> + <td>Specifies whether to use the I2C interface for full speed PHY. This + parameter is only applicable if PHY_TYPE is FS. + - 0: Disabled (default) + - 1: Enabled + </td></tr> + + <tr> + <td>ulpi_fs_ls</td> + <td>Specifies whether to use ULPI FS/LS mode only. + - 0: Disabled (default) + - 1: Enabled + </td></tr> + + <tr> + <td>ts_dline</td> + <td>Specifies whether term select D-Line pulsing for all PHYs is enabled. + - 0: Disabled (default) + - 1: Enabled + </td></tr> + + <tr> + <td>en_multiple_tx_fifo</td> + <td>Specifies whether dedicatedto tx fifos are enabled for non periodic IN EPs. + The driver will automatically detect the value for this parameter if none is + specified. + - 0: Disabled + - 1: Enabled (default, if available) + </td></tr> + + <tr> + <td>dev_tx_fifo_size_n (n = 1 to 15)</td> + <td>Number of 4-byte words in each of the Tx FIFOs in device mode + when dynamic FIFO sizing is enabled. + - Values: 4 to 768 (default 256) + </td></tr> + + <tr> + <td>tx_thr_length</td> + <td>Transmit Threshold length in 32 bit double words + - Values: 8 to 128 (default 64) + </td></tr> + + <tr> + <td>rx_thr_length</td> + <td>Receive Threshold length in 32 bit double words + - Values: 8 to 128 (default 64) + </td></tr> + +<tr> + <td>thr_ctl</td> + <td>Specifies whether to enable Thresholding for Device mode. Bits 0, 1, 2 of + this parmater specifies if thresholding is enabled for non-Iso Tx, Iso Tx and + Rx transfers accordingly. + The driver will automatically detect the value for this parameter if none is + specified. + - Values: 0 to 7 (default 0) + Bit values indicate: + - 0: Thresholding disabled + - 1: Thresholding enabled + </td></tr> + +<tr> + <td>dma_desc_enable</td> + <td>Specifies whether to enable Descriptor DMA mode. + The driver will automatically detect the value for this parameter if none is + specified. + - 0: Descriptor DMA disabled + - 1: Descriptor DMA (default, if available) + </td></tr> + +<tr> + <td>mpi_enable</td> + <td>Specifies whether to enable MPI enhancement mode. + The driver will automatically detect the value for this parameter if none is + specified. + - 0: MPI disabled (default) + - 1: MPI enable + </td></tr> + +<tr> + <td>pti_enable</td> + <td>Specifies whether to enable PTI enhancement support. + The driver will automatically detect the value for this parameter if none is + specified. + - 0: PTI disabled (default) + - 1: PTI enable + </td></tr> + +<tr> + <td>lpm_enable</td> + <td>Specifies whether to enable LPM support. + The driver will automatically detect the value for this parameter if none is + specified. + - 0: LPM disabled + - 1: LPM enable (default, if available) + </td></tr> + +<tr> + <td>ic_usb_cap</td> + <td>Specifies whether to enable IC_USB capability. + The driver will automatically detect the value for this parameter if none is + specified. + - 0: IC_USB disabled (default, if available) + - 1: IC_USB enable + </td></tr> + +<tr> + <td>ahb_thr_ratio</td> + <td>Specifies AHB Threshold ratio. + - Values: 0 to 3 (default 0) + </td></tr> + +<tr> + <td>power_down</td> + <td>Specifies Power Down(Hibernation) Mode. + The driver will automatically detect the value for this parameter if none is + specified. + - 0: Power Down disabled (default) + - 2: Power Down enabled + </td></tr> + + <tr> + <td>reload_ctl</td> + <td>Specifies whether dynamic reloading of the HFIR register is allowed during + run time. The driver will automatically detect the value for this parameter if + none is specified. In case the HFIR value is reloaded when HFIR.RldCtrl == 1'b0 + the core might misbehave. + - 0: Reload Control disabled (default) + - 1: Reload Control enabled + </td></tr> + + <tr> + <td>dev_out_nak</td> + <td>Specifies whether Device OUT NAK enhancement enabled or no. + The driver will automatically detect the value for this parameter if + none is specified. This parameter is valid only when OTG_EN_DESC_DMA == 1b1. + - 0: The core does not set NAK after Bulk OUT transfer complete (default) + - 1: The core sets NAK after Bulk OUT transfer complete + </td></tr> + + <tr> + <td>cont_on_bna</td> + <td>Specifies whether Enable Continue on BNA enabled or no. + After receiving BNA interrupt the core disables the endpoint,when the + endpoint is re-enabled by the application the + - 0: Core starts processing from the DOEPDMA descriptor (default) + - 1: Core starts processing from the descriptor which received the BNA. + This parameter is valid only when OTG_EN_DESC_DMA == 1b1. + </td></tr> + + <tr> + <td>ahb_single</td> + <td>This bit when programmed supports SINGLE transfers for remainder data + in a transfer for DMA mode of operation. + - 0: The remainder data will be sent using INCR burst size (default) + - 1: The remainder data will be sent using SINGLE burst size. + </td></tr> + +<tr> + <td>adp_enable</td> + <td>Specifies whether ADP feature is enabled. + The driver will automatically detect the value for this parameter if none is + specified. + - 0: ADP feature disabled (default) + - 1: ADP feature enabled + </td></tr> + + <tr> + <td>otg_ver</td> + <td>Specifies whether OTG is performing as USB OTG Revision 2.0 or Revision 1.3 + USB OTG device. + - 0: OTG 2.0 support disabled (default) + - 1: OTG 2.0 support enabled + </td></tr> + +*/ diff --git a/drivers/usb/host/dwc_otg/dwc_otg_driver.h b/drivers/usb/host/dwc_otg/dwc_otg_driver.h new file mode 100644 index 00000000000000..6a8be63a0ab20f --- /dev/null +++ b/drivers/usb/host/dwc_otg/dwc_otg_driver.h @@ -0,0 +1,86 @@ +/* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_driver.h $ + * $Revision: #19 $ + * $Date: 2010/11/15 $ + * $Change: 1627671 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ + +#ifndef __DWC_OTG_DRIVER_H__ +#define __DWC_OTG_DRIVER_H__ + +/** @file + * This file contains the interface to the Linux driver. + */ +#include "dwc_otg_os_dep.h" +#include "dwc_otg_core_if.h" + +/* Type declarations */ +struct dwc_otg_pcd; +struct dwc_otg_hcd; + +/** + * This structure is a wrapper that encapsulates the driver components used to + * manage a single DWC_otg controller. + */ +typedef struct dwc_otg_device { + /** Structure containing OS-dependent stuff. KEEP THIS STRUCT AT THE + * VERY BEGINNING OF THE DEVICE STRUCT. OSes such as FreeBSD and NetBSD + * require this. */ + struct os_dependent os_dep; + + /** Pointer to the core interface structure. */ + dwc_otg_core_if_t *core_if; + + /** Pointer to the PCD structure. */ + struct dwc_otg_pcd *pcd; + + /** Pointer to the HCD structure. */ + struct dwc_otg_hcd *hcd; + + /** Flag to indicate whether the common IRQ handler is installed. */ + uint8_t common_irq_installed; + +} dwc_otg_device_t; + +/*We must clear S3C24XX_EINTPEND external interrupt register + * because after clearing in this register trigerred IRQ from + * H/W core in kernel interrupt can be occured again before OTG + * handlers clear all IRQ sources of Core registers because of + * timing latencies and Low Level IRQ Type. + */ +#ifdef CONFIG_MACH_IPMATE +#define S3C2410X_CLEAR_EINTPEND() \ +do { \ + __raw_writel(1UL << 11,S3C24XX_EINTPEND); \ +} while (0) +#else +#define S3C2410X_CLEAR_EINTPEND() do { } while (0) +#endif + +#endif diff --git a/drivers/usb/host/dwc_otg/dwc_otg_fiq_fsm.c b/drivers/usb/host/dwc_otg/dwc_otg_fiq_fsm.c new file mode 100644 index 00000000000000..eff4d1e2288e5c --- /dev/null +++ b/drivers/usb/host/dwc_otg/dwc_otg_fiq_fsm.c @@ -0,0 +1,1434 @@ +/* + * dwc_otg_fiq_fsm.c - The finite state machine FIQ + * + * Copyright (c) 2013 Raspberry Pi Foundation + * + * Author: Jonathan Bell <jonathan@raspberrypi.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Raspberry Pi nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * This FIQ implements functionality that performs split transactions on + * the dwc_otg hardware without any outside intervention. A split transaction + * is "queued" by nominating a specific host channel to perform the entirety + * of a split transaction. This FIQ will then perform the microframe-precise + * scheduling required in each phase of the transaction until completion. + * + * The FIQ functionality is glued into the Synopsys driver via the entry point + * in the FSM enqueue function, and at the exit point in handling a HC interrupt + * for a FSM-enabled channel. + * + * NB: Large parts of this implementation have architecture-specific code. + * For porting this functionality to other ARM machines, the minimum is required: + * - An interrupt controller allowing the top-level dwc USB interrupt to be routed + * to the FIQ + * - A method of forcing a software generated interrupt from FIQ mode that then + * triggers an IRQ entry (with the dwc USB handler called by this IRQ number) + * - Guaranteed interrupt routing such that both the FIQ and SGI occur on the same + * processor core - there is no locking between the FIQ and IRQ (aside from + * local_fiq_disable) + * + */ + +#include "dwc_otg_fiq_fsm.h" + + +char buffer[1000*16]; +int wptr; +void notrace _fiq_print(enum fiq_debug_level dbg_lvl, volatile struct fiq_state *state, char *fmt, ...) +{ + enum fiq_debug_level dbg_lvl_req = FIQDBG_ERR; + va_list args; + char text[17]; + hfnum_data_t hfnum = { .d32 = FIQ_READ(state->dwc_regs_base + 0x408) }; + + if((dbg_lvl & dbg_lvl_req) || dbg_lvl == FIQDBG_ERR) + { + snprintf(text, 9, " %4d:%1u ", hfnum.b.frnum/8, hfnum.b.frnum & 7); + va_start(args, fmt); + vsnprintf(text+8, 9, fmt, args); + va_end(args); + + memcpy(buffer + wptr, text, 16); + wptr = (wptr + 16) % sizeof(buffer); + } +} + + +#ifdef CONFIG_ARM64 + +inline void fiq_fsm_spin_lock(fiq_lock_t *lock) +{ + spin_lock((spinlock_t *)lock); +} + +inline void fiq_fsm_spin_unlock(fiq_lock_t *lock) +{ + spin_unlock((spinlock_t *)lock); +} + +#else + +/** + * fiq_fsm_spin_lock() - ARMv6+ bare bones spinlock + * Must be called with local interrupts and FIQ disabled. + */ +#if defined(CONFIG_ARCH_BCM2835) && defined(CONFIG_SMP) +inline void fiq_fsm_spin_lock(fiq_lock_t *lock) +{ + unsigned long tmp; + uint32_t newval; + fiq_lock_t lockval; + /* Nested locking, yay. If we are on the same CPU as the fiq, then the disable + * will be sufficient. If we are on a different CPU, then the lock protects us. */ + prefetchw(&lock->slock); + asm volatile ( + "1: ldrex %0, [%3]\n" + " add %1, %0, %4\n" + " strex %2, %1, [%3]\n" + " teq %2, #0\n" + " bne 1b" + : "=&r" (lockval), "=&r" (newval), "=&r" (tmp) + : "r" (&lock->slock), "I" (1 << 16) + : "cc"); + + while (lockval.tickets.next != lockval.tickets.owner) { + wfe(); + lockval.tickets.owner = READ_ONCE(lock->tickets.owner); + } + smp_mb(); +} +#else +inline void fiq_fsm_spin_lock(fiq_lock_t *lock) { } +#endif + +/** + * fiq_fsm_spin_unlock() - ARMv6+ bare bones spinunlock + */ +#if defined(CONFIG_ARCH_BCM2835) && defined(CONFIG_SMP) +inline void fiq_fsm_spin_unlock(fiq_lock_t *lock) +{ + smp_mb(); + lock->tickets.owner++; + dsb_sev(); +} +#else +inline void fiq_fsm_spin_unlock(fiq_lock_t *lock) { } +#endif + +#endif + +/** + * fiq_fsm_restart_channel() - Poke channel enable bit for a split transaction + * @channel: channel to re-enable + */ +static void notrace fiq_fsm_restart_channel(struct fiq_state *st, int n, int force) +{ + hcchar_data_t hcchar = { .d32 = FIQ_READ(st->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCCHAR) }; + + hcchar.b.chen = 0; + if (st->channel[n].hcchar_copy.b.eptype & 0x1) { + hfnum_data_t hfnum = { .d32 = FIQ_READ(st->dwc_regs_base + HFNUM) }; + /* Hardware bug workaround: update the ssplit index */ + if (st->channel[n].hcsplt_copy.b.spltena) + st->channel[n].expected_uframe = (hfnum.b.frnum + 1) & 0x3FFF; + + hcchar.b.oddfrm = (hfnum.b.frnum & 0x1) ? 0 : 1; + } + + FIQ_WRITE(st->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCCHAR, hcchar.d32); + hcchar.d32 = FIQ_READ(st->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCCHAR); + hcchar.b.chen = 1; + + FIQ_WRITE(st->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCCHAR, hcchar.d32); + fiq_print(FIQDBG_INT, st, "HCGO %01d %01d", n, force); +} + +/** + * fiq_fsm_setup_csplit() - Prepare a host channel for a CSplit transaction stage + * @st: Pointer to the channel's state + * @n : channel number + * + * Change host channel registers to perform a complete-split transaction. Being mindful of the + * endpoint direction, set control regs up correctly. + */ +static void notrace fiq_fsm_setup_csplit(struct fiq_state *st, int n) +{ + hcsplt_data_t hcsplt = { .d32 = FIQ_READ(st->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCSPLT) }; + hctsiz_data_t hctsiz = { .d32 = FIQ_READ(st->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCTSIZ) }; + + hcsplt.b.compsplt = 1; + if (st->channel[n].hcchar_copy.b.epdir == 1) { + // If IN, the CSPLIT result contains the data or a hub handshake. hctsiz = maxpacket. + hctsiz.b.xfersize = st->channel[n].hctsiz_copy.b.xfersize; + } else { + // If OUT, the CSPLIT result contains handshake only. + hctsiz.b.xfersize = 0; + } + FIQ_WRITE(st->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCSPLT, hcsplt.d32); + FIQ_WRITE(st->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCTSIZ, hctsiz.d32); + mb(); +} + +/** + * fiq_fsm_restart_np_pending() - Restart a single non-periodic contended transfer + * @st: Pointer to the channel's state + * @num_channels: Total number of host channels + * @orig_channel: Channel index of completed transfer + * + * In the case where an IN and OUT transfer are simultaneously scheduled to the + * same device/EP, inadequate hub implementations will misbehave. Once the first + * transfer is complete, a pending non-periodic split can then be issued. + */ +static void notrace fiq_fsm_restart_np_pending(struct fiq_state *st, int num_channels, int orig_channel) +{ + int i; + int dev_addr = st->channel[orig_channel].hcchar_copy.b.devaddr; + int ep_num = st->channel[orig_channel].hcchar_copy.b.epnum; + for (i = 0; i < num_channels; i++) { + if (st->channel[i].fsm == FIQ_NP_SSPLIT_PENDING && + st->channel[i].hcchar_copy.b.devaddr == dev_addr && + st->channel[i].hcchar_copy.b.epnum == ep_num) { + st->channel[i].fsm = FIQ_NP_SSPLIT_STARTED; + fiq_fsm_restart_channel(st, i, 0); + break; + } + } +} + +static inline int notrace fiq_get_xfer_len(struct fiq_state *st, int n) +{ + /* The xfersize register is a bit wonky. For IN transfers, it decrements by the packet size. */ + hctsiz_data_t hctsiz = { .d32 = FIQ_READ(st->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCTSIZ) }; + + if (st->channel[n].hcchar_copy.b.epdir == 0) { + return st->channel[n].hctsiz_copy.b.xfersize; + } else { + return st->channel[n].hctsiz_copy.b.xfersize - hctsiz.b.xfersize; + } + +} + + +/** + * fiq_increment_dma_buf() - update DMA address for bounce buffers after a CSPLIT + * + * Of use only for IN periodic transfers. + */ +static int notrace fiq_increment_dma_buf(struct fiq_state *st, int num_channels, int n) +{ + hcdma_data_t hcdma; + int i = st->channel[n].dma_info.index; + int len; + struct fiq_dma_channel *split_dma = + (struct fiq_dma_channel *)(uintptr_t)st->dma_base; + + len = fiq_get_xfer_len(st, n); + fiq_print(FIQDBG_INT, st, "LEN: %03d", len); + st->channel[n].dma_info.slot_len[i] = len; + i++; + if (i > 6) + BUG(); + + hcdma.d32 = lower_32_bits((uintptr_t)&split_dma[n].index[i].buf[0]); + FIQ_WRITE(st->dwc_regs_base + HC_START + (HC_OFFSET * n) + HC_DMA, hcdma.d32); + st->channel[n].dma_info.index = i; + return 0; +} + +/** + * fiq_reload_hctsiz() - for IN transactions, reset HCTSIZ + */ +static void notrace fiq_fsm_reload_hctsiz(struct fiq_state *st, int n) +{ + hctsiz_data_t hctsiz = { .d32 = FIQ_READ(st->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCTSIZ) }; + hctsiz.b.xfersize = st->channel[n].hctsiz_copy.b.xfersize; + hctsiz.b.pktcnt = 1; + FIQ_WRITE(st->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCTSIZ, hctsiz.d32); +} + +/** + * fiq_fsm_reload_hcdma() - for OUT transactions, rewind DMA pointer + */ +static void notrace fiq_fsm_reload_hcdma(struct fiq_state *st, int n) +{ + hcdma_data_t hcdma = st->channel[n].hcdma_copy; + FIQ_WRITE(st->dwc_regs_base + HC_START + (HC_OFFSET * n) + HC_DMA, hcdma.d32); +} + +/** + * fiq_iso_out_advance() - update DMA address and split position bits + * for isochronous OUT transactions. + * + * Returns 1 if this is the last packet queued, 0 otherwise. Split-ALL and + * Split-BEGIN states are not handled - this is done when the transaction was queued. + * + * This function must only be called from the FIQ_ISO_OUT_ACTIVE state. + */ +static int notrace fiq_iso_out_advance(struct fiq_state *st, int num_channels, int n) +{ + hcsplt_data_t hcsplt; + hctsiz_data_t hctsiz; + hcdma_data_t hcdma; + struct fiq_dma_channel *split_dma = + (struct fiq_dma_channel *)(uintptr_t)st->dma_base; + int last = 0; + int i = st->channel[n].dma_info.index; + + fiq_print(FIQDBG_INT, st, "ADV %01d %01d ", n, i); + i++; + if (i == 4) + last = 1; + if (st->channel[n].dma_info.slot_len[i+1] == 255) + last = 1; + + /* New DMA address - address of bounce buffer referred to in index */ + hcdma.d32 = lower_32_bits((uintptr_t)&split_dma[n].index[i].buf[0]); + //hcdma.d32 = FIQ_READ(st->dwc_regs_base + HC_START + (HC_OFFSET * n) + HC_DMA); + //hcdma.d32 += st->channel[n].dma_info.slot_len[i]; + fiq_print(FIQDBG_INT, st, "LAST: %01d ", last); + fiq_print(FIQDBG_INT, st, "LEN: %03d", st->channel[n].dma_info.slot_len[i]); + hcsplt.d32 = FIQ_READ(st->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCSPLT); + hctsiz.d32 = FIQ_READ(st->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCTSIZ); + hcsplt.b.xactpos = (last) ? ISOC_XACTPOS_END : ISOC_XACTPOS_MID; + /* Set up new packet length */ + hctsiz.b.pktcnt = 1; + hctsiz.b.xfersize = st->channel[n].dma_info.slot_len[i]; + fiq_print(FIQDBG_INT, st, "%08x", hctsiz.d32); + + st->channel[n].dma_info.index++; + FIQ_WRITE(st->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCSPLT, hcsplt.d32); + FIQ_WRITE(st->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCTSIZ, hctsiz.d32); + FIQ_WRITE(st->dwc_regs_base + HC_START + (HC_OFFSET * n) + HC_DMA, hcdma.d32); + return last; +} + +/** + * fiq_fsm_tt_next_isoc() - queue next pending isochronous out start-split on a TT + * + * Despite the limitations of the DWC core, we can force a microframe pipeline of + * isochronous OUT start-split transactions while waiting for a corresponding other-type + * of endpoint to finish its CSPLITs. TTs have big periodic buffers therefore it + * is very unlikely that filling the start-split FIFO will cause data loss. + * This allows much better interleaving of transactions in an order-independent way- + * there is no requirement to prioritise isochronous, just a state-space search has + * to be performed on each periodic start-split complete interrupt. + */ +static int notrace fiq_fsm_tt_next_isoc(struct fiq_state *st, int num_channels, int n) +{ + int hub_addr = st->channel[n].hub_addr; + int port_addr = st->channel[n].port_addr; + int i, poked = 0; + for (i = 0; i < num_channels; i++) { + if (i == n || st->channel[i].fsm == FIQ_PASSTHROUGH) + continue; + if (st->channel[i].hub_addr == hub_addr && + st->channel[i].port_addr == port_addr) { + switch (st->channel[i].fsm) { + case FIQ_PER_ISO_OUT_PENDING: + if (st->channel[i].nrpackets == 1) { + st->channel[i].fsm = FIQ_PER_ISO_OUT_LAST; + } else { + st->channel[i].fsm = FIQ_PER_ISO_OUT_ACTIVE; + } + fiq_fsm_restart_channel(st, i, 0); + poked = 1; + break; + + default: + break; + } + } + if (poked) + break; + } + return poked; +} + +/** + * fiq_fsm_tt_in_use() - search for host channels using this TT + * @n: Channel to use as reference + * + */ +int notrace noinline fiq_fsm_tt_in_use(struct fiq_state *st, int num_channels, int n) +{ + int hub_addr = st->channel[n].hub_addr; + int port_addr = st->channel[n].port_addr; + int i, in_use = 0; + for (i = 0; i < num_channels; i++) { + if (i == n || st->channel[i].fsm == FIQ_PASSTHROUGH) + continue; + switch (st->channel[i].fsm) { + /* TT is reserved for channels that are in the middle of a periodic + * split transaction. + */ + case FIQ_PER_SSPLIT_STARTED: + case FIQ_PER_CSPLIT_WAIT: + case FIQ_PER_CSPLIT_NYET1: + //case FIQ_PER_CSPLIT_POLL: + case FIQ_PER_ISO_OUT_ACTIVE: + case FIQ_PER_ISO_OUT_LAST: + if (st->channel[i].hub_addr == hub_addr && + st->channel[i].port_addr == port_addr) { + in_use = 1; + } + break; + default: + break; + } + if (in_use) + break; + } + return in_use; +} + +/** + * fiq_fsm_more_csplits() - determine whether additional CSPLITs need + * to be issued for this IN transaction. + * + * We cannot tell the inbound PID of a data packet due to hardware limitations. + * we need to make an educated guess as to whether we need to queue another CSPLIT + * or not. A no-brainer is when we have received enough data to fill the endpoint + * size, but for endpoints that give variable-length data then we have to resort + * to heuristics. + * + * We also return whether this is the last CSPLIT to be queued, again based on + * heuristics. This is to allow a 1-uframe overlap of periodic split transactions. + * Note: requires at least 1 CSPLIT to have been performed prior to being called. + */ + +/* + * We need some way of guaranteeing if a returned periodic packet of size X + * has a DATA0 PID. + * The heuristic value of 144 bytes assumes that the received data has maximal + * bit-stuffing and the clock frequency of the transmitting device is at the lowest + * permissible limit. If the transfer length results in a final packet size + * 144 < p <= 188, then an erroneous CSPLIT will be issued. + * Also used to ensure that an endpoint will nominally only return a single + * complete-split worth of data. + */ +#define DATA0_PID_HEURISTIC 144 + +static int notrace noinline fiq_fsm_more_csplits(struct fiq_state *state, int n, int *probably_last) +{ + + int i; + int total_len = 0; + int more_needed = 1; + struct fiq_channel_state *st = &state->channel[n]; + + for (i = 0; i < st->dma_info.index; i++) { + total_len += st->dma_info.slot_len[i]; + } + + *probably_last = 0; + + if (st->hcchar_copy.b.eptype == 0x3) { + /* + * An interrupt endpoint will take max 2 CSPLITs. if we are receiving data + * then this is definitely the last CSPLIT. + */ + *probably_last = 1; + } else { + /* Isoc IN. This is a bit risky if we are the first transaction: + * we may have been held off slightly. */ + if (i > 1 && st->dma_info.slot_len[st->dma_info.index-1] <= DATA0_PID_HEURISTIC) { + more_needed = 0; + } + /* If in the next uframe we will receive enough data to fill the endpoint, + * then only issue 1 more csplit. + */ + if (st->hctsiz_copy.b.xfersize - total_len <= DATA0_PID_HEURISTIC) + *probably_last = 1; + } + + if (total_len >= st->hctsiz_copy.b.xfersize || + i == 6 || total_len == 0) + /* Note: due to bit stuffing it is possible to have > 6 CSPLITs for + * a single endpoint. Accepting more would completely break our scheduling mechanism though + * - in these extreme cases we will pass through a truncated packet. + */ + more_needed = 0; + + return more_needed; +} + +/** + * fiq_fsm_too_late() - Test transaction for lateness + * + * If a SSPLIT for a large IN transaction is issued too late in a frame, + * the hub will disable the port to the device and respond with ERR handshakes. + * The hub status endpoint will not reflect this change. + * Returns 1 if we will issue a SSPLIT that will result in a device babble. + */ +int notrace fiq_fsm_too_late(struct fiq_state *st, int n) +{ + int uframe; + hfnum_data_t hfnum = { .d32 = FIQ_READ(st->dwc_regs_base + HFNUM) }; + uframe = hfnum.b.frnum & 0x7; + if ((uframe < 6) && (st->channel[n].nrpackets + 1 + uframe > 7)) { + return 1; + } else { + return 0; + } +} + + +/** + * fiq_fsm_start_next_periodic() - A half-arsed attempt at a microframe pipeline + * + * Search pending transactions in the start-split pending state and queue them. + * Don't queue packets in uframe .5 (comes out in .6) (USB2.0 11.18.4). + * Note: we specifically don't do isochronous OUT transactions first because better + * use of the TT's start-split fifo can be achieved by pipelining an IN before an OUT. + */ +static void notrace noinline fiq_fsm_start_next_periodic(struct fiq_state *st, int num_channels) +{ + int n; + hfnum_data_t hfnum = { .d32 = FIQ_READ(st->dwc_regs_base + HFNUM) }; + if ((hfnum.b.frnum & 0x7) == 5) + return; + for (n = 0; n < num_channels; n++) { + if (st->channel[n].fsm == FIQ_PER_SSPLIT_QUEUED) { + /* Check to see if any other transactions are using this TT */ + if(!fiq_fsm_tt_in_use(st, num_channels, n)) { + if (!fiq_fsm_too_late(st, n)) { + st->channel[n].fsm = FIQ_PER_SSPLIT_STARTED; + fiq_print(FIQDBG_INT, st, "NEXTPER "); + fiq_fsm_restart_channel(st, n, 0); + } else { + st->channel[n].fsm = FIQ_PER_SPLIT_TIMEOUT; + } + break; + } + } + } + for (n = 0; n < num_channels; n++) { + if (st->channel[n].fsm == FIQ_PER_ISO_OUT_PENDING) { + if (!fiq_fsm_tt_in_use(st, num_channels, n)) { + fiq_print(FIQDBG_INT, st, "NEXTISO "); + if (st->channel[n].nrpackets == 1) + st->channel[n].fsm = FIQ_PER_ISO_OUT_LAST; + else + st->channel[n].fsm = FIQ_PER_ISO_OUT_ACTIVE; + fiq_fsm_restart_channel(st, n, 0); + break; + } + } + } +} + +/** + * fiq_fsm_update_hs_isoc() - update isochronous frame and transfer data + * @state: Pointer to fiq_state + * @n: Channel transaction is active on + * @hcint: Copy of host channel interrupt register + * + * Returns 0 if there are no more transactions for this HC to do, 1 + * otherwise. + */ +static int notrace noinline fiq_fsm_update_hs_isoc(struct fiq_state *state, int n, hcint_data_t hcint) +{ + struct fiq_channel_state *st = &state->channel[n]; + int xfer_len = 0, nrpackets = 0; + hcdma_data_t hcdma; + fiq_print(FIQDBG_INT, state, "HSISO %02d", n); + + xfer_len = fiq_get_xfer_len(state, n); + st->hs_isoc_info.iso_desc[st->hs_isoc_info.index].actual_length = xfer_len; + + st->hs_isoc_info.iso_desc[st->hs_isoc_info.index].status = hcint.d32; + + st->hs_isoc_info.index++; + if (st->hs_isoc_info.index == st->hs_isoc_info.nrframes) { + return 0; + } + + /* grab the next DMA address offset from the array */ + hcdma.d32 = st->hcdma_copy.d32 + st->hs_isoc_info.iso_desc[st->hs_isoc_info.index].offset; + FIQ_WRITE(state->dwc_regs_base + HC_START + (HC_OFFSET * n) + HC_DMA, hcdma.d32); + + /* We need to set multi_count. This is a bit tricky - has to be set per-transaction as + * the core needs to be told to send the correct number. Caution: for IN transfers, + * this is always set to the maximum size of the endpoint. */ + xfer_len = st->hs_isoc_info.iso_desc[st->hs_isoc_info.index].length; + /* Integer divide in a FIQ: fun. FIXME: make this not suck */ + nrpackets = (xfer_len + st->hcchar_copy.b.mps - 1) / st->hcchar_copy.b.mps; + if (nrpackets == 0) + nrpackets = 1; + st->hcchar_copy.b.multicnt = nrpackets; + st->hctsiz_copy.b.pktcnt = nrpackets; + + /* Initial PID also needs to be set */ + if (st->hcchar_copy.b.epdir == 0) { + st->hctsiz_copy.b.xfersize = xfer_len; + switch (st->hcchar_copy.b.multicnt) { + case 1: + st->hctsiz_copy.b.pid = DWC_PID_DATA0; + break; + case 2: + case 3: + st->hctsiz_copy.b.pid = DWC_PID_MDATA; + break; + } + + } else { + st->hctsiz_copy.b.xfersize = nrpackets * st->hcchar_copy.b.mps; + switch (st->hcchar_copy.b.multicnt) { + case 1: + st->hctsiz_copy.b.pid = DWC_PID_DATA0; + break; + case 2: + st->hctsiz_copy.b.pid = DWC_PID_DATA1; + break; + case 3: + st->hctsiz_copy.b.pid = DWC_PID_DATA2; + break; + } + } + FIQ_WRITE(state->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCTSIZ, st->hctsiz_copy.d32); + FIQ_WRITE(state->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCCHAR, st->hcchar_copy.d32); + /* Channel is enabled on hcint handler exit */ + fiq_print(FIQDBG_INT, state, "HSISOOUT"); + return 1; +} + + +/** + * fiq_fsm_do_sof() - FSM start-of-frame interrupt handler + * @state: Pointer to the state struct passed from banked FIQ mode registers. + * @num_channels: set according to the DWC hardware configuration + * + * The SOF handler in FSM mode has two functions + * 1. Hold off SOF from causing schedule advancement in IRQ context if there's + * nothing to do + * 2. Advance certain FSM states that require either a microframe delay, or a microframe + * of holdoff. + * + * The second part is architecture-specific to mach-bcm2835 - + * a sane interrupt controller would have a mask register for ARM interrupt sources + * to be promoted to the nFIQ line, but it doesn't. Instead a single interrupt + * number (USB) can be enabled. This means that certain parts of the USB specification + * that require "wait a little while, then issue another packet" cannot be fulfilled with + * the timing granularity required to achieve optimal throughout. The workaround is to use + * the SOF "timer" (125uS) to perform this task. + */ +static int notrace noinline fiq_fsm_do_sof(struct fiq_state *state, int num_channels) +{ + hfnum_data_t hfnum = { .d32 = FIQ_READ(state->dwc_regs_base + HFNUM) }; + int n; + int kick_irq = 0; + + if ((hfnum.b.frnum & 0x7) == 1) { + /* We cannot issue csplits for transactions in the last frame past (n+1).1 + * Check to see if there are any transactions that are stale. + * Boot them out. + */ + for (n = 0; n < num_channels; n++) { + switch (state->channel[n].fsm) { + case FIQ_PER_CSPLIT_WAIT: + case FIQ_PER_CSPLIT_NYET1: + case FIQ_PER_CSPLIT_POLL: + case FIQ_PER_CSPLIT_LAST: + /* Check if we are no longer in the same full-speed frame. */ + if (((state->channel[n].expected_uframe & 0x3FFF) & ~0x7) < + (hfnum.b.frnum & ~0x7)) + state->channel[n].fsm = FIQ_PER_SPLIT_TIMEOUT; + break; + default: + break; + } + } + } + + for (n = 0; n < num_channels; n++) { + switch (state->channel[n].fsm) { + + case FIQ_NP_SSPLIT_RETRY: + case FIQ_NP_IN_CSPLIT_RETRY: + case FIQ_NP_OUT_CSPLIT_RETRY: + fiq_fsm_restart_channel(state, n, 0); + break; + + case FIQ_HS_ISOC_SLEEPING: + /* Is it time to wake this channel yet? */ + if (--state->channel[n].uframe_sleeps == 0) { + state->channel[n].fsm = FIQ_HS_ISOC_TURBO; + fiq_fsm_restart_channel(state, n, 0); + } + break; + + case FIQ_PER_SSPLIT_QUEUED: + if ((hfnum.b.frnum & 0x7) == 5) + break; + if(!fiq_fsm_tt_in_use(state, num_channels, n)) { + if (!fiq_fsm_too_late(state, n)) { + fiq_print(FIQDBG_INT, state, "SOF GO %01d", n); + fiq_fsm_restart_channel(state, n, 0); + state->channel[n].fsm = FIQ_PER_SSPLIT_STARTED; + } else { + /* Transaction cannot be started without risking a device babble error */ + state->channel[n].fsm = FIQ_PER_SPLIT_TIMEOUT; + state->haintmsk_saved.b2.chint &= ~(1 << n); + FIQ_WRITE(state->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCINTMSK, 0); + kick_irq |= 1; + } + } + break; + + case FIQ_PER_ISO_OUT_PENDING: + /* Ordinarily, this should be poked after the SSPLIT + * complete interrupt for a competing transfer on the same + * TT. Doesn't happen for aborted transactions though. + */ + if ((hfnum.b.frnum & 0x7) >= 5) + break; + if (!fiq_fsm_tt_in_use(state, num_channels, n)) { + /* Hardware bug. SOF can sometimes occur after the channel halt interrupt + * that caused this. + */ + fiq_fsm_restart_channel(state, n, 0); + fiq_print(FIQDBG_INT, state, "SOF ISOC"); + if (state->channel[n].nrpackets == 1) { + state->channel[n].fsm = FIQ_PER_ISO_OUT_LAST; + } else { + state->channel[n].fsm = FIQ_PER_ISO_OUT_ACTIVE; + } + } + break; + + case FIQ_PER_CSPLIT_WAIT: + /* we are guaranteed to be in this state if and only if the SSPLIT interrupt + * occurred when the bus transaction occurred. The SOF interrupt reversal bug + * will utterly bugger this up though. + */ + if (hfnum.b.frnum != state->channel[n].expected_uframe) { + fiq_print(FIQDBG_INT, state, "SOFCS %d ", n); + state->channel[n].fsm = FIQ_PER_CSPLIT_POLL; + fiq_fsm_restart_channel(state, n, 0); + fiq_fsm_start_next_periodic(state, num_channels); + + } + break; + + case FIQ_PER_SPLIT_TIMEOUT: + case FIQ_DEQUEUE_ISSUED: + /* Ugly: we have to force a HCD interrupt. + * Poke the mask for the channel in question. + * We will take a fake SOF because of this, but + * that's OK. + */ + state->haintmsk_saved.b2.chint &= ~(1 << n); + FIQ_WRITE(state->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCINTMSK, 0); + kick_irq |= 1; + break; + + default: + break; + } + } + + if (state->kick_np_queues || + dwc_frame_num_le(state->next_sched_frame, hfnum.b.frnum)) + kick_irq |= 1; + + return !kick_irq; +} + + +/** + * fiq_fsm_do_hcintr() - FSM host channel interrupt handler + * @state: Pointer to the FIQ state struct + * @num_channels: Number of channels as per hardware config + * @n: channel for which HAINT(i) was raised + * + * An important property is that only the CHHLT interrupt is unmasked. Unfortunately, AHBerr is as well. + */ +static int notrace noinline fiq_fsm_do_hcintr(struct fiq_state *state, int num_channels, int n) +{ + hcint_data_t hcint; + hcintmsk_data_t hcintmsk; + hcint_data_t hcint_probe; + hcchar_data_t hcchar; + int handled = 0; + int restart = 0; + int last_csplit = 0; + int start_next_periodic = 0; + struct fiq_channel_state *st = &state->channel[n]; + hfnum_data_t hfnum; + + hcint.d32 = FIQ_READ(state->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCINT); + hcintmsk.d32 = FIQ_READ(state->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCINTMSK); + hcint_probe.d32 = hcint.d32 & hcintmsk.d32; + + if (st->fsm != FIQ_PASSTHROUGH) { + fiq_print(FIQDBG_INT, state, "HC%01d ST%02d", n, st->fsm); + fiq_print(FIQDBG_INT, state, "%08x", hcint.d32); + } + + switch (st->fsm) { + + case FIQ_PASSTHROUGH: + case FIQ_DEQUEUE_ISSUED: + /* doesn't belong to us, kick it upstairs */ + break; + + case FIQ_PASSTHROUGH_ERRORSTATE: + /* We are here to emulate the error recovery mechanism of the dwc HCD. + * Several interrupts are unmasked if a previous transaction failed - it's + * death for the FIQ to attempt to handle them as the channel isn't halted. + * Emulate what the HCD does in this situation: mask and continue. + * The FSM has no other state setup so this has to be handled out-of-band. + */ + fiq_print(FIQDBG_ERR, state, "ERRST %02d", n); + if (hcint_probe.b.nak || hcint_probe.b.ack || hcint_probe.b.datatglerr) { + fiq_print(FIQDBG_ERR, state, "RESET %02d", n); + /* In some random cases we can get a NAK interrupt coincident with a Xacterr + * interrupt, after the device has disappeared. + */ + if (!hcint.b.xacterr) + st->nr_errors = 0; + hcintmsk.b.nak = 0; + hcintmsk.b.ack = 0; + hcintmsk.b.datatglerr = 0; + FIQ_WRITE(state->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCINTMSK, hcintmsk.d32); + return 1; + } + if (hcint_probe.b.chhltd) { + fiq_print(FIQDBG_ERR, state, "CHHLT %02d", n); + fiq_print(FIQDBG_ERR, state, "%08x", hcint.d32); + return 0; + } + break; + + /* Non-periodic state groups */ + case FIQ_NP_SSPLIT_STARTED: + case FIQ_NP_SSPLIT_RETRY: + /* Got a HCINT for a NP SSPLIT. Expected ACK / NAK / fail */ + if (hcint.b.ack) { + /* SSPLIT complete. For OUT, the data has been sent. For IN, the LS transaction + * will start shortly. SOF needs to kick the transaction to prevent a NYET flood. + */ + if(st->hcchar_copy.b.epdir == 1) + st->fsm = FIQ_NP_IN_CSPLIT_RETRY; + else + st->fsm = FIQ_NP_OUT_CSPLIT_RETRY; + st->nr_errors = 0; + handled = 1; + fiq_fsm_setup_csplit(state, n); + } else if (hcint.b.nak) { + // No buffer space in TT. Retry on a uframe boundary. + fiq_fsm_reload_hcdma(state, n); + st->fsm = FIQ_NP_SSPLIT_RETRY; + handled = 1; + } else if (hcint.b.xacterr) { + // The only other one we care about is xacterr. This implies HS bus error - retry. + st->nr_errors++; + if(st->hcchar_copy.b.epdir == 0) + fiq_fsm_reload_hcdma(state, n); + st->fsm = FIQ_NP_SSPLIT_RETRY; + if (st->nr_errors >= 3) { + st->fsm = FIQ_NP_SPLIT_HS_ABORTED; + } else { + handled = 1; + restart = 1; + } + } else { + st->fsm = FIQ_NP_SPLIT_LS_ABORTED; + handled = 0; + restart = 0; + } + break; + + case FIQ_NP_IN_CSPLIT_RETRY: + /* Received a CSPLIT done interrupt. + * Expected Data/NAK/STALL/NYET for IN. + */ + if (hcint.b.xfercomp) { + /* For IN, data is present. */ + st->fsm = FIQ_NP_SPLIT_DONE; + } else if (hcint.b.nak) { + /* no endpoint data. Punt it upstairs */ + st->fsm = FIQ_NP_SPLIT_DONE; + } else if (hcint.b.nyet) { + /* CSPLIT NYET - retry on a uframe boundary. */ + handled = 1; + st->nr_errors = 0; + } else if (hcint.b.datatglerr) { + /* data toggle errors do not set the xfercomp bit. */ + st->fsm = FIQ_NP_SPLIT_LS_ABORTED; + } else if (hcint.b.xacterr) { + /* HS error. Retry immediate */ + st->fsm = FIQ_NP_IN_CSPLIT_RETRY; + st->nr_errors++; + if (st->nr_errors >= 3) { + st->fsm = FIQ_NP_SPLIT_HS_ABORTED; + } else { + handled = 1; + restart = 1; + } + } else if (hcint.b.stall || hcint.b.bblerr) { + /* A STALL implies either a LS bus error or a genuine STALL. */ + st->fsm = FIQ_NP_SPLIT_LS_ABORTED; + } else { + /* Hardware bug. It's possible in some cases to + * get a channel halt with nothing else set when + * the response was a NYET. Treat as local 3-strikes retry. + */ + hcint_data_t hcint_test = hcint; + hcint_test.b.chhltd = 0; + if (!hcint_test.d32) { + st->nr_errors++; + if (st->nr_errors >= 3) { + st->fsm = FIQ_NP_SPLIT_HS_ABORTED; + } else { + handled = 1; + } + } else { + /* Bail out if something unexpected happened */ + st->fsm = FIQ_NP_SPLIT_HS_ABORTED; + } + } + if (st->fsm != FIQ_NP_IN_CSPLIT_RETRY) { + fiq_fsm_restart_np_pending(state, num_channels, n); + } + break; + + case FIQ_NP_OUT_CSPLIT_RETRY: + /* Received a CSPLIT done interrupt. + * Expected ACK/NAK/STALL/NYET/XFERCOMP for OUT.*/ + if (hcint.b.xfercomp) { + st->fsm = FIQ_NP_SPLIT_DONE; + } else if (hcint.b.nak) { + // The HCD will implement the holdoff on frame boundaries. + st->fsm = FIQ_NP_SPLIT_DONE; + } else if (hcint.b.nyet) { + // Hub still processing. + st->fsm = FIQ_NP_OUT_CSPLIT_RETRY; + handled = 1; + st->nr_errors = 0; + //restart = 1; + } else if (hcint.b.xacterr) { + /* HS error. retry immediate */ + st->fsm = FIQ_NP_OUT_CSPLIT_RETRY; + st->nr_errors++; + if (st->nr_errors >= 3) { + st->fsm = FIQ_NP_SPLIT_HS_ABORTED; + } else { + handled = 1; + restart = 1; + } + } else if (hcint.b.stall) { + /* LS bus error or genuine stall */ + st->fsm = FIQ_NP_SPLIT_LS_ABORTED; + } else { + /* + * Hardware bug. It's possible in some cases to get a + * channel halt with nothing else set when the response was a NYET. + * Treat as local 3-strikes retry. + */ + hcint_data_t hcint_test = hcint; + hcint_test.b.chhltd = 0; + if (!hcint_test.d32) { + st->nr_errors++; + if (st->nr_errors >= 3) { + st->fsm = FIQ_NP_SPLIT_HS_ABORTED; + } else { + handled = 1; + } + } else { + // Something unexpected happened. AHBerror or babble perhaps. Let the IRQ deal with it. + st->fsm = FIQ_NP_SPLIT_HS_ABORTED; + } + } + if (st->fsm != FIQ_NP_OUT_CSPLIT_RETRY) { + fiq_fsm_restart_np_pending(state, num_channels, n); + } + break; + + /* Periodic split states (except isoc out) */ + case FIQ_PER_SSPLIT_STARTED: + /* Expect an ACK or failure for SSPLIT */ + if (hcint.b.ack) { + /* + * SSPLIT transfer complete interrupt - the generation of this interrupt is fraught with bugs. + * For a packet queued in microframe n-3 to appear in n-2, if the channel is enabled near the EOF1 + * point for microframe n-3, the packet will not appear on the bus until microframe n. + * Additionally, the generation of the actual interrupt is dodgy. For a packet appearing on the bus + * in microframe n, sometimes the interrupt is generated immediately. Sometimes, it appears in n+1 + * coincident with SOF for n+1. + * SOF is also buggy. It can sometimes be raised AFTER the first bus transaction has taken place. + * These appear to be caused by timing/clock crossing bugs within the core itself. + * State machine workaround. + */ + hfnum.d32 = FIQ_READ(state->dwc_regs_base + HFNUM); + hcchar.d32 = FIQ_READ(state->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCCHAR); + fiq_fsm_setup_csplit(state, n); + /* Poke the oddfrm bit. If we are equivalent, we received the interrupt at the correct + * time. If not, then we're in the next SOF. + */ + if ((hfnum.b.frnum & 0x1) == hcchar.b.oddfrm) { + fiq_print(FIQDBG_INT, state, "CSWAIT %01d", n); + st->expected_uframe = hfnum.b.frnum; + st->fsm = FIQ_PER_CSPLIT_WAIT; + } else { + fiq_print(FIQDBG_INT, state, "CSPOL %01d", n); + /* For isochronous IN endpoints, + * we need to hold off if we are expecting a lot of data */ + if (st->hcchar_copy.b.mps < DATA0_PID_HEURISTIC) { + start_next_periodic = 1; + } + /* Danger will robinson: we are in a broken state. If our first interrupt after + * this is a NYET, it will be delayed by 1 uframe and result in an unrecoverable + * lag. Unmask the NYET interrupt. + */ + st->expected_uframe = (hfnum.b.frnum + 1) & 0x3FFF; + st->fsm = FIQ_PER_CSPLIT_BROKEN_NYET1; + restart = 1; + } + handled = 1; + } else if (hcint.b.xacterr) { + /* 3-strikes retry is enabled, we have hit our max nr_errors */ + st->fsm = FIQ_PER_SPLIT_HS_ABORTED; + start_next_periodic = 1; + } else { + st->fsm = FIQ_PER_SPLIT_HS_ABORTED; + start_next_periodic = 1; + } + /* We can now queue the next isochronous OUT transaction, if one is pending. */ + if(fiq_fsm_tt_next_isoc(state, num_channels, n)) { + fiq_print(FIQDBG_INT, state, "NEXTISO "); + } + break; + + case FIQ_PER_CSPLIT_NYET1: + /* First CSPLIT attempt was a NYET. If we get a subsequent NYET, + * we are too late and the TT has dropped its CSPLIT fifo. + */ + hfnum.d32 = FIQ_READ(state->dwc_regs_base + HFNUM); + hcchar.d32 = FIQ_READ(state->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCCHAR); + start_next_periodic = 1; + if (hcint.b.nak) { + st->fsm = FIQ_PER_SPLIT_DONE; + } else if (hcint.b.xfercomp) { + fiq_increment_dma_buf(state, num_channels, n); + st->fsm = FIQ_PER_CSPLIT_POLL; + st->nr_errors = 0; + if (fiq_fsm_more_csplits(state, n, &last_csplit)) { + handled = 1; + restart = 1; + if (!last_csplit) + start_next_periodic = 0; + } else { + st->fsm = FIQ_PER_SPLIT_DONE; + } + } else if (hcint.b.nyet) { + /* Doh. Data lost. */ + st->fsm = FIQ_PER_SPLIT_NYET_ABORTED; + } else if (hcint.b.xacterr || hcint.b.stall || hcint.b.bblerr) { + st->fsm = FIQ_PER_SPLIT_LS_ABORTED; + } else { + st->fsm = FIQ_PER_SPLIT_HS_ABORTED; + } + break; + + case FIQ_PER_CSPLIT_BROKEN_NYET1: + /* + * we got here because our host channel is in the delayed-interrupt + * state and we cannot take a NYET interrupt any later than when it + * occurred. Disable then re-enable the channel if this happens to force + * CSPLITs to occur at the right time. + */ + hfnum.d32 = FIQ_READ(state->dwc_regs_base + HFNUM); + hcchar.d32 = FIQ_READ(state->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCCHAR); + fiq_print(FIQDBG_INT, state, "BROK: %01d ", n); + if (hcint.b.nak) { + st->fsm = FIQ_PER_SPLIT_DONE; + start_next_periodic = 1; + } else if (hcint.b.xfercomp) { + fiq_increment_dma_buf(state, num_channels, n); + if (fiq_fsm_more_csplits(state, n, &last_csplit)) { + st->fsm = FIQ_PER_CSPLIT_POLL; + handled = 1; + restart = 1; + start_next_periodic = 1; + /* Reload HCTSIZ for the next transfer */ + fiq_fsm_reload_hctsiz(state, n); + if (!last_csplit) + start_next_periodic = 0; + } else { + st->fsm = FIQ_PER_SPLIT_DONE; + } + } else if (hcint.b.nyet) { + st->fsm = FIQ_PER_SPLIT_NYET_ABORTED; + start_next_periodic = 1; + } else if (hcint.b.xacterr || hcint.b.stall || hcint.b.bblerr) { + /* Local 3-strikes retry is handled by the core. This is a ERR response.*/ + st->fsm = FIQ_PER_SPLIT_LS_ABORTED; + } else { + st->fsm = FIQ_PER_SPLIT_HS_ABORTED; + } + break; + + case FIQ_PER_CSPLIT_POLL: + hfnum.d32 = FIQ_READ(state->dwc_regs_base + HFNUM); + hcchar.d32 = FIQ_READ(state->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCCHAR); + start_next_periodic = 1; + if (hcint.b.nak) { + st->fsm = FIQ_PER_SPLIT_DONE; + } else if (hcint.b.xfercomp) { + fiq_increment_dma_buf(state, num_channels, n); + if (fiq_fsm_more_csplits(state, n, &last_csplit)) { + handled = 1; + restart = 1; + /* Reload HCTSIZ for the next transfer */ + fiq_fsm_reload_hctsiz(state, n); + if (!last_csplit) + start_next_periodic = 0; + } else { + st->fsm = FIQ_PER_SPLIT_DONE; + } + } else if (hcint.b.nyet) { + /* Are we a NYET after the first data packet? */ + if (st->nrpackets == 0) { + st->fsm = FIQ_PER_CSPLIT_NYET1; + handled = 1; + restart = 1; + } else { + /* We got a NYET when polling CSPLITs. Can happen + * if our heuristic fails, or if someone disables us + * for any significant length of time. + */ + if (st->nr_errors >= 3) { + st->fsm = FIQ_PER_SPLIT_NYET_ABORTED; + } else { + st->fsm = FIQ_PER_SPLIT_DONE; + } + } + } else if (hcint.b.xacterr || hcint.b.stall || hcint.b.bblerr) { + /* For xacterr, Local 3-strikes retry is handled by the core. This is a ERR response.*/ + st->fsm = FIQ_PER_SPLIT_LS_ABORTED; + } else { + st->fsm = FIQ_PER_SPLIT_HS_ABORTED; + } + break; + + case FIQ_HS_ISOC_TURBO: + if (fiq_fsm_update_hs_isoc(state, n, hcint)) { + /* more transactions to come */ + handled = 1; + fiq_print(FIQDBG_INT, state, "HSISO M "); + /* For strided transfers, put ourselves to sleep */ + if (st->hs_isoc_info.stride > 1) { + st->uframe_sleeps = st->hs_isoc_info.stride - 1; + st->fsm = FIQ_HS_ISOC_SLEEPING; + } else { + restart = 1; + } + } else { + st->fsm = FIQ_HS_ISOC_DONE; + fiq_print(FIQDBG_INT, state, "HSISO F "); + } + break; + + case FIQ_HS_ISOC_ABORTED: + /* This abort is called by the driver rewriting the state mid-transaction + * which allows the dequeue mechanism to work more effectively. + */ + break; + + case FIQ_PER_ISO_OUT_ACTIVE: + if (hcint.b.ack) { + if(fiq_iso_out_advance(state, num_channels, n)) { + /* last OUT transfer */ + st->fsm = FIQ_PER_ISO_OUT_LAST; + /* + * Assuming the periodic FIFO in the dwc core + * actually does its job properly, we can queue + * the next ssplit now and in theory, the wire + * transactions will be in-order. + */ + // No it doesn't. It appears to process requests in host channel order. + //start_next_periodic = 1; + } + handled = 1; + restart = 1; + } else { + /* + * Isochronous transactions carry on regardless. Log the error + * and continue. + */ + //explode += 1; + st->nr_errors++; + if(fiq_iso_out_advance(state, num_channels, n)) { + st->fsm = FIQ_PER_ISO_OUT_LAST; + //start_next_periodic = 1; + } + handled = 1; + restart = 1; + } + break; + + case FIQ_PER_ISO_OUT_LAST: + if (hcint.b.ack) { + /* All done here */ + st->fsm = FIQ_PER_ISO_OUT_DONE; + } else { + st->fsm = FIQ_PER_ISO_OUT_DONE; + st->nr_errors++; + } + start_next_periodic = 1; + break; + + case FIQ_PER_SPLIT_TIMEOUT: + /* SOF kicked us because we overran. */ + start_next_periodic = 1; + break; + + default: + break; + } + + if (handled) { + FIQ_WRITE(state->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCINT, hcint.d32); + } else { + /* Copy the regs into the state so the IRQ knows what to do */ + st->hcint_copy.d32 = hcint.d32; + } + + if (restart) { + /* Restart always implies handled. */ + if (restart == 2) { + /* For complete-split INs, the show must go on. + * Force a channel restart */ + fiq_fsm_restart_channel(state, n, 1); + } else { + fiq_fsm_restart_channel(state, n, 0); + } + } + if (start_next_periodic) { + fiq_fsm_start_next_periodic(state, num_channels); + } + if (st->fsm != FIQ_PASSTHROUGH) { + fiq_print(FIQDBG_INT, state, "FSMOUT%02d", st->fsm); + } + + return handled; +} + + +/** + * dwc_otg_fiq_fsm() - Flying State Machine (monster) FIQ + * @state: pointer to state struct passed from the banked FIQ mode registers. + * @num_channels: set according to the DWC hardware configuration + * @dma: pointer to DMA bounce buffers for split transaction slots + * + * The FSM FIQ performs the low-level tasks that normally would be performed by the microcode + * inside an EHCI or similar host controller regarding split transactions. The DWC core + * interrupts each and every time a split transaction packet is received or sent successfully. + * This results in either an interrupt storm when everything is working "properly", or + * the interrupt latency of the system in general breaks time-sensitive periodic split + * transactions. Pushing the low-level, but relatively easy state machine work into the FIQ + * solves these problems. + * + * Return: void + */ +void notrace dwc_otg_fiq_fsm(struct fiq_state *state, int num_channels) +{ + gintsts_data_t gintsts, gintsts_handled; + gintmsk_data_t gintmsk; + //hfnum_data_t hfnum; + haint_data_t haint, haint_handled; + haintmsk_data_t haintmsk; + int kick_irq = 0; + + /* Ensure peripheral reads issued prior to FIQ entry are complete */ + dsb(sy); + + gintsts_handled.d32 = 0; + haint_handled.d32 = 0; + + fiq_fsm_spin_lock(&state->lock); + gintsts.d32 = FIQ_READ(state->dwc_regs_base + GINTSTS); + gintmsk.d32 = FIQ_READ(state->dwc_regs_base + GINTMSK); + gintsts.d32 &= gintmsk.d32; + + if (gintsts.b.sofintr) { + /* For FSM mode, SOF is required to keep the state machine advance for + * certain stages of the periodic pipeline. It's death to mask this + * interrupt in that case. + */ + + if (!fiq_fsm_do_sof(state, num_channels)) { + /* Kick IRQ once. Queue advancement means that all pending transactions + * will get serviced when the IRQ finally executes. + */ + if (state->gintmsk_saved.b.sofintr == 1) + kick_irq |= 1; + state->gintmsk_saved.b.sofintr = 0; + } + gintsts_handled.b.sofintr = 1; + } + + if (gintsts.b.hcintr) { + int i; + haint.d32 = FIQ_READ(state->dwc_regs_base + HAINT); + haintmsk.d32 = FIQ_READ(state->dwc_regs_base + HAINTMSK); + haint.d32 &= haintmsk.d32; + haint_handled.d32 = 0; + for (i=0; i<num_channels; i++) { + if (haint.b2.chint & (1 << i)) { + if(!fiq_fsm_do_hcintr(state, num_channels, i)) { + /* HCINT was not handled in FIQ + * HAINT is level-sensitive, leading to level-sensitive ginststs.b.hcint bit. + * Mask HAINT(i) but keep top-level hcint unmasked. + */ + state->haintmsk_saved.b2.chint &= ~(1 << i); + } else { + /* do_hcintr cleaned up after itself, but clear haint */ + haint_handled.b2.chint |= (1 << i); + } + } + } + + if (haint_handled.b2.chint) { + FIQ_WRITE(state->dwc_regs_base + HAINT, haint_handled.d32); + } + + if (haintmsk.d32 != (haintmsk.d32 & state->haintmsk_saved.d32)) { + /* + * This is necessary to avoid multiple retriggers of the MPHI in the case + * where interrupts are held off and HCINTs start to pile up. + * Only wake up the IRQ if a new interrupt came in, was not handled and was + * masked. + */ + haintmsk.d32 &= state->haintmsk_saved.d32; + FIQ_WRITE(state->dwc_regs_base + HAINTMSK, haintmsk.d32); + kick_irq |= 1; + } + /* Top-Level interrupt - always handled because it's level-sensitive */ + gintsts_handled.b.hcintr = 1; + } + + + /* Clear the bits in the saved register that were not handled but were triggered. */ + state->gintmsk_saved.d32 &= ~(gintsts.d32 & ~gintsts_handled.d32); + + /* FIQ didn't handle something - mask has changed - write new mask */ + if (gintmsk.d32 != (gintmsk.d32 & state->gintmsk_saved.d32)) { + gintmsk.d32 &= state->gintmsk_saved.d32; + gintmsk.b.sofintr = 1; + FIQ_WRITE(state->dwc_regs_base + GINTMSK, gintmsk.d32); +// fiq_print(FIQDBG_INT, state, "KICKGINT"); +// fiq_print(FIQDBG_INT, state, "%08x", gintmsk.d32); +// fiq_print(FIQDBG_INT, state, "%08x", state->gintmsk_saved.d32); + kick_irq |= 1; + } + + if (gintsts_handled.d32) { + /* Only applies to edge-sensitive bits in GINTSTS */ + FIQ_WRITE(state->dwc_regs_base + GINTSTS, gintsts_handled.d32); + } + + /* We got an interrupt, didn't handle it. */ + if (kick_irq) { + state->mphi_int_count++; + if (state->mphi_regs.swirq_set) { + FIQ_WRITE(state->mphi_regs.swirq_set, 1); + } else { + FIQ_WRITE(state->mphi_regs.outdda, state->dummy_send_dma); + FIQ_WRITE(state->mphi_regs.outddb, (1<<29)); + } + + } + state->fiq_done++; + mb(); + fiq_fsm_spin_unlock(&state->lock); +} + + +/** + * dwc_otg_fiq_nop() - FIQ "lite" + * @state: pointer to state struct passed from the banked FIQ mode registers. + * + * The "nop" handler does not intervene on any interrupts other than SOF. + * It is limited in scope to deciding at each SOF if the IRQ SOF handler (which deals + * with non-periodic/periodic queues) needs to be kicked. + * + * This is done to hold off the SOF interrupt, which occurs at a rate of 8000 per second. + * + * Return: void + */ +void notrace dwc_otg_fiq_nop(struct fiq_state *state) +{ + gintsts_data_t gintsts, gintsts_handled; + gintmsk_data_t gintmsk; + hfnum_data_t hfnum; + + /* Ensure peripheral reads issued prior to FIQ entry are complete */ + dsb(sy); + + fiq_fsm_spin_lock(&state->lock); + hfnum.d32 = FIQ_READ(state->dwc_regs_base + HFNUM); + gintsts.d32 = FIQ_READ(state->dwc_regs_base + GINTSTS); + gintmsk.d32 = FIQ_READ(state->dwc_regs_base + GINTMSK); + gintsts.d32 &= gintmsk.d32; + gintsts_handled.d32 = 0; + + if (gintsts.b.sofintr) { + if (!state->kick_np_queues && + dwc_frame_num_gt(state->next_sched_frame, hfnum.b.frnum)) { + /* SOF handled, no work to do, just ACK interrupt */ + gintsts_handled.b.sofintr = 1; + } else { + /* Kick IRQ */ + state->gintmsk_saved.b.sofintr = 0; + } + } + + /* Reset handled interrupts */ + if(gintsts_handled.d32) { + FIQ_WRITE(state->dwc_regs_base + GINTSTS, gintsts_handled.d32); + } + + /* Clear the bits in the saved register that were not handled but were triggered. */ + state->gintmsk_saved.d32 &= ~(gintsts.d32 & ~gintsts_handled.d32); + + /* We got an interrupt, didn't handle it and want to mask it */ + if (~(state->gintmsk_saved.d32)) { + state->mphi_int_count++; + gintmsk.d32 &= state->gintmsk_saved.d32; + FIQ_WRITE(state->dwc_regs_base + GINTMSK, gintmsk.d32); + if (state->mphi_regs.swirq_set) { + FIQ_WRITE(state->mphi_regs.swirq_set, 1); + } else { + /* Force a clear before another dummy send */ + FIQ_WRITE(state->mphi_regs.intstat, (1<<29)); + FIQ_WRITE(state->mphi_regs.outdda, state->dummy_send_dma); + FIQ_WRITE(state->mphi_regs.outddb, (1<<29)); + } + } + state->fiq_done++; + mb(); + fiq_fsm_spin_unlock(&state->lock); +} diff --git a/drivers/usb/host/dwc_otg/dwc_otg_fiq_fsm.h b/drivers/usb/host/dwc_otg/dwc_otg_fiq_fsm.h new file mode 100644 index 00000000000000..8b080b7882fb23 --- /dev/null +++ b/drivers/usb/host/dwc_otg/dwc_otg_fiq_fsm.h @@ -0,0 +1,395 @@ +/* + * dwc_otg_fiq_fsm.h - Finite state machine FIQ header definitions + * + * Copyright (c) 2013 Raspberry Pi Foundation + * + * Author: Jonathan Bell <jonathan@raspberrypi.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Raspberry Pi nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * This FIQ implements functionality that performs split transactions on + * the dwc_otg hardware without any outside intervention. A split transaction + * is "queued" by nominating a specific host channel to perform the entirety + * of a split transaction. This FIQ will then perform the microframe-precise + * scheduling required in each phase of the transaction until completion. + * + * The FIQ functionality has been surgically implanted into the Synopsys + * vendor-provided driver. + * + */ + +#ifndef DWC_OTG_FIQ_FSM_H_ +#define DWC_OTG_FIQ_FSM_H_ + +#include "dwc_otg_regs.h" +#include "dwc_otg_cil.h" +#include "dwc_otg_hcd.h" +#include <linux/kernel.h> +#include <linux/irqflags.h> +#include <linux/string.h> +#include <asm/barrier.h> + +#if 0 +#define FLAME_ON(x) \ +do { \ + int gpioreg; \ + \ + gpioreg = readl(__io_address(0x20200000+0x8)); \ + gpioreg &= ~(7 << (x-20)*3); \ + gpioreg |= 0x1 << (x-20)*3; \ + writel(gpioreg, __io_address(0x20200000+0x8)); \ + \ + writel(1<<x, __io_address(0x20200000+(0x1C))); \ +} while (0) + +#define FLAME_OFF(x) \ +do { \ + writel(1<<x, __io_address(0x20200000+(0x28))); \ +} while (0) +#else +#define FLAME_ON(x) do { } while (0) +#define FLAME_OFF(X) do { } while (0) +#endif + +/* This is a quick-and-dirty arch-specific register read/write. We know that + * writes to a peripheral on BCM2835 will always arrive in-order, also that + * reads and writes are executed in-order therefore the need for memory barriers + * is obviated if we're only talking to USB. + */ +#define FIQ_WRITE(_addr_,_data_) (*(volatile unsigned int *) (_addr_) = (_data_)) +#define FIQ_READ(_addr_) (*(volatile unsigned int *) (_addr_)) + +/* FIQ-ified register definitions. Offsets are from dwc_regs_base. */ +#define GINTSTS 0x014 +#define GINTMSK 0x018 +/* Debug register. Poll the top of the received packets FIFO. */ +#define GRXSTSR 0x01C +#define HFNUM 0x408 +#define HAINT 0x414 +#define HAINTMSK 0x418 +#define HPRT0 0x440 + +/* HC_regs start from an offset of 0x500 */ +#define HC_START 0x500 +#define HC_OFFSET 0x020 + +#define HC_DMA 0x14 + +#define HCCHAR 0x00 +#define HCSPLT 0x04 +#define HCINT 0x08 +#define HCINTMSK 0x0C +#define HCTSIZ 0x10 + +#define ISOC_XACTPOS_ALL 0b11 +#define ISOC_XACTPOS_BEGIN 0b10 +#define ISOC_XACTPOS_MID 0b00 +#define ISOC_XACTPOS_END 0b01 + +#define DWC_PID_DATA2 0b01 +#define DWC_PID_MDATA 0b11 +#define DWC_PID_DATA1 0b10 +#define DWC_PID_DATA0 0b00 + +typedef struct { + volatile void* base; + volatile void* ctrl; + volatile void* outdda; + volatile void* outddb; + volatile void* intstat; + volatile void* swirq_set; + volatile void* swirq_clr; +} mphi_regs_t; + +enum fiq_debug_level { + FIQDBG_SCHED = (1 << 0), + FIQDBG_INT = (1 << 1), + FIQDBG_ERR = (1 << 2), + FIQDBG_PORTHUB = (1 << 3), +}; + +#ifdef CONFIG_ARM64 + +typedef spinlock_t fiq_lock_t; + +#else + +typedef struct { + union { + uint32_t slock; + struct _tickets { + uint16_t owner; + uint16_t next; + } tickets; + }; +} fiq_lock_t; + +#endif + +struct fiq_state; + +extern void _fiq_print (enum fiq_debug_level dbg_lvl, volatile struct fiq_state *state, char *fmt, ...); +#if 0 +#define fiq_print _fiq_print +#else +#define fiq_print(x, y, ...) +#endif + +extern bool fiq_enable, fiq_fsm_enable; +extern ushort nak_holdoff; + +/** + * enum fiq_fsm_state - The FIQ FSM states. + * + * This is the "core" of the FIQ FSM. Broadly, the FSM states follow the + * USB2.0 specification for host responses to various transaction states. + * There are modifications to this host state machine because of a variety of + * quirks and limitations in the dwc_otg hardware. + * + * The fsm state is also used to communicate back to the driver on completion of + * a split transaction. The end states are used in conjunction with the interrupts + * raised by the final transaction. + */ +enum fiq_fsm_state { + /* FIQ isn't enabled for this host channel */ + FIQ_PASSTHROUGH = 0, + /* For the first interrupt received for this channel, + * the FIQ has to ack any interrupts indicating success. */ + FIQ_PASSTHROUGH_ERRORSTATE = 31, + /* Nonperiodic state groups */ + FIQ_NP_SSPLIT_STARTED = 1, + FIQ_NP_SSPLIT_RETRY = 2, + /* TT contention - working around hub bugs */ + FIQ_NP_SSPLIT_PENDING = 33, + FIQ_NP_OUT_CSPLIT_RETRY = 3, + FIQ_NP_IN_CSPLIT_RETRY = 4, + FIQ_NP_SPLIT_DONE = 5, + FIQ_NP_SPLIT_LS_ABORTED = 6, + /* This differentiates a HS transaction error from a LS one + * (handling the hub state is different) */ + FIQ_NP_SPLIT_HS_ABORTED = 7, + + /* Periodic state groups */ + /* Periodic transactions are either started directly by the IRQ handler + * or deferred if the TT is already in use. + */ + FIQ_PER_SSPLIT_QUEUED = 8, + FIQ_PER_SSPLIT_STARTED = 9, + FIQ_PER_SSPLIT_LAST = 10, + + + FIQ_PER_ISO_OUT_PENDING = 11, + FIQ_PER_ISO_OUT_ACTIVE = 12, + FIQ_PER_ISO_OUT_LAST = 13, + FIQ_PER_ISO_OUT_DONE = 27, + + FIQ_PER_CSPLIT_WAIT = 14, + FIQ_PER_CSPLIT_NYET1 = 15, + FIQ_PER_CSPLIT_BROKEN_NYET1 = 28, + FIQ_PER_CSPLIT_NYET_FAFF = 29, + /* For multiple CSPLITs (large isoc IN, or delayed interrupt) */ + FIQ_PER_CSPLIT_POLL = 16, + /* The last CSPLIT for a transaction has been issued, differentiates + * for the state machine to queue the next packet. + */ + FIQ_PER_CSPLIT_LAST = 17, + + FIQ_PER_SPLIT_DONE = 18, + FIQ_PER_SPLIT_LS_ABORTED = 19, + FIQ_PER_SPLIT_HS_ABORTED = 20, + FIQ_PER_SPLIT_NYET_ABORTED = 21, + /* Frame rollover has occurred without the transaction finishing. */ + FIQ_PER_SPLIT_TIMEOUT = 22, + + /* FIQ-accelerated HS Isochronous state groups */ + FIQ_HS_ISOC_TURBO = 23, + /* For interval > 1, SOF wakes up the isochronous FSM */ + FIQ_HS_ISOC_SLEEPING = 24, + FIQ_HS_ISOC_DONE = 25, + FIQ_HS_ISOC_ABORTED = 26, + FIQ_DEQUEUE_ISSUED = 30, + FIQ_TEST = 32, +}; + +struct fiq_stack { + int magic1; + uint8_t stack[2048]; + int magic2; +}; + + +/** + * struct fiq_dma_info - DMA bounce buffer utilisation information (per-channel) + * @index: Number of slots reported used for IN transactions / number of slots + * transmitted for an OUT transaction + * @slot_len[6]: Number of actual transfer bytes in each slot (255 if unused) + * + * Split transaction transfers can have variable length depending on other bus + * traffic. The OTG core DMA engine requires 4-byte aligned addresses therefore + * each transaction needs a guaranteed aligned address. A maximum of 6 split transfers + * can happen per-frame. + */ +struct fiq_dma_info { + u8 index; + u8 slot_len[6]; +}; + +struct fiq_split_dma_slot { + u8 buf[188]; +} __attribute__((packed)); + +struct fiq_dma_channel { + struct fiq_split_dma_slot index[6]; +} __attribute__((packed)); + +/** + * struct fiq_hs_isoc_info - USB2.0 isochronous data + * @iso_frame: Pointer to the array of OTG URB iso_frame_descs. + * @nrframes: Total length of iso_frame_desc array + * @index: Current index (FIQ-maintained) + * @stride: Interval in uframes between HS isoc transactions + */ +struct fiq_hs_isoc_info { + struct dwc_otg_hcd_iso_packet_desc *iso_desc; + unsigned int nrframes; + unsigned int index; + unsigned int stride; +}; + +/** + * struct fiq_channel_state - FIQ state machine storage + * @fsm: Current state of the channel as understood by the FIQ + * @nr_errors: Number of transaction errors on this split-transaction + * @hub_addr: SSPLIT/CSPLIT destination hub + * @port_addr: SSPLIT/CSPLIT destination port - always 1 if single TT hub + * @nrpackets: For isoc OUT, the number of split-OUT packets to transmit. For + * split-IN, number of CSPLIT data packets that were received. + * @hcchar_copy: + * @hcsplt_copy: + * @hcintmsk_copy: + * @hctsiz_copy: Copies of the host channel registers. + * For use as scratch, or for returning state. + * + * The fiq_channel_state is state storage between interrupts for a host channel. The + * FSM state is stored here. Members of this structure must only be set up by the + * driver prior to enabling the FIQ for this host channel, and not touched until the FIQ + * has updated the state to either a COMPLETE state group or ABORT state group. + */ + +struct fiq_channel_state { + enum fiq_fsm_state fsm; + unsigned int nr_errors; + unsigned int hub_addr; + unsigned int port_addr; + /* Hardware bug workaround: sometimes channel halt interrupts are + * delayed until the next SOF. Keep track of when we expected to get interrupted. */ + unsigned int expected_uframe; + /* number of uframes remaining (for interval > 1 HS isoc transfers) before next transfer */ + unsigned int uframe_sleeps; + /* in/out for communicating number of dma buffers used, or number of ISOC to do */ + unsigned int nrpackets; + struct fiq_dma_info dma_info; + struct fiq_hs_isoc_info hs_isoc_info; + /* Copies of HC registers - in/out communication from/to IRQ handler + * and for ease of channel setup. A bit of mungeing is performed - for + * example the hctsiz.b.maxp is _always_ the max packet size of the endpoint. + */ + hcchar_data_t hcchar_copy; + hcsplt_data_t hcsplt_copy; + hcint_data_t hcint_copy; + hcintmsk_data_t hcintmsk_copy; + hctsiz_data_t hctsiz_copy; + hcdma_data_t hcdma_copy; +}; + +/** + * struct fiq_state - top-level FIQ state machine storage + * @mphi_regs: virtual address of the MPHI peripheral register file + * @dwc_regs_base: virtual address of the base of the DWC core register file + * @dma_base: physical address for the base of the DMA bounce buffers + * @dummy_send: Scratch area for sending a fake message to the MPHI peripheral + * @gintmsk_saved: Top-level mask of interrupts that the FIQ has not handled. + * Used for determining which interrupts fired to set off the IRQ handler. + * @haintmsk_saved: Mask of interrupts from host channels that the FIQ did not handle internally. + * @np_count: Non-periodic transactions in the active queue + * @np_sent: Count of non-periodic transactions that have completed + * @next_sched_frame: For periodic transactions handled by the driver's SOF-driven queuing mechanism, + * this is the next frame on which a SOF interrupt is required. Used to hold off + * passing SOF through to the driver until necessary. + * @channel[n]: Per-channel FIQ state. Allocated during init depending on the number of host + * channels configured into the core logic. + * + * This is passed as the first argument to the dwc_otg_fiq_fsm top-level FIQ handler from the asm stub. + * It contains top-level state information. + */ +struct fiq_state { + fiq_lock_t lock; + mphi_regs_t mphi_regs; + void *dwc_regs_base; + dma_addr_t dma_base; + struct fiq_dma_channel *fiq_dmab; + void *dummy_send; + dma_addr_t dummy_send_dma; + gintmsk_data_t gintmsk_saved; + haintmsk_data_t haintmsk_saved; + int mphi_int_count; + unsigned int fiq_done; + unsigned int kick_np_queues; + unsigned int next_sched_frame; +#ifdef FIQ_DEBUG + char * buffer; + unsigned int bufsiz; +#endif + struct fiq_channel_state channel[]; +}; + +#ifdef CONFIG_ARM64 + +#ifdef local_fiq_enable +#undef local_fiq_enable +#endif + +#ifdef local_fiq_disable +#undef local_fiq_disable +#endif + +extern void local_fiq_enable(void); + +extern void local_fiq_disable(void); + +#endif + +extern void fiq_fsm_spin_lock(fiq_lock_t *lock); + +extern void fiq_fsm_spin_unlock(fiq_lock_t *lock); + +extern int fiq_fsm_too_late(struct fiq_state *st, int n); + +extern int fiq_fsm_tt_in_use(struct fiq_state *st, int num_channels, int n); + +extern void dwc_otg_fiq_fsm(struct fiq_state *state, int num_channels); + +extern void dwc_otg_fiq_nop(struct fiq_state *state); + +#endif /* DWC_OTG_FIQ_FSM_H_ */ diff --git a/drivers/usb/host/dwc_otg/dwc_otg_fiq_stub.S b/drivers/usb/host/dwc_otg/dwc_otg_fiq_stub.S new file mode 100644 index 00000000000000..ffa8d21bc61e89 --- /dev/null +++ b/drivers/usb/host/dwc_otg/dwc_otg_fiq_stub.S @@ -0,0 +1,80 @@ +/* + * dwc_otg_fiq_fsm.S - assembly stub for the FSM FIQ + * + * Copyright (c) 2013 Raspberry Pi Foundation + * + * Author: Jonathan Bell <jonathan@raspberrypi.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Raspberry Pi nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +#include <asm/assembler.h> +#include <linux/linkage.h> + + +.text + +.global _dwc_otg_fiq_stub_end; + +/** + * _dwc_otg_fiq_stub() - entry copied to the FIQ vector page to allow + * a C-style function call with arguments from the FIQ banked registers. + * r0 = &hcd->fiq_state + * r1 = &hcd->num_channels + * r2 = &hcd->dma_buffers + * Tramples: r0, r1, r2, r4, fp, ip + */ + +ENTRY(_dwc_otg_fiq_stub) + /* Stash unbanked regs - SP will have been set up for us */ + mov ip, sp; + stmdb sp!, {r0-r12, lr}; +#ifdef FIQ_DEBUG + // Cycle profiling - read cycle counter at start + mrc p15, 0, r5, c15, c12, 1; +#endif + /* r11 = fp, don't trample it */ + mov r4, fp; + /* set EABI frame size */ + sub fp, ip, #512; + + /* for fiq NOP mode - just need state */ + mov r0, r8; + /* r9 = num_channels */ + mov r1, r9; + /* r10 = struct *dma_bufs */ +// mov r2, r10; + + /* r4 = &fiq_c_function */ + blx r4; +#ifdef FIQ_DEBUG + mrc p15, 0, r4, c15, c12, 1; + subs r5, r5, r4; + // r5 is now the cycle count time for executing the FIQ. Store it somewhere? +#endif + ldmia sp!, {r0-r12, lr}; + subs pc, lr, #4; +_dwc_otg_fiq_stub_end: +END(_dwc_otg_fiq_stub) diff --git a/drivers/usb/host/dwc_otg/dwc_otg_hcd.c b/drivers/usb/host/dwc_otg/dwc_otg_hcd.c new file mode 100644 index 00000000000000..2ee688acf171ca --- /dev/null +++ b/drivers/usb/host/dwc_otg/dwc_otg_hcd.c @@ -0,0 +1,4366 @@ + +/* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_hcd.c $ + * $Revision: #104 $ + * $Date: 2011/10/24 $ + * $Change: 1871159 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ +#ifndef DWC_DEVICE_ONLY + +/** @file + * This file implements HCD Core. All code in this file is portable and doesn't + * use any OS specific functions. + * Interface provided by HCD Core is defined in <code><hcd_if.h></code> + * header file. + */ + +#include <linux/usb.h> +#include <linux/usb/hcd.h> + +#include "dwc_otg_hcd.h" +#include "dwc_otg_regs.h" +#include "dwc_otg_fiq_fsm.h" + +extern bool microframe_schedule; +extern uint16_t fiq_fsm_mask, nak_holdoff; + +//#define DEBUG_HOST_CHANNELS +#ifdef DEBUG_HOST_CHANNELS +static int last_sel_trans_num_per_scheduled = 0; +static int last_sel_trans_num_nonper_scheduled = 0; +static int last_sel_trans_num_avail_hc_at_start = 0; +static int last_sel_trans_num_avail_hc_at_end = 0; +#endif /* DEBUG_HOST_CHANNELS */ + +static_assert(FIQ_PASSTHROUGH == 0); + +dwc_otg_hcd_t *dwc_otg_hcd_alloc_hcd(void) +{ + return DWC_ALLOC(sizeof(dwc_otg_hcd_t)); +} + +/** + * Connection timeout function. An OTG host is required to display a + * message if the device does not connect within 10 seconds. + */ +static void dwc_otg_hcd_connect_timeout(void *ptr) +{ + DWC_DEBUGPL(DBG_HCDV, "%s(%p)\n", __func__, ptr); + DWC_PRINTF("Connect Timeout\n"); + __DWC_ERROR("Device Not Connected/Responding\n"); +} + +#if defined(DEBUG) +static void dump_channel_info(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh) +{ + if (qh->channel != NULL) { + dwc_hc_t *hc = qh->channel; + dwc_list_link_t *item; + dwc_otg_qh_t *qh_item; + int num_channels = hcd->core_if->core_params->host_channels; + int i; + + dwc_otg_hc_regs_t *hc_regs; + hcchar_data_t hcchar; + hcsplt_data_t hcsplt; + hctsiz_data_t hctsiz; + uint32_t hcdma; + + hc_regs = hcd->core_if->host_if->hc_regs[hc->hc_num]; + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + hcsplt.d32 = DWC_READ_REG32(&hc_regs->hcsplt); + hctsiz.d32 = DWC_READ_REG32(&hc_regs->hctsiz); + hcdma = DWC_READ_REG32(&hc_regs->hcdma); + + DWC_PRINTF(" Assigned to channel %p:\n", hc); + DWC_PRINTF(" hcchar 0x%08x, hcsplt 0x%08x\n", hcchar.d32, + hcsplt.d32); + DWC_PRINTF(" hctsiz 0x%08x, hcdma 0x%08x\n", hctsiz.d32, + hcdma); + DWC_PRINTF(" dev_addr: %d, ep_num: %d, ep_is_in: %d\n", + hc->dev_addr, hc->ep_num, hc->ep_is_in); + DWC_PRINTF(" ep_type: %d\n", hc->ep_type); + DWC_PRINTF(" max_packet: %d\n", hc->max_packet); + DWC_PRINTF(" data_pid_start: %d\n", hc->data_pid_start); + DWC_PRINTF(" xfer_started: %d\n", hc->xfer_started); + DWC_PRINTF(" halt_status: %d\n", hc->halt_status); + DWC_PRINTF(" xfer_buff: %p\n", hc->xfer_buff); + DWC_PRINTF(" xfer_len: %d\n", hc->xfer_len); + DWC_PRINTF(" qh: %p\n", hc->qh); + DWC_PRINTF(" NP inactive sched:\n"); + DWC_LIST_FOREACH(item, &hcd->non_periodic_sched_inactive) { + qh_item = + DWC_LIST_ENTRY(item, dwc_otg_qh_t, qh_list_entry); + DWC_PRINTF(" %p\n", qh_item); + } + DWC_PRINTF(" NP active sched:\n"); + DWC_LIST_FOREACH(item, &hcd->non_periodic_sched_active) { + qh_item = + DWC_LIST_ENTRY(item, dwc_otg_qh_t, qh_list_entry); + DWC_PRINTF(" %p\n", qh_item); + } + DWC_PRINTF(" Channels: \n"); + for (i = 0; i < num_channels; i++) { + dwc_hc_t *hc = hcd->hc_ptr_array[i]; + DWC_PRINTF(" %2d: %p\n", i, hc); + } + } +} +#else +#define dump_channel_info(hcd, qh) +#endif /* DEBUG */ + +/** + * Work queue function for starting the HCD when A-Cable is connected. + * The hcd_start() must be called in a process context. + */ +static void hcd_start_func(void *_vp) +{ + dwc_otg_hcd_t *hcd = (dwc_otg_hcd_t *) _vp; + + DWC_DEBUGPL(DBG_HCDV, "%s() %p\n", __func__, hcd); + if (hcd) { + hcd->fops->start(hcd); + } +} + +static void del_xfer_timers(dwc_otg_hcd_t * hcd) +{ +#ifdef DEBUG + int i; + int num_channels = hcd->core_if->core_params->host_channels; + for (i = 0; i < num_channels; i++) { + DWC_TIMER_CANCEL(hcd->core_if->hc_xfer_timer[i]); + } +#endif +} + +static void del_timers(dwc_otg_hcd_t * hcd) +{ + del_xfer_timers(hcd); + DWC_TIMER_CANCEL(hcd->conn_timer); +} + +/** + * Processes all the URBs in a single list of QHs. Completes them with + * -ESHUTDOWN and frees the QTD. + */ +static void kill_urbs_in_qh_list(dwc_otg_hcd_t * hcd, dwc_list_link_t * qh_list) +{ + dwc_list_link_t *qh_item, *qh_tmp; + dwc_otg_qh_t *qh; + dwc_otg_qtd_t *qtd, *qtd_tmp; + int quiesced = 0; + + DWC_LIST_FOREACH_SAFE(qh_item, qh_tmp, qh_list) { + qh = DWC_LIST_ENTRY(qh_item, dwc_otg_qh_t, qh_list_entry); + DWC_CIRCLEQ_FOREACH_SAFE(qtd, qtd_tmp, + &qh->qtd_list, qtd_list_entry) { + qtd = DWC_CIRCLEQ_FIRST(&qh->qtd_list); + if (qtd->urb != NULL) { + hcd->fops->complete(hcd, qtd->urb->priv, + qtd->urb, -DWC_E_SHUTDOWN); + dwc_otg_hcd_qtd_remove_and_free(hcd, qtd, qh); + } + + } + if(qh->channel) { + int n = qh->channel->hc_num; + /* Using hcchar.chen == 1 is not a reliable test. + * It is possible that the channel has already halted + * but not yet been through the IRQ handler. + */ + if (fiq_fsm_enable && (hcd->fiq_state->channel[qh->channel->hc_num].fsm != FIQ_PASSTHROUGH)) { + qh->channel->halt_status = DWC_OTG_HC_XFER_URB_DEQUEUE; + qh->channel->halt_pending = 1; + if (hcd->fiq_state->channel[n].fsm == FIQ_HS_ISOC_TURBO || + hcd->fiq_state->channel[n].fsm == FIQ_HS_ISOC_SLEEPING) + hcd->fiq_state->channel[n].fsm = FIQ_HS_ISOC_ABORTED; + /* We're called from disconnect callback or in the middle of freeing the HCD here, + * so FIQ is disabled, top-level interrupts masked and we're holding the spinlock. + * No further URBs will be submitted, but wait 1 microframe for any previously + * submitted periodic DMA to finish. + */ + if (!quiesced) { + udelay(125); + quiesced = 1; + } + } else { + dwc_otg_hc_halt(hcd->core_if, qh->channel, + DWC_OTG_HC_XFER_URB_DEQUEUE); + } + qh->channel = NULL; + } + dwc_otg_hcd_qh_remove(hcd, qh); + } +} + +/** + * Responds with an error status of ESHUTDOWN to all URBs in the non-periodic + * and periodic schedules. The QTD associated with each URB is removed from + * the schedule and freed. This function may be called when a disconnect is + * detected or when the HCD is being stopped. + */ +static void kill_all_urbs(dwc_otg_hcd_t * hcd) +{ + kill_urbs_in_qh_list(hcd, &hcd->non_periodic_sched_inactive); + kill_urbs_in_qh_list(hcd, &hcd->non_periodic_sched_active); + kill_urbs_in_qh_list(hcd, &hcd->periodic_sched_inactive); + kill_urbs_in_qh_list(hcd, &hcd->periodic_sched_ready); + kill_urbs_in_qh_list(hcd, &hcd->periodic_sched_assigned); + kill_urbs_in_qh_list(hcd, &hcd->periodic_sched_queued); +} + +/** + * Start the connection timer. An OTG host is required to display a + * message if the device does not connect within 10 seconds. The + * timer is deleted if a port connect interrupt occurs before the + * timer expires. + */ +static void dwc_otg_hcd_start_connect_timer(dwc_otg_hcd_t * hcd) +{ + DWC_TIMER_SCHEDULE(hcd->conn_timer, 10000 /* 10 secs */ ); +} + +/** + * HCD Callback function for disconnect of the HCD. + * + * @param p void pointer to the <code>struct usb_hcd</code> + */ +static int32_t dwc_otg_hcd_session_start_cb(void *p) +{ + dwc_otg_hcd_t *dwc_otg_hcd; + DWC_DEBUGPL(DBG_HCDV, "%s(%p)\n", __func__, p); + dwc_otg_hcd = p; + dwc_otg_hcd_start_connect_timer(dwc_otg_hcd); + return 1; +} + +/** + * HCD Callback function for starting the HCD when A-Cable is + * connected. + * + * @param p void pointer to the <code>struct usb_hcd</code> + */ +static int32_t dwc_otg_hcd_start_cb(void *p) +{ + dwc_otg_hcd_t *dwc_otg_hcd = p; + dwc_otg_core_if_t *core_if; + hprt0_data_t hprt0; + + core_if = dwc_otg_hcd->core_if; + + if (core_if->op_state == B_HOST) { + /* + * Reset the port. During a HNP mode switch the reset + * needs to occur within 1ms and have a duration of at + * least 50ms. + */ + hprt0.d32 = dwc_otg_read_hprt0(core_if); + hprt0.b.prtrst = 1; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + } + DWC_WORKQ_SCHEDULE_DELAYED(core_if->wq_otg, + hcd_start_func, dwc_otg_hcd, 50, + "start hcd"); + + return 1; +} + +/** + * HCD Callback function for disconnect of the HCD. + * + * @param p void pointer to the <code>struct usb_hcd</code> + */ +static int32_t dwc_otg_hcd_disconnect_cb(void *p) +{ + gintsts_data_t intr; + dwc_otg_hcd_t *dwc_otg_hcd = p; + + DWC_SPINLOCK(dwc_otg_hcd->lock); + /* + * Set status flags for the hub driver. + */ + dwc_otg_hcd->flags.b.port_connect_status_change = 1; + dwc_otg_hcd->flags.b.port_connect_status = 0; + if(fiq_enable) { + local_fiq_disable(); + fiq_fsm_spin_lock(&dwc_otg_hcd->fiq_state->lock); + } + /* + * Shutdown any transfers in process by clearing the Tx FIFO Empty + * interrupt mask and status bits and disabling subsequent host + * channel interrupts. + */ + intr.d32 = 0; + intr.b.nptxfempty = 1; + intr.b.ptxfempty = 1; + intr.b.hcintr = 1; + DWC_MODIFY_REG32(&dwc_otg_hcd->core_if->core_global_regs->gintmsk, + intr.d32, 0); + DWC_MODIFY_REG32(&dwc_otg_hcd->core_if->core_global_regs->gintsts, + intr.d32, 0); + + del_timers(dwc_otg_hcd); + + /* + * Turn off the vbus power only if the core has transitioned to device + * mode. If still in host mode, need to keep power on to detect a + * reconnection. + */ + if (dwc_otg_is_device_mode(dwc_otg_hcd->core_if)) { + if (dwc_otg_hcd->core_if->op_state != A_SUSPEND) { + hprt0_data_t hprt0 = {.d32 = 0 }; + DWC_PRINTF("Disconnect: PortPower off\n"); + hprt0.b.prtpwr = 0; + DWC_WRITE_REG32(dwc_otg_hcd->core_if->host_if->hprt0, + hprt0.d32); + } + + dwc_otg_disable_host_interrupts(dwc_otg_hcd->core_if); + } + + /* Respond with an error status to all URBs in the schedule. */ + kill_all_urbs(dwc_otg_hcd); + + if (dwc_otg_is_host_mode(dwc_otg_hcd->core_if)) { + /* Clean up any host channels that were in use. */ + int num_channels; + int i; + dwc_hc_t *channel; + dwc_otg_hc_regs_t *hc_regs; + hcchar_data_t hcchar; + + num_channels = dwc_otg_hcd->core_if->core_params->host_channels; + + if (!dwc_otg_hcd->core_if->dma_enable) { + /* Flush out any channel requests in slave mode. */ + for (i = 0; i < num_channels; i++) { + channel = dwc_otg_hcd->hc_ptr_array[i]; + if (DWC_CIRCLEQ_EMPTY_ENTRY + (channel, hc_list_entry)) { + hc_regs = + dwc_otg_hcd->core_if-> + host_if->hc_regs[i]; + hcchar.d32 = + DWC_READ_REG32(&hc_regs->hcchar); + if (hcchar.b.chen) { + hcchar.b.chen = 0; + hcchar.b.chdis = 1; + hcchar.b.epdir = 0; + DWC_WRITE_REG32 + (&hc_regs->hcchar, + hcchar.d32); + } + } + } + } + + if(fiq_fsm_enable) { + for(i=0; i < 128; i++) { + dwc_otg_hcd->hub_port[i] = 0; + } + } + } + + if(fiq_enable) { + fiq_fsm_spin_unlock(&dwc_otg_hcd->fiq_state->lock); + local_fiq_enable(); + } + + if (dwc_otg_hcd->fops->disconnect) { + dwc_otg_hcd->fops->disconnect(dwc_otg_hcd); + } + + DWC_SPINUNLOCK(dwc_otg_hcd->lock); + return 1; +} + +/** + * HCD Callback function for stopping the HCD. + * + * @param p void pointer to the <code>struct usb_hcd</code> + */ +static int32_t dwc_otg_hcd_stop_cb(void *p) +{ + dwc_otg_hcd_t *dwc_otg_hcd = p; + + DWC_DEBUGPL(DBG_HCDV, "%s(%p)\n", __func__, p); + dwc_otg_hcd_stop(dwc_otg_hcd); + return 1; +} + +#ifdef CONFIG_USB_DWC_OTG_LPM +/** + * HCD Callback function for sleep of HCD. + * + * @param p void pointer to the <code>struct usb_hcd</code> + */ +static int dwc_otg_hcd_sleep_cb(void *p) +{ + dwc_otg_hcd_t *hcd = p; + + dwc_otg_hcd_free_hc_from_lpm(hcd); + + return 0; +} +#endif + + +/** + * HCD Callback function for Remote Wakeup. + * + * @param p void pointer to the <code>struct usb_hcd</code> + */ +static int dwc_otg_hcd_rem_wakeup_cb(void *p) +{ + dwc_otg_hcd_t *hcd = p; + + if (hcd->core_if->lx_state == DWC_OTG_L2) { + hcd->flags.b.port_suspend_change = 1; + } +#ifdef CONFIG_USB_DWC_OTG_LPM + else { + hcd->flags.b.port_l1_change = 1; + } +#endif + return 0; +} + +/** + * Halts the DWC_otg host mode operations in a clean manner. USB transfers are + * stopped. + */ +void dwc_otg_hcd_stop(dwc_otg_hcd_t * hcd) +{ + hprt0_data_t hprt0 = {.d32 = 0 }; + + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD STOP\n"); + + /* + * The root hub should be disconnected before this function is called. + * The disconnect will clear the QTD lists (via ..._hcd_urb_dequeue) + * and the QH lists (via ..._hcd_endpoint_disable). + */ + + /* Turn off all host-specific interrupts. */ + dwc_otg_disable_host_interrupts(hcd->core_if); + + /* Turn off the vbus power */ + DWC_PRINTF("PortPower off\n"); + hprt0.b.prtpwr = 0; + DWC_WRITE_REG32(hcd->core_if->host_if->hprt0, hprt0.d32); + dwc_mdelay(1); +} + +int dwc_otg_hcd_urb_enqueue(dwc_otg_hcd_t * hcd, + dwc_otg_hcd_urb_t * dwc_otg_urb, void **ep_handle, + int atomic_alloc) +{ + int retval = 0; + uint8_t needs_scheduling = 0; + dwc_otg_transaction_type_e tr_type; + dwc_otg_qtd_t *qtd; + gintmsk_data_t intr_mask = {.d32 = 0 }; + hprt0_data_t hprt0 = { .d32 = 0 }; + +#ifdef DEBUG /* integrity checks (Broadcom) */ + if (NULL == hcd->core_if) { + DWC_ERROR("**** DWC OTG HCD URB Enqueue - HCD has NULL core_if\n"); + /* No longer connected. */ + return -DWC_E_INVALID; + } +#endif + if (!hcd->flags.b.port_connect_status) { + /* No longer connected. */ + DWC_ERROR("Not connected\n"); + return -DWC_E_NO_DEVICE; + } + + /* Some core configurations cannot support LS traffic on a FS root port */ + if ((hcd->fops->speed(hcd, dwc_otg_urb->priv) == USB_SPEED_LOW) && + (hcd->core_if->hwcfg2.b.fs_phy_type == 1) && + (hcd->core_if->hwcfg2.b.hs_phy_type == 1)) { + hprt0.d32 = DWC_READ_REG32(hcd->core_if->host_if->hprt0); + if (hprt0.b.prtspd == DWC_HPRT0_PRTSPD_FULL_SPEED) { + return -DWC_E_NO_DEVICE; + } + } + + qtd = dwc_otg_hcd_qtd_create(dwc_otg_urb, atomic_alloc); + if (qtd == NULL) { + DWC_ERROR("DWC OTG HCD URB Enqueue failed creating QTD\n"); + return -DWC_E_NO_MEMORY; + } +#ifdef DEBUG /* integrity checks (Broadcom) */ + if (qtd->urb == NULL) { + DWC_ERROR("**** DWC OTG HCD URB Enqueue created QTD with no URBs\n"); + return -DWC_E_NO_MEMORY; + } + if (qtd->urb->priv == NULL) { + DWC_ERROR("**** DWC OTG HCD URB Enqueue created QTD URB with no URB handle\n"); + return -DWC_E_NO_MEMORY; + } +#endif + intr_mask.d32 = DWC_READ_REG32(&hcd->core_if->core_global_regs->gintmsk); + if(!intr_mask.b.sofintr || fiq_enable) needs_scheduling = 1; + if((((dwc_otg_qh_t *)ep_handle)->ep_type == UE_BULK) && !(qtd->urb->flags & URB_GIVEBACK_ASAP)) + /* Do not schedule SG transactions until qtd has URB_GIVEBACK_ASAP set */ + needs_scheduling = 0; + + retval = dwc_otg_hcd_qtd_add(qtd, hcd, (dwc_otg_qh_t **) ep_handle, atomic_alloc); + // creates a new queue in ep_handle if it doesn't exist already + if (retval < 0) { + DWC_ERROR("DWC OTG HCD URB Enqueue failed adding QTD. " + "Error status %d\n", retval); + dwc_otg_hcd_qtd_free(qtd); + return retval; + } + + if(needs_scheduling) { + tr_type = dwc_otg_hcd_select_transactions(hcd); + if (tr_type != DWC_OTG_TRANSACTION_NONE) { + dwc_otg_hcd_queue_transactions(hcd, tr_type); + } + } + return retval; +} + +int dwc_otg_hcd_urb_dequeue(dwc_otg_hcd_t * hcd, + dwc_otg_hcd_urb_t * dwc_otg_urb) +{ + dwc_otg_qh_t *qh; + dwc_otg_qtd_t *urb_qtd; + BUG_ON(!hcd); + BUG_ON(!dwc_otg_urb); + +#ifdef DEBUG /* integrity checks (Broadcom) */ + + if (hcd == NULL) { + DWC_ERROR("**** DWC OTG HCD URB Dequeue has NULL HCD\n"); + return -DWC_E_INVALID; + } + if (dwc_otg_urb == NULL) { + DWC_ERROR("**** DWC OTG HCD URB Dequeue has NULL URB\n"); + return -DWC_E_INVALID; + } + if (dwc_otg_urb->qtd == NULL) { + DWC_ERROR("**** DWC OTG HCD URB Dequeue with NULL QTD\n"); + return -DWC_E_INVALID; + } + urb_qtd = dwc_otg_urb->qtd; + BUG_ON(!urb_qtd); + if (urb_qtd->qh == NULL) { + DWC_ERROR("**** DWC OTG HCD URB Dequeue with QTD with NULL Q handler\n"); + return -DWC_E_INVALID; + } +#else + urb_qtd = dwc_otg_urb->qtd; + BUG_ON(!urb_qtd); +#endif + qh = urb_qtd->qh; + BUG_ON(!qh); + if (CHK_DEBUG_LEVEL(DBG_HCDV | DBG_HCD_URB)) { + if (urb_qtd->in_process) { + dump_channel_info(hcd, qh); + } + } +#ifdef DEBUG /* integrity checks (Broadcom) */ + if (hcd->core_if == NULL) { + DWC_ERROR("**** DWC OTG HCD URB Dequeue HCD has NULL core_if\n"); + return -DWC_E_INVALID; + } +#endif + if (urb_qtd->in_process && qh->channel) { + /* The QTD is in process (it has been assigned to a channel). */ + if (hcd->flags.b.port_connect_status) { + int n = qh->channel->hc_num; + /* + * If still connected (i.e. in host mode), halt the + * channel so it can be used for other transfers. If + * no longer connected, the host registers can't be + * written to halt the channel since the core is in + * device mode. + */ + /* In FIQ FSM mode, we need to shut down carefully. + * The FIQ may attempt to restart a disabled channel */ + if (fiq_fsm_enable && (hcd->fiq_state->channel[n].fsm != FIQ_PASSTHROUGH)) { + int retries = 3; + int running = 0; + enum fiq_fsm_state state; + + local_fiq_disable(); + fiq_fsm_spin_lock(&hcd->fiq_state->lock); + qh->channel->halt_status = DWC_OTG_HC_XFER_URB_DEQUEUE; + qh->channel->halt_pending = 1; + if (hcd->fiq_state->channel[n].fsm == FIQ_HS_ISOC_TURBO || + hcd->fiq_state->channel[n].fsm == FIQ_HS_ISOC_SLEEPING) + hcd->fiq_state->channel[n].fsm = FIQ_HS_ISOC_ABORTED; + fiq_fsm_spin_unlock(&hcd->fiq_state->lock); + local_fiq_enable(); + + if (dwc_qh_is_non_per(qh)) { + do { + state = READ_ONCE(hcd->fiq_state->channel[n].fsm); + running = (state != FIQ_NP_SPLIT_DONE) && + (state != FIQ_NP_SPLIT_LS_ABORTED) && + (state != FIQ_NP_SPLIT_HS_ABORTED); + if (!running) + break; + udelay(125); + } while(--retries); + if (!retries) + DWC_WARN("Timed out waiting for FSM NP transfer to complete on %d", + qh->channel->hc_num); + } + } else { + dwc_otg_hc_halt(hcd->core_if, qh->channel, + DWC_OTG_HC_XFER_URB_DEQUEUE); + } + } + } + + /* + * Free the QTD and clean up the associated QH. Leave the QH in the + * schedule if it has any remaining QTDs. + */ + + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD URB Dequeue - " + "delete %sQueue handler\n", + hcd->core_if->dma_desc_enable?"DMA ":""); + if (!hcd->core_if->dma_desc_enable) { + uint8_t b = urb_qtd->in_process; + if (nak_holdoff && qh->do_split && dwc_qh_is_non_per(qh)) + qh->nak_frame = 0xFFFF; + dwc_otg_hcd_qtd_remove_and_free(hcd, urb_qtd, qh); + if (b) { + dwc_otg_hcd_qh_deactivate(hcd, qh, 0); + qh->channel = NULL; + } else if (DWC_CIRCLEQ_EMPTY(&qh->qtd_list)) { + dwc_otg_hcd_qh_remove(hcd, qh); + } + } else { + dwc_otg_hcd_qtd_remove_and_free(hcd, urb_qtd, qh); + } + return 0; +} + +int dwc_otg_hcd_endpoint_disable(dwc_otg_hcd_t * hcd, void *ep_handle, + int retry) +{ + dwc_otg_qh_t *qh = (dwc_otg_qh_t *) ep_handle; + int retval = 0; + dwc_irqflags_t flags; + + if (retry < 0) { + retval = -DWC_E_INVALID; + goto done; + } + + if (!qh) { + retval = -DWC_E_INVALID; + goto done; + } + + DWC_SPINLOCK_IRQSAVE(hcd->lock, &flags); + + while (!DWC_CIRCLEQ_EMPTY(&qh->qtd_list) && retry) { + DWC_SPINUNLOCK_IRQRESTORE(hcd->lock, flags); + retry--; + dwc_msleep(5); + DWC_SPINLOCK_IRQSAVE(hcd->lock, &flags); + } + + dwc_otg_hcd_qh_remove(hcd, qh); + + DWC_SPINUNLOCK_IRQRESTORE(hcd->lock, flags); + /* + * Split dwc_otg_hcd_qh_remove_and_free() into qh_remove + * and qh_free to prevent stack dump on DWC_DMA_FREE() with + * irq_disabled (spinlock_irqsave) in dwc_otg_hcd_desc_list_free() + * and dwc_otg_hcd_frame_list_alloc(). + */ + dwc_otg_hcd_qh_free(hcd, qh); + +done: + return retval; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30) +int dwc_otg_hcd_endpoint_reset(dwc_otg_hcd_t * hcd, void *ep_handle) +{ + int retval = 0; + dwc_otg_qh_t *qh = (dwc_otg_qh_t *) ep_handle; + if (!qh) + return -DWC_E_INVALID; + + qh->data_toggle = DWC_OTG_HC_PID_DATA0; + return retval; +} +#endif + +/** + * HCD Callback structure for handling mode switching. + */ +static dwc_otg_cil_callbacks_t hcd_cil_callbacks = { + .start = dwc_otg_hcd_start_cb, + .stop = dwc_otg_hcd_stop_cb, + .disconnect = dwc_otg_hcd_disconnect_cb, + .session_start = dwc_otg_hcd_session_start_cb, + .resume_wakeup = dwc_otg_hcd_rem_wakeup_cb, +#ifdef CONFIG_USB_DWC_OTG_LPM + .sleep = dwc_otg_hcd_sleep_cb, +#endif + .p = 0, +}; + +/** + * Reset tasklet function + */ +static void reset_tasklet_func(void *data) +{ + dwc_otg_hcd_t *dwc_otg_hcd = (dwc_otg_hcd_t *) data; + dwc_otg_core_if_t *core_if = dwc_otg_hcd->core_if; + hprt0_data_t hprt0; + + DWC_DEBUGPL(DBG_HCDV, "USB RESET tasklet called\n"); + + hprt0.d32 = dwc_otg_read_hprt0(core_if); + hprt0.b.prtrst = 1; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + dwc_mdelay(60); + + hprt0.b.prtrst = 0; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + dwc_otg_hcd->flags.b.port_reset_change = 1; +} + +static void completion_tasklet_func(void *ptr) +{ + dwc_otg_hcd_t *hcd = (dwc_otg_hcd_t *) ptr; + struct urb *urb; + urb_tq_entry_t *item; + dwc_irqflags_t flags; + + /* This could just be spin_lock_irq */ + DWC_SPINLOCK_IRQSAVE(hcd->lock, &flags); + while (!DWC_TAILQ_EMPTY(&hcd->completed_urb_list)) { + item = DWC_TAILQ_FIRST(&hcd->completed_urb_list); + urb = item->urb; + DWC_TAILQ_REMOVE(&hcd->completed_urb_list, item, + urb_tq_entries); + DWC_SPINUNLOCK_IRQRESTORE(hcd->lock, flags); + DWC_FREE(item); + + usb_hcd_giveback_urb(hcd->priv, urb, urb->status); + + + DWC_SPINLOCK_IRQSAVE(hcd->lock, &flags); + } + DWC_SPINUNLOCK_IRQRESTORE(hcd->lock, flags); + return; +} + +static void qh_list_free(dwc_otg_hcd_t * hcd, dwc_list_link_t * qh_list) +{ + dwc_list_link_t *item; + dwc_otg_qh_t *qh; + dwc_irqflags_t flags; + + if (!qh_list->next) { + /* The list hasn't been initialized yet. */ + return; + } + /* + * Hold spinlock here. Not needed in that case if bellow + * function is being called from ISR + */ + DWC_SPINLOCK_IRQSAVE(hcd->lock, &flags); + /* Ensure there are no QTDs or URBs left. */ + kill_urbs_in_qh_list(hcd, qh_list); + DWC_SPINUNLOCK_IRQRESTORE(hcd->lock, flags); + + DWC_LIST_FOREACH(item, qh_list) { + qh = DWC_LIST_ENTRY(item, dwc_otg_qh_t, qh_list_entry); + dwc_otg_hcd_qh_remove_and_free(hcd, qh); + } +} + +/** + * Exit from Hibernation if Host did not detect SRP from connected SRP capable + * Device during SRP time by host power up. + */ +#ifdef DWC_DEV_SRPCAP +static void dwc_otg_hcd_power_up(void *ptr) +{ + gpwrdn_data_t gpwrdn = {.d32 = 0 }; + dwc_otg_core_if_t *core_if = (dwc_otg_core_if_t *) ptr; + + DWC_PRINTF("%s called\n", __FUNCTION__); + + if (!core_if->hibernation_suspend) { + DWC_PRINTF("Already exited from Hibernation\n"); + return; + } + + /* Switch on the voltage to the core */ + gpwrdn.b.pwrdnswtch = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + dwc_udelay(10); + + /* Reset the core */ + gpwrdn.d32 = 0; + gpwrdn.b.pwrdnrstn = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + dwc_udelay(10); + + /* Disable power clamps */ + gpwrdn.d32 = 0; + gpwrdn.b.pwrdnclmp = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + + /* Remove reset the core signal */ + gpwrdn.d32 = 0; + gpwrdn.b.pwrdnrstn = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, gpwrdn.d32); + dwc_udelay(10); + + /* Disable PMU interrupt */ + gpwrdn.d32 = 0; + gpwrdn.b.pmuintsel = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + + core_if->hibernation_suspend = 0; + + /* Disable PMU */ + gpwrdn.d32 = 0; + gpwrdn.b.pmuactv = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + dwc_udelay(10); + + /* Enable VBUS */ + gpwrdn.d32 = 0; + gpwrdn.b.dis_vbus = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0); + + core_if->op_state = A_HOST; + dwc_otg_core_init(core_if); + dwc_otg_enable_global_interrupts(core_if); + cil_hcd_start(core_if); +} +#endif + +void dwc_otg_cleanup_fiq_channel(dwc_otg_hcd_t *hcd, uint32_t num) +{ + struct fiq_channel_state *st = &hcd->fiq_state->channel[num]; + struct fiq_dma_channel *split_dma = hcd->fiq_dmab; + int i; + + st->fsm = FIQ_PASSTHROUGH; + st->hcchar_copy.d32 = 0; + st->hcsplt_copy.d32 = 0; + st->hcint_copy.d32 = 0; + st->hcintmsk_copy.d32 = 0; + st->hctsiz_copy.d32 = 0; + st->hcdma_copy.d32 = 0; + st->nr_errors = 0; + st->hub_addr = 0; + st->port_addr = 0; + st->expected_uframe = 0; + st->nrpackets = 0; + st->dma_info.index = 0; + for (i = 0; i < 6; i++) + st->dma_info.slot_len[i] = 255; + st->hs_isoc_info.index = 0; + st->hs_isoc_info.iso_desc = NULL; + st->hs_isoc_info.nrframes = 0; + + DWC_MEMSET(&split_dma[num].index[0], 0x6b, 1128); +} + +/** + * Frees secondary storage associated with the dwc_otg_hcd structure contained + * in the struct usb_hcd field. + */ +static void dwc_otg_hcd_free(dwc_otg_hcd_t * dwc_otg_hcd) +{ + struct device *dev = dwc_otg_hcd_to_dev(dwc_otg_hcd); + int i; + + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD FREE\n"); + + del_timers(dwc_otg_hcd); + + /* Free memory for QH/QTD lists */ + qh_list_free(dwc_otg_hcd, &dwc_otg_hcd->non_periodic_sched_inactive); + qh_list_free(dwc_otg_hcd, &dwc_otg_hcd->non_periodic_sched_active); + qh_list_free(dwc_otg_hcd, &dwc_otg_hcd->periodic_sched_inactive); + qh_list_free(dwc_otg_hcd, &dwc_otg_hcd->periodic_sched_ready); + qh_list_free(dwc_otg_hcd, &dwc_otg_hcd->periodic_sched_assigned); + qh_list_free(dwc_otg_hcd, &dwc_otg_hcd->periodic_sched_queued); + + /* Free memory for the host channels. */ + for (i = 0; i < MAX_EPS_CHANNELS; i++) { + dwc_hc_t *hc = dwc_otg_hcd->hc_ptr_array[i]; + +#ifdef DEBUG + if (dwc_otg_hcd->core_if->hc_xfer_timer[i]) { + DWC_TIMER_FREE(dwc_otg_hcd->core_if->hc_xfer_timer[i]); + } +#endif + if (hc != NULL) { + DWC_DEBUGPL(DBG_HCDV, "HCD Free channel #%i, hc=%p\n", + i, hc); + DWC_FREE(hc); + } + } + + if (dwc_otg_hcd->core_if->dma_enable) { + if (dwc_otg_hcd->status_buf_dma) { + DWC_DMA_FREE(dev, DWC_OTG_HCD_STATUS_BUF_SIZE, + dwc_otg_hcd->status_buf, + dwc_otg_hcd->status_buf_dma); + } + } else if (dwc_otg_hcd->status_buf != NULL) { + DWC_FREE(dwc_otg_hcd->status_buf); + } + DWC_SPINLOCK_FREE(dwc_otg_hcd->lock); + /* Set core_if's lock pointer to NULL */ + dwc_otg_hcd->core_if->lock = NULL; + + DWC_TIMER_FREE(dwc_otg_hcd->conn_timer); + DWC_TASK_FREE(dwc_otg_hcd->reset_tasklet); + DWC_TASK_FREE(dwc_otg_hcd->completion_tasklet); + DWC_DMA_FREE(dev, 16, dwc_otg_hcd->fiq_state->dummy_send, + dwc_otg_hcd->fiq_state->dummy_send_dma); + DWC_FREE(dwc_otg_hcd->fiq_state); + +#ifdef DWC_DEV_SRPCAP + if (dwc_otg_hcd->core_if->power_down == 2 && + dwc_otg_hcd->core_if->pwron_timer) { + DWC_TIMER_FREE(dwc_otg_hcd->core_if->pwron_timer); + } +#endif + DWC_FREE(dwc_otg_hcd); +} + +int dwc_otg_hcd_init(dwc_otg_hcd_t * hcd, dwc_otg_core_if_t * core_if) +{ + struct device *dev = dwc_otg_hcd_to_dev(hcd); + int retval = 0; + int num_channels; + int i; + dwc_hc_t *channel; + +#if (defined(DWC_LINUX) && defined(CONFIG_DEBUG_SPINLOCK)) + DWC_SPINLOCK_ALLOC_LINUX_DEBUG(hcd->lock); +#else + hcd->lock = DWC_SPINLOCK_ALLOC(); +#endif + DWC_DEBUGPL(DBG_HCDV, "init of HCD %p given core_if %p\n", + hcd, core_if); + if (!hcd->lock) { + DWC_ERROR("Could not allocate lock for pcd"); + DWC_FREE(hcd); + retval = -DWC_E_NO_MEMORY; + goto out; + } + hcd->core_if = core_if; + + /* Register the HCD CIL Callbacks */ + dwc_otg_cil_register_hcd_callbacks(hcd->core_if, + &hcd_cil_callbacks, hcd); + + /* Initialize the non-periodic schedule. */ + DWC_LIST_INIT(&hcd->non_periodic_sched_inactive); + DWC_LIST_INIT(&hcd->non_periodic_sched_active); + + /* Initialize the periodic schedule. */ + DWC_LIST_INIT(&hcd->periodic_sched_inactive); + DWC_LIST_INIT(&hcd->periodic_sched_ready); + DWC_LIST_INIT(&hcd->periodic_sched_assigned); + DWC_LIST_INIT(&hcd->periodic_sched_queued); + DWC_TAILQ_INIT(&hcd->completed_urb_list); + /* + * Create a host channel descriptor for each host channel implemented + * in the controller. Initialize the channel descriptor array. + */ + DWC_CIRCLEQ_INIT(&hcd->free_hc_list); + num_channels = hcd->core_if->core_params->host_channels; + DWC_MEMSET(hcd->hc_ptr_array, 0, sizeof(hcd->hc_ptr_array)); + for (i = 0; i < num_channels; i++) { + channel = DWC_ALLOC(sizeof(dwc_hc_t)); + if (channel == NULL) { + retval = -DWC_E_NO_MEMORY; + DWC_ERROR("%s: host channel allocation failed\n", + __func__); + dwc_otg_hcd_free(hcd); + goto out; + } + channel->hc_num = i; + hcd->hc_ptr_array[i] = channel; +#ifdef DEBUG + hcd->core_if->hc_xfer_timer[i] = + DWC_TIMER_ALLOC("hc timer", hc_xfer_timeout, + &hcd->core_if->hc_xfer_info[i]); +#endif + DWC_DEBUGPL(DBG_HCDV, "HCD Added channel #%d, hc=%p\n", i, + channel); + } + + if (fiq_enable) { + hcd->fiq_state = DWC_ALLOC(sizeof(struct fiq_state) + (sizeof(struct fiq_channel_state) * num_channels)); + if (!hcd->fiq_state) { + retval = -DWC_E_NO_MEMORY; + DWC_ERROR("%s: cannot allocate fiq_state structure\n", __func__); + dwc_otg_hcd_free(hcd); + goto out; + } + DWC_MEMSET(hcd->fiq_state, 0, (sizeof(struct fiq_state) + (sizeof(struct fiq_channel_state) * num_channels))); + +#ifdef CONFIG_ARM64 + spin_lock_init(&hcd->fiq_state->lock); +#endif + + hcd->fiq_state->dummy_send = DWC_DMA_ALLOC_ATOMIC(dev, 16, + &hcd->fiq_state->dummy_send_dma); + + hcd->fiq_stack = DWC_ALLOC(sizeof(struct fiq_stack)); + if (!hcd->fiq_stack) { + retval = -DWC_E_NO_MEMORY; + DWC_ERROR("%s: cannot allocate fiq_stack structure\n", __func__); + dwc_otg_hcd_free(hcd); + goto out; + } + hcd->fiq_stack->magic1 = 0xDEADBEEF; + hcd->fiq_stack->magic2 = 0xD00DFEED; + hcd->fiq_state->gintmsk_saved.d32 = ~0; + hcd->fiq_state->haintmsk_saved.b2.chint = ~0; + + /* This bit is terrible and uses no API, but necessary. The FIQ has no concept of DMA pools + * (and if it did, would be a lot slower). This allocates a chunk of memory (~9kiB for 8 host channels) + * for use as transaction bounce buffers in a 2-D array. Our access into this chunk is done by some + * moderately readable array casts. + */ + hcd->fiq_dmab = DWC_DMA_ALLOC(dev, (sizeof(struct fiq_dma_channel) * num_channels), &hcd->fiq_state->dma_base); + DWC_WARN("FIQ DMA bounce buffers: virt = %px dma = %pad len=%zu", + hcd->fiq_dmab, &hcd->fiq_state->dma_base, + sizeof(struct fiq_dma_channel) * num_channels); + + DWC_MEMSET(hcd->fiq_dmab, 0x6b, 9024); + + /* pointer for debug in fiq_print */ + hcd->fiq_state->fiq_dmab = hcd->fiq_dmab; + if (fiq_fsm_enable) { + int i; + for (i=0; i < hcd->core_if->core_params->host_channels; i++) { + dwc_otg_cleanup_fiq_channel(hcd, i); + } + DWC_PRINTF("FIQ FSM acceleration enabled for :\n%s%s%s%s", + (fiq_fsm_mask & 0x1) ? "Non-periodic Split Transactions\n" : "", + (fiq_fsm_mask & 0x2) ? "Periodic Split Transactions\n" : "", + (fiq_fsm_mask & 0x4) ? "High-Speed Isochronous Endpoints\n" : "", + (fiq_fsm_mask & 0x8) ? "Interrupt/Control Split Transaction hack enabled\n" : ""); + } + } + + /* Initialize the Connection timeout timer. */ + hcd->conn_timer = DWC_TIMER_ALLOC("Connection timer", + dwc_otg_hcd_connect_timeout, 0); + + printk(KERN_DEBUG "dwc_otg: Microframe scheduler %s\n", microframe_schedule ? "enabled":"disabled"); + if (microframe_schedule) + init_hcd_usecs(hcd); + + /* Initialize reset tasklet. */ + hcd->reset_tasklet = DWC_TASK_ALLOC("reset_tasklet", reset_tasklet_func, hcd); + + hcd->completion_tasklet = DWC_TASK_ALLOC("completion_tasklet", + completion_tasklet_func, hcd); +#ifdef DWC_DEV_SRPCAP + if (hcd->core_if->power_down == 2) { + /* Initialize Power on timer for Host power up in case hibernation */ + hcd->core_if->pwron_timer = DWC_TIMER_ALLOC("PWRON TIMER", + dwc_otg_hcd_power_up, core_if); + } +#endif + + /* + * Allocate space for storing data on status transactions. Normally no + * data is sent, but this space acts as a bit bucket. This must be + * done after usb_add_hcd since that function allocates the DMA buffer + * pool. + */ + if (hcd->core_if->dma_enable) { + hcd->status_buf = + DWC_DMA_ALLOC(dev, DWC_OTG_HCD_STATUS_BUF_SIZE, + &hcd->status_buf_dma); + } else { + hcd->status_buf = DWC_ALLOC(DWC_OTG_HCD_STATUS_BUF_SIZE); + } + if (!hcd->status_buf) { + retval = -DWC_E_NO_MEMORY; + DWC_ERROR("%s: status_buf allocation failed\n", __func__); + dwc_otg_hcd_free(hcd); + goto out; + } + + hcd->otg_port = 1; + hcd->frame_list = NULL; + hcd->frame_list_dma = 0; + hcd->periodic_qh_count = 0; + + DWC_MEMSET(hcd->hub_port, 0, sizeof(hcd->hub_port)); +#ifdef FIQ_DEBUG + DWC_MEMSET(hcd->hub_port_alloc, -1, sizeof(hcd->hub_port_alloc)); +#endif + +out: + return retval; +} + +void dwc_otg_hcd_remove(dwc_otg_hcd_t * hcd) +{ + /* Turn off all host-specific interrupts. */ + dwc_otg_disable_host_interrupts(hcd->core_if); + + dwc_otg_hcd_free(hcd); +} + +/** + * Initializes dynamic portions of the DWC_otg HCD state. + */ +static void dwc_otg_hcd_reinit(dwc_otg_hcd_t * hcd) +{ + int num_channels; + int i; + dwc_hc_t *channel; + dwc_hc_t *channel_tmp; + + hcd->flags.d32 = 0; + + hcd->non_periodic_qh_ptr = &hcd->non_periodic_sched_active; + if (!microframe_schedule) { + hcd->non_periodic_channels = 0; + hcd->periodic_channels = 0; + } else { + hcd->available_host_channels = hcd->core_if->core_params->host_channels; + } + /* + * Put all channels in the free channel list and clean up channel + * states. + */ + DWC_CIRCLEQ_FOREACH_SAFE(channel, channel_tmp, + &hcd->free_hc_list, hc_list_entry) { + DWC_CIRCLEQ_REMOVE(&hcd->free_hc_list, channel, hc_list_entry); + } + + num_channels = hcd->core_if->core_params->host_channels; + for (i = 0; i < num_channels; i++) { + channel = hcd->hc_ptr_array[i]; + DWC_CIRCLEQ_INSERT_TAIL(&hcd->free_hc_list, channel, + hc_list_entry); + dwc_otg_hc_cleanup(hcd->core_if, channel); + } + + /* Initialize the DWC core for host mode operation. */ + dwc_otg_core_host_init(hcd->core_if); + + /* Set core_if's lock pointer to the hcd->lock */ + hcd->core_if->lock = hcd->lock; +} + +/** + * Assigns transactions from a QTD to a free host channel and initializes the + * host channel to perform the transactions. The host channel is removed from + * the free list. + * + * @param hcd The HCD state structure. + * @param qh Transactions from the first QTD for this QH are selected and + * assigned to a free host channel. + */ +static void assign_and_init_hc(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh) +{ + dwc_hc_t *hc; + dwc_otg_qtd_t *qtd; + dwc_otg_hcd_urb_t *urb; + void* ptr = NULL; + uint16_t wLength; + uint32_t intr_enable; + unsigned long flags; + gintmsk_data_t gintmsk = { .d32 = 0, }; + struct device *dev = dwc_otg_hcd_to_dev(hcd); + + qtd = DWC_CIRCLEQ_FIRST(&qh->qtd_list); + + urb = qtd->urb; + + DWC_DEBUGPL(DBG_HCDV, "%s(%p,%p) - urb %x, actual_length %d\n", __func__, hcd, qh, (unsigned int)urb, urb->actual_length); + + if (((urb->actual_length < 0) || (urb->actual_length > urb->length)) && !dwc_otg_hcd_is_pipe_in(&urb->pipe_info)) + urb->actual_length = urb->length; + + + hc = DWC_CIRCLEQ_FIRST(&hcd->free_hc_list); + + /* Remove the host channel from the free list. */ + DWC_CIRCLEQ_REMOVE_INIT(&hcd->free_hc_list, hc, hc_list_entry); + + qh->channel = hc; + + qtd->in_process = 1; + + /* + * Use usb_pipedevice to determine device address. This address is + * 0 before the SET_ADDRESS command and the correct address afterward. + */ + hc->dev_addr = dwc_otg_hcd_get_dev_addr(&urb->pipe_info); + hc->ep_num = dwc_otg_hcd_get_ep_num(&urb->pipe_info); + hc->speed = qh->dev_speed; + hc->max_packet = dwc_max_packet(qh->maxp); + + hc->xfer_started = 0; + hc->halt_status = DWC_OTG_HC_XFER_NO_HALT_STATUS; + hc->error_state = (qtd->error_count > 0); + hc->halt_on_queue = 0; + hc->halt_pending = 0; + hc->requests = 0; + + /* + * The following values may be modified in the transfer type section + * below. The xfer_len value may be reduced when the transfer is + * started to accommodate the max widths of the XferSize and PktCnt + * fields in the HCTSIZn register. + */ + + hc->ep_is_in = (dwc_otg_hcd_is_pipe_in(&urb->pipe_info) != 0); + if (hc->ep_is_in) { + hc->do_ping = 0; + } else { + hc->do_ping = qh->ping_state; + } + + hc->data_pid_start = qh->data_toggle; + hc->multi_count = 1; + + if (hcd->core_if->dma_enable) { + hc->xfer_buff = + (uint8_t *)(uintptr_t)urb->dma + urb->actual_length; + + /* For non-dword aligned case */ + if (((unsigned long)hc->xfer_buff & 0x3) + && !hcd->core_if->dma_desc_enable) { + ptr = (uint8_t *) urb->buf + urb->actual_length; + } + } else { + hc->xfer_buff = (uint8_t *) urb->buf + urb->actual_length; + } + hc->xfer_len = urb->length - urb->actual_length; + hc->xfer_count = 0; + + /* + * Set the split attributes + */ + hc->do_split = 0; + if (qh->do_split) { + uint32_t hub_addr, port_addr; + hc->do_split = 1; + hc->start_pkt_count = 1; + hc->xact_pos = qtd->isoc_split_pos; + /* We don't need to do complete splits anymore */ +// if(fiq_fsm_enable) + if (0) + hc->complete_split = qtd->complete_split = 0; + else + hc->complete_split = qtd->complete_split; + + hcd->fops->hub_info(hcd, urb->priv, &hub_addr, &port_addr); + hc->hub_addr = (uint8_t) hub_addr; + hc->port_addr = (uint8_t) port_addr; + } + + switch (dwc_otg_hcd_get_pipe_type(&urb->pipe_info)) { + case UE_CONTROL: + hc->ep_type = DWC_OTG_EP_TYPE_CONTROL; + switch (qtd->control_phase) { + case DWC_OTG_CONTROL_SETUP: + DWC_DEBUGPL(DBG_HCDV, " Control setup transaction\n"); + hc->do_ping = 0; + hc->ep_is_in = 0; + hc->data_pid_start = DWC_OTG_HC_PID_SETUP; + if (hcd->core_if->dma_enable) { + hc->xfer_buff = + (uint8_t *)(uintptr_t)urb->setup_dma; + } else { + hc->xfer_buff = (uint8_t *) urb->setup_packet; + } + hc->xfer_len = 8; + ptr = NULL; + break; + case DWC_OTG_CONTROL_DATA: + DWC_DEBUGPL(DBG_HCDV, " Control data transaction\n"); + /* + * Hardware bug: small IN packets with length < 4 + * cause a 4-byte write to memory. We can only catch + * the case where we know a short packet is going to be + * returned in a control transfer, as the length is + * specified in the setup packet. This is only an issue + * for drivers that insist on packing a device's various + * properties into a struct and querying them one at a + * time (uvcvideo). + * Force the use of align_buf so that the subsequent + * memcpy puts the right number of bytes in the URB's + * buffer. + */ + wLength = ((uint16_t *)urb->setup_packet)[3]; + #if 0 + if (hc->ep_is_in && wLength < 4) + ptr = hc->xfer_buff; + #endif + + hc->data_pid_start = qtd->data_toggle; + break; + case DWC_OTG_CONTROL_STATUS: + /* + * Direction is opposite of data direction or IN if no + * data. + */ + DWC_DEBUGPL(DBG_HCDV, " Control status transaction\n"); + if (urb->length == 0) { + hc->ep_is_in = 1; + } else { + hc->ep_is_in = + dwc_otg_hcd_is_pipe_out(&urb->pipe_info); + } + if (hc->ep_is_in) { + hc->do_ping = 0; + } + + hc->data_pid_start = DWC_OTG_HC_PID_DATA1; + + hc->xfer_len = 0; + if (hcd->core_if->dma_enable) { + hc->xfer_buff = (uint8_t *) + (uintptr_t)hcd->status_buf_dma; + } else { + hc->xfer_buff = (uint8_t *) hcd->status_buf; + } + ptr = NULL; + break; + } + break; + case UE_BULK: + hc->ep_type = DWC_OTG_EP_TYPE_BULK; + break; + case UE_INTERRUPT: + hc->ep_type = DWC_OTG_EP_TYPE_INTR; + break; + case UE_ISOCHRONOUS: + { + struct dwc_otg_hcd_iso_packet_desc *frame_desc; + + hc->ep_type = DWC_OTG_EP_TYPE_ISOC; + + if (hcd->core_if->dma_desc_enable) + break; + + frame_desc = &urb->iso_descs[qtd->isoc_frame_index]; + + frame_desc->status = 0; + + if (hcd->core_if->dma_enable) { + hc->xfer_buff = (uint8_t *)(uintptr_t)urb->dma; + } else { + hc->xfer_buff = (uint8_t *) urb->buf; + } + hc->xfer_buff += + frame_desc->offset + qtd->isoc_split_offset; + hc->xfer_len = + frame_desc->length - qtd->isoc_split_offset; + + /* For non-dword aligned buffers */ + if (((unsigned long)hc->xfer_buff & 0x3) + && hcd->core_if->dma_enable) { + ptr = + (uint8_t *) urb->buf + frame_desc->offset + + qtd->isoc_split_offset; + } else + ptr = NULL; + + if (hc->xact_pos == DWC_HCSPLIT_XACTPOS_ALL) { + if (hc->xfer_len <= 188) { + hc->xact_pos = DWC_HCSPLIT_XACTPOS_ALL; + } else { + hc->xact_pos = + DWC_HCSPLIT_XACTPOS_BEGIN; + } + } + } + break; + } + /* non DWORD-aligned buffer case */ + if (ptr) { + uint32_t buf_size; + if (hc->ep_type != DWC_OTG_EP_TYPE_ISOC) { + buf_size = hcd->core_if->core_params->max_transfer_size; + } else { + buf_size = 4096; + } + if (!qh->dw_align_buf) { + qh->dw_align_buf = DWC_DMA_ALLOC_ATOMIC(dev, buf_size, + &qh->dw_align_buf_dma); + if (!qh->dw_align_buf) { + DWC_ERROR + ("%s: Failed to allocate memory to handle " + "non-dword aligned buffer case\n", + __func__); + return; + } + } + if (!hc->ep_is_in) { + dwc_memcpy(qh->dw_align_buf, ptr, hc->xfer_len); + } + hc->align_buff = qh->dw_align_buf_dma; + } else { + hc->align_buff = 0; + } + + if (hc->ep_type == DWC_OTG_EP_TYPE_INTR || + hc->ep_type == DWC_OTG_EP_TYPE_ISOC) { + /* + * This value may be modified when the transfer is started to + * reflect the actual transfer length. + */ + hc->multi_count = dwc_hb_mult(qh->maxp); + } + + if (hcd->core_if->dma_desc_enable) + hc->desc_list_addr = qh->desc_list_dma; + + dwc_otg_hc_init(hcd->core_if, hc); + + local_irq_save(flags); + + if (fiq_enable) { + local_fiq_disable(); + fiq_fsm_spin_lock(&hcd->fiq_state->lock); + } + + /* Enable the top level host channel interrupt. */ + intr_enable = (1 << hc->hc_num); + DWC_MODIFY_REG32(&hcd->core_if->host_if->host_global_regs->haintmsk, 0, intr_enable); + + /* Make sure host channel interrupts are enabled. */ + gintmsk.b.hcintr = 1; + DWC_MODIFY_REG32(&hcd->core_if->core_global_regs->gintmsk, 0, gintmsk.d32); + + if (fiq_enable) { + fiq_fsm_spin_unlock(&hcd->fiq_state->lock); + local_fiq_enable(); + } + + local_irq_restore(flags); + hc->qh = qh; +} + + +/** + * fiq_fsm_transaction_suitable() - Test a QH for compatibility with the FIQ + * @hcd: Pointer to the dwc_otg_hcd struct + * @qh: pointer to the endpoint's queue head + * + * Transaction start/end control flow is grafted onto the existing dwc_otg + * mechanisms, to avoid spaghettifying the functions more than they already are. + * This function's eligibility check is altered by debug parameter. + * + * Returns: 0 for unsuitable, 1 implies the FIQ can be enabled for this transaction. + */ + +int fiq_fsm_transaction_suitable(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh) +{ + if (qh->do_split) { + switch (qh->ep_type) { + case UE_CONTROL: + case UE_BULK: + if (fiq_fsm_mask & (1 << 0)) + return 1; + break; + case UE_INTERRUPT: + case UE_ISOCHRONOUS: + if (fiq_fsm_mask & (1 << 1)) + return 1; + break; + default: + break; + } + } else if (qh->ep_type == UE_ISOCHRONOUS) { + if (fiq_fsm_mask & (1 << 2)) { + /* ISOCH support. We test for compatibility: + * - DWORD aligned buffers + * - Must be at least 2 transfers (otherwise pointless to use the FIQ) + * If yes, then the fsm enqueue function will handle the state machine setup. + */ + dwc_otg_qtd_t *qtd = DWC_CIRCLEQ_FIRST(&qh->qtd_list); + dwc_otg_hcd_urb_t *urb = qtd->urb; + dwc_dma_t ptr; + int i; + + if (urb->packet_count < 2) + return 0; + for (i = 0; i < urb->packet_count; i++) { + ptr = urb->dma + urb->iso_descs[i].offset; + if (ptr & 0x3) + return 0; + } + return 1; + } + } + return 0; +} + +/** + * fiq_fsm_setup_periodic_dma() - Set up DMA bounce buffers + * @hcd: Pointer to the dwc_otg_hcd struct + * @qh: Pointer to the endpoint's queue head + * + * Periodic split transactions are transmitted modulo 188 bytes. + * This necessitates slicing data up into buckets for isochronous out + * and fixing up the DMA address for all IN transfers. + * + * Returns 1 if the DMA bounce buffers have been used, 0 if the default + * HC buffer has been used. + */ +static int fiq_fsm_setup_periodic_dma(dwc_otg_hcd_t *hcd, struct fiq_channel_state *st, dwc_otg_qh_t *qh) + { + int frame_length, i = 0; + uint8_t *ptr = NULL; + dwc_hc_t *hc = qh->channel; + struct fiq_dma_channel *split_dma; + struct dwc_otg_hcd_iso_packet_desc *frame_desc; + + for (i = 0; i < 6; i++) { + st->dma_info.slot_len[i] = 255; + } + st->dma_info.index = 0; + i = 0; + if (hc->ep_is_in) { + /* + * Set dma_regs to bounce buffer. FIQ will update the + * state depending on transaction progress. + * Pointer arithmetic on hcd->fiq_state->dma_base (a dma_addr_t) + * to point it to the correct offset in the allocated buffers. + */ + split_dma = (struct fiq_dma_channel *) + (uintptr_t)hcd->fiq_state->dma_base; + st->hcdma_copy.d32 = lower_32_bits((uintptr_t) + &split_dma[hc->hc_num].index[0].buf[0]); + + /* Calculate the max number of CSPLITS such that the FIQ can time out + * a transaction if it fails. + */ + frame_length = st->hcchar_copy.b.mps; + do { + i++; + frame_length -= 188; + } while (frame_length >= 0); + st->nrpackets = i; + return 1; + } else { + if (qh->ep_type == UE_ISOCHRONOUS) { + + dwc_otg_qtd_t *qtd = DWC_CIRCLEQ_FIRST(&qh->qtd_list); + + frame_desc = &qtd->urb->iso_descs[qtd->isoc_frame_index]; + frame_length = frame_desc->length; + + /* Virtual address for bounce buffers */ + split_dma = hcd->fiq_dmab; + + ptr = qtd->urb->buf + frame_desc->offset; + if (frame_length == 0) { + /* + * for isochronous transactions, we must still transmit a packet + * even if the length is zero. + */ + st->dma_info.slot_len[0] = 0; + st->nrpackets = 1; + } else { + do { + if (frame_length <= 188) { + dwc_memcpy(&split_dma[hc->hc_num].index[i].buf[0], ptr, frame_length); + st->dma_info.slot_len[i] = frame_length; + ptr += frame_length; + } else { + dwc_memcpy(&split_dma[hc->hc_num].index[i].buf[0], ptr, 188); + st->dma_info.slot_len[i] = 188; + ptr += 188; + } + i++; + frame_length -= 188; + } while (frame_length > 0); + st->nrpackets = i; + } + ptr = qtd->urb->buf + frame_desc->offset; + /* + * Point the HC at the DMA address of the bounce buffers + * + * Pointer arithmetic on hcd->fiq_state->dma_base (a + * dma_addr_t) to point it to the correct offset in the + * allocated buffers. + */ + split_dma = (struct fiq_dma_channel *) + (uintptr_t)hcd->fiq_state->dma_base; + st->hcdma_copy.d32 = lower_32_bits((uintptr_t) + &split_dma[hc->hc_num].index[0].buf[0]); + + /* fixup xfersize to the actual packet size */ + st->hctsiz_copy.b.pid = 0; + st->hctsiz_copy.b.xfersize = st->dma_info.slot_len[0]; + return 1; + } else { + /* For interrupt, single OUT packet required, goes in the SSPLIT from hc_buff. */ + return 0; + } + } +} + +/** + * fiq_fsm_np_tt_contended() - Avoid performing contended non-periodic transfers + * @hcd: Pointer to the dwc_otg_hcd struct + * @qh: Pointer to the endpoint's queue head + * + * Certain hub chips don't differentiate between IN and OUT non-periodic pipes + * with the same endpoint number. If transfers get completed out of order + * (disregarding the direction token) then the hub can lock up + * or return erroneous responses. + * + * Returns 1 if initiating the transfer would cause contention, 0 otherwise. + */ +static int fiq_fsm_np_tt_contended(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh) +{ + int i; + struct fiq_channel_state *st; + int dev_addr = qh->channel->dev_addr; + int ep_num = qh->channel->ep_num; + for (i = 0; i < hcd->core_if->core_params->host_channels; i++) { + if (i == qh->channel->hc_num) + continue; + st = &hcd->fiq_state->channel[i]; + switch (st->fsm) { + case FIQ_NP_SSPLIT_STARTED: + case FIQ_NP_SSPLIT_RETRY: + case FIQ_NP_SSPLIT_PENDING: + case FIQ_NP_OUT_CSPLIT_RETRY: + case FIQ_NP_IN_CSPLIT_RETRY: + if (st->hcchar_copy.b.devaddr == dev_addr && + st->hcchar_copy.b.epnum == ep_num) + return 1; + break; + default: + break; + } + } + return 0; +} + +/* + * Pushing a periodic request into the queue near the EOF1 point + * in a microframe causes erroneous behaviour (frmovrun) interrupt. + * Usually, the request goes out on the bus causing a transfer but + * the core does not transfer the data to memory. + * This guard interval (in number of 60MHz clocks) is required which + * must cater for CPU latency between reading the value and enabling + * the channel. + */ +#define PERIODIC_FRREM_BACKOFF 1000 + +static int fiq_fsm_queue_isoc_transaction(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh) +{ + dwc_hc_t *hc = qh->channel; + dwc_otg_hc_regs_t *hc_regs = hcd->core_if->host_if->hc_regs[hc->hc_num]; + dwc_otg_qtd_t *qtd = DWC_CIRCLEQ_FIRST(&qh->qtd_list); + int frame; + struct fiq_channel_state *st = &hcd->fiq_state->channel[hc->hc_num]; + int xfer_len, nrpackets; + hcdma_data_t hcdma; + hfnum_data_t hfnum; + + if (st->fsm != FIQ_PASSTHROUGH) + return 0; + + st->nr_errors = 0; + + st->hcchar_copy.d32 = 0; + st->hcchar_copy.b.mps = hc->max_packet; + st->hcchar_copy.b.epdir = hc->ep_is_in; + st->hcchar_copy.b.devaddr = hc->dev_addr; + st->hcchar_copy.b.epnum = hc->ep_num; + st->hcchar_copy.b.eptype = hc->ep_type; + + st->hcintmsk_copy.b.chhltd = 1; + + frame = dwc_otg_hcd_get_frame_number(hcd); + st->hcchar_copy.b.oddfrm = (frame & 0x1) ? 0 : 1; + + st->hcchar_copy.b.lspddev = 0; + /* Enable the channel later as a final register write. */ + + st->hcsplt_copy.d32 = 0; + + st->hs_isoc_info.iso_desc = (struct dwc_otg_hcd_iso_packet_desc *) &qtd->urb->iso_descs; + st->hs_isoc_info.nrframes = qtd->urb->packet_count; + /* grab the next DMA address offset from the array */ + st->hcdma_copy.d32 = qtd->urb->dma; + hcdma.d32 = st->hcdma_copy.d32 + st->hs_isoc_info.iso_desc[0].offset; + + /* We need to set multi_count. This is a bit tricky - has to be set per-transaction as + * the core needs to be told to send the correct number. Caution: for IN transfers, + * this is always set to the maximum size of the endpoint. */ + xfer_len = st->hs_isoc_info.iso_desc[0].length; + nrpackets = (xfer_len + st->hcchar_copy.b.mps - 1) / st->hcchar_copy.b.mps; + if (nrpackets == 0) + nrpackets = 1; + st->hcchar_copy.b.multicnt = nrpackets; + st->hctsiz_copy.b.pktcnt = nrpackets; + + /* Initial PID also needs to be set */ + if (st->hcchar_copy.b.epdir == 0) { + st->hctsiz_copy.b.xfersize = xfer_len; + switch (st->hcchar_copy.b.multicnt) { + case 1: + st->hctsiz_copy.b.pid = DWC_PID_DATA0; + break; + case 2: + case 3: + st->hctsiz_copy.b.pid = DWC_PID_MDATA; + break; + } + + } else { + st->hctsiz_copy.b.xfersize = nrpackets * st->hcchar_copy.b.mps; + switch (st->hcchar_copy.b.multicnt) { + case 1: + st->hctsiz_copy.b.pid = DWC_PID_DATA0; + break; + case 2: + st->hctsiz_copy.b.pid = DWC_PID_DATA1; + break; + case 3: + st->hctsiz_copy.b.pid = DWC_PID_DATA2; + break; + } + } + + st->hs_isoc_info.stride = qh->interval; + st->uframe_sleeps = 0; + + fiq_print(FIQDBG_INT, hcd->fiq_state, "FSMQ %01d ", hc->hc_num); + fiq_print(FIQDBG_INT, hcd->fiq_state, "%08x", st->hcchar_copy.d32); + fiq_print(FIQDBG_INT, hcd->fiq_state, "%08x", st->hctsiz_copy.d32); + fiq_print(FIQDBG_INT, hcd->fiq_state, "%08x", st->hcdma_copy.d32); + hfnum.d32 = DWC_READ_REG32(&hcd->core_if->host_if->host_global_regs->hfnum); + local_fiq_disable(); + fiq_fsm_spin_lock(&hcd->fiq_state->lock); + DWC_WRITE_REG32(&hc_regs->hctsiz, st->hctsiz_copy.d32); + DWC_WRITE_REG32(&hc_regs->hcsplt, st->hcsplt_copy.d32); + DWC_WRITE_REG32(&hc_regs->hcdma, st->hcdma_copy.d32); + DWC_WRITE_REG32(&hc_regs->hcchar, st->hcchar_copy.d32); + DWC_WRITE_REG32(&hc_regs->hcintmsk, st->hcintmsk_copy.d32); + if (hfnum.b.frrem < PERIODIC_FRREM_BACKOFF) { + /* Prevent queueing near EOF1. Bad things happen if a periodic + * split transaction is queued very close to EOF. SOF interrupt handler + * will wake this channel at the next interrupt. + */ + st->fsm = FIQ_HS_ISOC_SLEEPING; + st->uframe_sleeps = 1; + } else { + st->fsm = FIQ_HS_ISOC_TURBO; + st->hcchar_copy.b.chen = 1; + DWC_WRITE_REG32(&hc_regs->hcchar, st->hcchar_copy.d32); + } + mb(); + st->hcchar_copy.b.chen = 0; + fiq_fsm_spin_unlock(&hcd->fiq_state->lock); + local_fiq_enable(); + return 0; +} + + +/** + * fiq_fsm_queue_split_transaction() - Set up a host channel and FIQ state + * @hcd: Pointer to the dwc_otg_hcd struct + * @qh: Pointer to the endpoint's queue head + * + * This overrides the dwc_otg driver's normal method of queueing a transaction. + * Called from dwc_otg_hcd_queue_transactions(), this performs specific setup + * for the nominated host channel. + * + * For periodic transfers, it also peeks at the FIQ state to see if an immediate + * start is possible. If not, then the FIQ is left to start the transfer. + */ +static int fiq_fsm_queue_split_transaction(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh) +{ + int start_immediate = 1, i; + hfnum_data_t hfnum; + dwc_hc_t *hc = qh->channel; + dwc_otg_hc_regs_t *hc_regs = hcd->core_if->host_if->hc_regs[hc->hc_num]; + /* Program HC registers, setup FIQ_state, examine FIQ if periodic, start transfer (not if uframe 5) */ + int hub_addr, port_addr, frame, uframe; + struct fiq_channel_state *st = &hcd->fiq_state->channel[hc->hc_num]; + + /* + * Non-periodic channel assignments stay in the non_periodic_active queue. + * Therefore we get repeatedly called until the FIQ's done processing this channel. + */ + if (qh->channel->xfer_started == 1) + return 0; + + if (st->fsm != FIQ_PASSTHROUGH) { + pr_warn_ratelimited("%s:%d: Queue called for an active channel\n", __func__, __LINE__); + return 0; + } + + qh->channel->xfer_started = 1; + + st->nr_errors = 0; + + st->hcchar_copy.d32 = 0; + st->hcchar_copy.b.mps = min_t(uint32_t, hc->xfer_len, hc->max_packet); + st->hcchar_copy.b.epdir = hc->ep_is_in; + st->hcchar_copy.b.devaddr = hc->dev_addr; + st->hcchar_copy.b.epnum = hc->ep_num; + st->hcchar_copy.b.eptype = hc->ep_type; + if (hc->ep_type & 0x1) { + if (hc->ep_is_in) + st->hcchar_copy.b.multicnt = 3; + else + /* Docs say set this to 1, but driver sets to 0! */ + st->hcchar_copy.b.multicnt = 0; + } else { + st->hcchar_copy.b.multicnt = 1; + st->hcchar_copy.b.oddfrm = 0; + } + st->hcchar_copy.b.lspddev = (hc->speed == DWC_OTG_EP_SPEED_LOW) ? 1 : 0; + /* Enable the channel later as a final register write. */ + + st->hcsplt_copy.d32 = 0; + if(qh->do_split) { + hcd->fops->hub_info(hcd, DWC_CIRCLEQ_FIRST(&qh->qtd_list)->urb->priv, &hub_addr, &port_addr); + st->hcsplt_copy.b.compsplt = 0; + st->hcsplt_copy.b.spltena = 1; + // XACTPOS is for isoc-out only but needs initialising anyway. + st->hcsplt_copy.b.xactpos = ISOC_XACTPOS_ALL; + if((qh->ep_type == DWC_OTG_EP_TYPE_ISOC) && (!qh->ep_is_in)) { + /* For packetsize 0 < L < 188, ISOC_XACTPOS_ALL. + * for longer than this, ISOC_XACTPOS_BEGIN and the FIQ + * will update as necessary. + */ + if (hc->xfer_len > 188) { + st->hcsplt_copy.b.xactpos = ISOC_XACTPOS_BEGIN; + } + } + st->hcsplt_copy.b.hubaddr = (uint8_t) hub_addr; + st->hcsplt_copy.b.prtaddr = (uint8_t) port_addr; + st->hub_addr = hub_addr; + st->port_addr = port_addr; + } + + st->hctsiz_copy.d32 = 0; + st->hctsiz_copy.b.dopng = 0; + st->hctsiz_copy.b.pid = hc->data_pid_start; + + if (hc->ep_is_in || (hc->xfer_len > hc->max_packet)) { + hc->xfer_len = min_t(uint32_t, hc->xfer_len, hc->max_packet); + } else if (!hc->ep_is_in && (hc->xfer_len > 188)) { + hc->xfer_len = 188; + } + st->hctsiz_copy.b.xfersize = hc->xfer_len; + + st->hctsiz_copy.b.pktcnt = 1; + + if (hc->ep_type & 0x1) { + /* + * For potentially multi-packet transfers, must use the DMA bounce buffers. For IN transfers, + * the DMA address is the address of the first 188byte slot buffer in the bounce buffer array. + * For multi-packet OUT transfers, we need to copy the data into the bounce buffer array so the FIQ can punt + * the right address out as necessary. hc->xfer_buff and hc->xfer_len have already been set + * in assign_and_init_hc(), but this is for the eventual transaction completion only. The FIQ + * must not touch internal driver state. + */ + if(!fiq_fsm_setup_periodic_dma(hcd, st, qh)) { + if (hc->align_buff) { + st->hcdma_copy.d32 = hc->align_buff; + } else { + st->hcdma_copy.d32 = lower_32_bits((uintptr_t)hc->xfer_buff); + } + } + } else { + if (hc->align_buff) { + st->hcdma_copy.d32 = hc->align_buff; + } else { + st->hcdma_copy.d32 = lower_32_bits((uintptr_t)hc->xfer_buff); + } + } + /* The FIQ depends upon no other interrupts being enabled except channel halt. + * Fixup channel interrupt mask. */ + st->hcintmsk_copy.d32 = 0; + st->hcintmsk_copy.b.chhltd = 1; + st->hcintmsk_copy.b.ahberr = 1; + + /* Hack courtesy of FreeBSD: apparently forcing Interrupt Split transactions + * as Control puts the transfer into the non-periodic request queue and the + * non-periodic handler in the hub. Makes things lots easier. + */ + if ((fiq_fsm_mask & 0x8) && hc->ep_type == UE_INTERRUPT) { + st->hcchar_copy.b.multicnt = 0; + st->hcchar_copy.b.oddfrm = 0; + st->hcchar_copy.b.eptype = UE_CONTROL; + if (hc->align_buff) { + st->hcdma_copy.d32 = hc->align_buff; + } else { + st->hcdma_copy.d32 = lower_32_bits((uintptr_t)hc->xfer_buff); + } + } + DWC_WRITE_REG32(&hc_regs->hcdma, st->hcdma_copy.d32); + DWC_WRITE_REG32(&hc_regs->hctsiz, st->hctsiz_copy.d32); + DWC_WRITE_REG32(&hc_regs->hcsplt, st->hcsplt_copy.d32); + DWC_WRITE_REG32(&hc_regs->hcchar, st->hcchar_copy.d32); + DWC_WRITE_REG32(&hc_regs->hcintmsk, st->hcintmsk_copy.d32); + + local_fiq_disable(); + fiq_fsm_spin_lock(&hcd->fiq_state->lock); + + if (hc->ep_type & 0x1) { + hfnum.d32 = DWC_READ_REG32(&hcd->core_if->host_if->host_global_regs->hfnum); + frame = (hfnum.b.frnum & ~0x7) >> 3; + uframe = hfnum.b.frnum & 0x7; + if (hfnum.b.frrem < PERIODIC_FRREM_BACKOFF) { + /* Prevent queueing near EOF1. Bad things happen if a periodic + * split transaction is queued very close to EOF. + */ + start_immediate = 0; + } else if (uframe == 5) { + start_immediate = 0; + } else if (hc->ep_type == UE_ISOCHRONOUS && !hc->ep_is_in) { + start_immediate = 0; + } else if (hc->ep_is_in && fiq_fsm_too_late(hcd->fiq_state, hc->hc_num)) { + start_immediate = 0; + } else { + /* Search through all host channels to determine if a transaction + * is currently in progress */ + for (i = 0; i < hcd->core_if->core_params->host_channels; i++) { + if (i == hc->hc_num || hcd->fiq_state->channel[i].fsm == FIQ_PASSTHROUGH) + continue; + switch (hcd->fiq_state->channel[i].fsm) { + /* TT is reserved for channels that are in the middle of a periodic + * split transaction. + */ + case FIQ_PER_SSPLIT_STARTED: + case FIQ_PER_CSPLIT_WAIT: + case FIQ_PER_CSPLIT_NYET1: + case FIQ_PER_CSPLIT_POLL: + case FIQ_PER_ISO_OUT_ACTIVE: + case FIQ_PER_ISO_OUT_LAST: + if (hcd->fiq_state->channel[i].hub_addr == hub_addr && + hcd->fiq_state->channel[i].port_addr == port_addr) { + start_immediate = 0; + } + break; + default: + break; + } + if (!start_immediate) + break; + } + } + } + if ((fiq_fsm_mask & 0x8) && hc->ep_type == UE_INTERRUPT) + start_immediate = 1; + + fiq_print(FIQDBG_INT, hcd->fiq_state, "FSMQ %01d %01d", hc->hc_num, start_immediate); + fiq_print(FIQDBG_INT, hcd->fiq_state, "%08d", hfnum.b.frrem); + //fiq_print(FIQDBG_INT, hcd->fiq_state, "H:%02dP:%02d", hub_addr, port_addr); + //fiq_print(FIQDBG_INT, hcd->fiq_state, "%08x", st->hctsiz_copy.d32); + //fiq_print(FIQDBG_INT, hcd->fiq_state, "%08x", st->hcdma_copy.d32); + switch (hc->ep_type) { + case UE_CONTROL: + case UE_BULK: + if (fiq_fsm_np_tt_contended(hcd, qh)) { + st->fsm = FIQ_NP_SSPLIT_PENDING; + start_immediate = 0; + } else { + st->fsm = FIQ_NP_SSPLIT_STARTED; + } + break; + case UE_ISOCHRONOUS: + if (hc->ep_is_in) { + if (start_immediate) { + st->fsm = FIQ_PER_SSPLIT_STARTED; + } else { + st->fsm = FIQ_PER_SSPLIT_QUEUED; + } + } else { + if (start_immediate) { + /* Single-isoc OUT packets don't require FIQ involvement */ + if (st->nrpackets == 1) { + st->fsm = FIQ_PER_ISO_OUT_LAST; + } else { + st->fsm = FIQ_PER_ISO_OUT_ACTIVE; + } + } else { + st->fsm = FIQ_PER_ISO_OUT_PENDING; + } + } + break; + case UE_INTERRUPT: + if (fiq_fsm_mask & 0x8) { + if (fiq_fsm_np_tt_contended(hcd, qh)) { + st->fsm = FIQ_NP_SSPLIT_PENDING; + start_immediate = 0; + } else { + st->fsm = FIQ_NP_SSPLIT_STARTED; + } + } else if (start_immediate) { + st->fsm = FIQ_PER_SSPLIT_STARTED; + } else { + st->fsm = FIQ_PER_SSPLIT_QUEUED; + } + break; + default: + break; + } + if (start_immediate) { + /* Set the oddfrm bit as close as possible to actual queueing */ + frame = dwc_otg_hcd_get_frame_number(hcd); + st->expected_uframe = (frame + 1) & 0x3FFF; + st->hcchar_copy.b.oddfrm = (frame & 0x1) ? 0 : 1; + st->hcchar_copy.b.chen = 1; + DWC_WRITE_REG32(&hc_regs->hcchar, st->hcchar_copy.d32); + } + mb(); + fiq_fsm_spin_unlock(&hcd->fiq_state->lock); + local_fiq_enable(); + return 0; +} + + +/** + * This function selects transactions from the HCD transfer schedule and + * assigns them to available host channels. It is called from HCD interrupt + * handler functions. + * + * @param hcd The HCD state structure. + * + * @return The types of new transactions that were assigned to host channels. + */ +dwc_otg_transaction_type_e dwc_otg_hcd_select_transactions(dwc_otg_hcd_t * hcd) +{ + dwc_list_link_t *qh_ptr; + dwc_otg_qh_t *qh; + int num_channels; + dwc_otg_transaction_type_e ret_val = DWC_OTG_TRANSACTION_NONE; + +#ifdef DEBUG_HOST_CHANNELS + last_sel_trans_num_per_scheduled = 0; + last_sel_trans_num_nonper_scheduled = 0; + last_sel_trans_num_avail_hc_at_start = hcd->available_host_channels; +#endif /* DEBUG_HOST_CHANNELS */ + + /* Process entries in the periodic ready list. */ + qh_ptr = DWC_LIST_FIRST(&hcd->periodic_sched_ready); + + while (qh_ptr != &hcd->periodic_sched_ready && + !DWC_CIRCLEQ_EMPTY(&hcd->free_hc_list)) { + + qh = DWC_LIST_ENTRY(qh_ptr, dwc_otg_qh_t, qh_list_entry); + + if (microframe_schedule) { + // Make sure we leave one channel for non periodic transactions. + if (hcd->available_host_channels <= 1) { + break; + } + hcd->available_host_channels--; +#ifdef DEBUG_HOST_CHANNELS + last_sel_trans_num_per_scheduled++; +#endif /* DEBUG_HOST_CHANNELS */ + } + qh = DWC_LIST_ENTRY(qh_ptr, dwc_otg_qh_t, qh_list_entry); + assign_and_init_hc(hcd, qh); + + /* + * Move the QH from the periodic ready schedule to the + * periodic assigned schedule. + */ + qh_ptr = DWC_LIST_NEXT(qh_ptr); + DWC_LIST_MOVE_HEAD(&hcd->periodic_sched_assigned, + &qh->qh_list_entry); + } + + /* + * Process entries in the inactive portion of the non-periodic + * schedule. Some free host channels may not be used if they are + * reserved for periodic transfers. + */ + qh_ptr = hcd->non_periodic_sched_inactive.next; + num_channels = hcd->core_if->core_params->host_channels; + while (qh_ptr != &hcd->non_periodic_sched_inactive && + (microframe_schedule || hcd->non_periodic_channels < + num_channels - hcd->periodic_channels) && + !DWC_CIRCLEQ_EMPTY(&hcd->free_hc_list)) { + + qh = DWC_LIST_ENTRY(qh_ptr, dwc_otg_qh_t, qh_list_entry); + /* + * Check to see if this is a NAK'd retransmit, in which case ignore for retransmission + * we hold off on bulk retransmissions to reduce NAK interrupt overhead for full-speed + * cheeky devices that just hold off using NAKs + */ + if (fiq_enable && nak_holdoff && qh->do_split) { + if (qh->nak_frame != 0xffff) { + uint16_t next_frame = dwc_frame_num_inc(qh->nak_frame, (qh->ep_type == UE_BULK) ? nak_holdoff : 8); + uint16_t frame = dwc_otg_hcd_get_frame_number(hcd); + if (dwc_frame_num_le(frame, next_frame)) { + if(dwc_frame_num_le(next_frame, hcd->fiq_state->next_sched_frame)) { + hcd->fiq_state->next_sched_frame = next_frame; + } + qh_ptr = DWC_LIST_NEXT(qh_ptr); + continue; + } else { + qh->nak_frame = 0xFFFF; + } + } + } + + if (microframe_schedule) { + if (hcd->available_host_channels < 1) { + break; + } + hcd->available_host_channels--; +#ifdef DEBUG_HOST_CHANNELS + last_sel_trans_num_nonper_scheduled++; +#endif /* DEBUG_HOST_CHANNELS */ + } + + assign_and_init_hc(hcd, qh); + + /* + * Move the QH from the non-periodic inactive schedule to the + * non-periodic active schedule. + */ + qh_ptr = DWC_LIST_NEXT(qh_ptr); + DWC_LIST_MOVE_HEAD(&hcd->non_periodic_sched_active, + &qh->qh_list_entry); + + if (!microframe_schedule) + hcd->non_periodic_channels++; + } + /* we moved a non-periodic QH to the active schedule. If the inactive queue is empty, + * stop the FIQ from kicking us. We could potentially still have elements here if we + * ran out of host channels. + */ + if (fiq_enable) { + if (DWC_LIST_EMPTY(&hcd->non_periodic_sched_inactive)) { + hcd->fiq_state->kick_np_queues = 0; + } else { + /* For each entry remaining in the NP inactive queue, + * if this a NAK'd retransmit then don't set the kick flag. + */ + if(nak_holdoff) { + DWC_LIST_FOREACH(qh_ptr, &hcd->non_periodic_sched_inactive) { + qh = DWC_LIST_ENTRY(qh_ptr, dwc_otg_qh_t, qh_list_entry); + if (qh->nak_frame == 0xFFFF) { + hcd->fiq_state->kick_np_queues = 1; + } + } + } + } + } + if(!DWC_LIST_EMPTY(&hcd->periodic_sched_assigned)) + ret_val |= DWC_OTG_TRANSACTION_PERIODIC; + + if(!DWC_LIST_EMPTY(&hcd->non_periodic_sched_active)) + ret_val |= DWC_OTG_TRANSACTION_NON_PERIODIC; + + +#ifdef DEBUG_HOST_CHANNELS + last_sel_trans_num_avail_hc_at_end = hcd->available_host_channels; +#endif /* DEBUG_HOST_CHANNELS */ + return ret_val; +} + +/** + * Attempts to queue a single transaction request for a host channel + * associated with either a periodic or non-periodic transfer. This function + * assumes that there is space available in the appropriate request queue. For + * an OUT transfer or SETUP transaction in Slave mode, it checks whether space + * is available in the appropriate Tx FIFO. + * + * @param hcd The HCD state structure. + * @param hc Host channel descriptor associated with either a periodic or + * non-periodic transfer. + * @param fifo_dwords_avail Number of DWORDs available in the periodic Tx + * FIFO for periodic transfers or the non-periodic Tx FIFO for non-periodic + * transfers. + * + * @return 1 if a request is queued and more requests may be needed to + * complete the transfer, 0 if no more requests are required for this + * transfer, -1 if there is insufficient space in the Tx FIFO. + */ +static int queue_transaction(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, uint16_t fifo_dwords_avail) +{ + int retval; + + if (hcd->core_if->dma_enable) { + if (hcd->core_if->dma_desc_enable) { + if (!hc->xfer_started + || (hc->ep_type == DWC_OTG_EP_TYPE_ISOC)) { + dwc_otg_hcd_start_xfer_ddma(hcd, hc->qh); + hc->qh->ping_state = 0; + } + } else if (!hc->xfer_started) { + if (fiq_fsm_enable && hc->error_state) { + hcd->fiq_state->channel[hc->hc_num].nr_errors = + DWC_CIRCLEQ_FIRST(&hc->qh->qtd_list)->error_count; + hcd->fiq_state->channel[hc->hc_num].fsm = + FIQ_PASSTHROUGH_ERRORSTATE; + } + dwc_otg_hc_start_transfer(hcd->core_if, hc); + hc->qh->ping_state = 0; + } + retval = 0; + } else if (hc->halt_pending) { + /* Don't queue a request if the channel has been halted. */ + retval = 0; + } else if (hc->halt_on_queue) { + dwc_otg_hc_halt(hcd->core_if, hc, hc->halt_status); + retval = 0; + } else if (hc->do_ping) { + if (!hc->xfer_started) { + dwc_otg_hc_start_transfer(hcd->core_if, hc); + } + retval = 0; + } else if (!hc->ep_is_in || hc->data_pid_start == DWC_OTG_HC_PID_SETUP) { + if ((fifo_dwords_avail * 4) >= hc->max_packet) { + if (!hc->xfer_started) { + dwc_otg_hc_start_transfer(hcd->core_if, hc); + retval = 1; + } else { + retval = + dwc_otg_hc_continue_transfer(hcd->core_if, + hc); + } + } else { + retval = -1; + } + } else { + if (!hc->xfer_started) { + dwc_otg_hc_start_transfer(hcd->core_if, hc); + retval = 1; + } else { + retval = dwc_otg_hc_continue_transfer(hcd->core_if, hc); + } + } + + return retval; +} + +/** + * Processes periodic channels for the next frame and queues transactions for + * these channels to the DWC_otg controller. After queueing transactions, the + * Periodic Tx FIFO Empty interrupt is enabled if there are more transactions + * to queue as Periodic Tx FIFO or request queue space becomes available. + * Otherwise, the Periodic Tx FIFO Empty interrupt is disabled. + */ +static void process_periodic_channels(dwc_otg_hcd_t * hcd) +{ + hptxsts_data_t tx_status; + dwc_list_link_t *qh_ptr; + dwc_otg_qh_t *qh; + int status = 0; + int no_queue_space = 0; + int no_fifo_space = 0; + + dwc_otg_host_global_regs_t *host_regs; + host_regs = hcd->core_if->host_if->host_global_regs; + + DWC_DEBUGPL(DBG_HCDV, "Queue periodic transactions\n"); +#ifdef DEBUG + tx_status.d32 = DWC_READ_REG32(&host_regs->hptxsts); + DWC_DEBUGPL(DBG_HCDV, + " P Tx Req Queue Space Avail (before queue): %d\n", + tx_status.b.ptxqspcavail); + DWC_DEBUGPL(DBG_HCDV, " P Tx FIFO Space Avail (before queue): %d\n", + tx_status.b.ptxfspcavail); +#endif + + qh_ptr = hcd->periodic_sched_assigned.next; + while (qh_ptr != &hcd->periodic_sched_assigned) { + tx_status.d32 = DWC_READ_REG32(&host_regs->hptxsts); + if (tx_status.b.ptxqspcavail == 0) { + no_queue_space = 1; + break; + } + + qh = DWC_LIST_ENTRY(qh_ptr, dwc_otg_qh_t, qh_list_entry); + + // Do not send a split start transaction any later than frame .6 + // Note, we have to schedule a periodic in .5 to make it go in .6 + if(fiq_fsm_enable && qh->do_split && ((dwc_otg_hcd_get_frame_number(hcd) + 1) & 7) > 6) + { + qh_ptr = qh_ptr->next; + hcd->fiq_state->next_sched_frame = dwc_otg_hcd_get_frame_number(hcd) | 7; + continue; + } + + if (fiq_fsm_enable && fiq_fsm_transaction_suitable(hcd, qh)) { + if (qh->do_split) + fiq_fsm_queue_split_transaction(hcd, qh); + else + fiq_fsm_queue_isoc_transaction(hcd, qh); + } else { + + /* + * Set a flag if we're queueing high-bandwidth in slave mode. + * The flag prevents any halts to get into the request queue in + * the middle of multiple high-bandwidth packets getting queued. + */ + if (!hcd->core_if->dma_enable && qh->channel->multi_count > 1) { + hcd->core_if->queuing_high_bandwidth = 1; + } + status = queue_transaction(hcd, qh->channel, + tx_status.b.ptxfspcavail); + if (status < 0) { + no_fifo_space = 1; + break; + } + } + + /* + * In Slave mode, stay on the current transfer until there is + * nothing more to do or the high-bandwidth request count is + * reached. In DMA mode, only need to queue one request. The + * controller automatically handles multiple packets for + * high-bandwidth transfers. + */ + if (hcd->core_if->dma_enable || status == 0 || + qh->channel->requests == qh->channel->multi_count) { + qh_ptr = qh_ptr->next; + /* + * Move the QH from the periodic assigned schedule to + * the periodic queued schedule. + */ + DWC_LIST_MOVE_HEAD(&hcd->periodic_sched_queued, + &qh->qh_list_entry); + + /* done queuing high bandwidth */ + hcd->core_if->queuing_high_bandwidth = 0; + } + } + + if (!hcd->core_if->dma_enable) { + dwc_otg_core_global_regs_t *global_regs; + gintmsk_data_t intr_mask = {.d32 = 0 }; + + global_regs = hcd->core_if->core_global_regs; + intr_mask.b.ptxfempty = 1; +#ifdef DEBUG + tx_status.d32 = DWC_READ_REG32(&host_regs->hptxsts); + DWC_DEBUGPL(DBG_HCDV, + " P Tx Req Queue Space Avail (after queue): %d\n", + tx_status.b.ptxqspcavail); + DWC_DEBUGPL(DBG_HCDV, + " P Tx FIFO Space Avail (after queue): %d\n", + tx_status.b.ptxfspcavail); +#endif + if (!DWC_LIST_EMPTY(&hcd->periodic_sched_assigned) || + no_queue_space || no_fifo_space) { + /* + * May need to queue more transactions as the request + * queue or Tx FIFO empties. Enable the periodic Tx + * FIFO empty interrupt. (Always use the half-empty + * level to ensure that new requests are loaded as + * soon as possible.) + */ + DWC_MODIFY_REG32(&global_regs->gintmsk, 0, + intr_mask.d32); + } else { + /* + * Disable the Tx FIFO empty interrupt since there are + * no more transactions that need to be queued right + * now. This function is called from interrupt + * handlers to queue more transactions as transfer + * states change. + */ + DWC_MODIFY_REG32(&global_regs->gintmsk, intr_mask.d32, + 0); + } + } +} + +/** + * Processes active non-periodic channels and queues transactions for these + * channels to the DWC_otg controller. After queueing transactions, the NP Tx + * FIFO Empty interrupt is enabled if there are more transactions to queue as + * NP Tx FIFO or request queue space becomes available. Otherwise, the NP Tx + * FIFO Empty interrupt is disabled. + */ +static void process_non_periodic_channels(dwc_otg_hcd_t * hcd) +{ + gnptxsts_data_t tx_status; + dwc_list_link_t *orig_qh_ptr; + dwc_otg_qh_t *qh; + int status; + int no_queue_space = 0; + int no_fifo_space = 0; + int more_to_do = 0; + + dwc_otg_core_global_regs_t *global_regs = + hcd->core_if->core_global_regs; + + DWC_DEBUGPL(DBG_HCDV, "Queue non-periodic transactions\n"); +#ifdef DEBUG + tx_status.d32 = DWC_READ_REG32(&global_regs->gnptxsts); + DWC_DEBUGPL(DBG_HCDV, + " NP Tx Req Queue Space Avail (before queue): %d\n", + tx_status.b.nptxqspcavail); + DWC_DEBUGPL(DBG_HCDV, " NP Tx FIFO Space Avail (before queue): %d\n", + tx_status.b.nptxfspcavail); +#endif + /* + * Keep track of the starting point. Skip over the start-of-list + * entry. + */ + if (hcd->non_periodic_qh_ptr == &hcd->non_periodic_sched_active) { + hcd->non_periodic_qh_ptr = hcd->non_periodic_qh_ptr->next; + } + orig_qh_ptr = hcd->non_periodic_qh_ptr; + + /* + * Process once through the active list or until no more space is + * available in the request queue or the Tx FIFO. + */ + do { + tx_status.d32 = DWC_READ_REG32(&global_regs->gnptxsts); + if (!hcd->core_if->dma_enable && tx_status.b.nptxqspcavail == 0) { + no_queue_space = 1; + break; + } + + qh = DWC_LIST_ENTRY(hcd->non_periodic_qh_ptr, dwc_otg_qh_t, + qh_list_entry); + + if(fiq_fsm_enable && fiq_fsm_transaction_suitable(hcd, qh)) { + fiq_fsm_queue_split_transaction(hcd, qh); + } else { + status = queue_transaction(hcd, qh->channel, + tx_status.b.nptxfspcavail); + + if (status > 0) { + more_to_do = 1; + } else if (status < 0) { + no_fifo_space = 1; + break; + } + } + /* Advance to next QH, skipping start-of-list entry. */ + hcd->non_periodic_qh_ptr = hcd->non_periodic_qh_ptr->next; + if (hcd->non_periodic_qh_ptr == &hcd->non_periodic_sched_active) { + hcd->non_periodic_qh_ptr = + hcd->non_periodic_qh_ptr->next; + } + + } while (hcd->non_periodic_qh_ptr != orig_qh_ptr); + + if (!hcd->core_if->dma_enable) { + gintmsk_data_t intr_mask = {.d32 = 0 }; + intr_mask.b.nptxfempty = 1; + +#ifdef DEBUG + tx_status.d32 = DWC_READ_REG32(&global_regs->gnptxsts); + DWC_DEBUGPL(DBG_HCDV, + " NP Tx Req Queue Space Avail (after queue): %d\n", + tx_status.b.nptxqspcavail); + DWC_DEBUGPL(DBG_HCDV, + " NP Tx FIFO Space Avail (after queue): %d\n", + tx_status.b.nptxfspcavail); +#endif + if (more_to_do || no_queue_space || no_fifo_space) { + /* + * May need to queue more transactions as the request + * queue or Tx FIFO empties. Enable the non-periodic + * Tx FIFO empty interrupt. (Always use the half-empty + * level to ensure that new requests are loaded as + * soon as possible.) + */ + DWC_MODIFY_REG32(&global_regs->gintmsk, 0, + intr_mask.d32); + } else { + /* + * Disable the Tx FIFO empty interrupt since there are + * no more transactions that need to be queued right + * now. This function is called from interrupt + * handlers to queue more transactions as transfer + * states change. + */ + DWC_MODIFY_REG32(&global_regs->gintmsk, intr_mask.d32, + 0); + } + } +} + +/** + * This function processes the currently active host channels and queues + * transactions for these channels to the DWC_otg controller. It is called + * from HCD interrupt handler functions. + * + * @param hcd The HCD state structure. + * @param tr_type The type(s) of transactions to queue (non-periodic, + * periodic, or both). + */ +void dwc_otg_hcd_queue_transactions(dwc_otg_hcd_t * hcd, + dwc_otg_transaction_type_e tr_type) +{ +#ifdef DEBUG_SOF + DWC_DEBUGPL(DBG_HCD, "Queue Transactions\n"); +#endif + /* Process host channels associated with periodic transfers. */ + if ((tr_type == DWC_OTG_TRANSACTION_PERIODIC || + tr_type == DWC_OTG_TRANSACTION_ALL) && + !DWC_LIST_EMPTY(&hcd->periodic_sched_assigned)) { + + process_periodic_channels(hcd); + } + + /* Process host channels associated with non-periodic transfers. */ + if (tr_type == DWC_OTG_TRANSACTION_NON_PERIODIC || + tr_type == DWC_OTG_TRANSACTION_ALL) { + if (!DWC_LIST_EMPTY(&hcd->non_periodic_sched_active)) { + process_non_periodic_channels(hcd); + } else { + /* + * Ensure NP Tx FIFO empty interrupt is disabled when + * there are no non-periodic transfers to process. + */ + gintmsk_data_t gintmsk = {.d32 = 0 }; + gintmsk.b.nptxfempty = 1; + + if (fiq_enable) { + local_fiq_disable(); + fiq_fsm_spin_lock(&hcd->fiq_state->lock); + DWC_MODIFY_REG32(&hcd->core_if->core_global_regs->gintmsk, gintmsk.d32, 0); + fiq_fsm_spin_unlock(&hcd->fiq_state->lock); + local_fiq_enable(); + } else { + DWC_MODIFY_REG32(&hcd->core_if->core_global_regs->gintmsk, gintmsk.d32, 0); + } + } + } +} + +#ifdef DWC_HS_ELECT_TST +/* + * Quick and dirty hack to implement the HS Electrical Test + * SINGLE_STEP_GET_DEVICE_DESCRIPTOR feature. + * + * This code was copied from our userspace app "hset". It sends a + * Get Device Descriptor control sequence in two parts, first the + * Setup packet by itself, followed some time later by the In and + * Ack packets. Rather than trying to figure out how to add this + * functionality to the normal driver code, we just hijack the + * hardware, using these two function to drive the hardware + * directly. + */ + +static dwc_otg_core_global_regs_t *global_regs; +static dwc_otg_host_global_regs_t *hc_global_regs; +static dwc_otg_hc_regs_t *hc_regs; +static uint32_t *data_fifo; + +static void do_setup(void) +{ + gintsts_data_t gintsts; + hctsiz_data_t hctsiz; + hcchar_data_t hcchar; + haint_data_t haint; + hcint_data_t hcint; + + /* Enable HAINTs */ + DWC_WRITE_REG32(&hc_global_regs->haintmsk, 0x0001); + + /* Enable HCINTs */ + DWC_WRITE_REG32(&hc_regs->hcintmsk, 0x04a3); + + /* Read GINTSTS */ + gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts); + + /* Read HAINT */ + haint.d32 = DWC_READ_REG32(&hc_global_regs->haint); + + /* Read HCINT */ + hcint.d32 = DWC_READ_REG32(&hc_regs->hcint); + + /* Read HCCHAR */ + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + + /* Clear HCINT */ + DWC_WRITE_REG32(&hc_regs->hcint, hcint.d32); + + /* Clear HAINT */ + DWC_WRITE_REG32(&hc_global_regs->haint, haint.d32); + + /* Clear GINTSTS */ + DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32); + + /* Read GINTSTS */ + gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts); + + /* + * Send Setup packet (Get Device Descriptor) + */ + + /* Make sure channel is disabled */ + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + if (hcchar.b.chen) { + hcchar.b.chdis = 1; +// hcchar.b.chen = 1; + DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32); + //sleep(1); + dwc_mdelay(1000); + + /* Read GINTSTS */ + gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts); + + /* Read HAINT */ + haint.d32 = DWC_READ_REG32(&hc_global_regs->haint); + + /* Read HCINT */ + hcint.d32 = DWC_READ_REG32(&hc_regs->hcint); + + /* Read HCCHAR */ + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + + /* Clear HCINT */ + DWC_WRITE_REG32(&hc_regs->hcint, hcint.d32); + + /* Clear HAINT */ + DWC_WRITE_REG32(&hc_global_regs->haint, haint.d32); + + /* Clear GINTSTS */ + DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32); + + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + } + + /* Set HCTSIZ */ + hctsiz.d32 = 0; + hctsiz.b.xfersize = 8; + hctsiz.b.pktcnt = 1; + hctsiz.b.pid = DWC_OTG_HC_PID_SETUP; + DWC_WRITE_REG32(&hc_regs->hctsiz, hctsiz.d32); + + /* Set HCCHAR */ + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + hcchar.b.eptype = DWC_OTG_EP_TYPE_CONTROL; + hcchar.b.epdir = 0; + hcchar.b.epnum = 0; + hcchar.b.mps = 8; + hcchar.b.chen = 1; + DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32); + + /* Fill FIFO with Setup data for Get Device Descriptor */ + data_fifo = (uint32_t *) ((char *)global_regs + 0x1000); + DWC_WRITE_REG32(data_fifo++, 0x01000680); + DWC_WRITE_REG32(data_fifo++, 0x00080000); + + gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts); + + /* Wait for host channel interrupt */ + do { + gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts); + } while (gintsts.b.hcintr == 0); + + /* Disable HCINTs */ + DWC_WRITE_REG32(&hc_regs->hcintmsk, 0x0000); + + /* Disable HAINTs */ + DWC_WRITE_REG32(&hc_global_regs->haintmsk, 0x0000); + + /* Read HAINT */ + haint.d32 = DWC_READ_REG32(&hc_global_regs->haint); + + /* Read HCINT */ + hcint.d32 = DWC_READ_REG32(&hc_regs->hcint); + + /* Read HCCHAR */ + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + + /* Clear HCINT */ + DWC_WRITE_REG32(&hc_regs->hcint, hcint.d32); + + /* Clear HAINT */ + DWC_WRITE_REG32(&hc_global_regs->haint, haint.d32); + + /* Clear GINTSTS */ + DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32); + + /* Read GINTSTS */ + gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts); +} + +static void do_in_ack(void) +{ + gintsts_data_t gintsts; + hctsiz_data_t hctsiz; + hcchar_data_t hcchar; + haint_data_t haint; + hcint_data_t hcint; + host_grxsts_data_t grxsts; + + /* Enable HAINTs */ + DWC_WRITE_REG32(&hc_global_regs->haintmsk, 0x0001); + + /* Enable HCINTs */ + DWC_WRITE_REG32(&hc_regs->hcintmsk, 0x04a3); + + /* Read GINTSTS */ + gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts); + + /* Read HAINT */ + haint.d32 = DWC_READ_REG32(&hc_global_regs->haint); + + /* Read HCINT */ + hcint.d32 = DWC_READ_REG32(&hc_regs->hcint); + + /* Read HCCHAR */ + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + + /* Clear HCINT */ + DWC_WRITE_REG32(&hc_regs->hcint, hcint.d32); + + /* Clear HAINT */ + DWC_WRITE_REG32(&hc_global_regs->haint, haint.d32); + + /* Clear GINTSTS */ + DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32); + + /* Read GINTSTS */ + gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts); + + /* + * Receive Control In packet + */ + + /* Make sure channel is disabled */ + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + if (hcchar.b.chen) { + hcchar.b.chdis = 1; + hcchar.b.chen = 1; + DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32); + //sleep(1); + dwc_mdelay(1000); + + /* Read GINTSTS */ + gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts); + + /* Read HAINT */ + haint.d32 = DWC_READ_REG32(&hc_global_regs->haint); + + /* Read HCINT */ + hcint.d32 = DWC_READ_REG32(&hc_regs->hcint); + + /* Read HCCHAR */ + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + + /* Clear HCINT */ + DWC_WRITE_REG32(&hc_regs->hcint, hcint.d32); + + /* Clear HAINT */ + DWC_WRITE_REG32(&hc_global_regs->haint, haint.d32); + + /* Clear GINTSTS */ + DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32); + + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + } + + /* Set HCTSIZ */ + hctsiz.d32 = 0; + hctsiz.b.xfersize = 8; + hctsiz.b.pktcnt = 1; + hctsiz.b.pid = DWC_OTG_HC_PID_DATA1; + DWC_WRITE_REG32(&hc_regs->hctsiz, hctsiz.d32); + + /* Set HCCHAR */ + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + hcchar.b.eptype = DWC_OTG_EP_TYPE_CONTROL; + hcchar.b.epdir = 1; + hcchar.b.epnum = 0; + hcchar.b.mps = 8; + hcchar.b.chen = 1; + DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32); + + gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts); + + /* Wait for receive status queue interrupt */ + do { + gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts); + } while (gintsts.b.rxstsqlvl == 0); + + /* Read RXSTS */ + grxsts.d32 = DWC_READ_REG32(&global_regs->grxstsp); + + /* Clear RXSTSQLVL in GINTSTS */ + gintsts.d32 = 0; + gintsts.b.rxstsqlvl = 1; + DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32); + + switch (grxsts.b.pktsts) { + case DWC_GRXSTS_PKTSTS_IN: + /* Read the data into the host buffer */ + if (grxsts.b.bcnt > 0) { + int i; + int word_count = (grxsts.b.bcnt + 3) / 4; + + data_fifo = (uint32_t *) ((char *)global_regs + 0x1000); + + for (i = 0; i < word_count; i++) { + (void)DWC_READ_REG32(data_fifo++); + } + } + break; + + default: + break; + } + + gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts); + + /* Wait for receive status queue interrupt */ + do { + gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts); + } while (gintsts.b.rxstsqlvl == 0); + + /* Read RXSTS */ + grxsts.d32 = DWC_READ_REG32(&global_regs->grxstsp); + + /* Clear RXSTSQLVL in GINTSTS */ + gintsts.d32 = 0; + gintsts.b.rxstsqlvl = 1; + DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32); + + switch (grxsts.b.pktsts) { + case DWC_GRXSTS_PKTSTS_IN_XFER_COMP: + break; + + default: + break; + } + + gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts); + + /* Wait for host channel interrupt */ + do { + gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts); + } while (gintsts.b.hcintr == 0); + + /* Read HAINT */ + haint.d32 = DWC_READ_REG32(&hc_global_regs->haint); + + /* Read HCINT */ + hcint.d32 = DWC_READ_REG32(&hc_regs->hcint); + + /* Read HCCHAR */ + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + + /* Clear HCINT */ + DWC_WRITE_REG32(&hc_regs->hcint, hcint.d32); + + /* Clear HAINT */ + DWC_WRITE_REG32(&hc_global_regs->haint, haint.d32); + + /* Clear GINTSTS */ + DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32); + + /* Read GINTSTS */ + gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts); + +// usleep(100000); +// mdelay(100); + dwc_mdelay(1); + + /* + * Send handshake packet + */ + + /* Read HAINT */ + haint.d32 = DWC_READ_REG32(&hc_global_regs->haint); + + /* Read HCINT */ + hcint.d32 = DWC_READ_REG32(&hc_regs->hcint); + + /* Read HCCHAR */ + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + + /* Clear HCINT */ + DWC_WRITE_REG32(&hc_regs->hcint, hcint.d32); + + /* Clear HAINT */ + DWC_WRITE_REG32(&hc_global_regs->haint, haint.d32); + + /* Clear GINTSTS */ + DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32); + + /* Read GINTSTS */ + gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts); + + /* Make sure channel is disabled */ + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + if (hcchar.b.chen) { + hcchar.b.chdis = 1; + hcchar.b.chen = 1; + DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32); + //sleep(1); + dwc_mdelay(1000); + + /* Read GINTSTS */ + gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts); + + /* Read HAINT */ + haint.d32 = DWC_READ_REG32(&hc_global_regs->haint); + + /* Read HCINT */ + hcint.d32 = DWC_READ_REG32(&hc_regs->hcint); + + /* Read HCCHAR */ + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + + /* Clear HCINT */ + DWC_WRITE_REG32(&hc_regs->hcint, hcint.d32); + + /* Clear HAINT */ + DWC_WRITE_REG32(&hc_global_regs->haint, haint.d32); + + /* Clear GINTSTS */ + DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32); + + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + } + + /* Set HCTSIZ */ + hctsiz.d32 = 0; + hctsiz.b.xfersize = 0; + hctsiz.b.pktcnt = 1; + hctsiz.b.pid = DWC_OTG_HC_PID_DATA1; + DWC_WRITE_REG32(&hc_regs->hctsiz, hctsiz.d32); + + /* Set HCCHAR */ + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + hcchar.b.eptype = DWC_OTG_EP_TYPE_CONTROL; + hcchar.b.epdir = 0; + hcchar.b.epnum = 0; + hcchar.b.mps = 8; + hcchar.b.chen = 1; + DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32); + + gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts); + + /* Wait for host channel interrupt */ + do { + gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts); + } while (gintsts.b.hcintr == 0); + + /* Disable HCINTs */ + DWC_WRITE_REG32(&hc_regs->hcintmsk, 0x0000); + + /* Disable HAINTs */ + DWC_WRITE_REG32(&hc_global_regs->haintmsk, 0x0000); + + /* Read HAINT */ + haint.d32 = DWC_READ_REG32(&hc_global_regs->haint); + + /* Read HCINT */ + hcint.d32 = DWC_READ_REG32(&hc_regs->hcint); + + /* Read HCCHAR */ + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + + /* Clear HCINT */ + DWC_WRITE_REG32(&hc_regs->hcint, hcint.d32); + + /* Clear HAINT */ + DWC_WRITE_REG32(&hc_global_regs->haint, haint.d32); + + /* Clear GINTSTS */ + DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32); + + /* Read GINTSTS */ + gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts); +} +#endif + +/** Handles hub class-specific requests. */ +int dwc_otg_hcd_hub_control(dwc_otg_hcd_t * dwc_otg_hcd, + uint16_t typeReq, + uint16_t wValue, + uint16_t wIndex, uint8_t * buf, uint16_t wLength) +{ + int retval = 0; + + dwc_otg_core_if_t *core_if = dwc_otg_hcd->core_if; + usb_hub_descriptor_t *hub_desc; + hprt0_data_t hprt0 = {.d32 = 0 }; + + uint32_t port_status; + + switch (typeReq) { + case UCR_CLEAR_HUB_FEATURE: + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - " + "ClearHubFeature 0x%x\n", wValue); + switch (wValue) { + case UHF_C_HUB_LOCAL_POWER: + case UHF_C_HUB_OVER_CURRENT: + /* Nothing required here */ + break; + default: + retval = -DWC_E_INVALID; + DWC_ERROR("DWC OTG HCD - " + "ClearHubFeature request %xh unknown\n", + wValue); + } + break; + case UCR_CLEAR_PORT_FEATURE: +#ifdef CONFIG_USB_DWC_OTG_LPM + if (wValue != UHF_PORT_L1) +#endif + if (!wIndex || wIndex > 1) + goto error; + + switch (wValue) { + case UHF_PORT_ENABLE: + DWC_DEBUGPL(DBG_ANY, "DWC OTG HCD HUB CONTROL - " + "ClearPortFeature USB_PORT_FEAT_ENABLE\n"); + hprt0.d32 = dwc_otg_read_hprt0(core_if); + hprt0.b.prtena = 1; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + break; + case UHF_PORT_SUSPEND: + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - " + "ClearPortFeature USB_PORT_FEAT_SUSPEND\n"); + + if (core_if->power_down == 2) { + dwc_otg_host_hibernation_restore(core_if, 0, 0); + } else { + DWC_WRITE_REG32(core_if->pcgcctl, 0); + dwc_mdelay(5); + + hprt0.d32 = dwc_otg_read_hprt0(core_if); + hprt0.b.prtres = 1; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + hprt0.b.prtsusp = 0; + /* Clear Resume bit */ + dwc_mdelay(100); + hprt0.b.prtres = 0; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + } + break; +#ifdef CONFIG_USB_DWC_OTG_LPM + case UHF_PORT_L1: + { + pcgcctl_data_t pcgcctl = {.d32 = 0 }; + glpmcfg_data_t lpmcfg = {.d32 = 0 }; + + lpmcfg.d32 = + DWC_READ_REG32(&core_if-> + core_global_regs->glpmcfg); + lpmcfg.b.en_utmi_sleep = 0; + lpmcfg.b.hird_thres &= (~(1 << 4)); + lpmcfg.b.prt_sleep_sts = 1; + DWC_WRITE_REG32(&core_if-> + core_global_regs->glpmcfg, + lpmcfg.d32); + + /* Clear Enbl_L1Gating bit. */ + pcgcctl.b.enbl_sleep_gating = 1; + DWC_MODIFY_REG32(core_if->pcgcctl, pcgcctl.d32, + 0); + + dwc_mdelay(5); + + hprt0.d32 = dwc_otg_read_hprt0(core_if); + hprt0.b.prtres = 1; + DWC_WRITE_REG32(core_if->host_if->hprt0, + hprt0.d32); + /* This bit will be cleared in wakeup interrupt handle */ + break; + } +#endif + case UHF_PORT_POWER: + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - " + "ClearPortFeature USB_PORT_FEAT_POWER\n"); + hprt0.d32 = dwc_otg_read_hprt0(core_if); + hprt0.b.prtpwr = 0; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + break; + case UHF_PORT_INDICATOR: + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - " + "ClearPortFeature USB_PORT_FEAT_INDICATOR\n"); + /* Port inidicator not supported */ + break; + case UHF_C_PORT_CONNECTION: + /* Clears drivers internal connect status change + * flag */ + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - " + "ClearPortFeature USB_PORT_FEAT_C_CONNECTION\n"); + dwc_otg_hcd->flags.b.port_connect_status_change = 0; + break; + case UHF_C_PORT_RESET: + /* Clears the driver's internal Port Reset Change + * flag */ + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - " + "ClearPortFeature USB_PORT_FEAT_C_RESET\n"); + dwc_otg_hcd->flags.b.port_reset_change = 0; + break; + case UHF_C_PORT_ENABLE: + /* Clears the driver's internal Port + * Enable/Disable Change flag */ + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - " + "ClearPortFeature USB_PORT_FEAT_C_ENABLE\n"); + dwc_otg_hcd->flags.b.port_enable_change = 0; + break; + case UHF_C_PORT_SUSPEND: + /* Clears the driver's internal Port Suspend + * Change flag, which is set when resume signaling on + * the host port is complete */ + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - " + "ClearPortFeature USB_PORT_FEAT_C_SUSPEND\n"); + dwc_otg_hcd->flags.b.port_suspend_change = 0; + break; +#ifdef CONFIG_USB_DWC_OTG_LPM + case UHF_C_PORT_L1: + dwc_otg_hcd->flags.b.port_l1_change = 0; + break; +#endif + case UHF_C_PORT_OVER_CURRENT: + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - " + "ClearPortFeature USB_PORT_FEAT_C_OVER_CURRENT\n"); + dwc_otg_hcd->flags.b.port_over_current_change = 0; + break; + default: + retval = -DWC_E_INVALID; + DWC_ERROR("DWC OTG HCD - " + "ClearPortFeature request %xh " + "unknown or unsupported\n", wValue); + } + break; + case UCR_GET_HUB_DESCRIPTOR: + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - " + "GetHubDescriptor\n"); + hub_desc = (usb_hub_descriptor_t *) buf; + hub_desc->bDescLength = 9; + hub_desc->bDescriptorType = 0x29; + hub_desc->bNbrPorts = 1; + USETW(hub_desc->wHubCharacteristics, 0x08); + hub_desc->bPwrOn2PwrGood = 1; + hub_desc->bHubContrCurrent = 0; + hub_desc->DeviceRemovable[0] = 0; + hub_desc->DeviceRemovable[1] = 0xff; + break; + case UCR_GET_HUB_STATUS: + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - " + "GetHubStatus\n"); + DWC_MEMSET(buf, 0, 4); + break; + case UCR_GET_PORT_STATUS: + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - " + "GetPortStatus wIndex = 0x%04x FLAGS=0x%08x\n", + wIndex, dwc_otg_hcd->flags.d32); + if (!wIndex || wIndex > 1) + goto error; + + port_status = 0; + + if (dwc_otg_hcd->flags.b.port_connect_status_change) + port_status |= (1 << UHF_C_PORT_CONNECTION); + + if (dwc_otg_hcd->flags.b.port_enable_change) + port_status |= (1 << UHF_C_PORT_ENABLE); + + if (dwc_otg_hcd->flags.b.port_suspend_change) + port_status |= (1 << UHF_C_PORT_SUSPEND); + + if (dwc_otg_hcd->flags.b.port_l1_change) + port_status |= (1 << UHF_C_PORT_L1); + + if (dwc_otg_hcd->flags.b.port_reset_change) { + port_status |= (1 << UHF_C_PORT_RESET); + } + + if (dwc_otg_hcd->flags.b.port_over_current_change) { + DWC_WARN("Overcurrent change detected\n"); + port_status |= (1 << UHF_C_PORT_OVER_CURRENT); + } + + if (!dwc_otg_hcd->flags.b.port_connect_status) { + /* + * The port is disconnected, which means the core is + * either in device mode or it soon will be. Just + * return 0's for the remainder of the port status + * since the port register can't be read if the core + * is in device mode. + */ + *((__le32 *) buf) = dwc_cpu_to_le32(&port_status); + break; + } + + hprt0.d32 = DWC_READ_REG32(core_if->host_if->hprt0); + DWC_DEBUGPL(DBG_HCDV, " HPRT0: 0x%08x\n", hprt0.d32); + + if (hprt0.b.prtconnsts) + port_status |= (1 << UHF_PORT_CONNECTION); + + if (hprt0.b.prtena) + port_status |= (1 << UHF_PORT_ENABLE); + + if (hprt0.b.prtsusp) + port_status |= (1 << UHF_PORT_SUSPEND); + + if (hprt0.b.prtovrcurract) + port_status |= (1 << UHF_PORT_OVER_CURRENT); + + if (hprt0.b.prtrst) + port_status |= (1 << UHF_PORT_RESET); + + if (hprt0.b.prtpwr) + port_status |= (1 << UHF_PORT_POWER); + + if (hprt0.b.prtspd == DWC_HPRT0_PRTSPD_HIGH_SPEED) + port_status |= (1 << UHF_PORT_HIGH_SPEED); + else if (hprt0.b.prtspd == DWC_HPRT0_PRTSPD_LOW_SPEED) + port_status |= (1 << UHF_PORT_LOW_SPEED); + + if (hprt0.b.prttstctl) + port_status |= (1 << UHF_PORT_TEST); + if (dwc_otg_get_lpm_portsleepstatus(dwc_otg_hcd->core_if)) { + port_status |= (1 << UHF_PORT_L1); + } + /* + For Synopsys HW emulation of Power down wkup_control asserts the + hreset_n and prst_n on suspned. This causes the HPRT0 to be zero. + We intentionally tell the software that port is in L2Suspend state. + Only for STE. + */ + if ((core_if->power_down == 2) + && (core_if->hibernation_suspend == 1)) { + port_status |= (1 << UHF_PORT_SUSPEND); + } + /* USB_PORT_FEAT_INDICATOR unsupported always 0 */ + + *((__le32 *) buf) = dwc_cpu_to_le32(&port_status); + + break; + case UCR_SET_HUB_FEATURE: + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - " + "SetHubFeature\n"); + /* No HUB features supported */ + break; + case UCR_SET_PORT_FEATURE: + if (wValue != UHF_PORT_TEST && (!wIndex || wIndex > 1)) + goto error; + + if (!dwc_otg_hcd->flags.b.port_connect_status) { + /* + * The port is disconnected, which means the core is + * either in device mode or it soon will be. Just + * return without doing anything since the port + * register can't be written if the core is in device + * mode. + */ + break; + } + + switch (wValue) { + case UHF_PORT_SUSPEND: + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - " + "SetPortFeature - USB_PORT_FEAT_SUSPEND\n"); + if (dwc_otg_hcd_otg_port(dwc_otg_hcd) != wIndex) { + goto error; + } + if (core_if->power_down == 2) { + int timeout = 300; + dwc_irqflags_t flags; + pcgcctl_data_t pcgcctl = {.d32 = 0 }; + gpwrdn_data_t gpwrdn = {.d32 = 0 }; + gusbcfg_data_t gusbcfg = {.d32 = 0 }; +#ifdef DWC_DEV_SRPCAP + int32_t otg_cap_param = core_if->core_params->otg_cap; +#endif + DWC_PRINTF("Preparing for complete power-off\n"); + + /* Save registers before hibernation */ + dwc_otg_save_global_regs(core_if); + dwc_otg_save_host_regs(core_if); + + hprt0.d32 = dwc_otg_read_hprt0(core_if); + hprt0.b.prtsusp = 1; + hprt0.b.prtena = 0; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + /* Spin hprt0.b.prtsusp to became 1 */ + do { + hprt0.d32 = dwc_otg_read_hprt0(core_if); + if (hprt0.b.prtsusp) { + break; + } + dwc_mdelay(1); + } while (--timeout); + if (!timeout) { + DWC_WARN("Suspend wasn't genereted\n"); + } + dwc_udelay(10); + + /* + * We need to disable interrupts to prevent servicing of any IRQ + * during going to hibernation + */ + DWC_SPINLOCK_IRQSAVE(dwc_otg_hcd->lock, &flags); + core_if->lx_state = DWC_OTG_L2; +#ifdef DWC_DEV_SRPCAP + hprt0.d32 = dwc_otg_read_hprt0(core_if); + hprt0.b.prtpwr = 0; + hprt0.b.prtena = 0; + DWC_WRITE_REG32(core_if->host_if->hprt0, + hprt0.d32); +#endif + gusbcfg.d32 = + DWC_READ_REG32(&core_if->core_global_regs-> + gusbcfg); + if (gusbcfg.b.ulpi_utmi_sel == 1) { + /* ULPI interface */ + /* Suspend the Phy Clock */ + pcgcctl.d32 = 0; + pcgcctl.b.stoppclk = 1; + DWC_MODIFY_REG32(core_if->pcgcctl, 0, + pcgcctl.d32); + dwc_udelay(10); + gpwrdn.b.pmuactv = 1; + DWC_MODIFY_REG32(&core_if-> + core_global_regs-> + gpwrdn, 0, gpwrdn.d32); + } else { + /* UTMI+ Interface */ + gpwrdn.b.pmuactv = 1; + DWC_MODIFY_REG32(&core_if-> + core_global_regs-> + gpwrdn, 0, gpwrdn.d32); + dwc_udelay(10); + pcgcctl.b.stoppclk = 1; + DWC_MODIFY_REG32(core_if->pcgcctl, 0, pcgcctl.d32); + dwc_udelay(10); + } +#ifdef DWC_DEV_SRPCAP + gpwrdn.d32 = 0; + gpwrdn.b.dis_vbus = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs-> + gpwrdn, 0, gpwrdn.d32); +#endif + gpwrdn.d32 = 0; + gpwrdn.b.pmuintsel = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs-> + gpwrdn, 0, gpwrdn.d32); + dwc_udelay(10); + + gpwrdn.d32 = 0; +#ifdef DWC_DEV_SRPCAP + gpwrdn.b.srp_det_msk = 1; +#endif + gpwrdn.b.disconn_det_msk = 1; + gpwrdn.b.lnstchng_msk = 1; + gpwrdn.b.sts_chngint_msk = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs-> + gpwrdn, 0, gpwrdn.d32); + dwc_udelay(10); + + /* Enable Power Down Clamp and all interrupts in GPWRDN */ + gpwrdn.d32 = 0; + gpwrdn.b.pwrdnclmp = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs-> + gpwrdn, 0, gpwrdn.d32); + dwc_udelay(10); + + /* Switch off VDD */ + gpwrdn.d32 = 0; + gpwrdn.b.pwrdnswtch = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs-> + gpwrdn, 0, gpwrdn.d32); + +#ifdef DWC_DEV_SRPCAP + if (otg_cap_param == DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE) + { + core_if->pwron_timer_started = 1; + DWC_TIMER_SCHEDULE(core_if->pwron_timer, 6000 /* 6 secs */ ); + } +#endif + /* Save gpwrdn register for further usage if stschng interrupt */ + core_if->gr_backup->gpwrdn_local = + DWC_READ_REG32(&core_if->core_global_regs->gpwrdn); + + /* Set flag to indicate that we are in hibernation */ + core_if->hibernation_suspend = 1; + DWC_SPINUNLOCK_IRQRESTORE(dwc_otg_hcd->lock,flags); + + DWC_PRINTF("Host hibernation completed\n"); + // Exit from case statement + break; + + } + if (dwc_otg_hcd_otg_port(dwc_otg_hcd) == wIndex && + dwc_otg_hcd->fops->get_b_hnp_enable(dwc_otg_hcd)) { + gotgctl_data_t gotgctl = {.d32 = 0 }; + gotgctl.b.hstsethnpen = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs-> + gotgctl, 0, gotgctl.d32); + core_if->op_state = A_SUSPEND; + } + hprt0.d32 = dwc_otg_read_hprt0(core_if); + hprt0.b.prtsusp = 1; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + { + dwc_irqflags_t flags; + /* Update lx_state */ + DWC_SPINLOCK_IRQSAVE(dwc_otg_hcd->lock, &flags); + core_if->lx_state = DWC_OTG_L2; + DWC_SPINUNLOCK_IRQRESTORE(dwc_otg_hcd->lock, flags); + } + /* Suspend the Phy Clock */ + { + pcgcctl_data_t pcgcctl = {.d32 = 0 }; + pcgcctl.b.stoppclk = 1; + DWC_MODIFY_REG32(core_if->pcgcctl, 0, + pcgcctl.d32); + dwc_udelay(10); + } + + /* For HNP the bus must be suspended for at least 200ms. */ + if (dwc_otg_hcd->fops->get_b_hnp_enable(dwc_otg_hcd)) { + pcgcctl_data_t pcgcctl = {.d32 = 0 }; + pcgcctl.b.stoppclk = 1; + DWC_MODIFY_REG32(core_if->pcgcctl, pcgcctl.d32, 0); + dwc_mdelay(200); + } + + /** @todo - check how sw can wait for 1 sec to check asesvld??? */ +#if 0 //vahrama !!!!!!!!!!!!!!!!!! + if (core_if->adp_enable) { + gotgctl_data_t gotgctl = {.d32 = 0 }; + gpwrdn_data_t gpwrdn; + + while (gotgctl.b.asesvld == 1) { + gotgctl.d32 = + DWC_READ_REG32(&core_if-> + core_global_regs-> + gotgctl); + dwc_mdelay(100); + } + + /* Enable Power Down Logic */ + gpwrdn.d32 = 0; + gpwrdn.b.pmuactv = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs-> + gpwrdn, 0, gpwrdn.d32); + + /* Unmask SRP detected interrupt from Power Down Logic */ + gpwrdn.d32 = 0; + gpwrdn.b.srp_det_msk = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs-> + gpwrdn, 0, gpwrdn.d32); + + dwc_otg_adp_probe_start(core_if); + } +#endif + break; + case UHF_PORT_POWER: + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - " + "SetPortFeature - USB_PORT_FEAT_POWER\n"); + hprt0.d32 = dwc_otg_read_hprt0(core_if); + hprt0.b.prtpwr = 1; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + break; + case UHF_PORT_RESET: + if ((core_if->power_down == 2) + && (core_if->hibernation_suspend == 1)) { + /* If we are going to exit from Hibernated + * state via USB RESET. + */ + dwc_otg_host_hibernation_restore(core_if, 0, 1); + } else { + hprt0.d32 = dwc_otg_read_hprt0(core_if); + + DWC_DEBUGPL(DBG_HCD, + "DWC OTG HCD HUB CONTROL - " + "SetPortFeature - USB_PORT_FEAT_RESET\n"); + { + pcgcctl_data_t pcgcctl = {.d32 = 0 }; + pcgcctl.b.enbl_sleep_gating = 1; + pcgcctl.b.stoppclk = 1; + DWC_MODIFY_REG32(core_if->pcgcctl, pcgcctl.d32, 0); + DWC_WRITE_REG32(core_if->pcgcctl, 0); + } +#ifdef CONFIG_USB_DWC_OTG_LPM + { + glpmcfg_data_t lpmcfg; + lpmcfg.d32 = + DWC_READ_REG32(&core_if->core_global_regs->glpmcfg); + if (lpmcfg.b.prt_sleep_sts) { + lpmcfg.b.en_utmi_sleep = 0; + lpmcfg.b.hird_thres &= (~(1 << 4)); + DWC_WRITE_REG32 + (&core_if->core_global_regs->glpmcfg, + lpmcfg.d32); + dwc_mdelay(1); + } + } +#endif + hprt0.d32 = dwc_otg_read_hprt0(core_if); + /* Clear suspend bit if resetting from suspended state. */ + hprt0.b.prtsusp = 0; + /* When B-Host the Port reset bit is set in + * the Start HCD Callback function, so that + * the reset is started within 1ms of the HNP + * success interrupt. */ + if (!dwc_otg_hcd_is_b_host(dwc_otg_hcd)) { + hprt0.b.prtpwr = 1; + hprt0.b.prtrst = 1; + DWC_PRINTF("Indeed it is in host mode hprt0 = %08x\n",hprt0.d32); + DWC_WRITE_REG32(core_if->host_if->hprt0, + hprt0.d32); + } + /* Clear reset bit in 10ms (FS/LS) or 50ms (HS) */ + dwc_mdelay(60); + hprt0.b.prtrst = 0; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + core_if->lx_state = DWC_OTG_L0; /* Now back to the on state */ + } + break; +#ifdef DWC_HS_ELECT_TST + case UHF_PORT_TEST: + { + uint32_t t; + gintmsk_data_t gintmsk; + + t = (wIndex >> 8); /* MSB wIndex USB */ + DWC_DEBUGPL(DBG_HCD, + "DWC OTG HCD HUB CONTROL - " + "SetPortFeature - USB_PORT_FEAT_TEST %d\n", + t); + DWC_WARN("USB_PORT_FEAT_TEST %d\n", t); + if (t < 6) { + hprt0.d32 = dwc_otg_read_hprt0(core_if); + hprt0.b.prttstctl = t; + DWC_WRITE_REG32(core_if->host_if->hprt0, + hprt0.d32); + } else { + /* Setup global vars with reg addresses (quick and + * dirty hack, should be cleaned up) + */ + global_regs = core_if->core_global_regs; + hc_global_regs = + core_if->host_if->host_global_regs; + hc_regs = + (dwc_otg_hc_regs_t *) ((char *) + global_regs + + 0x500); + data_fifo = + (uint32_t *) ((char *)global_regs + + 0x1000); + + if (t == 6) { /* HS_HOST_PORT_SUSPEND_RESUME */ + /* Save current interrupt mask */ + gintmsk.d32 = + DWC_READ_REG32 + (&global_regs->gintmsk); + + /* Disable all interrupts while we muck with + * the hardware directly + */ + DWC_WRITE_REG32(&global_regs->gintmsk, 0); + + /* 15 second delay per the test spec */ + dwc_mdelay(15000); + + /* Drive suspend on the root port */ + hprt0.d32 = + dwc_otg_read_hprt0(core_if); + hprt0.b.prtsusp = 1; + hprt0.b.prtres = 0; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + + /* 15 second delay per the test spec */ + dwc_mdelay(15000); + + /* Drive resume on the root port */ + hprt0.d32 = + dwc_otg_read_hprt0(core_if); + hprt0.b.prtsusp = 0; + hprt0.b.prtres = 1; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + dwc_mdelay(100); + + /* Clear the resume bit */ + hprt0.b.prtres = 0; + DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32); + + /* Restore interrupts */ + DWC_WRITE_REG32(&global_regs->gintmsk, gintmsk.d32); + } else if (t == 7) { /* SINGLE_STEP_GET_DEVICE_DESCRIPTOR setup */ + /* Save current interrupt mask */ + gintmsk.d32 = + DWC_READ_REG32 + (&global_regs->gintmsk); + + /* Disable all interrupts while we muck with + * the hardware directly + */ + DWC_WRITE_REG32(&global_regs->gintmsk, 0); + + /* 15 second delay per the test spec */ + dwc_mdelay(15000); + + /* Send the Setup packet */ + do_setup(); + + /* 15 second delay so nothing else happens for awhile */ + dwc_mdelay(15000); + + /* Restore interrupts */ + DWC_WRITE_REG32(&global_regs->gintmsk, gintmsk.d32); + } else if (t == 8) { /* SINGLE_STEP_GET_DEVICE_DESCRIPTOR execute */ + /* Save current interrupt mask */ + gintmsk.d32 = + DWC_READ_REG32 + (&global_regs->gintmsk); + + /* Disable all interrupts while we muck with + * the hardware directly + */ + DWC_WRITE_REG32(&global_regs->gintmsk, 0); + + /* Send the Setup packet */ + do_setup(); + + /* 15 second delay so nothing else happens for awhile */ + dwc_mdelay(15000); + + /* Send the In and Ack packets */ + do_in_ack(); + + /* 15 second delay so nothing else happens for awhile */ + dwc_mdelay(15000); + + /* Restore interrupts */ + DWC_WRITE_REG32(&global_regs->gintmsk, gintmsk.d32); + } + } + break; + } +#endif /* DWC_HS_ELECT_TST */ + + case UHF_PORT_INDICATOR: + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - " + "SetPortFeature - USB_PORT_FEAT_INDICATOR\n"); + /* Not supported */ + break; + default: + retval = -DWC_E_INVALID; + DWC_ERROR("DWC OTG HCD - " + "SetPortFeature request %xh " + "unknown or unsupported\n", wValue); + break; + } + break; +#ifdef CONFIG_USB_DWC_OTG_LPM + case UCR_SET_AND_TEST_PORT_FEATURE: + if (wValue != UHF_PORT_L1) { + goto error; + } + { + int portnum, hird, devaddr, remwake; + glpmcfg_data_t lpmcfg; + uint32_t time_usecs; + gintsts_data_t gintsts; + gintmsk_data_t gintmsk; + + if (!dwc_otg_get_param_lpm_enable(core_if)) { + goto error; + } + if (wValue != UHF_PORT_L1 || wLength != 1) { + goto error; + } + /* Check if the port currently is in SLEEP state */ + lpmcfg.d32 = + DWC_READ_REG32(&core_if->core_global_regs->glpmcfg); + if (lpmcfg.b.prt_sleep_sts) { + DWC_INFO("Port is already in sleep mode\n"); + buf[0] = 0; /* Return success */ + break; + } + + portnum = wIndex & 0xf; + hird = (wIndex >> 4) & 0xf; + devaddr = (wIndex >> 8) & 0x7f; + remwake = (wIndex >> 15); + + if (portnum != 1) { + retval = -DWC_E_INVALID; + DWC_WARN + ("Wrong port number(%d) in SetandTestPortFeature request\n", + portnum); + break; + } + + DWC_PRINTF + ("SetandTestPortFeature request: portnum = %d, hird = %d, devaddr = %d, rewake = %d\n", + portnum, hird, devaddr, remwake); + /* Disable LPM interrupt */ + gintmsk.d32 = 0; + gintmsk.b.lpmtranrcvd = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gintmsk, + gintmsk.d32, 0); + + if (dwc_otg_hcd_send_lpm + (dwc_otg_hcd, devaddr, hird, remwake)) { + retval = -DWC_E_INVALID; + break; + } + + time_usecs = 10 * (lpmcfg.b.retry_count + 1); + /* We will consider timeout if time_usecs microseconds pass, + * and we don't receive LPM transaction status. + * After receiving non-error responce(ACK/NYET/STALL) from device, + * core will set lpmtranrcvd bit. + */ + do { + gintsts.d32 = + DWC_READ_REG32(&core_if->core_global_regs->gintsts); + if (gintsts.b.lpmtranrcvd) { + break; + } + dwc_udelay(1); + } while (--time_usecs); + /* lpm_int bit will be cleared in LPM interrupt handler */ + + /* Now fill status + * 0x00 - Success + * 0x10 - NYET + * 0x11 - Timeout + */ + if (!gintsts.b.lpmtranrcvd) { + buf[0] = 0x3; /* Completion code is Timeout */ + dwc_otg_hcd_free_hc_from_lpm(dwc_otg_hcd); + } else { + lpmcfg.d32 = + DWC_READ_REG32(&core_if->core_global_regs->glpmcfg); + if (lpmcfg.b.lpm_resp == 0x3) { + /* ACK responce from the device */ + buf[0] = 0x00; /* Success */ + } else if (lpmcfg.b.lpm_resp == 0x2) { + /* NYET responce from the device */ + buf[0] = 0x2; + } else { + /* Otherwise responce with Timeout */ + buf[0] = 0x3; + } + } + DWC_PRINTF("Device responce to LPM trans is %x\n", + lpmcfg.b.lpm_resp); + DWC_MODIFY_REG32(&core_if->core_global_regs->gintmsk, 0, + gintmsk.d32); + + break; + } +#endif /* CONFIG_USB_DWC_OTG_LPM */ + default: +error: + retval = -DWC_E_INVALID; + DWC_WARN("DWC OTG HCD - " + "Unknown hub control request type or invalid typeReq: %xh wIndex: %xh wValue: %xh\n", + typeReq, wIndex, wValue); + break; + } + + return retval; +} + +#ifdef CONFIG_USB_DWC_OTG_LPM +/** Returns index of host channel to perform LPM transaction. */ +int dwc_otg_hcd_get_hc_for_lpm_tran(dwc_otg_hcd_t * hcd, uint8_t devaddr) +{ + dwc_otg_core_if_t *core_if = hcd->core_if; + dwc_hc_t *hc; + hcchar_data_t hcchar; + gintmsk_data_t gintmsk = {.d32 = 0 }; + + if (DWC_CIRCLEQ_EMPTY(&hcd->free_hc_list)) { + DWC_PRINTF("No free channel to select for LPM transaction\n"); + return -1; + } + + hc = DWC_CIRCLEQ_FIRST(&hcd->free_hc_list); + + /* Mask host channel interrupts. */ + gintmsk.b.hcintr = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gintmsk, gintmsk.d32, 0); + + /* Fill fields that core needs for LPM transaction */ + hcchar.b.devaddr = devaddr; + hcchar.b.epnum = 0; + hcchar.b.eptype = DWC_OTG_EP_TYPE_CONTROL; + hcchar.b.mps = 64; + hcchar.b.lspddev = (hc->speed == DWC_OTG_EP_SPEED_LOW); + hcchar.b.epdir = 0; /* OUT */ + DWC_WRITE_REG32(&core_if->host_if->hc_regs[hc->hc_num]->hcchar, + hcchar.d32); + + /* Remove the host channel from the free list. */ + DWC_CIRCLEQ_REMOVE_INIT(&hcd->free_hc_list, hc, hc_list_entry); + + DWC_PRINTF("hcnum = %d devaddr = %d\n", hc->hc_num, devaddr); + + return hc->hc_num; +} + +/** Release hc after performing LPM transaction */ +void dwc_otg_hcd_free_hc_from_lpm(dwc_otg_hcd_t * hcd) +{ + dwc_hc_t *hc; + glpmcfg_data_t lpmcfg; + uint8_t hc_num; + + lpmcfg.d32 = DWC_READ_REG32(&hcd->core_if->core_global_regs->glpmcfg); + hc_num = lpmcfg.b.lpm_chan_index; + + hc = hcd->hc_ptr_array[hc_num]; + + DWC_PRINTF("Freeing channel %d after LPM\n", hc_num); + /* Return host channel to free list */ + DWC_CIRCLEQ_INSERT_TAIL(&hcd->free_hc_list, hc, hc_list_entry); +} + +int dwc_otg_hcd_send_lpm(dwc_otg_hcd_t * hcd, uint8_t devaddr, uint8_t hird, + uint8_t bRemoteWake) +{ + glpmcfg_data_t lpmcfg; + pcgcctl_data_t pcgcctl = {.d32 = 0 }; + int channel; + + channel = dwc_otg_hcd_get_hc_for_lpm_tran(hcd, devaddr); + if (channel < 0) { + return channel; + } + + pcgcctl.b.enbl_sleep_gating = 1; + DWC_MODIFY_REG32(hcd->core_if->pcgcctl, 0, pcgcctl.d32); + + /* Read LPM config register */ + lpmcfg.d32 = DWC_READ_REG32(&hcd->core_if->core_global_regs->glpmcfg); + + /* Program LPM transaction fields */ + lpmcfg.b.rem_wkup_en = bRemoteWake; + lpmcfg.b.hird = hird; + lpmcfg.b.hird_thres = 0x1c; + lpmcfg.b.lpm_chan_index = channel; + lpmcfg.b.en_utmi_sleep = 1; + /* Program LPM config register */ + DWC_WRITE_REG32(&hcd->core_if->core_global_regs->glpmcfg, lpmcfg.d32); + + /* Send LPM transaction */ + lpmcfg.b.send_lpm = 1; + DWC_WRITE_REG32(&hcd->core_if->core_global_regs->glpmcfg, lpmcfg.d32); + + return 0; +} + +#endif /* CONFIG_USB_DWC_OTG_LPM */ + +int dwc_otg_hcd_is_status_changed(dwc_otg_hcd_t * hcd, int port) +{ + int retval; + + if (port != 1) { + return -DWC_E_INVALID; + } + + retval = (hcd->flags.b.port_connect_status_change || + hcd->flags.b.port_reset_change || + hcd->flags.b.port_enable_change || + hcd->flags.b.port_suspend_change || + hcd->flags.b.port_over_current_change); +#ifdef DEBUG + if (retval) { + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB STATUS DATA:" + " Root port status changed\n"); + DWC_DEBUGPL(DBG_HCDV, " port_connect_status_change: %d\n", + hcd->flags.b.port_connect_status_change); + DWC_DEBUGPL(DBG_HCDV, " port_reset_change: %d\n", + hcd->flags.b.port_reset_change); + DWC_DEBUGPL(DBG_HCDV, " port_enable_change: %d\n", + hcd->flags.b.port_enable_change); + DWC_DEBUGPL(DBG_HCDV, " port_suspend_change: %d\n", + hcd->flags.b.port_suspend_change); + DWC_DEBUGPL(DBG_HCDV, " port_over_current_change: %d\n", + hcd->flags.b.port_over_current_change); + } +#endif + return retval; +} + +int dwc_otg_hcd_get_frame_number(dwc_otg_hcd_t * dwc_otg_hcd) +{ + hfnum_data_t hfnum; + hfnum.d32 = + DWC_READ_REG32(&dwc_otg_hcd->core_if->host_if->host_global_regs-> + hfnum); + +#ifdef DEBUG_SOF + DWC_DEBUGPL(DBG_HCDV, "DWC OTG HCD GET FRAME NUMBER %d\n", + hfnum.b.frnum); +#endif + return hfnum.b.frnum; +} + +int dwc_otg_hcd_start(dwc_otg_hcd_t * hcd, + struct dwc_otg_hcd_function_ops *fops) +{ + int retval = 0; + + hcd->fops = fops; + if (!dwc_otg_is_device_mode(hcd->core_if) && + (!hcd->core_if->adp_enable || hcd->core_if->adp.adp_started)) { + dwc_otg_hcd_reinit(hcd); + } else { + retval = -DWC_E_NO_DEVICE; + } + + return retval; +} + +void *dwc_otg_hcd_get_priv_data(dwc_otg_hcd_t * hcd) +{ + return hcd->priv; +} + +void dwc_otg_hcd_set_priv_data(dwc_otg_hcd_t * hcd, void *priv_data) +{ + hcd->priv = priv_data; +} + +uint32_t dwc_otg_hcd_otg_port(dwc_otg_hcd_t * hcd) +{ + return hcd->otg_port; +} + +uint32_t dwc_otg_hcd_is_b_host(dwc_otg_hcd_t * hcd) +{ + uint32_t is_b_host; + if (hcd->core_if->op_state == B_HOST) { + is_b_host = 1; + } else { + is_b_host = 0; + } + + return is_b_host; +} + +dwc_otg_hcd_urb_t *dwc_otg_hcd_urb_alloc(dwc_otg_hcd_t * hcd, + int iso_desc_count, int atomic_alloc) +{ + dwc_otg_hcd_urb_t *dwc_otg_urb; + uint32_t size; + + size = + sizeof(*dwc_otg_urb) + + iso_desc_count * sizeof(struct dwc_otg_hcd_iso_packet_desc); + if (atomic_alloc) + dwc_otg_urb = DWC_ALLOC_ATOMIC(size); + else + dwc_otg_urb = DWC_ALLOC(size); + + if (dwc_otg_urb) + dwc_otg_urb->packet_count = iso_desc_count; + else { + DWC_ERROR("**** DWC OTG HCD URB alloc - " + "%salloc of %db failed\n", + atomic_alloc?"atomic ":"", size); + } + return dwc_otg_urb; +} + +void dwc_otg_hcd_urb_set_pipeinfo(dwc_otg_hcd_urb_t * dwc_otg_urb, + uint8_t dev_addr, uint8_t ep_num, + uint8_t ep_type, uint8_t ep_dir, uint16_t mps) +{ + dwc_otg_hcd_fill_pipe(&dwc_otg_urb->pipe_info, dev_addr, ep_num, + ep_type, ep_dir, mps); +#if 0 + DWC_PRINTF + ("addr = %d, ep_num = %d, ep_dir = 0x%x, ep_type = 0x%x, mps = %d\n", + dev_addr, ep_num, ep_dir, ep_type, mps); +#endif +} + +void dwc_otg_hcd_urb_set_params(dwc_otg_hcd_urb_t * dwc_otg_urb, + void *urb_handle, void *buf, dwc_dma_t dma, + uint32_t buflen, void *setup_packet, + dwc_dma_t setup_dma, uint32_t flags, + uint16_t interval) +{ + dwc_otg_urb->priv = urb_handle; + dwc_otg_urb->buf = buf; + dwc_otg_urb->dma = dma; + dwc_otg_urb->length = buflen; + dwc_otg_urb->setup_packet = setup_packet; + dwc_otg_urb->setup_dma = setup_dma; + dwc_otg_urb->flags = flags; + dwc_otg_urb->interval = interval; + dwc_otg_urb->status = -DWC_E_IN_PROGRESS; +} + +uint32_t dwc_otg_hcd_urb_get_status(dwc_otg_hcd_urb_t * dwc_otg_urb) +{ + return dwc_otg_urb->status; +} + +uint32_t dwc_otg_hcd_urb_get_actual_length(dwc_otg_hcd_urb_t * dwc_otg_urb) +{ + return dwc_otg_urb->actual_length; +} + +uint32_t dwc_otg_hcd_urb_get_error_count(dwc_otg_hcd_urb_t * dwc_otg_urb) +{ + return dwc_otg_urb->error_count; +} + +void dwc_otg_hcd_urb_set_iso_desc_params(dwc_otg_hcd_urb_t * dwc_otg_urb, + int desc_num, uint32_t offset, + uint32_t length) +{ + dwc_otg_urb->iso_descs[desc_num].offset = offset; + dwc_otg_urb->iso_descs[desc_num].length = length; +} + +uint32_t dwc_otg_hcd_urb_get_iso_desc_status(dwc_otg_hcd_urb_t * dwc_otg_urb, + int desc_num) +{ + return dwc_otg_urb->iso_descs[desc_num].status; +} + +uint32_t dwc_otg_hcd_urb_get_iso_desc_actual_length(dwc_otg_hcd_urb_t * + dwc_otg_urb, int desc_num) +{ + return dwc_otg_urb->iso_descs[desc_num].actual_length; +} + +int dwc_otg_hcd_is_bandwidth_allocated(dwc_otg_hcd_t * hcd, void *ep_handle) +{ + int allocated = 0; + dwc_otg_qh_t *qh = (dwc_otg_qh_t *) ep_handle; + + if (qh) { + if (!DWC_LIST_EMPTY(&qh->qh_list_entry)) { + allocated = 1; + } + } + return allocated; +} + +int dwc_otg_hcd_is_bandwidth_freed(dwc_otg_hcd_t * hcd, void *ep_handle) +{ + dwc_otg_qh_t *qh = (dwc_otg_qh_t *) ep_handle; + int freed = 0; + DWC_ASSERT(qh, "qh is not allocated\n"); + + if (DWC_LIST_EMPTY(&qh->qh_list_entry)) { + freed = 1; + } + + return freed; +} + +uint8_t dwc_otg_hcd_get_ep_bandwidth(dwc_otg_hcd_t * hcd, void *ep_handle) +{ + dwc_otg_qh_t *qh = (dwc_otg_qh_t *) ep_handle; + DWC_ASSERT(qh, "qh is not allocated\n"); + return qh->usecs; +} + +void dwc_otg_hcd_dump_state(dwc_otg_hcd_t * hcd) +{ +#ifdef DEBUG + int num_channels; + int i; + gnptxsts_data_t np_tx_status; + hptxsts_data_t p_tx_status; + + num_channels = hcd->core_if->core_params->host_channels; + DWC_PRINTF("\n"); + DWC_PRINTF + ("************************************************************\n"); + DWC_PRINTF("HCD State:\n"); + DWC_PRINTF(" Num channels: %d\n", num_channels); + for (i = 0; i < num_channels; i++) { + dwc_hc_t *hc = hcd->hc_ptr_array[i]; + DWC_PRINTF(" Channel %d:\n", i); + DWC_PRINTF(" dev_addr: %d, ep_num: %d, ep_is_in: %d\n", + hc->dev_addr, hc->ep_num, hc->ep_is_in); + DWC_PRINTF(" speed: %d\n", hc->speed); + DWC_PRINTF(" ep_type: %d\n", hc->ep_type); + DWC_PRINTF(" max_packet: %d\n", hc->max_packet); + DWC_PRINTF(" data_pid_start: %d\n", hc->data_pid_start); + DWC_PRINTF(" multi_count: %d\n", hc->multi_count); + DWC_PRINTF(" xfer_started: %d\n", hc->xfer_started); + DWC_PRINTF(" xfer_buff: %p\n", hc->xfer_buff); + DWC_PRINTF(" xfer_len: %d\n", hc->xfer_len); + DWC_PRINTF(" xfer_count: %d\n", hc->xfer_count); + DWC_PRINTF(" halt_on_queue: %d\n", hc->halt_on_queue); + DWC_PRINTF(" halt_pending: %d\n", hc->halt_pending); + DWC_PRINTF(" halt_status: %d\n", hc->halt_status); + DWC_PRINTF(" do_split: %d\n", hc->do_split); + DWC_PRINTF(" complete_split: %d\n", hc->complete_split); + DWC_PRINTF(" hub_addr: %d\n", hc->hub_addr); + DWC_PRINTF(" port_addr: %d\n", hc->port_addr); + DWC_PRINTF(" xact_pos: %d\n", hc->xact_pos); + DWC_PRINTF(" requests: %d\n", hc->requests); + DWC_PRINTF(" qh: %p\n", hc->qh); + if (hc->xfer_started) { + hfnum_data_t hfnum; + hcchar_data_t hcchar; + hctsiz_data_t hctsiz; + hcint_data_t hcint; + hcintmsk_data_t hcintmsk; + hfnum.d32 = + DWC_READ_REG32(&hcd->core_if-> + host_if->host_global_regs->hfnum); + hcchar.d32 = + DWC_READ_REG32(&hcd->core_if->host_if-> + hc_regs[i]->hcchar); + hctsiz.d32 = + DWC_READ_REG32(&hcd->core_if->host_if-> + hc_regs[i]->hctsiz); + hcint.d32 = + DWC_READ_REG32(&hcd->core_if->host_if-> + hc_regs[i]->hcint); + hcintmsk.d32 = + DWC_READ_REG32(&hcd->core_if->host_if-> + hc_regs[i]->hcintmsk); + DWC_PRINTF(" hfnum: 0x%08x\n", hfnum.d32); + DWC_PRINTF(" hcchar: 0x%08x\n", hcchar.d32); + DWC_PRINTF(" hctsiz: 0x%08x\n", hctsiz.d32); + DWC_PRINTF(" hcint: 0x%08x\n", hcint.d32); + DWC_PRINTF(" hcintmsk: 0x%08x\n", hcintmsk.d32); + } + if (hc->xfer_started && hc->qh) { + dwc_otg_qtd_t *qtd; + dwc_otg_hcd_urb_t *urb; + + DWC_CIRCLEQ_FOREACH(qtd, &hc->qh->qtd_list, qtd_list_entry) { + if (!qtd->in_process) + break; + + urb = qtd->urb; + DWC_PRINTF(" URB Info:\n"); + DWC_PRINTF(" qtd: %p, urb: %p\n", qtd, urb); + if (urb) { + DWC_PRINTF(" Dev: %d, EP: %d %s\n", + dwc_otg_hcd_get_dev_addr(&urb-> + pipe_info), + dwc_otg_hcd_get_ep_num(&urb-> + pipe_info), + dwc_otg_hcd_is_pipe_in(&urb-> + pipe_info) ? + "IN" : "OUT"); + DWC_PRINTF(" Max packet size: %d\n", + dwc_otg_hcd_get_mps(&urb-> + pipe_info)); + DWC_PRINTF(" transfer_buffer: %p\n", + urb->buf); + DWC_PRINTF(" transfer_dma: %p\n", + (void *)urb->dma); + DWC_PRINTF(" transfer_buffer_length: %d\n", + urb->length); + DWC_PRINTF(" actual_length: %d\n", + urb->actual_length); + } + } + } + } + DWC_PRINTF(" non_periodic_channels: %d\n", hcd->non_periodic_channels); + DWC_PRINTF(" periodic_channels: %d\n", hcd->periodic_channels); + DWC_PRINTF(" periodic_usecs: %d\n", hcd->periodic_usecs); + np_tx_status.d32 = + DWC_READ_REG32(&hcd->core_if->core_global_regs->gnptxsts); + DWC_PRINTF(" NP Tx Req Queue Space Avail: %d\n", + np_tx_status.b.nptxqspcavail); + DWC_PRINTF(" NP Tx FIFO Space Avail: %d\n", + np_tx_status.b.nptxfspcavail); + p_tx_status.d32 = + DWC_READ_REG32(&hcd->core_if->host_if->host_global_regs->hptxsts); + DWC_PRINTF(" P Tx Req Queue Space Avail: %d\n", + p_tx_status.b.ptxqspcavail); + DWC_PRINTF(" P Tx FIFO Space Avail: %d\n", p_tx_status.b.ptxfspcavail); + dwc_otg_hcd_dump_frrem(hcd); + dwc_otg_dump_global_registers(hcd->core_if); + dwc_otg_dump_host_registers(hcd->core_if); + DWC_PRINTF + ("************************************************************\n"); + DWC_PRINTF("\n"); +#endif +} + +#ifdef DEBUG +void dwc_print_setup_data(uint8_t * setup) +{ + int i; + if (CHK_DEBUG_LEVEL(DBG_HCD)) { + DWC_PRINTF("Setup Data = MSB "); + for (i = 7; i >= 0; i--) + DWC_PRINTF("%02x ", setup[i]); + DWC_PRINTF("\n"); + DWC_PRINTF(" bmRequestType Tranfer = %s\n", + (setup[0] & 0x80) ? "Device-to-Host" : + "Host-to-Device"); + DWC_PRINTF(" bmRequestType Type = "); + switch ((setup[0] & 0x60) >> 5) { + case 0: + DWC_PRINTF("Standard\n"); + break; + case 1: + DWC_PRINTF("Class\n"); + break; + case 2: + DWC_PRINTF("Vendor\n"); + break; + case 3: + DWC_PRINTF("Reserved\n"); + break; + } + DWC_PRINTF(" bmRequestType Recipient = "); + switch (setup[0] & 0x1f) { + case 0: + DWC_PRINTF("Device\n"); + break; + case 1: + DWC_PRINTF("Interface\n"); + break; + case 2: + DWC_PRINTF("Endpoint\n"); + break; + case 3: + DWC_PRINTF("Other\n"); + break; + default: + DWC_PRINTF("Reserved\n"); + break; + } + DWC_PRINTF(" bRequest = 0x%0x\n", setup[1]); + DWC_PRINTF(" wValue = 0x%0x\n", *((uint16_t *) & setup[2])); + DWC_PRINTF(" wIndex = 0x%0x\n", *((uint16_t *) & setup[4])); + DWC_PRINTF(" wLength = 0x%0x\n\n", *((uint16_t *) & setup[6])); + } +} +#endif + +void dwc_otg_hcd_dump_frrem(dwc_otg_hcd_t * hcd) +{ +#if 0 + DWC_PRINTF("Frame remaining at SOF:\n"); + DWC_PRINTF(" samples %u, accum %llu, avg %llu\n", + hcd->frrem_samples, hcd->frrem_accum, + (hcd->frrem_samples > 0) ? + hcd->frrem_accum / hcd->frrem_samples : 0); + + DWC_PRINTF("\n"); + DWC_PRINTF("Frame remaining at start_transfer (uframe 7):\n"); + DWC_PRINTF(" samples %u, accum %llu, avg %llu\n", + hcd->core_if->hfnum_7_samples, + hcd->core_if->hfnum_7_frrem_accum, + (hcd->core_if->hfnum_7_samples > + 0) ? hcd->core_if->hfnum_7_frrem_accum / + hcd->core_if->hfnum_7_samples : 0); + DWC_PRINTF("Frame remaining at start_transfer (uframe 0):\n"); + DWC_PRINTF(" samples %u, accum %llu, avg %llu\n", + hcd->core_if->hfnum_0_samples, + hcd->core_if->hfnum_0_frrem_accum, + (hcd->core_if->hfnum_0_samples > + 0) ? hcd->core_if->hfnum_0_frrem_accum / + hcd->core_if->hfnum_0_samples : 0); + DWC_PRINTF("Frame remaining at start_transfer (uframe 1-6):\n"); + DWC_PRINTF(" samples %u, accum %llu, avg %llu\n", + hcd->core_if->hfnum_other_samples, + hcd->core_if->hfnum_other_frrem_accum, + (hcd->core_if->hfnum_other_samples > + 0) ? hcd->core_if->hfnum_other_frrem_accum / + hcd->core_if->hfnum_other_samples : 0); + + DWC_PRINTF("\n"); + DWC_PRINTF("Frame remaining at sample point A (uframe 7):\n"); + DWC_PRINTF(" samples %u, accum %llu, avg %llu\n", + hcd->hfnum_7_samples_a, hcd->hfnum_7_frrem_accum_a, + (hcd->hfnum_7_samples_a > 0) ? + hcd->hfnum_7_frrem_accum_a / hcd->hfnum_7_samples_a : 0); + DWC_PRINTF("Frame remaining at sample point A (uframe 0):\n"); + DWC_PRINTF(" samples %u, accum %llu, avg %llu\n", + hcd->hfnum_0_samples_a, hcd->hfnum_0_frrem_accum_a, + (hcd->hfnum_0_samples_a > 0) ? + hcd->hfnum_0_frrem_accum_a / hcd->hfnum_0_samples_a : 0); + DWC_PRINTF("Frame remaining at sample point A (uframe 1-6):\n"); + DWC_PRINTF(" samples %u, accum %llu, avg %llu\n", + hcd->hfnum_other_samples_a, hcd->hfnum_other_frrem_accum_a, + (hcd->hfnum_other_samples_a > 0) ? + hcd->hfnum_other_frrem_accum_a / + hcd->hfnum_other_samples_a : 0); + + DWC_PRINTF("\n"); + DWC_PRINTF("Frame remaining at sample point B (uframe 7):\n"); + DWC_PRINTF(" samples %u, accum %llu, avg %llu\n", + hcd->hfnum_7_samples_b, hcd->hfnum_7_frrem_accum_b, + (hcd->hfnum_7_samples_b > 0) ? + hcd->hfnum_7_frrem_accum_b / hcd->hfnum_7_samples_b : 0); + DWC_PRINTF("Frame remaining at sample point B (uframe 0):\n"); + DWC_PRINTF(" samples %u, accum %llu, avg %llu\n", + hcd->hfnum_0_samples_b, hcd->hfnum_0_frrem_accum_b, + (hcd->hfnum_0_samples_b > 0) ? + hcd->hfnum_0_frrem_accum_b / hcd->hfnum_0_samples_b : 0); + DWC_PRINTF("Frame remaining at sample point B (uframe 1-6):\n"); + DWC_PRINTF(" samples %u, accum %llu, avg %llu\n", + hcd->hfnum_other_samples_b, hcd->hfnum_other_frrem_accum_b, + (hcd->hfnum_other_samples_b > 0) ? + hcd->hfnum_other_frrem_accum_b / + hcd->hfnum_other_samples_b : 0); +#endif +} + +#endif /* DWC_DEVICE_ONLY */ diff --git a/drivers/usb/host/dwc_otg/dwc_otg_hcd.h b/drivers/usb/host/dwc_otg/dwc_otg_hcd.h new file mode 100644 index 00000000000000..e0611c1592b1c9 --- /dev/null +++ b/drivers/usb/host/dwc_otg/dwc_otg_hcd.h @@ -0,0 +1,870 @@ +/* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_hcd.h $ + * $Revision: #58 $ + * $Date: 2011/09/15 $ + * $Change: 1846647 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ +#ifndef DWC_DEVICE_ONLY +#ifndef __DWC_HCD_H__ +#define __DWC_HCD_H__ + +#include "dwc_otg_os_dep.h" +#include "usb.h" +#include "dwc_otg_hcd_if.h" +#include "dwc_otg_core_if.h" +#include "dwc_list.h" +#include "dwc_otg_cil.h" +#include "dwc_otg_fiq_fsm.h" +#include "dwc_otg_driver.h" + + +/** + * @file + * + * This file contains the structures, constants, and interfaces for + * the Host Contoller Driver (HCD). + * + * The Host Controller Driver (HCD) is responsible for translating requests + * from the USB Driver into the appropriate actions on the DWC_otg controller. + * It isolates the USBD from the specifics of the controller by providing an + * API to the USBD. + */ + +struct dwc_otg_hcd_pipe_info { + uint8_t dev_addr; + uint8_t ep_num; + uint8_t pipe_type; + uint8_t pipe_dir; + uint16_t mps; +}; + +struct dwc_otg_hcd_iso_packet_desc { + uint32_t offset; + uint32_t length; + uint32_t actual_length; + uint32_t status; +}; + +struct dwc_otg_qtd; + +struct dwc_otg_hcd_urb { + void *priv; + struct dwc_otg_qtd *qtd; + void *buf; + dwc_dma_t dma; + void *setup_packet; + dwc_dma_t setup_dma; + uint32_t length; + uint32_t actual_length; + uint32_t status; + uint32_t error_count; + uint32_t packet_count; + uint32_t flags; + uint16_t interval; + struct dwc_otg_hcd_pipe_info pipe_info; + struct dwc_otg_hcd_iso_packet_desc iso_descs[]; +}; + +static inline uint8_t dwc_otg_hcd_get_ep_num(struct dwc_otg_hcd_pipe_info *pipe) +{ + return pipe->ep_num; +} + +static inline uint8_t dwc_otg_hcd_get_pipe_type(struct dwc_otg_hcd_pipe_info + *pipe) +{ + return pipe->pipe_type; +} + +static inline uint16_t dwc_otg_hcd_get_mps(struct dwc_otg_hcd_pipe_info *pipe) +{ + return pipe->mps; +} + +static inline uint8_t dwc_otg_hcd_get_dev_addr(struct dwc_otg_hcd_pipe_info + *pipe) +{ + return pipe->dev_addr; +} + +static inline uint8_t dwc_otg_hcd_is_pipe_isoc(struct dwc_otg_hcd_pipe_info + *pipe) +{ + return (pipe->pipe_type == UE_ISOCHRONOUS); +} + +static inline uint8_t dwc_otg_hcd_is_pipe_int(struct dwc_otg_hcd_pipe_info + *pipe) +{ + return (pipe->pipe_type == UE_INTERRUPT); +} + +static inline uint8_t dwc_otg_hcd_is_pipe_bulk(struct dwc_otg_hcd_pipe_info + *pipe) +{ + return (pipe->pipe_type == UE_BULK); +} + +static inline uint8_t dwc_otg_hcd_is_pipe_control(struct dwc_otg_hcd_pipe_info + *pipe) +{ + return (pipe->pipe_type == UE_CONTROL); +} + +static inline uint8_t dwc_otg_hcd_is_pipe_in(struct dwc_otg_hcd_pipe_info *pipe) +{ + return (pipe->pipe_dir == UE_DIR_IN); +} + +static inline uint8_t dwc_otg_hcd_is_pipe_out(struct dwc_otg_hcd_pipe_info + *pipe) +{ + return (!dwc_otg_hcd_is_pipe_in(pipe)); +} + +static inline void dwc_otg_hcd_fill_pipe(struct dwc_otg_hcd_pipe_info *pipe, + uint8_t devaddr, uint8_t ep_num, + uint8_t pipe_type, uint8_t pipe_dir, + uint16_t mps) +{ + pipe->dev_addr = devaddr; + pipe->ep_num = ep_num; + pipe->pipe_type = pipe_type; + pipe->pipe_dir = pipe_dir; + pipe->mps = mps; +} + +/** + * Phases for control transfers. + */ +typedef enum dwc_otg_control_phase { + DWC_OTG_CONTROL_SETUP, + DWC_OTG_CONTROL_DATA, + DWC_OTG_CONTROL_STATUS +} dwc_otg_control_phase_e; + +/** Transaction types. */ +typedef enum dwc_otg_transaction_type { + DWC_OTG_TRANSACTION_NONE = 0, + DWC_OTG_TRANSACTION_PERIODIC = 1, + DWC_OTG_TRANSACTION_NON_PERIODIC = 2, + DWC_OTG_TRANSACTION_ALL = DWC_OTG_TRANSACTION_PERIODIC + DWC_OTG_TRANSACTION_NON_PERIODIC +} dwc_otg_transaction_type_e; + +struct dwc_otg_qh; + +/** + * A Queue Transfer Descriptor (QTD) holds the state of a bulk, control, + * interrupt, or isochronous transfer. A single QTD is created for each URB + * (of one of these types) submitted to the HCD. The transfer associated with + * a QTD may require one or multiple transactions. + * + * A QTD is linked to a Queue Head, which is entered in either the + * non-periodic or periodic schedule for execution. When a QTD is chosen for + * execution, some or all of its transactions may be executed. After + * execution, the state of the QTD is updated. The QTD may be retired if all + * its transactions are complete or if an error occurred. Otherwise, it + * remains in the schedule so more transactions can be executed later. + */ +typedef struct dwc_otg_qtd { + /** + * Determines the PID of the next data packet for the data phase of + * control transfers. Ignored for other transfer types.<br> + * One of the following values: + * - DWC_OTG_HC_PID_DATA0 + * - DWC_OTG_HC_PID_DATA1 + */ + uint8_t data_toggle; + + /** Current phase for control transfers (Setup, Data, or Status). */ + dwc_otg_control_phase_e control_phase; + + /** Keep track of the current split type + * for FS/LS endpoints on a HS Hub */ + uint8_t complete_split; + + /** How many bytes transferred during SSPLIT OUT */ + uint32_t ssplit_out_xfer_count; + + /** + * Holds the number of bus errors that have occurred for a transaction + * within this transfer. + */ + uint8_t error_count; + + /** + * Index of the next frame descriptor for an isochronous transfer. A + * frame descriptor describes the buffer position and length of the + * data to be transferred in the next scheduled (micro)frame of an + * isochronous transfer. It also holds status for that transaction. + * The frame index starts at 0. + */ + uint16_t isoc_frame_index; + + /** Position of the ISOC split on full/low speed */ + uint8_t isoc_split_pos; + + /** Position of the ISOC split in the buffer for the current frame */ + uint16_t isoc_split_offset; + + /** URB for this transfer */ + struct dwc_otg_hcd_urb *urb; + + struct dwc_otg_qh *qh; + + /** This list of QTDs */ + DWC_CIRCLEQ_ENTRY(dwc_otg_qtd) qtd_list_entry; + + /** Indicates if this QTD is currently processed by HW. */ + uint8_t in_process; + + /** Number of DMA descriptors for this QTD */ + uint8_t n_desc; + + /** + * Last activated frame(packet) index. + * Used in Descriptor DMA mode only. + */ + uint16_t isoc_frame_index_last; + +} dwc_otg_qtd_t; + +DWC_CIRCLEQ_HEAD(dwc_otg_qtd_list, dwc_otg_qtd); + +/** + * A Queue Head (QH) holds the static characteristics of an endpoint and + * maintains a list of transfers (QTDs) for that endpoint. A QH structure may + * be entered in either the non-periodic or periodic schedule. + */ +typedef struct dwc_otg_qh { + /** + * Endpoint type. + * One of the following values: + * - UE_CONTROL + * - UE_BULK + * - UE_INTERRUPT + * - UE_ISOCHRONOUS + */ + uint8_t ep_type; + uint8_t ep_is_in; + + /** wMaxPacketSize Field of Endpoint Descriptor. */ + uint16_t maxp; + + /** + * Device speed. + * One of the following values: + * - DWC_OTG_EP_SPEED_LOW + * - DWC_OTG_EP_SPEED_FULL + * - DWC_OTG_EP_SPEED_HIGH + */ + uint8_t dev_speed; + + /** + * Determines the PID of the next data packet for non-control + * transfers. Ignored for control transfers.<br> + * One of the following values: + * - DWC_OTG_HC_PID_DATA0 + * - DWC_OTG_HC_PID_DATA1 + */ + uint8_t data_toggle; + + /** Ping state if 1. */ + uint8_t ping_state; + + /** + * List of QTDs for this QH. + */ + struct dwc_otg_qtd_list qtd_list; + + /** Host channel currently processing transfers for this QH. */ + struct dwc_hc *channel; + + /** Full/low speed endpoint on high-speed hub requires split. */ + uint8_t do_split; + + /** @name Periodic schedule information */ + /** @{ */ + + /** Bandwidth in microseconds per (micro)frame. */ + uint16_t usecs; + + /** Interval between transfers in (micro)frames. */ + uint16_t interval; + + /** + * (micro)frame to initialize a periodic transfer. The transfer + * executes in the following (micro)frame. + */ + uint16_t sched_frame; + + /* + ** Frame a NAK was received on this queue head, used to minimise NAK retransmission + */ + uint16_t nak_frame; + + /** (micro)frame at which last start split was initialized. */ + uint16_t start_split_frame; + + /** @} */ + + /** + * Used instead of original buffer if + * it(physical address) is not dword-aligned. + */ + uint8_t *dw_align_buf; + dwc_dma_t dw_align_buf_dma; + + /** Entry for QH in either the periodic or non-periodic schedule. */ + dwc_list_link_t qh_list_entry; + + /** @name Descriptor DMA support */ + /** @{ */ + + /** Descriptor List. */ + dwc_otg_host_dma_desc_t *desc_list; + + /** Descriptor List physical address. */ + dwc_dma_t desc_list_dma; + + /** + * Xfer Bytes array. + * Each element corresponds to a descriptor and indicates + * original XferSize size value for the descriptor. + */ + uint32_t *n_bytes; + + /** Actual number of transfer descriptors in a list. */ + uint16_t ntd; + + /** First activated isochronous transfer descriptor index. */ + uint8_t td_first; + /** Last activated isochronous transfer descriptor index. */ + uint8_t td_last; + + /** @} */ + + + uint16_t speed; + uint16_t frame_usecs[8]; + + uint32_t skip_count; +} dwc_otg_qh_t; + +DWC_CIRCLEQ_HEAD(hc_list, dwc_hc); + +typedef struct urb_tq_entry { + struct urb *urb; + DWC_TAILQ_ENTRY(urb_tq_entry) urb_tq_entries; +} urb_tq_entry_t; + +DWC_TAILQ_HEAD(urb_list, urb_tq_entry); + +/** + * This structure holds the state of the HCD, including the non-periodic and + * periodic schedules. + */ +struct dwc_otg_hcd { + /** The DWC otg device pointer */ + struct dwc_otg_device *otg_dev; + /** DWC OTG Core Interface Layer */ + dwc_otg_core_if_t *core_if; + + /** Function HCD driver callbacks */ + struct dwc_otg_hcd_function_ops *fops; + + /** Internal DWC HCD Flags */ + volatile union dwc_otg_hcd_internal_flags { + uint32_t d32; + struct { + unsigned port_connect_status_change:1; + unsigned port_connect_status:1; + unsigned port_reset_change:1; + unsigned port_enable_change:1; + unsigned port_suspend_change:1; + unsigned port_over_current_change:1; + unsigned port_l1_change:1; + unsigned port_speed:2; + unsigned reserved:24; + } b; + } flags; + + /** + * Inactive items in the non-periodic schedule. This is a list of + * Queue Heads. Transfers associated with these Queue Heads are not + * currently assigned to a host channel. + */ + dwc_list_link_t non_periodic_sched_inactive; + + /** + * Active items in the non-periodic schedule. This is a list of + * Queue Heads. Transfers associated with these Queue Heads are + * currently assigned to a host channel. + */ + dwc_list_link_t non_periodic_sched_active; + + /** + * Pointer to the next Queue Head to process in the active + * non-periodic schedule. + */ + dwc_list_link_t *non_periodic_qh_ptr; + + /** + * Inactive items in the periodic schedule. This is a list of QHs for + * periodic transfers that are _not_ scheduled for the next frame. + * Each QH in the list has an interval counter that determines when it + * needs to be scheduled for execution. This scheduling mechanism + * allows only a simple calculation for periodic bandwidth used (i.e. + * must assume that all periodic transfers may need to execute in the + * same frame). However, it greatly simplifies scheduling and should + * be sufficient for the vast majority of OTG hosts, which need to + * connect to a small number of peripherals at one time. + * + * Items move from this list to periodic_sched_ready when the QH + * interval counter is 0 at SOF. + */ + dwc_list_link_t periodic_sched_inactive; + + /** + * List of periodic QHs that are ready for execution in the next + * frame, but have not yet been assigned to host channels. + * + * Items move from this list to periodic_sched_assigned as host + * channels become available during the current frame. + */ + dwc_list_link_t periodic_sched_ready; + + /** + * List of periodic QHs to be executed in the next frame that are + * assigned to host channels. + * + * Items move from this list to periodic_sched_queued as the + * transactions for the QH are queued to the DWC_otg controller. + */ + dwc_list_link_t periodic_sched_assigned; + + /** + * List of periodic QHs that have been queued for execution. + * + * Items move from this list to either periodic_sched_inactive or + * periodic_sched_ready when the channel associated with the transfer + * is released. If the interval for the QH is 1, the item moves to + * periodic_sched_ready because it must be rescheduled for the next + * frame. Otherwise, the item moves to periodic_sched_inactive. + */ + dwc_list_link_t periodic_sched_queued; + + /** + * Total bandwidth claimed so far for periodic transfers. This value + * is in microseconds per (micro)frame. The assumption is that all + * periodic transfers may occur in the same (micro)frame. + */ + uint16_t periodic_usecs; + + /** + * Total bandwidth claimed so far for all periodic transfers + * in a frame. + * This will include a mixture of HS and FS transfers. + * Units are microseconds per (micro)frame. + * We have a budget per frame and have to schedule + * transactions accordingly. + * Watch out for the fact that things are actually scheduled for the + * "next frame". + */ + uint16_t frame_usecs[8]; + + + /** + * Frame number read from the core at SOF. The value ranges from 0 to + * DWC_HFNUM_MAX_FRNUM. + */ + uint16_t frame_number; + + /** + * Count of periodic QHs, if using several eps. For SOF enable/disable. + */ + uint16_t periodic_qh_count; + + /** + * Free host channels in the controller. This is a list of + * dwc_hc_t items. + */ + struct hc_list free_hc_list; + /** + * Number of host channels assigned to periodic transfers. Currently + * assuming that there is a dedicated host channel for each periodic + * transaction and at least one host channel available for + * non-periodic transactions. + */ + int periodic_channels; /* microframe_schedule==0 */ + + /** + * Number of host channels assigned to non-periodic transfers. + */ + int non_periodic_channels; /* microframe_schedule==0 */ + + /** + * Number of host channels assigned to non-periodic transfers. + */ + int available_host_channels; + + /** + * Array of pointers to the host channel descriptors. Allows accessing + * a host channel descriptor given the host channel number. This is + * useful in interrupt handlers. + */ + struct dwc_hc *hc_ptr_array[MAX_EPS_CHANNELS]; + + /** + * Buffer to use for any data received during the status phase of a + * control transfer. Normally no data is transferred during the status + * phase. This buffer is used as a bit bucket. + */ + uint8_t *status_buf; + + /** + * DMA address for status_buf. + */ + dma_addr_t status_buf_dma; +#define DWC_OTG_HCD_STATUS_BUF_SIZE 64 + + /** + * Connection timer. An OTG host must display a message if the device + * does not connect. Started when the VBus power is turned on via + * sysfs attribute "buspower". + */ + dwc_timer_t *conn_timer; + + /* Tasket to do a reset */ + dwc_tasklet_t *reset_tasklet; + + dwc_tasklet_t *completion_tasklet; + struct urb_list completed_urb_list; + + /* */ + dwc_spinlock_t *lock; + /** + * Private data that could be used by OS wrapper. + */ + void *priv; + + uint8_t otg_port; + + /** Frame List */ + uint32_t *frame_list; + + /** Hub - Port assignment */ + int hub_port[128]; +#ifdef FIQ_DEBUG + int hub_port_alloc[2048]; +#endif + + /** Frame List DMA address */ + dma_addr_t frame_list_dma; + + struct fiq_stack *fiq_stack; + struct fiq_state *fiq_state; + + /** Virtual address for split transaction DMA bounce buffers */ + struct fiq_dma_channel *fiq_dmab; + +#ifdef DEBUG + uint32_t frrem_samples; + uint64_t frrem_accum; + + uint32_t hfnum_7_samples_a; + uint64_t hfnum_7_frrem_accum_a; + uint32_t hfnum_0_samples_a; + uint64_t hfnum_0_frrem_accum_a; + uint32_t hfnum_other_samples_a; + uint64_t hfnum_other_frrem_accum_a; + + uint32_t hfnum_7_samples_b; + uint64_t hfnum_7_frrem_accum_b; + uint32_t hfnum_0_samples_b; + uint64_t hfnum_0_frrem_accum_b; + uint32_t hfnum_other_samples_b; + uint64_t hfnum_other_frrem_accum_b; +#endif +}; + +static inline struct device *dwc_otg_hcd_to_dev(struct dwc_otg_hcd *hcd) +{ + return &hcd->otg_dev->os_dep.platformdev->dev; +} + +/** @name Transaction Execution Functions */ +/** @{ */ +extern dwc_otg_transaction_type_e dwc_otg_hcd_select_transactions(dwc_otg_hcd_t + * hcd); +extern void dwc_otg_hcd_queue_transactions(dwc_otg_hcd_t * hcd, + dwc_otg_transaction_type_e tr_type); + +int dwc_otg_hcd_allocate_port(dwc_otg_hcd_t * hcd, dwc_otg_qh_t *qh); +void dwc_otg_hcd_release_port(dwc_otg_hcd_t * dwc_otg_hcd, dwc_otg_qh_t *qh); + +extern int fiq_fsm_queue_transaction(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh); +extern int fiq_fsm_transaction_suitable(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh); +extern void dwc_otg_cleanup_fiq_channel(dwc_otg_hcd_t *hcd, uint32_t num); + +/** @} */ + +/** @name Interrupt Handler Functions */ +/** @{ */ +extern int32_t dwc_otg_hcd_handle_intr(dwc_otg_hcd_t * dwc_otg_hcd); +extern int32_t dwc_otg_hcd_handle_sof_intr(dwc_otg_hcd_t * dwc_otg_hcd); +extern int32_t dwc_otg_hcd_handle_rx_status_q_level_intr(dwc_otg_hcd_t * + dwc_otg_hcd); +extern int32_t dwc_otg_hcd_handle_np_tx_fifo_empty_intr(dwc_otg_hcd_t * + dwc_otg_hcd); +extern int32_t dwc_otg_hcd_handle_perio_tx_fifo_empty_intr(dwc_otg_hcd_t * + dwc_otg_hcd); +extern int32_t dwc_otg_hcd_handle_incomplete_periodic_intr(dwc_otg_hcd_t * + dwc_otg_hcd); +extern int32_t dwc_otg_hcd_handle_port_intr(dwc_otg_hcd_t * dwc_otg_hcd); +extern int32_t dwc_otg_hcd_handle_conn_id_status_change_intr(dwc_otg_hcd_t * + dwc_otg_hcd); +extern int32_t dwc_otg_hcd_handle_disconnect_intr(dwc_otg_hcd_t * dwc_otg_hcd); +extern int32_t dwc_otg_hcd_handle_hc_intr(dwc_otg_hcd_t * dwc_otg_hcd); +extern int32_t dwc_otg_hcd_handle_hc_n_intr(dwc_otg_hcd_t * dwc_otg_hcd, + uint32_t num); +extern int32_t dwc_otg_hcd_handle_session_req_intr(dwc_otg_hcd_t * dwc_otg_hcd); +extern int32_t dwc_otg_hcd_handle_wakeup_detected_intr(dwc_otg_hcd_t * + dwc_otg_hcd); +/** @} */ + +/** @name Schedule Queue Functions */ +/** @{ */ + +/* Implemented in dwc_otg_hcd_queue.c */ +extern dwc_otg_qh_t *dwc_otg_hcd_qh_create(dwc_otg_hcd_t * hcd, + dwc_otg_hcd_urb_t * urb, int atomic_alloc); +extern void dwc_otg_hcd_qh_free(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh); +extern int dwc_otg_hcd_qh_add(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh); +extern void dwc_otg_hcd_qh_remove(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh); +extern void dwc_otg_hcd_qh_deactivate(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh, + int sched_csplit); + +/** Remove and free a QH */ +static inline void dwc_otg_hcd_qh_remove_and_free(dwc_otg_hcd_t * hcd, + dwc_otg_qh_t * qh) +{ + dwc_irqflags_t flags; + DWC_SPINLOCK_IRQSAVE(hcd->lock, &flags); + dwc_otg_hcd_qh_remove(hcd, qh); + DWC_SPINUNLOCK_IRQRESTORE(hcd->lock, flags); + dwc_otg_hcd_qh_free(hcd, qh); +} + +/** Allocates memory for a QH structure. + * @return Returns the memory allocate or NULL on error. */ +static inline dwc_otg_qh_t *dwc_otg_hcd_qh_alloc(int atomic_alloc) +{ + if (atomic_alloc) + return (dwc_otg_qh_t *) DWC_ALLOC_ATOMIC(sizeof(dwc_otg_qh_t)); + else + return (dwc_otg_qh_t *) DWC_ALLOC(sizeof(dwc_otg_qh_t)); +} + +extern dwc_otg_qtd_t *dwc_otg_hcd_qtd_create(dwc_otg_hcd_urb_t * urb, + int atomic_alloc); +extern void dwc_otg_hcd_qtd_init(dwc_otg_qtd_t * qtd, dwc_otg_hcd_urb_t * urb); +extern int dwc_otg_hcd_qtd_add(dwc_otg_qtd_t * qtd, dwc_otg_hcd_t * dwc_otg_hcd, + dwc_otg_qh_t ** qh, int atomic_alloc); + +/** Allocates memory for a QTD structure. + * @return Returns the memory allocate or NULL on error. */ +static inline dwc_otg_qtd_t *dwc_otg_hcd_qtd_alloc(int atomic_alloc) +{ + if (atomic_alloc) + return (dwc_otg_qtd_t *) DWC_ALLOC_ATOMIC(sizeof(dwc_otg_qtd_t)); + else + return (dwc_otg_qtd_t *) DWC_ALLOC(sizeof(dwc_otg_qtd_t)); +} + +/** Frees the memory for a QTD structure. QTD should already be removed from + * list. + * @param qtd QTD to free.*/ +static inline void dwc_otg_hcd_qtd_free(dwc_otg_qtd_t * qtd) +{ + DWC_FREE(qtd); +} + +/** Removes a QTD from list. + * @param hcd HCD instance. + * @param qtd QTD to remove from list. + * @param qh QTD belongs to. + */ +static inline void dwc_otg_hcd_qtd_remove(dwc_otg_hcd_t * hcd, + dwc_otg_qtd_t * qtd, + dwc_otg_qh_t * qh) +{ + DWC_CIRCLEQ_REMOVE(&qh->qtd_list, qtd, qtd_list_entry); +} + +/** Remove and free a QTD + * Need to disable IRQ and hold hcd lock while calling this function out of + * interrupt servicing chain */ +static inline void dwc_otg_hcd_qtd_remove_and_free(dwc_otg_hcd_t * hcd, + dwc_otg_qtd_t * qtd, + dwc_otg_qh_t * qh) +{ + dwc_otg_hcd_qtd_remove(hcd, qtd, qh); + dwc_otg_hcd_qtd_free(qtd); +} + +/** @} */ + +/** @name Descriptor DMA Supporting Functions */ +/** @{ */ + +extern void dwc_otg_hcd_start_xfer_ddma(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh); +extern void dwc_otg_hcd_complete_xfer_ddma(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_halt_status_e halt_status); + +extern int dwc_otg_hcd_qh_init_ddma(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh); +extern void dwc_otg_hcd_qh_free_ddma(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh); + +/** @} */ + +/** @name Internal Functions */ +/** @{ */ +dwc_otg_qh_t *dwc_urb_to_qh(dwc_otg_hcd_urb_t * urb); +/** @} */ + +#ifdef CONFIG_USB_DWC_OTG_LPM +extern int dwc_otg_hcd_get_hc_for_lpm_tran(dwc_otg_hcd_t * hcd, + uint8_t devaddr); +extern void dwc_otg_hcd_free_hc_from_lpm(dwc_otg_hcd_t * hcd); +#endif + +/** Gets the QH that contains the list_head */ +#define dwc_list_to_qh(_list_head_ptr_) container_of(_list_head_ptr_, dwc_otg_qh_t, qh_list_entry) + +/** Gets the QTD that contains the list_head */ +#define dwc_list_to_qtd(_list_head_ptr_) container_of(_list_head_ptr_, dwc_otg_qtd_t, qtd_list_entry) + +/** Check if QH is non-periodic */ +#define dwc_qh_is_non_per(_qh_ptr_) ((_qh_ptr_->ep_type == UE_BULK) || \ + (_qh_ptr_->ep_type == UE_CONTROL)) + +/** High bandwidth multiplier as encoded in highspeed endpoint descriptors */ +#define dwc_hb_mult(wMaxPacketSize) (1 + (((wMaxPacketSize) >> 11) & 0x03)) + +/** Packet size for any kind of endpoint descriptor */ +#define dwc_max_packet(wMaxPacketSize) ((wMaxPacketSize) & 0x07ff) + +/** + * Returns true if _frame1 is less than or equal to _frame2. The comparison is + * done modulo DWC_HFNUM_MAX_FRNUM. This accounts for the rollover of the + * frame number when the max frame number is reached. + */ +static inline int dwc_frame_num_le(uint16_t frame1, uint16_t frame2) +{ + return ((frame2 - frame1) & DWC_HFNUM_MAX_FRNUM) <= + (DWC_HFNUM_MAX_FRNUM >> 1); +} + +/** + * Returns true if _frame1 is greater than _frame2. The comparison is done + * modulo DWC_HFNUM_MAX_FRNUM. This accounts for the rollover of the frame + * number when the max frame number is reached. + */ +static inline int dwc_frame_num_gt(uint16_t frame1, uint16_t frame2) +{ + return (frame1 != frame2) && + (((frame1 - frame2) & DWC_HFNUM_MAX_FRNUM) < + (DWC_HFNUM_MAX_FRNUM >> 1)); +} + +/** + * Increments _frame by the amount specified by _inc. The addition is done + * modulo DWC_HFNUM_MAX_FRNUM. Returns the incremented value. + */ +static inline uint16_t dwc_frame_num_inc(uint16_t frame, uint16_t inc) +{ + return (frame + inc) & DWC_HFNUM_MAX_FRNUM; +} + +static inline uint16_t dwc_full_frame_num(uint16_t frame) +{ + return (frame & DWC_HFNUM_MAX_FRNUM) >> 3; +} + +static inline uint16_t dwc_micro_frame_num(uint16_t frame) +{ + return frame & 0x7; +} + +extern void init_hcd_usecs(dwc_otg_hcd_t *_hcd); + +void dwc_otg_hcd_save_data_toggle(dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_qtd_t * qtd); + +#ifdef DEBUG +/** + * Macro to sample the remaining PHY clocks left in the current frame. This + * may be used during debugging to determine the average time it takes to + * execute sections of code. There are two possible sample points, "a" and + * "b", so the _letter argument must be one of these values. + * + * To dump the average sample times, read the "hcd_frrem" sysfs attribute. For + * example, "cat /sys/devices/lm0/hcd_frrem". + */ +#define dwc_sample_frrem(_hcd, _qh, _letter) \ +{ \ + hfnum_data_t hfnum; \ + dwc_otg_qtd_t *qtd; \ + qtd = list_entry(_qh->qtd_list.next, dwc_otg_qtd_t, qtd_list_entry); \ + if (usb_pipeint(qtd->urb->pipe) && _qh->start_split_frame != 0 && !qtd->complete_split) { \ + hfnum.d32 = DWC_READ_REG32(&_hcd->core_if->host_if->host_global_regs->hfnum); \ + switch (hfnum.b.frnum & 0x7) { \ + case 7: \ + _hcd->hfnum_7_samples_##_letter++; \ + _hcd->hfnum_7_frrem_accum_##_letter += hfnum.b.frrem; \ + break; \ + case 0: \ + _hcd->hfnum_0_samples_##_letter++; \ + _hcd->hfnum_0_frrem_accum_##_letter += hfnum.b.frrem; \ + break; \ + default: \ + _hcd->hfnum_other_samples_##_letter++; \ + _hcd->hfnum_other_frrem_accum_##_letter += hfnum.b.frrem; \ + break; \ + } \ + } \ +} +#else +#define dwc_sample_frrem(_hcd, _qh, _letter) +#endif +#endif +#endif /* DWC_DEVICE_ONLY */ diff --git a/drivers/usb/host/dwc_otg/dwc_otg_hcd_ddma.c b/drivers/usb/host/dwc_otg/dwc_otg_hcd_ddma.c new file mode 100644 index 00000000000000..0cf5050190904d --- /dev/null +++ b/drivers/usb/host/dwc_otg/dwc_otg_hcd_ddma.c @@ -0,0 +1,1135 @@ +/*========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_hcd_ddma.c $ + * $Revision: #10 $ + * $Date: 2011/10/20 $ + * $Change: 1869464 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ +#ifndef DWC_DEVICE_ONLY + +/** @file + * This file contains Descriptor DMA support implementation for host mode. + */ + +#include "dwc_otg_hcd.h" +#include "dwc_otg_regs.h" + +extern bool microframe_schedule; + +static inline uint8_t frame_list_idx(uint16_t frame) +{ + return (frame & (MAX_FRLIST_EN_NUM - 1)); +} + +static inline uint16_t desclist_idx_inc(uint16_t idx, uint16_t inc, uint8_t speed) +{ + return (idx + inc) & + (((speed == + DWC_OTG_EP_SPEED_HIGH) ? MAX_DMA_DESC_NUM_HS_ISOC : + MAX_DMA_DESC_NUM_GENERIC) - 1); +} + +static inline uint16_t desclist_idx_dec(uint16_t idx, uint16_t inc, uint8_t speed) +{ + return (idx - inc) & + (((speed == + DWC_OTG_EP_SPEED_HIGH) ? MAX_DMA_DESC_NUM_HS_ISOC : + MAX_DMA_DESC_NUM_GENERIC) - 1); +} + +static inline uint16_t max_desc_num(dwc_otg_qh_t * qh) +{ + return (((qh->ep_type == UE_ISOCHRONOUS) + && (qh->dev_speed == DWC_OTG_EP_SPEED_HIGH)) + ? MAX_DMA_DESC_NUM_HS_ISOC : MAX_DMA_DESC_NUM_GENERIC); +} +static inline uint16_t frame_incr_val(dwc_otg_qh_t * qh) +{ + return ((qh->dev_speed == DWC_OTG_EP_SPEED_HIGH) + ? ((qh->interval + 8 - 1) / 8) + : qh->interval); +} + +static int desc_list_alloc(struct device *dev, dwc_otg_qh_t * qh) +{ + int retval = 0; + + qh->desc_list = (dwc_otg_host_dma_desc_t *) + DWC_DMA_ALLOC(dev, sizeof(dwc_otg_host_dma_desc_t) * max_desc_num(qh), + &qh->desc_list_dma); + + if (!qh->desc_list) { + retval = -DWC_E_NO_MEMORY; + DWC_ERROR("%s: DMA descriptor list allocation failed\n", __func__); + + } + + dwc_memset(qh->desc_list, 0x00, + sizeof(dwc_otg_host_dma_desc_t) * max_desc_num(qh)); + + qh->n_bytes = + (uint32_t *) DWC_ALLOC(sizeof(uint32_t) * max_desc_num(qh)); + + if (!qh->n_bytes) { + retval = -DWC_E_NO_MEMORY; + DWC_ERROR + ("%s: Failed to allocate array for descriptors' size actual values\n", + __func__); + + } + return retval; + +} + +static void desc_list_free(struct device *dev, dwc_otg_qh_t * qh) +{ + if (qh->desc_list) { + DWC_DMA_FREE(dev, max_desc_num(qh), qh->desc_list, + qh->desc_list_dma); + qh->desc_list = NULL; + } + + if (qh->n_bytes) { + DWC_FREE(qh->n_bytes); + qh->n_bytes = NULL; + } +} + +static int frame_list_alloc(dwc_otg_hcd_t * hcd) +{ + struct device *dev = dwc_otg_hcd_to_dev(hcd); + int retval = 0; + + if (hcd->frame_list) + return 0; + + hcd->frame_list = DWC_DMA_ALLOC(dev, 4 * MAX_FRLIST_EN_NUM, + &hcd->frame_list_dma); + if (!hcd->frame_list) { + retval = -DWC_E_NO_MEMORY; + DWC_ERROR("%s: Frame List allocation failed\n", __func__); + } + + dwc_memset(hcd->frame_list, 0x00, 4 * MAX_FRLIST_EN_NUM); + + return retval; +} + +static void frame_list_free(dwc_otg_hcd_t * hcd) +{ + struct device *dev = dwc_otg_hcd_to_dev(hcd); + + if (!hcd->frame_list) + return; + + DWC_DMA_FREE(dev, 4 * MAX_FRLIST_EN_NUM, hcd->frame_list, hcd->frame_list_dma); + hcd->frame_list = NULL; +} + +static void per_sched_enable(dwc_otg_hcd_t * hcd, uint16_t fr_list_en) +{ + + hcfg_data_t hcfg; + + hcfg.d32 = DWC_READ_REG32(&hcd->core_if->host_if->host_global_regs->hcfg); + + if (hcfg.b.perschedena) { + /* already enabled */ + return; + } + + DWC_WRITE_REG32(&hcd->core_if->host_if->host_global_regs->hflbaddr, + hcd->frame_list_dma); + + switch (fr_list_en) { + case 64: + hcfg.b.frlisten = 3; + break; + case 32: + hcfg.b.frlisten = 2; + break; + case 16: + hcfg.b.frlisten = 1; + break; + case 8: + hcfg.b.frlisten = 0; + break; + default: + break; + } + + hcfg.b.perschedena = 1; + + DWC_DEBUGPL(DBG_HCD, "Enabling Periodic schedule\n"); + DWC_WRITE_REG32(&hcd->core_if->host_if->host_global_regs->hcfg, hcfg.d32); + +} + +static void per_sched_disable(dwc_otg_hcd_t * hcd) +{ + hcfg_data_t hcfg; + + hcfg.d32 = DWC_READ_REG32(&hcd->core_if->host_if->host_global_regs->hcfg); + + if (!hcfg.b.perschedena) { + /* already disabled */ + return; + } + hcfg.b.perschedena = 0; + + DWC_DEBUGPL(DBG_HCD, "Disabling Periodic schedule\n"); + DWC_WRITE_REG32(&hcd->core_if->host_if->host_global_regs->hcfg, hcfg.d32); +} + +/* + * Activates/Deactivates FrameList entries for the channel + * based on endpoint servicing period. + */ +static void update_frame_list(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh, uint8_t enable) +{ + uint16_t i, j, inc; + dwc_hc_t *hc = NULL; + + if (!qh->channel) { + DWC_ERROR("qh->channel = %p", qh->channel); + return; + } + + if (!hcd) { + DWC_ERROR("------hcd = %p", hcd); + return; + } + + if (!hcd->frame_list) { + DWC_ERROR("-------hcd->frame_list = %p", hcd->frame_list); + return; + } + + hc = qh->channel; + inc = frame_incr_val(qh); + if (qh->ep_type == UE_ISOCHRONOUS) + i = frame_list_idx(qh->sched_frame); + else + i = 0; + + j = i; + do { + if (enable) + hcd->frame_list[j] |= (1 << hc->hc_num); + else + hcd->frame_list[j] &= ~(1 << hc->hc_num); + j = (j + inc) & (MAX_FRLIST_EN_NUM - 1); + } + while (j != i); + if (!enable) + return; + hc->schinfo = 0; + if (qh->channel->speed == DWC_OTG_EP_SPEED_HIGH) { + j = 1; + /* TODO - check this */ + inc = (8 + qh->interval - 1) / qh->interval; + for (i = 0; i < inc; i++) { + hc->schinfo |= j; + j = j << qh->interval; + } + } else { + hc->schinfo = 0xff; + } +} + +#if 0 +static void dump_frame_list(dwc_otg_hcd_t * hcd) +{ + int i = 0; + DWC_PRINTF("--FRAME LIST (hex) --\n"); + for (i = 0; i < MAX_FRLIST_EN_NUM; i++) { + DWC_PRINTF("%x\t", hcd->frame_list[i]); + if (!(i % 8) && i) + DWC_PRINTF("\n"); + } + DWC_PRINTF("\n----\n"); + +} +#endif + +static void release_channel_ddma(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh) +{ + dwc_hc_t *hc = qh->channel; + if (dwc_qh_is_non_per(qh)) { + if (!microframe_schedule) + hcd->non_periodic_channels--; + else + hcd->available_host_channels++; + } else + update_frame_list(hcd, qh, 0); + + /* + * The condition is added to prevent double cleanup try in case of device + * disconnect. See channel cleanup in dwc_otg_hcd_disconnect_cb(). + */ + if (hc->qh) { + dwc_otg_hc_cleanup(hcd->core_if, hc); + DWC_CIRCLEQ_INSERT_TAIL(&hcd->free_hc_list, hc, hc_list_entry); + hc->qh = NULL; + } + + qh->channel = NULL; + qh->ntd = 0; + + if (qh->desc_list) { + dwc_memset(qh->desc_list, 0x00, + sizeof(dwc_otg_host_dma_desc_t) * max_desc_num(qh)); + } +} + +/** + * Initializes a QH structure's Descriptor DMA related members. + * Allocates memory for descriptor list. + * On first periodic QH, allocates memory for FrameList + * and enables periodic scheduling. + * + * @param hcd The HCD state structure for the DWC OTG controller. + * @param qh The QH to init. + * + * @return 0 if successful, negative error code otherwise. + */ +int dwc_otg_hcd_qh_init_ddma(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh) +{ + struct device *dev = dwc_otg_hcd_to_dev(hcd); + int retval = 0; + + if (qh->do_split) { + DWC_ERROR("SPLIT Transfers are not supported in Descriptor DMA.\n"); + return -1; + } + + retval = desc_list_alloc(dev, qh); + + if ((retval == 0) + && (qh->ep_type == UE_ISOCHRONOUS || qh->ep_type == UE_INTERRUPT)) { + if (!hcd->frame_list) { + retval = frame_list_alloc(hcd); + /* Enable periodic schedule on first periodic QH */ + if (retval == 0) + per_sched_enable(hcd, MAX_FRLIST_EN_NUM); + } + } + + qh->ntd = 0; + + return retval; +} + +/** + * Frees descriptor list memory associated with the QH. + * If QH is periodic and the last, frees FrameList memory + * and disables periodic scheduling. + * + * @param hcd The HCD state structure for the DWC OTG controller. + * @param qh The QH to init. + */ +void dwc_otg_hcd_qh_free_ddma(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh) +{ + struct device *dev = dwc_otg_hcd_to_dev(hcd); + + desc_list_free(dev, qh); + + /* + * Channel still assigned due to some reasons. + * Seen on Isoc URB dequeue. Channel halted but no subsequent + * ChHalted interrupt to release the channel. Afterwards + * when it comes here from endpoint disable routine + * channel remains assigned. + */ + if (qh->channel) + release_channel_ddma(hcd, qh); + + if ((qh->ep_type == UE_ISOCHRONOUS || qh->ep_type == UE_INTERRUPT) + && (microframe_schedule || !hcd->periodic_channels) && hcd->frame_list) { + + per_sched_disable(hcd); + frame_list_free(hcd); + } +} + +static uint8_t frame_to_desc_idx(dwc_otg_qh_t * qh, uint16_t frame_idx) +{ + if (qh->dev_speed == DWC_OTG_EP_SPEED_HIGH) { + /* + * Descriptor set(8 descriptors) index + * which is 8-aligned. + */ + return (frame_idx & ((MAX_DMA_DESC_NUM_HS_ISOC / 8) - 1)) * 8; + } else { + return (frame_idx & (MAX_DMA_DESC_NUM_GENERIC - 1)); + } +} + +/* + * Determine starting frame for Isochronous transfer. + * Few frames skipped to prevent race condition with HC. + */ +static uint8_t calc_starting_frame(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh, + uint8_t * skip_frames) +{ + uint16_t frame = 0; + hcd->frame_number = dwc_otg_hcd_get_frame_number(hcd); + + /* sched_frame is always frame number(not uFrame) both in FS and HS !! */ + + /* + * skip_frames is used to limit activated descriptors number + * to avoid the situation when HC services the last activated + * descriptor firstly. + * Example for FS: + * Current frame is 1, scheduled frame is 3. Since HC always fetches the descriptor + * corresponding to curr_frame+1, the descriptor corresponding to frame 2 + * will be fetched. If the number of descriptors is max=64 (or greather) the + * list will be fully programmed with Active descriptors and it is possible + * case(rare) that the latest descriptor(considering rollback) corresponding + * to frame 2 will be serviced first. HS case is more probable because, in fact, + * up to 11 uframes(16 in the code) may be skipped. + */ + if (qh->dev_speed == DWC_OTG_EP_SPEED_HIGH) { + /* + * Consider uframe counter also, to start xfer asap. + * If half of the frame elapsed skip 2 frames otherwise + * just 1 frame. + * Starting descriptor index must be 8-aligned, so + * if the current frame is near to complete the next one + * is skipped as well. + */ + + if (dwc_micro_frame_num(hcd->frame_number) >= 5) { + *skip_frames = 2 * 8; + frame = dwc_frame_num_inc(hcd->frame_number, *skip_frames); + } else { + *skip_frames = 1 * 8; + frame = dwc_frame_num_inc(hcd->frame_number, *skip_frames); + } + + frame = dwc_full_frame_num(frame); + } else { + /* + * Two frames are skipped for FS - the current and the next. + * But for descriptor programming, 1 frame(descriptor) is enough, + * see example above. + */ + *skip_frames = 1; + frame = dwc_frame_num_inc(hcd->frame_number, 2); + } + + return frame; +} + +/* + * Calculate initial descriptor index for isochronous transfer + * based on scheduled frame. + */ +static uint8_t recalc_initial_desc_idx(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh) +{ + uint16_t frame = 0, fr_idx, fr_idx_tmp; + uint8_t skip_frames = 0; + /* + * With current ISOC processing algorithm the channel is being + * released when no more QTDs in the list(qh->ntd == 0). + * Thus this function is called only when qh->ntd == 0 and qh->channel == 0. + * + * So qh->channel != NULL branch is not used and just not removed from the + * source file. It is required for another possible approach which is, + * do not disable and release the channel when ISOC session completed, + * just move QH to inactive schedule until new QTD arrives. + * On new QTD, the QH moved back to 'ready' schedule, + * starting frame and therefore starting desc_index are recalculated. + * In this case channel is released only on ep_disable. + */ + + /* Calculate starting descriptor index. For INTERRUPT endpoint it is always 0. */ + if (qh->channel) { + frame = calc_starting_frame(hcd, qh, &skip_frames); + /* + * Calculate initial descriptor index based on FrameList current bitmap + * and servicing period. + */ + fr_idx_tmp = frame_list_idx(frame); + fr_idx = + (MAX_FRLIST_EN_NUM + frame_list_idx(qh->sched_frame) - + fr_idx_tmp) + % frame_incr_val(qh); + fr_idx = (fr_idx + fr_idx_tmp) % MAX_FRLIST_EN_NUM; + } else { + qh->sched_frame = calc_starting_frame(hcd, qh, &skip_frames); + fr_idx = frame_list_idx(qh->sched_frame); + } + + qh->td_first = qh->td_last = frame_to_desc_idx(qh, fr_idx); + + return skip_frames; +} + +#define ISOC_URB_GIVEBACK_ASAP + +#define MAX_ISOC_XFER_SIZE_FS 1023 +#define MAX_ISOC_XFER_SIZE_HS 3072 +#define DESCNUM_THRESHOLD 4 + +static void init_isoc_dma_desc(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh, + uint8_t skip_frames) +{ + struct dwc_otg_hcd_iso_packet_desc *frame_desc; + dwc_otg_qtd_t *qtd; + dwc_otg_host_dma_desc_t *dma_desc; + uint16_t idx, inc, n_desc, ntd_max, max_xfer_size; + + idx = qh->td_last; + inc = qh->interval; + n_desc = 0; + + ntd_max = (max_desc_num(qh) + qh->interval - 1) / qh->interval; + if (skip_frames && !qh->channel) + ntd_max = ntd_max - skip_frames / qh->interval; + + max_xfer_size = + (qh->dev_speed == + DWC_OTG_EP_SPEED_HIGH) ? MAX_ISOC_XFER_SIZE_HS : + MAX_ISOC_XFER_SIZE_FS; + + DWC_CIRCLEQ_FOREACH(qtd, &qh->qtd_list, qtd_list_entry) { + while ((qh->ntd < ntd_max) + && (qtd->isoc_frame_index_last < + qtd->urb->packet_count)) { + + dma_desc = &qh->desc_list[idx]; + dwc_memset(dma_desc, 0x00, sizeof(dwc_otg_host_dma_desc_t)); + + frame_desc = &qtd->urb->iso_descs[qtd->isoc_frame_index_last]; + + if (frame_desc->length > max_xfer_size) + qh->n_bytes[idx] = max_xfer_size; + else + qh->n_bytes[idx] = frame_desc->length; + dma_desc->status.b_isoc.n_bytes = qh->n_bytes[idx]; + dma_desc->status.b_isoc.a = 1; + dma_desc->status.b_isoc.sts = 0; + + dma_desc->buf = qtd->urb->dma + frame_desc->offset; + + qh->ntd++; + + qtd->isoc_frame_index_last++; + +#ifdef ISOC_URB_GIVEBACK_ASAP + /* + * Set IOC for each descriptor corresponding to the + * last frame of the URB. + */ + if (qtd->isoc_frame_index_last == + qtd->urb->packet_count) + dma_desc->status.b_isoc.ioc = 1; + +#endif + idx = desclist_idx_inc(idx, inc, qh->dev_speed); + n_desc++; + + } + qtd->in_process = 1; + } + + qh->td_last = idx; + +#ifdef ISOC_URB_GIVEBACK_ASAP + /* Set IOC for the last descriptor if descriptor list is full */ + if (qh->ntd == ntd_max) { + idx = desclist_idx_dec(qh->td_last, inc, qh->dev_speed); + qh->desc_list[idx].status.b_isoc.ioc = 1; + } +#else + /* + * Set IOC bit only for one descriptor. + * Always try to be ahead of HW processing, + * i.e. on IOC generation driver activates next descriptors but + * core continues to process descriptors followed the one with IOC set. + */ + + if (n_desc > DESCNUM_THRESHOLD) { + /* + * Move IOC "up". Required even if there is only one QTD + * in the list, cause QTDs migth continue to be queued, + * but during the activation it was only one queued. + * Actually more than one QTD might be in the list if this function called + * from XferCompletion - QTDs was queued during HW processing of the previous + * descriptor chunk. + */ + idx = dwc_desclist_idx_dec(idx, inc * ((qh->ntd + 1) / 2), qh->dev_speed); + } else { + /* + * Set the IOC for the latest descriptor + * if either number of descriptor is not greather than threshold + * or no more new descriptors activated. + */ + idx = dwc_desclist_idx_dec(qh->td_last, inc, qh->dev_speed); + } + + qh->desc_list[idx].status.b_isoc.ioc = 1; +#endif +} + +static void init_non_isoc_dma_desc(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh) +{ + + dwc_hc_t *hc; + dwc_otg_host_dma_desc_t *dma_desc; + dwc_otg_qtd_t *qtd; + int num_packets, len, n_desc = 0; + + hc = qh->channel; + + /* + * Start with hc->xfer_buff initialized in + * assign_and_init_hc(), then if SG transfer consists of multiple URBs, + * this pointer re-assigned to the buffer of the currently processed QTD. + * For non-SG request there is always one QTD active. + */ + + DWC_CIRCLEQ_FOREACH(qtd, &qh->qtd_list, qtd_list_entry) { + + if (n_desc) { + /* SG request - more than 1 QTDs */ + hc->xfer_buff = (uint8_t *)(uintptr_t)qtd->urb->dma + + qtd->urb->actual_length; + hc->xfer_len = qtd->urb->length - qtd->urb->actual_length; + } + + qtd->n_desc = 0; + + do { + dma_desc = &qh->desc_list[n_desc]; + len = hc->xfer_len; + + if (len > MAX_DMA_DESC_SIZE) + len = MAX_DMA_DESC_SIZE - hc->max_packet + 1; + + if (hc->ep_is_in) { + if (len > 0) { + num_packets = (len + hc->max_packet - 1) / hc->max_packet; + } else { + /* Need 1 packet for transfer length of 0. */ + num_packets = 1; + } + /* Always program an integral # of max packets for IN transfers. */ + len = num_packets * hc->max_packet; + } + + dma_desc->status.b.n_bytes = len; + + qh->n_bytes[n_desc] = len; + + if ((qh->ep_type == UE_CONTROL) + && (qtd->control_phase == DWC_OTG_CONTROL_SETUP)) + dma_desc->status.b.sup = 1; /* Setup Packet */ + + dma_desc->status.b.a = 1; /* Active descriptor */ + dma_desc->status.b.sts = 0; + + dma_desc->buf = + ((unsigned long)hc->xfer_buff & 0xffffffff); + + /* + * Last descriptor(or single) of IN transfer + * with actual size less than MaxPacket. + */ + if (len > hc->xfer_len) { + hc->xfer_len = 0; + } else { + hc->xfer_buff += len; + hc->xfer_len -= len; + } + + qtd->n_desc++; + n_desc++; + } + while ((hc->xfer_len > 0) && (n_desc != MAX_DMA_DESC_NUM_GENERIC)); + + + qtd->in_process = 1; + + if (qh->ep_type == UE_CONTROL) + break; + + if (n_desc == MAX_DMA_DESC_NUM_GENERIC) + break; + } + + if (n_desc) { + /* Request Transfer Complete interrupt for the last descriptor */ + qh->desc_list[n_desc - 1].status.b.ioc = 1; + /* End of List indicator */ + qh->desc_list[n_desc - 1].status.b.eol = 1; + + hc->ntd = n_desc; + } +} + +/** + * For Control and Bulk endpoints initializes descriptor list + * and starts the transfer. + * + * For Interrupt and Isochronous endpoints initializes descriptor list + * then updates FrameList, marking appropriate entries as active. + * In case of Isochronous, the starting descriptor index is calculated based + * on the scheduled frame, but only on the first transfer descriptor within a session. + * Then starts the transfer via enabling the channel. + * For Isochronous endpoint the channel is not halted on XferComplete + * interrupt so remains assigned to the endpoint(QH) until session is done. + * + * @param hcd The HCD state structure for the DWC OTG controller. + * @param qh The QH to init. + * + * @return 0 if successful, negative error code otherwise. + */ +void dwc_otg_hcd_start_xfer_ddma(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh) +{ + /* Channel is already assigned */ + dwc_hc_t *hc = qh->channel; + uint8_t skip_frames = 0; + + switch (hc->ep_type) { + case DWC_OTG_EP_TYPE_CONTROL: + case DWC_OTG_EP_TYPE_BULK: + init_non_isoc_dma_desc(hcd, qh); + + dwc_otg_hc_start_transfer_ddma(hcd->core_if, hc); + break; + case DWC_OTG_EP_TYPE_INTR: + init_non_isoc_dma_desc(hcd, qh); + + update_frame_list(hcd, qh, 1); + + dwc_otg_hc_start_transfer_ddma(hcd->core_if, hc); + break; + case DWC_OTG_EP_TYPE_ISOC: + + if (!qh->ntd) + skip_frames = recalc_initial_desc_idx(hcd, qh); + + init_isoc_dma_desc(hcd, qh, skip_frames); + + if (!hc->xfer_started) { + + update_frame_list(hcd, qh, 1); + + /* + * Always set to max, instead of actual size. + * Otherwise ntd will be changed with + * channel being enabled. Not recommended. + * + */ + hc->ntd = max_desc_num(qh); + /* Enable channel only once for ISOC */ + dwc_otg_hc_start_transfer_ddma(hcd->core_if, hc); + } + + break; + default: + + break; + } +} + +static void complete_isoc_xfer_ddma(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_halt_status_e halt_status) +{ + struct dwc_otg_hcd_iso_packet_desc *frame_desc; + dwc_otg_qtd_t *qtd, *qtd_tmp; + dwc_otg_qh_t *qh; + dwc_otg_host_dma_desc_t *dma_desc; + uint16_t idx, remain; + uint8_t urb_compl; + + qh = hc->qh; + idx = qh->td_first; + + if (hc->halt_status == DWC_OTG_HC_XFER_URB_DEQUEUE) { + DWC_CIRCLEQ_FOREACH_SAFE(qtd, qtd_tmp, &hc->qh->qtd_list, qtd_list_entry) + qtd->in_process = 0; + return; + } else if ((halt_status == DWC_OTG_HC_XFER_AHB_ERR) || + (halt_status == DWC_OTG_HC_XFER_BABBLE_ERR)) { + /* + * Channel is halted in these error cases. + * Considered as serious issues. + * Complete all URBs marking all frames as failed, + * irrespective whether some of the descriptors(frames) succeeded or no. + * Pass error code to completion routine as well, to + * update urb->status, some of class drivers might use it to stop + * queing transfer requests. + */ + int err = (halt_status == DWC_OTG_HC_XFER_AHB_ERR) + ? (-DWC_E_IO) + : (-DWC_E_OVERFLOW); + + DWC_CIRCLEQ_FOREACH_SAFE(qtd, qtd_tmp, &hc->qh->qtd_list, qtd_list_entry) { + for (idx = 0; idx < qtd->urb->packet_count; idx++) { + frame_desc = &qtd->urb->iso_descs[idx]; + frame_desc->status = err; + } + hcd->fops->complete(hcd, qtd->urb->priv, qtd->urb, err); + dwc_otg_hcd_qtd_remove_and_free(hcd, qtd, qh); + } + return; + } + + DWC_CIRCLEQ_FOREACH_SAFE(qtd, qtd_tmp, &hc->qh->qtd_list, qtd_list_entry) { + + if (!qtd->in_process) + break; + + urb_compl = 0; + + do { + + dma_desc = &qh->desc_list[idx]; + + frame_desc = &qtd->urb->iso_descs[qtd->isoc_frame_index]; + remain = hc->ep_is_in ? dma_desc->status.b_isoc.n_bytes : 0; + + if (dma_desc->status.b_isoc.sts == DMA_DESC_STS_PKTERR) { + /* + * XactError or, unable to complete all the transactions + * in the scheduled micro-frame/frame, + * both indicated by DMA_DESC_STS_PKTERR. + */ + qtd->urb->error_count++; + frame_desc->actual_length = qh->n_bytes[idx] - remain; + frame_desc->status = -DWC_E_PROTOCOL; + } else { + /* Success */ + + frame_desc->actual_length = qh->n_bytes[idx] - remain; + frame_desc->status = 0; + } + + if (++qtd->isoc_frame_index == qtd->urb->packet_count) { + /* + * urb->status is not used for isoc transfers here. + * The individual frame_desc status are used instead. + */ + + hcd->fops->complete(hcd, qtd->urb->priv, qtd->urb, 0); + dwc_otg_hcd_qtd_remove_and_free(hcd, qtd, qh); + + /* + * This check is necessary because urb_dequeue can be called + * from urb complete callback(sound driver example). + * All pending URBs are dequeued there, so no need for + * further processing. + */ + if (hc->halt_status == DWC_OTG_HC_XFER_URB_DEQUEUE) { + return; + } + + urb_compl = 1; + + } + + qh->ntd--; + + /* Stop if IOC requested descriptor reached */ + if (dma_desc->status.b_isoc.ioc) { + idx = desclist_idx_inc(idx, qh->interval, hc->speed); + goto stop_scan; + } + + idx = desclist_idx_inc(idx, qh->interval, hc->speed); + + if (urb_compl) + break; + } + while (idx != qh->td_first); + } +stop_scan: + qh->td_first = idx; +} + +static uint8_t update_non_isoc_urb_state_ddma(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_qtd_t * qtd, + dwc_otg_host_dma_desc_t * dma_desc, + dwc_otg_halt_status_e halt_status, + uint32_t n_bytes, uint8_t * xfer_done) +{ + + uint16_t remain = hc->ep_is_in ? dma_desc->status.b.n_bytes : 0; + dwc_otg_hcd_urb_t *urb = qtd->urb; + + if (halt_status == DWC_OTG_HC_XFER_AHB_ERR) { + urb->status = -DWC_E_IO; + return 1; + } + if (dma_desc->status.b.sts == DMA_DESC_STS_PKTERR) { + switch (halt_status) { + case DWC_OTG_HC_XFER_STALL: + urb->status = -DWC_E_PIPE; + break; + case DWC_OTG_HC_XFER_BABBLE_ERR: + urb->status = -DWC_E_OVERFLOW; + break; + case DWC_OTG_HC_XFER_XACT_ERR: + urb->status = -DWC_E_PROTOCOL; + break; + default: + DWC_ERROR("%s: Unhandled descriptor error status (%d)\n", __func__, + halt_status); + break; + } + return 1; + } + + if (dma_desc->status.b.a == 1) { + DWC_DEBUGPL(DBG_HCDV, + "Active descriptor encountered on channel %d\n", + hc->hc_num); + return 0; + } + + if (hc->ep_type == DWC_OTG_EP_TYPE_CONTROL) { + if (qtd->control_phase == DWC_OTG_CONTROL_DATA) { + urb->actual_length += n_bytes - remain; + if (remain || urb->actual_length == urb->length) { + /* + * For Control Data stage do not set urb->status=0 to prevent + * URB callback. Set it when Status phase done. See below. + */ + *xfer_done = 1; + } + + } else if (qtd->control_phase == DWC_OTG_CONTROL_STATUS) { + urb->status = 0; + *xfer_done = 1; + } + /* No handling for SETUP stage */ + } else { + /* BULK and INTR */ + urb->actual_length += n_bytes - remain; + if (remain || urb->actual_length == urb->length) { + urb->status = 0; + *xfer_done = 1; + } + } + + return 0; +} + +static void complete_non_isoc_xfer_ddma(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_halt_status_e halt_status) +{ + dwc_otg_hcd_urb_t *urb = NULL; + dwc_otg_qtd_t *qtd, *qtd_tmp; + dwc_otg_qh_t *qh; + dwc_otg_host_dma_desc_t *dma_desc; + uint32_t n_bytes, n_desc, i; + uint8_t failed = 0, xfer_done; + + n_desc = 0; + + qh = hc->qh; + + if (hc->halt_status == DWC_OTG_HC_XFER_URB_DEQUEUE) { + DWC_CIRCLEQ_FOREACH_SAFE(qtd, qtd_tmp, &hc->qh->qtd_list, qtd_list_entry) { + qtd->in_process = 0; + } + return; + } + + DWC_CIRCLEQ_FOREACH_SAFE(qtd, qtd_tmp, &qh->qtd_list, qtd_list_entry) { + + urb = qtd->urb; + + n_bytes = 0; + xfer_done = 0; + + for (i = 0; i < qtd->n_desc; i++) { + dma_desc = &qh->desc_list[n_desc]; + + n_bytes = qh->n_bytes[n_desc]; + + failed = + update_non_isoc_urb_state_ddma(hcd, hc, qtd, + dma_desc, + halt_status, n_bytes, + &xfer_done); + + if (failed + || (xfer_done + && (urb->status != -DWC_E_IN_PROGRESS))) { + + hcd->fops->complete(hcd, urb->priv, urb, + urb->status); + dwc_otg_hcd_qtd_remove_and_free(hcd, qtd, qh); + + if (failed) + goto stop_scan; + } else if (qh->ep_type == UE_CONTROL) { + if (qtd->control_phase == DWC_OTG_CONTROL_SETUP) { + if (urb->length > 0) { + qtd->control_phase = DWC_OTG_CONTROL_DATA; + } else { + qtd->control_phase = DWC_OTG_CONTROL_STATUS; + } + DWC_DEBUGPL(DBG_HCDV, " Control setup transaction done\n"); + } else if (qtd->control_phase == DWC_OTG_CONTROL_DATA) { + if (xfer_done) { + qtd->control_phase = DWC_OTG_CONTROL_STATUS; + DWC_DEBUGPL(DBG_HCDV, " Control data transfer done\n"); + } else if (i + 1 == qtd->n_desc) { + /* + * Last descriptor for Control data stage which is + * not completed yet. + */ + dwc_otg_hcd_save_data_toggle(hc, hc_regs, qtd); + } + } + } + + n_desc++; + } + + } + +stop_scan: + + if (qh->ep_type != UE_CONTROL) { + /* + * Resetting the data toggle for bulk + * and interrupt endpoints in case of stall. See handle_hc_stall_intr() + */ + if (halt_status == DWC_OTG_HC_XFER_STALL) + qh->data_toggle = DWC_OTG_HC_PID_DATA0; + else + dwc_otg_hcd_save_data_toggle(hc, hc_regs, qtd); + } + + if (halt_status == DWC_OTG_HC_XFER_COMPLETE) { + hcint_data_t hcint; + hcint.d32 = DWC_READ_REG32(&hc_regs->hcint); + if (hcint.b.nyet) { + /* + * Got a NYET on the last transaction of the transfer. It + * means that the endpoint should be in the PING state at the + * beginning of the next transfer. + */ + qh->ping_state = 1; + clear_hc_int(hc_regs, nyet); + } + + } + +} + +/** + * This function is called from interrupt handlers. + * Scans the descriptor list, updates URB's status and + * calls completion routine for the URB if it's done. + * Releases the channel to be used by other transfers. + * In case of Isochronous endpoint the channel is not halted until + * the end of the session, i.e. QTD list is empty. + * If periodic channel released the FrameList is updated accordingly. + * + * Calls transaction selection routines to activate pending transfers. + * + * @param hcd The HCD state structure for the DWC OTG controller. + * @param hc Host channel, the transfer is completed on. + * @param hc_regs Host channel registers. + * @param halt_status Reason the channel is being halted, + * or just XferComplete for isochronous transfer + */ +void dwc_otg_hcd_complete_xfer_ddma(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_halt_status_e halt_status) +{ + uint8_t continue_isoc_xfer = 0; + dwc_otg_transaction_type_e tr_type; + dwc_otg_qh_t *qh = hc->qh; + + if (hc->ep_type == DWC_OTG_EP_TYPE_ISOC) { + + complete_isoc_xfer_ddma(hcd, hc, hc_regs, halt_status); + + /* Release the channel if halted or session completed */ + if (halt_status != DWC_OTG_HC_XFER_COMPLETE || + DWC_CIRCLEQ_EMPTY(&qh->qtd_list)) { + + /* Halt the channel if session completed */ + if (halt_status == DWC_OTG_HC_XFER_COMPLETE) { + dwc_otg_hc_halt(hcd->core_if, hc, halt_status); + } + + release_channel_ddma(hcd, qh); + dwc_otg_hcd_qh_remove(hcd, qh); + } else { + /* Keep in assigned schedule to continue transfer */ + DWC_LIST_MOVE_HEAD(&hcd->periodic_sched_assigned, + &qh->qh_list_entry); + continue_isoc_xfer = 1; + + } + /** @todo Consider the case when period exceeds FrameList size. + * Frame Rollover interrupt should be used. + */ + } else { + /* Scan descriptor list to complete the URB(s), then release the channel */ + complete_non_isoc_xfer_ddma(hcd, hc, hc_regs, halt_status); + + release_channel_ddma(hcd, qh); + dwc_otg_hcd_qh_remove(hcd, qh); + + if (!DWC_CIRCLEQ_EMPTY(&qh->qtd_list)) { + /* Add back to inactive non-periodic schedule on normal completion */ + dwc_otg_hcd_qh_add(hcd, qh); + } + + } + tr_type = dwc_otg_hcd_select_transactions(hcd); + if (tr_type != DWC_OTG_TRANSACTION_NONE || continue_isoc_xfer) { + if (continue_isoc_xfer) { + if (tr_type == DWC_OTG_TRANSACTION_NONE) { + tr_type = DWC_OTG_TRANSACTION_PERIODIC; + } else if (tr_type == DWC_OTG_TRANSACTION_NON_PERIODIC) { + tr_type = DWC_OTG_TRANSACTION_ALL; + } + } + dwc_otg_hcd_queue_transactions(hcd, tr_type); + } +} + +#endif /* DWC_DEVICE_ONLY */ diff --git a/drivers/usb/host/dwc_otg/dwc_otg_hcd_if.h b/drivers/usb/host/dwc_otg/dwc_otg_hcd_if.h new file mode 100644 index 00000000000000..847c547d7ebbec --- /dev/null +++ b/drivers/usb/host/dwc_otg/dwc_otg_hcd_if.h @@ -0,0 +1,441 @@ +/* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_hcd_if.h $ + * $Revision: #12 $ + * $Date: 2011/10/26 $ + * $Change: 1873028 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ +#ifndef DWC_DEVICE_ONLY +#ifndef __DWC_HCD_IF_H__ +#define __DWC_HCD_IF_H__ + +#include "dwc_otg_core_if.h" + +/** @file + * This file defines DWC_OTG HCD Core API. + */ + +struct dwc_otg_hcd; +typedef struct dwc_otg_hcd dwc_otg_hcd_t; + +struct dwc_otg_hcd_urb; +typedef struct dwc_otg_hcd_urb dwc_otg_hcd_urb_t; + +/** @name HCD Function Driver Callbacks */ +/** @{ */ + +/** This function is called whenever core switches to host mode. */ +typedef int (*dwc_otg_hcd_start_cb_t) (dwc_otg_hcd_t * hcd); + +/** This function is called when device has been disconnected */ +typedef int (*dwc_otg_hcd_disconnect_cb_t) (dwc_otg_hcd_t * hcd); + +/** Wrapper provides this function to HCD to core, so it can get hub information to which device is connected */ +typedef int (*dwc_otg_hcd_hub_info_from_urb_cb_t) (dwc_otg_hcd_t * hcd, + void *urb_handle, + uint32_t * hub_addr, + uint32_t * port_addr); +/** Via this function HCD core gets device speed */ +typedef int (*dwc_otg_hcd_speed_from_urb_cb_t) (dwc_otg_hcd_t * hcd, + void *urb_handle); + +/** This function is called when urb is completed */ +typedef int (*dwc_otg_hcd_complete_urb_cb_t) (dwc_otg_hcd_t * hcd, + void *urb_handle, + dwc_otg_hcd_urb_t * dwc_otg_urb, + int32_t status); + +/** Via this function HCD core gets b_hnp_enable parameter */ +typedef int (*dwc_otg_hcd_get_b_hnp_enable) (dwc_otg_hcd_t * hcd); + +struct dwc_otg_hcd_function_ops { + dwc_otg_hcd_start_cb_t start; + dwc_otg_hcd_disconnect_cb_t disconnect; + dwc_otg_hcd_hub_info_from_urb_cb_t hub_info; + dwc_otg_hcd_speed_from_urb_cb_t speed; + dwc_otg_hcd_complete_urb_cb_t complete; + dwc_otg_hcd_get_b_hnp_enable get_b_hnp_enable; +}; +/** @} */ + +/** @name HCD Core API */ +/** @{ */ +/** This function allocates dwc_otg_hcd structure and returns pointer on it. */ +extern dwc_otg_hcd_t *dwc_otg_hcd_alloc_hcd(void); + +/** This function should be called to initiate HCD Core. + * + * @param hcd The HCD + * @param core_if The DWC_OTG Core + * + * Returns -DWC_E_NO_MEMORY if no enough memory. + * Returns 0 on success + */ +extern int dwc_otg_hcd_init(dwc_otg_hcd_t * hcd, dwc_otg_core_if_t * core_if); + +/** Frees HCD + * + * @param hcd The HCD + */ +extern void dwc_otg_hcd_remove(dwc_otg_hcd_t * hcd); + +/** This function should be called on every hardware interrupt. + * + * @param dwc_otg_hcd The HCD + * + * Returns non zero if interrupt is handled + * Return 0 if interrupt is not handled + */ +extern int32_t dwc_otg_hcd_handle_intr(dwc_otg_hcd_t * dwc_otg_hcd); + +/** This function is used to handle the fast interrupt + * + */ +#ifdef CONFIG_ARM64 +extern void dwc_otg_hcd_handle_fiq(void); +#else +extern void __attribute__ ((naked)) dwc_otg_hcd_handle_fiq(void); +#endif + +/** + * Returns private data set by + * dwc_otg_hcd_set_priv_data function. + * + * @param hcd The HCD + */ +extern void *dwc_otg_hcd_get_priv_data(dwc_otg_hcd_t * hcd); + +/** + * Set private data. + * + * @param hcd The HCD + * @param priv_data pointer to be stored in private data + */ +extern void dwc_otg_hcd_set_priv_data(dwc_otg_hcd_t * hcd, void *priv_data); + +/** + * This function initializes the HCD Core. + * + * @param hcd The HCD + * @param fops The Function Driver Operations data structure containing pointers to all callbacks. + * + * Returns -DWC_E_NO_DEVICE if Core is currently is in device mode. + * Returns 0 on success + */ +extern int dwc_otg_hcd_start(dwc_otg_hcd_t * hcd, + struct dwc_otg_hcd_function_ops *fops); + +/** + * Halts the DWC_otg host mode operations in a clean manner. USB transfers are + * stopped. + * + * @param hcd The HCD + */ +extern void dwc_otg_hcd_stop(dwc_otg_hcd_t * hcd); + +/** + * Handles hub class-specific requests. + * + * @param dwc_otg_hcd The HCD + * @param typeReq Request Type + * @param wValue wValue from control request + * @param wIndex wIndex from control request + * @param buf data buffer + * @param wLength data buffer length + * + * Returns -DWC_E_INVALID if invalid argument is passed + * Returns 0 on success + */ +extern int dwc_otg_hcd_hub_control(dwc_otg_hcd_t * dwc_otg_hcd, + uint16_t typeReq, uint16_t wValue, + uint16_t wIndex, uint8_t * buf, + uint16_t wLength); + +/** + * Returns otg port number. + * + * @param hcd The HCD + */ +extern uint32_t dwc_otg_hcd_otg_port(dwc_otg_hcd_t * hcd); + +/** + * Returns OTG version - either 1.3 or 2.0. + * + * @param core_if The core_if structure pointer + */ +extern uint16_t dwc_otg_get_otg_version(dwc_otg_core_if_t * core_if); + +/** + * Returns 1 if currently core is acting as B host, and 0 otherwise. + * + * @param hcd The HCD + */ +extern uint32_t dwc_otg_hcd_is_b_host(dwc_otg_hcd_t * hcd); + +/** + * Returns current frame number. + * + * @param hcd The HCD + */ +extern int dwc_otg_hcd_get_frame_number(dwc_otg_hcd_t * hcd); + +/** + * Dumps hcd state. + * + * @param hcd The HCD + */ +extern void dwc_otg_hcd_dump_state(dwc_otg_hcd_t * hcd); + +/** + * Dump the average frame remaining at SOF. This can be used to + * determine average interrupt latency. Frame remaining is also shown for + * start transfer and two additional sample points. + * Currently this function is not implemented. + * + * @param hcd The HCD + */ +extern void dwc_otg_hcd_dump_frrem(dwc_otg_hcd_t * hcd); + +/** + * Sends LPM transaction to the local device. + * + * @param hcd The HCD + * @param devaddr Device Address + * @param hird Host initiated resume duration + * @param bRemoteWake Value of bRemoteWake field in LPM transaction + * + * Returns negative value if sending LPM transaction was not succeeded. + * Returns 0 on success. + */ +extern int dwc_otg_hcd_send_lpm(dwc_otg_hcd_t * hcd, uint8_t devaddr, + uint8_t hird, uint8_t bRemoteWake); + +/* URB interface */ + +/** + * Allocates memory for dwc_otg_hcd_urb structure. + * Allocated memory should be freed by call of DWC_FREE. + * + * @param hcd The HCD + * @param iso_desc_count Count of ISOC descriptors + * @param atomic_alloc Specefies whether to perform atomic allocation. + */ +extern dwc_otg_hcd_urb_t *dwc_otg_hcd_urb_alloc(dwc_otg_hcd_t * hcd, + int iso_desc_count, + int atomic_alloc); + +/** + * Set pipe information in URB. + * + * @param hcd_urb DWC_OTG URB + * @param devaddr Device Address + * @param ep_num Endpoint Number + * @param ep_type Endpoint Type + * @param ep_dir Endpoint Direction + * @param mps Max Packet Size + */ +extern void dwc_otg_hcd_urb_set_pipeinfo(dwc_otg_hcd_urb_t * hcd_urb, + uint8_t devaddr, uint8_t ep_num, + uint8_t ep_type, uint8_t ep_dir, + uint16_t mps); + +/* Transfer flags */ +#define URB_GIVEBACK_ASAP 0x1 +#define URB_SEND_ZERO_PACKET 0x2 + +/** + * Sets dwc_otg_hcd_urb parameters. + * + * @param urb DWC_OTG URB allocated by dwc_otg_hcd_urb_alloc function. + * @param urb_handle Unique handle for request, this will be passed back + * to function driver in completion callback. + * @param buf The buffer for the data + * @param dma The DMA buffer for the data + * @param buflen Transfer length + * @param sp Buffer for setup data + * @param sp_dma DMA address of setup data buffer + * @param flags Transfer flags + * @param interval Polling interval for interrupt or isochronous transfers. + */ +extern void dwc_otg_hcd_urb_set_params(dwc_otg_hcd_urb_t * urb, + void *urb_handle, void *buf, + dwc_dma_t dma, uint32_t buflen, void *sp, + dwc_dma_t sp_dma, uint32_t flags, + uint16_t interval); + +/** Gets status from dwc_otg_hcd_urb + * + * @param dwc_otg_urb DWC_OTG URB + */ +extern uint32_t dwc_otg_hcd_urb_get_status(dwc_otg_hcd_urb_t * dwc_otg_urb); + +/** Gets actual length from dwc_otg_hcd_urb + * + * @param dwc_otg_urb DWC_OTG URB + */ +extern uint32_t dwc_otg_hcd_urb_get_actual_length(dwc_otg_hcd_urb_t * + dwc_otg_urb); + +/** Gets error count from dwc_otg_hcd_urb. Only for ISOC URBs + * + * @param dwc_otg_urb DWC_OTG URB + */ +extern uint32_t dwc_otg_hcd_urb_get_error_count(dwc_otg_hcd_urb_t * + dwc_otg_urb); + +/** Set ISOC descriptor offset and length + * + * @param dwc_otg_urb DWC_OTG URB + * @param desc_num ISOC descriptor number + * @param offset Offset from beginig of buffer. + * @param length Transaction length + */ +extern void dwc_otg_hcd_urb_set_iso_desc_params(dwc_otg_hcd_urb_t * dwc_otg_urb, + int desc_num, uint32_t offset, + uint32_t length); + +/** Get status of ISOC descriptor, specified by desc_num + * + * @param dwc_otg_urb DWC_OTG URB + * @param desc_num ISOC descriptor number + */ +extern uint32_t dwc_otg_hcd_urb_get_iso_desc_status(dwc_otg_hcd_urb_t * + dwc_otg_urb, int desc_num); + +/** Get actual length of ISOC descriptor, specified by desc_num + * + * @param dwc_otg_urb DWC_OTG URB + * @param desc_num ISOC descriptor number + */ +extern uint32_t dwc_otg_hcd_urb_get_iso_desc_actual_length(dwc_otg_hcd_urb_t * + dwc_otg_urb, + int desc_num); + +/** Queue URB. After transfer is completes, the complete callback will be called with the URB status + * + * @param dwc_otg_hcd The HCD + * @param dwc_otg_urb DWC_OTG URB + * @param ep_handle Out parameter for returning endpoint handle + * @param atomic_alloc Flag to do atomic allocation if needed + * + * Returns -DWC_E_NO_DEVICE if no device is connected. + * Returns -DWC_E_NO_MEMORY if there is no enough memory. + * Returns 0 on success. + */ +extern int dwc_otg_hcd_urb_enqueue(dwc_otg_hcd_t * dwc_otg_hcd, + dwc_otg_hcd_urb_t * dwc_otg_urb, + void **ep_handle, int atomic_alloc); + +/** De-queue the specified URB + * + * @param dwc_otg_hcd The HCD + * @param dwc_otg_urb DWC_OTG URB + */ +extern int dwc_otg_hcd_urb_dequeue(dwc_otg_hcd_t * dwc_otg_hcd, + dwc_otg_hcd_urb_t * dwc_otg_urb); + +/** Frees resources in the DWC_otg controller related to a given endpoint. + * Any URBs for the endpoint must already be dequeued. + * + * @param hcd The HCD + * @param ep_handle Endpoint handle, returned by dwc_otg_hcd_urb_enqueue function + * @param retry Number of retries if there are queued transfers. + * + * Returns -DWC_E_INVALID if invalid arguments are passed. + * Returns 0 on success + */ +extern int dwc_otg_hcd_endpoint_disable(dwc_otg_hcd_t * hcd, void *ep_handle, + int retry); + +/* Resets the data toggle in qh structure. This function can be called from + * usb_clear_halt routine. + * + * @param hcd The HCD + * @param ep_handle Endpoint handle, returned by dwc_otg_hcd_urb_enqueue function + * + * Returns -DWC_E_INVALID if invalid arguments are passed. + * Returns 0 on success + */ +extern int dwc_otg_hcd_endpoint_reset(dwc_otg_hcd_t * hcd, void *ep_handle); + +/** Returns 1 if status of specified port is changed and 0 otherwise. + * + * @param hcd The HCD + * @param port Port number + */ +extern int dwc_otg_hcd_is_status_changed(dwc_otg_hcd_t * hcd, int port); + +/** Call this function to check if bandwidth was allocated for specified endpoint. + * Only for ISOC and INTERRUPT endpoints. + * + * @param hcd The HCD + * @param ep_handle Endpoint handle + */ +extern int dwc_otg_hcd_is_bandwidth_allocated(dwc_otg_hcd_t * hcd, + void *ep_handle); + +/** Call this function to check if bandwidth was freed for specified endpoint. + * + * @param hcd The HCD + * @param ep_handle Endpoint handle + */ +extern int dwc_otg_hcd_is_bandwidth_freed(dwc_otg_hcd_t * hcd, void *ep_handle); + +/** Returns bandwidth allocated for specified endpoint in microseconds. + * Only for ISOC and INTERRUPT endpoints. + * + * @param hcd The HCD + * @param ep_handle Endpoint handle + */ +extern uint8_t dwc_otg_hcd_get_ep_bandwidth(dwc_otg_hcd_t * hcd, + void *ep_handle); + +extern int hcd_init( +#ifdef LM_INTERFACE + struct lm_device *_dev +#elif defined(PCI_INTERFACE) + struct pci_dev *_dev +#elif defined(PLATFORM_INTERFACE) + struct platform_device *dev +#endif + ); + +extern void hcd_remove( +#ifdef LM_INTERFACE + struct lm_device *_dev +#elif defined(PCI_INTERFACE) + struct pci_dev *_dev +#elif defined(PLATFORM_INTERFACE) + struct platform_device *_dev +#endif + ); + +/** @} */ + +#endif /* __DWC_HCD_IF_H__ */ +#endif /* DWC_DEVICE_ONLY */ diff --git a/drivers/usb/host/dwc_otg/dwc_otg_hcd_intr.c b/drivers/usb/host/dwc_otg/dwc_otg_hcd_intr.c new file mode 100644 index 00000000000000..2a1617b475fcec --- /dev/null +++ b/drivers/usb/host/dwc_otg/dwc_otg_hcd_intr.c @@ -0,0 +1,2757 @@ +/* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_hcd_intr.c $ + * $Revision: #89 $ + * $Date: 2011/10/20 $ + * $Change: 1869487 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ +#ifndef DWC_DEVICE_ONLY + +#include "dwc_otg_hcd.h" +#include "dwc_otg_regs.h" + +#include <linux/jiffies.h> +#ifdef CONFIG_ARM +#include <asm/fiq.h> +#endif + +extern bool microframe_schedule; + +/** @file + * This file contains the implementation of the HCD Interrupt handlers. + */ + +int fiq_done, int_done; + +#ifdef FIQ_DEBUG +char buffer[1000*16]; +int wptr; +void notrace _fiq_print(FIQDBG_T dbg_lvl, char *fmt, ...) +{ + FIQDBG_T dbg_lvl_req = FIQDBG_PORTHUB; + va_list args; + char text[17]; + hfnum_data_t hfnum = { .d32 = FIQ_READ(dwc_regs_base + 0x408) }; + + if(dbg_lvl & dbg_lvl_req || dbg_lvl == FIQDBG_ERR) + { + local_fiq_disable(); + snprintf(text, 9, "%4d%d:%d ", hfnum.b.frnum/8, hfnum.b.frnum%8, 8 - hfnum.b.frrem/937); + va_start(args, fmt); + vsnprintf(text+8, 9, fmt, args); + va_end(args); + + memcpy(buffer + wptr, text, 16); + wptr = (wptr + 16) % sizeof(buffer); + local_fiq_enable(); + } +} +#endif + +/** This function handles interrupts for the HCD. */ +int32_t dwc_otg_hcd_handle_intr(dwc_otg_hcd_t * dwc_otg_hcd) +{ + int retval = 0; + static int last_time; + dwc_otg_core_if_t *core_if = dwc_otg_hcd->core_if; + gintsts_data_t gintsts; + gintmsk_data_t gintmsk; + hfnum_data_t hfnum; + haintmsk_data_t haintmsk; + +#ifdef DEBUG + dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs; + +#endif + + gintsts.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintsts); + gintmsk.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintmsk); + + /* Exit from ISR if core is hibernated */ + if (core_if->hibernation_suspend == 1) { + goto exit_handler_routine; + } + DWC_SPINLOCK(dwc_otg_hcd->lock); + /* Check if HOST Mode */ + if (dwc_otg_is_host_mode(core_if)) { + if (fiq_enable) { + local_fiq_disable(); + fiq_fsm_spin_lock(&dwc_otg_hcd->fiq_state->lock); + /* Pull in from the FIQ's disabled mask */ + gintmsk.d32 = gintmsk.d32 | ~(dwc_otg_hcd->fiq_state->gintmsk_saved.d32); + dwc_otg_hcd->fiq_state->gintmsk_saved.d32 = ~0; + } + + if (fiq_fsm_enable && ( 0x0000FFFF & ~(dwc_otg_hcd->fiq_state->haintmsk_saved.b2.chint))) { + gintsts.b.hcintr = 1; + } + + /* Danger will robinson: fake a SOF if necessary */ + if (fiq_fsm_enable && (dwc_otg_hcd->fiq_state->gintmsk_saved.b.sofintr == 1)) { + gintsts.b.sofintr = 1; + } + gintsts.d32 &= gintmsk.d32; + + if (fiq_enable) { + fiq_fsm_spin_unlock(&dwc_otg_hcd->fiq_state->lock); + local_fiq_enable(); + } + + if (!gintsts.d32) { + goto exit_handler_routine; + } + +#ifdef DEBUG + // We should be OK doing this because the common interrupts should already have been serviced + /* Don't print debug message in the interrupt handler on SOF */ +#ifndef DEBUG_SOF + if (gintsts.d32 != DWC_SOF_INTR_MASK) +#endif + DWC_DEBUGPL(DBG_HCDI, "\n"); +#endif + +#ifdef DEBUG +#ifndef DEBUG_SOF + if (gintsts.d32 != DWC_SOF_INTR_MASK) +#endif + DWC_DEBUGPL(DBG_HCDI, + "DWC OTG HCD Interrupt Detected gintsts&gintmsk=0x%08x core_if=%p\n", + gintsts.d32, core_if); +#endif + hfnum.d32 = DWC_READ_REG32(&dwc_otg_hcd->core_if->host_if->host_global_regs->hfnum); + if (gintsts.b.sofintr) { + retval |= dwc_otg_hcd_handle_sof_intr(dwc_otg_hcd); + } + + if (gintsts.b.rxstsqlvl) { + retval |= + dwc_otg_hcd_handle_rx_status_q_level_intr + (dwc_otg_hcd); + } + if (gintsts.b.nptxfempty) { + retval |= + dwc_otg_hcd_handle_np_tx_fifo_empty_intr + (dwc_otg_hcd); + } + if (gintsts.b.i2cintr) { + /** @todo Implement i2cintr handler. */ + } + if (gintsts.b.portintr) { + + gintmsk_data_t gintmsk = { .b.portintr = 1}; + retval |= dwc_otg_hcd_handle_port_intr(dwc_otg_hcd); + if (fiq_enable) { + local_fiq_disable(); + fiq_fsm_spin_lock(&dwc_otg_hcd->fiq_state->lock); + DWC_MODIFY_REG32(&dwc_otg_hcd->core_if->core_global_regs->gintmsk, 0, gintmsk.d32); + fiq_fsm_spin_unlock(&dwc_otg_hcd->fiq_state->lock); + local_fiq_enable(); + } else { + DWC_MODIFY_REG32(&dwc_otg_hcd->core_if->core_global_regs->gintmsk, 0, gintmsk.d32); + } + } + if (gintsts.b.hcintr) { + retval |= dwc_otg_hcd_handle_hc_intr(dwc_otg_hcd); + } + if (gintsts.b.ptxfempty) { + retval |= + dwc_otg_hcd_handle_perio_tx_fifo_empty_intr + (dwc_otg_hcd); + } +#ifdef DEBUG +#ifndef DEBUG_SOF + if (gintsts.d32 != DWC_SOF_INTR_MASK) +#endif + { + DWC_DEBUGPL(DBG_HCDI, + "DWC OTG HCD Finished Servicing Interrupts\n"); + DWC_DEBUGPL(DBG_HCDV, "DWC OTG HCD gintsts=0x%08x\n", + DWC_READ_REG32(&global_regs->gintsts)); + DWC_DEBUGPL(DBG_HCDV, "DWC OTG HCD gintmsk=0x%08x\n", + DWC_READ_REG32(&global_regs->gintmsk)); + } +#endif + +#ifdef DEBUG +#ifndef DEBUG_SOF + if (gintsts.d32 != DWC_SOF_INTR_MASK) +#endif + DWC_DEBUGPL(DBG_HCDI, "\n"); +#endif + + } + +exit_handler_routine: + if (fiq_enable) { + gintmsk_data_t gintmsk_new; + haintmsk_data_t haintmsk_new; + local_fiq_disable(); + fiq_fsm_spin_lock(&dwc_otg_hcd->fiq_state->lock); + gintmsk_new.d32 = *(volatile uint32_t *)&dwc_otg_hcd->fiq_state->gintmsk_saved.d32; + if(fiq_fsm_enable) + haintmsk_new.d32 = *(volatile uint32_t *)&dwc_otg_hcd->fiq_state->haintmsk_saved.d32; + else + haintmsk_new.d32 = 0x0000FFFF; + + /* The FIQ could have sneaked another interrupt in. If so, don't clear MPHI */ + if ((gintmsk_new.d32 == ~0) && (haintmsk_new.d32 == 0x0000FFFF)) { + if (dwc_otg_hcd->fiq_state->mphi_regs.swirq_clr) { + DWC_WRITE_REG32(dwc_otg_hcd->fiq_state->mphi_regs.swirq_clr, 1); + } else { + DWC_WRITE_REG32(dwc_otg_hcd->fiq_state->mphi_regs.intstat, (1<<16)); + } + if (dwc_otg_hcd->fiq_state->mphi_int_count >= 50) { + fiq_print(FIQDBG_INT, dwc_otg_hcd->fiq_state, "MPHI CLR"); + DWC_WRITE_REG32(dwc_otg_hcd->fiq_state->mphi_regs.ctrl, ((1<<31) + (1<<16))); + while (!(DWC_READ_REG32(dwc_otg_hcd->fiq_state->mphi_regs.ctrl) & (1 << 17))) + ; + DWC_WRITE_REG32(dwc_otg_hcd->fiq_state->mphi_regs.ctrl, (1<<31)); + dwc_otg_hcd->fiq_state->mphi_int_count = 0; + } + int_done++; + } + haintmsk.d32 = DWC_READ_REG32(&core_if->host_if->host_global_regs->haintmsk); + /* Re-enable interrupts that the FIQ masked (first time round) */ + FIQ_WRITE(dwc_otg_hcd->fiq_state->dwc_regs_base + GINTMSK, gintmsk.d32); + fiq_fsm_spin_unlock(&dwc_otg_hcd->fiq_state->lock); + local_fiq_enable(); + + if ((jiffies / HZ) > last_time) { + //dwc_otg_qh_t *qh; + //dwc_list_link_t *cur; + /* Once a second output the fiq and irq numbers, useful for debug */ + last_time = jiffies / HZ; + // DWC_WARN("np_kick=%d AHC=%d sched_frame=%d cur_frame=%d int_done=%d fiq_done=%d", + // dwc_otg_hcd->fiq_state->kick_np_queues, dwc_otg_hcd->available_host_channels, + // dwc_otg_hcd->fiq_state->next_sched_frame, hfnum.b.frnum, int_done, dwc_otg_hcd->fiq_state->fiq_done); + //printk(KERN_WARNING "Periodic queues:\n"); + } + } + + DWC_SPINUNLOCK(dwc_otg_hcd->lock); + return retval; +} + +#ifdef DWC_TRACK_MISSED_SOFS + +#warning Compiling code to track missed SOFs +#define FRAME_NUM_ARRAY_SIZE 1000 +/** + * This function is for debug only. + */ +static inline void track_missed_sofs(uint16_t curr_frame_number) +{ + static uint16_t frame_num_array[FRAME_NUM_ARRAY_SIZE]; + static uint16_t last_frame_num_array[FRAME_NUM_ARRAY_SIZE]; + static int frame_num_idx = 0; + static uint16_t last_frame_num = DWC_HFNUM_MAX_FRNUM; + static int dumped_frame_num_array = 0; + + if (frame_num_idx < FRAME_NUM_ARRAY_SIZE) { + if (((last_frame_num + 1) & DWC_HFNUM_MAX_FRNUM) != + curr_frame_number) { + frame_num_array[frame_num_idx] = curr_frame_number; + last_frame_num_array[frame_num_idx++] = last_frame_num; + } + } else if (!dumped_frame_num_array) { + int i; + DWC_PRINTF("Frame Last Frame\n"); + DWC_PRINTF("----- ----------\n"); + for (i = 0; i < FRAME_NUM_ARRAY_SIZE; i++) { + DWC_PRINTF("0x%04x 0x%04x\n", + frame_num_array[i], last_frame_num_array[i]); + } + dumped_frame_num_array = 1; + } + last_frame_num = curr_frame_number; +} +#endif + +/** + * Handles the start-of-frame interrupt in host mode. Non-periodic + * transactions may be queued to the DWC_otg controller for the current + * (micro)frame. Periodic transactions may be queued to the controller for the + * next (micro)frame. + */ +int32_t dwc_otg_hcd_handle_sof_intr(dwc_otg_hcd_t * hcd) +{ + hfnum_data_t hfnum; + gintsts_data_t gintsts = { .d32 = 0 }; + dwc_list_link_t *qh_entry; + dwc_otg_qh_t *qh; + dwc_otg_transaction_type_e tr_type; + int did_something = 0; + int32_t next_sched_frame = -1; + + hfnum.d32 = + DWC_READ_REG32(&hcd->core_if->host_if->host_global_regs->hfnum); + +#ifdef DEBUG_SOF + DWC_DEBUGPL(DBG_HCD, "--Start of Frame Interrupt--\n"); +#endif + hcd->frame_number = hfnum.b.frnum; + +#ifdef DEBUG + hcd->frrem_accum += hfnum.b.frrem; + hcd->frrem_samples++; +#endif + +#ifdef DWC_TRACK_MISSED_SOFS + track_missed_sofs(hcd->frame_number); +#endif + /* Determine whether any periodic QHs should be executed. */ + qh_entry = DWC_LIST_FIRST(&hcd->periodic_sched_inactive); + while (qh_entry != &hcd->periodic_sched_inactive) { + qh = DWC_LIST_ENTRY(qh_entry, dwc_otg_qh_t, qh_list_entry); + qh_entry = qh_entry->next; + if (dwc_frame_num_le(qh->sched_frame, hcd->frame_number)) { + + /* + * Move QH to the ready list to be executed next + * (micro)frame. + */ + DWC_LIST_MOVE_HEAD(&hcd->periodic_sched_ready, + &qh->qh_list_entry); + + did_something = 1; + } + else + { + if(next_sched_frame < 0 || dwc_frame_num_le(qh->sched_frame, next_sched_frame)) + { + next_sched_frame = qh->sched_frame; + } + } + } + if (fiq_enable) + hcd->fiq_state->next_sched_frame = next_sched_frame; + + tr_type = dwc_otg_hcd_select_transactions(hcd); + if (tr_type != DWC_OTG_TRANSACTION_NONE) { + dwc_otg_hcd_queue_transactions(hcd, tr_type); + did_something = 1; + } + + /* Clear interrupt - but do not trample on the FIQ sof */ + if (!fiq_fsm_enable) { + gintsts.b.sofintr = 1; + DWC_WRITE_REG32(&hcd->core_if->core_global_regs->gintsts, gintsts.d32); + } + return 1; +} + +/** Handles the Rx Status Queue Level Interrupt, which indicates that there is at + * least one packet in the Rx FIFO. The packets are moved from the FIFO to + * memory if the DWC_otg controller is operating in Slave mode. */ +int32_t dwc_otg_hcd_handle_rx_status_q_level_intr(dwc_otg_hcd_t * dwc_otg_hcd) +{ + host_grxsts_data_t grxsts; + dwc_hc_t *hc = NULL; + + DWC_DEBUGPL(DBG_HCD, "--RxStsQ Level Interrupt--\n"); + + grxsts.d32 = + DWC_READ_REG32(&dwc_otg_hcd->core_if->core_global_regs->grxstsp); + + hc = dwc_otg_hcd->hc_ptr_array[grxsts.b.chnum]; + if (!hc) { + DWC_ERROR("Unable to get corresponding channel\n"); + return 0; + } + + /* Packet Status */ + DWC_DEBUGPL(DBG_HCDV, " Ch num = %d\n", grxsts.b.chnum); + DWC_DEBUGPL(DBG_HCDV, " Count = %d\n", grxsts.b.bcnt); + DWC_DEBUGPL(DBG_HCDV, " DPID = %d, hc.dpid = %d\n", grxsts.b.dpid, + hc->data_pid_start); + DWC_DEBUGPL(DBG_HCDV, " PStatus = %d\n", grxsts.b.pktsts); + + switch (grxsts.b.pktsts) { + case DWC_GRXSTS_PKTSTS_IN: + /* Read the data into the host buffer. */ + if (grxsts.b.bcnt > 0) { + dwc_otg_read_packet(dwc_otg_hcd->core_if, + hc->xfer_buff, grxsts.b.bcnt); + + /* Update the HC fields for the next packet received. */ + hc->xfer_count += grxsts.b.bcnt; + hc->xfer_buff += grxsts.b.bcnt; + } + break; + case DWC_GRXSTS_PKTSTS_IN_XFER_COMP: + case DWC_GRXSTS_PKTSTS_DATA_TOGGLE_ERR: + case DWC_GRXSTS_PKTSTS_CH_HALTED: + /* Handled in interrupt, just ignore data */ + break; + default: + DWC_ERROR("RX_STS_Q Interrupt: Unknown status %d\n", + grxsts.b.pktsts); + break; + } + + return 1; +} + +/** This interrupt occurs when the non-periodic Tx FIFO is half-empty. More + * data packets may be written to the FIFO for OUT transfers. More requests + * may be written to the non-periodic request queue for IN transfers. This + * interrupt is enabled only in Slave mode. */ +int32_t dwc_otg_hcd_handle_np_tx_fifo_empty_intr(dwc_otg_hcd_t * dwc_otg_hcd) +{ + DWC_DEBUGPL(DBG_HCD, "--Non-Periodic TxFIFO Empty Interrupt--\n"); + dwc_otg_hcd_queue_transactions(dwc_otg_hcd, + DWC_OTG_TRANSACTION_NON_PERIODIC); + return 1; +} + +/** This interrupt occurs when the periodic Tx FIFO is half-empty. More data + * packets may be written to the FIFO for OUT transfers. More requests may be + * written to the periodic request queue for IN transfers. This interrupt is + * enabled only in Slave mode. */ +int32_t dwc_otg_hcd_handle_perio_tx_fifo_empty_intr(dwc_otg_hcd_t * dwc_otg_hcd) +{ + DWC_DEBUGPL(DBG_HCD, "--Periodic TxFIFO Empty Interrupt--\n"); + dwc_otg_hcd_queue_transactions(dwc_otg_hcd, + DWC_OTG_TRANSACTION_PERIODIC); + return 1; +} + +/** There are multiple conditions that can cause a port interrupt. This function + * determines which interrupt conditions have occurred and handles them + * appropriately. */ +int32_t dwc_otg_hcd_handle_port_intr(dwc_otg_hcd_t * dwc_otg_hcd) +{ + int retval = 0; + hprt0_data_t hprt0; + hprt0_data_t hprt0_modify; + + hprt0.d32 = DWC_READ_REG32(dwc_otg_hcd->core_if->host_if->hprt0); + hprt0_modify.d32 = DWC_READ_REG32(dwc_otg_hcd->core_if->host_if->hprt0); + + /* Clear appropriate bits in HPRT0 to clear the interrupt bit in + * GINTSTS */ + + hprt0_modify.b.prtena = 0; + hprt0_modify.b.prtconndet = 0; + hprt0_modify.b.prtenchng = 0; + hprt0_modify.b.prtovrcurrchng = 0; + + /* Port Connect Detected + * Set flag and clear if detected */ + if (dwc_otg_hcd->core_if->hibernation_suspend == 1) { + // Dont modify port status if we are in hibernation state + hprt0_modify.b.prtconndet = 1; + hprt0_modify.b.prtenchng = 1; + DWC_WRITE_REG32(dwc_otg_hcd->core_if->host_if->hprt0, hprt0_modify.d32); + hprt0.d32 = DWC_READ_REG32(dwc_otg_hcd->core_if->host_if->hprt0); + return retval; + } + + if (hprt0.b.prtconndet) { + /** @todo - check if steps performed in 'else' block should be perfromed regardles adp */ + if (dwc_otg_hcd->core_if->adp_enable && + dwc_otg_hcd->core_if->adp.vbuson_timer_started == 1) { + DWC_PRINTF("PORT CONNECT DETECTED ----------------\n"); + DWC_TIMER_CANCEL(dwc_otg_hcd->core_if->adp.vbuson_timer); + dwc_otg_hcd->core_if->adp.vbuson_timer_started = 0; + /* TODO - check if this is required, as + * host initialization was already performed + * after initial ADP probing + */ + /*dwc_otg_hcd->core_if->adp.vbuson_timer_started = 0; + dwc_otg_core_init(dwc_otg_hcd->core_if); + dwc_otg_enable_global_interrupts(dwc_otg_hcd->core_if); + cil_hcd_start(dwc_otg_hcd->core_if);*/ + } else { + + DWC_DEBUGPL(DBG_HCD, "--Port Interrupt HPRT0=0x%08x " + "Port Connect Detected--\n", hprt0.d32); + dwc_otg_hcd->flags.b.port_connect_status_change = 1; + dwc_otg_hcd->flags.b.port_connect_status = 1; + hprt0_modify.b.prtconndet = 1; + + /* B-Device has connected, Delete the connection timer. */ + DWC_TIMER_CANCEL(dwc_otg_hcd->conn_timer); + } + /* The Hub driver asserts a reset when it sees port connect + * status change flag */ + retval |= 1; + } + + /* Port Enable Changed + * Clear if detected - Set internal flag if disabled */ + if (hprt0.b.prtenchng) { + DWC_DEBUGPL(DBG_HCD, " --Port Interrupt HPRT0=0x%08x " + "Port Enable Changed--\n", hprt0.d32); + hprt0_modify.b.prtenchng = 1; + if (hprt0.b.prtena == 1) { + hfir_data_t hfir; + int do_reset = 0; + dwc_otg_core_params_t *params = + dwc_otg_hcd->core_if->core_params; + dwc_otg_core_global_regs_t *global_regs = + dwc_otg_hcd->core_if->core_global_regs; + dwc_otg_host_if_t *host_if = + dwc_otg_hcd->core_if->host_if; + + dwc_otg_hcd->flags.b.port_speed = hprt0.b.prtspd; + if (microframe_schedule) + init_hcd_usecs(dwc_otg_hcd); + + /* Every time when port enables calculate + * HFIR.FrInterval + */ + hfir.d32 = DWC_READ_REG32(&host_if->host_global_regs->hfir); + hfir.b.frint = calc_frame_interval(dwc_otg_hcd->core_if); + DWC_WRITE_REG32(&host_if->host_global_regs->hfir, hfir.d32); + + /* Check if we need to adjust the PHY clock speed for + * low power and adjust it */ + if (params->host_support_fs_ls_low_power) { + gusbcfg_data_t usbcfg; + + usbcfg.d32 = + DWC_READ_REG32(&global_regs->gusbcfg); + + if (hprt0.b.prtspd == DWC_HPRT0_PRTSPD_LOW_SPEED + || hprt0.b.prtspd == + DWC_HPRT0_PRTSPD_FULL_SPEED) { + /* + * Low power + */ + hcfg_data_t hcfg; + if (usbcfg.b.phylpwrclksel == 0) { + /* Set PHY low power clock select for FS/LS devices */ + usbcfg.b.phylpwrclksel = 1; + DWC_WRITE_REG32 + (&global_regs->gusbcfg, + usbcfg.d32); + do_reset = 1; + } + + hcfg.d32 = + DWC_READ_REG32 + (&host_if->host_global_regs->hcfg); + + if (hprt0.b.prtspd == + DWC_HPRT0_PRTSPD_LOW_SPEED + && params->host_ls_low_power_phy_clk + == + DWC_HOST_LS_LOW_POWER_PHY_CLK_PARAM_6MHZ) + { + /* 6 MHZ */ + DWC_DEBUGPL(DBG_CIL, + "FS_PHY programming HCFG to 6 MHz (Low Power)\n"); + if (hcfg.b.fslspclksel != + DWC_HCFG_6_MHZ) { + hcfg.b.fslspclksel = + DWC_HCFG_6_MHZ; + DWC_WRITE_REG32 + (&host_if->host_global_regs->hcfg, + hcfg.d32); + do_reset = 1; + } + } else { + /* 48 MHZ */ + DWC_DEBUGPL(DBG_CIL, + "FS_PHY programming HCFG to 48 MHz ()\n"); + if (hcfg.b.fslspclksel != + DWC_HCFG_48_MHZ) { + hcfg.b.fslspclksel = + DWC_HCFG_48_MHZ; + DWC_WRITE_REG32 + (&host_if->host_global_regs->hcfg, + hcfg.d32); + do_reset = 1; + } + } + } else { + /* + * Not low power + */ + if (usbcfg.b.phylpwrclksel == 1) { + usbcfg.b.phylpwrclksel = 0; + DWC_WRITE_REG32 + (&global_regs->gusbcfg, + usbcfg.d32); + do_reset = 1; + } + } + + if (do_reset) { + DWC_TASK_SCHEDULE(dwc_otg_hcd->reset_tasklet); + } + } + + if (!do_reset) { + /* Port has been enabled set the reset change flag */ + dwc_otg_hcd->flags.b.port_reset_change = 1; + } + } else { + dwc_otg_hcd->flags.b.port_enable_change = 1; + } + retval |= 1; + } + + /** Overcurrent Change Interrupt */ + if (hprt0.b.prtovrcurrchng) { + DWC_DEBUGPL(DBG_HCD, " --Port Interrupt HPRT0=0x%08x " + "Port Overcurrent Changed--\n", hprt0.d32); + dwc_otg_hcd->flags.b.port_over_current_change = 1; + hprt0_modify.b.prtovrcurrchng = 1; + retval |= 1; + } + + /* Clear Port Interrupts */ + DWC_WRITE_REG32(dwc_otg_hcd->core_if->host_if->hprt0, hprt0_modify.d32); + + return retval; +} + +/** This interrupt indicates that one or more host channels has a pending + * interrupt. There are multiple conditions that can cause each host channel + * interrupt. This function determines which conditions have occurred for each + * host channel interrupt and handles them appropriately. */ +int32_t dwc_otg_hcd_handle_hc_intr(dwc_otg_hcd_t * dwc_otg_hcd) +{ + int i; + int retval = 0; + haint_data_t haint = { .d32 = 0 } ; + + /* Clear appropriate bits in HCINTn to clear the interrupt bit in + * GINTSTS */ + + if (!fiq_fsm_enable) + haint.d32 = dwc_otg_read_host_all_channels_intr(dwc_otg_hcd->core_if); + + // Overwrite with saved interrupts from fiq handler + if(fiq_fsm_enable) + { + /* check the mask? */ + local_fiq_disable(); + fiq_fsm_spin_lock(&dwc_otg_hcd->fiq_state->lock); + haint.b2.chint |= ~(dwc_otg_hcd->fiq_state->haintmsk_saved.b2.chint); + dwc_otg_hcd->fiq_state->haintmsk_saved.b2.chint = ~0; + fiq_fsm_spin_unlock(&dwc_otg_hcd->fiq_state->lock); + local_fiq_enable(); + } + + for (i = 0; i < dwc_otg_hcd->core_if->core_params->host_channels; i++) { + if (haint.b2.chint & (1 << i)) { + retval |= dwc_otg_hcd_handle_hc_n_intr(dwc_otg_hcd, i); + } + } + + return retval; +} + +/** + * Gets the actual length of a transfer after the transfer halts. _halt_status + * holds the reason for the halt. + * + * For IN transfers where halt_status is DWC_OTG_HC_XFER_COMPLETE, + * *short_read is set to 1 upon return if less than the requested + * number of bytes were transferred. Otherwise, *short_read is set to 0 upon + * return. short_read may also be NULL on entry, in which case it remains + * unchanged. + */ +static uint32_t get_actual_xfer_length(dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_qtd_t * qtd, + dwc_otg_halt_status_e halt_status, + int *short_read) +{ + hctsiz_data_t hctsiz; + uint32_t length; + + if (short_read != NULL) { + *short_read = 0; + } + hctsiz.d32 = DWC_READ_REG32(&hc_regs->hctsiz); + + if (halt_status == DWC_OTG_HC_XFER_COMPLETE) { + if (hc->ep_is_in) { + length = hc->xfer_len - hctsiz.b.xfersize; + if (short_read != NULL) { + *short_read = (hctsiz.b.xfersize != 0); + } + } else if (hc->qh->do_split) { + //length = split_out_xfersize[hc->hc_num]; + length = qtd->ssplit_out_xfer_count; + } else { + length = hc->xfer_len; + } + } else { + /* + * Must use the hctsiz.pktcnt field to determine how much data + * has been transferred. This field reflects the number of + * packets that have been transferred via the USB. This is + * always an integral number of packets if the transfer was + * halted before its normal completion. (Can't use the + * hctsiz.xfersize field because that reflects the number of + * bytes transferred via the AHB, not the USB). + */ + length = + (hc->start_pkt_count - hctsiz.b.pktcnt) * hc->max_packet; + } + + return length; +} + +/** + * Updates the state of the URB after a Transfer Complete interrupt on the + * host channel. Updates the actual_length field of the URB based on the + * number of bytes transferred via the host channel. Sets the URB status + * if the data transfer is finished. + * + * @return 1 if the data transfer specified by the URB is completely finished, + * 0 otherwise. + */ +static int update_urb_state_xfer_comp(dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_hcd_urb_t * urb, + dwc_otg_qtd_t * qtd) +{ + int xfer_done = 0; + int short_read = 0; + + int xfer_length; + + xfer_length = get_actual_xfer_length(hc, hc_regs, qtd, + DWC_OTG_HC_XFER_COMPLETE, + &short_read); + + if (urb->actual_length + xfer_length > urb->length) { + printk_once(KERN_DEBUG "dwc_otg: DEVICE:%03d : %s:%d:trimming xfer length\n", + hc->dev_addr, __func__, __LINE__); + xfer_length = urb->length - urb->actual_length; + } + + /* non DWORD-aligned buffer case handling. */ + if (hc->align_buff && xfer_length && hc->ep_is_in) { + dwc_memcpy(urb->buf + urb->actual_length, hc->qh->dw_align_buf, + xfer_length); + } + + urb->actual_length += xfer_length; + + if (xfer_length && (hc->ep_type == DWC_OTG_EP_TYPE_BULK) && + (urb->flags & URB_SEND_ZERO_PACKET) + && (urb->actual_length == urb->length) + && !(urb->length % hc->max_packet)) { + xfer_done = 0; + } else if (short_read || urb->actual_length >= urb->length) { + xfer_done = 1; + urb->status = 0; + } + +#ifdef DEBUG + { + hctsiz_data_t hctsiz; + hctsiz.d32 = DWC_READ_REG32(&hc_regs->hctsiz); + DWC_DEBUGPL(DBG_HCDV, "DWC_otg: %s: %s, channel %d\n", + __func__, (hc->ep_is_in ? "IN" : "OUT"), + hc->hc_num); + DWC_DEBUGPL(DBG_HCDV, " hc->xfer_len %d\n", hc->xfer_len); + DWC_DEBUGPL(DBG_HCDV, " hctsiz.xfersize %d\n", + hctsiz.b.xfersize); + DWC_DEBUGPL(DBG_HCDV, " urb->transfer_buffer_length %d\n", + urb->length); + DWC_DEBUGPL(DBG_HCDV, " urb->actual_length %d\n", + urb->actual_length); + DWC_DEBUGPL(DBG_HCDV, " short_read %d, xfer_done %d\n", + short_read, xfer_done); + } +#endif + + return xfer_done; +} + +/* + * Save the starting data toggle for the next transfer. The data toggle is + * saved in the QH for non-control transfers and it's saved in the QTD for + * control transfers. + */ +void dwc_otg_hcd_save_data_toggle(dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, dwc_otg_qtd_t * qtd) +{ + hctsiz_data_t hctsiz; + hctsiz.d32 = DWC_READ_REG32(&hc_regs->hctsiz); + + if (hc->ep_type != DWC_OTG_EP_TYPE_CONTROL) { + dwc_otg_qh_t *qh = hc->qh; + if (hctsiz.b.pid == DWC_HCTSIZ_DATA0) { + qh->data_toggle = DWC_OTG_HC_PID_DATA0; + } else { + qh->data_toggle = DWC_OTG_HC_PID_DATA1; + } + } else { + if (hctsiz.b.pid == DWC_HCTSIZ_DATA0) { + qtd->data_toggle = DWC_OTG_HC_PID_DATA0; + } else { + qtd->data_toggle = DWC_OTG_HC_PID_DATA1; + } + } +} + +/** + * Updates the state of an Isochronous URB when the transfer is stopped for + * any reason. The fields of the current entry in the frame descriptor array + * are set based on the transfer state and the input _halt_status. Completes + * the Isochronous URB if all the URB frames have been completed. + * + * @return DWC_OTG_HC_XFER_COMPLETE if there are more frames remaining to be + * transferred in the URB. Otherwise return DWC_OTG_HC_XFER_URB_COMPLETE. + */ +static dwc_otg_halt_status_e +update_isoc_urb_state(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_qtd_t * qtd, dwc_otg_halt_status_e halt_status) +{ + dwc_otg_hcd_urb_t *urb = qtd->urb; + dwc_otg_halt_status_e ret_val = halt_status; + struct dwc_otg_hcd_iso_packet_desc *frame_desc; + + frame_desc = &urb->iso_descs[qtd->isoc_frame_index]; + switch (halt_status) { + case DWC_OTG_HC_XFER_COMPLETE: + frame_desc->status = 0; + frame_desc->actual_length = + get_actual_xfer_length(hc, hc_regs, qtd, halt_status, NULL); + + /* non DWORD-aligned buffer case handling. */ + if (hc->align_buff && frame_desc->actual_length && hc->ep_is_in) { + dwc_memcpy(urb->buf + frame_desc->offset + qtd->isoc_split_offset, + hc->qh->dw_align_buf, frame_desc->actual_length); + } + + break; + case DWC_OTG_HC_XFER_FRAME_OVERRUN: + urb->error_count++; + if (hc->ep_is_in) { + frame_desc->status = -DWC_E_NO_STREAM_RES; + } else { + frame_desc->status = -DWC_E_COMMUNICATION; + } + frame_desc->actual_length = 0; + break; + case DWC_OTG_HC_XFER_BABBLE_ERR: + urb->error_count++; + frame_desc->status = -DWC_E_OVERFLOW; + /* Don't need to update actual_length in this case. */ + break; + case DWC_OTG_HC_XFER_XACT_ERR: + urb->error_count++; + frame_desc->status = -DWC_E_PROTOCOL; + frame_desc->actual_length = + get_actual_xfer_length(hc, hc_regs, qtd, halt_status, NULL); + + /* non DWORD-aligned buffer case handling. */ + if (hc->align_buff && frame_desc->actual_length && hc->ep_is_in) { + dwc_memcpy(urb->buf + frame_desc->offset + qtd->isoc_split_offset, + hc->qh->dw_align_buf, frame_desc->actual_length); + } + /* Skip whole frame */ + if (hc->qh->do_split && (hc->ep_type == DWC_OTG_EP_TYPE_ISOC) && + hc->ep_is_in && hcd->core_if->dma_enable) { + qtd->complete_split = 0; + qtd->isoc_split_offset = 0; + } + + break; + default: + DWC_ASSERT(1, "Unhandled _halt_status (%d)\n", halt_status); + break; + } + if (++qtd->isoc_frame_index == urb->packet_count) { + /* + * urb->status is not used for isoc transfers. + * The individual frame_desc statuses are used instead. + */ + hcd->fops->complete(hcd, urb->priv, urb, 0); + ret_val = DWC_OTG_HC_XFER_URB_COMPLETE; + } else { + ret_val = DWC_OTG_HC_XFER_COMPLETE; + } + return ret_val; +} + +/** + * Frees the first QTD in the QH's list if free_qtd is 1. For non-periodic + * QHs, removes the QH from the active non-periodic schedule. If any QTDs are + * still linked to the QH, the QH is added to the end of the inactive + * non-periodic schedule. For periodic QHs, removes the QH from the periodic + * schedule if no more QTDs are linked to the QH. + */ +static void deactivate_qh(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh, int free_qtd) +{ + int continue_split = 0; + dwc_otg_qtd_t *qtd; + + DWC_DEBUGPL(DBG_HCDV, " %s(%p,%p,%d)\n", __func__, hcd, qh, free_qtd); + + qtd = DWC_CIRCLEQ_FIRST(&qh->qtd_list); + + if (qtd->complete_split) { + continue_split = 1; + } else if (qtd->isoc_split_pos == DWC_HCSPLIT_XACTPOS_MID || + qtd->isoc_split_pos == DWC_HCSPLIT_XACTPOS_END) { + continue_split = 1; + } + + if (free_qtd) { + dwc_otg_hcd_qtd_remove_and_free(hcd, qtd, qh); + continue_split = 0; + } + + qh->channel = NULL; + dwc_otg_hcd_qh_deactivate(hcd, qh, continue_split); +} + +/** + * Releases a host channel for use by other transfers. Attempts to select and + * queue more transactions since at least one host channel is available. + * + * @param hcd The HCD state structure. + * @param hc The host channel to release. + * @param qtd The QTD associated with the host channel. This QTD may be freed + * if the transfer is complete or an error has occurred. + * @param halt_status Reason the channel is being released. This status + * determines the actions taken by this function. + */ +static void release_channel(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_qtd_t * qtd, + dwc_otg_halt_status_e halt_status) +{ + dwc_otg_transaction_type_e tr_type; + int free_qtd; + + int hog_port = 0; + + DWC_DEBUGPL(DBG_HCDV, " %s: channel %d, halt_status %d, xfer_len %d\n", + __func__, hc->hc_num, halt_status, hc->xfer_len); + + if(fiq_fsm_enable && hc->do_split) { + if(!hc->ep_is_in && hc->ep_type == UE_ISOCHRONOUS) { + if(hc->xact_pos == DWC_HCSPLIT_XACTPOS_MID || + hc->xact_pos == DWC_HCSPLIT_XACTPOS_BEGIN) { + hog_port = 0; + } + } + } + + switch (halt_status) { + case DWC_OTG_HC_XFER_URB_COMPLETE: + free_qtd = 1; + break; + case DWC_OTG_HC_XFER_AHB_ERR: + case DWC_OTG_HC_XFER_STALL: + case DWC_OTG_HC_XFER_BABBLE_ERR: + free_qtd = 1; + break; + case DWC_OTG_HC_XFER_XACT_ERR: + if (qtd->error_count >= 3) { + DWC_DEBUGPL(DBG_HCDV, + " Complete URB with transaction error\n"); + free_qtd = 1; + qtd->urb->status = -DWC_E_PROTOCOL; + hcd->fops->complete(hcd, qtd->urb->priv, + qtd->urb, -DWC_E_PROTOCOL); + } else { + free_qtd = 0; + } + break; + case DWC_OTG_HC_XFER_URB_DEQUEUE: + /* + * The QTD has already been removed and the QH has been + * deactivated. Don't want to do anything except release the + * host channel and try to queue more transfers. + */ + goto cleanup; + case DWC_OTG_HC_XFER_NO_HALT_STATUS: + free_qtd = 0; + break; + case DWC_OTG_HC_XFER_PERIODIC_INCOMPLETE: + DWC_DEBUGPL(DBG_HCDV, + " Complete URB with I/O error\n"); + free_qtd = 1; + qtd->urb->status = -DWC_E_IO; + hcd->fops->complete(hcd, qtd->urb->priv, + qtd->urb, -DWC_E_IO); + break; + default: + free_qtd = 0; + break; + } + + deactivate_qh(hcd, hc->qh, free_qtd); + +cleanup: + /* + * Release the host channel for use by other transfers. The cleanup + * function clears the channel interrupt enables and conditions, so + * there's no need to clear the Channel Halted interrupt separately. + */ + if (fiq_fsm_enable && hcd->fiq_state->channel[hc->hc_num].fsm != FIQ_PASSTHROUGH) + dwc_otg_cleanup_fiq_channel(hcd, hc->hc_num); + dwc_otg_hc_cleanup(hcd->core_if, hc); + DWC_CIRCLEQ_INSERT_TAIL(&hcd->free_hc_list, hc, hc_list_entry); + + if (!microframe_schedule) { + switch (hc->ep_type) { + case DWC_OTG_EP_TYPE_CONTROL: + case DWC_OTG_EP_TYPE_BULK: + hcd->non_periodic_channels--; + break; + + default: + /* + * Don't release reservations for periodic channels here. + * That's done when a periodic transfer is descheduled (i.e. + * when the QH is removed from the periodic schedule). + */ + break; + } + } else { + hcd->available_host_channels++; + fiq_print(FIQDBG_INT, hcd->fiq_state, "AHC = %d ", hcd->available_host_channels); + } + + /* Try to queue more transfers now that there's a free channel. */ + tr_type = dwc_otg_hcd_select_transactions(hcd); + if (tr_type != DWC_OTG_TRANSACTION_NONE) { + dwc_otg_hcd_queue_transactions(hcd, tr_type); + } +} + +/** + * Halts a host channel. If the channel cannot be halted immediately because + * the request queue is full, this function ensures that the FIFO empty + * interrupt for the appropriate queue is enabled so that the halt request can + * be queued when there is space in the request queue. + * + * This function may also be called in DMA mode. In that case, the channel is + * simply released since the core always halts the channel automatically in + * DMA mode. + */ +static void halt_channel(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_qtd_t * qtd, dwc_otg_halt_status_e halt_status) +{ + if (hcd->core_if->dma_enable) { + release_channel(hcd, hc, qtd, halt_status); + return; + } + + /* Slave mode processing... */ + dwc_otg_hc_halt(hcd->core_if, hc, halt_status); + + if (hc->halt_on_queue) { + gintmsk_data_t gintmsk = {.d32 = 0 }; + dwc_otg_core_global_regs_t *global_regs; + global_regs = hcd->core_if->core_global_regs; + + if (hc->ep_type == DWC_OTG_EP_TYPE_CONTROL || + hc->ep_type == DWC_OTG_EP_TYPE_BULK) { + /* + * Make sure the Non-periodic Tx FIFO empty interrupt + * is enabled so that the non-periodic schedule will + * be processed. + */ + gintmsk.b.nptxfempty = 1; + if (fiq_enable) { + local_fiq_disable(); + fiq_fsm_spin_lock(&hcd->fiq_state->lock); + DWC_MODIFY_REG32(&global_regs->gintmsk, 0, gintmsk.d32); + fiq_fsm_spin_unlock(&hcd->fiq_state->lock); + local_fiq_enable(); + } else { + DWC_MODIFY_REG32(&global_regs->gintmsk, 0, gintmsk.d32); + } + } else { + /* + * Move the QH from the periodic queued schedule to + * the periodic assigned schedule. This allows the + * halt to be queued when the periodic schedule is + * processed. + */ + DWC_LIST_MOVE_HEAD(&hcd->periodic_sched_assigned, + &hc->qh->qh_list_entry); + + /* + * Make sure the Periodic Tx FIFO Empty interrupt is + * enabled so that the periodic schedule will be + * processed. + */ + gintmsk.b.ptxfempty = 1; + if (fiq_enable) { + local_fiq_disable(); + fiq_fsm_spin_lock(&hcd->fiq_state->lock); + DWC_MODIFY_REG32(&global_regs->gintmsk, 0, gintmsk.d32); + fiq_fsm_spin_unlock(&hcd->fiq_state->lock); + local_fiq_enable(); + } else { + DWC_MODIFY_REG32(&global_regs->gintmsk, 0, gintmsk.d32); + } + } + } +} + +/** + * Performs common cleanup for non-periodic transfers after a Transfer + * Complete interrupt. This function should be called after any endpoint type + * specific handling is finished to release the host channel. + */ +static void complete_non_periodic_xfer(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_qtd_t * qtd, + dwc_otg_halt_status_e halt_status) +{ + hcint_data_t hcint; + + qtd->error_count = 0; + + hcint.d32 = DWC_READ_REG32(&hc_regs->hcint); + if (hcint.b.nyet) { + /* + * Got a NYET on the last transaction of the transfer. This + * means that the endpoint should be in the PING state at the + * beginning of the next transfer. + */ + hc->qh->ping_state = 1; + clear_hc_int(hc_regs, nyet); + } + + /* + * Always halt and release the host channel to make it available for + * more transfers. There may still be more phases for a control + * transfer or more data packets for a bulk transfer at this point, + * but the host channel is still halted. A channel will be reassigned + * to the transfer when the non-periodic schedule is processed after + * the channel is released. This allows transactions to be queued + * properly via dwc_otg_hcd_queue_transactions, which also enables the + * Tx FIFO Empty interrupt if necessary. + */ + if (hc->ep_is_in) { + /* + * IN transfers in Slave mode require an explicit disable to + * halt the channel. (In DMA mode, this call simply releases + * the channel.) + */ + halt_channel(hcd, hc, qtd, halt_status); + } else { + /* + * The channel is automatically disabled by the core for OUT + * transfers in Slave mode. + */ + release_channel(hcd, hc, qtd, halt_status); + } +} + +/** + * Performs common cleanup for periodic transfers after a Transfer Complete + * interrupt. This function should be called after any endpoint type specific + * handling is finished to release the host channel. + */ +static void complete_periodic_xfer(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_qtd_t * qtd, + dwc_otg_halt_status_e halt_status) +{ + hctsiz_data_t hctsiz; + qtd->error_count = 0; + + hctsiz.d32 = DWC_READ_REG32(&hc_regs->hctsiz); + if (!hc->ep_is_in || hctsiz.b.pktcnt == 0) { + /* Core halts channel in these cases. */ + release_channel(hcd, hc, qtd, halt_status); + } else { + /* Flush any outstanding requests from the Tx queue. */ + halt_channel(hcd, hc, qtd, halt_status); + } +} + +static int32_t handle_xfercomp_isoc_split_in(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_qtd_t * qtd) +{ + uint32_t len; + struct dwc_otg_hcd_iso_packet_desc *frame_desc; + frame_desc = &qtd->urb->iso_descs[qtd->isoc_frame_index]; + + len = get_actual_xfer_length(hc, hc_regs, qtd, + DWC_OTG_HC_XFER_COMPLETE, NULL); + + if (!len) { + qtd->complete_split = 0; + qtd->isoc_split_offset = 0; + return 0; + } + frame_desc->actual_length += len; + + if (hc->align_buff && len) + dwc_memcpy(qtd->urb->buf + frame_desc->offset + + qtd->isoc_split_offset, hc->qh->dw_align_buf, len); + qtd->isoc_split_offset += len; + + if (frame_desc->length == frame_desc->actual_length) { + frame_desc->status = 0; + qtd->isoc_frame_index++; + qtd->complete_split = 0; + qtd->isoc_split_offset = 0; + } + + if (qtd->isoc_frame_index == qtd->urb->packet_count) { + hcd->fops->complete(hcd, qtd->urb->priv, qtd->urb, 0); + release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_URB_COMPLETE); + } else { + release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NO_HALT_STATUS); + } + + return 1; /* Indicates that channel released */ +} + +/** + * Handles a host channel Transfer Complete interrupt. This handler may be + * called in either DMA mode or Slave mode. + */ +static int32_t handle_hc_xfercomp_intr(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_qtd_t * qtd) +{ + int urb_xfer_done; + dwc_otg_halt_status_e halt_status = DWC_OTG_HC_XFER_COMPLETE; + dwc_otg_hcd_urb_t *urb = qtd->urb; + int pipe_type = dwc_otg_hcd_get_pipe_type(&urb->pipe_info); + + DWC_DEBUGPL(DBG_HCDI, "--Host Channel %d Interrupt: " + "Transfer Complete--\n", hc->hc_num); + + if (hcd->core_if->dma_desc_enable) { + dwc_otg_hcd_complete_xfer_ddma(hcd, hc, hc_regs, halt_status); + if (pipe_type == UE_ISOCHRONOUS) { + /* Do not disable the interrupt, just clear it */ + clear_hc_int(hc_regs, xfercomp); + return 1; + } + goto handle_xfercomp_done; + } + + /* + * Handle xfer complete on CSPLIT. + */ + + if (hc->qh->do_split) { + if ((hc->ep_type == DWC_OTG_EP_TYPE_ISOC) && hc->ep_is_in + && hcd->core_if->dma_enable) { + if (qtd->complete_split + && handle_xfercomp_isoc_split_in(hcd, hc, hc_regs, + qtd)) + goto handle_xfercomp_done; + } else { + qtd->complete_split = 0; + } + } + + /* Update the QTD and URB states. */ + switch (pipe_type) { + case UE_CONTROL: + switch (qtd->control_phase) { + case DWC_OTG_CONTROL_SETUP: + if (urb->length > 0) { + qtd->control_phase = DWC_OTG_CONTROL_DATA; + } else { + qtd->control_phase = DWC_OTG_CONTROL_STATUS; + } + DWC_DEBUGPL(DBG_HCDV, + " Control setup transaction done\n"); + halt_status = DWC_OTG_HC_XFER_COMPLETE; + break; + case DWC_OTG_CONTROL_DATA:{ + urb_xfer_done = + update_urb_state_xfer_comp(hc, hc_regs, urb, + qtd); + if (urb_xfer_done) { + qtd->control_phase = + DWC_OTG_CONTROL_STATUS; + DWC_DEBUGPL(DBG_HCDV, + " Control data transfer done\n"); + } else { + dwc_otg_hcd_save_data_toggle(hc, hc_regs, qtd); + } + halt_status = DWC_OTG_HC_XFER_COMPLETE; + break; + } + case DWC_OTG_CONTROL_STATUS: + DWC_DEBUGPL(DBG_HCDV, " Control transfer complete\n"); + if (urb->status == -DWC_E_IN_PROGRESS) { + urb->status = 0; + } + hcd->fops->complete(hcd, urb->priv, urb, urb->status); + halt_status = DWC_OTG_HC_XFER_URB_COMPLETE; + break; + } + + complete_non_periodic_xfer(hcd, hc, hc_regs, qtd, halt_status); + break; + case UE_BULK: + DWC_DEBUGPL(DBG_HCDV, " Bulk transfer complete\n"); + urb_xfer_done = + update_urb_state_xfer_comp(hc, hc_regs, urb, qtd); + if (urb_xfer_done) { + hcd->fops->complete(hcd, urb->priv, urb, urb->status); + halt_status = DWC_OTG_HC_XFER_URB_COMPLETE; + } else { + halt_status = DWC_OTG_HC_XFER_COMPLETE; + } + + dwc_otg_hcd_save_data_toggle(hc, hc_regs, qtd); + complete_non_periodic_xfer(hcd, hc, hc_regs, qtd, halt_status); + break; + case UE_INTERRUPT: + DWC_DEBUGPL(DBG_HCDV, " Interrupt transfer complete\n"); + urb_xfer_done = + update_urb_state_xfer_comp(hc, hc_regs, urb, qtd); + + /* + * Interrupt URB is done on the first transfer complete + * interrupt. + */ + if (urb_xfer_done) { + hcd->fops->complete(hcd, urb->priv, urb, urb->status); + halt_status = DWC_OTG_HC_XFER_URB_COMPLETE; + } else { + halt_status = DWC_OTG_HC_XFER_COMPLETE; + } + + dwc_otg_hcd_save_data_toggle(hc, hc_regs, qtd); + complete_periodic_xfer(hcd, hc, hc_regs, qtd, halt_status); + break; + case UE_ISOCHRONOUS: + DWC_DEBUGPL(DBG_HCDV, " Isochronous transfer complete\n"); + if (qtd->isoc_split_pos == DWC_HCSPLIT_XACTPOS_ALL) { + halt_status = + update_isoc_urb_state(hcd, hc, hc_regs, qtd, + DWC_OTG_HC_XFER_COMPLETE); + } + complete_periodic_xfer(hcd, hc, hc_regs, qtd, halt_status); + break; + } + +handle_xfercomp_done: + disable_hc_int(hc_regs, xfercompl); + + return 1; +} + +/** + * Handles a host channel STALL interrupt. This handler may be called in + * either DMA mode or Slave mode. + */ +static int32_t handle_hc_stall_intr(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_qtd_t * qtd) +{ + dwc_otg_hcd_urb_t *urb = qtd->urb; + int pipe_type = dwc_otg_hcd_get_pipe_type(&urb->pipe_info); + + DWC_DEBUGPL(DBG_HCD, "--Host Channel %d Interrupt: " + "STALL Received--\n", hc->hc_num); + + if (hcd->core_if->dma_desc_enable) { + dwc_otg_hcd_complete_xfer_ddma(hcd, hc, hc_regs, DWC_OTG_HC_XFER_STALL); + goto handle_stall_done; + } + + if (pipe_type == UE_CONTROL) { + hcd->fops->complete(hcd, urb->priv, urb, -DWC_E_PIPE); + } + + if (pipe_type == UE_BULK || pipe_type == UE_INTERRUPT) { + hcd->fops->complete(hcd, urb->priv, urb, -DWC_E_PIPE); + /* + * USB protocol requires resetting the data toggle for bulk + * and interrupt endpoints when a CLEAR_FEATURE(ENDPOINT_HALT) + * setup command is issued to the endpoint. Anticipate the + * CLEAR_FEATURE command since a STALL has occurred and reset + * the data toggle now. + */ + hc->qh->data_toggle = 0; + } + + halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_STALL); + +handle_stall_done: + disable_hc_int(hc_regs, stall); + + return 1; +} + +/* + * Updates the state of the URB when a transfer has been stopped due to an + * abnormal condition before the transfer completes. Modifies the + * actual_length field of the URB to reflect the number of bytes that have + * actually been transferred via the host channel. + */ +static void update_urb_state_xfer_intr(dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_hcd_urb_t * urb, + dwc_otg_qtd_t * qtd, + dwc_otg_halt_status_e halt_status) +{ + uint32_t bytes_transferred = get_actual_xfer_length(hc, hc_regs, qtd, + halt_status, NULL); + + if (urb->actual_length + bytes_transferred > urb->length) { + printk_once(KERN_DEBUG "dwc_otg: DEVICE:%03d : %s:%d:trimming xfer length\n", + hc->dev_addr, __func__, __LINE__); + bytes_transferred = urb->length - urb->actual_length; + } + + /* non DWORD-aligned buffer case handling. */ + if (hc->align_buff && bytes_transferred && hc->ep_is_in) { + dwc_memcpy(urb->buf + urb->actual_length, hc->qh->dw_align_buf, + bytes_transferred); + } + + urb->actual_length += bytes_transferred; + +#ifdef DEBUG + { + hctsiz_data_t hctsiz; + hctsiz.d32 = DWC_READ_REG32(&hc_regs->hctsiz); + DWC_DEBUGPL(DBG_HCDV, "DWC_otg: %s: %s, channel %d\n", + __func__, (hc->ep_is_in ? "IN" : "OUT"), + hc->hc_num); + DWC_DEBUGPL(DBG_HCDV, " hc->start_pkt_count %d\n", + hc->start_pkt_count); + DWC_DEBUGPL(DBG_HCDV, " hctsiz.pktcnt %d\n", hctsiz.b.pktcnt); + DWC_DEBUGPL(DBG_HCDV, " hc->max_packet %d\n", hc->max_packet); + DWC_DEBUGPL(DBG_HCDV, " bytes_transferred %d\n", + bytes_transferred); + DWC_DEBUGPL(DBG_HCDV, " urb->actual_length %d\n", + urb->actual_length); + DWC_DEBUGPL(DBG_HCDV, " urb->transfer_buffer_length %d\n", + urb->length); + } +#endif +} + +/** + * Handles a host channel NAK interrupt. This handler may be called in either + * DMA mode or Slave mode. + */ +static int32_t handle_hc_nak_intr(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_qtd_t * qtd) +{ + DWC_DEBUGPL(DBG_HCDI, "--Host Channel %d Interrupt: " + "NAK Received--\n", hc->hc_num); + + /* + * When we get bulk NAKs then remember this so we holdoff on this qh until + * the beginning of the next frame + */ + switch(dwc_otg_hcd_get_pipe_type(&qtd->urb->pipe_info)) { + case UE_BULK: + case UE_CONTROL: + if (nak_holdoff && qtd->qh->do_split) + hc->qh->nak_frame = dwc_otg_hcd_get_frame_number(hcd); + } + + /* + * Handle NAK for IN/OUT SSPLIT/CSPLIT transfers, bulk, control, and + * interrupt. Re-start the SSPLIT transfer. + */ + if (hc->do_split) { + if (hc->complete_split) { + qtd->error_count = 0; + } + qtd->complete_split = 0; + halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NAK); + goto handle_nak_done; + } + + switch (dwc_otg_hcd_get_pipe_type(&qtd->urb->pipe_info)) { + case UE_CONTROL: + case UE_BULK: + if (hcd->core_if->dma_enable && hc->ep_is_in) { + /* + * NAK interrupts are enabled on bulk/control IN + * transfers in DMA mode for the sole purpose of + * resetting the error count after a transaction error + * occurs. The core will continue transferring data. + * Disable other interrupts unmasked for the same + * reason. + */ + disable_hc_int(hc_regs, datatglerr); + disable_hc_int(hc_regs, ack); + qtd->error_count = 0; + goto handle_nak_done; + } + + /* + * NAK interrupts normally occur during OUT transfers in DMA + * or Slave mode. For IN transfers, more requests will be + * queued as request queue space is available. + */ + qtd->error_count = 0; + + if (!hc->qh->ping_state) { + update_urb_state_xfer_intr(hc, hc_regs, + qtd->urb, qtd, + DWC_OTG_HC_XFER_NAK); + dwc_otg_hcd_save_data_toggle(hc, hc_regs, qtd); + + if (hc->speed == DWC_OTG_EP_SPEED_HIGH) + hc->qh->ping_state = 1; + } + + /* + * Halt the channel so the transfer can be re-started from + * the appropriate point or the PING protocol will + * start/continue. + */ + halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NAK); + break; + case UE_INTERRUPT: + qtd->error_count = 0; + halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NAK); + break; + case UE_ISOCHRONOUS: + /* Should never get called for isochronous transfers. */ + DWC_ASSERT(1, "NACK interrupt for ISOC transfer\n"); + break; + } + +handle_nak_done: + disable_hc_int(hc_regs, nak); + + return 1; +} + +/** + * Handles a host channel ACK interrupt. This interrupt is enabled when + * performing the PING protocol in Slave mode, when errors occur during + * either Slave mode or DMA mode, and during Start Split transactions. + */ +static int32_t handle_hc_ack_intr(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_qtd_t * qtd) +{ + DWC_DEBUGPL(DBG_HCDI, "--Host Channel %d Interrupt: " + "ACK Received--\n", hc->hc_num); + + if (hc->do_split) { + /* + * Handle ACK on SSPLIT. + * ACK should not occur in CSPLIT. + */ + if (!hc->ep_is_in && hc->data_pid_start != DWC_OTG_HC_PID_SETUP) { + qtd->ssplit_out_xfer_count = hc->xfer_len; + } + if (!(hc->ep_type == DWC_OTG_EP_TYPE_ISOC && !hc->ep_is_in)) { + /* Don't need complete for isochronous out transfers. */ + qtd->complete_split = 1; + } + + /* ISOC OUT */ + if (hc->ep_type == DWC_OTG_EP_TYPE_ISOC && !hc->ep_is_in) { + switch (hc->xact_pos) { + case DWC_HCSPLIT_XACTPOS_ALL: + break; + case DWC_HCSPLIT_XACTPOS_END: + qtd->isoc_split_pos = DWC_HCSPLIT_XACTPOS_ALL; + qtd->isoc_split_offset = 0; + break; + case DWC_HCSPLIT_XACTPOS_BEGIN: + case DWC_HCSPLIT_XACTPOS_MID: + /* + * For BEGIN or MID, calculate the length for + * the next microframe to determine the correct + * SSPLIT token, either MID or END. + */ + { + struct dwc_otg_hcd_iso_packet_desc + *frame_desc; + + frame_desc = + &qtd->urb-> + iso_descs[qtd->isoc_frame_index]; + qtd->isoc_split_offset += 188; + + if ((frame_desc->length - + qtd->isoc_split_offset) <= 188) { + qtd->isoc_split_pos = + DWC_HCSPLIT_XACTPOS_END; + } else { + qtd->isoc_split_pos = + DWC_HCSPLIT_XACTPOS_MID; + } + + } + break; + } + } else { + halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_ACK); + } + } else { + /* + * An unmasked ACK on a non-split DMA transaction is + * for the sole purpose of resetting error counts. Disable other + * interrupts unmasked for the same reason. + */ + if(hcd->core_if->dma_enable) { + disable_hc_int(hc_regs, datatglerr); + disable_hc_int(hc_regs, nak); + } + qtd->error_count = 0; + + if (hc->qh->ping_state) { + hc->qh->ping_state = 0; + /* + * Halt the channel so the transfer can be re-started + * from the appropriate point. This only happens in + * Slave mode. In DMA mode, the ping_state is cleared + * when the transfer is started because the core + * automatically executes the PING, then the transfer. + */ + halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_ACK); + } + } + + /* + * If the ACK occurred when _not_ in the PING state, let the channel + * continue transferring data after clearing the error count. + */ + + disable_hc_int(hc_regs, ack); + + return 1; +} + +/** + * Handles a host channel NYET interrupt. This interrupt should only occur on + * Bulk and Control OUT endpoints and for complete split transactions. If a + * NYET occurs at the same time as a Transfer Complete interrupt, it is + * handled in the xfercomp interrupt handler, not here. This handler may be + * called in either DMA mode or Slave mode. + */ +static int32_t handle_hc_nyet_intr(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_qtd_t * qtd) +{ + DWC_DEBUGPL(DBG_HCDI, "--Host Channel %d Interrupt: " + "NYET Received--\n", hc->hc_num); + + /* + * NYET on CSPLIT + * re-do the CSPLIT immediately on non-periodic + */ + if (hc->do_split && hc->complete_split) { + if (hc->ep_is_in && (hc->ep_type == DWC_OTG_EP_TYPE_ISOC) + && hcd->core_if->dma_enable) { + qtd->complete_split = 0; + qtd->isoc_split_offset = 0; + if (++qtd->isoc_frame_index == qtd->urb->packet_count) { + hcd->fops->complete(hcd, qtd->urb->priv, qtd->urb, 0); + release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_URB_COMPLETE); + } + else + release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NO_HALT_STATUS); + goto handle_nyet_done; + } + + if (hc->ep_type == DWC_OTG_EP_TYPE_INTR || + hc->ep_type == DWC_OTG_EP_TYPE_ISOC) { + int frnum = dwc_otg_hcd_get_frame_number(hcd); + + // With the FIQ running we only ever see the failed NYET + if (dwc_full_frame_num(frnum) != + dwc_full_frame_num(hc->qh->sched_frame) || + fiq_fsm_enable) { + /* + * No longer in the same full speed frame. + * Treat this as a transaction error. + */ +#if 0 + /** @todo Fix system performance so this can + * be treated as an error. Right now complete + * splits cannot be scheduled precisely enough + * due to other system activity, so this error + * occurs regularly in Slave mode. + */ + qtd->error_count++; +#endif + qtd->complete_split = 0; + halt_channel(hcd, hc, qtd, + DWC_OTG_HC_XFER_XACT_ERR); + /** @todo add support for isoc release */ + goto handle_nyet_done; + } + } + + halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NYET); + goto handle_nyet_done; + } + + hc->qh->ping_state = 1; + qtd->error_count = 0; + + update_urb_state_xfer_intr(hc, hc_regs, qtd->urb, qtd, + DWC_OTG_HC_XFER_NYET); + dwc_otg_hcd_save_data_toggle(hc, hc_regs, qtd); + + /* + * Halt the channel and re-start the transfer so the PING + * protocol will start. + */ + halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NYET); + +handle_nyet_done: + disable_hc_int(hc_regs, nyet); + return 1; +} + +/** + * Handles a host channel babble interrupt. This handler may be called in + * either DMA mode or Slave mode. + */ +static int32_t handle_hc_babble_intr(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_qtd_t * qtd) +{ + DWC_DEBUGPL(DBG_HCDI, "--Host Channel %d Interrupt: " + "Babble Error--\n", hc->hc_num); + + if (hcd->core_if->dma_desc_enable) { + dwc_otg_hcd_complete_xfer_ddma(hcd, hc, hc_regs, + DWC_OTG_HC_XFER_BABBLE_ERR); + goto handle_babble_done; + } + + if (hc->ep_type != DWC_OTG_EP_TYPE_ISOC) { + hcd->fops->complete(hcd, qtd->urb->priv, + qtd->urb, -DWC_E_OVERFLOW); + halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_BABBLE_ERR); + } else { + dwc_otg_halt_status_e halt_status; + halt_status = update_isoc_urb_state(hcd, hc, hc_regs, qtd, + DWC_OTG_HC_XFER_BABBLE_ERR); + halt_channel(hcd, hc, qtd, halt_status); + } + +handle_babble_done: + disable_hc_int(hc_regs, bblerr); + return 1; +} + +/** + * Handles a host channel AHB error interrupt. This handler is only called in + * DMA mode. + */ +static int32_t handle_hc_ahberr_intr(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_qtd_t * qtd) +{ + hcchar_data_t hcchar; + hcsplt_data_t hcsplt; + hctsiz_data_t hctsiz; + uint32_t hcdma; + char *pipetype, *speed; + + dwc_otg_hcd_urb_t *urb = qtd->urb; + + DWC_DEBUGPL(DBG_HCDI, "--Host Channel %d Interrupt: " + "AHB Error--\n", hc->hc_num); + + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + hcsplt.d32 = DWC_READ_REG32(&hc_regs->hcsplt); + hctsiz.d32 = DWC_READ_REG32(&hc_regs->hctsiz); + hcdma = DWC_READ_REG32(&hc_regs->hcdma); + + DWC_ERROR("AHB ERROR, Channel %d\n", hc->hc_num); + DWC_ERROR(" hcchar 0x%08x, hcsplt 0x%08x\n", hcchar.d32, hcsplt.d32); + DWC_ERROR(" hctsiz 0x%08x, hcdma 0x%08x\n", hctsiz.d32, hcdma); + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD URB Enqueue\n"); + DWC_ERROR(" Device address: %d\n", + dwc_otg_hcd_get_dev_addr(&urb->pipe_info)); + DWC_ERROR(" Endpoint: %d, %s\n", + dwc_otg_hcd_get_ep_num(&urb->pipe_info), + (dwc_otg_hcd_is_pipe_in(&urb->pipe_info) ? "IN" : "OUT")); + + switch (dwc_otg_hcd_get_pipe_type(&urb->pipe_info)) { + case UE_CONTROL: + pipetype = "CONTROL"; + break; + case UE_BULK: + pipetype = "BULK"; + break; + case UE_INTERRUPT: + pipetype = "INTERRUPT"; + break; + case UE_ISOCHRONOUS: + pipetype = "ISOCHRONOUS"; + break; + default: + pipetype = "UNKNOWN"; + break; + } + + DWC_ERROR(" Endpoint type: %s\n", pipetype); + + switch (hc->speed) { + case DWC_OTG_EP_SPEED_HIGH: + speed = "HIGH"; + break; + case DWC_OTG_EP_SPEED_FULL: + speed = "FULL"; + break; + case DWC_OTG_EP_SPEED_LOW: + speed = "LOW"; + break; + default: + speed = "UNKNOWN"; + break; + }; + + DWC_ERROR(" Speed: %s\n", speed); + + DWC_ERROR(" Max packet size: %d\n", + dwc_otg_hcd_get_mps(&urb->pipe_info)); + DWC_ERROR(" Data buffer length: %d\n", urb->length); + DWC_ERROR(" Transfer buffer: %p, Transfer DMA: %pad\n", + urb->buf, &urb->dma); + DWC_ERROR(" Setup buffer: %p, Setup DMA: %pad\n", + urb->setup_packet, &urb->setup_dma); + DWC_ERROR(" Interval: %d\n", urb->interval); + + /* Core haltes the channel for Descriptor DMA mode */ + if (hcd->core_if->dma_desc_enable) { + dwc_otg_hcd_complete_xfer_ddma(hcd, hc, hc_regs, + DWC_OTG_HC_XFER_AHB_ERR); + goto handle_ahberr_done; + } + + hcd->fops->complete(hcd, urb->priv, urb, -DWC_E_IO); + + /* + * Force a channel halt. Don't call halt_channel because that won't + * write to the HCCHARn register in DMA mode to force the halt. + */ + dwc_otg_hc_halt(hcd->core_if, hc, DWC_OTG_HC_XFER_AHB_ERR); +handle_ahberr_done: + disable_hc_int(hc_regs, ahberr); + return 1; +} + +/** + * Handles a host channel transaction error interrupt. This handler may be + * called in either DMA mode or Slave mode. + */ +static int32_t handle_hc_xacterr_intr(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_qtd_t * qtd) +{ + DWC_DEBUGPL(DBG_HCDI, "--Host Channel %d Interrupt: " + "Transaction Error--\n", hc->hc_num); + + if (hcd->core_if->dma_desc_enable) { + dwc_otg_hcd_complete_xfer_ddma(hcd, hc, hc_regs, + DWC_OTG_HC_XFER_XACT_ERR); + goto handle_xacterr_done; + } + + switch (dwc_otg_hcd_get_pipe_type(&qtd->urb->pipe_info)) { + case UE_CONTROL: + case UE_BULK: + qtd->error_count++; + if (!hc->qh->ping_state) { + + update_urb_state_xfer_intr(hc, hc_regs, + qtd->urb, qtd, + DWC_OTG_HC_XFER_XACT_ERR); + dwc_otg_hcd_save_data_toggle(hc, hc_regs, qtd); + if (!hc->ep_is_in && hc->speed == DWC_OTG_EP_SPEED_HIGH) { + hc->qh->ping_state = 1; + } + } + + /* + * Halt the channel so the transfer can be re-started from + * the appropriate point or the PING protocol will start. + */ + halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_XACT_ERR); + break; + case UE_INTERRUPT: + qtd->error_count++; + if (hc->do_split && hc->complete_split) { + qtd->complete_split = 0; + } + halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_XACT_ERR); + break; + case UE_ISOCHRONOUS: + { + dwc_otg_halt_status_e halt_status; + halt_status = + update_isoc_urb_state(hcd, hc, hc_regs, qtd, + DWC_OTG_HC_XFER_XACT_ERR); + + halt_channel(hcd, hc, qtd, halt_status); + } + break; + } +handle_xacterr_done: + disable_hc_int(hc_regs, xacterr); + + return 1; +} + +/** + * Handles a host channel frame overrun interrupt. This handler may be called + * in either DMA mode or Slave mode. + */ +static int32_t handle_hc_frmovrun_intr(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_qtd_t * qtd) +{ + DWC_DEBUGPL(DBG_HCDI, "--Host Channel %d Interrupt: " + "Frame Overrun--\n", hc->hc_num); + + switch (dwc_otg_hcd_get_pipe_type(&qtd->urb->pipe_info)) { + case UE_CONTROL: + case UE_BULK: + break; + case UE_INTERRUPT: + halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_FRAME_OVERRUN); + break; + case UE_ISOCHRONOUS: + { + dwc_otg_halt_status_e halt_status; + halt_status = + update_isoc_urb_state(hcd, hc, hc_regs, qtd, + DWC_OTG_HC_XFER_FRAME_OVERRUN); + + halt_channel(hcd, hc, qtd, halt_status); + } + break; + } + + disable_hc_int(hc_regs, frmovrun); + + return 1; +} + +/** + * Handles a host channel data toggle error interrupt. This handler may be + * called in either DMA mode or Slave mode. + */ +static int32_t handle_hc_datatglerr_intr(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_qtd_t * qtd) +{ + DWC_DEBUGPL(DBG_HCDI, "--Host Channel %d Interrupt: " + "Data Toggle Error on %s transfer--\n", + hc->hc_num, (hc->ep_is_in ? "IN" : "OUT")); + + /* Data toggles on split transactions cause the hc to halt. + * restart transfer */ + if(hc->qh->do_split) + { + qtd->error_count++; + dwc_otg_hcd_save_data_toggle(hc, hc_regs, qtd); + update_urb_state_xfer_intr(hc, hc_regs, + qtd->urb, qtd, DWC_OTG_HC_XFER_XACT_ERR); + halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_XACT_ERR); + } else if (hc->ep_is_in) { + /* An unmasked data toggle error on a non-split DMA transaction is + * for the sole purpose of resetting error counts. Disable other + * interrupts unmasked for the same reason. + */ + if(hcd->core_if->dma_enable) { + disable_hc_int(hc_regs, ack); + disable_hc_int(hc_regs, nak); + } + qtd->error_count = 0; + } + + disable_hc_int(hc_regs, datatglerr); + + return 1; +} + +#ifdef DEBUG +/** + * This function is for debug only. It checks that a valid halt status is set + * and that HCCHARn.chdis is clear. If there's a problem, corrective action is + * taken and a warning is issued. + * @return 1 if halt status is ok, 0 otherwise. + */ +static inline int halt_status_ok(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_qtd_t * qtd) +{ + hcchar_data_t hcchar; + hctsiz_data_t hctsiz; + hcint_data_t hcint; + hcintmsk_data_t hcintmsk; + hcsplt_data_t hcsplt; + + if (hc->halt_status == DWC_OTG_HC_XFER_NO_HALT_STATUS) { + /* + * This code is here only as a check. This condition should + * never happen. Ignore the halt if it does occur. + */ + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + hctsiz.d32 = DWC_READ_REG32(&hc_regs->hctsiz); + hcint.d32 = DWC_READ_REG32(&hc_regs->hcint); + hcintmsk.d32 = DWC_READ_REG32(&hc_regs->hcintmsk); + hcsplt.d32 = DWC_READ_REG32(&hc_regs->hcsplt); + DWC_WARN + ("%s: hc->halt_status == DWC_OTG_HC_XFER_NO_HALT_STATUS, " + "channel %d, hcchar 0x%08x, hctsiz 0x%08x, " + "hcint 0x%08x, hcintmsk 0x%08x, " + "hcsplt 0x%08x, qtd->complete_split %d\n", __func__, + hc->hc_num, hcchar.d32, hctsiz.d32, hcint.d32, + hcintmsk.d32, hcsplt.d32, qtd->complete_split); + + DWC_WARN("%s: no halt status, channel %d, ignoring interrupt\n", + __func__, hc->hc_num); + DWC_WARN("\n"); + clear_hc_int(hc_regs, chhltd); + return 0; + } + + /* + * This code is here only as a check. hcchar.chdis should + * never be set when the halt interrupt occurs. Halt the + * channel again if it does occur. + */ + hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar); + if (hcchar.b.chdis) { + DWC_WARN("%s: hcchar.chdis set unexpectedly, " + "hcchar 0x%08x, trying to halt again\n", + __func__, hcchar.d32); + clear_hc_int(hc_regs, chhltd); + hc->halt_pending = 0; + halt_channel(hcd, hc, qtd, hc->halt_status); + return 0; + } + + return 1; +} +#endif + +/** + * Handles a host Channel Halted interrupt in DMA mode. This handler + * determines the reason the channel halted and proceeds accordingly. + */ +static void handle_hc_chhltd_intr_dma(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_qtd_t * qtd) +{ + int out_nak_enh = 0; + hcint_data_t hcint; + hcintmsk_data_t hcintmsk; + /* For core with OUT NAK enhancement, the flow for high- + * speed CONTROL/BULK OUT is handled a little differently. + */ + if (hcd->core_if->snpsid >= OTG_CORE_REV_2_71a) { + if (hc->speed == DWC_OTG_EP_SPEED_HIGH && !hc->ep_is_in && + (hc->ep_type == DWC_OTG_EP_TYPE_CONTROL || + hc->ep_type == DWC_OTG_EP_TYPE_BULK)) { + out_nak_enh = 1; + } + } + + if (hc->halt_status == DWC_OTG_HC_XFER_URB_DEQUEUE || + (hc->halt_status == DWC_OTG_HC_XFER_AHB_ERR + && !hcd->core_if->dma_desc_enable)) { + /* + * Just release the channel. A dequeue can happen on a + * transfer timeout. In the case of an AHB Error, the channel + * was forced to halt because there's no way to gracefully + * recover. + */ + if (hcd->core_if->dma_desc_enable) + dwc_otg_hcd_complete_xfer_ddma(hcd, hc, hc_regs, + hc->halt_status); + else + release_channel(hcd, hc, qtd, hc->halt_status); + return; + } + + /* Read the HCINTn register to determine the cause for the halt. */ + + hcint.d32 = DWC_READ_REG32(&hc_regs->hcint); + hcintmsk.d32 = DWC_READ_REG32(&hc_regs->hcintmsk); + + if (hcint.b.xfercomp) { + /** @todo This is here because of a possible hardware bug. Spec + * says that on SPLIT-ISOC OUT transfers in DMA mode that a HALT + * interrupt w/ACK bit set should occur, but I only see the + * XFERCOMP bit, even with it masked out. This is a workaround + * for that behavior. Should fix this when hardware is fixed. + */ + if (hc->ep_type == DWC_OTG_EP_TYPE_ISOC && !hc->ep_is_in) { + handle_hc_ack_intr(hcd, hc, hc_regs, qtd); + } + handle_hc_xfercomp_intr(hcd, hc, hc_regs, qtd); + } else if (hcint.b.stall) { + handle_hc_stall_intr(hcd, hc, hc_regs, qtd); + } else if (hcint.b.xacterr && !hcd->core_if->dma_desc_enable) { + if (out_nak_enh) { + if (hcint.b.nyet || hcint.b.nak || hcint.b.ack) { + DWC_DEBUGPL(DBG_HCD, "XactErr with NYET/NAK/ACK\n"); + qtd->error_count = 0; + } else { + DWC_DEBUGPL(DBG_HCD, "XactErr without NYET/NAK/ACK\n"); + } + } + + /* + * Must handle xacterr before nak or ack. Could get a xacterr + * at the same time as either of these on a BULK/CONTROL OUT + * that started with a PING. The xacterr takes precedence. + */ + handle_hc_xacterr_intr(hcd, hc, hc_regs, qtd); + } else if (hcint.b.xcs_xact && hcd->core_if->dma_desc_enable) { + handle_hc_xacterr_intr(hcd, hc, hc_regs, qtd); + } else if (hcint.b.ahberr && hcd->core_if->dma_desc_enable) { + handle_hc_ahberr_intr(hcd, hc, hc_regs, qtd); + } else if (hcint.b.bblerr) { + handle_hc_babble_intr(hcd, hc, hc_regs, qtd); + } else if (hcint.b.frmovrun) { + handle_hc_frmovrun_intr(hcd, hc, hc_regs, qtd); + } else if (hcint.b.datatglerr) { + handle_hc_datatglerr_intr(hcd, hc, hc_regs, qtd); + } else if (!out_nak_enh) { + if (hcint.b.nyet) { + /* + * Must handle nyet before nak or ack. Could get a nyet at the + * same time as either of those on a BULK/CONTROL OUT that + * started with a PING. The nyet takes precedence. + */ + handle_hc_nyet_intr(hcd, hc, hc_regs, qtd); + } else if (hcint.b.nak && !hcintmsk.b.nak) { + /* + * If nak is not masked, it's because a non-split IN transfer + * is in an error state. In that case, the nak is handled by + * the nak interrupt handler, not here. Handle nak here for + * BULK/CONTROL OUT transfers, which halt on a NAK to allow + * rewinding the buffer pointer. + */ + handle_hc_nak_intr(hcd, hc, hc_regs, qtd); + } else if (hcint.b.ack && !hcintmsk.b.ack) { + /* + * If ack is not masked, it's because a non-split IN transfer + * is in an error state. In that case, the ack is handled by + * the ack interrupt handler, not here. Handle ack here for + * split transfers. Start splits halt on ACK. + */ + handle_hc_ack_intr(hcd, hc, hc_regs, qtd); + } else { + if (hc->ep_type == DWC_OTG_EP_TYPE_INTR || + hc->ep_type == DWC_OTG_EP_TYPE_ISOC) { + /* + * A periodic transfer halted with no other channel + * interrupts set. Assume it was halted by the core + * because it could not be completed in its scheduled + * (micro)frame. + */ +#ifdef DEBUG + DWC_PRINTF + ("%s: Halt channel %d (assume incomplete periodic transfer)\n", + __func__, hc->hc_num); +#endif + halt_channel(hcd, hc, qtd, + DWC_OTG_HC_XFER_PERIODIC_INCOMPLETE); + } else { + DWC_ERROR + ("%s: Channel %d, DMA Mode -- ChHltd set, but reason " + "for halting is unknown, hcint 0x%08x, intsts 0x%08x\n", + __func__, hc->hc_num, hcint.d32, + DWC_READ_REG32(&hcd-> + core_if->core_global_regs-> + gintsts)); + /* Failthrough: use 3-strikes rule */ + qtd->error_count++; + dwc_otg_hcd_save_data_toggle(hc, hc_regs, qtd); + update_urb_state_xfer_intr(hc, hc_regs, + qtd->urb, qtd, DWC_OTG_HC_XFER_XACT_ERR); + halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_XACT_ERR); + } + + } + } else { + DWC_PRINTF("NYET/NAK/ACK/other in non-error case, 0x%08x\n", + hcint.d32); + /* Failthrough: use 3-strikes rule */ + qtd->error_count++; + dwc_otg_hcd_save_data_toggle(hc, hc_regs, qtd); + update_urb_state_xfer_intr(hc, hc_regs, + qtd->urb, qtd, DWC_OTG_HC_XFER_XACT_ERR); + halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_XACT_ERR); + } +} + +/** + * Handles a host channel Channel Halted interrupt. + * + * In slave mode, this handler is called only when the driver specifically + * requests a halt. This occurs during handling other host channel interrupts + * (e.g. nak, xacterr, stall, nyet, etc.). + * + * In DMA mode, this is the interrupt that occurs when the core has finished + * processing a transfer on a channel. Other host channel interrupts (except + * ahberr) are disabled in DMA mode. + */ +static int32_t handle_hc_chhltd_intr(dwc_otg_hcd_t * hcd, + dwc_hc_t * hc, + dwc_otg_hc_regs_t * hc_regs, + dwc_otg_qtd_t * qtd) +{ + DWC_DEBUGPL(DBG_HCDI, "--Host Channel %d Interrupt: " + "Channel Halted--\n", hc->hc_num); + + if (hcd->core_if->dma_enable) { + handle_hc_chhltd_intr_dma(hcd, hc, hc_regs, qtd); + } else { +#ifdef DEBUG + if (!halt_status_ok(hcd, hc, hc_regs, qtd)) { + return 1; + } +#endif + release_channel(hcd, hc, qtd, hc->halt_status); + } + + return 1; +} + + +/** + * dwc_otg_fiq_unmangle_isoc() - Update the iso_frame_desc structure on + * FIQ transfer completion + * @hcd: Pointer to dwc_otg_hcd struct + * @num: Host channel number + * + * 1. Un-mangle the status as recorded in each iso_frame_desc status + * 2. Copy it from the dwc_otg_urb into the real URB + */ +static void dwc_otg_fiq_unmangle_isoc(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh, dwc_otg_qtd_t *qtd, uint32_t num) +{ + struct dwc_otg_hcd_urb *dwc_urb = qtd->urb; + int nr_frames = dwc_urb->packet_count; + int i; + hcint_data_t frame_hcint; + + for (i = 0; i < nr_frames; i++) { + frame_hcint.d32 = dwc_urb->iso_descs[i].status; + if (frame_hcint.b.xfercomp) { + dwc_urb->iso_descs[i].status = 0; + dwc_urb->actual_length += dwc_urb->iso_descs[i].actual_length; + } else if (frame_hcint.b.frmovrun) { + if (qh->ep_is_in) + dwc_urb->iso_descs[i].status = -DWC_E_NO_STREAM_RES; + else + dwc_urb->iso_descs[i].status = -DWC_E_COMMUNICATION; + dwc_urb->error_count++; + dwc_urb->iso_descs[i].actual_length = 0; + } else if (frame_hcint.b.xacterr) { + dwc_urb->iso_descs[i].status = -DWC_E_PROTOCOL; + dwc_urb->error_count++; + dwc_urb->iso_descs[i].actual_length = 0; + } else if (frame_hcint.b.bblerr) { + dwc_urb->iso_descs[i].status = -DWC_E_OVERFLOW; + dwc_urb->error_count++; + dwc_urb->iso_descs[i].actual_length = 0; + } else { + /* Something went wrong */ + dwc_urb->iso_descs[i].status = -1; + dwc_urb->iso_descs[i].actual_length = 0; + dwc_urb->error_count++; + } + } + qh->sched_frame = dwc_frame_num_inc(qh->sched_frame, qh->interval * (nr_frames - 1)); + + //printk_ratelimited(KERN_INFO "%s: HS isochronous of %d/%d frames with %d errors complete\n", + // __FUNCTION__, i, dwc_urb->packet_count, dwc_urb->error_count); +} + +/** + * dwc_otg_fiq_unsetup_per_dma() - Remove data from bounce buffers for split transactions + * @hcd: Pointer to dwc_otg_hcd struct + * @num: Host channel number + * + * Copies data from the FIQ bounce buffers into the URB's transfer buffer. Does not modify URB state. + * Returns total length of data or -1 if the buffers were not used. + * + */ +static int dwc_otg_fiq_unsetup_per_dma(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh, dwc_otg_qtd_t *qtd, uint32_t num) +{ + dwc_hc_t *hc = qh->channel; + struct fiq_dma_channel *split_dma = hcd->fiq_dmab; + struct fiq_channel_state *st = &hcd->fiq_state->channel[num]; + uint8_t *ptr = NULL; + int index = 0, len = 0; + int i = 0; + if (hc->ep_is_in) { + /* Copy data out of the DMA bounce buffers to the URB's buffer. + * The align_buf is ignored as this is ignored on FSM enqueue. */ + ptr = qtd->urb->buf; + if (qh->ep_type == UE_ISOCHRONOUS) { + /* Isoc IN transactions - grab the offset of the iso_frame_desc into the URB transfer buffer */ + index = qtd->isoc_frame_index; + ptr += qtd->urb->iso_descs[index].offset; + } else { + /* Need to increment by actual_length for interrupt IN */ + ptr += qtd->urb->actual_length; + } + + for (i = 0; i < st->dma_info.index; i++) { + len += st->dma_info.slot_len[i]; + dwc_memcpy(ptr, &split_dma[num].index[i].buf[0], st->dma_info.slot_len[i]); + ptr += st->dma_info.slot_len[i]; + } + return len; + } else { + /* OUT endpoints - nothing to do. */ + return -1; + } + +} +/** + * dwc_otg_hcd_handle_hc_fsm() - handle an unmasked channel interrupt + * from a channel handled in the FIQ + * @hcd: Pointer to dwc_otg_hcd struct + * @num: Host channel number + * + * If a host channel interrupt was received by the IRQ and this was a channel + * used by the FIQ, the execution flow for transfer completion is substantially + * different from the normal (messy) path. This function and its friends handles + * channel cleanup and transaction completion from a FIQ transaction. + */ +static void dwc_otg_hcd_handle_hc_fsm(dwc_otg_hcd_t *hcd, uint32_t num) +{ + struct fiq_channel_state *st = &hcd->fiq_state->channel[num]; + dwc_hc_t *hc = hcd->hc_ptr_array[num]; + dwc_otg_qtd_t *qtd; + dwc_otg_hc_regs_t *hc_regs = hcd->core_if->host_if->hc_regs[num]; + hcint_data_t hcint = hcd->fiq_state->channel[num].hcint_copy; + hctsiz_data_t hctsiz = hcd->fiq_state->channel[num].hctsiz_copy; + int hostchannels = 0; + fiq_print(FIQDBG_INT, hcd->fiq_state, "OUT %01d %01d ", num , st->fsm); + + hostchannels = hcd->available_host_channels; + if (hc->halt_pending) { + /* Dequeue: The FIQ was allowed to complete the transfer but state has been cleared. */ + if (hc->qh && st->fsm == FIQ_NP_SPLIT_DONE && + hcint.b.xfercomp && hc->qh->ep_type == UE_BULK) { + if (hctsiz.b.pid == DWC_HCTSIZ_DATA0) { + hc->qh->data_toggle = DWC_OTG_HC_PID_DATA1; + } else { + hc->qh->data_toggle = DWC_OTG_HC_PID_DATA0; + } + } + release_channel(hcd, hc, NULL, hc->halt_status); + return; + } + + qtd = DWC_CIRCLEQ_FIRST(&hc->qh->qtd_list); + switch (st->fsm) { + case FIQ_TEST: + break; + + case FIQ_DEQUEUE_ISSUED: + /* Handled above, but keep for posterity */ + release_channel(hcd, hc, NULL, hc->halt_status); + break; + + case FIQ_NP_SPLIT_DONE: + /* Nonperiodic transaction complete. */ + if (!hc->ep_is_in) { + qtd->ssplit_out_xfer_count = hc->xfer_len; + } + if (hcint.b.xfercomp) { + handle_hc_xfercomp_intr(hcd, hc, hc_regs, qtd); + } else if (hcint.b.nak) { + handle_hc_nak_intr(hcd, hc, hc_regs, qtd); + } else { + DWC_WARN("Unexpected IRQ state on FSM transaction:" + "dev_addr=%d ep=%d fsm=%d, hcint=0x%08x\n", + hc->dev_addr, hc->ep_num, st->fsm, hcint.d32); + release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NO_HALT_STATUS); + } + break; + + case FIQ_NP_SPLIT_HS_ABORTED: + /* A HS abort is a 3-strikes on the HS bus at any point in the transaction. + * Normally a CLEAR_TT_BUFFER hub command would be required: we can't do that + * because there's no guarantee which order a non-periodic split happened in. + * We could end up clearing a perfectly good transaction out of the buffer. + */ + if (hcint.b.xacterr) { + qtd->error_count += st->nr_errors; + handle_hc_xacterr_intr(hcd, hc, hc_regs, qtd); + } else if (hcint.b.ahberr) { + handle_hc_ahberr_intr(hcd, hc, hc_regs, qtd); + } else { + DWC_WARN("Unexpected IRQ state on FSM transaction:" + "dev_addr=%d ep=%d fsm=%d, hcint=0x%08x\n", + hc->dev_addr, hc->ep_num, st->fsm, hcint.d32); + release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NO_HALT_STATUS); + } + break; + + case FIQ_NP_SPLIT_LS_ABORTED: + /* A few cases can cause this - either an unknown state on a SSPLIT or + * STALL/data toggle error response on a CSPLIT */ + if (hcint.b.stall) { + handle_hc_stall_intr(hcd, hc, hc_regs, qtd); + } else if (hcint.b.datatglerr) { + handle_hc_datatglerr_intr(hcd, hc, hc_regs, qtd); + } else if (hcint.b.bblerr) { + handle_hc_babble_intr(hcd, hc, hc_regs, qtd); + } else if (hcint.b.ahberr) { + handle_hc_ahberr_intr(hcd, hc, hc_regs, qtd); + } else { + DWC_WARN("Unexpected IRQ state on FSM transaction:" + "dev_addr=%d ep=%d fsm=%d, hcint=0x%08x\n", + hc->dev_addr, hc->ep_num, st->fsm, hcint.d32); + release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NO_HALT_STATUS); + } + break; + + case FIQ_PER_SPLIT_DONE: + /* Isoc IN or Interrupt IN/OUT */ + + /* Flow control here is different from the normal execution by the driver. + * We need to completely ignore most of the driver's method of handling + * split transactions and do it ourselves. + */ + if (hc->ep_type == UE_INTERRUPT) { + if (hcint.b.nak) { + handle_hc_nak_intr(hcd, hc, hc_regs, qtd); + } else if (hc->ep_is_in) { + int len; + len = dwc_otg_fiq_unsetup_per_dma(hcd, hc->qh, qtd, num); + //printk(KERN_NOTICE "FIQ Transaction: hc=%d len=%d urb_len = %d\n", num, len, qtd->urb->length); + qtd->urb->actual_length += len; + if (qtd->urb->actual_length >= qtd->urb->length) { + qtd->urb->status = 0; + hcd->fops->complete(hcd, qtd->urb->priv, qtd->urb, qtd->urb->status); + release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_URB_COMPLETE); + } else { + /* Interrupt transfer not complete yet - is it a short read? */ + if (len < hc->max_packet) { + /* Interrupt transaction complete */ + qtd->urb->status = 0; + hcd->fops->complete(hcd, qtd->urb->priv, qtd->urb, qtd->urb->status); + release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_URB_COMPLETE); + } else { + /* Further transactions required */ + release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_COMPLETE); + } + } + } else { + /* Interrupt OUT complete. */ + dwc_otg_hcd_save_data_toggle(hc, hc_regs, qtd); + qtd->urb->actual_length += hc->xfer_len; + if (qtd->urb->actual_length >= qtd->urb->length) { + qtd->urb->status = 0; + hcd->fops->complete(hcd, qtd->urb->priv, qtd->urb, qtd->urb->status); + release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_URB_COMPLETE); + } else { + release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_COMPLETE); + } + } + } else { + /* ISOC IN complete. */ + struct dwc_otg_hcd_iso_packet_desc *frame_desc = &qtd->urb->iso_descs[qtd->isoc_frame_index]; + int len = 0; + /* Record errors, update qtd. */ + if (st->nr_errors) { + frame_desc->actual_length = 0; + frame_desc->status = -DWC_E_PROTOCOL; + } else { + frame_desc->status = 0; + /* Unswizzle dma */ + len = dwc_otg_fiq_unsetup_per_dma(hcd, hc->qh, qtd, num); + frame_desc->actual_length = len; + } + qtd->isoc_frame_index++; + if (qtd->isoc_frame_index == qtd->urb->packet_count) { + hcd->fops->complete(hcd, qtd->urb->priv, qtd->urb, 0); + release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_URB_COMPLETE); + } else { + release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_COMPLETE); + } + } + break; + + case FIQ_PER_ISO_OUT_DONE: { + struct dwc_otg_hcd_iso_packet_desc *frame_desc = &qtd->urb->iso_descs[qtd->isoc_frame_index]; + /* Record errors, update qtd. */ + if (st->nr_errors) { + frame_desc->actual_length = 0; + frame_desc->status = -DWC_E_PROTOCOL; + } else { + frame_desc->status = 0; + frame_desc->actual_length = frame_desc->length; + } + qtd->isoc_frame_index++; + qtd->isoc_split_offset = 0; + if (qtd->isoc_frame_index == qtd->urb->packet_count) { + hcd->fops->complete(hcd, qtd->urb->priv, qtd->urb, 0); + release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_URB_COMPLETE); + } else { + release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_COMPLETE); + } + } + break; + + case FIQ_PER_SPLIT_NYET_ABORTED: + /* Doh. lost the data. */ + printk_ratelimited(KERN_INFO "Transfer to device %d endpoint 0x%x frame %d failed " + "- FIQ reported NYET. Data may have been lost.\n", + hc->dev_addr, hc->ep_num, dwc_otg_hcd_get_frame_number(hcd) >> 3); + if (hc->ep_type == UE_ISOCHRONOUS) { + struct dwc_otg_hcd_iso_packet_desc *frame_desc = &qtd->urb->iso_descs[qtd->isoc_frame_index]; + /* Record errors, update qtd. */ + frame_desc->actual_length = 0; + frame_desc->status = -DWC_E_PROTOCOL; + qtd->isoc_frame_index++; + qtd->isoc_split_offset = 0; + if (qtd->isoc_frame_index == qtd->urb->packet_count) { + hcd->fops->complete(hcd, qtd->urb->priv, qtd->urb, 0); + release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_URB_COMPLETE); + } else { + release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_COMPLETE); + } + } else { + release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NO_HALT_STATUS); + } + break; + + case FIQ_HS_ISOC_DONE: + /* The FIQ has performed a whole pile of isochronous transactions. + * The status is recorded as the interrupt state should the transaction + * fail. + */ + dwc_otg_fiq_unmangle_isoc(hcd, hc->qh, qtd, num); + hcd->fops->complete(hcd, qtd->urb->priv, qtd->urb, 0); + release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_URB_COMPLETE); + break; + + case FIQ_PER_SPLIT_LS_ABORTED: + if (hcint.b.xacterr) { + /* Hub has responded with an ERR packet. Device + * has been unplugged or the port has been disabled. + * TODO: need to issue a reset to the hub port. */ + qtd->error_count += 3; + handle_hc_xacterr_intr(hcd, hc, hc_regs, qtd); + } else if (hcint.b.stall) { + handle_hc_stall_intr(hcd, hc, hc_regs, qtd); + } else if (hcint.b.bblerr) { + handle_hc_babble_intr(hcd, hc, hc_regs, qtd); + } else { + printk_ratelimited(KERN_INFO "Transfer to device %d endpoint 0x%x failed " + "- FIQ reported FSM=%d. Data may have been lost.\n", + st->fsm, hc->dev_addr, hc->ep_num); + release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NO_HALT_STATUS); + } + break; + + case FIQ_PER_SPLIT_HS_ABORTED: + /* Either the SSPLIT phase suffered transaction errors or something + * unexpected happened. + */ + qtd->error_count += 3; + handle_hc_xacterr_intr(hcd, hc, hc_regs, qtd); + release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NO_HALT_STATUS); + break; + + case FIQ_PER_SPLIT_TIMEOUT: + /* Couldn't complete in the nominated frame */ + printk(KERN_INFO "Transfer to device %d endpoint 0x%x frame %d failed " + "- FIQ timed out. Data may have been lost.\n", + hc->dev_addr, hc->ep_num, dwc_otg_hcd_get_frame_number(hcd) >> 3); + if (hc->ep_type == UE_ISOCHRONOUS) { + struct dwc_otg_hcd_iso_packet_desc *frame_desc = &qtd->urb->iso_descs[qtd->isoc_frame_index]; + /* Record errors, update qtd. */ + frame_desc->actual_length = 0; + if (hc->ep_is_in) { + frame_desc->status = -DWC_E_NO_STREAM_RES; + } else { + frame_desc->status = -DWC_E_COMMUNICATION; + } + qtd->isoc_frame_index++; + if (qtd->isoc_frame_index == qtd->urb->packet_count) { + hcd->fops->complete(hcd, qtd->urb->priv, qtd->urb, 0); + release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_URB_COMPLETE); + } else { + release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_COMPLETE); + } + } else { + release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NO_HALT_STATUS); + } + break; + + default: + DWC_WARN("Unexpected state received on hc=%d fsm=%d on transfer to device %d ep 0x%x", + hc->hc_num, st->fsm, hc->dev_addr, hc->ep_num); + qtd->error_count++; + release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NO_HALT_STATUS); + } + return; +} + +/** Handles interrupt for a specific Host Channel */ +int32_t dwc_otg_hcd_handle_hc_n_intr(dwc_otg_hcd_t * dwc_otg_hcd, uint32_t num) +{ + int retval = 0; + hcint_data_t hcint; + hcintmsk_data_t hcintmsk; + dwc_hc_t *hc; + dwc_otg_hc_regs_t *hc_regs; + dwc_otg_qtd_t *qtd; + + DWC_DEBUGPL(DBG_HCDV, "--Host Channel Interrupt--, Channel %d\n", num); + + hc = dwc_otg_hcd->hc_ptr_array[num]; + hc_regs = dwc_otg_hcd->core_if->host_if->hc_regs[num]; + if(hc->halt_status == DWC_OTG_HC_XFER_URB_DEQUEUE) { + /* A dequeue was issued for this transfer. Our QTD has gone away + * but in the case of a FIQ transfer, the transfer would have run + * to completion. + */ + if (fiq_fsm_enable && dwc_otg_hcd->fiq_state->channel[num].fsm != FIQ_PASSTHROUGH) { + dwc_otg_hcd_handle_hc_fsm(dwc_otg_hcd, num); + } else { + release_channel(dwc_otg_hcd, hc, NULL, hc->halt_status); + } + return 1; + } + qtd = DWC_CIRCLEQ_FIRST(&hc->qh->qtd_list); + + /* + * FSM mode: Check to see if this is a HC interrupt from a channel handled by the FIQ. + * Execution path is fundamentally different for the channels after a FIQ has completed + * a split transaction. + */ + if (fiq_fsm_enable) { + switch (dwc_otg_hcd->fiq_state->channel[num].fsm) { + case FIQ_PASSTHROUGH: + break; + case FIQ_PASSTHROUGH_ERRORSTATE: + /* Hook into the error count */ + fiq_print(FIQDBG_ERR, dwc_otg_hcd->fiq_state, "HCDERR%02d", num); + if (!dwc_otg_hcd->fiq_state->channel[num].nr_errors) { + qtd->error_count = 0; + fiq_print(FIQDBG_ERR, dwc_otg_hcd->fiq_state, "RESET "); + } + break; + default: + dwc_otg_hcd_handle_hc_fsm(dwc_otg_hcd, num); + return 1; + } + } + + hcint.d32 = DWC_READ_REG32(&hc_regs->hcint); + hcintmsk.d32 = DWC_READ_REG32(&hc_regs->hcintmsk); + hcint.d32 = hcint.d32 & hcintmsk.d32; + if (!dwc_otg_hcd->core_if->dma_enable) { + if (hcint.b.chhltd && hcint.d32 != 0x2) { + hcint.b.chhltd = 0; + } + } + + if (hcint.b.xfercomp) { + retval |= + handle_hc_xfercomp_intr(dwc_otg_hcd, hc, hc_regs, qtd); + /* + * If NYET occurred at same time as Xfer Complete, the NYET is + * handled by the Xfer Complete interrupt handler. Don't want + * to call the NYET interrupt handler in this case. + */ + hcint.b.nyet = 0; + } + if (hcint.b.chhltd) { + retval |= handle_hc_chhltd_intr(dwc_otg_hcd, hc, hc_regs, qtd); + } + if (hcint.b.ahberr) { + retval |= handle_hc_ahberr_intr(dwc_otg_hcd, hc, hc_regs, qtd); + } + if (hcint.b.stall) { + retval |= handle_hc_stall_intr(dwc_otg_hcd, hc, hc_regs, qtd); + } + if (hcint.b.nak) { + retval |= handle_hc_nak_intr(dwc_otg_hcd, hc, hc_regs, qtd); + } + if (hcint.b.ack) { + if(!hcint.b.chhltd) + retval |= handle_hc_ack_intr(dwc_otg_hcd, hc, hc_regs, qtd); + } + if (hcint.b.nyet) { + retval |= handle_hc_nyet_intr(dwc_otg_hcd, hc, hc_regs, qtd); + } + if (hcint.b.xacterr) { + retval |= handle_hc_xacterr_intr(dwc_otg_hcd, hc, hc_regs, qtd); + } + if (hcint.b.bblerr) { + retval |= handle_hc_babble_intr(dwc_otg_hcd, hc, hc_regs, qtd); + } + if (hcint.b.frmovrun) { + retval |= + handle_hc_frmovrun_intr(dwc_otg_hcd, hc, hc_regs, qtd); + } + if (hcint.b.datatglerr) { + retval |= + handle_hc_datatglerr_intr(dwc_otg_hcd, hc, hc_regs, qtd); + } + + return retval; +} +#endif /* DWC_DEVICE_ONLY */ diff --git a/drivers/usb/host/dwc_otg/dwc_otg_hcd_linux.c b/drivers/usb/host/dwc_otg/dwc_otg_hcd_linux.c new file mode 100644 index 00000000000000..298ca842003fd6 --- /dev/null +++ b/drivers/usb/host/dwc_otg/dwc_otg_hcd_linux.c @@ -0,0 +1,1084 @@ + +/* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_hcd_linux.c $ + * $Revision: #20 $ + * $Date: 2011/10/26 $ + * $Change: 1872981 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ +#ifndef DWC_DEVICE_ONLY + +/** + * @file + * + * This file contains the implementation of the HCD. In Linux, the HCD + * implements the hc_driver API. + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/list.h> +#include <linux/interrupt.h> +#include <linux/string.h> +#include <linux/dma-mapping.h> +#include <linux/version.h> +#include <asm/io.h> +#ifdef CONFIG_ARM +#include <asm/fiq.h> +#endif +#include <linux/usb.h> +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35) +#include <../drivers/usb/core/hcd.h> +#else +#include <linux/usb/hcd.h> +#endif +#include <asm/bug.h> + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30)) +#define USB_URB_EP_LINKING 1 +#else +#define USB_URB_EP_LINKING 0 +#endif + +#include "dwc_otg_hcd_if.h" +#include "dwc_otg_dbg.h" +#include "dwc_otg_driver.h" +#include "dwc_otg_hcd.h" + +#ifndef __virt_to_bus +#define __virt_to_bus __virt_to_phys +#define __bus_to_virt __phys_to_virt +#define __pfn_to_bus(x) __pfn_to_phys(x) +#define __bus_to_pfn(x) __phys_to_pfn(x) +#endif + +extern unsigned char _dwc_otg_fiq_stub, _dwc_otg_fiq_stub_end; + +/** + * Gets the endpoint number from a _bEndpointAddress argument. The endpoint is + * qualified with its direction (possible 32 endpoints per device). + */ +#define dwc_ep_addr_to_endpoint(_bEndpointAddress_) ((_bEndpointAddress_ & USB_ENDPOINT_NUMBER_MASK) | \ + ((_bEndpointAddress_ & USB_DIR_IN) != 0) << 4) + +static const char dwc_otg_hcd_name[] = "dwc_otg_hcd"; + +extern bool fiq_enable; + +/** @name Linux HC Driver API Functions */ +/** @{ */ +/* manage i/o requests, device state */ +static int dwc_otg_urb_enqueue(struct usb_hcd *hcd, +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28) + struct usb_host_endpoint *ep, +#endif + struct urb *urb, gfp_t mem_flags); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,30) +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28) +static int dwc_otg_urb_dequeue(struct usb_hcd *hcd, struct urb *urb); +#endif +#else /* kernels at or post 2.6.30 */ +static int dwc_otg_urb_dequeue(struct usb_hcd *hcd, + struct urb *urb, int status); +#endif /* LINUX_VERSION_CODE < KERNEL_VERSION(2,6,30) */ + +static void endpoint_disable(struct usb_hcd *hcd, struct usb_host_endpoint *ep); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30) +static void endpoint_reset(struct usb_hcd *hcd, struct usb_host_endpoint *ep); +#endif +static irqreturn_t dwc_otg_hcd_irq(struct usb_hcd *hcd); +extern int hcd_start(struct usb_hcd *hcd); +extern void hcd_stop(struct usb_hcd *hcd); +static int get_frame_number(struct usb_hcd *hcd); +extern int hub_status_data(struct usb_hcd *hcd, char *buf); +extern int hub_control(struct usb_hcd *hcd, + u16 typeReq, + u16 wValue, u16 wIndex, char *buf, u16 wLength); + +struct wrapper_priv_data { + dwc_otg_hcd_t *dwc_otg_hcd; +}; + +/** @} */ + +static struct hc_driver dwc_otg_hc_driver = { + + .description = dwc_otg_hcd_name, + .product_desc = "DWC OTG Controller", + .hcd_priv_size = sizeof(struct wrapper_priv_data), + + .irq = dwc_otg_hcd_irq, + + .flags = HCD_MEMORY | HCD_DMA | HCD_USB2, + + //.reset = + .start = hcd_start, + //.suspend = + //.resume = + .stop = hcd_stop, + + .urb_enqueue = dwc_otg_urb_enqueue, + .urb_dequeue = dwc_otg_urb_dequeue, + .endpoint_disable = endpoint_disable, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30) + .endpoint_reset = endpoint_reset, +#endif + .get_frame_number = get_frame_number, + + .hub_status_data = hub_status_data, + .hub_control = hub_control, + //.bus_suspend = + //.bus_resume = +}; + +/** Gets the dwc_otg_hcd from a struct usb_hcd */ +static inline dwc_otg_hcd_t *hcd_to_dwc_otg_hcd(struct usb_hcd *hcd) +{ + struct wrapper_priv_data *p; + p = (struct wrapper_priv_data *)(hcd->hcd_priv); + return p->dwc_otg_hcd; +} + +/** Gets the struct usb_hcd that contains a dwc_otg_hcd_t. */ +static inline struct usb_hcd *dwc_otg_hcd_to_hcd(dwc_otg_hcd_t * dwc_otg_hcd) +{ + return dwc_otg_hcd_get_priv_data(dwc_otg_hcd); +} + +/** Gets the usb_host_endpoint associated with an URB. */ +inline struct usb_host_endpoint *dwc_urb_to_endpoint(struct urb *urb) +{ + struct usb_device *dev = urb->dev; + int ep_num = usb_pipeendpoint(urb->pipe); + + if (usb_pipein(urb->pipe)) + return dev->ep_in[ep_num]; + else + return dev->ep_out[ep_num]; +} + +static int _disconnect(dwc_otg_hcd_t * hcd) +{ + struct usb_hcd *usb_hcd = dwc_otg_hcd_to_hcd(hcd); + + usb_hcd->self.is_b_host = 0; + return 0; +} + +static int _start(dwc_otg_hcd_t * hcd) +{ + struct usb_hcd *usb_hcd = dwc_otg_hcd_to_hcd(hcd); + + usb_hcd->self.is_b_host = dwc_otg_hcd_is_b_host(hcd); + hcd_start(usb_hcd); + + return 0; +} + +static int _hub_info(dwc_otg_hcd_t * hcd, void *urb_handle, uint32_t * hub_addr, + uint32_t * port_addr) +{ + struct urb *urb = (struct urb *)urb_handle; + struct usb_bus *bus; +#if 1 //GRAYG - temporary + if (NULL == urb_handle) + DWC_ERROR("**** %s - NULL URB handle\n", __func__);//GRAYG + if (NULL == urb->dev) + DWC_ERROR("**** %s - URB has no device\n", __func__);//GRAYG + if (NULL == port_addr) + DWC_ERROR("**** %s - NULL port_address\n", __func__);//GRAYG +#endif + if (urb->dev->tt) { + if (NULL == urb->dev->tt->hub) { + DWC_ERROR("**** %s - (URB's transactor has no TT - giving no hub)\n", + __func__); //GRAYG + //*hub_addr = (u8)usb_pipedevice(urb->pipe); //GRAYG + *hub_addr = 0; //GRAYG + // we probably shouldn't have a transaction translator if + // there's no associated hub? + } else { + bus = hcd_to_bus(dwc_otg_hcd_to_hcd(hcd)); + if (urb->dev->tt->hub == bus->root_hub) + *hub_addr = 0; + else + *hub_addr = urb->dev->tt->hub->devnum; + } + *port_addr = urb->dev->ttport; + } else { + *hub_addr = 0; + *port_addr = urb->dev->ttport; + } + return 0; +} + +static int _speed(dwc_otg_hcd_t * hcd, void *urb_handle) +{ + struct urb *urb = (struct urb *)urb_handle; + return urb->dev->speed; +} + +static int _get_b_hnp_enable(dwc_otg_hcd_t * hcd) +{ + struct usb_hcd *usb_hcd = dwc_otg_hcd_to_hcd(hcd); + return usb_hcd->self.b_hnp_enable; +} + +static void allocate_bus_bandwidth(struct usb_hcd *hcd, uint32_t bw, + struct urb *urb) +{ + hcd_to_bus(hcd)->bandwidth_allocated += bw / urb->interval; + if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) { + hcd_to_bus(hcd)->bandwidth_isoc_reqs++; + } else { + hcd_to_bus(hcd)->bandwidth_int_reqs++; + } +} + +static void free_bus_bandwidth(struct usb_hcd *hcd, uint32_t bw, + struct urb *urb) +{ + hcd_to_bus(hcd)->bandwidth_allocated -= bw / urb->interval; + if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) { + hcd_to_bus(hcd)->bandwidth_isoc_reqs--; + } else { + hcd_to_bus(hcd)->bandwidth_int_reqs--; + } +} + +/** + * Sets the final status of an URB and returns it to the device driver. Any + * required cleanup of the URB is performed. The HCD lock should be held on + * entry. + */ +static int _complete(dwc_otg_hcd_t * hcd, void *urb_handle, + dwc_otg_hcd_urb_t * dwc_otg_urb, int32_t status) +{ + struct urb *urb = (struct urb *)urb_handle; + urb_tq_entry_t *new_entry; + int rc = 0; + if (CHK_DEBUG_LEVEL(DBG_HCDV | DBG_HCD_URB)) { + DWC_PRINTF("%s: urb %p, device %d, ep %d %s, status=%d\n", + __func__, urb, usb_pipedevice(urb->pipe), + usb_pipeendpoint(urb->pipe), + usb_pipein(urb->pipe) ? "IN" : "OUT", status); + if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) { + int i; + for (i = 0; i < urb->number_of_packets; i++) { + DWC_PRINTF(" ISO Desc %d status: %d\n", + i, urb->iso_frame_desc[i].status); + } + } + } + new_entry = DWC_ALLOC_ATOMIC(sizeof(urb_tq_entry_t)); + urb->actual_length = dwc_otg_hcd_urb_get_actual_length(dwc_otg_urb); + /* Convert status value. */ + switch (status) { + case -DWC_E_PROTOCOL: + status = -EPROTO; + break; + case -DWC_E_IN_PROGRESS: + status = -EINPROGRESS; + break; + case -DWC_E_PIPE: + status = -EPIPE; + break; + case -DWC_E_IO: + status = -EIO; + break; + case -DWC_E_TIMEOUT: + status = -ETIMEDOUT; + break; + case -DWC_E_OVERFLOW: + status = -EOVERFLOW; + break; + case -DWC_E_SHUTDOWN: + status = -ESHUTDOWN; + break; + default: + if (status) { + DWC_PRINTF("Uknown urb status %d\n", status); + + } + } + + if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) { + int i; + + urb->error_count = dwc_otg_hcd_urb_get_error_count(dwc_otg_urb); + urb->actual_length = 0; + for (i = 0; i < urb->number_of_packets; ++i) { + urb->iso_frame_desc[i].actual_length = + dwc_otg_hcd_urb_get_iso_desc_actual_length + (dwc_otg_urb, i); + urb->actual_length += urb->iso_frame_desc[i].actual_length; + urb->iso_frame_desc[i].status = + dwc_otg_hcd_urb_get_iso_desc_status(dwc_otg_urb, i); + } + } + + urb->status = status; + urb->hcpriv = NULL; + if (!status) { + if ((urb->transfer_flags & URB_SHORT_NOT_OK) && + (urb->actual_length < urb->transfer_buffer_length)) { + urb->status = -EREMOTEIO; + } + } + + if ((usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) || + (usb_pipetype(urb->pipe) == PIPE_INTERRUPT)) { + struct usb_host_endpoint *ep = dwc_urb_to_endpoint(urb); + if (ep) { + free_bus_bandwidth(dwc_otg_hcd_to_hcd(hcd), + dwc_otg_hcd_get_ep_bandwidth(hcd, + ep->hcpriv), + urb); + } + } + DWC_FREE(dwc_otg_urb); + if (!new_entry) { + DWC_ERROR("dwc_otg_hcd: complete: cannot allocate URB TQ entry\n"); + urb->status = -EPROTO; + /* don't schedule the tasklet - + * directly return the packet here with error. */ +#if USB_URB_EP_LINKING + usb_hcd_unlink_urb_from_ep(dwc_otg_hcd_to_hcd(hcd), urb); +#endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28) + usb_hcd_giveback_urb(dwc_otg_hcd_to_hcd(hcd), urb); +#else + usb_hcd_giveback_urb(dwc_otg_hcd_to_hcd(hcd), urb, urb->status); +#endif + } else { + new_entry->urb = urb; +#if USB_URB_EP_LINKING + rc = usb_hcd_check_unlink_urb(dwc_otg_hcd_to_hcd(hcd), urb, urb->status); + if(0 == rc) { + usb_hcd_unlink_urb_from_ep(dwc_otg_hcd_to_hcd(hcd), urb); + } +#endif + if(0 == rc) { + DWC_TAILQ_INSERT_TAIL(&hcd->completed_urb_list, new_entry, + urb_tq_entries); + DWC_TASK_HI_SCHEDULE(hcd->completion_tasklet); + } + } + return 0; +} + +static struct dwc_otg_hcd_function_ops hcd_fops = { + .start = _start, + .disconnect = _disconnect, + .hub_info = _hub_info, + .speed = _speed, + .complete = _complete, + .get_b_hnp_enable = _get_b_hnp_enable, +}; + +#ifdef CONFIG_ARM64 + +static int simfiq_irq = -1; + +void local_fiq_enable(void) +{ + if (simfiq_irq >= 0) + enable_irq(simfiq_irq); +} + +void local_fiq_disable(void) +{ + if (simfiq_irq >= 0) + disable_irq(simfiq_irq); +} + +static irqreturn_t fiq_irq_handler(int irq, void *dev_id) +{ + dwc_otg_hcd_t *dwc_otg_hcd = (dwc_otg_hcd_t *)dev_id; + + if (fiq_fsm_enable) + dwc_otg_fiq_fsm(dwc_otg_hcd->fiq_state, dwc_otg_hcd->core_if->core_params->host_channels); + else + dwc_otg_fiq_nop(dwc_otg_hcd->fiq_state); + + return IRQ_HANDLED; +} + +#else +static struct fiq_handler fh = { + .name = "usb_fiq", +}; + +#endif + +static void hcd_init_fiq(void *cookie) +{ + dwc_otg_device_t *otg_dev = cookie; + dwc_otg_hcd_t *dwc_otg_hcd = otg_dev->hcd; +#ifdef CONFIG_ARM64 + int retval = 0; + int irq; +#else + struct pt_regs regs; + int irq; + + if (claim_fiq(&fh)) { + DWC_ERROR("Can't claim FIQ"); + BUG(); + } + DWC_WARN("FIQ on core %d", smp_processor_id()); + DWC_WARN("FIQ ASM at %px length %d", &_dwc_otg_fiq_stub, (int)(&_dwc_otg_fiq_stub_end - &_dwc_otg_fiq_stub)); + set_fiq_handler((void *) &_dwc_otg_fiq_stub, &_dwc_otg_fiq_stub_end - &_dwc_otg_fiq_stub); + memset(®s,0,sizeof(regs)); + + regs.ARM_r8 = (long) dwc_otg_hcd->fiq_state; + if (fiq_fsm_enable) { + regs.ARM_r9 = dwc_otg_hcd->core_if->core_params->host_channels; + //regs.ARM_r10 = dwc_otg_hcd->dma; + regs.ARM_fp = (long) dwc_otg_fiq_fsm; + } else { + regs.ARM_fp = (long) dwc_otg_fiq_nop; + } + + regs.ARM_sp = (long) dwc_otg_hcd->fiq_stack + (sizeof(struct fiq_stack) - 4); + +// __show_regs(®s); + set_fiq_regs(®s); +#endif + + dwc_otg_hcd->fiq_state->dwc_regs_base = otg_dev->os_dep.base; + //Set the mphi periph to the required registers + dwc_otg_hcd->fiq_state->mphi_regs.base = otg_dev->os_dep.mphi_base; + if (otg_dev->os_dep.use_swirq) { + dwc_otg_hcd->fiq_state->mphi_regs.swirq_set = + otg_dev->os_dep.mphi_base + 0x1f0; + dwc_otg_hcd->fiq_state->mphi_regs.swirq_clr = + otg_dev->os_dep.mphi_base + 0x1f4; + DWC_WARN("Fake MPHI regs_base at %px", + dwc_otg_hcd->fiq_state->mphi_regs.base); + } else { + dwc_otg_hcd->fiq_state->mphi_regs.ctrl = + otg_dev->os_dep.mphi_base + 0x4c; + dwc_otg_hcd->fiq_state->mphi_regs.outdda + = otg_dev->os_dep.mphi_base + 0x28; + dwc_otg_hcd->fiq_state->mphi_regs.outddb + = otg_dev->os_dep.mphi_base + 0x2c; + dwc_otg_hcd->fiq_state->mphi_regs.intstat + = otg_dev->os_dep.mphi_base + 0x50; + DWC_WARN("MPHI regs_base at %px", + dwc_otg_hcd->fiq_state->mphi_regs.base); + + //Enable mphi peripheral + writel((1<<31),dwc_otg_hcd->fiq_state->mphi_regs.ctrl); +#ifdef DEBUG + if (readl(dwc_otg_hcd->fiq_state->mphi_regs.ctrl) & 0x80000000) + DWC_WARN("MPHI periph has been enabled"); + else + DWC_WARN("MPHI periph has NOT been enabled"); +#endif + } + // Enable FIQ interrupt from USB peripheral +#ifdef CONFIG_ARM64 + irq = otg_dev->os_dep.fiq_num; + + if (irq < 0) { + DWC_ERROR("Can't get SIM-FIQ irq"); + return; + } + + retval = request_irq(irq, fiq_irq_handler, 0, "dwc_otg_sim-fiq", dwc_otg_hcd); + + if (retval < 0) { + DWC_ERROR("Unable to request SIM-FIQ irq\n"); + return; + } + + simfiq_irq = irq; +#else +#ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLER + irq = otg_dev->os_dep.fiq_num; +#else + irq = INTERRUPT_VC_USB; +#endif + if (irq < 0) { + DWC_ERROR("Can't get FIQ irq"); + return; + } + /* + * We could take an interrupt immediately after enabling the FIQ. + * Ensure coherency of hcd->fiq_state. + */ + smp_mb(); + enable_fiq(irq); + local_fiq_enable(); +#endif + +} + +/** + * Initializes the HCD. This function allocates memory for and initializes the + * static parts of the usb_hcd and dwc_otg_hcd structures. It also registers the + * USB bus with the core and calls the hc_driver->start() function. It returns + * a negative error on failure. + */ +int hcd_init(dwc_bus_dev_t *_dev) +{ + struct usb_hcd *hcd = NULL; + dwc_otg_hcd_t *dwc_otg_hcd = NULL; + dwc_otg_device_t *otg_dev = DWC_OTG_BUSDRVDATA(_dev); + int retval = 0; + u64 dmamask; + + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD INIT otg_dev=%p\n", otg_dev); + + /* Set device flags indicating whether the HCD supports DMA. */ + if (dwc_otg_is_dma_enable(otg_dev->core_if)) + dmamask = DMA_BIT_MASK(32); + else + dmamask = 0; + +#if defined(LM_INTERFACE) || defined(PLATFORM_INTERFACE) + dma_set_mask(&_dev->dev, dmamask); + dma_set_coherent_mask(&_dev->dev, dmamask); +#elif defined(PCI_INTERFACE) + pci_set_dma_mask(_dev, dmamask); + pci_set_consistent_dma_mask(_dev, dmamask); +#endif + + /* + * Allocate memory for the base HCD plus the DWC OTG HCD. + * Initialize the base HCD. + */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,30) + hcd = usb_create_hcd(&dwc_otg_hc_driver, &_dev->dev, _dev->dev.bus_id); +#else + hcd = usb_create_hcd(&dwc_otg_hc_driver, &_dev->dev, dev_name(&_dev->dev)); + hcd->has_tt = 1; +// hcd->uses_new_polling = 1; +// hcd->poll_rh = 0; +#endif + if (!hcd) { + retval = -ENOMEM; + goto error1; + } + + hcd->regs = otg_dev->os_dep.base; + + + /* Initialize the DWC OTG HCD. */ + dwc_otg_hcd = dwc_otg_hcd_alloc_hcd(); + if (!dwc_otg_hcd) { + goto error2; + } + ((struct wrapper_priv_data *)(hcd->hcd_priv))->dwc_otg_hcd = + dwc_otg_hcd; + otg_dev->hcd = dwc_otg_hcd; + otg_dev->hcd->otg_dev = otg_dev; + +#ifdef CONFIG_ARM64 + if (dwc_otg_hcd_init(dwc_otg_hcd, otg_dev->core_if)) + goto error2; + + if (fiq_enable) + hcd_init_fiq(otg_dev); +#else + if (dwc_otg_hcd_init(dwc_otg_hcd, otg_dev->core_if)) { + goto error2; + } + + if (fiq_enable) { + if (num_online_cpus() > 1) { + /* + * bcm2709: can run the FIQ on a separate core to IRQs. + * Ensure driver state is visible to other cores before setting up the FIQ. + */ + smp_mb(); + smp_call_function_single(1, hcd_init_fiq, otg_dev, 1); + } else { + smp_call_function_single(0, hcd_init_fiq, otg_dev, 1); + } + } +#endif + + hcd->self.otg_port = dwc_otg_hcd_otg_port(dwc_otg_hcd); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,33) //don't support for LM(with 2.6.20.1 kernel) +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35) //version field absent later + hcd->self.otg_version = dwc_otg_get_otg_version(otg_dev->core_if); +#endif + /* Don't support SG list at this point */ + hcd->self.sg_tablesize = 0; +#endif + /* + * Finish generic HCD initialization and start the HCD. This function + * allocates the DMA buffer pool, registers the USB bus, requests the + * IRQ line, and calls hcd_start method. + */ + retval = usb_add_hcd(hcd, otg_dev->os_dep.irq_num, IRQF_SHARED); + if (retval < 0) { + goto error2; + } + + dwc_otg_hcd_set_priv_data(dwc_otg_hcd, hcd); + return 0; + +error2: + usb_put_hcd(hcd); +error1: + return retval; +} + +/** + * Removes the HCD. + * Frees memory and resources associated with the HCD and deregisters the bus. + */ +void hcd_remove(dwc_bus_dev_t *_dev) +{ + dwc_otg_device_t *otg_dev = DWC_OTG_BUSDRVDATA(_dev); + dwc_otg_hcd_t *dwc_otg_hcd; + struct usb_hcd *hcd; + + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD REMOVE otg_dev=%p\n", otg_dev); + + if (!otg_dev) { + DWC_DEBUGPL(DBG_ANY, "%s: otg_dev NULL!\n", __func__); + return; + } + + dwc_otg_hcd = otg_dev->hcd; + + if (!dwc_otg_hcd) { + DWC_DEBUGPL(DBG_ANY, "%s: otg_dev->hcd NULL!\n", __func__); + return; + } + + hcd = dwc_otg_hcd_to_hcd(dwc_otg_hcd); + + if (!hcd) { + DWC_DEBUGPL(DBG_ANY, + "%s: dwc_otg_hcd_to_hcd(dwc_otg_hcd) NULL!\n", + __func__); + return; + } + usb_remove_hcd(hcd); + dwc_otg_hcd_set_priv_data(dwc_otg_hcd, NULL); + dwc_otg_hcd_remove(dwc_otg_hcd); + usb_put_hcd(hcd); +} + +/* ========================================================================= + * Linux HC Driver Functions + * ========================================================================= */ + +/** Initializes the DWC_otg controller and its root hub and prepares it for host + * mode operation. Activates the root port. Returns 0 on success and a negative + * error code on failure. */ +int hcd_start(struct usb_hcd *hcd) +{ + dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd); + struct usb_bus *bus; + + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD START\n"); + bus = hcd_to_bus(hcd); + + hcd->state = HC_STATE_RUNNING; + if (dwc_otg_hcd_start(dwc_otg_hcd, &hcd_fops)) { + return 0; + } + + /* Initialize and connect root hub if one is not already attached */ + if (bus->root_hub) { + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD Has Root Hub\n"); + /* Inform the HUB driver to resume. */ + usb_hcd_resume_root_hub(hcd); + } + + return 0; +} + +/** + * Halts the DWC_otg host mode operations in a clean manner. USB transfers are + * stopped. + */ +void hcd_stop(struct usb_hcd *hcd) +{ + dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd); + + dwc_otg_hcd_stop(dwc_otg_hcd); +} + +/** Returns the current frame number. */ +static int get_frame_number(struct usb_hcd *hcd) +{ + hprt0_data_t hprt0; + dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd); + hprt0.d32 = DWC_READ_REG32(dwc_otg_hcd->core_if->host_if->hprt0); + if (hprt0.b.prtspd == DWC_HPRT0_PRTSPD_HIGH_SPEED) + return dwc_otg_hcd_get_frame_number(dwc_otg_hcd) >> 3; + else + return dwc_otg_hcd_get_frame_number(dwc_otg_hcd); +} + +#ifdef DEBUG +static void dump_urb_info(struct urb *urb, char *fn_name) +{ + DWC_PRINTF("%s, urb %p\n", fn_name, urb); + DWC_PRINTF(" Device address: %d\n", usb_pipedevice(urb->pipe)); + DWC_PRINTF(" Endpoint: %d, %s\n", usb_pipeendpoint(urb->pipe), + (usb_pipein(urb->pipe) ? "IN" : "OUT")); + DWC_PRINTF(" Endpoint type: %s\n", ( { + char *pipetype; + switch (usb_pipetype(urb->pipe)) { +case PIPE_CONTROL: +pipetype = "CONTROL"; break; case PIPE_BULK: +pipetype = "BULK"; break; case PIPE_INTERRUPT: +pipetype = "INTERRUPT"; break; case PIPE_ISOCHRONOUS: +pipetype = "ISOCHRONOUS"; break; default: + pipetype = "UNKNOWN"; break;}; + pipetype;} + )) ; + DWC_PRINTF(" Speed: %s\n", ( { + char *speed; switch (urb->dev->speed) { +case USB_SPEED_HIGH: +speed = "HIGH"; break; case USB_SPEED_FULL: +speed = "FULL"; break; case USB_SPEED_LOW: +speed = "LOW"; break; default: + speed = "UNKNOWN"; break;}; + speed;} + )) ; + DWC_PRINTF(" Max packet size: %d\n", + usb_maxpacket(urb->dev, urb->pipe); + DWC_PRINTF(" Data buffer length: %d\n", urb->transfer_buffer_length); + DWC_PRINTF(" Transfer buffer: %p, Transfer DMA: %p\n", + urb->transfer_buffer, (void *)urb->transfer_dma); + DWC_PRINTF(" Setup buffer: %p, Setup DMA: %p\n", + urb->setup_packet, (void *)urb->setup_dma); + DWC_PRINTF(" Interval: %d\n", urb->interval); + if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) { + int i; + for (i = 0; i < urb->number_of_packets; i++) { + DWC_PRINTF(" ISO Desc %d:\n", i); + DWC_PRINTF(" offset: %d, length %d\n", + urb->iso_frame_desc[i].offset, + urb->iso_frame_desc[i].length); + } + } +} +#endif + +/** Starts processing a USB transfer request specified by a USB Request Block + * (URB). mem_flags indicates the type of memory allocation to use while + * processing this URB. */ +static int dwc_otg_urb_enqueue(struct usb_hcd *hcd, +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28) + struct usb_host_endpoint *ep, +#endif + struct urb *urb, gfp_t mem_flags) +{ + int retval = 0; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,28) + struct usb_host_endpoint *ep = urb->ep; +#endif + dwc_irqflags_t irqflags; + dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd); + dwc_otg_hcd_urb_t *dwc_otg_urb; + int i; + int alloc_bandwidth = 0; + uint8_t ep_type = 0; + uint32_t flags = 0; + void *buf; + +#ifdef DEBUG + if (CHK_DEBUG_LEVEL(DBG_HCDV | DBG_HCD_URB)) { + dump_urb_info(urb, "dwc_otg_urb_enqueue"); + } +#endif + if ((usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) + || (usb_pipetype(urb->pipe) == PIPE_INTERRUPT)) { + if (!dwc_otg_hcd_is_bandwidth_allocated + (dwc_otg_hcd, ep->hcpriv)) { + alloc_bandwidth = 1; + } + } + + switch (usb_pipetype(urb->pipe)) { + case PIPE_CONTROL: + ep_type = USB_ENDPOINT_XFER_CONTROL; + break; + case PIPE_ISOCHRONOUS: + ep_type = USB_ENDPOINT_XFER_ISOC; + break; + case PIPE_BULK: + ep_type = USB_ENDPOINT_XFER_BULK; + break; + case PIPE_INTERRUPT: + ep_type = USB_ENDPOINT_XFER_INT; + break; + default: + DWC_WARN("Wrong EP type - %d\n", usb_pipetype(urb->pipe)); + } + + /* # of packets is often 0 - do we really need to call this then? */ + dwc_otg_urb = dwc_otg_hcd_urb_alloc(dwc_otg_hcd, + urb->number_of_packets, + mem_flags == GFP_ATOMIC ? 1 : 0); + + if(dwc_otg_urb == NULL) + return -ENOMEM; + + if (!dwc_otg_urb && urb->number_of_packets) + return -ENOMEM; + + dwc_otg_hcd_urb_set_pipeinfo(dwc_otg_urb, usb_pipedevice(urb->pipe), + usb_pipeendpoint(urb->pipe), ep_type, + usb_pipein(urb->pipe), + usb_maxpacket(urb->dev, urb->pipe)); + + buf = urb->transfer_buffer; + if (hcd_uses_dma(hcd) && !buf && urb->transfer_buffer_length) { + /* + * Calculate virtual address from physical address, + * because some class driver may not fill transfer_buffer. + * In Buffer DMA mode virual address is used, + * when handling non DWORD aligned buffers. + */ + buf = (void *)__bus_to_virt((unsigned long)urb->transfer_dma); + dev_warn_once(&urb->dev->dev, + "USB transfer_buffer was NULL, will use __bus_to_virt(%pad)=%p\n", + &urb->transfer_dma, buf); + } + + if (!buf && urb->transfer_buffer_length) { + DWC_FREE(dwc_otg_urb); + DWC_ERROR("transfer_buffer is NULL in PIO mode or both " + "transfer_buffer and transfer_dma are NULL in DMA mode\n"); + return -EINVAL; + } + + if (!(urb->transfer_flags & URB_NO_INTERRUPT)) + flags |= URB_GIVEBACK_ASAP; + if (urb->transfer_flags & URB_ZERO_PACKET) + flags |= URB_SEND_ZERO_PACKET; + + dwc_otg_hcd_urb_set_params(dwc_otg_urb, urb, buf, + urb->transfer_dma, + urb->transfer_buffer_length, + urb->setup_packet, + urb->setup_dma, flags, urb->interval); + + for (i = 0; i < urb->number_of_packets; ++i) { + dwc_otg_hcd_urb_set_iso_desc_params(dwc_otg_urb, i, + urb-> + iso_frame_desc[i].offset, + urb-> + iso_frame_desc[i].length); + } + + DWC_SPINLOCK_IRQSAVE(dwc_otg_hcd->lock, &irqflags); + urb->hcpriv = dwc_otg_urb; +#if USB_URB_EP_LINKING + retval = usb_hcd_link_urb_to_ep(hcd, urb); + if (0 == retval) +#endif + { + retval = dwc_otg_hcd_urb_enqueue(dwc_otg_hcd, dwc_otg_urb, + &ep->hcpriv, 1); + if (0 == retval) { + if (alloc_bandwidth) { + allocate_bus_bandwidth(hcd, + dwc_otg_hcd_get_ep_bandwidth( + dwc_otg_hcd, ep->hcpriv), + urb); + } + } else { + DWC_DEBUGPL(DBG_HCD, "DWC OTG dwc_otg_hcd_urb_enqueue failed rc %d\n", retval); +#if USB_URB_EP_LINKING + usb_hcd_unlink_urb_from_ep(hcd, urb); +#endif + DWC_FREE(dwc_otg_urb); + urb->hcpriv = NULL; + if (retval == -DWC_E_NO_DEVICE) + retval = -ENODEV; + } + } +#if USB_URB_EP_LINKING + else + { + DWC_FREE(dwc_otg_urb); + urb->hcpriv = NULL; + } +#endif + DWC_SPINUNLOCK_IRQRESTORE(dwc_otg_hcd->lock, irqflags); + return retval; +} + +/** Aborts/cancels a USB transfer request. Always returns 0 to indicate + * success. */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28) +static int dwc_otg_urb_dequeue(struct usb_hcd *hcd, struct urb *urb) +#else +static int dwc_otg_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) +#endif +{ + dwc_irqflags_t flags; + dwc_otg_hcd_t *dwc_otg_hcd; + int rc; + + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD URB Dequeue\n"); + + dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd); + +#ifdef DEBUG + if (CHK_DEBUG_LEVEL(DBG_HCDV | DBG_HCD_URB)) { + dump_urb_info(urb, "dwc_otg_urb_dequeue"); + } +#endif + + DWC_SPINLOCK_IRQSAVE(dwc_otg_hcd->lock, &flags); + rc = usb_hcd_check_unlink_urb(hcd, urb, status); + if (0 == rc) { + if(urb->hcpriv != NULL) { + dwc_otg_hcd_urb_dequeue(dwc_otg_hcd, + (dwc_otg_hcd_urb_t *)urb->hcpriv); + + DWC_FREE(urb->hcpriv); + urb->hcpriv = NULL; + } + } + + if (0 == rc) { + /* Higher layer software sets URB status. */ +#if USB_URB_EP_LINKING + usb_hcd_unlink_urb_from_ep(hcd, urb); +#endif + DWC_SPINUNLOCK_IRQRESTORE(dwc_otg_hcd->lock, flags); + + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28) + usb_hcd_giveback_urb(hcd, urb); +#else + usb_hcd_giveback_urb(hcd, urb, status); +#endif + if (CHK_DEBUG_LEVEL(DBG_HCDV | DBG_HCD_URB)) { + DWC_PRINTF("Called usb_hcd_giveback_urb() \n"); + DWC_PRINTF(" 1urb->status = %d\n", urb->status); + } + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD URB Dequeue OK\n"); + } else { + DWC_SPINUNLOCK_IRQRESTORE(dwc_otg_hcd->lock, flags); + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD URB Dequeue failed - rc %d\n", + rc); + } + + return rc; +} + +/* Frees resources in the DWC_otg controller related to a given endpoint. Also + * clears state in the HCD related to the endpoint. Any URBs for the endpoint + * must already be dequeued. */ +static void endpoint_disable(struct usb_hcd *hcd, struct usb_host_endpoint *ep) +{ + dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd); + + DWC_DEBUGPL(DBG_HCD, + "DWC OTG HCD EP DISABLE: _bEndpointAddress=0x%02x, " + "endpoint=%d\n", ep->desc.bEndpointAddress, + dwc_ep_addr_to_endpoint(ep->desc.bEndpointAddress)); + dwc_otg_hcd_endpoint_disable(dwc_otg_hcd, ep->hcpriv, 250); + ep->hcpriv = NULL; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30) +/* Resets endpoint specific parameter values, in current version used to reset + * the data toggle(as a WA). This function can be called from usb_clear_halt routine */ +static void endpoint_reset(struct usb_hcd *hcd, struct usb_host_endpoint *ep) +{ + dwc_irqflags_t flags; + dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd); + + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD EP RESET: Endpoint Num=0x%02d\n", + ep->desc.bEndpointAddress); + + DWC_SPINLOCK_IRQSAVE(dwc_otg_hcd->lock, &flags); + if (ep->hcpriv) { + dwc_otg_hcd_endpoint_reset(dwc_otg_hcd, ep->hcpriv); + } + DWC_SPINUNLOCK_IRQRESTORE(dwc_otg_hcd->lock, flags); +} +#endif + +/** Handles host mode interrupts for the DWC_otg controller. Returns IRQ_NONE if + * there was no interrupt to handle. Returns IRQ_HANDLED if there was a valid + * interrupt. + * + * This function is called by the USB core when an interrupt occurs */ +static irqreturn_t dwc_otg_hcd_irq(struct usb_hcd *hcd) +{ + dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd); + int32_t retval = dwc_otg_hcd_handle_intr(dwc_otg_hcd); + if (retval != 0) { + S3C2410X_CLEAR_EINTPEND(); + } + return IRQ_RETVAL(retval); +} + +/** Creates Status Change bitmap for the root hub and root port. The bitmap is + * returned in buf. Bit 0 is the status change indicator for the root hub. Bit 1 + * is the status change indicator for the single root port. Returns 1 if either + * change indicator is 1, otherwise returns 0. */ +int hub_status_data(struct usb_hcd *hcd, char *buf) +{ + dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd); + + buf[0] = 0; + buf[0] |= (dwc_otg_hcd_is_status_changed(dwc_otg_hcd, 1)) << 1; + + return (buf[0] != 0); +} + +/** Handles hub class-specific requests. */ +int hub_control(struct usb_hcd *hcd, + u16 typeReq, u16 wValue, u16 wIndex, char *buf, u16 wLength) +{ + int retval; + + retval = dwc_otg_hcd_hub_control(hcd_to_dwc_otg_hcd(hcd), + typeReq, wValue, wIndex, buf, wLength); + + switch (retval) { + case -DWC_E_INVALID: + retval = -EINVAL; + break; + } + + return retval; +} + +#endif /* DWC_DEVICE_ONLY */ diff --git a/drivers/usb/host/dwc_otg/dwc_otg_hcd_queue.c b/drivers/usb/host/dwc_otg/dwc_otg_hcd_queue.c new file mode 100644 index 00000000000000..d8a49a8568f244 --- /dev/null +++ b/drivers/usb/host/dwc_otg/dwc_otg_hcd_queue.c @@ -0,0 +1,974 @@ +/* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_hcd_queue.c $ + * $Revision: #44 $ + * $Date: 2011/10/26 $ + * $Change: 1873028 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ +#ifndef DWC_DEVICE_ONLY + +/** + * @file + * + * This file contains the functions to manage Queue Heads and Queue + * Transfer Descriptors. + */ + +#include "dwc_otg_hcd.h" +#include "dwc_otg_regs.h" + +extern bool microframe_schedule; +extern unsigned short int_ep_interval_min; + +/** + * Free each QTD in the QH's QTD-list then free the QH. QH should already be + * removed from a list. QTD list should already be empty if called from URB + * Dequeue. + * + * @param hcd HCD instance. + * @param qh The QH to free. + */ +void dwc_otg_hcd_qh_free(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh) +{ + dwc_otg_qtd_t *qtd, *qtd_tmp; + dwc_irqflags_t flags; + uint32_t buf_size = 0; + uint8_t *align_buf_virt = NULL; + dwc_dma_t align_buf_dma; + struct device *dev = dwc_otg_hcd_to_dev(hcd); + + /* Free each QTD in the QTD list */ + DWC_SPINLOCK_IRQSAVE(hcd->lock, &flags); + DWC_CIRCLEQ_FOREACH_SAFE(qtd, qtd_tmp, &qh->qtd_list, qtd_list_entry) { + DWC_CIRCLEQ_REMOVE(&qh->qtd_list, qtd, qtd_list_entry); + dwc_otg_hcd_qtd_free(qtd); + } + + if (hcd->core_if->dma_desc_enable) { + dwc_otg_hcd_qh_free_ddma(hcd, qh); + } else if (qh->dw_align_buf) { + if (qh->ep_type == UE_ISOCHRONOUS) { + buf_size = 4096; + } else { + buf_size = hcd->core_if->core_params->max_transfer_size; + } + align_buf_virt = qh->dw_align_buf; + align_buf_dma = qh->dw_align_buf_dma; + } + + DWC_FREE(qh); + DWC_SPINUNLOCK_IRQRESTORE(hcd->lock, flags); + if (align_buf_virt) + DWC_DMA_FREE(dev, buf_size, align_buf_virt, align_buf_dma); + return; +} + +#define BitStuffTime(bytecount) ((8 * 7* bytecount) / 6) +#define HS_HOST_DELAY 5 /* nanoseconds */ +#define FS_LS_HOST_DELAY 1000 /* nanoseconds */ +#define HUB_LS_SETUP 333 /* nanoseconds */ +#define NS_TO_US(ns) ((ns + 500) / 1000) + /* convert & round nanoseconds to microseconds */ + +static uint32_t calc_bus_time(int speed, int is_in, int is_isoc, int bytecount) +{ + unsigned long retval; + + switch (speed) { + case USB_SPEED_HIGH: + if (is_isoc) { + retval = + ((38 * 8 * 2083) + + (2083 * (3 + BitStuffTime(bytecount)))) / 1000 + + HS_HOST_DELAY; + } else { + retval = + ((55 * 8 * 2083) + + (2083 * (3 + BitStuffTime(bytecount)))) / 1000 + + HS_HOST_DELAY; + } + break; + case USB_SPEED_FULL: + if (is_isoc) { + retval = + (8354 * (31 + 10 * BitStuffTime(bytecount))) / 1000; + if (is_in) { + retval = 7268 + FS_LS_HOST_DELAY + retval; + } else { + retval = 6265 + FS_LS_HOST_DELAY + retval; + } + } else { + retval = + (8354 * (31 + 10 * BitStuffTime(bytecount))) / 1000; + retval = 9107 + FS_LS_HOST_DELAY + retval; + } + break; + case USB_SPEED_LOW: + if (is_in) { + retval = + (67667 * (31 + 10 * BitStuffTime(bytecount))) / + 1000; + retval = + 64060 + (2 * HUB_LS_SETUP) + FS_LS_HOST_DELAY + + retval; + } else { + retval = + (66700 * (31 + 10 * BitStuffTime(bytecount))) / + 1000; + retval = + 64107 + (2 * HUB_LS_SETUP) + FS_LS_HOST_DELAY + + retval; + } + break; + default: + DWC_WARN("Unknown device speed\n"); + retval = -1; + } + + return NS_TO_US(retval); +} + +/** + * Initializes a QH structure. + * + * @param hcd The HCD state structure for the DWC OTG controller. + * @param qh The QH to init. + * @param urb Holds the information about the device/endpoint that we need + * to initialize the QH. + */ +#define SCHEDULE_SLOP 10 +static void qh_init(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh, dwc_otg_hcd_urb_t * urb) +{ + char *speed, *type; + int dev_speed; + uint32_t hub_addr, hub_port; + hprt0_data_t hprt; + + dwc_memset(qh, 0, sizeof(dwc_otg_qh_t)); + hprt.d32 = DWC_READ_REG32(hcd->core_if->host_if->hprt0); + + /* Initialize QH */ + qh->ep_type = dwc_otg_hcd_get_pipe_type(&urb->pipe_info); + qh->ep_is_in = dwc_otg_hcd_is_pipe_in(&urb->pipe_info) ? 1 : 0; + + qh->data_toggle = DWC_OTG_HC_PID_DATA0; + qh->maxp = dwc_otg_hcd_get_mps(&urb->pipe_info); + DWC_CIRCLEQ_INIT(&qh->qtd_list); + DWC_LIST_INIT(&qh->qh_list_entry); + qh->channel = NULL; + + /* FS/LS Enpoint on HS Hub + * NOT virtual root hub */ + dev_speed = hcd->fops->speed(hcd, urb->priv); + + hcd->fops->hub_info(hcd, urb->priv, &hub_addr, &hub_port); + qh->do_split = 0; + if (microframe_schedule) + qh->speed = dev_speed; + + qh->nak_frame = 0xffff; + + if (hprt.b.prtspd == DWC_HPRT0_PRTSPD_HIGH_SPEED && + dev_speed != USB_SPEED_HIGH) { + DWC_DEBUGPL(DBG_HCD, + "QH init: EP %d: TT found at hub addr %d, for port %d\n", + dwc_otg_hcd_get_ep_num(&urb->pipe_info), hub_addr, + hub_port); + qh->do_split = 1; + qh->skip_count = 0; + } + + if (qh->ep_type == UE_INTERRUPT || qh->ep_type == UE_ISOCHRONOUS) { + /* Compute scheduling parameters once and save them. */ + + /** @todo Account for split transfers in the bus time. */ + int bytecount = + dwc_hb_mult(qh->maxp) * dwc_max_packet(qh->maxp); + + qh->usecs = + calc_bus_time((qh->do_split ? USB_SPEED_HIGH : dev_speed), + qh->ep_is_in, (qh->ep_type == UE_ISOCHRONOUS), + bytecount); + /* Start in a slightly future (micro)frame. */ + qh->sched_frame = dwc_frame_num_inc(hcd->frame_number, + SCHEDULE_SLOP); + qh->interval = urb->interval; + + if (hprt.b.prtspd == DWC_HPRT0_PRTSPD_HIGH_SPEED) { + if (dev_speed == USB_SPEED_LOW || + dev_speed == USB_SPEED_FULL) { + qh->interval *= 8; + qh->sched_frame |= 0x7; + qh->start_split_frame = qh->sched_frame; + } else if (int_ep_interval_min >= 2 && + qh->interval < int_ep_interval_min && + qh->ep_type == UE_INTERRUPT) { + qh->interval = int_ep_interval_min; + } + } + } + + DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD QH Initialized\n"); + DWC_DEBUGPL(DBG_HCDV, "DWC OTG HCD QH - qh = %p\n", qh); + DWC_DEBUGPL(DBG_HCDV, "DWC OTG HCD QH - Device Address = %d\n", + dwc_otg_hcd_get_dev_addr(&urb->pipe_info)); + DWC_DEBUGPL(DBG_HCDV, "DWC OTG HCD QH - Endpoint %d, %s\n", + dwc_otg_hcd_get_ep_num(&urb->pipe_info), + dwc_otg_hcd_is_pipe_in(&urb->pipe_info) ? "IN" : "OUT"); + switch (dev_speed) { + case USB_SPEED_LOW: + qh->dev_speed = DWC_OTG_EP_SPEED_LOW; + speed = "low"; + break; + case USB_SPEED_FULL: + qh->dev_speed = DWC_OTG_EP_SPEED_FULL; + speed = "full"; + break; + case USB_SPEED_HIGH: + qh->dev_speed = DWC_OTG_EP_SPEED_HIGH; + speed = "high"; + break; + default: + speed = "?"; + break; + } + DWC_DEBUGPL(DBG_HCDV, "DWC OTG HCD QH - Speed = %s\n", speed); + + switch (qh->ep_type) { + case UE_ISOCHRONOUS: + type = "isochronous"; + break; + case UE_INTERRUPT: + type = "interrupt"; + break; + case UE_CONTROL: + type = "control"; + break; + case UE_BULK: + type = "bulk"; + break; + default: + type = "?"; + break; + } + + DWC_DEBUGPL(DBG_HCDV, "DWC OTG HCD QH - Type = %s\n", type); + +#ifdef DEBUG + if (qh->ep_type == UE_INTERRUPT) { + DWC_DEBUGPL(DBG_HCDV, "DWC OTG HCD QH - usecs = %d\n", + qh->usecs); + DWC_DEBUGPL(DBG_HCDV, "DWC OTG HCD QH - interval = %d\n", + qh->interval); + } +#endif + +} + +/** + * This function allocates and initializes a QH. + * + * @param hcd The HCD state structure for the DWC OTG controller. + * @param urb Holds the information about the device/endpoint that we need + * to initialize the QH. + * @param atomic_alloc Flag to do atomic allocation if needed + * + * @return Returns pointer to the newly allocated QH, or NULL on error. */ +dwc_otg_qh_t *dwc_otg_hcd_qh_create(dwc_otg_hcd_t * hcd, + dwc_otg_hcd_urb_t * urb, int atomic_alloc) +{ + dwc_otg_qh_t *qh; + + /* Allocate memory */ + /** @todo add memflags argument */ + qh = dwc_otg_hcd_qh_alloc(atomic_alloc); + if (qh == NULL) { + DWC_ERROR("qh allocation failed"); + return NULL; + } + + qh_init(hcd, qh, urb); + + if (hcd->core_if->dma_desc_enable + && (dwc_otg_hcd_qh_init_ddma(hcd, qh) < 0)) { + dwc_otg_hcd_qh_free(hcd, qh); + return NULL; + } + + return qh; +} + +/* microframe_schedule=0 start */ + +/** + * Checks that a channel is available for a periodic transfer. + * + * @return 0 if successful, negative error code otherise. + */ +static int periodic_channel_available(dwc_otg_hcd_t * hcd) +{ + /* + * Currently assuming that there is a dedicated host channnel for each + * periodic transaction plus at least one host channel for + * non-periodic transactions. + */ + int status; + int num_channels; + + num_channels = hcd->core_if->core_params->host_channels; + if ((hcd->periodic_channels + hcd->non_periodic_channels < num_channels) + && (hcd->periodic_channels < num_channels - 1)) { + status = 0; + } else { + DWC_INFO("%s: Total channels: %d, Periodic: %d, Non-periodic: %d\n", + __func__, num_channels, hcd->periodic_channels, hcd->non_periodic_channels); //NOTICE + status = -DWC_E_NO_SPACE; + } + + return status; +} + +/** + * Checks that there is sufficient bandwidth for the specified QH in the + * periodic schedule. For simplicity, this calculation assumes that all the + * transfers in the periodic schedule may occur in the same (micro)frame. + * + * @param hcd The HCD state structure for the DWC OTG controller. + * @param qh QH containing periodic bandwidth required. + * + * @return 0 if successful, negative error code otherwise. + */ +static int check_periodic_bandwidth(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh) +{ + int status; + int16_t max_claimed_usecs; + + status = 0; + + if ((qh->dev_speed == DWC_OTG_EP_SPEED_HIGH) || qh->do_split) { + /* + * High speed mode. + * Max periodic usecs is 80% x 125 usec = 100 usec. + */ + + max_claimed_usecs = 100 - qh->usecs; + } else { + /* + * Full speed mode. + * Max periodic usecs is 90% x 1000 usec = 900 usec. + */ + max_claimed_usecs = 900 - qh->usecs; + } + + if (hcd->periodic_usecs > max_claimed_usecs) { + DWC_INFO("%s: already claimed usecs %d, required usecs %d\n", __func__, hcd->periodic_usecs, qh->usecs); //NOTICE + status = -DWC_E_NO_SPACE; + } + + return status; +} + +/* microframe_schedule=0 end */ + +/** + * Microframe scheduler + * track the total use in hcd->frame_usecs + * keep each qh use in qh->frame_usecs + * when surrendering the qh then donate the time back + */ +const unsigned short max_uframe_usecs[]={ 100, 100, 100, 100, 100, 100, 30, 0 }; + +/* + * called from dwc_otg_hcd.c:dwc_otg_hcd_init + */ +void init_hcd_usecs(dwc_otg_hcd_t *_hcd) +{ + int i; + if (_hcd->flags.b.port_speed == DWC_HPRT0_PRTSPD_FULL_SPEED) { + _hcd->frame_usecs[0] = 900; + for (i = 1; i < 8; i++) + _hcd->frame_usecs[i] = 0; + } else { + for (i = 0; i < 8; i++) + _hcd->frame_usecs[i] = max_uframe_usecs[i]; + } +} + +static int find_single_uframe(dwc_otg_hcd_t * _hcd, dwc_otg_qh_t * _qh) +{ + int i; + unsigned short utime; + int t_left; + int ret; + int done; + + ret = -1; + utime = _qh->usecs; + t_left = utime; + i = 0; + done = 0; + while (done == 0) { + /* At the start _hcd->frame_usecs[i] = max_uframe_usecs[i]; */ + if (utime <= _hcd->frame_usecs[i]) { + _hcd->frame_usecs[i] -= utime; + _qh->frame_usecs[i] += utime; + t_left -= utime; + ret = i; + done = 1; + return ret; + } else { + i++; + if (i == 8) { + done = 1; + ret = -1; + } + } + } + return ret; + } + +/* + * use this for FS apps that can span multiple uframes + */ +static int find_multi_uframe(dwc_otg_hcd_t * _hcd, dwc_otg_qh_t * _qh) +{ + int i; + int j; + unsigned short utime; + int t_left; + int ret; + int done; + unsigned short xtime; + + ret = -1; + utime = _qh->usecs; + t_left = utime; + i = 0; + done = 0; +loop: + while (done == 0) { + if(_hcd->frame_usecs[i] <= 0) { + i++; + if (i == 8) { + done = 1; + ret = -1; + } + goto loop; + } + + /* + * we need n consecutive slots + * so use j as a start slot j plus j+1 must be enough time (for now) + */ + xtime= _hcd->frame_usecs[i]; + for (j = i+1 ; j < 8 ; j++ ) { + /* + * if we add this frame remaining time to xtime we may + * be OK, if not we need to test j for a complete frame + */ + if ((xtime+_hcd->frame_usecs[j]) < utime) { + if (_hcd->frame_usecs[j] < max_uframe_usecs[j]) { + j = 8; + ret = -1; + continue; + } + } + if (xtime >= utime) { + ret = i; + j = 8; /* stop loop with a good value ret */ + continue; + } + /* add the frame time to x time */ + xtime += _hcd->frame_usecs[j]; + /* we must have a fully available next frame or break */ + if ((xtime < utime) + && (_hcd->frame_usecs[j] == max_uframe_usecs[j])) { + ret = -1; + j = 8; /* stop loop with a bad value ret */ + continue; + } + } + if (ret >= 0) { + t_left = utime; + for (j = i; (t_left>0) && (j < 8); j++ ) { + t_left -= _hcd->frame_usecs[j]; + if ( t_left <= 0 ) { + _qh->frame_usecs[j] += _hcd->frame_usecs[j] + t_left; + _hcd->frame_usecs[j]= -t_left; + ret = i; + done = 1; + } else { + _qh->frame_usecs[j] += _hcd->frame_usecs[j]; + _hcd->frame_usecs[j] = 0; + } + } + } else { + i++; + if (i == 8) { + done = 1; + ret = -1; + } + } + } + return ret; +} + +static int find_uframe(dwc_otg_hcd_t * _hcd, dwc_otg_qh_t * _qh) +{ + int ret; + ret = -1; + + if (_qh->speed == USB_SPEED_HIGH || + _hcd->flags.b.port_speed == DWC_HPRT0_PRTSPD_FULL_SPEED) { + /* if this is a hs transaction we need a full frame - or account for FS usecs */ + ret = find_single_uframe(_hcd, _qh); + } else { + /* if this is a fs transaction we may need a sequence of frames */ + ret = find_multi_uframe(_hcd, _qh); + } + return ret; +} + +/** + * Checks that the max transfer size allowed in a host channel is large enough + * to handle the maximum data transfer in a single (micro)frame for a periodic + * transfer. + * + * @param hcd The HCD state structure for the DWC OTG controller. + * @param qh QH for a periodic endpoint. + * + * @return 0 if successful, negative error code otherwise. + */ +static int check_max_xfer_size(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh) +{ + int status; + uint32_t max_xfer_size; + uint32_t max_channel_xfer_size; + + status = 0; + + max_xfer_size = dwc_max_packet(qh->maxp) * dwc_hb_mult(qh->maxp); + max_channel_xfer_size = hcd->core_if->core_params->max_transfer_size; + + if (max_xfer_size > max_channel_xfer_size) { + DWC_INFO("%s: Periodic xfer length %d > " "max xfer length for channel %d\n", + __func__, max_xfer_size, max_channel_xfer_size); //NOTICE + status = -DWC_E_NO_SPACE; + } + + return status; +} + + + +/** + * Schedules an interrupt or isochronous transfer in the periodic schedule. + * + * @param hcd The HCD state structure for the DWC OTG controller. + * @param qh QH for the periodic transfer. The QH should already contain the + * scheduling information. + * + * @return 0 if successful, negative error code otherwise. + */ +static int schedule_periodic(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh) +{ + int status = 0; + + if (microframe_schedule) { + int frame; + status = find_uframe(hcd, qh); + frame = -1; + if (status == 0) { + frame = 7; + } else { + if (status > 0 ) + frame = status-1; + } + + /* Set the new frame up */ + if (frame > -1) { + qh->sched_frame &= ~0x7; + qh->sched_frame |= (frame & 7); + } + + if (status != -1) + status = 0; + } else { + status = periodic_channel_available(hcd); + if (status) { + DWC_INFO("%s: No host channel available for periodic " "transfer.\n", __func__); //NOTICE + return status; + } + + status = check_periodic_bandwidth(hcd, qh); + } + if (status) { + DWC_INFO("%s: Insufficient periodic bandwidth for " + "periodic transfer.\n", __func__); + return -DWC_E_NO_SPACE; + } + status = check_max_xfer_size(hcd, qh); + if (status) { + DWC_INFO("%s: Channel max transfer size too small " + "for periodic transfer.\n", __func__); + return status; + } + + if (hcd->core_if->dma_desc_enable) { + /* Don't rely on SOF and start in ready schedule */ + DWC_LIST_INSERT_TAIL(&hcd->periodic_sched_ready, &qh->qh_list_entry); + } + else { + if(fiq_enable && (DWC_LIST_EMPTY(&hcd->periodic_sched_inactive) || dwc_frame_num_le(qh->sched_frame, hcd->fiq_state->next_sched_frame))) + { + hcd->fiq_state->next_sched_frame = qh->sched_frame; + + } + /* Always start in the inactive schedule. */ + DWC_LIST_INSERT_TAIL(&hcd->periodic_sched_inactive, &qh->qh_list_entry); + } + + if (!microframe_schedule) { + /* Reserve the periodic channel. */ + hcd->periodic_channels++; + } + + /* Update claimed usecs per (micro)frame. */ + hcd->periodic_usecs += qh->usecs; + + return status; +} + + +/** + * This function adds a QH to either the non periodic or periodic schedule if + * it is not already in the schedule. If the QH is already in the schedule, no + * action is taken. + * + * @return 0 if successful, negative error code otherwise. + */ +int dwc_otg_hcd_qh_add(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh) +{ + int status = 0; + gintmsk_data_t intr_mask = {.d32 = 0 }; + + if (!DWC_LIST_EMPTY(&qh->qh_list_entry)) { + /* QH already in a schedule. */ + return status; + } + + /* Add the new QH to the appropriate schedule */ + if (dwc_qh_is_non_per(qh)) { + /* Always start in the inactive schedule. */ + DWC_LIST_INSERT_TAIL(&hcd->non_periodic_sched_inactive, + &qh->qh_list_entry); + //hcd->fiq_state->kick_np_queues = 1; + } else { + /* If the QH wasn't in a schedule, then sched_frame is stale. */ + qh->sched_frame = dwc_frame_num_inc(dwc_otg_hcd_get_frame_number(hcd), + max_t(uint32_t, qh->interval, SCHEDULE_SLOP)); + status = schedule_periodic(hcd, qh); + qh->start_split_frame = qh->sched_frame; + if ( !hcd->periodic_qh_count ) { + intr_mask.b.sofintr = 1; + if (fiq_enable) { + local_fiq_disable(); + fiq_fsm_spin_lock(&hcd->fiq_state->lock); + DWC_MODIFY_REG32(&hcd->core_if->core_global_regs->gintmsk, intr_mask.d32, intr_mask.d32); + fiq_fsm_spin_unlock(&hcd->fiq_state->lock); + local_fiq_enable(); + } else { + DWC_MODIFY_REG32(&hcd->core_if->core_global_regs->gintmsk, intr_mask.d32, intr_mask.d32); + } + } + hcd->periodic_qh_count++; + } + + return status; +} + +/** + * Removes an interrupt or isochronous transfer from the periodic schedule. + * + * @param hcd The HCD state structure for the DWC OTG controller. + * @param qh QH for the periodic transfer. + */ +static void deschedule_periodic(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh) +{ + int i; + DWC_LIST_REMOVE_INIT(&qh->qh_list_entry); + + /* Update claimed usecs per (micro)frame. */ + hcd->periodic_usecs -= qh->usecs; + + if (!microframe_schedule) { + /* Release the periodic channel reservation. */ + hcd->periodic_channels--; + } else { + for (i = 0; i < 8; i++) { + hcd->frame_usecs[i] += qh->frame_usecs[i]; + qh->frame_usecs[i] = 0; + } + } +} + +/** + * Removes a QH from either the non-periodic or periodic schedule. Memory is + * not freed. + * + * @param hcd The HCD state structure. + * @param qh QH to remove from schedule. */ +void dwc_otg_hcd_qh_remove(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh) +{ + gintmsk_data_t intr_mask = {.d32 = 0 }; + + if (DWC_LIST_EMPTY(&qh->qh_list_entry)) { + /* QH is not in a schedule. */ + return; + } + + if (dwc_qh_is_non_per(qh)) { + if (hcd->non_periodic_qh_ptr == &qh->qh_list_entry) { + hcd->non_periodic_qh_ptr = + hcd->non_periodic_qh_ptr->next; + } + DWC_LIST_REMOVE_INIT(&qh->qh_list_entry); + //if (!DWC_LIST_EMPTY(&hcd->non_periodic_sched_inactive)) + // hcd->fiq_state->kick_np_queues = 1; + } else { + deschedule_periodic(hcd, qh); + hcd->periodic_qh_count--; + if( !hcd->periodic_qh_count && !fiq_fsm_enable ) { + intr_mask.b.sofintr = 1; + if (fiq_enable) { + local_fiq_disable(); + fiq_fsm_spin_lock(&hcd->fiq_state->lock); + DWC_MODIFY_REG32(&hcd->core_if->core_global_regs->gintmsk, intr_mask.d32, 0); + fiq_fsm_spin_unlock(&hcd->fiq_state->lock); + local_fiq_enable(); + } else { + DWC_MODIFY_REG32(&hcd->core_if->core_global_regs->gintmsk, intr_mask.d32, 0); + } + } + } +} + +/** + * Deactivates a QH. For non-periodic QHs, removes the QH from the active + * non-periodic schedule. The QH is added to the inactive non-periodic + * schedule if any QTDs are still attached to the QH. + * + * For periodic QHs, the QH is removed from the periodic queued schedule. If + * there are any QTDs still attached to the QH, the QH is added to either the + * periodic inactive schedule or the periodic ready schedule and its next + * scheduled frame is calculated. The QH is placed in the ready schedule if + * the scheduled frame has been reached already. Otherwise it's placed in the + * inactive schedule. If there are no QTDs attached to the QH, the QH is + * completely removed from the periodic schedule. + */ +void dwc_otg_hcd_qh_deactivate(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh, + int sched_next_periodic_split) +{ + if (dwc_qh_is_non_per(qh)) { + dwc_otg_hcd_qh_remove(hcd, qh); + if (!DWC_CIRCLEQ_EMPTY(&qh->qtd_list)) { + /* Add back to inactive non-periodic schedule. */ + dwc_otg_hcd_qh_add(hcd, qh); + //hcd->fiq_state->kick_np_queues = 1; + } else { + if(nak_holdoff && qh->do_split) { + qh->nak_frame = 0xFFFF; + } + } + } else { + uint16_t frame_number = dwc_otg_hcd_get_frame_number(hcd); + + if (qh->do_split) { + /* Schedule the next continuing periodic split transfer */ + if (sched_next_periodic_split) { + + qh->sched_frame = frame_number; + + if (dwc_frame_num_le(frame_number, + dwc_frame_num_inc + (qh->start_split_frame, + 1))) { + /* + * Allow one frame to elapse after start + * split microframe before scheduling + * complete split, but DONT if we are + * doing the next start split in the + * same frame for an ISOC out. + */ + if ((qh->ep_type != UE_ISOCHRONOUS) || + (qh->ep_is_in != 0)) { + qh->sched_frame = + dwc_frame_num_inc(qh->sched_frame, 1); + } + } + } else { + qh->sched_frame = + dwc_frame_num_inc(qh->start_split_frame, + qh->interval); + if (dwc_frame_num_le + (qh->sched_frame, frame_number)) { + qh->sched_frame = frame_number; + } + qh->sched_frame |= 0x7; + qh->start_split_frame = qh->sched_frame; + } + } else { + qh->sched_frame = + dwc_frame_num_inc(qh->sched_frame, qh->interval); + if (dwc_frame_num_le(qh->sched_frame, frame_number)) { + qh->sched_frame = frame_number; + } + } + + if (DWC_CIRCLEQ_EMPTY(&qh->qtd_list)) { + dwc_otg_hcd_qh_remove(hcd, qh); + } else { + /* + * Remove from periodic_sched_queued and move to + * appropriate queue. + */ + if ((microframe_schedule && dwc_frame_num_le(qh->sched_frame, frame_number)) || + (!microframe_schedule && qh->sched_frame == frame_number)) { + DWC_LIST_MOVE_HEAD(&hcd->periodic_sched_ready, + &qh->qh_list_entry); + } else { + if(fiq_enable && !dwc_frame_num_le(hcd->fiq_state->next_sched_frame, qh->sched_frame)) + { + hcd->fiq_state->next_sched_frame = qh->sched_frame; + } + + DWC_LIST_MOVE_HEAD + (&hcd->periodic_sched_inactive, + &qh->qh_list_entry); + } + } + } +} + +/** + * This function allocates and initializes a QTD. + * + * @param urb The URB to create a QTD from. Each URB-QTD pair will end up + * pointing to each other so each pair should have a unique correlation. + * @param atomic_alloc Flag to do atomic alloc if needed + * + * @return Returns pointer to the newly allocated QTD, or NULL on error. */ +dwc_otg_qtd_t *dwc_otg_hcd_qtd_create(dwc_otg_hcd_urb_t * urb, int atomic_alloc) +{ + dwc_otg_qtd_t *qtd; + + qtd = dwc_otg_hcd_qtd_alloc(atomic_alloc); + if (qtd == NULL) { + return NULL; + } + + dwc_otg_hcd_qtd_init(qtd, urb); + return qtd; +} + +/** + * Initializes a QTD structure. + * + * @param qtd The QTD to initialize. + * @param urb The URB to use for initialization. */ +void dwc_otg_hcd_qtd_init(dwc_otg_qtd_t * qtd, dwc_otg_hcd_urb_t * urb) +{ + dwc_memset(qtd, 0, sizeof(dwc_otg_qtd_t)); + qtd->urb = urb; + if (dwc_otg_hcd_get_pipe_type(&urb->pipe_info) == UE_CONTROL) { + /* + * The only time the QTD data toggle is used is on the data + * phase of control transfers. This phase always starts with + * DATA1. + */ + qtd->data_toggle = DWC_OTG_HC_PID_DATA1; + qtd->control_phase = DWC_OTG_CONTROL_SETUP; + } + + /* start split */ + qtd->complete_split = 0; + qtd->isoc_split_pos = DWC_HCSPLIT_XACTPOS_ALL; + qtd->isoc_split_offset = 0; + qtd->in_process = 0; + + /* Store the qtd ptr in the urb to reference what QTD. */ + urb->qtd = qtd; + return; +} + +/** + * This function adds a QTD to the QTD-list of a QH. It will find the correct + * QH to place the QTD into. If it does not find a QH, then it will create a + * new QH. If the QH to which the QTD is added is not currently scheduled, it + * is placed into the proper schedule based on its EP type. + * HCD lock must be held and interrupts must be disabled on entry + * + * @param[in] qtd The QTD to add + * @param[in] hcd The DWC HCD structure + * @param[out] qh out parameter to return queue head + * @param atomic_alloc Flag to do atomic alloc if needed + * + * @return 0 if successful, negative error code otherwise. + */ +int dwc_otg_hcd_qtd_add(dwc_otg_qtd_t * qtd, + dwc_otg_hcd_t * hcd, dwc_otg_qh_t ** qh, int atomic_alloc) +{ + int retval = 0; + dwc_otg_hcd_urb_t *urb = qtd->urb; + + /* + * Get the QH which holds the QTD-list to insert to. Create QH if it + * doesn't exist. + */ + if (*qh == NULL) { + *qh = dwc_otg_hcd_qh_create(hcd, urb, atomic_alloc); + if (*qh == NULL) { + retval = -DWC_E_NO_MEMORY; + goto done; + } else { + if (fiq_enable) + hcd->fiq_state->kick_np_queues = 1; + } + } + retval = dwc_otg_hcd_qh_add(hcd, *qh); + if (retval == 0) { + DWC_CIRCLEQ_INSERT_TAIL(&((*qh)->qtd_list), qtd, + qtd_list_entry); + qtd->qh = *qh; + } +done: + + return retval; +} + +#endif /* DWC_DEVICE_ONLY */ diff --git a/drivers/usb/host/dwc_otg/dwc_otg_os_dep.h b/drivers/usb/host/dwc_otg/dwc_otg_os_dep.h new file mode 100644 index 00000000000000..0bfa981571696e --- /dev/null +++ b/drivers/usb/host/dwc_otg/dwc_otg_os_dep.h @@ -0,0 +1,200 @@ +#ifndef _DWC_OS_DEP_H_ +#define _DWC_OS_DEP_H_ + +/** + * @file + * + * This file contains OS dependent structures. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/list.h> +#include <linux/interrupt.h> +#include <linux/ctype.h> +#include <linux/string.h> +#include <linux/dma-mapping.h> +#include <linux/jiffies.h> +#include <linux/delay.h> +#include <linux/timer.h> +#include <linux/workqueue.h> +#include <linux/stat.h> +#include <linux/pci.h> +#include <linux/compiler.h> + +#include <linux/version.h> + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,20) +# include <linux/irq.h> +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,21) +# include <linux/usb/ch9.h> +#else +# include <linux/usb_ch9.h> +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24) +# include <linux/usb/gadget.h> +#else +# include <linux/usb_gadget.h> +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) +# include <asm/irq.h> +#endif + +#ifdef PCI_INTERFACE +# include <asm/io.h> +#endif + +#ifdef LM_INTERFACE +# include <linux/unaligned.h> +# include <asm/sizes.h> +# include <asm/param.h> +# include <asm/io.h> +# if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,30)) +# include <asm/arch/hardware.h> +# include <asm/arch/lm.h> +# include <asm/arch/irqs.h> +# include <asm/arch/regs-irq.h> +# else +/* in 2.6.31, at least, we seem to have lost the generic LM infrastructure - + here we assume that the machine architecture provides definitions + in its own header +*/ +# include <mach/lm.h> +# include <mach/hardware.h> +# endif +#endif + +#ifdef PLATFORM_INTERFACE +#include <linux/platform_device.h> +#ifdef CONFIG_ARM +#include <asm/mach/map.h> +#endif +#endif + +/** The OS page size */ +#define DWC_OS_PAGE_SIZE PAGE_SIZE + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,14) +typedef int gfp_t; +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18) +# define IRQF_SHARED SA_SHIRQ +#endif + +typedef struct os_dependent { + /** Base address returned from ioremap() */ + void *base; + + /** Register offset for Diagnostic API */ + uint32_t reg_offset; + + /** Base address for MPHI peripheral */ + void *mphi_base; + + /** mphi_base actually points to the SWIRQ block */ + bool use_swirq; + + /** IRQ number (<0 if not valid) */ + int irq_num; + + /** FIQ number (<0 if not valid) */ + int fiq_num; + +#ifdef LM_INTERFACE + struct lm_device *lmdev; +#elif defined(PCI_INTERFACE) + struct pci_dev *pcidev; + + /** Start address of a PCI region */ + resource_size_t rsrc_start; + + /** Length address of a PCI region */ + resource_size_t rsrc_len; +#elif defined(PLATFORM_INTERFACE) + struct platform_device *platformdev; +#endif + +} os_dependent_t; + +#ifdef __cplusplus +} +#endif + + + +/* Type for the our device on the chosen bus */ +#if defined(LM_INTERFACE) +typedef struct lm_device dwc_bus_dev_t; +#elif defined(PCI_INTERFACE) +typedef struct pci_dev dwc_bus_dev_t; +#elif defined(PLATFORM_INTERFACE) +typedef struct platform_device dwc_bus_dev_t; +#endif + +/* Helper macro to retrieve drvdata from the device on the chosen bus */ +#if defined(LM_INTERFACE) +#define DWC_OTG_BUSDRVDATA(_dev) lm_get_drvdata(_dev) +#elif defined(PCI_INTERFACE) +#define DWC_OTG_BUSDRVDATA(_dev) pci_get_drvdata(_dev) +#elif defined(PLATFORM_INTERFACE) +#define DWC_OTG_BUSDRVDATA(_dev) platform_get_drvdata(_dev) +#endif + +/** + * Helper macro returning the otg_device structure of a given struct device + * + * c.f. static dwc_otg_device_t *dwc_otg_drvdev(struct device *_dev) + */ +#ifdef LM_INTERFACE +#define DWC_OTG_GETDRVDEV(_var, _dev) do { \ + struct lm_device *lm_dev = \ + container_of(_dev, struct lm_device, dev); \ + _var = lm_get_drvdata(lm_dev); \ + } while (0) + +#elif defined(PCI_INTERFACE) +#define DWC_OTG_GETDRVDEV(_var, _dev) do { \ + _var = dev_get_drvdata(_dev); \ + } while (0) + +#elif defined(PLATFORM_INTERFACE) +#define DWC_OTG_GETDRVDEV(_var, _dev) do { \ + struct platform_device *platform_dev = \ + container_of(_dev, struct platform_device, dev); \ + _var = platform_get_drvdata(platform_dev); \ + } while (0) +#endif + + +/** + * Helper macro returning the struct dev of the given struct os_dependent + * + * c.f. static struct device *dwc_otg_getdev(struct os_dependent *osdep) + */ +#ifdef LM_INTERFACE +#define DWC_OTG_OS_GETDEV(_osdep) \ + ((_osdep).lmdev == NULL? NULL: &(_osdep).lmdev->dev) +#elif defined(PCI_INTERFACE) +#define DWC_OTG_OS_GETDEV(_osdep) \ + ((_osdep).pci_dev == NULL? NULL: &(_osdep).pci_dev->dev) +#elif defined(PLATFORM_INTERFACE) +#define DWC_OTG_OS_GETDEV(_osdep) \ + ((_osdep).platformdev == NULL? NULL: &(_osdep).platformdev->dev) +#endif + + + + +#endif /* _DWC_OS_DEP_H_ */ diff --git a/drivers/usb/host/dwc_otg/dwc_otg_pcd.c b/drivers/usb/host/dwc_otg/dwc_otg_pcd.c new file mode 100644 index 00000000000000..ec2d45167a0fe6 --- /dev/null +++ b/drivers/usb/host/dwc_otg/dwc_otg_pcd.c @@ -0,0 +1,2723 @@ +/* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_pcd.c $ + * $Revision: #101 $ + * $Date: 2012/08/10 $ + * $Change: 2047372 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ +#ifndef DWC_HOST_ONLY + +/** @file + * This file implements PCD Core. All code in this file is portable and doesn't + * use any OS specific functions. + * PCD Core provides Interface, defined in <code><dwc_otg_pcd_if.h></code> + * header file, which can be used to implement OS specific PCD interface. + * + * An important function of the PCD is managing interrupts generated + * by the DWC_otg controller. The implementation of the DWC_otg device + * mode interrupt service routines is in dwc_otg_pcd_intr.c. + * + * @todo Add Device Mode test modes (Test J mode, Test K mode, etc). + * @todo Does it work when the request size is greater than DEPTSIZ + * transfer size + * + */ + +#include "dwc_otg_pcd.h" + +#ifdef DWC_UTE_CFI +#include "dwc_otg_cfi.h" + +extern int init_cfi(cfiobject_t * cfiobj); +#endif + +/** + * Choose endpoint from ep arrays using usb_ep structure. + */ +static dwc_otg_pcd_ep_t *get_ep_from_handle(dwc_otg_pcd_t * pcd, void *handle) +{ + int i; + if (pcd->ep0.priv == handle) { + return &pcd->ep0; + } + for (i = 0; i < MAX_EPS_CHANNELS - 1; i++) { + if (pcd->in_ep[i].priv == handle) + return &pcd->in_ep[i]; + if (pcd->out_ep[i].priv == handle) + return &pcd->out_ep[i]; + } + + return NULL; +} + +/** + * This function completes a request. It call's the request call back. + */ +void dwc_otg_request_done(dwc_otg_pcd_ep_t * ep, dwc_otg_pcd_request_t * req, + int32_t status) +{ + unsigned stopped = ep->stopped; + + DWC_DEBUGPL(DBG_PCDV, "%s(ep %p req %p)\n", __func__, ep, req); + DWC_CIRCLEQ_REMOVE_INIT(&ep->queue, req, queue_entry); + + /* don't modify queue heads during completion callback */ + ep->stopped = 1; + /* spin_unlock/spin_lock now done in fops->complete() */ + ep->pcd->fops->complete(ep->pcd, ep->priv, req->priv, status, + req->actual); + + if (ep->pcd->request_pending > 0) { + --ep->pcd->request_pending; + } + + ep->stopped = stopped; + DWC_FREE(req); +} + +/** + * This function terminates all the requsts in the EP request queue. + */ +void dwc_otg_request_nuke(dwc_otg_pcd_ep_t * ep) +{ + dwc_otg_pcd_request_t *req; + + ep->stopped = 1; + + /* called with irqs blocked?? */ + while (!DWC_CIRCLEQ_EMPTY(&ep->queue)) { + req = DWC_CIRCLEQ_FIRST(&ep->queue); + dwc_otg_request_done(ep, req, -DWC_E_SHUTDOWN); + } +} + +void dwc_otg_pcd_start(dwc_otg_pcd_t * pcd, + const struct dwc_otg_pcd_function_ops *fops) +{ + pcd->fops = fops; +} + +/** + * PCD Callback function for initializing the PCD when switching to + * device mode. + * + * @param p void pointer to the <code>dwc_otg_pcd_t</code> + */ +static int32_t dwc_otg_pcd_start_cb(void *p) +{ + dwc_otg_pcd_t *pcd = (dwc_otg_pcd_t *) p; + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + + /* + * Initialized the Core for Device mode. + */ + if (dwc_otg_is_device_mode(core_if)) { + dwc_otg_core_dev_init(core_if); + /* Set core_if's lock pointer to the pcd->lock */ + core_if->lock = pcd->lock; + } + return 1; +} + +/** CFI-specific buffer allocation function for EP */ +#ifdef DWC_UTE_CFI +uint8_t *cfiw_ep_alloc_buffer(dwc_otg_pcd_t * pcd, void *pep, dwc_dma_t * addr, + size_t buflen, int flags) +{ + dwc_otg_pcd_ep_t *ep; + ep = get_ep_from_handle(pcd, pep); + if (!ep) { + DWC_WARN("bad ep\n"); + return -DWC_E_INVALID; + } + + return pcd->cfi->ops.ep_alloc_buf(pcd->cfi, pcd, ep, addr, buflen, + flags); +} +#else +uint8_t *cfiw_ep_alloc_buffer(dwc_otg_pcd_t * pcd, void *pep, dwc_dma_t * addr, + size_t buflen, int flags); +#endif + +/** + * PCD Callback function for notifying the PCD when resuming from + * suspend. + * + * @param p void pointer to the <code>dwc_otg_pcd_t</code> + */ +static int32_t dwc_otg_pcd_resume_cb(void *p) +{ + dwc_otg_pcd_t *pcd = (dwc_otg_pcd_t *) p; + + if (pcd->fops->resume) { + pcd->fops->resume(pcd); + } + + /* Stop the SRP timeout timer. */ + if ((GET_CORE_IF(pcd)->core_params->phy_type != DWC_PHY_TYPE_PARAM_FS) + || (!GET_CORE_IF(pcd)->core_params->i2c_enable)) { + if (GET_CORE_IF(pcd)->srp_timer_started) { + GET_CORE_IF(pcd)->srp_timer_started = 0; + DWC_TIMER_CANCEL(GET_CORE_IF(pcd)->srp_timer); + } + } + return 1; +} + +/** + * PCD Callback function for notifying the PCD device is suspended. + * + * @param p void pointer to the <code>dwc_otg_pcd_t</code> + */ +static int32_t dwc_otg_pcd_suspend_cb(void *p) +{ + dwc_otg_pcd_t *pcd = (dwc_otg_pcd_t *) p; + + if (pcd->fops->suspend) { + DWC_SPINUNLOCK(pcd->lock); + pcd->fops->suspend(pcd); + DWC_SPINLOCK(pcd->lock); + } + + return 1; +} + +/** + * PCD Callback function for stopping the PCD when switching to Host + * mode. + * + * @param p void pointer to the <code>dwc_otg_pcd_t</code> + */ +static int32_t dwc_otg_pcd_stop_cb(void *p) +{ + dwc_otg_pcd_t *pcd = (dwc_otg_pcd_t *) p; + extern void dwc_otg_pcd_stop(dwc_otg_pcd_t * _pcd); + + dwc_otg_pcd_stop(pcd); + return 1; +} + +/** + * PCD Callback structure for handling mode switching. + */ +static dwc_otg_cil_callbacks_t pcd_callbacks = { + .start = dwc_otg_pcd_start_cb, + .stop = dwc_otg_pcd_stop_cb, + .suspend = dwc_otg_pcd_suspend_cb, + .resume_wakeup = dwc_otg_pcd_resume_cb, + .p = 0, /* Set at registration */ +}; + +/** + * This function allocates a DMA Descriptor chain for the Endpoint + * buffer to be used for a transfer to/from the specified endpoint. + */ +static dwc_otg_dev_dma_desc_t *dwc_otg_ep_alloc_desc_chain(struct device *dev, + dwc_dma_t * dma_desc_addr, + uint32_t count) +{ + return DWC_DMA_ALLOC_ATOMIC(dev, count * sizeof(dwc_otg_dev_dma_desc_t), + dma_desc_addr); +} + +/** + * This function frees a DMA Descriptor chain that was allocated by ep_alloc_desc. + */ +static void dwc_otg_ep_free_desc_chain(struct device *dev, + dwc_otg_dev_dma_desc_t * desc_addr, + uint32_t dma_desc_addr, uint32_t count) +{ + DWC_DMA_FREE(dev, count * sizeof(dwc_otg_dev_dma_desc_t), desc_addr, + dma_desc_addr); +} + +#ifdef DWC_EN_ISOC + +/** + * This function initializes a descriptor chain for Isochronous transfer + * + * @param core_if Programming view of DWC_otg controller. + * @param dwc_ep The EP to start the transfer on. + * + */ +void dwc_otg_iso_ep_start_ddma_transfer(dwc_otg_core_if_t * core_if, + dwc_ep_t * dwc_ep) +{ + + dsts_data_t dsts = {.d32 = 0 }; + depctl_data_t depctl = {.d32 = 0 }; + volatile uint32_t *addr; + int i, j; + uint32_t len; + + if (dwc_ep->is_in) + dwc_ep->desc_cnt = dwc_ep->buf_proc_intrvl / dwc_ep->bInterval; + else + dwc_ep->desc_cnt = + dwc_ep->buf_proc_intrvl * dwc_ep->pkt_per_frm / + dwc_ep->bInterval; + + /** Allocate descriptors for double buffering */ + dwc_ep->iso_desc_addr = + dwc_otg_ep_alloc_desc_chain(&dwc_ep->iso_dma_desc_addr, + dwc_ep->desc_cnt * 2); + if (dwc_ep->desc_addr) { + DWC_WARN("%s, can't allocate DMA descriptor chain\n", __func__); + return; + } + + dsts.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dsts); + + /** ISO OUT EP */ + if (dwc_ep->is_in == 0) { + dev_dma_desc_sts_t sts = {.d32 = 0 }; + dwc_otg_dev_dma_desc_t *dma_desc = dwc_ep->iso_desc_addr; + dma_addr_t dma_ad; + uint32_t data_per_desc; + dwc_otg_dev_out_ep_regs_t *out_regs = + core_if->dev_if->out_ep_regs[dwc_ep->num]; + int offset; + + addr = &core_if->dev_if->out_ep_regs[dwc_ep->num]->doepctl; + dma_ad = (dma_addr_t) DWC_READ_REG32(&(out_regs->doepdma)); + + /** Buffer 0 descriptors setup */ + dma_ad = dwc_ep->dma_addr0; + + sts.b_iso_out.bs = BS_HOST_READY; + sts.b_iso_out.rxsts = 0; + sts.b_iso_out.l = 0; + sts.b_iso_out.sp = 0; + sts.b_iso_out.ioc = 0; + sts.b_iso_out.pid = 0; + sts.b_iso_out.framenum = 0; + + offset = 0; + for (i = 0; i < dwc_ep->desc_cnt - dwc_ep->pkt_per_frm; + i += dwc_ep->pkt_per_frm) { + + for (j = 0; j < dwc_ep->pkt_per_frm; ++j) { + uint32_t len = (j + 1) * dwc_ep->maxpacket; + if (len > dwc_ep->data_per_frame) + data_per_desc = + dwc_ep->data_per_frame - + j * dwc_ep->maxpacket; + else + data_per_desc = dwc_ep->maxpacket; + len = data_per_desc % 4; + if (len) + data_per_desc += 4 - len; + + sts.b_iso_out.rxbytes = data_per_desc; + dma_desc->buf = dma_ad; + dma_desc->status.d32 = sts.d32; + + offset += data_per_desc; + dma_desc++; + dma_ad += data_per_desc; + } + } + + for (j = 0; j < dwc_ep->pkt_per_frm - 1; ++j) { + uint32_t len = (j + 1) * dwc_ep->maxpacket; + if (len > dwc_ep->data_per_frame) + data_per_desc = + dwc_ep->data_per_frame - + j * dwc_ep->maxpacket; + else + data_per_desc = dwc_ep->maxpacket; + len = data_per_desc % 4; + if (len) + data_per_desc += 4 - len; + sts.b_iso_out.rxbytes = data_per_desc; + dma_desc->buf = dma_ad; + dma_desc->status.d32 = sts.d32; + + offset += data_per_desc; + dma_desc++; + dma_ad += data_per_desc; + } + + sts.b_iso_out.ioc = 1; + len = (j + 1) * dwc_ep->maxpacket; + if (len > dwc_ep->data_per_frame) + data_per_desc = + dwc_ep->data_per_frame - j * dwc_ep->maxpacket; + else + data_per_desc = dwc_ep->maxpacket; + len = data_per_desc % 4; + if (len) + data_per_desc += 4 - len; + sts.b_iso_out.rxbytes = data_per_desc; + + dma_desc->buf = dma_ad; + dma_desc->status.d32 = sts.d32; + dma_desc++; + + /** Buffer 1 descriptors setup */ + sts.b_iso_out.ioc = 0; + dma_ad = dwc_ep->dma_addr1; + + offset = 0; + for (i = 0; i < dwc_ep->desc_cnt - dwc_ep->pkt_per_frm; + i += dwc_ep->pkt_per_frm) { + for (j = 0; j < dwc_ep->pkt_per_frm; ++j) { + uint32_t len = (j + 1) * dwc_ep->maxpacket; + if (len > dwc_ep->data_per_frame) + data_per_desc = + dwc_ep->data_per_frame - + j * dwc_ep->maxpacket; + else + data_per_desc = dwc_ep->maxpacket; + len = data_per_desc % 4; + if (len) + data_per_desc += 4 - len; + + data_per_desc = + sts.b_iso_out.rxbytes = data_per_desc; + dma_desc->buf = dma_ad; + dma_desc->status.d32 = sts.d32; + + offset += data_per_desc; + dma_desc++; + dma_ad += data_per_desc; + } + } + for (j = 0; j < dwc_ep->pkt_per_frm - 1; ++j) { + data_per_desc = + ((j + 1) * dwc_ep->maxpacket > + dwc_ep->data_per_frame) ? dwc_ep->data_per_frame - + j * dwc_ep->maxpacket : dwc_ep->maxpacket; + data_per_desc += + (data_per_desc % 4) ? (4 - data_per_desc % 4) : 0; + sts.b_iso_out.rxbytes = data_per_desc; + dma_desc->buf = dma_ad; + dma_desc->status.d32 = sts.d32; + + offset += data_per_desc; + dma_desc++; + dma_ad += data_per_desc; + } + + sts.b_iso_out.ioc = 1; + sts.b_iso_out.l = 1; + data_per_desc = + ((j + 1) * dwc_ep->maxpacket > + dwc_ep->data_per_frame) ? dwc_ep->data_per_frame - + j * dwc_ep->maxpacket : dwc_ep->maxpacket; + data_per_desc += + (data_per_desc % 4) ? (4 - data_per_desc % 4) : 0; + sts.b_iso_out.rxbytes = data_per_desc; + + dma_desc->buf = dma_ad; + dma_desc->status.d32 = sts.d32; + + dwc_ep->next_frame = 0; + + /** Write dma_ad into DOEPDMA register */ + DWC_WRITE_REG32(&(out_regs->doepdma), + (uint32_t) dwc_ep->iso_dma_desc_addr); + + } + /** ISO IN EP */ + else { + dev_dma_desc_sts_t sts = {.d32 = 0 }; + dwc_otg_dev_dma_desc_t *dma_desc = dwc_ep->iso_desc_addr; + dma_addr_t dma_ad; + dwc_otg_dev_in_ep_regs_t *in_regs = + core_if->dev_if->in_ep_regs[dwc_ep->num]; + unsigned int frmnumber; + fifosize_data_t txfifosize, rxfifosize; + + txfifosize.d32 = + DWC_READ_REG32(&core_if->dev_if->in_ep_regs[dwc_ep->num]-> + dtxfsts); + rxfifosize.d32 = + DWC_READ_REG32(&core_if->core_global_regs->grxfsiz); + + addr = &core_if->dev_if->in_ep_regs[dwc_ep->num]->diepctl; + + dma_ad = dwc_ep->dma_addr0; + + dsts.d32 = + DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dsts); + + sts.b_iso_in.bs = BS_HOST_READY; + sts.b_iso_in.txsts = 0; + sts.b_iso_in.sp = + (dwc_ep->data_per_frame % dwc_ep->maxpacket) ? 1 : 0; + sts.b_iso_in.ioc = 0; + sts.b_iso_in.pid = dwc_ep->pkt_per_frm; + + frmnumber = dwc_ep->next_frame; + + sts.b_iso_in.framenum = frmnumber; + sts.b_iso_in.txbytes = dwc_ep->data_per_frame; + sts.b_iso_in.l = 0; + + /** Buffer 0 descriptors setup */ + for (i = 0; i < dwc_ep->desc_cnt - 1; i++) { + dma_desc->buf = dma_ad; + dma_desc->status.d32 = sts.d32; + dma_desc++; + + dma_ad += dwc_ep->data_per_frame; + sts.b_iso_in.framenum += dwc_ep->bInterval; + } + + sts.b_iso_in.ioc = 1; + dma_desc->buf = dma_ad; + dma_desc->status.d32 = sts.d32; + ++dma_desc; + + /** Buffer 1 descriptors setup */ + sts.b_iso_in.ioc = 0; + dma_ad = dwc_ep->dma_addr1; + + for (i = 0; i < dwc_ep->desc_cnt - dwc_ep->pkt_per_frm; + i += dwc_ep->pkt_per_frm) { + dma_desc->buf = dma_ad; + dma_desc->status.d32 = sts.d32; + dma_desc++; + + dma_ad += dwc_ep->data_per_frame; + sts.b_iso_in.framenum += dwc_ep->bInterval; + + sts.b_iso_in.ioc = 0; + } + sts.b_iso_in.ioc = 1; + sts.b_iso_in.l = 1; + + dma_desc->buf = dma_ad; + dma_desc->status.d32 = sts.d32; + + dwc_ep->next_frame = sts.b_iso_in.framenum + dwc_ep->bInterval; + + /** Write dma_ad into diepdma register */ + DWC_WRITE_REG32(&(in_regs->diepdma), + (uint32_t) dwc_ep->iso_dma_desc_addr); + } + /** Enable endpoint, clear nak */ + depctl.d32 = 0; + depctl.b.epena = 1; + depctl.b.usbactep = 1; + depctl.b.cnak = 1; + + DWC_MODIFY_REG32(addr, depctl.d32, depctl.d32); + depctl.d32 = DWC_READ_REG32(addr); +} + +/** + * This function initializes a descriptor chain for Isochronous transfer + * + * @param core_if Programming view of DWC_otg controller. + * @param ep The EP to start the transfer on. + * + */ +void dwc_otg_iso_ep_start_buf_transfer(dwc_otg_core_if_t * core_if, + dwc_ep_t * ep) +{ + depctl_data_t depctl = {.d32 = 0 }; + volatile uint32_t *addr; + + if (ep->is_in) { + addr = &core_if->dev_if->in_ep_regs[ep->num]->diepctl; + } else { + addr = &core_if->dev_if->out_ep_regs[ep->num]->doepctl; + } + + if (core_if->dma_enable == 0 || core_if->dma_desc_enable != 0) { + return; + } else { + deptsiz_data_t deptsiz = {.d32 = 0 }; + + ep->xfer_len = + ep->data_per_frame * ep->buf_proc_intrvl / ep->bInterval; + ep->pkt_cnt = + (ep->xfer_len - 1 + ep->maxpacket) / ep->maxpacket; + ep->xfer_count = 0; + ep->xfer_buff = + (ep->proc_buf_num) ? ep->xfer_buff1 : ep->xfer_buff0; + ep->dma_addr = + (ep->proc_buf_num) ? ep->dma_addr1 : ep->dma_addr0; + + if (ep->is_in) { + /* Program the transfer size and packet count + * as follows: xfersize = N * maxpacket + + * short_packet pktcnt = N + (short_packet + * exist ? 1 : 0) + */ + deptsiz.b.mc = ep->pkt_per_frm; + deptsiz.b.xfersize = ep->xfer_len; + deptsiz.b.pktcnt = + (ep->xfer_len - 1 + ep->maxpacket) / ep->maxpacket; + DWC_WRITE_REG32(&core_if->dev_if->in_ep_regs[ep->num]-> + dieptsiz, deptsiz.d32); + + /* Write the DMA register */ + DWC_WRITE_REG32(& + (core_if->dev_if->in_ep_regs[ep->num]-> + diepdma), (uint32_t) ep->dma_addr); + + } else { + deptsiz.b.pktcnt = + (ep->xfer_len + (ep->maxpacket - 1)) / + ep->maxpacket; + deptsiz.b.xfersize = deptsiz.b.pktcnt * ep->maxpacket; + + DWC_WRITE_REG32(&core_if->dev_if->out_ep_regs[ep->num]-> + doeptsiz, deptsiz.d32); + + /* Write the DMA register */ + DWC_WRITE_REG32(& + (core_if->dev_if->out_ep_regs[ep->num]-> + doepdma), (uint32_t) ep->dma_addr); + + } + /** Enable endpoint, clear nak */ + depctl.d32 = 0; + depctl.b.epena = 1; + depctl.b.cnak = 1; + + DWC_MODIFY_REG32(addr, depctl.d32, depctl.d32); + } +} + +/** + * This function does the setup for a data transfer for an EP and + * starts the transfer. For an IN transfer, the packets will be + * loaded into the appropriate Tx FIFO in the ISR. For OUT transfers, + * the packets are unloaded from the Rx FIFO in the ISR. + * + * @param core_if Programming view of DWC_otg controller. + * @param ep The EP to start the transfer on. + */ + +static void dwc_otg_iso_ep_start_transfer(dwc_otg_core_if_t * core_if, + dwc_ep_t * ep) +{ + if (core_if->dma_enable) { + if (core_if->dma_desc_enable) { + if (ep->is_in) { + ep->desc_cnt = ep->pkt_cnt / ep->pkt_per_frm; + } else { + ep->desc_cnt = ep->pkt_cnt; + } + dwc_otg_iso_ep_start_ddma_transfer(core_if, ep); + } else { + if (core_if->pti_enh_enable) { + dwc_otg_iso_ep_start_buf_transfer(core_if, ep); + } else { + ep->cur_pkt_addr = + (ep->proc_buf_num) ? ep->xfer_buff1 : ep-> + xfer_buff0; + ep->cur_pkt_dma_addr = + (ep->proc_buf_num) ? ep->dma_addr1 : ep-> + dma_addr0; + dwc_otg_iso_ep_start_frm_transfer(core_if, ep); + } + } + } else { + ep->cur_pkt_addr = + (ep->proc_buf_num) ? ep->xfer_buff1 : ep->xfer_buff0; + ep->cur_pkt_dma_addr = + (ep->proc_buf_num) ? ep->dma_addr1 : ep->dma_addr0; + dwc_otg_iso_ep_start_frm_transfer(core_if, ep); + } +} + +/** + * This function stops transfer for an EP and + * resets the ep's variables. + * + * @param core_if Programming view of DWC_otg controller. + * @param ep The EP to start the transfer on. + */ + +void dwc_otg_iso_ep_stop_transfer(dwc_otg_core_if_t * core_if, dwc_ep_t * ep) +{ + depctl_data_t depctl = {.d32 = 0 }; + volatile uint32_t *addr; + + if (ep->is_in == 1) { + addr = &core_if->dev_if->in_ep_regs[ep->num]->diepctl; + } else { + addr = &core_if->dev_if->out_ep_regs[ep->num]->doepctl; + } + + /* disable the ep */ + depctl.d32 = DWC_READ_REG32(addr); + + depctl.b.epdis = 1; + depctl.b.snak = 1; + + DWC_WRITE_REG32(addr, depctl.d32); + + if (core_if->dma_desc_enable && + ep->iso_desc_addr && ep->iso_dma_desc_addr) { + dwc_otg_ep_free_desc_chain(ep->iso_desc_addr, + ep->iso_dma_desc_addr, + ep->desc_cnt * 2); + } + + /* reset varibales */ + ep->dma_addr0 = 0; + ep->dma_addr1 = 0; + ep->xfer_buff0 = 0; + ep->xfer_buff1 = 0; + ep->data_per_frame = 0; + ep->data_pattern_frame = 0; + ep->sync_frame = 0; + ep->buf_proc_intrvl = 0; + ep->bInterval = 0; + ep->proc_buf_num = 0; + ep->pkt_per_frm = 0; + ep->pkt_per_frm = 0; + ep->desc_cnt = 0; + ep->iso_desc_addr = 0; + ep->iso_dma_desc_addr = 0; +} + +int dwc_otg_pcd_iso_ep_start(dwc_otg_pcd_t * pcd, void *ep_handle, + uint8_t * buf0, uint8_t * buf1, dwc_dma_t dma0, + dwc_dma_t dma1, int sync_frame, int dp_frame, + int data_per_frame, int start_frame, + int buf_proc_intrvl, void *req_handle, + int atomic_alloc) +{ + dwc_otg_pcd_ep_t *ep; + dwc_irqflags_t flags = 0; + dwc_ep_t *dwc_ep; + int32_t frm_data; + dsts_data_t dsts; + dwc_otg_core_if_t *core_if; + + ep = get_ep_from_handle(pcd, ep_handle); + + if (!ep || !ep->desc || ep->dwc_ep.num == 0) { + DWC_WARN("bad ep\n"); + return -DWC_E_INVALID; + } + + DWC_SPINLOCK_IRQSAVE(pcd->lock, &flags); + core_if = GET_CORE_IF(pcd); + dwc_ep = &ep->dwc_ep; + + if (ep->iso_req_handle) { + DWC_WARN("ISO request in progress\n"); + } + + dwc_ep->dma_addr0 = dma0; + dwc_ep->dma_addr1 = dma1; + + dwc_ep->xfer_buff0 = buf0; + dwc_ep->xfer_buff1 = buf1; + + dwc_ep->data_per_frame = data_per_frame; + + /** @todo - pattern data support is to be implemented in the future */ + dwc_ep->data_pattern_frame = dp_frame; + dwc_ep->sync_frame = sync_frame; + + dwc_ep->buf_proc_intrvl = buf_proc_intrvl; + + dwc_ep->bInterval = 1 << (ep->desc->bInterval - 1); + + dwc_ep->proc_buf_num = 0; + + dwc_ep->pkt_per_frm = 0; + frm_data = ep->dwc_ep.data_per_frame; + while (frm_data > 0) { + dwc_ep->pkt_per_frm++; + frm_data -= ep->dwc_ep.maxpacket; + } + + dsts.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dsts); + + if (start_frame == -1) { + dwc_ep->next_frame = dsts.b.soffn + 1; + if (dwc_ep->bInterval != 1) { + dwc_ep->next_frame = + dwc_ep->next_frame + (dwc_ep->bInterval - 1 - + dwc_ep->next_frame % + dwc_ep->bInterval); + } + } else { + dwc_ep->next_frame = start_frame; + } + + if (!core_if->pti_enh_enable) { + dwc_ep->pkt_cnt = + dwc_ep->buf_proc_intrvl * dwc_ep->pkt_per_frm / + dwc_ep->bInterval; + } else { + dwc_ep->pkt_cnt = + (dwc_ep->data_per_frame * + (dwc_ep->buf_proc_intrvl / dwc_ep->bInterval) + - 1 + dwc_ep->maxpacket) / dwc_ep->maxpacket; + } + + if (core_if->dma_desc_enable) { + dwc_ep->desc_cnt = + dwc_ep->buf_proc_intrvl * dwc_ep->pkt_per_frm / + dwc_ep->bInterval; + } + + if (atomic_alloc) { + dwc_ep->pkt_info = + DWC_ALLOC_ATOMIC(sizeof(iso_pkt_info_t) * dwc_ep->pkt_cnt); + } else { + dwc_ep->pkt_info = + DWC_ALLOC(sizeof(iso_pkt_info_t) * dwc_ep->pkt_cnt); + } + if (!dwc_ep->pkt_info) { + DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags); + return -DWC_E_NO_MEMORY; + } + if (core_if->pti_enh_enable) { + dwc_memset(dwc_ep->pkt_info, 0, + sizeof(iso_pkt_info_t) * dwc_ep->pkt_cnt); + } + + dwc_ep->cur_pkt = 0; + ep->iso_req_handle = req_handle; + + DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags); + dwc_otg_iso_ep_start_transfer(core_if, dwc_ep); + return 0; +} + +int dwc_otg_pcd_iso_ep_stop(dwc_otg_pcd_t * pcd, void *ep_handle, + void *req_handle) +{ + dwc_irqflags_t flags = 0; + dwc_otg_pcd_ep_t *ep; + dwc_ep_t *dwc_ep; + + ep = get_ep_from_handle(pcd, ep_handle); + if (!ep || !ep->desc || ep->dwc_ep.num == 0) { + DWC_WARN("bad ep\n"); + return -DWC_E_INVALID; + } + dwc_ep = &ep->dwc_ep; + + dwc_otg_iso_ep_stop_transfer(GET_CORE_IF(pcd), dwc_ep); + + DWC_FREE(dwc_ep->pkt_info); + DWC_SPINLOCK_IRQSAVE(pcd->lock, &flags); + if (ep->iso_req_handle != req_handle) { + DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags); + return -DWC_E_INVALID; + } + + DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags); + + ep->iso_req_handle = 0; + return 0; +} + +/** + * This function is used for perodical data exchnage between PCD and gadget drivers. + * for Isochronous EPs + * + * - Every time a sync period completes this function is called to + * perform data exchange between PCD and gadget + */ +void dwc_otg_iso_buffer_done(dwc_otg_pcd_t * pcd, dwc_otg_pcd_ep_t * ep, + void *req_handle) +{ + int i; + dwc_ep_t *dwc_ep; + + dwc_ep = &ep->dwc_ep; + + DWC_SPINUNLOCK(ep->pcd->lock); + pcd->fops->isoc_complete(pcd, ep->priv, ep->iso_req_handle, + dwc_ep->proc_buf_num ^ 0x1); + DWC_SPINLOCK(ep->pcd->lock); + + for (i = 0; i < dwc_ep->pkt_cnt; ++i) { + dwc_ep->pkt_info[i].status = 0; + dwc_ep->pkt_info[i].offset = 0; + dwc_ep->pkt_info[i].length = 0; + } +} + +int dwc_otg_pcd_get_iso_packet_count(dwc_otg_pcd_t * pcd, void *ep_handle, + void *iso_req_handle) +{ + dwc_otg_pcd_ep_t *ep; + dwc_ep_t *dwc_ep; + + ep = get_ep_from_handle(pcd, ep_handle); + if (!ep->desc || ep->dwc_ep.num == 0) { + DWC_WARN("bad ep\n"); + return -DWC_E_INVALID; + } + dwc_ep = &ep->dwc_ep; + + return dwc_ep->pkt_cnt; +} + +void dwc_otg_pcd_get_iso_packet_params(dwc_otg_pcd_t * pcd, void *ep_handle, + void *iso_req_handle, int packet, + int *status, int *actual, int *offset) +{ + dwc_otg_pcd_ep_t *ep; + dwc_ep_t *dwc_ep; + + ep = get_ep_from_handle(pcd, ep_handle); + if (!ep) + DWC_WARN("bad ep\n"); + + dwc_ep = &ep->dwc_ep; + + *status = dwc_ep->pkt_info[packet].status; + *actual = dwc_ep->pkt_info[packet].length; + *offset = dwc_ep->pkt_info[packet].offset; +} + +#endif /* DWC_EN_ISOC */ + +static void dwc_otg_pcd_init_ep(dwc_otg_pcd_t * pcd, dwc_otg_pcd_ep_t * pcd_ep, + uint32_t is_in, uint32_t ep_num) +{ + /* Init EP structure */ + pcd_ep->desc = 0; + pcd_ep->pcd = pcd; + pcd_ep->stopped = 1; + pcd_ep->queue_sof = 0; + + /* Init DWC ep structure */ + pcd_ep->dwc_ep.is_in = is_in; + pcd_ep->dwc_ep.num = ep_num; + pcd_ep->dwc_ep.active = 0; + pcd_ep->dwc_ep.tx_fifo_num = 0; + /* Control until ep is actvated */ + pcd_ep->dwc_ep.type = DWC_OTG_EP_TYPE_CONTROL; + pcd_ep->dwc_ep.maxpacket = MAX_PACKET_SIZE; + pcd_ep->dwc_ep.dma_addr = 0; + pcd_ep->dwc_ep.start_xfer_buff = 0; + pcd_ep->dwc_ep.xfer_buff = 0; + pcd_ep->dwc_ep.xfer_len = 0; + pcd_ep->dwc_ep.xfer_count = 0; + pcd_ep->dwc_ep.sent_zlp = 0; + pcd_ep->dwc_ep.total_len = 0; + pcd_ep->dwc_ep.desc_addr = 0; + pcd_ep->dwc_ep.dma_desc_addr = 0; + DWC_CIRCLEQ_INIT(&pcd_ep->queue); +} + +/** + * Initialize ep's + */ +static void dwc_otg_pcd_reinit(dwc_otg_pcd_t * pcd) +{ + int i; + uint32_t hwcfg1; + dwc_otg_pcd_ep_t *ep; + int in_ep_cntr, out_ep_cntr; + uint32_t num_in_eps = (GET_CORE_IF(pcd))->dev_if->num_in_eps; + uint32_t num_out_eps = (GET_CORE_IF(pcd))->dev_if->num_out_eps; + + /** + * Initialize the EP0 structure. + */ + ep = &pcd->ep0; + dwc_otg_pcd_init_ep(pcd, ep, 0, 0); + + in_ep_cntr = 0; + hwcfg1 = (GET_CORE_IF(pcd))->hwcfg1.d32 >> 3; + for (i = 1; in_ep_cntr < num_in_eps; i++) { + if ((hwcfg1 & 0x1) == 0) { + dwc_otg_pcd_ep_t *ep = &pcd->in_ep[in_ep_cntr]; + in_ep_cntr++; + /** + * @todo NGS: Add direction to EP, based on contents + * of HWCFG1. Need a copy of HWCFG1 in pcd structure? + * sprintf(";r + */ + dwc_otg_pcd_init_ep(pcd, ep, 1 /* IN */ , i); + + DWC_CIRCLEQ_INIT(&ep->queue); + } + hwcfg1 >>= 2; + } + + out_ep_cntr = 0; + hwcfg1 = (GET_CORE_IF(pcd))->hwcfg1.d32 >> 2; + for (i = 1; out_ep_cntr < num_out_eps; i++) { + if ((hwcfg1 & 0x1) == 0) { + dwc_otg_pcd_ep_t *ep = &pcd->out_ep[out_ep_cntr]; + out_ep_cntr++; + /** + * @todo NGS: Add direction to EP, based on contents + * of HWCFG1. Need a copy of HWCFG1 in pcd structure? + * sprintf(";r + */ + dwc_otg_pcd_init_ep(pcd, ep, 0 /* OUT */ , i); + DWC_CIRCLEQ_INIT(&ep->queue); + } + hwcfg1 >>= 2; + } + + pcd->ep0state = EP0_DISCONNECT; + pcd->ep0.dwc_ep.maxpacket = MAX_EP0_SIZE; + pcd->ep0.dwc_ep.type = DWC_OTG_EP_TYPE_CONTROL; +} + +/** + * This function is called when the SRP timer expires. The SRP should + * complete within 6 seconds. + */ +static void srp_timeout(void *ptr) +{ + gotgctl_data_t gotgctl; + dwc_otg_core_if_t *core_if = (dwc_otg_core_if_t *) ptr; + volatile uint32_t *addr = &core_if->core_global_regs->gotgctl; + + gotgctl.d32 = DWC_READ_REG32(addr); + + core_if->srp_timer_started = 0; + + if (core_if->adp_enable) { + if (gotgctl.b.bsesvld == 0) { + gpwrdn_data_t gpwrdn = {.d32 = 0 }; + DWC_PRINTF("SRP Timeout BSESSVLD = 0\n"); + /* Power off the core */ + if (core_if->power_down == 2) { + gpwrdn.b.pwrdnswtch = 1; + DWC_MODIFY_REG32(&core_if-> + core_global_regs->gpwrdn, + gpwrdn.d32, 0); + } + + gpwrdn.d32 = 0; + gpwrdn.b.pmuintsel = 1; + gpwrdn.b.pmuactv = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, + gpwrdn.d32); + dwc_otg_adp_probe_start(core_if); + } else { + DWC_PRINTF("SRP Timeout BSESSVLD = 1\n"); + core_if->op_state = B_PERIPHERAL; + dwc_otg_core_init(core_if); + dwc_otg_enable_global_interrupts(core_if); + cil_pcd_start(core_if); + } + } + + if ((core_if->core_params->phy_type == DWC_PHY_TYPE_PARAM_FS) && + (core_if->core_params->i2c_enable)) { + DWC_PRINTF("SRP Timeout\n"); + + if ((core_if->srp_success) && (gotgctl.b.bsesvld)) { + if (core_if->pcd_cb && core_if->pcd_cb->resume_wakeup) { + core_if->pcd_cb->resume_wakeup(core_if->pcd_cb->p); + } + + /* Clear Session Request */ + gotgctl.d32 = 0; + gotgctl.b.sesreq = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gotgctl, + gotgctl.d32, 0); + + core_if->srp_success = 0; + } else { + __DWC_ERROR("Device not connected/responding\n"); + gotgctl.b.sesreq = 0; + DWC_WRITE_REG32(addr, gotgctl.d32); + } + } else if (gotgctl.b.sesreq) { + DWC_PRINTF("SRP Timeout\n"); + + __DWC_ERROR("Device not connected/responding\n"); + gotgctl.b.sesreq = 0; + DWC_WRITE_REG32(addr, gotgctl.d32); + } else { + DWC_PRINTF(" SRP GOTGCTL=%0x\n", gotgctl.d32); + } +} + +/** + * Tasklet + * + */ +static void start_xfer_tasklet_func(void *data) +{ + dwc_otg_pcd_t *pcd = (dwc_otg_pcd_t *) data; + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + + int i; + depctl_data_t diepctl; + + DWC_DEBUGPL(DBG_PCDV, "Start xfer tasklet\n"); + + diepctl.d32 = DWC_READ_REG32(&core_if->dev_if->in_ep_regs[0]->diepctl); + + if (pcd->ep0.queue_sof) { + pcd->ep0.queue_sof = 0; + start_next_request(&pcd->ep0); + // break; + } + + for (i = 0; i < core_if->dev_if->num_in_eps; i++) { + depctl_data_t diepctl; + diepctl.d32 = + DWC_READ_REG32(&core_if->dev_if->in_ep_regs[i]->diepctl); + + if (pcd->in_ep[i].queue_sof) { + pcd->in_ep[i].queue_sof = 0; + start_next_request(&pcd->in_ep[i]); + // break; + } + } + + return; +} + +/** + * This function initialized the PCD portion of the driver. + * + */ +dwc_otg_pcd_t *dwc_otg_pcd_init(dwc_otg_device_t *otg_dev) +{ + struct device *dev = &otg_dev->os_dep.platformdev->dev; + dwc_otg_core_if_t *core_if = otg_dev->core_if; + dwc_otg_pcd_t *pcd = NULL; + dwc_otg_dev_if_t *dev_if; + int i; + + /* + * Allocate PCD structure + */ + pcd = DWC_ALLOC(sizeof(dwc_otg_pcd_t)); + + if (pcd == NULL) { + return NULL; + } + +#if (defined(DWC_LINUX) && defined(CONFIG_DEBUG_SPINLOCK)) + DWC_SPINLOCK_ALLOC_LINUX_DEBUG(pcd->lock); +#else + pcd->lock = DWC_SPINLOCK_ALLOC(); +#endif + DWC_DEBUGPL(DBG_HCDV, "Init of PCD %p given core_if %p\n", + pcd, core_if);//GRAYG + if (!pcd->lock) { + DWC_ERROR("Could not allocate lock for pcd"); + DWC_FREE(pcd); + return NULL; + } + /* Set core_if's lock pointer to hcd->lock */ + core_if->lock = pcd->lock; + pcd->core_if = core_if; + + dev_if = core_if->dev_if; + dev_if->isoc_ep = NULL; + + if (core_if->hwcfg4.b.ded_fifo_en) { + DWC_PRINTF("Dedicated Tx FIFOs mode\n"); + } else { + DWC_PRINTF("Shared Tx FIFO mode\n"); + } + + /* + * Initialized the Core for Device mode here if there is nod ADP support. + * Otherwise it will be done later in dwc_otg_adp_start routine. + */ + if (dwc_otg_is_device_mode(core_if) /*&& !core_if->adp_enable*/) { + dwc_otg_core_dev_init(core_if); + } + + /* + * Register the PCD Callbacks. + */ + dwc_otg_cil_register_pcd_callbacks(core_if, &pcd_callbacks, pcd); + + /* + * Initialize the DMA buffer for SETUP packets + */ + if (GET_CORE_IF(pcd)->dma_enable) { + pcd->setup_pkt = + DWC_DMA_ALLOC(dev, sizeof(*pcd->setup_pkt) * 5, + &pcd->setup_pkt_dma_handle); + if (pcd->setup_pkt == NULL) { + DWC_FREE(pcd); + return NULL; + } + + pcd->status_buf = + DWC_DMA_ALLOC(dev, sizeof(uint16_t), + &pcd->status_buf_dma_handle); + if (pcd->status_buf == NULL) { + DWC_DMA_FREE(dev, sizeof(*pcd->setup_pkt) * 5, + pcd->setup_pkt, pcd->setup_pkt_dma_handle); + DWC_FREE(pcd); + return NULL; + } + + if (GET_CORE_IF(pcd)->dma_desc_enable) { + dev_if->setup_desc_addr[0] = + dwc_otg_ep_alloc_desc_chain(dev, + &dev_if->dma_setup_desc_addr[0], 1); + dev_if->setup_desc_addr[1] = + dwc_otg_ep_alloc_desc_chain(dev, + &dev_if->dma_setup_desc_addr[1], 1); + dev_if->in_desc_addr = + dwc_otg_ep_alloc_desc_chain(dev, + &dev_if->dma_in_desc_addr, 1); + dev_if->out_desc_addr = + dwc_otg_ep_alloc_desc_chain(dev, + &dev_if->dma_out_desc_addr, 1); + pcd->data_terminated = 0; + + if (dev_if->setup_desc_addr[0] == 0 + || dev_if->setup_desc_addr[1] == 0 + || dev_if->in_desc_addr == 0 + || dev_if->out_desc_addr == 0) { + + if (dev_if->out_desc_addr) + dwc_otg_ep_free_desc_chain(dev, + dev_if->out_desc_addr, + dev_if->dma_out_desc_addr, 1); + if (dev_if->in_desc_addr) + dwc_otg_ep_free_desc_chain(dev, + dev_if->in_desc_addr, + dev_if->dma_in_desc_addr, 1); + if (dev_if->setup_desc_addr[1]) + dwc_otg_ep_free_desc_chain(dev, + dev_if->setup_desc_addr[1], + dev_if->dma_setup_desc_addr[1], 1); + if (dev_if->setup_desc_addr[0]) + dwc_otg_ep_free_desc_chain(dev, + dev_if->setup_desc_addr[0], + dev_if->dma_setup_desc_addr[0], 1); + + DWC_DMA_FREE(dev, sizeof(*pcd->setup_pkt) * 5, + pcd->setup_pkt, + pcd->setup_pkt_dma_handle); + DWC_DMA_FREE(dev, sizeof(*pcd->status_buf), + pcd->status_buf, + pcd->status_buf_dma_handle); + + DWC_FREE(pcd); + + return NULL; + } + } + } else { + pcd->setup_pkt = DWC_ALLOC(sizeof(*pcd->setup_pkt) * 5); + if (pcd->setup_pkt == NULL) { + DWC_FREE(pcd); + return NULL; + } + + pcd->status_buf = DWC_ALLOC(sizeof(uint16_t)); + if (pcd->status_buf == NULL) { + DWC_FREE(pcd->setup_pkt); + DWC_FREE(pcd); + return NULL; + } + } + + dwc_otg_pcd_reinit(pcd); + + /* Allocate the cfi object for the PCD */ +#ifdef DWC_UTE_CFI + pcd->cfi = DWC_ALLOC(sizeof(cfiobject_t)); + if (NULL == pcd->cfi) + goto fail; + if (init_cfi(pcd->cfi)) { + CFI_INFO("%s: Failed to init the CFI object\n", __func__); + goto fail; + } +#endif + + /* Initialize tasklets */ + pcd->start_xfer_tasklet = DWC_TASK_ALLOC("xfer_tasklet", + start_xfer_tasklet_func, pcd); + pcd->test_mode_tasklet = DWC_TASK_ALLOC("test_mode_tasklet", + do_test_mode, pcd); + + /* Initialize SRP timer */ + core_if->srp_timer = DWC_TIMER_ALLOC("SRP TIMER", srp_timeout, core_if); + + if (core_if->core_params->dev_out_nak) { + /** + * Initialize xfer timeout timer. Implemented for + * 2.93a feature "Device DDMA OUT NAK Enhancement" + */ + for(i = 0; i < MAX_EPS_CHANNELS; i++) { + pcd->core_if->ep_xfer_timer[i] = + DWC_TIMER_ALLOC("ep timer", ep_xfer_timeout, + &pcd->core_if->ep_xfer_info[i]); + } + } + + return pcd; +#ifdef DWC_UTE_CFI +fail: +#endif + if (pcd->setup_pkt) + DWC_FREE(pcd->setup_pkt); + if (pcd->status_buf) + DWC_FREE(pcd->status_buf); +#ifdef DWC_UTE_CFI + if (pcd->cfi) + DWC_FREE(pcd->cfi); +#endif + if (pcd) + DWC_FREE(pcd); + return NULL; + +} + +/** + * Remove PCD specific data + */ +void dwc_otg_pcd_remove(dwc_otg_pcd_t * pcd) +{ + dwc_otg_dev_if_t *dev_if = GET_CORE_IF(pcd)->dev_if; + struct device *dev = dwc_otg_pcd_to_dev(pcd); + int i; + + if (pcd->core_if->core_params->dev_out_nak) { + for (i = 0; i < MAX_EPS_CHANNELS; i++) { + DWC_TIMER_CANCEL(pcd->core_if->ep_xfer_timer[i]); + pcd->core_if->ep_xfer_info[i].state = 0; + } + } + + if (GET_CORE_IF(pcd)->dma_enable) { + DWC_DMA_FREE(dev, sizeof(*pcd->setup_pkt) * 5, pcd->setup_pkt, + pcd->setup_pkt_dma_handle); + DWC_DMA_FREE(dev, sizeof(uint16_t), pcd->status_buf, + pcd->status_buf_dma_handle); + if (GET_CORE_IF(pcd)->dma_desc_enable) { + dwc_otg_ep_free_desc_chain(dev, + dev_if->setup_desc_addr[0], + dev_if->dma_setup_desc_addr + [0], 1); + dwc_otg_ep_free_desc_chain(dev, + dev_if->setup_desc_addr[1], + dev_if->dma_setup_desc_addr + [1], 1); + dwc_otg_ep_free_desc_chain(dev, + dev_if->in_desc_addr, + dev_if->dma_in_desc_addr, 1); + dwc_otg_ep_free_desc_chain(dev, + dev_if->out_desc_addr, + dev_if->dma_out_desc_addr, + 1); + } + } else { + DWC_FREE(pcd->setup_pkt); + DWC_FREE(pcd->status_buf); + } + DWC_SPINLOCK_FREE(pcd->lock); + /* Set core_if's lock pointer to NULL */ + pcd->core_if->lock = NULL; + + DWC_TASK_FREE(pcd->start_xfer_tasklet); + DWC_TASK_FREE(pcd->test_mode_tasklet); + if (pcd->core_if->core_params->dev_out_nak) { + for (i = 0; i < MAX_EPS_CHANNELS; i++) { + if (pcd->core_if->ep_xfer_timer[i]) { + DWC_TIMER_FREE(pcd->core_if->ep_xfer_timer[i]); + } + } + } + +/* Release the CFI object's dynamic memory */ +#ifdef DWC_UTE_CFI + if (pcd->cfi->ops.release) { + pcd->cfi->ops.release(pcd->cfi); + } +#endif + + DWC_FREE(pcd); +} + +/** + * Returns whether registered pcd is dual speed or not + */ +uint32_t dwc_otg_pcd_is_dualspeed(dwc_otg_pcd_t * pcd) +{ + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + + if ((core_if->core_params->speed == DWC_SPEED_PARAM_FULL) || + ((core_if->hwcfg2.b.hs_phy_type == 2) && + (core_if->hwcfg2.b.fs_phy_type == 1) && + (core_if->core_params->ulpi_fs_ls))) { + return 0; + } + + return 1; +} + +/** + * Returns whether registered pcd is OTG capable or not + */ +uint32_t dwc_otg_pcd_is_otg(dwc_otg_pcd_t * pcd) +{ + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + gusbcfg_data_t usbcfg = {.d32 = 0 }; + + usbcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->gusbcfg); + if (!usbcfg.b.srpcap || !usbcfg.b.hnpcap) { + return 0; + } + + return 1; +} + +/** + * This function assigns periodic Tx FIFO to an periodic EP + * in shared Tx FIFO mode + */ +static uint32_t assign_tx_fifo(dwc_otg_core_if_t * core_if) +{ + uint32_t TxMsk = 1; + int i; + + for (i = 0; i < core_if->hwcfg4.b.num_in_eps; ++i) { + if ((TxMsk & core_if->tx_msk) == 0) { + core_if->tx_msk |= TxMsk; + return i + 1; + } + TxMsk <<= 1; + } + return 0; +} + +/** + * This function assigns periodic Tx FIFO to an periodic EP + * in shared Tx FIFO mode + */ +static uint32_t assign_perio_tx_fifo(dwc_otg_core_if_t * core_if) +{ + uint32_t PerTxMsk = 1; + int i; + for (i = 0; i < core_if->hwcfg4.b.num_dev_perio_in_ep; ++i) { + if ((PerTxMsk & core_if->p_tx_msk) == 0) { + core_if->p_tx_msk |= PerTxMsk; + return i + 1; + } + PerTxMsk <<= 1; + } + return 0; +} + +/** + * This function releases periodic Tx FIFO + * in shared Tx FIFO mode + */ +static void release_perio_tx_fifo(dwc_otg_core_if_t * core_if, + uint32_t fifo_num) +{ + core_if->p_tx_msk = + (core_if->p_tx_msk & (1 << (fifo_num - 1))) ^ core_if->p_tx_msk; +} + +/** + * This function releases periodic Tx FIFO + * in shared Tx FIFO mode + */ +static void release_tx_fifo(dwc_otg_core_if_t * core_if, uint32_t fifo_num) +{ + core_if->tx_msk = + (core_if->tx_msk & (1 << (fifo_num - 1))) ^ core_if->tx_msk; +} + +/** + * This function is being called from gadget + * to enable PCD endpoint. + */ +int dwc_otg_pcd_ep_enable(dwc_otg_pcd_t * pcd, + const uint8_t * ep_desc, void *usb_ep) +{ + int num, dir; + dwc_otg_pcd_ep_t *ep = NULL; + const usb_endpoint_descriptor_t *desc; + dwc_irqflags_t flags; + fifosize_data_t dptxfsiz = {.d32 = 0 }; + gdfifocfg_data_t gdfifocfg = {.d32 = 0 }; + gdfifocfg_data_t gdfifocfgbase = {.d32 = 0 }; + int retval = 0; + int i, epcount; + struct device *dev = dwc_otg_pcd_to_dev(pcd); + + desc = (const usb_endpoint_descriptor_t *)ep_desc; + + if (!desc) { + pcd->ep0.priv = usb_ep; + ep = &pcd->ep0; + retval = -DWC_E_INVALID; + goto out; + } + + num = UE_GET_ADDR(desc->bEndpointAddress); + dir = UE_GET_DIR(desc->bEndpointAddress); + + if (!UGETW(desc->wMaxPacketSize)) { + DWC_WARN("bad maxpacketsize\n"); + retval = -DWC_E_INVALID; + goto out; + } + + if (dir == UE_DIR_IN) { + epcount = pcd->core_if->dev_if->num_in_eps; + for (i = 0; i < epcount; i++) { + if (num == pcd->in_ep[i].dwc_ep.num) { + ep = &pcd->in_ep[i]; + break; + } + } + } else { + epcount = pcd->core_if->dev_if->num_out_eps; + for (i = 0; i < epcount; i++) { + if (num == pcd->out_ep[i].dwc_ep.num) { + ep = &pcd->out_ep[i]; + break; + } + } + } + + if (!ep) { + DWC_WARN("bad address\n"); + retval = -DWC_E_INVALID; + goto out; + } + + DWC_SPINLOCK_IRQSAVE(pcd->lock, &flags); + + ep->desc = desc; + ep->priv = usb_ep; + + /* + * Activate the EP + */ + ep->stopped = 0; + + ep->dwc_ep.is_in = (dir == UE_DIR_IN); + ep->dwc_ep.maxpacket = UGETW(desc->wMaxPacketSize); + + ep->dwc_ep.type = desc->bmAttributes & UE_XFERTYPE; + + if (ep->dwc_ep.is_in) { + if (!GET_CORE_IF(pcd)->en_multiple_tx_fifo) { + ep->dwc_ep.tx_fifo_num = 0; + + if (ep->dwc_ep.type == UE_ISOCHRONOUS) { + /* + * if ISOC EP then assign a Periodic Tx FIFO. + */ + ep->dwc_ep.tx_fifo_num = + assign_perio_tx_fifo(GET_CORE_IF(pcd)); + } + } else { + /* + * if Dedicated FIFOs mode is on then assign a Tx FIFO. + */ + ep->dwc_ep.tx_fifo_num = + assign_tx_fifo(GET_CORE_IF(pcd)); + } + + /* Calculating EP info controller base address */ + if (ep->dwc_ep.tx_fifo_num + && GET_CORE_IF(pcd)->en_multiple_tx_fifo) { + gdfifocfg.d32 = + DWC_READ_REG32(&GET_CORE_IF(pcd)-> + core_global_regs->gdfifocfg); + gdfifocfgbase.d32 = gdfifocfg.d32 >> 16; + dptxfsiz.d32 = + (DWC_READ_REG32 + (&GET_CORE_IF(pcd)->core_global_regs-> + dtxfsiz[ep->dwc_ep.tx_fifo_num - 1]) >> 16); + gdfifocfg.b.epinfobase = + gdfifocfgbase.d32 + dptxfsiz.d32; + if (GET_CORE_IF(pcd)->snpsid <= OTG_CORE_REV_2_94a) { + DWC_WRITE_REG32(&GET_CORE_IF(pcd)-> + core_global_regs->gdfifocfg, + gdfifocfg.d32); + } + } + } + /* Set initial data PID. */ + if (ep->dwc_ep.type == UE_BULK) { + ep->dwc_ep.data_pid_start = 0; + } + + /* Alloc DMA Descriptors */ + if (GET_CORE_IF(pcd)->dma_desc_enable) { +#ifndef DWC_UTE_PER_IO + if (ep->dwc_ep.type != UE_ISOCHRONOUS) { +#endif + ep->dwc_ep.desc_addr = + dwc_otg_ep_alloc_desc_chain(dev, + &ep->dwc_ep.dma_desc_addr, + MAX_DMA_DESC_CNT); + if (!ep->dwc_ep.desc_addr) { + DWC_WARN("%s, can't allocate DMA descriptor\n", + __func__); + retval = -DWC_E_SHUTDOWN; + DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags); + goto out; + } +#ifndef DWC_UTE_PER_IO + } +#endif + } + + DWC_DEBUGPL(DBG_PCD, "Activate %s: type=%d, mps=%d desc=%p\n", + (ep->dwc_ep.is_in ? "IN" : "OUT"), + ep->dwc_ep.type, ep->dwc_ep.maxpacket, ep->desc); +#ifdef DWC_UTE_PER_IO + ep->dwc_ep.xiso_bInterval = 1 << (ep->desc->bInterval - 1); +#endif + if (ep->dwc_ep.type == DWC_OTG_EP_TYPE_ISOC) { + ep->dwc_ep.bInterval = 1 << (ep->desc->bInterval - 1); + ep->dwc_ep.frame_num = 0xFFFFFFFF; + } + + dwc_otg_ep_activate(GET_CORE_IF(pcd), &ep->dwc_ep); + +#ifdef DWC_UTE_CFI + if (pcd->cfi->ops.ep_enable) { + pcd->cfi->ops.ep_enable(pcd->cfi, pcd, ep); + } +#endif + + DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags); + +out: + return retval; +} + +/** + * This function is being called from gadget + * to disable PCD endpoint. + */ +int dwc_otg_pcd_ep_disable(dwc_otg_pcd_t * pcd, void *ep_handle) +{ + dwc_otg_pcd_ep_t *ep; + dwc_irqflags_t flags; + dwc_otg_dev_dma_desc_t *desc_addr; + dwc_dma_t dma_desc_addr; + gdfifocfg_data_t gdfifocfgbase = {.d32 = 0 }; + gdfifocfg_data_t gdfifocfg = {.d32 = 0 }; + fifosize_data_t dptxfsiz = {.d32 = 0 }; + struct device *dev = dwc_otg_pcd_to_dev(pcd); + + ep = get_ep_from_handle(pcd, ep_handle); + + if (!ep || !ep->desc) { + DWC_DEBUGPL(DBG_PCD, "bad ep address\n"); + return -DWC_E_INVALID; + } + + DWC_SPINLOCK_IRQSAVE(pcd->lock, &flags); + + dwc_otg_request_nuke(ep); + + dwc_otg_ep_deactivate(GET_CORE_IF(pcd), &ep->dwc_ep); + if (pcd->core_if->core_params->dev_out_nak) { + DWC_TIMER_CANCEL(pcd->core_if->ep_xfer_timer[ep->dwc_ep.num]); + pcd->core_if->ep_xfer_info[ep->dwc_ep.num].state = 0; + } + ep->desc = NULL; + ep->stopped = 1; + + gdfifocfg.d32 = + DWC_READ_REG32(&GET_CORE_IF(pcd)->core_global_regs->gdfifocfg); + gdfifocfgbase.d32 = gdfifocfg.d32 >> 16; + + if (ep->dwc_ep.is_in) { + if (GET_CORE_IF(pcd)->en_multiple_tx_fifo) { + /* Flush the Tx FIFO */ + dwc_otg_flush_tx_fifo(GET_CORE_IF(pcd), + ep->dwc_ep.tx_fifo_num); + } + release_perio_tx_fifo(GET_CORE_IF(pcd), ep->dwc_ep.tx_fifo_num); + release_tx_fifo(GET_CORE_IF(pcd), ep->dwc_ep.tx_fifo_num); + if (GET_CORE_IF(pcd)->en_multiple_tx_fifo) { + /* Decreasing EPinfo Base Addr */ + dptxfsiz.d32 = + (DWC_READ_REG32 + (&GET_CORE_IF(pcd)-> + core_global_regs->dtxfsiz[ep->dwc_ep.tx_fifo_num-1]) >> 16); + gdfifocfg.b.epinfobase = gdfifocfgbase.d32 - dptxfsiz.d32; + if (GET_CORE_IF(pcd)->snpsid <= OTG_CORE_REV_2_94a) { + DWC_WRITE_REG32(&GET_CORE_IF(pcd)->core_global_regs->gdfifocfg, + gdfifocfg.d32); + } + } + } + + /* Free DMA Descriptors */ + if (GET_CORE_IF(pcd)->dma_desc_enable) { + if (ep->dwc_ep.type != UE_ISOCHRONOUS) { + desc_addr = ep->dwc_ep.desc_addr; + dma_desc_addr = ep->dwc_ep.dma_desc_addr; + + /* Cannot call dma_free_coherent() with IRQs disabled */ + DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags); + dwc_otg_ep_free_desc_chain(dev, desc_addr, dma_desc_addr, + MAX_DMA_DESC_CNT); + + goto out_unlocked; + } + } + DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags); + +out_unlocked: + DWC_DEBUGPL(DBG_PCD, "%d %s disabled\n", ep->dwc_ep.num, + ep->dwc_ep.is_in ? "IN" : "OUT"); + return 0; + +} + +/******************************************************************************/ +#ifdef DWC_UTE_PER_IO + +/** + * Free the request and its extended parts + * + */ +void dwc_pcd_xiso_ereq_free(dwc_otg_pcd_ep_t * ep, dwc_otg_pcd_request_t * req) +{ + DWC_FREE(req->ext_req.per_io_frame_descs); + DWC_FREE(req); +} + +/** + * Start the next request in the endpoint's queue. + * + */ +int dwc_otg_pcd_xiso_start_next_request(dwc_otg_pcd_t * pcd, + dwc_otg_pcd_ep_t * ep) +{ + int i; + dwc_otg_pcd_request_t *req = NULL; + dwc_ep_t *dwcep = NULL; + struct dwc_iso_xreq_port *ereq = NULL; + struct dwc_iso_pkt_desc_port *ddesc_iso; + uint16_t nat; + depctl_data_t diepctl; + + dwcep = &ep->dwc_ep; + + if (dwcep->xiso_active_xfers > 0) { +#if 0 //Disable this to decrease s/w overhead that is crucial for Isoc transfers + DWC_WARN("There are currently active transfers for EP%d \ + (active=%d; queued=%d)", dwcep->num, dwcep->xiso_active_xfers, + dwcep->xiso_queued_xfers); +#endif + return 0; + } + + nat = UGETW(ep->desc->wMaxPacketSize); + nat = (nat >> 11) & 0x03; + + if (!DWC_CIRCLEQ_EMPTY(&ep->queue)) { + req = DWC_CIRCLEQ_FIRST(&ep->queue); + ereq = &req->ext_req; + ep->stopped = 0; + + /* Get the frame number */ + dwcep->xiso_frame_num = + dwc_otg_get_frame_number(GET_CORE_IF(pcd)); + DWC_DEBUG("FRM_NUM=%d", dwcep->xiso_frame_num); + + ddesc_iso = ereq->per_io_frame_descs; + + if (dwcep->is_in) { + /* Setup DMA Descriptor chain for IN Isoc request */ + for (i = 0; i < ereq->pio_pkt_count; i++) { + //if ((i % (nat + 1)) == 0) + if ( i > 0 ) + dwcep->xiso_frame_num = + (dwcep->xiso_bInterval + + dwcep->xiso_frame_num) & 0x3FFF; + dwcep->desc_addr[i].buf = + req->dma + ddesc_iso[i].offset; + dwcep->desc_addr[i].status.b_iso_in.txbytes = + ddesc_iso[i].length; + dwcep->desc_addr[i].status.b_iso_in.framenum = + dwcep->xiso_frame_num; + dwcep->desc_addr[i].status.b_iso_in.bs = + BS_HOST_READY; + dwcep->desc_addr[i].status.b_iso_in.txsts = 0; + dwcep->desc_addr[i].status.b_iso_in.sp = + (ddesc_iso[i].length % + dwcep->maxpacket) ? 1 : 0; + dwcep->desc_addr[i].status.b_iso_in.ioc = 0; + dwcep->desc_addr[i].status.b_iso_in.pid = nat + 1; + dwcep->desc_addr[i].status.b_iso_in.l = 0; + + /* Process the last descriptor */ + if (i == ereq->pio_pkt_count - 1) { + dwcep->desc_addr[i].status.b_iso_in.ioc = 1; + dwcep->desc_addr[i].status.b_iso_in.l = 1; + } + } + + /* Setup and start the transfer for this endpoint */ + dwcep->xiso_active_xfers++; + DWC_WRITE_REG32(&GET_CORE_IF(pcd)->dev_if-> + in_ep_regs[dwcep->num]->diepdma, + dwcep->dma_desc_addr); + diepctl.d32 = 0; + diepctl.b.epena = 1; + diepctl.b.cnak = 1; + DWC_MODIFY_REG32(&GET_CORE_IF(pcd)->dev_if-> + in_ep_regs[dwcep->num]->diepctl, 0, + diepctl.d32); + } else { + /* Setup DMA Descriptor chain for OUT Isoc request */ + for (i = 0; i < ereq->pio_pkt_count; i++) { + //if ((i % (nat + 1)) == 0) + dwcep->xiso_frame_num = (dwcep->xiso_bInterval + + dwcep->xiso_frame_num) & 0x3FFF; + dwcep->desc_addr[i].buf = + req->dma + ddesc_iso[i].offset; + dwcep->desc_addr[i].status.b_iso_out.rxbytes = + ddesc_iso[i].length; + dwcep->desc_addr[i].status.b_iso_out.framenum = + dwcep->xiso_frame_num; + dwcep->desc_addr[i].status.b_iso_out.bs = + BS_HOST_READY; + dwcep->desc_addr[i].status.b_iso_out.rxsts = 0; + dwcep->desc_addr[i].status.b_iso_out.sp = + (ddesc_iso[i].length % + dwcep->maxpacket) ? 1 : 0; + dwcep->desc_addr[i].status.b_iso_out.ioc = 0; + dwcep->desc_addr[i].status.b_iso_out.pid = nat + 1; + dwcep->desc_addr[i].status.b_iso_out.l = 0; + + /* Process the last descriptor */ + if (i == ereq->pio_pkt_count - 1) { + dwcep->desc_addr[i].status.b_iso_out.ioc = 1; + dwcep->desc_addr[i].status.b_iso_out.l = 1; + } + } + + /* Setup and start the transfer for this endpoint */ + dwcep->xiso_active_xfers++; + DWC_WRITE_REG32(&GET_CORE_IF(pcd)-> + dev_if->out_ep_regs[dwcep->num]-> + doepdma, dwcep->dma_desc_addr); + diepctl.d32 = 0; + diepctl.b.epena = 1; + diepctl.b.cnak = 1; + DWC_MODIFY_REG32(&GET_CORE_IF(pcd)-> + dev_if->out_ep_regs[dwcep->num]-> + doepctl, 0, diepctl.d32); + } + + } else { + ep->stopped = 1; + } + + return 0; +} + +/** + * - Remove the request from the queue + */ +void complete_xiso_ep(dwc_otg_pcd_ep_t * ep) +{ + dwc_otg_pcd_request_t *req = NULL; + struct dwc_iso_xreq_port *ereq = NULL; + struct dwc_iso_pkt_desc_port *ddesc_iso = NULL; + dwc_ep_t *dwcep = NULL; + int i; + + //DWC_DEBUG(); + dwcep = &ep->dwc_ep; + + /* Get the first pending request from the queue */ + if (!DWC_CIRCLEQ_EMPTY(&ep->queue)) { + req = DWC_CIRCLEQ_FIRST(&ep->queue); + if (!req) { + DWC_PRINTF("complete_ep 0x%p, req = NULL!\n", ep); + return; + } + dwcep->xiso_active_xfers--; + dwcep->xiso_queued_xfers--; + /* Remove this request from the queue */ + DWC_CIRCLEQ_REMOVE_INIT(&ep->queue, req, queue_entry); + } else { + DWC_PRINTF("complete_ep 0x%p, ep->queue empty!\n", ep); + return; + } + + ep->stopped = 1; + ereq = &req->ext_req; + ddesc_iso = ereq->per_io_frame_descs; + + if (dwcep->xiso_active_xfers < 0) { + DWC_WARN("EP#%d (xiso_active_xfers=%d)", dwcep->num, + dwcep->xiso_active_xfers); + } + + /* Fill the Isoc descs of portable extended req from dma descriptors */ + for (i = 0; i < ereq->pio_pkt_count; i++) { + if (dwcep->is_in) { /* IN endpoints */ + ddesc_iso[i].actual_length = ddesc_iso[i].length - + dwcep->desc_addr[i].status.b_iso_in.txbytes; + ddesc_iso[i].status = + dwcep->desc_addr[i].status.b_iso_in.txsts; + } else { /* OUT endpoints */ + ddesc_iso[i].actual_length = ddesc_iso[i].length - + dwcep->desc_addr[i].status.b_iso_out.rxbytes; + ddesc_iso[i].status = + dwcep->desc_addr[i].status.b_iso_out.rxsts; + } + } + + DWC_SPINUNLOCK(ep->pcd->lock); + + /* Call the completion function in the non-portable logic */ + ep->pcd->fops->xisoc_complete(ep->pcd, ep->priv, req->priv, 0, + &req->ext_req); + + DWC_SPINLOCK(ep->pcd->lock); + + /* Free the request - specific freeing needed for extended request object */ + dwc_pcd_xiso_ereq_free(ep, req); + + /* Start the next request */ + dwc_otg_pcd_xiso_start_next_request(ep->pcd, ep); + + return; +} + +/** + * Create and initialize the Isoc pkt descriptors of the extended request. + * + */ +static int dwc_otg_pcd_xiso_create_pkt_descs(dwc_otg_pcd_request_t * req, + void *ereq_nonport, + int atomic_alloc) +{ + struct dwc_iso_xreq_port *ereq = NULL; + struct dwc_iso_xreq_port *req_mapped = NULL; + struct dwc_iso_pkt_desc_port *ipds = NULL; /* To be created in this function */ + uint32_t pkt_count; + int i; + + ereq = &req->ext_req; + req_mapped = (struct dwc_iso_xreq_port *)ereq_nonport; + pkt_count = req_mapped->pio_pkt_count; + + /* Create the isoc descs */ + if (atomic_alloc) { + ipds = DWC_ALLOC_ATOMIC(sizeof(*ipds) * pkt_count); + } else { + ipds = DWC_ALLOC(sizeof(*ipds) * pkt_count); + } + + if (!ipds) { + DWC_ERROR("Failed to allocate isoc descriptors"); + return -DWC_E_NO_MEMORY; + } + + /* Initialize the extended request fields */ + ereq->per_io_frame_descs = ipds; + ereq->error_count = 0; + ereq->pio_alloc_pkt_count = pkt_count; + ereq->pio_pkt_count = pkt_count; + ereq->tr_sub_flags = req_mapped->tr_sub_flags; + + /* Init the Isoc descriptors */ + for (i = 0; i < pkt_count; i++) { + ipds[i].length = req_mapped->per_io_frame_descs[i].length; + ipds[i].offset = req_mapped->per_io_frame_descs[i].offset; + ipds[i].status = req_mapped->per_io_frame_descs[i].status; /* 0 */ + ipds[i].actual_length = + req_mapped->per_io_frame_descs[i].actual_length; + } + + return 0; +} + +static void prn_ext_request(struct dwc_iso_xreq_port *ereq) +{ + struct dwc_iso_pkt_desc_port *xfd = NULL; + int i; + + DWC_DEBUG("per_io_frame_descs=%p", ereq->per_io_frame_descs); + DWC_DEBUG("tr_sub_flags=%d", ereq->tr_sub_flags); + DWC_DEBUG("error_count=%d", ereq->error_count); + DWC_DEBUG("pio_alloc_pkt_count=%d", ereq->pio_alloc_pkt_count); + DWC_DEBUG("pio_pkt_count=%d", ereq->pio_pkt_count); + DWC_DEBUG("res=%d", ereq->res); + + for (i = 0; i < ereq->pio_pkt_count; i++) { + xfd = &ereq->per_io_frame_descs[0]; + DWC_DEBUG("FD #%d", i); + + DWC_DEBUG("xfd->actual_length=%d", xfd->actual_length); + DWC_DEBUG("xfd->length=%d", xfd->length); + DWC_DEBUG("xfd->offset=%d", xfd->offset); + DWC_DEBUG("xfd->status=%d", xfd->status); + } +} + +/** + * + */ +int dwc_otg_pcd_xiso_ep_queue(dwc_otg_pcd_t * pcd, void *ep_handle, + uint8_t * buf, dwc_dma_t dma_buf, uint32_t buflen, + int zero, void *req_handle, int atomic_alloc, + void *ereq_nonport) +{ + dwc_otg_pcd_request_t *req = NULL; + dwc_otg_pcd_ep_t *ep; + dwc_irqflags_t flags; + int res; + + ep = get_ep_from_handle(pcd, ep_handle); + if (!ep) { + DWC_WARN("bad ep\n"); + return -DWC_E_INVALID; + } + + /* We support this extension only for DDMA mode */ + if (ep->dwc_ep.type == DWC_OTG_EP_TYPE_ISOC) + if (!GET_CORE_IF(pcd)->dma_desc_enable) + return -DWC_E_INVALID; + + /* Create a dwc_otg_pcd_request_t object */ + if (atomic_alloc) { + req = DWC_ALLOC_ATOMIC(sizeof(*req)); + } else { + req = DWC_ALLOC(sizeof(*req)); + } + + if (!req) { + return -DWC_E_NO_MEMORY; + } + + /* Create the Isoc descs for this request which shall be the exact match + * of the structure sent to us from the non-portable logic */ + res = + dwc_otg_pcd_xiso_create_pkt_descs(req, ereq_nonport, atomic_alloc); + if (res) { + DWC_WARN("Failed to init the Isoc descriptors"); + DWC_FREE(req); + return res; + } + + DWC_SPINLOCK_IRQSAVE(pcd->lock, &flags); + + DWC_CIRCLEQ_INIT_ENTRY(req, queue_entry); + req->buf = buf; + req->dma = dma_buf; + req->length = buflen; + req->sent_zlp = zero; + req->priv = req_handle; + + //DWC_SPINLOCK_IRQSAVE(pcd->lock, &flags); + ep->dwc_ep.dma_addr = dma_buf; + ep->dwc_ep.start_xfer_buff = buf; + ep->dwc_ep.xfer_buff = buf; + ep->dwc_ep.xfer_len = 0; + ep->dwc_ep.xfer_count = 0; + ep->dwc_ep.sent_zlp = 0; + ep->dwc_ep.total_len = buflen; + + /* Add this request to the tail */ + DWC_CIRCLEQ_INSERT_TAIL(&ep->queue, req, queue_entry); + ep->dwc_ep.xiso_queued_xfers++; + +//DWC_DEBUG("CP_0"); +//DWC_DEBUG("req->ext_req.tr_sub_flags=%d", req->ext_req.tr_sub_flags); +//prn_ext_request((struct dwc_iso_xreq_port *) ereq_nonport); +//prn_ext_request(&req->ext_req); + + //DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags); + + /* If the req->status == ASAP then check if there is any active transfer + * for this endpoint. If no active transfers, then get the first entry + * from the queue and start that transfer + */ + if (req->ext_req.tr_sub_flags == DWC_EREQ_TF_ASAP) { + res = dwc_otg_pcd_xiso_start_next_request(pcd, ep); + if (res) { + DWC_WARN("Failed to start the next Isoc transfer"); + DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags); + DWC_FREE(req); + return res; + } + } + + DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags); + return 0; +} + +#endif +/* END ifdef DWC_UTE_PER_IO ***************************************************/ +int dwc_otg_pcd_ep_queue(dwc_otg_pcd_t * pcd, void *ep_handle, + uint8_t * buf, dwc_dma_t dma_buf, uint32_t buflen, + int zero, void *req_handle, int atomic_alloc) +{ + struct device *dev = dwc_otg_pcd_to_dev(pcd); + dwc_irqflags_t flags; + dwc_otg_pcd_request_t *req; + dwc_otg_pcd_ep_t *ep; + uint32_t max_transfer; + + ep = get_ep_from_handle(pcd, ep_handle); + if (!ep || (!ep->desc && ep->dwc_ep.num != 0)) { + DWC_WARN("bad ep\n"); + return -DWC_E_INVALID; + } + + if (atomic_alloc) { + req = DWC_ALLOC_ATOMIC(sizeof(*req)); + } else { + req = DWC_ALLOC(sizeof(*req)); + } + + if (!req) { + return -DWC_E_NO_MEMORY; + } + DWC_CIRCLEQ_INIT_ENTRY(req, queue_entry); + if (!GET_CORE_IF(pcd)->core_params->opt) { + if (ep->dwc_ep.num != 0) { + DWC_ERROR("queue req %p, len %d buf %p\n", + req_handle, buflen, buf); + } + } + + req->buf = buf; + req->dma = dma_buf; + req->length = buflen; + req->sent_zlp = zero; + req->priv = req_handle; + req->dw_align_buf = NULL; + if ((dma_buf & 0x3) && GET_CORE_IF(pcd)->dma_enable + && !GET_CORE_IF(pcd)->dma_desc_enable) + req->dw_align_buf = DWC_DMA_ALLOC(dev, buflen, + &req->dw_align_buf_dma); + DWC_SPINLOCK_IRQSAVE(pcd->lock, &flags); + + /* + * After adding request to the queue for IN ISOC wait for In Token Received + * when TX FIFO is empty interrupt and for OUT ISOC wait for OUT Token + * Received when EP is disabled interrupt to obtain starting microframe + * (odd/even) start transfer + */ + if (ep->dwc_ep.type == DWC_OTG_EP_TYPE_ISOC) { + if (req != 0) { + depctl_data_t depctl = {.d32 = + DWC_READ_REG32(&pcd->core_if->dev_if-> + in_ep_regs[ep->dwc_ep.num]-> + diepctl) }; + ++pcd->request_pending; + + DWC_CIRCLEQ_INSERT_TAIL(&ep->queue, req, queue_entry); + if (ep->dwc_ep.is_in) { + depctl.b.cnak = 1; + DWC_WRITE_REG32(&pcd->core_if->dev_if-> + in_ep_regs[ep->dwc_ep.num]-> + diepctl, depctl.d32); + } + + DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags); + } + return 0; + } + + /* + * For EP0 IN without premature status, zlp is required? + */ + if (ep->dwc_ep.num == 0 && ep->dwc_ep.is_in) { + DWC_DEBUGPL(DBG_PCDV, "%d-OUT ZLP\n", ep->dwc_ep.num); + //_req->zero = 1; + } + + /* Start the transfer */ + if (DWC_CIRCLEQ_EMPTY(&ep->queue) && !ep->stopped) { + /* EP0 Transfer? */ + if (ep->dwc_ep.num == 0) { + switch (pcd->ep0state) { + case EP0_IN_DATA_PHASE: + DWC_DEBUGPL(DBG_PCD, + "%s ep0: EP0_IN_DATA_PHASE\n", + __func__); + break; + + case EP0_OUT_DATA_PHASE: + DWC_DEBUGPL(DBG_PCD, + "%s ep0: EP0_OUT_DATA_PHASE\n", + __func__); + if (pcd->request_config) { + /* Complete STATUS PHASE */ + ep->dwc_ep.is_in = 1; + pcd->ep0state = EP0_IN_STATUS_PHASE; + } + break; + + case EP0_IN_STATUS_PHASE: + DWC_DEBUGPL(DBG_PCD, + "%s ep0: EP0_IN_STATUS_PHASE\n", + __func__); + break; + + default: + DWC_DEBUGPL(DBG_ANY, "ep0: odd state %d\n", + pcd->ep0state); + DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags); + return -DWC_E_SHUTDOWN; + } + + ep->dwc_ep.dma_addr = dma_buf; + ep->dwc_ep.start_xfer_buff = buf; + ep->dwc_ep.xfer_buff = buf; + ep->dwc_ep.xfer_len = buflen; + ep->dwc_ep.xfer_count = 0; + ep->dwc_ep.sent_zlp = 0; + ep->dwc_ep.total_len = ep->dwc_ep.xfer_len; + + if (zero) { + if ((ep->dwc_ep.xfer_len % + ep->dwc_ep.maxpacket == 0) + && (ep->dwc_ep.xfer_len != 0)) { + ep->dwc_ep.sent_zlp = 1; + } + + } + + dwc_otg_ep0_start_transfer(GET_CORE_IF(pcd), + &ep->dwc_ep); + } // non-ep0 endpoints + else { +#ifdef DWC_UTE_CFI + if (ep->dwc_ep.buff_mode != BM_STANDARD) { + /* store the request length */ + ep->dwc_ep.cfi_req_len = buflen; + pcd->cfi->ops.build_descriptors(pcd->cfi, pcd, + ep, req); + } else { +#endif + max_transfer = + GET_CORE_IF(ep->pcd)->core_params-> + max_transfer_size; + + /* Setup and start the Transfer */ + if (req->dw_align_buf){ + if (ep->dwc_ep.is_in) + dwc_memcpy(req->dw_align_buf, + buf, buflen); + ep->dwc_ep.dma_addr = + req->dw_align_buf_dma; + ep->dwc_ep.start_xfer_buff = + req->dw_align_buf; + ep->dwc_ep.xfer_buff = + req->dw_align_buf; + } else { + ep->dwc_ep.dma_addr = dma_buf; + ep->dwc_ep.start_xfer_buff = buf; + ep->dwc_ep.xfer_buff = buf; + } + ep->dwc_ep.xfer_len = 0; + ep->dwc_ep.xfer_count = 0; + ep->dwc_ep.sent_zlp = 0; + ep->dwc_ep.total_len = buflen; + + ep->dwc_ep.maxxfer = max_transfer; + if (GET_CORE_IF(pcd)->dma_desc_enable) { + uint32_t out_max_xfer = + DDMA_MAX_TRANSFER_SIZE - + (DDMA_MAX_TRANSFER_SIZE % 4); + if (ep->dwc_ep.is_in) { + if (ep->dwc_ep.maxxfer > + DDMA_MAX_TRANSFER_SIZE) { + ep->dwc_ep.maxxfer = + DDMA_MAX_TRANSFER_SIZE; + } + } else { + if (ep->dwc_ep.maxxfer > + out_max_xfer) { + ep->dwc_ep.maxxfer = + out_max_xfer; + } + } + } + if (ep->dwc_ep.maxxfer < ep->dwc_ep.total_len) { + ep->dwc_ep.maxxfer -= + (ep->dwc_ep.maxxfer % + ep->dwc_ep.maxpacket); + } + + if (zero) { + if ((ep->dwc_ep.total_len % + ep->dwc_ep.maxpacket == 0) + && (ep->dwc_ep.total_len != 0)) { + ep->dwc_ep.sent_zlp = 1; + } + } +#ifdef DWC_UTE_CFI + } +#endif + dwc_otg_ep_start_transfer(GET_CORE_IF(pcd), + &ep->dwc_ep); + } + } + + if (req != 0) { + ++pcd->request_pending; + DWC_CIRCLEQ_INSERT_TAIL(&ep->queue, req, queue_entry); + if (ep->dwc_ep.is_in && ep->stopped + && !(GET_CORE_IF(pcd)->dma_enable)) { + /** @todo NGS Create a function for this. */ + diepmsk_data_t diepmsk = {.d32 = 0 }; + diepmsk.b.intktxfemp = 1; + if (GET_CORE_IF(pcd)->multiproc_int_enable) { + DWC_MODIFY_REG32(&GET_CORE_IF(pcd)-> + dev_if->dev_global_regs->diepeachintmsk + [ep->dwc_ep.num], 0, + diepmsk.d32); + } else { + DWC_MODIFY_REG32(&GET_CORE_IF(pcd)-> + dev_if->dev_global_regs-> + diepmsk, 0, diepmsk.d32); + } + + } + } + DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags); + + return 0; +} + +int dwc_otg_pcd_ep_dequeue(dwc_otg_pcd_t * pcd, void *ep_handle, + void *req_handle) +{ + dwc_irqflags_t flags; + dwc_otg_pcd_request_t *req; + dwc_otg_pcd_ep_t *ep; + + ep = get_ep_from_handle(pcd, ep_handle); + if (!ep || (!ep->desc && ep->dwc_ep.num != 0)) { + DWC_WARN("bad argument\n"); + return -DWC_E_INVALID; + } + + DWC_SPINLOCK_IRQSAVE(pcd->lock, &flags); + + /* make sure it's actually queued on this endpoint */ + DWC_CIRCLEQ_FOREACH(req, &ep->queue, queue_entry) { + if (req->priv == (void *)req_handle) { + break; + } + } + + if (req->priv != (void *)req_handle) { + DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags); + return -DWC_E_INVALID; + } + + if (!DWC_CIRCLEQ_EMPTY_ENTRY(req, queue_entry)) { + dwc_otg_request_done(ep, req, -DWC_E_RESTART); + } else { + req = NULL; + } + + DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags); + + return req ? 0 : -DWC_E_SHUTDOWN; + +} + +/** + * dwc_otg_pcd_ep_wedge - sets the halt feature and ignores clear requests + * + * Use this to stall an endpoint and ignore CLEAR_FEATURE(HALT_ENDPOINT) + * requests. If the gadget driver clears the halt status, it will + * automatically unwedge the endpoint. + * + * Returns zero on success, else negative DWC error code. + */ +int dwc_otg_pcd_ep_wedge(dwc_otg_pcd_t * pcd, void *ep_handle) +{ + dwc_otg_pcd_ep_t *ep; + dwc_irqflags_t flags; + int retval = 0; + + ep = get_ep_from_handle(pcd, ep_handle); + + if ((!ep->desc && ep != &pcd->ep0) || + (ep->desc && (ep->desc->bmAttributes == UE_ISOCHRONOUS))) { + DWC_WARN("%s, bad ep\n", __func__); + return -DWC_E_INVALID; + } + + DWC_SPINLOCK_IRQSAVE(pcd->lock, &flags); + if (!DWC_CIRCLEQ_EMPTY(&ep->queue)) { + DWC_WARN("%d %s XFer In process\n", ep->dwc_ep.num, + ep->dwc_ep.is_in ? "IN" : "OUT"); + retval = -DWC_E_AGAIN; + } else { + /* This code needs to be reviewed */ + if (ep->dwc_ep.is_in == 1 && GET_CORE_IF(pcd)->dma_desc_enable) { + dtxfsts_data_t txstatus; + fifosize_data_t txfifosize; + + txfifosize.d32 = + DWC_READ_REG32(&GET_CORE_IF(pcd)-> + core_global_regs->dtxfsiz[ep->dwc_ep. + tx_fifo_num]); + txstatus.d32 = + DWC_READ_REG32(&GET_CORE_IF(pcd)-> + dev_if->in_ep_regs[ep->dwc_ep.num]-> + dtxfsts); + + if (txstatus.b.txfspcavail < txfifosize.b.depth) { + DWC_WARN("%s() Data In Tx Fifo\n", __func__); + retval = -DWC_E_AGAIN; + } else { + if (ep->dwc_ep.num == 0) { + pcd->ep0state = EP0_STALL; + } + + ep->stopped = 1; + dwc_otg_ep_set_stall(GET_CORE_IF(pcd), + &ep->dwc_ep); + } + } else { + if (ep->dwc_ep.num == 0) { + pcd->ep0state = EP0_STALL; + } + + ep->stopped = 1; + dwc_otg_ep_set_stall(GET_CORE_IF(pcd), &ep->dwc_ep); + } + } + + DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags); + + return retval; +} + +int dwc_otg_pcd_ep_halt(dwc_otg_pcd_t * pcd, void *ep_handle, int value) +{ + dwc_otg_pcd_ep_t *ep; + dwc_irqflags_t flags; + int retval = 0; + + ep = get_ep_from_handle(pcd, ep_handle); + + if (!ep || (!ep->desc && ep != &pcd->ep0) || + (ep->desc && (ep->desc->bmAttributes == UE_ISOCHRONOUS))) { + DWC_WARN("%s, bad ep\n", __func__); + return -DWC_E_INVALID; + } + + DWC_SPINLOCK_IRQSAVE(pcd->lock, &flags); + if (!DWC_CIRCLEQ_EMPTY(&ep->queue)) { + DWC_WARN("%d %s XFer In process\n", ep->dwc_ep.num, + ep->dwc_ep.is_in ? "IN" : "OUT"); + retval = -DWC_E_AGAIN; + } else if (value == 0) { + dwc_otg_ep_clear_stall(GET_CORE_IF(pcd), &ep->dwc_ep); + } else if (value == 1) { + if (ep->dwc_ep.is_in == 1 && GET_CORE_IF(pcd)->dma_desc_enable) { + dtxfsts_data_t txstatus; + fifosize_data_t txfifosize; + + txfifosize.d32 = + DWC_READ_REG32(&GET_CORE_IF(pcd)->core_global_regs-> + dtxfsiz[ep->dwc_ep.tx_fifo_num]); + txstatus.d32 = + DWC_READ_REG32(&GET_CORE_IF(pcd)->dev_if-> + in_ep_regs[ep->dwc_ep.num]->dtxfsts); + + if (txstatus.b.txfspcavail < txfifosize.b.depth) { + DWC_WARN("%s() Data In Tx Fifo\n", __func__); + retval = -DWC_E_AGAIN; + } else { + if (ep->dwc_ep.num == 0) { + pcd->ep0state = EP0_STALL; + } + + ep->stopped = 1; + dwc_otg_ep_set_stall(GET_CORE_IF(pcd), + &ep->dwc_ep); + } + } else { + if (ep->dwc_ep.num == 0) { + pcd->ep0state = EP0_STALL; + } + + ep->stopped = 1; + dwc_otg_ep_set_stall(GET_CORE_IF(pcd), &ep->dwc_ep); + } + } else if (value == 2) { + ep->dwc_ep.stall_clear_flag = 0; + } else if (value == 3) { + ep->dwc_ep.stall_clear_flag = 1; + } + + DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags); + + return retval; +} + +/** + * This function initiates remote wakeup of the host from suspend state. + */ +static void dwc_otg_pcd_rem_wkup_from_suspend(dwc_otg_pcd_t * pcd, int set) +{ + dctl_data_t dctl = { 0 }; + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + dsts_data_t dsts; + + dsts.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dsts); + if (!dsts.b.suspsts) { + DWC_WARN("Remote wakeup while is not in suspend state\n"); + } + /* Check if DEVICE_REMOTE_WAKEUP feature enabled */ + if (pcd->remote_wakeup_enable) { + if (set) { + + if (core_if->adp_enable) { + gpwrdn_data_t gpwrdn; + + dwc_otg_adp_probe_stop(core_if); + + /* Mask SRP detected interrupt from Power Down Logic */ + gpwrdn.d32 = 0; + gpwrdn.b.srp_det_msk = 1; + DWC_MODIFY_REG32(&core_if-> + core_global_regs->gpwrdn, + gpwrdn.d32, 0); + + /* Disable Power Down Logic */ + gpwrdn.d32 = 0; + gpwrdn.b.pmuactv = 1; + DWC_MODIFY_REG32(&core_if-> + core_global_regs->gpwrdn, + gpwrdn.d32, 0); + + /* + * Initialize the Core for Device mode. + */ + core_if->op_state = B_PERIPHERAL; + dwc_otg_core_init(core_if); + dwc_otg_enable_global_interrupts(core_if); + cil_pcd_start(core_if); + + dwc_otg_initiate_srp(core_if); + } + + dctl.b.rmtwkupsig = 1; + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs-> + dctl, 0, dctl.d32); + DWC_DEBUGPL(DBG_PCD, "Set Remote Wakeup\n"); + + dwc_mdelay(2); + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs-> + dctl, dctl.d32, 0); + DWC_DEBUGPL(DBG_PCD, "Clear Remote Wakeup\n"); + } + } else { + DWC_DEBUGPL(DBG_PCD, "Remote Wakeup is disabled\n"); + } +} + +#ifdef CONFIG_USB_DWC_OTG_LPM +/** + * This function initiates remote wakeup of the host from L1 sleep state. + */ +void dwc_otg_pcd_rem_wkup_from_sleep(dwc_otg_pcd_t * pcd, int set) +{ + glpmcfg_data_t lpmcfg; + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + + lpmcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->glpmcfg); + + /* Check if we are in L1 state */ + if (!lpmcfg.b.prt_sleep_sts) { + DWC_DEBUGPL(DBG_PCD, "Device is not in sleep state\n"); + return; + } + + /* Check if host allows remote wakeup */ + if (!lpmcfg.b.rem_wkup_en) { + DWC_DEBUGPL(DBG_PCD, "Host does not allow remote wakeup\n"); + return; + } + + /* Check if Resume OK */ + if (!lpmcfg.b.sleep_state_resumeok) { + DWC_DEBUGPL(DBG_PCD, "Sleep state resume is not OK\n"); + return; + } + + lpmcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->glpmcfg); + lpmcfg.b.en_utmi_sleep = 0; + lpmcfg.b.hird_thres &= (~(1 << 4)); + DWC_WRITE_REG32(&core_if->core_global_regs->glpmcfg, lpmcfg.d32); + + if (set) { + dctl_data_t dctl = {.d32 = 0 }; + dctl.b.rmtwkupsig = 1; + /* Set RmtWkUpSig bit to start remote wakup signaling. + * Hardware will automatically clear this bit. + */ + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->dctl, + 0, dctl.d32); + DWC_DEBUGPL(DBG_PCD, "Set Remote Wakeup\n"); + } + +} +#endif + +/** + * Performs remote wakeup. + */ +void dwc_otg_pcd_remote_wakeup(dwc_otg_pcd_t * pcd, int set) +{ + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + dwc_irqflags_t flags; + if (dwc_otg_is_device_mode(core_if)) { + DWC_SPINLOCK_IRQSAVE(pcd->lock, &flags); +#ifdef CONFIG_USB_DWC_OTG_LPM + if (core_if->lx_state == DWC_OTG_L1) { + dwc_otg_pcd_rem_wkup_from_sleep(pcd, set); + } else { +#endif + dwc_otg_pcd_rem_wkup_from_suspend(pcd, set); +#ifdef CONFIG_USB_DWC_OTG_LPM + } +#endif + DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags); + } + return; +} + +void dwc_otg_pcd_disconnect_us(dwc_otg_pcd_t * pcd, int no_of_usecs) +{ + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + dctl_data_t dctl = { 0 }; + + if (dwc_otg_is_device_mode(core_if)) { + dctl.b.sftdiscon = 1; + DWC_PRINTF("Soft disconnect for %d useconds\n",no_of_usecs); + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->dctl, 0, dctl.d32); + dwc_udelay(no_of_usecs); + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->dctl, dctl.d32,0); + + } else{ + DWC_PRINTF("NOT SUPPORTED IN HOST MODE\n"); + } + return; + +} + +int dwc_otg_pcd_wakeup(dwc_otg_pcd_t * pcd) +{ + dsts_data_t dsts; + gotgctl_data_t gotgctl; + + /* + * This function starts the Protocol if no session is in progress. If + * a session is already in progress, but the device is suspended, + * remote wakeup signaling is started. + */ + + /* Check if valid session */ + gotgctl.d32 = + DWC_READ_REG32(&(GET_CORE_IF(pcd)->core_global_regs->gotgctl)); + if (gotgctl.b.bsesvld) { + /* Check if suspend state */ + dsts.d32 = + DWC_READ_REG32(& + (GET_CORE_IF(pcd)->dev_if-> + dev_global_regs->dsts)); + if (dsts.b.suspsts) { + dwc_otg_pcd_remote_wakeup(pcd, 1); + } + } else { + dwc_otg_pcd_initiate_srp(pcd); + } + + return 0; + +} + +/** + * Start the SRP timer to detect when the SRP does not complete within + * 6 seconds. + * + * @param pcd the pcd structure. + */ +void dwc_otg_pcd_initiate_srp(dwc_otg_pcd_t * pcd) +{ + dwc_irqflags_t flags; + DWC_SPINLOCK_IRQSAVE(pcd->lock, &flags); + dwc_otg_initiate_srp(GET_CORE_IF(pcd)); + DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags); +} + +int dwc_otg_pcd_get_frame_number(dwc_otg_pcd_t * pcd) +{ + return dwc_otg_get_frame_number(GET_CORE_IF(pcd)); +} + +int dwc_otg_pcd_is_lpm_enabled(dwc_otg_pcd_t * pcd) +{ + return GET_CORE_IF(pcd)->core_params->lpm_enable; +} + +uint32_t get_b_hnp_enable(dwc_otg_pcd_t * pcd) +{ + return pcd->b_hnp_enable; +} + +uint32_t get_a_hnp_support(dwc_otg_pcd_t * pcd) +{ + return pcd->a_hnp_support; +} + +uint32_t get_a_alt_hnp_support(dwc_otg_pcd_t * pcd) +{ + return pcd->a_alt_hnp_support; +} + +int dwc_otg_pcd_get_rmwkup_enable(dwc_otg_pcd_t * pcd) +{ + return pcd->remote_wakeup_enable; +} + +#endif /* DWC_HOST_ONLY */ diff --git a/drivers/usb/host/dwc_otg/dwc_otg_pcd.h b/drivers/usb/host/dwc_otg/dwc_otg_pcd.h new file mode 100644 index 00000000000000..fffc4c9d8bc65f --- /dev/null +++ b/drivers/usb/host/dwc_otg/dwc_otg_pcd.h @@ -0,0 +1,277 @@ +/* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_pcd.h $ + * $Revision: #48 $ + * $Date: 2012/08/10 $ + * $Change: 2047372 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ +#ifndef DWC_HOST_ONLY +#if !defined(__DWC_PCD_H__) +#define __DWC_PCD_H__ + +#include "dwc_otg_os_dep.h" +#include "usb.h" +#include "dwc_otg_cil.h" +#include "dwc_otg_pcd_if.h" +#include "dwc_otg_driver.h" + +struct cfiobject; + +/** + * @file + * + * This file contains the structures, constants, and interfaces for + * the Perpherial Contoller Driver (PCD). + * + * The Peripheral Controller Driver (PCD) for Linux will implement the + * Gadget API, so that the existing Gadget drivers can be used. For + * the Mass Storage Function driver the File-backed USB Storage Gadget + * (FBS) driver will be used. The FBS driver supports the + * Control-Bulk (CB), Control-Bulk-Interrupt (CBI), and Bulk-Only + * transports. + * + */ + +/** Invalid DMA Address */ +#define DWC_DMA_ADDR_INVALID (~(dwc_dma_t)0) + +/** Max Transfer size for any EP */ +#define DDMA_MAX_TRANSFER_SIZE 65535 + +/** + * Get the pointer to the core_if from the pcd pointer. + */ +#define GET_CORE_IF( _pcd ) (_pcd->core_if) + +/** + * States of EP0. + */ +typedef enum ep0_state { + EP0_DISCONNECT, /* no host */ + EP0_IDLE, + EP0_IN_DATA_PHASE, + EP0_OUT_DATA_PHASE, + EP0_IN_STATUS_PHASE, + EP0_OUT_STATUS_PHASE, + EP0_STALL, +} ep0state_e; + +/** Fordward declaration.*/ +struct dwc_otg_pcd; + +/** DWC_otg iso request structure. + * + */ +typedef struct usb_iso_request dwc_otg_pcd_iso_request_t; + +#ifdef DWC_UTE_PER_IO + +/** + * This shall be the exact analogy of the same type structure defined in the + * usb_gadget.h. Each descriptor contains + */ +struct dwc_iso_pkt_desc_port { + uint32_t offset; + uint32_t length; /* expected length */ + uint32_t actual_length; + uint32_t status; +}; + +struct dwc_iso_xreq_port { + /** transfer/submission flag */ + uint32_t tr_sub_flags; + /** Start the request ASAP */ +#define DWC_EREQ_TF_ASAP 0x00000002 + /** Just enqueue the request w/o initiating a transfer */ +#define DWC_EREQ_TF_ENQUEUE 0x00000004 + + /** + * count of ISO packets attached to this request - shall + * not exceed the pio_alloc_pkt_count + */ + uint32_t pio_pkt_count; + /** count of ISO packets allocated for this request */ + uint32_t pio_alloc_pkt_count; + /** number of ISO packet errors */ + uint32_t error_count; + /** reserved for future extension */ + uint32_t res; + /** Will be allocated and freed in the UTE gadget and based on the CFC value */ + struct dwc_iso_pkt_desc_port *per_io_frame_descs; +}; +#endif +/** DWC_otg request structure. + * This structure is a list of requests. + */ +typedef struct dwc_otg_pcd_request { + void *priv; + void *buf; + dwc_dma_t dma; + uint32_t length; + uint32_t actual; + unsigned sent_zlp:1; + /** + * Used instead of original buffer if + * it(physical address) is not dword-aligned. + **/ + uint8_t *dw_align_buf; + dwc_dma_t dw_align_buf_dma; + + DWC_CIRCLEQ_ENTRY(dwc_otg_pcd_request) queue_entry; +#ifdef DWC_UTE_PER_IO + struct dwc_iso_xreq_port ext_req; + //void *priv_ereq_nport; /* */ +#endif +} dwc_otg_pcd_request_t; + +DWC_CIRCLEQ_HEAD(req_list, dwc_otg_pcd_request); + +/** PCD EP structure. + * This structure describes an EP, there is an array of EPs in the PCD + * structure. + */ +typedef struct dwc_otg_pcd_ep { + /** USB EP Descriptor */ + const usb_endpoint_descriptor_t *desc; + + /** queue of dwc_otg_pcd_requests. */ + struct req_list queue; + unsigned stopped:1; + unsigned disabling:1; + unsigned dma:1; + unsigned queue_sof:1; + +#ifdef DWC_EN_ISOC + /** ISOC req handle passed */ + void *iso_req_handle; +#endif //_EN_ISOC_ + + /** DWC_otg ep data. */ + dwc_ep_t dwc_ep; + + /** Pointer to PCD */ + struct dwc_otg_pcd *pcd; + + void *priv; +} dwc_otg_pcd_ep_t; + +/** DWC_otg PCD Structure. + * This structure encapsulates the data for the dwc_otg PCD. + */ +struct dwc_otg_pcd { + const struct dwc_otg_pcd_function_ops *fops; + /** The DWC otg device pointer */ + struct dwc_otg_device *otg_dev; + /** Core Interface */ + dwc_otg_core_if_t *core_if; + /** State of EP0 */ + ep0state_e ep0state; + /** EP0 Request is pending */ + unsigned ep0_pending:1; + /** Indicates when SET CONFIGURATION Request is in process */ + unsigned request_config:1; + /** The state of the Remote Wakeup Enable. */ + unsigned remote_wakeup_enable:1; + /** The state of the B-Device HNP Enable. */ + unsigned b_hnp_enable:1; + /** The state of A-Device HNP Support. */ + unsigned a_hnp_support:1; + /** The state of the A-Device Alt HNP support. */ + unsigned a_alt_hnp_support:1; + /** Count of pending Requests */ + unsigned request_pending; + + /** SETUP packet for EP0 + * This structure is allocated as a DMA buffer on PCD initialization + * with enough space for up to 3 setup packets. + */ + union { + usb_device_request_t req; + uint32_t d32[2]; + } *setup_pkt; + + dwc_dma_t setup_pkt_dma_handle; + + /* Additional buffer and flag for CTRL_WR premature case */ + uint8_t *backup_buf; + unsigned data_terminated; + + /** 2-byte dma buffer used to return status from GET_STATUS */ + uint16_t *status_buf; + dwc_dma_t status_buf_dma_handle; + + /** EP0 */ + dwc_otg_pcd_ep_t ep0; + + /** Array of IN EPs. */ + dwc_otg_pcd_ep_t in_ep[MAX_EPS_CHANNELS - 1]; + /** Array of OUT EPs. */ + dwc_otg_pcd_ep_t out_ep[MAX_EPS_CHANNELS - 1]; + /** number of valid EPs in the above array. */ +// unsigned num_eps : 4; + dwc_spinlock_t *lock; + + /** Tasklet to defer starting of TEST mode transmissions until + * Status Phase has been completed. + */ + dwc_tasklet_t *test_mode_tasklet; + + /** Tasklet to delay starting of xfer in DMA mode */ + dwc_tasklet_t *start_xfer_tasklet; + + /** The test mode to enter when the tasklet is executed. */ + unsigned test_mode; + /** The cfi_api structure that implements most of the CFI API + * and OTG specific core configuration functionality + */ +#ifdef DWC_UTE_CFI + struct cfiobject *cfi; +#endif + +}; + +static inline struct device *dwc_otg_pcd_to_dev(struct dwc_otg_pcd *pcd) +{ + return &pcd->otg_dev->os_dep.platformdev->dev; +} + +extern void dwc_otg_pcd_stop(dwc_otg_pcd_t * pcd); +extern dwc_otg_pcd_ep_t *get_ep_by_addr(dwc_otg_pcd_t * pcd, u16 wIndex); +extern void start_next_request(dwc_otg_pcd_ep_t * ep); + +//FIXME this functions should be static, and this prototypes should be removed +extern void dwc_otg_request_nuke(dwc_otg_pcd_ep_t * ep); +extern void dwc_otg_request_done(dwc_otg_pcd_ep_t * ep, + dwc_otg_pcd_request_t * req, int32_t status); + +void dwc_otg_iso_buffer_done(dwc_otg_pcd_t * pcd, dwc_otg_pcd_ep_t * ep, + void *req_handle); + +extern void do_test_mode(void *data); +#endif +#endif /* DWC_HOST_ONLY */ diff --git a/drivers/usb/host/dwc_otg/dwc_otg_pcd_if.h b/drivers/usb/host/dwc_otg/dwc_otg_pcd_if.h new file mode 100644 index 00000000000000..badc93a41c3877 --- /dev/null +++ b/drivers/usb/host/dwc_otg/dwc_otg_pcd_if.h @@ -0,0 +1,381 @@ +/* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_pcd_if.h $ + * $Revision: #11 $ + * $Date: 2011/10/26 $ + * $Change: 1873028 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ +#ifndef DWC_HOST_ONLY + +#if !defined(__DWC_PCD_IF_H__) +#define __DWC_PCD_IF_H__ + +//#include "dwc_os.h" +#include "dwc_otg_core_if.h" +#include "dwc_otg_driver.h" + +/** @file + * This file defines DWC_OTG PCD Core API. + */ + +struct dwc_otg_pcd; +typedef struct dwc_otg_pcd dwc_otg_pcd_t; + +/** Maxpacket size for EP0 */ +#define MAX_EP0_SIZE 64 +/** Maxpacket size for any EP */ +#define MAX_PACKET_SIZE 1024 + +/** @name Function Driver Callbacks */ +/** @{ */ + +/** This function will be called whenever a previously queued request has + * completed. The status value will be set to -DWC_E_SHUTDOWN to indicated a + * failed or aborted transfer, or -DWC_E_RESTART to indicate the device was reset, + * or -DWC_E_TIMEOUT to indicate it timed out, or -DWC_E_INVALID to indicate invalid + * parameters. */ +typedef int (*dwc_completion_cb_t) (dwc_otg_pcd_t * pcd, void *ep_handle, + void *req_handle, int32_t status, + uint32_t actual); +/** + * This function will be called whenever a previousle queued ISOC request has + * completed. Count of ISOC packets could be read using dwc_otg_pcd_get_iso_packet_count + * function. + * The status of each ISOC packet could be read using dwc_otg_pcd_get_iso_packet_* + * functions. + */ +typedef int (*dwc_isoc_completion_cb_t) (dwc_otg_pcd_t * pcd, void *ep_handle, + void *req_handle, int proc_buf_num); +/** This function should handle any SETUP request that cannot be handled by the + * PCD Core. This includes most GET_DESCRIPTORs, SET_CONFIGS, Any + * class-specific requests, etc. The function must non-blocking. + * + * Returns 0 on success. + * Returns -DWC_E_NOT_SUPPORTED if the request is not supported. + * Returns -DWC_E_INVALID if the setup request had invalid parameters or bytes. + * Returns -DWC_E_SHUTDOWN on any other error. */ +typedef int (*dwc_setup_cb_t) (dwc_otg_pcd_t * pcd, uint8_t * bytes); +/** This is called whenever the device has been disconnected. The function + * driver should take appropriate action to clean up all pending requests in the + * PCD Core, remove all endpoints (except ep0), and initialize back to reset + * state. */ +typedef int (*dwc_disconnect_cb_t) (dwc_otg_pcd_t * pcd); +/** This function is called when device has been connected. */ +typedef int (*dwc_connect_cb_t) (dwc_otg_pcd_t * pcd, int speed); +/** This function is called when device has been suspended */ +typedef int (*dwc_suspend_cb_t) (dwc_otg_pcd_t * pcd); +/** This function is called when device has received LPM tokens, i.e. + * device has been sent to sleep state. */ +typedef int (*dwc_sleep_cb_t) (dwc_otg_pcd_t * pcd); +/** This function is called when device has been resumed + * from suspend(L2) or L1 sleep state. */ +typedef int (*dwc_resume_cb_t) (dwc_otg_pcd_t * pcd); +/** This function is called whenever hnp params has been changed. + * User can call get_b_hnp_enable, get_a_hnp_support, get_a_alt_hnp_support functions + * to get hnp parameters. */ +typedef int (*dwc_hnp_params_changed_cb_t) (dwc_otg_pcd_t * pcd); +/** This function is called whenever USB RESET is detected. */ +typedef int (*dwc_reset_cb_t) (dwc_otg_pcd_t * pcd); + +typedef int (*cfi_setup_cb_t) (dwc_otg_pcd_t * pcd, void *ctrl_req_bytes); + +/** + * + * @param ep_handle Void pointer to the usb_ep structure + * @param ereq_port Pointer to the extended request structure created in the + * portable part. + */ +typedef int (*xiso_completion_cb_t) (dwc_otg_pcd_t * pcd, void *ep_handle, + void *req_handle, int32_t status, + void *ereq_port); +/** Function Driver Ops Data Structure */ +struct dwc_otg_pcd_function_ops { + dwc_connect_cb_t connect; + dwc_disconnect_cb_t disconnect; + dwc_setup_cb_t setup; + dwc_completion_cb_t complete; + dwc_isoc_completion_cb_t isoc_complete; + dwc_suspend_cb_t suspend; + dwc_sleep_cb_t sleep; + dwc_resume_cb_t resume; + dwc_reset_cb_t reset; + dwc_hnp_params_changed_cb_t hnp_changed; + cfi_setup_cb_t cfi_setup; +#ifdef DWC_UTE_PER_IO + xiso_completion_cb_t xisoc_complete; +#endif +}; +/** @} */ + +/** @name Function Driver Functions */ +/** @{ */ + +/** Call this function to get pointer on dwc_otg_pcd_t, + * this pointer will be used for all PCD API functions. + * + * @param core_if The DWC_OTG Core + */ +extern dwc_otg_pcd_t *dwc_otg_pcd_init(dwc_otg_device_t *otg_dev); + +/** Frees PCD allocated by dwc_otg_pcd_init + * + * @param pcd The PCD + */ +extern void dwc_otg_pcd_remove(dwc_otg_pcd_t * pcd); + +/** Call this to bind the function driver to the PCD Core. + * + * @param pcd Pointer on dwc_otg_pcd_t returned by dwc_otg_pcd_init function. + * @param fops The Function Driver Ops data structure containing pointers to all callbacks. + */ +extern void dwc_otg_pcd_start(dwc_otg_pcd_t * pcd, + const struct dwc_otg_pcd_function_ops *fops); + +/** Enables an endpoint for use. This function enables an endpoint in + * the PCD. The endpoint is described by the ep_desc which has the + * same format as a USB ep descriptor. The ep_handle parameter is used to refer + * to the endpoint from other API functions and in callbacks. Normally this + * should be called after a SET_CONFIGURATION/SET_INTERFACE to configure the + * core for that interface. + * + * Returns -DWC_E_INVALID if invalid parameters were passed. + * Returns -DWC_E_SHUTDOWN if any other error ocurred. + * Returns 0 on success. + * + * @param pcd The PCD + * @param ep_desc Endpoint descriptor + * @param usb_ep Handle on endpoint, that will be used to identify endpoint. + */ +extern int dwc_otg_pcd_ep_enable(dwc_otg_pcd_t * pcd, + const uint8_t * ep_desc, void *usb_ep); + +/** Disable the endpoint referenced by ep_handle. + * + * Returns -DWC_E_INVALID if invalid parameters were passed. + * Returns -DWC_E_SHUTDOWN if any other error occurred. + * Returns 0 on success. */ +extern int dwc_otg_pcd_ep_disable(dwc_otg_pcd_t * pcd, void *ep_handle); + +/** Queue a data transfer request on the endpoint referenced by ep_handle. + * After the transfer is completes, the complete callback will be called with + * the request status. + * + * @param pcd The PCD + * @param ep_handle The handle of the endpoint + * @param buf The buffer for the data + * @param dma_buf The DMA buffer for the data + * @param buflen The length of the data transfer + * @param zero Specifies whether to send zero length last packet. + * @param req_handle Set this handle to any value to use to reference this + * request in the ep_dequeue function or from the complete callback + * @param atomic_alloc If driver need to perform atomic allocations + * for internal data structures. + * + * Returns -DWC_E_INVALID if invalid parameters were passed. + * Returns -DWC_E_SHUTDOWN if any other error ocurred. + * Returns 0 on success. */ +extern int dwc_otg_pcd_ep_queue(dwc_otg_pcd_t * pcd, void *ep_handle, + uint8_t * buf, dwc_dma_t dma_buf, + uint32_t buflen, int zero, void *req_handle, + int atomic_alloc); +#ifdef DWC_UTE_PER_IO +/** + * + * @param ereq_nonport Pointer to the extended request part of the + * usb_request structure defined in usb_gadget.h file. + */ +extern int dwc_otg_pcd_xiso_ep_queue(dwc_otg_pcd_t * pcd, void *ep_handle, + uint8_t * buf, dwc_dma_t dma_buf, + uint32_t buflen, int zero, + void *req_handle, int atomic_alloc, + void *ereq_nonport); + +#endif + +/** De-queue the specified data transfer that has not yet completed. + * + * Returns -DWC_E_INVALID if invalid parameters were passed. + * Returns -DWC_E_SHUTDOWN if any other error ocurred. + * Returns 0 on success. */ +extern int dwc_otg_pcd_ep_dequeue(dwc_otg_pcd_t * pcd, void *ep_handle, + void *req_handle); + +/** Halt (STALL) an endpoint or clear it. + * + * Returns -DWC_E_INVALID if invalid parameters were passed. + * Returns -DWC_E_SHUTDOWN if any other error ocurred. + * Returns -DWC_E_AGAIN if the STALL cannot be sent and must be tried again later + * Returns 0 on success. */ +extern int dwc_otg_pcd_ep_halt(dwc_otg_pcd_t * pcd, void *ep_handle, int value); + +/** This function */ +extern int dwc_otg_pcd_ep_wedge(dwc_otg_pcd_t * pcd, void *ep_handle); + +/** This function should be called on every hardware interrupt */ +extern int32_t dwc_otg_pcd_handle_intr(dwc_otg_pcd_t * pcd); + +/** This function returns current frame number */ +extern int dwc_otg_pcd_get_frame_number(dwc_otg_pcd_t * pcd); + +/** + * Start isochronous transfers on the endpoint referenced by ep_handle. + * For isochronous transfers duble buffering is used. + * After processing each of buffers comlete callback will be called with + * status for each transaction. + * + * @param pcd The PCD + * @param ep_handle The handle of the endpoint + * @param buf0 The virtual address of first data buffer + * @param buf1 The virtual address of second data buffer + * @param dma0 The DMA address of first data buffer + * @param dma1 The DMA address of second data buffer + * @param sync_frame Data pattern frame number + * @param dp_frame Data size for pattern frame + * @param data_per_frame Data size for regular frame + * @param start_frame Frame number to start transfers, if -1 then start transfers ASAP. + * @param buf_proc_intrvl Interval of ISOC Buffer processing + * @param req_handle Handle of ISOC request + * @param atomic_alloc Specefies whether to perform atomic allocation for + * internal data structures. + * + * Returns -DWC_E_NO_MEMORY if there is no enough memory. + * Returns -DWC_E_INVALID if incorrect arguments are passed to the function. + * Returns -DW_E_SHUTDOWN for any other error. + * Returns 0 on success + */ +extern int dwc_otg_pcd_iso_ep_start(dwc_otg_pcd_t * pcd, void *ep_handle, + uint8_t * buf0, uint8_t * buf1, + dwc_dma_t dma0, dwc_dma_t dma1, + int sync_frame, int dp_frame, + int data_per_frame, int start_frame, + int buf_proc_intrvl, void *req_handle, + int atomic_alloc); + +/** Stop ISOC transfers on endpoint referenced by ep_handle. + * + * @param pcd The PCD + * @param ep_handle The handle of the endpoint + * @param req_handle Handle of ISOC request + * + * Returns -DWC_E_INVALID if incorrect arguments are passed to the function + * Returns 0 on success + */ +int dwc_otg_pcd_iso_ep_stop(dwc_otg_pcd_t * pcd, void *ep_handle, + void *req_handle); + +/** Get ISOC packet status. + * + * @param pcd The PCD + * @param ep_handle The handle of the endpoint + * @param iso_req_handle Isochronoush request handle + * @param packet Number of packet + * @param status Out parameter for returning status + * @param actual Out parameter for returning actual length + * @param offset Out parameter for returning offset + * + */ +extern void dwc_otg_pcd_get_iso_packet_params(dwc_otg_pcd_t * pcd, + void *ep_handle, + void *iso_req_handle, int packet, + int *status, int *actual, + int *offset); + +/** Get ISOC packet count. + * + * @param pcd The PCD + * @param ep_handle The handle of the endpoint + * @param iso_req_handle + */ +extern int dwc_otg_pcd_get_iso_packet_count(dwc_otg_pcd_t * pcd, + void *ep_handle, + void *iso_req_handle); + +/** This function starts the SRP Protocol if no session is in progress. If + * a session is already in progress, but the device is suspended, + * remote wakeup signaling is started. + */ +extern int dwc_otg_pcd_wakeup(dwc_otg_pcd_t * pcd); + +/** This function returns 1 if LPM support is enabled, and 0 otherwise. */ +extern int dwc_otg_pcd_is_lpm_enabled(dwc_otg_pcd_t * pcd); + +/** This function returns 1 if remote wakeup is allowed and 0, otherwise. */ +extern int dwc_otg_pcd_get_rmwkup_enable(dwc_otg_pcd_t * pcd); + +/** Initiate SRP */ +extern void dwc_otg_pcd_initiate_srp(dwc_otg_pcd_t * pcd); + +/** Starts remote wakeup signaling. */ +extern void dwc_otg_pcd_remote_wakeup(dwc_otg_pcd_t * pcd, int set); + +/** Starts micorsecond soft disconnect. */ +extern void dwc_otg_pcd_disconnect_us(dwc_otg_pcd_t * pcd, int no_of_usecs); +/** This function returns whether device is dualspeed.*/ +extern uint32_t dwc_otg_pcd_is_dualspeed(dwc_otg_pcd_t * pcd); + +/** This function returns whether device is otg. */ +extern uint32_t dwc_otg_pcd_is_otg(dwc_otg_pcd_t * pcd); + +/** These functions allow to get hnp parameters */ +extern uint32_t get_b_hnp_enable(dwc_otg_pcd_t * pcd); +extern uint32_t get_a_hnp_support(dwc_otg_pcd_t * pcd); +extern uint32_t get_a_alt_hnp_support(dwc_otg_pcd_t * pcd); + +/** CFI specific Interface functions */ +/** Allocate a cfi buffer */ +extern uint8_t *cfiw_ep_alloc_buffer(dwc_otg_pcd_t * pcd, void *pep, + dwc_dma_t * addr, size_t buflen, + int flags); + +extern int pcd_init( +#ifdef LM_INTERFACE + struct lm_device *_dev +#elif defined(PCI_INTERFACE) + struct pci_dev *_dev +#elif defined(PLATFORM_INTERFACE) + struct platform_device *dev +#endif + ); + +extern void pcd_remove( +#ifdef LM_INTERFACE + struct lm_device *_dev +#elif defined(PCI_INTERFACE) + struct pci_dev *_dev +#elif defined(PLATFORM_INTERFACE) + struct platform_device *_dev +#endif + ); + +/******************************************************************************/ + +/** @} */ + +#endif /* __DWC_PCD_IF_H__ */ + +#endif /* DWC_HOST_ONLY */ diff --git a/drivers/usb/host/dwc_otg/dwc_otg_pcd_intr.c b/drivers/usb/host/dwc_otg/dwc_otg_pcd_intr.c new file mode 100644 index 00000000000000..9a255a96c209b3 --- /dev/null +++ b/drivers/usb/host/dwc_otg/dwc_otg_pcd_intr.c @@ -0,0 +1,5148 @@ +/* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_pcd_intr.c $ + * $Revision: #116 $ + * $Date: 2012/08/10 $ + * $Change: 2047372 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ +#ifndef DWC_HOST_ONLY + +#include "dwc_otg_pcd.h" + +#ifdef DWC_UTE_CFI +#include "dwc_otg_cfi.h" +#endif + +#ifdef DWC_UTE_PER_IO +extern void complete_xiso_ep(dwc_otg_pcd_ep_t * ep); +#endif +//#define PRINT_CFI_DMA_DESCS + +#define DEBUG_EP0 + +/** + * This function updates OTG. + */ +static void dwc_otg_pcd_update_otg(dwc_otg_pcd_t * pcd, const unsigned reset) +{ + + if (reset) { + pcd->b_hnp_enable = 0; + pcd->a_hnp_support = 0; + pcd->a_alt_hnp_support = 0; + } + + if (pcd->fops->hnp_changed) { + pcd->fops->hnp_changed(pcd); + } +} + +/** @file + * This file contains the implementation of the PCD Interrupt handlers. + * + * The PCD handles the device interrupts. Many conditions can cause a + * device interrupt. When an interrupt occurs, the device interrupt + * service routine determines the cause of the interrupt and + * dispatches handling to the appropriate function. These interrupt + * handling functions are described below. + * All interrupt registers are processed from LSB to MSB. + */ + +/** + * This function prints the ep0 state for debug purposes. + */ +static inline void print_ep0_state(dwc_otg_pcd_t * pcd) +{ +#ifdef DEBUG + char str[40]; + + switch (pcd->ep0state) { + case EP0_DISCONNECT: + dwc_strcpy(str, "EP0_DISCONNECT"); + break; + case EP0_IDLE: + dwc_strcpy(str, "EP0_IDLE"); + break; + case EP0_IN_DATA_PHASE: + dwc_strcpy(str, "EP0_IN_DATA_PHASE"); + break; + case EP0_OUT_DATA_PHASE: + dwc_strcpy(str, "EP0_OUT_DATA_PHASE"); + break; + case EP0_IN_STATUS_PHASE: + dwc_strcpy(str, "EP0_IN_STATUS_PHASE"); + break; + case EP0_OUT_STATUS_PHASE: + dwc_strcpy(str, "EP0_OUT_STATUS_PHASE"); + break; + case EP0_STALL: + dwc_strcpy(str, "EP0_STALL"); + break; + default: + dwc_strcpy(str, "EP0_INVALID"); + } + + DWC_DEBUGPL(DBG_ANY, "%s(%d)\n", str, pcd->ep0state); +#endif +} + +/** + * This function calculate the size of the payload in the memory + * for out endpoints and prints size for debug purposes(used in + * 2.93a DevOutNak feature). + */ +static inline void print_memory_payload(dwc_otg_pcd_t * pcd, dwc_ep_t * ep) +{ +#ifdef DEBUG + deptsiz_data_t deptsiz_init = {.d32 = 0 }; + deptsiz_data_t deptsiz_updt = {.d32 = 0 }; + int pack_num; + unsigned payload; + + deptsiz_init.d32 = pcd->core_if->start_doeptsiz_val[ep->num]; + deptsiz_updt.d32 = + DWC_READ_REG32(&pcd->core_if->dev_if-> + out_ep_regs[ep->num]->doeptsiz); + /* Payload will be */ + payload = deptsiz_init.b.xfersize - deptsiz_updt.b.xfersize; + /* Packet count is decremented every time a packet + * is written to the RxFIFO not in to the external memory + * So, if payload == 0, then it means no packet was sent to ext memory*/ + pack_num = (!payload) ? 0 : (deptsiz_init.b.pktcnt - deptsiz_updt.b.pktcnt); + DWC_DEBUGPL(DBG_PCDV, + "Payload for EP%d-%s\n", + ep->num, (ep->is_in ? "IN" : "OUT")); + DWC_DEBUGPL(DBG_PCDV, + "Number of transfered bytes = 0x%08x\n", payload); + DWC_DEBUGPL(DBG_PCDV, + "Number of transfered packets = %d\n", pack_num); +#endif +} + + +#ifdef DWC_UTE_CFI +static inline void print_desc(struct dwc_otg_dma_desc *ddesc, + const uint8_t * epname, int descnum) +{ + CFI_INFO + ("%s DMA_DESC(%d) buf=0x%08x bytes=0x%04x; sp=0x%x; l=0x%x; sts=0x%02x; bs=0x%02x\n", + epname, descnum, ddesc->buf, ddesc->status.b.bytes, + ddesc->status.b.sp, ddesc->status.b.l, ddesc->status.b.sts, + ddesc->status.b.bs); +} +#endif + +/** + * This function returns pointer to in ep struct with number ep_num + */ +static inline dwc_otg_pcd_ep_t *get_in_ep(dwc_otg_pcd_t * pcd, uint32_t ep_num) +{ + int i; + int num_in_eps = GET_CORE_IF(pcd)->dev_if->num_in_eps; + if (ep_num == 0) { + return &pcd->ep0; + } else { + for (i = 0; i < num_in_eps; ++i) { + if (pcd->in_ep[i].dwc_ep.num == ep_num) + return &pcd->in_ep[i]; + } + return 0; + } +} + +/** + * This function returns pointer to out ep struct with number ep_num + */ +static inline dwc_otg_pcd_ep_t *get_out_ep(dwc_otg_pcd_t * pcd, uint32_t ep_num) +{ + int i; + int num_out_eps = GET_CORE_IF(pcd)->dev_if->num_out_eps; + if (ep_num == 0) { + return &pcd->ep0; + } else { + for (i = 0; i < num_out_eps; ++i) { + if (pcd->out_ep[i].dwc_ep.num == ep_num) + return &pcd->out_ep[i]; + } + return 0; + } +} + +/** + * This functions gets a pointer to an EP from the wIndex address + * value of the control request. + */ +dwc_otg_pcd_ep_t *get_ep_by_addr(dwc_otg_pcd_t * pcd, u16 wIndex) +{ + dwc_otg_pcd_ep_t *ep; + uint32_t ep_num = UE_GET_ADDR(wIndex); + + if (ep_num == 0) { + ep = &pcd->ep0; + } else if (UE_GET_DIR(wIndex) == UE_DIR_IN) { /* in ep */ + ep = &pcd->in_ep[ep_num - 1]; + } else { + ep = &pcd->out_ep[ep_num - 1]; + } + + return ep; +} + +/** + * This function checks the EP request queue, if the queue is not + * empty the next request is started. + */ +void start_next_request(dwc_otg_pcd_ep_t * ep) +{ + dwc_otg_pcd_request_t *req = 0; + uint32_t max_transfer = + GET_CORE_IF(ep->pcd)->core_params->max_transfer_size; + +#ifdef DWC_UTE_CFI + struct dwc_otg_pcd *pcd; + pcd = ep->pcd; +#endif + + if (!DWC_CIRCLEQ_EMPTY(&ep->queue)) { + req = DWC_CIRCLEQ_FIRST(&ep->queue); + +#ifdef DWC_UTE_CFI + if (ep->dwc_ep.buff_mode != BM_STANDARD) { + ep->dwc_ep.cfi_req_len = req->length; + pcd->cfi->ops.build_descriptors(pcd->cfi, pcd, ep, req); + } else { +#endif + /* Setup and start the Transfer */ + if (req->dw_align_buf) { + ep->dwc_ep.dma_addr = req->dw_align_buf_dma; + ep->dwc_ep.start_xfer_buff = req->dw_align_buf; + ep->dwc_ep.xfer_buff = req->dw_align_buf; + } else { + ep->dwc_ep.dma_addr = req->dma; + ep->dwc_ep.start_xfer_buff = req->buf; + ep->dwc_ep.xfer_buff = req->buf; + } + ep->dwc_ep.sent_zlp = 0; + ep->dwc_ep.total_len = req->length; + ep->dwc_ep.xfer_len = 0; + ep->dwc_ep.xfer_count = 0; + + ep->dwc_ep.maxxfer = max_transfer; + if (GET_CORE_IF(ep->pcd)->dma_desc_enable) { + uint32_t out_max_xfer = DDMA_MAX_TRANSFER_SIZE + - (DDMA_MAX_TRANSFER_SIZE % 4); + if (ep->dwc_ep.is_in) { + if (ep->dwc_ep.maxxfer > + DDMA_MAX_TRANSFER_SIZE) { + ep->dwc_ep.maxxfer = + DDMA_MAX_TRANSFER_SIZE; + } + } else { + if (ep->dwc_ep.maxxfer > out_max_xfer) { + ep->dwc_ep.maxxfer = + out_max_xfer; + } + } + } + if (ep->dwc_ep.maxxfer < ep->dwc_ep.total_len) { + ep->dwc_ep.maxxfer -= + (ep->dwc_ep.maxxfer % ep->dwc_ep.maxpacket); + } + if (req->sent_zlp) { + if ((ep->dwc_ep.total_len % + ep->dwc_ep.maxpacket == 0) + && (ep->dwc_ep.total_len != 0)) { + ep->dwc_ep.sent_zlp = 1; + } + + } +#ifdef DWC_UTE_CFI + } +#endif + dwc_otg_ep_start_transfer(GET_CORE_IF(ep->pcd), &ep->dwc_ep); + } else if (ep->dwc_ep.type == DWC_OTG_EP_TYPE_ISOC) { + DWC_PRINTF("There are no more ISOC requests \n"); + ep->dwc_ep.frame_num = 0xFFFFFFFF; + } +} + +/** + * This function handles the SOF Interrupts. At this time the SOF + * Interrupt is disabled. + */ +static int32_t dwc_otg_pcd_handle_sof_intr(dwc_otg_pcd_t * pcd) +{ + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + + gintsts_data_t gintsts; + + DWC_DEBUGPL(DBG_PCD, "SOF\n"); + + /* Clear interrupt */ + gintsts.d32 = 0; + gintsts.b.sofintr = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, gintsts.d32); + + return 1; +} + +/** + * This function handles the Rx Status Queue Level Interrupt, which + * indicates that there is a least one packet in the Rx FIFO. The + * packets are moved from the FIFO to memory, where they will be + * processed when the Endpoint Interrupt Register indicates Transfer + * Complete or SETUP Phase Done. + * + * Repeat the following until the Rx Status Queue is empty: + * -# Read the Receive Status Pop Register (GRXSTSP) to get Packet + * info + * -# If Receive FIFO is empty then skip to step Clear the interrupt + * and exit + * -# If SETUP Packet call dwc_otg_read_setup_packet to copy the + * SETUP data to the buffer + * -# If OUT Data Packet call dwc_otg_read_packet to copy the data + * to the destination buffer + */ +static int32_t dwc_otg_pcd_handle_rx_status_q_level_intr(dwc_otg_pcd_t * pcd) +{ + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs; + gintmsk_data_t gintmask = {.d32 = 0 }; + device_grxsts_data_t status; + dwc_otg_pcd_ep_t *ep; + gintsts_data_t gintsts; +#ifdef DEBUG + static char *dpid_str[] = { "D0", "D2", "D1", "MDATA" }; +#endif + + //DWC_DEBUGPL(DBG_PCDV, "%s(%p)\n", __func__, _pcd); + /* Disable the Rx Status Queue Level interrupt */ + gintmask.b.rxstsqlvl = 1; + DWC_MODIFY_REG32(&global_regs->gintmsk, gintmask.d32, 0); + + /* Get the Status from the top of the FIFO */ + status.d32 = DWC_READ_REG32(&global_regs->grxstsp); + + DWC_DEBUGPL(DBG_PCD, "EP:%d BCnt:%d DPID:%s " + "pktsts:%x Frame:%d(0x%0x)\n", + status.b.epnum, status.b.bcnt, + dpid_str[status.b.dpid], + status.b.pktsts, status.b.fn, status.b.fn); + /* Get pointer to EP structure */ + ep = get_out_ep(pcd, status.b.epnum); + + switch (status.b.pktsts) { + case DWC_DSTS_GOUT_NAK: + DWC_DEBUGPL(DBG_PCDV, "Global OUT NAK\n"); + break; + case DWC_STS_DATA_UPDT: + DWC_DEBUGPL(DBG_PCDV, "OUT Data Packet\n"); + if (status.b.bcnt && ep->dwc_ep.xfer_buff) { + /** @todo NGS Check for buffer overflow? */ + dwc_otg_read_packet(core_if, + ep->dwc_ep.xfer_buff, + status.b.bcnt); + ep->dwc_ep.xfer_count += status.b.bcnt; + ep->dwc_ep.xfer_buff += status.b.bcnt; + } + break; + case DWC_STS_XFER_COMP: + DWC_DEBUGPL(DBG_PCDV, "OUT Complete\n"); + break; + case DWC_DSTS_SETUP_COMP: +#ifdef DEBUG_EP0 + DWC_DEBUGPL(DBG_PCDV, "Setup Complete\n"); +#endif + break; + case DWC_DSTS_SETUP_UPDT: + dwc_otg_read_setup_packet(core_if, pcd->setup_pkt->d32); +#ifdef DEBUG_EP0 + DWC_DEBUGPL(DBG_PCD, + "SETUP PKT: %02x.%02x v%04x i%04x l%04x\n", + pcd->setup_pkt->req.bmRequestType, + pcd->setup_pkt->req.bRequest, + UGETW(pcd->setup_pkt->req.wValue), + UGETW(pcd->setup_pkt->req.wIndex), + UGETW(pcd->setup_pkt->req.wLength)); +#endif + ep->dwc_ep.xfer_count += status.b.bcnt; + break; + default: + DWC_DEBUGPL(DBG_PCDV, "Invalid Packet Status (0x%0x)\n", + status.b.pktsts); + break; + } + + /* Enable the Rx Status Queue Level interrupt */ + DWC_MODIFY_REG32(&global_regs->gintmsk, 0, gintmask.d32); + /* Clear interrupt */ + gintsts.d32 = 0; + gintsts.b.rxstsqlvl = 1; + DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32); + + //DWC_DEBUGPL(DBG_PCDV, "EXIT: %s\n", __func__); + return 1; +} + +/** + * This function examines the Device IN Token Learning Queue to + * determine the EP number of the last IN token received. This + * implementation is for the Mass Storage device where there are only + * 2 IN EPs (Control-IN and BULK-IN). + * + * The EP numbers for the first six IN Tokens are in DTKNQR1 and there + * are 8 EP Numbers in each of the other possible DTKNQ Registers. + * + * @param core_if Programming view of DWC_otg controller. + * + */ +static inline int get_ep_of_last_in_token(dwc_otg_core_if_t * core_if) +{ + dwc_otg_device_global_regs_t *dev_global_regs = + core_if->dev_if->dev_global_regs; + const uint32_t TOKEN_Q_DEPTH = core_if->hwcfg2.b.dev_token_q_depth; + /* Number of Token Queue Registers */ + const int DTKNQ_REG_CNT = (TOKEN_Q_DEPTH + 7) / 8; + dtknq1_data_t dtknqr1; + uint32_t in_tkn_epnums[4]; + int ndx = 0; + int i = 0; + volatile uint32_t *addr = &dev_global_regs->dtknqr1; + int epnum = 0; + + //DWC_DEBUGPL(DBG_PCD,"dev_token_q_depth=%d\n",TOKEN_Q_DEPTH); + + /* Read the DTKNQ Registers */ + for (i = 0; i < DTKNQ_REG_CNT; i++) { + in_tkn_epnums[i] = DWC_READ_REG32(addr); + DWC_DEBUGPL(DBG_PCDV, "DTKNQR%d=0x%08x\n", i + 1, + in_tkn_epnums[i]); + if (addr == &dev_global_regs->dvbusdis) { + addr = &dev_global_regs->dtknqr3_dthrctl; + } else { + ++addr; + } + + } + + /* Copy the DTKNQR1 data to the bit field. */ + dtknqr1.d32 = in_tkn_epnums[0]; + /* Get the EP numbers */ + in_tkn_epnums[0] = dtknqr1.b.epnums0_5; + ndx = dtknqr1.b.intknwptr - 1; + + //DWC_DEBUGPL(DBG_PCDV,"ndx=%d\n",ndx); + if (ndx == -1) { + /** @todo Find a simpler way to calculate the max + * queue position.*/ + int cnt = TOKEN_Q_DEPTH; + if (TOKEN_Q_DEPTH <= 6) { + cnt = TOKEN_Q_DEPTH - 1; + } else if (TOKEN_Q_DEPTH <= 14) { + cnt = TOKEN_Q_DEPTH - 7; + } else if (TOKEN_Q_DEPTH <= 22) { + cnt = TOKEN_Q_DEPTH - 15; + } else { + cnt = TOKEN_Q_DEPTH - 23; + } + epnum = (in_tkn_epnums[DTKNQ_REG_CNT - 1] >> (cnt * 4)) & 0xF; + } else { + if (ndx <= 5) { + epnum = (in_tkn_epnums[0] >> (ndx * 4)) & 0xF; + } else if (ndx <= 13) { + ndx -= 6; + epnum = (in_tkn_epnums[1] >> (ndx * 4)) & 0xF; + } else if (ndx <= 21) { + ndx -= 14; + epnum = (in_tkn_epnums[2] >> (ndx * 4)) & 0xF; + } else if (ndx <= 29) { + ndx -= 22; + epnum = (in_tkn_epnums[3] >> (ndx * 4)) & 0xF; + } + } + //DWC_DEBUGPL(DBG_PCD,"epnum=%d\n",epnum); + return epnum; +} + +/** + * This interrupt occurs when the non-periodic Tx FIFO is half-empty. + * The active request is checked for the next packet to be loaded into + * the non-periodic Tx FIFO. + */ +static int32_t dwc_otg_pcd_handle_np_tx_fifo_empty_intr(dwc_otg_pcd_t * pcd) +{ + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs; + dwc_otg_dev_in_ep_regs_t *ep_regs; + gnptxsts_data_t txstatus = {.d32 = 0 }; + gintsts_data_t gintsts; + + int epnum = 0; + dwc_otg_pcd_ep_t *ep = 0; + uint32_t len = 0; + int dwords; + + /* Get the epnum from the IN Token Learning Queue. */ + epnum = get_ep_of_last_in_token(core_if); + ep = get_in_ep(pcd, epnum); + + DWC_DEBUGPL(DBG_PCD, "NP TxFifo Empty: %d \n", epnum); + + ep_regs = core_if->dev_if->in_ep_regs[epnum]; + + len = ep->dwc_ep.xfer_len - ep->dwc_ep.xfer_count; + if (len > ep->dwc_ep.maxpacket) { + len = ep->dwc_ep.maxpacket; + } + dwords = (len + 3) / 4; + + /* While there is space in the queue and space in the FIFO and + * More data to tranfer, Write packets to the Tx FIFO */ + txstatus.d32 = DWC_READ_REG32(&global_regs->gnptxsts); + DWC_DEBUGPL(DBG_PCDV, "b4 GNPTXSTS=0x%08x\n", txstatus.d32); + + while (txstatus.b.nptxqspcavail > 0 && + txstatus.b.nptxfspcavail > dwords && + ep->dwc_ep.xfer_count < ep->dwc_ep.xfer_len) { + /* Write the FIFO */ + dwc_otg_ep_write_packet(core_if, &ep->dwc_ep, 0); + len = ep->dwc_ep.xfer_len - ep->dwc_ep.xfer_count; + + if (len > ep->dwc_ep.maxpacket) { + len = ep->dwc_ep.maxpacket; + } + + dwords = (len + 3) / 4; + txstatus.d32 = DWC_READ_REG32(&global_regs->gnptxsts); + DWC_DEBUGPL(DBG_PCDV, "GNPTXSTS=0x%08x\n", txstatus.d32); + } + + DWC_DEBUGPL(DBG_PCDV, "GNPTXSTS=0x%08x\n", + DWC_READ_REG32(&global_regs->gnptxsts)); + + /* Clear interrupt */ + gintsts.d32 = 0; + gintsts.b.nptxfempty = 1; + DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32); + + return 1; +} + +/** + * This function is called when dedicated Tx FIFO Empty interrupt occurs. + * The active request is checked for the next packet to be loaded into + * apropriate Tx FIFO. + */ +static int32_t write_empty_tx_fifo(dwc_otg_pcd_t * pcd, uint32_t epnum) +{ + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + dwc_otg_dev_if_t *dev_if = core_if->dev_if; + dwc_otg_dev_in_ep_regs_t *ep_regs; + dtxfsts_data_t txstatus = {.d32 = 0 }; + dwc_otg_pcd_ep_t *ep = 0; + uint32_t len = 0; + int dwords; + + ep = get_in_ep(pcd, epnum); + + DWC_DEBUGPL(DBG_PCD, "Dedicated TxFifo Empty: %d \n", epnum); + + ep_regs = core_if->dev_if->in_ep_regs[epnum]; + + len = ep->dwc_ep.xfer_len - ep->dwc_ep.xfer_count; + + if (len > ep->dwc_ep.maxpacket) { + len = ep->dwc_ep.maxpacket; + } + + dwords = (len + 3) / 4; + + /* While there is space in the queue and space in the FIFO and + * More data to tranfer, Write packets to the Tx FIFO */ + txstatus.d32 = DWC_READ_REG32(&dev_if->in_ep_regs[epnum]->dtxfsts); + DWC_DEBUGPL(DBG_PCDV, "b4 dtxfsts[%d]=0x%08x\n", epnum, txstatus.d32); + + while (txstatus.b.txfspcavail > dwords && + ep->dwc_ep.xfer_count < ep->dwc_ep.xfer_len && + ep->dwc_ep.xfer_len != 0) { + /* Write the FIFO */ + dwc_otg_ep_write_packet(core_if, &ep->dwc_ep, 0); + + len = ep->dwc_ep.xfer_len - ep->dwc_ep.xfer_count; + if (len > ep->dwc_ep.maxpacket) { + len = ep->dwc_ep.maxpacket; + } + + dwords = (len + 3) / 4; + txstatus.d32 = + DWC_READ_REG32(&dev_if->in_ep_regs[epnum]->dtxfsts); + DWC_DEBUGPL(DBG_PCDV, "dtxfsts[%d]=0x%08x\n", epnum, + txstatus.d32); + } + + DWC_DEBUGPL(DBG_PCDV, "b4 dtxfsts[%d]=0x%08x\n", epnum, + DWC_READ_REG32(&dev_if->in_ep_regs[epnum]->dtxfsts)); + + return 1; +} + +/** + * This function is called when the Device is disconnected. It stops + * any active requests and informs the Gadget driver of the + * disconnect. + */ +void dwc_otg_pcd_stop(dwc_otg_pcd_t * pcd) +{ + int i, num_in_eps, num_out_eps; + dwc_otg_pcd_ep_t *ep; + + gintmsk_data_t intr_mask = {.d32 = 0 }; + + DWC_SPINLOCK(pcd->lock); + + num_in_eps = GET_CORE_IF(pcd)->dev_if->num_in_eps; + num_out_eps = GET_CORE_IF(pcd)->dev_if->num_out_eps; + + DWC_DEBUGPL(DBG_PCDV, "%s() \n", __func__); + /* don't disconnect drivers more than once */ + if (pcd->ep0state == EP0_DISCONNECT) { + DWC_DEBUGPL(DBG_ANY, "%s() Already Disconnected\n", __func__); + DWC_SPINUNLOCK(pcd->lock); + return; + } + pcd->ep0state = EP0_DISCONNECT; + + /* Reset the OTG state. */ + dwc_otg_pcd_update_otg(pcd, 1); + + /* Disable the NP Tx Fifo Empty Interrupt. */ + intr_mask.b.nptxfempty = 1; + DWC_MODIFY_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintmsk, + intr_mask.d32, 0); + + /* Flush the FIFOs */ + /**@todo NGS Flush Periodic FIFOs */ + dwc_otg_flush_tx_fifo(GET_CORE_IF(pcd), 0x10); + dwc_otg_flush_rx_fifo(GET_CORE_IF(pcd)); + + /* prevent new request submissions, kill any outstanding requests */ + ep = &pcd->ep0; + dwc_otg_request_nuke(ep); + /* prevent new request submissions, kill any outstanding requests */ + for (i = 0; i < num_in_eps; i++) { + dwc_otg_pcd_ep_t *ep = &pcd->in_ep[i]; + dwc_otg_request_nuke(ep); + } + /* prevent new request submissions, kill any outstanding requests */ + for (i = 0; i < num_out_eps; i++) { + dwc_otg_pcd_ep_t *ep = &pcd->out_ep[i]; + dwc_otg_request_nuke(ep); + } + + /* report disconnect; the driver is already quiesced */ + if (pcd->fops->disconnect) { + DWC_SPINUNLOCK(pcd->lock); + pcd->fops->disconnect(pcd); + DWC_SPINLOCK(pcd->lock); + } + DWC_SPINUNLOCK(pcd->lock); +} + +/** + * This interrupt indicates that ... + */ +static int32_t dwc_otg_pcd_handle_i2c_intr(dwc_otg_pcd_t * pcd) +{ + gintmsk_data_t intr_mask = {.d32 = 0 }; + gintsts_data_t gintsts; + + DWC_PRINTF("INTERRUPT Handler not implemented for %s\n", "i2cintr"); + intr_mask.b.i2cintr = 1; + DWC_MODIFY_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintmsk, + intr_mask.d32, 0); + + /* Clear interrupt */ + gintsts.d32 = 0; + gintsts.b.i2cintr = 1; + DWC_WRITE_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintsts, + gintsts.d32); + return 1; +} + +/** + * This interrupt indicates that ... + */ +static int32_t dwc_otg_pcd_handle_early_suspend_intr(dwc_otg_pcd_t * pcd) +{ + gintsts_data_t gintsts; +#if defined(VERBOSE) + DWC_PRINTF("Early Suspend Detected\n"); +#endif + + /* Clear interrupt */ + gintsts.d32 = 0; + gintsts.b.erlysuspend = 1; + DWC_WRITE_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintsts, + gintsts.d32); + return 1; +} + +/** + * This function configures EPO to receive SETUP packets. + * + * @todo NGS: Update the comments from the HW FS. + * + * -# Program the following fields in the endpoint specific registers + * for Control OUT EP 0, in order to receive a setup packet + * - DOEPTSIZ0.Packet Count = 3 (To receive up to 3 back to back + * setup packets) + * - DOEPTSIZE0.Transfer Size = 24 Bytes (To receive up to 3 back + * to back setup packets) + * - In DMA mode, DOEPDMA0 Register with a memory address to + * store any setup packets received + * + * @param core_if Programming view of DWC_otg controller. + * @param pcd Programming view of the PCD. + */ +static inline void ep0_out_start(dwc_otg_core_if_t * core_if, + dwc_otg_pcd_t * pcd) +{ + dwc_otg_dev_if_t *dev_if = core_if->dev_if; + deptsiz0_data_t doeptsize0 = {.d32 = 0 }; + dwc_otg_dev_dma_desc_t *dma_desc; + depctl_data_t doepctl = {.d32 = 0 }; + +#ifdef VERBOSE + DWC_DEBUGPL(DBG_PCDV, "%s() doepctl0=%0x\n", __func__, + DWC_READ_REG32(&dev_if->out_ep_regs[0]->doepctl)); +#endif + if (core_if->snpsid >= OTG_CORE_REV_3_00a) { + doepctl.d32 = DWC_READ_REG32(&dev_if->out_ep_regs[0]->doepctl); + if (doepctl.b.epena) { + return; + } + } + + doeptsize0.b.supcnt = 3; + doeptsize0.b.pktcnt = 1; + doeptsize0.b.xfersize = 8 * 3; + + if (core_if->dma_enable) { + if (!core_if->dma_desc_enable) { + /** put here as for Hermes mode deptisz register should not be written */ + DWC_WRITE_REG32(&dev_if->out_ep_regs[0]->doeptsiz, + doeptsize0.d32); + + /** @todo dma needs to handle multiple setup packets (up to 3) */ + DWC_WRITE_REG32(&dev_if->out_ep_regs[0]->doepdma, + pcd->setup_pkt_dma_handle); + } else { + dev_if->setup_desc_index = + (dev_if->setup_desc_index + 1) & 1; + dma_desc = + dev_if->setup_desc_addr[dev_if->setup_desc_index]; + + /** DMA Descriptor Setup */ + dma_desc->status.b.bs = BS_HOST_BUSY; + if (core_if->snpsid >= OTG_CORE_REV_3_00a) { + dma_desc->status.b.sr = 0; + dma_desc->status.b.mtrf = 0; + } + dma_desc->status.b.l = 1; + dma_desc->status.b.ioc = 1; + dma_desc->status.b.bytes = pcd->ep0.dwc_ep.maxpacket; + dma_desc->buf = pcd->setup_pkt_dma_handle; + dma_desc->status.b.sts = 0; + dma_desc->status.b.bs = BS_HOST_READY; + + /** DOEPDMA0 Register write */ + DWC_WRITE_REG32(&dev_if->out_ep_regs[0]->doepdma, + dev_if->dma_setup_desc_addr + [dev_if->setup_desc_index]); + } + + } else { + /** put here as for Hermes mode deptisz register should not be written */ + DWC_WRITE_REG32(&dev_if->out_ep_regs[0]->doeptsiz, + doeptsize0.d32); + } + + /** DOEPCTL0 Register write cnak will be set after setup interrupt */ + doepctl.d32 = 0; + doepctl.b.epena = 1; + if (core_if->snpsid <= OTG_CORE_REV_2_94a) { + doepctl.b.cnak = 1; + DWC_WRITE_REG32(&dev_if->out_ep_regs[0]->doepctl, doepctl.d32); + } else { + DWC_MODIFY_REG32(&dev_if->out_ep_regs[0]->doepctl, 0, doepctl.d32); + } + +#ifdef VERBOSE + DWC_DEBUGPL(DBG_PCDV, "doepctl0=%0x\n", + DWC_READ_REG32(&dev_if->out_ep_regs[0]->doepctl)); + DWC_DEBUGPL(DBG_PCDV, "diepctl0=%0x\n", + DWC_READ_REG32(&dev_if->in_ep_regs[0]->diepctl)); +#endif +} + +/** + * This interrupt occurs when a USB Reset is detected. When the USB + * Reset Interrupt occurs the device state is set to DEFAULT and the + * EP0 state is set to IDLE. + * -# Set the NAK bit for all OUT endpoints (DOEPCTLn.SNAK = 1) + * -# Unmask the following interrupt bits + * - DAINTMSK.INEP0 = 1 (Control 0 IN endpoint) + * - DAINTMSK.OUTEP0 = 1 (Control 0 OUT endpoint) + * - DOEPMSK.SETUP = 1 + * - DOEPMSK.XferCompl = 1 + * - DIEPMSK.XferCompl = 1 + * - DIEPMSK.TimeOut = 1 + * -# Program the following fields in the endpoint specific registers + * for Control OUT EP 0, in order to receive a setup packet + * - DOEPTSIZ0.Packet Count = 3 (To receive up to 3 back to back + * setup packets) + * - DOEPTSIZE0.Transfer Size = 24 Bytes (To receive up to 3 back + * to back setup packets) + * - In DMA mode, DOEPDMA0 Register with a memory address to + * store any setup packets received + * At this point, all the required initialization, except for enabling + * the control 0 OUT endpoint is done, for receiving SETUP packets. + */ +static int32_t dwc_otg_pcd_handle_usb_reset_intr(dwc_otg_pcd_t * pcd) +{ + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + dwc_otg_dev_if_t *dev_if = core_if->dev_if; + depctl_data_t doepctl = {.d32 = 0 }; + depctl_data_t diepctl = {.d32 = 0 }; + daint_data_t daintmsk = {.d32 = 0 }; + doepmsk_data_t doepmsk = {.d32 = 0 }; + diepmsk_data_t diepmsk = {.d32 = 0 }; + dcfg_data_t dcfg = {.d32 = 0 }; + grstctl_t resetctl = {.d32 = 0 }; + dctl_data_t dctl = {.d32 = 0 }; + int i = 0; + gintsts_data_t gintsts; + pcgcctl_data_t power = {.d32 = 0 }; + + power.d32 = DWC_READ_REG32(core_if->pcgcctl); + if (power.b.stoppclk) { + power.d32 = 0; + power.b.stoppclk = 1; + DWC_MODIFY_REG32(core_if->pcgcctl, power.d32, 0); + + power.b.pwrclmp = 1; + DWC_MODIFY_REG32(core_if->pcgcctl, power.d32, 0); + + power.b.rstpdwnmodule = 1; + DWC_MODIFY_REG32(core_if->pcgcctl, power.d32, 0); + } + + core_if->lx_state = DWC_OTG_L0; + + DWC_PRINTF("USB RESET\n"); +#ifdef DWC_EN_ISOC + for (i = 1; i < 16; ++i) { + dwc_otg_pcd_ep_t *ep; + dwc_ep_t *dwc_ep; + ep = get_in_ep(pcd, i); + if (ep != 0) { + dwc_ep = &ep->dwc_ep; + dwc_ep->next_frame = 0xffffffff; + } + } +#endif /* DWC_EN_ISOC */ + + /* reset the HNP settings */ + dwc_otg_pcd_update_otg(pcd, 1); + + /* Clear the Remote Wakeup Signalling */ + dctl.b.rmtwkupsig = 1; + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->dctl, dctl.d32, 0); + + /* Set NAK for all OUT EPs */ + doepctl.b.snak = 1; + for (i = 0; i <= dev_if->num_out_eps; i++) { + DWC_WRITE_REG32(&dev_if->out_ep_regs[i]->doepctl, doepctl.d32); + } + + /* Flush the NP Tx FIFO */ + dwc_otg_flush_tx_fifo(core_if, 0x10); + /* Flush the Learning Queue */ + resetctl.b.intknqflsh = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->grstctl, resetctl.d32); + + if (!core_if->core_params->en_multiple_tx_fifo && core_if->dma_enable) { + core_if->start_predict = 0; + for (i = 0; i<= core_if->dev_if->num_in_eps; ++i) { + core_if->nextep_seq[i] = 0xff; // 0xff - EP not active + } + core_if->nextep_seq[0] = 0; + core_if->first_in_nextep_seq = 0; + diepctl.d32 = DWC_READ_REG32(&dev_if->in_ep_regs[0]->diepctl); + diepctl.b.nextep = 0; + DWC_WRITE_REG32(&dev_if->in_ep_regs[0]->diepctl, diepctl.d32); + + /* Update IN Endpoint Mismatch Count by active IN NP EP count + 1 */ + dcfg.d32 = DWC_READ_REG32(&dev_if->dev_global_regs->dcfg); + dcfg.b.epmscnt = 2; + DWC_WRITE_REG32(&dev_if->dev_global_regs->dcfg, dcfg.d32); + + DWC_DEBUGPL(DBG_PCDV, + "%s first_in_nextep_seq= %2d; nextep_seq[]:\n", + __func__, core_if->first_in_nextep_seq); + for (i=0; i <= core_if->dev_if->num_in_eps; i++) { + DWC_DEBUGPL(DBG_PCDV, "%2d\n", core_if->nextep_seq[i]); + } + } + + if (core_if->multiproc_int_enable) { + daintmsk.b.inep0 = 1; + daintmsk.b.outep0 = 1; + DWC_WRITE_REG32(&dev_if->dev_global_regs->deachintmsk, + daintmsk.d32); + + doepmsk.b.setup = 1; + doepmsk.b.xfercompl = 1; + doepmsk.b.ahberr = 1; + doepmsk.b.epdisabled = 1; + + if ((core_if->dma_desc_enable) || + (core_if->dma_enable + && core_if->snpsid >= OTG_CORE_REV_3_00a)) { + doepmsk.b.stsphsercvd = 1; + } + if (core_if->dma_desc_enable) + doepmsk.b.bna = 1; +/* + doepmsk.b.babble = 1; + doepmsk.b.nyet = 1; + + if (core_if->dma_enable) { + doepmsk.b.nak = 1; + } +*/ + DWC_WRITE_REG32(&dev_if->dev_global_regs->doepeachintmsk[0], + doepmsk.d32); + + diepmsk.b.xfercompl = 1; + diepmsk.b.timeout = 1; + diepmsk.b.epdisabled = 1; + diepmsk.b.ahberr = 1; + diepmsk.b.intknepmis = 1; + if (!core_if->en_multiple_tx_fifo && core_if->dma_enable) + diepmsk.b.intknepmis = 0; + +/* if (core_if->dma_desc_enable) { + diepmsk.b.bna = 1; + } +*/ +/* + if (core_if->dma_enable) { + diepmsk.b.nak = 1; + } +*/ + DWC_WRITE_REG32(&dev_if->dev_global_regs->diepeachintmsk[0], + diepmsk.d32); + } else { + daintmsk.b.inep0 = 1; + daintmsk.b.outep0 = 1; + DWC_WRITE_REG32(&dev_if->dev_global_regs->daintmsk, + daintmsk.d32); + + doepmsk.b.setup = 1; + doepmsk.b.xfercompl = 1; + doepmsk.b.ahberr = 1; + doepmsk.b.epdisabled = 1; + + if ((core_if->dma_desc_enable) || + (core_if->dma_enable + && core_if->snpsid >= OTG_CORE_REV_3_00a)) { + doepmsk.b.stsphsercvd = 1; + } + if (core_if->dma_desc_enable) + doepmsk.b.bna = 1; + DWC_WRITE_REG32(&dev_if->dev_global_regs->doepmsk, doepmsk.d32); + + diepmsk.b.xfercompl = 1; + diepmsk.b.timeout = 1; + diepmsk.b.epdisabled = 1; + diepmsk.b.ahberr = 1; + if (!core_if->en_multiple_tx_fifo && core_if->dma_enable) + diepmsk.b.intknepmis = 0; +/* + if (core_if->dma_desc_enable) { + diepmsk.b.bna = 1; + } +*/ + + DWC_WRITE_REG32(&dev_if->dev_global_regs->diepmsk, diepmsk.d32); + } + + /* Reset Device Address */ + dcfg.d32 = DWC_READ_REG32(&dev_if->dev_global_regs->dcfg); + dcfg.b.devaddr = 0; + DWC_WRITE_REG32(&dev_if->dev_global_regs->dcfg, dcfg.d32); + + /* setup EP0 to receive SETUP packets */ + if (core_if->snpsid <= OTG_CORE_REV_2_94a) + ep0_out_start(core_if, pcd); + + /* Clear interrupt */ + gintsts.d32 = 0; + gintsts.b.usbreset = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, gintsts.d32); + + return 1; +} + +/** + * Get the device speed from the device status register and convert it + * to USB speed constant. + * + * @param core_if Programming view of DWC_otg controller. + */ +static int get_device_speed(dwc_otg_core_if_t * core_if) +{ + dsts_data_t dsts; + int speed = 0; + dsts.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dsts); + + switch (dsts.b.enumspd) { + case DWC_DSTS_ENUMSPD_HS_PHY_30MHZ_OR_60MHZ: + speed = USB_SPEED_HIGH; + break; + case DWC_DSTS_ENUMSPD_FS_PHY_30MHZ_OR_60MHZ: + case DWC_DSTS_ENUMSPD_FS_PHY_48MHZ: + speed = USB_SPEED_FULL; + break; + + case DWC_DSTS_ENUMSPD_LS_PHY_6MHZ: + speed = USB_SPEED_LOW; + break; + } + + return speed; +} + +/** + * Read the device status register and set the device speed in the + * data structure. + * Set up EP0 to receive SETUP packets by calling dwc_ep0_activate. + */ +static int32_t dwc_otg_pcd_handle_enum_done_intr(dwc_otg_pcd_t * pcd) +{ + dwc_otg_pcd_ep_t *ep0 = &pcd->ep0; + gintsts_data_t gintsts; + gusbcfg_data_t gusbcfg; + dwc_otg_core_global_regs_t *global_regs = + GET_CORE_IF(pcd)->core_global_regs; + uint8_t utmi16b, utmi8b; + int speed; + DWC_DEBUGPL(DBG_PCD, "SPEED ENUM\n"); + + if (GET_CORE_IF(pcd)->snpsid >= OTG_CORE_REV_2_60a) { + utmi16b = 6; //vahrama old value was 6; + utmi8b = 9; + } else { + utmi16b = 4; + utmi8b = 8; + } + dwc_otg_ep0_activate(GET_CORE_IF(pcd), &ep0->dwc_ep); + if (GET_CORE_IF(pcd)->snpsid >= OTG_CORE_REV_3_00a) { + ep0_out_start(GET_CORE_IF(pcd), pcd); + } + +#ifdef DEBUG_EP0 + print_ep0_state(pcd); +#endif + + if (pcd->ep0state == EP0_DISCONNECT) { + pcd->ep0state = EP0_IDLE; + } else if (pcd->ep0state == EP0_STALL) { + pcd->ep0state = EP0_IDLE; + } + + pcd->ep0state = EP0_IDLE; + + ep0->stopped = 0; + + speed = get_device_speed(GET_CORE_IF(pcd)); + pcd->fops->connect(pcd, speed); + + /* Set USB turnaround time based on device speed and PHY interface. */ + gusbcfg.d32 = DWC_READ_REG32(&global_regs->gusbcfg); + if (speed == USB_SPEED_HIGH) { + if (GET_CORE_IF(pcd)->hwcfg2.b.hs_phy_type == + DWC_HWCFG2_HS_PHY_TYPE_ULPI) { + /* ULPI interface */ + gusbcfg.b.usbtrdtim = 9; + } + if (GET_CORE_IF(pcd)->hwcfg2.b.hs_phy_type == + DWC_HWCFG2_HS_PHY_TYPE_UTMI) { + /* UTMI+ interface */ + if (GET_CORE_IF(pcd)->hwcfg4.b.utmi_phy_data_width == 0) { + gusbcfg.b.usbtrdtim = utmi8b; + } else if (GET_CORE_IF(pcd)->hwcfg4. + b.utmi_phy_data_width == 1) { + gusbcfg.b.usbtrdtim = utmi16b; + } else if (GET_CORE_IF(pcd)-> + core_params->phy_utmi_width == 8) { + gusbcfg.b.usbtrdtim = utmi8b; + } else { + gusbcfg.b.usbtrdtim = utmi16b; + } + } + if (GET_CORE_IF(pcd)->hwcfg2.b.hs_phy_type == + DWC_HWCFG2_HS_PHY_TYPE_UTMI_ULPI) { + /* UTMI+ OR ULPI interface */ + if (gusbcfg.b.ulpi_utmi_sel == 1) { + /* ULPI interface */ + gusbcfg.b.usbtrdtim = 9; + } else { + /* UTMI+ interface */ + if (GET_CORE_IF(pcd)-> + core_params->phy_utmi_width == 16) { + gusbcfg.b.usbtrdtim = utmi16b; + } else { + gusbcfg.b.usbtrdtim = utmi8b; + } + } + } + } else { + /* Full or low speed */ + gusbcfg.b.usbtrdtim = 9; + } + DWC_WRITE_REG32(&global_regs->gusbcfg, gusbcfg.d32); + + /* Clear interrupt */ + gintsts.d32 = 0; + gintsts.b.enumdone = 1; + DWC_WRITE_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintsts, + gintsts.d32); + return 1; +} + +/** + * This interrupt indicates that the ISO OUT Packet was dropped due to + * Rx FIFO full or Rx Status Queue Full. If this interrupt occurs + * read all the data from the Rx FIFO. + */ +static int32_t dwc_otg_pcd_handle_isoc_out_packet_dropped_intr(dwc_otg_pcd_t * pcd) +{ + gintmsk_data_t intr_mask = {.d32 = 0 }; + gintsts_data_t gintsts; + + DWC_WARN("INTERRUPT Handler not implemented for %s\n", + "ISOC Out Dropped"); + + intr_mask.b.isooutdrop = 1; + DWC_MODIFY_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintmsk, + intr_mask.d32, 0); + + /* Clear interrupt */ + gintsts.d32 = 0; + gintsts.b.isooutdrop = 1; + DWC_WRITE_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintsts, + gintsts.d32); + + return 1; +} + +/** + * This interrupt indicates the end of the portion of the micro-frame + * for periodic transactions. If there is a periodic transaction for + * the next frame, load the packets into the EP periodic Tx FIFO. + */ +static int32_t dwc_otg_pcd_handle_end_periodic_frame_intr(dwc_otg_pcd_t * pcd) +{ + gintmsk_data_t intr_mask = {.d32 = 0 }; + gintsts_data_t gintsts; + DWC_PRINTF("INTERRUPT Handler not implemented for %s\n", "EOP"); + + intr_mask.b.eopframe = 1; + DWC_MODIFY_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintmsk, + intr_mask.d32, 0); + + /* Clear interrupt */ + gintsts.d32 = 0; + gintsts.b.eopframe = 1; + DWC_WRITE_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintsts, + gintsts.d32); + + return 1; +} + +/** + * This interrupt indicates that EP of the packet on the top of the + * non-periodic Tx FIFO does not match EP of the IN Token received. + * + * The "Device IN Token Queue" Registers are read to determine the + * order the IN Tokens have been received. The non-periodic Tx FIFO + * is flushed, so it can be reloaded in the order seen in the IN Token + * Queue. + */ +static int32_t dwc_otg_pcd_handle_ep_mismatch_intr(dwc_otg_pcd_t * pcd) +{ + gintsts_data_t gintsts; + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + dctl_data_t dctl; + gintmsk_data_t intr_mask = {.d32 = 0 }; + + if (!core_if->en_multiple_tx_fifo && core_if->dma_enable) { + core_if->start_predict = 1; + + DWC_DEBUGPL(DBG_PCDV, "%s(%p)\n", __func__, core_if); + + gintsts.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintsts); + if (!gintsts.b.ginnakeff) { + /* Disable EP Mismatch interrupt */ + intr_mask.d32 = 0; + intr_mask.b.epmismatch = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gintmsk, intr_mask.d32, 0); + /* Enable the Global IN NAK Effective Interrupt */ + intr_mask.d32 = 0; + intr_mask.b.ginnakeff = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gintmsk, 0, intr_mask.d32); + /* Set the global non-periodic IN NAK handshake */ + dctl.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dctl); + dctl.b.sgnpinnak = 1; + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->dctl, dctl.d32); + } else { + DWC_PRINTF("gintsts.b.ginnakeff = 1! dctl.b.sgnpinnak not set\n"); + } + /* Disabling of all EP's will be done in dwc_otg_pcd_handle_in_nak_effective() + * handler after Global IN NAK Effective interrupt will be asserted */ + } + /* Clear interrupt */ + gintsts.d32 = 0; + gintsts.b.epmismatch = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, gintsts.d32); + + return 1; +} + +/** + * This interrupt is valid only in DMA mode. This interrupt indicates that the + * core has stopped fetching data for IN endpoints due to the unavailability of + * TxFIFO space or Request Queue space. This interrupt is used by the + * application for an endpoint mismatch algorithm. + * + * @param pcd The PCD + */ +static int32_t dwc_otg_pcd_handle_ep_fetsusp_intr(dwc_otg_pcd_t * pcd) +{ + gintsts_data_t gintsts; + gintmsk_data_t gintmsk_data; + dctl_data_t dctl; + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + DWC_DEBUGPL(DBG_PCDV, "%s(%p)\n", __func__, core_if); + + /* Clear the global non-periodic IN NAK handshake */ + dctl.d32 = 0; + dctl.b.cgnpinnak = 1; + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->dctl, dctl.d32, dctl.d32); + + /* Mask GINTSTS.FETSUSP interrupt */ + gintmsk_data.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintmsk); + gintmsk_data.b.fetsusp = 0; + DWC_WRITE_REG32(&core_if->core_global_regs->gintmsk, gintmsk_data.d32); + + /* Clear interrupt */ + gintsts.d32 = 0; + gintsts.b.fetsusp = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, gintsts.d32); + + return 1; +} +/** + * This funcion stalls EP0. + */ +static inline void ep0_do_stall(dwc_otg_pcd_t * pcd, const int err_val) +{ + dwc_otg_pcd_ep_t *ep0 = &pcd->ep0; + usb_device_request_t *ctrl = &pcd->setup_pkt->req; + DWC_WARN("req %02x.%02x protocol STALL; err %d\n", + ctrl->bmRequestType, ctrl->bRequest, err_val); + + ep0->dwc_ep.is_in = 1; + dwc_otg_ep_set_stall(GET_CORE_IF(pcd), &ep0->dwc_ep); + pcd->ep0.stopped = 1; + pcd->ep0state = EP0_IDLE; + ep0_out_start(GET_CORE_IF(pcd), pcd); +} + +/** + * This functions delegates the setup command to the gadget driver. + */ +static inline void do_gadget_setup(dwc_otg_pcd_t * pcd, + usb_device_request_t * ctrl) +{ + int ret = 0; + DWC_SPINUNLOCK(pcd->lock); + ret = pcd->fops->setup(pcd, (uint8_t *) ctrl); + DWC_SPINLOCK(pcd->lock); + if (ret < 0) { + ep0_do_stall(pcd, ret); + } + + /** @todo This is a g_file_storage gadget driver specific + * workaround: a DELAYED_STATUS result from the fsg_setup + * routine will result in the gadget queueing a EP0 IN status + * phase for a two-stage control transfer. Exactly the same as + * a SET_CONFIGURATION/SET_INTERFACE except that this is a class + * specific request. Need a generic way to know when the gadget + * driver will queue the status phase. Can we assume when we + * call the gadget driver setup() function that it will always + * queue and require the following flag? Need to look into + * this. + */ + + if (ret == 256 + 999) { + pcd->request_config = 1; + } +} + +#ifdef DWC_UTE_CFI +/** + * This functions delegates the CFI setup commands to the gadget driver. + * This function will return a negative value to indicate a failure. + */ +static inline int cfi_gadget_setup(dwc_otg_pcd_t * pcd, + struct cfi_usb_ctrlrequest *ctrl_req) +{ + int ret = 0; + + if (pcd->fops && pcd->fops->cfi_setup) { + DWC_SPINUNLOCK(pcd->lock); + ret = pcd->fops->cfi_setup(pcd, ctrl_req); + DWC_SPINLOCK(pcd->lock); + if (ret < 0) { + ep0_do_stall(pcd, ret); + return ret; + } + } + + return ret; +} +#endif + +/** + * This function starts the Zero-Length Packet for the IN status phase + * of a 2 stage control transfer. + */ +static inline void do_setup_in_status_phase(dwc_otg_pcd_t * pcd) +{ + dwc_otg_pcd_ep_t *ep0 = &pcd->ep0; + if (pcd->ep0state == EP0_STALL) { + return; + } + + pcd->ep0state = EP0_IN_STATUS_PHASE; + + /* Prepare for more SETUP Packets */ + DWC_DEBUGPL(DBG_PCD, "EP0 IN ZLP\n"); + if ((GET_CORE_IF(pcd)->snpsid >= OTG_CORE_REV_3_00a) + && (pcd->core_if->dma_desc_enable) + && (ep0->dwc_ep.xfer_count < ep0->dwc_ep.total_len)) { + DWC_DEBUGPL(DBG_PCDV, + "Data terminated wait next packet in out_desc_addr\n"); + pcd->backup_buf = phys_to_virt(ep0->dwc_ep.dma_addr); + pcd->data_terminated = 1; + } + ep0->dwc_ep.xfer_len = 0; + ep0->dwc_ep.xfer_count = 0; + ep0->dwc_ep.is_in = 1; + ep0->dwc_ep.dma_addr = pcd->setup_pkt_dma_handle; + dwc_otg_ep0_start_transfer(GET_CORE_IF(pcd), &ep0->dwc_ep); + + /* Prepare for more SETUP Packets */ + //ep0_out_start(GET_CORE_IF(pcd), pcd); +} + +/** + * This function starts the Zero-Length Packet for the OUT status phase + * of a 2 stage control transfer. + */ +static inline void do_setup_out_status_phase(dwc_otg_pcd_t * pcd) +{ + dwc_otg_pcd_ep_t *ep0 = &pcd->ep0; + if (pcd->ep0state == EP0_STALL) { + DWC_DEBUGPL(DBG_PCD, "EP0 STALLED\n"); + return; + } + pcd->ep0state = EP0_OUT_STATUS_PHASE; + + DWC_DEBUGPL(DBG_PCD, "EP0 OUT ZLP\n"); + ep0->dwc_ep.xfer_len = 0; + ep0->dwc_ep.xfer_count = 0; + ep0->dwc_ep.is_in = 0; + ep0->dwc_ep.dma_addr = pcd->setup_pkt_dma_handle; + dwc_otg_ep0_start_transfer(GET_CORE_IF(pcd), &ep0->dwc_ep); + + /* Prepare for more SETUP Packets */ + if (GET_CORE_IF(pcd)->dma_enable == 0) { + ep0_out_start(GET_CORE_IF(pcd), pcd); + } +} + +/** + * Clear the EP halt (STALL) and if pending requests start the + * transfer. + */ +static inline void pcd_clear_halt(dwc_otg_pcd_t * pcd, dwc_otg_pcd_ep_t * ep) +{ + if (ep->dwc_ep.stall_clear_flag == 0) + dwc_otg_ep_clear_stall(GET_CORE_IF(pcd), &ep->dwc_ep); + + /* Reactive the EP */ + dwc_otg_ep_activate(GET_CORE_IF(pcd), &ep->dwc_ep); + if (ep->stopped) { + ep->stopped = 0; + /* If there is a request in the EP queue start it */ + + /** @todo FIXME: this causes an EP mismatch in DMA mode. + * epmismatch not yet implemented. */ + + /* + * Above fixme is solved by implmenting a tasklet to call the + * start_next_request(), outside of interrupt context at some + * time after the current time, after a clear-halt setup packet. + * Still need to implement ep mismatch in the future if a gadget + * ever uses more than one endpoint at once + */ + ep->queue_sof = 1; + DWC_TASK_SCHEDULE(pcd->start_xfer_tasklet); + } + /* Start Control Status Phase */ + do_setup_in_status_phase(pcd); +} + +/** + * This function is called when the SET_FEATURE TEST_MODE Setup packet + * is sent from the host. The Device Control register is written with + * the Test Mode bits set to the specified Test Mode. This is done as + * a tasklet so that the "Status" phase of the control transfer + * completes before transmitting the TEST packets. + * + * @todo This has not been tested since the tasklet struct was put + * into the PCD struct! + * + */ +void do_test_mode(void *data) +{ + dctl_data_t dctl; + dwc_otg_pcd_t *pcd = (dwc_otg_pcd_t *) data; + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + int test_mode = pcd->test_mode; + +// DWC_WARN("%s() has not been tested since being rewritten!\n", __func__); + + dctl.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dctl); + switch (test_mode) { + case 1: // TEST_J + dctl.b.tstctl = 1; + break; + + case 2: // TEST_K + dctl.b.tstctl = 2; + break; + + case 3: // TEST_SE0_NAK + dctl.b.tstctl = 3; + break; + + case 4: // TEST_PACKET + dctl.b.tstctl = 4; + break; + + case 5: // TEST_FORCE_ENABLE + dctl.b.tstctl = 5; + break; + } + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->dctl, dctl.d32); +} + +/** + * This function process the GET_STATUS Setup Commands. + */ +static inline void do_get_status(dwc_otg_pcd_t * pcd) +{ + usb_device_request_t ctrl = pcd->setup_pkt->req; + dwc_otg_pcd_ep_t *ep; + dwc_otg_pcd_ep_t *ep0 = &pcd->ep0; + uint16_t *status = pcd->status_buf; + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + +#ifdef DEBUG_EP0 + DWC_DEBUGPL(DBG_PCD, + "GET_STATUS %02x.%02x v%04x i%04x l%04x\n", + ctrl.bmRequestType, ctrl.bRequest, + UGETW(ctrl.wValue), UGETW(ctrl.wIndex), + UGETW(ctrl.wLength)); +#endif + + switch (UT_GET_RECIPIENT(ctrl.bmRequestType)) { + case UT_DEVICE: + if(UGETW(ctrl.wIndex) == 0xF000) { /* OTG Status selector */ + DWC_PRINTF("wIndex - %d\n", UGETW(ctrl.wIndex)); + DWC_PRINTF("OTG VERSION - %d\n", core_if->otg_ver); + DWC_PRINTF("OTG CAP - %d, %d\n", + core_if->core_params->otg_cap, + DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE); + if (core_if->otg_ver == 1 + && core_if->core_params->otg_cap == + DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE) { + uint8_t *otgsts = (uint8_t*)pcd->status_buf; + *otgsts = (core_if->otg_sts & 0x1); + pcd->ep0_pending = 1; + ep0->dwc_ep.start_xfer_buff = + (uint8_t *) otgsts; + ep0->dwc_ep.xfer_buff = (uint8_t *) otgsts; + ep0->dwc_ep.dma_addr = + pcd->status_buf_dma_handle; + ep0->dwc_ep.xfer_len = 1; + ep0->dwc_ep.xfer_count = 0; + ep0->dwc_ep.total_len = ep0->dwc_ep.xfer_len; + dwc_otg_ep0_start_transfer(GET_CORE_IF(pcd), + &ep0->dwc_ep); + return; + } else { + ep0_do_stall(pcd, -DWC_E_NOT_SUPPORTED); + return; + } + break; + } else { + *status = 0x1; /* Self powered */ + *status |= pcd->remote_wakeup_enable << 1; + break; + } + case UT_INTERFACE: + *status = 0; + break; + + case UT_ENDPOINT: + ep = get_ep_by_addr(pcd, UGETW(ctrl.wIndex)); + if (ep == 0 || UGETW(ctrl.wLength) > 2) { + ep0_do_stall(pcd, -DWC_E_NOT_SUPPORTED); + return; + } + /** @todo check for EP stall */ + *status = ep->stopped; + break; + } + pcd->ep0_pending = 1; + ep0->dwc_ep.start_xfer_buff = (uint8_t *) status; + ep0->dwc_ep.xfer_buff = (uint8_t *) status; + ep0->dwc_ep.dma_addr = pcd->status_buf_dma_handle; + ep0->dwc_ep.xfer_len = 2; + ep0->dwc_ep.xfer_count = 0; + ep0->dwc_ep.total_len = ep0->dwc_ep.xfer_len; + dwc_otg_ep0_start_transfer(GET_CORE_IF(pcd), &ep0->dwc_ep); +} + +/** + * This function process the SET_FEATURE Setup Commands. + */ +static inline void do_set_feature(dwc_otg_pcd_t * pcd) +{ + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs; + usb_device_request_t ctrl = pcd->setup_pkt->req; + dwc_otg_pcd_ep_t *ep = 0; + int32_t otg_cap_param = core_if->core_params->otg_cap; + gotgctl_data_t gotgctl = {.d32 = 0 }; + + DWC_DEBUGPL(DBG_PCD, "SET_FEATURE:%02x.%02x v%04x i%04x l%04x\n", + ctrl.bmRequestType, ctrl.bRequest, + UGETW(ctrl.wValue), UGETW(ctrl.wIndex), + UGETW(ctrl.wLength)); + DWC_DEBUGPL(DBG_PCD, "otg_cap=%d\n", otg_cap_param); + + switch (UT_GET_RECIPIENT(ctrl.bmRequestType)) { + case UT_DEVICE: + switch (UGETW(ctrl.wValue)) { + case UF_DEVICE_REMOTE_WAKEUP: + pcd->remote_wakeup_enable = 1; + break; + + case UF_TEST_MODE: + /* Setup the Test Mode tasklet to do the Test + * Packet generation after the SETUP Status + * phase has completed. */ + + /** @todo This has not been tested since the + * tasklet struct was put into the PCD + * struct! */ + pcd->test_mode = UGETW(ctrl.wIndex) >> 8; + DWC_TASK_SCHEDULE(pcd->test_mode_tasklet); + break; + + case UF_DEVICE_B_HNP_ENABLE: + DWC_DEBUGPL(DBG_PCDV, + "SET_FEATURE: USB_DEVICE_B_HNP_ENABLE\n"); + + /* dev may initiate HNP */ + if (otg_cap_param == DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE) { + pcd->b_hnp_enable = 1; + dwc_otg_pcd_update_otg(pcd, 0); + DWC_DEBUGPL(DBG_PCD, "Request B HNP\n"); + /**@todo Is the gotgctl.devhnpen cleared + * by a USB Reset? */ + gotgctl.b.devhnpen = 1; + gotgctl.b.hnpreq = 1; + DWC_WRITE_REG32(&global_regs->gotgctl, + gotgctl.d32); + } else { + ep0_do_stall(pcd, -DWC_E_NOT_SUPPORTED); + return; + } + break; + + case UF_DEVICE_A_HNP_SUPPORT: + /* RH port supports HNP */ + DWC_DEBUGPL(DBG_PCDV, + "SET_FEATURE: USB_DEVICE_A_HNP_SUPPORT\n"); + if (otg_cap_param == DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE) { + pcd->a_hnp_support = 1; + dwc_otg_pcd_update_otg(pcd, 0); + } else { + ep0_do_stall(pcd, -DWC_E_NOT_SUPPORTED); + return; + } + break; + + case UF_DEVICE_A_ALT_HNP_SUPPORT: + /* other RH port does */ + DWC_DEBUGPL(DBG_PCDV, + "SET_FEATURE: USB_DEVICE_A_ALT_HNP_SUPPORT\n"); + if (otg_cap_param == DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE) { + pcd->a_alt_hnp_support = 1; + dwc_otg_pcd_update_otg(pcd, 0); + } else { + ep0_do_stall(pcd, -DWC_E_NOT_SUPPORTED); + return; + } + break; + + default: + ep0_do_stall(pcd, -DWC_E_NOT_SUPPORTED); + return; + + } + do_setup_in_status_phase(pcd); + break; + + case UT_INTERFACE: + do_gadget_setup(pcd, &ctrl); + break; + + case UT_ENDPOINT: + if (UGETW(ctrl.wValue) == UF_ENDPOINT_HALT) { + ep = get_ep_by_addr(pcd, UGETW(ctrl.wIndex)); + if (ep == 0) { + ep0_do_stall(pcd, -DWC_E_NOT_SUPPORTED); + return; + } + ep->stopped = 1; + dwc_otg_ep_set_stall(core_if, &ep->dwc_ep); + } + do_setup_in_status_phase(pcd); + break; + } +} + +/** + * This function process the CLEAR_FEATURE Setup Commands. + */ +static inline void do_clear_feature(dwc_otg_pcd_t * pcd) +{ + usb_device_request_t ctrl = pcd->setup_pkt->req; + dwc_otg_pcd_ep_t *ep = 0; + + DWC_DEBUGPL(DBG_PCD, + "CLEAR_FEATURE:%02x.%02x v%04x i%04x l%04x\n", + ctrl.bmRequestType, ctrl.bRequest, + UGETW(ctrl.wValue), UGETW(ctrl.wIndex), + UGETW(ctrl.wLength)); + + switch (UT_GET_RECIPIENT(ctrl.bmRequestType)) { + case UT_DEVICE: + switch (UGETW(ctrl.wValue)) { + case UF_DEVICE_REMOTE_WAKEUP: + pcd->remote_wakeup_enable = 0; + break; + + case UF_TEST_MODE: + /** @todo Add CLEAR_FEATURE for TEST modes. */ + break; + + default: + ep0_do_stall(pcd, -DWC_E_NOT_SUPPORTED); + return; + } + do_setup_in_status_phase(pcd); + break; + + case UT_ENDPOINT: + ep = get_ep_by_addr(pcd, UGETW(ctrl.wIndex)); + if (ep == 0) { + ep0_do_stall(pcd, -DWC_E_NOT_SUPPORTED); + return; + } + + pcd_clear_halt(pcd, ep); + + break; + } +} + +/** + * This function process the SET_ADDRESS Setup Commands. + */ +static inline void do_set_address(dwc_otg_pcd_t * pcd) +{ + dwc_otg_dev_if_t *dev_if = GET_CORE_IF(pcd)->dev_if; + usb_device_request_t ctrl = pcd->setup_pkt->req; + + if (ctrl.bmRequestType == UT_DEVICE) { + dcfg_data_t dcfg = {.d32 = 0 }; + +#ifdef DEBUG_EP0 +// DWC_DEBUGPL(DBG_PCDV, "SET_ADDRESS:%d\n", ctrl.wValue); +#endif + dcfg.b.devaddr = UGETW(ctrl.wValue); + DWC_MODIFY_REG32(&dev_if->dev_global_regs->dcfg, 0, dcfg.d32); + do_setup_in_status_phase(pcd); + } +} + +/** + * This function processes SETUP commands. In Linux, the USB Command + * processing is done in two places - the first being the PCD and the + * second in the Gadget Driver (for example, the File-Backed Storage + * Gadget Driver). + * + * <table> + * <tr><td>Command </td><td>Driver </td><td>Description</td></tr> + * + * <tr><td>GET_STATUS </td><td>PCD </td><td>Command is processed as + * defined in chapter 9 of the USB 2.0 Specification chapter 9 + * </td></tr> + * + * <tr><td>CLEAR_FEATURE </td><td>PCD </td><td>The Device and Endpoint + * requests are the ENDPOINT_HALT feature is procesed, all others the + * interface requests are ignored.</td></tr> + * + * <tr><td>SET_FEATURE </td><td>PCD </td><td>The Device and Endpoint + * requests are processed by the PCD. Interface requests are passed + * to the Gadget Driver.</td></tr> + * + * <tr><td>SET_ADDRESS </td><td>PCD </td><td>Program the DCFG reg, + * with device address received </td></tr> + * + * <tr><td>GET_DESCRIPTOR </td><td>Gadget Driver </td><td>Return the + * requested descriptor</td></tr> + * + * <tr><td>SET_DESCRIPTOR </td><td>Gadget Driver </td><td>Optional - + * not implemented by any of the existing Gadget Drivers.</td></tr> + * + * <tr><td>SET_CONFIGURATION </td><td>Gadget Driver </td><td>Disable + * all EPs and enable EPs for new configuration.</td></tr> + * + * <tr><td>GET_CONFIGURATION </td><td>Gadget Driver </td><td>Return + * the current configuration</td></tr> + * + * <tr><td>SET_INTERFACE </td><td>Gadget Driver </td><td>Disable all + * EPs and enable EPs for new configuration.</td></tr> + * + * <tr><td>GET_INTERFACE </td><td>Gadget Driver </td><td>Return the + * current interface.</td></tr> + * + * <tr><td>SYNC_FRAME </td><td>PCD </td><td>Display debug + * message.</td></tr> + * </table> + * + * When the SETUP Phase Done interrupt occurs, the PCD SETUP commands are + * processed by pcd_setup. Calling the Function Driver's setup function from + * pcd_setup processes the gadget SETUP commands. + */ +static inline void pcd_setup(dwc_otg_pcd_t * pcd) +{ + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + dwc_otg_dev_if_t *dev_if = core_if->dev_if; + usb_device_request_t ctrl = pcd->setup_pkt->req; + dwc_otg_pcd_ep_t *ep0 = &pcd->ep0; + + deptsiz0_data_t doeptsize0 = {.d32 = 0 }; + +#ifdef DWC_UTE_CFI + int retval = 0; + struct cfi_usb_ctrlrequest cfi_req; +#endif + + doeptsize0.d32 = DWC_READ_REG32(&dev_if->out_ep_regs[0]->doeptsiz); + + /** In BDMA more then 1 setup packet is not supported till 3.00a */ + if (core_if->dma_enable && core_if->dma_desc_enable == 0 + && (doeptsize0.b.supcnt < 2) + && (core_if->snpsid < OTG_CORE_REV_2_94a)) { + DWC_ERROR + ("\n\n----------- CANNOT handle > 1 setup packet in DMA mode\n\n"); + } + if ((core_if->snpsid >= OTG_CORE_REV_3_00a) + && (core_if->dma_enable == 1) && (core_if->dma_desc_enable == 0)) { + ctrl = + (pcd->setup_pkt + + (3 - doeptsize0.b.supcnt - 1 + + ep0->dwc_ep.stp_rollover))->req; + } +#ifdef DEBUG_EP0 + DWC_DEBUGPL(DBG_PCD, "SETUP %02x.%02x v%04x i%04x l%04x\n", + ctrl.bmRequestType, ctrl.bRequest, + UGETW(ctrl.wValue), UGETW(ctrl.wIndex), + UGETW(ctrl.wLength)); +#endif + + /* Clean up the request queue */ + dwc_otg_request_nuke(ep0); + ep0->stopped = 0; + + if (ctrl.bmRequestType & UE_DIR_IN) { + ep0->dwc_ep.is_in = 1; + pcd->ep0state = EP0_IN_DATA_PHASE; + } else { + ep0->dwc_ep.is_in = 0; + pcd->ep0state = EP0_OUT_DATA_PHASE; + } + + if (UGETW(ctrl.wLength) == 0) { + ep0->dwc_ep.is_in = 1; + pcd->ep0state = EP0_IN_STATUS_PHASE; + } + + if (UT_GET_TYPE(ctrl.bmRequestType) != UT_STANDARD) { + +#ifdef DWC_UTE_CFI + DWC_MEMCPY(&cfi_req, &ctrl, sizeof(usb_device_request_t)); + + //printk(KERN_ALERT "CFI: req_type=0x%02x; req=0x%02x\n", + ctrl.bRequestType, ctrl.bRequest); + if (UT_GET_TYPE(cfi_req.bRequestType) == UT_VENDOR) { + if (cfi_req.bRequest > 0xB0 && cfi_req.bRequest < 0xBF) { + retval = cfi_setup(pcd, &cfi_req); + if (retval < 0) { + ep0_do_stall(pcd, retval); + pcd->ep0_pending = 0; + return; + } + + /* if need gadget setup then call it and check the retval */ + if (pcd->cfi->need_gadget_att) { + retval = + cfi_gadget_setup(pcd, + &pcd-> + cfi->ctrl_req); + if (retval < 0) { + pcd->ep0_pending = 0; + return; + } + } + + if (pcd->cfi->need_status_in_complete) { + do_setup_in_status_phase(pcd); + } + return; + } + } +#endif + + /* handle non-standard (class/vendor) requests in the gadget driver */ + do_gadget_setup(pcd, &ctrl); + return; + } + + /** @todo NGS: Handle bad setup packet? */ + +/////////////////////////////////////////// +//// --- Standard Request handling --- //// + + switch (ctrl.bRequest) { + case UR_GET_STATUS: + do_get_status(pcd); + break; + + case UR_CLEAR_FEATURE: + do_clear_feature(pcd); + break; + + case UR_SET_FEATURE: + do_set_feature(pcd); + break; + + case UR_SET_ADDRESS: + do_set_address(pcd); + break; + + case UR_SET_INTERFACE: + case UR_SET_CONFIG: +// _pcd->request_config = 1; /* Configuration changed */ + do_gadget_setup(pcd, &ctrl); + break; + + case UR_SYNCH_FRAME: + do_gadget_setup(pcd, &ctrl); + break; + + default: + /* Call the Gadget Driver's setup functions */ + do_gadget_setup(pcd, &ctrl); + break; + } +} + +/** + * This function completes the ep0 control transfer. + */ +static int32_t ep0_complete_request(dwc_otg_pcd_ep_t * ep) +{ + dwc_otg_core_if_t *core_if = GET_CORE_IF(ep->pcd); + dwc_otg_dev_if_t *dev_if = core_if->dev_if; + dwc_otg_dev_in_ep_regs_t *in_ep_regs = + dev_if->in_ep_regs[ep->dwc_ep.num]; +#ifdef DEBUG_EP0 + dwc_otg_dev_out_ep_regs_t *out_ep_regs = + dev_if->out_ep_regs[ep->dwc_ep.num]; +#endif + deptsiz0_data_t deptsiz; + dev_dma_desc_sts_t desc_sts; + dwc_otg_pcd_request_t *req; + int is_last = 0; + dwc_otg_pcd_t *pcd = ep->pcd; + +#ifdef DWC_UTE_CFI + struct cfi_usb_ctrlrequest *ctrlreq; + int retval = -DWC_E_NOT_SUPPORTED; +#endif + + desc_sts.b.bytes = 0; + + if (pcd->ep0_pending && DWC_CIRCLEQ_EMPTY(&ep->queue)) { + if (ep->dwc_ep.is_in) { +#ifdef DEBUG_EP0 + DWC_DEBUGPL(DBG_PCDV, "Do setup OUT status phase\n"); +#endif + do_setup_out_status_phase(pcd); + } else { +#ifdef DEBUG_EP0 + DWC_DEBUGPL(DBG_PCDV, "Do setup IN status phase\n"); +#endif + +#ifdef DWC_UTE_CFI + ctrlreq = &pcd->cfi->ctrl_req; + + if (UT_GET_TYPE(ctrlreq->bRequestType) == UT_VENDOR) { + if (ctrlreq->bRequest > 0xB0 + && ctrlreq->bRequest < 0xBF) { + + /* Return if the PCD failed to handle the request */ + if ((retval = + pcd->cfi->ops. + ctrl_write_complete(pcd->cfi, + pcd)) < 0) { + CFI_INFO + ("ERROR setting a new value in the PCD(%d)\n", + retval); + ep0_do_stall(pcd, retval); + pcd->ep0_pending = 0; + return 0; + } + + /* If the gadget needs to be notified on the request */ + if (pcd->cfi->need_gadget_att == 1) { + //retval = do_gadget_setup(pcd, &pcd->cfi->ctrl_req); + retval = + cfi_gadget_setup(pcd, + &pcd->cfi-> + ctrl_req); + + /* Return from the function if the gadget failed to process + * the request properly - this should never happen !!! + */ + if (retval < 0) { + CFI_INFO + ("ERROR setting a new value in the gadget(%d)\n", + retval); + pcd->ep0_pending = 0; + return 0; + } + } + + CFI_INFO("%s: RETVAL=%d\n", __func__, + retval); + /* If we hit here then the PCD and the gadget has properly + * handled the request - so send the ZLP IN to the host. + */ + /* @todo: MAS - decide whether we need to start the setup + * stage based on the need_setup value of the cfi object + */ + do_setup_in_status_phase(pcd); + pcd->ep0_pending = 0; + return 1; + } + } +#endif + + do_setup_in_status_phase(pcd); + } + pcd->ep0_pending = 0; + return 1; + } + + if (DWC_CIRCLEQ_EMPTY(&ep->queue)) { + return 0; + } + req = DWC_CIRCLEQ_FIRST(&ep->queue); + + if (pcd->ep0state == EP0_OUT_STATUS_PHASE + || pcd->ep0state == EP0_IN_STATUS_PHASE) { + is_last = 1; + } else if (ep->dwc_ep.is_in) { + deptsiz.d32 = DWC_READ_REG32(&in_ep_regs->dieptsiz); + if (core_if->dma_desc_enable != 0) + desc_sts = dev_if->in_desc_addr->status; +#ifdef DEBUG_EP0 + DWC_DEBUGPL(DBG_PCDV, "%d len=%d xfersize=%d pktcnt=%d\n", + ep->dwc_ep.num, ep->dwc_ep.xfer_len, + deptsiz.b.xfersize, deptsiz.b.pktcnt); +#endif + + if (((core_if->dma_desc_enable == 0) + && (deptsiz.b.xfersize == 0)) + || ((core_if->dma_desc_enable != 0) + && (desc_sts.b.bytes == 0))) { + req->actual = ep->dwc_ep.xfer_count; + /* Is a Zero Len Packet needed? */ + if (req->sent_zlp) { +#ifdef DEBUG_EP0 + DWC_DEBUGPL(DBG_PCD, "Setup Rx ZLP\n"); +#endif + req->sent_zlp = 0; + } + do_setup_out_status_phase(pcd); + } + } else { + /* ep0-OUT */ +#ifdef DEBUG_EP0 + deptsiz.d32 = DWC_READ_REG32(&out_ep_regs->doeptsiz); + DWC_DEBUGPL(DBG_PCDV, "%d len=%d xsize=%d pktcnt=%d\n", + ep->dwc_ep.num, ep->dwc_ep.xfer_len, + deptsiz.b.xfersize, deptsiz.b.pktcnt); +#endif + req->actual = ep->dwc_ep.xfer_count; + + /* Is a Zero Len Packet needed? */ + if (req->sent_zlp) { +#ifdef DEBUG_EP0 + DWC_DEBUGPL(DBG_PCDV, "Setup Tx ZLP\n"); +#endif + req->sent_zlp = 0; + } + /* For older cores do setup in status phase in Slave/BDMA modes, + * starting from 3.00 do that only in slave, and for DMA modes + * just re-enable ep 0 OUT here*/ + if (core_if->dma_enable == 0 + || (core_if->dma_desc_enable == 0 + && core_if->snpsid <= OTG_CORE_REV_2_94a)) { + do_setup_in_status_phase(pcd); + } else if (core_if->snpsid >= OTG_CORE_REV_3_00a) { + DWC_DEBUGPL(DBG_PCDV, + "Enable out ep before in status phase\n"); + ep0_out_start(core_if, pcd); + } + } + + /* Complete the request */ + if (is_last) { + dwc_otg_request_done(ep, req, 0); + ep->dwc_ep.start_xfer_buff = 0; + ep->dwc_ep.xfer_buff = 0; + ep->dwc_ep.xfer_len = 0; + return 1; + } + return 0; +} + +#ifdef DWC_UTE_CFI +/** + * This function calculates traverses all the CFI DMA descriptors and + * and accumulates the bytes that are left to be transfered. + * + * @return The total bytes left to transfered, or a negative value as failure + */ +static inline int cfi_calc_desc_residue(dwc_otg_pcd_ep_t * ep) +{ + int32_t ret = 0; + int i; + struct dwc_otg_dma_desc *ddesc = NULL; + struct cfi_ep *cfiep; + + /* See if the pcd_ep has its respective cfi_ep mapped */ + cfiep = get_cfi_ep_by_pcd_ep(ep->pcd->cfi, ep); + if (!cfiep) { + CFI_INFO("%s: Failed to find ep\n", __func__); + return -1; + } + + ddesc = ep->dwc_ep.descs; + + for (i = 0; (i < cfiep->desc_count) && (i < MAX_DMA_DESCS_PER_EP); i++) { + +#if defined(PRINT_CFI_DMA_DESCS) + print_desc(ddesc, ep->ep.name, i); +#endif + ret += ddesc->status.b.bytes; + ddesc++; + } + + if (ret) + CFI_INFO("!!!!!!!!!! WARNING (%s) - residue=%d\n", __func__, + ret); + + return ret; +} +#endif + +/** + * This function completes the request for the EP. If there are + * additional requests for the EP in the queue they will be started. + */ +static void complete_ep(dwc_otg_pcd_ep_t * ep) +{ + dwc_otg_core_if_t *core_if = GET_CORE_IF(ep->pcd); + struct device *dev = dwc_otg_pcd_to_dev(ep->pcd); + dwc_otg_dev_if_t *dev_if = core_if->dev_if; + dwc_otg_dev_in_ep_regs_t *in_ep_regs = + dev_if->in_ep_regs[ep->dwc_ep.num]; + deptsiz_data_t deptsiz; + dev_dma_desc_sts_t desc_sts; + dwc_otg_pcd_request_t *req = 0; + dwc_otg_dev_dma_desc_t *dma_desc; + uint32_t byte_count = 0; + int is_last = 0; + int i; + + DWC_DEBUGPL(DBG_PCDV, "%s() %d-%s\n", __func__, ep->dwc_ep.num, + (ep->dwc_ep.is_in ? "IN" : "OUT")); + + /* Get any pending requests */ + if (!DWC_CIRCLEQ_EMPTY(&ep->queue)) { + req = DWC_CIRCLEQ_FIRST(&ep->queue); + if (!req) { + DWC_PRINTF("complete_ep 0x%p, req = NULL!\n", ep); + return; + } + } else { + DWC_PRINTF("complete_ep 0x%p, ep->queue empty!\n", ep); + return; + } + + DWC_DEBUGPL(DBG_PCD, "Requests %d\n", ep->pcd->request_pending); + + if (ep->dwc_ep.is_in) { + deptsiz.d32 = DWC_READ_REG32(&in_ep_regs->dieptsiz); + + if (core_if->dma_enable) { + if (core_if->dma_desc_enable == 0) { + if (deptsiz.b.xfersize == 0 + && deptsiz.b.pktcnt == 0) { + byte_count = + ep->dwc_ep.xfer_len - + ep->dwc_ep.xfer_count; + + ep->dwc_ep.xfer_buff += byte_count; + ep->dwc_ep.dma_addr += byte_count; + ep->dwc_ep.xfer_count += byte_count; + + DWC_DEBUGPL(DBG_PCDV, + "%d-%s len=%d xfersize=%d pktcnt=%d\n", + ep->dwc_ep.num, + (ep->dwc_ep. + is_in ? "IN" : "OUT"), + ep->dwc_ep.xfer_len, + deptsiz.b.xfersize, + deptsiz.b.pktcnt); + + if (ep->dwc_ep.xfer_len < + ep->dwc_ep.total_len) { + dwc_otg_ep_start_transfer + (core_if, &ep->dwc_ep); + } else if (ep->dwc_ep.sent_zlp) { + /* + * This fragment of code should initiate 0 + * length transfer in case if it is queued + * a transfer with size divisible to EPs max + * packet size and with usb_request zero field + * is set, which means that after data is transfered, + * it is also should be transfered + * a 0 length packet at the end. For Slave and + * Buffer DMA modes in this case SW has + * to initiate 2 transfers one with transfer size, + * and the second with 0 size. For Descriptor + * DMA mode SW is able to initiate a transfer, + * which will handle all the packets including + * the last 0 length. + */ + ep->dwc_ep.sent_zlp = 0; + dwc_otg_ep_start_zl_transfer + (core_if, &ep->dwc_ep); + } else { + is_last = 1; + } + } else { + if (ep->dwc_ep.type == + DWC_OTG_EP_TYPE_ISOC) { + req->actual = 0; + dwc_otg_request_done(ep, req, 0); + + ep->dwc_ep.start_xfer_buff = 0; + ep->dwc_ep.xfer_buff = 0; + ep->dwc_ep.xfer_len = 0; + + /* If there is a request in the queue start it. */ + start_next_request(ep); + } else + DWC_WARN + ("Incomplete transfer (%d - %s [siz=%d pkt=%d])\n", + ep->dwc_ep.num, + (ep->dwc_ep.is_in ? "IN" : "OUT"), + deptsiz.b.xfersize, + deptsiz.b.pktcnt); + } + } else { + dma_desc = ep->dwc_ep.desc_addr; + byte_count = 0; + ep->dwc_ep.sent_zlp = 0; + +#ifdef DWC_UTE_CFI + CFI_INFO("%s: BUFFER_MODE=%d\n", __func__, + ep->dwc_ep.buff_mode); + if (ep->dwc_ep.buff_mode != BM_STANDARD) { + int residue; + + residue = cfi_calc_desc_residue(ep); + if (residue < 0) + return; + + byte_count = residue; + } else { +#endif + for (i = 0; i < ep->dwc_ep.desc_cnt; + ++i) { + desc_sts = dma_desc->status; + byte_count += desc_sts.b.bytes; + dma_desc++; + } +#ifdef DWC_UTE_CFI + } +#endif + if (byte_count == 0) { + ep->dwc_ep.xfer_count = + ep->dwc_ep.total_len; + is_last = 1; + } else { + DWC_WARN("Incomplete transfer\n"); + } + } + } else { + if (deptsiz.b.xfersize == 0 && deptsiz.b.pktcnt == 0) { + DWC_DEBUGPL(DBG_PCDV, + "%d-%s len=%d xfersize=%d pktcnt=%d\n", + ep->dwc_ep.num, + ep->dwc_ep.is_in ? "IN" : "OUT", + ep->dwc_ep.xfer_len, + deptsiz.b.xfersize, + deptsiz.b.pktcnt); + + /* Check if the whole transfer was completed, + * if no, setup transfer for next portion of data + */ + if (ep->dwc_ep.xfer_len < ep->dwc_ep.total_len) { + dwc_otg_ep_start_transfer(core_if, + &ep->dwc_ep); + } else if (ep->dwc_ep.sent_zlp) { + /* + * This fragment of code should initiate 0 + * length trasfer in case if it is queued + * a trasfer with size divisible to EPs max + * packet size and with usb_request zero field + * is set, which means that after data is transfered, + * it is also should be transfered + * a 0 length packet at the end. For Slave and + * Buffer DMA modes in this case SW has + * to initiate 2 transfers one with transfer size, + * and the second with 0 size. For Desriptor + * DMA mode SW is able to initiate a transfer, + * which will handle all the packets including + * the last 0 legth. + */ + ep->dwc_ep.sent_zlp = 0; + dwc_otg_ep_start_zl_transfer(core_if, + &ep->dwc_ep); + } else { + is_last = 1; + } + } else { + DWC_WARN + ("Incomplete transfer (%d-%s [siz=%d pkt=%d])\n", + ep->dwc_ep.num, + (ep->dwc_ep.is_in ? "IN" : "OUT"), + deptsiz.b.xfersize, deptsiz.b.pktcnt); + } + } + } else { + dwc_otg_dev_out_ep_regs_t *out_ep_regs = + dev_if->out_ep_regs[ep->dwc_ep.num]; + desc_sts.d32 = 0; + if (core_if->dma_enable) { + if (core_if->dma_desc_enable) { + dma_desc = ep->dwc_ep.desc_addr; + byte_count = 0; + ep->dwc_ep.sent_zlp = 0; + +#ifdef DWC_UTE_CFI + CFI_INFO("%s: BUFFER_MODE=%d\n", __func__, + ep->dwc_ep.buff_mode); + if (ep->dwc_ep.buff_mode != BM_STANDARD) { + int residue; + residue = cfi_calc_desc_residue(ep); + if (residue < 0) + return; + byte_count = residue; + } else { +#endif + + for (i = 0; i < ep->dwc_ep.desc_cnt; + ++i) { + desc_sts = dma_desc->status; + byte_count += desc_sts.b.bytes; + dma_desc++; + } + +#ifdef DWC_UTE_CFI + } +#endif + /* Checking for interrupt Out transfers with not + * dword aligned mps sizes + */ + if (ep->dwc_ep.type == DWC_OTG_EP_TYPE_INTR && + (ep->dwc_ep.maxpacket%4)) { + ep->dwc_ep.xfer_count = + ep->dwc_ep.total_len - byte_count; + if ((ep->dwc_ep.xfer_len % + ep->dwc_ep.maxpacket) + && (ep->dwc_ep.xfer_len / + ep->dwc_ep.maxpacket < + MAX_DMA_DESC_CNT)) + ep->dwc_ep.xfer_len -= + (ep->dwc_ep.desc_cnt - + 1) * ep->dwc_ep.maxpacket + + ep->dwc_ep.xfer_len % + ep->dwc_ep.maxpacket; + else + ep->dwc_ep.xfer_len -= + ep->dwc_ep.desc_cnt * + ep->dwc_ep.maxpacket; + if (ep->dwc_ep.xfer_len > 0) { + dwc_otg_ep_start_transfer + (core_if, &ep->dwc_ep); + } else { + is_last = 1; + } + } else { + ep->dwc_ep.xfer_count = + ep->dwc_ep.total_len - byte_count + + ((4 - + (ep->dwc_ep. + total_len & 0x3)) & 0x3); + is_last = 1; + } + } else { + deptsiz.d32 = 0; + deptsiz.d32 = + DWC_READ_REG32(&out_ep_regs->doeptsiz); + + byte_count = (ep->dwc_ep.xfer_len - + ep->dwc_ep.xfer_count - + deptsiz.b.xfersize); + ep->dwc_ep.xfer_buff += byte_count; + ep->dwc_ep.dma_addr += byte_count; + ep->dwc_ep.xfer_count += byte_count; + + /* Check if the whole transfer was completed, + * if no, setup transfer for next portion of data + */ + if (ep->dwc_ep.xfer_len < ep->dwc_ep.total_len) { + dwc_otg_ep_start_transfer(core_if, + &ep->dwc_ep); + } else if (ep->dwc_ep.sent_zlp) { + /* + * This fragment of code should initiate 0 + * length trasfer in case if it is queued + * a trasfer with size divisible to EPs max + * packet size and with usb_request zero field + * is set, which means that after data is transfered, + * it is also should be transfered + * a 0 length packet at the end. For Slave and + * Buffer DMA modes in this case SW has + * to initiate 2 transfers one with transfer size, + * and the second with 0 size. For Desriptor + * DMA mode SW is able to initiate a transfer, + * which will handle all the packets including + * the last 0 legth. + */ + ep->dwc_ep.sent_zlp = 0; + dwc_otg_ep_start_zl_transfer(core_if, + &ep->dwc_ep); + } else { + is_last = 1; + } + } + } else { + /* Check if the whole transfer was completed, + * if no, setup transfer for next portion of data + */ + if (ep->dwc_ep.xfer_len < ep->dwc_ep.total_len) { + dwc_otg_ep_start_transfer(core_if, &ep->dwc_ep); + } else if (ep->dwc_ep.sent_zlp) { + /* + * This fragment of code should initiate 0 + * length transfer in case if it is queued + * a transfer with size divisible to EPs max + * packet size and with usb_request zero field + * is set, which means that after data is transfered, + * it is also should be transfered + * a 0 length packet at the end. For Slave and + * Buffer DMA modes in this case SW has + * to initiate 2 transfers one with transfer size, + * and the second with 0 size. For Descriptor + * DMA mode SW is able to initiate a transfer, + * which will handle all the packets including + * the last 0 length. + */ + ep->dwc_ep.sent_zlp = 0; + dwc_otg_ep_start_zl_transfer(core_if, + &ep->dwc_ep); + } else { + is_last = 1; + } + } + + DWC_DEBUGPL(DBG_PCDV, + "addr %p, %d-%s len=%d cnt=%d xsize=%d pktcnt=%d\n", + &out_ep_regs->doeptsiz, ep->dwc_ep.num, + ep->dwc_ep.is_in ? "IN" : "OUT", + ep->dwc_ep.xfer_len, ep->dwc_ep.xfer_count, + deptsiz.b.xfersize, deptsiz.b.pktcnt); + } + + /* Complete the request */ + if (is_last) { +#ifdef DWC_UTE_CFI + if (ep->dwc_ep.buff_mode != BM_STANDARD) { + req->actual = ep->dwc_ep.cfi_req_len - byte_count; + } else { +#endif + req->actual = ep->dwc_ep.xfer_count; +#ifdef DWC_UTE_CFI + } +#endif + if (req->dw_align_buf) { + if (!ep->dwc_ep.is_in) { + dwc_memcpy(req->buf, req->dw_align_buf, req->length); + } + DWC_DMA_FREE(dev, req->length, req->dw_align_buf, + req->dw_align_buf_dma); + } + + dwc_otg_request_done(ep, req, 0); + + ep->dwc_ep.start_xfer_buff = 0; + ep->dwc_ep.xfer_buff = 0; + ep->dwc_ep.xfer_len = 0; + + /* If there is a request in the queue start it. */ + start_next_request(ep); + } +} + +#ifdef DWC_EN_ISOC + +/** + * This function BNA interrupt for Isochronous EPs + * + */ +static void dwc_otg_pcd_handle_iso_bna(dwc_otg_pcd_ep_t * ep) +{ + dwc_ep_t *dwc_ep = &ep->dwc_ep; + volatile uint32_t *addr; + depctl_data_t depctl = {.d32 = 0 }; + dwc_otg_pcd_t *pcd = ep->pcd; + dwc_otg_dev_dma_desc_t *dma_desc; + int i; + + dma_desc = + dwc_ep->iso_desc_addr + dwc_ep->desc_cnt * (dwc_ep->proc_buf_num); + + if (dwc_ep->is_in) { + dev_dma_desc_sts_t sts = {.d32 = 0 }; + for (i = 0; i < dwc_ep->desc_cnt; ++i, ++dma_desc) { + sts.d32 = dma_desc->status.d32; + sts.b_iso_in.bs = BS_HOST_READY; + dma_desc->status.d32 = sts.d32; + } + } else { + dev_dma_desc_sts_t sts = {.d32 = 0 }; + for (i = 0; i < dwc_ep->desc_cnt; ++i, ++dma_desc) { + sts.d32 = dma_desc->status.d32; + sts.b_iso_out.bs = BS_HOST_READY; + dma_desc->status.d32 = sts.d32; + } + } + + if (dwc_ep->is_in == 0) { + addr = + &GET_CORE_IF(pcd)->dev_if->out_ep_regs[dwc_ep-> + num]->doepctl; + } else { + addr = + &GET_CORE_IF(pcd)->dev_if->in_ep_regs[dwc_ep->num]->diepctl; + } + depctl.b.epena = 1; + DWC_MODIFY_REG32(addr, depctl.d32, depctl.d32); +} + +/** + * This function sets latest iso packet information(non-PTI mode) + * + * @param core_if Programming view of DWC_otg controller. + * @param ep The EP to start the transfer on. + * + */ +void set_current_pkt_info(dwc_otg_core_if_t * core_if, dwc_ep_t * ep) +{ + deptsiz_data_t deptsiz = {.d32 = 0 }; + dma_addr_t dma_addr; + uint32_t offset; + + if (ep->proc_buf_num) + dma_addr = ep->dma_addr1; + else + dma_addr = ep->dma_addr0; + + if (ep->is_in) { + deptsiz.d32 = + DWC_READ_REG32(&core_if->dev_if-> + in_ep_regs[ep->num]->dieptsiz); + offset = ep->data_per_frame; + } else { + deptsiz.d32 = + DWC_READ_REG32(&core_if->dev_if-> + out_ep_regs[ep->num]->doeptsiz); + offset = + ep->data_per_frame + + (0x4 & (0x4 - (ep->data_per_frame & 0x3))); + } + + if (!deptsiz.b.xfersize) { + ep->pkt_info[ep->cur_pkt].length = ep->data_per_frame; + ep->pkt_info[ep->cur_pkt].offset = + ep->cur_pkt_dma_addr - dma_addr; + ep->pkt_info[ep->cur_pkt].status = 0; + } else { + ep->pkt_info[ep->cur_pkt].length = ep->data_per_frame; + ep->pkt_info[ep->cur_pkt].offset = + ep->cur_pkt_dma_addr - dma_addr; + ep->pkt_info[ep->cur_pkt].status = -DWC_E_NO_DATA; + } + ep->cur_pkt_addr += offset; + ep->cur_pkt_dma_addr += offset; + ep->cur_pkt++; +} + +/** + * This function sets latest iso packet information(DDMA mode) + * + * @param core_if Programming view of DWC_otg controller. + * @param dwc_ep The EP to start the transfer on. + * + */ +static void set_ddma_iso_pkts_info(dwc_otg_core_if_t * core_if, + dwc_ep_t * dwc_ep) +{ + dwc_otg_dev_dma_desc_t *dma_desc; + dev_dma_desc_sts_t sts = {.d32 = 0 }; + iso_pkt_info_t *iso_packet; + uint32_t data_per_desc; + uint32_t offset; + int i, j; + + iso_packet = dwc_ep->pkt_info; + + /** Reinit closed DMA Descriptors*/ + /** ISO OUT EP */ + if (dwc_ep->is_in == 0) { + dma_desc = + dwc_ep->iso_desc_addr + + dwc_ep->desc_cnt * dwc_ep->proc_buf_num; + offset = 0; + + for (i = 0; i < dwc_ep->desc_cnt - dwc_ep->pkt_per_frm; + i += dwc_ep->pkt_per_frm) { + for (j = 0; j < dwc_ep->pkt_per_frm; ++j) { + data_per_desc = + ((j + 1) * dwc_ep->maxpacket > + dwc_ep-> + data_per_frame) ? dwc_ep->data_per_frame - + j * dwc_ep->maxpacket : dwc_ep->maxpacket; + data_per_desc += + (data_per_desc % 4) ? (4 - + data_per_desc % + 4) : 0; + + sts.d32 = dma_desc->status.d32; + + /* Write status in iso_packet_decsriptor */ + iso_packet->status = + sts.b_iso_out.rxsts + + (sts.b_iso_out.bs ^ BS_DMA_DONE); + if (iso_packet->status) { + iso_packet->status = -DWC_E_NO_DATA; + } + + /* Received data length */ + if (!sts.b_iso_out.rxbytes) { + iso_packet->length = + data_per_desc - + sts.b_iso_out.rxbytes; + } else { + iso_packet->length = + data_per_desc - + sts.b_iso_out.rxbytes + (4 - + dwc_ep->data_per_frame + % 4); + } + + iso_packet->offset = offset; + + offset += data_per_desc; + dma_desc++; + iso_packet++; + } + } + + for (j = 0; j < dwc_ep->pkt_per_frm - 1; ++j) { + data_per_desc = + ((j + 1) * dwc_ep->maxpacket > + dwc_ep->data_per_frame) ? dwc_ep->data_per_frame - + j * dwc_ep->maxpacket : dwc_ep->maxpacket; + data_per_desc += + (data_per_desc % 4) ? (4 - data_per_desc % 4) : 0; + + sts.d32 = dma_desc->status.d32; + + /* Write status in iso_packet_decsriptor */ + iso_packet->status = + sts.b_iso_out.rxsts + + (sts.b_iso_out.bs ^ BS_DMA_DONE); + if (iso_packet->status) { + iso_packet->status = -DWC_E_NO_DATA; + } + + /* Received data length */ + iso_packet->length = + dwc_ep->data_per_frame - sts.b_iso_out.rxbytes; + + iso_packet->offset = offset; + + offset += data_per_desc; + iso_packet++; + dma_desc++; + } + + sts.d32 = dma_desc->status.d32; + + /* Write status in iso_packet_decsriptor */ + iso_packet->status = + sts.b_iso_out.rxsts + (sts.b_iso_out.bs ^ BS_DMA_DONE); + if (iso_packet->status) { + iso_packet->status = -DWC_E_NO_DATA; + } + /* Received data length */ + if (!sts.b_iso_out.rxbytes) { + iso_packet->length = + dwc_ep->data_per_frame - sts.b_iso_out.rxbytes; + } else { + iso_packet->length = + dwc_ep->data_per_frame - sts.b_iso_out.rxbytes + + (4 - dwc_ep->data_per_frame % 4); + } + + iso_packet->offset = offset; + } else { +/** ISO IN EP */ + + dma_desc = + dwc_ep->iso_desc_addr + + dwc_ep->desc_cnt * dwc_ep->proc_buf_num; + + for (i = 0; i < dwc_ep->desc_cnt - 1; i++) { + sts.d32 = dma_desc->status.d32; + + /* Write status in iso packet descriptor */ + iso_packet->status = + sts.b_iso_in.txsts + + (sts.b_iso_in.bs ^ BS_DMA_DONE); + if (iso_packet->status != 0) { + iso_packet->status = -DWC_E_NO_DATA; + + } + /* Bytes has been transfered */ + iso_packet->length = + dwc_ep->data_per_frame - sts.b_iso_in.txbytes; + + dma_desc++; + iso_packet++; + } + + sts.d32 = dma_desc->status.d32; + while (sts.b_iso_in.bs == BS_DMA_BUSY) { + sts.d32 = dma_desc->status.d32; + } + + /* Write status in iso packet descriptor ??? do be done with ERROR codes */ + iso_packet->status = + sts.b_iso_in.txsts + (sts.b_iso_in.bs ^ BS_DMA_DONE); + if (iso_packet->status != 0) { + iso_packet->status = -DWC_E_NO_DATA; + } + + /* Bytes has been transfered */ + iso_packet->length = + dwc_ep->data_per_frame - sts.b_iso_in.txbytes; + } +} + +/** + * This function reinitialize DMA Descriptors for Isochronous transfer + * + * @param core_if Programming view of DWC_otg controller. + * @param dwc_ep The EP to start the transfer on. + * + */ +static void reinit_ddma_iso_xfer(dwc_otg_core_if_t * core_if, dwc_ep_t * dwc_ep) +{ + int i, j; + dwc_otg_dev_dma_desc_t *dma_desc; + dma_addr_t dma_ad; + volatile uint32_t *addr; + dev_dma_desc_sts_t sts = {.d32 = 0 }; + uint32_t data_per_desc; + + if (dwc_ep->is_in == 0) { + addr = &core_if->dev_if->out_ep_regs[dwc_ep->num]->doepctl; + } else { + addr = &core_if->dev_if->in_ep_regs[dwc_ep->num]->diepctl; + } + + if (dwc_ep->proc_buf_num == 0) { + /** Buffer 0 descriptors setup */ + dma_ad = dwc_ep->dma_addr0; + } else { + /** Buffer 1 descriptors setup */ + dma_ad = dwc_ep->dma_addr1; + } + + /** Reinit closed DMA Descriptors*/ + /** ISO OUT EP */ + if (dwc_ep->is_in == 0) { + dma_desc = + dwc_ep->iso_desc_addr + + dwc_ep->desc_cnt * dwc_ep->proc_buf_num; + + sts.b_iso_out.bs = BS_HOST_READY; + sts.b_iso_out.rxsts = 0; + sts.b_iso_out.l = 0; + sts.b_iso_out.sp = 0; + sts.b_iso_out.ioc = 0; + sts.b_iso_out.pid = 0; + sts.b_iso_out.framenum = 0; + + for (i = 0; i < dwc_ep->desc_cnt - dwc_ep->pkt_per_frm; + i += dwc_ep->pkt_per_frm) { + for (j = 0; j < dwc_ep->pkt_per_frm; ++j) { + data_per_desc = + ((j + 1) * dwc_ep->maxpacket > + dwc_ep-> + data_per_frame) ? dwc_ep->data_per_frame - + j * dwc_ep->maxpacket : dwc_ep->maxpacket; + data_per_desc += + (data_per_desc % 4) ? (4 - + data_per_desc % + 4) : 0; + sts.b_iso_out.rxbytes = data_per_desc; + dma_desc->buf = dma_ad; + dma_desc->status.d32 = sts.d32; + + dma_ad += data_per_desc; + dma_desc++; + } + } + + for (j = 0; j < dwc_ep->pkt_per_frm - 1; ++j) { + + data_per_desc = + ((j + 1) * dwc_ep->maxpacket > + dwc_ep->data_per_frame) ? dwc_ep->data_per_frame - + j * dwc_ep->maxpacket : dwc_ep->maxpacket; + data_per_desc += + (data_per_desc % 4) ? (4 - data_per_desc % 4) : 0; + sts.b_iso_out.rxbytes = data_per_desc; + + dma_desc->buf = dma_ad; + dma_desc->status.d32 = sts.d32; + + dma_desc++; + dma_ad += data_per_desc; + } + + sts.b_iso_out.ioc = 1; + sts.b_iso_out.l = dwc_ep->proc_buf_num; + + data_per_desc = + ((j + 1) * dwc_ep->maxpacket > + dwc_ep->data_per_frame) ? dwc_ep->data_per_frame - + j * dwc_ep->maxpacket : dwc_ep->maxpacket; + data_per_desc += + (data_per_desc % 4) ? (4 - data_per_desc % 4) : 0; + sts.b_iso_out.rxbytes = data_per_desc; + + dma_desc->buf = dma_ad; + dma_desc->status.d32 = sts.d32; + } else { +/** ISO IN EP */ + + dma_desc = + dwc_ep->iso_desc_addr + + dwc_ep->desc_cnt * dwc_ep->proc_buf_num; + + sts.b_iso_in.bs = BS_HOST_READY; + sts.b_iso_in.txsts = 0; + sts.b_iso_in.sp = 0; + sts.b_iso_in.ioc = 0; + sts.b_iso_in.pid = dwc_ep->pkt_per_frm; + sts.b_iso_in.framenum = dwc_ep->next_frame; + sts.b_iso_in.txbytes = dwc_ep->data_per_frame; + sts.b_iso_in.l = 0; + + for (i = 0; i < dwc_ep->desc_cnt - 1; i++) { + dma_desc->buf = dma_ad; + dma_desc->status.d32 = sts.d32; + + sts.b_iso_in.framenum += dwc_ep->bInterval; + dma_ad += dwc_ep->data_per_frame; + dma_desc++; + } + + sts.b_iso_in.ioc = 1; + sts.b_iso_in.l = dwc_ep->proc_buf_num; + + dma_desc->buf = dma_ad; + dma_desc->status.d32 = sts.d32; + + dwc_ep->next_frame = + sts.b_iso_in.framenum + dwc_ep->bInterval * 1; + } + dwc_ep->proc_buf_num = (dwc_ep->proc_buf_num ^ 1) & 0x1; +} + +/** + * This function is to handle Iso EP transfer complete interrupt + * in case Iso out packet was dropped + * + * @param core_if Programming view of DWC_otg controller. + * @param dwc_ep The EP for wihich transfer complete was asserted + * + */ +static uint32_t handle_iso_out_pkt_dropped(dwc_otg_core_if_t * core_if, + dwc_ep_t * dwc_ep) +{ + uint32_t dma_addr; + uint32_t drp_pkt; + uint32_t drp_pkt_cnt; + deptsiz_data_t deptsiz = {.d32 = 0 }; + depctl_data_t depctl = {.d32 = 0 }; + int i; + + deptsiz.d32 = + DWC_READ_REG32(&core_if->dev_if-> + out_ep_regs[dwc_ep->num]->doeptsiz); + + drp_pkt = dwc_ep->pkt_cnt - deptsiz.b.pktcnt; + drp_pkt_cnt = dwc_ep->pkt_per_frm - (drp_pkt % dwc_ep->pkt_per_frm); + + /* Setting dropped packets status */ + for (i = 0; i < drp_pkt_cnt; ++i) { + dwc_ep->pkt_info[drp_pkt].status = -DWC_E_NO_DATA; + drp_pkt++; + deptsiz.b.pktcnt--; + } + + if (deptsiz.b.pktcnt > 0) { + deptsiz.b.xfersize = + dwc_ep->xfer_len - (dwc_ep->pkt_cnt - + deptsiz.b.pktcnt) * dwc_ep->maxpacket; + } else { + deptsiz.b.xfersize = 0; + deptsiz.b.pktcnt = 0; + } + + DWC_WRITE_REG32(&core_if->dev_if->out_ep_regs[dwc_ep->num]->doeptsiz, + deptsiz.d32); + + if (deptsiz.b.pktcnt > 0) { + if (dwc_ep->proc_buf_num) { + dma_addr = + dwc_ep->dma_addr1 + dwc_ep->xfer_len - + deptsiz.b.xfersize; + } else { + dma_addr = + dwc_ep->dma_addr0 + dwc_ep->xfer_len - + deptsiz.b.xfersize;; + } + + DWC_WRITE_REG32(&core_if->dev_if-> + out_ep_regs[dwc_ep->num]->doepdma, dma_addr); + + /** Re-enable endpoint, clear nak */ + depctl.d32 = 0; + depctl.b.epena = 1; + depctl.b.cnak = 1; + + DWC_MODIFY_REG32(&core_if->dev_if-> + out_ep_regs[dwc_ep->num]->doepctl, depctl.d32, + depctl.d32); + return 0; + } else { + return 1; + } +} + +/** + * This function sets iso packets information(PTI mode) + * + * @param core_if Programming view of DWC_otg controller. + * @param ep The EP to start the transfer on. + * + */ +static uint32_t set_iso_pkts_info(dwc_otg_core_if_t * core_if, dwc_ep_t * ep) +{ + int i, j; + dma_addr_t dma_ad; + iso_pkt_info_t *packet_info = ep->pkt_info; + uint32_t offset; + uint32_t frame_data; + deptsiz_data_t deptsiz; + + if (ep->proc_buf_num == 0) { + /** Buffer 0 descriptors setup */ + dma_ad = ep->dma_addr0; + } else { + /** Buffer 1 descriptors setup */ + dma_ad = ep->dma_addr1; + } + + if (ep->is_in) { + deptsiz.d32 = + DWC_READ_REG32(&core_if->dev_if->in_ep_regs[ep->num]-> + dieptsiz); + } else { + deptsiz.d32 = + DWC_READ_REG32(&core_if->dev_if->out_ep_regs[ep->num]-> + doeptsiz); + } + + if (!deptsiz.b.xfersize) { + offset = 0; + for (i = 0; i < ep->pkt_cnt; i += ep->pkt_per_frm) { + frame_data = ep->data_per_frame; + for (j = 0; j < ep->pkt_per_frm; ++j) { + + /* Packet status - is not set as initially + * it is set to 0 and if packet was sent + successfully, status field will remain 0*/ + + /* Bytes has been transfered */ + packet_info->length = + (ep->maxpacket < + frame_data) ? ep->maxpacket : frame_data; + + /* Received packet offset */ + packet_info->offset = offset; + offset += packet_info->length; + frame_data -= packet_info->length; + + packet_info++; + } + } + return 1; + } else { + /* This is a workaround for in case of Transfer Complete with + * PktDrpSts interrupts merging - in this case Transfer complete + * interrupt for Isoc Out Endpoint is asserted without PktDrpSts + * set and with DOEPTSIZ register non zero. Investigations showed, + * that this happens when Out packet is dropped, but because of + * interrupts merging during first interrupt handling PktDrpSts + * bit is cleared and for next merged interrupts it is not reset. + * In this case SW hadles the interrupt as if PktDrpSts bit is set. + */ + if (ep->is_in) { + return 1; + } else { + return handle_iso_out_pkt_dropped(core_if, ep); + } + } +} + +/** + * This function is to handle Iso EP transfer complete interrupt + * + * @param pcd The PCD + * @param ep The EP for which transfer complete was asserted + * + */ +static void complete_iso_ep(dwc_otg_pcd_t * pcd, dwc_otg_pcd_ep_t * ep) +{ + dwc_otg_core_if_t *core_if = GET_CORE_IF(ep->pcd); + dwc_ep_t *dwc_ep = &ep->dwc_ep; + uint8_t is_last = 0; + + if (ep->dwc_ep.next_frame == 0xffffffff) { + DWC_WARN("Next frame is not set!\n"); + return; + } + + if (core_if->dma_enable) { + if (core_if->dma_desc_enable) { + set_ddma_iso_pkts_info(core_if, dwc_ep); + reinit_ddma_iso_xfer(core_if, dwc_ep); + is_last = 1; + } else { + if (core_if->pti_enh_enable) { + if (set_iso_pkts_info(core_if, dwc_ep)) { + dwc_ep->proc_buf_num = + (dwc_ep->proc_buf_num ^ 1) & 0x1; + dwc_otg_iso_ep_start_buf_transfer + (core_if, dwc_ep); + is_last = 1; + } + } else { + set_current_pkt_info(core_if, dwc_ep); + if (dwc_ep->cur_pkt >= dwc_ep->pkt_cnt) { + is_last = 1; + dwc_ep->cur_pkt = 0; + dwc_ep->proc_buf_num = + (dwc_ep->proc_buf_num ^ 1) & 0x1; + if (dwc_ep->proc_buf_num) { + dwc_ep->cur_pkt_addr = + dwc_ep->xfer_buff1; + dwc_ep->cur_pkt_dma_addr = + dwc_ep->dma_addr1; + } else { + dwc_ep->cur_pkt_addr = + dwc_ep->xfer_buff0; + dwc_ep->cur_pkt_dma_addr = + dwc_ep->dma_addr0; + } + + } + dwc_otg_iso_ep_start_frm_transfer(core_if, + dwc_ep); + } + } + } else { + set_current_pkt_info(core_if, dwc_ep); + if (dwc_ep->cur_pkt >= dwc_ep->pkt_cnt) { + is_last = 1; + dwc_ep->cur_pkt = 0; + dwc_ep->proc_buf_num = (dwc_ep->proc_buf_num ^ 1) & 0x1; + if (dwc_ep->proc_buf_num) { + dwc_ep->cur_pkt_addr = dwc_ep->xfer_buff1; + dwc_ep->cur_pkt_dma_addr = dwc_ep->dma_addr1; + } else { + dwc_ep->cur_pkt_addr = dwc_ep->xfer_buff0; + dwc_ep->cur_pkt_dma_addr = dwc_ep->dma_addr0; + } + + } + dwc_otg_iso_ep_start_frm_transfer(core_if, dwc_ep); + } + if (is_last) + dwc_otg_iso_buffer_done(pcd, ep, ep->iso_req_handle); +} +#endif /* DWC_EN_ISOC */ + +/** + * This function handle BNA interrupt for Non Isochronous EPs + * + */ +static void dwc_otg_pcd_handle_noniso_bna(dwc_otg_pcd_ep_t * ep) +{ + dwc_ep_t *dwc_ep = &ep->dwc_ep; + volatile uint32_t *addr; + depctl_data_t depctl = {.d32 = 0 }; + dwc_otg_pcd_t *pcd = ep->pcd; + dwc_otg_dev_dma_desc_t *dma_desc; + dev_dma_desc_sts_t sts = {.d32 = 0 }; + dwc_otg_core_if_t *core_if = ep->pcd->core_if; + int i, start; + + if (!dwc_ep->desc_cnt) + DWC_WARN("Ep%d %s Descriptor count = %d \n", dwc_ep->num, + (dwc_ep->is_in ? "IN" : "OUT"), dwc_ep->desc_cnt); + + if (core_if->core_params->cont_on_bna && !dwc_ep->is_in + && dwc_ep->type != DWC_OTG_EP_TYPE_CONTROL) { + uint32_t doepdma; + dwc_otg_dev_out_ep_regs_t *out_regs = + core_if->dev_if->out_ep_regs[dwc_ep->num]; + doepdma = DWC_READ_REG32(&(out_regs->doepdma)); + start = (doepdma - dwc_ep->dma_desc_addr)/sizeof(dwc_otg_dev_dma_desc_t); + dma_desc = &(dwc_ep->desc_addr[start]); + } else { + start = 0; + dma_desc = dwc_ep->desc_addr; + } + + + for (i = start; i < dwc_ep->desc_cnt; ++i, ++dma_desc) { + sts.d32 = dma_desc->status.d32; + sts.b.bs = BS_HOST_READY; + dma_desc->status.d32 = sts.d32; + } + + if (dwc_ep->is_in == 0) { + addr = + &GET_CORE_IF(pcd)->dev_if->out_ep_regs[dwc_ep->num]-> + doepctl; + } else { + addr = + &GET_CORE_IF(pcd)->dev_if->in_ep_regs[dwc_ep->num]->diepctl; + } + depctl.b.epena = 1; + depctl.b.cnak = 1; + DWC_MODIFY_REG32(addr, 0, depctl.d32); +} + +/** + * This function handles EP0 Control transfers. + * + * The state of the control transfers are tracked in + * <code>ep0state</code>. + */ +static void handle_ep0(dwc_otg_pcd_t * pcd) +{ + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + dwc_otg_pcd_ep_t *ep0 = &pcd->ep0; + dev_dma_desc_sts_t desc_sts; + deptsiz0_data_t deptsiz; + uint32_t byte_count; + +#ifdef DEBUG_EP0 + DWC_DEBUGPL(DBG_PCDV, "%s()\n", __func__); + print_ep0_state(pcd); +#endif + +// DWC_PRINTF("HANDLE EP0\n"); + + switch (pcd->ep0state) { + case EP0_DISCONNECT: + break; + + case EP0_IDLE: + pcd->request_config = 0; + + pcd_setup(pcd); + break; + + case EP0_IN_DATA_PHASE: +#ifdef DEBUG_EP0 + DWC_DEBUGPL(DBG_PCD, "DATA_IN EP%d-%s: type=%d, mps=%d\n", + ep0->dwc_ep.num, (ep0->dwc_ep.is_in ? "IN" : "OUT"), + ep0->dwc_ep.type, ep0->dwc_ep.maxpacket); +#endif + + if (core_if->dma_enable != 0) { + /* + * For EP0 we can only program 1 packet at a time so we + * need to do the make calculations after each complete. + * Call write_packet to make the calculations, as in + * slave mode, and use those values to determine if we + * can complete. + */ + if (core_if->dma_desc_enable == 0) { + deptsiz.d32 = + DWC_READ_REG32(&core_if-> + dev_if->in_ep_regs[0]-> + dieptsiz); + byte_count = + ep0->dwc_ep.xfer_len - deptsiz.b.xfersize; + } else { + desc_sts = + core_if->dev_if->in_desc_addr->status; + byte_count = + ep0->dwc_ep.xfer_len - desc_sts.b.bytes; + } + ep0->dwc_ep.xfer_count += byte_count; + ep0->dwc_ep.xfer_buff += byte_count; + ep0->dwc_ep.dma_addr += byte_count; + } + if (ep0->dwc_ep.xfer_count < ep0->dwc_ep.total_len) { + dwc_otg_ep0_continue_transfer(GET_CORE_IF(pcd), + &ep0->dwc_ep); + DWC_DEBUGPL(DBG_PCD, "CONTINUE TRANSFER\n"); + } else if (ep0->dwc_ep.sent_zlp) { + dwc_otg_ep0_continue_transfer(GET_CORE_IF(pcd), + &ep0->dwc_ep); + ep0->dwc_ep.sent_zlp = 0; + DWC_DEBUGPL(DBG_PCD, "CONTINUE TRANSFER sent zlp\n"); + } else { + ep0_complete_request(ep0); + DWC_DEBUGPL(DBG_PCD, "COMPLETE TRANSFER\n"); + } + break; + case EP0_OUT_DATA_PHASE: +#ifdef DEBUG_EP0 + DWC_DEBUGPL(DBG_PCD, "DATA_OUT EP%d-%s: type=%d, mps=%d\n", + ep0->dwc_ep.num, (ep0->dwc_ep.is_in ? "IN" : "OUT"), + ep0->dwc_ep.type, ep0->dwc_ep.maxpacket); +#endif + if (core_if->dma_enable != 0) { + if (core_if->dma_desc_enable == 0) { + deptsiz.d32 = + DWC_READ_REG32(&core_if-> + dev_if->out_ep_regs[0]-> + doeptsiz); + byte_count = + ep0->dwc_ep.maxpacket - deptsiz.b.xfersize; + } else { + desc_sts = + core_if->dev_if->out_desc_addr->status; + byte_count = + ep0->dwc_ep.maxpacket - desc_sts.b.bytes; + } + ep0->dwc_ep.xfer_count += byte_count; + ep0->dwc_ep.xfer_buff += byte_count; + ep0->dwc_ep.dma_addr += byte_count; + } + if (ep0->dwc_ep.xfer_count < ep0->dwc_ep.total_len) { + dwc_otg_ep0_continue_transfer(GET_CORE_IF(pcd), + &ep0->dwc_ep); + DWC_DEBUGPL(DBG_PCD, "CONTINUE TRANSFER\n"); + } else if (ep0->dwc_ep.sent_zlp) { + dwc_otg_ep0_continue_transfer(GET_CORE_IF(pcd), + &ep0->dwc_ep); + ep0->dwc_ep.sent_zlp = 0; + DWC_DEBUGPL(DBG_PCD, "CONTINUE TRANSFER sent zlp\n"); + } else { + ep0_complete_request(ep0); + DWC_DEBUGPL(DBG_PCD, "COMPLETE TRANSFER\n"); + } + break; + + case EP0_IN_STATUS_PHASE: + case EP0_OUT_STATUS_PHASE: + DWC_DEBUGPL(DBG_PCD, "CASE: EP0_STATUS\n"); + ep0_complete_request(ep0); + pcd->ep0state = EP0_IDLE; + ep0->stopped = 1; + ep0->dwc_ep.is_in = 0; /* OUT for next SETUP */ + + /* Prepare for more SETUP Packets */ + if (core_if->dma_enable) { + ep0_out_start(core_if, pcd); + } + break; + + case EP0_STALL: + DWC_ERROR("EP0 STALLed, should not get here pcd_setup()\n"); + break; + } +#ifdef DEBUG_EP0 + print_ep0_state(pcd); +#endif +} + +/** + * Restart transfer + */ +static void restart_transfer(dwc_otg_pcd_t * pcd, const uint32_t epnum) +{ + dwc_otg_core_if_t *core_if; + dwc_otg_dev_if_t *dev_if; + deptsiz_data_t dieptsiz = {.d32 = 0 }; + dwc_otg_pcd_ep_t *ep; + + ep = get_in_ep(pcd, epnum); + +#ifdef DWC_EN_ISOC + if (ep->dwc_ep.type == DWC_OTG_EP_TYPE_ISOC) { + return; + } +#endif /* DWC_EN_ISOC */ + + core_if = GET_CORE_IF(pcd); + dev_if = core_if->dev_if; + + dieptsiz.d32 = DWC_READ_REG32(&dev_if->in_ep_regs[epnum]->dieptsiz); + + DWC_DEBUGPL(DBG_PCD, "xfer_buff=%p xfer_count=%0x xfer_len=%0x" + " stopped=%d\n", ep->dwc_ep.xfer_buff, + ep->dwc_ep.xfer_count, ep->dwc_ep.xfer_len, ep->stopped); + /* + * If xfersize is 0 and pktcnt in not 0, resend the last packet. + */ + if (dieptsiz.b.pktcnt && dieptsiz.b.xfersize == 0 && + ep->dwc_ep.start_xfer_buff != 0) { + if (ep->dwc_ep.total_len <= ep->dwc_ep.maxpacket) { + ep->dwc_ep.xfer_count = 0; + ep->dwc_ep.xfer_buff = ep->dwc_ep.start_xfer_buff; + ep->dwc_ep.xfer_len = ep->dwc_ep.xfer_count; + } else { + ep->dwc_ep.xfer_count -= ep->dwc_ep.maxpacket; + /* convert packet size to dwords. */ + ep->dwc_ep.xfer_buff -= ep->dwc_ep.maxpacket; + ep->dwc_ep.xfer_len = ep->dwc_ep.xfer_count; + } + ep->stopped = 0; + DWC_DEBUGPL(DBG_PCD, "xfer_buff=%p xfer_count=%0x " + "xfer_len=%0x stopped=%d\n", + ep->dwc_ep.xfer_buff, + ep->dwc_ep.xfer_count, ep->dwc_ep.xfer_len, + ep->stopped); + if (epnum == 0) { + dwc_otg_ep0_start_transfer(core_if, &ep->dwc_ep); + } else { + dwc_otg_ep_start_transfer(core_if, &ep->dwc_ep); + } + } +} + +/* + * This function create new nextep sequnce based on Learn Queue. + * + * @param core_if Programming view of DWC_otg controller + */ +static void predict_nextep_seq( dwc_otg_core_if_t * core_if) +{ + dwc_otg_device_global_regs_t *dev_global_regs = + core_if->dev_if->dev_global_regs; + const uint32_t TOKEN_Q_DEPTH = core_if->hwcfg2.b.dev_token_q_depth; + /* Number of Token Queue Registers */ + const int DTKNQ_REG_CNT = (TOKEN_Q_DEPTH + 7) / 8; + dtknq1_data_t dtknqr1; + uint32_t in_tkn_epnums[4]; + uint8_t seqnum[MAX_EPS_CHANNELS]; + uint8_t intkn_seq[1 << 5]; + grstctl_t resetctl = {.d32 = 0 }; + uint8_t temp; + int ndx = 0; + int start = 0; + int end = 0; + int sort_done = 0; + int i = 0; + volatile uint32_t *addr = &dev_global_regs->dtknqr1; + + + DWC_DEBUGPL(DBG_PCD,"dev_token_q_depth=%d\n",TOKEN_Q_DEPTH); + + /* Read the DTKNQ Registers */ + for (i = 0; i < DTKNQ_REG_CNT; i++) { + in_tkn_epnums[i] = DWC_READ_REG32(addr); + DWC_DEBUGPL(DBG_PCDV, "DTKNQR%d=0x%08x\n", i + 1, + in_tkn_epnums[i]); + if (addr == &dev_global_regs->dvbusdis) { + addr = &dev_global_regs->dtknqr3_dthrctl; + } else { + ++addr; + } + + } + + /* Copy the DTKNQR1 data to the bit field. */ + dtknqr1.d32 = in_tkn_epnums[0]; + if (dtknqr1.b.wrap_bit) { + ndx = dtknqr1.b.intknwptr; + end = ndx -1; + if (end < 0) + end = TOKEN_Q_DEPTH -1; + } else { + ndx = 0; + end = dtknqr1.b.intknwptr -1; + if (end < 0) + end = 0; + } + start = ndx; + + /* Fill seqnum[] by initial values: EP number + 31 */ + for (i=0; i <= core_if->dev_if->num_in_eps; i++) { + seqnum[i] = i +31; + } + + /* Fill intkn_seq[] from in_tkn_epnums[0] */ + for (i=0; i < 6; i++) + intkn_seq[i] = (in_tkn_epnums[0] >> ((7-i) * 4)) & 0xf; + + if (TOKEN_Q_DEPTH > 6) { + /* Fill intkn_seq[] from in_tkn_epnums[1] */ + for (i=6; i < 14; i++) + intkn_seq[i] = + (in_tkn_epnums[1] >> ((7 - (i - 6)) * 4)) & 0xf; + } + + if (TOKEN_Q_DEPTH > 14) { + /* Fill intkn_seq[] from in_tkn_epnums[1] */ + for (i=14; i < 22; i++) + intkn_seq[i] = + (in_tkn_epnums[2] >> ((7 - (i - 14)) * 4)) & 0xf; + } + + if (TOKEN_Q_DEPTH > 22) { + /* Fill intkn_seq[] from in_tkn_epnums[1] */ + for (i=22; i < 30; i++) + intkn_seq[i] = + (in_tkn_epnums[3] >> ((7 - (i - 22)) * 4)) & 0xf; + } + + DWC_DEBUGPL(DBG_PCDV, "%s start=%d end=%d intkn_seq[]:\n", __func__, + start, end); + for (i=0; i<TOKEN_Q_DEPTH; i++) + DWC_DEBUGPL(DBG_PCDV,"%d\n", intkn_seq[i]); + + /* Update seqnum based on intkn_seq[] */ + i = 0; + do { + seqnum[intkn_seq[ndx]] = i; + ndx++; + i++; + if (ndx == TOKEN_Q_DEPTH) + ndx = 0; + } while ( i < TOKEN_Q_DEPTH ); + + /* Mark non active EP's in seqnum[] by 0xff */ + for (i=0; i<=core_if->dev_if->num_in_eps; i++) { + if (core_if->nextep_seq[i] == 0xff ) + seqnum[i] = 0xff; + } + + /* Sort seqnum[] */ + sort_done = 0; + while (!sort_done) { + sort_done = 1; + for (i=0; i<core_if->dev_if->num_in_eps; i++) { + if (seqnum[i] > seqnum[i+1]) { + temp = seqnum[i]; + seqnum[i] = seqnum[i+1]; + seqnum[i+1] = temp; + sort_done = 0; + } + } + } + + ndx = start + seqnum[0]; + if (ndx >= TOKEN_Q_DEPTH) + ndx = ndx % TOKEN_Q_DEPTH; + core_if->first_in_nextep_seq = intkn_seq[ndx]; + + /* Update seqnum[] by EP numbers */ + for (i=0; i<=core_if->dev_if->num_in_eps; i++) { + ndx = start + i; + if (seqnum[i] < 31) { + ndx = start + seqnum[i]; + if (ndx >= TOKEN_Q_DEPTH) + ndx = ndx % TOKEN_Q_DEPTH; + seqnum[i] = intkn_seq[ndx]; + } else { + if (seqnum[i] < 0xff) { + seqnum[i] = seqnum[i] - 31; + } else { + break; + } + } + } + + /* Update nextep_seq[] based on seqnum[] */ + for (i=0; i<core_if->dev_if->num_in_eps; i++) { + if (seqnum[i] != 0xff) { + if (seqnum[i+1] != 0xff) { + core_if->nextep_seq[seqnum[i]] = seqnum[i+1]; + } else { + core_if->nextep_seq[seqnum[i]] = core_if->first_in_nextep_seq; + break; + } + } else { + break; + } + } + + DWC_DEBUGPL(DBG_PCDV, "%s first_in_nextep_seq= %2d; nextep_seq[]:\n", + __func__, core_if->first_in_nextep_seq); + for (i=0; i <= core_if->dev_if->num_in_eps; i++) { + DWC_DEBUGPL(DBG_PCDV,"%2d\n", core_if->nextep_seq[i]); + } + + /* Flush the Learning Queue */ + resetctl.d32 = DWC_READ_REG32(&core_if->core_global_regs->grstctl); + resetctl.b.intknqflsh = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->grstctl, resetctl.d32); + + +} + +/** + * handle the IN EP disable interrupt. + */ +static inline void handle_in_ep_disable_intr(dwc_otg_pcd_t * pcd, + const uint32_t epnum) +{ + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + dwc_otg_dev_if_t *dev_if = core_if->dev_if; + deptsiz_data_t dieptsiz = {.d32 = 0 }; + dctl_data_t dctl = {.d32 = 0 }; + dwc_otg_pcd_ep_t *ep; + dwc_ep_t *dwc_ep; + gintmsk_data_t gintmsk_data; + depctl_data_t depctl; + uint32_t diepdma; + uint32_t remain_to_transfer = 0; + uint8_t i; + uint32_t xfer_size; + + ep = get_in_ep(pcd, epnum); + dwc_ep = &ep->dwc_ep; + + if (dwc_ep->type == DWC_OTG_EP_TYPE_ISOC) { + dwc_otg_flush_tx_fifo(core_if, dwc_ep->tx_fifo_num); + complete_ep(ep); + return; + } + + DWC_DEBUGPL(DBG_PCD, "diepctl%d=%0x\n", epnum, + DWC_READ_REG32(&dev_if->in_ep_regs[epnum]->diepctl)); + dieptsiz.d32 = DWC_READ_REG32(&dev_if->in_ep_regs[epnum]->dieptsiz); + depctl.d32 = DWC_READ_REG32(&dev_if->in_ep_regs[epnum]->diepctl); + + DWC_DEBUGPL(DBG_ANY, "pktcnt=%d size=%d\n", + dieptsiz.b.pktcnt, dieptsiz.b.xfersize); + + if ((core_if->start_predict == 0) || (depctl.b.eptype & 1)) { + if (ep->stopped) { + if (core_if->en_multiple_tx_fifo) + /* Flush the Tx FIFO */ + dwc_otg_flush_tx_fifo(core_if, dwc_ep->tx_fifo_num); + /* Clear the Global IN NP NAK */ + dctl.d32 = 0; + dctl.b.cgnpinnak = 1; + DWC_MODIFY_REG32(&dev_if->dev_global_regs->dctl, dctl.d32, dctl.d32); + /* Restart the transaction */ + if (dieptsiz.b.pktcnt != 0 || dieptsiz.b.xfersize != 0) { + restart_transfer(pcd, epnum); + } + } else { + /* Restart the transaction */ + if (dieptsiz.b.pktcnt != 0 || dieptsiz.b.xfersize != 0) { + restart_transfer(pcd, epnum); + } + DWC_DEBUGPL(DBG_ANY, "STOPPED!!!\n"); + } + return; + } + + if (core_if->start_predict > 2) { // NP IN EP + core_if->start_predict--; + return; + } + + core_if->start_predict--; + + if (core_if->start_predict == 1) { // All NP IN Ep's disabled now + + predict_nextep_seq(core_if); + + /* Update all active IN EP's NextEP field based of nextep_seq[] */ + for ( i = 0; i <= core_if->dev_if->num_in_eps; i++) { + depctl.d32 = + DWC_READ_REG32(&dev_if->in_ep_regs[i]->diepctl); + if (core_if->nextep_seq[i] != 0xff) { // Active NP IN EP + depctl.b.nextep = core_if->nextep_seq[i]; + DWC_WRITE_REG32(&dev_if->in_ep_regs[i]->diepctl, depctl.d32); + } + } + /* Flush Shared NP TxFIFO */ + dwc_otg_flush_tx_fifo(core_if, 0); + /* Rewind buffers */ + if (!core_if->dma_desc_enable) { + i = core_if->first_in_nextep_seq; + do { + ep = get_in_ep(pcd, i); + dieptsiz.d32 = DWC_READ_REG32(&dev_if->in_ep_regs[i]->dieptsiz); + xfer_size = ep->dwc_ep.total_len - ep->dwc_ep.xfer_count; + if (xfer_size > ep->dwc_ep.maxxfer) + xfer_size = ep->dwc_ep.maxxfer; + depctl.d32 = DWC_READ_REG32(&dev_if->in_ep_regs[i]->diepctl); + if (dieptsiz.b.pktcnt != 0) { + if (xfer_size == 0) { + remain_to_transfer = 0; + } else { + if ((xfer_size % ep->dwc_ep.maxpacket) == 0) { + remain_to_transfer = + dieptsiz.b.pktcnt * ep->dwc_ep.maxpacket; + } else { + remain_to_transfer = ((dieptsiz.b.pktcnt -1) * ep->dwc_ep.maxpacket) + + (xfer_size % ep->dwc_ep.maxpacket); + } + } + diepdma = DWC_READ_REG32(&dev_if->in_ep_regs[i]->diepdma); + dieptsiz.b.xfersize = remain_to_transfer; + DWC_WRITE_REG32(&dev_if->in_ep_regs[i]->dieptsiz, dieptsiz.d32); + diepdma = ep->dwc_ep.dma_addr + (xfer_size - remain_to_transfer); + DWC_WRITE_REG32(&dev_if->in_ep_regs[i]->diepdma, diepdma); + } + i = core_if->nextep_seq[i]; + } while (i != core_if->first_in_nextep_seq); + } else { // dma_desc_enable + DWC_PRINTF("%s Learning Queue not supported in DDMA\n", __func__); + } + + /* Restart transfers in predicted sequences */ + i = core_if->first_in_nextep_seq; + do { + dieptsiz.d32 = DWC_READ_REG32(&dev_if->in_ep_regs[i]->dieptsiz); + depctl.d32 = DWC_READ_REG32(&dev_if->in_ep_regs[i]->diepctl); + if (dieptsiz.b.pktcnt != 0) { + depctl.d32 = DWC_READ_REG32(&dev_if->in_ep_regs[i]->diepctl); + depctl.b.epena = 1; + depctl.b.cnak = 1; + DWC_WRITE_REG32(&dev_if->in_ep_regs[i]->diepctl, depctl.d32); + } + i = core_if->nextep_seq[i]; + } while (i != core_if->first_in_nextep_seq); + + /* Clear the global non-periodic IN NAK handshake */ + dctl.d32 = 0; + dctl.b.cgnpinnak = 1; + DWC_MODIFY_REG32(&dev_if->dev_global_regs->dctl, dctl.d32, dctl.d32); + + /* Unmask EP Mismatch interrupt */ + gintmsk_data.d32 = 0; + gintmsk_data.b.epmismatch = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gintmsk, 0, gintmsk_data.d32); + + core_if->start_predict = 0; + + } +} + +/** + * Handler for the IN EP timeout handshake interrupt. + */ +static inline void handle_in_ep_timeout_intr(dwc_otg_pcd_t * pcd, + const uint32_t epnum) +{ + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + dwc_otg_dev_if_t *dev_if = core_if->dev_if; + +#ifdef DEBUG + deptsiz_data_t dieptsiz = {.d32 = 0 }; + uint32_t num = 0; +#endif + dctl_data_t dctl = {.d32 = 0 }; + dwc_otg_pcd_ep_t *ep; + + gintmsk_data_t intr_mask = {.d32 = 0 }; + + ep = get_in_ep(pcd, epnum); + + /* Disable the NP Tx Fifo Empty Interrrupt */ + if (!core_if->dma_enable) { + intr_mask.b.nptxfempty = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gintmsk, + intr_mask.d32, 0); + } + /** @todo NGS Check EP type. + * Implement for Periodic EPs */ + /* + * Non-periodic EP + */ + /* Enable the Global IN NAK Effective Interrupt */ + intr_mask.b.ginnakeff = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gintmsk, 0, intr_mask.d32); + + /* Set Global IN NAK */ + dctl.b.sgnpinnak = 1; + DWC_MODIFY_REG32(&dev_if->dev_global_regs->dctl, dctl.d32, dctl.d32); + + ep->stopped = 1; + +#ifdef DEBUG + dieptsiz.d32 = DWC_READ_REG32(&dev_if->in_ep_regs[num]->dieptsiz); + DWC_DEBUGPL(DBG_ANY, "pktcnt=%d size=%d\n", + dieptsiz.b.pktcnt, dieptsiz.b.xfersize); +#endif + +#ifdef DISABLE_PERIODIC_EP + /* + * Set the NAK bit for this EP to + * start the disable process. + */ + diepctl.d32 = 0; + diepctl.b.snak = 1; + DWC_MODIFY_REG32(&dev_if->in_ep_regs[num]->diepctl, diepctl.d32, + diepctl.d32); + ep->disabling = 1; + ep->stopped = 1; +#endif +} + +/** + * Handler for the IN EP NAK interrupt. + */ +static inline int32_t handle_in_ep_nak_intr(dwc_otg_pcd_t * pcd, + const uint32_t epnum) +{ + /** @todo implement ISR */ + dwc_otg_core_if_t *core_if; + diepmsk_data_t intr_mask = {.d32 = 0 }; + + DWC_PRINTF("INTERRUPT Handler not implemented for %s\n", "IN EP NAK"); + core_if = GET_CORE_IF(pcd); + intr_mask.b.nak = 1; + + if (core_if->multiproc_int_enable) { + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs-> + diepeachintmsk[epnum], intr_mask.d32, 0); + } else { + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->diepmsk, + intr_mask.d32, 0); + } + + return 1; +} + +/** + * Handler for the OUT EP Babble interrupt. + */ +static inline int32_t handle_out_ep_babble_intr(dwc_otg_pcd_t * pcd, + const uint32_t epnum) +{ + /** @todo implement ISR */ + dwc_otg_core_if_t *core_if; + doepmsk_data_t intr_mask = {.d32 = 0 }; + + DWC_PRINTF("INTERRUPT Handler not implemented for %s\n", + "OUT EP Babble"); + core_if = GET_CORE_IF(pcd); + intr_mask.b.babble = 1; + + if (core_if->multiproc_int_enable) { + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs-> + doepeachintmsk[epnum], intr_mask.d32, 0); + } else { + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->doepmsk, + intr_mask.d32, 0); + } + + return 1; +} + +/** + * Handler for the OUT EP NAK interrupt. + */ +static inline int32_t handle_out_ep_nak_intr(dwc_otg_pcd_t * pcd, + const uint32_t epnum) +{ + /** @todo implement ISR */ + dwc_otg_core_if_t *core_if; + doepmsk_data_t intr_mask = {.d32 = 0 }; + + DWC_DEBUGPL(DBG_ANY, "INTERRUPT Handler not implemented for %s\n", "OUT EP NAK"); + core_if = GET_CORE_IF(pcd); + intr_mask.b.nak = 1; + + if (core_if->multiproc_int_enable) { + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs-> + doepeachintmsk[epnum], intr_mask.d32, 0); + } else { + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->doepmsk, + intr_mask.d32, 0); + } + + return 1; +} + +/** + * Handler for the OUT EP NYET interrupt. + */ +static inline int32_t handle_out_ep_nyet_intr(dwc_otg_pcd_t * pcd, + const uint32_t epnum) +{ + /** @todo implement ISR */ + dwc_otg_core_if_t *core_if; + doepmsk_data_t intr_mask = {.d32 = 0 }; + + DWC_PRINTF("INTERRUPT Handler not implemented for %s\n", "OUT EP NYET"); + core_if = GET_CORE_IF(pcd); + intr_mask.b.nyet = 1; + + if (core_if->multiproc_int_enable) { + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs-> + doepeachintmsk[epnum], intr_mask.d32, 0); + } else { + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->doepmsk, + intr_mask.d32, 0); + } + + return 1; +} + +/** + * This interrupt indicates that an IN EP has a pending Interrupt. + * The sequence for handling the IN EP interrupt is shown below: + * -# Read the Device All Endpoint Interrupt register + * -# Repeat the following for each IN EP interrupt bit set (from + * LSB to MSB). + * -# Read the Device Endpoint Interrupt (DIEPINTn) register + * -# If "Transfer Complete" call the request complete function + * -# If "Endpoint Disabled" complete the EP disable procedure. + * -# If "AHB Error Interrupt" log error + * -# If "Time-out Handshake" log error + * -# If "IN Token Received when TxFIFO Empty" write packet to Tx + * FIFO. + * -# If "IN Token EP Mismatch" (disable, this is handled by EP + * Mismatch Interrupt) + */ +static int32_t dwc_otg_pcd_handle_in_ep_intr(dwc_otg_pcd_t * pcd) +{ +#define CLEAR_IN_EP_INTR(__core_if,__epnum,__intr) \ +do { \ + diepint_data_t diepint = {.d32=0}; \ + diepint.b.__intr = 1; \ + DWC_WRITE_REG32(&__core_if->dev_if->in_ep_regs[__epnum]->diepint, \ + diepint.d32); \ +} while (0) + + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + dwc_otg_dev_if_t *dev_if = core_if->dev_if; + diepint_data_t diepint = {.d32 = 0 }; + depctl_data_t depctl = {.d32 = 0 }; + uint32_t ep_intr; + uint32_t epnum = 0; + dwc_otg_pcd_ep_t *ep; + dwc_ep_t *dwc_ep; + gintmsk_data_t intr_mask = {.d32 = 0 }; + + DWC_DEBUGPL(DBG_PCDV, "%s(%p)\n", __func__, pcd); + + /* Read in the device interrupt bits */ + ep_intr = dwc_otg_read_dev_all_in_ep_intr(core_if); + + /* Service the Device IN interrupts for each endpoint */ + while (ep_intr) { + if (ep_intr & 0x1) { + uint32_t empty_msk; + /* Get EP pointer */ + ep = get_in_ep(pcd, epnum); + dwc_ep = &ep->dwc_ep; + + depctl.d32 = + DWC_READ_REG32(&dev_if->in_ep_regs[epnum]->diepctl); + empty_msk = + DWC_READ_REG32(&dev_if-> + dev_global_regs->dtknqr4_fifoemptymsk); + + DWC_DEBUGPL(DBG_PCDV, + "IN EP INTERRUPT - %d\nepmty_msk - %8x diepctl - %8x\n", + epnum, empty_msk, depctl.d32); + + DWC_DEBUGPL(DBG_PCD, + "EP%d-%s: type=%d, mps=%d\n", + dwc_ep->num, (dwc_ep->is_in ? "IN" : "OUT"), + dwc_ep->type, dwc_ep->maxpacket); + + diepint.d32 = + dwc_otg_read_dev_in_ep_intr(core_if, dwc_ep); + + DWC_DEBUGPL(DBG_PCDV, + "EP %d Interrupt Register - 0x%x\n", epnum, + diepint.d32); + /* Transfer complete */ + if (diepint.b.xfercompl) { + /* Disable the NP Tx FIFO Empty + * Interrupt */ + if (core_if->en_multiple_tx_fifo == 0) { + intr_mask.b.nptxfempty = 1; + DWC_MODIFY_REG32 + (&core_if->core_global_regs->gintmsk, + intr_mask.d32, 0); + } else { + /* Disable the Tx FIFO Empty Interrupt for this EP */ + uint32_t fifoemptymsk = + 0x1 << dwc_ep->num; + DWC_MODIFY_REG32(&core_if-> + dev_if->dev_global_regs->dtknqr4_fifoemptymsk, + fifoemptymsk, 0); + } + /* Clear the bit in DIEPINTn for this interrupt */ + CLEAR_IN_EP_INTR(core_if, epnum, xfercompl); + + /* Complete the transfer */ + if (epnum == 0) { + handle_ep0(pcd); + } +#ifdef DWC_EN_ISOC + else if (dwc_ep->type == DWC_OTG_EP_TYPE_ISOC) { + if (!ep->stopped) + complete_iso_ep(pcd, ep); + } +#endif /* DWC_EN_ISOC */ +#ifdef DWC_UTE_PER_IO + else if (dwc_ep->type == DWC_OTG_EP_TYPE_ISOC) { + if (!ep->stopped) + complete_xiso_ep(ep); + } +#endif /* DWC_UTE_PER_IO */ + else { + if (dwc_ep->type == DWC_OTG_EP_TYPE_ISOC && + dwc_ep->bInterval > 1) { + dwc_ep->frame_num += dwc_ep->bInterval; + if (dwc_ep->frame_num > 0x3FFF) + { + dwc_ep->frm_overrun = 1; + dwc_ep->frame_num &= 0x3FFF; + } else + dwc_ep->frm_overrun = 0; + } + complete_ep(ep); + if(diepint.b.nak) + CLEAR_IN_EP_INTR(core_if, epnum, nak); + } + } + /* Endpoint disable */ + if (diepint.b.epdisabled) { + DWC_DEBUGPL(DBG_ANY, "EP%d IN disabled\n", + epnum); + handle_in_ep_disable_intr(pcd, epnum); + + /* Clear the bit in DIEPINTn for this interrupt */ + CLEAR_IN_EP_INTR(core_if, epnum, epdisabled); + } + /* AHB Error */ + if (diepint.b.ahberr) { + DWC_ERROR("EP%d IN AHB Error\n", epnum); + /* Clear the bit in DIEPINTn for this interrupt */ + CLEAR_IN_EP_INTR(core_if, epnum, ahberr); + } + /* TimeOUT Handshake (non-ISOC IN EPs) */ + if (diepint.b.timeout) { + DWC_ERROR("EP%d IN Time-out\n", epnum); + handle_in_ep_timeout_intr(pcd, epnum); + + CLEAR_IN_EP_INTR(core_if, epnum, timeout); + } + /** IN Token received with TxF Empty */ + if (diepint.b.intktxfemp) { + DWC_DEBUGPL(DBG_ANY, + "EP%d IN TKN TxFifo Empty\n", + epnum); + if (!ep->stopped && epnum != 0) { + + diepmsk_data_t diepmsk = {.d32 = 0 }; + diepmsk.b.intktxfemp = 1; + + if (core_if->multiproc_int_enable) { + DWC_MODIFY_REG32 + (&dev_if->dev_global_regs->diepeachintmsk + [epnum], diepmsk.d32, 0); + } else { + DWC_MODIFY_REG32 + (&dev_if->dev_global_regs->diepmsk, + diepmsk.d32, 0); + } + } else if (core_if->dma_desc_enable + && epnum == 0 + && pcd->ep0state == + EP0_OUT_STATUS_PHASE) { + // EP0 IN set STALL + depctl.d32 = + DWC_READ_REG32(&dev_if->in_ep_regs + [epnum]->diepctl); + + /* set the disable and stall bits */ + if (depctl.b.epena) { + depctl.b.epdis = 1; + } + depctl.b.stall = 1; + DWC_WRITE_REG32(&dev_if->in_ep_regs + [epnum]->diepctl, + depctl.d32); + } + CLEAR_IN_EP_INTR(core_if, epnum, intktxfemp); + } + /** IN Token Received with EP mismatch */ + if (diepint.b.intknepmis) { + DWC_DEBUGPL(DBG_ANY, + "EP%d IN TKN EP Mismatch\n", epnum); + CLEAR_IN_EP_INTR(core_if, epnum, intknepmis); + } + /** IN Endpoint NAK Effective */ + if (diepint.b.inepnakeff) { + DWC_DEBUGPL(DBG_ANY, + "EP%d IN EP NAK Effective\n", + epnum); + /* Periodic EP */ + if (ep->disabling) { + depctl.d32 = 0; + depctl.b.snak = 1; + depctl.b.epdis = 1; + DWC_MODIFY_REG32(&dev_if->in_ep_regs + [epnum]->diepctl, + depctl.d32, + depctl.d32); + } + CLEAR_IN_EP_INTR(core_if, epnum, inepnakeff); + + } + + /** IN EP Tx FIFO Empty Intr */ + if (diepint.b.emptyintr) { + DWC_DEBUGPL(DBG_ANY, + "EP%d Tx FIFO Empty Intr \n", + epnum); + write_empty_tx_fifo(pcd, epnum); + + CLEAR_IN_EP_INTR(core_if, epnum, emptyintr); + + } + + /** IN EP BNA Intr */ + if (diepint.b.bna) { + CLEAR_IN_EP_INTR(core_if, epnum, bna); + if (core_if->dma_desc_enable) { +#ifdef DWC_EN_ISOC + if (dwc_ep->type == + DWC_OTG_EP_TYPE_ISOC) { + /* + * This checking is performed to prevent first "false" BNA + * handling occuring right after reconnect + */ + if (dwc_ep->next_frame != + 0xffffffff) + dwc_otg_pcd_handle_iso_bna(ep); + } else +#endif /* DWC_EN_ISOC */ + { + dwc_otg_pcd_handle_noniso_bna(ep); + } + } + } + /* NAK Interrutp */ + if (diepint.b.nak) { + DWC_DEBUGPL(DBG_ANY, "EP%d IN NAK Interrupt\n", + epnum); + if (ep->dwc_ep.type == DWC_OTG_EP_TYPE_ISOC) { + depctl_data_t depctl; + if (ep->dwc_ep.frame_num == 0xFFFFFFFF) { + ep->dwc_ep.frame_num = core_if->frame_num; + if (ep->dwc_ep.bInterval > 1) { + depctl.d32 = 0; + depctl.d32 = DWC_READ_REG32(&dev_if->in_ep_regs[epnum]->diepctl); + if (ep->dwc_ep.frame_num & 0x1) { + depctl.b.setd1pid = 1; + depctl.b.setd0pid = 0; + } else { + depctl.b.setd0pid = 1; + depctl.b.setd1pid = 0; + } + DWC_WRITE_REG32(&dev_if->in_ep_regs[epnum]->diepctl, depctl.d32); + } + start_next_request(ep); + } + ep->dwc_ep.frame_num += ep->dwc_ep.bInterval; + if (dwc_ep->frame_num > 0x3FFF) { + dwc_ep->frm_overrun = 1; + dwc_ep->frame_num &= 0x3FFF; + } else + dwc_ep->frm_overrun = 0; + } + + CLEAR_IN_EP_INTR(core_if, epnum, nak); + } + } + epnum++; + ep_intr >>= 1; + } + + return 1; +#undef CLEAR_IN_EP_INTR +} + +/** + * This interrupt indicates that an OUT EP has a pending Interrupt. + * The sequence for handling the OUT EP interrupt is shown below: + * -# Read the Device All Endpoint Interrupt register + * -# Repeat the following for each OUT EP interrupt bit set (from + * LSB to MSB). + * -# Read the Device Endpoint Interrupt (DOEPINTn) register + * -# If "Transfer Complete" call the request complete function + * -# If "Endpoint Disabled" complete the EP disable procedure. + * -# If "AHB Error Interrupt" log error + * -# If "Setup Phase Done" process Setup Packet (See Standard USB + * Command Processing) + */ +static int32_t dwc_otg_pcd_handle_out_ep_intr(dwc_otg_pcd_t * pcd) +{ +#define CLEAR_OUT_EP_INTR(__core_if,__epnum,__intr) \ +do { \ + doepint_data_t doepint = {.d32=0}; \ + doepint.b.__intr = 1; \ + DWC_WRITE_REG32(&__core_if->dev_if->out_ep_regs[__epnum]->doepint, \ + doepint.d32); \ +} while (0) + + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + uint32_t ep_intr; + doepint_data_t doepint = {.d32 = 0 }; + uint32_t epnum = 0; + dwc_otg_pcd_ep_t *ep; + dwc_ep_t *dwc_ep; + dctl_data_t dctl = {.d32 = 0 }; + gintmsk_data_t gintmsk = {.d32 = 0 }; + + + DWC_DEBUGPL(DBG_PCDV, "%s()\n", __func__); + + /* Read in the device interrupt bits */ + ep_intr = dwc_otg_read_dev_all_out_ep_intr(core_if); + + while (ep_intr) { + if (ep_intr & 0x1) { + /* Get EP pointer */ + ep = get_out_ep(pcd, epnum); + dwc_ep = &ep->dwc_ep; + +#ifdef VERBOSE + DWC_DEBUGPL(DBG_PCDV, + "EP%d-%s: type=%d, mps=%d\n", + dwc_ep->num, (dwc_ep->is_in ? "IN" : "OUT"), + dwc_ep->type, dwc_ep->maxpacket); +#endif + doepint.d32 = + dwc_otg_read_dev_out_ep_intr(core_if, dwc_ep); + /* Moved this interrupt upper due to core deffect of asserting + * OUT EP 0 xfercompl along with stsphsrcvd in BDMA */ + if (doepint.b.stsphsercvd) { + deptsiz0_data_t deptsiz; + CLEAR_OUT_EP_INTR(core_if, epnum, stsphsercvd); + deptsiz.d32 = + DWC_READ_REG32(&core_if->dev_if-> + out_ep_regs[0]->doeptsiz); + if (core_if->snpsid >= OTG_CORE_REV_3_00a + && core_if->dma_enable + && core_if->dma_desc_enable == 0 + && doepint.b.xfercompl + && deptsiz.b.xfersize == 24) { + CLEAR_OUT_EP_INTR(core_if, epnum, + xfercompl); + doepint.b.xfercompl = 0; + ep0_out_start(core_if, pcd); + } + if ((core_if->dma_desc_enable) || + (core_if->dma_enable + && core_if->snpsid >= + OTG_CORE_REV_3_00a)) { + do_setup_in_status_phase(pcd); + } + } + /* Transfer complete */ + if (doepint.b.xfercompl) { + + if (epnum == 0) { + /* Clear the bit in DOEPINTn for this interrupt */ + CLEAR_OUT_EP_INTR(core_if, epnum, xfercompl); + if (core_if->snpsid >= OTG_CORE_REV_3_00a) { + DWC_DEBUGPL(DBG_PCDV, "DOEPINT=%x doepint=%x\n", + DWC_READ_REG32(&core_if->dev_if->out_ep_regs[0]->doepint), + doepint.d32); + DWC_DEBUGPL(DBG_PCDV, "DOEPCTL=%x \n", + DWC_READ_REG32(&core_if->dev_if->out_ep_regs[0]->doepctl)); + + if (core_if->snpsid >= OTG_CORE_REV_3_00a + && core_if->dma_enable == 0) { + doepint_data_t doepint; + doepint.d32 = DWC_READ_REG32(&core_if->dev_if-> + out_ep_regs[0]->doepint); + if (pcd->ep0state == EP0_IDLE && doepint.b.sr) { + CLEAR_OUT_EP_INTR(core_if, epnum, sr); + goto exit_xfercompl; + } + } + /* In case of DDMA look at SR bit to go to the Data Stage */ + if (core_if->dma_desc_enable) { + dev_dma_desc_sts_t status = {.d32 = 0}; + if (pcd->ep0state == EP0_IDLE) { + status.d32 = core_if->dev_if->setup_desc_addr[core_if-> + dev_if->setup_desc_index]->status.d32; + if(pcd->data_terminated) { + pcd->data_terminated = 0; + status.d32 = core_if->dev_if->out_desc_addr->status.d32; + dwc_memcpy(&pcd->setup_pkt->req, pcd->backup_buf, 8); + } + if (status.b.sr) { + if (doepint.b.setup) { + DWC_DEBUGPL(DBG_PCDV, "DMA DESC EP0_IDLE SR=1 setup=1\n"); + /* Already started data stage, clear setup */ + CLEAR_OUT_EP_INTR(core_if, epnum, setup); + doepint.b.setup = 0; + handle_ep0(pcd); + /* Prepare for more setup packets */ + if (pcd->ep0state == EP0_IN_STATUS_PHASE || + pcd->ep0state == EP0_IN_DATA_PHASE) { + ep0_out_start(core_if, pcd); + } + + goto exit_xfercompl; + } else { + /* Prepare for more setup packets */ + DWC_DEBUGPL(DBG_PCDV, + "EP0_IDLE SR=1 setup=0 new setup comes\n"); + ep0_out_start(core_if, pcd); + } + } + } else { + dwc_otg_pcd_request_t *req; + dev_dma_desc_sts_t status = {.d32 = 0}; + diepint_data_t diepint0; + diepint0.d32 = DWC_READ_REG32(&core_if->dev_if-> + in_ep_regs[0]->diepint); + + if (pcd->ep0state == EP0_STALL || pcd->ep0state == EP0_DISCONNECT) { + DWC_ERROR("EP0 is stalled/disconnected\n"); + } + + /* Clear IN xfercompl if set */ + if (diepint0.b.xfercompl && (pcd->ep0state == EP0_IN_STATUS_PHASE + || pcd->ep0state == EP0_IN_DATA_PHASE)) { + DWC_WRITE_REG32(&core_if->dev_if-> + in_ep_regs[0]->diepint, diepint0.d32); + } + + status.d32 = core_if->dev_if->setup_desc_addr[core_if-> + dev_if->setup_desc_index]->status.d32; + + if (ep->dwc_ep.xfer_count != ep->dwc_ep.total_len + && (pcd->ep0state == EP0_OUT_DATA_PHASE)) + status.d32 = core_if->dev_if->out_desc_addr->status.d32; + if (pcd->ep0state == EP0_OUT_STATUS_PHASE) + status.d32 = core_if->dev_if-> + out_desc_addr->status.d32; + + if (status.b.sr) { + if (DWC_CIRCLEQ_EMPTY(&ep->queue)) { + DWC_DEBUGPL(DBG_PCDV, "Request queue empty!!\n"); + } else { + DWC_DEBUGPL(DBG_PCDV, "complete req!!\n"); + req = DWC_CIRCLEQ_FIRST(&ep->queue); + if (ep->dwc_ep.xfer_count != ep->dwc_ep.total_len && + pcd->ep0state == EP0_OUT_DATA_PHASE) { + /* Read arrived setup packet from req->buf */ + dwc_memcpy(&pcd->setup_pkt->req, + req->buf + ep->dwc_ep.xfer_count, 8); + } + req->actual = ep->dwc_ep.xfer_count; + dwc_otg_request_done(ep, req, -ECONNRESET); + ep->dwc_ep.start_xfer_buff = 0; + ep->dwc_ep.xfer_buff = 0; + ep->dwc_ep.xfer_len = 0; + } + pcd->ep0state = EP0_IDLE; + if (doepint.b.setup) { + DWC_DEBUGPL(DBG_PCDV, "EP0_IDLE SR=1 setup=1\n"); + /* Data stage started, clear setup */ + CLEAR_OUT_EP_INTR(core_if, epnum, setup); + doepint.b.setup = 0; + handle_ep0(pcd); + /* Prepare for setup packets if ep0in was enabled*/ + if (pcd->ep0state == EP0_IN_STATUS_PHASE) { + ep0_out_start(core_if, pcd); + } + + goto exit_xfercompl; + } else { + /* Prepare for more setup packets */ + DWC_DEBUGPL(DBG_PCDV, + "EP0_IDLE SR=1 setup=0 new setup comes 2\n"); + ep0_out_start(core_if, pcd); + } + } + } + } + if (core_if->snpsid >= OTG_CORE_REV_2_94a && core_if->dma_enable + && core_if->dma_desc_enable == 0) { + doepint_data_t doepint_temp = {.d32 = 0}; + deptsiz0_data_t doeptsize0 = {.d32 = 0 }; + doepint_temp.d32 = DWC_READ_REG32(&core_if->dev_if-> + out_ep_regs[ep->dwc_ep.num]->doepint); + doeptsize0.d32 = DWC_READ_REG32(&core_if->dev_if-> + out_ep_regs[ep->dwc_ep.num]->doeptsiz); + if (pcd->ep0state == EP0_IDLE) { + if (doepint_temp.b.sr) { + CLEAR_OUT_EP_INTR(core_if, epnum, sr); + } + doepint.d32 = DWC_READ_REG32(&core_if->dev_if-> + out_ep_regs[0]->doepint); + if (doeptsize0.b.supcnt == 3) { + DWC_DEBUGPL(DBG_ANY, "Rolling over!!!!!!!\n"); + ep->dwc_ep.stp_rollover = 1; + } + if (doepint.b.setup) { +retry: + /* Already started data stage, clear setup */ + CLEAR_OUT_EP_INTR(core_if, epnum, setup); + doepint.b.setup = 0; + handle_ep0(pcd); + ep->dwc_ep.stp_rollover = 0; + /* Prepare for more setup packets */ + if (pcd->ep0state == EP0_IN_STATUS_PHASE || + pcd->ep0state == EP0_IN_DATA_PHASE) { + ep0_out_start(core_if, pcd); + } + goto exit_xfercompl; + } else { + /* Prepare for more setup packets */ + DWC_DEBUGPL(DBG_ANY, + "EP0_IDLE SR=1 setup=0 new setup comes\n"); + doepint.d32 = DWC_READ_REG32(&core_if->dev_if-> + out_ep_regs[0]->doepint); + if(doepint.b.setup) + goto retry; + ep0_out_start(core_if, pcd); + } + } else { + dwc_otg_pcd_request_t *req; + diepint_data_t diepint0 = {.d32 = 0}; + doepint_data_t doepint_temp = {.d32 = 0}; + depctl_data_t diepctl0; + diepint0.d32 = DWC_READ_REG32(&core_if->dev_if-> + in_ep_regs[0]->diepint); + diepctl0.d32 = DWC_READ_REG32(&core_if->dev_if-> + in_ep_regs[0]->diepctl); + + if (pcd->ep0state == EP0_IN_DATA_PHASE + || pcd->ep0state == EP0_IN_STATUS_PHASE) { + if (diepint0.b.xfercompl) { + DWC_WRITE_REG32(&core_if->dev_if-> + in_ep_regs[0]->diepint, diepint0.d32); + } + if (diepctl0.b.epena) { + diepint_data_t diepint = {.d32 = 0}; + diepctl0.b.snak = 1; + DWC_WRITE_REG32(&core_if->dev_if-> + in_ep_regs[0]->diepctl, diepctl0.d32); + do { + dwc_udelay(10); + diepint.d32 = DWC_READ_REG32(&core_if->dev_if-> + in_ep_regs[0]->diepint); + } while (!diepint.b.inepnakeff); + diepint.b.inepnakeff = 1; + DWC_WRITE_REG32(&core_if->dev_if-> + in_ep_regs[0]->diepint, diepint.d32); + diepctl0.d32 = 0; + diepctl0.b.epdis = 1; + DWC_WRITE_REG32(&core_if->dev_if->in_ep_regs[0]->diepctl, + diepctl0.d32); + do { + dwc_udelay(10); + diepint.d32 = DWC_READ_REG32(&core_if->dev_if-> + in_ep_regs[0]->diepint); + } while (!diepint.b.epdisabled); + diepint.b.epdisabled = 1; + DWC_WRITE_REG32(&core_if->dev_if->in_ep_regs[0]->diepint, + diepint.d32); + } + } + doepint_temp.d32 = DWC_READ_REG32(&core_if->dev_if-> + out_ep_regs[ep->dwc_ep.num]->doepint); + if (doepint_temp.b.sr) { + CLEAR_OUT_EP_INTR(core_if, epnum, sr); + if (DWC_CIRCLEQ_EMPTY(&ep->queue)) { + DWC_DEBUGPL(DBG_PCDV, "Request queue empty!!\n"); + } else { + DWC_DEBUGPL(DBG_PCDV, "complete req!!\n"); + req = DWC_CIRCLEQ_FIRST(&ep->queue); + if (ep->dwc_ep.xfer_count != ep->dwc_ep.total_len && + pcd->ep0state == EP0_OUT_DATA_PHASE) { + /* Read arrived setup packet from req->buf */ + dwc_memcpy(&pcd->setup_pkt->req, + req->buf + ep->dwc_ep.xfer_count, 8); + } + req->actual = ep->dwc_ep.xfer_count; + dwc_otg_request_done(ep, req, -ECONNRESET); + ep->dwc_ep.start_xfer_buff = 0; + ep->dwc_ep.xfer_buff = 0; + ep->dwc_ep.xfer_len = 0; + } + pcd->ep0state = EP0_IDLE; + if (doepint.b.setup) { + DWC_DEBUGPL(DBG_PCDV, "EP0_IDLE SR=1 setup=1\n"); + /* Data stage started, clear setup */ + CLEAR_OUT_EP_INTR(core_if, epnum, setup); + doepint.b.setup = 0; + handle_ep0(pcd); + /* Prepare for setup packets if ep0in was enabled*/ + if (pcd->ep0state == EP0_IN_STATUS_PHASE) { + ep0_out_start(core_if, pcd); + } + goto exit_xfercompl; + } else { + /* Prepare for more setup packets */ + DWC_DEBUGPL(DBG_PCDV, + "EP0_IDLE SR=1 setup=0 new setup comes 2\n"); + ep0_out_start(core_if, pcd); + } + } + } + } + if (core_if->dma_enable == 0 || pcd->ep0state != EP0_IDLE) + handle_ep0(pcd); +exit_xfercompl: + DWC_DEBUGPL(DBG_PCDV, "DOEPINT=%x doepint=%x\n", + dwc_otg_read_dev_out_ep_intr(core_if, dwc_ep), doepint.d32); + } else { + if (core_if->dma_desc_enable == 0 + || pcd->ep0state != EP0_IDLE) + handle_ep0(pcd); + } +#ifdef DWC_EN_ISOC + } else if (dwc_ep->type == DWC_OTG_EP_TYPE_ISOC) { + if (doepint.b.pktdrpsts == 0) { + /* Clear the bit in DOEPINTn for this interrupt */ + CLEAR_OUT_EP_INTR(core_if, + epnum, + xfercompl); + complete_iso_ep(pcd, ep); + } else { + + doepint_data_t doepint = {.d32 = 0 }; + doepint.b.xfercompl = 1; + doepint.b.pktdrpsts = 1; + DWC_WRITE_REG32 + (&core_if->dev_if->out_ep_regs + [epnum]->doepint, + doepint.d32); + if (handle_iso_out_pkt_dropped + (core_if, dwc_ep)) { + complete_iso_ep(pcd, + ep); + } + } +#endif /* DWC_EN_ISOC */ +#ifdef DWC_UTE_PER_IO + } else if (dwc_ep->type == DWC_OTG_EP_TYPE_ISOC) { + CLEAR_OUT_EP_INTR(core_if, epnum, xfercompl); + if (!ep->stopped) + complete_xiso_ep(ep); +#endif /* DWC_UTE_PER_IO */ + } else { + /* Clear the bit in DOEPINTn for this interrupt */ + CLEAR_OUT_EP_INTR(core_if, epnum, + xfercompl); + + if (core_if->core_params->dev_out_nak) { + DWC_TIMER_CANCEL(pcd->core_if->ep_xfer_timer[epnum]); + pcd->core_if->ep_xfer_info[epnum].state = 0; +#ifdef DEBUG + print_memory_payload(pcd, dwc_ep); +#endif + } + complete_ep(ep); + } + + } + + /* Endpoint disable */ + if (doepint.b.epdisabled) { + + /* Clear the bit in DOEPINTn for this interrupt */ + CLEAR_OUT_EP_INTR(core_if, epnum, epdisabled); + if (core_if->core_params->dev_out_nak) { +#ifdef DEBUG + print_memory_payload(pcd, dwc_ep); +#endif + /* In case of timeout condition */ + if (core_if->ep_xfer_info[epnum].state == 2) { + dctl.d32 = DWC_READ_REG32(&core_if->dev_if-> + dev_global_regs->dctl); + dctl.b.cgoutnak = 1; + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->dctl, + dctl.d32); + /* Unmask goutnakeff interrupt which was masked + * during handle nak out interrupt */ + gintmsk.b.goutnakeff = 1; + DWC_MODIFY_REG32(&core_if->core_global_regs->gintmsk, + 0, gintmsk.d32); + + complete_ep(ep); + } + } + if (ep->dwc_ep.type == DWC_OTG_EP_TYPE_ISOC) + { + dctl_data_t dctl; + gintmsk_data_t intr_mask = {.d32 = 0}; + dwc_otg_pcd_request_t *req = 0; + + dctl.d32 = DWC_READ_REG32(&core_if->dev_if-> + dev_global_regs->dctl); + dctl.b.cgoutnak = 1; + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->dctl, + dctl.d32); + + intr_mask.d32 = 0; + intr_mask.b.incomplisoout = 1; + + /* Get any pending requests */ + if (!DWC_CIRCLEQ_EMPTY(&ep->queue)) { + req = DWC_CIRCLEQ_FIRST(&ep->queue); + if (!req) { + DWC_PRINTF("complete_ep 0x%p, req = NULL!\n", ep); + } else { + dwc_otg_request_done(ep, req, 0); + start_next_request(ep); + } + } else { + DWC_PRINTF("complete_ep 0x%p, ep->queue empty!\n", ep); + } + } + } + /* AHB Error */ + if (doepint.b.ahberr) { + DWC_ERROR("EP%d OUT AHB Error\n", epnum); + DWC_ERROR("EP%d DEPDMA=0x%08x \n", + epnum, core_if->dev_if->out_ep_regs[epnum]->doepdma); + CLEAR_OUT_EP_INTR(core_if, epnum, ahberr); + } + /* Setup Phase Done (contorl EPs) */ + if (doepint.b.setup) { +#ifdef DEBUG_EP0 + DWC_DEBUGPL(DBG_PCD, "EP%d SETUP Done\n", epnum); +#endif + CLEAR_OUT_EP_INTR(core_if, epnum, setup); + + handle_ep0(pcd); + } + + /** OUT EP BNA Intr */ + if (doepint.b.bna) { + CLEAR_OUT_EP_INTR(core_if, epnum, bna); + if (core_if->dma_desc_enable) { +#ifdef DWC_EN_ISOC + if (dwc_ep->type == + DWC_OTG_EP_TYPE_ISOC) { + /* + * This checking is performed to prevent first "false" BNA + * handling occuring right after reconnect + */ + if (dwc_ep->next_frame != + 0xffffffff) + dwc_otg_pcd_handle_iso_bna(ep); + } else +#endif /* DWC_EN_ISOC */ + { + dwc_otg_pcd_handle_noniso_bna(ep); + } + } + } + /* Babble Interrupt */ + if (doepint.b.babble) { + DWC_DEBUGPL(DBG_ANY, "EP%d OUT Babble\n", + epnum); + handle_out_ep_babble_intr(pcd, epnum); + + CLEAR_OUT_EP_INTR(core_if, epnum, babble); + } + if (doepint.b.outtknepdis) { + DWC_DEBUGPL(DBG_ANY, "EP%d OUT Token received when EP is \ + disabled\n",epnum); + if (ep->dwc_ep.type == DWC_OTG_EP_TYPE_ISOC) { + doepmsk_data_t doepmsk = {.d32 = 0}; + ep->dwc_ep.frame_num = core_if->frame_num; + if (ep->dwc_ep.bInterval > 1) { + depctl_data_t depctl; + depctl.d32 = DWC_READ_REG32(&core_if->dev_if-> + out_ep_regs[epnum]->doepctl); + if (ep->dwc_ep.frame_num & 0x1) { + depctl.b.setd1pid = 1; + depctl.b.setd0pid = 0; + } else { + depctl.b.setd0pid = 1; + depctl.b.setd1pid = 0; + } + DWC_WRITE_REG32(&core_if->dev_if-> + out_ep_regs[epnum]->doepctl, depctl.d32); + } + start_next_request(ep); + doepmsk.b.outtknepdis = 1; + DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->doepmsk, + doepmsk.d32, 0); + } + CLEAR_OUT_EP_INTR(core_if, epnum, outtknepdis); + } + + /* NAK Interrutp */ + if (doepint.b.nak) { + DWC_DEBUGPL(DBG_ANY, "EP%d OUT NAK\n", epnum); + handle_out_ep_nak_intr(pcd, epnum); + + CLEAR_OUT_EP_INTR(core_if, epnum, nak); + } + /* NYET Interrutp */ + if (doepint.b.nyet) { + DWC_DEBUGPL(DBG_ANY, "EP%d OUT NYET\n", epnum); + handle_out_ep_nyet_intr(pcd, epnum); + + CLEAR_OUT_EP_INTR(core_if, epnum, nyet); + } + } + + epnum++; + ep_intr >>= 1; + } + + return 1; + +#undef CLEAR_OUT_EP_INTR +} +static int drop_transfer(uint32_t trgt_fr, uint32_t curr_fr, uint8_t frm_overrun) +{ + int retval = 0; + if(!frm_overrun && curr_fr >= trgt_fr) + retval = 1; + else if (frm_overrun + && (curr_fr >= trgt_fr && ((curr_fr - trgt_fr) < 0x3FFF / 2))) + retval = 1; + return retval; +} +/** + * Incomplete ISO IN Transfer Interrupt. + * This interrupt indicates one of the following conditions occurred + * while transmitting an ISOC transaction. + * - Corrupted IN Token for ISOC EP. + * - Packet not complete in FIFO. + * The follow actions will be taken: + * -# Determine the EP + * -# Set incomplete flag in dwc_ep structure + * -# Disable EP; when "Endpoint Disabled" interrupt is received + * Flush FIFO + */ +static int32_t dwc_otg_pcd_handle_incomplete_isoc_in_intr(dwc_otg_pcd_t * pcd) +{ + gintsts_data_t gintsts; + +#ifdef DWC_EN_ISOC + dwc_otg_dev_if_t *dev_if; + deptsiz_data_t deptsiz = {.d32 = 0 }; + depctl_data_t depctl = {.d32 = 0 }; + dsts_data_t dsts = {.d32 = 0 }; + dwc_ep_t *dwc_ep; + int i; + + dev_if = GET_CORE_IF(pcd)->dev_if; + + for (i = 1; i <= dev_if->num_in_eps; ++i) { + dwc_ep = &pcd->in_ep[i].dwc_ep; + if (dwc_ep->active && dwc_ep->type == DWC_OTG_EP_TYPE_ISOC) { + deptsiz.d32 = + DWC_READ_REG32(&dev_if->in_ep_regs[i]->dieptsiz); + depctl.d32 = + DWC_READ_REG32(&dev_if->in_ep_regs[i]->diepctl); + + if (depctl.b.epdis && deptsiz.d32) { + set_current_pkt_info(GET_CORE_IF(pcd), dwc_ep); + if (dwc_ep->cur_pkt >= dwc_ep->pkt_cnt) { + dwc_ep->cur_pkt = 0; + dwc_ep->proc_buf_num = + (dwc_ep->proc_buf_num ^ 1) & 0x1; + + if (dwc_ep->proc_buf_num) { + dwc_ep->cur_pkt_addr = + dwc_ep->xfer_buff1; + dwc_ep->cur_pkt_dma_addr = + dwc_ep->dma_addr1; + } else { + dwc_ep->cur_pkt_addr = + dwc_ep->xfer_buff0; + dwc_ep->cur_pkt_dma_addr = + dwc_ep->dma_addr0; + } + + } + + dsts.d32 = + DWC_READ_REG32(&GET_CORE_IF(pcd)->dev_if-> + dev_global_regs->dsts); + dwc_ep->next_frame = dsts.b.soffn; + + dwc_otg_iso_ep_start_frm_transfer(GET_CORE_IF + (pcd), + dwc_ep); + } + } + } + +#else + depctl_data_t depctl = {.d32 = 0 }; + dwc_ep_t *dwc_ep; + dwc_otg_dev_if_t *dev_if; + int i; + dev_if = GET_CORE_IF(pcd)->dev_if; + + DWC_DEBUGPL(DBG_PCD,"Incomplete ISO IN \n"); + + for (i = 1; i <= dev_if->num_in_eps; ++i) { + dwc_ep = &pcd->in_ep[i-1].dwc_ep; + depctl.d32 = + DWC_READ_REG32(&dev_if->in_ep_regs[i]->diepctl); + if (depctl.b.epena && dwc_ep->type == DWC_OTG_EP_TYPE_ISOC) { + if (drop_transfer(dwc_ep->frame_num, GET_CORE_IF(pcd)->frame_num, + dwc_ep->frm_overrun)) + { + depctl.d32 = + DWC_READ_REG32(&dev_if->in_ep_regs[i]->diepctl); + depctl.b.snak = 1; + depctl.b.epdis = 1; + DWC_MODIFY_REG32(&dev_if->in_ep_regs[i]->diepctl, depctl.d32, depctl.d32); + } + } + } + + /*intr_mask.b.incomplisoin = 1; + DWC_MODIFY_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintmsk, + intr_mask.d32, 0); */ +#endif //DWC_EN_ISOC + + /* Clear interrupt */ + gintsts.d32 = 0; + gintsts.b.incomplisoin = 1; + DWC_WRITE_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintsts, + gintsts.d32); + + return 1; +} + +/** + * Incomplete ISO OUT Transfer Interrupt. + * + * This interrupt indicates that the core has dropped an ISO OUT + * packet. The following conditions can be the cause: + * - FIFO Full, the entire packet would not fit in the FIFO. + * - CRC Error + * - Corrupted Token + * The follow actions will be taken: + * -# Determine the EP + * -# Set incomplete flag in dwc_ep structure + * -# Read any data from the FIFO + * -# Disable EP. When "Endpoint Disabled" interrupt is received + * re-enable EP. + */ +static int32_t dwc_otg_pcd_handle_incomplete_isoc_out_intr(dwc_otg_pcd_t * pcd) +{ + + gintsts_data_t gintsts; + +#ifdef DWC_EN_ISOC + dwc_otg_dev_if_t *dev_if; + deptsiz_data_t deptsiz = {.d32 = 0 }; + depctl_data_t depctl = {.d32 = 0 }; + dsts_data_t dsts = {.d32 = 0 }; + dwc_ep_t *dwc_ep; + int i; + + dev_if = GET_CORE_IF(pcd)->dev_if; + + for (i = 1; i <= dev_if->num_out_eps; ++i) { + dwc_ep = &pcd->in_ep[i].dwc_ep; + if (pcd->out_ep[i].dwc_ep.active && + pcd->out_ep[i].dwc_ep.type == DWC_OTG_EP_TYPE_ISOC) { + deptsiz.d32 = + DWC_READ_REG32(&dev_if->out_ep_regs[i]->doeptsiz); + depctl.d32 = + DWC_READ_REG32(&dev_if->out_ep_regs[i]->doepctl); + + if (depctl.b.epdis && deptsiz.d32) { + set_current_pkt_info(GET_CORE_IF(pcd), + &pcd->out_ep[i].dwc_ep); + if (dwc_ep->cur_pkt >= dwc_ep->pkt_cnt) { + dwc_ep->cur_pkt = 0; + dwc_ep->proc_buf_num = + (dwc_ep->proc_buf_num ^ 1) & 0x1; + + if (dwc_ep->proc_buf_num) { + dwc_ep->cur_pkt_addr = + dwc_ep->xfer_buff1; + dwc_ep->cur_pkt_dma_addr = + dwc_ep->dma_addr1; + } else { + dwc_ep->cur_pkt_addr = + dwc_ep->xfer_buff0; + dwc_ep->cur_pkt_dma_addr = + dwc_ep->dma_addr0; + } + + } + + dsts.d32 = + DWC_READ_REG32(&GET_CORE_IF(pcd)->dev_if-> + dev_global_regs->dsts); + dwc_ep->next_frame = dsts.b.soffn; + + dwc_otg_iso_ep_start_frm_transfer(GET_CORE_IF + (pcd), + dwc_ep); + } + } + } +#else + /** @todo implement ISR */ + gintmsk_data_t intr_mask = {.d32 = 0 }; + dwc_otg_core_if_t *core_if; + deptsiz_data_t deptsiz = {.d32 = 0 }; + depctl_data_t depctl = {.d32 = 0 }; + dctl_data_t dctl = {.d32 = 0 }; + dwc_ep_t *dwc_ep = NULL; + int i; + core_if = GET_CORE_IF(pcd); + + for (i = 0; i < core_if->dev_if->num_out_eps; ++i) { + dwc_ep = &pcd->out_ep[i].dwc_ep; + depctl.d32 = + DWC_READ_REG32(&core_if->dev_if->out_ep_regs[dwc_ep->num]->doepctl); + if (depctl.b.epena && depctl.b.dpid == (core_if->frame_num & 0x1)) { + core_if->dev_if->isoc_ep = dwc_ep; + deptsiz.d32 = + DWC_READ_REG32(&core_if->dev_if->out_ep_regs[dwc_ep->num]->doeptsiz); + break; + } + } + dctl.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dctl); + gintsts.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintsts); + intr_mask.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintmsk); + + if (!intr_mask.b.goutnakeff) { + /* Unmask it */ + intr_mask.b.goutnakeff = 1; + DWC_WRITE_REG32(&core_if->core_global_regs->gintmsk, intr_mask.d32); + } + if (!gintsts.b.goutnakeff) { + dctl.b.sgoutnak = 1; + } + DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->dctl, dctl.d32); + + depctl.d32 = DWC_READ_REG32(&core_if->dev_if->out_ep_regs[dwc_ep->num]->doepctl); + if (depctl.b.epena) { + depctl.b.epdis = 1; + depctl.b.snak = 1; + } + DWC_WRITE_REG32(&core_if->dev_if->out_ep_regs[dwc_ep->num]->doepctl, depctl.d32); + + intr_mask.d32 = 0; + intr_mask.b.incomplisoout = 1; + +#endif /* DWC_EN_ISOC */ + + /* Clear interrupt */ + gintsts.d32 = 0; + gintsts.b.incomplisoout = 1; + DWC_WRITE_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintsts, + gintsts.d32); + + return 1; +} + +/** + * This function handles the Global IN NAK Effective interrupt. + * + */ +static int32_t dwc_otg_pcd_handle_in_nak_effective(dwc_otg_pcd_t * pcd) +{ + dwc_otg_dev_if_t *dev_if = GET_CORE_IF(pcd)->dev_if; + depctl_data_t diepctl = {.d32 = 0 }; + gintmsk_data_t intr_mask = {.d32 = 0 }; + gintsts_data_t gintsts; + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); + int i; + + DWC_DEBUGPL(DBG_PCD, "Global IN NAK Effective\n"); + + /* Disable all active IN EPs */ + for (i = 0; i <= dev_if->num_in_eps; i++) { + diepctl.d32 = DWC_READ_REG32(&dev_if->in_ep_regs[i]->diepctl); + if (!(diepctl.b.eptype & 1) && diepctl.b.epena) { + if (core_if->start_predict > 0) + core_if->start_predict++; + diepctl.b.epdis = 1; + diepctl.b.snak = 1; + DWC_WRITE_REG32(&dev_if->in_ep_regs[i]->diepctl, diepctl.d32); + } + } + + + /* Disable the Global IN NAK Effective Interrupt */ + intr_mask.b.ginnakeff = 1; + DWC_MODIFY_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintmsk, + intr_mask.d32, 0); + + /* Clear interrupt */ + gintsts.d32 = 0; + gintsts.b.ginnakeff = 1; + DWC_WRITE_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintsts, + gintsts.d32); + + return 1; +} + +/** + * OUT NAK Effective. + * + */ +static int32_t dwc_otg_pcd_handle_out_nak_effective(dwc_otg_pcd_t * pcd) +{ + dwc_otg_dev_if_t *dev_if = GET_CORE_IF(pcd)->dev_if; + gintmsk_data_t intr_mask = {.d32 = 0 }; + gintsts_data_t gintsts; + depctl_data_t doepctl; + int i; + + /* Disable the Global OUT NAK Effective Interrupt */ + intr_mask.b.goutnakeff = 1; + DWC_MODIFY_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintmsk, + intr_mask.d32, 0); + + /* If DEV OUT NAK enabled*/ + if (pcd->core_if->core_params->dev_out_nak) { + /* Run over all out endpoints to determine the ep number on + * which the timeout has happened + */ + for (i = 0; i <= dev_if->num_out_eps; i++) { + if ( pcd->core_if->ep_xfer_info[i].state == 2 ) + break; + } + if (i > dev_if->num_out_eps) { + dctl_data_t dctl; + dctl.d32 = + DWC_READ_REG32(&dev_if->dev_global_regs->dctl); + dctl.b.cgoutnak = 1; + DWC_WRITE_REG32(&dev_if->dev_global_regs->dctl, + dctl.d32); + goto out; + } + + /* Disable the endpoint */ + doepctl.d32 = DWC_READ_REG32(&dev_if->out_ep_regs[i]->doepctl); + if (doepctl.b.epena) { + doepctl.b.epdis = 1; + doepctl.b.snak = 1; + } + DWC_WRITE_REG32(&dev_if->out_ep_regs[i]->doepctl, doepctl.d32); + return 1; + } + /* We come here from Incomplete ISO OUT handler */ + if (dev_if->isoc_ep) { + dwc_ep_t *dwc_ep = (dwc_ep_t *)dev_if->isoc_ep; + uint32_t epnum = dwc_ep->num; + doepint_data_t doepint; + doepint.d32 = + DWC_READ_REG32(&dev_if->out_ep_regs[dwc_ep->num]->doepint); + dev_if->isoc_ep = NULL; + doepctl.d32 = + DWC_READ_REG32(&dev_if->out_ep_regs[epnum]->doepctl); + DWC_PRINTF("Before disable DOEPCTL = %08x\n", doepctl.d32); + if (doepctl.b.epena) { + doepctl.b.epdis = 1; + doepctl.b.snak = 1; + } + DWC_WRITE_REG32(&dev_if->out_ep_regs[epnum]->doepctl, + doepctl.d32); + return 1; + } else + DWC_PRINTF("INTERRUPT Handler not implemented for %s\n", + "Global OUT NAK Effective\n"); + +out: + /* Clear interrupt */ + gintsts.d32 = 0; + gintsts.b.goutnakeff = 1; + DWC_WRITE_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintsts, + gintsts.d32); + + return 1; +} + +/** + * PCD interrupt handler. + * + * The PCD handles the device interrupts. Many conditions can cause a + * device interrupt. When an interrupt occurs, the device interrupt + * service routine determines the cause of the interrupt and + * dispatches handling to the appropriate function. These interrupt + * handling functions are described below. + * + * All interrupt registers are processed from LSB to MSB. + * + */ +int32_t dwc_otg_pcd_handle_intr(dwc_otg_pcd_t * pcd) +{ + dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); +#ifdef VERBOSE + dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs; +#endif + gintsts_data_t gintr_status; + int32_t retval = 0; + + /* Exit from ISR if core is hibernated */ + if (core_if->hibernation_suspend == 1) { + return retval; + } +#ifdef VERBOSE + DWC_DEBUGPL(DBG_ANY, "%s() gintsts=%08x gintmsk=%08x\n", + __func__, + DWC_READ_REG32(&global_regs->gintsts), + DWC_READ_REG32(&global_regs->gintmsk)); +#endif + + if (dwc_otg_is_device_mode(core_if)) { + DWC_SPINLOCK(pcd->lock); +#ifdef VERBOSE + DWC_DEBUGPL(DBG_PCDV, "%s() gintsts=%08x gintmsk=%08x\n", + __func__, + DWC_READ_REG32(&global_regs->gintsts), + DWC_READ_REG32(&global_regs->gintmsk)); +#endif + + gintr_status.d32 = dwc_otg_read_core_intr(core_if); + + DWC_DEBUGPL(DBG_PCDV, "%s: gintsts&gintmsk=%08x\n", + __func__, gintr_status.d32); + + if (gintr_status.b.sofintr) { + retval |= dwc_otg_pcd_handle_sof_intr(pcd); + } + if (gintr_status.b.rxstsqlvl) { + retval |= + dwc_otg_pcd_handle_rx_status_q_level_intr(pcd); + } + if (gintr_status.b.nptxfempty) { + retval |= dwc_otg_pcd_handle_np_tx_fifo_empty_intr(pcd); + } + if (gintr_status.b.goutnakeff) { + retval |= dwc_otg_pcd_handle_out_nak_effective(pcd); + } + if (gintr_status.b.i2cintr) { + retval |= dwc_otg_pcd_handle_i2c_intr(pcd); + } + if (gintr_status.b.erlysuspend) { + retval |= dwc_otg_pcd_handle_early_suspend_intr(pcd); + } + if (gintr_status.b.usbreset) { + retval |= dwc_otg_pcd_handle_usb_reset_intr(pcd); + } + if (gintr_status.b.enumdone) { + retval |= dwc_otg_pcd_handle_enum_done_intr(pcd); + } + if (gintr_status.b.isooutdrop) { + retval |= + dwc_otg_pcd_handle_isoc_out_packet_dropped_intr + (pcd); + } + if (gintr_status.b.eopframe) { + retval |= + dwc_otg_pcd_handle_end_periodic_frame_intr(pcd); + } + if (gintr_status.b.inepint) { + if (!core_if->multiproc_int_enable) { + retval |= dwc_otg_pcd_handle_in_ep_intr(pcd); + } + } + if (gintr_status.b.outepintr) { + if (!core_if->multiproc_int_enable) { + retval |= dwc_otg_pcd_handle_out_ep_intr(pcd); + } + } + if (gintr_status.b.epmismatch) { + retval |= dwc_otg_pcd_handle_ep_mismatch_intr(pcd); + } + if (gintr_status.b.fetsusp) { + retval |= dwc_otg_pcd_handle_ep_fetsusp_intr(pcd); + } + if (gintr_status.b.ginnakeff) { + retval |= dwc_otg_pcd_handle_in_nak_effective(pcd); + } + if (gintr_status.b.incomplisoin) { + retval |= + dwc_otg_pcd_handle_incomplete_isoc_in_intr(pcd); + } + if (gintr_status.b.incomplisoout) { + retval |= + dwc_otg_pcd_handle_incomplete_isoc_out_intr(pcd); + } + + /* In MPI mode Device Endpoints interrupts are asserted + * without setting outepintr and inepint bits set, so these + * Interrupt handlers are called without checking these bit-fields + */ + if (core_if->multiproc_int_enable) { + retval |= dwc_otg_pcd_handle_in_ep_intr(pcd); + retval |= dwc_otg_pcd_handle_out_ep_intr(pcd); + } +#ifdef VERBOSE + DWC_DEBUGPL(DBG_PCDV, "%s() gintsts=%0x\n", __func__, + DWC_READ_REG32(&global_regs->gintsts)); +#endif + DWC_SPINUNLOCK(pcd->lock); + } + return retval; +} + +#endif /* DWC_HOST_ONLY */ diff --git a/drivers/usb/host/dwc_otg/dwc_otg_pcd_linux.c b/drivers/usb/host/dwc_otg/dwc_otg_pcd_linux.c new file mode 100644 index 00000000000000..e214955d6914c0 --- /dev/null +++ b/drivers/usb/host/dwc_otg/dwc_otg_pcd_linux.c @@ -0,0 +1,1262 @@ + /* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_pcd_linux.c $ + * $Revision: #21 $ + * $Date: 2012/08/10 $ + * $Change: 2047372 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ +#ifndef DWC_HOST_ONLY + +/** @file + * This file implements the Peripheral Controller Driver. + * + * The Peripheral Controller Driver (PCD) is responsible for + * translating requests from the Function Driver into the appropriate + * actions on the DWC_otg controller. It isolates the Function Driver + * from the specifics of the controller by providing an API to the + * Function Driver. + * + * The Peripheral Controller Driver for Linux will implement the + * Gadget API, so that the existing Gadget drivers can be used. + * (Gadget Driver is the Linux terminology for a Function Driver.) + * + * The Linux Gadget API is defined in the header file + * <code><linux/usb_gadget.h></code>. The USB EP operations API is + * defined in the structure <code>usb_ep_ops</code> and the USB + * Controller API is defined in the structure + * <code>usb_gadget_ops</code>. + * + */ + +#include "dwc_otg_os_dep.h" +#include "dwc_otg_pcd_if.h" +#include "dwc_otg_pcd.h" +#include "dwc_otg_driver.h" +#include "dwc_otg_dbg.h" + +extern bool fiq_enable; + +static struct gadget_wrapper { + dwc_otg_pcd_t *pcd; + + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + + struct usb_ep ep0; + struct usb_ep in_ep[16]; + struct usb_ep out_ep[16]; + +} *gadget_wrapper; + +/* Display the contents of the buffer */ +extern void dump_msg(const u8 * buf, unsigned int length); +/** + * Get the dwc_otg_pcd_ep_t* from usb_ep* pointer - NULL in case + * if the endpoint is not found + */ +static struct dwc_otg_pcd_ep *ep_from_handle(dwc_otg_pcd_t * pcd, void *handle) +{ + int i; + if (pcd->ep0.priv == handle) { + return &pcd->ep0; + } + + for (i = 0; i < MAX_EPS_CHANNELS - 1; i++) { + if (pcd->in_ep[i].priv == handle) + return &pcd->in_ep[i]; + if (pcd->out_ep[i].priv == handle) + return &pcd->out_ep[i]; + } + + return NULL; +} + +/* USB Endpoint Operations */ +/* + * The following sections briefly describe the behavior of the Gadget + * API endpoint operations implemented in the DWC_otg driver + * software. Detailed descriptions of the generic behavior of each of + * these functions can be found in the Linux header file + * include/linux/usb_gadget.h. + * + * The Gadget API provides wrapper functions for each of the function + * pointers defined in usb_ep_ops. The Gadget Driver calls the wrapper + * function, which then calls the underlying PCD function. The + * following sections are named according to the wrapper + * functions. Within each section, the corresponding DWC_otg PCD + * function name is specified. + * + */ + +/** + * This function is called by the Gadget Driver for each EP to be + * configured for the current configuration (SET_CONFIGURATION). + * + * This function initializes the dwc_otg_ep_t data structure, and then + * calls dwc_otg_ep_activate. + */ +static int ep_enable(struct usb_ep *usb_ep, + const struct usb_endpoint_descriptor *ep_desc) +{ + int retval; + + DWC_DEBUGPL(DBG_PCDV, "%s(%p,%p)\n", __func__, usb_ep, ep_desc); + + if (!usb_ep || !ep_desc || ep_desc->bDescriptorType != USB_DT_ENDPOINT) { + DWC_WARN("%s, bad ep or descriptor\n", __func__); + return -EINVAL; + } + if (usb_ep == &gadget_wrapper->ep0) { + DWC_WARN("%s, bad ep(0)\n", __func__); + return -EINVAL; + } + + /* Check FIFO size? */ + if (!ep_desc->wMaxPacketSize) { + DWC_WARN("%s, bad %s maxpacket\n", __func__, usb_ep->name); + return -ERANGE; + } + + if (!gadget_wrapper->driver || + gadget_wrapper->gadget.speed == USB_SPEED_UNKNOWN) { + DWC_WARN("%s, bogus device state\n", __func__); + return -ESHUTDOWN; + } + + /* Delete after check - MAS */ +#if 0 + nat = (uint32_t) ep_desc->wMaxPacketSize; + printk(KERN_ALERT "%s: nat (before) =%d\n", __func__, nat); + nat = (nat >> 11) & 0x03; + printk(KERN_ALERT "%s: nat (after) =%d\n", __func__, nat); +#endif + retval = dwc_otg_pcd_ep_enable(gadget_wrapper->pcd, + (const uint8_t *)ep_desc, + (void *)usb_ep); + if (retval) { + DWC_WARN("dwc_otg_pcd_ep_enable failed\n"); + return -EINVAL; + } + + usb_ep->maxpacket = le16_to_cpu(ep_desc->wMaxPacketSize); + + return 0; +} + +/** + * This function is called when an EP is disabled due to disconnect or + * change in configuration. Any pending requests will terminate with a + * status of -ESHUTDOWN. + * + * This function modifies the dwc_otg_ep_t data structure for this EP, + * and then calls dwc_otg_ep_deactivate. + */ +static int ep_disable(struct usb_ep *usb_ep) +{ + int retval; + + DWC_DEBUGPL(DBG_PCDV, "%s(%p)\n", __func__, usb_ep); + if (!usb_ep) { + DWC_DEBUGPL(DBG_PCD, "%s, %s not enabled\n", __func__, + usb_ep ? usb_ep->name : NULL); + return -EINVAL; + } + + retval = dwc_otg_pcd_ep_disable(gadget_wrapper->pcd, usb_ep); + if (retval) { + retval = -EINVAL; + } + + return retval; +} + +/** + * This function allocates a request object to use with the specified + * endpoint. + * + * @param ep The endpoint to be used with with the request + * @param gfp_flags the GFP_* flags to use. + */ +static struct usb_request *dwc_otg_pcd_alloc_request(struct usb_ep *ep, + gfp_t gfp_flags) +{ + struct usb_request *usb_req; + + DWC_DEBUGPL(DBG_PCDV, "%s(%p,%d)\n", __func__, ep, gfp_flags); + if (0 == ep) { + DWC_WARN("%s() %s\n", __func__, "Invalid EP!\n"); + return 0; + } + usb_req = kzalloc(sizeof(*usb_req), gfp_flags); + if (0 == usb_req) { + DWC_WARN("%s() %s\n", __func__, "request allocation failed!\n"); + return 0; + } + usb_req->dma = DWC_DMA_ADDR_INVALID; + + return usb_req; +} + +/** + * This function frees a request object. + * + * @param ep The endpoint associated with the request + * @param req The request being freed + */ +static void dwc_otg_pcd_free_request(struct usb_ep *ep, struct usb_request *req) +{ + DWC_DEBUGPL(DBG_PCDV, "%s(%p,%p)\n", __func__, ep, req); + + if (0 == ep || 0 == req) { + DWC_WARN("%s() %s\n", __func__, + "Invalid ep or req argument!\n"); + return; + } + + kfree(req); +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28) +/** + * This function allocates an I/O buffer to be used for a transfer + * to/from the specified endpoint. + * + * @param usb_ep The endpoint to be used with with the request + * @param bytes The desired number of bytes for the buffer + * @param dma Pointer to the buffer's DMA address; must be valid + * @param gfp_flags the GFP_* flags to use. + * @return address of a new buffer or null is buffer could not be allocated. + */ +static void *dwc_otg_pcd_alloc_buffer(struct usb_ep *usb_ep, unsigned bytes, + dma_addr_t * dma, gfp_t gfp_flags) +{ + void *buf; + dwc_otg_pcd_t *pcd = 0; + + pcd = gadget_wrapper->pcd; + + DWC_DEBUGPL(DBG_PCDV, "%s(%p,%d,%p,%0x)\n", __func__, usb_ep, bytes, + dma, gfp_flags); + + /* Check dword alignment */ + if ((bytes & 0x3UL) != 0) { + DWC_WARN("%s() Buffer size is not a multiple of" + "DWORD size (%d)", __func__, bytes); + } + + buf = dma_alloc_coherent(NULL, bytes, dma, gfp_flags); + WARN_ON(!buf); + + /* Check dword alignment */ + if (((int)buf & 0x3UL) != 0) { + DWC_WARN("%s() Buffer is not DWORD aligned (%p)", + __func__, buf); + } + + return buf; +} + +/** + * This function frees an I/O buffer that was allocated by alloc_buffer. + * + * @param usb_ep the endpoint associated with the buffer + * @param buf address of the buffer + * @param dma The buffer's DMA address + * @param bytes The number of bytes of the buffer + */ +static void dwc_otg_pcd_free_buffer(struct usb_ep *usb_ep, void *buf, + dma_addr_t dma, unsigned bytes) +{ + dwc_otg_pcd_t *pcd = 0; + + pcd = gadget_wrapper->pcd; + + DWC_DEBUGPL(DBG_PCDV, "%s(%p,%0x,%d)\n", __func__, buf, dma, bytes); + + dma_free_coherent(NULL, bytes, buf, dma); +} +#endif + +/** + * This function is used to submit an I/O Request to an EP. + * + * - When the request completes the request's completion callback + * is called to return the request to the driver. + * - An EP, except control EPs, may have multiple requests + * pending. + * - Once submitted the request cannot be examined or modified. + * - Each request is turned into one or more packets. + * - A BULK EP can queue any amount of data; the transfer is + * packetized. + * - Zero length Packets are specified with the request 'zero' + * flag. + */ +static int ep_queue(struct usb_ep *usb_ep, struct usb_request *usb_req, + gfp_t gfp_flags) +{ + dwc_otg_pcd_t *pcd; + struct dwc_otg_pcd_ep *ep = NULL; + int retval = 0, is_isoc_ep = 0; + dma_addr_t dma_addr = DWC_DMA_ADDR_INVALID; + + DWC_DEBUGPL(DBG_PCDV, "%s(%p,%p,%d)\n", + __func__, usb_ep, usb_req, gfp_flags); + + if (!usb_req || !usb_req->complete || !usb_req->buf) { + DWC_WARN("bad params\n"); + return -EINVAL; + } + + if (!usb_ep) { + DWC_WARN("bad ep\n"); + return -EINVAL; + } + + pcd = gadget_wrapper->pcd; + if (!gadget_wrapper->driver || + gadget_wrapper->gadget.speed == USB_SPEED_UNKNOWN) { + DWC_DEBUGPL(DBG_PCDV, "gadget.speed=%d\n", + gadget_wrapper->gadget.speed); + DWC_WARN("bogus device state\n"); + return -ESHUTDOWN; + } + + DWC_DEBUGPL(DBG_PCD, "%s queue req %p, len %d buf %p\n", + usb_ep->name, usb_req, usb_req->length, usb_req->buf); + + usb_req->status = -EINPROGRESS; + usb_req->actual = 0; + + ep = ep_from_handle(pcd, usb_ep); + if (ep == NULL) + is_isoc_ep = 0; + else + is_isoc_ep = (ep->dwc_ep.type == DWC_OTG_EP_TYPE_ISOC) ? 1 : 0; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28) + dma_addr = usb_req->dma; +#else + if (GET_CORE_IF(pcd)->dma_enable) { + dwc_otg_device_t *otg_dev = gadget_wrapper->pcd->otg_dev; + struct device *dev = NULL; + + if (otg_dev != NULL) + dev = DWC_OTG_OS_GETDEV(otg_dev->os_dep); + + if (usb_req->length != 0 && + usb_req->dma == DWC_DMA_ADDR_INVALID) { + dma_addr = dma_map_single(dev, usb_req->buf, + usb_req->length, + ep->dwc_ep.is_in ? + DMA_TO_DEVICE: + DMA_FROM_DEVICE); + } + } +#endif + +#ifdef DWC_UTE_PER_IO + if (is_isoc_ep == 1) { + retval = dwc_otg_pcd_xiso_ep_queue(pcd, usb_ep, usb_req->buf, dma_addr, + usb_req->length, usb_req->zero, usb_req, + gfp_flags == GFP_ATOMIC ? 1 : 0, &usb_req->ext_req); + if (retval) + return -EINVAL; + + return 0; + } +#endif + retval = dwc_otg_pcd_ep_queue(pcd, usb_ep, usb_req->buf, dma_addr, + usb_req->length, usb_req->zero, usb_req, + gfp_flags == GFP_ATOMIC ? 1 : 0); + if (retval) { + return -EINVAL; + } + + return 0; +} + +/** + * This function cancels an I/O request from an EP. + */ +static int ep_dequeue(struct usb_ep *usb_ep, struct usb_request *usb_req) +{ + DWC_DEBUGPL(DBG_PCDV, "%s(%p,%p)\n", __func__, usb_ep, usb_req); + + if (!usb_ep || !usb_req) { + DWC_WARN("bad argument\n"); + return -EINVAL; + } + if (!gadget_wrapper->driver || + gadget_wrapper->gadget.speed == USB_SPEED_UNKNOWN) { + DWC_WARN("bogus device state\n"); + return -ESHUTDOWN; + } + if (dwc_otg_pcd_ep_dequeue(gadget_wrapper->pcd, usb_ep, usb_req)) { + return -EINVAL; + } + + return 0; +} + +/** + * usb_ep_set_halt stalls an endpoint. + * + * usb_ep_clear_halt clears an endpoint halt and resets its data + * toggle. + * + * Both of these functions are implemented with the same underlying + * function. The behavior depends on the value argument. + * + * @param[in] usb_ep the Endpoint to halt or clear halt. + * @param[in] value + * - 0 means clear_halt. + * - 1 means set_halt, + * - 2 means clear stall lock flag. + * - 3 means set stall lock flag. + */ +static int ep_halt(struct usb_ep *usb_ep, int value) +{ + int retval = 0; + + DWC_DEBUGPL(DBG_PCD, "HALT %s %d\n", usb_ep->name, value); + + if (!usb_ep) { + DWC_WARN("bad ep\n"); + return -EINVAL; + } + + retval = dwc_otg_pcd_ep_halt(gadget_wrapper->pcd, usb_ep, value); + if (retval == -DWC_E_AGAIN) { + return -EAGAIN; + } else if (retval) { + retval = -EINVAL; + } + + return retval; +} + +//#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30)) +#if 0 +/** + * ep_wedge: sets the halt feature and ignores clear requests + * + * @usb_ep: the endpoint being wedged + * + * Use this to stall an endpoint and ignore CLEAR_FEATURE(HALT_ENDPOINT) + * requests. If the gadget driver clears the halt status, it will + * automatically unwedge the endpoint. + * + * Returns zero on success, else negative errno. * + * Check usb_ep_set_wedge() at "usb_gadget.h" for details + */ +static int ep_wedge(struct usb_ep *usb_ep) +{ + int retval = 0; + + DWC_DEBUGPL(DBG_PCD, "WEDGE %s\n", usb_ep->name); + + if (!usb_ep) { + DWC_WARN("bad ep\n"); + return -EINVAL; + } + + retval = dwc_otg_pcd_ep_wedge(gadget_wrapper->pcd, usb_ep); + if (retval == -DWC_E_AGAIN) { + retval = -EAGAIN; + } else if (retval) { + retval = -EINVAL; + } + + return retval; +} +#endif + +#ifdef DWC_EN_ISOC +/** + * This function is used to submit an ISOC Transfer Request to an EP. + * + * - Every time a sync period completes the request's completion callback + * is called to provide data to the gadget driver. + * - Once submitted the request cannot be modified. + * - Each request is turned into periodic data packets untill ISO + * Transfer is stopped.. + */ +static int iso_ep_start(struct usb_ep *usb_ep, struct usb_iso_request *req, + gfp_t gfp_flags) +{ + int retval = 0; + + if (!req || !req->process_buffer || !req->buf0 || !req->buf1) { + DWC_WARN("bad params\n"); + return -EINVAL; + } + + if (!usb_ep) { + DWC_PRINTF("bad params\n"); + return -EINVAL; + } + + req->status = -EINPROGRESS; + + retval = + dwc_otg_pcd_iso_ep_start(gadget_wrapper->pcd, usb_ep, req->buf0, + req->buf1, req->dma0, req->dma1, + req->sync_frame, req->data_pattern_frame, + req->data_per_frame, + req-> + flags & USB_REQ_ISO_ASAP ? -1 : + req->start_frame, req->buf_proc_intrvl, + req, gfp_flags == GFP_ATOMIC ? 1 : 0); + + if (retval) { + return -EINVAL; + } + + return retval; +} + +/** + * This function stops ISO EP Periodic Data Transfer. + */ +static int iso_ep_stop(struct usb_ep *usb_ep, struct usb_iso_request *req) +{ + int retval = 0; + if (!usb_ep) { + DWC_WARN("bad ep\n"); + } + + if (!gadget_wrapper->driver || + gadget_wrapper->gadget.speed == USB_SPEED_UNKNOWN) { + DWC_DEBUGPL(DBG_PCDV, "gadget.speed=%d\n", + gadget_wrapper->gadget.speed); + DWC_WARN("bogus device state\n"); + } + + dwc_otg_pcd_iso_ep_stop(gadget_wrapper->pcd, usb_ep, req); + if (retval) { + retval = -EINVAL; + } + + return retval; +} + +static struct usb_iso_request *alloc_iso_request(struct usb_ep *ep, + int packets, gfp_t gfp_flags) +{ + struct usb_iso_request *pReq = NULL; + uint32_t req_size; + + req_size = sizeof(struct usb_iso_request); + req_size += + (2 * packets * (sizeof(struct usb_gadget_iso_packet_descriptor))); + + pReq = kmalloc(req_size, gfp_flags); + if (!pReq) { + DWC_WARN("Can't allocate Iso Request\n"); + return 0; + } + pReq->iso_packet_desc0 = (void *)(pReq + 1); + + pReq->iso_packet_desc1 = pReq->iso_packet_desc0 + packets; + + return pReq; +} + +static void free_iso_request(struct usb_ep *ep, struct usb_iso_request *req) +{ + kfree(req); +} + +static struct usb_isoc_ep_ops dwc_otg_pcd_ep_ops = { + .ep_ops = { + .enable = ep_enable, + .disable = ep_disable, + + .alloc_request = dwc_otg_pcd_alloc_request, + .free_request = dwc_otg_pcd_free_request, + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28) + .alloc_buffer = dwc_otg_pcd_alloc_buffer, + .free_buffer = dwc_otg_pcd_free_buffer, +#endif + + .queue = ep_queue, + .dequeue = ep_dequeue, + + .set_halt = ep_halt, + .fifo_status = 0, + .fifo_flush = 0, + }, + .iso_ep_start = iso_ep_start, + .iso_ep_stop = iso_ep_stop, + .alloc_iso_request = alloc_iso_request, + .free_iso_request = free_iso_request, +}; + +#else + + int (*enable) (struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc); + int (*disable) (struct usb_ep *ep); + + struct usb_request *(*alloc_request) (struct usb_ep *ep, + gfp_t gfp_flags); + void (*free_request) (struct usb_ep *ep, struct usb_request *req); + + int (*queue) (struct usb_ep *ep, struct usb_request *req, + gfp_t gfp_flags); + int (*dequeue) (struct usb_ep *ep, struct usb_request *req); + + int (*set_halt) (struct usb_ep *ep, int value); + int (*set_wedge) (struct usb_ep *ep); + + int (*fifo_status) (struct usb_ep *ep); + void (*fifo_flush) (struct usb_ep *ep); +static struct usb_ep_ops dwc_otg_pcd_ep_ops = { + .enable = ep_enable, + .disable = ep_disable, + + .alloc_request = dwc_otg_pcd_alloc_request, + .free_request = dwc_otg_pcd_free_request, + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28) + .alloc_buffer = dwc_otg_pcd_alloc_buffer, + .free_buffer = dwc_otg_pcd_free_buffer, +#else + /* .set_wedge = ep_wedge, */ + .set_wedge = NULL, /* uses set_halt instead */ +#endif + + .queue = ep_queue, + .dequeue = ep_dequeue, + + .set_halt = ep_halt, + .fifo_status = 0, + .fifo_flush = 0, + +}; + +#endif /* _EN_ISOC_ */ +/* Gadget Operations */ +/** + * The following gadget operations will be implemented in the DWC_otg + * PCD. Functions in the API that are not described below are not + * implemented. + * + * The Gadget API provides wrapper functions for each of the function + * pointers defined in usb_gadget_ops. The Gadget Driver calls the + * wrapper function, which then calls the underlying PCD function. The + * following sections are named according to the wrapper functions + * (except for ioctl, which doesn't have a wrapper function). Within + * each section, the corresponding DWC_otg PCD function name is + * specified. + * + */ + +/** + *Gets the USB Frame number of the last SOF. + */ +static int get_frame_number(struct usb_gadget *gadget) +{ + struct gadget_wrapper *d; + + DWC_DEBUGPL(DBG_PCDV, "%s(%p)\n", __func__, gadget); + + if (gadget == 0) { + return -ENODEV; + } + + d = container_of(gadget, struct gadget_wrapper, gadget); + return dwc_otg_pcd_get_frame_number(d->pcd); +} + +#ifdef CONFIG_USB_DWC_OTG_LPM +static int test_lpm_enabled(struct usb_gadget *gadget) +{ + struct gadget_wrapper *d; + + d = container_of(gadget, struct gadget_wrapper, gadget); + + return dwc_otg_pcd_is_lpm_enabled(d->pcd); +} +#endif + +/** + * Initiates Session Request Protocol (SRP) to wakeup the host if no + * session is in progress. If a session is already in progress, but + * the device is suspended, remote wakeup signaling is started. + * + */ +static int wakeup(struct usb_gadget *gadget) +{ + struct gadget_wrapper *d; + + DWC_DEBUGPL(DBG_PCDV, "%s(%p)\n", __func__, gadget); + + if (gadget == 0) { + return -ENODEV; + } else { + d = container_of(gadget, struct gadget_wrapper, gadget); + } + dwc_otg_pcd_wakeup(d->pcd); + return 0; +} + +static const struct usb_gadget_ops dwc_otg_pcd_ops = { + .get_frame = get_frame_number, + .wakeup = wakeup, +#ifdef CONFIG_USB_DWC_OTG_LPM + .lpm_support = test_lpm_enabled, +#endif + // current versions must always be self-powered +}; + +static int _setup(dwc_otg_pcd_t * pcd, uint8_t * bytes) +{ + int retval = -DWC_E_NOT_SUPPORTED; + if (gadget_wrapper->driver && gadget_wrapper->driver->setup) { + retval = gadget_wrapper->driver->setup(&gadget_wrapper->gadget, + (struct usb_ctrlrequest + *)bytes); + } + + if (retval == -ENOTSUPP) { + retval = -DWC_E_NOT_SUPPORTED; + } else if (retval < 0) { + retval = -DWC_E_INVALID; + } + + return retval; +} + +#ifdef DWC_EN_ISOC +static int _isoc_complete(dwc_otg_pcd_t * pcd, void *ep_handle, + void *req_handle, int proc_buf_num) +{ + int i, packet_count; + struct usb_gadget_iso_packet_descriptor *iso_packet = 0; + struct usb_iso_request *iso_req = req_handle; + + if (proc_buf_num) { + iso_packet = iso_req->iso_packet_desc1; + } else { + iso_packet = iso_req->iso_packet_desc0; + } + packet_count = + dwc_otg_pcd_get_iso_packet_count(pcd, ep_handle, req_handle); + for (i = 0; i < packet_count; ++i) { + int status; + int actual; + int offset; + dwc_otg_pcd_get_iso_packet_params(pcd, ep_handle, req_handle, + i, &status, &actual, &offset); + switch (status) { + case -DWC_E_NO_DATA: + status = -ENODATA; + break; + default: + if (status) { + DWC_PRINTF("unknown status in isoc packet\n"); + } + + } + iso_packet[i].status = status; + iso_packet[i].offset = offset; + iso_packet[i].actual_length = actual; + } + + iso_req->status = 0; + iso_req->process_buffer(ep_handle, iso_req); + + return 0; +} +#endif /* DWC_EN_ISOC */ + +#ifdef DWC_UTE_PER_IO +/** + * Copy the contents of the extended request to the Linux usb_request's + * extended part and call the gadget's completion. + * + * @param pcd Pointer to the pcd structure + * @param ep_handle Void pointer to the usb_ep structure + * @param req_handle Void pointer to the usb_request structure + * @param status Request status returned from the portable logic + * @param ereq_port Void pointer to the extended request structure + * created in the the portable part that contains the + * results of the processed iso packets. + */ +static int _xisoc_complete(dwc_otg_pcd_t * pcd, void *ep_handle, + void *req_handle, int32_t status, void *ereq_port) +{ + struct dwc_ute_iso_req_ext *ereqorg = NULL; + struct dwc_iso_xreq_port *ereqport = NULL; + struct dwc_ute_iso_packet_descriptor *desc_org = NULL; + int i; + struct usb_request *req; + //struct dwc_ute_iso_packet_descriptor * + //int status = 0; + + req = (struct usb_request *)req_handle; + ereqorg = &req->ext_req; + ereqport = (struct dwc_iso_xreq_port *)ereq_port; + desc_org = ereqorg->per_io_frame_descs; + + if (req && req->complete) { + /* Copy the request data from the portable logic to our request */ + for (i = 0; i < ereqport->pio_pkt_count; i++) { + desc_org[i].actual_length = + ereqport->per_io_frame_descs[i].actual_length; + desc_org[i].status = + ereqport->per_io_frame_descs[i].status; + } + + switch (status) { + case -DWC_E_SHUTDOWN: + req->status = -ESHUTDOWN; + break; + case -DWC_E_RESTART: + req->status = -ECONNRESET; + break; + case -DWC_E_INVALID: + req->status = -EINVAL; + break; + case -DWC_E_TIMEOUT: + req->status = -ETIMEDOUT; + break; + default: + req->status = status; + } + + /* And call the gadget's completion */ + req->complete(ep_handle, req); + } + + return 0; +} +#endif /* DWC_UTE_PER_IO */ + +static int _complete(dwc_otg_pcd_t * pcd, void *ep_handle, + void *req_handle, int32_t status, uint32_t actual) +{ + struct usb_request *req = (struct usb_request *)req_handle; +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,27) + struct dwc_otg_pcd_ep *ep = NULL; +#endif + + if (req && req->complete) { + switch (status) { + case -DWC_E_SHUTDOWN: + req->status = -ESHUTDOWN; + break; + case -DWC_E_RESTART: + req->status = -ECONNRESET; + break; + case -DWC_E_INVALID: + req->status = -EINVAL; + break; + case -DWC_E_TIMEOUT: + req->status = -ETIMEDOUT; + break; + default: + req->status = status; + + } + + req->actual = actual; + DWC_SPINUNLOCK(pcd->lock); + req->complete(ep_handle, req); + DWC_SPINLOCK(pcd->lock); + } +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,27) + ep = ep_from_handle(pcd, ep_handle); + if (GET_CORE_IF(pcd)->dma_enable) { + if (req->length != 0) { + dwc_otg_device_t *otg_dev = gadget_wrapper->pcd->otg_dev; + struct device *dev = NULL; + + if (otg_dev != NULL) + dev = DWC_OTG_OS_GETDEV(otg_dev->os_dep); + + dma_unmap_single(dev, req->dma, req->length, + ep->dwc_ep.is_in ? + DMA_TO_DEVICE: DMA_FROM_DEVICE); + } + } +#endif + + return 0; +} + +static int _connect(dwc_otg_pcd_t * pcd, int speed) +{ + gadget_wrapper->gadget.speed = speed; + return 0; +} + +static int _disconnect(dwc_otg_pcd_t * pcd) +{ + if (gadget_wrapper->driver && gadget_wrapper->driver->disconnect) { + gadget_wrapper->driver->disconnect(&gadget_wrapper->gadget); + } + return 0; +} + +static int _resume(dwc_otg_pcd_t * pcd) +{ + if (gadget_wrapper->driver && gadget_wrapper->driver->resume) { + gadget_wrapper->driver->resume(&gadget_wrapper->gadget); + } + + return 0; +} + +static int _suspend(dwc_otg_pcd_t * pcd) +{ + if (gadget_wrapper->driver && gadget_wrapper->driver->suspend) { + gadget_wrapper->driver->suspend(&gadget_wrapper->gadget); + } + return 0; +} + +/** + * This function updates the otg values in the gadget structure. + */ +static int _hnp_changed(dwc_otg_pcd_t * pcd) +{ + + if (!gadget_wrapper->gadget.is_otg) + return 0; + + gadget_wrapper->gadget.b_hnp_enable = get_b_hnp_enable(pcd); + gadget_wrapper->gadget.a_hnp_support = get_a_hnp_support(pcd); + gadget_wrapper->gadget.a_alt_hnp_support = get_a_alt_hnp_support(pcd); + return 0; +} + +static int _reset(dwc_otg_pcd_t * pcd) +{ + return 0; +} + +#ifdef DWC_UTE_CFI +static int _cfi_setup(dwc_otg_pcd_t * pcd, void *cfi_req) +{ + int retval = -DWC_E_INVALID; + if (gadget_wrapper->driver->cfi_feature_setup) { + retval = + gadget_wrapper->driver-> + cfi_feature_setup(&gadget_wrapper->gadget, + (struct cfi_usb_ctrlrequest *)cfi_req); + } + + return retval; +} +#endif + +static const struct dwc_otg_pcd_function_ops fops = { + .complete = _complete, +#ifdef DWC_EN_ISOC + .isoc_complete = _isoc_complete, +#endif + .setup = _setup, + .disconnect = _disconnect, + .connect = _connect, + .resume = _resume, + .suspend = _suspend, + .hnp_changed = _hnp_changed, + .reset = _reset, +#ifdef DWC_UTE_CFI + .cfi_setup = _cfi_setup, +#endif +#ifdef DWC_UTE_PER_IO + .xisoc_complete = _xisoc_complete, +#endif +}; + +/** + * This function is the top level PCD interrupt handler. + */ +static irqreturn_t dwc_otg_pcd_irq(int irq, void *dev) +{ + dwc_otg_pcd_t *pcd = dev; + int32_t retval = IRQ_NONE; + + retval = dwc_otg_pcd_handle_intr(pcd); + if (retval != 0) { + S3C2410X_CLEAR_EINTPEND(); + } + return IRQ_RETVAL(retval); +} + +/** + * This function initialized the usb_ep structures to there default + * state. + * + * @param d Pointer on gadget_wrapper. + */ +static void gadget_add_eps(struct gadget_wrapper *d) +{ + static const char *names[] = { + + "ep0", + "ep1in", + "ep2in", + "ep3in", + "ep4in", + "ep5in", + "ep6in", + "ep7in", + "ep8in", + "ep9in", + "ep10in", + "ep11in", + "ep12in", + "ep13in", + "ep14in", + "ep15in", + "ep1out", + "ep2out", + "ep3out", + "ep4out", + "ep5out", + "ep6out", + "ep7out", + "ep8out", + "ep9out", + "ep10out", + "ep11out", + "ep12out", + "ep13out", + "ep14out", + "ep15out" + }; + + int i; + struct usb_ep *ep; + int8_t dev_endpoints; + + DWC_DEBUGPL(DBG_PCDV, "%s\n", __func__); + + INIT_LIST_HEAD(&d->gadget.ep_list); + d->gadget.ep0 = &d->ep0; + d->gadget.speed = USB_SPEED_UNKNOWN; + + INIT_LIST_HEAD(&d->gadget.ep0->ep_list); + + /** + * Initialize the EP0 structure. + */ + ep = &d->ep0; + + /* Init the usb_ep structure. */ + ep->name = names[0]; + ep->ops = (struct usb_ep_ops *)&dwc_otg_pcd_ep_ops; + + /** + * @todo NGS: What should the max packet size be set to + * here? Before EP type is set? + */ + ep->maxpacket = MAX_PACKET_SIZE; + dwc_otg_pcd_ep_enable(d->pcd, NULL, ep); + + list_add_tail(&ep->ep_list, &d->gadget.ep_list); + + /** + * Initialize the EP structures. + */ + dev_endpoints = d->pcd->core_if->dev_if->num_in_eps; + + for (i = 0; i < dev_endpoints; i++) { + ep = &d->in_ep[i]; + + /* Init the usb_ep structure. */ + ep->name = names[d->pcd->in_ep[i].dwc_ep.num]; + ep->ops = (struct usb_ep_ops *)&dwc_otg_pcd_ep_ops; + + /** + * @todo NGS: What should the max packet size be set to + * here? Before EP type is set? + */ + ep->maxpacket = MAX_PACKET_SIZE; + list_add_tail(&ep->ep_list, &d->gadget.ep_list); + } + + dev_endpoints = d->pcd->core_if->dev_if->num_out_eps; + + for (i = 0; i < dev_endpoints; i++) { + ep = &d->out_ep[i]; + + /* Init the usb_ep structure. */ + ep->name = names[15 + d->pcd->out_ep[i].dwc_ep.num]; + ep->ops = (struct usb_ep_ops *)&dwc_otg_pcd_ep_ops; + + /** + * @todo NGS: What should the max packet size be set to + * here? Before EP type is set? + */ + ep->maxpacket = MAX_PACKET_SIZE; + + list_add_tail(&ep->ep_list, &d->gadget.ep_list); + } + + /* remove ep0 from the list. There is a ep0 pointer. */ + list_del_init(&d->ep0.ep_list); + + d->ep0.maxpacket = MAX_EP0_SIZE; +} + +/** + * This function releases the Gadget device. + * required by device_unregister(). + * + * @todo Should this do something? Should it free the PCD? + */ +static void dwc_otg_pcd_gadget_release(struct device *dev) +{ + DWC_DEBUGPL(DBG_PCDV, "%s(%p)\n", __func__, dev); +} + +static struct gadget_wrapper *alloc_wrapper(dwc_bus_dev_t *_dev) +{ + static char pcd_name[] = "dwc_otg_pcd"; + dwc_otg_device_t *otg_dev = DWC_OTG_BUSDRVDATA(_dev); + struct gadget_wrapper *d; + int retval; + + d = DWC_ALLOC(sizeof(*d)); + if (d == NULL) { + return NULL; + } + + memset(d, 0, sizeof(*d)); + + d->gadget.name = pcd_name; + d->pcd = otg_dev->pcd; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,30) + strcpy(d->gadget.dev.bus_id, "gadget"); +#else + dev_set_name(&d->gadget.dev, "%s", "gadget"); +#endif + + d->gadget.dev.parent = &_dev->dev; + d->gadget.dev.release = dwc_otg_pcd_gadget_release; + d->gadget.ops = &dwc_otg_pcd_ops; + d->gadget.max_speed = dwc_otg_pcd_is_dualspeed(otg_dev->pcd) ? USB_SPEED_HIGH:USB_SPEED_FULL; + d->gadget.is_otg = dwc_otg_pcd_is_otg(otg_dev->pcd); + + d->driver = 0; + /* Register the gadget device */ + retval = device_register(&d->gadget.dev); + if (retval != 0) { + DWC_ERROR("device_register failed\n"); + DWC_FREE(d); + return NULL; + } + + return d; +} + +static void free_wrapper(struct gadget_wrapper *d) +{ + if (d->driver) { + /* should have been done already by driver model core */ + DWC_WARN("driver '%s' is still registered\n", + d->driver->driver.name); +#ifdef CONFIG_USB_GADGET + usb_gadget_unregister_driver(d->driver); +#endif + } + + device_unregister(&d->gadget.dev); + DWC_FREE(d); +} + +/** + * This function initialized the PCD portion of the driver. + * + */ +int pcd_init(dwc_bus_dev_t *_dev) +{ + dwc_otg_device_t *otg_dev = DWC_OTG_BUSDRVDATA(_dev); + int retval = 0; + + DWC_DEBUGPL(DBG_PCDV, "%s(%p) otg_dev=%p\n", __func__, _dev, otg_dev); + + otg_dev->pcd = dwc_otg_pcd_init(otg_dev); + + if (!otg_dev->pcd) { + DWC_ERROR("dwc_otg_pcd_init failed\n"); + return -ENOMEM; + } + + otg_dev->pcd->otg_dev = otg_dev; + gadget_wrapper = alloc_wrapper(_dev); + + /* + * Initialize EP structures + */ + gadget_add_eps(gadget_wrapper); + /* + * Setup interupt handler + */ + DWC_DEBUGPL(DBG_ANY, "registering handler for irq%d\n", + otg_dev->os_dep.irq_num); + retval = request_irq(otg_dev->os_dep.irq_num, dwc_otg_pcd_irq, + IRQF_SHARED, gadget_wrapper->gadget.name, + otg_dev->pcd); + if (retval != 0) { + DWC_ERROR("request of irq%d failed\n", otg_dev->os_dep.irq_num); + free_wrapper(gadget_wrapper); + return -EBUSY; + } + + dwc_otg_pcd_start(gadget_wrapper->pcd, &fops); + + return retval; +} + +/** + * Cleanup the PCD. + */ +void pcd_remove(dwc_bus_dev_t *_dev) +{ + dwc_otg_device_t *otg_dev = DWC_OTG_BUSDRVDATA(_dev); + dwc_otg_pcd_t *pcd = otg_dev->pcd; + + DWC_DEBUGPL(DBG_PCDV, "%s(%p) otg_dev %p\n", __func__, _dev, otg_dev); + + /* + * Free the IRQ + */ + free_irq(otg_dev->os_dep.irq_num, pcd); + dwc_otg_pcd_remove(otg_dev->pcd); + free_wrapper(gadget_wrapper); + otg_dev->pcd = 0; +} + +#endif /* DWC_HOST_ONLY */ diff --git a/drivers/usb/host/dwc_otg/dwc_otg_regs.h b/drivers/usb/host/dwc_otg/dwc_otg_regs.h new file mode 100644 index 00000000000000..8e0e7b569f1ac0 --- /dev/null +++ b/drivers/usb/host/dwc_otg/dwc_otg_regs.h @@ -0,0 +1,2550 @@ +/* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_regs.h $ + * $Revision: #98 $ + * $Date: 2012/08/10 $ + * $Change: 2047372 $ + * + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ + +#ifndef __DWC_OTG_REGS_H__ +#define __DWC_OTG_REGS_H__ + +#include "dwc_otg_core_if.h" + +/** + * @file + * + * This file contains the data structures for accessing the DWC_otg core registers. + * + * The application interfaces with the HS OTG core by reading from and + * writing to the Control and Status Register (CSR) space through the + * AHB Slave interface. These registers are 32 bits wide, and the + * addresses are 32-bit-block aligned. + * CSRs are classified as follows: + * - Core Global Registers + * - Device Mode Registers + * - Device Global Registers + * - Device Endpoint Specific Registers + * - Host Mode Registers + * - Host Global Registers + * - Host Port CSRs + * - Host Channel Specific Registers + * + * Only the Core Global registers can be accessed in both Device and + * Host modes. When the HS OTG core is operating in one mode, either + * Device or Host, the application must not access registers from the + * other mode. When the core switches from one mode to another, the + * registers in the new mode of operation must be reprogrammed as they + * would be after a power-on reset. + */ + +/****************************************************************************/ +/** DWC_otg Core registers . + * The dwc_otg_core_global_regs structure defines the size + * and relative field offsets for the Core Global registers. + */ +typedef struct dwc_otg_core_global_regs { + /** OTG Control and Status Register. <i>Offset: 000h</i> */ + volatile uint32_t gotgctl; + /** OTG Interrupt Register. <i>Offset: 004h</i> */ + volatile uint32_t gotgint; + /**Core AHB Configuration Register. <i>Offset: 008h</i> */ + volatile uint32_t gahbcfg; + +#define DWC_GLBINTRMASK 0x0001 +#define DWC_DMAENABLE 0x0020 +#define DWC_NPTXEMPTYLVL_EMPTY 0x0080 +#define DWC_NPTXEMPTYLVL_HALFEMPTY 0x0000 +#define DWC_PTXEMPTYLVL_EMPTY 0x0100 +#define DWC_PTXEMPTYLVL_HALFEMPTY 0x0000 + + /**Core USB Configuration Register. <i>Offset: 00Ch</i> */ + volatile uint32_t gusbcfg; + /**Core Reset Register. <i>Offset: 010h</i> */ + volatile uint32_t grstctl; + /**Core Interrupt Register. <i>Offset: 014h</i> */ + volatile uint32_t gintsts; + /**Core Interrupt Mask Register. <i>Offset: 018h</i> */ + volatile uint32_t gintmsk; + /**Receive Status Queue Read Register (Read Only). <i>Offset: 01Ch</i> */ + volatile uint32_t grxstsr; + /**Receive Status Queue Read & POP Register (Read Only). <i>Offset: 020h</i>*/ + volatile uint32_t grxstsp; + /**Receive FIFO Size Register. <i>Offset: 024h</i> */ + volatile uint32_t grxfsiz; + /**Non Periodic Transmit FIFO Size Register. <i>Offset: 028h</i> */ + volatile uint32_t gnptxfsiz; + /**Non Periodic Transmit FIFO/Queue Status Register (Read + * Only). <i>Offset: 02Ch</i> */ + volatile uint32_t gnptxsts; + /**I2C Access Register. <i>Offset: 030h</i> */ + volatile uint32_t gi2cctl; + /**PHY Vendor Control Register. <i>Offset: 034h</i> */ + volatile uint32_t gpvndctl; + /**General Purpose Input/Output Register. <i>Offset: 038h</i> */ + volatile uint32_t ggpio; + /**User ID Register. <i>Offset: 03Ch</i> */ + volatile uint32_t guid; + /**Synopsys ID Register (Read Only). <i>Offset: 040h</i> */ + volatile uint32_t gsnpsid; + /**User HW Config1 Register (Read Only). <i>Offset: 044h</i> */ + volatile uint32_t ghwcfg1; + /**User HW Config2 Register (Read Only). <i>Offset: 048h</i> */ + volatile uint32_t ghwcfg2; +#define DWC_SLAVE_ONLY_ARCH 0 +#define DWC_EXT_DMA_ARCH 1 +#define DWC_INT_DMA_ARCH 2 + +#define DWC_MODE_HNP_SRP_CAPABLE 0 +#define DWC_MODE_SRP_ONLY_CAPABLE 1 +#define DWC_MODE_NO_HNP_SRP_CAPABLE 2 +#define DWC_MODE_SRP_CAPABLE_DEVICE 3 +#define DWC_MODE_NO_SRP_CAPABLE_DEVICE 4 +#define DWC_MODE_SRP_CAPABLE_HOST 5 +#define DWC_MODE_NO_SRP_CAPABLE_HOST 6 + + /**User HW Config3 Register (Read Only). <i>Offset: 04Ch</i> */ + volatile uint32_t ghwcfg3; + /**User HW Config4 Register (Read Only). <i>Offset: 050h</i>*/ + volatile uint32_t ghwcfg4; + /** Core LPM Configuration register <i>Offset: 054h</i>*/ + volatile uint32_t glpmcfg; + /** Global PowerDn Register <i>Offset: 058h</i> */ + volatile uint32_t gpwrdn; + /** Global DFIFO SW Config Register <i>Offset: 05Ch</i> */ + volatile uint32_t gdfifocfg; + /** ADP Control Register <i>Offset: 060h</i> */ + volatile uint32_t adpctl; + /** Reserved <i>Offset: 064h-0FFh</i> */ + volatile uint32_t reserved39[39]; + /** Host Periodic Transmit FIFO Size Register. <i>Offset: 100h</i> */ + volatile uint32_t hptxfsiz; + /** Device Periodic Transmit FIFO#n Register if dedicated fifos are disabled, + otherwise Device Transmit FIFO#n Register. + * <i>Offset: 104h + (FIFO_Number-1)*04h, 1 <= FIFO Number <= 15 (1<=n<=15).</i> */ + volatile uint32_t dtxfsiz[15]; +} dwc_otg_core_global_regs_t; + +/** + * This union represents the bit fields of the Core OTG Control + * and Status Register (GOTGCTL). Set the bits using the bit + * fields then write the <i>d32</i> value to the register. + */ +typedef union gotgctl_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + unsigned sesreqscs:1; + unsigned sesreq:1; + unsigned vbvalidoven:1; + unsigned vbvalidovval:1; + unsigned avalidoven:1; + unsigned avalidovval:1; + unsigned bvalidoven:1; + unsigned bvalidovval:1; + unsigned hstnegscs:1; + unsigned hnpreq:1; + unsigned hstsethnpen:1; + unsigned devhnpen:1; + unsigned reserved12_15:4; + unsigned conidsts:1; + unsigned dbnctime:1; + unsigned asesvld:1; + unsigned bsesvld:1; + unsigned otgver:1; + unsigned reserved1:1; + unsigned multvalidbc:5; + unsigned chirpen:1; + unsigned reserved28_31:4; + } b; +} gotgctl_data_t; + +/** + * This union represents the bit fields of the Core OTG Interrupt Register + * (GOTGINT). Set/clear the bits using the bit fields then write the <i>d32</i> + * value to the register. + */ +typedef union gotgint_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + /** Current Mode */ + unsigned reserved0_1:2; + + /** Session End Detected */ + unsigned sesenddet:1; + + unsigned reserved3_7:5; + + /** Session Request Success Status Change */ + unsigned sesreqsucstschng:1; + /** Host Negotiation Success Status Change */ + unsigned hstnegsucstschng:1; + + unsigned reserved10_16:7; + + /** Host Negotiation Detected */ + unsigned hstnegdet:1; + /** A-Device Timeout Change */ + unsigned adevtoutchng:1; + /** Debounce Done */ + unsigned debdone:1; + /** Multi-Valued input changed */ + unsigned mvic:1; + + unsigned reserved31_21:11; + + } b; +} gotgint_data_t; + +/** + * This union represents the bit fields of the Core AHB Configuration + * Register (GAHBCFG). Set/clear the bits using the bit fields then + * write the <i>d32</i> value to the register. + */ +typedef union gahbcfg_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + unsigned glblintrmsk:1; +#define DWC_GAHBCFG_GLBINT_ENABLE 1 + + unsigned hburstlen:4; +#define DWC_GAHBCFG_INT_DMA_BURST_SINGLE 0 +#define DWC_GAHBCFG_INT_DMA_BURST_INCR 1 +#define DWC_GAHBCFG_INT_DMA_BURST_INCR4 3 +#define DWC_GAHBCFG_INT_DMA_BURST_INCR8 5 +#define DWC_GAHBCFG_INT_DMA_BURST_INCR16 7 + + unsigned dmaenable:1; +#define DWC_GAHBCFG_DMAENABLE 1 + unsigned reserved:1; + unsigned nptxfemplvl_txfemplvl:1; + unsigned ptxfemplvl:1; +#define DWC_GAHBCFG_TXFEMPTYLVL_EMPTY 1 +#define DWC_GAHBCFG_TXFEMPTYLVL_HALFEMPTY 0 + unsigned reserved9_20:12; + unsigned remmemsupp:1; + unsigned notialldmawrit:1; + unsigned ahbsingle:1; + unsigned reserved24_31:8; + } b; +} gahbcfg_data_t; + +/** + * This union represents the bit fields of the Core USB Configuration + * Register (GUSBCFG). Set the bits using the bit fields then write + * the <i>d32</i> value to the register. + */ +typedef union gusbcfg_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + unsigned toutcal:3; + unsigned phyif:1; + unsigned ulpi_utmi_sel:1; + unsigned fsintf:1; + unsigned physel:1; + unsigned ddrsel:1; + unsigned srpcap:1; + unsigned hnpcap:1; + unsigned usbtrdtim:4; + unsigned reserved1:1; + unsigned phylpwrclksel:1; + unsigned otgutmifssel:1; + unsigned ulpi_fsls:1; + unsigned ulpi_auto_res:1; + unsigned ulpi_clk_sus_m:1; + unsigned ulpi_ext_vbus_drv:1; + unsigned ulpi_int_vbus_indicator:1; + unsigned term_sel_dl_pulse:1; + unsigned indicator_complement:1; + unsigned indicator_pass_through:1; + unsigned ulpi_int_prot_dis:1; + unsigned ic_usb_cap:1; + unsigned ic_traffic_pull_remove:1; + unsigned tx_end_delay:1; + unsigned force_host_mode:1; + unsigned force_dev_mode:1; + unsigned reserved31:1; + } b; +} gusbcfg_data_t; + +/** + * This union represents the bit fields of the Core Reset Register + * (GRSTCTL). Set/clear the bits using the bit fields then write the + * <i>d32</i> value to the register. + */ +typedef union grstctl_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + /** Core Soft Reset (CSftRst) (Device and Host) + * + * The application can flush the control logic in the + * entire core using this bit. This bit resets the + * pipelines in the AHB Clock domain as well as the + * PHY Clock domain. + * + * The state machines are reset to an IDLE state, the + * control bits in the CSRs are cleared, all the + * transmit FIFOs and the receive FIFO are flushed. + * + * The status mask bits that control the generation of + * the interrupt, are cleared, to clear the + * interrupt. The interrupt status bits are not + * cleared, so the application can get the status of + * any events that occurred in the core after it has + * set this bit. + * + * Any transactions on the AHB are terminated as soon + * as possible following the protocol. Any + * transactions on the USB are terminated immediately. + * + * The configuration settings in the CSRs are + * unchanged, so the software doesn't have to + * reprogram these registers (Device + * Configuration/Host Configuration/Core System + * Configuration/Core PHY Configuration). + * + * The application can write to this bit, any time it + * wants to reset the core. This is a self clearing + * bit and the core clears this bit after all the + * necessary logic is reset in the core, which may + * take several clocks, depending on the current state + * of the core. + */ + unsigned csftrst:1; + /** Hclk Soft Reset + * + * The application uses this bit to reset the control logic in + * the AHB clock domain. Only AHB clock domain pipelines are + * reset. + */ + unsigned hsftrst:1; + /** Host Frame Counter Reset (Host Only)<br> + * + * The application can reset the (micro)frame number + * counter inside the core, using this bit. When the + * (micro)frame counter is reset, the subsequent SOF + * sent out by the core, will have a (micro)frame + * number of 0. + */ + unsigned hstfrm:1; + /** In Token Sequence Learning Queue Flush + * (INTknQFlsh) (Device Only) + */ + unsigned intknqflsh:1; + /** RxFIFO Flush (RxFFlsh) (Device and Host) + * + * The application can flush the entire Receive FIFO + * using this bit. The application must first + * ensure that the core is not in the middle of a + * transaction. The application should write into + * this bit, only after making sure that neither the + * DMA engine is reading from the RxFIFO nor the MAC + * is writing the data in to the FIFO. The + * application should wait until the bit is cleared + * before performing any other operations. This bit + * will takes 8 clocks (slowest of PHY or AHB clock) + * to clear. + */ + unsigned rxfflsh:1; + /** TxFIFO Flush (TxFFlsh) (Device and Host). + * + * This bit is used to selectively flush a single or + * all transmit FIFOs. The application must first + * ensure that the core is not in the middle of a + * transaction. The application should write into + * this bit, only after making sure that neither the + * DMA engine is writing into the TxFIFO nor the MAC + * is reading the data out of the FIFO. The + * application should wait until the core clears this + * bit, before performing any operations. This bit + * will takes 8 clocks (slowest of PHY or AHB clock) + * to clear. + */ + unsigned txfflsh:1; + + /** TxFIFO Number (TxFNum) (Device and Host). + * + * This is the FIFO number which needs to be flushed, + * using the TxFIFO Flush bit. This field should not + * be changed until the TxFIFO Flush bit is cleared by + * the core. + * - 0x0 : Non Periodic TxFIFO Flush + * - 0x1 : Periodic TxFIFO #1 Flush in device mode + * or Periodic TxFIFO in host mode + * - 0x2 : Periodic TxFIFO #2 Flush in device mode. + * - ... + * - 0xF : Periodic TxFIFO #15 Flush in device mode + * - 0x10: Flush all the Transmit NonPeriodic and + * Transmit Periodic FIFOs in the core + */ + unsigned txfnum:5; + /** Reserved */ + unsigned reserved11_29:19; + /** DMA Request Signal. Indicated DMA request is in + * probress. Used for debug purpose. */ + unsigned dmareq:1; + /** AHB Master Idle. Indicates the AHB Master State + * Machine is in IDLE condition. */ + unsigned ahbidle:1; + } b; +} grstctl_t; + +/** + * This union represents the bit fields of the Core Interrupt Mask + * Register (GINTMSK). Set/clear the bits using the bit fields then + * write the <i>d32</i> value to the register. + */ +typedef union gintmsk_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + unsigned reserved0:1; + unsigned modemismatch:1; + unsigned otgintr:1; + unsigned sofintr:1; + unsigned rxstsqlvl:1; + unsigned nptxfempty:1; + unsigned ginnakeff:1; + unsigned goutnakeff:1; + unsigned ulpickint:1; + unsigned i2cintr:1; + unsigned erlysuspend:1; + unsigned usbsuspend:1; + unsigned usbreset:1; + unsigned enumdone:1; + unsigned isooutdrop:1; + unsigned eopframe:1; + unsigned restoredone:1; + unsigned epmismatch:1; + unsigned inepintr:1; + unsigned outepintr:1; + unsigned incomplisoin:1; + unsigned incomplisoout:1; + unsigned fetsusp:1; + unsigned resetdet:1; + unsigned portintr:1; + unsigned hcintr:1; + unsigned ptxfempty:1; + unsigned lpmtranrcvd:1; + unsigned conidstschng:1; + unsigned disconnect:1; + unsigned sessreqintr:1; + unsigned wkupintr:1; + } b; +} gintmsk_data_t; +/** + * This union represents the bit fields of the Core Interrupt Register + * (GINTSTS). Set/clear the bits using the bit fields then write the + * <i>d32</i> value to the register. + */ +typedef union gintsts_data { + /** raw register data */ + uint32_t d32; +#define DWC_SOF_INTR_MASK 0x0008 + /** register bits */ + struct { +#define DWC_HOST_MODE 1 + unsigned curmode:1; + unsigned modemismatch:1; + unsigned otgintr:1; + unsigned sofintr:1; + unsigned rxstsqlvl:1; + unsigned nptxfempty:1; + unsigned ginnakeff:1; + unsigned goutnakeff:1; + unsigned ulpickint:1; + unsigned i2cintr:1; + unsigned erlysuspend:1; + unsigned usbsuspend:1; + unsigned usbreset:1; + unsigned enumdone:1; + unsigned isooutdrop:1; + unsigned eopframe:1; + unsigned restoredone:1; + unsigned epmismatch:1; + unsigned inepint:1; + unsigned outepintr:1; + unsigned incomplisoin:1; + unsigned incomplisoout:1; + unsigned fetsusp:1; + unsigned resetdet:1; + unsigned portintr:1; + unsigned hcintr:1; + unsigned ptxfempty:1; + unsigned lpmtranrcvd:1; + unsigned conidstschng:1; + unsigned disconnect:1; + unsigned sessreqintr:1; + unsigned wkupintr:1; + } b; +} gintsts_data_t; + +/** + * This union represents the bit fields in the Device Receive Status Read and + * Pop Registers (GRXSTSR, GRXSTSP) Read the register into the <i>d32</i> + * element then read out the bits using the <i>b</i>it elements. + */ +typedef union device_grxsts_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + unsigned epnum:4; + unsigned bcnt:11; + unsigned dpid:2; + +#define DWC_STS_DATA_UPDT 0x2 // OUT Data Packet +#define DWC_STS_XFER_COMP 0x3 // OUT Data Transfer Complete + +#define DWC_DSTS_GOUT_NAK 0x1 // Global OUT NAK +#define DWC_DSTS_SETUP_COMP 0x4 // Setup Phase Complete +#define DWC_DSTS_SETUP_UPDT 0x6 // SETUP Packet + unsigned pktsts:4; + unsigned fn:4; + unsigned reserved25_31:7; + } b; +} device_grxsts_data_t; + +/** + * This union represents the bit fields in the Host Receive Status Read and + * Pop Registers (GRXSTSR, GRXSTSP) Read the register into the <i>d32</i> + * element then read out the bits using the <i>b</i>it elements. + */ +typedef union host_grxsts_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + unsigned chnum:4; + unsigned bcnt:11; + unsigned dpid:2; + + unsigned pktsts:4; +#define DWC_GRXSTS_PKTSTS_IN 0x2 +#define DWC_GRXSTS_PKTSTS_IN_XFER_COMP 0x3 +#define DWC_GRXSTS_PKTSTS_DATA_TOGGLE_ERR 0x5 +#define DWC_GRXSTS_PKTSTS_CH_HALTED 0x7 + + unsigned reserved21_31:11; + } b; +} host_grxsts_data_t; + +/** + * This union represents the bit fields in the FIFO Size Registers (HPTXFSIZ, + * GNPTXFSIZ, DPTXFSIZn, DIEPTXFn). Read the register into the <i>d32</i> element + * then read out the bits using the <i>b</i>it elements. + */ +typedef union fifosize_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + unsigned startaddr:16; + unsigned depth:16; + } b; +} fifosize_data_t; + +/** + * This union represents the bit fields in the Non-Periodic Transmit + * FIFO/Queue Status Register (GNPTXSTS). Read the register into the + * <i>d32</i> element then read out the bits using the <i>b</i>it + * elements. + */ +typedef union gnptxsts_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + unsigned nptxfspcavail:16; + unsigned nptxqspcavail:8; + /** Top of the Non-Periodic Transmit Request Queue + * - bit 24 - Terminate (Last entry for the selected + * channel/EP) + * - bits 26:25 - Token Type + * - 2'b00 - IN/OUT + * - 2'b01 - Zero Length OUT + * - 2'b10 - PING/Complete Split + * - 2'b11 - Channel Halt + * - bits 30:27 - Channel/EP Number + */ + unsigned nptxqtop_terminate:1; + unsigned nptxqtop_token:2; + unsigned nptxqtop_chnep:4; + unsigned reserved:1; + } b; +} gnptxsts_data_t; + +/** + * This union represents the bit fields in the Transmit + * FIFO Status Register (DTXFSTS). Read the register into the + * <i>d32</i> element then read out the bits using the <i>b</i>it + * elements. + */ +typedef union dtxfsts_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + unsigned txfspcavail:16; + unsigned reserved:16; + } b; +} dtxfsts_data_t; + +/** + * This union represents the bit fields in the I2C Control Register + * (I2CCTL). Read the register into the <i>d32</i> element then read out the + * bits using the <i>b</i>it elements. + */ +typedef union gi2cctl_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + unsigned rwdata:8; + unsigned regaddr:8; + unsigned addr:7; + unsigned i2cen:1; + unsigned ack:1; + unsigned i2csuspctl:1; + unsigned i2cdevaddr:2; + unsigned i2cdatse0:1; + unsigned reserved:1; + unsigned rw:1; + unsigned bsydne:1; + } b; +} gi2cctl_data_t; + +/** + * This union represents the bit fields in the PHY Vendor Control Register + * (GPVNDCTL). Read the register into the <i>d32</i> element then read out the + * bits using the <i>b</i>it elements. + */ +typedef union gpvndctl_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + unsigned regdata:8; + unsigned vctrl:8; + unsigned regaddr16_21:6; + unsigned regwr:1; + unsigned reserved23_24:2; + unsigned newregreq:1; + unsigned vstsbsy:1; + unsigned vstsdone:1; + unsigned reserved28_30:3; + unsigned disulpidrvr:1; + } b; +} gpvndctl_data_t; + +/** + * This union represents the bit fields in the General Purpose + * Input/Output Register (GGPIO). + * Read the register into the <i>d32</i> element then read out the + * bits using the <i>b</i>it elements. + */ +typedef union ggpio_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + unsigned gpi:16; + unsigned gpo:16; + } b; +} ggpio_data_t; + +/** + * This union represents the bit fields in the User ID Register + * (GUID). Read the register into the <i>d32</i> element then read out the + * bits using the <i>b</i>it elements. + */ +typedef union guid_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + unsigned rwdata:32; + } b; +} guid_data_t; + +/** + * This union represents the bit fields in the Synopsys ID Register + * (GSNPSID). Read the register into the <i>d32</i> element then read out the + * bits using the <i>b</i>it elements. + */ +typedef union gsnpsid_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + unsigned rwdata:32; + } b; +} gsnpsid_data_t; + +/** + * This union represents the bit fields in the User HW Config1 + * Register. Read the register into the <i>d32</i> element then read + * out the bits using the <i>b</i>it elements. + */ +typedef union hwcfg1_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + unsigned ep_dir0:2; + unsigned ep_dir1:2; + unsigned ep_dir2:2; + unsigned ep_dir3:2; + unsigned ep_dir4:2; + unsigned ep_dir5:2; + unsigned ep_dir6:2; + unsigned ep_dir7:2; + unsigned ep_dir8:2; + unsigned ep_dir9:2; + unsigned ep_dir10:2; + unsigned ep_dir11:2; + unsigned ep_dir12:2; + unsigned ep_dir13:2; + unsigned ep_dir14:2; + unsigned ep_dir15:2; + } b; +} hwcfg1_data_t; + +/** + * This union represents the bit fields in the User HW Config2 + * Register. Read the register into the <i>d32</i> element then read + * out the bits using the <i>b</i>it elements. + */ +typedef union hwcfg2_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + /* GHWCFG2 */ + unsigned op_mode:3; +#define DWC_HWCFG2_OP_MODE_HNP_SRP_CAPABLE_OTG 0 +#define DWC_HWCFG2_OP_MODE_SRP_ONLY_CAPABLE_OTG 1 +#define DWC_HWCFG2_OP_MODE_NO_HNP_SRP_CAPABLE_OTG 2 +#define DWC_HWCFG2_OP_MODE_SRP_CAPABLE_DEVICE 3 +#define DWC_HWCFG2_OP_MODE_NO_SRP_CAPABLE_DEVICE 4 +#define DWC_HWCFG2_OP_MODE_SRP_CAPABLE_HOST 5 +#define DWC_HWCFG2_OP_MODE_NO_SRP_CAPABLE_HOST 6 + + unsigned architecture:2; + unsigned point2point:1; + unsigned hs_phy_type:2; +#define DWC_HWCFG2_HS_PHY_TYPE_NOT_SUPPORTED 0 +#define DWC_HWCFG2_HS_PHY_TYPE_UTMI 1 +#define DWC_HWCFG2_HS_PHY_TYPE_ULPI 2 +#define DWC_HWCFG2_HS_PHY_TYPE_UTMI_ULPI 3 + + unsigned fs_phy_type:2; + unsigned num_dev_ep:4; + unsigned num_host_chan:4; + unsigned perio_ep_supported:1; + unsigned dynamic_fifo:1; + unsigned multi_proc_int:1; + unsigned reserved21:1; + unsigned nonperio_tx_q_depth:2; + unsigned host_perio_tx_q_depth:2; + unsigned dev_token_q_depth:5; + unsigned otg_enable_ic_usb:1; + } b; +} hwcfg2_data_t; + +/** + * This union represents the bit fields in the User HW Config3 + * Register. Read the register into the <i>d32</i> element then read + * out the bits using the <i>b</i>it elements. + */ +typedef union hwcfg3_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + /* GHWCFG3 */ + unsigned xfer_size_cntr_width:4; + unsigned packet_size_cntr_width:3; + unsigned otg_func:1; + unsigned i2c:1; + unsigned vendor_ctrl_if:1; + unsigned optional_features:1; + unsigned synch_reset_type:1; + unsigned adp_supp:1; + unsigned otg_enable_hsic:1; + unsigned bc_support:1; + unsigned otg_lpm_en:1; + unsigned dfifo_depth:16; + } b; +} hwcfg3_data_t; + +/** + * This union represents the bit fields in the User HW Config4 + * Register. Read the register into the <i>d32</i> element then read + * out the bits using the <i>b</i>it elements. + */ +typedef union hwcfg4_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + unsigned num_dev_perio_in_ep:4; + unsigned power_optimiz:1; + unsigned min_ahb_freq:1; + unsigned hiber:1; + unsigned xhiber:1; + unsigned reserved:6; + unsigned utmi_phy_data_width:2; + unsigned num_dev_mode_ctrl_ep:4; + unsigned iddig_filt_en:1; + unsigned vbus_valid_filt_en:1; + unsigned a_valid_filt_en:1; + unsigned b_valid_filt_en:1; + unsigned session_end_filt_en:1; + unsigned ded_fifo_en:1; + unsigned num_in_eps:4; + unsigned desc_dma:1; + unsigned desc_dma_dyn:1; + } b; +} hwcfg4_data_t; + +/** + * This union represents the bit fields of the Core LPM Configuration + * Register (GLPMCFG). Set the bits using bit fields then write + * the <i>d32</i> value to the register. + */ +typedef union glpmctl_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + /** LPM-Capable (LPMCap) (Device and Host) + * The application uses this bit to control + * the DWC_otg core LPM capabilities. + */ + unsigned lpm_cap_en:1; + /** LPM response programmed by application (AppL1Res) (Device) + * Handshake response to LPM token pre-programmed + * by device application software. + */ + unsigned appl_resp:1; + /** Host Initiated Resume Duration (HIRD) (Device and Host) + * In Host mode this field indicates the value of HIRD + * to be sent in an LPM transaction. + * In Device mode this field is updated with the + * Received LPM Token HIRD bmAttribute + * when an ACK/NYET/STALL response is sent + * to an LPM transaction. + */ + unsigned hird:4; + /** RemoteWakeEnable (bRemoteWake) (Device and Host) + * In Host mode this bit indicates the value of remote + * wake up to be sent in wIndex field of LPM transaction. + * In Device mode this field is updated with the + * Received LPM Token bRemoteWake bmAttribute + * when an ACK/NYET/STALL response is sent + * to an LPM transaction. + */ + unsigned rem_wkup_en:1; + /** Enable utmi_sleep_n (EnblSlpM) (Device and Host) + * The application uses this bit to control + * the utmi_sleep_n assertion to the PHY when in L1 state. + */ + unsigned en_utmi_sleep:1; + /** HIRD Threshold (HIRD_Thres) (Device and Host) + */ + unsigned hird_thres:5; + /** LPM Response (CoreL1Res) (Device and Host) + * In Host mode this bit contains handsake response to + * LPM transaction. + * In Device mode the response of the core to + * LPM transaction received is reflected in these two bits. + - 0x0 : ERROR (No handshake response) + - 0x1 : STALL + - 0x2 : NYET + - 0x3 : ACK + */ + unsigned lpm_resp:2; + /** Port Sleep Status (SlpSts) (Device and Host) + * This bit is set as long as a Sleep condition + * is present on the USB bus. + */ + unsigned prt_sleep_sts:1; + /** Sleep State Resume OK (L1ResumeOK) (Device and Host) + * Indicates that the application or host + * can start resume from Sleep state. + */ + unsigned sleep_state_resumeok:1; + /** LPM channel Index (LPM_Chnl_Indx) (Host) + * The channel number on which the LPM transaction + * has to be applied while sending + * an LPM transaction to the local device. + */ + unsigned lpm_chan_index:4; + /** LPM Retry Count (LPM_Retry_Cnt) (Host) + * Number host retries that would be performed + * if the device response was not valid response. + */ + unsigned retry_count:3; + /** Send LPM Transaction (SndLPM) (Host) + * When set by application software, + * an LPM transaction containing two tokens + * is sent. + */ + unsigned send_lpm:1; + /** LPM Retry status (LPM_RetryCnt_Sts) (Host) + * Number of LPM Host Retries still remaining + * to be transmitted for the current LPM sequence + */ + unsigned retry_count_sts:3; + unsigned reserved28_29:2; + /** In host mode once this bit is set, the host + * configures to drive the HSIC Idle state on the bus. + * It then waits for the device to initiate the Connect sequence. + * In device mode once this bit is set, the device waits for + * the HSIC Idle line state on the bus. Upon receving the Idle + * line state, it initiates the HSIC Connect sequence. + */ + unsigned hsic_connect:1; + /** This bit overrides and functionally inverts + * the if_select_hsic input port signal. + */ + unsigned inv_sel_hsic:1; + } b; +} glpmcfg_data_t; + +/** + * This union represents the bit fields of the Core ADP Timer, Control and + * Status Register (ADPTIMCTLSTS). Set the bits using bit fields then write + * the <i>d32</i> value to the register. + */ +typedef union adpctl_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + /** Probe Discharge (PRB_DSCHG) + * These bits set the times for TADP_DSCHG. + * These bits are defined as follows: + * 2'b00 - 4 msec + * 2'b01 - 8 msec + * 2'b10 - 16 msec + * 2'b11 - 32 msec + */ + unsigned prb_dschg:2; + /** Probe Delta (PRB_DELTA) + * These bits set the resolution for RTIM value. + * The bits are defined in units of 32 kHz clock cycles as follows: + * 2'b00 - 1 cycles + * 2'b01 - 2 cycles + * 2'b10 - 3 cycles + * 2'b11 - 4 cycles + * For example if this value is chosen to 2'b01, it means that RTIM + * increments for every 3(three) 32Khz clock cycles. + */ + unsigned prb_delta:2; + /** Probe Period (PRB_PER) + * These bits sets the TADP_PRD as shown in Figure 4 as follows: + * 2'b00 - 0.625 to 0.925 sec (typical 0.775 sec) + * 2'b01 - 1.25 to 1.85 sec (typical 1.55 sec) + * 2'b10 - 1.9 to 2.6 sec (typical 2.275 sec) + * 2'b11 - Reserved + */ + unsigned prb_per:2; + /** These bits capture the latest time it took for VBUS to ramp from + * VADP_SINK to VADP_PRB. + * 0x000 - 1 cycles + * 0x001 - 2 cycles + * 0x002 - 3 cycles + * etc + * 0x7FF - 2048 cycles + * A time of 1024 cycles at 32 kHz corresponds to a time of 32 msec. + */ + unsigned rtim:11; + /** Enable Probe (EnaPrb) + * When programmed to 1'b1, the core performs a probe operation. + * This bit is valid only if OTG_Ver = 1'b1. + */ + unsigned enaprb:1; + /** Enable Sense (EnaSns) + * When programmed to 1'b1, the core performs a Sense operation. + * This bit is valid only if OTG_Ver = 1'b1. + */ + unsigned enasns:1; + /** ADP Reset (ADPRes) + * When set, ADP controller is reset. + * This bit is valid only if OTG_Ver = 1'b1. + */ + unsigned adpres:1; + /** ADP Enable (ADPEn) + * When set, the core performs either ADP probing or sensing + * based on EnaPrb or EnaSns. + * This bit is valid only if OTG_Ver = 1'b1. + */ + unsigned adpen:1; + /** ADP Probe Interrupt (ADP_PRB_INT) + * When this bit is set, it means that the VBUS + * voltage is greater than VADP_PRB or VADP_PRB is reached. + * This bit is valid only if OTG_Ver = 1'b1. + */ + unsigned adp_prb_int:1; + /** + * ADP Sense Interrupt (ADP_SNS_INT) + * When this bit is set, it means that the VBUS voltage is greater than + * VADP_SNS value or VADP_SNS is reached. + * This bit is valid only if OTG_Ver = 1'b1. + */ + unsigned adp_sns_int:1; + /** ADP Tomeout Interrupt (ADP_TMOUT_INT) + * This bit is relevant only for an ADP probe. + * When this bit is set, it means that the ramp time has + * completed ie ADPCTL.RTIM has reached its terminal value + * of 0x7FF. This is a debug feature that allows software + * to read the ramp time after each cycle. + * This bit is valid only if OTG_Ver = 1'b1. + */ + unsigned adp_tmout_int:1; + /** ADP Probe Interrupt Mask (ADP_PRB_INT_MSK) + * When this bit is set, it unmasks the interrupt due to ADP_PRB_INT. + * This bit is valid only if OTG_Ver = 1'b1. + */ + unsigned adp_prb_int_msk:1; + /** ADP Sense Interrupt Mask (ADP_SNS_INT_MSK) + * When this bit is set, it unmasks the interrupt due to ADP_SNS_INT. + * This bit is valid only if OTG_Ver = 1'b1. + */ + unsigned adp_sns_int_msk:1; + /** ADP Timoeout Interrupt Mask (ADP_TMOUT_MSK) + * When this bit is set, it unmasks the interrupt due to ADP_TMOUT_INT. + * This bit is valid only if OTG_Ver = 1'b1. + */ + unsigned adp_tmout_int_msk:1; + /** Access Request + * 2'b00 - Read/Write Valid (updated by the core) + * 2'b01 - Read + * 2'b00 - Write + * 2'b00 - Reserved + */ + unsigned ar:2; + /** Reserved */ + unsigned reserved29_31:3; + } b; +} adpctl_data_t; + +//////////////////////////////////////////// +// Device Registers +/** + * Device Global Registers. <i>Offsets 800h-BFFh</i> + * + * The following structures define the size and relative field offsets + * for the Device Mode Registers. + * + * <i>These registers are visible only in Device mode and must not be + * accessed in Host mode, as the results are unknown.</i> + */ +typedef struct dwc_otg_dev_global_regs { + /** Device Configuration Register. <i>Offset 800h</i> */ + volatile uint32_t dcfg; + /** Device Control Register. <i>Offset: 804h</i> */ + volatile uint32_t dctl; + /** Device Status Register (Read Only). <i>Offset: 808h</i> */ + volatile uint32_t dsts; + /** Reserved. <i>Offset: 80Ch</i> */ + uint32_t unused; + /** Device IN Endpoint Common Interrupt Mask + * Register. <i>Offset: 810h</i> */ + volatile uint32_t diepmsk; + /** Device OUT Endpoint Common Interrupt Mask + * Register. <i>Offset: 814h</i> */ + volatile uint32_t doepmsk; + /** Device All Endpoints Interrupt Register. <i>Offset: 818h</i> */ + volatile uint32_t daint; + /** Device All Endpoints Interrupt Mask Register. <i>Offset: + * 81Ch</i> */ + volatile uint32_t daintmsk; + /** Device IN Token Queue Read Register-1 (Read Only). + * <i>Offset: 820h</i> */ + volatile uint32_t dtknqr1; + /** Device IN Token Queue Read Register-2 (Read Only). + * <i>Offset: 824h</i> */ + volatile uint32_t dtknqr2; + /** Device VBUS discharge Register. <i>Offset: 828h</i> */ + volatile uint32_t dvbusdis; + /** Device VBUS Pulse Register. <i>Offset: 82Ch</i> */ + volatile uint32_t dvbuspulse; + /** Device IN Token Queue Read Register-3 (Read Only). / + * Device Thresholding control register (Read/Write) + * <i>Offset: 830h</i> */ + volatile uint32_t dtknqr3_dthrctl; + /** Device IN Token Queue Read Register-4 (Read Only). / + * Device IN EPs empty Inr. Mask Register (Read/Write) + * <i>Offset: 834h</i> */ + volatile uint32_t dtknqr4_fifoemptymsk; + /** Device Each Endpoint Interrupt Register (Read Only). / + * <i>Offset: 838h</i> */ + volatile uint32_t deachint; + /** Device Each Endpoint Interrupt mask Register (Read/Write). / + * <i>Offset: 83Ch</i> */ + volatile uint32_t deachintmsk; + /** Device Each In Endpoint Interrupt mask Register (Read/Write). / + * <i>Offset: 840h</i> */ + volatile uint32_t diepeachintmsk[MAX_EPS_CHANNELS]; + /** Device Each Out Endpoint Interrupt mask Register (Read/Write). / + * <i>Offset: 880h</i> */ + volatile uint32_t doepeachintmsk[MAX_EPS_CHANNELS]; +} dwc_otg_device_global_regs_t; + +/** + * This union represents the bit fields in the Device Configuration + * Register. Read the register into the <i>d32</i> member then + * set/clear the bits using the <i>b</i>it elements. Write the + * <i>d32</i> member to the dcfg register. + */ +typedef union dcfg_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + /** Device Speed */ + unsigned devspd:2; + /** Non Zero Length Status OUT Handshake */ + unsigned nzstsouthshk:1; +#define DWC_DCFG_SEND_STALL 1 + + unsigned ena32khzs:1; + /** Device Addresses */ + unsigned devaddr:7; + /** Periodic Frame Interval */ + unsigned perfrint:2; +#define DWC_DCFG_FRAME_INTERVAL_80 0 +#define DWC_DCFG_FRAME_INTERVAL_85 1 +#define DWC_DCFG_FRAME_INTERVAL_90 2 +#define DWC_DCFG_FRAME_INTERVAL_95 3 + + /** Enable Device OUT NAK for bulk in DDMA mode */ + unsigned endevoutnak:1; + + unsigned reserved14_17:4; + /** In Endpoint Mis-match count */ + unsigned epmscnt:5; + /** Enable Descriptor DMA in Device mode */ + unsigned descdma:1; + unsigned perschintvl:2; + unsigned resvalid:6; + } b; +} dcfg_data_t; + +/** + * This union represents the bit fields in the Device Control + * Register. Read the register into the <i>d32</i> member then + * set/clear the bits using the <i>b</i>it elements. + */ +typedef union dctl_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + /** Remote Wakeup */ + unsigned rmtwkupsig:1; + /** Soft Disconnect */ + unsigned sftdiscon:1; + /** Global Non-Periodic IN NAK Status */ + unsigned gnpinnaksts:1; + /** Global OUT NAK Status */ + unsigned goutnaksts:1; + /** Test Control */ + unsigned tstctl:3; + /** Set Global Non-Periodic IN NAK */ + unsigned sgnpinnak:1; + /** Clear Global Non-Periodic IN NAK */ + unsigned cgnpinnak:1; + /** Set Global OUT NAK */ + unsigned sgoutnak:1; + /** Clear Global OUT NAK */ + unsigned cgoutnak:1; + /** Power-On Programming Done */ + unsigned pwronprgdone:1; + /** Reserved */ + unsigned reserved:1; + /** Global Multi Count */ + unsigned gmc:2; + /** Ignore Frame Number for ISOC EPs */ + unsigned ifrmnum:1; + /** NAK on Babble */ + unsigned nakonbble:1; + /** Enable Continue on BNA */ + unsigned encontonbna:1; + + unsigned reserved18_31:14; + } b; +} dctl_data_t; + +/** + * This union represents the bit fields in the Device Status + * Register. Read the register into the <i>d32</i> member then + * set/clear the bits using the <i>b</i>it elements. + */ +typedef union dsts_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + /** Suspend Status */ + unsigned suspsts:1; + /** Enumerated Speed */ + unsigned enumspd:2; +#define DWC_DSTS_ENUMSPD_HS_PHY_30MHZ_OR_60MHZ 0 +#define DWC_DSTS_ENUMSPD_FS_PHY_30MHZ_OR_60MHZ 1 +#define DWC_DSTS_ENUMSPD_LS_PHY_6MHZ 2 +#define DWC_DSTS_ENUMSPD_FS_PHY_48MHZ 3 + /** Erratic Error */ + unsigned errticerr:1; + unsigned reserved4_7:4; + /** Frame or Microframe Number of the received SOF */ + unsigned soffn:14; + unsigned reserved22_31:10; + } b; +} dsts_data_t; + +/** + * This union represents the bit fields in the Device IN EP Interrupt + * Register and the Device IN EP Common Mask Register. + * + * - Read the register into the <i>d32</i> member then set/clear the + * bits using the <i>b</i>it elements. + */ +typedef union diepint_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + /** Transfer complete mask */ + unsigned xfercompl:1; + /** Endpoint disable mask */ + unsigned epdisabled:1; + /** AHB Error mask */ + unsigned ahberr:1; + /** TimeOUT Handshake mask (non-ISOC EPs) */ + unsigned timeout:1; + /** IN Token received with TxF Empty mask */ + unsigned intktxfemp:1; + /** IN Token Received with EP mismatch mask */ + unsigned intknepmis:1; + /** IN Endpoint NAK Effective mask */ + unsigned inepnakeff:1; + /** Reserved */ + unsigned emptyintr:1; + + unsigned txfifoundrn:1; + + /** BNA Interrupt mask */ + unsigned bna:1; + + unsigned reserved10_12:3; + /** BNA Interrupt mask */ + unsigned nak:1; + + unsigned reserved14_31:18; + } b; +} diepint_data_t; + +/** + * This union represents the bit fields in the Device IN EP + * Common/Dedicated Interrupt Mask Register. + */ +typedef union diepint_data diepmsk_data_t; + +/** + * This union represents the bit fields in the Device OUT EP Interrupt + * Registerand Device OUT EP Common Interrupt Mask Register. + * + * - Read the register into the <i>d32</i> member then set/clear the + * bits using the <i>b</i>it elements. + */ +typedef union doepint_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + /** Transfer complete */ + unsigned xfercompl:1; + /** Endpoint disable */ + unsigned epdisabled:1; + /** AHB Error */ + unsigned ahberr:1; + /** Setup Phase Done (contorl EPs) */ + unsigned setup:1; + /** OUT Token Received when Endpoint Disabled */ + unsigned outtknepdis:1; + + unsigned stsphsercvd:1; + /** Back-to-Back SETUP Packets Received */ + unsigned back2backsetup:1; + + unsigned reserved7:1; + /** OUT packet Error */ + unsigned outpkterr:1; + /** BNA Interrupt */ + unsigned bna:1; + + unsigned reserved10:1; + /** Packet Drop Status */ + unsigned pktdrpsts:1; + /** Babble Interrupt */ + unsigned babble:1; + /** NAK Interrupt */ + unsigned nak:1; + /** NYET Interrupt */ + unsigned nyet:1; + /** Bit indicating setup packet received */ + unsigned sr:1; + + unsigned reserved16_31:16; + } b; +} doepint_data_t; + +/** + * This union represents the bit fields in the Device OUT EP + * Common/Dedicated Interrupt Mask Register. + */ +typedef union doepint_data doepmsk_data_t; + +/** + * This union represents the bit fields in the Device All EP Interrupt + * and Mask Registers. + * - Read the register into the <i>d32</i> member then set/clear the + * bits using the <i>b</i>it elements. + */ +typedef union daint_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + /** IN Endpoint bits */ + unsigned in:16; + /** OUT Endpoint bits */ + unsigned out:16; + } ep; + struct { + /** IN Endpoint bits */ + unsigned inep0:1; + unsigned inep1:1; + unsigned inep2:1; + unsigned inep3:1; + unsigned inep4:1; + unsigned inep5:1; + unsigned inep6:1; + unsigned inep7:1; + unsigned inep8:1; + unsigned inep9:1; + unsigned inep10:1; + unsigned inep11:1; + unsigned inep12:1; + unsigned inep13:1; + unsigned inep14:1; + unsigned inep15:1; + /** OUT Endpoint bits */ + unsigned outep0:1; + unsigned outep1:1; + unsigned outep2:1; + unsigned outep3:1; + unsigned outep4:1; + unsigned outep5:1; + unsigned outep6:1; + unsigned outep7:1; + unsigned outep8:1; + unsigned outep9:1; + unsigned outep10:1; + unsigned outep11:1; + unsigned outep12:1; + unsigned outep13:1; + unsigned outep14:1; + unsigned outep15:1; + } b; +} daint_data_t; + +/** + * This union represents the bit fields in the Device IN Token Queue + * Read Registers. + * - Read the register into the <i>d32</i> member. + * - READ-ONLY Register + */ +typedef union dtknq1_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + /** In Token Queue Write Pointer */ + unsigned intknwptr:5; + /** Reserved */ + unsigned reserved05_06:2; + /** write pointer has wrapped. */ + unsigned wrap_bit:1; + /** EP Numbers of IN Tokens 0 ... 4 */ + unsigned epnums0_5:24; + } b; +} dtknq1_data_t; + +/** + * This union represents Threshold control Register + * - Read and write the register into the <i>d32</i> member. + * - READ-WRITABLE Register + */ +typedef union dthrctl_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + /** non ISO Tx Thr. Enable */ + unsigned non_iso_thr_en:1; + /** ISO Tx Thr. Enable */ + unsigned iso_thr_en:1; + /** Tx Thr. Length */ + unsigned tx_thr_len:9; + /** AHB Threshold ratio */ + unsigned ahb_thr_ratio:2; + /** Reserved */ + unsigned reserved13_15:3; + /** Rx Thr. Enable */ + unsigned rx_thr_en:1; + /** Rx Thr. Length */ + unsigned rx_thr_len:9; + unsigned reserved26:1; + /** Arbiter Parking Enable*/ + unsigned arbprken:1; + /** Reserved */ + unsigned reserved28_31:4; + } b; +} dthrctl_data_t; + +/** + * Device Logical IN Endpoint-Specific Registers. <i>Offsets + * 900h-AFCh</i> + * + * There will be one set of endpoint registers per logical endpoint + * implemented. + * + * <i>These registers are visible only in Device mode and must not be + * accessed in Host mode, as the results are unknown.</i> + */ +typedef struct dwc_otg_dev_in_ep_regs { + /** Device IN Endpoint Control Register. <i>Offset:900h + + * (ep_num * 20h) + 00h</i> */ + volatile uint32_t diepctl; + /** Reserved. <i>Offset:900h + (ep_num * 20h) + 04h</i> */ + uint32_t reserved04; + /** Device IN Endpoint Interrupt Register. <i>Offset:900h + + * (ep_num * 20h) + 08h</i> */ + volatile uint32_t diepint; + /** Reserved. <i>Offset:900h + (ep_num * 20h) + 0Ch</i> */ + uint32_t reserved0C; + /** Device IN Endpoint Transfer Size + * Register. <i>Offset:900h + (ep_num * 20h) + 10h</i> */ + volatile uint32_t dieptsiz; + /** Device IN Endpoint DMA Address Register. <i>Offset:900h + + * (ep_num * 20h) + 14h</i> */ + volatile uint32_t diepdma; + /** Device IN Endpoint Transmit FIFO Status Register. <i>Offset:900h + + * (ep_num * 20h) + 18h</i> */ + volatile uint32_t dtxfsts; + /** Device IN Endpoint DMA Buffer Register. <i>Offset:900h + + * (ep_num * 20h) + 1Ch</i> */ + volatile uint32_t diepdmab; +} dwc_otg_dev_in_ep_regs_t; + +/** + * Device Logical OUT Endpoint-Specific Registers. <i>Offsets: + * B00h-CFCh</i> + * + * There will be one set of endpoint registers per logical endpoint + * implemented. + * + * <i>These registers are visible only in Device mode and must not be + * accessed in Host mode, as the results are unknown.</i> + */ +typedef struct dwc_otg_dev_out_ep_regs { + /** Device OUT Endpoint Control Register. <i>Offset:B00h + + * (ep_num * 20h) + 00h</i> */ + volatile uint32_t doepctl; + /** Reserved. <i>Offset:B00h + (ep_num * 20h) + 04h</i> */ + uint32_t reserved04; + /** Device OUT Endpoint Interrupt Register. <i>Offset:B00h + + * (ep_num * 20h) + 08h</i> */ + volatile uint32_t doepint; + /** Reserved. <i>Offset:B00h + (ep_num * 20h) + 0Ch</i> */ + uint32_t reserved0C; + /** Device OUT Endpoint Transfer Size Register. <i>Offset: + * B00h + (ep_num * 20h) + 10h</i> */ + volatile uint32_t doeptsiz; + /** Device OUT Endpoint DMA Address Register. <i>Offset:B00h + * + (ep_num * 20h) + 14h</i> */ + volatile uint32_t doepdma; + /** Reserved. <i>Offset:B00h + * (ep_num * 20h) + 18h</i> */ + uint32_t unused; + /** Device OUT Endpoint DMA Buffer Register. <i>Offset:B00h + * + (ep_num * 20h) + 1Ch</i> */ + uint32_t doepdmab; +} dwc_otg_dev_out_ep_regs_t; + +/** + * This union represents the bit fields in the Device EP Control + * Register. Read the register into the <i>d32</i> member then + * set/clear the bits using the <i>b</i>it elements. + */ +typedef union depctl_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + /** Maximum Packet Size + * IN/OUT EPn + * IN/OUT EP0 - 2 bits + * 2'b00: 64 Bytes + * 2'b01: 32 + * 2'b10: 16 + * 2'b11: 8 */ + unsigned mps:11; +#define DWC_DEP0CTL_MPS_64 0 +#define DWC_DEP0CTL_MPS_32 1 +#define DWC_DEP0CTL_MPS_16 2 +#define DWC_DEP0CTL_MPS_8 3 + + /** Next Endpoint + * IN EPn/IN EP0 + * OUT EPn/OUT EP0 - reserved */ + unsigned nextep:4; + + /** USB Active Endpoint */ + unsigned usbactep:1; + + /** Endpoint DPID (INTR/Bulk IN and OUT endpoints) + * This field contains the PID of the packet going to + * be received or transmitted on this endpoint. The + * application should program the PID of the first + * packet going to be received or transmitted on this + * endpoint , after the endpoint is + * activated. Application use the SetD1PID and + * SetD0PID fields of this register to program either + * D0 or D1 PID. + * + * The encoding for this field is + * - 0: D0 + * - 1: D1 + */ + unsigned dpid:1; + + /** NAK Status */ + unsigned naksts:1; + + /** Endpoint Type + * 2'b00: Control + * 2'b01: Isochronous + * 2'b10: Bulk + * 2'b11: Interrupt */ + unsigned eptype:2; + + /** Snoop Mode + * OUT EPn/OUT EP0 + * IN EPn/IN EP0 - reserved */ + unsigned snp:1; + + /** Stall Handshake */ + unsigned stall:1; + + /** Tx Fifo Number + * IN EPn/IN EP0 + * OUT EPn/OUT EP0 - reserved */ + unsigned txfnum:4; + + /** Clear NAK */ + unsigned cnak:1; + /** Set NAK */ + unsigned snak:1; + /** Set DATA0 PID (INTR/Bulk IN and OUT endpoints) + * Writing to this field sets the Endpoint DPID (DPID) + * field in this register to DATA0. Set Even + * (micro)frame (SetEvenFr) (ISO IN and OUT Endpoints) + * Writing to this field sets the Even/Odd + * (micro)frame (EO_FrNum) field to even (micro) + * frame. + */ + unsigned setd0pid:1; + /** Set DATA1 PID (INTR/Bulk IN and OUT endpoints) + * Writing to this field sets the Endpoint DPID (DPID) + * field in this register to DATA1 Set Odd + * (micro)frame (SetOddFr) (ISO IN and OUT Endpoints) + * Writing to this field sets the Even/Odd + * (micro)frame (EO_FrNum) field to odd (micro) frame. + */ + unsigned setd1pid:1; + + /** Endpoint Disable */ + unsigned epdis:1; + /** Endpoint Enable */ + unsigned epena:1; + } b; +} depctl_data_t; + +/** + * This union represents the bit fields in the Device EP Transfer + * Size Register. Read the register into the <i>d32</i> member then + * set/clear the bits using the <i>b</i>it elements. + */ +typedef union deptsiz_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + /** Transfer size */ + unsigned xfersize:19; +/** Max packet count for EP (pow(2,10)-1) */ +#define MAX_PKT_CNT 1023 + /** Packet Count */ + unsigned pktcnt:10; + /** Multi Count - Periodic IN endpoints */ + unsigned mc:2; + unsigned reserved:1; + } b; +} deptsiz_data_t; + +/** + * This union represents the bit fields in the Device EP 0 Transfer + * Size Register. Read the register into the <i>d32</i> member then + * set/clear the bits using the <i>b</i>it elements. + */ +typedef union deptsiz0_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + /** Transfer size */ + unsigned xfersize:7; + /** Reserved */ + unsigned reserved7_18:12; + /** Packet Count */ + unsigned pktcnt:2; + /** Reserved */ + unsigned reserved21_28:8; + /**Setup Packet Count (DOEPTSIZ0 Only) */ + unsigned supcnt:2; + unsigned reserved31; + } b; +} deptsiz0_data_t; + +///////////////////////////////////////////////// +// DMA Descriptor Specific Structures +// + +/** Buffer status definitions */ + +#define BS_HOST_READY 0x0 +#define BS_DMA_BUSY 0x1 +#define BS_DMA_DONE 0x2 +#define BS_HOST_BUSY 0x3 + +/** Receive/Transmit status definitions */ + +#define RTS_SUCCESS 0x0 +#define RTS_BUFFLUSH 0x1 +#define RTS_RESERVED 0x2 +#define RTS_BUFERR 0x3 + +/** + * This union represents the bit fields in the DMA Descriptor + * status quadlet. Read the quadlet into the <i>d32</i> member then + * set/clear the bits using the <i>b</i>it, <i>b_iso_out</i> and + * <i>b_iso_in</i> elements. + */ +typedef union dev_dma_desc_sts { + /** raw register data */ + uint32_t d32; + /** quadlet bits */ + struct { + /** Received number of bytes */ + unsigned bytes:16; + /** NAK bit - only for OUT EPs */ + unsigned nak:1; + unsigned reserved17_22:6; + /** Multiple Transfer - only for OUT EPs */ + unsigned mtrf:1; + /** Setup Packet received - only for OUT EPs */ + unsigned sr:1; + /** Interrupt On Complete */ + unsigned ioc:1; + /** Short Packet */ + unsigned sp:1; + /** Last */ + unsigned l:1; + /** Receive Status */ + unsigned sts:2; + /** Buffer Status */ + unsigned bs:2; + } b; + +//#ifdef DWC_EN_ISOC + /** iso out quadlet bits */ + struct { + /** Received number of bytes */ + unsigned rxbytes:11; + + unsigned reserved11:1; + /** Frame Number */ + unsigned framenum:11; + /** Received ISO Data PID */ + unsigned pid:2; + /** Interrupt On Complete */ + unsigned ioc:1; + /** Short Packet */ + unsigned sp:1; + /** Last */ + unsigned l:1; + /** Receive Status */ + unsigned rxsts:2; + /** Buffer Status */ + unsigned bs:2; + } b_iso_out; + + /** iso in quadlet bits */ + struct { + /** Transmited number of bytes */ + unsigned txbytes:12; + /** Frame Number */ + unsigned framenum:11; + /** Transmited ISO Data PID */ + unsigned pid:2; + /** Interrupt On Complete */ + unsigned ioc:1; + /** Short Packet */ + unsigned sp:1; + /** Last */ + unsigned l:1; + /** Transmit Status */ + unsigned txsts:2; + /** Buffer Status */ + unsigned bs:2; + } b_iso_in; +//#endif /* DWC_EN_ISOC */ +} dev_dma_desc_sts_t; + +/** + * DMA Descriptor structure + * + * DMA Descriptor structure contains two quadlets: + * Status quadlet and Data buffer pointer. + */ +typedef struct dwc_otg_dev_dma_desc { + /** DMA Descriptor status quadlet */ + dev_dma_desc_sts_t status; + /** DMA Descriptor data buffer pointer */ + uint32_t buf; +} dwc_otg_dev_dma_desc_t; + +/** + * The dwc_otg_dev_if structure contains information needed to manage + * the DWC_otg controller acting in device mode. It represents the + * programming view of the device-specific aspects of the controller. + */ +typedef struct dwc_otg_dev_if { + /** Pointer to device Global registers. + * Device Global Registers starting at offset 800h + */ + dwc_otg_device_global_regs_t *dev_global_regs; +#define DWC_DEV_GLOBAL_REG_OFFSET 0x800 + + /** + * Device Logical IN Endpoint-Specific Registers 900h-AFCh + */ + dwc_otg_dev_in_ep_regs_t *in_ep_regs[MAX_EPS_CHANNELS]; +#define DWC_DEV_IN_EP_REG_OFFSET 0x900 +#define DWC_EP_REG_OFFSET 0x20 + + /** Device Logical OUT Endpoint-Specific Registers B00h-CFCh */ + dwc_otg_dev_out_ep_regs_t *out_ep_regs[MAX_EPS_CHANNELS]; +#define DWC_DEV_OUT_EP_REG_OFFSET 0xB00 + + /* Device configuration information */ + uint8_t speed; /**< Device Speed 0: Unknown, 1: LS, 2:FS, 3: HS */ + uint8_t num_in_eps; /**< Number # of Tx EP range: 0-15 exept ep0 */ + uint8_t num_out_eps; /**< Number # of Rx EP range: 0-15 exept ep 0*/ + + /** Size of periodic FIFOs (Bytes) */ + uint16_t perio_tx_fifo_size[MAX_PERIO_FIFOS]; + + /** Size of Tx FIFOs (Bytes) */ + uint16_t tx_fifo_size[MAX_TX_FIFOS]; + + /** Thresholding enable flags and length varaiables **/ + uint16_t rx_thr_en; + uint16_t iso_tx_thr_en; + uint16_t non_iso_tx_thr_en; + + uint16_t rx_thr_length; + uint16_t tx_thr_length; + + /** + * Pointers to the DMA Descriptors for EP0 Control + * transfers (virtual and physical) + */ + + /** 2 descriptors for SETUP packets */ + dwc_dma_t dma_setup_desc_addr[2]; + dwc_otg_dev_dma_desc_t *setup_desc_addr[2]; + + /** Pointer to Descriptor with latest SETUP packet */ + dwc_otg_dev_dma_desc_t *psetup; + + /** Index of current SETUP handler descriptor */ + uint32_t setup_desc_index; + + /** Descriptor for Data In or Status In phases */ + dwc_dma_t dma_in_desc_addr; + dwc_otg_dev_dma_desc_t *in_desc_addr; + + /** Descriptor for Data Out or Status Out phases */ + dwc_dma_t dma_out_desc_addr; + dwc_otg_dev_dma_desc_t *out_desc_addr; + + /** Setup Packet Detected - if set clear NAK when queueing */ + uint32_t spd; + /** Isoc ep pointer on which incomplete happens */ + void *isoc_ep; + +} dwc_otg_dev_if_t; + +///////////////////////////////////////////////// +// Host Mode Register Structures +// +/** + * The Host Global Registers structure defines the size and relative + * field offsets for the Host Mode Global Registers. Host Global + * Registers offsets 400h-7FFh. +*/ +typedef struct dwc_otg_host_global_regs { + /** Host Configuration Register. <i>Offset: 400h</i> */ + volatile uint32_t hcfg; + /** Host Frame Interval Register. <i>Offset: 404h</i> */ + volatile uint32_t hfir; + /** Host Frame Number / Frame Remaining Register. <i>Offset: 408h</i> */ + volatile uint32_t hfnum; + /** Reserved. <i>Offset: 40Ch</i> */ + uint32_t reserved40C; + /** Host Periodic Transmit FIFO/ Queue Status Register. <i>Offset: 410h</i> */ + volatile uint32_t hptxsts; + /** Host All Channels Interrupt Register. <i>Offset: 414h</i> */ + volatile uint32_t haint; + /** Host All Channels Interrupt Mask Register. <i>Offset: 418h</i> */ + volatile uint32_t haintmsk; + /** Host Frame List Base Address Register . <i>Offset: 41Ch</i> */ + volatile uint32_t hflbaddr; +} dwc_otg_host_global_regs_t; + +/** + * This union represents the bit fields in the Host Configuration Register. + * Read the register into the <i>d32</i> member then set/clear the bits using + * the <i>b</i>it elements. Write the <i>d32</i> member to the hcfg register. + */ +typedef union hcfg_data { + /** raw register data */ + uint32_t d32; + + /** register bits */ + struct { + /** FS/LS Phy Clock Select */ + unsigned fslspclksel:2; +#define DWC_HCFG_30_60_MHZ 0 +#define DWC_HCFG_48_MHZ 1 +#define DWC_HCFG_6_MHZ 2 + + /** FS/LS Only Support */ + unsigned fslssupp:1; + unsigned reserved3_6:4; + /** Enable 32-KHz Suspend Mode */ + unsigned ena32khzs:1; + /** Resume Validation Periiod */ + unsigned resvalid:8; + unsigned reserved16_22:7; + /** Enable Scatter/gather DMA in Host mode */ + unsigned descdma:1; + /** Frame List Entries */ + unsigned frlisten:2; + /** Enable Periodic Scheduling */ + unsigned perschedena:1; + unsigned reserved27_30:4; + unsigned modechtimen:1; + } b; +} hcfg_data_t; + +/** + * This union represents the bit fields in the Host Frame Remaing/Number + * Register. + */ +typedef union hfir_data { + /** raw register data */ + uint32_t d32; + + /** register bits */ + struct { + unsigned frint:16; + unsigned hfirrldctrl:1; + unsigned reserved:15; + } b; +} hfir_data_t; + +/** + * This union represents the bit fields in the Host Frame Remaing/Number + * Register. + */ +typedef union hfnum_data { + /** raw register data */ + uint32_t d32; + + /** register bits */ + struct { + unsigned frnum:16; +#define DWC_HFNUM_MAX_FRNUM 0x3FFF + unsigned frrem:16; + } b; +} hfnum_data_t; + +typedef union hptxsts_data { + /** raw register data */ + uint32_t d32; + + /** register bits */ + struct { + unsigned ptxfspcavail:16; + unsigned ptxqspcavail:8; + /** Top of the Periodic Transmit Request Queue + * - bit 24 - Terminate (last entry for the selected channel) + * - bits 26:25 - Token Type + * - 2'b00 - Zero length + * - 2'b01 - Ping + * - 2'b10 - Disable + * - bits 30:27 - Channel Number + * - bit 31 - Odd/even microframe + */ + unsigned ptxqtop_terminate:1; + unsigned ptxqtop_token:2; + unsigned ptxqtop_chnum:4; + unsigned ptxqtop_odd:1; + } b; +} hptxsts_data_t; + +/** + * This union represents the bit fields in the Host Port Control and Status + * Register. Read the register into the <i>d32</i> member then set/clear the + * bits using the <i>b</i>it elements. Write the <i>d32</i> member to the + * hprt0 register. + */ +typedef union hprt0_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + unsigned prtconnsts:1; + unsigned prtconndet:1; + unsigned prtena:1; + unsigned prtenchng:1; + unsigned prtovrcurract:1; + unsigned prtovrcurrchng:1; + unsigned prtres:1; + unsigned prtsusp:1; + unsigned prtrst:1; + unsigned reserved9:1; + unsigned prtlnsts:2; + unsigned prtpwr:1; + unsigned prttstctl:4; + unsigned prtspd:2; +#define DWC_HPRT0_PRTSPD_HIGH_SPEED 0 +#define DWC_HPRT0_PRTSPD_FULL_SPEED 1 +#define DWC_HPRT0_PRTSPD_LOW_SPEED 2 + unsigned reserved19_31:13; + } b; +} hprt0_data_t; + +/** + * This union represents the bit fields in the Host All Interrupt + * Register. + */ +typedef union haint_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + unsigned ch0:1; + unsigned ch1:1; + unsigned ch2:1; + unsigned ch3:1; + unsigned ch4:1; + unsigned ch5:1; + unsigned ch6:1; + unsigned ch7:1; + unsigned ch8:1; + unsigned ch9:1; + unsigned ch10:1; + unsigned ch11:1; + unsigned ch12:1; + unsigned ch13:1; + unsigned ch14:1; + unsigned ch15:1; + unsigned reserved:16; + } b; + + struct { + unsigned chint:16; + unsigned reserved:16; + } b2; +} haint_data_t; + +/** + * This union represents the bit fields in the Host All Interrupt + * Register. + */ +typedef union haintmsk_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + unsigned ch0:1; + unsigned ch1:1; + unsigned ch2:1; + unsigned ch3:1; + unsigned ch4:1; + unsigned ch5:1; + unsigned ch6:1; + unsigned ch7:1; + unsigned ch8:1; + unsigned ch9:1; + unsigned ch10:1; + unsigned ch11:1; + unsigned ch12:1; + unsigned ch13:1; + unsigned ch14:1; + unsigned ch15:1; + unsigned reserved:16; + } b; + + struct { + unsigned chint:16; + unsigned reserved:16; + } b2; +} haintmsk_data_t; + +/** + * Host Channel Specific Registers. <i>500h-5FCh</i> + */ +typedef struct dwc_otg_hc_regs { + /** Host Channel 0 Characteristic Register. <i>Offset: 500h + (chan_num * 20h) + 00h</i> */ + volatile uint32_t hcchar; + /** Host Channel 0 Split Control Register. <i>Offset: 500h + (chan_num * 20h) + 04h</i> */ + volatile uint32_t hcsplt; + /** Host Channel 0 Interrupt Register. <i>Offset: 500h + (chan_num * 20h) + 08h</i> */ + volatile uint32_t hcint; + /** Host Channel 0 Interrupt Mask Register. <i>Offset: 500h + (chan_num * 20h) + 0Ch</i> */ + volatile uint32_t hcintmsk; + /** Host Channel 0 Transfer Size Register. <i>Offset: 500h + (chan_num * 20h) + 10h</i> */ + volatile uint32_t hctsiz; + /** Host Channel 0 DMA Address Register. <i>Offset: 500h + (chan_num * 20h) + 14h</i> */ + volatile uint32_t hcdma; + volatile uint32_t reserved; + /** Host Channel 0 DMA Buffer Address Register. <i>Offset: 500h + (chan_num * 20h) + 1Ch</i> */ + volatile uint32_t hcdmab; +} dwc_otg_hc_regs_t; + +/** + * This union represents the bit fields in the Host Channel Characteristics + * Register. Read the register into the <i>d32</i> member then set/clear the + * bits using the <i>b</i>it elements. Write the <i>d32</i> member to the + * hcchar register. + */ +typedef union hcchar_data { + /** raw register data */ + uint32_t d32; + + /** register bits */ + struct { + /** Maximum packet size in bytes */ + unsigned mps:11; + + /** Endpoint number */ + unsigned epnum:4; + + /** 0: OUT, 1: IN */ + unsigned epdir:1; + + unsigned reserved:1; + + /** 0: Full/high speed device, 1: Low speed device */ + unsigned lspddev:1; + + /** 0: Control, 1: Isoc, 2: Bulk, 3: Intr */ + unsigned eptype:2; + + /** Packets per frame for periodic transfers. 0 is reserved. */ + unsigned multicnt:2; + + /** Device address */ + unsigned devaddr:7; + + /** + * Frame to transmit periodic transaction. + * 0: even, 1: odd + */ + unsigned oddfrm:1; + + /** Channel disable */ + unsigned chdis:1; + + /** Channel enable */ + unsigned chen:1; + } b; +} hcchar_data_t; + +typedef union hcsplt_data { + /** raw register data */ + uint32_t d32; + + /** register bits */ + struct { + /** Port Address */ + unsigned prtaddr:7; + + /** Hub Address */ + unsigned hubaddr:7; + + /** Transaction Position */ + unsigned xactpos:2; +#define DWC_HCSPLIT_XACTPOS_MID 0 +#define DWC_HCSPLIT_XACTPOS_END 1 +#define DWC_HCSPLIT_XACTPOS_BEGIN 2 +#define DWC_HCSPLIT_XACTPOS_ALL 3 + + /** Do Complete Split */ + unsigned compsplt:1; + + /** Reserved */ + unsigned reserved:14; + + /** Split Enble */ + unsigned spltena:1; + } b; +} hcsplt_data_t; + +/** + * This union represents the bit fields in the Host All Interrupt + * Register. + */ +typedef union hcint_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + /** Transfer Complete */ + unsigned xfercomp:1; + /** Channel Halted */ + unsigned chhltd:1; + /** AHB Error */ + unsigned ahberr:1; + /** STALL Response Received */ + unsigned stall:1; + /** NAK Response Received */ + unsigned nak:1; + /** ACK Response Received */ + unsigned ack:1; + /** NYET Response Received */ + unsigned nyet:1; + /** Transaction Err */ + unsigned xacterr:1; + /** Babble Error */ + unsigned bblerr:1; + /** Frame Overrun */ + unsigned frmovrun:1; + /** Data Toggle Error */ + unsigned datatglerr:1; + /** Buffer Not Available (only for DDMA mode) */ + unsigned bna:1; + /** Exessive transaction error (only for DDMA mode) */ + unsigned xcs_xact:1; + /** Frame List Rollover interrupt */ + unsigned frm_list_roll:1; + /** Reserved */ + unsigned reserved14_31:18; + } b; +} hcint_data_t; + +/** + * This union represents the bit fields in the Host Channel Interrupt Mask + * Register. Read the register into the <i>d32</i> member then set/clear the + * bits using the <i>b</i>it elements. Write the <i>d32</i> member to the + * hcintmsk register. + */ +typedef union hcintmsk_data { + /** raw register data */ + uint32_t d32; + + /** register bits */ + struct { + unsigned xfercompl:1; + unsigned chhltd:1; + unsigned ahberr:1; + unsigned stall:1; + unsigned nak:1; + unsigned ack:1; + unsigned nyet:1; + unsigned xacterr:1; + unsigned bblerr:1; + unsigned frmovrun:1; + unsigned datatglerr:1; + unsigned bna:1; + unsigned xcs_xact:1; + unsigned frm_list_roll:1; + unsigned reserved14_31:18; + } b; +} hcintmsk_data_t; + +/** + * This union represents the bit fields in the Host Channel Transfer Size + * Register. Read the register into the <i>d32</i> member then set/clear the + * bits using the <i>b</i>it elements. Write the <i>d32</i> member to the + * hcchar register. + */ + +typedef union hctsiz_data { + /** raw register data */ + uint32_t d32; + + /** register bits */ + struct { + /** Total transfer size in bytes */ + unsigned xfersize:19; + + /** Data packets to transfer */ + unsigned pktcnt:10; + + /** + * Packet ID for next data packet + * 0: DATA0 + * 1: DATA2 + * 2: DATA1 + * 3: MDATA (non-Control), SETUP (Control) + */ + unsigned pid:2; +#define DWC_HCTSIZ_DATA0 0 +#define DWC_HCTSIZ_DATA1 2 +#define DWC_HCTSIZ_DATA2 1 +#define DWC_HCTSIZ_MDATA 3 +#define DWC_HCTSIZ_SETUP 3 + + /** Do PING protocol when 1 */ + unsigned dopng:1; + } b; + + /** register bits */ + struct { + /** Scheduling information */ + unsigned schinfo:8; + + /** Number of transfer descriptors. + * Max value: + * 64 in general, + * 256 only for HS isochronous endpoint. + */ + unsigned ntd:8; + + /** Data packets to transfer */ + unsigned reserved16_28:13; + + /** + * Packet ID for next data packet + * 0: DATA0 + * 1: DATA2 + * 2: DATA1 + * 3: MDATA (non-Control) + */ + unsigned pid:2; + + /** Do PING protocol when 1 */ + unsigned dopng:1; + } b_ddma; +} hctsiz_data_t; + +/** + * This union represents the bit fields in the Host DMA Address + * Register used in Descriptor DMA mode. + */ +typedef union hcdma_data { + /** raw register data */ + uint32_t d32; + /** register bits */ + struct { + unsigned reserved0_2:3; + /** Current Transfer Descriptor. Not used for ISOC */ + unsigned ctd:8; + /** Start Address of Descriptor List */ + unsigned dma_addr:21; + } b; +} hcdma_data_t; + +/** + * This union represents the bit fields in the DMA Descriptor + * status quadlet for host mode. Read the quadlet into the <i>d32</i> member then + * set/clear the bits using the <i>b</i>it elements. + */ +typedef union host_dma_desc_sts { + /** raw register data */ + uint32_t d32; + /** quadlet bits */ + + /* for non-isochronous */ + struct { + /** Number of bytes */ + unsigned n_bytes:17; + /** QTD offset to jump when Short Packet received - only for IN EPs */ + unsigned qtd_offset:6; + /** + * Set to request the core to jump to alternate QTD if + * Short Packet received - only for IN EPs + */ + unsigned a_qtd:1; + /** + * Setup Packet bit. When set indicates that buffer contains + * setup packet. + */ + unsigned sup:1; + /** Interrupt On Complete */ + unsigned ioc:1; + /** End of List */ + unsigned eol:1; + unsigned reserved27:1; + /** Rx/Tx Status */ + unsigned sts:2; +#define DMA_DESC_STS_PKTERR 1 + unsigned reserved30:1; + /** Active Bit */ + unsigned a:1; + } b; + /* for isochronous */ + struct { + /** Number of bytes */ + unsigned n_bytes:12; + unsigned reserved12_24:13; + /** Interrupt On Complete */ + unsigned ioc:1; + unsigned reserved26_27:2; + /** Rx/Tx Status */ + unsigned sts:2; + unsigned reserved30:1; + /** Active Bit */ + unsigned a:1; + } b_isoc; +} host_dma_desc_sts_t; + +#define MAX_DMA_DESC_SIZE 131071 +#define MAX_DMA_DESC_NUM_GENERIC 64 +#define MAX_DMA_DESC_NUM_HS_ISOC 256 +#define MAX_FRLIST_EN_NUM 64 +/** + * Host-mode DMA Descriptor structure + * + * DMA Descriptor structure contains two quadlets: + * Status quadlet and Data buffer pointer. + */ +typedef struct dwc_otg_host_dma_desc { + /** DMA Descriptor status quadlet */ + host_dma_desc_sts_t status; + /** DMA Descriptor data buffer pointer */ + uint32_t buf; +} dwc_otg_host_dma_desc_t; + +/** OTG Host Interface Structure. + * + * The OTG Host Interface Structure structure contains information + * needed to manage the DWC_otg controller acting in host mode. It + * represents the programming view of the host-specific aspects of the + * controller. + */ +typedef struct dwc_otg_host_if { + /** Host Global Registers starting at offset 400h.*/ + dwc_otg_host_global_regs_t *host_global_regs; +#define DWC_OTG_HOST_GLOBAL_REG_OFFSET 0x400 + + /** Host Port 0 Control and Status Register */ + volatile uint32_t *hprt0; +#define DWC_OTG_HOST_PORT_REGS_OFFSET 0x440 + + /** Host Channel Specific Registers at offsets 500h-5FCh. */ + dwc_otg_hc_regs_t *hc_regs[MAX_EPS_CHANNELS]; +#define DWC_OTG_HOST_CHAN_REGS_OFFSET 0x500 +#define DWC_OTG_CHAN_REGS_OFFSET 0x20 + + /* Host configuration information */ + /** Number of Host Channels (range: 1-16) */ + uint8_t num_host_channels; + /** Periodic EPs supported (0: no, 1: yes) */ + uint8_t perio_eps_supported; + /** Periodic Tx FIFO Size (Only 1 host periodic Tx FIFO) */ + uint16_t perio_tx_fifo_size; + +} dwc_otg_host_if_t; + +/** + * This union represents the bit fields in the Power and Clock Gating Control + * Register. Read the register into the <i>d32</i> member then set/clear the + * bits using the <i>b</i>it elements. + */ +typedef union pcgcctl_data { + /** raw register data */ + uint32_t d32; + + /** register bits */ + struct { + /** Stop Pclk */ + unsigned stoppclk:1; + /** Gate Hclk */ + unsigned gatehclk:1; + /** Power Clamp */ + unsigned pwrclmp:1; + /** Reset Power Down Modules */ + unsigned rstpdwnmodule:1; + /** Reserved */ + unsigned reserved:1; + /** Enable Sleep Clock Gating (Enbl_L1Gating) */ + unsigned enbl_sleep_gating:1; + /** PHY In Sleep (PhySleep) */ + unsigned phy_in_sleep:1; + /** Deep Sleep*/ + unsigned deep_sleep:1; + unsigned resetaftsusp:1; + unsigned restoremode:1; + unsigned enbl_extnd_hiber:1; + unsigned extnd_hiber_pwrclmp:1; + unsigned extnd_hiber_switch:1; + unsigned ess_reg_restored:1; + unsigned prt_clk_sel:2; + unsigned port_power:1; + unsigned max_xcvrselect:2; + unsigned max_termsel:1; + unsigned mac_dev_addr:7; + unsigned p2hd_dev_enum_spd:2; + unsigned p2hd_prt_spd:2; + unsigned if_dev_mode:1; + } b; +} pcgcctl_data_t; + +/** + * This union represents the bit fields in the Global Data FIFO Software + * Configuration Register. Read the register into the <i>d32</i> member then + * set/clear the bits using the <i>b</i>it elements. + */ +typedef union gdfifocfg_data { + /* raw register data */ + uint32_t d32; + /** register bits */ + struct { + /** OTG Data FIFO depth */ + unsigned gdfifocfg:16; + /** Start address of EP info controller */ + unsigned epinfobase:16; + } b; +} gdfifocfg_data_t; + +/** + * This union represents the bit fields in the Global Power Down Register + * Register. Read the register into the <i>d32</i> member then set/clear the + * bits using the <i>b</i>it elements. + */ +typedef union gpwrdn_data { + /* raw register data */ + uint32_t d32; + + /** register bits */ + struct { + /** PMU Interrupt Select */ + unsigned pmuintsel:1; + /** PMU Active */ + unsigned pmuactv:1; + /** Restore */ + unsigned restore:1; + /** Power Down Clamp */ + unsigned pwrdnclmp:1; + /** Power Down Reset */ + unsigned pwrdnrstn:1; + /** Power Down Switch */ + unsigned pwrdnswtch:1; + /** Disable VBUS */ + unsigned dis_vbus:1; + /** Line State Change */ + unsigned lnstschng:1; + /** Line state change mask */ + unsigned lnstchng_msk:1; + /** Reset Detected */ + unsigned rst_det:1; + /** Reset Detect mask */ + unsigned rst_det_msk:1; + /** Disconnect Detected */ + unsigned disconn_det:1; + /** Disconnect Detect mask */ + unsigned disconn_det_msk:1; + /** Connect Detected*/ + unsigned connect_det:1; + /** Connect Detected Mask*/ + unsigned connect_det_msk:1; + /** SRP Detected */ + unsigned srp_det:1; + /** SRP Detect mask */ + unsigned srp_det_msk:1; + /** Status Change Interrupt */ + unsigned sts_chngint:1; + /** Status Change Interrupt Mask */ + unsigned sts_chngint_msk:1; + /** Line State */ + unsigned linestate:2; + /** Indicates current mode(status of IDDIG signal) */ + unsigned idsts:1; + /** B Session Valid signal status*/ + unsigned bsessvld:1; + /** ADP Event Detected */ + unsigned adp_int:1; + /** Multi Valued ID pin */ + unsigned mult_val_id_bc:5; + /** Reserved 24_31 */ + unsigned reserved29_31:3; + } b; +} gpwrdn_data_t; + +#endif diff --git a/drivers/usb/host/dwc_otg/test/Makefile b/drivers/usb/host/dwc_otg/test/Makefile new file mode 100644 index 00000000000000..fc453759dea3e8 --- /dev/null +++ b/drivers/usb/host/dwc_otg/test/Makefile @@ -0,0 +1,16 @@ + +PERL=/usr/bin/perl +PL_TESTS=test_sysfs.pl test_mod_param.pl + +.PHONY : test +test : perl_tests + +perl_tests : + @echo + @echo Running perl tests + @for test in $(PL_TESTS); do \ + if $(PERL) ./$$test ; then \ + echo "=======> $$test, PASSED" ; \ + else echo "=======> $$test, FAILED" ; \ + fi \ + done diff --git a/drivers/usb/host/dwc_otg/test/dwc_otg_test.pm b/drivers/usb/host/dwc_otg/test/dwc_otg_test.pm new file mode 100644 index 00000000000000..85e55fd6ddbc78 --- /dev/null +++ b/drivers/usb/host/dwc_otg/test/dwc_otg_test.pm @@ -0,0 +1,337 @@ +package dwc_otg_test; + +use strict; +use Exporter (); + +use vars qw(@ISA @EXPORT +$sysfsdir $paramdir $errors $params +); + +@ISA = qw(Exporter); + +# +# Globals +# +$sysfsdir = "/sys/devices/lm0"; +$paramdir = "/sys/module/dwc_otg"; +$errors = 0; + +$params = [ + { + NAME => "otg_cap", + DEFAULT => 0, + ENUM => [], + LOW => 0, + HIGH => 2 + }, + { + NAME => "dma_enable", + DEFAULT => 0, + ENUM => [], + LOW => 0, + HIGH => 1 + }, + { + NAME => "dma_burst_size", + DEFAULT => 32, + ENUM => [1, 4, 8, 16, 32, 64, 128, 256], + LOW => 1, + HIGH => 256 + }, + { + NAME => "host_speed", + DEFAULT => 0, + ENUM => [], + LOW => 0, + HIGH => 1 + }, + { + NAME => "host_support_fs_ls_low_power", + DEFAULT => 0, + ENUM => [], + LOW => 0, + HIGH => 1 + }, + { + NAME => "host_ls_low_power_phy_clk", + DEFAULT => 0, + ENUM => [], + LOW => 0, + HIGH => 1 + }, + { + NAME => "dev_speed", + DEFAULT => 0, + ENUM => [], + LOW => 0, + HIGH => 1 + }, + { + NAME => "enable_dynamic_fifo", + DEFAULT => 1, + ENUM => [], + LOW => 0, + HIGH => 1 + }, + { + NAME => "data_fifo_size", + DEFAULT => 8192, + ENUM => [], + LOW => 32, + HIGH => 32768 + }, + { + NAME => "dev_rx_fifo_size", + DEFAULT => 1064, + ENUM => [], + LOW => 16, + HIGH => 32768 + }, + { + NAME => "dev_nperio_tx_fifo_size", + DEFAULT => 1024, + ENUM => [], + LOW => 16, + HIGH => 32768 + }, + { + NAME => "dev_perio_tx_fifo_size_1", + DEFAULT => 256, + ENUM => [], + LOW => 4, + HIGH => 768 + }, + { + NAME => "dev_perio_tx_fifo_size_2", + DEFAULT => 256, + ENUM => [], + LOW => 4, + HIGH => 768 + }, + { + NAME => "dev_perio_tx_fifo_size_3", + DEFAULT => 256, + ENUM => [], + LOW => 4, + HIGH => 768 + }, + { + NAME => "dev_perio_tx_fifo_size_4", + DEFAULT => 256, + ENUM => [], + LOW => 4, + HIGH => 768 + }, + { + NAME => "dev_perio_tx_fifo_size_5", + DEFAULT => 256, + ENUM => [], + LOW => 4, + HIGH => 768 + }, + { + NAME => "dev_perio_tx_fifo_size_6", + DEFAULT => 256, + ENUM => [], + LOW => 4, + HIGH => 768 + }, + { + NAME => "dev_perio_tx_fifo_size_7", + DEFAULT => 256, + ENUM => [], + LOW => 4, + HIGH => 768 + }, + { + NAME => "dev_perio_tx_fifo_size_8", + DEFAULT => 256, + ENUM => [], + LOW => 4, + HIGH => 768 + }, + { + NAME => "dev_perio_tx_fifo_size_9", + DEFAULT => 256, + ENUM => [], + LOW => 4, + HIGH => 768 + }, + { + NAME => "dev_perio_tx_fifo_size_10", + DEFAULT => 256, + ENUM => [], + LOW => 4, + HIGH => 768 + }, + { + NAME => "dev_perio_tx_fifo_size_11", + DEFAULT => 256, + ENUM => [], + LOW => 4, + HIGH => 768 + }, + { + NAME => "dev_perio_tx_fifo_size_12", + DEFAULT => 256, + ENUM => [], + LOW => 4, + HIGH => 768 + }, + { + NAME => "dev_perio_tx_fifo_size_13", + DEFAULT => 256, + ENUM => [], + LOW => 4, + HIGH => 768 + }, + { + NAME => "dev_perio_tx_fifo_size_14", + DEFAULT => 256, + ENUM => [], + LOW => 4, + HIGH => 768 + }, + { + NAME => "dev_perio_tx_fifo_size_15", + DEFAULT => 256, + ENUM => [], + LOW => 4, + HIGH => 768 + }, + { + NAME => "host_rx_fifo_size", + DEFAULT => 1024, + ENUM => [], + LOW => 16, + HIGH => 32768 + }, + { + NAME => "host_nperio_tx_fifo_size", + DEFAULT => 1024, + ENUM => [], + LOW => 16, + HIGH => 32768 + }, + { + NAME => "host_perio_tx_fifo_size", + DEFAULT => 1024, + ENUM => [], + LOW => 16, + HIGH => 32768 + }, + { + NAME => "max_transfer_size", + DEFAULT => 65535, + ENUM => [], + LOW => 2047, + HIGH => 65535 + }, + { + NAME => "max_packet_count", + DEFAULT => 511, + ENUM => [], + LOW => 15, + HIGH => 511 + }, + { + NAME => "host_channels", + DEFAULT => 12, + ENUM => [], + LOW => 1, + HIGH => 16 + }, + { + NAME => "dev_endpoints", + DEFAULT => 6, + ENUM => [], + LOW => 1, + HIGH => 15 + }, + { + NAME => "phy_type", + DEFAULT => 1, + ENUM => [], + LOW => 0, + HIGH => 2 + }, + { + NAME => "phy_utmi_width", + DEFAULT => 16, + ENUM => [8, 16], + LOW => 8, + HIGH => 16 + }, + { + NAME => "phy_ulpi_ddr", + DEFAULT => 0, + ENUM => [], + LOW => 0, + HIGH => 1 + }, + ]; + + +# +# +sub check_arch { + $_ = `uname -m`; + chomp; + unless (m/armv4tl/) { + warn "# \n# Can't execute on $_. Run on integrator platform.\n# \n"; + return 0; + } + return 1; +} + +# +# +sub load_module { + my $params = shift; + print "\nRemoving Module\n"; + system "rmmod dwc_otg"; + print "Loading Module\n"; + if ($params ne "") { + print "Module Parameters: $params\n"; + } + if (system("modprobe dwc_otg $params")) { + warn "Unable to load module\n"; + return 0; + } + return 1; +} + +# +# +sub test_status { + my $arg = shift; + + print "\n"; + + if (defined $arg) { + warn "WARNING: $arg\n"; + } + + if ($errors > 0) { + warn "TEST FAILED with $errors errors\n"; + return 0; + } else { + print "TEST PASSED\n"; + return 0 if (defined $arg); + } + return 1; +} + +# +# +@EXPORT = qw( +$sysfsdir +$paramdir +$params +$errors +check_arch +load_module +test_status +); + +1; diff --git a/drivers/usb/host/dwc_otg/test/test_mod_param.pl b/drivers/usb/host/dwc_otg/test/test_mod_param.pl new file mode 100644 index 00000000000000..dc3820df577bae --- /dev/null +++ b/drivers/usb/host/dwc_otg/test/test_mod_param.pl @@ -0,0 +1,133 @@ +#!/usr/bin/perl -w +# +# Run this program on the integrator. +# +# - Tests module parameter default values. +# - Tests setting of valid module parameter values via modprobe. +# - Tests invalid module parameter values. +# ----------------------------------------------------------------------------- +use strict; +use dwc_otg_test; + +check_arch() or die; + +# +# +sub test { + my ($param,$expected) = @_; + my $value = get($param); + + if ($value == $expected) { + print "$param = $value, okay\n"; + } + + else { + warn "ERROR: value of $param != $expected, $value\n"; + $errors ++; + } +} + +# +# +sub get { + my $param = shift; + my $tmp = `cat $paramdir/$param`; + chomp $tmp; + return $tmp; +} + +# +# +sub test_main { + + print "\nTesting Module Parameters\n"; + + load_module("") or die; + + # Test initial values + print "\nTesting Default Values\n"; + foreach (@{$params}) { + test ($_->{NAME}, $_->{DEFAULT}); + } + + # Test low value + print "\nTesting Low Value\n"; + my $cmd_params = ""; + foreach (@{$params}) { + $cmd_params = $cmd_params . "$_->{NAME}=$_->{LOW} "; + } + load_module($cmd_params) or die; + + foreach (@{$params}) { + test ($_->{NAME}, $_->{LOW}); + } + + # Test high value + print "\nTesting High Value\n"; + $cmd_params = ""; + foreach (@{$params}) { + $cmd_params = $cmd_params . "$_->{NAME}=$_->{HIGH} "; + } + load_module($cmd_params) or die; + + foreach (@{$params}) { + test ($_->{NAME}, $_->{HIGH}); + } + + # Test Enum + print "\nTesting Enumerated\n"; + foreach (@{$params}) { + if (defined $_->{ENUM}) { + my $value; + foreach $value (@{$_->{ENUM}}) { + $cmd_params = "$_->{NAME}=$value"; + load_module($cmd_params) or die; + test ($_->{NAME}, $value); + } + } + } + + # Test Invalid Values + print "\nTesting Invalid Values\n"; + $cmd_params = ""; + foreach (@{$params}) { + $cmd_params = $cmd_params . sprintf "$_->{NAME}=%d ", $_->{LOW}-1; + } + load_module($cmd_params) or die; + + foreach (@{$params}) { + test ($_->{NAME}, $_->{DEFAULT}); + } + + $cmd_params = ""; + foreach (@{$params}) { + $cmd_params = $cmd_params . sprintf "$_->{NAME}=%d ", $_->{HIGH}+1; + } + load_module($cmd_params) or die; + + foreach (@{$params}) { + test ($_->{NAME}, $_->{DEFAULT}); + } + + print "\nTesting Enumerated\n"; + foreach (@{$params}) { + if (defined $_->{ENUM}) { + my $value; + foreach $value (@{$_->{ENUM}}) { + $value = $value + 1; + $cmd_params = "$_->{NAME}=$value"; + load_module($cmd_params) or die; + test ($_->{NAME}, $_->{DEFAULT}); + $value = $value - 2; + $cmd_params = "$_->{NAME}=$value"; + load_module($cmd_params) or die; + test ($_->{NAME}, $_->{DEFAULT}); + } + } + } + + test_status() or die; +} + +test_main(); +0; diff --git a/drivers/usb/host/dwc_otg/test/test_sysfs.pl b/drivers/usb/host/dwc_otg/test/test_sysfs.pl new file mode 100644 index 00000000000000..cdc9963176e5a4 --- /dev/null +++ b/drivers/usb/host/dwc_otg/test/test_sysfs.pl @@ -0,0 +1,193 @@ +#!/usr/bin/perl -w +# +# Run this program on the integrator +# - Tests select sysfs attributes. +# - Todo ... test more attributes, hnp/srp, buspower/bussuspend, etc. +# ----------------------------------------------------------------------------- +use strict; +use dwc_otg_test; + +check_arch() or die; + +# +# +sub test { + my ($attr,$expected) = @_; + my $string = get($attr); + + if ($string eq $expected) { + printf("$attr = $string, okay\n"); + } + else { + warn "ERROR: value of $attr != $expected, $string\n"; + $errors ++; + } +} + +# +# +sub set { + my ($reg, $value) = @_; + system "echo $value > $sysfsdir/$reg"; +} + +# +# +sub get { + my $attr = shift; + my $string = `cat $sysfsdir/$attr`; + chomp $string; + if ($string =~ m/\s\=\s/) { + my $tmp; + ($tmp, $string) = split /\s=\s/, $string; + } + return $string; +} + +# +# +sub test_main { + print("\nTesting Sysfs Attributes\n"); + + load_module("") or die; + + # Test initial values of regoffset/regvalue/guid/gsnpsid + print("\nTesting Default Values\n"); + + test("regoffset", "0xffffffff"); + test("regvalue", "invalid offset"); + test("guid", "0x12345678"); # this will fail if it has been changed + test("gsnpsid", "0x4f54200a"); + + # Test operation of regoffset/regvalue + print("\nTesting regoffset\n"); + set('regoffset', '5a5a5a5a'); + test("regoffset", "0xffffffff"); + + set('regoffset', '0'); + test("regoffset", "0x00000000"); + + set('regoffset', '40000'); + test("regoffset", "0x00000000"); + + set('regoffset', '3ffff'); + test("regoffset", "0x0003ffff"); + + set('regoffset', '1'); + test("regoffset", "0x00000001"); + + print("\nTesting regvalue\n"); + set('regoffset', '3c'); + test("regvalue", "0x12345678"); + set('regvalue', '5a5a5a5a'); + test("regvalue", "0x5a5a5a5a"); + set('regvalue','a5a5a5a5'); + test("regvalue", "0xa5a5a5a5"); + set('guid','12345678'); + + # Test HNP Capable + print("\nTesting HNP Capable bit\n"); + set('hnpcapable', '1'); + test("hnpcapable", "0x1"); + set('hnpcapable','0'); + test("hnpcapable", "0x0"); + + set('regoffset','0c'); + + my $old = get('gusbcfg'); + print("setting hnpcapable\n"); + set('hnpcapable', '1'); + test("hnpcapable", "0x1"); + test('gusbcfg', sprintf "0x%08x", (oct ($old) | (1<<9))); + test('regvalue', sprintf "0x%08x", (oct ($old) | (1<<9))); + + $old = get('gusbcfg'); + print("clearing hnpcapable\n"); + set('hnpcapable', '0'); + test("hnpcapable", "0x0"); + test ('gusbcfg', sprintf "0x%08x", oct ($old) & (~(1<<9))); + test ('regvalue', sprintf "0x%08x", oct ($old) & (~(1<<9))); + + # Test SRP Capable + print("\nTesting SRP Capable bit\n"); + set('srpcapable', '1'); + test("srpcapable", "0x1"); + set('srpcapable','0'); + test("srpcapable", "0x0"); + + set('regoffset','0c'); + + $old = get('gusbcfg'); + print("setting srpcapable\n"); + set('srpcapable', '1'); + test("srpcapable", "0x1"); + test('gusbcfg', sprintf "0x%08x", (oct ($old) | (1<<8))); + test('regvalue', sprintf "0x%08x", (oct ($old) | (1<<8))); + + $old = get('gusbcfg'); + print("clearing srpcapable\n"); + set('srpcapable', '0'); + test("srpcapable", "0x0"); + test('gusbcfg', sprintf "0x%08x", oct ($old) & (~(1<<8))); + test('regvalue', sprintf "0x%08x", oct ($old) & (~(1<<8))); + + # Test GGPIO + print("\nTesting GGPIO\n"); + set('ggpio','5a5a5a5a'); + test('ggpio','0x5a5a0000'); + set('ggpio','a5a5a5a5'); + test('ggpio','0xa5a50000'); + set('ggpio','11110000'); + test('ggpio','0x11110000'); + set('ggpio','00001111'); + test('ggpio','0x00000000'); + + # Test DEVSPEED + print("\nTesting DEVSPEED\n"); + set('regoffset','800'); + $old = get('regvalue'); + set('devspeed','0'); + test('devspeed','0x0'); + test('regvalue',sprintf("0x%08x", oct($old) & ~(0x3))); + set('devspeed','1'); + test('devspeed','0x1'); + test('regvalue',sprintf("0x%08x", oct($old) & ~(0x3) | 1)); + set('devspeed','2'); + test('devspeed','0x2'); + test('regvalue',sprintf("0x%08x", oct($old) & ~(0x3) | 2)); + set('devspeed','3'); + test('devspeed','0x3'); + test('regvalue',sprintf("0x%08x", oct($old) & ~(0x3) | 3)); + set('devspeed','4'); + test('devspeed','0x0'); + test('regvalue',sprintf("0x%08x", oct($old) & ~(0x3))); + set('devspeed','5'); + test('devspeed','0x1'); + test('regvalue',sprintf("0x%08x", oct($old) & ~(0x3) | 1)); + + + # mode Returns the current mode:0 for device mode1 for host mode Read + # hnp Initiate the Host Negotiation Protocol. Read returns the status. Read/Write + # srp Initiate the Session Request Protocol. Read returns the status. Read/Write + # buspower Get or Set the Power State of the bus (0 - Off or 1 - On) Read/Write + # bussuspend Suspend the USB bus. Read/Write + # busconnected Get the connection status of the bus Read + + # gotgctl Get or set the Core Control Status Register. Read/Write + ## gusbcfg Get or set the Core USB Configuration Register Read/Write + # grxfsiz Get or set the Receive FIFO Size Register Read/Write + # gnptxfsiz Get or set the non-periodic Transmit Size Register Read/Write + # gpvndctl Get or set the PHY Vendor Control Register Read/Write + ## ggpio Get the value in the lower 16-bits of the General Purpose IO Register or Set the upper 16 bits. Read/Write + ## guid Get or set the value of the User ID Register Read/Write + ## gsnpsid Get the value of the Synopsys ID Regester Read + ## devspeed Get or set the device speed setting in the DCFG register Read/Write + # enumspeed Gets the device enumeration Speed. Read + # hptxfsiz Get the value of the Host Periodic Transmit FIFO Read + # hprt0 Get or Set the value in the Host Port Control and Status Register Read/Write + + test_status("TEST NYI") or die; +} + +test_main(); +0; diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index d2900197a49e74..d70f85659af304 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -715,6 +715,14 @@ void xhci_setup_streams_ep_input_ctx(struct xhci_hcd *xhci, ep_ctx->ep_info &= cpu_to_le32(~EP_MAXPSTREAMS_MASK); ep_ctx->ep_info |= cpu_to_le32(EP_MAXPSTREAMS(max_primary_streams) | EP_HAS_LSA); + + /* + * Set Host Initiated Data Move Disable to always defer stream + * selection to the device. xHC implementations may treat this + * field as "don't care, forced to 1" anyway - xHCI 1.2 s4.12.1. + */ + ep_ctx->ep_info2 |= EP_HID; + ep_ctx->deq = cpu_to_le64(stream_info->ctx_array_dma); } @@ -1395,6 +1403,7 @@ int xhci_endpoint_init(struct xhci_hcd *xhci, unsigned int ep_index; struct xhci_ep_ctx *ep_ctx; struct xhci_ring *ep_ring; + struct usb_interface_cache *intfc; unsigned int max_packet; enum xhci_ring_type ring_type; u32 max_esit_payload; @@ -1404,6 +1413,8 @@ int xhci_endpoint_init(struct xhci_hcd *xhci, unsigned int mult; unsigned int avg_trb_len; unsigned int err_count = 0; + unsigned int is_ums_dev = 0; + unsigned int i; ep_index = xhci_get_endpoint_index(&ep->desc); ep_ctx = xhci_get_ep_ctx(xhci, virt_dev->in_ctx, ep_index); @@ -1435,9 +1446,35 @@ int xhci_endpoint_init(struct xhci_hcd *xhci, mult = xhci_get_endpoint_mult(udev, ep); max_packet = usb_endpoint_maxp(&ep->desc); - max_burst = xhci_get_endpoint_max_burst(udev, ep); avg_trb_len = max_esit_payload; + /* + * VL805 errata - Bulk OUT bursts to superspeed mass-storage + * devices behind hub ports can cause data corruption with + * non-wMaxPacket-multiple transfers. + */ + for (i = 0; i < udev->config->desc.bNumInterfaces; i++) { + intfc = udev->config->intf_cache[i]; + /* + * Slight hack - look at interface altsetting 0, which + * should be the UMS bulk-only interface. If the class + * matches, then we disable out bursts for all OUT + * endpoints because endpoint assignments may change + * between alternate settings. + */ + if (intfc->altsetting[0].desc.bInterfaceClass == + USB_CLASS_MASS_STORAGE) { + is_ums_dev = 1; + break; + } + } + if (xhci->quirks & XHCI_VLI_SS_BULK_OUT_BUG && + usb_endpoint_is_bulk_out(&ep->desc) && is_ums_dev && + udev->route) + max_burst = 0; + else + max_burst = xhci_get_endpoint_max_burst(udev, ep); + /* FIXME dig Mult and streams info out of ep companion desc */ /* Allow 3 retries for everything but isoc, set CErr = 3 */ diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index deb3c98c9beaf6..87de4c9054cf50 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -27,6 +27,8 @@ #define SPARSE_DISABLE_BIT 17 #define SPARSE_CNTL_ENABLE 0xC12C +#define VL805_FW_VER_0138C0 0x0138C0 + /* Device for a quirk */ #define PCI_VENDOR_ID_FRESCO_LOGIC 0x1b73 #define PCI_DEVICE_ID_FRESCO_LOGIC_PDK 0x1000 @@ -242,6 +244,16 @@ static int xhci_pci_reinit(struct xhci_hcd *xhci, struct pci_dev *pdev) return 0; } +static u32 xhci_vl805_get_fw_version(struct pci_dev *dev) +{ + int ret; + u32 ver; + + ret = pci_read_config_dword(dev, 0x50, &ver); + /* Default to a fw version of 0 instead of ~0 */ + return ret ? 0 : ver; +} + static void xhci_pci_quirks(struct device *dev, struct xhci_hcd *xhci) { struct pci_dev *pdev = to_pci_dev(dev); @@ -424,8 +436,15 @@ static void xhci_pci_quirks(struct device *dev, struct xhci_hcd *xhci) pdev->device == 0x3432) xhci->quirks |= XHCI_BROKEN_STREAMS; - if (pdev->vendor == PCI_VENDOR_ID_VIA && pdev->device == 0x3483) + if (pdev->vendor == PCI_VENDOR_ID_VIA && pdev->device == 0x3483) { xhci->quirks |= XHCI_LPM_SUPPORT; + xhci->quirks |= XHCI_EP_CTX_BROKEN_DCS; + xhci->quirks |= XHCI_AVOID_DQ_ON_LINK; + xhci->quirks |= XHCI_ZHAOXIN_TRB_FETCH; + xhci->quirks |= XHCI_VLI_SS_BULK_OUT_BUG; + if (xhci_vl805_get_fw_version(pdev) < VL805_FW_VER_0138C0) + xhci->quirks |= XHCI_VLI_HUB_TT_QUIRK; + } if (pdev->vendor == PCI_VENDOR_ID_ASMEDIA && pdev->device == PCI_DEVICE_ID_ASMEDIA_1042_XHCI) { diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 4384b86ea7b66c..3273103fd2d54e 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -510,6 +510,19 @@ void xhci_ring_ep_doorbell(struct xhci_hcd *xhci, trace_xhci_ring_ep_doorbell(slot_id, DB_VALUE(ep_index, stream_id)); + /* + * For non-coherent systems with PCIe DMA (such as Pi 4, Pi 5) there + * is a theoretical race between the TRB write and barrier, which + * is reported complete as soon as the write leaves the CPU domain, + * the doorbell write, which may be reported as complete by the RC + * at some arbitrary point, and the visibility of new TRBs in system + * RAM by the endpoint DMA engine. + * + * This read before the write positively serialises the CPU state + * by incurring a round-trip across the link. + */ + readl(db_addr); + writel(DB_VALUE(ep_index, stream_id), db_addr); /* flush the write */ readl(db_addr); @@ -638,8 +651,11 @@ static int xhci_move_dequeue_past_td(struct xhci_hcd *xhci, struct xhci_ring *ep_ring; struct xhci_command *cmd; struct xhci_segment *new_seg; + struct xhci_segment *halted_seg = NULL; union xhci_trb *new_deq; int new_cycle; + union xhci_trb *halted_trb; + int index = 0; dma_addr_t addr; u64 hw_dequeue; bool cycle_found = false; @@ -658,7 +674,25 @@ static int xhci_move_dequeue_past_td(struct xhci_hcd *xhci, hw_dequeue = xhci_get_hw_deq(xhci, dev, ep_index, stream_id); new_seg = ep_ring->deq_seg; new_deq = ep_ring->dequeue; - new_cycle = hw_dequeue & 0x1; + + /* + * Quirk: xHC write-back of the DCS field in the hardware dequeue + * pointer is wrong - use the cycle state of the TRB pointed to by + * the dequeue pointer. + */ + if (xhci->quirks & XHCI_EP_CTX_BROKEN_DCS && + !(ep->ep_state & EP_HAS_STREAMS)) + halted_seg = trb_in_td(xhci, td, hw_dequeue & ~0xf, false); + if (halted_seg) { + index = ((dma_addr_t)(hw_dequeue & ~0xf) - halted_seg->dma) / + sizeof(*halted_trb); + halted_trb = &halted_seg->trbs[index]; + new_cycle = halted_trb->generic.field[3] & 0x1; + xhci_dbg(xhci, "Endpoint DCS = %d TRB index = %d cycle = %d\n", + (u8)(hw_dequeue & 0x1), index, new_cycle); + } else { + new_cycle = hw_dequeue & 0x1; + } /* * We want to find the pointer, segment and cycle state of the new trb @@ -690,6 +724,16 @@ static int xhci_move_dequeue_past_td(struct xhci_hcd *xhci, } while (!cycle_found || !td_last_trb_found); + /* + * Quirk: the xHC does not correctly parse link TRBs if the HW Dequeue + * pointer is set to one. Advance to the next TRB (and next segment). + */ + if (xhci->quirks & XHCI_AVOID_DQ_ON_LINK && trb_is_link(new_deq)) { + if (link_trb_toggles_cycle(new_deq)) + new_cycle ^= 0x1; + next_trb(xhci, ep_ring, &new_seg, &new_deq); + } + /* Don't update the ring cycle state for the producer (us). */ addr = xhci_trb_virt_to_dma(new_seg, new_deq); if (addr == 0) { @@ -699,9 +743,9 @@ static int xhci_move_dequeue_past_td(struct xhci_hcd *xhci, } if ((ep->ep_state & SET_DEQ_PENDING)) { - xhci_warn(xhci, "Set TR Deq already pending, don't submit for 0x%pad\n", - &addr); - return -EBUSY; + xhci_warn(xhci, "WARN A Set TR Deq Ptr command is pending for slot %u ep %u\n", + slot_id, ep_index); + ep->ep_state &= ~SET_DEQ_PENDING; } /* This function gets called from contexts where it cannot sleep */ @@ -3583,6 +3627,48 @@ static int xhci_align_td(struct xhci_hcd *xhci, struct urb *urb, u32 enqd_len, return 1; } +static void xhci_vl805_hub_tt_quirk(struct xhci_hcd *xhci, struct urb *urb, + struct xhci_ring *ring) +{ + struct list_head *tmp; + struct usb_device *udev = urb->dev; + unsigned int timeout = 0; + unsigned int single_td = 0; + + /* + * Adding a TD to an Idle ring for a FS nonperiodic endpoint + * that is behind the internal hub's TT will run the risk of causing a + * downstream port babble if submitted late in uFrame 7. + * Wait until we've moved on into at least uFrame 0 + * (MFINDEX references the next SOF to be transmitted). + * + * Rings for IN endpoints in the Running state also risk causing + * babble if the returned data is large, but there's not much we can do + * about it here. + */ + if (udev->route & 0xffff0 || udev->speed != USB_SPEED_FULL) + return; + + list_for_each(tmp, &ring->td_list) { + single_td++; + if (single_td == 2) { + single_td = 0; + break; + } + } + if (single_td) { + while (timeout < 20 && + (readl(&xhci->run_regs->microframe_index) & 0x7) == 0) { + udelay(10); + timeout++; + } + if (timeout >= 20) + xhci_warn(xhci, "MFINDEX didn't advance - %u.%u dodged\n", + readl(&xhci->run_regs->microframe_index) >> 3, + readl(&xhci->run_regs->microframe_index) & 7); + } +} + /* This is very similar to what ehci-q.c qtd_fill() does */ int xhci_queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags, struct urb *urb, int slot_id, unsigned int ep_index) @@ -3737,6 +3823,8 @@ int xhci_queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags, } check_trb_math(urb, enqd_len); + if (xhci->quirks & XHCI_VLI_HUB_TT_QUIRK) + xhci_vl805_hub_tt_quirk(xhci, urb, ring); giveback_first_trb(xhci, slot_id, ep_index, urb->stream_id, start_cycle, start_trb); return 0; @@ -3885,6 +3973,8 @@ int xhci_queue_ctrl_tx(struct xhci_hcd *xhci, gfp_t mem_flags, /* Event on completion */ field | TRB_IOC | TRB_TYPE(TRB_STATUS) | ep_ring->cycle_state); + if (xhci->quirks & XHCI_VLI_HUB_TT_QUIRK) + xhci_vl805_hub_tt_quirk(xhci, urb, ep_ring); giveback_first_trb(xhci, slot_id, ep_index, 0, start_cycle, start_trb); return 0; diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 358ed674f782fb..1b6e68f9119cfa 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -1537,6 +1537,109 @@ static int xhci_check_ep0_maxpacket(struct xhci_hcd *xhci, struct xhci_virt_devi return ret; } +/* + * RPI: Fixup endpoint intervals when requested + * - Check interval versus the (cached) endpoint context + * - set the endpoint interval to the new value + * - force an endpoint configure command + * XXX: bandwidth is not recalculated. We should probably do that. + */ + +static unsigned int xhci_get_endpoint_flag_from_index(unsigned int ep_index) +{ + return 1 << (ep_index + 1); +} + +static void xhci_fixup_endpoint(struct usb_hcd *hcd, struct usb_device *udev, + struct usb_host_endpoint *ep, int interval) +{ + struct xhci_hcd *xhci; + struct xhci_ep_ctx *ep_ctx_out, *ep_ctx_in; + struct xhci_command *command; + struct xhci_input_control_ctx *ctrl_ctx; + struct xhci_virt_device *vdev; + int xhci_interval; + int ret; + int ep_index; + unsigned long flags; + u32 ep_info_tmp; + + xhci = hcd_to_xhci(hcd); + ep_index = xhci_get_endpoint_index(&ep->desc); + + /* FS/LS interval translations */ + if ((udev->speed == USB_SPEED_FULL || + udev->speed == USB_SPEED_LOW)) + interval *= 8; + + mutex_lock(&xhci->mutex); + + spin_lock_irqsave(&xhci->lock, flags); + + vdev = xhci->devs[udev->slot_id]; + /* Get context-derived endpoint interval */ + ep_ctx_out = xhci_get_ep_ctx(xhci, vdev->out_ctx, ep_index); + ep_ctx_in = xhci_get_ep_ctx(xhci, vdev->in_ctx, ep_index); + xhci_interval = EP_INTERVAL_TO_UFRAMES(le32_to_cpu(ep_ctx_out->ep_info)); + + if (interval == xhci_interval) { + spin_unlock_irqrestore(&xhci->lock, flags); + mutex_unlock(&xhci->mutex); + return; + } + + xhci_dbg(xhci, "Fixup interval=%d xhci_interval=%d\n", + interval, xhci_interval); + command = xhci_alloc_command_with_ctx(xhci, true, GFP_ATOMIC); + if (!command) { + /* Failure here is benign, poll at the original rate */ + spin_unlock_irqrestore(&xhci->lock, flags); + mutex_unlock(&xhci->mutex); + return; + } + + /* xHCI uses exponents for intervals... */ + xhci_interval = fls(interval) - 1; + xhci_interval = clamp_val(xhci_interval, 3, 10); + ep_info_tmp = le32_to_cpu(ep_ctx_out->ep_info); + ep_info_tmp &= ~EP_INTERVAL(255); + ep_info_tmp |= EP_INTERVAL(xhci_interval); + + /* Keep the endpoint context up-to-date while issuing the command. */ + xhci_endpoint_copy(xhci, vdev->in_ctx, + vdev->out_ctx, ep_index); + ep_ctx_in->ep_info = cpu_to_le32(ep_info_tmp); + + /* + * We need to drop the lock, so take an explicit copy + * of the ep context. + */ + xhci_endpoint_copy(xhci, command->in_ctx, vdev->in_ctx, ep_index); + + ctrl_ctx = xhci_get_input_control_ctx(command->in_ctx); + if (!ctrl_ctx) { + xhci_warn(xhci, + "%s: Could not get input context, bad type.\n", + __func__); + spin_unlock_irqrestore(&xhci->lock, flags); + xhci_free_command(xhci, command); + mutex_unlock(&xhci->mutex); + return; + } + ctrl_ctx->add_flags = xhci_get_endpoint_flag_from_index(ep_index); + ctrl_ctx->drop_flags = ctrl_ctx->add_flags; + + spin_unlock_irqrestore(&xhci->lock, flags); + + ret = xhci_configure_endpoint(xhci, udev, command, + false, false); + if (ret) + xhci_warn(xhci, "%s: Configure endpoint failed: %d\n", + __func__, ret); + xhci_free_command(xhci, command); + mutex_unlock(&xhci->mutex); +} + /* * non-error returns are a promise to giveback() the urb later * we drop ownership so next owner (or urb unlink) can get it @@ -5402,6 +5505,7 @@ static const struct hc_driver xhci_hc_driver = { .endpoint_reset = xhci_endpoint_reset, .check_bandwidth = xhci_check_bandwidth, .reset_bandwidth = xhci_reset_bandwidth, + .fixup_endpoint = xhci_fixup_endpoint, .address_device = xhci_address_device, .enable_device = xhci_enable_device, .update_hub_device = xhci_update_hub_device, diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 673179047eb82e..d55285643e7b8f 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -465,6 +465,8 @@ struct xhci_ep_ctx { #define CTX_TO_EP_MAXPSTREAMS(p) (((p) & EP_MAXPSTREAMS_MASK) >> 10) /* Endpoint is set up with a Linear Stream Array (vs. Secondary Stream Array) */ #define EP_HAS_LSA (1 << 15) +/* Host initiated data move disable in info2 */ +#define EP_HID (1 << 7) /* hosts with LEC=1 use bits 31:24 as ESIT high bits. */ #define CTX_TO_MAX_ESIT_PAYLOAD_HI(p) (((p) >> 24) & 0xff) @@ -1392,7 +1394,7 @@ struct urb_priv { }; /* Number of Event Ring segments to allocate, when amount is not specified. (spec allows 32k) */ -#define ERST_DEFAULT_SEGS 2 +#define ERST_DEFAULT_SEGS 8 /* Poll every 60 seconds */ #define POLL_TIMEOUT 60 /* Stop endpoint command timeout (secs) for URB cancellation watchdog timer */ @@ -1627,6 +1629,11 @@ struct xhci_hcd { #define XHCI_CDNS_SCTX_QUIRK BIT_ULL(48) #define XHCI_ETRON_HOST BIT_ULL(49) +/* Downstream VLI fixes */ +#define XHCI_AVOID_DQ_ON_LINK BIT_ULL(56) +#define XHCI_VLI_SS_BULK_OUT_BUG BIT_ULL(57) +#define XHCI_VLI_HUB_TT_QUIRK BIT_ULL(58) + unsigned int num_active_eps; unsigned int limit_active_eps; struct xhci_port *hw_ports; diff --git a/drivers/usb/phy/phy-generic.c b/drivers/usb/phy/phy-generic.c index e7d50e0a161238..aeced547e0e723 100644 --- a/drivers/usb/phy/phy-generic.c +++ b/drivers/usb/phy/phy-generic.c @@ -256,13 +256,6 @@ int usb_phy_gen_create_phy(struct device *dev, struct usb_phy_generic *nop) return dev_err_probe(dev, PTR_ERR(nop->vcc), "could not get vcc regulator\n"); - nop->vbus_draw = devm_regulator_get_exclusive(dev, "vbus"); - if (PTR_ERR(nop->vbus_draw) == -ENODEV) - nop->vbus_draw = NULL; - if (IS_ERR(nop->vbus_draw)) - return dev_err_probe(dev, PTR_ERR(nop->vbus_draw), - "could not get vbus regulator\n"); - nop->dev = dev; nop->phy.dev = nop->dev; nop->phy.label = "nop-xceiv"; diff --git a/drivers/usb/serial/pl2303.c b/drivers/usb/serial/pl2303.c index ad41363e3cea5a..010688dd9e49ce 100644 --- a/drivers/usb/serial/pl2303.c +++ b/drivers/usb/serial/pl2303.c @@ -31,6 +31,7 @@ #define PL2303_QUIRK_UART_STATE_IDX0 BIT(0) #define PL2303_QUIRK_LEGACY BIT(1) #define PL2303_QUIRK_ENDPOINT_HACK BIT(2) +#define PL2303_QUIRK_NO_BREAK_GETLINE BIT(3) static const struct usb_device_id id_table[] = { { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID), @@ -467,6 +468,25 @@ static int pl2303_detect_type(struct usb_serial *serial) return -ENODEV; } +static bool pl2303_is_hxd_clone(struct usb_serial *serial) +{ + struct usb_device *udev = serial->dev; + unsigned char *buf; + int ret; + + buf = kmalloc(7, GFP_KERNEL); + if (!buf) + return false; + + ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), + GET_LINE_REQUEST, GET_LINE_REQUEST_TYPE, + 0, 0, buf, 7, 100); + + kfree(buf); + + return ret == -EPIPE; +} + static int pl2303_startup(struct usb_serial *serial) { struct pl2303_serial_private *spriv; @@ -489,6 +509,9 @@ static int pl2303_startup(struct usb_serial *serial) spriv->quirks = (unsigned long)usb_get_serial_data(serial); spriv->quirks |= spriv->type->quirks; + if (type == TYPE_HXD && pl2303_is_hxd_clone(serial)) + spriv->quirks |= PL2303_QUIRK_NO_BREAK_GETLINE; + usb_set_serial_data(serial, spriv); if (type != TYPE_HXN) { @@ -725,9 +748,18 @@ static void pl2303_encode_baud_rate(struct tty_struct *tty, static int pl2303_get_line_request(struct usb_serial_port *port, unsigned char buf[7]) { - struct usb_device *udev = port->serial->dev; + struct usb_serial *serial = port->serial; + struct pl2303_serial_private *spriv = usb_get_serial_data(serial); + struct usb_device *udev = serial->dev; int ret; + if (spriv->quirks & PL2303_QUIRK_NO_BREAK_GETLINE) { + struct pl2303_private *priv = usb_get_serial_port_data(port); + + memcpy(buf, priv->line_settings, 7); + return 0; + } + ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), GET_LINE_REQUEST, GET_LINE_REQUEST_TYPE, 0, 0, buf, 7, 100); @@ -1064,9 +1096,13 @@ static int pl2303_carrier_raised(struct usb_serial_port *port) static int pl2303_set_break(struct usb_serial_port *port, bool enable) { struct usb_serial *serial = port->serial; + struct pl2303_serial_private *spriv = usb_get_serial_data(serial); u16 state; int result; + if (spriv->quirks & PL2303_QUIRK_NO_BREAK_GETLINE) + return -ENOTTY; + if (enable) state = BREAK_ON; else diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig index 3614a5d29c716e..d0951c7f375ac8 100644 --- a/drivers/video/backlight/Kconfig +++ b/drivers/video/backlight/Kconfig @@ -249,6 +249,13 @@ config BACKLIGHT_PWM If you have a LCD backlight adjustable by PWM, say Y to enable this driver. +config BACKLIGHT_RPI + tristate "Raspberry Pi display firmware driven backlight" + depends on RASPBERRYPI_FIRMWARE + help + If you have the Raspberry Pi DSI touchscreen display, say Y to + enable the mailbox-controlled backlight driver. + config BACKLIGHT_DA903X tristate "Backlight Driver for DA9030/DA9034 using WLED" depends on PMIC_DA903X diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile index 8fc98f760a8ad4..631227dfc1aab5 100644 --- a/drivers/video/backlight/Makefile +++ b/drivers/video/backlight/Makefile @@ -52,6 +52,7 @@ obj-$(CONFIG_BACKLIGHT_PANDORA) += pandora_bl.o obj-$(CONFIG_BACKLIGHT_PCF50633) += pcf50633-backlight.o obj-$(CONFIG_BACKLIGHT_PWM) += pwm_bl.o obj-$(CONFIG_BACKLIGHT_QCOM_WLED) += qcom-wled.o +obj-$(CONFIG_BACKLIGHT_RPI) += rpi_backlight.o obj-$(CONFIG_BACKLIGHT_RT4831) += rt4831-backlight.o obj-$(CONFIG_BACKLIGHT_SAHARA) += kb3886_bl.o obj-$(CONFIG_BACKLIGHT_SKY81452) += sky81452-backlight.o diff --git a/drivers/video/backlight/backlight.c b/drivers/video/backlight/backlight.c index a82934694d05c3..eb7af55d25370e 100644 --- a/drivers/video/backlight/backlight.c +++ b/drivers/video/backlight/backlight.c @@ -287,6 +287,15 @@ static ssize_t max_brightness_show(struct device *dev, } static DEVICE_ATTR_RO(max_brightness); +static ssize_t display_name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct backlight_device *bd = to_backlight_device(dev); + + return sprintf(buf, "%s\n", bd->props.display_name); +} +static DEVICE_ATTR_RO(display_name); + static ssize_t actual_brightness_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -365,6 +374,7 @@ static struct attribute *bl_device_attrs[] = { &dev_attr_max_brightness.attr, &dev_attr_scale.attr, &dev_attr_type.attr, + &dev_attr_display_name.attr, NULL, }; ATTRIBUTE_GROUPS(bl_device); @@ -668,6 +678,17 @@ static int of_parent_match(struct device *dev, const void *data) return dev->parent && dev->parent->of_node == data; } +int backlight_set_display_name(struct backlight_device *bd, const char *name) +{ + if (!bd) + return -EINVAL; + + strscpy_pad(bd->props.display_name, name, sizeof(bd->props.display_name)); + + return 0; +} +EXPORT_SYMBOL(backlight_set_display_name); + /** * of_find_backlight_by_node() - find backlight device by device-tree node * @node: device-tree node of the backlight device diff --git a/drivers/video/backlight/rpi_backlight.c b/drivers/video/backlight/rpi_backlight.c new file mode 100644 index 00000000000000..877f29f1d7b074 --- /dev/null +++ b/drivers/video/backlight/rpi_backlight.c @@ -0,0 +1,118 @@ +/* + * rpi_bl.c - Backlight controller through VPU + * + * 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 <linux/backlight.h> +#include <linux/err.h> +#include <linux/fb.h> +#include <linux/gpio.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <soc/bcm2835/raspberrypi-firmware.h> + +struct rpi_backlight { + struct device *dev; + struct device *fbdev; + struct rpi_firmware *fw; +}; + +static int rpi_backlight_update_status(struct backlight_device *bl) +{ + struct rpi_backlight *gbl = bl_get_data(bl); + int brightness = bl->props.brightness; + int ret; + + if (bl->props.power != FB_BLANK_UNBLANK || + bl->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK)) + brightness = 0; + + ret = rpi_firmware_property(gbl->fw, + RPI_FIRMWARE_FRAMEBUFFER_SET_BACKLIGHT, + &brightness, sizeof(brightness)); + if (ret) { + dev_err(gbl->dev, "Failed to set brightness\n"); + return ret; + } + + if (brightness < 0) { + dev_err(gbl->dev, "Backlight change failed\n"); + return -EAGAIN; + } + + return 0; +} + +static const struct backlight_ops rpi_backlight_ops = { + .options = BL_CORE_SUSPENDRESUME, + .update_status = rpi_backlight_update_status, +}; + +static int rpi_backlight_probe(struct platform_device *pdev) +{ + struct backlight_properties props; + struct backlight_device *bl; + struct rpi_backlight *gbl; + struct device_node *fw_node; + + gbl = devm_kzalloc(&pdev->dev, sizeof(*gbl), GFP_KERNEL); + if (gbl == NULL) + return -ENOMEM; + + gbl->dev = &pdev->dev; + + fw_node = of_parse_phandle(pdev->dev.of_node, "firmware", 0); + if (!fw_node) { + dev_err(&pdev->dev, "Missing firmware node\n"); + return -ENOENT; + } + + gbl->fw = rpi_firmware_get(fw_node); + if (!gbl->fw) + return -EPROBE_DEFER; + + memset(&props, 0, sizeof(props)); + props.type = BACKLIGHT_RAW; + props.max_brightness = 255; + bl = devm_backlight_device_register(&pdev->dev, dev_name(&pdev->dev), + &pdev->dev, gbl, &rpi_backlight_ops, + &props); + if (IS_ERR(bl)) { + dev_err(&pdev->dev, "failed to register backlight\n"); + return PTR_ERR(bl); + } + + bl->props.brightness = 255; + backlight_update_status(bl); + + platform_set_drvdata(pdev, bl); + return 0; +} + +static const struct of_device_id rpi_backlight_of_match[] = { + { .compatible = "raspberrypi,rpi-backlight" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, rpi_backlight_of_match); + +static struct platform_driver rpi_backlight_driver = { + .driver = { + .name = "rpi-backlight", + .of_match_table = of_match_ptr(rpi_backlight_of_match), + }, + .probe = rpi_backlight_probe, +}; + +module_platform_driver(rpi_backlight_driver); + +MODULE_AUTHOR("Gordon Hollingworth <gordon@raspberrypi.org>"); +MODULE_DESCRIPTION("Raspberry Pi mailbox based Backlight Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/Kconfig b/drivers/video/fbdev/Kconfig index de035071fedb1b..8e5400ee3ce378 100644 --- a/drivers/video/fbdev/Kconfig +++ b/drivers/video/fbdev/Kconfig @@ -61,6 +61,19 @@ config FB_MACMODES tristate depends on FB +config FB_BCM2708 + tristate "BCM2708 framebuffer support" + depends on FB && RASPBERRYPI_FIRMWARE + select FB_DEVICE + select FB_IOMEM_HELPERS + help + This framebuffer device driver is for the BCM2708 framebuffer. + + If you want to compile this as a module (=code which can be + inserted into and removed from the running kernel), say M + here and read <file:Documentation/kbuild/modules.txt>. The module + will be called bcm2708_fb. + config FB_GRVGA tristate "Aeroflex Gaisler framebuffer support" depends on FB && SPARC @@ -1811,6 +1824,19 @@ config FB_SM712 called sm712fb. If you want to compile it as a module, say M here and read <file:Documentation/kbuild/modules.rst>. +config FB_RPISENSE + tristate "Raspberry Pi Sense HAT framebuffer" + depends on FB && I2C + select MFD_SIMPLE_MFD_I2C + select FB_SYS_FOPS + select FB_SYS_FILLRECT + select FB_SYS_COPYAREA + select FB_SYS_IMAGEBLIT + select FB_DEFERRED_IO + + help + This is the framebuffer driver for the Raspberry Pi Sense HAT + source "drivers/video/fbdev/omap/Kconfig" source "drivers/video/fbdev/omap2/Kconfig" source "drivers/video/fbdev/mmp/Kconfig" diff --git a/drivers/video/fbdev/Makefile b/drivers/video/fbdev/Makefile index b3d12f977c06b1..ea305adc3f3c6a 100644 --- a/drivers/video/fbdev/Makefile +++ b/drivers/video/fbdev/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_FB_SBUS) += sbuslib.o obj-$(CONFIG_FB_WMT_GE_ROPS) += wmt_ge_rops.o # Hardware specific drivers go first +obj-$(CONFIG_FB_BCM2708) += bcm2708_fb.o obj-$(CONFIG_FB_AMIGA) += amifb.o c2p_planar.o obj-$(CONFIG_FB_ARC) += arcfb.o obj-$(CONFIG_FB_CLPS711X) += clps711x-fb.o @@ -123,6 +124,7 @@ obj-$(CONFIG_FB_VGA16) += vga16fb.o obj-$(CONFIG_FB_OF) += offb.o obj-$(CONFIG_FB_SSD1307) += ssd1307fb.o obj-$(CONFIG_FB_SIMPLE) += simplefb.o +obj-$(CONFIG_FB_RPISENSE) += rpisense-fb.o # the test framebuffer is last obj-$(CONFIG_FB_VIRTUAL) += vfb.o diff --git a/drivers/video/fbdev/bcm2708_fb.c b/drivers/video/fbdev/bcm2708_fb.c new file mode 100644 index 00000000000000..91f3e55538bffc --- /dev/null +++ b/drivers/video/fbdev/bcm2708_fb.c @@ -0,0 +1,1262 @@ +/* + * linux/drivers/video/bcm2708_fb.c + * + * Copyright (C) 2010 Broadcom + * Copyright (C) 2018 Raspberry Pi (Trading) Ltd + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + * + * Broadcom simple framebuffer driver + * + * This file is derived from cirrusfb.c + * Copyright 1999-2001 Jeff Garzik <jgarzik@pobox.com> + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/list.h> +#include <linux/platform_data/dma-bcm2708.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/printk.h> +#include <linux/console.h> +#include <linux/debugfs.h> +#include <linux/uaccess.h> +#include <linux/io.h> +#include <linux/dma-mapping.h> +#include <linux/cred.h> +#include <soc/bcm2835/raspberrypi-firmware.h> +#include <linux/mutex.h> +#include <linux/compat.h> + +//#define BCM2708_FB_DEBUG +#define MODULE_NAME "bcm2708_fb" + +#ifdef BCM2708_FB_DEBUG +#define print_debug(fmt, ...) pr_debug("%s:%s:%d: " fmt, \ + MODULE_NAME, __func__, __LINE__, ##__VA_ARGS__) +#else +#define print_debug(fmt, ...) +#endif + +/* This is limited to 16 characters when displayed by X startup */ +static const char *bcm2708_name = "BCM2708 FB"; + +#define DRIVER_NAME "bcm2708_fb" + +static int fbwidth = 800; /* module parameter */ +static int fbheight = 480; /* module parameter */ +static int fbdepth = 32; /* module parameter */ +static int fbswap; /* module parameter */ + +static u32 dma_busy_wait_threshold = 1 << 15; +module_param(dma_busy_wait_threshold, int, 0644); +MODULE_PARM_DESC(dma_busy_wait_threshold, "Busy-wait for DMA completion below this area"); + +struct fb_alloc_tags { + struct rpi_firmware_property_tag_header tag1; + u32 xres, yres; + struct rpi_firmware_property_tag_header tag2; + u32 xres_virtual, yres_virtual; + struct rpi_firmware_property_tag_header tag3; + u32 bpp; + struct rpi_firmware_property_tag_header tag4; + u32 xoffset, yoffset; + struct rpi_firmware_property_tag_header tag5; + u32 base, screen_size; + struct rpi_firmware_property_tag_header tag6; + u32 pitch; +}; + +struct bcm2708_fb_stats { + struct debugfs_regset32 regset; + u32 dma_copies; + u32 dma_irqs; +}; + +struct vc4_display_settings_t { + u32 display_num; + u32 width; + u32 height; + u32 depth; + u32 pitch; + u32 virtual_width; + u32 virtual_height; + u32 virtual_width_offset; + u32 virtual_height_offset; + unsigned long fb_bus_address; +}; + +struct bcm2708_fb_dev; + +struct bcm2708_fb { + struct fb_info fb; + struct platform_device *dev; + u32 cmap[16]; + u32 gpu_cmap[256]; + struct dentry *debugfs_dir; + struct dentry *debugfs_subdir; + unsigned long fb_bus_address; + struct { u32 base, length; } gpu; + struct vc4_display_settings_t display_settings; + struct debugfs_regset32 screeninfo_regset; + struct bcm2708_fb_dev *fbdev; + unsigned int image_size; + dma_addr_t dma_addr; + void *cpuaddr; +}; + +#define MAX_FRAMEBUFFERS 3 + +struct bcm2708_fb_dev { + int firmware_supports_multifb; + /* Protects the DMA system from multiple FB access */ + struct mutex dma_mutex; + int dma_chan; + int dma_irq; + void __iomem *dma_chan_base; + wait_queue_head_t dma_waitq; + bool disable_arm_alloc; + struct bcm2708_fb_stats dma_stats; + void *cb_base; /* DMA control blocks */ + dma_addr_t cb_handle; + int instance_count; + int num_displays; + struct rpi_firmware *fw; + struct bcm2708_fb displays[MAX_FRAMEBUFFERS]; +}; + +#define to_bcm2708(info) container_of(info, struct bcm2708_fb, fb) + +static void bcm2708_fb_debugfs_deinit(struct bcm2708_fb *fb) +{ + debugfs_remove_recursive(fb->debugfs_subdir); + fb->debugfs_subdir = NULL; + + fb->fbdev->instance_count--; + + if (!fb->fbdev->instance_count) { + debugfs_remove_recursive(fb->debugfs_dir); + fb->debugfs_dir = NULL; + } +} + +static int bcm2708_fb_debugfs_init(struct bcm2708_fb *fb) +{ + char buf[3]; + struct bcm2708_fb_dev *fbdev = fb->fbdev; + + static struct debugfs_reg32 stats_registers[] = { + {"dma_copies", offsetof(struct bcm2708_fb_stats, dma_copies)}, + {"dma_irqs", offsetof(struct bcm2708_fb_stats, dma_irqs)}, + }; + + static struct debugfs_reg32 screeninfo[] = { + {"width", offsetof(struct fb_var_screeninfo, xres)}, + {"height", offsetof(struct fb_var_screeninfo, yres)}, + {"bpp", offsetof(struct fb_var_screeninfo, bits_per_pixel)}, + {"xres_virtual", offsetof(struct fb_var_screeninfo, xres_virtual)}, + {"yres_virtual", offsetof(struct fb_var_screeninfo, yres_virtual)}, + {"xoffset", offsetof(struct fb_var_screeninfo, xoffset)}, + {"yoffset", offsetof(struct fb_var_screeninfo, yoffset)}, + }; + + fb->debugfs_dir = debugfs_lookup(DRIVER_NAME, NULL); + + if (!fb->debugfs_dir) + fb->debugfs_dir = debugfs_create_dir(DRIVER_NAME, NULL); + + if (!fb->debugfs_dir) { + dev_warn(fb->fb.dev, "%s: could not create debugfs folder\n", + __func__); + return -EFAULT; + } + + snprintf(buf, sizeof(buf), "%u", fb->display_settings.display_num); + + fb->debugfs_subdir = debugfs_create_dir(buf, fb->debugfs_dir); + + if (!fb->debugfs_subdir) { + dev_warn(fb->fb.dev, "%s: could not create debugfs entry %u\n", + __func__, fb->display_settings.display_num); + return -EFAULT; + } + + fbdev->dma_stats.regset.regs = stats_registers; + fbdev->dma_stats.regset.nregs = ARRAY_SIZE(stats_registers); + fbdev->dma_stats.regset.base = &fbdev->dma_stats; + + debugfs_create_regset32("dma_stats", 0444, fb->debugfs_subdir, + &fbdev->dma_stats.regset); + + fb->screeninfo_regset.regs = screeninfo; + fb->screeninfo_regset.nregs = ARRAY_SIZE(screeninfo); + fb->screeninfo_regset.base = &fb->fb.var; + + debugfs_create_regset32("screeninfo", 0444, fb->debugfs_subdir, + &fb->screeninfo_regset); + + fbdev->instance_count++; + + return 0; +} + +static void set_display_num(struct bcm2708_fb *fb) +{ + if (fb && fb->fbdev && fb->fbdev->firmware_supports_multifb) { + u32 tmp = fb->display_settings.display_num; + + if (rpi_firmware_property(fb->fbdev->fw, + RPI_FIRMWARE_FRAMEBUFFER_SET_DISPLAY_NUM, + &tmp, + sizeof(tmp))) + dev_warn_once(fb->fb.dev, + "Set display number call failed. Old GPU firmware?"); + } +} + +static int bcm2708_fb_set_bitfields(struct fb_var_screeninfo *var) +{ + int ret = 0; + + memset(&var->transp, 0, sizeof(var->transp)); + + var->red.msb_right = 0; + var->green.msb_right = 0; + var->blue.msb_right = 0; + + switch (var->bits_per_pixel) { + case 1: + case 2: + case 4: + case 8: + var->red.length = var->bits_per_pixel; + var->red.offset = 0; + var->green.length = var->bits_per_pixel; + var->green.offset = 0; + var->blue.length = var->bits_per_pixel; + var->blue.offset = 0; + break; + case 16: + var->red.length = 5; + var->blue.length = 5; + /* + * Green length can be 5 or 6 depending whether + * we're operating in RGB555 or RGB565 mode. + */ + if (var->green.length != 5 && var->green.length != 6) + var->green.length = 6; + break; + case 24: + var->red.length = 8; + var->blue.length = 8; + var->green.length = 8; + break; + case 32: + var->red.length = 8; + var->green.length = 8; + var->blue.length = 8; + var->transp.length = 8; + break; + default: + ret = -EINVAL; + break; + } + + /* + * >= 16bpp displays have separate colour component bitfields + * encoded in the pixel data. Calculate their position from + * the bitfield length defined above. + */ + if (ret == 0 && var->bits_per_pixel >= 24 && fbswap) { + var->blue.offset = 0; + var->green.offset = var->blue.offset + var->blue.length; + var->red.offset = var->green.offset + var->green.length; + var->transp.offset = var->red.offset + var->red.length; + } else if (ret == 0 && var->bits_per_pixel >= 24) { + var->red.offset = 0; + var->green.offset = var->red.offset + var->red.length; + var->blue.offset = var->green.offset + var->green.length; + var->transp.offset = var->blue.offset + var->blue.length; + } else if (ret == 0 && var->bits_per_pixel >= 16) { + var->blue.offset = 0; + var->green.offset = var->blue.offset + var->blue.length; + var->red.offset = var->green.offset + var->green.length; + var->transp.offset = var->red.offset + var->red.length; + } + + return ret; +} + +static int bcm2708_fb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + /* info input, var output */ + print_debug("%s(%p) %ux%u (%ux%u), %ul, %u\n", + __func__, info, info->var.xres, info->var.yres, + info->var.xres_virtual, info->var.yres_virtual, + info->screen_size, info->var.bits_per_pixel); + print_debug("%s(%p) %ux%u (%ux%u), %u\n", __func__, var, var->xres, + var->yres, var->xres_virtual, var->yres_virtual, + var->bits_per_pixel); + + if (!var->bits_per_pixel) + var->bits_per_pixel = 16; + + if (bcm2708_fb_set_bitfields(var) != 0) { + pr_err("%s: invalid bits_per_pixel %d\n", __func__, + var->bits_per_pixel); + return -EINVAL; + } + + if (var->xres_virtual < var->xres) + var->xres_virtual = var->xres; + /* use highest possible virtual resolution */ + if (var->yres_virtual == -1) { + var->yres_virtual = 480; + + pr_err("%s: virtual resolution set to maximum of %dx%d\n", + __func__, var->xres_virtual, var->yres_virtual); + } + if (var->yres_virtual < var->yres) + var->yres_virtual = var->yres; + + if (var->xoffset < 0) + var->xoffset = 0; + if (var->yoffset < 0) + var->yoffset = 0; + + /* truncate xoffset and yoffset to maximum if too high */ + if (var->xoffset > var->xres_virtual - var->xres) + var->xoffset = var->xres_virtual - var->xres - 1; + if (var->yoffset > var->yres_virtual - var->yres) + var->yoffset = var->yres_virtual - var->yres - 1; + + return 0; +} + +static int bcm2708_fb_set_par(struct fb_info *info) +{ + struct bcm2708_fb *fb = to_bcm2708(info); + struct fb_alloc_tags fbinfo = { + .tag1 = { RPI_FIRMWARE_FRAMEBUFFER_SET_PHYSICAL_WIDTH_HEIGHT, + 8, 0, }, + .xres = info->var.xres, + .yres = info->var.yres, + .tag2 = { RPI_FIRMWARE_FRAMEBUFFER_SET_VIRTUAL_WIDTH_HEIGHT, + 8, 0, }, + .xres_virtual = info->var.xres_virtual, + .yres_virtual = info->var.yres_virtual, + .tag3 = { RPI_FIRMWARE_FRAMEBUFFER_SET_DEPTH, 4, 0 }, + .bpp = info->var.bits_per_pixel, + .tag4 = { RPI_FIRMWARE_FRAMEBUFFER_SET_VIRTUAL_OFFSET, 8, 0 }, + .xoffset = info->var.xoffset, + .yoffset = info->var.yoffset, + .tag5 = { RPI_FIRMWARE_FRAMEBUFFER_ALLOCATE, 8, 0 }, + /* base and screen_size will be initialised later */ + .tag6 = { RPI_FIRMWARE_FRAMEBUFFER_SET_PITCH, 4, 0 }, + /* pitch will be initialised later */ + }; + int ret, image_size; + + print_debug("%s(%p) %dx%d (%dx%d), %d, %d (display %d)\n", __func__, + info, + info->var.xres, info->var.yres, info->var.xres_virtual, + info->var.yres_virtual, (int)info->screen_size, + info->var.bits_per_pixel, value); + + /* Need to set the display number to act on first + * Cannot do it in the tag list because on older firmware the call + * will fail and stop the rest of the list being executed. + * We can ignore this call failing as the default at other end is 0 + */ + set_display_num(fb); + + /* Try allocating our own buffer. We can specify all the parameters */ + image_size = ((info->var.xres * info->var.yres) * + info->var.bits_per_pixel) >> 3; + + if (!fb->fbdev->disable_arm_alloc && + (image_size != fb->image_size || !fb->dma_addr)) { + if (fb->dma_addr) { + dma_free_coherent(info->device, fb->image_size, + fb->cpuaddr, fb->dma_addr); + fb->image_size = 0; + fb->cpuaddr = NULL; + fb->dma_addr = 0; + } + + fb->cpuaddr = dma_alloc_coherent(info->device, image_size, + &fb->dma_addr, GFP_KERNEL); + + if (!fb->cpuaddr) { + fb->dma_addr = 0; + fb->fbdev->disable_arm_alloc = true; + } else { + fb->image_size = image_size; + } + } + + if (fb->cpuaddr) { + fbinfo.base = fb->dma_addr; + fbinfo.screen_size = image_size; + fbinfo.pitch = (info->var.xres * info->var.bits_per_pixel) >> 3; + + ret = rpi_firmware_property_list(fb->fbdev->fw, &fbinfo, + sizeof(fbinfo)); + if (ret || fbinfo.base != fb->dma_addr) { + /* Firmware either failed, or assigned a different base + * address (ie it doesn't support being passed an FB + * allocation). + * Destroy the allocation, and don't try again. + */ + dma_free_coherent(info->device, fb->image_size, + fb->cpuaddr, fb->dma_addr); + fb->image_size = 0; + fb->cpuaddr = NULL; + fb->dma_addr = 0; + fb->fbdev->disable_arm_alloc = true; + } + } else { + /* Our allocation failed - drop into the old scheme of + * allocation by the VPU. + */ + ret = -ENOMEM; + } + + if (ret) { + /* Old scheme: + * - FRAMEBUFFER_ALLOCATE passes 0 for base and screen_size. + * - GET_PITCH instead of SET_PITCH. + */ + fbinfo.base = 0; + fbinfo.screen_size = 0; + fbinfo.tag6.tag = RPI_FIRMWARE_FRAMEBUFFER_GET_PITCH; + fbinfo.pitch = 0; + + ret = rpi_firmware_property_list(fb->fbdev->fw, &fbinfo, + sizeof(fbinfo)); + if (ret) { + dev_err(info->device, + "Failed to allocate GPU framebuffer (%d)\n", + ret); + return ret; + } + } + + if (info->var.bits_per_pixel <= 8) + fb->fb.fix.visual = FB_VISUAL_PSEUDOCOLOR; + else + fb->fb.fix.visual = FB_VISUAL_TRUECOLOR; + + fb->fb.fix.line_length = fbinfo.pitch; + fbinfo.base |= 0x40000000; + fb->fb_bus_address = fbinfo.base; + fbinfo.base &= ~0xc0000000; + fb->fb.fix.smem_start = fbinfo.base; + fb->fb.fix.smem_len = fbinfo.pitch * fbinfo.yres_virtual; + fb->fb.screen_size = fbinfo.screen_size; + + if (!fb->dma_addr) { + if (fb->fb.screen_base) + iounmap(fb->fb.screen_base); + + fb->fb.screen_base = ioremap_wc(fbinfo.base, + fb->fb.screen_size); + } else { + fb->fb.screen_base = fb->cpuaddr; + } + + if (!fb->fb.screen_base) { + /* the console may currently be locked */ + console_trylock(); + console_unlock(); + dev_err(info->device, "Failed to set screen_base\n"); + return -ENOMEM; + } + + print_debug("%s: start = %p,%p width=%d, height=%d, bpp=%d, pitch=%d size=%d\n", + __func__, (void *)fb->fb.screen_base, + (void *)fb->fb_bus_address, fbinfo.xres, fbinfo.yres, + fbinfo.bpp, fbinfo.pitch, (int)fb->fb.screen_size); + + return 0; +} + +static inline u32 convert_bitfield(int val, struct fb_bitfield *bf) +{ + unsigned int mask = (1 << bf->length) - 1; + + return (val >> (16 - bf->length) & mask) << bf->offset; +} + +static int bcm2708_fb_setcolreg(unsigned int regno, unsigned int red, + unsigned int green, unsigned int blue, + unsigned int transp, struct fb_info *info) +{ + struct bcm2708_fb *fb = to_bcm2708(info); + + if (fb->fb.var.bits_per_pixel <= 8) { + if (regno < 256) { + /* blue [23:16], green [15:8], red [7:0] */ + fb->gpu_cmap[regno] = ((red >> 8) & 0xff) << 0 | + ((green >> 8) & 0xff) << 8 | + ((blue >> 8) & 0xff) << 16; + } + /* Hack: we need to tell GPU the palette has changed, but + * currently bcm2708_fb_set_par takes noticeable time when + * called for every (256) colour + * So just call it for what looks like the last colour in a + * list for now. + */ + if (regno == 15 || regno == 255) { + struct packet { + u32 offset; + u32 length; + u32 cmap[256]; + } *packet; + int ret; + + packet = kmalloc(sizeof(*packet), GFP_KERNEL); + if (!packet) + return -ENOMEM; + packet->offset = 0; + packet->length = regno + 1; + memcpy(packet->cmap, fb->gpu_cmap, + sizeof(packet->cmap)); + + set_display_num(fb); + + ret = rpi_firmware_property(fb->fbdev->fw, + RPI_FIRMWARE_FRAMEBUFFER_SET_PALETTE, + packet, + (2 + packet->length) * sizeof(u32)); + if (ret || packet->offset) + dev_err(info->device, + "Failed to set palette (%d,%u)\n", + ret, packet->offset); + kfree(packet); + } + } else if (regno < 16) { + fb->cmap[regno] = convert_bitfield(transp, &fb->fb.var.transp) | + convert_bitfield(blue, &fb->fb.var.blue) | + convert_bitfield(green, &fb->fb.var.green) | + convert_bitfield(red, &fb->fb.var.red); + } + return regno > 255; +} + +static int bcm2708_fb_blank(int blank_mode, struct fb_info *info) +{ + struct bcm2708_fb *fb = to_bcm2708(info); + u32 value; + int ret; + + switch (blank_mode) { + case FB_BLANK_UNBLANK: + value = 0; + break; + case FB_BLANK_NORMAL: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_POWERDOWN: + value = 1; + break; + default: + return -EINVAL; + } + + set_display_num(fb); + + ret = rpi_firmware_property(fb->fbdev->fw, RPI_FIRMWARE_FRAMEBUFFER_BLANK, + &value, sizeof(value)); + + if (ret) + dev_err(info->device, "%s(%d) failed: %d\n", __func__, + blank_mode, ret); + + return ret; +} + +static int bcm2708_fb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + s32 result; + + info->var.xoffset = var->xoffset; + info->var.yoffset = var->yoffset; + result = bcm2708_fb_set_par(info); + if (result != 0) + pr_err("%s(%u,%u) returns=%d\n", __func__, var->xoffset, + var->yoffset, result); + return result; +} + +static void dma_memcpy(struct bcm2708_fb *fb, dma_addr_t dst, dma_addr_t src, + int size) +{ + struct bcm2708_fb_dev *fbdev = fb->fbdev; + struct bcm2708_dma_cb *cb = fbdev->cb_base; + int burst_size = (fbdev->dma_chan == 0) ? 8 : 2; + + cb->info = BCM2708_DMA_BURST(burst_size) | BCM2708_DMA_S_WIDTH | + BCM2708_DMA_S_INC | BCM2708_DMA_D_WIDTH | + BCM2708_DMA_D_INC; + cb->dst = dst; + cb->src = src; + cb->length = size; + cb->stride = 0; + cb->pad[0] = 0; + cb->pad[1] = 0; + cb->next = 0; + + // Not sure what to do if this gets a signal whilst waiting + if (mutex_lock_interruptible(&fbdev->dma_mutex)) + return; + + if (size < dma_busy_wait_threshold) { + bcm_dma_start(fbdev->dma_chan_base, fbdev->cb_handle); + bcm_dma_wait_idle(fbdev->dma_chan_base); + } else { + void __iomem *local_dma_chan = fbdev->dma_chan_base; + + cb->info |= BCM2708_DMA_INT_EN; + bcm_dma_start(fbdev->dma_chan_base, fbdev->cb_handle); + while (bcm_dma_is_busy(local_dma_chan)) { + wait_event_interruptible(fbdev->dma_waitq, + !bcm_dma_is_busy(local_dma_chan)); + } + fbdev->dma_stats.dma_irqs++; + } + fbdev->dma_stats.dma_copies++; + + mutex_unlock(&fbdev->dma_mutex); +} + +/* address with no aliases */ +#define INTALIAS_NORMAL(x) ((x) & ~0xc0000000) +/* cache coherent but non-allocating in L1 and L2 */ +#define INTALIAS_L1L2_NONALLOCATING(x) (((x) & ~0xc0000000) | 0x80000000) + +static long vc_mem_copy(struct bcm2708_fb *fb, struct fb_dmacopy *ioparam) +{ + size_t size = PAGE_SIZE; + u32 *buf = NULL; + dma_addr_t bus_addr; + long rc = 0; + size_t offset; + + /* restrict this to root user */ + if (!uid_eq(current_euid(), GLOBAL_ROOT_UID)) { + rc = -EFAULT; + goto out; + } + + if (!fb->gpu.base || !fb->gpu.length) { + pr_err("[%s]: Unable to determine gpu memory (%x,%x)\n", + __func__, fb->gpu.base, fb->gpu.length); + return -EFAULT; + } + + if (INTALIAS_NORMAL(ioparam->src) < fb->gpu.base || + INTALIAS_NORMAL(ioparam->src) >= fb->gpu.base + fb->gpu.length) { + pr_err("[%s]: Invalid memory access %x (%x-%x)", __func__, + INTALIAS_NORMAL(ioparam->src), fb->gpu.base, + fb->gpu.base + fb->gpu.length); + return -EFAULT; + } + + buf = dma_alloc_coherent(fb->fb.device, PAGE_ALIGN(size), &bus_addr, + GFP_ATOMIC); + if (!buf) { + pr_err("[%s]: failed to dma_alloc_coherent(%zd)\n", __func__, + size); + rc = -ENOMEM; + goto out; + } + + for (offset = 0; offset < ioparam->length; offset += size) { + size_t remaining = ioparam->length - offset; + size_t s = min(size, remaining); + u8 *p = (u8 *)((uintptr_t)ioparam->src + offset); + u8 *q = (u8 *)ioparam->dst + offset; + + dma_memcpy(fb, bus_addr, + INTALIAS_L1L2_NONALLOCATING((u32)(uintptr_t)p), + size); + if (copy_to_user(q, buf, s) != 0) { + pr_err("[%s]: failed to copy-to-user\n", __func__); + rc = -EFAULT; + goto out; + } + } +out: + if (buf) + dma_free_coherent(fb->fb.device, PAGE_ALIGN(size), buf, + bus_addr); + return rc; +} + +static int bcm2708_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + struct bcm2708_fb *fb = to_bcm2708(info); + u32 dummy = 0; + int ret; + + switch (cmd) { + case FBIO_WAITFORVSYNC: + set_display_num(fb); + + ret = rpi_firmware_property(fb->fbdev->fw, + RPI_FIRMWARE_FRAMEBUFFER_SET_VSYNC, + &dummy, sizeof(dummy)); + break; + + case FBIODMACOPY: + { + struct fb_dmacopy ioparam; + /* Get the parameter data. + */ + if (copy_from_user + (&ioparam, (void *)arg, sizeof(ioparam))) { + pr_err("[%s]: failed to copy-from-user\n", __func__); + ret = -EFAULT; + break; + } + ret = vc_mem_copy(fb, &ioparam); + break; + } + default: + dev_dbg(info->device, "Unknown ioctl 0x%x\n", cmd); + return -ENOTTY; + } + + if (ret) + dev_err(info->device, "ioctl 0x%x failed (%d)\n", cmd, ret); + + return ret; +} + +#ifdef CONFIG_COMPAT +struct fb_dmacopy32 { + compat_uptr_t dst; + __u32 src; + __u32 length; +}; + +#define FBIODMACOPY32 _IOW('z', 0x22, struct fb_dmacopy32) + +static int bcm2708_compat_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + struct bcm2708_fb *fb = to_bcm2708(info); + int ret; + + switch (cmd) { + case FBIODMACOPY32: + { + struct fb_dmacopy32 param32; + struct fb_dmacopy param; + /* Get the parameter data. + */ + if (copy_from_user(¶m32, (void *)arg, sizeof(param32))) { + pr_err("[%s]: failed to copy-from-user\n", __func__); + ret = -EFAULT; + break; + } + param.dst = compat_ptr(param32.dst); + param.src = param32.src; + param.length = param32.length; + ret = vc_mem_copy(fb, ¶m); + break; + } + default: + ret = bcm2708_ioctl(info, cmd, arg); + break; + } + return ret; +} +#endif + +/* A helper function for configuring dma control block */ +static void set_dma_cb(struct bcm2708_dma_cb *cb, + int burst_size, + dma_addr_t dst, + int dst_stride, + dma_addr_t src, + int src_stride, + int w, + int h) +{ + cb->info = BCM2708_DMA_BURST(burst_size) | BCM2708_DMA_S_WIDTH | + BCM2708_DMA_S_INC | BCM2708_DMA_D_WIDTH | + BCM2708_DMA_D_INC | BCM2708_DMA_TDMODE; + cb->dst = dst; + cb->src = src; + /* + * This is not really obvious from the DMA documentation, + * but the top 16 bits must be programmmed to "height -1" + * and not "height" in 2D mode. + */ + cb->length = ((h - 1) << 16) | w; + cb->stride = ((dst_stride - w) << 16) | (u16)(src_stride - w); + cb->pad[0] = 0; + cb->pad[1] = 0; +} + +static void bcm2708_fb_copyarea(struct fb_info *info, + const struct fb_copyarea *region) +{ + struct bcm2708_fb *fb = to_bcm2708(info); + struct bcm2708_fb_dev *fbdev = fb->fbdev; + struct bcm2708_dma_cb *cb = fbdev->cb_base; + int bytes_per_pixel = (info->var.bits_per_pixel + 7) >> 3; + + /* Channel 0 supports larger bursts and is a bit faster */ + int burst_size = (fbdev->dma_chan == 0) ? 8 : 2; + int pixels = region->width * region->height; + + /* If DMA is currently in use (ie being used on another FB), then + * rather than wait for it to finish, just use the cfb_copyarea + */ + if (!mutex_trylock(&fbdev->dma_mutex) || + bytes_per_pixel > 4 || + info->var.xres * info->var.yres > 1920 * 1200 || + region->width <= 0 || region->width > info->var.xres || + region->height <= 0 || region->height > info->var.yres || + region->sx < 0 || region->sx >= info->var.xres || + region->sy < 0 || region->sy >= info->var.yres || + region->dx < 0 || region->dx >= info->var.xres || + region->dy < 0 || region->dy >= info->var.yres || + region->sx + region->width > info->var.xres || + region->dx + region->width > info->var.xres || + region->sy + region->height > info->var.yres || + region->dy + region->height > info->var.yres) { + cfb_copyarea(info, region); + return; + } + + if (region->dy == region->sy && region->dx > region->sx) { + /* + * A difficult case of overlapped copy. Because DMA can't + * copy individual scanlines in backwards direction, we need + * two-pass processing. We do it by programming a chain of dma + * control blocks in the first 16K part of the buffer and use + * the remaining 48K as the intermediate temporary scratch + * buffer. The buffer size is sufficient to handle up to + * 1920x1200 resolution at 32bpp pixel depth. + */ + int y; + dma_addr_t control_block_pa = fbdev->cb_handle; + dma_addr_t scratchbuf = fbdev->cb_handle + 16 * 1024; + int scanline_size = bytes_per_pixel * region->width; + int scanlines_per_cb = (64 * 1024 - 16 * 1024) / scanline_size; + + for (y = 0; y < region->height; y += scanlines_per_cb) { + dma_addr_t src = + fb->fb_bus_address + + bytes_per_pixel * region->sx + + (region->sy + y) * fb->fb.fix.line_length; + dma_addr_t dst = + fb->fb_bus_address + + bytes_per_pixel * region->dx + + (region->dy + y) * fb->fb.fix.line_length; + + if (region->height - y < scanlines_per_cb) + scanlines_per_cb = region->height - y; + + set_dma_cb(cb, burst_size, scratchbuf, scanline_size, + src, fb->fb.fix.line_length, + scanline_size, scanlines_per_cb); + control_block_pa += sizeof(struct bcm2708_dma_cb); + cb->next = control_block_pa; + cb++; + + set_dma_cb(cb, burst_size, dst, fb->fb.fix.line_length, + scratchbuf, scanline_size, + scanline_size, scanlines_per_cb); + control_block_pa += sizeof(struct bcm2708_dma_cb); + cb->next = control_block_pa; + cb++; + } + /* move the pointer back to the last dma control block */ + cb--; + } else { + /* A single dma control block is enough. */ + int sy, dy, stride; + + if (region->dy <= region->sy) { + /* processing from top to bottom */ + dy = region->dy; + sy = region->sy; + stride = fb->fb.fix.line_length; + } else { + /* processing from bottom to top */ + dy = region->dy + region->height - 1; + sy = region->sy + region->height - 1; + stride = -fb->fb.fix.line_length; + } + set_dma_cb(cb, burst_size, + fb->fb_bus_address + dy * fb->fb.fix.line_length + + bytes_per_pixel * region->dx, + stride, + fb->fb_bus_address + sy * fb->fb.fix.line_length + + bytes_per_pixel * region->sx, + stride, + region->width * bytes_per_pixel, + region->height); + } + + /* end of dma control blocks chain */ + cb->next = 0; + + if (pixels < dma_busy_wait_threshold) { + bcm_dma_start(fbdev->dma_chan_base, fbdev->cb_handle); + bcm_dma_wait_idle(fbdev->dma_chan_base); + } else { + void __iomem *local_dma_chan = fbdev->dma_chan_base; + + cb->info |= BCM2708_DMA_INT_EN; + bcm_dma_start(fbdev->dma_chan_base, fbdev->cb_handle); + while (bcm_dma_is_busy(local_dma_chan)) { + wait_event_interruptible(fbdev->dma_waitq, + !bcm_dma_is_busy(local_dma_chan)); + } + fbdev->dma_stats.dma_irqs++; + } + fbdev->dma_stats.dma_copies++; + + mutex_unlock(&fbdev->dma_mutex); +} + +static irqreturn_t bcm2708_fb_dma_irq(int irq, void *cxt) +{ + struct bcm2708_fb_dev *fbdev = cxt; + + /* FIXME: should read status register to check if this is + * actually interrupting us or not, in case this interrupt + * ever becomes shared amongst several DMA channels + * + * readl(dma_chan_base + BCM2708_DMA_CS) & BCM2708_DMA_IRQ; + */ + + /* acknowledge the interrupt */ + writel(BCM2708_DMA_INT, fbdev->dma_chan_base + BCM2708_DMA_CS); + + wake_up(&fbdev->dma_waitq); + return IRQ_HANDLED; +} + +static struct fb_ops bcm2708_fb_ops = { + .owner = THIS_MODULE, + __FB_DEFAULT_IOMEM_OPS_RDWR, + .fb_check_var = bcm2708_fb_check_var, + .fb_set_par = bcm2708_fb_set_par, + .fb_setcolreg = bcm2708_fb_setcolreg, + .fb_blank = bcm2708_fb_blank, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = bcm2708_fb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_pan_display = bcm2708_fb_pan_display, + .fb_ioctl = bcm2708_ioctl, +#ifdef CONFIG_COMPAT + .fb_compat_ioctl = bcm2708_compat_ioctl, +#endif + __FB_DEFAULT_IOMEM_OPS_MMAP, +}; + +static int bcm2708_fb_register(struct bcm2708_fb *fb) +{ + int ret; + + fb->fb.fbops = &bcm2708_fb_ops; + fb->fb.flags = FBINFO_HWACCEL_COPYAREA; + fb->fb.pseudo_palette = fb->cmap; + + strncpy(fb->fb.fix.id, bcm2708_name, sizeof(fb->fb.fix.id)); + fb->fb.fix.type = FB_TYPE_PACKED_PIXELS; + fb->fb.fix.type_aux = 0; + fb->fb.fix.xpanstep = 1; + fb->fb.fix.ypanstep = 1; + fb->fb.fix.ywrapstep = 0; + fb->fb.fix.accel = FB_ACCEL_NONE; + + /* If we have data from the VC4 on FB's, use that, otherwise use the + * module parameters + */ + if (fb->display_settings.width) { + fb->fb.var.xres = fb->display_settings.width; + fb->fb.var.yres = fb->display_settings.height; + fb->fb.var.xres_virtual = fb->fb.var.xres; + fb->fb.var.yres_virtual = fb->fb.var.yres; + fb->fb.var.bits_per_pixel = fb->display_settings.depth; + } else { + fb->fb.var.xres = fbwidth; + fb->fb.var.yres = fbheight; + fb->fb.var.xres_virtual = fbwidth; + fb->fb.var.yres_virtual = fbheight; + fb->fb.var.bits_per_pixel = fbdepth; + } + + fb->fb.var.vmode = FB_VMODE_NONINTERLACED; + fb->fb.var.activate = FB_ACTIVATE_NOW; + fb->fb.var.nonstd = 0; + fb->fb.var.height = -1; /* height of picture in mm */ + fb->fb.var.width = -1; /* width of picture in mm */ + fb->fb.var.accel_flags = 0; + + fb->fb.monspecs.hfmin = 0; + fb->fb.monspecs.hfmax = 100000; + fb->fb.monspecs.vfmin = 0; + fb->fb.monspecs.vfmax = 400; + fb->fb.monspecs.dclkmin = 1000000; + fb->fb.monspecs.dclkmax = 100000000; + + bcm2708_fb_set_bitfields(&fb->fb.var); + + /* + * Allocate colourmap. + */ + fb_set_var(&fb->fb, &fb->fb.var); + + ret = bcm2708_fb_set_par(&fb->fb); + + if (ret) + return ret; + + ret = register_framebuffer(&fb->fb); + + if (ret == 0) + goto out; + + dev_warn(fb->fb.dev, "Unable to register framebuffer (%d)\n", ret); +out: + return ret; +} + +static int bcm2708_fb_probe(struct platform_device *dev) +{ + struct device_node *fw_np; + struct rpi_firmware *fw; + int ret, i; + u32 num_displays; + struct bcm2708_fb_dev *fbdev; + struct { u32 base, length; } gpu_mem; + + fbdev = devm_kzalloc(&dev->dev, sizeof(*fbdev), GFP_KERNEL); + + if (!fbdev) + return -ENOMEM; + + fw_np = of_parse_phandle(dev->dev.of_node, "firmware", 0); + +/* Remove comment when booting without Device Tree is no longer supported + * if (!fw_np) { + * dev_err(&dev->dev, "Missing firmware node\n"); + * return -ENOENT; + * } + */ + fw = rpi_firmware_get(fw_np); + fbdev->fw = fw; + + if (!fw) + return -EPROBE_DEFER; + + ret = rpi_firmware_property(fw, + RPI_FIRMWARE_FRAMEBUFFER_GET_NUM_DISPLAYS, + &num_displays, sizeof(u32)); + + /* If we fail to get the number of displays, or it returns 0, then + * assume old firmware that doesn't have the mailbox call, so just + * set one display + */ + if (ret || num_displays == 0) { + dev_err(&dev->dev, + "Unable to determine number of FBs. Disabling driver.\n"); + return -ENOENT; + } else { + fbdev->firmware_supports_multifb = 1; + } + + if (num_displays > MAX_FRAMEBUFFERS) { + dev_warn(&dev->dev, + "More displays reported from firmware than supported in driver (%u vs %u)", + num_displays, MAX_FRAMEBUFFERS); + num_displays = MAX_FRAMEBUFFERS; + } + + dev_info(&dev->dev, "FB found %d display(s)\n", num_displays); + + /* Set up the DMA information. Note we have just one set of DMA + * parameters to work with all the FB's so requires synchronising when + * being used + */ + + mutex_init(&fbdev->dma_mutex); + + fbdev->cb_base = dma_alloc_wc(&dev->dev, SZ_64K, + &fbdev->cb_handle, + GFP_KERNEL); + if (!fbdev->cb_base) { + dev_err(&dev->dev, "cannot allocate DMA CBs\n"); + ret = -ENOMEM; + goto free_fb; + } + + ret = bcm_dma_chan_alloc(BCM_DMA_FEATURE_BULK, + &fbdev->dma_chan_base, + &fbdev->dma_irq); + if (ret < 0) { + dev_err(&dev->dev, "Couldn't allocate a DMA channel\n"); + goto free_cb; + } + fbdev->dma_chan = ret; + + ret = request_irq(fbdev->dma_irq, bcm2708_fb_dma_irq, + 0, "bcm2708_fb DMA", fbdev); + if (ret) { + dev_err(&dev->dev, + "Failed to request DMA irq\n"); + goto free_dma_chan; + } + + rpi_firmware_property(fbdev->fw, + RPI_FIRMWARE_GET_VC_MEMORY, + &gpu_mem, sizeof(gpu_mem)); + + for (i = 0; i < num_displays; i++) { + struct bcm2708_fb *fb = &fbdev->displays[i]; + + fb->display_settings.display_num = i; + fb->dev = dev; + fb->fb.device = &dev->dev; + fb->fbdev = fbdev; + + fb->gpu.base = gpu_mem.base; + fb->gpu.length = gpu_mem.length; + + if (fbdev->firmware_supports_multifb) { + ret = rpi_firmware_property(fw, + RPI_FIRMWARE_FRAMEBUFFER_GET_DISPLAY_SETTINGS, + &fb->display_settings, + GET_DISPLAY_SETTINGS_PAYLOAD_SIZE); + } else { + memset(&fb->display_settings, 0, + sizeof(fb->display_settings)); + } + + ret = bcm2708_fb_register(fb); + + if (ret == 0) { + bcm2708_fb_debugfs_init(fb); + + fbdev->num_displays++; + + dev_info(&dev->dev, + "Registered framebuffer for display %u, size %ux%u\n", + fb->display_settings.display_num, + fb->fb.var.xres, + fb->fb.var.yres); + } else { + // Use this to flag if this FB entry is in use. + fb->fbdev = NULL; + } + } + + // Did we actually successfully create any FB's? + if (fbdev->num_displays) { + init_waitqueue_head(&fbdev->dma_waitq); + platform_set_drvdata(dev, fbdev); + return ret; + } + +free_dma_chan: + bcm_dma_chan_free(fbdev->dma_chan); +free_cb: + dma_free_wc(&dev->dev, SZ_64K, fbdev->cb_base, + fbdev->cb_handle); +free_fb: + dev_err(&dev->dev, "probe failed, err %d\n", ret); + + return ret; +} + +static void bcm2708_fb_remove(struct platform_device *dev) +{ + struct bcm2708_fb_dev *fbdev = platform_get_drvdata(dev); + int i; + + platform_set_drvdata(dev, NULL); + + for (i = 0; i < fbdev->num_displays; i++) { + if (fbdev->displays[i].fb.screen_base) + iounmap(fbdev->displays[i].fb.screen_base); + + if (fbdev->displays[i].fbdev) { + unregister_framebuffer(&fbdev->displays[i].fb); + bcm2708_fb_debugfs_deinit(&fbdev->displays[i]); + } + } + + dma_free_wc(&dev->dev, SZ_64K, fbdev->cb_base, + fbdev->cb_handle); + bcm_dma_chan_free(fbdev->dma_chan); + free_irq(fbdev->dma_irq, fbdev); + + mutex_destroy(&fbdev->dma_mutex); +} + +static const struct of_device_id bcm2708_fb_of_match_table[] = { + { .compatible = "brcm,bcm2708-fb", }, + {}, +}; +MODULE_DEVICE_TABLE(of, bcm2708_fb_of_match_table); + +static struct platform_driver bcm2708_fb_driver = { + .probe = bcm2708_fb_probe, + .remove = bcm2708_fb_remove, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = bcm2708_fb_of_match_table, + }, +}; + +static int __init bcm2708_fb_init(void) +{ + return platform_driver_register(&bcm2708_fb_driver); +} + +module_init(bcm2708_fb_init); + +static void __exit bcm2708_fb_exit(void) +{ + platform_driver_unregister(&bcm2708_fb_driver); +} + +module_exit(bcm2708_fb_exit); + +module_param(fbwidth, int, 0644); +module_param(fbheight, int, 0644); +module_param(fbdepth, int, 0644); +module_param(fbswap, int, 0644); + +MODULE_DESCRIPTION("BCM2708 framebuffer driver"); +MODULE_LICENSE("GPL"); + +MODULE_PARM_DESC(fbwidth, "Width of ARM Framebuffer"); +MODULE_PARM_DESC(fbheight, "Height of ARM Framebuffer"); +MODULE_PARM_DESC(fbdepth, "Bit depth of ARM Framebuffer"); +MODULE_PARM_DESC(fbswap, "Swap order of red and blue in 24 and 32 bit modes"); diff --git a/drivers/video/fbdev/core/fb_chrdev.c b/drivers/video/fbdev/core/fb_chrdev.c index 4ebd16b7e3b8d6..e3dd94bb7ef596 100644 --- a/drivers/video/fbdev/core/fb_chrdev.c +++ b/drivers/video/fbdev/core/fb_chrdev.c @@ -59,6 +59,30 @@ static ssize_t fb_write(struct file *file, const char __user *buf, size_t count, return info->fbops->fb_write(info, buf, count, ppos); } +static int fb_copyarea_user(struct fb_info *info, + struct fb_copyarea *copy) +{ + int ret = 0; + lock_fb_info(info); + if (copy->dx >= info->var.xres || + copy->sx >= info->var.xres || + copy->width > info->var.xres || + copy->dy >= info->var.yres || + copy->sy >= info->var.yres || + copy->height > info->var.yres || + copy->dx + copy->width > info->var.xres || + copy->sx + copy->width > info->var.xres || + copy->dy + copy->height > info->var.yres || + copy->sy + copy->height > info->var.yres) { + ret = -EINVAL; + goto out; + } + info->fbops->fb_copyarea(info, copy); +out: + unlock_fb_info(info); + return ret; +} + static long do_fb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg) { @@ -67,6 +91,7 @@ static long do_fb_ioctl(struct fb_info *info, unsigned int cmd, struct fb_fix_screeninfo fix; struct fb_cmap cmap_from; struct fb_cmap_user cmap; + struct fb_copyarea copy; void __user *argp = (void __user *)arg; long ret = 0; @@ -148,6 +173,15 @@ static long do_fb_ioctl(struct fb_info *info, unsigned int cmd, unlock_fb_info(info); console_unlock(); break; + case FBIOCOPYAREA: + if (info->flags & FBINFO_HWACCEL_COPYAREA) { + /* only provide this ioctl if it is accelerated */ + if (copy_from_user(©, argp, sizeof(copy))) + return -EFAULT; + ret = fb_copyarea_user(info, ©); + break; + } + fallthrough; default: lock_fb_info(info); fb = info->fbops; @@ -287,6 +321,7 @@ static long fb_compat_ioctl(struct file *file, unsigned int cmd, case FBIOPAN_DISPLAY: case FBIOGET_CON2FBMAP: case FBIOPUT_CON2FBMAP: + case FBIOCOPYAREA: arg = (unsigned long) compat_ptr(arg); fallthrough; case FBIOBLANK: diff --git a/drivers/video/fbdev/core/fb_defio.c b/drivers/video/fbdev/core/fb_defio.c index 65363df8e81b5a..605632f7c24544 100644 --- a/drivers/video/fbdev/core/fb_defio.c +++ b/drivers/video/fbdev/core/fb_defio.c @@ -346,7 +346,8 @@ static void fb_deferred_io_lastclose(struct fb_info *info) { unsigned long i; - flush_delayed_work(&info->deferred_work); + if (!list_empty(&info->fbdefio->pagereflist)) + flush_delayed_work(&info->deferred_work); /* clear out the mapping that we setup */ for (i = 0; i < info->npagerefs; ++i) diff --git a/drivers/video/fbdev/core/fbmem.c b/drivers/video/fbdev/core/fbmem.c index 3c568cff2913e4..d8ffb2c624538f 100644 --- a/drivers/video/fbdev/core/fbmem.c +++ b/drivers/video/fbdev/core/fbmem.c @@ -31,6 +31,7 @@ struct class *fb_class; DEFINE_MUTEX(registration_lock); struct fb_info *registered_fb[FB_MAX] __read_mostly; int num_registered_fb __read_mostly; +int min_dynamic_fb __read_mostly; #define for_each_registered_fb(i) \ for (i = 0; i < FB_MAX; i++) \ if (!registered_fb[i]) {} else @@ -398,10 +399,12 @@ static int do_register_framebuffer(struct fb_info *fb_info) return -ENXIO; num_registered_fb++; - for (i = 0 ; i < FB_MAX; i++) - if (!registered_fb[i]) - break; - fb_info->node = i; + if (!fb_info->custom_fb_num || fb_info->node >= FB_MAX || registered_fb[fb_info->node]) { + for (i = min_dynamic_fb ; i < FB_MAX; i++) + if (!registered_fb[i]) + break; + fb_info->node = i; + } refcount_set(&fb_info->count, 1); mutex_init(&fb_info->lock); mutex_init(&fb_info->mm_lock); @@ -436,7 +439,7 @@ static int do_register_framebuffer(struct fb_info *fb_info) fb_var_to_videomode(&mode, &fb_info->var); fb_add_videomode(&mode, &fb_info->modelist); - registered_fb[i] = fb_info; + registered_fb[fb_info->node] = fb_info; #ifdef CONFIG_GUMSTIX_AM200EPD { @@ -497,6 +500,12 @@ static void do_unregister_framebuffer(struct fb_info *fb_info) put_fb_info(fb_info); } +void fb_set_lowest_dynamic_fb(int min_fb_dev) +{ + min_dynamic_fb = min_fb_dev; +} +EXPORT_SYMBOL(fb_set_lowest_dynamic_fb); + /** * register_framebuffer - registers a frame buffer device * @fb_info: frame buffer info structure diff --git a/drivers/video/fbdev/rpisense-fb.c b/drivers/video/fbdev/rpisense-fb.c new file mode 100644 index 00000000000000..a2a63ddbe012e6 --- /dev/null +++ b/drivers/video/fbdev/rpisense-fb.c @@ -0,0 +1,295 @@ +/* + * Raspberry Pi Sense HAT framebuffer driver + * http://raspberrypi.org + * + * Copyright (C) 2015 Raspberry Pi + * + * Author: Serge Schneider + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/delay.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/vmalloc.h> +#include <linux/platform_device.h> + +#include <linux/mfd/rpisense/framebuffer.h> + +static bool lowlight; +module_param(lowlight, bool, 0); +MODULE_PARM_DESC(lowlight, "Reduce LED matrix brightness to one third"); + +struct rpisense_fb_param { + char __iomem *vmem; + u8 *vmem_work; + u32 vmemsize; + u8 *gamma; +}; + +static u8 gamma_default[32] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, + 0x02, 0x02, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0E, 0x0F, 0x11, + 0x12, 0x14, 0x15, 0x17, 0x19, 0x1B, 0x1D, 0x1F,}; + +static u8 gamma_low[32] = {0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, + 0x03, 0x03, 0x03, 0x04, 0x04, 0x05, 0x05, 0x06, + 0x06, 0x07, 0x07, 0x08, 0x08, 0x09, 0x0A, 0x0A,}; + +static u8 gamma_user[32]; + +static u32 pseudo_palette[16]; + +static struct rpisense_fb_param rpisense_fb_param = { + .vmem = NULL, + .vmemsize = 128, + .gamma = gamma_default, +}; + +static struct fb_deferred_io rpisense_fb_defio; + +static struct fb_fix_screeninfo rpisense_fb_fix = { + .id = "RPi-Sense FB", + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_TRUECOLOR, + .xpanstep = 0, + .ypanstep = 0, + .ywrapstep = 0, + .accel = FB_ACCEL_NONE, + .line_length = 16, +}; + +static struct fb_var_screeninfo rpisense_fb_var = { + .xres = 8, + .yres = 8, + .xres_virtual = 8, + .yres_virtual = 8, + .bits_per_pixel = 16, + .red = {11, 5, 0}, + .green = {5, 6, 0}, + .blue = {0, 5, 0}, +}; + +static ssize_t rpisense_fb_write(struct fb_info *info, + const char __user *buf, size_t count, + loff_t *ppos) +{ + ssize_t res = fb_sys_write(info, buf, count, ppos); + + schedule_delayed_work(&info->deferred_work, rpisense_fb_defio.delay); + return res; +} + +static void rpisense_fb_fillrect(struct fb_info *info, + const struct fb_fillrect *rect) +{ + sys_fillrect(info, rect); + schedule_delayed_work(&info->deferred_work, rpisense_fb_defio.delay); +} + +static void rpisense_fb_copyarea(struct fb_info *info, + const struct fb_copyarea *area) +{ + sys_copyarea(info, area); + schedule_delayed_work(&info->deferred_work, rpisense_fb_defio.delay); +} + +static void rpisense_fb_imageblit(struct fb_info *info, + const struct fb_image *image) +{ + sys_imageblit(info, image); + schedule_delayed_work(&info->deferred_work, rpisense_fb_defio.delay); +} + +static void rpisense_fb_deferred_io(struct fb_info *info, + struct list_head *pagelist) +{ + int i; + int j; + u8 *vmem_work = rpisense_fb_param.vmem_work; + u16 *mem = (u16 *)rpisense_fb_param.vmem; + u8 *gamma = rpisense_fb_param.gamma; + struct rpisense_fb *rpisense_fb = info->par; + + for (j = 0; j < 8; j++) { + for (i = 0; i < 8; i++) { + vmem_work[(j * 24) + i] = + gamma[(mem[(j * 8) + i] >> 11) & 0x1F]; + vmem_work[(j * 24) + (i + 8)] = + gamma[(mem[(j * 8) + i] >> 6) & 0x1F]; + vmem_work[(j * 24) + (i + 16)] = + gamma[(mem[(j * 8) + i]) & 0x1F]; + } + } + regmap_bulk_write(rpisense_fb->regmap, 0, vmem_work, 192); +} + +static struct fb_deferred_io rpisense_fb_defio = { + .delay = HZ/100, + .deferred_io = rpisense_fb_deferred_io, +}; + +static int rpisense_fb_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + switch (cmd) { + case SENSEFB_FBIOGET_GAMMA: + if (copy_to_user((void __user *) arg, rpisense_fb_param.gamma, + sizeof(u8[32]))) + return -EFAULT; + return 0; + case SENSEFB_FBIOSET_GAMMA: + if (copy_from_user(gamma_user, (void __user *)arg, + sizeof(u8[32]))) + return -EFAULT; + rpisense_fb_param.gamma = gamma_user; + schedule_delayed_work(&info->deferred_work, + rpisense_fb_defio.delay); + return 0; + case SENSEFB_FBIORESET_GAMMA: + switch (arg) { + case 0: + rpisense_fb_param.gamma = gamma_default; + break; + case 1: + rpisense_fb_param.gamma = gamma_low; + break; + case 2: + rpisense_fb_param.gamma = gamma_user; + break; + default: + return -EINVAL; + } + schedule_delayed_work(&info->deferred_work, + rpisense_fb_defio.delay); + break; + default: + return -EINVAL; + } + return 0; +} + +static struct fb_ops rpisense_fb_ops = { + .owner = THIS_MODULE, + .fb_read = fb_sys_read, + .fb_write = rpisense_fb_write, + .fb_fillrect = rpisense_fb_fillrect, + .fb_copyarea = rpisense_fb_copyarea, + .fb_imageblit = rpisense_fb_imageblit, + .fb_ioctl = rpisense_fb_ioctl, + .fb_mmap = fb_deferred_io_mmap, +}; + +static int rpisense_fb_probe(struct platform_device *pdev) +{ + struct fb_info *info; + int ret = -ENOMEM; + struct rpisense_fb *rpisense_fb; + + info = framebuffer_alloc(sizeof(*rpisense_fb), &pdev->dev); + if (!info) { + dev_err(&pdev->dev, "Could not allocate framebuffer.\n"); + goto err_malloc; + } + + rpisense_fb = info->par; + platform_set_drvdata(pdev, rpisense_fb); + + rpisense_fb->pdev = pdev; + rpisense_fb->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!rpisense_fb->regmap) { + dev_err(&pdev->dev, + "unable to get sensehat regmap"); + return -ENODEV; + } + + rpisense_fb_param.vmem = vzalloc(rpisense_fb_param.vmemsize); + if (!rpisense_fb_param.vmem) + return ret; + + rpisense_fb_param.vmem_work = devm_kmalloc(&pdev->dev, 193, GFP_KERNEL); + if (!rpisense_fb_param.vmem_work) + goto err_malloc; + + + rpisense_fb_fix.smem_start = (unsigned long)rpisense_fb_param.vmem; + rpisense_fb_fix.smem_len = rpisense_fb_param.vmemsize; + + info->fbops = &rpisense_fb_ops; + info->fix = rpisense_fb_fix; + info->var = rpisense_fb_var; + info->fbdefio = &rpisense_fb_defio; + info->flags = FBINFO_VIRTFB; + info->screen_base = rpisense_fb_param.vmem; + info->screen_size = rpisense_fb_param.vmemsize; + info->pseudo_palette = pseudo_palette; + + if (lowlight) + rpisense_fb_param.gamma = gamma_low; + + fb_deferred_io_init(info); + + ret = register_framebuffer(info); + if (ret < 0) { + dev_err(&pdev->dev, "Could not register framebuffer.\n"); + goto err_fballoc; + } + + fb_info(info, "%s frame buffer device\n", info->fix.id); + schedule_delayed_work(&info->deferred_work, rpisense_fb_defio.delay); + return 0; +err_fballoc: + framebuffer_release(info); +err_malloc: + vfree(rpisense_fb_param.vmem); + return ret; +} + +static void rpisense_fb_remove(struct platform_device *pdev) +{ + struct rpisense_fb *rpisense_fb = platform_get_drvdata(pdev); + struct fb_info *info = rpisense_fb->info; + + if (info) { + unregister_framebuffer(info); + fb_deferred_io_cleanup(info); + framebuffer_release(info); + vfree(rpisense_fb_param.vmem); + } +} + +static const struct of_device_id rpisense_fb_id[] = { + { .compatible = "raspberrypi,rpi-sense-fb" }, + { }, +}; +MODULE_DEVICE_TABLE(of, rpisense_fb_id); + +static struct platform_driver rpisense_fb_driver = { + .probe = rpisense_fb_probe, + .remove = rpisense_fb_remove, + .driver = { + .name = "rpi-sense-fb", + .owner = THIS_MODULE, + .of_match_table = rpisense_fb_id, + }, +}; + +module_platform_driver(rpisense_fb_driver); + +MODULE_DESCRIPTION("Raspberry Pi Sense HAT framebuffer driver"); +MODULE_AUTHOR("Serge Schneider <serge@raspberrypi.org>"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/video/logo/logo_linux_clut224.ppm b/drivers/video/logo/logo_linux_clut224.ppm index 3c14e43b82fefe..7626beb6a5bb8d 100644 --- a/drivers/video/logo/logo_linux_clut224.ppm +++ b/drivers/video/logo/logo_linux_clut224.ppm @@ -1,1604 +1,883 @@ P3 -# Standard 224-color Linux logo -80 80 +63 80 255 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 6 6 6 6 6 6 10 10 10 10 10 10 - 10 10 10 6 6 6 6 6 6 6 6 6 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 6 6 6 10 10 10 14 14 14 - 22 22 22 26 26 26 30 30 30 34 34 34 - 30 30 30 30 30 30 26 26 26 18 18 18 - 14 14 14 10 10 10 6 6 6 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 1 0 0 1 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 6 6 6 14 14 14 26 26 26 42 42 42 - 54 54 54 66 66 66 78 78 78 78 78 78 - 78 78 78 74 74 74 66 66 66 54 54 54 - 42 42 42 26 26 26 18 18 18 10 10 10 - 6 6 6 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 1 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 10 10 10 - 22 22 22 42 42 42 66 66 66 86 86 86 - 66 66 66 38 38 38 38 38 38 22 22 22 - 26 26 26 34 34 34 54 54 54 66 66 66 - 86 86 86 70 70 70 46 46 46 26 26 26 - 14 14 14 6 6 6 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 1 0 0 1 0 0 1 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 10 10 10 26 26 26 - 50 50 50 82 82 82 58 58 58 6 6 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 6 6 6 54 54 54 86 86 86 66 66 66 - 38 38 38 18 18 18 6 6 6 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 6 6 6 22 22 22 50 50 50 - 78 78 78 34 34 34 2 2 6 2 2 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 2 2 6 2 2 6 6 6 6 70 70 70 - 78 78 78 46 46 46 22 22 22 6 6 6 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 1 0 0 1 0 0 1 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 6 6 6 18 18 18 42 42 42 82 82 82 - 26 26 26 2 2 6 2 2 6 2 2 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 2 2 6 2 2 6 2 2 6 14 14 14 - 46 46 46 34 34 34 6 6 6 2 2 6 - 42 42 42 78 78 78 42 42 42 18 18 18 - 6 6 6 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 1 0 0 0 0 0 1 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 10 10 10 30 30 30 66 66 66 58 58 58 - 2 2 6 2 2 6 2 2 6 2 2 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 2 2 6 2 2 6 2 2 6 26 26 26 - 86 86 86 101 101 101 46 46 46 10 10 10 - 2 2 6 58 58 58 70 70 70 34 34 34 - 10 10 10 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 1 0 0 1 0 0 1 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 14 14 14 42 42 42 86 86 86 10 10 10 - 2 2 6 2 2 6 2 2 6 2 2 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 2 2 6 2 2 6 2 2 6 30 30 30 - 94 94 94 94 94 94 58 58 58 26 26 26 - 2 2 6 6 6 6 78 78 78 54 54 54 - 22 22 22 6 6 6 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 6 6 6 - 22 22 22 62 62 62 62 62 62 2 2 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 2 2 6 2 2 6 2 2 6 26 26 26 - 54 54 54 38 38 38 18 18 18 10 10 10 - 2 2 6 2 2 6 34 34 34 82 82 82 - 38 38 38 14 14 14 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 1 0 0 1 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 6 6 6 - 30 30 30 78 78 78 30 30 30 2 2 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 2 2 6 2 2 6 2 2 6 10 10 10 - 10 10 10 2 2 6 2 2 6 2 2 6 - 2 2 6 2 2 6 2 2 6 78 78 78 - 50 50 50 18 18 18 6 6 6 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 1 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 10 10 10 - 38 38 38 86 86 86 14 14 14 2 2 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 2 2 6 2 2 6 2 2 6 54 54 54 - 66 66 66 26 26 26 6 6 6 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 1 0 0 1 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 14 14 14 - 42 42 42 82 82 82 2 2 6 2 2 6 - 2 2 6 6 6 6 10 10 10 2 2 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 2 2 6 2 2 6 2 2 6 6 6 6 - 14 14 14 10 10 10 2 2 6 2 2 6 - 2 2 6 2 2 6 2 2 6 18 18 18 - 82 82 82 34 34 34 10 10 10 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 1 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 14 14 14 - 46 46 46 86 86 86 2 2 6 2 2 6 - 6 6 6 6 6 6 22 22 22 34 34 34 - 6 6 6 2 2 6 2 2 6 2 2 6 - 2 2 6 2 2 6 18 18 18 34 34 34 - 10 10 10 50 50 50 22 22 22 2 2 6 - 2 2 6 2 2 6 2 2 6 10 10 10 - 86 86 86 42 42 42 14 14 14 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 1 0 0 1 0 0 1 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 14 14 14 - 46 46 46 86 86 86 2 2 6 2 2 6 - 38 38 38 116 116 116 94 94 94 22 22 22 - 22 22 22 2 2 6 2 2 6 2 2 6 - 14 14 14 86 86 86 138 138 138 162 162 162 -154 154 154 38 38 38 26 26 26 6 6 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 86 86 86 46 46 46 14 14 14 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 14 14 14 - 46 46 46 86 86 86 2 2 6 14 14 14 -134 134 134 198 198 198 195 195 195 116 116 116 - 10 10 10 2 2 6 2 2 6 6 6 6 -101 98 89 187 187 187 210 210 210 218 218 218 -214 214 214 134 134 134 14 14 14 6 6 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 86 86 86 50 50 50 18 18 18 6 6 6 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 1 0 0 0 - 0 0 1 0 0 1 0 0 1 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 14 14 14 - 46 46 46 86 86 86 2 2 6 54 54 54 -218 218 218 195 195 195 226 226 226 246 246 246 - 58 58 58 2 2 6 2 2 6 30 30 30 -210 210 210 253 253 253 174 174 174 123 123 123 -221 221 221 234 234 234 74 74 74 2 2 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 70 70 70 58 58 58 22 22 22 6 6 6 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 14 14 14 - 46 46 46 82 82 82 2 2 6 106 106 106 -170 170 170 26 26 26 86 86 86 226 226 226 -123 123 123 10 10 10 14 14 14 46 46 46 -231 231 231 190 190 190 6 6 6 70 70 70 - 90 90 90 238 238 238 158 158 158 2 2 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 70 70 70 58 58 58 22 22 22 6 6 6 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 1 0 0 0 - 0 0 1 0 0 1 0 0 1 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 14 14 14 - 42 42 42 86 86 86 6 6 6 116 116 116 -106 106 106 6 6 6 70 70 70 149 149 149 -128 128 128 18 18 18 38 38 38 54 54 54 -221 221 221 106 106 106 2 2 6 14 14 14 - 46 46 46 190 190 190 198 198 198 2 2 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 74 74 74 62 62 62 22 22 22 6 6 6 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 1 0 0 0 - 0 0 1 0 0 0 0 0 1 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 14 14 14 - 42 42 42 94 94 94 14 14 14 101 101 101 -128 128 128 2 2 6 18 18 18 116 116 116 -118 98 46 121 92 8 121 92 8 98 78 10 -162 162 162 106 106 106 2 2 6 2 2 6 - 2 2 6 195 195 195 195 195 195 6 6 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 74 74 74 62 62 62 22 22 22 6 6 6 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 1 0 0 1 - 0 0 1 0 0 0 0 0 1 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 10 10 10 - 38 38 38 90 90 90 14 14 14 58 58 58 -210 210 210 26 26 26 54 38 6 154 114 10 -226 170 11 236 186 11 225 175 15 184 144 12 -215 174 15 175 146 61 37 26 9 2 2 6 - 70 70 70 246 246 246 138 138 138 2 2 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 70 70 70 66 66 66 26 26 26 6 6 6 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 10 10 10 - 38 38 38 86 86 86 14 14 14 10 10 10 -195 195 195 188 164 115 192 133 9 225 175 15 -239 182 13 234 190 10 232 195 16 232 200 30 -245 207 45 241 208 19 232 195 16 184 144 12 -218 194 134 211 206 186 42 42 42 2 2 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 50 50 50 74 74 74 30 30 30 6 6 6 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 10 10 10 - 34 34 34 86 86 86 14 14 14 2 2 6 -121 87 25 192 133 9 219 162 10 239 182 13 -236 186 11 232 195 16 241 208 19 244 214 54 -246 218 60 246 218 38 246 215 20 241 208 19 -241 208 19 226 184 13 121 87 25 2 2 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 50 50 50 82 82 82 34 34 34 10 10 10 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 10 10 10 - 34 34 34 82 82 82 30 30 30 61 42 6 -180 123 7 206 145 10 230 174 11 239 182 13 -234 190 10 238 202 15 241 208 19 246 218 74 -246 218 38 246 215 20 246 215 20 246 215 20 -226 184 13 215 174 15 184 144 12 6 6 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 26 26 26 94 94 94 42 42 42 14 14 14 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 10 10 10 - 30 30 30 78 78 78 50 50 50 104 69 6 -192 133 9 216 158 10 236 178 12 236 186 11 -232 195 16 241 208 19 244 214 54 245 215 43 -246 215 20 246 215 20 241 208 19 198 155 10 -200 144 11 216 158 10 156 118 10 2 2 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 6 6 6 90 90 90 54 54 54 18 18 18 - 6 6 6 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 10 10 10 - 30 30 30 78 78 78 46 46 46 22 22 22 -137 92 6 210 162 10 239 182 13 238 190 10 -238 202 15 241 208 19 246 215 20 246 215 20 -241 208 19 203 166 17 185 133 11 210 150 10 -216 158 10 210 150 10 102 78 10 2 2 6 - 6 6 6 54 54 54 14 14 14 2 2 6 - 2 2 6 62 62 62 74 74 74 30 30 30 - 10 10 10 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 10 10 10 - 34 34 34 78 78 78 50 50 50 6 6 6 - 94 70 30 139 102 15 190 146 13 226 184 13 -232 200 30 232 195 16 215 174 15 190 146 13 -168 122 10 192 133 9 210 150 10 213 154 11 -202 150 34 182 157 106 101 98 89 2 2 6 - 2 2 6 78 78 78 116 116 116 58 58 58 - 2 2 6 22 22 22 90 90 90 46 46 46 - 18 18 18 6 6 6 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 10 10 10 - 38 38 38 86 86 86 50 50 50 6 6 6 -128 128 128 174 154 114 156 107 11 168 122 10 -198 155 10 184 144 12 197 138 11 200 144 11 -206 145 10 206 145 10 197 138 11 188 164 115 -195 195 195 198 198 198 174 174 174 14 14 14 - 2 2 6 22 22 22 116 116 116 116 116 116 - 22 22 22 2 2 6 74 74 74 70 70 70 - 30 30 30 10 10 10 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 6 6 6 18 18 18 - 50 50 50 101 101 101 26 26 26 10 10 10 -138 138 138 190 190 190 174 154 114 156 107 11 -197 138 11 200 144 11 197 138 11 192 133 9 -180 123 7 190 142 34 190 178 144 187 187 187 -202 202 202 221 221 221 214 214 214 66 66 66 - 2 2 6 2 2 6 50 50 50 62 62 62 - 6 6 6 2 2 6 10 10 10 90 90 90 - 50 50 50 18 18 18 6 6 6 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 10 10 10 34 34 34 - 74 74 74 74 74 74 2 2 6 6 6 6 -144 144 144 198 198 198 190 190 190 178 166 146 -154 121 60 156 107 11 156 107 11 168 124 44 -174 154 114 187 187 187 190 190 190 210 210 210 -246 246 246 253 253 253 253 253 253 182 182 182 - 6 6 6 2 2 6 2 2 6 2 2 6 - 2 2 6 2 2 6 2 2 6 62 62 62 - 74 74 74 34 34 34 14 14 14 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 10 10 10 22 22 22 54 54 54 - 94 94 94 18 18 18 2 2 6 46 46 46 -234 234 234 221 221 221 190 190 190 190 190 190 -190 190 190 187 187 187 187 187 187 190 190 190 -190 190 190 195 195 195 214 214 214 242 242 242 -253 253 253 253 253 253 253 253 253 253 253 253 - 82 82 82 2 2 6 2 2 6 2 2 6 - 2 2 6 2 2 6 2 2 6 14 14 14 - 86 86 86 54 54 54 22 22 22 6 6 6 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 6 6 6 18 18 18 46 46 46 90 90 90 - 46 46 46 18 18 18 6 6 6 182 182 182 -253 253 253 246 246 246 206 206 206 190 190 190 -190 190 190 190 190 190 190 190 190 190 190 190 -206 206 206 231 231 231 250 250 250 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -202 202 202 14 14 14 2 2 6 2 2 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 42 42 42 86 86 86 42 42 42 18 18 18 - 6 6 6 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 6 6 6 - 14 14 14 38 38 38 74 74 74 66 66 66 - 2 2 6 6 6 6 90 90 90 250 250 250 -253 253 253 253 253 253 238 238 238 198 198 198 -190 190 190 190 190 190 195 195 195 221 221 221 -246 246 246 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 82 82 82 2 2 6 2 2 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 2 2 6 78 78 78 70 70 70 34 34 34 - 14 14 14 6 6 6 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 14 14 14 - 34 34 34 66 66 66 78 78 78 6 6 6 - 2 2 6 18 18 18 218 218 218 253 253 253 -253 253 253 253 253 253 253 253 253 246 246 246 -226 226 226 231 231 231 246 246 246 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 178 178 178 2 2 6 2 2 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 2 2 6 18 18 18 90 90 90 62 62 62 - 30 30 30 10 10 10 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 10 10 10 26 26 26 - 58 58 58 90 90 90 18 18 18 2 2 6 - 2 2 6 110 110 110 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -250 250 250 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 231 231 231 18 18 18 2 2 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 2 2 6 2 2 6 18 18 18 94 94 94 - 54 54 54 26 26 26 10 10 10 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 6 6 6 22 22 22 50 50 50 - 90 90 90 26 26 26 2 2 6 2 2 6 - 14 14 14 195 195 195 250 250 250 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -250 250 250 242 242 242 54 54 54 2 2 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 2 2 6 2 2 6 2 2 6 38 38 38 - 86 86 86 50 50 50 22 22 22 6 6 6 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 6 6 6 14 14 14 38 38 38 82 82 82 - 34 34 34 2 2 6 2 2 6 2 2 6 - 42 42 42 195 195 195 246 246 246 253 253 253 -253 253 253 253 253 253 253 253 253 250 250 250 -242 242 242 242 242 242 250 250 250 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 250 250 250 246 246 246 238 238 238 -226 226 226 231 231 231 101 101 101 6 6 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 38 38 38 82 82 82 42 42 42 14 14 14 - 6 6 6 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 10 10 10 26 26 26 62 62 62 66 66 66 - 2 2 6 2 2 6 2 2 6 6 6 6 - 70 70 70 170 170 170 206 206 206 234 234 234 -246 246 246 250 250 250 250 250 250 238 238 238 -226 226 226 231 231 231 238 238 238 250 250 250 -250 250 250 250 250 250 246 246 246 231 231 231 -214 214 214 206 206 206 202 202 202 202 202 202 -198 198 198 202 202 202 182 182 182 18 18 18 - 2 2 6 2 2 6 2 2 6 2 2 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 2 2 6 62 62 62 66 66 66 30 30 30 - 10 10 10 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 14 14 14 42 42 42 82 82 82 18 18 18 - 2 2 6 2 2 6 2 2 6 10 10 10 - 94 94 94 182 182 182 218 218 218 242 242 242 -250 250 250 253 253 253 253 253 253 250 250 250 -234 234 234 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 246 246 246 -238 238 238 226 226 226 210 210 210 202 202 202 -195 195 195 195 195 195 210 210 210 158 158 158 - 6 6 6 14 14 14 50 50 50 14 14 14 - 2 2 6 2 2 6 2 2 6 2 2 6 - 2 2 6 6 6 6 86 86 86 46 46 46 - 18 18 18 6 6 6 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 6 6 6 - 22 22 22 54 54 54 70 70 70 2 2 6 - 2 2 6 10 10 10 2 2 6 22 22 22 -166 166 166 231 231 231 250 250 250 253 253 253 -253 253 253 253 253 253 253 253 253 250 250 250 -242 242 242 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 246 246 246 -231 231 231 206 206 206 198 198 198 226 226 226 - 94 94 94 2 2 6 6 6 6 38 38 38 - 30 30 30 2 2 6 2 2 6 2 2 6 - 2 2 6 2 2 6 62 62 62 66 66 66 - 26 26 26 10 10 10 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 10 10 10 - 30 30 30 74 74 74 50 50 50 2 2 6 - 26 26 26 26 26 26 2 2 6 106 106 106 -238 238 238 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 246 246 246 218 218 218 202 202 202 -210 210 210 14 14 14 2 2 6 2 2 6 - 30 30 30 22 22 22 2 2 6 2 2 6 - 2 2 6 2 2 6 18 18 18 86 86 86 - 42 42 42 14 14 14 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 14 14 14 - 42 42 42 90 90 90 22 22 22 2 2 6 - 42 42 42 2 2 6 18 18 18 218 218 218 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 250 250 250 221 221 221 -218 218 218 101 101 101 2 2 6 14 14 14 - 18 18 18 38 38 38 10 10 10 2 2 6 - 2 2 6 2 2 6 2 2 6 78 78 78 - 58 58 58 22 22 22 6 6 6 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 6 6 6 18 18 18 - 54 54 54 82 82 82 2 2 6 26 26 26 - 22 22 22 2 2 6 123 123 123 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 250 250 250 -238 238 238 198 198 198 6 6 6 38 38 38 - 58 58 58 26 26 26 38 38 38 2 2 6 - 2 2 6 2 2 6 2 2 6 46 46 46 - 78 78 78 30 30 30 10 10 10 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 10 10 10 30 30 30 - 74 74 74 58 58 58 2 2 6 42 42 42 - 2 2 6 22 22 22 231 231 231 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 250 250 250 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 246 246 246 46 46 46 38 38 38 - 42 42 42 14 14 14 38 38 38 14 14 14 - 2 2 6 2 2 6 2 2 6 6 6 6 - 86 86 86 46 46 46 14 14 14 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 6 6 6 14 14 14 42 42 42 - 90 90 90 18 18 18 18 18 18 26 26 26 - 2 2 6 116 116 116 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 250 250 250 238 238 238 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 94 94 94 6 6 6 - 2 2 6 2 2 6 10 10 10 34 34 34 - 2 2 6 2 2 6 2 2 6 2 2 6 - 74 74 74 58 58 58 22 22 22 6 6 6 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 10 10 10 26 26 26 66 66 66 - 82 82 82 2 2 6 38 38 38 6 6 6 - 14 14 14 210 210 210 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 246 246 246 242 242 242 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 144 144 144 2 2 6 - 2 2 6 2 2 6 2 2 6 46 46 46 - 2 2 6 2 2 6 2 2 6 2 2 6 - 42 42 42 74 74 74 30 30 30 10 10 10 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 6 6 6 14 14 14 42 42 42 90 90 90 - 26 26 26 6 6 6 42 42 42 2 2 6 - 74 74 74 250 250 250 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 242 242 242 242 242 242 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 182 182 182 2 2 6 - 2 2 6 2 2 6 2 2 6 46 46 46 - 2 2 6 2 2 6 2 2 6 2 2 6 - 10 10 10 86 86 86 38 38 38 10 10 10 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 10 10 10 26 26 26 66 66 66 82 82 82 - 2 2 6 22 22 22 18 18 18 2 2 6 -149 149 149 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 234 234 234 242 242 242 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 206 206 206 2 2 6 - 2 2 6 2 2 6 2 2 6 38 38 38 - 2 2 6 2 2 6 2 2 6 2 2 6 - 6 6 6 86 86 86 46 46 46 14 14 14 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 6 6 6 - 18 18 18 46 46 46 86 86 86 18 18 18 - 2 2 6 34 34 34 10 10 10 6 6 6 -210 210 210 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 234 234 234 242 242 242 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 221 221 221 6 6 6 - 2 2 6 2 2 6 6 6 6 30 30 30 - 2 2 6 2 2 6 2 2 6 2 2 6 - 2 2 6 82 82 82 54 54 54 18 18 18 - 6 6 6 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 10 10 10 - 26 26 26 66 66 66 62 62 62 2 2 6 - 2 2 6 38 38 38 10 10 10 26 26 26 -238 238 238 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 231 231 231 238 238 238 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 231 231 231 6 6 6 - 2 2 6 2 2 6 10 10 10 30 30 30 - 2 2 6 2 2 6 2 2 6 2 2 6 - 2 2 6 66 66 66 58 58 58 22 22 22 - 6 6 6 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 10 10 10 - 38 38 38 78 78 78 6 6 6 2 2 6 - 2 2 6 46 46 46 14 14 14 42 42 42 -246 246 246 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 231 231 231 242 242 242 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 234 234 234 10 10 10 - 2 2 6 2 2 6 22 22 22 14 14 14 - 2 2 6 2 2 6 2 2 6 2 2 6 - 2 2 6 66 66 66 62 62 62 22 22 22 - 6 6 6 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 6 6 6 18 18 18 - 50 50 50 74 74 74 2 2 6 2 2 6 - 14 14 14 70 70 70 34 34 34 62 62 62 -250 250 250 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 231 231 231 246 246 246 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 234 234 234 14 14 14 - 2 2 6 2 2 6 30 30 30 2 2 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 2 2 6 66 66 66 62 62 62 22 22 22 - 6 6 6 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 6 6 6 18 18 18 - 54 54 54 62 62 62 2 2 6 2 2 6 - 2 2 6 30 30 30 46 46 46 70 70 70 -250 250 250 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 231 231 231 246 246 246 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 226 226 226 10 10 10 - 2 2 6 6 6 6 30 30 30 2 2 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 2 2 6 66 66 66 58 58 58 22 22 22 - 6 6 6 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 6 6 6 22 22 22 - 58 58 58 62 62 62 2 2 6 2 2 6 - 2 2 6 2 2 6 30 30 30 78 78 78 -250 250 250 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 231 231 231 246 246 246 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 206 206 206 2 2 6 - 22 22 22 34 34 34 18 14 6 22 22 22 - 26 26 26 18 18 18 6 6 6 2 2 6 - 2 2 6 82 82 82 54 54 54 18 18 18 - 6 6 6 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 6 6 6 26 26 26 - 62 62 62 106 106 106 74 54 14 185 133 11 -210 162 10 121 92 8 6 6 6 62 62 62 -238 238 238 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 231 231 231 246 246 246 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 158 158 158 18 18 18 - 14 14 14 2 2 6 2 2 6 2 2 6 - 6 6 6 18 18 18 66 66 66 38 38 38 - 6 6 6 94 94 94 50 50 50 18 18 18 - 6 6 6 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 6 6 6 - 10 10 10 10 10 10 18 18 18 38 38 38 - 78 78 78 142 134 106 216 158 10 242 186 14 -246 190 14 246 190 14 156 118 10 10 10 10 - 90 90 90 238 238 238 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 231 231 231 250 250 250 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 246 230 190 -238 204 91 238 204 91 181 142 44 37 26 9 - 2 2 6 2 2 6 2 2 6 2 2 6 - 2 2 6 2 2 6 38 38 38 46 46 46 - 26 26 26 106 106 106 54 54 54 18 18 18 - 6 6 6 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 6 6 6 14 14 14 22 22 22 - 30 30 30 38 38 38 50 50 50 70 70 70 -106 106 106 190 142 34 226 170 11 242 186 14 -246 190 14 246 190 14 246 190 14 154 114 10 - 6 6 6 74 74 74 226 226 226 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 231 231 231 250 250 250 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 228 184 62 -241 196 14 241 208 19 232 195 16 38 30 10 - 2 2 6 2 2 6 2 2 6 2 2 6 - 2 2 6 6 6 6 30 30 30 26 26 26 -203 166 17 154 142 90 66 66 66 26 26 26 - 6 6 6 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 6 6 6 18 18 18 38 38 38 58 58 58 - 78 78 78 86 86 86 101 101 101 123 123 123 -175 146 61 210 150 10 234 174 13 246 186 14 -246 190 14 246 190 14 246 190 14 238 190 10 -102 78 10 2 2 6 46 46 46 198 198 198 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 234 234 234 242 242 242 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 224 178 62 -242 186 14 241 196 14 210 166 10 22 18 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 2 2 6 2 2 6 6 6 6 121 92 8 -238 202 15 232 195 16 82 82 82 34 34 34 - 10 10 10 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 14 14 14 38 38 38 70 70 70 154 122 46 -190 142 34 200 144 11 197 138 11 197 138 11 -213 154 11 226 170 11 242 186 14 246 190 14 -246 190 14 246 190 14 246 190 14 246 190 14 -225 175 15 46 32 6 2 2 6 22 22 22 -158 158 158 250 250 250 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 250 250 250 242 242 242 224 178 62 -239 182 13 236 186 11 213 154 11 46 32 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 2 2 6 2 2 6 61 42 6 225 175 15 -238 190 10 236 186 11 112 100 78 42 42 42 - 14 14 14 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 6 6 6 - 22 22 22 54 54 54 154 122 46 213 154 11 -226 170 11 230 174 11 226 170 11 226 170 11 -236 178 12 242 186 14 246 190 14 246 190 14 -246 190 14 246 190 14 246 190 14 246 190 14 -241 196 14 184 144 12 10 10 10 2 2 6 - 6 6 6 116 116 116 242 242 242 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 231 231 231 198 198 198 214 170 54 -236 178 12 236 178 12 210 150 10 137 92 6 - 18 14 6 2 2 6 2 2 6 2 2 6 - 6 6 6 70 47 6 200 144 11 236 178 12 -239 182 13 239 182 13 124 112 88 58 58 58 - 22 22 22 6 6 6 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 10 10 10 - 30 30 30 70 70 70 180 133 36 226 170 11 -239 182 13 242 186 14 242 186 14 246 186 14 -246 190 14 246 190 14 246 190 14 246 190 14 -246 190 14 246 190 14 246 190 14 246 190 14 -246 190 14 232 195 16 98 70 6 2 2 6 - 2 2 6 2 2 6 66 66 66 221 221 221 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 206 206 206 198 198 198 214 166 58 -230 174 11 230 174 11 216 158 10 192 133 9 -163 110 8 116 81 8 102 78 10 116 81 8 -167 114 7 197 138 11 226 170 11 239 182 13 -242 186 14 242 186 14 162 146 94 78 78 78 - 34 34 34 14 14 14 6 6 6 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 6 6 6 - 30 30 30 78 78 78 190 142 34 226 170 11 -239 182 13 246 190 14 246 190 14 246 190 14 -246 190 14 246 190 14 246 190 14 246 190 14 -246 190 14 246 190 14 246 190 14 246 190 14 -246 190 14 241 196 14 203 166 17 22 18 6 - 2 2 6 2 2 6 2 2 6 38 38 38 -218 218 218 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -250 250 250 206 206 206 198 198 198 202 162 69 -226 170 11 236 178 12 224 166 10 210 150 10 -200 144 11 197 138 11 192 133 9 197 138 11 -210 150 10 226 170 11 242 186 14 246 190 14 -246 190 14 246 186 14 225 175 15 124 112 88 - 62 62 62 30 30 30 14 14 14 6 6 6 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 10 10 10 - 30 30 30 78 78 78 174 135 50 224 166 10 -239 182 13 246 190 14 246 190 14 246 190 14 -246 190 14 246 190 14 246 190 14 246 190 14 -246 190 14 246 190 14 246 190 14 246 190 14 -246 190 14 246 190 14 241 196 14 139 102 15 - 2 2 6 2 2 6 2 2 6 2 2 6 - 78 78 78 250 250 250 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -250 250 250 214 214 214 198 198 198 190 150 46 -219 162 10 236 178 12 234 174 13 224 166 10 -216 158 10 213 154 11 213 154 11 216 158 10 -226 170 11 239 182 13 246 190 14 246 190 14 -246 190 14 246 190 14 242 186 14 206 162 42 -101 101 101 58 58 58 30 30 30 14 14 14 - 6 6 6 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 10 10 10 - 30 30 30 74 74 74 174 135 50 216 158 10 -236 178 12 246 190 14 246 190 14 246 190 14 -246 190 14 246 190 14 246 190 14 246 190 14 -246 190 14 246 190 14 246 190 14 246 190 14 -246 190 14 246 190 14 241 196 14 226 184 13 - 61 42 6 2 2 6 2 2 6 2 2 6 - 22 22 22 238 238 238 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 226 226 226 187 187 187 180 133 36 -216 158 10 236 178 12 239 182 13 236 178 12 -230 174 11 226 170 11 226 170 11 230 174 11 -236 178 12 242 186 14 246 190 14 246 190 14 -246 190 14 246 190 14 246 186 14 239 182 13 -206 162 42 106 106 106 66 66 66 34 34 34 - 14 14 14 6 6 6 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 6 6 6 - 26 26 26 70 70 70 163 133 67 213 154 11 -236 178 12 246 190 14 246 190 14 246 190 14 -246 190 14 246 190 14 246 190 14 246 190 14 -246 190 14 246 190 14 246 190 14 246 190 14 -246 190 14 246 190 14 246 190 14 241 196 14 -190 146 13 18 14 6 2 2 6 2 2 6 - 46 46 46 246 246 246 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 221 221 221 86 86 86 156 107 11 -216 158 10 236 178 12 242 186 14 246 186 14 -242 186 14 239 182 13 239 182 13 242 186 14 -242 186 14 246 186 14 246 190 14 246 190 14 -246 190 14 246 190 14 246 190 14 246 190 14 -242 186 14 225 175 15 142 122 72 66 66 66 - 30 30 30 10 10 10 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 6 6 6 - 26 26 26 70 70 70 163 133 67 210 150 10 -236 178 12 246 190 14 246 190 14 246 190 14 -246 190 14 246 190 14 246 190 14 246 190 14 -246 190 14 246 190 14 246 190 14 246 190 14 -246 190 14 246 190 14 246 190 14 246 190 14 -232 195 16 121 92 8 34 34 34 106 106 106 -221 221 221 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -242 242 242 82 82 82 18 14 6 163 110 8 -216 158 10 236 178 12 242 186 14 246 190 14 -246 190 14 246 190 14 246 190 14 246 190 14 -246 190 14 246 190 14 246 190 14 246 190 14 -246 190 14 246 190 14 246 190 14 246 190 14 -246 190 14 246 190 14 242 186 14 163 133 67 - 46 46 46 18 18 18 6 6 6 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 10 10 10 - 30 30 30 78 78 78 163 133 67 210 150 10 -236 178 12 246 186 14 246 190 14 246 190 14 -246 190 14 246 190 14 246 190 14 246 190 14 -246 190 14 246 190 14 246 190 14 246 190 14 -246 190 14 246 190 14 246 190 14 246 190 14 -241 196 14 215 174 15 190 178 144 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 218 218 218 - 58 58 58 2 2 6 22 18 6 167 114 7 -216 158 10 236 178 12 246 186 14 246 190 14 -246 190 14 246 190 14 246 190 14 246 190 14 -246 190 14 246 190 14 246 190 14 246 190 14 -246 190 14 246 190 14 246 190 14 246 190 14 -246 190 14 246 186 14 242 186 14 190 150 46 - 54 54 54 22 22 22 6 6 6 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 14 14 14 - 38 38 38 86 86 86 180 133 36 213 154 11 -236 178 12 246 186 14 246 190 14 246 190 14 -246 190 14 246 190 14 246 190 14 246 190 14 -246 190 14 246 190 14 246 190 14 246 190 14 -246 190 14 246 190 14 246 190 14 246 190 14 -246 190 14 232 195 16 190 146 13 214 214 214 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 250 250 250 170 170 170 26 26 26 - 2 2 6 2 2 6 37 26 9 163 110 8 -219 162 10 239 182 13 246 186 14 246 190 14 -246 190 14 246 190 14 246 190 14 246 190 14 -246 190 14 246 190 14 246 190 14 246 190 14 -246 190 14 246 190 14 246 190 14 246 190 14 -246 186 14 236 178 12 224 166 10 142 122 72 - 46 46 46 18 18 18 6 6 6 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 6 6 6 18 18 18 - 50 50 50 109 106 95 192 133 9 224 166 10 -242 186 14 246 190 14 246 190 14 246 190 14 -246 190 14 246 190 14 246 190 14 246 190 14 -246 190 14 246 190 14 246 190 14 246 190 14 -246 190 14 246 190 14 246 190 14 246 190 14 -242 186 14 226 184 13 210 162 10 142 110 46 -226 226 226 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -253 253 253 253 253 253 253 253 253 253 253 253 -198 198 198 66 66 66 2 2 6 2 2 6 - 2 2 6 2 2 6 50 34 6 156 107 11 -219 162 10 239 182 13 246 186 14 246 190 14 -246 190 14 246 190 14 246 190 14 246 190 14 -246 190 14 246 190 14 246 190 14 246 190 14 -246 190 14 246 190 14 246 190 14 242 186 14 -234 174 13 213 154 11 154 122 46 66 66 66 - 30 30 30 10 10 10 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 6 6 6 22 22 22 - 58 58 58 154 121 60 206 145 10 234 174 13 -242 186 14 246 186 14 246 190 14 246 190 14 -246 190 14 246 190 14 246 190 14 246 190 14 -246 190 14 246 190 14 246 190 14 246 190 14 -246 190 14 246 190 14 246 190 14 246 190 14 -246 186 14 236 178 12 210 162 10 163 110 8 - 61 42 6 138 138 138 218 218 218 250 250 250 -253 253 253 253 253 253 253 253 253 250 250 250 -242 242 242 210 210 210 144 144 144 66 66 66 - 6 6 6 2 2 6 2 2 6 2 2 6 - 2 2 6 2 2 6 61 42 6 163 110 8 -216 158 10 236 178 12 246 190 14 246 190 14 -246 190 14 246 190 14 246 190 14 246 190 14 -246 190 14 246 190 14 246 190 14 246 190 14 -246 190 14 239 182 13 230 174 11 216 158 10 -190 142 34 124 112 88 70 70 70 38 38 38 - 18 18 18 6 6 6 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 6 6 6 22 22 22 - 62 62 62 168 124 44 206 145 10 224 166 10 -236 178 12 239 182 13 242 186 14 242 186 14 -246 186 14 246 190 14 246 190 14 246 190 14 -246 190 14 246 190 14 246 190 14 246 190 14 -246 190 14 246 190 14 246 190 14 246 190 14 -246 190 14 236 178 12 216 158 10 175 118 6 - 80 54 7 2 2 6 6 6 6 30 30 30 - 54 54 54 62 62 62 50 50 50 38 38 38 - 14 14 14 2 2 6 2 2 6 2 2 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 2 2 6 6 6 6 80 54 7 167 114 7 -213 154 11 236 178 12 246 190 14 246 190 14 -246 190 14 246 190 14 246 190 14 246 190 14 -246 190 14 242 186 14 239 182 13 239 182 13 -230 174 11 210 150 10 174 135 50 124 112 88 - 82 82 82 54 54 54 34 34 34 18 18 18 - 6 6 6 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 6 6 6 18 18 18 - 50 50 50 158 118 36 192 133 9 200 144 11 -216 158 10 219 162 10 224 166 10 226 170 11 -230 174 11 236 178 12 239 182 13 239 182 13 -242 186 14 246 186 14 246 190 14 246 190 14 -246 190 14 246 190 14 246 190 14 246 190 14 -246 186 14 230 174 11 210 150 10 163 110 8 -104 69 6 10 10 10 2 2 6 2 2 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 2 2 6 6 6 6 91 60 6 167 114 7 -206 145 10 230 174 11 242 186 14 246 190 14 -246 190 14 246 190 14 246 186 14 242 186 14 -239 182 13 230 174 11 224 166 10 213 154 11 -180 133 36 124 112 88 86 86 86 58 58 58 - 38 38 38 22 22 22 10 10 10 6 6 6 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 14 14 14 - 34 34 34 70 70 70 138 110 50 158 118 36 -167 114 7 180 123 7 192 133 9 197 138 11 -200 144 11 206 145 10 213 154 11 219 162 10 -224 166 10 230 174 11 239 182 13 242 186 14 -246 186 14 246 186 14 246 186 14 246 186 14 -239 182 13 216 158 10 185 133 11 152 99 6 -104 69 6 18 14 6 2 2 6 2 2 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 2 2 6 2 2 6 2 2 6 2 2 6 - 2 2 6 6 6 6 80 54 7 152 99 6 -192 133 9 219 162 10 236 178 12 239 182 13 -246 186 14 242 186 14 239 182 13 236 178 12 -224 166 10 206 145 10 192 133 9 154 121 60 - 94 94 94 62 62 62 42 42 42 22 22 22 - 14 14 14 6 6 6 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 6 6 6 - 18 18 18 34 34 34 58 58 58 78 78 78 -101 98 89 124 112 88 142 110 46 156 107 11 -163 110 8 167 114 7 175 118 6 180 123 7 -185 133 11 197 138 11 210 150 10 219 162 10 -226 170 11 236 178 12 236 178 12 234 174 13 -219 162 10 197 138 11 163 110 8 130 83 6 - 91 60 6 10 10 10 2 2 6 2 2 6 - 18 18 18 38 38 38 38 38 38 38 38 38 - 38 38 38 38 38 38 38 38 38 38 38 38 - 38 38 38 38 38 38 26 26 26 2 2 6 - 2 2 6 6 6 6 70 47 6 137 92 6 -175 118 6 200 144 11 219 162 10 230 174 11 -234 174 13 230 174 11 219 162 10 210 150 10 -192 133 9 163 110 8 124 112 88 82 82 82 - 50 50 50 30 30 30 14 14 14 6 6 6 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 6 6 6 14 14 14 22 22 22 34 34 34 - 42 42 42 58 58 58 74 74 74 86 86 86 -101 98 89 122 102 70 130 98 46 121 87 25 -137 92 6 152 99 6 163 110 8 180 123 7 -185 133 11 197 138 11 206 145 10 200 144 11 -180 123 7 156 107 11 130 83 6 104 69 6 - 50 34 6 54 54 54 110 110 110 101 98 89 - 86 86 86 82 82 82 78 78 78 78 78 78 - 78 78 78 78 78 78 78 78 78 78 78 78 - 78 78 78 82 82 82 86 86 86 94 94 94 -106 106 106 101 101 101 86 66 34 124 80 6 -156 107 11 180 123 7 192 133 9 200 144 11 -206 145 10 200 144 11 192 133 9 175 118 6 -139 102 15 109 106 95 70 70 70 42 42 42 - 22 22 22 10 10 10 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 6 6 6 10 10 10 - 14 14 14 22 22 22 30 30 30 38 38 38 - 50 50 50 62 62 62 74 74 74 90 90 90 -101 98 89 112 100 78 121 87 25 124 80 6 -137 92 6 152 99 6 152 99 6 152 99 6 -138 86 6 124 80 6 98 70 6 86 66 30 -101 98 89 82 82 82 58 58 58 46 46 46 - 38 38 38 34 34 34 34 34 34 34 34 34 - 34 34 34 34 34 34 34 34 34 34 34 34 - 34 34 34 34 34 34 38 38 38 42 42 42 - 54 54 54 82 82 82 94 86 76 91 60 6 -134 86 6 156 107 11 167 114 7 175 118 6 -175 118 6 167 114 7 152 99 6 121 87 25 -101 98 89 62 62 62 34 34 34 18 18 18 - 6 6 6 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 6 6 6 6 6 6 10 10 10 - 18 18 18 22 22 22 30 30 30 42 42 42 - 50 50 50 66 66 66 86 86 86 101 98 89 -106 86 58 98 70 6 104 69 6 104 69 6 -104 69 6 91 60 6 82 62 34 90 90 90 - 62 62 62 38 38 38 22 22 22 14 14 14 - 10 10 10 10 10 10 10 10 10 10 10 10 - 10 10 10 10 10 10 6 6 6 10 10 10 - 10 10 10 10 10 10 10 10 10 14 14 14 - 22 22 22 42 42 42 70 70 70 89 81 66 - 80 54 7 104 69 6 124 80 6 137 92 6 -134 86 6 116 81 8 100 82 52 86 86 86 - 58 58 58 30 30 30 14 14 14 6 6 6 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 6 6 6 10 10 10 14 14 14 - 18 18 18 26 26 26 38 38 38 54 54 54 - 70 70 70 86 86 86 94 86 76 89 81 66 - 89 81 66 86 86 86 74 74 74 50 50 50 - 30 30 30 14 14 14 6 6 6 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 6 6 6 18 18 18 34 34 34 58 58 58 - 82 82 82 89 81 66 89 81 66 89 81 66 - 94 86 66 94 86 76 74 74 74 50 50 50 - 26 26 26 14 14 14 6 6 6 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 6 6 6 6 6 6 14 14 14 18 18 18 - 30 30 30 38 38 38 46 46 46 54 54 54 - 50 50 50 42 42 42 30 30 30 18 18 18 - 10 10 10 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 6 6 6 14 14 14 26 26 26 - 38 38 38 50 50 50 58 58 58 58 58 58 - 54 54 54 42 42 42 30 30 30 18 18 18 - 10 10 10 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 6 6 6 - 6 6 6 10 10 10 14 14 14 18 18 18 - 18 18 18 14 14 14 10 10 10 6 6 6 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 6 6 6 - 14 14 14 18 18 18 22 22 22 22 22 22 - 18 18 18 14 14 14 10 10 10 6 6 6 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 1 0 +0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 +0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 +10 15 3 2 3 1 12 18 4 42 61 14 19 27 6 11 16 4 +38 55 13 10 15 3 3 4 1 10 15 3 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 3 1 +12 18 4 1 1 0 23 34 8 31 45 11 10 15 3 32 47 11 +34 49 12 3 4 1 3 4 1 3 4 1 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 10 15 3 29 42 10 26 37 9 12 18 4 +55 80 19 81 118 28 55 80 19 92 132 31 106 153 36 69 100 23 +100 144 34 80 116 27 42 61 14 81 118 28 23 34 8 27 40 9 +15 21 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 1 1 0 29 42 10 15 21 5 50 72 17 +74 107 25 45 64 15 102 148 35 80 116 27 84 121 28 111 160 38 +69 100 23 65 94 22 81 118 28 29 42 10 17 25 6 29 42 10 +23 34 8 2 3 1 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 4 1 +15 21 5 15 21 5 34 49 12 101 146 34 111 161 38 97 141 33 +97 141 33 119 172 41 117 170 40 116 167 40 118 170 40 118 171 40 +117 169 40 118 170 40 111 160 38 118 170 40 96 138 32 89 128 30 +81 118 28 11 16 4 10 15 3 1 1 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +3 4 1 3 4 1 34 49 12 101 146 34 79 115 27 111 160 38 +114 165 39 113 163 39 118 170 40 117 169 40 118 171 40 117 169 40 +116 167 40 119 172 41 113 163 39 92 132 31 105 151 36 113 163 39 +75 109 26 19 27 6 16 23 5 11 16 4 0 1 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 10 15 3 +80 116 27 106 153 36 105 151 36 114 165 39 118 170 40 118 171 40 +118 171 40 117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 +117 169 40 117 169 40 117 170 40 117 169 40 118 170 40 118 170 40 +117 170 40 75 109 26 75 109 26 34 49 12 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 4 1 +64 92 22 65 94 22 100 144 34 118 171 40 118 170 40 117 169 40 +117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 +117 169 40 117 169 40 117 169 40 118 171 41 118 170 40 117 169 40 +109 158 37 105 151 36 104 150 35 47 69 16 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +42 61 14 115 167 39 118 170 40 117 169 40 117 169 40 117 169 40 +117 170 40 117 170 40 117 169 40 117 169 40 117 169 40 117 169 40 +117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 +117 169 40 117 169 40 118 170 40 96 138 32 17 25 6 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 47 69 16 +114 165 39 117 168 40 117 170 40 117 169 40 117 169 40 117 169 40 +117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 +117 169 40 117 169 40 118 170 40 117 169 40 117 169 40 117 169 40 +117 170 40 119 172 41 96 138 32 12 18 4 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 10 15 3 +32 47 11 105 151 36 118 170 40 117 169 40 117 169 40 116 168 40 +109 157 37 111 160 38 117 169 40 118 171 40 117 169 40 117 169 40 +117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 +117 169 40 117 169 40 117 169 40 118 171 40 69 100 23 2 3 1 +0 0 0 0 0 0 0 0 0 0 0 0 19 27 6 101 146 34 +118 171 40 117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 +117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 117 170 40 +118 171 40 115 166 39 107 154 36 111 161 38 117 169 40 117 169 40 +117 169 40 118 171 40 75 109 26 19 27 6 2 3 1 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 16 23 5 +89 128 30 117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 +111 160 38 92 132 31 79 115 27 96 138 32 115 166 39 119 171 41 +117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 +117 169 40 117 169 40 117 169 40 118 170 40 109 157 37 26 37 9 +0 0 0 0 0 0 0 0 0 0 0 0 64 92 22 118 171 40 +117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 +117 169 40 117 169 40 117 169 40 118 170 40 118 171 40 109 157 37 +89 128 30 81 118 28 100 144 34 115 166 39 117 169 40 117 169 40 +117 169 40 117 170 40 113 163 39 60 86 20 1 1 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +27 40 9 96 138 32 118 170 40 117 169 40 117 169 40 117 169 40 +117 170 40 117 169 40 101 146 34 67 96 23 55 80 19 84 121 28 +113 163 39 119 171 41 117 169 40 117 169 40 117 169 40 117 169 40 +117 169 40 117 169 40 117 169 40 117 169 40 119 171 41 65 94 22 +0 0 0 0 0 0 0 0 0 15 21 5 101 146 34 118 171 40 +117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 +117 169 40 118 170 40 118 171 40 104 150 35 69 100 23 53 76 18 +81 118 28 111 160 38 118 170 40 117 169 40 117 169 40 117 169 40 +117 169 40 114 165 39 69 100 23 10 15 3 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 +31 45 11 77 111 26 117 169 40 117 169 40 117 169 40 117 169 40 +117 169 40 117 169 40 118 170 40 116 168 40 92 132 31 47 69 16 +38 55 13 81 118 28 113 163 39 119 171 41 117 169 40 117 169 40 +117 169 40 117 169 40 117 169 40 117 169 40 118 171 41 92 132 31 +10 15 3 0 0 0 0 0 0 36 52 12 115 166 39 117 169 40 +117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 118 170 40 +118 171 40 102 148 35 64 92 22 34 49 12 65 94 22 106 153 36 +118 171 40 117 170 40 117 169 40 117 169 40 117 169 40 117 169 40 +118 170 40 107 154 36 55 80 19 15 21 5 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +29 42 10 101 146 34 118 171 40 117 169 40 117 169 40 117 169 40 +117 169 40 117 169 40 117 169 40 117 169 40 118 171 40 113 163 39 +75 109 26 27 40 9 36 52 12 89 128 30 116 167 40 118 171 40 +117 169 40 117 169 40 117 169 40 117 169 40 118 170 40 104 150 35 +16 23 5 0 0 0 0 0 0 53 76 18 118 171 40 117 169 40 +117 169 40 117 169 40 117 169 40 117 169 40 119 171 41 109 157 37 +67 96 23 23 34 8 42 61 14 96 138 32 118 170 40 118 170 40 +117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 +117 169 40 117 169 40 74 107 25 10 15 3 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 31 45 11 101 146 34 118 170 40 117 169 40 117 169 40 +117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 +119 171 41 102 148 35 47 69 16 14 20 5 50 72 17 102 148 35 +118 171 40 117 169 40 117 169 40 117 169 40 118 170 40 102 148 35 +15 21 5 0 0 0 0 0 0 50 72 17 118 170 40 117 169 40 +117 169 40 117 169 40 118 170 40 116 167 40 84 121 28 27 40 9 +19 27 6 74 107 25 114 165 39 118 171 40 117 169 40 117 169 40 +117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 +117 169 40 75 109 26 10 15 4 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 38 55 13 102 148 35 118 171 40 117 169 40 117 169 40 +117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 +117 169 40 118 170 40 115 167 39 77 111 26 17 25 6 19 27 6 +77 111 26 115 166 39 118 170 40 117 169 40 119 172 41 81 118 28 +3 4 1 0 0 0 0 0 0 27 40 9 111 160 38 118 170 40 +117 169 40 118 171 40 105 151 36 50 72 17 10 15 3 38 55 13 +100 144 34 118 171 40 117 169 40 117 169 40 117 169 40 117 169 40 +117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 +117 169 40 79 115 27 15 21 5 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 10 15 3 64 92 22 111 160 38 117 169 40 117 169 40 +117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 +117 169 40 117 169 40 117 169 40 118 171 40 96 138 32 32 47 11 +3 4 1 50 72 17 107 154 36 120 173 41 105 151 36 31 45 11 +0 0 0 0 0 0 0 0 0 3 4 1 65 94 22 117 169 40 +118 170 40 89 128 30 26 37 9 3 4 1 60 86 20 111 161 38 +118 171 40 117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 +117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 +97 141 33 36 52 12 1 1 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 14 20 5 75 109 26 117 168 40 117 169 40 +117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 +117 169 40 117 169 40 117 169 40 117 169 40 118 171 40 107 154 36 +45 64 15 2 3 1 31 45 11 75 109 26 32 47 11 0 1 0 +0 0 0 0 0 0 0 0 0 0 0 0 10 15 3 55 80 19 +65 94 22 11 16 4 11 16 4 75 109 26 116 168 40 118 170 40 +117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 +117 169 40 117 169 40 117 169 40 117 169 40 118 170 40 107 154 36 +47 69 16 3 4 1 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 12 18 4 69 100 23 111 161 38 118 171 40 +117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 +117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 118 170 40 +111 160 38 50 72 17 2 3 1 2 3 1 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 +1 1 0 12 18 4 81 118 28 118 170 40 117 169 40 117 169 40 +117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 +117 169 40 117 169 40 117 169 40 117 170 40 118 171 40 101 146 34 +42 61 14 2 3 1 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 3 4 1 36 52 12 89 128 30 +117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 +117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 +118 171 41 101 146 34 14 20 5 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 47 69 16 118 170 40 117 169 40 117 169 40 117 169 40 +117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 +117 169 40 117 169 40 117 170 40 111 160 38 69 100 23 19 27 6 +0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 11 16 4 69 100 23 +115 167 39 119 172 41 117 169 40 117 169 40 117 169 40 117 169 40 +117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 +119 172 41 75 109 26 3 4 1 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 23 34 8 106 153 36 118 170 40 117 169 40 117 169 40 +117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 +117 169 40 118 170 40 119 172 41 105 151 36 42 61 14 2 3 1 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 15 21 5 +45 64 15 80 116 27 114 165 39 118 170 40 117 169 40 117 169 40 +117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 119 172 41 +97 141 33 20 30 7 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 1 1 0 53 76 18 114 165 39 118 171 40 117 169 40 +117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 117 169 40 +118 171 40 104 150 35 64 92 22 31 45 11 10 15 3 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 36 52 12 97 141 33 109 158 37 113 163 39 116 168 40 +117 169 40 117 170 40 118 170 40 119 172 41 115 167 39 84 121 28 +23 34 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 3 4 1 50 72 17 102 148 35 118 171 40 +119 171 41 118 170 40 117 169 40 117 169 40 115 166 39 111 161 38 +109 157 37 79 115 27 12 18 4 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 3 4 1 15 21 5 23 34 8 45 64 15 106 153 36 +116 167 40 111 160 38 101 146 34 79 115 27 42 61 14 10 15 3 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 1 1 0 20 30 7 60 86 20 +89 128 30 106 153 36 113 163 39 117 169 40 84 121 28 29 42 10 +19 27 6 10 15 3 2 3 1 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 16 23 5 38 55 13 +36 52 12 26 37 9 12 18 4 2 3 1 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 1 0 0 19 2 7 52 5 18 +78 7 27 88 8 31 81 7 29 56 5 19 25 2 9 3 0 1 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +3 4 1 19 27 6 31 45 11 38 55 13 32 47 11 3 4 1 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 0 1 +9 0 3 12 1 4 9 0 3 4 0 1 0 0 0 0 0 0 +0 0 0 0 0 0 28 3 10 99 9 35 156 14 55 182 16 64 +189 17 66 190 17 67 189 17 66 184 17 65 166 15 58 118 13 41 +45 4 16 3 0 1 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 11 1 4 52 5 18 101 9 35 134 12 47 +151 14 53 154 14 54 151 14 53 113 10 40 11 1 4 0 0 0 +3 0 1 67 6 24 159 14 56 190 17 67 190 17 67 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 190 17 67 191 17 67 +174 16 61 101 9 35 14 1 5 0 0 0 35 3 12 108 10 38 +122 11 43 122 11 43 112 10 39 87 8 30 50 5 17 13 1 5 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +3 0 1 56 5 19 141 13 49 182 16 64 191 17 67 191 17 67 +190 17 67 190 17 67 191 17 67 113 10 40 3 0 1 1 0 0 +79 7 28 180 16 63 190 17 67 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +189 17 66 188 17 66 122 11 43 11 1 4 41 4 14 176 16 62 +191 17 67 191 17 67 191 17 67 190 17 67 181 16 63 146 13 51 +75 7 26 10 1 4 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 7 1 2 +90 8 32 178 16 62 191 17 67 188 17 66 188 17 66 188 17 66 +188 17 66 190 17 67 141 13 49 22 2 8 0 0 0 41 4 14 +173 16 61 190 17 67 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 88 8 31 1 0 0 89 8 31 +185 17 65 189 17 66 188 17 66 188 17 66 189 17 66 191 17 67 +186 17 65 124 11 43 25 2 9 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 2 0 1 89 8 31 +184 17 65 189 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +190 17 67 151 14 53 34 3 12 0 0 0 0 0 0 79 7 28 +190 17 67 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 191 17 67 146 13 51 9 1 3 7 1 2 +108 10 38 187 17 66 189 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 190 17 67 141 13 49 22 2 8 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 52 5 18 176 16 62 +189 17 66 188 17 66 188 17 66 188 17 66 188 17 66 190 17 67 +151 14 53 38 3 13 0 0 0 0 0 0 0 0 0 50 5 17 +180 16 63 189 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 191 17 67 141 13 49 7 1 3 0 0 0 +11 1 4 112 10 39 187 17 66 189 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 190 17 67 113 10 40 5 0 2 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 7 1 3 132 12 46 191 17 67 +188 17 66 188 17 66 188 17 66 188 17 66 190 17 67 146 13 51 +35 3 12 0 0 0 0 0 0 0 0 0 0 0 0 5 0 2 +101 9 35 185 17 65 190 17 67 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 190 17 67 180 16 63 67 6 24 0 0 0 0 0 0 +0 0 0 11 1 4 108 10 38 186 17 65 189 17 66 188 17 66 +188 17 66 188 17 66 189 17 66 180 16 63 56 5 19 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 44 4 15 177 16 62 189 17 66 +188 17 66 188 17 66 189 17 66 189 17 66 134 12 47 28 3 10 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +8 1 3 79 7 28 159 14 56 188 17 66 191 17 67 190 17 67 +189 17 66 189 17 66 189 17 66 189 17 66 190 17 67 191 17 67 +188 17 66 158 14 55 72 7 25 4 0 1 0 0 0 0 0 0 +0 0 0 0 0 0 8 1 3 95 9 33 182 16 64 189 17 67 +188 17 66 188 17 66 188 17 66 191 17 67 122 11 43 3 0 1 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 88 8 31 190 17 67 188 17 66 +188 17 66 189 17 66 185 17 65 113 10 40 18 2 6 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 1 0 0 24 2 8 77 7 27 124 11 43 154 14 54 +168 15 59 173 16 61 173 16 61 168 15 59 154 14 54 124 11 43 +77 7 27 22 2 8 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 5 0 2 77 7 27 173 16 61 +190 17 67 188 17 66 188 17 66 190 17 67 164 15 57 23 2 8 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 1 0 0 118 13 41 191 17 67 188 17 66 +190 17 67 174 16 61 87 8 30 8 1 3 0 0 0 0 0 0 +0 0 0 0 0 0 10 1 4 29 3 10 40 4 14 36 3 13 +18 2 6 2 0 1 0 0 0 0 0 0 3 0 1 14 1 5 +26 2 9 33 3 11 32 3 11 25 2 9 13 1 5 3 0 1 +0 0 0 14 1 5 56 5 19 95 9 33 109 10 38 101 9 35 +77 7 27 35 3 12 5 0 2 0 0 0 1 0 0 56 5 19 +156 14 55 190 17 67 188 17 66 188 17 66 182 16 64 50 5 17 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 5 0 2 134 12 47 191 17 67 189 17 66 +151 14 53 52 5 18 2 0 1 0 0 0 0 0 0 1 0 0 +28 3 10 90 8 32 146 13 51 170 15 60 178 16 62 174 16 61 +158 14 55 112 10 39 40 4 14 1 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 0 1 +56 5 19 146 13 51 183 17 64 191 17 67 191 17 67 191 17 67 +188 17 66 173 16 61 122 11 43 41 4 14 1 0 0 0 0 0 +30 3 10 124 11 43 185 17 65 190 17 67 187 17 66 67 6 24 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 6 1 2 134 12 47 168 15 59 99 9 35 +21 2 7 0 0 0 0 0 0 0 0 0 6 1 2 77 7 27 +162 15 57 190 17 67 191 17 67 189 17 66 189 17 66 189 17 66 +190 17 67 191 17 67 169 15 59 75 7 26 3 0 1 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 2 0 1 79 7 28 +178 16 62 191 17 67 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 189 17 66 191 17 67 170 15 60 79 7 28 5 0 2 +0 0 0 10 1 3 78 7 27 159 14 56 188 17 66 75 7 26 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 1 0 0 35 3 12 29 3 10 2 0 1 +0 0 0 0 0 0 0 0 0 9 1 3 101 9 35 183 17 64 +190 17 67 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 190 17 67 178 16 63 67 6 23 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 52 5 18 174 16 61 +190 17 67 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 190 17 67 182 16 64 89 8 31 +4 0 1 0 0 0 0 0 0 25 2 9 73 7 26 31 3 11 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 4 0 1 98 9 34 187 17 66 189 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 190 17 67 158 14 55 25 2 9 +0 0 0 0 0 0 0 0 0 8 1 3 134 12 47 191 17 67 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 189 17 66 180 16 63 +68 6 24 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 6 1 2 19 2 7 3 0 1 0 0 0 0 0 0 +0 0 0 0 0 0 65 6 23 180 16 63 189 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 189 17 66 83 8 29 +0 0 0 0 0 0 0 0 0 41 4 14 177 16 62 189 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 190 17 67 +159 14 56 28 3 10 0 0 0 0 0 0 0 0 0 23 2 8 +41 4 14 5 0 2 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +23 2 8 113 10 40 159 14 56 65 6 23 0 0 0 0 0 0 +0 0 0 16 1 6 146 13 51 191 17 67 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 191 17 67 132 12 46 +5 0 2 0 0 0 0 0 0 77 7 27 189 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +190 17 67 98 9 34 0 0 0 0 0 0 12 1 4 134 12 47 +178 16 63 108 10 38 16 1 6 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 30 3 10 +141 13 49 190 17 67 191 17 67 134 12 47 6 1 2 0 0 0 +0 0 0 68 6 24 186 17 65 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 190 17 67 156 14 55 +14 1 5 0 0 0 0 0 0 98 9 34 191 17 67 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +190 17 67 156 14 55 19 2 7 0 0 0 47 4 16 181 16 63 +190 17 67 189 17 66 126 14 44 17 2 6 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 16 1 6 134 12 47 +191 17 67 188 17 66 190 17 67 162 15 57 19 2 7 0 0 0 +3 0 1 123 11 43 191 17 67 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 190 17 67 163 15 57 +20 2 7 0 0 0 0 0 0 101 9 35 191 17 67 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 182 16 64 52 5 18 0 0 0 73 7 26 188 17 66 +188 17 66 188 17 66 189 17 66 109 10 38 5 0 2 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 95 9 33 189 17 66 +188 17 66 188 17 66 189 17 66 171 15 60 29 3 10 0 0 0 +16 1 6 156 14 55 190 17 67 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 190 17 67 158 14 55 +17 2 6 0 0 0 0 0 0 85 8 30 190 17 67 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 189 17 66 81 7 29 0 0 0 85 8 30 190 17 67 +188 17 66 188 17 66 189 17 66 180 16 63 56 5 19 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 25 2 9 162 15 57 190 17 67 +188 17 66 188 17 66 189 17 66 173 16 61 31 3 11 0 0 0 +30 3 10 171 15 60 189 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 191 17 67 141 13 49 +7 1 2 0 0 0 0 0 0 56 5 19 183 17 64 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 191 17 67 98 9 34 0 0 0 88 8 31 190 17 67 +188 17 66 188 17 66 188 17 66 191 17 67 124 11 43 5 0 2 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 68 6 24 187 17 66 188 17 66 +188 17 66 188 17 66 189 17 66 170 15 60 28 3 10 0 0 0 +34 3 12 174 16 61 189 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 191 17 67 101 9 35 +0 0 0 0 0 0 0 0 0 21 2 7 159 14 56 190 17 67 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 191 17 67 98 9 34 0 0 0 81 7 29 189 17 66 +188 17 66 188 17 66 188 17 66 189 17 66 168 15 59 28 3 10 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 109 10 38 191 17 67 188 17 66 +188 17 66 188 17 66 190 17 67 163 15 57 21 2 7 0 0 0 +26 2 9 168 15 59 189 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 189 17 66 180 16 63 47 4 16 +0 0 0 0 0 0 0 0 0 0 0 0 108 10 38 190 17 67 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 189 17 66 78 7 27 0 0 0 68 6 24 187 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 183 17 64 56 5 19 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 3 0 1 131 12 46 191 17 67 188 17 66 +188 17 66 188 17 66 190 17 67 151 14 53 12 1 4 0 0 0 +11 1 4 146 13 51 190 17 67 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 191 17 67 126 14 44 7 1 2 +0 0 0 0 0 0 0 0 0 0 0 0 32 3 11 164 15 58 +190 17 67 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +189 17 66 178 16 62 44 4 15 0 0 0 50 5 17 182 16 64 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 72 7 25 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 5 0 2 134 12 47 191 17 67 188 17 66 +188 17 66 188 17 66 191 17 67 131 12 46 3 0 1 0 0 0 +0 0 0 101 9 35 190 17 67 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 190 17 67 170 15 60 44 4 15 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 77 7 27 +183 17 64 189 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +191 17 67 134 12 47 9 1 3 0 0 0 31 3 11 171 15 60 +189 17 66 188 17 66 188 17 66 188 17 66 188 17 66 72 7 25 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 2 0 1 124 11 43 191 17 67 188 17 66 +188 17 66 188 17 66 191 17 67 101 9 35 0 0 0 0 0 0 +0 0 0 35 3 12 168 15 59 190 17 67 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 189 17 66 182 16 64 77 7 27 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 1 2 +99 9 35 185 17 65 189 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 189 17 66 +177 16 62 56 5 19 0 0 0 0 0 0 13 1 5 151 14 53 +190 17 67 188 17 66 188 17 66 188 17 66 185 17 65 56 5 19 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 99 9 35 191 17 67 188 17 66 +188 17 66 188 17 66 186 17 65 65 6 23 0 0 0 0 0 0 +0 0 0 0 0 0 79 7 28 182 16 64 190 17 67 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +191 17 67 177 16 62 83 8 29 4 0 1 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +8 1 3 89 8 31 175 16 62 191 17 67 189 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 190 17 67 181 16 63 +85 8 30 3 0 1 0 0 0 0 0 0 1 0 0 118 13 41 +191 17 67 188 17 66 188 17 66 189 17 66 173 16 61 34 3 12 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 56 5 19 183 17 64 188 17 66 +188 17 66 189 17 66 169 15 59 30 3 10 0 0 0 0 0 0 +0 0 0 0 0 0 5 0 2 83 8 29 173 16 61 191 17 67 +190 17 67 189 17 66 189 17 66 190 17 67 191 17 67 187 17 66 +151 14 53 56 5 19 3 0 1 0 0 0 16 1 6 50 5 17 +79 7 28 95 9 33 95 9 33 75 7 26 41 4 14 10 1 4 +0 0 0 2 0 1 50 5 17 132 12 46 178 16 62 190 17 67 +191 17 67 191 17 67 191 17 67 186 17 65 154 14 54 68 6 24 +4 0 1 0 0 0 0 0 0 0 0 0 0 0 0 72 7 25 +187 17 66 188 17 66 188 17 66 191 17 67 141 13 49 9 1 3 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 14 1 5 151 14 53 190 17 67 +188 17 66 191 17 67 131 12 46 5 0 2 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 2 0 1 44 4 15 113 10 40 +156 14 55 173 16 61 174 16 61 164 15 58 134 12 47 77 7 27 +18 2 6 0 0 0 16 1 6 85 8 30 151 14 53 182 16 64 +189 17 66 191 17 67 190 17 67 188 17 66 177 16 62 141 13 49 +68 6 24 8 1 3 0 0 0 8 1 3 44 4 15 88 8 31 +113 10 40 122 11 43 108 10 38 67 6 24 20 2 7 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 28 3 10 +166 15 58 190 17 67 188 17 66 187 17 66 79 7 28 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 73 7 26 185 17 65 +189 17 66 184 17 65 65 6 23 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 1 +17 2 6 32 3 11 34 3 12 22 2 8 6 1 2 0 0 0 +0 0 0 38 3 13 141 13 49 188 17 66 190 17 67 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 189 17 66 191 17 67 +184 17 65 122 11 43 21 2 7 0 0 0 0 0 0 0 0 0 +0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 +108 10 38 191 17 67 191 17 67 141 13 49 16 1 6 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 8 1 3 112 10 39 +186 17 65 124 11 43 10 1 4 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +36 3 13 156 14 55 191 17 67 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +189 17 66 190 17 67 134 12 47 18 2 6 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 7 1 2 41 4 14 75 7 26 66 5 23 19 2 7 +26 2 9 144 13 50 154 14 54 40 4 14 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 13 1 5 +56 5 19 19 2 7 0 0 0 7 1 2 29 3 10 35 3 12 +19 2 7 2 0 1 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 13 1 5 +134 12 47 191 17 67 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 189 17 67 108 10 38 3 0 1 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 +40 4 14 124 11 43 177 16 62 188 17 66 187 17 66 144 13 50 +24 2 8 17 2 6 22 2 8 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 19 2 7 122 11 43 171 15 60 175 16 62 +159 14 56 112 10 39 40 4 14 2 0 1 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 72 7 25 +186 17 65 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 189 17 66 174 16 61 41 4 14 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 3 0 1 72 7 25 +168 15 59 191 17 67 189 17 66 188 17 66 188 17 66 190 17 67 +95 9 33 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 95 9 33 191 17 67 189 17 66 189 17 66 +190 17 67 191 17 67 171 15 60 90 8 32 12 1 4 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 5 0 2 132 12 46 +191 17 67 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 190 17 67 98 9 34 0 0 0 +0 0 0 0 0 0 0 0 0 5 0 2 88 8 31 180 16 63 +190 17 67 188 17 66 188 17 66 188 17 66 188 17 66 191 17 67 +146 13 51 11 1 4 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 9 1 3 144 13 50 191 17 67 188 17 66 188 17 66 +188 17 66 188 17 66 189 17 66 187 17 66 123 11 43 20 2 7 +0 0 0 0 0 0 0 0 0 0 0 0 21 2 7 163 15 57 +190 17 67 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 191 17 67 134 12 47 5 0 2 +0 0 0 0 0 0 3 0 1 88 8 31 182 16 64 189 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 189 17 66 +171 15 60 31 3 11 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 20 2 7 162 15 57 190 17 67 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 190 17 67 132 12 46 +20 2 7 0 0 0 0 0 0 0 0 0 32 3 11 173 16 61 +189 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 190 17 67 151 14 53 12 1 4 +0 0 0 0 0 0 72 7 25 180 16 63 189 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +181 16 63 47 4 16 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 21 2 7 163 15 57 190 17 67 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 190 17 67 +122 11 43 9 1 3 0 0 0 0 0 0 30 3 10 171 15 60 +189 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 190 17 67 146 13 51 10 1 4 +0 0 0 38 3 13 166 15 58 190 17 67 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +183 17 64 52 5 18 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 13 1 5 154 14 54 190 17 67 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +186 17 65 79 7 28 0 0 0 0 0 0 14 1 5 156 14 54 +190 17 67 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 191 17 67 124 11 43 2 0 1 +5 0 2 122 11 43 191 17 67 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +182 16 64 47 4 16 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 3 0 1 126 14 44 191 17 67 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +190 17 67 158 14 55 23 2 8 0 0 0 1 0 0 113 10 40 +191 17 67 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 78 7 27 0 0 0 +47 4 16 177 16 62 189 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 189 17 66 +173 16 61 34 3 12 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 85 8 30 189 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 79 7 28 0 0 0 0 0 0 47 4 16 +175 16 62 189 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 190 17 67 156 14 55 22 2 8 0 0 0 +109 10 38 191 17 67 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 190 17 67 +151 14 53 13 1 5 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 35 3 12 173 16 61 189 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 191 17 67 134 12 47 7 1 2 0 0 0 3 0 1 +99 9 35 188 17 66 189 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 189 17 66 181 16 63 68 6 24 0 0 0 18 2 6 +156 14 55 190 17 67 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 190 17 67 +101 9 35 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 3 0 1 118 13 41 191 17 67 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 189 17 66 168 15 59 28 3 10 0 0 0 0 0 0 +12 1 4 113 10 40 187 17 66 189 17 67 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +190 17 67 180 16 63 88 8 31 4 0 1 0 0 0 47 4 16 +180 16 63 189 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 190 17 67 168 15 59 +36 3 13 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 38 3 13 164 15 58 190 17 67 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 182 16 64 50 5 17 0 0 0 0 0 0 +0 0 0 11 1 4 90 8 32 169 15 59 190 17 67 190 17 67 +189 17 66 189 17 66 189 17 66 189 17 66 191 17 67 189 17 66 +158 14 55 68 6 24 4 0 1 0 0 0 0 0 0 73 7 26 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 189 17 66 185 17 65 83 8 29 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 65 6 23 174 16 61 +190 17 67 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 185 17 65 56 5 19 0 0 0 0 0 0 +0 0 0 0 0 0 2 0 1 35 3 12 99 9 35 146 13 51 +170 15 60 177 16 62 177 16 62 166 15 58 141 13 49 85 8 30 +24 2 8 0 0 0 0 0 0 0 0 0 0 0 0 85 8 30 +190 17 67 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 189 17 66 112 10 39 8 1 3 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 68 6 24 +170 15 60 191 17 67 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 182 16 64 50 5 17 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 11 1 4 +28 3 10 40 4 14 38 3 13 25 2 9 8 1 3 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 78 7 27 +189 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 189 17 66 187 17 66 113 10 40 14 1 5 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 +47 4 16 141 13 49 186 17 65 191 17 67 190 17 67 189 17 66 +189 17 66 191 17 67 156 14 55 20 2 7 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 44 4 15 +178 16 62 190 17 67 188 17 66 188 17 66 188 17 66 190 17 67 +191 17 67 173 16 61 90 8 32 10 1 4 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 14 1 5 68 6 24 131 12 46 162 15 57 174 16 61 +171 15 60 146 13 51 56 5 19 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 3 0 1 14 1 5 29 3 10 +41 4 14 47 4 16 50 5 17 45 4 16 34 3 12 18 2 6 +5 0 2 0 0 0 0 0 0 0 0 0 0 0 0 5 0 2 +90 8 32 169 15 59 185 17 65 187 17 66 182 16 64 163 15 57 +113 10 40 41 4 14 2 0 1 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 5 0 2 21 2 7 34 3 12 +29 3 10 11 1 4 0 0 0 0 0 0 0 0 0 0 0 0 +3 0 1 32 3 11 79 7 28 124 11 43 154 14 54 171 15 60 +180 16 63 182 16 64 182 16 64 180 16 63 174 16 61 159 14 56 +132 12 46 88 8 31 34 3 12 3 0 1 0 0 0 0 0 0 +3 0 1 29 3 10 56 5 19 65 6 23 50 5 17 23 2 8 +3 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 25 2 9 +109 10 38 169 15 59 189 17 66 191 17 67 190 17 67 189 17 66 +189 17 66 188 17 66 188 17 66 188 17 66 189 17 66 190 17 67 +191 17 67 190 17 67 171 15 60 98 9 34 10 1 3 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 14 1 5 141 13 49 +191 17 67 189 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 189 17 67 186 17 65 65 6 23 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 23 2 8 166 15 58 +190 17 67 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 189 17 66 176 16 62 45 4 16 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 83 8 29 +183 17 64 189 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +188 17 66 189 17 66 185 17 65 95 9 33 3 0 1 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 5 0 2 +85 8 30 176 16 62 191 17 67 188 17 66 188 17 66 188 17 66 +188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 188 17 66 +191 17 67 180 16 63 95 9 33 7 1 3 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +2 0 1 52 5 18 141 13 49 185 17 65 191 17 67 189 17 67 +189 17 66 188 17 66 188 17 66 189 17 66 191 17 67 187 17 66 +146 13 51 56 5 19 4 0 1 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 14 1 5 68 6 24 131 12 46 166 15 58 +180 16 63 183 17 64 180 16 63 168 15 59 134 12 47 75 7 26 +17 2 6 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 5 0 2 24 2 8 +44 4 15 52 5 18 45 4 16 26 2 9 6 1 2 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 diff --git a/drivers/w1/masters/w1-gpio.c b/drivers/w1/masters/w1-gpio.c index a39fa8bf866ae3..e33a6e4030c83e 100644 --- a/drivers/w1/masters/w1-gpio.c +++ b/drivers/w1/masters/w1-gpio.c @@ -35,7 +35,7 @@ static u8 w1_gpio_set_pullup(void *data, int delay) * This will OVERRIDE open drain emulation and force-pull * the line high for some time. */ - gpiod_set_raw_value(ddata->gpiod, 1); + gpiod_direction_output_raw(ddata->gpiod, 1); msleep(ddata->pullup_duration); /* * This will simply set the line as input since we are doing @@ -89,6 +89,9 @@ static int w1_gpio_probe(struct platform_device *pdev) if (!master) return -ENOMEM; + if (device_property_present(dev, "raspberrypi,delay-needs-poll")) + master->delay_needs_poll = true; + ddata->gpiod = devm_gpiod_get_index(dev, NULL, 0, gflags); if (IS_ERR(ddata->gpiod)) return dev_err_probe(dev, PTR_ERR(ddata->gpiod), "gpio_request (pin) failed\n"); diff --git a/drivers/w1/w1.c b/drivers/w1/w1.c index d82e86d3ddf642..097716ca1e1133 100644 --- a/drivers/w1/w1.c +++ b/drivers/w1/w1.c @@ -733,8 +733,10 @@ int w1_attach_slave_device(struct w1_master *dev, struct w1_reg_num *rn) atomic_set(&sl->refcnt, 1); atomic_inc(&sl->master->refcnt); dev->slave_count++; +#if 0 dev_info(&dev->dev, "Attaching one wire slave %02x.%012llx crc %02x\n", rn->family, (unsigned long long)rn->id, rn->crc); +#endif /* slave modules need to be loaded in a context with unlocked mutex */ mutex_unlock(&dev->mutex); diff --git a/drivers/w1/w1_io.c b/drivers/w1/w1_io.c index db3c9522a8a26f..b495624984bd4d 100644 --- a/drivers/w1/w1_io.c +++ b/drivers/w1/w1_io.c @@ -6,6 +6,7 @@ #include <asm/io.h> #include <linux/delay.h> +#include <linux/ktime.h> #include <linux/moduleparam.h> #include <linux/module.h> @@ -36,9 +37,21 @@ static u8 w1_crc8_table[] = { 116, 42, 200, 150, 21, 75, 169, 247, 182, 232, 10, 84, 215, 137, 107, 53 }; -static void w1_delay(unsigned long tm) +static void w1_delay(struct w1_master *dev, unsigned long tm) { - udelay(tm * w1_delay_parm); + ktime_t start, delta; + + if (!dev->bus_master->delay_needs_poll) { + udelay(tm * w1_delay_parm); + return; + } + + start = ktime_get(); + delta = ktime_add(start, ns_to_ktime(1000 * tm * w1_delay_parm)); + do { + dev->bus_master->read_bit(dev->bus_master->data); + udelay(1); + } while (ktime_before(ktime_get(), delta)); } static void w1_write_bit(struct w1_master *dev, int bit); @@ -77,14 +90,14 @@ static void w1_write_bit(struct w1_master *dev, int bit) if (bit) { dev->bus_master->write_bit(dev->bus_master->data, 0); - w1_delay(6); + w1_delay(dev, 6); dev->bus_master->write_bit(dev->bus_master->data, 1); - w1_delay(64); + w1_delay(dev, 64); } else { dev->bus_master->write_bit(dev->bus_master->data, 0); - w1_delay(60); + w1_delay(dev, 60); dev->bus_master->write_bit(dev->bus_master->data, 1); - w1_delay(10); + w1_delay(dev, 10); } if(w1_disable_irqs) local_irq_restore(flags); @@ -164,14 +177,14 @@ static u8 w1_read_bit(struct w1_master *dev) /* sample timing is critical here */ local_irq_save(flags); dev->bus_master->write_bit(dev->bus_master->data, 0); - w1_delay(6); + w1_delay(dev, 6); dev->bus_master->write_bit(dev->bus_master->data, 1); - w1_delay(9); + w1_delay(dev, 9); result = dev->bus_master->read_bit(dev->bus_master->data); local_irq_restore(flags); - w1_delay(55); + w1_delay(dev, 55); return result & 0x1; } @@ -333,16 +346,16 @@ int w1_reset_bus(struct w1_master *dev) * cpu for such a short amount of time AND get it back in * the maximum amount of time. */ - w1_delay(500); + w1_delay(dev, 500); dev->bus_master->write_bit(dev->bus_master->data, 1); - w1_delay(70); + w1_delay(dev, 70); result = dev->bus_master->read_bit(dev->bus_master->data) & 0x1; /* minimum 70 (above) + 430 = 500 us * There aren't any timing requirements between a reset and * the following transactions. Sleeping is safe here. */ - /* w1_delay(430); min required time */ + /* w1_delay(dev, 430); min required time */ msleep(1); } diff --git a/drivers/watchdog/bcm2835_wdt.c b/drivers/watchdog/bcm2835_wdt.c index bb001c5d7f17fd..2ce96ba0ee39e4 100644 --- a/drivers/watchdog/bcm2835_wdt.c +++ b/drivers/watchdog/bcm2835_wdt.c @@ -32,13 +32,7 @@ #define PM_RSTC_WRCFG_SET 0x00000030 #define PM_RSTC_WRCFG_FULL_RESET 0x00000020 #define PM_RSTC_RESET 0x00000102 - -/* - * The Raspberry Pi firmware uses the RSTS register to know which partition - * to boot from. The partition value is spread into bits 0, 2, 4, 6, 8, 10. - * Partition 63 is a special partition used by the firmware to indicate halt. - */ -#define PM_RSTS_RASPBERRYPI_HALT 0x555 +#define PM_RSTS_PARTITION_CLR 0xfffffaaa #define SECS_TO_WDOG_TICKS(x) ((x) << 16) #define WDOG_TICKS_TO_SECS(x) ((x) >> 16) @@ -98,9 +92,24 @@ static unsigned int bcm2835_wdt_get_timeleft(struct watchdog_device *wdog) return WDOG_TICKS_TO_SECS(ret & PM_WDOG_TIME_SET); } -static void __bcm2835_restart(struct bcm2835_wdt *wdt) +/* + * The Raspberry Pi firmware uses the RSTS register to know which partiton + * to boot from. The partiton value is spread into bits 0, 2, 4, 6, 8, 10. + * Partiton 63 is a special partition used by the firmware to indicate halt. + */ + +static void __bcm2835_restart(struct bcm2835_wdt *wdt, u8 partition) { - u32 val; + u32 val, rsts; + + rsts = (partition & BIT(0)) | ((partition & BIT(1)) << 1) | + ((partition & BIT(2)) << 2) | ((partition & BIT(3)) << 3) | + ((partition & BIT(4)) << 4) | ((partition & BIT(5)) << 5); + + val = readl_relaxed(wdt->base + PM_RSTS); + val &= PM_RSTS_PARTITION_CLR; + val |= PM_PASSWORD | rsts; + writel_relaxed(val, wdt->base + PM_RSTS); /* use a timeout of 10 ticks (~150us) */ writel_relaxed(10 | PM_PASSWORD, wdt->base + PM_WDOG); @@ -118,7 +127,15 @@ static int bcm2835_restart(struct watchdog_device *wdog, { struct bcm2835_wdt *wdt = watchdog_get_drvdata(wdog); - __bcm2835_restart(wdt); + unsigned long val; + u8 partition = 0; + + // Allow extra arguments separated by spaces after + // the partition number. + if (data && sscanf(data, "%lu", &val) && val < 63) + partition = val; + + __bcm2835_restart(wdt, partition); return 0; } @@ -153,19 +170,9 @@ static struct watchdog_device bcm2835_wdt_wdd = { static void bcm2835_power_off(void) { struct bcm2835_wdt *wdt = bcm2835_power_off_wdt; - u32 val; - - /* - * We set the watchdog hard reset bit here to distinguish this reset - * from the normal (full) reset. bootcode.bin will not reboot after a - * hard reset. - */ - val = readl_relaxed(wdt->base + PM_RSTS); - val |= PM_PASSWORD | PM_RSTS_RASPBERRYPI_HALT; - writel_relaxed(val, wdt->base + PM_RSTS); - /* Continue with normal reset mechanism */ - __bcm2835_restart(wdt); + /* Partition 63 tells the firmware that this is a halt */ + __bcm2835_restart(wdt, 63); } static int bcm2835_wdt_probe(struct platform_device *pdev) diff --git a/include/drm/drm_blend.h b/include/drm/drm_blend.h index 88bdfec3bd8848..a84c58f3f13cdc 100644 --- a/include/drm/drm_blend.h +++ b/include/drm/drm_blend.h @@ -34,6 +34,7 @@ struct drm_device; struct drm_atomic_state; struct drm_plane; +struct drm_connector; static inline bool drm_rotation_90_or_270(unsigned int rotation) { @@ -58,4 +59,8 @@ int drm_atomic_normalize_zpos(struct drm_device *dev, struct drm_atomic_state *state); int drm_plane_create_blend_mode_property(struct drm_plane *plane, unsigned int supported_modes); + +int drm_connector_create_rotation_property(struct drm_connector *conn, + unsigned int rotation, + unsigned int supported_rotations); #endif diff --git a/include/drm/drm_color_mgmt.h b/include/drm/drm_color_mgmt.h index ed81741036d766..00359830a4be7e 100644 --- a/include/drm/drm_color_mgmt.h +++ b/include/drm/drm_color_mgmt.h @@ -91,6 +91,9 @@ int drm_plane_create_color_properties(struct drm_plane *plane, enum drm_color_encoding default_encoding, enum drm_color_range default_range); +int drm_plane_create_chroma_siting_properties(struct drm_plane *plane, + int32_t default_chroma_siting_h, int32_t default_chroma_siting_v); + /** * enum drm_color_lut_tests - hw-specific LUT tests to perform * diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index 1e2b25e204cb52..758b218608cb36 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h @@ -1139,6 +1139,11 @@ struct drm_connector_state { * @drm_atomic_helper_connector_hdmi_check(). */ struct drm_connector_hdmi_state hdmi; + + /** + * @rotation: Connector property to rotate the maximum output image. + */ + u32 rotation; }; /** @@ -1926,6 +1931,12 @@ struct drm_connector { */ struct drm_property *broadcast_rgb_property; + /** + * @rotation_property: Optional DRM property controlling rotation of the + * output. + */ + struct drm_property *rotation_property; + #define DRM_CONNECTOR_POLL_HPD (1 << 0) #define DRM_CONNECTOR_POLL_CONNECT (1 << 1) #define DRM_CONNECTOR_POLL_DISCONNECT (1 << 2) diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h index 8b48a1974da314..dd7ad1a44717e8 100644 --- a/include/drm/drm_crtc.h +++ b/include/drm/drm_crtc.h @@ -192,7 +192,7 @@ struct drm_crtc_state { * @plane_mask: Bitmask of drm_plane_mask(plane) of planes attached to * this CRTC. */ - u32 plane_mask; + u64 plane_mask; /** * @connector_mask: Bitmask of drm_connector_mask(connector) of diff --git a/include/drm/drm_gem.h b/include/drm/drm_gem.h index d8b86df2ec0dab..5b8b1b059d32c4 100644 --- a/include/drm/drm_gem.h +++ b/include/drm/drm_gem.h @@ -473,6 +473,9 @@ void drm_gem_object_release(struct drm_gem_object *obj); void drm_gem_object_free(struct kref *kref); int drm_gem_object_init(struct drm_device *dev, struct drm_gem_object *obj, size_t size); +int drm_gem_object_init_with_mnt(struct drm_device *dev, + struct drm_gem_object *obj, size_t size, + struct vfsmount *gemfs); void drm_gem_private_object_init(struct drm_device *dev, struct drm_gem_object *obj, size_t size); void drm_gem_private_object_fini(struct drm_gem_object *obj); diff --git a/include/drm/drm_gem_shmem_helper.h b/include/drm/drm_gem_shmem_helper.h index efbc9f27312b53..d22e3fb53631ab 100644 --- a/include/drm/drm_gem_shmem_helper.h +++ b/include/drm/drm_gem_shmem_helper.h @@ -97,6 +97,9 @@ struct drm_gem_shmem_object { container_of(obj, struct drm_gem_shmem_object, base) struct drm_gem_shmem_object *drm_gem_shmem_create(struct drm_device *dev, size_t size); +struct drm_gem_shmem_object *drm_gem_shmem_create_with_mnt(struct drm_device *dev, + size_t size, + struct vfsmount *gemfs); void drm_gem_shmem_free(struct drm_gem_shmem_object *shmem); void drm_gem_shmem_put_pages(struct drm_gem_shmem_object *shmem); diff --git a/include/drm/drm_mipi_dsi.h b/include/drm/drm_mipi_dsi.h index f725f865461147..2836d25b42202d 100644 --- a/include/drm/drm_mipi_dsi.h +++ b/include/drm/drm_mipi_dsi.h @@ -114,29 +114,43 @@ struct mipi_dsi_host *of_find_mipi_dsi_host_by_node(struct device_node *node); /* DSI mode flags */ -/* video mode */ +/* Video mode display. + * Not set denotes a command mode display. + */ #define MIPI_DSI_MODE_VIDEO BIT(0) -/* video burst mode */ +/* Video burst mode. + * Link frequency to be configured via platform configuration. + * This should always be set in conjunction with MIPI_DSI_MODE_VIDEO. + * (DSI spec V1.1 8.11.4) + */ #define MIPI_DSI_MODE_VIDEO_BURST BIT(1) -/* video pulse mode */ +/* Video pulse mode. + * Not set denotes sync event mode. (DSI spec V1.1 8.11.2) + */ #define MIPI_DSI_MODE_VIDEO_SYNC_PULSE BIT(2) -/* enable auto vertical count mode */ +/* Enable auto vertical count mode */ #define MIPI_DSI_MODE_VIDEO_AUTO_VERT BIT(3) -/* enable hsync-end packets in vsync-pulse and v-porch area */ +/* Enable hsync-end packets in vsync-pulse and v-porch area */ #define MIPI_DSI_MODE_VIDEO_HSE BIT(4) -/* disable hfront-porch area */ +/* Transmit NULL packets or LP mode during hfront-porch area. + * Not set denotes sending a blanking packet instead. (DSI spec V1.1 8.11.1) + */ #define MIPI_DSI_MODE_VIDEO_NO_HFP BIT(5) -/* disable hback-porch area */ +/* Transmit NULL packets or LP mode during hback-porch area. + * Not set denotes sending a blanking packet instead. (DSI spec V1.1 8.11.1) + */ #define MIPI_DSI_MODE_VIDEO_NO_HBP BIT(6) -/* disable hsync-active area */ +/* Transmit NULL packets or LP mode during hsync-active area. + * Not set denotes sending a blanking packet instead. (DSI spec V1.1 8.11.1) + */ #define MIPI_DSI_MODE_VIDEO_NO_HSA BIT(7) -/* flush display FIFO on vsync pulse */ +/* Flush display FIFO on vsync pulse */ #define MIPI_DSI_MODE_VSYNC_FLUSH BIT(8) -/* disable EoT packets in HS mode */ +/* Disable EoT packets in HS mode. (DSI spec V1.1 8.1) */ #define MIPI_DSI_MODE_NO_EOT_PACKET BIT(9) -/* device supports non-continuous clock behavior (DSI spec 5.6.1) */ +/* Device supports non-continuous clock behavior (DSI spec V1.1 5.6.1) */ #define MIPI_DSI_CLOCK_NON_CONTINUOUS BIT(10) -/* transmit data in low power */ +/* Transmit data in low power */ #define MIPI_DSI_MODE_LPM BIT(11) /* transmit data ending at the same time for all lanes within one hsync */ #define MIPI_DSI_HS_PKT_END_ALIGNED BIT(12) diff --git a/include/drm/drm_plane.h b/include/drm/drm_plane.h index dd718c62ac31bf..eddf344053fd3d 100644 --- a/include/drm/drm_plane.h +++ b/include/drm/drm_plane.h @@ -183,6 +183,24 @@ struct drm_plane_state { */ enum drm_color_range color_range; + /** + * @chroma_siting_h: + * + * Location of chroma samples horizontally compared to luma + * 0 means chroma is sited with left luma + * 0x8000 is interstitial. 0x10000 is sited with right luma + */ + int32_t chroma_siting_h; + + /** + * @chroma_siting_v: + * + * Location of chroma samples vertically compared to luma + * 0 means chroma is sited with top luma + * 0x8000 is interstitial. 0x10000 is sited with bottom luma + */ + int32_t chroma_siting_v; + /** * @fb_damage_clips: * @@ -786,6 +804,24 @@ struct drm_plane { * @kmsg_panic: Used to register a panic notifier for this plane */ struct kmsg_dumper kmsg_panic; + + /** + * @chroma_siting_h_property: + * + * Optional "CHROMA_SITING_H" property for specifying + * chroma siting for YUV formats. + * See drm_plane_create_chroma_siting_properties(). + */ + struct drm_property *chroma_siting_h_property; + + /** + * @chroma_siting_v_property: + * + * Optional "CHROMA_SITING_V" property for specifying + * chroma siting for YUV formats. + * See drm_plane_create_chroma_siting_properties(). + */ + struct drm_property *chroma_siting_v_property; }; #define obj_to_plane(x) container_of(x, struct drm_plane, base) @@ -907,9 +943,9 @@ static inline unsigned int drm_plane_index(const struct drm_plane *plane) * drm_plane_mask - find the mask of a registered plane * @plane: plane to find mask for */ -static inline u32 drm_plane_mask(const struct drm_plane *plane) +static inline u64 drm_plane_mask(const struct drm_plane *plane) { - return 1 << drm_plane_index(plane); + return 1ULL << drm_plane_index(plane); } struct drm_plane * drm_plane_from_index(struct drm_device *dev, int idx); diff --git a/include/dt-bindings/clock/rp1.h b/include/dt-bindings/clock/rp1.h new file mode 100644 index 00000000000000..1ebb25f1692344 --- /dev/null +++ b/include/dt-bindings/clock/rp1.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2021 Raspberry Pi Ltd. + */ + +#define RP1_PLL_SYS_CORE 0 +#define RP1_PLL_AUDIO_CORE 1 +#define RP1_PLL_VIDEO_CORE 2 + +#define RP1_PLL_SYS 3 +#define RP1_PLL_AUDIO 4 +#define RP1_PLL_VIDEO 5 + +#define RP1_PLL_SYS_PRI_PH 6 +#define RP1_PLL_SYS_SEC_PH 7 +#define RP1_PLL_AUDIO_PRI_PH 8 + +#define RP1_PLL_SYS_SEC 9 +#define RP1_PLL_AUDIO_SEC 10 +#define RP1_PLL_VIDEO_SEC 11 + +#define RP1_CLK_SYS 12 +#define RP1_CLK_SLOW_SYS 13 +#define RP1_CLK_DMA 14 +#define RP1_CLK_UART 15 +#define RP1_CLK_ETH 16 +#define RP1_CLK_PWM0 17 +#define RP1_CLK_PWM1 18 +#define RP1_CLK_AUDIO_IN 19 +#define RP1_CLK_AUDIO_OUT 20 +#define RP1_CLK_I2S 21 +#define RP1_CLK_MIPI0_CFG 22 +#define RP1_CLK_MIPI1_CFG 23 +#define RP1_CLK_PCIE_AUX 24 +#define RP1_CLK_USBH0_MICROFRAME 25 +#define RP1_CLK_USBH1_MICROFRAME 26 +#define RP1_CLK_USBH0_SUSPEND 27 +#define RP1_CLK_USBH1_SUSPEND 28 +#define RP1_CLK_ETH_TSU 29 +#define RP1_CLK_ADC 30 +#define RP1_CLK_SDIO_TIMER 31 +#define RP1_CLK_SDIO_ALT_SRC 32 +#define RP1_CLK_GP0 33 +#define RP1_CLK_GP1 34 +#define RP1_CLK_GP2 35 +#define RP1_CLK_GP3 36 +#define RP1_CLK_GP4 37 +#define RP1_CLK_GP5 38 +#define RP1_CLK_VEC 39 +#define RP1_CLK_DPI 40 +#define RP1_CLK_MIPI0_DPI 41 +#define RP1_CLK_MIPI1_DPI 42 + +/* Extra PLL output channels - RP1B0 only */ +#define RP1_PLL_VIDEO_PRI_PH 43 +#define RP1_PLL_AUDIO_TERN 44 + +/* MIPI clocks managed by the DSI driver */ +#define RP1_CLK_MIPI0_DSI_BYTECLOCK 45 +#define RP1_CLK_MIPI1_DSI_BYTECLOCK 46 diff --git a/include/dt-bindings/gpio/gpio-fsm.h b/include/dt-bindings/gpio/gpio-fsm.h new file mode 100644 index 00000000000000..eb40cfdc71dfea --- /dev/null +++ b/include/dt-bindings/gpio/gpio-fsm.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * This header provides constants for binding rpi,gpio-fsm. + */ + +#ifndef _DT_BINDINGS_GPIO_FSM_H +#define _DT_BINDINGS_GPIO_FSM_H + +#define GF_IN 0 +#define GF_OUT 1 +#define GF_SOFT 2 +#define GF_DELAY 3 +#define GF_SHUTDOWN 4 + +#define GF_IO(t, v) (((v) << 16) | ((t) & 0xffff)) + +#define GF_IP(x) GF_IO(GF_IN, (x)) +#define GF_OP(x) GF_IO(GF_OUT, (x)) +#define GF_SW(x) GF_IO(GF_SOFT, (x)) + +#endif diff --git a/include/dt-bindings/mfd/rp1.h b/include/dt-bindings/mfd/rp1.h new file mode 100644 index 00000000000000..80bbfd61b2700f --- /dev/null +++ b/include/dt-bindings/mfd/rp1.h @@ -0,0 +1,235 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * This header provides constants for the PY MFD. + */ + +#ifndef _RP1_H +#define _RP1_H + +/* Address map */ +#define RP1_SYSINFO_BASE 0x000000 +#define RP1_TBMAN_BASE 0x004000 +#define RP1_SYSCFG_BASE 0x008000 +#define RP1_OTP_BASE 0x00c000 +#define RP1_POWER_BASE 0x010000 +#define RP1_RESETS_BASE 0x014000 +#define RP1_CLOCKS_BANK_DEFAULT_BASE 0x018000 +#define RP1_CLOCKS_BANK_VIDEO_BASE 0x01c000 +#define RP1_PLL_SYS_BASE 0x020000 +#define RP1_PLL_AUDIO_BASE 0x024000 +#define RP1_PLL_VIDEO_BASE 0x028000 +#define RP1_UART0_BASE 0x030000 +#define RP1_UART1_BASE 0x034000 +#define RP1_UART2_BASE 0x038000 +#define RP1_UART3_BASE 0x03c000 +#define RP1_UART4_BASE 0x040000 +#define RP1_UART5_BASE 0x044000 +#define RP1_SPI8_BASE 0x04c000 +#define RP1_SPI0_BASE 0x050000 +#define RP1_SPI1_BASE 0x054000 +#define RP1_SPI2_BASE 0x058000 +#define RP1_SPI3_BASE 0x05c000 +#define RP1_SPI4_BASE 0x060000 +#define RP1_SPI5_BASE 0x064000 +#define RP1_SPI6_BASE 0x068000 +#define RP1_SPI7_BASE 0x06c000 +#define RP1_I2C0_BASE 0x070000 +#define RP1_I2C1_BASE 0x074000 +#define RP1_I2C2_BASE 0x078000 +#define RP1_I2C3_BASE 0x07c000 +#define RP1_I2C4_BASE 0x080000 +#define RP1_I2C5_BASE 0x084000 +#define RP1_I2C6_BASE 0x088000 +#define RP1_AUDIO_IN_BASE 0x090000 +#define RP1_AUDIO_OUT_BASE 0x094000 +#define RP1_PWM0_BASE 0x098000 +#define RP1_PWM1_BASE 0x09c000 +#define RP1_I2S0_BASE 0x0a0000 +#define RP1_I2S1_BASE 0x0a4000 +#define RP1_I2S2_BASE 0x0a8000 +#define RP1_TIMER_BASE 0x0ac000 +#define RP1_SDIO0_APBS_BASE 0x0b0000 +#define RP1_SDIO1_APBS_BASE 0x0b4000 +#define RP1_BUSFABRIC_MONITOR_BASE 0x0c0000 +#define RP1_BUSFABRIC_AXISHIM_BASE 0x0c4000 +#define RP1_ADC_BASE 0x0c8000 +#define RP1_IO_BANK0_BASE 0x0d0000 +#define RP1_IO_BANK1_BASE 0x0d4000 +#define RP1_IO_BANK2_BASE 0x0d8000 +#define RP1_SYS_RIO0_BASE 0x0e0000 +#define RP1_SYS_RIO1_BASE 0x0e4000 +#define RP1_SYS_RIO2_BASE 0x0e8000 +#define RP1_PADS_BANK0_BASE 0x0f0000 +#define RP1_PADS_BANK1_BASE 0x0f4000 +#define RP1_PADS_BANK2_BASE 0x0f8000 +#define RP1_PADS_ETH_BASE 0x0fc000 +#define RP1_ETH_IP_BASE 0x100000 +#define RP1_ETH_CFG_BASE 0x104000 +#define RP1_PCIE_APBS_BASE 0x108000 +#define RP1_MIPI0_CSIDMA_BASE 0x110000 +#define RP1_MIPI0_CSIHOST_BASE 0x114000 +#define RP1_MIPI0_DSIDMA_BASE 0x118000 +#define RP1_MIPI0_DSIHOST_BASE 0x11c000 +#define RP1_MIPI0_MIPICFG_BASE 0x120000 +#define RP1_MIPI0_ISP_BASE 0x124000 +#define RP1_MIPI1_CSIDMA_BASE 0x128000 +#define RP1_MIPI1_CSIHOST_BASE 0x12c000 +#define RP1_MIPI1_DSIDMA_BASE 0x130000 +#define RP1_MIPI1_DSIHOST_BASE 0x134000 +#define RP1_MIPI1_MIPICFG_BASE 0x138000 +#define RP1_MIPI1_ISP_BASE 0x13c000 +#define RP1_VIDEO_OUT_CFG_BASE 0x140000 +#define RP1_VIDEO_OUT_VEC_BASE 0x144000 +#define RP1_VIDEO_OUT_DPI_BASE 0x148000 +#define RP1_XOSC_BASE 0x150000 +#define RP1_WATCHDOG_BASE 0x154000 +#define RP1_DMA_TICK_BASE 0x158000 +#define RP1_SDIO_CLOCKS_BASE 0x15c000 +#define RP1_USBHOST0_APBS_BASE 0x160000 +#define RP1_USBHOST1_APBS_BASE 0x164000 +#define RP1_ROSC0_BASE 0x168000 +#define RP1_ROSC1_BASE 0x16c000 +#define RP1_VBUSCTRL_BASE 0x170000 +#define RP1_TICKS_BASE 0x174000 +#define RP1_PIO_APBS_BASE 0x178000 +#define RP1_SDIO0_AHBLS_BASE 0x180000 +#define RP1_SDIO1_AHBLS_BASE 0x184000 +#define RP1_DMA_BASE 0x188000 +#define RP1_RAM_BASE 0x1c0000 +#define RP1_RAM_SIZE 0x020000 +#define RP1_USBHOST0_AXIS_BASE 0x200000 +#define RP1_USBHOST1_AXIS_BASE 0x300000 +#define RP1_EXAC_BASE 0x400000 + +/* Interrupts */ + +#define RP1_INT_IO_BANK0 0 +#define RP1_INT_IO_BANK1 1 +#define RP1_INT_IO_BANK2 2 +#define RP1_INT_AUDIO_IN 3 +#define RP1_INT_AUDIO_OUT 4 +#define RP1_INT_PWM0 5 +#define RP1_INT_ETH 6 +#define RP1_INT_I2C0 7 +#define RP1_INT_I2C1 8 +#define RP1_INT_I2C2 9 +#define RP1_INT_I2C3 10 +#define RP1_INT_I2C4 11 +#define RP1_INT_I2C5 12 +#define RP1_INT_I2C6 13 +#define RP1_INT_I2S0 14 +#define RP1_INT_I2S1 15 +#define RP1_INT_I2S2 16 +#define RP1_INT_SDIO0 17 +#define RP1_INT_SDIO1 18 +#define RP1_INT_SPI0 19 +#define RP1_INT_SPI1 20 +#define RP1_INT_SPI2 21 +#define RP1_INT_SPI3 22 +#define RP1_INT_SPI4 23 +#define RP1_INT_SPI5 24 +#define RP1_INT_UART0 25 +#define RP1_INT_TIMER_0 26 +#define RP1_INT_TIMER_1 27 +#define RP1_INT_TIMER_2 28 +#define RP1_INT_TIMER_3 29 +#define RP1_INT_USBHOST0 30 +#define RP1_INT_USBHOST0_0 31 +#define RP1_INT_USBHOST0_1 32 +#define RP1_INT_USBHOST0_2 33 +#define RP1_INT_USBHOST0_3 34 +#define RP1_INT_USBHOST1 35 +#define RP1_INT_USBHOST1_0 36 +#define RP1_INT_USBHOST1_1 37 +#define RP1_INT_USBHOST1_2 38 +#define RP1_INT_USBHOST1_3 39 +#define RP1_INT_DMA 40 +#define RP1_INT_PWM1 41 +#define RP1_INT_UART1 42 +#define RP1_INT_UART2 43 +#define RP1_INT_UART3 44 +#define RP1_INT_UART4 45 +#define RP1_INT_UART5 46 +#define RP1_INT_MIPI0 47 +#define RP1_INT_MIPI1 48 +#define RP1_INT_VIDEO_OUT 49 +#define RP1_INT_PIO_0 50 +#define RP1_INT_PIO_1 51 +#define RP1_INT_ADC_FIFO 52 +#define RP1_INT_PCIE_OUT 53 +#define RP1_INT_SPI6 54 +#define RP1_INT_SPI7 55 +#define RP1_INT_SPI8 56 +#define RP1_INT_SYSCFG 58 +#define RP1_INT_CLOCKS_DEFAULT 59 +#define RP1_INT_VBUSCTRL 60 +#define RP1_INT_PROC_MISC 57 +#define RP1_INT_END 61 + +/* DMA peripherals (for pacing) */ +#define RP1_DMA_I2C0_RX 0x0 +#define RP1_DMA_I2C0_TX 0x1 +#define RP1_DMA_I2C1_RX 0x2 +#define RP1_DMA_I2C1_TX 0x3 +#define RP1_DMA_I2C2_RX 0x4 +#define RP1_DMA_I2C2_TX 0x5 +#define RP1_DMA_I2C3_RX 0x6 +#define RP1_DMA_I2C3_TX 0x7 +#define RP1_DMA_I2C4_RX 0x8 +#define RP1_DMA_I2C4_TX 0x9 +#define RP1_DMA_I2C5_RX 0xa +#define RP1_DMA_I2C5_TX 0xb +#define RP1_DMA_SPI0_RX 0xc +#define RP1_DMA_SPI0_TX 0xd +#define RP1_DMA_SPI1_RX 0xe +#define RP1_DMA_SPI1_TX 0xf +#define RP1_DMA_SPI2_RX 0x10 +#define RP1_DMA_SPI2_TX 0x11 +#define RP1_DMA_SPI3_RX 0x12 +#define RP1_DMA_SPI3_TX 0x13 +#define RP1_DMA_SPI4_RX 0x14 +#define RP1_DMA_SPI4_TX 0x15 +#define RP1_DMA_SPI5_RX 0x16 +#define RP1_DMA_SPI5_TX 0x17 +#define RP1_DMA_PWM0 0x18 +#define RP1_DMA_UART0_RX 0x19 +#define RP1_DMA_UART0_TX 0x1a +#define RP1_DMA_AUDIO_IN_CH0 0x1b +#define RP1_DMA_AUDIO_IN_CH1 0x1c +#define RP1_DMA_AUDIO_OUT 0x1d +#define RP1_DMA_PWM1 0x1e +#define RP1_DMA_I2S0_RX 0x1f +#define RP1_DMA_I2S0_TX 0x20 +#define RP1_DMA_I2S1_RX 0x21 +#define RP1_DMA_I2S1_TX 0x22 +#define RP1_DMA_I2S2_RX 0x23 +#define RP1_DMA_I2S2_TX 0x24 +#define RP1_DMA_UART1_RX 0x25 +#define RP1_DMA_UART1_TX 0x26 +#define RP1_DMA_UART2_RX 0x27 +#define RP1_DMA_UART2_TX 0x28 +#define RP1_DMA_UART3_RX 0x29 +#define RP1_DMA_UART3_TX 0x2a +#define RP1_DMA_UART4_RX 0x2b +#define RP1_DMA_UART4_TX 0x2c +#define RP1_DMA_UART5_RX 0x2d +#define RP1_DMA_UART5_TX 0x2e +#define RP1_DMA_ADC 0x2f +#define RP1_DMA_DMA_TICK_TICK0 0x30 +#define RP1_DMA_DMA_TICK_TICK1 0x31 +#define RP1_DMA_SPI6_RX 0x32 +#define RP1_DMA_SPI6_TX 0x33 +#define RP1_DMA_SPI7_RX 0x34 +#define RP1_DMA_SPI7_TX 0x35 +#define RP1_DMA_SPI8_RX 0x36 +#define RP1_DMA_SPI8_TX 0x37 +#define RP1_DMA_PIO_CH0_TX 0x38 +#define RP1_DMA_PIO_CH0_RX 0x39 +#define RP1_DMA_PIO_CH1_TX 0x3a +#define RP1_DMA_PIO_CH1_RX 0x3b +#define RP1_DMA_PIO_CH2_TX 0x3c +#define RP1_DMA_PIO_CH2_RX 0x3d +#define RP1_DMA_PIO_CH3_TX 0x3e +#define RP1_DMA_PIO_CH3_RX 0x3f + +#endif diff --git a/include/linux/backlight.h b/include/linux/backlight.h index ea9c1bc8148ee0..3caa7b4c1f3cdf 100644 --- a/include/linux/backlight.h +++ b/include/linux/backlight.h @@ -255,6 +255,13 @@ struct backlight_properties { * @scale: The type of the brightness scale. */ enum backlight_scale scale; + +#define BL_DISPLAY_NAME_LEN 32 + /** + * @display_name: Optional name that can be registered to associate a + * backlight device with a display device. + */ + char display_name[BL_DISPLAY_NAME_LEN]; }; /** @@ -459,12 +466,20 @@ of_find_backlight_by_node(struct device_node *node) #if IS_ENABLED(CONFIG_BACKLIGHT_CLASS_DEVICE) struct backlight_device *devm_of_find_backlight(struct device *dev); +int backlight_set_display_name(struct backlight_device *bd, const char *name); #else static inline struct backlight_device * devm_of_find_backlight(struct device *dev) { return NULL; } + +static inline int backlight_set_display_name(struct backlight_device *bd, + const char *name) +{ + return 0; +} + #endif #endif diff --git a/include/linux/brcmphy.h b/include/linux/brcmphy.h index 028b3e00378e3d..535aa34a419655 100644 --- a/include/linux/brcmphy.h +++ b/include/linux/brcmphy.h @@ -24,6 +24,7 @@ #define PHY_ID_BCM5411 0x00206070 #define PHY_ID_BCM5421 0x002060e0 #define PHY_ID_BCM54210E 0x600d84a0 +#define PHY_ID_BCM54213PE 0x600d84a2 #define PHY_ID_BCM5464 0x002060b0 #define PHY_ID_BCM5461 0x002060c0 #define PHY_ID_BCM54612E 0x03625e60 diff --git a/include/linux/broadcom/bcm2835_smi.h b/include/linux/broadcom/bcm2835_smi.h new file mode 100644 index 00000000000000..ee3a75edfc033e --- /dev/null +++ b/include/linux/broadcom/bcm2835_smi.h @@ -0,0 +1,391 @@ +/** + * Declarations and definitions for Broadcom's Secondary Memory Interface + * + * Written by Luke Wren <luke@raspberrypi.org> + * Copyright (c) 2015, Raspberry Pi (Trading) Ltd. + * Copyright (c) 2010-2012 Broadcom. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The names of the above-listed copyright holders may not be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2, as published by the Free + * Software Foundation. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef BCM2835_SMI_H +#define BCM2835_SMI_H + +#include <linux/ioctl.h> + +#ifndef __KERNEL__ +#include <stdint.h> +#include <stdbool.h> +#endif + +#define BCM2835_SMI_IOC_MAGIC 0x1 +#define BCM2835_SMI_INVALID_HANDLE (~0) + +/* IOCTLs 0x100...0x1ff are not device-specific - we can use them */ +#define BCM2835_SMI_IOC_GET_SETTINGS _IO(BCM2835_SMI_IOC_MAGIC, 0) +#define BCM2835_SMI_IOC_WRITE_SETTINGS _IO(BCM2835_SMI_IOC_MAGIC, 1) +#define BCM2835_SMI_IOC_ADDRESS _IO(BCM2835_SMI_IOC_MAGIC, 2) +#define BCM2835_SMI_IOC_MAX 2 + +#define SMI_WIDTH_8BIT 0 +#define SMI_WIDTH_16BIT 1 +#define SMI_WIDTH_9BIT 2 +#define SMI_WIDTH_18BIT 3 + +/* max number of bytes where DMA will not be used */ +#define DMA_THRESHOLD_BYTES 128 +#define DMA_BOUNCE_BUFFER_SIZE (1024 * 1024 / 2) +#define DMA_BOUNCE_BUFFER_COUNT 3 + + +struct smi_settings { + int data_width; + /* Whether or not to pack multiple SMI transfers into a + single 32 bit FIFO word */ + bool pack_data; + + /* Timing for reads (writes the same but for WE) + * + * OE ----------+ +-------------------- + * | | + * +----------+ + * SD -<==============================>----------- + * SA -<=========================================>- + * <-setup-> <-strobe -> <-hold -> <- pace -> + */ + + int read_setup_time; + int read_hold_time; + int read_pace_time; + int read_strobe_time; + + int write_setup_time; + int write_hold_time; + int write_pace_time; + int write_strobe_time; + + bool dma_enable; /* DREQs */ + bool dma_passthrough_enable; /* External DREQs */ + int dma_read_thresh; + int dma_write_thresh; + int dma_panic_read_thresh; + int dma_panic_write_thresh; +}; + +/**************************************************************************** +* +* Declare exported SMI functions +* +***************************************************************************/ + +#ifdef __KERNEL__ + +#include <linux/dmaengine.h> /* for enum dma_transfer_direction */ +#include <linux/of.h> +#include <linux/semaphore.h> + +struct bcm2835_smi_instance; + +struct bcm2835_smi_bounce_info { + struct semaphore callback_sem; + void *buffer[DMA_BOUNCE_BUFFER_COUNT]; + dma_addr_t phys[DMA_BOUNCE_BUFFER_COUNT]; + struct scatterlist sgl[DMA_BOUNCE_BUFFER_COUNT]; +}; + + +void bcm2835_smi_set_regs_from_settings(struct bcm2835_smi_instance *); + +struct smi_settings *bcm2835_smi_get_settings_from_regs( + struct bcm2835_smi_instance *inst); + +void bcm2835_smi_write_buf( + struct bcm2835_smi_instance *inst, + const void *buf, + size_t n_bytes); + +void bcm2835_smi_read_buf( + struct bcm2835_smi_instance *inst, + void *buf, + size_t n_bytes); + +void bcm2835_smi_set_address(struct bcm2835_smi_instance *inst, + unsigned int address); + +ssize_t bcm2835_smi_user_dma( + struct bcm2835_smi_instance *inst, + enum dma_transfer_direction dma_dir, + char __user *user_ptr, + size_t count, + struct bcm2835_smi_bounce_info **bounce); + +struct bcm2835_smi_instance *bcm2835_smi_get(struct device_node *node); + +#endif /* __KERNEL__ */ + +/**************************************************************** +* +* Implementation-only declarations +* +****************************************************************/ + +#ifdef BCM2835_SMI_IMPLEMENTATION + +/* Clock manager registers for SMI clock: */ +#define CM_SMI_BASE_ADDRESS ((BCM2708_PERI_BASE) + 0x1010b0) +/* Clock manager "password" to protect registers from spurious writes */ +#define CM_PWD (0x5a << 24) + +#define CM_SMI_CTL 0x00 +#define CM_SMI_DIV 0x04 + +#define CM_SMI_CTL_FLIP (1 << 8) +#define CM_SMI_CTL_BUSY (1 << 7) +#define CM_SMI_CTL_KILL (1 << 5) +#define CM_SMI_CTL_ENAB (1 << 4) +#define CM_SMI_CTL_SRC_MASK (0xf) +#define CM_SMI_CTL_SRC_OFFS (0) + +#define CM_SMI_DIV_DIVI_MASK (0xf << 12) +#define CM_SMI_DIV_DIVI_OFFS (12) +#define CM_SMI_DIV_DIVF_MASK (0xff << 4) +#define CM_SMI_DIV_DIVF_OFFS (4) + +/* SMI register mapping:*/ +#define SMI_BASE_ADDRESS ((BCM2708_PERI_BASE) + 0x600000) + +#define SMICS 0x00 /* control + status register */ +#define SMIL 0x04 /* length/count (n external txfers) */ +#define SMIA 0x08 /* address register */ +#define SMID 0x0c /* data register */ +#define SMIDSR0 0x10 /* device 0 read settings */ +#define SMIDSW0 0x14 /* device 0 write settings */ +#define SMIDSR1 0x18 /* device 1 read settings */ +#define SMIDSW1 0x1c /* device 1 write settings */ +#define SMIDSR2 0x20 /* device 2 read settings */ +#define SMIDSW2 0x24 /* device 2 write settings */ +#define SMIDSR3 0x28 /* device 3 read settings */ +#define SMIDSW3 0x2c /* device 3 write settings */ +#define SMIDC 0x30 /* DMA control registers */ +#define SMIDCS 0x34 /* direct control/status register */ +#define SMIDA 0x38 /* direct address register */ +#define SMIDD 0x3c /* direct data registers */ +#define SMIFD 0x40 /* FIFO debug register */ + + + +/* Control and Status register bits: + * SMICS_RXF : RX fifo full: 1 when RX fifo is full + * SMICS_TXE : TX fifo empty: 1 when empty. + * SMICS_RXD : RX fifo contains data: 1 when there is data. + * SMICS_TXD : TX fifo can accept data: 1 when true. + * SMICS_RXR : RX fifo needs reading: 1 when fifo more than 3/4 full, or + * when "DONE" and fifo not emptied. + * SMICS_TXW : TX fifo needs writing: 1 when less than 1/4 full. + * SMICS_AFERR : AXI FIFO error: 1 when fifo read when empty or written + * when full. Write 1 to clear. + * SMICS_EDREQ : 1 when external DREQ received. + * SMICS_PXLDAT : Pixel data: write 1 to enable pixel transfer modes. + * SMICS_SETERR : 1 if there was an error writing to setup regs (e.g. + * tx was in progress). Write 1 to clear. + * SMICS_PVMODE : Set to 1 to enable pixel valve mode. + * SMICS_INTR : Set to 1 to enable interrupt on RX. + * SMICS_INTT : Set to 1 to enable interrupt on TX. + * SMICS_INTD : Set to 1 to enable interrupt on DONE condition. + * SMICS_TEEN : Tear effect mode enabled: Programmed transfers will wait + * for a TE trigger before writing. + * SMICS_PAD1 : Padding settings for external transfers. For writes: the + * number of bytes initially written to the TX fifo that + * SMICS_PAD0 : should be ignored. For reads: the number of bytes that will + * be read before the data, and should be dropped. + * SMICS_WRITE : Transfer direction: 1 = write to external device, 0 = read + * SMICS_CLEAR : Write 1 to clear the FIFOs. + * SMICS_START : Write 1 to start the programmed transfer. + * SMICS_ACTIVE : Reads as 1 when a programmed transfer is underway. + * SMICS_DONE : Reads as 1 when transfer finished. For RX, not set until + * FIFO emptied. + * SMICS_ENABLE : Set to 1 to enable the SMI peripheral, 0 to disable. + */ + +#define SMICS_RXF (1 << 31) +#define SMICS_TXE (1 << 30) +#define SMICS_RXD (1 << 29) +#define SMICS_TXD (1 << 28) +#define SMICS_RXR (1 << 27) +#define SMICS_TXW (1 << 26) +#define SMICS_AFERR (1 << 25) +#define SMICS_EDREQ (1 << 15) +#define SMICS_PXLDAT (1 << 14) +#define SMICS_SETERR (1 << 13) +#define SMICS_PVMODE (1 << 12) +#define SMICS_INTR (1 << 11) +#define SMICS_INTT (1 << 10) +#define SMICS_INTD (1 << 9) +#define SMICS_TEEN (1 << 8) +#define SMICS_PAD1 (1 << 7) +#define SMICS_PAD0 (1 << 6) +#define SMICS_WRITE (1 << 5) +#define SMICS_CLEAR (1 << 4) +#define SMICS_START (1 << 3) +#define SMICS_ACTIVE (1 << 2) +#define SMICS_DONE (1 << 1) +#define SMICS_ENABLE (1 << 0) + +/* Address register bits: */ + +#define SMIA_DEVICE_MASK ((1 << 9) | (1 << 8)) +#define SMIA_DEVICE_OFFS (8) +#define SMIA_ADDR_MASK (0x3f) /* bits 5 -> 0 */ +#define SMIA_ADDR_OFFS (0) + +/* DMA control register bits: + * SMIDC_DMAEN : DMA enable: set 1: DMA requests will be issued. + * SMIDC_DMAP : DMA passthrough: when set to 0, top two data pins are used by + * SMI as usual. When set to 1, the top two pins are used for + * external DREQs: pin 16 read request, 17 write. + * SMIDC_PANIC* : Threshold at which DMA will panic during read/write. + * SMIDC_REQ* : Threshold at which DMA will generate a DREQ. + */ + +#define SMIDC_DMAEN (1 << 28) +#define SMIDC_DMAP (1 << 24) +#define SMIDC_PANICR_MASK (0x3f << 18) +#define SMIDC_PANICR_OFFS (18) +#define SMIDC_PANICW_MASK (0x3f << 12) +#define SMIDC_PANICW_OFFS (12) +#define SMIDC_REQR_MASK (0x3f << 6) +#define SMIDC_REQR_OFFS (6) +#define SMIDC_REQW_MASK (0x3f) +#define SMIDC_REQW_OFFS (0) + +/* Device settings register bits: same for all 4 (or 3?) device register sets. + * Device read settings: + * SMIDSR_RWIDTH : Read transfer width. 00 = 8bit, 01 = 16bit, + * 10 = 18bit, 11 = 9bit. + * SMIDSR_RSETUP : Read setup time: number of core cycles between chip + * select/address and read strobe. Min 1, max 64. + * SMIDSR_MODE68 : 1 for System 68 mode (i.e. enable + direction pins, + * rather than OE + WE pin) + * SMIDSR_FSETUP : If set to 1, setup time only applies to first + * transfer after address change. + * SMIDSR_RHOLD : Number of core cycles between read strobe going + * inactive and CS/address going inactive. Min 1, max 64 + * SMIDSR_RPACEALL : When set to 1, this device's RPACE value will always + * be used for the next transaction, even if it is not + * to this device. + * SMIDSR_RPACE : Number of core cycles spent waiting between CS + * deassert and start of next transfer. Min 1, max 128 + * SMIDSR_RDREQ : 1 = use external DMA request on SD16 to pace reads + * from device. Must also set DMAP in SMICS. + * SMIDSR_RSTROBE : Number of cycles to assert the read strobe. + * min 1, max 128. + */ +#define SMIDSR_RWIDTH_MASK ((1<<31)|(1<<30)) +#define SMIDSR_RWIDTH_OFFS (30) +#define SMIDSR_RSETUP_MASK (0x3f << 24) +#define SMIDSR_RSETUP_OFFS (24) +#define SMIDSR_MODE68 (1 << 23) +#define SMIDSR_FSETUP (1 << 22) +#define SMIDSR_RHOLD_MASK (0x3f << 16) +#define SMIDSR_RHOLD_OFFS (16) +#define SMIDSR_RPACEALL (1 << 15) +#define SMIDSR_RPACE_MASK (0x7f << 8) +#define SMIDSR_RPACE_OFFS (8) +#define SMIDSR_RDREQ (1 << 7) +#define SMIDSR_RSTROBE_MASK (0x7f) +#define SMIDSR_RSTROBE_OFFS (0) + +/* Device write settings: + * SMIDSW_WWIDTH : Write transfer width. 00 = 8bit, 01 = 16bit, + * 10= 18bit, 11 = 9bit. + * SMIDSW_WSETUP : Number of cycles between CS assert and write strobe. + * Min 1, max 64. + * SMIDSW_WFORMAT : Pixel format of input. 0 = 16bit RGB 565, + * 1 = 32bit RGBA 8888 + * SMIDSW_WSWAP : 1 = swap pixel data bits. (Use with SMICS_PXLDAT) + * SMIDSW_WHOLD : Time between WE deassert and CS deassert. 1 to 64 + * SMIDSW_WPACEALL : 1: this device's WPACE will be used for the next + * transfer, regardless of that transfer's device. + * SMIDSW_WPACE : Cycles between CS deassert and next CS assert. + * Min 1, max 128 + * SMIDSW_WDREQ : Use external DREQ on pin 17 to pace writes. DMAP must + * be set in SMICS. + * SMIDSW_WSTROBE : Number of cycles to assert the write strobe. + * Min 1, max 128 + */ +#define SMIDSW_WWIDTH_MASK ((1<<31)|(1<<30)) +#define SMIDSW_WWIDTH_OFFS (30) +#define SMIDSW_WSETUP_MASK (0x3f << 24) +#define SMIDSW_WSETUP_OFFS (24) +#define SMIDSW_WFORMAT (1 << 23) +#define SMIDSW_WSWAP (1 << 22) +#define SMIDSW_WHOLD_MASK (0x3f << 16) +#define SMIDSW_WHOLD_OFFS (16) +#define SMIDSW_WPACEALL (1 << 15) +#define SMIDSW_WPACE_MASK (0x7f << 8) +#define SMIDSW_WPACE_OFFS (8) +#define SMIDSW_WDREQ (1 << 7) +#define SMIDSW_WSTROBE_MASK (0x7f) +#define SMIDSW_WSTROBE_OFFS (0) + +/* Direct transfer control + status register + * SMIDCS_WRITE : Direction of transfer: 1 -> write, 0 -> read + * SMIDCS_DONE : 1 when a transfer has finished. Write 1 to clear. + * SMIDCS_START : Write 1 to start a transfer, if one is not already underway. + * SMIDCE_ENABLE: Write 1 to enable SMI in direct mode. + */ + +#define SMIDCS_WRITE (1 << 3) +#define SMIDCS_DONE (1 << 2) +#define SMIDCS_START (1 << 1) +#define SMIDCS_ENABLE (1 << 0) + +/* Direct transfer address register + * SMIDA_DEVICE : Indicates which of the device settings banks should be used. + * SMIDA_ADDR : The value to be asserted on the address pins. + */ + +#define SMIDA_DEVICE_MASK ((1<<9)|(1<<8)) +#define SMIDA_DEVICE_OFFS (8) +#define SMIDA_ADDR_MASK (0x3f) +#define SMIDA_ADDR_OFFS (0) + +/* FIFO debug register + * SMIFD_FLVL : The high-tide mark of FIFO count during the most recent txfer + * SMIFD_FCNT : The current FIFO count. + */ +#define SMIFD_FLVL_MASK (0x3f << 8) +#define SMIFD_FLVL_OFFS (8) +#define SMIFD_FCNT_MASK (0x3f) +#define SMIFD_FCNT_OFFS (0) + +#endif /* BCM2835_SMI_IMPLEMENTATION */ + +#endif /* BCM2835_SMI_H */ diff --git a/include/linux/broadcom/vc_mem.h b/include/linux/broadcom/vc_mem.h new file mode 100644 index 00000000000000..3c707923749647 --- /dev/null +++ b/include/linux/broadcom/vc_mem.h @@ -0,0 +1,39 @@ +/* + * Copyright 2010 - 2011 Broadcom Corporation. All rights reserved. + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2, available at + * http://www.broadcom.com/licenses/GPLv2.php (the "GPL"). + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a + * license other than the GPL, without Broadcom's express prior written + * consent. + */ + +#ifndef _VC_MEM_H +#define _VC_MEM_H + +#include <linux/ioctl.h> + +#define VC_MEM_IOC_MAGIC 'v' + +#define VC_MEM_IOC_MEM_PHYS_ADDR _IOR(VC_MEM_IOC_MAGIC, 0, unsigned long) +#define VC_MEM_IOC_MEM_SIZE _IOR(VC_MEM_IOC_MAGIC, 1, unsigned int) +#define VC_MEM_IOC_MEM_BASE _IOR(VC_MEM_IOC_MAGIC, 2, unsigned int) +#define VC_MEM_IOC_MEM_LOAD _IOR(VC_MEM_IOC_MAGIC, 3, unsigned int) + +#ifdef __KERNEL__ +#define VC_MEM_TO_ARM_ADDR_MASK 0x3FFFFFFF + +extern unsigned long mm_vc_mem_phys_addr; +extern unsigned int mm_vc_mem_size; +extern int vc_mem_get_current_size(void); +#endif + +#ifdef CONFIG_COMPAT +#define VC_MEM_IOC_MEM_PHYS_ADDR32 _IOR(VC_MEM_IOC_MAGIC, 0, compat_ulong_t) +#endif + +#endif /* _VC_MEM_H */ diff --git a/include/linux/cma.h b/include/linux/cma.h index d15b64f51336df..961e452312cd6d 100644 --- a/include/linux/cma.h +++ b/include/linux/cma.h @@ -56,6 +56,7 @@ extern void cma_reserve_pages_on_error(struct cma *cma); #ifdef CONFIG_CMA struct folio *cma_alloc_folio(struct cma *cma, int order, gfp_t gfp); bool cma_free_folio(struct cma *cma, const struct folio *folio); +int cma_check_range(u64 *start, u64 *end); #else static inline struct folio *cma_alloc_folio(struct cma *cma, int order, gfp_t gfp) { @@ -66,6 +67,11 @@ static inline bool cma_free_folio(struct cma *cma, const struct folio *folio) { return false; } + +static inline int cma_check_range(u64 *start, u64 *end) +{ + return 0; +} #endif #endif diff --git a/include/linux/fb.h b/include/linux/fb.h index 267b59ead43212..807b48e9dfb63d 100644 --- a/include/linux/fb.h +++ b/include/linux/fb.h @@ -511,6 +511,7 @@ struct fb_info { bool skip_vt_switch; /* no VT switch on suspend/resume required */ bool skip_panic; /* Do not write to the fb after a panic */ + bool custom_fb_num; /* Use value in node as the preferred node number */ }; /* This will go away @@ -600,6 +601,7 @@ extern ssize_t fb_sys_write(struct fb_info *info, const char __user *buf, .fb_imageblit = sys_imageblit /* fbmem.c */ +extern void fb_set_lowest_dynamic_fb(int min_fb_dev); extern int register_framebuffer(struct fb_info *fb_info); extern void unregister_framebuffer(struct fb_info *fb_info); extern int devm_register_framebuffer(struct device *dev, struct fb_info *fb_info); diff --git a/include/linux/gpio/driver.h b/include/linux/gpio/driver.h index 2dd7cb9cc270a6..f22767a99c7a7e 100644 --- a/include/linux/gpio/driver.h +++ b/include/linux/gpio/driver.h @@ -713,6 +713,7 @@ int bgpio_init(struct gpio_chip *gc, struct device *dev, #define BGPIOF_READ_OUTPUT_REG_SET BIT(4) /* reg_set stores output value */ #define BGPIOF_NO_OUTPUT BIT(5) /* only input */ #define BGPIOF_NO_SET_ON_INPUT BIT(6) +#define BGPIOF_REG_DIRECT BIT(7) /* ignore shadow registers */ #ifdef CONFIG_GPIOLIB_IRQCHIP int gpiochip_irqchip_add_domain(struct gpio_chip *gc, diff --git a/include/linux/irqchip/irq-bcm2836.h b/include/linux/irqchip/irq-bcm2836.h index ac5719d8f56be1..6fe053486447d9 100644 --- a/include/linux/irqchip/irq-bcm2836.h +++ b/include/linux/irqchip/irq-bcm2836.h @@ -59,3 +59,5 @@ #define LOCAL_IRQ_GPU_FAST 8 #define LOCAL_IRQ_PMU_FAST 9 #define LAST_IRQ LOCAL_IRQ_PMU_FAST + +void bcm2836_arm_irqchip_spin_gpu_irq(void); diff --git a/include/linux/leds.h b/include/linux/leds.h index 2337f516fa7c2c..df9b22e5d6e974 100644 --- a/include/linux/leds.h +++ b/include/linux/leds.h @@ -109,6 +109,9 @@ struct led_classdev { #define LED_INIT_DEFAULT_TRIGGER BIT(23) #define LED_REJECT_NAME_CONFLICT BIT(24) #define LED_MULTI_COLOR BIT(25) + /* Additions for Raspberry Pi PWR LED */ +#define SET_GPIO_INPUT BIT(30) +#define SET_GPIO_OUTPUT BIT(31) /* set_brightness_work / blink_timer flags, atomic, private. */ unsigned long work_flags; diff --git a/include/linux/mempolicy.h b/include/linux/mempolicy.h index 1add16f216124d..54ace89e59a9c8 100644 --- a/include/linux/mempolicy.h +++ b/include/linux/mempolicy.h @@ -135,6 +135,8 @@ bool vma_policy_mof(struct vm_area_struct *vma); extern void numa_default_policy(void); extern void numa_policy_init(void); +nodemask_t *numa_policy_nodemask(gfp_t gfp, struct mempolicy *pol, pgoff_t ilx, + int *nid); extern void mpol_rebind_task(struct task_struct *tsk, const nodemask_t *new); extern void mpol_rebind_mm(struct mm_struct *mm, nodemask_t *new); @@ -238,6 +240,14 @@ static inline void numa_policy_init(void) { } +static inline nodemask_t * +numa_policy_nodemask(gfp_t gfp, struct mempolicy *pol, pgoff_t ilx, int *nid) +{ + *nid = NUMA_NO_NODE; + + return NULL; +} + static inline void numa_default_policy(void) { } diff --git a/include/linux/mfd/rpisense/framebuffer.h b/include/linux/mfd/rpisense/framebuffer.h new file mode 100644 index 00000000000000..621f24386bb0b0 --- /dev/null +++ b/include/linux/mfd/rpisense/framebuffer.h @@ -0,0 +1,35 @@ +/* + * Raspberry Pi Sense HAT framebuffer driver + * http://raspberrypi.org + * + * Copyright (C) 2015 Raspberry Pi + * + * Author: Serge Schneider + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#ifndef __LINUX_RPISENSE_FB_H_ +#define __LINUX_RPISENSE_FB_H_ + +#include <linux/regmap.h> + +#define SENSEFB_FBIO_IOC_MAGIC 0xF1 + +#define SENSEFB_FBIOGET_GAMMA _IO(SENSEFB_FBIO_IOC_MAGIC, 0) +#define SENSEFB_FBIOSET_GAMMA _IO(SENSEFB_FBIO_IOC_MAGIC, 1) +#define SENSEFB_FBIORESET_GAMMA _IO(SENSEFB_FBIO_IOC_MAGIC, 2) + +struct rpisense; + +struct rpisense_fb { + struct fb_info *info; + struct platform_device *pdev; + struct regmap *regmap; +}; + +#endif diff --git a/include/linux/microchipphy.h b/include/linux/microchipphy.h index 517288da19fd3d..626c450d71f45c 100644 --- a/include/linux/microchipphy.h +++ b/include/linux/microchipphy.h @@ -61,6 +61,14 @@ /* Registers specific to the LAN7800/LAN7850 embedded phy */ #define LAN78XX_PHY_LED_MODE_SELECT (0x1D) +#define LAN78XX_PHY_CTRL3 (0x14) +#define LAN78XX_PHY_CTRL3_AUTO_DOWNSHIFT (0x0010) +#define LAN78XX_PHY_CTRL3_DOWNSHIFT_CTRL_MASK (0x000c) +#define LAN78XX_PHY_CTRL3_DOWNSHIFT_CTRL_2 (0x0000) +#define LAN78XX_PHY_CTRL3_DOWNSHIFT_CTRL_3 (0x0004) +#define LAN78XX_PHY_CTRL3_DOWNSHIFT_CTRL_4 (0x0008) +#define LAN78XX_PHY_CTRL3_DOWNSHIFT_CTRL_5 (0x000c) + /* DSP registers */ #define PHY_ARDENNES_MMD_DEV_3_PHY_CFG (0x806A) #define PHY_ARDENNES_MMD_DEV_3_PHY_CFG_ZD_DLY_EN_ (0x2000) diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index eb67d3d5ff5b22..6289155f806f15 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -295,6 +295,8 @@ struct mmc_card { #define MMC_QUIRK_BROKEN_SD_CACHE (1<<15) /* Disable broken SD cache support */ #define MMC_QUIRK_BROKEN_CACHE_FLUSH (1<<16) /* Don't flush cache until the write has occurred */ #define MMC_QUIRK_BROKEN_SD_POWEROFF_NOTIFY (1<<17) /* Disable broken SD poweroff notify support */ +#define MMC_QUIRK_WORKING_SD_CQ (1<<30) /* SD card has known-good CQ implementation */ +#define MMC_QUIRK_ERASE_BROKEN (1<<31) /* Skip erase */ bool written_flag; /* Indicates eMMC has been written since power on */ bool reenable_cmdq; /* Re-enable Command Queue */ @@ -319,6 +321,7 @@ struct mmc_card { struct sd_switch_caps sw_caps; /* switch (CMD6) caps */ struct sd_ext_reg ext_power; /* SD extension reg for PM */ struct sd_ext_reg ext_perf; /* SD extension reg for PERF */ + u8 *ext_reg_buf; /* 512 byte block for extension register R/W */ unsigned int sdio_funcs; /* number of SDIO functions */ atomic_t sdio_funcs_probed; /* number of probed SDIO funcs */ @@ -341,6 +344,8 @@ struct mmc_card { unsigned int nr_parts; struct workqueue_struct *complete_wq; /* Private workqueue */ + + unsigned int max_posted_writes; /* command queue posted write limit */ }; static inline bool mmc_large_sector(struct mmc_card *card) diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 8fc2b328ec4d19..b89232b091b63a 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -404,6 +404,7 @@ struct mmc_host { #define MMC_CAP2_CRYPTO 0 #endif #define MMC_CAP2_ALT_GPT_TEGRA (1 << 28) /* Host with eMMC that has GPT entry at a non-standard location */ +#define MMC_CAP2_SD_CQE_PERMISSIVE (1 << 31) /* Ignore allow-list for CQ capable SD card detection */ int fixed_drv_type; /* fixed driver type for non-removable media */ diff --git a/include/linux/mmc/sd.h b/include/linux/mmc/sd.h index 865cc0ca8543d1..eb4e05ef9abdc9 100644 --- a/include/linux/mmc/sd.h +++ b/include/linux/mmc/sd.h @@ -29,6 +29,9 @@ #define SD_APP_OP_COND 41 /* bcr [31:0] OCR R3 */ #define SD_APP_SEND_SCR 51 /* adtc R1 */ + /* class 1 */ +#define SD_CMDQ_TASK_MGMT 43 /* ac See below R1b */ + /* class 11 */ #define SD_READ_EXTR_SINGLE 48 /* adtc [31:0] R1 */ #define SD_WRITE_EXTR_SINGLE 49 /* adtc [31:0] R1 */ @@ -61,6 +64,15 @@ * [7:0] Check Pattern (0xAA) */ +/* + * SD_CMDQ_TASK_MGMT argument format: + * + * [31:21] Reserved (0) + * [20:16] Task ID + * [15:4] Reserved (0) + * [3:0] Operation - 0x1 = abort all tasks, 0x2 = abort Task ID + */ + /* * SCR field definitions */ diff --git a/include/linux/module.h b/include/linux/module.h index 88ecc5e9f52307..5e2686e056e4bf 100644 --- a/include/linux/module.h +++ b/include/linux/module.h @@ -513,7 +513,7 @@ struct module { unsigned int num_bpf_raw_events; struct bpf_raw_event_map *bpf_raw_events; #endif -#ifdef CONFIG_DEBUG_INFO_BTF_MODULES +#if 1 unsigned int btf_data_size; unsigned int btf_base_data_size; void *btf_data; diff --git a/include/linux/pio_instructions.h b/include/linux/pio_instructions.h new file mode 100644 index 00000000000000..a72934b1ed607e --- /dev/null +++ b/include/linux/pio_instructions.h @@ -0,0 +1,481 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + */ + +#ifndef _HARDWARE_PIO_INSTRUCTIONS_H +#define _HARDWARE_PIO_INSTRUCTIONS_H + +/** \brief PIO instruction encoding + * \defgroup pio_instructions pio_instructions + * \ingroup hardware_pio + * + * Functions for generating PIO instruction encodings programmatically. In debug builds + *`PARAM_ASSERTIONS_ENABLED_PIO_INSTRUCTIONS` can be set to 1 to enable validation of encoding function + * parameters. + * + * For fuller descriptions of the instructions in question see the "RP2040 Datasheet" + */ + +// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_PIO_INSTRUCTIONS, Enable/disable assertions in the PIO instructions, type=bool, default=0, group=pio_instructions +#ifndef PARAM_ASSERTIONS_ENABLED_PIO_INSTRUCTIONS +#define PARAM_ASSERTIONS_ENABLED_PIO_INSTRUCTIONS 0 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +enum pio_instr_bits { + pio_instr_bits_jmp = 0x0000, + pio_instr_bits_wait = 0x2000, + pio_instr_bits_in = 0x4000, + pio_instr_bits_out = 0x6000, + pio_instr_bits_push = 0x8000, + pio_instr_bits_pull = 0x8080, + pio_instr_bits_mov = 0xa000, + pio_instr_bits_irq = 0xc000, + pio_instr_bits_set = 0xe000, +}; + +#ifndef NDEBUG +#define _PIO_INVALID_IN_SRC 0x08u +#define _PIO_INVALID_OUT_DEST 0x10u +#define _PIO_INVALID_SET_DEST 0x20u +#define _PIO_INVALID_MOV_SRC 0x40u +#define _PIO_INVALID_MOV_DEST 0x80u +#else +#define _PIO_INVALID_IN_SRC 0u +#define _PIO_INVALID_OUT_DEST 0u +#define _PIO_INVALID_SET_DEST 0u +#define _PIO_INVALID_MOV_SRC 0u +#define _PIO_INVALID_MOV_DEST 0u +#endif + +/*! \brief Enumeration of values to pass for source/destination args for instruction encoding functions + * \ingroup pio_instructions + * + * \note Not all values are suitable for all functions. Validity is only checked in debug mode when + * `PARAM_ASSERTIONS_ENABLED_PIO_INSTRUCTIONS` is 1 + */ +enum pio_src_dest { + pio_pins = 0u, + pio_x = 1u, + pio_y = 2u, + pio_null = 3u | _PIO_INVALID_SET_DEST | _PIO_INVALID_MOV_DEST, + pio_pindirs = 4u | _PIO_INVALID_IN_SRC | _PIO_INVALID_MOV_SRC | _PIO_INVALID_MOV_DEST, + pio_exec_mov = 4u | _PIO_INVALID_IN_SRC | _PIO_INVALID_OUT_DEST | _PIO_INVALID_SET_DEST | _PIO_INVALID_MOV_SRC, + pio_status = 5u | _PIO_INVALID_IN_SRC | _PIO_INVALID_OUT_DEST | _PIO_INVALID_SET_DEST | _PIO_INVALID_MOV_DEST, + pio_pc = 5u | _PIO_INVALID_IN_SRC | _PIO_INVALID_SET_DEST | _PIO_INVALID_MOV_SRC, + pio_isr = 6u | _PIO_INVALID_SET_DEST, + pio_osr = 7u | _PIO_INVALID_OUT_DEST | _PIO_INVALID_SET_DEST, + pio_exec_out = 7u | _PIO_INVALID_IN_SRC | _PIO_INVALID_SET_DEST | _PIO_INVALID_MOV_SRC | _PIO_INVALID_MOV_DEST, +}; + +static inline uint _pio_major_instr_bits(uint instr) { + return instr & 0xe000u; +} + +static inline uint _pio_encode_instr_and_args(enum pio_instr_bits instr_bits, uint arg1, uint arg2) { + valid_params_if(PIO_INSTRUCTIONS, arg1 <= 0x7); +#if PARAM_ASSERTIONS_ENABLED(PIO_INSTRUCTIONS) + uint32_t major = _pio_major_instr_bits(instr_bits); + if (major == pio_instr_bits_in || major == pio_instr_bits_out) { + assert(arg2 && arg2 <= 32); + } else { + assert(arg2 <= 31); + } +#endif + return instr_bits | (arg1 << 5u) | (arg2 & 0x1fu); +} + +static inline uint _pio_encode_instr_and_src_dest(enum pio_instr_bits instr_bits, enum pio_src_dest dest, uint value) { + return _pio_encode_instr_and_args(instr_bits, dest & 7u, value); +} + +/*! \brief Encode just the delay slot bits of an instruction + * \ingroup pio_instructions + * + * \note This function does not return a valid instruction encoding; instead it returns an encoding of the delay + * slot suitable for `OR`ing with the result of an encoding function for an actual instruction. Care should be taken when + * combining the results of this function with the results of \ref pio_encode_sideset and \ref pio_encode_sideset_opt + * as they share the same bits within the instruction encoding. + * + * \param cycles the number of cycles 0-31 (or less if side set is being used) + * \return the delay slot bits to be ORed with an instruction encoding + */ +static inline uint pio_encode_delay(uint cycles) { + // note that the maximum cycles will be smaller if sideset_bit_count > 0 + valid_params_if(PIO_INSTRUCTIONS, cycles <= 0x1f); + return cycles << 8u; +} + +/*! \brief Encode just the side set bits of an instruction (in non optional side set mode) + * \ingroup pio_instructions + * + * \note This function does not return a valid instruction encoding; instead it returns an encoding of the side set bits + * suitable for `OR`ing with the result of an encoding function for an actual instruction. Care should be taken when + * combining the results of this function with the results of \ref pio_encode_delay as they share the same bits + * within the instruction encoding. + * + * \param sideset_bit_count number of side set bits as would be specified via `.sideset` in pioasm + * \param value the value to sideset on the pins + * \return the side set bits to be ORed with an instruction encoding + */ +static inline uint pio_encode_sideset(uint sideset_bit_count, uint value) { + valid_params_if(PIO_INSTRUCTIONS, sideset_bit_count >= 1 && sideset_bit_count <= 5); + valid_params_if(PIO_INSTRUCTIONS, value <= ((1u << sideset_bit_count) - 1)); + return value << (13u - sideset_bit_count); +} + +/*! \brief Encode just the side set bits of an instruction (in optional -`opt` side set mode) + * \ingroup pio_instructions + * + * \note This function does not return a valid instruction encoding; instead it returns an encoding of the side set bits + * suitable for `OR`ing with the result of an encoding function for an actual instruction. Care should be taken when + * combining the results of this function with the results of \ref pio_encode_delay as they share the same bits + * within the instruction encoding. + * + * \param sideset_bit_count number of side set bits as would be specified via `.sideset <n> opt` in pioasm + * \param value the value to sideset on the pins + * \return the side set bits to be ORed with an instruction encoding + */ +static inline uint pio_encode_sideset_opt(uint sideset_bit_count, uint value) { + valid_params_if(PIO_INSTRUCTIONS, sideset_bit_count >= 1 && sideset_bit_count <= 4); + valid_params_if(PIO_INSTRUCTIONS, value <= ((1u << sideset_bit_count) - 1)); + return 0x1000u | value << (12u - sideset_bit_count); +} + +/*! \brief Encode an unconditional JMP instruction + * \ingroup pio_instructions + * + * This is the equivalent of `JMP <addr>` + * + * \param addr The target address 0-31 (an absolute address within the PIO instruction memory) + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline uint pio_encode_jmp(uint addr) { + return _pio_encode_instr_and_args(pio_instr_bits_jmp, 0, addr); +} + +/*! \brief Encode a conditional JMP if scratch X zero instruction + * \ingroup pio_instructions + * + * This is the equivalent of `JMP !X <addr>` + * + * \param addr The target address 0-31 (an absolute address within the PIO instruction memory) + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline uint pio_encode_jmp_not_x(uint addr) { + return _pio_encode_instr_and_args(pio_instr_bits_jmp, 1, addr); +} + +/*! \brief Encode a conditional JMP if scratch X non-zero (and post-decrement X) instruction + * \ingroup pio_instructions + * + * This is the equivalent of `JMP X-- <addr>` + * + * \param addr The target address 0-31 (an absolute address within the PIO instruction memory) + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline uint pio_encode_jmp_x_dec(uint addr) { + return _pio_encode_instr_and_args(pio_instr_bits_jmp, 2, addr); +} + +/*! \brief Encode a conditional JMP if scratch Y zero instruction + * \ingroup pio_instructions + * + * This is the equivalent of `JMP !Y <addr>` + * + * \param addr The target address 0-31 (an absolute address within the PIO instruction memory) + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline uint pio_encode_jmp_not_y(uint addr) { + return _pio_encode_instr_and_args(pio_instr_bits_jmp, 3, addr); +} + +/*! \brief Encode a conditional JMP if scratch Y non-zero (and post-decrement Y) instruction + * \ingroup pio_instructions + * + * This is the equivalent of `JMP Y-- <addr>` + * + * \param addr The target address 0-31 (an absolute address within the PIO instruction memory) + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline uint pio_encode_jmp_y_dec(uint addr) { + return _pio_encode_instr_and_args(pio_instr_bits_jmp, 4, addr); +} + +/*! \brief Encode a conditional JMP if scratch X not equal scratch Y instruction + * \ingroup pio_instructions + * + * This is the equivalent of `JMP X!=Y <addr>` + * + * \param addr The target address 0-31 (an absolute address within the PIO instruction memory) + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline uint pio_encode_jmp_x_ne_y(uint addr) { + return _pio_encode_instr_and_args(pio_instr_bits_jmp, 5, addr); +} + +/*! \brief Encode a conditional JMP if input pin high instruction + * \ingroup pio_instructions + * + * This is the equivalent of `JMP PIN <addr>` + * + * \param addr The target address 0-31 (an absolute address within the PIO instruction memory) + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline uint pio_encode_jmp_pin(uint addr) { + return _pio_encode_instr_and_args(pio_instr_bits_jmp, 6, addr); +} + +/*! \brief Encode a conditional JMP if output shift register not empty instruction + * \ingroup pio_instructions + * + * This is the equivalent of `JMP !OSRE <addr>` + * + * \param addr The target address 0-31 (an absolute address within the PIO instruction memory) + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline uint pio_encode_jmp_not_osre(uint addr) { + return _pio_encode_instr_and_args(pio_instr_bits_jmp, 7, addr); +} + +static inline uint _pio_encode_irq(bool relative, uint irq) { + valid_params_if(PIO_INSTRUCTIONS, irq <= 7); + return (relative ? 0x10u : 0x0u) | irq; +} + +/*! \brief Encode a WAIT for GPIO pin instruction + * \ingroup pio_instructions + * + * This is the equivalent of `WAIT <polarity> GPIO <gpio>` + * + * \param polarity true for `WAIT 1`, false for `WAIT 0` + * \param gpio The real GPIO number 0-31 + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline uint pio_encode_wait_gpio(bool polarity, uint gpio) { + return _pio_encode_instr_and_args(pio_instr_bits_wait, 0u | (polarity ? 4u : 0u), gpio); +} + +/*! \brief Encode a WAIT for pin instruction + * \ingroup pio_instructions + * + * This is the equivalent of `WAIT <polarity> PIN <pin>` + * + * \param polarity true for `WAIT 1`, false for `WAIT 0` + * \param pin The pin number 0-31 relative to the executing SM's input pin mapping + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline uint pio_encode_wait_pin(bool polarity, uint pin) { + return _pio_encode_instr_and_args(pio_instr_bits_wait, 1u | (polarity ? 4u : 0u), pin); +} + +/*! \brief Encode a WAIT for IRQ instruction + * \ingroup pio_instructions + * + * This is the equivalent of `WAIT <polarity> IRQ <irq> <relative>` + * + * \param polarity true for `WAIT 1`, false for `WAIT 0` + * \param relative true for a `WAIT IRQ <irq> REL`, false for regular `WAIT IRQ <irq>` + * \param irq the irq number 0-7 + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline uint pio_encode_wait_irq(bool polarity, bool relative, uint irq) { + valid_params_if(PIO_INSTRUCTIONS, irq <= 7); + return _pio_encode_instr_and_args(pio_instr_bits_wait, 2u | (polarity ? 4u : 0u), _pio_encode_irq(relative, irq)); +} + +/*! \brief Encode an IN instruction + * \ingroup pio_instructions + * + * This is the equivalent of `IN <src>, <count>` + * + * \param src The source to take data from + * \param count The number of bits 1-32 + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline uint pio_encode_in(enum pio_src_dest src, uint count) { + valid_params_if(PIO_INSTRUCTIONS, !(src & _PIO_INVALID_IN_SRC)); + return _pio_encode_instr_and_src_dest(pio_instr_bits_in, src, count); +} + +/*! \brief Encode an OUT instruction + * \ingroup pio_instructions + * + * This is the equivalent of `OUT <src>, <count>` + * + * \param dest The destination to write data to + * \param count The number of bits 1-32 + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline uint pio_encode_out(enum pio_src_dest dest, uint count) { + valid_params_if(PIO_INSTRUCTIONS, !(dest & _PIO_INVALID_OUT_DEST)); + return _pio_encode_instr_and_src_dest(pio_instr_bits_out, dest, count); +} + +/*! \brief Encode a PUSH instruction + * \ingroup pio_instructions + * + * This is the equivalent of `PUSH <if_full>, <block>` + * + * \param if_full true for `PUSH IF_FULL ...`, false for `PUSH ...` + * \param block true for `PUSH ... BLOCK`, false for `PUSH ...` + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline uint pio_encode_push(bool if_full, bool block) { + return _pio_encode_instr_and_args(pio_instr_bits_push, (if_full ? 2u : 0u) | (block ? 1u : 0u), 0); +} + +/*! \brief Encode a PULL instruction + * \ingroup pio_instructions + * + * This is the equivalent of `PULL <if_empty>, <block>` + * + * \param if_empty true for `PULL IF_EMPTY ...`, false for `PULL ...` + * \param block true for `PULL ... BLOCK`, false for `PULL ...` + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline uint pio_encode_pull(bool if_empty, bool block) { + return _pio_encode_instr_and_args(pio_instr_bits_pull, (if_empty ? 2u : 0u) | (block ? 1u : 0u), 0); +} + +/*! \brief Encode a MOV instruction + * \ingroup pio_instructions + * + * This is the equivalent of `MOV <dest>, <src>` + * + * \param dest The destination to write data to + * \param src The source to take data from + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline uint pio_encode_mov(enum pio_src_dest dest, enum pio_src_dest src) { + valid_params_if(PIO_INSTRUCTIONS, !(dest & _PIO_INVALID_MOV_DEST)); + valid_params_if(PIO_INSTRUCTIONS, !(src & _PIO_INVALID_MOV_SRC)); + return _pio_encode_instr_and_src_dest(pio_instr_bits_mov, dest, src & 7u); +} + +/*! \brief Encode a MOV instruction with bit invert + * \ingroup pio_instructions + * + * This is the equivalent of `MOV <dest>, ~<src>` + * + * \param dest The destination to write inverted data to + * \param src The source to take data from + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline uint pio_encode_mov_not(enum pio_src_dest dest, enum pio_src_dest src) { + valid_params_if(PIO_INSTRUCTIONS, !(dest & _PIO_INVALID_MOV_DEST)); + valid_params_if(PIO_INSTRUCTIONS, !(src & _PIO_INVALID_MOV_SRC)); + return _pio_encode_instr_and_src_dest(pio_instr_bits_mov, dest, (1u << 3u) | (src & 7u)); +} + +/*! \brief Encode a MOV instruction with bit reverse + * \ingroup pio_instructions + * + * This is the equivalent of `MOV <dest>, ::<src>` + * + * \param dest The destination to write bit reversed data to + * \param src The source to take data from + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline uint pio_encode_mov_reverse(enum pio_src_dest dest, enum pio_src_dest src) { + valid_params_if(PIO_INSTRUCTIONS, !(dest & _PIO_INVALID_MOV_DEST)); + valid_params_if(PIO_INSTRUCTIONS, !(src & _PIO_INVALID_MOV_SRC)); + return _pio_encode_instr_and_src_dest(pio_instr_bits_mov, dest, (2u << 3u) | (src & 7u)); +} + +/*! \brief Encode a IRQ SET instruction + * \ingroup pio_instructions + * + * This is the equivalent of `IRQ SET <irq> <relative>` + * + * \param relative true for a `IRQ SET <irq> REL`, false for regular `IRQ SET <irq>` + * \param irq the irq number 0-7 + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline uint pio_encode_irq_set(bool relative, uint irq) { + return _pio_encode_instr_and_args(pio_instr_bits_irq, 0, _pio_encode_irq(relative, irq)); +} + +/*! \brief Encode a IRQ WAIT instruction + * \ingroup pio_instructions + * + * This is the equivalent of `IRQ WAIT <irq> <relative>` + * + * \param relative true for a `IRQ WAIT <irq> REL`, false for regular `IRQ WAIT <irq>` + * \param irq the irq number 0-7 + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline uint pio_encode_irq_wait(bool relative, uint irq) { + return _pio_encode_instr_and_args(pio_instr_bits_irq, 1, _pio_encode_irq(relative, irq)); +} + +/*! \brief Encode a IRQ CLEAR instruction + * \ingroup pio_instructions + * + * This is the equivalent of `IRQ CLEAR <irq> <relative>` + * + * \param relative true for a `IRQ CLEAR <irq> REL`, false for regular `IRQ CLEAR <irq>` + * \param irq the irq number 0-7 + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline uint pio_encode_irq_clear(bool relative, uint irq) { + return _pio_encode_instr_and_args(pio_instr_bits_irq, 2, _pio_encode_irq(relative, irq)); +} + +/*! \brief Encode a SET instruction + * \ingroup pio_instructions + * + * This is the equivalent of `SET <dest>, <value>` + * + * \param dest The destination to apply the value to + * \param value The value 0-31 + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline uint pio_encode_set(enum pio_src_dest dest, uint value) { + valid_params_if(PIO_INSTRUCTIONS, !(dest & _PIO_INVALID_SET_DEST)); + return _pio_encode_instr_and_src_dest(pio_instr_bits_set, dest, value); +} + +/*! \brief Encode a NOP instruction + * \ingroup pio_instructions + * + * This is the equivalent of `NOP` which is itself encoded as `MOV y, y` + * + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline uint pio_encode_nop(void) { + return pio_encode_mov(pio_y, pio_y); +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/linux/pio_rp1.h b/include/linux/pio_rp1.h new file mode 100644 index 00000000000000..f262fdd9c8f1c3 --- /dev/null +++ b/include/linux/pio_rp1.h @@ -0,0 +1,1019 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2024 Raspberry Pi Ltd. + * All rights reserved. + */ + +#ifndef _PIO_RP1_H +#define _PIO_RP1_H + +#include <uapi/misc/rp1_pio_if.h> + +#define PARAM_WARNINGS_ENABLED 1 + +#ifdef DEBUG +#define PARAM_WARNINGS_ENABLED 1 +#endif + +#ifndef PARAM_WARNINGS_ENABLED +#define PARAM_WARNINGS_ENABLED 0 +#endif + +#define bad_params_if(client, test) \ + ({ bool f = (test); if (f && client) pio_set_error(client, -EINVAL); \ + if (f && PARAM_WARNINGS_ENABLED) WARN_ON((test)); \ + f; }) + +#ifndef PARAM_ASSERTIONS_ENABLE_ALL +#define PARAM_ASSERTIONS_ENABLE_ALL 0 +#endif + +#ifndef PARAM_ASSERTIONS_DISABLE_ALL +#define PARAM_ASSERTIONS_DISABLE_ALL 0 +#endif + +#define PARAM_ASSERTIONS_ENABLED(x) \ + ((PARAM_ASSERTIONS_ENABLED_ ## x || PARAM_ASSERTIONS_ENABLE_ALL) && \ + !PARAM_ASSERTIONS_DISABLE_ALL) +#define valid_params_if(x, test) ({if (PARAM_ASSERTIONS_ENABLED(x)) WARN_ON(test); }) + +#include <linux/pio_instructions.h> + +#define NUM_PIO_STATE_MACHINES 4 +#define PIO_INSTRUCTION_COUNT 32 +#define PIO_ORIGIN_ANY ((uint)(~0)) +#define GPIOS_MASK ((1 << RP1_PIO_GPIO_COUNT) - 1) + +#define PICO_NO_HARDWARE 0 + +#define pio0 pio_open_helper(0) + +#define PROC_PIO_SM0_PINCTRL_OUT_BASE_BITS 0x0000001f +#define PROC_PIO_SM0_PINCTRL_OUT_BASE_LSB 0 +#define PROC_PIO_SM0_PINCTRL_OUT_COUNT_BITS 0x03f00000 +#define PROC_PIO_SM0_PINCTRL_OUT_COUNT_LSB 20 +#define PROC_PIO_SM0_PINCTRL_SET_BASE_BITS 0x000003e0 +#define PROC_PIO_SM0_PINCTRL_SET_BASE_LSB 5 +#define PROC_PIO_SM0_PINCTRL_SET_COUNT_BITS 0x1c000000 +#define PROC_PIO_SM0_PINCTRL_SET_COUNT_LSB 26 +#define PROC_PIO_SM0_PINCTRL_IN_BASE_BITS 0x000f8000 +#define PROC_PIO_SM0_PINCTRL_IN_BASE_LSB 15 +#define PROC_PIO_SM0_PINCTRL_SIDESET_BASE_BITS 0x00007c00 +#define PROC_PIO_SM0_PINCTRL_SIDESET_BASE_LSB 10 +#define PROC_PIO_SM0_PINCTRL_SIDESET_COUNT_BITS 0xe0000000 +#define PROC_PIO_SM0_PINCTRL_SIDESET_COUNT_LSB 29 +#define PROC_PIO_SM0_EXECCTRL_SIDE_EN_BITS 0x40000000 +#define PROC_PIO_SM0_EXECCTRL_SIDE_EN_LSB 30 +#define PROC_PIO_SM0_EXECCTRL_SIDE_PINDIR_BITS 0x20000000 +#define PROC_PIO_SM0_EXECCTRL_SIDE_PINDIR_LSB 29 +#define PROC_PIO_SM0_CLKDIV_INT_LSB 16 +#define PROC_PIO_SM0_CLKDIV_FRAC_LSB 8 +#define PROC_PIO_SM0_EXECCTRL_WRAP_TOP_BITS 0x0001f000 +#define PROC_PIO_SM0_EXECCTRL_WRAP_TOP_LSB 12 +#define PROC_PIO_SM0_EXECCTRL_WRAP_BOTTOM_BITS 0x00000f80 +#define PROC_PIO_SM0_EXECCTRL_WRAP_BOTTOM_LSB 7 +#define PROC_PIO_SM0_EXECCTRL_JMP_PIN_BITS 0x1f000000 +#define PROC_PIO_SM0_EXECCTRL_JMP_PIN_LSB 24 +#define PROC_PIO_SM0_SHIFTCTRL_IN_SHIFTDIR_BITS 0x00040000 +#define PROC_PIO_SM0_SHIFTCTRL_IN_SHIFTDIR_LSB 18 +#define PROC_PIO_SM0_SHIFTCTRL_AUTOPULL_BITS 0x00020000 +#define PROC_PIO_SM0_SHIFTCTRL_AUTOPULL_LSB 17 +#define PROC_PIO_SM0_SHIFTCTRL_AUTOPUSH_BITS 0x00010000 +#define PROC_PIO_SM0_SHIFTCTRL_AUTOPUSH_LSB 16 +#define PROC_PIO_SM0_SHIFTCTRL_PUSH_THRESH_BITS 0x01f00000 +#define PROC_PIO_SM0_SHIFTCTRL_PUSH_THRESH_LSB 20 +#define PROC_PIO_SM0_SHIFTCTRL_OUT_SHIFTDIR_BITS 0x00080000 +#define PROC_PIO_SM0_SHIFTCTRL_OUT_SHIFTDIR_LSB 19 +#define PROC_PIO_SM0_SHIFTCTRL_PULL_THRESH_BITS 0x3e000000 +#define PROC_PIO_SM0_SHIFTCTRL_PULL_THRESH_LSB 25 +#define PROC_PIO_SM0_SHIFTCTRL_FJOIN_TX_BITS 0x40000000 +#define PROC_PIO_SM0_SHIFTCTRL_FJOIN_TX_LSB 30 +#define PROC_PIO_SM0_SHIFTCTRL_FJOIN_RX_BITS 0x80000000 +#define PROC_PIO_SM0_SHIFTCTRL_FJOIN_RX_LSB 31 +#define PROC_PIO_SM0_EXECCTRL_OUT_STICKY_BITS 0x00020000 +#define PROC_PIO_SM0_EXECCTRL_OUT_STICKY_LSB 17 +#define PROC_PIO_SM0_EXECCTRL_INLINE_OUT_EN_BITS 0x00040000 +#define PROC_PIO_SM0_EXECCTRL_INLINE_OUT_EN_LSB 18 +#define PROC_PIO_SM0_EXECCTRL_OUT_EN_SEL_BITS 0x00f80000 +#define PROC_PIO_SM0_EXECCTRL_OUT_EN_SEL_LSB 19 +#define PROC_PIO_SM0_EXECCTRL_STATUS_SEL_BITS 0x00000020 +#define PROC_PIO_SM0_EXECCTRL_STATUS_SEL_LSB 5 +#define PROC_PIO_SM0_EXECCTRL_STATUS_N_BITS 0x0000001f +#define PROC_PIO_SM0_EXECCTRL_STATUS_N_LSB 0 + +enum pio_fifo_join { + PIO_FIFO_JOIN_NONE = 0, + PIO_FIFO_JOIN_TX = 1, + PIO_FIFO_JOIN_RX = 2, +}; + +enum pio_mov_status_type { + STATUS_TX_LESSTHAN = 0, + STATUS_RX_LESSTHAN = 1 +}; + +enum pio_xfer_dir { + PIO_DIR_TO_SM, + PIO_DIR_FROM_SM, + PIO_DIR_COUNT +}; + +enum clock_index { + clk_sys = 5 +}; + +typedef struct pio_program { + const uint16_t *instructions; + uint8_t length; + int8_t origin; // required instruction memory origin or -1 +} pio_program_t; + +enum gpio_function { + GPIO_FUNC_FSEL0 = 0, + GPIO_FUNC_FSEL1 = 1, + GPIO_FUNC_FSEL2 = 2, + GPIO_FUNC_FSEL3 = 3, + GPIO_FUNC_FSEL4 = 4, + GPIO_FUNC_FSEL5 = 5, + GPIO_FUNC_FSEL6 = 6, + GPIO_FUNC_FSEL7 = 7, + GPIO_FUNC_FSEL8 = 8, + GPIO_FUNC_NULL = 0x1f, + + // Name a few + GPIO_FUNC_SYS_RIO = 5, + GPIO_FUNC_PROC_RIO = 6, + GPIO_FUNC_PIO = 7, +}; + +enum gpio_irq_level { + GPIO_IRQ_LEVEL_LOW = 0x1u, + GPIO_IRQ_LEVEL_HIGH = 0x2u, + GPIO_IRQ_EDGE_FALL = 0x4u, + GPIO_IRQ_EDGE_RISE = 0x8u, +}; + +enum gpio_override { + GPIO_OVERRIDE_NORMAL = 0, + GPIO_OVERRIDE_INVERT = 1, + GPIO_OVERRIDE_LOW = 2, + GPIO_OVERRIDE_HIGH = 3, +}; +enum gpio_slew_rate { + GPIO_SLEW_RATE_SLOW = 0, + GPIO_SLEW_RATE_FAST = 1 +}; + +enum gpio_drive_strength { + GPIO_DRIVE_STRENGTH_2MA = 0, + GPIO_DRIVE_STRENGTH_4MA = 1, + GPIO_DRIVE_STRENGTH_8MA = 2, + GPIO_DRIVE_STRENGTH_12MA = 3 +}; + +struct fp24_8 { + uint32_t val; +}; + +typedef rp1_pio_sm_config pio_sm_config; + +typedef struct rp1_pio_client *PIO; + +int rp1_pio_init(void); +PIO rp1_pio_open(void); +void rp1_pio_close(struct rp1_pio_client *client); +void rp1_pio_set_error(struct rp1_pio_client *client, int err); +int rp1_pio_get_error(const struct rp1_pio_client *client); +void rp1_pio_clear_error(struct rp1_pio_client *client); +int rp1_pio_sm_config_xfer(struct rp1_pio_client *client, uint sm, uint dir, + uint buf_size, uint buf_count); +int rp1_pio_sm_xfer_data(struct rp1_pio_client *client, uint sm, uint dir, + uint data_bytes, void *data, dma_addr_t dma_addr, + void (*callback)(void *param), void *param); + +int rp1_pio_can_add_program(struct rp1_pio_client *client, void *param); +int rp1_pio_add_program(struct rp1_pio_client *client, void *param); +int rp1_pio_remove_program(struct rp1_pio_client *client, void *param); +int rp1_pio_clear_instr_mem(struct rp1_pio_client *client, void *param); +int rp1_pio_sm_claim(struct rp1_pio_client *client, void *param); +int rp1_pio_sm_unclaim(struct rp1_pio_client *client, void *param); +int rp1_pio_sm_is_claimed(struct rp1_pio_client *client, void *param); +int rp1_pio_sm_init(struct rp1_pio_client *client, void *param); +int rp1_pio_sm_set_config(struct rp1_pio_client *client, void *param); +int rp1_pio_sm_exec(struct rp1_pio_client *client, void *param); +int rp1_pio_sm_clear_fifos(struct rp1_pio_client *client, void *param); +int rp1_pio_sm_set_clkdiv(struct rp1_pio_client *client, void *param); +int rp1_pio_sm_set_pins(struct rp1_pio_client *client, void *param); +int rp1_pio_sm_set_pindirs(struct rp1_pio_client *client, void *param); +int rp1_pio_sm_set_enabled(struct rp1_pio_client *client, void *param); +int rp1_pio_sm_restart(struct rp1_pio_client *client, void *param); +int rp1_pio_sm_clkdiv_restart(struct rp1_pio_client *client, void *param); +int rp1_pio_sm_enable_sync(struct rp1_pio_client *client, void *param); +int rp1_pio_sm_put(struct rp1_pio_client *client, void *param); +int rp1_pio_sm_get(struct rp1_pio_client *client, void *param); +int rp1_pio_sm_set_dmactrl(struct rp1_pio_client *client, void *param); +int rp1_pio_sm_fifo_state(struct rp1_pio_client *client, void *param); +int rp1_pio_sm_drain_tx(struct rp1_pio_client *client, void *param); +int rp1_pio_gpio_init(struct rp1_pio_client *client, void *param); +int rp1_pio_gpio_set_function(struct rp1_pio_client *client, void *param); +int rp1_pio_gpio_set_pulls(struct rp1_pio_client *client, void *param); +int rp1_pio_gpio_set_outover(struct rp1_pio_client *client, void *param); +int rp1_pio_gpio_set_inover(struct rp1_pio_client *client, void *param); +int rp1_pio_gpio_set_oeover(struct rp1_pio_client *client, void *param); +int rp1_pio_gpio_set_input_enabled(struct rp1_pio_client *client, void *param); +int rp1_pio_gpio_set_drive_strength(struct rp1_pio_client *client, void *param); + +static inline int pio_init(void) +{ + return rp1_pio_init(); +} + +static inline struct rp1_pio_client *pio_open(void) +{ + return rp1_pio_open(); +} + +static inline void pio_close(struct rp1_pio_client *client) +{ + rp1_pio_close(client); +} + +static inline void pio_set_error(struct rp1_pio_client *client, int err) +{ + rp1_pio_set_error(client, err); +} + +static inline int pio_get_error(const struct rp1_pio_client *client) +{ + return rp1_pio_get_error(client); +} + +static inline void pio_clear_error(struct rp1_pio_client *client) +{ + rp1_pio_clear_error(client); +} + +static inline int pio_sm_config_xfer(struct rp1_pio_client *client, uint sm, uint dir, + uint buf_size, uint buf_count) +{ + return rp1_pio_sm_config_xfer(client, sm, dir, buf_size, buf_count); +} + +static inline int pio_sm_xfer_data(struct rp1_pio_client *client, uint sm, uint dir, + uint data_bytes, void *data, dma_addr_t dma_addr, + void (*callback)(void *param), void *param) +{ + return rp1_pio_sm_xfer_data(client, sm, dir, data_bytes, data, dma_addr, callback, param); +} + +static inline struct fp24_8 make_fp24_8(uint mul, uint div) +{ + struct fp24_8 res = { .val = ((unsigned long long)mul << 8) / div }; + + return res; +} + +static inline bool pio_can_add_program(struct rp1_pio_client *client, + const pio_program_t *program) +{ + struct rp1_pio_add_program_args args; + + if (bad_params_if(client, program->length > PIO_INSTRUCTION_COUNT)) + return false; + args.origin = (program->origin == -1) ? PIO_ORIGIN_ANY : program->origin; + args.num_instrs = program->length; + + memcpy(args.instrs, program->instructions, args.num_instrs * sizeof(args.instrs[0])); + return rp1_pio_can_add_program(client, &args); +} + +static inline bool pio_can_add_program_at_offset(struct rp1_pio_client *client, + const pio_program_t *program, uint offset) +{ + struct rp1_pio_add_program_args args; + + if (bad_params_if(client, program->length > PIO_INSTRUCTION_COUNT || + offset >= PIO_INSTRUCTION_COUNT)) + return false; + args.origin = offset; + args.num_instrs = program->length; + + memcpy(args.instrs, program->instructions, args.num_instrs * sizeof(args.instrs[0])); + return !rp1_pio_can_add_program(client, &args); +} + +static inline uint pio_add_program(struct rp1_pio_client *client, const pio_program_t *program) +{ + struct rp1_pio_add_program_args args; + int offset; + + if (bad_params_if(client, program->length > PIO_INSTRUCTION_COUNT)) + return PIO_ORIGIN_ANY; + args.origin = (program->origin == -1) ? PIO_ORIGIN_ANY : program->origin; + args.num_instrs = program->length; + + memcpy(args.instrs, program->instructions, args.num_instrs * sizeof(args.instrs[0])); + offset = rp1_pio_add_program(client, &args); + return (offset >= 0) ? offset : PIO_ORIGIN_ANY; +} + +static inline int pio_add_program_at_offset(struct rp1_pio_client *client, + const pio_program_t *program, uint offset) +{ + struct rp1_pio_add_program_args args; + + if (bad_params_if(client, program->length > PIO_INSTRUCTION_COUNT || + offset >= PIO_INSTRUCTION_COUNT)) + return -EINVAL; + args.origin = offset; + args.num_instrs = program->length; + + memcpy(args.instrs, program->instructions, args.num_instrs * sizeof(args.instrs[0])); + return rp1_pio_add_program(client, &args); +} + +static inline int pio_remove_program(struct rp1_pio_client *client, const pio_program_t *program, + uint loaded_offset) +{ + struct rp1_pio_remove_program_args args; + + args.origin = loaded_offset; + args.num_instrs = program->length; + + return rp1_pio_remove_program(client, &args); +} + +static inline int pio_clear_instruction_memory(struct rp1_pio_client *client) +{ + return rp1_pio_clear_instr_mem(client, NULL); +} + +static inline int pio_sm_claim(struct rp1_pio_client *client, uint sm) +{ + struct rp1_pio_sm_claim_args args = { .mask = 1 << sm }; + + if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES)) + return -EINVAL; + + return rp1_pio_sm_claim(client, &args); +} + +static inline int pio_claim_sm_mask(struct rp1_pio_client *client, uint mask) +{ + struct rp1_pio_sm_claim_args args = { .mask = mask }; + + if (bad_params_if(client, mask >= (1 << NUM_PIO_STATE_MACHINES))) + return -EINVAL; + + return rp1_pio_sm_claim(client, &args); +} + +static inline int pio_sm_unclaim(struct rp1_pio_client *client, uint sm) +{ + struct rp1_pio_sm_claim_args args = { .mask = 1 << sm }; + + if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES)) + return -EINVAL; + + return rp1_pio_sm_unclaim(client, &args); +} + +static inline int pio_claim_unused_sm(struct rp1_pio_client *client, bool required) +{ + struct rp1_pio_sm_claim_args args = { .mask = 0 }; + int sm; + + sm = rp1_pio_sm_claim(client, &args); + if (sm < 0 && required) + WARN_ON("No PIO state machines are available"); + return sm; +} + +static inline bool pio_sm_is_claimed(struct rp1_pio_client *client, uint sm) +{ + struct rp1_pio_sm_claim_args args = { .mask = (1 << sm) }; + + if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES)) + return true; + return rp1_pio_sm_is_claimed(client, &args); +} + +static inline int pio_sm_init(struct rp1_pio_client *client, uint sm, uint initial_pc, + const pio_sm_config *config) +{ + struct rp1_pio_sm_init_args args = { .sm = sm, .initial_pc = initial_pc, + .config = *config }; + + if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES || + initial_pc >= PIO_INSTRUCTION_COUNT)) + return -EINVAL; + + return rp1_pio_sm_init(client, &args); +} + +static inline int pio_sm_set_config(struct rp1_pio_client *client, uint sm, + const pio_sm_config *config) +{ + struct rp1_pio_sm_init_args args = { .sm = sm, .config = *config }; + + if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES)) + return -EINVAL; + + return rp1_pio_sm_set_config(client, &args); +} + +static inline int pio_sm_exec(struct rp1_pio_client *client, uint sm, uint instr) +{ + struct rp1_pio_sm_exec_args args = { .sm = sm, .instr = instr, .blocking = false }; + + if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES || instr > (uint16_t)~0)) + return -EINVAL; + + return rp1_pio_sm_exec(client, &args); +} + +static inline int pio_sm_exec_wait_blocking(struct rp1_pio_client *client, uint sm, uint instr) +{ + struct rp1_pio_sm_exec_args args = { .sm = sm, .instr = instr, .blocking = true }; + + if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES || instr > (uint16_t)~0)) + return -EINVAL; + + return rp1_pio_sm_exec(client, &args); +} + +static inline int pio_sm_clear_fifos(struct rp1_pio_client *client, uint sm) +{ + struct rp1_pio_sm_clear_fifos_args args = { .sm = sm }; + + if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES)) + return -EINVAL; + return rp1_pio_sm_clear_fifos(client, &args); +} + +static inline bool pio_calculate_clkdiv_from_fp24_8(struct fp24_8 div, uint16_t *div_int, + uint8_t *div_frac) +{ + uint inum = (div.val >> 8); + + if (bad_params_if(NULL, inum < 1 || inum > 65536)) + return false; + *div_int = (uint16_t)inum; + if (*div_int == 0) + *div_frac = 0; + else + *div_frac = div.val & 0xff; + return true; +} + +static inline int pio_sm_set_clkdiv_int_frac(struct rp1_pio_client *client, uint sm, + uint16_t div_int, uint8_t div_frac) +{ + struct rp1_pio_sm_set_clkdiv_args args = { .sm = sm, .div_int = div_int, + .div_frac = div_frac }; + + if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES || + (div_int == 0 && div_frac != 0))) + return -EINVAL; + return rp1_pio_sm_set_clkdiv(client, &args); +} + +static inline int pio_sm_set_clkdiv(struct rp1_pio_client *client, uint sm, struct fp24_8 div) +{ + struct rp1_pio_sm_set_clkdiv_args args = { .sm = sm }; + + if (!pio_calculate_clkdiv_from_fp24_8(div, &args.div_int, &args.div_frac)) + return -EINVAL; + return rp1_pio_sm_set_clkdiv(client, &args); +} + +static inline int pio_sm_set_pins(struct rp1_pio_client *client, uint sm, uint32_t pin_values) +{ + struct rp1_pio_sm_set_pins_args args = { .sm = sm, .values = pin_values, + .mask = GPIOS_MASK }; + + if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES)) + return -EINVAL; + return rp1_pio_sm_set_pins(client, &args); +} + +static inline int pio_sm_set_pins_with_mask(struct rp1_pio_client *client, uint sm, + uint32_t pin_values, uint32_t pin_mask) +{ + struct rp1_pio_sm_set_pins_args args = { .sm = sm, .values = pin_values, + .mask = pin_mask }; + + if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES)) + return -EINVAL; + return rp1_pio_sm_set_pins(client, &args); +} + +static inline int pio_sm_set_pindirs_with_mask(struct rp1_pio_client *client, uint sm, + uint32_t pin_dirs, uint32_t pin_mask) +{ + struct rp1_pio_sm_set_pindirs_args args = { .sm = sm, .dirs = pin_dirs, + .mask = pin_mask }; + + if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES || + (pin_dirs & GPIOS_MASK) != pin_dirs || + (pin_mask & pin_mask) != pin_mask)) + return -EINVAL; + return rp1_pio_sm_set_pindirs(client, &args); +} + +static inline int pio_sm_set_consecutive_pindirs(struct rp1_pio_client *client, uint sm, + uint pin_base, uint pin_count, bool is_out) +{ + uint32_t mask = ((1 << pin_count) - 1) << pin_base; + struct rp1_pio_sm_set_pindirs_args args = { .sm = sm, .dirs = is_out ? mask : 0, + .mask = mask }; + + if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES || + pin_base >= RP1_PIO_GPIO_COUNT || + pin_count > RP1_PIO_GPIO_COUNT || + (pin_base + pin_count) > RP1_PIO_GPIO_COUNT)) + return -EINVAL; + return rp1_pio_sm_set_pindirs(client, &args); +} + +static inline int pio_sm_set_enabled(struct rp1_pio_client *client, uint sm, bool enabled) +{ + struct rp1_pio_sm_set_enabled_args args = { .mask = (1 << sm), .enable = enabled }; + + if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES)) + return -EINVAL; + return rp1_pio_sm_set_enabled(client, &args); +} + +static inline int pio_set_sm_mask_enabled(struct rp1_pio_client *client, uint32_t mask, + bool enabled) +{ + struct rp1_pio_sm_set_enabled_args args = { .mask = mask, .enable = enabled }; + + if (bad_params_if(client, mask >= (1 << NUM_PIO_STATE_MACHINES))) + return -EINVAL; + return rp1_pio_sm_set_enabled(client, &args); +} + +static inline int pio_sm_restart(struct rp1_pio_client *client, uint sm) +{ + struct rp1_pio_sm_restart_args args = { .mask = (1 << sm) }; + + if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES)) + return -EINVAL; + return rp1_pio_sm_restart(client, &args); +} + +static inline int pio_restart_sm_mask(struct rp1_pio_client *client, uint32_t mask) +{ + struct rp1_pio_sm_restart_args args = { .mask = (uint16_t)mask }; + + if (bad_params_if(client, mask >= (1 << NUM_PIO_STATE_MACHINES))) + return -EINVAL; + return rp1_pio_sm_restart(client, &args); +} + +static inline int pio_sm_clkdiv_restart(struct rp1_pio_client *client, uint sm) +{ + struct rp1_pio_sm_restart_args args = { .mask = (1 << sm) }; + + if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES)) + return -EINVAL; + return rp1_pio_sm_clkdiv_restart(client, &args); +} + +static inline int pio_clkdiv_restart_sm_mask(struct rp1_pio_client *client, uint32_t mask) +{ + struct rp1_pio_sm_restart_args args = { .mask = (uint16_t)mask }; + + if (bad_params_if(client, mask >= (1 << NUM_PIO_STATE_MACHINES))) + return -EINVAL; + return rp1_pio_sm_clkdiv_restart(client, &args); +} + +static inline int pio_enable_sm_in_sync_mask(struct rp1_pio_client *client, uint32_t mask) +{ + struct rp1_pio_sm_enable_sync_args args = { .mask = (uint16_t)mask }; + + if (bad_params_if(client, mask >= (1 << NUM_PIO_STATE_MACHINES))) + return -EINVAL; + return rp1_pio_sm_enable_sync(client, &args); +} + +static inline int pio_sm_set_dmactrl(struct rp1_pio_client *client, uint sm, bool is_tx, + uint32_t ctrl) +{ + struct rp1_pio_sm_set_dmactrl_args args = { .sm = sm, .is_tx = is_tx, .ctrl = ctrl }; + + if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES)) + return -EINVAL; + return rp1_pio_sm_set_dmactrl(client, &args); +}; + +static inline int pio_sm_drain_tx_fifo(struct rp1_pio_client *client, uint sm) +{ + struct rp1_pio_sm_clear_fifos_args args = { .sm = sm }; + + if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES)) + return -EINVAL; + return rp1_pio_sm_drain_tx(client, &args); +}; + +static inline int pio_sm_put(struct rp1_pio_client *client, uint sm, uint32_t data) +{ + struct rp1_pio_sm_put_args args = { .sm = (uint16_t)sm, .blocking = false, .data = data }; + + if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES)) + return -EINVAL; + return rp1_pio_sm_put(client, &args); +} + +static inline int pio_sm_put_blocking(struct rp1_pio_client *client, uint sm, uint32_t data) +{ + struct rp1_pio_sm_put_args args = { .sm = (uint16_t)sm, .blocking = true, .data = data }; + + if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES)) + return -EINVAL; + return rp1_pio_sm_put(client, &args); +} + +static inline uint32_t pio_sm_get(struct rp1_pio_client *client, uint sm) +{ + struct rp1_pio_sm_get_args args = { .sm = (uint16_t)sm, .blocking = false }; + + if (!bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES)) + rp1_pio_sm_get(client, &args); + return args.data; +} + +static inline uint32_t pio_sm_get_blocking(struct rp1_pio_client *client, uint sm) +{ + struct rp1_pio_sm_get_args args = { .sm = (uint16_t)sm, .blocking = true }; + + if (!bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES)) + rp1_pio_sm_get(client, &args); + return args.data; +} + +static inline int pio_sm_is_rx_fifo_empty(struct rp1_pio_client *client, uint sm) +{ + struct rp1_pio_sm_fifo_state_args args = { .sm = sm, .tx = false }; + int ret; + + if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES)) + return -EINVAL; + ret = rp1_pio_sm_fifo_state(client, &args); + if (ret == sizeof(args)) + ret = args.empty; + return ret; +}; + +static inline int pio_sm_is_rx_fifo_full(struct rp1_pio_client *client, uint sm) +{ + struct rp1_pio_sm_fifo_state_args args = { .sm = sm, .tx = false }; + int ret; + + if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES)) + return -EINVAL; + ret = rp1_pio_sm_fifo_state(client, &args); + if (ret == sizeof(args)) + ret = args.full; + return ret; +}; + +static inline int pio_sm_rx_fifo_level(struct rp1_pio_client *client, uint sm) +{ + struct rp1_pio_sm_fifo_state_args args = { .sm = sm, .tx = false }; + int ret; + + if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES)) + return -EINVAL; + ret = rp1_pio_sm_fifo_state(client, &args); + if (ret == sizeof(args)) + ret = args.level; + return ret; +}; + +static inline int pio_sm_is_tx_fifo_empty(struct rp1_pio_client *client, uint sm) +{ + struct rp1_pio_sm_fifo_state_args args = { .sm = sm, .tx = true }; + int ret; + + if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES)) + return -EINVAL; + ret = rp1_pio_sm_fifo_state(client, &args); + if (ret == sizeof(args)) + ret = args.empty; + return ret; +}; + +static inline int pio_sm_is_tx_fifo_full(struct rp1_pio_client *client, uint sm) +{ + struct rp1_pio_sm_fifo_state_args args = { .sm = sm, .tx = true }; + int ret; + + if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES)) + return -EINVAL; + ret = rp1_pio_sm_fifo_state(client, &args); + if (ret == sizeof(args)) + ret = args.full; + return ret; +}; + +static inline int pio_sm_tx_fifo_level(struct rp1_pio_client *client, uint sm) +{ + struct rp1_pio_sm_fifo_state_args args = { .sm = sm, .tx = true }; + int ret; + + if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES)) + return -EINVAL; + ret = rp1_pio_sm_fifo_state(client, &args); + if (ret == sizeof(args)) + ret = args.level; + return ret; +}; + +static inline void sm_config_set_out_pins(pio_sm_config *c, uint out_base, uint out_count) +{ + if (bad_params_if(NULL, out_base >= RP1_PIO_GPIO_COUNT || + out_count > RP1_PIO_GPIO_COUNT)) + return; + + c->pinctrl = (c->pinctrl & ~(PROC_PIO_SM0_PINCTRL_OUT_BASE_BITS | + PROC_PIO_SM0_PINCTRL_OUT_COUNT_BITS)) | + (out_base << PROC_PIO_SM0_PINCTRL_OUT_BASE_LSB) | + (out_count << PROC_PIO_SM0_PINCTRL_OUT_COUNT_LSB); +} + +static inline void sm_config_set_set_pins(pio_sm_config *c, uint set_base, uint set_count) +{ + if (bad_params_if(NULL, set_base >= RP1_PIO_GPIO_COUNT || + set_count > 5)) + return; + + c->pinctrl = (c->pinctrl & ~(PROC_PIO_SM0_PINCTRL_SET_BASE_BITS | + PROC_PIO_SM0_PINCTRL_SET_COUNT_BITS)) | + (set_base << PROC_PIO_SM0_PINCTRL_SET_BASE_LSB) | + (set_count << PROC_PIO_SM0_PINCTRL_SET_COUNT_LSB); +} + + +static inline void sm_config_set_in_pins(pio_sm_config *c, uint in_base) +{ + if (bad_params_if(NULL, in_base >= RP1_PIO_GPIO_COUNT)) + return; + + c->pinctrl = (c->pinctrl & ~PROC_PIO_SM0_PINCTRL_IN_BASE_BITS) | + (in_base << PROC_PIO_SM0_PINCTRL_IN_BASE_LSB); +} + +static inline void sm_config_set_sideset_pins(pio_sm_config *c, uint sideset_base) +{ + if (bad_params_if(NULL, sideset_base >= RP1_PIO_GPIO_COUNT)) + return; + + c->pinctrl = (c->pinctrl & ~PROC_PIO_SM0_PINCTRL_SIDESET_BASE_BITS) | + (sideset_base << PROC_PIO_SM0_PINCTRL_SIDESET_BASE_LSB); +} + +static inline void sm_config_set_sideset(pio_sm_config *c, uint bit_count, bool optional, + bool pindirs) +{ + if (bad_params_if(NULL, bit_count > 5 || + (optional && (bit_count == 0)))) + return; + c->pinctrl = (c->pinctrl & ~PROC_PIO_SM0_PINCTRL_SIDESET_COUNT_BITS) | + (bit_count << PROC_PIO_SM0_PINCTRL_SIDESET_COUNT_LSB); + + c->execctrl = (c->execctrl & ~(PROC_PIO_SM0_EXECCTRL_SIDE_EN_BITS | + PROC_PIO_SM0_EXECCTRL_SIDE_PINDIR_BITS)) | + (optional << PROC_PIO_SM0_EXECCTRL_SIDE_EN_LSB) | + (pindirs << PROC_PIO_SM0_EXECCTRL_SIDE_PINDIR_LSB); +} + +static inline void sm_config_set_clkdiv_int_frac(pio_sm_config *c, uint16_t div_int, + uint8_t div_frac) +{ + if (bad_params_if(NULL, div_int == 0 && div_frac != 0)) + return; + + c->clkdiv = + (((uint)div_frac) << PROC_PIO_SM0_CLKDIV_FRAC_LSB) | + (((uint)div_int) << PROC_PIO_SM0_CLKDIV_INT_LSB); +} + +static inline void sm_config_set_clkdiv(pio_sm_config *c, struct fp24_8 div) +{ + uint16_t div_int; + uint8_t div_frac; + + pio_calculate_clkdiv_from_fp24_8(div, &div_int, &div_frac); + sm_config_set_clkdiv_int_frac(c, div_int, div_frac); +} + +static inline void sm_config_set_wrap(pio_sm_config *c, uint wrap_target, uint wrap) +{ + if (bad_params_if(NULL, wrap >= PIO_INSTRUCTION_COUNT || + wrap_target >= PIO_INSTRUCTION_COUNT)) + return; + + c->execctrl = (c->execctrl & ~(PROC_PIO_SM0_EXECCTRL_WRAP_TOP_BITS | + PROC_PIO_SM0_EXECCTRL_WRAP_BOTTOM_BITS)) | + (wrap_target << PROC_PIO_SM0_EXECCTRL_WRAP_BOTTOM_LSB) | + (wrap << PROC_PIO_SM0_EXECCTRL_WRAP_TOP_LSB); +} + +static inline void sm_config_set_jmp_pin(pio_sm_config *c, uint pin) +{ + if (bad_params_if(NULL, pin >= RP1_PIO_GPIO_COUNT)) + return; + + c->execctrl = (c->execctrl & ~PROC_PIO_SM0_EXECCTRL_JMP_PIN_BITS) | + (pin << PROC_PIO_SM0_EXECCTRL_JMP_PIN_LSB); +} + +static inline void sm_config_set_in_shift(pio_sm_config *c, bool shift_right, bool autopush, + uint push_threshold) +{ + if (bad_params_if(NULL, push_threshold > 32)) + return; + + c->shiftctrl = (c->shiftctrl & + ~(PROC_PIO_SM0_SHIFTCTRL_IN_SHIFTDIR_BITS | + PROC_PIO_SM0_SHIFTCTRL_AUTOPUSH_BITS | + PROC_PIO_SM0_SHIFTCTRL_PUSH_THRESH_BITS)) | + (shift_right << PROC_PIO_SM0_SHIFTCTRL_IN_SHIFTDIR_LSB) | + (autopush << PROC_PIO_SM0_SHIFTCTRL_AUTOPUSH_LSB) | + ((push_threshold & 0x1fu) << PROC_PIO_SM0_SHIFTCTRL_PUSH_THRESH_LSB); +} + +static inline void sm_config_set_out_shift(pio_sm_config *c, bool shift_right, bool autopull, + uint pull_threshold) +{ + if (bad_params_if(NULL, pull_threshold > 32)) + return; + + c->shiftctrl = (c->shiftctrl & + ~(PROC_PIO_SM0_SHIFTCTRL_OUT_SHIFTDIR_BITS | + PROC_PIO_SM0_SHIFTCTRL_AUTOPULL_BITS | + PROC_PIO_SM0_SHIFTCTRL_PULL_THRESH_BITS)) | + (shift_right << PROC_PIO_SM0_SHIFTCTRL_OUT_SHIFTDIR_LSB) | + (autopull << PROC_PIO_SM0_SHIFTCTRL_AUTOPULL_LSB) | + ((pull_threshold & 0x1fu) << PROC_PIO_SM0_SHIFTCTRL_PULL_THRESH_LSB); +} + +static inline void sm_config_set_fifo_join(pio_sm_config *c, enum pio_fifo_join join) +{ + if (bad_params_if(NULL, join != PIO_FIFO_JOIN_NONE && + join != PIO_FIFO_JOIN_TX && + join != PIO_FIFO_JOIN_RX)) + return; + + c->shiftctrl = (c->shiftctrl & (uint)~(PROC_PIO_SM0_SHIFTCTRL_FJOIN_TX_BITS | + PROC_PIO_SM0_SHIFTCTRL_FJOIN_RX_BITS)) | + (((uint)join) << PROC_PIO_SM0_SHIFTCTRL_FJOIN_TX_LSB); +} + +static inline void sm_config_set_out_special(pio_sm_config *c, bool sticky, bool has_enable_pin, + uint enable_pin_index) +{ + c->execctrl = (c->execctrl & + (uint)~(PROC_PIO_SM0_EXECCTRL_OUT_STICKY_BITS | + PROC_PIO_SM0_EXECCTRL_INLINE_OUT_EN_BITS | + PROC_PIO_SM0_EXECCTRL_OUT_EN_SEL_BITS)) | + (sticky << PROC_PIO_SM0_EXECCTRL_OUT_STICKY_LSB) | + (has_enable_pin << PROC_PIO_SM0_EXECCTRL_INLINE_OUT_EN_LSB) | + ((enable_pin_index << PROC_PIO_SM0_EXECCTRL_OUT_EN_SEL_LSB) & + PROC_PIO_SM0_EXECCTRL_OUT_EN_SEL_BITS); +} + +static inline void sm_config_set_mov_status(pio_sm_config *c, enum pio_mov_status_type status_sel, + uint status_n) +{ + if (bad_params_if(NULL, status_sel != STATUS_TX_LESSTHAN && + status_sel != STATUS_RX_LESSTHAN)) + return; + + c->execctrl = (c->execctrl + & ~(PROC_PIO_SM0_EXECCTRL_STATUS_SEL_BITS | PROC_PIO_SM0_EXECCTRL_STATUS_N_BITS)) + | ((((uint)status_sel) << PROC_PIO_SM0_EXECCTRL_STATUS_SEL_LSB) & + PROC_PIO_SM0_EXECCTRL_STATUS_SEL_BITS) + | ((status_n << PROC_PIO_SM0_EXECCTRL_STATUS_N_LSB) & + PROC_PIO_SM0_EXECCTRL_STATUS_N_BITS); +} + +static inline pio_sm_config pio_get_default_sm_config(void) +{ + pio_sm_config c = { 0 }; + + sm_config_set_clkdiv_int_frac(&c, 1, 0); + sm_config_set_wrap(&c, 0, 31); + sm_config_set_in_shift(&c, true, false, 32); + sm_config_set_out_shift(&c, true, false, 32); + return c; +} + +static inline uint32_t clock_get_hz(enum clock_index clk_index) +{ + const uint32_t MHZ = 1000000; + + if (bad_params_if(NULL, clk_index != clk_sys)) + return 0; + return 200 * MHZ; +} + +static inline int pio_gpio_set_function(struct rp1_pio_client *client, uint gpio, + enum gpio_function fn) +{ + struct rp1_gpio_set_function_args args = { .gpio = gpio, .fn = fn }; + + if (bad_params_if(client, gpio >= RP1_PIO_GPIO_COUNT)) + return -EINVAL; + return rp1_pio_gpio_set_function(client, &args); +} + +static inline int pio_gpio_init(struct rp1_pio_client *client, uint gpio) +{ + struct rp1_gpio_init_args args = { .gpio = gpio }; + int ret; + + if (bad_params_if(client, gpio >= RP1_PIO_GPIO_COUNT)) + return -EINVAL; + ret = rp1_pio_gpio_init(client, &args); + if (ret) + return ret; + return pio_gpio_set_function(client, gpio, RP1_GPIO_FUNC_PIO); +} + +static inline int pio_gpio_set_pulls(struct rp1_pio_client *client, uint gpio, bool up, bool down) +{ + struct rp1_gpio_set_pulls_args args = { .gpio = gpio, .up = up, .down = down }; + + if (bad_params_if(client, gpio >= RP1_PIO_GPIO_COUNT)) + return -EINVAL; + return rp1_pio_gpio_set_pulls(client, &args); +} + +static inline int pio_gpio_set_outover(struct rp1_pio_client *client, uint gpio, uint value) +{ + struct rp1_gpio_set_args args = { .gpio = gpio, .value = value }; + + if (bad_params_if(client, gpio >= RP1_PIO_GPIO_COUNT)) + return -EINVAL; + return rp1_pio_gpio_set_outover(client, &args); +} + +static inline int pio_gpio_set_inover(struct rp1_pio_client *client, uint gpio, uint value) +{ + struct rp1_gpio_set_args args = { .gpio = gpio, .value = value }; + + if (bad_params_if(client, gpio >= RP1_PIO_GPIO_COUNT)) + return -EINVAL; + return rp1_pio_gpio_set_inover(client, &args); +} + +static inline int pio_gpio_set_oeover(struct rp1_pio_client *client, uint gpio, uint value) +{ + struct rp1_gpio_set_args args = { .gpio = gpio, .value = value }; + + if (bad_params_if(client, gpio >= RP1_PIO_GPIO_COUNT)) + return -EINVAL; + return rp1_pio_gpio_set_oeover(client, &args); +} + +static inline int pio_gpio_set_input_enabled(struct rp1_pio_client *client, uint gpio, + bool enabled) +{ + struct rp1_gpio_set_args args = { .gpio = gpio, .value = enabled }; + + if (bad_params_if(client, gpio >= RP1_PIO_GPIO_COUNT)) + return -EINVAL; + return rp1_pio_gpio_set_input_enabled(client, &args); +} + +static inline int pio_gpio_set_drive_strength(struct rp1_pio_client *client, uint gpio, + enum gpio_drive_strength drive) +{ + struct rp1_gpio_set_args args = { .gpio = gpio, .value = drive }; + + if (bad_params_if(client, gpio >= RP1_PIO_GPIO_COUNT)) + return -EINVAL; + return rp1_pio_gpio_set_drive_strength(client, &args); +} + +static inline int pio_gpio_pull_up(struct rp1_pio_client *client, uint gpio) +{ + return pio_gpio_set_pulls(client, gpio, true, false); +} + +static inline int pio_gpio_pull_down(struct rp1_pio_client *client, uint gpio) +{ + return pio_gpio_set_pulls(client, gpio, false, true); +} + +static inline int pio_gpio_disable_pulls(struct rp1_pio_client *client, uint gpio) +{ + return pio_gpio_set_pulls(client, gpio, false, false); +} + +#endif diff --git a/include/linux/platform_data/dma-bcm2708.h b/include/linux/platform_data/dma-bcm2708.h new file mode 100644 index 00000000000000..6ca874d332a8bc --- /dev/null +++ b/include/linux/platform_data/dma-bcm2708.h @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2010 Broadcom + * + * 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. + */ + +#ifndef _PLAT_BCM2708_DMA_H +#define _PLAT_BCM2708_DMA_H + +/* DMA CS Control and Status bits */ +#define BCM2708_DMA_ACTIVE BIT(0) +#define BCM2708_DMA_INT BIT(2) +#define BCM2708_DMA_ISPAUSED BIT(4) /* Pause requested or not active */ +#define BCM2708_DMA_ISHELD BIT(5) /* Is held by DREQ flow control */ +#define BCM2708_DMA_ERR BIT(8) +#define BCM2708_DMA_ABORT BIT(30) /* stop current CB, go to next, WO */ +#define BCM2708_DMA_RESET BIT(31) /* WO, self clearing */ + +/* DMA control block "info" field bits */ +#define BCM2708_DMA_INT_EN BIT(0) +#define BCM2708_DMA_TDMODE BIT(1) +#define BCM2708_DMA_WAIT_RESP BIT(3) +#define BCM2708_DMA_D_INC BIT(4) +#define BCM2708_DMA_D_WIDTH BIT(5) +#define BCM2708_DMA_D_DREQ BIT(6) +#define BCM2708_DMA_S_INC BIT(8) +#define BCM2708_DMA_S_WIDTH BIT(9) +#define BCM2708_DMA_S_DREQ BIT(10) + +#define BCM2708_DMA_BURST(x) (((x) & 0xf) << 12) +#define BCM2708_DMA_PER_MAP(x) ((x) << 16) +#define BCM2708_DMA_WAITS(x) (((x) & 0x1f) << 21) + +#define BCM2708_DMA_DREQ_EMMC 11 +#define BCM2708_DMA_DREQ_SDHOST 13 + +#define BCM2708_DMA_CS 0x00 /* Control and Status */ +#define BCM2708_DMA_ADDR 0x04 +/* the current control block appears in the following registers - read only */ +#define BCM2708_DMA_INFO 0x08 +#define BCM2708_DMA_SOURCE_AD 0x0c +#define BCM2708_DMA_DEST_AD 0x10 +#define BCM2708_DMA_NEXTCB 0x1C +#define BCM2708_DMA_DEBUG 0x20 + +#define BCM2708_DMA4_CS (BCM2708_DMA_CHAN(4) + BCM2708_DMA_CS) +#define BCM2708_DMA4_ADDR (BCM2708_DMA_CHAN(4) + BCM2708_DMA_ADDR) + +#define BCM2708_DMA_TDMODE_LEN(w, h) ((h) << 16 | (w)) + +/* When listing features we can ask for when allocating DMA channels give + those with higher priority smaller ordinal numbers */ +#define BCM_DMA_FEATURE_FAST_ORD 0 +#define BCM_DMA_FEATURE_BULK_ORD 1 +#define BCM_DMA_FEATURE_NORMAL_ORD 2 +#define BCM_DMA_FEATURE_LITE_ORD 3 +#define BCM_DMA_FEATURE_FAST BIT(BCM_DMA_FEATURE_FAST_ORD) +#define BCM_DMA_FEATURE_BULK BIT(BCM_DMA_FEATURE_BULK_ORD) +#define BCM_DMA_FEATURE_NORMAL BIT(BCM_DMA_FEATURE_NORMAL_ORD) +#define BCM_DMA_FEATURE_LITE BIT(BCM_DMA_FEATURE_LITE_ORD) +#define BCM_DMA_FEATURE_COUNT 4 + +struct bcm2708_dma_cb { + u32 info; + u32 src; + u32 dst; + u32 length; + u32 stride; + u32 next; + u32 pad[2]; +}; + +struct scatterlist; +struct platform_device; + +#if defined(CONFIG_DMA_BCM2708) || defined(CONFIG_DMA_BCM2708_MODULE) + +int bcm_sg_suitable_for_dma(struct scatterlist *sg_ptr, int sg_len); +void bcm_dma_start(void __iomem *dma_chan_base, dma_addr_t control_block); +void bcm_dma_wait_idle(void __iomem *dma_chan_base); +bool bcm_dma_is_busy(void __iomem *dma_chan_base); +int bcm_dma_abort(void __iomem *dma_chan_base); + +/* return channel no or -ve error */ +int bcm_dma_chan_alloc(unsigned preferred_feature_set, + void __iomem **out_dma_base, int *out_dma_irq); +int bcm_dma_chan_free(int channel); + +int bcm_dmaman_probe(struct platform_device *pdev, void __iomem *base, + u32 chans_available); +int bcm_dmaman_remove(struct platform_device *pdev); + +#else /* CONFIG_DMA_BCM2708 */ + +static inline int bcm_sg_suitable_for_dma(struct scatterlist *sg_ptr, + int sg_len) +{ + return 0; +} + +static inline void bcm_dma_start(void __iomem *dma_chan_base, + dma_addr_t control_block) { } + +static inline void bcm_dma_wait_idle(void __iomem *dma_chan_base) { } + +static inline bool bcm_dma_is_busy(void __iomem *dma_chan_base) +{ + return false; +} + +static inline int bcm_dma_abort(void __iomem *dma_chan_base) +{ + return -EINVAL; +} + +static inline int bcm_dma_chan_alloc(unsigned preferred_feature_set, + void __iomem **out_dma_base, + int *out_dma_irq) +{ + return -EINVAL; +} + +static inline int bcm_dma_chan_free(int channel) +{ + return -EINVAL; +} + +static inline int bcm_dmaman_probe(struct platform_device *pdev, + void __iomem *base, u32 chans_available) +{ + return 0; +} + +static inline int bcm_dmaman_remove(struct platform_device *pdev) +{ + return 0; +} + +#endif /* CONFIG_DMA_BCM2708 || CONFIG_DMA_BCM2708_MODULE */ + +#endif /* _PLAT_BCM2708_DMA_H */ diff --git a/include/linux/rp1-firmware.h b/include/linux/rp1-firmware.h new file mode 100644 index 00000000000000..19f11d6d79b167 --- /dev/null +++ b/include/linux/rp1-firmware.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2023 2023-2024 Raspberry Pi Ltd. + */ + +#ifndef __SOC_RP1_FIRMWARE_H__ +#define __SOC_RP1_FIRMWARE_H__ + +#include <linux/types.h> +#include <linux/of_device.h> + +#define RP1_FOURCC(s) ((uint32_t)((s[0] << 24) | (s[1] << 16) | (s[2] << 8) | (s[3] << 0))) + +struct rp1_firmware; + +#if IS_ENABLED(CONFIG_FIRMWARE_RP1) +int rp1_firmware_message(struct rp1_firmware *fw, uint16_t op, + const void *data, unsigned int data_len, + void *resp, unsigned int resp_space); +void rp1_firmware_put(struct rp1_firmware *fw); +struct rp1_firmware *rp1_firmware_get(struct device_node *fwnode); +struct rp1_firmware *devm_rp1_firmware_get(struct device *dev, struct device_node *fwnode); +int rp1_firmware_get_feature(struct rp1_firmware *fw, uint32_t fourcc, + uint32_t *op_base, uint32_t *op_count); +#else +static inline int rp1_firmware_message(struct rp1_firmware *fw, uint16_t op, + const void *data, unsigned int data_len, + void *resp, unsigned int resp_space) +{ + return -EOPNOTSUPP; +} + +static inline void rp1_firmware_put(struct rp1_firmware *fw) { } + +static inline struct rp1_firmware *rp1_firmware_get(struct device_node *fwnode) +{ + return NULL; +} + +static inline struct rp1_firmware *devm_rp1_firmware_get(struct device *dev, + struct device_node *fwnode) +{ + return NULL; +} + +static inline int rp1_firmware_get_feature(struct rp1_firmware *fw, uint32_t fourcc, + uint32_t *op_base, uint32_t *op_count) +{ + return -EOPNOTSUPP; +} +#endif + +#endif /* __SOC_RP1_FIRMWARE_H__ */ diff --git a/include/linux/rp1_platform.h b/include/linux/rp1_platform.h new file mode 100644 index 00000000000000..f805dbe1ed9be3 --- /dev/null +++ b/include/linux/rp1_platform.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2021-2022 Raspberry Pi Ltd. + * All rights reserved. + */ + +#ifndef _RP1_PLATFORM_H +#define _RP1_PLATFORM_H + +#include <vdso/bits.h> + +#define RP1_B0_CHIP_ID 0x10001927 +#define RP1_C0_CHIP_ID 0x20001927 + +#define RP1_PLATFORM_ASIC BIT(1) +#define RP1_PLATFORM_FPGA BIT(0) + +void rp1_get_platform(u32 *chip_id, u32 *platform); + +#endif diff --git a/include/linux/usb.h b/include/linux/usb.h index 672d8fc2abdb02..63c6fe16623053 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -1873,6 +1873,8 @@ extern int usb_clear_halt(struct usb_device *dev, int pipe); extern int usb_reset_configuration(struct usb_device *dev); extern int usb_set_interface(struct usb_device *dev, int ifnum, int alternate); extern void usb_reset_endpoint(struct usb_device *dev, unsigned int epaddr); +extern void usb_fixup_endpoint(struct usb_device *dev, int epaddr, + int interval); /* this request isn't really synchronous, but it belongs with the others */ extern int usb_driver_set_configuration(struct usb_device *udev, int config); diff --git a/include/linux/usb/hcd.h b/include/linux/usb/hcd.h index ac95e7c89df58a..8fd38368cb31a9 100644 --- a/include/linux/usb/hcd.h +++ b/include/linux/usb/hcd.h @@ -372,6 +372,11 @@ struct hc_driver { * or bandwidth constraints. */ void (*reset_bandwidth)(struct usb_hcd *, struct usb_device *); + /* Override the endpoint-derived interval + * (if there is any cached hardware state). + */ + void (*fixup_endpoint)(struct usb_hcd *hcd, struct usb_device *udev, + struct usb_host_endpoint *ep, int interval); /* Set the hardware-chosen device address */ int (*address_device)(struct usb_hcd *, struct usb_device *udev, unsigned int timeout_ms); @@ -437,6 +442,8 @@ extern void usb_hcd_unmap_urb_setup_for_dma(struct usb_hcd *, struct urb *); extern void usb_hcd_unmap_urb_for_dma(struct usb_hcd *, struct urb *); extern void usb_hcd_flush_endpoint(struct usb_device *udev, struct usb_host_endpoint *ep); +extern void usb_hcd_fixup_endpoint(struct usb_device *udev, + struct usb_host_endpoint *ep, int interval); extern void usb_hcd_disable_endpoint(struct usb_device *udev, struct usb_host_endpoint *ep); extern void usb_hcd_reset_endpoint(struct usb_device *udev, diff --git a/include/linux/w1.h b/include/linux/w1.h index 064805bfae3fce..6bcbafb6ea4ffd 100644 --- a/include/linux/w1.h +++ b/include/linux/w1.h @@ -122,6 +122,9 @@ typedef void (*w1_slave_found_callback)(struct w1_master *, u64); * @dev_id: Optional device id string, which w1 slaves could use for * creating names, which then give a connection to the w1 master * + * @delay_needs_poll: work around jitter introduced with GPIO controllers + * accessed over PCIe (RP1) + * * Note: read_bit and write_bit are very low level functions and should only * be used with hardware that doesn't really support 1-wire operations, * like a parallel/serial port. @@ -156,6 +159,8 @@ struct w1_bus_master { u8, w1_slave_found_callback); char *dev_id; + + bool delay_needs_poll; }; /** diff --git a/include/media/media-request.h b/include/media/media-request.h index 3cd25a2717ce7c..0de5c2c9418839 100644 --- a/include/media/media-request.h +++ b/include/media/media-request.h @@ -189,6 +189,10 @@ static inline void media_request_get(struct media_request *req) */ void media_request_put(struct media_request *req); +void media_request_pin(struct media_request *req); + +void media_request_unpin(struct media_request *req); + /** * media_request_get_by_fd - Get a media request by fd * @@ -228,6 +232,14 @@ static inline void media_request_put(struct media_request *req) { } +static inline void media_request_pin(struct media_request *req) +{ +} + +static inline void media_request_unpin(struct media_request *req) +{ +} + static inline struct media_request * media_request_get_by_fd(struct media_device *mdev, int request_fd) { diff --git a/include/media/videobuf2-core.h b/include/media/videobuf2-core.h index 9b02aeba41089c..c75b4f14581a35 100644 --- a/include/media/videobuf2-core.h +++ b/include/media/videobuf2-core.h @@ -967,6 +967,21 @@ int vb2_core_streamon(struct vb2_queue *q, unsigned int type); */ int vb2_core_streamoff(struct vb2_queue *q, unsigned int type); +/** + * vb2_core_expbuf_dmabuf() - Export a buffer as a dma_buf structure + * @q: videobuf2 queue + * @type: buffer type + * @index: id number of the buffer + * @plane: index of the plane to be exported, 0 for single plane queues + * @flags: flags for newly created file, currently only O_CLOEXEC is + * supported, refer to manual of open syscall for more details + * @dmabuf: Returns the dmabuf pointer + * + */ +int vb2_core_expbuf_dmabuf(struct vb2_queue *q, unsigned int type, + struct vb2_buffer *vb, unsigned int plane, + unsigned int flags, struct dma_buf **dmabuf); + /** * vb2_core_expbuf() - Export a buffer as a file descriptor. * @q: pointer to &struct vb2_queue with videobuf2 queue. diff --git a/include/soc/bcm2835/raspberrypi-firmware.h b/include/soc/bcm2835/raspberrypi-firmware.h index 73cac8d0287e89..3e71d45477a422 100644 --- a/include/soc/bcm2835/raspberrypi-firmware.h +++ b/include/soc/bcm2835/raspberrypi-firmware.h @@ -36,6 +36,8 @@ struct rpi_firmware_property_tag_header { enum rpi_firmware_property_tag { RPI_FIRMWARE_PROPERTY_END = 0, RPI_FIRMWARE_GET_FIRMWARE_REVISION = 0x00000001, + RPI_FIRMWARE_GET_FIRMWARE_VARIANT = 0x00000002, + RPI_FIRMWARE_GET_FIRMWARE_HASH = 0x00000003, RPI_FIRMWARE_SET_CURSOR_INFO = 0x00008010, RPI_FIRMWARE_SET_CURSOR_STATE = 0x00008011, @@ -71,6 +73,7 @@ enum rpi_firmware_property_tag { RPI_FIRMWARE_GET_DISPMANX_RESOURCE_MEM_HANDLE = 0x00030014, RPI_FIRMWARE_GET_EDID_BLOCK = 0x00030020, RPI_FIRMWARE_GET_CUSTOMER_OTP = 0x00030021, + RPI_FIRMWARE_GET_EDID_BLOCK_DISPLAY = 0x00030023, RPI_FIRMWARE_GET_DOMAIN_STATE = 0x00030030, RPI_FIRMWARE_GET_THROTTLED = 0x00030046, RPI_FIRMWARE_GET_CLOCK_MEASURED = 0x00030047, @@ -89,9 +92,14 @@ enum rpi_firmware_property_tag { RPI_FIRMWARE_GET_PERIPH_REG = 0x00030045, RPI_FIRMWARE_SET_PERIPH_REG = 0x00038045, RPI_FIRMWARE_GET_POE_HAT_VAL = 0x00030049, - RPI_FIRMWARE_SET_POE_HAT_VAL = 0x00030050, + RPI_FIRMWARE_SET_POE_HAT_VAL = 0x00038049, + RPI_FIRMWARE_SET_POE_HAT_VAL_OLD = 0x00030050, RPI_FIRMWARE_NOTIFY_XHCI_RESET = 0x00030058, + RPI_FIRMWARE_GET_REBOOT_FLAGS = 0x00030064, + RPI_FIRMWARE_SET_REBOOT_FLAGS = 0x00038064, RPI_FIRMWARE_NOTIFY_DISPLAY_DONE = 0x00030066, + RPI_FIRMWARE_GET_SW_UART = 0x0003008a, + RPI_FIRMWARE_SET_SW_UART = 0x0003808a, /* Dispmanx TAGS */ RPI_FIRMWARE_FRAMEBUFFER_ALLOCATE = 0x00040001, @@ -105,9 +113,16 @@ enum rpi_firmware_property_tag { RPI_FIRMWARE_FRAMEBUFFER_GET_VIRTUAL_OFFSET = 0x00040009, RPI_FIRMWARE_FRAMEBUFFER_GET_OVERSCAN = 0x0004000a, RPI_FIRMWARE_FRAMEBUFFER_GET_PALETTE = 0x0004000b, + RPI_FIRMWARE_FRAMEBUFFER_GET_LAYER = 0x0004000c, + RPI_FIRMWARE_FRAMEBUFFER_GET_TRANSFORM = 0x0004000d, + RPI_FIRMWARE_FRAMEBUFFER_GET_VSYNC = 0x0004000e, RPI_FIRMWARE_FRAMEBUFFER_GET_TOUCHBUF = 0x0004000f, RPI_FIRMWARE_FRAMEBUFFER_GET_GPIOVIRTBUF = 0x00040010, RPI_FIRMWARE_FRAMEBUFFER_RELEASE = 0x00048001, + RPI_FIRMWARE_FRAMEBUFFER_GET_DISPLAY_ID = 0x00040016, + RPI_FIRMWARE_FRAMEBUFFER_SET_DISPLAY_NUM = 0x00048013, + RPI_FIRMWARE_FRAMEBUFFER_GET_NUM_DISPLAYS = 0x00040013, + RPI_FIRMWARE_FRAMEBUFFER_GET_DISPLAY_SETTINGS = 0x00040014, RPI_FIRMWARE_FRAMEBUFFER_TEST_PHYSICAL_WIDTH_HEIGHT = 0x00044003, RPI_FIRMWARE_FRAMEBUFFER_TEST_VIRTUAL_WIDTH_HEIGHT = 0x00044004, RPI_FIRMWARE_FRAMEBUFFER_TEST_DEPTH = 0x00044005, @@ -116,22 +131,33 @@ enum rpi_firmware_property_tag { RPI_FIRMWARE_FRAMEBUFFER_TEST_VIRTUAL_OFFSET = 0x00044009, RPI_FIRMWARE_FRAMEBUFFER_TEST_OVERSCAN = 0x0004400a, RPI_FIRMWARE_FRAMEBUFFER_TEST_PALETTE = 0x0004400b, + RPI_FIRMWARE_FRAMEBUFFER_TEST_LAYER = 0x0004400c, + RPI_FIRMWARE_FRAMEBUFFER_TEST_TRANSFORM = 0x0004400d, RPI_FIRMWARE_FRAMEBUFFER_TEST_VSYNC = 0x0004400e, RPI_FIRMWARE_FRAMEBUFFER_SET_PHYSICAL_WIDTH_HEIGHT = 0x00048003, RPI_FIRMWARE_FRAMEBUFFER_SET_VIRTUAL_WIDTH_HEIGHT = 0x00048004, RPI_FIRMWARE_FRAMEBUFFER_SET_DEPTH = 0x00048005, RPI_FIRMWARE_FRAMEBUFFER_SET_PIXEL_ORDER = 0x00048006, RPI_FIRMWARE_FRAMEBUFFER_SET_ALPHA_MODE = 0x00048007, + RPI_FIRMWARE_FRAMEBUFFER_SET_PITCH = 0x00048008, RPI_FIRMWARE_FRAMEBUFFER_SET_VIRTUAL_OFFSET = 0x00048009, RPI_FIRMWARE_FRAMEBUFFER_SET_OVERSCAN = 0x0004800a, RPI_FIRMWARE_FRAMEBUFFER_SET_PALETTE = 0x0004800b, + RPI_FIRMWARE_FRAMEBUFFER_SET_TOUCHBUF = 0x0004801f, RPI_FIRMWARE_FRAMEBUFFER_SET_GPIOVIRTBUF = 0x00048020, RPI_FIRMWARE_FRAMEBUFFER_SET_VSYNC = 0x0004800e, + RPI_FIRMWARE_FRAMEBUFFER_SET_LAYER = 0x0004800c, + RPI_FIRMWARE_FRAMEBUFFER_SET_TRANSFORM = 0x0004800d, RPI_FIRMWARE_FRAMEBUFFER_SET_BACKLIGHT = 0x0004800f, RPI_FIRMWARE_VCHIQ_INIT = 0x00048010, + RPI_FIRMWARE_SET_PLANE = 0x00048015, + RPI_FIRMWARE_GET_DISPLAY_TIMING = 0x00040017, + RPI_FIRMWARE_SET_TIMING = 0x00048017, + RPI_FIRMWARE_GET_DISPLAY_CFG = 0x00040018, + RPI_FIRMWARE_SET_DISPLAY_POWER = 0x00048019, RPI_FIRMWARE_GET_COMMAND_LINE = 0x00050001, RPI_FIRMWARE_GET_DMA_CHANNELS = 0x00060001, }; @@ -152,9 +178,12 @@ enum rpi_firmware_clk_id { RPI_FIRMWARE_M2MC_CLK_ID, RPI_FIRMWARE_PIXEL_BVB_CLK_ID, RPI_FIRMWARE_VEC_CLK_ID, + RPI_FIRMWARE_DISP_CLK_ID, RPI_FIRMWARE_NUM_CLK_ID, }; +#define GET_DISPLAY_SETTINGS_PAYLOAD_SIZE 64 + /** * struct rpi_firmware_clk_rate_request - Firmware Request for a rate * @id: ID of the clock being queried diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h index c082810c08a8b2..563e337d55a508 100644 --- a/include/uapi/drm/drm_mode.h +++ b/include/uapi/drm/drm_mode.h @@ -203,6 +203,7 @@ extern "C" { */ #define DRM_MODE_REFLECT_X (1<<4) #define DRM_MODE_REFLECT_Y (1<<5) +#define DRM_MODE_TRANSPOSE (1<<6) /* * DRM_MODE_REFLECT_MASK diff --git a/include/uapi/drm/v3d_drm.h b/include/uapi/drm/v3d_drm.h index 87fc5bb0a61e21..dbbc404d2b3dd6 100644 --- a/include/uapi/drm/v3d_drm.h +++ b/include/uapi/drm/v3d_drm.h @@ -43,6 +43,7 @@ extern "C" { #define DRM_V3D_PERFMON_GET_VALUES 0x0a #define DRM_V3D_SUBMIT_CPU 0x0b #define DRM_V3D_PERFMON_GET_COUNTER 0x0c +#define DRM_V3D_PERFMON_SET_GLOBAL 0x0d #define DRM_IOCTL_V3D_SUBMIT_CL DRM_IOWR(DRM_COMMAND_BASE + DRM_V3D_SUBMIT_CL, struct drm_v3d_submit_cl) #define DRM_IOCTL_V3D_WAIT_BO DRM_IOWR(DRM_COMMAND_BASE + DRM_V3D_WAIT_BO, struct drm_v3d_wait_bo) @@ -61,6 +62,8 @@ extern "C" { #define DRM_IOCTL_V3D_SUBMIT_CPU DRM_IOW(DRM_COMMAND_BASE + DRM_V3D_SUBMIT_CPU, struct drm_v3d_submit_cpu) #define DRM_IOCTL_V3D_PERFMON_GET_COUNTER DRM_IOWR(DRM_COMMAND_BASE + DRM_V3D_PERFMON_GET_COUNTER, \ struct drm_v3d_perfmon_get_counter) +#define DRM_IOCTL_V3D_PERFMON_SET_GLOBAL DRM_IOW(DRM_COMMAND_BASE + DRM_V3D_PERFMON_SET_GLOBAL, \ + struct drm_v3d_perfmon_set_global) #define DRM_V3D_SUBMIT_CL_FLUSH_CACHE 0x01 #define DRM_V3D_SUBMIT_EXTENSION 0x02 @@ -290,6 +293,7 @@ enum drm_v3d_param { DRM_V3D_PARAM_SUPPORTS_MULTISYNC_EXT, DRM_V3D_PARAM_SUPPORTS_CPU_QUEUE, DRM_V3D_PARAM_MAX_PERF_COUNTERS, + DRM_V3D_PARAM_SUPPORTS_SUPER_PAGES, }; struct drm_v3d_get_param { @@ -765,6 +769,21 @@ struct drm_v3d_perfmon_get_counter { __u8 reserved[7]; }; +#define DRM_V3D_PERFMON_CLEAR_GLOBAL 0x0001 + +/** + * struct drm_v3d_perfmon_set_global - ioctl to define a global performance + * monitor + * + * The global performance monitor will be used for all jobs. If a global + * performance monitor is defined, jobs with a self-defined performance + * monitor won't be allowed. + */ +struct drm_v3d_perfmon_set_global { + __u32 flags; + __u32 id; +}; + #if defined(__cplusplus) } #endif diff --git a/include/uapi/linux/bcm2835-isp.h b/include/uapi/linux/bcm2835-isp.h new file mode 100644 index 00000000000000..c50e3ca8156577 --- /dev/null +++ b/include/uapi/linux/bcm2835-isp.h @@ -0,0 +1,347 @@ +/* SPDX-License-Identifier: ((GPL-2.0+ WITH Linux-syscall-note) OR BSD-3-Clause) */ +/* + * bcm2835-isp.h + * + * BCM2835 ISP driver - user space header file. + * + * Copyright © 2019-2020 Raspberry Pi (Trading) Ltd. + * + * Author: Naushir Patuck (naush@raspberrypi.com) + * + */ + +#ifndef __BCM2835_ISP_H_ +#define __BCM2835_ISP_H_ + +#include <linux/v4l2-controls.h> + +#define V4L2_CID_USER_BCM2835_ISP_CC_MATRIX \ + (V4L2_CID_USER_BCM2835_ISP_BASE + 0x0001) +#define V4L2_CID_USER_BCM2835_ISP_LENS_SHADING \ + (V4L2_CID_USER_BCM2835_ISP_BASE + 0x0002) +#define V4L2_CID_USER_BCM2835_ISP_BLACK_LEVEL \ + (V4L2_CID_USER_BCM2835_ISP_BASE + 0x0003) +#define V4L2_CID_USER_BCM2835_ISP_GEQ \ + (V4L2_CID_USER_BCM2835_ISP_BASE + 0x0004) +#define V4L2_CID_USER_BCM2835_ISP_GAMMA \ + (V4L2_CID_USER_BCM2835_ISP_BASE + 0x0005) +#define V4L2_CID_USER_BCM2835_ISP_DENOISE \ + (V4L2_CID_USER_BCM2835_ISP_BASE + 0x0006) +#define V4L2_CID_USER_BCM2835_ISP_SHARPEN \ + (V4L2_CID_USER_BCM2835_ISP_BASE + 0x0007) +#define V4L2_CID_USER_BCM2835_ISP_DPC \ + (V4L2_CID_USER_BCM2835_ISP_BASE + 0x0008) +#define V4L2_CID_USER_BCM2835_ISP_CDN \ + (V4L2_CID_USER_BCM2835_ISP_BASE + 0x0009) + +/* + * All structs below are directly mapped onto the equivalent structs in + * drivers/staging/vc04_services/vchiq-mmal/mmal-parameters.h + * for convenience. + */ + +/** + * struct bcm2835_isp_rational - Rational value type. + * + * @num: Numerator. + * @den: Denominator. + */ +struct bcm2835_isp_rational { + __s32 num; + __u32 den; +}; + +/** + * struct bcm2835_isp_ccm - Colour correction matrix. + * + * @ccm: 3x3 correction matrix coefficients. + * @offsets: 1x3 correction offsets. + */ +struct bcm2835_isp_ccm { + struct bcm2835_isp_rational ccm[3][3]; + __s32 offsets[3]; +}; + +/** + * struct bcm2835_isp_custom_ccm - Custom CCM applied with the + * V4L2_CID_USER_BCM2835_ISP_CC_MATRIX ctrl. + * + * @enabled: Enable custom CCM. + * @ccm: Custom CCM coefficients and offsets. + */ +struct bcm2835_isp_custom_ccm { + __u32 enabled; + struct bcm2835_isp_ccm ccm; +}; + +/** + * enum bcm2835_isp_gain_format - format of the gains in the lens shading + * tables used with the + * V4L2_CID_USER_BCM2835_ISP_LENS_SHADING ctrl. + * + * @GAIN_FORMAT_U0P8_1: Gains are u0.8 format, starting at 1.0 + * @GAIN_FORMAT_U1P7_0: Gains are u1.7 format, starting at 0.0 + * @GAIN_FORMAT_U1P7_1: Gains are u1.7 format, starting at 1.0 + * @GAIN_FORMAT_U2P6_0: Gains are u2.6 format, starting at 0.0 + * @GAIN_FORMAT_U2P6_1: Gains are u2.6 format, starting at 1.0 + * @GAIN_FORMAT_U3P5_0: Gains are u3.5 format, starting at 0.0 + * @GAIN_FORMAT_U3P5_1: Gains are u3.5 format, starting at 1.0 + * @GAIN_FORMAT_U4P10: Gains are u4.10 format, starting at 0.0 + */ +enum bcm2835_isp_gain_format { + GAIN_FORMAT_U0P8_1 = 0, + GAIN_FORMAT_U1P7_0 = 1, + GAIN_FORMAT_U1P7_1 = 2, + GAIN_FORMAT_U2P6_0 = 3, + GAIN_FORMAT_U2P6_1 = 4, + GAIN_FORMAT_U3P5_0 = 5, + GAIN_FORMAT_U3P5_1 = 6, + GAIN_FORMAT_U4P10 = 7, +}; + +/** + * struct bcm2835_isp_lens_shading - Lens shading tables supplied with the + * V4L2_CID_USER_BCM2835_ISP_LENS_SHADING + * ctrl. + * + * @enabled: Enable lens shading. + * @grid_cell_size: Size of grid cells in samples (16, 32, 64, 128 or 256). + * @grid_width: Width of lens shading tables in grid cells. + * @grid_stride: Row to row distance (in grid cells) between grid cells + * in the same horizontal location. + * @grid_height: Height of lens shading tables in grid cells. + * @dmabuf: dmabuf file handle containing the table. + * @ref_transform: Reference transform - unsupported, please pass zero. + * @corner_sampled: Whether the gains are sampled at the corner points + * of the grid cells or in the cell centres. + * @gain_format: Format of the gains (see enum &bcm2835_isp_gain_format). + */ +struct bcm2835_isp_lens_shading { + __u32 enabled; + __u32 grid_cell_size; + __u32 grid_width; + __u32 grid_stride; + __u32 grid_height; + __s32 dmabuf; + __u32 ref_transform; + __u32 corner_sampled; + __u32 gain_format; +}; + +/** + * struct bcm2835_isp_black_level - Sensor black level set with the + * V4L2_CID_USER_BCM2835_ISP_BLACK_LEVEL ctrl. + * + * @enabled: Enable black level. + * @black_level_r: Black level for red channel. + * @black_level_g: Black level for green channels. + * @black_level_b: Black level for blue channel. + */ +struct bcm2835_isp_black_level { + __u32 enabled; + __u16 black_level_r; + __u16 black_level_g; + __u16 black_level_b; + __u8 padding[2]; /* Unused */ +}; + +/** + * struct bcm2835_isp_geq - Green equalisation parameters set with the + * V4L2_CID_USER_BCM2835_ISP_GEQ ctrl. + * + * @enabled: Enable green equalisation. + * @offset: Fixed offset of the green equalisation threshold. + * @slope: Slope of the green equalisation threshold. + */ +struct bcm2835_isp_geq { + __u32 enabled; + __u32 offset; + struct bcm2835_isp_rational slope; +}; + +#define BCM2835_NUM_GAMMA_PTS 33 + +/** + * struct bcm2835_isp_gamma - Gamma parameters set with the + * V4L2_CID_USER_BCM2835_ISP_GAMMA ctrl. + * + * @enabled: Enable gamma adjustment. + * @X: X values of the points defining the gamma curve. + * Values should be scaled to 16 bits. + * @Y: Y values of the points defining the gamma curve. + * Values should be scaled to 16 bits. + */ +struct bcm2835_isp_gamma { + __u32 enabled; + __u16 x[BCM2835_NUM_GAMMA_PTS]; + __u16 y[BCM2835_NUM_GAMMA_PTS]; +}; + +/** + * enum bcm2835_isp_cdn_mode - Mode of operation for colour denoise. + * + * @CDN_MODE_FAST: Fast (but lower quality) colour denoise + * algorithm, typically used for video recording. + * @CDN_HIGH_QUALITY: High quality (but slower) colour denoise + * algorithm, typically used for stills capture. + */ +enum bcm2835_isp_cdn_mode { + CDN_MODE_FAST = 0, + CDN_MODE_HIGH_QUALITY = 1, +}; + +/** + * struct bcm2835_isp_cdn - Colour denoise parameters set with the + * V4L2_CID_USER_BCM2835_ISP_CDN ctrl. + * + * @enabled: Enable colour denoise. + * @mode: Colour denoise operating mode (see enum &bcm2835_isp_cdn_mode) + */ +struct bcm2835_isp_cdn { + __u32 enabled; + __u32 mode; +}; + +/** + * struct bcm2835_isp_denoise - Denoise parameters set with the + * V4L2_CID_USER_BCM2835_ISP_DENOISE ctrl. + * + * @enabled: Enable denoise. + * @constant: Fixed offset of the noise threshold. + * @slope: Slope of the noise threshold. + * @strength: Denoise strength between 0.0 (off) and 1.0 (maximum). + */ +struct bcm2835_isp_denoise { + __u32 enabled; + __u32 constant; + struct bcm2835_isp_rational slope; + struct bcm2835_isp_rational strength; +}; + +/** + * struct bcm2835_isp_sharpen - Sharpen parameters set with the + * V4L2_CID_USER_BCM2835_ISP_SHARPEN ctrl. + * + * @enabled: Enable sharpening. + * @threshold: Threshold at which to start sharpening pixels. + * @strength: Strength with which pixel sharpening increases. + * @limit: Limit to the amount of sharpening applied. + */ +struct bcm2835_isp_sharpen { + __u32 enabled; + struct bcm2835_isp_rational threshold; + struct bcm2835_isp_rational strength; + struct bcm2835_isp_rational limit; +}; + +/** + * enum bcm2835_isp_dpc_mode - defective pixel correction (DPC) strength. + * + * @DPC_MODE_OFF: No DPC. + * @DPC_MODE_NORMAL: Normal DPC. + * @DPC_MODE_STRONG: Strong DPC. + */ +enum bcm2835_isp_dpc_mode { + DPC_MODE_OFF = 0, + DPC_MODE_NORMAL = 1, + DPC_MODE_STRONG = 2, +}; + +/** + * struct bcm2835_isp_dpc - Defective pixel correction (DPC) parameters set + * with the V4L2_CID_USER_BCM2835_ISP_DPC ctrl. + * + * @enabled: Enable DPC. + * @strength: DPC strength (see enum &bcm2835_isp_dpc_mode). + */ +struct bcm2835_isp_dpc { + __u32 enabled; + __u32 strength; +}; + +/* + * ISP statistics structures. + * + * The bcm2835_isp_stats structure is generated at the output of the + * statistics node. Note that this does not directly map onto the statistics + * output of the ISP HW. Instead, the MMAL firmware code maps the HW statistics + * to the bcm2835_isp_stats structure. + */ +#define DEFAULT_AWB_REGIONS_X 16 +#define DEFAULT_AWB_REGIONS_Y 12 + +#define NUM_HISTOGRAMS 2 +#define NUM_HISTOGRAM_BINS 128 +#define AWB_REGIONS (DEFAULT_AWB_REGIONS_X * DEFAULT_AWB_REGIONS_Y) +#define FLOATING_REGIONS 16 +#define AGC_REGIONS 16 +#define FOCUS_REGIONS 12 + +/** + * struct bcm2835_isp_stats_hist - Histogram statistics + * + * @r_hist: Red channel histogram. + * @g_hist: Combined green channel histogram. + * @b_hist: Blue channel histogram. + */ +struct bcm2835_isp_stats_hist { + __u32 r_hist[NUM_HISTOGRAM_BINS]; + __u32 g_hist[NUM_HISTOGRAM_BINS]; + __u32 b_hist[NUM_HISTOGRAM_BINS]; +}; + +/** + * struct bcm2835_isp_stats_region - Region sums. + * + * @counted: The number of 2x2 bayer tiles accumulated. + * @notcounted: The number of 2x2 bayer tiles not accumulated. + * @r_sum: Total sum of counted pixels in the red channel for a region. + * @g_sum: Total sum of counted pixels in the green channel for a region. + * @b_sum: Total sum of counted pixels in the blue channel for a region. + */ +struct bcm2835_isp_stats_region { + __u32 counted; + __u32 notcounted; + __u64 r_sum; + __u64 g_sum; + __u64 b_sum; +}; + +/** + * struct bcm2835_isp_stats_focus - Focus statistics. + * + * @contrast_val: Focus measure - accumulated output of the focus filter. + * In the first dimension, index [0] counts pixels below a + * preset threshold, and index [1] counts pixels above the + * threshold. In the second dimension, index [0] uses the + * first predefined filter, and index [1] uses the second + * predefined filter. + * @contrast_val_num: The number of counted pixels in the above accumulation. + */ +struct bcm2835_isp_stats_focus { + __u64 contrast_val[2][2]; + __u32 contrast_val_num[2][2]; +}; + +/** + * struct bcm2835_isp_stats - ISP statistics. + * + * @version: Version of the bcm2835_isp_stats structure. + * @size: Size of the bcm2835_isp_stats structure. + * @hist: Histogram statistics for the entire image. + * @awb_stats: Statistics for the regions defined for AWB calculations. + * @floating_stats: Statistics for arbitrarily placed (floating) regions. + * @agc_stats: Statistics for the regions defined for AGC calculations. + * @focus_stats: Focus filter statistics for the focus regions. + */ +struct bcm2835_isp_stats { + __u32 version; + __u32 size; + struct bcm2835_isp_stats_hist hist[NUM_HISTOGRAMS]; + struct bcm2835_isp_stats_region awb_stats[AWB_REGIONS]; + struct bcm2835_isp_stats_region floating_stats[FLOATING_REGIONS]; + struct bcm2835_isp_stats_region agc_stats[AGC_REGIONS]; + struct bcm2835_isp_stats_focus focus_stats[FOCUS_REGIONS]; +}; + +#endif /* __BCM2835_ISP_H_ */ diff --git a/include/uapi/linux/fb.h b/include/uapi/linux/fb.h index cde8f173f566b8..63aac986db0b1d 100644 --- a/include/uapi/linux/fb.h +++ b/include/uapi/linux/fb.h @@ -36,6 +36,12 @@ #define FBIOPUT_MODEINFO 0x4617 #define FBIOGET_DISPINFO 0x4618 #define FBIO_WAITFORVSYNC _IOW('F', 0x20, __u32) +/* + * HACK: use 'z' in order not to clash with any other ioctl numbers which might + * be concurrently added to the mainline kernel + */ +#define FBIOCOPYAREA _IOW('z', 0x21, struct fb_copyarea) +#define FBIODMACOPY _IOW('z', 0x22, struct fb_dmacopy) #define FB_TYPE_PACKED_PIXELS 0 /* Packed Pixels */ #define FB_TYPE_PLANES 1 /* Non interleaved planes */ @@ -342,6 +348,12 @@ struct fb_copyarea { __u32 sy; }; +struct fb_dmacopy { + void *dst; + __u32 src; + __u32 length; +}; + struct fb_fillrect { __u32 dx; /* screen-relative */ __u32 dy; diff --git a/include/uapi/linux/media-bus-format.h b/include/uapi/linux/media-bus-format.h index d4c1d991014b39..ac214f7ef341fe 100644 --- a/include/uapi/linux/media-bus-format.h +++ b/include/uapi/linux/media-bus-format.h @@ -183,4 +183,7 @@ #define MEDIA_BUS_FMT_META_20 0x8006 #define MEDIA_BUS_FMT_META_24 0x8007 +/* Sensor ancillary metadata formats - next is 0x7002 */ +#define MEDIA_BUS_FMT_SENSOR_DATA 0x7002 + #endif /* __LINUX_MEDIA_BUS_FORMAT_H */ diff --git a/include/uapi/linux/media/raspberrypi/pisp_be_config.h b/include/uapi/linux/media/raspberrypi/pisp_be_config.h index cbeb714f4d61ad..82560db4da610f 100644 --- a/include/uapi/linux/media/raspberrypi/pisp_be_config.h +++ b/include/uapi/linux/media/raspberrypi/pisp_be_config.h @@ -716,13 +716,6 @@ struct pisp_be_hog_buffer_config { /** * struct pisp_be_config - RaspberryPi PiSP Back End Processing configuration * - * @input_buffer: Input buffer addresses - * @tdn_input_buffer: TDN input buffer addresses - * @stitch_input_buffer: Stitch input buffer addresses - * @tdn_output_buffer: TDN output buffer addresses - * @stitch_output_buffer: Stitch output buffer addresses - * @output_buffer: Output buffers addresses - * @hog_buffer: HOG buffer addresses * @global: Global PiSP configuration * @input_format: Input image format * @decompress: Decompress configuration @@ -761,28 +754,10 @@ struct pisp_be_hog_buffer_config { * @output_format: Output format configuration * @hog: HOG configuration * @axi: AXI bus configuration - * @lsc_extra: LSC extra info - * @cac_extra: CAC extra info - * @downscale_extra: Downscaler extra info - * @resample_extra: Resample extra info - * @crop: Crop configuration - * @hog_format: HOG format info - * @dirty_flags_bayer: Bayer enable dirty flags - * (:c:type:`pisp_be_bayer_enable`) - * @dirty_flags_rgb: RGB enable dirty flags - * (:c:type:`pisp_be_rgb_enable`) - * @dirty_flags_extra: Extra dirty flags */ struct pisp_be_config { - /* I/O configuration: */ - struct pisp_be_input_buffer_config input_buffer; - struct pisp_be_tdn_input_buffer_config tdn_input_buffer; - struct pisp_be_stitch_input_buffer_config stitch_input_buffer; - struct pisp_be_tdn_output_buffer_config tdn_output_buffer; - struct pisp_be_stitch_output_buffer_config stitch_output_buffer; - struct pisp_be_output_buffer_config - output_buffer[PISP_BACK_END_NUM_OUTPUTS]; - struct pisp_be_hog_buffer_config hog_buffer; + /* For backward compatibility */ + uint8_t pad0[112]; /* Processing configuration: */ struct pisp_be_global_config global; struct pisp_image_format_config input_format; @@ -823,17 +798,8 @@ struct pisp_be_config { output_format[PISP_BACK_END_NUM_OUTPUTS]; struct pisp_be_hog_config hog; struct pisp_be_axi_config axi; - /* Non-register fields: */ - struct pisp_be_lsc_extra lsc_extra; - struct pisp_be_cac_extra cac_extra; - struct pisp_be_downscale_extra - downscale_extra[PISP_BACK_END_NUM_OUTPUTS]; - struct pisp_be_resample_extra resample_extra[PISP_BACK_END_NUM_OUTPUTS]; - struct pisp_be_crop_config crop; - struct pisp_image_format_config hog_format; - __u32 dirty_flags_bayer; /* these use pisp_be_bayer_enable */ - __u32 dirty_flags_rgb; /* use pisp_be_rgb_enable */ - __u32 dirty_flags_extra; /* these use pisp_be_dirty_t */ + /* For backward compatibility */ + uint8_t pad1[84]; } __attribute__((packed)); /** diff --git a/include/uapi/linux/mempolicy.h b/include/uapi/linux/mempolicy.h index 1f9bb10d1a473f..f3df4b6084aab6 100644 --- a/include/uapi/linux/mempolicy.h +++ b/include/uapi/linux/mempolicy.h @@ -24,6 +24,7 @@ enum { MPOL_LOCAL, MPOL_PREFERRED_MANY, MPOL_WEIGHTED_INTERLEAVE, + MPOL_RANDOM, MPOL_MAX, /* always last member of enum */ }; diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h index 9c007a106330b9..e0ca374c0db8bb 100644 --- a/include/uapi/linux/serial_core.h +++ b/include/uapi/linux/serial_core.h @@ -231,6 +231,9 @@ /* Sunplus UART */ #define PORT_SUNPLUS 123 +/* RPi firmware UART */ +#define PORT_RPI_FW 124 + /* Generic type identifier for ports which type is not important to userspace. */ #define PORT_GENERIC (-1) diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h index 974fd254e57309..f7ac33a1d57bc1 100644 --- a/include/uapi/linux/v4l2-controls.h +++ b/include/uapi/linux/v4l2-controls.h @@ -215,6 +215,16 @@ enum v4l2_colorfx { */ #define V4L2_CID_USER_THP7312_BASE (V4L2_CID_USER_BASE + 0x11c0) +/* The base for the bcm2835-isp driver controls. + * We reserve 16 controls for this driver. */ +#define V4L2_CID_USER_BCM2835_ISP_BASE (V4L2_CID_USER_BASE + 0x10e0) + +/* + * The base for IMX500 driver controls. + * We reserve 16 controls for this driver. + */ +#define V4L2_CID_USER_IMX500_BASE (V4L2_CID_USER_BASE + 0x2000) + /* MPEG-class control IDs */ /* The MPEG controls are applicable to all codec controls * and the 'MPEG' part of the define is historical */ @@ -1018,6 +1028,7 @@ enum v4l2_auto_n_preset_white_balance { V4L2_WHITE_BALANCE_FLASH = 7, V4L2_WHITE_BALANCE_CLOUDY = 8, V4L2_WHITE_BALANCE_SHADE = 9, + V4L2_WHITE_BALANCE_GREYWORLD = 10, }; #define V4L2_CID_WIDE_DYNAMIC_RANGE (V4L2_CID_CAMERA_CLASS_BASE+21) diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h index 27239cb64065d3..3a8e4bc19b3d80 100644 --- a/include/uapi/linux/videodev2.h +++ b/include/uapi/linux/videodev2.h @@ -82,6 +82,11 @@ ((__u32)(a) | ((__u32)(b) << 8) | ((__u32)(c) << 16) | ((__u32)(d) << 24)) #define v4l2_fourcc_be(a, b, c, d) (v4l2_fourcc(a, b, c, d) | (1U << 31)) +#define V4L2_FOURCC_CONV "%c%c%c%c%s" +#define V4L2_FOURCC_CONV_ARGS(fourcc) \ + (fourcc) & 0x7f, ((fourcc) >> 8) & 0x7f, ((fourcc) >> 16) & 0x7f, \ + ((fourcc) >> 24) & 0x7f, (fourcc) & BIT(31) ? "-BE" : "" + /* * E N U M S */ @@ -810,6 +815,10 @@ struct v4l2_pix_format { #define V4L2_PIX_FMT_QC10C v4l2_fourcc('Q', '1', '0', 'C') /* Qualcomm 10-bit compressed */ #define V4L2_PIX_FMT_AJPG v4l2_fourcc('A', 'J', 'P', 'G') /* Aspeed JPEG */ #define V4L2_PIX_FMT_HEXTILE v4l2_fourcc('H', 'X', 'T', 'L') /* Hextile compressed */ +#define V4L2_PIX_FMT_NV12_COL128 v4l2_fourcc('N', 'C', '1', '2') /* 12 Y/CbCr 4:2:0 128 pixel wide column */ +#define V4L2_PIX_FMT_NV12_10_COL128 v4l2_fourcc('N', 'C', '3', '0') + /* Y/CbCr 4:2:0 10bpc, 3x10 packed as 4 bytes in + * a 128 bytes / 96 pixel wide column */ /* 10bit raw packed, 32 bytes for every 25 pixels, last LSB 6 bits unused */ #define V4L2_PIX_FMT_IPU3_SBGGR10 v4l2_fourcc('i', 'p', '3', 'b') /* IPU3 packed 10-bit BGGR bayer */ @@ -829,6 +838,17 @@ struct v4l2_pix_format { #define V4L2_PIX_FMT_PISP_COMP2_BGGR v4l2_fourcc('P', 'C', '2', 'B') /* PiSP 8-bit mode 2 compressed BGGR bayer */ #define V4L2_PIX_FMT_PISP_COMP2_MONO v4l2_fourcc('P', 'C', '2', 'M') /* PiSP 8-bit mode 2 compressed monochrome */ +/* The pixel format for all our buffers (the precise format is found in the config buffer). */ +#define V4L2_PIX_FMT_RPI_BE v4l2_fourcc('R', 'P', 'B', 'P') +#define V4L2_PIX_FMT_PISP_COMP1_RGGB v4l2_fourcc('P', 'C', '1', 'R') +#define V4L2_PIX_FMT_PISP_COMP1_GRBG v4l2_fourcc('P', 'C', '1', 'G') +#define V4L2_PIX_FMT_PISP_COMP1_GBRG v4l2_fourcc('P', 'C', '1', 'g') +#define V4L2_PIX_FMT_PISP_COMP1_BGGR v4l2_fourcc('P', 'C', '1', 'B') +#define V4L2_PIX_FMT_PISP_COMP2_RGGB v4l2_fourcc('P', 'C', '2', 'R') +#define V4L2_PIX_FMT_PISP_COMP2_GRBG v4l2_fourcc('P', 'C', '2', 'G') +#define V4L2_PIX_FMT_PISP_COMP2_GBRG v4l2_fourcc('P', 'C', '2', 'g') +#define V4L2_PIX_FMT_PISP_COMP2_BGGR v4l2_fourcc('P', 'C', '2', 'B') + /* SDR formats - used only for Software Defined Radio devices */ #define V4L2_SDR_FMT_CU8 v4l2_fourcc('C', 'U', '0', '8') /* IQ u8 */ #define V4L2_SDR_FMT_CU16LE v4l2_fourcc('C', 'U', '1', '6') /* IQ u16le */ @@ -851,6 +871,8 @@ struct v4l2_pix_format { #define V4L2_META_FMT_UVC v4l2_fourcc('U', 'V', 'C', 'H') /* UVC Payload Header metadata */ #define V4L2_META_FMT_D4XX v4l2_fourcc('D', '4', 'X', 'X') /* D4XX Payload Header metadata */ #define V4L2_META_FMT_VIVID v4l2_fourcc('V', 'I', 'V', 'D') /* Vivid Metadata */ +#define V4L2_META_FMT_SENSOR_DATA v4l2_fourcc('S', 'E', 'N', 'S') /* Sensor Ancillary metadata */ +#define V4L2_META_FMT_BCM2835_ISP_STATS v4l2_fourcc('B', 'S', 'T', 'A') /* BCM2835 ISP image statistics output */ /* Vendor specific - used for RK_ISP1 camera sub-system */ #define V4L2_META_FMT_RK_ISP1_PARAMS v4l2_fourcc('R', 'K', '1', 'P') /* Rockchip ISP1 3A Parameters */ @@ -860,6 +882,12 @@ struct v4l2_pix_format { /* Vendor specific - used for RaspberryPi PiSP */ #define V4L2_META_FMT_RPI_BE_CFG v4l2_fourcc('R', 'P', 'B', 'C') /* PiSP BE configuration */ +/* The metadata format identifier for FE configuration buffers. */ +#define V4L2_META_FMT_RPI_FE_CFG v4l2_fourcc('R', 'P', 'F', 'C') + +/* The metadata format identifier for FE configuration buffers. */ +#define V4L2_META_FMT_RPI_FE_STATS v4l2_fourcc('R', 'P', 'F', 'S') + #ifdef __KERNEL__ /* * Line-based metadata formats. Remember to update v4l_fill_fmtdesc() when diff --git a/include/uapi/misc/rp1_pio_if.h b/include/uapi/misc/rp1_pio_if.h new file mode 100644 index 00000000000000..a9a54d3322ec64 --- /dev/null +++ b/include/uapi/misc/rp1_pio_if.h @@ -0,0 +1,235 @@ +/* SPDX-License-Identifier: GPL-2.0 + WITH Linux-syscall-note */ +/* + * Copyright (c) 2023-24 Raspberry Pi Ltd. + * All rights reserved. + */ +#ifndef _PIO_RP1_IF_H +#define _PIO_RP1_IF_H + +#include <linux/ioctl.h> + +#define RP1_PIO_INSTRUCTION_COUNT 32 +#define RP1_PIO_SM_COUNT 4 +#define RP1_PIO_GPIO_COUNT 28 +#define RP1_GPIO_FUNC_PIO 7 + +#define RP1_PIO_ORIGIN_ANY ((uint16_t)(~0)) + +#define RP1_PIO_DIR_TO_SM 0 +#define RP1_PIO_DIR_FROM_SM 1 +#define RP1_PIO_DIR_COUNT 2 + +typedef struct { + uint32_t clkdiv; + uint32_t execctrl; + uint32_t shiftctrl; + uint32_t pinctrl; +} rp1_pio_sm_config; + +struct rp1_pio_add_program_args { + uint16_t num_instrs; + uint16_t origin; + uint16_t instrs[RP1_PIO_INSTRUCTION_COUNT]; +}; + +struct rp1_pio_remove_program_args { + uint16_t num_instrs; + uint16_t origin; +}; + +struct rp1_pio_sm_claim_args { + uint16_t mask; +}; + +struct rp1_pio_sm_init_args { + uint16_t sm; + uint16_t initial_pc; + rp1_pio_sm_config config; +}; + +struct rp1_pio_sm_set_config_args { + uint16_t sm; + uint16_t rsvd; + rp1_pio_sm_config config; +}; + +struct rp1_pio_sm_exec_args { + uint16_t sm; + uint16_t instr; + uint8_t blocking; + uint8_t rsvd; +}; + +struct rp1_pio_sm_clear_fifos_args { + uint16_t sm; +}; + +struct rp1_pio_sm_set_clkdiv_args { + uint16_t sm; + uint16_t div_int; + uint8_t div_frac; + uint8_t rsvd; +}; + +struct rp1_pio_sm_set_pins_args { + uint16_t sm; + uint16_t rsvd; + uint32_t values; + uint32_t mask; +}; + +struct rp1_pio_sm_set_pindirs_args { + uint16_t sm; + uint16_t rsvd; + uint32_t dirs; + uint32_t mask; +}; + +struct rp1_pio_sm_set_enabled_args { + uint16_t mask; + uint8_t enable; + uint8_t rsvd; +}; + +struct rp1_pio_sm_restart_args { + uint16_t mask; +}; + +struct rp1_pio_sm_clkdiv_restart_args { + uint16_t mask; +}; + +struct rp1_pio_sm_enable_sync_args { + uint16_t mask; +}; + +struct rp1_pio_sm_put_args { + uint16_t sm; + uint8_t blocking; + uint8_t rsvd; + uint32_t data; +}; + +struct rp1_pio_sm_get_args { + uint16_t sm; + uint8_t blocking; + uint8_t rsvd; + uint32_t data; /* OUT */ +}; + +struct rp1_pio_sm_set_dmactrl_args { + uint16_t sm; + uint8_t is_tx; + uint8_t rsvd; + uint32_t ctrl; +}; + +struct rp1_pio_sm_fifo_state_args { + uint16_t sm; + uint8_t tx; + uint8_t rsvd; + uint16_t level; /* OUT */ + uint8_t empty; /* OUT */ + uint8_t full; /* OUT */ +}; + +struct rp1_gpio_init_args { + uint16_t gpio; +}; + +struct rp1_gpio_set_function_args { + uint16_t gpio; + uint16_t fn; +}; + +struct rp1_gpio_set_pulls_args { + uint16_t gpio; + uint8_t up; + uint8_t down; +}; + +struct rp1_gpio_set_args { + uint16_t gpio; + uint16_t value; +}; + +struct rp1_pio_sm_config_xfer_args { + uint16_t sm; + uint16_t dir; + uint16_t buf_size; + uint16_t buf_count; +}; + +struct rp1_pio_sm_config_xfer32_args { + uint16_t sm; + uint16_t dir; + uint32_t buf_size; + uint32_t buf_count; +}; + +struct rp1_pio_sm_xfer_data_args { + uint16_t sm; + uint16_t dir; + uint16_t data_bytes; + void *data; +}; + +struct rp1_pio_sm_xfer_data32_args { + uint16_t sm; + uint16_t dir; + uint32_t data_bytes; + void *data; +}; + +struct rp1_access_hw_args { + uint32_t addr; + uint32_t len; + void *data; +}; + +#define PIO_IOC_MAGIC 102 + +#define PIO_IOC_SM_CONFIG_XFER _IOW(PIO_IOC_MAGIC, 0, struct rp1_pio_sm_config_xfer_args) +#define PIO_IOC_SM_XFER_DATA _IOW(PIO_IOC_MAGIC, 1, struct rp1_pio_sm_xfer_data_args) +#define PIO_IOC_SM_XFER_DATA32 _IOW(PIO_IOC_MAGIC, 2, struct rp1_pio_sm_xfer_data32_args) +#define PIO_IOC_SM_CONFIG_XFER32 _IOW(PIO_IOC_MAGIC, 3, struct rp1_pio_sm_config_xfer32_args) + +#define PIO_IOC_READ_HW _IOW(PIO_IOC_MAGIC, 8, struct rp1_access_hw_args) +#define PIO_IOC_WRITE_HW _IOW(PIO_IOC_MAGIC, 9, struct rp1_access_hw_args) + +#define PIO_IOC_CAN_ADD_PROGRAM _IOW(PIO_IOC_MAGIC, 10, struct rp1_pio_add_program_args) +#define PIO_IOC_ADD_PROGRAM _IOW(PIO_IOC_MAGIC, 11, struct rp1_pio_add_program_args) +#define PIO_IOC_REMOVE_PROGRAM _IOW(PIO_IOC_MAGIC, 12, struct rp1_pio_remove_program_args) +#define PIO_IOC_CLEAR_INSTR_MEM _IO(PIO_IOC_MAGIC, 13) + +#define PIO_IOC_SM_CLAIM _IOW(PIO_IOC_MAGIC, 20, struct rp1_pio_sm_claim_args) +#define PIO_IOC_SM_UNCLAIM _IOW(PIO_IOC_MAGIC, 21, struct rp1_pio_sm_claim_args) +#define PIO_IOC_SM_IS_CLAIMED _IOW(PIO_IOC_MAGIC, 22, struct rp1_pio_sm_claim_args) + +#define PIO_IOC_SM_INIT _IOW(PIO_IOC_MAGIC, 30, struct rp1_pio_sm_init_args) +#define PIO_IOC_SM_SET_CONFIG _IOW(PIO_IOC_MAGIC, 31, struct rp1_pio_sm_set_config_args) +#define PIO_IOC_SM_EXEC _IOW(PIO_IOC_MAGIC, 32, struct rp1_pio_sm_exec_args) +#define PIO_IOC_SM_CLEAR_FIFOS _IOW(PIO_IOC_MAGIC, 33, struct rp1_pio_sm_clear_fifos_args) +#define PIO_IOC_SM_SET_CLKDIV _IOW(PIO_IOC_MAGIC, 34, struct rp1_pio_sm_set_clkdiv_args) +#define PIO_IOC_SM_SET_PINS _IOW(PIO_IOC_MAGIC, 35, struct rp1_pio_sm_set_pins_args) +#define PIO_IOC_SM_SET_PINDIRS _IOW(PIO_IOC_MAGIC, 36, struct rp1_pio_sm_set_pindirs_args) +#define PIO_IOC_SM_SET_ENABLED _IOW(PIO_IOC_MAGIC, 37, struct rp1_pio_sm_set_enabled_args) +#define PIO_IOC_SM_RESTART _IOW(PIO_IOC_MAGIC, 38, struct rp1_pio_sm_restart_args) +#define PIO_IOC_SM_CLKDIV_RESTART _IOW(PIO_IOC_MAGIC, 39, struct rp1_pio_sm_restart_args) +#define PIO_IOC_SM_ENABLE_SYNC _IOW(PIO_IOC_MAGIC, 40, struct rp1_pio_sm_enable_sync_args) +#define PIO_IOC_SM_PUT _IOW(PIO_IOC_MAGIC, 41, struct rp1_pio_sm_put_args) +#define PIO_IOC_SM_GET _IOWR(PIO_IOC_MAGIC, 42, struct rp1_pio_sm_get_args) +#define PIO_IOC_SM_SET_DMACTRL _IOW(PIO_IOC_MAGIC, 43, struct rp1_pio_sm_set_dmactrl_args) +#define PIO_IOC_SM_FIFO_STATE _IOW(PIO_IOC_MAGIC, 44, struct rp1_pio_sm_fifo_state_args) +#define PIO_IOC_SM_DRAIN_TX _IOW(PIO_IOC_MAGIC, 45, struct rp1_pio_sm_clear_fifos_args) + +#define PIO_IOC_GPIO_INIT _IOW(PIO_IOC_MAGIC, 50, struct rp1_gpio_init_args) +#define PIO_IOC_GPIO_SET_FUNCTION _IOW(PIO_IOC_MAGIC, 51, struct rp1_gpio_set_function_args) +#define PIO_IOC_GPIO_SET_PULLS _IOW(PIO_IOC_MAGIC, 52, struct rp1_gpio_set_pulls_args) +#define PIO_IOC_GPIO_SET_OUTOVER _IOW(PIO_IOC_MAGIC, 53, struct rp1_gpio_set_args) +#define PIO_IOC_GPIO_SET_INOVER _IOW(PIO_IOC_MAGIC, 54, struct rp1_gpio_set_args) +#define PIO_IOC_GPIO_SET_OEOVER _IOW(PIO_IOC_MAGIC, 55, struct rp1_gpio_set_args) +#define PIO_IOC_GPIO_SET_INPUT_ENABLED _IOW(PIO_IOC_MAGIC, 56, struct rp1_gpio_set_args) +#define PIO_IOC_GPIO_SET_DRIVE_STRENGTH _IOW(PIO_IOC_MAGIC, 57, struct rp1_gpio_set_args) + +#endif diff --git a/kernel/cgroup/cgroup.c b/kernel/cgroup/cgroup.c index 216535e055e112..f94eb1ab8d905b 100644 --- a/kernel/cgroup/cgroup.c +++ b/kernel/cgroup/cgroup.c @@ -6852,6 +6852,39 @@ static int __init cgroup_disable(char *str) } __setup("cgroup_disable=", cgroup_disable); +static int __init cgroup_enable(char *str) +{ + struct cgroup_subsys *ss; + char *token; + int i; + + while ((token = strsep(&str, ",")) != NULL) { + if (!*token) + continue; + + for_each_subsys(ss, i) { + if (strcmp(token, ss->name) && + strcmp(token, ss->legacy_name)) + continue; + + static_branch_enable(cgroup_subsys_enabled_key[i]); + pr_info("Enabling %s control group subsystem\n", + ss->name); + } + + for (i = 0; i < OPT_FEATURE_COUNT; i++) { + if (strcmp(token, cgroup_opt_feature_names[i])) + continue; + cgroup_feature_disable_mask &= ~(1 << i); + pr_info("Enabling %s control group feature\n", + cgroup_opt_feature_names[i]); + break; + } + } + return 1; +} +__setup("cgroup_enable=", cgroup_enable); + void __init __weak enable_debug_cgroup(void) { } static int __init enable_cgroup_debug(char *str) diff --git a/kernel/resource.c b/kernel/resource.c index 4101016e8b205c..3076810b265bba 100644 --- a/kernel/resource.c +++ b/kernel/resource.c @@ -192,6 +192,12 @@ static int __release_resource(struct resource *old, bool release_child) { struct resource *tmp, **p, *chd; + if (!old->parent) { + WARN(old->sibling, "sibling but no parent"); + if (old->sibling) + return -EINVAL; + return 0; + } p = &old->parent->child; for (;;) { tmp = *p; diff --git a/lib/earlycpio.c b/lib/earlycpio.c index d2c37d64fd0c39..4b1ce69a6ee504 100644 --- a/lib/earlycpio.c +++ b/lib/earlycpio.c @@ -139,3 +139,4 @@ struct cpio_data find_cpio_data(const char *path, void *data, quit: return cd; } +EXPORT_SYMBOL_GPL(find_cpio_data); diff --git a/mm/cma.c b/mm/cma.c index 2d9fae9392835b..65d2a04ffb985e 100644 --- a/mm/cma.c +++ b/mm/cma.c @@ -602,3 +602,39 @@ int cma_for_each_area(int (*it)(struct cma *cma, void *data), void *data) return 0; } + +struct cma_check_range_data { + u64 start, end; +}; + +static int check_range(struct cma *cma_, void *data) +{ + struct cma_check_range_data *range = data; + struct cma_check_range_data cma; + bool starts_in_range; + bool ends_in_range; + + cma.start = cma_get_base(cma_); + cma.end = cma.start + cma_get_size(cma_) - 1; + + starts_in_range = cma.start >= range->start && cma.start <= range->end; + ends_in_range = cma.end >= range->start && cma.end <= range->end; + + if (starts_in_range == ends_in_range) + return 0; + + pr_notice("CMA %s [%llx-%llx] straddles range [%llx-%llx]\n", + cma_->name, cma.start, cma.end, range->start, range->end); + + return -EINVAL; +} + +int cma_check_range(u64 *start, u64 *end) +{ + struct cma_check_range_data range = { + .start = *start, + .end = *end, + }; + + return cma_for_each_area(check_range, &range); +} diff --git a/mm/mempolicy.c b/mm/mempolicy.c index 7b908c4cc7eecb..1fa15d85358d5b 100644 --- a/mm/mempolicy.c +++ b/mm/mempolicy.c @@ -41,6 +41,9 @@ * preferred many Try a set of nodes first before normal fallback. This is * similar to preferred without the special case. * + * random Allocate memory from a random node out of allowed set of + * nodes. + * * default Allocate on the local node first, or when on a VMA * use the process policy. This is what Linux always did * in a NUMA aware kernel and still does by, ahem, default. @@ -452,6 +455,10 @@ static const struct mempolicy_operations mpol_ops[MPOL_MAX] = { .create = mpol_new_nodemask, .rebind = mpol_rebind_nodemask, }, + [MPOL_RANDOM] = { + .create = mpol_new_nodemask, + .rebind = mpol_rebind_nodemask, + }, }; static bool migrate_folio_add(struct folio *folio, struct list_head *foliolist, @@ -900,6 +907,7 @@ static void get_policy_nodemask(struct mempolicy *pol, nodemask_t *nodes) case MPOL_PREFERRED: case MPOL_PREFERRED_MANY: case MPOL_WEIGHTED_INTERLEAVE: + case MPOL_RANDOM: *nodes = pol->nodes; break; case MPOL_LOCAL: @@ -1917,6 +1925,27 @@ static unsigned int interleave_nodes(struct mempolicy *policy) return nid; } +static unsigned int read_once_policy_nodemask(struct mempolicy *pol, nodemask_t *mask); + +static unsigned int random_nodes(struct mempolicy *policy) +{ + unsigned int nid = first_node(policy->nodes); + unsigned int cpuset_mems_cookie; + nodemask_t nodemask; + unsigned int r; + + r = get_random_u32_below(read_once_policy_nodemask(policy, &nodemask)); + + /* to prevent miscount, use tsk->mems_allowed_seq to detect rebind */ + do { + cpuset_mems_cookie = read_mems_allowed_begin(); + while (r--) + nid = next_node_in(nid, policy->nodes); + } while (read_mems_allowed_retry(cpuset_mems_cookie)); + + return nid; +} + /* * Depending on the memory policy provide a node from which to allocate the * next slab entry. @@ -1962,6 +1991,9 @@ unsigned int mempolicy_slab_node(void) case MPOL_LOCAL: return node; + case MPOL_RANDOM: + return random_nodes(policy); + default: BUG(); } @@ -2042,6 +2074,33 @@ static unsigned int interleave_nid(struct mempolicy *pol, pgoff_t ilx) return nid; } +static unsigned int random_nid(struct mempolicy *pol, + struct vm_area_struct *vma, + pgoff_t ilx) +{ + nodemask_t nodemask; + unsigned int r, nnodes; + int i, nid; + + nnodes = read_once_policy_nodemask(pol, &nodemask); + if (!nnodes) + return numa_node_id(); + + /* + * QQQ + * Can we say hash of vma+ilx is sufficiently random but still + * stable in case of reliance on stable, as it appears is with + * mpol_misplaced and interleaving? + */ + r = hash_long((unsigned long)vma + ilx, + ilog2(roundup_pow_of_two(nnodes))); + + nid = first_node(nodemask); + for (i = 0; i < r; i++) + nid = next_node(nid, nodemask); + return nid; +} + /* * Return a nodemask representing a mempolicy for filtering nodes for * page allocation, together with preferred node id (or the input node id). @@ -2085,11 +2144,20 @@ static nodemask_t *policy_nodemask(gfp_t gfp, struct mempolicy *pol, weighted_interleave_nodes(pol) : weighted_interleave_nid(pol, ilx); break; + case MPOL_RANDOM: + *nid = random_nodes(pol); + break; } return nodemask; } +nodemask_t *numa_policy_nodemask(gfp_t gfp, struct mempolicy *pol, pgoff_t ilx, + int *nid) +{ + return policy_nodemask(gfp, pol, ilx, nid); +} + #ifdef CONFIG_HUGETLBFS /* * huge_node(@vma, @addr, @gfp_flags, @mpol) @@ -2147,6 +2215,7 @@ bool init_nodemask_of_mempolicy(nodemask_t *mask) case MPOL_BIND: case MPOL_INTERLEAVE: case MPOL_WEIGHTED_INTERLEAVE: + case MPOL_RANDOM: *mask = mempolicy->nodes; break; @@ -2627,6 +2696,7 @@ bool __mpol_equal(struct mempolicy *a, struct mempolicy *b) case MPOL_PREFERRED: case MPOL_PREFERRED_MANY: case MPOL_WEIGHTED_INTERLEAVE: + case MPOL_RANDOM: return !!nodes_equal(a->nodes, b->nodes); case MPOL_LOCAL: return true; @@ -2818,6 +2888,10 @@ int mpol_misplaced(struct folio *folio, struct vm_fault *vmf, polnid = zonelist_node_idx(z); break; + case MPOL_RANDOM: + polnid = random_nid(pol, vma, ilx); + break; + default: BUG(); } @@ -3146,7 +3220,9 @@ void __init numa_policy_init(void) /* Reset policy of current process to default */ void numa_default_policy(void) { - do_set_mempolicy(MPOL_DEFAULT, 0, NULL); + struct mempolicy *pol = &default_policy; + + do_set_mempolicy(pol->mode, pol->flags, &pol->nodes); } /* @@ -3161,9 +3237,9 @@ static const char * const policy_modes[] = [MPOL_WEIGHTED_INTERLEAVE] = "weighted interleave", [MPOL_LOCAL] = "local", [MPOL_PREFERRED_MANY] = "prefer (many)", + [MPOL_RANDOM] = "random", }; -#ifdef CONFIG_TMPFS /** * mpol_parse_str - parse string to mempolicy, for tmpfs mpol mount option. * @str: string containing mempolicy to parse @@ -3176,13 +3252,18 @@ static const char * const policy_modes[] = */ int mpol_parse_str(char *str, struct mempolicy **mpol) { - struct mempolicy *new = NULL; + struct mempolicy *new; unsigned short mode_flags; nodemask_t nodes; char *nodelist = strchr(str, ':'); char *flags = strchr(str, '='); int err = 1, mode; + if (*mpol) + new = *mpol; + else + new = NULL; + if (flags) *flags++ = '\0'; /* terminate mode string */ @@ -3219,6 +3300,7 @@ int mpol_parse_str(char *str, struct mempolicy **mpol) break; case MPOL_INTERLEAVE: case MPOL_WEIGHTED_INTERLEAVE: + case MPOL_RANDOM: /* * Default to online nodes with memory if no nodelist */ @@ -3262,9 +3344,16 @@ int mpol_parse_str(char *str, struct mempolicy **mpol) goto out; } - new = mpol_new(mode, mode_flags, &nodes); - if (IS_ERR(new)) - goto out; + if (!new) { + new = mpol_new(mode, mode_flags, &nodes); + if (IS_ERR(new)) + goto out; + } else { + atomic_set(&new->refcnt, 1); + new->mode = mode; + new->flags = mode_flags; + new->home_node = NUMA_NO_NODE; + } /* * Save nodes for mpol_to_str() to show the tmpfs mount options @@ -3297,7 +3386,29 @@ int mpol_parse_str(char *str, struct mempolicy **mpol) *mpol = new; return err; } -#endif /* CONFIG_TMPFS */ + +static int __init setup_numapolicy(char *str) +{ + struct mempolicy pol = { }, *ppol = &pol; + char buf[128]; + int ret; + + if (str) + ret = mpol_parse_str(str, &ppol); + else + ret = -EINVAL; + + if (!ret) { + default_policy = pol; + mpol_to_str(buf, sizeof(buf), &pol); + pr_info("NUMA default policy overridden to '%s'\n", buf); + } else { + pr_warn("Unable to parse numa_policy=\n"); + } + + return ret == 0; +} +__setup("numa_policy=", setup_numapolicy); /** * mpol_to_str - format a mempolicy structure for printing @@ -3334,6 +3445,7 @@ void mpol_to_str(char *buffer, int maxlen, struct mempolicy *pol) case MPOL_BIND: case MPOL_INTERLEAVE: case MPOL_WEIGHTED_INTERLEAVE: + case MPOL_RANDOM: nodes = pol->nodes; break; default: diff --git a/mm/numa_emulation.c b/mm/numa_emulation.c index 031fb9961bf7b2..ef43d8b571c61f 100644 --- a/mm/numa_emulation.c +++ b/mm/numa_emulation.c @@ -7,6 +7,7 @@ #include <linux/topology.h> #include <linux/memblock.h> #include <linux/numa_memblks.h> +#include <linux/cma.h> #include <asm/numa.h> #define FAKE_NODE_MIN_SIZE ((u64)32 << 20) @@ -51,6 +52,7 @@ static int __init emu_setup_memblk(struct numa_meminfo *ei, { struct numa_memblk *eb = &ei->blk[ei->nr_blks]; struct numa_memblk *pb = &pi->blk[phys_blk]; + int ret; if (ei->nr_blks >= NR_NODE_MEMBLKS) { pr_err("NUMA: Too many emulated memblks, failing emulation\n"); @@ -62,6 +64,10 @@ static int __init emu_setup_memblk(struct numa_meminfo *ei, eb->end = pb->start + size; eb->nid = nid; + ret = cma_check_range(&eb->start, &eb->end); + if (ret) + return ret; + if (emu_nid_to_phys[nid] == NUMA_NO_NODE) emu_nid_to_phys[nid] = pb->nid; diff --git a/mm/page_alloc.c b/mm/page_alloc.c index de65e8b4f75f21..defc09d84de96c 100644 --- a/mm/page_alloc.c +++ b/mm/page_alloc.c @@ -207,6 +207,27 @@ EXPORT_SYMBOL(node_states); gfp_t gfp_allowed_mask __read_mostly = GFP_BOOT_MASK; +#define ALLOC_IN_CMA_THRESHOLD_MAX 16 +#define ALLOC_IN_CMA_THRESHOLD_DEFAULT 12 + +static unsigned long _alloc_in_cma_threshold __read_mostly + = ALLOC_IN_CMA_THRESHOLD_DEFAULT; + +static int __init alloc_in_cma_threshold_setup(char *buf) +{ + unsigned long res; + + if (kstrtoul(buf, 10, &res) < 0 || + res > ALLOC_IN_CMA_THRESHOLD_MAX) { + pr_err("Bad alloc_cma_threshold value\n"); + return 0; + } + _alloc_in_cma_threshold = res; + pr_info("Setting alloc_in_cma_threshold to %lu\n", res); + return 0; +} +early_param("alloc_in_cma_threshold", alloc_in_cma_threshold_setup); + #ifdef CONFIG_HUGETLB_PAGE_SIZE_VARIABLE unsigned int pageblock_order __read_mostly; #endif @@ -2270,12 +2291,13 @@ __rmqueue(struct zone *zone, unsigned int order, int migratetype, if (IS_ENABLED(CONFIG_CMA)) { /* * Balance movable allocations between regular and CMA areas by - * allocating from CMA when over half of the zone's free memory - * is in the CMA area. + * allocating from CMA when over more than a given proportion of + * the zone's free memory is in the CMA area. */ if (alloc_flags & ALLOC_CMA && zone_page_state(zone, NR_FREE_CMA_PAGES) > - zone_page_state(zone, NR_FREE_PAGES) / 2) { + zone_page_state(zone, NR_FREE_PAGES) / ALLOC_IN_CMA_THRESHOLD_MAX + * _alloc_in_cma_threshold) { page = __rmqueue_cma_fallback(zone, order); if (page) return page; diff --git a/mm/vmscan.c b/mm/vmscan.c index 77d015d5db0c5b..b5b9b0b8de1090 100644 --- a/mm/vmscan.c +++ b/mm/vmscan.c @@ -4121,7 +4121,7 @@ bool lru_gen_look_around(struct page_vma_mapped_walk *pvmw) if (!folio) continue; - if (!ptep_clear_young_notify(vma, addr, pte + i)) + if (!ptep_clear_flush_young_notify(vma, addr, pte + i)) continue; young++; diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index 7b2b04d6b85630..8b4a7ec5a7b14a 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -4862,6 +4862,8 @@ static const struct { */ static int hci_dev_setup_sync(struct hci_dev *hdev) { + struct fwnode_handle *fwnode = + hdev->dev.parent ? dev_fwnode(hdev->dev.parent) : NULL; int ret = 0; bool invalid_bdaddr; size_t i; @@ -4890,7 +4892,8 @@ static int hci_dev_setup_sync(struct hci_dev *hdev) test_bit(HCI_QUIRK_USE_BDADDR_PROPERTY, &hdev->quirks); if (!ret) { if (test_bit(HCI_QUIRK_USE_BDADDR_PROPERTY, &hdev->quirks) && - !bacmp(&hdev->public_addr, BDADDR_ANY)) + !bacmp(&hdev->public_addr, BDADDR_ANY) && + (invalid_bdaddr || !fwnode_property_present(fwnode, "fallback-bd-address"))) hci_dev_get_bd_addr_from_property(hdev); if (invalid_bdaddr && bacmp(&hdev->public_addr, BDADDR_ANY) && diff --git a/net/wireless/certs/debian.hex b/net/wireless/certs/debian.hex new file mode 100644 index 00000000000000..c5ab03f8c500d2 --- /dev/null +++ b/net/wireless/certs/debian.hex @@ -0,0 +1,1426 @@ +0x30, +0x82, +0x02, +0xbd, +0x30, +0x82, +0x01, +0xa5, +0x02, +0x14, +0x57, +0x7e, +0x02, +0x1c, +0xb9, +0x80, +0xe0, +0xe8, +0x20, +0x82, +0x1b, +0xa7, +0xb5, +0x4b, +0x49, +0x61, +0xb8, +0xb4, +0xfa, +0xdf, +0x30, +0x0d, +0x06, +0x09, +0x2a, +0x86, +0x48, +0x86, +0xf7, +0x0d, +0x01, +0x01, +0x0b, +0x05, +0x00, +0x30, +0x1a, +0x31, +0x18, +0x30, +0x16, +0x06, +0x03, +0x55, +0x04, +0x03, +0x0c, +0x0f, +0x62, +0x65, +0x6e, +0x68, +0x40, +0x64, +0x65, +0x62, +0x69, +0x61, +0x6e, +0x2e, +0x6f, +0x72, +0x67, +0x30, +0x20, +0x17, +0x0d, +0x32, +0x30, +0x30, +0x31, +0x33, +0x30, +0x31, +0x33, +0x32, +0x36, +0x31, +0x33, +0x5a, +0x18, +0x0f, +0x32, +0x31, +0x32, +0x30, +0x30, +0x31, +0x30, +0x36, +0x31, +0x33, +0x32, +0x36, +0x31, +0x33, +0x5a, +0x30, +0x1a, +0x31, +0x18, +0x30, +0x16, +0x06, +0x03, +0x55, +0x04, +0x03, +0x0c, +0x0f, +0x62, +0x65, +0x6e, +0x68, +0x40, +0x64, +0x65, +0x62, +0x69, +0x61, +0x6e, +0x2e, +0x6f, +0x72, +0x67, +0x30, +0x82, +0x01, +0x22, +0x30, +0x0d, +0x06, +0x09, +0x2a, +0x86, +0x48, +0x86, +0xf7, +0x0d, +0x01, +0x01, +0x01, +0x05, +0x00, +0x03, +0x82, +0x01, +0x0f, +0x00, +0x30, +0x82, +0x01, +0x0a, +0x02, +0x82, +0x01, +0x01, +0x00, +0x9d, +0xe1, +0x77, +0xa0, +0x24, +0xa0, +0xd5, +0x79, +0x65, +0x3a, +0x07, +0x90, +0xc9, +0xf6, +0xa5, +0xa6, +0x1f, +0x84, +0x1c, +0x23, +0x07, +0x4b, +0x4f, +0xa5, +0x03, +0xc6, +0x0f, +0xf7, +0x54, +0xd5, +0x8b, +0x7e, +0x79, +0x81, +0x00, +0xd2, +0xe9, +0x3d, +0xf4, +0x97, +0xfe, +0x84, +0xcd, +0x55, +0xbd, +0xc9, +0x8f, +0x21, +0x57, +0x88, +0x06, +0x39, +0x90, +0x66, +0x41, +0x26, +0x79, +0x2c, +0xca, +0x3f, +0x95, +0x87, +0x01, +0x11, +0x2f, +0x2f, +0xb0, +0xe1, +0x0b, +0x43, +0xfc, +0x5f, +0x2f, +0x4f, +0x67, +0x04, +0xdb, +0x4d, +0xb7, +0x72, +0x4d, +0xd1, +0xc5, +0x76, +0x73, +0x4d, +0x91, +0x69, +0xb0, +0x71, +0x17, +0x36, +0xea, +0xab, +0x0a, +0x3a, +0xcd, +0x95, +0x9b, +0x76, +0x1b, +0x8e, +0x21, +0x17, +0x8f, +0xc5, +0x02, +0xbf, +0x24, +0xc7, +0xc0, +0x40, +0xb1, +0x3b, +0xc4, +0x80, +0x7c, +0x71, +0xa5, +0x51, +0xdc, +0xf7, +0x3a, +0x58, +0x7f, +0xb1, +0x07, +0x81, +0x8a, +0x10, +0xd1, +0xf6, +0x93, +0x17, +0x71, +0xe0, +0xfa, +0x51, +0x79, +0x15, +0xd4, +0xd7, +0x8f, +0xad, +0xbd, +0x6f, +0x38, +0xe1, +0x26, +0x7d, +0xbc, +0xf0, +0x3e, +0x80, +0x89, +0xb4, +0xec, +0x8e, +0x69, +0x90, +0xdb, +0x97, +0x8a, +0xf0, +0x23, +0x23, +0x83, +0x82, +0x3b, +0x6a, +0xb1, +0xac, +0xeb, +0xe7, +0x99, +0x74, +0x2a, +0x35, +0x8e, +0xa9, +0x64, +0xfd, +0x46, +0x9e, +0xe8, +0xe5, +0x48, +0x61, +0x31, +0x6e, +0xe6, +0xfc, +0x19, +0x18, +0x54, +0xc3, +0x1b, +0x4f, +0xd6, +0x00, +0x44, +0x87, +0x1c, +0x37, +0x45, +0xea, +0xf5, +0xc9, +0xcb, +0x0f, +0x0c, +0x55, +0xec, +0xcf, +0x6a, +0xc2, +0x45, +0x26, +0x23, +0xa2, +0x31, +0x52, +0x4d, +0xee, +0x21, +0x7d, +0xfd, +0x58, +0x72, +0xc2, +0x28, +0xc5, +0x8e, +0xa9, +0xd0, +0xee, +0x01, +0x77, +0x08, +0xa5, +0xf0, +0x22, +0x2b, +0x47, +0x79, +0x2b, +0xcf, +0x9a, +0x46, +0xb5, +0x8f, +0xfd, +0x64, +0xa2, +0xb5, +0xed, +0x02, +0x03, +0x01, +0x00, +0x01, +0x30, +0x0d, +0x06, +0x09, +0x2a, +0x86, +0x48, +0x86, +0xf7, +0x0d, +0x01, +0x01, +0x0b, +0x05, +0x00, +0x03, +0x82, +0x01, +0x01, +0x00, +0x20, +0x44, +0xfe, +0xa9, +0x9e, +0xdd, +0x9b, +0xea, +0xce, +0x25, +0x75, +0x08, +0xf0, +0x2b, +0x53, +0xf7, +0x5a, +0x36, +0x1c, +0x4a, +0x23, +0x7f, +0xd0, +0x41, +0x3c, +0x12, +0x2b, +0xb9, +0x80, +0x4e, +0x8a, +0x15, +0x5d, +0x1f, +0x40, +0xa7, +0x26, +0x28, +0x32, +0xc3, +0x5b, +0x06, +0x28, +0x2d, +0x3d, +0x08, +0x09, +0x1e, +0x01, +0xe9, +0x67, +0xe3, +0x33, +0xe6, +0x15, +0x45, +0x39, +0xee, +0x17, +0x83, +0xdb, +0x42, +0xff, +0x7f, +0x35, +0xf4, +0xac, +0x16, +0xdb, +0xba, +0xb8, +0x1a, +0x20, +0x21, +0x41, +0xff, +0xf3, +0x92, +0xff, +0x65, +0x6e, +0x29, +0x16, +0xd0, +0xbf, +0x8d, +0xdf, +0x48, +0x2c, +0x73, +0x36, +0x7f, +0x22, +0xe6, +0xee, +0x78, +0xb4, +0x63, +0x83, +0x0e, +0x39, +0xeb, +0xaf, +0x10, +0x2a, +0x90, +0xd3, +0xfc, +0xe6, +0xc3, +0x8f, +0x97, +0x5b, +0x76, +0xbf, +0x9b, +0xf5, +0x98, +0xd2, +0x53, +0x06, +0x8b, +0xf8, +0xa4, +0x04, +0x9b, +0x1b, +0x62, +0x6a, +0x9d, +0xac, +0xe6, +0x4b, +0x0d, +0xc9, +0xd7, +0x56, +0x63, +0x15, +0x01, +0x38, +0x8c, +0xbe, +0xf1, +0x44, +0xc4, +0x38, +0x27, +0xe0, +0xcf, +0x72, +0xd6, +0x3d, +0xe4, +0xf7, +0x4b, +0x3b, +0xd2, +0xb1, +0x0c, +0xd5, +0x83, +0x6d, +0x1e, +0x10, +0x04, +0x69, +0x29, +0x88, +0x69, +0xe0, +0x7d, +0xd7, +0xdb, +0xb4, +0x59, +0x72, +0x8d, +0x9d, +0x3c, +0x43, +0xaf, +0xc6, +0x7d, +0xb7, +0x21, +0x15, +0x52, +0x8a, +0xe9, +0x9b, +0x6b, +0x2e, +0xe8, +0x27, +0x3c, +0x3f, +0x2d, +0x84, +0xfb, +0x9a, +0x22, +0x0a, +0x9f, +0x6a, +0x25, +0xe6, +0x39, +0xe4, +0x74, +0x73, +0xb6, +0x2a, +0x70, +0xaa, +0x1d, +0xcb, +0xcc, +0xd4, +0xa0, +0x1b, +0x26, +0x71, +0x63, +0x04, +0xc5, +0x12, +0x21, +0x48, +0xba, +0x92, +0x27, +0x06, +0xa8, +0x3e, +0x6d, +0xa1, +0x43, +0xa5, +0xd2, +0x2a, +0xf7, +0xca, +0xc4, +0x26, +0xe8, +0x5b, +0x1f, +0xe4, +0xdc, +0x89, +0xdc, +0x1f, +0x04, +0x79, +0x3f, +0x30, +0x82, +0x02, +0xcd, +0x30, +0x82, +0x01, +0xb5, +0x02, +0x14, +0x3a, +0xbb, +0xc6, +0xec, +0x14, +0x6e, +0x09, +0xd1, +0xb6, +0x01, +0x6a, +0xb9, +0xd6, +0xcf, +0x71, +0xdd, +0x23, +0x3f, +0x03, +0x28, +0x30, +0x0d, +0x06, +0x09, +0x2a, +0x86, +0x48, +0x86, +0xf7, +0x0d, +0x01, +0x01, +0x0b, +0x05, +0x00, +0x30, +0x22, +0x31, +0x20, +0x30, +0x1e, +0x06, +0x03, +0x55, +0x04, +0x03, +0x0c, +0x17, +0x72, +0x6f, +0x6d, +0x61, +0x69, +0x6e, +0x2e, +0x70, +0x65, +0x72, +0x69, +0x65, +0x72, +0x40, +0x67, +0x6d, +0x61, +0x69, +0x6c, +0x2e, +0x63, +0x6f, +0x6d, +0x30, +0x20, +0x17, +0x0d, +0x32, +0x30, +0x30, +0x32, +0x32, +0x34, +0x31, +0x39, +0x30, +0x31, +0x34, +0x34, +0x5a, +0x18, +0x0f, +0x32, +0x31, +0x32, +0x30, +0x30, +0x31, +0x33, +0x31, +0x31, +0x39, +0x30, +0x31, +0x34, +0x34, +0x5a, +0x30, +0x22, +0x31, +0x20, +0x30, +0x1e, +0x06, +0x03, +0x55, +0x04, +0x03, +0x0c, +0x17, +0x72, +0x6f, +0x6d, +0x61, +0x69, +0x6e, +0x2e, +0x70, +0x65, +0x72, +0x69, +0x65, +0x72, +0x40, +0x67, +0x6d, +0x61, +0x69, +0x6c, +0x2e, +0x63, +0x6f, +0x6d, +0x30, +0x82, +0x01, +0x22, +0x30, +0x0d, +0x06, +0x09, +0x2a, +0x86, +0x48, +0x86, +0xf7, +0x0d, +0x01, +0x01, +0x01, +0x05, +0x00, +0x03, +0x82, +0x01, +0x0f, +0x00, +0x30, +0x82, +0x01, +0x0a, +0x02, +0x82, +0x01, +0x01, +0x00, +0xf0, +0xb8, +0x4f, +0x3f, +0x70, +0x78, +0xf8, +0x74, +0x45, +0xa2, +0x28, +0xaf, +0x04, +0x75, +0x04, +0xa3, +0xf3, +0xa7, +0xc7, +0x04, +0xac, +0xb6, +0xe1, +0xfc, +0xe1, +0xc0, +0x3d, +0xe0, +0x26, +0x90, +0x8a, +0x45, +0x60, +0xc4, +0x75, +0xf3, +0x1a, +0x33, +0x37, +0x56, +0x7d, +0x30, +0x07, +0x75, +0x0e, +0xa6, +0x79, +0x06, +0x95, +0x9d, +0x17, +0x3c, +0x09, +0xa9, +0x7f, +0xab, +0x95, +0x5d, +0xed, +0xe0, +0x75, +0x26, +0x2f, +0x65, +0x65, +0xcd, +0x61, +0xb1, +0x33, +0x27, +0x67, +0x41, +0xa1, +0x01, +0x13, +0xe9, +0x13, +0x6a, +0x6d, +0x4e, +0x98, +0xe1, +0x9e, +0x7b, +0x0b, +0x5b, +0x44, +0xef, +0x68, +0x5a, +0x6f, +0x7d, +0x97, +0xa1, +0x33, +0x22, +0x97, +0x12, +0x21, +0x09, +0x8f, +0x90, +0xe0, +0x25, +0x94, +0xdd, +0x8a, +0x3a, +0xf7, +0x4a, +0x60, +0x04, +0x26, +0x6d, +0x00, +0x82, +0xe4, +0xcf, +0x64, +0x1c, +0x79, +0x15, +0x24, +0xf2, +0x42, +0x86, +0xf5, +0x10, +0x86, +0xac, +0x20, +0x88, +0x90, +0x87, +0xdf, +0x8c, +0x37, +0x7c, +0xbf, +0x35, +0xd5, +0x6f, +0x9f, +0x77, +0xc3, +0xcd, +0x69, +0x25, +0x06, +0xc2, +0x65, +0x51, +0x71, +0x89, +0x7f, +0x6e, +0x4d, +0xe5, +0xd5, +0x8a, +0x36, +0x1a, +0xad, +0xc1, +0x18, +0xd6, +0x14, +0x42, +0x87, +0xf0, +0x93, +0x83, +0xf1, +0x99, +0x74, +0xc4, +0x13, +0xaa, +0x3b, +0x66, +0x85, +0x6f, +0xe0, +0xbc, +0x5f, +0xb6, +0x40, +0xa6, +0x41, +0x06, +0x0a, +0xba, +0x0e, +0xe9, +0x32, +0x44, +0x10, +0x39, +0x53, +0xcd, +0xbf, +0xf3, +0xd3, +0x26, +0xf6, +0xb6, +0x2b, +0x40, +0x2e, +0xb9, +0x88, +0xc1, +0xf4, +0xe3, +0xa0, +0x28, +0x77, +0x4f, +0xba, +0xa8, +0xca, +0x9c, +0x05, +0xba, +0x88, +0x96, +0x99, +0x54, +0x89, +0xa2, +0x8d, +0xf3, +0x73, +0xa1, +0x8c, +0x4a, +0xa8, +0x71, +0xee, +0x2e, +0xd2, +0x83, +0x14, +0x48, +0xbd, +0x98, +0xc6, +0xce, +0xdc, +0xa8, +0xa3, +0x97, +0x2e, +0x40, +0x16, +0x2f, +0x02, +0x03, +0x01, +0x00, +0x01, +0x30, +0x0d, +0x06, +0x09, +0x2a, +0x86, +0x48, +0x86, +0xf7, +0x0d, +0x01, +0x01, +0x0b, +0x05, +0x00, +0x03, +0x82, +0x01, +0x01, +0x00, +0x76, +0x5d, +0x03, +0x3d, +0xb6, +0x96, +0x00, +0x1b, +0x6e, +0x0c, +0xdd, +0xbb, +0xc8, +0xdf, +0xbc, +0xeb, +0x6c, +0x01, +0x40, +0x1a, +0x2b, +0x07, +0x60, +0xa1, +0x1a, +0xe1, +0x43, +0x57, +0xfa, +0xbe, +0xde, +0xbb, +0x8f, +0x73, +0xf3, +0x92, +0xa2, +0xaa, +0x83, +0x01, +0xc1, +0x17, +0xe4, +0x9d, +0x09, +0x41, +0xe0, +0x32, +0x33, +0x97, +0x4b, +0xf2, +0xdc, +0x0f, +0x8b, +0xa8, +0xb8, +0x5a, +0x04, +0x86, +0xf6, +0x71, +0xa1, +0x97, +0xd0, +0x54, +0x56, +0x10, +0x8e, +0x54, +0x99, +0x0d, +0x2a, +0xa9, +0xaf, +0x1b, +0x55, +0x59, +0x06, +0x2b, +0xa4, +0x5f, +0xb1, +0x54, +0xa6, +0xec, +0xc7, +0xd6, +0x43, +0xee, +0x86, +0x2c, +0x9b, +0x18, +0x9d, +0x8f, +0x00, +0x82, +0xc1, +0x88, +0x61, +0x16, +0x85, +0x3c, +0x17, +0x56, +0xfe, +0x6a, +0xa0, +0x7a, +0x68, +0xc5, +0x7b, +0x3d, +0x3c, +0xb6, +0x13, +0x18, +0x99, +0x6d, +0x74, +0x65, +0x13, +0x67, +0xb7, +0xfc, +0x5a, +0x44, +0x48, +0x72, +0xa0, +0x73, +0xb8, +0xff, +0x02, +0x9d, +0x7c, +0x5b, +0xf9, +0x7c, +0x75, +0x0a, +0x3c, +0x81, +0x80, +0x3c, +0x41, +0xf2, +0xd5, +0xfa, +0x3d, +0x1f, +0xe3, +0xda, +0x8c, +0xa5, +0x17, +0x1f, +0x53, +0x1a, +0x75, +0xad, +0x4e, +0x11, +0x1c, +0x07, +0xec, +0x0a, +0x69, +0xfd, +0x33, +0xfa, +0x32, +0x7e, +0x66, +0xf5, +0x29, +0xe8, +0x4d, +0x8a, +0xfa, +0x0d, +0x4b, +0x68, +0xc3, +0x95, +0x11, +0xba, +0x6f, +0x1e, +0x07, +0x8c, +0x85, +0xc7, +0xc7, +0xc9, +0xc1, +0x30, +0xa3, +0x70, +0xb0, +0xa1, +0xe0, +0xd5, +0x85, +0x15, +0x94, +0x77, +0xc1, +0x1c, +0x91, +0xf1, +0x5f, +0x50, +0xcd, +0x2c, +0x57, +0x4b, +0x22, +0x4f, +0xee, +0x95, +0xd7, +0xa7, +0xa4, +0x59, +0x62, +0xae, +0xb9, +0xbf, +0xd7, +0x63, +0x5a, +0x04, +0xfc, +0x24, +0x11, +0xae, +0x34, +0x4b, +0xf4, +0x0c, +0x9f, +0x0b, +0x59, +0x7d, +0x27, +0x39, +0x54, +0x69, +0x4f, +0xfd, +0x6e, +0x44, +0x9f, +0x21, diff --git a/scripts/Makefile.build b/scripts/Makefile.build index 880785b52c04ad..c2a55059fb5c6c 100644 --- a/scripts/Makefile.build +++ b/scripts/Makefile.build @@ -41,6 +41,20 @@ include $(srctree)/scripts/Makefile.compiler include $(kbuild-file) include $(srctree)/scripts/Makefile.lib +# Do not include hostprogs rules unless needed. +# $(sort ...) is used here to remove duplicated words and excessive spaces. +hostprogs := $(sort $(hostprogs)) +ifneq ($(hostprogs),) +include $(srctree)/scripts/Makefile.host +endif + +# Do not include userprogs rules unless needed. +# $(sort ...) is used here to remove duplicated words and excessive spaces. +userprogs := $(sort $(userprogs)) +ifneq ($(userprogs),) +include $(srctree)/scripts/Makefile.userprogs +endif + ifndef obj $(warning kbuild: Makefile.build is included improperly) endif @@ -57,6 +71,7 @@ endif # subdir-builtin and subdir-modorder may contain duplications. Use $(sort ...) subdir-builtin := $(sort $(filter %/built-in.a, $(real-obj-y))) subdir-modorder := $(sort $(filter %/modules.order, $(obj-m))) +subdir-dtbslist := $(sort $(filter %/dtbs-list, $(dtb-y))) targets-for-builtin := $(extra-y) @@ -273,15 +288,10 @@ rust_common_cmd = \ # would not match each other. quiet_cmd_rustc_o_rs = $(RUSTC_OR_CLIPPY_QUIET) $(quiet_modtag) $@ - cmd_rustc_o_rs = $(rust_common_cmd) --emit=obj=$@ $< $(cmd_objtool) - -define rule_rustc_o_rs - $(call cmd_and_fixdep,rustc_o_rs) - $(call cmd,gen_objtooldep) -endef + cmd_rustc_o_rs = $(rust_common_cmd) --emit=obj=$@ $< $(obj)/%.o: $(obj)/%.rs FORCE - +$(call if_changed_rule,rustc_o_rs) + +$(call if_changed_dep,rustc_o_rs) quiet_cmd_rustc_rsi_rs = $(RUSTC_OR_CLIPPY_QUIET) $(quiet_modtag) $@ cmd_rustc_rsi_rs = \ @@ -353,7 +363,7 @@ $(obj)/%.o: $(obj)/%.S FORCE targets += $(filter-out $(subdir-builtin), $(real-obj-y)) targets += $(filter-out $(subdir-modorder), $(real-obj-m)) -targets += $(lib-y) $(always-y) +targets += $(real-dtb-y) $(lib-y) $(always-y) # Linker scripts preprocessor (.lds.S -> .lds) # --------------------------------------------------------------------------- @@ -379,6 +389,7 @@ $(obj)/%.asn1.c $(obj)/%.asn1.h: $(src)/%.asn1 $(objtree)/scripts/asn1_compiler # To build objects in subdirs, we need to descend into the directories $(subdir-builtin): $(obj)/%/built-in.a: $(obj)/% ; $(subdir-modorder): $(obj)/%/modules.order: $(obj)/% ; +$(subdir-dtbslist): $(obj)/%/dtbs-list: $(obj)/% ; # # Rule to compile a set of .o files into one .a file (without symbol table) @@ -394,8 +405,12 @@ quiet_cmd_ar_builtin = AR $@ $(obj)/built-in.a: $(real-obj-y) FORCE $(call if_changed,ar_builtin) -# This is a list of build artifacts from the current Makefile and its -# sub-directories. The timestamp should be updated when any of the member files. +# +# Rule to create modules.order and dtbs-list +# +# This is a list of build artifacts (module or dtb) from the current Makefile +# and its sub-directories. The timestamp should be updated when any of the +# member files. cmd_gen_order = { $(foreach m, $(real-prereqs), \ $(if $(filter %/$(notdir $@), $m), cat $m, echo $m);) :; } \ @@ -404,6 +419,9 @@ cmd_gen_order = { $(foreach m, $(real-prereqs), \ $(obj)/modules.order: $(obj-m) FORCE $(call if_changed,gen_order) +$(obj)/dtbs-list: $(dtb-y) $(dtbo-y) FORCE + $(call if_changed,gen_order) + # # Rule to compile a set of .o files into one .a file (with symbol table) # @@ -432,26 +450,15 @@ intermediate_targets = $(foreach sfx, $(2), \ $(patsubst %$(strip $(1)),%$(sfx), \ $(filter %$(strip $(1)), $(targets)))) # %.asn1.o <- %.asn1.[ch] <- %.asn1 -targets += $(call intermediate_targets, .asn1.o, .asn1.c .asn1.h) - -# Include additional build rules when necessary -# --------------------------------------------------------------------------- - -# $(sort ...) is used here to remove duplicated words and excessive spaces. -hostprogs := $(sort $(hostprogs)) -ifneq ($(hostprogs),) -include $(srctree)/scripts/Makefile.host -endif - -# $(sort ...) is used here to remove duplicated words and excessive spaces. -userprogs := $(sort $(userprogs)) -ifneq ($(userprogs),) -include $(srctree)/scripts/Makefile.userprogs -endif - -ifneq ($(need-dtbslist)$(dtb-y)$(dtb-)$(filter %.dtb %.dtb.o %.dtbo.o,$(targets)),) -include $(srctree)/scripts/Makefile.dtbs -endif +# %.dtb.o <- %.dtb.S <- %.dtb <- %.dts +# %.dtbo.o <- %.dtbo.S <- %.dtbo <- %.dtso +# %.lex.o <- %.lex.c <- %.l +# %.tab.o <- %.tab.[ch] <- %.y +targets += $(call intermediate_targets, .asn1.o, .asn1.c .asn1.h) \ + $(call intermediate_targets, .dtb.o, .dtb.S .dtb) \ + $(call intermediate_targets, .dtbo.o, .dtbo.S .dtbo) \ + $(call intermediate_targets, .lex.o, .lex.c) \ + $(call intermediate_targets, .tab.o, .tab.c .tab.h) # Build # --------------------------------------------------------------------------- diff --git a/scripts/Makefile.dtbinst b/scripts/Makefile.dtbinst index 9d920419a62cf4..4be9ebd3995c0b 100644 --- a/scripts/Makefile.dtbinst +++ b/scripts/Makefile.dtbinst @@ -31,9 +31,14 @@ $(dst)/%: $(obj)/$(1)% $$(call cmd,dtb_install) endef -$(foreach d, $(sort $(dir $(dtbs))), $(eval $(call gen_install_rules,$(d)))) +define overlays_install_rules +$(dst)/overlays/%: $(obj)/$(1)% + $$(call cmd,dtb_install) +endef + +$(foreach d, $(sort $(dir $(dtbs))), $(if $(findstring "overlays/","$(d)"),$(eval $(call overlays_install_rules,$(d))),$(eval $(call gen_install_rules,$(d))))) -dtbs := $(notdir $(dtbs)) +dtbs := $(foreach d, $(dtbs), $(if $(findstring overlays/,$(d)),$(d),$(notdir $(d)))) endif # CONFIG_ARCH_WANT_FLAT_DTB_INSTALL diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib index fe5e132fcea89a..d9c1d68694ccf1 100644 --- a/scripts/Makefile.lib +++ b/scripts/Makefile.lib @@ -45,6 +45,11 @@ else obj-y := $(filter-out %/, $(obj-y)) endif +ifdef need-dtbslist +dtb-y += $(addsuffix /dtbs-list, $(subdir-ym)) +always-y += dtbs-list +endif + # Expand $(foo-objs) $(foo-y) etc. by replacing their individuals suffix-search = $(strip $(foreach s, $3, $($(1:%$(strip $2)=%$s)))) # List composite targets that are constructed by combining other targets @@ -75,6 +80,19 @@ always-y += $(hostprogs-always-y) $(hostprogs-always-m) userprogs += $(userprogs-always-y) $(userprogs-always-m) always-y += $(userprogs-always-y) $(userprogs-always-m) +# DTB +# If CONFIG_OF_ALL_DTBS is enabled, all DT blobs are built +dtb-$(CONFIG_OF_ALL_DTBS) += $(dtb-) + +# Composite DTB (i.e. DTB constructed by overlay) +multi-dtb-y := $(call multi-search, $(dtb-y), .dtb, -dtbs) +# Primitive DTB compiled from *.dts +real-dtb-y := $(call real-search, $(dtb-y), .dtb, -dtbs) +# Base DTB that overlay is applied onto +base-dtb-y := $(filter %.dtb, $(call real-search, $(multi-dtb-y), .dtb, -dtbs)) + +always-y += $(dtb-y) + # Add subdir path ifneq ($(obj),.) @@ -86,6 +104,10 @@ lib-y := $(addprefix $(obj)/,$(lib-y)) real-obj-y := $(addprefix $(obj)/,$(real-obj-y)) real-obj-m := $(addprefix $(obj)/,$(real-obj-m)) multi-obj-m := $(addprefix $(obj)/, $(multi-obj-m)) +dtb-y := $(addprefix $(obj)/, $(dtb-y)) +dtbo-y := $(addprefix $(obj)/, $(dtbo-y)) +multi-dtb-y := $(addprefix $(obj)/, $(multi-dtb-y)) +real-dtb-y := $(addprefix $(obj)/, $(real-dtb-y)) subdir-ym := $(addprefix $(obj)/,$(subdir-ym)) endif @@ -146,9 +168,6 @@ ifneq ($(CONFIG_KASAN_HW_TAGS),y) _c_flags += $(if $(patsubst n%,, \ $(KASAN_SANITIZE_$(target-stem).o)$(KASAN_SANITIZE)$(is-kernel-object)), \ $(CFLAGS_KASAN), $(CFLAGS_KASAN_NOSANITIZE)) -_rust_flags += $(if $(patsubst n%,, \ - $(KASAN_SANITIZE_$(target-stem).o)$(KASAN_SANITIZE)$(is-kernel-object)), \ - $(RUSTFLAGS_KASAN)) endif endif @@ -220,7 +239,7 @@ modkern_rustflags = \ modkern_aflags = $(if $(part-of-module), \ $(KBUILD_AFLAGS_MODULE) $(AFLAGS_MODULE), \ - $(KBUILD_AFLAGS_KERNEL) $(AFLAGS_KERNEL) $(modfile_flags)) + $(KBUILD_AFLAGS_KERNEL) $(AFLAGS_KERNEL)) c_flags = -Wp,-MMD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE) \ -include $(srctree)/include/linux/compiler_types.h \ @@ -230,13 +249,19 @@ c_flags = -Wp,-MMD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE) \ rust_flags = $(_rust_flags) $(modkern_rustflags) @$(objtree)/include/generated/rustc_cfg a_flags = -Wp,-MMD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE) \ - $(_a_flags) $(modkern_aflags) $(modname_flags) + $(_a_flags) $(modkern_aflags) cpp_flags = -Wp,-MMD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE) \ $(_cpp_flags) ld_flags = $(KBUILD_LDFLAGS) $(ldflags-y) $(LDFLAGS_$(@F)) +DTC_INCLUDE := $(srctree)/scripts/dtc/include-prefixes + +dtc_cpp_flags = -Wp,-MMD,$(depfile).pre.tmp -nostdinc \ + $(addprefix -I,$(DTC_INCLUDE)) \ + -undef -D__DTS__ + ifdef CONFIG_OBJTOOL objtool := $(objtree)/tools/objtool/objtool @@ -326,6 +351,114 @@ cmd_objcopy = $(OBJCOPY) $(OBJCOPYFLAGS) $(OBJCOPYFLAGS_$(@F)) $< $@ quiet_cmd_gzip = GZIP $@ cmd_gzip = cat $(real-prereqs) | $(KGZIP) -n -f -9 > $@ +# DTC +# --------------------------------------------------------------------------- +DTC ?= $(objtree)/scripts/dtc/dtc +DTC_FLAGS += \ + -Wno-unique_unit_address + +# Disable noisy checks by default +ifeq ($(findstring 1,$(KBUILD_EXTRA_WARN)),) +DTC_FLAGS += -Wno-unit_address_vs_reg \ + -Wno-gpios_property \ + -Wno-avoid_unnecessary_addr_size \ + -Wno-alias_paths \ + -Wno-graph_child_address \ + -Wno-simple_bus_reg +else +DTC_FLAGS += \ + -Wunique_unit_address_if_enabled +endif + +ifneq ($(findstring 2,$(KBUILD_EXTRA_WARN)),) +DTC_FLAGS += -Wnode_name_chars_strict \ + -Wproperty_name_chars_strict \ + -Wunique_unit_address +endif + +DTC_FLAGS += $(DTC_FLAGS_$(target-stem)) + +# Set -@ if the target is a base DTB that overlay is applied onto +DTC_FLAGS += $(if $(filter $(patsubst $(obj)/%,%,$@), $(base-dtb-y)), -@) + +# Generate an assembly file to wrap the output of the device tree compiler +quiet_cmd_wrap_S_dtb = WRAP $@ + cmd_wrap_S_dtb = { \ + symbase=__$(patsubst .%,%,$(suffix $<))_$(subst -,_,$(notdir $*)); \ + echo '\#include <asm-generic/vmlinux.lds.h>'; \ + echo '.section .dtb.init.rodata,"a"'; \ + echo '.balign STRUCT_ALIGNMENT'; \ + echo ".global $${symbase}_begin"; \ + echo "$${symbase}_begin:"; \ + echo '.incbin "$<" '; \ + echo ".global $${symbase}_end"; \ + echo "$${symbase}_end:"; \ + echo '.balign STRUCT_ALIGNMENT'; \ + } > $@ + +$(obj)/%.dtb.S: $(obj)/%.dtb FORCE + $(call if_changed,wrap_S_dtb) + +$(obj)/%.dtbo.S: $(obj)/%.dtbo FORCE + $(call if_changed,wrap_S_dtb) + +quiet_dtb_check_tag = $(if $(dtb-check-enabled),[C], ) +cmd_dtb_check = $(if $(dtb-check-enabled),; $(DT_CHECKER) $(DT_CHECKER_FLAGS) -u $(srctree)/$(DT_BINDING_DIR) -p $(DT_TMP_SCHEMA) $@ || true) + +quiet_cmd_dtc = DTC $(quiet_dtb_check_tag) $@ +cmd_dtc = $(HOSTCC) -E $(dtc_cpp_flags) -x assembler-with-cpp -o $(dtc-tmp) $< ; \ + $(DTC) -o $@ -b 0 \ + $(addprefix -i,$(dir $<) $(DTC_INCLUDE)) $(DTC_FLAGS) \ + -d $(depfile).dtc.tmp $(dtc-tmp) ; \ + cat $(depfile).pre.tmp $(depfile).dtc.tmp > $(depfile) \ + $(cmd_dtb_check) + +# NOTE: +# Do not replace $(filter %.dtb %.dtbo, $^) with $(real-prereqs). When a single +# DTB is turned into a multi-blob DTB, $^ will contain header file dependencies +# recorded in the .*.cmd file. +quiet_cmd_fdtoverlay = OVL $(quiet_dtb_check_tag) $@ + cmd_fdtoverlay = $(objtree)/scripts/dtc/fdtoverlay -o $@ -i $(filter %.dtb %.dtbo, $^) $(cmd_dtb_check) + +$(multi-dtb-y): FORCE + $(call if_changed,fdtoverlay) +$(call multi_depend, $(multi-dtb-y), .dtb, -dtbs) + +ifneq ($(CHECK_DTBS),) +DT_CHECKER ?= dt-validate +DT_CHECKER_FLAGS ?= $(if $(DT_SCHEMA_FILES),-l $(DT_SCHEMA_FILES),-m) +DT_BINDING_DIR := Documentation/devicetree/bindings +DT_TMP_SCHEMA := $(objtree)/$(DT_BINDING_DIR)/processed-schema.json +dtb-check-enabled = $(if $(filter %.dtb, $@),y) +endif + +$(obj)/%.dtb: $(obj)/%.dts $(DTC) $(DT_TMP_SCHEMA) FORCE + $(call if_changed_dep,dtc) + +$(obj)/%.dtbo: $(src)/%.dtso $(DTC) FORCE + $(call if_changed_dep,dtc) + +quiet_cmd_dtco = DTCO $@ +cmd_dtco = mkdir -p $(dir ${dtc-tmp}) ; \ + $(CPP) $(dtc_cpp_flags) -x assembler-with-cpp -o $(dtc-tmp) $< ; \ + $(DTC) -@ -H epapr -O dtb -o $@ -b 0 \ + -i $(dir $<) $(DTC_FLAGS) \ + -Wno-interrupts_property \ + -Wno-label_is_string \ + -Wno-reg_format \ + -Wno-pci_device_bus_num \ + -Wno-i2c_bus_reg \ + -Wno-spi_bus_reg \ + -Wno-avoid_default_addr_size \ + -Wno-interrupt_provider \ + -d $(depfile).dtc.tmp $(dtc-tmp) ; \ + cat $(depfile).pre.tmp $(depfile).dtc.tmp > $(depfile) + +$(obj)/%.dtbo: $(src)/%-overlay.dts FORCE + $(call if_changed_dep,dtco) + +dtc-tmp = $(subst $(comma),_,$(dot-target).dts.tmp) + # Bzip2 # --------------------------------------------------------------------------- @@ -418,17 +551,14 @@ quiet_cmd_fit = FIT $@ # XZ # --------------------------------------------------------------------------- -# Use xzkern or xzkern_with_size to compress the kernel image and xzmisc to -# compress other things. +# Use xzkern to compress the kernel image and xzmisc to compress other things. # # xzkern uses a big LZMA2 dictionary since it doesn't increase memory usage # of the kernel decompressor. A BCJ filter is used if it is available for -# the target architecture. -# -# xzkern_with_size also appends uncompressed size of the data using -# size_append. The .xz format has the size information available at the end -# of the file too, but it's in more complex format and it's good to avoid -# changing the part of the boot code that reads the uncompressed size. +# the target architecture. xzkern also appends uncompressed size of the data +# using size_append. The .xz format has the size information available at +# the end of the file too, but it's in more complex format and it's good to +# avoid changing the part of the boot code that reads the uncompressed size. # Note that the bytes added by size_append will make the xz tool think that # the file is corrupt. This is expected. # diff --git a/scripts/dtc/checks.c b/scripts/dtc/checks.c index 6e06aeab5503f7..17ae5257005820 100644 --- a/scripts/dtc/checks.c +++ b/scripts/dtc/checks.c @@ -724,7 +724,7 @@ static void check_alias_paths(struct check *c, struct dt_info *dti, continue; } if (strspn(prop->name, LOWERCASE DIGITS "-") != strlen(prop->name)) - FAIL(c, dti, node, "aliases property name must include only lowercase and '-'"); + FAIL(c, dti, node, "aliases property name (%s) must include only lowercase and '-'", prop->name); } } WARNING(alias_paths, check_alias_paths, NULL); diff --git a/sound/drivers/Kconfig b/sound/drivers/Kconfig index 6debd8e95cb7a5..e2c4b6fd756fd9 100644 --- a/sound/drivers/Kconfig +++ b/sound/drivers/Kconfig @@ -263,4 +263,14 @@ config SND_AC97_POWER_SAVE_DEFAULT See SND_AC97_POWER_SAVE for more details. +config SND_PIMIDI + tristate "Pimidi driver" + depends on SND_SEQUENCER && CRC8 + select SND_RAWMIDI + help + Say Y here to include support for Blokas Pimidi. + + To compile this driver as a module, choose M here: the module + will be called snd-pimidi. + endif # SND_DRIVERS diff --git a/sound/drivers/Makefile b/sound/drivers/Makefile index a08bdd70ec9c2d..2166e101949372 100644 --- a/sound/drivers/Makefile +++ b/sound/drivers/Makefile @@ -9,6 +9,7 @@ snd-aloop-y := aloop.o snd-mtpav-y := mtpav.o snd-mts64-y := mts64.o snd-pcmtest-y := pcmtest.o +snd-pimidi-y := pimidi.o snd-portman2x4-y := portman2x4.o snd-serial-u16550-y := serial-u16550.o snd-serial-generic-y := serial-generic.o @@ -23,6 +24,7 @@ obj-$(CONFIG_SND_SERIAL_U16550) += snd-serial-u16550.o obj-$(CONFIG_SND_SERIAL_GENERIC) += snd-serial-generic.o obj-$(CONFIG_SND_MTPAV) += snd-mtpav.o obj-$(CONFIG_SND_MTS64) += snd-mts64.o +obj-$(CONFIG_SND_PIMIDI) += snd-pimidi.o obj-$(CONFIG_SND_PORTMAN2X4) += snd-portman2x4.o obj-$(CONFIG_SND) += opl3/ opl4/ mpu401/ vx/ pcsp/ diff --git a/sound/drivers/pimidi.c b/sound/drivers/pimidi.c new file mode 100644 index 00000000000000..c399707425b787 --- /dev/null +++ b/sound/drivers/pimidi.c @@ -0,0 +1,1113 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Pimidi Linux kernel module. + * Copyright (C) 2017-2024 Vilniaus Blokas UAB, https://blokas.io/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 of the + * License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/completion.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/irq.h> +#include <linux/irqdesc.h> +#include <linux/bitops.h> +#include <linux/of_irq.h> +#include <linux/kfifo.h> +#include <linux/list.h> +#include <linux/workqueue.h> +#include <linux/gpio.h> +#include <linux/gpio/consumer.h> +#include <linux/interrupt.h> +#include <linux/mutex.h> +#include <linux/refcount.h> +#include <linux/crc8.h> +#include <linux/delay.h> + +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/rawmidi.h> +#include <sound/asequencer.h> +#include <sound/info.h> + +#define PIMIDI_LOG_IMPL(instance, log_func, msg, ...) log_func("pimidi(%s)[%c]: " msg "\n", \ + __func__, (instance) ? (instance)->d + '0' : 'G', ## __VA_ARGS__) + +#ifdef PIMIDI_DEBUG +# define printd(instance, ...) PIMIDI_LOG_IMPL(instance, pr_alert, __VA_ARGS__) +# define printd_rl(instance, ...) PIMIDI_LOG_IMPL(instance, pr_alert_ratelimited, __VA_ARGS__) +# define printd_g(...) printd((struct pimidi_instance *)NULL, __VA_ARGS__) +#else +# define printd(instance, ...) do {} while (0) +# define printd_rl(instance, ...) do {} while (0) +# define printd_g(...) do {} while (0) +#endif + +#define printe(instance, ...) PIMIDI_LOG_IMPL(instance, pr_err, __VA_ARGS__) +#define printe_rl(instance, ...) PIMIDI_LOG_IMPL(instance, pr_err_ratelimited, __VA_ARGS__) +#define printi(instance, ...) PIMIDI_LOG_IMPL(instance, pr_info, __VA_ARGS__) +#define printw(instance, ...) PIMIDI_LOG_IMPL(instance, pr_warn, __VA_ARGS__) +#define printw_rl(instance, ...) PIMIDI_LOG_IMPL(instance, pr_warn_ratelimited, __VA_ARGS__) + +#define printe_g(...) printe((struct pimidi_instance *)NULL, __VA_ARGS__) +#define printi_g(...) printi((struct pimidi_instance *)NULL, __VA_ARGS__) + +DECLARE_CRC8_TABLE(pimidi_crc8_table); +enum { PIMIDI_CRC8_POLYNOMIAL = 0x83 }; +enum { PIMIDI_MAX_DEVICES = 4 }; +enum { PIMIDI_MAX_PACKET_SIZE = 17 }; +enum { PIMIDI_PORTS = 2 }; + +struct pimidi_shared { + // lock protects the shared reset_gpio and devices list. + struct mutex lock; + struct gpio_desc *reset_gpio; + struct workqueue_struct *work_queue; + struct list_head devices; +}; + +static struct pimidi_shared pimidi_global = { + .devices = LIST_HEAD_INIT(pimidi_global.devices), +}; + +struct pimidi_version_t { + u8 hwrev; + u8 major; + u8 minor; + u8 build; +}; + +enum { PIMIDI_IN_FIFO_SIZE = 4096 }; + +struct pimidi_midi_port { + // in_lock protects the input substream. + struct mutex in_lock; + // out_lock protects the output substream. + struct mutex out_lock; + DECLARE_KFIFO(in_fifo, uint8_t, PIMIDI_IN_FIFO_SIZE); + unsigned int last_output_at; + unsigned int output_buffer_used_in_millibytes; + struct work_struct in_handler; + struct delayed_work out_handler; + unsigned long enabled_streams; + unsigned int tx_cnt; + unsigned int rx_cnt; +}; + +struct pimidi_instance { + struct list_head list; + struct i2c_client *i2c_client; + struct pimidi_version_t version; + char serial[11]; + char d; + struct gpio_desc *data_ready_gpio; + + struct work_struct drdy_handler; + + // comm_lock serializes I2C communication. + struct mutex comm_lock; + char *rx_buf; + size_t rx_len; + int rx_status; + struct completion *rx_completion; + + struct snd_rawmidi *rawmidi; + struct pimidi_midi_port midi_port[PIMIDI_PORTS]; + bool stopping; +}; + +static struct snd_rawmidi_substream *pimidi_find_substream(struct snd_rawmidi *rawmidi, + int stream, + int number + ) +{ + struct snd_rawmidi_substream *substream; + + list_for_each_entry(substream, &rawmidi->streams[stream].substreams, list) { + if (substream->number == number) + return substream; + } + return NULL; +} + +static void pimidi_midi_in_handler(struct pimidi_instance *instance, int port) +{ + int i, n, err; + + printd(instance, "(%d)", port); + + struct pimidi_midi_port *midi_port = &instance->midi_port[port]; + + if (!test_bit(SNDRV_RAWMIDI_STREAM_INPUT, &midi_port->enabled_streams)) { + printd(instance, "Input not enabled for %d", port); + return; + } + + u8 data[512]; + + n = kfifo_out_peek(&midi_port->in_fifo, data, sizeof(data)); + printd(instance, "Peeked %d MIDI bytes", n); + + mutex_lock(&midi_port->in_lock); + struct snd_rawmidi_substream *substream = + pimidi_find_substream(instance->rawmidi, + SNDRV_RAWMIDI_STREAM_INPUT, + port); + + err = snd_rawmidi_receive(substream, data, n); + if (err > 0) + midi_port->rx_cnt += err; + mutex_unlock(&midi_port->in_lock); + + for (i = 0; i < err; ++i) + kfifo_skip(&midi_port->in_fifo); + + if (n != err) + printw_rl(instance, + "Not all MIDI data consumed for port %d: %d / %d", port, err, n); + + if (!kfifo_is_empty(&midi_port->in_fifo) && !instance->stopping) + queue_work(pimidi_global.work_queue, &midi_port->in_handler); + + printd(instance, "Done"); +} + +static void pimidi_midi_in_handler_0(struct work_struct *work) +{ + pimidi_midi_in_handler(container_of(work, struct pimidi_instance, midi_port[0].in_handler), + 0); +} + +static void pimidi_midi_in_handler_1(struct work_struct *work) +{ + pimidi_midi_in_handler(container_of(work, struct pimidi_instance, midi_port[1].in_handler), + 1); +} + +static void pimidi_midi_out_handler(struct pimidi_instance *instance, int port) +{ + printd(instance, "(%d)", port); + if (!test_bit(SNDRV_RAWMIDI_STREAM_OUTPUT, &instance->midi_port[port].enabled_streams)) { + printd(instance, "Output not enabled for %d", port); + return; + } + + struct pimidi_midi_port *midi_port = &instance->midi_port[port]; + + struct snd_rawmidi_substream *substream = + pimidi_find_substream(instance->rawmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, port); + + mutex_lock(&midi_port->out_lock); + + enum { MIDI_MILLI_BYTES_PER_JIFFY = 3125000 / HZ }; + enum { MIDI_MAX_OUTPUT_BUFFER_SIZE_IN_MILLIBYTES = + (512 - PIMIDI_MAX_PACKET_SIZE - 1) * 1000 }; + + unsigned int now = jiffies; + unsigned int millibytes_became_available = + (MIDI_MILLI_BYTES_PER_JIFFY) * (now - midi_port->last_output_at); + + midi_port->output_buffer_used_in_millibytes = + midi_port->output_buffer_used_in_millibytes <= + millibytes_became_available ? 0 : midi_port->output_buffer_used_in_millibytes - + millibytes_became_available; + + unsigned int output_buffer_available = + (MIDI_MAX_OUTPUT_BUFFER_SIZE_IN_MILLIBYTES + - midi_port->output_buffer_used_in_millibytes) + / 1000; + + u8 buffer[PIMIDI_MAX_PACKET_SIZE]; + int n, batch, err; + + for (batch = 0; batch < 3; ++batch) { + if (output_buffer_available == 0) + printd(instance, "Buffer full"); + + printd(instance, "Buffer available: %u (%u +%u, %u -> %u, dt %u) (%u) @ %u", + output_buffer_available, midi_port->output_buffer_used_in_millibytes, + millibytes_became_available, midi_port->last_output_at, now, + now - midi_port->last_output_at, midi_port->tx_cnt, HZ); + midi_port->last_output_at = now; + + n = output_buffer_available + ? snd_rawmidi_transmit_peek(substream, buffer + 1, + min(output_buffer_available, + sizeof(buffer) - 2)) + : 0; + if (n > 0) { + printd(instance, "Peeked: %d", n); + snd_rawmidi_transmit_ack(substream, n); + + buffer[0] = (port << 4) | n; + buffer[n + 1] = ~crc8(pimidi_crc8_table, buffer, n + 1, CRC8_INIT_VALUE); + +#ifdef PIMIDI_DEBUG + pr_debug("%s[%d]: Sending %d bytes:", __func__, instance->d, n + 2); + int i; + + for (i = 0; i < n + 2; ++i) + pr_cont(" %02x", buffer[i]); + + pr_cont("\n"); +#endif + mutex_lock(&instance->comm_lock); + err = i2c_master_send(instance->i2c_client, buffer, n + 2); + mutex_unlock(&instance->comm_lock); + + if (err < 0) { + printe(instance, + "Error occurred when sending MIDI data over I2C! (%d)", + err); + goto cleanup; + } + + midi_port->tx_cnt += n; + midi_port->output_buffer_used_in_millibytes += n * 1000; + output_buffer_available -= n; + } else if (n < 0) { + err = n; + printe(instance, "snd_rawmidi_transmit_peek returned error %d!", err); + goto cleanup; + } else { + break; + } + } + + printd(instance, "Checking if empty %p", substream); + if (!snd_rawmidi_transmit_empty(substream) && !instance->stopping) { + unsigned int delay = 1; + + if (output_buffer_available == 0) + delay = 125000 / MIDI_MILLI_BYTES_PER_JIFFY; + printd(instance, "Queue more work after %u jiffies", delay); + mod_delayed_work(pimidi_global.work_queue, &midi_port->out_handler, delay); + } + +cleanup: + mutex_unlock(&midi_port->out_lock); + printd(instance, "Done"); +} + +static void pimidi_midi_out_handler_0(struct work_struct *work) +{ + pimidi_midi_out_handler(container_of(work, struct pimidi_instance, + midi_port[0].out_handler.work), 0); +} + +static void pimidi_midi_out_handler_1(struct work_struct *work) +{ + pimidi_midi_out_handler(container_of(work, struct pimidi_instance, + midi_port[1].out_handler.work), 1); +} + +static void pimidi_midi_output_trigger(struct snd_rawmidi_substream *substream, int up) +{ + struct pimidi_instance *instance = substream->rmidi->private_data; + + printd(instance, "(%d, %d, %d)", substream->stream, substream->number, up); + + if (up == 0) { + clear_bit(substream->stream, + &instance->midi_port[substream->number].enabled_streams); + } else { + set_bit(substream->stream, + &instance->midi_port[substream->number].enabled_streams); + if (!delayed_work_pending(&instance->midi_port[substream->number].out_handler)) { + printd(instance, "Queueing work"); + queue_delayed_work(pimidi_global.work_queue, + &instance->midi_port[substream->number].out_handler, 0); + } + } +} + +static void pimidi_midi_output_drain(struct snd_rawmidi_substream *substream) +{ + struct pimidi_instance *instance = substream->rmidi->private_data; + + printd(instance, "(%d, %d)", substream->stream, substream->number); + + printd(instance, "Begin draining!"); + + queue_delayed_work(pimidi_global.work_queue, + &instance->midi_port[substream->number].out_handler, 0); + + unsigned long deadline = jiffies + 5 * HZ; + + do { + printd(instance, "Before flush"); + while (delayed_work_pending(&instance->midi_port[substream->number].out_handler)) + flush_delayed_work(&instance->midi_port[substream->number].out_handler); + printd(instance, "Flushed"); + } while (!snd_rawmidi_transmit_empty(substream) && time_before(jiffies, deadline)); + + printd(instance, "Done!"); +} + +static int pimidi_midi_output_close(struct snd_rawmidi_substream *substream) +{ + struct pimidi_instance *instance = substream->rmidi->private_data; + struct pimidi_midi_port *midi_port = &instance->midi_port[substream->number]; + + mutex_lock(&midi_port->out_lock); + clear_bit(substream->stream, &midi_port->enabled_streams); + mutex_unlock(&midi_port->out_lock); + return 0; +} + +static int pimidi_midi_input_close(struct snd_rawmidi_substream *substream) +{ + struct pimidi_instance *instance = substream->rmidi->private_data; + struct pimidi_midi_port *midi_port = &instance->midi_port[substream->number]; + + mutex_lock(&midi_port->in_lock); + clear_bit(substream->stream, &midi_port->enabled_streams); + mutex_unlock(&midi_port->in_lock); + return 0; +} + +static void pimidi_midi_input_trigger(struct snd_rawmidi_substream *substream, int up) +{ + struct pimidi_instance *instance = substream->rmidi->private_data; + + printd(instance, "(%d, %d, %d)", substream->stream, substream->number, up); + + if (up == 0) { + clear_bit(substream->stream, + &instance->midi_port[substream->number].enabled_streams); + cancel_work_sync(&instance->midi_port[substream->number].in_handler); + } else { + set_bit(substream->stream, + &instance->midi_port[substream->number].enabled_streams); + if (!instance->stopping) + queue_work(pimidi_global.work_queue, + &instance->midi_port[substream->number].in_handler); + } +} + +static void pimidi_get_port_info(struct snd_rawmidi *rmidi, int number, + struct snd_seq_port_info *seq_port_info) +{ + printd_g("%p, %d, %p", rmidi, number, seq_port_info); + seq_port_info->type = + SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | + SNDRV_SEQ_PORT_TYPE_HARDWARE | + SNDRV_SEQ_PORT_TYPE_PORT; + strscpy(seq_port_info->name, number == 0 ? "a" : "b", + sizeof(seq_port_info->name)); + seq_port_info->midi_voices = 0; +} + +static const struct snd_rawmidi_global_ops pimidi_midi_ops = { + .get_port_info = pimidi_get_port_info, +}; + +static int pimidi_midi_open(struct snd_rawmidi_substream *substream) +{ + printd_g("(%p) stream=%d number=%d", substream, substream->stream, substream->number); + return 0; +} + +static const struct snd_rawmidi_ops pimidi_midi_output_ops = { + .open = pimidi_midi_open, + .close = pimidi_midi_output_close, + .trigger = pimidi_midi_output_trigger, + .drain = pimidi_midi_output_drain, +}; + +static const struct snd_rawmidi_ops pimidi_midi_input_ops = { + .open = pimidi_midi_open, + .close = pimidi_midi_input_close, + .trigger = pimidi_midi_input_trigger, +}; + +static int pimidi_register(struct pimidi_instance *instance) +{ + int err = 0; + + mutex_lock(&pimidi_global.lock); + printd(instance, "Registering..."); + if (!pimidi_global.reset_gpio) { + printd_g("Getting reset pin."); + pimidi_global.reset_gpio = gpiod_get(&instance->i2c_client->dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(pimidi_global.reset_gpio)) { + err = PTR_ERR(pimidi_global.reset_gpio); + printe_g("gpiod_get failed: %d", err); + pimidi_global.reset_gpio = NULL; + mutex_unlock(&pimidi_global.lock); + return err; + } + } + list_add_tail(&instance->list, &pimidi_global.devices); + mutex_unlock(&pimidi_global.lock); + return err; +} + +static void pimidi_unregister(struct pimidi_instance *instance) +{ + mutex_lock(&pimidi_global.lock); + printd(instance, "Unregistering..."); + list_del(&instance->list); + if (list_empty(&pimidi_global.devices)) { + printd_g("Releasing reset pin"); + gpiod_put(pimidi_global.reset_gpio); + pimidi_global.reset_gpio = NULL; + } + mutex_unlock(&pimidi_global.lock); +} + +static void pimidi_perform_reset(void) +{ + mutex_lock(&pimidi_global.lock); + + printd_g("Performing reset."); + + struct list_head *p; + + list_for_each(p, &pimidi_global.devices) { + struct pimidi_instance *instance = list_entry(p, struct pimidi_instance, list); + + printd(instance, "Pausing..."); + instance->stopping = true; + disable_irq(instance->i2c_client->irq); + cancel_work(&instance->drdy_handler); + + int i; + + for (i = 0; i < PIMIDI_PORTS; ++i) { + cancel_work(&instance->midi_port[i].in_handler); + cancel_delayed_work(&instance->midi_port[i].out_handler); + } + + drain_workqueue(pimidi_global.work_queue); + } + + printd_g("Reset = low"); + gpiod_set_value(pimidi_global.reset_gpio, 1); + + list_for_each(p, &pimidi_global.devices) { + struct pimidi_instance *instance = list_entry(p, struct pimidi_instance, list); + + if (gpiod_is_active_low(instance->data_ready_gpio)) + gpiod_toggle_active_low(instance->data_ready_gpio); + gpiod_direction_output(instance->data_ready_gpio, 1); + printd(instance, "DRDY high"); + } + + usleep_range(1000, 5000); + printd_g("Reset = high"); + gpiod_set_value(pimidi_global.reset_gpio, 0); + msleep(30); + + int i; + + for (i = 0; i < PIMIDI_MAX_DEVICES; ++i) { + usleep_range(1000, 3000); + list_for_each(p, &pimidi_global.devices) { + struct pimidi_instance *instance = list_entry(p, struct pimidi_instance, + list); + + if (instance->d < i) + continue; + printd(instance, "DRDY -> %d", !gpiod_get_value(instance->data_ready_gpio)); + gpiod_set_value(instance->data_ready_gpio, + !gpiod_get_value(instance->data_ready_gpio)); + } + } + usleep_range(16000, 20000); + + list_for_each(p, &pimidi_global.devices) { + struct pimidi_instance *instance = list_entry(p, struct pimidi_instance, list); + + if (!gpiod_is_active_low(instance->data_ready_gpio)) + gpiod_toggle_active_low(instance->data_ready_gpio); + + printd(instance, "DRDY input"); + gpiod_direction_input(instance->data_ready_gpio); + + printd(instance, "Resume..."); + instance->stopping = false; + enable_irq(instance->i2c_client->irq); + } + + printd_g("Reset done."); + usleep_range(16000, 20000); + + mutex_unlock(&pimidi_global.lock); +} + +static int pimidi_read_version(struct pimidi_version_t *version, struct pimidi_instance *instance) +{ + memset(version, 0, sizeof(*version)); + + const char cmd[4] = { 0xb2, 0x01, 0x01, 0x95 }; + + char result[9]; + + memset(result, 0, sizeof(result)); + + DECLARE_COMPLETION_ONSTACK(done); + + mutex_lock(&instance->comm_lock); + int err = i2c_master_send(instance->i2c_client, cmd, sizeof(cmd)); + + if (err < 0) { + mutex_unlock(&instance->comm_lock); + return err; + } + instance->rx_buf = result; + instance->rx_len = sizeof(result); + instance->rx_completion = &done; + mutex_unlock(&instance->comm_lock); + + printd(instance, "Waiting for drdy"); + wait_for_completion_io_timeout(&done, msecs_to_jiffies(1000u)); + printd(instance, "Done waiting"); + + if (!completion_done(&done)) { + mutex_lock(&instance->comm_lock); + instance->rx_buf = NULL; + instance->rx_len = 0; + instance->rx_status = -ETIMEDOUT; + instance->rx_completion = NULL; + mutex_unlock(&instance->comm_lock); + return -ETIMEDOUT; + } + + if (CRC8_GOOD_VALUE(pimidi_crc8_table) != crc8(pimidi_crc8_table, result, sizeof(result), + CRC8_INIT_VALUE)) + return -EIO; + + const char expected[4] = { 0xb7, 0x81, 0x01, 0x00 }; + + if (memcmp(result, expected, sizeof(expected)) != 0) + return -EPROTO; + + u32 v = ntohl(*(uint32_t *)(result + 4)); + + version->hwrev = v >> 24; + version->major = (v & 0x00ff0000) >> 16; + version->minor = (v & 0x0000ff00) >> 8; + version->build = v & 0x000000ff; + + return 0; +} + +static int pimidi_read_serial(char serial[11], struct pimidi_instance *instance) +{ + memset(serial, 0, sizeof(char[11])); + + const char cmd[4] = { 0xb2, 0x03, 0x04, 0x97 }; + + char result[PIMIDI_MAX_PACKET_SIZE]; + + memset(result, 0, sizeof(result)); + + DECLARE_COMPLETION_ONSTACK(done); + + mutex_lock(&instance->comm_lock); + int err = i2c_master_send(instance->i2c_client, cmd, sizeof(cmd)); + + if (err < 0) { + mutex_unlock(&instance->comm_lock); + return err; + } + instance->rx_buf = result; + instance->rx_len = sizeof(result); + instance->rx_completion = &done; + mutex_unlock(&instance->comm_lock); + + printd(instance, "Waiting for drdy"); + wait_for_completion_io_timeout(&done, msecs_to_jiffies(1000u)); + printd(instance, "Done waiting"); + + if (!completion_done(&done)) { + mutex_lock(&instance->comm_lock); + instance->rx_buf = NULL; + instance->rx_len = 0; + instance->rx_status = -ETIMEDOUT; + instance->rx_completion = NULL; + mutex_unlock(&instance->comm_lock); + printe(instance, "Timed out"); + return -ETIMEDOUT; + } + + if (CRC8_GOOD_VALUE(pimidi_crc8_table) != crc8(pimidi_crc8_table, result, + (result[0] & 0x0f) + 2, CRC8_INIT_VALUE)) + return -EIO; + + const char expected[4] = { 0xbd, 0x83, 0x04, 0x0a }; + + if (memcmp(result, expected, sizeof(expected)) != 0) { + printe(instance, "Unexpected response: %02x %02x %02x %02x", result[0], result[1], + result[2], result[3]); + return -EPROTO; + } + + memcpy(serial, result + 4, 10); + + if (strspn(serial, "\xff") == 10) + strscpy(serial, "(unset)", 8); + + return 0; +} + +static void pimidi_handle_midi_data(struct pimidi_instance *instance, int port, const uint8_t *data, + unsigned int n) +{ + printd(instance, "Handling MIDI data for port %d (%u bytes)", port, n); + if (n == 0) + return; + + struct pimidi_midi_port *midi_port = &instance->midi_port[port]; + + kfifo_in(&midi_port->in_fifo, data, n); + + if (!instance->stopping) + queue_work(pimidi_global.work_queue, &midi_port->in_handler); + + printd(instance, "Done"); +} + +static void pimidi_drdy_continue(struct pimidi_instance *instance) +{ + if (instance->stopping) { + printd(instance, "Refusing to queue work / enable IRQ due to stopping."); + return; + } + + if (gpiod_get_value(instance->data_ready_gpio)) { + printd_rl(instance, "Queue work due to DRDY line still low"); + queue_work(pimidi_global.work_queue, &instance->drdy_handler); + } else { + printd_rl(instance, "Enabling irq for more data"); + enable_irq(gpiod_to_irq(instance->data_ready_gpio)); + } +} + +static void pimidi_drdy_handler(struct work_struct *work) +{ + struct pimidi_instance *instance = container_of(work, struct pimidi_instance, drdy_handler); + + printd(instance, "(%p)", work); + + mutex_lock(&instance->comm_lock); + if (!instance->rx_completion) { + u8 data[PIMIDI_MAX_PACKET_SIZE]; + int n = i2c_master_recv(instance->i2c_client, data, 3); + + if (n < 0) { + printe(instance, "Error reading from device: %d", n); + mutex_unlock(&instance->comm_lock); + pimidi_drdy_continue(instance); + return; + } + + if (data[0] == 0xfe) { + printe_rl(instance, "Invalid packet 0x%02x 0x%02x 0x%02x", data[0], data[1], + data[2]); + mutex_unlock(&instance->comm_lock); + pimidi_drdy_continue(instance); + return; + } + + int len = (data[0] & 0x0f) + 2; + + if (len > n) { + printd(instance, "Need %d more bytes", len - n); + int err = i2c_master_recv(instance->i2c_client, data + n, len - n); + + if (err < 0) { + printe(instance, "Error reading remainder from device: %d", err); + mutex_unlock(&instance->comm_lock); + pimidi_drdy_continue(instance); + return; +#ifdef PIMIDI_DEBUG + } else { + pr_debug("Recv_2:"); + int i; + + for (i = n; i < len; ++i) + pr_cont(" %02x", data[i]); + pr_cont("\n"); +#endif + } + } + + if (CRC8_GOOD_VALUE(pimidi_crc8_table) == crc8(pimidi_crc8_table, data, len, + CRC8_INIT_VALUE)) { + switch (data[0] & 0xf0) { + case 0x00: + pimidi_handle_midi_data(instance, 0, data + 1, len - 2); + break; + case 0x10: + pimidi_handle_midi_data(instance, 1, data + 1, len - 2); + break; + default: + printd(instance, "Unhandled command %02x", data[0]); + break; + } + } else { + printe(instance, "I2C rx corruption detected."); + pr_info("Packet [%d]:", len); + int i; + + for (i = 0; i < len; ++i) + pr_cont(" %02x", data[i]); + pr_cont("\n"); + } + + mutex_unlock(&instance->comm_lock); + } else { + printd(instance, "Completing drdy"); + instance->rx_status = i2c_master_recv(instance->i2c_client, instance->rx_buf, 3); + printd(instance, "Recv_1 %02x %02x %02x", instance->rx_buf[0], instance->rx_buf[1], + instance->rx_buf[2]); + if (instance->rx_len > 3 && instance->rx_status == 3) { + instance->rx_status = i2c_master_recv(instance->i2c_client, + instance->rx_buf + 3, + instance->rx_len - 3); + if (instance->rx_status >= 0) + instance->rx_status += 3; +#ifdef PIMIDI_DEBUG + pr_debug("Recv_2:"); + int i; + + for (i = 3; i < instance->rx_len; ++i) + pr_cont(" %02x", instance->rx_buf[i]); + pr_cont("\n"); +#endif + } + struct completion *done = instance->rx_completion; + + instance->rx_buf = NULL; + instance->rx_len = 0; + instance->rx_completion = NULL; + complete_all(done); + mutex_unlock(&instance->comm_lock); + } + + pimidi_drdy_continue(instance); +} + +static irqreturn_t pimidi_drdy_interrupt_handler(int irq, void *dev_id) +{ + struct pimidi_instance *instance = (struct pimidi_instance *)dev_id; + + if (instance->stopping) { + printd(instance, "DRDY interrupt, but stopping, ignoring..."); + return IRQ_HANDLED; + } + + printd(instance, "DRDY interrupt, masking"); + disable_irq_nosync(irq); + + printd(instance, "Queue work due to DRDY interrupt"); + queue_work(pimidi_global.work_queue, &instance->drdy_handler); + + return IRQ_HANDLED; +} + +static void pimidi_proc_stat_show(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + const unsigned int *d = entry->private_data; + + snd_iprintf(buffer, "%u\n", *d); +} + +static void pimidi_proc_serial_show(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + struct pimidi_instance *instance = entry->private_data; + + snd_iprintf(buffer, "%s\n", instance->serial); +} + +static void pimidi_proc_version_show(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + struct pimidi_instance *instance = entry->private_data; + + snd_iprintf(buffer, "%u.%u.%u\n", instance->version.major, instance->version.minor, + instance->version.build); +} + +static void pimidi_proc_hwrev_show(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + struct pimidi_instance *instance = entry->private_data; + + snd_iprintf(buffer, "%u\n", instance->version.hwrev); +} + +static int pimidi_i2c_probe(struct i2c_client *client) +{ + struct snd_card *card = NULL; + int err, d, i; + + d = client->addr - 0x20; + + if (d < 0 || d >= 8) { + printe_g("Unexpected device address: %d", client->addr); + err = -EINVAL; + goto finalize; + } + + err = snd_card_new(&client->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, THIS_MODULE, + sizeof(struct pimidi_instance), &card); + + if (err) { + printe_g("snd_card_new failed: %d", err); + return err; + } + + struct pimidi_instance *instance = (struct pimidi_instance *)card->private_data; + + instance->i2c_client = client; + instance->d = d; + + struct snd_rawmidi *rawmidi; + + err = snd_rawmidi_new(card, card->shortname, 0, 2, 2, &rawmidi); + if (err < 0) { + printe(instance, "snd_rawmidi_new failed: %d", err); + goto finalize; + } + + instance->rawmidi = rawmidi; + strscpy(rawmidi->name, "pimidi", sizeof(rawmidi->name)); + + rawmidi->info_flags = + SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT | SNDRV_RAWMIDI_INFO_DUPLEX; + rawmidi->private_data = instance; + rawmidi->ops = &pimidi_midi_ops; + + snd_rawmidi_set_ops(rawmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &pimidi_midi_output_ops); + snd_rawmidi_set_ops(rawmidi, SNDRV_RAWMIDI_STREAM_INPUT, &pimidi_midi_input_ops); + + instance->data_ready_gpio = devm_gpiod_get(&client->dev, "data-ready", GPIOD_OUT_HIGH); + if (IS_ERR(instance->data_ready_gpio)) { + err = PTR_ERR(instance->data_ready_gpio); + printe(instance, "devm_gpiod_get failed: %d", err); + goto finalize; + } + + err = pimidi_register(instance); + if (err < 0) { + printe(instance, "pimidi_register failed: %d", err); + goto finalize; + } + + pimidi_perform_reset(); + + INIT_WORK(&instance->drdy_handler, pimidi_drdy_handler); + mutex_init(&instance->comm_lock); + + err = devm_request_irq(&client->dev, client->irq, pimidi_drdy_interrupt_handler, + IRQF_SHARED | IRQF_TRIGGER_LOW, "data_ready_int", instance); + + if (err != 0) { + printe(instance, "data_available IRQ request failed! %d", err); + goto finalize; + } + + err = pimidi_read_version(&instance->version, instance); + if (err < 0) { + printe(instance, "pimidi_read_version failed: %d", err); + goto finalize; + } + + err = pimidi_read_serial(instance->serial, instance); + if (err < 0) { + printe(instance, "pimidi_read_serial failed: %d", err); + goto finalize; + } else if (instance->serial[0] != 'P' || instance->serial[1] != 'M' || + strlen(instance->serial) != 10) { + printe(instance, "Unexpected serial number: %s", instance->serial); + err = -EIO; + goto finalize; + } + + printi(instance, "pimidi%d hw:%d version %u.%u.%u-%u, serial %s", + d, + card->number, + instance->version.major, + instance->version.minor, + instance->version.build, + instance->version.hwrev, + instance->serial + ); + + strscpy(card->driver, "snd-pimidi", sizeof(card->driver)); + snprintf(card->shortname, sizeof(card->shortname), "pimidi%d", d); + snprintf(card->longname, sizeof(card->longname), "pimidi%d %s", d, instance->serial); + snprintf(card->id, sizeof(card->id), "pimidi%d", d); + + snprintf(pimidi_find_substream(rawmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, 0)->name, + 10u, "pimidi%d-a", d); + snprintf(pimidi_find_substream(rawmidi, SNDRV_RAWMIDI_STREAM_INPUT, 0)->name, + 10u, "pimidi%d-a", d); + snprintf(pimidi_find_substream(rawmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, 1)->name, + 10u, "pimidi%d-b", d); + snprintf(pimidi_find_substream(rawmidi, SNDRV_RAWMIDI_STREAM_INPUT, 1)->name, + 10u, "pimidi%d-b", d); + + err = snd_card_ro_proc_new(card, "a-tx", &instance->midi_port[0].tx_cnt, + pimidi_proc_stat_show); + err = snd_card_ro_proc_new(card, "a-rx", &instance->midi_port[0].rx_cnt, + pimidi_proc_stat_show); + err = snd_card_ro_proc_new(card, "b-tx", &instance->midi_port[1].tx_cnt, + pimidi_proc_stat_show); + err = snd_card_ro_proc_new(card, "b-rx", &instance->midi_port[1].rx_cnt, + pimidi_proc_stat_show); + err = snd_card_ro_proc_new(card, "serial", instance, pimidi_proc_serial_show); + err = snd_card_ro_proc_new(card, "version", instance, pimidi_proc_version_show); + err = snd_card_ro_proc_new(card, "hwrev", instance, pimidi_proc_hwrev_show); + if (err < 0) { + printe(instance, "snd_card_ro_proc_new failed: %d", err); + goto finalize; + } + + err = snd_card_register(card); + if (err < 0) { + printe(instance, "snd_card_register failed: %d", err); + goto finalize; + } + +finalize: + if (err) { + instance->stopping = true; + cancel_work_sync(&instance->drdy_handler); + mutex_destroy(&instance->comm_lock); + pimidi_unregister(instance); + snd_card_free(card); + return err; + } + + for (i = 0; i < PIMIDI_PORTS; ++i) { + struct pimidi_midi_port *port = &instance->midi_port[i]; + + mutex_init(&port->in_lock); + mutex_init(&port->out_lock); + INIT_WORK(&port->in_handler, + i == 0 ? pimidi_midi_in_handler_0 : pimidi_midi_in_handler_1); + INIT_DELAYED_WORK(&port->out_handler, + i == 0 ? pimidi_midi_out_handler_0 : pimidi_midi_out_handler_1); + INIT_KFIFO(port->in_fifo); + port->last_output_at = jiffies; + } + + i2c_set_clientdata(client, card); + return 0; +} + +static void pimidi_i2c_remove(struct i2c_client *client) +{ + printd_g("(%p)", client); + + int i; + struct snd_card *card = i2c_get_clientdata(client); + + if (card) { + printi_g("Unloading hw:%d %s", card->number, card->longname); + struct pimidi_instance *instance = (struct pimidi_instance *)card->private_data; + + instance->stopping = true; + i2c_set_clientdata(client, NULL); + devm_free_irq(&client->dev, client->irq, instance); + cancel_work_sync(&instance->drdy_handler); + + for (i = 0; i < PIMIDI_PORTS; ++i) { + cancel_work_sync(&instance->midi_port[i].in_handler); + cancel_delayed_work_sync(&instance->midi_port[i].out_handler); + mutex_destroy(&instance->midi_port[i].out_lock); + mutex_destroy(&instance->midi_port[i].in_lock); + kfifo_free(&instance->midi_port[i].in_fifo); + } + + mutex_destroy(&instance->comm_lock); + pimidi_unregister(instance); + snd_card_free(card); + } +} + +static const struct i2c_device_id pimidi_i2c_ids[] = { + { "pimidi", 0 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, pimidi_i2c_ids); + +static const struct of_device_id pimidi_i2c_dt_ids[] = { + { .compatible = "blokaslabs,pimidi", }, + {} +}; +MODULE_DEVICE_TABLE(of, pimidi_i2c_dt_ids); + +static struct i2c_driver pimidi_i2c_driver = { + .driver = { + .name = "pimidi", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(pimidi_i2c_dt_ids), + }, + .probe = pimidi_i2c_probe, + .remove = pimidi_i2c_remove, + .id_table = pimidi_i2c_ids, +}; + +static int pimidi_module_init(void) +{ + int err = 0; + + mutex_init(&pimidi_global.lock); + + INIT_LIST_HEAD(&pimidi_global.devices); + + pimidi_global.work_queue = create_singlethread_workqueue("pimidi"); + if (!pimidi_global.work_queue) { + err = -ENOMEM; + goto cleanup; + } + + err = i2c_add_driver(&pimidi_i2c_driver); + if (err < 0) + goto cleanup; + + crc8_populate_msb(pimidi_crc8_table, PIMIDI_CRC8_POLYNOMIAL); + + return 0; + +cleanup: + mutex_destroy(&pimidi_global.lock); + return err; +} + +static void pimidi_module_exit(void) +{ + i2c_del_driver(&pimidi_i2c_driver); + mutex_lock(&pimidi_global.lock); + if (pimidi_global.reset_gpio) { + gpiod_put(pimidi_global.reset_gpio); + pimidi_global.reset_gpio = NULL; + } + mutex_unlock(&pimidi_global.lock); + + destroy_workqueue(pimidi_global.work_queue); + pimidi_global.work_queue = NULL; + + mutex_destroy(&pimidi_global.lock); +} + +module_init(pimidi_module_init); +module_exit(pimidi_module_exit); + +MODULE_AUTHOR("Giedrius Trainavi\xc4\x8dius <giedrius@blokas.io>"); +MODULE_DESCRIPTION("MIDI driver for Blokas Pimidi, https://blokas.io/"); +MODULE_LICENSE("GPL"); + +/* vim: set ts=8 sw=8 noexpandtab: */ diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index e87bd15a8b4393..60a98ef7dcc511 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -106,6 +106,7 @@ source "sound/soc/meson/Kconfig" source "sound/soc/mxs/Kconfig" source "sound/soc/pxa/Kconfig" source "sound/soc/qcom/Kconfig" +source "sound/soc/raspberrypi/Kconfig" source "sound/soc/rockchip/Kconfig" source "sound/soc/samsung/Kconfig" source "sound/soc/sh/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 775bb38c2ed447..94d7dfa17e256d 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -59,6 +59,7 @@ obj-$(CONFIG_SND_SOC) += mxs/ obj-$(CONFIG_SND_SOC) += kirkwood/ obj-$(CONFIG_SND_SOC) += pxa/ obj-$(CONFIG_SND_SOC) += qcom/ +obj-$(CONFIG_SND_SOC) += raspberrypi/ obj-$(CONFIG_SND_SOC) += rockchip/ obj-$(CONFIG_SND_SOC) += samsung/ obj-$(CONFIG_SND_SOC) += sh/ diff --git a/sound/soc/bcm/Kconfig b/sound/soc/bcm/Kconfig index 4218057b087426..fa50cab51478c4 100644 --- a/sound/soc/bcm/Kconfig +++ b/sound/soc/bcm/Kconfig @@ -26,3 +26,287 @@ config SND_BCM63XX_I2S_WHISTLER DSL/PON chips (bcm63158, bcm63178) If you don't know what to do here, say N + +config SND_BCM2708_SOC_CHIPDIP_DAC + tristate "Support for the ChipDip DAC" + help + Say Y or M if you want to add support for the ChipDip DAC soundcard + +config SND_BCM2708_SOC_GOOGLEVOICEHAT_SOUNDCARD + tristate "Support for Google voiceHAT soundcard" + select SND_SOC_VOICEHAT + select SND_RPI_SIMPLE_SOUNDCARD + help + Say Y or M if you want to add support for voiceHAT soundcard. + +config SND_BCM2708_SOC_HIFIBERRY_ADC + tristate "Support for HifiBerry ADC" + select SND_SOC_PCM186X_I2C + select SND_RPI_HIFIBERRY_ADC + help + Say Y or M if you want to add support for HifiBerry ADC. + Use this module for HiFiBerry's ADC-only sound cards + +config SND_BCM2708_SOC_HIFIBERRY_ADC8X + tristate "Support for HifiBerry ADC8X" + select SND_RPI_SIMPLE_SOUNDCARD + help + Say Y or M if you want to add support for HifiBerry ADC8X. + Note: ADC8X only works on PI5 + +config SND_BCM2708_SOC_HIFIBERRY_DAC + tristate "Support for HifiBerry DAC and DAC8X" + select SND_SOC_PCM5102A + select SND_RPI_SIMPLE_SOUNDCARD + help + Say Y or M if you want to add support for HifiBerry DAC and DAC8X. + Note: DAC8X only works on PI5 + +config SND_BCM2708_SOC_HIFIBERRY_DACPLUS + tristate "Support for HifiBerry DAC+" + select SND_SOC_PCM512x + select SND_SOC_TPA6130A2 + select COMMON_CLK_HIFIBERRY_DACPRO + help + Say Y or M if you want to add support for HifiBerry DAC+. + +config SND_BCM2708_SOC_HIFIBERRY_DACPLUSHD + tristate "Support for HifiBerry DAC+ HD" + select SND_SOC_PCM179X_I2C + select COMMON_CLK_HIFIBERRY_DACPLUSHD + help + Say Y or M if you want to add support for HifiBerry DAC+ HD. + +config SND_BCM2708_SOC_HIFIBERRY_DACPLUSADC + tristate "Support for HifiBerry DAC+ADC" + select SND_SOC_PCM512x_I2C + select SND_SOC_DMIC + select COMMON_CLK_HIFIBERRY_DACPRO + help + Say Y or M if you want to add support for HifiBerry DAC+ADC. + +config SND_BCM2708_SOC_HIFIBERRY_DACPLUSADCPRO + tristate "Support for HifiBerry DAC+ADC PRO" + select SND_SOC_PCM512x_I2C + select SND_SOC_PCM186X_I2C + select SND_SOC_TPA6130A2 + select COMMON_CLK_HIFIBERRY_DACPRO + help + Say Y or M if you want to add support for HifiBerry DAC+ADC PRO. + +config SND_BCM2708_SOC_HIFIBERRY_DACPLUSDSP + tristate "Support for HifiBerry DAC+DSP" + select SND_RPI_SIMPLE_SOUNDCARD + help + Say Y or M if you want to add support for HifiBerry DSP-DAC. + +config SND_BCM2708_SOC_HIFIBERRY_DIGI + tristate "Support for HifiBerry Digi" + select SND_SOC_WM8804 + help + Say Y or M if you want to add support for HifiBerry Digi S/PDIF output board. + +config SND_BCM2708_SOC_HIFIBERRY_AMP + tristate "Support for the HifiBerry Amp" + select SND_SOC_TAS5713 + select SND_RPI_SIMPLE_SOUNDCARD + help + Say Y or M if you want to add support for the HifiBerry Amp amplifier board. + +config SND_BCM2708_SOC_PIFI_40 + tristate "Support for the PiFi-40 amp" + select SND_SOC_TAS571X + select SND_PIFI_40 + help + Say Y or M if you want to add support for the PiFi40 amp board + +config SND_BCM2708_SOC_RPI_CIRRUS + tristate "Support for Cirrus Logic Audio Card" + select SND_SOC_WM5102 + select SND_SOC_WM8804 + help + Say Y or M if you want to add support for the Wolfson and + Cirrus Logic audio cards. + +config SND_BCM2708_SOC_RPI_DAC + tristate "Support for RPi-DAC" + select SND_SOC_PCM1794A + select SND_RPI_SIMPLE_SOUNDCARD + help + Say Y or M if you want to add support for RPi-DAC. + +config SND_BCM2708_SOC_RPI_PROTO + tristate "Support for Rpi-PROTO" + select SND_SOC_WM8731_I2C + help + Say Y or M if you want to add support for Audio Codec Board PROTO (WM8731). + +config SND_BCM2708_SOC_JUSTBOOM_BOTH + tristate "Support for simultaneous JustBoom Digi and JustBoom DAC" + select SND_SOC_WM8804 + select SND_SOC_PCM512x + help + Say Y or M if you want to add support for simultaneous + JustBoom Digi and JustBoom DAC. + + This is not the right choice if you only have one but both of + these cards. + +config SND_BCM2708_SOC_JUSTBOOM_DAC + tristate "Support for JustBoom DAC" + select SND_SOC_PCM512x + help + Say Y or M if you want to add support for JustBoom DAC. + +config SND_BCM2708_SOC_JUSTBOOM_DIGI + tristate "Support for JustBoom Digi" + select SND_SOC_WM8804 + select SND_RPI_WM8804_SOUNDCARD + help + Say Y or M if you want to add support for JustBoom Digi. + +config SND_BCM2708_SOC_IQAUDIO_CODEC + tristate "Support for IQaudIO-CODEC" + select SND_SOC_DA7213 + help + Say Y or M if you want to add support for IQaudIO-CODEC. + +config SND_BCM2708_SOC_IQAUDIO_DAC + tristate "Support for IQaudIO-DAC" + select SND_SOC_PCM512x_I2C + help + Say Y or M if you want to add support for IQaudIO-DAC. + +config SND_BCM2708_SOC_IQAUDIO_DIGI + tristate "Support for IQAudIO Digi" + select SND_SOC_WM8804 + select SND_RPI_WM8804_SOUNDCARD + help + Say Y or M if you want to add support for IQAudIO Digital IO board. + +config SND_BCM2708_SOC_I_SABRE_Q2M + tristate "Support for Audiophonics I-Sabre Q2M DAC" + select SND_SOC_I_SABRE_CODEC + help + Say Y or M if you want to add support for Audiophonics I-SABRE Q2M DAC + +config SND_BCM2708_SOC_ADAU1977_ADC + tristate "Support for ADAU1977 ADC" + select SND_SOC_ADAU1977_I2C + select SND_RPI_SIMPLE_SOUNDCARD + help + Say Y or M if you want to add support for ADAU1977 ADC. + +config SND_AUDIOINJECTOR_PI_SOUNDCARD + tristate "Support for audioinjector.net Pi add on soundcard" + select SND_SOC_WM8731_I2C + help + Say Y or M if you want to add support for audioinjector.net Pi Hat + +config SND_AUDIOINJECTOR_OCTO_SOUNDCARD + tristate "Support for audioinjector.net Octo channel (Hat) soundcard" + select SND_SOC_CS42XX8_I2C + help + Say Y or M if you want to add support for audioinjector.net octo add on + +config SND_AUDIOINJECTOR_ISOLATED_SOUNDCARD + tristate "Support for audioinjector.net isolated DAC and ADC soundcard" + select SND_SOC_CS4271_I2C + help + Say Y or M if you want to add support for audioinjector.net isolated soundcard + +config SND_AUDIOSENSE_PI + tristate "Support for AudioSense Add-On Soundcard" + select SND_SOC_TLV320AIC32X4_I2C + help + Say Y or M if you want to add support for tlv320aic32x4 add-on + +config SND_DIGIDAC1_SOUNDCARD + tristate "Support for Red Rocks Audio DigiDAC1" + select SND_SOC_WM8804 + select SND_SOC_WM8741 + help + Say Y or M if you want to add support for Red Rocks Audio DigiDAC1 board. + +config SND_BCM2708_SOC_DIONAUDIO_LOCO + tristate "Support for Dion Audio LOCO DAC-AMP" + select SND_SOC_PCM5102a + help + Say Y or M if you want to add support for Dion Audio LOCO. + +config SND_BCM2708_SOC_DIONAUDIO_LOCO_V2 + tristate "Support for Dion Audio LOCO-V2 DAC-AMP" + select SND_SOC_PCM5122 + help + Say Y or M if you want to add support for Dion Audio LOCO-V2. + +config SND_BCM2708_SOC_ALLO_PIANO_DAC + tristate "Support for Allo Piano DAC" + select SND_SOC_PCM512x_I2C + help + Say Y or M if you want to add support for Allo Piano DAC. + +config SND_BCM2708_SOC_ALLO_PIANO_DAC_PLUS + tristate "Support for Allo Piano DAC Plus" + select SND_SOC_PCM512x_I2C + help + Say Y or M if you want to add support for Allo Piano DAC Plus. + +config SND_BCM2708_SOC_ALLO_BOSS_DAC + tristate "Support for Allo Boss DAC" + select SND_SOC_PCM512x_I2C + select COMMON_CLK_HIFIBERRY_DACPRO + help + Say Y or M if you want to add support for Allo Boss DAC. + +config SND_BCM2708_SOC_ALLO_BOSS2_DAC + tristate "Support for Allo Boss2 DAC" + depends on I2C + select REGMAP_I2C + select SND_AUDIO_GRAPH_CARD + help + Say Y or M if you want to add support for Allo Boss2 DAC. + +config SND_BCM2708_SOC_ALLO_DIGIONE + tristate "Support for Allo DigiOne" + select SND_SOC_WM8804 + select SND_RPI_WM8804_SOUNDCARD + help + Say Y or M if you want to add support for Allo DigiOne. + +config SND_BCM2708_SOC_ALLO_KATANA_DAC + tristate "Support for Allo Katana DAC" + depends on I2C + select REGMAP_I2C + select SND_AUDIO_GRAPH_CARD + help + Say Y or M if you want to add support for Allo Katana DAC. + +config SND_BCM2708_SOC_FE_PI_AUDIO + tristate "Support for Fe-Pi-Audio" + select SND_SOC_SGTL5000 + help + Say Y or M if you want to add support for Fe-Pi-Audio. + +config SND_PISOUND + tristate "Support for Blokas Labs pisound" + select SND_RAWMIDI + help + Say Y or M if you want to add support for Blokas Labs pisound. + +config SND_RPI_SIMPLE_SOUNDCARD + tristate "Support for Raspberry Pi simple soundcards" + help + Say Y or M if you want to add support Raspbery Pi simple soundcards + +config SND_RPI_WM8804_SOUNDCARD + tristate "Support for Raspberry Pi generic WM8804 soundcards" + help + Say Y or M if you want to add support for the Raspberry Pi + generic driver for WM8804 based soundcards. + +config SND_DACBERRY400 + tristate "Support for DACBERRY400 Soundcard" + select SND_SOC_TLV320AIC3X_I2C + help + Say Y or M if you want to add support for tlv320aic3x add-on diff --git a/sound/soc/bcm/Makefile b/sound/soc/bcm/Makefile index 0c1325a97b709f..15c3c4a742c603 100644 --- a/sound/soc/bcm/Makefile +++ b/sound/soc/bcm/Makefile @@ -12,4 +12,75 @@ obj-$(CONFIG_SND_SOC_CYGNUS) += snd-soc-cygnus.o # BCM63XX Platform Support snd-soc-63xx-y := bcm63xx-i2s-whistler.o bcm63xx-pcm-whistler.o -obj-$(CONFIG_SND_BCM63XX_I2S_WHISTLER) += snd-soc-63xx.o \ No newline at end of file +obj-$(CONFIG_SND_BCM63XX_I2S_WHISTLER) += snd-soc-63xx.o + +# Google voiceHAT custom codec support +snd-soc-googlevoicehat-codec-objs := googlevoicehat-codec.o + +# BCM2708 Machine Support +snd-soc-hifiberry-adc-objs := hifiberry_adc.o +snd-soc-hifiberry-dacplus-objs := hifiberry_dacplus.o +snd-soc-hifiberry-dacplushd-objs := hifiberry_dacplushd.o +snd-soc-hifiberry-dacplusadc-objs := hifiberry_dacplusadc.o +snd-soc-hifiberry-dacplusadcpro-objs := hifiberry_dacplusadcpro.o +snd-soc-hifiberry-dacplusdsp-objs := hifiberry_dacplusdsp.o +snd-soc-justboom-both-objs := justboom-both.o +snd-soc-justboom-dac-objs := justboom-dac.o +snd-soc-rpi-cirrus-objs := rpi-cirrus.o +snd-soc-rpi-proto-objs := rpi-proto.o +snd-soc-iqaudio-codec-objs := iqaudio-codec.o +snd-soc-iqaudio-dac-objs := iqaudio-dac.o + snd-soc-i-sabre-q2m-objs := i-sabre-q2m.o +snd-soc-audioinjector-pi-soundcard-objs := audioinjector-pi-soundcard.o +snd-soc-audioinjector-octo-soundcard-objs := audioinjector-octo-soundcard.o +snd-soc-audioinjector-isolated-soundcard-objs := audioinjector-isolated-soundcard.o +snd-soc-audiosense-pi-objs := audiosense-pi.o +snd-soc-digidac1-soundcard-objs := digidac1-soundcard.o +snd-soc-dionaudio-loco-objs := dionaudio_loco.o +snd-soc-dionaudio-loco-v2-objs := dionaudio_loco-v2.o +snd-soc-allo-boss-dac-objs := allo-boss-dac.o +snd-soc-allo-boss2-dac-objs := allo-boss2-dac.o +snd-soc-allo-piano-dac-objs := allo-piano-dac.o +snd-soc-allo-piano-dac-plus-objs := allo-piano-dac-plus.o +snd-soc-allo-katana-codec-objs := allo-katana-codec.o +snd-soc-pisound-objs := pisound.o +snd-soc-fe-pi-audio-objs := fe-pi-audio.o +snd-soc-rpi-simple-soundcard-objs := rpi-simple-soundcard.o +snd-soc-rpi-wm8804-soundcard-objs := rpi-wm8804-soundcard.o +snd-soc-pifi-40-objs := pifi-40.o +snd-soc-chipdip-dac-objs := chipdip-dac.o +snd-soc-dacberry400-objs := dacberry400.o + +obj-$(CONFIG_SND_BCM2708_SOC_GOOGLEVOICEHAT_SOUNDCARD) += snd-soc-googlevoicehat-codec.o +obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_ADC) += snd-soc-hifiberry-adc.o +obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUS) += snd-soc-hifiberry-dacplus.o +obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSHD) += snd-soc-hifiberry-dacplushd.o +obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSADC) += snd-soc-hifiberry-dacplusadc.o +obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSADCPRO) += snd-soc-hifiberry-dacplusadcpro.o +obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSDSP) += snd-soc-hifiberry-dacplusdsp.o +obj-$(CONFIG_SND_BCM2708_SOC_JUSTBOOM_BOTH) += snd-soc-justboom-both.o +obj-$(CONFIG_SND_BCM2708_SOC_JUSTBOOM_DAC) += snd-soc-justboom-dac.o +obj-$(CONFIG_SND_BCM2708_SOC_RPI_CIRRUS) += snd-soc-rpi-cirrus.o +obj-$(CONFIG_SND_BCM2708_SOC_RPI_PROTO) += snd-soc-rpi-proto.o +obj-$(CONFIG_SND_BCM2708_SOC_IQAUDIO_CODEC) += snd-soc-iqaudio-codec.o +obj-$(CONFIG_SND_BCM2708_SOC_IQAUDIO_DAC) += snd-soc-iqaudio-dac.o +obj-$(CONFIG_SND_BCM2708_SOC_I_SABRE_Q2M) += snd-soc-i-sabre-q2m.o +obj-$(CONFIG_SND_AUDIOINJECTOR_PI_SOUNDCARD) += snd-soc-audioinjector-pi-soundcard.o +obj-$(CONFIG_SND_AUDIOINJECTOR_OCTO_SOUNDCARD) += snd-soc-audioinjector-octo-soundcard.o +obj-$(CONFIG_SND_AUDIOINJECTOR_ISOLATED_SOUNDCARD) += snd-soc-audioinjector-isolated-soundcard.o +obj-$(CONFIG_SND_AUDIOSENSE_PI) += snd-soc-audiosense-pi.o +obj-$(CONFIG_SND_DIGIDAC1_SOUNDCARD) += snd-soc-digidac1-soundcard.o +obj-$(CONFIG_SND_BCM2708_SOC_DIONAUDIO_LOCO) += snd-soc-dionaudio-loco.o +obj-$(CONFIG_SND_BCM2708_SOC_DIONAUDIO_LOCO_V2) += snd-soc-dionaudio-loco-v2.o +obj-$(CONFIG_SND_BCM2708_SOC_ALLO_BOSS_DAC) += snd-soc-allo-boss-dac.o +obj-$(CONFIG_SND_BCM2708_SOC_ALLO_BOSS2_DAC) += snd-soc-allo-boss2-dac.o +obj-$(CONFIG_SND_BCM2708_SOC_ALLO_PIANO_DAC) += snd-soc-allo-piano-dac.o +obj-$(CONFIG_SND_BCM2708_SOC_ALLO_PIANO_DAC_PLUS) += snd-soc-allo-piano-dac-plus.o +obj-$(CONFIG_SND_BCM2708_SOC_ALLO_KATANA_DAC) += snd-soc-allo-katana-codec.o +obj-$(CONFIG_SND_PISOUND) += snd-soc-pisound.o +obj-$(CONFIG_SND_BCM2708_SOC_FE_PI_AUDIO) += snd-soc-fe-pi-audio.o +obj-$(CONFIG_SND_RPI_SIMPLE_SOUNDCARD) += snd-soc-rpi-simple-soundcard.o +obj-$(CONFIG_SND_RPI_WM8804_SOUNDCARD) += snd-soc-rpi-wm8804-soundcard.o +obj-$(CONFIG_SND_BCM2708_SOC_PIFI_40) += snd-soc-pifi-40.o +obj-$(CONFIG_SND_BCM2708_SOC_CHIPDIP_DAC) += snd-soc-chipdip-dac.o +obj-$(CONFIG_SND_DACBERRY400) += snd-soc-dacberry400.o diff --git a/sound/soc/bcm/allo-boss-dac.c b/sound/soc/bcm/allo-boss-dac.c new file mode 100644 index 00000000000000..aa0723b98fabb9 --- /dev/null +++ b/sound/soc/bcm/allo-boss-dac.c @@ -0,0 +1,470 @@ +/* + * ALSA ASoC Machine Driver for Allo Boss DAC + * + * Author: Baswaraj K <jaikumar@cem-solutions.net> + * Copyright 2017 + * based on code by Daniel Matuschek, + * Stuart MacLean <stuart@hifiberry.com> + * based on code by Florian Meier <florian.meier@koalo.de> + * + * 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 <linux/module.h> +#include <linux/gpio/consumer.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/delay.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include "../codecs/pcm512x.h" + +#define ALLO_BOSS_NOCLOCK 0 +#define ALLO_BOSS_CLK44EN 1 +#define ALLO_BOSS_CLK48EN 2 + +struct pcm512x_priv { + struct regmap *regmap; + struct clk *sclk; +}; + +static struct gpio_desc *mute_gpio; + +/* Clock rate of CLK44EN attached to GPIO6 pin */ +#define CLK_44EN_RATE 45158400UL +/* Clock rate of CLK48EN attached to GPIO3 pin */ +#define CLK_48EN_RATE 49152000UL + +static bool slave; +static bool snd_soc_allo_boss_master; +static bool digital_gain_0db_limit = true; + +static void snd_allo_boss_select_clk(struct snd_soc_component *component, + int clk_id) +{ + switch (clk_id) { + case ALLO_BOSS_NOCLOCK: + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x00); + break; + case ALLO_BOSS_CLK44EN: + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x20); + break; + case ALLO_BOSS_CLK48EN: + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x04); + break; + } +} + +static void snd_allo_boss_clk_gpio(struct snd_soc_component *component) +{ + snd_soc_component_update_bits(component, PCM512x_GPIO_EN, 0x24, 0x24); + snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_3, 0x0f, 0x02); + snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_6, 0x0f, 0x02); +} + +static bool snd_allo_boss_is_sclk(struct snd_soc_component *component) +{ + unsigned int sck; + + sck = snd_soc_component_read(component, PCM512x_RATE_DET_4); + return (!(sck & 0x40)); +} + +static bool snd_allo_boss_is_sclk_sleep( + struct snd_soc_component *component) +{ + msleep(2); + return snd_allo_boss_is_sclk(component); +} + +static bool snd_allo_boss_is_master_card(struct snd_soc_component *component) +{ + bool isClk44EN, isClk48En, isNoClk; + + snd_allo_boss_clk_gpio(component); + + snd_allo_boss_select_clk(component, ALLO_BOSS_CLK44EN); + isClk44EN = snd_allo_boss_is_sclk_sleep(component); + + snd_allo_boss_select_clk(component, ALLO_BOSS_NOCLOCK); + isNoClk = snd_allo_boss_is_sclk_sleep(component); + + snd_allo_boss_select_clk(component, ALLO_BOSS_CLK48EN); + isClk48En = snd_allo_boss_is_sclk_sleep(component); + + return (isClk44EN && isClk48En && !isNoClk); +} + +static int snd_allo_boss_clk_for_rate(int sample_rate) +{ + int type; + + switch (sample_rate) { + case 11025: + case 22050: + case 44100: + case 88200: + case 176400: + case 352800: + type = ALLO_BOSS_CLK44EN; + break; + default: + type = ALLO_BOSS_CLK48EN; + break; + } + return type; +} + +static void snd_allo_boss_set_sclk(struct snd_soc_component *component, + int sample_rate) +{ + struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component); + + if (!IS_ERR(pcm512x->sclk)) { + int ctype; + + ctype = snd_allo_boss_clk_for_rate(sample_rate); + clk_set_rate(pcm512x->sclk, (ctype == ALLO_BOSS_CLK44EN) + ? CLK_44EN_RATE : CLK_48EN_RATE); + snd_allo_boss_select_clk(component, ctype); + } +} + +static int snd_allo_boss_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + struct pcm512x_priv *priv = snd_soc_component_get_drvdata(component); + + if (slave) + snd_soc_allo_boss_master = false; + else + snd_soc_allo_boss_master = + snd_allo_boss_is_master_card(component); + + if (snd_soc_allo_boss_master) { + struct snd_soc_dai_link *dai = rtd->dai_link; + + dai->name = "BossDAC"; + dai->stream_name = "Boss DAC HiFi [Master]"; + dai->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM; + + snd_soc_component_update_bits(component, PCM512x_BCLK_LRCLK_CFG, 0x31, 0x11); + snd_soc_component_update_bits(component, PCM512x_MASTER_MODE, 0x03, 0x03); + snd_soc_component_update_bits(component, PCM512x_MASTER_CLKDIV_2, 0x7f, 63); + /* + * Default sclk to CLK_48EN_RATE, otherwise codec + * pcm512x_dai_startup_master method could call + * snd_pcm_hw_constraint_ratnums using CLK_44EN/64 + * which will mask 384k sample rate. + */ + if (!IS_ERR(priv->sclk)) + clk_set_rate(priv->sclk, CLK_48EN_RATE); + } else { + priv->sclk = ERR_PTR(-ENOENT); + } + + snd_soc_component_update_bits(component, PCM512x_GPIO_EN, 0x08, 0x08); + snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_4, 0x0f, 0x02); + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x08); + + if (digital_gain_0db_limit) { + int ret; + struct snd_soc_card *card = rtd->card; + + ret = snd_soc_limit_volume(card, "Digital Playback Volume", + 207); + if (ret < 0) + dev_warn(card->dev, "Failed to set volume limit: %d\n", + ret); + } + + return 0; +} + +static int snd_allo_boss_update_rate_den( + struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component); + struct snd_ratnum *rats_no_pll; + unsigned int num = 0, den = 0; + int err; + + rats_no_pll = devm_kzalloc(rtd->dev, sizeof(*rats_no_pll), GFP_KERNEL); + if (!rats_no_pll) + return -ENOMEM; + + rats_no_pll->num = clk_get_rate(pcm512x->sclk) / 64; + rats_no_pll->den_min = 1; + rats_no_pll->den_max = 128; + rats_no_pll->den_step = 1; + + err = snd_interval_ratnum(hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE), 1, rats_no_pll, &num, &den); + if (err >= 0 && den) { + params->rate_num = num; + params->rate_den = den; + } + + devm_kfree(rtd->dev, rats_no_pll); + return 0; +} + +static void snd_allo_boss_gpio_mute(struct snd_soc_card *card) +{ + if (mute_gpio) + gpiod_set_value_cansleep(mute_gpio, 1); +} + +static void snd_allo_boss_gpio_unmute(struct snd_soc_card *card) +{ + if (mute_gpio) + gpiod_set_value_cansleep(mute_gpio, 0); +} + +static int snd_allo_boss_set_bias_level(struct snd_soc_card *card, + struct snd_soc_dapm_context *dapm, enum snd_soc_bias_level level) +{ + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_dai *codec_dai; + + rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]); + codec_dai = snd_soc_rtd_to_codec(rtd, 0); + + if (dapm->dev != codec_dai->dev) + return 0; + + switch (level) { + case SND_SOC_BIAS_PREPARE: + if (dapm->bias_level != SND_SOC_BIAS_STANDBY) + break; + /* UNMUTE DAC */ + snd_allo_boss_gpio_unmute(card); + break; + + case SND_SOC_BIAS_STANDBY: + if (dapm->bias_level != SND_SOC_BIAS_PREPARE) + break; + /* MUTE DAC */ + snd_allo_boss_gpio_mute(card); + break; + + default: + break; + } + + return 0; +} + +static int snd_allo_boss_hw_params( + struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) +{ + int ret = 0; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + int channels = params_channels(params); + int width = snd_pcm_format_width(params_format(params)); + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + struct snd_soc_card *card = rtd->card; + + /* Using powers of 2 allows for an integer clock divisor */ + width = width <= 16 ? 16 : 32; + + /* Mute before changing sample rate */ + snd_allo_boss_gpio_mute(card); + + if (snd_soc_allo_boss_master) { + snd_allo_boss_set_sclk(component, params_rate(params)); + + ret = snd_allo_boss_update_rate_den(substream, params); + if (ret) + goto error; + } + + ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_cpu(rtd, 0), channels * width); + + if (ret) + goto error; + + ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_codec(rtd, 0), channels * width); + + if (ret) + goto error; + + /* Unmute after setting parameters or having an error */ +error: + snd_allo_boss_gpio_unmute(card); + + return ret; +} + +static int snd_allo_boss_startup( + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + struct snd_soc_card *card = rtd->card; + + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x08); + snd_allo_boss_gpio_mute(card); + + if (snd_soc_allo_boss_master) { + struct pcm512x_priv *priv = snd_soc_component_get_drvdata(component); + /* + * Default sclk to CLK_48EN_RATE, otherwise codec + * pcm512x_dai_startup_master method could call + * snd_pcm_hw_constraint_ratnums using CLK_44EN/64 + * which will mask 384k sample rate. + */ + if (!IS_ERR(priv->sclk)) + clk_set_rate(priv->sclk, CLK_48EN_RATE); + } + + return 0; +} + +static void snd_allo_boss_shutdown( + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x00); +} + +static int snd_allo_boss_prepare( + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + + snd_allo_boss_gpio_unmute(card); + return 0; +} +/* machine stream operations */ +static struct snd_soc_ops snd_allo_boss_ops = { + .hw_params = snd_allo_boss_hw_params, + .startup = snd_allo_boss_startup, + .shutdown = snd_allo_boss_shutdown, + .prepare = snd_allo_boss_prepare, +}; + +SND_SOC_DAILINK_DEFS(allo_boss, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004d", "pcm512x-hifi")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0"))); + +static struct snd_soc_dai_link snd_allo_boss_dai[] = { +{ + .name = "Boss DAC", + .stream_name = "Boss DAC HiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &snd_allo_boss_ops, + .init = snd_allo_boss_init, + SND_SOC_DAILINK_REG(allo_boss), +}, +}; + +/* audio machine driver */ +static struct snd_soc_card snd_allo_boss = { + .name = "BossDAC", + .owner = THIS_MODULE, + .dai_link = snd_allo_boss_dai, + .num_links = ARRAY_SIZE(snd_allo_boss_dai), +}; + +static int snd_allo_boss_probe(struct platform_device *pdev) +{ + int ret = 0; + + snd_allo_boss.dev = &pdev->dev; + + if (pdev->dev.of_node) { + struct device_node *i2s_node; + struct snd_soc_dai_link *dai; + + dai = &snd_allo_boss_dai[0]; + i2s_node = of_parse_phandle(pdev->dev.of_node, + "i2s-controller", 0); + + if (i2s_node) { + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + } + + digital_gain_0db_limit = !of_property_read_bool( + pdev->dev.of_node, "allo,24db_digital_gain"); + slave = of_property_read_bool(pdev->dev.of_node, + "allo,slave"); + + mute_gpio = devm_gpiod_get_optional(&pdev->dev, "mute", + GPIOD_OUT_LOW); + if (IS_ERR(mute_gpio)) { + ret = PTR_ERR(mute_gpio); + dev_err(&pdev->dev, + "failed to get mute gpio: %d\n", ret); + return ret; + } + + if (mute_gpio) + snd_allo_boss.set_bias_level = + snd_allo_boss_set_bias_level; + + ret = snd_soc_register_card(&snd_allo_boss); + if (ret) { + dev_err(&pdev->dev, + "snd_soc_register_card() failed: %d\n", ret); + return ret; + } + + if (mute_gpio) + snd_allo_boss_gpio_mute(&snd_allo_boss); + + return 0; + } + + return -EINVAL; +} + +static void snd_allo_boss_remove(struct platform_device *pdev) +{ + snd_allo_boss_gpio_mute(&snd_allo_boss); + snd_soc_unregister_card(&snd_allo_boss); +} + +static const struct of_device_id snd_allo_boss_of_match[] = { + { .compatible = "allo,boss-dac", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, snd_allo_boss_of_match); + +static struct platform_driver snd_allo_boss_driver = { + .driver = { + .name = "snd-allo-boss-dac", + .owner = THIS_MODULE, + .of_match_table = snd_allo_boss_of_match, + }, + .probe = snd_allo_boss_probe, + .remove = snd_allo_boss_remove, +}; + +module_platform_driver(snd_allo_boss_driver); + +MODULE_AUTHOR("Baswaraj K <jaikumar@cem-solutions.net>"); +MODULE_DESCRIPTION("ALSA ASoC Machine Driver for Allo Boss DAC"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/allo-boss2-dac.c b/sound/soc/bcm/allo-boss2-dac.c new file mode 100644 index 00000000000000..20dededd919642 --- /dev/null +++ b/sound/soc/bcm/allo-boss2-dac.c @@ -0,0 +1,1130 @@ +/* + * Driver for the ALLO KATANA CODEC + * + * Author: Jaikumar <sudeepkumar@cem-solutions.net> + * Copyright 2018 + * + * 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 <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/gpio/consumer.h> +#include <linux/platform_device.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/of_device.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> +#include <sound/tlv.h> +#include <linux/of_gpio.h> +#include <linux/regulator/consumer.h> +#include <linux/pm_runtime.h> +#include <linux/of_irq.h> +#include <linux/completion.h> +#include <linux/mutex.h> +#include <linux/workqueue.h> +#include <sound/jack.h> + +#include "../codecs/cs43130.h" + +#include <linux/clk.h> +#include <linux/gcd.h> +#define DEBUG + +#define CS43130_DSD_EN_MASK 0x10 +#define CS43130_PDN_DONE_INT_MASK 0x00 + +static struct gpio_desc *snd_allo_clk44gpio; +static struct gpio_desc *snd_allo_clk48gpio; + +struct cs43130_priv { + struct snd_soc_component *component; + struct regmap *regmap; + struct regulator_bulk_data supplies[CS43130_NUM_SUPPLIES]; + struct gpio_desc *reset_gpio; + unsigned int dev_id; /* codec device ID */ + int xtal_ibias; + /* shared by both DAIs */ + struct mutex clk_mutex; + int clk_req; + bool pll_bypass; + struct completion xtal_rdy; + struct completion pll_rdy; + unsigned int mclk; + unsigned int mclk_int; + int mclk_int_src; + + /* DAI specific */ + struct cs43130_dai dais[CS43130_DAI_ID_MAX]; + + /* HP load specific */ + bool dc_meas; + bool ac_meas; + bool hpload_done; + struct completion hpload_evt; + unsigned int hpload_stat; + u16 hpload_dc[2]; + u16 dc_threshold[CS43130_DC_THRESHOLD]; + u16 ac_freq[CS43130_AC_FREQ]; + u16 hpload_ac[CS43130_AC_FREQ][2]; + struct workqueue_struct *wq; + struct work_struct work; + struct snd_soc_jack jack; +}; + +static const struct reg_default cs43130_reg_defaults[] = { + {CS43130_SYS_CLK_CTL_1, 0x06}, + {CS43130_SP_SRATE, 0x01}, + {CS43130_SP_BITSIZE, 0x05}, + {CS43130_PAD_INT_CFG, 0x03}, + {CS43130_PWDN_CTL, 0xFE}, + {CS43130_CRYSTAL_SET, 0x04}, + {CS43130_PLL_SET_1, 0x00}, + {CS43130_PLL_SET_2, 0x00}, + {CS43130_PLL_SET_3, 0x00}, + {CS43130_PLL_SET_4, 0x00}, + {CS43130_PLL_SET_5, 0x40}, + {CS43130_PLL_SET_6, 0x10}, + {CS43130_PLL_SET_7, 0x80}, + {CS43130_PLL_SET_8, 0x03}, + {CS43130_PLL_SET_9, 0x02}, + {CS43130_PLL_SET_10, 0x02}, + {CS43130_CLKOUT_CTL, 0x00}, + {CS43130_ASP_NUM_1, 0x01}, + {CS43130_ASP_NUM_2, 0x00}, + {CS43130_ASP_DEN_1, 0x08}, + {CS43130_ASP_DEN_2, 0x00}, + {CS43130_ASP_LRCK_HI_TIME_1, 0x1F}, + {CS43130_ASP_LRCK_HI_TIME_2, 0x00}, + {CS43130_ASP_LRCK_PERIOD_1, 0x3F}, + {CS43130_ASP_LRCK_PERIOD_2, 0x00}, + {CS43130_ASP_CLOCK_CONF, 0x0C}, + {CS43130_ASP_FRAME_CONF, 0x0A}, + {CS43130_XSP_NUM_1, 0x01}, + {CS43130_XSP_NUM_2, 0x00}, + {CS43130_XSP_DEN_1, 0x02}, + {CS43130_XSP_DEN_2, 0x00}, + {CS43130_XSP_LRCK_HI_TIME_1, 0x1F}, + {CS43130_XSP_LRCK_HI_TIME_2, 0x00}, + {CS43130_XSP_LRCK_PERIOD_1, 0x3F}, + {CS43130_XSP_LRCK_PERIOD_2, 0x00}, + {CS43130_XSP_CLOCK_CONF, 0x0C}, + {CS43130_XSP_FRAME_CONF, 0x0A}, + {CS43130_ASP_CH_1_LOC, 0x00}, + {CS43130_ASP_CH_2_LOC, 0x00}, + {CS43130_ASP_CH_1_SZ_EN, 0x06}, + {CS43130_ASP_CH_2_SZ_EN, 0x0E}, + {CS43130_XSP_CH_1_LOC, 0x00}, + {CS43130_XSP_CH_2_LOC, 0x00}, + {CS43130_XSP_CH_1_SZ_EN, 0x06}, + {CS43130_XSP_CH_2_SZ_EN, 0x0E}, + {CS43130_DSD_VOL_B, 0x78}, + {CS43130_DSD_VOL_A, 0x78}, + {CS43130_DSD_PATH_CTL_1, 0xA8}, + {CS43130_DSD_INT_CFG, 0x00}, + {CS43130_DSD_PATH_CTL_2, 0x02}, + {CS43130_DSD_PCM_MIX_CTL, 0x00}, + {CS43130_DSD_PATH_CTL_3, 0x40}, + {CS43130_HP_OUT_CTL_1, 0x30}, + {CS43130_PCM_FILT_OPT, 0x02}, + {CS43130_PCM_VOL_B, 0x78}, + {CS43130_PCM_VOL_A, 0x78}, + {CS43130_PCM_PATH_CTL_1, 0xA8}, + {CS43130_PCM_PATH_CTL_2, 0x00}, + {CS43130_CLASS_H_CTL, 0x1E}, + {CS43130_HP_DETECT, 0x04}, + {CS43130_HP_LOAD_1, 0x00}, + {CS43130_HP_MEAS_LOAD_1, 0x00}, + {CS43130_HP_MEAS_LOAD_2, 0x00}, + {CS43130_INT_MASK_1, 0xFF}, + {CS43130_INT_MASK_2, 0xFF}, + {CS43130_INT_MASK_3, 0xFF}, + {CS43130_INT_MASK_4, 0xFF}, + {CS43130_INT_MASK_5, 0xFF}, +}; +static bool cs43130_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS43130_INT_STATUS_1 ... CS43130_INT_STATUS_5: + case CS43130_HP_DC_STAT_1 ... CS43130_HP_DC_STAT_2: + case CS43130_HP_AC_STAT_1 ... CS43130_HP_AC_STAT_2: + return true; + default: + return false; + } +} + +static const char * const pcm_spd_texts[] = { + "Fast", + "Slow", +}; + +static SOC_ENUM_SINGLE_DECL(pcm_spd_enum, CS43130_PCM_FILT_OPT, 7, + pcm_spd_texts); + +static const SNDRV_CTL_TLVD_DECLARE_DB_MINMAX(master_tlv, -12750, 0); + +static const struct snd_kcontrol_new cs43130_controls[] = { + SOC_DOUBLE_R_TLV("Master Playback Volume", CS43130_PCM_VOL_B, + CS43130_PCM_VOL_A, 0, 255, 1, master_tlv), + SOC_DOUBLE("Master Playback Switch", CS43130_PCM_PATH_CTL_1, + 0, 1, 1, 1), + SOC_DOUBLE_R_TLV("Digital Playback Volume", CS43130_DSD_VOL_B, + CS43130_DSD_VOL_A, 0, 255, 1, master_tlv), + SOC_DOUBLE("Digital Playback Switch", CS43130_DSD_PATH_CTL_1, + 0, 1, 1, 1), + SOC_SINGLE("HV_Enable", CS43130_HP_OUT_CTL_1, 0, 1, 0), + SOC_ENUM("PCM Filter Speed", pcm_spd_enum), + SOC_SINGLE("PCM Phase Compensation", CS43130_PCM_FILT_OPT, 6, 1, 0), + SOC_SINGLE("PCM Nonoversample Emulate", CS43130_PCM_FILT_OPT, 5, 1, 0), + SOC_SINGLE("PCM High-pass Filter", CS43130_PCM_FILT_OPT, 1, 1, 0), + SOC_SINGLE("PCM De-emphasis Filter", CS43130_PCM_FILT_OPT, 0, 1, 0), +}; + +static bool cs43130_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS43130_DEVID_AB ... CS43130_SYS_CLK_CTL_1: + case CS43130_SP_SRATE ... CS43130_PAD_INT_CFG: + case CS43130_PWDN_CTL: + case CS43130_CRYSTAL_SET: + case CS43130_PLL_SET_1 ... CS43130_PLL_SET_5: + case CS43130_PLL_SET_6: + case CS43130_PLL_SET_7: + case CS43130_PLL_SET_8: + case CS43130_PLL_SET_9: + case CS43130_PLL_SET_10: + case CS43130_CLKOUT_CTL: + case CS43130_ASP_NUM_1 ... CS43130_ASP_FRAME_CONF: + case CS43130_XSP_NUM_1 ... CS43130_XSP_FRAME_CONF: + case CS43130_ASP_CH_1_LOC: + case CS43130_ASP_CH_2_LOC: + case CS43130_ASP_CH_1_SZ_EN: + case CS43130_ASP_CH_2_SZ_EN: + case CS43130_XSP_CH_1_LOC: + case CS43130_XSP_CH_2_LOC: + case CS43130_XSP_CH_1_SZ_EN: + case CS43130_XSP_CH_2_SZ_EN: + case CS43130_DSD_VOL_B ... CS43130_DSD_PATH_CTL_3: + case CS43130_HP_OUT_CTL_1: + case CS43130_PCM_FILT_OPT ... CS43130_PCM_PATH_CTL_2: + case CS43130_CLASS_H_CTL: + case CS43130_HP_DETECT: + case CS43130_HP_STATUS: + case CS43130_HP_LOAD_1: + case CS43130_HP_MEAS_LOAD_1: + case CS43130_HP_MEAS_LOAD_2: + case CS43130_HP_DC_STAT_1: + case CS43130_HP_DC_STAT_2: + case CS43130_HP_AC_STAT_1: + case CS43130_HP_AC_STAT_2: + case CS43130_HP_LOAD_STAT: + case CS43130_INT_STATUS_1 ... CS43130_INT_STATUS_5: + case CS43130_INT_MASK_1 ... CS43130_INT_MASK_5: + return true; + default: + return false; + } +} +static bool cs43130_precious_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS43130_INT_STATUS_1 ... CS43130_INT_STATUS_5: + return true; + default: + return false; + } +} +static int cs43130_pcm_pdn(struct snd_soc_component *component) +{ + struct cs43130_priv *cs43130 = + snd_soc_component_get_drvdata(component); + int ret; + unsigned int reg, pdn_int; + + regmap_write(cs43130->regmap, CS43130_DSD_PATH_CTL_2, 0x02); + regmap_update_bits(cs43130->regmap, CS43130_INT_MASK_1, + CS43130_PDN_DONE_INT_MASK, 0); + regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL, + CS43130_PDN_HP_MASK, 1 << CS43130_PDN_HP_SHIFT); + usleep_range(10, 50); + ret = regmap_read(cs43130->regmap, CS43130_INT_STATUS_1, ®); + pdn_int = reg & 0xFE; + regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL, + CS43130_PDN_ASP_MASK, 1 << CS43130_PDN_ASP_SHIFT); + return 0; + +} +static int cs43130_pwr_up_asp_dac(struct snd_soc_component *component) +{ + struct cs43130_priv *cs43130 = + snd_soc_component_get_drvdata(component); + + regmap_update_bits(cs43130->regmap, CS43130_PAD_INT_CFG, + CS43130_ASP_3ST_MASK, 0); + regmap_write(cs43130->regmap, CS43130_DXD1, 0x99); + regmap_write(cs43130->regmap, CS43130_DXD13, 0x20); + regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL, + CS43130_PDN_ASP_MASK, 0); + regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL, + CS43130_PDN_HP_MASK, 0); + usleep_range(10000, 12000); + regmap_write(cs43130->regmap, CS43130_DXD1, 0x00); + regmap_write(cs43130->regmap, CS43130_DXD13, 0x00); + return 0; +} +static int cs43130_change_clksrc(struct snd_soc_component *component, + enum cs43130_mclk_src_sel src) +{ + int ret; + struct cs43130_priv *cs43130 = + snd_soc_component_get_drvdata(component); + int mclk_int_decoded; + + if (src == cs43130->mclk_int_src) { + /* clk source has not changed */ + return 0; + } + switch (cs43130->mclk_int) { + case CS43130_MCLK_22M: + mclk_int_decoded = CS43130_MCLK_22P5; + break; + case CS43130_MCLK_24M: + mclk_int_decoded = CS43130_MCLK_24P5; + break; + default: + dev_err(component->dev, "Invalid MCLK INT freq: %u\n", + cs43130->mclk_int); + return -EINVAL; + } + + switch (src) { + case CS43130_MCLK_SRC_EXT: + cs43130->pll_bypass = true; + cs43130->mclk_int_src = CS43130_MCLK_SRC_EXT; + if (cs43130->xtal_ibias == CS43130_XTAL_UNUSED) { + regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL, + CS43130_PDN_XTAL_MASK, + 1 << CS43130_PDN_XTAL_SHIFT); + } else { + reinit_completion(&cs43130->xtal_rdy); + regmap_update_bits(cs43130->regmap, CS43130_INT_MASK_1, + CS43130_XTAL_RDY_INT_MASK, 0); + regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL, + CS43130_PDN_XTAL_MASK, 0); + ret = wait_for_completion_timeout(&cs43130->xtal_rdy, + msecs_to_jiffies(100)); + regmap_update_bits(cs43130->regmap, CS43130_INT_MASK_1, + CS43130_XTAL_RDY_INT_MASK, + 1 << CS43130_XTAL_RDY_INT_SHIFT); + if (ret == 0) { + dev_err(component->dev, "Timeout waiting for XTAL_READY interrupt\n"); + return -ETIMEDOUT; + } + } + regmap_update_bits(cs43130->regmap, CS43130_SYS_CLK_CTL_1, + CS43130_MCLK_SRC_SEL_MASK, + src << CS43130_MCLK_SRC_SEL_SHIFT); + regmap_update_bits(cs43130->regmap, CS43130_SYS_CLK_CTL_1, + CS43130_MCLK_INT_MASK, + mclk_int_decoded << CS43130_MCLK_INT_SHIFT); + usleep_range(150, 200); + regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL, + CS43130_PDN_PLL_MASK, + 1 << CS43130_PDN_PLL_SHIFT); + break; + case CS43130_MCLK_SRC_RCO: + cs43130->mclk_int_src = CS43130_MCLK_SRC_RCO; + + regmap_update_bits(cs43130->regmap, CS43130_SYS_CLK_CTL_1, + CS43130_MCLK_SRC_SEL_MASK, + src << CS43130_MCLK_SRC_SEL_SHIFT); + regmap_update_bits(cs43130->regmap, CS43130_SYS_CLK_CTL_1, + CS43130_MCLK_INT_MASK, + CS43130_MCLK_22P5 << CS43130_MCLK_INT_SHIFT); + usleep_range(150, 200); + regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL, + CS43130_PDN_XTAL_MASK, + 1 << CS43130_PDN_XTAL_SHIFT); + regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL, + CS43130_PDN_PLL_MASK, + 1 << CS43130_PDN_PLL_SHIFT); + break; + default: + dev_err(component->dev, "Invalid MCLK source value\n"); + return -EINVAL; + } + + return 0; +} +static const struct cs43130_bitwidth_map cs43130_bitwidth_table[] = { + {8, CS43130_SP_BIT_SIZE_8, CS43130_CH_BIT_SIZE_8}, + {16, CS43130_SP_BIT_SIZE_16, CS43130_CH_BIT_SIZE_16}, + {24, CS43130_SP_BIT_SIZE_24, CS43130_CH_BIT_SIZE_24}, + {32, CS43130_SP_BIT_SIZE_32, CS43130_CH_BIT_SIZE_32}, +}; + +static const struct cs43130_bitwidth_map *cs43130_get_bitwidth_table( + unsigned int bitwidth) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cs43130_bitwidth_table); i++) { + if (cs43130_bitwidth_table[i].bitwidth == bitwidth) + return &cs43130_bitwidth_table[i]; + } + + return NULL; +} +static int cs43130_set_bitwidth(int dai_id, unsigned int bitwidth_dai, + struct regmap *regmap) +{ + const struct cs43130_bitwidth_map *bw_map; + + bw_map = cs43130_get_bitwidth_table(bitwidth_dai); + if (!bw_map) + return -EINVAL; + + switch (dai_id) { + case CS43130_ASP_PCM_DAI: + case CS43130_ASP_DOP_DAI: + regmap_update_bits(regmap, CS43130_ASP_CH_1_SZ_EN, + CS43130_CH_BITSIZE_MASK, bw_map->ch_bit); + regmap_update_bits(regmap, CS43130_ASP_CH_2_SZ_EN, + CS43130_CH_BITSIZE_MASK, bw_map->ch_bit); + regmap_update_bits(regmap, CS43130_SP_BITSIZE, + CS43130_ASP_BITSIZE_MASK, bw_map->sp_bit); + break; + case CS43130_XSP_DOP_DAI: + regmap_update_bits(regmap, CS43130_XSP_CH_1_SZ_EN, + CS43130_CH_BITSIZE_MASK, bw_map->ch_bit); + regmap_update_bits(regmap, CS43130_XSP_CH_2_SZ_EN, + CS43130_CH_BITSIZE_MASK, bw_map->ch_bit); + regmap_update_bits(regmap, CS43130_SP_BITSIZE, + CS43130_XSP_BITSIZE_MASK, bw_map->sp_bit << + CS43130_XSP_BITSIZE_SHIFT); + break; + default: + return -EINVAL; + } + + return 0; +} +static const struct cs43130_rate_map cs43130_rate_table[] = { + {32000, CS43130_ASP_SPRATE_32K}, + {44100, CS43130_ASP_SPRATE_44_1K}, + {48000, CS43130_ASP_SPRATE_48K}, + {88200, CS43130_ASP_SPRATE_88_2K}, + {96000, CS43130_ASP_SPRATE_96K}, + {176400, CS43130_ASP_SPRATE_176_4K}, + {192000, CS43130_ASP_SPRATE_192K}, + {352800, CS43130_ASP_SPRATE_352_8K}, + {384000, CS43130_ASP_SPRATE_384K}, +}; + +static const struct cs43130_rate_map *cs43130_get_rate_table(int fs) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cs43130_rate_table); i++) { + if (cs43130_rate_table[i].fs == fs) + return &cs43130_rate_table[i]; + } + + return NULL; +} + +static const struct cs43130_clk_gen *cs43130_get_clk_gen(int mclk_int, int fs, + const struct cs43130_clk_gen *clk_gen_table, int len_clk_gen_table) +{ + int i; + + for (i = 0; i < len_clk_gen_table; i++) { + if (clk_gen_table[i].mclk_int == mclk_int && + clk_gen_table[i].fs == fs) + return &clk_gen_table[i]; + } + return NULL; +} + +static int cs43130_set_sp_fmt(int dai_id, unsigned int bitwidth_sclk, + struct snd_pcm_hw_params *params, + struct cs43130_priv *cs43130) +{ + u16 frm_size; + u16 hi_size; + u8 frm_delay; + u8 frm_phase; + u8 frm_data; + u8 sclk_edge; + u8 lrck_edge; + u8 clk_data; + u8 loc_ch1; + u8 loc_ch2; + u8 dai_mode_val; + const struct cs43130_clk_gen *clk_gen; + + switch (cs43130->dais[dai_id].dai_format) { + case SND_SOC_DAIFMT_I2S: + hi_size = bitwidth_sclk; + frm_delay = 2; + frm_phase = 0; + break; + case SND_SOC_DAIFMT_LEFT_J: + hi_size = bitwidth_sclk; + frm_delay = 2; + frm_phase = 1; + break; + case SND_SOC_DAIFMT_DSP_A: + hi_size = 1; + frm_delay = 2; + frm_phase = 1; + break; + case SND_SOC_DAIFMT_DSP_B: + hi_size = 1; + frm_delay = 0; + frm_phase = 1; + break; + default: + return -EINVAL; + } + switch (cs43130->dais[dai_id].dai_mode) { + case SND_SOC_DAIFMT_CBS_CFS: + dai_mode_val = 0; + break; + case SND_SOC_DAIFMT_CBM_CFM: + dai_mode_val = 1; + break; + default: + return -EINVAL; + } + + frm_size = bitwidth_sclk * params_channels(params); + sclk_edge = 1; + lrck_edge = 0; + loc_ch1 = 0; + loc_ch2 = bitwidth_sclk * (params_channels(params) - 1); + + frm_data = frm_delay & CS43130_SP_FSD_MASK; + frm_data |= (frm_phase << CS43130_SP_STP_SHIFT) & CS43130_SP_STP_MASK; + + clk_data = lrck_edge & CS43130_SP_LCPOL_IN_MASK; + clk_data |= (lrck_edge << CS43130_SP_LCPOL_OUT_SHIFT) & + CS43130_SP_LCPOL_OUT_MASK; + clk_data |= (sclk_edge << CS43130_SP_SCPOL_IN_SHIFT) & + CS43130_SP_SCPOL_IN_MASK; + clk_data |= (sclk_edge << CS43130_SP_SCPOL_OUT_SHIFT) & + CS43130_SP_SCPOL_OUT_MASK; + clk_data |= (dai_mode_val << CS43130_SP_MODE_SHIFT) & + CS43130_SP_MODE_MASK; + switch (dai_id) { + case CS43130_ASP_PCM_DAI: + case CS43130_ASP_DOP_DAI: + regmap_update_bits(cs43130->regmap, CS43130_ASP_LRCK_PERIOD_1, + CS43130_SP_LCPR_DATA_MASK, (frm_size - 1) >> + CS43130_SP_LCPR_LSB_DATA_SHIFT); + regmap_update_bits(cs43130->regmap, CS43130_ASP_LRCK_PERIOD_2, + CS43130_SP_LCPR_DATA_MASK, (frm_size - 1) >> + CS43130_SP_LCPR_MSB_DATA_SHIFT); + regmap_update_bits(cs43130->regmap, CS43130_ASP_LRCK_HI_TIME_1, + CS43130_SP_LCHI_DATA_MASK, (hi_size - 1) >> + CS43130_SP_LCHI_LSB_DATA_SHIFT); + regmap_update_bits(cs43130->regmap, CS43130_ASP_LRCK_HI_TIME_2, + CS43130_SP_LCHI_DATA_MASK, (hi_size - 1) >> + CS43130_SP_LCHI_MSB_DATA_SHIFT); + regmap_write(cs43130->regmap, CS43130_ASP_FRAME_CONF, frm_data); + regmap_write(cs43130->regmap, CS43130_ASP_CH_1_LOC, loc_ch1); + regmap_write(cs43130->regmap, CS43130_ASP_CH_2_LOC, loc_ch2); + regmap_update_bits(cs43130->regmap, CS43130_ASP_CH_1_SZ_EN, + CS43130_CH_EN_MASK, 1 << CS43130_CH_EN_SHIFT); + regmap_update_bits(cs43130->regmap, CS43130_ASP_CH_2_SZ_EN, + CS43130_CH_EN_MASK, 1 << CS43130_CH_EN_SHIFT); + regmap_write(cs43130->regmap, CS43130_ASP_CLOCK_CONF, clk_data); + break; + case CS43130_XSP_DOP_DAI: + regmap_update_bits(cs43130->regmap, CS43130_XSP_LRCK_PERIOD_1, + CS43130_SP_LCPR_DATA_MASK, (frm_size - 1) >> + CS43130_SP_LCPR_LSB_DATA_SHIFT); + regmap_update_bits(cs43130->regmap, CS43130_XSP_LRCK_PERIOD_2, + CS43130_SP_LCPR_DATA_MASK, (frm_size - 1) >> + CS43130_SP_LCPR_MSB_DATA_SHIFT); + regmap_update_bits(cs43130->regmap, CS43130_XSP_LRCK_HI_TIME_1, + CS43130_SP_LCHI_DATA_MASK, (hi_size - 1) >> + CS43130_SP_LCHI_LSB_DATA_SHIFT); + regmap_update_bits(cs43130->regmap, CS43130_XSP_LRCK_HI_TIME_2, + CS43130_SP_LCHI_DATA_MASK, (hi_size - 1) >> + CS43130_SP_LCHI_MSB_DATA_SHIFT); + regmap_write(cs43130->regmap, CS43130_XSP_FRAME_CONF, frm_data); + regmap_write(cs43130->regmap, CS43130_XSP_CH_1_LOC, loc_ch1); + regmap_write(cs43130->regmap, CS43130_XSP_CH_2_LOC, loc_ch2); + regmap_update_bits(cs43130->regmap, CS43130_XSP_CH_1_SZ_EN, + CS43130_CH_EN_MASK, 1 << CS43130_CH_EN_SHIFT); + regmap_update_bits(cs43130->regmap, CS43130_XSP_CH_2_SZ_EN, + CS43130_CH_EN_MASK, 1 << CS43130_CH_EN_SHIFT); + regmap_write(cs43130->regmap, CS43130_XSP_CLOCK_CONF, clk_data); + break; + default: + return -EINVAL; + } + switch (frm_size) { + case 16: + clk_gen = cs43130_get_clk_gen(cs43130->mclk_int, + params_rate(params), + cs43130_16_clk_gen, + ARRAY_SIZE(cs43130_16_clk_gen)); + break; + case 32: + clk_gen = cs43130_get_clk_gen(cs43130->mclk_int, + params_rate(params), + cs43130_32_clk_gen, + ARRAY_SIZE(cs43130_32_clk_gen)); + break; + case 48: + clk_gen = cs43130_get_clk_gen(cs43130->mclk_int, + params_rate(params), + cs43130_48_clk_gen, + ARRAY_SIZE(cs43130_48_clk_gen)); + break; + case 64: + clk_gen = cs43130_get_clk_gen(cs43130->mclk_int, + params_rate(params), + cs43130_64_clk_gen, + ARRAY_SIZE(cs43130_64_clk_gen)); + break; + default: + return -EINVAL; + } + if (!clk_gen) + return -EINVAL; + switch (dai_id) { + case CS43130_ASP_PCM_DAI: + case CS43130_ASP_DOP_DAI: + regmap_write(cs43130->regmap, CS43130_ASP_DEN_1, + (clk_gen->v.denominator & CS43130_SP_M_LSB_DATA_MASK) >> + CS43130_SP_M_LSB_DATA_SHIFT); + regmap_write(cs43130->regmap, CS43130_ASP_DEN_2, + (clk_gen->v.denominator & CS43130_SP_M_MSB_DATA_MASK) >> + CS43130_SP_M_MSB_DATA_SHIFT); + regmap_write(cs43130->regmap, CS43130_ASP_NUM_1, + (clk_gen->v.numerator & CS43130_SP_N_LSB_DATA_MASK) >> + CS43130_SP_N_LSB_DATA_SHIFT); + regmap_write(cs43130->regmap, CS43130_ASP_NUM_2, + (clk_gen->v.numerator & CS43130_SP_N_MSB_DATA_MASK) >> + CS43130_SP_N_MSB_DATA_SHIFT); + break; + case CS43130_XSP_DOP_DAI: + regmap_write(cs43130->regmap, CS43130_XSP_DEN_1, + (clk_gen->v.denominator & CS43130_SP_M_LSB_DATA_MASK) >> + CS43130_SP_M_LSB_DATA_SHIFT); + regmap_write(cs43130->regmap, CS43130_XSP_DEN_2, + (clk_gen->v.denominator & CS43130_SP_M_MSB_DATA_MASK) >> + CS43130_SP_M_MSB_DATA_SHIFT); + regmap_write(cs43130->regmap, CS43130_XSP_NUM_1, + (clk_gen->v.numerator & CS43130_SP_N_LSB_DATA_MASK) >> + CS43130_SP_N_LSB_DATA_SHIFT); + regmap_write(cs43130->regmap, CS43130_XSP_NUM_2, + (clk_gen->v.numerator & CS43130_SP_N_MSB_DATA_MASK) >> + CS43130_SP_N_MSB_DATA_SHIFT); + break; + default: + return -EINVAL; + } + return 0; +} + +static int cs43130_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct cs43130_priv *cs43130 = + snd_soc_component_get_drvdata(component); + const struct cs43130_rate_map *rate_map; + unsigned int sclk = cs43130->dais[dai->id].sclk; + unsigned int bitwidth_sclk; + unsigned int bitwidth_dai = (unsigned int)(params_width(params)); + unsigned int dop_rate = (unsigned int)(params_rate(params)); + unsigned int required_clk, ret; + u8 dsd_speed; + + cs43130->pll_bypass = true; + cs43130_pcm_pdn(component); + mutex_lock(&cs43130->clk_mutex); + if (!cs43130->clk_req) { + /* no DAI is currently using clk */ + if (!(CS43130_MCLK_22M % params_rate(params))) { + required_clk = CS43130_MCLK_22M; + cs43130->mclk_int = CS43130_MCLK_22M; + gpiod_set_value_cansleep(snd_allo_clk44gpio, 1); + gpiod_set_value_cansleep(snd_allo_clk48gpio, 0); + usleep_range(13500, 14000); + } else { + required_clk = CS43130_MCLK_24M; + cs43130->mclk_int = CS43130_MCLK_24M; + gpiod_set_value_cansleep(snd_allo_clk48gpio, 1); + gpiod_set_value_cansleep(snd_allo_clk44gpio, 0); + usleep_range(13500, 14000); + } + if (cs43130->pll_bypass) + cs43130_change_clksrc(component, CS43130_MCLK_SRC_EXT); + else + cs43130_change_clksrc(component, CS43130_MCLK_SRC_PLL); + } + + cs43130->clk_req++; + mutex_unlock(&cs43130->clk_mutex); + + switch (dai->id) { + case CS43130_ASP_DOP_DAI: + case CS43130_XSP_DOP_DAI: + /* DoP bitwidth is always 24-bit */ + bitwidth_dai = 24; + sclk = params_rate(params) * bitwidth_dai * + params_channels(params); + + switch (params_rate(params)) { + case 176400: + dsd_speed = 0; + break; + case 352800: + dsd_speed = 1; + break; + default: + dev_err(component->dev, "Rate(%u) not supported\n", + params_rate(params)); + return -EINVAL; + } + + regmap_update_bits(cs43130->regmap, CS43130_DSD_PATH_CTL_2, + CS43130_DSD_SPEED_MASK, + dsd_speed << CS43130_DSD_SPEED_SHIFT); + break; + case CS43130_ASP_PCM_DAI: + rate_map = cs43130_get_rate_table(params_rate(params)); + if (!rate_map) + return -EINVAL; + + regmap_write(cs43130->regmap, CS43130_SP_SRATE, rate_map->val); + if ((dop_rate == 176400) && (bitwidth_dai == 24)) { + dsd_speed = 0; + regmap_update_bits(cs43130->regmap, + CS43130_DSD_PATH_CTL_2, + CS43130_DSD_SPEED_MASK, + dsd_speed << CS43130_DSD_SPEED_SHIFT); + regmap_update_bits(cs43130->regmap, + CS43130_DSD_PATH_CTL_2, + CS43130_DSD_SRC_MASK, + CS43130_DSD_SRC_ASP << + CS43130_DSD_SRC_SHIFT); + regmap_update_bits(cs43130->regmap, + CS43130_DSD_PATH_CTL_2, + CS43130_DSD_EN_MASK, 0x01 << + CS43130_DSD_EN_SHIFT); + } + break; + default: + dev_err(component->dev, "Invalid DAI (%d)\n", dai->id); + return -EINVAL; + } + + switch (dai->id) { + case CS43130_ASP_DOP_DAI: + regmap_update_bits(cs43130->regmap, CS43130_DSD_PATH_CTL_2, + CS43130_DSD_SRC_MASK, CS43130_DSD_SRC_ASP << + CS43130_DSD_SRC_SHIFT); + regmap_update_bits(cs43130->regmap, CS43130_DSD_PATH_CTL_2, + CS43130_DSD_EN_MASK, 0x01 << + CS43130_DSD_EN_SHIFT); + break; + case CS43130_XSP_DOP_DAI: + regmap_update_bits(cs43130->regmap, CS43130_DSD_PATH_CTL_2, + CS43130_DSD_SRC_MASK, CS43130_DSD_SRC_XSP << + CS43130_DSD_SRC_SHIFT); + break; + } + if (!sclk && cs43130->dais[dai->id].dai_mode == + SND_SOC_DAIFMT_CBM_CFM) { + /* Calculate SCLK in master mode if unassigned */ + sclk = params_rate(params) * bitwidth_dai * + params_channels(params); + } + if (!sclk) { + /* at this point, SCLK must be set */ + dev_err(component->dev, "SCLK freq is not set\n"); + return -EINVAL; + } + + bitwidth_sclk = (sclk / params_rate(params)) / params_channels(params); + if (bitwidth_sclk < bitwidth_dai) { + dev_err(component->dev, "Format not supported: SCLK freq is too low\n"); + return -EINVAL; + } + + dev_dbg(component->dev, + "sclk = %u, fs = %d, bitwidth_dai = %u\n", + sclk, params_rate(params), bitwidth_dai); + + dev_dbg(component->dev, + "bitwidth_sclk = %u, num_ch = %u\n", + bitwidth_sclk, params_channels(params)); + + cs43130_set_bitwidth(dai->id, bitwidth_dai, cs43130->regmap); + cs43130_set_sp_fmt(dai->id, bitwidth_sclk, params, cs43130); + ret = cs43130_pwr_up_asp_dac(component); + return 0; +} + +static int cs43130_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct cs43130_priv *cs43130 = + snd_soc_component_get_drvdata(component); + + mutex_lock(&cs43130->clk_mutex); + cs43130->clk_req--; + if (!cs43130->clk_req) { + /* no DAI is currently using clk */ + cs43130_change_clksrc(component, CS43130_MCLK_SRC_RCO); + cs43130_pcm_pdn(component); + } + mutex_unlock(&cs43130->clk_mutex); + + return 0; +} + +static const unsigned int cs43130_asp_src_rates[] = { + 32000, 44100, 48000, 88200, 96000, 176400, 192000 +}; + +static const struct snd_pcm_hw_constraint_list cs43130_asp_constraints = { + .count = ARRAY_SIZE(cs43130_asp_src_rates), + .list = cs43130_asp_src_rates, +}; + +static int cs43130_pcm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &cs43130_asp_constraints); +} + +static int cs43130_pcm_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct cs43130_priv *cs43130 = + snd_soc_component_get_drvdata(component); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + cs43130->dais[codec_dai->id].dai_mode = SND_SOC_DAIFMT_CBS_CFS; + break; + case SND_SOC_DAIFMT_CBM_CFM: + cs43130->dais[codec_dai->id].dai_mode = SND_SOC_DAIFMT_CBM_CFM; + break; + default: + dev_err(component->dev, "unsupported mode\n"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + cs43130->dais[codec_dai->id].dai_format = SND_SOC_DAIFMT_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + cs43130->dais[codec_dai->id].dai_format = SND_SOC_DAIFMT_LEFT_J; + break; + default: + dev_err(component->dev, + "unsupported audio format\n"); + return -EINVAL; + } + + dev_dbg(component->dev, "dai_id = %d, dai_mode = %u, dai_format = %u\n", + codec_dai->id, + cs43130->dais[codec_dai->id].dai_mode, + cs43130->dais[codec_dai->id].dai_format); + + return 0; +} + +static int cs43130_set_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct cs43130_priv *cs43130 = + snd_soc_component_get_drvdata(component); + + cs43130->dais[codec_dai->id].sclk = freq; + dev_dbg(component->dev, "dai_id = %d, sclk = %u\n", codec_dai->id, + cs43130->dais[codec_dai->id].sclk); + + return 0; +} + +static int cs43130_component_set_sysclk(struct snd_soc_component *component, + int clk_id, int source, + unsigned int freq, int dir) +{ + struct cs43130_priv *cs43130 = + snd_soc_component_get_drvdata(component); + + dev_dbg(component->dev, "clk_id = %d, source = %d, freq = %d, dir = %d\n", + clk_id, source, freq, dir); + + switch (freq) { + case CS43130_MCLK_22M: + case CS43130_MCLK_24M: + cs43130->mclk = freq; + break; + default: + dev_err(component->dev, "Invalid MCLK INT freq: %u\n", freq); + return -EINVAL; + } + + if (source == CS43130_MCLK_SRC_EXT) { + cs43130->pll_bypass = true; + } else { + dev_err(component->dev, "Invalid MCLK source\n"); + return -EINVAL; + } + + return 0; +} +static u16 const cs43130_ac_freq[CS43130_AC_FREQ] = { + 24, + 43, + 93, + 200, + 431, + 928, + 2000, + 4309, + 9283, + 20000, +}; +static const struct snd_soc_dai_ops cs43130_dai_ops = { + .startup = cs43130_pcm_startup, + .hw_params = cs43130_hw_params, + .hw_free = cs43130_hw_free, + .set_sysclk = cs43130_set_sysclk, + .set_fmt = cs43130_pcm_set_fmt, +}; + +static struct snd_soc_dai_driver cs43130_codec_dai = { + .name = "allo-cs43130", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 44100, + .rate_max = 192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE + + }, + .ops = &cs43130_dai_ops, +}; + +static struct snd_soc_component_driver cs43130_component_driver = { + .idle_bias_on = true, + .controls = cs43130_controls, + .num_controls = ARRAY_SIZE(cs43130_controls), + .set_sysclk = cs43130_component_set_sysclk, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, +}; + +static const struct regmap_config cs43130_regmap = { + .reg_bits = 24, + .pad_bits = 8, + .val_bits = 8, + + .max_register = CS43130_LASTREG, + .reg_defaults = cs43130_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(cs43130_reg_defaults), + .readable_reg = cs43130_readable_register, + .precious_reg = cs43130_precious_register, + .volatile_reg = cs43130_volatile_register, + .cache_type = REGCACHE_RBTREE, + /* needed for regcache_sync */ + .use_single_read = true, + .use_single_write = true, +}; + +static u16 const cs43130_dc_threshold[CS43130_DC_THRESHOLD] = { + 50, + 120, +}; + +static int cs43130_handle_device_data(struct i2c_client *i2c_client, + struct cs43130_priv *cs43130) +{ + struct device_node *np = i2c_client->dev.of_node; + unsigned int val; + int i; + + if (of_property_read_u32(np, "cirrus,xtal-ibias", &val) < 0) { + /* Crystal is unused. System clock is used for external MCLK */ + cs43130->xtal_ibias = CS43130_XTAL_UNUSED; + return 0; + } + + switch (val) { + case 1: + cs43130->xtal_ibias = CS43130_XTAL_IBIAS_7_5UA; + break; + case 2: + cs43130->xtal_ibias = CS43130_XTAL_IBIAS_12_5UA; + break; + case 3: + cs43130->xtal_ibias = CS43130_XTAL_IBIAS_15UA; + break; + default: + dev_err(&i2c_client->dev, + "Invalid cirrus,xtal-ibias value: %d\n", val); + return -EINVAL; + } + + cs43130->dc_meas = of_property_read_bool(np, "cirrus,dc-measure"); + cs43130->ac_meas = of_property_read_bool(np, "cirrus,ac-measure"); + + if (of_property_read_u16_array(np, "cirrus,ac-freq", cs43130->ac_freq, + CS43130_AC_FREQ) < 0) { + for (i = 0; i < CS43130_AC_FREQ; i++) + cs43130->ac_freq[i] = cs43130_ac_freq[i]; + } + + if (of_property_read_u16_array(np, "cirrus,dc-threshold", + cs43130->dc_threshold, + CS43130_DC_THRESHOLD) < 0) { + for (i = 0; i < CS43130_DC_THRESHOLD; i++) + cs43130->dc_threshold[i] = cs43130_dc_threshold[i]; + } + + return 0; +} + + +static int allo_cs43130_component_probe(struct i2c_client *i2c) +{ + struct regmap *regmap; + struct regmap_config config = cs43130_regmap; + struct device *dev = &i2c->dev; + struct cs43130_priv *cs43130; + unsigned int devid = 0; + unsigned int reg; + int ret; + + regmap = devm_regmap_init_i2c(i2c, &config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + cs43130 = devm_kzalloc(dev, sizeof(struct cs43130_priv), + GFP_KERNEL); + if (!cs43130) + return -ENOMEM; + + dev_set_drvdata(dev, cs43130); + cs43130->regmap = regmap; + + if (i2c->dev.of_node) { + ret = cs43130_handle_device_data(i2c, cs43130); + if (ret != 0) + return ret; + } + usleep_range(2000, 2050); + + ret = regmap_read(cs43130->regmap, CS43130_DEVID_AB, ®); + devid = (reg & 0xFF) << 12; + ret = regmap_read(cs43130->regmap, CS43130_DEVID_CD, ®); + devid |= (reg & 0xFF) << 4; + ret = regmap_read(cs43130->regmap, CS43130_DEVID_E, ®); + devid |= (reg & 0xF0) >> 4; + if (devid != CS43198_CHIP_ID) { + dev_err(dev, "Failed to read Chip or wrong Chip id: %d\n", ret); + return ret; + } + + cs43130->mclk_int_src = CS43130_MCLK_SRC_RCO; + msleep(20); + + ret = snd_soc_register_component(dev, &cs43130_component_driver, + &cs43130_codec_dai, 1); + if (ret != 0) { + dev_err(dev, "failed to register codec: %d\n", ret); + return ret; + } + regmap_update_bits(cs43130->regmap, CS43130_PAD_INT_CFG, + CS43130_ASP_3ST_MASK, 0); + regmap_update_bits(cs43130->regmap, CS43130_PAD_INT_CFG, + CS43130_XSP_3ST_MASK, 1); + regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL, + CS43130_PDN_HP_MASK, 1 << CS43130_PDN_HP_SHIFT); + msleep(20); + regmap_write(cs43130->regmap, CS43130_CLASS_H_CTL, 0x06); + snd_allo_clk44gpio = devm_gpiod_get(dev, "clock44", GPIOD_OUT_HIGH); + if (IS_ERR(snd_allo_clk44gpio)) + dev_err(dev, "devm_gpiod_get() failed\n"); + + snd_allo_clk48gpio = devm_gpiod_get(dev, "clock48", GPIOD_OUT_LOW); + if (IS_ERR(snd_allo_clk48gpio)) + dev_err(dev, "devm_gpiod_get() failed\n"); + + return 0; +} + +static void allo_cs43130_component_remove(struct i2c_client *i2c) +{ + snd_soc_unregister_component(&i2c->dev); +} + +static const struct i2c_device_id allo_cs43130_component_id[] = { + { "allo-cs43198", }, + { } +}; +MODULE_DEVICE_TABLE(i2c, allo_cs43130_component_id); + +static const struct of_device_id allo_cs43130_codec_of_match[] = { + { .compatible = "allo,allo-cs43198", }, + { } +}; +MODULE_DEVICE_TABLE(of, allo_cs43130_codec_of_match); + +static struct i2c_driver allo_cs43130_component_driver = { + .probe = allo_cs43130_component_probe, + .remove = allo_cs43130_component_remove, + .id_table = allo_cs43130_component_id, + .driver = { + .name = "allo-cs43198", + .of_match_table = allo_cs43130_codec_of_match, + }, +}; + +module_i2c_driver(allo_cs43130_component_driver); + +MODULE_DESCRIPTION("ASoC Allo Boss2 Codec Driver"); +MODULE_AUTHOR("Sudeepkumar <sudeepkumar@cem-solutions.net>"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/allo-katana-codec.c b/sound/soc/bcm/allo-katana-codec.c new file mode 100644 index 00000000000000..266a89e4dc94b0 --- /dev/null +++ b/sound/soc/bcm/allo-katana-codec.c @@ -0,0 +1,386 @@ +/* + * Driver for the ALLO KATANA CODEC + * + * Author: Jaikumar <jaikumar@cem-solutions.net> + * Copyright 2018 + * + * 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 <linux/init.h> +#include <linux/module.h> +#include <linux/clk.h> +#include <linux/kernel.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/gcd.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm_params.h> +#include <sound/tlv.h> +#include <linux/i2c.h> + + +#define KATANA_CODEC_CHIP_ID 0x30 +#define KATANA_CODEC_VIRT_BASE 0x100 +#define KATANA_CODEC_PAGE 0 + +#define KATANA_CODEC_CHIP_ID_REG (KATANA_CODEC_VIRT_BASE + 0) +#define KATANA_CODEC_RESET (KATANA_CODEC_VIRT_BASE + 1) +#define KATANA_CODEC_VOLUME_1 (KATANA_CODEC_VIRT_BASE + 2) +#define KATANA_CODEC_VOLUME_2 (KATANA_CODEC_VIRT_BASE + 3) +#define KATANA_CODEC_MUTE (KATANA_CODEC_VIRT_BASE + 4) +#define KATANA_CODEC_DSP_PROGRAM (KATANA_CODEC_VIRT_BASE + 5) +#define KATANA_CODEC_DEEMPHASIS (KATANA_CODEC_VIRT_BASE + 6) +#define KATANA_CODEC_DOP (KATANA_CODEC_VIRT_BASE + 7) +#define KATANA_CODEC_FORMAT (KATANA_CODEC_VIRT_BASE + 8) +#define KATANA_CODEC_COMMAND (KATANA_CODEC_VIRT_BASE + 9) +#define KATANA_CODEC_MUTE_STREAM (KATANA_CODEC_VIRT_BASE + 10) + +#define KATANA_CODEC_MAX_REGISTER (KATANA_CODEC_VIRT_BASE + 10) + +#define KATANA_CODEC_FMT 0xff +#define KATANA_CODEC_CHAN_MONO 0x00 +#define KATANA_CODEC_CHAN_STEREO 0x80 +#define KATANA_CODEC_ALEN_16 0x10 +#define KATANA_CODEC_ALEN_24 0x20 +#define KATANA_CODEC_ALEN_32 0x30 +#define KATANA_CODEC_RATE_11025 0x01 +#define KATANA_CODEC_RATE_22050 0x02 +#define KATANA_CODEC_RATE_32000 0x03 +#define KATANA_CODEC_RATE_44100 0x04 +#define KATANA_CODEC_RATE_48000 0x05 +#define KATANA_CODEC_RATE_88200 0x06 +#define KATANA_CODEC_RATE_96000 0x07 +#define KATANA_CODEC_RATE_176400 0x08 +#define KATANA_CODEC_RATE_192000 0x09 +#define KATANA_CODEC_RATE_352800 0x0a +#define KATANA_CODEC_RATE_384000 0x0b + + +struct katana_codec_priv { + struct regmap *regmap; + int fmt; +}; + +static const struct reg_default katana_codec_reg_defaults[] = { + { KATANA_CODEC_RESET, 0x00 }, + { KATANA_CODEC_VOLUME_1, 0xF0 }, + { KATANA_CODEC_VOLUME_2, 0xF0 }, + { KATANA_CODEC_MUTE, 0x00 }, + { KATANA_CODEC_DSP_PROGRAM, 0x04 }, + { KATANA_CODEC_DEEMPHASIS, 0x00 }, + { KATANA_CODEC_DOP, 0x00 }, + { KATANA_CODEC_FORMAT, 0xb4 }, +}; + +static const char * const katana_codec_dsp_program_texts[] = { + "Linear Phase Fast Roll-off Filter", + "Linear Phase Slow Roll-off Filter", + "Minimum Phase Fast Roll-off Filter", + "Minimum Phase Slow Roll-off Filter", + "Apodizing Fast Roll-off Filter", + "Corrected Minimum Phase Fast Roll-off Filter", + "Brick Wall Filter", +}; + +static const unsigned int katana_codec_dsp_program_values[] = { + 0, + 1, + 2, + 3, + 4, + 6, + 7, +}; + +static SOC_VALUE_ENUM_SINGLE_DECL(katana_codec_dsp_program, + KATANA_CODEC_DSP_PROGRAM, 0, 0x07, + katana_codec_dsp_program_texts, + katana_codec_dsp_program_values); + +static const char * const katana_codec_deemphasis_texts[] = { + "Bypass", + "32kHz", + "44.1kHz", + "48kHz", +}; + +static const unsigned int katana_codec_deemphasis_values[] = { + 0, + 1, + 2, + 3, +}; + +static SOC_VALUE_ENUM_SINGLE_DECL(katana_codec_deemphasis, + KATANA_CODEC_DEEMPHASIS, 0, 0x03, + katana_codec_deemphasis_texts, + katana_codec_deemphasis_values); + +static const SNDRV_CTL_TLVD_DECLARE_DB_MINMAX(master_tlv, -12750, 0); + +static const struct snd_kcontrol_new katana_codec_controls[] = { + SOC_DOUBLE_R_TLV("Master Playback Volume", KATANA_CODEC_VOLUME_1, + KATANA_CODEC_VOLUME_2, 0, 255, 1, master_tlv), + SOC_DOUBLE("Master Playback Switch", KATANA_CODEC_MUTE, 0, 0, 1, 1), + SOC_ENUM("DSP Program Route", katana_codec_dsp_program), + SOC_ENUM("Deemphasis Route", katana_codec_deemphasis), + SOC_SINGLE("DoP Playback Switch", KATANA_CODEC_DOP, 0, 1, 1) +}; + +static bool katana_codec_readable_register(struct device *dev, + unsigned int reg) +{ + switch (reg) { + case KATANA_CODEC_CHIP_ID_REG: + return true; + default: + return reg < 0xff; + } +} + +static int katana_codec_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct katana_codec_priv *katana_codec = + snd_soc_component_get_drvdata(component); + int fmt = 0; + int ret; + + dev_dbg(component->card->dev, "hw_params %u Hz, %u channels, %u bits\n", + params_rate(params), + params_channels(params), + params_width(params)); + + switch (katana_codec->fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: // master + if (params_channels(params) == 2) + fmt = KATANA_CODEC_CHAN_STEREO; + else + fmt = KATANA_CODEC_CHAN_MONO; + + switch (params_width(params)) { + case 16: + fmt |= KATANA_CODEC_ALEN_16; + break; + case 24: + fmt |= KATANA_CODEC_ALEN_24; + break; + case 32: + fmt |= KATANA_CODEC_ALEN_32; + break; + default: + dev_err(component->card->dev, "Bad frame size: %d\n", + params_width(params)); + return -EINVAL; + } + + switch (params_rate(params)) { + case 44100: + fmt |= KATANA_CODEC_RATE_44100; + break; + case 48000: + fmt |= KATANA_CODEC_RATE_48000; + break; + case 88200: + fmt |= KATANA_CODEC_RATE_88200; + break; + case 96000: + fmt |= KATANA_CODEC_RATE_96000; + break; + case 176400: + fmt |= KATANA_CODEC_RATE_176400; + break; + case 192000: + fmt |= KATANA_CODEC_RATE_192000; + break; + case 352800: + fmt |= KATANA_CODEC_RATE_352800; + break; + case 384000: + fmt |= KATANA_CODEC_RATE_384000; + break; + default: + dev_err(component->card->dev, "Bad sample rate: %d\n", + params_rate(params)); + return -EINVAL; + } + + ret = regmap_write(katana_codec->regmap, KATANA_CODEC_FORMAT, + fmt); + if (ret != 0) { + dev_err(component->card->dev, "Failed to set format: %d\n", ret); + return ret; + } + break; + + case SND_SOC_DAIFMT_CBS_CFS: + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int katana_codec_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + struct katana_codec_priv *katana_codec = + snd_soc_component_get_drvdata(component); + + katana_codec->fmt = fmt; + + return 0; +} + +static int katana_codec_dai_mute_stream(struct snd_soc_dai *dai, int mute, + int stream) +{ + struct snd_soc_component *component = dai->component; + struct katana_codec_priv *katana_codec = + snd_soc_component_get_drvdata(component); + int ret = 0; + + ret = regmap_write(katana_codec->regmap, KATANA_CODEC_MUTE_STREAM, + mute); + if (ret != 0) { + dev_err(component->card->dev, "Failed to set mute: %d\n", ret); + return ret; + } + return ret; +} + +static const struct snd_soc_dai_ops katana_codec_dai_ops = { + .mute_stream = katana_codec_dai_mute_stream, + .hw_params = katana_codec_hw_params, + .set_fmt = katana_codec_set_fmt, +}; + +static struct snd_soc_dai_driver katana_codec_dai = { + .name = "allo-katana-codec", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 44100, + .rate_max = 384000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE + }, + .ops = &katana_codec_dai_ops, +}; + +static struct snd_soc_component_driver katana_codec_component_driver = { + .idle_bias_on = true, + + .controls = katana_codec_controls, + .num_controls = ARRAY_SIZE(katana_codec_controls), +}; + +static const struct regmap_range_cfg katana_codec_range = { + .name = "Pages", .range_min = KATANA_CODEC_VIRT_BASE, + .range_max = KATANA_CODEC_MAX_REGISTER, + .selector_reg = KATANA_CODEC_PAGE, + .selector_mask = 0xff, + .window_start = 0, .window_len = 0x100, +}; + +const struct regmap_config katana_codec_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .ranges = &katana_codec_range, + .num_ranges = 1, + + .max_register = KATANA_CODEC_MAX_REGISTER, + .readable_reg = katana_codec_readable_register, + .reg_defaults = katana_codec_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(katana_codec_reg_defaults), + .cache_type = REGCACHE_RBTREE, +}; + +static int allo_katana_component_probe(struct i2c_client *i2c) +{ + struct regmap *regmap; + struct regmap_config config = katana_codec_regmap; + struct device *dev = &i2c->dev; + struct katana_codec_priv *katana_codec; + unsigned int chip_id = 0; + int ret; + + regmap = devm_regmap_init_i2c(i2c, &config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + katana_codec = devm_kzalloc(dev, sizeof(struct katana_codec_priv), + GFP_KERNEL); + if (!katana_codec) + return -ENOMEM; + + dev_set_drvdata(dev, katana_codec); + katana_codec->regmap = regmap; + + ret = regmap_read(regmap, KATANA_CODEC_CHIP_ID_REG, &chip_id); + if ((ret != 0) || (chip_id != KATANA_CODEC_CHIP_ID)) { + dev_err(dev, "Failed to read Chip or wrong Chip id: %d\n", ret); + return ret; + } + regmap_update_bits(regmap, KATANA_CODEC_RESET, 0x01, 0x01); + msleep(10); + + ret = snd_soc_register_component(dev, &katana_codec_component_driver, + &katana_codec_dai, 1); + if (ret != 0) { + dev_err(dev, "failed to register codec: %d\n", ret); + return ret; + } + + return 0; +} + +static void allo_katana_component_remove(struct i2c_client *i2c) +{ + snd_soc_unregister_component(&i2c->dev); +} + +static const struct i2c_device_id allo_katana_component_id[] = { + { "allo-katana-codec", }, + { } +}; +MODULE_DEVICE_TABLE(i2c, allo_katana_component_id); + +static const struct of_device_id allo_katana_codec_of_match[] = { + { .compatible = "allo,allo-katana-codec", }, + { } +}; +MODULE_DEVICE_TABLE(of, allo_katana_codec_of_match); + +static struct i2c_driver allo_katana_component_driver = { + .probe = allo_katana_component_probe, + .remove = allo_katana_component_remove, + .id_table = allo_katana_component_id, + .driver = { + .name = "allo-katana-codec", + .of_match_table = allo_katana_codec_of_match, + }, +}; + +module_i2c_driver(allo_katana_component_driver); + +MODULE_DESCRIPTION("ASoC Allo Katana Codec Driver"); +MODULE_AUTHOR("Jaikumar <jaikumar@cem-solutions.net>"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/allo-piano-dac-plus.c b/sound/soc/bcm/allo-piano-dac-plus.c new file mode 100644 index 00000000000000..07cbd4215b7df1 --- /dev/null +++ b/sound/soc/bcm/allo-piano-dac-plus.c @@ -0,0 +1,1026 @@ +/* + * ALSA ASoC Machine Driver for Allo Piano DAC Plus Subwoofer + * + * Author: Baswaraj K <jaikumar@cem-solutions.net> + * Copyright 2020 + * based on code by David Knell <david.knell@gmail.com) + * based on code by Daniel Matuschek <info@crazy-audio.com> + * based on code by Florian Meier <florian.meier@koalo.de> + * + * 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 <linux/module.h> +#include <linux/platform_device.h> +#include <linux/gpio/consumer.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <linux/firmware.h> +#include <linux/delay.h> +#include <sound/tlv.h> +#include "../codecs/pcm512x.h" + +#define P_DAC_LEFT_MUTE 0x10 +#define P_DAC_RIGHT_MUTE 0x01 +#define P_DAC_MUTE 0x11 +#define P_DAC_UNMUTE 0x00 +#define P_MUTE 1 +#define P_UNMUTE 0 + +struct dsp_code { + char i2c_addr; + char offset; + char val; +}; + +struct glb_pool { + struct mutex lock; + unsigned int dual_mode; + unsigned int set_lowpass; + unsigned int set_mode; + unsigned int set_rate; + unsigned int dsp_page_number; +}; + +static bool digital_gain_0db_limit = true; +bool glb_mclk; + +static struct gpio_desc *mute_gpio[2]; + +static const char * const allo_piano_mode_texts[] = { + "None", + "2.0", + "2.1", + "2.2", +}; + +static SOC_ENUM_SINGLE_DECL(allo_piano_mode_enum, + 0, 0, allo_piano_mode_texts); + +static const char * const allo_piano_dual_mode_texts[] = { + "None", + "Dual-Mono", + "Dual-Stereo", +}; + +static SOC_ENUM_SINGLE_DECL(allo_piano_dual_mode_enum, + 0, 0, allo_piano_dual_mode_texts); + +static const char * const allo_piano_dsp_low_pass_texts[] = { + "60", + "70", + "80", + "90", + "100", + "110", + "120", + "130", + "140", + "150", + "160", + "170", + "180", + "190", + "200", +}; + +static SOC_ENUM_SINGLE_DECL(allo_piano_enum, + 0, 0, allo_piano_dsp_low_pass_texts); + +static int __snd_allo_piano_dsp_program(struct snd_soc_pcm_runtime *rtd, + unsigned int mode, unsigned int rate, unsigned int lowpass) +{ + const struct firmware *fw; + struct snd_soc_card *card = rtd->card; + struct glb_pool *glb_ptr = card->drvdata; + char firmware_name[60]; + int ret = 0, dac = 0; + + if (rate <= 46000) + rate = 44100; + else if (rate <= 68000) + rate = 48000; + else if (rate <= 92000) + rate = 88200; + else if (rate <= 136000) + rate = 96000; + else if (rate <= 184000) + rate = 176400; + else + rate = 192000; + + if (lowpass > 14) + glb_ptr->set_lowpass = lowpass = 0; + + if (mode > 3) + glb_ptr->set_mode = mode = 0; + + if (mode > 0) + glb_ptr->dual_mode = 0; + + /* same configuration loaded */ + if ((rate == glb_ptr->set_rate) && (lowpass == glb_ptr->set_lowpass) + && (mode == glb_ptr->set_mode)) + return 0; + + switch (mode) { + case 0: /* None */ + return 1; + + case 1: /* 2.0 */ + snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component, + PCM512x_MUTE, P_DAC_UNMUTE); + snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component, + PCM512x_MUTE, P_DAC_MUTE); + glb_ptr->set_rate = rate; + glb_ptr->set_mode = mode; + glb_ptr->set_lowpass = lowpass; + return 1; + + default: + snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component, + PCM512x_MUTE, P_DAC_UNMUTE); + snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component, + PCM512x_MUTE, P_DAC_UNMUTE); + } + + for (dac = 0; dac < rtd->dai_link->num_codecs; dac++) { + struct dsp_code *dsp_code_read; + int i = 1; + + if (dac == 0) { /* high */ + snprintf(firmware_name, sizeof(firmware_name), + "allo/piano/2.2/allo-piano-dsp-%d-%d-%d.bin", + rate, ((lowpass * 10) + 60), dac); + } else { /* low */ + snprintf(firmware_name, sizeof(firmware_name), + "allo/piano/2.%d/allo-piano-dsp-%d-%d-%d.bin", + (mode - 1), rate, ((lowpass * 10) + 60), dac); + } + + dev_info(rtd->card->dev, "Dsp Firmware File Name: %s\n", + firmware_name); + + ret = request_firmware(&fw, firmware_name, rtd->card->dev); + if (ret < 0) { + dev_err(rtd->card->dev, + "Error: Allo Piano Firmware %s missing. %d\n", + firmware_name, ret); + goto err; + } + + while (i < (fw->size - 1)) { + dsp_code_read = (struct dsp_code *)&fw->data[i]; + + if (dsp_code_read->offset == 0) { + glb_ptr->dsp_page_number = dsp_code_read->val; + ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, dac)->component, + PCM512x_PAGE_BASE(0), + dsp_code_read->val); + + } else if (dsp_code_read->offset != 0) { + ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, dac)->component, + (PCM512x_PAGE_BASE( + glb_ptr->dsp_page_number) + + dsp_code_read->offset), + dsp_code_read->val); + } + if (ret < 0) { + dev_err(rtd->card->dev, + "Failed to write Register: %d\n", ret); + release_firmware(fw); + goto err; + } + i = i + 3; + } + release_firmware(fw); + } + glb_ptr->set_rate = rate; + glb_ptr->set_mode = mode; + glb_ptr->set_lowpass = lowpass; + return 1; + +err: + return ret; +} + +static int snd_allo_piano_dsp_program(struct snd_soc_pcm_runtime *rtd, + unsigned int mode, unsigned int rate, unsigned int lowpass) +{ + struct snd_soc_card *card = rtd->card; + struct glb_pool *glb_ptr = card->drvdata; + int ret = 0; + + mutex_lock(&glb_ptr->lock); + + ret = __snd_allo_piano_dsp_program(rtd, mode, rate, lowpass); + + mutex_unlock(&glb_ptr->lock); + + return ret; +} + +static int snd_allo_piano_dual_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct glb_pool *glb_ptr = card->drvdata; + + ucontrol->value.integer.value[0] = glb_ptr->dual_mode; + + return 0; +} + +static int snd_allo_piano_dual_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct glb_pool *glb_ptr = card->drvdata; + struct snd_soc_pcm_runtime *rtd; + struct snd_card *snd_card_ptr = card->snd_card; + struct snd_kcontrol *kctl; + struct soc_mixer_control *mc; + unsigned int left_val = 0, right_val = 0; + + rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]); + + if (ucontrol->value.integer.value[0] > 0) { + glb_ptr->dual_mode = ucontrol->value.integer.value[0]; + glb_ptr->set_mode = 0; + } else { + if (glb_ptr->set_mode <= 0) { + glb_ptr->dual_mode = 1; + glb_ptr->set_mode = 0; + } else { + glb_ptr->dual_mode = 0; + return 0; + } + } + + if (glb_ptr->dual_mode == 1) { // Dual Mono + snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component, + PCM512x_MUTE, P_DAC_RIGHT_MUTE); + snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component, + PCM512x_MUTE, P_DAC_LEFT_MUTE); + snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component, + PCM512x_DIGITAL_VOLUME_3, 0xff); + snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component, + PCM512x_DIGITAL_VOLUME_2, 0xff); + + list_for_each_entry(kctl, &snd_card_ptr->controls, list) { + if (!strncmp(kctl->id.name, "Main Digital Playback Volume", + sizeof(kctl->id.name))) { + mc = (struct soc_mixer_control *) + kctl->private_value; + mc->rreg = mc->reg; + break; + } + if (!strncmp(kctl->id.name, "Sub Digital Playback Volume", + sizeof(kctl->id.name))) { + mc = (struct soc_mixer_control *) + kctl->private_value; + mc->rreg = mc->reg; + break; + } + } + } else { + left_val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 0)->component, + PCM512x_DIGITAL_VOLUME_2); + right_val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 1)->component, + PCM512x_DIGITAL_VOLUME_3); + + list_for_each_entry(kctl, &snd_card_ptr->controls, list) { + if (!strncmp(kctl->id.name, "Main Digital Playback Volume", + sizeof(kctl->id.name))) { + mc = (struct soc_mixer_control *) + kctl->private_value; + mc->rreg = PCM512x_DIGITAL_VOLUME_3; + break; + } + if (!strncmp(kctl->id.name, "Sub Digital Playback Volume", + sizeof(kctl->id.name))) { + mc = (struct soc_mixer_control *) + kctl->private_value; + mc->rreg = PCM512x_DIGITAL_VOLUME_2; + break; + } + } + + snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component, + PCM512x_DIGITAL_VOLUME_3, left_val); + snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component, + PCM512x_DIGITAL_VOLUME_2, right_val); + snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component, + PCM512x_MUTE, P_DAC_UNMUTE); + snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component, + PCM512x_MUTE, P_DAC_UNMUTE); + } + + return 0; +} + +static int snd_allo_piano_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct glb_pool *glb_ptr = card->drvdata; + + ucontrol->value.integer.value[0] = glb_ptr->set_mode; + return 0; +} + +static int snd_allo_piano_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct snd_soc_pcm_runtime *rtd; + struct glb_pool *glb_ptr = card->drvdata; + struct snd_card *snd_card_ptr = card->snd_card; + struct snd_kcontrol *kctl; + struct soc_mixer_control *mc; + unsigned int left_val = 0, right_val = 0; + + rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]); + + if ((glb_ptr->dual_mode == 1) && + (ucontrol->value.integer.value[0] > 0)) { + left_val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 0)->component, + PCM512x_DIGITAL_VOLUME_2); + right_val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 1)->component, + PCM512x_DIGITAL_VOLUME_2); + + list_for_each_entry(kctl, &snd_card_ptr->controls, list) { + if (!strncmp(kctl->id.name, "Main Digital Playback Volume", + sizeof(kctl->id.name))) { + mc = (struct soc_mixer_control *) + kctl->private_value; + mc->rreg = PCM512x_DIGITAL_VOLUME_3; + break; + } + if (!strncmp(kctl->id.name, "Sub Digital Playback Volume", + sizeof(kctl->id.name))) { + mc = (struct soc_mixer_control *) + kctl->private_value; + mc->rreg = PCM512x_DIGITAL_VOLUME_2; + break; + } + } + snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component, + PCM512x_DIGITAL_VOLUME_3, left_val); + snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component, + PCM512x_DIGITAL_VOLUME_3, right_val); + } + + return(snd_allo_piano_dsp_program(rtd, + ucontrol->value.integer.value[0], + glb_ptr->set_rate, glb_ptr->set_lowpass)); +} + +static int snd_allo_piano_lowpass_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct glb_pool *glb_ptr = card->drvdata; + + ucontrol->value.integer.value[0] = glb_ptr->set_lowpass; + return 0; +} + +static int snd_allo_piano_lowpass_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct snd_soc_pcm_runtime *rtd; + struct glb_pool *glb_ptr = card->drvdata; + + rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]); + return(snd_allo_piano_dsp_program(rtd, + glb_ptr->set_mode, glb_ptr->set_rate, + ucontrol->value.integer.value[0])); +} + +static int pcm512x_get_reg_sub(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct glb_pool *glb_ptr = card->drvdata; + struct snd_soc_pcm_runtime *rtd; + unsigned int left_val = 0; + unsigned int right_val = 0; + rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]); + + right_val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 1)->component, + PCM512x_DIGITAL_VOLUME_3); + if (glb_ptr->dual_mode != 1) { + left_val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 1)->component, + PCM512x_DIGITAL_VOLUME_2); + + } else { + left_val = right_val; + } + + ucontrol->value.integer.value[0] = + (~(left_val >> mc->shift)) & mc->max; + ucontrol->value.integer.value[1] = + (~(right_val >> mc->shift)) & mc->max; + + return 0; +} + +static int pcm512x_set_reg_sub(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct glb_pool *glb_ptr = card->drvdata; + struct snd_soc_pcm_runtime *rtd; + unsigned int left_val = (ucontrol->value.integer.value[0] & mc->max); + unsigned int right_val = (ucontrol->value.integer.value[1] & mc->max); + int ret = 0; + + rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]); + + // When in Dual Mono, Sub vol control should not set anything. + if (glb_ptr->dual_mode != 1) { //Not in Dual Mono mode + + ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component, + PCM512x_DIGITAL_VOLUME_2, (~left_val)); + if (ret < 0) + return ret; + + ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component, + PCM512x_DIGITAL_VOLUME_3, (~right_val)); + if (ret < 0) + return ret; + + } + + return 1; +} + +static int pcm512x_get_reg_sub_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct snd_soc_pcm_runtime *rtd; + int val = 0; + + rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]); + val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 1)->component, PCM512x_MUTE); + + ucontrol->value.integer.value[0] = + (val & P_DAC_LEFT_MUTE) ? P_UNMUTE : P_MUTE; + ucontrol->value.integer.value[1] = + (val & P_DAC_RIGHT_MUTE) ? P_UNMUTE : P_MUTE; + + return val; +} + +static int pcm512x_set_reg_sub_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct snd_soc_pcm_runtime *rtd; + struct glb_pool *glb_ptr = card->drvdata; + unsigned int left_val = (ucontrol->value.integer.value[0]); + unsigned int right_val = (ucontrol->value.integer.value[1]); + int ret = 0; + + rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]); + if (glb_ptr->set_mode != 1) { + ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component, PCM512x_MUTE, + ~((left_val & 0x01)<<4 | (right_val & 0x01))); + if (ret < 0) + return ret; + } + return 1; + +} + +static int pcm512x_get_reg_master(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct glb_pool *glb_ptr = card->drvdata; + struct snd_soc_pcm_runtime *rtd; + unsigned int left_val = 0, right_val = 0; + + rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]); + + left_val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 0)->component, + PCM512x_DIGITAL_VOLUME_2); + + if (glb_ptr->dual_mode == 1) { // in Dual Mono mode + right_val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 1)->component, + PCM512x_DIGITAL_VOLUME_3); + } else { + right_val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 0)->component, + PCM512x_DIGITAL_VOLUME_3); + } + + ucontrol->value.integer.value[0] = + (~(left_val >> mc->shift)) & mc->max; + ucontrol->value.integer.value[1] = + (~(right_val >> mc->shift)) & mc->max; + + return 0; +} + +static int pcm512x_set_reg_master(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct glb_pool *glb_ptr = card->drvdata; + struct snd_soc_pcm_runtime *rtd; + unsigned int left_val = (ucontrol->value.integer.value[0] & mc->max); + unsigned int right_val = (ucontrol->value.integer.value[1] & mc->max); + int ret = 0; + + rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]); + + if (glb_ptr->dual_mode == 1) { //in Dual Mono Mode + + ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component, + PCM512x_DIGITAL_VOLUME_2, (~left_val)); + if (ret < 0) + return ret; + + ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component, + PCM512x_DIGITAL_VOLUME_3, (~right_val)); + if (ret < 0) + return ret; + + } else { + + ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component, + PCM512x_DIGITAL_VOLUME_2, (~left_val)); + if (ret < 0) + return ret; + + ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component, + PCM512x_DIGITAL_VOLUME_3, (~right_val)); + if (ret < 0) + return ret; + + } + return 1; +} + +static int pcm512x_get_reg_master_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct glb_pool *glb_ptr = card->drvdata; + struct snd_soc_pcm_runtime *rtd; + int val = 0; + + rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]); + + val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 0)->component, PCM512x_MUTE); + + ucontrol->value.integer.value[0] = + (val & P_DAC_LEFT_MUTE) ? P_UNMUTE : P_MUTE; + + if (glb_ptr->dual_mode == 1) { + val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 1)->component, PCM512x_MUTE); + } + ucontrol->value.integer.value[1] = + (val & P_DAC_RIGHT_MUTE) ? P_UNMUTE : P_MUTE; + + return val; +} + +static int pcm512x_set_reg_master_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct snd_soc_pcm_runtime *rtd; + struct glb_pool *glb_ptr = card->drvdata; + unsigned int left_val = (ucontrol->value.integer.value[0]); + unsigned int right_val = (ucontrol->value.integer.value[1]); + int ret = 0; + + rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]); + if (glb_ptr->dual_mode == 1) { + ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component, PCM512x_MUTE, + ~((left_val & 0x01)<<4)); + if (ret < 0) + return ret; + ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component, PCM512x_MUTE, + ~((right_val & 0x01))); + if (ret < 0) + return ret; + + } else if (glb_ptr->set_mode == 1) { + ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component, PCM512x_MUTE, + ~((left_val & 0x01)<<4 | (right_val & 0x01))); + if (ret < 0) + return ret; + + } else { + ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component, PCM512x_MUTE, + ~((left_val & 0x01)<<4 | (right_val & 0x01))); + if (ret < 0) + return ret; + + ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component, PCM512x_MUTE, + ~((left_val & 0x01)<<4 | (right_val & 0x01))); + if (ret < 0) + return ret; + } + return 1; +} + +static const DECLARE_TLV_DB_SCALE(digital_tlv_sub, -10350, 50, 1); +static const DECLARE_TLV_DB_SCALE(digital_tlv_master, -10350, 50, 1); + +static const struct snd_kcontrol_new allo_piano_controls[] = { + SOC_ENUM_EXT("Subwoofer mode Route", + allo_piano_mode_enum, + snd_allo_piano_mode_get, + snd_allo_piano_mode_put), + + SOC_ENUM_EXT("Dual Mode Route", + allo_piano_dual_mode_enum, + snd_allo_piano_dual_mode_get, + snd_allo_piano_dual_mode_put), + + SOC_ENUM_EXT("Lowpass Route", allo_piano_enum, + snd_allo_piano_lowpass_get, + snd_allo_piano_lowpass_put), + + SOC_DOUBLE_R_EXT_TLV("Subwoofer Playback Volume", + PCM512x_DIGITAL_VOLUME_2, + PCM512x_DIGITAL_VOLUME_3, 0, 255, 1, + pcm512x_get_reg_sub, + pcm512x_set_reg_sub, + digital_tlv_sub), + + SOC_DOUBLE_EXT("Subwoofer Playback Switch", + PCM512x_MUTE, + PCM512x_RQML_SHIFT, + PCM512x_RQMR_SHIFT, 1, 1, + pcm512x_get_reg_sub_switch, + pcm512x_set_reg_sub_switch), + + SOC_DOUBLE_R_EXT_TLV("Master Playback Volume", + PCM512x_DIGITAL_VOLUME_2, + PCM512x_DIGITAL_VOLUME_3, 0, 255, 1, + pcm512x_get_reg_master, + pcm512x_set_reg_master, + digital_tlv_master), + + SOC_DOUBLE_EXT("Master Playback Switch", + PCM512x_MUTE, + PCM512x_RQML_SHIFT, + PCM512x_RQMR_SHIFT, 1, 1, + pcm512x_get_reg_master_switch, + pcm512x_set_reg_master_switch), +}; + +static const char * const codec_ctl_pfx[] = { "Main", "Sub" }; +static const char * const codec_ctl_name[] = { + "Digital Playback Volume", + "Digital Playback Switch", + "Auto Mute Mono Switch", + "Auto Mute Switch", + "Auto Mute Time Left", + "Auto Mute Time Right", + "Clock Missing Period", + "Max Overclock DAC", + "Max Overclock DSP", + "Max Overclock PLL", + "Volume Ramp Down Emergency Rate", + "Volume Ramp Down Emergency Step", + "Volume Ramp Up Rate", + "Volume Ramp Down Rate", + "Volume Ramp Up Step", + "Volume Ramp Down Step" +}; + +static int snd_allo_piano_dac_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct glb_pool *glb_ptr; + struct snd_kcontrol *kctl; + int i, j; + + glb_ptr = kzalloc(sizeof(struct glb_pool), GFP_KERNEL); + if (!glb_ptr) + return -ENOMEM; + + card->drvdata = glb_ptr; + glb_ptr->dual_mode = 2; + glb_ptr->set_mode = 0; + + mutex_init(&glb_ptr->lock); + + // Remove codec controls + for (i = 0; i < ARRAY_SIZE(codec_ctl_pfx); i++) { + for (j = 0; j < ARRAY_SIZE(codec_ctl_name); j++) { + char cname[256]; + + sprintf(cname, "%s %s", codec_ctl_pfx[i], codec_ctl_name[j]); + kctl = snd_soc_card_get_kcontrol(card, cname); + if (kctl) { + kctl->vd[0].access = + SNDRV_CTL_ELEM_ACCESS_READWRITE; + snd_ctl_remove(card->snd_card, kctl); + } + } + } + + return 0; +} + +static void snd_allo_piano_gpio_mute(struct snd_soc_card *card) +{ + if (mute_gpio[0]) + gpiod_set_value_cansleep(mute_gpio[0], P_MUTE); + + if (mute_gpio[1]) + gpiod_set_value_cansleep(mute_gpio[1], P_MUTE); +} + +static void snd_allo_piano_gpio_unmute(struct snd_soc_card *card) +{ + if (mute_gpio[0]) + gpiod_set_value_cansleep(mute_gpio[0], P_UNMUTE); + + if (mute_gpio[1]) + gpiod_set_value_cansleep(mute_gpio[1], P_UNMUTE); +} + +static int snd_allo_piano_set_bias_level(struct snd_soc_card *card, + struct snd_soc_dapm_context *dapm, enum snd_soc_bias_level level) +{ + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_dai *codec_dai; + + rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]); + codec_dai = snd_soc_rtd_to_codec(rtd, 0); + + if (dapm->dev != codec_dai->dev) + return 0; + + switch (level) { + case SND_SOC_BIAS_PREPARE: + if (dapm->bias_level != SND_SOC_BIAS_STANDBY) + break; + /* UNMUTE DAC */ + snd_allo_piano_gpio_unmute(card); + break; + + case SND_SOC_BIAS_STANDBY: + if (dapm->bias_level != SND_SOC_BIAS_PREPARE) + break; + /* MUTE DAC */ + snd_allo_piano_gpio_mute(card); + break; + + default: + break; + } + + return 0; +} + +static int snd_allo_piano_dac_startup( + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + + snd_allo_piano_gpio_mute(card); + + return 0; +} + +static int snd_allo_piano_dac_hw_params( + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + unsigned int rate = params_rate(params); + struct snd_soc_card *card = rtd->card; + struct glb_pool *glb_ptr = card->drvdata; + int ret = 0, val = 0, dac; + + for (dac = 0; (glb_mclk && dac < 2); dac++) { + /* Configure the PLL clock reference for both the Codecs */ + val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, dac)->component, + PCM512x_RATE_DET_4); + + if (val & 0x40) { + snd_soc_component_write(snd_soc_rtd_to_codec(rtd, dac)->component, + PCM512x_PLL_REF, + PCM512x_SREF_BCK); + + dev_info(snd_soc_rtd_to_codec(rtd, dac)->component->dev, + "Setting BCLK as input clock & Enable PLL\n"); + } else { + snd_soc_component_write(snd_soc_rtd_to_codec(rtd, dac)->component, + PCM512x_PLL_EN, + 0x00); + + snd_soc_component_write(snd_soc_rtd_to_codec(rtd, dac)->component, + PCM512x_PLL_REF, + PCM512x_SREF_SCK); + + dev_info(snd_soc_rtd_to_codec(rtd, dac)->component->dev, + "Setting SCLK as input clock & disabled PLL\n"); + } + } + + ret = snd_allo_piano_dsp_program(rtd, glb_ptr->set_mode, rate, + glb_ptr->set_lowpass); + if (ret < 0) + return ret; + + return ret; +} + +static int snd_allo_piano_dac_prepare( + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + + snd_allo_piano_gpio_unmute(card); + + return 0; +} + +/* machine stream operations */ +static struct snd_soc_ops snd_allo_piano_dac_ops = { + .startup = snd_allo_piano_dac_startup, + .hw_params = snd_allo_piano_dac_hw_params, + .prepare = snd_allo_piano_dac_prepare, +}; + +static struct snd_soc_dai_link_component allo_piano_2_1_codecs[] = { + { + .dai_name = "pcm512x-hifi", + }, + { + .dai_name = "pcm512x-hifi", + }, +}; + +SND_SOC_DAILINK_DEFS(allo_piano_dai_plus, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004c", "pcm512x-hifi"), + COMP_CODEC("pcm512x.1-004d", "pcm512x-hifi")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link snd_allo_piano_dac_dai[] = { + { + .name = "PianoDACPlus", + .stream_name = "PianoDACPlus", + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &snd_allo_piano_dac_ops, + .init = snd_allo_piano_dac_init, + SND_SOC_DAILINK_REG(allo_piano_dai_plus), + }, +}; + +/* audio machine driver */ +static struct snd_soc_card snd_allo_piano_dac = { + .name = "PianoDACPlus", + .owner = THIS_MODULE, + .dai_link = snd_allo_piano_dac_dai, + .num_links = ARRAY_SIZE(snd_allo_piano_dac_dai), + .controls = allo_piano_controls, + .num_controls = ARRAY_SIZE(allo_piano_controls), +}; + +static int snd_allo_piano_dac_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &snd_allo_piano_dac; + int ret = 0, i = 0; + + card->dev = &pdev->dev; + platform_set_drvdata(pdev, &snd_allo_piano_dac); + + if (pdev->dev.of_node) { + struct device_node *i2s_node; + struct snd_soc_dai_link *dai; + + dai = &snd_allo_piano_dac_dai[0]; + i2s_node = of_parse_phandle(pdev->dev.of_node, + "i2s-controller", 0); + if (i2s_node) { + for (i = 0; i < card->num_links; i++) { + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + } + } + digital_gain_0db_limit = + !of_property_read_bool(pdev->dev.of_node, + "allo,24db_digital_gain"); + + glb_mclk = of_property_read_bool(pdev->dev.of_node, + "allo,glb_mclk"); + + allo_piano_2_1_codecs[0].of_node = + of_parse_phandle(pdev->dev.of_node, "audio-codec", 0); + allo_piano_2_1_codecs[1].of_node = + of_parse_phandle(pdev->dev.of_node, "audio-codec", 1); + if (!allo_piano_2_1_codecs[0].of_node || !allo_piano_2_1_codecs[1].of_node) + return dev_err_probe(&pdev->dev, -EINVAL, + "Property 'audio-codec' missing or invalid\n"); + + mute_gpio[0] = devm_gpiod_get_optional(&pdev->dev, "mute1", + GPIOD_OUT_LOW); + if (IS_ERR(mute_gpio[0])) + return dev_err_probe(&pdev->dev, PTR_ERR(mute_gpio[0]), + "failed to get mute1 gpio\n"); + + mute_gpio[1] = devm_gpiod_get_optional(&pdev->dev, "mute2", + GPIOD_OUT_LOW); + if (IS_ERR(mute_gpio[1])) + return dev_err_probe(&pdev->dev, PTR_ERR(mute_gpio[1]), + "failed to get mute2 gpio\n"); + + if (mute_gpio[0] && mute_gpio[1]) + snd_allo_piano_dac.set_bias_level = + snd_allo_piano_set_bias_level; + + ret = snd_soc_register_card(&snd_allo_piano_dac); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, "snd_soc_register_card() failed\n"); + + if (digital_gain_0db_limit) { + ret = snd_soc_limit_volume(card, "Master Playback Volume", + 207); + if (ret < 0) + dev_warn(card->dev, "Failed to set master volume limit: %d\n", + ret); + + ret = snd_soc_limit_volume(card, "Subwoofer Playback Volume", + 207); + if (ret < 0) + dev_warn(card->dev, "Failed to set subwoofer volume limit: %d\n", + ret); + } + + if ((mute_gpio[0]) && (mute_gpio[1])) + snd_allo_piano_gpio_mute(&snd_allo_piano_dac); + + return 0; + } + + return -EINVAL; +} + +static void snd_allo_piano_dac_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + + kfree(&card->drvdata); + snd_allo_piano_gpio_mute(&snd_allo_piano_dac); + snd_soc_unregister_card(&snd_allo_piano_dac); +} + +static const struct of_device_id snd_allo_piano_dac_of_match[] = { + { .compatible = "allo,piano-dac-plus", }, + { /* sentinel */ }, +}; + +MODULE_DEVICE_TABLE(of, snd_allo_piano_dac_of_match); + +static struct platform_driver snd_allo_piano_dac_driver = { + .driver = { + .name = "snd-allo-piano-dac-plus", + .owner = THIS_MODULE, + .of_match_table = snd_allo_piano_dac_of_match, + }, + .probe = snd_allo_piano_dac_probe, + .remove = snd_allo_piano_dac_remove, +}; + +module_platform_driver(snd_allo_piano_dac_driver); + +MODULE_AUTHOR("Baswaraj K <jaikumar@cem-solutions.net>"); +MODULE_DESCRIPTION("ALSA ASoC Machine Driver for Allo Piano DAC Plus"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/allo-piano-dac.c b/sound/soc/bcm/allo-piano-dac.c new file mode 100644 index 00000000000000..61640fb9543146 --- /dev/null +++ b/sound/soc/bcm/allo-piano-dac.c @@ -0,0 +1,122 @@ +/* + * ALSA ASoC Machine Driver for Allo Piano DAC + * + * Author: Baswaraj K <jaikumar@cem-solutions.net> + * Copyright 2016 + * based on code by Daniel Matuschek <info@crazy-audio.com> + * based on code by Florian Meier <florian.meier@koalo.de> + * + * 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 <linux/module.h> +#include <linux/platform_device.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +static bool digital_gain_0db_limit = true; + +static int snd_allo_piano_dac_init(struct snd_soc_pcm_runtime *rtd) +{ + if (digital_gain_0db_limit) { + int ret; + struct snd_soc_card *card = rtd->card; + + ret = snd_soc_limit_volume(card, "Digital Playback Volume", + 207); + if (ret < 0) + dev_warn(card->dev, "Failed to set volume limit: %d\n", + ret); + } + + return 0; +} + +SND_SOC_DAILINK_DEFS(allo_piano_dai, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004c", "pcm512x-hifi")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0"))); + +static struct snd_soc_dai_link snd_allo_piano_dac_dai[] = { +{ + .name = "Piano DAC", + .stream_name = "Piano DAC HiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .init = snd_allo_piano_dac_init, + SND_SOC_DAILINK_REG(allo_piano_dai), +}, +}; + +/* audio machine driver */ +static struct snd_soc_card snd_allo_piano_dac = { + .name = "PianoDAC", + .owner = THIS_MODULE, + .dai_link = snd_allo_piano_dac_dai, + .num_links = ARRAY_SIZE(snd_allo_piano_dac_dai), +}; + +static int snd_allo_piano_dac_probe(struct platform_device *pdev) +{ + int ret = 0; + + snd_allo_piano_dac.dev = &pdev->dev; + + if (pdev->dev.of_node) { + struct device_node *i2s_node; + struct snd_soc_dai_link *dai; + + dai = &snd_allo_piano_dac_dai[0]; + i2s_node = of_parse_phandle(pdev->dev.of_node, + "i2s-controller", 0); + + if (i2s_node) { + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + } + + digital_gain_0db_limit = !of_property_read_bool( + pdev->dev.of_node, "allo,24db_digital_gain"); + } + + ret = devm_snd_soc_register_card(&pdev->dev, &snd_allo_piano_dac); + if (ret && ret != -EPROBE_DEFER) + dev_err(&pdev->dev, + "snd_soc_register_card() failed: %d\n", ret); + + return ret; +} + +static const struct of_device_id snd_allo_piano_dac_of_match[] = { + { .compatible = "allo,piano-dac", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, snd_allo_piano_dac_of_match); + +static struct platform_driver snd_allo_piano_dac_driver = { + .driver = { + .name = "snd-allo-piano-dac", + .owner = THIS_MODULE, + .of_match_table = snd_allo_piano_dac_of_match, + }, + .probe = snd_allo_piano_dac_probe, +}; + +module_platform_driver(snd_allo_piano_dac_driver); + +MODULE_AUTHOR("Baswaraj K <jaikumar@cem-solutions.net>"); +MODULE_DESCRIPTION("ALSA ASoC Machine Driver for Allo Piano DAC"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/audioinjector-isolated-soundcard.c b/sound/soc/bcm/audioinjector-isolated-soundcard.c new file mode 100644 index 00000000000000..6abe08223f3406 --- /dev/null +++ b/sound/soc/bcm/audioinjector-isolated-soundcard.c @@ -0,0 +1,184 @@ +/* + * ASoC Driver for AudioInjector.net isolated soundcard + * + * Created on: 20-February-2020 + * Author: flatmax@flatmax.org + * based on audioinjector-octo-soundcard.c + * + * Copyright (C) 2020 Flatmax Pty. 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. + * + * 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 <linux/module.h> +#include <linux/types.h> +#include <linux/gpio/consumer.h> + +#include <sound/core.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> +#include <sound/control.h> + +static struct gpio_desc *mute_gpio; + +static const unsigned int audioinjector_isolated_rates[] = { + 192000, 96000, 48000, 32000, 24000, 16000, 8000 +}; + +static struct snd_pcm_hw_constraint_list audioinjector_isolated_constraints = { + .list = audioinjector_isolated_rates, + .count = ARRAY_SIZE(audioinjector_isolated_rates), +}; + +static int audioinjector_isolated_dai_init(struct snd_soc_pcm_runtime *rtd) +{ + int ret=snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), 0, 24576000, 0); + if (ret) + return ret; + + return snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_cpu(rtd, 0), 64); +} + +static int audioinjector_isolated_startup(struct snd_pcm_substream *substream) +{ + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &audioinjector_isolated_constraints); + + return 0; +} + +static int audioinjector_isolated_trigger(struct snd_pcm_substream *substream, + int cmd){ + + switch (cmd) { + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + gpiod_set_value(mute_gpio, 0); + break; + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + gpiod_set_value(mute_gpio, 1); + break; + default: + return -EINVAL; + } + return 0; +} + +static struct snd_soc_ops audioinjector_isolated_ops = { + .startup = audioinjector_isolated_startup, + .trigger = audioinjector_isolated_trigger, +}; + +SND_SOC_DAILINK_DEFS(audioinjector_isolated, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("cs4271.1-0010", "cs4271-hifi")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0"))); + +static struct snd_soc_dai_link audioinjector_isolated_dai[] = { + { + .name = "AudioInjector ISO", + .stream_name = "AI-HIFI", + .ops = &audioinjector_isolated_ops, + .init = audioinjector_isolated_dai_init, + .symmetric_rate = 1, + .symmetric_channels = 1, + .dai_fmt = SND_SOC_DAIFMT_CBM_CFM|SND_SOC_DAIFMT_I2S|SND_SOC_DAIFMT_NB_NF, + SND_SOC_DAILINK_REG(audioinjector_isolated), + } +}; + +static const struct snd_soc_dapm_widget audioinjector_isolated_widgets[] = { + SND_SOC_DAPM_OUTPUT("OUTPUTS"), + SND_SOC_DAPM_INPUT("INPUTS"), +}; + +static const struct snd_soc_dapm_route audioinjector_isolated_route[] = { + /* Balanced outputs */ + {"OUTPUTS", NULL, "AOUTA+"}, + {"OUTPUTS", NULL, "AOUTA-"}, + {"OUTPUTS", NULL, "AOUTB+"}, + {"OUTPUTS", NULL, "AOUTB-"}, + + /* Balanced inputs */ + {"AINA", NULL, "INPUTS"}, + {"AINB", NULL, "INPUTS"}, +}; + +static struct snd_soc_card snd_soc_audioinjector_isolated = { + .name = "audioinjector-isolated-soundcard", + .owner = THIS_MODULE, + .dai_link = audioinjector_isolated_dai, + .num_links = ARRAY_SIZE(audioinjector_isolated_dai), + + .dapm_widgets = audioinjector_isolated_widgets, + .num_dapm_widgets = ARRAY_SIZE(audioinjector_isolated_widgets), + .dapm_routes = audioinjector_isolated_route, + .num_dapm_routes = ARRAY_SIZE(audioinjector_isolated_route), +}; + +static int audioinjector_isolated_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &snd_soc_audioinjector_isolated; + int ret; + + card->dev = &pdev->dev; + + if (pdev->dev.of_node) { + struct snd_soc_dai_link *dai = &audioinjector_isolated_dai[0]; + struct device_node *i2s_node = + of_parse_phandle(pdev->dev.of_node, "i2s-controller", 0); + + if (i2s_node) { + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + } else { + dev_err(&pdev->dev, + "i2s-controller missing or invalid in DT\n"); + return -EINVAL; + } + + mute_gpio = devm_gpiod_get_optional(&pdev->dev, "mute", GPIOD_OUT_LOW); + if (IS_ERR(mute_gpio)){ + dev_err(&pdev->dev, "mute gpio not found in dt overlay\n"); + return PTR_ERR(mute_gpio); + } + } + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret && ret != -EPROBE_DEFER) + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + return ret; +} + +static const struct of_device_id audioinjector_isolated_of_match[] = { + { .compatible = "ai,audioinjector-isolated-soundcard", }, + {}, +}; +MODULE_DEVICE_TABLE(of, audioinjector_isolated_of_match); + +static struct platform_driver audioinjector_isolated_driver = { + .driver = { + .name = "audioinjector-isolated", + .owner = THIS_MODULE, + .of_match_table = audioinjector_isolated_of_match, + }, + .probe = audioinjector_isolated_probe, +}; + +module_platform_driver(audioinjector_isolated_driver); +MODULE_AUTHOR("Matt Flax <flatmax@flatmax.org>"); +MODULE_DESCRIPTION("AudioInjector.net isolated Soundcard"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:audioinjector-isolated-soundcard"); diff --git a/sound/soc/bcm/audioinjector-octo-soundcard.c b/sound/soc/bcm/audioinjector-octo-soundcard.c new file mode 100644 index 00000000000000..e9521d0c60a224 --- /dev/null +++ b/sound/soc/bcm/audioinjector-octo-soundcard.c @@ -0,0 +1,347 @@ +/* + * ASoC Driver for AudioInjector Pi octo channel soundcard (hat) + * + * Created on: 27-October-2016 + * Author: flatmax@flatmax.org + * based on audioinjector-pi-soundcard.c + * + * Copyright (C) 2016 Flatmax Pty. 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. + * + * 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 <linux/module.h> +#include <linux/types.h> +#include <linux/gpio/consumer.h> + +#include <sound/core.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> +#include <sound/control.h> + +static struct gpio_descs *mult_gpios; +static struct gpio_desc *codec_rst_gpio; +static unsigned int audioinjector_octo_rate; +static bool non_stop_clocks; + +static const unsigned int audioinjector_octo_rates[] = { + 96000, 48000, 32000, 24000, 16000, 8000, 88200, 44100, 29400, 22050, 14700, +}; + +static struct snd_pcm_hw_constraint_list audioinjector_octo_constraints = { + .list = audioinjector_octo_rates, + .count = ARRAY_SIZE(audioinjector_octo_rates), +}; + +static int audioinjector_octo_dai_init(struct snd_soc_pcm_runtime *rtd) +{ + return snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_cpu(rtd, 0), 64); +} + +static int audioinjector_octo_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + snd_soc_rtd_to_cpu(rtd, 0)->driver->playback.channels_min = 8; + snd_soc_rtd_to_cpu(rtd, 0)->driver->playback.channels_max = 8; + snd_soc_rtd_to_cpu(rtd, 0)->driver->capture.channels_min = 8; + snd_soc_rtd_to_cpu(rtd, 0)->driver->capture.channels_max = 8; + snd_soc_rtd_to_codec(rtd, 0)->driver->capture.channels_max = 8; + + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &audioinjector_octo_constraints); + + return 0; +} + +static void audioinjector_octo_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + snd_soc_rtd_to_cpu(rtd, 0)->driver->playback.channels_min = 2; + snd_soc_rtd_to_cpu(rtd, 0)->driver->playback.channels_max = 2; + snd_soc_rtd_to_cpu(rtd, 0)->driver->capture.channels_min = 2; + snd_soc_rtd_to_cpu(rtd, 0)->driver->capture.channels_max = 2; + snd_soc_rtd_to_codec(rtd, 0)->driver->capture.channels_max = 6; +} + +static int audioinjector_octo_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + // set codec DAI configuration + int ret = snd_soc_dai_set_fmt(snd_soc_rtd_to_codec(rtd, 0), + SND_SOC_DAIFMT_CBS_CFS|SND_SOC_DAIFMT_DSP_A| + SND_SOC_DAIFMT_NB_NF); + if (ret < 0) + return ret; + + // set cpu DAI configuration + ret = snd_soc_dai_set_fmt(snd_soc_rtd_to_cpu(rtd, 0), + SND_SOC_DAIFMT_CBM_CFM|SND_SOC_DAIFMT_I2S| + SND_SOC_DAIFMT_NB_NF); + if (ret < 0) + return ret; + + audioinjector_octo_rate = params_rate(params); + + // Set the correct sysclock for the codec + switch (audioinjector_octo_rate) { + case 96000: + case 48000: + return snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), 0, 49152000, + 0); + break; + case 24000: + return snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), 0, 49152000/2, + 0); + break; + case 32000: + case 16000: + return snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), 0, 49152000/3, + 0); + break; + case 8000: + return snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), 0, 49152000/6, + 0); + break; + case 88200: + case 44100: + return snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), 0, 45185400, + 0); + break; + case 22050: + return snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), 0, 45185400/2, + 0); + break; + case 29400: + case 14700: + return snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), 0, 45185400/3, + 0); + break; + default: + return -EINVAL; + } +} + +static int audioinjector_octo_trigger(struct snd_pcm_substream *substream, + int cmd){ + DECLARE_BITMAP(mult, 4); + + memset(mult, 0, sizeof(mult)); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (!non_stop_clocks) + break; + fallthrough; + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + switch (audioinjector_octo_rate) { + case 96000: + __assign_bit(3, mult, 1); + fallthrough; + case 88200: + __assign_bit(1, mult, 1); + __assign_bit(2, mult, 1); + break; + case 48000: + __assign_bit(3, mult, 1); + fallthrough; + case 44100: + __assign_bit(2, mult, 1); + break; + case 32000: + __assign_bit(3, mult, 1); + fallthrough; + case 29400: + __assign_bit(0, mult, 1); + __assign_bit(1, mult, 1); + break; + case 24000: + __assign_bit(3, mult, 1); + fallthrough; + case 22050: + __assign_bit(1, mult, 1); + break; + case 16000: + __assign_bit(3, mult, 1); + fallthrough; + case 14700: + __assign_bit(0, mult, 1); + break; + case 8000: + __assign_bit(3, mult, 1); + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + gpiod_set_array_value_cansleep(mult_gpios->ndescs, mult_gpios->desc, + NULL, mult); + + return 0; +} + +static struct snd_soc_ops audioinjector_octo_ops = { + .startup = audioinjector_octo_startup, + .shutdown = audioinjector_octo_shutdown, + .hw_params = audioinjector_octo_hw_params, + .trigger = audioinjector_octo_trigger, +}; + +SND_SOC_DAILINK_DEFS(audioinjector_octo, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "cs42448")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link audioinjector_octo_dai[] = { + { + .name = "AudioInjector Octo", + .stream_name = "AudioInject-HIFI", + .ops = &audioinjector_octo_ops, + .init = audioinjector_octo_dai_init, + .symmetric_rate = 1, + .symmetric_channels = 1, + SND_SOC_DAILINK_REG(audioinjector_octo), + }, +}; + +static const struct snd_soc_dapm_widget audioinjector_octo_widgets[] = { + SND_SOC_DAPM_OUTPUT("OUTPUTS0"), + SND_SOC_DAPM_OUTPUT("OUTPUTS1"), + SND_SOC_DAPM_OUTPUT("OUTPUTS2"), + SND_SOC_DAPM_OUTPUT("OUTPUTS3"), + SND_SOC_DAPM_INPUT("INPUTS0"), + SND_SOC_DAPM_INPUT("INPUTS1"), + SND_SOC_DAPM_INPUT("INPUTS2"), +}; + +static const struct snd_soc_dapm_route audioinjector_octo_route[] = { + /* Balanced outputs */ + {"OUTPUTS0", NULL, "AOUT1L"}, + {"OUTPUTS0", NULL, "AOUT1R"}, + {"OUTPUTS1", NULL, "AOUT2L"}, + {"OUTPUTS1", NULL, "AOUT2R"}, + {"OUTPUTS2", NULL, "AOUT3L"}, + {"OUTPUTS2", NULL, "AOUT3R"}, + {"OUTPUTS3", NULL, "AOUT4L"}, + {"OUTPUTS3", NULL, "AOUT4R"}, + + /* Balanced inputs */ + {"AIN1L", NULL, "INPUTS0"}, + {"AIN1R", NULL, "INPUTS0"}, + {"AIN2L", NULL, "INPUTS1"}, + {"AIN2R", NULL, "INPUTS1"}, + {"AIN3L", NULL, "INPUTS2"}, + {"AIN3R", NULL, "INPUTS2"}, +}; + +static struct snd_soc_card snd_soc_audioinjector_octo = { + .name = "audioinjector-octo-soundcard", + .owner = THIS_MODULE, + .dai_link = audioinjector_octo_dai, + .num_links = ARRAY_SIZE(audioinjector_octo_dai), + + .dapm_widgets = audioinjector_octo_widgets, + .num_dapm_widgets = ARRAY_SIZE(audioinjector_octo_widgets), + .dapm_routes = audioinjector_octo_route, + .num_dapm_routes = ARRAY_SIZE(audioinjector_octo_route), +}; + +static int audioinjector_octo_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &snd_soc_audioinjector_octo; + int ret; + + card->dev = &pdev->dev; + + if (pdev->dev.of_node) { + struct snd_soc_dai_link *dai = &audioinjector_octo_dai[0]; + struct device_node *i2s_node = + of_parse_phandle(pdev->dev.of_node, + "i2s-controller", 0); + struct device_node *codec_node = + of_parse_phandle(pdev->dev.of_node, + "codec", 0); + + mult_gpios = devm_gpiod_get_array_optional(&pdev->dev, "mult", + GPIOD_OUT_LOW); + if (IS_ERR(mult_gpios)) + return PTR_ERR(mult_gpios); + + codec_rst_gpio = devm_gpiod_get_optional(&pdev->dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(codec_rst_gpio)) + return PTR_ERR(codec_rst_gpio); + + non_stop_clocks = of_property_read_bool(pdev->dev.of_node, "non-stop-clocks"); + + if (codec_rst_gpio) + gpiod_set_value(codec_rst_gpio, 1); + msleep(500); + if (codec_rst_gpio) + gpiod_set_value(codec_rst_gpio, 0); + msleep(500); + if (codec_rst_gpio) + gpiod_set_value(codec_rst_gpio, 1); + msleep(500); + + if (i2s_node && codec_node) { + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + dai->codecs->name = NULL; + dai->codecs->of_node = codec_node; + } else + if (!i2s_node) { + dev_err(&pdev->dev, + "i2s-controller missing or invalid in DT\n"); + return -EINVAL; + } else { + dev_err(&pdev->dev, + "Property 'codec' missing or invalid\n"); + return -EINVAL; + } + } + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret != 0) + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + return ret; +} + +static const struct of_device_id audioinjector_octo_of_match[] = { + { .compatible = "ai,audioinjector-octo-soundcard", }, + {}, +}; +MODULE_DEVICE_TABLE(of, audioinjector_octo_of_match); + +static struct platform_driver audioinjector_octo_driver = { + .driver = { + .name = "audioinjector-octo", + .owner = THIS_MODULE, + .of_match_table = audioinjector_octo_of_match, + }, + .probe = audioinjector_octo_probe, +}; + +module_platform_driver(audioinjector_octo_driver); +MODULE_AUTHOR("Matt Flax <flatmax@flatmax.org>"); +MODULE_DESCRIPTION("AudioInjector.net octo Soundcard"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:audioinjector-octo-soundcard"); diff --git a/sound/soc/bcm/audioinjector-pi-soundcard.c b/sound/soc/bcm/audioinjector-pi-soundcard.c new file mode 100644 index 00000000000000..23958e5bf28f64 --- /dev/null +++ b/sound/soc/bcm/audioinjector-pi-soundcard.c @@ -0,0 +1,190 @@ +/* + * ASoC Driver for AudioInjector Pi add on soundcard + * + * Created on: 13-May-2016 + * Author: flatmax@flatmax.org + * based on code by Cliff Cai <Cliff.Cai@analog.com> for the ssm2602 machine blackfin. + * with help from Lars-Peter Clausen for simplifying the original code to use the dai_fmt field. + * i2s_node code taken from the other sound/soc/bcm machine drivers. + * + * Copyright (C) 2016 Flatmax Pty. 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. + * + * 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 <linux/module.h> +#include <linux/types.h> + +#include <sound/core.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> +#include <sound/control.h> + +#include "../codecs/wm8731.h" + +static const unsigned int bcm2835_rates_12000000[] = { + 8000, 16000, 32000, 44100, 48000, 96000, 88200, +}; + +static struct snd_pcm_hw_constraint_list bcm2835_constraints_12000000 = { + .list = bcm2835_rates_12000000, + .count = ARRAY_SIZE(bcm2835_rates_12000000), +}; + +static int snd_audioinjector_pi_soundcard_startup(struct snd_pcm_substream *substream) +{ + /* Setup constraints, because there is a 12 MHz XTAL on the board */ + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &bcm2835_constraints_12000000); + return 0; +} + +static int snd_audioinjector_pi_soundcard_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + + switch (params_rate(params)){ + case 8000: + return snd_soc_dai_set_bclk_ratio(cpu_dai, 1); + case 16000: + return snd_soc_dai_set_bclk_ratio(cpu_dai, 750); + case 32000: + return snd_soc_dai_set_bclk_ratio(cpu_dai, 375); + case 44100: + return snd_soc_dai_set_bclk_ratio(cpu_dai, 272); + case 48000: + return snd_soc_dai_set_bclk_ratio(cpu_dai, 250); + case 88200: + return snd_soc_dai_set_bclk_ratio(cpu_dai, 136); + case 96000: + return snd_soc_dai_set_bclk_ratio(cpu_dai, 125); + default: + return snd_soc_dai_set_bclk_ratio(cpu_dai, 125); + } +} + +/* machine stream operations */ +static struct snd_soc_ops snd_audioinjector_pi_soundcard_ops = { + .startup = snd_audioinjector_pi_soundcard_startup, + .hw_params = snd_audioinjector_pi_soundcard_hw_params, +}; + +static int audioinjector_pi_soundcard_dai_init(struct snd_soc_pcm_runtime *rtd) +{ + return snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), WM8731_SYSCLK_XTAL, 12000000, SND_SOC_CLOCK_IN); +} + +SND_SOC_DAILINK_DEFS(audioinjector_pi, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("wm8731.1-001a", "wm8731-hifi")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2835-i2s.0"))); + +static struct snd_soc_dai_link audioinjector_pi_soundcard_dai[] = { + { + .name = "AudioInjector audio", + .stream_name = "AudioInjector audio", + .ops = &snd_audioinjector_pi_soundcard_ops, + .init = audioinjector_pi_soundcard_dai_init, + .dai_fmt = SND_SOC_DAIFMT_CBM_CFM|SND_SOC_DAIFMT_I2S|SND_SOC_DAIFMT_NB_NF, + SND_SOC_DAILINK_REG(audioinjector_pi), + }, +}; + +static const struct snd_soc_dapm_widget wm8731_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), + SND_SOC_DAPM_LINE("Line In Jacks", NULL), + SND_SOC_DAPM_MIC("Microphone", NULL), +}; + +static const struct snd_soc_dapm_route audioinjector_audio_map[] = { + /* headphone connected to LHPOUT, RHPOUT */ + {"Headphone Jack", NULL, "LHPOUT"}, + {"Headphone Jack", NULL, "RHPOUT"}, + + /* speaker connected to LOUT, ROUT */ + {"Ext Spk", NULL, "ROUT"}, + {"Ext Spk", NULL, "LOUT"}, + + /* line inputs */ + {"Line In Jacks", NULL, "Line Input"}, + + /* mic is connected to Mic Jack, with WM8731 Mic Bias */ + {"Microphone", NULL, "Mic Bias"}, +}; + +static struct snd_soc_card snd_soc_audioinjector = { + .name = "audioinjector-pi-soundcard", + .owner = THIS_MODULE, + .dai_link = audioinjector_pi_soundcard_dai, + .num_links = ARRAY_SIZE(audioinjector_pi_soundcard_dai), + + .dapm_widgets = wm8731_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8731_dapm_widgets), + .dapm_routes = audioinjector_audio_map, + .num_dapm_routes = ARRAY_SIZE(audioinjector_audio_map), +}; + +static int audioinjector_pi_soundcard_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &snd_soc_audioinjector; + int ret; + + card->dev = &pdev->dev; + + if (pdev->dev.of_node) { + struct snd_soc_dai_link *dai = &audioinjector_pi_soundcard_dai[0]; + struct device_node *i2s_node = of_parse_phandle(pdev->dev.of_node, + "i2s-controller", 0); + + if (i2s_node) { + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + } else + if (!dai->cpus->of_node) { + dev_err(&pdev->dev, "Property 'i2s-controller' missing or invalid\n"); + return -EINVAL; + } + } + + if ((ret = devm_snd_soc_register_card(&pdev->dev, card))) + return dev_err_probe(&pdev->dev, ret, "%s\n", __func__); + + dev_info(&pdev->dev, "successfully loaded\n"); + + return ret; +} + +static const struct of_device_id audioinjector_pi_soundcard_of_match[] = { + { .compatible = "ai,audioinjector-pi-soundcard", }, + {}, +}; +MODULE_DEVICE_TABLE(of, audioinjector_pi_soundcard_of_match); + +static struct platform_driver audioinjector_pi_soundcard_driver = { + .driver = { + .name = "audioinjector-stereo", + .owner = THIS_MODULE, + .of_match_table = audioinjector_pi_soundcard_of_match, + }, + .probe = audioinjector_pi_soundcard_probe, +}; + +module_platform_driver(audioinjector_pi_soundcard_driver); +MODULE_AUTHOR("Matt Flax <flatmax@flatmax.org>"); +MODULE_DESCRIPTION("AudioInjector.net Pi Soundcard"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:audioinjector-pi-soundcard"); + diff --git a/sound/soc/bcm/audiosense-pi.c b/sound/soc/bcm/audiosense-pi.c new file mode 100644 index 00000000000000..200bb76df2ccf0 --- /dev/null +++ b/sound/soc/bcm/audiosense-pi.c @@ -0,0 +1,247 @@ +/* + * ASoC Driver for AudioSense add on soundcard + * Author: + * Bhargav A K <anur.bhargav@gmail.com> + * Copyright 2017 + * + * 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 <linux/module.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/i2c.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include <sound/control.h> + +#include <sound/tlv320aic32x4.h> +#include "../codecs/tlv320aic32x4.h" + +#define AIC32X4_SYSCLK_XTAL 0x00 + +/* + * Setup Codec Sample Rates and Channels + * Supported Rates: + * 8000, 11025, 16000, 22050, 32000, 44100, 48000, 64000, 88200, 96000, + */ +static const unsigned int audiosense_pi_rate[] = { + 48000, +}; + +static struct snd_pcm_hw_constraint_list audiosense_constraints_rates = { + .list = audiosense_pi_rate, + .count = ARRAY_SIZE(audiosense_pi_rate), +}; + +static const unsigned int audiosense_pi_channels[] = { + 2, +}; + +static struct snd_pcm_hw_constraint_list audiosense_constraints_ch = { + .count = ARRAY_SIZE(audiosense_pi_channels), + .list = audiosense_pi_channels, + .mask = 0, +}; + +/* Setup DAPM widgets and paths */ +static const struct snd_soc_dapm_widget audiosense_pi_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_LINE("Line Out", NULL), + SND_SOC_DAPM_LINE("Line In", NULL), + SND_SOC_DAPM_INPUT("CM_L"), + SND_SOC_DAPM_INPUT("CM_R"), +}; + +static const struct snd_soc_dapm_route audiosense_pi_audio_map[] = { + /* Line Inputs are connected to + * (IN1_L | IN1_R) + * (IN2_L | IN2_R) + * (IN3_L | IN3_R) + */ + {"IN1_L", NULL, "Line In"}, + {"IN1_R", NULL, "Line In"}, + {"IN2_L", NULL, "Line In"}, + {"IN2_R", NULL, "Line In"}, + {"IN3_L", NULL, "Line In"}, + {"IN3_R", NULL, "Line In"}, + + /* Mic is connected to IN2_L and IN2_R */ + {"Left ADC", NULL, "Mic Bias"}, + {"Right ADC", NULL, "Mic Bias"}, + + /* Headphone connected to HPL, HPR */ + {"Headphone Jack", NULL, "HPL"}, + {"Headphone Jack", NULL, "HPR"}, + + /* Speakers connected to LOL and LOR */ + {"Line Out", NULL, "LOL"}, + {"Line Out", NULL, "LOR"}, +}; + +static int audiosense_pi_card_init(struct snd_soc_pcm_runtime *rtd) +{ + /* TODO: init of the codec specific dapm data, ignore suspend/resume */ + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + + snd_soc_component_update_bits(component, AIC32X4_MICBIAS, 0x78, + AIC32X4_MICBIAS_LDOIN | + AIC32X4_MICBIAS_2075V); + snd_soc_component_update_bits(component, AIC32X4_PWRCFG, 0x08, + AIC32X4_AVDDWEAKDISABLE); + snd_soc_component_update_bits(component, AIC32X4_LDOCTL, 0x01, + AIC32X4_LDOCTLEN); + + return 0; +} + +static int audiosense_pi_card_hw_params( + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + int ret = 0; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + + /* Set the codec system clock, there is a 12 MHz XTAL on the board */ + ret = snd_soc_dai_set_sysclk(codec_dai, AIC32X4_SYSCLK_XTAL, + 12000000, SND_SOC_CLOCK_IN); + if (ret) { + dev_err(rtd->card->dev, + "could not set codec driver clock params\n"); + return ret; + } + return 0; +} + +static int audiosense_pi_card_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + /* + * Set codec to 48Khz Sampling, Stereo I/O and 16 bit audio + */ + runtime->hw.channels_max = 2; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &audiosense_constraints_ch); + + runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; + snd_pcm_hw_constraint_msbits(runtime, 0, 16, 16); + + + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &audiosense_constraints_rates); + return 0; +} + +static struct snd_soc_ops audiosense_pi_card_ops = { + .startup = audiosense_pi_card_startup, + .hw_params = audiosense_pi_card_hw_params, +}; + +SND_SOC_DAILINK_DEFS(audiosense_pi, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("tlv320aic32x4.1-0018", "tlv320aic32x4-hifi")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0"))); + +static struct snd_soc_dai_link audiosense_pi_card_dai[] = { + { + .name = "TLV320AIC3204 Audio", + .stream_name = "TLV320AIC3204 Hifi Audio", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM, + .ops = &audiosense_pi_card_ops, + .init = audiosense_pi_card_init, + SND_SOC_DAILINK_REG(audiosense_pi), + }, +}; + +static struct snd_soc_card audiosense_pi_card = { + .name = "audiosense-pi", + .driver_name = "audiosense-pi", + .dai_link = audiosense_pi_card_dai, + .owner = THIS_MODULE, + .num_links = ARRAY_SIZE(audiosense_pi_card_dai), + .dapm_widgets = audiosense_pi_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(audiosense_pi_dapm_widgets), + .dapm_routes = audiosense_pi_audio_map, + .num_dapm_routes = ARRAY_SIZE(audiosense_pi_audio_map), +}; + +static int audiosense_pi_card_probe(struct platform_device *pdev) +{ + int ret = 0; + struct snd_soc_card *card = &audiosense_pi_card; + struct snd_soc_dai_link *dai = &audiosense_pi_card_dai[0]; + struct device_node *i2s_node = pdev->dev.of_node; + + card->dev = &pdev->dev; + + if (!dai) { + dev_err(&pdev->dev, "DAI not found. Missing or Invalid\n"); + return -EINVAL; + } + + i2s_node = of_parse_phandle(pdev->dev.of_node, "i2s-controller", 0); + if (!i2s_node) { + dev_err(&pdev->dev, + "Property 'i2s-controller' missing or invalid\n"); + return -EINVAL; + } + + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + + of_node_put(i2s_node); + + ret = snd_soc_register_card(card); + if (ret && ret != -EPROBE_DEFER) + dev_err(&pdev->dev, + "snd_soc_register_card() failed: %d\n", ret); + + return ret; +} + +static void audiosense_pi_card_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + + snd_soc_unregister_card(card); +} + +static const struct of_device_id audiosense_pi_card_of_match[] = { + { .compatible = "as,audiosense-pi", }, + {}, +}; +MODULE_DEVICE_TABLE(of, audiosense_pi_card_of_match); + +static struct platform_driver audiosense_pi_card_driver = { + .driver = { + .name = "audiosense-snd-card", + .owner = THIS_MODULE, + .of_match_table = audiosense_pi_card_of_match, + }, + .probe = audiosense_pi_card_probe, + .remove = audiosense_pi_card_remove, +}; + +module_platform_driver(audiosense_pi_card_driver); + +MODULE_AUTHOR("Bhargav AK <anur.bhargav@gmail.com>"); +MODULE_DESCRIPTION("ASoC Driver for TLV320AIC3204 Audio"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:audiosense-pi"); + diff --git a/sound/soc/bcm/bcm2835-i2s.c b/sound/soc/bcm/bcm2835-i2s.c index 9bda6499e66e1c..952f6e5c7536b1 100644 --- a/sound/soc/bcm/bcm2835-i2s.c +++ b/sound/soc/bcm/bcm2835-i2s.c @@ -30,7 +30,6 @@ #include <linux/init.h> #include <linux/io.h> #include <linux/module.h> -#include <linux/of_address.h> #include <linux/slab.h> #include <sound/core.h> @@ -620,6 +619,10 @@ static int bcm2835_i2s_prepare(struct snd_pcm_substream *substream, struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); uint32_t cs_reg; + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 256, + ~0); + /* * Clear both FIFOs if the one that should be started * is not empty at the moment. This should only happen @@ -701,6 +704,10 @@ static int bcm2835_i2s_startup(struct snd_pcm_substream *substream, /* Should this still be running stop it */ bcm2835_i2s_stop_clock(dev); + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + 256, ~0); + /* Enable PCM block */ regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG, BCM2835_I2S_EN, BCM2835_I2S_EN); @@ -830,8 +837,7 @@ static int bcm2835_i2s_probe(struct platform_device *pdev) struct bcm2835_i2s_dev *dev; int ret; void __iomem *base; - const __be32 *addr; - dma_addr_t dma_base; + struct resource *res; dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); @@ -846,7 +852,7 @@ static int bcm2835_i2s_probe(struct platform_device *pdev) "could not get clk\n"); /* Request ioarea */ - base = devm_platform_ioremap_resource(pdev, 0); + base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); if (IS_ERR(base)) return PTR_ERR(base); @@ -855,19 +861,11 @@ static int bcm2835_i2s_probe(struct platform_device *pdev) if (IS_ERR(dev->i2s_regmap)) return PTR_ERR(dev->i2s_regmap); - /* Set the DMA address - we have to parse DT ourselves */ - addr = of_get_address(pdev->dev.of_node, 0, NULL, NULL); - if (!addr) { - dev_err(&pdev->dev, "could not get DMA-register address\n"); - return -EINVAL; - } - dma_base = be32_to_cpup(addr); - dev->dma_data[SNDRV_PCM_STREAM_PLAYBACK].addr = - dma_base + BCM2835_I2S_FIFO_A_REG; + res->start + BCM2835_I2S_FIFO_A_REG; dev->dma_data[SNDRV_PCM_STREAM_CAPTURE].addr = - dma_base + BCM2835_I2S_FIFO_A_REG; + res->start + BCM2835_I2S_FIFO_A_REG; /* Set the bus width */ dev->dma_data[SNDRV_PCM_STREAM_PLAYBACK].addr_width = diff --git a/sound/soc/bcm/chipdip-dac.c b/sound/soc/bcm/chipdip-dac.c new file mode 100644 index 00000000000000..ddf4f5ed90f6f2 --- /dev/null +++ b/sound/soc/bcm/chipdip-dac.c @@ -0,0 +1,275 @@ +/* + * ASoC Driver for ChipDip DAC + * + * Author: Evgenij Sapunov + * Copyright 2021 + * based on code by Milan Neskovic <info@justboom.co> + * based on code by Jaikumar <jaikumar@cem-solutions.net> + * + * Thanks to Phil Elwell (pelwell) for help. + * + * 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 <linux/module.h> +#include <linux/gpio/consumer.h> +#include <linux/platform_device.h> +#include <linux/delay.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/jack.h> + +#define SR_BIT_0 0 //sample rate bits +#define SR_BIT_1 1 +#define SR_BIT_2 2 +#define BD_BIT_0 3 //bit depth bits +#define BD_BIT_1 4 + +#define SAMPLE_RATE_MASK_44_1 0 +#define SAMPLE_RATE_MASK_48 (1 << SR_BIT_0) +#define SAMPLE_RATE_MASK_88_2 ((1 << SR_BIT_2) | (1 << SR_BIT_1)) +#define SAMPLE_RATE_MASK_96 (1 << SR_BIT_1) +#define SAMPLE_RATE_MASK_176_4 ((1 << SR_BIT_2) | (1 << SR_BIT_1) | (1 << SR_BIT_0)) +#define SAMPLE_RATE_MASK_192 ((1 << SR_BIT_1) | (1 << SR_BIT_0)) +#define SAMPLE_RATE_MASK ((1 << SR_BIT_2) | (1 << SR_BIT_1) | (1 << SR_BIT_0)) + +#define BIT_DEPTH_MASK_16 0 +#define BIT_DEPTH_MASK_24 (1 << BD_BIT_0) +#define BIT_DEPTH_MASK_32 (1 << BD_BIT_1) +#define BIT_DEPTH_MASK ((1 << BD_BIT_1) | (1 << BD_BIT_0)) + +#define MUTE_ACTIVE 0 +#define MUTE_NOT_ACTIVE 1 + +#define HW_PARAMS_GPIO_COUNT 5 + +static struct gpio_desc *mute_gpio; +static struct gpio_desc *sdwn_gpio; +static struct gpio_desc *hw_params_gpios[HW_PARAMS_GPIO_COUNT]; +static int current_width; +static int current_rate; + +static void snd_rpi_chipdip_dac_gpio_array_set(int value); +static void snd_rpi_chipdip_dac_gpio_set(struct gpio_desc *gpio_item, int value); + +static void snd_rpi_chipdip_dac_gpio_array_set(int value) +{ + int i = 0; + + for (i = 0; i < HW_PARAMS_GPIO_COUNT; i++) + snd_rpi_chipdip_dac_gpio_set(hw_params_gpios[i], ((value >> i) & 1)); +} + +static void snd_rpi_chipdip_dac_gpio_set(struct gpio_desc *gpio_item, int value) +{ + if (gpio_item) + gpiod_set_value_cansleep(gpio_item, value); +} + +static int snd_rpi_chipdip_dac_init(struct snd_soc_pcm_runtime *rtd) +{ + return 0; +} + +static int snd_rpi_chipdip_dac_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + int ret = 0; + int gpio_change_pending = 0; + int sample_rate_state = 0; + int bit_depth_state = 0; + int param_value = params_width(params); + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_cpu(rtd, 0), 2 * 32); + + if (current_width != param_value) { + current_width = param_value; + gpio_change_pending = 1; + + switch (param_value) { + case 16: + bit_depth_state = BIT_DEPTH_MASK_16; + break; + case 24: + bit_depth_state = BIT_DEPTH_MASK_24; + break; + case 32: + bit_depth_state = BIT_DEPTH_MASK_32; + break; + default: + return -EINVAL; + } + } + + param_value = params_rate(params); + if (current_rate != param_value) { + current_rate = param_value; + gpio_change_pending = 1; + + switch (param_value) { + case 44100: + sample_rate_state = SAMPLE_RATE_MASK_44_1; + break; + case 48000: + sample_rate_state = SAMPLE_RATE_MASK_48; + break; + case 88200: + sample_rate_state = SAMPLE_RATE_MASK_88_2; + break; + case 96000: + sample_rate_state = SAMPLE_RATE_MASK_96; + break; + case 176400: + sample_rate_state = SAMPLE_RATE_MASK_176_4; + break; + case 192000: + sample_rate_state = SAMPLE_RATE_MASK_192; + break; + default: + return -EINVAL; + } + } + + if (gpio_change_pending) { + snd_rpi_chipdip_dac_gpio_set(mute_gpio, MUTE_ACTIVE); + snd_rpi_chipdip_dac_gpio_array_set(bit_depth_state | sample_rate_state); + msleep(300); + snd_rpi_chipdip_dac_gpio_set(mute_gpio, MUTE_NOT_ACTIVE); + } + + return ret; +} + +static int snd_rpi_chipdip_dac_startup(struct snd_pcm_substream *substream) +{ + return 0; +} + +static void snd_rpi_chipdip_dac_shutdown(struct snd_pcm_substream *substream) +{ + +} + +/* machine stream operations */ +static struct snd_soc_ops snd_rpi_chipdip_dac_ops = { + .hw_params = snd_rpi_chipdip_dac_hw_params, + .startup = snd_rpi_chipdip_dac_startup, + .shutdown = snd_rpi_chipdip_dac_shutdown, +}; + +SND_SOC_DAILINK_DEFS(hifi, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("spdif-transmitter", "dit-hifi")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0"))); + +static struct snd_soc_dai_link snd_rpi_chipdip_dac_dai[] = { +{ + .name = "ChipDip DAC", + .stream_name = "ChipDip DAC HiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM, + .ops = &snd_rpi_chipdip_dac_ops, + .init = snd_rpi_chipdip_dac_init, + SND_SOC_DAILINK_REG(hifi), +}, +}; + +/* audio machine driver */ +static struct snd_soc_card snd_rpi_chipdip_dac = { + .name = "ChipDipDAC", + .driver_name = "ChipdipDac", + .owner = THIS_MODULE, + .dai_link = snd_rpi_chipdip_dac_dai, + .num_links = ARRAY_SIZE(snd_rpi_chipdip_dac_dai), +}; + +static int snd_rpi_chipdip_dac_probe(struct platform_device *pdev) +{ + int ret = 0; + int i = 0; + + snd_rpi_chipdip_dac.dev = &pdev->dev; + + if (pdev->dev.of_node) { + struct device_node *i2s_node; + struct snd_soc_dai_link *dai = &snd_rpi_chipdip_dac_dai[0]; + i2s_node = of_parse_phandle(pdev->dev.of_node, + "i2s-controller", 0); + + if (i2s_node) { + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + } + } + + hw_params_gpios[SR_BIT_0] = devm_gpiod_get_optional(&pdev->dev, "sr0", GPIOD_OUT_LOW); + hw_params_gpios[SR_BIT_1] = devm_gpiod_get_optional(&pdev->dev, "sr1", GPIOD_OUT_LOW); + hw_params_gpios[SR_BIT_2] = devm_gpiod_get_optional(&pdev->dev, "sr2", GPIOD_OUT_LOW); + hw_params_gpios[BD_BIT_0] = devm_gpiod_get_optional(&pdev->dev, "res0", GPIOD_OUT_LOW); + hw_params_gpios[BD_BIT_1] = devm_gpiod_get_optional(&pdev->dev, "res1", GPIOD_OUT_LOW); + mute_gpio = devm_gpiod_get_optional(&pdev->dev, "mute", GPIOD_OUT_LOW); + sdwn_gpio = devm_gpiod_get_optional(&pdev->dev, "sdwn", GPIOD_OUT_HIGH); + + for (i = 0; i < HW_PARAMS_GPIO_COUNT; i++) { + if (IS_ERR(hw_params_gpios[i])) { + ret = PTR_ERR(hw_params_gpios[i]); + dev_err(&pdev->dev, "failed to get hw_params gpio: %d\n", ret); + return ret; + } + } + + if (IS_ERR(mute_gpio)) { + ret = PTR_ERR(mute_gpio); + dev_err(&pdev->dev, "failed to get mute gpio: %d\n", ret); + return ret; + } + + if (IS_ERR(sdwn_gpio)) { + ret = PTR_ERR(sdwn_gpio); + dev_err(&pdev->dev, "failed to get sdwn gpio: %d\n", ret); + return ret; + } + + snd_rpi_chipdip_dac_gpio_set(sdwn_gpio, 1); + + ret = devm_snd_soc_register_card(&pdev->dev, &snd_rpi_chipdip_dac); + if (ret && ret != -EPROBE_DEFER) + dev_err(&pdev->dev, + "snd_soc_register_card() failed: %d\n", ret); + + return ret; +} + +static const struct of_device_id snd_rpi_chipdip_dac_of_match[] = { + { .compatible = "chipdip,chipdip-dac", }, + {}, +}; +MODULE_DEVICE_TABLE(of, snd_rpi_chipdip_dac_of_match); + +static struct platform_driver snd_rpi_chipdip_dac_driver = { + .driver = { + .name = "snd-rpi-chipdip-dac", + .owner = THIS_MODULE, + .of_match_table = snd_rpi_chipdip_dac_of_match, + }, + .probe = snd_rpi_chipdip_dac_probe, +}; + +module_platform_driver(snd_rpi_chipdip_dac_driver); + +MODULE_AUTHOR("Evgenij Sapunov <evgenij.sapunov@chipdip.ru>"); +MODULE_DESCRIPTION("ASoC Driver for ChipDip DAC"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/dacberry400.c b/sound/soc/bcm/dacberry400.c new file mode 100644 index 00000000000000..4a2520d077d2b7 --- /dev/null +++ b/sound/soc/bcm/dacberry400.c @@ -0,0 +1,258 @@ +/* + * ASoC Driver for Dacberry400 soundcard + * Author: + * Ashish Vara<ashishhvara@gmail.com> + * Copyright 2022 + * + * 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 <linux/module.h> +#include <linux/gpio/consumer.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include <linux/i2c.h> +#include <linux/acpi.h> +#include <linux/slab.h> +#include "../sound/soc/codecs/tlv320aic3x.h" + +static const struct snd_kcontrol_new dacberry400_controls[] = { + SOC_DAPM_PIN_SWITCH("MIC Jack"), + SOC_DAPM_PIN_SWITCH("Line In"), + SOC_DAPM_PIN_SWITCH("Line Out"), + SOC_DAPM_PIN_SWITCH("Headphone Jack"), +}; + +static const struct snd_soc_dapm_widget dacberry400_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("MIC Jack", NULL), + SND_SOC_DAPM_LINE("Line In", NULL), + SND_SOC_DAPM_LINE("Line Out", NULL), +}; + +static const struct snd_soc_dapm_route dacberry400_audio_map[] = { + {"Headphone Jack", NULL, "HPLOUT"}, + {"Headphone Jack", NULL, "HPROUT"}, + + {"LINE1L", NULL, "Line In"}, + {"LINE1R", NULL, "Line In"}, + + {"Line Out", NULL, "LLOUT"}, + {"Line Out", NULL, "RLOUT"}, + + {"MIC3L", NULL, "MIC Jack"}, + {"MIC3R", NULL, "MIC Jack"}, +}; + +static int snd_rpi_dacberry400_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + struct snd_soc_component *component = codec_dai->component; + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, 2, 12000000, + SND_SOC_CLOCK_OUT); + + if (ret && ret != -ENOTSUPP) + goto err; + + snd_soc_component_write(component, HPRCOM_CFG, 0x20); + snd_soc_component_write(component, DACL1_2_HPLOUT_VOL, 0x80); + snd_soc_component_write(component, DACR1_2_HPROUT_VOL, 0x80); +err: + return ret; +} + +static int snd_rpi_dacberry400_set_bias_level(struct snd_soc_card *card, + struct snd_soc_dapm_context *dapm, enum snd_soc_bias_level level) +{ + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_dai *codec_dai; + struct snd_soc_component *component; + struct dacberry_priv *aic3x; + u8 hpcom_reg = 0; + + rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]); + codec_dai = snd_soc_rtd_to_codec(rtd, 0); + component = codec_dai->component; + aic3x = snd_soc_component_get_drvdata(component); + if (dapm->dev != codec_dai->dev) + return 0; + + switch (level) { + case SND_SOC_BIAS_PREPARE: + if (dapm->bias_level != SND_SOC_BIAS_STANDBY) + break; + /* UNMUTE ADC/DAC */ + hpcom_reg = snd_soc_component_read(component, HPLCOM_CFG); + snd_soc_component_write(component, HPLCOM_CFG, hpcom_reg | 0x20); + snd_soc_component_write(component, LINE1R_2_RADC_CTRL, 0x04); + snd_soc_component_write(component, LINE1L_2_LADC_CTRL, 0x04); + snd_soc_component_write(component, LADC_VOL, 0x00); + snd_soc_component_write(component, RADC_VOL, 0x00); + pr_info("%s: unmute ADC/DAC\n", __func__); + break; + + case SND_SOC_BIAS_STANDBY: + if (dapm->bias_level != SND_SOC_BIAS_PREPARE) + break; + /* MUTE ADC/DAC */ + snd_soc_component_write(component, LDAC_VOL, 0x80); + snd_soc_component_write(component, RDAC_VOL, 0x80); + snd_soc_component_write(component, LADC_VOL, 0x80); + snd_soc_component_write(component, RADC_VOL, 0x80); + snd_soc_component_write(component, LINE1R_2_RADC_CTRL, 0x00); + snd_soc_component_write(component, LINE1L_2_LADC_CTRL, 0x00); + snd_soc_component_write(component, HPLCOM_CFG, 0x00); + pr_info("%s: mute ADC/DAC\n", __func__); + break; + default: + break; + } + + return 0; +} + +static int snd_rpi_dacberry400_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + int ret = 0; + u8 data; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + struct snd_soc_component *component = codec_dai->component; + int fsref = (params_rate(params) % 11025 == 0) ? 44100 : 48000; + int channels = params_channels(params); + int width = 32; + u8 clock = 0; + + data = (LDAC2LCH | RDAC2RCH); + data |= (fsref == 44100) ? FSREF_44100 : FSREF_48000; + if (params_rate(params) >= 64000) + data |= DUAL_RATE_MODE; + ret = snd_soc_component_write(component, 0x7, data); + width = params_width(params); + + clock = snd_soc_component_read(component, 2); + + ret = snd_soc_dai_set_bclk_ratio(cpu_dai, channels*width); + + return ret; +} + +static const struct snd_soc_ops snd_rpi_dacberry400_ops = { + .hw_params = snd_rpi_dacberry400_hw_params, +}; + + +SND_SOC_DAILINK_DEFS(rpi_dacberry400, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2835-i2s.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("tlv320aic3x.1-0018", "tlv320aic3x-hifi")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2835-i2s.0"))); + +static struct snd_soc_dai_link snd_rpi_dacberry400_dai[] = { +{ + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .init = snd_rpi_dacberry400_init, + .ops = &snd_rpi_dacberry400_ops, + .symmetric_rate = 1, + SND_SOC_DAILINK_REG(rpi_dacberry400), +}, +}; + +static struct snd_soc_card snd_rpi_dacberry400 = { + .owner = THIS_MODULE, + .dai_link = snd_rpi_dacberry400_dai, + .num_links = ARRAY_SIZE(snd_rpi_dacberry400_dai), + .controls = dacberry400_controls, + .num_controls = ARRAY_SIZE(dacberry400_controls), + .dapm_widgets = dacberry400_widgets, + .num_dapm_widgets = ARRAY_SIZE(dacberry400_widgets), + .dapm_routes = dacberry400_audio_map, + .num_dapm_routes = ARRAY_SIZE(dacberry400_audio_map), + .set_bias_level = snd_rpi_dacberry400_set_bias_level, +}; + +static int snd_rpi_dacberry400_probe(struct platform_device *pdev) +{ + int ret = 0; + + snd_rpi_dacberry400.dev = &pdev->dev; + + if (pdev->dev.of_node) { + struct device_node *i2s_node; + struct snd_soc_card *card = &snd_rpi_dacberry400; + struct snd_soc_dai_link *dai = &snd_rpi_dacberry400_dai[0]; + + i2s_node = of_parse_phandle(pdev->dev.of_node, + "i2s-controller", 0); + if (i2s_node) { + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + of_node_put(i2s_node); + } + + if (of_property_read_string(pdev->dev.of_node, "card_name", + &card->name)) + card->name = "tlvaudioCODEC"; + + if (of_property_read_string(pdev->dev.of_node, "dai_name", + &dai->name)) + dai->name = "tlvaudio CODEC"; + + } + + ret = snd_soc_register_card(&snd_rpi_dacberry400); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(&pdev->dev, + "snd_soc_register_card() failed: %d\n", ret); + return ret; + } + + return 0; +} + +static void snd_rpi_dacberry400_remove(struct platform_device *pdev) +{ + snd_soc_unregister_card(&snd_rpi_dacberry400); +} + +static const struct of_device_id dacberry400_match_id[] = { + { .compatible = "osaelectronics,dacberry400",}, + {}, +}; +MODULE_DEVICE_TABLE(of, dacberry400_match_id); + +static struct platform_driver snd_rpi_dacberry400_driver = { + .driver = { + .name = "snd-rpi-dacberry400", + .owner = THIS_MODULE, + .of_match_table = dacberry400_match_id, + }, + .probe = snd_rpi_dacberry400_probe, + .remove = snd_rpi_dacberry400_remove, +}; + +module_platform_driver(snd_rpi_dacberry400_driver); + +MODULE_AUTHOR("Ashish Vara"); +MODULE_DESCRIPTION("Dacberry400 sound card driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:dacberry400"); +MODULE_SOFTDEP("pre: snd-soc-tlv320aic3x"); diff --git a/sound/soc/bcm/digidac1-soundcard.c b/sound/soc/bcm/digidac1-soundcard.c new file mode 100644 index 00000000000000..e1e7054534c8a4 --- /dev/null +++ b/sound/soc/bcm/digidac1-soundcard.c @@ -0,0 +1,421 @@ +/* + * ASoC Driver for RRA DigiDAC1 + * Copyright 2016 + * Author: José M. Tasende <vintage@redrocksaudio.es> + * based on the HifiBerry DAC driver by Florian Meier <florian.meier@koalo.de> + * and the Wolfson card driver by Nikesh Oswal, <Nikesh.Oswal@wolfsonmicro.com> + * 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 <linux/module.h> +#include <linux/platform_device.h> +#include <linux/i2c.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include <sound/soc-dapm.h> +#include <sound/tlv.h> +#include <linux/regulator/consumer.h> + +#include "../codecs/wm8804.h" +#include "../codecs/wm8741.h" + +#define WM8741_NUM_SUPPLIES 2 + +/* codec private data */ +struct wm8741_priv { + struct wm8741_platform_data pdata; + struct regmap *regmap; + struct regulator_bulk_data supplies[WM8741_NUM_SUPPLIES]; + unsigned int sysclk; + const struct snd_pcm_hw_constraint_list *sysclk_constraints; +}; + +static int samplerate = 44100; + +/* New Alsa Controls not exposed by original wm8741 codec driver */ +/* in actual driver the att. adjustment is wrong because */ +/* this DAC has a coarse attenuation register with 4dB steps */ +/* and a fine level register with 0.125dB steps */ +/* each register has 32 steps so combining both we have 1024 steps */ +/* of 0.125 dB. */ +/* The original level controls from driver are removed at startup */ +/* and replaced by the corrected ones. */ +/* The same wm8741 driver can be used for wm8741 and wm8742 devices */ + +static const DECLARE_TLV_DB_SCALE(dac_tlv_fine, 0, 13, 0); +static const DECLARE_TLV_DB_SCALE(dac_tlv_coarse, -12700, 400, 1); +static const char *w8741_dither[4] = {"Off", "RPDF", "TPDF", "HPDF"}; +static const char *w8741_filter[5] = { + "Type 1", "Type 2", "Type 3", "Type 4", "Type 5"}; +static const char *w8741_switch[2] = {"Off", "On"}; +static const struct soc_enum w8741_enum[] = { +SOC_ENUM_SINGLE(WM8741_MODE_CONTROL_2, 0, 4, w8741_dither),/* dithering type */ +SOC_ENUM_SINGLE(WM8741_FILTER_CONTROL, 0, 5, w8741_filter),/* filter type */ +SOC_ENUM_SINGLE(WM8741_FORMAT_CONTROL, 6, 2, w8741_switch),/* phase invert */ +SOC_ENUM_SINGLE(WM8741_VOLUME_CONTROL, 0, 2, w8741_switch),/* volume ramp */ +SOC_ENUM_SINGLE(WM8741_VOLUME_CONTROL, 3, 2, w8741_switch),/* soft mute */ +}; + +static const struct snd_kcontrol_new w8741_snd_controls_stereo[] = { +SOC_DOUBLE_R_TLV("DAC Fine Playback Volume", WM8741_DACLLSB_ATTENUATION, + WM8741_DACRLSB_ATTENUATION, 0, 31, 1, dac_tlv_fine), +SOC_DOUBLE_R_TLV("Digital Playback Volume", WM8741_DACLMSB_ATTENUATION, + WM8741_DACRMSB_ATTENUATION, 0, 31, 1, dac_tlv_coarse), +SOC_ENUM("DAC Dither", w8741_enum[0]), +SOC_ENUM("DAC Digital Filter", w8741_enum[1]), +SOC_ENUM("DAC Phase Invert", w8741_enum[2]), +SOC_ENUM("DAC Volume Ramp", w8741_enum[3]), +SOC_ENUM("DAC Soft Mute", w8741_enum[4]), +}; + +static const struct snd_kcontrol_new w8741_snd_controls_mono_left[] = { +SOC_SINGLE_TLV("DAC Fine Playback Volume", WM8741_DACLLSB_ATTENUATION, + 0, 31, 0, dac_tlv_fine), +SOC_SINGLE_TLV("Digital Playback Volume", WM8741_DACLMSB_ATTENUATION, + 0, 31, 1, dac_tlv_coarse), +SOC_ENUM("DAC Dither", w8741_enum[0]), +SOC_ENUM("DAC Digital Filter", w8741_enum[1]), +SOC_ENUM("DAC Phase Invert", w8741_enum[2]), +SOC_ENUM("DAC Volume Ramp", w8741_enum[3]), +SOC_ENUM("DAC Soft Mute", w8741_enum[4]), +}; + +static const struct snd_kcontrol_new w8741_snd_controls_mono_right[] = { +SOC_SINGLE_TLV("DAC Fine Playback Volume", WM8741_DACRLSB_ATTENUATION, + 0, 31, 0, dac_tlv_fine), +SOC_SINGLE_TLV("Digital Playback Volume", WM8741_DACRMSB_ATTENUATION, + 0, 31, 1, dac_tlv_coarse), +SOC_ENUM("DAC Dither", w8741_enum[0]), +SOC_ENUM("DAC Digital Filter", w8741_enum[1]), +SOC_ENUM("DAC Phase Invert", w8741_enum[2]), +SOC_ENUM("DAC Volume Ramp", w8741_enum[3]), +SOC_ENUM("DAC Soft Mute", w8741_enum[4]), +}; + +static int w8741_add_controls(struct snd_soc_component *component) +{ + struct wm8741_priv *wm8741 = snd_soc_component_get_drvdata(component); + + switch (wm8741->pdata.diff_mode) { + case WM8741_DIFF_MODE_STEREO: + case WM8741_DIFF_MODE_STEREO_REVERSED: + snd_soc_add_component_controls(component, + w8741_snd_controls_stereo, + ARRAY_SIZE(w8741_snd_controls_stereo)); + break; + case WM8741_DIFF_MODE_MONO_LEFT: + snd_soc_add_component_controls(component, + w8741_snd_controls_mono_left, + ARRAY_SIZE(w8741_snd_controls_mono_left)); + break; + case WM8741_DIFF_MODE_MONO_RIGHT: + snd_soc_add_component_controls(component, + w8741_snd_controls_mono_right, + ARRAY_SIZE(w8741_snd_controls_mono_right)); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int digidac1_soundcard_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + struct snd_soc_card *card = rtd->card; + struct snd_soc_pcm_runtime *wm8741_rtd; + struct snd_soc_component *wm8741_component; + struct snd_card *sound_card = card->snd_card; + struct snd_kcontrol *kctl; + int ret; + + wm8741_rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[1]); + if (!wm8741_rtd) { + dev_warn(card->dev, "digidac1_soundcard_init: couldn't get wm8741 rtd\n"); + return -EFAULT; + } + wm8741_component = snd_soc_rtd_to_codec(wm8741_rtd, 0)->component; + ret = w8741_add_controls(wm8741_component); + if (ret < 0) + dev_warn(card->dev, "Failed to add new wm8741 controls: %d\n", + ret); + + /* enable TX output */ + snd_soc_component_update_bits(component, WM8804_PWRDN, 0x4, 0x0); + + kctl = snd_soc_card_get_kcontrol(card, + "Playback Volume"); + if (kctl) { + kctl->vd[0].access = SNDRV_CTL_ELEM_ACCESS_READWRITE; + snd_ctl_remove(sound_card, kctl); + } + kctl = snd_soc_card_get_kcontrol(card, + "Fine Playback Volume"); + if (kctl) { + kctl->vd[0].access = SNDRV_CTL_ELEM_ACCESS_READWRITE; + snd_ctl_remove(sound_card, kctl); + } + return 0; +} + +static int digidac1_soundcard_startup(struct snd_pcm_substream *substream) +{ + /* turn on wm8804 digital output */ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + struct snd_soc_card *card = rtd->card; + struct snd_soc_pcm_runtime *wm8741_rtd; + struct snd_soc_component *wm8741_component; + + snd_soc_component_update_bits(component, WM8804_PWRDN, 0x3c, 0x00); + wm8741_rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[1]); + if (!wm8741_rtd) { + dev_warn(card->dev, "digidac1_soundcard_startup: couldn't get WM8741 rtd\n"); + return -EFAULT; + } + wm8741_component = snd_soc_rtd_to_codec(wm8741_rtd, 0)->component; + + /* latch wm8741 level */ + snd_soc_component_update_bits(wm8741_component, WM8741_DACLLSB_ATTENUATION, + WM8741_UPDATELL, WM8741_UPDATELL); + snd_soc_component_update_bits(wm8741_component, WM8741_DACLMSB_ATTENUATION, + WM8741_UPDATELM, WM8741_UPDATELM); + snd_soc_component_update_bits(wm8741_component, WM8741_DACRLSB_ATTENUATION, + WM8741_UPDATERL, WM8741_UPDATERL); + snd_soc_component_update_bits(wm8741_component, WM8741_DACRMSB_ATTENUATION, + WM8741_UPDATERM, WM8741_UPDATERM); + + return 0; +} + +static void digidac1_soundcard_shutdown(struct snd_pcm_substream *substream) +{ + /* turn off wm8804 digital output */ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + + snd_soc_component_update_bits(component, WM8804_PWRDN, 0x3c, 0x3c); +} + +static int digidac1_soundcard_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + struct snd_soc_card *card = rtd->card; + struct snd_soc_pcm_runtime *wm8741_rtd; + struct snd_soc_component *wm8741_component; + + int sysclk = 27000000; + long mclk_freq = 0; + int mclk_div = 1; + int sampling_freq = 1; + int ret; + + wm8741_rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[1]); + if (!wm8741_rtd) { + dev_warn(card->dev, "digidac1_soundcard_hw_params: couldn't get WM8741 rtd\n"); + return -EFAULT; + } + wm8741_component = snd_soc_rtd_to_codec(wm8741_rtd, 0)->component; + samplerate = params_rate(params); + + if (samplerate <= 96000) { + mclk_freq = samplerate*256; + mclk_div = WM8804_MCLKDIV_256FS; + } else { + mclk_freq = samplerate*128; + mclk_div = WM8804_MCLKDIV_128FS; + } + + switch (samplerate) { + case 32000: + sampling_freq = 0x03; + break; + case 44100: + sampling_freq = 0x00; + break; + case 48000: + sampling_freq = 0x02; + break; + case 88200: + sampling_freq = 0x08; + break; + case 96000: + sampling_freq = 0x0a; + break; + case 176400: + sampling_freq = 0x0c; + break; + case 192000: + sampling_freq = 0x0e; + break; + default: + dev_err(card->dev, + "Failed to set WM8804 SYSCLK, unsupported samplerate %d\n", + samplerate); + } + + snd_soc_dai_set_clkdiv(codec_dai, WM8804_MCLK_DIV, mclk_div); + snd_soc_dai_set_pll(codec_dai, 0, 0, sysclk, mclk_freq); + + ret = snd_soc_dai_set_sysclk(codec_dai, WM8804_TX_CLKSRC_PLL, + sysclk, SND_SOC_CLOCK_OUT); + if (ret < 0) { + dev_err(card->dev, + "Failed to set WM8804 SYSCLK: %d\n", ret); + return ret; + } + /* Enable wm8804 TX output */ + snd_soc_component_update_bits(component, WM8804_PWRDN, 0x4, 0x0); + + /* wm8804 Power on */ + snd_soc_component_update_bits(component, WM8804_PWRDN, 0x9, 0); + + /* wm8804 set sampling frequency status bits */ + snd_soc_component_update_bits(component, WM8804_SPDTX4, 0x0f, sampling_freq); + + /* Now update wm8741 registers for the correct oversampling */ + if (samplerate <= 48000) + snd_soc_component_update_bits(wm8741_component, WM8741_MODE_CONTROL_1, + WM8741_OSR_MASK, 0x00); + else if (samplerate <= 96000) + snd_soc_component_update_bits(wm8741_component, WM8741_MODE_CONTROL_1, + WM8741_OSR_MASK, 0x20); + else + snd_soc_component_update_bits(wm8741_component, WM8741_MODE_CONTROL_1, + WM8741_OSR_MASK, 0x40); + + /* wm8741 bit size */ + switch (params_width(params)) { + case 16: + snd_soc_component_update_bits(wm8741_component, WM8741_FORMAT_CONTROL, + WM8741_IWL_MASK, 0x00); + break; + case 20: + snd_soc_component_update_bits(wm8741_component, WM8741_FORMAT_CONTROL, + WM8741_IWL_MASK, 0x01); + break; + case 24: + snd_soc_component_update_bits(wm8741_component, WM8741_FORMAT_CONTROL, + WM8741_IWL_MASK, 0x02); + break; + case 32: + snd_soc_component_update_bits(wm8741_component, WM8741_FORMAT_CONTROL, + WM8741_IWL_MASK, 0x03); + break; + default: + dev_dbg(card->dev, "wm8741_hw_params: Unsupported bit size param = %d", + params_width(params)); + return -EINVAL; + } + + return snd_soc_dai_set_bclk_ratio(cpu_dai, 64); +} +/* machine stream operations */ +static struct snd_soc_ops digidac1_soundcard_ops = { + .hw_params = digidac1_soundcard_hw_params, + .startup = digidac1_soundcard_startup, + .shutdown = digidac1_soundcard_shutdown, +}; + +SND_SOC_DAILINK_DEFS(digidac1, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("wm8804.1-003b", "wm8804-spdif")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2835-i2s.0"))); + +SND_SOC_DAILINK_DEFS(digidac11, + DAILINK_COMP_ARRAY(COMP_CPU("wm8804-spdif")), + DAILINK_COMP_ARRAY(COMP_CODEC("wm8741.1-001a", "wm8741")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link digidac1_soundcard_dai[] = { + { + .name = "RRA DigiDAC1", + .stream_name = "RRA DigiDAC1 HiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM, + .ops = &digidac1_soundcard_ops, + .init = digidac1_soundcard_init, + SND_SOC_DAILINK_REG(digidac1), + }, + { + .name = "RRA DigiDAC11", + .stream_name = "RRA DigiDAC11 HiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAILINK_REG(digidac11), + }, +}; + +/* audio machine driver */ +static struct snd_soc_card digidac1_soundcard = { + .name = "digidac1-soundcard", + .owner = THIS_MODULE, + .dai_link = digidac1_soundcard_dai, + .num_links = ARRAY_SIZE(digidac1_soundcard_dai), +}; + +static int digidac1_soundcard_probe(struct platform_device *pdev) +{ + int ret = 0; + + digidac1_soundcard.dev = &pdev->dev; + + if (pdev->dev.of_node) { + struct device_node *i2s_node; + struct snd_soc_dai_link *dai = &digidac1_soundcard_dai[0]; + + i2s_node = of_parse_phandle(pdev->dev.of_node, + "i2s-controller", 0); + + if (i2s_node) { + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + } + } + + ret = devm_snd_soc_register_card(&pdev->dev, &digidac1_soundcard); + if (ret && ret != -EPROBE_DEFER) + dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", + ret); + + return ret; +} + +static const struct of_device_id digidac1_soundcard_of_match[] = { + { .compatible = "rra,digidac1-soundcard", }, + {}, +}; +MODULE_DEVICE_TABLE(of, digidac1_soundcard_of_match); + +static struct platform_driver digidac1_soundcard_driver = { + .driver = { + .name = "digidac1-audio", + .owner = THIS_MODULE, + .of_match_table = digidac1_soundcard_of_match, + }, + .probe = digidac1_soundcard_probe, +}; + +module_platform_driver(digidac1_soundcard_driver); + +MODULE_AUTHOR("José M. Tasende <vintage@redrocksaudio.es>"); +MODULE_DESCRIPTION("ASoC Driver for RRA DigiDAC1"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/dionaudio_loco-v2.c b/sound/soc/bcm/dionaudio_loco-v2.c new file mode 100644 index 00000000000000..49aeaf121a3fe2 --- /dev/null +++ b/sound/soc/bcm/dionaudio_loco-v2.c @@ -0,0 +1,118 @@ +/* + * ASoC Driver for Dion Audio LOCO-V2 DAC-AMP + * + * Author: Miquel Blauw <info@dionaudio.nl> + * Copyright 2017 + * + * Based on the software of the RPi-DAC writen by Florian Meier + * + * 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 <linux/module.h> +#include <linux/platform_device.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/jack.h> + +static bool digital_gain_0db_limit = true; + +static int snd_rpi_dionaudio_loco_v2_init(struct snd_soc_pcm_runtime *rtd) +{ + if (digital_gain_0db_limit) { + int ret; + struct snd_soc_card *card = rtd->card; + + ret = snd_soc_limit_volume(card, "Digital Playback Volume", 207); + if (ret < 0) + dev_warn(card->dev, "Failed to set volume limit: %d\n", ret); + } + + return 0; +} + +SND_SOC_DAILINK_DEFS(dionaudio_loco_v2, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004d", "pcm512x-hifi")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0"))); + +static struct snd_soc_dai_link snd_rpi_dionaudio_loco_v2_dai[] = { +{ + .name = "DionAudio LOCO-V2", + .stream_name = "DionAudio LOCO-V2 DAC-AMP", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .init = snd_rpi_dionaudio_loco_v2_init, + SND_SOC_DAILINK_REG(dionaudio_loco_v2), +},}; + +/* audio machine driver */ +static struct snd_soc_card snd_rpi_dionaudio_loco_v2 = { + .name = "Dion Audio LOCO-V2", + .owner = THIS_MODULE, + .dai_link = snd_rpi_dionaudio_loco_v2_dai, + .num_links = ARRAY_SIZE(snd_rpi_dionaudio_loco_v2_dai), +}; + +static int snd_rpi_dionaudio_loco_v2_probe(struct platform_device *pdev) +{ + int ret = 0; + + snd_rpi_dionaudio_loco_v2.dev = &pdev->dev; + + if (pdev->dev.of_node) { + struct device_node *i2s_node; + struct snd_soc_dai_link *dai = + &snd_rpi_dionaudio_loco_v2_dai[0]; + + i2s_node = of_parse_phandle(pdev->dev.of_node, + "i2s-controller", 0); + if (i2s_node) { + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + } + + digital_gain_0db_limit = !of_property_read_bool( + pdev->dev.of_node, "dionaudio,24db_digital_gain"); + } + + ret = devm_snd_soc_register_card(&pdev->dev, &snd_rpi_dionaudio_loco_v2); + if (ret) + dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", + ret); + + return ret; +} + +static const struct of_device_id dionaudio_of_match[] = { + { .compatible = "dionaudio,dionaudio-loco-v2", }, + {}, +}; +MODULE_DEVICE_TABLE(of, dionaudio_of_match); + +static struct platform_driver snd_rpi_dionaudio_loco_v2_driver = { + .driver = { + .name = "snd-rpi-dionaudio-loco-v2", + .owner = THIS_MODULE, + .of_match_table = dionaudio_of_match, + }, + .probe = snd_rpi_dionaudio_loco_v2_probe, +}; + +module_platform_driver(snd_rpi_dionaudio_loco_v2_driver); + +MODULE_AUTHOR("Miquel Blauw <info@dionaudio.nl>"); +MODULE_DESCRIPTION("ASoC Driver for DionAudio LOCO-V2"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/dionaudio_loco.c b/sound/soc/bcm/dionaudio_loco.c new file mode 100644 index 00000000000000..5d5b677bf11cba --- /dev/null +++ b/sound/soc/bcm/dionaudio_loco.c @@ -0,0 +1,121 @@ +/* + * ASoC Driver for Dion Audio LOCO DAC-AMP + * + * Author: Miquel Blauw <info@dionaudio.nl> + * Copyright 2016 + * + * Based on the software of the RPi-DAC writen by Florian Meier + * + * 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 <linux/module.h> +#include <linux/platform_device.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/jack.h> + +static int snd_rpi_dionaudio_loco_hw_params( + struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + + unsigned int sample_bits = + snd_pcm_format_width(params_format(params)); + + /* Using powers of 2 allows for an integer clock divisor */ + sample_bits = sample_bits <= 16 ? 16 : 32; + + return snd_soc_dai_set_bclk_ratio(cpu_dai, sample_bits * 2); +} + +/* machine stream operations */ +static struct snd_soc_ops snd_rpi_dionaudio_loco_ops = { + .hw_params = snd_rpi_dionaudio_loco_hw_params, +}; + +SND_SOC_DAILINK_DEFS(dionaudio_loco, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("pcm5102a-codec", "pcm5102a-hifi")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0"))); + +static struct snd_soc_dai_link snd_rpi_dionaudio_loco_dai[] = { +{ + .name = "DionAudio LOCO", + .stream_name = "DionAudio LOCO DAC-AMP", + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &snd_rpi_dionaudio_loco_ops, + SND_SOC_DAILINK_REG(dionaudio_loco), +}, +}; + +/* audio machine driver */ +static struct snd_soc_card snd_rpi_dionaudio_loco = { + .name = "snd_rpi_dionaudio_loco", + .owner = THIS_MODULE, + .dai_link = snd_rpi_dionaudio_loco_dai, + .num_links = ARRAY_SIZE(snd_rpi_dionaudio_loco_dai), +}; + +static int snd_rpi_dionaudio_loco_probe(struct platform_device *pdev) +{ + struct device_node *np; + int ret = 0; + + snd_rpi_dionaudio_loco.dev = &pdev->dev; + + np = pdev->dev.of_node; + if (np) { + struct snd_soc_dai_link *dai = &snd_rpi_dionaudio_loco_dai[0]; + struct device_node *i2s_np; + + i2s_np = of_parse_phandle(np, "i2s-controller", 0); + if (i2s_np) { + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_np; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_np; + } + } + + ret = devm_snd_soc_register_card(&pdev->dev, &snd_rpi_dionaudio_loco); + if (ret && ret != -EPROBE_DEFER) + dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", + ret); + + return ret; +} + +static const struct of_device_id snd_rpi_dionaudio_loco_of_match[] = { + { .compatible = "dionaudio,loco-pcm5242-tpa3118", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, snd_rpi_dionaudio_loco_of_match); + +static struct platform_driver snd_rpi_dionaudio_loco_driver = { + .driver = { + .name = "snd-dionaudio-loco", + .owner = THIS_MODULE, + .of_match_table = snd_rpi_dionaudio_loco_of_match, + }, + .probe = snd_rpi_dionaudio_loco_probe, +}; + +module_platform_driver(snd_rpi_dionaudio_loco_driver); + +MODULE_AUTHOR("Miquel Blauw <info@dionaudio.nl>"); +MODULE_DESCRIPTION("ASoC Driver for DionAudio LOCO"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/fe-pi-audio.c b/sound/soc/bcm/fe-pi-audio.c new file mode 100644 index 00000000000000..3263e2a9c30daa --- /dev/null +++ b/sound/soc/bcm/fe-pi-audio.c @@ -0,0 +1,154 @@ +/* + * ASoC Driver for Fe-Pi Audio Sound Card + * + * Author: Henry Kupis <kuupaz@gmail.com> + * Copyright 2016 + * based on code by Florian Meier <florian.meier@koalo.de> + * based on code by Shawn Guo <shawn.guo@linaro.org> + * + * 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 <linux/module.h> +#include <linux/platform_device.h> +#include <linux/io.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/jack.h> + +#include "../codecs/sgtl5000.h" + +static int snd_fe_pi_audio_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + + snd_soc_dapm_force_enable_pin(&card->dapm, "LO"); + snd_soc_dapm_force_enable_pin(&card->dapm, "ADC"); + snd_soc_dapm_force_enable_pin(&card->dapm, "DAC"); + snd_soc_dapm_force_enable_pin(&card->dapm, "HP"); + snd_soc_component_update_bits(component, SGTL5000_CHIP_ANA_POWER, + SGTL5000_VAG_POWERUP, SGTL5000_VAG_POWERUP); + + return 0; +} + +static int snd_fe_pi_audio_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct device *dev = rtd->card->dev; + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + + int ret; + + /* Set SGTL5000's SYSCLK */ + ret = snd_soc_dai_set_sysclk(codec_dai, SGTL5000_SYSCLK, 12288000, SND_SOC_CLOCK_IN); + if (ret) { + dev_err(dev, "could not set codec driver clock params\n"); + return ret; + } + + return 0; +} + + +static struct snd_soc_ops snd_fe_pi_audio_ops = { + .hw_params = snd_fe_pi_audio_hw_params, +}; + +SND_SOC_DAILINK_DEFS(fe_pi, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("sgtl5000.1-000a", "sgtl5000")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0"))); + +static struct snd_soc_dai_link snd_fe_pi_audio_dai[] = { + { + .name = "FE-PI", + .stream_name = "Fe-Pi HiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM, + .ops = &snd_fe_pi_audio_ops, + .init = snd_fe_pi_audio_init, + SND_SOC_DAILINK_REG(fe_pi), + }, +}; + +static const struct snd_soc_dapm_route fe_pi_audio_dapm_routes[] = { + {"ADC", NULL, "Mic Bias"}, +}; + + +static struct snd_soc_card fe_pi_audio = { + .name = "Fe-Pi Audio", + .owner = THIS_MODULE, + .dai_link = snd_fe_pi_audio_dai, + .num_links = ARRAY_SIZE(snd_fe_pi_audio_dai), + + .dapm_routes = fe_pi_audio_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(fe_pi_audio_dapm_routes), +}; + +static int snd_fe_pi_audio_probe(struct platform_device *pdev) +{ + int ret = 0; + struct snd_soc_card *card = &fe_pi_audio; + struct device_node *np = pdev->dev.of_node; + struct device_node *i2s_node; + struct snd_soc_dai_link *dai = &snd_fe_pi_audio_dai[0]; + + fe_pi_audio.dev = &pdev->dev; + + i2s_node = of_parse_phandle(np, "i2s-controller", 0); + if (!i2s_node) { + dev_err(&pdev->dev, "i2s_node phandle missing or invalid\n"); + return -EINVAL; + } + + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + + of_node_put(i2s_node); + + card->dev = &pdev->dev; + platform_set_drvdata(pdev, card); + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret && ret != -EPROBE_DEFER) + dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", ret); + + return ret; +} + +static const struct of_device_id snd_fe_pi_audio_of_match[] = { + { .compatible = "fe-pi,fe-pi-audio", }, + {}, +}; +MODULE_DEVICE_TABLE(of, snd_fe_pi_audio_of_match); + +static struct platform_driver snd_fe_pi_audio_driver = { + .driver = { + .name = "snd-fe-pi-audio", + .owner = THIS_MODULE, + .of_match_table = snd_fe_pi_audio_of_match, + }, + .probe = snd_fe_pi_audio_probe, +}; + +module_platform_driver(snd_fe_pi_audio_driver); + +MODULE_AUTHOR("Henry Kupis <fe-pi@cox.net>"); +MODULE_DESCRIPTION("ASoC Driver for Fe-Pi Audio"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/googlevoicehat-codec.c b/sound/soc/bcm/googlevoicehat-codec.c new file mode 100644 index 00000000000000..572db9373e63eb --- /dev/null +++ b/sound/soc/bcm/googlevoicehat-codec.c @@ -0,0 +1,212 @@ +/* + * Driver for the Google voiceHAT audio codec for Raspberry Pi. + * + * Author: Peter Malkin <petermalkin@google.com> + * Copyright 2016 + * + * 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 <linux/device.h> +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/gpio/consumer.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/version.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dai.h> +#include <sound/soc-dapm.h> + +#define ICS43432_RATE_MIN_HZ 7190 /* from data sheet */ +#define ICS43432_RATE_MAX_HZ 52800 /* from data sheet */ +/* Delay in enabling SDMODE after clock settles to remove pop */ +#define SDMODE_DELAY_MS 5 + +struct voicehat_priv { + struct delayed_work enable_sdmode_work; + struct gpio_desc *sdmode_gpio; + unsigned long sdmode_delay_jiffies; +}; + +static void voicehat_enable_sdmode_work(struct work_struct *work) +{ + struct voicehat_priv *voicehat = container_of(work, + struct voicehat_priv, + enable_sdmode_work.work); + gpiod_set_value(voicehat->sdmode_gpio, 1); +} + +static int voicehat_component_probe(struct snd_soc_component *component) +{ + struct voicehat_priv *voicehat = + snd_soc_component_get_drvdata(component); + + voicehat->sdmode_gpio = devm_gpiod_get(component->dev, "sdmode", + GPIOD_OUT_LOW); + if (IS_ERR(voicehat->sdmode_gpio)) { + dev_err(component->dev, "Unable to allocate GPIO pin\n"); + return PTR_ERR(voicehat->sdmode_gpio); + } + + INIT_DELAYED_WORK(&voicehat->enable_sdmode_work, + voicehat_enable_sdmode_work); + return 0; +} + +static void voicehat_component_remove(struct snd_soc_component *component) +{ + struct voicehat_priv *voicehat = + snd_soc_component_get_drvdata(component); + + cancel_delayed_work_sync(&voicehat->enable_sdmode_work); +} + +static const struct snd_soc_dapm_widget voicehat_dapm_widgets[] = { + SND_SOC_DAPM_OUTPUT("Speaker"), +}; + +static const struct snd_soc_dapm_route voicehat_dapm_routes[] = { + {"Speaker", NULL, "HiFi Playback"}, +}; + +static const struct snd_soc_component_driver voicehat_component_driver = { + .probe = voicehat_component_probe, + .remove = voicehat_component_remove, + .dapm_widgets = voicehat_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(voicehat_dapm_widgets), + .dapm_routes = voicehat_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(voicehat_dapm_routes), +}; + +static int voicehat_daiops_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct voicehat_priv *voicehat = snd_soc_component_get_drvdata(component); + + if (voicehat->sdmode_delay_jiffies == 0) + return 0; + + dev_dbg(dai->dev, "CMD %d", cmd); + dev_dbg(dai->dev, "Playback Active %d", dai->stream[SNDRV_PCM_STREAM_PLAYBACK].active); + dev_dbg(dai->dev, "Capture Active %d", dai->stream[SNDRV_PCM_STREAM_CAPTURE].active); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + dev_info(dai->dev, "Enabling audio amp...\n"); + queue_delayed_work( + system_power_efficient_wq, + &voicehat->enable_sdmode_work, + voicehat->sdmode_delay_jiffies); + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + cancel_delayed_work(&voicehat->enable_sdmode_work); + dev_info(dai->dev, "Disabling audio amp...\n"); + gpiod_set_value(voicehat->sdmode_gpio, 0); + } + break; + } + return 0; +} + +static const struct snd_soc_dai_ops voicehat_dai_ops = { + .trigger = voicehat_daiops_trigger, +}; + +static struct snd_soc_dai_driver voicehat_dai = { + .name = "voicehat-hifi", + .capture = { + .stream_name = "HiFi Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S32_LE + }, + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S32_LE + }, + .ops = &voicehat_dai_ops, + .symmetric_rate = 1 +}; + +#ifdef CONFIG_OF +static const struct of_device_id voicehat_ids[] = { + { .compatible = "google,voicehat", }, {} + }; + MODULE_DEVICE_TABLE(of, voicehat_ids); +#endif + +static int voicehat_platform_probe(struct platform_device *pdev) +{ + struct voicehat_priv *voicehat; + unsigned int sdmode_delay; + int ret; + + voicehat = devm_kzalloc(&pdev->dev, sizeof(*voicehat), GFP_KERNEL); + if (!voicehat) + return -ENOMEM; + + ret = device_property_read_u32(&pdev->dev, "voicehat_sdmode_delay", + &sdmode_delay); + + if (ret) { + sdmode_delay = SDMODE_DELAY_MS; + dev_info(&pdev->dev, + "property 'voicehat_sdmode_delay' not found default 5 mS"); + } else { + dev_info(&pdev->dev, "property 'voicehat_sdmode_delay' found delay= %d mS", + sdmode_delay); + } + voicehat->sdmode_delay_jiffies = msecs_to_jiffies(sdmode_delay); + + dev_set_drvdata(&pdev->dev, voicehat); + + return snd_soc_register_component(&pdev->dev, + &voicehat_component_driver, + &voicehat_dai, + 1); +} + +static void voicehat_platform_remove(struct platform_device *pdev) +{ + snd_soc_unregister_component(&pdev->dev); +} + +static struct platform_driver voicehat_driver = { + .driver = { + .name = "voicehat-codec", + .of_match_table = of_match_ptr(voicehat_ids), + }, + .probe = voicehat_platform_probe, + .remove = voicehat_platform_remove, +}; + +module_platform_driver(voicehat_driver); + +MODULE_DESCRIPTION("Google voiceHAT Codec driver"); +MODULE_AUTHOR("Peter Malkin <petermalkin@google.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/hifiberry_adc.c b/sound/soc/bcm/hifiberry_adc.c new file mode 100644 index 00000000000000..bcd7f84d7e9140 --- /dev/null +++ b/sound/soc/bcm/hifiberry_adc.c @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ASoC Driver for HiFiBerry ADC + * + * Author: Joerg Schambacher <joerg@hifiberry.com> + * Copyright 2024 + * + * 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 <linux/module.h> +#include <linux/platform_device.h> +#include <linux/kernel.h> +#include <linux/clk.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/i2c.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include <sound/tlv.h> + +#include "../codecs/pcm186x.h" +#include "hifiberry_adc_controls.h" + +static bool leds_off; + +static int pcm1863_add_controls(struct snd_soc_component *component) +{ + snd_soc_add_component_controls(component, + pcm1863_snd_controls_card, + ARRAY_SIZE(pcm1863_snd_controls_card)); + return 0; +} + +static int snd_rpi_hifiberry_adc_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + struct snd_soc_component *adc = codec_dai->component; + int ret; + + ret = pcm1863_add_controls(adc); + if (ret < 0) + dev_warn(rtd->dev, "Failed to add pcm1863 controls: %d\n", + ret); + + codec_dai->driver->capture.rates = + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000; + + /* set GPIO2 to output, GPIO3 input */ + snd_soc_component_write(adc, PCM186X_GPIO3_2_CTRL, 0x00); + snd_soc_component_write(adc, PCM186X_GPIO3_2_DIR_CTRL, 0x04); + if (leds_off) + snd_soc_component_update_bits(adc, PCM186X_GPIO_IN_OUT, 0x40, 0x00); + else + snd_soc_component_update_bits(adc, PCM186X_GPIO_IN_OUT, 0x40, 0x40); + + return 0; +} + +static int snd_rpi_hifiberry_adc_hw_params( + struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) +{ + int ret = 0; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + int channels = params_channels(params); + int width = snd_pcm_format_width(params_format(params)); + + /* Using powers of 2 allows for an integer clock divisor */ + width = width <= 16 ? 16 : 32; + + ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_cpu(rtd, 0), channels * width); + return ret; +} + +/* machine stream operations */ +static const struct snd_soc_ops snd_rpi_hifiberry_adc_ops = { + .hw_params = snd_rpi_hifiberry_adc_hw_params, +}; + +SND_SOC_DAILINK_DEFS(hifi, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("pcm186x.1-004a", "pcm1863-aif")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0"))); + +static struct snd_soc_dai_link snd_rpi_hifiberry_adc_dai[] = { +{ + .name = "HiFiBerry ADC", + .stream_name = "HiFiBerry ADC HiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &snd_rpi_hifiberry_adc_ops, + .init = snd_rpi_hifiberry_adc_init, + SND_SOC_DAILINK_REG(hifi), +}, +}; + +/* audio machine driver */ +static struct snd_soc_card snd_rpi_hifiberry_adc = { + .name = "snd_rpi_hifiberry_adc", + .driver_name = "HifiberryAdc", + .owner = THIS_MODULE, + .dai_link = snd_rpi_hifiberry_adc_dai, + .num_links = ARRAY_SIZE(snd_rpi_hifiberry_adc_dai), +}; + +static int snd_rpi_hifiberry_adc_probe(struct platform_device *pdev) +{ + int ret = 0, i = 0; + struct snd_soc_card *card = &snd_rpi_hifiberry_adc; + + snd_rpi_hifiberry_adc.dev = &pdev->dev; + if (pdev->dev.of_node) { + struct device_node *i2s_node; + struct snd_soc_dai_link *dai; + + dai = &snd_rpi_hifiberry_adc_dai[0]; + i2s_node = of_parse_phandle(pdev->dev.of_node, + "i2s-controller", 0); + if (i2s_node) { + for (i = 0; i < card->num_links; i++) { + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + } + } + } + leds_off = of_property_read_bool(pdev->dev.of_node, + "hifiberry-adc,leds_off"); + ret = snd_soc_register_card(&snd_rpi_hifiberry_adc); + if (ret && ret != -EPROBE_DEFER) + dev_err(&pdev->dev, + "snd_soc_register_card() failed: %d\n", ret); + + return ret; +} + +static const struct of_device_id snd_rpi_hifiberry_adc_of_match[] = { + { .compatible = "hifiberry,hifiberry-adc", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, snd_rpi_hifiberry_adc_of_match); + +static struct platform_driver snd_rpi_hifiberry_adc_driver = { + .driver = { + .name = "snd-rpi-hifiberry-adc", + .owner = THIS_MODULE, + .of_match_table = snd_rpi_hifiberry_adc_of_match, + }, + .probe = snd_rpi_hifiberry_adc_probe, +}; + +module_platform_driver(snd_rpi_hifiberry_adc_driver); + +MODULE_AUTHOR("Joerg Schambacher <joerg@hifiberry.com>"); +MODULE_DESCRIPTION("ASoC Driver for HiFiBerry ADC"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/bcm/hifiberry_adc_controls.h b/sound/soc/bcm/hifiberry_adc_controls.h new file mode 100644 index 00000000000000..8d8911bb0afba4 --- /dev/null +++ b/sound/soc/bcm/hifiberry_adc_controls.h @@ -0,0 +1,128 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * ALSA mixer/Kcontrol definitions common to HiFiBerry ADCs + * + * used by DAC+ADC Pro (hifiberry_dacplusadcpro.c), + * ADC (hifiberry_adc.c) + * + * Author: Joerg Schambacher <joerg@hifiberry.com> + * Copyright 2024 + * + * 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. + */ + +static const unsigned int pcm186x_adc_input_channel_sel_value[] = { + 0x00, 0x01, 0x02, 0x03, 0x10 +}; + +static const char * const pcm186x_adcl_input_channel_sel_text[] = { + "No Select", + "VINL1[SE]", /* Default for ADCL */ + "VINL2[SE]", + "VINL2[SE] + VINL1[SE]", + "{VIN1P, VIN1M}[DIFF]" +}; + +static const char * const pcm186x_adcr_input_channel_sel_text[] = { + "No Select", + "VINR1[SE]", /* Default for ADCR */ + "VINR2[SE]", + "VINR2[SE] + VINR1[SE]", + "{VIN2P, VIN2M}[DIFF]" +}; + +static const struct soc_enum pcm186x_adc_input_channel_sel[] = { + SOC_VALUE_ENUM_SINGLE(PCM186X_ADC1_INPUT_SEL_L, 0, + PCM186X_ADC_INPUT_SEL_MASK, + ARRAY_SIZE(pcm186x_adcl_input_channel_sel_text), + pcm186x_adcl_input_channel_sel_text, + pcm186x_adc_input_channel_sel_value), + SOC_VALUE_ENUM_SINGLE(PCM186X_ADC1_INPUT_SEL_R, 0, + PCM186X_ADC_INPUT_SEL_MASK, + ARRAY_SIZE(pcm186x_adcr_input_channel_sel_text), + pcm186x_adcr_input_channel_sel_text, + pcm186x_adc_input_channel_sel_value), +}; + +static const unsigned int pcm186x_mic_bias_sel_value[] = { + 0x00, 0x01, 0x11 +}; + +static const char * const pcm186x_mic_bias_sel_text[] = { + "Mic Bias off", + "Mic Bias on", + "Mic Bias with Bypass Resistor" +}; + +static const struct soc_enum pcm186x_mic_bias_sel[] = { + SOC_VALUE_ENUM_SINGLE(PCM186X_MIC_BIAS_CTRL, 0, + GENMASK(4, 0), + ARRAY_SIZE(pcm186x_mic_bias_sel_text), + pcm186x_mic_bias_sel_text, + pcm186x_mic_bias_sel_value), +}; + +static const unsigned int pcm186x_gain_sel_value[] = { + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50 +}; + +static const char * const pcm186x_gain_sel_text[] = { + "-12.0dB", "-11.5dB", "-11.0dB", "-10.5dB", "-10.0dB", "-9.5dB", + "-9.0dB", "-8.5dB", "-8.0dB", "-7.5dB", "-7.0dB", "-6.5dB", + "-6.0dB", "-5.5dB", "-5.0dB", "-4.5dB", "-4.0dB", "-3.5dB", + "-3.0dB", "-2.5dB", "-2.0dB", "-1.5dB", "-1.0dB", "-0.5dB", + "0.0dB", "0.5dB", "1.0dB", "1.5dB", "2.0dB", "2.5dB", + "3.0dB", "3.5dB", "4.0dB", "4.5dB", "5.0dB", "5.5dB", + "6.0dB", "6.5dB", "7.0dB", "7.5dB", "8.0dB", "8.5dB", + "9.0dB", "9.5dB", "10.0dB", "10.5dB", "11.0dB", "11.5dB", + "12.0dB", "12.5dB", "13.0dB", "13.5dB", "14.0dB", "14.5dB", + "15.0dB", "15.5dB", "16.0dB", "16.5dB", "17.0dB", "17.5dB", + "18.0dB", "18.5dB", "19.0dB", "19.5dB", "20.0dB", "20.5dB", + "21.0dB", "21.5dB", "22.0dB", "22.5dB", "23.0dB", "23.5dB", + "24.0dB", "24.5dB", "25.0dB", "25.5dB", "26.0dB", "26.5dB", + "27.0dB", "27.5dB", "28.0dB", "28.5dB", "29.0dB", "29.5dB", + "30.0dB", "30.5dB", "31.0dB", "31.5dB", "32.0dB", "32.5dB", + "33.0dB", "33.5dB", "34.0dB", "34.5dB", "35.0dB", "35.5dB", + "36.0dB", "36.5dB", "37.0dB", "37.5dB", "38.0dB", "38.5dB", + "39.0dB", "39.5dB", "40.0dB"}; + +static const struct soc_enum pcm186x_gain_sel[] = { + SOC_VALUE_ENUM_SINGLE(PCM186X_PGA_VAL_CH1_L, 0, + 0xff, + ARRAY_SIZE(pcm186x_gain_sel_text), + pcm186x_gain_sel_text, + pcm186x_gain_sel_value), + SOC_VALUE_ENUM_SINGLE(PCM186X_PGA_VAL_CH1_R, 0, + 0xff, + ARRAY_SIZE(pcm186x_gain_sel_text), + pcm186x_gain_sel_text, + pcm186x_gain_sel_value), +}; + +static const struct snd_kcontrol_new pcm1863_snd_controls_card[] = { + SOC_ENUM("ADC Left Input", pcm186x_adc_input_channel_sel[0]), + SOC_ENUM("ADC Right Input", pcm186x_adc_input_channel_sel[1]), + SOC_ENUM("ADC Mic Bias", pcm186x_mic_bias_sel), + SOC_ENUM("PGA Gain Left", pcm186x_gain_sel[0]), + SOC_ENUM("PGA Gain Right", pcm186x_gain_sel[1]), +}; diff --git a/sound/soc/bcm/hifiberry_dacplus.c b/sound/soc/bcm/hifiberry_dacplus.c new file mode 100644 index 00000000000000..a6dacdced79a74 --- /dev/null +++ b/sound/soc/bcm/hifiberry_dacplus.c @@ -0,0 +1,563 @@ +/* + * ASoC Driver for HiFiBerry DAC+ / DAC Pro / AMP100 + * + * Author: Daniel Matuschek, Stuart MacLean <stuart@hifiberry.com> + * Copyright 2014-2015 + * based on code by Florian Meier <florian.meier@koalo.de> + * Headphone/AMP100 Joerg Schambacher <joerg@hifiberry.com> + * + * 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 <linux/module.h> +#include <linux/gpio/consumer.h> +#include <../drivers/gpio/gpiolib.h> +#include <linux/platform_device.h> +#include <linux/kernel.h> +#include <linux/clk.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/i2c.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/jack.h> + +#include "../codecs/pcm512x.h" + +#define HIFIBERRY_DACPRO_NOCLOCK 0 +#define HIFIBERRY_DACPRO_CLK44EN 1 +#define HIFIBERRY_DACPRO_CLK48EN 2 + +struct pcm512x_priv { + struct regmap *regmap; + struct clk *sclk; +}; + +/* Clock rate of CLK44EN attached to GPIO6 pin */ +#define CLK_44EN_RATE 22579200UL +/* Clock rate of CLK48EN attached to GPIO3 pin */ +#define CLK_48EN_RATE 24576000UL + +static bool slave; +static bool snd_rpi_hifiberry_is_dacpro; +static bool digital_gain_0db_limit = true; +static bool leds_off; +static bool auto_mute; +static int mute_ext_ctl; +static int mute_ext; +static bool tas_device; +static struct gpio_desc *snd_mute_gpio; +static struct gpio_desc *snd_reset_gpio; +static struct snd_soc_card snd_rpi_hifiberry_dacplus; + +static const u32 master_dai_rates[] = { + 44100, 48000, 88200, 96000, + 176400, 192000, 352800, 384000, +}; + +static const struct snd_pcm_hw_constraint_list constraints_master = { + .count = ARRAY_SIZE(master_dai_rates), + .list = master_dai_rates, +}; + +static int snd_rpi_hifiberry_dacplus_mute_set(int mute) +{ + gpiod_set_value_cansleep(snd_mute_gpio, mute); + return 1; +} + +static int snd_rpi_hifiberry_dacplus_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = mute_ext; + + return 0; +} + +static int snd_rpi_hifiberry_dacplus_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + if (mute_ext == ucontrol->value.integer.value[0]) + return 0; + + mute_ext = ucontrol->value.integer.value[0]; + + return snd_rpi_hifiberry_dacplus_mute_set(mute_ext); +} + +static const char * const mute_text[] = {"Play", "Mute"}; +static const struct soc_enum hb_dacplus_opt_mute_enum = + SOC_ENUM_SINGLE_EXT(2, mute_text); + +static const struct snd_kcontrol_new hb_dacplus_opt_mute_controls[] = { + SOC_ENUM_EXT("Mute(ext)", hb_dacplus_opt_mute_enum, + snd_rpi_hifiberry_dacplus_mute_get, + snd_rpi_hifiberry_dacplus_mute_put), +}; + +static void snd_rpi_hifiberry_dacplus_select_clk(struct snd_soc_component *component, + int clk_id) +{ + switch (clk_id) { + case HIFIBERRY_DACPRO_NOCLOCK: + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x00); + break; + case HIFIBERRY_DACPRO_CLK44EN: + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x20); + break; + case HIFIBERRY_DACPRO_CLK48EN: + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x04); + break; + } + usleep_range(3000, 4000); +} + +static void snd_rpi_hifiberry_dacplus_clk_gpio(struct snd_soc_component *component) +{ + snd_soc_component_update_bits(component, PCM512x_GPIO_EN, 0x24, 0x24); + snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_3, 0x0f, 0x02); + snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_6, 0x0f, 0x02); +} + +static bool snd_rpi_hifiberry_dacplus_is_sclk(struct snd_soc_component *component) +{ + unsigned int sck; + + sck = snd_soc_component_read(component, PCM512x_RATE_DET_4); + return (!(sck & 0x40)); +} + +static bool snd_rpi_hifiberry_dacplus_is_pro_card(struct snd_soc_component *component) +{ + bool isClk44EN, isClk48En, isNoClk; + + snd_rpi_hifiberry_dacplus_clk_gpio(component); + + snd_rpi_hifiberry_dacplus_select_clk(component, HIFIBERRY_DACPRO_CLK44EN); + isClk44EN = snd_rpi_hifiberry_dacplus_is_sclk(component); + + snd_rpi_hifiberry_dacplus_select_clk(component, HIFIBERRY_DACPRO_NOCLOCK); + isNoClk = snd_rpi_hifiberry_dacplus_is_sclk(component); + + snd_rpi_hifiberry_dacplus_select_clk(component, HIFIBERRY_DACPRO_CLK48EN); + isClk48En = snd_rpi_hifiberry_dacplus_is_sclk(component); + + return (isClk44EN && isClk48En && !isNoClk); +} + +static int snd_rpi_hifiberry_dacplus_clk_for_rate(int sample_rate) +{ + int type; + + switch (sample_rate) { + case 11025: + case 22050: + case 44100: + case 88200: + case 176400: + case 352800: + type = HIFIBERRY_DACPRO_CLK44EN; + break; + default: + type = HIFIBERRY_DACPRO_CLK48EN; + break; + } + return type; +} + +static void snd_rpi_hifiberry_dacplus_set_sclk(struct snd_soc_component *component, + int sample_rate) +{ + struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component); + + if (!IS_ERR(pcm512x->sclk)) { + int ctype; + + ctype = snd_rpi_hifiberry_dacplus_clk_for_rate(sample_rate); + clk_set_rate(pcm512x->sclk, (ctype == HIFIBERRY_DACPRO_CLK44EN) + ? CLK_44EN_RATE : CLK_48EN_RATE); + snd_rpi_hifiberry_dacplus_select_clk(component, ctype); + } +} + +static int snd_rpi_hifiberry_dacplus_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + struct pcm512x_priv *priv; + struct snd_soc_card *card = &snd_rpi_hifiberry_dacplus; + + if (slave) + snd_rpi_hifiberry_is_dacpro = false; + else + snd_rpi_hifiberry_is_dacpro = + snd_rpi_hifiberry_dacplus_is_pro_card(component); + + if (snd_rpi_hifiberry_is_dacpro) { + struct snd_soc_dai_link *dai = rtd->dai_link; + + if (tas_device) { + dai->name = "HiFiBerry AMP4 Pro"; + dai->stream_name = "HiFiBerry AMP4 Pro HiFi"; + } else { + dai->name = "HiFiBerry DAC+ Pro"; + dai->stream_name = "HiFiBerry DAC+ Pro HiFi"; + } + dai->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM; + + snd_soc_component_update_bits(component, PCM512x_BCLK_LRCLK_CFG, 0x31, 0x11); + snd_soc_component_update_bits(component, PCM512x_MASTER_MODE, 0x03, 0x03); + snd_soc_component_update_bits(component, PCM512x_MASTER_CLKDIV_2, 0x7f, 63); + } else { + priv = snd_soc_component_get_drvdata(component); + priv->sclk = ERR_PTR(-ENOENT); + } + + snd_soc_component_update_bits(component, PCM512x_GPIO_EN, 0x08, 0x08); + snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_4, 0x0f, 0x02); + if (leds_off) + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x00); + else + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x08); + + if (digital_gain_0db_limit) { + int ret; + struct snd_soc_card *card = rtd->card; + + ret = snd_soc_limit_volume(card, "Digital Playback Volume", 207); + if (ret < 0) + dev_warn(card->dev, "Failed to set volume limit: %d\n", ret); + } + if (snd_reset_gpio) { + gpiod_set_value_cansleep(snd_reset_gpio, 0); + msleep(1); + gpiod_set_value_cansleep(snd_reset_gpio, 1); + msleep(1); + gpiod_set_value_cansleep(snd_reset_gpio, 0); + } + + if (mute_ext_ctl) + snd_soc_add_card_controls(card, hb_dacplus_opt_mute_controls, + ARRAY_SIZE(hb_dacplus_opt_mute_controls)); + + if (snd_mute_gpio) + gpiod_set_value_cansleep(snd_mute_gpio, mute_ext); + + return 0; +} + +static int snd_rpi_hifiberry_dacplus_update_rate_den( + struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component); + struct snd_ratnum *rats_no_pll; + unsigned int num = 0, den = 0; + int err; + + rats_no_pll = devm_kzalloc(rtd->dev, sizeof(*rats_no_pll), GFP_KERNEL); + if (!rats_no_pll) + return -ENOMEM; + + rats_no_pll->num = clk_get_rate(pcm512x->sclk) / 64; + rats_no_pll->den_min = 1; + rats_no_pll->den_max = 128; + rats_no_pll->den_step = 1; + + err = snd_interval_ratnum(hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE), 1, rats_no_pll, &num, &den); + if (err >= 0 && den) { + params->rate_num = num; + params->rate_den = den; + } + + devm_kfree(rtd->dev, rats_no_pll); + return 0; +} + +static int snd_rpi_hifiberry_dacplus_hw_params( + struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) +{ + int ret = 0; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + int channels = params_channels(params); + int width = snd_pcm_format_width(params_format(params)); + + /* Using powers of 2 allows for an integer clock divisor */ + width = width <= 16 ? 16 : 32; + + if (snd_rpi_hifiberry_is_dacpro) { + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + + snd_rpi_hifiberry_dacplus_set_sclk(component, + params_rate(params)); + + ret = snd_rpi_hifiberry_dacplus_update_rate_den( + substream, params); + } + + ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_cpu(rtd, 0), channels * width); + if (ret) + return ret; + ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_codec(rtd, 0), channels * width); + return ret; +} + +static int snd_rpi_hifiberry_dacplus_startup( + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + int ret; + + if (tas_device && !slave) { + ret = snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_master); + if (ret < 0) { + dev_err(rtd->card->dev, + "Cannot apply constraints for sample rates\n"); + return ret; + } + } + + if (auto_mute) + gpiod_set_value_cansleep(snd_mute_gpio, 0); + if (leds_off) + return 0; + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x08); + return 0; +} + +static void snd_rpi_hifiberry_dacplus_shutdown( + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x00); + if (auto_mute) + gpiod_set_value_cansleep(snd_mute_gpio, 1); +} + +/* machine stream operations */ +static const struct snd_soc_ops snd_rpi_hifiberry_dacplus_ops = { + .hw_params = snd_rpi_hifiberry_dacplus_hw_params, + .startup = snd_rpi_hifiberry_dacplus_startup, + .shutdown = snd_rpi_hifiberry_dacplus_shutdown, +}; + +SND_SOC_DAILINK_DEFS(rpi_hifiberry_dacplus, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004d", "pcm512x-hifi")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0"))); + +static struct snd_soc_dai_link snd_rpi_hifiberry_dacplus_dai[] = { +{ + .name = "HiFiBerry DAC+", + .stream_name = "HiFiBerry DAC+ HiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &snd_rpi_hifiberry_dacplus_ops, + .init = snd_rpi_hifiberry_dacplus_init, + SND_SOC_DAILINK_REG(rpi_hifiberry_dacplus), +}, +}; + +/* aux device for optional headphone amp */ +static struct snd_soc_aux_dev hifiberry_dacplus_aux_devs[] = { + { + .dlc = { + .name = "tpa6130a2.1-0060", + }, + }, +}; + +/* audio machine driver */ +static struct snd_soc_card snd_rpi_hifiberry_dacplus = { + .name = "snd_rpi_hifiberry_dacplus", + .driver_name = "HifiberryDacp", + .owner = THIS_MODULE, + .dai_link = snd_rpi_hifiberry_dacplus_dai, + .num_links = ARRAY_SIZE(snd_rpi_hifiberry_dacplus_dai), +}; + +static int hb_hp_detect(void) +{ + struct i2c_adapter *adap = i2c_get_adapter(1); + int ret; + struct i2c_client tpa_i2c_client = { + .addr = 0x60, + .adapter = adap, + }; + + if (!adap) + return -EPROBE_DEFER; /* I2C module not yet available */ + + ret = i2c_smbus_read_byte(&tpa_i2c_client) >= 0; + i2c_put_adapter(adap); + return ret; +}; + +static struct property tpa_enable_prop = { + .name = "status", + .length = 4 + 1, /* length 'okay' + 1 */ + .value = "okay", + }; + +static int snd_rpi_hifiberry_dacplus_probe(struct platform_device *pdev) +{ + int ret = 0; + struct snd_soc_card *card = &snd_rpi_hifiberry_dacplus; + int len; + struct device_node *tpa_node; + struct device_node *tas_node; + struct property *tpa_prop; + struct of_changeset ocs; + struct property *pp; + int tmp; + + /* probe for head phone amp */ + ret = hb_hp_detect(); + if (ret < 0) + return ret; + if (ret) { + card->aux_dev = hifiberry_dacplus_aux_devs; + card->num_aux_devs = + ARRAY_SIZE(hifiberry_dacplus_aux_devs); + tpa_node = of_find_compatible_node(NULL, NULL, "ti,tpa6130a2"); + tpa_prop = of_find_property(tpa_node, "status", &len); + + if (strcmp((char *)tpa_prop->value, "okay")) { + /* and activate headphone using change_sets */ + dev_info(&pdev->dev, "activating headphone amplifier"); + of_changeset_init(&ocs); + ret = of_changeset_update_property(&ocs, tpa_node, + &tpa_enable_prop); + if (ret) { + dev_err(&pdev->dev, + "cannot activate headphone amplifier\n"); + return -ENODEV; + } + ret = of_changeset_apply(&ocs); + if (ret) { + dev_err(&pdev->dev, + "cannot activate headphone amplifier\n"); + return -ENODEV; + } + } + } + + tas_node = of_find_compatible_node(NULL, NULL, "ti,tas5756"); + if (tas_node) { + tas_device = true; + dev_info(&pdev->dev, "TAS5756 device found!\n"); + }; + + snd_rpi_hifiberry_dacplus.dev = &pdev->dev; + if (pdev->dev.of_node) { + struct device_node *i2s_node; + struct snd_soc_dai_link *dai; + + dai = &snd_rpi_hifiberry_dacplus_dai[0]; + i2s_node = of_parse_phandle(pdev->dev.of_node, + "i2s-controller", 0); + + if (i2s_node) { + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + } + + digital_gain_0db_limit = !of_property_read_bool( + pdev->dev.of_node, "hifiberry,24db_digital_gain"); + slave = of_property_read_bool(pdev->dev.of_node, + "hifiberry-dacplus,slave"); + leds_off = of_property_read_bool(pdev->dev.of_node, + "hifiberry-dacplus,leds_off"); + auto_mute = of_property_read_bool(pdev->dev.of_node, + "hifiberry-dacplus,auto_mute"); + + /* + * check for HW MUTE as defined in DT-overlay + * active high, therefore default to HIGH to MUTE + */ + snd_mute_gpio = devm_gpiod_get_optional(&pdev->dev, + "mute", GPIOD_OUT_HIGH); + if (IS_ERR(snd_mute_gpio)) { + dev_err(&pdev->dev, "Can't allocate GPIO (HW-MUTE)"); + return PTR_ERR(snd_mute_gpio); + } + + /* add ALSA control if requested in DT-overlay (AMP100) */ + pp = of_find_property(pdev->dev.of_node, + "hifiberry-dacplus,mute_ext_ctl", &tmp); + if (pp) { + if (!of_property_read_u32(pdev->dev.of_node, + "hifiberry-dacplus,mute_ext_ctl", &mute_ext)) { + /* ALSA control will be used */ + mute_ext_ctl = 1; + } + } + + /* check for HW RESET (AMP100) */ + snd_reset_gpio = devm_gpiod_get_optional(&pdev->dev, + "reset", GPIOD_OUT_HIGH); + if (IS_ERR(snd_reset_gpio)) { + dev_err(&pdev->dev, "Can't allocate GPIO (HW-RESET)"); + return PTR_ERR(snd_reset_gpio); + } + + } + + ret = devm_snd_soc_register_card(&pdev->dev, + &snd_rpi_hifiberry_dacplus); + if (ret && ret != -EPROBE_DEFER) + dev_err(&pdev->dev, + "snd_soc_register_card() failed: %d\n", ret); + if (!ret) { + if (snd_mute_gpio) + dev_info(&pdev->dev, "GPIO%i for HW-MUTE selected", + gpio_chip_hwgpio(snd_mute_gpio)); + if (snd_reset_gpio) + dev_info(&pdev->dev, "GPIO%i for HW-RESET selected", + gpio_chip_hwgpio(snd_reset_gpio)); + } + return ret; +} + +static const struct of_device_id snd_rpi_hifiberry_dacplus_of_match[] = { + { .compatible = "hifiberry,hifiberry-dacplus", }, + {}, +}; +MODULE_DEVICE_TABLE(of, snd_rpi_hifiberry_dacplus_of_match); + +static struct platform_driver snd_rpi_hifiberry_dacplus_driver = { + .driver = { + .name = "snd-rpi-hifiberry-dacplus", + .owner = THIS_MODULE, + .of_match_table = snd_rpi_hifiberry_dacplus_of_match, + }, + .probe = snd_rpi_hifiberry_dacplus_probe, +}; + +module_platform_driver(snd_rpi_hifiberry_dacplus_driver); + +MODULE_AUTHOR("Daniel Matuschek <daniel@hifiberry.com>"); +MODULE_DESCRIPTION("ASoC Driver for HiFiBerry DAC+"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/hifiberry_dacplusadc.c b/sound/soc/bcm/hifiberry_dacplusadc.c new file mode 100644 index 00000000000000..d21b006aad7d05 --- /dev/null +++ b/sound/soc/bcm/hifiberry_dacplusadc.c @@ -0,0 +1,399 @@ +/* + * ASoC Driver for HiFiBerry DAC+ / DAC Pro with ADC + * + * Author: Daniel Matuschek, Stuart MacLean <stuart@hifiberry.com> + * Copyright 2014-2015 + * based on code by Florian Meier <florian.meier@koalo.de> + * ADC added by Joerg Schambacher <joscha@schambacher.com> + * Copyright 2018 + * + * 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 <linux/module.h> +#include <linux/platform_device.h> +#include <linux/kernel.h> +#include <linux/clk.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/slab.h> +#include <linux/delay.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/jack.h> + +#include "../codecs/pcm512x.h" + +#define HIFIBERRY_DACPRO_NOCLOCK 0 +#define HIFIBERRY_DACPRO_CLK44EN 1 +#define HIFIBERRY_DACPRO_CLK48EN 2 + +struct platform_device *dmic_codec_dev; + +struct pcm512x_priv { + struct regmap *regmap; + struct clk *sclk; +}; + +/* Clock rate of CLK44EN attached to GPIO6 pin */ +#define CLK_44EN_RATE 22579200UL +/* Clock rate of CLK48EN attached to GPIO3 pin */ +#define CLK_48EN_RATE 24576000UL + +static bool slave; +static bool snd_rpi_hifiberry_is_dacpro; +static bool digital_gain_0db_limit = true; +static bool leds_off; + +static void snd_rpi_hifiberry_dacplusadc_select_clk(struct snd_soc_component *component, + int clk_id) +{ + switch (clk_id) { + case HIFIBERRY_DACPRO_NOCLOCK: + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x00); + break; + case HIFIBERRY_DACPRO_CLK44EN: + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x20); + break; + case HIFIBERRY_DACPRO_CLK48EN: + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x04); + break; + } +} + +static void snd_rpi_hifiberry_dacplusadc_clk_gpio(struct snd_soc_component *component) +{ + snd_soc_component_update_bits(component, PCM512x_GPIO_EN, 0x24, 0x24); + snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_3, 0x0f, 0x02); + snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_6, 0x0f, 0x02); +} + +static bool snd_rpi_hifiberry_dacplusadc_is_sclk(struct snd_soc_component *component) +{ + unsigned int sck; + + sck = snd_soc_component_read(component, PCM512x_RATE_DET_4); + return (!(sck & 0x40)); +} + +static bool snd_rpi_hifiberry_dacplusadc_is_sclk_sleep( + struct snd_soc_component *component) +{ + msleep(2); + return snd_rpi_hifiberry_dacplusadc_is_sclk(component); +} + +static bool snd_rpi_hifiberry_dacplusadc_is_pro_card(struct snd_soc_component *component) +{ + bool isClk44EN, isClk48En, isNoClk; + + snd_rpi_hifiberry_dacplusadc_clk_gpio(component); + + snd_rpi_hifiberry_dacplusadc_select_clk(component, HIFIBERRY_DACPRO_CLK44EN); + isClk44EN = snd_rpi_hifiberry_dacplusadc_is_sclk_sleep(component); + + snd_rpi_hifiberry_dacplusadc_select_clk(component, HIFIBERRY_DACPRO_NOCLOCK); + isNoClk = snd_rpi_hifiberry_dacplusadc_is_sclk_sleep(component); + + snd_rpi_hifiberry_dacplusadc_select_clk(component, HIFIBERRY_DACPRO_CLK48EN); + isClk48En = snd_rpi_hifiberry_dacplusadc_is_sclk_sleep(component); + + return (isClk44EN && isClk48En && !isNoClk); +} + +static int snd_rpi_hifiberry_dacplusadc_clk_for_rate(int sample_rate) +{ + int type; + + switch (sample_rate) { + case 11025: + case 22050: + case 44100: + case 88200: + case 176400: + case 352800: + type = HIFIBERRY_DACPRO_CLK44EN; + break; + default: + type = HIFIBERRY_DACPRO_CLK48EN; + break; + } + return type; +} + +static void snd_rpi_hifiberry_dacplusadc_set_sclk(struct snd_soc_component *component, + int sample_rate) +{ + struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component); + + if (!IS_ERR(pcm512x->sclk)) { + int ctype; + + ctype = snd_rpi_hifiberry_dacplusadc_clk_for_rate(sample_rate); + clk_set_rate(pcm512x->sclk, (ctype == HIFIBERRY_DACPRO_CLK44EN) + ? CLK_44EN_RATE : CLK_48EN_RATE); + snd_rpi_hifiberry_dacplusadc_select_clk(component, ctype); + } +} + +static int snd_rpi_hifiberry_dacplusadc_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + struct pcm512x_priv *priv; + + if (slave) + snd_rpi_hifiberry_is_dacpro = false; + else + snd_rpi_hifiberry_is_dacpro = + snd_rpi_hifiberry_dacplusadc_is_pro_card(component); + + if (snd_rpi_hifiberry_is_dacpro) { + struct snd_soc_dai_link *dai = rtd->dai_link; + + dai->name = "HiFiBerry ADCDAC+ Pro"; + dai->stream_name = "HiFiBerry ADCDAC+ Pro HiFi"; + dai->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM; + + snd_soc_component_update_bits(component, PCM512x_BCLK_LRCLK_CFG, 0x31, 0x11); + snd_soc_component_update_bits(component, PCM512x_MASTER_MODE, 0x03, 0x03); + snd_soc_component_update_bits(component, PCM512x_MASTER_CLKDIV_2, 0x7f, 63); + } else { + priv = snd_soc_component_get_drvdata(component); + priv->sclk = ERR_PTR(-ENOENT); + } + + snd_soc_component_update_bits(component, PCM512x_GPIO_EN, 0x08, 0x08); + snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_4, 0x0f, 0x02); + if (leds_off) + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x00); + else + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x08); + + if (digital_gain_0db_limit) { + int ret; + struct snd_soc_card *card = rtd->card; + + ret = snd_soc_limit_volume(card, "Digital Playback Volume", 207); + if (ret < 0) + dev_warn(card->dev, "Failed to set volume limit: %d\n", ret); + } + + return 0; +} + +static int snd_rpi_hifiberry_dacplusadc_update_rate_den( + struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component); + struct snd_ratnum *rats_no_pll; + unsigned int num = 0, den = 0; + int err; + + rats_no_pll = devm_kzalloc(rtd->dev, sizeof(*rats_no_pll), GFP_KERNEL); + if (!rats_no_pll) + return -ENOMEM; + + rats_no_pll->num = clk_get_rate(pcm512x->sclk) / 64; + rats_no_pll->den_min = 1; + rats_no_pll->den_max = 128; + rats_no_pll->den_step = 1; + + err = snd_interval_ratnum(hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE), 1, rats_no_pll, &num, &den); + if (err >= 0 && den) { + params->rate_num = num; + params->rate_den = den; + } + + devm_kfree(rtd->dev, rats_no_pll); + return 0; +} + +static int snd_rpi_hifiberry_dacplusadc_hw_params( + struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) +{ + int ret = 0; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + int channels = params_channels(params); + int width = snd_pcm_format_width(params_format(params)); + + /* Using powers of 2 allows for an integer clock divisor */ + width = width <= 16 ? 16 : 32; + + if (snd_rpi_hifiberry_is_dacpro) { + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + + snd_rpi_hifiberry_dacplusadc_set_sclk(component, + params_rate(params)); + + ret = snd_rpi_hifiberry_dacplusadc_update_rate_den( + substream, params); + } + + ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_cpu(rtd, 0), channels * width); + if (ret) + return ret; + ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_codec(rtd, 0), channels * width); + return ret; +} + +static int hifiberry_dacplusadc_LED_cnt; + +static int snd_rpi_hifiberry_dacplusadc_startup( + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + + if (leds_off) + return 0; + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, + 0x08, 0x08); + hifiberry_dacplusadc_LED_cnt++; + return 0; +} + +static void snd_rpi_hifiberry_dacplusadc_shutdown( + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + + hifiberry_dacplusadc_LED_cnt--; + if (!hifiberry_dacplusadc_LED_cnt) + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, + 0x08, 0x00); +} + +/* machine stream operations */ +static struct snd_soc_ops snd_rpi_hifiberry_dacplusadc_ops = { + .hw_params = snd_rpi_hifiberry_dacplusadc_hw_params, + .startup = snd_rpi_hifiberry_dacplusadc_startup, + .shutdown = snd_rpi_hifiberry_dacplusadc_shutdown, +}; + +SND_SOC_DAILINK_DEFS(hifi, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004d", "pcm512x-hifi"), + COMP_CODEC("dmic-codec", "dmic-hifi")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0"))); + +static struct snd_soc_dai_link snd_rpi_hifiberry_dacplusadc_dai[] = { +{ + .name = "HiFiBerry DAC+ADC", + .stream_name = "HiFiBerry DAC+ADC HiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &snd_rpi_hifiberry_dacplusadc_ops, + .init = snd_rpi_hifiberry_dacplusadc_init, + SND_SOC_DAILINK_REG(hifi), +}, +}; + +/* audio machine driver */ +static struct snd_soc_card snd_rpi_hifiberry_dacplusadc = { + .name = "snd_rpi_hifiberry_dacplusadc", + .driver_name = "HifiberryDacpAdc", + .owner = THIS_MODULE, + .dai_link = snd_rpi_hifiberry_dacplusadc_dai, + .num_links = ARRAY_SIZE(snd_rpi_hifiberry_dacplusadc_dai), +}; + + +static int snd_rpi_hifiberry_dacplusadc_probe(struct platform_device *pdev) +{ + int ret = 0; + + snd_rpi_hifiberry_dacplusadc.dev = &pdev->dev; + if (pdev->dev.of_node) { + struct device_node *i2s_node; + struct snd_soc_dai_link *dai; + + dai = &snd_rpi_hifiberry_dacplusadc_dai[0]; + i2s_node = of_parse_phandle(pdev->dev.of_node, + "i2s-controller", 0); + if (i2s_node) { + dai->cpus->of_node = i2s_node; + dai->platforms->of_node = i2s_node; + dai->cpus->dai_name = NULL; + dai->platforms->name = NULL; + } + } + digital_gain_0db_limit = !of_property_read_bool( + pdev->dev.of_node, "hifiberry,24db_digital_gain"); + slave = of_property_read_bool(pdev->dev.of_node, + "hifiberry-dacplusadc,slave"); + leds_off = of_property_read_bool(pdev->dev.of_node, + "hifiberry-dacplusadc,leds_off"); + + ret = devm_snd_soc_register_card(&pdev->dev, + &snd_rpi_hifiberry_dacplusadc); + if (ret && ret != -EPROBE_DEFER) + dev_err(&pdev->dev, + "snd_soc_register_card() failed: %d\n", ret); + + return ret; +} + +static const struct of_device_id snd_rpi_hifiberry_dacplusadc_of_match[] = { + { .compatible = "hifiberry,hifiberry-dacplusadc", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, snd_rpi_hifiberry_dacplusadc_of_match); + +static struct platform_driver snd_rpi_hifiberry_dacplusadc_driver = { + .driver = { + .name = "snd-rpi-hifiberry-dacplusadc", + .owner = THIS_MODULE, + .of_match_table = snd_rpi_hifiberry_dacplusadc_of_match, + }, + .probe = snd_rpi_hifiberry_dacplusadc_probe, +}; + +static int __init hifiberry_dacplusadc_init(void) +{ + int ret; + + dmic_codec_dev = platform_device_register_simple("dmic-codec", -1, NULL, + 0); + if (IS_ERR(dmic_codec_dev)) { + pr_err("%s: dmic-codec device registration failed\n", __func__); + return PTR_ERR(dmic_codec_dev); + } + + ret = platform_driver_register(&snd_rpi_hifiberry_dacplusadc_driver); + if (ret) { + pr_err("%s: platform driver registration failed\n", __func__); + platform_device_unregister(dmic_codec_dev); + } + + return ret; +} +module_init(hifiberry_dacplusadc_init); + +static void __exit hifiberry_dacplusadc_exit(void) +{ + platform_driver_unregister(&snd_rpi_hifiberry_dacplusadc_driver); + platform_device_unregister(dmic_codec_dev); +} +module_exit(hifiberry_dacplusadc_exit); + +MODULE_AUTHOR("Joerg Schambacher <joscha@schambacher.com>"); +MODULE_AUTHOR("Daniel Matuschek <daniel@hifiberry.com>"); +MODULE_DESCRIPTION("ASoC Driver for HiFiBerry DAC+ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/hifiberry_dacplusadcpro.c b/sound/soc/bcm/hifiberry_dacplusadcpro.c new file mode 100644 index 00000000000000..1060069f389c28 --- /dev/null +++ b/sound/soc/bcm/hifiberry_dacplusadcpro.c @@ -0,0 +1,498 @@ +/* + * ASoC Driver for HiFiBerry DAC+ / DAC Pro with ADC PRO Version (SW control) + * + * Author: Daniel Matuschek, Stuart MacLean <stuart@hifiberry.com> + * Copyright 2014-2015 + * based on code by Florian Meier <florian.meier@koalo.de> + * ADC, HP added by Joerg Schambacher <joerg@hifiberry.com> + * Copyright 2018-21 + * + * 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 <linux/module.h> +#include <linux/platform_device.h> +#include <linux/kernel.h> +#include <linux/clk.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/i2c.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include <sound/tlv.h> + +#include "../codecs/pcm512x.h" +#include "../codecs/pcm186x.h" +#include "hifiberry_adc_controls.h" + +#define HIFIBERRY_DACPRO_NOCLOCK 0 +#define HIFIBERRY_DACPRO_CLK44EN 1 +#define HIFIBERRY_DACPRO_CLK48EN 2 + +struct pcm512x_priv { + struct regmap *regmap; + struct clk *sclk; +}; + +/* Clock rate of CLK44EN attached to GPIO6 pin */ +#define CLK_44EN_RATE 22579200UL +/* Clock rate of CLK48EN attached to GPIO3 pin */ +#define CLK_48EN_RATE 24576000UL + +static bool slave; +static bool snd_rpi_hifiberry_is_dacpro; +static bool digital_gain_0db_limit = true; +static bool leds_off; + +static int pcm1863_add_controls(struct snd_soc_component *component) +{ + snd_soc_add_component_controls(component, + pcm1863_snd_controls_card, + ARRAY_SIZE(pcm1863_snd_controls_card)); + return 0; +} + +static void snd_rpi_hifiberry_dacplusadcpro_select_clk( + struct snd_soc_component *component, int clk_id) +{ + switch (clk_id) { + case HIFIBERRY_DACPRO_NOCLOCK: + snd_soc_component_update_bits(component, + PCM512x_GPIO_CONTROL_1, 0x24, 0x00); + break; + case HIFIBERRY_DACPRO_CLK44EN: + snd_soc_component_update_bits(component, + PCM512x_GPIO_CONTROL_1, 0x24, 0x20); + break; + case HIFIBERRY_DACPRO_CLK48EN: + snd_soc_component_update_bits(component, + PCM512x_GPIO_CONTROL_1, 0x24, 0x04); + break; + } + usleep_range(3000, 4000); +} + +static void snd_rpi_hifiberry_dacplusadcpro_clk_gpio(struct snd_soc_component *component) +{ + snd_soc_component_update_bits(component, PCM512x_GPIO_EN, 0x24, 0x24); + snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_3, 0x0f, 0x02); + snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_6, 0x0f, 0x02); +} + +static bool snd_rpi_hifiberry_dacplusadcpro_is_sclk(struct snd_soc_component *component) +{ + unsigned int sck; + + sck = snd_soc_component_read(component, PCM512x_RATE_DET_4); + return (!(sck & 0x40)); +} + +static bool snd_rpi_hifiberry_dacplusadcpro_is_pro_card(struct snd_soc_component *component) +{ + bool isClk44EN, isClk48En, isNoClk; + + snd_rpi_hifiberry_dacplusadcpro_clk_gpio(component); + + snd_rpi_hifiberry_dacplusadcpro_select_clk(component, HIFIBERRY_DACPRO_CLK44EN); + isClk44EN = snd_rpi_hifiberry_dacplusadcpro_is_sclk(component); + + snd_rpi_hifiberry_dacplusadcpro_select_clk(component, HIFIBERRY_DACPRO_NOCLOCK); + isNoClk = snd_rpi_hifiberry_dacplusadcpro_is_sclk(component); + + snd_rpi_hifiberry_dacplusadcpro_select_clk(component, HIFIBERRY_DACPRO_CLK48EN); + isClk48En = snd_rpi_hifiberry_dacplusadcpro_is_sclk(component); + + return (isClk44EN && isClk48En && !isNoClk); +} + +static int snd_rpi_hifiberry_dacplusadcpro_clk_for_rate(int sample_rate) +{ + int type; + + switch (sample_rate) { + case 11025: + case 22050: + case 44100: + case 88200: + case 176400: + case 352800: + type = HIFIBERRY_DACPRO_CLK44EN; + break; + default: + type = HIFIBERRY_DACPRO_CLK48EN; + break; + } + return type; +} + +static void snd_rpi_hifiberry_dacplusadcpro_set_sclk(struct snd_soc_component *component, + int sample_rate) +{ + struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component); + + if (!IS_ERR(pcm512x->sclk)) { + int ctype; + + ctype = snd_rpi_hifiberry_dacplusadcpro_clk_for_rate(sample_rate); + clk_set_rate(pcm512x->sclk, (ctype == HIFIBERRY_DACPRO_CLK44EN) + ? CLK_44EN_RATE : CLK_48EN_RATE); + snd_rpi_hifiberry_dacplusadcpro_select_clk(component, ctype); + } +} + +static int snd_rpi_hifiberry_dacplusadcpro_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *dac = snd_soc_rtd_to_codec(rtd, 0)->component; + struct snd_soc_component *adc = snd_soc_rtd_to_codec(rtd, 1)->component; + struct snd_soc_dai_driver *adc_driver = snd_soc_rtd_to_codec(rtd, 1)->driver; + struct pcm512x_priv *priv; + int ret; + + if (slave) + snd_rpi_hifiberry_is_dacpro = false; + else + snd_rpi_hifiberry_is_dacpro = + snd_rpi_hifiberry_dacplusadcpro_is_pro_card(dac); + + if (snd_rpi_hifiberry_is_dacpro) { + struct snd_soc_dai_link *dai = rtd->dai_link; + + dai->name = "HiFiBerry DAC+ADC Pro"; + dai->stream_name = "HiFiBerry DAC+ADC Pro HiFi"; + dai->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM; + + // set DAC DAI configuration + ret = snd_soc_dai_set_fmt(snd_soc_rtd_to_codec(rtd, 0), + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + // set ADC DAI configuration + ret = snd_soc_dai_set_fmt(snd_soc_rtd_to_codec(rtd, 1), + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + // set CPU DAI configuration + ret = snd_soc_dai_set_fmt(snd_soc_rtd_to_cpu(rtd, 0), + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + snd_soc_component_update_bits(dac, PCM512x_BCLK_LRCLK_CFG, 0x31, 0x11); + snd_soc_component_update_bits(dac, PCM512x_MASTER_MODE, 0x03, 0x03); + snd_soc_component_update_bits(dac, PCM512x_MASTER_CLKDIV_2, 0x7f, 63); + } else { + priv = snd_soc_component_get_drvdata(dac); + priv->sclk = ERR_PTR(-ENOENT); + } + + /* disable 24bit mode as long as I2S module does not have sign extension fixed */ + adc_driver->capture.formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE; + + snd_soc_component_update_bits(dac, PCM512x_GPIO_EN, 0x08, 0x08); + snd_soc_component_update_bits(dac, PCM512x_GPIO_OUTPUT_4, 0x0f, 0x02); + if (leds_off) + snd_soc_component_update_bits(dac, PCM512x_GPIO_CONTROL_1, 0x08, 0x00); + else + snd_soc_component_update_bits(dac, PCM512x_GPIO_CONTROL_1, 0x08, 0x08); + + ret = pcm1863_add_controls(adc); + if (ret < 0) + dev_warn(rtd->dev, "Failed to add pcm1863 controls: %d\n", + ret); + + /* set GPIO2 to output, GPIO3 input */ + snd_soc_component_write(adc, PCM186X_GPIO3_2_CTRL, 0x00); + snd_soc_component_write(adc, PCM186X_GPIO3_2_DIR_CTRL, 0x04); + if (leds_off) + snd_soc_component_update_bits(adc, PCM186X_GPIO_IN_OUT, 0x40, 0x00); + else + snd_soc_component_update_bits(adc, PCM186X_GPIO_IN_OUT, 0x40, 0x40); + + if (digital_gain_0db_limit) { + int ret; + struct snd_soc_card *card = rtd->card; + + ret = snd_soc_limit_volume(card, "Digital Playback Volume", 207); + if (ret < 0) + dev_warn(card->dev, "Failed to set volume limit: %d\n", ret); + } + + return 0; +} + +static int snd_rpi_hifiberry_dacplusadcpro_update_rate_den( + struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; /* only use DAC */ + struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component); + struct snd_ratnum *rats_no_pll; + unsigned int num = 0, den = 0; + int err; + + rats_no_pll = devm_kzalloc(rtd->dev, sizeof(*rats_no_pll), GFP_KERNEL); + if (!rats_no_pll) + return -ENOMEM; + + rats_no_pll->num = clk_get_rate(pcm512x->sclk) / 64; + rats_no_pll->den_min = 1; + rats_no_pll->den_max = 128; + rats_no_pll->den_step = 1; + + err = snd_interval_ratnum(hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE), 1, rats_no_pll, &num, &den); + if (err >= 0 && den) { + params->rate_num = num; + params->rate_den = den; + } + + devm_kfree(rtd->dev, rats_no_pll); + return 0; +} + +static int snd_rpi_hifiberry_dacplusadcpro_hw_params( + struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) +{ + int ret = 0; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + int channels = params_channels(params); + int width = snd_pcm_format_width(params_format(params)); + struct snd_soc_component *dac = snd_soc_rtd_to_codec(rtd, 0)->component; + struct snd_soc_dai *dai = snd_soc_rtd_to_codec(rtd, 0); + struct snd_soc_dai_driver *drv = dai->driver; + const struct snd_soc_dai_ops *ops = drv->ops; + + /* Using powers of 2 allows for an integer clock divisor */ + width = width <= 16 ? 16 : 32; + + if (snd_rpi_hifiberry_is_dacpro) { + snd_rpi_hifiberry_dacplusadcpro_set_sclk(dac, + params_rate(params)); + + ret = snd_rpi_hifiberry_dacplusadcpro_update_rate_den( + substream, params); + if (ret) + return ret; + } + + ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_cpu(rtd, 0), channels * width); + if (ret) + return ret; + ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_codec(rtd, 0), channels * width); + if (ret) + return ret; + if (snd_rpi_hifiberry_is_dacpro && ops->hw_params) + ret = ops->hw_params(substream, params, dai); + return ret; +} + +static int snd_rpi_hifiberry_dacplusadcpro_startup( + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *dac = snd_soc_rtd_to_codec(rtd, 0)->component; + struct snd_soc_component *adc = snd_soc_rtd_to_codec(rtd, 1)->component; + + if (leds_off) + return 0; + /* switch on respective LED */ + if (!substream->stream) + snd_soc_component_update_bits(dac, PCM512x_GPIO_CONTROL_1, 0x08, 0x08); + else + snd_soc_component_update_bits(adc, PCM186X_GPIO_IN_OUT, 0x40, 0x40); + return 0; +} + +static void snd_rpi_hifiberry_dacplusadcpro_shutdown( + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *dac = snd_soc_rtd_to_codec(rtd, 0)->component; + struct snd_soc_component *adc = snd_soc_rtd_to_codec(rtd, 1)->component; + + /* switch off respective LED */ + if (!substream->stream) + snd_soc_component_update_bits(dac, PCM512x_GPIO_CONTROL_1, 0x08, 0x00); + else + snd_soc_component_update_bits(adc, PCM186X_GPIO_IN_OUT, 0x40, 0x00); +} + + +/* machine stream operations */ +static struct snd_soc_ops snd_rpi_hifiberry_dacplusadcpro_ops = { + .hw_params = snd_rpi_hifiberry_dacplusadcpro_hw_params, + .startup = snd_rpi_hifiberry_dacplusadcpro_startup, + .shutdown = snd_rpi_hifiberry_dacplusadcpro_shutdown, +}; + +SND_SOC_DAILINK_DEFS(hifi, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004d", "pcm512x-hifi"), + COMP_CODEC("pcm186x.1-004a", "pcm1863-aif")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0"))); + +static struct snd_soc_dai_link snd_rpi_hifiberry_dacplusadcpro_dai[] = { +{ + .name = "HiFiBerry DAC+ADC PRO", + .stream_name = "HiFiBerry DAC+ADC PRO HiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &snd_rpi_hifiberry_dacplusadcpro_ops, + .init = snd_rpi_hifiberry_dacplusadcpro_init, + SND_SOC_DAILINK_REG(hifi), +}, +}; + +/* aux device for optional headphone amp */ +static struct snd_soc_aux_dev hifiberry_dacplusadcpro_aux_devs[] = { + { + .dlc = { + .name = "tpa6130a2.1-0060", + }, + }, +}; + +/* audio machine driver */ +static struct snd_soc_card snd_rpi_hifiberry_dacplusadcpro = { + .name = "snd_rpi_hifiberry_dacplusadcpro", + .driver_name = "HifiberryDacpAdcPro", + .owner = THIS_MODULE, + .dai_link = snd_rpi_hifiberry_dacplusadcpro_dai, + .num_links = ARRAY_SIZE(snd_rpi_hifiberry_dacplusadcpro_dai), +}; + +static int hb_hp_detect(void) +{ + struct i2c_adapter *adap = i2c_get_adapter(1); + int ret; + struct i2c_client tpa_i2c_client = { + .addr = 0x60, + .adapter = adap, + }; + + if (!adap) + return -EPROBE_DEFER; /* I2C module not yet available */ + + ret = i2c_smbus_read_byte(&tpa_i2c_client) >= 0; + i2c_put_adapter(adap); + return ret; +}; + +static struct property tpa_enable_prop = { + .name = "status", + .length = 4 + 1, /* length 'okay' + 1 */ + .value = "okay", + }; + +static int snd_rpi_hifiberry_dacplusadcpro_probe(struct platform_device *pdev) +{ + int ret = 0, i = 0; + struct snd_soc_card *card = &snd_rpi_hifiberry_dacplusadcpro; + struct device_node *tpa_node; + struct property *tpa_prop; + struct of_changeset ocs; + int len; + + /* probe for head phone amp */ + ret = hb_hp_detect(); + if (ret < 0) + return ret; + if (ret) { + card->aux_dev = hifiberry_dacplusadcpro_aux_devs; + card->num_aux_devs = + ARRAY_SIZE(hifiberry_dacplusadcpro_aux_devs); + tpa_node = of_find_compatible_node(NULL, NULL, "ti,tpa6130a2"); + tpa_prop = of_find_property(tpa_node, "status", &len); + + if (strcmp((char *)tpa_prop->value, "okay")) { + /* and activate headphone using change_sets */ + dev_info(&pdev->dev, "activating headphone amplifier"); + of_changeset_init(&ocs); + ret = of_changeset_update_property(&ocs, tpa_node, + &tpa_enable_prop); + if (ret) { + dev_err(&pdev->dev, + "cannot activate headphone amplifier\n"); + return -ENODEV; + } + ret = of_changeset_apply(&ocs); + if (ret) { + dev_err(&pdev->dev, + "cannot activate headphone amplifier\n"); + return -ENODEV; + } + } + } + + snd_rpi_hifiberry_dacplusadcpro.dev = &pdev->dev; + if (pdev->dev.of_node) { + struct device_node *i2s_node; + struct snd_soc_dai_link *dai; + + dai = &snd_rpi_hifiberry_dacplusadcpro_dai[0]; + i2s_node = of_parse_phandle(pdev->dev.of_node, + "i2s-controller", 0); + if (i2s_node) { + for (i = 0; i < card->num_links; i++) { + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + } + } + } + digital_gain_0db_limit = !of_property_read_bool( + pdev->dev.of_node, "hifiberry-dacplusadcpro,24db_digital_gain"); + slave = of_property_read_bool(pdev->dev.of_node, + "hifiberry-dacplusadcpro,slave"); + leds_off = of_property_read_bool(pdev->dev.of_node, + "hifiberry-dacplusadcpro,leds_off"); + ret = snd_soc_register_card(&snd_rpi_hifiberry_dacplusadcpro); + if (ret && ret != -EPROBE_DEFER) + dev_err(&pdev->dev, + "snd_soc_register_card() failed: %d\n", ret); + + return ret; +} + +static const struct of_device_id snd_rpi_hifiberry_dacplusadcpro_of_match[] = { + { .compatible = "hifiberry,hifiberry-dacplusadcpro", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, snd_rpi_hifiberry_dacplusadcpro_of_match); + +static struct platform_driver snd_rpi_hifiberry_dacplusadcpro_driver = { + .driver = { + .name = "snd-rpi-hifiberry-dacplusadcpro", + .owner = THIS_MODULE, + .of_match_table = snd_rpi_hifiberry_dacplusadcpro_of_match, + }, + .probe = snd_rpi_hifiberry_dacplusadcpro_probe, +}; + +module_platform_driver(snd_rpi_hifiberry_dacplusadcpro_driver); + +MODULE_AUTHOR("Joerg Schambacher <joerg@hifiberry.com>"); +MODULE_AUTHOR("Daniel Matuschek <daniel@hifiberry.com>"); +MODULE_DESCRIPTION("ASoC Driver for HiFiBerry DAC+ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/hifiberry_dacplusdsp.c b/sound/soc/bcm/hifiberry_dacplusdsp.c new file mode 100644 index 00000000000000..5a6ea965042d2f --- /dev/null +++ b/sound/soc/bcm/hifiberry_dacplusdsp.c @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ASoC Driver for HiFiBerry DAC + DSP + * + * Author: Joerg Schambacher <joscha@schambacher.com> + * Copyright 2018 + * + * 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 <linux/init.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <sound/soc.h> + +static struct snd_soc_component_driver dacplusdsp_component_driver; + +static struct snd_soc_dai_driver dacplusdsp_dai = { + .name = "dacplusdsp-hifi", + .capture = { + .stream_name = "DAC+DSP Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .playback = { + .stream_name = "DACP+DSP Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .symmetric_rate = 1}; + +#ifdef CONFIG_OF +static const struct of_device_id dacplusdsp_ids[] = { + { + .compatible = "hifiberry,dacplusdsp", + }, + {} }; +MODULE_DEVICE_TABLE(of, dacplusdsp_ids); +#endif + +static int dacplusdsp_platform_probe(struct platform_device *pdev) +{ + int ret; + + ret = snd_soc_register_component(&pdev->dev, + &dacplusdsp_component_driver, &dacplusdsp_dai, 1); + if (ret) { + pr_alert("snd_soc_register_component failed\n"); + return ret; + } + + return 0; +} + +static void dacplusdsp_platform_remove(struct platform_device *pdev) +{ + snd_soc_unregister_component(&pdev->dev); +} + +static struct platform_driver dacplusdsp_driver = { + .driver = { + .name = "hifiberry-dacplusdsp-codec", + .of_match_table = of_match_ptr(dacplusdsp_ids), + }, + .probe = dacplusdsp_platform_probe, + .remove = dacplusdsp_platform_remove, +}; + +module_platform_driver(dacplusdsp_driver); + +MODULE_AUTHOR("Joerg Schambacher <joerg@i2audio.com>"); +MODULE_DESCRIPTION("ASoC Driver for HiFiBerry DAC+DSP"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/hifiberry_dacplushd.c b/sound/soc/bcm/hifiberry_dacplushd.c new file mode 100644 index 00000000000000..4a5f7e4f95d982 --- /dev/null +++ b/sound/soc/bcm/hifiberry_dacplushd.c @@ -0,0 +1,233 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ASoC Driver for HiFiBerry DAC+ HD + * + * Author: Joerg Schambacher, i2Audio GmbH for HiFiBerry + * Copyright 2020 + * + * 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 <linux/module.h> +#include <linux/platform_device.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/gpio/consumer.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <linux/i2c.h> +#include <linux/clk.h> + +#include "../codecs/pcm179x.h" + +#define DEFAULT_RATE 44100 + +struct brd_drv_data { + struct regmap *regmap; + struct clk *sclk; +}; + +static struct brd_drv_data drvdata; +static struct gpio_desc *reset_gpio; +static const unsigned int hb_dacplushd_rates[] = { + 192000, 96000, 48000, 176400, 88200, 44100, +}; + +static struct snd_pcm_hw_constraint_list hb_dacplushd_constraints = { + .list = hb_dacplushd_rates, + .count = ARRAY_SIZE(hb_dacplushd_rates), +}; + +static int snd_rpi_hb_dacplushd_startup(struct snd_pcm_substream *substream) +{ + /* constraints for standard sample rates */ + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &hb_dacplushd_constraints); + return 0; +} + +static void snd_rpi_hifiberry_dacplushd_set_sclk( + struct snd_soc_component *component, + int sample_rate) +{ + if (!IS_ERR(drvdata.sclk)) + clk_set_rate(drvdata.sclk, sample_rate); +} + +static int snd_rpi_hifiberry_dacplushd_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai_link *dai = rtd->dai_link; + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + + dai->name = "HiFiBerry DAC+ HD"; + dai->stream_name = "HiFiBerry DAC+ HD HiFi"; + dai->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM; + + /* allow only fixed 32 clock counts per channel */ + snd_soc_dai_set_bclk_ratio(cpu_dai, 32*2); + + return 0; +} + +static int snd_rpi_hifiberry_dacplushd_hw_params( + struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) +{ + int ret = 0; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + + snd_rpi_hifiberry_dacplushd_set_sclk(component, params_rate(params)); + return ret; +} + +/* machine stream operations */ +static struct snd_soc_ops snd_rpi_hifiberry_dacplushd_ops = { + .startup = snd_rpi_hb_dacplushd_startup, + .hw_params = snd_rpi_hifiberry_dacplushd_hw_params, +}; + +SND_SOC_DAILINK_DEFS(hifi, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("pcm179x.1-004c", "pcm179x-hifi")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0"))); + + +static struct snd_soc_dai_link snd_rpi_hifiberry_dacplushd_dai[] = { +{ + .name = "HiFiBerry DAC+ HD", + .stream_name = "HiFiBerry DAC+ HD HiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &snd_rpi_hifiberry_dacplushd_ops, + .init = snd_rpi_hifiberry_dacplushd_init, + SND_SOC_DAILINK_REG(hifi), +}, +}; + +/* audio machine driver */ +static struct snd_soc_card snd_rpi_hifiberry_dacplushd = { + .name = "snd_rpi_hifiberry_dacplushd", + .driver_name = "HifiberryDacplusHD", + .owner = THIS_MODULE, + .dai_link = snd_rpi_hifiberry_dacplushd_dai, + .num_links = ARRAY_SIZE(snd_rpi_hifiberry_dacplushd_dai), +}; + +static int snd_rpi_hifiberry_dacplushd_probe(struct platform_device *pdev) +{ + int ret = 0; + static int dac_reset_done; + struct device *dev = &pdev->dev; + struct device_node *dev_node = dev->of_node; + + snd_rpi_hifiberry_dacplushd.dev = &pdev->dev; + + /* get GPIO and release DAC from RESET */ + if (!dac_reset_done) { + reset_gpio = gpiod_get(&pdev->dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(reset_gpio)) { + dev_err(&pdev->dev, "gpiod_get() failed\n"); + return -EINVAL; + } + dac_reset_done = 1; + } + if (!IS_ERR(reset_gpio)) + gpiod_set_value(reset_gpio, 0); + msleep(1); + if (!IS_ERR(reset_gpio)) + gpiod_set_value(reset_gpio, 1); + msleep(1); + if (!IS_ERR(reset_gpio)) + gpiod_set_value(reset_gpio, 0); + + if (pdev->dev.of_node) { + struct device_node *i2s_node; + struct snd_soc_dai_link *dai; + + dai = &snd_rpi_hifiberry_dacplushd_dai[0]; + i2s_node = of_parse_phandle(pdev->dev.of_node, + "i2s-controller", 0); + + if (i2s_node) { + dai->cpus->of_node = i2s_node; + dai->platforms->of_node = i2s_node; + dai->cpus->dai_name = NULL; + dai->platforms->name = NULL; + } else { + return -EPROBE_DEFER; + } + + } + + ret = devm_snd_soc_register_card(&pdev->dev, + &snd_rpi_hifiberry_dacplushd); + if (ret && ret != -EPROBE_DEFER) { + dev_err(&pdev->dev, + "snd_soc_register_card() failed: %d\n", ret); + return ret; + } + if (ret == -EPROBE_DEFER) + return ret; + + dev_set_drvdata(dev, &drvdata); + if (dev_node == NULL) { + dev_err(&pdev->dev, "Device tree node not found\n"); + return -ENODEV; + } + + drvdata.sclk = devm_clk_get(dev, NULL); + if (IS_ERR(drvdata.sclk)) { + drvdata.sclk = ERR_PTR(-ENOENT); + return -ENODEV; + } + + clk_set_rate(drvdata.sclk, DEFAULT_RATE); + + return ret; +} + +static void snd_rpi_hifiberry_dacplushd_remove(struct platform_device *pdev) +{ + /* put DAC into RESET and release GPIO */ + gpiod_set_value(reset_gpio, 0); + gpiod_put(reset_gpio); +} + +static const struct of_device_id snd_rpi_hifiberry_dacplushd_of_match[] = { + { .compatible = "hifiberry,hifiberry-dacplushd", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, snd_rpi_hifiberry_dacplushd_of_match); + +static struct platform_driver snd_rpi_hifiberry_dacplushd_driver = { + .driver = { + .name = "snd-rpi-hifiberry-dacplushd", + .owner = THIS_MODULE, + .of_match_table = snd_rpi_hifiberry_dacplushd_of_match, + }, + .probe = snd_rpi_hifiberry_dacplushd_probe, + .remove = snd_rpi_hifiberry_dacplushd_remove, +}; + +module_platform_driver(snd_rpi_hifiberry_dacplushd_driver); + +MODULE_AUTHOR("Joerg Schambacher <joerg@i2audio.com>"); +MODULE_DESCRIPTION("ASoC Driver for HiFiBerry DAC+ HD"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/i-sabre-q2m.c b/sound/soc/bcm/i-sabre-q2m.c new file mode 100644 index 00000000000000..6099651f53ee4c --- /dev/null +++ b/sound/soc/bcm/i-sabre-q2m.c @@ -0,0 +1,159 @@ +/* + * ASoC Driver for I-Sabre Q2M + * + * Author: Satoru Kawase + * Modified by: Xiao Qingyong + * Update kernel v4.18+ by : Audiophonics + * Copyright 2018 Audiophonics + * + * 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 <linux/kernel.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/fs.h> +#include <asm/uaccess.h> +#include <sound/core.h> +#include <sound/soc.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> + +#include "../codecs/i-sabre-codec.h" + + +static int snd_rpi_i_sabre_q2m_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + unsigned int value; + + /* Device ID */ + value = snd_soc_component_read(component, ISABRECODEC_REG_01); + dev_info(component->card->dev, "Audiophonics Device ID : %02X\n", value); + + /* API revision */ + value = snd_soc_component_read(component, ISABRECODEC_REG_02); + dev_info(component->card->dev, "Audiophonics API revision : %02X\n", value); + + return 0; +} + +static int snd_rpi_i_sabre_q2m_hw_params( + struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + int bclk_ratio; + + /* Using powers of 2 allows for an integer clock divisor */ + bclk_ratio = (snd_pcm_format_width(params_format(params)) <= 16 ? 16 : 32) * + params_channels(params); + return snd_soc_dai_set_bclk_ratio(cpu_dai, bclk_ratio); +} + +/* machine stream operations */ +static struct snd_soc_ops snd_rpi_i_sabre_q2m_ops = { + .hw_params = snd_rpi_i_sabre_q2m_hw_params, +}; + +SND_SOC_DAILINK_DEFS(rpi_i_sabre_q2m, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("i-sabre-codec-i2c.1-0048", "i-sabre-codec-dai")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0"))); + +static struct snd_soc_dai_link snd_rpi_i_sabre_q2m_dai[] = { + { + .name = "I-Sabre Q2M", + .stream_name = "I-Sabre Q2M DAC", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBS_CFS, + .init = snd_rpi_i_sabre_q2m_init, + .ops = &snd_rpi_i_sabre_q2m_ops, + SND_SOC_DAILINK_REG(rpi_i_sabre_q2m), + } +}; + +/* audio machine driver */ +static struct snd_soc_card snd_rpi_i_sabre_q2m = { + .name = "I-Sabre Q2M DAC", + .owner = THIS_MODULE, + .dai_link = snd_rpi_i_sabre_q2m_dai, + .num_links = ARRAY_SIZE(snd_rpi_i_sabre_q2m_dai) +}; + + +static int snd_rpi_i_sabre_q2m_probe(struct platform_device *pdev) +{ + int ret = 0; + + snd_rpi_i_sabre_q2m.dev = &pdev->dev; + if (pdev->dev.of_node) { + struct device_node *i2s_node; + struct snd_soc_dai_link *dai; + + dai = &snd_rpi_i_sabre_q2m_dai[0]; + i2s_node = of_parse_phandle(pdev->dev.of_node, + "i2s-controller", 0); + if (i2s_node) { + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + } else { + dev_err(&pdev->dev, + "Property 'i2s-controller' missing or invalid\n"); + return (-EINVAL); + } + + dai->name = "I-Sabre Q2M"; + dai->stream_name = "I-Sabre Q2M DAC"; + dai->dai_fmt = SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBS_CFS; + } + + /* Wait for registering codec driver */ + mdelay(50); + + ret = snd_soc_register_card(&snd_rpi_i_sabre_q2m); + if (ret) { + dev_err(&pdev->dev, + "snd_soc_register_card() failed: %d\n", ret); + } + + return ret; +} + +static void snd_rpi_i_sabre_q2m_remove(struct platform_device *pdev) +{ + snd_soc_unregister_card(&snd_rpi_i_sabre_q2m); +} + +static const struct of_device_id snd_rpi_i_sabre_q2m_of_match[] = { + { .compatible = "audiophonics,i-sabre-q2m", }, + {} +}; +MODULE_DEVICE_TABLE(of, snd_rpi_i_sabre_q2m_of_match); + +static struct platform_driver snd_rpi_i_sabre_q2m_driver = { + .driver = { + .name = "snd-rpi-i-sabre-q2m", + .owner = THIS_MODULE, + .of_match_table = snd_rpi_i_sabre_q2m_of_match, + }, + .probe = snd_rpi_i_sabre_q2m_probe, + .remove = snd_rpi_i_sabre_q2m_remove, +}; +module_platform_driver(snd_rpi_i_sabre_q2m_driver); + +MODULE_DESCRIPTION("ASoC Driver for I-Sabre Q2M"); +MODULE_AUTHOR("Audiophonics <http://www.audiophonics.fr>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/bcm/iqaudio-codec.c b/sound/soc/bcm/iqaudio-codec.c new file mode 100644 index 00000000000000..8eafd795d454d9 --- /dev/null +++ b/sound/soc/bcm/iqaudio-codec.c @@ -0,0 +1,283 @@ +/* + * ASoC Driver for IQaudIO Raspberry Pi Codec board + * + * Author: Gordon Garrity <gordon@iqaudio.com> + * (C) Copyright IQaudio Limited, 2017-2019 + * + * 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 <linux/module.h> +#include <linux/gpio/consumer.h> +#include <linux/platform_device.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/jack.h> + +#include <linux/acpi.h> +#include <linux/slab.h> +#include "../codecs/da7213.h" + +static int pll_out = DA7213_PLL_FREQ_OUT_90316800; + +static int snd_rpi_iqaudio_pll_control(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + int ret = 0; + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct snd_soc_pcm_runtime *rtd = + snd_soc_get_pcm_runtime(card, &card->dai_link[0]); + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + + if (SND_SOC_DAPM_EVENT_OFF(event)) { + ret = snd_soc_dai_set_pll(codec_dai, 0, DA7213_SYSCLK_MCLK, 0, + 0); + if (ret) + dev_err(card->dev, "Failed to bypass PLL: %d\n", ret); + /* Allow PLL time to bypass */ + msleep(100); + } else if (SND_SOC_DAPM_EVENT_ON(event)) { + ret = snd_soc_dai_set_pll(codec_dai, 0, DA7213_SYSCLK_PLL, 0, + pll_out); + if (ret) + dev_err(card->dev, "Failed to enable PLL: %d\n", ret); + /* Allow PLL time to lock */ + msleep(100); + } + + return ret; +} + +static int snd_rpi_iqaudio_post_dapm_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + switch (event) { + case SND_SOC_DAPM_POST_PMU: + /* Delay for mic bias ramp */ + msleep(1000); + break; + default: + break; + } + + return 0; +} + +static const struct snd_kcontrol_new dapm_controls[] = { + SOC_DAPM_PIN_SWITCH("HP Jack"), + SOC_DAPM_PIN_SWITCH("MIC Jack"), + SOC_DAPM_PIN_SWITCH("Onboard MIC"), + SOC_DAPM_PIN_SWITCH("AUX Jack"), +}; + +static const struct snd_soc_dapm_widget dapm_widgets[] = { + SND_SOC_DAPM_HP("HP Jack", NULL), + SND_SOC_DAPM_MIC("MIC Jack", NULL), + SND_SOC_DAPM_MIC("Onboard MIC", NULL), + SND_SOC_DAPM_LINE("AUX Jack", NULL), + SND_SOC_DAPM_SUPPLY("PLL Control", SND_SOC_NOPM, 0, 0, + snd_rpi_iqaudio_pll_control, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_POST("Post Power Up Event", snd_rpi_iqaudio_post_dapm_event), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + {"HP Jack", NULL, "HPL"}, + {"HP Jack", NULL, "HPR"}, + {"HP Jack", NULL, "PLL Control"}, + + {"AUXR", NULL, "AUX Jack"}, + {"AUXL", NULL, "AUX Jack"}, + {"AUX Jack", NULL, "PLL Control"}, + + /* Assume Mic1 is linked to Headset and Mic2 to on-board mic */ + {"MIC1", NULL, "MIC Jack"}, + {"MIC Jack", NULL, "PLL Control"}, + {"MIC2", NULL, "Onboard MIC"}, + {"Onboard MIC", NULL, "PLL Control"}, +}; + +/* machine stream operations */ + +static int snd_rpi_iqaudio_codec_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + int ret; + + /* + * Disable AUX Jack Pin by default to prevent PLL being enabled at + * startup. This avoids holding the PLL to a fixed SR config for + * subsequent streams. + * + * This pin can still be enabled later, as required by user-space. + */ + snd_soc_dapm_disable_pin(&rtd->card->dapm, "AUX Jack"); + snd_soc_dapm_sync(&rtd->card->dapm); + + /* Impose BCLK ratios otherwise the codec may cheat */ + ret = snd_soc_dai_set_bclk_ratio(cpu_dai, 64); + if (ret) { + dev_err(rtd->dev, "Failed to set CPU BLCK ratio\n"); + return ret; + } + + ret = snd_soc_dai_set_bclk_ratio(codec_dai, 64); + if (ret) { + dev_err(rtd->dev, "Failed to set codec BCLK ratio\n"); + return ret; + } + + /* Set MCLK frequency to codec, onboard 11.2896MHz clock */ + return snd_soc_dai_set_sysclk(codec_dai, DA7213_CLKSRC_MCLK, 11289600, + SND_SOC_CLOCK_OUT); +} + +static int snd_rpi_iqaudio_codec_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + unsigned int samplerate = params_rate(params); + + switch (samplerate) { + case 8000: + case 16000: + case 32000: + case 48000: + case 96000: + pll_out = DA7213_PLL_FREQ_OUT_98304000; + break; + case 44100: + case 88200: + pll_out = DA7213_PLL_FREQ_OUT_90316800; + break; + default: + dev_err(rtd->dev,"Unsupported samplerate %d\n", samplerate); + return -EINVAL; + } + + return snd_soc_dai_set_pll(codec_dai, 0, DA7213_SYSCLK_PLL, 0, pll_out); +} + +static const struct snd_soc_ops snd_rpi_iqaudio_codec_ops = { + .hw_params = snd_rpi_iqaudio_codec_hw_params, +}; + +SND_SOC_DAILINK_DEFS(rpi_iqaudio, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("da7213.1-001a", "da7213-hifi")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2835-i2s.0"))); + +static struct snd_soc_dai_link snd_rpi_iqaudio_codec_dai[] = { +{ + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM, + .init = snd_rpi_iqaudio_codec_init, + .ops = &snd_rpi_iqaudio_codec_ops, + .symmetric_rate = 1, + .symmetric_channels = 1, + .symmetric_sample_bits = 1, + SND_SOC_DAILINK_REG(rpi_iqaudio), +}, +}; + +/* audio machine driver */ +static struct snd_soc_card snd_rpi_iqaudio_codec = { + .owner = THIS_MODULE, + .dai_link = snd_rpi_iqaudio_codec_dai, + .num_links = ARRAY_SIZE(snd_rpi_iqaudio_codec_dai), + .controls = dapm_controls, + .num_controls = ARRAY_SIZE(dapm_controls), + .dapm_widgets = dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(dapm_widgets), + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), +}; + +static int snd_rpi_iqaudio_codec_probe(struct platform_device *pdev) +{ + int ret = 0; + + snd_rpi_iqaudio_codec.dev = &pdev->dev; + + if (pdev->dev.of_node) { + struct device_node *i2s_node; + struct snd_soc_card *card = &snd_rpi_iqaudio_codec; + struct snd_soc_dai_link *dai = &snd_rpi_iqaudio_codec_dai[0]; + + i2s_node = of_parse_phandle(pdev->dev.of_node, + "i2s-controller", 0); + if (i2s_node) { + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + } + + if (of_property_read_string(pdev->dev.of_node, "card_name", + &card->name)) + card->name = "IQaudIOCODEC"; + + if (of_property_read_string(pdev->dev.of_node, "dai_name", + &dai->name)) + dai->name = "IQaudIO CODEC"; + + if (of_property_read_string(pdev->dev.of_node, + "dai_stream_name", &dai->stream_name)) + dai->stream_name = "IQaudIO CODEC HiFi v1.2"; + + } + + ret = snd_soc_register_card(&snd_rpi_iqaudio_codec); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(&pdev->dev, + "snd_soc_register_card() failed: %d\n", ret); + return ret; + } + + return 0; +} + +static void snd_rpi_iqaudio_codec_remove(struct platform_device *pdev) +{ + snd_soc_unregister_card(&snd_rpi_iqaudio_codec); +} + +static const struct of_device_id iqaudio_of_match[] = { + { .compatible = "iqaudio,iqaudio-codec", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, iqaudio_of_match); + +static struct platform_driver snd_rpi_iqaudio_codec_driver = { + .driver = { + .name = "snd-rpi-iqaudio-codec", + .owner = THIS_MODULE, + .of_match_table = iqaudio_of_match, + }, + .probe = snd_rpi_iqaudio_codec_probe, + .remove = snd_rpi_iqaudio_codec_remove, +}; + + + +module_platform_driver(snd_rpi_iqaudio_codec_driver); + +MODULE_AUTHOR("Gordon Garrity <gordon@iqaudio.com>"); +MODULE_DESCRIPTION("ASoC Driver for IQaudIO CODEC"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/iqaudio-dac.c b/sound/soc/bcm/iqaudio-dac.c new file mode 100644 index 00000000000000..f0680af30b741b --- /dev/null +++ b/sound/soc/bcm/iqaudio-dac.c @@ -0,0 +1,223 @@ +/* + * ASoC Driver for IQaudIO DAC + * + * Author: Florian Meier <florian.meier@koalo.de> + * Copyright 2013 + * + * 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 <linux/module.h> +#include <linux/gpio/consumer.h> +#include <linux/platform_device.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/jack.h> + +static bool digital_gain_0db_limit = true; + +static struct gpio_desc *mute_gpio; + +static int snd_rpi_iqaudio_dac_init(struct snd_soc_pcm_runtime *rtd) +{ + if (digital_gain_0db_limit) + { + int ret; + struct snd_soc_card *card = rtd->card; + + ret = snd_soc_limit_volume(card, "Digital Playback Volume", 207); + if (ret < 0) + dev_warn(card->dev, "Failed to set volume limit: %d\n", ret); + } + + return 0; +} + +static void snd_rpi_iqaudio_gpio_mute(struct snd_soc_card *card) +{ + if (mute_gpio) { + dev_info(card->dev, "%s: muting amp using GPIO22\n", + __func__); + gpiod_set_value_cansleep(mute_gpio, 0); + } +} + +static void snd_rpi_iqaudio_gpio_unmute(struct snd_soc_card *card) +{ + if (mute_gpio) { + dev_info(card->dev, "%s: un-muting amp using GPIO22\n", + __func__); + gpiod_set_value_cansleep(mute_gpio, 1); + } +} + +static int snd_rpi_iqaudio_set_bias_level(struct snd_soc_card *card, + struct snd_soc_dapm_context *dapm, enum snd_soc_bias_level level) +{ + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_dai *codec_dai; + + rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]); + codec_dai = snd_soc_rtd_to_codec(rtd, 0); + + if (dapm->dev != codec_dai->dev) + return 0; + + switch (level) { + case SND_SOC_BIAS_PREPARE: + if (dapm->bias_level != SND_SOC_BIAS_STANDBY) + break; + + /* UNMUTE AMP */ + snd_rpi_iqaudio_gpio_unmute(card); + + break; + case SND_SOC_BIAS_STANDBY: + if (dapm->bias_level != SND_SOC_BIAS_PREPARE) + break; + + /* MUTE AMP */ + snd_rpi_iqaudio_gpio_mute(card); + + break; + default: + break; + } + + return 0; +} + +SND_SOC_DAILINK_DEFS(hifi, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004c", "pcm512x-hifi")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0"))); + +static struct snd_soc_dai_link snd_rpi_iqaudio_dac_dai[] = { +{ + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .init = snd_rpi_iqaudio_dac_init, + SND_SOC_DAILINK_REG(hifi), +}, +}; + +/* audio machine driver */ +static struct snd_soc_card snd_rpi_iqaudio_dac = { + .owner = THIS_MODULE, + .dai_link = snd_rpi_iqaudio_dac_dai, + .num_links = ARRAY_SIZE(snd_rpi_iqaudio_dac_dai), +}; + +static int snd_rpi_iqaudio_dac_probe(struct platform_device *pdev) +{ + int ret = 0; + bool gpio_unmute = false; + + snd_rpi_iqaudio_dac.dev = &pdev->dev; + + if (pdev->dev.of_node) { + struct device_node *i2s_node; + struct snd_soc_card *card = &snd_rpi_iqaudio_dac; + struct snd_soc_dai_link *dai = &snd_rpi_iqaudio_dac_dai[0]; + bool auto_gpio_mute = false; + + i2s_node = of_parse_phandle(pdev->dev.of_node, + "i2s-controller", 0); + if (i2s_node) { + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + } + + digital_gain_0db_limit = !of_property_read_bool( + pdev->dev.of_node, "iqaudio,24db_digital_gain"); + + if (of_property_read_string(pdev->dev.of_node, "card_name", + &card->name)) + card->name = "IQaudIODAC"; + + if (of_property_read_string(pdev->dev.of_node, "dai_name", + &dai->name)) + dai->name = "IQaudIO DAC"; + + if (of_property_read_string(pdev->dev.of_node, + "dai_stream_name", &dai->stream_name)) + dai->stream_name = "IQaudIO DAC HiFi"; + + /* gpio_unmute - one time unmute amp using GPIO */ + gpio_unmute = of_property_read_bool(pdev->dev.of_node, + "iqaudio-dac,unmute-amp"); + + /* auto_gpio_mute - mute/unmute amp using GPIO */ + auto_gpio_mute = of_property_read_bool(pdev->dev.of_node, + "iqaudio-dac,auto-mute-amp"); + + if (auto_gpio_mute || gpio_unmute) { + mute_gpio = devm_gpiod_get_optional(&pdev->dev, "mute", + GPIOD_OUT_LOW); + if (IS_ERR(mute_gpio)) { + ret = PTR_ERR(mute_gpio); + dev_err(&pdev->dev, + "Failed to get mute gpio: %d\n", ret); + return ret; + } + + if (auto_gpio_mute && mute_gpio) + snd_rpi_iqaudio_dac.set_bias_level = + snd_rpi_iqaudio_set_bias_level; + } + } + + ret = snd_soc_register_card(&snd_rpi_iqaudio_dac); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(&pdev->dev, + "snd_soc_register_card() failed: %d\n", ret); + return ret; + } + + if (gpio_unmute && mute_gpio) + snd_rpi_iqaudio_gpio_unmute(&snd_rpi_iqaudio_dac); + + return 0; +} + +static void snd_rpi_iqaudio_dac_remove(struct platform_device *pdev) +{ + snd_rpi_iqaudio_gpio_mute(&snd_rpi_iqaudio_dac); + + snd_soc_unregister_card(&snd_rpi_iqaudio_dac); +} + +static const struct of_device_id iqaudio_of_match[] = { + { .compatible = "iqaudio,iqaudio-dac", }, + {}, +}; +MODULE_DEVICE_TABLE(of, iqaudio_of_match); + +static struct platform_driver snd_rpi_iqaudio_dac_driver = { + .driver = { + .name = "snd-rpi-iqaudio-dac", + .owner = THIS_MODULE, + .of_match_table = iqaudio_of_match, + }, + .probe = snd_rpi_iqaudio_dac_probe, + .remove = snd_rpi_iqaudio_dac_remove, +}; + +module_platform_driver(snd_rpi_iqaudio_dac_driver); + +MODULE_AUTHOR("Florian Meier <florian.meier@koalo.de>"); +MODULE_DESCRIPTION("ASoC Driver for IQAudio DAC"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/justboom-both.c b/sound/soc/bcm/justboom-both.c new file mode 100644 index 00000000000000..835217ffb0ad0a --- /dev/null +++ b/sound/soc/bcm/justboom-both.c @@ -0,0 +1,266 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * rpi--wm8804.c -- ALSA SoC Raspberry Pi soundcard. + * + * Authors: Johannes Krude <johannes@krude.de + * + * Driver for when connecting simultaneously justboom-digi and justboom-dac + * + * Based upon code from: + * justboom-digi.c + * by Milan Neskovic <info@justboom.co> + * justboom-dac.c + * by Milan Neskovic <info@justboom.co> + * + * 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 <linux/module.h> +#include <linux/platform_device.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/jack.h> + +#include "../codecs/wm8804.h" +#include "../codecs/pcm512x.h" + + +static bool digital_gain_0db_limit = true; + +static int snd_rpi_justboom_both_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *digi = snd_soc_rtd_to_codec(rtd, 0)->component; + struct snd_soc_component *dac = snd_soc_rtd_to_codec(rtd, 1)->component; + + /* enable TX output */ + snd_soc_component_update_bits(digi, WM8804_PWRDN, 0x4, 0x0); + + snd_soc_component_update_bits(dac, PCM512x_GPIO_EN, 0x08, 0x08); + snd_soc_component_update_bits(dac, PCM512x_GPIO_OUTPUT_4, 0xf, 0x02); + snd_soc_component_update_bits(dac, PCM512x_GPIO_CONTROL_1, 0x08, 0x08); + + if (digital_gain_0db_limit) { + int ret; + struct snd_soc_card *card = rtd->card; + + ret = snd_soc_limit_volume(card, "Digital Playback Volume", + 207); + if (ret < 0) + dev_warn(card->dev, "Failed to set volume limit: %d\n", + ret); + } + + return 0; +} + +static int snd_rpi_justboom_both_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + struct snd_soc_component *digi = snd_soc_rtd_to_codec(rtd, 0)->component; + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + + int sysclk = 27000000; /* This is fixed on this board */ + + long mclk_freq = 0; + int mclk_div = 1; + int sampling_freq = 1; + + int ret; + + int samplerate = params_rate(params); + + if (samplerate <= 96000) { + mclk_freq = samplerate*256; + mclk_div = WM8804_MCLKDIV_256FS; + } else { + mclk_freq = samplerate*128; + mclk_div = WM8804_MCLKDIV_128FS; + } + + switch (samplerate) { + case 32000: + sampling_freq = 0x03; + break; + case 44100: + sampling_freq = 0x00; + break; + case 48000: + sampling_freq = 0x02; + break; + case 88200: + sampling_freq = 0x08; + break; + case 96000: + sampling_freq = 0x0a; + break; + case 176400: + sampling_freq = 0x0c; + break; + case 192000: + sampling_freq = 0x0e; + break; + default: + dev_err(rtd->card->dev, + "Failed to set WM8804 SYSCLK, unsupported samplerate %d\n", + samplerate); + } + + snd_soc_dai_set_clkdiv(codec_dai, WM8804_MCLK_DIV, mclk_div); + snd_soc_dai_set_pll(codec_dai, 0, 0, sysclk, mclk_freq); + + ret = snd_soc_dai_set_sysclk(codec_dai, WM8804_TX_CLKSRC_PLL, + sysclk, SND_SOC_CLOCK_OUT); + if (ret < 0) { + dev_err(rtd->card->dev, + "Failed to set WM8804 SYSCLK: %d\n", ret); + return ret; + } + + /* Enable TX output */ + snd_soc_component_update_bits(digi, WM8804_PWRDN, 0x4, 0x0); + + /* Power on */ + snd_soc_component_update_bits(digi, WM8804_PWRDN, 0x9, 0); + + /* set sampling frequency status bits */ + snd_soc_component_update_bits(digi, WM8804_SPDTX4, 0x0f, sampling_freq); + + return snd_soc_dai_set_bclk_ratio(cpu_dai, 64); +} + +static int snd_rpi_justboom_both_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *digi = snd_soc_rtd_to_codec(rtd, 0)->component; + struct snd_soc_component *dac = snd_soc_rtd_to_codec(rtd, 1)->component; + + /* turn on digital output */ + snd_soc_component_update_bits(digi, WM8804_PWRDN, 0x3c, 0x00); + + snd_soc_component_update_bits(dac, PCM512x_GPIO_CONTROL_1, 0x08, 0x08); + + return 0; +} + +static void snd_rpi_justboom_both_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *digi = snd_soc_rtd_to_codec(rtd, 0)->component; + struct snd_soc_component *dac = snd_soc_rtd_to_codec(rtd, 1)->component; + + snd_soc_component_update_bits(dac, PCM512x_GPIO_CONTROL_1, 0x08, 0x00); + + /* turn off output */ + snd_soc_component_update_bits(digi, WM8804_PWRDN, 0x3c, 0x3c); +} + +/* machine stream operations */ +static struct snd_soc_ops snd_rpi_justboom_both_ops = { + .hw_params = snd_rpi_justboom_both_hw_params, + .startup = snd_rpi_justboom_both_startup, + .shutdown = snd_rpi_justboom_both_shutdown, +}; + +SND_SOC_DAILINK_DEFS(rpi_justboom_both, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004d", "pcm512x-hifi"), + COMP_CODEC("wm8804.1-003b", "wm8804-spdif")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0"))); + +static struct snd_soc_dai_link snd_rpi_justboom_both_dai[] = { +{ + .name = "JustBoom Digi", + .stream_name = "JustBoom Digi HiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM, + .ops = &snd_rpi_justboom_both_ops, + .init = snd_rpi_justboom_both_init, + SND_SOC_DAILINK_REG(rpi_justboom_both), +}, +}; + +/* audio machine driver */ +static struct snd_soc_card snd_rpi_justboom_both = { + .name = "snd_rpi_justboom_both", + .driver_name = "JustBoomBoth", + .owner = THIS_MODULE, + .dai_link = snd_rpi_justboom_both_dai, + .num_links = ARRAY_SIZE(snd_rpi_justboom_both_dai), +}; + +static int snd_rpi_justboom_both_probe(struct platform_device *pdev) +{ + int ret = 0; + struct snd_soc_card *card = &snd_rpi_justboom_both; + + snd_rpi_justboom_both.dev = &pdev->dev; + + if (pdev->dev.of_node) { + struct device_node *i2s_node; + struct snd_soc_dai_link *dai = &snd_rpi_justboom_both_dai[0]; + + i2s_node = of_parse_phandle(pdev->dev.of_node, + "i2s-controller", 0); + + if (i2s_node) { + int i; + + for (i = 0; i < card->num_links; i++) { + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + } + } + + digital_gain_0db_limit = !of_property_read_bool( + pdev->dev.of_node, "justboom,24db_digital_gain"); + } + + ret = snd_soc_register_card(card); + if (ret && ret != -EPROBE_DEFER) { + dev_err(&pdev->dev, + "snd_soc_register_card() failed: %d\n", ret); + } + + return ret; +} + +static void snd_rpi_justboom_both_remove(struct platform_device *pdev) +{ + snd_soc_unregister_card(&snd_rpi_justboom_both); +} + +static const struct of_device_id snd_rpi_justboom_both_of_match[] = { + { .compatible = "justboom,justboom-both", }, + {}, +}; +MODULE_DEVICE_TABLE(of, snd_rpi_justboom_both_of_match); + +static struct platform_driver snd_rpi_justboom_both_driver = { + .driver = { + .name = "snd-rpi-justboom-both", + .owner = THIS_MODULE, + .of_match_table = snd_rpi_justboom_both_of_match, + }, + .probe = snd_rpi_justboom_both_probe, + .remove = snd_rpi_justboom_both_remove, +}; + +module_platform_driver(snd_rpi_justboom_both_driver); + +MODULE_AUTHOR("Johannes Krude <johannes@krude.de>"); +MODULE_DESCRIPTION("ASoC Driver for simultaneous use of JustBoom PI Digi & DAC HAT Sound Cards"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/justboom-dac.c b/sound/soc/bcm/justboom-dac.c new file mode 100644 index 00000000000000..e3b258a3518dd0 --- /dev/null +++ b/sound/soc/bcm/justboom-dac.c @@ -0,0 +1,147 @@ +/* + * ASoC Driver for JustBoom DAC Raspberry Pi HAT Sound Card + * + * Author: Milan Neskovic + * Copyright 2016 + * based on code by Daniel Matuschek <info@crazy-audio.com> + * based on code by Florian Meier <florian.meier@koalo.de> + * + * 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 <linux/module.h> +#include <linux/platform_device.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/jack.h> + +#include "../codecs/pcm512x.h" + +static bool digital_gain_0db_limit = true; + +static int snd_rpi_justboom_dac_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + snd_soc_component_update_bits(component, PCM512x_GPIO_EN, 0x08, 0x08); + snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_4, 0xf, 0x02); + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08,0x08); + + if (digital_gain_0db_limit) + { + int ret; + struct snd_soc_card *card = rtd->card; + + ret = snd_soc_limit_volume(card, "Digital Playback Volume", 207); + if (ret < 0) + dev_warn(card->dev, "Failed to set volume limit: %d\n", ret); + } + + return 0; +} + +static int snd_rpi_justboom_dac_startup(struct snd_pcm_substream *substream) { + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08,0x08); + return 0; +} + +static void snd_rpi_justboom_dac_shutdown(struct snd_pcm_substream *substream) { + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08,0x00); +} + +/* machine stream operations */ +static struct snd_soc_ops snd_rpi_justboom_dac_ops = { + .startup = snd_rpi_justboom_dac_startup, + .shutdown = snd_rpi_justboom_dac_shutdown, +}; + +SND_SOC_DAILINK_DEFS(hifi, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004d", "pcm512x-hifi")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0"))); + +static struct snd_soc_dai_link snd_rpi_justboom_dac_dai[] = { +{ + .name = "JustBoom DAC", + .stream_name = "JustBoom DAC HiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &snd_rpi_justboom_dac_ops, + .init = snd_rpi_justboom_dac_init, + SND_SOC_DAILINK_REG(hifi), +}, +}; + +/* audio machine driver */ +static struct snd_soc_card snd_rpi_justboom_dac = { + .name = "snd_rpi_justboom_dac", + .driver_name = "JustBoomDac", + .owner = THIS_MODULE, + .dai_link = snd_rpi_justboom_dac_dai, + .num_links = ARRAY_SIZE(snd_rpi_justboom_dac_dai), +}; + +static int snd_rpi_justboom_dac_probe(struct platform_device *pdev) +{ + int ret = 0; + + snd_rpi_justboom_dac.dev = &pdev->dev; + + if (pdev->dev.of_node) { + struct device_node *i2s_node; + struct snd_soc_dai_link *dai = &snd_rpi_justboom_dac_dai[0]; + i2s_node = of_parse_phandle(pdev->dev.of_node, + "i2s-controller", 0); + + if (i2s_node) { + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + } + + digital_gain_0db_limit = !of_property_read_bool( + pdev->dev.of_node, "justboom,24db_digital_gain"); + } + + ret = devm_snd_soc_register_card(&pdev->dev, &snd_rpi_justboom_dac); + if (ret && ret != -EPROBE_DEFER) + dev_err(&pdev->dev, + "snd_soc_register_card() failed: %d\n", ret); + + return ret; +} + +static const struct of_device_id snd_rpi_justboom_dac_of_match[] = { + { .compatible = "justboom,justboom-dac", }, + {}, +}; +MODULE_DEVICE_TABLE(of, snd_rpi_justboom_dac_of_match); + +static struct platform_driver snd_rpi_justboom_dac_driver = { + .driver = { + .name = "snd-rpi-justboom-dac", + .owner = THIS_MODULE, + .of_match_table = snd_rpi_justboom_dac_of_match, + }, + .probe = snd_rpi_justboom_dac_probe, +}; + +module_platform_driver(snd_rpi_justboom_dac_driver); + +MODULE_AUTHOR("Milan Neskovic <info@justboom.co>"); +MODULE_DESCRIPTION("ASoC Driver for JustBoom PI DAC HAT Sound Card"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/pifi-40.c b/sound/soc/bcm/pifi-40.c new file mode 100644 index 00000000000000..cfdc7c67358336 --- /dev/null +++ b/sound/soc/bcm/pifi-40.c @@ -0,0 +1,281 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ALSA ASoC Machine Driver for PiFi-40 + * + * Author: David Knell <david.knell@gmail.com) + * based on code by Daniel Matuschek <info@crazy-audio.com> + * based on code by Florian Meier <florian.meier@koalo.de> + * Copyright (C) 2020 + * + * 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 <linux/module.h> +#include <linux/platform_device.h> +#include <linux/gpio/consumer.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <linux/firmware.h> +#include <linux/delay.h> +#include <sound/tlv.h> + +static struct gpio_desc *pdn_gpio; +static int vol = 0x30; + +// Volume control +static int pifi_40_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = vol; + ucontrol->value.integer.value[1] = vol; + return 0; +} + +static int pifi_40_vol_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct snd_soc_pcm_runtime *rtd; + unsigned int v = ucontrol->value.integer.value[0]; + struct snd_soc_component *dac[2]; + + rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]); + dac[0] = snd_soc_rtd_to_codec(rtd, 0)->component; + dac[1] = snd_soc_rtd_to_codec(rtd, 1)->component; + + snd_soc_component_write(dac[0], 0x07, 255 - v); + snd_soc_component_write(dac[1], 0x07, 255 - v); + + vol = v; + return 1; +} + +static const DECLARE_TLV_DB_SCALE(digital_tlv_master, -10350, 50, 1); +static const struct snd_kcontrol_new pifi_40_controls[] = { + SOC_DOUBLE_R_EXT_TLV("Master Volume", 0x00, 0x01, + 0x00, // Min + 0xff, // Max + 0x01, // Invert + pifi_40_vol_get, pifi_40_vol_set, + digital_tlv_master) +}; + +static const char * const codec_ctl_pfx[] = { "Left", "Right" }; + +static const char * const codec_ctl_name[] = { "Master Volume", + "Speaker Volume", + "Speaker Switch" }; + +static int snd_pifi_40_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct snd_soc_component *dac[2]; + struct snd_kcontrol *kctl; + int i, j; + + dac[0] = snd_soc_rtd_to_codec(rtd, 0)->component; + dac[1] = snd_soc_rtd_to_codec(rtd, 1)->component; + + + // Set up cards - pulse power down first + gpiod_set_value_cansleep(pdn_gpio, 1); + usleep_range(1000, 10000); + gpiod_set_value_cansleep(pdn_gpio, 0); + usleep_range(20000, 30000); + + // Oscillator trim + snd_soc_component_write(dac[0], 0x1b, 0); + snd_soc_component_write(dac[1], 0x1b, 0); + usleep_range(60000, 80000); + + // Common setup + for (i = 0; i < 2; i++) { + // MCLK at 64fs, sample rate 44.1 or 48kHz + snd_soc_component_write(dac[i], 0x00, 0x60); + + // Set up for PBTL + snd_soc_component_write(dac[i], 0x19, 0x3A); + snd_soc_component_write(dac[i], 0x25, 0x01103245); + + // Master vol to -10db + snd_soc_component_write(dac[i], 0x07, 0x44); + } + // Inputs set to L and R respectively + snd_soc_component_write(dac[0], 0x20, 0x00017772); + snd_soc_component_write(dac[1], 0x20, 0x00107772); + + // Remove codec controls + for (i = 0; i < 2; i++) { + for (j = 0; j < 3; j++) { + char cname[256]; + + sprintf(cname, "%s %s", codec_ctl_pfx[i], + codec_ctl_name[j]); + kctl = snd_soc_card_get_kcontrol(card, cname); + if (!kctl) { + pr_info("Control %s not found\n", + cname); + } else { + kctl->vd[0].access = + SNDRV_CTL_ELEM_ACCESS_READWRITE; + snd_ctl_remove(card->snd_card, kctl); + } + } + } + + return 0; +} + +static int snd_pifi_40_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + + return snd_soc_dai_set_bclk_ratio(cpu_dai, 64); +} + +static struct snd_soc_ops snd_pifi_40_ops = { .hw_params = + snd_pifi_40_hw_params }; + +static struct snd_soc_dai_link_component pifi_40_codecs[] = { + { + .dai_name = "tas571x-hifi", + }, + { + .dai_name = "tas571x-hifi", + }, +}; + +SND_SOC_DAILINK_DEFS( + pifi_40_dai, DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC("tas571x.1-001a", "tas571x-hifi"), + COMP_CODEC("tas571x.1-001b", "tas571x-hifi")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link snd_pifi_40_dai[] = { + { + .name = "PiFi40", + .stream_name = "PiFi40", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &snd_pifi_40_ops, + .init = snd_pifi_40_init, + SND_SOC_DAILINK_REG(pifi_40_dai), + }, +}; + +// Machine driver +static struct snd_soc_card snd_pifi_40 = { + .name = "PiFi40", + .owner = THIS_MODULE, + .dai_link = snd_pifi_40_dai, + .num_links = ARRAY_SIZE(snd_pifi_40_dai), + .controls = pifi_40_controls, + .num_controls = ARRAY_SIZE(pifi_40_controls) +}; + +static void snd_pifi_40_pdn(struct snd_soc_card *card, int on) +{ + if (pdn_gpio) + gpiod_set_value_cansleep(pdn_gpio, on ? 0 : 1); +} + +static int snd_pifi_40_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &snd_pifi_40; + int ret = 0, i = 0; + + card->dev = &pdev->dev; + platform_set_drvdata(pdev, &snd_pifi_40); + + if (pdev->dev.of_node) { + struct device_node *i2s_node; + struct snd_soc_dai_link *dai; + + dai = &snd_pifi_40_dai[0]; + i2s_node = of_parse_phandle(pdev->dev.of_node, "i2s-controller", + 0); + if (i2s_node) { + for (i = 0; i < card->num_links; i++) { + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + } + } + + pifi_40_codecs[0].of_node = + of_parse_phandle(pdev->dev.of_node, "audio-codec", 0); + pifi_40_codecs[1].of_node = + of_parse_phandle(pdev->dev.of_node, "audio-codec", 1); + if (!pifi_40_codecs[0].of_node || !pifi_40_codecs[1].of_node) { + dev_err(&pdev->dev, + "Property 'audio-codec' missing or invalid\n"); + return -EINVAL; + } + + pdn_gpio = devm_gpiod_get_optional(&pdev->dev, "pdn", + GPIOD_OUT_LOW); + if (IS_ERR(pdn_gpio)) { + ret = PTR_ERR(pdn_gpio); + dev_err(&pdev->dev, "failed to get pdn gpio: %d\n", + ret); + return ret; + } + + ret = snd_soc_register_card(&snd_pifi_40); + if (ret < 0) { + dev_err(&pdev->dev, + "snd_soc_register_card() failed: %d\n", ret); + return ret; + } + + return 0; + } + + return -EINVAL; +} + +static void snd_pifi_40_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + + kfree(&card->drvdata); + snd_pifi_40_pdn(&snd_pifi_40, 0); + snd_soc_unregister_card(&snd_pifi_40); +} + +static const struct of_device_id snd_pifi_40_of_match[] = { + { + .compatible = "pifi,pifi-40", + }, + { /* sentinel */ }, +}; + +MODULE_DEVICE_TABLE(of, snd_pifi_40_of_match); + +static struct platform_driver snd_pifi_40_driver = { + .driver = { + .name = "snd-pifi-40", + .owner = THIS_MODULE, + .of_match_table = snd_pifi_40_of_match, + }, + .probe = snd_pifi_40_probe, + .remove = snd_pifi_40_remove, +}; + +module_platform_driver(snd_pifi_40_driver); + +MODULE_AUTHOR("David Knell <david.knell@gmail.com>"); +MODULE_DESCRIPTION("ALSA ASoC Machine Driver for PiFi-40"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/pisound.c b/sound/soc/bcm/pisound.c new file mode 100644 index 00000000000000..84396880e50273 --- /dev/null +++ b/sound/soc/bcm/pisound.c @@ -0,0 +1,1254 @@ +/* + * Pisound Linux kernel module. + * Copyright (C) 2016-2024 Vilniaus Blokas UAB, https://blokas.io/pisound + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 of the + * License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * 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., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/gpio.h> +#include <linux/kobject.h> +#include <linux/sysfs.h> +#include <linux/delay.h> +#include <linux/spi/spi.h> +#include <linux/interrupt.h> +#include <linux/kfifo.h> +#include <linux/jiffies.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include <sound/rawmidi.h> +#include <sound/asequencer.h> +#include <sound/control.h> + +static int pisnd_spi_init(struct device *dev); +static void pisnd_spi_uninit(void); + +static void pisnd_spi_flush(void); +static void pisnd_spi_start(void); +static uint8_t pisnd_spi_recv(uint8_t *buffer, uint8_t length); + +typedef void (*pisnd_spi_recv_cb)(void *data); +static void pisnd_spi_set_callback(pisnd_spi_recv_cb cb, void *data); + +static const char *pisnd_spi_get_serial(void); +static const char *pisnd_spi_get_id(void); +static const char *pisnd_spi_get_fw_version(void); +static const char *pisnd_spi_get_hw_version(void); + +static int pisnd_midi_init(struct snd_card *card); +static void pisnd_midi_uninit(void); + +enum task_e { + TASK_PROCESS = 0, +}; + +static void pisnd_schedule_process(enum task_e task); + +#define PISOUND_LOG_PREFIX "pisound: " + +#ifdef PISOUND_DEBUG +# define printd(...) pr_alert(PISOUND_LOG_PREFIX __VA_ARGS__) +#else +# define printd(...) do {} while (0) +#endif + +#define printe(...) pr_err(PISOUND_LOG_PREFIX __VA_ARGS__) +#define printi(...) pr_info(PISOUND_LOG_PREFIX __VA_ARGS__) + +static struct snd_rawmidi *g_rmidi; +static struct snd_rawmidi_substream *g_midi_output_substream; + +static int pisnd_output_open(struct snd_rawmidi_substream *substream) +{ + g_midi_output_substream = substream; + return 0; +} + +static int pisnd_output_close(struct snd_rawmidi_substream *substream) +{ + g_midi_output_substream = NULL; + return 0; +} + +static void pisnd_output_trigger( + struct snd_rawmidi_substream *substream, + int up + ) +{ + if (substream != g_midi_output_substream) { + printe("MIDI output trigger called for an unexpected stream!"); + return; + } + + if (!up) + return; + + pisnd_spi_start(); +} + +static void pisnd_output_drain(struct snd_rawmidi_substream *substream) +{ + pisnd_spi_flush(); +} + +static int pisnd_input_open(struct snd_rawmidi_substream *substream) +{ + return 0; +} + +static int pisnd_input_close(struct snd_rawmidi_substream *substream) +{ + return 0; +} + +static void pisnd_midi_recv_callback(void *substream) +{ + uint8_t data[128]; + uint8_t n = 0; + + while ((n = pisnd_spi_recv(data, sizeof(data)))) { + int res = snd_rawmidi_receive(substream, data, n); + (void)res; + printd("midi recv %u bytes, res = %d\n", n, res); + } +} + +static void pisnd_input_trigger(struct snd_rawmidi_substream *substream, int up) +{ + if (up) { + pisnd_spi_set_callback(pisnd_midi_recv_callback, substream); + pisnd_schedule_process(TASK_PROCESS); + } else { + pisnd_spi_set_callback(NULL, NULL); + } +} + +static const struct snd_rawmidi_ops pisnd_output_ops = { + .open = pisnd_output_open, + .close = pisnd_output_close, + .trigger = pisnd_output_trigger, + .drain = pisnd_output_drain, +}; + +static const struct snd_rawmidi_ops pisnd_input_ops = { + .open = pisnd_input_open, + .close = pisnd_input_close, + .trigger = pisnd_input_trigger, +}; + +static void pisnd_get_port_info( + struct snd_rawmidi *rmidi, + int number, + struct snd_seq_port_info *seq_port_info + ) +{ + seq_port_info->type = + SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | + SNDRV_SEQ_PORT_TYPE_HARDWARE | + SNDRV_SEQ_PORT_TYPE_PORT; + seq_port_info->midi_voices = 0; +} + +static struct snd_rawmidi_global_ops pisnd_global_ops = { + .get_port_info = pisnd_get_port_info, +}; + +static int pisnd_midi_init(struct snd_card *card) +{ + int err; + + g_midi_output_substream = NULL; + + err = snd_rawmidi_new(card, "pisound MIDI", 0, 1, 1, &g_rmidi); + + if (err < 0) { + printe("snd_rawmidi_new failed: %d\n", err); + return err; + } + + strcpy(g_rmidi->name, "pisound MIDI "); + strcat(g_rmidi->name, pisnd_spi_get_serial()); + + g_rmidi->info_flags = + SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + + g_rmidi->ops = &pisnd_global_ops; + + g_rmidi->private_data = (void *)0; + + snd_rawmidi_set_ops( + g_rmidi, + SNDRV_RAWMIDI_STREAM_OUTPUT, + &pisnd_output_ops + ); + + snd_rawmidi_set_ops( + g_rmidi, + SNDRV_RAWMIDI_STREAM_INPUT, + &pisnd_input_ops + ); + + return 0; +} + +static void pisnd_midi_uninit(void) +{ +} + +static void *g_recvData; +static pisnd_spi_recv_cb g_recvCallback; + +#define FIFO_SIZE 4096 + +static char g_serial_num[11]; +static char g_id[25]; +enum { MAX_VERSION_STR_LEN = 6 }; +static char g_fw_version[MAX_VERSION_STR_LEN]; +static char g_hw_version[MAX_VERSION_STR_LEN]; +static u32 g_spi_speed_hz; + +static uint8_t g_ledFlashDuration; +static bool g_ledFlashDurationChanged; + +DEFINE_KFIFO(spi_fifo_in, uint8_t, FIFO_SIZE); +DEFINE_KFIFO(spi_fifo_out, uint8_t, FIFO_SIZE); + +static struct gpio_desc *data_available; +static struct gpio_desc *spi_reset; + +static struct spi_device *pisnd_spi_device; + +static struct workqueue_struct *pisnd_workqueue; +static struct work_struct pisnd_work_process; + +static void pisnd_work_handler(struct work_struct *work); + +static void spi_transfer(const uint8_t *txbuf, uint8_t *rxbuf, int len); +static uint16_t spi_transfer16(uint16_t val); + +static int pisnd_init_workqueues(void) +{ + pisnd_workqueue = create_singlethread_workqueue("pisnd_workqueue"); + INIT_WORK(&pisnd_work_process, pisnd_work_handler); + + return 0; +} + +static void pisnd_uninit_workqueues(void) +{ + flush_workqueue(pisnd_workqueue); + destroy_workqueue(pisnd_workqueue); + + pisnd_workqueue = NULL; +} + +static bool pisnd_spi_has_more(void) +{ + return gpiod_get_value(data_available); +} + +static void pisnd_schedule_process(enum task_e task) +{ + if (pisnd_spi_device != NULL && + pisnd_workqueue != NULL && + !work_pending(&pisnd_work_process) + ) { + printd("schedule: has more = %d\n", pisnd_spi_has_more()); + if (task == TASK_PROCESS) + queue_work(pisnd_workqueue, &pisnd_work_process); + } +} + +static irqreturn_t data_available_interrupt_handler(int irq, void *dev_id) +{ + if (irq == gpiod_to_irq(data_available) && pisnd_spi_has_more()) { + printd("schedule from irq\n"); + pisnd_schedule_process(TASK_PROCESS); + } + + return IRQ_HANDLED; +} + +static uint16_t spi_transfer16(uint16_t val) +{ + uint8_t txbuf[2]; + uint8_t rxbuf[2]; + + if (!pisnd_spi_device) { + printe("pisnd_spi_device null, returning\n"); + return 0; + } + + txbuf[0] = val >> 8; + txbuf[1] = val & 0xff; + + spi_transfer(txbuf, rxbuf, sizeof(txbuf)); + + printd("received: %02x%02x\n", rxbuf[0], rxbuf[1]); + + return (rxbuf[0] << 8) | rxbuf[1]; +} + +static void spi_transfer(const uint8_t *txbuf, uint8_t *rxbuf, int len) +{ + int err; + struct spi_transfer transfer; + struct spi_message msg; + + memset(rxbuf, 0, len); + + if (!pisnd_spi_device) { + printe("pisnd_spi_device null, returning\n"); + return; + } + + spi_message_init(&msg); + + memset(&transfer, 0, sizeof(transfer)); + + transfer.tx_buf = txbuf; + transfer.rx_buf = rxbuf; + transfer.len = len; + transfer.speed_hz = g_spi_speed_hz; + transfer.delay.value = 10; + transfer.delay.unit = SPI_DELAY_UNIT_USECS; + + spi_message_add_tail(&transfer, &msg); + + err = spi_sync(pisnd_spi_device, &msg); + + if (err < 0) { + printe("spi_sync error %d\n", err); + return; + } + + printd("hasMore %d\n", pisnd_spi_has_more()); +} + +static int spi_read_bytes(char *dst, size_t length, uint8_t *bytesRead) +{ + uint16_t rx; + uint8_t size; + uint8_t i; + + memset(dst, 0, length); + *bytesRead = 0; + + rx = spi_transfer16(0); + if (!(rx >> 8)) + return -EINVAL; + + size = rx & 0xff; + + if (size > length) + return -EINVAL; + + for (i = 0; i < size; ++i) { + rx = spi_transfer16(0); + if (!(rx >> 8)) + return -EINVAL; + + dst[i] = rx & 0xff; + } + + *bytesRead = i; + + return 0; +} + +static int spi_device_match(struct device *dev, const void *data) +{ + struct spi_device *spi = container_of(dev, struct spi_device, dev); + + printd(" %s %s %dkHz %d bits mode=0x%02X\n", + spi->modalias, dev_name(dev), spi->max_speed_hz/1000, + spi->bits_per_word, spi->mode); + + if (strcmp("pisound-spi", spi->modalias) == 0) { + printi("\tFound!\n"); + return 1; + } + + printe("\tNot found!\n"); + return 0; +} + +static struct spi_device *pisnd_spi_find_device(void) +{ + struct device *dev; + + printi("Searching for spi device...\n"); + dev = bus_find_device(&spi_bus_type, NULL, NULL, spi_device_match); + if (dev != NULL) + return container_of(dev, struct spi_device, dev); + else + return NULL; +} + +static void pisnd_work_handler(struct work_struct *work) +{ + enum { TRANSFER_SIZE = 4 }; + enum { PISOUND_OUTPUT_BUFFER_SIZE_MILLIBYTES = 127 * 1000 }; + enum { MIDI_MILLIBYTES_PER_JIFFIE = (3125 * 1000) / HZ }; + int out_buffer_used_millibytes = 0; + unsigned long now; + uint8_t val; + uint8_t txbuf[TRANSFER_SIZE]; + uint8_t rxbuf[TRANSFER_SIZE]; + uint8_t midibuf[TRANSFER_SIZE]; + int i, n; + bool had_data; + + unsigned long last_transfer_at = jiffies; + + if (work == &pisnd_work_process) { + if (pisnd_spi_device == NULL) + return; + + do { + if (g_midi_output_substream && + kfifo_avail(&spi_fifo_out) >= sizeof(midibuf)) { + + n = snd_rawmidi_transmit_peek( + g_midi_output_substream, + midibuf, sizeof(midibuf) + ); + + if (n > 0) { + for (i = 0; i < n; ++i) + kfifo_put( + &spi_fifo_out, + midibuf[i] + ); + snd_rawmidi_transmit_ack( + g_midi_output_substream, + i + ); + } + } + + had_data = false; + memset(txbuf, 0, sizeof(txbuf)); + for (i = 0; i < sizeof(txbuf) && + ((out_buffer_used_millibytes+1000 < + PISOUND_OUTPUT_BUFFER_SIZE_MILLIBYTES) || + g_ledFlashDurationChanged); + i += 2) { + + val = 0; + + if (g_ledFlashDurationChanged) { + txbuf[i+0] = 0xf0; + txbuf[i+1] = g_ledFlashDuration; + g_ledFlashDuration = 0; + g_ledFlashDurationChanged = false; + } else if (kfifo_get(&spi_fifo_out, &val)) { + txbuf[i+0] = 0x0f; + txbuf[i+1] = val; + out_buffer_used_millibytes += 1000; + } + } + + spi_transfer(txbuf, rxbuf, sizeof(txbuf)); + /* Estimate the Pisound's MIDI output buffer usage, so + * that we don't overflow it. Space in the buffer should + * be becoming available at the UART MIDI byte transfer + * rate. + */ + now = jiffies; + if (now != last_transfer_at) { + out_buffer_used_millibytes -= + (now - last_transfer_at) * + MIDI_MILLIBYTES_PER_JIFFIE; + if (out_buffer_used_millibytes < 0) + out_buffer_used_millibytes = 0; + last_transfer_at = now; + } + + for (i = 0; i < sizeof(rxbuf); i += 2) { + if (rxbuf[i]) { + kfifo_put(&spi_fifo_in, rxbuf[i+1]); + if (kfifo_len(&spi_fifo_in) > 16 && + g_recvCallback) + g_recvCallback(g_recvData); + had_data = true; + } + } + } while (had_data + || !kfifo_is_empty(&spi_fifo_out) + || pisnd_spi_has_more() + || g_ledFlashDurationChanged + || out_buffer_used_millibytes != 0 + ); + + if (!kfifo_is_empty(&spi_fifo_in) && g_recvCallback) + g_recvCallback(g_recvData); + } +} + +static int pisnd_spi_gpio_init(struct device *dev) +{ + spi_reset = gpiod_get_index(dev, "reset", 1, GPIOD_ASIS); + data_available = gpiod_get_index(dev, "data_available", 0, GPIOD_ASIS); + + gpiod_direction_output(spi_reset, 1); + gpiod_direction_input(data_available); + + /* Reset the slave. */ + gpiod_set_value(spi_reset, false); + mdelay(1); + gpiod_set_value(spi_reset, true); + + /* Give time for spi slave to start. */ + mdelay(64); + + return 0; +} + +static void pisnd_spi_gpio_uninit(void) +{ + gpiod_set_value(spi_reset, false); + gpiod_put(spi_reset); + spi_reset = NULL; + + gpiod_put(data_available); + data_available = NULL; +} + +static int pisnd_spi_gpio_irq_init(struct device *dev) +{ + return request_threaded_irq( + gpiod_to_irq(data_available), NULL, + data_available_interrupt_handler, + IRQF_TIMER | IRQF_TRIGGER_RISING | IRQF_ONESHOT, + "data_available_int", + NULL + ); +} + +static void pisnd_spi_gpio_irq_uninit(void) +{ + free_irq(gpiod_to_irq(data_available), NULL); +} + +static int spi_read_info(void) +{ + uint16_t tmp; + uint8_t count; + uint8_t n; + uint8_t i; + uint8_t j; + char buffer[257]; + int ret; + char *p; + + memset(g_serial_num, 0, sizeof(g_serial_num)); + memset(g_fw_version, 0, sizeof(g_fw_version)); + strcpy(g_hw_version, "1.0"); // Assume 1.0 hw version. + memset(g_id, 0, sizeof(g_id)); + + tmp = spi_transfer16(0); + + if (!(tmp >> 8)) + return -EINVAL; + + count = tmp & 0xff; + + for (i = 0; i < count; ++i) { + memset(buffer, 0, sizeof(buffer)); + ret = spi_read_bytes(buffer, sizeof(buffer)-1, &n); + + if (ret < 0) + return ret; + + switch (i) { + case 0: + if (n != 2) + return -EINVAL; + + snprintf( + g_fw_version, + MAX_VERSION_STR_LEN, + "%x.%02x", + buffer[0], + buffer[1] + ); + + g_fw_version[MAX_VERSION_STR_LEN-1] = '\0'; + break; + case 3: + if (n != 2) + return -EINVAL; + + snprintf( + g_hw_version, + MAX_VERSION_STR_LEN, + "%x.%x", + buffer[0], + buffer[1] + ); + + g_hw_version[MAX_VERSION_STR_LEN-1] = '\0'; + break; + case 1: + if (n >= sizeof(g_serial_num)) + return -EINVAL; + + memcpy(g_serial_num, buffer, sizeof(g_serial_num)); + break; + case 2: + { + if (n*2 >= sizeof(g_id)) + return -EINVAL; + + p = g_id; + for (j = 0; j < n; ++j) + p += sprintf(p, "%02x", buffer[j]); + + *p = '\0'; + } + break; + default: + break; + } + } + + return 0; +} + +static int pisnd_spi_init(struct device *dev) +{ + int ret; + struct spi_device *spi; + + memset(g_serial_num, 0, sizeof(g_serial_num)); + memset(g_id, 0, sizeof(g_id)); + memset(g_fw_version, 0, sizeof(g_fw_version)); + memset(g_hw_version, 0, sizeof(g_hw_version)); + + g_spi_speed_hz = 150000; + if (dev->of_node) { + struct device_node *spi_node; + + spi_node = of_parse_phandle( + dev->of_node, + "spi-controller", + 0 + ); + + if (spi_node) { + ret = of_property_read_u32(spi_node, "spi-speed-hz", &g_spi_speed_hz); + if (ret != 0) + printe("Failed reading spi-speed-hz! (%d)\n", ret); + + of_node_put(spi_node); + } + } + printi("Using SPI speed: %u\n", g_spi_speed_hz); + + spi = pisnd_spi_find_device(); + + if (spi != NULL) { + printd("initializing spi!\n"); + pisnd_spi_device = spi; + ret = spi_setup(pisnd_spi_device); + } else { + printe("SPI device not found, deferring!\n"); + return -EPROBE_DEFER; + } + + ret = pisnd_spi_gpio_init(dev); + + if (ret < 0) { + printe("SPI GPIO init failed: %d\n", ret); + spi_dev_put(pisnd_spi_device); + pisnd_spi_device = NULL; + pisnd_spi_gpio_uninit(); + return ret; + } + + ret = spi_read_info(); + + if (ret < 0) { + printe("Reading card info failed: %d\n", ret); + spi_dev_put(pisnd_spi_device); + pisnd_spi_device = NULL; + pisnd_spi_gpio_uninit(); + return ret; + } + + /* Flash the LEDs. */ + spi_transfer16(0xf008); + + ret = pisnd_spi_gpio_irq_init(dev); + if (ret < 0) { + printe("SPI irq request failed: %d\n", ret); + spi_dev_put(pisnd_spi_device); + pisnd_spi_device = NULL; + pisnd_spi_gpio_irq_uninit(); + pisnd_spi_gpio_uninit(); + } + + ret = pisnd_init_workqueues(); + if (ret != 0) { + printe("Workqueue initialization failed: %d\n", ret); + spi_dev_put(pisnd_spi_device); + pisnd_spi_device = NULL; + pisnd_spi_gpio_irq_uninit(); + pisnd_spi_gpio_uninit(); + pisnd_uninit_workqueues(); + return ret; + } + + if (pisnd_spi_has_more()) { + printd("data is available, scheduling from init\n"); + pisnd_schedule_process(TASK_PROCESS); + } + + return 0; +} + +static void pisnd_spi_uninit(void) +{ + pisnd_uninit_workqueues(); + + spi_dev_put(pisnd_spi_device); + pisnd_spi_device = NULL; + + pisnd_spi_gpio_irq_uninit(); + pisnd_spi_gpio_uninit(); +} + +static void pisnd_spi_flash_leds(uint8_t duration) +{ + g_ledFlashDuration = duration; + g_ledFlashDurationChanged = true; + printd("schedule from spi_flash_leds\n"); + pisnd_schedule_process(TASK_PROCESS); +} + +static void pisnd_spi_flush(void) +{ + while (!kfifo_is_empty(&spi_fifo_out)) { + pisnd_spi_start(); + flush_workqueue(pisnd_workqueue); + } +} + +static void pisnd_spi_start(void) +{ + printd("schedule from spi_start\n"); + pisnd_schedule_process(TASK_PROCESS); +} + +static uint8_t pisnd_spi_recv(uint8_t *buffer, uint8_t length) +{ + return kfifo_out(&spi_fifo_in, buffer, length); +} + +static void pisnd_spi_set_callback(pisnd_spi_recv_cb cb, void *data) +{ + g_recvData = data; + g_recvCallback = cb; +} + +static const char *pisnd_spi_get_serial(void) +{ + return g_serial_num; +} + +static const char *pisnd_spi_get_id(void) +{ + return g_id; +} + +static const char *pisnd_spi_get_fw_version(void) +{ + return g_fw_version; +} + +static const char *pisnd_spi_get_hw_version(void) +{ + return g_hw_version; +} + +static const struct of_device_id pisound_of_match[] = { + { .compatible = "blokaslabs,pisound", }, + { .compatible = "blokaslabs,pisound-spi", }, + {}, +}; + +enum { + SWITCH = 0, + VOLUME = 1, +}; + +static int pisnd_ctl_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + if (kcontrol->private_value == SWITCH) { + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; + } else if (kcontrol->private_value == VOLUME) { + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 100; + return 0; + } + return -EINVAL; +} + +static int pisnd_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + if (kcontrol->private_value == SWITCH) { + ucontrol->value.integer.value[0] = 1; + return 0; + } else if (kcontrol->private_value == VOLUME) { + ucontrol->value.integer.value[0] = 100; + return 0; + } + + return -EINVAL; +} + +static struct snd_kcontrol_new pisnd_ctl[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Switch", + .index = 0, + .private_value = SWITCH, + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = pisnd_ctl_info, + .get = pisnd_ctl_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Volume", + .index = 0, + .private_value = VOLUME, + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = pisnd_ctl_info, + .get = pisnd_ctl_get, + }, +}; + +static int pisnd_ctl_init(struct snd_card *card) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(pisnd_ctl); ++i) { + err = snd_ctl_add(card, snd_ctl_new1(&pisnd_ctl[i], NULL)); + if (err < 0) + return err; + } + + return 0; +} + +static int pisnd_ctl_uninit(void) +{ + return 0; +} + +static struct gpio_desc *osr0, *osr1, *osr2; +static struct gpio_desc *reset; + +static int pisnd_hw_params( + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params + ) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + + /* Pisound runs on fixed 32 clock counts per channel, + * as generated by the master ADC. + */ + snd_soc_dai_set_bclk_ratio(cpu_dai, 32*2); + + printd("rate = %d\n", params_rate(params)); + printd("ch = %d\n", params_channels(params)); + printd("bits = %u\n", + snd_pcm_format_width(params_format(params))); + printd("format = %d\n", params_format(params)); + + gpiod_set_value(reset, false); + + switch (params_rate(params)) { + case 48000: + gpiod_set_value(osr0, true); + gpiod_set_value(osr1, false); + gpiod_set_value(osr2, false); + break; + case 96000: + gpiod_set_value(osr0, true); + gpiod_set_value(osr1, false); + gpiod_set_value(osr2, true); + break; + case 192000: + gpiod_set_value(osr0, true); + gpiod_set_value(osr1, true); + gpiod_set_value(osr2, true); + break; + default: + printe("Unsupported rate %u!\n", params_rate(params)); + return -EINVAL; + } + + gpiod_set_value(reset, true); + + return 0; +} + +static unsigned int rates[3] = { + 48000, 96000, 192000 +}; + +static struct snd_pcm_hw_constraint_list constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, +}; + +static int pisnd_startup(struct snd_pcm_substream *substream) +{ + int err = snd_pcm_hw_constraint_list( + substream->runtime, + 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_rates + ); + + if (err < 0) + return err; + + err = snd_pcm_hw_constraint_single( + substream->runtime, + SNDRV_PCM_HW_PARAM_CHANNELS, + 2 + ); + + if (err < 0) + return err; + + err = snd_pcm_hw_constraint_mask64( + substream->runtime, + SNDRV_PCM_HW_PARAM_FORMAT, + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE + ); + + if (err < 0) + return err; + + return 0; +} + +static const struct snd_soc_ops pisnd_ops = { + .startup = pisnd_startup, + .hw_params = pisnd_hw_params, +}; + +SND_SOC_DAILINK_DEFS(pisnd, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), + DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0"))); + +static struct snd_soc_dai_link pisnd_dai[] = { + { + .name = "pisound", + .stream_name = "pisound", + .dai_fmt = + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM, + .ops = &pisnd_ops, + SND_SOC_DAILINK_REG(pisnd), + }, +}; + +static int pisnd_card_probe(struct snd_soc_card *card) +{ + int err = pisnd_midi_init(card->snd_card); + + if (err < 0) { + printe("pisnd_midi_init failed: %d\n", err); + return err; + } + + err = pisnd_ctl_init(card->snd_card); + if (err < 0) { + printe("pisnd_ctl_init failed: %d\n", err); + return err; + } + + return 0; +} + +static int pisnd_card_remove(struct snd_soc_card *card) +{ + pisnd_ctl_uninit(); + pisnd_midi_uninit(); + return 0; +} + +static struct snd_soc_card pisnd_card = { + .name = "pisound", + .owner = THIS_MODULE, + .dai_link = pisnd_dai, + .num_links = ARRAY_SIZE(pisnd_dai), + .probe = pisnd_card_probe, + .remove = pisnd_card_remove, +}; + +static int pisnd_init_gpio(struct device *dev) +{ + osr0 = gpiod_get_index(dev, "osr", 0, GPIOD_ASIS); + osr1 = gpiod_get_index(dev, "osr", 1, GPIOD_ASIS); + osr2 = gpiod_get_index(dev, "osr", 2, GPIOD_ASIS); + + reset = gpiod_get_index(dev, "reset", 0, GPIOD_ASIS); + + gpiod_direction_output(osr0, 1); + gpiod_direction_output(osr1, 1); + gpiod_direction_output(osr2, 1); + gpiod_direction_output(reset, 1); + + gpiod_set_value(reset, false); + gpiod_set_value(osr0, true); + gpiod_set_value(osr1, false); + gpiod_set_value(osr2, false); + gpiod_set_value(reset, true); + + return 0; +} + +static int pisnd_uninit_gpio(void) +{ + int i; + + struct gpio_desc **gpios[] = { + &osr0, &osr1, &osr2, &reset, + }; + + for (i = 0; i < ARRAY_SIZE(gpios); ++i) { + if (*gpios[i] == NULL) { + printd("weird, GPIO[%d] is NULL already\n", i); + continue; + } + + gpiod_put(*gpios[i]); + *gpios[i] = NULL; + } + + return 0; +} + +static struct kobject *pisnd_kobj; + +static ssize_t pisnd_serial_show( + struct kobject *kobj, + struct kobj_attribute *attr, + char *buf + ) +{ + return sprintf(buf, "%s\n", pisnd_spi_get_serial()); +} + +static ssize_t pisnd_id_show( + struct kobject *kobj, + struct kobj_attribute *attr, + char *buf + ) +{ + return sprintf(buf, "%s\n", pisnd_spi_get_id()); +} + +static ssize_t pisnd_fw_version_show( + struct kobject *kobj, + struct kobj_attribute *attr, + char *buf + ) +{ + return sprintf(buf, "%s\n", pisnd_spi_get_fw_version()); +} + +static ssize_t pisnd_hw_version_show( + struct kobject *kobj, + struct kobj_attribute *attr, + char *buf +) +{ + return sprintf(buf, "%s\n", pisnd_spi_get_hw_version()); +} + +static ssize_t pisnd_led_store( + struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, + size_t length + ) +{ + uint32_t timeout; + int err; + + err = kstrtou32(buf, 10, &timeout); + + if (err == 0 && timeout <= 255) + pisnd_spi_flash_leds(timeout); + + return length; +} + +static struct kobj_attribute pisnd_serial_attribute = + __ATTR(serial, 0444, pisnd_serial_show, NULL); +static struct kobj_attribute pisnd_id_attribute = + __ATTR(id, 0444, pisnd_id_show, NULL); +static struct kobj_attribute pisnd_fw_version_attribute = + __ATTR(version, 0444, pisnd_fw_version_show, NULL); +static struct kobj_attribute pisnd_hw_version_attribute = +__ATTR(hw_version, 0444, pisnd_hw_version_show, NULL); +static struct kobj_attribute pisnd_led_attribute = + __ATTR(led, 0644, NULL, pisnd_led_store); + +static struct attribute *attrs[] = { + &pisnd_serial_attribute.attr, + &pisnd_id_attribute.attr, + &pisnd_fw_version_attribute.attr, + &pisnd_hw_version_attribute.attr, + &pisnd_led_attribute.attr, + NULL +}; + +static struct attribute_group attr_group = { .attrs = attrs }; + +static int pisnd_probe(struct platform_device *pdev) +{ + int ret = 0; + int i; + + ret = pisnd_spi_init(&pdev->dev); + if (ret < 0) { + printe("pisnd_spi_init failed: %d\n", ret); + return ret; + } + + printi("Detected Pisound card:\n"); + printi("\tSerial: %s\n", pisnd_spi_get_serial()); + printi("\tFirmware Version: %s\n", pisnd_spi_get_fw_version()); + printi("\tHardware Version: %s\n", pisnd_spi_get_hw_version()); + printi("\tId: %s\n", pisnd_spi_get_id()); + + pisnd_kobj = kobject_create_and_add("pisound", kernel_kobj); + if (!pisnd_kobj) { + pisnd_spi_uninit(); + return -ENOMEM; + } + + ret = sysfs_create_group(pisnd_kobj, &attr_group); + if (ret < 0) { + pisnd_spi_uninit(); + kobject_put(pisnd_kobj); + return -ENOMEM; + } + + pisnd_init_gpio(&pdev->dev); + pisnd_card.dev = &pdev->dev; + + if (pdev->dev.of_node) { + struct device_node *i2s_node; + + i2s_node = of_parse_phandle( + pdev->dev.of_node, + "i2s-controller", + 0 + ); + + for (i = 0; i < pisnd_card.num_links; ++i) { + struct snd_soc_dai_link *dai = &pisnd_dai[i]; + + if (i2s_node) { + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + dai->stream_name = pisnd_spi_get_serial(); + } + } + } + + ret = snd_soc_register_card(&pisnd_card); + + if (ret < 0) { + if (ret != -EPROBE_DEFER) + printe("snd_soc_register_card() failed: %d\n", ret); + pisnd_uninit_gpio(); + kobject_put(pisnd_kobj); + pisnd_spi_uninit(); + } + + return ret; +} + +static void pisnd_remove(struct platform_device *pdev) +{ + printi("Unloading.\n"); + + if (pisnd_kobj) { + kobject_put(pisnd_kobj); + pisnd_kobj = NULL; + } + + pisnd_spi_uninit(); + + /* Turn off */ + gpiod_set_value(reset, false); + pisnd_uninit_gpio(); + + snd_soc_unregister_card(&pisnd_card); +} + +MODULE_DEVICE_TABLE(of, pisound_of_match); + +static struct platform_driver pisnd_driver = { + .driver = { + .name = "snd-rpi-pisound", + .owner = THIS_MODULE, + .of_match_table = pisound_of_match, + }, + .probe = pisnd_probe, + .remove = pisnd_remove, +}; + +module_platform_driver(pisnd_driver); + +MODULE_AUTHOR("Giedrius Trainavicius <giedrius@blokas.io>"); +MODULE_DESCRIPTION("ASoC Driver for Pisound, https://blokas.io/pisound"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/rpi-cirrus.c b/sound/soc/bcm/rpi-cirrus.c new file mode 100644 index 00000000000000..b04c1bf38f6bbf --- /dev/null +++ b/sound/soc/bcm/rpi-cirrus.c @@ -0,0 +1,1027 @@ +/* + * ASoC machine driver for Cirrus Logic Audio Card + * (with WM5102 and WM8804 codecs) + * + * Copyright 2015-2017 Matthias Reichl <hias@horus.com> + * + * Based on rpi-cirrus-sound-pi driver (c) Wolfson / Cirrus Logic Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/list.h> +#include <linux/delay.h> +#include <sound/pcm_params.h> + +#include <linux/mfd/arizona/registers.h> + +#include "../codecs/wm5102.h" +#include "../codecs/wm8804.h" + +#define WM8804_CLKOUT_HZ 12000000 + +#define RPI_CIRRUS_DEFAULT_RATE 44100 +#define WM5102_MAX_SYSCLK_1 49152000 /* max sysclk for 4K family */ +#define WM5102_MAX_SYSCLK_2 45158400 /* max sysclk for 11.025K family */ + +static inline unsigned int calc_sysclk(unsigned int rate) +{ + return (rate % 4000) ? WM5102_MAX_SYSCLK_2 : WM5102_MAX_SYSCLK_1; +} + +enum { + DAI_WM5102 = 0, + DAI_WM8804, +}; + +struct rpi_cirrus_priv { + /* mutex for synchronzing FLL1 access with DAPM */ + struct mutex lock; + unsigned int card_rate; + int sync_path_enable; + int fll1_freq; /* negative means RefClock in spdif rx case */ + + /* track hw params/free for substreams */ + unsigned int params_set; + unsigned int min_rate_idx, max_rate_idx; + unsigned char iec958_status[4]; +}; + +/* helper functions */ +static inline struct snd_soc_pcm_runtime *get_wm5102_runtime( + struct snd_soc_card *card) { + return snd_soc_get_pcm_runtime(card, &card->dai_link[DAI_WM5102]); +} + +static inline struct snd_soc_pcm_runtime *get_wm8804_runtime( + struct snd_soc_card *card) { + return snd_soc_get_pcm_runtime(card, &card->dai_link[DAI_WM8804]); +} + + +struct rate_info { + unsigned int value; + char *text; +}; + +static struct rate_info min_rates[] = { + { 0, "off"}, + { 32000, "32kHz"}, + { 44100, "44.1kHz"} +}; + +#define NUM_MIN_RATES ARRAY_SIZE(min_rates) + +static int rpi_cirrus_min_rate_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = NUM_MIN_RATES; + + if (uinfo->value.enumerated.item >= NUM_MIN_RATES) + uinfo->value.enumerated.item = NUM_MIN_RATES - 1; + strcpy(uinfo->value.enumerated.name, + min_rates[uinfo->value.enumerated.item].text); + return 0; +} + +static int rpi_cirrus_min_rate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card); + + ucontrol->value.enumerated.item[0] = priv->min_rate_idx; + return 0; +} + +static int rpi_cirrus_min_rate_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card); + int changed = 0; + + if (priv->min_rate_idx != ucontrol->value.enumerated.item[0]) { + changed = 1; + priv->min_rate_idx = ucontrol->value.enumerated.item[0]; + } + + return changed; +} + +static struct rate_info max_rates[] = { + { 0, "off"}, + { 48000, "48kHz"}, + { 96000, "96kHz"} +}; + +#define NUM_MAX_RATES ARRAY_SIZE(max_rates) + +static int rpi_cirrus_max_rate_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = NUM_MAX_RATES; + if (uinfo->value.enumerated.item >= NUM_MAX_RATES) + uinfo->value.enumerated.item = NUM_MAX_RATES - 1; + strcpy(uinfo->value.enumerated.name, + max_rates[uinfo->value.enumerated.item].text); + return 0; +} + +static int rpi_cirrus_max_rate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card); + + ucontrol->value.enumerated.item[0] = priv->max_rate_idx; + return 0; +} + +static int rpi_cirrus_max_rate_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card); + int changed = 0; + + if (priv->max_rate_idx != ucontrol->value.enumerated.item[0]) { + changed = 1; + priv->max_rate_idx = ucontrol->value.enumerated.item[0]; + } + + return changed; +} + +static int rpi_cirrus_spdif_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int rpi_cirrus_spdif_playback_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card); + int i; + + for (i = 0; i < 4; i++) + ucontrol->value.iec958.status[i] = priv->iec958_status[i]; + + return 0; +} + +static int rpi_cirrus_spdif_playback_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct snd_soc_component *wm8804_component = + snd_soc_rtd_to_codec(get_wm8804_runtime(card), 0)->component; + struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card); + unsigned char *stat = priv->iec958_status; + unsigned char *ctrl_stat = ucontrol->value.iec958.status; + unsigned int mask; + int i, changed = 0; + + for (i = 0; i < 4; i++) { + mask = (i == 3) ? 0x3f : 0xff; + if ((ctrl_stat[i] & mask) != (stat[i] & mask)) { + changed = 1; + stat[i] = ctrl_stat[i] & mask; + snd_soc_component_update_bits(wm8804_component, + WM8804_SPDTX1 + i, mask, stat[i]); + } + } + + return changed; +} + +static int rpi_cirrus_spdif_mask_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.iec958.status[0] = 0xff; + ucontrol->value.iec958.status[1] = 0xff; + ucontrol->value.iec958.status[2] = 0xff; + ucontrol->value.iec958.status[3] = 0x3f; + + return 0; +} + +static int rpi_cirrus_spdif_capture_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct snd_soc_component *wm8804_component = + snd_soc_rtd_to_codec(get_wm8804_runtime(card), 0)->component; + unsigned int val, mask; + int i; + + for (i = 0; i < 4; i++) { + val = snd_soc_component_read(wm8804_component, + WM8804_RXCHAN1 + i); + mask = (i == 3) ? 0x3f : 0xff; + ucontrol->value.iec958.status[i] = val & mask; + } + + return 0; +} + +#define SPDIF_FLAG_CTRL(desc, reg, bit, invert) \ +{ \ + .access = SNDRV_CTL_ELEM_ACCESS_READ \ + | SNDRV_CTL_ELEM_ACCESS_VOLATILE, \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, NONE) \ + desc " Flag", \ + .info = snd_ctl_boolean_mono_info, \ + .get = rpi_cirrus_spdif_status_flag_get, \ + .private_value = \ + (bit) | ((reg) << 8) | ((invert) << 16) \ +} + +static int rpi_cirrus_spdif_status_flag_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct snd_soc_component *wm8804_component = + snd_soc_rtd_to_codec(get_wm8804_runtime(card), 0)->component; + + unsigned int bit = kcontrol->private_value & 0xff; + unsigned int reg = (kcontrol->private_value >> 8) & 0xff; + unsigned int invert = (kcontrol->private_value >> 16) & 0xff; + unsigned int val; + bool flag; + + val = snd_soc_component_read(wm8804_component, reg); + + flag = val & (1 << bit); + + ucontrol->value.integer.value[0] = invert ? !flag : flag; + + return 0; +} + +static const char * const recovered_frequency_texts[] = { + "176.4/192 kHz", + "88.2/96 kHz", + "44.1/48 kHz", + "32 kHz" +}; + +#define NUM_RECOVERED_FREQUENCIES \ + ARRAY_SIZE(recovered_frequency_texts) + +static int rpi_cirrus_recovered_frequency_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = NUM_RECOVERED_FREQUENCIES; + if (uinfo->value.enumerated.item >= NUM_RECOVERED_FREQUENCIES) + uinfo->value.enumerated.item = NUM_RECOVERED_FREQUENCIES - 1; + strcpy(uinfo->value.enumerated.name, + recovered_frequency_texts[uinfo->value.enumerated.item]); + return 0; +} + +static int rpi_cirrus_recovered_frequency_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct snd_soc_component *wm8804_component = + snd_soc_rtd_to_codec(get_wm8804_runtime(card), 0)->component; + unsigned int val; + + val = snd_soc_component_read(wm8804_component, WM8804_SPDSTAT); + + ucontrol->value.enumerated.item[0] = (val >> 4) & 0x03; + return 0; +} + +static const struct snd_kcontrol_new rpi_cirrus_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Min Sample Rate", + .info = rpi_cirrus_min_rate_info, + .get = rpi_cirrus_min_rate_get, + .put = rpi_cirrus_min_rate_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Max Sample Rate", + .info = rpi_cirrus_max_rate_info, + .get = rpi_cirrus_max_rate_get, + .put = rpi_cirrus_max_rate_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), + .info = rpi_cirrus_spdif_info, + .get = rpi_cirrus_spdif_playback_get, + .put = rpi_cirrus_spdif_playback_put, + }, + { + .access = SNDRV_CTL_ELEM_ACCESS_READ + | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, DEFAULT), + .info = rpi_cirrus_spdif_info, + .get = rpi_cirrus_spdif_capture_get, + }, + { + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, MASK), + .info = rpi_cirrus_spdif_info, + .get = rpi_cirrus_spdif_mask_get, + }, + { + .access = SNDRV_CTL_ELEM_ACCESS_READ + | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, NONE) + "Recovered Frequency", + .info = rpi_cirrus_recovered_frequency_info, + .get = rpi_cirrus_recovered_frequency_get, + }, + SPDIF_FLAG_CTRL("Audio", WM8804_SPDSTAT, 0, 1), + SPDIF_FLAG_CTRL("Non-PCM", WM8804_SPDSTAT, 1, 0), + SPDIF_FLAG_CTRL("Copyright", WM8804_SPDSTAT, 2, 1), + SPDIF_FLAG_CTRL("De-Emphasis", WM8804_SPDSTAT, 3, 0), + SPDIF_FLAG_CTRL("Lock", WM8804_SPDSTAT, 6, 1), + SPDIF_FLAG_CTRL("Invalid", WM8804_INTSTAT, 1, 0), + SPDIF_FLAG_CTRL("TransErr", WM8804_INTSTAT, 3, 0), +}; + +static const char * const linein_micbias_texts[] = { + "off", "on", +}; + +static SOC_ENUM_SINGLE_VIRT_DECL(linein_micbias_enum, + linein_micbias_texts); + +static const struct snd_kcontrol_new linein_micbias_mux = + SOC_DAPM_ENUM("Route", linein_micbias_enum); + +static int rpi_cirrus_spdif_rx_enable_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event); + +const struct snd_soc_dapm_widget rpi_cirrus_dapm_widgets[] = { + SND_SOC_DAPM_MIC("DMIC", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_INPUT("Line Input"), + SND_SOC_DAPM_MIC("Line Input with Micbias", NULL), + SND_SOC_DAPM_MUX("Line Input Micbias", SND_SOC_NOPM, 0, 0, + &linein_micbias_mux), + SND_SOC_DAPM_INPUT("dummy SPDIF in"), + SND_SOC_DAPM_PGA_E("dummy SPDIFRX", SND_SOC_NOPM, 0, 0, NULL, 0, + rpi_cirrus_spdif_rx_enable_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_INPUT("Dummy Input"), + SND_SOC_DAPM_OUTPUT("Dummy Output"), +}; + +const struct snd_soc_dapm_route rpi_cirrus_dapm_routes[] = { + { "IN1L", NULL, "Headset Mic" }, + { "IN1R", NULL, "Headset Mic" }, + { "Headset Mic", NULL, "MICBIAS1" }, + + { "IN2L", NULL, "DMIC" }, + { "IN2R", NULL, "DMIC" }, + { "DMIC", NULL, "MICBIAS2" }, + + { "IN3L", NULL, "Line Input Micbias" }, + { "IN3R", NULL, "Line Input Micbias" }, + + { "Line Input Micbias", "off", "Line Input" }, + { "Line Input Micbias", "on", "Line Input with Micbias" }, + + /* Make sure MICVDD is enabled, otherwise we get noise */ + { "Line Input", NULL, "MICVDD" }, + { "Line Input with Micbias", NULL, "MICBIAS3" }, + + /* Dummy routes to check whether SPDIF RX is enabled or not */ + {"dummy SPDIFRX", NULL, "dummy SPDIF in"}, + {"AIFTX", NULL, "dummy SPDIFRX"}, + + /* + * Dummy routes to keep wm5102 from staying off on + * playback/capture if all mixers are off. + */ + { "Dummy Output", NULL, "AIF1RX1" }, + { "Dummy Output", NULL, "AIF1RX2" }, + { "AIF1TX1", NULL, "Dummy Input" }, + { "AIF1TX2", NULL, "Dummy Input" }, +}; + +static int rpi_cirrus_clear_flls(struct snd_soc_card *card, + struct snd_soc_component *wm5102_component) { + + int ret1, ret2; + + ret1 = snd_soc_component_set_pll(wm5102_component, + WM5102_FLL1, ARIZONA_FLL_SRC_NONE, 0, 0); + ret2 = snd_soc_component_set_pll(wm5102_component, + WM5102_FLL1_REFCLK, ARIZONA_FLL_SRC_NONE, 0, 0); + + if (ret1) { + dev_warn(card->dev, + "setting FLL1 to zero failed: %d\n", ret1); + return ret1; + } + if (ret2) { + dev_warn(card->dev, + "setting FLL1_REFCLK to zero failed: %d\n", ret2); + return ret2; + } + return 0; +} + +static int rpi_cirrus_set_fll(struct snd_soc_card *card, + struct snd_soc_component *wm5102_component, unsigned int clk_freq) +{ + int ret = snd_soc_component_set_pll(wm5102_component, + WM5102_FLL1, + ARIZONA_CLK_SRC_MCLK1, + WM8804_CLKOUT_HZ, + clk_freq); + if (ret) + dev_err(card->dev, "Failed to set FLL1 to %d: %d\n", + clk_freq, ret); + + usleep_range(1000, 2000); + return ret; +} + +static int rpi_cirrus_set_fll_refclk(struct snd_soc_card *card, + struct snd_soc_component *wm5102_component, + unsigned int clk_freq, unsigned int aif2_freq) +{ + int ret = snd_soc_component_set_pll(wm5102_component, + WM5102_FLL1_REFCLK, + ARIZONA_CLK_SRC_MCLK1, + WM8804_CLKOUT_HZ, + clk_freq); + if (ret) { + dev_err(card->dev, + "Failed to set FLL1_REFCLK to %d: %d\n", + clk_freq, ret); + return ret; + } + + ret = snd_soc_component_set_pll(wm5102_component, + WM5102_FLL1, + ARIZONA_CLK_SRC_AIF2BCLK, + aif2_freq, clk_freq); + if (ret) + dev_err(card->dev, + "Failed to set FLL1 with Sync Clock %d to %d: %d\n", + aif2_freq, clk_freq, ret); + + usleep_range(1000, 2000); + return ret; +} + +static int rpi_cirrus_spdif_rx_enable_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_card *card = w->dapm->card; + struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_component *wm5102_component = + snd_soc_rtd_to_codec(get_wm5102_runtime(card), 0)->component; + + unsigned int clk_freq, aif2_freq; + int ret = 0; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + mutex_lock(&priv->lock); + + /* Enable sync path in case of SPDIF capture use case */ + + clk_freq = calc_sysclk(priv->card_rate); + aif2_freq = 64 * priv->card_rate; + + dev_dbg(card->dev, + "spdif_rx: changing FLL1 to use Ref Clock clk: %d spdif: %d\n", + clk_freq, aif2_freq); + + ret = rpi_cirrus_clear_flls(card, wm5102_component); + if (ret) { + dev_err(card->dev, "spdif_rx: failed to clear FLLs\n"); + goto out; + } + + ret = rpi_cirrus_set_fll_refclk(card, wm5102_component, + clk_freq, aif2_freq); + + if (ret) { + dev_err(card->dev, "spdif_rx: failed to set FLLs\n"); + goto out; + } + + /* set to negative to indicate we're doing spdif rx */ + priv->fll1_freq = -clk_freq; + priv->sync_path_enable = 1; + break; + + case SND_SOC_DAPM_POST_PMD: + mutex_lock(&priv->lock); + priv->sync_path_enable = 0; + break; + + default: + return 0; + } + +out: + mutex_unlock(&priv->lock); + return ret; +} + +static int rpi_cirrus_set_bias_level(struct snd_soc_card *card, + struct snd_soc_dapm_context *dapm, + enum snd_soc_bias_level level) +{ + struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_pcm_runtime *wm5102_runtime = get_wm5102_runtime(card); + struct snd_soc_component *wm5102_component = + snd_soc_rtd_to_codec(wm5102_runtime, 0)->component; + + int ret = 0; + unsigned int clk_freq; + + if (dapm->dev != snd_soc_rtd_to_codec(wm5102_runtime, 0)->dev) + return 0; + + switch (level) { + case SND_SOC_BIAS_PREPARE: + if (dapm->bias_level == SND_SOC_BIAS_ON) + break; + + mutex_lock(&priv->lock); + + if (!priv->sync_path_enable) { + clk_freq = calc_sysclk(priv->card_rate); + + dev_dbg(card->dev, + "set_bias: changing FLL1 from %d to %d\n", + priv->fll1_freq, clk_freq); + + ret = rpi_cirrus_set_fll(card, + wm5102_component, clk_freq); + if (ret) + dev_err(card->dev, + "set_bias: Failed to set FLL1\n"); + else + priv->fll1_freq = clk_freq; + } + mutex_unlock(&priv->lock); + break; + default: + break; + } + + return ret; +} + +static int rpi_cirrus_set_bias_level_post(struct snd_soc_card *card, + struct snd_soc_dapm_context *dapm, + enum snd_soc_bias_level level) +{ + struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_pcm_runtime *wm5102_runtime = get_wm5102_runtime(card); + struct snd_soc_component *wm5102_component = + snd_soc_rtd_to_codec(wm5102_runtime, 0)->component; + + if (dapm->dev != snd_soc_rtd_to_codec(wm5102_runtime, 0)->dev) + return 0; + + switch (level) { + case SND_SOC_BIAS_STANDBY: + mutex_lock(&priv->lock); + + dev_dbg(card->dev, + "set_bias_post: changing FLL1 from %d to off\n", + priv->fll1_freq); + + if (rpi_cirrus_clear_flls(card, wm5102_component)) + dev_err(card->dev, + "set_bias_post: failed to clear FLLs\n"); + else + priv->fll1_freq = 0; + + mutex_unlock(&priv->lock); + + break; + default: + break; + } + + return 0; +} + +static int rpi_cirrus_set_wm8804_pll(struct snd_soc_card *card, + struct snd_soc_dai *wm8804_dai, unsigned int rate) +{ + int ret; + + /* use 256fs */ + unsigned int clk_freq = rate * 256; + + ret = snd_soc_dai_set_pll(wm8804_dai, 0, 0, + WM8804_CLKOUT_HZ, clk_freq); + if (ret) { + dev_err(card->dev, + "Failed to set WM8804 PLL to %d: %d\n", clk_freq, ret); + return ret; + } + + /* Set MCLK as PLL Output */ + ret = snd_soc_dai_set_sysclk(wm8804_dai, + WM8804_TX_CLKSRC_PLL, clk_freq, 0); + if (ret) { + dev_err(card->dev, + "Failed to set MCLK as PLL Output: %d\n", ret); + return ret; + } + + return ret; +} + +static int rpi_cirrus_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card); + unsigned int min_rate = min_rates[priv->min_rate_idx].value; + unsigned int max_rate = max_rates[priv->max_rate_idx].value; + + if (min_rate || max_rate) { + if (max_rate == 0) + max_rate = UINT_MAX; + + dev_dbg(card->dev, + "startup: limiting rate to %u-%u\n", + min_rate, max_rate); + + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, min_rate, max_rate); + } + + return 0; +} + +static struct snd_soc_pcm_stream rpi_cirrus_dai_link2_params = { + .formats = SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 2, + .channels_max = 2, + .rate_min = RPI_CIRRUS_DEFAULT_RATE, + .rate_max = RPI_CIRRUS_DEFAULT_RATE, +}; + +static int rpi_cirrus_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_dai *bcm_i2s_dai = snd_soc_rtd_to_cpu(rtd, 0); + struct snd_soc_component *wm5102_component = snd_soc_rtd_to_codec(rtd, 0)->component; + struct snd_soc_dai *wm8804_dai = snd_soc_rtd_to_codec(get_wm8804_runtime(card), 0); + + int ret; + + unsigned int width = snd_pcm_format_width(params_format(params)); + unsigned int rate = params_rate(params); + unsigned int clk_freq = calc_sysclk(rate); + + /* Using powers of 2 allows for an integer clock divisor */ + width = width <= 16 ? 16 : 32; + + mutex_lock(&priv->lock); + + dev_dbg(card->dev, "hw_params: setting rate to %d\n", rate); + + ret = snd_soc_dai_set_bclk_ratio(bcm_i2s_dai, 2 * width); + if (ret) { + dev_err(card->dev, "set_bclk_ratio failed: %d\n", ret); + goto out; + } + + ret = snd_soc_dai_set_tdm_slot(snd_soc_rtd_to_codec(rtd, 0), 0x03, 0x03, 2, width); + if (ret) { + dev_err(card->dev, "set_tdm_slot failed: %d\n", ret); + goto out; + } + + /* WM8804 supports sample rates from 32k only */ + if (rate >= 32000) { + ret = rpi_cirrus_set_wm8804_pll(card, wm8804_dai, rate); + if (ret) + goto out; + } + + ret = snd_soc_component_set_sysclk(wm5102_component, + ARIZONA_CLK_SYSCLK, + ARIZONA_CLK_SRC_FLL1, + clk_freq, + SND_SOC_CLOCK_IN); + if (ret) { + dev_err(card->dev, "Failed to set SYSCLK: %d\n", ret); + goto out; + } + + if ((priv->fll1_freq > 0) && (priv->fll1_freq != clk_freq)) { + dev_dbg(card->dev, + "hw_params: changing FLL1 from %d to %d\n", + priv->fll1_freq, clk_freq); + + if (rpi_cirrus_clear_flls(card, wm5102_component)) { + dev_err(card->dev, "hw_params: failed to clear FLLs\n"); + goto out; + } + + if (rpi_cirrus_set_fll(card, wm5102_component, clk_freq)) { + dev_err(card->dev, "hw_params: failed to set FLL\n"); + goto out; + } + + priv->fll1_freq = clk_freq; + } + + priv->card_rate = rate; + rpi_cirrus_dai_link2_params.rate_min = rate; + rpi_cirrus_dai_link2_params.rate_max = rate; + + priv->params_set |= 1 << substream->stream; + +out: + mutex_unlock(&priv->lock); + + return ret; +} + +static int rpi_cirrus_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_component *wm5102_component = snd_soc_rtd_to_codec(rtd, 0)->component; + int ret; + unsigned int old_params_set = priv->params_set; + + priv->params_set &= ~(1 << substream->stream); + + /* disable sysclk if this was the last open stream */ + if (priv->params_set == 0 && old_params_set) { + dev_dbg(card->dev, + "hw_free: Setting SYSCLK to Zero\n"); + + ret = snd_soc_component_set_sysclk(wm5102_component, + ARIZONA_CLK_SYSCLK, + ARIZONA_CLK_SRC_FLL1, + 0, + SND_SOC_CLOCK_IN); + if (ret) + dev_err(card->dev, + "hw_free: Failed to set SYSCLK to Zero: %d\n", + ret); + } + return 0; +} + +static int rpi_cirrus_init_wm5102(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + int ret; + + /* no 32kHz input, derive it from sysclk if needed */ + snd_soc_component_update_bits(component, + ARIZONA_CLOCK_32K_1, ARIZONA_CLK_32K_SRC_MASK, 2); + + if (rpi_cirrus_clear_flls(rtd->card, component)) + dev_warn(rtd->card->dev, + "init_wm5102: failed to clear FLLs\n"); + + ret = snd_soc_component_set_sysclk(component, + ARIZONA_CLK_SYSCLK, ARIZONA_CLK_SRC_FLL1, + 0, SND_SOC_CLOCK_IN); + if (ret) { + dev_err(rtd->card->dev, + "Failed to set SYSCLK to Zero: %d\n", ret); + return ret; + } + + return 0; +} + +static int rpi_cirrus_init_wm8804(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + struct snd_soc_component *component = codec_dai->component; + struct snd_soc_card *card = rtd->card; + struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card); + unsigned int val, mask; + int i, ret; + + for (i = 0; i < 4; i++) { + val = snd_soc_component_read(component, + WM8804_SPDTX1 + i); + mask = (i == 3) ? 0x3f : 0xff; + priv->iec958_status[i] = val & mask; + } + + /* Setup for 256fs */ + ret = snd_soc_dai_set_clkdiv(codec_dai, + WM8804_MCLK_DIV, WM8804_MCLKDIV_256FS); + if (ret) { + dev_err(card->dev, + "init_wm8804: Failed to set MCLK_DIV to 256fs: %d\n", + ret); + return ret; + } + + /* Output OSC on CLKOUT */ + ret = snd_soc_dai_set_sysclk(codec_dai, + WM8804_CLKOUT_SRC_OSCCLK, WM8804_CLKOUT_HZ, 0); + if (ret) + dev_err(card->dev, + "init_wm8804: Failed to set CLKOUT as OSC Frequency: %d\n", + ret); + + /* Init PLL with default samplerate */ + ret = rpi_cirrus_set_wm8804_pll(card, codec_dai, + RPI_CIRRUS_DEFAULT_RATE); + if (ret) + dev_err(card->dev, + "init_wm8804: Failed to setup PLL for %dHz: %d\n", + RPI_CIRRUS_DEFAULT_RATE, ret); + + return ret; +} + +static struct snd_soc_ops rpi_cirrus_ops = { + .startup = rpi_cirrus_startup, + .hw_params = rpi_cirrus_hw_params, + .hw_free = rpi_cirrus_hw_free, +}; + +SND_SOC_DAILINK_DEFS(wm5102, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC("wm5102-codec", "wm5102-aif1")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(wm8804, + DAILINK_COMP_ARRAY(COMP_CPU("wm5102-aif2")), + DAILINK_COMP_ARRAY(COMP_CODEC("wm8804.1-003b", "wm8804-spdif"))); + +static struct snd_soc_dai_link rpi_cirrus_dai[] = { + [DAI_WM5102] = { + .name = "WM5102", + .stream_name = "WM5102 AiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM, + .ops = &rpi_cirrus_ops, + .init = rpi_cirrus_init_wm5102, + SND_SOC_DAILINK_REG(wm5102), + }, + [DAI_WM8804] = { + .name = "WM5102 SPDIF", + .stream_name = "SPDIF Tx/Rx", + .dai_fmt = SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM, + .ignore_suspend = 1, + .c2c_params = &rpi_cirrus_dai_link2_params, + .init = rpi_cirrus_init_wm8804, + SND_SOC_DAILINK_REG(wm8804), + }, +}; + + +static int rpi_cirrus_late_probe(struct snd_soc_card *card) +{ + struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_pcm_runtime *wm5102_runtime = get_wm5102_runtime(card); + struct snd_soc_pcm_runtime *wm8804_runtime = get_wm8804_runtime(card); + int ret; + + dev_dbg(card->dev, "iec958_bits: %02x %02x %02x %02x\n", + priv->iec958_status[0], + priv->iec958_status[1], + priv->iec958_status[2], + priv->iec958_status[3]); + + ret = snd_soc_dai_set_sysclk( + snd_soc_rtd_to_codec(wm5102_runtime, 0), ARIZONA_CLK_SYSCLK, 0, 0); + if (ret) { + dev_err(card->dev, + "Failed to set WM5102 codec dai clk domain: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk( + snd_soc_rtd_to_cpu(wm8804_runtime, 0), ARIZONA_CLK_SYSCLK, 0, 0); + if (ret) + dev_err(card->dev, + "Failed to set WM8804 codec dai clk domain: %d\n", ret); + + return ret; +} + +/* audio machine driver */ +static struct snd_soc_card rpi_cirrus_card = { + .name = "RPi-Cirrus", + .driver_name = "RPiCirrus", + .owner = THIS_MODULE, + .dai_link = rpi_cirrus_dai, + .num_links = ARRAY_SIZE(rpi_cirrus_dai), + .late_probe = rpi_cirrus_late_probe, + .controls = rpi_cirrus_controls, + .num_controls = ARRAY_SIZE(rpi_cirrus_controls), + .dapm_widgets = rpi_cirrus_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(rpi_cirrus_dapm_widgets), + .dapm_routes = rpi_cirrus_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(rpi_cirrus_dapm_routes), + .set_bias_level = rpi_cirrus_set_bias_level, + .set_bias_level_post = rpi_cirrus_set_bias_level_post, +}; + +static int rpi_cirrus_probe(struct platform_device *pdev) +{ + int ret = 0; + struct rpi_cirrus_priv *priv; + struct device_node *i2s_node; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->min_rate_idx = 1; /* min samplerate 32kHz */ + priv->card_rate = RPI_CIRRUS_DEFAULT_RATE; + + mutex_init(&priv->lock); + + snd_soc_card_set_drvdata(&rpi_cirrus_card, priv); + + if (!pdev->dev.of_node) + return -ENODEV; + + i2s_node = of_parse_phandle( + pdev->dev.of_node, "i2s-controller", 0); + if (!i2s_node) { + dev_err(&pdev->dev, "i2s-controller missing in DT\n"); + return -ENODEV; + } + + rpi_cirrus_dai[DAI_WM5102].cpus->of_node = i2s_node; + rpi_cirrus_dai[DAI_WM5102].platforms->of_node = i2s_node; + + rpi_cirrus_card.dev = &pdev->dev; + + ret = devm_snd_soc_register_card(&pdev->dev, &rpi_cirrus_card); + if (ret) { + if (ret == -EPROBE_DEFER) + dev_dbg(&pdev->dev, + "register card requested probe deferral\n"); + else + dev_err(&pdev->dev, + "Failed to register card: %d\n", ret); + } + + return ret; +} + +static const struct of_device_id rpi_cirrus_of_match[] = { + { .compatible = "wlf,rpi-cirrus", }, + {}, +}; +MODULE_DEVICE_TABLE(of, rpi_cirrus_of_match); + +static struct platform_driver rpi_cirrus_driver = { + .driver = { + .name = "snd-rpi-cirrus", + .of_match_table = of_match_ptr(rpi_cirrus_of_match), + }, + .probe = rpi_cirrus_probe, +}; + +module_platform_driver(rpi_cirrus_driver); + +MODULE_AUTHOR("Matthias Reichl <hias@horus.com>"); +MODULE_DESCRIPTION("ASoC driver for Cirrus Logic Audio Card"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/bcm/rpi-proto.c b/sound/soc/bcm/rpi-proto.c new file mode 100644 index 00000000000000..d3cdadfbeec857 --- /dev/null +++ b/sound/soc/bcm/rpi-proto.c @@ -0,0 +1,147 @@ +/* + * ASoC driver for PROTO AudioCODEC (with a WM8731) + * connected to a Raspberry Pi + * + * Author: Florian Meier, <koalo@koalo.de> + * Copyright 2013 + * + * 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 <linux/module.h> +#include <linux/platform_device.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/jack.h> + +#include "../codecs/wm8731.h" + +static const unsigned int wm8731_rates_12288000[] = { + 8000, 32000, 48000, 96000, +}; + +static struct snd_pcm_hw_constraint_list wm8731_constraints_12288000 = { + .list = wm8731_rates_12288000, + .count = ARRAY_SIZE(wm8731_rates_12288000), +}; + +static int snd_rpi_proto_startup(struct snd_pcm_substream *substream) +{ + /* Setup constraints, because there is a 12.288 MHz XTAL on the board */ + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &wm8731_constraints_12288000); + return 0; +} + +static int snd_rpi_proto_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + int sysclk = 12288000; /* This is fixed on this board */ + + /* Set proto bclk */ + int ret = snd_soc_dai_set_bclk_ratio(cpu_dai,32*2); + if (ret < 0){ + dev_err(rtd->card->dev, + "Failed to set BCLK ratio %d\n", ret); + return ret; + } + + /* Set proto sysclk */ + ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK_XTAL, + sysclk, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(rtd->card->dev, + "Failed to set WM8731 SYSCLK: %d\n", ret); + return ret; + } + + return 0; +} + +/* machine stream operations */ +static struct snd_soc_ops snd_rpi_proto_ops = { + .startup = snd_rpi_proto_startup, + .hw_params = snd_rpi_proto_hw_params, +}; + +SND_SOC_DAILINK_DEFS(rpi_proto, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("wm8731.1-001a", "wm8731-hifi")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0"))); + +static struct snd_soc_dai_link snd_rpi_proto_dai[] = { +{ + .name = "WM8731", + .stream_name = "WM8731 HiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM, + .ops = &snd_rpi_proto_ops, + SND_SOC_DAILINK_REG(rpi_proto), +}, +}; + +/* audio machine driver */ +static struct snd_soc_card snd_rpi_proto = { + .name = "snd_rpi_proto", + .owner = THIS_MODULE, + .dai_link = snd_rpi_proto_dai, + .num_links = ARRAY_SIZE(snd_rpi_proto_dai), +}; + +static int snd_rpi_proto_probe(struct platform_device *pdev) +{ + int ret = 0; + + snd_rpi_proto.dev = &pdev->dev; + + if (pdev->dev.of_node) { + struct device_node *i2s_node; + struct snd_soc_dai_link *dai = &snd_rpi_proto_dai[0]; + i2s_node = of_parse_phandle(pdev->dev.of_node, + "i2s-controller", 0); + + if (i2s_node) { + dai->cpus->dai_name = NULL; + dai->cpus->of_node = i2s_node; + dai->platforms->name = NULL; + dai->platforms->of_node = i2s_node; + } + } + + ret = devm_snd_soc_register_card(&pdev->dev, &snd_rpi_proto); + if (ret && ret != -EPROBE_DEFER) + dev_err(&pdev->dev, + "snd_soc_register_card() failed: %d\n", ret); + + return ret; +} + +static const struct of_device_id snd_rpi_proto_of_match[] = { + { .compatible = "rpi,rpi-proto", }, + {}, +}; +MODULE_DEVICE_TABLE(of, snd_rpi_proto_of_match); + +static struct platform_driver snd_rpi_proto_driver = { + .driver = { + .name = "snd-rpi-proto", + .owner = THIS_MODULE, + .of_match_table = snd_rpi_proto_of_match, + }, + .probe = snd_rpi_proto_probe, +}; + +module_platform_driver(snd_rpi_proto_driver); + +MODULE_AUTHOR("Florian Meier"); +MODULE_DESCRIPTION("ASoC Driver for Raspberry Pi connected to PROTO board (WM8731)"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/bcm/rpi-simple-soundcard.c b/sound/soc/bcm/rpi-simple-soundcard.c new file mode 100644 index 00000000000000..b7dc818716fa4b --- /dev/null +++ b/sound/soc/bcm/rpi-simple-soundcard.c @@ -0,0 +1,590 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * rpi-simple-soundcard.c -- ALSA SoC Raspberry Pi soundcard. + * + * Copyright (C) 2018 Raspberry Pi. + * + * Authors: Tim Gover <tim.gover@raspberrypi.org> + * + * Based on code: + * hifiberry_amp.c, hifiberry_dac.c, rpi-dac.c + * by Florian Meier <florian.meier@koalo.de> + * + * googlevoicehat-soundcard.c + * by Peter Malkin <petermalkin@google.com> + * + * adau1977-adc.c + * by Andrey Grodzovsky <andrey2805@gmail.com> + * + * merus-amp.c + * by Ariel Muszkat <ariel.muszkat@gmail.com> + * Jorgen Kragh Jakobsen <jorgen.kraghjakobsen@infineon.com> + * + * 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 <linux/module.h> +#include <linux/platform_device.h> +#include <linux/gpio/consumer.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +/* Parameters for generic RPI functions */ +struct snd_rpi_simple_drvdata { + struct snd_soc_dai_link *dai; + const char* card_name; + unsigned int fixed_bclk_ratio; +}; + +static struct snd_soc_card snd_rpi_simple = { + .driver_name = "RPi-simple", + .owner = THIS_MODULE, + .dai_link = NULL, + .num_links = 1, /* Only a single DAI supported at the moment */ +}; + +static int snd_rpi_simple_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_rpi_simple_drvdata *drvdata = + snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + + if (drvdata->fixed_bclk_ratio > 0) + return snd_soc_dai_set_bclk_ratio(cpu_dai, + drvdata->fixed_bclk_ratio); + + return 0; +} + +static int pifi_mini_210_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *dac; + struct gpio_desc *pdn_gpio, *rst_gpio; + struct snd_soc_dai *codec_dai; + int ret; + + snd_rpi_simple_init(rtd); + codec_dai = snd_soc_rtd_to_codec(rtd, 0); + + dac = codec_dai[0].component; + + pdn_gpio = devm_gpiod_get_optional(snd_rpi_simple.dev, "pdn", + GPIOD_OUT_LOW); + if (IS_ERR(pdn_gpio)) { + ret = PTR_ERR(pdn_gpio); + dev_err(snd_rpi_simple.dev, "failed to get pdn gpio: %d\n", ret); + return ret; + } + + rst_gpio = devm_gpiod_get_optional(snd_rpi_simple.dev, "rst", + GPIOD_OUT_LOW); + if (IS_ERR(rst_gpio)) { + ret = PTR_ERR(rst_gpio); + dev_err(snd_rpi_simple.dev, "failed to get rst gpio: %d\n", ret); + return ret; + } + + // Set up cards - pulse power down and reset first, then + // set up according to datasheet + gpiod_set_value_cansleep(pdn_gpio, 1); + gpiod_set_value_cansleep(rst_gpio, 1); + usleep_range(1000, 10000); + gpiod_set_value_cansleep(pdn_gpio, 0); + usleep_range(20000, 30000); + gpiod_set_value_cansleep(rst_gpio, 0); + usleep_range(20000, 30000); + + // Oscillator trim + snd_soc_component_write(dac, 0x1b, 0); + usleep_range(60000, 80000); + + // MCLK at 64fs, sample rate 44.1 or 48kHz + snd_soc_component_write(dac, 0x00, 0x60); + + // Set up for BTL - AD/BD mode - AD is 0x00107772, BD is 0x00987772 + snd_soc_component_write(dac, 0x20, 0x00107772); + + // End mute + snd_soc_component_write(dac, 0x05, 0x00); + + return 0; +} + +static int snd_rpi_simple_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + struct snd_rpi_simple_drvdata *drvdata; + unsigned int sample_bits; + + drvdata = snd_soc_card_get_drvdata(rtd->card); + + if (drvdata->fixed_bclk_ratio > 0) + return 0; // BCLK is configured in .init + + /* The simple drivers just set the bclk_ratio to sample_bits * 2 so + * hard-code this for now, but sticking to powers of 2 to allow for + * integer clock divisors. More complex drivers could just replace + * the hw_params routine. + */ + sample_bits = snd_pcm_format_width(params_format(params)); + sample_bits = sample_bits <= 16 ? 16 : 32; + + return snd_soc_dai_set_bclk_ratio(cpu_dai, sample_bits * 2); +} + +static struct snd_soc_ops snd_rpi_simple_ops = { + .hw_params = snd_rpi_simple_hw_params, +}; + +static int snd_merus_amp_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + int rate; + + rate = params_rate(params); + if (rate > 48000) { + dev_err(rtd->card->dev, + "Unsupported samplerate %d\n", + rate); + return -EINVAL; + } + return 0; +} + +static struct snd_soc_ops snd_merus_amp_ops = { + .hw_params = snd_merus_amp_hw_params, +}; + +enum adau1977_clk_id { + ADAU1977_SYSCLK, +}; + +enum adau1977_sysclk_src { + ADAU1977_SYSCLK_SRC_MCLK, + ADAU1977_SYSCLK_SRC_LRCLK, +}; + +static int adau1977_init(struct snd_soc_pcm_runtime *rtd) +{ + int ret; + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + + ret = snd_soc_dai_set_tdm_slot(codec_dai, 0, 0, 0, 0); + if (ret < 0) + return ret; + + return snd_soc_component_set_sysclk(codec_dai->component, + ADAU1977_SYSCLK, ADAU1977_SYSCLK_SRC_MCLK, + 11289600, SND_SOC_CLOCK_IN); +} + +SND_SOC_DAILINK_DEFS(adau1977, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC("adau1977.1-0011", "adau1977-hifi")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link snd_rpi_adau1977_dai[] = { + { + .name = "adau1977", + .stream_name = "ADAU1977", + .init = adau1977_init, + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM, + SND_SOC_DAILINK_REG(adau1977), + }, +}; + +static struct snd_rpi_simple_drvdata drvdata_adau1977 = { + .card_name = "snd_rpi_adau1977_adc", + .dai = snd_rpi_adau1977_dai, +}; + +SND_SOC_DAILINK_DEFS(gvchat, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC("voicehat-codec", "voicehat-hifi")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link snd_googlevoicehat_soundcard_dai[] = { +{ + .name = "Google voiceHAT SoundCard", + .stream_name = "Google voiceHAT SoundCard HiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAILINK_REG(gvchat), +}, +}; + +static struct snd_rpi_simple_drvdata drvdata_googlevoicehat = { + .card_name = "snd_rpi_googlevoicehat_soundcard", + .dai = snd_googlevoicehat_soundcard_dai, +}; + +SND_SOC_DAILINK_DEFS(hifiberry_dacplusdsp, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC("dacplusdsp-codec", "dacplusdsp-hifi")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link snd_hifiberrydacplusdsp_soundcard_dai[] = { +{ + .name = "Hifiberry DAC+DSP SoundCard", + .stream_name = "Hifiberry DAC+DSP SoundCard HiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAILINK_REG(hifiberry_dacplusdsp), +}, +}; + +static struct snd_rpi_simple_drvdata drvdata_hifiberrydacplusdsp = { + .card_name = "snd_rpi_hifiberrydacplusdsp_soundcard", + .dai = snd_hifiberrydacplusdsp_soundcard_dai, +}; + +SND_SOC_DAILINK_DEFS(hifiberry_adc, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC("snd-soc-dummy", "snd-soc-dummy-dai")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static int hifiberry_adc8x_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + + /* set limits of 8 channels and 192ksps sample rate + */ + codec_dai->driver->capture.channels_max = 8; + codec_dai->driver->capture.rates = SNDRV_PCM_RATE_8000_192000; + + return 0; +} + +static struct snd_soc_dai_link snd_hifiberry_adc8x_dai[] = { + { + .name = "HifiBerry ADC8x", + .stream_name = "HifiBerry ADC8x HiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .init = hifiberry_adc8x_init, + SND_SOC_DAILINK_REG(hifiberry_adc), + }, +}; + +static struct snd_rpi_simple_drvdata drvdata_hifiberry_adc8x = { + .card_name = "snd_rpi_hifiberry_adc8x", + .dai = snd_hifiberry_adc8x_dai, + .fixed_bclk_ratio = 64, +}; + +SND_SOC_DAILINK_DEFS(hifiberry_amp, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC("tas5713.1-001b", "tas5713-hifi")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link snd_hifiberry_amp_dai[] = { + { + .name = "HifiBerry AMP", + .stream_name = "HifiBerry AMP HiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAILINK_REG(hifiberry_amp), + }, +}; + +static struct snd_rpi_simple_drvdata drvdata_hifiberry_amp = { + .card_name = "snd_rpi_hifiberry_amp", + .dai = snd_hifiberry_amp_dai, + .fixed_bclk_ratio = 64, +}; + +SND_SOC_DAILINK_DEFS(hifiberry_amp3, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC("ma120x0p.1-0020", "ma120x0p-amp")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link snd_hifiberry_amp3_dai[] = { + { + .name = "HifiberryAmp3", + .stream_name = "Hifiberry Amp3", + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAILINK_REG(hifiberry_amp3), + }, +}; + +static struct snd_rpi_simple_drvdata drvdata_hifiberry_amp3 = { + .card_name = "snd_rpi_hifiberry_amp3", + .dai = snd_hifiberry_amp3_dai, + .fixed_bclk_ratio = 64, +}; + +SND_SOC_DAILINK_DEFS(hifiberry_dac, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC("pcm5102a-codec", "pcm5102a-hifi")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link snd_hifiberry_dac_dai[] = { + { + .name = "HifiBerry DAC", + .stream_name = "HifiBerry DAC HiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAILINK_REG(hifiberry_dac), + }, +}; + +static struct snd_rpi_simple_drvdata drvdata_hifiberry_dac = { + .card_name = "snd_rpi_hifiberry_dac", + .dai = snd_hifiberry_dac_dai, +}; + +SND_SOC_DAILINK_DEFS(hifiberry_dac8x, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC("snd-soc-dummy", "snd-soc-dummy-dai")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static int hifiberry_dac8x_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + struct snd_soc_card *card = rtd->card; + struct gpio_desc *gpio_desc; + bool has_adc; + + /* Configure the codec for 8 channel playback */ + codec_dai->driver->playback.channels_max = 8; + codec_dai->driver->playback.rates = SNDRV_PCM_RATE_8000_192000; + + /* Activate capture based on ADC8x detection */ + gpio_desc = devm_gpiod_get(card->dev, "hasadc", GPIOD_IN); + if (IS_ERR(gpio_desc)) { + dev_err(card->dev, "Failed to get GPIO: %ld\n", PTR_ERR(gpio_desc)); + return PTR_ERR(gpio_desc); + } + + has_adc = gpiod_get_value(gpio_desc); + + if (has_adc) { + struct snd_soc_dai_link *dai = rtd->dai_link; + + dev_info(card->dev, "ADC8x detected: capture enabled\n"); + codec_dai->driver->symmetric_rate = 1; + codec_dai->driver->symmetric_channels = 1; + codec_dai->driver->symmetric_sample_bits = 1; + codec_dai->driver->capture.rates = SNDRV_PCM_RATE_8000_192000; + dai->name = "HiFiBerry DAC8xADC8x"; + dai->stream_name = "HiFiBerry DAC8xADC8x HiFi"; + } else { + dev_info(card->dev, "no ADC8x detected\n"); + rtd->dai_link->playback_only = 1; // Disable capture + } + + return 0; +} + +static struct snd_soc_dai_link snd_hifiberry_dac8x_dai[] = { + { + .name = "HifiBerry DAC8x", + .stream_name = "HifiBerry DAC8x HiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .init = hifiberry_dac8x_init, + SND_SOC_DAILINK_REG(hifiberry_dac8x), + }, +}; + +static struct snd_rpi_simple_drvdata drvdata_hifiberry_dac8x = { + .card_name = "snd_rpi_hifiberry_dac8x", + .dai = snd_hifiberry_dac8x_dai, + .fixed_bclk_ratio = 64, +}; + +SND_SOC_DAILINK_DEFS(dionaudio_kiwi, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC("pcm1794a-codec", "pcm1794a-hifi")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link snd_dionaudio_kiwi_dai[] = { +{ + .name = "DionAudio KIWI", + .stream_name = "DionAudio KIWI STREAMER", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAILINK_REG(dionaudio_kiwi), +}, +}; + +static struct snd_rpi_simple_drvdata drvdata_dionaudio_kiwi = { + .card_name = "snd_rpi_dionaudio_kiwi", + .dai = snd_dionaudio_kiwi_dai, + .fixed_bclk_ratio = 64, +}; + +SND_SOC_DAILINK_DEFS(rpi_dac, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC("pcm1794a-codec", "pcm1794a-hifi")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link snd_rpi_dac_dai[] = { +{ + .name = "RPi-DAC", + .stream_name = "RPi-DAC HiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAILINK_REG(rpi_dac), +}, +}; + +static struct snd_rpi_simple_drvdata drvdata_rpi_dac = { + .card_name = "snd_rpi_rpi_dac", + .dai = snd_rpi_dac_dai, + .fixed_bclk_ratio = 64, +}; + +SND_SOC_DAILINK_DEFS(merus_amp, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC("ma120x0p.1-0020", "ma120x0p-amp")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link snd_merus_amp_dai[] = { + { + .name = "MerusAmp", + .stream_name = "Merus Audio Amp", + .ops = &snd_merus_amp_ops, + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAILINK_REG(merus_amp), + }, +}; + +static struct snd_rpi_simple_drvdata drvdata_merus_amp = { + .card_name = "snd_rpi_merus_amp", + .dai = snd_merus_amp_dai, + .fixed_bclk_ratio = 64, +}; + +SND_SOC_DAILINK_DEFS(pifi_mini_210, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC("tas571x.1-001a", "tas571x-hifi")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link snd_pifi_mini_210_dai[] = { + { + .name = "PiFi Mini 210", + .stream_name = "PiFi Mini 210 HiFi", + .init = pifi_mini_210_init, + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAILINK_REG(pifi_mini_210), + }, +}; + +static struct snd_rpi_simple_drvdata drvdata_pifi_mini_210 = { + .card_name = "snd_pifi_mini_210", + .dai = snd_pifi_mini_210_dai, + .fixed_bclk_ratio = 64, +}; + +static const struct of_device_id snd_rpi_simple_of_match[] = { + { .compatible = "adi,adau1977-adc", + .data = (void *) &drvdata_adau1977 }, + { .compatible = "googlevoicehat,googlevoicehat-soundcard", + .data = (void *) &drvdata_googlevoicehat }, + { .compatible = "hifiberrydacplusdsp,hifiberrydacplusdsp-soundcard", + .data = (void *) &drvdata_hifiberrydacplusdsp }, + { .compatible = "hifiberry,hifiberry-adc8x", + .data = (void *) &drvdata_hifiberry_adc8x }, + { .compatible = "hifiberry,hifiberry-amp", + .data = (void *) &drvdata_hifiberry_amp }, + { .compatible = "hifiberry,hifiberry-amp3", + .data = (void *) &drvdata_hifiberry_amp3 }, + { .compatible = "hifiberry,hifiberry-dac", + .data = (void *) &drvdata_hifiberry_dac }, + { .compatible = "hifiberry,hifiberry-dac8x", + .data = (void *) &drvdata_hifiberry_dac8x }, + { .compatible = "dionaudio,dionaudio-kiwi", + .data = (void *) &drvdata_dionaudio_kiwi }, + { .compatible = "rpi,rpi-dac", &drvdata_rpi_dac}, + { .compatible = "merus,merus-amp", + .data = (void *) &drvdata_merus_amp }, + { .compatible = "pifi,pifi-mini-210", + .data = (void *) &drvdata_pifi_mini_210 }, + {}, +}; + +static int snd_rpi_simple_probe(struct platform_device *pdev) +{ + int ret = 0; + const struct of_device_id *of_id; + + snd_rpi_simple.dev = &pdev->dev; + of_id = of_match_node(snd_rpi_simple_of_match, pdev->dev.of_node); + + if (pdev->dev.of_node && of_id->data) { + struct device_node *i2s_node; + struct snd_rpi_simple_drvdata *drvdata = + (struct snd_rpi_simple_drvdata *) of_id->data; + struct snd_soc_dai_link *dai = drvdata->dai; + + snd_soc_card_set_drvdata(&snd_rpi_simple, drvdata); + + /* More complex drivers might override individual functions */ + if (!dai->init) + dai->init = snd_rpi_simple_init; + if (!dai->ops) + dai->ops = &snd_rpi_simple_ops; + + snd_rpi_simple.name = drvdata->card_name; + + snd_rpi_simple.dai_link = dai; + i2s_node = of_parse_phandle(pdev->dev.of_node, + "i2s-controller", 0); + if (!i2s_node) { + pr_err("Failed to find i2s-controller DT node\n"); + return -ENODEV; + } + + dai->cpus->of_node = i2s_node; + dai->platforms->of_node = i2s_node; + } + + ret = devm_snd_soc_register_card(&pdev->dev, &snd_rpi_simple); + if (ret && ret != -EPROBE_DEFER) + dev_err(&pdev->dev, "Failed to register card %d\n", ret); + + return ret; +} + +static struct platform_driver snd_rpi_simple_driver = { + .driver = { + .name = "snd-rpi-simple", + .owner = THIS_MODULE, + .of_match_table = snd_rpi_simple_of_match, + }, + .probe = snd_rpi_simple_probe, +}; +MODULE_DEVICE_TABLE(of, snd_rpi_simple_of_match); + +module_platform_driver(snd_rpi_simple_driver); + +MODULE_AUTHOR("Tim Gover <tim.gover@raspberrypi.org>"); +MODULE_DESCRIPTION("ASoC Raspberry Pi simple soundcard driver "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/rpi-wm8804-soundcard.c b/sound/soc/bcm/rpi-wm8804-soundcard.c new file mode 100644 index 00000000000000..3ecbf7adf7af88 --- /dev/null +++ b/sound/soc/bcm/rpi-wm8804-soundcard.c @@ -0,0 +1,549 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * rpi--wm8804.c -- ALSA SoC Raspberry Pi soundcard. + * + * Copyright (C) 2018 Raspberry Pi. + * + * Authors: Tim Gover <tim.gover@raspberrypi.org> + * + * Generic driver for Pi Hat WM8804 digi sounds cards + * + * Based upon code from: + * justboom-digi.c + * by Milan Neskovic <info@justboom.co> + * + * iqaudio_digi.c + * by Daniel Matuschek <info@crazy-audio.com> + * + * allo-digione.c + * by Baswaraj <jaikumar@cem-solutions.net> + * + * hifiberry-digi.c + * Daniel Matuschek <info@crazy-audio.com> + * + * 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 <linux/gpio/consumer.h> +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/delay.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include "../codecs/wm8804.h" + +struct wm8804_clk_cfg { + unsigned int sysclk_freq; + unsigned int mclk_freq; + unsigned int mclk_div; +}; + +/* Parameters for generic functions */ +struct snd_rpi_wm8804_drvdata { + /* Required - pointer to the DAI structure */ + struct snd_soc_dai_link *dai; + /* Required - snd_soc_card name */ + const char *card_name; + /* Optional DT node names if card info is defined in DT */ + const char *card_name_dt; + const char *dai_name_dt; + const char *dai_stream_name_dt; + /* Optional probe extension - called prior to register_card */ + int (*probe)(struct platform_device *pdev); +}; + +static struct gpio_desc *snd_clk44gpio; +static struct gpio_desc *snd_clk48gpio; +static int wm8804_samplerate = 0; +static struct gpio_desc *led_gpio_1; +static struct gpio_desc *led_gpio_2; +static struct gpio_desc *led_gpio_3; +static struct gpio_desc *custom_reset; + +/* Forward declarations */ +static struct snd_soc_dai_link snd_allo_digione_dai[]; +static struct snd_soc_card snd_rpi_wm8804; + + +#define CLK_44EN_RATE 22579200UL +#define CLK_48EN_RATE 24576000UL + +static const char * const wm8805_input_select_text[] = { + "Rx 0", + "Rx 1", + "Rx 2", + "Rx 3", + "Rx 4", + "Rx 5", + "Rx 6", + "Rx 7" +}; + +static const unsigned int wm8805_input_channel_select_value[] = { + 0, 1, 2, 3, 4, 5, 6, 7 +}; + +static const struct soc_enum wm8805_input_channel_sel[] = { + SOC_VALUE_ENUM_SINGLE(WM8804_PLL6, 0, 7, ARRAY_SIZE(wm8805_input_select_text), + wm8805_input_select_text, wm8805_input_channel_select_value), +}; + +static const struct snd_kcontrol_new wm8805_input_controls_card[] = { + SOC_ENUM("Select Input Channel", wm8805_input_channel_sel[0]), +}; + +static int wm8805_add_input_controls(struct snd_soc_component *component) +{ + snd_soc_add_component_controls(component, wm8805_input_controls_card, + ARRAY_SIZE(wm8805_input_controls_card)); + return 0; +} + +static unsigned int snd_rpi_wm8804_enable_clock(unsigned int samplerate) +{ + switch (samplerate) { + case 11025: + case 22050: + case 44100: + case 88200: + case 176400: + gpiod_set_value_cansleep(snd_clk44gpio, 1); + gpiod_set_value_cansleep(snd_clk48gpio, 0); + return CLK_44EN_RATE; + default: + gpiod_set_value_cansleep(snd_clk48gpio, 1); + gpiod_set_value_cansleep(snd_clk44gpio, 0); + return CLK_48EN_RATE; + } +} + +static void snd_rpi_wm8804_clk_cfg(unsigned int samplerate, + struct wm8804_clk_cfg *clk_cfg) +{ + clk_cfg->sysclk_freq = 27000000; + + if (samplerate <= 96000 || + snd_rpi_wm8804.dai_link == snd_allo_digione_dai) { + clk_cfg->mclk_freq = samplerate * 256; + clk_cfg->mclk_div = WM8804_MCLKDIV_256FS; + } else { + clk_cfg->mclk_freq = samplerate * 128; + clk_cfg->mclk_div = WM8804_MCLKDIV_128FS; + } + + if (!(IS_ERR(snd_clk44gpio) || IS_ERR(snd_clk48gpio))) + clk_cfg->sysclk_freq = snd_rpi_wm8804_enable_clock(samplerate); +} + +static int snd_rpi_wm8804_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + int sampling_freq = 1; + int ret; + struct wm8804_clk_cfg clk_cfg; + int samplerate = params_rate(params); + + if (samplerate == wm8804_samplerate) + return 0; + + /* clear until all clocks are setup properly */ + wm8804_samplerate = 0; + + snd_rpi_wm8804_clk_cfg(samplerate, &clk_cfg); + + pr_debug("%s samplerate: %d mclk_freq: %u mclk_div: %u sysclk: %u\n", + __func__, samplerate, clk_cfg.mclk_freq, + clk_cfg.mclk_div, clk_cfg.sysclk_freq); + + switch (samplerate) { + case 32000: + sampling_freq = 0x03; + break; + case 44100: + sampling_freq = 0x00; + break; + case 48000: + sampling_freq = 0x02; + break; + case 88200: + sampling_freq = 0x08; + break; + case 96000: + sampling_freq = 0x0a; + break; + case 176400: + sampling_freq = 0x0c; + break; + case 192000: + sampling_freq = 0x0e; + break; + default: + dev_err(rtd->card->dev, + "Failed to set WM8804 SYSCLK, unsupported samplerate %d\n", + samplerate); + } + + snd_soc_dai_set_clkdiv(codec_dai, WM8804_MCLK_DIV, clk_cfg.mclk_div); + snd_soc_dai_set_pll(codec_dai, 0, 0, + clk_cfg.sysclk_freq, clk_cfg.mclk_freq); + + ret = snd_soc_dai_set_sysclk(codec_dai, WM8804_TX_CLKSRC_PLL, + clk_cfg.sysclk_freq, SND_SOC_CLOCK_OUT); + if (ret < 0) { + dev_err(rtd->card->dev, + "Failed to set WM8804 SYSCLK: %d\n", ret); + return ret; + } + + wm8804_samplerate = samplerate; + + /* set sampling frequency status bits */ + snd_soc_component_update_bits(component, WM8804_SPDTX4, 0x0f, + sampling_freq); + + return snd_soc_dai_set_bclk_ratio(cpu_dai, 64); +} + +static struct snd_soc_ops snd_rpi_wm8804_ops = { + .hw_params = snd_rpi_wm8804_hw_params, +}; + +static int snd_interlude_audio_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + int ret = snd_rpi_wm8804_hw_params(substream, params); + int samplerate = params_rate(params); + + switch (samplerate) { + case 44100: + gpiod_set_value_cansleep(led_gpio_1, 1); + gpiod_set_value_cansleep(led_gpio_2, 0); + gpiod_set_value_cansleep(led_gpio_3, 0); + break; + case 48000: + gpiod_set_value_cansleep(led_gpio_1, 1); + gpiod_set_value_cansleep(led_gpio_2, 0); + gpiod_set_value_cansleep(led_gpio_3, 0); + break; + case 88200: + gpiod_set_value_cansleep(led_gpio_1, 0); + gpiod_set_value_cansleep(led_gpio_2, 1); + gpiod_set_value_cansleep(led_gpio_3, 0); + break; + case 96000: + gpiod_set_value_cansleep(led_gpio_1, 0); + gpiod_set_value_cansleep(led_gpio_2, 1); + gpiod_set_value_cansleep(led_gpio_3, 0); + break; + case 176400: + gpiod_set_value_cansleep(led_gpio_1, 0); + gpiod_set_value_cansleep(led_gpio_2, 0); + gpiod_set_value_cansleep(led_gpio_3, 1); + break; + case 192000: + gpiod_set_value_cansleep(led_gpio_1, 0); + gpiod_set_value_cansleep(led_gpio_2, 0); + gpiod_set_value_cansleep(led_gpio_3, 1); + break; + default: + break; + } + return ret; +} + +const struct snd_soc_ops interlude_audio_digital_dai_ops = { + .hw_params = snd_interlude_audio_hw_params, +}; + +SND_SOC_DAILINK_DEFS(justboom_digi, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link snd_justboom_digi_dai[] = { +{ + .name = "JustBoom Digi", + .stream_name = "JustBoom Digi HiFi", + SND_SOC_DAILINK_REG(justboom_digi), +}, +}; + +static struct snd_rpi_wm8804_drvdata drvdata_justboom_digi = { + .card_name = "snd_rpi_justboom_digi", + .dai = snd_justboom_digi_dai, +}; + +SND_SOC_DAILINK_DEFS(iqaudio_digi, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link snd_iqaudio_digi_dai[] = { +{ + .name = "IQAudIO Digi", + .stream_name = "IQAudIO Digi HiFi", + SND_SOC_DAILINK_REG(iqaudio_digi), +}, +}; + +static struct snd_rpi_wm8804_drvdata drvdata_iqaudio_digi = { + .card_name = "IQAudIODigi", + .dai = snd_iqaudio_digi_dai, + .card_name_dt = "wm8804-digi,card-name", + .dai_name_dt = "wm8804-digi,dai-name", + .dai_stream_name_dt = "wm8804-digi,dai-stream-name", +}; + +static int snd_allo_digione_probe(struct platform_device *pdev) +{ + pr_debug("%s\n", __func__); + + if (IS_ERR(snd_clk44gpio) || IS_ERR(snd_clk48gpio)) { + dev_err(&pdev->dev, "devm_gpiod_get() failed\n"); + return -EINVAL; + } + return 0; +} + +SND_SOC_DAILINK_DEFS(allo_digione, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link snd_allo_digione_dai[] = { +{ + .name = "Allo DigiOne", + .stream_name = "Allo DigiOne HiFi", + SND_SOC_DAILINK_REG(allo_digione), +}, +}; + +static struct snd_rpi_wm8804_drvdata drvdata_allo_digione = { + .card_name = "snd_allo_digione", + .dai = snd_allo_digione_dai, + .probe = snd_allo_digione_probe, +}; + +SND_SOC_DAILINK_DEFS(hifiberry_digi, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link snd_hifiberry_digi_dai[] = { +{ + .name = "HifiBerry Digi", + .stream_name = "HifiBerry Digi HiFi", + SND_SOC_DAILINK_REG(hifiberry_digi), +}, +}; + +static int snd_hifiberry_digi_probe(struct platform_device *pdev) +{ + pr_debug("%s\n", __func__); + + if (IS_ERR(snd_clk44gpio) || IS_ERR(snd_clk48gpio)) + return 0; + + snd_hifiberry_digi_dai->name = "HiFiBerry Digi+ Pro"; + snd_hifiberry_digi_dai->stream_name = "HiFiBerry Digi+ Pro HiFi"; + return 0; +} + +static struct snd_rpi_wm8804_drvdata drvdata_hifiberry_digi = { + .card_name = "snd_rpi_hifiberry_digi", + .dai = snd_hifiberry_digi_dai, + .probe = snd_hifiberry_digi_probe, +}; + +SND_SOC_DAILINK_DEFS(interlude_audio_digital, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static int snd_interlude_audio_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; + int ret; + + ret = wm8805_add_input_controls(component); + if (ret != 0) + pr_err("failed to add input controls"); + + return 0; +} + + +static struct snd_soc_dai_link snd_interlude_audio_digital_dai[] = { +{ + .name = "Interlude Audio Digital", + .stream_name = "Interlude Audio Digital HiFi", + .init = snd_interlude_audio_init, + .ops = &interlude_audio_digital_dai_ops, + SND_SOC_DAILINK_REG(interlude_audio_digital), +}, +}; + + +static int snd_interlude_audio_digital_probe(struct platform_device *pdev) +{ + if (IS_ERR(snd_clk44gpio) || IS_ERR(snd_clk48gpio)) + return 0; + + custom_reset = devm_gpiod_get(&pdev->dev, "reset", GPIOD_OUT_LOW); + gpiod_set_value_cansleep(custom_reset, 0); + mdelay(10); + gpiod_set_value_cansleep(custom_reset, 1); + + snd_interlude_audio_digital_dai->name = "Interlude Audio Digital"; + snd_interlude_audio_digital_dai->stream_name = "Interlude Audio Digital HiFi"; + led_gpio_1 = devm_gpiod_get(&pdev->dev, "led1", GPIOD_OUT_LOW); + led_gpio_2 = devm_gpiod_get(&pdev->dev, "led2", GPIOD_OUT_LOW); + led_gpio_3 = devm_gpiod_get(&pdev->dev, "led3", GPIOD_OUT_LOW); + return 0; +} + + +static struct snd_rpi_wm8804_drvdata drvdata_interlude_audio_digital = { + .card_name = "snd_IA_Digital_Hat", + .dai = snd_interlude_audio_digital_dai, + .probe = snd_interlude_audio_digital_probe, +}; + +static const struct of_device_id snd_rpi_wm8804_of_match[] = { + { .compatible = "justboom,justboom-digi", + .data = (void *) &drvdata_justboom_digi }, + { .compatible = "iqaudio,wm8804-digi", + .data = (void *) &drvdata_iqaudio_digi }, + { .compatible = "allo,allo-digione", + .data = (void *) &drvdata_allo_digione }, + { .compatible = "hifiberry,hifiberry-digi", + .data = (void *) &drvdata_hifiberry_digi }, + { .compatible = "interludeaudio,interludeaudio-digital", + .data = (void *) &drvdata_interlude_audio_digital }, + {}, +}; + +static struct snd_soc_card snd_rpi_wm8804 = { + .driver_name = "RPi-WM8804", + .owner = THIS_MODULE, + .dai_link = NULL, + .num_links = 1, +}; + +static int snd_rpi_wm8804_probe(struct platform_device *pdev) +{ + int ret = 0; + const struct of_device_id *of_id; + + snd_rpi_wm8804.dev = &pdev->dev; + of_id = of_match_node(snd_rpi_wm8804_of_match, pdev->dev.of_node); + + if (pdev->dev.of_node && of_id->data) { + struct device_node *i2s_node; + struct snd_rpi_wm8804_drvdata *drvdata = + (struct snd_rpi_wm8804_drvdata *) of_id->data; + struct snd_soc_dai_link *dai = drvdata->dai; + + snd_soc_card_set_drvdata(&snd_rpi_wm8804, drvdata); + + if (!dai->ops) + dai->ops = &snd_rpi_wm8804_ops; + if (!dai->codecs->dai_name) + dai->codecs->dai_name = "wm8804-spdif"; + if (!dai->codecs->name) + dai->codecs->name = "wm8804.1-003b"; + if (!dai->dai_fmt) + dai->dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM; + + snd_rpi_wm8804.dai_link = dai; + i2s_node = of_parse_phandle(pdev->dev.of_node, + "i2s-controller", 0); + if (!i2s_node) { + pr_err("Failed to find i2s-controller DT node\n"); + return -ENODEV; + } + + snd_rpi_wm8804.name = drvdata->card_name; + + /* If requested by in drvdata get card & DAI names from DT */ + if (drvdata->card_name_dt) + of_property_read_string(i2s_node, + drvdata->card_name_dt, + &snd_rpi_wm8804.name); + + if (drvdata->dai_name_dt) + of_property_read_string(i2s_node, + drvdata->dai_name_dt, + &dai->name); + + if (drvdata->dai_stream_name_dt) + of_property_read_string(i2s_node, + drvdata->dai_stream_name_dt, + &dai->stream_name); + + dai->cpus->of_node = i2s_node; + dai->platforms->of_node = i2s_node; + + /* + * clk44gpio and clk48gpio are not required by all cards so + * don't check the error status. + */ + snd_clk44gpio = + devm_gpiod_get(&pdev->dev, "clock44", GPIOD_OUT_LOW); + + snd_clk48gpio = + devm_gpiod_get(&pdev->dev, "clock48", GPIOD_OUT_LOW); + + if (drvdata->probe) { + ret = drvdata->probe(pdev); + if (ret < 0) { + dev_err(&pdev->dev, "Custom probe failed %d\n", + ret); + return ret; + } + } + + pr_debug("%s card: %s dai: %s stream: %s\n", __func__, + snd_rpi_wm8804.name, + dai->name, dai->stream_name); + } + + ret = devm_snd_soc_register_card(&pdev->dev, &snd_rpi_wm8804); + if (ret && ret != -EPROBE_DEFER) + dev_err(&pdev->dev, "Failed to register card %d\n", ret); + + return ret; +} + +static struct platform_driver snd_rpi_wm8804_driver = { + .driver = { + .name = "snd-rpi-wm8804", + .owner = THIS_MODULE, + .of_match_table = snd_rpi_wm8804_of_match, + }, + .probe = snd_rpi_wm8804_probe, +}; +MODULE_DEVICE_TABLE(of, snd_rpi_wm8804_of_match); + +module_platform_driver(snd_rpi_wm8804_driver); + +MODULE_AUTHOR("Tim Gover <tim.gover@raspberrypi.org>"); +MODULE_DESCRIPTION("ASoC Raspberry Pi Hat generic digi driver for WM8804 based cards"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 0d9d1d250f2b5e..3acde34498c819 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -125,6 +125,7 @@ config SND_SOC_ALL_CODECS imply SND_SOC_IDT821034 imply SND_SOC_INNO_RK3036 imply SND_SOC_ISABELLE + imply SND_SOC_I_SABRE_CODEC imply SND_SOC_JZ4740_CODEC imply SND_SOC_JZ4725B_CODEC imply SND_SOC_JZ4760_CODEC @@ -132,6 +133,7 @@ config SND_SOC_ALL_CODECS imply SND_SOC_LM4857 imply SND_SOC_LM49453 imply SND_SOC_LOCHNAGAR_SC + imply SND_SOC_MA120X0P imply SND_SOC_MAX98088 imply SND_SOC_MAX98090 imply SND_SOC_MAX98095 @@ -175,6 +177,7 @@ config SND_SOC_ALL_CODECS imply SND_SOC_PCM179X_SPI imply SND_SOC_PCM186X_I2C imply SND_SOC_PCM186X_SPI + imply SND_SOC_PCM1794A imply SND_SOC_PCM3008 imply SND_SOC_PCM3060_I2C imply SND_SOC_PCM3060_SPI @@ -267,6 +270,7 @@ config SND_SOC_ALL_CODECS imply SND_SOC_TLV320ADCX140 imply SND_SOC_TLV320AIC23_I2C imply SND_SOC_TLV320AIC23_SPI + imply SND_SOC_TAS5713 imply SND_SOC_TLV320AIC26 imply SND_SOC_TLV320AIC31XX imply SND_SOC_TLV320AIC32X4_I2C @@ -424,12 +428,12 @@ config SND_SOC_AD193X tristate config SND_SOC_AD193X_SPI - tristate + tristate "Analog Devices AU193X CODEC - SPI" depends on SPI_MASTER select SND_SOC_AD193X config SND_SOC_AD193X_I2C - tristate + tristate "Analog Devices AU193X CODEC - I2C" depends on I2C select SND_SOC_AD193X @@ -1230,6 +1234,13 @@ config SND_SOC_LOCHNAGAR_SC This driver support the sound card functionality of the Cirrus Logic Lochnagar audio development board. +config SND_SOC_MA120X0P + tristate "Infineon Merus(TM) MA120X0P Multilevel Class-D Audio amplifiers" + depends on I2C + help + Enable support for Infineon MA120X0P Multilevel Class-D audio power + amplifiers. + config SND_SOC_MADERA tristate default y if SND_SOC_CS47L15=y @@ -1639,6 +1650,10 @@ config SND_SOC_RT5616 tristate "Realtek RT5616 CODEC" depends on I2C +config SND_SOC_PCM1794A + tristate + depends on I2C + config SND_SOC_RT5631 tristate "Realtek ALC5631/RT5631 CODEC" depends on I2C @@ -1996,6 +2011,9 @@ config SND_SOC_TFA9879 tristate "NXP Semiconductors TFA9879 amplifier" depends on I2C +config SND_SOC_TAS5713 + tristate + config SND_SOC_TFA989X tristate "NXP/Goodix TFA989X (TFA1) amplifiers" depends on I2C @@ -2597,4 +2615,8 @@ config SND_SOC_LPASS_TX_MACRO select SND_SOC_LPASS_MACRO_COMMON tristate "Qualcomm TX Macro in LPASS(Low Power Audio SubSystem)" +config SND_SOC_I_SABRE_CODEC + tristate "Audiophonics I-SABRE Codec" + depends on I2C + endmenu diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 69cb0b39f22007..14d8631ba30acd 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -820,3 +820,12 @@ obj-$(CONFIG_SND_SOC_LPASS_TX_MACRO) += snd-soc-lpass-tx-macro.o # Mux obj-$(CONFIG_SND_SOC_SIMPLE_MUX) += snd-soc-simple-mux.o + +snd-soc-i-sabre-codec-objs := i-sabre-codec.o +snd-soc-ma120x0p-objs := ma120x0p.o +snd-soc-pcm1794a-objs := pcm1794a.o +snd-soc-tas5713-objs := tas5713.o +obj-$(CONFIG_SND_SOC_I_SABRE_CODEC) += snd-soc-i-sabre-codec.o +obj-$(CONFIG_SND_SOC_MA120X0P) += snd-soc-ma120x0p.o +obj-$(CONFIG_SND_SOC_PCM1794A) += snd-soc-pcm1794a.o +obj-$(CONFIG_SND_SOC_TAS5713) += snd-soc-tas5713.o diff --git a/sound/soc/codecs/adau1977-i2c.c b/sound/soc/codecs/adau1977-i2c.c index 24c7b9c84c1981..328dfdc3f4751a 100644 --- a/sound/soc/codecs/adau1977-i2c.c +++ b/sound/soc/codecs/adau1977-i2c.c @@ -38,9 +38,19 @@ static const struct i2c_device_id adau1977_i2c_ids[] = { }; MODULE_DEVICE_TABLE(i2c, adau1977_i2c_ids); +static const struct of_device_id adau1977_of_ids[] = { + { .compatible = "adi,adau1977", }, + { .compatible = "adi,adau1978", }, + { .compatible = "adi,adau1979", }, + { } +}; +MODULE_DEVICE_TABLE(of, adau1977_of_ids); + + static struct i2c_driver adau1977_i2c_driver = { .driver = { .name = "adau1977", + .of_match_table = adau1977_of_ids, }, .probe = adau1977_i2c_probe, .id_table = adau1977_i2c_ids, diff --git a/sound/soc/codecs/cs42xx8-i2c.c b/sound/soc/codecs/cs42xx8-i2c.c index ecaebf8e1c8fc7..ecf7af852ec8ef 100644 --- a/sound/soc/codecs/cs42xx8-i2c.c +++ b/sound/soc/codecs/cs42xx8-i2c.c @@ -58,11 +58,18 @@ static const struct i2c_device_id cs42xx8_i2c_id[] = { }; MODULE_DEVICE_TABLE(i2c, cs42xx8_i2c_id); +const struct of_device_id cs42xx8_i2c_of_match[] = { + { .compatible = "cirrus,cs42448", .data = &cs42448_data, }, + { .compatible = "cirrus,cs42888", .data = &cs42888_data, }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, cs42xx8_i2c_of_match); + static struct i2c_driver cs42xx8_i2c_driver = { .driver = { .name = "cs42xx8", .pm = &cs42xx8_pm, - .of_match_table = cs42xx8_of_match, + .of_match_table = cs42xx8_i2c_of_match, }, .probe = cs42xx8_i2c_probe, .remove = cs42xx8_i2c_remove, diff --git a/sound/soc/codecs/cs42xx8.c b/sound/soc/codecs/cs42xx8.c index 9c44b6283b8f96..8e3b88d7d3af08 100644 --- a/sound/soc/codecs/cs42xx8.c +++ b/sound/soc/codecs/cs42xx8.c @@ -510,6 +510,16 @@ const struct cs42xx8_driver_data cs42888_data = { }; EXPORT_SYMBOL_GPL(cs42888_data); +const struct of_device_id cs42xx8_of_match[] = { + { .compatible = "cirrus,cs42448", .data = &cs42448_data, }, + { .compatible = "cirrus,cs42888", .data = &cs42888_data, }, + { /* sentinel */ } +}; +#if !IS_ENABLED(CONFIG_SND_SOC_CS42XX8_I2C) +MODULE_DEVICE_TABLE(of, cs42xx8_of_match); +EXPORT_SYMBOL_GPL(cs42xx8_of_match); +#endif + int cs42xx8_probe(struct device *dev, struct regmap *regmap, struct cs42xx8_driver_data *drvdata) { struct cs42xx8_priv *cs42xx8; diff --git a/sound/soc/codecs/da7213.c b/sound/soc/codecs/da7213.c index f17f02d01f8c0f..9724f50ed021e1 100644 --- a/sound/soc/codecs/da7213.c +++ b/sound/soc/codecs/da7213.c @@ -1346,6 +1346,8 @@ static int da7213_hw_params(struct snd_pcm_substream *substream, switch (params_width(params)) { case 16: dai_ctrl |= DA7213_DAI_WORD_LENGTH_S16_LE; + if (da7213->bclk_ratio == 64) + break; dai_clk_mode = DA7213_DAI_BCLKS_PER_WCLK_32; /* 32bit for 1ch and 2ch */ break; case 20: @@ -1361,6 +1363,9 @@ static int da7213_hw_params(struct snd_pcm_substream *substream, return -EINVAL; } + if (da7213->bclk_ratio == 32 && params_width(params) != 16) + return -EINVAL; + /* Set sampling rate */ switch (params_rate(params)) { case 8000: @@ -1523,6 +1528,21 @@ static int da7213_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) return 0; } +static int da7213_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio) +{ + struct snd_soc_component *component = dai->component; + struct da7213_priv *da7213 = snd_soc_component_get_drvdata(component); + + if (ratio != 32 && ratio != 64) { + dev_err(component->dev, "Invalid bclk ratio %d\n", ratio); + return -EINVAL; + } + + da7213->bclk_ratio = ratio; + + return 0; +} + static int da7213_mute(struct snd_soc_dai *dai, int mute, int direction) { struct snd_soc_component *component = dai->component; @@ -1735,6 +1755,7 @@ static const u64 da7213_dai_formats = static const struct snd_soc_dai_ops da7213_dai_ops = { .hw_params = da7213_hw_params, .set_fmt = da7213_set_dai_fmt, + .set_bclk_ratio = da7213_set_bclk_ratio, .mute_stream = da7213_mute, .no_capture_mute = 1, .auto_selectable_formats = &da7213_dai_formats, diff --git a/sound/soc/codecs/da7213.h b/sound/soc/codecs/da7213.h index 505b731c0adb93..5b6d8835689120 100644 --- a/sound/soc/codecs/da7213.h +++ b/sound/soc/codecs/da7213.h @@ -600,6 +600,7 @@ struct da7213_priv { struct clk *mclk; unsigned int mclk_rate; unsigned int out_rate; + unsigned int bclk_ratio; int clk_src; bool master; bool alc_calib_auto; diff --git a/sound/soc/codecs/i-sabre-codec.c b/sound/soc/codecs/i-sabre-codec.c new file mode 100644 index 00000000000000..518c57f4558e10 --- /dev/null +++ b/sound/soc/codecs/i-sabre-codec.c @@ -0,0 +1,389 @@ +/* + * Driver for I-Sabre Q2M + * + * Author: Satoru Kawase + * Modified by: Xiao Qingyong + * Modified by: JC BARBAUD (Mute) + * Update kernel v4.18+ by : Audiophonics + * Copyright 2018 Audiophonics + * + * 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 <linux/init.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/i2c.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> +#include <sound/tlv.h> + +#include "i-sabre-codec.h" + + +/* I-Sabre Q2M Codec Private Data */ +struct i_sabre_codec_priv { + struct regmap *regmap; + unsigned int fmt; +}; + + +/* I-Sabre Q2M Codec Default Register Value */ +static const struct reg_default i_sabre_codec_reg_defaults[] = { + { ISABRECODEC_REG_10, 0x00 }, + { ISABRECODEC_REG_20, 0x00 }, + { ISABRECODEC_REG_21, 0x00 }, + { ISABRECODEC_REG_22, 0x00 }, + { ISABRECODEC_REG_24, 0x00 }, +}; + + +static bool i_sabre_codec_writeable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ISABRECODEC_REG_10: + case ISABRECODEC_REG_20: + case ISABRECODEC_REG_21: + case ISABRECODEC_REG_22: + case ISABRECODEC_REG_24: + return true; + + default: + return false; + } +} + +static bool i_sabre_codec_readable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ISABRECODEC_REG_01: + case ISABRECODEC_REG_02: + case ISABRECODEC_REG_10: + case ISABRECODEC_REG_20: + case ISABRECODEC_REG_21: + case ISABRECODEC_REG_22: + case ISABRECODEC_REG_24: + return true; + + default: + return false; + } +} + +static bool i_sabre_codec_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ISABRECODEC_REG_01: + case ISABRECODEC_REG_02: + return true; + + default: + return false; + } +} + + +/* Volume Scale */ +static const DECLARE_TLV_DB_SCALE(volume_tlv, -10000, 100, 0); + + +/* Filter Type */ +static const char * const fir_filter_type_texts[] = { + "brick wall", + "corrected minimum phase fast", + "minimum phase slow", + "minimum phase fast", + "linear phase slow", + "linear phase fast", + "apodizing fast", +}; + +static SOC_ENUM_SINGLE_DECL(i_sabre_fir_filter_type_enum, + ISABRECODEC_REG_22, 0, fir_filter_type_texts); + + +/* I2S / SPDIF Select */ +static const char * const iis_spdif_sel_texts[] = { + "I2S", + "SPDIF", +}; + +static SOC_ENUM_SINGLE_DECL(i_sabre_iis_spdif_sel_enum, + ISABRECODEC_REG_24, 0, iis_spdif_sel_texts); + + +/* Control */ +static const struct snd_kcontrol_new i_sabre_codec_controls[] = { +SOC_SINGLE_RANGE_TLV("Digital Playback Volume", ISABRECODEC_REG_20, 0, 0, 100, 1, volume_tlv), +SOC_SINGLE("Digital Playback Switch", ISABRECODEC_REG_21, 0, 1, 1), +SOC_ENUM("FIR Filter Type", i_sabre_fir_filter_type_enum), +SOC_ENUM("I2S/SPDIF Select", i_sabre_iis_spdif_sel_enum), +}; + + +static const u32 i_sabre_codec_dai_rates_slave[] = { + 8000, 11025, 16000, 22050, 32000, + 44100, 48000, 64000, 88200, 96000, + 176400, 192000, 352800, 384000, + 705600, 768000, 1411200, 1536000 +}; + +static const struct snd_pcm_hw_constraint_list constraints_slave = { + .list = i_sabre_codec_dai_rates_slave, + .count = ARRAY_SIZE(i_sabre_codec_dai_rates_slave), +}; + +static int i_sabre_codec_dai_startup_slave( + struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + int ret; + + ret = snd_pcm_hw_constraint_list(substream->runtime, + 0, SNDRV_PCM_HW_PARAM_RATE, &constraints_slave); + if (ret != 0) { + dev_err(component->card->dev, "Failed to setup rates constraints: %d\n", ret); + } + + return ret; +} + +static int i_sabre_codec_dai_startup( + struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct i_sabre_codec_priv *i_sabre_codec + = snd_soc_component_get_drvdata(component); + + switch (i_sabre_codec->fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + return i_sabre_codec_dai_startup_slave(substream, dai); + + default: + return (-EINVAL); + } +} + +static int i_sabre_codec_hw_params( + struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct i_sabre_codec_priv *i_sabre_codec + = snd_soc_component_get_drvdata(component); + unsigned int daifmt; + int format_width; + + dev_dbg(component->card->dev, "hw_params %u Hz, %u channels\n", + params_rate(params), params_channels(params)); + + /* Check I2S Format (Bit Size) */ + format_width = snd_pcm_format_width(params_format(params)); + if ((format_width != 32) && (format_width != 16)) { + dev_err(component->card->dev, "Bad frame size: %d\n", + snd_pcm_format_width(params_format(params))); + return (-EINVAL); + } + + /* Check Slave Mode */ + daifmt = i_sabre_codec->fmt & SND_SOC_DAIFMT_MASTER_MASK; + if (daifmt != SND_SOC_DAIFMT_CBS_CFS) { + return (-EINVAL); + } + + /* Notify Sampling Frequency */ + switch (params_rate(params)) + { + case 44100: + case 48000: + case 88200: + case 96000: + case 176400: + case 192000: + snd_soc_component_update_bits(component, ISABRECODEC_REG_10, 0x01, 0x00); + break; + + case 352800: + case 384000: + case 705600: + case 768000: + case 1411200: + case 1536000: + snd_soc_component_update_bits(component, ISABRECODEC_REG_10, 0x01, 0x01); + break; + } + + return 0; +} + +static int i_sabre_codec_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + struct i_sabre_codec_priv *i_sabre_codec + = snd_soc_component_get_drvdata(component); + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + + case SND_SOC_DAIFMT_RIGHT_J: + case SND_SOC_DAIFMT_LEFT_J: + default: + return (-EINVAL); + } + + /* clock inversion */ + if ((fmt & SND_SOC_DAIFMT_INV_MASK) != SND_SOC_DAIFMT_NB_NF) { + return (-EINVAL); + } + + /* Set Audio Data Format */ + i_sabre_codec->fmt = fmt; + + return 0; +} + +static int i_sabre_codec_dac_mute(struct snd_soc_dai *dai, int mute, int direction) +{ + struct snd_soc_component *component = dai->component; + + if (mute) { + snd_soc_component_update_bits(component, ISABRECODEC_REG_21, 0x01, 0x01); + } else { + snd_soc_component_update_bits(component, ISABRECODEC_REG_21, 0x01, 0x00); + } + + return 0; +} + + +static const struct snd_soc_dai_ops i_sabre_codec_dai_ops = { + .startup = i_sabre_codec_dai_startup, + .hw_params = i_sabre_codec_hw_params, + .set_fmt = i_sabre_codec_set_fmt, + .mute_stream = i_sabre_codec_dac_mute, +}; + +static struct snd_soc_dai_driver i_sabre_codec_dai = { + .name = "i-sabre-codec-dai", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 8000, + .rate_max = 1536000, + .formats = SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &i_sabre_codec_dai_ops, +}; + +static struct snd_soc_component_driver i_sabre_codec_codec_driver = { + .controls = i_sabre_codec_controls, + .num_controls = ARRAY_SIZE(i_sabre_codec_controls), +}; + + +static const struct regmap_config i_sabre_codec_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = ISABRECODEC_MAX_REG, + + .reg_defaults = i_sabre_codec_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(i_sabre_codec_reg_defaults), + + .writeable_reg = i_sabre_codec_writeable, + .readable_reg = i_sabre_codec_readable, + .volatile_reg = i_sabre_codec_volatile, + + .cache_type = REGCACHE_RBTREE, +}; + + +static int i_sabre_codec_probe(struct device *dev, struct regmap *regmap) +{ + struct i_sabre_codec_priv *i_sabre_codec; + int ret; + + i_sabre_codec = devm_kzalloc(dev, sizeof(*i_sabre_codec), GFP_KERNEL); + if (!i_sabre_codec) { + dev_err(dev, "devm_kzalloc"); + return (-ENOMEM); + } + + i_sabre_codec->regmap = regmap; + + dev_set_drvdata(dev, i_sabre_codec); + + ret = snd_soc_register_component(dev, + &i_sabre_codec_codec_driver, &i_sabre_codec_dai, 1); + if (ret != 0) { + dev_err(dev, "Failed to register CODEC: %d\n", ret); + return ret; + } + + return 0; +} + +static void i_sabre_codec_remove(struct device *dev) +{ + snd_soc_unregister_component(dev); +} + + +static int i_sabre_codec_i2c_probe(struct i2c_client *i2c) +{ + struct regmap *regmap; + + regmap = devm_regmap_init_i2c(i2c, &i_sabre_codec_regmap); + if (IS_ERR(regmap)) { + return PTR_ERR(regmap); + } + + return i_sabre_codec_probe(&i2c->dev, regmap); +} + +static void i_sabre_codec_i2c_remove(struct i2c_client *i2c) +{ + i_sabre_codec_remove(&i2c->dev); +} + + +static const struct i2c_device_id i_sabre_codec_i2c_id[] = { + { "i-sabre-codec", }, + { } +}; +MODULE_DEVICE_TABLE(i2c, i_sabre_codec_i2c_id); + +static const struct of_device_id i_sabre_codec_of_match[] = { + { .compatible = "audiophonics,i-sabre-codec", }, + { } +}; +MODULE_DEVICE_TABLE(of, i_sabre_codec_of_match); + +static struct i2c_driver i_sabre_codec_i2c_driver = { + .driver = { + .name = "i-sabre-codec-i2c", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(i_sabre_codec_of_match), + }, + .probe = i_sabre_codec_i2c_probe, + .remove = i_sabre_codec_i2c_remove, + .id_table = i_sabre_codec_i2c_id, +}; +module_i2c_driver(i_sabre_codec_i2c_driver); + + +MODULE_DESCRIPTION("ASoC I-Sabre Q2M codec driver"); +MODULE_AUTHOR("Audiophonics <http://www.audiophonics.fr>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/i-sabre-codec.h b/sound/soc/codecs/i-sabre-codec.h new file mode 100644 index 00000000000000..9cac5a2446b9e4 --- /dev/null +++ b/sound/soc/codecs/i-sabre-codec.h @@ -0,0 +1,42 @@ +/* + * Driver for I-Sabre Q2M + * + * Author: Satoru Kawase + * Modified by: Xiao Qingyong + * Copyright 2018 Audiophonics + * + * 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 _SND_SOC_ISABRECODEC +#define _SND_SOC_ISABRECODEC + + +/* ISABRECODEC Register Address */ +#define ISABRECODEC_REG_01 0x01 /* Virtual Device ID : 0x01 = es9038q2m */ +#define ISABRECODEC_REG_02 0x02 /* API revision : 0x01 = Revision 01 */ +#define ISABRECODEC_REG_10 0x10 /* 0x01 = above 192kHz, 0x00 = otherwise */ +#define ISABRECODEC_REG_20 0x20 /* 0 - 100 (decimal value, 0 = min., 100 = max.) */ +#define ISABRECODEC_REG_21 0x21 /* 0x00 = Mute OFF, 0x01 = Mute ON */ +#define ISABRECODEC_REG_22 0x22 +/* + 0x00 = brick wall, + 0x01 = corrected minimum phase fast, + 0x02 = minimum phase slow, + 0x03 = minimum phase fast, + 0x04 = linear phase slow, + 0x05 = linear phase fast, + 0x06 = apodizing fast, +*/ +//#define ISABRECODEC_REG_23 0x23 /* reserved */ +#define ISABRECODEC_REG_24 0x24 /* 0x00 = I2S, 0x01 = SPDIF */ +#define ISABRECODEC_MAX_REG 0x24 /* Maximum Register Number */ + +#endif /* _SND_SOC_ISABRECODEC */ diff --git a/sound/soc/codecs/ma120x0p.c b/sound/soc/codecs/ma120x0p.c new file mode 100644 index 00000000000000..3df7e759ace1bb --- /dev/null +++ b/sound/soc/codecs/ma120x0p.c @@ -0,0 +1,1380 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ASoC Driver for Infineon Merus(TM) ma120x0p multi-level class-D amplifier + * + * Authors: Ariel Muszkat <ariel.muszkat@gmail.com> + * Jorgen Kragh Jakobsen <jorgen.kraghjakobsen@infineon.com> + * + * Copyright (C) 2019 Infineon Technologies AG + * + */ +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm_runtime.h> +#include <linux/i2c.h> +#include <linux/of_device.h> +#include <linux/spi/spi.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> +#include <linux/gpio/consumer.h> +#include <linux/gpio.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> +#include <sound/tlv.h> +#include <linux/interrupt.h> + +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/fs.h> +#include <linux/uaccess.h> + +#ifndef _MA120X0P_ +#define _MA120X0P_ +//------------------------------------------------------------------manualPM--- +// Select Manual PowerMode control +#define ma_manualpm__a 0 +#define ma_manualpm__len 1 +#define ma_manualpm__mask 0x40 +#define ma_manualpm__shift 0x06 +#define ma_manualpm__reset 0x00 +//--------------------------------------------------------------------pm_man--- +// manual selected power mode +#define ma_pm_man__a 0 +#define ma_pm_man__len 2 +#define ma_pm_man__mask 0x30 +#define ma_pm_man__shift 0x04 +#define ma_pm_man__reset 0x03 +//------------------------------------------ ----------------------mthr_1to2--- +// mod. index threshold value for pm1=>pm2 change. +#define ma_mthr_1to2__a 1 +#define ma_mthr_1to2__len 8 +#define ma_mthr_1to2__mask 0xff +#define ma_mthr_1to2__shift 0x00 +#define ma_mthr_1to2__reset 0x3c +//-----------------------------------------------------------------mthr_2to1--- +// mod. index threshold value for pm2=>pm1 change. +#define ma_mthr_2to1__a 2 +#define ma_mthr_2to1__len 8 +#define ma_mthr_2to1__mask 0xff +#define ma_mthr_2to1__shift 0x00 +#define ma_mthr_2to1__reset 0x32 +//-----------------------------------------------------------------mthr_2to3--- +// mod. index threshold value for pm2=>pm3 change. +#define ma_mthr_2to3__a 3 +#define ma_mthr_2to3__len 8 +#define ma_mthr_2to3__mask 0xff +#define ma_mthr_2to3__shift 0x00 +#define ma_mthr_2to3__reset 0x5a +//-----------------------------------------------------------------mthr_3to2--- +// mod. index threshold value for pm3=>pm2 change. +#define ma_mthr_3to2__a 4 +#define ma_mthr_3to2__len 8 +#define ma_mthr_3to2__mask 0xff +#define ma_mthr_3to2__shift 0x00 +#define ma_mthr_3to2__reset 0x50 +//-------------------------------------------------------------pwmclkdiv_nom--- +// pwm default clock divider value +#define ma_pwmclkdiv_nom__a 8 +#define ma_pwmclkdiv_nom__len 8 +#define ma_pwmclkdiv_nom__mask 0xff +#define ma_pwmclkdiv_nom__shift 0x00 +#define ma_pwmclkdiv_nom__reset 0x26 +//--------- ----------------------------------------------------ocp_latch_en--- +// high to use permanently latching level-2 ocp +#define ma_ocp_latch_en__a 10 +#define ma_ocp_latch_en__len 1 +#define ma_ocp_latch_en__mask 0x02 +#define ma_ocp_latch_en__shift 0x01 +#define ma_ocp_latch_en__reset 0x00 +//---------------------------------------------------------------lf_clamp_en--- +// high (default) to enable lf int2+3 clamping on clip +#define ma_lf_clamp_en__a 10 +#define ma_lf_clamp_en__len 1 +#define ma_lf_clamp_en__mask 0x80 +#define ma_lf_clamp_en__shift 0x07 +#define ma_lf_clamp_en__reset 0x00 +//-------------------------------------------------------pmcfg_btl_b.modtype--- +// +#define ma_pmcfg_btl_b__modtype__a 18 +#define ma_pmcfg_btl_b__modtype__len 2 +#define ma_pmcfg_btl_b__modtype__mask 0x18 +#define ma_pmcfg_btl_b__modtype__shift 0x03 +#define ma_pmcfg_btl_b__modtype__reset 0x02 +//-------------------------------------------------------pmcfg_btl_b.freqdiv--- +#define ma_pmcfg_btl_b__freqdiv__a 18 +#define ma_pmcfg_btl_b__freqdiv__len 2 +#define ma_pmcfg_btl_b__freqdiv__mask 0x06 +#define ma_pmcfg_btl_b__freqdiv__shift 0x01 +#define ma_pmcfg_btl_b__freqdiv__reset 0x01 +//----------------------------------------------------pmcfg_btl_b.lf_gain_ol--- +// +#define ma_pmcfg_btl_b__lf_gain_ol__a 18 +#define ma_pmcfg_btl_b__lf_gain_ol__len 1 +#define ma_pmcfg_btl_b__lf_gain_ol__mask 0x01 +#define ma_pmcfg_btl_b__lf_gain_ol__shift 0x00 +#define ma_pmcfg_btl_b__lf_gain_ol__reset 0x01 +//-------------------------------------------------------pmcfg_btl_c.freqdiv--- +// +#define ma_pmcfg_btl_c__freqdiv__a 19 +#define ma_pmcfg_btl_c__freqdiv__len 2 +#define ma_pmcfg_btl_c__freqdiv__mask 0x06 +#define ma_pmcfg_btl_c__freqdiv__shift 0x01 +#define ma_pmcfg_btl_c__freqdiv__reset 0x01 +//-------------------------------------------------------pmcfg_btl_c.modtype--- +// +#define ma_pmcfg_btl_c__modtype__a 19 +#define ma_pmcfg_btl_c__modtype__len 2 +#define ma_pmcfg_btl_c__modtype__mask 0x18 +#define ma_pmcfg_btl_c__modtype__shift 0x03 +#define ma_pmcfg_btl_c__modtype__reset 0x01 +//----------------------------------------------------pmcfg_btl_c.lf_gain_ol--- +// +#define ma_pmcfg_btl_c__lf_gain_ol__a 19 +#define ma_pmcfg_btl_c__lf_gain_ol__len 1 +#define ma_pmcfg_btl_c__lf_gain_ol__mask 0x01 +#define ma_pmcfg_btl_c__lf_gain_ol__shift 0x00 +#define ma_pmcfg_btl_c__lf_gain_ol__reset 0x00 +//-------------------------------------------------------pmcfg_btl_d.modtype--- +// +#define ma_pmcfg_btl_d__modtype__a 20 +#define ma_pmcfg_btl_d__modtype__len 2 +#define ma_pmcfg_btl_d__modtype__mask 0x18 +#define ma_pmcfg_btl_d__modtype__shift 0x03 +#define ma_pmcfg_btl_d__modtype__reset 0x02 +//-------------------------------------------------------pmcfg_btl_d.freqdiv--- +// +#define ma_pmcfg_btl_d__freqdiv__a 20 +#define ma_pmcfg_btl_d__freqdiv__len 2 +#define ma_pmcfg_btl_d__freqdiv__mask 0x06 +#define ma_pmcfg_btl_d__freqdiv__shift 0x01 +#define ma_pmcfg_btl_d__freqdiv__reset 0x02 +//----------------------------------------------------pmcfg_btl_d.lf_gain_ol--- +// +#define ma_pmcfg_btl_d__lf_gain_ol__a 20 +#define ma_pmcfg_btl_d__lf_gain_ol__len 1 +#define ma_pmcfg_btl_d__lf_gain_ol__mask 0x01 +#define ma_pmcfg_btl_d__lf_gain_ol__shift 0x00 +#define ma_pmcfg_btl_d__lf_gain_ol__reset 0x00 +//------------ -------------------------------------------pmcfg_se_a.modtype--- +// +#define ma_pmcfg_se_a__modtype__a 21 +#define ma_pmcfg_se_a__modtype__len 2 +#define ma_pmcfg_se_a__modtype__mask 0x18 +#define ma_pmcfg_se_a__modtype__shift 0x03 +#define ma_pmcfg_se_a__modtype__reset 0x01 +//--------------------------------------------------------pmcfg_se_a.freqdiv--- +// +#define ma_pmcfg_se_a__freqdiv__a 21 +#define ma_pmcfg_se_a__freqdiv__len 2 +#define ma_pmcfg_se_a__freqdiv__mask 0x06 +#define ma_pmcfg_se_a__freqdiv__shift 0x01 +#define ma_pmcfg_se_a__freqdiv__reset 0x00 +//-----------------------------------------------------pmcfg_se_a.lf_gain_ol--- +// +#define ma_pmcfg_se_a__lf_gain_ol__a 21 +#define ma_pmcfg_se_a__lf_gain_ol__len 1 +#define ma_pmcfg_se_a__lf_gain_ol__mask 0x01 +#define ma_pmcfg_se_a__lf_gain_ol__shift 0x00 +#define ma_pmcfg_se_a__lf_gain_ol__reset 0x01 +//-----------------------------------------------------pmcfg_se_b.lf_gain_ol--- +// +#define ma_pmcfg_se_b__lf_gain_ol__a 22 +#define ma_pmcfg_se_b__lf_gain_ol__len 1 +#define ma_pmcfg_se_b__lf_gain_ol__mask 0x01 +#define ma_pmcfg_se_b__lf_gain_ol__shift 0x00 +#define ma_pmcfg_se_b__lf_gain_ol__reset 0x00 +//--------------------------------------------------------pmcfg_se_b.freqdiv--- +// +#define ma_pmcfg_se_b__freqdiv__a 22 +#define ma_pmcfg_se_b__freqdiv__len 2 +#define ma_pmcfg_se_b__freqdiv__mask 0x06 +#define ma_pmcfg_se_b__freqdiv__shift 0x01 +#define ma_pmcfg_se_b__freqdiv__reset 0x01 +//--------------------------------------------------------pmcfg_se_b.modtype--- +// +#define ma_pmcfg_se_b__modtype__a 22 +#define ma_pmcfg_se_b__modtype__len 2 +#define ma_pmcfg_se_b__modtype__mask 0x18 +#define ma_pmcfg_se_b__modtype__shift 0x03 +#define ma_pmcfg_se_b__modtype__reset 0x01 +//----------------------------------------------------------balwaitcount_pm1--- +// pm1 balancing period. +#define ma_balwaitcount_pm1__a 23 +#define ma_balwaitcount_pm1__len 8 +#define ma_balwaitcount_pm1__mask 0xff +#define ma_balwaitcount_pm1__shift 0x00 +#define ma_balwaitcount_pm1__reset 0x14 +//----------------------------------------------------------balwaitcount_pm2--- +// pm2 balancing period. +#define ma_balwaitcount_pm2__a 24 +#define ma_balwaitcount_pm2__len 8 +#define ma_balwaitcount_pm2__mask 0xff +#define ma_balwaitcount_pm2__shift 0x00 +#define ma_balwaitcount_pm2__reset 0x14 +//----------------------------------------------------------balwaitcount_pm3--- +// pm3 balancing period. +#define ma_balwaitcount_pm3__a 25 +#define ma_balwaitcount_pm3__len 8 +#define ma_balwaitcount_pm3__mask 0xff +#define ma_balwaitcount_pm3__shift 0x00 +#define ma_balwaitcount_pm3__reset 0x1a +//-------------------------------------------------------------usespread_pm1--- +// pm1 pwm spread-spectrum mode on/off. +#define ma_usespread_pm1__a 26 +#define ma_usespread_pm1__len 1 +#define ma_usespread_pm1__mask 0x40 +#define ma_usespread_pm1__shift 0x06 +#define ma_usespread_pm1__reset 0x00 +//---------------------------------------------------------------dtsteps_pm1--- +// pm1 dead time setting [10ns steps]. +#define ma_dtsteps_pm1__a 26 +#define ma_dtsteps_pm1__len 3 +#define ma_dtsteps_pm1__mask 0x38 +#define ma_dtsteps_pm1__shift 0x03 +#define ma_dtsteps_pm1__reset 0x04 +//---------------------------------------------------------------baltype_pm1--- +// pm1 balancing sensor scheme. +#define ma_baltype_pm1__a 26 +#define ma_baltype_pm1__len 3 +#define ma_baltype_pm1__mask 0x07 +#define ma_baltype_pm1__shift 0x00 +#define ma_baltype_pm1__reset 0x00 +//-------------------------------------------------------------usespread_pm2--- +// pm2 pwm spread-spectrum mode on/off. +#define ma_usespread_pm2__a 27 +#define ma_usespread_pm2__len 1 +#define ma_usespread_pm2__mask 0x40 +#define ma_usespread_pm2__shift 0x06 +#define ma_usespread_pm2__reset 0x00 +//---------------------------------------------------------------dtsteps_pm2--- +// pm2 dead time setting [10ns steps]. +#define ma_dtsteps_pm2__a 27 +#define ma_dtsteps_pm2__len 3 +#define ma_dtsteps_pm2__mask 0x38 +#define ma_dtsteps_pm2__shift 0x03 +#define ma_dtsteps_pm2__reset 0x03 +//---------------------------------------------------------------baltype_pm2--- +// pm2 balancing sensor scheme. +#define ma_baltype_pm2__a 27 +#define ma_baltype_pm2__len 3 +#define ma_baltype_pm2__mask 0x07 +#define ma_baltype_pm2__shift 0x00 +#define ma_baltype_pm2__reset 0x01 +//-------------------------------------------------------------usespread_pm3--- +// pm3 pwm spread-spectrum mode on/off. +#define ma_usespread_pm3__a 28 +#define ma_usespread_pm3__len 1 +#define ma_usespread_pm3__mask 0x40 +#define ma_usespread_pm3__shift 0x06 +#define ma_usespread_pm3__reset 0x00 +//---------------------------------------------------------------dtsteps_pm3--- +// pm3 dead time setting [10ns steps]. +#define ma_dtsteps_pm3__a 28 +#define ma_dtsteps_pm3__len 3 +#define ma_dtsteps_pm3__mask 0x38 +#define ma_dtsteps_pm3__shift 0x03 +#define ma_dtsteps_pm3__reset 0x01 +//---------------------------------------------------------------baltype_pm3--- +// pm3 balancing sensor scheme. +#define ma_baltype_pm3__a 28 +#define ma_baltype_pm3__len 3 +#define ma_baltype_pm3__mask 0x07 +#define ma_baltype_pm3__shift 0x00 +#define ma_baltype_pm3__reset 0x03 +//-----------------------------------------------------------------pmprofile--- +// pm profile select. valid presets: 0-1-2-3-4. 5=> custom profile. +#define ma_pmprofile__a 29 +#define ma_pmprofile__len 3 +#define ma_pmprofile__mask 0x07 +#define ma_pmprofile__shift 0x00 +#define ma_pmprofile__reset 0x00 +//-------------------------------------------------------------------pm3_man--- +// custom profile pm3 contents. 0=>a, 1=>b, 2=>c, 3=>d +#define ma_pm3_man__a 30 +#define ma_pm3_man__len 2 +#define ma_pm3_man__mask 0x30 +#define ma_pm3_man__shift 0x04 +#define ma_pm3_man__reset 0x02 +//-------------------------------------------------------------------pm2_man--- +// custom profile pm2 contents. 0=>a, 1=>b, 2=>c, 3=>d +#define ma_pm2_man__a 30 +#define ma_pm2_man__len 2 +#define ma_pm2_man__mask 0x0c +#define ma_pm2_man__shift 0x02 +#define ma_pm2_man__reset 0x03 +//-------------------------------------------------------------------pm1_man--- +// custom profile pm1 contents. 0=>a, 1=>b, 2=>c, 3=>d +#define ma_pm1_man__a 30 +#define ma_pm1_man__len 2 +#define ma_pm1_man__mask 0x03 +#define ma_pm1_man__shift 0x00 +#define ma_pm1_man__reset 0x03 +//-----------------------------------------------------------ocp_latch_clear--- +// low-high clears current ocp latched condition. +#define ma_ocp_latch_clear__a 32 +#define ma_ocp_latch_clear__len 1 +#define ma_ocp_latch_clear__mask 0x80 +#define ma_ocp_latch_clear__shift 0x07 +#define ma_ocp_latch_clear__reset 0x00 +//-------------------------------------------------------------audio_in_mode--- +// audio input mode; 0-1-2-3-4-5 +#define ma_audio_in_mode__a 37 +#define ma_audio_in_mode__len 3 +#define ma_audio_in_mode__mask 0xe0 +#define ma_audio_in_mode__shift 0x05 +#define ma_audio_in_mode__reset 0x00 +//-----------------------------------------------------------------eh_dcshdn--- +// high to enable dc protection +#define ma_eh_dcshdn__a 38 +#define ma_eh_dcshdn__len 1 +#define ma_eh_dcshdn__mask 0x04 +#define ma_eh_dcshdn__shift 0x02 +#define ma_eh_dcshdn__reset 0x01 +//---------------------------------------------------------audio_in_mode_ext--- +// if set, audio_in_mode is controlled from audio_in_mode register. if not set +//audio_in_mode is set from fuse bank setting +#define ma_audio_in_mode_ext__a 39 +#define ma_audio_in_mode_ext__len 1 +#define ma_audio_in_mode_ext__mask 0x20 +#define ma_audio_in_mode_ext__shift 0x05 +#define ma_audio_in_mode_ext__reset 0x00 +//------------------------------------------------------------------eh_clear--- +// flip to clear error registers +#define ma_eh_clear__a 45 +#define ma_eh_clear__len 1 +#define ma_eh_clear__mask 0x04 +#define ma_eh_clear__shift 0x02 +#define ma_eh_clear__reset 0x00 +//----------------------------------------------------------thermal_compr_en--- +// enable otw-contr. input compression? +#define ma_thermal_compr_en__a 45 +#define ma_thermal_compr_en__len 1 +#define ma_thermal_compr_en__mask 0x20 +#define ma_thermal_compr_en__shift 0x05 +#define ma_thermal_compr_en__reset 0x01 +//---------------------------------------------------------------system_mute--- +// 1 = mute system, 0 = normal operation +#define ma_system_mute__a 45 +#define ma_system_mute__len 1 +#define ma_system_mute__mask 0x40 +#define ma_system_mute__shift 0x06 +#define ma_system_mute__reset 0x00 +//------------------------------------------------------thermal_compr_max_db--- +// audio limiter max thermal reduction +#define ma_thermal_compr_max_db__a 46 +#define ma_thermal_compr_max_db__len 3 +#define ma_thermal_compr_max_db__mask 0x07 +#define ma_thermal_compr_max_db__shift 0x00 +#define ma_thermal_compr_max_db__reset 0x04 +//---------------------------------------------------------audio_proc_enable--- +// enable audio proc, bypass if not enabled +#define ma_audio_proc_enable__a 53 +#define ma_audio_proc_enable__len 1 +#define ma_audio_proc_enable__mask 0x08 +#define ma_audio_proc_enable__shift 0x03 +#define ma_audio_proc_enable__reset 0x00 +//--------------------------------------------------------audio_proc_release--- +// 00:slow, 01:normal, 10:fast +#define ma_audio_proc_release__a 53 +#define ma_audio_proc_release__len 2 +#define ma_audio_proc_release__mask 0x30 +#define ma_audio_proc_release__shift 0x04 +#define ma_audio_proc_release__reset 0x00 +//---------------------------------------------------------audio_proc_attack--- +// 00:slow, 01:normal, 10:fast +#define ma_audio_proc_attack__a 53 +#define ma_audio_proc_attack__len 2 +#define ma_audio_proc_attack__mask 0xc0 +#define ma_audio_proc_attack__shift 0x06 +#define ma_audio_proc_attack__reset 0x00 +//----------------------------------------------------------------i2s_format--- +// i2s basic data format, 000 = std. i2s, 001 = left justified (default) +#define ma_i2s_format__a 53 +#define ma_i2s_format__len 3 +#define ma_i2s_format__mask 0x07 +#define ma_i2s_format__shift 0x00 +#define ma_i2s_format__reset 0x01 +//--------------------------------------------------audio_proc_limiterenable--- +// 1: enable limiter, 0: disable limiter +#define ma_audio_proc_limiterenable__a 54 +#define ma_audio_proc_limiterenable__len 1 +#define ma_audio_proc_limiterenable__mask 0x40 +#define ma_audio_proc_limiterenable__shift 0x06 +#define ma_audio_proc_limiterenable__reset 0x00 +//-----------------------------------------------------------audio_proc_mute--- +// 1: mute, 0: unmute +#define ma_audio_proc_mute__a 54 +#define ma_audio_proc_mute__len 1 +#define ma_audio_proc_mute__mask 0x80 +#define ma_audio_proc_mute__shift 0x07 +#define ma_audio_proc_mute__reset 0x00 +//---------------------------------------------------------------i2s_sck_pol--- +// i2s sck polarity cfg. 0 = rising edge data change +#define ma_i2s_sck_pol__a 54 +#define ma_i2s_sck_pol__len 1 +#define ma_i2s_sck_pol__mask 0x01 +#define ma_i2s_sck_pol__shift 0x00 +#define ma_i2s_sck_pol__reset 0x01 +//-------------------------------------------------------------i2s_framesize--- +// i2s word length. 00 = 32bit, 01 = 24bit +#define ma_i2s_framesize__a 54 +#define ma_i2s_framesize__len 2 +#define ma_i2s_framesize__mask 0x18 +#define ma_i2s_framesize__shift 0x03 +#define ma_i2s_framesize__reset 0x00 +//----------------------------------------------------------------i2s_ws_pol--- +// i2s ws polarity. 0 = low first +#define ma_i2s_ws_pol__a 54 +#define ma_i2s_ws_pol__len 1 +#define ma_i2s_ws_pol__mask 0x02 +#define ma_i2s_ws_pol__shift 0x01 +#define ma_i2s_ws_pol__reset 0x00 +//-----------------------------------------------------------------i2s_order--- +// i2s word bit order. 0 = msb first +#define ma_i2s_order__a 54 +#define ma_i2s_order__len 1 +#define ma_i2s_order__mask 0x04 +#define ma_i2s_order__shift 0x02 +#define ma_i2s_order__reset 0x00 +//------------------------------------------------------------i2s_rightfirst--- +// i2s l/r word order; 0 = left first +#define ma_i2s_rightfirst__a 54 +#define ma_i2s_rightfirst__len 1 +#define ma_i2s_rightfirst__mask 0x20 +#define ma_i2s_rightfirst__shift 0x05 +#define ma_i2s_rightfirst__reset 0x00 +//-------------------------------------------------------------vol_db_master--- +// master volume db +#define ma_vol_db_master__a 64 +#define ma_vol_db_master__len 8 +#define ma_vol_db_master__mask 0xff +#define ma_vol_db_master__shift 0x00 +#define ma_vol_db_master__reset 0x18 +//------------------------------------------------------------vol_lsb_master--- +// master volume lsb 1/4 steps +#define ma_vol_lsb_master__a 65 +#define ma_vol_lsb_master__len 2 +#define ma_vol_lsb_master__mask 0x03 +#define ma_vol_lsb_master__shift 0x00 +#define ma_vol_lsb_master__reset 0x00 +//----------------------------------------------------------------vol_db_ch0--- +// volume channel 0 +#define ma_vol_db_ch0__a 66 +#define ma_vol_db_ch0__len 8 +#define ma_vol_db_ch0__mask 0xff +#define ma_vol_db_ch0__shift 0x00 +#define ma_vol_db_ch0__reset 0x18 +//----------------------------------------------------------------vol_db_ch1--- +// volume channel 1 +#define ma_vol_db_ch1__a 67 +#define ma_vol_db_ch1__len 8 +#define ma_vol_db_ch1__mask 0xff +#define ma_vol_db_ch1__shift 0x00 +#define ma_vol_db_ch1__reset 0x18 +//----------------------------------------------------------------vol_db_ch2--- +// volume channel 2 +#define ma_vol_db_ch2__a 68 +#define ma_vol_db_ch2__len 8 +#define ma_vol_db_ch2__mask 0xff +#define ma_vol_db_ch2__shift 0x00 +#define ma_vol_db_ch2__reset 0x18 +//----------------------------------------------------------------vol_db_ch3--- +// volume channel 3 +#define ma_vol_db_ch3__a 69 +#define ma_vol_db_ch3__len 8 +#define ma_vol_db_ch3__mask 0xff +#define ma_vol_db_ch3__shift 0x00 +#define ma_vol_db_ch3__reset 0x18 +//---------------------------------------------------------------vol_lsb_ch0--- +// volume channel 1 - 1/4 steps +#define ma_vol_lsb_ch0__a 70 +#define ma_vol_lsb_ch0__len 2 +#define ma_vol_lsb_ch0__mask 0x03 +#define ma_vol_lsb_ch0__shift 0x00 +#define ma_vol_lsb_ch0__reset 0x00 +//---------------------------------------------------------------vol_lsb_ch1--- +// volume channel 3 - 1/4 steps +#define ma_vol_lsb_ch1__a 70 +#define ma_vol_lsb_ch1__len 2 +#define ma_vol_lsb_ch1__mask 0x0c +#define ma_vol_lsb_ch1__shift 0x02 +#define ma_vol_lsb_ch1__reset 0x00 +//---------------------------------------------------------------vol_lsb_ch2--- +// volume channel 2 - 1/4 steps +#define ma_vol_lsb_ch2__a 70 +#define ma_vol_lsb_ch2__len 2 +#define ma_vol_lsb_ch2__mask 0x30 +#define ma_vol_lsb_ch2__shift 0x04 +#define ma_vol_lsb_ch2__reset 0x00 +//---------------------------------------------------------------vol_lsb_ch3--- +// volume channel 3 - 1/4 steps +#define ma_vol_lsb_ch3__a 70 +#define ma_vol_lsb_ch3__len 2 +#define ma_vol_lsb_ch3__mask 0xc0 +#define ma_vol_lsb_ch3__shift 0x06 +#define ma_vol_lsb_ch3__reset 0x00 +//----------------------------------------------------------------thr_db_ch0--- +// thr_db channel 0 +#define ma_thr_db_ch0__a 71 +#define ma_thr_db_ch0__len 8 +#define ma_thr_db_ch0__mask 0xff +#define ma_thr_db_ch0__shift 0x00 +#define ma_thr_db_ch0__reset 0x18 +//----------------------------------------------------------------thr_db_ch1--- +// thr db ch1 +#define ma_thr_db_ch1__a 72 +#define ma_thr_db_ch1__len 8 +#define ma_thr_db_ch1__mask 0xff +#define ma_thr_db_ch1__shift 0x00 +#define ma_thr_db_ch1__reset 0x18 +//----------------------------------------------------------------thr_db_ch2--- +// thr db ch2 +#define ma_thr_db_ch2__a 73 +#define ma_thr_db_ch2__len 8 +#define ma_thr_db_ch2__mask 0xff +#define ma_thr_db_ch2__shift 0x00 +#define ma_thr_db_ch2__reset 0x18 +//----------------------------------------------------------------thr_db_ch3--- +// threshold db ch3 +#define ma_thr_db_ch3__a 74 +#define ma_thr_db_ch3__len 8 +#define ma_thr_db_ch3__mask 0xff +#define ma_thr_db_ch3__shift 0x00 +#define ma_thr_db_ch3__reset 0x18 +//---------------------------------------------------------------thr_lsb_ch0--- +// thr lsb ch0 +#define ma_thr_lsb_ch0__a 75 +#define ma_thr_lsb_ch0__len 2 +#define ma_thr_lsb_ch0__mask 0x03 +#define ma_thr_lsb_ch0__shift 0x00 +#define ma_thr_lsb_ch0__reset 0x00 +//---------------------------------------------------------------thr_lsb_ch1--- +// thr lsb ch1 +#define ma_thr_lsb_ch1__a 75 +#define ma_thr_lsb_ch1__len 2 +#define ma_thr_lsb_ch1__mask 0x0c +#define ma_thr_lsb_ch1__shift 0x02 +#define ma_thr_lsb_ch1__reset 0x00 +//---------------------------------------------------------------thr_lsb_ch2--- +// thr lsb ch2 1/4 db step +#define ma_thr_lsb_ch2__a 75 +#define ma_thr_lsb_ch2__len 2 +#define ma_thr_lsb_ch2__mask 0x30 +#define ma_thr_lsb_ch2__shift 0x04 +#define ma_thr_lsb_ch2__reset 0x00 +//---------------------------------------------------------------thr_lsb_ch3--- +// threshold lsb ch3 +#define ma_thr_lsb_ch3__a 75 +#define ma_thr_lsb_ch3__len 2 +#define ma_thr_lsb_ch3__mask 0xc0 +#define ma_thr_lsb_ch3__shift 0x06 +#define ma_thr_lsb_ch3__reset 0x00 +//-----------------------------------------------------------dcu_mon0.pm_mon--- +// power mode monitor channel 0 +#define ma_dcu_mon0__pm_mon__a 96 +#define ma_dcu_mon0__pm_mon__len 2 +#define ma_dcu_mon0__pm_mon__mask 0x03 +#define ma_dcu_mon0__pm_mon__shift 0x00 +#define ma_dcu_mon0__pm_mon__reset 0x00 +//-----------------------------------------------------dcu_mon0.freqmode_mon--- +// frequence mode monitor channel 0 +#define ma_dcu_mon0__freqmode_mon__a 96 +#define ma_dcu_mon0__freqmode_mon__len 3 +#define ma_dcu_mon0__freqmode_mon__mask 0x70 +#define ma_dcu_mon0__freqmode_mon__shift 0x04 +#define ma_dcu_mon0__freqmode_mon__reset 0x00 +//-------------------------------------------------------dcu_mon0.pps_passed--- +// dcu0 pps completion indicator +#define ma_dcu_mon0__pps_passed__a 96 +#define ma_dcu_mon0__pps_passed__len 1 +#define ma_dcu_mon0__pps_passed__mask 0x80 +#define ma_dcu_mon0__pps_passed__shift 0x07 +#define ma_dcu_mon0__pps_passed__reset 0x00 +//----------------------------------------------------------dcu_mon0.ocp_mon--- +// ocp monitor channel 0 +#define ma_dcu_mon0__ocp_mon__a 97 +#define ma_dcu_mon0__ocp_mon__len 1 +#define ma_dcu_mon0__ocp_mon__mask 0x01 +#define ma_dcu_mon0__ocp_mon__shift 0x00 +#define ma_dcu_mon0__ocp_mon__reset 0x00 +//--------------------------------------------------------dcu_mon0.vcfly1_ok--- +// cfly1 protection monitor channel 0. +#define ma_dcu_mon0__vcfly1_ok__a 97 +#define ma_dcu_mon0__vcfly1_ok__len 1 +#define ma_dcu_mon0__vcfly1_ok__mask 0x02 +#define ma_dcu_mon0__vcfly1_ok__shift 0x01 +#define ma_dcu_mon0__vcfly1_ok__reset 0x00 +//--------------------------------------------------------dcu_mon0.vcfly2_ok--- +// cfly2 protection monitor channel 0. +#define ma_dcu_mon0__vcfly2_ok__a 97 +#define ma_dcu_mon0__vcfly2_ok__len 1 +#define ma_dcu_mon0__vcfly2_ok__mask 0x04 +#define ma_dcu_mon0__vcfly2_ok__shift 0x02 +#define ma_dcu_mon0__vcfly2_ok__reset 0x00 +//----------------------------------------------------------dcu_mon0.pvdd_ok--- +// dcu0 pvdd monitor +#define ma_dcu_mon0__pvdd_ok__a 97 +#define ma_dcu_mon0__pvdd_ok__len 1 +#define ma_dcu_mon0__pvdd_ok__mask 0x08 +#define ma_dcu_mon0__pvdd_ok__shift 0x03 +#define ma_dcu_mon0__pvdd_ok__reset 0x00 +//-----------------------------------------------------------dcu_mon0.vdd_ok--- +// dcu0 vdd monitor +#define ma_dcu_mon0__vdd_ok__a 97 +#define ma_dcu_mon0__vdd_ok__len 1 +#define ma_dcu_mon0__vdd_ok__mask 0x10 +#define ma_dcu_mon0__vdd_ok__shift 0x04 +#define ma_dcu_mon0__vdd_ok__reset 0x00 +//-------------------------------------------------------------dcu_mon0.mute--- +// dcu0 mute monitor +#define ma_dcu_mon0__mute__a 97 +#define ma_dcu_mon0__mute__len 1 +#define ma_dcu_mon0__mute__mask 0x20 +#define ma_dcu_mon0__mute__shift 0x05 +#define ma_dcu_mon0__mute__reset 0x00 +//------------------------------------------------------------dcu_mon0.m_mon--- +// m sense monitor channel 0 +#define ma_dcu_mon0__m_mon__a 98 +#define ma_dcu_mon0__m_mon__len 8 +#define ma_dcu_mon0__m_mon__mask 0xff +#define ma_dcu_mon0__m_mon__shift 0x00 +#define ma_dcu_mon0__m_mon__reset 0x00 +//-----------------------------------------------------------dcu_mon1.pm_mon--- +// power mode monitor channel 1 +#define ma_dcu_mon1__pm_mon__a 100 +#define ma_dcu_mon1__pm_mon__len 2 +#define ma_dcu_mon1__pm_mon__mask 0x03 +#define ma_dcu_mon1__pm_mon__shift 0x00 +#define ma_dcu_mon1__pm_mon__reset 0x00 +//-----------------------------------------------------dcu_mon1.freqmode_mon--- +// frequence mode monitor channel 1 +#define ma_dcu_mon1__freqmode_mon__a 100 +#define ma_dcu_mon1__freqmode_mon__len 3 +#define ma_dcu_mon1__freqmode_mon__mask 0x70 +#define ma_dcu_mon1__freqmode_mon__shift 0x04 +#define ma_dcu_mon1__freqmode_mon__reset 0x00 +//-------------------------------------------------------dcu_mon1.pps_passed--- +// dcu1 pps completion indicator +#define ma_dcu_mon1__pps_passed__a 100 +#define ma_dcu_mon1__pps_passed__len 1 +#define ma_dcu_mon1__pps_passed__mask 0x80 +#define ma_dcu_mon1__pps_passed__shift 0x07 +#define ma_dcu_mon1__pps_passed__reset 0x00 +//----------------------------------------------------------dcu_mon1.ocp_mon--- +// ocp monitor channel 1 +#define ma_dcu_mon1__ocp_mon__a 101 +#define ma_dcu_mon1__ocp_mon__len 1 +#define ma_dcu_mon1__ocp_mon__mask 0x01 +#define ma_dcu_mon1__ocp_mon__shift 0x00 +#define ma_dcu_mon1__ocp_mon__reset 0x00 +//--------------------------------------------------------dcu_mon1.vcfly1_ok--- +// cfly1 protcetion monitor channel 1 +#define ma_dcu_mon1__vcfly1_ok__a 101 +#define ma_dcu_mon1__vcfly1_ok__len 1 +#define ma_dcu_mon1__vcfly1_ok__mask 0x02 +#define ma_dcu_mon1__vcfly1_ok__shift 0x01 +#define ma_dcu_mon1__vcfly1_ok__reset 0x00 +//--------------------------------------------------------dcu_mon1.vcfly2_ok--- +// cfly2 protection monitor channel 1 +#define ma_dcu_mon1__vcfly2_ok__a 101 +#define ma_dcu_mon1__vcfly2_ok__len 1 +#define ma_dcu_mon1__vcfly2_ok__mask 0x04 +#define ma_dcu_mon1__vcfly2_ok__shift 0x02 +#define ma_dcu_mon1__vcfly2_ok__reset 0x00 +//----------------------------------------------------------dcu_mon1.pvdd_ok--- +// dcu1 pvdd monitor +#define ma_dcu_mon1__pvdd_ok__a 101 +#define ma_dcu_mon1__pvdd_ok__len 1 +#define ma_dcu_mon1__pvdd_ok__mask 0x08 +#define ma_dcu_mon1__pvdd_ok__shift 0x03 +#define ma_dcu_mon1__pvdd_ok__reset 0x00 +//-----------------------------------------------------------dcu_mon1.vdd_ok--- +// dcu1 vdd monitor +#define ma_dcu_mon1__vdd_ok__a 101 +#define ma_dcu_mon1__vdd_ok__len 1 +#define ma_dcu_mon1__vdd_ok__mask 0x10 +#define ma_dcu_mon1__vdd_ok__shift 0x04 +#define ma_dcu_mon1__vdd_ok__reset 0x00 +//-------------------------------------------------------------dcu_mon1.mute--- +// dcu1 mute monitor +#define ma_dcu_mon1__mute__a 101 +#define ma_dcu_mon1__mute__len 1 +#define ma_dcu_mon1__mute__mask 0x20 +#define ma_dcu_mon1__mute__shift 0x05 +#define ma_dcu_mon1__mute__reset 0x00 +//------------------------------------------------------------dcu_mon1.m_mon--- +// m sense monitor channel 1 +#define ma_dcu_mon1__m_mon__a 102 +#define ma_dcu_mon1__m_mon__len 8 +#define ma_dcu_mon1__m_mon__mask 0xff +#define ma_dcu_mon1__m_mon__shift 0x00 +#define ma_dcu_mon1__m_mon__reset 0x00 +//--------------------------------------------------------dcu_mon0.sw_enable--- +// dcu0 switch enable monitor +#define ma_dcu_mon0__sw_enable__a 104 +#define ma_dcu_mon0__sw_enable__len 1 +#define ma_dcu_mon0__sw_enable__mask 0x40 +#define ma_dcu_mon0__sw_enable__shift 0x06 +#define ma_dcu_mon0__sw_enable__reset 0x00 +//--------------------------------------------------------dcu_mon1.sw_enable--- +// dcu1 switch enable monitor +#define ma_dcu_mon1__sw_enable__a 104 +#define ma_dcu_mon1__sw_enable__len 1 +#define ma_dcu_mon1__sw_enable__mask 0x80 +#define ma_dcu_mon1__sw_enable__shift 0x07 +#define ma_dcu_mon1__sw_enable__reset 0x00 +//------------------------------------------------------------hvboot0_ok_mon--- +// hvboot0_ok for test/debug +#define ma_hvboot0_ok_mon__a 105 +#define ma_hvboot0_ok_mon__len 1 +#define ma_hvboot0_ok_mon__mask 0x40 +#define ma_hvboot0_ok_mon__shift 0x06 +#define ma_hvboot0_ok_mon__reset 0x00 +//------------------------------------------------------------hvboot1_ok_mon--- +// hvboot1_ok for test/debug +#define ma_hvboot1_ok_mon__a 105 +#define ma_hvboot1_ok_mon__len 1 +#define ma_hvboot1_ok_mon__mask 0x80 +#define ma_hvboot1_ok_mon__shift 0x07 +#define ma_hvboot1_ok_mon__reset 0x00 +//-----------------------------------------------------------------error_acc--- +// accumulated errors, at and after triggering +#define ma_error_acc__a 109 +#define ma_error_acc__len 8 +#define ma_error_acc__mask 0xff +#define ma_error_acc__shift 0x00 +#define ma_error_acc__reset 0x00 +//-------------------------------------------------------------i2s_data_rate--- +// detected i2s data rate: 00/01/10 = x1/x2/x4 +#define ma_i2s_data_rate__a 116 +#define ma_i2s_data_rate__len 2 +#define ma_i2s_data_rate__mask 0x03 +#define ma_i2s_data_rate__shift 0x00 +#define ma_i2s_data_rate__reset 0x00 +//---------------------------------------------------------audio_in_mode_mon--- +// audio input mode monitor +#define ma_audio_in_mode_mon__a 116 +#define ma_audio_in_mode_mon__len 3 +#define ma_audio_in_mode_mon__mask 0x1c +#define ma_audio_in_mode_mon__shift 0x02 +#define ma_audio_in_mode_mon__reset 0x00 +//------------------------------------------------------------------msel_mon--- +// msel[2:0] monitor register +#define ma_msel_mon__a 117 +#define ma_msel_mon__len 3 +#define ma_msel_mon__mask 0x07 +#define ma_msel_mon__shift 0x00 +#define ma_msel_mon__reset 0x00 +//---------------------------------------------------------------------error--- +// current error flag monitor reg - for app. ctrl. +#define ma_error__a 124 +#define ma_error__len 8 +#define ma_error__mask 0xff +#define ma_error__shift 0x00 +#define ma_error__reset 0x00 +//----------------------------------------------------audio_proc_limiter_mon--- +// b7-b4: channel 3-0 limiter active +#define ma_audio_proc_limiter_mon__a 126 +#define ma_audio_proc_limiter_mon__len 4 +#define ma_audio_proc_limiter_mon__mask 0xf0 +#define ma_audio_proc_limiter_mon__shift 0x04 +#define ma_audio_proc_limiter_mon__reset 0x00 +//-------------------------------------------------------audio_proc_clip_mon--- +// b3-b0: channel 3-0 clipping monitor +#define ma_audio_proc_clip_mon__a 126 +#define ma_audio_proc_clip_mon__len 4 +#define ma_audio_proc_clip_mon__mask 0x0f +#define ma_audio_proc_clip_mon__shift 0x00 +#define ma_audio_proc_clip_mon__reset 0x00 +#endif + +#define SOC_ENUM_ERR(xname, xenum)\ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\ + .access = SNDRV_CTL_ELEM_ACCESS_READ,\ + .info = snd_soc_info_enum_double,\ + .get = snd_soc_get_enum_double, .put = snd_soc_put_enum_double,\ + .private_value = (unsigned long)&(xenum) } + +static struct i2c_client *i2c; + +struct ma120x0p_priv { + struct regmap *regmap; + int mclk_div; + struct snd_soc_component *component; + struct gpio_desc *enable_gpio; + struct gpio_desc *mute_gpio; + struct gpio_desc *booster_gpio; + struct gpio_desc *error_gpio; +}; + +static struct ma120x0p_priv *priv_data; + +//Used to share the IRQ number within this file +static unsigned int irqNumber; + +// Function prototype for the custom IRQ handler function +static irqreturn_t ma120x0p_irq_handler(int irq, void *data); + +//Alsa Controls +static const char * const limenable_text[] = {"Bypassed", "Enabled"}; +static const char * const limatack_text[] = {"Slow", "Normal", "Fast"}; +static const char * const limrelease_text[] = {"Slow", "Normal", "Fast"}; + +static const char * const err_flycap_text[] = {"Ok", "Error"}; +static const char * const err_overcurr_text[] = {"Ok", "Error"}; +static const char * const err_pllerr_text[] = {"Ok", "Error"}; +static const char * const err_pvddunder_text[] = {"Ok", "Error"}; +static const char * const err_overtempw_text[] = {"Ok", "Error"}; +static const char * const err_overtempe_text[] = {"Ok", "Error"}; +static const char * const err_pinlowimp_text[] = {"Ok", "Error"}; +static const char * const err_dcprot_text[] = {"Ok", "Error"}; + +static const char * const pwr_mode_prof_text[] = {"PMF0", "PMF1", "PMF2", +"PMF3", "PMF4"}; + +static const struct soc_enum lim_enable_ctrl = + SOC_ENUM_SINGLE(ma_audio_proc_limiterenable__a, + ma_audio_proc_limiterenable__shift, + ma_audio_proc_limiterenable__len + 1, + limenable_text); +static const struct soc_enum limatack_ctrl = + SOC_ENUM_SINGLE(ma_audio_proc_attack__a, + ma_audio_proc_attack__shift, + ma_audio_proc_attack__len + 1, + limatack_text); +static const struct soc_enum limrelease_ctrl = + SOC_ENUM_SINGLE(ma_audio_proc_release__a, + ma_audio_proc_release__shift, + ma_audio_proc_release__len + 1, + limrelease_text); +static const struct soc_enum err_flycap_ctrl = + SOC_ENUM_SINGLE(ma_error__a, 0, 3, err_flycap_text); +static const struct soc_enum err_overcurr_ctrl = + SOC_ENUM_SINGLE(ma_error__a, 1, 3, err_overcurr_text); +static const struct soc_enum err_pllerr_ctrl = + SOC_ENUM_SINGLE(ma_error__a, 2, 3, err_pllerr_text); +static const struct soc_enum err_pvddunder_ctrl = + SOC_ENUM_SINGLE(ma_error__a, 3, 3, err_pvddunder_text); +static const struct soc_enum err_overtempw_ctrl = + SOC_ENUM_SINGLE(ma_error__a, 4, 3, err_overtempw_text); +static const struct soc_enum err_overtempe_ctrl = + SOC_ENUM_SINGLE(ma_error__a, 5, 3, err_overtempe_text); +static const struct soc_enum err_pinlowimp_ctrl = + SOC_ENUM_SINGLE(ma_error__a, 6, 3, err_pinlowimp_text); +static const struct soc_enum err_dcprot_ctrl = + SOC_ENUM_SINGLE(ma_error__a, 7, 3, err_dcprot_text); +static const struct soc_enum pwr_mode_prof_ctrl = + SOC_ENUM_SINGLE(ma_pmprofile__a, ma_pmprofile__shift, 5, + pwr_mode_prof_text); + +static const char * const pwr_mode_texts[] = { + "Dynamic power mode", + "Power mode 1", + "Power mode 2", + "Power mode 3", + }; + +static const int pwr_mode_values[] = { + 0x10, + 0x50, + 0x60, + 0x70, + }; + +static SOC_VALUE_ENUM_SINGLE_DECL(pwr_mode_ctrl, + ma_pm_man__a, 0, 0x70, + pwr_mode_texts, + pwr_mode_values); + +static const DECLARE_TLV_DB_SCALE(ma120x0p_vol_tlv, -14400, 100, 0); +static const DECLARE_TLV_DB_SCALE(ma120x0p_lim_tlv, -5000, 100, 0); +static const DECLARE_TLV_DB_SCALE(ma120x0p_lr_tlv, -5000, 100, 0); + +static const struct snd_kcontrol_new ma120x0p_snd_controls[] = { + //Master Volume + SOC_SINGLE_RANGE_TLV("A.Mstr Vol Volume", + ma_vol_db_master__a, 0, 0x18, 0xa8, 1, ma120x0p_vol_tlv), + + //L-R Volume ch0 + SOC_SINGLE_RANGE_TLV("B.L Vol Volume", + ma_vol_db_ch0__a, 0, 0x18, 0x4a, 1, ma120x0p_lr_tlv), + SOC_SINGLE_RANGE_TLV("C.R Vol Volume", + ma_vol_db_ch1__a, 0, 0x18, 0x4a, 1, ma120x0p_lr_tlv), + + //L-R Limiter Threshold ch0-ch1 + SOC_DOUBLE_R_RANGE_TLV("D.Lim thresh Volume", + ma_thr_db_ch0__a, ma_thr_db_ch1__a, 0, 0x0e, 0x4a, 1, + ma120x0p_lim_tlv), + + //Enum Switches/Selectors + //SOC_ENUM("E.AudioProc Mute", audioproc_mute_ctrl), + SOC_ENUM("F.Limiter Enable", lim_enable_ctrl), + SOC_ENUM("G.Limiter Attck", limatack_ctrl), + SOC_ENUM("H.Limiter Rls", limrelease_ctrl), + + //Enum Error Monitor (read-only) + SOC_ENUM_ERR("I.Err flycap", err_flycap_ctrl), + SOC_ENUM_ERR("J.Err overcurr", err_overcurr_ctrl), + SOC_ENUM_ERR("K.Err pllerr", err_pllerr_ctrl), + SOC_ENUM_ERR("L.Err pvddunder", err_pvddunder_ctrl), + SOC_ENUM_ERR("M.Err overtempw", err_overtempw_ctrl), + SOC_ENUM_ERR("N.Err overtempe", err_overtempe_ctrl), + SOC_ENUM_ERR("O.Err pinlowimp", err_pinlowimp_ctrl), + SOC_ENUM_ERR("P.Err dcprot", err_dcprot_ctrl), + + //Power modes profiles + SOC_ENUM("Q.PM Prof", pwr_mode_prof_ctrl), + + // Power mode selection (Dynamic,1,2,3) + SOC_ENUM("R.Power Mode", pwr_mode_ctrl), +}; + +//Machine Driver +static int ma120x0p_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + u16 blen = 0x00; + + struct snd_soc_component *component = dai->component; + + priv_data->component = component; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + blen = 0x10; + break; + case SNDRV_PCM_FORMAT_S24_LE: + blen = 0x00; + break; + case SNDRV_PCM_FORMAT_S32_LE: + blen = 0x00; + break; + default: + dev_err(dai->dev, "Unsupported word length: %u\n", + params_format(params)); + return -EINVAL; + } + + // set word length + snd_soc_component_update_bits(component, ma_i2s_framesize__a, + ma_i2s_framesize__mask, blen); + + return 0; +} + +static int ma120x0p_mute_stream(struct snd_soc_dai *dai, int mute, int stream) +{ + int val = 0; + + struct ma120x0p_priv *ma120x0p; + + struct snd_soc_component *component = dai->component; + + ma120x0p = snd_soc_component_get_drvdata(component); + + if (mute) + val = 0; + else + val = 1; + + gpiod_set_value_cansleep(priv_data->mute_gpio, val); + + return 0; +} + +static const struct snd_soc_dai_ops ma120x0p_dai_ops = { + .hw_params = ma120x0p_hw_params, + .mute_stream = ma120x0p_mute_stream, +}; + +static struct snd_soc_dai_driver ma120x0p_dai = { + .name = "ma120x0p-amp", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 44100, + .rate_max = 192000, + .formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE + }, + .ops = &ma120x0p_dai_ops, +}; + +//Codec Driver +static int ma120x0p_clear_err(struct snd_soc_component *component) +{ + int ret = 0; + + struct ma120x0p_priv *ma120x0p; + + ma120x0p = snd_soc_component_get_drvdata(component); + + ret = snd_soc_component_update_bits(component, + ma_eh_clear__a, ma_eh_clear__mask, 0x00); + if (ret < 0) + return ret; + + ret = snd_soc_component_update_bits(component, + ma_eh_clear__a, ma_eh_clear__mask, 0x04); + if (ret < 0) + return ret; + + ret = snd_soc_component_update_bits(component, + ma_eh_clear__a, ma_eh_clear__mask, 0x00); + if (ret < 0) + return ret; + + return 0; +} + +static void ma120x0p_remove(struct snd_soc_component *component) +{ + struct ma120x0p_priv *ma120x0p; + + ma120x0p = snd_soc_component_get_drvdata(component); +} + +static int ma120x0p_probe(struct snd_soc_component *component) +{ + struct ma120x0p_priv *ma120x0p; + + int ret = 0; + + i2c = container_of(component->dev, struct i2c_client, dev); + + ma120x0p = snd_soc_component_get_drvdata(component); + + //Reset error + ma120x0p_clear_err(component); + if (ret < 0) + return ret; + + // set serial audio format I2S and enable audio processor + ret = snd_soc_component_write(component, ma_i2s_format__a, 0x08); + if (ret < 0) + return ret; + + // Enable audio limiter + ret = snd_soc_component_update_bits(component, + ma_audio_proc_limiterenable__a, + ma_audio_proc_limiterenable__mask, 0x40); + if (ret < 0) + return ret; + + // Set lim attack to fast + ret = snd_soc_component_update_bits(component, + ma_audio_proc_attack__a, ma_audio_proc_attack__mask, 0x80); + if (ret < 0) + return ret; + + // Set lim attack to low + ret = snd_soc_component_update_bits(component, + ma_audio_proc_release__a, ma_audio_proc_release__mask, 0x00); + if (ret < 0) + return ret; + + // set volume to 0dB + ret = snd_soc_component_write(component, ma_vol_db_master__a, 0x18); + if (ret < 0) + return ret; + + // set ch0 lim thresh to -15dB + ret = snd_soc_component_write(component, ma_thr_db_ch0__a, 0x27); + if (ret < 0) + return ret; + + // set ch1 lim thresh to -15dB + ret = snd_soc_component_write(component, ma_thr_db_ch1__a, 0x27); + if (ret < 0) + return ret; + + //Check for errors + ret = snd_soc_component_test_bits(component, ma_error_acc__a, 0x00, 0); + if (ret < 0) + return ret; + ret = snd_soc_component_test_bits(component, ma_error_acc__a, 0x01, 0); + if (ret < 0) + return ret; + ret = snd_soc_component_test_bits(component, ma_error_acc__a, 0x02, 0); + if (ret < 0) + return ret; + ret = snd_soc_component_test_bits(component, ma_error_acc__a, 0x08, 0); + if (ret < 0) + return ret; + ret = snd_soc_component_test_bits(component, ma_error_acc__a, 0x10, 0); + if (ret < 0) + return ret; + ret = snd_soc_component_test_bits(component, ma_error_acc__a, 0x20, 0); + if (ret < 0) + return ret; + ret = snd_soc_component_test_bits(component, ma_error_acc__a, 0x40, 0); + if (ret < 0) + return ret; + ret = snd_soc_component_test_bits(component, ma_error_acc__a, 0x80, 0); + if (ret < 0) + return ret; + + return 0; +} + +static int ma120x0p_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + int ret = 0; + + struct ma120x0p_priv *ma120x0p; + + ma120x0p = snd_soc_component_get_drvdata(component); + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + break; + + case SND_SOC_BIAS_STANDBY: + ret = gpiod_get_value_cansleep(priv_data->enable_gpio); + if (ret != 0) { + dev_err(component->dev, "Device ma120x0p disabled in STANDBY BIAS: %d\n", + ret); + return ret; + } + break; + + case SND_SOC_BIAS_OFF: + break; + } + + return 0; +} + +static const struct snd_soc_dapm_widget ma120x0p_dapm_widgets[] = { + SND_SOC_DAPM_OUTPUT("OUT_A"), + SND_SOC_DAPM_OUTPUT("OUT_B"), +}; + +static const struct snd_soc_dapm_route ma120x0p_dapm_routes[] = { + { "OUT_B", NULL, "Playback" }, + { "OUT_A", NULL, "Playback" }, +}; + +static const struct snd_soc_component_driver ma120x0p_component_driver = { + .probe = ma120x0p_probe, + .remove = ma120x0p_remove, + .set_bias_level = ma120x0p_set_bias_level, + .dapm_widgets = ma120x0p_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(ma120x0p_dapm_widgets), + .dapm_routes = ma120x0p_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(ma120x0p_dapm_routes), + .controls = ma120x0p_snd_controls, + .num_controls = ARRAY_SIZE(ma120x0p_snd_controls), + .use_pmdown_time = 1, + .endianness = 1, +}; + +//I2C Driver +static const struct reg_default ma120x0p_reg_defaults[] = { + { 0x01, 0x3c }, +}; + +static bool ma120x0p_reg_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ma_error__a: + return true; + default: + return false; + } +} + +static const struct of_device_id ma120x0p_of_match[] = { + { .compatible = "ma,ma120x0p", }, + { } +}; + +MODULE_DEVICE_TABLE(of, ma120x0p_of_match); + +static struct regmap_config ma120x0p_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = 255, + .volatile_reg = ma120x0p_reg_volatile, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = ma120x0p_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(ma120x0p_reg_defaults), +}; + +static int ma120x0p_i2c_probe(struct i2c_client *i2c) +{ + int ret; + + priv_data = devm_kzalloc(&i2c->dev, sizeof(*priv_data), GFP_KERNEL); + if (!priv_data) + return -ENOMEM; + i2c_set_clientdata(i2c, priv_data); + + priv_data->regmap = devm_regmap_init_i2c(i2c, &ma120x0p_regmap_config); + if (IS_ERR(priv_data->regmap)) { + ret = PTR_ERR(priv_data->regmap); + return ret; + } + + //Startup sequence + + //Make sure the device is muted + priv_data->mute_gpio = devm_gpiod_get_optional(&i2c->dev, "mute_gp", + GPIOD_OUT_LOW); + if (IS_ERR(priv_data->mute_gpio)) { + ret = PTR_ERR(priv_data->mute_gpio); + dev_err(&i2c->dev, "Failed to get mute gpio line: %d\n", ret); + return ret; + } + msleep(50); + +// MA120xx0P devices are usually powered by an integrated boost converter. +// An option GPIO control line is provided to enable the booster properly and +// in sync with the enable and mute GPIO lines. + priv_data->booster_gpio = devm_gpiod_get_optional(&i2c->dev, + "booster_gp", GPIOD_OUT_LOW); + if (IS_ERR(priv_data->booster_gpio)) { + ret = PTR_ERR(priv_data->booster_gpio); + dev_err(&i2c->dev, + "Failed to get booster enable gpio line: %d\n", ret); + return ret; + } + msleep(50); + + //Enable booster and wait 200ms until stable PVDD + gpiod_set_value_cansleep(priv_data->booster_gpio, 1); + msleep(200); + + //Enable ma120x0pp + priv_data->enable_gpio = devm_gpiod_get_optional(&i2c->dev, + "enable_gp", GPIOD_OUT_LOW); + if (IS_ERR(priv_data->enable_gpio)) { + ret = PTR_ERR(priv_data->enable_gpio); + dev_err(&i2c->dev, + "Failed to get ma120x0p enable gpio line: %d\n", ret); + return ret; + } + msleep(50); + + //Optional use of ma120x0pp error line as an interrupt trigger to + //platform GPIO. + //Get error input gpio ma120x0p + priv_data->error_gpio = devm_gpiod_get_optional(&i2c->dev, + "error_gp", GPIOD_IN); + if (IS_ERR(priv_data->error_gpio)) { + ret = PTR_ERR(priv_data->error_gpio); + dev_err(&i2c->dev, + "Failed to get ma120x0p error gpio line: %d\n", ret); + return ret; + } + + if (priv_data->error_gpio != NULL) { + irqNumber = gpiod_to_irq(priv_data->error_gpio); + + ret = devm_request_threaded_irq(&i2c->dev, + irqNumber, ma120x0p_irq_handler, + NULL, IRQF_TRIGGER_FALLING, + "ma120x0p", priv_data); + if (ret != 0) + dev_warn(&i2c->dev, "Failed to request IRQ: %d\n", + ret); + } + + ret = devm_snd_soc_register_component(&i2c->dev, + &ma120x0p_component_driver, &ma120x0p_dai, 1); + + return ret; +} + +static irqreturn_t ma120x0p_irq_handler(int irq, void *data) +{ + gpiod_set_value_cansleep(priv_data->mute_gpio, 0); + gpiod_set_value_cansleep(priv_data->enable_gpio, 1); + return IRQ_HANDLED; +} + +static void ma120x0p_i2c_remove(struct i2c_client *i2c) +{ + snd_soc_unregister_component(&i2c->dev); + i2c_set_clientdata(i2c, NULL); + + gpiod_set_value_cansleep(priv_data->mute_gpio, 0); + msleep(30); + gpiod_set_value_cansleep(priv_data->enable_gpio, 1); + msleep(200); + gpiod_set_value_cansleep(priv_data->booster_gpio, 0); + msleep(200); + + kfree(priv_data); +} + +static void ma120x0p_i2c_shutdown(struct i2c_client *i2c) +{ + snd_soc_unregister_component(&i2c->dev); + i2c_set_clientdata(i2c, NULL); + + gpiod_set_value_cansleep(priv_data->mute_gpio, 0); + msleep(30); + gpiod_set_value_cansleep(priv_data->enable_gpio, 1); + msleep(200); + gpiod_set_value_cansleep(priv_data->booster_gpio, 0); + msleep(200); + + kfree(priv_data); +} + +static const struct i2c_device_id ma120x0p_i2c_id[] = { + { "ma120x0p", 0 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, ma120x0p_i2c_id); + +static struct i2c_driver ma120x0p_i2c_driver = { + .driver = { + .name = "ma120x0p", + .owner = THIS_MODULE, + .of_match_table = ma120x0p_of_match, + }, + .probe = ma120x0p_i2c_probe, + .remove = ma120x0p_i2c_remove, + .shutdown = ma120x0p_i2c_shutdown, + .id_table = ma120x0p_i2c_id +}; + +static int __init ma120x0p_modinit(void) +{ + int ret = 0; + + ret = i2c_add_driver(&ma120x0p_i2c_driver); + if (ret != 0) { + pr_err("Failed to register MA120X0P I2C driver: %d\n", ret); + return ret; + } + return ret; +} +module_init(ma120x0p_modinit); + +static void __exit ma120x0p_exit(void) +{ + i2c_del_driver(&ma120x0p_i2c_driver); +} +module_exit(ma120x0p_exit); + +MODULE_AUTHOR("Ariel Muszkat ariel.muszkat@gmail.com>"); +MODULE_DESCRIPTION("ASoC driver for ma120x0p"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/pcm1794a.c b/sound/soc/codecs/pcm1794a.c new file mode 100644 index 00000000000000..3877a138f896bf --- /dev/null +++ b/sound/soc/codecs/pcm1794a.c @@ -0,0 +1,68 @@ +/* + * Driver for the PCM1794A codec + * + * Author: Florian Meier <florian.meier@koalo.de> + * Copyright 2013 + * + * 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 <linux/init.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#include <sound/soc.h> + +static struct snd_soc_dai_driver pcm1794a_dai = { + .name = "pcm1794a-hifi", + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE + }, +}; + +static struct snd_soc_component_driver soc_component_dev_pcm1794a; + +static int pcm1794a_probe(struct platform_device *pdev) +{ + return snd_soc_register_component(&pdev->dev, &soc_component_dev_pcm1794a, + &pcm1794a_dai, 1); +} + +static void pcm1794a_remove(struct platform_device *pdev) +{ + snd_soc_unregister_component(&pdev->dev); +} + +static const struct of_device_id pcm1794a_of_match[] = { + { .compatible = "ti,pcm1794a", }, + { } +}; +MODULE_DEVICE_TABLE(of, pcm1794a_of_match); + +static struct platform_driver pcm1794a_component_driver = { + .probe = pcm1794a_probe, + .remove = pcm1794a_remove, + .driver = { + .name = "pcm1794a-codec", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(pcm1794a_of_match), + }, +}; + +module_platform_driver(pcm1794a_component_driver); + +MODULE_DESCRIPTION("ASoC PCM1794A codec driver"); +MODULE_AUTHOR("Florian Meier <florian.meier@koalo.de>"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/pcm3168a.c b/sound/soc/codecs/pcm3168a.c index fac0617ab95b65..9e7b030437404d 100644 --- a/sound/soc/codecs/pcm3168a.c +++ b/sound/soc/codecs/pcm3168a.c @@ -60,6 +60,7 @@ struct pcm3168a_priv { struct clk *scki; struct gpio_desc *gpio_rst; unsigned long sysclk; + bool adc_fc, dac_fc; // Force clock consumer mode struct pcm3168a_io_params io_params[2]; struct snd_soc_dai_driver dai_drv[2]; @@ -478,6 +479,12 @@ static int pcm3168a_hw_params(struct snd_pcm_substream *substream, ms = 0; } + // Force clock consumer mode if needed + if (pcm3168a->adc_fc && dai->id == PCM3168A_DAI_ADC) + ms = 0; + if (pcm3168a->dac_fc && dai->id == PCM3168A_DAI_DAC) + ms = 0; + format = io_params->format; if (io_params->slot_width) @@ -756,6 +763,11 @@ int pcm3168a_probe(struct device *dev, struct regmap *regmap) pcm3168a->sysclk = clk_get_rate(pcm3168a->scki); + pcm3168a->adc_fc = of_property_read_bool(dev->of_node, + "adc-force-cons"); + pcm3168a->dac_fc = of_property_read_bool(dev->of_node, + "dac-force-cons"); + for (i = 0; i < ARRAY_SIZE(pcm3168a->supplies); i++) pcm3168a->supplies[i].supply = pcm3168a_supply_names[i]; diff --git a/sound/soc/codecs/pcm512x.c b/sound/soc/codecs/pcm512x.c index aa8edf87b74339..c653998497f342 100644 --- a/sound/soc/codecs/pcm512x.c +++ b/sound/soc/codecs/pcm512x.c @@ -537,7 +537,7 @@ static unsigned long pcm512x_ncp_target(struct pcm512x_priv *pcm512x, static const u32 pcm512x_dai_rates[] = { 8000, 11025, 16000, 22050, 32000, 44100, 48000, 64000, - 88200, 96000, 176400, 192000, 384000, + 88200, 96000, 176400, 192000, 352800, 384000, }; static const struct snd_pcm_hw_constraint_list constraints_slave = { @@ -630,7 +630,7 @@ static int pcm512x_dai_startup_slave(struct snd_pcm_substream *substream, struct regmap *regmap = pcm512x->regmap; if (IS_ERR(pcm512x->sclk)) { - dev_info(dev, "No SCLK, using BCLK: %ld\n", + dev_dbg(dev, "No SCLK, using BCLK: %ld\n", PTR_ERR(pcm512x->sclk)); /* Disable reporting of missing SCLK as an error */ diff --git a/sound/soc/codecs/tas5713.c b/sound/soc/codecs/tas5713.c new file mode 100644 index 00000000000000..01dd21a1d7a256 --- /dev/null +++ b/sound/soc/codecs/tas5713.c @@ -0,0 +1,360 @@ +/* + * ASoC Driver for TAS5713 + * + * Author: Sebastian Eickhoff <basti.eickhoff@googlemail.com> + * Copyright 2014 + * + * 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 <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/of_device.h> +#include <linux/spi/spi.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/initval.h> +#include <sound/tlv.h> + +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/fs.h> +#include <asm/uaccess.h> + +#include "tas5713.h" + + +static struct i2c_client *i2c; + +struct tas5713_priv { + struct regmap *regmap; + int mclk_div; + struct snd_soc_component *component; +}; + +static struct tas5713_priv *priv_data; + + + + +/* + * _ _ ___ _ ___ _ _ + * /_\ | | / __| /_\ / __|___ _ _| |_ _ _ ___| |___ + * / _ \| |__\__ \/ _ \ | (__/ _ \ ' \ _| '_/ _ \ (_-< + * /_/ \_\____|___/_/ \_\ \___\___/_||_\__|_| \___/_/__/ + * + */ + +static const DECLARE_TLV_DB_SCALE(tas5713_vol_tlv, -10000, 50, 1); + + +static const struct snd_kcontrol_new tas5713_snd_controls[] = { + SOC_SINGLE_TLV ("Master" , TAS5713_VOL_MASTER, 0, 248, 1, tas5713_vol_tlv), + SOC_DOUBLE_R_TLV("Channels" , TAS5713_VOL_CH1, TAS5713_VOL_CH2, 0, 248, 1, tas5713_vol_tlv) +}; + + + + +/* + * __ __ _ _ ___ _ + * | \/ |__ _ __| |_ (_)_ _ ___ | \ _ _(_)_ _____ _ _ + * | |\/| / _` / _| ' \| | ' \/ -_) | |) | '_| \ V / -_) '_| + * |_| |_\__,_\__|_||_|_|_||_\___| |___/|_| |_|\_/\___|_| + * + */ + +static int tas5713_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + u16 blen = 0x00; + + struct snd_soc_component *component = dai->component; + priv_data->component = component; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + blen = 0x03; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + blen = 0x1; + break; + case SNDRV_PCM_FORMAT_S24_LE: + blen = 0x04; + break; + case SNDRV_PCM_FORMAT_S32_LE: + blen = 0x05; + break; + default: + dev_err(dai->dev, "Unsupported word length: %u\n", + params_format(params)); + return -EINVAL; + } + + // set word length + snd_soc_component_update_bits(component, TAS5713_SERIAL_DATA_INTERFACE, 0x7, blen); + + return 0; +} + + +static int tas5713_mute_stream(struct snd_soc_dai *dai, int mute, int stream) +{ + unsigned int val = 0; + + struct tas5713_priv *tas5713; + struct snd_soc_component *component = dai->component; + tas5713 = snd_soc_component_get_drvdata(component); + + if (mute) { + val = TAS5713_SOFT_MUTE_ALL; + } + + return regmap_write(tas5713->regmap, TAS5713_SOFT_MUTE, val); +} + + +static const struct snd_soc_dai_ops tas5713_dai_ops = { + .hw_params = tas5713_hw_params, + .mute_stream = tas5713_mute_stream, +}; + + +static struct snd_soc_dai_driver tas5713_dai = { + .name = "tas5713-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE ), + }, + .ops = &tas5713_dai_ops, +}; + + + + +/* + * ___ _ ___ _ + * / __|___ __| |___ __ | \ _ _(_)_ _____ _ _ + * | (__/ _ \/ _` / -_) _| | |) | '_| \ V / -_) '_| + * \___\___/\__,_\___\__| |___/|_| |_|\_/\___|_| + * + */ + +static void tas5713_remove(struct snd_soc_component *component) +{ + struct tas5713_priv *tas5713; + + tas5713 = snd_soc_component_get_drvdata(component); +} + + +static int tas5713_probe(struct snd_soc_component *component) +{ + struct tas5713_priv *tas5713; + int i, ret; + + i2c = container_of(component->dev, struct i2c_client, dev); + + tas5713 = snd_soc_component_get_drvdata(component); + + // Reset error + ret = snd_soc_component_write(component, TAS5713_ERROR_STATUS, 0x00); + if (ret < 0) return ret; + + // Trim oscillator + ret = snd_soc_component_write(component, TAS5713_OSC_TRIM, 0x00); + if (ret < 0) return ret; + msleep(1000); + + // Reset error + ret = snd_soc_component_write(component, TAS5713_ERROR_STATUS, 0x00); + if (ret < 0) return ret; + + // I2S 24bit + ret = snd_soc_component_write(component, TAS5713_SERIAL_DATA_INTERFACE, 0x05); + if (ret < 0) return ret; + + // Unmute + ret = snd_soc_component_write(component, TAS5713_SYSTEM_CTRL2, 0x00); + if (ret < 0) return ret; + ret = snd_soc_component_write(component, TAS5713_SOFT_MUTE, 0x00); + if (ret < 0) return ret; + + // Set volume to 0db + ret = snd_soc_component_write(component, TAS5713_VOL_MASTER, 0x00); + if (ret < 0) return ret; + + // Now start programming the default initialization sequence + for (i = 0; i < ARRAY_SIZE(tas5713_init_sequence); ++i) { + ret = i2c_master_send(i2c, + tas5713_init_sequence[i].data, + tas5713_init_sequence[i].size); + if (ret < 0) { + printk(KERN_INFO "TAS5713 CODEC PROBE: InitSeq returns: %d\n", ret); + } + } + + // Unmute + ret = snd_soc_component_write(component, TAS5713_SYSTEM_CTRL2, 0x00); + if (ret < 0) return ret; + + return 0; +} + + +static struct snd_soc_component_driver soc_codec_dev_tas5713 = { + .probe = tas5713_probe, + .remove = tas5713_remove, + .controls = tas5713_snd_controls, + .num_controls = ARRAY_SIZE(tas5713_snd_controls), +}; + + + + +/* + * ___ ___ ___ ___ _ + * |_ _|_ ) __| | \ _ _(_)_ _____ _ _ + * | | / / (__ | |) | '_| \ V / -_) '_| + * |___/___\___| |___/|_| |_|\_/\___|_| + * + */ + +static const struct reg_default tas5713_reg_defaults[] = { + { 0x07 ,0x80 }, // R7 - VOL_MASTER - -40dB + { 0x08 , 30 }, // R8 - VOL_CH1 - 0dB + { 0x09 , 30 }, // R9 - VOL_CH2 - 0dB + { 0x0A ,0x80 }, // R10 - VOL_HEADPHONE - -40dB +}; + + +static bool tas5713_reg_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TAS5713_DEVICE_ID: + case TAS5713_ERROR_STATUS: + case TAS5713_CLOCK_CTRL: + return true; + default: + return false; + } +} + + +static const struct of_device_id tas5713_of_match[] = { + { .compatible = "ti,tas5713", }, + { } +}; +MODULE_DEVICE_TABLE(of, tas5713_of_match); + + +static struct regmap_config tas5713_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = TAS5713_MAX_REGISTER, + .volatile_reg = tas5713_reg_volatile, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = tas5713_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(tas5713_reg_defaults), +}; + + +static int tas5713_i2c_probe(struct i2c_client *i2c) +{ + int ret; + + priv_data = devm_kzalloc(&i2c->dev, sizeof *priv_data, GFP_KERNEL); + if (!priv_data) + return -ENOMEM; + + priv_data->regmap = devm_regmap_init_i2c(i2c, &tas5713_regmap_config); + if (IS_ERR(priv_data->regmap)) { + ret = PTR_ERR(priv_data->regmap); + return ret; + } + + i2c_set_clientdata(i2c, priv_data); + + ret = snd_soc_register_component(&i2c->dev, + &soc_codec_dev_tas5713, &tas5713_dai, 1); + + return ret; +} + + +static void tas5713_i2c_remove(struct i2c_client *i2c) +{ + snd_soc_unregister_component(&i2c->dev); + i2c_set_clientdata(i2c, NULL); + + kfree(priv_data); +} + + +static const struct i2c_device_id tas5713_i2c_id[] = { + { "tas5713", 0 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, tas5713_i2c_id); + + +static struct i2c_driver tas5713_i2c_driver = { + .driver = { + .name = "tas5713", + .owner = THIS_MODULE, + .of_match_table = tas5713_of_match, + }, + .probe = tas5713_i2c_probe, + .remove = tas5713_i2c_remove, + .id_table = tas5713_i2c_id +}; + + +static int __init tas5713_modinit(void) +{ + int ret = 0; + + ret = i2c_add_driver(&tas5713_i2c_driver); + if (ret) { + printk(KERN_ERR "Failed to register tas5713 I2C driver: %d\n", + ret); + } + + return ret; +} +module_init(tas5713_modinit); + + +static void __exit tas5713_exit(void) +{ + i2c_del_driver(&tas5713_i2c_driver); +} +module_exit(tas5713_exit); + + +MODULE_AUTHOR("Sebastian Eickhoff <basti.eickhoff@googlemail.com>"); +MODULE_DESCRIPTION("ASoC driver for TAS5713"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/tas5713.h b/sound/soc/codecs/tas5713.h new file mode 100644 index 00000000000000..8f019e04898754 --- /dev/null +++ b/sound/soc/codecs/tas5713.h @@ -0,0 +1,210 @@ +/* + * ASoC Driver for TAS5713 + * + * Author: Sebastian Eickhoff <basti.eickhoff@googlemail.com> + * Copyright 2014 + * + * 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 _TAS5713_H +#define _TAS5713_H + + +// TAS5713 I2C-bus register addresses + +#define TAS5713_CLOCK_CTRL 0x00 +#define TAS5713_DEVICE_ID 0x01 +#define TAS5713_ERROR_STATUS 0x02 +#define TAS5713_SYSTEM_CTRL1 0x03 +#define TAS5713_SERIAL_DATA_INTERFACE 0x04 +#define TAS5713_SYSTEM_CTRL2 0x05 +#define TAS5713_SOFT_MUTE 0x06 +#define TAS5713_VOL_MASTER 0x07 +#define TAS5713_VOL_CH1 0x08 +#define TAS5713_VOL_CH2 0x09 +#define TAS5713_VOL_HEADPHONE 0x0A +#define TAS5713_VOL_CONFIG 0x0E +#define TAS5713_MODULATION_LIMIT 0x10 +#define TAS5713_IC_DLY_CH1 0x11 +#define TAS5713_IC_DLY_CH2 0x12 +#define TAS5713_IC_DLY_CH3 0x13 +#define TAS5713_IC_DLY_CH4 0x14 + +#define TAS5713_START_STOP_PERIOD 0x1A +#define TAS5713_OSC_TRIM 0x1B +#define TAS5713_BKND_ERR 0x1C + +#define TAS5713_INPUT_MUX 0x20 +#define TAS5713_SRC_SELECT_CH4 0x21 +#define TAS5713_PWM_MUX 0x25 + +#define TAS5713_CH1_BQ0 0x29 +#define TAS5713_CH1_BQ1 0x2A +#define TAS5713_CH1_BQ2 0x2B +#define TAS5713_CH1_BQ3 0x2C +#define TAS5713_CH1_BQ4 0x2D +#define TAS5713_CH1_BQ5 0x2E +#define TAS5713_CH1_BQ6 0x2F +#define TAS5713_CH1_BQ7 0x58 +#define TAS5713_CH1_BQ8 0x59 + +#define TAS5713_CH2_BQ0 0x30 +#define TAS5713_CH2_BQ1 0x31 +#define TAS5713_CH2_BQ2 0x32 +#define TAS5713_CH2_BQ3 0x33 +#define TAS5713_CH2_BQ4 0x34 +#define TAS5713_CH2_BQ5 0x35 +#define TAS5713_CH2_BQ6 0x36 +#define TAS5713_CH2_BQ7 0x5C +#define TAS5713_CH2_BQ8 0x5D + +#define TAS5713_CH4_BQ0 0x5A +#define TAS5713_CH4_BQ1 0x5B +#define TAS5713_CH3_BQ0 0x5E +#define TAS5713_CH3_BQ1 0x5F + +#define TAS5713_DRC1_SOFTENING_FILTER_ALPHA_OMEGA 0x3B +#define TAS5713_DRC1_ATTACK_RELEASE_RATE 0x3C +#define TAS5713_DRC2_SOFTENING_FILTER_ALPHA_OMEGA 0x3E +#define TAS5713_DRC2_ATTACK_RELEASE_RATE 0x3F +#define TAS5713_DRC1_ATTACK_RELEASE_THRES 0x40 +#define TAS5713_DRC2_ATTACK_RELEASE_THRES 0x43 +#define TAS5713_DRC_CTRL 0x46 + +#define TAS5713_BANK_SW_CTRL 0x50 +#define TAS5713_CH1_OUTPUT_MIXER 0x51 +#define TAS5713_CH2_OUTPUT_MIXER 0x52 +#define TAS5713_CH1_INPUT_MIXER 0x53 +#define TAS5713_CH2_INPUT_MIXER 0x54 +#define TAS5713_OUTPUT_POST_SCALE 0x56 +#define TAS5713_OUTPUT_PRESCALE 0x57 + +#define TAS5713_IDF_POST_SCALE 0x62 + +#define TAS5713_CH1_INLINE_MIXER 0x70 +#define TAS5713_CH1_INLINE_DRC_EN_MIXER 0x71 +#define TAS5713_CH1_R_CHANNEL_MIXER 0x72 +#define TAS5713_CH1_L_CHANNEL_MIXER 0x73 +#define TAS5713_CH2_INLINE_MIXER 0x74 +#define TAS5713_CH2_INLINE_DRC_EN_MIXER 0x75 +#define TAS5713_CH2_L_CHANNEL_MIXER 0x76 +#define TAS5713_CH2_R_CHANNEL_MIXER 0x77 + +#define TAS5713_UPDATE_DEV_ADDR_KEY 0xF8 +#define TAS5713_UPDATE_DEV_ADDR_REG 0xF9 + +#define TAS5713_REGISTER_COUNT 0x46 +#define TAS5713_MAX_REGISTER 0xF9 + + +// Bitmasks for registers +#define TAS5713_SOFT_MUTE_ALL 0x07 + + + +struct tas5713_init_command { + const int size; + const char *const data; +}; + +static const struct tas5713_init_command tas5713_init_sequence[] = { + + // Trim oscillator + { .size = 2, .data = "\x1B\x00" }, + // System control register 1 (0x03): block DC + { .size = 2, .data = "\x03\x80" }, + // Mute everything + { .size = 2, .data = "\x05\x40" }, + // Modulation limit register (0x10): 97.7% + { .size = 2, .data = "\x10\x02" }, + // Interchannel delay registers + // (0x11, 0x12, 0x13, and 0x14): BD mode + { .size = 2, .data = "\x11\xB8" }, + { .size = 2, .data = "\x12\x60" }, + { .size = 2, .data = "\x13\xA0" }, + { .size = 2, .data = "\x14\x48" }, + // PWM shutdown group register (0x19): no shutdown + { .size = 2, .data = "\x19\x00" }, + // Input multiplexer register (0x20): BD mode + { .size = 2, .data = "\x20\x00\x89\x77\x72" }, + // PWM output mux register (0x25) + // Channel 1 --> OUTA, channel 1 neg --> OUTB + // Channel 2 --> OUTC, channel 2 neg --> OUTD + { .size = 5, .data = "\x25\x01\x02\x13\x45" }, + // DRC control (0x46): DRC off + { .size = 5, .data = "\x46\x00\x00\x00\x00" }, + // BKND_ERR register (0x1C): 299ms reset period + { .size = 2, .data = "\x1C\x07" }, + // Mute channel 3 + { .size = 2, .data = "\x0A\xFF" }, + // Volume configuration register (0x0E): volume slew 512 steps + { .size = 2, .data = "\x0E\x90" }, + // Clock control register (0x00): 44/48kHz, MCLK=64xfs + { .size = 2, .data = "\x00\x60" }, + // Bank switch and eq control (0x50): no bank switching + { .size = 5, .data = "\x50\x00\x00\x00\x00" }, + // Volume registers (0x07, 0x08, 0x09, 0x0A) + { .size = 2, .data = "\x07\x20" }, + { .size = 2, .data = "\x08\x30" }, + { .size = 2, .data = "\x09\x30" }, + { .size = 2, .data = "\x0A\xFF" }, + // 0x72, 0x73, 0x76, 0x77 input mixer: + // no intermix between channels + { .size = 5, .data = "\x72\x00\x00\x00\x00" }, + { .size = 5, .data = "\x73\x00\x80\x00\x00" }, + { .size = 5, .data = "\x76\x00\x00\x00\x00" }, + { .size = 5, .data = "\x77\x00\x80\x00\x00" }, + // 0x70, 0x71, 0x74, 0x75 inline DRC mixer: + // no inline DRC inmix + { .size = 5, .data = "\x70\x00\x80\x00\x00" }, + { .size = 5, .data = "\x71\x00\x00\x00\x00" }, + { .size = 5, .data = "\x74\x00\x80\x00\x00" }, + { .size = 5, .data = "\x75\x00\x00\x00\x00" }, + // 0x56, 0x57 Output scale + { .size = 5, .data = "\x56\x00\x80\x00\x00" }, + { .size = 5, .data = "\x57\x00\x02\x00\x00" }, + // 0x3B, 0x3c + { .size = 9, .data = "\x3B\x00\x08\x00\x00\x00\x78\x00\x00" }, + { .size = 9, .data = "\x3C\x00\x00\x01\x00\xFF\xFF\xFF\x00" }, + { .size = 9, .data = "\x3E\x00\x08\x00\x00\x00\x78\x00\x00" }, + { .size = 9, .data = "\x3F\x00\x00\x01\x00\xFF\xFF\xFF\x00" }, + { .size = 9, .data = "\x40\x00\x00\x01\x00\xFF\xFF\xFF\x00" }, + { .size = 9, .data = "\x43\x00\x00\x01\x00\xFF\xFF\xFF\x00" }, + // 0x51, 0x52: output mixer + { .size = 9, .data = "\x51\x00\x80\x00\x00\x00\x00\x00\x00" }, + { .size = 9, .data = "\x52\x00\x80\x00\x00\x00\x00\x00\x00" }, + // PEQ defaults + { .size = 21, .data = "\x29\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }, + { .size = 21, .data = "\x2A\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }, + { .size = 21, .data = "\x2B\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }, + { .size = 21, .data = "\x2C\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }, + { .size = 21, .data = "\x2D\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }, + { .size = 21, .data = "\x2E\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }, + { .size = 21, .data = "\x2F\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }, + { .size = 21, .data = "\x30\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }, + { .size = 21, .data = "\x31\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }, + { .size = 21, .data = "\x32\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }, + { .size = 21, .data = "\x33\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }, + { .size = 21, .data = "\x34\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }, + { .size = 21, .data = "\x35\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }, + { .size = 21, .data = "\x36\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }, + { .size = 21, .data = "\x58\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }, + { .size = 21, .data = "\x59\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }, + { .size = 21, .data = "\x5C\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }, + { .size = 21, .data = "\x5D\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }, + { .size = 21, .data = "\x5E\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }, + { .size = 21, .data = "\x5F\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }, + { .size = 21, .data = "\x5A\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }, + { .size = 21, .data = "\x5B\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }, +}; + + +#endif /* _TAS5713_H */ diff --git a/sound/soc/dwc/dwc-i2s.c b/sound/soc/dwc/dwc-i2s.c index 57b789d7fbedd4..8f1aa8a3418d25 100644 --- a/sound/soc/dwc/dwc-i2s.c +++ b/sound/soc/dwc/dwc-i2s.c @@ -211,12 +211,21 @@ static void i2s_start(struct dw_i2s_dev *dev, static void i2s_stop(struct dw_i2s_dev *dev, struct snd_pcm_substream *substream) { + if (dev->is_jh7110) { + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai_link *dai_link = rtd->dai_link; + dai_link->trigger_stop = SND_SOC_TRIGGER_ORDER_LDC; + } + i2s_clear_irqs(dev, substream->stream); + + i2s_disable_irqs(dev, substream->stream, 8); +} + +static void i2s_pause(struct dw_i2s_dev *dev, + struct snd_pcm_substream *substream) +{ i2s_clear_irqs(dev, substream->stream); - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - i2s_write_reg(dev->i2s_base, ITER, 0); - else - i2s_write_reg(dev->i2s_base, IRER, 0); if (dev->use_pio || dev->is_jh7110) i2s_disable_irqs(dev, substream->stream, 8); @@ -225,14 +234,39 @@ static void i2s_stop(struct dw_i2s_dev *dev, if (!dev->active) { i2s_write_reg(dev->i2s_base, CER, 0); - i2s_write_reg(dev->i2s_base, IER, 0); + /* Keep the device enabled until the shutdown - do not clear IER */ } } +static void dw_i2s_config(struct dw_i2s_dev *dev, int stream); static int dw_i2s_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *cpu_dai) { struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(cpu_dai); + union dw_i2s_snd_dma_data *dma_data = NULL; + u32 dmacr; + + dev_dbg(dev->dev, "%s(%s)\n", __func__, substream->name); + if (!(dev->capability & DWC_I2S_RECORD) && + substream->stream == SNDRV_PCM_STREAM_CAPTURE) + return -EINVAL; + + if (!(dev->capability & DWC_I2S_PLAY) && + substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + return -EINVAL; + + dw_i2s_config(dev, substream->stream); + dmacr = i2s_read_reg(dev->i2s_base, I2S_DMACR); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + dma_data = &dev->play_dma_data; + dmacr |= DMACR_DMAEN_TX; + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + dma_data = &dev->capture_dma_data; + dmacr |= DMACR_DMAEN_RX; + } + + snd_soc_dai_set_dma_data(cpu_dai, substream, (void *)dma_data); + i2s_write_reg(dev->i2s_base, I2S_DMACR, dmacr); if (dev->is_jh7110) { struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); @@ -244,22 +278,52 @@ static int dw_i2s_startup(struct snd_pcm_substream *substream, return 0; } +static void dw_i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); + + dev_dbg(dev->dev, "%s(%s)\n", __func__, substream->name); + i2s_disable_channels(dev, substream->stream); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + i2s_write_reg(dev->i2s_base, ITER, 0); + else + i2s_write_reg(dev->i2s_base, IRER, 0); + + i2s_disable_irqs(dev, substream->stream, 8); + + if (!dev->active) { + i2s_write_reg(dev->i2s_base, CER, 0); + i2s_write_reg(dev->i2s_base, IER, 0); + } +} + static void dw_i2s_config(struct dw_i2s_dev *dev, int stream) { u32 ch_reg; struct i2s_clk_config_data *config = &dev->config; - + u32 dmacr; + u32 comp1 = i2s_read_reg(dev->i2s_base, dev->i2s_reg_comp1); + u32 fifo_depth = 1 << (1 + COMP1_FIFO_DEPTH_GLOBAL(comp1)); i2s_disable_channels(dev, stream); + dmacr = i2s_read_reg(dev->i2s_base, I2S_DMACR); + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + dmacr &= ~(DMACR_DMAEN_TXCH0 * 0xf); + else + dmacr &= ~(DMACR_DMAEN_RXCH0 * 0xf); + for (ch_reg = 0; ch_reg < (config->chan_nr / 2); ch_reg++) { if (stream == SNDRV_PCM_STREAM_PLAYBACK) { i2s_write_reg(dev->i2s_base, TCR(ch_reg), dev->xfer_resolution); i2s_write_reg(dev->i2s_base, TFCR(ch_reg), - dev->fifo_th - 1); + fifo_depth - dev->fifo_th - 1); i2s_write_reg(dev->i2s_base, TER(ch_reg), TER_TXCHEN | dev->tdm_mask << TER_TXSLOT_SHIFT); + dmacr |= (DMACR_DMAEN_TXCH0 << ch_reg); } else { i2s_write_reg(dev->i2s_base, RCR(ch_reg), dev->xfer_resolution); @@ -267,9 +331,11 @@ static void dw_i2s_config(struct dw_i2s_dev *dev, int stream) dev->fifo_th - 1); i2s_write_reg(dev->i2s_base, RER(ch_reg), RER_RXCHEN | dev->tdm_mask << RER_RXSLOT_SHIFT); + dmacr |= (DMACR_DMAEN_RXCH0 << ch_reg); } - } + + i2s_write_reg(dev->i2s_base, I2S_DMACR, dmacr); } static int dw_i2s_hw_params(struct snd_pcm_substream *substream, @@ -277,24 +343,32 @@ static int dw_i2s_hw_params(struct snd_pcm_substream *substream, { struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); struct i2s_clk_config_data *config = &dev->config; + union dw_i2s_snd_dma_data *dma_data = NULL; int ret; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dma_data = &dev->play_dma_data; + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + dma_data = &dev->capture_dma_data; + else + return -1; + switch (params_format(params)) { case SNDRV_PCM_FORMAT_S16_LE: config->data_width = 16; - dev->ccr = 0x00; + dma_data->dt.addr_width = 2; dev->xfer_resolution = 0x02; break; case SNDRV_PCM_FORMAT_S24_LE: config->data_width = 24; - dev->ccr = 0x08; + dma_data->dt.addr_width = 4; dev->xfer_resolution = 0x04; break; case SNDRV_PCM_FORMAT_S32_LE: config->data_width = 32; - dev->ccr = 0x10; + dma_data->dt.addr_width = 4; dev->xfer_resolution = 0x05; break; @@ -315,17 +389,37 @@ static int dw_i2s_hw_params(struct snd_pcm_substream *substream, case TWO_CHANNEL_SUPPORT: break; default: - dev_err(dev->dev, "channel not supported\n"); + dev_err(dev->dev, "channel count %d not supported\n", config->chan_nr); return -EINVAL; } dw_i2s_config(dev, substream->stream); - i2s_write_reg(dev->i2s_base, CCR, dev->ccr); - config->sample_rate = params_rate(params); if (dev->capability & DW_I2S_MASTER) { + u32 frame_length = config->data_width * 2; + + if (dev->bclk_ratio) + frame_length = dev->bclk_ratio; + + switch (frame_length) { + case 32: + dev->ccr = 0x00; + break; + + case 48: + dev->ccr = 0x08; + break; + + case 64: + dev->ccr = 0x10; + break; + + default: + return -EINVAL; + } + if (dev->i2s_clk_cfg) { ret = dev->i2s_clk_cfg(config); if (ret < 0) { @@ -333,8 +427,7 @@ static int dw_i2s_hw_params(struct snd_pcm_substream *substream, return ret; } } else { - u32 bitclk = config->sample_rate * - config->data_width * 2; + u32 bitclk = config->sample_rate * frame_length; ret = clk_set_rate(dev->clk, bitclk); if (ret) { @@ -343,6 +436,8 @@ static int dw_i2s_hw_params(struct snd_pcm_substream *substream, return ret; } } + + i2s_write_reg(dev->i2s_base, CCR, dev->ccr); } return 0; } @@ -374,9 +469,12 @@ static int dw_i2s_trigger(struct snd_pcm_substream *substream, i2s_start(dev, substream); break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + dev->active--; + i2s_pause(dev, substream); + break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: - case SNDRV_PCM_TRIGGER_PAUSE_PUSH: dev->active--; i2s_stop(dev, substream); break; @@ -460,6 +558,18 @@ static int dw_i2s_set_tdm_slot(struct snd_soc_dai *cpu_dai, unsigned int tx_mask return 0; } +static int dw_i2s_set_bclk_ratio(struct snd_soc_dai *cpu_dai, + unsigned int ratio) +{ + struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(cpu_dai); + + dev_dbg(dev->dev, "%s(%d)\n", __func__, ratio); + + dev->bclk_ratio = ratio; + + return 0; +} + static int dw_i2s_dai_probe(struct snd_soc_dai *dai) { struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); @@ -471,11 +581,13 @@ static int dw_i2s_dai_probe(struct snd_soc_dai *dai) static const struct snd_soc_dai_ops dw_i2s_dai_ops = { .probe = dw_i2s_dai_probe, .startup = dw_i2s_startup, + .shutdown = dw_i2s_shutdown, .hw_params = dw_i2s_hw_params, .prepare = dw_i2s_prepare, .trigger = dw_i2s_trigger, .set_fmt = dw_i2s_set_fmt, .set_tdm_slot = dw_i2s_set_tdm_slot, + .set_bclk_ratio = dw_i2s_set_bclk_ratio, }; #ifdef CONFIG_PM @@ -606,7 +718,7 @@ static int dw_configure_dai(struct dw_i2s_dev *dev, idx = 1; dw_i2s_dai->playback.channels_min = MIN_CHANNEL_NUM; dw_i2s_dai->playback.channels_max = - 1 << (COMP1_TX_CHANNELS(comp1) + 1); + 2 * (COMP1_TX_CHANNELS(comp1) + 1); dw_i2s_dai->playback.formats = formats[idx]; dw_i2s_dai->playback.rates = rates; } @@ -620,7 +732,7 @@ static int dw_configure_dai(struct dw_i2s_dev *dev, idx = 1; dw_i2s_dai->capture.channels_min = MIN_CHANNEL_NUM; dw_i2s_dai->capture.channels_max = - 1 << (COMP1_RX_CHANNELS(comp1) + 1); + 2 * (COMP1_RX_CHANNELS(comp1) + 1); dw_i2s_dai->capture.formats = formats[idx]; dw_i2s_dai->capture.rates = rates; } @@ -681,8 +793,8 @@ static int dw_configure_dai_by_pd(struct dw_i2s_dev *dev, dev->capture_dma_data.pd.data = pdata->capture_dma_data; dev->play_dma_data.pd.addr = res->start + I2S_TXDMA; dev->capture_dma_data.pd.addr = res->start + I2S_RXDMA; - dev->play_dma_data.pd.max_burst = 16; - dev->capture_dma_data.pd.max_burst = 16; + dev->play_dma_data.pd.max_burst = dev->fifo_th; + dev->capture_dma_data.pd.max_burst = dev->fifo_th; dev->play_dma_data.pd.addr_width = bus_widths[idx]; dev->capture_dma_data.pd.addr_width = bus_widths[idx]; dev->play_dma_data.pd.filter = pdata->filter; @@ -702,7 +814,7 @@ static int dw_configure_dai_by_dt(struct dw_i2s_dev *dev, u32 idx2; int ret; - ret = dw_configure_dai(dev, dw_i2s_dai, SNDRV_PCM_RATE_8000_192000); + ret = dw_configure_dai(dev, dw_i2s_dai, SNDRV_PCM_RATE_8000_384000); if (ret < 0) return ret; @@ -713,7 +825,10 @@ static int dw_configure_dai_by_dt(struct dw_i2s_dev *dev, dev->play_dma_data.dt.addr = res->start + I2S_TXDMA; dev->play_dma_data.dt.fifo_size = fifo_depth * (fifo_width[idx2]) >> 8; - dev->play_dma_data.dt.maxburst = 16; + if (dev->max_dma_burst) + dev->play_dma_data.dt.maxburst = dev->max_dma_burst; + else + dev->play_dma_data.dt.maxburst = fifo_depth / 2; } if (COMP1_RX_ENABLED(comp1)) { idx2 = COMP2_RX_WORDSIZE_0(comp2); @@ -722,9 +837,14 @@ static int dw_configure_dai_by_dt(struct dw_i2s_dev *dev, dev->capture_dma_data.dt.addr = res->start + I2S_RXDMA; dev->capture_dma_data.dt.fifo_size = fifo_depth * (fifo_width[idx2] >> 8); - dev->capture_dma_data.dt.maxburst = 16; + if (dev->max_dma_burst) + dev->capture_dma_data.dt.maxburst = dev->max_dma_burst; + else + dev->capture_dma_data.dt.maxburst = fifo_depth / 2; } + if (dev->max_dma_burst) + dev->fifo_th = min(dev->max_dma_burst, dev->fifo_th); return 0; } @@ -968,6 +1088,8 @@ static int dw_i2s_probe(struct platform_device *pdev) } } + of_property_read_u32(pdev->dev.of_node, "dma-maxburst", &dev->max_dma_burst); + dev->bclk_ratio = 0; dev->i2s_reg_comp1 = I2S_COMP_PARAM_1; dev->i2s_reg_comp2 = I2S_COMP_PARAM_2; if (pdata) { diff --git a/sound/soc/dwc/local.h b/sound/soc/dwc/local.h index dce88c9ad5f333..afff89db5dbb1b 100644 --- a/sound/soc/dwc/local.h +++ b/sound/soc/dwc/local.h @@ -63,6 +63,17 @@ #define TER_TXSLOT_SHIFT 8 #define TER_TXCHEN BIT(0) +#define DMACR_DMAEN_TX BIT(17) +#define DMACR_DMAEN_RX BIT(16) +#define DMACR_DMAEN_TXCH3 BIT(11) +#define DMACR_DMAEN_TXCH2 BIT(10) +#define DMACR_DMAEN_TXCH1 BIT(9) +#define DMACR_DMAEN_TXCH0 BIT(8) +#define DMACR_DMAEN_RXCH3 BIT(3) +#define DMACR_DMAEN_RXCH2 BIT(2) +#define DMACR_DMAEN_RXCH1 BIT(1) +#define DMACR_DMAEN_RXCH0 BIT(0) + /* I2SCOMPRegisters */ #define I2S_COMP_PARAM_2 0x01F0 #define I2S_COMP_PARAM_1 0x01F4 @@ -117,10 +128,12 @@ struct dw_i2s_dev { unsigned int quirks; unsigned int i2s_reg_comp1; unsigned int i2s_reg_comp2; + unsigned int bclk_ratio; struct device *dev; u32 ccr; u32 xfer_resolution; u32 fifo_th; + u32 max_dma_burst; u32 l_reg; u32 r_reg; bool is_jh7110; /* Flag for StarFive JH7110 SoC */ diff --git a/sound/soc/raspberrypi/Kconfig b/sound/soc/raspberrypi/Kconfig new file mode 100644 index 00000000000000..389bb82e3651e9 --- /dev/null +++ b/sound/soc/raspberrypi/Kconfig @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0-only +config SND_RP1_AUDIO_OUT + tristate "PWM Audio Out from RP1" + select SND_SOC_GENERIC_DMAENGINE_PCM + select SND_SOC_SPDIF + help + Say Y or M if you want to add support for PWM digital + audio output from a Raspberry Pi 5, 500 or CM5. + + Output is from RP1 GPIOs pins 12 and 13 only, and additional + components will be needed. It may be useful when HDMI, I2S + or USB audio devices are unavailable, or for compatibility. diff --git a/sound/soc/raspberrypi/Makefile b/sound/soc/raspberrypi/Makefile new file mode 100644 index 00000000000000..80ff460c785a3c --- /dev/null +++ b/sound/soc/raspberrypi/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_SND_RP1_AUDIO_OUT) += rp1_aout.o diff --git a/sound/soc/raspberrypi/rp1_aout.c b/sound/soc/raspberrypi/rp1_aout.c new file mode 100644 index 00000000000000..ffcc7b815d5edd --- /dev/null +++ b/sound/soc/raspberrypi/rp1_aout.c @@ -0,0 +1,372 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2025 Raspberry Pi Ltd. + * + * rp1_aout.c -- Driver for Raspberry Pi RP1 Audio Out block. + * This is a modified 2-channel PWM with hardware 40 times oversampling, + * 2nd order noise shaping and dither. Only output (playback) is supported. + * + * For ASOC, this is implemented as a "DAI" and will need to be linked to a + * dummy codec such as "linux,spdif-dit" and card such as "simple-audio-card". + * + * Driver/file structure derived in part from "soc/starfive/jh7110_pwmdac.c" + * and other example drivers. + */ + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/reset.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <sound/dmaengine_pcm.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#define AUDIO_OUT_CTRL 0x0000 +#define AUDIO_OUT_CTRL_PERIPH_EN BIT(31) +#define AUDIO_OUT_CTRL_CIC_RATE GENMASK(11, 8) +#define AUDIO_OUT_CTRL_CHANNEL_SWAP BIT(2) +#define AUDIO_OUT_CTRL_RIGHT_CH_ENABLE BIT(1) +#define AUDIO_OUT_CTRL_LEFT_CH_ENABLE BIT(0) + +#define AUDIO_OUT_SDMCTL_LEFT 0x0004 +#define AUDIO_OUT_SDMCTL_RIGHT 0x0008 +#define AUDIO_OUT_SDMCTL_LR_BIAS GENMASK(31, 16) +#define AUDIO_OUT_SDMCTL_LR_BYPASS BIT(7) +#define AUDIO_OUT_SDMCTL_LR_SDM_ORDER BIT(6) +#define AUDIO_OUT_SDMCTL_LR_CLAMP_EN BIT(5) +#define AUDIO_OUT_SDMCTL_LR_DITHER_EN BIT(4) +#define AUDIO_OUT_SDMCTL_LR_OUTPUT_BITWIDTH GENMASK(3, 0) + +#define AUDIO_OUT_QCLAMP_LEFT 0x000c +#define AUDIO_OUT_QCLAMP_RIGHT 0x0010 +#define AUDIO_OUT_QCLAMP_LR_MAX GENMASK(31, 16) +#define AUDIO_OUT_QCLAMP_LR_MIN GENMASK(15, 0) + +#define AUDIO_OUT_MUTE_CTRL_LEFT 0x0014 +#define AUDIO_OUT_MUTE_CTRL_RIGHT 0x0018 +#define AUDIO_OUT_MUTE_CTRL_LR_BYPASS BIT(31) +#define AUDIO_OUT_MUTE_CTRL_LR_MUTE_PERIOD GENMASK(23, 16) +#define AUDIO_OUT_MUTE_CTRL_LR_STEP_SIZE GENMASK(15, 8) +#define AUDIO_OUT_MUTE_CTRL_LR_INIT_UNMUTE BIT(5) +#define AUDIO_OUT_MUTE_CTRL_LR_INIT_MUTE BIT(4) +#define AUDIO_OUT_MUTE_CTRL_LR_MUTE_FSM GENMASK(3, 0) + +#define AUDIO_OUT_PWMCTL_LEFT 0x001c +#define AUDIO_OUT_PWMRANGE_LEFT 0x0020 +#define AUDIO_OUT_PWMCTL_RIGHT 0x0028 +#define AUDIO_OUT_PWMRANGE_RIGHT 0x002c + +#define AUDIO_OUT_FIFO_CONTROL 0x0034 +#define AUDIO_OUT_FIFO_CONTROL_DMA_DREQ_EN BIT(31) +#define AUDIO_OUT_FIFO_CONTROL_FLUSH_DONE BIT(25) +#define AUDIO_OUT_FIFO_CONTROL_FIFO_FLUSH BIT(24) +#define AUDIO_OUT_FIFO_CONTROL_DWELL_TIME GENMASK(20, 16) +#define AUDIO_OUT_FIFO_CONTROL_THRESHOLD GENMASK(5, 0) + +#define AUDIO_OUT_SAMPLE_FIFO 0x0038 + +struct rp1_aout { + void __iomem *regs; + phys_addr_t physaddr; + struct clk *clk; + struct device *dev; + struct snd_dmaengine_dai_dma_data play_dma_data; + bool initted; + bool swap_lr; +}; + +static inline void aout_reg_wr(struct rp1_aout *ao, unsigned int offset, u32 val) +{ + void __iomem *addr = ao->regs + offset; + + writel(val, addr); +} + +static inline u32 aout_reg_rd(struct rp1_aout *ao, unsigned int offset) +{ + void __iomem *addr = ao->regs + offset; + + return readl(addr); +} + +static void audio_init(struct rp1_aout *aout) +{ + u32 val; + + /* + * The hardware was tuned to play at 48 kHz with 40 times oversampling and + * 40-level two-sided PWM, for a clock rate of 48000 * 40 * 80 = 153.6 MHz. + * Changing these settings is not recommended. At those rates, the filter + * leaves ~2.2 dB headroom, so Qclamp will clip just slightly below FSD. + */ + + /* Clamp to +/- (32767 * 40 / 64) before quantization */ + val = FIELD_PREP_CONST(AUDIO_OUT_QCLAMP_LR_MAX, 20479) | + FIELD_PREP_CONST(AUDIO_OUT_QCLAMP_LR_MIN, (u16)(-20479)); + aout_reg_wr(aout, AUDIO_OUT_QCLAMP_LEFT, val); + aout_reg_wr(aout, AUDIO_OUT_QCLAMP_RIGHT, val); + aout_reg_wr(aout, AUDIO_OUT_PWMCTL_LEFT, 0); + aout_reg_wr(aout, AUDIO_OUT_PWMCTL_RIGHT, 0); + + /* Range = 39 */ + aout_reg_wr(aout, AUDIO_OUT_PWMRANGE_LEFT, 0x27); + aout_reg_wr(aout, AUDIO_OUT_PWMRANGE_RIGHT, 0x27); + + /* bias = 20 (half FSD). Quantize to 5+1 bits */ + val = FIELD_PREP_CONST(AUDIO_OUT_SDMCTL_LR_BIAS, 0x14) | + AUDIO_OUT_SDMCTL_LR_CLAMP_EN | + AUDIO_OUT_SDMCTL_LR_DITHER_EN | + FIELD_PREP_CONST(AUDIO_OUT_SDMCTL_LR_OUTPUT_BITWIDTH, 5); + aout_reg_wr(aout, AUDIO_OUT_SDMCTL_LEFT, val); + aout_reg_wr(aout, AUDIO_OUT_SDMCTL_RIGHT, val); + + /* ~300ms ramp = 12k*40 samples to FSD/2 => step size 1, interval 13 */ + val = FIELD_PREP_CONST(AUDIO_OUT_MUTE_CTRL_LR_STEP_SIZE, 1) | + FIELD_PREP_CONST(AUDIO_OUT_MUTE_CTRL_LR_MUTE_PERIOD, 13); + aout_reg_wr(aout, AUDIO_OUT_MUTE_CTRL_LEFT, val); + aout_reg_wr(aout, AUDIO_OUT_MUTE_CTRL_RIGHT, val); + + /* Configure DMA flow control with threshold at half FIFO depth */ + val = FIELD_PREP_CONST(AUDIO_OUT_FIFO_CONTROL_DWELL_TIME, 2) | + FIELD_PREP_CONST(AUDIO_OUT_FIFO_CONTROL_THRESHOLD, 0x10) | + AUDIO_OUT_FIFO_CONTROL_DMA_DREQ_EN; + aout_reg_wr(aout, AUDIO_OUT_FIFO_CONTROL, val); +} + +static void audio_startup(struct rp1_aout *aout) +{ + u32 val; + + /* CIC rate 10, for an overall upsampling ratio of 40 */ + val = FIELD_PREP_CONST(AUDIO_OUT_CTRL_CIC_RATE, 0xa) | + AUDIO_OUT_CTRL_LEFT_CH_ENABLE | AUDIO_OUT_CTRL_RIGHT_CH_ENABLE; + if (aout->swap_lr) + val |= AUDIO_OUT_CTRL_CHANNEL_SWAP; + aout_reg_wr(aout, AUDIO_OUT_CTRL, val); + aout_reg_rd(aout, AUDIO_OUT_CTRL); /* synchronization delay */ + + /* Press the "go" button */ + val |= AUDIO_OUT_CTRL_PERIPH_EN; + aout_reg_wr(aout, AUDIO_OUT_CTRL, val); + aout_reg_rd(aout, AUDIO_OUT_CTRL); /* FIFO reset release delay */ + + /* Poke zeroes in to avoid undefined values on underrun */ + aout_reg_wr(aout, AUDIO_OUT_SAMPLE_FIFO, 0); +} + +static void audio_muting(struct rp1_aout *aout, u32 flag) +{ + u32 val = FIELD_PREP_CONST(AUDIO_OUT_MUTE_CTRL_LR_STEP_SIZE, 1) | + FIELD_PREP_CONST(AUDIO_OUT_MUTE_CTRL_LR_MUTE_PERIOD, 13); + aout_reg_wr(aout, AUDIO_OUT_MUTE_CTRL_LEFT, val); + aout_reg_wr(aout, AUDIO_OUT_MUTE_CTRL_RIGHT, val); + + val |= flag; + aout_reg_wr(aout, AUDIO_OUT_MUTE_CTRL_LEFT, val); + aout_reg_wr(aout, AUDIO_OUT_MUTE_CTRL_RIGHT, val); + aout_reg_rd(aout, AUDIO_OUT_MUTE_CTRL_RIGHT); /* synchronization delay */ +} + +static void audio_mute_sync(struct rp1_aout *aout) +{ + static const u32 mask = 0x1 | 0x4; /* transitional states */ + unsigned int count; + + for (count = 0; count < 500; count++) { + if ((aout_reg_rd(aout, AUDIO_OUT_MUTE_CTRL_LEFT) & mask) == 0) + break; + usleep_range(1000, 5000); + } + for (; count < 500; count++) { + if ((aout_reg_rd(aout, AUDIO_OUT_MUTE_CTRL_RIGHT) & mask) == 0) + break; + usleep_range(1000, 5000); + } +} + +/* Device DAI interface */ + +static int rp1_aout_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct rp1_aout *aout = dev_get_drvdata(dai->dev); + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai_link *dai_link = rtd->dai_link; + + dai_link->trigger_stop = SND_SOC_TRIGGER_ORDER_LDC; + + if (!aout->initted) { + dev_info(dai->dev, "RP1 Audio Out start\n"); + audio_init(aout); + audio_startup(aout); + audio_muting(aout, AUDIO_OUT_MUTE_CTRL_LR_INIT_UNMUTE); + aout->initted = true; + } + + return 0; +} + +static int rp1_aout_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct rp1_aout *aout = dev_get_drvdata(dai->dev); + + /* We only support this one configuration */ + if (params_rate(params) != 48000 || params_channels(params) != 2 || + params_format(params) != SNDRV_PCM_FORMAT_S16_LE) { + dev_err(dai->dev, "Invalid HW params\n"); + return -EINVAL; + } + + audio_mute_sync(aout); + + return 0; +} + +static int rp1_aout_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct rp1_aout *aout = snd_soc_dai_get_drvdata(dai); + + if (cmd == SNDRV_PCM_TRIGGER_STOP || + cmd == SNDRV_PCM_TRIGGER_SUSPEND || + cmd == SNDRV_PCM_TRIGGER_PAUSE_PUSH) { + /* Push a zero sample (assuming DMA has stopped already) */ + aout_reg_wr(aout, AUDIO_OUT_SAMPLE_FIFO, 0); + } + + return 0; +} + +static int rp1_aout_dai_probe(struct snd_soc_dai *dai) +{ + struct rp1_aout *aout = dev_get_drvdata(dai->dev); + + snd_soc_dai_init_dma_data(dai, &aout->play_dma_data, NULL); + snd_soc_dai_set_drvdata(dai, aout); + + return 0; +} + +static const struct snd_soc_dai_ops rp1_aout_dai_ops = { + .probe = rp1_aout_dai_probe, + .startup = rp1_aout_startup, + .hw_params = rp1_aout_hw_params, + .trigger = rp1_aout_trigger, +}; + +static const struct snd_soc_component_driver rp1_aout_component = { + .name = "rp1-aout", +}; + +static struct snd_soc_dai_driver rp1_aout_dai = { + .name = "rp1-aout", + .id = 0, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &rp1_aout_dai_ops, +}; + +static int rp1_aout_platform_probe(struct platform_device *pdev) +{ + int ret; + struct rp1_aout *aout; + struct resource *ioresource; + + dev_info(&pdev->dev, "rp1_aout_platform_probe"); + aout = devm_kzalloc(&pdev->dev, sizeof(*aout), GFP_KERNEL); + if (!aout) + return -ENOMEM; + + aout->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(aout->clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(aout->clk), + "could not get clk\n"); + + aout->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &ioresource); + if (IS_ERR(aout->regs)) + return dev_err_probe(&pdev->dev, PTR_ERR(aout->regs), + "could not map registers\n"); + aout->physaddr = ioresource->start; + aout->swap_lr = of_property_read_bool(pdev->dev.of_node, "swap_lr"); + + /* Initialize playback DMA parameters */ + aout->play_dma_data.addr = aout->physaddr + AUDIO_OUT_SAMPLE_FIFO; + aout->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + aout->play_dma_data.fifo_size = 16; /* actually 32 but the threshold is 16(?) */ + aout->play_dma_data.maxburst = 4; + + clk_prepare_enable(aout->clk); + aout->dev = &pdev->dev; + platform_set_drvdata(pdev, aout); + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) + return dev_err_probe(&pdev->dev, ret, "failed to register pcm\n"); + + /* Finally, register the component so simple-audio-card can detect it */ + ret = devm_snd_soc_register_component(&pdev->dev, + &rp1_aout_component, + &rp1_aout_dai, 1); + if (ret) + return dev_err_probe(&pdev->dev, ret, "failed to register dai\n"); + + return 0; +} + +static void rp1_aout_platform_remove(struct platform_device *pdev) +{ + struct rp1_aout *aout = platform_get_drvdata(pdev); + + if (aout) { + /* + * We leave the PWM carrier/bias running between playbacks, + * but mute it just before shutting down, to avoid a click + * (devm should clear up everything else). + */ + if (aout->initted) { + audio_mute_sync(aout); + audio_muting(aout, + AUDIO_OUT_MUTE_CTRL_LR_INIT_MUTE); + audio_mute_sync(aout); + aout->initted = false; + } + clk_disable_unprepare(aout->clk); + } +} + +static const struct of_device_id rp1_aout_of_match[] = { + { .compatible = "raspberrypi,rp1-audio-out", }, + { /* sentinel */ }, +}; + +MODULE_DEVICE_TABLE(of, rp1_aout_of_match); + +static struct platform_driver rp1_audio_out_driver = { + .probe = rp1_aout_platform_probe, + .remove = rp1_aout_platform_remove, + .shutdown = rp1_aout_platform_remove, + .driver = { + .name = "rp1-audio-out", + .of_match_table = rp1_aout_of_match, + }, +}; + +module_platform_driver(rp1_audio_out_driver); + +MODULE_DESCRIPTION("RP1 Audio out"); +MODULE_AUTHOR("Nick Hollinghurst <nick.hollinghurst@raspberrypi.com>"); +MODULE_AUTHOR("Jonathan Bell <jonathan@raspberrypi.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 20248a29d1674a..0ffe9ae62b02f0 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1434,7 +1434,15 @@ int snd_soc_runtime_set_dai_fmt(struct snd_soc_pcm_runtime *rtd, return 0; for_each_rtd_codec_dais(rtd, i, codec_dai) { - ret = snd_soc_dai_set_fmt(codec_dai, dai_fmt); + unsigned int codec_dai_fmt = dai_fmt; + + // there can only be one master when using multiple codecs + if (i && (codec_dai_fmt & SND_SOC_DAIFMT_MASTER_MASK)) { + codec_dai_fmt &= ~SND_SOC_DAIFMT_MASTER_MASK; + codec_dai_fmt |= SND_SOC_DAIFMT_CBS_CFS; + } + + ret = snd_soc_dai_set_fmt(codec_dai, codec_dai_fmt); if (ret != 0 && ret != -ENOTSUPP) return ret; } diff --git a/sound/usb/card.c b/sound/usb/card.c index 9c411b82a218dc..ea6e476e6edab7 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -869,8 +869,14 @@ static int usb_audio_probe(struct usb_interface *intf, if (ignore_ctl_error) chip->quirk_flags |= QUIRK_FLAG_IGNORE_CTL_ERROR; - if (chip->quirk_flags & QUIRK_FLAG_DISABLE_AUTOSUSPEND) + if (chip->quirk_flags & QUIRK_FLAG_DISABLE_AUTOSUSPEND) { + /* + * Grab the interface, because on a webcam uvcvideo may race + * with snd-usb-audio during probe and re-enable autosuspend. + */ + usb_autopm_get_interface(intf); usb_disable_autosuspend(interface_to_usbdev(intf)); + } /* * For devices with more than one control interface, we assume the diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index a97efb7b131ea2..3babecaa09b06a 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2353,6 +2353,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_ALIGN_TRANSFER), DEVICE_FLG(0x534d, 0x2109, /* MacroSilicon MS2109 */ QUIRK_FLAG_ALIGN_TRANSFER), + DEVICE_FLG(0x09da, 0x2695, /* A4Tech FHD 1080p webcam */ + QUIRK_FLAG_DISABLE_AUTOSUSPEND | QUIRK_FLAG_GET_SAMPLE_RATE), /* Vendor matches */ VENDOR_FLG(0x045e, /* MS Lifecam */