Merge tag 'v5.15.57' into rpi-5.15.y
authorDom Cobley <popcornmix@gmail.com>
Tue, 23 Aug 2022 13:07:27 +0000 (14:07 +0100)
committerDom Cobley <popcornmix@gmail.com>
Tue, 23 Aug 2022 13:07:27 +0000 (14:07 +0100)
This is the 5.15.57 stable release

916 files changed:
.github/ISSUE_TEMPLATE/bug_report.yml [new file with mode: 0644]
.github/ISSUE_TEMPLATE/config.yml [new file with mode: 0644]
Documentation/admin-guide/media/bcm2835-isp.rst [new file with mode: 0644]
Documentation/devicetree/bindings/clock/raspberrypi,firmware-clocks.yaml [new file with mode: 0644]
Documentation/devicetree/bindings/display/brcm,bcm2711-hdmi.yaml
Documentation/devicetree/bindings/display/brcm,bcm2835-dsi0.yaml
Documentation/devicetree/bindings/display/brcm,bcm2835-hdmi.yaml
Documentation/devicetree/bindings/display/brcm,bcm2835-vc4.yaml
Documentation/devicetree/bindings/display/panel/panel-mipi-dbi-spi.yaml [new file with mode: 0644]
Documentation/devicetree/bindings/display/panel/panel-simple.yaml
Documentation/devicetree/bindings/hwmon/microchip,emc2305.yaml [new file with mode: 0644]
Documentation/devicetree/bindings/media/bcm2835-unicam.txt [new file with mode: 0644]
Documentation/devicetree/bindings/media/i2c/ad5398.txt [new file with mode: 0644]
Documentation/devicetree/bindings/media/i2c/arducam,64mp.yaml [new file with mode: 0644]
Documentation/devicetree/bindings/media/i2c/arducam-pivariety.yaml [new file with mode: 0644]
Documentation/devicetree/bindings/media/i2c/imx219.txt [new file with mode: 0644]
Documentation/devicetree/bindings/media/i2c/imx258.yaml
Documentation/devicetree/bindings/media/i2c/imx290.txt
Documentation/devicetree/bindings/media/i2c/imx378.yaml [new file with mode: 0644]
Documentation/devicetree/bindings/media/i2c/imx477.yaml [new file with mode: 0644]
Documentation/devicetree/bindings/media/i2c/imx519.yaml [new file with mode: 0644]
Documentation/devicetree/bindings/media/i2c/irs1125.txt [new file with mode: 0644]
Documentation/devicetree/bindings/media/i2c/sony,imx296.yaml [new file with mode: 0644]
Documentation/devicetree/bindings/media/rpivid_hevc.yaml [new file with mode: 0644]
Documentation/devicetree/bindings/misc/brcm,bcm2835-smi-dev.txt [new file with mode: 0644]
Documentation/devicetree/bindings/misc/brcm,bcm2835-smi.txt [new file with mode: 0644]
Documentation/devicetree/bindings/net/microchip,lan78xx.txt
Documentation/devicetree/bindings/pci/brcmstb-pcie.txt [new file with mode: 0644]
Documentation/devicetree/bindings/power/reset/gpio-poweroff.txt
Documentation/devicetree/bindings/serial/pl011.yaml
Documentation/devicetree/bindings/spi/spi-gpio.yaml
Documentation/devicetree/bindings/vendor-prefixes.txt [new file with mode: 0644]
Documentation/devicetree/bindings/vendor-prefixes.yaml
Documentation/devicetree/configfs-overlays.txt [new file with mode: 0644]
Documentation/hwmon/emc2305.rst [new file with mode: 0644]
Documentation/userspace-api/media/drivers/index.rst
Documentation/userspace-api/media/mediactl/media-controller-model.rst
Documentation/userspace-api/media/mediactl/media-types.rst
Documentation/userspace-api/media/v4l/ext-ctrls-codec.rst
Documentation/userspace-api/media/v4l/ext-ctrls-image-source.rst
Documentation/userspace-api/media/v4l/meta-formats.rst
Documentation/userspace-api/media/v4l/pixfmt-compressed.rst
Documentation/userspace-api/media/v4l/pixfmt-meta-bcm2835-isp-stats.rst [new file with mode: 0644]
Documentation/userspace-api/media/v4l/pixfmt-meta-sensor-data.rst [new file with mode: 0644]
Documentation/userspace-api/media/v4l/pixfmt-nv12-col128.rst [new file with mode: 0644]
Documentation/userspace-api/media/v4l/pixfmt-y12p.rst [new file with mode: 0644]
Documentation/userspace-api/media/v4l/pixfmt-y14p.rst [new file with mode: 0644]
Documentation/userspace-api/media/v4l/pixfmt-yuv-planar.rst
Documentation/userspace-api/media/v4l/subdev-formats.rst
Documentation/userspace-api/media/v4l/vidioc-queryctrl.rst
Documentation/userspace-api/media/v4l/yuv-formats.rst
MAINTAINERS
arch/arm/boot/dts/Makefile
arch/arm/boot/dts/bcm2708-rpi-b-plus.dts [new file with mode: 0644]
arch/arm/boot/dts/bcm2708-rpi-b-rev1.dts [new file with mode: 0644]
arch/arm/boot/dts/bcm2708-rpi-b.dts [new file with mode: 0644]
arch/arm/boot/dts/bcm2708-rpi-bt.dtsi [new file with mode: 0644]
arch/arm/boot/dts/bcm2708-rpi-cm.dts [new file with mode: 0644]
arch/arm/boot/dts/bcm2708-rpi-cm.dtsi [new file with mode: 0644]
arch/arm/boot/dts/bcm2708-rpi-zero-w.dts [new file with mode: 0644]
arch/arm/boot/dts/bcm2708-rpi-zero.dts [new file with mode: 0644]
arch/arm/boot/dts/bcm2708-rpi.dtsi [new file with mode: 0644]
arch/arm/boot/dts/bcm2708.dtsi [new file with mode: 0644]
arch/arm/boot/dts/bcm2709-rpi-2-b.dts [new file with mode: 0644]
arch/arm/boot/dts/bcm2709-rpi-cm2.dts [new file with mode: 0644]
arch/arm/boot/dts/bcm2709-rpi.dtsi [new file with mode: 0644]
arch/arm/boot/dts/bcm2709.dtsi [new file with mode: 0644]
arch/arm/boot/dts/bcm270x-rpi.dtsi [new file with mode: 0644]
arch/arm/boot/dts/bcm270x.dtsi [new file with mode: 0644]
arch/arm/boot/dts/bcm2710-rpi-2-b.dts [new file with mode: 0644]
arch/arm/boot/dts/bcm2710-rpi-3-b-plus.dts [new file with mode: 0644]
arch/arm/boot/dts/bcm2710-rpi-3-b.dts [new file with mode: 0644]
arch/arm/boot/dts/bcm2710-rpi-cm3.dts [new file with mode: 0644]
arch/arm/boot/dts/bcm2710-rpi-zero-2-w.dts [new file with mode: 0644]
arch/arm/boot/dts/bcm2710-rpi-zero-2.dts [new file with mode: 0644]
arch/arm/boot/dts/bcm2710.dtsi [new file with mode: 0644]
arch/arm/boot/dts/bcm2711-rpi-4-b.dts
arch/arm/boot/dts/bcm2711-rpi-400.dts
arch/arm/boot/dts/bcm2711-rpi-cm4.dts [new file with mode: 0644]
arch/arm/boot/dts/bcm2711-rpi-cm4s.dts [new file with mode: 0644]
arch/arm/boot/dts/bcm2711-rpi-ds.dtsi [new file with mode: 0644]
arch/arm/boot/dts/bcm2711.dtsi
arch/arm/boot/dts/bcm271x-rpi-bt.dtsi [new file with mode: 0644]
arch/arm/boot/dts/bcm2835-common.dtsi
arch/arm/boot/dts/bcm2835-rpi-a-plus.dts
arch/arm/boot/dts/bcm2835-rpi-a.dts
arch/arm/boot/dts/bcm2835-rpi-b-plus.dts
arch/arm/boot/dts/bcm2835-rpi-b-rev2.dts
arch/arm/boot/dts/bcm2835-rpi-b.dts
arch/arm/boot/dts/bcm2835-rpi-cm1-io1.dts
arch/arm/boot/dts/bcm2835-rpi-zero-w.dts
arch/arm/boot/dts/bcm2835-rpi-zero.dts
arch/arm/boot/dts/bcm2835-rpi.dtsi
arch/arm/boot/dts/bcm2835.dtsi
arch/arm/boot/dts/bcm2836-rpi-2-b.dts
arch/arm/boot/dts/bcm2837-rpi-3-a-plus.dts
arch/arm/boot/dts/bcm2837-rpi-3-b-plus.dts
arch/arm/boot/dts/bcm2837-rpi-3-b.dts
arch/arm/boot/dts/bcm2837-rpi-cm3-io3.dts
arch/arm/boot/dts/bcm283x-rpi-csi0-2lane.dtsi [new file with mode: 0644]
arch/arm/boot/dts/bcm283x-rpi-csi1-2lane.dtsi [new file with mode: 0644]
arch/arm/boot/dts/bcm283x-rpi-csi1-4lane.dtsi [new file with mode: 0644]
arch/arm/boot/dts/bcm283x-rpi-i2c0mux_0_28.dtsi [new file with mode: 0644]
arch/arm/boot/dts/bcm283x-rpi-i2c0mux_0_44.dtsi [new file with mode: 0644]
arch/arm/boot/dts/bcm283x.dtsi
arch/arm/boot/dts/overlays/Makefile [new file with mode: 0644]
arch/arm/boot/dts/overlays/README [new file with mode: 0644]
arch/arm/boot/dts/overlays/act-led-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/adafruit-st7735r-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/adafruit18-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/adau1977-adc-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/adau7002-simple-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/ads1015-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/ads1115-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/ads7846-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/adv7282m-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/adv728x-m-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/akkordion-iqdacplus-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/allo-boss-dac-pcm512x-audio-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/allo-boss2-dac-audio-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/allo-digione-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/allo-katana-dac-audio-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/allo-piano-dac-pcm512x-audio-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/allo-piano-dac-plus-pcm512x-audio-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/anyspi-overlay.dts [new file with mode: 0755]
arch/arm/boot/dts/overlays/apds9960-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/applepi-dac-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/arducam-64mp-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/arducam-pivariety-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/at86rf233-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/audioinjector-addons-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/audioinjector-bare-i2s-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/audioinjector-isolated-soundcard-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/audioinjector-ultra-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/audioinjector-wm8731-audio-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/audiosense-pi-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/audremap-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/balena-fin-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/camera-mux-2port-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/camera-mux-4port-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/cap1106-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/chipdip-dac-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/cma-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/cutiepi-panel-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/dacberry400-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/dht11-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/dionaudio-kiwi-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/dionaudio-loco-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/dionaudio-loco-v2-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/disable-bt-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/disable-wifi-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/dpi18-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/dpi18cpadhi-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/dpi24-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/draws-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/dwc-otg-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/dwc2-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/edt-ft5406-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/edt-ft5406.dtsi [new file with mode: 0644]
arch/arm/boot/dts/overlays/enc28j60-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/enc28j60-spi2-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/exc3000-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/fbtft-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/fe-pi-audio-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/fsm-demo-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/gc9a01-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/ghost-amp-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/goodix-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/googlevoicehat-soundcard-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/gpio-fan-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/gpio-hog-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/gpio-ir-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/gpio-ir-tx-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/gpio-key-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/gpio-led-overlay.dts [new file with mode: 0755]
arch/arm/boot/dts/overlays/gpio-no-bank0-irq-overlay.dts [new file with mode: 0755]
arch/arm/boot/dts/overlays/gpio-no-irq-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/gpio-poweroff-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/gpio-shutdown-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/hd44780-lcd-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/hdmi-backlight-hwhack-gpio-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/hifiberry-amp-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/hifiberry-amp100-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/hifiberry-amp3-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/hifiberry-dac-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/hifiberry-dacplus-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/hifiberry-dacplusadc-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/hifiberry-dacplusadcpro-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/hifiberry-dacplusdsp-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/hifiberry-dacplushd-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/hifiberry-digi-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/hifiberry-digi-pro-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/highperi-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/hy28a-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/hy28b-2017-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/hy28b-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/i-sabre-q2m-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/i2c-bcm2708-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/i2c-fan-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/i2c-gpio-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/i2c-mux-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/i2c-pwm-pca9685a-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/i2c-rtc-common.dtsi [new file with mode: 0644]
arch/arm/boot/dts/overlays/i2c-rtc-gpio-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/i2c-rtc-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/i2c-sensor-common.dtsi [new file with mode: 0755]
arch/arm/boot/dts/overlays/i2c-sensor-overlay.dts [new file with mode: 0755]
arch/arm/boot/dts/overlays/i2c0-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/i2c1-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/i2c3-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/i2c4-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/i2c5-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/i2c6-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/i2s-gpio28-31-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/ilitek251x-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/imx219-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/imx219.dtsi [new file with mode: 0644]
arch/arm/boot/dts/overlays/imx258-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/imx258.dtsi [new file with mode: 0644]
arch/arm/boot/dts/overlays/imx290-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/imx290_327-overlay.dtsi [new file with mode: 0644]
arch/arm/boot/dts/overlays/imx290_327.dtsi [new file with mode: 0644]
arch/arm/boot/dts/overlays/imx296-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/imx327-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/imx378-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/imx462-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/imx477-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/imx477_378-overlay.dtsi [new file with mode: 0644]
arch/arm/boot/dts/overlays/imx477_378.dtsi [new file with mode: 0644]
arch/arm/boot/dts/overlays/imx519-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/iqaudio-codec-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/iqaudio-dac-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/iqaudio-dacplus-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/iqaudio-digi-wm8804-audio-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/iqs550-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/irs1125-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/jedec-spi-nor-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/justboom-both-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/justboom-dac-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/justboom-digi-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/ltc294x-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/max98357a-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/maxtherm-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/mbed-dac-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/mcp23017-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/mcp23s17-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/mcp2515-can0-overlay.dts [new file with mode: 0755]
arch/arm/boot/dts/overlays/mcp2515-can1-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/mcp2515-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/mcp251xfd-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/mcp3008-overlay.dts [new file with mode: 0755]
arch/arm/boot/dts/overlays/mcp3202-overlay.dts [new file with mode: 0755]
arch/arm/boot/dts/overlays/mcp342x-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/media-center-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/merus-amp-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/midi-uart0-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/midi-uart1-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/midi-uart2-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/midi-uart3-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/midi-uart4-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/midi-uart5-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/minipitft13-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/miniuart-bt-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/mipi-dbi-spi-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/mlx90640-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/mmc-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/mpu6050-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/mz61581-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/ov2311-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/ov2311.dtsi [new file with mode: 0644]
arch/arm/boot/dts/overlays/ov5647-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/ov5647.dtsi [new file with mode: 0644]
arch/arm/boot/dts/overlays/ov7251-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/ov7251.dtsi [new file with mode: 0644]
arch/arm/boot/dts/overlays/ov9281-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/ov9281.dtsi [new file with mode: 0644]
arch/arm/boot/dts/overlays/overlay_map.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/papirus-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/pca953x-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/pcie-32bit-dma-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/pibell-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/pifacedigital-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/pifi-40-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/pifi-dac-hd-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/pifi-dac-zero-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/pifi-mini-210-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/piglow-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/piscreen-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/piscreen2r-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/pisound-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/pitft22-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/pitft28-capacitive-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/pitft28-resistive-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/pitft35-resistive-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/pps-gpio-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/pwm-2chan-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/pwm-ir-tx-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/pwm-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/qca7000-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/qca7000-uart0-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/ramoops-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/ramoops-pi4-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/rotary-encoder-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/rpi-backlight-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/rpi-cirrus-wm5102-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/rpi-dac-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/rpi-display-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/rpi-ft5406-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/rpi-poe-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/rpi-poe-plus-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/rpi-proto-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/rpi-sense-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/rpi-tv-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/rra-digidac1-wm8741-audio-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/sainsmart18-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/sc16is750-i2c-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/sc16is752-i2c-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/sc16is752-spi0-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/sc16is752-spi1-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/sdhost-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/sdio-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/seeed-can-fd-hat-v1-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/seeed-can-fd-hat-v2-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/sh1106-spi-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/si446x-spi0-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/smi-dev-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/smi-nand-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/smi-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/spi-gpio35-39-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/spi-gpio40-45-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/spi-rtc-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/spi0-0cs-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/spi0-1cs-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/spi0-2cs-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/spi1-1cs-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/spi1-2cs-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/spi1-3cs-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/spi2-1cs-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/spi2-2cs-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/spi2-3cs-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/spi3-1cs-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/spi3-2cs-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/spi4-1cs-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/spi4-2cs-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/spi5-1cs-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/spi5-2cs-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/spi6-1cs-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/spi6-2cs-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/ssd1306-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/ssd1306-spi-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/ssd1331-spi-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/ssd1351-spi-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/superaudioboard-overlay.dts [new file with mode: 0755]
arch/arm/boot/dts/overlays/sx150x-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/tc358743-audio-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/tc358743-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/tinylcd35-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/tpm-slb9670-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/uart0-overlay.dts [new file with mode: 0755]
arch/arm/boot/dts/overlays/uart1-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/uart2-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/uart3-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/uart4-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/uart5-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/udrc-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/ugreen-dabboard-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/upstream-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/upstream-pi4-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/vc4-fkms-v3d-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/vc4-fkms-v3d-pi4-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/vc4-kms-dpi-generic-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/vc4-kms-dpi-hyperpixel.dtsi [new file with mode: 0644]
arch/arm/boot/dts/overlays/vc4-kms-dpi-hyperpixel2r-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/vc4-kms-dpi-hyperpixel4-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/vc4-kms-dpi-hyperpixel4sq-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/vc4-kms-dpi-panel-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/vc4-kms-dpi.dtsi [new file with mode: 0644]
arch/arm/boot/dts/overlays/vc4-kms-dsi-7inch-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/vc4-kms-dsi-lt070me05000-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/vc4-kms-dsi-lt070me05000-v2-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/vc4-kms-kippah-7inch-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/vc4-kms-v3d-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/vc4-kms-v3d-pi4-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/vc4-kms-vga666-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/vga666-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/vl805-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/w1-gpio-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/w1-gpio-pullup-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/w5500-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/waveshare-can-fd-hat-mode-a-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/waveshare-can-fd-hat-mode-b-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/wittypi-overlay.dts [new file with mode: 0644]
arch/arm/boot/dts/overlays/wm8960-soundcard-overlay.dts [new file with mode: 0644]
arch/arm/configs/bcm2709_defconfig [new file with mode: 0644]
arch/arm/configs/bcm2711_defconfig [new file with mode: 0644]
arch/arm/configs/bcmrpi_defconfig [new file with mode: 0644]
arch/arm/include/asm/cacheflush.h
arch/arm/include/asm/glue-cache.h
arch/arm/include/asm/irqflags.h
arch/arm/include/asm/string.h
arch/arm/include/asm/uaccess.h
arch/arm/kernel/fiq.c
arch/arm/kernel/fiqasm.S
arch/arm/kernel/reboot.c
arch/arm/kernel/setup.c
arch/arm/lib/Makefile
arch/arm/lib/arm-mem.h [new file with mode: 0644]
arch/arm/lib/copy_from_user.S
arch/arm/lib/exports_rpi.c [new file with mode: 0644]
arch/arm/lib/memcmp_rpi.S [new file with mode: 0644]
arch/arm/lib/memcpy_rpi.S [new file with mode: 0644]
arch/arm/lib/memcpymove.h [new file with mode: 0644]
arch/arm/lib/memmove_rpi.S [new file with mode: 0644]
arch/arm/lib/memset_rpi.S [new file with mode: 0644]
arch/arm/lib/uaccess_with_memcpy.c
arch/arm/mach-bcm/Kconfig
arch/arm/mach-bcm/board_bcm2835.c
arch/arm/mm/cache-v6.S
arch/arm/mm/cache-v7.S
arch/arm/mm/proc-macros.S
arch/arm/mm/proc-syms.c
arch/arm/mm/proc-v6.S
arch/arm/vfp/vfpmodule.c
arch/arm64/boot/dts/Makefile
arch/arm64/boot/dts/broadcom/Makefile
arch/arm64/boot/dts/broadcom/bcm2710-rpi-2-b.dts [new file with mode: 0644]
arch/arm64/boot/dts/broadcom/bcm2710-rpi-3-b-plus.dts [new file with mode: 0644]
arch/arm64/boot/dts/broadcom/bcm2710-rpi-3-b.dts [new file with mode: 0644]
arch/arm64/boot/dts/broadcom/bcm2710-rpi-cm3.dts [new file with mode: 0644]
arch/arm64/boot/dts/broadcom/bcm2710-rpi-zero-2-w.dts [new file with mode: 0644]
arch/arm64/boot/dts/broadcom/bcm2710-rpi-zero-2.dts [new file with mode: 0644]
arch/arm64/boot/dts/broadcom/bcm2711-rpi-4-b.dts
arch/arm64/boot/dts/broadcom/bcm2711-rpi-400.dts
arch/arm64/boot/dts/broadcom/bcm2711-rpi-cm4.dts [new file with mode: 0644]
arch/arm64/boot/dts/broadcom/bcm2711-rpi-cm4s.dts [new file with mode: 0644]
arch/arm64/boot/dts/broadcom/bcm283x-rpi-csi1-2lane.dtsi [new symlink]
arch/arm64/boot/dts/broadcom/bcm283x-rpi-lan7515.dtsi [new symlink]
arch/arm64/boot/dts/overlays [new symlink]
arch/arm64/configs/bcm2711_defconfig [new file with mode: 0644]
arch/arm64/configs/bcmrpi3_defconfig [new file with mode: 0644]
arch/arm64/crypto/aes-cipher-glue.c
arch/arm64/crypto/aes-glue.c
arch/arm64/crypto/aes-neonbs-glue.c
arch/arm64/kernel/armv8_deprecated.c
arch/arm64/kernel/cpuinfo.c
arch/arm64/kernel/setup.c
drivers/bluetooth/hci_h5.c
drivers/char/Kconfig
drivers/char/Makefile
drivers/char/broadcom/Kconfig [new file with mode: 0644]
drivers/char/broadcom/Makefile [new file with mode: 0644]
drivers/char/broadcom/bcm2835-gpiomem.c [new file with mode: 0644]
drivers/char/broadcom/bcm2835_smi_dev.c [new file with mode: 0644]
drivers/char/broadcom/rpivid-mem.c [new file with mode: 0644]
drivers/char/broadcom/vc_mem.c [new file with mode: 0644]
drivers/char/broadcom/vcio.c [new file with mode: 0644]
drivers/char/hw_random/Kconfig
drivers/char/hw_random/bcm2835-rng.c
drivers/char/hw_random/iproc-rng200.c
drivers/char/tpm/tpm_tis_spi_main.c
drivers/clk/.kunitconfig [new file with mode: 0644]
drivers/clk/Kconfig
drivers/clk/Makefile
drivers/clk/bcm/clk-bcm2835.c
drivers/clk/bcm/clk-raspberrypi.c
drivers/clk/clk-allo-dac.c [new file with mode: 0644]
drivers/clk/clk-hifiberry-dachd.c [new file with mode: 0644]
drivers/clk/clk-hifiberry-dacpro.c [new file with mode: 0644]
drivers/clk/clk.c
drivers/clk/clk_test.c [new file with mode: 0644]
drivers/dma/Kconfig
drivers/dma/Makefile
drivers/dma/bcm2708-dmaengine.c [new file with mode: 0644]
drivers/dma/bcm2835-dma.c
drivers/firmware/raspberrypi.c
drivers/gpio/Kconfig
drivers/gpio/Makefile
drivers/gpio/gpio-bcm-virt.c [new file with mode: 0644]
drivers/gpio/gpio-fsm.c [new file with mode: 0644]
drivers/gpio/gpio-pwm.c [new file with mode: 0644]
drivers/gpio/gpiolib.c
drivers/gpu/drm/bridge/panel.c
drivers/gpu/drm/bridge/tc358762.c
drivers/gpu/drm/drm_atomic_helper.c
drivers/gpu/drm/drm_atomic_state_helper.c
drivers/gpu/drm/drm_atomic_uapi.c
drivers/gpu/drm/drm_bridge.c
drivers/gpu/drm/drm_color_mgmt.c
drivers/gpu/drm/drm_edid.c
drivers/gpu/drm/drm_fourcc.c
drivers/gpu/drm/drm_framebuffer.c
drivers/gpu/drm/drm_mode_object.c
drivers/gpu/drm/drm_modes.c
drivers/gpu/drm/drm_panel.c
drivers/gpu/drm/drm_probe_helper.c
drivers/gpu/drm/i915/display/intel_display.c
drivers/gpu/drm/i915/display/intel_hdmi.c
drivers/gpu/drm/i915/display/intel_lspcon.c
drivers/gpu/drm/msm/msm_atomic.c
drivers/gpu/drm/panel/Kconfig
drivers/gpu/drm/panel/Makefile
drivers/gpu/drm/panel/panel-ilitek-ili9806e.c [new file with mode: 0644]
drivers/gpu/drm/panel/panel-ilitek-ili9881c.c
drivers/gpu/drm/panel/panel-jdi-lt070me05000.c
drivers/gpu/drm/panel/panel-raspberrypi-touchscreen.c
drivers/gpu/drm/panel/panel-simple.c
drivers/gpu/drm/panel/panel-sitronix-st7701.c
drivers/gpu/drm/panel/panel-tdo-y17p.c [new file with mode: 0644]
drivers/gpu/drm/tiny/Kconfig
drivers/gpu/drm/tiny/Makefile
drivers/gpu/drm/tiny/panel-mipi-dbi.c [new file with mode: 0644]
drivers/gpu/drm/v3d/Kconfig
drivers/gpu/drm/v3d/v3d_debugfs.c
drivers/gpu/drm/v3d/v3d_drv.c
drivers/gpu/drm/v3d/v3d_drv.h
drivers/gpu/drm/v3d/v3d_gem.c
drivers/gpu/drm/v3d/v3d_irq.c
drivers/gpu/drm/v3d/v3d_mmu.c
drivers/gpu/drm/vc4/Kconfig
drivers/gpu/drm/vc4/Makefile
drivers/gpu/drm/vc4/vc4_bo.c
drivers/gpu/drm/vc4/vc4_crtc.c
drivers/gpu/drm/vc4/vc4_debugfs.c
drivers/gpu/drm/vc4/vc4_dpi.c
drivers/gpu/drm/vc4/vc4_drv.c
drivers/gpu/drm/vc4/vc4_drv.h
drivers/gpu/drm/vc4/vc4_dsi.c
drivers/gpu/drm/vc4/vc4_firmware_kms.c [new file with mode: 0644]
drivers/gpu/drm/vc4/vc4_gem.c
drivers/gpu/drm/vc4/vc4_hdmi.c
drivers/gpu/drm/vc4/vc4_hdmi.h
drivers/gpu/drm/vc4/vc4_hdmi_phy.c
drivers/gpu/drm/vc4/vc4_hdmi_regs.h
drivers/gpu/drm/vc4/vc4_hvs.c
drivers/gpu/drm/vc4/vc4_irq.c
drivers/gpu/drm/vc4/vc4_kms.c
drivers/gpu/drm/vc4/vc4_perfmon.c
drivers/gpu/drm/vc4/vc4_plane.c
drivers/gpu/drm/vc4/vc4_regs.h
drivers/gpu/drm/vc4/vc4_render_cl.c
drivers/gpu/drm/vc4/vc4_v3d.c
drivers/gpu/drm/vc4/vc4_validate.c
drivers/gpu/drm/vc4/vc4_validate_shaders.c
drivers/gpu/drm/vc4/vc4_vec.c
drivers/gpu/drm/vc4/vc_image_types.h [new file with mode: 0644]
drivers/hid/hid-ids.h
drivers/hid/hid-quirks.c
drivers/hid/usbhid/hid-core.c
drivers/hwmon/Kconfig
drivers/hwmon/Makefile
drivers/hwmon/emc2305.c [new file with mode: 0644]
drivers/i2c/busses/Kconfig
drivers/i2c/busses/Makefile
drivers/i2c/busses/i2c-bcm2708.c [new file with mode: 0644]
drivers/i2c/busses/i2c-bcm2835.c
drivers/i2c/busses/i2c-gpio.c
drivers/input/joystick/Kconfig
drivers/input/joystick/Makefile
drivers/input/joystick/rpisense-js.c [new file with mode: 0644]
drivers/input/touchscreen/edt-ft5x06.c
drivers/irqchip/irq-bcm2835.c
drivers/irqchip/irq-bcm2836.c
drivers/leds/leds-gpio.c
drivers/leds/trigger/Kconfig
drivers/leds/trigger/Makefile
drivers/leds/trigger/ledtrig-actpwr.c [new file with mode: 0644]
drivers/leds/trigger/ledtrig-input.c [new file with mode: 0644]
drivers/mailbox/bcm2835-mailbox.c
drivers/media/common/videobuf2/videobuf2-core.c
drivers/media/i2c/Kconfig
drivers/media/i2c/Makefile
drivers/media/i2c/ad5398_vcm.c [new file with mode: 0644]
drivers/media/i2c/adv7180.c
drivers/media/i2c/arducam-pivariety.c [new file with mode: 0644]
drivers/media/i2c/arducam-pivariety.h [new file with mode: 0644]
drivers/media/i2c/arducam_64mp.c [new file with mode: 0644]
drivers/media/i2c/imx219.c
drivers/media/i2c/imx258.c
drivers/media/i2c/imx290.c
drivers/media/i2c/imx296.c [new file with mode: 0644]
drivers/media/i2c/imx477.c [new file with mode: 0644]
drivers/media/i2c/imx519.c [new file with mode: 0644]
drivers/media/i2c/irs1125.c [new file with mode: 0644]
drivers/media/i2c/irs1125.h [new file with mode: 0644]
drivers/media/i2c/ov2311.c [new file with mode: 0644]
drivers/media/i2c/ov5647.c
drivers/media/i2c/ov7251.c
drivers/media/i2c/ov9281.c [new file with mode: 0644]
drivers/media/i2c/tc358743.c
drivers/media/mc/mc-entity.c
drivers/media/mc/mc-request.c
drivers/media/platform/Kconfig
drivers/media/platform/Makefile
drivers/media/platform/bcm2835/Kconfig [new file with mode: 0644]
drivers/media/platform/bcm2835/Makefile [new file with mode: 0644]
drivers/media/platform/bcm2835/bcm2835-unicam.c [new file with mode: 0644]
drivers/media/platform/bcm2835/vc4-regs-unicam.h [new file with mode: 0644]
drivers/media/platform/rockchip/rkisp1/rkisp1-isp.c
drivers/media/platform/rockchip/rkisp1/rkisp1-resizer.c
drivers/media/platform/video-mux.c
drivers/media/spi/Kconfig
drivers/media/usb/dvb-usb-v2/rtl28xxu.c
drivers/media/v4l2-core/v4l2-async.c
drivers/media/v4l2-core/v4l2-ctrls-api.c
drivers/media/v4l2-core/v4l2-ctrls-core.c
drivers/media/v4l2-core/v4l2-ctrls-defs.c
drivers/media/v4l2-core/v4l2-ctrls-priv.h
drivers/media/v4l2-core/v4l2-ctrls-request.c
drivers/media/v4l2-core/v4l2-ioctl.c
drivers/media/v4l2-core/v4l2-mem2mem.c
drivers/mfd/Kconfig
drivers/mfd/Makefile
drivers/mfd/bcm2835-pm.c
drivers/mfd/rpisense-core.c [new file with mode: 0644]
drivers/mfd/simple-mfd-i2c.c
drivers/misc/Kconfig
drivers/misc/Makefile
drivers/misc/bcm2835_smi.c [new file with mode: 0644]
drivers/mmc/core/block.c
drivers/mmc/core/core.c
drivers/mmc/core/quirks.h
drivers/mmc/host/Kconfig
drivers/mmc/host/Makefile
drivers/mmc/host/bcm2835-mmc.c [new file with mode: 0644]
drivers/mmc/host/bcm2835-sdhost.c [new file with mode: 0644]
drivers/mmc/host/sdhci-iproc.c
drivers/mmc/host/sdhci.c
drivers/net/ethernet/broadcom/genet/bcmgenet.c
drivers/net/ethernet/broadcom/genet/bcmgenet.h
drivers/net/ethernet/broadcom/genet/bcmmii.c
drivers/net/phy/Kconfig
drivers/net/phy/Makefile
drivers/net/phy/bcm-phy-lib.h
drivers/net/phy/bcm-phy-ptp.c [new file with mode: 0644]
drivers/net/phy/broadcom.c
drivers/net/phy/microchip.c
drivers/net/phy/smsc.c
drivers/net/usb/lan78xx.c
drivers/net/usb/smsc95xx.c
drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h
drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.c
drivers/net/wireless/broadcom/brcm80211/brcmfmac/debug.h
drivers/net/wireless/broadcom/brcm80211/brcmfmac/firmware.c
drivers/net/wireless/broadcom/brcm80211/brcmfmac/of.c
drivers/net/wireless/broadcom/brcm80211/brcmfmac/of.h
drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c
drivers/net/wireless/broadcom/brcm80211/brcmfmac/usb.c
drivers/of/Kconfig
drivers/of/Makefile
drivers/of/configfs.c [new file with mode: 0644]
drivers/of/overlay.c
drivers/pci/controller/pcie-brcmstb.c
drivers/perf/Kconfig
drivers/perf/Makefile
drivers/perf/raspberrypi_axi_monitor.c [new file with mode: 0644]
drivers/pinctrl/bcm/pinctrl-bcm2835.c
drivers/power/reset/gpio-poweroff.c
drivers/power/supply/Kconfig
drivers/power/supply/Makefile
drivers/power/supply/rpi_poe_power.c [new file with mode: 0644]
drivers/pps/clients/pps-gpio.c
drivers/pwm/pwm-raspberrypi-poe.c
drivers/regulator/rpi-panel-attiny-regulator.c
drivers/rtc/rtc-pcf2123.c
drivers/rtc/rtc-pcf85063.c
drivers/rtc/rtc-pcf8523.c
drivers/rtc/rtc-rv3028.c
drivers/soc/bcm/Kconfig
drivers/soc/bcm/bcm2835-power.c
drivers/spi/spi-bcm2835.c
drivers/spi/spi-gpio.c
drivers/spi/spi.c
drivers/spi/spidev.c
drivers/staging/fbtft/fb_st7735r.c
drivers/staging/fbtft/fb_st7789v.c
drivers/staging/fbtft/fbtft-core.c
drivers/staging/fbtft/fbtft.h
drivers/staging/media/Kconfig
drivers/staging/media/Makefile
drivers/staging/media/rpivid/Kconfig [new file with mode: 0644]
drivers/staging/media/rpivid/Makefile [new file with mode: 0644]
drivers/staging/media/rpivid/rpivid.c [new file with mode: 0644]
drivers/staging/media/rpivid/rpivid.h [new file with mode: 0644]
drivers/staging/media/rpivid/rpivid_dec.c [new file with mode: 0644]
drivers/staging/media/rpivid/rpivid_dec.h [new file with mode: 0644]
drivers/staging/media/rpivid/rpivid_h265.c [new file with mode: 0644]
drivers/staging/media/rpivid/rpivid_hw.c [new file with mode: 0644]
drivers/staging/media/rpivid/rpivid_hw.h [new file with mode: 0644]
drivers/staging/media/rpivid/rpivid_video.c [new file with mode: 0644]
drivers/staging/media/rpivid/rpivid_video.h [new file with mode: 0644]
drivers/staging/vc04_services/Kconfig
drivers/staging/vc04_services/Makefile
drivers/staging/vc04_services/bcm2835-audio/bcm2835.c
drivers/staging/vc04_services/bcm2835-audio/bcm2835.h
drivers/staging/vc04_services/bcm2835-camera/bcm2835-camera.c
drivers/staging/vc04_services/bcm2835-camera/bcm2835-camera.h
drivers/staging/vc04_services/bcm2835-camera/controls.c
drivers/staging/vc04_services/bcm2835-codec/Kconfig [new file with mode: 0644]
drivers/staging/vc04_services/bcm2835-codec/Makefile [new file with mode: 0644]
drivers/staging/vc04_services/bcm2835-codec/TODO [new file with mode: 0644]
drivers/staging/vc04_services/bcm2835-codec/bcm2835-v4l2-codec.c [new file with mode: 0644]
drivers/staging/vc04_services/bcm2835-isp/Kconfig [new file with mode: 0644]
drivers/staging/vc04_services/bcm2835-isp/Makefile [new file with mode: 0644]
drivers/staging/vc04_services/bcm2835-isp/bcm2835-isp-ctrls.h [new file with mode: 0644]
drivers/staging/vc04_services/bcm2835-isp/bcm2835-isp-fmts.h [new file with mode: 0644]
drivers/staging/vc04_services/bcm2835-isp/bcm2835-v4l2-isp.c [new file with mode: 0644]
drivers/staging/vc04_services/include/linux/broadcom/vc_sm_cma_ioctl.h [new file with mode: 0644]
drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.c
drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.h
drivers/staging/vc04_services/vc-sm-cma/Kconfig [new file with mode: 0644]
drivers/staging/vc04_services/vc-sm-cma/Makefile [new file with mode: 0644]
drivers/staging/vc04_services/vc-sm-cma/TODO [new file with mode: 0644]
drivers/staging/vc04_services/vc-sm-cma/vc_sm.c [new file with mode: 0644]
drivers/staging/vc04_services/vc-sm-cma/vc_sm.h [new file with mode: 0644]
drivers/staging/vc04_services/vc-sm-cma/vc_sm_cma_vchi.c [new file with mode: 0644]
drivers/staging/vc04_services/vc-sm-cma/vc_sm_cma_vchi.h [new file with mode: 0644]
drivers/staging/vc04_services/vc-sm-cma/vc_sm_defs.h [new file with mode: 0644]
drivers/staging/vc04_services/vc-sm-cma/vc_sm_knl.h [new file with mode: 0644]
drivers/staging/vc04_services/vchiq-mmal/Kconfig
drivers/staging/vc04_services/vchiq-mmal/mmal-common.h
drivers/staging/vc04_services/vchiq-mmal/mmal-encodings.h
drivers/staging/vc04_services/vchiq-mmal/mmal-msg-format.h
drivers/staging/vc04_services/vchiq-mmal/mmal-msg.h
drivers/staging/vc04_services/vchiq-mmal/mmal-parameters.h
drivers/staging/vc04_services/vchiq-mmal/mmal-vchiq.c
drivers/staging/vc04_services/vchiq-mmal/mmal-vchiq.h
drivers/thermal/broadcom/bcm2711_thermal.c
drivers/thermal/gov_step_wise.c
drivers/tty/serial/8250/8250_bcm2835aux.c
drivers/tty/serial/amba-pl011.c
drivers/tty/serial/sc16is7xx.c
drivers/usb/Makefile
drivers/usb/core/generic.c
drivers/usb/core/hcd.c
drivers/usb/core/hub.c
drivers/usb/core/message.c
drivers/usb/core/otg_productlist.h
drivers/usb/gadget/file_storage.c [new file with mode: 0644]
drivers/usb/host/Kconfig
drivers/usb/host/Makefile
drivers/usb/host/dwc_common_port/Makefile [new file with mode: 0644]
drivers/usb/host/dwc_common_port/Makefile.fbsd [new file with mode: 0644]
drivers/usb/host/dwc_common_port/Makefile.linux [new file with mode: 0644]
drivers/usb/host/dwc_common_port/changes.txt [new file with mode: 0644]
drivers/usb/host/dwc_common_port/doc/doxygen.cfg [new file with mode: 0644]
drivers/usb/host/dwc_common_port/dwc_cc.c [new file with mode: 0644]
drivers/usb/host/dwc_common_port/dwc_cc.h [new file with mode: 0644]
drivers/usb/host/dwc_common_port/dwc_common_fbsd.c [new file with mode: 0644]
drivers/usb/host/dwc_common_port/dwc_common_linux.c [new file with mode: 0644]
drivers/usb/host/dwc_common_port/dwc_common_nbsd.c [new file with mode: 0644]
drivers/usb/host/dwc_common_port/dwc_crypto.c [new file with mode: 0644]
drivers/usb/host/dwc_common_port/dwc_crypto.h [new file with mode: 0644]
drivers/usb/host/dwc_common_port/dwc_dh.c [new file with mode: 0644]
drivers/usb/host/dwc_common_port/dwc_dh.h [new file with mode: 0644]
drivers/usb/host/dwc_common_port/dwc_list.h [new file with mode: 0644]
drivers/usb/host/dwc_common_port/dwc_mem.c [new file with mode: 0644]
drivers/usb/host/dwc_common_port/dwc_modpow.c [new file with mode: 0644]
drivers/usb/host/dwc_common_port/dwc_modpow.h [new file with mode: 0644]
drivers/usb/host/dwc_common_port/dwc_notifier.c [new file with mode: 0644]
drivers/usb/host/dwc_common_port/dwc_notifier.h [new file with mode: 0644]
drivers/usb/host/dwc_common_port/dwc_os.h [new file with mode: 0644]
drivers/usb/host/dwc_common_port/usb.h [new file with mode: 0644]
drivers/usb/host/dwc_otg/Makefile [new file with mode: 0644]
drivers/usb/host/dwc_otg/doc/doxygen.cfg [new file with mode: 0644]
drivers/usb/host/dwc_otg/dummy_audio.c [new file with mode: 0644]
drivers/usb/host/dwc_otg/dwc_cfi_common.h [new file with mode: 0644]
drivers/usb/host/dwc_otg/dwc_otg_adp.c [new file with mode: 0644]
drivers/usb/host/dwc_otg/dwc_otg_adp.h [new file with mode: 0644]
drivers/usb/host/dwc_otg/dwc_otg_attr.c [new file with mode: 0644]
drivers/usb/host/dwc_otg/dwc_otg_attr.h [new file with mode: 0644]
drivers/usb/host/dwc_otg/dwc_otg_cfi.c [new file with mode: 0644]
drivers/usb/host/dwc_otg/dwc_otg_cfi.h [new file with mode: 0644]
drivers/usb/host/dwc_otg/dwc_otg_cil.c [new file with mode: 0644]
drivers/usb/host/dwc_otg/dwc_otg_cil.h [new file with mode: 0644]
drivers/usb/host/dwc_otg/dwc_otg_cil_intr.c [new file with mode: 0644]
drivers/usb/host/dwc_otg/dwc_otg_core_if.h [new file with mode: 0644]
drivers/usb/host/dwc_otg/dwc_otg_dbg.h [new file with mode: 0644]
drivers/usb/host/dwc_otg/dwc_otg_driver.c [new file with mode: 0644]
drivers/usb/host/dwc_otg/dwc_otg_driver.h [new file with mode: 0644]
drivers/usb/host/dwc_otg/dwc_otg_fiq_fsm.c [new file with mode: 0644]
drivers/usb/host/dwc_otg/dwc_otg_fiq_fsm.h [new file with mode: 0644]
drivers/usb/host/dwc_otg/dwc_otg_fiq_stub.S [new file with mode: 0644]
drivers/usb/host/dwc_otg/dwc_otg_hcd.c [new file with mode: 0644]
drivers/usb/host/dwc_otg/dwc_otg_hcd.h [new file with mode: 0644]
drivers/usb/host/dwc_otg/dwc_otg_hcd_ddma.c [new file with mode: 0644]
drivers/usb/host/dwc_otg/dwc_otg_hcd_if.h [new file with mode: 0644]
drivers/usb/host/dwc_otg/dwc_otg_hcd_intr.c [new file with mode: 0644]
drivers/usb/host/dwc_otg/dwc_otg_hcd_linux.c [new file with mode: 0644]
drivers/usb/host/dwc_otg/dwc_otg_hcd_queue.c [new file with mode: 0644]
drivers/usb/host/dwc_otg/dwc_otg_os_dep.h [new file with mode: 0644]
drivers/usb/host/dwc_otg/dwc_otg_pcd.c [new file with mode: 0644]
drivers/usb/host/dwc_otg/dwc_otg_pcd.h [new file with mode: 0644]
drivers/usb/host/dwc_otg/dwc_otg_pcd_if.h [new file with mode: 0644]
drivers/usb/host/dwc_otg/dwc_otg_pcd_intr.c [new file with mode: 0644]
drivers/usb/host/dwc_otg/dwc_otg_pcd_linux.c [new file with mode: 0644]
drivers/usb/host/dwc_otg/dwc_otg_regs.h [new file with mode: 0644]
drivers/usb/host/dwc_otg/test/Makefile [new file with mode: 0644]
drivers/usb/host/dwc_otg/test/dwc_otg_test.pm [new file with mode: 0644]
drivers/usb/host/dwc_otg/test/test_mod_param.pl [new file with mode: 0644]
drivers/usb/host/dwc_otg/test/test_sysfs.pl [new file with mode: 0644]
drivers/usb/host/xhci-mem.c
drivers/usb/host/xhci-pci.c
drivers/usb/host/xhci-ring.c
drivers/usb/host/xhci.c
drivers/usb/host/xhci.h
drivers/video/backlight/Kconfig
drivers/video/backlight/Makefile
drivers/video/backlight/rpi_backlight.c [new file with mode: 0644]
drivers/video/fbdev/Kconfig
drivers/video/fbdev/Makefile
drivers/video/fbdev/bcm2708_fb.c [new file with mode: 0644]
drivers/video/fbdev/core/cfbimgblt.c
drivers/video/fbdev/core/fbmem.c
drivers/video/fbdev/rpisense-fb.c [new file with mode: 0644]
drivers/video/logo/logo_linux_clut224.ppm
drivers/w1/masters/w1-gpio.c
drivers/watchdog/bcm2835_wdt.c
fs/ext4/mballoc.c
include/drm/drm_bridge.h
include/drm/drm_color_mgmt.h
include/drm/drm_edid.h
include/drm/drm_mipi_dbi.h
include/drm/drm_mipi_dsi.h
include/drm/drm_mode_object.h
include/drm/drm_modes.h
include/drm/drm_panel.h
include/drm/drm_plane.h
include/drm/drm_probe_helper.h
include/dt-bindings/gpio/gpio-fsm.h [new file with mode: 0644]
include/linux/brcmphy.h
include/linux/broadcom/bcm2835_smi.h [new file with mode: 0644]
include/linux/broadcom/vc_mem.h [new file with mode: 0644]
include/linux/clk-provider.h
include/linux/clk.h
include/linux/fb.h
include/linux/leds.h
include/linux/mfd/bcm2835-pm.h
include/linux/mfd/rpisense/core.h [new file with mode: 0644]
include/linux/mfd/rpisense/framebuffer.h [new file with mode: 0644]
include/linux/mfd/rpisense/joystick.h [new file with mode: 0644]
include/linux/microchipphy.h
include/linux/mmc/card.h
include/linux/platform_data/dma-bcm2708.h [new file with mode: 0644]
include/linux/usb.h
include/linux/usb/hcd.h
include/media/hevc-ctrls.h
include/media/media-entity.h
include/media/media-request.h
include/media/v4l2-ctrls.h
include/media/v4l2-mediabus.h
include/media/videobuf2-core.h
include/soc/bcm2835/raspberrypi-firmware.h
include/uapi/drm/drm_fourcc.h
include/uapi/linux/bcm2835-isp.h [new file with mode: 0644]
include/uapi/linux/fb.h
include/uapi/linux/media-bus-format.h
include/uapi/linux/media.h
include/uapi/linux/v4l2-controls.h
include/uapi/linux/videodev2.h
kernel/cgroup/cgroup.c
kernel/resource.c
mm/page_alloc.c
mm/zswap.c
net/bluetooth/smp.c
scripts/Makefile.dtbinst
scripts/Makefile.lib
sound/soc/bcm/Kconfig
sound/soc/bcm/Makefile
sound/soc/bcm/allo-boss-dac.c [new file with mode: 0644]
sound/soc/bcm/allo-boss2-dac.c [new file with mode: 0644]
sound/soc/bcm/allo-katana-codec.c [new file with mode: 0644]
sound/soc/bcm/allo-piano-dac-plus.c [new file with mode: 0644]
sound/soc/bcm/allo-piano-dac.c [new file with mode: 0644]
sound/soc/bcm/audioinjector-isolated-soundcard.c [new file with mode: 0644]
sound/soc/bcm/audioinjector-octo-soundcard.c [new file with mode: 0644]
sound/soc/bcm/audioinjector-pi-soundcard.c [new file with mode: 0644]
sound/soc/bcm/audiosense-pi.c [new file with mode: 0644]
sound/soc/bcm/chipdip-dac.c [new file with mode: 0644]
sound/soc/bcm/dacberry400.c [new file with mode: 0644]
sound/soc/bcm/digidac1-soundcard.c [new file with mode: 0644]
sound/soc/bcm/dionaudio_loco-v2.c [new file with mode: 0644]
sound/soc/bcm/dionaudio_loco.c [new file with mode: 0644]
sound/soc/bcm/fe-pi-audio.c [new file with mode: 0644]
sound/soc/bcm/googlevoicehat-codec.c [new file with mode: 0644]
sound/soc/bcm/hifiberry_dacplus.c [new file with mode: 0644]
sound/soc/bcm/hifiberry_dacplusadc.c [new file with mode: 0644]
sound/soc/bcm/hifiberry_dacplusadcpro.c [new file with mode: 0644]
sound/soc/bcm/hifiberry_dacplusdsp.c [new file with mode: 0644]
sound/soc/bcm/hifiberry_dacplushd.c [new file with mode: 0644]
sound/soc/bcm/i-sabre-q2m.c [new file with mode: 0644]
sound/soc/bcm/iqaudio-codec.c [new file with mode: 0644]
sound/soc/bcm/iqaudio-dac.c [new file with mode: 0644]
sound/soc/bcm/justboom-both.c [new file with mode: 0644]
sound/soc/bcm/justboom-dac.c [new file with mode: 0644]
sound/soc/bcm/pifi-40.c [new file with mode: 0644]
sound/soc/bcm/pisound.c [new file with mode: 0644]
sound/soc/bcm/rpi-cirrus.c [new file with mode: 0644]
sound/soc/bcm/rpi-proto.c [new file with mode: 0644]
sound/soc/bcm/rpi-simple-soundcard.c [new file with mode: 0644]
sound/soc/bcm/rpi-wm8804-soundcard.c [new file with mode: 0644]
sound/soc/codecs/Kconfig
sound/soc/codecs/Makefile
sound/soc/codecs/cs42xx8-i2c.c
sound/soc/codecs/cs42xx8.c
sound/soc/codecs/i-sabre-codec.c [new file with mode: 0644]
sound/soc/codecs/i-sabre-codec.h [new file with mode: 0644]
sound/soc/codecs/ma120x0p.c [new file with mode: 0644]
sound/soc/codecs/pcm1794a.c [new file with mode: 0644]
sound/soc/codecs/pcm512x.c
sound/soc/codecs/tas5713.c [new file with mode: 0644]
sound/soc/codecs/tas5713.h [new file with mode: 0644]
sound/soc/soc-core.c
sound/usb/card.c
sound/usb/quirks.c

diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
new file mode 100644 (file)
index 0000000..e8a3c98
--- /dev/null
@@ -0,0 +1,91 @@
+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 CM1
+      - Raspberry Pi CM3
+      - Raspberry Pi CM3 Lite
+      - Raspberry Pi CM3+
+      - Raspberry Pi CM3+ Lite
+      - Raspberry Pi CM4
+      - Raspberry Pi CM4 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 (file)
index 0000000..338282d
--- /dev/null
@@ -0,0 +1,9 @@
+blank_issues_enabled: false
+contact_links:
+  - name: "⛔ Question"
+    url: https://www.raspberrypi.org/forums
+    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 the Raspbian distribution packages"
+    url: https://github.com/RPi-Distro/repo
+    about: "If you have problems with the Raspbian distribution packages, please report them in the github.com/RPi-Distro/repo."
+
diff --git a/Documentation/admin-guide/media/bcm2835-isp.rst b/Documentation/admin-guide/media/bcm2835-isp.rst
new file mode 100644 (file)
index 0000000..e1c19f7
--- /dev/null
@@ -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/clock/raspberrypi,firmware-clocks.yaml b/Documentation/devicetree/bindings/clock/raspberrypi,firmware-clocks.yaml
new file mode 100644 (file)
index 0000000..2047e25
--- /dev/null
@@ -0,0 +1,32 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/clock/raspberrypi,firmware-clocks.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: RaspberryPi Firmware Clocks Device Tree Bindings
+
+maintainers:
+  - Maxime Ripard <mripard@kernel.org>
+
+properties:
+  "#clock-cells":
+    const: 1
+
+  compatible:
+    const: raspberrypi,firmware-clocks
+
+required:
+  - "#clock-cells"
+  - compatible
+
+additionalProperties: false
+
+examples:
+  - |
+    firmware_clocks: firmware-clocks {
+        compatible = "raspberrypi,firmware-clocks";
+        #clock-cells = <1>;
+    };
+
+...
index 3260857..112bc56 100644 (file)
@@ -21,6 +21,7 @@ properties:
       - brcm,bcm2711-dsi1
       - brcm,bcm2835-dsi0
       - brcm,bcm2835-dsi1
+      - brcm,bcm2711-dsi1
 
   reg:
     maxItems: 1
index 031e35e..e7ef3a3 100644 (file)
@@ -11,24 +11,58 @@ maintainers:
 
 properties:
   compatible:
-    const: brcm,bcm2835-hdmi
+    enum:
+      - brcm,bcm2835-hdmi
+      - brcm,bcm2711-hdmi0
+      - brcm,bcm2711-hdmi1
 
   reg:
+    oneOf:
+      - items:
+        - description: HDMI register range
+        - description: HD register range
+
+      - items:
+        - description: HDMI controller register range
+        - description: DVP register range
+        - description: HDMI PHY register range
+        - description: Rate Manager register range
+        - description: Packet RAM register range
+        - description: Metadata RAM register range
+        - description: CSC register range
+        - description: CEC register range
+        - description: HD register range
+
+  reg-names:
     items:
-      - description: HDMI register range
-      - description: HD register range
+      - const: hdmi
+      - const: dvp
+      - const: phy
+      - const: rm
+      - const: packet
+      - const: metadata
+      - const: csc
+      - const: cec
+      - const: hd
 
   interrupts:
     minItems: 2
 
   clocks:
-    items:
-      - description: The pixel clock
-      - description: The HDMI state machine clock
+    oneOf:
+      - items:
+        - description: The pixel clock
+        - description: The HDMI state machine clock
+
+      - items:
+        - description: The HDMI state machine clock
 
   clock-names:
-    items:
-      - const: pixel
+    oneOf:
+      - items:
+        - const: pixel
+        - const: hdmi
+
       - const: hdmi
 
   ddc:
@@ -51,15 +85,54 @@ properties:
   dma-names:
     const: audio-rx
 
+  resets:
+    maxItems: 1
+
 required:
   - compatible
   - reg
-  - interrupts
   - clocks
   - ddc
 
 additionalProperties: false
 
+if:
+  properties:
+    compatible:
+      contains:
+        enum:
+          - brcm,bcm2711-hdmi0
+          - brcm,bcm2711-hdmi1
+
+then:
+  properties:
+    reg:
+      minItems: 9
+
+    clocks:
+      maxItems: 1
+
+    clock-names:
+      maxItems: 1
+
+  required:
+    - reg-names
+    - resets
+
+else:
+  properties:
+    reg:
+      maxItems: 2
+
+    clocks:
+      minItems: 2
+
+    clock-names:
+      minItems: 2
+
+  required:
+    - interrupts
+
 examples:
   - |
     #include <dt-bindings/clock/bcm2835.h>
@@ -77,4 +150,31 @@ examples:
         clock-names = "pixel", "hdmi";
     };
 
+  - |
+    hdmi0: hdmi@7ef00700 {
+        compatible = "brcm,bcm2711-hdmi0";
+        reg = <0x7ef00700 0x300>,
+              <0x7ef00300 0x200>,
+              <0x7ef00f00 0x80>,
+              <0x7ef00f80 0x80>,
+              <0x7ef01b00 0x200>,
+              <0x7ef01f00 0x400>,
+              <0x7ef00200 0x80>,
+              <0x7ef04300 0x100>,
+              <0x7ef20000 0x100>;
+        reg-names = "hdmi",
+                    "dvp",
+                    "phy",
+                    "rm",
+                    "packet",
+                    "metadata",
+                    "csc",
+                    "cec",
+                    "hd";
+        clocks = <&firmware_clocks 13>;
+        clock-names = "hdmi";
+        resets = <&dvp 0>;
+        ddc = <&ddc0>;
+    };
+
 ...
index 49a5e04..eac739a 100644 (file)
@@ -21,6 +21,11 @@ properties:
       - brcm,bcm2835-vc4
       - brcm,cygnus-vc4
 
+  raspberrypi,firmware:
+    $ref: /schemas/types.yaml#/definitions/phandle
+    description: >
+      Phandle to the mailbox node to communicate with the firmware.
+
 required:
   - compatible
 
diff --git a/Documentation/devicetree/bindings/display/panel/panel-mipi-dbi-spi.yaml b/Documentation/devicetree/bindings/display/panel/panel-mipi-dbi-spi.yaml
new file mode 100644 (file)
index 0000000..f297899
--- /dev/null
@@ -0,0 +1,126 @@
+# SPDX-License-Identifier: (GPL-2.0-only or BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/panel/panel-mipi-dbi-spi.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: MIPI DBI SPI Panel
+
+maintainers:
+  - Noralf Trønnes <noralf@tronnes.org>
+
+description: |
+  This binding is for display panels using a MIPI DBI compatible controller
+  in SPI mode.
+
+  The MIPI Alliance Standard for Display Bus Interface defines the electrical
+  and logical interfaces for display controllers historically used in mobile
+  phones. The standard defines 4 display architecture types and this binding is
+  for type 1 which has full frame memory. There are 3 interface types in the
+  standard and type C is the serial interface.
+
+  The standard defines the following interface signals for type C:
+  - Power:
+    - Vdd: Power supply for display module
+    - Vddi: Logic level supply for interface signals
+    Combined into one in this binding called: power-supply
+  - Interface:
+    - CSx: Chip select
+    - SCL: Serial clock
+    - Dout: Serial out
+    - Din: Serial in
+    - SDA: Bidrectional in/out
+    - D/CX: Data/command selection, high=data, low=command
+      Called dc-gpios in this binding.
+    - RESX: Reset when low
+      Called reset-gpios in this binding.
+
+  The type C interface has 3 options:
+
+    - Option 1: 9-bit mode and D/CX as the 9th bit
+      |              Command              |  the next command or following data  |
+      |<0><D7><D6><D5><D4><D3><D2><D1><D0>|<D/CX><D7><D6><D5><D4><D3><D2><D1><D0>|
+
+    - Option 2: 16-bit mode and D/CX as a 9th bit
+      |              Command or data                              |
+      |<X><X><X><X><X><X><X><D/CX><D7><D6><D5><D4><D3><D2><D1><D0>|
+
+    - Option 3: 8-bit mode and D/CX as a separate interface line
+      |        Command or data         |
+      |<D7><D6><D5><D4><D3><D2><D1><D0>|
+
+  The panel resolution is specified using the panel-timing node properties
+  hactive (width) and vactive (height). The other mandatory panel-timing
+  properties should be set to zero except clock-frequency which can be
+  optionally set to inform about the actual pixel clock frequency.
+
+  If the panel is wired to the controller at an offset specify this using
+  hback-porch (x-offset) and vback-porch (y-offset).
+
+allOf:
+  - $ref: panel-common.yaml#
+  - $ref: /schemas/spi/spi-peripheral-props.yaml#
+
+properties:
+  compatible:
+    items:
+      - enum:
+          - sainsmart18
+      - const: panel-mipi-dbi-spi
+
+  write-only:
+    type: boolean
+    description:
+      Controller is not readable (ie. Din (MISO on the SPI interface) is not
+      wired up).
+
+  dc-gpios:
+    maxItems: 1
+    description: |
+      Controller data/command selection (D/CX) in 4-line SPI mode.
+      If not set, the controller is in 3-line SPI mode.
+
+required:
+  - compatible
+  - reg
+  - panel-timing
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+
+    spi {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        display@0{
+            compatible = "sainsmart18", "panel-mipi-dbi-spi";
+            reg = <0>;
+            spi-max-frequency = <40000000>;
+
+            dc-gpios = <&gpio 24 GPIO_ACTIVE_HIGH>;
+            reset-gpios = <&gpio 25 GPIO_ACTIVE_HIGH>;
+            write-only;
+
+            backlight = <&backlight>;
+
+            width-mm = <35>;
+            height-mm = <28>;
+
+            panel-timing {
+                hactive = <160>;
+                vactive = <128>;
+                hback-porch = <0>;
+                vback-porch = <0>;
+                clock-frequency = <0>;
+                hfront-porch = <0>;
+                hsync-len = <0>;
+                vfront-porch = <0>;
+                vsync-len = <0>;
+            };
+        };
+    };
+
+...
index 335776c..37a9e6d 100644 (file)
@@ -148,6 +148,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
@@ -162,6 +164,8 @@ properties:
       - ivo,m133nwf4-r0
         # 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 G070Y2-L01 7" WVGA (800x480) TFT LCD panel
diff --git a/Documentation/devicetree/bindings/hwmon/microchip,emc2305.yaml b/Documentation/devicetree/bindings/hwmon/microchip,emc2305.yaml
new file mode 100644 (file)
index 0000000..efdc3ce
--- /dev/null
@@ -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 (file)
index 0000000..164d037
--- /dev/null
@@ -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 (file)
index 0000000..446ac97
--- /dev/null
@@ -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 (file)
index 0000000..b71a197
--- /dev/null
@@ -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 (file)
index 0000000..92bf4ff
--- /dev/null
@@ -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/imx219.txt b/Documentation/devicetree/bindings/media/i2c/imx219.txt
new file mode 100644 (file)
index 0000000..a02f1ce
--- /dev/null
@@ -0,0 +1,59 @@
+* Sony 1/4.0-Inch 8Mpixel CMOS Digital Image Sensor
+
+The Sony imx219 is a 1/4.0-inch CMOS active pixel digital image sensor with
+an active array size of 3280H x 2464V. It is programmable through I2C
+interface. The I2C address is fixed to 0x10 as per sensor data sheet.
+Image data is sent through MIPI CSI-2, which is configured as either 2 or 4
+data lanes.
+
+Required Properties:
+- compatible: value should be "sony,imx219" for imx219 sensor
+- reg: I2C bus address of the device
+- clocks: reference to the xclk input clock.
+- clock-names: should be "xclk".
+- DOVDD-supply: Digital I/O voltage supply, 1.8 volts
+- AVDD-supply: Analog voltage supply, 2.8 volts
+- DVDD-supply: Digital core voltage supply, 1.2 volts
+
+Optional Properties:
+- xclr-gpios: reference to the GPIO connected to the xclr pin, if any. Must be
+             released after all supplies are applied.
+             This is an active high signal to the imx219.
+
+The imx219 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 2>, or  <1 2 3 4> (two or four lane CSI-2
+  supported)
+
+Example:
+       sensor@10 {
+               compatible = "sony,imx219";
+               reg = <0x10>;
+               #address-cells = <1>;
+               #size-cells = <0>;
+               clocks = <&imx219_clk>;
+               clock-names = "xclk";
+               xclr-gpios = <&gpio_sensor 0 0>;
+               DOVDD-supply = <&vgen4_reg>; /* 1.8v */
+               AVDD-supply = <&vgen3_reg>;  /* 2.8v */
+               DVDD-supply = <&vgen2_reg>;  /* 1.2v */
+
+               imx219_clk: camera-clk {
+                       compatible = "fixed-clock";
+                       #clock-cells = <0>;
+                       clock-frequency = <24000000>;
+               };
+
+               port {
+                       sensor_out: endpoint {
+                               remote-endpoint = <&csiss_in>;
+                               clock-lanes = <0>;
+                               data-lanes = <1 2>;
+                       };
+               };
+       };
index cde0f73..3fc30a7 100644 (file)
@@ -14,10 +14,13 @@ description: |-
   type stacked image sensor with a square pixel array of size 4208 x 3120. It
   is programmable through I2C interface.  Image data is sent through MIPI
   CSI-2.
+  There are a number of variants of the sensor which cannot be detected at
+  runtime, so multiple compatible strings are required to differentiate these.
 
 properties:
   compatible:
     const: sony,imx258
+    const: sony,imx258-pdaf
 
   assigned-clocks: true
   assigned-clock-parents: true
index a3cc214..294e636 100644 (file)
@@ -1,13 +1,14 @@
 * Sony IMX290 1/2.8-Inch CMOS Image Sensor
 
 The Sony IMX290 is a 1/2.8-Inch CMOS Solid-state image sensor with
-Square Pixel for Color Cameras. It is programmable through I2C and 4-wire
-interfaces. The sensor output is available via CMOS logic parallel SDR output,
+Square Pixel for Color or Monochrome Cameras. It is programmable through I2C
+and 4-wire interfaces.
+The sensor output is available via CMOS logic parallel SDR output,
 Low voltage LVDS DDR output and CSI-2 serial data output. The CSI-2 bus is the
 default. No bindings have been defined for the other busses.
 
 Required Properties:
-- compatible: Should be "sony,imx290"
+- compatible: Should be "sony,imx290", or "sony,imx290-mono"
 - reg: I2C bus address of the device
 - clocks: Reference to the xclk clock.
 - clock-names: Should be "xclk".
diff --git a/Documentation/devicetree/bindings/media/i2c/imx378.yaml b/Documentation/devicetree/bindings/media/i2c/imx378.yaml
new file mode 100644 (file)
index 0000000..f832b4b
--- /dev/null
@@ -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 (file)
index 0000000..0994e13
--- /dev/null
@@ -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 (file)
index 0000000..717230a
--- /dev/null
@@ -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 (file)
index 0000000..25a4802
--- /dev/null
@@ -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/sony,imx296.yaml b/Documentation/devicetree/bindings/media/i2c/sony,imx296.yaml
new file mode 100644 (file)
index 0000000..e8f9a73
--- /dev/null
@@ -0,0 +1,95 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/i2c/sony,imx296.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Sony IMX296 1/2.8-Inch CMOS Image Sensor
+
+maintainers:
+  - Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
+  - Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+
+description: |-
+  The Sony IMX296 is a 1/2.9-Inch active pixel type CMOS Solid-state image
+  sensor with square pixel array and 1.58 M effective pixels. This chip
+  features a global shutter with variable charge-integration time. It is
+  programmable through I2C and 4-wire interfaces. The sensor output is
+  available via CSI-2 serial data output (1 Lane).
+
+properties:
+  compatible:
+    const: sony,imx296
+
+  reg:
+    maxItems: 1
+
+  clocks:
+    maxItems: 1
+
+  clock-names:
+    description: Input clock (37.125 MHz, 54 MHz or 74.25 MHz)
+    items:
+      - const: inck
+
+  avdd-supply:
+    description: Analog power supply (3.3V)
+
+  dvdd-supply:
+    description: Digital power supply (1.2V)
+
+  ovdd-supply:
+    description: Interface power supply (1.8V)
+
+  reset-gpios:
+    description: Sensor reset (XCLR) GPIO
+    maxItems: 1
+
+  port:
+    $ref: /schemas/graph.yaml#/properties/port
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - clock-names
+  - avdd-supply
+  - dvdd-supply
+  - ovdd-supply
+  - port
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        imx296: camera-sensor@1a {
+            compatible = "sony,imx296";
+            reg = <0x1a>;
+
+            pinctrl-names = "default";
+            pinctrl-0 = <&camera_rear_default>;
+
+            clocks = <&gcc 90>;
+            clock-names = "inck";
+
+            avdd-supply = <&camera_vdda_3v3>;
+            dvdd-supply = <&camera_vddd_1v2>;
+            ovdd-supply = <&camera_vddo_1v8>;
+
+            reset-gpios = <&msmgpio 35 GPIO_ACTIVE_LOW>;
+
+            port {
+                imx296_ep: endpoint {
+                    remote-endpoint = <&csiphy0_ep>;
+                };
+            };
+        };
+    };
+
+...
diff --git a/Documentation/devicetree/bindings/media/rpivid_hevc.yaml b/Documentation/devicetree/bindings/media/rpivid_hevc.yaml
new file mode 100644 (file)
index 0000000..ce6b81a
--- /dev/null
@@ -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 (file)
index 0000000..68cc8eb
--- /dev/null
@@ -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 (file)
index 0000000..b76dc69
--- /dev/null
@@ -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>;
+};
+
index 11a6795..104768b 100644 (file)
@@ -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/brcmstb-pcie.txt b/Documentation/devicetree/bindings/pci/brcmstb-pcie.txt
new file mode 100644 (file)
index 0000000..a1a9ad5
--- /dev/null
@@ -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>;
+       };
index 3e56c1b..76dd7b0 100644 (file)
@@ -31,6 +31,7 @@ Optional properties:
 - inactive-delay-ms: Delay (default 100) to wait after driving gpio inactive
 - timeout-ms: Time to wait before asserting a WARN_ON(1). If nothing is
               specified, 3000 ms is used.
+- export : Export the GPIO line to the sysfs system
 
 Examples:
 
index 5ea00f8..8df2883 100644 (file)
@@ -91,6 +91,12 @@ properties:
       3000ms.
     default: 3000
 
+  cts-event-workaround:
+    description:
+      Enables the (otherwise vendor-specific) workaround for the
+      CTS-induced TX lockup.
+    type: boolean
+
 required:
   - compatible
   - reg
index 0d0b6d9..9136f08 100644 (file)
@@ -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/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
new file mode 100644 (file)
index 0000000..f8d3254
--- /dev/null
@@ -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.
index a867f71..c266587 100644 (file)
@@ -119,6 +119,8 @@ patternProperties:
     description: arcx Inc. / Archronix Inc.
   "^aries,.*":
     description: Aries Embedded GmbH
+  "^arducam,.*":
+    description: Arducam Technology co., Ltd.
   "^arm,.*":
     description: ARM Ltd.
   "^armadeus,.*":
@@ -173,6 +175,8 @@ patternProperties:
     description: Beckhoff Automation GmbH & Co. KG
   "^bitmain,.*":
     description: Bitmain Technologies
+  "^blokaslabs,.*":
+    description: Vilniaus Blokas UAB
   "^blutek,.*":
     description: BluTek Power
   "^boe,.*":
@@ -437,6 +441,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 (file)
index 0000000..5fa43e0
--- /dev/null
@@ -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/hwmon/emc2305.rst b/Documentation/hwmon/emc2305.rst
new file mode 100644 (file)
index 0000000..258da49
--- /dev/null
@@ -0,0 +1,40 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+Kernel driver emc2305
+=====================
+
+Supported chips:
+   Microchip EMC2305, EMC2303, EMC2302, EMC2301
+
+   Addresses scanned: I2C 0x27, 0x2c, 0x2d, 0x2e, 0x2f, 0x4c, 0x4d
+   Prefixes: 'emc2305'
+
+   Datasheet: Publicly available at the Microchip website :
+      https://www.microchip.com/en-us/product/EMC2305
+
+Description:
+-----------
+This driver implements support for Microchip EMC2301/2/3/5 RPM-based PWM Fan Controller.
+The EMC2305 Fan Controller supports up to 5 independently controlled PWM fan drives.
+Fan rotation speeds are reported in RPM.
+The driver supports the RPM-based PWM control to keep a fan at the desired speed.
+The driver provides the possibility to have one common PWM interface for all FANs
+or up to the maximum available or configured independent PWMs.
+
+The driver provides the following sysfs interfaces in hwmon subsystem:
+
+================= == ===================================================
+fan[1-5]_fault    RO files for tachometers TACH1-TACH5 fault indication
+fan[1-5]_input    RO files for tachometers TACH1-TACH5 input (in RPM)
+pwm[1-5]          RW file for fan[1-5] target duty cycle (0..255)
+================= == ===================================================
+
+sysfs interfaces in thermal subsystem:
+
+================= == ========================================================================
+cur_state         RW file for the current cooling state of the cooling device (0..max_state)
+max_state         RO file for the maximum cooling state of the cooling device
+================= == ========================================================================
+
+Configuration is possible via device tree:
+Documentation/devicetree/bindings/hwmon/microchip,emc2305.yaml
index 12e3c51..8f4d593 100644 (file)
@@ -35,6 +35,7 @@ For more details see the file COPYING in the source distribution of Linux.
        cx2341x-uapi
         hantro
        imx-uapi
+       bcm2835-isp
        max2175
        meye-uapi
        omap3isp-uapi
index 222cb99..78bfdfb 100644 (file)
@@ -33,3 +33,9 @@ are:
 
 -  An **interface link** is a point-to-point bidirectional control
    connection between a Linux Kernel interface and an entity.
+
+- An **ancillary link** is a point-to-point connection denoting that two
+  entities form a single logical unit. For example this could represent the
+  fact that a particular camera sensor and lens controller form a single
+  physical module, meaning this lens controller drives the lens for this
+  camera sensor.
\ No newline at end of file
index 0a26397..6074725 100644 (file)
@@ -412,14 +412,21 @@ must be set for every pad.
          is set by drivers and is read-only for applications.
 
     *  -  ``MEDIA_LNK_FL_LINK_TYPE``
-       -  This is a bitmask that defines the type of the link. Currently,
-         two types of links are supported:
+       -  This is a bitmask that defines the type of the link. The following
+         link types are currently supported:
 
          .. _MEDIA-LNK-FL-DATA-LINK:
 
-         ``MEDIA_LNK_FL_DATA_LINK`` if the link is between two pads
+         ``MEDIA_LNK_FL_DATA_LINK`` for links that represent a data connection
+     between two pads.
 
          .. _MEDIA-LNK-FL-INTERFACE-LINK:
 
-         ``MEDIA_LNK_FL_INTERFACE_LINK`` if the link is between an
-         interface and an entity
+         ``MEDIA_LNK_FL_INTERFACE_LINK`` for links that associate an entity to its
+     interface.
+
+         .. _MEDIA-LNK-FL-ANCILLARY-LINK:
+
+         ``MEDIA_LNK_FL_ANCILLARY_LINK`` for links that represent a physical
+     relationship between two entities. The link may or may not be ummutable, so
+     applications must not assume either case.
\ No newline at end of file
index 976d344..b699257 100644 (file)
@@ -2961,6 +2961,9 @@ enum v4l2_mpeg_video_hevc_size_of_length_field -
     * - __u32
       - ``data_bit_offset``
       - Offset (in bits) to the video data in the current slice data.
+    * - __u32
+      - ``slice_segment_addr``
+      -
     * - __u8
       - ``nal_unit_type``
       -
@@ -3181,6 +3184,47 @@ enum v4l2_mpeg_video_hevc_size_of_length_field -
 
     \normalsize
 
+``V4L2_CID_MPEG_VIDEO_HEVC_SCALING_MATRIX (struct)``
+    Specifies the scaling matrix (as extracted from the bitstream) for
+    the associated HEVC slice data. The bitstream parameters are
+    defined according to :ref:`hevc`, section 7.4.5 "Scaling list
+    data semantics". For further documentation, refer to the above
+    specification, unless there is an explicit comment stating
+    otherwise.
+
+    .. note::
+
+       This compound control is not yet part of the public kernel API and
+       it is expected to change.
+
+.. c:type:: v4l2_ctrl_hevc_scaling_matrix
+
+.. cssclass:: longtable
+
+.. flat-table:: struct v4l2_ctrl_hevc_scaling_matrix
+    :header-rows:  0
+    :stub-columns: 0
+    :widths:       1 1 2
+
+    * - __u8
+      - ``scaling_list_4x4[6][16]``
+      -
+    * - __u8
+      - ``scaling_list_8x8[6][64]``
+      -
+    * - __u8
+      - ``scaling_list_16x16[6][64]``
+      -
+    * - __u8
+      - ``scaling_list_32x32[2][64]``
+      -
+    * - __u8
+      - ``scaling_list_dc_coef_16x16[6]``
+      -
+    * - __u8
+      - ``scaling_list_dc_coef_32x32[2]``
+      -
+
 ``V4L2_CID_MPEG_VIDEO_HEVC_DECODE_MODE (enum)``
     Specifies the decoding mode to use. Currently exposes slice-based and
     frame-based decoding but new modes might be added later on.
index de43f5c..71f23f1 100644 (file)
@@ -72,3 +72,23 @@ Image Source Control IDs
     * - __u32
       - ``height``
       - Height of the area.
+
+``V4L2_CID_NOTIFY_GAINS (integer array)``
+    The sensor is notified what gains will be applied to the different
+    colour channels by subsequent processing (such as by an ISP). The
+    sensor is merely informed of these values in case it performs
+    processing that requires them, but it does not apply them itself to
+    the output pixels.
+
+    Currently it is defined only for Bayer sensors, and is an array
+    control taking 4 gain values, being the gains for each of the
+    Bayer channels. The gains are always in the order B, Gb, Gr and R,
+    irrespective of the exact Bayer order of the sensor itself.
+
+    The use of an array allows this control to be extended to sensors
+    with, for example, non-Bayer CFAs (colour filter arrays).
+
+    The units for the gain values are linear, with the default value
+    representing a gain of exactly 1.0. For example, if this default value
+    is reported as being (say) 128, then a value of 192 would represent
+    a gain of exactly 1.5.
index fff2535..c1dd92a 100644 (file)
@@ -12,9 +12,11 @@ These formats are used for the :ref:`metadata` interface only.
 .. toctree::
     :maxdepth: 1
 
+    pixfmt-meta-bcm2835-isp-stats
     pixfmt-meta-d4xx
     pixfmt-meta-intel-ipu3
     pixfmt-meta-rkisp1
+    pixfmt-meta-sensor-data
     pixfmt-meta-uvc
     pixfmt-meta-vsp1-hgo
     pixfmt-meta-vsp1-hgt
index 0ede399..e7d69b7 100644 (file)
@@ -195,8 +195,9 @@ Compressed Formats
        Metadata associated with the frame to decode is required to be passed
        through the following controls:
         ``V4L2_CID_MPEG_VIDEO_HEVC_SPS``,
-        ``V4L2_CID_MPEG_VIDEO_HEVC_PPS``, and
-        ``V4L2_CID_MPEG_VIDEO_HEVC_SLICE_PARAMS``.
+        ``V4L2_CID_MPEG_VIDEO_HEVC_PPS``,
+        ``V4L2_CID_MPEG_VIDEO_HEVC_SLICE_PARAMS``, and
+        ``V4L2_CID_MPEG_VIDEO_HEVC_SCALING_MATRIX``.
        See the :ref:`associated Codec Control IDs <v4l2-mpeg-hevc>`.
        Buffers associated with this pixel format must contain the appropriate
        number of macroblocks to decode a full corresponding frame.
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 (file)
index 0000000..f974774
--- /dev/null
@@ -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 (file)
index 0000000..4a67e20
--- /dev/null
@@ -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 (file)
index 0000000..196ca33
--- /dev/null
@@ -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-y12p.rst b/Documentation/userspace-api/media/v4l/pixfmt-y12p.rst
new file mode 100644 (file)
index 0000000..3704f91
--- /dev/null
@@ -0,0 +1,45 @@
+.. 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-Y12P:
+
+******************************
+V4L2_PIX_FMT_Y12P ('Y12P')
+******************************
+
+Grey-scale image as a MIPI RAW12 packed array
+
+
+Description
+===========
+
+This is a packed grey-scale image format with a depth of 12 bits per
+pixel. Two consecutive pixels are packed into 3 bytes. The first 2 bytes
+contain the 8 high order bits of the pixels, and the 3rd byte contains the 4
+least significants bits of each pixel, in the same order.
+
+**Byte Order.**
+Each cell is one byte.
+
+.. tabularcolumns:: |p{2.2cm}|p{1.2cm}|p{1.2cm}|p{3.1cm}|
+
+
+.. flat-table::
+    :header-rows:  0
+    :stub-columns: 0
+    :widths:       2 1 1 1
+
+
+    -  -  start + 0:
+       -  Y'\ :sub:`00high`
+       -  Y'\ :sub:`01high`
+       -  Y'\ :sub:`01low`\ (bits 7--4)
+
+          Y'\ :sub:`00low`\ (bits 3--0)
+
diff --git a/Documentation/userspace-api/media/v4l/pixfmt-y14p.rst b/Documentation/userspace-api/media/v4l/pixfmt-y14p.rst
new file mode 100644 (file)
index 0000000..27fe14c
--- /dev/null
@@ -0,0 +1,54 @@
+.. 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-Y14P:
+
+**************************
+V4L2_PIX_FMT_Y14P ('Y14P')
+**************************
+
+Grey-scale image as a MIPI RAW14 packed array
+
+
+Description
+===========
+
+This is a packed grey-scale image format with a depth of 14 bits per
+pixel. Every four consecutive samples are packed into seven bytes. Each
+of the first four bytes contain the eight high order bits of the pixels,
+and the three following bytes contains the six least significants bits of
+each pixel, in the same order.
+
+**Byte Order.**
+Each cell is one byte.
+
+.. tabularcolumns:: |p{1.8cm}|p{1.0cm}|p{1.0cm}|p{1.0cm}|p{1.1cm}|p{3.3cm}|p{3.3cm}|p{3.3cm}|
+
+.. flat-table::
+    :header-rows:  0
+    :stub-columns: 0
+    :widths:       2 1 1 1 1 3 3 3
+
+
+    -  -  start + 0:
+       -  Y'\ :sub:`00high`
+       -  Y'\ :sub:`01high`
+       -  Y'\ :sub:`02high`
+       -  Y'\ :sub:`03high`
+       -  Y'\ :sub:`01low bits 1--0`\ (bits 7--6)
+
+         Y'\ :sub:`00low bits 5--0`\ (bits 5--0)
+
+       -  Y'\ :sub:`02low bits 3--0`\ (bits 7--4)
+
+         Y'\ :sub:`01low bits 5--2`\ (bits 3--0)
+
+       -  Y'\ :sub:`03low bits 5--0`\ (bits 7--2)
+
+         Y'\ :sub:`02low bits 5--4`\ (bits 1--0)
index 090c091..34739b5 100644 (file)
@@ -470,6 +470,17 @@ number of lines as the luma plane.
       - Cb\ :sub:`33`
       - Cr\ :sub:`33`
 
+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
 ========================
index bd68588..6901f53 100644 (file)
@@ -624,6 +624,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
+      - 0x1020
+      -
+      -
+      -
+      -
+      -
+      -
+      -
+      -
+      -
+      - 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
@@ -912,6 +949,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_RGB666_1X18
+      - 0x101f
+      -
+      -
+      -
+      -
+      -
+      -
+      -
+      -
+      -
+      -
+      -
+      -
+      -
+      -
+      -
+      - 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
@@ -986,6 +1060,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
+      - 0x101e
+      -
+      -
+      -
+      -
+      -
+      -
+      -
+      -
+      -
+      - 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
@@ -7930,3 +8041,35 @@ The following table lists the existing metadata formats.
        both sides of the link and the bus format is a fixed
        metadata format that is not configurable from userspace.
        Width and height will be set to 0 for this format.
+
+
+.. _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>`_
+
index f9ecf62..819a70a 100644 (file)
@@ -607,6 +607,14 @@ See also the examples in :ref:`control`.
        ``V4L2_CTRL_FLAG_GRABBED`` flag when buffers are allocated or
        streaming is in progress since most drivers do not support changing
        the format in that case.
+    * - ``V4L2_CTRL_FLAG_DYNAMIC_ARRAY``
+      - 0x0800
+      - This control is a dynamically sized 1-dimensional array. It
+        behaves the same as a regular array, except that the number
+       of elements as reported by the ``elems`` field is between 1 and
+       ``dims[0]``. So setting the control with a differently sized
+       array will change the ``elems`` field when the control is
+       queried afterwards.
 
 Return Value
 ============
index 24b34cd..44589ac 100644 (file)
@@ -267,7 +267,28 @@ image.
     pixfmt-packed-yuv
     pixfmt-yuv-planar
     pixfmt-yuv-luma
+    pixfmt-y12p
+    pixfmt-y14p
     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
index edc3257..3501023 100644 (file)
@@ -1388,6 +1388,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 ARCHITECTED TIMER DRIVER
 M:     Mark Rutland <mark.rutland@arm.com>
 M:     Marc Zyngier <maz@kernel.org>
@@ -3553,6 +3569,29 @@ N:       bcm113*
 N:     bcm216*
 N:     kona
 
+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 BCM2835 CAMERA DRIVER
+M:     Raspberry Pi Kernel Maintenance <kernel-list@raspberrypi.com>
+L:     linux-media@vger.kernel.org
+S:     Maintained
+F:     drivers/media/platform/bcm2835/
+F:     Documentation/devicetree/bindings/media/brcm,bcm2835-unicam.yaml
+
+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 BCM47XX MIPS ARCHITECTURE
 M:     Hauke Mehrtens <hauke@hauke-m.de>
 M:     Rafał Miłecki <zajec5@gmail.com>
@@ -5956,6 +5995,14 @@ T:       git git://anongit.freedesktop.org/drm/drm-misc
 F:     Documentation/devicetree/bindings/display/multi-inno,mi0283qt.txt
 F:     drivers/gpu/drm/tiny/mi0283qt.c
 
+DRM DRIVER FOR MIPI DBI compatible panels
+M:     Noralf Trønnes <noralf@tronnes.org>
+S:     Maintained
+W:     https://github.com/notro/panel-mipi-dbi/wiki
+T:     git git://anongit.freedesktop.org/drm/drm-misc
+F:     Documentation/devicetree/bindings/display/panel/panel-mipi-dbi-spi.yaml
+F:     drivers/gpu/drm/tiny/panel-mipi-dbi.c
+
 DRM DRIVER FOR MSM ADRENO GPU
 M:     Rob Clark <robdclark@gmail.com>
 M:     Sean Paul <sean@poorly.run>
@@ -17470,6 +17517,15 @@ T:     git git://linuxtv.org/media_tree.git
 F:     Documentation/devicetree/bindings/media/i2c/imx290.txt
 F:     drivers/media/i2c/imx290.c
 
+SONY IMX296 SENSOR DRIVER
+M:     Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+M:     Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
+L:     linux-media@vger.kernel.org
+S:     Maintained
+T:     git git://linuxtv.org/media_tree.git
+F:     Documentation/devicetree/bindings/media/i2c/sony,imx296.yaml
+F:     drivers/media/i2c/imx296.c
+
 SONY IMX319 SENSOR DRIVER
 M:     Bingbu Cao <bingbu.cao@intel.com>
 L:     linux-media@vger.kernel.org
@@ -17511,6 +17567,23 @@ T:     git git://linuxtv.org/media_tree.git
 F:     Documentation/devicetree/bindings/media/i2c/sony,imx412.yaml
 F:     drivers/media/i2c/imx412.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 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 MEMORYSTICK SUBSYSTEM
 M:     Maxim Levitsky <maximlevitsky@gmail.com>
 M:     Alex Dubov <oakad@yahoo.com>
index 27ca1ca..9ccee64 100644 (file)
@@ -1,4 +1,25 @@
 # SPDX-License-Identifier: GPL-2.0
+
+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 \
+       bcm2711-rpi-4-b.dtb \
+       bcm2711-rpi-400.dtb \
+       bcm2709-rpi-cm2.dtb \
+       bcm2710-rpi-cm3.dtb \
+       bcm2711-rpi-cm4.dtb \
+       bcm2711-rpi-cm4s.dtb
+
 dtb-$(CONFIG_ARCH_ALPINE) += \
        alpine-db.dtb
 dtb-$(CONFIG_MACH_ARTPEC6) += \
@@ -1499,3 +1520,13 @@ dtb-$(CONFIG_ARCH_ASPEED) += \
        aspeed-bmc-portwell-neptune.dtb \
        aspeed-bmc-quanta-q71l.dtb \
        aspeed-bmc-supermicro-x11spi.dtb
+
+targets += dtbs dtbs_install
+targets += $(dtb-y)
+
+subdir-y       := overlays
+
+# Enable fixups to support overlays on BCM2835 platforms
+ifeq ($(CONFIG_ARCH_BCM2835),y)
+       DTC_FLAGS += -@
+endif
diff --git a/arch/arm/boot/dts/bcm2708-rpi-b-plus.dts b/arch/arm/boot/dts/bcm2708-rpi-b-plus.dts
new file mode 100644 (file)
index 0000000..ea2d667
--- /dev/null
@@ -0,0 +1,197 @@
+/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"
+
+/ {
+       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",
+                         "SDA1",
+                         "SCL1",
+                         "GPIO_GCLK",
+                         "GPIO5",
+                         "GPIO6",
+                         "SPI_CE1_N",
+                         "SPI_CE0_N",
+                         "SPI_MISO",
+                         "SPI_MOSI",
+                         "SPI_SCLK",
+                         "GPIO12",
+                         "GPIO13",
+                         /* Serial port */
+                         "TXD0",
+                         "RXD0",
+                         "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>;
+       };
+};
+
+&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>;
+};
+
+&leds {
+       act_led: led-act {
+               label = "led0";
+               linux,default-trigger = "mmc0";
+               gpios = <&gpio 47 0>;
+       };
+
+       pwr_led: led-pwr {
+               label = "led1";
+               linux,default-trigger = "input";
+               gpios = <&gpio 35 0>;
+       };
+};
+
+&hdmi {
+       hpd-gpios = <&gpio 46 GPIO_ACTIVE_LOW>;
+};
+
+&audio {
+       pinctrl-names = "default";
+       pinctrl-0 = <&audio_pins>;
+};
+
+&cam1_reg {
+       gpio = <&gpio 41 GPIO_ACTIVE_HIGH>;
+};
+
+cam0_reg: &cam_dummy_reg {
+};
+
+/ {
+       __overrides__ {
+               act_led_gpio = <&act_led>,"gpios:4";
+               act_led_activelow = <&act_led>,"gpios:8";
+               act_led_trigger = <&act_led>,"linux,default-trigger";
+
+               pwr_led_gpio = <&pwr_led>,"gpios:4";
+               pwr_led_activelow = <&pwr_led>,"gpios:8";
+               pwr_led_trigger = <&pwr_led>,"linux,default-trigger";
+       };
+};
diff --git a/arch/arm/boot/dts/bcm2708-rpi-b-rev1.dts b/arch/arm/boot/dts/bcm2708-rpi-b-rev1.dts
new file mode 100644 (file)
index 0000000..9bea710
--- /dev/null
@@ -0,0 +1,205 @@
+/dts-v1/;
+
+#include "bcm2708.dtsi"
+#include "bcm2708-rpi.dtsi"
+#include "bcm283x-rpi-smsc9512.dtsi"
+#include "bcm283x-rpi-csi1-2lane.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>;
+       };
+};
+
+&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>;
+};
+
+&leds {
+       act_led: led-act {
+               label = "led0";
+               linux,default-trigger = "mmc0";
+               gpios = <&gpio 16 1>;
+       };
+};
+
+&hdmi {
+       hpd-gpios = <&gpio 46 GPIO_ACTIVE_HIGH>;
+};
+
+&audio {
+       pinctrl-names = "default";
+       pinctrl-0 = <&audio_pins>;
+};
+
+&cam1_reg {
+       gpio = <&gpio 27 GPIO_ACTIVE_HIGH>;
+};
+
+cam0_reg: &cam_dummy_reg {
+};
+
+/ {
+       __overrides__ {
+               act_led_gpio = <&act_led>,"gpios:4";
+               act_led_activelow = <&act_led>,"gpios:8";
+               act_led_trigger = <&act_led>,"linux,default-trigger";
+       };
+};
diff --git a/arch/arm/boot/dts/bcm2708-rpi-b.dts b/arch/arm/boot/dts/bcm2708-rpi-b.dts
new file mode 100644 (file)
index 0000000..ab1f3d2
--- /dev/null
@@ -0,0 +1,187 @@
+/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"
+
+/ {
+       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>;
+       };
+};
+
+&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>;
+};
+
+&leds {
+       act_led: led-act {
+               label = "led0";
+               linux,default-trigger = "mmc0";
+               gpios = <&gpio 16 1>;
+       };
+};
+
+&hdmi {
+       hpd-gpios = <&gpio 46 GPIO_ACTIVE_HIGH>;
+};
+
+&audio {
+       pinctrl-names = "default";
+       pinctrl-0 = <&audio_pins>;
+};
+
+&cam1_reg {
+       gpio = <&gpio 21 GPIO_ACTIVE_HIGH>;
+};
+
+cam0_reg: &cam_dummy_reg {
+};
+
+/ {
+       __overrides__ {
+               act_led_gpio = <&act_led>,"gpios:4";
+               act_led_activelow = <&act_led>,"gpios:8";
+               act_led_trigger = <&act_led>,"linux,default-trigger";
+       };
+};
diff --git a/arch/arm/boot/dts/bcm2708-rpi-bt.dtsi b/arch/arm/boot/dts/bcm2708-rpi-bt.dtsi
new file mode 100644 (file)
index 0000000..a18f80a
--- /dev/null
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: GPL-2.0
+
+&uart0 {
+       bt: bluetooth {
+               compatible = "brcm,bcm43438-bt";
+               max-speed = <3000000>;
+               shutdown-gpios = <&gpio 45 GPIO_ACTIVE_HIGH>;
+               status = "disabled";
+       };
+};
+
+&uart1 {
+       minibt: bluetooth {
+               compatible = "brcm,bcm43438-bt";
+               max-speed = <460800>;
+               shutdown-gpios = <&gpio 45 GPIO_ACTIVE_HIGH>;
+               status = "disabled";
+       };
+};
+
+/ {
+       __overrides__ {
+               krnbt = <&bt>,"status";
+               krnbt_baudrate = <&bt>,"max-speed:0";
+       };
+};
diff --git a/arch/arm/boot/dts/bcm2708-rpi-cm.dts b/arch/arm/boot/dts/bcm2708-rpi-cm.dts
new file mode 100644 (file)
index 0000000..3634f67
--- /dev/null
@@ -0,0 +1,171 @@
+/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 2 GPIO_ACTIVE_HIGH>;
+       status = "disabled";
+};
+
+cam0_reg: &cam0_regulator {
+       gpio = <&gpio 30 GPIO_ACTIVE_HIGH>;
+};
+
+&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>;
+};
+
+&audio {
+       pinctrl-names = "default";
+       pinctrl-0 = <&audio_pins>;
+};
+
+&hdmi {
+       hpd-gpios = <&gpio 46 GPIO_ACTIVE_HIGH>;
+};
diff --git a/arch/arm/boot/dts/bcm2708-rpi-cm.dtsi b/arch/arm/boot/dts/bcm2708-rpi-cm.dtsi
new file mode 100644 (file)
index 0000000..c7845d2
--- /dev/null
@@ -0,0 +1,22 @@
+#include "bcm2708.dtsi"
+#include "bcm2708-rpi.dtsi"
+
+&leds {
+       act_led: led-act {
+               label = "led0";
+               linux,default-trigger = "mmc0";
+               gpios = <&gpio 47 0>;
+       };
+};
+
+/ {
+       __overrides__ {
+               act_led_gpio = <&act_led>,"gpios:4";
+               act_led_activelow = <&act_led>,"gpios:8";
+               act_led_trigger = <&act_led>,"linux,default-trigger";
+               cam0_reg = <&cam0_reg>,"status";
+               cam0_reg_gpio = <&cam0_reg>,"gpio:4";
+               cam1_reg = <&cam1_reg>,"status";
+               cam1_reg_gpio = <&cam1_reg>,"gpio:4";
+       };
+};
diff --git a/arch/arm/boot/dts/bcm2708-rpi-zero-w.dts b/arch/arm/boot/dts/bcm2708-rpi-zero-w.dts
new file mode 100644 (file)
index 0000000..b49c3cb
--- /dev/null
@@ -0,0 +1,245 @@
+/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"
+
+/ {
+       compatible = "raspberrypi,model-zero-w", "brcm,bcm2835";
+       model = "Raspberry Pi Zero W";
+
+       chosen {
+               bootargs = "coherent_pool=1M 8250.nr_uarts=1 snd_bcm2835.enable_compat_alsa=0 snd_bcm2835.enable_hdmi=1";
+       };
+
+       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",
+                         "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",
+                         "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;
+       };
+
+       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>;
+};
+
+&leds {
+       act_led: led-act {
+               label = "led0";
+               linux,default-trigger = "actpwr";
+               gpios = <&gpio 47 GPIO_ACTIVE_LOW>;
+       };
+};
+
+&hdmi {
+       hpd-gpios = <&gpio 46 GPIO_ACTIVE_LOW>;
+};
+
+&audio {
+       pinctrl-names = "default";
+       pinctrl-0 = <&audio_pins>;
+       brcm,disable-headphones = <1>;
+};
+
+&cam1_reg {
+       gpio = <&gpio 44 GPIO_ACTIVE_HIGH>;
+};
+
+cam0_reg: &cam_dummy_reg {
+};
+
+/ {
+       __overrides__ {
+               act_led_gpio = <&act_led>,"gpios:4";
+               act_led_activelow = <&act_led>,"gpios:8";
+               act_led_trigger = <&act_led>,"linux,default-trigger";
+       };
+};
diff --git a/arch/arm/boot/dts/bcm2708-rpi-zero.dts b/arch/arm/boot/dts/bcm2708-rpi-zero.dts
new file mode 100644 (file)
index 0000000..f888478
--- /dev/null
@@ -0,0 +1,190 @@
+/dts-v1/;
+
+#include "bcm2708.dtsi"
+#include "bcm2708-rpi.dtsi"
+#include "bcm283x-rpi-csi1-2lane.dtsi"
+#include "bcm283x-rpi-i2c0mux_0_28.dtsi"
+
+/ {
+       compatible = "raspberrypi,model-zero", "brcm,bcm2835";
+       model = "Raspberry Pi Zero";
+
+       chosen {
+               bootargs = "coherent_pool=1M snd_bcm2835.enable_compat_alsa=0 snd_bcm2835.enable_hdmi=1";
+       };
+};
+
+&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",
+                         "SDA1",
+                         "SCL1",
+                         "GPIO_GCLK",
+                         "GPIO5",
+                         "GPIO6",
+                         "SPI_CE1_N",
+                         "SPI_CE0_N",
+                         "SPI_MISO",
+                         "SPI_MOSI",
+                         "SPI_SCLK",
+                         "GPIO12",
+                         "GPIO13",
+                         /* Serial port */
+                         "TXD0",
+                         "RXD0",
+                         "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>;
+};
+
+&leds {
+       act_led: led-act {
+               label = "led0";
+               linux,default-trigger = "actpwr";
+               gpios = <&gpio 47 GPIO_ACTIVE_LOW>;
+       };
+};
+
+&hdmi {
+       hpd-gpios = <&gpio 46 GPIO_ACTIVE_LOW>;
+};
+
+&audio {
+       pinctrl-names = "default";
+       pinctrl-0 = <&audio_pins>;
+       brcm,disable-headphones = <1>;
+};
+
+&cam1_reg {
+       gpio = <&gpio 41 GPIO_ACTIVE_HIGH>;
+};
+
+cam0_reg: &cam_dummy_reg {
+};
+
+/ {
+       __overrides__ {
+               act_led_gpio = <&act_led>,"gpios:4";
+               act_led_activelow = <&act_led>,"gpios:8";
+               act_led_trigger = <&act_led>,"linux,default-trigger";
+       };
+};
diff --git a/arch/arm/boot/dts/bcm2708-rpi.dtsi b/arch/arm/boot/dts/bcm2708-rpi.dtsi
new file mode 100644 (file)
index 0000000..e2458b1
--- /dev/null
@@ -0,0 +1,36 @@
+/* Downstream modifications common to bcm2835, bcm2836, bcm2837 */
+
+#include "bcm2835-rpi.dtsi"
+#include "bcm270x-rpi.dtsi"
+
+/ {
+       memory@0 {
+               device_type = "memory";
+               reg = <0x0 0x0>;
+       };
+
+       aliases {
+               i2c2 = &i2c2;
+       };
+
+       __overrides__ {
+               i2c2_iknowwhatimdoing = <&i2c2>,"status";
+               i2c2_baudrate = <&i2c2>,"clock-frequency:0";
+               sd_poll_once = <&sdhost>,"non-removable?";
+       };
+};
+
+&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/bcm2708.dtsi b/arch/arm/boot/dts/bcm2708.dtsi
new file mode 100644 (file)
index 0000000..36ec498
--- /dev/null
@@ -0,0 +1,12 @@
+#include "bcm2835.dtsi"
+#include "bcm270x.dtsi"
+
+/ {
+       __overrides__ {
+               arm_freq;
+       };
+};
+
+&vc4 {
+       status = "disabled";
+};
diff --git a/arch/arm/boot/dts/bcm2709-rpi-2-b.dts b/arch/arm/boot/dts/bcm2709-rpi-2-b.dts
new file mode 100644 (file)
index 0000000..87eeacd
--- /dev/null
@@ -0,0 +1,197 @@
+/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"
+
+/ {
+       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",
+                         "SDA1",
+                         "SCL1",
+                         "GPIO_GCLK",
+                         "GPIO5",
+                         "GPIO6",
+                         "SPI_CE1_N",
+                         "SPI_CE0_N",
+                         "SPI_MISO",
+                         "SPI_MOSI",
+                         "SPI_SCLK",
+                         "GPIO12",
+                         "GPIO13",
+                         /* Serial port */
+                         "TXD0",
+                         "RXD0",
+                         "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>;
+       };
+};
+
+&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>;
+};
+
+&leds {
+       act_led: led-act {
+               label = "led0";
+               linux,default-trigger = "mmc0";
+               gpios = <&gpio 47 0>;
+       };
+
+       pwr_led: led-pwr {
+               label = "led1";
+               linux,default-trigger = "input";
+               gpios = <&gpio 35 0>;
+       };
+};
+
+&hdmi {
+       hpd-gpios = <&gpio 46 GPIO_ACTIVE_LOW>;
+};
+
+&audio {
+       pinctrl-names = "default";
+       pinctrl-0 = <&audio_pins>;
+};
+
+&cam1_reg {
+       gpio = <&gpio 41 GPIO_ACTIVE_HIGH>;
+};
+
+cam0_reg: &cam_dummy_reg {
+};
+
+/ {
+       __overrides__ {
+               act_led_gpio = <&act_led>,"gpios:4";
+               act_led_activelow = <&act_led>,"gpios:8";
+               act_led_trigger = <&act_led>,"linux,default-trigger";
+
+               pwr_led_gpio = <&pwr_led>,"gpios:4";
+               pwr_led_activelow = <&pwr_led>,"gpios:8";
+               pwr_led_trigger = <&pwr_led>,"linux,default-trigger";
+       };
+};
diff --git a/arch/arm/boot/dts/bcm2709-rpi-cm2.dts b/arch/arm/boot/dts/bcm2709-rpi-cm2.dts
new file mode 100644 (file)
index 0000000..17fd77b
--- /dev/null
@@ -0,0 +1,219 @@
+/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"
+
+/ {
+       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>;
+};
+
+&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;
+       };
+};
+
+&soc {
+       virtgpio: virtgpio {
+               compatible = "brcm,bcm2835-virtgpio";
+               gpio-controller;
+               #gpio-cells = <2>;
+               firmware = <&firmware>;
+               status = "okay";
+       };
+
+};
+
+&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";
+       };
+};
+
+&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>;
+};
+
+&leds {
+       act_led: led-act {
+               label = "led0";
+               linux,default-trigger = "mmc0";
+               gpios = <&virtgpio 0 0>;
+       };
+};
+
+&hdmi {
+       hpd-gpios = <&expgpio 0 GPIO_ACTIVE_LOW>;
+};
+
+&audio {
+       pinctrl-names = "default";
+       pinctrl-0 = <&audio_pins>;
+};
+
+/ {
+       __overrides__ {
+               act_led_gpio = <&act_led>,"gpios:4";
+               act_led_activelow = <&act_led>,"gpios:8";
+               act_led_trigger = <&act_led>,"linux,default-trigger";
+               cam0_reg = <&cam0_reg>,"status";
+               cam0_reg_gpio = <&cam0_reg>,"gpio:4";
+               cam1_reg = <&cam1_reg>,"status";
+               cam1_reg_gpio = <&cam1_reg>,"gpio:4";
+       };
+};
diff --git a/arch/arm/boot/dts/bcm2709-rpi.dtsi b/arch/arm/boot/dts/bcm2709-rpi.dtsi
new file mode 100644 (file)
index 0000000..babfa41
--- /dev/null
@@ -0,0 +1,5 @@
+#include "bcm2708-rpi.dtsi"
+
+&vchiq {
+       compatible = "brcm,bcm2836-vchiq", "brcm,bcm2835-vchiq";
+};
diff --git a/arch/arm/boot/dts/bcm2709.dtsi b/arch/arm/boot/dts/bcm2709.dtsi
new file mode 100644 (file)
index 0000000..68eafc1
--- /dev/null
@@ -0,0 +1,22 @@
+#include "bcm2836.dtsi"
+#include "bcm270x.dtsi"
+
+/ {
+       soc {
+               ranges = <0x7e000000 0x3f000000 0x01000000>,
+                        <0x40000000 0x40000000 0x00040000>;
+
+               /delete-node/ timer@7e003000;
+       };
+
+       __overrides__ {
+               arm_freq = <&v7_cpu0>, "clock-frequency:0",
+                          <&v7_cpu1>, "clock-frequency:0",
+                          <&v7_cpu2>, "clock-frequency:0",
+                          <&v7_cpu3>, "clock-frequency:0";
+       };
+};
+
+&vc4 {
+       status = "disabled";
+};
diff --git a/arch/arm/boot/dts/bcm270x-rpi.dtsi b/arch/arm/boot/dts/bcm270x-rpi.dtsi
new file mode 100644 (file)
index 0000000..be9a24c
--- /dev/null
@@ -0,0 +1,156 @@
+/* Downstream modifications to bcm2835-rpi.dtsi */
+
+/ {
+       aliases {
+               audio = &audio;
+               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;
+               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";
+       };
+
+       leds: leds {
+               compatible = "gpio-leds";
+       };
+
+       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";
+               i2c0_baudrate = <&i2c0if>,"clock-frequency:0";
+               i2c1_baudrate = <&i2c1>,"clock-frequency:0";
+
+               audio = <&audio>,"status";
+               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";
+       };
+};
+
+&uart0 {
+       skip-init;
+};
+
+&uart1 {
+       skip-init;
+};
+
+&txp {
+       status = "disabled";
+};
+
+&i2c0if {
+       status = "disabled";
+};
+
+&i2c0mux {
+       status = "disabled";
+};
+
+&i2c1 {
+       status = "disabled";
+};
+
+&clocks {
+       firmware = <&firmware>;
+};
+
+&sdhci {
+       pinctrl-names = "default";
+       pinctrl-0 = <&emmc_gpio48>;
+       bus-width = <4>;
+};
+
+&cpu_thermal {
+       /delete-node/ trips;
+       /delete-node/ cooling-maps;
+};
+
+&vec {
+       status = "disabled";
+};
+
+&vchiq {
+       /* Onboard audio */
+       audio: bcm2835_audio {
+               compatible = "brcm,bcm2835-audio";
+               brcm,firmware = <&firmware>;
+               brcm,pwm-channels = <8>;
+               status = "disabled";
+       };
+};
+
+&firmware {
+       vcio: vcio {
+               compatible = "raspberrypi,vcio";
+       };
+};
diff --git a/arch/arm/boot/dts/bcm270x.dtsi b/arch/arm/boot/dts/bcm270x.dtsi
new file mode 100644 (file)
index 0000000..a5cabb5
--- /dev/null
@@ -0,0 +1,272 @@
+/* Downstream bcm283x.dtsi diff */
+#include <dt-bindings/power/raspberrypi-power.h>
+
+/ {
+       chosen {
+               bootargs = "coherent_pool=1M snd_bcm2835.enable_compat_alsa=0 snd_bcm2835.enable_hdmi=1";
+               /delete-property/ stdout-path;
+       };
+
+       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";
+               };
+
+               csi0: csi@7e800000 {
+                       compatible = "brcm,bcm2835-unicam";
+                       reg = <0x7e800000 0x800>,
+                             <0x7e802000 0x4>;
+                       interrupts = <2 6>;
+                       clocks = <&clocks BCM2835_CLOCK_CAM0>,
+                                <&firmware_clocks 4>;
+                       clock-names = "lp", "vpu";
+                       power-domains = <&power RPI_POWER_DOMAIN_UNICAM0>;
+                       #address-cells = <1>;
+                       #size-cells = <0>;
+                       #clock-cells = <1>;
+                       status = "disabled";
+               };
+
+               csi1: csi@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";
+                       power-domains = <&power RPI_POWER_DOMAIN_UNICAM1>;
+                       #address-cells = <1>;
+                       #size-cells = <0>;
+                       #clock-cells = <1>;
+                       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";
+               };
+       };
+
+       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/bcm2710-rpi-2-b.dts b/arch/arm/boot/dts/bcm2710-rpi-2-b.dts
new file mode 100644 (file)
index 0000000..6ff2373
--- /dev/null
@@ -0,0 +1,197 @@
+/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"
+
+/ {
+       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",
+                         "SDA1",
+                         "SCL1",
+                         "GPIO_GCLK",
+                         "GPIO5",
+                         "GPIO6",
+                         "SPI_CE1_N",
+                         "SPI_CE0_N",
+                         "SPI_MISO",
+                         "SPI_MOSI",
+                         "SPI_SCLK",
+                         "GPIO12",
+                         "GPIO13",
+                         /* Serial port */
+                         "TXD0",
+                         "RXD0",
+                         "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>;
+       };
+};
+
+&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>;
+};
+
+&leds {
+       act_led: led-act {
+               label = "led0";
+               linux,default-trigger = "mmc0";
+               gpios = <&gpio 47 0>;
+       };
+
+       pwr_led: led-pwr {
+               label = "led1";
+               linux,default-trigger = "input";
+               gpios = <&gpio 35 0>;
+       };
+};
+
+&hdmi {
+       hpd-gpios = <&gpio 46 GPIO_ACTIVE_LOW>;
+};
+
+&audio {
+       pinctrl-names = "default";
+       pinctrl-0 = <&audio_pins>;
+};
+
+&cam1_reg {
+       gpio = <&gpio 41 GPIO_ACTIVE_HIGH>;
+};
+
+cam0_reg: &cam_dummy_reg {
+};
+
+/ {
+       __overrides__ {
+               act_led_gpio = <&act_led>,"gpios:4";
+               act_led_activelow = <&act_led>,"gpios:8";
+               act_led_trigger = <&act_led>,"linux,default-trigger";
+
+               pwr_led_gpio = <&pwr_led>,"gpios:4";
+               pwr_led_activelow = <&pwr_led>,"gpios:8";
+               pwr_led_trigger = <&pwr_led>,"linux,default-trigger";
+       };
+};
diff --git a/arch/arm/boot/dts/bcm2710-rpi-3-b-plus.dts b/arch/arm/boot/dts/bcm2710-rpi-3-b-plus.dts
new file mode 100644 (file)
index 0000000..5f248fb
--- /dev/null
@@ -0,0 +1,286 @@
+/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"
+
+/ {
+       compatible = "raspberrypi,3-model-b-plus", "brcm,bcm2837";
+       model = "Raspberry Pi 3 Model B+";
+
+       chosen {
+               bootargs = "coherent_pool=1M 8250.nr_uarts=1 snd_bcm2835.enable_compat_alsa=0 snd_bcm2835.enable_hdmi=1";
+       };
+
+       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",
+                         "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",
+                         "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;
+       };
+
+       audio_pins: audio_pins {
+               brcm,pins = <40 41>;
+               brcm,function = <4>;
+       };
+};
+
+&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>;
+};
+
+&leds {
+       act_led: led-act {
+               label = "led0";
+               linux,default-trigger = "mmc0";
+               gpios = <&gpio 29 0>;
+       };
+
+       pwr_led: led-pwr {
+               label = "led1";
+               linux,default-trigger = "default-on";
+               gpios = <&expgpio 2 GPIO_ACTIVE_LOW>;
+       };
+};
+
+&hdmi {
+       hpd-gpios = <&gpio 28 GPIO_ACTIVE_LOW>;
+};
+
+&audio {
+       pinctrl-names = "default";
+       pinctrl-0 = <&audio_pins>;
+};
+
+&eth_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 {
+};
+
+/ {
+       __overrides__ {
+               act_led_gpio = <&act_led>,"gpios:4";
+               act_led_activelow = <&act_led>,"gpios:8";
+               act_led_trigger = <&act_led>,"linux,default-trigger";
+
+               pwr_led_gpio = <&pwr_led>,"gpios:4";
+               pwr_led_activelow = <&pwr_led>,"gpios:8";
+               pwr_led_trigger = <&pwr_led>,"linux,default-trigger";
+
+               eee = <&eth_phy>,"microchip,eee-enabled?";
+               tx_lpi_timer = <&eth_phy>,"microchip,tx-lpi-timer:0";
+               eth_led0 = <&eth_phy>,"microchip,led-modes:0";
+               eth_led1 = <&eth_phy>,"microchip,led-modes:4";
+               eth_downshift_after = <&eth_phy>,"microchip,downshift-after:0";
+               eth_max_speed = <&eth_phy>,"max-speed:0";
+       };
+};
diff --git a/arch/arm/boot/dts/bcm2710-rpi-3-b.dts b/arch/arm/boot/dts/bcm2710-rpi-3-b.dts
new file mode 100644 (file)
index 0000000..10485ad
--- /dev/null
@@ -0,0 +1,288 @@
+/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"
+
+/ {
+       compatible = "raspberrypi,3-model-b", "brcm,bcm2837";
+       model = "Raspberry Pi 3 Model B";
+
+       chosen {
+               bootargs = "coherent_pool=1M 8250.nr_uarts=1 snd_bcm2835.enable_compat_alsa=0 snd_bcm2835.enable_hdmi=1";
+       };
+
+       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",
+                         "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",
+                         "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;
+       };
+
+       audio_pins: audio_pins {
+               brcm,pins = <40 41>;
+               brcm,function = <4>;
+       };
+};
+
+&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";
+       };
+};
+
+&soc {
+       virtgpio: virtgpio {
+               compatible = "brcm,bcm2835-virtgpio";
+               gpio-controller;
+               #gpio-cells = <2>;
+               firmware = <&firmware>;
+               status = "okay";
+       };
+
+};
+
+&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";
+       };
+};
+
+&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>;
+};
+
+&leds {
+       act_led: led-act {
+               label = "led0";
+               linux,default-trigger = "mmc0";
+               gpios = <&virtgpio 0 0>;
+       };
+
+       pwr_led: led-pwr {
+               label = "led1";
+               linux,default-trigger = "input";
+               gpios = <&expgpio 7 0>;
+       };
+};
+
+&hdmi {
+       hpd-gpios = <&expgpio 4 GPIO_ACTIVE_LOW>;
+};
+
+&audio {
+       pinctrl-names = "default";
+       pinctrl-0 = <&audio_pins>;
+};
+
+&cam1_reg {
+       gpio = <&expgpio 5 GPIO_ACTIVE_HIGH>;
+};
+
+cam0_reg: &cam_dummy_reg {
+};
+
+/ {
+       __overrides__ {
+               act_led_gpio = <&act_led>,"gpios:4";
+               act_led_activelow = <&act_led>,"gpios:8";
+               act_led_trigger = <&act_led>,"linux,default-trigger";
+
+               pwr_led_gpio = <&pwr_led>,"gpios:4";
+               pwr_led_activelow = <&pwr_led>,"gpios:8";
+               pwr_led_trigger = <&pwr_led>,"linux,default-trigger";
+       };
+};
diff --git a/arch/arm/boot/dts/bcm2710-rpi-cm3.dts b/arch/arm/boot/dts/bcm2710-rpi-cm3.dts
new file mode 100644 (file)
index 0000000..f7f1833
--- /dev/null
@@ -0,0 +1,218 @@
+/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"
+/ {
+       compatible = "raspberrypi,3-compute-module", "brcm,bcm2837";
+       model = "Raspberry Pi Compute Module 3";
+};
+
+&cam1_reg {
+       gpio = <&gpio 2 GPIO_ACTIVE_HIGH>;
+       status = "disabled";
+};
+
+cam0_reg: &cam0_regulator {
+       gpio = <&gpio 30 GPIO_ACTIVE_HIGH>;
+};
+
+&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;
+       };
+};
+
+&soc {
+       virtgpio: virtgpio {
+               compatible = "brcm,bcm2835-virtgpio";
+               gpio-controller;
+               #gpio-cells = <2>;
+               firmware = <&firmware>;
+               status = "okay";
+       };
+
+};
+
+&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";
+       };
+};
+
+&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>;
+};
+
+&leds {
+       act_led: led-act {
+               label = "led0";
+               linux,default-trigger = "mmc0";
+               gpios = <&virtgpio 0 0>;
+       };
+};
+
+&hdmi {
+       hpd-gpios = <&expgpio 0 GPIO_ACTIVE_LOW>;
+};
+
+&audio {
+       pinctrl-names = "default";
+       pinctrl-0 = <&audio_pins>;
+};
+
+/ {
+       __overrides__ {
+               act_led_gpio = <&act_led>,"gpios:4";
+               act_led_activelow = <&act_led>,"gpios:8";
+               act_led_trigger = <&act_led>,"linux,default-trigger";
+               cam0_reg = <&cam0_reg>,"status";
+               cam0_reg_gpio = <&cam0_reg>,"gpio:4";
+               cam1_reg = <&cam1_reg>,"status";
+               cam1_reg_gpio = <&cam1_reg>,"gpio:4";
+       };
+};
diff --git a/arch/arm/boot/dts/bcm2710-rpi-zero-2-w.dts b/arch/arm/boot/dts/bcm2710-rpi-zero-2-w.dts
new file mode 100644 (file)
index 0000000..c0d7bd7
--- /dev/null
@@ -0,0 +1,266 @@
+/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"
+
+/ {
+       compatible = "raspberrypi,model-zero-2-w", "brcm,bcm2837";
+       model = "Raspberry Pi Zero 2 W";
+
+       chosen {
+               bootargs = "coherent_pool=1M 8250.nr_uarts=1 snd_bcm2835.enable_compat_alsa=0 snd_bcm2835.enable_hdmi=1";
+       };
+
+       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",
+                         "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",
+                         "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;
+       };
+
+       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";
+
+               firmwares {
+                       fw_43436p {
+                               chipid = <43430>;
+                               revmask = <4>;
+                               fw_base = "brcm/brcmfmac43436-sdio";
+                       };
+                       fw_43436s {
+                               chipid = <43430>;
+                               revmask = <2>;
+                               fw_base = "brcm/brcmfmac43436s-sdio";
+                       };
+               };
+       };
+};
+
+&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>;
+};
+
+&leds {
+       act_led: led-act {
+               label = "led0";
+               linux,default-trigger = "actpwr";
+               gpios = <&gpio 29 GPIO_ACTIVE_LOW>;
+       };
+};
+
+&hdmi {
+       hpd-gpios = <&gpio 28 GPIO_ACTIVE_LOW>;
+};
+
+&audio {
+       pinctrl-names = "default";
+       pinctrl-0 = <&audio_pins>;
+       brcm,disable-headphones = <1>;
+};
+
+&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 {
+};
+
+/ {
+       __overrides__ {
+               act_led_gpio = <&act_led>,"gpios:4";
+               act_led_activelow = <&act_led>,"gpios:8";
+               act_led_trigger = <&act_led>,"linux,default-trigger";
+       };
+};
diff --git a/arch/arm/boot/dts/bcm2710-rpi-zero-2.dts b/arch/arm/boot/dts/bcm2710-rpi-zero-2.dts
new file mode 100644 (file)
index 0000000..daa12bd
--- /dev/null
@@ -0,0 +1 @@
+#include "bcm2710-rpi-zero-2-w.dts"
diff --git a/arch/arm/boot/dts/bcm2710.dtsi b/arch/arm/boot/dts/bcm2710.dtsi
new file mode 100644 (file)
index 0000000..e7e5c91
--- /dev/null
@@ -0,0 +1,25 @@
+#include "bcm2837.dtsi"
+#include "bcm270x.dtsi"
+
+/ {
+       compatible = "brcm,bcm2837", "brcm,bcm2836";
+
+       arm-pmu {
+               compatible = "arm,cortex-a53-pmu", "arm,cortex-a7-pmu";
+       };
+
+       soc {
+               /delete-node/ timer@7e003000;
+       };
+
+       __overrides__ {
+               arm_freq = <&cpu0>, "clock-frequency:0",
+                      <&cpu1>, "clock-frequency:0",
+                      <&cpu2>, "clock-frequency:0",
+                      <&cpu3>, "clock-frequency:0";
+       };
+};
+
+&vc4 {
+       status = "disabled";
+};
index 72ce80f..8c0ab39 100644 (file)
@@ -2,7 +2,7 @@
 /dts-v1/;
 #include "bcm2711.dtsi"
 #include "bcm2711-rpi.dtsi"
-#include "bcm283x-rpi-usb-peripheral.dtsi"
+//#include "bcm283x-rpi-usb-peripheral.dtsi"
 
 / {
        compatible = "raspberrypi,4-model-b", "brcm,bcm2711";
@@ -72,7 +72,7 @@
                          "VDD_SD_IO_SEL",
                          "CAM_GPIO",
                          "SD_PWR_ON",
-                         "";
+                         "SD_OC_N";
 };
 
 &gpio {
 &vec {
        status = "disabled";
 };
+
+// =============================================
+// Downstream rpi- changes
+
+#define BCM2711
+
+#include "bcm270x.dtsi"
+#include "bcm271x-rpi-bt.dtsi"
+
+/ {
+       soc {
+               /delete-node/ pixelvalve@7e807000;
+               /delete-node/ hdmi@7e902000;
+       };
+};
+
+#include "bcm2711-rpi-ds.dtsi"
+#include "bcm283x-rpi-csi1-2lane.dtsi"
+#include "bcm283x-rpi-i2c0mux_0_44.dtsi"
+
+/ {
+       chosen {
+               bootargs = "coherent_pool=1M 8250.nr_uarts=1 snd_bcm2835.enable_compat_alsa=0 snd_bcm2835.enable_hdmi=1";
+       };
+
+       aliases {
+               serial0 = &uart1;
+               serial1 = &uart0;
+               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;
+       };
+
+       /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 {
+       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>;
+       };
+
+       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;
+       };
+
+       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>;
+       };
+};
+
+&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>;
+       };
+};
+
+&leds {
+       act_led: led-act {
+               label = "led0";
+               linux,default-trigger = "mmc0";
+               gpios = <&gpio 42 GPIO_ACTIVE_HIGH>;
+       };
+
+       pwr_led: led-pwr {
+               label = "led1";
+               linux,default-trigger = "default-on";
+               gpios = <&expgpio 2 GPIO_ACTIVE_LOW>;
+       };
+};
+
+&pwm1 {
+       status = "disabled";
+};
+
+&audio {
+       pinctrl-names = "default";
+       pinctrl-0 = <&audio_pins>;
+};
+
+&cam1_reg {
+       gpio = <&expgpio 5 GPIO_ACTIVE_HIGH>;
+};
+
+cam0_reg: &cam_dummy_reg {
+};
+
+/ {
+       __overrides__ {
+               act_led_gpio = <&act_led>,"gpios:4";
+               act_led_activelow = <&act_led>,"gpios:8";
+               act_led_trigger = <&act_led>,"linux,default-trigger";
+
+               pwr_led_gpio = <&pwr_led>,"gpios:4";
+               pwr_led_activelow = <&pwr_led>,"gpios:8";
+               pwr_led_trigger = <&pwr_led>,"linux,default-trigger";
+
+               eth_led0 = <&phy1>,"led-modes:0";
+               eth_led1 = <&phy1>,"led-modes:4";
+
+               sd_poll_once = <&emmc2>, "non-removable?";
+               spi_dma4 = <&spi0>, "dmas:0=", <&dma40>,
+                          <&spi0>, "dmas:8=", <&dma40>;
+       };
+};
index c53d9eb..113ea9f 100644 (file)
@@ -1,6 +1,8 @@
 // SPDX-License-Identifier: GPL-2.0
 /dts-v1/;
-#include "bcm2711-rpi-4-b.dts"
+#include "bcm2711.dtsi"
+#include "bcm2711-rpi.dtsi"
+//#include "bcm283x-rpi-usb-peripheral.dtsi"
 
 / {
        compatible = "raspberrypi,400", "brcm,bcm2711";
        };
 
        leds {
-               /delete-node/ led-act;
+               led-act {
+                       gpios = <&gpio 42 GPIO_ACTIVE_HIGH>;
+               };
 
                led-pwr {
-                       gpios = <&gpio 42 GPIO_ACTIVE_HIGH>;
+                       label = "PWR";
+                       gpios = <&expgpio 2 GPIO_ACTIVE_LOW>;
+                       default-state = "keep";
+                       linux,default-trigger = "default-on";
                };
        };
 
-       gpio-poweroff {
-               compatible = "gpio-poweroff";
-               gpios = <&expgpio 5 GPIO_ACTIVE_HIGH>;
+       wifi_pwrseq: wifi-pwrseq {
+               compatible = "mmc-pwrseq-simple";
+               reset-gpios = <&expgpio 1 GPIO_ACTIVE_LOW>;
+       };
+
+       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>;
        };
 };
 
+&ddc0 {
+       status = "okay";
+};
+
+&ddc1 {
+       status = "okay";
+};
+
 &expgpio {
        gpio-line-names = "BT_ON",
                          "WL_ON",
                          "SHUTDOWN_REQUEST";
 };
 
+&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";
+};
+
+&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";
+};
+
+/* SDHCI is used to control the SDIO for wireless */
+&sdhci {
+       #address-cells = <1>;
+       #size-cells = <0>;
+       pinctrl-names = "default";
+       pinctrl-0 = <&emmc_gpio34>;
+       bus-width = <4>;
+       non-removable;
+       mmc-pwrseq = <&wifi_pwrseq>;
+       status = "okay";
+
+       brcmf: wifi@1 {
+               reg = <1>;
+               compatible = "brcm,bcm4329-fmac";
+       };
+};
+
+/* EMMC2 is used to drive the SD card */
+&emmc2 {
+       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@1 {
+               /* No PHY interrupt */
+               reg = <0x1>;
+       };
+};
+
+&pcie0 {
+       pci@0,0 {
+               device_type = "pci";
+               #address-cells = <3>;
+               #size-cells = <2>;
+               ranges;
+
+               reg = <0 0 0 0 0>;
+
+               usb@0,0 {
+                       reg = <0 0 0 0 0>;
+                       resets = <&reset RASPBERRYPI_FIRMWARE_RESET_ID_USB>;
+               };
+       };
+};
+
+/* uart0 communicates with the BT module */
+&uart0 {
+       pinctrl-names = "default";
+       pinctrl-0 = <&uart0_ctsrts_gpio30 &uart0_gpio32>;
+       uart-has-rtscts;
+       status = "okay";
+
+       bluetooth {
+               compatible = "brcm,bcm43438-bt";
+               max-speed = <2000000>;
+               shutdown-gpios = <&expgpio 0 GPIO_ACTIVE_HIGH>;
+       };
+};
+
+/* uart1 is mapped to the pin header */
+&uart1 {
+       pinctrl-names = "default";
+       pinctrl-0 = <&uart1_gpio14>;
+       status = "okay";
+};
+
+&vc4 {
+       status = "okay";
+};
+
+&vec {
+       status = "disabled";
+};
+
+// =============================================
+// Downstream rpi- changes
+
+#define BCM2711
+
+#include "bcm270x.dtsi"
+#include "bcm271x-rpi-bt.dtsi"
+
+/ {
+       soc {
+               /delete-node/ pixelvalve@7e807000;
+               /delete-node/ hdmi@7e902000;
+       };
+};
+
+#include "bcm2711-rpi-ds.dtsi"
+#include "bcm283x-rpi-csi1-2lane.dtsi"
+#include "bcm283x-rpi-i2c0mux_0_44.dtsi"
+
+/ {
+       chosen {
+               bootargs = "coherent_pool=1M 8250.nr_uarts=1 snd_bcm2835.enable_compat_alsa=0 snd_bcm2835.enable_hdmi=1";
+       };
+
+       aliases {
+               serial0 = &uart1;
+               serial1 = &uart0;
+               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;
+       };
+
+       /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 {
+       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>;
+       };
+
+       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;
+       };
+
+       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>;
+       };
+};
+
+&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
+
+/ {
+       power_ctrl: power_ctrl {
+               compatible = "gpio-poweroff";
+               gpios = <&expgpio 5 0>;
+               force;
+       };
+};
+
+&sdhost {
+       status = "disabled";
+};
+
+&phy1 {
+       led-modes = <0x00 0x08>; /* link/activity link */
+};
+
+&gpio {
+       audio_pins: audio_pins {
+               brcm,pins = <>;
+               brcm,function = <>;
+       };
+};
+
+&leds {
+       act_led: led-act {
+               label = "led0";
+               linux,default-trigger = "default-on";
+               default-state = "on";
+               gpios = <&gpio 42 GPIO_ACTIVE_HIGH>;
+       };
+
+       pwr_led: led-pwr {
+               label = "led1";
+               linux,default-trigger = "default-on";
+               gpios = <&expgpio 2 GPIO_ACTIVE_LOW>;
+       };
+};
+
+&pwm1 {
+       status = "disabled";
+};
+
+&audio {
+       pinctrl-names = "default";
+       pinctrl-0 = <&audio_pins>;
+       brcm,disable-headphones = <1>;
+};
+
 &genet_mdio {
        clock-frequency = <1950000>;
 };
 
-&pm {
-       /delete-property/ system-power-controller;
+/ {
+       __overrides__ {
+               act_led_gpio = <&act_led>,"gpios:4";
+               act_led_activelow = <&act_led>,"gpios:8";
+               act_led_trigger = <&act_led>,"linux,default-trigger";
+
+               pwr_led_gpio = <&pwr_led>,"gpios:4";
+               pwr_led_activelow = <&pwr_led>,"gpios:8";
+               pwr_led_trigger = <&pwr_led>,"linux,default-trigger";
+
+               eth_led0 = <&phy1>,"led-modes:0";
+               eth_led1 = <&phy1>,"led-modes:4";
+
+               sd_poll_once = <&emmc2>, "non-removable?";
+               spi_dma4 = <&spi0>, "dmas:0=", <&dma40>,
+                          <&spi0>, "dmas:8=", <&dma40>;
+       };
 };
diff --git a/arch/arm/boot/dts/bcm2711-rpi-cm4.dts b/arch/arm/boot/dts/bcm2711-rpi-cm4.dts
new file mode 100644 (file)
index 0000000..6e99da1
--- /dev/null
@@ -0,0 +1,598 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+#include "bcm2711.dtsi"
+#include "bcm2711-rpi.dtsi"
+//#include "bcm283x-rpi-usb-peripheral.dtsi"
+
+/ {
+       compatible = "raspberrypi,4-compute-module", "brcm,bcm2711";
+       model = "Raspberry Pi Compute Module 4";
+
+       chosen {
+               /* 8250 auxiliary UART instead of pl011 */
+               stdout-path = "serial1:115200n8";
+       };
+
+       leds {
+               led-act {
+                       gpios = <&gpio 42 GPIO_ACTIVE_HIGH>;
+               };
+
+               led-pwr {
+                       label = "PWR";
+                       gpios = <&expgpio 2 GPIO_ACTIVE_LOW>;
+                       default-state = "keep";
+                       linux,default-trigger = "default-on";
+               };
+       };
+
+       wifi_pwrseq: wifi-pwrseq {
+               compatible = "mmc-pwrseq-simple";
+               reset-gpios = <&expgpio 1 GPIO_ACTIVE_LOW>;
+       };
+
+       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>;
+       };
+};
+
+&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";
+};
+
+&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";
+};
+
+/* SDHCI is used to control the SDIO for wireless */
+&sdhci {
+       #address-cells = <1>;
+       #size-cells = <0>;
+       pinctrl-names = "default";
+       pinctrl-0 = <&emmc_gpio34>;
+       bus-width = <4>;
+       non-removable;
+       mmc-pwrseq = <&wifi_pwrseq>;
+       status = "okay";
+
+       brcmf: wifi@1 {
+               reg = <1>;
+               compatible = "brcm,bcm4329-fmac";
+       };
+};
+
+/* 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;
+       status = "okay";
+
+       bluetooth {
+               compatible = "brcm,bcm43438-bt";
+               max-speed = <2000000>;
+               shutdown-gpios = <&expgpio 0 GPIO_ACTIVE_HIGH>;
+       };
+};
+
+/* uart1 is mapped to the pin header */
+&uart1 {
+       pinctrl-names = "default";
+       pinctrl-0 = <&uart1_gpio14>;
+       status = "okay";
+};
+
+&vchiq {
+       interrupts = <GIC_SPI 34 IRQ_TYPE_LEVEL_HIGH>;
+};
+
+&vc4 {
+       status = "okay";
+};
+
+&vec {
+       status = "disabled";
+};
+
+// =============================================
+// Downstream rpi- changes
+
+#define BCM2711
+
+#include "bcm270x.dtsi"
+#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"
+
+/ {
+       chosen {
+               bootargs = "coherent_pool=1M 8250.nr_uarts=1 snd_bcm2835.enable_compat_alsa=0 snd_bcm2835.enable_hdmi=1";
+       };
+
+       aliases {
+               serial0 = &uart1;
+               serial1 = &uart0;
+               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;
+       };
+
+       /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 {
+       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>;
+       };
+
+       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;
+       };
+
+       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>;
+       };
+};
+
+&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 = <>;
+       };
+};
+
+&leds {
+       act_led: led-act {
+               label = "led0";
+               linux,default-trigger = "mmc0";
+               gpios = <&gpio 42 GPIO_ACTIVE_HIGH>;
+       };
+
+       pwr_led: led-pwr {
+               label = "led1";
+               linux,default-trigger = "default-on";
+               gpios = <&expgpio 2 GPIO_ACTIVE_LOW>;
+       };
+};
+
+&pwm1 {
+       status = "disabled";
+};
+
+&audio {
+       pinctrl-names = "default";
+       pinctrl-0 = <&audio_pins>;
+       brcm,disable-headphones = <1>;
+};
+
+cam0_reg: &cam1_reg {
+       gpio = <&expgpio 5 GPIO_ACTIVE_HIGH>;
+};
+
+/ {
+       __overrides__ {
+               act_led_gpio = <&act_led>,"gpios:4";
+               act_led_activelow = <&act_led>,"gpios:8";
+               act_led_trigger = <&act_led>,"linux,default-trigger";
+
+               pwr_led_gpio = <&pwr_led>,"gpios:4";
+               pwr_led_activelow = <&pwr_led>,"gpios:8";
+               pwr_led_trigger = <&pwr_led>,"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";
+
+               sd_poll_once = <&emmc2>, "non-removable?";
+               spi_dma4 = <&spi0>, "dmas:0=", <&dma40>,
+                          <&spi0>, "dmas:8=", <&dma40>;
+       };
+};
diff --git a/arch/arm/boot/dts/bcm2711-rpi-cm4s.dts b/arch/arm/boot/dts/bcm2711-rpi-cm4s.dts
new file mode 100644 (file)
index 0000000..03c6a5e
--- /dev/null
@@ -0,0 +1,427 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+#include "bcm2711.dtsi"
+#include "bcm2711-rpi.dtsi"
+
+/ {
+       compatible = "raspberrypi,4-compute-module-s", "brcm,bcm2711";
+       model = "Raspberry Pi Compute Module 4S";
+
+       chosen {
+               /* 8250 auxiliary UART instead of pl011 */
+               stdout-path = "serial1:115200n8";
+       };
+
+       leds {
+               led-act {
+                       gpios = <&virtgpio 0 0>;
+               };
+       };
+};
+
+&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",
+                         "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",
+                         "GPIO28",
+                         "GPIO29",
+                         "GPIO30",
+                         "GPIO31",
+                         "GPIO32",
+                         "GPIO33",
+                         "GPIO34",
+                         "GPIO35",
+                         "GPIO36",
+                         "GPIO37",
+                         "GPIO38",
+                         "GPIO39",
+                         "PWM0_MISO",
+                         "PWM1_MOSI",
+                         "GPIO42",
+                         "GPIO43",
+                         "GPIO44",
+                         "GPIO45";
+};
+
+&hdmi0 {
+       status = "okay";
+};
+
+&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
+
+#define BCM2711
+
+#include "bcm270x.dtsi"
+#include "bcm2711-rpi-ds.dtsi"
+
+/ {
+       soc {
+               /delete-node/ pixelvalve@7e807000;
+               /delete-node/ hdmi@7e902000;
+
+               virtgpio: virtgpio {
+                       compatible = "brcm,bcm2835-virtgpio";
+                       gpio-controller;
+                       #gpio-cells = <2>;
+                       firmware = <&firmware>;
+                       status = "okay";
+               };
+       };
+};
+
+#include "bcm283x-rpi-csi0-2lane.dtsi"
+#include "bcm283x-rpi-csi1-4lane.dtsi"
+#include "bcm283x-rpi-i2c0mux_0_28.dtsi"
+
+/ {
+       chosen {
+               bootargs = "coherent_pool=1M 8250.nr_uarts=1 snd_bcm2835.enable_compat_alsa=0 snd_bcm2835.enable_hdmi=1";
+       };
+
+       aliases {
+               serial0 = &uart0;
+               mmc0 = &emmc2;
+               mmc1 = &mmcnr;
+               mmc2 = &sdhost;
+               i2c3 = &i2c3;
+               i2c4 = &i2c4;
+               i2c5 = &i2c5;
+               i2c6 = &i2c6;
+               spi3 = &spi3;
+               spi4 = &spi4;
+               spi5 = &spi5;
+               spi6 = &spi6;
+               /delete-property/ intc;
+       };
+
+       /delete-node/ wifi-pwrseq;
+};
+
+&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 {
+       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>;
+       };
+
+       uart0_pins: uart0_pins {
+               brcm,pins;
+               brcm,function;
+               brcm,pull;
+       };
+
+       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>;
+       };
+};
+
+&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";
+};
+
+&leds {
+       act_led: led-act {
+               label = "led0";
+               linux,default-trigger = "mmc0";
+       };
+};
+
+&pwm1 {
+       status = "disabled";
+};
+
+&audio {
+       pinctrl-names = "default";
+       pinctrl-0 = <&audio_pins>;
+       brcm,disable-headphones = <1>;
+};
+
+&cam1_reg {
+       gpio = <&gpio 2 GPIO_ACTIVE_HIGH>;
+       status = "disabled";
+};
+
+cam0_reg: &cam0_regulator {
+       gpio = <&gpio 30 GPIO_ACTIVE_HIGH>;
+       status = "disabled";
+};
+
+/ {
+       __overrides__ {
+               act_led_gpio = <&act_led>,"gpios:4";
+               act_led_activelow = <&act_led>,"gpios:8";
+               act_led_trigger = <&act_led>,"linux,default-trigger";
+
+               sd_poll_once = <&emmc2>, "non-removable?";
+               spi_dma4 = <&spi0>, "dmas:0=", <&dma40>,
+                          <&spi0>, "dmas:8=", <&dma40>;
+       };
+};
diff --git a/arch/arm/boot/dts/bcm2711-rpi-ds.dtsi b/arch/arm/boot/dts/bcm2711-rpi-ds.dtsi
new file mode 100644 (file)
index 0000000..5b18e8f
--- /dev/null
@@ -0,0 +1,196 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "bcm270x-rpi.dtsi"
+
+/ {
+       __overrides__ {
+               arm_freq;
+       };
+
+       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";
+               };
+       };
+
+       scb: scb {
+            /* Add a label */
+       };
+};
+
+&vc4 {
+       raspberrypi,firmware = <&firmware>;
+};
+
+&cma {
+       /* Limit cma to the lower 768MB to allow room for HIGHMEM on 32-bit */
+       alloc-ranges = <0x0 0x00000000 0x30000000>;
+};
+
+&scb {
+       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 = <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";
+       };
+};
+
+&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>;
+};
+
+&emmc2 {
+       mmc-ddr-3_3v;
+};
+
+&vc4 {
+       status = "disabled";
+};
+
+&pixelvalve0 {
+       status = "disabled";
+};
+
+&pixelvalve1 {
+       status = "disabled";
+};
+
+&pixelvalve2 {
+       status = "disabled";
+};
+
+&pixelvalve3 {
+       status = "disabled";
+};
+
+&pixelvalve4 {
+       status = "disabled";
+};
+
+&hdmi0 {
+       dmas = <&dma (10|(1<<27)|(1<<24)|(10<<16)|(15<<20))>;
+       status = "disabled";
+};
+
+&ddc0 {
+       status = "disabled";
+};
+
+&hdmi1 {
+       dmas = <&dma (17|(1<<27)|(1<<24)|(10<<16)|(15<<20))>;
+       status = "disabled";
+};
+
+&ddc1 {
+       status = "disabled";
+};
+
+&dvp {
+       status = "disabled";
+};
+
+&system_timer {
+       status = "disabled";
+};
index 89af574..ff84a09 100644 (file)
                vec: vec@7ec13000 {
                        compatible = "brcm,bcm2711-vec";
                        reg = <0x7ec13000 0x1000>;
-                       clocks = <&clocks BCM2835_CLOCK_VEC>;
+                       clocks = <&firmware_clocks 15>;
                        interrupts = <GIC_SPI 123 IRQ_TYPE_LEVEL_HIGH>;
                        status = "disabled";
                };
                aon_intr: interrupt-controller@7ef00100 {
                        compatible = "brcm,bcm2711-l2-intc", "brcm,l2-intc";
                        reg = <0x7ef00100 0x30>;
-                       interrupts = <GIC_SPI 96 IRQ_TYPE_LEVEL_HIGH>;
+                       interrupts = <GIC_SPI 96 IRQ_TYPE_EDGE_RISING>;
                        interrupt-controller;
                        #interrupt-cells = <1>;
+                       status = "disabled";
                };
 
                hdmi0: hdmi@7ef00700 {
                              <0x7ef01f00 0x400>,
                              <0x7ef00200 0x80>,
                              <0x7ef04300 0x100>,
-                             <0x7ef20000 0x100>;
+                             <0x7ef20000 0x100>,
+                             <0x7ef00100 0x30>;
                        reg-names = "hdmi",
                                    "dvp",
                                    "phy",
                                    "metadata",
                                    "csc",
                                    "cec",
-                                   "hd";
+                                   "hd",
+                                   "intr2";
+                       clocks = <&firmware_clocks 13>,
+                                <&firmware_clocks 14>,
+                                <&dvp 0>,
+                                <&clk_27MHz>;
                        clock-names = "hdmi", "bvb", "audio", "cec";
                        resets = <&dvp 0>;
                        interrupt-parent = <&aon_intr>;
                        interrupt-names = "cec-tx", "cec-rx", "cec-low",
                                          "wakeup", "hpd-connected", "hpd-removed";
                        ddc = <&ddc0>;
-                       dmas = <&dma 10>;
+                       dmas = <&dma (10 | (1 << 27) | (1 << 24)| (15 << 20) | (10 << 16))>;
                        dma-names = "audio-rx";
                        status = "disabled";
                };
                              <0x7ef06f00 0x400>,
                              <0x7ef00280 0x80>,
                              <0x7ef09300 0x100>,
-                             <0x7ef20000 0x100>;
+                             <0x7ef20000 0x100>,
+                             <0x7ef00100 0x30>;
                        reg-names = "hdmi",
                                    "dvp",
                                    "phy",
                                    "metadata",
                                    "csc",
                                    "cec",
-                                   "hd";
+                                   "hd",
+                                   "intr2";
                        ddc = <&ddc1>;
                        clock-names = "hdmi", "bvb", "audio", "cec";
+                       clocks = <&firmware_clocks 13>,
+                                <&firmware_clocks 14>,
+                                <&dvp 0>,
+                                <&clk_27MHz>;
                        resets = <&dvp 1>;
                        interrupt-parent = <&aon_intr>;
-                       interrupts = <8>, <7>, <6>,
+                       interrupts = <8>, <7>, <6>,     // This is correct
                                     <9>, <10>, <11>;
                        interrupt-names = "cec-tx", "cec-rx", "cec-low",
                                          "wakeup", "hpd-connected", "hpd-removed";
-                       dmas = <&dma 17>;
+                       dmas = <&dma (17 | (1 << 27) | (1 << 24)| (15 << 20) | (10 << 16))>;
                        dma-names = "audio-rx";
                        status = "disabled";
                };
        };
 
        arm-pmu {
-               compatible = "arm,cortex-a72-pmu", "arm,armv8-pmuv3";
+               compatible = "arm,cortex-a72-pmu", "arm,armv8-pmuv3", "arm,cortex-a7-pmu";
                interrupts = <GIC_SPI 16 IRQ_TYPE_LEVEL_HIGH>,
                        <GIC_SPI 17 IRQ_TYPE_LEVEL_HIGH>,
                        <GIC_SPI 18 IRQ_TYPE_LEVEL_HIGH>,
        scb {
                compatible = "simple-bus";
                #address-cells = <2>;
-               #size-cells = <1>;
+               #size-cells = <2>;
 
-               ranges = <0x0 0x7c000000  0x0 0xfc000000  0x03800000>,
-                        <0x6 0x00000000  0x6 0x00000000  0x40000000>;
+               ranges = <0x0 0x7c000000  0x0 0xfc000000  0x0 0x03800000>,
+                        <0x6 0x00000000  0x6 0x00000000  0x0 0x40000000>;
 
                pcie0: pcie@7d500000 {
                        compatible = "brcm,bcm2711-pcie";
-                       reg = <0x0 0x7d500000 0x9310>;
+                       reg = <0x0 0x7d500000  0x0 0x9310>;
                        device_type = "pci";
                        #address-cells = <3>;
                        #interrupt-cells = <1>;
                        msi-controller;
                        msi-parent = <&pcie0>;
 
-                       ranges = <0x02000000 0x0 0xf8000000 0x6 0x00000000
-                                 0x0 0x04000000>;
+                       ranges = <0x02000000 0x0 0xc0000000 0x6 0x00000000
+                                 0x0 0x40000000>;
                        /*
                         * The wrapper around the PCIe block has a bug
                         * preventing it from accessing beyond the first 3GB of
 
                genet: ethernet@7d580000 {
                        compatible = "brcm,bcm2711-genet-v5";
-                       reg = <0x0 0x7d580000 0x10000>;
+                       reg = <0x0 0x7d580000  0x0 0x10000>;
                        #address-cells = <0x1>;
                        #size-cells = <0x1>;
                        interrupts = <GIC_SPI 157 IRQ_TYPE_LEVEL_HIGH>,
        alloc-ranges = <0x0 0x00000000 0x40000000>;
 };
 
-&i2c0 {
+&i2c0if {
        compatible = "brcm,bcm2711-i2c", "brcm,bcm2835-i2c";
        interrupts = <GIC_SPI 117 IRQ_TYPE_LEVEL_HIGH>;
 };
 &usb {
        interrupts = <GIC_SPI 73 IRQ_TYPE_LEVEL_HIGH>;
 };
-
-&vec {
-       compatible = "brcm,bcm2711-vec";
-       interrupts = <GIC_SPI 123 IRQ_TYPE_LEVEL_HIGH>;
-};
diff --git a/arch/arm/boot/dts/bcm271x-rpi-bt.dtsi b/arch/arm/boot/dts/bcm271x-rpi-bt.dtsi
new file mode 100644 (file)
index 0000000..6b9b79f
--- /dev/null
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: GPL-2.0
+
+&uart0 {
+       bt: bluetooth {
+               compatible = "brcm,bcm43438-bt";
+               max-speed = <3000000>;
+               shutdown-gpios = <&expgpio 0 GPIO_ACTIVE_HIGH>;
+               status = "disabled";
+       };
+};
+
+&uart1 {
+       minibt: bluetooth {
+               compatible = "brcm,bcm43438-bt";
+               max-speed = <460800>;
+               shutdown-gpios = <&expgpio 0 GPIO_ACTIVE_HIGH>;
+               status = "disabled";
+       };
+};
+
+/ {
+       __overrides__ {
+               krnbt = <&bt>,"status";
+               krnbt_baudrate = <&bt>,"max-speed:0";
+       };
+};
index c25e797..69c91c5 100644 (file)
                vec: vec@7e806000 {
                        compatible = "brcm,bcm2835-vec";
                        reg = <0x7e806000 0x1000>;
-                       clocks = <&clocks BCM2835_CLOCK_VEC>;
+                       clocks = <&firmware_clocks 15>;
                        interrupts = <2 27>;
                        status = "disabled";
                };
                        compatible = "brcm,bcm2835-hdmi";
                        reg = <0x7e902000 0x600>,
                              <0x7e808000 0x100>;
+                       reg-names = "hdmi",
+                                   "hd";
                        interrupts = <2 8>, <2 9>;
                        ddc = <&i2c2>;
-                       clocks = <&clocks BCM2835_PLLH_PIX>,
-                                <&clocks BCM2835_CLOCK_HSM>;
+                       clocks = <&firmware_clocks 9>,
+                                <&firmware_clocks 13>;
                        clock-names = "pixel", "hdmi";
-                       dmas = <&dma 17>;
+                       dmas = <&dma (17|(1<<27)|(1<<24))>;
                        dma-names = "audio-rx";
                        status = "disabled";
                };
index 40b9405..d2384d8 100644 (file)
        pinctrl-0 = <&uart0_gpio14>;
        status = "okay";
 };
+
+/* i2c on camera/display connector is gpio 28&29 */
+&i2c0mux {
+       pinctrl-1 = <&i2c0_gpio28>;
+};
index 11edb58..4ceca67 100644 (file)
        pinctrl-0 = <&uart0_gpio14>;
        status = "okay";
 };
+
+/* i2c0 on camera/display connector is gpio 0&1. Not exposed on header.
+ * To avoid having to remap everything, map both ports to gpios 0&1
+ */
+&i2c0mux {
+       pinctrl-1 = <&i2c0_gpio0>;
+};
index 1b435c6..8f2d10d 100644 (file)
        pinctrl-0 = <&uart0_gpio14>;
        status = "okay";
 };
+
+/* i2c on camera/display connector is gpio 28&29 */
+&i2c0mux {
+       pinctrl-1 = <&i2c0_gpio28>;
+};
index a23c25c..547c88a 100644 (file)
        pinctrl-0 = <&uart0_gpio14>;
        status = "okay";
 };
+
+/* i2c0 on camera/display connector is gpio 0&1. Not exposed on header.
+ * To avoid having to remap everything, map both ports to gpios 0&1
+ */
+&i2c0mux {
+       pinctrl-1 = <&i2c0_gpio0>;
+};
index 25d8721..c9f3c45 100644 (file)
        pinctrl-0 = <&uart0_gpio14>;
        status = "okay";
 };
+
+/* camera/display connector use BSC1 on GPIOS 2&3.
+ * To avoid having to remap everything, map both ports to gpios 0&1
+ */
+&i2c0mux {
+       pinctrl-1 = <&i2c0_gpio0>;
+};
index a75c882..95564c9 100644 (file)
@@ -95,3 +95,8 @@
        pinctrl-0 = <&uart0_gpio14>;
        status = "okay";
 };
+
+/* WHAT TO DO HERE? */
+&i2c0mux {
+       pinctrl-1 = <&i2c0_gpio28>;
+};
index 00582eb..7d32734 100644 (file)
        pinctrl-0 = <&uart1_gpio14>;
        status = "okay";
 };
+
+/* i2c on camera/display connector is gpio 28&29 */
+&i2c0mux {
+       pinctrl-1 = <&i2c0_gpio28>;
+};
index 6f9b3a9..a0eabab 100644 (file)
        pinctrl-0 = <&uart0_gpio14>;
        status = "okay";
 };
+
+/* i2c on camera/display connector is gpio 28&29 */
+&i2c0mux {
+       pinctrl-1 = <&i2c0_gpio28>;
+};
index 87ddcad..edc55bb 100644 (file)
 
                        mboxes = <&mailbox>;
                        dma-ranges;
+
+                       firmware_clocks: clocks {
+                               compatible = "raspberrypi,firmware-clocks";
+                               #clock-cells = <1>;
+                       };
                };
 
                power: power {
        };
 };
 
-&i2c0 {
-       pinctrl-names = "default";
-       pinctrl-0 = <&i2c0_gpio0>;
+&i2c0if {
        status = "okay";
        clock-frequency = <100000>;
 };
 
+&i2c0mux {
+       pinctrl-0 = <&i2c0_gpio0>;
+       /* pinctrl-1 varies based on platform */
+       status = "okay";
+};
+
 &i2c1 {
        pinctrl-names = "default";
        pinctrl-0 = <&i2c1_gpio2>;
        power-domains = <&power RPI_POWER_DOMAIN_USB>;
 };
 
+&vc4 {
+       raspberrypi,firmware = <&firmware>;
+};
+
 &vec {
        power-domains = <&power RPI_POWER_DOMAIN_VEC>;
        status = "okay";
index 0549686..2ea8912 100644 (file)
@@ -19,7 +19,7 @@
 
        soc {
                ranges = <0x7e000000 0x20000000 0x02000000>;
-               dma-ranges = <0x40000000 0x00000000 0x20000000>;
+               dma-ranges = <0x80000000 0x00000000 0x20000000>;
        };
 
        arm-pmu {
index d8af8ee..bf22b74 100644 (file)
        pinctrl-0 = <&uart0_gpio14>;
        status = "okay";
 };
+
+/* i2c on camera/display connector is gpio 28&29 */
+&i2c0mux {
+       pinctrl-1 = <&i2c0_gpio28>;
+};
index 77099a7..9529c04 100644 (file)
        pinctrl-0 = <&uart1_gpio14>;
        status = "okay";
 };
+
+/* i2c on camera/display connector is gpio 44&45 */
+&i2c0mux {
+       pinctrl-1 = <&i2c0_gpio44>;
+};
index 90472e7..23fd63b 100644 (file)
        pinctrl-0 = <&uart1_gpio14>;
        status = "okay";
 };
+
+/* i2c on camera/display connector is gpio 44&45 */
+&i2c0mux {
+       pinctrl-1 = <&i2c0_gpio44>;
+};
index dd4a486..8f16b6b 100644 (file)
        status = "okay";
        bus-width = <4>;
 };
+
+/* i2c on camera/display connector is gpio 44&45 */
+&i2c0mux {
+       pinctrl-1 = <&i2c0_gpio44>;
+};
index 3dfce43..f0a7ef1 100644 (file)
@@ -94,3 +94,8 @@
        pinctrl-0 = <&uart0_gpio14>;
        status = "okay";
 };
+
+/* WHAT TO DO HERE? */
+&i2c0mux {
+       pinctrl-1 = <&i2c0_gpio28>;
+};
diff --git a/arch/arm/boot/dts/bcm283x-rpi-csi0-2lane.dtsi b/arch/arm/boot/dts/bcm283x-rpi-csi0-2lane.dtsi
new file mode 100644 (file)
index 0000000..6e4ce86
--- /dev/null
@@ -0,0 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0-only
+&csi0 {
+       brcm,num-data-lanes = <2>;
+};
diff --git a/arch/arm/boot/dts/bcm283x-rpi-csi1-2lane.dtsi b/arch/arm/boot/dts/bcm283x-rpi-csi1-2lane.dtsi
new file mode 100644 (file)
index 0000000..6938f4d
--- /dev/null
@@ -0,0 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0-only
+&csi1 {
+       brcm,num-data-lanes = <2>;
+};
diff --git a/arch/arm/boot/dts/bcm283x-rpi-csi1-4lane.dtsi b/arch/arm/boot/dts/bcm283x-rpi-csi1-4lane.dtsi
new file mode 100644 (file)
index 0000000..b370374
--- /dev/null
@@ -0,0 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0-only
+&csi1 {
+       brcm,num-data-lanes = <4>;
+};
diff --git a/arch/arm/boot/dts/bcm283x-rpi-i2c0mux_0_28.dtsi b/arch/arm/boot/dts/bcm283x-rpi-i2c0mux_0_28.dtsi
new file mode 100644 (file)
index 0000000..38f0074
--- /dev/null
@@ -0,0 +1,4 @@
+&i2c0mux {
+       pinctrl-0 = <&i2c0_gpio0>;
+       pinctrl-1 = <&i2c0_gpio28>;
+};
diff --git a/arch/arm/boot/dts/bcm283x-rpi-i2c0mux_0_44.dtsi b/arch/arm/boot/dts/bcm283x-rpi-i2c0mux_0_44.dtsi
new file mode 100644 (file)
index 0000000..119946d
--- /dev/null
@@ -0,0 +1,4 @@
+&i2c0mux {
+       pinctrl-0 = <&i2c0_gpio0>;
+       pinctrl-1 = <&i2c0_gpio44>;
+};
index c113661..62d7ee5 100644 (file)
                        status = "disabled";
                };
 
-               i2c0: i2c@7e205000 {
+               i2c0if: i2c@7e205000 {
                        compatible = "brcm,bcm2835-i2c";
                        reg = <0x7e205000 0x200>;
                        interrupts = <2 21>;
                        status = "disabled";
                };
 
+               i2c0mux: i2c0mux {
+                       compatible = "i2c-mux-pinctrl";
+                       #address-cells = <1>;
+                       #size-cells = <0>;
+
+                       i2c-parent = <&i2c0if>;
+
+                       pinctrl-names = "i2c0", "i2c_csi_dsi";
+
+                       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>;
+                       };
+               };
+
                dpi: dpi@7e208000 {
                        compatible = "brcm,bcm2835-dpi";
                        reg = <0x7e208000 0x8c>;
diff --git a/arch/arm/boot/dts/overlays/Makefile b/arch/arm/boot/dts/overlays/Makefile
new file mode 100644 (file)
index 0000000..4bbe05e
--- /dev/null
@@ -0,0 +1,281 @@
+# Overlays for the Raspberry Pi platform
+
+dtb-$(CONFIG_ARCH_BCM2835) += overlay_map.dtb
+
+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 \
+       balena-fin.dtbo \
+       camera-mux-2port.dtbo \
+       camera-mux-4port.dtbo \
+       cap1106.dtbo \
+       chipdip-dac.dtbo \
+       cma.dtbo \
+       cutiepi-panel.dtbo \
+       dacberry400.dtbo \
+       dht11.dtbo \
+       dionaudio-kiwi.dtbo \
+       dionaudio-loco.dtbo \
+       dionaudio-loco-v2.dtbo \
+       disable-bt.dtbo \
+       disable-wifi.dtbo \
+       dpi18.dtbo \
+       dpi18cpadhi.dtbo \
+       dpi24.dtbo \
+       draws.dtbo \
+       dwc-otg.dtbo \
+       dwc2.dtbo \
+       edt-ft5406.dtbo \
+       enc28j60.dtbo \
+       enc28j60-spi2.dtbo \
+       exc3000.dtbo \
+       fbtft.dtbo \
+       fe-pi-audio.dtbo \
+       fsm-demo.dtbo \
+       gc9a01.dtbo \
+       ghost-amp.dtbo \
+       goodix.dtbo \
+       googlevoicehat-soundcard.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-lcd.dtbo \
+       hdmi-backlight-hwhack-gpio.dtbo \
+       hifiberry-amp.dtbo \
+       hifiberry-amp100.dtbo \
+       hifiberry-amp3.dtbo \
+       hifiberry-dac.dtbo \
+       hifiberry-dacplus.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 \
+       i2c1.dtbo \
+       i2c3.dtbo \
+       i2c4.dtbo \
+       i2c5.dtbo \
+       i2c6.dtbo \
+       i2s-gpio28-31.dtbo \
+       ilitek251x.dtbo \
+       imx219.dtbo \
+       imx258.dtbo \
+       imx290.dtbo \
+       imx296.dtbo \
+       imx327.dtbo \
+       imx378.dtbo \
+       imx462.dtbo \
+       imx477.dtbo \
+       imx519.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-uart1.dtbo \
+       midi-uart2.dtbo \
+       midi-uart3.dtbo \
+       midi-uart4.dtbo \
+       midi-uart5.dtbo \
+       minipitft13.dtbo \
+       miniuart-bt.dtbo \
+       mipi-dbi-spi.dtbo \
+       mlx90640.dtbo \
+       mmc.dtbo \
+       mpu6050.dtbo \
+       mz61581.dtbo \
+       ov2311.dtbo \
+       ov5647.dtbo \
+       ov7251.dtbo \
+       ov9281.dtbo \
+       papirus.dtbo \
+       pca953x.dtbo \
+       pcie-32bit-dma.dtbo \
+       pibell.dtbo \
+       pifacedigital.dtbo \
+       pifi-40.dtbo \
+       pifi-dac-hd.dtbo \
+       pifi-dac-zero.dtbo \
+       pifi-mini-210.dtbo \
+       piglow.dtbo \
+       piscreen.dtbo \
+       piscreen2r.dtbo \
+       pisound.dtbo \
+       pitft22.dtbo \
+       pitft28-capacitive.dtbo \
+       pitft28-resistive.dtbo \
+       pitft35-resistive.dtbo \
+       pps-gpio.dtbo \
+       pwm.dtbo \
+       pwm-2chan.dtbo \
+       pwm-ir-tx.dtbo \
+       qca7000.dtbo \
+       qca7000-uart0.dtbo \
+       ramoops.dtbo \
+       ramoops-pi4.dtbo \
+       rotary-encoder.dtbo \
+       rpi-backlight.dtbo \
+       rpi-cirrus-wm5102.dtbo \
+       rpi-dac.dtbo \
+       rpi-display.dtbo \
+       rpi-ft5406.dtbo \
+       rpi-poe.dtbo \
+       rpi-poe-plus.dtbo \
+       rpi-proto.dtbo \
+       rpi-sense.dtbo \
+       rpi-tv.dtbo \
+       rra-digidac1-wm8741-audio.dtbo \
+       sainsmart18.dtbo \
+       sc16is750-i2c.dtbo \
+       sc16is752-i2c.dtbo \
+       sc16is752-spi0.dtbo \
+       sc16is752-spi1.dtbo \
+       sdhost.dtbo \
+       sdio.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-2cs.dtbo \
+       spi1-1cs.dtbo \
+       spi1-2cs.dtbo \
+       spi1-3cs.dtbo \
+       spi2-1cs.dtbo \
+       spi2-2cs.dtbo \
+       spi2-3cs.dtbo \
+       spi3-1cs.dtbo \
+       spi3-2cs.dtbo \
+       spi4-1cs.dtbo \
+       spi4-2cs.dtbo \
+       spi5-1cs.dtbo \
+       spi5-2cs.dtbo \
+       spi6-1cs.dtbo \
+       spi6-2cs.dtbo \
+       ssd1306.dtbo \
+       ssd1306-spi.dtbo \
+       ssd1331-spi.dtbo \
+       ssd1351-spi.dtbo \
+       superaudioboard.dtbo \
+       sx150x.dtbo \
+       tc358743.dtbo \
+       tc358743-audio.dtbo \
+       tinylcd35.dtbo \
+       tpm-slb9670.dtbo \
+       uart0.dtbo \
+       uart1.dtbo \
+       uart2.dtbo \
+       uart3.dtbo \
+       uart4.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-lt070me05000.dtbo \
+       vc4-kms-dsi-lt070me05000-v2.dtbo \
+       vc4-kms-kippah-7inch.dtbo \
+       vc4-kms-v3d.dtbo \
+       vc4-kms-v3d-pi4.dtbo \
+       vc4-kms-vga666.dtbo \
+       vga666.dtbo \
+       vl805.dtbo \
+       w1-gpio.dtbo \
+       w1-gpio-pullup.dtbo \
+       w5500.dtbo \
+       waveshare-can-fd-hat-mode-a.dtbo \
+       waveshare-can-fd-hat-mode-b.dtbo \
+       wittypi.dtbo \
+       wm8960-soundcard.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 (file)
index 0000000..3152c23
--- /dev/null
@@ -0,0 +1,4395 @@
+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:
+        ant1                    Select antenna 1 (default). CM4 only.
+
+        ant2                    Select antenna 2. CM4 only.
+
+        noant                   Disable both antennas. CM4 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.
+
+        cam0_reg                Enables CAM 0 regulator. CM1 & 3 only.
+
+        cam0_reg_gpio           Set GPIO for CAM 0 regulator. Default 30.
+                                CM1 & 3 only.
+
+        cam1_reg                Enables CAM 1 regulator. CM1 & 3 only.
+
+        cam1_reg_gpio           Set GPIO for CAM 1 regulator. Default 2.
+                                CM1 & 3 only.
+
+        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 (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
+
+                                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 (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.
+
+        i2c_arm                 Set to "on" to enable the ARM's i2c interface
+                                (default "off")
+
+        i2c_vc                  Set to "on" to enable the i2c interface
+                                usually reserved for the VideoCore processor
+                                (default "off")
+
+        i2c                     An alias for i2c_arm
+
+        i2c_arm_baudrate        Set the baudrate of the ARM's i2c interface
+                                (default "100000")
+
+        i2c_vc_baudrate         Set the baudrate of the VideoCore i2c interface
+                                (default "100000")
+
+        i2c_baudrate            An alias for i2c_arm_baudrate
+
+        i2s                     Set to "on" to enable the i2s interface
+                                (default "off")
+
+        krnbt                   Set to "on" to enable autoprobing of Bluetooth
+                                driver without need of hciattach/btattach
+                                (default "off")
+
+        krnbt_baudrate          Set the baudrate of the PL011 UART when used
+                                with krnbt=on
+
+        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)
+
+        random                  Set to "on" to enable the hardware random
+                                number generator (default "on")
+
+        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.
+
+        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")
+
+        uart1                   Set to "on" or "off" to enable or disable uart1
+                                (default varies)
+
+        watchdog                Set to "on" to enable the hardware watchdog
+                                (default "off")
+
+        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.
+
+        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.
+
+        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)
+        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).
+
+
+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
+                                (default off)
+        pins_12_13              Select GPIOs 12 & 13 (default)
+        pins_18_19              Select GPIOs 18 & 19
+
+
+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:   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-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-ov2311             Select OV2311 for camera on port 0
+        cam0-ov5647             Select OV5647 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-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-ov2311             Select OV2311 for camera on port 1
+        cam1-ov5647             Select OV5647 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
+
+
+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-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-ov2311             Select OV2311 for camera on port 0
+        cam0-ov5647             Select OV5647 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-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-ov2311             Select OV2311 for camera on port 1
+        cam1-ov5647             Select OV5647 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-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-ov2311             Select OV2311 for camera on port 2
+        cam2-ov5647             Select OV5647 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-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-ov2311             Select OV2311 for camera on port 3
+        cam3-ov5647             Select OV5647 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
+
+
+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:   chipdip-dac
+Info:   Configures Chip Dip audio cards.
+Load:   dtoverlay=chipdip-dac
+Params: <None>
+
+
+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:   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 Pi 3B, 3B+, 3A+, 4B and Zero W, restoring
+        UART0/ttyAMA0 over GPIOs 14 & 15.
+        N.B. To disable the systemd service that initialises the modem so it
+        doesn't use the UART, use 'sudo systemctl disable hciuart'.
+Load:   dtoverlay=disable-bt
+Params: <None>
+
+
+Name:   disable-wifi
+Info:   Disable onboard WLAN on Pi 3B, 3B+, 3A+, 4B and Zero W.
+Load:   dtoverlay=disable-wifi
+Params: <None>
+
+
+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 on the CSI/DSI I2C interface.
+        This works with the Raspberry Pi 7" touchscreen when not being polled
+        by the firmware.
+        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
+
+
+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:   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: interrupt               GPIO used for interrupt (default 4)
+        reset                   GPIO used for reset (default 17)
+
+
+Name:   googlevoicehat-soundcard
+Info:   Configures the Google voiceHAT soundcard
+Load:   dtoverlay=googlevoicehat-soundcard
+Params: <None>
+
+
+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-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
+
+        display_width           Width of the display in characters
+
+
+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-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 DAC+ 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.
+        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-dac
+Info:   Configures the HifiBerry DAC audio cards
+Load:   dtoverlay=hifiberry-dac
+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+ 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-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!)
+        slave                   Force DAC+ 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-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 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-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.
+
+        i2c0                    Choose the I2C0 bus on GPIOs 0&1
+
+        i2c_csi_dsi             Choose the I2C0 bus on GPIOs 44&45
+
+        i2c3                    Choose the I2C3 bus (configure with the i2c3
+                                overlay - BCM2711 only)
+
+        i2c4                    Choose the I2C3 bus (configure with the i2c3
+                                overlay - BCM2711 only)
+
+        i2c5                    Choose the I2C5 bus (configure with the i2c4
+                                overlay - BCM2711 only)
+
+        i2c6                    Choose the I2C6 bus (configure with the i2c6
+                                overlay - BCM2711 only)
+
+        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)
+
+
+[ 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)
+
+
+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
+
+        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
+
+        rv8803                  Select the Micro Crystal RV8803 device
+
+        sd3078                  Select the ZXW Shenzhen whwave SD3078 device
+
+        s35390a                 Select the ABLIC S35390A device
+
+        i2c0                    Choose the I2C0 bus on GPIOs 0&1
+
+        i2c_csi_dsi             Choose the I2C0 bus on GPIOs 44&45
+
+        i2c3                    Choose the I2C3 bus (configure with the i2c3
+                                overlay - BCM2711 only)
+
+        i2c4                    Choose the I2C3 bus (configure with the i2c3
+                                overlay - BCM2711 only)
+
+        i2c5                    Choose the I2C5 bus (configure with the i2c4
+                                overlay - BCM2711 only)
+
+        i2c6                    Choose the I2C6 bus (configure with the i2c6
+                                overlay - BCM2711 only)
+
+        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, RV1805, RV3028)
+
+        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
+                                off or 1 for Vdd < VBackup (RV3028 only)
+
+
+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
+
+        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
+
+        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, RV1805, RV3028)
+
+        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
+                                off or 1 for Vdd < VBackup (RV3028 only)
+
+        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 BH1750, BME280, BME680,
+                                BMP280, CCS811, DS1621, HDC100X, LM75, SHT3x or
+                                TMP102
+
+        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
+
+        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
+
+        hdc100x                 Select the Texas Instruments HDC100x temp sensor
+                                Valid addresses 0x40-0x43, default 0x40
+
+        htu21                   Select the HTU21 temperature and humidity sensor
+
+        int_pin                 Set the GPIO to use for interrupts (max30102
+                                only)
+
+        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
+
+        sht3x                   Select the Sensiron SHT3x temperature and
+                                humidity sensor. Valid addresses 0x44-0x45,
+                                default 0x44
+
+        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
+
+        i2c0                    Choose the I2C0 bus on GPIOs 0&1
+
+        i2c_csi_dsi             Choose the I2C0 bus on GPIOs 44&45
+
+        i2c3                    Choose the I2C3 bus (configure with the i2c3
+                                overlay - BCM2711 only)
+
+        i2c4                    Choose the I2C3 bus (configure with the i2c3
+                                overlay - BCM2711 only)
+
+        i2c5                    Choose the I2C5 bus (configure with the i2c4
+                                overlay - BCM2711 only)
+
+        i2c6                    Choose the I2C6 bus (configure with the i2c6
+                                overlay - BCM2711 only)
+
+
+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:   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:   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:   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-gpio28-31
+Info:   move I2S function block to GPIO 28 to 31
+Load:   dtoverlay=i2s-gpio28-31
+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)
+
+
+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.
+
+
+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).
+
+
+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).
+
+
+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).
+
+
+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).
+
+
+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: 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>.
+
+
+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)
+
+        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}
+        gpio_out_pin            GPIO for output (default "17")
+        gpio_in_pin             GPIO for input (default "18")
+        gpio_in_pull            Pull up/down/off on the input pin
+                                (default "down")
+        sense                   Override the IR receive auto-detection logic:
+                                 "0" = force active-high
+                                 "1" = force active-low
+                                 "-1" = use auto-detection
+                                (default "-1")
+        softcarrier             Turn the software carrier "on" or "off"
+                                (default "on")
+        invert                  "on" = invert the output pin (default "off")
+        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-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-uart2
+Info:   Configures UART2 (ttyAMA1) so that a requested 38.4kbaud actually gets
+        31.25kbaud, the frequency required for MIDI
+Load:   dtoverlay=midi-uart2
+Params: <None>
+
+
+Name:   midi-uart3
+Info:   Configures UART3 (ttyAMA2) so that a requested 38.4kbaud actually gets
+        31.25kbaud, the frequency required for MIDI
+Load:   dtoverlay=midi-uart3
+Params: <None>
+
+
+Name:   midi-uart4
+Info:   Configures UART4 (ttyAMA3) so that a requested 38.4kbaud actually gets
+        31.25kbaud, the frequency required for MIDI
+Load:   dtoverlay=midi-uart4
+Params: <None>
+
+
+Name:   midi-uart5
+Info:   Configures UART5 (ttyAMA4) 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 on Pi 3B, 3B+, 3A+, 4B and Zero W
+        to use the mini-UART (ttyS0) and restore UART0/ttyAMA0 over GPIOs 14 &
+        15. Note that this may reduce the maximum usable baudrate.
+        N.B. It is also necessary to edit /lib/systemd/system/hciuart.service
+        and replace ttyAMA0 with ttyS0, unless using Raspbian or another
+        distribution with udev rules that create /dev/serial0 and /dev/serial1,
+        in which case use /dev/serial1 instead because it will always be
+        correct. Furthermore, you must also set core_freq and core_freq_min to
+        the same value in config.txt or the miniuart will not work.
+Load:   dtoverlay=miniuart-bt,<param>=<val>
+Params: krnbt                   Set to "on" to enable 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:   Overlay for i2c connected mpu6050 imu
+Load:   dtoverlay=mpu6050,<param>=<val>
+Params: interrupt               GPIO pin for interrupt (default 4)
+        addr                    I2C address of the device (default 0x68)
+
+
+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:   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).
+
+
+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.
+        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)
+        pca16416                Select the NXP PCA16416 (16 bit)
+        pca16524                Select the NXP PCA16524 (24 bit)
+        pca19555a               Select the NXP PCA19555A (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:   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>
+
+
+[ 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:   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)
+
+
+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:   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}
+
+
+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}
+
+        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)
+
+
+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)
+
+
+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)
+
+
+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-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:   qca7000
+Info:   in-tech's 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:   in-tech's 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:   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:   Configures the Cirrus Logic Audio Card
+Load:   dtoverlay=rpi-cirrus-wm5102
+Params: <None>
+
+
+Name:   rpi-dac
+Info:   Configures the RPi DAC audio card
+Load:   dtoverlay=rpi-dac
+Params: <None>
+
+
+Name:   rpi-display
+Info:   RPi-Display - 2.8" Touch Display by Watterott
+        Linux has 2 drivers that support this display and this overlay supports
+        both.
+
+        Examples:
+          fbtft/fb_ili9341: dtoverlay=rpi-display
+          drm/mi0283qt: dtoverlay=rpi-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=rpi-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:   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 600);
+        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-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:   Configures the RPi Proto audio card
+Load:   dtoverlay=rpi-proto
+Params: <None>
+
+
+Name:   rpi-sense
+Info:   Raspberry Pi Sense HAT
+Load:   dtoverlay=rpi-sense
+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)
+        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)
+        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:   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-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 only accessible on devices with a 40pin header, eg:
+              A+, B+, Zero and PI2 B; as well as the Compute Module.
+Load:   dtoverlay=spi1-1cs,<param>=<val>
+Params: cs0_pin                 GPIO pin for CS0 (default 18 - BCM SPI1_CE0).
+        cs0_spidev              Set to 'disabled' to stop the creation of a
+                                userspace device node /dev/spidev1.0 (default
+                                is 'okay' 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 only accessible on devices with a 40pin header, eg:
+              A+, B+, Zero and PI2 B; as well as the Compute Module.
+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 'disabled' to stop the creation of a
+                                userspace device node /dev/spidev1.0 (default
+                                is 'okay' or enabled).
+        cs1_spidev              Set to 'disabled' to stop the creation of a
+                                userspace device node /dev/spidev1.1 (default
+                                is 'okay' 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 only accessible on devices with a 40pin header, eg:
+              A+, B+, Zero and PI2 B; as well as the Compute Module.
+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 'disabled' to stop the creation of a
+                                userspace device node /dev/spidev1.0 (default
+                                is 'okay' or enabled).
+        cs1_spidev              Set to 'disabled' to stop the creation of a
+                                userspace device node /dev/spidev1.1 (default
+                                is 'okay' or enabled).
+        cs2_spidev              Set to 'disabled' to stop the creation of a
+                                userspace device node /dev/spidev1.2 (default
+                                is 'okay' or enabled).
+
+
+Name:   spi2-1cs
+Info:   Enables spi2 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.: spi2 is only accessible with the Compute Module.
+Load:   dtoverlay=spi2-1cs,<param>=<val>
+Params: cs0_pin                 GPIO pin for CS0 (default 43 - BCM SPI2_CE0).
+        cs0_spidev              Set to 'disabled' to stop the creation of a
+                                userspace device node /dev/spidev2.0 (default
+                                is 'okay' or enabled).
+
+
+Name:   spi2-2cs
+Info:   Enables spi2 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.: spi2 is only accessible with the Compute Module.
+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 'disabled' to stop the creation of a
+                                userspace device node /dev/spidev2.0 (default
+                                is 'okay' or enabled).
+        cs1_spidev              Set to 'disabled' to stop the creation of a
+                                userspace device node /dev/spidev2.1 (default
+                                is 'okay' or enabled).
+
+
+Name:   spi2-3cs
+Info:   Enables spi2 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.
+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 'disabled' to stop the creation of a
+                                userspace device node /dev/spidev2.0 (default
+                                is 'okay' or enabled).
+        cs1_spidev              Set to 'disabled' to stop the creation of a
+                                userspace device node /dev/spidev2.1 (default
+                                is 'okay' or enabled).
+        cs2_spidev              Set to 'disabled' to stop the creation of a
+                                userspace device node /dev/spidev2.2 (default
+                                is 'okay' or enabled).
+
+
+Name:   spi3-1cs
+Info:   Enables spi3 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=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-2cs
+Info:   Enables spi3 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=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:   spi4-1cs
+Info:   Enables spi4 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 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 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 only.
+Load:   dtoverlay=spi5-1cs,<param>=<val>
+Params: cs0_pin                 GPIO pin for CS0 (default 12 - BCM SPI5_CE0).
+        cs0_spidev              Set to 'off' to prevent the creation of a
+                                userspace device node /dev/spidev5.0 (default
+                                is 'on' or enabled).
+
+
+Name:   spi5-2cs
+Info:   Enables spi5 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=spi5-2cs,<param>=<val>
+Params: cs0_pin                 GPIO pin for CS0 (default 12 - BCM SPI5_CE0).
+        cs1_pin                 GPIO pin for CS1 (default 26 - BCM SPI5_CE1).
+        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:   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)
+
+
+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:   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:   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:   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:   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:   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:   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:   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 Eric Anholt's DRM VC4 V3D driver on top of the dispmanx
+        display stack.
+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 Eric Anholt's 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
+        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)
+
+
+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
+
+
+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-kippah-7inch
+Info:   This overlay is now deprecated - see vc4-kms-dpi-panel,kippah-7inch
+Load:   <Deprecated>
+
+
+Name:   vc4-kms-v3d
+Info:   Enable Eric Anholt's DRM VC4 HDMI/HVS/V3D driver.
+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 Eric Anholt's 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-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-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:   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:   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
+
+
+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 (file)
index 0000000..2f4bbb4
--- /dev/null
@@ -0,0 +1,27 @@
+/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 = <&act_led>;
+               frag0: __overlay__ {
+                       gpios = <&gpio 0 0>;
+               };
+       };
+
+       __overrides__ {
+               gpio = <&frag0>,"gpios:4";
+               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 (file)
index 0000000..6e69bd7
--- /dev/null
@@ -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 (file)
index 0000000..e1ce94a
--- /dev/null
@@ -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 (file)
index 0000000..298488e
--- /dev/null
@@ -0,0 +1,40 @@
+// Definitions for ADAU1977 ADC
+/dts-v1/;
+/plugin/;
+
+/ {
+       compatible = "brcm,bcm2835";
+    
+       fragment@0 {
+               target = <&i2c>;
+               
+               __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>;
+               __overlay__ {
+                       status = "okay";
+               };
+       };
+
+       fragment@2 {
+               target = <&sound>;
+               __overlay__ {
+                       compatible = "adi,adau1977-adc";
+                       i2s-controller = <&i2s>;
+                       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 (file)
index 0000000..5fed769
--- /dev/null
@@ -0,0 +1,52 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+    compatible = "brcm,bcm2835";
+
+    fragment@0 {
+        target = <&i2s>;
+        __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>;
+            };
+            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 (file)
index 0000000..dc17646
--- /dev/null
@@ -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 (file)
index 0000000..e44ced7
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+ * TI ADS1115 multi-channel ADC overlay
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+       compatible = "brcm,bcm2835";
+
+       fragment@0 {
+               target = <&i2c_arm>;
+               __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>;
+                       };
+               };
+       };
+
+       fragment@1 {
+               target = <&ads1115>;
+               __dormant__ {
+                       #address-cells = <1>;
+                       #size-cells = <0>;
+
+                       channel_a: channel_a {
+                               reg = <4>;
+                               ti,gain = <1>;
+                               ti,datarate = <7>;
+                       };
+               };
+       };
+
+       fragment@2 {
+               target = <&ads1115>;
+               __dormant__ {
+                       #address-cells = <1>;
+                       #size-cells = <0>;
+
+                       channel_b: channel_b {
+                               reg = <5>;
+                               ti,gain = <1>;
+                               ti,datarate = <7>;
+                       };
+               };
+       };
+
+       fragment@3 {
+               target = <&ads1115>;
+               __dormant__ {
+                       #address-cells = <1>;
+                       #size-cells = <0>;
+
+                       channel_c: channel_c {
+                               reg = <6>;
+                               ti,gain = <1>;
+                               ti,datarate = <7>;
+                       };
+               };
+       };
+
+       fragment@4 {
+               target = <&ads1115>;
+               __dormant__ {
+                       #address-cells = <1>;
+                       #size-cells = <0>;
+
+                       channel_d: channel_d {
+                               reg = <7>;
+                               ti,gain = <1>;
+                               ti,datarate = <7>;
+                       };
+               };
+       };
+
+       __overrides__ {
+               addr =            <&ads1115>,"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/ads7846-overlay.dts b/arch/arm/boot/dts/overlays/ads7846-overlay.dts
new file mode 100644 (file)
index 0000000..1c5c9b6
--- /dev/null
@@ -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 0>;
+
+                               /* 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 (file)
index 0000000..f7e97c4
--- /dev/null
@@ -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>;
+               __dormant__ {
+                       brcm,media-controller;
+               };
+       };
+
+       __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 (file)
index 0000000..ea392e8
--- /dev/null
@@ -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 (file)
index 0000000..82f9b37
--- /dev/null
@@ -0,0 +1,49 @@
+// Definitions for Digital Dreamtime Akkordion using IQaudIO DAC+ or DACZero
+/dts-v1/;
+/plugin/;
+
+/ {
+       compatible = "brcm,bcm2835";
+
+       fragment@0 {
+               target = <&i2s>;
+               __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>;
+                       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 (file)
index 0000000..873cb2f
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * 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>;
+                       };
+               };
+       };
+
+       fragment@1 {
+               target = <&i2s>;
+               __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>;
+                       mute-gpios = <&gpio 6 1>;
+                       status = "okay";
+               };
+       };
+
+       __overrides__ {
+               24db_digital_gain = <&boss_dac>,"allo,24db_digital_gain?";
+               slave = <&boss_dac>,"allo,slave?";
+       };
+};
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 (file)
index 0000000..a6adfb4
--- /dev/null
@@ -0,0 +1,57 @@
+/* * Definitions for Allo Boss2 DAC boards
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+       compatible = "brcm,bcm2835";
+
+       fragment@0 {
+               target = <&i2s>;
+               __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 (file)
index 0000000..ea018ac
--- /dev/null
@@ -0,0 +1,44 @@
+// Definitions for Allo DigiOne
+/dts-v1/;
+/plugin/;
+
+/ {
+       compatible = "brcm,bcm2835";
+
+       fragment@0 {
+               target = <&i2s>;
+               __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>;
+                       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 (file)
index 0000000..b25fd68
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * Definitions for Allo Katana DAC boards
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+       compatible = "brcm,bcm2835";
+
+       fragment@0 {
+               target = <&i2s>;
+               __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-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 (file)
index 0000000..bfc66da
--- /dev/null
@@ -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>;
+               __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>;
+                       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 (file)
index 0000000..d47a35d
--- /dev/null
@@ -0,0 +1,57 @@
+// Definitions for Piano DAC
+/dts-v1/;
+/plugin/;
+
+/ {
+       compatible = "brcm,bcm2835";
+
+       fragment@0 {
+               target = <&i2s>;
+               __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>;
+                       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 (executable)
index 0000000..87523dc
--- /dev/null
@@ -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 (file)
index 0000000..bb18cca
--- /dev/null
@@ -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 (file)
index 0000000..4769296
--- /dev/null
@@ -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>;
+                    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>;
+        __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 (file)
index 0000000..19c8cb6
--- /dev/null
@@ -0,0 +1,94 @@
+// 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";
+
+                       arducam_64mp: arducam_64mp@1a {
+                               compatible = "arducam,64mp";
+                               reg = <0x1a>;
+                               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_64mp_0: endpoint {
+                                               remote-endpoint = <&csi1_ep>;
+                                               clock-lanes = <0>;
+                                               data-lanes = <1 2>;
+                                               clock-noncontinuous;
+                                               link-frequencies =
+                                                       /bits/ 64 <456000000>;
+                                       };
+                               };
+                       };
+               };
+       };
+
+       csi_frag: fragment@1 {
+               target = <&csi1>;
+               csi: __overlay__ {
+                       status = "okay";
+                       brcm,media-controller;
+
+                       port{
+                               csi1_ep: endpoint{
+                                       remote-endpoint = <&arducam_64mp_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";
+               };
+       };
+
+       __overrides__ {
+               rotation = <&arducam_64mp>,"rotation:0";
+               orientation = <&arducam_64mp>,"orientation:0";
+               media-controller = <&csi>,"brcm,media-controller?";
+               cam0 = <&i2c_frag>, "target:0=",<&i2c_vc>,
+                      <&csi_frag>, "target:0=",<&csi0>,
+                      <&clk_frag>, "target:0=",<&cam0_clk>,
+                      <&arducam_64mp>, "clocks:0=",<&cam0_clk>,
+                      <&arducam_64mp>, "VANA-supply:0=",<&cam0_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 (file)
index 0000000..7434e24
--- /dev/null
@@ -0,0 +1,94 @@
+// 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";
+                       brcm,media-controller;
+
+                       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";
+               };
+       };
+
+       __overrides__ {
+               rotation = <&arducam_pivariety>,"rotation:0";
+               orientation = <&arducam_pivariety>,"orientation:0";
+               media-controller = <&csi>,"brcm,media-controller?";
+               cam0 = <&i2c_frag>, "target:0=",<&i2c_vc>,
+                      <&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 (file)
index 0000000..5a3f457
--- /dev/null
@@ -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 (file)
index 0000000..57a66ea
--- /dev/null
@@ -0,0 +1,60 @@
+// Definitions for audioinjector.net audio add on soundcard
+/dts-v1/;
+/plugin/;
+
+/ {
+       compatible = "brcm,bcm2835";
+
+       fragment@0 {
+               target = <&i2s>;
+               __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>;
+                       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 (file)
index 0000000..7565ac4
--- /dev/null
@@ -0,0 +1,50 @@
+// Definitions for audioinjector.net audio soundcard
+/dts-v1/;
+/plugin/;
+
+/ {
+       compatible = "brcm,bcm2835";
+
+       fragment@0 {
+               target = <&i2s>;
+               __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>;
+                       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>;
+                               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 (file)
index 0000000..63e05cf
--- /dev/null
@@ -0,0 +1,55 @@
+// Definitions for audioinjector.net audio isolated soundcard
+/dts-v1/;
+/plugin/;
+
+/ {
+       compatible = "brcm,bcm2835";
+
+       fragment@0 {
+               target = <&i2s>;
+               __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>;
+                       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 (file)
index 0000000..fb4a467
--- /dev/null
@@ -0,0 +1,71 @@
+// Definitions for audioinjector.net audio add on soundcard
+/dts-v1/;
+/plugin/;
+
+/ {
+       compatible = "brcm,bcm2835";
+
+       fragment@0 {
+               target = <&i2s>;
+               __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>;
+                       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>;
+                               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 (file)
index 0000000..68f4427
--- /dev/null
@@ -0,0 +1,39 @@
+// Definitions for audioinjector.net audio add on soundcard
+/dts-v1/;
+/plugin/;
+
+/ {
+       compatible = "brcm,bcm2835";
+
+       fragment@0 {
+               target = <&i2s>;
+               __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>;
+                       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 (file)
index 0000000..81af263
--- /dev/null
@@ -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>;
+               __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>;
+                       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 (file)
index 0000000..7324890
--- /dev/null
@@ -0,0 +1,42 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+        compatible = "brcm,bcm2835";
+
+        fragment@0 {
+                target = <&audio_pins>;
+                frag0: __overlay__ {
+                };
+        };
+
+       fragment@1 {
+                target = <&audio_pins>;
+                __overlay__ {
+                        brcm,pins = < 12 13 >;
+                        brcm,function = < 4 >; /* alt0 alt0 */
+                };
+        };
+
+       fragment@2 {
+               target = <&audio_pins>;
+               __dormant__ {
+                        brcm,pins = < 18 19 >;
+                        brcm,function = < 2 >; /* alt5 alt5 */
+               };
+       };
+
+       fragment@3 {
+               target = <&audio>;
+               __overlay__  {
+                       brcm,disable-headphones = <0>;
+               };
+       };
+
+       __overrides__ {
+               swap_lr = <&frag0>, "swap_lr?";
+               enable_jack = <&frag0>, "enable_jack?";
+               pins_12_13 = <0>,"+1-2";
+               pins_18_19 = <0>,"-1+2";
+       };
+};
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 (file)
index 0000000..8fc2258
--- /dev/null
@@ -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/camera-mux-2port-overlay.dts b/arch/arm/boot/dts/overlays/camera-mux-2port-overlay.dts
new file mode 100644 (file)
index 0000000..ef24d5a
--- /dev/null
@@ -0,0 +1,409 @@
+// 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 */
+       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 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 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
+                               };
+
+                               i2c@1 {
+                                       reg = <1>;
+                                       #address-cells = <1>;
+                                       #size-cells = <0>;
+
+                                       #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 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
+                               };
+                       };
+               };
+       };
+
+       fragment@201 {
+               target = <&csi1>;
+               __overlay__ {
+                       status = "okay";
+
+                       brcm,media-controller;
+
+                       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-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-ov5647 = <&mux_in0>, "remote-endpoint:0=",<&ov5647_0_ep>,
+                             <&ov5647_0_ep>, "remote-endpoint:0=",<&mux_in0>,
+                             <&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";
+
+               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-ov5647 = <&mux_in1>, "remote-endpoint:0=",<&ov5647_1_ep>,
+                             <&ov5647_1_ep>, "remote-endpoint:0=",<&mux_in1>,
+                             <&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";
+
+               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";
+       };
+};
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 (file)
index 0000000..e1a9529
--- /dev/null
@@ -0,0 +1,684 @@
+// 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 */
+       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 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 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
+                               };
+
+                               i2c@1 {
+                                       reg = <1>;
+                                       #address-cells = <1>;
+                                       #size-cells = <0>;
+
+                                       #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 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
+                               };
+
+                               i2c@2 {
+                                       reg = <2>;
+                                       #address-cells = <1>;
+                                       #size-cells = <0>;
+
+                                       #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 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
+                               };
+
+                               i2c@3 {
+                                       reg = <3>;
+                                       #address-cells = <1>;
+                                       #size-cells = <0>;
+
+                                       #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 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
+                               };
+                       };
+               };
+       };
+
+       fragment@201 {
+               target = <&csi1>;
+               __overlay__ {
+                       status = "okay";
+
+                       brcm,media-controller;
+
+                       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-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-ov5647 = <&mux_in0>, "remote-endpoint:0=",<&ov5647_0_ep>,
+                             <&ov5647_0_ep>, "remote-endpoint:0=",<&mux_in0>,
+                             <&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";
+
+               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-ov5647 = <&mux_in1>, "remote-endpoint:0=",<&ov5647_1_ep>,
+                             <&ov5647_1_ep>, "remote-endpoint:0=",<&mux_in1>,
+                             <&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";
+
+               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-ov5647 = <&mux_in2>, "remote-endpoint:0=",<&ov5647_2_ep>,
+                             <&ov5647_2_ep>, "remote-endpoint:0=",<&mux_in2>,
+                             <&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";
+
+               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-ov5647 = <&mux_in3>, "remote-endpoint:0=",<&ov5647_3_ep>,
+                             <&ov5647_3_ep>, "remote-endpoint:0=",<&mux_in3>,
+                             <&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";
+
+               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";
+       };
+};
diff --git a/arch/arm/boot/dts/overlays/cap1106-overlay.dts b/arch/arm/boot/dts/overlays/cap1106-overlay.dts
new file mode 100644 (file)
index 0000000..0a585e7
--- /dev/null
@@ -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/chipdip-dac-overlay.dts b/arch/arm/boot/dts/overlays/chipdip-dac-overlay.dts
new file mode 100644 (file)
index 0000000..09c7417
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Device Tree overlay for ChipDip DAC
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+       compatible = "brcm,bcm2835";
+
+       fragment@0 {
+               target = <&i2s>;
+               __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>;
+                       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/cma-overlay.dts b/arch/arm/boot/dts/overlays/cma-overlay.dts
new file mode 100644 (file)
index 0000000..1d87c59
--- /dev/null
@@ -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/cutiepi-panel-overlay.dts b/arch/arm/boot/dts/overlays/cutiepi-panel-overlay.dts
new file mode 100644 (file)
index 0000000..6f9694e
--- /dev/null
@@ -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>;
+                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 (file)
index 0000000..4e03baa
--- /dev/null
@@ -0,0 +1,71 @@
+// Definitions for DACberry400
+/dts-v1/;
+/plugin/;
+/ {
+       compatible = "brcm,bcm2835";
+
+       fragment@0 {
+               target = <&i2s>;
+               __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>;
+                       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 (file)
index 0000000..8b0fc6b
--- /dev/null
@@ -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 (file)
index 0000000..128ef54
--- /dev/null
@@ -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>;
+               __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>;
+                       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 (file)
index 0000000..d863e5c
--- /dev/null
@@ -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>;
+               __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>;
+                       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 (file)
index 0000000..dfb8922
--- /dev/null
@@ -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>;
+                       status = "okay";
+               };
+       };
+
+       fragment@1 {
+               target = <&i2s>;
+               __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 (file)
index 0000000..d5a66e5
--- /dev/null
@@ -0,0 +1,64 @@
+/dts-v1/;
+/plugin/;
+
+/* Disable Bluetooth and restore UART0/ttyAMA0 over GPIOs 14 & 15.
+   To disable the systemd service that initialises the modem so it doesn't use
+   the UART:
+
+       sudo systemctl disable hciuart
+*/
+
+#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-wifi-overlay.dts b/arch/arm/boot/dts/overlays/disable-wifi-overlay.dts
new file mode 100644 (file)
index 0000000..75e0464
--- /dev/null
@@ -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/dpi18-overlay.dts b/arch/arm/boot/dts/overlays/dpi18-overlay.dts
new file mode 100644 (file)
index 0000000..4abe5be
--- /dev/null
@@ -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 (file)
index 0000000..50c88a1
--- /dev/null
@@ -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 (file)
index 0000000..44335cc
--- /dev/null
@@ -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 (file)
index 0000000..d18187d
--- /dev/null
@@ -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>;
+        __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>;
+            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>;
+            };
+
+            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 (file)
index 0000000..78c5e9f
--- /dev/null
@@ -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 (file)
index 0000000..0d83e34
--- /dev/null
@@ -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 (file)
index 0000000..1210e4b
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * Device Tree overlay for EDT 5406 touchscreen controller, as used on the
+ * Raspberry Pi 7" panel
+ *
+ */
+
+/dts-v1/;
+/plugin/;
+
+#include "edt-ft5406.dtsi"
+
+/ {
+       fragment@0 {
+               target = <&i2c0if>;
+               __overlay__ {
+                       status = "okay";
+               };
+       };
+
+       fragment@1 {
+               target = <&i2c0mux>;
+               __overlay__ {
+                       status = "okay";
+               };
+       };
+};
diff --git a/arch/arm/boot/dts/overlays/edt-ft5406.dtsi b/arch/arm/boot/dts/overlays/edt-ft5406.dtsi
new file mode 100644 (file)
index 0000000..2d0ff0e
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * 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 = <&i2c_csi_dsi>;
+               __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 (file)
index 0000000..7af5c2e
--- /dev/null
@@ -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 = <&eth1_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 = <&eth1>, "interrupts:0",
+                         <&eth1_pins>, "brcm,pins:0";
+               speed   = <&eth1>, "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 (file)
index 0000000..17cb5b8
--- /dev/null
@@ -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 = <&eth1_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 = <&eth1>, "interrupts:0",
+                         <&eth1_pins>, "brcm,pins:0";
+               speed   = <&eth1>, "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 (file)
index 0000000..6f087fb
--- /dev/null
@@ -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/fbtft-overlay.dts b/arch/arm/boot/dts/overlays/fbtft-overlay.dts
new file mode 100644 (file)
index 0000000..db45f8c
--- /dev/null
@@ -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 (file)
index 0000000..1ab71e6
--- /dev/null
@@ -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 = <&reg_1v8>;
+                               status = "okay";
+                       };
+               };
+       };
+
+       fragment@3 {
+               target = <&i2s>;
+               __overlay__ {
+                       status = "okay";
+               };
+       };
+
+       fragment@4 {
+               target = <&sound>;
+               __overlay__ {
+                       compatible = "fe-pi,fe-pi-audio";
+                       i2s-controller = <&i2s>;
+                       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 (file)
index 0000000..e9944f5
--- /dev/null
@@ -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 (file)
index 0000000..3d31030
--- /dev/null
@@ -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 (file)
index 0000000..7509e00
--- /dev/null
@@ -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>;
+               __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>;
+                       mute-gpios = <&amp 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 = <&amp>,"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 (file)
index 0000000..8571527
--- /dev/null
@@ -0,0 +1,46 @@
+// 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
+                       };
+               };
+       };
+
+       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__ {
+               interrupt = <&goodix_pins>,"brcm,pins:0",
+                       <&gt9271>,"interrupts:0",
+                       <&gt9271>,"irq-gpios:4";
+               reset = <&goodix_pins>,"brcm,pins:4",
+                       <&gt9271>,"reset-gpios:4";
+       };
+};
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 (file)
index 0000000..e443be1
--- /dev/null
@@ -0,0 +1,49 @@
+// Definitions for Google voiceHAT v1 soundcard overlay
+/dts-v1/;
+/plugin/;
+
+/ {
+       compatible = "brcm,bcm2835";
+
+       fragment@0 {
+               target = <&i2s>;
+               __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>;
+                       status = "okay";
+               };
+       };
+};
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 (file)
index 0000000..fa77c3f
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * 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>;
+               polling-delay = <2000>; /* milliseconds */
+               __overlay__ {
+                       trips {
+                               cpu_hot: trip-point@0 {
+                                       temperature = <55000>;  /* (millicelsius) Fan started at 55°C */
+                                       hysteresis = <10000>;   /* (millicelsius) Fan stopped at 45°C */
+                                       type = "active";
+                               };
+                       };
+                       cooling-maps {
+                               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 (file)
index 0000000..c9e3904
--- /dev/null
@@ -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 (file)
index 0000000..162b6ce
--- /dev/null
@@ -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 (file)
index 0000000..3625431
--- /dev/null
@@ -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 (file)
index 0000000..2e7253d
--- /dev/null
@@ -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 (executable)
index 0000000..d8e9d53
--- /dev/null
@@ -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 (executable)
index 0000000..96cbe80
--- /dev/null
@@ -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 (file)
index 0000000..55f9bff
--- /dev/null
@@ -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 (file)
index 0000000..8153f83
--- /dev/null
@@ -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 (file)
index 0000000..da14806
--- /dev/null
@@ -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/hd44780-lcd-overlay.dts b/arch/arm/boot/dts/overlays/hd44780-lcd-overlay.dts
new file mode 100644 (file)
index 0000000..ee72666
--- /dev/null
@@ -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 (file)
index 0000000..50b9a26
--- /dev/null
@@ -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-amp-overlay.dts b/arch/arm/boot/dts/overlays/hifiberry-amp-overlay.dts
new file mode 100644 (file)
index 0000000..142518a
--- /dev/null
@@ -0,0 +1,39 @@
+// Definitions for HiFiBerry Amp/Amp+
+/dts-v1/;
+/plugin/;
+
+/ {
+       compatible = "brcm,bcm2835";
+
+       fragment@0 {
+               target = <&i2s>;
+               __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>;
+                       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 (file)
index 0000000..ebdef55
--- /dev/null
@@ -0,0 +1,64 @@
+// 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>;
+                       };
+               };
+       };
+
+       fragment@1 {
+               target = <&i2s>;
+               __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>;
+                       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?";
+               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 (file)
index 0000000..a01e263
--- /dev/null
@@ -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>;
+               __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>;
+                       status = "okay";
+               };
+       };
+};
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 (file)
index 0000000..ea8a6c8
--- /dev/null
@@ -0,0 +1,34 @@
+// Definitions for HiFiBerry DAC
+/dts-v1/;
+/plugin/;
+
+/ {
+       compatible = "brcm,bcm2835";
+
+       fragment@0 {
+               target = <&i2s>;
+               __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>;
+                       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 (file)
index 0000000..ff19015
--- /dev/null
@@ -0,0 +1,65 @@
+// 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>;
+                       };
+               };
+       };
+
+       fragment@1 {
+               target = <&i2s>;
+               __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>;
+                       status = "okay";
+               };
+       };
+
+       __overrides__ {
+               24db_digital_gain =
+                       <&hifiberry_dacplus>,"hifiberry,24db_digital_gain?";
+               slave = <&hifiberry_dacplus>,"hifiberry-dacplus,slave?";
+               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 (file)
index 0000000..540563d
--- /dev/null
@@ -0,0 +1,72 @@
+// Definitions for HiFiBerry DAC+ADC
+/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>;
+               __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>;
+                       status = "okay";
+               };
+       };
+
+       __overrides__ {
+               24db_digital_gain =
+                       <&hifiberry_dacplusadc>,"hifiberry,24db_digital_gain?";
+               slave = <&hifiberry_dacplusadc>,"hifiberry-dacplusadc,slave?";
+               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 (file)
index 0000000..561cd84
--- /dev/null
@@ -0,0 +1,70 @@
+// 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>;
+                       };
+               };
+       };
+
+       fragment@1 {
+               target = <&i2s>;
+               __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>;
+                       status = "okay";
+               };
+       };
+
+       __overrides__ {
+               24db_digital_gain =
+                       <&hifiberry_dacplusadcpro>,"hifiberry-dacplusadcpro,24db_digital_gain?";
+               slave = <&hifiberry_dacplusadcpro>,"hifiberry-dacplusadcpro,slave?";
+               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 (file)
index 0000000..63432e8
--- /dev/null
@@ -0,0 +1,34 @@
+// Definitions for hifiberry DAC+DSP soundcard overlay
+/dts-v1/;
+/plugin/;
+
+/ {
+       compatible = "brcm,bcm2835";
+
+       fragment@0 {
+               target = <&i2s>;
+               __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>;
+                       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 (file)
index 0000000..b916513
--- /dev/null
@@ -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>;
+               __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>;
+                       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 (file)
index 0000000..a2309a5
--- /dev/null
@@ -0,0 +1,41 @@
+// Definitions for HiFiBerry Digi
+/dts-v1/;
+/plugin/;
+
+/ {
+       compatible = "brcm,bcm2835";
+
+       fragment@0 {
+               target = <&i2s>;
+               __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>;
+                       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 (file)
index 0000000..83de602
--- /dev/null
@@ -0,0 +1,43 @@
+// Definitions for HiFiBerry Digi Pro
+/dts-v1/;
+/plugin/;
+
+/ {
+       compatible = "brcm,bcm2835";
+
+       fragment@0 {
+               target = <&i2s>;
+               __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>;
+                       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 (file)
index 0000000..46cb76c
--- /dev/null
@@ -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 (file)
index 0000000..5843a5e
--- /dev/null
@@ -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 0>;
+                               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 (file)
index 0000000..95bfb1e
--- /dev/null
@@ -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 0>;
+                               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 (file)
index 0000000..9edd084
--- /dev/null
@@ -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 0>;
+                               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 (file)
index 0000000..0c4cff3
--- /dev/null
@@ -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>;
+                       status = "okay";
+               };
+       };
+
+       fragment@1 {
+               target = <&i2s>;
+               __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 (file)
index 0000000..8204b6b
--- /dev/null
@@ -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-fan-overlay.dts b/arch/arm/boot/dts/overlays/i2c-fan-overlay.dts
new file mode 100644 (file)
index 0000000..9474441
--- /dev/null
@@ -0,0 +1,100 @@
+// Definitions for I2C based sensors using the Industrial IO or HWMON interface.
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/thermal/thermal.h>
+
+/ {
+       compatible = "brcm,bcm2835";
+
+       fragment@0 {
+               target = <&i2cbus>;
+               __dormant__ {
+                       #address-cells = <1>;
+                       #size-cells = <0>;
+                       status = "okay";
+
+                       emc2301: emc2301@2f {
+                               compatible = "microchip,emc2301";
+                               reg = <0x2f>;
+                               status = "okay";
+                               #cooling-cells = <0x02>;
+                       };
+               };
+       };
+
+       frag100: fragment@100 {
+               target = <&i2c_arm>;
+               i2cbus: __overlay__ {
+                       status = "okay";
+               };
+       };
+
+       fragment@101 {
+               target = <&i2c0if>;
+               __dormant__ {
+                       status = "okay";
+               };
+       };
+
+       fragment@102 {
+               target = <&i2c0mux>;
+               __dormant__ {
+                       status = "okay";
+               };
+       };
+
+       fragment@103 {
+               target = <&cpu_thermal>;
+               polling-delay = <2000>; /* milliseconds */
+               __overlay__ {
+                       trips {
+                               fanmid0: fanmid0 {
+                                       temperature = <50000>;
+                                       hysteresis = <2000>;
+                                       type = "active";
+                               };
+                               fanmax0: fanmax0 {
+                                       temperature = <75000>;
+                                       hysteresis = <2000>;
+                                       type = "active";
+                               };
+                       };
+                       cooling-maps {
+                               map0: map0 {
+                                       trip = <&fanmid0>;
+                                       cooling-device = <&emc2301 2 6>;
+                               };
+                               map1: map1 {
+                                       trip = <&fanmax0>;
+                                       cooling-device = <&emc2301 7 THERMAL_NO_LIMIT>;
+                               };
+                       };
+               };
+       };
+
+       __overrides__ {
+               i2c0 =          <&frag100>,"target:0=",<&i2c0>;
+               i2c_csi_dsi =   <&frag100>,"target:0=",<&i2c_csi_dsi>,
+                               <0>,"+101+102";
+               i2c3 = <&frag100>, "target?=0",
+                      <&frag100>, "target-path=i2c3";
+               i2c4 = <&frag100>, "target?=0",
+                      <&frag100>, "target-path=i2c4";
+               i2c5 = <&frag100>, "target?=0",
+                      <&frag100>, "target-path=i2c5";
+               i2c6 = <&frag100>, "target?=0",
+                      <&frag100>, "target-path=i2c6";
+               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 (file)
index 0000000..63231b5
--- /dev/null
@@ -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 (file)
index 0000000..112aed9
--- /dev/null
@@ -0,0 +1,139 @@
+// Umbrella I2C Mux overlay
+
+/dts-v1/;
+/plugin/;
+
+/{
+       compatible = "brcm,bcm2835";
+
+       fragment@0 {
+               target = <&i2c_arm>;
+               __dormant__ {
+                       #address-cells = <1>;
+                       #size-cells = <0>;
+                       status = "okay";
+
+                       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 = <&i2c_arm>;
+               __dormant__ {
+                       #address-cells = <1>;
+                       #size-cells = <0>;
+                       status = "okay";
+
+                       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 = <&i2c_arm>;
+               __dormant__ {
+                       #address-cells = <1>;
+                       #size-cells = <0>;
+                       status = "okay";
+
+                       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";
+       };
+};
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 (file)
index 0000000..9bb1646
--- /dev/null
@@ -0,0 +1,26 @@
+// Definitions for NXP PCA9685A I2C PWM controller on ARM I2C bus.
+/dts-v1/;
+/plugin/;
+
+/{
+       compatible = "brcm,bcm2835";
+
+       fragment@0 {
+               target = <&i2c_arm>;
+               __overlay__ {
+                       #address-cells = <1>;
+                       #size-cells = <0>;
+                       status = "okay";
+
+                       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 (file)
index 0000000..5e03764
--- /dev/null
@@ -0,0 +1,337 @@
+// 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 = "ablic,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>;
+                       };
+               };
+       };
+
+
+       __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";
+
+               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",
+                                       <&rv1805>,"abracon,tc-resistor:0",
+                                       <&bq32000>,"abracon,tc-resistor: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?";
+       };
+};
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 (file)
index 0000000..c83480c
--- /dev/null
@@ -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 (file)
index 0000000..cd31eac
--- /dev/null
@@ -0,0 +1,42 @@
+// Definitions for several I2C based Real Time Clocks
+/dts-v1/;
+/plugin/;
+
+#include "i2c-rtc-common.dtsi"
+
+/ {
+       frag100: fragment@100 {
+               target = <&i2c_arm>;
+               i2cbus: __overlay__ {
+                       status = "okay";
+               };
+       };
+
+       fragment@101 {
+               target = <&i2c0if>;
+               __dormant__ {
+                       status = "okay";
+               };
+       };
+
+       fragment@102 {
+               target = <&i2c0mux>;
+               __dormant__ {
+                       status = "okay";
+               };
+       };
+
+       __overrides__ {
+               i2c0 = <&frag100>, "target:0=",<&i2c0>;
+               i2c_csi_dsi = <&frag100>, "target:0=",<&i2c_csi_dsi>,
+                             <0>,"+101+102";
+               i2c3 = <&frag100>, "target?=0",
+                      <&frag100>, "target-path=i2c3";
+               i2c4 = <&frag100>, "target?=0",
+                      <&frag100>, "target-path=i2c4";
+               i2c5 = <&frag100>, "target?=0",
+                      <&frag100>, "target-path=i2c5";
+               i2c6 = <&frag100>, "target?=0",
+                      <&frag100>, "target-path=i2c6";
+       };
+};
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 (executable)
index 0000000..2716898
--- /dev/null
@@ -0,0 +1,341 @@
+// Definitions for I2C based sensors using the Industrial IO or HWMON interface.
+/dts-v1/;
+/plugin/;
+
+/ {
+       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 = "htu21";
+                               reg = <0x40>;
+                               status = "okay";
+                       };
+               };
+       };
+
+       fragment@5 {
+               target = <&i2cbus>;
+               __dormant__ {
+                       #address-cells = <1>;
+                       #size-cells = <0>;
+                       status = "okay";
+
+                       lm75: lm75@4f {
+                               compatible = "lm75";
+                               reg = <0x4f>;
+                               status = "okay";
+                       };
+               };
+       };
+
+       fragment@6 {
+               target = <&i2cbus>;
+               __dormant__ {
+                       #address-cells = <1>;
+                       #size-cells = <0>;
+                       status = "okay";
+
+                       si7020: si7020@40 {
+                               compatible = "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 = "hdc100x";
+                               reg = <0x40>;
+                               status = "okay";
+                       };
+               };
+       };
+
+       fragment@9 {
+               target = <&i2cbus>;
+               __dormant__ {
+                       #address-cells = <1>;
+                       #size-cells = <0>;
+                       status = "okay";
+
+                       tsl4531: tsl4531@29 {
+                               compatible = "tsl4531";
+                               reg = <0x29>;
+                               status = "okay";
+                       };
+               };
+       };
+
+       fragment@10 {
+               target = <&i2cbus>;
+               __dormant__ {
+                       #address-cells = <1>;
+                       #size-cells = <0>;
+                       status = "okay";
+
+                       veml6070: veml6070@38 {
+                               compatible = "veml6070";
+                               reg = <0x38>;
+                               status = "okay";
+                       };
+               };
+       };
+
+       fragment@11 {
+               target = <&i2cbus>;
+               __dormant__ {
+                       #address-cells = <1>;
+                       #size-cells = <0>;
+                       status = "okay";
+
+                       sht3x: sht3x@44 {
+                               compatible = "sht3x";
+                               reg = <0x44>;
+                               status = "okay";
+                       };
+               };
+       };
+
+       fragment@12 {
+               target = <&i2cbus>;
+               __dormant__ {
+                       #address-cells = <1>;
+                       #size-cells = <0>;
+                       status = "okay";
+
+                       ds1621: ds1621@48 {
+                               compatible = "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 = "ccs811";
+                               reg = <0x5b>;
+                               status = "okay";
+                       };
+               };
+       };
+
+       fragment@18 {
+               target = <&i2cbus>;
+               __dormant__ {
+                       #address-cells = <1>;
+                       #size-cells = <0>;
+                       status = "okay";
+
+                       bh1750: bh1750@23 {
+                               compatible = "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 2>;
+                       };
+               };
+       };
+
+       __overrides__ {
+               bme280 = <0>,"+0";
+               bmp085 = <0>,"+1";
+               bmp180 = <0>,"+2";
+               bmp280 = <0>,"+3";
+               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";
+
+               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";
+               int_pin = <&max30102>, "interrupts: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 (executable)
index 0000000..f8a3965
--- /dev/null
@@ -0,0 +1,42 @@
+// Definitions for I2C based sensors using the Industrial IO or HWMON interface.
+/dts-v1/;
+/plugin/;
+
+#include "i2c-sensor-common.dtsi"
+
+/ {
+       frag100: fragment@100 {
+               target = <&i2c_arm>;
+               i2cbus: __overlay__ {
+                       status = "okay";
+               };
+       };
+
+       fragment@101 {
+               target = <&i2c0if>;
+               __dormant__ {
+                       status = "okay";
+               };
+       };
+
+       fragment@102 {
+               target = <&i2c0mux>;
+               __dormant__ {
+                       status = "okay";
+               };
+       };
+
+       __overrides__ {
+               i2c0 = <&frag100>, "target:0=",<&i2c0>;
+               i2c_csi_dsi = <&frag100>, "target:0=",<&i2c_csi_dsi>,
+                             <0>,"+101+102";
+               i2c3 = <&frag100>, "target?=0",
+                      <&frag100>, "target-path=i2c3";
+               i2c4 = <&frag100>, "target?=0",
+                      <&frag100>, "target-path=i2c4";
+               i2c5 = <&frag100>, "target?=0",
+                      <&frag100>, "target-path=i2c5";
+               i2c6 = <&frag100>, "target?=0",
+                      <&frag100>, "target-path=i2c6";
+       };
+};
diff --git a/arch/arm/boot/dts/overlays/i2c0-overlay.dts b/arch/arm/boot/dts/overlays/i2c0-overlay.dts
new file mode 100644 (file)
index 0000000..46bf1bf
--- /dev/null
@@ -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/i2c1-overlay.dts b/arch/arm/boot/dts/overlays/i2c1-overlay.dts
new file mode 100644 (file)
index 0000000..addaed7
--- /dev/null
@@ -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/i2c3-overlay.dts b/arch/arm/boot/dts/overlays/i2c3-overlay.dts
new file mode 100644 (file)
index 0000000..e24a1df
--- /dev/null
@@ -0,0 +1,36 @@
+/dts-v1/;
+/plugin/;
+
+/{
+       compatible = "brcm,bcm2711";
+
+       fragment@0 {
+               target = <&i2c3>;
+               frag0: __overlay__ {
+                       status = "okay";
+                       pinctrl-names = "default";
+                       pinctrl-0 = <&i2c3_pins>;
+                       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/i2c4-overlay.dts b/arch/arm/boot/dts/overlays/i2c4-overlay.dts
new file mode 100644 (file)
index 0000000..14c7f4d
--- /dev/null
@@ -0,0 +1,36 @@
+/dts-v1/;
+/plugin/;
+
+/{
+       compatible = "brcm,bcm2711";
+
+       fragment@0 {
+               target = <&i2c4>;
+               frag0: __overlay__ {
+                       status = "okay";
+                       pinctrl-names = "default";
+                       pinctrl-0 = <&i2c4_pins>;
+                       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 (file)
index 0000000..7953621
--- /dev/null
@@ -0,0 +1,36 @@
+/dts-v1/;
+/plugin/;
+
+/{
+       compatible = "brcm,bcm2711";
+
+       fragment@0 {
+               target = <&i2c5>;
+               frag0: __overlay__ {
+                       status = "okay";
+                       pinctrl-names = "default";
+                       pinctrl-0 = <&i2c5_pins>;
+                       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 (file)
index 0000000..555305a
--- /dev/null
@@ -0,0 +1,36 @@
+/dts-v1/;
+/plugin/;
+
+/{
+       compatible = "brcm,bcm2711";
+
+       fragment@0 {
+               target = <&i2c6>;
+               frag0: __overlay__ {
+                       status = "okay";
+                       pinctrl-names = "default";
+                       pinctrl-0 = <&i2c6_pins>;
+                       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-gpio28-31-overlay.dts b/arch/arm/boot/dts/overlays/i2s-gpio28-31-overlay.dts
new file mode 100644 (file)
index 0000000..cf43094
--- /dev/null
@@ -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/ilitek251x-overlay.dts b/arch/arm/boot/dts/overlays/ilitek251x-overlay.dts
new file mode 100644 (file)
index 0000000..551aba5
--- /dev/null
@@ -0,0 +1,45 @@
+// 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 //
+                       };
+               };
+       };
+
+       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";
+       };
+};
diff --git a/arch/arm/boot/dts/overlays/imx219-overlay.dts b/arch/arm/boot/dts/overlays/imx219-overlay.dts
new file mode 100644 (file)
index 0000000..aa97450
--- /dev/null
@@ -0,0 +1,89 @@
+// 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";
+                       brcm,media-controller;
+
+                       port {
+                               csi_ep: endpoint {
+                                       remote-endpoint = <&cam_endpoint>;
+                                       clock-lanes = <0>;
+                                       data-lanes = <1 2>;
+                                       clock-noncontinuous;
+                               };
+                       };
+               };
+       };
+
+       __overrides__ {
+               rotation = <&cam_node>,"rotation:0";
+               orientation = <&cam_node>,"orientation:0";
+               media-controller = <&csi>,"brcm,media-controller?";
+               cam0 = <&i2c_frag>, "target:0=",<&i2c_vc>,
+                      <&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>;
+       };
+};
+
+&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 (file)
index 0000000..fa870f7
--- /dev/null
@@ -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 (file)
index 0000000..7117682
--- /dev/null
@@ -0,0 +1,131 @@
+// 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";
+                       brcm,media-controller;
+
+                       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>;
+                       };
+               };
+       };
+
+       __overrides__ {
+               rotation = <&cam_node>,"rotation:0";
+               orientation = <&cam_node>,"orientation:0";
+               media-controller = <&csi>,"brcm,media-controller?";
+               cam0 = <&i2c_frag>, "target:0=",<&i2c_vc>,
+                      <&csi_frag>, "target:0=",<&csi0>,
+                      <&clk_frag>, "target:0=",<&cam0_clk>,
+                      <&reg_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 (file)
index 0000000..cca81e1
--- /dev/null
@@ -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 (file)
index 0000000..2e70e17
--- /dev/null
@@ -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,imx290";
+               };
+       };
+
+       fragment@102 {
+               target = <&cam_node>;
+               __dormant__ {
+                       compatible = "sony,imx290-mono";
+               };
+       };
+
+       __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 (file)
index 0000000..d8141de
--- /dev/null
@@ -0,0 +1,112 @@
+// 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
+/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 "imx290_327.dtsi"
+               };
+       };
+
+       csi_frag: fragment@1 {
+               target = <&csi1>;
+               csi: __overlay__ {
+                       status = "okay";
+                       brcm,media-controller;
+
+                       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>;
+               };
+       };
+
+       __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 = <&csi>,"brcm,media-controller?";
+               cam0 = <&i2c_frag>, "target:0=",<&i2c_vc>,
+                      <&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 (file)
index 0000000..9afe7e9
--- /dev/null
@@ -0,0 +1,24 @@
+// Fragment to configure and IMX290 / IMX327 / IMX462 image sensor
+
+cam_node: imx290@1a {
+       compatible = "sony,imx290";
+       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 (file)
index 0000000..85b6f7e
--- /dev/null
@@ -0,0 +1,103 @@
+// 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>;
+               __overlay__ {
+                       status = "okay";
+                       clock-frequency = <37125000>;
+               };
+       };
+
+       fragment@2 {
+               target = <&i2c0mux>;
+               __overlay__ {
+                       status = "okay";
+               };
+       };
+
+       reg_frag: fragment@5 {
+               target = <&cam1_reg>;
+               cam_reg: __overlay__ {
+                       startup-delay-us = <500000>;
+               };
+       };
+
+       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";
+                       brcm,media-controller;
+
+                       port {
+                               csi_ep: endpoint {
+                                       remote-endpoint = <&imx296_0>;
+                                       clock-lanes = <0>;
+                                       data-lanes = <1>;
+                                       clock-noncontinuous;
+                               };
+                       };
+               };
+       };
+
+       __overrides__ {
+               rotation = <&imx296>,"rotation:0";
+               orientation = <&imx296>,"orientation:0";
+               media-controller = <&csi>,"brcm,media-controller?";
+               cam0 = <&i2c_frag>, "target:0=",<&i2c_vc>,
+                      <&csi_frag>, "target:0=",<&csi0>,
+                      <&clk_frag>, "target:0=",<&cam0_clk>,
+                      <&imx296>, "clocks:0=",<&cam0_clk>,
+                      <&imx296>, "VANA-supply:0=",<&cam0_reg>;
+       };
+};
diff --git a/arch/arm/boot/dts/overlays/imx327-overlay.dts b/arch/arm/boot/dts/overlays/imx327-overlay.dts
new file mode 100644 (file)
index 0000000..d01a67c
--- /dev/null
@@ -0,0 +1,32 @@
+// 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,imx327";
+               };
+       };
+
+       fragment@102 {
+               target = <&cam_node>;
+               __dormant__ {
+                       compatible = "sony,imx327-mono";
+               };
+       };
+
+       __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 (file)
index 0000000..6a99991
--- /dev/null
@@ -0,0 +1,10 @@
+// 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";
+};
diff --git a/arch/arm/boot/dts/overlays/imx462-overlay.dts b/arch/arm/boot/dts/overlays/imx462-overlay.dts
new file mode 100644 (file)
index 0000000..e2b54a7
--- /dev/null
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Definitions for IMX462 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,imx462";
+               };
+       };
+
+       fragment@102 {
+               target = <&cam_node>;
+               __dormant__ {
+                       compatible = "sony,imx462-mono";
+               };
+       };
+
+       __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 (file)
index 0000000..f6482a9
--- /dev/null
@@ -0,0 +1,10 @@
+// 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";
+};
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 (file)
index 0000000..7afc0a2
--- /dev/null
@@ -0,0 +1,83 @@
+// 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>;
+               };
+       };
+
+       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";
+                       brcm,media-controller;
+
+                       port {
+                               csi_ep: endpoint {
+                                       remote-endpoint = <&cam_endpoint>;
+                                       clock-lanes = <0>;
+                                       data-lanes = <1 2>;
+                                       clock-noncontinuous;
+                               };
+                       };
+               };
+       };
+
+       __overrides__ {
+               rotation = <&cam_node>,"rotation:0";
+               orientation = <&cam_node>,"orientation:0";
+               media-controller = <&csi>,"brcm,media-controller?";
+               cam0 = <&i2c_frag>, "target:0=",<&i2c_vc>,
+                      <&csi_frag>, "target:0=",<&csi0>,
+                      <&clk_frag>, "target:0=",<&cam0_clk>,
+                      <&reg_frag>, "target:0=",<&cam0_reg>,
+                      <&cam_node>, "clocks:0=",<&cam0_clk>,
+                      <&cam_node>, "VANA-supply:0=",<&cam0_reg>;
+       };
+};
+
+&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 (file)
index 0000000..a0c154c
--- /dev/null
@@ -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/imx519-overlay.dts b/arch/arm/boot/dts/overlays/imx519-overlay.dts
new file mode 100644 (file)
index 0000000..ada1224
--- /dev/null
@@ -0,0 +1,96 @@
+// 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";
+
+                       imx519: imx519@1a {
+                               compatible = "sony,imx519";
+                               reg = <0x1a>;
+                               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 {
+                                       imx519_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";
+                       brcm,media-controller;
+
+                       port{
+                               csi1_ep: endpoint{
+                                       remote-endpoint = <&imx519_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";
+               };
+       };
+
+       __overrides__ {
+               rotation = <&imx519>,"rotation:0";
+               orientation = <&imx519>,"orientation:0";
+               media-controller = <&csi>,"brcm,media-controller?";
+               cam0 = <&i2c_frag>, "target:0=",<&i2c_vc>,
+                      <&csi_frag>, "target:0=",<&csi0>,
+                      <&clk_frag>, "target:0=",<&cam0_clk>,
+                      <&imx519>, "clocks:0=",<&cam0_clk>,
+                      <&imx519>, "VANA-supply:0=",<&cam0_reg>;
+       };
+};
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 (file)
index 0000000..9110f5d
--- /dev/null
@@ -0,0 +1,42 @@
+// Definitions for IQaudIO CODEC
+/dts-v1/;
+/plugin/;
+
+/ {
+       compatible = "brcm,bcm2835";
+
+       fragment@0 {
+               target = <&i2s>;
+               __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>;
+                       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 (file)
index 0000000..24073ca
--- /dev/null
@@ -0,0 +1,46 @@
+// Definitions for IQaudIO DAC
+/dts-v1/;
+/plugin/;
+
+/ {
+       compatible = "brcm,bcm2835";
+
+       fragment@0 {
+               target = <&i2s>;
+               __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>;
+                       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 (file)
index 0000000..7c70b25
--- /dev/null
@@ -0,0 +1,49 @@
+// Definitions for IQaudIO DAC+
+/dts-v1/;
+/plugin/;
+
+/ {
+       compatible = "brcm,bcm2835";
+
+       fragment@0 {
+               target = <&i2s>;
+               __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>;
+                       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 (file)
index 0000000..ee54095
--- /dev/null
@@ -0,0 +1,47 @@
+// Definitions for IQAudIO Digi WM8804 audio board
+/dts-v1/;
+/plugin/;
+
+/ {
+       compatible = "brcm,bcm2835";
+
+       fragment@0 {
+               target = <&i2s>;
+               __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>;
+                       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 (file)
index 0000000..c395693
--- /dev/null
@@ -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 (file)
index 0000000..8f8432c
--- /dev/null
@@ -0,0 +1,90 @@
+// 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>;
+               };
+       };
+
+       __overrides__ {
+               media-controller = <&csi>,"brcm,media-controller?";
+               cam0 = <&i2c_frag>, "target:0=",<&i2c_vc>,
+                      <&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 (file)
index 0000000..585c7db
--- /dev/null
@@ -0,0 +1,309 @@
+// 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>.
+//
+// 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 flash on spi0.0
+       fragment@8 {
+               target = <&spi0>;
+               __dormant__ {
+                       status = "okay";
+                        #address-cells = <1>;
+                        #size-cells = <0>;
+                       spi_nor_00: spi_nor@0 {
+                               #address-cells = <1>;
+                               #size-cells = <1>;
+                               compatible = "jedec,spi-nor";
+                               reg = <0>;
+                               spi-max-frequency = <500000>;
+                       };
+               };
+       };
+
+       // enable flash on spi0.1
+       fragment@9 {
+               target = <&spi0>;
+               __dormant__ {
+                       status = "okay";
+                        #address-cells = <1>;
+                        #size-cells = <0>;
+                       spi_nor_01: spi_nor@1 {
+                               #address-cells = <1>;
+                               #size-cells = <1>;
+                               compatible = "jedec,spi-nor";
+                               reg = <1>;
+                               spi-max-frequency = <500000>;
+                       };
+               };
+       };
+
+       // enable flash on spi1.0
+       fragment@10 {
+               target = <&spi1>;
+               __dormant__ {
+                       status = "okay";
+                        #address-cells = <1>;
+                        #size-cells = <0>;
+                       spi_nor_10: spi_nor@0 {
+                               #address-cells = <1>;
+                               #size-cells = <1>;
+                               compatible = "jedec,spi-nor";
+                               reg = <0>;
+                               spi-max-frequency = <500000>;
+                       };
+               };
+       };
+
+       // enable flash on spi1.1
+       fragment@11 {
+               target = <&spi1>;
+               __dormant__ {
+                       status = "okay";
+                        #address-cells = <1>;
+                        #size-cells = <0>;
+                       spi_nor_11: spi_nor@1 {
+                               #address-cells = <1>;
+                               #size-cells = <1>;
+                               compatible = "jedec,spi-nor";
+                               reg = <1>;
+                               spi-max-frequency = <500000>;
+                       };
+               };
+       };
+
+       // enable flash on spi1.2
+       fragment@12 {
+               target = <&spi1>;
+               __dormant__ {
+                       status = "okay";
+                        #address-cells = <1>;
+                        #size-cells = <0>;
+                       spi_nor_12: spi_nor@2 {
+                               #address-cells = <1>;
+                               #size-cells = <1>;
+                               compatible = "jedec,spi-nor";
+                               reg = <2>;
+                               spi-max-frequency = <500000>;
+                       };
+               };
+       };
+
+       // enable flash on spi2.0
+       fragment@13 {
+               target = <&spi2>;
+               __dormant__ {
+                       status = "okay";
+                        #address-cells = <1>;
+                        #size-cells = <0>;
+                       spi_nor_20: spi_nor@0 {
+                               #address-cells = <1>;
+                               #size-cells = <1>;
+                               compatible = "jedec,spi-nor";
+                               reg = <0>;
+                               spi-max-frequency = <500000>;
+                       };
+               };
+       };
+
+       // enable flash on spi2.1
+       fragment@14 {
+               target = <&spi2>;
+               __dormant__ {
+                       status = "okay";
+                        #address-cells = <1>;
+                        #size-cells = <0>;
+                       spi_nor_21: spi_nor@1 {
+                               #address-cells = <1>;
+                               #size-cells = <1>;
+                               compatible = "jedec,spi-nor";
+                               reg = <1>;
+                               spi-max-frequency = <500000>;
+                       };
+               };
+       };
+
+       // enable flash on spi2.2
+       fragment@15 {
+               target = <&spi2>;
+               __dormant__ {
+                       status = "okay";
+                        #address-cells = <1>;
+                        #size-cells = <0>;
+                       spi_nor_22: spi_nor@2 {
+                               #address-cells = <1>;
+                               #size-cells = <1>;
+                               compatible = "jedec,spi-nor";
+                               reg = <2>;
+                               spi-max-frequency = <500000>;
+                       };
+               };
+       };
+
+       // Enable fast read for device on spi0.0.
+       // Use default active low interrupt signalling.
+       fragment@16 {
+               target = <&spi_nor_00>;
+               __dormant__ {
+                       m25p,fast-read;
+               };
+       };
+
+       // Enable fast read for device on spi0.1.
+       // Use default active low interrupt signalling.
+       fragment@17 {
+               target = <&spi_nor_01>;
+               __dormant__ {
+                       m25p,fast-read;
+               };
+       };
+
+       // Enable fast read for device on spi1.0.
+       // Use default active low interrupt signalling.
+       fragment@18 {
+               target = <&spi_nor_10>;
+               __dormant__ {
+                       m25p,fast-read;
+               };
+       };
+
+       // Enable fast read for device on spi1.1.
+       // Use default active low interrupt signalling.
+       fragment@19 {
+               target = <&spi_nor_11>;
+               __dormant__ {
+                       m25p,fast-read;
+               };
+       };
+
+       // Enable fast read for device on spi1.2.
+       // Use default active low interrupt signalling.
+       fragment@20 {
+               target = <&spi_nor_12>;
+               __dormant__ {
+                       m25p,fast-read;
+               };
+       };
+
+       // Enable fast read for device on spi2.0.
+       // Use default active low interrupt signalling.
+       fragment@21 {
+               target = <&spi_nor_20>;
+               __dormant__ {
+                       m25p,fast-read;
+               };
+       };
+
+       // Enable fast read for device on spi2.1.
+       // Use default active low interrupt signalling.
+       fragment@22 {
+               target = <&spi_nor_21>;
+               __dormant__ {
+                       m25p,fast-read;
+               };
+       };
+
+       // Enable fast read for device on spi2.2.
+       // Use default active low interrupt signalling.
+       fragment@23 {
+               target = <&spi_nor_22>;
+               __dormant__ {
+                       m25p,fast-read;
+               };
+       };
+
+       __overrides__ {
+               flash-spi0-0       = <0>,"+0+8";
+               flash-spi0-1       = <0>,"+1+9";
+               flash-spi1-0       = <0>,"+2+10";
+               flash-spi1-1       = <0>,"+3+11";
+               flash-spi1-2       = <0>,"+4+12";
+               flash-spi2-0       = <0>,"+5+13";
+               flash-spi2-1       = <0>,"+6+14";
+               flash-spi2-2       = <0>,"+7+15";
+               flash-fastr-spi0-0 = <0>,"+0+8+16";
+               flash-fastr-spi0-1 = <0>,"+1+9+17";
+               flash-fastr-spi1-0 = <0>,"+2+10+18";
+               flash-fastr-spi1-1 = <0>,"+3+11+19";
+               flash-fastr-spi1-2 = <0>,"+4+12+20";
+               flash-fastr-spi2-0 = <0>,"+5+13+21";
+               flash-fastr-spi2-1 = <0>,"+6+14+22";
+               flash-fastr-spi2-2 = <0>,"+7+15+23";
+       };
+};
+
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 (file)
index 0000000..9c42670
--- /dev/null
@@ -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>;
+               __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>;
+                       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 (file)
index 0000000..d00515d
--- /dev/null
@@ -0,0 +1,46 @@
+// Definitions for JustBoom DAC
+/dts-v1/;
+/plugin/;
+
+/ {
+       compatible = "brcm,bcm2835";
+
+       fragment@0 {
+               target = <&i2s>;
+               __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>;
+                       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 (file)
index 0000000..e733360
--- /dev/null
@@ -0,0 +1,41 @@
+// Definitions for JustBoom Digi
+/dts-v1/;
+/plugin/;
+
+/ {
+       compatible = "brcm,bcm2835";
+
+       fragment@0 {
+               target = <&i2s>;
+               __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>;
+                       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 (file)
index 0000000..6d971f3
--- /dev/null
@@ -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 = <&ltc2941>, "lltc,resistor-sense:0",
+                                <&ltc2942>, "lltc,resistor-sense:0",
+                                <&ltc2943>, "lltc,resistor-sense:0",
+                                <&ltc2944>, "lltc,resistor-sense:0";
+               prescaler-exponent = <&ltc2941>, "lltc,prescaler-exponent:0",
+                                <&ltc2942>, "lltc,prescaler-exponent:0",
+                                <&ltc2943>, "lltc,prescaler-exponent:0",
+                                <&ltc2944>, "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 (file)
index 0000000..9e2afb0
--- /dev/null
@@ -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>;
+               __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>;
+                       };
+                       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>;
+                       };
+                       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 (file)
index 0000000..9964e24
--- /dev/null
@@ -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 (file)
index 0000000..840dd9b
--- /dev/null
@@ -0,0 +1,64 @@
+// Definitions for mbed DAC
+/dts-v1/;
+/plugin/;
+
+/ {
+       compatible = "brcm,bcm2835";
+
+       fragment@0 {
+               target = <&i2s>;
+               __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>;
+                       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>;
+                       };
+
+                       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 (file)
index 0000000..c546d8b
--- /dev/null
@@ -0,0 +1,69 @@
+// Definitions for MCP23017 Gpio Extender from Microchip Semiconductor
+
+/dts-v1/;
+/plugin/;
+
+/ {
+       compatible = "brcm,bcm2835";
+
+       fragment@0 {
+               target = <&i2c1>;
+               __overlay__ {
+                       status = "okay";
+               };
+       };
+
+       fragment@1 {
+               target = <&gpio>;
+               __overlay__ {
+                       mcp23017_pins: mcp23017_pins@20 {
+                               brcm,pins = <4>;
+                               brcm,function = <0>;
+                       };
+               };
+       };
+
+       fragment@2 {
+               target = <&i2c1>;
+               __overlay__ {
+                       #address-cells = <1>;
+                       #size-cells = <0>;
+
+                       mcp23017: mcp@20 {
+                               compatible = "microchip,mcp23017";
+                               reg = <0x20>;
+                               gpio-controller;
+                               #gpio-cells = <2>;
+
+                               status = "okay";
+                       };
+               };
+       };
+
+       fragment@3 {
+               target = <&mcp23017>;
+               __dormant__ {
+                       compatible = "microchip,mcp23008";
+               };
+       };
+
+       fragment@4 {
+               target = <&mcp23017>;
+               mcp23017_irq: __overlay__ {
+                       #interrupt-cells=<2>;
+                       interrupt-parent = <&gpio>;
+                       interrupts = <4 2>;
+                       interrupt-controller;
+                       microchip,irq-mirror;
+               };
+       };
+
+       __overrides__ {
+               gpiopin = <&mcp23017_pins>,"brcm,pins:0",
+                               <&mcp23017_irq>,"interrupts:0";
+               addr = <&mcp23017>,"reg:0", <&mcp23017_pins>,"reg:0";
+               mcp23008 = <0>,"=3";
+               noints = <0>,"!1!4";
+       };
+};
+
diff --git a/arch/arm/boot/dts/overlays/mcp23s17-overlay.dts b/arch/arm/boot/dts/overlays/mcp23s17-overlay.dts
new file mode 100644 (file)
index 0000000..484d64b
--- /dev/null
@@ -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 (executable)
index 0000000..46f143d
--- /dev/null
@@ -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 (file)
index 0000000..0a8dd57
--- /dev/null
@@ -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 (file)
index 0000000..cda1fb0
--- /dev/null
@@ -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 (file)
index 0000000..65c861b
--- /dev/null
@@ -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 = <&reg_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",
+                       <&reg_mcp251xfd_xceiver>, "name=reg-mcp251xfd-spi0-0-xceiver",
+                       <&reg_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",
+                       <&reg_mcp251xfd_xceiver>, "name=reg-mcp251xfd-spi0-1-xceiver",
+                       <&reg_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",
+                       <&reg_mcp251xfd_xceiver>, "name=reg-mcp251xfd-spi1-0-xceiver",
+                       <&reg_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",
+                       <&reg_mcp251xfd_xceiver>, "name=reg-mcp251xfd-spi1-1-xceiver",
+                       <&reg_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",
+                       <&reg_mcp251xfd_xceiver>, "name=reg-mcp251xfd-spi1-2-xceiver",
+                       <&reg_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",
+                       <&reg_mcp251xfd_xceiver>, "name=reg-mcp251xfd-spi2-0-xceiver",
+                       <&reg_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",
+                       <&reg_mcp251xfd_xceiver>, "name=reg-mcp251xfd-spi2-1-xceiver",
+                       <&reg_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",
+                       <&reg_mcp251xfd_xceiver>, "name=reg-mcp251xfd-spi2-2-xceiver",
+                       <&reg_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",
+                       <&reg_mcp251xfd_xceiver>, "gpio:4";
+               xceiver_active_high = <&reg_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 (executable)
index 0000000..957fdb9
--- /dev/null
@@ -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 (executable)
index 0000000..8e4e9f6
--- /dev/null
@@ -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 (file)
index 0000000..714eca5
--- /dev/null
@@ -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 (file)
index 0000000..9fab935
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ * 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>;
+                       };
+               };
+       };
+
+       fragment@4 {
+               target-path = "/";
+               __overlay__ {
+                       lirc_rpi: lirc_rpi {
+                               compatible = "rpi,lirc-rpi";
+                               pinctrl-names = "default";
+                               pinctrl-0 = <&lirc_pins>;
+                               status = "okay";
+
+                               // Override autodetection of IR receiver circuit
+                               // (0 = active high, 1 = active low, -1 = no override )
+                               rpi,sense = <0xffffffff>;
+
+                               // Software carrier
+                               // (0 = off, 1 = on)
+                               rpi,softcarrier = <1>;
+
+                               // Invert output
+                               // (0 = off, 1 = on)
+                               rpi,invert = <0>;
+
+                               // Enable debugging messages
+                               // (0 = off, 1 = on)
+                               rpi,debug = <0>;
+                       };
+               };
+       };
+
+       fragment@5 {
+               target = <&gpio>;
+               __overlay__ {
+                       lirc_pins: lirc_pins {
+                               brcm,pins = <6 5>;
+                               brcm,function = <1 0>; // out in
+                               brcm,pull = <0 1>; // off down
+                       };
+               };
+       };
+
+       __overrides__ {
+               speed =     <&rpidisplay>,"spi-max-frequency:0";
+               rotate =    <&rpidisplay>,"rotate:0";
+               fps =       <&rpidisplay>,"fps:0";
+               debug =     <&rpidisplay>,"debug:0", 
+                           <&lirc_rpi>,"rpi,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";
+
+               gpio_out_pin =  <&lirc_pins>,"brcm,pins:0";
+               gpio_in_pin =   <&lirc_pins>,"brcm,pins:4";
+               gpio_in_pull =  <&lirc_pins>,"brcm,pull:4";
+
+               sense =         <&lirc_rpi>,"rpi,sense:0";
+               softcarrier =   <&lirc_rpi>,"rpi,softcarrier:0";
+               invert =        <&lirc_rpi>,"rpi,invert: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 (file)
index 0000000..ec5c7c2
--- /dev/null
@@ -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>;
+               __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>;
+                       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 (file)
index 0000000..f7e44d2
--- /dev/null
@@ -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-uart1-overlay.dts b/arch/arm/boot/dts/overlays/midi-uart1-overlay.dts
new file mode 100644 (file)
index 0000000..e0bc410
--- /dev/null
@@ -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-uart2-overlay.dts b/arch/arm/boot/dts/overlays/midi-uart2-overlay.dts
new file mode 100644 (file)
index 0000000..66f3092
--- /dev/null
@@ -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,bcm2835";
+
+        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-uart3-overlay.dts b/arch/arm/boot/dts/overlays/midi-uart3-overlay.dts
new file mode 100644 (file)
index 0000000..55c6cb9
--- /dev/null
@@ -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,bcm2835";
+
+        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-uart4-overlay.dts b/arch/arm/boot/dts/overlays/midi-uart4-overlay.dts
new file mode 100644 (file)
index 0000000..5819df1
--- /dev/null
@@ -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,bcm2835";
+
+        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-uart5-overlay.dts b/arch/arm/boot/dts/overlays/midi-uart5-overlay.dts
new file mode 100644 (file)
index 0000000..a1d37f7
--- /dev/null
@@ -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,bcm2835";
+
+        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 (file)
index 0000000..5e0941e
--- /dev/null
@@ -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 (file)
index 0000000..da49f14
--- /dev/null
@@ -0,0 +1,93 @@
+/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 &bt_pins &fake_bt_cts>;
+                       status = "okay";
+               };
+       };
+
+       fragment@3 {
+               target = <&uart0_pins>;
+               __overlay__ {
+                       brcm,pins;
+                       brcm,function;
+                       brcm,pull;
+               };
+       };
+
+       fragment@4 {
+               target = <&uart1_pins>;
+               __overlay__ {
+                       brcm,pins = <32 33>;
+                       brcm,function = <2>; /* alt5=UART1 */
+                       brcm,pull = <0 2>;
+               };
+       };
+
+       fragment@5 {
+               target = <&gpio>;
+               __overlay__ {
+                       fake_bt_cts: fake_bt_cts {
+                               brcm,pins = <31>;
+                               brcm,function = <1>; /* output */
+                       };
+               };
+       };
+
+       fragment@6 {
+               target-path = "/aliases";
+               __overlay__ {
+                       serial0 = "/soc/serial@7e201000";
+                       serial1 = "/soc/serial@7e215040";
+               };
+       };
+
+       fragment@7 {
+               target = <&minibt>;
+               minibt_frag: __overlay__ {
+               };
+       };
+
+       __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 (file)
index 0000000..42b0e6b
--- /dev/null
@@ -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 = <16>;
+                               pwms = <&pwm 0 200000>;
+                       };
+               };
+       };
+
+       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 = <&audio>;
+               __dormant__  {
+                   brcm,disable-headphones;
+               };
+       };
+
+       __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 (file)
index 0000000..a2655ed
--- /dev/null
@@ -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 (file)
index 0000000..c1a2f69
--- /dev/null
@@ -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/mpu6050-overlay.dts b/arch/arm/boot/dts/overlays/mpu6050-overlay.dts
new file mode 100644 (file)
index 0000000..1b4c065
--- /dev/null
@@ -0,0 +1,29 @@
+// Definitions for MPU6050
+/dts-v1/;
+/plugin/;
+
+/ {
+        compatible = "brcm,bcm2835";
+
+        fragment@0 {
+                target = <&i2c1>;
+                __overlay__ {
+                        #address-cells = <1>;
+                        #size-cells = <0>;
+                        status = "okay";
+                        clock-frequency = <400000>;
+
+                        mpu6050: mpu6050@68 {
+                                compatible = "invensense,mpu6050";
+                                reg = <0x68>;
+                                interrupt-parent = <&gpio>;
+                                interrupts = <4 1>;
+                        };
+                };
+        };
+
+        __overrides__ {
+                interrupt = <&mpu6050>,"interrupts:0";
+                addr = <&mpu6050>,"reg: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 (file)
index 0000000..6e00e8b
--- /dev/null
@@ -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 0>;
+
+                               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 (file)
index 0000000..aeefca5
--- /dev/null
@@ -0,0 +1,77 @@
+// 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";
+                       brcm,media-controller;
+
+                       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>;
+               };
+       };
+
+       __overrides__ {
+               rotation = <&cam_node>,"rotation:0";
+               orientation = <&cam_node>,"orientation:0";
+               media-controller = <&csi>,"brcm,media-controller?";
+               cam0 = <&i2c_frag>, "target:0=",<&i2c_vc>,
+                      <&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 (file)
index 0000000..a1714d6
--- /dev/null
@@ -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 (file)
index 0000000..3fcb0b8
--- /dev/null
@@ -0,0 +1,92 @@
+// 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: ad5398@c {
+                               compatible = "adi,ad5398";
+                               reg = <0x0c>;
+                               status = "disabled";
+                               VANA-supply = <&cam1_reg>;
+                       };
+               };
+       };
+
+       csi_frag: fragment@1 {
+               target = <&csi1>;
+               csi: __overlay__ {
+                       status = "okay";
+                       brcm,media-controller;
+
+                       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";
+               };
+       };
+
+       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>;
+               };
+       };
+
+       __overrides__ {
+               rotation = <&cam_node>,"rotation:0";
+               orientation = <&cam_node>,"orientation:0";
+               media-controller = <&csi>,"brcm,media-controller?";
+               cam0 = <&i2c_frag>, "target:0=",<&i2c_vc>,
+                      <&csi_frag>, "target:0=",<&csi0>,
+                      <&reg_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 = <&vcm>, "status=okay",
+                     <&cam_node>,"lens-focus:0=", <&vcm>;
+       };
+};
+
+&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 (file)
index 0000000..6455a19
--- /dev/null
@@ -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/ov7251-overlay.dts b/arch/arm/boot/dts/overlays/ov7251-overlay.dts
new file mode 100644 (file)
index 0000000..9e490aa
--- /dev/null
@@ -0,0 +1,77 @@
+// 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";
+                       brcm,media-controller;
+
+                       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>;
+               };
+       };
+
+       __overrides__ {
+               rotation = <&cam_node>,"rotation:0";
+               orientation = <&cam_node>,"orientation:0";
+               media-controller = <&csi>,"brcm,media-controller?";
+               cam0 = <&i2c_frag>, "target:0=",<&i2c_vc>,
+                      <&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 (file)
index 0000000..561fed1
--- /dev/null
@@ -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 (file)
index 0000000..8a678d4
--- /dev/null
@@ -0,0 +1,78 @@
+// 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";
+                       brcm,media-controller;
+
+                       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";
+               };
+       };
+
+       clk_frag: fragment@4 {
+               target = <&cam1_clk>;
+               __overlay__ {
+                       status = "okay";
+                       clock-frequency = <24000000>;
+               };
+       };
+
+       __overrides__ {
+               rotation = <&cam_node>,"rotation:0";
+               orientation = <&cam_node>,"orientation:0";
+               media-controller = <&csi>,"brcm,media-controller?";
+               cam0 = <&i2c_frag>, "target:0=",<&i2c_vc>,
+                      <&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/ov9281.dtsi b/arch/arm/boot/dts/overlays/ov9281.dtsi
new file mode 100644 (file)
index 0000000..7df43bc
--- /dev/null
@@ -0,0 +1,27 @@
+// 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>;
+                       clock-noncontinuous;
+                       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 (file)
index 0000000..df63a17
--- /dev/null
@@ -0,0 +1,195 @@
+/dts-v1/;
+
+/ {
+       bmp085_i2c-sensor {
+               deprecated = "use i2c-sensor,bmp085";
+       };
+
+       cutiepi-panel {
+               bcm2711;
+       };
+
+       highperi {
+               bcm2711;
+       };
+
+       i2c0-bcm2708 {
+               deprecated = "use i2c0";
+       };
+
+       i2c1-bcm2708 {
+               deprecated = "use i2c1";
+       };
+
+       i2c3 {
+               bcm2711;
+       };
+
+       i2c4 {
+               bcm2711;
+       };
+
+       i2c5 {
+               bcm2711;
+       };
+
+       i2c6 {
+               bcm2711;
+       };
+
+       lirc-rpi {
+               deprecated = "use gpio-ir";
+       };
+
+       midi-uart2 {
+               bcm2711;
+       };
+
+       midi-uart3 {
+               bcm2711;
+       };
+
+       midi-uart4 {
+               bcm2711;
+       };
+
+       midi-uart5 {
+               bcm2711;
+       };
+
+       pcie-32bit-dma {
+               bcm2711;
+       };
+
+       pi3-act-led {
+               renamed = "act-led";
+       };
+
+       pi3-disable-bt {
+               renamed = "disable-bt";
+       };
+
+       pi3-disable-wifi {
+               renamed = "disable-wifi";
+       };
+
+       pi3-miniuart-bt {
+               renamed = "miniuart-bt";
+       };
+
+       ramoops {
+               bcm2835;
+               bcm2711 = "ramoops-pi4";
+       };
+
+       ramoops-pi4 {
+               bcm2711;
+       };
+
+       rpivid-v4l2 {
+               deprecated = "no longer necessary";
+       };
+
+       sdio-1bit {
+               deprecated = "use sdio,bus_width=1,gpios_22_25";
+       };
+
+       sdtweak {
+               deprecated = "use 'dtparam=sd_poll_once' etc.";
+       };
+
+       spi0-cs {
+               renamed = "spi0-2cs";
+       };
+
+       spi0-hw-cs {
+               deprecated = "no longer necessary";
+       };
+
+       spi3-1cs {
+               bcm2711;
+       };
+
+       spi3-2cs {
+               bcm2711;
+       };
+
+       spi4-1cs {
+               bcm2711;
+       };
+
+       spi4-2cs {
+               bcm2711;
+       };
+
+       spi5-1cs {
+               bcm2711;
+       };
+
+       spi5-2cs {
+               bcm2711;
+       };
+
+       spi6-1cs {
+               bcm2711;
+       };
+
+       spi6-2cs {
+               bcm2711;
+       };
+
+       uart2 {
+               bcm2711;
+       };
+
+       uart3 {
+               bcm2711;
+       };
+
+       uart4 {
+               bcm2711;
+       };
+
+       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";
+       };
+
+       vc4-fkms-v3d-pi4 {
+               bcm2711;
+       };
+
+       vc4-kms-dpi-at056tn53v1 {
+               deprecated = "use vc4-kms-dpi-panel,at056tn53v1";
+       };
+
+       vc4-kms-v3d {
+               bcm2835;
+               bcm2711 = "vc4-kms-v3d-pi4";
+       };
+
+       vc4-kms-v3d-pi4 {
+               bcm2711;
+       };
+
+       vl805 {
+               bcm2711;
+       };
+};
diff --git a/arch/arm/boot/dts/overlays/papirus-overlay.dts b/arch/arm/boot/dts/overlays/papirus-overlay.dts
new file mode 100644 (file)
index 0000000..d3e819c
--- /dev/null
@@ -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 = "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 (file)
index 0000000..8b6ee44
--- /dev/null
@@ -0,0 +1,240 @@
+// Definitions for NXP PCA953x family of I2C GPIO controllers on ARM I2C bus.
+/dts-v1/;
+/plugin/;
+
+/{
+       compatible = "brcm,bcm2835";
+
+       fragment@0 {
+               target = <&i2c_arm>;
+               __overlay__ {
+                       #address-cells = <1>;
+                       #size-cells = <0>;
+                       status = "okay";
+
+                       pca: pca@20 {
+                               compatible = "nxp,pca9534";
+                               reg = <0x20>;
+                               gpio-controller;
+                               #gpio-cells = <2>;
+
+                               status = "okay";
+                       };
+               };
+       };
+
+       fragment@1 {
+               target = <&pca>;
+               __dormant__ {
+                       compatible = "nxp,pca6416";
+               };
+       };
+       fragment@2 {
+               target = <&pca>;
+               __dormant__ {
+                       compatible = "nxp,pca9505";
+               };
+       };
+       fragment@3 {
+               target = <&pca>;
+               __dormant__ {
+                       compatible = "nxp,pca9535";
+               };
+       };
+       fragment@4 {
+               target = <&pca>;
+               __dormant__ {
+                       compatible = "nxp,pca9536";
+               };
+       };
+       fragment@5 {
+               target = <&pca>;
+               __dormant__ {
+                       compatible = "nxp,pca9537";
+               };
+       };
+       fragment@6 {
+               target = <&pca>;
+               __dormant__ {
+                       compatible = "nxp,pca9538";
+               };
+       };
+       fragment@7 {
+               target = <&pca>;
+               __dormant__ {
+                       compatible = "nxp,pca9539";
+               };
+       };
+       fragment@8 {
+               target = <&pca>;
+               __dormant__ {
+                       compatible = "nxp,pca9554";
+               };
+       };
+       fragment@9 {
+               target = <&pca>;
+               __dormant__ {
+                       compatible = "nxp,pca9555";
+               };
+       };
+       fragment@10 {
+               target = <&pca>;
+               __dormant__ {
+                       compatible = "nxp,pca9556";
+               };
+       };
+       fragment@11 {
+               target = <&pca>;
+               __dormant__ {
+                       compatible = "nxp,pca9557";
+               };
+       };
+       fragment@12 {
+               target = <&pca>;
+               __dormant__ {
+                       compatible = "nxp,pca9574";
+               };
+       };
+       fragment@13 {
+               target = <&pca>;
+               __dormant__ {
+                       compatible = "nxp,pca9575";
+               };
+       };
+       fragment@14 {
+               target = <&pca>;
+               __dormant__ {
+                       compatible = "nxp,pca9698";
+               };
+       };
+       fragment@15 {
+               target = <&pca>;
+               __dormant__ {
+                       compatible = "nxp,pca16416";
+               };
+       };
+       fragment@16 {
+               target = <&pca>;
+               __dormant__ {
+                       compatible = "nxp,pca16524";
+               };
+       };
+       fragment@17 {
+               target = <&pca>;
+               __dormant__ {
+                       compatible = "nxp,pca19555a";
+               };
+       };
+       fragment@18 {
+               target = <&pca>;
+               __dormant__ {
+                       compatible = "maxim,max7310";
+               };
+       };
+       fragment@19 {
+               target = <&pca>;
+               __dormant__ {
+                       compatible = "maxim,max7312";
+               };
+       };
+       fragment@20 {
+               target = <&pca>;
+               __dormant__ {
+                       compatible = "maxim,max7313";
+               };
+       };
+       fragment@21 {
+               target = <&pca>;
+               __dormant__ {
+                       compatible = "maxim,max7315";
+               };
+       };
+       fragment@22 {
+               target = <&pca>;
+               __dormant__ {
+                       compatible = "ti,pca6107";
+               };
+       };
+       fragment@23 {
+               target = <&pca>;
+               __dormant__ {
+                       compatible = "ti,tca6408";
+               };
+       };
+       fragment@24 {
+               target = <&pca>;
+               __dormant__ {
+                       compatible = "ti,tca6416";
+               };
+       };
+       fragment@25 {
+               target = <&pca>;
+               __dormant__ {
+                       compatible = "ti,tca6424";
+               };
+       };
+       fragment@26 {
+               target = <&pca>;
+               __dormant__ {
+                       compatible = "ti,tca9539";
+               };
+       };
+       fragment@27 {
+               target = <&pca>;
+               __dormant__ {
+                       compatible = "ti,tca9554";
+               };
+       };
+       fragment@28 {
+               target = <&pca>;
+               __dormant__ {
+                       compatible = "onnn,cat9554";
+               };
+       };
+       fragment@29 {
+               target = <&pca>;
+               __dormant__ {
+                       compatible = "onnn,pca9654";
+               };
+       };
+       fragment@30 {
+               target = <&pca>;
+               __dormant__ {
+                       compatible = "exar,xra1202";
+               };
+       };
+
+       __overrides__ {
+               addr = <&pca>,"reg:0";
+               pca6416 = <0>, "+1";
+               pca9505 = <0>, "+2";
+               pca9535 = <0>, "+3";
+               pca9536 = <0>, "+4";
+               pca9537 = <0>, "+5";
+               pca9538 = <0>, "+6";
+               pca9539 = <0>, "+7";
+               pca9554 = <0>, "+8";
+               pca9555 = <0>, "+9";
+               pca9556 = <0>, "+10";
+               pca9557 = <0>, "+11";
+               pca9574 = <0>, "+12";
+               pca9575 = <0>, "+13";
+               pca9698 = <0>, "+14";
+               pca16416 = <0>, "+15";
+               pca16524 = <0>, "+16";
+               pca19555a = <0>, "+17";
+               max7310 = <0>, "+18";
+               max7312 = <0>, "+19";
+               max7313 = <0>, "+20";
+               max7315 = <0>, "+21";
+               pca6107 = <0>, "+22";
+               tca6408 = <0>, "+23";
+               tca6416 = <0>, "+24";
+               tca6424 = <0>, "+25";
+               tca9539 = <0>, "+26";
+               tca9554 = <0>, "+27";
+               cat9554 = <0>, "+28";
+               pca9654 = <0>, "+29";
+               xra1202 = <0>, "+30";
+       };
+};
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 (file)
index 0000000..9557035
--- /dev/null
@@ -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/pibell-overlay.dts b/arch/arm/boot/dts/overlays/pibell-overlay.dts
new file mode 100644 (file)
index 0000000..9333a9b
--- /dev/null
@@ -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>;
+        __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>;
+
+/* 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>;
+
+/* 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 (file)
index 0000000..532a858
--- /dev/null
@@ -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 (file)
index 0000000..51a20e5
--- /dev/null
@@ -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>;
+               __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>;
+                       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 (file)
index 0000000..67f50db
--- /dev/null
@@ -0,0 +1,49 @@
+// Overlay for PiFi-DAC-HD
+/dts-v1/;
+/plugin/;
+
+/ {
+       compatible = "brcm,bcm2835";
+
+       fragment@0 {
+               target = <&i2s>;
+               __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>;
+                               };
+                               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 (file)
index 0000000..645ea74
--- /dev/null
@@ -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>;
+                                       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>;
+               __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 (file)
index 0000000..963597d
--- /dev/null
@@ -0,0 +1,42 @@
+// Definitions for PiFi Mini 210
+/dts-v1/;
+/plugin/;
+
+/ {
+       compatible = "brcm,bcm2835";
+
+       fragment@0 {
+               target = <&i2s>;
+               __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>;
+
+                       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 (file)
index 0000000..075bcee
--- /dev/null
@@ -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/piscreen-overlay.dts b/arch/arm/boot/dts/overlays/piscreen-overlay.dts
new file mode 100644 (file)
index 0000000..1ac75a2
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * Device Tree overlay for PiScreen 3.5" display shield by Ozzmaker
+ *
+ */
+
+/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__ {
+                       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 1>;
+                               dc-gpios = <&gpio 24 0>;
+                               led-gpios = <&gpio 22 0>;
+                               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 0>;
+                               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";
+               fps =           <&piscreen>,"fps:0";
+               debug =         <&piscreen>,"debug:0";
+               xohms =         <&piscreen_ts>,"ti,x-plate-ohms;0";
+       };
+};
diff --git a/arch/arm/boot/dts/overlays/piscreen2r-overlay.dts b/arch/arm/boot/dts/overlays/piscreen2r-overlay.dts
new file mode 100644 (file)
index 0000000..9d2b511
--- /dev/null
@@ -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 0>;
+                               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 (file)
index 0000000..49efb2b
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ * Pisound Linux kernel module.
+ * Copyright (C) 2016-2017  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>;
+                               pinctrl-names = "default";
+                               pinctrl-0 = <&spi0_pins>;
+                               spi-max-frequency = <1000000>;
+                       };
+               };
+       };
+
+       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>;
+                       status = "okay";
+
+                       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__ {
+                       pinctrl-names = "default";
+                       pinctrl-0 = <&pisound_button_pins>;
+
+                       pisound_button_pins: pisound_button_pins {
+                               brcm,pins = <17>;
+                               brcm,function = <0>; // Input
+                               brcm,pull = <2>; // Pull-Up
+                       };
+               };
+       };
+
+       fragment@7 {
+               target = <&i2s>;
+               __overlay__ {
+                       status = "okay";
+               };
+       };
+};
diff --git a/arch/arm/boot/dts/overlays/pitft22-overlay.dts b/arch/arm/boot/dts/overlays/pitft22-overlay.dts
new file mode 100644 (file)
index 0000000..4c44ab6
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * 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";
+                fps =     <&pitft>,"fps:0";
+                debug =   <&pitft>,"debug:0";
+        };
+};
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 (file)
index 0000000..33901ee
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * 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";
+                fps =     <&pitft>,"fps:0";
+                debug =   <&pitft>,"debug:0";
+                touch-sizex = <&ft6236>,"touchscreen-size-x?";
+                touch-sizey = <&ft6236>,"touchscreen-size-y?";
+                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 (file)
index 0000000..cfe7d2f
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ * 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", "multi-inno,mi0283qt";
+                               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 {
+                               compatible = "st,stmpe610";
+                               reg = <1>;
+
+                               spi-max-frequency = <500000>;
+                               irq-gpio = <&gpio 24 0x2>; /* IRQF_TRIGGER_FALLING */
+                               interrupts = <24 2>; /* high-to-low edge triggered */
+                               interrupt-parent = <&gpio>;
+                               interrupt-controller;
+
+                               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=multi-inno,mi0283qt";
+       };
+};
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 (file)
index 0000000..fc0f5e5
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+ * 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", "adafruit,yx350hv15";
+                               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 {
+                               compatible = "st,stmpe610";
+                               reg = <1>;
+
+                               spi-max-frequency = <500000>;
+                               irq-gpio = <&gpio 24 0x2>; /* IRQF_TRIGGER_FALLING */
+                               interrupts = <24 2>; /* high-to-low edge triggered */
+                               interrupt-parent = <&gpio>;
+                               interrupt-controller;
+
+                               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>;
+       };
+};
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 (file)
index 0000000..524a1c1
--- /dev/null
@@ -0,0 +1,38 @@
+/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?";
+       };
+};
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 (file)
index 0000000..4ddbbfa
--- /dev/null
@@ -0,0 +1,49 @@
+/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>;
+                       assigned-clock-rates = <100000000>;
+                       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-ir-tx-overlay.dts b/arch/arm/boot/dts/overlays/pwm-ir-tx-overlay.dts
new file mode 100644 (file)
index 0000000..119caf7
--- /dev/null
@@ -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>;
+                       };
+               };
+       };
+
+       __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 (file)
index 0000000..92876ab
--- /dev/null
@@ -0,0 +1,45 @@
+/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>;
+                       assigned-clock-rates = <100000000>;
+                       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/qca7000-overlay.dts b/arch/arm/boot/dts/overlays/qca7000-overlay.dts
new file mode 100644 (file)
index 0000000..f695f36
--- /dev/null
@@ -0,0 +1,55 @@
+// Overlay for the Qualcomm Atheros QCA7000 on PLC Stamp micro EVK
+// Visit: https://in-tech-smartcharging.com/products/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 = <&eth1_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 = <&eth1>, "interrupts:0",
+                         <&eth1_pins>, "brcm,pins:0";
+               speed   = <&eth1>, "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 (file)
index 0000000..f103916
--- /dev/null
@@ -0,0 +1,46 @@
+// Overlay for the Qualcomm Atheros QCA7000 on PLC Stamp micro EVK
+// Visit: https://in-tech-smartcharging.com/products/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 = <&eth2>, "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 (file)
index 0000000..e503865
--- /dev/null
@@ -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 (file)
index 0000000..4f3d30e
--- /dev/null
@@ -0,0 +1,25 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+       compatible = "brcm,bcm2835";
+
+       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/rotary-encoder-overlay.dts b/arch/arm/boot/dts/overlays/rotary-encoder-overlay.dts
new file mode 100644 (file)
index 0000000..ea1d952
--- /dev/null
@@ -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 (file)
index 0000000..cac5e44
--- /dev/null
@@ -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-cirrus-wm5102-overlay.dts b/arch/arm/boot/dts/overlays/rpi-cirrus-wm5102-overlay.dts
new file mode 100644 (file)
index 0000000..ed0c274
--- /dev/null
@@ -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>;
+               __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>;
+                       status = "okay";
+               };
+       };
+};
diff --git a/arch/arm/boot/dts/overlays/rpi-dac-overlay.dts b/arch/arm/boot/dts/overlays/rpi-dac-overlay.dts
new file mode 100644 (file)
index 0000000..07a9153
--- /dev/null
@@ -0,0 +1,34 @@
+// Definitions for RPi DAC
+/dts-v1/;
+/plugin/;
+
+/ {
+       compatible = "brcm,bcm2835";
+
+       fragment@0 {
+               target = <&i2s>;
+               __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>;
+                       status = "okay";
+               };
+       };
+};
diff --git a/arch/arm/boot/dts/overlays/rpi-display-overlay.dts b/arch/arm/boot/dts/overlays/rpi-display-overlay.dts
new file mode 100644 (file)
index 0000000..789644b
--- /dev/null
@@ -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>;
+                       };
+               };
+       };
+
+       fragment@22 {
+               target = <&pwm>;
+               __dormant__ {
+                       assigned-clock-rates = <1000000>;
+                       status = "okay";
+               };
+       };
+
+       fragment@23 {
+               target = <&audio>;
+               __dormant__  {
+                   brcm,disable-headphones;
+               };
+       };
+
+       __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/rpi-ft5406-overlay.dts b/arch/arm/boot/dts/overlays/rpi-ft5406-overlay.dts
new file mode 100644 (file)
index 0000000..8483c4f
--- /dev/null
@@ -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-poe-overlay.dts b/arch/arm/boot/dts/overlays/rpi-poe-overlay.dts
new file mode 100644 (file)
index 0000000..b8bef5c
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+ * 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>;
+                       };
+               };
+       };
+
+       fragment@1 {
+               target = <&cpu_thermal>;
+               __overlay__ {
+                       trips {
+                               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";
+                               };
+                       };
+                       cooling-maps {
+                               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@2 {
+               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@3 {
+               target = <&firmware>;
+               __overlay__ {
+                       fwpwm: pwm {
+                               compatible = "raspberrypi,firmware-poe-pwm";
+                               #pwm-cells = <2>;
+                       };
+               };
+       };
+
+       fragment@4 {
+               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@5 {
+               target = <&i2c0if>;
+               __dormant__ {
+                       status = "okay";
+               };
+       };
+
+       fragment@6 {
+               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 (file)
index 0000000..54deda2
--- /dev/null
@@ -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>;
+};
+
+&params {
+       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-proto-overlay.dts b/arch/arm/boot/dts/overlays/rpi-proto-overlay.dts
new file mode 100644 (file)
index 0000000..9cda044
--- /dev/null
@@ -0,0 +1,39 @@
+// Definitions for Rpi-Proto
+/dts-v1/;
+/plugin/;
+
+/ {
+       compatible = "brcm,bcm2835";
+
+       fragment@0 {
+               target = <&i2s>;
+               __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>;
+                       status = "okay";
+               };
+       };
+};
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 (file)
index 0000000..89d8d2e
--- /dev/null
@@ -0,0 +1,47 @@
+// rpi-sense HAT
+/dts-v1/;
+/plugin/;
+
+/ {
+       compatible = "brcm,bcm2835";
+
+       fragment@0 {
+               target = <&i2c1>;
+               __overlay__ {
+                       #address-cells = <1>;
+                       #size-cells = <0>;
+                       status = "okay";
+
+                       rpi-sense@46 {
+                               compatible = "rpi,rpi-sense";
+                               reg = <0x46>;
+                               keys-int-gpios = <&gpio 23 1>;
+                               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";
+                       };
+               };
+       };
+};
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 (file)
index 0000000..3c97a54
--- /dev/null
@@ -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 (file)
index 0000000..87e9a32
--- /dev/null
@@ -0,0 +1,49 @@
+// Definitions for RRA DigiDAC1 Audio card
+/dts-v1/;
+/plugin/;
+
+/ {
+       compatible = "brcm,bcm2835";
+
+       fragment@0 {
+               target = <&i2s>;
+               __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>;
+                       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 (file)
index 0000000..c51f1c0
--- /dev/null
@@ -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 (file)
index 0000000..04d74d6
--- /dev/null
@@ -0,0 +1,43 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+       compatible = "brcm,bcm2835";
+
+       fragment@0 {
+               target = <&i2c_arm>;
+               __overlay__ {
+                       #address-cells = <1>;
+                       #size-cells = <0>;
+                       status = "okay";
+
+                       sc16is750: sc16is750@48 {
+                               compatible = "nxp,sc16is750";
+                               reg = <0x48>; /* i2c address */
+                               clocks = <&sc16is750_clk>;
+                               interrupt-parent = <&gpio>;
+                               interrupts = <24 2>; /* IRQ_TYPE_EDGE_FALLING */
+                               gpio-controller;
+                               #gpio-cells = <2>;
+                               i2c-max-frequency = <400000>;
+                       };
+               };
+       };
+
+       fragment@1 {
+               target-path = "/";
+               __overlay__ {
+                       sc16is750_clk: sc16is750_i2c_clk@48 {
+                               compatible = "fixed-clock";
+                               #clock-cells = <0>;
+                               clock-frequency = <14745600>;
+                       };
+               };
+       };
+
+       __overrides__ {
+               int_pin = <&sc16is750>,"interrupts:0";
+               addr = <&sc16is750>,"reg:0", <&sc16is750_clk>,"name";
+               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 (file)
index 0000000..da05e98
--- /dev/null
@@ -0,0 +1,43 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+       compatible = "brcm,bcm2835";
+
+       fragment@0 {
+               target = <&i2c_arm>;
+               __overlay__ {
+                       #address-cells = <1>;
+                       #size-cells = <0>;
+                       status = "okay";
+
+                       sc16is752: sc16is752@48 {
+                               compatible = "nxp,sc16is752";
+                               reg = <0x48>; /* i2c address */
+                               clocks = <&sc16is752_clk>;
+                               interrupt-parent = <&gpio>;
+                               interrupts = <24 2>; /* IRQ_TYPE_EDGE_FALLING */
+                               gpio-controller;
+                               #gpio-cells = <2>;
+                               i2c-max-frequency = <400000>;
+                       };
+               };
+       };
+
+       fragment@1 {
+               target-path = "/";
+               __overlay__ {
+                       sc16is752_clk: sc16is752_i2c_clk@48 {
+                               compatible = "fixed-clock";
+                               #clock-cells = <0>;
+                               clock-frequency = <14745600>;
+                       };
+               };
+       };
+
+       __overrides__ {
+               int_pin = <&sc16is752>,"interrupts: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 (file)
index 0000000..a49a047
--- /dev/null
@@ -0,0 +1,49 @@
+/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 */
+                               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>;
+                       };
+               };
+       };
+
+       __overrides__ {
+               int_pin = <&sc16is752>,"interrupts: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 (file)
index 0000000..730c6e8
--- /dev/null
@@ -0,0 +1,67 @@
+/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>;
+               __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 */
+                               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";
+               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 (file)
index 0000000..0b72b4e
--- /dev/null
@@ -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 (file)
index 0000000..873e490
--- /dev/null
@@ -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/seeed-can-fd-hat-v1-overlay.dts b/arch/arm/boot/dts/overlays/seeed-can-fd-hat-v1-overlay.dts
new file mode 100644 (file)
index 0000000..210d027
--- /dev/null
@@ -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 (file)
index 0000000..e843d0b
--- /dev/null
@@ -0,0 +1,117 @@
+// 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 = <&i2cbus>;
+               __overlay__ {
+                       #address-cells = <1>;
+                       #size-cells = <0>;
+                       pcf85063@51 {
+                               compatible = "nxp,pcf85063";
+                               reg = <0x51>;
+                       };
+               };
+       };
+       fragment@9 {
+               target = <&i2c_arm>;
+               i2cbus: __overlay__ {
+                       status = "okay";
+               };
+       };
+};
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 (file)
index 0000000..57a0cc9
--- /dev/null
@@ -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 (file)
index 0000000..90495f0
--- /dev/null
@@ -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 (file)
index 0000000..bafab6c
--- /dev/null
@@ -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 (file)
index 0000000..ae1e503
--- /dev/null
@@ -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 (file)
index 0000000..bb8c783
--- /dev/null
@@ -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 (file)
index 0000000..a132b86
--- /dev/null
@@ -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 (file)
index 0000000..9ebcaf1
--- /dev/null
@@ -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 (file)
index 0000000..51b7fec
--- /dev/null
@@ -0,0 +1,75 @@
+// Definitions for several SPI-based Real Time Clocks
+/dts-v1/;
+/plugin/;
+
+/ {
+       compatible = "brcm,bcm2835";
+
+       fragment@0 {
+               target = <&rtc>;
+               __dormant__ {
+                       compatible = "maxim,ds3232";
+               };
+       };
+
+       fragment@1 {
+               target = <&rtc>;
+               __dormant__ {
+                       compatible = "maxim,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 (file)
index 0000000..0d2acab
--- /dev/null
@@ -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-overlay.dts b/arch/arm/boot/dts/overlays/spi0-1cs-overlay.dts
new file mode 100644 (file)
index 0000000..e6eb66e
--- /dev/null
@@ -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 (file)
index 0000000..df65195
--- /dev/null
@@ -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 (file)
index 0000000..ea2794b
--- /dev/null
@@ -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 (file)
index 0000000..dab34ee
--- /dev/null
@@ -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 (file)
index 0000000..bc7e7d0
--- /dev/null
@@ -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 (file)
index 0000000..2a29750
--- /dev/null
@@ -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-2cs-overlay.dts b/arch/arm/boot/dts/overlays/spi2-2cs-overlay.dts
new file mode 100644 (file)
index 0000000..642678f
--- /dev/null
@@ -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-3cs-overlay.dts b/arch/arm/boot/dts/overlays/spi2-3cs-overlay.dts
new file mode 100644 (file)
index 0000000..28d40c6
--- /dev/null
@@ -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 (file)
index 0000000..335af86
--- /dev/null
@@ -0,0 +1,44 @@
+/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>;
+
+                       pinctrl-names = "default";
+                       pinctrl-0 = <&spi3_pins &spi3_cs_pins>;
+                       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-2cs-overlay.dts b/arch/arm/boot/dts/overlays/spi3-2cs-overlay.dts
new file mode 100644 (file)
index 0000000..ce65da2
--- /dev/null
@@ -0,0 +1,56 @@
+/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>;
+
+                       pinctrl-names = "default";
+                       pinctrl-0 = <&spi3_pins &spi3_cs_pins>;
+                       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/spi4-1cs-overlay.dts b/arch/arm/boot/dts/overlays/spi4-1cs-overlay.dts
new file mode 100644 (file)
index 0000000..85d70b4
--- /dev/null
@@ -0,0 +1,44 @@
+/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>;
+
+                       pinctrl-names = "default";
+                       pinctrl-0 = <&spi4_pins &spi4_cs_pins>;
+                       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 (file)
index 0000000..8bc2215
--- /dev/null
@@ -0,0 +1,56 @@
+/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>;
+
+                       pinctrl-names = "default";
+                       pinctrl-0 = <&spi4_pins &spi4_cs_pins>;
+                       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 (file)
index 0000000..c0f8cb8
--- /dev/null
@@ -0,0 +1,44 @@
+/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>;
+
+                       pinctrl-names = "default";
+                       pinctrl-0 = <&spi5_pins &spi5_cs_pins>;
+                       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-2cs-overlay.dts b/arch/arm/boot/dts/overlays/spi5-2cs-overlay.dts
new file mode 100644 (file)
index 0000000..7758b9c
--- /dev/null
@@ -0,0 +1,56 @@
+/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>;
+
+                       pinctrl-names = "default";
+                       pinctrl-0 = <&spi5_pins &spi5_cs_pins>;
+                       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/spi6-1cs-overlay.dts b/arch/arm/boot/dts/overlays/spi6-1cs-overlay.dts
new file mode 100644 (file)
index 0000000..8c8a953
--- /dev/null
@@ -0,0 +1,44 @@
+/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>;
+
+                       pinctrl-names = "default";
+                       pinctrl-0 = <&spi6_pins &spi6_cs_pins>;
+                       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 (file)
index 0000000..2ff897f
--- /dev/null
@@ -0,0 +1,56 @@
+/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>;
+
+                       pinctrl-names = "default";
+                       pinctrl-0 = <&spi6_pins &spi6_cs_pins>;
+                       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 (file)
index 0000000..84cf10e
--- /dev/null
@@ -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 (file)
index 0000000..ffc90c7
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * 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";
+       };
+};
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 (file)
index 0000000..9fd5ebf
--- /dev/null
@@ -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 (file)
index 0000000..ffc872c
--- /dev/null
@@ -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/superaudioboard-overlay.dts b/arch/arm/boot/dts/overlays/superaudioboard-overlay.dts
new file mode 100755 (executable)
index 0000000..bad6153
--- /dev/null
@@ -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>;
+                       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>;
+                               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>;
+               __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 (file)
index 0000000..1d10693
--- /dev/null
@@ -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 (file)
index 0000000..047695b
--- /dev/null
@@ -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>;
+               __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_slave>;
+                       simple-audio-card,frame-master = <&dailink0_slave>;
+                       status = "okay";
+
+                       simple-audio-card,cpu {
+                               sound-dai = <&i2s>;
+                               dai-tdm-slot-num = <2>;
+                               dai-tdm-slot-width = <32>;
+                       };
+                       dailink0_slave: 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 (file)
index 0000000..c3eebfd
--- /dev/null
@@ -0,0 +1,109 @@
+// 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>;
+               };
+       };
+
+       __overrides__ {
+               4lane = <0>, "-2+3-7+8";
+               link-frequency = <&tc358743_0>,"link-frequencies#0";
+               media-controller = <&csi>,"brcm,media-controller?";
+               cam0 = <&i2c_frag>, "target:0=",<&i2c_vc>,
+                      <&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 (file)
index 0000000..a102b09
--- /dev/null
@@ -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 0>;
+                               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 (file)
index 0000000..e691885
--- /dev/null
@@ -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/uart0-overlay.dts b/arch/arm/boot/dts/overlays/uart0-overlay.dts
new file mode 100755 (executable)
index 0000000..6bf2e0f
--- /dev/null
@@ -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/uart1-overlay.dts b/arch/arm/boot/dts/overlays/uart1-overlay.dts
new file mode 100644 (file)
index 0000000..64163bf
--- /dev/null
@@ -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/uart2-overlay.dts b/arch/arm/boot/dts/overlays/uart2-overlay.dts
new file mode 100644 (file)
index 0000000..9face24
--- /dev/null
@@ -0,0 +1,27 @@
+/dts-v1/;
+/plugin/;
+
+/{
+       compatible = "brcm,bcm2711";
+
+       fragment@0 {
+               target = <&uart2>;
+               __overlay__ {
+                       pinctrl-names = "default";
+                       pinctrl-0 = <&uart2_pins>;
+                       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/uart3-overlay.dts b/arch/arm/boot/dts/overlays/uart3-overlay.dts
new file mode 100644 (file)
index 0000000..ae9f9fe
--- /dev/null
@@ -0,0 +1,27 @@
+/dts-v1/;
+/plugin/;
+
+/{
+       compatible = "brcm,bcm2711";
+
+       fragment@0 {
+               target = <&uart3>;
+               __overlay__ {
+                       pinctrl-names = "default";
+                       pinctrl-0 = <&uart3_pins>;
+                       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/uart4-overlay.dts b/arch/arm/boot/dts/overlays/uart4-overlay.dts
new file mode 100644 (file)
index 0000000..ac004ff
--- /dev/null
@@ -0,0 +1,27 @@
+/dts-v1/;
+/plugin/;
+
+/{
+       compatible = "brcm,bcm2711";
+
+       fragment@0 {
+               target = <&uart4>;
+               __overlay__ {
+                       pinctrl-names = "default";
+                       pinctrl-0 = <&uart4_pins>;
+                       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/uart5-overlay.dts b/arch/arm/boot/dts/overlays/uart5-overlay.dts
new file mode 100644 (file)
index 0000000..04eaf37
--- /dev/null
@@ -0,0 +1,27 @@
+/dts-v1/;
+/plugin/;
+
+/{
+       compatible = "brcm,bcm2711";
+
+       fragment@0 {
+               target = <&uart5>;
+               __overlay__ {
+                       pinctrl-names = "default";
+                       pinctrl-0 = <&uart5_pins>;
+                       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 (file)
index 0000000..ae7c379
--- /dev/null
@@ -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>;
+        __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>;
+            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>;
+            };
+
+            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 (file)
index 0000000..fc8d9b1
--- /dev/null
@@ -0,0 +1,49 @@
+// Definitions for the ugreen dabboard I2S
+/dts-v1/;
+/plugin/;
+
+/ {
+       compatible = "brcm,bcm2835";
+
+       fragment@0 {
+               target = <&i2s>;
+               __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_slave>;
+                       simple-audio-card,frame-master = <&dailink0_slave>;
+                       simple-audio-card,widgets = "Microphone", "Microphone Jack";
+                       status = "okay";
+                       simple-audio-card,cpu {
+                               sound-dai = <&i2s>;
+                       };
+                       dailink0_slave: 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 (file)
index 0000000..2852bea
--- /dev/null
@@ -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 = <&audio>;
+               __overlay__ {
+                       brcm,disable-hdmi;
+               };
+       };
+       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 (file)
index 0000000..f432863
--- /dev/null
@@ -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 = <&audio>;
+               __overlay__ {
+                       brcm,disable-hdmi;
+               };
+       };
+       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 (file)
index 0000000..ca34449
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * vc4-fkms-v3d-overlay.dts
+ */
+
+/dts-v1/;
+/plugin/;
+
+#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";
+               };
+       };
+};
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 (file)
index 0000000..7792ead
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * vc4-fkms-v3d-overlay.dts
+ */
+
+/dts-v1/;
+/plugin/;
+
+#include "cma-overlay.dts"
+
+&frag0 {
+       size = <((320-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";
+               };
+       };
+};
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 (file)
index 0000000..85875c2
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * 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";
+
+               width-mm = <&panel>, "width-mm:0";
+               height-mm = <&panel>, "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=0x1020",
+                       <&dpi_node_generic>, "pinctrl-0:0=",<&dpi_16bit_cpadhi_gpio0>;
+               bgr666 = <&panel_generic>, "bus-format:0=0x101f";
+               bgr666-padhi = <&panel_generic>, "bus-format:0=0x101e",
+                       <&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";
+       };
+};
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 (file)
index 0000000..585402a
--- /dev/null
@@ -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 (file)
index 0000000..4cd9d6a
--- /dev/null
@@ -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 (file)
index 0000000..eafc25a
--- /dev/null
@@ -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 (file)
index 0000000..7000463
--- /dev/null
@@ -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 (file)
index 0000000..ee9e2e8
--- /dev/null
@@ -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 (file)
index 0000000..f78fa48
--- /dev/null
@@ -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>;
+                       };
+               };
+       };
+
+       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 = <&audio>;
+               __dormant__  {
+                   brcm,disable-headphones;
+               };
+       };
+
+       __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 (file)
index 0000000..5e1700d
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * Device Tree overlay for RaspberryPi 7" Touchscreen panel
+ *
+ */
+
+/dts-v1/;
+/plugin/;
+
+#include "edt-ft5406.dtsi"
+
+/ {
+       /* No compatible as it will have come from edt-ft5406.dtsi */
+
+       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 = <&reg_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_disp1: panel_disp1@0 {
+                               reg = <0>;
+                               compatible = "raspberrypi,7inch-dsi", "simple-panel";
+                               backlight = <&reg_display>;
+                               power-supply = <&reg_display>;
+
+                               port {
+                                       panel_in: endpoint {
+                                               remote-endpoint = <&bridge_out>;
+                                       };
+                               };
+                       };
+
+                       reg_bridge: reg_bridge@0 {
+                               reg = <0>;
+                               compatible = "regulator-fixed";
+                               regulator-name = "bridge_reg";
+                               gpio = <&reg_display 0 0>;
+                               vin-supply = <&reg_display>;
+                               enable-active-high;
+                       };
+               };
+       };
+
+       fragment@2 {
+               target = <&i2c_csi_dsi>;
+               __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";
+               };
+       };
+       fragment@5 {
+               target = <&ft5406>;
+               __overlay__ {
+                       vcc-supply = <&reg_display>;
+                       reset-gpio = <&reg_display 1 1>;
+               };
+       };
+
+       __overrides__ {
+               disable_touch = <0>, "-10-11-12";
+       };
+};
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 (file)
index 0000000..d7b8f67
--- /dev/null
@@ -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 = <&lt070me05000_pins>,"brcm,pins:8",
+                       <&lt070me05000>,"reset-gpios:4";
+
+               enable = <&lt070me05000_pins>,"brcm,pins:0",
+                       <&lt070me05000>,"enable-gpios:4";
+
+               dcdc-en = <&lt070me05000_pins>,"brcm,pins:4",
+                       <&lt070me05000>,"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 (file)
index 0000000..5dcd0f2
--- /dev/null
@@ -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-kippah-7inch-overlay.dts b/arch/arm/boot/dts/overlays/vc4-kms-kippah-7inch-overlay.dts
new file mode 100644 (file)
index 0000000..4c1aa1c
--- /dev/null
@@ -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 (file)
index 0000000..1e9afd8
--- /dev/null
@@ -0,0 +1,124 @@
+/*
+ * vc4-kms-v3d-overlay.dts
+ */
+
+/dts-v1/;
+/plugin/;
+
+#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 = <&audio>;
+               __overlay__  {
+                   brcm,disable-hdmi;
+               };
+       };
+
+       __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 (file)
index 0000000..39df213
--- /dev/null
@@ -0,0 +1,200 @@
+/*
+ * vc4-kms-v3d-pi4-overlay.dts
+ */
+
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/clock/bcm2835.h>
+
+#include "cma-overlay.dts"
+
+&frag0 {
+       size = <((320-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 = <&audio>;
+               __overlay__  {
+                   brcm,disable-hdmi;
+               };
+       };
+
+       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-vga666-overlay.dts b/arch/arm/boot/dts/overlays/vc4-kms-vga666-overlay.dts
new file mode 100644 (file)
index 0000000..6e78709
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * 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";
+               };
+       };
+
+       __overrides__ {
+               ddc = <0>,"=2", <0>,"=3", <0>,"=4";
+       };
+};
diff --git a/arch/arm/boot/dts/overlays/vga666-overlay.dts b/arch/arm/boot/dts/overlays/vga666-overlay.dts
new file mode 100644 (file)
index 0000000..a4968d1
--- /dev/null
@@ -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 (file)
index 0000000..81adf34
--- /dev/null
@@ -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 (file)
index 0000000..f44e325
--- /dev/null
@@ -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-pullup-overlay.dts b/arch/arm/boot/dts/overlays/w1-gpio-pullup-overlay.dts
new file mode 100644 (file)
index 0000000..953c6a1
--- /dev/null
@@ -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/w5500-overlay.dts b/arch/arm/boot/dts/overlays/w5500-overlay.dts
new file mode 100644 (file)
index 0000000..4d3e662
--- /dev/null
@@ -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 = <&eth1_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 = <&eth1>, "interrupts:0",
+                         <&eth1_pins>, "brcm,pins:0";
+               speed   = <&eth1>, "spi-max-frequency:0";
+               cs      = <&eth1>, "reg:0",
+                         <0>, "!0=1";
+       };
+};
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 (file)
index 0000000..59388cc
--- /dev/null
@@ -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 (file)
index 0000000..b250492
--- /dev/null
@@ -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 (file)
index 0000000..71ce806
--- /dev/null
@@ -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 (file)
index 0000000..289fa4d
--- /dev/null
@@ -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>;
+               __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 {
+                               compatible = "wlf,wm8960";
+                               reg = <0x1a>;
+                               #sound-dai-cells = <0>;
+                               AVDD-supply = <&vdd_5v0_reg>;
+                               DVDD-supply = <&vdd_3v3_reg>;
+                       };
+               };
+       };
+
+
+       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>;
+                       };
+                       dailink0_slave: simple-audio-card,codec {
+                               sound-dai = <&wm8960>;
+                               clocks = <&wm8960_mclk>;
+                               clock-names = "mclk";
+                       };
+               };
+       };
+
+       __overrides__ {
+               alsaname = <&slave_overlay>,"simple-audio-card,name";
+               compatible = <&wm8960>,"compatible";
+       };
+};
diff --git a/arch/arm/configs/bcm2709_defconfig b/arch/arm/configs/bcm2709_defconfig
new file mode 100644 (file)
index 0000000..985722d
--- /dev/null
@@ -0,0 +1,1558 @@
+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_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_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_EMBEDDED=y
+# CONFIG_COMPAT_BRK is not set
+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_CPU_SW_DOMAIN_PAN 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_POWERSAVE=y
+CONFIG_CPU_FREQ_GOV_PERFORMANCE=y
+CONFIG_CPU_FREQ_GOV_USERSPACE=y
+CONFIG_CPU_FREQ_GOV_ONDEMAND=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_CRYPTO_SHA1_ARM_NEON=m
+CONFIG_CRYPTO_AES_ARM=m
+CONFIG_CRYPTO_AES_ARM_BS=m
+CONFIG_JUMP_LABEL=y
+CONFIG_MODULES=y
+CONFIG_MODULE_UNLOAD=y
+CONFIG_MODVERSIONS=y
+CONFIG_MODULE_SRCVERSION_ALL=y
+CONFIG_MODULE_COMPRESS_XZ=y
+CONFIG_PARTITION_ADVANCED=y
+CONFIG_MAC_PARTITION=y
+CONFIG_BINFMT_MISC=m
+CONFIG_CLEANCACHE=y
+CONFIG_FRONTSWAP=y
+CONFIG_CMA=y
+CONFIG_ZSWAP=y
+CONFIG_Z3FOLD=m
+CONFIG_ZSMALLOC=m
+CONFIG_NET=y
+CONFIG_PACKET=y
+CONFIG_UNIX=y
+CONFIG_XFRM_USER=y
+CONFIG_NET_KEY=m
+CONFIG_INET=y
+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=m
+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_COUNTER=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_OBJREF=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_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_FLOW_TABLE_IPV4=m
+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_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_CLUSTERIP=m
+CONFIG_IP_NF_TARGET_ECN=m
+CONFIG_IP_NF_TARGET_TTL=m
+CONFIG_IP_NF_RAW=m
+CONFIG_IP_NF_ARPTABLES=m
+CONFIG_IP_NF_ARPFILTER=m
+CONFIG_IP_NF_ARP_MANGLE=m
+CONFIG_NFT_DUP_IPV6=m
+CONFIG_NFT_FIB_IPV6=m
+CONFIG_NF_FLOW_TABLE_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_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_CBQ=m
+CONFIG_NET_SCH_HTB=m
+CONFIG_NET_SCH_HFSC=m
+CONFIG_NET_SCH_ATM=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_DSMARK=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_TCINDEX=m
+CONFIG_NET_CLS_ROUTE4=m
+CONFIG_NET_CLS_FW=m
+CONFIG_NET_CLS_U32=m
+CONFIG_CLS_U32_MARK=y
+CONFIG_NET_CLS_RSVP=m
+CONFIG_NET_CLS_RSVP6=m
+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_IPT=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_CAN_VCAN=m
+CONFIG_CAN_SLCAN=m
+CONFIG_CAN_MCP251X=m
+CONFIG_CAN_MCP251XFD=m
+CONFIG_CAN_EMS_USB=m
+CONFIG_CAN_GS_USB=m
+CONFIG_CAN_PEAK_USB=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_NOR=m
+CONFIG_MTD_UBI=m
+CONFIG_OF_CONFIGFS=y
+CONFIG_ZRAM=m
+CONFIG_BLK_DEV_LOOP=y
+CONFIG_BLK_DEV_CRYPTOLOOP=m
+CONFIG_BLK_DEV_DRBD=m
+CONFIG_BLK_DEV_NBD=m
+CONFIG_BLK_DEV_RAM=y
+CONFIG_CDROM_PKTCDVD=m
+CONFIG_ATA_OVER_ETH=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_MD=y
+CONFIG_MD_LINEAR=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_DELAY=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_NET_VRF=m
+CONFIG_ENC28J60=m
+CONFIG_QCA7000_SPI=m
+CONFIG_QCA7000_UART=m
+CONFIG_WIZNET_W5100=m
+CONFIG_WIZNET_W5100_SPI=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_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_HOSTAP=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_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_USB_ZD1201=m
+CONFIG_ZD1211RW=m
+CONFIG_MAC80211_HWSIM=m
+CONFIG_USB_NET_RNDIS_WLAN=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_RPISENSE=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_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_GAMEPORT_NS558=m
+CONFIG_GAMEPORT_L4=m
+CONFIG_BRCM_CHAR_DRIVERS=y
+CONFIG_BCM_VCIO=y
+CONFIG_BCM2835_DEVGPIOMEM=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_SC16IS7XX_SPI=y
+CONFIG_SERIAL_DEV_BUS=y
+CONFIG_TTY_PRINTK=y
+CONFIG_HW_RANDOM=y
+CONFIG_TCG_TPM=m
+CONFIG_TCG_TIS_SPI=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_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_DS1WM=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_BATTERY_GAUGE_LTC2941=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_SHTC1=m
+CONFIG_SENSORS_EMC2305=m
+CONFIG_SENSORS_INA2XX=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_RC_CORE=y
+CONFIG_LIRC=y
+CONFIG_BPF_LIRC_MODE2=y
+CONFIG_RC_DECODERS=y
+CONFIG_IR_NEC_DECODER=m
+CONFIG_IR_RC5_DECODER=m
+CONFIG_IR_RC6_DECODER=m
+CONFIG_IR_JVC_DECODER=m
+CONFIG_IR_SONY_DECODER=m
+CONFIG_IR_SANYO_DECODER=m
+CONFIG_IR_SHARP_DECODER=m
+CONFIG_IR_MCE_KBD_DECODER=m
+CONFIG_IR_XMP_DECODER=m
+CONFIG_IR_IMON_DECODER=m
+CONFIG_RC_DEVICES=y
+CONFIG_RC_ATI_REMOTE=m
+CONFIG_IR_IMON=m
+CONFIG_IR_MCEUSB=m
+CONFIG_IR_REDRAT3=m
+CONFIG_IR_STREAMZAP=m
+CONFIG_IR_IGUANA=m
+CONFIG_IR_TTUSBIR=m
+CONFIG_RC_LOOPBACK=m
+CONFIG_IR_GPIO_CIR=m
+CONFIG_IR_GPIO_TX=m
+CONFIG_IR_PWM_TX=m
+CONFIG_IR_TOY=m
+CONFIG_MEDIA_CEC_RC=y
+CONFIG_MEDIA_SUPPORT=m
+CONFIG_MEDIA_USB_SUPPORT=y
+CONFIG_USB_VIDEO_CLASS=m
+CONFIG_USB_GSPCA=m
+CONFIG_USB_M5602=m
+CONFIG_USB_STV06XX=m
+CONFIG_USB_GL860=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_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_SPCA1528=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_PWC=m
+CONFIG_VIDEO_CPIA2=m
+CONFIG_USB_ZR364XX=m
+CONFIG_USB_STKWEBCAM=m
+CONFIG_USB_S2255=m
+CONFIG_VIDEO_USBTV=m
+CONFIG_VIDEO_PVRUSB2=m
+CONFIG_VIDEO_HDPVR=m
+CONFIG_VIDEO_STK1160_COMMON=m
+CONFIG_VIDEO_GO7007=m
+CONFIG_VIDEO_GO7007_USB=m
+CONFIG_VIDEO_GO7007_USB_S2250_BOARD=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_VIDEO_TM6000=m
+CONFIG_VIDEO_TM6000_ALSA=m
+CONFIG_VIDEO_TM6000_DVB=m
+CONFIG_DVB_USB=m
+CONFIG_DVB_USB_A800=m
+CONFIG_DVB_USB_DIBUSB_MB=m
+CONFIG_DVB_USB_DIBUSB_MB_FAULTY=y
+CONFIG_DVB_USB_DIBUSB_MC=m
+CONFIG_DVB_USB_DIB0700=m
+CONFIG_DVB_USB_UMT_010=m
+CONFIG_DVB_USB_CXUSB=m
+CONFIG_DVB_USB_M920X=m
+CONFIG_DVB_USB_DIGITV=m
+CONFIG_DVB_USB_VP7045=m
+CONFIG_DVB_USB_VP702X=m
+CONFIG_DVB_USB_GP8PSK=m
+CONFIG_DVB_USB_NOVA_T_USB2=m
+CONFIG_DVB_USB_TTUSB2=m
+CONFIG_DVB_USB_DTT200U=m
+CONFIG_DVB_USB_OPERA1=m
+CONFIG_DVB_USB_AF9005=m
+CONFIG_DVB_USB_AF9005_REMOTE=m
+CONFIG_DVB_USB_PCTV452E=m
+CONFIG_DVB_USB_DW2102=m
+CONFIG_DVB_USB_CINERGY_T2=m
+CONFIG_DVB_USB_DTV5100=m
+CONFIG_DVB_USB_AZ6027=m
+CONFIG_DVB_USB_TECHNISAT_USB2=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_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_DVBSKY=m
+CONFIG_SMS_USB_DRV=m
+CONFIG_DVB_B2C2_FLEXCOP_USB=m
+CONFIG_DVB_AS102=m
+CONFIG_VIDEO_EM28XX=m
+CONFIG_VIDEO_EM28XX_V4L2=m
+CONFIG_VIDEO_EM28XX_ALSA=m
+CONFIG_VIDEO_EM28XX_DVB=m
+CONFIG_RADIO_SI470X=m
+CONFIG_USB_SI470X=m
+CONFIG_I2C_SI470X=m
+CONFIG_RADIO_SI4713=m
+CONFIG_I2C_SI4713=m
+CONFIG_USB_MR800=m
+CONFIG_USB_DSBR=m
+CONFIG_RADIO_SHARK=m
+CONFIG_RADIO_SHARK2=m
+CONFIG_USB_KEENE=m
+CONFIG_USB_MA901=m
+CONFIG_RADIO_TEA5764=m
+CONFIG_RADIO_SAA7706H=m
+CONFIG_RADIO_TEF6862=m
+CONFIG_RADIO_WL1273=m
+CONFIG_RADIO_WL128X=m
+CONFIG_V4L_PLATFORM_DRIVERS=y
+CONFIG_VIDEO_MUX=m
+CONFIG_VIDEO_BCM2835_UNICAM=m
+CONFIG_VIDEO_UDA1342=m
+CONFIG_VIDEO_SONY_BTF_MPX=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_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_IMX477=m
+CONFIG_VIDEO_IMX519=m
+CONFIG_VIDEO_OV2311=m
+CONFIG_VIDEO_OV5647=m
+CONFIG_VIDEO_OV7251=m
+CONFIG_VIDEO_OV7640=m
+CONFIG_VIDEO_OV9281=m
+CONFIG_VIDEO_IRS1125=m
+CONFIG_VIDEO_MT9V011=m
+CONFIG_VIDEO_AD5398=m
+CONFIG_DRM=m
+CONFIG_DRM_LOAD_EDID_FIRMWARE=y
+CONFIG_DRM_UDL=m
+CONFIG_DRM_PANEL_SIMPLE=m
+CONFIG_DRM_PANEL_ILITEK_ILI9806E=m
+CONFIG_DRM_PANEL_JDI_LT070ME05000=m
+CONFIG_DRM_PANEL_RASPBERRYPI_TOUCHSCREEN=m
+CONFIG_DRM_PANEL_SITRONIX_ST7701=m
+CONFIG_DRM_PANEL_TPO_Y17P=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_MI0283QT=m
+CONFIG_TINYDRM_REPAPER=m
+CONFIG_TINYDRM_ST7586=m
+CONFIG_TINYDRM_ST7735R=m
+CONFIG_DRM_GUD=m
+CONFIG_FB=y
+CONFIG_FB_BCM2708=y
+CONFIG_FB_UDL=m
+CONFIG_FB_SIMPLE=y
+CONFIG_FB_SSD1307=m
+CONFIG_FB_RPISENSE=m
+CONFIG_BACKLIGHT_PWM=m
+CONFIG_BACKLIGHT_RPI=m
+CONFIG_BACKLIGHT_GPIO=m
+CONFIG_FRAMEBUFFER_CONSOLE=y
+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_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_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_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_WM8804_I2C=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_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_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_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_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_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_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_FTDI_ELAN=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_BCM2835_SDHOST=y
+CONFIG_MMC_SDHCI=y
+CONFIG_MMC_SDHCI_PLTFM=y
+CONFIG_MMC_SPI=m
+CONFIG_LEDS_CLASS=y
+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_GPIO=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_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_AUXDISPLAY=y
+CONFIG_HD44780=m
+CONFIG_UIO=m
+CONFIG_UIO_PDRV_GENIRQ=m
+CONFIG_STAGING=y
+CONFIG_PRISM2_USB=m
+CONFIG_R8712U=m
+CONFIG_R8188EU=m
+CONFIG_88EU_AP_MODE=y
+CONFIG_VT6656=m
+CONFIG_STAGING_MEDIA=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_UC1701=m
+CONFIG_FB_TFT_UPD161704=m
+CONFIG_FB_TFT_WATTEROTT=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_HTU21=m
+CONFIG_INV_MPU6050_I2C=m
+CONFIG_APDS9960=m
+CONFIG_BH1750=m
+CONFIG_TSL4531=m
+CONFIG_VEML6070=m
+CONFIG_IIO_HRTIMER_TRIGGER=m
+CONFIG_IIO_INTERRUPT_TRIGGER=m
+CONFIG_IIO_SYSFS_TRIGGER=m
+CONFIG_BMP280=m
+CONFIG_MAXIM_THERMOCOUPLE=m
+CONFIG_MAX31856=m
+CONFIG_PWM_BCM2835=m
+CONFIG_PWM_PCA9685=m
+CONFIG_PWM_RASPBERRYPI_POE=m
+CONFIG_RPI_AXIPERF=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_FS_ENCRYPTION=y
+CONFIG_FANOTIFY=y
+CONFIG_QFMT_V1=m
+CONFIG_QFMT_V2=m
+CONFIG_AUTOFS4_FS=y
+CONFIG_FUSE_FS=m
+CONFIG_CUSE=m
+CONFIG_OVERLAY_FS=m
+CONFIG_FSCACHE=y
+CONFIG_FSCACHE_STATS=y
+CONFIG_CACHEFILES=y
+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_NTFS_FS=m
+CONFIG_NTFS_RW=y
+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_PSTORE=y
+CONFIG_PSTORE_CONSOLE=y
+CONFIG_PSTORE_RAM=y
+CONFIG_NFS_FS=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_NFS_FSCACHE=y
+CONFIG_NFSD=m
+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_SECURITY=y
+CONFIG_SECURITY_APPARMOR=y
+CONFIG_LSM=""
+CONFIG_CRYPTO_USER=m
+CONFIG_CRYPTO_CHACHA20POLY1305=m
+CONFIG_CRYPTO_ADIANTUM=m
+CONFIG_CRYPTO_XCBC=m
+CONFIG_CRYPTO_WP512=m
+CONFIG_CRYPTO_CAST5=m
+CONFIG_CRYPTO_DES=y
+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_HW is not set
+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_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_IRQSOFF_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 (file)
index 0000000..cbf7a29
--- /dev/null
@@ -0,0 +1,1583 @@
+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_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_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_EMBEDDED=y
+# CONFIG_COMPAT_BRK is not set
+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_POWERSAVE=y
+CONFIG_CPU_FREQ_GOV_PERFORMANCE=y
+CONFIG_CPU_FREQ_GOV_USERSPACE=y
+CONFIG_CPU_FREQ_GOV_ONDEMAND=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_CRYPTO_SHA1_ARM_NEON=m
+CONFIG_CRYPTO_AES_ARM=m
+CONFIG_CRYPTO_AES_ARM_BS=m
+CONFIG_JUMP_LABEL=y
+CONFIG_MODULES=y
+CONFIG_MODULE_UNLOAD=y
+CONFIG_MODVERSIONS=y
+CONFIG_MODULE_SRCVERSION_ALL=y
+CONFIG_MODULE_COMPRESS_XZ=y
+CONFIG_BLK_DEV_THROTTLING=y
+CONFIG_PARTITION_ADVANCED=y
+CONFIG_MAC_PARTITION=y
+CONFIG_BINFMT_MISC=m
+CONFIG_CLEANCACHE=y
+CONFIG_FRONTSWAP=y
+CONFIG_CMA=y
+CONFIG_ZSWAP=y
+CONFIG_Z3FOLD=m
+CONFIG_ZSMALLOC=m
+CONFIG_NET=y
+CONFIG_PACKET=y
+CONFIG_UNIX=y
+CONFIG_XFRM_USER=y
+CONFIG_NET_KEY=m
+CONFIG_INET=y
+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=m
+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_COUNTER=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_OBJREF=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_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_FLOW_TABLE_IPV4=m
+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_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_CLUSTERIP=m
+CONFIG_IP_NF_TARGET_ECN=m
+CONFIG_IP_NF_TARGET_TTL=m
+CONFIG_IP_NF_RAW=m
+CONFIG_IP_NF_ARPTABLES=m
+CONFIG_IP_NF_ARPFILTER=m
+CONFIG_IP_NF_ARP_MANGLE=m
+CONFIG_NFT_DUP_IPV6=m
+CONFIG_NFT_FIB_IPV6=m
+CONFIG_NF_FLOW_TABLE_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_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_CBQ=m
+CONFIG_NET_SCH_HTB=m
+CONFIG_NET_SCH_HFSC=m
+CONFIG_NET_SCH_ATM=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_DSMARK=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_TCINDEX=m
+CONFIG_NET_CLS_ROUTE4=m
+CONFIG_NET_CLS_FW=m
+CONFIG_NET_CLS_U32=m
+CONFIG_CLS_U32_MARK=y
+CONFIG_NET_CLS_RSVP=m
+CONFIG_NET_CLS_RSVP6=m
+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_IPT=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_CAN_VCAN=m
+CONFIG_CAN_SLCAN=m
+CONFIG_CAN_MCP251X=m
+CONFIG_CAN_MCP251XFD=m
+CONFIG_CAN_EMS_USB=m
+CONFIG_CAN_GS_USB=m
+CONFIG_CAN_PEAK_USB=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_NOR=m
+CONFIG_MTD_UBI=m
+CONFIG_OF_CONFIGFS=y
+CONFIG_ZRAM=m
+CONFIG_BLK_DEV_LOOP=y
+CONFIG_BLK_DEV_CRYPTOLOOP=m
+CONFIG_BLK_DEV_DRBD=m
+CONFIG_BLK_DEV_NBD=m
+CONFIG_BLK_DEV_RAM=y
+CONFIG_CDROM_PKTCDVD=m
+CONFIG_ATA_OVER_ETH=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_MD_LINEAR=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_DELAY=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_NET_VRF=m
+CONFIG_BCMGENET=y
+CONFIG_ENC28J60=m
+CONFIG_QCA7000_SPI=m
+CONFIG_QCA7000_UART=m
+CONFIG_R8169=m
+CONFIG_WIZNET_W5100=m
+CONFIG_WIZNET_W5100_SPI=m
+CONFIG_MICREL_PHY=y
+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_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_HOSTAP=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_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_USB_ZD1201=m
+CONFIG_ZD1211RW=m
+CONFIG_MAC80211_HWSIM=m
+CONFIG_USB_NET_RNDIS_WLAN=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_RPISENSE=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_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_GAMEPORT_NS558=m
+CONFIG_GAMEPORT_L4=m
+CONFIG_BRCM_CHAR_DRIVERS=y
+CONFIG_BCM_VCIO=y
+CONFIG_BCM2835_DEVGPIOMEM=y
+CONFIG_RPIVID_MEM=m
+# 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_SC16IS7XX_SPI=y
+CONFIG_SERIAL_DEV_BUS=y
+CONFIG_TTY_PRINTK=y
+CONFIG_HW_RANDOM=y
+CONFIG_TCG_TPM=m
+CONFIG_TCG_TIS_SPI=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_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_DS1WM=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_BATTERY_GAUGE_LTC2941=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_SHTC1=m
+CONFIG_SENSORS_EMC2305=m
+CONFIG_SENSORS_INA2XX=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_RC_CORE=y
+CONFIG_LIRC=y
+CONFIG_BPF_LIRC_MODE2=y
+CONFIG_RC_DECODERS=y
+CONFIG_IR_NEC_DECODER=m
+CONFIG_IR_RC5_DECODER=m
+CONFIG_IR_RC6_DECODER=m
+CONFIG_IR_JVC_DECODER=m
+CONFIG_IR_SONY_DECODER=m
+CONFIG_IR_SANYO_DECODER=m
+CONFIG_IR_SHARP_DECODER=m
+CONFIG_IR_MCE_KBD_DECODER=m
+CONFIG_IR_XMP_DECODER=m
+CONFIG_IR_IMON_DECODER=m
+CONFIG_RC_DEVICES=y
+CONFIG_RC_ATI_REMOTE=m
+CONFIG_IR_IMON=m
+CONFIG_IR_MCEUSB=m
+CONFIG_IR_REDRAT3=m
+CONFIG_IR_STREAMZAP=m
+CONFIG_IR_IGUANA=m
+CONFIG_IR_TTUSBIR=m
+CONFIG_RC_LOOPBACK=m
+CONFIG_IR_GPIO_CIR=m
+CONFIG_IR_GPIO_TX=m
+CONFIG_IR_PWM_TX=m
+CONFIG_IR_TOY=m
+CONFIG_MEDIA_CEC_RC=y
+CONFIG_MEDIA_SUPPORT=m
+CONFIG_MEDIA_USB_SUPPORT=y
+CONFIG_USB_VIDEO_CLASS=m
+CONFIG_USB_GSPCA=m
+CONFIG_USB_M5602=m
+CONFIG_USB_STV06XX=m
+CONFIG_USB_GL860=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_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_SPCA1528=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_PWC=m
+CONFIG_VIDEO_CPIA2=m
+CONFIG_USB_ZR364XX=m
+CONFIG_USB_STKWEBCAM=m
+CONFIG_USB_S2255=m
+CONFIG_VIDEO_USBTV=m
+CONFIG_VIDEO_PVRUSB2=m
+CONFIG_VIDEO_HDPVR=m
+CONFIG_VIDEO_STK1160_COMMON=m
+CONFIG_VIDEO_GO7007=m
+CONFIG_VIDEO_GO7007_USB=m
+CONFIG_VIDEO_GO7007_USB_S2250_BOARD=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_VIDEO_TM6000=m
+CONFIG_VIDEO_TM6000_ALSA=m
+CONFIG_VIDEO_TM6000_DVB=m
+CONFIG_DVB_USB=m
+CONFIG_DVB_USB_A800=m
+CONFIG_DVB_USB_DIBUSB_MB=m
+CONFIG_DVB_USB_DIBUSB_MB_FAULTY=y
+CONFIG_DVB_USB_DIBUSB_MC=m
+CONFIG_DVB_USB_DIB0700=m
+CONFIG_DVB_USB_UMT_010=m
+CONFIG_DVB_USB_CXUSB=m
+CONFIG_DVB_USB_M920X=m
+CONFIG_DVB_USB_DIGITV=m
+CONFIG_DVB_USB_VP7045=m
+CONFIG_DVB_USB_VP702X=m
+CONFIG_DVB_USB_GP8PSK=m
+CONFIG_DVB_USB_NOVA_T_USB2=m
+CONFIG_DVB_USB_TTUSB2=m
+CONFIG_DVB_USB_DTT200U=m
+CONFIG_DVB_USB_OPERA1=m
+CONFIG_DVB_USB_AF9005=m
+CONFIG_DVB_USB_AF9005_REMOTE=m
+CONFIG_DVB_USB_PCTV452E=m
+CONFIG_DVB_USB_DW2102=m
+CONFIG_DVB_USB_CINERGY_T2=m
+CONFIG_DVB_USB_DTV5100=m
+CONFIG_DVB_USB_AZ6027=m
+CONFIG_DVB_USB_TECHNISAT_USB2=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_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_DVBSKY=m
+CONFIG_SMS_USB_DRV=m
+CONFIG_DVB_B2C2_FLEXCOP_USB=m
+CONFIG_DVB_AS102=m
+CONFIG_VIDEO_EM28XX=m
+CONFIG_VIDEO_EM28XX_V4L2=m
+CONFIG_VIDEO_EM28XX_ALSA=m
+CONFIG_VIDEO_EM28XX_DVB=m
+CONFIG_RADIO_SI470X=m
+CONFIG_USB_SI470X=m
+CONFIG_I2C_SI470X=m
+CONFIG_RADIO_SI4713=m
+CONFIG_I2C_SI4713=m
+CONFIG_USB_MR800=m
+CONFIG_USB_DSBR=m
+CONFIG_RADIO_SHARK=m
+CONFIG_RADIO_SHARK2=m
+CONFIG_USB_KEENE=m
+CONFIG_USB_MA901=m
+CONFIG_RADIO_TEA5764=m
+CONFIG_RADIO_SAA7706H=m
+CONFIG_RADIO_TEF6862=m
+CONFIG_RADIO_WL1273=m
+CONFIG_RADIO_WL128X=m
+CONFIG_V4L_PLATFORM_DRIVERS=y
+CONFIG_VIDEO_MUX=m
+CONFIG_VIDEO_BCM2835_UNICAM=m
+CONFIG_VIDEO_UDA1342=m
+CONFIG_VIDEO_SONY_BTF_MPX=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_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_IMX477=m
+CONFIG_VIDEO_IMX519=m
+CONFIG_VIDEO_OV2311=m
+CONFIG_VIDEO_OV5647=m
+CONFIG_VIDEO_OV7251=m
+CONFIG_VIDEO_OV7640=m
+CONFIG_VIDEO_OV9281=m
+CONFIG_VIDEO_IRS1125=m
+CONFIG_VIDEO_MT9V011=m
+CONFIG_VIDEO_AD5398=m
+CONFIG_DRM=m
+CONFIG_DRM_LOAD_EDID_FIRMWARE=y
+CONFIG_DRM_UDL=m
+CONFIG_DRM_PANEL_SIMPLE=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_TPO_Y17P=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_MI0283QT=m
+CONFIG_TINYDRM_REPAPER=m
+CONFIG_TINYDRM_ST7586=m
+CONFIG_TINYDRM_ST7735R=m
+CONFIG_DRM_GUD=m
+CONFIG_FB=y
+CONFIG_FB_BCM2708=y
+CONFIG_FB_UDL=m
+CONFIG_FB_SIMPLE=y
+CONFIG_FB_SSD1307=m
+CONFIG_FB_RPISENSE=m
+CONFIG_BACKLIGHT_PWM=m
+CONFIG_BACKLIGHT_RPI=m
+CONFIG_BACKLIGHT_GPIO=m
+CONFIG_FRAMEBUFFER_CONSOLE=y
+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_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_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_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_WM8804_I2C=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_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_USB=y
+CONFIG_USB_ANNOUNCE_NEW_DEVICES=y
+CONFIG_USB_MON=m
+CONFIG_USB_XHCI_HCD=y
+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_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_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_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_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_FTDI_ELAN=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_BCM2835_SDHOST=y
+CONFIG_MMC_SDHCI=y
+CONFIG_MMC_SDHCI_PLTFM=y
+CONFIG_MMC_SDHCI_IPROC=y
+CONFIG_MMC_SPI=m
+CONFIG_LEDS_CLASS=y
+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_GPIO=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_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_AUXDISPLAY=y
+CONFIG_HD44780=m
+CONFIG_UIO=m
+CONFIG_UIO_PDRV_GENIRQ=m
+CONFIG_STAGING=y
+CONFIG_PRISM2_USB=m
+CONFIG_R8712U=m
+CONFIG_R8188EU=m
+CONFIG_88EU_AP_MODE=y
+CONFIG_VT6656=m
+CONFIG_STAGING_MEDIA=y
+CONFIG_VIDEO_RPIVID=m
+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_UC1701=m
+CONFIG_FB_TFT_UPD161704=m
+CONFIG_FB_TFT_WATTEROTT=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_HTU21=m
+CONFIG_INV_MPU6050_I2C=m
+CONFIG_APDS9960=m
+CONFIG_BH1750=m
+CONFIG_TSL4531=m
+CONFIG_VEML6070=m
+CONFIG_IIO_HRTIMER_TRIGGER=m
+CONFIG_IIO_INTERRUPT_TRIGGER=m
+CONFIG_IIO_SYSFS_TRIGGER=m
+CONFIG_BMP280=m
+CONFIG_MAXIM_THERMOCOUPLE=m
+CONFIG_MAX31856=m
+CONFIG_PWM_BCM2835=m
+CONFIG_PWM_PCA9685=m
+CONFIG_PWM_RASPBERRYPI_POE=m
+CONFIG_RPI_AXIPERF=m
+CONFIG_NVMEM_RMEM=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_FS_ENCRYPTION=y
+CONFIG_FANOTIFY=y
+CONFIG_QFMT_V1=m
+CONFIG_QFMT_V2=m
+CONFIG_AUTOFS4_FS=y
+CONFIG_FUSE_FS=m
+CONFIG_CUSE=m
+CONFIG_OVERLAY_FS=m
+CONFIG_FSCACHE=y
+CONFIG_FSCACHE_STATS=y
+CONFIG_CACHEFILES=y
+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_NTFS_FS=m
+CONFIG_NTFS_RW=y
+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_PSTORE=y
+CONFIG_PSTORE_CONSOLE=y
+CONFIG_PSTORE_RAM=y
+CONFIG_NFS_FS=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_NFS_FSCACHE=y
+CONFIG_NFSD=m
+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_SECURITY=y
+CONFIG_SECURITY_APPARMOR=y
+CONFIG_LSM=""
+CONFIG_CRYPTO_USER=m
+CONFIG_CRYPTO_CHACHA20POLY1305=m
+CONFIG_CRYPTO_ADIANTUM=m
+CONFIG_CRYPTO_XCBC=m
+CONFIG_CRYPTO_WP512=m
+CONFIG_CRYPTO_CAST5=m
+CONFIG_CRYPTO_DES=y
+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_HW is not set
+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_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_IRQSOFF_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 (file)
index 0000000..c621999
--- /dev/null
@@ -0,0 +1,1552 @@
+# 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_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_EMBEDDED=y
+# CONFIG_COMPAT_BRK is not set
+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_CPU_SW_DOMAIN_PAN 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_POWERSAVE=y
+CONFIG_CPU_FREQ_GOV_PERFORMANCE=y
+CONFIG_CPU_FREQ_GOV_USERSPACE=y
+CONFIG_CPU_FREQ_GOV_ONDEMAND=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_CRYPTO_SHA1_ARM=m
+CONFIG_CRYPTO_AES_ARM=m
+CONFIG_JUMP_LABEL=y
+CONFIG_MODULES=y
+CONFIG_MODULE_UNLOAD=y
+CONFIG_MODVERSIONS=y
+CONFIG_MODULE_SRCVERSION_ALL=y
+CONFIG_MODULE_COMPRESS_XZ=y
+CONFIG_PARTITION_ADVANCED=y
+CONFIG_MAC_PARTITION=y
+CONFIG_BINFMT_MISC=m
+CONFIG_CLEANCACHE=y
+CONFIG_FRONTSWAP=y
+CONFIG_CMA=y
+CONFIG_ZSWAP=y
+CONFIG_Z3FOLD=m
+CONFIG_ZSMALLOC=m
+CONFIG_NET=y
+CONFIG_PACKET=y
+CONFIG_UNIX=y
+CONFIG_XFRM_USER=y
+CONFIG_NET_KEY=m
+CONFIG_INET=y
+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=m
+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_COUNTER=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_OBJREF=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_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_FLOW_TABLE_IPV4=m
+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_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_CLUSTERIP=m
+CONFIG_IP_NF_TARGET_ECN=m
+CONFIG_IP_NF_TARGET_TTL=m
+CONFIG_IP_NF_RAW=m
+CONFIG_IP_NF_ARPTABLES=m
+CONFIG_IP_NF_ARPFILTER=m
+CONFIG_IP_NF_ARP_MANGLE=m
+CONFIG_NFT_DUP_IPV6=m
+CONFIG_NFT_FIB_IPV6=m
+CONFIG_NF_FLOW_TABLE_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_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_CBQ=m
+CONFIG_NET_SCH_HTB=m
+CONFIG_NET_SCH_HFSC=m
+CONFIG_NET_SCH_ATM=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_DSMARK=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_TCINDEX=m
+CONFIG_NET_CLS_ROUTE4=m
+CONFIG_NET_CLS_FW=m
+CONFIG_NET_CLS_U32=m
+CONFIG_CLS_U32_MARK=y
+CONFIG_NET_CLS_RSVP=m
+CONFIG_NET_CLS_RSVP6=m
+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_IPT=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_CAN_VCAN=m
+CONFIG_CAN_SLCAN=m
+CONFIG_CAN_MCP251X=m
+CONFIG_CAN_MCP251XFD=m
+CONFIG_CAN_EMS_USB=m
+CONFIG_CAN_GS_USB=m
+CONFIG_CAN_PEAK_USB=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_NOR=m
+CONFIG_MTD_UBI=m
+CONFIG_OF_CONFIGFS=y
+CONFIG_ZRAM=m
+CONFIG_BLK_DEV_LOOP=y
+CONFIG_BLK_DEV_CRYPTOLOOP=m
+CONFIG_BLK_DEV_DRBD=m
+CONFIG_BLK_DEV_NBD=m
+CONFIG_BLK_DEV_RAM=y
+CONFIG_CDROM_PKTCDVD=m
+CONFIG_ATA_OVER_ETH=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_MD=y
+CONFIG_MD_LINEAR=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_DELAY=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_NET_VRF=m
+CONFIG_ENC28J60=m
+CONFIG_QCA7000_SPI=m
+CONFIG_QCA7000_UART=m
+CONFIG_WIZNET_W5100=m
+CONFIG_WIZNET_W5100_SPI=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_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_HOSTAP=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_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_USB_ZD1201=m
+CONFIG_ZD1211RW=m
+CONFIG_MAC80211_HWSIM=m
+CONFIG_USB_NET_RNDIS_WLAN=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_RPISENSE=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_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_GAMEPORT_NS558=m
+CONFIG_GAMEPORT_L4=m
+CONFIG_BRCM_CHAR_DRIVERS=y
+CONFIG_BCM_VCIO=y
+CONFIG_BCM2835_DEVGPIOMEM=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_SC16IS7XX_SPI=y
+CONFIG_SERIAL_DEV_BUS=y
+CONFIG_TTY_PRINTK=y
+CONFIG_HW_RANDOM=y
+CONFIG_TCG_TPM=m
+CONFIG_TCG_TIS_SPI=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_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_DS1WM=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_BATTERY_GAUGE_LTC2941=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_SHTC1=m
+CONFIG_SENSORS_EMC2305=m
+CONFIG_SENSORS_INA2XX=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_RC_CORE=y
+CONFIG_LIRC=y
+CONFIG_BPF_LIRC_MODE2=y
+CONFIG_RC_DECODERS=y
+CONFIG_IR_NEC_DECODER=m
+CONFIG_IR_RC5_DECODER=m
+CONFIG_IR_RC6_DECODER=m
+CONFIG_IR_JVC_DECODER=m
+CONFIG_IR_SONY_DECODER=m
+CONFIG_IR_SANYO_DECODER=m
+CONFIG_IR_SHARP_DECODER=m
+CONFIG_IR_MCE_KBD_DECODER=m
+CONFIG_IR_XMP_DECODER=m
+CONFIG_IR_IMON_DECODER=m
+CONFIG_RC_DEVICES=y
+CONFIG_RC_ATI_REMOTE=m
+CONFIG_IR_IMON=m
+CONFIG_IR_MCEUSB=m
+CONFIG_IR_REDRAT3=m
+CONFIG_IR_STREAMZAP=m
+CONFIG_IR_IGUANA=m
+CONFIG_IR_TTUSBIR=m
+CONFIG_RC_LOOPBACK=m
+CONFIG_IR_GPIO_CIR=m
+CONFIG_IR_GPIO_TX=m
+CONFIG_IR_PWM_TX=m
+CONFIG_IR_TOY=m
+CONFIG_MEDIA_CEC_RC=y
+CONFIG_MEDIA_SUPPORT=m
+CONFIG_MEDIA_USB_SUPPORT=y
+CONFIG_USB_VIDEO_CLASS=m
+CONFIG_USB_GSPCA=m
+CONFIG_USB_M5602=m
+CONFIG_USB_STV06XX=m
+CONFIG_USB_GL860=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_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_SPCA1528=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_PWC=m
+CONFIG_VIDEO_CPIA2=m
+CONFIG_USB_ZR364XX=m
+CONFIG_USB_STKWEBCAM=m
+CONFIG_USB_S2255=m
+CONFIG_VIDEO_USBTV=m
+CONFIG_VIDEO_PVRUSB2=m
+CONFIG_VIDEO_HDPVR=m
+CONFIG_VIDEO_STK1160_COMMON=m
+CONFIG_VIDEO_GO7007=m
+CONFIG_VIDEO_GO7007_USB=m
+CONFIG_VIDEO_GO7007_USB_S2250_BOARD=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_VIDEO_TM6000=m
+CONFIG_VIDEO_TM6000_ALSA=m
+CONFIG_VIDEO_TM6000_DVB=m
+CONFIG_DVB_USB=m
+CONFIG_DVB_USB_A800=m
+CONFIG_DVB_USB_DIBUSB_MB=m
+CONFIG_DVB_USB_DIBUSB_MB_FAULTY=y
+CONFIG_DVB_USB_DIBUSB_MC=m
+CONFIG_DVB_USB_DIB0700=m
+CONFIG_DVB_USB_UMT_010=m
+CONFIG_DVB_USB_CXUSB=m
+CONFIG_DVB_USB_M920X=m
+CONFIG_DVB_USB_DIGITV=m
+CONFIG_DVB_USB_VP7045=m
+CONFIG_DVB_USB_VP702X=m
+CONFIG_DVB_USB_GP8PSK=m
+CONFIG_DVB_USB_NOVA_T_USB2=m
+CONFIG_DVB_USB_TTUSB2=m
+CONFIG_DVB_USB_DTT200U=m
+CONFIG_DVB_USB_OPERA1=m
+CONFIG_DVB_USB_AF9005=m
+CONFIG_DVB_USB_AF9005_REMOTE=m
+CONFIG_DVB_USB_PCTV452E=m
+CONFIG_DVB_USB_DW2102=m
+CONFIG_DVB_USB_CINERGY_T2=m
+CONFIG_DVB_USB_DTV5100=m
+CONFIG_DVB_USB_AZ6027=m
+CONFIG_DVB_USB_TECHNISAT_USB2=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_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_DVBSKY=m
+CONFIG_SMS_USB_DRV=m
+CONFIG_DVB_B2C2_FLEXCOP_USB=m
+CONFIG_DVB_AS102=m
+CONFIG_VIDEO_EM28XX=m
+CONFIG_VIDEO_EM28XX_V4L2=m
+CONFIG_VIDEO_EM28XX_ALSA=m
+CONFIG_VIDEO_EM28XX_DVB=m
+CONFIG_RADIO_SI470X=m
+CONFIG_USB_SI470X=m
+CONFIG_I2C_SI470X=m
+CONFIG_RADIO_SI4713=m
+CONFIG_I2C_SI4713=m
+CONFIG_USB_MR800=m
+CONFIG_USB_DSBR=m
+CONFIG_RADIO_SHARK=m
+CONFIG_RADIO_SHARK2=m
+CONFIG_USB_KEENE=m
+CONFIG_USB_MA901=m
+CONFIG_RADIO_TEA5764=m
+CONFIG_RADIO_SAA7706H=m
+CONFIG_RADIO_TEF6862=m
+CONFIG_RADIO_WL1273=m
+CONFIG_RADIO_WL128X=m
+CONFIG_V4L_PLATFORM_DRIVERS=y
+CONFIG_VIDEO_MUX=m
+CONFIG_VIDEO_BCM2835_UNICAM=m
+CONFIG_VIDEO_UDA1342=m
+CONFIG_VIDEO_SONY_BTF_MPX=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_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_IMX477=m
+CONFIG_VIDEO_IMX519=m
+CONFIG_VIDEO_OV2311=m
+CONFIG_VIDEO_OV5647=m
+CONFIG_VIDEO_OV7251=m
+CONFIG_VIDEO_OV7640=m
+CONFIG_VIDEO_OV9281=m
+CONFIG_VIDEO_IRS1125=m
+CONFIG_VIDEO_MT9V011=m
+CONFIG_VIDEO_AD5398=m
+CONFIG_DRM=m
+CONFIG_DRM_LOAD_EDID_FIRMWARE=y
+CONFIG_DRM_UDL=m
+CONFIG_DRM_PANEL_SIMPLE=m
+CONFIG_DRM_PANEL_ILITEK_ILI9806E=m
+CONFIG_DRM_PANEL_JDI_LT070ME05000=m
+CONFIG_DRM_PANEL_RASPBERRYPI_TOUCHSCREEN=m
+CONFIG_DRM_PANEL_SITRONIX_ST7701=m
+CONFIG_DRM_PANEL_TPO_Y17P=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_MI0283QT=m
+CONFIG_TINYDRM_REPAPER=m
+CONFIG_TINYDRM_ST7586=m
+CONFIG_TINYDRM_ST7735R=m
+CONFIG_DRM_GUD=m
+CONFIG_FB=y
+CONFIG_FB_BCM2708=y
+CONFIG_FB_UDL=m
+CONFIG_FB_SIMPLE=y
+CONFIG_FB_SSD1307=m
+CONFIG_FB_RPISENSE=m
+CONFIG_BACKLIGHT_PWM=m
+CONFIG_BACKLIGHT_RPI=m
+CONFIG_BACKLIGHT_GPIO=m
+CONFIG_FRAMEBUFFER_CONSOLE=y
+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_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_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_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_WM8804_I2C=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_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_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_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_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_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_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_FTDI_ELAN=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_BCM2835_SDHOST=y
+CONFIG_MMC_SDHCI=y
+CONFIG_MMC_SDHCI_PLTFM=y
+CONFIG_MMC_SPI=m
+CONFIG_LEDS_CLASS=y
+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_GPIO=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_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_AUXDISPLAY=y
+CONFIG_HD44780=m
+CONFIG_UIO=m
+CONFIG_UIO_PDRV_GENIRQ=m
+CONFIG_STAGING=y
+CONFIG_PRISM2_USB=m
+CONFIG_R8712U=m
+CONFIG_R8188EU=m
+CONFIG_88EU_AP_MODE=y
+CONFIG_VT6656=m
+CONFIG_STAGING_MEDIA=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_UC1701=m
+CONFIG_FB_TFT_UPD161704=m
+CONFIG_FB_TFT_WATTEROTT=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_HTU21=m
+CONFIG_INV_MPU6050_I2C=m
+CONFIG_APDS9960=m
+CONFIG_BH1750=m
+CONFIG_TSL4531=m
+CONFIG_VEML6070=m
+CONFIG_IIO_HRTIMER_TRIGGER=m
+CONFIG_IIO_INTERRUPT_TRIGGER=m
+CONFIG_IIO_SYSFS_TRIGGER=m
+CONFIG_BMP280=m
+CONFIG_MAXIM_THERMOCOUPLE=m
+CONFIG_MAX31856=m
+CONFIG_PWM_BCM2835=m
+CONFIG_PWM_PCA9685=m
+CONFIG_PWM_RASPBERRYPI_POE=m
+CONFIG_RPI_AXIPERF=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_FANOTIFY=y
+CONFIG_QFMT_V1=m
+CONFIG_QFMT_V2=m
+CONFIG_AUTOFS4_FS=y
+CONFIG_FUSE_FS=m
+CONFIG_CUSE=m
+CONFIG_OVERLAY_FS=m
+CONFIG_FSCACHE=y
+CONFIG_FSCACHE_STATS=y
+CONFIG_CACHEFILES=y
+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_NTFS_FS=m
+CONFIG_NTFS_RW=y
+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_PSTORE=y
+CONFIG_PSTORE_CONSOLE=y
+CONFIG_PSTORE_RAM=y
+CONFIG_NFS_FS=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_NFS_FSCACHE=y
+CONFIG_NFSD=m
+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_SECURITY=y
+CONFIG_SECURITY_APPARMOR=y
+CONFIG_LSM=""
+CONFIG_CRYPTO_USER=m
+CONFIG_CRYPTO_CRYPTD=m
+CONFIG_CRYPTO_CHACHA20POLY1305=m
+CONFIG_CRYPTO_CBC=y
+CONFIG_CRYPTO_CTS=m
+CONFIG_CRYPTO_XTS=m
+CONFIG_CRYPTO_ADIANTUM=m
+CONFIG_CRYPTO_XCBC=m
+CONFIG_CRYPTO_WP512=m
+CONFIG_CRYPTO_CAST5=m
+CONFIG_CRYPTO_DES=y
+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_HW is not set
+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_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_IRQSOFF_TRACER=y
+CONFIG_SCHED_TRACER=y
+CONFIG_BLK_DEV_IO_TRACE=y
+# CONFIG_UPROBE_EVENTS is not set
index 5e56288..d367258 100644 (file)
  *     DMA Cache Coherency
  *     ===================
  *
+ *     dma_inv_range(start, end)
+ *
+ *             Invalidate (discard) the specified virtual address range.
+ *             May not write back any entries.  If 'start' or 'end'
+ *             are not cache line aligned, those lines must be written
+ *             back.
+ *             - start  - virtual start address
+ *             - end    - virtual end address
+ *
+ *     dma_clean_range(start, end)
+ *
+ *             Clean (write back) the specified virtual address range.
+ *             - start  - virtual start address
+ *             - end    - virtual end address
+ *
  *     dma_flush_range(start, end)
  *
  *             Clean and invalidate the specified virtual address range.
@@ -112,6 +127,8 @@ struct cpu_cache_fns {
        void (*dma_map_area)(const void *, size_t, int);
        void (*dma_unmap_area)(const void *, size_t, int);
 
+       void (*dma_inv_range)(const void *, const void *);
+       void (*dma_clean_range)(const void *, const void *);
        void (*dma_flush_range)(const void *, const void *);
 } __no_randomize_layout;
 
@@ -137,6 +154,8 @@ extern struct cpu_cache_fns cpu_cache;
  * is visible to DMA, or data written by DMA to system memory is
  * visible to the CPU.
  */
+#define dmac_inv_range                 cpu_cache.dma_inv_range
+#define dmac_clean_range               cpu_cache.dma_clean_range
 #define dmac_flush_range               cpu_cache.dma_flush_range
 
 #else
@@ -156,6 +175,8 @@ extern void __cpuc_flush_dcache_area(void *, size_t);
  * is visible to DMA, or data written by DMA to system memory is
  * visible to the CPU.
  */
+extern void dmac_inv_range(const void *, const void *);
+extern void dmac_clean_range(const void *, const void *);
 extern void dmac_flush_range(const void *, const void *);
 
 #endif
index 724f8da..aa74173 100644 (file)
@@ -155,6 +155,8 @@ static inline void nop_dma_unmap_area(const void *s, size_t l, int f) { }
 #define __cpuc_coherent_user_range     __glue(_CACHE,_coherent_user_range)
 #define __cpuc_flush_dcache_area       __glue(_CACHE,_flush_kern_dcache_area)
 
+#define dmac_inv_range                 __glue(_CACHE,_dma_inv_range)
+#define dmac_clean_range               __glue(_CACHE,_dma_clean_range)
 #define dmac_flush_range               __glue(_CACHE,_dma_flush_range)
 #endif
 
index aeec7f2..a3b1866 100644 (file)
@@ -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");
index 6c607c6..ba7fc0b 100644 (file)
@@ -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
index 32dbfd8..75bb047 100644 (file)
@@ -529,6 +529,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)
 {
index 98ca3e3..c3fe7d3 100644 (file)
@@ -56,6 +56,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
@@ -140,6 +142,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);
 }
 
index 8dd26e1..eef4847 100644 (file)
@@ -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)
index 3044fcb..fe22c46 100644 (file)
@@ -101,9 +101,7 @@ void machine_shutdown(void)
  */
 void machine_halt(void)
 {
-       local_irq_disable();
-       smp_send_stop();
-       while (1);
+       machine_power_off();
 }
 
 /*
index 284a80c..12e73ce 100644 (file)
@@ -1264,6 +1264,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) {
                /*
@@ -1323,6 +1325,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;
 }
 
index 6d2ba45..8271cde 100644 (file)
@@ -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 (file)
index 0000000..5d4bda1
--- /dev/null
@@ -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
index 480a207..d24527e 100644 (file)
 
        .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
@@ -116,6 +117,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 (file)
index 0000000..1f82604
--- /dev/null
@@ -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 (file)
index 0000000..bf6e4ed
--- /dev/null
@@ -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 (file)
index 0000000..77a1dbe
--- /dev/null
@@ -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 *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)
+        memcpy  0
+ENDPROC(memcpy)
+ENDPROC(mmiocpy)
diff --git a/arch/arm/lib/memcpymove.h b/arch/arm/lib/memcpymove.h
new file mode 100644 (file)
index 0000000..65a6e06
--- /dev/null
@@ -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 (file)
index 0000000..5715dfd
--- /dev/null
@@ -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 (file)
index 0000000..087d68e
--- /dev/null
@@ -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)
index 106f83a..b483e57 100644 (file)
 #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;
 
        /*
@@ -86,7 +94,46 @@ pin_page_for_write(const void __user *_addr, pte_t **ptep, spinlock_t **ptlp)
        return 1;
 }
 
-static unsigned long noinline
+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;
+}
+
+unsigned long noinline
 __copy_to_user_memcpy(void __user *to, const void *from, unsigned long n)
 {
        unsigned long ua_flags;
@@ -139,6 +186,57 @@ out:
        return n;
 }
 
+unsigned long noinline
+__copy_from_user_memcpy(void *to, const void __user *from, unsigned long n)
+{
+       unsigned long ua_flags;
+       int atomic;
+
+       if (unlikely(uaccess_kernel())) {
+               memcpy(to, (const void *)from, n);
+               return 0;
+       }
+
+       /* 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;
+}
+
 unsigned long
 arm_copy_to_user(void __user *to, const void *from, unsigned long n)
 {
@@ -149,7 +247,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);
@@ -159,6 +257,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)
index 2890e61..a77c495 100644 (file)
@@ -162,9 +162,11 @@ config ARCH_BCM2835
        select HAVE_ARM_ARCH_TIMER if ARCH_MULTI_V7
        select BCM2835_TIMER
        select BRCMSTB_L2_IRQ
+       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.
@@ -183,6 +185,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_BCM_63XX
        bool "Broadcom BCM63xx DSL SoC"
        depends on ARCH_MULTI_V7
index bfc556f..91a758c 100644 (file)
 
 #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",
@@ -24,6 +114,25 @@ static const char * const bcm2835_compat[] = {
 };
 
 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
index f0f65eb..8680118 100644 (file)
@@ -198,7 +198,7 @@ ENTRY(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)
 #ifdef CONFIG_DMA_CACHE_RWFO
        ldrb    r2, [r0]                        @ read for ownership
        strb    r2, [r0]                        @ write for ownership
@@ -243,7 +243,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 CONFIG_DMA_CACHE_RWFO
index 830bbfb..0972487 100644 (file)
@@ -363,7 +363,8 @@ ENDPROC(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
@@ -393,7 +394,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
index d9f7dfe..687d126 100644 (file)
@@ -334,6 +334,8 @@ ENTRY(\name\()_cache_fns)
        .long   \name\()_flush_kern_dcache_area
        .long   \name\()_dma_map_area
        .long   \name\()_dma_unmap_area
+       .long   \name\()_dma_inv_range
+       .long   \name\()_dma_clean_range
        .long   \name\()_dma_flush_range
        .size   \name\()_cache_fns, . - \name\()_cache_fns
 .endm
index e212495..33e4a9b 100644 (file)
@@ -27,6 +27,9 @@ EXPORT_SYMBOL(__cpuc_flush_user_all);
 EXPORT_SYMBOL(__cpuc_flush_user_range);
 EXPORT_SYMBOL(__cpuc_coherent_kern_range);
 EXPORT_SYMBOL(__cpuc_flush_dcache_area);
+EXPORT_SYMBOL(dmac_inv_range);
+EXPORT_SYMBOL(dmac_clean_range);
+EXPORT_SYMBOL(dmac_flush_range);
 #else
 EXPORT_SYMBOL(cpu_cache);
 #endif
index a0618f3..b3a2fce 100644 (file)
@@ -70,10 +70,19 @@ ENDPROC(cpu_v6_reset)
  *
  *     IRQs are already disabled.
  */
+
+/* See jira SW-5991 for details of this workaround */
 ENTRY(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
 
 ENTRY(cpu_v6_dcache_clean_area)
index 2cb355c..1e2dcf8 100644 (file)
@@ -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
 
                /*
@@ -454,13 +457,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
@@ -523,7 +529,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;
@@ -726,7 +738,8 @@ void kernel_neon_begin(void)
        cpu = get_cpu();
 
        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,
index 639e01a..becca30 100644 (file)
@@ -30,3 +30,5 @@ subdir-y += synaptics
 subdir-y += ti
 subdir-y += toshiba
 subdir-y += xilinx
+
+subdir-y += overlays
index 11eae3e..9873335 100644 (file)
@@ -5,7 +5,22 @@ dtb-$(CONFIG_ARCH_BCM2835) += bcm2711-rpi-400.dtb \
                              bcm2837-rpi-3-b.dtb \
                              bcm2837-rpi-3-b-plus.dtb \
                              bcm2837-rpi-cm3-io3.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) += bcm2711-rpi-4-b.dtb
+dtb-$(CONFIG_ARCH_BCM2835) += bcm2711-rpi-400.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
 
 subdir-y       += bcm4908
 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 (file)
index 0000000..36ecea7
--- /dev/null
@@ -0,0 +1 @@
+#include "../../../../arm/boot/dts/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 (file)
index 0000000..22fc6a8
--- /dev/null
@@ -0,0 +1 @@
+#include "../../../../arm/boot/dts/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 (file)
index 0000000..4cacc5b
--- /dev/null
@@ -0,0 +1 @@
+#include "../../../../arm/boot/dts/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 (file)
index 0000000..e1e1378
--- /dev/null
@@ -0,0 +1 @@
+#include "../../../../arm/boot/dts/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 (file)
index 0000000..4d34e0a
--- /dev/null
@@ -0,0 +1 @@
+#include "../../../../arm/boot/dts/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 (file)
index 0000000..4d34e0a
--- /dev/null
@@ -0,0 +1 @@
+#include "../../../../arm/boot/dts/bcm2710-rpi-zero-2-w.dts"
index d24c536..bf69a4b 100644 (file)
@@ -1,2 +1 @@
-// SPDX-License-Identifier: GPL-2.0
-#include "arm/bcm2711-rpi-4-b.dts"
+#include "../../../../arm/boot/dts/bcm2711-rpi-4-b.dts"
index b9000f5..90c2b5a 100644 (file)
@@ -1,2 +1 @@
-// SPDX-License-Identifier: GPL-2.0
-#include "arm/bcm2711-rpi-400.dts"
+#include "../../../../arm/boot/dts/bcm2711-rpi-400.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 (file)
index 0000000..8064a58
--- /dev/null
@@ -0,0 +1 @@
+#include "../../../../arm/boot/dts/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 (file)
index 0000000..28e0980
--- /dev/null
@@ -0,0 +1 @@
+#include "../../../../arm/boot/dts/bcm2711-rpi-cm4s.dts"
diff --git a/arch/arm64/boot/dts/broadcom/bcm283x-rpi-csi1-2lane.dtsi b/arch/arm64/boot/dts/broadcom/bcm283x-rpi-csi1-2lane.dtsi
new file mode 120000 (symlink)
index 0000000..e5c4002
--- /dev/null
@@ -0,0 +1 @@
+../../../../arm/boot/dts/bcm283x-rpi-csi1-2lane.dtsi
\ No newline at end of file
diff --git a/arch/arm64/boot/dts/broadcom/bcm283x-rpi-lan7515.dtsi b/arch/arm64/boot/dts/broadcom/bcm283x-rpi-lan7515.dtsi
new file mode 120000 (symlink)
index 0000000..fc4c05b
--- /dev/null
@@ -0,0 +1 @@
+../../../../arm/boot/dts/bcm283x-rpi-lan7515.dtsi
\ No newline at end of file
diff --git a/arch/arm64/boot/dts/overlays b/arch/arm64/boot/dts/overlays
new file mode 120000 (symlink)
index 0000000..ded0864
--- /dev/null
@@ -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 (file)
index 0000000..6219138
--- /dev/null
@@ -0,0 +1,1602 @@
+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_XACCT=y
+CONFIG_TASK_IO_ACCOUNTING=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_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_EMBEDDED=y
+# CONFIG_COMPAT_BRK is not set
+CONFIG_PROFILING=y
+CONFIG_ARCH_BCM2835=y
+# CONFIG_CAVIUM_ERRATUM_22375 is not set
+# CONFIG_CAVIUM_ERRATUM_23154 is not set
+# CONFIG_CAVIUM_ERRATUM_27456 is not set
+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_CPU_IDLE=y
+CONFIG_ARM_CPUIDLE=y
+CONFIG_CPU_FREQ=y
+CONFIG_CPU_FREQ_STAT=y
+CONFIG_CPU_FREQ_DEFAULT_GOV_POWERSAVE=y
+CONFIG_CPU_FREQ_GOV_PERFORMANCE=y
+CONFIG_CPU_FREQ_GOV_USERSPACE=y
+CONFIG_CPU_FREQ_GOV_ONDEMAND=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_CRYPTO_AES_ARM64_BS=m
+CONFIG_JUMP_LABEL=y
+CONFIG_MODULES=y
+CONFIG_MODULE_UNLOAD=y
+CONFIG_MODVERSIONS=y
+CONFIG_MODULE_SRCVERSION_ALL=y
+CONFIG_MODULE_COMPRESS_XZ=y
+CONFIG_BLK_DEV_THROTTLING=y
+CONFIG_PARTITION_ADVANCED=y
+CONFIG_MAC_PARTITION=y
+CONFIG_BINFMT_MISC=m
+CONFIG_CLEANCACHE=y
+CONFIG_FRONTSWAP=y
+CONFIG_CMA=y
+CONFIG_ZSWAP=y
+CONFIG_Z3FOLD=m
+CONFIG_ZSMALLOC=m
+CONFIG_NET=y
+CONFIG_PACKET=y
+CONFIG_UNIX=y
+CONFIG_XFRM_USER=m
+CONFIG_XFRM_STATISTICS=y
+CONFIG_NET_KEY=m
+CONFIG_INET=y
+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=m
+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_COUNTER=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_OBJREF=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_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_FLOW_TABLE_IPV4=m
+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_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_CLUSTERIP=m
+CONFIG_IP_NF_TARGET_ECN=m
+CONFIG_IP_NF_TARGET_TTL=m
+CONFIG_IP_NF_RAW=m
+CONFIG_IP_NF_ARPTABLES=m
+CONFIG_IP_NF_ARPFILTER=m
+CONFIG_IP_NF_ARP_MANGLE=m
+CONFIG_NFT_DUP_IPV6=m
+CONFIG_NFT_FIB_IPV6=m
+CONFIG_NF_FLOW_TABLE_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_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_CBQ=m
+CONFIG_NET_SCH_HTB=m
+CONFIG_NET_SCH_HFSC=m
+CONFIG_NET_SCH_ATM=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_DSMARK=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_TCINDEX=m
+CONFIG_NET_CLS_ROUTE4=m
+CONFIG_NET_CLS_FW=m
+CONFIG_NET_CLS_U32=m
+CONFIG_CLS_U32_MARK=y
+CONFIG_NET_CLS_RSVP=m
+CONFIG_NET_CLS_RSVP6=m
+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_IPT=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_CAN_VCAN=m
+CONFIG_CAN_SLCAN=m
+CONFIG_CAN_MCP251X=m
+CONFIG_CAN_MCP251XFD=m
+CONFIG_CAN_EMS_USB=m
+CONFIG_CAN_GS_USB=m
+CONFIG_CAN_PEAK_USB=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_PCIE_BRCMSTB=y
+CONFIG_UEVENT_HELPER=y
+CONFIG_DEVTMPFS=y
+CONFIG_DEVTMPFS_MOUNT=y
+CONFIG_RASPBERRYPI_FIRMWARE=y
+# CONFIG_EFI_VARS_PSTORE is not set
+CONFIG_MTD=m
+CONFIG_MTD_BLOCK=m
+CONFIG_MTD_BLOCK2MTD=m
+CONFIG_MTD_SPI_NOR=m
+CONFIG_MTD_UBI=m
+CONFIG_OF_CONFIGFS=y
+CONFIG_ZRAM=m
+CONFIG_BLK_DEV_LOOP=y
+CONFIG_BLK_DEV_CRYPTOLOOP=m
+CONFIG_BLK_DEV_DRBD=m
+CONFIG_BLK_DEV_NBD=m
+CONFIG_BLK_DEV_RAM=y
+CONFIG_CDROM_PKTCDVD=m
+CONFIG_ATA_OVER_ETH=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_MD_LINEAR=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_DELAY=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_NET_VRF=m
+CONFIG_VSOCKMON=m
+CONFIG_BCMGENET=y
+CONFIG_ENC28J60=m
+CONFIG_QCA7000_SPI=m
+CONFIG_QCA7000_UART=m
+CONFIG_R8169=m
+CONFIG_WIZNET_W5100=m
+CONFIG_WIZNET_W5100_SPI=m
+CONFIG_MICREL_PHY=y
+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_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_HOSTAP=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_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_USB_ZD1201=m
+CONFIG_ZD1211RW=m
+CONFIG_MAC80211_HWSIM=m
+CONFIG_USB_NET_RNDIS_WLAN=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_RPISENSE=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_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_GAMEPORT_NS558=m
+CONFIG_GAMEPORT_L4=m
+CONFIG_BRCM_CHAR_DRIVERS=y
+CONFIG_BCM_VCIO=y
+CONFIG_BCM2835_DEVGPIOMEM=y
+CONFIG_RPIVID_MEM=m
+# 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_SC16IS7XX_SPI=y
+CONFIG_SERIAL_DEV_BUS=y
+CONFIG_TTY_PRINTK=y
+CONFIG_HW_RANDOM=y
+CONFIG_TCG_TPM=m
+CONFIG_TCG_TIS_SPI=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_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_DS1WM=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_GPIO=y
+CONFIG_RPI_POE_POWER=m
+CONFIG_BATTERY_DS2760=m
+CONFIG_BATTERY_MAX17040=m
+CONFIG_BATTERY_GAUGE_LTC2941=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_SHTC1=m
+CONFIG_SENSORS_EMC2305=m
+CONFIG_SENSORS_INA2XX=m
+CONFIG_SENSORS_TMP102=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_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_RC_CORE=y
+CONFIG_LIRC=y
+CONFIG_BPF_LIRC_MODE2=y
+CONFIG_RC_DECODERS=y
+CONFIG_IR_NEC_DECODER=m
+CONFIG_IR_RC5_DECODER=m
+CONFIG_IR_RC6_DECODER=m
+CONFIG_IR_JVC_DECODER=m
+CONFIG_IR_SONY_DECODER=m
+CONFIG_IR_SANYO_DECODER=m
+CONFIG_IR_SHARP_DECODER=m
+CONFIG_IR_MCE_KBD_DECODER=m
+CONFIG_IR_XMP_DECODER=m
+CONFIG_IR_IMON_DECODER=m
+CONFIG_RC_DEVICES=y
+CONFIG_RC_ATI_REMOTE=m
+CONFIG_IR_IMON=m
+CONFIG_IR_MCEUSB=m
+CONFIG_IR_REDRAT3=m
+CONFIG_IR_STREAMZAP=m
+CONFIG_IR_IGUANA=m
+CONFIG_IR_TTUSBIR=m
+CONFIG_RC_LOOPBACK=m
+CONFIG_IR_GPIO_CIR=m
+CONFIG_IR_GPIO_TX=m
+CONFIG_IR_PWM_TX=m
+CONFIG_IR_TOY=m
+CONFIG_MEDIA_CEC_RC=y
+CONFIG_MEDIA_SUPPORT=m
+CONFIG_MEDIA_USB_SUPPORT=y
+CONFIG_USB_VIDEO_CLASS=m
+CONFIG_USB_GSPCA=m
+CONFIG_USB_M5602=m
+CONFIG_USB_STV06XX=m
+CONFIG_USB_GL860=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_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_SPCA1528=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_PWC=m
+CONFIG_VIDEO_CPIA2=m
+CONFIG_USB_ZR364XX=m
+CONFIG_USB_STKWEBCAM=m
+CONFIG_USB_S2255=m
+CONFIG_VIDEO_USBTV=m
+CONFIG_VIDEO_PVRUSB2=m
+CONFIG_VIDEO_HDPVR=m
+CONFIG_VIDEO_STK1160_COMMON=m
+CONFIG_VIDEO_GO7007=m
+CONFIG_VIDEO_GO7007_USB=m
+CONFIG_VIDEO_GO7007_USB_S2250_BOARD=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_VIDEO_TM6000=m
+CONFIG_VIDEO_TM6000_ALSA=m
+CONFIG_VIDEO_TM6000_DVB=m
+CONFIG_DVB_USB=m
+CONFIG_DVB_USB_A800=m
+CONFIG_DVB_USB_DIBUSB_MB=m
+CONFIG_DVB_USB_DIBUSB_MB_FAULTY=y
+CONFIG_DVB_USB_DIBUSB_MC=m
+CONFIG_DVB_USB_DIB0700=m
+CONFIG_DVB_USB_UMT_010=m
+CONFIG_DVB_USB_CXUSB=m
+CONFIG_DVB_USB_M920X=m
+CONFIG_DVB_USB_DIGITV=m
+CONFIG_DVB_USB_VP7045=m
+CONFIG_DVB_USB_VP702X=m
+CONFIG_DVB_USB_GP8PSK=m
+CONFIG_DVB_USB_NOVA_T_USB2=m
+CONFIG_DVB_USB_TTUSB2=m
+CONFIG_DVB_USB_DTT200U=m
+CONFIG_DVB_USB_OPERA1=m
+CONFIG_DVB_USB_AF9005=m
+CONFIG_DVB_USB_AF9005_REMOTE=m
+CONFIG_DVB_USB_PCTV452E=m
+CONFIG_DVB_USB_DW2102=m
+CONFIG_DVB_USB_CINERGY_T2=m
+CONFIG_DVB_USB_DTV5100=m
+CONFIG_DVB_USB_AZ6027=m
+CONFIG_DVB_USB_TECHNISAT_USB2=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_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_DVBSKY=m
+CONFIG_SMS_USB_DRV=m
+CONFIG_DVB_B2C2_FLEXCOP_USB=m
+CONFIG_DVB_AS102=m
+CONFIG_VIDEO_EM28XX=m
+CONFIG_VIDEO_EM28XX_V4L2=m
+CONFIG_VIDEO_EM28XX_ALSA=m
+CONFIG_VIDEO_EM28XX_DVB=m
+CONFIG_RADIO_SI470X=m
+CONFIG_USB_SI470X=m
+CONFIG_I2C_SI470X=m
+CONFIG_RADIO_SI4713=m
+CONFIG_I2C_SI4713=m
+CONFIG_USB_MR800=m
+CONFIG_USB_DSBR=m
+CONFIG_RADIO_SHARK=m
+CONFIG_RADIO_SHARK2=m
+CONFIG_USB_KEENE=m
+CONFIG_USB_MA901=m
+CONFIG_RADIO_TEA5764=m
+CONFIG_RADIO_SAA7706H=m
+CONFIG_RADIO_TEF6862=m
+CONFIG_RADIO_WL1273=m
+CONFIG_RADIO_WL128X=m
+CONFIG_V4L_PLATFORM_DRIVERS=y
+CONFIG_VIDEO_MUX=m
+CONFIG_VIDEO_BCM2835_UNICAM=m
+CONFIG_V4L_TEST_DRIVERS=y
+CONFIG_VIDEO_VIMC=m
+CONFIG_VIDEO_VIVID=m
+CONFIG_VIDEO_VIM2M=m
+CONFIG_VIDEO_VICODEC=m
+CONFIG_VIDEO_UDA1342=m
+CONFIG_VIDEO_SONY_BTF_MPX=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_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_IMX477=m
+CONFIG_VIDEO_IMX519=m
+CONFIG_VIDEO_OV2311=m
+CONFIG_VIDEO_OV5647=m
+CONFIG_VIDEO_OV7251=m
+CONFIG_VIDEO_OV7640=m
+CONFIG_VIDEO_OV9281=m
+CONFIG_VIDEO_IRS1125=m
+CONFIG_VIDEO_MT9V011=m
+CONFIG_VIDEO_AD5398=m
+CONFIG_DRM=m
+CONFIG_DRM_LOAD_EDID_FIRMWARE=y
+CONFIG_DRM_UDL=m
+CONFIG_DRM_PANEL_SIMPLE=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_TPO_Y17P=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_MI0283QT=m
+CONFIG_TINYDRM_REPAPER=m
+CONFIG_TINYDRM_ST7586=m
+CONFIG_TINYDRM_ST7735R=m
+CONFIG_DRM_GUD=m
+CONFIG_FB=y
+CONFIG_FB_BCM2708=y
+CONFIG_FB_UDL=m
+CONFIG_FB_SIMPLE=y
+CONFIG_FB_SSD1307=m
+CONFIG_FB_RPISENSE=m
+CONFIG_BACKLIGHT_PWM=m
+CONFIG_BACKLIGHT_RPI=m
+CONFIG_BACKLIGHT_GPIO=m
+CONFIG_FRAMEBUFFER_CONSOLE=y
+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_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_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_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_WM8804_I2C=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_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_USB=y
+CONFIG_USB_ANNOUNCE_NEW_DEVICES=y
+CONFIG_USB_MON=m
+CONFIG_USB_XHCI_HCD=y
+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_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_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_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_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_FTDI_ELAN=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_BCM2835_SDHOST=y
+CONFIG_MMC_SDHCI=y
+CONFIG_MMC_SDHCI_PLTFM=y
+CONFIG_MMC_SDHCI_IPROC=y
+CONFIG_MMC_SPI=m
+CONFIG_LEDS_CLASS=y
+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_GPIO=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_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_AUXDISPLAY=y
+CONFIG_HD44780=m
+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_PRISM2_USB=m
+CONFIG_R8712U=m
+CONFIG_R8188EU=m
+CONFIG_88EU_AP_MODE=y
+CONFIG_VT6656=m
+CONFIG_STAGING_MEDIA=y
+CONFIG_VIDEO_RPIVID=m
+CONFIG_ASHMEM=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_UC1701=m
+CONFIG_FB_TFT_UPD161704=m
+CONFIG_FB_TFT_WATTEROTT=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_HTU21=m
+CONFIG_INV_MPU6050_I2C=m
+CONFIG_APDS9960=m
+CONFIG_BH1750=m
+CONFIG_TSL4531=m
+CONFIG_VEML6070=m
+CONFIG_IIO_HRTIMER_TRIGGER=m
+CONFIG_IIO_INTERRUPT_TRIGGER=m
+CONFIG_IIO_SYSFS_TRIGGER=m
+CONFIG_BMP280=m
+CONFIG_MAXIM_THERMOCOUPLE=m
+CONFIG_MAX31856=m
+CONFIG_PWM_BCM2835=m
+CONFIG_PWM_PCA9685=m
+CONFIG_PWM_RASPBERRYPI_POE=m
+CONFIG_RPI_AXIPERF=m
+CONFIG_ANDROID=y
+CONFIG_ANDROID_BINDER_IPC=y
+CONFIG_ANDROID_BINDERFS=y
+CONFIG_NVMEM_RMEM=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_FS_ENCRYPTION=y
+CONFIG_FANOTIFY=y
+CONFIG_QFMT_V1=m
+CONFIG_QFMT_V2=m
+CONFIG_AUTOFS4_FS=y
+CONFIG_FUSE_FS=m
+CONFIG_CUSE=m
+CONFIG_OVERLAY_FS=m
+CONFIG_FSCACHE=y
+CONFIG_FSCACHE_STATS=y
+CONFIG_CACHEFILES=y
+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_NTFS_FS=m
+CONFIG_NTFS_RW=y
+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_PSTORE=y
+CONFIG_PSTORE_CONSOLE=y
+CONFIG_PSTORE_RAM=y
+CONFIG_NFS_FS=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_NFS_FSCACHE=y
+CONFIG_NFSD=m
+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_SECURITY=y
+CONFIG_SECURITY_APPARMOR=y
+CONFIG_LSM=""
+CONFIG_CRYPTO_USER=m
+CONFIG_CRYPTO_CHACHA20POLY1305=m
+CONFIG_CRYPTO_CBC=m
+CONFIG_CRYPTO_ADIANTUM=m
+CONFIG_CRYPTO_HMAC=m
+CONFIG_CRYPTO_XCBC=m
+CONFIG_CRYPTO_WP512=m
+CONFIG_CRYPTO_AES=m
+CONFIG_CRYPTO_CAST5=m
+CONFIG_CRYPTO_DES=y
+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_HW is not set
+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_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_IRQSOFF_TRACER=y
+CONFIG_SCHED_TRACER=y
+CONFIG_BLK_DEV_IO_TRACE=y
+# CONFIG_UPROBE_EVENTS is not set
diff --git a/arch/arm64/configs/bcmrpi3_defconfig b/arch/arm64/configs/bcmrpi3_defconfig
new file mode 100644 (file)
index 0000000..96be5e2
--- /dev/null
@@ -0,0 +1,1498 @@
+CONFIG_LOCALVERSION="-v8"
+# CONFIG_LOCALVERSION_AUTO is not set
+CONFIG_SYSVIPC=y
+CONFIG_POSIX_MQUEUE=y
+CONFIG_NO_HZ=y
+CONFIG_HIGH_RES_TIMERS=y
+CONFIG_BPF_SYSCALL=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_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_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_EMBEDDED=y
+# CONFIG_COMPAT_BRK is not set
+CONFIG_PROFILING=y
+CONFIG_ARCH_BCM2835=y
+# CONFIG_CAVIUM_ERRATUM_22375 is not set
+# CONFIG_CAVIUM_ERRATUM_23154 is not set
+# CONFIG_CAVIUM_ERRATUM_27456 is not set
+CONFIG_SCHED_MC=y
+CONFIG_NR_CPUS=4
+CONFIG_HZ_1000=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_CPU_IDLE=y
+CONFIG_ARM_CPUIDLE=y
+CONFIG_CPU_FREQ=y
+CONFIG_CPU_FREQ_STAT=y
+CONFIG_CPU_FREQ_DEFAULT_GOV_POWERSAVE=y
+CONFIG_CPU_FREQ_GOV_PERFORMANCE=y
+CONFIG_CPU_FREQ_GOV_USERSPACE=y
+CONFIG_CPU_FREQ_GOV_ONDEMAND=y
+CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y
+CONFIG_CPU_FREQ_GOV_SCHEDUTIL=y
+CONFIG_CPUFREQ_DT=y
+CONFIG_ARM_RASPBERRYPI_CPUFREQ=y
+CONFIG_CRYPTO_AES_ARM64_BS=m
+CONFIG_JUMP_LABEL=y
+CONFIG_MODULES=y
+CONFIG_MODULE_UNLOAD=y
+CONFIG_MODVERSIONS=y
+CONFIG_MODULE_SRCVERSION_ALL=y
+CONFIG_MODULE_COMPRESS_XZ=y
+CONFIG_PARTITION_ADVANCED=y
+CONFIG_MAC_PARTITION=y
+CONFIG_BINFMT_MISC=y
+CONFIG_CLEANCACHE=y
+CONFIG_FRONTSWAP=y
+CONFIG_CMA=y
+CONFIG_ZSWAP=y
+CONFIG_Z3FOLD=m
+CONFIG_ZSMALLOC=m
+CONFIG_NET=y
+CONFIG_PACKET=y
+CONFIG_UNIX=y
+CONFIG_XFRM_USER=y
+CONFIG_NET_KEY=m
+CONFIG_INET=y
+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_FOU=m
+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_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_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_COUNTER=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_OBJREF=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_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_FLOW_TABLE_IPV4=m
+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_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_CLUSTERIP=m
+CONFIG_IP_NF_TARGET_ECN=m
+CONFIG_IP_NF_TARGET_TTL=m
+CONFIG_IP_NF_RAW=m
+CONFIG_IP_NF_ARPTABLES=m
+CONFIG_IP_NF_ARPFILTER=m
+CONFIG_IP_NF_ARP_MANGLE=m
+CONFIG_NFT_DUP_IPV6=m
+CONFIG_NFT_FIB_IPV6=m
+CONFIG_NF_FLOW_TABLE_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_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_CBQ=m
+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_DSMARK=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_INGRESS=m
+CONFIG_NET_SCH_PLUG=m
+CONFIG_NET_CLS_BASIC=m
+CONFIG_NET_CLS_TCINDEX=m
+CONFIG_NET_CLS_ROUTE4=m
+CONFIG_NET_CLS_FW=m
+CONFIG_NET_CLS_U32=m
+CONFIG_CLS_U32_MARK=y
+CONFIG_NET_CLS_RSVP=m
+CONFIG_NET_CLS_RSVP6=m
+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_IPT=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_CAN_VCAN=m
+CONFIG_CAN_MCP251X=m
+CONFIG_CAN_MCP251XFD=m
+CONFIG_CAN_EMS_USB=m
+CONFIG_CAN_PEAK_USB=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_EFI_VARS_PSTORE is not set
+CONFIG_MTD=m
+CONFIG_MTD_BLOCK=m
+CONFIG_MTD_UBI=m
+CONFIG_OF_CONFIGFS=y
+CONFIG_ZRAM=m
+CONFIG_BLK_DEV_LOOP=y
+CONFIG_BLK_DEV_CRYPTOLOOP=m
+CONFIG_BLK_DEV_DRBD=m
+CONFIG_BLK_DEV_NBD=m
+CONFIG_BLK_DEV_RAM=y
+CONFIG_CDROM_PKTCDVD=m
+CONFIG_ATA_OVER_ETH=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_MD=y
+CONFIG_MD_LINEAR=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_DELAY=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_NET_VRF=m
+CONFIG_VSOCKMON=m
+CONFIG_ENC28J60=m
+CONFIG_QCA7000_SPI=m
+CONFIG_QCA7000_UART=m
+CONFIG_WIZNET_W5100=m
+CONFIG_WIZNET_W5100_SPI=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_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_HOSTAP=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_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_USB_ZD1201=m
+CONFIG_ZD1211RW=m
+CONFIG_MAC80211_HWSIM=m
+CONFIG_USB_NET_RNDIS_WLAN=m
+CONFIG_IEEE802154_AT86RF230=m
+CONFIG_IEEE802154_MRF24J40=m
+CONFIG_IEEE802154_CC2520=m
+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_RPISENSE=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_EKTF2127=m
+CONFIG_TOUCHSCREEN_EDT_FT5X06=m
+CONFIG_TOUCHSCREEN_RASPBERRYPI_FW=m
+CONFIG_TOUCHSCREEN_USB_COMPOSITE=m
+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_GAMEPORT_NS558=m
+CONFIG_GAMEPORT_L4=m
+CONFIG_BRCM_CHAR_DRIVERS=y
+CONFIG_BCM_VCIO=y
+CONFIG_BCM2835_DEVGPIOMEM=y
+# CONFIG_BCM2835_SMI_DEV is not set
+# 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_SC16IS7XX_SPI=y
+CONFIG_SERIAL_DEV_BUS=y
+CONFIG_TTY_PRINTK=y
+CONFIG_HW_RANDOM=y
+CONFIG_TCG_TPM=m
+CONFIG_TCG_TIS_SPI=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_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_DS1WM=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_GPIO=y
+CONFIG_RPI_POE_POWER=m
+CONFIG_BATTERY_DS2760=m
+CONFIG_BATTERY_MAX17040=m
+CONFIG_BATTERY_GAUGE_LTC2941=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_SHTC1=m
+CONFIG_SENSORS_EMC2305=m
+CONFIG_SENSORS_INA2XX=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_SYSCON=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_RC_CORE=y
+CONFIG_LIRC=y
+CONFIG_BPF_LIRC_MODE2=y
+CONFIG_RC_DECODERS=y
+CONFIG_IR_NEC_DECODER=m
+CONFIG_IR_RC5_DECODER=m
+CONFIG_IR_RC6_DECODER=m
+CONFIG_IR_JVC_DECODER=m
+CONFIG_IR_SONY_DECODER=m
+CONFIG_IR_SANYO_DECODER=m
+CONFIG_IR_SHARP_DECODER=m
+CONFIG_IR_MCE_KBD_DECODER=m
+CONFIG_IR_XMP_DECODER=m
+CONFIG_IR_IMON_DECODER=m
+CONFIG_RC_DEVICES=y
+CONFIG_RC_ATI_REMOTE=m
+CONFIG_IR_IMON=m
+CONFIG_IR_MCEUSB=m
+CONFIG_IR_REDRAT3=m
+CONFIG_IR_STREAMZAP=m
+CONFIG_IR_IGUANA=m
+CONFIG_IR_TTUSBIR=m
+CONFIG_RC_LOOPBACK=m
+CONFIG_IR_GPIO_CIR=m
+CONFIG_IR_GPIO_TX=m
+CONFIG_IR_PWM_TX=m
+CONFIG_IR_TOY=m
+CONFIG_MEDIA_CEC_RC=y
+CONFIG_MEDIA_SUPPORT=m
+CONFIG_MEDIA_USB_SUPPORT=y
+CONFIG_USB_VIDEO_CLASS=m
+CONFIG_USB_GSPCA=m
+CONFIG_USB_M5602=m
+CONFIG_USB_STV06XX=m
+CONFIG_USB_GL860=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_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_SPCA1528=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_PWC=m
+CONFIG_VIDEO_CPIA2=m
+CONFIG_USB_ZR364XX=m
+CONFIG_USB_STKWEBCAM=m
+CONFIG_USB_S2255=m
+CONFIG_VIDEO_USBTV=m
+CONFIG_VIDEO_PVRUSB2=m
+CONFIG_VIDEO_HDPVR=m
+CONFIG_VIDEO_STK1160_COMMON=m
+CONFIG_VIDEO_GO7007=m
+CONFIG_VIDEO_GO7007_USB=m
+CONFIG_VIDEO_GO7007_USB_S2250_BOARD=m
+CONFIG_VIDEO_AU0828=m
+CONFIG_DVB_USB_V2=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_EC168=m
+CONFIG_DVB_USB_GL861=m
+CONFIG_DVB_USB_MXL111SF=m
+CONFIG_DVB_USB_DVBSKY=m
+CONFIG_SMS_USB_DRV=m
+CONFIG_DVB_B2C2_FLEXCOP_USB=m
+CONFIG_DVB_AS102=m
+CONFIG_VIDEO_EM28XX=m
+CONFIG_VIDEO_EM28XX_V4L2=m
+CONFIG_VIDEO_EM28XX_ALSA=m
+CONFIG_VIDEO_EM28XX_DVB=m
+CONFIG_RADIO_SI470X=m
+CONFIG_USB_SI470X=m
+CONFIG_I2C_SI470X=m
+CONFIG_RADIO_SI4713=m
+CONFIG_I2C_SI4713=m
+CONFIG_USB_MR800=m
+CONFIG_USB_DSBR=m
+CONFIG_RADIO_SHARK=m
+CONFIG_RADIO_SHARK2=m
+CONFIG_USB_KEENE=m
+CONFIG_USB_MA901=m
+CONFIG_RADIO_TEA5764=m
+CONFIG_RADIO_SAA7706H=m
+CONFIG_RADIO_TEF6862=m
+CONFIG_RADIO_WL1273=m
+CONFIG_RADIO_WL128X=m
+CONFIG_V4L_PLATFORM_DRIVERS=y
+CONFIG_VIDEO_MUX=m
+CONFIG_VIDEO_BCM2835_UNICAM=m
+CONFIG_V4L_TEST_DRIVERS=y
+CONFIG_VIDEO_VIMC=m
+CONFIG_VIDEO_VIVID=m
+CONFIG_VIDEO_VIM2M=m
+CONFIG_VIDEO_VICODEC=m
+CONFIG_VIDEO_UDA1342=m
+CONFIG_VIDEO_SONY_BTF_MPX=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_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_IMX477=m
+CONFIG_VIDEO_IMX519=m
+CONFIG_VIDEO_OV2311=m
+CONFIG_VIDEO_OV5647=m
+CONFIG_VIDEO_OV7251=m
+CONFIG_VIDEO_OV7640=m
+CONFIG_VIDEO_OV9281=m
+CONFIG_VIDEO_IRS1125=m
+CONFIG_VIDEO_MT9V011=m
+CONFIG_VIDEO_AD5398=m
+CONFIG_DRM=m
+CONFIG_DRM_LOAD_EDID_FIRMWARE=y
+CONFIG_DRM_UDL=m
+CONFIG_DRM_PANEL_SIMPLE=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_TPO_Y17P=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_MI0283QT=m
+CONFIG_TINYDRM_REPAPER=m
+CONFIG_TINYDRM_ST7586=m
+CONFIG_TINYDRM_ST7735R=m
+CONFIG_DRM_GUD=m
+CONFIG_FB=y
+CONFIG_FB_BCM2708=y
+CONFIG_FB_UDL=m
+CONFIG_FB_SIMPLE=y
+CONFIG_FB_SSD1307=m
+CONFIG_FB_RPISENSE=m
+CONFIG_BACKLIGHT_PWM=m
+CONFIG_BACKLIGHT_RPI=m
+CONFIG_BACKLIGHT_GPIO=m
+CONFIG_FRAMEBUFFER_CONSOLE=y
+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_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_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_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_WM8804_I2C=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_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_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_USB_DWC2=y
+CONFIG_USB_DWC2_HOST=y
+CONFIG_USB_SERIAL=m
+CONFIG_USB_SERIAL_GENERIC=y
+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_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_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_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_FTDI_ELAN=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_MMC=y
+CONFIG_MMC_BLOCK_MINORS=32
+CONFIG_MMC_BCM2835_MMC=y
+CONFIG_MMC_BCM2835_DMA=y
+CONFIG_MMC_BCM2835_SDHOST=y
+CONFIG_MMC_SDHCI=y
+CONFIG_MMC_SDHCI_PLTFM=y
+CONFIG_MMC_SDHCI_IPROC=m
+CONFIG_MMC_SPI=m
+CONFIG_LEDS_CLASS=y
+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_GPIO=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_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_VHOST_VSOCK=m
+CONFIG_STAGING=y
+CONFIG_PRISM2_USB=m
+CONFIG_R8712U=m
+CONFIG_R8188EU=m
+CONFIG_88EU_AP_MODE=y
+CONFIG_VT6656=m
+CONFIG_STAGING_MEDIA=y
+CONFIG_ASHMEM=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_UC1701=m
+CONFIG_FB_TFT_UPD161704=m
+CONFIG_FB_TFT_WATTEROTT=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_HTU21=m
+CONFIG_INV_MPU6050_I2C=m
+CONFIG_APDS9960=m
+CONFIG_BH1750=m
+CONFIG_TSL4531=m
+CONFIG_VEML6070=m
+CONFIG_IIO_HRTIMER_TRIGGER=m
+CONFIG_IIO_INTERRUPT_TRIGGER=m
+CONFIG_IIO_SYSFS_TRIGGER=m
+CONFIG_BMP280=m
+CONFIG_MAXIM_THERMOCOUPLE=m
+CONFIG_MAX31856=m
+CONFIG_PWM_BCM2835=m
+CONFIG_PWM_PCA9685=m
+CONFIG_PWM_RASPBERRYPI_POE=m
+CONFIG_RPI_AXIPERF=m
+CONFIG_ANDROID=y
+CONFIG_ANDROID_BINDER_IPC=y
+CONFIG_ANDROID_BINDERFS=y
+CONFIG_NVMEM_RMEM=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_FS_ENCRYPTION=y
+CONFIG_FANOTIFY=y
+CONFIG_QFMT_V1=m
+CONFIG_QFMT_V2=m
+CONFIG_AUTOFS4_FS=y
+CONFIG_FUSE_FS=m
+CONFIG_CUSE=m
+CONFIG_OVERLAY_FS=m
+CONFIG_FSCACHE=y
+CONFIG_FSCACHE_STATS=y
+CONFIG_CACHEFILES=y
+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_NTFS_FS=m
+CONFIG_NTFS_RW=y
+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_PSTORE=y
+CONFIG_PSTORE_CONSOLE=y
+CONFIG_PSTORE_RAM=y
+CONFIG_NFS_FS=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_NFS_FSCACHE=y
+CONFIG_NFSD=m
+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_SECURITY=y
+CONFIG_SECURITY_APPARMOR=y
+CONFIG_LSM=""
+CONFIG_CRYPTO_USER=m
+CONFIG_CRYPTO_CHACHA20POLY1305=m
+CONFIG_CRYPTO_ADIANTUM=m
+CONFIG_CRYPTO_XCBC=m
+CONFIG_CRYPTO_WP512=m
+CONFIG_CRYPTO_CAST5=m
+CONFIG_CRYPTO_DES=y
+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_HW is not set
+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_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_IRQSOFF_TRACER=y
+CONFIG_SCHED_TRACER=y
+CONFIG_BLK_DEV_IO_TRACE=y
+# CONFIG_UPROBE_EVENTS is not set
index 8caf6df..2c57728 100644 (file)
@@ -9,6 +9,16 @@
 #include <linux/crypto.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("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);
 
index 17e7359..445082a 100644 (file)
@@ -57,17 +57,17 @@ MODULE_DESCRIPTION("AES-ECB/CBC/CTR/XTS using ARMv8 Crypto Extensions");
 #define aes_mac_update         neon_aes_mac_update
 MODULE_DESCRIPTION("AES-ECB/CBC/CTR/XTS 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)");
-#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");
index fb507d5..cc52829 100644 (file)
 MODULE_AUTHOR("Ard Biesheuvel <ard.biesheuvel@linaro.org>");
 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[],
index 0e86e8b..62078b0 100644 (file)
@@ -182,10 +182,15 @@ static void __init register_insn_emulation(struct insn_emulation_ops *ops)
 
        switch (ops->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);
                insn->max = INSN_HW;
+#endif
                break;
        case INSN_OBSOLETE:
                insn->current_mode = INSN_UNDEF;
index 591c18a..ae90c11 100644 (file)
@@ -17,6 +17,7 @@
 #include <linux/elf.h>
 #include <linux/init.h>
 #include <linux/kernel.h>
+#include <linux/of_platform.h>
 #include <linux/personality.h>
 #include <linux/preempt.h>
 #include <linux/printk.h>
@@ -140,6 +141,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);
@@ -200,6 +205,26 @@ static int c_show(struct seq_file *m, void *v)
                seq_printf(m, "CPU revision\t: %d\n\n", MIDR_REVISION(midr));
        }
 
+       seq_printf(m, "Hardware\t: BCM2835\n");
+
+       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;
 }
 
index be5f85b..644b7cd 100644 (file)
@@ -307,13 +307,14 @@ void __init __no_sanitize_address setup_arch(char **cmdline_p)
        early_fixmap_init();
        early_ioremap_init();
 
-       setup_machine_fdt(__fdt_pointer);
-
        /*
         * Initialise the static keys early as they may be enabled by the
-        * cpufeature code and early parameters.
+        * cpufeature code, early parameters, and DT setup.
         */
        jump_label_init();
+
+       setup_machine_fdt(__fdt_pointer);
+
        parse_early_param();
 
        /*
index e0ea9d2..c439114 100644 (file)
@@ -357,7 +357,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);
index d454428..6814f1e 100644 (file)
@@ -5,6 +5,8 @@
 
 menu "Character devices"
 
+source "drivers/char/broadcom/Kconfig"
+
 source "drivers/tty/Kconfig"
 
 config TTY_PRINTK
index 264eb39..9bcd7d7 100644 (file)
@@ -46,3 +46,4 @@ 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/
diff --git a/drivers/char/broadcom/Kconfig b/drivers/char/broadcom/Kconfig
new file mode 100644 (file)
index 0000000..e555e84
--- /dev/null
@@ -0,0 +1,49 @@
+#
+# 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_DEVGPIOMEM
+       tristate "/dev/gpiomem rootless GPIO access via mmap() on the BCM2835"
+       default m
+       help
+               Provides users with root-free access to the GPIO registers
+               on the 2835. Calling mmap(/dev/gpiomem) will map the GPIO
+               register page to the user's pointer.
+
+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.
+
+config RPIVID_MEM
+       tristate "Character device driver for the Raspberry Pi RPIVid video decoder hardware"
+       default n
+       help
+               This driver provides a character device interface for memory-map operations
+               so userspace tools can access the control and status registers of the
+               Raspberry Pi RPiVid video decoder hardware.
diff --git a/drivers/char/broadcom/Makefile b/drivers/char/broadcom/Makefile
new file mode 100644 (file)
index 0000000..a302fb3
--- /dev/null
@@ -0,0 +1,5 @@
+obj-$(CONFIG_BCM2708_VCMEM)    += vc_mem.o
+obj-$(CONFIG_BCM_VCIO)         += vcio.o
+obj-$(CONFIG_BCM2835_DEVGPIOMEM)+= bcm2835-gpiomem.o
+obj-$(CONFIG_BCM2835_SMI_DEV)  += bcm2835_smi_dev.o
+obj-$(CONFIG_RPIVID_MEM)       += rpivid-mem.o
diff --git a/drivers/char/broadcom/bcm2835-gpiomem.c b/drivers/char/broadcom/bcm2835-gpiomem.c
new file mode 100644 (file)
index 0000000..f5e7f1b
--- /dev/null
@@ -0,0 +1,258 @@
+/**
+ * GPIO memory device driver
+ *
+ * Creates a chardev /dev/gpiomem which will provide user access to
+ * the BCM2835's GPIO registers when it is mmap()'d.
+ * No longer need root for user GPIO access, but without relaxing permissions
+ * on /dev/mem.
+ *
+ * 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/mm.h>
+#include <linux/slab.h>
+#include <linux/cdev.h>
+#include <linux/pagemap.h>
+#include <linux/io.h>
+
+#define DEVICE_NAME "bcm2835-gpiomem"
+#define DRIVER_NAME "gpiomem-bcm2835"
+#define DEVICE_MINOR 0
+
+struct bcm2835_gpiomem_instance {
+       unsigned long gpio_regs_phys;
+       struct device *dev;
+};
+
+static struct cdev bcm2835_gpiomem_cdev;
+static dev_t bcm2835_gpiomem_devid;
+static struct class *bcm2835_gpiomem_class;
+static struct device *bcm2835_gpiomem_dev;
+static struct bcm2835_gpiomem_instance *inst;
+
+
+/****************************************************************************
+*
+*   GPIO mem chardev file ops
+*
+***************************************************************************/
+
+static int bcm2835_gpiomem_open(struct inode *inode, struct file *file)
+{
+       int dev = iminor(inode);
+       int ret = 0;
+
+       if (dev != DEVICE_MINOR) {
+               dev_err(inst->dev, "Unknown minor device: %d", dev);
+               ret = -ENXIO;
+       }
+       return ret;
+}
+
+static int bcm2835_gpiomem_release(struct inode *inode, struct file *file)
+{
+       int dev = iminor(inode);
+       int ret = 0;
+
+       if (dev != DEVICE_MINOR) {
+               dev_err(inst->dev, "Unknown minor device %d", dev);
+               ret = -ENXIO;
+       }
+       return ret;
+}
+
+static const struct vm_operations_struct bcm2835_gpiomem_vm_ops = {
+#ifdef CONFIG_HAVE_IOREMAP_PROT
+       .access = generic_access_phys
+#endif
+};
+
+static int bcm2835_gpiomem_mmap(struct file *file, struct vm_area_struct *vma)
+{
+       /* Ignore what the user says - they're getting the GPIO regs
+          whether they like it or not! */
+       unsigned long gpio_page = inst->gpio_regs_phys >> PAGE_SHIFT;
+
+       vma->vm_page_prot = phys_mem_access_prot(file, gpio_page,
+                                                PAGE_SIZE,
+                                                vma->vm_page_prot);
+       vma->vm_ops = &bcm2835_gpiomem_vm_ops;
+       if (remap_pfn_range(vma, vma->vm_start,
+                       gpio_page,
+                       PAGE_SIZE,
+                       vma->vm_page_prot)) {
+               return -EAGAIN;
+       }
+       return 0;
+}
+
+static const struct file_operations
+bcm2835_gpiomem_fops = {
+       .owner = THIS_MODULE,
+       .open = bcm2835_gpiomem_open,
+       .release = bcm2835_gpiomem_release,
+       .mmap = bcm2835_gpiomem_mmap,
+};
+
+
+ /****************************************************************************
+*
+*   Probe and remove functions
+*
+***************************************************************************/
+
+
+static int bcm2835_gpiomem_probe(struct platform_device *pdev)
+{
+       int err;
+       void *ptr_err;
+       struct device *dev = &pdev->dev;
+       struct resource *ioresource;
+
+       /* Allocate buffers and instance data */
+
+       inst = kzalloc(sizeof(struct bcm2835_gpiomem_instance), GFP_KERNEL);
+
+       if (!inst) {
+               err = -ENOMEM;
+               goto failed_inst_alloc;
+       }
+
+       inst->dev = dev;
+
+       ioresource = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (ioresource) {
+               inst->gpio_regs_phys = ioresource->start;
+       } else {
+               dev_err(inst->dev, "failed to get IO resource");
+               err = -ENOENT;
+               goto failed_get_resource;
+       }
+
+       /* Create character device entries */
+
+       err = alloc_chrdev_region(&bcm2835_gpiomem_devid,
+                                 DEVICE_MINOR, 1, DEVICE_NAME);
+       if (err != 0) {
+               dev_err(inst->dev, "unable to allocate device number");
+               goto failed_alloc_chrdev;
+       }
+       cdev_init(&bcm2835_gpiomem_cdev, &bcm2835_gpiomem_fops);
+       bcm2835_gpiomem_cdev.owner = THIS_MODULE;
+       err = cdev_add(&bcm2835_gpiomem_cdev, bcm2835_gpiomem_devid, 1);
+       if (err != 0) {
+               dev_err(inst->dev, "unable to register device");
+               goto failed_cdev_add;
+       }
+
+       /* Create sysfs entries */
+
+       bcm2835_gpiomem_class = class_create(THIS_MODULE, DEVICE_NAME);
+       ptr_err = bcm2835_gpiomem_class;
+       if (IS_ERR(ptr_err))
+               goto failed_class_create;
+
+       bcm2835_gpiomem_dev = device_create(bcm2835_gpiomem_class, NULL,
+                                       bcm2835_gpiomem_devid, NULL,
+                                       "gpiomem");
+       ptr_err = bcm2835_gpiomem_dev;
+       if (IS_ERR(ptr_err))
+               goto failed_device_create;
+
+       dev_info(inst->dev, "Initialised: Registers at 0x%08lx",
+               inst->gpio_regs_phys);
+
+       return 0;
+
+failed_device_create:
+       class_destroy(bcm2835_gpiomem_class);
+failed_class_create:
+       cdev_del(&bcm2835_gpiomem_cdev);
+       err = PTR_ERR(ptr_err);
+failed_cdev_add:
+       unregister_chrdev_region(bcm2835_gpiomem_devid, 1);
+failed_alloc_chrdev:
+failed_get_resource:
+       kfree(inst);
+failed_inst_alloc:
+       dev_err(inst->dev, "could not load bcm2835_gpiomem");
+       return err;
+}
+
+static int bcm2835_gpiomem_remove(struct platform_device *pdev)
+{
+       struct device *dev = inst->dev;
+
+       kfree(inst);
+       device_destroy(bcm2835_gpiomem_class, bcm2835_gpiomem_devid);
+       class_destroy(bcm2835_gpiomem_class);
+       cdev_del(&bcm2835_gpiomem_cdev);
+       unregister_chrdev_region(bcm2835_gpiomem_devid, 1);
+
+       dev_info(dev, "GPIO mem driver removed - OK");
+       return 0;
+}
+
+ /****************************************************************************
+*
+*   Register the driver with device tree
+*
+***************************************************************************/
+
+static const struct of_device_id bcm2835_gpiomem_of_match[] = {
+       {.compatible = "brcm,bcm2835-gpiomem",},
+       { /* sentinel */ },
+};
+
+MODULE_DEVICE_TABLE(of, bcm2835_gpiomem_of_match);
+
+static struct platform_driver bcm2835_gpiomem_driver = {
+       .probe = bcm2835_gpiomem_probe,
+       .remove = bcm2835_gpiomem_remove,
+       .driver = {
+                  .name = DRIVER_NAME,
+                  .owner = THIS_MODULE,
+                  .of_match_table = bcm2835_gpiomem_of_match,
+                  },
+};
+
+module_platform_driver(bcm2835_gpiomem_driver);
+
+MODULE_ALIAS("platform:gpiomem-bcm2835");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("gpiomem driver for accessing GPIO from userspace");
+MODULE_AUTHOR("Luke Wren <luke@raspberrypi.org>");
diff --git a/drivers/char/broadcom/bcm2835_smi_dev.c b/drivers/char/broadcom/bcm2835_smi_dev.c
new file mode 100644 (file)
index 0000000..34976fa
--- /dev/null
@@ -0,0 +1,409 @@
+/**
+ * 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(THIS_MODULE, 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 int 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");
+       return 0;
+}
+
+/****************************************************************************
+*
+*   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/rpivid-mem.c b/drivers/char/broadcom/rpivid-mem.c
new file mode 100644 (file)
index 0000000..9f38083
--- /dev/null
@@ -0,0 +1,270 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+/**
+ * rpivid-mem.c - character device access to the RPiVid decoder registers
+ *
+ * Based on bcm2835-gpiomem.c. Provides IO memory access to the decoder
+ * register blocks such that ffmpeg plugins can access the hardware.
+ *
+ * Jonathan Bell <jonathan@raspberrypi.org>
+ * Copyright (c) 2019, 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/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 "rpivid-mem"
+#define DEVICE_MINOR 0
+
+struct rpivid_mem_priv {
+       dev_t devid;
+       struct class *class;
+       struct cdev rpivid_mem_cdev;
+       unsigned long regs_phys;
+       unsigned long mem_window_len;
+       struct device *dev;
+       const char *name;
+};
+
+static int rpivid_mem_open(struct inode *inode, struct file *file)
+{
+       int dev = iminor(inode);
+       int ret = 0;
+       struct rpivid_mem_priv *priv;
+
+       if (dev != DEVICE_MINOR && dev != DEVICE_MINOR + 1)
+               ret = -ENXIO;
+
+       priv = container_of(inode->i_cdev, struct rpivid_mem_priv,
+                               rpivid_mem_cdev);
+       if (!priv)
+               return -EINVAL;
+       file->private_data = priv;
+       return ret;
+}
+
+static int rpivid_mem_release(struct inode *inode, struct file *file)
+{
+       int dev = iminor(inode);
+       int ret = 0;
+
+       if (dev != DEVICE_MINOR && dev != DEVICE_MINOR + 1)
+               ret = -ENXIO;
+
+       return ret;
+}
+
+static const struct vm_operations_struct rpivid_mem_vm_ops = {
+#ifdef CONFIG_HAVE_IOREMAP_PROT
+       .access = generic_access_phys
+#endif
+};
+
+static int rpivid_mem_mmap(struct file *file, struct vm_area_struct *vma)
+{
+       struct rpivid_mem_priv *priv;
+       unsigned long pages;
+       unsigned long len;
+
+       priv = file->private_data;
+       pages = priv->regs_phys >> PAGE_SHIFT;
+       /*
+        * The address decode is far larger than the actual number of registers.
+        * Just map the whole lot in.
+        */
+       len = min(vma->vm_end - vma->vm_start, priv->mem_window_len);
+       vma->vm_page_prot = phys_mem_access_prot(file, pages, len,
+                                                vma->vm_page_prot);
+       vma->vm_ops = &rpivid_mem_vm_ops;
+       if (remap_pfn_range(vma, vma->vm_start,
+                           pages, len,
+                           vma->vm_page_prot)) {
+               return -EAGAIN;
+       }
+       return 0;
+}
+
+static const struct file_operations
+rpivid_mem_fops = {
+       .owner = THIS_MODULE,
+       .open = rpivid_mem_open,
+       .release = rpivid_mem_release,
+       .mmap = rpivid_mem_mmap,
+};
+
+static const struct of_device_id rpivid_mem_of_match[];
+static int rpivid_mem_probe(struct platform_device *pdev)
+{
+       int err;
+       const struct of_device_id *id;
+       struct device *dev = &pdev->dev;
+       struct resource *ioresource;
+       struct rpivid_mem_priv *priv;
+
+       /* Allocate buffers and instance data */
+
+       priv = kzalloc(sizeof(struct rpivid_mem_priv), GFP_KERNEL);
+
+       if (!priv) {
+               err = -ENOMEM;
+               goto failed_inst_alloc;
+       }
+       platform_set_drvdata(pdev, priv);
+
+       priv->dev = dev;
+       id = of_match_device(rpivid_mem_of_match, dev);
+       if (!id)
+               return -EINVAL;
+       priv->name = id->data;
+
+       ioresource = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (ioresource) {
+               priv->regs_phys = ioresource->start;
+               priv->mem_window_len = (ioresource->end + 1) - ioresource->start;
+       } else {
+               dev_err(priv->dev, "failed to get IO resource");
+               err = -ENOENT;
+               goto failed_get_resource;
+       }
+
+       /* Create character device entries */
+
+       err = alloc_chrdev_region(&priv->devid,
+                                 DEVICE_MINOR, 2, priv->name);
+       if (err != 0) {
+               dev_err(priv->dev, "unable to allocate device number");
+               goto failed_alloc_chrdev;
+       }
+       cdev_init(&priv->rpivid_mem_cdev, &rpivid_mem_fops);
+       priv->rpivid_mem_cdev.owner = THIS_MODULE;
+       err = cdev_add(&priv->rpivid_mem_cdev, priv->devid, 2);
+       if (err != 0) {
+               dev_err(priv->dev, "unable to register device");
+               goto failed_cdev_add;
+       }
+
+       /* Create sysfs entries */
+
+       priv->class = class_create(THIS_MODULE, 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, "%s initialised: Registers at 0x%08lx length 0x%08lx",
+               priv->name, priv->regs_phys, priv->mem_window_len);
+
+       return 0;
+
+failed_device_create:
+       class_destroy(priv->class);
+failed_class_create:
+       cdev_del(&priv->rpivid_mem_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 rpivid_mem");
+       return err;
+}
+
+static int rpivid_mem_remove(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct rpivid_mem_priv *priv = platform_get_drvdata(pdev);
+
+       device_destroy(priv->class, priv->devid);
+       class_destroy(priv->class);
+       cdev_del(&priv->rpivid_mem_cdev);
+       unregister_chrdev_region(priv->devid, 1);
+       kfree(priv);
+
+       dev_info(dev, "%s driver removed - OK", priv->name);
+       return 0;
+}
+
+static const struct of_device_id rpivid_mem_of_match[] = {
+       {
+               .compatible = "raspberrypi,rpivid-hevc-decoder",
+               .data = "rpivid-hevcmem",
+       },
+       {
+               .compatible = "raspberrypi,rpivid-h264-decoder",
+               .data = "rpivid-h264mem",
+       },
+       {
+               .compatible = "raspberrypi,rpivid-vp9-decoder",
+               .data = "rpivid-vp9mem",
+       },
+       /* The "intc" is included as this block of hardware contains the
+        * "frame done" status flags.
+        */
+       {
+               .compatible = "raspberrypi,rpivid-local-intc",
+               .data = "rpivid-intcmem",
+       },
+       { /* sentinel */ },
+};
+
+MODULE_DEVICE_TABLE(of, rpivid_mem_of_match);
+
+static struct platform_driver rpivid_mem_driver = {
+       .probe = rpivid_mem_probe,
+       .remove = rpivid_mem_remove,
+       .driver = {
+                  .name = DRIVER_NAME,
+                  .owner = THIS_MODULE,
+                  .of_match_table = rpivid_mem_of_match,
+                  },
+};
+
+module_platform_driver(rpivid_mem_driver);
+
+MODULE_ALIAS("platform:rpivid-mem");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Driver for accessing RPiVid decoder registers from userspace");
+MODULE_AUTHOR("Jonathan Bell <jonathan@raspberrypi.org>");
diff --git a/drivers/char/broadcom/vc_mem.c b/drivers/char/broadcom/vc_mem.c
new file mode 100644 (file)
index 0000000..195b61a
--- /dev/null
@@ -0,0 +1,373 @@
+/*
+ * 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>
+
+#define DRIVER_NAME  "vc-mem"
+
+/* 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
+
+/*
+ * 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;
+
+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 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;
+               }
+       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;
+
+       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(THIS_MODULE, 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
+
+       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__);
+
+       if (vc_mem_inited) {
+#if 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);
+       }
+}
+
+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 (file)
index 0000000..ac31461
--- /dev/null
@@ -0,0 +1,186 @@
+/*
+ *  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 <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 int vcio_remove(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+
+       misc_deregister(dev_get_drvdata(dev));
+       return 0;
+}
+
+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");
index 650c7d9..0e1e08f 100644 (file)
@@ -104,7 +104,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
index e7dd457..f9a4a89 100644 (file)
@@ -106,8 +106,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;
 }
index a437438..f689cd6 100644 (file)
@@ -28,6 +28,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
 #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;
@@ -165,6 +173,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);
@@ -189,11 +255,17 @@ static int iproc_rng200_probe(struct platform_device *pdev)
                return PTR_ERR(priv->base);
        }
 
-       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) {
index aaa59a0..b6cf048 100644 (file)
@@ -294,7 +294,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/.kunitconfig b/drivers/clk/.kunitconfig
new file mode 100644 (file)
index 0000000..e883f09
--- /dev/null
@@ -0,0 +1,3 @@
+CONFIG_KUNIT=y
+CONFIG_COMMON_CLK=y
+CONFIG_CLK_KUNIT_TEST=y
index c5b3dc9..8f905df 100644 (file)
@@ -90,6 +90,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
@@ -421,4 +427,12 @@ source "drivers/clk/x86/Kconfig"
 source "drivers/clk/xilinx/Kconfig"
 source "drivers/clk/zynqmp/Kconfig"
 
+# Kunit test cases
+config CLK_KUNIT_TEST
+       tristate "Basic Clock Framework Kunit Tests" if !KUNIT_ALL_TESTS
+       depends on KUNIT
+       default KUNIT_ALL_TESTS
+       help
+         Kunit tests for the common clock framework.
+
 endif
index e423121..99941b4 100644 (file)
@@ -2,6 +2,7 @@
 # common clock types
 obj-$(CONFIG_HAVE_CLK)         += clk-devres.o clk-bulk.o clkdev.o
 obj-$(CONFIG_COMMON_CLK)       += clk.o
+obj-$(CONFIG_CLK_KUNIT_TEST)   += clk_test.o
 obj-$(CONFIG_COMMON_CLK)       += clk-divider.o
 obj-$(CONFIG_COMMON_CLK)       += clk-fixed-factor.o
 obj-$(CONFIG_COMMON_CLK)       += clk-fixed-rate.o
@@ -17,6 +18,7 @@ endif
 
 # hardware specific clock types
 # please keep this section sorted lexicographically by file path name
+obj-$(CONFIG_SND_BCM2708_SOC_ALLO_BOSS_DAC)    += clk-allo-dac.o
 obj-$(CONFIG_MACH_ASM9260)             += clk-asm9260.o
 obj-$(CONFIG_COMMON_CLK_AXI_CLKGEN)    += clk-axi-clkgen.o
 obj-$(CONFIG_ARCH_AXXIA)               += clk-axm5516.o
@@ -38,6 +40,8 @@ obj-$(CONFIG_CLK_HSDK)                        += clk-hsdk-pll.o
 obj-$(CONFIG_COMMON_CLK_K210)          += clk-k210.o
 obj-$(CONFIG_LMK04832)                 += clk-lmk04832.o
 obj-$(CONFIG_COMMON_CLK_LOCHNAGAR)     += clk-lochnagar.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
index 3667b4d..9ed157b 100644 (file)
@@ -35,6 +35,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
 
 #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
@@ -313,6 +316,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;
 
@@ -640,15 +644,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,
@@ -1010,6 +1016,30 @@ static unsigned long bcm2835_clock_get_rate(struct clk_hw *hw,
        return bcm2835_clock_rate_from_divisor(clock, parent_rate, div);
 }
 
+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;
@@ -1068,8 +1098,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;
@@ -1079,15 +1111,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);
 
@@ -1098,6 +1139,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)
 {
@@ -1281,6 +1328,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,
@@ -1298,7 +1346,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,
@@ -1306,6 +1354,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)
 {
@@ -1323,6 +1373,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;
@@ -1378,6 +1431,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;
 
@@ -1432,6 +1492,15 @@ static struct clk_hw *bcm2835_register_clock(struct bcm2835_cprman *cprman,
        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.
         */
@@ -1442,7 +1511,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.
@@ -1667,16 +1735,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",
@@ -1977,14 +2041,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
@@ -2146,21 +2208,6 @@ static const struct bcm2835_clk_desc clk_desc_array[] = {
                .frac_bits = 12,
                .tcnt_mux = 28),
 
-       /* 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,
@@ -2210,6 +2257,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.
  *
@@ -2229,6 +2278,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;
@@ -2237,7 +2301,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);
@@ -2256,6 +2322,21 @@ static int bcm2835_clk_probe(struct platform_device *pdev)
        if (IS_ERR(cprman->regs))
                return PTR_ERR(cprman->regs);
 
+       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,
@@ -2289,8 +2370,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 = {
@@ -2316,7 +2404,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");
index dd3b71e..c782843 100644 (file)
@@ -33,6 +33,7 @@ enum rpi_firmware_clk_id {
        RPI_FIRMWARE_EMMC2_CLK_ID,
        RPI_FIRMWARE_M2MC_CLK_ID,
        RPI_FIRMWARE_PIXEL_BVB_CLK_ID,
+       RPI_FIRMWARE_VEC_CLK_ID,
        RPI_FIRMWARE_NUM_CLK_ID,
 };
 
@@ -51,11 +52,14 @@ static char *rpi_firmware_clk_names[] = {
        [RPI_FIRMWARE_EMMC2_CLK_ID]     = "emmc2",
        [RPI_FIRMWARE_M2MC_CLK_ID]      = "m2mc",
        [RPI_FIRMWARE_PIXEL_BVB_CLK_ID] = "pixel-bvb",
+       [RPI_FIRMWARE_VEC_CLK_ID]       = "vec",
 };
 
 #define RPI_FIRMWARE_STATE_ENABLE_BIT  BIT(0)
 #define RPI_FIRMWARE_STATE_WAIT_BIT    BIT(1)
 
+struct raspberrypi_clk_variant;
+
 struct raspberrypi_clk {
        struct device *dev;
        struct rpi_firmware *firmware;
@@ -66,10 +70,86 @@ struct raspberrypi_clk_data {
        struct clk_hw hw;
 
        unsigned int id;
+       struct raspberrypi_clk_variant *variant;
 
        struct raspberrypi_clk *rpi;
 };
 
+struct raspberrypi_clk_variant {
+       bool            export;
+       char            *clkdev;
+       unsigned long   min_rate;
+       bool            minimize;
+};
+
+static struct raspberrypi_clk_variant
+raspberrypi_clk_variants[RPI_FIRMWARE_NUM_CLK_ID] = {
+       [RPI_FIRMWARE_ARM_CLK_ID] = {
+               .export = true,
+               .clkdev = "cpu0",
+       },
+       [RPI_FIRMWARE_CORE_CLK_ID] = {
+               .export = true,
+
+               /*
+                * The clock is shared between the HVS and the CSI
+                * controllers, on the BCM2711 and will change depending
+                * on the pixels composited on the HVS and the capture
+                * resolution on Unicam.
+                *
+                * Since the rate can get quite large, and we need to
+                * coordinate between both driver instances, let's
+                * always use the minimum the drivers will let us.
+                */
+               .minimize = true,
+       },
+       [RPI_FIRMWARE_M2MC_CLK_ID] = {
+               .export = true,
+
+               /*
+                * If we boot without any cable connected to any of the
+                * HDMI connector, the firmware will skip the HSM
+                * initialization and leave it with a rate of 0,
+                * resulting in a bus lockup when we're accessing the
+                * registers even if it's enabled.
+                *
+                * Let's put a sensible default so that we don't end up
+                * in this situation.
+                */
+               .min_rate = 120000000,
+
+               /*
+                * The clock is shared between the two HDMI controllers
+                * on the BCM2711 and will change depending on the
+                * resolution output on each. Since the rate can get
+                * quite large, and we need to coordinate between both
+                * driver instances, let's always use the minimum the
+                * drivers will let us.
+                */
+               .minimize = true,
+       },
+       [RPI_FIRMWARE_V3D_CLK_ID] = {
+               .export = true,
+               .minimize = true,
+       },
+       [RPI_FIRMWARE_HEVC_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_PIXEL_CLK_ID] = {
+               .export = true,
+               .minimize = true,
+       },
+};
+
 /*
  * Structure of the message passed to Raspberry Pi's firmware in order to
  * change clock rates. The 'disable_turbo' option is only available to the ARM
@@ -97,7 +177,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;
 
@@ -165,12 +245,26 @@ 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);
+       struct raspberrypi_clk_variant *variant = data->variant;
+
        /*
         * The firmware will do the rounding but that isn't part of
         * the interface with the firmware, so we just do our best
         * here.
         */
+
        req->rate = clamp(req->rate, req->min_rate, req->max_rate);
+
+       /*
+        * We want to aggressively reduce the clock rate here, so let's
+        * just ignore the requested rate and return the bare minimum
+        * rate we can get away with.
+        */
+       if (variant->minimize && req->min_rate > 0)
+               req->rate = req->min_rate;
+
        return 0;
 }
 
@@ -183,7 +277,8 @@ static const struct clk_ops raspberrypi_firmware_clk_ops = {
 
 static struct clk_hw *raspberrypi_clk_register(struct raspberrypi_clk *rpi,
                                               unsigned int parent,
-                                              unsigned int id)
+                                              unsigned int id,
+                                              struct raspberrypi_clk_variant *variant)
 {
        struct raspberrypi_clk_data *data;
        struct clk_init_data init = {};
@@ -195,6 +290,7 @@ static struct clk_hw *raspberrypi_clk_register(struct raspberrypi_clk *rpi,
                return ERR_PTR(-ENOMEM);
        data->rpi = rpi;
        data->id = id;
+       data->variant = variant;
 
        init.name = devm_kasprintf(rpi->dev, GFP_KERNEL,
                                   "fw-clk-%s",
@@ -228,15 +324,28 @@ static struct clk_hw *raspberrypi_clk_register(struct raspberrypi_clk *rpi,
 
        clk_hw_set_rate_range(&data->hw, min_rate, max_rate);
 
-       if (id == RPI_FIRMWARE_ARM_CLK_ID) {
+       if (variant->clkdev) {
                ret = devm_clk_hw_register_clkdev(rpi->dev, &data->hw,
-                                                 NULL, "cpu0");
+                                                 NULL, variant->clkdev);
                if (ret) {
                        dev_err(rpi->dev, "Failed to initialize clkdev\n");
                        return ERR_PTR(ret);
                }
        }
 
+       if (variant->min_rate) {
+               unsigned long rate;
+
+               clk_hw_set_rate_range(&data->hw, variant->min_rate, max_rate);
+
+               rate = raspberrypi_fw_get_rate(&data->hw, 0);
+               if (rate < variant->min_rate) {
+                       ret = raspberrypi_fw_set_rate(&data->hw, variant->min_rate, 0);
+                       if (ret)
+                               return ERR_PTR(ret);
+               }
+       }
+
        return &data->hw;
 }
 
@@ -264,27 +373,27 @@ static int raspberrypi_discover_clocks(struct raspberrypi_clk *rpi,
                return ret;
 
        while (clks->id) {
-               struct clk_hw *hw;
-
-               switch (clks->id) {
-               case RPI_FIRMWARE_ARM_CLK_ID:
-               case RPI_FIRMWARE_CORE_CLK_ID:
-               case RPI_FIRMWARE_M2MC_CLK_ID:
-               case RPI_FIRMWARE_V3D_CLK_ID:
-               case RPI_FIRMWARE_PIXEL_BVB_CLK_ID:
+               struct raspberrypi_clk_variant *variant;
+
+               if (clks->id > RPI_FIRMWARE_NUM_CLK_ID) {
+                       dev_err(rpi->dev, "Unknown clock id: %u", clks->id);
+                       return -EINVAL;
+               }
+
+               variant = &raspberrypi_clk_variants[clks->id];
+               if (variant->export) {
+                       struct clk_hw *hw;
+
                        hw = raspberrypi_clk_register(rpi, clks->parent,
-                                                     clks->id);
+                                                     clks->id, variant);
                        if (IS_ERR(hw))
                                return PTR_ERR(hw);
 
                        data->hws[clks->id] = hw;
                        data->num = clks->id + 1;
-                       fallthrough;
-
-               default:
-                       clks++;
-                       break;
                }
+
+               clks++;
        }
 
        return 0;
diff --git a/drivers/clk/clk-allo-dac.c b/drivers/clk/clk-allo-dac.c
new file mode 100644 (file)
index 0000000..a9844cb
--- /dev/null
@@ -0,0 +1,161 @@
+/*
+ * Clock Driver for Allo DAC
+ *
+ * Author:     Baswaraj K <jaikumar@cem-solutions.net>
+ *             Copyright 2016
+ *             based on code by Stuart MacLean
+ *
+ * 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>
+
+/* 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
+
+/**
+ * struct allo_dac_clk - Common struct to the Allo DAC
+ * @hw: clk_hw for the common clk framework
+ * @mode: 0 => CLK44EN, 1 => CLK48EN
+ */
+struct clk_allo_hw {
+       struct clk_hw hw;
+       uint8_t mode;
+};
+
+#define to_allo_clk(_hw) container_of(_hw, struct clk_allo_hw, hw)
+
+static const struct of_device_id clk_allo_dac_dt_ids[] = {
+       { .compatible = "allo,dac-clk",},
+       { }
+};
+MODULE_DEVICE_TABLE(of, clk_allo_dac_dt_ids);
+
+static unsigned long clk_allo_dac_recalc_rate(struct clk_hw *hw,
+       unsigned long parent_rate)
+{
+       return (to_allo_clk(hw)->mode == 0) ? CLK_44EN_RATE :
+               CLK_48EN_RATE;
+}
+
+static long clk_allo_dac_round_rate(struct clk_hw *hw,
+       unsigned long rate, unsigned long *parent_rate)
+{
+       long actual_rate;
+
+       if (rate <= CLK_44EN_RATE) {
+               actual_rate = (long)CLK_44EN_RATE;
+       } else if (rate >= CLK_48EN_RATE) {
+               actual_rate = (long)CLK_48EN_RATE;
+       } else {
+               long diff44Rate = (long)(rate - CLK_44EN_RATE);
+               long diff48Rate = (long)(CLK_48EN_RATE - rate);
+
+               if (diff44Rate < diff48Rate)
+                       actual_rate = (long)CLK_44EN_RATE;
+               else
+                       actual_rate = (long)CLK_48EN_RATE;
+       }
+       return actual_rate;
+}
+
+
+static int clk_allo_dac_set_rate(struct clk_hw *hw,
+       unsigned long rate, unsigned long parent_rate)
+{
+       unsigned long actual_rate;
+       struct clk_allo_hw *clk = to_allo_clk(hw);
+
+       actual_rate = (unsigned long)clk_allo_dac_round_rate(hw, rate,
+               &parent_rate);
+       clk->mode = (actual_rate == CLK_44EN_RATE) ? 0 : 1;
+       return 0;
+}
+
+
+const struct clk_ops clk_allo_dac_rate_ops = {
+       .recalc_rate = clk_allo_dac_recalc_rate,
+       .round_rate = clk_allo_dac_round_rate,
+       .set_rate = clk_allo_dac_set_rate,
+};
+
+static int clk_allo_dac_probe(struct platform_device *pdev)
+{
+       int ret;
+       struct clk_allo_hw *proclk;
+       struct clk *clk;
+       struct device *dev;
+       struct clk_init_data init;
+
+       dev = &pdev->dev;
+
+       proclk = kzalloc(sizeof(struct clk_allo_hw), GFP_KERNEL);
+       if (!proclk)
+               return -ENOMEM;
+
+       init.name = "clk-allo-dac";
+       init.ops = &clk_allo_dac_rate_ops;
+       init.flags = 0;
+       init.parent_names = NULL;
+       init.num_parents = 0;
+
+       proclk->mode = 0;
+       proclk->hw.init = &init;
+
+       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 int clk_allo_dac_remove(struct platform_device *pdev)
+{
+       of_clk_del_provider(pdev->dev.of_node);
+       return 0;
+}
+
+static struct platform_driver clk_allo_dac_driver = {
+       .probe = clk_allo_dac_probe,
+       .remove = clk_allo_dac_remove,
+       .driver = {
+               .name = "clk-allo-dac",
+               .of_match_table = clk_allo_dac_dt_ids,
+       },
+};
+
+static int __init clk_allo_dac_init(void)
+{
+       return platform_driver_register(&clk_allo_dac_driver);
+}
+core_initcall(clk_allo_dac_init);
+
+static void __exit clk_allo_dac_exit(void)
+{
+       platform_driver_unregister(&clk_allo_dac_driver);
+}
+module_exit(clk_allo_dac_exit);
+
+MODULE_DESCRIPTION("Allo DAC clock driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:clk-allo-dac");
diff --git a/drivers/clk/clk-hifiberry-dachd.c b/drivers/clk/clk-hifiberry-dachd.c
new file mode 100644 (file)
index 0000000..ec528a0
--- /dev/null
@@ -0,0 +1,333 @@
+// 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,
+                            const struct i2c_device_id *id)
+{
+       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 int clk_hifiberry_dachd_i2c_remove(struct i2c_client *i2c)
+{
+       clk_hifiberry_dachd_remove(&i2c->dev);
+       return 0;
+}
+
+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 (file)
index 0000000..9e26344
--- /dev/null
@@ -0,0 +1,160 @@
+/*
+ * 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>
+
+/* 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
+
+/**
+ * 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;
+};
+
+#define to_hifiberry_clk(_hw) container_of(_hw, struct clk_hifiberry_hw, hw)
+
+static const struct of_device_id clk_hifiberry_dacpro_dt_ids[] = {
+       { .compatible = "hifiberry,dacpro-clk",},
+       { }
+};
+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)
+{
+       return (to_hifiberry_clk(hw)->mode == 0) ? CLK_44EN_RATE :
+               CLK_48EN_RATE;
+}
+
+static long clk_hifiberry_dacpro_round_rate(struct clk_hw *hw,
+       unsigned long rate, unsigned long *parent_rate)
+{
+       long actual_rate;
+
+       if (rate <= CLK_44EN_RATE) {
+               actual_rate = (long)CLK_44EN_RATE;
+       } else if (rate >= CLK_48EN_RATE) {
+               actual_rate = (long)CLK_48EN_RATE;
+       } else {
+               long diff44Rate = (long)(rate - CLK_44EN_RATE);
+               long diff48Rate = (long)(CLK_48EN_RATE - rate);
+
+               if (diff44Rate < diff48Rate)
+                       actual_rate = (long)CLK_44EN_RATE;
+               else
+                       actual_rate = (long)CLK_48EN_RATE;
+       }
+       return actual_rate;
+}
+
+
+static int clk_hifiberry_dacpro_set_rate(struct clk_hw *hw,
+       unsigned long rate, unsigned long parent_rate)
+{
+       unsigned long actual_rate;
+       struct clk_hifiberry_hw *clk = to_hifiberry_clk(hw);
+
+       actual_rate = (unsigned long)clk_hifiberry_dacpro_round_rate(hw, rate,
+               &parent_rate);
+       clk->mode = (actual_rate == CLK_44EN_RATE) ? 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)
+{
+       int ret;
+       struct clk_hifiberry_hw *proclk;
+       struct clk *clk;
+       struct device *dev;
+       struct clk_init_data init;
+
+       dev = &pdev->dev;
+
+       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;
+
+       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 int clk_hifiberry_dacpro_remove(struct platform_device *pdev)
+{
+       of_clk_del_provider(pdev->dev.of_node);
+       return 0;
+}
+
+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");
index d6dc58b..de6715c 100644 (file)
@@ -269,7 +269,7 @@ EXPORT_SYMBOL_GPL(__clk_get_name);
 
 const char *clk_hw_get_name(const struct clk_hw *hw)
 {
-       return hw->core->name;
+       return !hw ? NULL : hw->core->name;
 }
 EXPORT_SYMBOL_GPL(clk_hw_get_name);
 
@@ -542,6 +542,51 @@ static bool mux_is_better_rate(unsigned long rate, unsigned long now,
        return now <= rate && now > best;
 }
 
+static void clk_core_init_rate_req(struct clk_core * const core,
+                                  struct clk_rate_request *req,
+                                  unsigned long rate);
+
+static int clk_core_round_rate_nolock(struct clk_core *core,
+                                     struct clk_rate_request *req);
+
+static bool clk_core_has_parent(struct clk_core *core, struct clk_core *parent)
+{
+       unsigned int i;
+
+       /* Optimize for the case where the parent is already the parent. */
+       if (core == parent)
+               return true;
+
+       for (i = 0; i < core->num_parents; i++) {
+               struct clk_core *tmp = clk_core_get_parent_by_index(core, i);
+               if (!tmp)
+                       continue;
+
+               if (tmp == parent)
+                       return true;
+       }
+
+       return false;
+}
+
+static void
+clk_core_forward_rate_req(struct clk_core * const core,
+                         struct clk_core * const parent,
+                         struct clk_rate_request * const old_req,
+                         struct clk_rate_request *req)
+{
+       if (WARN_ON(!clk_core_has_parent(core, parent)))
+               return;
+
+       clk_core_init_rate_req(parent, req, old_req->rate);
+
+       if (req->min_rate < old_req->min_rate)
+               req->min_rate = old_req->min_rate;
+
+       if (req->max_rate > old_req->max_rate)
+               req->max_rate = old_req->max_rate;
+}
+
 int clk_mux_determine_rate_flags(struct clk_hw *hw,
                                 struct clk_rate_request *req,
                                 unsigned long flags)
@@ -549,14 +594,20 @@ int clk_mux_determine_rate_flags(struct clk_hw *hw,
        struct clk_core *core = hw->core, *parent, *best_parent = NULL;
        int i, num_parents, ret;
        unsigned long best = 0;
-       struct clk_rate_request parent_req = *req;
 
        /* if NO_REPARENT flag set, pass through to current parent */
        if (core->flags & CLK_SET_RATE_NO_REPARENT) {
                parent = core->parent;
                if (core->flags & CLK_SET_RATE_PARENT) {
-                       ret = __clk_determine_rate(parent ? parent->hw : NULL,
-                                                  &parent_req);
+                       struct clk_rate_request parent_req;
+
+                       if (!parent) {
+                               req->rate = 0;
+                               return 0;
+                       }
+
+                       clk_core_forward_rate_req(core, parent, req, &parent_req);
+                       ret = clk_core_round_rate_nolock(parent, &parent_req);
                        if (ret)
                                return ret;
 
@@ -573,23 +624,29 @@ int clk_mux_determine_rate_flags(struct clk_hw *hw,
        /* find the parent that can provide the fastest rate <= rate */
        num_parents = core->num_parents;
        for (i = 0; i < num_parents; i++) {
+               unsigned long parent_rate;
+
                parent = clk_core_get_parent_by_index(core, i);
                if (!parent)
                        continue;
 
                if (core->flags & CLK_SET_RATE_PARENT) {
-                       parent_req = *req;
-                       ret = __clk_determine_rate(parent->hw, &parent_req);
+                       struct clk_rate_request parent_req;
+
+                       clk_core_forward_rate_req(core, parent, req, &parent_req);
+                       ret = clk_core_round_rate_nolock(parent, &parent_req);
                        if (ret)
                                continue;
+
+                       parent_rate = parent_req.rate;
                } else {
-                       parent_req.rate = clk_core_get_rate_nolock(parent);
+                       parent_rate = clk_core_get_rate_nolock(parent);
                }
 
-               if (mux_is_better_rate(req->rate, parent_req.rate,
+               if (mux_is_better_rate(req->rate, parent_rate,
                                       best, flags)) {
                        best_parent = parent;
-                       best = parent_req.rate;
+                       best = parent_rate;
                }
        }
 
@@ -1347,6 +1404,8 @@ static int clk_core_determine_round_nolock(struct clk_core *core,
        if (!core)
                return 0;
 
+       req->rate = clamp(req->rate, req->min_rate, req->max_rate);
+
        /*
         * At this point, core protection will be disabled
         * - if the provider is not protected at all
@@ -1372,13 +1431,19 @@ static int clk_core_determine_round_nolock(struct clk_core *core,
 }
 
 static void clk_core_init_rate_req(struct clk_core * const core,
-                                  struct clk_rate_request *req)
+                                  struct clk_rate_request *req,
+                                  unsigned long rate)
 {
        struct clk_core *parent;
 
        if (WARN_ON(!core || !req))
                return;
 
+       memset(req, 0, sizeof(*req));
+
+       req->rate = rate;
+       clk_core_get_boundaries(core, &req->min_rate, &req->max_rate);
+
        parent = core->parent;
        if (parent) {
                req->best_parent_hw = parent->hw;
@@ -1389,6 +1454,26 @@ static void clk_core_init_rate_req(struct clk_core * const core,
        }
 }
 
+/**
+ * clk_hw_init_rate_request - Initializes a clk_rate_request
+ * @hw: the clk for which we want to submit a rate request
+ * @req: the clk_rate_request structure we want to initialise
+ * @rate: the rate which is to be requested
+ *
+ * Initializes and fills a clk_rate_request structure to submit to
+ * __clk_determine_rate or similar functions.
+ */
+void clk_hw_init_rate_request(struct clk_hw * const hw,
+                             struct clk_rate_request *req,
+                             unsigned long rate)
+{
+       if (WARN_ON(!hw || !req))
+               return;
+
+       clk_core_init_rate_req(hw->core, req, rate);
+}
+EXPORT_SYMBOL_GPL(clk_hw_init_rate_request);
+
 static bool clk_core_can_round(struct clk_core * const core)
 {
        return core->ops->determine_rate || core->ops->round_rate;
@@ -1397,6 +1482,8 @@ static bool clk_core_can_round(struct clk_core * const core)
 static int clk_core_round_rate_nolock(struct clk_core *core,
                                      struct clk_rate_request *req)
 {
+       int ret;
+
        lockdep_assert_held(&prepare_lock);
 
        if (!core) {
@@ -1404,12 +1491,22 @@ static int clk_core_round_rate_nolock(struct clk_core *core,
                return 0;
        }
 
-       clk_core_init_rate_req(core, req);
-
        if (clk_core_can_round(core))
                return clk_core_determine_round_nolock(core, req);
-       else if (core->flags & CLK_SET_RATE_PARENT)
-               return clk_core_round_rate_nolock(core->parent, req);
+
+       if (core->flags & CLK_SET_RATE_PARENT) {
+               struct clk_rate_request parent_req;
+
+               clk_core_forward_rate_req(core, core->parent, req, &parent_req);
+               ret = clk_core_round_rate_nolock(core->parent, &parent_req);
+               if (ret)
+                       return ret;
+
+               req->best_parent_rate = parent_req.rate;
+               req->rate = parent_req.rate;
+
+               return 0;
+       }
 
        req->rate = core->rate;
        return 0;
@@ -1453,8 +1550,7 @@ unsigned long clk_hw_round_rate(struct clk_hw *hw, unsigned long rate)
        int ret;
        struct clk_rate_request req;
 
-       clk_core_get_boundaries(hw->core, &req.min_rate, &req.max_rate);
-       req.rate = rate;
+       clk_core_init_rate_req(hw->core, &req, rate);
 
        ret = clk_core_round_rate_nolock(hw->core, &req);
        if (ret)
@@ -1486,8 +1582,7 @@ long clk_round_rate(struct clk *clk, unsigned long rate)
        if (clk->exclusive_count)
                clk_core_rate_unprotect(clk->core);
 
-       clk_core_get_boundaries(clk->core, &req.min_rate, &req.max_rate);
-       req.rate = rate;
+       clk_core_init_rate_req(clk->core, &req, rate);
 
        ret = clk_core_round_rate_nolock(clk->core, &req);
 
@@ -1756,6 +1851,23 @@ static void clk_core_update_orphan_status(struct clk_core *core, bool is_orphan)
                clk_core_update_orphan_status(child, is_orphan);
 }
 
+/*
+ * Update the orphan rate and req_rate of @core and all its children.
+ */
+static void clk_core_update_orphan_child_rates(struct clk_core *core)
+{
+       struct clk_core *child;
+       unsigned long parent_rate = 0;
+
+       if (core->parent)
+               parent_rate = core->parent->rate;
+
+       core->rate = core->req_rate = clk_recalc(core, parent_rate);
+
+       hlist_for_each_entry(child, &core->children, child_node)
+               clk_core_update_orphan_child_rates(child);
+}
+
 static void clk_reparent(struct clk_core *core, struct clk_core *new_parent)
 {
        bool was_orphan = core->orphan;
@@ -1780,6 +1892,7 @@ static void clk_reparent(struct clk_core *core, struct clk_core *new_parent)
        }
 
        core->parent = new_parent;
+       clk_core_update_orphan_child_rates(core);
 }
 
 static struct clk_core *__clk_set_parent_before(struct clk_core *core,
@@ -1974,11 +2087,7 @@ static struct clk_core *clk_calc_new_rates(struct clk_core *core,
        if (clk_core_can_round(core)) {
                struct clk_rate_request req;
 
-               req.rate = rate;
-               req.min_rate = min_rate;
-               req.max_rate = max_rate;
-
-               clk_core_init_rate_req(core, &req);
+               clk_core_init_rate_req(core, &req, rate);
 
                ret = clk_core_determine_round_nolock(core, &req);
                if (ret < 0)
@@ -2177,8 +2286,7 @@ static unsigned long clk_core_req_round_rate_nolock(struct clk_core *core,
        if (cnt < 0)
                return cnt;
 
-       clk_core_get_boundaries(core, &req.min_rate, &req.max_rate);
-       req.rate = req_rate;
+       clk_core_init_rate_req(core, &req, req_rate);
 
        ret = clk_core_round_rate_nolock(core, &req);
 
@@ -2329,19 +2437,15 @@ int clk_set_rate_exclusive(struct clk *clk, unsigned long rate)
 }
 EXPORT_SYMBOL_GPL(clk_set_rate_exclusive);
 
-/**
- * clk_set_rate_range - set a rate range for a clock source
- * @clk: clock source
- * @min: desired minimum clock rate in Hz, inclusive
- * @max: desired maximum clock rate in Hz, inclusive
- *
- * Returns success (0) or negative errno.
- */
-int clk_set_rate_range(struct clk *clk, unsigned long min, unsigned long max)
+static int clk_set_rate_range_nolock(struct clk *clk,
+                                    unsigned long min,
+                                    unsigned long max)
 {
        int ret = 0;
        unsigned long old_min, old_max, rate;
 
+       lockdep_assert_held(&prepare_lock);
+
        if (!clk)
                return 0;
 
@@ -2354,8 +2458,6 @@ int clk_set_rate_range(struct clk *clk, unsigned long min, unsigned long max)
                return -EINVAL;
        }
 
-       clk_prepare_lock();
-
        if (clk->exclusive_count)
                clk_core_rate_unprotect(clk->core);
 
@@ -2370,38 +2472,67 @@ int clk_set_rate_range(struct clk *clk, unsigned long min, unsigned long max)
                goto out;
        }
 
-       rate = clk_core_get_rate_nolock(clk->core);
-       if (rate < min || rate > max) {
-               /*
-                * FIXME:
-                * We are in bit of trouble here, current rate is outside the
-                * the requested range. We are going try to request appropriate
-                * range boundary but there is a catch. It may fail for the
-                * usual reason (clock broken, clock protected, etc) but also
-                * because:
-                * - round_rate() was not favorable and fell on the wrong
-                *   side of the boundary
-                * - the determine_rate() callback does not really check for
-                *   this corner case when determining the rate
-                */
+       rate = clk->core->req_rate;
+       if (clk->core->flags & CLK_GET_RATE_NOCACHE)
+               rate = clk_core_get_rate_recalc(clk->core);
 
-               if (rate < min)
-                       rate = min;
-               else
-                       rate = max;
+       if (clk->core->orphan && !rate) {
+               pr_warn("%s: clk %s: Clock is orphan and doesn't have a rate!\n",
+                       __func__, clk->core->name);
+               goto out;
+       }
 
-               ret = clk_core_set_rate_nolock(clk->core, rate);
-               if (ret) {
-                       /* rollback the changes */
-                       clk->min_rate = old_min;
-                       clk->max_rate = old_max;
-               }
+       /*
+        * Since the boundaries have been changed, let's give the
+        * opportunity to the provider to adjust the clock rate based on
+        * the new boundaries.
+        *
+        * We also need to handle the case where the clock is currently
+        * outside of the boundaries. Clamping the last requested rate
+        * to the current minimum and maximum will also handle this.
+        *
+        * FIXME:
+        * There is a catch. It may fail for the usual reason (clock
+        * broken, clock protected, etc) but also because:
+        * - round_rate() was not favorable and fell on the wrong
+        *   side of the boundary
+        * - the determine_rate() callback does not really check for
+        *   this corner case when determining the rate
+        */
+       rate = clamp(rate, min, max);
+       ret = clk_core_set_rate_nolock(clk->core, rate);
+       if (ret) {
+               /* rollback the changes */
+               clk->min_rate = old_min;
+               clk->max_rate = old_max;
        }
 
 out:
        if (clk->exclusive_count)
                clk_core_rate_protect(clk->core);
 
+       return ret;
+}
+
+/**
+ * clk_set_rate_range - set a rate range for a clock source
+ * @clk: clock source
+ * @min: desired minimum clock rate in Hz, inclusive
+ * @max: desired maximum clock rate in Hz, inclusive
+ *
+ * Return: 0 for success or negative errno on failure.
+ */
+int clk_set_rate_range(struct clk *clk, unsigned long min, unsigned long max)
+{
+       int ret;
+
+       if (!clk)
+               return 0;
+
+       clk_prepare_lock();
+
+       ret = clk_set_rate_range_nolock(clk, min, max);
+
        clk_prepare_unlock();
 
        return ret;
@@ -2445,6 +2576,26 @@ int clk_set_max_rate(struct clk *clk, unsigned long rate)
 EXPORT_SYMBOL_GPL(clk_set_max_rate);
 
 /**
+ * clk_get_rate_range - returns the clock rate range for a clock source
+ * @clk: clock source
+ * @min: Pointer to the variable that will hold the minimum
+ * @max: Pointer to the variable that will hold the maximum
+ *
+ * Fills the @min and @max variables with the minimum and maximum that
+ * the clock source can reach.
+ */
+void clk_get_rate_range(struct clk *clk, unsigned long *min, unsigned long *max)
+{
+       if (!clk || !min || !max)
+               return;
+
+       clk_prepare_lock();
+       clk_core_get_boundaries(clk->core, min, max);
+       clk_prepare_unlock();
+}
+EXPORT_SYMBOL_GPL(clk_get_rate_range);
+
+/**
  * clk_get_parent - return the parent of a clk
  * @clk: the clk whose parent gets returned
  *
@@ -2504,25 +2655,11 @@ void clk_hw_reparent(struct clk_hw *hw, struct clk_hw *new_parent)
  */
 bool clk_has_parent(struct clk *clk, struct clk *parent)
 {
-       struct clk_core *core, *parent_core;
-       int i;
-
        /* NULL clocks should be nops, so return success if either is NULL. */
        if (!clk || !parent)
                return true;
 
-       core = clk->core;
-       parent_core = parent->core;
-
-       /* Optimize for the case where the parent is already the parent. */
-       if (core->parent == parent_core)
-               return true;
-
-       for (i = 0; i < core->num_parents; i++)
-               if (!strcmp(core->parents[i].name, parent_core->name))
-                       return true;
-
-       return false;
+       return clk_core_has_parent(clk->core, parent->core);
 }
 EXPORT_SYMBOL_GPL(clk_has_parent);
 
@@ -4361,9 +4498,10 @@ void __clk_put(struct clk *clk)
        }
 
        hlist_del(&clk->clks_node);
-       if (clk->min_rate > clk->core->req_rate ||
-           clk->max_rate < clk->core->req_rate)
-               clk_core_set_rate_nolock(clk->core, clk->core->req_rate);
+
+       /* If we had any boundaries on that clock, let's drop them. */
+       if (clk->min_rate > 0 || clk->max_rate < ULONG_MAX)
+               clk_set_rate_range_nolock(clk, 0, ULONG_MAX);
 
        owner = clk->core->owner;
        kref_put(&clk->core->ref, __clk_release);
diff --git a/drivers/clk/clk_test.c b/drivers/clk/clk_test.c
new file mode 100644 (file)
index 0000000..af40d46
--- /dev/null
@@ -0,0 +1,2370 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Kunit test for clk rate management
+ */
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+
+/* Needed for clk_hw_get_clk() */
+#include "clk.h"
+
+#include <kunit/test.h>
+
+#define DUMMY_CLOCK_INIT_RATE  (42 * 1000 * 1000)
+#define DUMMY_CLOCK_RATE_1     (142 * 1000 * 1000)
+#define DUMMY_CLOCK_RATE_2     (242 * 1000 * 1000)
+
+struct clk_dummy_context {
+       struct clk_hw hw;
+       unsigned long rate;
+};
+
+static unsigned long clk_dummy_recalc_rate(struct clk_hw *hw,
+                                          unsigned long parent_rate)
+{
+       struct clk_dummy_context *ctx =
+               container_of(hw, struct clk_dummy_context, hw);
+
+       return ctx->rate;
+}
+
+static int clk_dummy_determine_rate(struct clk_hw *hw,
+                                   struct clk_rate_request *req)
+{
+       /* Just return the same rate without modifying it */
+       return 0;
+}
+
+static int clk_dummy_maximize_rate(struct clk_hw *hw,
+                                  struct clk_rate_request *req)
+{
+       /*
+        * If there's a maximum set, always run the clock at the maximum
+        * allowed.
+        */
+       if (req->max_rate < ULONG_MAX)
+               req->rate = req->max_rate;
+
+       return 0;
+}
+
+static int clk_dummy_minimize_rate(struct clk_hw *hw,
+                                  struct clk_rate_request *req)
+{
+       /*
+        * If there's a minimum set, always run the clock at the minimum
+        * allowed.
+        */
+       if (req->min_rate > 0)
+               req->rate = req->min_rate;
+
+       return 0;
+}
+
+static int clk_dummy_set_rate(struct clk_hw *hw,
+                             unsigned long rate,
+                             unsigned long parent_rate)
+{
+       struct clk_dummy_context *ctx =
+               container_of(hw, struct clk_dummy_context, hw);
+
+       ctx->rate = rate;
+       return 0;
+}
+
+static int clk_dummy_single_set_parent(struct clk_hw *hw, u8 index)
+{
+       if (index >= clk_hw_get_num_parents(hw))
+               return -EINVAL;
+
+       return 0;
+}
+
+static u8 clk_dummy_single_get_parent(struct clk_hw *hw)
+{
+       return 0;
+}
+
+static const struct clk_ops clk_dummy_rate_ops = {
+       .recalc_rate = clk_dummy_recalc_rate,
+       .determine_rate = clk_dummy_determine_rate,
+       .set_rate = clk_dummy_set_rate,
+};
+
+static const struct clk_ops clk_dummy_maximize_rate_ops = {
+       .recalc_rate = clk_dummy_recalc_rate,
+       .determine_rate = clk_dummy_maximize_rate,
+       .set_rate = clk_dummy_set_rate,
+};
+
+static const struct clk_ops clk_dummy_minimize_rate_ops = {
+       .recalc_rate = clk_dummy_recalc_rate,
+       .determine_rate = clk_dummy_minimize_rate,
+       .set_rate = clk_dummy_set_rate,
+};
+
+static const struct clk_ops clk_dummy_single_parent_ops = {
+       .set_parent = clk_dummy_single_set_parent,
+       .get_parent = clk_dummy_single_get_parent,
+};
+
+struct clk_multiple_parent_ctx {
+       struct clk_dummy_context parents_ctx[2];
+       struct clk_hw hw;
+       u8 current_parent;
+};
+
+static int clk_multiple_parents_mux_set_parent(struct clk_hw *hw, u8 index)
+{
+       struct clk_multiple_parent_ctx *ctx =
+               container_of(hw, struct clk_multiple_parent_ctx, hw);
+
+       if (index >= clk_hw_get_num_parents(hw))
+               return -EINVAL;
+
+       ctx->current_parent = index;
+
+       return 0;
+}
+
+static u8 clk_multiple_parents_mux_get_parent(struct clk_hw *hw)
+{
+       struct clk_multiple_parent_ctx *ctx =
+               container_of(hw, struct clk_multiple_parent_ctx, hw);
+
+       return ctx->current_parent;
+}
+
+static const struct clk_ops clk_multiple_parents_mux_ops = {
+       .get_parent = clk_multiple_parents_mux_get_parent,
+       .set_parent = clk_multiple_parents_mux_set_parent,
+       .determine_rate = __clk_mux_determine_rate_closest,
+};
+
+static int clk_test_init_with_ops(struct kunit *test, const struct clk_ops *ops)
+{
+       struct clk_dummy_context *ctx;
+       struct clk_init_data init = { };
+       int ret;
+
+       ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+       if (!ctx)
+               return -ENOMEM;
+       ctx->rate = DUMMY_CLOCK_INIT_RATE;
+       test->priv = ctx;
+
+       init.name = "test_dummy_rate";
+       init.ops = ops;
+       ctx->hw.init = &init;
+
+       ret = clk_hw_register(NULL, &ctx->hw);
+       if (ret)
+               return ret;
+
+       return 0;
+}
+
+static int clk_test_init(struct kunit *test)
+{
+       return clk_test_init_with_ops(test, &clk_dummy_rate_ops);
+}
+
+static int clk_maximize_test_init(struct kunit *test)
+{
+       return clk_test_init_with_ops(test, &clk_dummy_maximize_rate_ops);
+}
+
+static int clk_minimize_test_init(struct kunit *test)
+{
+       return clk_test_init_with_ops(test, &clk_dummy_minimize_rate_ops);
+}
+
+static void clk_test_exit(struct kunit *test)
+{
+       struct clk_dummy_context *ctx = test->priv;
+
+       clk_hw_unregister(&ctx->hw);
+}
+
+/*
+ * Test that the actual rate matches what is returned by clk_get_rate()
+ */
+static void clk_test_get_rate(struct kunit *test)
+{
+       struct clk_dummy_context *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       unsigned long rate;
+
+       rate = clk_get_rate(clk);
+       KUNIT_ASSERT_GT(test, rate, 0);
+       KUNIT_EXPECT_EQ(test, rate, ctx->rate);
+}
+
+/*
+ * Test that, after a call to clk_set_rate(), the rate returned by
+ * clk_get_rate() matches.
+ *
+ * This assumes that clk_ops.determine_rate or clk_ops.round_rate won't
+ * modify the requested rate, which is our case in clk_dummy_rate_ops.
+ */
+static void clk_test_set_get_rate(struct kunit *test)
+{
+       struct clk_dummy_context *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       unsigned long rate;
+
+       KUNIT_ASSERT_EQ(test,
+                       clk_set_rate(clk, DUMMY_CLOCK_RATE_1),
+                       0);
+
+       rate = clk_get_rate(clk);
+       KUNIT_ASSERT_GT(test, rate, 0);
+       KUNIT_EXPECT_EQ(test, rate, DUMMY_CLOCK_RATE_1);
+}
+
+/*
+ * Test that, after several calls to clk_set_rate(), the rate returned
+ * by clk_get_rate() matches the last one.
+ *
+ * This assumes that clk_ops.determine_rate or clk_ops.round_rate won't
+ * modify the requested rate, which is our case in clk_dummy_rate_ops.
+ */
+static void clk_test_set_set_get_rate(struct kunit *test)
+{
+       struct clk_dummy_context *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       unsigned long rate;
+
+       KUNIT_ASSERT_EQ(test,
+                       clk_set_rate(clk, DUMMY_CLOCK_RATE_1),
+                       0);
+
+       KUNIT_ASSERT_EQ(test,
+                       clk_set_rate(clk, DUMMY_CLOCK_RATE_2),
+                       0);
+
+       rate = clk_get_rate(clk);
+       KUNIT_ASSERT_GT(test, rate, 0);
+       KUNIT_EXPECT_EQ(test, rate, DUMMY_CLOCK_RATE_2);
+}
+
+/*
+ * Test that clk_round_rate and clk_set_rate are consitent and will
+ * return the same frequency.
+ */
+static void clk_test_round_set_get_rate(struct kunit *test)
+{
+       struct clk_dummy_context *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       unsigned long rounded_rate, set_rate;
+
+       rounded_rate = clk_round_rate(clk, DUMMY_CLOCK_RATE_1);
+       KUNIT_ASSERT_GT(test, rounded_rate, 0);
+       KUNIT_EXPECT_EQ(test, rounded_rate, DUMMY_CLOCK_RATE_1);
+
+       KUNIT_ASSERT_EQ(test,
+                       clk_set_rate(clk, DUMMY_CLOCK_RATE_1),
+                       0);
+
+       set_rate = clk_get_rate(clk);
+       KUNIT_ASSERT_GT(test, set_rate, 0);
+       KUNIT_EXPECT_EQ(test, rounded_rate, set_rate);
+}
+
+static struct kunit_case clk_test_cases[] = {
+       KUNIT_CASE(clk_test_get_rate),
+       KUNIT_CASE(clk_test_set_get_rate),
+       KUNIT_CASE(clk_test_set_set_get_rate),
+       KUNIT_CASE(clk_test_round_set_get_rate),
+       {}
+};
+
+/*
+ * Test suite for a basic rate clock, without any parent.
+ *
+ * These tests are supposed to exercise the rate API with simple scenarios
+ */
+static struct kunit_suite clk_test_suite = {
+       .name = "clk-test",
+       .init = clk_test_init,
+       .exit = clk_test_exit,
+       .test_cases = clk_test_cases,
+};
+
+static int clk_uncached_test_init(struct kunit *test)
+{
+       struct clk_dummy_context *ctx;
+       int ret;
+
+       ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+       if (!ctx)
+               return -ENOMEM;
+       test->priv = ctx;
+
+       ctx->rate = DUMMY_CLOCK_INIT_RATE;
+       ctx->hw.init = CLK_HW_INIT_NO_PARENT("test-clk",
+                                            &clk_dummy_rate_ops,
+                                            CLK_GET_RATE_NOCACHE);
+
+       ret = clk_hw_register(NULL, &ctx->hw);
+       if (ret)
+               return ret;
+
+       return 0;
+}
+
+/*
+ * Test that for an uncached clock, the clock framework doesn't cache
+ * the rate and clk_get_rate() will return the underlying clock rate
+ * even if it changed.
+ */
+static void clk_test_uncached_get_rate(struct kunit *test)
+{
+       struct clk_dummy_context *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       unsigned long rate;
+
+       rate = clk_get_rate(clk);
+       KUNIT_ASSERT_GT(test, rate, 0);
+       KUNIT_EXPECT_EQ(test, rate, DUMMY_CLOCK_INIT_RATE);
+
+       ctx->rate = DUMMY_CLOCK_RATE_1;
+       rate = clk_get_rate(clk);
+       KUNIT_ASSERT_GT(test, rate, 0);
+       KUNIT_EXPECT_EQ(test, rate, DUMMY_CLOCK_RATE_1);
+}
+
+/*
+ * Test that for an uncached clock, clk_set_rate_range() will work
+ * properly if the rate hasn't changed.
+ */
+static void clk_test_uncached_set_range(struct kunit *test)
+{
+       struct clk_dummy_context *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       unsigned long rate;
+
+       KUNIT_ASSERT_EQ(test,
+                       clk_set_rate_range(clk,
+                                          DUMMY_CLOCK_RATE_1,
+                                          DUMMY_CLOCK_RATE_2),
+                       0);
+
+       rate = clk_get_rate(clk);
+       KUNIT_ASSERT_GT(test, rate, 0);
+       KUNIT_EXPECT_GE(test, rate, DUMMY_CLOCK_RATE_1);
+       KUNIT_EXPECT_LE(test, rate, DUMMY_CLOCK_RATE_2);
+}
+
+/*
+ * Test that for an uncached clock, clk_set_rate_range() will work
+ * properly if the rate has changed in hardware.
+ *
+ * In this case, it means that if the rate wasn't initially in the range
+ * we're trying to set, but got changed at some point into the range
+ * without the kernel knowing about it, its rate shouldn't be affected.
+ */
+static void clk_test_uncached_updated_rate_set_range(struct kunit *test)
+{
+       struct clk_dummy_context *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       unsigned long rate;
+
+       ctx->rate = DUMMY_CLOCK_RATE_1 + 1000;
+       KUNIT_ASSERT_EQ(test,
+                       clk_set_rate_range(clk,
+                                          DUMMY_CLOCK_RATE_1,
+                                          DUMMY_CLOCK_RATE_2),
+                       0);
+
+       rate = clk_get_rate(clk);
+       KUNIT_ASSERT_GT(test, rate, 0);
+       KUNIT_EXPECT_EQ(test, rate, DUMMY_CLOCK_RATE_1 + 1000);
+}
+
+static struct kunit_case clk_uncached_test_cases[] = {
+       KUNIT_CASE(clk_test_uncached_get_rate),
+       KUNIT_CASE(clk_test_uncached_set_range),
+       KUNIT_CASE(clk_test_uncached_updated_rate_set_range),
+       {}
+};
+
+/*
+ * Test suite for a basic, uncached, rate clock, without any parent.
+ *
+ * These tests are supposed to exercise the rate API with simple scenarios
+ */
+static struct kunit_suite clk_uncached_test_suite = {
+       .name = "clk-uncached-test",
+       .init = clk_uncached_test_init,
+       .exit = clk_test_exit,
+       .test_cases = clk_uncached_test_cases,
+};
+
+static int
+clk_multiple_parents_mux_test_init(struct kunit *test)
+{
+       struct clk_multiple_parent_ctx *ctx;
+       const char *parents[2] = { "parent-0", "parent-1"};
+       int ret;
+
+       ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+       if (!ctx)
+               return -ENOMEM;
+       test->priv = ctx;
+
+       ctx->parents_ctx[0].hw.init = CLK_HW_INIT_NO_PARENT("parent-0",
+                                                           &clk_dummy_rate_ops,
+                                                           0);
+       ctx->parents_ctx[0].rate = DUMMY_CLOCK_RATE_1;
+       ret = clk_hw_register(NULL, &ctx->parents_ctx[0].hw);
+       if (ret)
+               return ret;
+
+       ctx->parents_ctx[1].hw.init = CLK_HW_INIT_NO_PARENT("parent-1",
+                                                           &clk_dummy_rate_ops,
+                                                           0);
+       ctx->parents_ctx[1].rate = DUMMY_CLOCK_RATE_2;
+       ret = clk_hw_register(NULL, &ctx->parents_ctx[1].hw);
+       if (ret)
+               return ret;
+
+       ctx->current_parent = 0;
+       ctx->hw.init = CLK_HW_INIT_PARENTS("test-mux", parents,
+                                          &clk_multiple_parents_mux_ops,
+                                          CLK_SET_RATE_PARENT);
+       ret = clk_hw_register(NULL, &ctx->hw);
+       if (ret)
+               return ret;
+
+       return 0;
+}
+
+static void
+clk_multiple_parents_mux_test_exit(struct kunit *test)
+{
+       struct clk_multiple_parent_ctx *ctx = test->priv;
+
+       clk_hw_unregister(&ctx->hw);
+       clk_hw_unregister(&ctx->parents_ctx[0].hw);
+       clk_hw_unregister(&ctx->parents_ctx[1].hw);
+}
+
+/*
+ * Test that for a clock with multiple parents, clk_get_parent()
+ * actually returns the current one.
+ */
+static void
+clk_test_multiple_parents_mux_get_parent(struct kunit *test)
+{
+       struct clk_multiple_parent_ctx *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       struct clk *parent;
+
+       parent = clk_get_parent(clk);
+       KUNIT_EXPECT_TRUE(test, clk_is_match(parent, ctx->parents_ctx[0].hw.clk));
+}
+
+/*
+ * Test that for a clock with a multiple parents, clk_has_parent()
+ * actually reports all of them as parents.
+ */
+static void
+clk_test_multiple_parents_mux_has_parent(struct kunit *test)
+{
+       struct clk_multiple_parent_ctx *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+
+       KUNIT_EXPECT_TRUE(test, clk_has_parent(clk, ctx->parents_ctx[0].hw.clk));
+       KUNIT_EXPECT_TRUE(test, clk_has_parent(clk, ctx->parents_ctx[1].hw.clk));
+}
+
+/*
+ * Test that for a clock with a multiple parents, if we set a range on
+ * that clock and the parent is changed, its rate after the reparenting
+ * is still within the range we asked for.
+ *
+ * FIXME: clk_set_parent() only does the reparenting but doesn't
+ * reevaluate whether the new clock rate is within its boundaries or
+ * not.
+ */
+static void
+clk_test_multiple_parents_mux_set_range_set_parent_get_rate(struct kunit *test)
+{
+       struct clk_multiple_parent_ctx *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       struct clk *parent1, *parent2;
+       unsigned long rate;
+       int ret;
+
+       kunit_skip(test, "This needs to be fixed in the core.");
+
+       parent1 = clk_hw_get_clk(&ctx->parents_ctx[0].hw, NULL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, parent1);
+       KUNIT_ASSERT_TRUE(test, clk_is_match(clk_get_parent(clk), parent1));
+
+       parent2 = clk_hw_get_clk(&ctx->parents_ctx[1].hw, NULL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, parent2);
+
+       ret = clk_set_rate(parent1, DUMMY_CLOCK_RATE_1);
+       KUNIT_ASSERT_EQ(test, ret, 0);
+
+       ret = clk_set_rate(parent2, DUMMY_CLOCK_RATE_2);
+       KUNIT_ASSERT_EQ(test, ret, 0);
+
+       ret = clk_set_rate_range(clk,
+                                DUMMY_CLOCK_RATE_1 - 1000,
+                                DUMMY_CLOCK_RATE_1 + 1000);
+       KUNIT_ASSERT_EQ(test, ret, 0);
+
+       ret = clk_set_parent(clk, parent2);
+       KUNIT_ASSERT_EQ(test, ret, 0);
+
+       rate = clk_get_rate(clk);
+       KUNIT_ASSERT_GT(test, rate, 0);
+       KUNIT_EXPECT_GE(test, rate, DUMMY_CLOCK_RATE_1 - 1000);
+       KUNIT_EXPECT_LE(test, rate, DUMMY_CLOCK_RATE_1 + 1000);
+
+       clk_put(parent2);
+       clk_put(parent1);
+}
+
+static struct kunit_case clk_multiple_parents_mux_test_cases[] = {
+       KUNIT_CASE(clk_test_multiple_parents_mux_get_parent),
+       KUNIT_CASE(clk_test_multiple_parents_mux_has_parent),
+       KUNIT_CASE(clk_test_multiple_parents_mux_set_range_set_parent_get_rate),
+       {}
+};
+
+/*
+ * Test suite for a basic mux clock with two parents, with
+ * CLK_SET_RATE_PARENT on the child.
+ *
+ * These tests are supposed to exercise the consumer API and check that
+ * the state of the child and parents are sane and consistent.
+ */
+static struct kunit_suite
+clk_multiple_parents_mux_test_suite = {
+       .name = "clk-multiple-parents-mux-test",
+       .init = clk_multiple_parents_mux_test_init,
+       .exit = clk_multiple_parents_mux_test_exit,
+       .test_cases = clk_multiple_parents_mux_test_cases,
+};
+
+static int
+clk_orphan_transparent_multiple_parent_mux_test_init(struct kunit *test)
+{
+       struct clk_multiple_parent_ctx *ctx;
+       const char *parents[2] = { "missing-parent", "proper-parent"};
+       int ret;
+
+       ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+       if (!ctx)
+               return -ENOMEM;
+       test->priv = ctx;
+
+       ctx->parents_ctx[1].hw.init = CLK_HW_INIT_NO_PARENT("proper-parent",
+                                                           &clk_dummy_rate_ops,
+                                                           0);
+       ctx->parents_ctx[1].rate = DUMMY_CLOCK_INIT_RATE;
+       ret = clk_hw_register(NULL, &ctx->parents_ctx[1].hw);
+       if (ret)
+               return ret;
+
+       ctx->hw.init = CLK_HW_INIT_PARENTS("test-orphan-mux", parents,
+                                          &clk_multiple_parents_mux_ops,
+                                          CLK_SET_RATE_PARENT);
+       ret = clk_hw_register(NULL, &ctx->hw);
+       if (ret)
+               return ret;
+
+       return 0;
+}
+
+static void
+clk_orphan_transparent_multiple_parent_mux_test_exit(struct kunit *test)
+{
+       struct clk_multiple_parent_ctx *ctx = test->priv;
+
+       clk_hw_unregister(&ctx->hw);
+       clk_hw_unregister(&ctx->parents_ctx[1].hw);
+}
+
+/*
+ * Test that, for a mux whose current parent hasn't been registered yet,
+ * clk_get_parent() will return NULL.
+ */
+static void
+clk_test_orphan_transparent_multiple_parent_mux_get_parent(struct kunit *test)
+{
+       struct clk_multiple_parent_ctx *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       struct clk *parent;
+
+       parent = clk_get_parent(clk);
+       KUNIT_EXPECT_PTR_EQ(test, parent, NULL);
+}
+
+/*
+ * Test that, for a mux whose current parent hasn't been registered yet,
+ * calling clk_set_parent() to a valid parent will properly update the
+ * mux parent and its orphan status.
+ */
+static void
+clk_test_orphan_transparent_multiple_parent_mux_set_parent(struct kunit *test)
+{
+       struct clk_multiple_parent_ctx *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       struct clk *parent, *new_parent;
+       int ret;
+
+       parent = clk_hw_get_clk(&ctx->parents_ctx[1].hw, NULL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, parent);
+
+       ret = clk_set_parent(clk, parent);
+       KUNIT_ASSERT_EQ(test, ret, 0);
+
+       new_parent = clk_get_parent(clk);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, parent);
+       KUNIT_EXPECT_TRUE(test, clk_is_match(parent, new_parent));
+
+       clk_put(parent);
+}
+
+/*
+ * Test that, for a mux that started orphan but got switched to a valid
+ * parent, calling clk_drop_range() on the mux won't affect the parent
+ * rate.
+ */
+static void
+clk_test_orphan_transparent_multiple_parent_mux_set_parent_drop_range(struct kunit *test)
+{
+       struct clk_multiple_parent_ctx *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk, *parent;
+       unsigned long parent_rate, new_parent_rate;
+       int ret;
+
+       parent = clk_hw_get_clk(&ctx->parents_ctx[1].hw, NULL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, parent);
+
+       parent_rate = clk_get_rate(parent);
+       KUNIT_ASSERT_GT(test, parent_rate, 0);
+
+       ret = clk_set_parent(clk, parent);
+       KUNIT_ASSERT_EQ(test, ret, 0);
+
+       ret = clk_drop_range(clk);
+       KUNIT_ASSERT_EQ(test, ret, 0);
+
+       new_parent_rate = clk_get_rate(clk);
+       KUNIT_ASSERT_GT(test, new_parent_rate, 0);
+       KUNIT_EXPECT_EQ(test, parent_rate, new_parent_rate);
+
+       clk_put(parent);
+}
+
+/*
+ * Test that, for a mux that started orphan but got switched to a valid
+ * parent, the rate of the mux and its new parent are consistent.
+ */
+static void
+clk_test_orphan_transparent_multiple_parent_mux_set_parent_get_rate(struct kunit *test)
+{
+       struct clk_multiple_parent_ctx *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       struct clk *parent;
+       unsigned long parent_rate, rate;
+       int ret;
+
+       parent = clk_hw_get_clk(&ctx->parents_ctx[1].hw, NULL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, parent);
+
+       parent_rate = clk_get_rate(parent);
+       KUNIT_ASSERT_GT(test, parent_rate, 0);
+
+       ret = clk_set_parent(clk, parent);
+       KUNIT_ASSERT_EQ(test, ret, 0);
+
+       rate = clk_get_rate(clk);
+       KUNIT_ASSERT_GT(test, rate, 0);
+       KUNIT_EXPECT_EQ(test, parent_rate, rate);
+}
+
+/*
+ * Test that, for a mux that started orphan but got switched to a valid
+ * parent, calling clk_put() on the mux won't affect the parent rate.
+ */
+static void
+clk_test_orphan_transparent_multiple_parent_mux_set_parent_put(struct kunit *test)
+{
+       struct clk_multiple_parent_ctx *ctx = test->priv;
+       struct clk *clk, *parent;
+       unsigned long parent_rate, new_parent_rate;
+       int ret;
+
+       parent = clk_hw_get_clk(&ctx->parents_ctx[1].hw, NULL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, parent);
+
+       clk = clk_hw_get_clk(&ctx->hw, NULL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, clk);
+
+       parent_rate = clk_get_rate(parent);
+       KUNIT_ASSERT_GT(test, parent_rate, 0);
+
+       ret = clk_set_parent(clk, parent);
+       KUNIT_ASSERT_EQ(test, ret, 0);
+
+       clk_put(clk);
+
+       new_parent_rate = clk_get_rate(parent);
+       KUNIT_ASSERT_GT(test, new_parent_rate, 0);
+       KUNIT_EXPECT_EQ(test, parent_rate, new_parent_rate);
+
+       clk_put(parent);
+}
+
+/*
+ * Test that, for a mux that started orphan but got switched to a valid
+ * parent, calling clk_set_rate_range() will affect the parent state if
+ * its rate is out of range.
+ */
+static void
+clk_test_orphan_transparent_multiple_parent_mux_set_parent_set_range_modified(struct kunit *test)
+{
+       struct clk_multiple_parent_ctx *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk, *parent;
+       unsigned long rate;
+       int ret;
+
+       parent = clk_hw_get_clk(&ctx->parents_ctx[1].hw, NULL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, parent);
+
+       ret = clk_set_parent(clk, parent);
+       KUNIT_ASSERT_EQ(test, ret, 0);
+
+       ret = clk_set_rate_range(clk, DUMMY_CLOCK_RATE_1, DUMMY_CLOCK_RATE_2);
+       KUNIT_ASSERT_EQ(test, ret, 0);
+
+       rate = clk_get_rate(clk);
+       KUNIT_ASSERT_GT(test, rate, 0);
+       KUNIT_EXPECT_GE(test, rate, DUMMY_CLOCK_RATE_1);
+       KUNIT_EXPECT_LE(test, rate, DUMMY_CLOCK_RATE_2);
+
+       clk_put(parent);
+}
+
+/*
+ * Test that, for a mux that started orphan but got switched to a valid
+ * parent, calling clk_set_rate_range() won't affect the parent state if
+ * its rate is within range.
+ */
+static void
+clk_test_orphan_transparent_multiple_parent_mux_set_parent_set_range_untouched(struct kunit *test)
+{
+       struct clk_multiple_parent_ctx *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk, *parent;
+       unsigned long parent_rate, new_parent_rate;
+       int ret;
+
+       parent = clk_hw_get_clk(&ctx->parents_ctx[1].hw, NULL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, parent);
+
+       parent_rate = clk_get_rate(parent);
+       KUNIT_ASSERT_GT(test, parent_rate, 0);
+
+       ret = clk_set_parent(clk, parent);
+       KUNIT_ASSERT_EQ(test, ret, 0);
+
+       ret = clk_set_rate_range(clk,
+                                DUMMY_CLOCK_INIT_RATE - 1000,
+                                DUMMY_CLOCK_INIT_RATE + 1000);
+       KUNIT_ASSERT_EQ(test, ret, 0);
+
+       new_parent_rate = clk_get_rate(parent);
+       KUNIT_ASSERT_GT(test, new_parent_rate, 0);
+       KUNIT_EXPECT_EQ(test, parent_rate, new_parent_rate);
+
+       clk_put(parent);
+}
+
+/*
+ * Test that, for a mux whose current parent hasn't been registered yet,
+ * calling clk_set_rate_range() will succeed but won't affect its rate.
+ */
+static void
+clk_test_orphan_transparent_multiple_parent_mux_set_range_get_rate(struct kunit *test)
+{
+       struct clk_multiple_parent_ctx *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       unsigned long rate;
+       int ret;
+
+       ret = clk_set_rate_range(clk, DUMMY_CLOCK_RATE_1, DUMMY_CLOCK_RATE_2);
+       KUNIT_ASSERT_EQ(test, ret, 0);
+
+       rate = clk_get_rate(clk);
+       KUNIT_EXPECT_EQ(test, rate, 0);
+}
+
+/*
+ * Test that, for a mux whose current parent hasn't been registered yet,
+ * calling clk_set_rate_range() will succeed, and will be taken into
+ * account when rounding a rate.
+ */
+static void
+clk_test_orphan_transparent_multiple_parent_mux_set_range_round_rate(struct kunit *test)
+{
+       struct clk_multiple_parent_ctx *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       unsigned long rate;
+       int ret;
+
+       ret = clk_set_rate_range(clk, DUMMY_CLOCK_RATE_1, DUMMY_CLOCK_RATE_2);
+       KUNIT_ASSERT_EQ(test, ret, 0);
+
+       rate = clk_round_rate(clk, DUMMY_CLOCK_RATE_1 - 1000);
+       KUNIT_ASSERT_GT(test, rate, 0);
+       KUNIT_EXPECT_GE(test, rate, DUMMY_CLOCK_RATE_1);
+       KUNIT_EXPECT_LE(test, rate, DUMMY_CLOCK_RATE_2);
+}
+
+/*
+ * Test that, for a mux that started orphan, was assigned and rate and
+ * then got switched to a valid parent, its rate is eventually within
+ * range.
+ *
+ * FIXME: Even though we update the rate as part of clk_set_parent(), we
+ * don't evaluate whether that new rate is within range and needs to be
+ * adjusted.
+ */
+static void
+clk_test_orphan_transparent_multiple_parent_mux_set_range_set_parent_get_rate(struct kunit *test)
+{
+       struct clk_multiple_parent_ctx *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk, *parent;
+       unsigned long rate;
+       int ret;
+
+       kunit_skip(test, "This needs to be fixed in the core.");
+
+       parent = clk_hw_get_clk(&ctx->parents_ctx[1].hw, NULL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, parent);
+
+       ret = clk_set_rate_range(clk, DUMMY_CLOCK_RATE_1, DUMMY_CLOCK_RATE_2);
+       KUNIT_ASSERT_EQ(test, ret, 0);
+
+       ret = clk_set_parent(clk, parent);
+       KUNIT_ASSERT_EQ(test, ret, 0);
+
+       rate = clk_get_rate(clk);
+       KUNIT_ASSERT_GT(test, rate, 0);
+       KUNIT_EXPECT_GE(test, rate, DUMMY_CLOCK_RATE_1);
+       KUNIT_EXPECT_LE(test, rate, DUMMY_CLOCK_RATE_2);
+
+       clk_put(parent);
+}
+
+static struct kunit_case clk_orphan_transparent_multiple_parent_mux_test_cases[] = {
+       KUNIT_CASE(clk_test_orphan_transparent_multiple_parent_mux_get_parent),
+       KUNIT_CASE(clk_test_orphan_transparent_multiple_parent_mux_set_parent),
+       KUNIT_CASE(clk_test_orphan_transparent_multiple_parent_mux_set_parent_drop_range),
+       KUNIT_CASE(clk_test_orphan_transparent_multiple_parent_mux_set_parent_get_rate),
+       KUNIT_CASE(clk_test_orphan_transparent_multiple_parent_mux_set_parent_put),
+       KUNIT_CASE(clk_test_orphan_transparent_multiple_parent_mux_set_parent_set_range_modified),
+       KUNIT_CASE(clk_test_orphan_transparent_multiple_parent_mux_set_parent_set_range_untouched),
+       KUNIT_CASE(clk_test_orphan_transparent_multiple_parent_mux_set_range_get_rate),
+       KUNIT_CASE(clk_test_orphan_transparent_multiple_parent_mux_set_range_round_rate),
+       KUNIT_CASE(clk_test_orphan_transparent_multiple_parent_mux_set_range_set_parent_get_rate),
+       {}
+};
+
+/*
+ * Test suite for a basic mux clock with two parents. The default parent
+ * isn't registered, only the second parent is. By default, the clock
+ * will thus be orphan.
+ *
+ * These tests are supposed to exercise the behaviour of the consumer
+ * API when dealing with an orphan clock, and how we deal with the
+ * transition to a valid parent.
+ */
+static struct kunit_suite clk_orphan_transparent_multiple_parent_mux_test_suite = {
+       .name = "clk-orphan-transparent-multiple-parent-mux-test",
+       .init = clk_orphan_transparent_multiple_parent_mux_test_init,
+       .exit = clk_orphan_transparent_multiple_parent_mux_test_exit,
+       .test_cases = clk_orphan_transparent_multiple_parent_mux_test_cases,
+};
+
+struct clk_single_parent_ctx {
+       struct clk_dummy_context parent_ctx;
+       struct clk_hw hw;
+};
+
+static int clk_single_parent_mux_test_init(struct kunit *test)
+{
+       struct clk_single_parent_ctx *ctx;
+       int ret;
+
+       ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+       if (!ctx)
+               return -ENOMEM;
+       test->priv = ctx;
+
+       ctx->parent_ctx.rate = DUMMY_CLOCK_INIT_RATE;
+       ctx->parent_ctx.hw.init =
+               CLK_HW_INIT_NO_PARENT("parent-clk",
+                                     &clk_dummy_rate_ops,
+                                     0);
+
+       ret = clk_hw_register(NULL, &ctx->parent_ctx.hw);
+       if (ret)
+               return ret;
+
+       ctx->hw.init = CLK_HW_INIT("test-clk", "parent-clk",
+                                  &clk_dummy_single_parent_ops,
+                                  CLK_SET_RATE_PARENT);
+
+       ret = clk_hw_register(NULL, &ctx->hw);
+       if (ret)
+               return ret;
+
+       return 0;
+}
+
+static void
+clk_single_parent_mux_test_exit(struct kunit *test)
+{
+       struct clk_single_parent_ctx *ctx = test->priv;
+
+       clk_hw_unregister(&ctx->hw);
+       clk_hw_unregister(&ctx->parent_ctx.hw);
+}
+
+/*
+ * Test that for a clock with a single parent, clk_get_parent() actually
+ * returns the parent.
+ */
+static void
+clk_test_single_parent_mux_get_parent(struct kunit *test)
+{
+       struct clk_single_parent_ctx *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       struct clk *parent;
+
+       parent = clk_get_parent(clk);
+       KUNIT_EXPECT_TRUE(test, clk_is_match(parent, ctx->parent_ctx.hw.clk));
+}
+
+/*
+ * Test that for a clock with a single parent and CLK_SET_RATE_PARENT,
+ * if we set a range on both the child clock and its parent, with a
+ * smaller range on the child, the rate range returned by
+ * clk_get_rate_range() is aggregate of both ranges.
+ */
+static void
+clk_test_single_parent_mux_get_range_both_child_smaller(struct kunit *test)
+{
+       struct clk_single_parent_ctx *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       struct clk *parent;
+       unsigned long min, max;
+       int ret;
+
+       parent = clk_get_parent(clk);
+       KUNIT_ASSERT_PTR_NE(test, parent, NULL);
+
+       ret = clk_set_rate_range(parent, DUMMY_CLOCK_RATE_1, DUMMY_CLOCK_RATE_2);
+       KUNIT_ASSERT_EQ(test, ret, 0);
+
+       ret = clk_set_rate_range(clk,
+                                DUMMY_CLOCK_RATE_1 + 1000,
+                                DUMMY_CLOCK_RATE_2 - 1000);
+       KUNIT_ASSERT_EQ(test, ret, 0);
+
+       clk_get_rate_range(clk, &min, &max);
+       KUNIT_EXPECT_EQ(test, min, DUMMY_CLOCK_RATE_1 + 1000);
+       KUNIT_EXPECT_EQ(test, max, DUMMY_CLOCK_RATE_2 - 1000);
+}
+
+/*
+ * Test that for a clock with a single parent and CLK_SET_RATE_PARENT,
+ * if we set a range on both the child clock and its parent, with a
+ * smaller range on the parent, the rate range returned by
+ * clk_get_rate_range() is aggregate of both ranges.
+ *
+ * FIXME: clk_get_rate_range() (and clk_core_get_boundaries() in
+ * particular) doesn't take the parent range into account when the clock
+ * has CLK_SET_RATE_PARENT.
+ */
+static void
+clk_test_single_parent_mux_get_range_both_parent_smaller(struct kunit *test)
+{
+       struct clk_single_parent_ctx *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       struct clk *parent;
+       unsigned long min, max;
+       int ret;
+
+       kunit_skip(test, "This needs to be fixed in the core.");
+
+       parent = clk_get_parent(clk);
+       KUNIT_ASSERT_PTR_NE(test, parent, NULL);
+
+       ret = clk_set_rate_range(parent,
+                                DUMMY_CLOCK_RATE_1 + 1000,
+                                DUMMY_CLOCK_RATE_2 - 1000);
+       KUNIT_ASSERT_EQ(test, ret, 0);
+
+       ret = clk_set_rate_range(clk, DUMMY_CLOCK_RATE_1, DUMMY_CLOCK_RATE_2);
+       KUNIT_ASSERT_EQ(test, ret, 0);
+
+       clk_get_rate_range(clk, &min, &max);
+       KUNIT_EXPECT_EQ(test, min, DUMMY_CLOCK_RATE_1 + 1000);
+       KUNIT_EXPECT_EQ(test, min, DUMMY_CLOCK_RATE_2 - 1000);
+}
+
+/*
+ * Test that for a clock with a single parent and CLK_SET_RATE_PARENT,
+ * if we set a range on the parent clock only, the rate range returned
+ * by clk_get_rate_range() on the children clock matches the parent
+ * range.
+ *
+ * FIXME: clk_get_rate_range() (and clk_core_get_boundaries() in
+ * particular) doesn't take the parent range into account when the clock
+ * has CLK_SET_RATE_PARENT.
+ */
+static void
+clk_test_single_parent_mux_get_range_parent_only(struct kunit *test)
+{
+       struct clk_single_parent_ctx *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       struct clk *parent;
+       unsigned long min, max;
+       int ret;
+
+       kunit_skip(test, "This needs to be fixed in the core.");
+
+       parent = clk_get_parent(clk);
+       KUNIT_ASSERT_PTR_NE(test, parent, NULL);
+
+       ret = clk_set_rate_range(parent, DUMMY_CLOCK_RATE_1, DUMMY_CLOCK_RATE_2);
+       KUNIT_ASSERT_EQ(test, ret, 0);
+
+       clk_get_rate_range(clk, &min, &max);
+       KUNIT_EXPECT_EQ(test, min, DUMMY_CLOCK_RATE_1);
+       KUNIT_EXPECT_EQ(test, min, DUMMY_CLOCK_RATE_2);
+}
+
+/*
+ * Test that for a clock with a single parent, clk_has_parent() actually
+ * reports it as a parent.
+ */
+static void
+clk_test_single_parent_mux_has_parent(struct kunit *test)
+{
+       struct clk_single_parent_ctx *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       struct clk *parent = ctx->parent_ctx.hw.clk;
+
+       KUNIT_EXPECT_TRUE(test, clk_has_parent(clk, parent));
+}
+
+/*
+ * Test that for a clock that can't modify its rate and with a single
+ * parent, if we set disjoints range on the parent and then the child,
+ * the second will return an error.
+ *
+ * FIXME: clk_set_rate_range() only considers the current clock when
+ * evaluating whether ranges are disjoints and not the upstream clocks
+ * ranges.
+ */
+static void
+clk_test_single_parent_mux_set_range_disjoint_child_last(struct kunit *test)
+{
+       struct clk_single_parent_ctx *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       struct clk *parent;
+       int ret;
+
+       kunit_skip(test, "This needs to be fixed in the core.");
+
+       parent = clk_get_parent(clk);
+       KUNIT_ASSERT_PTR_NE(test, parent, NULL);
+
+       ret = clk_set_rate_range(parent, 1000, 2000);
+       KUNIT_ASSERT_EQ(test, ret, 0);
+
+       ret = clk_set_rate_range(clk, 3000, 4000);
+       KUNIT_EXPECT_LT(test, ret, 0);
+}
+
+/*
+ * Test that for a clock that can't modify its rate and with a single
+ * parent, if we set disjoints range on the child and then the parent,
+ * the second will return an error.
+ *
+ * FIXME: clk_set_rate_range() only considers the current clock when
+ * evaluating whether ranges are disjoints and not the downstream clocks
+ * ranges.
+ */
+static void
+clk_test_single_parent_mux_set_range_disjoint_parent_last(struct kunit *test)
+{
+       struct clk_single_parent_ctx *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       struct clk *parent;
+       int ret;
+
+       kunit_skip(test, "This needs to be fixed in the core.");
+
+       parent = clk_get_parent(clk);
+       KUNIT_ASSERT_PTR_NE(test, parent, NULL);
+
+       ret = clk_set_rate_range(clk, 1000, 2000);
+       KUNIT_ASSERT_EQ(test, ret, 0);
+
+       ret = clk_set_rate_range(parent, 3000, 4000);
+       KUNIT_EXPECT_LT(test, ret, 0);
+}
+
+/*
+ * Test that for a clock that can't modify its rate and with a single
+ * parent, if we set a range on the parent and then call
+ * clk_round_rate(), the boundaries of the parent are taken into
+ * account.
+ */
+static void
+clk_test_single_parent_mux_set_range_round_rate_parent_only(struct kunit *test)
+{
+       struct clk_single_parent_ctx *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       struct clk *parent;
+       unsigned long rate;
+       int ret;
+
+       parent = clk_get_parent(clk);
+       KUNIT_ASSERT_PTR_NE(test, parent, NULL);
+
+       ret = clk_set_rate_range(parent, DUMMY_CLOCK_RATE_1, DUMMY_CLOCK_RATE_2);
+       KUNIT_ASSERT_EQ(test, ret, 0);
+
+       rate = clk_round_rate(clk, DUMMY_CLOCK_RATE_1 - 1000);
+       KUNIT_ASSERT_GT(test, rate, 0);
+       KUNIT_EXPECT_GE(test, rate, DUMMY_CLOCK_RATE_1);
+       KUNIT_EXPECT_LE(test, rate, DUMMY_CLOCK_RATE_2);
+}
+
+/*
+ * Test that for a clock that can't modify its rate and with a single
+ * parent, if we set a range on the parent and a more restrictive one on
+ * the child, and then call clk_round_rate(), the boundaries of the
+ * two clocks are taken into account.
+ */
+static void
+clk_test_single_parent_mux_set_range_round_rate_child_smaller(struct kunit *test)
+{
+       struct clk_single_parent_ctx *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       struct clk *parent;
+       unsigned long rate;
+       int ret;
+
+       parent = clk_get_parent(clk);
+       KUNIT_ASSERT_PTR_NE(test, parent, NULL);
+
+       ret = clk_set_rate_range(parent, DUMMY_CLOCK_RATE_1, DUMMY_CLOCK_RATE_2);
+       KUNIT_ASSERT_EQ(test, ret, 0);
+
+       ret = clk_set_rate_range(clk, DUMMY_CLOCK_RATE_1 + 1000, DUMMY_CLOCK_RATE_2 - 1000);
+       KUNIT_ASSERT_EQ(test, ret, 0);
+
+       rate = clk_round_rate(clk, DUMMY_CLOCK_RATE_1 - 1000);
+       KUNIT_ASSERT_GT(test, rate, 0);
+       KUNIT_EXPECT_GE(test, rate, DUMMY_CLOCK_RATE_1 + 1000);
+       KUNIT_EXPECT_LE(test, rate, DUMMY_CLOCK_RATE_2 - 1000);
+
+       rate = clk_round_rate(clk, DUMMY_CLOCK_RATE_2 + 1000);
+       KUNIT_ASSERT_GT(test, rate, 0);
+       KUNIT_EXPECT_GE(test, rate, DUMMY_CLOCK_RATE_1 + 1000);
+       KUNIT_EXPECT_LE(test, rate, DUMMY_CLOCK_RATE_2 - 1000);
+}
+
+/*
+ * Test that for a clock that can't modify its rate and with a single
+ * parent, if we set a range on the child and a more restrictive one on
+ * the parent, and then call clk_round_rate(), the boundaries of the
+ * two clocks are taken into account.
+ */
+static void
+clk_test_single_parent_mux_set_range_round_rate_parent_smaller(struct kunit *test)
+{
+       struct clk_single_parent_ctx *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       struct clk *parent;
+       unsigned long rate;
+       int ret;
+
+       parent = clk_get_parent(clk);
+       KUNIT_ASSERT_PTR_NE(test, parent, NULL);
+
+       ret = clk_set_rate_range(parent, DUMMY_CLOCK_RATE_1 + 1000, DUMMY_CLOCK_RATE_2 - 1000);
+       KUNIT_ASSERT_EQ(test, ret, 0);
+
+       ret = clk_set_rate_range(clk, DUMMY_CLOCK_RATE_1, DUMMY_CLOCK_RATE_2);
+       KUNIT_ASSERT_EQ(test, ret, 0);
+
+       rate = clk_round_rate(clk, DUMMY_CLOCK_RATE_1 - 1000);
+       KUNIT_ASSERT_GT(test, rate, 0);
+       KUNIT_EXPECT_GE(test, rate, DUMMY_CLOCK_RATE_1 + 1000);
+       KUNIT_EXPECT_LE(test, rate, DUMMY_CLOCK_RATE_2 - 1000);
+
+       rate = clk_round_rate(clk, DUMMY_CLOCK_RATE_2 + 1000);
+       KUNIT_ASSERT_GT(test, rate, 0);
+       KUNIT_EXPECT_GE(test, rate, DUMMY_CLOCK_RATE_1 + 1000);
+       KUNIT_EXPECT_LE(test, rate, DUMMY_CLOCK_RATE_2 - 1000);
+}
+
+static struct kunit_case clk_single_parent_mux_test_cases[] = {
+       KUNIT_CASE(clk_test_single_parent_mux_get_parent),
+       KUNIT_CASE(clk_test_single_parent_mux_get_range_both_child_smaller),
+       KUNIT_CASE(clk_test_single_parent_mux_get_range_both_parent_smaller),
+       KUNIT_CASE(clk_test_single_parent_mux_get_range_parent_only),
+       KUNIT_CASE(clk_test_single_parent_mux_has_parent),
+       KUNIT_CASE(clk_test_single_parent_mux_set_range_disjoint_child_last),
+       KUNIT_CASE(clk_test_single_parent_mux_set_range_disjoint_parent_last),
+       KUNIT_CASE(clk_test_single_parent_mux_set_range_round_rate_child_smaller),
+       KUNIT_CASE(clk_test_single_parent_mux_set_range_round_rate_parent_only),
+       KUNIT_CASE(clk_test_single_parent_mux_set_range_round_rate_parent_smaller),
+       {}
+};
+
+/*
+ * Test suite for a basic mux clock with one parent, with
+ * CLK_SET_RATE_PARENT on the child.
+ *
+ * These tests are supposed to exercise the consumer API and check that
+ * the state of the child and parent are sane and consistent.
+ */
+static struct kunit_suite
+clk_single_parent_mux_test_suite = {
+       .name = "clk-single-parent-mux-test",
+       .init = clk_single_parent_mux_test_init,
+       .exit = clk_single_parent_mux_test_exit,
+       .test_cases = clk_single_parent_mux_test_cases,
+};
+
+static int clk_orphan_transparent_single_parent_mux_test_init(struct kunit *test)
+{
+       struct clk_single_parent_ctx *ctx;
+       struct clk_init_data init = { };
+       const char *parents[] = { "orphan_parent" };
+       int ret;
+
+       ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+       if (!ctx)
+               return -ENOMEM;
+       test->priv = ctx;
+
+       init.name = "test_orphan_dummy_parent";
+       init.ops = &clk_dummy_single_parent_ops;
+       init.parent_names = parents;
+       init.num_parents = ARRAY_SIZE(parents);
+       init.flags = CLK_SET_RATE_PARENT;
+       ctx->hw.init = &init;
+
+       ret = clk_hw_register(NULL, &ctx->hw);
+       if (ret)
+               return ret;
+
+       memset(&init, 0, sizeof(init));
+       init.name = "orphan_parent";
+       init.ops = &clk_dummy_rate_ops;
+       ctx->parent_ctx.hw.init = &init;
+       ctx->parent_ctx.rate = DUMMY_CLOCK_INIT_RATE;
+
+       ret = clk_hw_register(NULL, &ctx->parent_ctx.hw);
+       if (ret)
+               return ret;
+
+       return 0;
+}
+
+/*
+ * Test that a mux-only clock, with an initial rate within a range,
+ * will still have the same rate after the range has been enforced.
+ *
+ * See:
+ * https://lore.kernel.org/linux-clk/7720158d-10a7-a17b-73a4-a8615c9c6d5c@collabora.com/
+ */
+static void clk_test_orphan_transparent_parent_mux_set_range(struct kunit *test)
+{
+       struct clk_single_parent_ctx *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       unsigned long rate, new_rate;
+
+       rate = clk_get_rate(clk);
+       KUNIT_ASSERT_GT(test, rate, 0);
+
+       KUNIT_ASSERT_EQ(test,
+                       clk_set_rate_range(clk,
+                                          ctx->parent_ctx.rate - 1000,
+                                          ctx->parent_ctx.rate + 1000),
+                       0);
+
+       new_rate = clk_get_rate(clk);
+       KUNIT_ASSERT_GT(test, new_rate, 0);
+       KUNIT_EXPECT_EQ(test, rate, new_rate);
+}
+
+static struct kunit_case clk_orphan_transparent_single_parent_mux_test_cases[] = {
+       KUNIT_CASE(clk_test_orphan_transparent_parent_mux_set_range),
+       {}
+};
+
+/*
+ * Test suite for a basic mux clock with one parent. The parent is
+ * registered after its child. The clock will thus be orphan when
+ * registered, but will no longer be when the tests run.
+ *
+ * These tests are supposed to make sure a clock that used to be orphan
+ * has a sane, consistent, behaviour.
+ */
+static struct kunit_suite clk_orphan_transparent_single_parent_test_suite = {
+       .name = "clk-orphan-transparent-single-parent-test",
+       .init = clk_orphan_transparent_single_parent_mux_test_init,
+       .exit = clk_single_parent_mux_test_exit,
+       .test_cases = clk_orphan_transparent_single_parent_mux_test_cases,
+};
+
+struct clk_single_parent_two_lvl_ctx {
+       struct clk_dummy_context parent_parent_ctx;
+       struct clk_dummy_context parent_ctx;
+       struct clk_hw hw;
+};
+
+static int
+clk_orphan_two_level_root_last_test_init(struct kunit *test)
+{
+       struct clk_single_parent_two_lvl_ctx *ctx;
+       int ret;
+
+       ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+       if (!ctx)
+               return -ENOMEM;
+       test->priv = ctx;
+
+       ctx->parent_ctx.hw.init =
+               CLK_HW_INIT("intermediate-parent",
+                           "root-parent",
+                           &clk_dummy_single_parent_ops,
+                           CLK_SET_RATE_PARENT);
+       ret = clk_hw_register(NULL, &ctx->parent_ctx.hw);
+       if (ret)
+               return ret;
+
+       ctx->hw.init =
+               CLK_HW_INIT("test-clk", "intermediate-parent",
+                           &clk_dummy_single_parent_ops,
+                           CLK_SET_RATE_PARENT);
+       ret = clk_hw_register(NULL, &ctx->hw);
+       if (ret)
+               return ret;
+
+       ctx->parent_parent_ctx.rate = DUMMY_CLOCK_INIT_RATE;
+       ctx->parent_parent_ctx.hw.init =
+               CLK_HW_INIT_NO_PARENT("root-parent",
+                                     &clk_dummy_rate_ops,
+                                     0);
+       ret = clk_hw_register(NULL, &ctx->parent_parent_ctx.hw);
+       if (ret)
+               return ret;
+
+       return 0;
+}
+
+static void
+clk_orphan_two_level_root_last_test_exit(struct kunit *test)
+{
+       struct clk_single_parent_two_lvl_ctx *ctx = test->priv;
+
+       clk_hw_unregister(&ctx->hw);
+       clk_hw_unregister(&ctx->parent_ctx.hw);
+       clk_hw_unregister(&ctx->parent_parent_ctx.hw);
+}
+
+/*
+ * Test that, for a clock whose parent used to be orphan, clk_get_rate()
+ * will return the proper rate.
+ */
+static void
+clk_orphan_two_level_root_last_test_get_rate(struct kunit *test)
+{
+       struct clk_single_parent_two_lvl_ctx *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       unsigned long rate;
+
+       rate = clk_get_rate(clk);
+       KUNIT_EXPECT_EQ(test, rate, DUMMY_CLOCK_INIT_RATE);
+}
+
+/*
+ * Test that, for a clock whose parent used to be orphan,
+ * clk_set_rate_range() won't affect its rate if it is already within
+ * range.
+ *
+ * See (for Exynos 4210):
+ * https://lore.kernel.org/linux-clk/366a0232-bb4a-c357-6aa8-636e398e05eb@samsung.com/
+ */
+static void
+clk_orphan_two_level_root_last_test_set_range(struct kunit *test)
+{
+       struct clk_single_parent_two_lvl_ctx *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       unsigned long rate;
+       int ret;
+
+       ret = clk_set_rate_range(clk,
+                                DUMMY_CLOCK_INIT_RATE - 1000,
+                                DUMMY_CLOCK_INIT_RATE + 1000);
+       KUNIT_ASSERT_EQ(test, ret, 0);
+
+       rate = clk_get_rate(clk);
+       KUNIT_ASSERT_GT(test, rate, 0);
+       KUNIT_EXPECT_EQ(test, rate, DUMMY_CLOCK_INIT_RATE);
+}
+
+static struct kunit_case
+clk_orphan_two_level_root_last_test_cases[] = {
+       KUNIT_CASE(clk_orphan_two_level_root_last_test_get_rate),
+       KUNIT_CASE(clk_orphan_two_level_root_last_test_set_range),
+       {}
+};
+
+/*
+ * Test suite for a basic, transparent, clock with a parent that is also
+ * such a clock. The parent's parent is registered last, while the
+ * parent and its child are registered in that order. The intermediate
+ * and leaf clocks will thus be orphan when registered, but the leaf
+ * clock itself will always have its parent and will never be
+ * reparented. Indeed, it's only orphan because its parent is.
+ *
+ * These tests are supposed to exercise the behaviour of the consumer
+ * API when dealing with an orphan clock, and how we deal with the
+ * transition to a valid parent.
+ */
+static struct kunit_suite
+clk_orphan_two_level_root_last_test_suite = {
+       .name = "clk-orphan-two-level-root-last-test",
+       .init = clk_orphan_two_level_root_last_test_init,
+       .exit = clk_orphan_two_level_root_last_test_exit,
+       .test_cases = clk_orphan_two_level_root_last_test_cases,
+};
+
+/*
+ * Test that clk_set_rate_range() and clk_get_rate_range() are
+ * consistent on a simple clock without any parent.
+ */
+static void clk_range_test_get_range(struct kunit *test)
+{
+       struct clk_dummy_context *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       unsigned long min, max;
+       int ret;
+
+       ret = clk_set_rate_range(clk, DUMMY_CLOCK_RATE_1, DUMMY_CLOCK_RATE_2);
+       KUNIT_ASSERT_EQ(test, ret, 0);
+
+       clk_get_rate_range(clk, &min, &max);
+       KUNIT_EXPECT_EQ(test, min, DUMMY_CLOCK_RATE_1);
+       KUNIT_EXPECT_EQ(test, max, DUMMY_CLOCK_RATE_2);
+}
+
+/*
+ * Test that, on a simple clock without any parent, if a rate range is
+ * set on a clk, it's properly reported by clk_get_rate_range() on all
+ * the clk structure of that clock.
+ */
+static void clk_range_test_get_range_multiple_clk(struct kunit *test)
+{
+       struct clk_dummy_context *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *user1, *user2;
+       unsigned long min, max;
+       int ret;
+
+       user1 = clk_hw_get_clk(hw, NULL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, user1);
+
+       user2 = clk_hw_get_clk(hw, NULL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, user2);
+
+       ret = clk_set_rate_range(user1,
+                                DUMMY_CLOCK_RATE_1,
+                                DUMMY_CLOCK_RATE_2);
+       KUNIT_ASSERT_EQ(test, ret, 0);
+
+       clk_get_rate_range(user2, &min, &max);
+       KUNIT_EXPECT_EQ(test, min, DUMMY_CLOCK_RATE_1);
+       KUNIT_EXPECT_EQ(test, max, DUMMY_CLOCK_RATE_2);
+}
+
+/*
+ * Test that, on a simple clock without any parent, if a rate range is
+ * set on struct clk_hw, it's properly reported by clk_get_rate_range().
+ */
+static void clk_range_test_get_range_with_hw(struct kunit *test)
+{
+       struct clk_dummy_context *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       unsigned long min, max;
+       int ret;
+
+       clk_hw_set_rate_range(hw, DUMMY_CLOCK_RATE_1, DUMMY_CLOCK_RATE_2);
+
+       ret = clk_set_rate_range(clk,
+                                DUMMY_CLOCK_RATE_1 + 1000,
+                                DUMMY_CLOCK_RATE_2 - 1000);
+       KUNIT_ASSERT_EQ(test, ret, 0);
+
+       clk_get_rate_range(clk, &min, &max);
+       KUNIT_EXPECT_EQ(test, min, DUMMY_CLOCK_RATE_1 + 1000);
+       KUNIT_EXPECT_EQ(test, max, DUMMY_CLOCK_RATE_2 - 1000);
+}
+
+/*
+ * Test that clk_set_rate_range won't return an error for a valid range
+ * and that it will make sure the rate of the clock is within the
+ * boundaries.
+ */
+static void clk_range_test_set_range(struct kunit *test)
+{
+       struct clk_dummy_context *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       unsigned long rate;
+
+       KUNIT_ASSERT_EQ(test,
+                       clk_set_rate_range(clk,
+                                          DUMMY_CLOCK_RATE_1,
+                                          DUMMY_CLOCK_RATE_2),
+                       0);
+
+       rate = clk_get_rate(clk);
+       KUNIT_ASSERT_GT(test, rate, 0);
+       KUNIT_EXPECT_GE(test, rate, DUMMY_CLOCK_RATE_1);
+       KUNIT_EXPECT_LE(test, rate, DUMMY_CLOCK_RATE_2);
+}
+
+/*
+ * Test that calling clk_set_rate_range with a minimum rate higher than
+ * the maximum rate returns an error.
+ */
+static void clk_range_test_set_range_invalid(struct kunit *test)
+{
+       struct clk_dummy_context *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+
+       KUNIT_EXPECT_LT(test,
+                       clk_set_rate_range(clk,
+                                          DUMMY_CLOCK_RATE_1 + 1000,
+                                          DUMMY_CLOCK_RATE_1),
+                       0);
+}
+
+/*
+ * Test that users can't set multiple, disjoints, range that would be
+ * impossible to meet.
+ */
+static void clk_range_test_multiple_disjoints_range(struct kunit *test)
+{
+       struct clk_dummy_context *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *user1, *user2;
+
+       user1 = clk_hw_get_clk(hw, NULL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, user1);
+
+       user2 = clk_hw_get_clk(hw, NULL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, user2);
+
+       KUNIT_ASSERT_EQ(test,
+                       clk_set_rate_range(user1, 1000, 2000),
+                       0);
+
+       KUNIT_EXPECT_LT(test,
+                       clk_set_rate_range(user2, 3000, 4000),
+                       0);
+
+       clk_put(user2);
+       clk_put(user1);
+}
+
+/*
+ * Test that if our clock has some boundaries and we try to round a rate
+ * lower than the minimum, the returned rate will be within range.
+ */
+static void clk_range_test_set_range_round_rate_lower(struct kunit *test)
+{
+       struct clk_dummy_context *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       long rate;
+
+       KUNIT_ASSERT_EQ(test,
+                       clk_set_rate_range(clk,
+                                          DUMMY_CLOCK_RATE_1,
+                                          DUMMY_CLOCK_RATE_2),
+                       0);
+
+       rate = clk_round_rate(clk, DUMMY_CLOCK_RATE_1 - 1000);
+       KUNIT_ASSERT_GT(test, rate, 0);
+       KUNIT_EXPECT_GE(test, rate, DUMMY_CLOCK_RATE_1);
+       KUNIT_EXPECT_LE(test, rate, DUMMY_CLOCK_RATE_2);
+}
+
+/*
+ * Test that if our clock has some boundaries and we try to set a rate
+ * higher than the maximum, the new rate will be within range.
+ */
+static void clk_range_test_set_range_set_rate_lower(struct kunit *test)
+{
+       struct clk_dummy_context *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       unsigned long rate;
+
+       KUNIT_ASSERT_EQ(test,
+                       clk_set_rate_range(clk,
+                                          DUMMY_CLOCK_RATE_1,
+                                          DUMMY_CLOCK_RATE_2),
+                       0);
+
+       KUNIT_ASSERT_EQ(test,
+                       clk_set_rate(clk, DUMMY_CLOCK_RATE_1 - 1000),
+                       0);
+
+       rate = clk_get_rate(clk);
+       KUNIT_ASSERT_GT(test, rate, 0);
+       KUNIT_EXPECT_GE(test, rate, DUMMY_CLOCK_RATE_1);
+       KUNIT_EXPECT_LE(test, rate, DUMMY_CLOCK_RATE_2);
+}
+
+/*
+ * Test that if our clock has some boundaries and we try to round and
+ * set a rate lower than the minimum, the rate returned by
+ * clk_round_rate() will be consistent with the new rate set by
+ * clk_set_rate().
+ */
+static void clk_range_test_set_range_set_round_rate_consistent_lower(struct kunit *test)
+{
+       struct clk_dummy_context *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       long rounded;
+
+       KUNIT_ASSERT_EQ(test,
+                       clk_set_rate_range(clk,
+                                          DUMMY_CLOCK_RATE_1,
+                                          DUMMY_CLOCK_RATE_2),
+                       0);
+
+       rounded = clk_round_rate(clk, DUMMY_CLOCK_RATE_1 - 1000);
+       KUNIT_ASSERT_GT(test, rounded, 0);
+
+       KUNIT_ASSERT_EQ(test,
+                       clk_set_rate(clk, DUMMY_CLOCK_RATE_1 - 1000),
+                       0);
+
+       KUNIT_EXPECT_EQ(test, rounded, clk_get_rate(clk));
+}
+
+/*
+ * Test that if our clock has some boundaries and we try to round a rate
+ * higher than the maximum, the returned rate will be within range.
+ */
+static void clk_range_test_set_range_round_rate_higher(struct kunit *test)
+{
+       struct clk_dummy_context *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       long rate;
+
+       KUNIT_ASSERT_EQ(test,
+                       clk_set_rate_range(clk,
+                                          DUMMY_CLOCK_RATE_1,
+                                          DUMMY_CLOCK_RATE_2),
+                       0);
+
+       rate = clk_round_rate(clk, DUMMY_CLOCK_RATE_2 + 1000);
+       KUNIT_ASSERT_GT(test, rate, 0);
+       KUNIT_EXPECT_GE(test, rate, DUMMY_CLOCK_RATE_1);
+       KUNIT_EXPECT_LE(test, rate, DUMMY_CLOCK_RATE_2);
+}
+
+/*
+ * Test that if our clock has some boundaries and we try to set a rate
+ * higher than the maximum, the new rate will be within range.
+ */
+static void clk_range_test_set_range_set_rate_higher(struct kunit *test)
+{
+       struct clk_dummy_context *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       unsigned long rate;
+
+       KUNIT_ASSERT_EQ(test,
+                       clk_set_rate_range(clk,
+                                          DUMMY_CLOCK_RATE_1,
+                                          DUMMY_CLOCK_RATE_2),
+                       0);
+
+       KUNIT_ASSERT_EQ(test,
+                       clk_set_rate(clk, DUMMY_CLOCK_RATE_2 + 1000),
+                       0);
+
+       rate = clk_get_rate(clk);
+       KUNIT_ASSERT_GT(test, rate, 0);
+       KUNIT_EXPECT_GE(test, rate, DUMMY_CLOCK_RATE_1);
+       KUNIT_EXPECT_LE(test, rate, DUMMY_CLOCK_RATE_2);
+}
+
+/*
+ * Test that if our clock has some boundaries and we try to round and
+ * set a rate higher than the maximum, the rate returned by
+ * clk_round_rate() will be consistent with the new rate set by
+ * clk_set_rate().
+ */
+static void clk_range_test_set_range_set_round_rate_consistent_higher(struct kunit *test)
+{
+       struct clk_dummy_context *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       long rounded;
+
+       KUNIT_ASSERT_EQ(test,
+                       clk_set_rate_range(clk,
+                                          DUMMY_CLOCK_RATE_1,
+                                          DUMMY_CLOCK_RATE_2),
+                       0);
+
+       rounded = clk_round_rate(clk, DUMMY_CLOCK_RATE_2 + 1000);
+       KUNIT_ASSERT_GT(test, rounded, 0);
+
+       KUNIT_ASSERT_EQ(test,
+                       clk_set_rate(clk, DUMMY_CLOCK_RATE_2 + 1000),
+                       0);
+
+       KUNIT_EXPECT_EQ(test, rounded, clk_get_rate(clk));
+}
+
+/*
+ * Test that if our clock has a rate lower than the minimum set by a
+ * call to clk_set_rate_range(), the rate will be raised to match the
+ * new minimum.
+ *
+ * This assumes that clk_ops.determine_rate or clk_ops.round_rate won't
+ * modify the requested rate, which is our case in clk_dummy_rate_ops.
+ */
+static void clk_range_test_set_range_get_rate_raised(struct kunit *test)
+{
+       struct clk_dummy_context *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       unsigned long rate;
+
+       KUNIT_ASSERT_EQ(test,
+                       clk_set_rate(clk, DUMMY_CLOCK_RATE_1 - 1000),
+                       0);
+
+       KUNIT_ASSERT_EQ(test,
+                       clk_set_rate_range(clk,
+                                          DUMMY_CLOCK_RATE_1,
+                                          DUMMY_CLOCK_RATE_2),
+                       0);
+
+       rate = clk_get_rate(clk);
+       KUNIT_ASSERT_GT(test, rate, 0);
+       KUNIT_EXPECT_EQ(test, rate, DUMMY_CLOCK_RATE_1);
+}
+
+/*
+ * Test that if our clock has a rate higher than the maximum set by a
+ * call to clk_set_rate_range(), the rate will be lowered to match the
+ * new maximum.
+ *
+ * This assumes that clk_ops.determine_rate or clk_ops.round_rate won't
+ * modify the requested rate, which is our case in clk_dummy_rate_ops.
+ */
+static void clk_range_test_set_range_get_rate_lowered(struct kunit *test)
+{
+       struct clk_dummy_context *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       unsigned long rate;
+
+       KUNIT_ASSERT_EQ(test,
+                       clk_set_rate(clk, DUMMY_CLOCK_RATE_2 + 1000),
+                       0);
+
+       KUNIT_ASSERT_EQ(test,
+                       clk_set_rate_range(clk,
+                                          DUMMY_CLOCK_RATE_1,
+                                          DUMMY_CLOCK_RATE_2),
+                       0);
+
+       rate = clk_get_rate(clk);
+       KUNIT_ASSERT_GT(test, rate, 0);
+       KUNIT_EXPECT_EQ(test, rate, DUMMY_CLOCK_RATE_2);
+}
+
+static struct kunit_case clk_range_test_cases[] = {
+       KUNIT_CASE(clk_range_test_get_range),
+       KUNIT_CASE(clk_range_test_get_range_with_hw),
+       KUNIT_CASE(clk_range_test_get_range_multiple_clk),
+       KUNIT_CASE(clk_range_test_set_range),
+       KUNIT_CASE(clk_range_test_set_range_invalid),
+       KUNIT_CASE(clk_range_test_multiple_disjoints_range),
+       KUNIT_CASE(clk_range_test_set_range_round_rate_lower),
+       KUNIT_CASE(clk_range_test_set_range_set_rate_lower),
+       KUNIT_CASE(clk_range_test_set_range_set_round_rate_consistent_lower),
+       KUNIT_CASE(clk_range_test_set_range_round_rate_higher),
+       KUNIT_CASE(clk_range_test_set_range_set_rate_higher),
+       KUNIT_CASE(clk_range_test_set_range_set_round_rate_consistent_higher),
+       KUNIT_CASE(clk_range_test_set_range_get_rate_raised),
+       KUNIT_CASE(clk_range_test_set_range_get_rate_lowered),
+       {}
+};
+
+/*
+ * Test suite for a basic rate clock, without any parent.
+ *
+ * These tests are supposed to exercise the rate range API
+ * (clk_set_rate_range, clk_set_min_rate, clk_set_max_rate,
+ * clk_drop_range).
+ */
+static struct kunit_suite clk_range_test_suite = {
+       .name = "clk-range-test",
+       .init = clk_test_init,
+       .exit = clk_test_exit,
+       .test_cases = clk_range_test_cases,
+};
+
+/*
+ * Test that if we have several subsequent calls to
+ * clk_set_rate_range(), the core will reevaluate whether a new rate is
+ * needed each and every time.
+ *
+ * With clk_dummy_maximize_rate_ops, this means that the the rate will
+ * trail along the maximum as it evolves.
+ */
+static void clk_range_test_set_range_rate_maximized(struct kunit *test)
+{
+       struct clk_dummy_context *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       unsigned long rate;
+
+       KUNIT_ASSERT_EQ(test,
+                       clk_set_rate(clk, DUMMY_CLOCK_RATE_2 + 1000),
+                       0);
+
+       KUNIT_ASSERT_EQ(test,
+                       clk_set_rate_range(clk,
+                                          DUMMY_CLOCK_RATE_1,
+                                          DUMMY_CLOCK_RATE_2),
+                       0);
+
+       rate = clk_get_rate(clk);
+       KUNIT_ASSERT_GT(test, rate, 0);
+       KUNIT_EXPECT_EQ(test, rate, DUMMY_CLOCK_RATE_2);
+
+       KUNIT_ASSERT_EQ(test,
+                       clk_set_rate_range(clk,
+                                          DUMMY_CLOCK_RATE_1,
+                                          DUMMY_CLOCK_RATE_2 - 1000),
+                       0);
+
+       rate = clk_get_rate(clk);
+       KUNIT_ASSERT_GT(test, rate, 0);
+       KUNIT_EXPECT_EQ(test, rate, DUMMY_CLOCK_RATE_2 - 1000);
+
+       KUNIT_ASSERT_EQ(test,
+                       clk_set_rate_range(clk,
+                                          DUMMY_CLOCK_RATE_1,
+                                          DUMMY_CLOCK_RATE_2),
+                       0);
+
+       rate = clk_get_rate(clk);
+       KUNIT_ASSERT_GT(test, rate, 0);
+       KUNIT_EXPECT_EQ(test, rate, DUMMY_CLOCK_RATE_2);
+}
+
+/*
+ * Test that if we have several subsequent calls to
+ * clk_set_rate_range(), across multiple users, the core will reevaluate
+ * whether a new rate is needed each and every time.
+ *
+ * With clk_dummy_maximize_rate_ops, this means that the the rate will
+ * trail along the maximum as it evolves.
+ */
+static void clk_range_test_multiple_set_range_rate_maximized(struct kunit *test)
+{
+       struct clk_dummy_context *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       struct clk *user1, *user2;
+       unsigned long rate;
+
+       user1 = clk_hw_get_clk(hw, NULL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, user1);
+
+       user2 = clk_hw_get_clk(hw, NULL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, user2);
+
+       KUNIT_ASSERT_EQ(test,
+                       clk_set_rate(clk, DUMMY_CLOCK_RATE_2 + 1000),
+                       0);
+
+       KUNIT_ASSERT_EQ(test,
+                       clk_set_rate_range(user1,
+                                          0,
+                                          DUMMY_CLOCK_RATE_2),
+                       0);
+
+       rate = clk_get_rate(clk);
+       KUNIT_ASSERT_GT(test, rate, 0);
+       KUNIT_EXPECT_EQ(test, rate, DUMMY_CLOCK_RATE_2);
+
+       KUNIT_ASSERT_EQ(test,
+                       clk_set_rate_range(user2,
+                                          0,
+                                          DUMMY_CLOCK_RATE_1),
+                       0);
+
+       rate = clk_get_rate(clk);
+       KUNIT_ASSERT_GT(test, rate, 0);
+       KUNIT_EXPECT_EQ(test, rate, DUMMY_CLOCK_RATE_1);
+
+       KUNIT_ASSERT_EQ(test,
+                       clk_drop_range(user2),
+                       0);
+
+       rate = clk_get_rate(clk);
+       KUNIT_ASSERT_GT(test, rate, 0);
+       KUNIT_EXPECT_EQ(test, rate, DUMMY_CLOCK_RATE_2);
+
+       clk_put(user2);
+       clk_put(user1);
+}
+
+/*
+ * Test that if we have several subsequent calls to
+ * clk_set_rate_range(), across multiple users, the core will reevaluate
+ * whether a new rate is needed, including when a user drop its clock.
+ *
+ * With clk_dummy_maximize_rate_ops, this means that the rate will
+ * trail along the maximum as it evolves.
+ */
+static void clk_range_test_multiple_set_range_rate_put_maximized(struct kunit *test)
+{
+       struct clk_dummy_context *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       struct clk *user1, *user2;
+       unsigned long rate;
+
+       user1 = clk_hw_get_clk(hw, NULL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, user1);
+
+       user2 = clk_hw_get_clk(hw, NULL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, user2);
+
+       KUNIT_ASSERT_EQ(test,
+                       clk_set_rate(clk, DUMMY_CLOCK_RATE_2 + 1000),
+                       0);
+
+       KUNIT_ASSERT_EQ(test,
+                       clk_set_rate_range(user1,
+                                          0,
+                                          DUMMY_CLOCK_RATE_2),
+                       0);
+
+       rate = clk_get_rate(clk);
+       KUNIT_ASSERT_GT(test, rate, 0);
+       KUNIT_EXPECT_EQ(test, rate, DUMMY_CLOCK_RATE_2);
+
+       KUNIT_ASSERT_EQ(test,
+                       clk_set_rate_range(user2,
+                                          0,
+                                          DUMMY_CLOCK_RATE_1),
+                       0);
+
+       rate = clk_get_rate(clk);
+       KUNIT_ASSERT_GT(test, rate, 0);
+       KUNIT_EXPECT_EQ(test, rate, DUMMY_CLOCK_RATE_1);
+
+       clk_put(user2);
+
+       rate = clk_get_rate(clk);
+       KUNIT_ASSERT_GT(test, rate, 0);
+       KUNIT_EXPECT_EQ(test, rate, DUMMY_CLOCK_RATE_2);
+
+       clk_put(user1);
+}
+
+static struct kunit_case clk_range_maximize_test_cases[] = {
+       KUNIT_CASE(clk_range_test_set_range_rate_maximized),
+       KUNIT_CASE(clk_range_test_multiple_set_range_rate_maximized),
+       KUNIT_CASE(clk_range_test_multiple_set_range_rate_put_maximized),
+       {}
+};
+
+/*
+ * Test suite for a basic rate clock, without any parent.
+ *
+ * These tests are supposed to exercise the rate range API
+ * (clk_set_rate_range, clk_set_min_rate, clk_set_max_rate,
+ * clk_drop_range), with a driver that will always try to run at the
+ * highest possible rate.
+ */
+static struct kunit_suite clk_range_maximize_test_suite = {
+       .name = "clk-range-maximize-test",
+       .init = clk_maximize_test_init,
+       .exit = clk_test_exit,
+       .test_cases = clk_range_maximize_test_cases,
+};
+
+/*
+ * Test that if we have several subsequent calls to
+ * clk_set_rate_range(), the core will reevaluate whether a new rate is
+ * needed each and every time.
+ *
+ * With clk_dummy_minimize_rate_ops, this means that the the rate will
+ * trail along the minimum as it evolves.
+ */
+static void clk_range_test_set_range_rate_minimized(struct kunit *test)
+{
+       struct clk_dummy_context *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       unsigned long rate;
+
+       KUNIT_ASSERT_EQ(test,
+                       clk_set_rate(clk, DUMMY_CLOCK_RATE_1 - 1000),
+                       0);
+
+       KUNIT_ASSERT_EQ(test,
+                       clk_set_rate_range(clk,
+                                          DUMMY_CLOCK_RATE_1,
+                                          DUMMY_CLOCK_RATE_2),
+                       0);
+
+       rate = clk_get_rate(clk);
+       KUNIT_ASSERT_GT(test, rate, 0);
+       KUNIT_EXPECT_EQ(test, rate, DUMMY_CLOCK_RATE_1);
+
+       KUNIT_ASSERT_EQ(test,
+                       clk_set_rate_range(clk,
+                                          DUMMY_CLOCK_RATE_1 + 1000,
+                                          DUMMY_CLOCK_RATE_2),
+                       0);
+
+       rate = clk_get_rate(clk);
+       KUNIT_ASSERT_GT(test, rate, 0);
+       KUNIT_EXPECT_EQ(test, rate, DUMMY_CLOCK_RATE_1 + 1000);
+
+       KUNIT_ASSERT_EQ(test,
+                       clk_set_rate_range(clk,
+                                          DUMMY_CLOCK_RATE_1,
+                                          DUMMY_CLOCK_RATE_2),
+                       0);
+
+       rate = clk_get_rate(clk);
+       KUNIT_ASSERT_GT(test, rate, 0);
+       KUNIT_EXPECT_EQ(test, rate, DUMMY_CLOCK_RATE_1);
+}
+
+/*
+ * Test that if we have several subsequent calls to
+ * clk_set_rate_range(), across multiple users, the core will reevaluate
+ * whether a new rate is needed each and every time.
+ *
+ * With clk_dummy_minimize_rate_ops, this means that the the rate will
+ * trail along the minimum as it evolves.
+ */
+static void clk_range_test_multiple_set_range_rate_minimized(struct kunit *test)
+{
+       struct clk_dummy_context *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       struct clk *user1, *user2;
+       unsigned long rate;
+
+       user1 = clk_hw_get_clk(hw, NULL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, user1);
+
+       user2 = clk_hw_get_clk(hw, NULL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, user2);
+
+       KUNIT_ASSERT_EQ(test,
+                       clk_set_rate_range(user1,
+                                          DUMMY_CLOCK_RATE_1,
+                                          ULONG_MAX),
+                       0);
+
+       rate = clk_get_rate(clk);
+       KUNIT_ASSERT_GT(test, rate, 0);
+       KUNIT_EXPECT_EQ(test, rate, DUMMY_CLOCK_RATE_1);
+
+       KUNIT_ASSERT_EQ(test,
+                       clk_set_rate_range(user2,
+                                          DUMMY_CLOCK_RATE_2,
+                                          ULONG_MAX),
+                       0);
+
+       rate = clk_get_rate(clk);
+       KUNIT_ASSERT_GT(test, rate, 0);
+       KUNIT_EXPECT_EQ(test, rate, DUMMY_CLOCK_RATE_2);
+
+       KUNIT_ASSERT_EQ(test,
+                       clk_drop_range(user2),
+                       0);
+
+       rate = clk_get_rate(clk);
+       KUNIT_ASSERT_GT(test, rate, 0);
+       KUNIT_EXPECT_EQ(test, rate, DUMMY_CLOCK_RATE_1);
+
+       clk_put(user2);
+       clk_put(user1);
+}
+
+/*
+ * Test that if we have several subsequent calls to
+ * clk_set_rate_range(), across multiple users, the core will reevaluate
+ * whether a new rate is needed, including when a user drop its clock.
+ *
+ * With clk_dummy_minimize_rate_ops, this means that the rate will
+ * trail along the minimum as it evolves.
+ */
+static void clk_range_test_multiple_set_range_rate_put_minimized(struct kunit *test)
+{
+       struct clk_dummy_context *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       struct clk *user1, *user2;
+       unsigned long rate;
+
+       user1 = clk_hw_get_clk(hw, NULL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, user1);
+
+       user2 = clk_hw_get_clk(hw, NULL);
+       KUNIT_ASSERT_NOT_ERR_OR_NULL(test, user2);
+
+       KUNIT_ASSERT_EQ(test,
+                       clk_set_rate_range(user1,
+                                          DUMMY_CLOCK_RATE_1,
+                                          ULONG_MAX),
+                       0);
+
+       rate = clk_get_rate(clk);
+       KUNIT_ASSERT_GT(test, rate, 0);
+       KUNIT_EXPECT_EQ(test, rate, DUMMY_CLOCK_RATE_1);
+
+       KUNIT_ASSERT_EQ(test,
+                       clk_set_rate_range(user2,
+                                          DUMMY_CLOCK_RATE_2,
+                                          ULONG_MAX),
+                       0);
+
+       rate = clk_get_rate(clk);
+       KUNIT_ASSERT_GT(test, rate, 0);
+       KUNIT_EXPECT_EQ(test, rate, DUMMY_CLOCK_RATE_2);
+
+       clk_put(user2);
+
+       rate = clk_get_rate(clk);
+       KUNIT_ASSERT_GT(test, rate, 0);
+       KUNIT_EXPECT_EQ(test, rate, DUMMY_CLOCK_RATE_1);
+
+       clk_put(user1);
+}
+
+static struct kunit_case clk_range_minimize_test_cases[] = {
+       KUNIT_CASE(clk_range_test_set_range_rate_minimized),
+       KUNIT_CASE(clk_range_test_multiple_set_range_rate_minimized),
+       KUNIT_CASE(clk_range_test_multiple_set_range_rate_put_minimized),
+       {}
+};
+
+/*
+ * Test suite for a basic rate clock, without any parent.
+ *
+ * These tests are supposed to exercise the rate range API
+ * (clk_set_rate_range, clk_set_min_rate, clk_set_max_rate,
+ * clk_drop_range), with a driver that will always try to run at the
+ * lowest possible rate.
+ */
+static struct kunit_suite clk_range_minimize_test_suite = {
+       .name = "clk-range-minimize-test",
+       .init = clk_minimize_test_init,
+       .exit = clk_test_exit,
+       .test_cases = clk_range_minimize_test_cases,
+};
+
+struct clk_leaf_mux_ctx {
+       struct clk_multiple_parent_ctx mux_ctx;
+       struct clk_hw hw;
+};
+
+static int
+clk_leaf_mux_set_rate_parent_test_init(struct kunit *test)
+{
+       struct clk_leaf_mux_ctx *ctx;
+       const char *top_parents[2] = { "parent-0", "parent-1" };
+       int ret;
+
+       ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+       if (!ctx)
+               return -ENOMEM;
+       test->priv = ctx;
+
+       ctx->mux_ctx.parents_ctx[0].hw.init = CLK_HW_INIT_NO_PARENT("parent-0",
+                                                                   &clk_dummy_rate_ops,
+                                                                   0);
+       ctx->mux_ctx.parents_ctx[0].rate = DUMMY_CLOCK_RATE_1;
+       ret = clk_hw_register(NULL, &ctx->mux_ctx.parents_ctx[0].hw);
+       if (ret)
+               return ret;
+
+       ctx->mux_ctx.parents_ctx[1].hw.init = CLK_HW_INIT_NO_PARENT("parent-1",
+                                                                   &clk_dummy_rate_ops,
+                                                                   0);
+       ctx->mux_ctx.parents_ctx[1].rate = DUMMY_CLOCK_RATE_2;
+       ret = clk_hw_register(NULL, &ctx->mux_ctx.parents_ctx[1].hw);
+       if (ret)
+               return ret;
+
+       ctx->mux_ctx.current_parent = 0;
+       ctx->mux_ctx.hw.init = CLK_HW_INIT_PARENTS("test-mux", top_parents,
+                                                  &clk_multiple_parents_mux_ops,
+                                                  0);
+       ret = clk_hw_register(NULL, &ctx->mux_ctx.hw);
+       if (ret)
+               return ret;
+
+       ctx->hw.init = CLK_HW_INIT_HW("test-clock", &ctx->mux_ctx.hw,
+                                     &clk_dummy_single_parent_ops,
+                                     CLK_SET_RATE_PARENT);
+       ret = clk_hw_register(NULL, &ctx->hw);
+       if (ret)
+               return ret;
+
+       return 0;
+}
+
+static void clk_leaf_mux_set_rate_parent_test_exit(struct kunit *test)
+{
+       struct clk_leaf_mux_ctx *ctx = test->priv;
+
+       clk_hw_unregister(&ctx->hw);
+       clk_hw_unregister(&ctx->mux_ctx.hw);
+       clk_hw_unregister(&ctx->mux_ctx.parents_ctx[0].hw);
+       clk_hw_unregister(&ctx->mux_ctx.parents_ctx[1].hw);
+}
+
+/*
+ * Test that, for a clock that will forward any rate request to its
+ * parent, the rate request structure returned by __clk_determine_rate
+ * is sane and will be what we expect.
+ */
+static void clk_leaf_mux_set_rate_parent_determine_rate(struct kunit *test)
+{
+       struct clk_leaf_mux_ctx *ctx = test->priv;
+       struct clk_hw *hw = &ctx->hw;
+       struct clk *clk = hw->clk;
+       struct clk_rate_request req;
+       unsigned long rate;
+       int ret;
+
+       rate = clk_get_rate(clk);
+       KUNIT_ASSERT_EQ(test, rate, DUMMY_CLOCK_RATE_1);
+
+       clk_hw_init_rate_request(hw, &req, DUMMY_CLOCK_RATE_2);
+
+       ret = __clk_determine_rate(hw, &req);
+       KUNIT_ASSERT_EQ(test, ret, 0);
+
+       KUNIT_EXPECT_EQ(test, req.rate, DUMMY_CLOCK_RATE_2);
+       KUNIT_EXPECT_EQ(test, req.best_parent_rate, DUMMY_CLOCK_RATE_2);
+       KUNIT_EXPECT_PTR_EQ(test, req.best_parent_hw, &ctx->mux_ctx.hw);
+}
+
+static struct kunit_case clk_leaf_mux_set_rate_parent_test_cases[] = {
+       KUNIT_CASE(clk_leaf_mux_set_rate_parent_determine_rate),
+       {}
+};
+
+/*
+ * Test suite for a clock whose parent is a mux with multiple parents.
+ * The leaf clock has CLK_SET_RATE_PARENT, and will forward rate
+ * requests to the mux, which will then select which parent is the best
+ * fit for a given rate.
+ *
+ * These tests are supposed to exercise the behaviour of muxes, and the
+ * proper selection of parents.
+  */
+static struct kunit_suite clk_leaf_mux_set_rate_parent_test_suite = {
+       .name = "clk-leaf-mux-set-rate-parent",
+       .init = clk_leaf_mux_set_rate_parent_test_init,
+       .exit = clk_leaf_mux_set_rate_parent_test_exit,
+       .test_cases = clk_leaf_mux_set_rate_parent_test_cases,
+};
+
+kunit_test_suites(
+       &clk_leaf_mux_set_rate_parent_test_suite,
+       &clk_test_suite,
+       &clk_multiple_parents_mux_test_suite,
+       &clk_orphan_transparent_multiple_parent_mux_test_suite,
+       &clk_orphan_transparent_single_parent_test_suite,
+       &clk_orphan_two_level_root_last_test_suite,
+       &clk_range_test_suite,
+       &clk_range_maximize_test_suite,
+       &clk_range_minimize_test_suite,
+       &clk_single_parent_mux_test_suite,
+       &clk_uncached_test_suite
+);
+MODULE_LICENSE("GPL v2");
index 80c2c03..bb1e9fb 100644 (file)
@@ -683,6 +683,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
index 616d926..2f9fa1a 100644 (file)
@@ -21,6 +21,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 (file)
index 0000000..075da9a
--- /dev/null
@@ -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);
+       struct resource *r;
+       int chan;
+
+       if (!dmaman_dev)
+               return -ENODEV;
+
+       mutex_lock(&dmaman->lock);
+       chan = vc_dmaman_chan_alloc(dmaman, required_feature_set);
+       if (chan < 0)
+               goto out;
+
+       r = platform_get_resource(pdev, IORESOURCE_IRQ, (unsigned int)chan);
+       if (!r) {
+               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 = r->start;
+       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");
index 630dfbb..6aee9e9 100644 (file)
@@ -25,6 +25,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>
 
 #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 +56,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 +69,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 +100,7 @@ struct bcm2835_chan {
        unsigned int irq_flags;
 
        bool is_lite_channel;
+       bool is_40bit_channel;
 };
 
 struct bcm2835_desc {
@@ -137,10 +158,30 @@ struct bcm2835_desc {
 #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_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)
+
+
 /* debug register bits */
 #define BCM2835_DMA_DEBUG_LAST_NOT_SET_ERR     BIT(0)
 #define BCM2835_DMA_DEBUG_FIFO_ERR             BIT(1)
@@ -165,13 +206,120 @@ 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 */
+#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_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)     ((min(x,16) - 1) << 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 inline size_t bcm2835_dma_max_frame_length(struct bcm2835_chan *c)
 {
        /* lite and normal channels have different max frame length */
@@ -201,6 +349,32 @@ 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);
+}
+
+static inline uint32_t to_bcm2711_dsti(uint32_t info)
+{
+       return ((info & BCM2835_DMA_D_INC) ? BCM2711_DMA40_INC : 0);
+}
+
+static inline uint32_t to_bcm2711_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 +393,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 +462,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 +480,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 +516,23 @@ 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;
+                       control_block->stride = 0;
+                       control_block->next = 0;
+               }
+
                /* set up length in control_block if requested */
                if (buf_len) {
                        /* calculate length honoring period_length */
@@ -353,7 +546,11 @@ static struct bcm2835_desc *bcm2835_dma_create_cb_chain(
                }
 
                /* link this the last controlblock */
-               if (frame)
+               if (frame && c->is_40bit_channel)
+                       ((struct bcm2711_dma40_scb *)
+                        d->cb_list[frame - 1].cb)->next_cb =
+                               to_bcm2711_cbaddr(cb_entry->paddr);
+               if (frame && !c->is_40bit_channel)
                        d->cb_list[frame - 1].cb->next = cb_entry->paddr;
 
                /* update src and dst and length */
@@ -363,11 +560,21 @@ static struct bcm2835_desc *bcm2835_dma_create_cb_chain(
                        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 missmatch */
        if (buf_len && (d->size != buf_len))
@@ -381,13 +588,12 @@ error_cb:
 }
 
 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 +601,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);
+                       }
                }
        }
 }
@@ -411,6 +638,10 @@ static void bcm2835_dma_abort(struct bcm2835_chan *c)
 {
        void __iomem *chan_base = c->chan_base;
        long int timeout = 10000;
+       u32 wait_mask = BCM2835_DMA_WAITING_FOR_WRITES;
+
+       if (c->is_40bit_channel)
+               wait_mask = BCM2711_DMA40_WAITING_FOR_WRITES;
 
        /*
         * A zero control block address means the channel is idle.
@@ -423,8 +654,7 @@ static void bcm2835_dma_abort(struct bcm2835_chan *c)
        writel(0, 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)
+       while ((readl(chan_base + BCM2835_DMA_CS) & wait_mask) && --timeout)
                cpu_relax();
 
        /* Peripheral might be stuck and fail to signal AXI write responses */
@@ -449,8 +679,16 @@ 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_bcm2711_cbaddr(d->cb_list[0].paddr),
+                      c->chan_base + BCM2711_DMA40_CB);
+               writel(BCM2711_DMA40_ACTIVE | BCM2711_DMA40_CS_FLAGS(c->dreq),
+                      c->chan_base + BCM2711_DMA40_CS);
+       } else {
+               writel(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,7 +715,7 @@ 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,
+       writel(BCM2835_DMA_INT | BCM2835_DMA_ACTIVE | BCM2835_DMA_CS_FLAGS(c->dreq),
               c->chan_base + BCM2835_DMA_CS);
 
        d = c->desc;
@@ -580,9 +818,17 @@ 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)
+                       pos = readl(c->chan_base + BCM2711_DMA40_SRC) +
+                               ((readl(c->chan_base + BCM2711_DMA40_SRCI) &
+                                 0xff) << 8);
+               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)
+                       pos = readl(c->chan_base + BCM2711_DMA40_DEST) +
+                               ((readl(c->chan_base + BCM2711_DMA40_DESTI) &
+                                 0xff) << 8);
+               else if (d->dir == DMA_DEV_TO_MEM && !c->is_40bit_channel)
                        pos = readl(c->chan_base + BCM2835_DMA_DEST_AD);
                else
                        pos = 0;
@@ -615,8 +861,9 @@ 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 |
+                  WIDE_SOURCE(c->dreq) | WIDE_DEST(c->dreq);
+       u32 extra = BCM2835_DMA_INT_EN | WAIT_RESP(c->dreq);
        size_t max_len = bcm2835_dma_max_frame_length(c);
        size_t frames;
 
@@ -628,7 +875,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 +893,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);
        u32 extra = BCM2835_DMA_INT_EN;
        size_t frames;
 
@@ -663,11 +911,21 @@ static struct dma_async_tx_descriptor *bcm2835_dma_prep_slave_sg(
                if (c->cfg.src_addr_width != DMA_SLAVE_BUSWIDTH_4_BYTES)
                        return NULL;
                src = c->cfg.src_addr;
+               /*
+                * One would think it ought to be possible to get the physical
+                * to dma address mapping information from the dma-ranges DT
+                * property, but I've not found a way yet that doesn't involve
+                * open-coding the whole thing.
+                */
+               if (c->is_40bit_channel)
+                   src |= 0x400000000ull;
                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;
+               if (c->is_40bit_channel)
+                   dst |= 0x400000000ull;
                info |= BCM2835_DMA_D_DREQ | BCM2835_DMA_S_INC;
        }
 
@@ -675,7 +933,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 +941,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 +956,7 @@ 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);
        u32 extra = 0;
        size_t max_len = bcm2835_dma_max_frame_length(c);
        size_t frames;
@@ -737,12 +995,16 @@ static struct dma_async_tx_descriptor *bcm2835_dma_prep_dma_cyclic(
                if (c->cfg.src_addr_width != DMA_SLAVE_BUSWIDTH_4_BYTES)
                        return NULL;
                src = c->cfg.src_addr;
+               if (c->is_40bit_channel)
+                   src |= 0x400000000ull;
                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;
+               if (c->is_40bit_channel)
+                   dst |= 0x400000000ull;
                src = buf_addr;
                info |= BCM2835_DMA_D_DREQ | BCM2835_DMA_S_INC;
 
@@ -762,7 +1024,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 +1032,12 @@ 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_bcm2711_cbaddr(d->cb_list[0].paddr);
+       else
+               d->cb_list[d->frames - 1].cb->next = d->cb_list[0].paddr;
 
        return vchan_tx_prep(&c->vc, &d->vd, flags);
 }
@@ -831,9 +1098,11 @@ 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;
 
        return 0;
@@ -853,8 +1122,58 @@ static void bcm2835_dma_free(struct bcm2835_dmadev *od)
                             DMA_TO_DEVICE, DMA_ATTR_SKIP_CPU_SYNC);
 }
 
+int bcm2711_dma40_memcpy_init(void)
+{
+       if (!memcpy_parent)
+               return -EPROBE_DEFER;
+
+       if (!memcpy_chan)
+               return -EINVAL;
+
+       if (!memcpy_scb)
+               return -ENOMEM;
+
+       return 0;
+}
+EXPORT_SYMBOL(bcm2711_dma40_memcpy_init);
+
+void bcm2711_dma40_memcpy(dma_addr_t dst, dma_addr_t src, size_t size)
+{
+       struct bcm2711_dma40_scb *scb = memcpy_scb;
+       unsigned long flags;
+
+       if (!scb) {
+               pr_err("bcm2711_dma40_memcpy not initialised!\n");
+               return;
+       }
+
+       spin_lock_irqsave(&memcpy_lock, flags);
+
+       scb->ti = 0;
+       scb->src = lower_32_bits(src);
+       scb->srci = upper_32_bits(src) | BCM2711_DMA40_MEMCPY_XFER_INFO;
+       scb->dst = lower_32_bits(dst);
+       scb->dsti = upper_32_bits(dst) | BCM2711_DMA40_MEMCPY_XFER_INFO;
+       scb->len = size;
+       scb->next_cb = 0;
+
+       writel((u32)(memcpy_scb_dma >> 5), memcpy_chan + BCM2711_DMA40_CB);
+       writel(BCM2711_DMA40_MEMCPY_FLAGS + BCM2711_DMA40_ACTIVE,
+              memcpy_chan + BCM2711_DMA40_CS);
+
+       /* Poll for completion */
+       while (!(readl(memcpy_chan + BCM2711_DMA40_CS) & BCM2711_DMA40_END))
+               cpu_relax();
+
+       writel(BCM2711_DMA40_END, memcpy_chan + BCM2711_DMA40_CS);
+
+       spin_unlock_irqrestore(&memcpy_lock, flags);
+}
+EXPORT_SYMBOL(bcm2711_dma40_memcpy);
+
 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 },
        {},
 };
 MODULE_DEVICE_TABLE(of, bcm2835_dma_of_match);
@@ -877,6 +1196,8 @@ 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;
@@ -886,11 +1207,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;
@@ -907,6 +1237,13 @@ static int bcm2835_dma_probe(struct platform_device *pdev)
        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);
@@ -942,6 +1279,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",
@@ -951,8 +1296,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;
@@ -975,13 +1348,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])) {
@@ -993,9 +1370,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,
@@ -1025,7 +1403,15 @@ static int 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);
 
        return 0;
@@ -1040,7 +1426,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");
index 4b8978b..1223e31 100644 (file)
@@ -12,6 +12,7 @@
 #include <linux/module.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>
 
@@ -30,8 +31,11 @@ struct rpi_firmware {
        u32 enabled;
 
        struct kref consumers;
+       u32 get_throttled;
 };
 
+static struct platform_device *g_pdev;
+
 static DEFINE_MUTEX(transaction_lock);
 
 static void response_callback(struct mbox_client *cl, void *msg)
@@ -173,15 +177,92 @@ int rpi_firmware_property(struct rpi_firmware *fw,
 
        kfree(data);
 
+       if ((tag == RPI_FIRMWARE_GET_THROTTLED) &&
+            memcmp(&fw->get_throttled, tag_data, sizeof(fw->get_throttled))) {
+               memcpy(&fw->get_throttled, tag_data, sizeof(fw->get_throttled));
+               sysfs_notify(&fw->cl.dev->kobj, NULL, "get_throttled");
+       }
+
        return ret;
 }
 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 ssize_t get_throttled_show(struct device *dev,
+                                 struct device_attribute *attr, char *buf)
+{
+       struct rpi_firmware *fw = dev_get_drvdata(dev);
+
+       WARN_ONCE(1, "deprecated, use hwmon sysfs instead\n");
+
+       return sprintf(buf, "%x\n", fw->get_throttled);
+}
+
+static DEVICE_ATTR_RO(get_throttled);
+
+static struct attribute *rpi_firmware_dev_attrs[] = {
+       &dev_attr_get_throttled.attr,
+       NULL,
+};
+
+static const struct attribute_group rpi_firmware_dev_group = {
+       .attrs = rpi_firmware_dev_attrs,
+};
+
 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));
@@ -191,7 +272,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
@@ -206,6 +315,11 @@ rpi_register_hwmon_driver(struct device *dev, struct rpi_firmware *fw)
 
        rpi_hwmon = platform_device_register_data(dev, "raspberrypi-hwmon",
                                                  -1, NULL, 0);
+
+       if (!IS_ERR_OR_NULL(rpi_hwmon)) {
+               if (devm_device_add_group(dev, &rpi_firmware_dev_group))
+                       dev_err(dev, "Failed to create get_trottled attr\n");
+       }
 }
 
 static void rpi_register_clk_driver(struct device *dev)
@@ -279,8 +393,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);
 
@@ -307,6 +423,7 @@ static int rpi_firmware_remove(struct platform_device *pdev)
        rpi_clk = NULL;
 
        rpi_firmware_put(fw);
+       g_pdev = NULL;
 
        return 0;
 }
@@ -381,7 +498,35 @@ static struct platform_driver rpi_firmware_driver = {
        .shutdown       = rpi_firmware_shutdown,
        .remove         = 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");
index 947474f..addea3a 100644 (file)
@@ -194,6 +194,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)
@@ -479,6 +485,14 @@ config GPIO_PMIC_EIC_SPRD
        help
          Say yes here to support Spreadtrum PMIC EIC 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
@@ -1226,6 +1240,15 @@ config HTC_EGPIO
          several HTC phones.  It provides basic support for input
          pins, output pins, and irqs.
 
+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
index fbcda63..0980f9b 100644 (file)
@@ -38,6 +38,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_BD70528)             += gpio-bd70528.o
 obj-$(CONFIG_GPIO_BD71815)             += gpio-bd71815.o
 obj-$(CONFIG_GPIO_BD71828)             += gpio-bd71828.o
@@ -60,6 +61,7 @@ obj-$(CONFIG_GPIO_EP93XX)             += gpio-ep93xx.o
 obj-$(CONFIG_GPIO_EXAR)                        += gpio-exar.o
 obj-$(CONFIG_GPIO_F7188X)              += gpio-f7188x.o
 obj-$(CONFIG_GPIO_FTGPIO010)           += gpio-ftgpio010.o
+obj-$(CONFIG_GPIO_FSM)                 += gpio-fsm.o
 obj-$(CONFIG_GPIO_GE_FPGA)             += gpio-ge.o
 obj-$(CONFIG_GPIO_GPIO_MM)             += gpio-gpio-mm.o
 obj-$(CONFIG_GPIO_GRGPIO)              += gpio-grgpio.o
@@ -119,6 +121,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 (file)
index 0000000..55c4019
--- /dev/null
@@ -0,0 +1,214 @@
+/*
+ *  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.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;
+       struct device_node *fw_node;
+       struct rpi_firmware *fw;
+       struct brcmvirt_gpio *ucb;
+       u32 gpiovirtbuf;
+
+       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;
+
+       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.label = MODULE_NAME;
+       ucb->gc.owner = THIS_MODULE;
+       //ucb->gc.dev = dev;
+       ucb->gc.of_node = np;
+       ucb->gc.base = 100;
+       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(&ucb->gc);
+       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 int brcmvirt_gpio_remove(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       int err = 0;
+       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);
+       return err;
+}
+
+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-fsm.c b/drivers/gpio/gpio-fsm.c
new file mode 100644 (file)
index 0000000..3a348f1
--- /dev/null
@@ -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/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 int 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 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_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_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>");
+
+       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;
+       gf->delay_target_state = NULL;
+
+       if (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);
+
+       if (new_state)
+               schedule_work(&gf->work);
+}
+
+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_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_DIR_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_DIR_OUT;
+       gpio_fsm_set_soft(gf, off, value);
+
+       return 0;
+}
+
+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 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;
+
+       // 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_go_to_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(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_go_to_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_work(struct work_struct *work)
+{
+       struct input_gpio_state *inp_state;
+       struct fsm_state *new_state;
+       struct fsm_state *state;
+       struct gpio_event *gp_ev;
+       struct gpio_fsm *gf;
+       int i;
+
+       gf = container_of(work, struct gpio_fsm, work);
+       spin_lock(&gf->spinlock);
+       state = gf->current_state;
+       new_state = gf->next_state;
+       if (!new_state)
+               new_state = gf->delay_target_state;
+       gf->next_state = NULL;
+       gf->delay_target_state = NULL;
+       spin_unlock(&gf->spinlock);
+
+       if (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);
+                       }
+               }
+       }
+
+       if (new_state)
+               gpio_fsm_enter_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(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(gf, target);
+}
+
+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;
+}
+
+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;
+}
+
+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;
+}
+
+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_DIR_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;
+}
+
+
+/*
+ * /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,
+       .owner =        THIS_MODULE,
+
+       .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;
+       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_DIR_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.of_node = np;
+       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_go_to_state(gf, gf->start_state);
+
+       return devm_gpiochip_add_data(dev, &gf->gc, gf);
+}
+
+static int 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");
+
+       return 0;
+}
+
+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-pwm.c b/drivers/gpio/gpio-pwm.c
new file mode 100644 (file)
index 0000000..89f5d6b
--- /dev/null
@@ -0,0 +1,144 @@
+// 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_state(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_state(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.of_node = dev->of_node;
+       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 int pwm_gpio_remove(struct platform_device *pdev)
+{
+       return 0;
+}
+
+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");
index 320baed..a929cb5 100644 (file)
@@ -52,6 +52,8 @@
 #define        extra_checks    0
 #endif
 
+#define dont_test_bit(b,d) (0)
+
 /* Device and char device-related information */
 static DEFINE_IDA(gpio_ida);
 static dev_t gpio_devt;
@@ -2361,8 +2363,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, &desc->flags) &&
-           test_bit(FLAG_IRQ_IS_ENABLED, &desc->flags)) {
+       if (dont_test_bit(FLAG_USED_AS_IRQ, &desc->flags) &&
+           dont_test_bit(FLAG_IRQ_IS_ENABLED, &desc->flags)) {
                gpiod_err(desc,
                          "%s: tried to set a GPIO tied to an IRQ as output\n",
                          __func__);
@@ -3180,8 +3182,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__);
index c916f4b..f11ff62 100644 (file)
@@ -79,6 +79,10 @@ static int panel_bridge_attach(struct drm_bridge *bridge,
                return ret;
        }
 
+       /* set up connector's "panel orientation" property */
+       drm_connector_set_panel_orientation(&panel_bridge->connector,
+                                           panel_bridge->panel->orientation);
+
        drm_connector_attach_encoder(&panel_bridge->connector,
                                          bridge->encoder);
 
@@ -221,6 +225,9 @@ struct drm_bridge *drm_panel_bridge_add_typed(struct drm_panel *panel,
        panel_bridge->bridge.ops = DRM_BRIDGE_OP_MODES;
        panel_bridge->bridge.type = connector_type;
 
+       panel_bridge->bridge.pre_enable_upstream_first =
+                                               panel->prepare_upstream_first;
+
        drm_bridge_add(&panel_bridge->bridge);
 
        return &panel_bridge->bridge;
index 1bfdfc6..82d1318 100644 (file)
@@ -237,13 +237,14 @@ static int tc358762_probe(struct mipi_dsi_device *dsi)
        ctx->bridge.funcs = &tc358762_bridge_funcs;
        ctx->bridge.type = DRM_MODE_CONNECTOR_DPI;
        ctx->bridge.of_node = dev->of_node;
+       ctx->bridge.pre_enable_upstream_first = true;
 
        drm_bridge_add(&ctx->bridge);
 
        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;
index aa09a19..e54f086 100644 (file)
@@ -431,6 +431,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.
@@ -1493,13 +1498,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;
@@ -2128,12 +2126,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);
index ddcf5c2..f098808 100644 (file)
@@ -243,11 +243,50 @@ EXPORT_SYMBOL(drm_atomic_helper_crtc_destroy_state);
 void __drm_atomic_helper_plane_state_reset(struct drm_plane_state *plane_state,
                                           struct drm_plane *plane)
 {
+       u64 val;
+
        plane_state->plane = plane;
        plane_state->rotation = DRM_MODE_ROTATE_0;
 
        plane_state->alpha = DRM_BLEND_ALPHA_OPAQUE;
        plane_state->pixel_blend_mode = DRM_MODE_BLEND_PREMULTI;
+
+       if (plane->color_encoding_property) {
+               if (!drm_object_property_get_default_value(&plane->base,
+                                                          plane->color_encoding_property,
+                                                          &val))
+                       plane_state->color_encoding = val;
+       }
+
+       if (plane->color_range_property) {
+               if (!drm_object_property_get_default_value(&plane->base,
+                                                          plane->color_range_property,
+                                                          &val))
+                       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,
+                                                          &val)) {
+                       plane_state->zpos = val;
+                       plane_state->normalized_zpos = val;
+               }
+       }
 }
 EXPORT_SYMBOL(__drm_atomic_helper_plane_state_reset);
 
index f195c70..4697613 100644 (file)
@@ -598,6 +598,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_atomic_replace_property_blob_from_id(dev,
                                        &state->fb_damage_clips,
@@ -664,6 +668,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;
@@ -707,6 +715,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;
 
@@ -726,12 +735,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->tv_mode_property) {
                state->tv.mode = val;
        } else if (property == config->tv_brightness_property) {
@@ -810,6 +823,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;
 }
 
index a8ed667..e0f5742 100644 (file)
@@ -463,20 +463,15 @@ EXPORT_SYMBOL(drm_bridge_chain_disable);
  * encoder chain, starting from the first bridge to the last. These are called
  * after completing the encoder's prepare op.
  *
+ * If a bridge sets @pre_enable_upstream_first, then the @post_disable for that
+ * bridge will be called before the previous one to reverse the @pre_enable
+ * calling direction.
+ *
  * Note: the bridge passed should be the one closest to the encoder
  */
 void drm_bridge_chain_post_disable(struct drm_bridge *bridge)
 {
-       struct drm_encoder *encoder;
-
-       if (!bridge)
-               return;
-
-       encoder = bridge->encoder;
-       list_for_each_entry_from(bridge, &encoder->bridge_chain, chain_node) {
-               if (bridge->funcs->post_disable)
-                       bridge->funcs->post_disable(bridge);
-       }
+       drm_atomic_bridge_chain_post_disable(bridge, NULL);
 }
 EXPORT_SYMBOL(drm_bridge_chain_post_disable);
 
@@ -518,24 +513,14 @@ EXPORT_SYMBOL(drm_bridge_chain_mode_set);
  * chain, starting from the last bridge to the first. These are called
  * before calling the encoder's commit op.
  *
+ * If a bridge sets @pre_enable_upstream_first, then the @pre_enable for the
+ * previous bridge will be called before @pre_enable of this bridge.
+ *
  * Note: the bridge passed should be the one closest to the encoder
  */
 void drm_bridge_chain_pre_enable(struct drm_bridge *bridge)
 {
-       struct drm_encoder *encoder;
-       struct drm_bridge *iter;
-
-       if (!bridge)
-               return;
-
-       encoder = bridge->encoder;
-       list_for_each_entry_reverse(iter, &encoder->bridge_chain, chain_node) {
-               if (iter->funcs->pre_enable)
-                       iter->funcs->pre_enable(iter);
-
-               if (iter == bridge)
-                       break;
-       }
+       drm_atomic_bridge_chain_pre_enable(bridge, NULL);
 }
 EXPORT_SYMBOL(drm_bridge_chain_pre_enable);
 
@@ -607,6 +592,25 @@ void drm_atomic_bridge_chain_disable(struct drm_bridge *bridge,
 }
 EXPORT_SYMBOL(drm_atomic_bridge_chain_disable);
 
+static void drm_atomic_bridge_call_post_disable(struct drm_bridge *bridge,
+                                               struct drm_atomic_state *old_state)
+{
+       if (old_state && bridge->funcs->atomic_post_disable) {
+               struct drm_bridge_state *old_bridge_state;
+
+               old_bridge_state =
+                       drm_atomic_get_old_bridge_state(old_state,
+                                                       bridge);
+               if (WARN_ON(!old_bridge_state))
+                       return;
+
+               bridge->funcs->atomic_post_disable(bridge,
+                                                  old_bridge_state);
+       } else if (bridge->funcs->post_disable) {
+               bridge->funcs->post_disable(bridge);
+       }
+}
+
 /**
  * drm_atomic_bridge_chain_post_disable - cleans up after disabling all bridges
  *                                       in the encoder chain
@@ -617,6 +621,9 @@ EXPORT_SYMBOL(drm_atomic_bridge_chain_disable);
  * &drm_bridge_funcs.post_disable) op for all the bridges in the encoder chain,
  * starting from the first bridge to the last. These are called after completing
  * &drm_encoder_helper_funcs.atomic_disable
+ * If a bridge sets @pre_enable_upstream_first, then the @post_disable for that
+ * bridge will be called before the previous one to reverse the @pre_enable
+ * calling direction.
  *
  * Note: the bridge passed should be the one closest to the encoder
  */
@@ -624,30 +631,75 @@ void drm_atomic_bridge_chain_post_disable(struct drm_bridge *bridge,
                                          struct drm_atomic_state *old_state)
 {
        struct drm_encoder *encoder;
+       struct drm_bridge *next, *limit;
 
        if (!bridge)
                return;
 
        encoder = bridge->encoder;
+
        list_for_each_entry_from(bridge, &encoder->bridge_chain, chain_node) {
-               if (bridge->funcs->atomic_post_disable) {
-                       struct drm_bridge_state *old_bridge_state;
+               limit = NULL;
+
+               if (!list_is_last(&bridge->chain_node, &encoder->bridge_chain)) {
+                       next = list_next_entry(bridge, chain_node);
+
+                       if (next->pre_enable_upstream_first) {
+                               /* Downstream bridge had requested that upstream
+                                * was enabled first, so disabled last
+                                */
+                               limit = next;
+
+                               /* Find the next bridge that has NOT requested
+                                * upstream to be enabled first / disabled last
+                                */
+                               list_for_each_entry_from(next, &encoder->bridge_chain,
+                                                        chain_node) {
+                                       if (next->pre_enable_upstream_first) {
+                                               next = list_prev_entry(next, chain_node);
+                                               limit = next;
+                                               break;
+                                       }
+                               }
+
+                               /* Call these bridges in reverse order */
+                               list_for_each_entry_from_reverse(next, &encoder->bridge_chain,
+                                                                chain_node) {
+                                       if (next == bridge)
+                                               break;
+
+                                       drm_atomic_bridge_call_post_disable(next,
+                                                                           old_state);
+                               }
+                       }
+               }
 
-                       old_bridge_state =
-                               drm_atomic_get_old_bridge_state(old_state,
-                                                               bridge);
-                       if (WARN_ON(!old_bridge_state))
-                               return;
+               drm_atomic_bridge_call_post_disable(bridge, old_state);
 
-                       bridge->funcs->atomic_post_disable(bridge,
-                                                          old_bridge_state);
-               } else if (bridge->funcs->post_disable) {
-                       bridge->funcs->post_disable(bridge);
-               }
+               if (limit)
+                       bridge = limit;
        }
 }
 EXPORT_SYMBOL(drm_atomic_bridge_chain_post_disable);
 
+static void drm_atomic_bridge_call_pre_enable(struct drm_bridge *bridge,
+                                             struct drm_atomic_state *old_state)
+{
+       if (old_state && bridge->funcs->atomic_pre_enable) {
+               struct drm_bridge_state *old_bridge_state;
+
+               old_bridge_state =
+                       drm_atomic_get_old_bridge_state(old_state,
+                                                       bridge);
+               if (WARN_ON(!old_bridge_state))
+                       return;
+
+               bridge->funcs->atomic_pre_enable(bridge, old_bridge_state);
+       } else if (bridge->funcs->pre_enable) {
+               bridge->funcs->pre_enable(bridge);
+       }
+}
+
 /**
  * drm_atomic_bridge_chain_pre_enable - prepares for enabling all bridges in
  *                                     the encoder chain
@@ -659,32 +711,61 @@ EXPORT_SYMBOL(drm_atomic_bridge_chain_post_disable);
  * starting from the last bridge to the first. These are called before calling
  * &drm_encoder_helper_funcs.atomic_enable
  *
+ * If a bridge sets @pre_enable_upstream_first, then the pre_enable for the
+ * upstream bridge will be called before pre_enable of this bridge.
+ *
  * Note: the bridge passed should be the one closest to the encoder
  */
 void drm_atomic_bridge_chain_pre_enable(struct drm_bridge *bridge,
                                        struct drm_atomic_state *old_state)
 {
        struct drm_encoder *encoder;
-       struct drm_bridge *iter;
+       struct drm_bridge *iter, *next, *limit;
 
        if (!bridge)
                return;
 
        encoder = bridge->encoder;
+
        list_for_each_entry_reverse(iter, &encoder->bridge_chain, chain_node) {
-               if (iter->funcs->atomic_pre_enable) {
-                       struct drm_bridge_state *old_bridge_state;
+               if (iter->pre_enable_upstream_first) {
+                       next = iter;
+                       limit = bridge;
+                       list_for_each_entry_from_reverse(next,
+                                                        &encoder->bridge_chain,
+                                                        chain_node) {
+                               if (next == bridge)
+                                       break;
+
+                               if (!next->pre_enable_upstream_first) {
+                                       /* Found first bridge that does NOT
+                                        * request upstream to be enabled first
+                                        */
+                                       limit = list_prev_entry(next, chain_node);
+                                       break;
+                               }
+                       }
+
+                       list_for_each_entry_from(next, &encoder->bridge_chain, chain_node) {
+                               /* Call requested upstream bridge pre_enable
+                                * in order.
+                                */
+                               if (next == iter)
+                                       /* At the first bridgge to request upstream
+                                        * bridges called first.
+                                        */
+                                       break;
+
+                               drm_atomic_bridge_call_pre_enable(next, old_state);
+                       }
+               }
 
-                       old_bridge_state =
-                               drm_atomic_get_old_bridge_state(old_state,
-                                                               iter);
-                       if (WARN_ON(!old_bridge_state))
-                               return;
+               drm_atomic_bridge_call_pre_enable(iter, old_state);
 
-                       iter->funcs->atomic_pre_enable(iter, old_bridge_state);
-               } else if (iter->funcs->pre_enable) {
-                       iter->funcs->pre_enable(iter);
-               }
+               if (iter->pre_enable_upstream_first)
+                       /* Jump all bridges that we have already pre_enabled
+                        */
+                       iter = limit;
 
                if (iter == bridge)
                        break;
index bb14f48..cc5252e 100644 (file)
@@ -326,7 +326,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;
 
@@ -585,6 +587,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
  * @tests: bitmask of tests to run
index 6ab048b..b2f878e 100644 (file)
@@ -5033,6 +5033,12 @@ static void drm_parse_hdmi_deep_color_info(struct drm_connector *connector,
                  connector->name, dc_bpc);
        info->bpc = dc_bpc;
 
+       /*
+        * Deep color support mandates RGB444 support for all video
+        * modes.
+        */
+       info->color_formats |= DRM_COLOR_FORMAT_RGB444;
+
        /* YCRCB444 is optional according to spec. */
        if (hdmi[6] & DRM_EDID_HDMI_DC_Y444) {
                info->edid_hdmi_ycbcr444_dc_modes = info->edid_hdmi_rgb444_dc_modes;
@@ -5742,13 +5748,13 @@ static const u32 hdmi_colorimetry_val[] = {
 #undef ACE
 
 /**
- * drm_hdmi_avi_infoframe_colorspace() - fill the HDMI AVI infoframe
- *                                       colorspace information
+ * drm_hdmi_avi_infoframe_colorimetry() - fill the HDMI AVI infoframe
+ *                                       colorimetry information
  * @frame: HDMI AVI infoframe
  * @conn_state: connector state
  */
 void
-drm_hdmi_avi_infoframe_colorspace(struct hdmi_avi_infoframe *frame,
+drm_hdmi_avi_infoframe_colorimetry(struct hdmi_avi_infoframe *frame,
                                  const struct drm_connector_state *conn_state)
 {
        u32 colorimetry_val;
@@ -5767,7 +5773,7 @@ drm_hdmi_avi_infoframe_colorspace(struct hdmi_avi_infoframe *frame,
        frame->extended_colorimetry = (colorimetry_val >> 2) &
                                        EXTENDED_COLORIMETRY_MASK;
 }
-EXPORT_SYMBOL(drm_hdmi_avi_infoframe_colorspace);
+EXPORT_SYMBOL(drm_hdmi_avi_infoframe_colorimetry);
 
 /**
  * drm_hdmi_avi_infoframe_quant_range() - fill the HDMI AVI infoframe
index eda832f..8537a41 100644 (file)
@@ -266,6 +266,9 @@ const struct drm_format_info *__drm_format_info(u32 format)
                  .num_planes = 3, .char_per_block = { 2, 2, 2 },
                  .block_w = { 1, 1, 1 }, .block_h = { 1, 1, 1 }, .hsub = 0,
                  .vsub = 0, .is_yuv = true },
+               { .format = DRM_FORMAT_P030,            .depth = 0,  .num_planes = 2,
+                 .char_per_block = { 4, 4, 0 }, .block_w = { 3, 0, 0 }, .block_h = { 1, 0, 0 },
+                 .hsub = 2, .vsub = 2, .is_yuv = true},
        };
 
        unsigned int i;
index 07f5abc..8683b59 100644 (file)
@@ -214,12 +214,16 @@ static int framebuffer_check(struct drm_device *dev,
                if (min_pitch > UINT_MAX)
                        return -ERANGE;
 
-               if ((uint64_t) height * r->pitches[i] + r->offsets[i] > UINT_MAX)
-                       return -ERANGE;
-
-               if (block_size && r->pitches[i] < min_pitch) {
-                       DRM_DEBUG_KMS("bad pitch %u for plane %d\n", r->pitches[i], i);
-                       return -EINVAL;
+               if (r->modifier[i] == DRM_FORMAT_MOD_LINEAR) {
+                       if ((uint64_t)height * r->pitches[i] + r->offsets[i] >
+                                                               UINT_MAX)
+                               return -ERANGE;
+
+                       if (block_size && r->pitches[i] < min_pitch) {
+                               DRM_DEBUG_KMS("bad pitch %u for plane %d\n",
+                                             r->pitches[i], i);
+                               return -EINVAL;
+                       }
                }
 
                if (r->modifier[i] && !(r->flags & DRM_MODE_FB_MODIFIERS)) {
index 86d9e90..ba1608e 100644 (file)
@@ -297,11 +297,26 @@ int drm_object_property_set_value(struct drm_mode_object *obj,
 }
 EXPORT_SYMBOL(drm_object_property_set_value);
 
+static int __drm_object_property_get_prop_value(struct drm_mode_object *obj,
+                                               struct drm_property *property,
+                                               uint64_t *val)
+{
+       int i;
+
+       for (i = 0; i < obj->properties->count; i++) {
+               if (obj->properties->properties[i] == property) {
+                       *val = obj->properties->values[i];
+                       return 0;
+               }
+       }
+
+       return -EINVAL;
+}
+
 static int __drm_object_property_get_value(struct drm_mode_object *obj,
                                           struct drm_property *property,
                                           uint64_t *val)
 {
-       int i;
 
        /* read-only properties bypass atomic mechanism and still store
         * their value in obj->properties->values[].. mostly to avoid
@@ -311,15 +326,7 @@ static int __drm_object_property_get_value(struct drm_mode_object *obj,
                        !(property->flags & DRM_MODE_PROP_IMMUTABLE))
                return drm_atomic_get_property(obj, property, val);
 
-       for (i = 0; i < obj->properties->count; i++) {
-               if (obj->properties->properties[i] == property) {
-                       *val = obj->properties->values[i];
-                       return 0;
-               }
-
-       }
-
-       return -EINVAL;
+       return __drm_object_property_get_prop_value(obj, property, val);
 }
 
 /**
@@ -348,6 +355,32 @@ int drm_object_property_get_value(struct drm_mode_object *obj,
 }
 EXPORT_SYMBOL(drm_object_property_get_value);
 
+/**
+ * drm_object_property_get_default_value - retrieve the default value of a
+ * property when in atomic mode.
+ * @obj: drm mode object to get property value from
+ * @property: property to retrieve
+ * @val: storage for the property value
+ *
+ * This function retrieves the default state of the given property as passed in
+ * to drm_object_attach_property
+ *
+ * Only atomic drivers should call this function directly, as for non-atomic
+ * drivers it will return the current value.
+ *
+ * Returns:
+ * Zero on success, error code on failure.
+ */
+int drm_object_property_get_default_value(struct drm_mode_object *obj,
+                                         struct drm_property *property,
+                                         uint64_t *val)
+{
+       WARN_ON(!drm_drv_uses_atomic_modeset(property->dev));
+
+       return __drm_object_property_get_prop_value(obj, property, val);
+}
+EXPORT_SYMBOL(drm_object_property_get_default_value);
+
 /* helper for getconnector and getproperties ioctls */
 int drm_mode_object_get_properties(struct drm_mode_object *obj, bool atomic,
                                   uint32_t __user *prop_ptr,
index 1c72208..0f492d7 100644 (file)
@@ -35,6 +35,7 @@
 #include <linux/list_sort.h>
 #include <linux/export.h>
 
+#include <video/of_display_timing.h>
 #include <video/of_videomode.h>
 #include <video/videomode.h>
 
@@ -727,6 +728,54 @@ int of_get_drm_display_mode(struct device_node *np,
        return 0;
 }
 EXPORT_SYMBOL_GPL(of_get_drm_display_mode);
+
+/**
+ * of_get_drm_panel_display_mode - get a panel-timing drm_display_mode from devicetree
+ * @np: device_node with the panel-timing specification
+ * @dmode: will be set to the return value
+ * @bus_flags: information about pixelclk, sync and DE polarity
+ *
+ * The Device Tree properties width-mm and height-mm will be read and set on
+ * the display mode if they are present.
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int of_get_drm_panel_display_mode(struct device_node *np,
+                                 struct drm_display_mode *dmode, u32 *bus_flags)
+{
+       u32 width_mm = 0, height_mm = 0;
+       struct display_timing timing;
+       struct videomode vm;
+       int ret;
+
+       ret = of_get_display_timing(np, "panel-timing", &timing);
+       if (ret)
+               return ret;
+
+       videomode_from_timing(&timing, &vm);
+
+       memset(dmode, 0, sizeof(*dmode));
+       drm_display_mode_from_videomode(&vm, dmode);
+       if (bus_flags)
+               drm_bus_flags_from_videomode(&vm, bus_flags);
+
+       ret = of_property_read_u32(np, "width-mm", &width_mm);
+       if (ret && ret != -EINVAL)
+               return ret;
+
+       ret = of_property_read_u32(np, "height-mm", &height_mm);
+       if (ret && ret != -EINVAL)
+               return ret;
+
+       dmode->width_mm = width_mm;
+       dmode->height_mm = height_mm;
+
+       drm_mode_debug_printmodeline(dmode);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(of_get_drm_panel_display_mode);
 #endif /* CONFIG_OF */
 #endif /* CONFIG_VIDEOMODE_HELPERS */
 
index f634371..bee5066 100644 (file)
@@ -61,6 +61,9 @@ void drm_panel_init(struct drm_panel *panel, struct device *dev,
        panel->dev = dev;
        panel->funcs = funcs;
        panel->connector_type = connector_type;
+
+       panel->orientation = DRM_MODE_PANEL_ORIENTATION_UNKNOWN;
+       of_drm_get_panel_orientation(dev->of_node, &panel->orientation);
 }
 EXPORT_SYMBOL(drm_panel_init);
 
@@ -289,16 +292,18 @@ int of_drm_get_panel_orientation(const struct device_node *np,
        if (ret < 0)
                return ret;
 
-       if (rotation == 0)
+       if (rotation == 0) {
                *orientation = DRM_MODE_PANEL_ORIENTATION_NORMAL;
-       else if (rotation == 90)
+       } else if (rotation == 90) {
                *orientation = DRM_MODE_PANEL_ORIENTATION_RIGHT_UP;
-       else if (rotation == 180)
+       } else if (rotation == 180) {
                *orientation = DRM_MODE_PANEL_ORIENTATION_BOTTOM_UP;
-       else if (rotation == 270)
+       } else if (rotation == 270) {
                *orientation = DRM_MODE_PANEL_ORIENTATION_LEFT_UP;
-       else
+       } else {
+               DRM_ERROR("%pOF: invalid orientation %d\n", np, ret);
                return -EINVAL;
+       }
 
        return 0;
 }
index 5606bca..fcf32ec 100644 (file)
@@ -795,6 +795,86 @@ void drm_kms_helper_poll_fini(struct drm_device *dev)
 }
 EXPORT_SYMBOL(drm_kms_helper_poll_fini);
 
+static bool check_connector_changed(struct drm_connector *connector)
+{
+       struct drm_device *dev = connector->dev;
+       enum drm_connector_status old_status;
+       u64 old_epoch_counter;
+       bool changed = false;
+
+       /* Only handle HPD capable connectors. */
+       drm_WARN_ON(dev, !(connector->polled & DRM_CONNECTOR_POLL_HPD));
+
+       drm_WARN_ON(dev, !mutex_is_locked(&dev->mode_config.mutex));
+
+       old_status = connector->status;
+       old_epoch_counter = connector->epoch_counter;
+
+       drm_dbg_kms(dev, "[CONNECTOR:%d:%s] Old epoch counter %llu\n",
+                   connector->base.id,
+                   connector->name,
+                   old_epoch_counter);
+
+       connector->status = drm_helper_probe_detect(connector, NULL, false);
+       drm_dbg_kms(dev, "[CONNECTOR:%d:%s] status updated from %s to %s\n",
+                   connector->base.id,
+                   connector->name,
+                   drm_get_connector_status_name(old_status),
+                   drm_get_connector_status_name(connector->status));
+
+       drm_dbg_kms(dev, "[CONNECTOR:%d:%s] New epoch counter %llu\n",
+                   connector->base.id,
+                   connector->name,
+                   connector->epoch_counter);
+
+       /*
+        * Check if epoch counter had changed, meaning that we need
+        * to send a uevent.
+        */
+       if (old_epoch_counter != connector->epoch_counter)
+               changed = true;
+
+       return changed;
+}
+
+/**
+ * drm_connector_helper_hpd_irq_event - hotplug processing
+ * @connector: drm_connector
+ *
+ * Drivers can use this helper function to run a detect cycle on a connector
+ * which has the DRM_CONNECTOR_POLL_HPD flag set in its &polled member.
+ *
+ * This helper function is useful for drivers which can track hotplug
+ * interrupts for a single connector. Drivers that want to send a
+ * hotplug event for all connectors or can't track hotplug interrupts
+ * per connector need to use drm_helper_hpd_irq_event().
+ *
+ * This function must be called from process context with no mode
+ * setting locks held.
+ *
+ * Note that a connector can be both polled and probed from the hotplug
+ * handler, in case the hotplug interrupt is known to be unreliable.
+ */
+bool drm_connector_helper_hpd_irq_event(struct drm_connector *connector)
+{
+       struct drm_device *dev = connector->dev;
+       bool changed;
+
+       mutex_lock(&dev->mode_config.mutex);
+       changed = check_connector_changed(connector);
+       mutex_unlock(&dev->mode_config.mutex);
+
+       if (changed) {
+               drm_kms_helper_hotplug_event(dev);
+               drm_dbg_kms(dev, "[CONNECTOR:%d:%s] Sent hotplug event\n",
+                           connector->base.id,
+                           connector->name);
+       }
+
+       return changed;
+}
+EXPORT_SYMBOL(drm_connector_helper_hpd_irq_event);
+
 /**
  * drm_helper_hpd_irq_event - hotplug processing
  * @dev: drm_device
@@ -808,9 +888,10 @@ EXPORT_SYMBOL(drm_kms_helper_poll_fini);
  * interrupts for each connector.
  *
  * Drivers which support hotplug interrupts for each connector individually and
- * which have a more fine-grained detect logic should bypass this code and
- * directly call drm_kms_helper_hotplug_event() in case the connector state
- * changed.
+ * which have a more fine-grained detect logic can use
+ * drm_connector_helper_hpd_irq_event(). Alternatively, they should bypass this
+ * code and directly call drm_kms_helper_hotplug_event() in case the connector
+ * state changed.
  *
  * This function must be called from process context with no mode
  * setting locks held.
@@ -822,9 +903,7 @@ bool drm_helper_hpd_irq_event(struct drm_device *dev)
 {
        struct drm_connector *connector;
        struct drm_connector_list_iter conn_iter;
-       enum drm_connector_status old_status;
        bool changed = false;
-       u64 old_epoch_counter;
 
        if (!dev->mode_config.poll_enabled)
                return false;
@@ -832,37 +911,8 @@ bool drm_helper_hpd_irq_event(struct drm_device *dev)
        mutex_lock(&dev->mode_config.mutex);
        drm_connector_list_iter_begin(dev, &conn_iter);
        drm_for_each_connector_iter(connector, &conn_iter) {
-               /* Only handle HPD capable connectors. */
-               if (!(connector->polled & DRM_CONNECTOR_POLL_HPD))
-                       continue;
-
-               old_status = connector->status;
-
-               old_epoch_counter = connector->epoch_counter;
-
-               DRM_DEBUG_KMS("[CONNECTOR:%d:%s] Old epoch counter %llu\n", connector->base.id,
-                             connector->name,
-                             old_epoch_counter);
-
-               connector->status = drm_helper_probe_detect(connector, NULL, false);
-               DRM_DEBUG_KMS("[CONNECTOR:%d:%s] status updated from %s to %s\n",
-                             connector->base.id,
-                             connector->name,
-                             drm_get_connector_status_name(old_status),
-                             drm_get_connector_status_name(connector->status));
-
-               DRM_DEBUG_KMS("[CONNECTOR:%d:%s] New epoch counter %llu\n",
-                             connector->base.id,
-                             connector->name,
-                             connector->epoch_counter);
-
-               /*
-                * Check if epoch counter had changed, meaning that we need
-                * to send a uevent.
-                */
-               if (old_epoch_counter != connector->epoch_counter)
+               if (check_connector_changed(connector))
                        changed = true;
-
        }
        drm_connector_list_iter_end(&conn_iter);
        mutex_unlock(&dev->mode_config.mutex);
index c9b051a..08e0347 100644 (file)
@@ -10821,6 +10821,19 @@ static int intel_atomic_commit(struct drm_device *dev,
                                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,
index 926ddc6..ceff358 100644 (file)
@@ -730,7 +730,7 @@ intel_hdmi_compute_avi_infoframe(struct intel_encoder *encoder,
        else
                frame->colorspace = HDMI_COLORSPACE_RGB;
 
-       drm_hdmi_avi_infoframe_colorspace(frame, conn_state);
+       drm_hdmi_avi_infoframe_colorimetry(frame, conn_state);
 
        /* nonsense combination */
        drm_WARN_ON(encoder->base.dev, crtc_state->limited_color_range &&
index 05d2d75..092a925 100644 (file)
@@ -537,7 +537,7 @@ void lspcon_set_infoframes(struct intel_encoder *encoder,
                frame.avi.colorspace = HDMI_COLORSPACE_RGB;
 
        /* Set the Colorspace as per the HDMI spec */
-       drm_hdmi_avi_infoframe_colorspace(&frame.avi, conn_state);
+       drm_hdmi_avi_infoframe_colorimetry(&frame.avi, conn_state);
 
        /* nonsense combination */
        drm_WARN_ON(encoder->base.dev, crtc_state->limited_color_range &&
index fab09e7..3f91a94 100644 (file)
@@ -246,6 +246,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:
index af1402d..a483351 100644 (file)
@@ -139,6 +139,17 @@ config DRM_PANEL_ILITEK_ILI9341
          QVGA (240x320) RGB panels. support serial & parallel rgb
          interface.
 
+config DRM_PANEL_ILITEK_ILI9806E
+       tristate "Ilitek ILI9806E-based panels"
+       depends on OF && SPI
+       depends on DRM_KMS_HELPER
+       depends on DRM_KMS_CMA_HELPER
+       depends on BACKLIGHT_CLASS_DEVICE
+       select DRM_MIPI_DBI
+       help
+         Say Y if you want to enable support for panels based on the
+         Ilitek ILI9806e controller.
+
 config DRM_PANEL_ILITEK_ILI9881C
        tristate "Ilitek ILI9881C-based panels"
        depends on OF
@@ -549,6 +560,17 @@ config DRM_PANEL_SONY_ACX565AKM
          Say Y here if you want to enable support for the Sony ACX565AKM
          800x600 3.5" panel (found on the Nokia N900).
 
+config DRM_PANEL_TPO_Y17P
+       tristate "TDO Y17P-based panels"
+       depends on OF && SPI
+       depends on DRM_KMS_HELPER
+       depends on DRM_KMS_CMA_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_TDO_TL070WSH30
        tristate "TDO TL070WSH30 DSI panel"
        depends on OF
index c813205..b7810a7 100644 (file)
@@ -12,6 +12,7 @@ obj-$(CONFIG_DRM_PANEL_FEIXIN_K101_IM2BA02) += panel-feixin-k101-im2ba02.o
 obj-$(CONFIG_DRM_PANEL_FEIYANG_FY07024DI26A30D) += panel-feiyang-fy07024di26a30d.o
 obj-$(CONFIG_DRM_PANEL_ILITEK_IL9322) += panel-ilitek-ili9322.o
 obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9341) += panel-ilitek-ili9341.o
+obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9806E) += panel-ilitek-ili9806e.o
 obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9881C) += panel-ilitek-ili9881c.o
 obj-$(CONFIG_DRM_PANEL_INNOLUX_EJ030NA) += panel-innolux-ej030na.o
 obj-$(CONFIG_DRM_PANEL_INNOLUX_P079ZCA) += panel-innolux-p079zca.o
@@ -57,6 +58,7 @@ obj-$(CONFIG_DRM_PANEL_SITRONIX_ST7789V) += panel-sitronix-st7789v.o
 obj-$(CONFIG_DRM_PANEL_SONY_ACX424AKP) += panel-sony-acx424akp.o
 obj-$(CONFIG_DRM_PANEL_SONY_ACX565AKM) += panel-sony-acx565akm.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
diff --git a/drivers/gpu/drm/panel/panel-ilitek-ili9806e.c b/drivers/gpu/drm/panel/panel-ilitek-ili9806e.c
new file mode 100644 (file)
index 0000000..89058f0
--- /dev/null
@@ -0,0 +1,486 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Ilitek ILI9806E TFT LCD drm_panel driver.
+ *
+ * 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_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 ili9806 {
+       struct drm_panel panel;
+       struct spi_device *spi;
+       struct gpio_desc *reset;
+       struct regulator *power;
+       u32 bus_format;
+};
+
+#define ILI9806_DATA           BIT(8)
+
+#define ILI9806_MAX_MSG_LEN    6
+
+struct ili9806e_msg {
+       unsigned int len;
+       u16 msg[ILI9806_MAX_MSG_LEN];
+};
+
+#define ILI9806_SET_PAGE(page) \
+       {                               \
+               .len = 6,               \
+               .msg = {                \
+                       0xFF,                   \
+                       ILI9806_DATA | 0xFF,    \
+                       ILI9806_DATA | 0x98,    \
+                       ILI9806_DATA | 0x06,    \
+                       ILI9806_DATA | 0x04,    \
+                       ILI9806_DATA | (page)   \
+               },                              \
+       }
+
+#define ILI9806_SET_REG_PARAM(reg, data)       \
+       {                                       \
+               .len = 2,                       \
+               .msg = {                        \
+                       (reg),                  \
+                       ILI9806_DATA | (data),  \
+               },                              \
+       }
+
+#define ILI9806_SET_REG(reg)   \
+       {                               \
+               .len = 1,               \
+               .msg = { (reg) },               \
+       }
+
+static const struct ili9806e_msg panel_init[] = {
+       ILI9806_SET_PAGE(1),
+
+       /* interface mode
+        *   SEPT_SDIO = 0 (spi interface transfer through SDA pin)
+        *   SDO_STATUS = 1 (always output, but without output tri-state)
+        */
+       ILI9806_SET_REG_PARAM(0x08, 0x10),
+       /* display control
+        * VSPL = 1 (vertical sync polarity)
+        * HSPL = 0 (horizontal sync polarity)
+        * DPL = 0 (PCLK polarity)
+        * EPL = 1 (data enable polarity)
+        */
+       ILI9806_SET_REG_PARAM(0x21, 0x0d),
+       /* resolution control (0x02 = 480x800) */
+       ILI9806_SET_REG_PARAM(0x30, 0x02),
+       /* display inversion control (0x00 = column inversion) */
+       ILI9806_SET_REG_PARAM(0x31, 0x00),
+       /* power control
+        *  EXB1T = 0 (internal charge pump)
+        *  EXT_CPCK_SEL = 1 (pump clock control signal = output 2 x waveform)
+        *  BT = 0 (DDVDH / DDVDL voltage = VCI x 2 / VCI x -2)
+        */
+       ILI9806_SET_REG_PARAM(0x40, 0x10),
+       /* power control
+        *  DDVDH_CLP = 5.6 (DDVDH clamp leve)
+        *  DDVDL_CLP = -5.6 (DDVDL clamp leve)
+        */
+       ILI9806_SET_REG_PARAM(0x41, 0x55),
+       /* power control
+        *  VGH_CP = 2DDVDH - DDVDL (step up factor for VGH)
+        *  VGL_CP = DDVDL + VCL - VCIP (step up factor for VGL)
+        */
+       ILI9806_SET_REG_PARAM(0x42, 0x02),
+       /* power control
+        *  VGH_CLPEN = 0 (disable VGH clamp level)
+        *  VGH_CLP = 9 (15.0 VGH clamp level - but this is disabled so not used?)
+        */
+       ILI9806_SET_REG_PARAM(0x43, 0x84),
+       /* power control
+        *  VGL_CLPEN = 0 (disable VGL clamp level)
+        *  VGL_CLP = 9 (-11.0 VGL clamp level - but this is disabled so not used?)
+        */
+       ILI9806_SET_REG_PARAM(0x44, 0x84),
+
+       /* power control
+        *  VREG1OUT voltage for positive gamma?
+        */
+       ILI9806_SET_REG_PARAM(0x50, 0x78),
+       /* power control
+        *  VREG2OUT voltage for negative gamma?
+        */
+       ILI9806_SET_REG_PARAM(0x51, 0x78),
+
+       ILI9806_SET_REG_PARAM(0x52, 0x00),
+       ILI9806_SET_REG_PARAM(0x53, 0x77),
+       ILI9806_SET_REG_PARAM(0x57, 0x60),
+       ILI9806_SET_REG_PARAM(0x60, 0x07),
+       ILI9806_SET_REG_PARAM(0x61, 0x00),
+       ILI9806_SET_REG_PARAM(0x62, 0x08),
+       ILI9806_SET_REG_PARAM(0x63, 0x00),
+       ILI9806_SET_REG_PARAM(0xA0, 0x00),
+       ILI9806_SET_REG_PARAM(0xA1, 0x07),
+       ILI9806_SET_REG_PARAM(0xA2, 0x0C),
+       ILI9806_SET_REG_PARAM(0xA3, 0x0B),
+       ILI9806_SET_REG_PARAM(0xA4, 0x03),
+       ILI9806_SET_REG_PARAM(0xA5, 0x07),
+       ILI9806_SET_REG_PARAM(0xA6, 0x06),
+       ILI9806_SET_REG_PARAM(0xA7, 0x04),
+       ILI9806_SET_REG_PARAM(0xA8, 0x08),
+       ILI9806_SET_REG_PARAM(0xA9, 0x0C),
+       ILI9806_SET_REG_PARAM(0xAA, 0x13),
+       ILI9806_SET_REG_PARAM(0xAB, 0x06),
+       ILI9806_SET_REG_PARAM(0xAC, 0x0D),
+       ILI9806_SET_REG_PARAM(0xAD, 0x19),
+       ILI9806_SET_REG_PARAM(0xAE, 0x10),
+       ILI9806_SET_REG_PARAM(0xAF, 0x00),
+       /* negative gamma control
+        * set the gray scale voltage to adjust the gamma characteristics of the panel
+        */
+       ILI9806_SET_REG_PARAM(0xC0, 0x00),
+       ILI9806_SET_REG_PARAM(0xC1, 0x07),
+       ILI9806_SET_REG_PARAM(0xC2, 0x0C),
+       ILI9806_SET_REG_PARAM(0xC3, 0x0B),
+       ILI9806_SET_REG_PARAM(0xC4, 0x03),
+       ILI9806_SET_REG_PARAM(0xC5, 0x07),
+       ILI9806_SET_REG_PARAM(0xC6, 0x07),
+       ILI9806_SET_REG_PARAM(0xC7, 0x04),
+       ILI9806_SET_REG_PARAM(0xC8, 0x08),
+       ILI9806_SET_REG_PARAM(0xC9, 0x0C),
+       ILI9806_SET_REG_PARAM(0xCA, 0x13),
+       ILI9806_SET_REG_PARAM(0xCB, 0x06),
+       ILI9806_SET_REG_PARAM(0xCC, 0x0D),
+       ILI9806_SET_REG_PARAM(0xCD, 0x18),
+       ILI9806_SET_REG_PARAM(0xCE, 0x10),
+       ILI9806_SET_REG_PARAM(0xCF, 0x00),
+
+       ILI9806_SET_PAGE(6),
+
+       ILI9806_SET_REG_PARAM(0x00, 0x20),
+       ILI9806_SET_REG_PARAM(0x01, 0x0A),
+       ILI9806_SET_REG_PARAM(0x02, 0x00),
+       ILI9806_SET_REG_PARAM(0x03, 0x00),
+       ILI9806_SET_REG_PARAM(0x04, 0x01),
+       ILI9806_SET_REG_PARAM(0x05, 0x01),
+       ILI9806_SET_REG_PARAM(0x06, 0x98),
+       ILI9806_SET_REG_PARAM(0x07, 0x06),
+       ILI9806_SET_REG_PARAM(0x08, 0x01),
+       ILI9806_SET_REG_PARAM(0x09, 0x80),
+       ILI9806_SET_REG_PARAM(0x0A, 0x00),
+       ILI9806_SET_REG_PARAM(0x0B, 0x00),
+       ILI9806_SET_REG_PARAM(0x0C, 0x01),
+       ILI9806_SET_REG_PARAM(0x0D, 0x01),
+       ILI9806_SET_REG_PARAM(0x0E, 0x00),
+       ILI9806_SET_REG_PARAM(0x0F, 0x00),
+       ILI9806_SET_REG_PARAM(0x10, 0xF0),
+       ILI9806_SET_REG_PARAM(0x11, 0xF4),
+       ILI9806_SET_REG_PARAM(0x12, 0x01),
+       ILI9806_SET_REG_PARAM(0x13, 0x00),
+       ILI9806_SET_REG_PARAM(0x14, 0x00),
+       ILI9806_SET_REG_PARAM(0x15, 0xC0),
+       ILI9806_SET_REG_PARAM(0x16, 0x08),
+       ILI9806_SET_REG_PARAM(0x17, 0x00),
+       ILI9806_SET_REG_PARAM(0x18, 0x00),
+       ILI9806_SET_REG_PARAM(0x19, 0x00),
+       ILI9806_SET_REG_PARAM(0x1A, 0x00),
+       ILI9806_SET_REG_PARAM(0x1B, 0x00),
+       ILI9806_SET_REG_PARAM(0x1C, 0x00),
+       ILI9806_SET_REG_PARAM(0x1D, 0x00),
+       ILI9806_SET_REG_PARAM(0x20, 0x01),
+       ILI9806_SET_REG_PARAM(0x21, 0x23),
+       ILI9806_SET_REG_PARAM(0x22, 0x45),
+       ILI9806_SET_REG_PARAM(0x23, 0x67),
+       ILI9806_SET_REG_PARAM(0x24, 0x01),
+       ILI9806_SET_REG_PARAM(0x25, 0x23),
+       ILI9806_SET_REG_PARAM(0x26, 0x45),
+       ILI9806_SET_REG_PARAM(0x27, 0x67),
+       ILI9806_SET_REG_PARAM(0x30, 0x11),
+       ILI9806_SET_REG_PARAM(0x31, 0x11),
+       ILI9806_SET_REG_PARAM(0x32, 0x00),
+       ILI9806_SET_REG_PARAM(0x33, 0xEE),
+       ILI9806_SET_REG_PARAM(0x34, 0xFF),
+       ILI9806_SET_REG_PARAM(0x35, 0xBB),
+       ILI9806_SET_REG_PARAM(0x36, 0xAA),
+       ILI9806_SET_REG_PARAM(0x37, 0xDD),
+       ILI9806_SET_REG_PARAM(0x38, 0xCC),
+       ILI9806_SET_REG_PARAM(0x39, 0x66),
+       ILI9806_SET_REG_PARAM(0x3A, 0x77),
+       ILI9806_SET_REG_PARAM(0x3B, 0x22),
+       ILI9806_SET_REG_PARAM(0x3C, 0x22),
+       ILI9806_SET_REG_PARAM(0x3D, 0x22),
+       ILI9806_SET_REG_PARAM(0x3E, 0x22),
+       ILI9806_SET_REG_PARAM(0x3F, 0x22),
+       ILI9806_SET_REG_PARAM(0x40, 0x22),
+       /* register doesn't exist on page 6? */
+       ILI9806_SET_REG_PARAM(0x52, 0x10),
+       /* doesn't make sense, not valid according to datasheet */
+       ILI9806_SET_REG_PARAM(0x53, 0x10),
+       /* doesn't make sense, not valid according to datasheet */
+       ILI9806_SET_REG_PARAM(0x54, 0x13),
+
+       ILI9806_SET_PAGE(7),
+
+       /* enable VREG */
+       ILI9806_SET_REG_PARAM(0x18, 0x1D),
+       /* enable VGL_REG */
+       ILI9806_SET_REG_PARAM(0x17, 0x22),
+       /* register doesn't exist on page 7? */
+       ILI9806_SET_REG_PARAM(0x02, 0x77),
+       /* register doesn't exist on page 7? */
+       ILI9806_SET_REG_PARAM(0x26, 0xB2),
+       /* register doesn't exist on page 7? */
+       ILI9806_SET_REG_PARAM(0xE1, 0x79),
+
+       ILI9806_SET_PAGE(0),
+
+       ILI9806_SET_REG_PARAM(MIPI_DCS_SET_PIXEL_FORMAT,
+                             MIPI_DCS_PIXEL_FMT_18BIT << 4),
+       ILI9806_SET_REG_PARAM(MIPI_DCS_SET_TEAR_ON, 0x00),
+       ILI9806_SET_REG(MIPI_DCS_EXIT_SLEEP_MODE),
+};
+
+#define NUM_INIT_REGS ARRAY_SIZE(panel_init)
+
+static inline struct ili9806 *panel_to_ili9806(struct drm_panel *panel)
+{
+       return container_of(panel, struct ili9806, panel);
+}
+
+static int ili9806_write_msg(struct ili9806 *ctx, const struct ili9806e_msg *msg)
+{
+       struct spi_transfer xfer = { };
+       struct spi_message spi;
+       //u16 txbuf[] = { msg->, ILI9806_DATA | data };
+
+       spi_message_init(&spi);
+
+       xfer.tx_buf = msg->msg;
+       xfer.bits_per_word = 9;
+       xfer.len = sizeof(u16) * msg->len;
+
+       spi_message_add_tail(&xfer, &spi);
+       return spi_sync(ctx->spi, &spi);
+}
+
+static int ili9806e_write_msg_list(struct ili9806 *ctx,
+                                  const struct ili9806e_msg msgs[],
+                                  unsigned int num_msgs)
+{
+       int ret, i;
+
+       for (i = 0; i < num_msgs; i++) {
+               ret = ili9806_write_msg(ctx, &msgs[i]);
+               if (ret)
+                       break;
+       }
+
+       return ret;
+}
+
+static const struct drm_display_mode ili9806e_480x800_mode = {
+       .clock = 32000,
+       .hdisplay = 480,
+       .hsync_start = 480 + 10,
+       .hsync_end = 480 + 10 + 16,
+       .htotal = 480 + 10 + 16 + 59,
+       .vdisplay = 800,
+       .vsync_start = 800 + 15,
+       .vsync_end = 800 + 15 + 113,
+       .vtotal = 800 + 15 + 113 + 15,
+       .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
+};
+
+static int ili9806_get_modes(struct drm_panel *panel,
+                            struct drm_connector *connector)
+{
+       struct ili9806 *ctx = panel_to_ili9806(panel);
+       struct drm_display_mode *mode;
+
+       mode = drm_mode_duplicate(connector->dev, &ili9806e_480x800_mode);
+       if (!mode) {
+               dev_err(panel->dev, "failed to add mode %ux%ux@%u\n",
+                       ili9806e_480x800_mode.hdisplay,
+                       ili9806e_480x800_mode.vdisplay,
+                       drm_mode_vrefresh(&ili9806e_480x800_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 = 61;
+       connector->display_info.height_mm = 103;
+       drm_display_info_set_bus_formats(&connector->display_info,
+                                        &ctx->bus_format, 1);
+       connector->display_info.bus_flags =
+                                       DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE;
+
+       return 1;
+}
+
+static int ili9806_prepare(struct drm_panel *panel)
+{
+       struct ili9806 *ctx = panel_to_ili9806(panel);
+       int ret;
+
+       ret = regulator_enable(ctx->power);
+       if (ret)
+               return ret;
+
+       ret = ili9806e_write_msg_list(ctx, panel_init, NUM_INIT_REGS);
+
+       return ret;
+}
+
+static int ili9806_enable(struct drm_panel *panel)
+{
+       struct ili9806 *ctx = panel_to_ili9806(panel);
+       const struct ili9806e_msg msg = ILI9806_SET_REG(MIPI_DCS_SET_DISPLAY_ON);
+       int ret;
+
+       ret = ili9806_write_msg(ctx, &msg);
+
+       return ret;
+}
+
+static int ili9806_disable(struct drm_panel *panel)
+{
+       struct ili9806 *ctx = panel_to_ili9806(panel);
+       const struct ili9806e_msg msg = ILI9806_SET_REG(MIPI_DCS_SET_DISPLAY_OFF);
+       int ret;
+
+       ret = ili9806_write_msg(ctx, &msg);
+
+       return ret;
+}
+
+static int ili9806_unprepare(struct drm_panel *panel)
+{
+       struct ili9806 *ctx = panel_to_ili9806(panel);
+       const struct ili9806e_msg msg = ILI9806_SET_REG(MIPI_DCS_ENTER_SLEEP_MODE);
+       int ret;
+
+       ret = ili9806_write_msg(ctx, &msg);
+
+       return ret;
+}
+
+static const struct drm_panel_funcs ili9806_drm_funcs = {
+       .disable        = ili9806_disable,
+       .enable         = ili9806_enable,
+       .get_modes      = ili9806_get_modes,
+       .prepare        = ili9806_prepare,
+       .unprepare      = ili9806_unprepare,
+};
+
+static const struct of_device_id ili9806_of_match[] = {
+       {       .compatible = "txw,txw397017s2",
+               .data = (void *)MEDIA_BUS_FMT_RGB888_1X24,
+       }, {
+               .compatible = "pimoroni,hyperpixel4",
+               .data = (void *)MEDIA_BUS_FMT_RGB666_1X24_CPADHI,
+       }, {
+               .compatible = "ilitek,ili9806e",
+               .data = (void *)MEDIA_BUS_FMT_RGB888_1X24,
+       }, {
+               /* sentinel */
+       }
+};
+MODULE_DEVICE_TABLE(of, ili9806_of_match);
+
+static int ili9806_probe(struct spi_device *spi)
+{
+       const struct ili9806e_msg panel_reset[] = {
+               ILI9806_SET_PAGE(0),
+               ILI9806_SET_REG_PARAM(0x01, 0x00)
+       };
+       const struct of_device_id *id;
+       struct ili9806 *ctx;
+       int ret;
+
+       ctx = devm_kzalloc(&spi->dev, sizeof(*ctx), GFP_KERNEL);
+       if (!ctx)
+               return -ENOMEM;
+
+       id = of_match_node(ili9806_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, &ili9806_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);
+       }
+
+       /* Soft reset */
+       ili9806e_write_msg_list(ctx, panel_reset, ARRAY_SIZE(panel_reset));
+       msleep(200);
+
+       ret = drm_panel_of_backlight(&ctx->panel);
+       if (ret)
+               return ret;
+
+       drm_panel_add(&ctx->panel);
+
+       return 0;
+}
+
+static int ili9806_remove(struct spi_device *spi)
+{
+       struct ili9806 *ctx = spi_get_drvdata(spi);
+
+       drm_panel_remove(&ctx->panel);
+
+       return 0;
+}
+
+static const struct spi_device_id ili9806_ids[] = {
+       { "txw397017s2", 0 },
+       { "ili9806e", 0 },
+       { "hyperpixel4", 0 },
+       { /* sentinel */ }
+};
+
+MODULE_DEVICE_TABLE(spi, ili9806_ids);
+
+static struct spi_driver ili9806_driver = {
+       .probe = ili9806_probe,
+       .remove = ili9806_remove,
+       .driver = {
+               .name = "ili9806e",
+               .of_match_table = ili9806_of_match,
+       },
+       .id_table = ili9806_ids,
+};
+module_spi_driver(ili9806_driver);
+
+MODULE_AUTHOR("Dave Stevenson <dave.stevenson@raspberrypi.com>");
+MODULE_DESCRIPTION("ili9806 LCD panel driver");
+MODULE_LICENSE("GPL v2");
index 534dd74..c2afc43 100644 (file)
@@ -1,6 +1,8 @@
 // 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>
  */
 
 #include <linux/delay.h>
@@ -42,6 +44,7 @@ struct ili9881c_desc {
        const struct ili9881c_instr *init;
        const size_t init_length;
        const struct drm_display_mode *mode;
+       const unsigned flags;
 };
 
 struct ili9881c {
@@ -453,6 +456,225 @@ static const struct ili9881c_instr k101_im2byl02_init[] = {
        ILI9881C_COMMAND_INSTR(0xD3, 0x3F), /* VN0 */
 };
 
+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),
+       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),
+
+       //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),
+       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),
+       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),
+
+       ILI9881C_SWITCH_PAGE_INSTR(4),
+       ILI9881C_COMMAND_INSTR(0x6C, 0x15),
+       ILI9881C_COMMAND_INSTR(0x6E, 0x2A),
+
+       //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),
+
+       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),
+
+       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),
+
+       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),
+
+       ILI9881C_SWITCH_PAGE_INSTR(0),
+       //PWM
+       ILI9881C_COMMAND_INSTR(0x51, 0x0F),
+       ILI9881C_COMMAND_INSTR(0x52, 0xFF),
+       ILI9881C_COMMAND_INSTR(0x53, 0x2C),
+
+       ILI9881C_COMMAND_INSTR(0x11, 0x00),
+       ILI9881C_COMMAND_INSTR(0x29, 0x00),
+       ILI9881C_COMMAND_INSTR(0x35, 0x00),
+};
+
 static inline struct ili9881c *panel_to_ili9881c(struct drm_panel *panel)
 {
        return container_of(panel, struct ili9881c, panel);
@@ -603,6 +825,23 @@ static const struct drm_display_mode k101_im2byl02_default_mode = {
        .height_mm      = 217,
 };
 
+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 int ili9881c_get_modes(struct drm_panel *panel,
                              struct drm_connector *connector)
 {
@@ -649,6 +888,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_upstream_first = true;
        drm_panel_init(&ctx->panel, &dsi->dev, &ili9881c_funcs,
                       DRM_MODE_CONNECTOR_DSI);
 
@@ -670,11 +910,15 @@ static int ili9881c_dsi_probe(struct mipi_dsi_device *dsi)
 
        drm_panel_add(&ctx->panel);
 
-       dsi->mode_flags = MIPI_DSI_MODE_VIDEO_SYNC_PULSE;
+       dsi->mode_flags = ctx->desc->flags;
        dsi->format = MIPI_DSI_FMT_RGB888;
        dsi->lanes = 4;
 
-       return mipi_dsi_attach(dsi);
+       ret = mipi_dsi_attach(dsi);
+       if (ret)
+               drm_panel_remove(&ctx->panel);
+
+       return ret;
 }
 
 static int ili9881c_dsi_remove(struct mipi_dsi_device *dsi)
@@ -691,18 +935,28 @@ static const struct ili9881c_desc lhr050h41_desc = {
        .init = lhr050h41_init,
        .init_length = ARRAY_SIZE(lhr050h41_init),
        .mode = &lhr050h41_default_mode,
+       .flags = MIPI_DSI_MODE_VIDEO_SYNC_PULSE,
 };
 
 static const struct ili9881c_desc k101_im2byl02_desc = {
        .init = k101_im2byl02_init,
        .init_length = ARRAY_SIZE(k101_im2byl02_init),
        .mode = &k101_im2byl02_default_mode,
+       .flags = MIPI_DSI_MODE_VIDEO_SYNC_PULSE,
+};
+
+static const struct ili9881c_desc nwe080_desc = {
+       .init = nwe080_init,
+       .init_length = ARRAY_SIZE(nwe080_init),
+       .mode = &nwe080_default_mode,
+       .flags = MIPI_DSI_MODE_VIDEO_SYNC_PULSE | MIPI_DSI_MODE_VIDEO,
 };
 
 static const struct of_device_id ili9881c_of_match[] = {
        { .compatible = "bananapi,lhr050h41", .data = &lhr050h41_desc },
        { .compatible = "feixin,k101-im2byl02", .data = &k101_im2byl02_desc },
-       { }
+       { .compatible = "nwe,nwe080", .data = &nwe080_desc },
+       {}
 };
 MODULE_DEVICE_TABLE(of, ili9881c_of_match);
 
index 3c86ad2..ff827cc 100644 (file)
@@ -205,11 +205,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);
 
        jdi->prepared = false;
 
@@ -233,13 +233,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);
@@ -263,11 +263,11 @@ poweroff:
        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;
 }
index 145047e..329341b 100644 (file)
@@ -219,7 +219,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,
@@ -268,12 +296,14 @@ 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;
 
        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;
        }
 
index 8dd7013..8f7e4af 100644 (file)
@@ -195,8 +195,6 @@ struct panel_simple {
        struct edid *edid;
 
        struct drm_display_mode override_mode;
-
-       enum drm_panel_orientation orientation;
 };
 
 static inline struct panel_simple *to_panel_simple(struct drm_panel *panel)
@@ -533,9 +531,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);
 
-       /* set up connector's "panel orientation" property */
-       drm_connector_set_panel_orientation(connector, p->orientation);
-
        return num;
 }
 
@@ -598,6 +593,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;
@@ -607,6 +603,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;
 
@@ -699,12 +697,6 @@ static int panel_simple_probe(struct device *dev, const struct panel_desc *desc,
                return err;
        }
 
-       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);
@@ -2318,6 +2310,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,
@@ -2472,6 +2490,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,
@@ -3727,6 +3777,31 @@ static const struct panel_desc qishenglong_gopher2b_lcd = {
        .connector_type = DRM_MODE_CONNECTOR_DPI,
 };
 
+static const struct drm_display_mode raspberrypi_7inch_mode = {
+       .clock = 25979400 / 1000,
+       .hdisplay = 800,
+       .hsync_start = 800 + 2,
+       .hsync_end = 800 + 2 + 2,
+       .htotal = 800 + 2 + 2 + 46,
+       .vdisplay = 480,
+       .vsync_start = 480 + 7,
+       .vsync_end = 480 + 7 + 2,
+       .vtotal = 480 + 7 + 2 + 21,
+       .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 },
@@ -4642,6 +4717,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
        }, {
@@ -4660,6 +4738,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,
        }, {
@@ -4804,6 +4885,9 @@ static const struct of_device_id platform_of_match[] = {
                .compatible = "qishenglong,gopher2b-lcd",
                .data = &qishenglong_gopher2b_lcd,
        }, {
+               .compatible = "raspberrypi,7inch-dsi",
+               .data = &raspberrypi_7inch,
+       }, {
                .compatible = "rocktech,rk070er9427",
                .data = &rocktech_rk070er9427,
        }, {
index 320a2a8..0744f68 100644 (file)
@@ -7,15 +7,20 @@
 #include <drm/drm_mipi_dsi.h>
 #include <drm/drm_modes.h>
 #include <drm/drm_panel.h>
+#include <drm/drm_print.h>
 
 #include <linux/gpio/consumer.h>
 #include <linux/delay.h>
+#include <linux/media-bus-format.h>
 #include <linux/module.h>
 #include <linux/of_device.h>
 #include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
 
 #include <video/mipi_display.h>
 
+#define SPI_DATA_FLAG                  0x100
+
 /* Command2 BKx selection command */
 #define DSI_CMD2BKX_SEL                        0xFF
 
  * 11 = CMD2BK1, Command2 BK1
  * 00 = Command2 disable
  */
+#define DSI_CMD2BK3_SEL                        0x13
 #define DSI_CMD2BK1_SEL                        0x11
 #define DSI_CMD2BK0_SEL                        0x10
 #define DSI_CMD2BKX_SEL_NONE           0x00
+#define SPI_CMD2BK3_SEL                        (SPI_DATA_FLAG | DSI_CMD2BK3_SEL)
+#define SPI_CMD2BK1_SEL                        (SPI_DATA_FLAG | DSI_CMD2BK1_SEL)
+#define SPI_CMD2BK0_SEL                        (SPI_DATA_FLAG | DSI_CMD2BK0_SEL)
+#define SPI_CMD2BKX_SEL_NONE           (SPI_DATA_FLAG | DSI_CMD2BKX_SEL_NONE)
 
 /* Command2, BK0 bytes */
 #define DSI_LINESET_LINE               0x69
 #define DSI_MIPISET1_EOT_EN            BIT(3)
 #define DSI_CMD2_BK1_MIPISET1_SET      (BIT(7) | DSI_MIPISET1_EOT_EN)
 
+struct st7701;
+
+enum st7701_ctrl_if {
+       ST7701_CTRL_DSI,
+       ST7701_CTRL_SPI,
+};
+
 struct st7701_panel_desc {
        const struct drm_display_mode *mode;
        unsigned int lanes;
        unsigned long flags;
        enum mipi_dsi_pixel_format format;
+       u32 mediabus_format;
        const char *const *supply_names;
        unsigned int num_supplies;
        unsigned int panel_sleep_delay;
+       void (*init_sequence)(struct st7701 *st7701);
+       unsigned int conn_type;
+       enum st7701_ctrl_if interface;
+       u32 bus_flags;
 };
 
 struct st7701 {
        struct drm_panel panel;
        struct mipi_dsi_device *dsi;
+       struct spi_device *spi;
+       const struct device *dev;
+
        const struct st7701_panel_desc *desc;
 
        struct regulator_bulk_data *supplies;
@@ -123,7 +148,23 @@ static inline int st7701_dsi_write(struct st7701 *st7701, const void *seq,
                st7701_dsi_write(st7701, d, ARRAY_SIZE(d));     \
        }
 
-static void st7701_init_sequence(struct st7701 *st7701)
+#define ST7701_SPI(st7701, seq...)                             \
+       {                                                       \
+               const u16 d[] = { seq };                        \
+               struct spi_transfer xfer = { };                 \
+               struct spi_message spi;                         \
+                                                               \
+               spi_message_init(&spi);                         \
+                                                               \
+               xfer.tx_buf = d;                                \
+               xfer.bits_per_word = 9;                         \
+               xfer.len = sizeof(u16) * ARRAY_SIZE(d);         \
+                                                               \
+               spi_message_add_tail(&xfer, &spi);              \
+               spi_sync((st7701)->spi, &spi);                  \
+       }
+
+static void ts8550b_init_sequence(struct st7701 *st7701)
 {
        const struct drm_display_mode *mode = st7701->desc->mode;
 
@@ -194,6 +235,111 @@ static void st7701_init_sequence(struct st7701 *st7701)
                   0x77, 0x01, 0x00, 0x00, DSI_CMD2BKX_SEL_NONE);
 }
 
+static void txw210001b0_init_sequence(struct st7701 *st7701)
+{
+       ST7701_SPI(st7701, MIPI_DCS_SOFT_RESET);
+
+       usleep_range(5000, 7000);
+
+       ST7701_SPI(st7701, DSI_CMD2BKX_SEL,
+                  0x177, 0x101, 0x100, 0x100, SPI_CMD2BK0_SEL);
+
+       ST7701_SPI(st7701, DSI_CMD2_BK0_LNESET, 0x13B, 0x100);
+
+       ST7701_SPI(st7701, DSI_CMD2_BK0_PORCTRL, 0x10B, 0x102);
+
+       ST7701_SPI(st7701, DSI_CMD2_BK0_INVSEL, 0x100, 0x102);
+
+       ST7701_SPI(st7701, 0xCC, 0x110);
+
+       /*
+        * Gamma option B:
+        * Positive Voltage Gamma Control
+        */
+       ST7701_SPI(st7701, DSI_CMD2_BK0_PVGAMCTRL,
+                  0x102, 0x113, 0x11B, 0x10D, 0x110, 0x105, 0x108, 0x107,
+                  0x107, 0x124, 0x104, 0x111, 0x10E, 0x12C, 0x133, 0x11D);
+
+       /* Negative Voltage Gamma Control */
+       ST7701_SPI(st7701, DSI_CMD2_BK0_NVGAMCTRL,
+                  0x105, 0x113, 0x11B, 0x10D, 0x111, 0x105, 0x108, 0x107,
+                  0x107, 0x124, 0x104, 0x111, 0x10E, 0x12C, 0x133, 0x11D);
+
+       ST7701_SPI(st7701, DSI_CMD2BKX_SEL,
+                  0x177, 0x101, 0x100, 0x100, SPI_CMD2BK1_SEL);
+
+       ST7701_SPI(st7701, DSI_CMD2_BK1_VRHS, 0x15D);
+
+       ST7701_SPI(st7701, DSI_CMD2_BK1_VCOM, 0x143);
+
+       ST7701_SPI(st7701, DSI_CMD2_BK1_VGHSS, 0x181);
+
+       ST7701_SPI(st7701, DSI_CMD2_BK1_TESTCMD, 0x180);
+
+       ST7701_SPI(st7701, DSI_CMD2_BK1_VGLS, 0x143);
+
+       ST7701_SPI(st7701, DSI_CMD2_BK1_PWCTLR1, 0x185);
+
+       ST7701_SPI(st7701, DSI_CMD2_BK1_PWCTLR2, 0x120);
+
+       ST7701_SPI(st7701, DSI_CMD2_BK1_SPD1, 0x178);
+
+       ST7701_SPI(st7701, DSI_CMD2_BK1_SPD2, 0x178);
+
+       ST7701_SPI(st7701, DSI_CMD2_BK1_MIPISET1, 0x188);
+
+       ST7701_SPI(st7701, 0xE0, 0x100, 0x100, 0x102);
+
+       ST7701_SPI(st7701, 0xE1,
+                  0x103, 0x1A0, 0x100, 0x100, 0x104, 0x1A0, 0x100, 0x100,
+                  0x100, 0x120, 0x120);
+
+       ST7701_SPI(st7701, 0xE2,
+                  0x100, 0x100, 0x100, 0x100, 0x100, 0x100, 0x100, 0x100,
+                  0x100, 0x100, 0x100, 0x100, 0x100);
+
+       ST7701_SPI(st7701, 0xE3, 0x100, 0x100, 0x111, 0x100);
+
+       ST7701_SPI(st7701, 0xE4, 0x122, 0x100);
+
+       ST7701_SPI(st7701, 0xE5,
+                  0x105, 0x1EC, 0x1A0, 0x1A0, 0x107, 0x1EE, 0x1A0, 0x1A0,
+                  0x100, 0x100, 0x100, 0x100, 0x100, 0x100, 0x100, 0x100);
+
+       ST7701_SPI(st7701, 0xE6, 0x100, 0x100, 0x111, 0x100);
+
+       ST7701_SPI(st7701, 0xE7, 0x122, 0x100);
+
+       ST7701_SPI(st7701, 0xE8,
+                  0x106, 0x1ED, 0x1A0, 0x1A0, 0x108, 0x1EF, 0x1A0, 0x1A0,
+                  0x100, 0x100, 0x100, 0x100, 0x100, 0x100, 0x100, 0x100);
+
+       ST7701_SPI(st7701, 0xEB,
+                  0x100, 0x100, 0x140, 0x140, 0x100, 0x100, 0x100);
+
+       ST7701_SPI(st7701, 0xED,
+                  0x1FF, 0x1FF, 0x1FF, 0x1BA, 0x10A, 0x1BF, 0x145, 0x1FF,
+                  0x1FF, 0x154, 0x1FB, 0x1A0, 0x1AB, 0x1FF, 0x1FF, 0x1FF);
+
+       ST7701_SPI(st7701, 0xEF, 0x110, 0x10D, 0x104, 0x108, 0x13F, 0x11F);
+
+       ST7701_SPI(st7701, DSI_CMD2BKX_SEL,
+                  0x177, 0x101, 0x100, 0x100, SPI_CMD2BK3_SEL);
+
+       ST7701_SPI(st7701, 0xEF, 0x108);
+
+       ST7701_SPI(st7701, DSI_CMD2BKX_SEL,
+                  0x177, 0x101, 0x100, 0x100, SPI_CMD2BKX_SEL_NONE);
+
+       ST7701_SPI(st7701, 0xCD, 0x108);  /* RGB format COLCTRL */
+
+       ST7701_SPI(st7701, 0x36, 0x108); /* MadCtl */
+
+       ST7701_SPI(st7701, 0x3A, 0x166);  /* Colmod */
+
+       ST7701_SPI(st7701, MIPI_DCS_EXIT_SLEEP_MODE);
+}
+
 static int st7701_prepare(struct drm_panel *panel)
 {
        struct st7701 *st7701 = panel_to_st7701(panel);
@@ -210,7 +356,7 @@ static int st7701_prepare(struct drm_panel *panel)
        gpiod_set_value(st7701->reset, 1);
        msleep(150);
 
-       st7701_init_sequence(st7701);
+       st7701->desc->init_sequence(st7701);
 
        return 0;
 }
@@ -219,7 +365,15 @@ static int st7701_enable(struct drm_panel *panel)
 {
        struct st7701 *st7701 = panel_to_st7701(panel);
 
-       ST7701_DSI(st7701, MIPI_DCS_SET_DISPLAY_ON, 0x00);
+       switch (st7701->desc->interface) {
+       case ST7701_CTRL_DSI:
+               ST7701_DSI(st7701, MIPI_DCS_SET_DISPLAY_ON, 0x00);
+               break;
+       case ST7701_CTRL_SPI:
+               ST7701_SPI(st7701, MIPI_DCS_SET_DISPLAY_ON);
+               msleep(30);
+               break;
+       }
 
        return 0;
 }
@@ -228,7 +382,14 @@ static int st7701_disable(struct drm_panel *panel)
 {
        struct st7701 *st7701 = panel_to_st7701(panel);
 
-       ST7701_DSI(st7701, MIPI_DCS_SET_DISPLAY_OFF, 0x00);
+       switch (st7701->desc->interface) {
+       case ST7701_CTRL_DSI:
+               ST7701_DSI(st7701, MIPI_DCS_SET_DISPLAY_OFF, 0x00);
+               break;
+       case ST7701_CTRL_SPI:
+               ST7701_SPI(st7701, MIPI_DCS_SET_DISPLAY_OFF);
+               break;
+       }
 
        return 0;
 }
@@ -237,7 +398,14 @@ static int st7701_unprepare(struct drm_panel *panel)
 {
        struct st7701 *st7701 = panel_to_st7701(panel);
 
-       ST7701_DSI(st7701, MIPI_DCS_ENTER_SLEEP_MODE, 0x00);
+       switch (st7701->desc->interface) {
+       case ST7701_CTRL_DSI:
+               ST7701_DSI(st7701, MIPI_DCS_ENTER_SLEEP_MODE, 0x00);
+               break;
+       case ST7701_CTRL_SPI:
+               ST7701_SPI(st7701, MIPI_DCS_ENTER_SLEEP_MODE);
+               break;
+       }
 
        msleep(st7701->sleep_delay);
 
@@ -268,7 +436,7 @@ static int st7701_get_modes(struct drm_panel *panel,
 
        mode = drm_mode_duplicate(connector->dev, desc_mode);
        if (!mode) {
-               dev_err(&st7701->dsi->dev, "failed to add mode %ux%u@%u\n",
+               dev_err(st7701->dev, "failed to add mode %ux%u@%u\n",
                        desc_mode->hdisplay, desc_mode->vdisplay,
                        drm_mode_vrefresh(desc_mode));
                return -ENOMEM;
@@ -277,9 +445,18 @@ static int st7701_get_modes(struct drm_panel *panel,
        drm_mode_set_name(mode);
        drm_mode_probed_add(connector, mode);
 
+       if (st7701->desc->mediabus_format)
+               drm_display_info_set_bus_formats(&connector->display_info,
+                                                &st7701->desc->mediabus_format,
+                                                1);
+       connector->display_info.bus_flags = 0;
+
        connector->display_info.width_mm = desc_mode->width_mm;
        connector->display_info.height_mm = desc_mode->height_mm;
 
+       if (st7701->desc->bus_flags)
+               connector->display_info.bus_flags = st7701->desc->bus_flags;
+
        return 1;
 }
 
@@ -323,24 +500,68 @@ static const struct st7701_panel_desc ts8550b_desc = {
        .supply_names = ts8550b_supply_names,
        .num_supplies = ARRAY_SIZE(ts8550b_supply_names),
        .panel_sleep_delay = 80, /* panel need extra 80ms for sleep out cmd */
+       .init_sequence = ts8550b_init_sequence,
+       .conn_type = DRM_MODE_CONNECTOR_DSI,
+       .interface = ST7701_CTRL_DSI,
 };
 
-static int st7701_dsi_probe(struct mipi_dsi_device *dsi)
+static const struct drm_display_mode txw210001b0_mode = {
+       .clock          = 19200,
+
+       .hdisplay       = 480,
+       .hsync_start    = 480 + 10,
+       .hsync_end      = 480 + 10 + 16,
+       .htotal         = 480 + 10 + 16 + 56,
+
+       .vdisplay       = 480,
+       .vsync_start    = 480 + 15,
+       .vsync_end      = 480 + 15 + 60,
+       .vtotal         = 480 + 15 + 60 + 15,
+
+       .width_mm       = 53,
+       .height_mm      = 53,
+       .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
+
+       .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
+};
+
+static const struct st7701_panel_desc txw210001b0_desc = {
+       .mode = &txw210001b0_mode,
+       .mediabus_format = MEDIA_BUS_FMT_RGB888_1X24,
+       .supply_names = ts8550b_supply_names,
+       .num_supplies = ARRAY_SIZE(ts8550b_supply_names),
+       .init_sequence = txw210001b0_init_sequence,
+       .conn_type = DRM_MODE_CONNECTOR_DPI,
+       .interface = ST7701_CTRL_SPI,
+       .bus_flags = DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE,
+};
+
+static const struct st7701_panel_desc hyperpixel2r_desc = {
+       .mode = &txw210001b0_mode,
+       .mediabus_format = MEDIA_BUS_FMT_RGB666_1X24_CPADHI,
+       .supply_names = ts8550b_supply_names,
+       .num_supplies = ARRAY_SIZE(ts8550b_supply_names),
+       .init_sequence = txw210001b0_init_sequence,
+       .conn_type = DRM_MODE_CONNECTOR_DPI,
+       .interface = ST7701_CTRL_SPI,
+       .bus_flags = DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE,
+};
+
+static int st7701_probe(struct device *dev, struct st7701 **ret_st7701)
 {
        const struct st7701_panel_desc *desc;
        struct st7701 *st7701;
        int ret, i;
 
-       st7701 = devm_kzalloc(&dsi->dev, sizeof(*st7701), GFP_KERNEL);
+       st7701 = devm_kzalloc(dev, sizeof(*st7701), GFP_KERNEL);
        if (!st7701)
                return -ENOMEM;
 
-       desc = of_device_get_match_data(&dsi->dev);
-       dsi->mode_flags = desc->flags;
-       dsi->format = desc->format;
-       dsi->lanes = desc->lanes;
+       desc = of_device_get_match_data(dev);
+       if (!desc)
+               return -EINVAL;
 
-       st7701->supplies = devm_kcalloc(&dsi->dev, desc->num_supplies,
+       st7701->supplies = devm_kcalloc(dev, desc->num_supplies,
                                        sizeof(*st7701->supplies),
                                        GFP_KERNEL);
        if (!st7701->supplies)
@@ -349,19 +570,19 @@ static int st7701_dsi_probe(struct mipi_dsi_device *dsi)
        for (i = 0; i < desc->num_supplies; i++)
                st7701->supplies[i].supply = desc->supply_names[i];
 
-       ret = devm_regulator_bulk_get(&dsi->dev, desc->num_supplies,
+       ret = devm_regulator_bulk_get(dev, desc->num_supplies,
                                      st7701->supplies);
        if (ret < 0)
                return ret;
 
-       st7701->reset = devm_gpiod_get(&dsi->dev, "reset", GPIOD_OUT_LOW);
+       st7701->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
        if (IS_ERR(st7701->reset)) {
-               dev_err(&dsi->dev, "Couldn't get our reset GPIO\n");
+               dev_err(dev, "Couldn't get our reset GPIO\n");
                return PTR_ERR(st7701->reset);
        }
 
-       drm_panel_init(&st7701->panel, &dsi->dev, &st7701_funcs,
-                      DRM_MODE_CONNECTOR_DSI);
+       drm_panel_init(&st7701->panel, dev, &st7701_funcs,
+                      desc->conn_type);
 
        /**
         * Once sleep out has been issued, ST7701 IC required to wait 120ms
@@ -380,9 +601,30 @@ static int st7701_dsi_probe(struct mipi_dsi_device *dsi)
 
        drm_panel_add(&st7701->panel);
 
+       st7701->desc = desc;
+       st7701->dev = dev;
+
+       *ret_st7701 = st7701;
+
+       return 0;
+}
+
+static int st7701_dsi_probe(struct mipi_dsi_device *dsi)
+{
+       struct st7701 *st7701;
+       int ret;
+
+       ret = st7701_probe(&dsi->dev, &st7701);
+
+       if (ret)
+               return ret;
+
+       dsi->mode_flags = st7701->desc->flags;
+       dsi->format = st7701->desc->format;
+       dsi->lanes = st7701->desc->lanes;
+
        mipi_dsi_set_drvdata(dsi, st7701);
        st7701->dsi = dsi;
-       st7701->desc = desc;
 
        return mipi_dsi_attach(dsi);
 }
@@ -397,21 +639,115 @@ static int st7701_dsi_remove(struct mipi_dsi_device *dsi)
        return 0;
 }
 
-static const struct of_device_id st7701_of_match[] = {
+static const struct of_device_id st7701_dsi_of_match[] = {
        { .compatible = "techstar,ts8550b", .data = &ts8550b_desc },
        { }
 };
-MODULE_DEVICE_TABLE(of, st7701_of_match);
+MODULE_DEVICE_TABLE(of, st7701_dsi_of_match);
 
 static struct mipi_dsi_driver st7701_dsi_driver = {
        .probe          = st7701_dsi_probe,
        .remove         = st7701_dsi_remove,
        .driver = {
                .name           = "st7701",
-               .of_match_table = st7701_of_match,
+               .of_match_table = st7701_dsi_of_match,
+       },
+};
+
+/* SPI display  probe */
+static const struct of_device_id st7701_spi_of_match[] = {
+       {       .compatible = "txw,txw210001b0",
+               .data = &txw210001b0_desc,
+       }, {
+               .compatible = "pimoroni,hyperpixel2round",
+               .data = &hyperpixel2r_desc,
+       }, {
+               /* sentinel */
+       }
+};
+MODULE_DEVICE_TABLE(of, st7701_spi_of_match);
+
+static int st7701_spi_probe(struct spi_device *spi)
+{
+       struct st7701 *st7701;
+       int ret;
+
+       spi->mode = SPI_MODE_3;
+       spi->bits_per_word = 9;
+       ret = spi_setup(spi);
+       if (ret < 0) {
+               dev_err(&spi->dev, "failed to setup SPI: %d\n", ret);
+               return ret;
+       }
+
+       ret = st7701_probe(&spi->dev, &st7701);
+
+       if (ret)
+               return ret;
+
+       spi_set_drvdata(spi, st7701);
+       st7701->spi = spi;
+
+       return 0;
+}
+
+static int st7701_spi_remove(struct spi_device *spi)
+{
+       struct st7701 *ctx = spi_get_drvdata(spi);
+
+       drm_panel_remove(&ctx->panel);
+
+       return 0;
+}
+
+static const struct spi_device_id st7701_spi_ids[] = {
+       { "txw210001b0", 0 },
+       { "hyperpixel2round", 0 },
+       { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(spi, st7701_spi_ids);
+
+static struct spi_driver st7701_spi_driver = {
+       .probe = st7701_spi_probe,
+       .remove = st7701_spi_remove,
+       .driver = {
+               .name = "st7701",
+               .of_match_table = st7701_spi_of_match,
        },
+       .id_table = st7701_spi_ids,
 };
-module_mipi_dsi_driver(st7701_dsi_driver);
+
+static int __init panel_st7701_init(void)
+{
+       int err;
+
+       err = spi_register_driver(&st7701_spi_driver);
+       if (err < 0)
+               return err;
+
+       if (IS_ENABLED(CONFIG_DRM_MIPI_DSI)) {
+               err = mipi_dsi_driver_register(&st7701_dsi_driver);
+               if (err < 0)
+                       goto err_did_spi_register;
+       }
+
+       return 0;
+
+err_did_spi_register:
+       spi_unregister_driver(&st7701_spi_driver);
+
+       return err;
+}
+module_init(panel_st7701_init);
+
+static void __exit panel_st7701_exit(void)
+{
+       if (IS_ENABLED(CONFIG_DRM_MIPI_DSI))
+               mipi_dsi_driver_unregister(&st7701_dsi_driver);
+
+       spi_unregister_driver(&st7701_spi_driver);
+}
+module_exit(panel_st7701_exit);
 
 MODULE_AUTHOR("Jagan Teki <jagan@amarulasolutions.com>");
 MODULE_DESCRIPTION("Sitronix ST7701 LCD Panel Driver");
diff --git a/drivers/gpu/drm/panel/panel-tdo-y17p.c b/drivers/gpu/drm/panel/panel-tdo-y17p.c
new file mode 100644 (file)
index 0000000..074bf0d
--- /dev/null
@@ -0,0 +1,279 @@
+// 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_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 int tdo_y17p_remove(struct spi_device *spi)
+{
+       struct tdo_y17p *ctx = spi_get_drvdata(spi);
+
+       drm_panel_remove(&ctx->panel);
+
+       return 0;
+}
+
+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");
index d31be27..3f3600d 100644 (file)
@@ -51,6 +51,21 @@ config DRM_GM12U320
         This is a KMS driver for projectors which use the GM12U320 chipset
         for video transfer over USB2/3, such as the Acer C120 mini projector.
 
+config DRM_PANEL_MIPI_DBI
+       tristate "DRM support for MIPI DBI compatible panels"
+       depends on DRM && SPI
+       select DRM_KMS_HELPER
+       select DRM_GEM_CMA_HELPER
+       select DRM_MIPI_DBI
+       select BACKLIGHT_CLASS_DEVICE
+       select VIDEOMODE_HELPERS
+       help
+         Say Y here if you want to enable support for MIPI DBI compatible
+         panels. The controller command setup can be provided using a
+         firmware file. For more information see
+         https://github.com/notro/panel-mipi-dbi/wiki.
+         To compile this driver as a module, choose M here.
+
 config DRM_SIMPLEDRM
        tristate "Simple framebuffer driver"
        depends on DRM
index e099428..b7ae58e 100644 (file)
@@ -4,6 +4,7 @@ obj-$(CONFIG_DRM_ARCPGU)                += arcpgu.o
 obj-$(CONFIG_DRM_BOCHS)                        += bochs.o
 obj-$(CONFIG_DRM_CIRRUS_QEMU)          += cirrus.o
 obj-$(CONFIG_DRM_GM12U320)             += gm12u320.o
+obj-$(CONFIG_DRM_PANEL_MIPI_DBI)       += panel-mipi-dbi.o
 obj-$(CONFIG_DRM_SIMPLEDRM)            += simpledrm.o
 obj-$(CONFIG_TINYDRM_HX8357D)          += hx8357d.o
 obj-$(CONFIG_TINYDRM_ILI9225)          += ili9225.o
diff --git a/drivers/gpu/drm/tiny/panel-mipi-dbi.c b/drivers/gpu/drm/tiny/panel-mipi-dbi.c
new file mode 100644 (file)
index 0000000..7f8c6c5
--- /dev/null
@@ -0,0 +1,398 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * DRM driver for MIPI DBI compatible display panels
+ *
+ * Copyright 2022 Noralf Trønnes
+ */
+
+#include <linux/backlight.h>
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_gem_atomic_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_managed.h>
+#include <drm/drm_mipi_dbi.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_modeset_helper.h>
+
+#include <video/mipi_display.h>
+
+static const u8 panel_mipi_dbi_magic[15] = { 'M', 'I', 'P', 'I', ' ', 'D', 'B', 'I',
+                                            0, 0, 0, 0, 0, 0, 0 };
+
+/*
+ * The display controller configuration is stored in a firmware file.
+ * The Device Tree 'compatible' property value with a '.bin' suffix is passed
+ * to request_firmware() to fetch this file.
+ */
+struct panel_mipi_dbi_config {
+       /* Magic string: panel_mipi_dbi_magic */
+       u8 magic[15];
+
+       /* Config file format version */
+       u8 file_format_version;
+
+       /*
+        * MIPI commands to execute when the display pipeline is enabled.
+        * This is used to configure the display controller.
+        *
+        * The commands are stored in a byte array with the format:
+        *     command, num_parameters, [ parameter, ...], command, ...
+        *
+        * Some commands require a pause before the next command can be received.
+        * Inserting a delay in the command sequence is done by using the NOP command with one
+        * parameter: delay in miliseconds (the No Operation command is part of the MIPI Display
+        * Command Set where it has no parameters).
+        *
+        * Example:
+        *     command 0x11
+        *     sleep 120ms
+        *     command 0xb1 parameters 0x01, 0x2c, 0x2d
+        *     command 0x29
+        *
+        * Byte sequence:
+        *     0x11 0x00
+        *     0x00 0x01 0x78
+        *     0xb1 0x03 0x01 0x2c 0x2d
+        *     0x29 0x00
+        */
+       u8 commands[];
+};
+
+struct panel_mipi_dbi_commands {
+       const u8 *buf;
+       size_t len;
+};
+
+static struct panel_mipi_dbi_commands *
+panel_mipi_dbi_check_commands(struct device *dev, const struct firmware *fw)
+{
+       const struct panel_mipi_dbi_config *config = (struct panel_mipi_dbi_config *)fw->data;
+       struct panel_mipi_dbi_commands *commands;
+       size_t size = fw->size, commands_len;
+       unsigned int i = 0;
+
+       if (size < sizeof(*config) + 2) { /* At least 1 command */
+               dev_err(dev, "config: file size=%zu is too small\n", size);
+               return ERR_PTR(-EINVAL);
+       }
+
+       if (memcmp(config->magic, panel_mipi_dbi_magic, sizeof(config->magic))) {
+               dev_err(dev, "config: Bad magic: %15ph\n", config->magic);
+               return ERR_PTR(-EINVAL);
+       }
+
+       if (config->file_format_version != 1) {
+               dev_err(dev, "config: version=%u is not supported\n", config->file_format_version);
+               return ERR_PTR(-EINVAL);
+       }
+
+       drm_dev_dbg(dev, DRM_UT_DRIVER, "size=%zu version=%u\n", size, config->file_format_version);
+
+       commands_len = size - sizeof(*config);
+
+       while ((i + 1) < commands_len) {
+               u8 command = config->commands[i++];
+               u8 num_parameters = config->commands[i++];
+               const u8 *parameters = &config->commands[i];
+
+               i += num_parameters;
+               if (i > commands_len) {
+                       dev_err(dev, "config: command=0x%02x num_parameters=%u overflows\n",
+                               command, num_parameters);
+                       return ERR_PTR(-EINVAL);
+               }
+
+               if (command == 0x00 && num_parameters == 1)
+                       drm_dev_dbg(dev, DRM_UT_DRIVER, "sleep %ums\n", parameters[0]);
+               else
+                       drm_dev_dbg(dev, DRM_UT_DRIVER, "command %02x %*ph\n",
+                                   command, num_parameters, parameters);
+       }
+
+       if (i != commands_len) {
+               dev_err(dev, "config: malformed command array\n");
+               return ERR_PTR(-EINVAL);
+       }
+
+       commands = devm_kzalloc(dev, sizeof(*commands), GFP_KERNEL);
+       if (!commands)
+               return ERR_PTR(-ENOMEM);
+
+       commands->len = commands_len;
+       commands->buf = devm_kmemdup(dev, config->commands, commands->len, GFP_KERNEL);
+       if (!commands->buf)
+               return ERR_PTR(-ENOMEM);
+
+       return commands;
+}
+
+static struct panel_mipi_dbi_commands *panel_mipi_dbi_commands_from_fw(struct device *dev)
+{
+       struct panel_mipi_dbi_commands *commands;
+       const struct firmware *fw;
+       const char *compatible;
+       char fw_name[40];
+       int ret;
+
+       ret = of_property_read_string_index(dev->of_node, "compatible", 0, &compatible);
+       if (ret)
+               return ERR_PTR(ret);
+
+       snprintf(fw_name, sizeof(fw_name), "%s.bin", compatible);
+       ret = request_firmware(&fw, fw_name, dev);
+       if (ret) {
+               dev_err(dev, "No config file found for compatible '%s' (error=%d)\n",
+                       compatible, ret);
+
+               return ERR_PTR(ret);
+       }
+
+       commands = panel_mipi_dbi_check_commands(dev, fw);
+       release_firmware(fw);
+
+       return commands;
+}
+
+static void panel_mipi_dbi_commands_execute(struct mipi_dbi *dbi,
+                                           struct panel_mipi_dbi_commands *commands)
+{
+       unsigned int i = 0;
+
+       if (!commands)
+               return;
+
+       while (i < commands->len) {
+               u8 command = commands->buf[i++];
+               u8 num_parameters = commands->buf[i++];
+               const u8 *parameters = &commands->buf[i];
+
+               if (command == 0x00 && num_parameters == 1)
+                       msleep(parameters[0]);
+               else if (num_parameters)
+                       mipi_dbi_command_stackbuf(dbi, command, parameters, num_parameters);
+               else
+                       mipi_dbi_command(dbi, command);
+
+               i += num_parameters;
+       }
+}
+
+static void panel_mipi_dbi_enable(struct drm_simple_display_pipe *pipe,
+                                 struct drm_crtc_state *crtc_state,
+                                 struct drm_plane_state *plane_state)
+{
+       struct mipi_dbi_dev *dbidev = drm_to_mipi_dbi_dev(pipe->crtc.dev);
+       struct mipi_dbi *dbi = &dbidev->dbi;
+       int ret, idx;
+
+       if (!drm_dev_enter(pipe->crtc.dev, &idx))
+               return;
+
+       drm_dbg(pipe->crtc.dev, "\n");
+
+       ret = mipi_dbi_poweron_conditional_reset(dbidev);
+       if (ret < 0)
+               goto out_exit;
+       if (!ret)
+               panel_mipi_dbi_commands_execute(dbi, dbidev->driver_private);
+
+       mipi_dbi_enable_flush(dbidev, crtc_state, plane_state);
+out_exit:
+       drm_dev_exit(idx);
+}
+
+static const struct drm_simple_display_pipe_funcs panel_mipi_dbi_pipe_funcs = {
+       .enable = panel_mipi_dbi_enable,
+       .disable = mipi_dbi_pipe_disable,
+       .update = mipi_dbi_pipe_update,
+};
+
+DEFINE_DRM_GEM_CMA_FOPS(panel_mipi_dbi_fops);
+
+static const struct drm_driver panel_mipi_dbi_driver = {
+       .driver_features        = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
+       .fops                   = &panel_mipi_dbi_fops,
+       DRM_GEM_CMA_DRIVER_OPS_VMAP,
+       .debugfs_init           = mipi_dbi_debugfs_init,
+       .name                   = "panel-mipi-dbi",
+       .desc                   = "MIPI DBI compatible display panel",
+       .date                   = "20220103",
+       .major                  = 1,
+       .minor                  = 0,
+};
+
+static int panel_mipi_dbi_get_mode(struct mipi_dbi_dev *dbidev, struct drm_display_mode *mode)
+{
+       struct device *dev = dbidev->drm.dev;
+       u16 hback_porch, vback_porch;
+       int ret;
+
+       ret = of_get_drm_panel_display_mode(dev->of_node, mode, NULL);
+       if (ret) {
+               dev_err(dev, "%pOF: failed to get panel-timing (error=%d)\n", dev->of_node, ret);
+               return ret;
+       }
+
+       mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+
+       hback_porch = mode->htotal - mode->hsync_end;
+       vback_porch = mode->vtotal - mode->vsync_end;
+
+       /*
+        * Make sure width and height are set and that only back porch and
+        * pixelclock are set in the other timing values. Also check that
+        * width and height don't exceed the 16-bit value specified by MIPI DCS.
+        */
+       if (!mode->hdisplay || !mode->vdisplay || mode->flags ||
+           mode->hsync_end > mode->hdisplay || (hback_porch + mode->hdisplay) > 0xffff ||
+           mode->vsync_end > mode->vdisplay || (vback_porch + mode->vdisplay) > 0xffff) {
+               dev_err(dev, "%pOF: panel-timing out of bounds\n", dev->of_node);
+               return -EINVAL;
+       }
+
+       /* The driver doesn't use the pixel clock but it is mandatory so fake one if not set */
+       if (!mode->clock)
+               mode->clock = mode->htotal * mode->vtotal * 60 / 1000;
+
+       dbidev->top_offset = vback_porch;
+       dbidev->left_offset = hback_porch;
+
+       return 0;
+}
+
+static int panel_mipi_dbi_spi_probe(struct spi_device *spi)
+{
+       struct device *dev = &spi->dev;
+       struct drm_display_mode mode;
+       struct mipi_dbi_dev *dbidev;
+       struct drm_device *drm;
+       struct mipi_dbi *dbi;
+       struct gpio_desc *dc;
+       int ret;
+
+       dbidev = devm_drm_dev_alloc(dev, &panel_mipi_dbi_driver, struct mipi_dbi_dev, drm);
+       if (IS_ERR(dbidev))
+               return PTR_ERR(dbidev);
+
+       dbi = &dbidev->dbi;
+       drm = &dbidev->drm;
+
+       ret = panel_mipi_dbi_get_mode(dbidev, &mode);
+       if (ret)
+               return ret;
+
+       dbidev->regulator = devm_regulator_get(dev, "power");
+       if (IS_ERR(dbidev->regulator))
+               return dev_err_probe(dev, PTR_ERR(dbidev->regulator),
+                                    "Failed to get regulator 'power'\n");
+
+       dbidev->backlight = devm_of_find_backlight(dev);
+       if (IS_ERR(dbidev->backlight))
+               return dev_err_probe(dev, PTR_ERR(dbidev->backlight), "Failed to get backlight\n");
+
+       dbi->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+       if (IS_ERR(dbi->reset))
+               return dev_err_probe(dev, PTR_ERR(dbi->reset), "Failed to get GPIO 'reset'\n");
+
+       dc = devm_gpiod_get_optional(dev, "dc", GPIOD_OUT_LOW);
+       if (IS_ERR(dc))
+               return dev_err_probe(dev, PTR_ERR(dc), "Failed to get GPIO 'dc'\n");
+
+       ret = mipi_dbi_spi_init(spi, dbi, dc);
+       if (ret)
+               return ret;
+
+       if (device_property_present(dev, "write-only"))
+               dbi->read_commands = NULL;
+
+       dbidev->driver_private = panel_mipi_dbi_commands_from_fw(dev);
+       if (IS_ERR(dbidev->driver_private))
+               return PTR_ERR(dbidev->driver_private);
+
+       ret = mipi_dbi_dev_init(dbidev, &panel_mipi_dbi_pipe_funcs, &mode, 0);
+       if (ret)
+               return ret;
+
+       drm_mode_config_reset(drm);
+
+       ret = drm_dev_register(drm, 0);
+       if (ret)
+               return ret;
+
+       spi_set_drvdata(spi, drm);
+
+       drm_fbdev_generic_setup(drm, 0);
+
+       return 0;
+}
+
+static int panel_mipi_dbi_spi_remove(struct spi_device *spi)
+{
+       struct drm_device *drm = spi_get_drvdata(spi);
+
+       drm_dev_unplug(drm);
+       drm_atomic_helper_shutdown(drm);
+
+       return 0;
+}
+
+static void panel_mipi_dbi_spi_shutdown(struct spi_device *spi)
+{
+       drm_atomic_helper_shutdown(spi_get_drvdata(spi));
+}
+
+static int __maybe_unused panel_mipi_dbi_pm_suspend(struct device *dev)
+{
+       return drm_mode_config_helper_suspend(dev_get_drvdata(dev));
+}
+
+static int __maybe_unused panel_mipi_dbi_pm_resume(struct device *dev)
+{
+       drm_mode_config_helper_resume(dev_get_drvdata(dev));
+
+       return 0;
+}
+
+static const struct dev_pm_ops panel_mipi_dbi_pm_ops = {
+       SET_SYSTEM_SLEEP_PM_OPS(panel_mipi_dbi_pm_suspend, panel_mipi_dbi_pm_resume)
+};
+
+static const struct of_device_id panel_mipi_dbi_spi_of_match[] = {
+       { .compatible = "panel-mipi-dbi-spi" },
+       {},
+};
+MODULE_DEVICE_TABLE(of, panel_mipi_dbi_spi_of_match);
+
+static const struct spi_device_id panel_mipi_dbi_spi_id[] = {
+       { "panel-mipi-dbi-spi", 0 },
+       { },
+};
+MODULE_DEVICE_TABLE(spi, panel_mipi_dbi_spi_id);
+
+static struct spi_driver panel_mipi_dbi_spi_driver = {
+       .driver = {
+               .name = "panel-mipi-dbi-spi",
+               .owner = THIS_MODULE,
+               .of_match_table = panel_mipi_dbi_spi_of_match,
+               .pm = &panel_mipi_dbi_pm_ops,
+       },
+       .id_table = panel_mipi_dbi_spi_id,
+       .probe = panel_mipi_dbi_spi_probe,
+       .remove = panel_mipi_dbi_spi_remove,
+       .shutdown = panel_mipi_dbi_spi_shutdown,
+};
+module_spi_driver(panel_mipi_dbi_spi_driver);
+
+MODULE_DESCRIPTION("MIPI DBI compatible display panel driver");
+MODULE_AUTHOR("Noralf Trønnes");
+MODULE_LICENSE("GPL");
index 9a5c446..b0e0486 100644 (file)
@@ -1,7 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0-only
 config DRM_V3D
        tristate "Broadcom V3D 3.x and newer"
-       depends on ARCH_BCM || ARCH_BCMSTB || COMPILE_TEST
+       depends on ARCH_BCM || ARCH_BCMSTB || ARCH_BCM2835 || COMPILE_TEST
        depends on DRM
        depends on COMMON_CLK
        depends on MMU
index e76b24b..b7b296b 100644 (file)
@@ -4,7 +4,6 @@
 #include <linux/circ_buf.h>
 #include <linux/ctype.h>
 #include <linux/debugfs.h>
-#include <linux/pm_runtime.h>
 #include <linux/seq_file.h>
 
 #include <drm/drm_debugfs.h>
@@ -130,11 +129,8 @@ static int v3d_v3d_debugfs_ident(struct seq_file *m, void *unused)
        struct drm_device *dev = node->minor->dev;
        struct v3d_dev *v3d = to_v3d_dev(dev);
        u32 ident0, ident1, ident2, ident3, cores;
-       int ret, core;
+       int core;
 
-       ret = pm_runtime_get_sync(v3d->drm.dev);
-       if (ret < 0)
-               return ret;
 
        ident0 = V3D_READ(V3D_HUB_IDENT0);
        ident1 = V3D_READ(V3D_HUB_IDENT1);
@@ -187,9 +183,6 @@ static int v3d_v3d_debugfs_ident(struct seq_file *m, void *unused)
                           (misccfg & V3D_MISCCFG_OVRTMUOUT) != 0);
        }
 
-       pm_runtime_mark_last_busy(v3d->drm.dev);
-       pm_runtime_put_autosuspend(v3d->drm.dev);
-
        return 0;
 }
 
@@ -217,11 +210,6 @@ static int v3d_measure_clock(struct seq_file *m, void *unused)
        uint32_t cycles;
        int core = 0;
        int measure_ms = 1000;
-       int ret;
-
-       ret = pm_runtime_get_sync(v3d->drm.dev);
-       if (ret < 0)
-               return ret;
 
        if (v3d->ver >= 40) {
                V3D_CORE_WRITE(core, V3D_V4_PCTR_0_SRC_0_3,
@@ -245,8 +233,6 @@ static int v3d_measure_clock(struct seq_file *m, void *unused)
                   cycles / (measure_ms * 1000),
                   (cycles / (measure_ms * 100)) % 10);
 
-       pm_runtime_mark_last_busy(v3d->drm.dev);
-       pm_runtime_put_autosuspend(v3d->drm.dev);
 
        return 0;
 }
index 6407a00..66d1f9a 100644 (file)
 #define DRIVER_MINOR 0
 #define DRIVER_PATCHLEVEL 0
 
+#ifdef CONFIG_PM
+static int v3d_runtime_suspend(struct device *dev)
+{
+       struct drm_device *drm = dev_get_drvdata(dev);
+       struct v3d_dev *v3d = to_v3d_dev(drm);
+
+       v3d_irq_disable(v3d);
+
+       clk_disable_unprepare(v3d->clk);
+
+       return 0;
+}
+
+static int v3d_runtime_resume(struct device *dev)
+{
+       struct drm_device *drm = dev_get_drvdata(dev);
+       struct v3d_dev *v3d = to_v3d_dev(drm);
+       int ret;
+
+       ret = clk_prepare_enable(v3d->clk);
+       if (ret != 0)
+               return ret;
+
+       /* XXX: VPM base */
+
+       v3d_mmu_set_page_table(v3d);
+       v3d_irq_enable(v3d);
+
+       return 0;
+}
+#endif
+
+static const struct dev_pm_ops v3d_pm_ops = {
+       SET_RUNTIME_PM_OPS(v3d_runtime_suspend, v3d_runtime_resume, NULL)
+};
+
 static int v3d_get_param_ioctl(struct drm_device *dev, void *data,
                               struct drm_file *file_priv)
 {
        struct v3d_dev *v3d = to_v3d_dev(dev);
        struct drm_v3d_get_param *args = data;
-       int ret;
        static const u32 reg_map[] = {
                [DRM_V3D_PARAM_V3D_UIFCFG] = V3D_HUB_UIFCFG,
                [DRM_V3D_PARAM_V3D_HUB_IDENT1] = V3D_HUB_IDENT1,
@@ -69,17 +104,12 @@ static int v3d_get_param_ioctl(struct drm_device *dev, void *data,
                if (args->value != 0)
                        return -EINVAL;
 
-               ret = pm_runtime_get_sync(v3d->drm.dev);
-               if (ret < 0)
-                       return ret;
                if (args->param >= DRM_V3D_PARAM_V3D_CORE0_IDENT0 &&
                    args->param <= DRM_V3D_PARAM_V3D_CORE0_IDENT2) {
                        args->value = V3D_CORE_READ(0, offset);
                } else {
                        args->value = V3D_READ(offset);
                }
-               pm_runtime_mark_last_busy(v3d->drm.dev);
-               pm_runtime_put_autosuspend(v3d->drm.dev);
                return 0;
        }
 
@@ -199,6 +229,7 @@ static const struct drm_driver v3d_drm_driver = {
 static const struct of_device_id v3d_of_match[] = {
        { .compatible = "brcm,7268-v3d" },
        { .compatible = "brcm,7278-v3d" },
+       { .compatible = "brcm,2711-v3d" },
        {},
 };
 MODULE_DEVICE_TABLE(of, v3d_of_match);
@@ -270,6 +301,21 @@ 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);
+       }
+       v3d->clk_up_rate = clk_get_max_rate(v3d->clk);
+       /* 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)
@@ -283,9 +329,6 @@ static int v3d_platform_drm_probe(struct platform_device *pdev)
                return -ENOMEM;
        }
 
-       pm_runtime_use_autosuspend(dev);
-       pm_runtime_set_autosuspend_delay(dev, 50);
-       pm_runtime_enable(dev);
 
        ret = v3d_gem_init(drm);
        if (ret)
@@ -299,6 +342,9 @@ static int v3d_platform_drm_probe(struct platform_device *pdev)
        if (ret)
                goto irq_disable;
 
+       ret = clk_set_min_rate(v3d->clk, v3d->clk_down_rate);
+       WARN_ON_ONCE(ret != 0);
+
        return 0;
 
 irq_disable:
@@ -331,6 +377,7 @@ static struct platform_driver v3d_platform_driver = {
        .driver         = {
                .name   = "v3d",
                .of_match_table = v3d_of_match,
+               .pm = &v3d_pm_ops,
        },
 };
 
index 2701347..37b353a 100644 (file)
@@ -85,6 +85,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. */
index 805d6f6..a7afccc 100644 (file)
@@ -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/pm_runtime.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);
+}
+
+
+static void
 v3d_init_core(struct v3d_dev *v3d, int core)
 {
        /* Set OVRTMUOUT, which means that the texture sampler uniform
@@ -358,6 +400,7 @@ v3d_job_free(struct kref *ref)
        struct v3d_job *job = container_of(ref, struct v3d_job, refcount);
        unsigned long index;
        struct dma_fence *fence;
+       struct v3d_dev *v3d = job->v3d;
        int i;
 
        for (i = 0; i < job->bo_count; i++) {
@@ -374,8 +417,7 @@ v3d_job_free(struct kref *ref)
        dma_fence_put(job->irq_fence);
        dma_fence_put(job->done_fence);
 
-       pm_runtime_mark_last_busy(job->v3d->drm.dev);
-       pm_runtime_put_autosuspend(job->v3d->drm.dev);
+       v3d_clock_up_put(v3d);
 
        if (job->perfmon)
                v3d_perfmon_put(job->perfmon);
@@ -446,10 +488,6 @@ v3d_job_init(struct v3d_dev *v3d, struct drm_file *file_priv,
        job->v3d = v3d;
        job->free = free;
 
-       ret = pm_runtime_get_sync(v3d->drm.dev);
-       if (ret < 0)
-               return ret;
-
        xa_init_flags(&job->deps, XA_FLAGS_ALLOC);
 
        ret = drm_syncobj_find_fence(file_priv, in_sync, 0, 0, &in_fence);
@@ -460,12 +498,12 @@ v3d_job_init(struct v3d_dev *v3d, struct drm_file *file_priv,
        if (ret)
                goto fail;
 
+       v3d_clock_up_get(v3d);
        kref_init(&job->refcount);
 
        return 0;
 fail:
        xa_destroy(&job->deps);
-       pm_runtime_put_autosuspend(v3d->drm.dev);
        return ret;
 }
 
@@ -914,6 +952,13 @@ v3d_gem_init(struct drm_device *dev)
        mutex_init(&v3d->sched_lock);
        mutex_init(&v3d->cache_clean_lock);
 
+       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);
+
        /* 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".
index e714d53..0f7a23f 100644 (file)
@@ -177,6 +177,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));
 
@@ -186,6 +187,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) ?
@@ -194,6 +196,7 @@ v3d_hub_irq(int irq, void *arg)
                         ", pte invalid" : ""),
                        ((intsts & V3D_HUB_INT_MMU_CAP) ?
                         ", cap exceeded" : ""));
+               logged_error = 1;
                status = IRQ_HANDLED;
        }
 
index 5a45353..618503f 100644 (file)
@@ -18,6 +18,8 @@
  * each client.  This is not yet implemented.
  */
 
+#include <linux/pm_runtime.h>
+
 #include "v3d_drv.h"
 #include "v3d_regs.h"
 
index 345a557..52a1c30 100644 (file)
@@ -9,6 +9,7 @@ config DRM_VC4
        select DRM_KMS_CMA_HELPER
        select DRM_GEM_CMA_HELPER
        select DRM_PANEL_BRIDGE
+       select PM
        select SND_PCM
        select SND_PCM_ELD
        select SND_SOC_GENERIC_DMAENGINE_PCM
index d0163e1..8281a04 100644 (file)
@@ -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 \
index f642bd6..90633e1 100644 (file)
@@ -248,6 +248,9 @@ 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->is_vc5))
+               return;
+
        mutex_lock(&vc4->purgeable.lock);
        list_add_tail(&bo->size_head, &vc4->purgeable.list);
        vc4->purgeable.num++;
@@ -259,6 +262,9 @@ 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->is_vc5))
+               return;
+
        /* list_del_init() is used here because the caller might release
         * the purgeable lock in order to acquire the madv one and update the
         * madv status.
@@ -389,6 +395,9 @@ 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->is_vc5))
+               return ERR_PTR(-ENODEV);
+
        bo = kzalloc(sizeof(*bo), GFP_KERNEL);
        if (!bo)
                return NULL;
@@ -415,6 +424,9 @@ struct vc4_bo *vc4_bo_create(struct drm_device *dev, size_t unaligned_size,
        struct drm_gem_cma_object *cma_obj;
        struct vc4_bo *bo;
 
+       if (WARN_ON_ONCE(vc4->is_vc5))
+               return ERR_PTR(-ENODEV);
+
        if (size == 0)
                return ERR_PTR(-EINVAL);
 
@@ -473,19 +485,20 @@ struct vc4_bo *vc4_bo_create(struct drm_device *dev, size_t unaligned_size,
        return bo;
 }
 
-int vc4_dumb_create(struct drm_file *file_priv,
-                   struct drm_device *dev,
-                   struct drm_mode_create_dumb *args)
+int vc4_bo_dumb_create(struct drm_file *file_priv,
+                      struct drm_device *dev,
+                      struct drm_mode_create_dumb *args)
 {
-       int min_pitch = DIV_ROUND_UP(args->width * args->bpp, 8);
+       struct vc4_dev *vc4 = to_vc4_dev(dev);
        struct vc4_bo *bo = NULL;
        int ret;
 
-       if (args->pitch < min_pitch)
-               args->pitch = min_pitch;
+       if (WARN_ON_ONCE(vc4->is_vc5))
+               return -ENODEV;
 
-       if (args->size < args->pitch * args->height)
-               args->size = args->pitch * args->height;
+       ret = vc4_dumb_fixup_args(args);
+       if (ret)
+               return ret;
 
        bo = vc4_bo_create(dev, args->size, false, VC4_BO_TYPE_DUMB);
        if (IS_ERR(bo))
@@ -603,8 +616,12 @@ static void vc4_bo_cache_time_work(struct work_struct *work)
 
 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->is_vc5))
+               return -ENODEV;
+
        /* Fast path: if the BO is already retained by someone, no need to
         * check the madv status.
         */
@@ -639,6 +656,11 @@ int vc4_bo_inc_usecnt(struct vc4_bo *bo)
 
 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->is_vc5))
+               return;
+
        /* Fast path: if the BO is still retained by someone, no need to test
         * the madv value.
         */
@@ -764,6 +786,9 @@ int vc4_create_bo_ioctl(struct drm_device *dev, void *data,
        struct vc4_bo *bo = NULL;
        int ret;
 
+       if (WARN_ON_ONCE(vc4->is_vc5))
+               return -ENODEV;
+
        ret = vc4_grab_bin_bo(vc4, vc4file);
        if (ret)
                return ret;
@@ -787,9 +812,13 @@ int vc4_create_bo_ioctl(struct drm_device *dev, void *data,
 int vc4_mmap_bo_ioctl(struct drm_device *dev, void *data,
                      struct drm_file *file_priv)
 {
+       struct vc4_dev *vc4 = to_vc4_dev(dev);
        struct drm_vc4_mmap_bo *args = data;
        struct drm_gem_object *gem_obj;
 
+       if (WARN_ON_ONCE(vc4->is_vc5))
+               return -ENODEV;
+
        gem_obj = drm_gem_object_lookup(file_priv, args->handle);
        if (!gem_obj) {
                DRM_DEBUG("Failed to look up GEM BO %d\n", args->handle);
@@ -813,6 +842,9 @@ vc4_create_shader_bo_ioctl(struct drm_device *dev, void *data,
        struct vc4_bo *bo = NULL;
        int ret;
 
+       if (WARN_ON_ONCE(vc4->is_vc5))
+               return -ENODEV;
+
        if (args->size == 0)
                return -EINVAL;
 
@@ -883,11 +915,15 @@ fail:
 int vc4_set_tiling_ioctl(struct drm_device *dev, void *data,
                         struct drm_file *file_priv)
 {
+       struct vc4_dev *vc4 = to_vc4_dev(dev);
        struct drm_vc4_set_tiling *args = data;
        struct drm_gem_object *gem_obj;
        struct vc4_bo *bo;
        bool t_format;
 
+       if (WARN_ON_ONCE(vc4->is_vc5))
+               return -ENODEV;
+
        if (args->flags != 0)
                return -EINVAL;
 
@@ -926,10 +962,14 @@ int vc4_set_tiling_ioctl(struct drm_device *dev, void *data,
 int vc4_get_tiling_ioctl(struct drm_device *dev, void *data,
                         struct drm_file *file_priv)
 {
+       struct vc4_dev *vc4 = to_vc4_dev(dev);
        struct drm_vc4_get_tiling *args = data;
        struct drm_gem_object *gem_obj;
        struct vc4_bo *bo;
 
+       if (WARN_ON_ONCE(vc4->is_vc5))
+               return -ENODEV;
+
        if (args->flags != 0 || args->modifier != 0)
                return -EINVAL;
 
@@ -956,6 +996,9 @@ int vc4_bo_cache_init(struct drm_device *dev)
        struct vc4_dev *vc4 = to_vc4_dev(dev);
        int i;
 
+       if (WARN_ON_ONCE(vc4->is_vc5))
+               return -ENODEV;
+
        /* Create the initial set of BO labels that the kernel will
         * use.  This lets us avoid a bunch of string reallocation in
         * the kernel's draw and BO allocation paths.
@@ -1015,6 +1058,9 @@ 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->is_vc5))
+               return -ENODEV;
+
        if (!args->len)
                return -EINVAL;
 
index 88dbb28..f11b824 100644 (file)
@@ -70,6 +70,7 @@ static const struct debugfs_reg32 crtc_regs[] = {
 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));
        /* Top/base are supposed to be 4-pixel aligned, but the
         * Raspberry Pi firmware fills the low bits (which are
@@ -89,6 +90,7 @@ static bool vc4_crtc_get_scanout_position(struct drm_crtc *crtc,
 {
        struct drm_device *dev = crtc->dev;
        struct vc4_dev *vc4 = to_vc4_dev(dev);
+       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 cob_size;
@@ -123,7 +125,7 @@ static bool vc4_crtc_get_scanout_position(struct drm_crtc *crtc,
                *vpos /= 2;
 
                /* Use hpos to correct for field offset in interlaced mode. */
-               if (vc4_hvs_get_fifo_frame_count(dev, vc4_crtc_state->assigned_channel) % 2)
+               if (vc4_hvs_get_fifo_frame_count(hvs, vc4_crtc_state->assigned_channel) % 2)
                        *hpos += mode->crtc_htotal / 2;
        }
 
@@ -254,7 +256,7 @@ static u32 vc4_get_fifo_full_level(struct vc4_crtc *vc4_crtc, u32 format)
                 * Removing 1 from the FIFO full level however
                 * seems to completely remove that issue.
                 */
-               if (!vc4->hvs->hvs5)
+               if (!vc4->is_vc5)
                        return fifo_len_bytes - 3 * HVS_FIFO_LATENCY_PIX - 1;
 
                return fifo_len_bytes - 3 * HVS_FIFO_LATENCY_PIX;
@@ -281,27 +283,32 @@ static u32 vc4_crtc_get_fifo_full_level_bits(struct vc4_crtc *vc4_crtc,
  * allows drivers to push pixels to more than one encoder from the
  * same CRTC.
  */
-static struct drm_encoder *vc4_get_crtc_encoder(struct drm_crtc *crtc,
-                                               struct drm_atomic_state *state,
-                                               struct drm_connector_state *(*get_state)(struct drm_atomic_state *state,
-                                                                                        struct drm_connector *connector))
+struct drm_encoder *vc4_get_crtc_encoder(struct drm_crtc *crtc,
+                                        struct drm_crtc_state *state)
 {
-       struct drm_connector *connector;
-       struct drm_connector_list_iter conn_iter;
+       struct drm_encoder *encoder;
 
-       drm_connector_list_iter_begin(crtc->dev, &conn_iter);
-       drm_for_each_connector_iter(connector, &conn_iter) {
-               struct drm_connector_state *conn_state = get_state(state, connector);
+       WARN_ON(hweight32(state->encoder_mask) > 1);
 
-               if (!conn_state)
-                       continue;
+       drm_for_each_encoder_mask(encoder, crtc->dev, state->encoder_mask)
+               return encoder;
 
-               if (conn_state->crtc == crtc) {
-                       drm_connector_list_iter_end(&conn_iter);
-                       return connector->encoder;
-               }
-       }
-       drm_connector_list_iter_end(&conn_iter);
+       return NULL;
+}
+
+#define drm_for_each_connector_mask(connector, dev, connector_mask) \
+       list_for_each_entry((connector), &(dev)->mode_config.connector_list, head) \
+               for_each_if ((connector_mask) & drm_connector_mask(connector))
+
+struct drm_connector *vc4_get_crtc_connector(struct drm_crtc *crtc,
+                                            struct drm_crtc_state *state)
+{
+       struct drm_connector *connector;
+
+       WARN_ON(hweight32(state->connector_mask) > 1);
+
+       drm_for_each_connector_mask(connector, crtc->dev, state->connector_mask)
+               return connector;
 
        return NULL;
 }
@@ -315,23 +322,31 @@ static void vc4_crtc_pixelvalve_reset(struct drm_crtc *crtc)
        CRTC_WRITE(PV_CONTROL, CRTC_READ(PV_CONTROL) | PV_CONTROL_FIFO_CLR);
 }
 
-static void vc4_crtc_config_pv(struct drm_crtc *crtc, struct drm_atomic_state *state)
+static void vc4_crtc_config_pv(struct drm_crtc *crtc, struct drm_encoder *encoder,
+                              struct drm_atomic_state *state)
 {
        struct drm_device *dev = crtc->dev;
        struct vc4_dev *vc4 = to_vc4_dev(dev);
-       struct drm_encoder *encoder = vc4_get_crtc_encoder(crtc, state,
-                                                          drm_atomic_get_new_connector_state);
        struct vc4_encoder *vc4_encoder = to_vc4_encoder(encoder);
        struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
        const struct vc4_pv_data *pv_data = vc4_crtc_to_vc4_pv_data(vc4_crtc);
        struct drm_crtc_state *crtc_state = crtc->state;
        struct drm_display_mode *mode = &crtc_state->adjusted_mode;
        bool interlace = mode->flags & DRM_MODE_FLAG_INTERLACE;
-       u32 pixel_rep = (mode->flags & DRM_MODE_FLAG_DBLCLK) ? 2 : 1;
+       bool is_hdmi = vc4_encoder->type == VC4_ENCODER_TYPE_HDMI0 ||
+                      vc4_encoder->type == VC4_ENCODER_TYPE_HDMI1;
+       u32 pixel_rep = ((mode->flags & DRM_MODE_FLAG_DBLCLK) && !is_hdmi) ? 2 : 1;
        bool is_dsi = (vc4_encoder->type == VC4_ENCODER_TYPE_DSI0 ||
                       vc4_encoder->type == VC4_ENCODER_TYPE_DSI1);
-       u32 format = is_dsi ? PV_CONTROL_FORMAT_DSIV_24 : PV_CONTROL_FORMAT_24;
+       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;
+
+       u16 vert_bp = mode->crtc_vtotal - mode->crtc_vsync_end;
+       u16 vert_sync = mode->crtc_vsync_end - mode->crtc_vsync_start;
+       u16 vert_fp = mode->crtc_vsync_start - mode->crtc_vdisplay;
+
        bool debug_dump_regs = false;
 
        if (debug_dump_regs) {
@@ -355,52 +370,64 @@ static void vc4_crtc_config_pv(struct drm_crtc *crtc, struct drm_atomic_state *s
                   VC4_SET_FIELD(mode->hdisplay * pixel_rep / ppc,
                                 PV_HORZB_HACTIVE));
 
-       CRTC_WRITE(PV_VERTA,
-                  VC4_SET_FIELD(mode->crtc_vtotal - mode->crtc_vsync_end,
-                                PV_VERTA_VBP) |
-                  VC4_SET_FIELD(mode->crtc_vsync_end - mode->crtc_vsync_start,
-                                PV_VERTA_VSYNC));
-       CRTC_WRITE(PV_VERTB,
-                  VC4_SET_FIELD(mode->crtc_vsync_start - mode->crtc_vdisplay,
-                                PV_VERTB_VFP) |
-                  VC4_SET_FIELD(mode->crtc_vdisplay, PV_VERTB_VACTIVE));
-
        if (interlace) {
+               bool odd_field_first = false;
+               u32 field_delay = mode->htotal * pixel_rep / (2 * ppc);
+               u16 vert_bp_even = vert_bp;
+               u16 vert_fp_even = vert_fp;
+
+               if (is_vec) {
+                       /* VEC (composite output) */
+                       ++field_delay;
+                       if (mode->htotal == 858) {
+                               /* 525-line mode (NTSC or PAL-M) */
+                               odd_field_first = true;
+                       }
+               }
+
+               if (odd_field_first)
+                       ++vert_fp_even;
+               else
+                       ++vert_bp;
+
                CRTC_WRITE(PV_VERTA_EVEN,
-                          VC4_SET_FIELD(mode->crtc_vtotal -
-                                        mode->crtc_vsync_end - 1,
-                                        PV_VERTA_VBP) |
-                          VC4_SET_FIELD(mode->crtc_vsync_end -
-                                        mode->crtc_vsync_start,
-                                        PV_VERTA_VSYNC));
+                          VC4_SET_FIELD(vert_bp_even, PV_VERTA_VBP) |
+                          VC4_SET_FIELD(vert_sync, PV_VERTA_VSYNC));
                CRTC_WRITE(PV_VERTB_EVEN,
-                          VC4_SET_FIELD(mode->crtc_vsync_start -
-                                        mode->crtc_vdisplay,
-                                        PV_VERTB_VFP) |
+                          VC4_SET_FIELD(vert_fp_even, PV_VERTB_VFP) |
                           VC4_SET_FIELD(mode->crtc_vdisplay, PV_VERTB_VACTIVE));
 
-               /* We set up first field even mode for HDMI.  VEC's
-                * NTSC mode would want first field odd instead, once
-                * we support it (to do so, set ODD_FIRST and put the
-                * delay in VSYNCD_EVEN instead).
+               /* We set up first field even mode for HDMI and VEC's PAL.
+                * For NTSC, we need first field odd.
                 */
                CRTC_WRITE(PV_V_CONTROL,
                           PV_VCONTROL_CONTINUOUS |
                           (is_dsi ? PV_VCONTROL_DSI : 0) |
                           PV_VCONTROL_INTERLACE |
-                          VC4_SET_FIELD(mode->htotal * pixel_rep / 2,
-                                        PV_VCONTROL_ODD_DELAY));
-               CRTC_WRITE(PV_VSYNCD_EVEN, 0);
+                          (odd_field_first
+                                  ? PV_VCONTROL_ODD_FIRST
+                                  : VC4_SET_FIELD(field_delay,
+                                                  PV_VCONTROL_ODD_DELAY)));
+               CRTC_WRITE(PV_VSYNCD_EVEN,
+                          (odd_field_first ? field_delay : 0));
        } else {
                CRTC_WRITE(PV_V_CONTROL,
                           PV_VCONTROL_CONTINUOUS |
                           (is_dsi ? PV_VCONTROL_DSI : 0));
+               CRTC_WRITE(PV_VSYNCD_EVEN, 0);
        }
 
+       CRTC_WRITE(PV_VERTA,
+                  VC4_SET_FIELD(vert_bp, PV_VERTA_VBP) |
+                  VC4_SET_FIELD(vert_sync, PV_VERTA_VSYNC));
+       CRTC_WRITE(PV_VERTB,
+                  VC4_SET_FIELD(vert_fp, PV_VERTB_VFP) |
+                  VC4_SET_FIELD(mode->crtc_vdisplay, PV_VERTB_VACTIVE));
+
        if (is_dsi)
                CRTC_WRITE(PV_HACT_ACT, mode->hdisplay * pixel_rep);
 
-       if (vc4->hvs->hvs5)
+       if (vc4->is_vc5)
                CRTC_WRITE(PV_MUX_CFG,
                           VC4_SET_FIELD(PV_MUX_CFG_RGB_PIXEL_MUX_MODE_NO_SWAP,
                                         PV_MUX_CFG_RGB_PIXEL_MUX_MODE));
@@ -426,6 +453,7 @@ static void vc4_crtc_config_pv(struct drm_crtc *crtc, struct drm_atomic_state *s
 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);
@@ -439,6 +467,7 @@ static int vc4_crtc_disable(struct drm_crtc *crtc,
        struct vc4_encoder *vc4_encoder = to_vc4_encoder(encoder);
        struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
        struct drm_device *dev = crtc->dev;
+       struct vc4_dev *vc4 = to_vc4_dev(dev);
        int ret;
 
        CRTC_WRITE(PV_V_CONTROL,
@@ -468,7 +497,7 @@ static int vc4_crtc_disable(struct drm_crtc *crtc,
                vc4_encoder->post_crtc_disable(encoder, state);
 
        vc4_crtc_pixelvalve_reset(crtc);
-       vc4_hvs_stop_channel(dev, channel);
+       vc4_hvs_stop_channel(vc4->hvs, channel);
 
        if (vc4_encoder && vc4_encoder->post_crtc_powerdown)
                vc4_encoder->post_crtc_powerdown(encoder, state);
@@ -494,6 +523,7 @@ static struct drm_encoder *vc4_crtc_get_encoder_by_type(struct drm_crtc *crtc,
 int vc4_crtc_disable_at_boot(struct drm_crtc *crtc)
 {
        struct drm_device *drm = crtc->dev;
+       struct vc4_dev *vc4 = to_vc4_dev(drm);
        struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
        enum vc4_encoder_type encoder_type;
        const struct vc4_pv_data *pv_data;
@@ -515,7 +545,7 @@ int vc4_crtc_disable_at_boot(struct drm_crtc *crtc)
        if (!(CRTC_READ(PV_V_CONTROL) & PV_VCONTROL_VIDEN))
                return 0;
 
-       channel = vc4_hvs_get_fifo_from_output(drm, vc4_crtc->data->hvs_output);
+       channel = vc4_hvs_get_fifo_from_output(vc4->hvs, vc4_crtc->data->hvs_output);
        if (channel < 0)
                return 0;
 
@@ -553,10 +583,12 @@ static void vc4_crtc_atomic_disable(struct drm_crtc *crtc,
        struct drm_crtc_state *old_state = drm_atomic_get_old_crtc_state(state,
                                                                         crtc);
        struct vc4_crtc_state *old_vc4_state = to_vc4_crtc_state(old_state);
-       struct drm_encoder *encoder = vc4_get_crtc_encoder(crtc, state,
-                                                          drm_atomic_get_old_connector_state);
+       struct drm_encoder *encoder = vc4_get_crtc_encoder(crtc, old_state);
        struct drm_device *dev = crtc->dev;
 
+       drm_dbg(dev, "Disabling CRTC %s (%u) connected to Encoder %s (%u)",
+               crtc->name, crtc->base.id, encoder->name, encoder->base.id);
+
        require_hvs_enabled(dev);
 
        /* Disable vblank irq handling before crtc is disabled. */
@@ -581,12 +613,16 @@ static void vc4_crtc_atomic_disable(struct drm_crtc *crtc,
 static void vc4_crtc_atomic_enable(struct drm_crtc *crtc,
                                   struct drm_atomic_state *state)
 {
+       struct drm_crtc_state *new_state = drm_atomic_get_new_crtc_state(state,
+                                                                        crtc);
        struct drm_device *dev = crtc->dev;
        struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
-       struct drm_encoder *encoder = vc4_get_crtc_encoder(crtc, state,
-                                                          drm_atomic_get_new_connector_state);
+       struct drm_encoder *encoder = vc4_get_crtc_encoder(crtc, new_state);
        struct vc4_encoder *vc4_encoder = to_vc4_encoder(encoder);
 
+       drm_dbg(dev, "Enabling CRTC %s (%u) connected to Encoder %s (%u)",
+               crtc->name, crtc->base.id, encoder->name, encoder->base.id);
+
        require_hvs_enabled(dev);
 
        /* Enable vblank irq handling before crtc is started otherwise
@@ -599,7 +635,7 @@ static void vc4_crtc_atomic_enable(struct drm_crtc *crtc,
        if (vc4_encoder->pre_crtc_configure)
                vc4_encoder->pre_crtc_configure(encoder, state);
 
-       vc4_crtc_config_pv(crtc, state);
+       vc4_crtc_config_pv(crtc, encoder, state);
 
        CRTC_WRITE(PV_CONTROL, CRTC_READ(PV_CONTROL) | PV_CONTROL_EN);
 
@@ -668,21 +704,42 @@ static int vc4_crtc_atomic_check(struct drm_crtc *crtc,
        struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc_state);
        struct drm_connector *conn;
        struct drm_connector_state *conn_state;
+       struct drm_encoder *encoder;
        int ret, i;
 
        ret = vc4_hvs_atomic_check(crtc, state);
        if (ret)
                return ret;
 
+       encoder = vc4_get_crtc_encoder(crtc, crtc_state);
+       if (encoder) {
+               const struct drm_display_mode *mode = &crtc_state->adjusted_mode;
+               struct vc4_encoder *vc4_encoder = to_vc4_encoder(encoder);
+
+               mode = &crtc_state->adjusted_mode;
+               if (vc4_encoder->type == VC4_ENCODER_TYPE_HDMI0) {
+                       vc4_state->hvs_load = max(mode->clock * mode->hdisplay / mode->htotal + 1000,
+                                                 mode->clock * 9 / 10) * 1000;
+               } else {
+                       vc4_state->hvs_load = mode->clock * 1000;
+               }
+       }
+
        for_each_new_connector_in_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;
+               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;
        }
 
@@ -710,6 +767,7 @@ static void vc4_crtc_handle_page_flip(struct vc4_crtc *vc4_crtc)
        struct drm_crtc *crtc = &vc4_crtc->base;
        struct drm_device *dev = crtc->dev;
        struct vc4_dev *vc4 = to_vc4_dev(dev);
+       struct vc4_hvs *hvs = vc4->hvs;
        u32 chan = vc4_crtc->current_hvs_channel;
        unsigned long flags;
 
@@ -728,7 +786,7 @@ 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(dev, chan);
+               vc4_hvs_unmask_underrun(hvs, chan);
        }
        spin_unlock(&vc4_crtc->irq_lock);
        spin_unlock_irqrestore(&dev->event_lock, flags);
@@ -762,17 +820,18 @@ struct vc4_async_flip_state {
        struct drm_framebuffer *old_fb;
        struct drm_pending_vblank_event *event;
 
-       struct vc4_seqno_cb cb;
+       union {
+               struct dma_fence_cb fence;
+               struct vc4_seqno_cb seqno;
+       } cb;
 };
 
 /* Called when the V3D execution for the BO being flipped to is done, so that
  * we can actually update the plane's address to point to it.
  */
 static void
-vc4_async_page_flip_complete(struct vc4_seqno_cb *cb)
+vc4_async_page_flip_complete(struct vc4_async_flip_state *flip_state)
 {
-       struct vc4_async_flip_state *flip_state =
-               container_of(cb, struct vc4_async_flip_state, cb);
        struct drm_crtc *crtc = flip_state->crtc;
        struct drm_device *dev = crtc->dev;
        struct drm_plane *plane = crtc->primary;
@@ -789,59 +848,85 @@ vc4_async_page_flip_complete(struct vc4_seqno_cb *cb)
        drm_crtc_vblank_put(crtc);
        drm_framebuffer_put(flip_state->fb);
 
-       /* Decrement the BO usecnt in order to keep the inc/dec calls balanced
-        * when the planes are updated through the async update path.
-        * FIXME: we should move to generic async-page-flip when it's
-        * available, so that we can get rid of this hand-made cleanup_fb()
-        * logic.
-        */
-       if (flip_state->old_fb) {
-               struct drm_gem_cma_object *cma_bo;
-               struct vc4_bo *bo;
+       if (flip_state->old_fb)
+               drm_framebuffer_put(flip_state->old_fb);
 
-               cma_bo = drm_fb_cma_get_gem_obj(flip_state->old_fb, 0);
+       kfree(flip_state);
+}
+
+static void vc4_async_page_flip_seqno_complete(struct vc4_seqno_cb *cb)
+{
+       struct vc4_async_flip_state *flip_state =
+               container_of(cb, struct vc4_async_flip_state, cb.seqno);
+       struct vc4_bo *bo = NULL;
+
+       if (flip_state->old_fb) {
+               struct drm_gem_cma_object *cma_bo =
+                       drm_fb_cma_get_gem_obj(flip_state->old_fb, 0);
                bo = to_vc4_bo(&cma_bo->base);
+       }
+
+       vc4_async_page_flip_complete(flip_state);
+
+       /*
+        * Decrement the BO usecnt in order to keep the inc/dec
+        * calls balanced when the planes are updated through
+        * the async update path.
+        *
+        * FIXME: we should move to generic async-page-flip when
+        * it's available, so that we can get rid of this
+        * hand-made cleanup_fb() logic.
+        */
+       if (bo)
                vc4_bo_dec_usecnt(bo);
-               drm_framebuffer_put(flip_state->old_fb);
+}
+
+static void vc4_async_page_flip_fence_complete(struct dma_fence *fence,
+                                              struct dma_fence_cb *cb)
+{
+       struct vc4_async_flip_state *flip_state =
+               container_of(cb, struct vc4_async_flip_state, cb.fence);
+
+       vc4_async_page_flip_complete(flip_state);
+       dma_fence_put(fence);
+}
+
+static int vc4_async_set_fence_cb(struct drm_device *dev,
+                                 struct vc4_async_flip_state *flip_state)
+{
+       struct drm_framebuffer *fb = flip_state->fb;
+       struct drm_gem_cma_object *cma_bo = drm_fb_cma_get_gem_obj(fb, 0);
+       struct vc4_dev *vc4 = to_vc4_dev(dev);
+       struct dma_fence *fence;
+
+       if (!vc4->is_vc5) {
+               struct vc4_bo *bo = to_vc4_bo(&cma_bo->base);
+
+               return vc4_queue_seqno_cb(dev, &flip_state->cb.seqno, bo->seqno,
+                                         vc4_async_page_flip_seqno_complete);
        }
 
-       kfree(flip_state);
+       fence = dma_fence_get(dma_resv_excl_fence(cma_bo->base.resv));
+       if (dma_fence_add_callback(fence, &flip_state->cb.fence,
+                                  vc4_async_page_flip_fence_complete))
+               vc4_async_page_flip_fence_complete(fence, &flip_state->cb.fence);
+
+       return 0;
 }
 
-/* Implements async (non-vblank-synced) page flips.
- *
- * The page flip ioctl needs to return immediately, so we grab the
- * modeset semaphore on the pipe, and queue the address update for
- * when V3D is done with the BO being flipped to.
- */
-static int vc4_async_page_flip(struct drm_crtc *crtc,
-                              struct drm_framebuffer *fb,
-                              struct drm_pending_vblank_event *event,
-                              uint32_t flags)
+static int
+vc4_async_page_flip_common(struct drm_crtc *crtc,
+                          struct drm_framebuffer *fb,
+                          struct drm_pending_vblank_event *event,
+                          uint32_t flags)
 {
        struct drm_device *dev = crtc->dev;
        struct drm_plane *plane = crtc->primary;
-       int ret = 0;
        struct vc4_async_flip_state *flip_state;
-       struct drm_gem_cma_object *cma_bo = drm_fb_cma_get_gem_obj(fb, 0);
-       struct vc4_bo *bo = to_vc4_bo(&cma_bo->base);
-
-       /* Increment the BO usecnt here, so that we never end up with an
-        * unbalanced number of vc4_bo_{dec,inc}_usecnt() calls when the
-        * plane is later updated through the non-async path.
-        * FIXME: we should move to generic async-page-flip when it's
-        * available, so that we can get rid of this hand-made prepare_fb()
-        * logic.
-        */
-       ret = vc4_bo_inc_usecnt(bo);
-       if (ret)
-               return ret;
 
        flip_state = kzalloc(sizeof(*flip_state), GFP_KERNEL);
-       if (!flip_state) {
-               vc4_bo_dec_usecnt(bo);
+       if (!flip_state)
                return -ENOMEM;
-       }
 
        drm_framebuffer_get(fb);
        flip_state->fb = fb;
@@ -868,23 +953,79 @@ static int vc4_async_page_flip(struct drm_crtc *crtc,
         */
        drm_atomic_set_fb_for_plane(plane->state, fb);
 
-       vc4_queue_seqno_cb(dev, &flip_state->cb, bo->seqno,
-                          vc4_async_page_flip_complete);
+       vc4_async_set_fence_cb(dev, flip_state);
 
        /* Driver takes ownership of state on successful async commit. */
        return 0;
 }
 
+/* Implements async (non-vblank-synced) page flips.
+ *
+ * The page flip ioctl needs to return immediately, so we grab the
+ * modeset semaphore on the pipe, and queue the address update for
+ * when V3D is done with the BO being flipped to.
+ */
+static int vc4_async_page_flip(struct drm_crtc *crtc,
+                              struct drm_framebuffer *fb,
+                              struct drm_pending_vblank_event *event,
+                              uint32_t flags)
+{
+       struct drm_device *dev = crtc->dev;
+       struct vc4_dev *vc4 = to_vc4_dev(dev);
+       struct drm_gem_cma_object *cma_bo = drm_fb_cma_get_gem_obj(fb, 0);
+       struct vc4_bo *bo = to_vc4_bo(&cma_bo->base);
+       int ret;
+
+       if (WARN_ON_ONCE(vc4->is_vc5))
+               return -ENODEV;
+
+       /*
+        * Increment the BO usecnt here, so that we never end up with an
+        * unbalanced number of vc4_bo_{dec,inc}_usecnt() calls when the
+        * plane is later updated through the non-async path.
+        *
+        * FIXME: we should move to generic async-page-flip when
+        * it's available, so that we can get rid of this
+        * hand-made prepare_fb() logic.
+        */
+       ret = vc4_bo_inc_usecnt(bo);
+       if (ret)
+               return ret;
+
+       ret = vc4_async_page_flip_common(crtc, fb, event, flags);
+       if (ret) {
+               vc4_bo_dec_usecnt(bo);
+               return ret;
+       }
+
+       return 0;
+}
+
+static int vc5_async_page_flip(struct drm_crtc *crtc,
+                              struct drm_framebuffer *fb,
+                              struct drm_pending_vblank_event *event,
+                              uint32_t flags)
+{
+       return vc4_async_page_flip_common(crtc, fb, event, flags);
+}
+
 int vc4_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)
-               return vc4_async_page_flip(crtc, fb, event, flags);
-       else
+       if (flags & DRM_MODE_PAGE_FLIP_ASYNC) {
+               struct drm_device *dev = crtc->dev;
+               struct vc4_dev *vc4 = to_vc4_dev(dev);
+
+               if (vc4->is_vc5)
+                       return vc5_async_page_flip(crtc, fb, event, flags);
+               else
+                       return vc4_async_page_flip(crtc, fb, event, flags);
+       } else {
                return drm_atomic_helper_page_flip(crtc, fb, event, flags, ctx);
+       }
 }
 
 struct drm_crtc_state *vc4_crtc_duplicate_state(struct drm_crtc *crtc)
@@ -1136,21 +1277,38 @@ int vc4_crtc_init(struct drm_device *drm, struct vc4_crtc *vc4_crtc,
                                  crtc_funcs, NULL);
        drm_crtc_helper_add(crtc, crtc_helper_funcs);
 
-       if (!vc4->hvs->hvs5) {
+       if (!vc4->is_vc5) {
                drm_mode_crtc_set_gamma_size(crtc, ARRAY_SIZE(vc4_crtc->lut_r));
-
                drm_crtc_enable_color_mgmt(crtc, 0, false, crtc->gamma_size);
+       }
 
+
+       if (!vc4->is_vc5) {
                /* We support CTM, but only for one CRTC at a time. It's therefore
                 * implemented as private driver state in vc4_kms, not here.
                 */
-               drm_crtc_enable_color_mgmt(crtc, 0, true, crtc->gamma_size);
-       }
+               drm_crtc_enable_color_mgmt(crtc, 0, true, 0);
 
-       for (i = 0; i < crtc->gamma_size; i++) {
-               vc4_crtc->lut_r[i] = i;
-               vc4_crtc->lut_g[i] = i;
-               vc4_crtc->lut_b[i] = i;
+               /* Initialize the VC4 gamma LUTs */
+               for (i = 0; i < crtc->gamma_size; i++) {
+                       vc4_crtc->lut_r[i] = i;
+                       vc4_crtc->lut_g[i] = i;
+                       vc4_crtc->lut_b[i] = i;
+               }
+       } else {
+               /* Initialize the VC5 gamma PWL entries. Assume 12-bit pipeline,
+                * evenly spread over full range.
+                */
+               for (i = 0; i < SCALER5_DSPGAMMA_NUM_POINTS; i++) {
+                       vc4_crtc->pwl_r[i] =
+                               VC5_HVS_SET_GAMMA_ENTRY(i << 8, i << 12, 1 << 8);
+                       vc4_crtc->pwl_g[i] =
+                               VC5_HVS_SET_GAMMA_ENTRY(i << 8, i << 12, 1 << 8);
+                       vc4_crtc->pwl_b[i] =
+                               VC5_HVS_SET_GAMMA_ENTRY(i << 8, i << 12, 1 << 8);
+                       vc4_crtc->pwl_a[i] =
+                               VC5_HVS_SET_GAMMA_ENTRY(i << 8, i << 12, 1 << 8);
+               }
        }
 
        return 0;
index 6da22af..2936a7e 100644 (file)
@@ -7,6 +7,7 @@
 #include <linux/circ_buf.h>
 #include <linux/ctype.h>
 #include <linux/debugfs.h>
+#include <linux/platform_device.h>
 
 #include "vc4_drv.h"
 #include "vc4_regs.h"
@@ -26,8 +27,10 @@ vc4_debugfs_init(struct drm_minor *minor)
        struct vc4_dev *vc4 = to_vc4_dev(minor->dev);
        struct vc4_debugfs_info_entry *entry;
 
-       debugfs_create_bool("hvs_load_tracker", S_IRUGO | S_IWUSR,
-                           minor->debugfs_root, &vc4->load_tracker_enabled);
+       if (vc4->hvs && !of_device_is_compatible(vc4->hvs->pdev->dev.of_node,
+                                    "brcm,bcm2711-vc5"))
+               debugfs_create_bool("hvs_load_tracker", S_IRUGO | S_IWUSR,
+                                   minor->debugfs_root, &vc4->load_tracker_enabled);
 
        list_for_each_entry(entry, &vc4->debugfs_list, link) {
                drm_debugfs_create_files(&entry->info, 1,
index a90f254..1bf1a4c 100644 (file)
@@ -131,7 +131,7 @@ static void vc4_dpi_encoder_enable(struct drm_encoder *encoder)
        struct vc4_dpi *dpi = vc4_encoder->dpi;
        struct drm_connector_list_iter conn_iter;
        struct drm_connector *connector = NULL, *connector_scan;
-       u32 dpi_c = DPI_ENABLE | DPI_OUTPUT_ENABLE_MODE;
+       u32 dpi_c = DPI_ENABLE;
        int ret;
 
        /* Look up the connector attached to DPI so we can get the
@@ -148,49 +148,88 @@ static void vc4_dpi_encoder_enable(struct drm_encoder *encoder)
        }
        drm_connector_list_iter_end(&conn_iter);
 
-       if (connector && connector->display_info.num_bus_formats) {
-               u32 bus_format = connector->display_info.bus_formats[0];
-
-               switch (bus_format) {
-               case MEDIA_BUS_FMT_RGB888_1X24:
-                       dpi_c |= VC4_SET_FIELD(DPI_FORMAT_24BIT_888_RGB,
-                                              DPI_FORMAT);
-                       break;
-               case MEDIA_BUS_FMT_BGR888_1X24:
-                       dpi_c |= VC4_SET_FIELD(DPI_FORMAT_24BIT_888_RGB,
-                                              DPI_FORMAT);
-                       dpi_c |= VC4_SET_FIELD(DPI_ORDER_BGR, DPI_ORDER);
-                       break;
-               case MEDIA_BUS_FMT_RGB666_1X24_CPADHI:
-                       dpi_c |= VC4_SET_FIELD(DPI_FORMAT_18BIT_666_RGB_2,
-                                              DPI_FORMAT);
-                       break;
-               case MEDIA_BUS_FMT_RGB666_1X18:
+       if (connector) {
+               if (connector->display_info.num_bus_formats) {
+                       u32 bus_format = connector->display_info.bus_formats[0];
+
+                       switch (bus_format) {
+                       case MEDIA_BUS_FMT_RGB888_1X24:
+                               dpi_c |= VC4_SET_FIELD(DPI_FORMAT_24BIT_888_RGB,
+                                                      DPI_FORMAT);
+                               break;
+                       case MEDIA_BUS_FMT_BGR888_1X24:
+                               dpi_c |= VC4_SET_FIELD(DPI_FORMAT_24BIT_888_RGB,
+                                                      DPI_FORMAT);
+                               dpi_c |= VC4_SET_FIELD(DPI_ORDER_BGR,
+                                                      DPI_ORDER);
+                               break;
+                       case MEDIA_BUS_FMT_RGB666_1X24_CPADHI:
+                               dpi_c |= VC4_SET_FIELD(DPI_FORMAT_18BIT_666_RGB_2,
+                                                      DPI_FORMAT);
+                               break;
+                       case MEDIA_BUS_FMT_BGR666_1X24_CPADHI:
+                               dpi_c |= VC4_SET_FIELD(DPI_FORMAT_18BIT_666_RGB_2,
+                                                      DPI_FORMAT);
+                               dpi_c |= VC4_SET_FIELD(DPI_ORDER_BGR,
+                                                      DPI_ORDER);
+                               break;
+                       default:
+                               DRM_ERROR("Unknown media bus format %d\n",
+                                         bus_format);
+                               fallthrough;
+                       case MEDIA_BUS_FMT_RGB666_1X18:
+                               dpi_c |= VC4_SET_FIELD(DPI_FORMAT_18BIT_666_RGB_1,
+                                                      DPI_FORMAT);
+                               break;
+                       case MEDIA_BUS_FMT_BGR666_1X18:
+                               dpi_c |= VC4_SET_FIELD(DPI_FORMAT_18BIT_666_RGB_1,
+                                                      DPI_FORMAT);
+                               dpi_c |= VC4_SET_FIELD(DPI_ORDER_BGR,
+                                                      DPI_ORDER);
+                               break;
+                       case MEDIA_BUS_FMT_RGB565_1X16:
+                               dpi_c |= VC4_SET_FIELD(DPI_FORMAT_16BIT_565_RGB_1,
+                                                      DPI_FORMAT);
+                               break;
+                       case MEDIA_BUS_FMT_RGB565_1X24_CPADHI:
+                               dpi_c |= VC4_SET_FIELD(DPI_FORMAT_16BIT_565_RGB_2,
+                                                      DPI_FORMAT);
+                               break;
+                       }
+               } else {
+                       /* Default to 18bit if no connector found. */
                        dpi_c |= VC4_SET_FIELD(DPI_FORMAT_18BIT_666_RGB_1,
                                               DPI_FORMAT);
-                       break;
-               case MEDIA_BUS_FMT_RGB565_1X16:
-                       dpi_c |= VC4_SET_FIELD(DPI_FORMAT_16BIT_565_RGB_3,
-                                              DPI_FORMAT);
-                       break;
-               default:
-                       DRM_ERROR("Unknown media bus format %d\n", bus_format);
-                       break;
+
                }
+
+               if (connector->display_info.bus_flags &
+                                       DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE)
+                       dpi_c |= DPI_PIXEL_CLK_INVERT;
+
+               if (connector->display_info.bus_flags & DRM_BUS_FLAG_DE_LOW)
+                       dpi_c |= DPI_OUTPUT_ENABLE_INVERT;
        } else {
-               /* Default to 24bit if no connector found. */
-               dpi_c |= VC4_SET_FIELD(DPI_FORMAT_24BIT_888_RGB, DPI_FORMAT);
+               /* Default to 18bit if no connector found. */
+               dpi_c |= VC4_SET_FIELD(DPI_FORMAT_18BIT_666_RGB_1, DPI_FORMAT);
        }
 
-       if (mode->flags & DRM_MODE_FLAG_NHSYNC)
-               dpi_c |= DPI_HSYNC_INVERT;
-       else if (!(mode->flags & DRM_MODE_FLAG_PHSYNC))
-               dpi_c |= DPI_HSYNC_DISABLE;
+       if (mode->flags & DRM_MODE_FLAG_CSYNC) {
+               if (mode->flags & DRM_MODE_FLAG_NCSYNC)
+                       dpi_c |= DPI_OUTPUT_ENABLE_INVERT;
+       } else {
+               dpi_c |= DPI_OUTPUT_ENABLE_MODE;
+
+               if (mode->flags & DRM_MODE_FLAG_NHSYNC)
+                       dpi_c |= DPI_HSYNC_INVERT;
+               else if (!(mode->flags & DRM_MODE_FLAG_PHSYNC))
+                       dpi_c |= DPI_HSYNC_DISABLE;
 
-       if (mode->flags & DRM_MODE_FLAG_NVSYNC)
-               dpi_c |= DPI_VSYNC_INVERT;
-       else if (!(mode->flags & DRM_MODE_FLAG_PVSYNC))
-               dpi_c |= DPI_VSYNC_DISABLE;
+               if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+                       dpi_c |= DPI_VSYNC_INVERT;
+               else if (!(mode->flags & DRM_MODE_FLAG_PVSYNC))
+                       dpi_c |= DPI_VSYNC_DISABLE;
+       }
 
        DPI_WRITE(DPI_C, dpi_c);
 
index f6c16c5..93e094b 100644 (file)
@@ -37,6 +37,8 @@
 #include <drm/drm_fb_helper.h>
 #include <drm/drm_vblank.h>
 
+#include <soc/bcm2835/raspberrypi-firmware.h>
+
 #include "uapi/drm/vc4_drm.h"
 
 #include "vc4_drv.h"
@@ -63,6 +65,32 @@ void __iomem *vc4_ioremap_regs(struct platform_device *dev, int index)
        return map;
 }
 
+int vc4_dumb_fixup_args(struct drm_mode_create_dumb *args)
+{
+       int min_pitch = DIV_ROUND_UP(args->width * args->bpp, 8);
+
+       if (args->pitch < min_pitch)
+               args->pitch = min_pitch;
+
+       if (args->size < args->pitch * args->height)
+               args->size = args->pitch * args->height;
+
+       return 0;
+}
+
+static int vc4_dumb_create(struct drm_file *file_priv,
+                          struct drm_device *dev,
+                          struct drm_mode_create_dumb *args)
+{
+       int ret;
+
+       ret = vc4_dumb_fixup_args(args);
+       if (ret)
+               return ret;
+
+       return drm_gem_cma_dumb_create_internal(file_priv, dev, args);
+}
+
 static int vc4_get_param_ioctl(struct drm_device *dev, void *data,
                               struct drm_file *file_priv)
 {
@@ -73,6 +101,9 @@ static int vc4_get_param_ioctl(struct drm_device *dev, void *data,
        if (args->pad != 0)
                return -EINVAL;
 
+       if (WARN_ON_ONCE(vc4->is_vc5))
+               return -ENODEV;
+
        if (!vc4->v3d)
                return -ENODEV;
 
@@ -116,11 +147,16 @@ static int vc4_get_param_ioctl(struct drm_device *dev, void *data,
 
 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->is_vc5))
+               return -ENODEV;
+
        vc4file = kzalloc(sizeof(*vc4file), GFP_KERNEL);
        if (!vc4file)
                return -ENOMEM;
+       vc4file->dev = vc4;
 
        vc4_perfmon_open_file(vc4file);
        file->driver_priv = vc4file;
@@ -132,6 +168,9 @@ 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->is_vc5))
+               return;
+
        if (vc4file->bin_bo_used)
                vc4_v3d_bin_bo_put(vc4);
 
@@ -160,7 +199,7 @@ static const struct drm_ioctl_desc vc4_drm_ioctls[] = {
        DRM_IOCTL_DEF_DRV(VC4_PERFMON_GET_VALUES, vc4_perfmon_get_values_ioctl, DRM_RENDER_ALLOW),
 };
 
-static struct drm_driver vc4_drm_driver = {
+static const struct drm_driver vc4_drm_driver = {
        .driver_features = (DRIVER_MODESET |
                            DRIVER_ATOMIC |
                            DRIVER_GEM |
@@ -175,7 +214,7 @@ static struct drm_driver vc4_drm_driver = {
 
        .gem_create_object = vc4_create_object,
 
-       DRM_GEM_CMA_DRIVER_OPS_WITH_DUMB_CREATE(vc4_dumb_create),
+       DRM_GEM_CMA_DRIVER_OPS_WITH_DUMB_CREATE(vc4_bo_dumb_create),
 
        .ioctls = vc4_drm_ioctls,
        .num_ioctls = ARRAY_SIZE(vc4_drm_ioctls),
@@ -189,6 +228,27 @@ static struct drm_driver vc4_drm_driver = {
        .patchlevel = DRIVER_PATCHLEVEL,
 };
 
+static const struct drm_driver vc5_drm_driver = {
+       .driver_features = (DRIVER_MODESET |
+                           DRIVER_ATOMIC |
+                           DRIVER_GEM),
+
+#if defined(CONFIG_DEBUG_FS)
+       .debugfs_init = vc4_debugfs_init,
+#endif
+
+       DRM_GEM_CMA_DRIVER_OPS_WITH_DUMB_CREATE(vc4_dumb_create),
+
+       .fops = &vc4_drm_fops,
+
+       .name = DRIVER_NAME,
+       .desc = DRIVER_DESC,
+       .date = DRIVER_DATE,
+       .major = DRIVER_MAJOR,
+       .minor = DRIVER_MINOR,
+       .patchlevel = DRIVER_PATCHLEVEL,
+};
+
 static int compare_dev(struct device *dev, void *data)
 {
        return dev == data;
@@ -214,63 +274,126 @@ static void vc4_match_add_drivers(struct device *dev,
        }
 }
 
+const struct of_device_id vc4_dma_range_matches[] = {
+       { .compatible = "brcm,bcm2835-hvs" },
+       { .compatible = "brcm,bcm2711-hvs" },
+       { .compatible = "raspberrypi,rpi-firmware-kms" },
+       { .compatible = "brcm,bcm2835-v3d" },
+       { .compatible = "brcm,cygnus-v3d" },
+       { .compatible = "brcm,vc4-v3d" },
+       {}
+};
+
+/*
+ * 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);
+       const struct drm_driver *driver;
+       struct rpi_firmware *firmware = NULL;
        struct drm_device *drm;
        struct vc4_dev *vc4;
        struct device_node *node;
        struct drm_crtc *crtc;
+       bool is_vc5;
        int ret = 0;
 
        dev->coherent_dma_mask = DMA_BIT_MASK(32);
 
-       /* If VC4 V3D is missing, don't advertise render nodes. */
-       node = of_find_matching_node_and_match(NULL, vc4_v3d_dt_match, NULL);
-       if (!node || !of_device_is_available(node))
-               vc4_drm_driver.driver_features &= ~DRIVER_RENDER;
-       of_node_put(node);
+       is_vc5 = of_device_is_compatible(dev->of_node, "brcm,bcm2711-vc5");
+       if (is_vc5)
+               driver = &vc5_drm_driver;
+       else
+               driver = &vc4_drm_driver;
 
-       vc4 = devm_drm_dev_alloc(dev, &vc4_drm_driver, struct vc4_dev, base);
+       node = of_find_matching_node_and_match(NULL, vc4_dma_range_matches,
+                                              NULL);
+       if (node) {
+               ret = of_dma_configure(dev, node, true);
+               of_node_put(node);
+
+               if (ret)
+                       return ret;
+       }
+
+       vc4 = devm_drm_dev_alloc(dev, driver, struct vc4_dev, base);
        if (IS_ERR(vc4))
                return PTR_ERR(vc4);
+       vc4->is_vc5 = is_vc5;
 
        drm = &vc4->base;
        platform_set_drvdata(pdev, drm);
        INIT_LIST_HEAD(&vc4->debugfs_list);
 
-       mutex_init(&vc4->bin_bo_lock);
+       if (!is_vc5) {
+               mutex_init(&vc4->bin_bo_lock);
 
-       ret = vc4_bo_cache_init(drm);
-       if (ret)
-               return ret;
+               ret = vc4_bo_cache_init(drm);
+               if (ret)
+                       return ret;
+       }
 
        ret = drmm_mode_config_init(drm);
        if (ret)
                return ret;
 
-       ret = vc4_gem_init(drm);
+       if (!is_vc5) {
+               ret = vc4_gem_init(drm);
+               if (ret)
+                       return ret;
+       }
+
+       node = of_find_compatible_node(NULL, NULL, "raspberrypi,bcm2835-firmware");
+       if (node) {
+               firmware = rpi_firmware_get(node);
+               of_node_put(node);
+
+               if (!firmware)
+                       return -EPROBE_DEFER;
+       }
+
+       ret = drm_aperture_remove_framebuffers(false, driver);
        if (ret)
                return ret;
 
+       if (firmware && !firmware_kms()) {
+               ret = rpi_firmware_property(firmware,
+                                           RPI_FIRMWARE_NOTIFY_DISPLAY_DONE,
+                                           NULL, 0);
+               if (ret)
+                       drm_warn(drm, "Couldn't stop firmware display driver: %d\n", ret);
+
+               rpi_firmware_put(firmware);
+       }
+
        ret = component_bind_all(dev, drm);
        if (ret)
                return ret;
 
-       ret = vc4_plane_create_additional_planes(drm);
-       if (ret)
-               goto unbind_all;
-
-       ret = drm_aperture_remove_framebuffers(false, &vc4_drm_driver);
-       if (ret)
-               goto unbind_all;
+       if (!vc4->firmware_kms) {
+               ret = vc4_plane_create_additional_planes(drm);
+               if (ret)
+                       goto unbind_all;
+       }
 
        ret = vc4_kms_load(drm);
        if (ret < 0)
                goto unbind_all;
 
-       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)
@@ -317,6 +440,7 @@ static struct platform_driver *const component_drivers[] = {
        &vc4_dsi_driver,
        &vc4_txp_driver,
        &vc4_crtc_driver,
+       &vc4_firmware_kms_driver,
        &vc4_v3d_driver,
 };
 
index 94c1787..4a8940c 100644 (file)
@@ -19,6 +19,7 @@
 #include <drm/drm_modeset_lock.h>
 
 #include "uapi/drm/vc4_drm.h"
+#include "vc4_regs.h"
 
 struct drm_device;
 struct drm_gem_object;
@@ -48,6 +49,8 @@ enum vc4_kernel_bo_type {
  * done. This way, only events related to a specific job will be counted.
  */
 struct vc4_perfmon {
+       struct vc4_dev *dev;
+
        /* Tracks the number of users of the perfmon, when this counter reaches
         * zero the perfmon is destroyed.
         */
@@ -74,13 +77,19 @@ struct vc4_perfmon {
 struct vc4_dev {
        struct drm_device base;
 
+       bool is_vc5;
+
        unsigned int irq;
 
+       bool firmware_kms;
+       struct rpi_firmware *firmware;
+
        struct vc4_hvs *hvs;
        struct vc4_v3d *v3d;
        struct vc4_dpi *dpi;
        struct vc4_vec *vec;
        struct vc4_txp *txp;
+       struct vc4_fkms *fkms;
 
        struct vc4_hang_state *hang_state;
 
@@ -202,9 +211,6 @@ struct vc4_dev {
 
        int power_refcount;
 
-       /* Set to true when the load tracker is supported. */
-       bool load_tracker_available;
-
        /* Set to true when the load tracker is active. */
        bool load_tracker_enabled;
 
@@ -319,6 +325,7 @@ struct vc4_v3d {
 };
 
 struct vc4_hvs {
+       struct vc4_dev *vc4;
        struct platform_device *pdev;
        void __iomem *regs;
        u32 __iomem *dlist;
@@ -337,8 +344,19 @@ struct vc4_hvs {
 
        struct debugfs_regset32 regset;
 
-       /* HVS version 5 flag, therefore requires updated dlist structures */
-       bool hvs5;
+       /*
+        * Even if HDMI0 on the RPi4 can output modes requiring a pixel
+        * rate higher than 297MHz, it needs some adjustments in the
+        * config.txt file to be able to do so and thus won't always be
+        * available.
+        */
+       bool vc5_hdmi_enable_scrambling;
+
+       /*
+        * 4096x2160@60 requires a core overclock to work, so register
+        * whether that is sufficient.
+        */
+       bool vc5_hdmi_enable_4096by2160;
 };
 
 struct vc4_plane {
@@ -381,7 +399,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];
@@ -479,6 +497,17 @@ struct vc4_pv_data {
 
 };
 
+struct vc5_gamma_entry {
+       u32 x_c_terms;
+       u32 grad_term;
+};
+
+#define VC5_HVS_SET_GAMMA_ENTRY(x, c, g) (struct vc5_gamma_entry){     \
+       .x_c_terms = VC4_SET_FIELD((x), SCALER5_DSPGAMMA_OFF_X) |       \
+                    VC4_SET_FIELD((c), SCALER5_DSPGAMMA_OFF_C),        \
+       .grad_term = (g)                                                \
+}
+
 struct vc4_crtc {
        struct drm_crtc base;
        struct platform_device *pdev;
@@ -488,9 +517,19 @@ struct vc4_crtc {
        /* Timestamp at start of vblank irq - unaffected by lock delays. */
        ktime_t t_vblank;
 
-       u8 lut_r[256];
-       u8 lut_g[256];
-       u8 lut_b[256];
+       union {
+               struct {  /* VC4 gamma LUT */
+                       u8 lut_r[256];
+                       u8 lut_g[256];
+                       u8 lut_b[256];
+               };
+               struct {  /* VC5 gamma PWL entries */
+                       struct vc5_gamma_entry pwl_r[SCALER5_DSPGAMMA_NUM_POINTS];
+                       struct vc5_gamma_entry pwl_g[SCALER5_DSPGAMMA_NUM_POINTS];
+                       struct vc5_gamma_entry pwl_b[SCALER5_DSPGAMMA_NUM_POINTS];
+                       struct vc5_gamma_entry pwl_a[SCALER5_DSPGAMMA_NUM_POINTS];
+               };
+       };
 
        struct drm_pending_vblank_event *event;
 
@@ -544,6 +583,12 @@ vc4_crtc_to_vc4_pv_data(const struct vc4_crtc *crtc)
        return container_of(data, struct vc4_pv_data, base);
 }
 
+struct drm_connector *vc4_get_crtc_connector(struct drm_crtc *crtc,
+                                            struct drm_crtc_state *state);
+
+struct drm_encoder *vc4_get_crtc_encoder(struct drm_crtc *crtc,
+                                        struct drm_crtc_state *state);
+
 struct vc4_crtc_state {
        struct drm_crtc_state base;
        /* Dlist area for this CRTC configuration. */
@@ -551,12 +596,9 @@ struct vc4_crtc_state {
        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;
 
        /* Transitional state below, only valid during atomic commits */
        bool update_muxing;
@@ -572,12 +614,14 @@ to_vc4_crtc_state(struct drm_crtc_state *crtc_state)
 
 #define V3D_READ(offset) readl(vc4->v3d->regs + offset)
 #define V3D_WRITE(offset, val) writel(val, vc4->v3d->regs + offset)
-#define HVS_READ(offset) readl(vc4->hvs->regs + offset)
-#define HVS_WRITE(offset, val) writel(val, vc4->hvs->regs + offset)
+#define HVS_READ(offset) readl(hvs->regs + offset)
+#define HVS_WRITE(offset, val) writel(val, hvs->regs + offset)
 
 #define VC4_REG32(reg) { .name = #reg, .offset = reg }
 
 struct vc4_exec_info {
+       struct vc4_dev *dev;
+
        /* Sequence number for this bin/render job. */
        uint64_t seqno;
 
@@ -699,6 +743,8 @@ struct vc4_exec_info {
  * released when the DRM file is closed should be placed here.
  */
 struct vc4_file {
+       struct vc4_dev *dev;
+
        struct {
                struct idr idr;
                struct mutex lock;
@@ -812,9 +858,9 @@ struct vc4_validated_shader_info {
 struct drm_gem_object *vc4_create_object(struct drm_device *dev, size_t size);
 struct vc4_bo *vc4_bo_create(struct drm_device *dev, size_t size,
                             bool from_cache, enum vc4_kernel_bo_type type);
-int vc4_dumb_create(struct drm_file *file_priv,
-                   struct drm_device *dev,
-                   struct drm_mode_create_dumb *args);
+int vc4_bo_dumb_create(struct drm_file *file_priv,
+                      struct drm_device *dev,
+                      struct drm_mode_create_dumb *args);
 int vc4_create_bo_ioctl(struct drm_device *dev, void *data,
                        struct drm_file *file_priv);
 int vc4_create_shader_bo_ioctl(struct drm_device *dev, void *data,
@@ -883,6 +929,7 @@ static inline void vc4_debugfs_add_regset32(struct drm_device *drm,
 
 /* vc4_drv.c */
 void __iomem *vc4_ioremap_regs(struct platform_device *dev, int index);
+int vc4_dumb_fixup_args(struct drm_mode_create_dumb *args);
 
 /* vc4_dpi.c */
 extern struct platform_driver vc4_dpi_driver;
@@ -893,6 +940,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,
@@ -931,17 +981,17 @@ void vc4_irq_reset(struct drm_device *dev);
 
 /* vc4_hvs.c */
 extern struct platform_driver vc4_hvs_driver;
-void vc4_hvs_stop_channel(struct drm_device *dev, unsigned int output);
-int vc4_hvs_get_fifo_from_output(struct drm_device *dev, unsigned int output);
-u8 vc4_hvs_get_fifo_frame_count(struct drm_device *dev, unsigned int fifo);
+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);
 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);
 void vc4_hvs_atomic_disable(struct drm_crtc *crtc, struct drm_atomic_state *state);
 void vc4_hvs_atomic_flush(struct drm_crtc *crtc, struct drm_atomic_state *state);
-void vc4_hvs_dump_state(struct drm_device *dev);
-void vc4_hvs_unmask_underrun(struct drm_device *dev, int channel);
-void vc4_hvs_mask_underrun(struct drm_device *dev, int channel);
+void vc4_hvs_dump_state(struct vc4_hvs *hvs);
+void vc4_hvs_unmask_underrun(struct vc4_hvs *hvs, int channel);
+void vc4_hvs_mask_underrun(struct vc4_hvs *hvs, int channel);
 
 /* vc4_kms.c */
 int vc4_kms_load(struct drm_device *dev);
index ca85063..0b80247 100644 (file)
 
 #define DSI0_TXPKT_PIX_FIFO            0x20 /* AKA PIX_FIFO */
 
-#define DSI0_INT_STAT          0x24
-#define DSI0_INT_EN            0x28
+#define DSI0_INT_STAT                  0x24
+#define DSI0_INT_EN                    0x28
+# define DSI0_INT_FIFO_ERR             BIT(25)
+# define DSI0_INT_CMDC_DONE_MASK       VC4_MASK(24, 23)
+# define DSI0_INT_CMDC_DONE_SHIFT      23
+#  define DSI0_INT_CMDC_DONE_NO_REPEAT         1
+#  define DSI0_INT_CMDC_DONE_REPEAT            3
+# define DSI0_INT_PHY_DIR_RTF          BIT(22)
+# define DSI0_INT_PHY_D1_ULPS          BIT(21)
+# define DSI0_INT_PHY_D1_STOP          BIT(20)
+# define DSI0_INT_PHY_RXLPDT           BIT(19)
+# define DSI0_INT_PHY_RXTRIG           BIT(18)
+# define DSI0_INT_PHY_D0_ULPS          BIT(17)
+# define DSI0_INT_PHY_D0_LPDT          BIT(16)
+# define DSI0_INT_PHY_D0_FTR           BIT(15)
+# define DSI0_INT_PHY_D0_STOP          BIT(14)
+/* Signaled when the clock lane enters the given state. */
+# define DSI0_INT_PHY_CLK_ULPS         BIT(13)
+# define DSI0_INT_PHY_CLK_HS           BIT(12)
+# define DSI0_INT_PHY_CLK_FTR          BIT(11)
+/* Signaled on timeouts */
+# define DSI0_INT_PR_TO                        BIT(10)
+# define DSI0_INT_TA_TO                        BIT(9)
+# define DSI0_INT_LPRX_TO              BIT(8)
+# define DSI0_INT_HSTX_TO              BIT(7)
+/* Contention on a line when trying to drive the line low */
+# define DSI0_INT_ERR_CONT_LP1         BIT(6)
+# define DSI0_INT_ERR_CONT_LP0         BIT(5)
+/* Control error: incorrect line state sequence on data lane 0. */
+# define DSI0_INT_ERR_CONTROL          BIT(4)
+# define DSI0_INT_ERR_SYNC_ESC         BIT(3)
+# define DSI0_INT_RX2_PKT              BIT(2)
+# define DSI0_INT_RX1_PKT              BIT(1)
+# define DSI0_INT_CMD_PKT              BIT(0)
+
+#define DSI0_INTERRUPTS_ALWAYS_ENABLED (DSI0_INT_ERR_SYNC_ESC | \
+                                        DSI0_INT_ERR_CONTROL |  \
+                                        DSI0_INT_ERR_CONT_LP0 | \
+                                        DSI0_INT_ERR_CONT_LP1 | \
+                                        DSI0_INT_HSTX_TO |      \
+                                        DSI0_INT_LPRX_TO |      \
+                                        DSI0_INT_TA_TO |        \
+                                        DSI0_INT_PR_TO)
+
 # define DSI1_INT_PHY_D3_ULPS          BIT(30)
 # define DSI1_INT_PHY_D3_STOP          BIT(29)
 # define DSI1_INT_PHY_D2_ULPS          BIT(28)
@@ -511,8 +553,8 @@ struct vc4_dsi {
 
        struct mipi_dsi_host dsi_host;
        struct drm_encoder *encoder;
-       struct drm_bridge *bridge;
-       struct list_head bridge_chain;
+       struct drm_bridge *out_bridge;
+       struct drm_bridge bridge;
 
        void __iomem *regs;
 
@@ -614,6 +656,12 @@ to_vc4_dsi_encoder(struct drm_encoder *encoder)
        return container_of(encoder, struct vc4_dsi_encoder, base.base);
 }
 
+static inline struct vc4_dsi *
+bridge_to_vc4_dsi(struct drm_bridge *bridge)
+{
+       return container_of(bridge, struct vc4_dsi, bridge);
+}
+
 static const struct debugfs_reg32 dsi0_regs[] = {
        VC4_REG32(DSI0_CTRL),
        VC4_REG32(DSI0_STAT),
@@ -751,24 +799,22 @@ dsi_esc_timing(u32 ns)
        return DIV_ROUND_UP(ns, ESC_TIME_NS);
 }
 
-static void vc4_dsi_encoder_disable(struct drm_encoder *encoder)
+static void vc4_dsi_bridge_disable(struct drm_bridge *bridge,
+                                  struct drm_bridge_state *state)
 {
-       struct vc4_dsi_encoder *vc4_encoder = to_vc4_dsi_encoder(encoder);
-       struct vc4_dsi *dsi = vc4_encoder->dsi;
-       struct device *dev = &dsi->pdev->dev;
-       struct drm_bridge *iter;
-
-       list_for_each_entry_reverse(iter, &dsi->bridge_chain, chain_node) {
-               if (iter->funcs->disable)
-                       iter->funcs->disable(iter);
-       }
+       struct vc4_dsi *dsi = bridge_to_vc4_dsi(bridge);
+       u32 disp0_ctrl;
 
-       vc4_dsi_ulps(dsi, true);
+       disp0_ctrl = DSI_PORT_READ(DISP0_CTRL);
+       disp0_ctrl &= ~DSI_DISP0_ENABLE;
+       DSI_PORT_WRITE(DISP0_CTRL, disp0_ctrl);
+}
 
-       list_for_each_entry_from(iter, &dsi->bridge_chain, chain_node) {
-               if (iter->funcs->post_disable)
-                       iter->funcs->post_disable(iter);
-       }
+static void vc4_dsi_bridge_post_disable(struct drm_bridge *bridge,
+                                       struct drm_bridge_state *state)
+{
+       struct vc4_dsi *dsi = bridge_to_vc4_dsi(bridge);
+       struct device *dev = &dsi->pdev->dev;
 
        clk_disable_unprepare(dsi->pll_phy_clock);
        clk_disable_unprepare(dsi->escape_clock);
@@ -790,12 +836,11 @@ static void vc4_dsi_encoder_disable(struct drm_encoder *encoder)
  * higher-than-expected clock rate to the panel, but that's what the
  * firmware does too.
  */
-static bool vc4_dsi_encoder_mode_fixup(struct drm_encoder *encoder,
-                                      const struct drm_display_mode *mode,
-                                      struct drm_display_mode *adjusted_mode)
+static bool vc4_dsi_bridge_mode_fixup(struct drm_bridge *bridge,
+                                     const struct drm_display_mode *mode,
+                                     struct drm_display_mode *adjusted_mode)
 {
-       struct vc4_dsi_encoder *vc4_encoder = to_vc4_dsi_encoder(encoder);
-       struct vc4_dsi *dsi = vc4_encoder->dsi;
+       struct vc4_dsi *dsi = bridge_to_vc4_dsi(bridge);
        struct clk *phy_parent = clk_get_parent(dsi->pll_phy_clock);
        unsigned long parent_rate = clk_get_rate(phy_parent);
        unsigned long pixel_clock_hz = mode->clock * 1000;
@@ -805,11 +850,9 @@ static bool vc4_dsi_encoder_mode_fixup(struct drm_encoder *encoder,
        /* Find what divider gets us a faster clock than the requested
         * pixel clock.
         */
-       for (divider = 1; divider < 8; divider++) {
-               if (parent_rate / divider < pll_clock) {
-                       divider--;
+       for (divider = 1; divider < 255; divider++) {
+               if (parent_rate / (divider + 1) < pll_clock)
                        break;
-               }
        }
 
        /* Now that we've picked a PLL divider, calculate back to its
@@ -829,19 +872,22 @@ static bool vc4_dsi_encoder_mode_fixup(struct drm_encoder *encoder,
        return true;
 }
 
-static void vc4_dsi_encoder_enable(struct drm_encoder *encoder)
+static void vc4_dsi_bridge_pre_enable(struct drm_bridge *bridge,
+                                     struct drm_bridge_state *old_state)
 {
-       struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode;
-       struct vc4_dsi_encoder *vc4_encoder = to_vc4_dsi_encoder(encoder);
-       struct vc4_dsi *dsi = vc4_encoder->dsi;
+       struct drm_atomic_state *state = old_state->base.state;
+       struct vc4_dsi *dsi = bridge_to_vc4_dsi(bridge);
+       const struct drm_crtc_state *crtc_state;
        struct device *dev = &dsi->pdev->dev;
+       const struct drm_display_mode *mode;
+       struct drm_connector *connector;
        bool debug_dump_regs = false;
-       struct drm_bridge *iter;
        unsigned long hs_clock;
+       struct drm_crtc *crtc;
        u32 ui_ns;
        /* Minimum LP state duration in escape clock cycles. */
        u32 lpx = dsi_esc_timing(60);
-       unsigned long pixel_clock_hz = mode->clock * 1000;
+       unsigned long pixel_clock_hz;
        unsigned long dsip_clock;
        unsigned long phy_clock;
        int ret;
@@ -858,6 +904,18 @@ static void vc4_dsi_encoder_enable(struct drm_encoder *encoder)
                drm_print_regset32(&p, &dsi->regset);
        }
 
+       /*
+        * Retrieve the CRTC adjusted mode. This requires a little dance to go
+        * from the bridge to the encoder, to the connector and to the CRTC.
+        */
+       connector = drm_atomic_get_new_connector_for_encoder(state,
+                                                            bridge->encoder);
+       crtc = drm_atomic_get_new_connector_state(state, connector)->crtc;
+       crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
+       mode = &crtc_state->adjusted_mode;
+
+       pixel_clock_hz = mode->clock * 1000;
+
        /* Round up the clk_set_rate() request slightly, since
         * PLLD_DSI1 is an integer divider and its rate selection will
         * never round up.
@@ -894,6 +952,9 @@ static void vc4_dsi_encoder_enable(struct drm_encoder *encoder)
 
                DSI_PORT_WRITE(PHY_AFEC0, afec0);
 
+               /* AFEC reset hold time */
+               mdelay(1);
+
                DSI_PORT_WRITE(PHY_AFEC1,
                               VC4_SET_FIELD(6,  DSI0_PHY_AFEC1_IDR_DLANE1) |
                               VC4_SET_FIELD(6,  DSI0_PHY_AFEC1_IDR_DLANE0) |
@@ -1060,20 +1121,12 @@ static void vc4_dsi_encoder_enable(struct drm_encoder *encoder)
                DSI_PORT_WRITE(CTRL, DSI_PORT_READ(CTRL) | DSI1_CTRL_EN);
 
        /* Bring AFE out of reset. */
-       if (dsi->variant->port == 0) {
-       } else {
-               DSI_PORT_WRITE(PHY_AFEC0,
-                              DSI_PORT_READ(PHY_AFEC0) &
-                              ~DSI1_PHY_AFEC0_RESET);
-       }
+       DSI_PORT_WRITE(PHY_AFEC0,
+                      DSI_PORT_READ(PHY_AFEC0) &
+                      ~DSI_PORT_BIT(PHY_AFEC0_RESET));
 
        vc4_dsi_ulps(dsi, false);
 
-       list_for_each_entry_reverse(iter, &dsi->bridge_chain, chain_node) {
-               if (iter->funcs->pre_enable)
-                       iter->funcs->pre_enable(iter);
-       }
-
        if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO) {
                DSI_PORT_WRITE(DISP0_CTRL,
                               VC4_SET_FIELD(dsi->divider,
@@ -1081,18 +1134,23 @@ static void vc4_dsi_encoder_enable(struct drm_encoder *encoder)
                               VC4_SET_FIELD(dsi->format, DSI_DISP0_PFORMAT) |
                               VC4_SET_FIELD(DSI_DISP0_LP_STOP_PERFRAME,
                                             DSI_DISP0_LP_STOP_CTRL) |
-                              DSI_DISP0_ST_END |
-                              DSI_DISP0_ENABLE);
+                              DSI_DISP0_ST_END);
        } else {
                DSI_PORT_WRITE(DISP0_CTRL,
-                              DSI_DISP0_COMMAND_MODE |
-                              DSI_DISP0_ENABLE);
+                              DSI_DISP0_COMMAND_MODE);
        }
+}
 
-       list_for_each_entry(iter, &dsi->bridge_chain, chain_node) {
-               if (iter->funcs->enable)
-                       iter->funcs->enable(iter);
-       }
+static void vc4_dsi_bridge_enable(struct drm_bridge *bridge,
+                                 struct drm_bridge_state *old_state)
+{
+       struct vc4_dsi *dsi = bridge_to_vc4_dsi(bridge);
+       bool debug_dump_regs = false;
+       u32 disp0_ctrl;
+
+       disp0_ctrl = DSI_PORT_READ(DISP0_CTRL);
+       disp0_ctrl |= DSI_DISP0_ENABLE;
+       DSI_PORT_WRITE(DISP0_CTRL, disp0_ctrl);
 
        if (debug_dump_regs) {
                struct drm_printer p = drm_info_printer(&dsi->pdev->dev);
@@ -1101,6 +1159,16 @@ static void vc4_dsi_encoder_enable(struct drm_encoder *encoder)
        }
 }
 
+static int vc4_dsi_bridge_attach(struct drm_bridge *bridge,
+                                enum drm_bridge_attach_flags flags)
+{
+       struct vc4_dsi *dsi = bridge_to_vc4_dsi(bridge);
+
+       /* Attach the panel or bridge to the dsi bridge */
+       return drm_bridge_attach(bridge->encoder, dsi->out_bridge,
+                                &dsi->bridge, flags);
+}
+
 static ssize_t vc4_dsi_host_transfer(struct mipi_dsi_host *host,
                                     const struct mipi_dsi_msg *msg)
 {
@@ -1184,13 +1252,28 @@ static ssize_t vc4_dsi_host_transfer(struct mipi_dsi_host *host,
        /* Enable the appropriate interrupt for the transfer completion. */
        dsi->xfer_result = 0;
        reinit_completion(&dsi->xfer_completion);
-       DSI_PORT_WRITE(INT_STAT, DSI1_INT_TXPKT1_DONE | DSI1_INT_PHY_DIR_RTF);
-       if (msg->rx_len) {
-               DSI_PORT_WRITE(INT_EN, (DSI1_INTERRUPTS_ALWAYS_ENABLED |
-                                       DSI1_INT_PHY_DIR_RTF));
+       if (dsi->variant->port == 0) {
+               DSI_PORT_WRITE(INT_STAT,
+                              DSI0_INT_CMDC_DONE_MASK | DSI1_INT_PHY_DIR_RTF);
+               if (msg->rx_len) {
+                       DSI_PORT_WRITE(INT_EN, (DSI0_INTERRUPTS_ALWAYS_ENABLED |
+                                               DSI0_INT_PHY_DIR_RTF));
+               } else {
+                       DSI_PORT_WRITE(INT_EN,
+                                      (DSI0_INTERRUPTS_ALWAYS_ENABLED |
+                                       VC4_SET_FIELD(DSI0_INT_CMDC_DONE_NO_REPEAT,
+                                                     DSI0_INT_CMDC_DONE)));
+               }
        } else {
-               DSI_PORT_WRITE(INT_EN, (DSI1_INTERRUPTS_ALWAYS_ENABLED |
-                                       DSI1_INT_TXPKT1_DONE));
+               DSI_PORT_WRITE(INT_STAT,
+                              DSI1_INT_TXPKT1_DONE | DSI1_INT_PHY_DIR_RTF);
+               if (msg->rx_len) {
+                       DSI_PORT_WRITE(INT_EN, (DSI1_INTERRUPTS_ALWAYS_ENABLED |
+                                               DSI1_INT_PHY_DIR_RTF));
+               } else {
+                       DSI_PORT_WRITE(INT_EN, (DSI1_INTERRUPTS_ALWAYS_ENABLED |
+                                               DSI1_INT_TXPKT1_DONE));
+               }
        }
 
        /* Send the packet. */
@@ -1207,7 +1290,7 @@ static ssize_t vc4_dsi_host_transfer(struct mipi_dsi_host *host,
                ret = dsi->xfer_result;
        }
 
-       DSI_PORT_WRITE(INT_EN, DSI1_INTERRUPTS_ALWAYS_ENABLED);
+       DSI_PORT_WRITE(INT_EN, DSI_PORT_BIT(INTERRUPTS_ALWAYS_ENABLED));
 
        if (ret)
                goto reset_fifo_and_return;
@@ -1253,7 +1336,7 @@ reset_fifo_and_return:
                       DSI_PORT_BIT(CTRL_RESET_FIFOS));
 
        DSI_PORT_WRITE(TXPKT1C, 0);
-       DSI_PORT_WRITE(INT_EN, DSI1_INTERRUPTS_ALWAYS_ENABLED);
+       DSI_PORT_WRITE(INT_EN, DSI_PORT_BIT(INTERRUPTS_ALWAYS_ENABLED));
        return ret;
 }
 
@@ -1262,6 +1345,7 @@ static int vc4_dsi_host_attach(struct mipi_dsi_host *host,
                               struct mipi_dsi_device *device)
 {
        struct vc4_dsi *dsi = host_to_dsi(host);
+       int ret;
 
        dsi->lanes = device->lanes;
        dsi->channel = device->channel;
@@ -1296,7 +1380,15 @@ static int vc4_dsi_host_attach(struct mipi_dsi_host *host,
                return 0;
        }
 
-       return component_add(&dsi->pdev->dev, &vc4_dsi_ops);
+       drm_bridge_add(&dsi->bridge);
+
+       ret = component_add(&dsi->pdev->dev, &vc4_dsi_ops);
+       if (ret) {
+               drm_bridge_remove(&dsi->bridge);
+               return ret;
+       }
+
+       return 0;
 }
 
 static int vc4_dsi_host_detach(struct mipi_dsi_host *host,
@@ -1305,6 +1397,7 @@ static int vc4_dsi_host_detach(struct mipi_dsi_host *host,
        struct vc4_dsi *dsi = host_to_dsi(host);
 
        component_del(&dsi->pdev->dev, &vc4_dsi_ops);
+       drm_bridge_remove(&dsi->bridge);
        return 0;
 }
 
@@ -1314,10 +1407,16 @@ static const struct mipi_dsi_host_ops vc4_dsi_host_ops = {
        .transfer = vc4_dsi_host_transfer,
 };
 
-static const struct drm_encoder_helper_funcs vc4_dsi_encoder_helper_funcs = {
-       .disable = vc4_dsi_encoder_disable,
-       .enable = vc4_dsi_encoder_enable,
-       .mode_fixup = vc4_dsi_encoder_mode_fixup,
+static const struct drm_bridge_funcs vc4_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 = vc4_dsi_bridge_pre_enable,
+       .atomic_enable = vc4_dsi_bridge_enable,
+       .atomic_disable = vc4_dsi_bridge_disable,
+       .atomic_post_disable = vc4_dsi_bridge_post_disable,
+       .attach = vc4_dsi_bridge_attach,
+       .mode_fixup = vc4_dsi_bridge_mode_fixup,
 };
 
 static const struct vc4_dsi_variant bcm2711_dsi1_variant = {
@@ -1390,26 +1489,28 @@ static irqreturn_t vc4_dsi_irq_handler(int irq, void *data)
        DSI_PORT_WRITE(INT_STAT, stat);
 
        dsi_handle_error(dsi, &ret, stat,
-                        DSI1_INT_ERR_SYNC_ESC, "LPDT sync");
+                        DSI_PORT_BIT(INT_ERR_SYNC_ESC), "LPDT sync");
        dsi_handle_error(dsi, &ret, stat,
-                        DSI1_INT_ERR_CONTROL, "data lane 0 sequence");
+                        DSI_PORT_BIT(INT_ERR_CONTROL), "data lane 0 sequence");
        dsi_handle_error(dsi, &ret, stat,
-                        DSI1_INT_ERR_CONT_LP0, "LP0 contention");
+                        DSI_PORT_BIT(INT_ERR_CONT_LP0), "LP0 contention");
        dsi_handle_error(dsi, &ret, stat,
-                        DSI1_INT_ERR_CONT_LP1, "LP1 contention");
+                        DSI_PORT_BIT(INT_ERR_CONT_LP1), "LP1 contention");
        dsi_handle_error(dsi, &ret, stat,
-                        DSI1_INT_HSTX_TO, "HSTX timeout");
+                        DSI_PORT_BIT(INT_HSTX_TO), "HSTX timeout");
        dsi_handle_error(dsi, &ret, stat,
-                        DSI1_INT_LPRX_TO, "LPRX timeout");
+                        DSI_PORT_BIT(INT_LPRX_TO), "LPRX timeout");
        dsi_handle_error(dsi, &ret, stat,
-                        DSI1_INT_TA_TO, "turnaround timeout");
+                        DSI_PORT_BIT(INT_TA_TO), "turnaround timeout");
        dsi_handle_error(dsi, &ret, stat,
-                        DSI1_INT_PR_TO, "peripheral reset timeout");
+                        DSI_PORT_BIT(INT_PR_TO), "peripheral reset timeout");
 
-       if (stat & (DSI1_INT_TXPKT1_DONE | DSI1_INT_PHY_DIR_RTF)) {
+       if (stat & ((dsi->variant->port ? DSI1_INT_TXPKT1_DONE :
+                                         DSI0_INT_CMDC_DONE_MASK) |
+                   DSI_PORT_BIT(INT_PHY_DIR_RTF))) {
                complete(&dsi->xfer_completion);
                ret = IRQ_HANDLED;
-       } else if (stat & DSI1_INT_HSTX_TO) {
+       } else if (stat & DSI_PORT_BIT(INT_HSTX_TO)) {
                complete(&dsi->xfer_completion);
                dsi->xfer_result = -ETIMEDOUT;
                ret = IRQ_HANDLED;
@@ -1509,8 +1610,8 @@ static int vc4_dsi_bind(struct device *dev, struct device *master, void *data)
        if (!vc4_dsi_encoder)
                return -ENOMEM;
 
-       INIT_LIST_HEAD(&dsi->bridge_chain);
-       vc4_dsi_encoder->base.type = VC4_ENCODER_TYPE_DSI1;
+       vc4_dsi_encoder->base.type = dsi->variant->port ?
+                       VC4_ENCODER_TYPE_DSI1 : VC4_ENCODER_TYPE_DSI0;
        vc4_dsi_encoder->dsi = dsi;
        dsi->encoder = &vc4_dsi_encoder->base.base;
 
@@ -1549,7 +1650,7 @@ static int vc4_dsi_bind(struct device *dev, struct device *master, void *data)
                        if (ret != -EPROBE_DEFER)
                                DRM_ERROR("Failed to get DMA channel: %d\n",
                                          ret);
-                       return ret;
+                       goto err_free_dma_mem;
                }
 
                /* Get the physical address of the device's registers.  The
@@ -1578,7 +1679,7 @@ static int vc4_dsi_bind(struct device *dev, struct device *master, void *data)
        if (ret) {
                if (ret != -EPROBE_DEFER)
                        dev_err(dev, "Failed to get interrupt: %d\n", ret);
-               return ret;
+               goto err_free_dma;
        }
 
        dsi->escape_clock = devm_clk_get(dev, "escape");
@@ -1586,7 +1687,7 @@ static int vc4_dsi_bind(struct device *dev, struct device *master, void *data)
                ret = PTR_ERR(dsi->escape_clock);
                if (ret != -EPROBE_DEFER)
                        dev_err(dev, "Failed to get escape clock: %d\n", ret);
-               return ret;
+               goto err_free_dma;
        }
 
        dsi->pll_phy_clock = devm_clk_get(dev, "phy");
@@ -1594,7 +1695,7 @@ static int vc4_dsi_bind(struct device *dev, struct device *master, void *data)
                ret = PTR_ERR(dsi->pll_phy_clock);
                if (ret != -EPROBE_DEFER)
                        dev_err(dev, "Failed to get phy clock: %d\n", ret);
-               return ret;
+               goto err_free_dma;
        }
 
        dsi->pixel_clock = devm_clk_get(dev, "pixel");
@@ -1602,11 +1703,11 @@ static int vc4_dsi_bind(struct device *dev, struct device *master, void *data)
                ret = PTR_ERR(dsi->pixel_clock);
                if (ret != -EPROBE_DEFER)
                        dev_err(dev, "Failed to get pixel clock: %d\n", ret);
-               return ret;
+               goto err_free_dma;
        }
 
        ret = drm_of_find_panel_or_bridge(dev->of_node, 0, 0,
-                                         &panel, &dsi->bridge);
+                                         &panel, &dsi->out_bridge);
        if (ret) {
                /* If the bridge or panel pointed by dev->of_node is not
                 * enabled, just return 0 here so that we don't prevent the DRM
@@ -1617,45 +1718,55 @@ static int vc4_dsi_bind(struct device *dev, struct device *master, void *data)
                if (ret == -ENODEV)
                        return 0;
 
-               return ret;
+               goto err_free_dma;
        }
 
        if (panel) {
-               dsi->bridge = devm_drm_panel_bridge_add_typed(dev, panel,
-                                                             DRM_MODE_CONNECTOR_DSI);
-               if (IS_ERR(dsi->bridge))
-                       return PTR_ERR(dsi->bridge);
+               dsi->out_bridge = devm_drm_panel_bridge_add_typed(dev, panel,
+                                                                 DRM_MODE_CONNECTOR_DSI);
+               if (IS_ERR(dsi->out_bridge)) {
+                       ret = PTR_ERR(dsi->out_bridge);
+                       goto err_free_dma;
+               }
        }
 
        /* The esc clock rate is supposed to always be 100Mhz. */
        ret = clk_set_rate(dsi->escape_clock, 100 * 1000000);
        if (ret) {
                dev_err(dev, "Failed to set esc clock: %d\n", ret);
-               return ret;
+               goto err_free_dma;
        }
 
        ret = vc4_dsi_init_phy_clocks(dsi);
        if (ret)
-               return ret;
+               goto err_free_dma;
 
        drm_simple_encoder_init(drm, dsi->encoder, DRM_MODE_ENCODER_DSI);
-       drm_encoder_helper_add(dsi->encoder, &vc4_dsi_encoder_helper_funcs);
 
-       ret = drm_bridge_attach(dsi->encoder, dsi->bridge, NULL, 0);
-       if (ret)
-               return ret;
-       /* Disable the atomic helper calls into the bridge.  We
-        * manually call the bridge pre_enable / enable / etc. calls
-        * from our driver, since we need to sequence them within the
-        * encoder's enable/disable paths.
-        */
-       list_splice_init(&dsi->encoder->bridge_chain, &dsi->bridge_chain);
+       ret = drm_bridge_attach(dsi->encoder, &dsi->bridge, NULL, 0);
+       if (ret) {
+               dev_err(dev, "bridge attach failed: %d\n", ret);
+               goto err_free_dma;
+       }
 
        vc4_debugfs_add_regset32(drm, dsi->variant->debugfs_name, &dsi->regset);
 
        pm_runtime_enable(dev);
 
        return 0;
+
+err_free_dma:
+       if (dsi->reg_dma_chan) {
+               dma_release_channel(dsi->reg_dma_chan);
+               dsi->reg_dma_chan = NULL;
+       }
+err_free_dma_mem:
+       if (dsi->reg_dma_mem) {
+               dma_free_coherent(dev, 4, dsi->reg_dma_mem, dsi->reg_dma_paddr);
+               dsi->reg_dma_mem = NULL;
+       }
+
+       return ret;
 }
 
 static void vc4_dsi_unbind(struct device *dev, struct device *master,
@@ -1663,15 +1774,20 @@ static void vc4_dsi_unbind(struct device *dev, struct device *master,
 {
        struct vc4_dsi *dsi = dev_get_drvdata(dev);
 
-       if (dsi->bridge)
+       if (dsi->out_bridge)
                pm_runtime_disable(dev);
 
-       /*
-        * Restore the bridge_chain so the bridge detach procedure can happen
-        * normally.
-        */
-       list_splice_init(&dsi->bridge_chain, &dsi->encoder->bridge_chain);
        drm_encoder_cleanup(dsi->encoder);
+
+       if (dsi->reg_dma_chan) {
+               dma_release_channel(dsi->reg_dma_chan);
+               dsi->reg_dma_chan = NULL;
+       }
+
+       if (dsi->reg_dma_mem) {
+               dma_free_coherent(dev, 4, dsi->reg_dma_mem, dsi->reg_dma_paddr);
+               dsi->reg_dma_mem = NULL;
+       }
 }
 
 static const struct component_ops vc4_dsi_ops = {
@@ -1689,6 +1805,10 @@ static int vc4_dsi_dev_probe(struct platform_device *pdev)
                return -ENOMEM;
        dev_set_drvdata(dev, dsi);
 
+       dsi->bridge.funcs = &vc4_dsi_bridge_funcs;
+       dsi->bridge.of_node = dev->of_node;
+       dsi->bridge.type = DRM_MODE_CONNECTOR_DSI;
+
        dsi->pdev = pdev;
        dsi->dsi_host.ops = &vc4_dsi_host_ops;
        dsi->dsi_host.dev = dev;
diff --git a/drivers/gpu/drm/vc4/vc4_firmware_kms.c b/drivers/gpu/drm/vc4/vc4_firmware_kms.c
new file mode 100644 (file)
index 0000000..99e379c
--- /dev/null
@@ -0,0 +1,1994 @@
+// 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_drv.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_fourcc.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 <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
+};
+
+struct vc4_fkms {
+       struct get_display_cfg cfg;
+       bool bcm2711;
+};
+
+#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,
+       },
+/*
+ *     FIXME: Need to resolve which DRM format goes to which vc_image format
+ *     for the remaining RGBA and RGBX formats.
+ *     {
+ *             .drm = DRM_FORMAT_ABGR8888,
+ *             .vc_image = VC_IMAGE_RGBA8888,
+ *     },
+ *     {
+ *             .drm = DRM_FORMAT_XBGR8888,
+ *             .vc_image = VC_IMAGE_RGBA8888,
+ *     },
+ */
+       {
+               .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
+
+#define vc4_crtc vc4_kms_crtc
+#define to_vc4_crtc to_vc4_kms_crtc
+struct vc4_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_crtc *to_vc4_crtc(struct drm_crtc *crtc)
+{
+       return container_of(crtc, struct vc4_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_cma_object *bo = drm_fb_cma_get_gem_obj(fb, 0);
+       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;
+       mb->plane.planes[0] = bo->paddr + 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.
+                */
+               mb->plane.planes[1] = bo->paddr + fb->offsets[1];
+               if (num_planes > 2)
+                       mb->plane.planes[2] = bo->paddr + 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_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);
+}
+
+/* Called during init to allocate the plane's atomic state. */
+static void vc4_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_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_plane_reset,
+       .atomic_duplicate_state = vc4_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_plane_atomic_check,
+       .atomic_update = vc4_plane_atomic_update,
+       .atomic_disable = vc4_plane_atomic_disable,
+};
+
+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_crtc *vc4_crtc = to_vc4_crtc(crtc);
+       struct drm_display_mode *mode = &crtc->state->adjusted_mode;
+       struct vc4_fkms_encoder *vc4_encoder =
+                                       to_vc4_fkms_encoder(vc4_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_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_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_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_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_crtc *vc4_crtc = to_vc4_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_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_crtc *vc4_crtc = to_vc4_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_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->bcm2711 &&
+           (vc4_crtc->display_number == 2 || vc4_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_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_crtc *vc4_crtc)
+{
+       struct drm_crtc *crtc = &vc4_crtc->base;
+       struct drm_device *dev = crtc->dev;
+       unsigned long flags;
+
+       spin_lock_irqsave(&dev->event_lock, flags);
+       if (vc4_crtc->event) {
+               drm_crtc_send_vblank_event(crtc, vc4_crtc->event);
+               vc4_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_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 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_crtc *vc4_crtc = to_vc4_crtc(crtc);
+
+       DRM_DEBUG_KMS("[CRTC:%d] enable_vblank.\n",
+                     crtc->base.id);
+       vc4_crtc->vblank_enabled = true;
+
+       return 0;
+}
+
+static void vc4_fkms_disable_vblank(struct drm_crtc *crtc)
+{
+       struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
+
+       DRM_DEBUG_KMS("[CRTC:%d] disable_vblank.\n",
+                     crtc->base.id);
+       vc4_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_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" },
+       { .compatible = "raspberrypi,rpi-firmware-kms-2711",
+         .data = (void *)1 },
+       {}
+};
+
+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;
+       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 {
+               edid = drm_do_get_edid(connector, vc4_fkms_get_edid_block,
+                                      fkms_connector);
+
+               /* 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, edid);
+               kfree(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.
+ */
+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.
+ */
+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.
+ */
+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;
+}
+
+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_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_crtc **ret_crtc)
+{
+       struct vc4_dev *vc4 = to_vc4_dev(drm);
+       struct vc4_crtc *vc4_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_crtc = devm_kzalloc(dev, sizeof(*vc4_crtc), GFP_KERNEL);
+       if (!vc4_crtc)
+               return -ENOMEM;
+       crtc = &vc4_crtc->base;
+
+       vc4_crtc->display_number = display_ref;
+       vc4_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_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_crtc->display_type, NULL);
+       drm_encoder_helper_add(&vc4_encoder->base,
+                              &vc4_fkms_encoder_helper_funcs);
+
+       vc4_crtc->connector = vc4_fkms_connector_init(drm, &vc4_encoder->base,
+                                                     display_ref);
+       if (IS_ERR(vc4_crtc->connector)) {
+               ret = PTR_ERR(vc4_crtc->connector);
+               goto err_destroy_encoder;
+       }
+
+       *ret_crtc = vc4_crtc;
+
+       return 0;
+
+err_destroy_encoder:
+       vc4_fkms_encoder_destroy(vc4_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_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;
+       if (match->data)
+               fkms->bcm2711 = true;
+
+       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) {
+               /* 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_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 int vc4_fkms_remove(struct platform_device *pdev)
+{
+       component_del(&pdev->dev, &vc4_fkms_ops);
+       return 0;
+}
+
+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,
+       },
+};
index 445d3ba..f844350 100644 (file)
@@ -76,6 +76,9 @@ vc4_get_hang_state_ioctl(struct drm_device *dev, void *data,
        u32 i;
        int ret = 0;
 
+       if (WARN_ON_ONCE(vc4->is_vc5))
+               return -ENODEV;
+
        if (!vc4->v3d) {
                DRM_DEBUG("VC4_GET_HANG_STATE with no VC4 V3D probed\n");
                return -ENODEV;
@@ -386,6 +389,9 @@ 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->is_vc5))
+               return -ENODEV;
+
        if (vc4->finished_seqno >= seqno)
                return 0;
 
@@ -468,6 +474,9 @@ 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->is_vc5))
+               return;
+
 again:
        exec = vc4_first_bin_job(vc4);
        if (!exec)
@@ -511,6 +520,9 @@ vc4_submit_next_render_job(struct drm_device *dev)
        if (!exec)
                return;
 
+       if (WARN_ON_ONCE(vc4->is_vc5))
+               return;
+
        /* A previous RCL may have written to one of our textures, and
         * our full cache flush at bin time may have occurred before
         * that RCL completed.  Flush the texture cache now, but not
@@ -528,6 +540,9 @@ 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->is_vc5))
+               return;
+
        list_move_tail(&exec->head, &vc4->render_job_list);
        if (was_empty)
                vc4_submit_next_render_job(dev);
@@ -992,6 +1007,9 @@ vc4_job_handle_completed(struct vc4_dev *vc4)
        unsigned long irqflags;
        struct vc4_seqno_cb *cb, *cb_temp;
 
+       if (WARN_ON_ONCE(vc4->is_vc5))
+               return;
+
        spin_lock_irqsave(&vc4->job_lock, irqflags);
        while (!list_empty(&vc4->job_done_list)) {
                struct vc4_exec_info *exec =
@@ -1028,6 +1046,9 @@ 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->is_vc5))
+               return -ENODEV;
+
        cb->func = func;
        INIT_WORK(&cb->work, vc4_seqno_cb_work);
 
@@ -1078,8 +1099,12 @@ int
 vc4_wait_seqno_ioctl(struct drm_device *dev, void *data,
                     struct drm_file *file_priv)
 {
+       struct vc4_dev *vc4 = to_vc4_dev(dev);
        struct drm_vc4_wait_seqno *args = data;
 
+       if (WARN_ON_ONCE(vc4->is_vc5))
+               return -ENODEV;
+
        return vc4_wait_for_seqno_ioctl_helper(dev, args->seqno,
                                               &args->timeout_ns);
 }
@@ -1088,11 +1113,15 @@ int
 vc4_wait_bo_ioctl(struct drm_device *dev, void *data,
                  struct drm_file *file_priv)
 {
+       struct vc4_dev *vc4 = to_vc4_dev(dev);
        int ret;
        struct drm_vc4_wait_bo *args = data;
        struct drm_gem_object *gem_obj;
        struct vc4_bo *bo;
 
+       if (WARN_ON_ONCE(vc4->is_vc5))
+               return -ENODEV;
+
        if (args->pad != 0)
                return -EINVAL;
 
@@ -1135,6 +1164,9 @@ vc4_submit_cl_ioctl(struct drm_device *dev, void *data,
        struct dma_fence *in_fence;
        int ret = 0;
 
+       if (WARN_ON_ONCE(vc4->is_vc5))
+               return -ENODEV;
+
        if (!vc4->v3d) {
                DRM_DEBUG("VC4_SUBMIT_CL with no VC4 V3D probed\n");
                return -ENODEV;
@@ -1158,6 +1190,7 @@ vc4_submit_cl_ioctl(struct drm_device *dev, void *data,
                DRM_ERROR("malloc failure on exec struct\n");
                return -ENOMEM;
        }
+       exec->dev = vc4;
 
        ret = vc4_v3d_pm_get(vc4);
        if (ret) {
@@ -1267,6 +1300,9 @@ int vc4_gem_init(struct drm_device *dev)
 {
        struct vc4_dev *vc4 = to_vc4_dev(dev);
 
+       if (WARN_ON_ONCE(vc4->is_vc5))
+               return -ENODEV;
+
        vc4->dma_fence_context = dma_fence_context_alloc(1);
 
        INIT_LIST_HEAD(&vc4->bin_job_list);
@@ -1312,11 +1348,15 @@ static void vc4_gem_destroy(struct drm_device *dev, void *unused)
 int vc4_gem_madvise_ioctl(struct drm_device *dev, void *data,
                          struct drm_file *file_priv)
 {
+       struct vc4_dev *vc4 = to_vc4_dev(dev);
        struct drm_vc4_gem_madvise *args = data;
        struct drm_gem_object *gem_obj;
        struct vc4_bo *bo;
        int ret;
 
+       if (WARN_ON_ONCE(vc4->is_vc5))
+               return -ENODEV;
+
        switch (args->madv) {
        case VC4_MADV_DONTNEED:
        case VC4_MADV_WILLNEED:
index ef7bea7..7e64944 100644 (file)
@@ -40,6 +40,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_address.h>
 #include <linux/of_gpio.h>
 #include <linux/of_platform.h>
 #include "vc4_hdmi_regs.h"
 #include "vc4_regs.h"
 
+/*
+ * "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
+
 #define VC5_HDMI_HORZA_HFP_SHIFT               16
 #define VC5_HDMI_HORZA_HFP_MASK                        VC4_MASK(28, 16)
 #define VC5_HDMI_HORZA_VPOS                    BIT(15)
 #define VC5_HDMI_VERTB_VSPO_SHIFT              16
 #define VC5_HDMI_VERTB_VSPO_MASK               VC4_MASK(29, 16)
 
+#define VC4_HDMI_MISC_CONTROL_PIXEL_REP_SHIFT  0
+#define VC4_HDMI_MISC_CONTROL_PIXEL_REP_MASK   VC4_MASK(3, 0)
+#define VC5_HDMI_MISC_CONTROL_PIXEL_REP_SHIFT  0
+#define VC5_HDMI_MISC_CONTROL_PIXEL_REP_MASK   VC4_MASK(3, 0)
+
 #define VC5_HDMI_SCRAMBLER_CTL_ENABLE          BIT(0)
 
 #define VC5_HDMI_DEEP_COLOR_CONFIG_1_INIT_PACK_PHASE_SHIFT     8
 
 #define HDMI_14_MAX_TMDS_CLK   (340 * 1000 * 1000)
 
-static bool vc4_hdmi_mode_needs_scrambling(const struct drm_display_mode *mode)
+/* bit field to force hotplug detection. bit0 = HDMI0 */
+static int force_hotplug = 0;
+module_param(force_hotplug, int, 0644);
+
+static const char * const output_format_str[] = {
+       [VC4_HDMI_OUTPUT_RGB]           = "RGB",
+       [VC4_HDMI_OUTPUT_YUV420]        = "YUV 4:2:0",
+       [VC4_HDMI_OUTPUT_YUV422]        = "YUV 4:2:2",
+       [VC4_HDMI_OUTPUT_YUV444]        = "YUV 4:4:4",
+};
+
+static const char *vc4_hdmi_output_fmt_str(enum vc4_hdmi_output_format fmt)
 {
-       return (mode->clock * 1000) > HDMI_14_MAX_TMDS_CLK;
+       if (fmt >= ARRAY_SIZE(output_format_str))
+               return "invalid";
+
+       return output_format_str[fmt];
+}
+
+static unsigned long long
+vc4_hdmi_encoder_compute_mode_clock(const struct drm_display_mode *mode,
+                                   unsigned int bpc, enum vc4_hdmi_output_format fmt);
+
+static bool vc4_hdmi_mode_needs_scrambling(const struct drm_display_mode *mode,
+                                          unsigned int bpc,
+                                          enum vc4_hdmi_output_format fmt)
+{
+       unsigned long long clock = vc4_hdmi_encoder_compute_mode_clock(mode, bpc, fmt);
+
+       return clock > HDMI_14_MAX_TMDS_CLK;
+}
+
+static bool vc4_hdmi_is_full_range(struct vc4_hdmi *vc4_hdmi,
+                                  const struct drm_display_mode *mode)
+{
+       struct vc4_hdmi_encoder *vc4_encoder = &vc4_hdmi->encoder;
+
+       if (vc4_hdmi->broadcast_rgb == VC4_BROADCAST_RGB_LIMITED)
+               return false;
+       else if (vc4_hdmi->broadcast_rgb == VC4_BROADCAST_RGB_FULL)
+               return true;
+       return !vc4_encoder->hdmi_monitor ||
+               drm_default_rgb_quant_range(mode) == HDMI_QUANTIZATION_RANGE_FULL;
 }
 
 static int vc4_hdmi_debugfs_regs(struct seq_file *m, void *unused)
@@ -113,12 +168,22 @@ static int vc4_hdmi_debugfs_regs(struct seq_file *m, void *unused)
 
        drm_print_regset32(&p, &vc4_hdmi->hdmi_regset);
        drm_print_regset32(&p, &vc4_hdmi->hd_regset);
+       drm_print_regset32(&p, &vc4_hdmi->cec_regset);
+       drm_print_regset32(&p, &vc4_hdmi->csc_regset);
+       drm_print_regset32(&p, &vc4_hdmi->dvp_regset);
+       drm_print_regset32(&p, &vc4_hdmi->phy_regset);
+       drm_print_regset32(&p, &vc4_hdmi->ram_regset);
+       drm_print_regset32(&p, &vc4_hdmi->rm_regset);
 
        return 0;
 }
 
 static void vc4_hdmi_reset(struct vc4_hdmi *vc4_hdmi)
 {
+       unsigned long flags;
+
+       spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
+
        HDMI_WRITE(HDMI_M_CTL, VC4_HD_M_SW_RST);
        udelay(1);
        HDMI_WRITE(HDMI_M_CTL, 0);
@@ -130,24 +195,36 @@ static void vc4_hdmi_reset(struct vc4_hdmi *vc4_hdmi)
                   VC4_HDMI_SW_RESET_FORMAT_DETECT);
 
        HDMI_WRITE(HDMI_SW_RESET_CONTROL, 0);
+
+       spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
 }
 
 static void vc5_hdmi_reset(struct vc4_hdmi *vc4_hdmi)
 {
+       unsigned long flags;
+
        reset_control_reset(vc4_hdmi->reset);
 
+       spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
+
        HDMI_WRITE(HDMI_DVP_CTL, 0);
 
        HDMI_WRITE(HDMI_CLOCK_STOP,
                   HDMI_READ(HDMI_CLOCK_STOP) | VC4_DVP_HT_CLOCK_STOP_PIXEL);
+
+       spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
 }
 
 #ifdef CONFIG_DRM_VC4_HDMI_CEC
 static void vc4_hdmi_cec_update_clk_div(struct vc4_hdmi *vc4_hdmi)
 {
+       unsigned long cec_rate = clk_get_rate(vc4_hdmi->cec_clock);
+       unsigned long flags;
        u16 clk_cnt;
        u32 value;
 
+       spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
+
        value = HDMI_READ(HDMI_CEC_CNTRL_1);
        value &= ~VC4_HDMI_CEC_DIV_CLK_CNT_MASK;
 
@@ -155,9 +232,11 @@ static void vc4_hdmi_cec_update_clk_div(struct vc4_hdmi *vc4_hdmi)
         * Set the clock divider: the hsm_clock rate and this divider
         * setting will give a 40 kHz CEC clock.
         */
-       clk_cnt = clk_get_rate(vc4_hdmi->cec_clock) / CEC_CLOCK_FREQ;
+       clk_cnt = cec_rate / CEC_CLOCK_FREQ;
        value |= clk_cnt << VC4_HDMI_CEC_DIV_CLK_CNT_SHIFT;
        HDMI_WRITE(HDMI_CEC_CNTRL_1, value);
+
+       spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
 }
 #else
 static void vc4_hdmi_cec_update_clk_div(struct vc4_hdmi *vc4_hdmi) {}
@@ -171,15 +250,19 @@ vc4_hdmi_connector_detect(struct drm_connector *connector, bool force)
        struct vc4_hdmi *vc4_hdmi = connector_to_vc4_hdmi(connector);
        bool connected = false;
 
+       mutex_lock(&vc4_hdmi->mutex);
+
        WARN_ON(pm_runtime_resume_and_get(&vc4_hdmi->pdev->dev));
 
-       if (vc4_hdmi->hpd_gpio &&
-           gpiod_get_value_cansleep(vc4_hdmi->hpd_gpio)) {
-               connected = true;
-       } else if (drm_probe_ddc(vc4_hdmi->ddc)) {
-               connected = true;
-       } else if (HDMI_READ(HDMI_HOTPLUG) & VC4_HDMI_HOTPLUG_CONNECTED) {
+       if (force_hotplug & BIT(vc4_hdmi->encoder.base.type - VC4_ENCODER_TYPE_HDMI0))
                connected = true;
+       else if (vc4_hdmi->hpd_gpio) {
+               if (gpiod_get_value_cansleep(vc4_hdmi->hpd_gpio))
+                       connected = true;
+       } else {
+               if (vc4_hdmi->variant->hp_detect &&
+                   vc4_hdmi->variant->hp_detect(vc4_hdmi))
+                       connected = true;
        }
 
        if (connected) {
@@ -190,16 +273,22 @@ vc4_hdmi_connector_detect(struct drm_connector *connector, bool force)
                                cec_s_phys_addr_from_edid(vc4_hdmi->cec_adap, edid);
                                vc4_hdmi->encoder.hdmi_monitor = drm_detect_hdmi_monitor(edid);
                                kfree(edid);
+                       } else {
+                               vc4_hdmi->encoder.hdmi_monitor = false;
                        }
                }
 
                vc4_hdmi_enable_scrambling(&vc4_hdmi->encoder.base.base);
                pm_runtime_put(&vc4_hdmi->pdev->dev);
+               mutex_unlock(&vc4_hdmi->mutex);
                return connector_status_connected;
        }
 
+       vc4_hdmi->encoder.hdmi_monitor = false;
+
        cec_phys_addr_invalidate(vc4_hdmi->cec_adap);
        pm_runtime_put(&vc4_hdmi->pdev->dev);
+       mutex_unlock(&vc4_hdmi->mutex);
        return connector_status_disconnected;
 }
 
@@ -213,13 +302,18 @@ static int vc4_hdmi_connector_get_modes(struct drm_connector *connector)
 {
        struct vc4_hdmi *vc4_hdmi = connector_to_vc4_hdmi(connector);
        struct vc4_hdmi_encoder *vc4_encoder = &vc4_hdmi->encoder;
+       struct vc4_dev *vc4 = to_vc4_dev(connector->dev);
        int ret = 0;
        struct edid *edid;
 
+       mutex_lock(&vc4_hdmi->mutex);
+
        edid = drm_get_edid(connector, vc4_hdmi->ddc);
        cec_s_phys_addr_from_edid(vc4_hdmi->cec_adap, edid);
-       if (!edid)
-               return -ENODEV;
+       if (!edid) {
+               ret = -ENODEV;
+               goto out;
+       }
 
        vc4_encoder->hdmi_monitor = drm_detect_hdmi_monitor(edid);
 
@@ -227,18 +321,21 @@ static int vc4_hdmi_connector_get_modes(struct drm_connector *connector)
        ret = drm_add_edid_modes(connector, edid);
        kfree(edid);
 
-       if (vc4_hdmi->disable_4kp60) {
+       if (!vc4->hvs->vc5_hdmi_enable_scrambling) {
                struct drm_device *drm = connector->dev;
                struct drm_display_mode *mode;
 
                list_for_each_entry(mode, &connector->probed_modes, head) {
-                       if (vc4_hdmi_mode_needs_scrambling(mode)) {
+                       if (vc4_hdmi_mode_needs_scrambling(mode, 8, VC4_HDMI_OUTPUT_RGB)) {
                                drm_warn_once(drm, "The core clock cannot reach frequencies high enough to support 4k @ 60Hz.");
                                drm_warn_once(drm, "Please change your config.txt file to add hdmi_enable_4kp60.");
                        }
                }
        }
 
+out:
+       mutex_unlock(&vc4_hdmi->mutex);
+
        return ret;
 }
 
@@ -247,14 +344,17 @@ static int vc4_hdmi_connector_atomic_check(struct drm_connector *connector,
 {
        struct drm_connector_state *old_state =
                drm_atomic_get_old_connector_state(state, connector);
+       struct vc4_hdmi_connector_state *old_vc4_state = conn_state_to_vc4_hdmi_conn_state(old_state);
        struct drm_connector_state *new_state =
                drm_atomic_get_new_connector_state(state, connector);
+       struct vc4_hdmi_connector_state *new_vc4_state = conn_state_to_vc4_hdmi_conn_state(new_state);
        struct drm_crtc *crtc = new_state->crtc;
 
        if (!crtc)
                return 0;
 
        if (old_state->colorspace != new_state->colorspace ||
+           old_vc4_state->broadcast_rgb != new_vc4_state->broadcast_rgb ||
            !drm_connector_atomic_hdr_metadata_equal(old_state, new_state)) {
                struct drm_crtc_state *crtc_state;
 
@@ -268,6 +368,65 @@ static int vc4_hdmi_connector_atomic_check(struct drm_connector *connector,
        return 0;
 }
 
+/**
+ * vc4_hdmi_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.
+ */
+int vc4_hdmi_connector_get_property(struct drm_connector *connector,
+                                   const struct drm_connector_state *state,
+                                   struct drm_property *property,
+                                   uint64_t *val)
+{
+       struct vc4_hdmi *vc4_hdmi = connector_to_vc4_hdmi(connector);
+       const struct vc4_hdmi_connector_state *vc4_conn_state =
+                               const_conn_state_to_vc4_hdmi_conn_state(state);
+
+       if (property == vc4_hdmi->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_hdmi_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.
+ */
+int vc4_hdmi_connector_set_property(struct drm_connector *connector,
+                                   struct drm_connector_state *state,
+                                   struct drm_property *property,
+                                   uint64_t val)
+{
+       struct vc4_hdmi *vc4_hdmi = connector_to_vc4_hdmi(connector);
+       struct vc4_hdmi_connector_state *vc4_conn_state =
+                               conn_state_to_vc4_hdmi_conn_state(state);
+
+       if (property == vc4_hdmi->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 void vc4_hdmi_connector_reset(struct drm_connector *connector)
 {
        struct vc4_hdmi_connector_state *old_state =
@@ -286,6 +445,7 @@ static void vc4_hdmi_connector_reset(struct drm_connector *connector)
 
        new_state->base.max_bpc = 8;
        new_state->base.max_requested_bpc = 8;
+       new_state->output_format = VC4_HDMI_OUTPUT_RGB;
        drm_atomic_helper_connector_tv_reset(connector);
 }
 
@@ -301,6 +461,9 @@ vc4_hdmi_connector_duplicate_state(struct drm_connector *connector)
                return NULL;
 
        new_state->pixel_rate = vc4_state->pixel_rate;
+       new_state->output_bpc = vc4_state->output_bpc;
+       new_state->output_format = vc4_state->output_format;
+       new_state->broadcast_rgb = vc4_state->broadcast_rgb;
        __drm_atomic_helper_connector_duplicate_state(connector, &new_state->base);
 
        return &new_state->base;
@@ -313,6 +476,8 @@ static const struct drm_connector_funcs vc4_hdmi_connector_funcs = {
        .reset = vc4_hdmi_connector_reset,
        .atomic_duplicate_state = vc4_hdmi_connector_duplicate_state,
        .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+       .atomic_get_property = vc4_hdmi_connector_get_property,
+       .atomic_set_property = vc4_hdmi_connector_set_property,
 };
 
 static const struct drm_connector_helper_funcs vc4_hdmi_connector_helper_funcs = {
@@ -320,6 +485,32 @@ static const struct drm_connector_helper_funcs vc4_hdmi_connector_helper_funcs =
        .atomic_check = vc4_hdmi_connector_atomic_check,
 };
 
+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_hdmi_attach_broadcast_rgb_property(struct drm_device *dev,
+                                      struct vc4_hdmi *vc4_hdmi)
+{
+       struct drm_property *prop = vc4_hdmi->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;
+
+               vc4_hdmi->broadcast_rgb_property = prop;
+       }
+
+       drm_object_attach_property(&vc4_hdmi->connector.base, prop, 0);
+}
+
 static int vc4_hdmi_connector_init(struct drm_device *dev,
                                   struct vc4_hdmi *vc4_hdmi)
 {
@@ -358,10 +549,13 @@ static int vc4_hdmi_connector_init(struct drm_device *dev,
 
        connector->interlace_allowed = 1;
        connector->doublescan_allowed = 0;
+       connector->stereo_allowed = 1;
 
        if (vc4_hdmi->variant->supports_hdr)
                drm_connector_attach_hdr_output_metadata_property(connector);
 
+       vc4_hdmi_attach_broadcast_rgb_property(dev, vc4_hdmi);
+
        drm_connector_attach_encoder(connector, encoder);
 
        return 0;
@@ -373,9 +567,12 @@ static int vc4_hdmi_stop_packet(struct drm_encoder *encoder,
 {
        struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
        u32 packet_id = type - 0x80;
+       unsigned long flags;
 
+       spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
        HDMI_WRITE(HDMI_RAM_PACKET_CONFIG,
                   HDMI_READ(HDMI_RAM_PACKET_CONFIG) & ~BIT(packet_id));
+       spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
 
        if (!poll)
                return 0;
@@ -392,9 +589,12 @@ static void vc4_hdmi_write_infoframe(struct drm_encoder *encoder,
        const struct vc4_hdmi_register *ram_packet_start =
                &vc4_hdmi->variant->registers[HDMI_RAM_PACKET_START];
        u32 packet_reg = ram_packet_start->offset + VC4_HDMI_PACKET_STRIDE * packet_id;
+       u32 packet_reg_next = ram_packet_start->offset +
+               VC4_HDMI_PACKET_STRIDE * (packet_id + 1);
        void __iomem *base = __vc4_hdmi_get_field_base(vc4_hdmi,
                                                       ram_packet_start->reg);
-       uint8_t buffer[VC4_HDMI_PACKET_STRIDE];
+       uint8_t buffer[VC4_HDMI_PACKET_STRIDE] = {};
+       unsigned long flags;
        ssize_t len, i;
        int ret;
 
@@ -412,6 +612,8 @@ static void vc4_hdmi_write_infoframe(struct drm_encoder *encoder,
                return;
        }
 
+       spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
+
        for (i = 0; i < len; i += 7) {
                writel(buffer[i + 0] << 0 |
                       buffer[i + 1] << 8 |
@@ -427,25 +629,62 @@ static void vc4_hdmi_write_infoframe(struct drm_encoder *encoder,
                packet_reg += 4;
        }
 
+       /*
+        * clear remainder of packet ram as it's included in the
+        * infoframe and triggers a checksum error on hdmi analyser
+        */
+       for (; packet_reg < packet_reg_next; packet_reg += 4)
+               writel(0, base + packet_reg);
+
        HDMI_WRITE(HDMI_RAM_PACKET_CONFIG,
                   HDMI_READ(HDMI_RAM_PACKET_CONFIG) | BIT(packet_id));
+
+       spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
+
        ret = wait_for((HDMI_READ(HDMI_RAM_PACKET_STATUS) &
                        BIT(packet_id)), 100);
        if (ret)
                DRM_ERROR("Failed to wait for infoframe to start: %d\n", ret);
 }
 
+static void vc4_hdmi_avi_infoframe_colorspace(struct hdmi_avi_infoframe *frame,
+                                             enum vc4_hdmi_output_format fmt)
+{
+       switch (fmt) {
+       case VC4_HDMI_OUTPUT_RGB:
+               frame->colorspace = HDMI_COLORSPACE_RGB;
+               break;
+
+       case VC4_HDMI_OUTPUT_YUV420:
+               frame->colorspace = HDMI_COLORSPACE_YUV420;
+               break;
+
+       case VC4_HDMI_OUTPUT_YUV422:
+               frame->colorspace = HDMI_COLORSPACE_YUV422;
+               break;
+
+       case VC4_HDMI_OUTPUT_YUV444:
+               frame->colorspace = HDMI_COLORSPACE_YUV444;
+               break;
+
+       default:
+               break;
+       }
+}
+
 static void vc4_hdmi_set_avi_infoframe(struct drm_encoder *encoder)
 {
        struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
-       struct vc4_hdmi_encoder *vc4_encoder = to_vc4_hdmi_encoder(encoder);
        struct drm_connector *connector = &vc4_hdmi->connector;
        struct drm_connector_state *cstate = connector->state;
-       struct drm_crtc *crtc = encoder->crtc;
-       const struct drm_display_mode *mode = &crtc->state->adjusted_mode;
+       struct vc4_hdmi_connector_state *vc4_state =
+               conn_state_to_vc4_hdmi_conn_state(cstate);
+       const struct drm_display_mode *mode = &vc4_hdmi->saved_adjusted_mode;
        union hdmi_infoframe frame;
        int ret;
 
+       lockdep_assert_held(&vc4_hdmi->mutex);
+
        ret = drm_hdmi_avi_infoframe_from_display_mode(&frame.avi,
                                                       connector, mode);
        if (ret < 0) {
@@ -455,10 +694,11 @@ static void vc4_hdmi_set_avi_infoframe(struct drm_encoder *encoder)
 
        drm_hdmi_avi_infoframe_quant_range(&frame.avi,
                                           connector, mode,
-                                          vc4_encoder->limited_rgb_range ?
-                                          HDMI_QUANTIZATION_RANGE_LIMITED :
-                                          HDMI_QUANTIZATION_RANGE_FULL);
-       drm_hdmi_avi_infoframe_colorspace(&frame.avi, cstate);
+                                          vc4_hdmi_is_full_range(vc4_hdmi, mode) ?
+                                          HDMI_QUANTIZATION_RANGE_FULL :
+                                          HDMI_QUANTIZATION_RANGE_LIMITED);
+       drm_hdmi_avi_infoframe_colorimetry(&frame.avi, cstate);
+       vc4_hdmi_avi_infoframe_colorspace(&frame.avi, vc4_state->output_format);
        drm_hdmi_avi_infoframe_bars(&frame.avi, cstate);
 
        vc4_hdmi_write_infoframe(encoder, &frame);
@@ -497,6 +737,8 @@ static void vc4_hdmi_set_hdr_infoframe(struct drm_encoder *encoder)
        struct drm_connector_state *conn_state = connector->state;
        union hdmi_infoframe frame;
 
+       lockdep_assert_held(&vc4_hdmi->mutex);
+
        if (!vc4_hdmi->variant->supports_hdr)
                return;
 
@@ -513,6 +755,8 @@ static void vc4_hdmi_set_infoframes(struct drm_encoder *encoder)
 {
        struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
 
+       lockdep_assert_held(&vc4_hdmi->mutex);
+
        vc4_hdmi_set_avi_infoframe(encoder);
        vc4_hdmi_set_spd_infoframe(encoder);
        /*
@@ -532,6 +776,8 @@ static bool vc4_hdmi_supports_scrambling(struct drm_encoder *encoder,
        struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
        struct drm_display_info *display = &vc4_hdmi->connector.display_info;
 
+       lockdep_assert_held(&vc4_hdmi->mutex);
+
        if (!vc4_encoder->hdmi_monitor)
                return false;
 
@@ -546,20 +792,29 @@ static bool vc4_hdmi_supports_scrambling(struct drm_encoder *encoder,
 
 static void vc4_hdmi_enable_scrambling(struct drm_encoder *encoder)
 {
-       struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode;
        struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
+       struct drm_display_mode *mode = &vc4_hdmi->saved_adjusted_mode;
+       unsigned long flags;
+
+       lockdep_assert_held(&vc4_hdmi->mutex);
 
        if (!vc4_hdmi_supports_scrambling(encoder, mode))
                return;
 
-       if (!vc4_hdmi_mode_needs_scrambling(mode))
+       if (!vc4_hdmi_mode_needs_scrambling(mode,
+                                           vc4_hdmi->output_bpc,
+                                           vc4_hdmi->output_format))
                return;
 
        drm_scdc_set_high_tmds_clock_ratio(vc4_hdmi->ddc, true);
        drm_scdc_set_scrambling(vc4_hdmi->ddc, true);
 
+       spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
        HDMI_WRITE(HDMI_SCRAMBLER_CTL, HDMI_READ(HDMI_SCRAMBLER_CTL) |
                   VC5_HDMI_SCRAMBLER_CTL_ENABLE);
+       spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
+
+       vc4_hdmi->scdc_enabled = true;
 
        queue_delayed_work(system_wq, &vc4_hdmi->scrambling_work,
                           msecs_to_jiffies(SCRAMBLING_POLLING_DELAY_MS));
@@ -568,24 +823,22 @@ static void vc4_hdmi_enable_scrambling(struct drm_encoder *encoder)
 static void vc4_hdmi_disable_scrambling(struct drm_encoder *encoder)
 {
        struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
-       struct drm_crtc *crtc = encoder->crtc;
+       unsigned long flags;
 
-       /*
-        * At boot, encoder->crtc will be NULL. Since we don't know the
-        * state of the scrambler and in order to avoid any
-        * inconsistency, let's disable it all the time.
-        */
-       if (crtc && !vc4_hdmi_supports_scrambling(encoder, &crtc->mode))
-               return;
+       lockdep_assert_held(&vc4_hdmi->mutex);
 
-       if (crtc && !vc4_hdmi_mode_needs_scrambling(&crtc->mode))
+       if (!vc4_hdmi->scdc_enabled)
                return;
 
+       vc4_hdmi->scdc_enabled = false;
+
        if (delayed_work_pending(&vc4_hdmi->scrambling_work))
                cancel_delayed_work_sync(&vc4_hdmi->scrambling_work);
 
+       spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
        HDMI_WRITE(HDMI_SCRAMBLER_CTL, HDMI_READ(HDMI_SCRAMBLER_CTL) &
                   ~VC5_HDMI_SCRAMBLER_CTL_ENABLE);
+       spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
 
        drm_scdc_set_scrambling(vc4_hdmi->ddc, false);
        drm_scdc_set_high_tmds_clock_ratio(vc4_hdmi->ddc, false);
@@ -611,26 +864,44 @@ static void vc4_hdmi_encoder_post_crtc_disable(struct drm_encoder *encoder,
                                               struct drm_atomic_state *state)
 {
        struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
+       unsigned long flags;
+
+       mutex_lock(&vc4_hdmi->mutex);
+
+       vc4_hdmi->output_enabled = false;
+       spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
 
        HDMI_WRITE(HDMI_RAM_PACKET_CONFIG, 0);
 
        HDMI_WRITE(HDMI_VID_CTL, HDMI_READ(HDMI_VID_CTL) | VC4_HD_VID_CTL_CLRRGB);
 
+       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);
+
        vc4_hdmi_disable_scrambling(encoder);
+
+       mutex_unlock(&vc4_hdmi->mutex);
 }
 
 static void vc4_hdmi_encoder_post_crtc_powerdown(struct drm_encoder *encoder,
                                                 struct drm_atomic_state *state)
 {
        struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
+       unsigned long flags;
        int ret;
 
+       mutex_lock(&vc4_hdmi->mutex);
+
+       spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
        HDMI_WRITE(HDMI_VID_CTL,
                   HDMI_READ(HDMI_VID_CTL) | VC4_HD_VID_CTL_BLANKPIX);
+       spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
 
        if (vc4_hdmi->variant->phy_disable)
                vc4_hdmi->variant->phy_disable(vc4_hdmi);
@@ -641,20 +912,23 @@ static void vc4_hdmi_encoder_post_crtc_powerdown(struct drm_encoder *encoder,
        ret = pm_runtime_put(&vc4_hdmi->pdev->dev);
        if (ret < 0)
                DRM_ERROR("Failed to release power domain: %d\n", ret);
-}
 
-static void vc4_hdmi_encoder_disable(struct drm_encoder *encoder)
-{
+       mutex_unlock(&vc4_hdmi->mutex);
 }
 
-static void vc4_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, bool enable)
+static void vc4_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi,
+                              struct drm_connector_state *state,
+                              const struct drm_display_mode *mode)
 {
+       unsigned long flags;
        u32 csc_ctl;
 
+       spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
+
        csc_ctl = VC4_SET_FIELD(VC4_HD_CSC_CTL_ORDER_BGR,
                                VC4_HD_CSC_CTL_ORDER);
 
-       if (enable) {
+       if (!vc4_hdmi_is_full_range(vc4_hdmi, mode)) {
                /* CEA VICs other than #1 requre limited range RGB
                 * output unless overridden by an AVI infoframe.
                 * Apply a colorspace conversion to squash 0-255 down
@@ -680,45 +954,236 @@ static void vc4_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, bool enable)
 
        /* The RGB order applies even when CSC is disabled. */
        HDMI_WRITE(HDMI_CSC_CTL, csc_ctl);
+
+       spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
 }
 
-static void vc5_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, bool enable)
-{
-       u32 csc_ctl;
+/*
+ * If we need to output Full Range RGB, then use the unity matrix
+ *
+ * [ 1      0      0      0]
+ * [ 0      1      0      0]
+ * [ 0      0      1      0]
+ *
+ * CEA VICs other than #1 require limited range RGB output unless
+ * overridden by an AVI infoframe. Apply a colorspace conversion to
+ * squash 0-255 down to 16-235. The matrix here is:
+ *
+ * [ 0.8594 0      0      16]
+ * [ 0      0.8594 0      16]
+ * [ 0      0      0.8594 16]
+ *
+ * Matrix is signed 2p13 fixed point, with signed 9p6 offsets
+ */
+static const u16 vc5_hdmi_csc_full_rgb_to_rgb[2][3][4] = {
+       {
+               /* Full range - unity */
+               { 0x2000, 0x0000, 0x0000, 0x0000 },
+               { 0x0000, 0x2000, 0x0000, 0x0000 },
+               { 0x0000, 0x0000, 0x2000, 0x0000 },
+       }, {
+               /* Limited range */
+               { 0x1b80, 0x0000, 0x0000, 0x0400 },
+               { 0x0000, 0x1b80, 0x0000, 0x0400 },
+               { 0x0000, 0x0000, 0x1b80, 0x0400 },
+       }
+};
+
+/*
+ * Conversion between Full Range RGB and YUV using the BT.601 Colorspace
+ *
+ * Full range
+ * [    0.299000   0.587000   0.114000   0.000000 ]
+ * [   -0.168736  -0.331264   0.500000 128.000000 ]
+ * [    0.500000  -0.418688  -0.081312 128.000000 ]
+ *
+ * Limited range
+ * [    0.255785   0.502160   0.097523  16.000000 ]
+ * [   -0.147644  -0.289856   0.437500 128.000000 ]
+ * [    0.437500  -0.366352  -0.071148 128.000000 ]
+ *
+ * Matrix is signed 2p13 fixed point, with signed 9p6 offsets
+ */
+static const u16 vc5_hdmi_csc_full_rgb_to_yuv_bt601[2][3][4] = {
+       {
+               /* Full range */
+               { 0x0991, 0x12c9, 0x03a6, 0x0000 },
+               { 0xfa9b, 0xf567, 0x1000, 0x2000 },
+               { 0x1000, 0xf29b, 0xfd67, 0x2000 },
+       }, {
+               /* Limited range */
+               { 0x082f, 0x1012, 0x031f, 0x0400 },
+               { 0xfb48, 0xf6ba, 0x0e00, 0x2000 },
+               { 0x0e00, 0xf448, 0xfdba, 0x2000 },
+       }
+};
 
-       csc_ctl = 0x07; /* RGB_CONVERT_MODE = custom matrix, || USE_RGB_TO_YCBCR */
+/*
+ * Conversion between Full Range RGB and YUV using the BT.709 Colorspace
+ *
+ * Full range
+ * [    0.212600   0.715200   0.072200   0.000000 ]
+ * [   -0.114572  -0.385428   0.500000 128.000000 ]
+ * [    0.500000  -0.454153  -0.045847 128.000000 ]
+ *
+ * Limited range
+ * [    0.181873   0.611831   0.061765  16.000000 ]
+ * [   -0.100251  -0.337249   0.437500 128.000000 ]
+ * [    0.437500  -0.397384  -0.040116 128.000000 ]
+ *
+ * Matrix is signed 2p13 fixed point, with signed 9p6 offsets
+ */
+static const u16 vc5_hdmi_csc_full_rgb_to_yuv_bt709[2][3][4] = {
+       {
+               /* Full range */
+               { 0x06ce, 0x16e3, 0x024f, 0x0000 },
+               { 0xfc56, 0xf3ac, 0x1000, 0x2000 },
+               { 0x1000, 0xf179, 0xfe89, 0x2000 },
+       }, {
+               /* Limited range        */
+               { 0x05d2, 0x1394, 0x01fa, 0x0400 },
+               { 0xfccc, 0xf536, 0x0e00, 0x2000 },
+               { 0x0e00, 0xf34a, 0xfeb8, 0x2000 },
+       }
+};
 
-       if (enable) {
-               /* CEA VICs other than #1 requre limited range RGB
-                * output unless overridden by an AVI infoframe.
-                * Apply a colorspace conversion to squash 0-255 down
-                * to 16-235.  The matrix here is:
-                *
-                * [ 0.8594 0      0      16]
-                * [ 0      0.8594 0      16]
-                * [ 0      0      0.8594 16]
-                * [ 0      0      0       1]
-                * Matrix is signed 2p13 fixed point, with signed 9p6 offsets
-                */
-               HDMI_WRITE(HDMI_CSC_12_11, (0x0000 << 16) | 0x1b80);
-               HDMI_WRITE(HDMI_CSC_14_13, (0x0400 << 16) | 0x0000);
-               HDMI_WRITE(HDMI_CSC_22_21, (0x1b80 << 16) | 0x0000);
-               HDMI_WRITE(HDMI_CSC_24_23, (0x0400 << 16) | 0x0000);
-               HDMI_WRITE(HDMI_CSC_32_31, (0x0000 << 16) | 0x0000);
-               HDMI_WRITE(HDMI_CSC_34_33, (0x0400 << 16) | 0x1b80);
-       } else {
-               /* Still use the matrix for full range, but make it unity.
-                * Matrix is signed 2p13 fixed point, with signed 9p6 offsets
-                */
-               HDMI_WRITE(HDMI_CSC_12_11, (0x0000 << 16) | 0x2000);
-               HDMI_WRITE(HDMI_CSC_14_13, (0x0000 << 16) | 0x0000);
-               HDMI_WRITE(HDMI_CSC_22_21, (0x2000 << 16) | 0x0000);
-               HDMI_WRITE(HDMI_CSC_24_23, (0x0000 << 16) | 0x0000);
-               HDMI_WRITE(HDMI_CSC_32_31, (0x0000 << 16) | 0x0000);
-               HDMI_WRITE(HDMI_CSC_34_33, (0x0000 << 16) | 0x2000);
+/*
+ * Conversion between Full Range RGB and YUV using the BT.2020 Colorspace
+ *
+ * Full range
+ * [    0.262700   0.678000   0.059300   0.000000 ]
+ * [   -0.139630  -0.360370   0.500000 128.000000 ]
+ * [    0.500000  -0.459786  -0.040214 128.000000 ]
+ *
+ * Limited range
+ * [    0.224732   0.580008   0.050729  16.000000 ]
+ * [   -0.122176  -0.315324   0.437500 128.000000 ]
+ * [    0.437500  -0.402312  -0.035188 128.000000 ]
+ *
+ * Matrix is signed 2p13 fixed point, with signed 9p6 offsets
+ */
+static const u16 vc5_hdmi_csc_full_rgb_to_yuv_bt2020[2][3][4] = {
+       {
+               /* Full range */
+               { 0x0868, 0x15b2, 0x01e6, 0x0000 },
+               { 0xfb89, 0xf479, 0x1000, 0x2000 },
+               { 0x1000, 0xf14a, 0xfeb8, 0x2000 },
+       }, {
+               /* Limited range */
+               { 0x0731, 0x128f, 0x01a0, 0x0400 },
+               { 0xfc18, 0xf5ea, 0x0e00, 0x2000 },
+               { 0x0e00, 0xf321, 0xfee1, 0x2000 },
        }
+};
+
+static void vc5_hdmi_set_csc_coeffs(struct vc4_hdmi *vc4_hdmi,
+                                   const u16 coeffs[3][4])
+{
+       lockdep_assert_held(&vc4_hdmi->hw_lock);
+
+       HDMI_WRITE(HDMI_CSC_12_11, (coeffs[0][1] << 16) | coeffs[0][0]);
+       HDMI_WRITE(HDMI_CSC_14_13, (coeffs[0][3] << 16) | coeffs[0][2]);
+       HDMI_WRITE(HDMI_CSC_22_21, (coeffs[1][1] << 16) | coeffs[1][0]);
+       HDMI_WRITE(HDMI_CSC_24_23, (coeffs[1][3] << 16) | coeffs[1][2]);
+       HDMI_WRITE(HDMI_CSC_32_31, (coeffs[2][1] << 16) | coeffs[2][0]);
+       HDMI_WRITE(HDMI_CSC_34_33, (coeffs[2][3] << 16) | coeffs[2][2]);
+}
+
+static void vc5_hdmi_set_csc_coeffs_swap(struct vc4_hdmi *vc4_hdmi,
+                                        const u16 coeffs[3][4])
+{
+       lockdep_assert_held(&vc4_hdmi->hw_lock);
+
+       /* YUV444 needs the CSC matrices using the channels in a different order */
+       HDMI_WRITE(HDMI_CSC_12_11, (coeffs[2][1] << 16) | coeffs[2][0]);
+       HDMI_WRITE(HDMI_CSC_14_13, (coeffs[2][3] << 16) | coeffs[2][2]);
+       HDMI_WRITE(HDMI_CSC_22_21, (coeffs[0][1] << 16) | coeffs[0][0]);
+       HDMI_WRITE(HDMI_CSC_24_23, (coeffs[0][3] << 16) | coeffs[0][2]);
+       HDMI_WRITE(HDMI_CSC_32_31, (coeffs[1][1] << 16) | coeffs[1][0]);
+       HDMI_WRITE(HDMI_CSC_34_33, (coeffs[1][3] << 16) | coeffs[1][2]);
+}
+
+static void vc5_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi,
+                              struct drm_connector_state *state,
+                              const struct drm_display_mode *mode)
+{
+       struct vc4_hdmi_connector_state *vc4_state =
+               conn_state_to_vc4_hdmi_conn_state(state);
+       unsigned int lim_range = vc4_hdmi_is_full_range(vc4_hdmi, mode) ? 0 : 1;
+       const u16 (*csc)[4];
+       unsigned long flags;
+       u32 if_cfg = 0;
+       u32 if_xbar = 0x543210;
+       u32 csc_chan_ctl = 0;
+       u32 csc_ctl = VC5_MT_CP_CSC_CTL_ENABLE | VC4_SET_FIELD(VC4_HD_CSC_CTL_MODE_CUSTOM,
+                                                              VC5_MT_CP_CSC_CTL_MODE);
+
+       spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
+
+       switch (vc4_state->output_format) {
+       case VC4_HDMI_OUTPUT_YUV444:
+       case VC4_HDMI_OUTPUT_YUV422:
+               switch (state->colorspace) {
+               default:
+               case DRM_MODE_COLORIMETRY_NO_DATA:
+               case DRM_MODE_COLORIMETRY_BT709_YCC:
+               case DRM_MODE_COLORIMETRY_XVYCC_709:
+               case DRM_MODE_COLORIMETRY_RGB_WIDE_FIXED:
+               case DRM_MODE_COLORIMETRY_RGB_WIDE_FLOAT:
+                       csc = vc5_hdmi_csc_full_rgb_to_yuv_bt709[lim_range];
+                       break;
+               case DRM_MODE_COLORIMETRY_SMPTE_170M_YCC:
+               case DRM_MODE_COLORIMETRY_XVYCC_601:
+               case DRM_MODE_COLORIMETRY_SYCC_601:
+               case DRM_MODE_COLORIMETRY_OPYCC_601:
+               case DRM_MODE_COLORIMETRY_BT601_YCC:
+                       csc = vc5_hdmi_csc_full_rgb_to_yuv_bt601[lim_range];
+                       break;
+               case DRM_MODE_COLORIMETRY_BT2020_CYCC:
+               case DRM_MODE_COLORIMETRY_BT2020_YCC:
+               case DRM_MODE_COLORIMETRY_BT2020_RGB:
+               case DRM_MODE_COLORIMETRY_DCI_P3_RGB_D65:
+               case DRM_MODE_COLORIMETRY_DCI_P3_RGB_THEATER:
+                       csc = vc5_hdmi_csc_full_rgb_to_yuv_bt2020[lim_range];
+                       break;
+               }
+
+               if (vc4_state->output_format == VC4_HDMI_OUTPUT_YUV422) {
+                       csc_ctl |= VC4_SET_FIELD(VC5_MT_CP_CSC_CTL_FILTER_MODE_444_TO_422_STANDARD,
+                                                VC5_MT_CP_CSC_CTL_FILTER_MODE_444_TO_422) |
+                               VC5_MT_CP_CSC_CTL_USE_444_TO_422 |
+                               VC5_MT_CP_CSC_CTL_USE_RNG_SUPPRESSION;
+
+                       csc_chan_ctl |= VC4_SET_FIELD(VC5_MT_CP_CHANNEL_CTL_OUTPUT_REMAP_LEGACY_STYLE,
+                                                     VC5_MT_CP_CHANNEL_CTL_OUTPUT_REMAP);
+
+                       if_cfg |= VC4_SET_FIELD(VC5_DVP_HT_VEC_INTERFACE_CFG_SEL_422_FORMAT_422_LEGACY,
+                                               VC5_DVP_HT_VEC_INTERFACE_CFG_SEL_422);
+
+                       vc5_hdmi_set_csc_coeffs(vc4_hdmi, csc);
+               } else {
+                       vc5_hdmi_set_csc_coeffs_swap(vc4_hdmi, csc);
+               }
+
+               break;
+
+       case VC4_HDMI_OUTPUT_RGB:
+               if_xbar = 0x354021;
 
+               vc5_hdmi_set_csc_coeffs(vc4_hdmi,
+                                       vc5_hdmi_csc_full_rgb_to_rgb[lim_range]);
+               break;
+
+       default:
+               break;
+       }
+
+       HDMI_WRITE(HDMI_VEC_INTERFACE_CFG, if_cfg);
+       HDMI_WRITE(HDMI_VEC_INTERFACE_XBAR, if_xbar);
+       HDMI_WRITE(HDMI_CSC_CHANNEL_CTL, csc_chan_ctl);
        HDMI_WRITE(HDMI_CSC_CTL, csc_ctl);
+
+       spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
 }
 
 static void vc4_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi,
@@ -735,13 +1200,17 @@ static void vc4_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi,
                                   VC4_HDMI_VERTA_VFP) |
                     VC4_SET_FIELD(mode->crtc_vdisplay, VC4_HDMI_VERTA_VAL));
        u32 vertb = (VC4_SET_FIELD(0, VC4_HDMI_VERTB_VSPO) |
-                    VC4_SET_FIELD(mode->crtc_vtotal - mode->crtc_vsync_end,
+                    VC4_SET_FIELD(mode->crtc_vtotal - mode->crtc_vsync_end +
+                                  interlaced,
                                   VC4_HDMI_VERTB_VBP));
        u32 vertb_even = (VC4_SET_FIELD(0, VC4_HDMI_VERTB_VSPO) |
                          VC4_SET_FIELD(mode->crtc_vtotal -
-                                       mode->crtc_vsync_end -
-                                       interlaced,
+                                       mode->crtc_vsync_end,
                                        VC4_HDMI_VERTB_VBP));
+       unsigned long flags;
+       u32 reg;
+
+       spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
 
        HDMI_WRITE(HDMI_HORZA,
                   (vsync_pos ? VC4_HDMI_HORZA_VPOS : 0) |
@@ -765,12 +1234,21 @@ static void vc4_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi,
 
        HDMI_WRITE(HDMI_VERTB0, vertb_even);
        HDMI_WRITE(HDMI_VERTB1, vertb);
+
+       reg = HDMI_READ(HDMI_MISC_CONTROL);
+       reg &= ~VC4_HDMI_MISC_CONTROL_PIXEL_REP_MASK;
+       reg |= VC4_SET_FIELD(pixel_rep - 1, VC4_HDMI_MISC_CONTROL_PIXEL_REP);
+       HDMI_WRITE(HDMI_MISC_CONTROL, reg);
+
+       spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
 }
 
 static void vc5_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi,
                                 struct drm_connector_state *state,
                                 struct drm_display_mode *mode)
 {
+       const struct vc4_hdmi_connector_state *vc4_state =
+               conn_state_to_vc4_hdmi_conn_state(state);
        bool hsync_pos = mode->flags & DRM_MODE_FLAG_PHSYNC;
        bool vsync_pos = mode->flags & DRM_MODE_FLAG_PVSYNC;
        bool interlaced = mode->flags & DRM_MODE_FLAG_INTERLACE;
@@ -780,19 +1258,22 @@ static void vc5_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi,
                     VC4_SET_FIELD(mode->crtc_vsync_start - mode->crtc_vdisplay,
                                   VC5_HDMI_VERTA_VFP) |
                     VC4_SET_FIELD(mode->crtc_vdisplay, VC5_HDMI_VERTA_VAL));
-       u32 vertb = (VC4_SET_FIELD(0, VC5_HDMI_VERTB_VSPO) |
-                    VC4_SET_FIELD(mode->crtc_vtotal - mode->crtc_vsync_end,
+       u32 vertb = (VC4_SET_FIELD(mode->htotal >> (2 - pixel_rep),
+                                  VC5_HDMI_VERTB_VSPO) |
+                    VC4_SET_FIELD(mode->crtc_vtotal - mode->crtc_vsync_end +
+                                  interlaced,
                                   VC4_HDMI_VERTB_VBP));
        u32 vertb_even = (VC4_SET_FIELD(0, VC5_HDMI_VERTB_VSPO) |
                          VC4_SET_FIELD(mode->crtc_vtotal -
-                                       mode->crtc_vsync_end -
-                                       interlaced,
+                                       mode->crtc_vsync_end,
                                        VC4_HDMI_VERTB_VBP));
+       unsigned long flags;
        unsigned char gcp;
        bool gcp_en;
        u32 reg;
 
-       HDMI_WRITE(HDMI_VEC_INTERFACE_XBAR, 0x354021);
+       spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
+
        HDMI_WRITE(HDMI_HORZA,
                   (vsync_pos ? VC5_HDMI_HORZA_VPOS : 0) |
                   (hsync_pos ? VC5_HDMI_HORZA_HPOS : 0) |
@@ -816,7 +1297,7 @@ static void vc5_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi,
        HDMI_WRITE(HDMI_VERTB0, vertb_even);
        HDMI_WRITE(HDMI_VERTB1, vertb);
 
-       switch (state->max_bpc) {
+       switch (vc4_state->output_bpc) {
        case 12:
                gcp = 6;
                gcp_en = true;
@@ -832,6 +1313,15 @@ static void vc5_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi,
                break;
        }
 
+       /*
+        * YCC422 is always 36-bit and not considered deep colour so
+        * doesn't signal in GCP
+        */
+       if (vc4_state->output_format == VC4_HDMI_OUTPUT_YUV422) {
+               gcp = 4;
+               gcp_en = false;
+       }
+
        reg = HDMI_READ(HDMI_DEEP_COLOR_CONFIG_1);
        reg &= ~(VC5_HDMI_DEEP_COLOR_CONFIG_1_INIT_PACK_PHASE_MASK |
                 VC5_HDMI_DEEP_COLOR_CONFIG_1_COLOR_DEPTH_MASK);
@@ -849,14 +1339,24 @@ static void vc5_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi,
        reg |= gcp_en ? VC5_HDMI_GCP_CONFIG_GCP_ENABLE : 0;
        HDMI_WRITE(HDMI_GCP_CONFIG, reg);
 
+       reg = HDMI_READ(HDMI_MISC_CONTROL);
+       reg &= ~VC5_HDMI_MISC_CONTROL_PIXEL_REP_MASK;
+       reg |= VC4_SET_FIELD(pixel_rep - 1, VC5_HDMI_MISC_CONTROL_PIXEL_REP);
+       HDMI_WRITE(HDMI_MISC_CONTROL, reg);
+
        HDMI_WRITE(HDMI_CLOCK_STOP, 0);
+
+       spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
 }
 
 static void vc4_hdmi_recenter_fifo(struct vc4_hdmi *vc4_hdmi)
 {
+       unsigned long flags;
        u32 drift;
        int ret;
 
+       spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
+
        drift = HDMI_READ(HDMI_FIFO_CTL);
        drift &= VC4_HDMI_FIFO_VALID_WRITE_MASK;
 
@@ -864,47 +1364,43 @@ static void vc4_hdmi_recenter_fifo(struct vc4_hdmi *vc4_hdmi)
                   drift & ~VC4_HDMI_FIFO_CTL_RECENTER);
        HDMI_WRITE(HDMI_FIFO_CTL,
                   drift | VC4_HDMI_FIFO_CTL_RECENTER);
+
+       spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
+
        usleep_range(1000, 1100);
+
+       spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
+
        HDMI_WRITE(HDMI_FIFO_CTL,
                   drift & ~VC4_HDMI_FIFO_CTL_RECENTER);
        HDMI_WRITE(HDMI_FIFO_CTL,
                   drift | VC4_HDMI_FIFO_CTL_RECENTER);
 
+       spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
+
        ret = wait_for(HDMI_READ(HDMI_FIFO_CTL) &
                       VC4_HDMI_FIFO_CTL_RECENTER_DONE, 1);
        WARN_ONCE(ret, "Timeout waiting for "
                  "VC4_HDMI_FIFO_CTL_RECENTER_DONE");
 }
 
-static struct drm_connector_state *
-vc4_hdmi_encoder_get_connector_state(struct drm_encoder *encoder,
-                                    struct drm_atomic_state *state)
-{
-       struct drm_connector_state *conn_state;
-       struct drm_connector *connector;
-       unsigned int i;
-
-       for_each_new_connector_in_state(state, connector, conn_state, i) {
-               if (conn_state->best_encoder == encoder)
-                       return conn_state;
-       }
-
-       return NULL;
-}
-
 static void vc4_hdmi_encoder_pre_crtc_configure(struct drm_encoder *encoder,
                                                struct drm_atomic_state *state)
 {
+       struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
+       struct drm_connector *connector = &vc4_hdmi->connector;
        struct drm_connector_state *conn_state =
-               vc4_hdmi_encoder_get_connector_state(encoder, state);
+               drm_atomic_get_new_connector_state(state, connector);
        struct vc4_hdmi_connector_state *vc4_conn_state =
                conn_state_to_vc4_hdmi_conn_state(conn_state);
-       struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode;
-       struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
+       struct drm_display_mode *mode = &vc4_hdmi->saved_adjusted_mode;
        unsigned long pixel_rate = vc4_conn_state->pixel_rate;
        unsigned long bvb_rate, hsm_rate;
+       unsigned long flags;
        int ret;
 
+       mutex_lock(&vc4_hdmi->mutex);
+
        /*
         * As stated in RPi's vc4 firmware "HDMI state machine (HSM) clock must
         * be faster than pixel clock, infinitesimally faster, tested in
@@ -925,13 +1421,13 @@ static void vc4_hdmi_encoder_pre_crtc_configure(struct drm_encoder *encoder,
        ret = clk_set_min_rate(vc4_hdmi->hsm_clock, hsm_rate);
        if (ret) {
                DRM_ERROR("Failed to set HSM clock rate: %d\n", ret);
-               return;
+               goto out;
        }
 
        ret = pm_runtime_resume_and_get(&vc4_hdmi->pdev->dev);
        if (ret < 0) {
                DRM_ERROR("Failed to retain power domain: %d\n", ret);
-               return;
+               goto out;
        }
 
        ret = clk_set_rate(vc4_hdmi->pixel_clock, pixel_rate);
@@ -971,57 +1467,68 @@ static void vc4_hdmi_encoder_pre_crtc_configure(struct drm_encoder *encoder,
        if (vc4_hdmi->variant->phy_init)
                vc4_hdmi->variant->phy_init(vc4_hdmi, vc4_conn_state);
 
+       spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
+
        HDMI_WRITE(HDMI_SCHEDULER_CONTROL,
                   HDMI_READ(HDMI_SCHEDULER_CONTROL) |
                   VC4_HDMI_SCHEDULER_CONTROL_MANUAL_FORMAT |
                   VC4_HDMI_SCHEDULER_CONTROL_IGNORE_VSYNC_PREDICTS);
 
+       spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
+
        if (vc4_hdmi->variant->set_timings)
                vc4_hdmi->variant->set_timings(vc4_hdmi, conn_state, mode);
 
+       mutex_unlock(&vc4_hdmi->mutex);
+
        return;
 
 err_disable_pixel_clock:
        clk_disable_unprepare(vc4_hdmi->pixel_clock);
 err_put_runtime_pm:
        pm_runtime_put(&vc4_hdmi->pdev->dev);
-
+out:
+       mutex_unlock(&vc4_hdmi->mutex);
        return;
 }
 
 static void vc4_hdmi_encoder_pre_crtc_enable(struct drm_encoder *encoder,
                                             struct drm_atomic_state *state)
 {
-       struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode;
-       struct vc4_hdmi_encoder *vc4_encoder = to_vc4_hdmi_encoder(encoder);
        struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
+       struct drm_connector *connector = &vc4_hdmi->connector;
+       struct drm_display_mode *mode = &vc4_hdmi->saved_adjusted_mode;
+       struct drm_connector_state *conn_state =
+               drm_atomic_get_new_connector_state(state, connector);
+       unsigned long flags;
 
-       if (vc4_encoder->hdmi_monitor &&
-           drm_default_rgb_quant_range(mode) == HDMI_QUANTIZATION_RANGE_LIMITED) {
-               if (vc4_hdmi->variant->csc_setup)
-                       vc4_hdmi->variant->csc_setup(vc4_hdmi, true);
-
-               vc4_encoder->limited_rgb_range = true;
-       } else {
-               if (vc4_hdmi->variant->csc_setup)
-                       vc4_hdmi->variant->csc_setup(vc4_hdmi, false);
+       mutex_lock(&vc4_hdmi->mutex);
 
-               vc4_encoder->limited_rgb_range = false;
-       }
+       if (vc4_hdmi->variant->csc_setup)
+               vc4_hdmi->variant->csc_setup(vc4_hdmi, conn_state, mode);
 
+       spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
        HDMI_WRITE(HDMI_FIFO_CTL, VC4_HDMI_FIFO_CTL_MASTER_SLAVE_N);
+       spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
+
+       mutex_unlock(&vc4_hdmi->mutex);
 }
 
 static void vc4_hdmi_encoder_post_crtc_enable(struct drm_encoder *encoder,
                                              struct drm_atomic_state *state)
 {
-       struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode;
        struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
+       struct drm_display_mode *mode = &vc4_hdmi->saved_adjusted_mode;
        struct vc4_hdmi_encoder *vc4_encoder = to_vc4_hdmi_encoder(encoder);
        bool hsync_pos = mode->flags & DRM_MODE_FLAG_PHSYNC;
        bool vsync_pos = mode->flags & DRM_MODE_FLAG_PVSYNC;
+       unsigned long flags;
        int ret;
 
+       mutex_lock(&vc4_hdmi->mutex);
+
+       spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
+
        HDMI_WRITE(HDMI_VID_CTL,
                   VC4_HD_VID_CTL_ENABLE |
                   VC4_HD_VID_CTL_CLRRGB |
@@ -1038,43 +1545,277 @@ static void vc4_hdmi_encoder_post_crtc_enable(struct drm_encoder *encoder,
                           HDMI_READ(HDMI_SCHEDULER_CONTROL) |
                           VC4_HDMI_SCHEDULER_CONTROL_MODE_HDMI);
 
-               ret = wait_for(HDMI_READ(HDMI_SCHEDULER_CONTROL) &
-                              VC4_HDMI_SCHEDULER_CONTROL_HDMI_ACTIVE, 1000);
-               WARN_ONCE(ret, "Timeout waiting for "
-                         "VC4_HDMI_SCHEDULER_CONTROL_HDMI_ACTIVE\n");
-       } else {
-               HDMI_WRITE(HDMI_RAM_PACKET_CONFIG,
-                          HDMI_READ(HDMI_RAM_PACKET_CONFIG) &
-                          ~(VC4_HDMI_RAM_PACKET_ENABLE));
-               HDMI_WRITE(HDMI_SCHEDULER_CONTROL,
-                          HDMI_READ(HDMI_SCHEDULER_CONTROL) &
-                          ~VC4_HDMI_SCHEDULER_CONTROL_MODE_HDMI);
+               spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
+
+               ret = wait_for(HDMI_READ(HDMI_SCHEDULER_CONTROL) &
+                              VC4_HDMI_SCHEDULER_CONTROL_HDMI_ACTIVE, 1000);
+               WARN_ONCE(ret, "Timeout waiting for "
+                         "VC4_HDMI_SCHEDULER_CONTROL_HDMI_ACTIVE\n");
+       } else {
+               HDMI_WRITE(HDMI_RAM_PACKET_CONFIG,
+                          HDMI_READ(HDMI_RAM_PACKET_CONFIG) &
+                          ~(VC4_HDMI_RAM_PACKET_ENABLE));
+               HDMI_WRITE(HDMI_SCHEDULER_CONTROL,
+                          HDMI_READ(HDMI_SCHEDULER_CONTROL) &
+                          ~VC4_HDMI_SCHEDULER_CONTROL_MODE_HDMI);
+
+               spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
+
+               ret = wait_for(!(HDMI_READ(HDMI_SCHEDULER_CONTROL) &
+                                VC4_HDMI_SCHEDULER_CONTROL_HDMI_ACTIVE), 1000);
+               WARN_ONCE(ret, "Timeout waiting for "
+                         "!VC4_HDMI_SCHEDULER_CONTROL_HDMI_ACTIVE\n");
+       }
+
+       if (vc4_encoder->hdmi_monitor) {
+               spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
+
+               WARN_ON(!(HDMI_READ(HDMI_SCHEDULER_CONTROL) &
+                         VC4_HDMI_SCHEDULER_CONTROL_HDMI_ACTIVE));
+
+               HDMI_WRITE(HDMI_RAM_PACKET_CONFIG,
+                          VC4_HDMI_RAM_PACKET_ENABLE);
+
+               spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
+               vc4_hdmi->output_enabled = true;
+
+               vc4_hdmi_set_infoframes(encoder);
+       }
+
+       vc4_hdmi_recenter_fifo(vc4_hdmi);
+       vc4_hdmi_enable_scrambling(encoder);
+
+       mutex_unlock(&vc4_hdmi->mutex);
+}
+
+static void vc4_hdmi_encoder_atomic_mode_set(struct drm_encoder *encoder,
+                                            struct drm_crtc_state *crtc_state,
+                                            struct drm_connector_state *conn_state)
+{
+       struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
+       struct vc4_hdmi_connector_state *vc4_state =
+               conn_state_to_vc4_hdmi_conn_state(conn_state);
+
+       mutex_lock(&vc4_hdmi->mutex);
+       vc4_hdmi->output_bpc = vc4_state->output_bpc;
+       vc4_hdmi->output_format = vc4_state->output_format;
+       vc4_hdmi->broadcast_rgb = vc4_state->broadcast_rgb;
+       memcpy(&vc4_hdmi->saved_adjusted_mode,
+              &crtc_state->adjusted_mode,
+              sizeof(vc4_hdmi->saved_adjusted_mode));
+       mutex_unlock(&vc4_hdmi->mutex);
+}
+
+static bool
+vc4_hdmi_sink_supports_format_bpc(const struct vc4_hdmi *vc4_hdmi,
+                                 const struct drm_display_info *info,
+                                 const struct drm_display_mode *mode,
+                                 unsigned int format, unsigned int bpc)
+{
+       struct drm_device *dev = vc4_hdmi->connector.dev;
+       u8 vic = drm_match_cea_mode(mode);
+
+       if (vic == 1 && bpc != 8) {
+               drm_dbg(dev, "VIC1 requires a bpc of 8, got %u\n", bpc);
+               return false;
+       }
+
+       if (!info->is_hdmi &&
+           (format != VC4_HDMI_OUTPUT_RGB || bpc != 8)) {
+               drm_dbg(dev, "DVI Monitors require an RGB output at 8 bpc\n");
+               return false;
+       }
+
+       switch (format) {
+       case VC4_HDMI_OUTPUT_RGB:
+               drm_dbg(dev, "RGB Format, checking the constraints.\n");
+
+               if (bpc == 10 && !(info->edid_hdmi_rgb444_dc_modes & DRM_EDID_HDMI_DC_30)) {
+                       drm_dbg(dev, "10 BPC but sink doesn't support Deep Color 30.\n");
+                       return false;
+               }
+
+               if (bpc == 12 && !(info->edid_hdmi_rgb444_dc_modes & DRM_EDID_HDMI_DC_36)) {
+                       drm_dbg(dev, "12 BPC but sink doesn't support Deep Color 36.\n");
+                       return false;
+               }
+
+               drm_dbg(dev, "RGB format supported in that configuration.\n");
+
+               return true;
+
+       case VC4_HDMI_OUTPUT_YUV422:
+               drm_dbg(dev, "YUV422 format, checking the constraints.\n");
+
+               if (!(info->color_formats & DRM_COLOR_FORMAT_YCRCB422)) {
+                       drm_dbg(dev, "Sink doesn't support YUV422.\n");
+                       return false;
+               }
+
+               if (bpc != 12) {
+                       drm_dbg(dev, "YUV422 only supports 12 bpc.\n");
+                       return false;
+               }
+
+               drm_dbg(dev, "YUV422 format supported in that configuration.\n");
+
+               return true;
+
+       case VC4_HDMI_OUTPUT_YUV444:
+               drm_dbg(dev, "YUV444 format, checking the constraints.\n");
+
+               if (!(info->color_formats & DRM_COLOR_FORMAT_YCRCB444)) {
+                       drm_dbg(dev, "Sink doesn't support YUV444.\n");
+                       return false;
+               }
+
+               if (bpc == 10 && !(info->edid_hdmi_rgb444_dc_modes & DRM_EDID_HDMI_DC_30)) {
+                       drm_dbg(dev, "10 BPC but sink doesn't support Deep Color 30.\n");
+                       return false;
+               }
+
+               if (bpc == 12 && !(info->edid_hdmi_rgb444_dc_modes & DRM_EDID_HDMI_DC_36)) {
+                       drm_dbg(dev, "12 BPC but sink doesn't support Deep Color 36.\n");
+                       return false;
+               }
+
+               drm_dbg(dev, "YUV444 format supported in that configuration.\n");
+
+               return true;
+       }
+
+       return false;
+}
+
+static enum drm_mode_status
+vc4_hdmi_encoder_clock_valid(const struct vc4_hdmi *vc4_hdmi,
+                            unsigned long long clock)
+{
+       const struct drm_connector *connector = &vc4_hdmi->connector;
+       const struct drm_display_info *info = &connector->display_info;
+       struct vc4_dev *vc4 = to_vc4_dev(connector->dev);
+
+       if (clock > vc4_hdmi->variant->max_pixel_clock)
+               return MODE_CLOCK_HIGH;
+
+       if (!vc4->hvs->vc5_hdmi_enable_scrambling && clock > HDMI_14_MAX_TMDS_CLK)
+               return MODE_CLOCK_HIGH;
+
+       if (info->max_tmds_clock && clock > (info->max_tmds_clock * 1000))
+               return MODE_CLOCK_HIGH;
+
+       return MODE_OK;
+}
+
+static unsigned long long
+vc4_hdmi_encoder_compute_mode_clock(const struct drm_display_mode *mode,
+                                   unsigned int bpc,
+                                   enum vc4_hdmi_output_format fmt)
+{
+       unsigned long long clock = mode->clock * 1000;
+
+       if (mode->flags & DRM_MODE_FLAG_DBLCLK)
+               clock = clock * 2;
+
+       if (fmt == VC4_HDMI_OUTPUT_YUV422)
+               bpc = 8;
+
+       return clock * bpc / 8;
+}
+
+static int
+vc4_hdmi_encoder_compute_clock(const struct vc4_hdmi *vc4_hdmi,
+                              struct vc4_hdmi_connector_state *vc4_state,
+                              const struct drm_display_mode *mode,
+                              unsigned int bpc, unsigned int fmt)
+{
+       unsigned long long clock;
+
+       clock = vc4_hdmi_encoder_compute_mode_clock(mode, bpc, fmt);
+       if (vc4_hdmi_encoder_clock_valid(vc4_hdmi, clock) != MODE_OK)
+               return -EINVAL;
+
+       vc4_state->pixel_rate = clock;
+
+       return 0;
+}
+
+static int
+vc4_hdmi_encoder_compute_format(const struct vc4_hdmi *vc4_hdmi,
+                               struct vc4_hdmi_connector_state *vc4_state,
+                               const struct drm_display_mode *mode,
+                               unsigned int bpc)
+{
+       struct drm_device *dev = vc4_hdmi->connector.dev;
+       const struct drm_connector *connector = &vc4_hdmi->connector;
+       const struct drm_display_info *info = &connector->display_info;
+       unsigned int format;
+
+       drm_dbg(dev, "Trying with an RGB output\n");
+
+       format = VC4_HDMI_OUTPUT_RGB;
+       if (vc4_hdmi_sink_supports_format_bpc(vc4_hdmi, info, mode, format, bpc)) {
+               int ret;
 
-               ret = wait_for(!(HDMI_READ(HDMI_SCHEDULER_CONTROL) &
-                                VC4_HDMI_SCHEDULER_CONTROL_HDMI_ACTIVE), 1000);
-               WARN_ONCE(ret, "Timeout waiting for "
-                         "!VC4_HDMI_SCHEDULER_CONTROL_HDMI_ACTIVE\n");
+               ret = vc4_hdmi_encoder_compute_clock(vc4_hdmi, vc4_state,
+                                                    mode, bpc, format);
+               if (!ret) {
+                       vc4_state->output_format = format;
+                       return 0;
+               }
        }
 
-       if (vc4_encoder->hdmi_monitor) {
-               WARN_ON(!(HDMI_READ(HDMI_SCHEDULER_CONTROL) &
-                         VC4_HDMI_SCHEDULER_CONTROL_HDMI_ACTIVE));
-               HDMI_WRITE(HDMI_SCHEDULER_CONTROL,
-                          HDMI_READ(HDMI_SCHEDULER_CONTROL) |
-                          VC4_HDMI_SCHEDULER_CONTROL_VERT_ALWAYS_KEEPOUT);
+       drm_dbg(dev, "Failed, Trying with an YUV422 output\n");
 
-               HDMI_WRITE(HDMI_RAM_PACKET_CONFIG,
-                          VC4_HDMI_RAM_PACKET_ENABLE);
+       format = VC4_HDMI_OUTPUT_YUV422;
+       if (vc4_hdmi_sink_supports_format_bpc(vc4_hdmi, info, mode, format, bpc)) {
+               int ret;
 
-               vc4_hdmi_set_infoframes(encoder);
+               ret = vc4_hdmi_encoder_compute_clock(vc4_hdmi, vc4_state,
+                                                    mode, bpc, format);
+               if (!ret) {
+                       vc4_state->output_format = format;
+                       return 0;
+               }
        }
 
-       vc4_hdmi_recenter_fifo(vc4_hdmi);
-       vc4_hdmi_enable_scrambling(encoder);
+       drm_dbg(dev, "Failed. No Format Supported for that bpc count.\n");
+
+       return -EINVAL;
 }
 
-static void vc4_hdmi_encoder_enable(struct drm_encoder *encoder)
+static int
+vc4_hdmi_encoder_compute_config(const struct vc4_hdmi *vc4_hdmi,
+                               struct vc4_hdmi_connector_state *vc4_state,
+                               const struct drm_display_mode *mode)
 {
+       struct drm_device *dev = vc4_hdmi->connector.dev;
+       struct drm_connector_state *conn_state = &vc4_state->base;
+       unsigned int max_bpc = clamp_t(unsigned int, conn_state->max_bpc, 8, 12);
+       unsigned int bpc;
+       int ret;
+
+       for (bpc = max_bpc; bpc >= 8; bpc -= 2) {
+               drm_dbg(dev, "Trying with a %d bpc output\n", bpc);
+
+               ret = vc4_hdmi_encoder_compute_format(vc4_hdmi, vc4_state,
+                                                     mode, bpc);
+               if (ret)
+                       continue;
+
+               vc4_state->output_bpc = bpc;
+
+               drm_dbg(dev,
+                       "Mode %ux%u @ %uHz: Found configuration: bpc: %u, fmt: %s, clock: %llu\n",
+                       mode->hdisplay, mode->vdisplay, drm_mode_vrefresh(mode),
+                       vc4_state->output_bpc,
+                       vc4_hdmi_output_fmt_str(vc4_state->output_format),
+                       vc4_state->pixel_rate);
+
+               break;
+       }
+
+       return ret;
 }
 
 #define WIFI_2_4GHz_CH1_MIN_FREQ       2400000000ULL
@@ -1087,13 +1828,38 @@ static int vc4_hdmi_encoder_atomic_check(struct drm_encoder *encoder,
        struct vc4_hdmi_connector_state *vc4_state = conn_state_to_vc4_hdmi_conn_state(conn_state);
        struct drm_display_mode *mode = &crtc_state->adjusted_mode;
        struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
+       struct drm_connector *connector = &vc4_hdmi->connector;
+       struct drm_connector_state *old_conn_state = drm_atomic_get_old_connector_state(conn_state->state, connector);
+       struct vc4_hdmi_connector_state *old_vc4_state = conn_state_to_vc4_hdmi_conn_state(old_conn_state);
+       struct vc4_dev *vc4 = to_vc4_dev(connector->dev);
        unsigned long long pixel_rate = mode->clock * 1000;
        unsigned long long tmds_rate;
+       int ret;
 
-       if (vc4_hdmi->variant->unsupported_odd_h_timings &&
-           !(mode->flags & DRM_MODE_FLAG_DBLCLK) &&
-           ((mode->hdisplay % 2) || (mode->hsync_start % 2) ||
-            (mode->hsync_end % 2) || (mode->htotal % 2)))
+       if (vc4_hdmi->variant->unsupported_odd_h_timings) {
+               if (mode->flags & DRM_MODE_FLAG_DBLCLK) {
+                       /* Only try to fixup DBLCLK modes to get 480i and 576i
+                        * working.
+                        * A generic solution for all modes with odd horizontal
+                        * timing values seems impossible based on trying to
+                        * solve it for 1366x768 monitors.
+                        */
+                       if ((mode->hsync_start - mode->hdisplay) & 1)
+                               mode->hsync_start--;
+                       if ((mode->hsync_end - mode->hsync_start) & 1)
+                               mode->hsync_end--;
+               }
+
+               /* Now check whether we still have odd values remaining */
+               if ((mode->hdisplay % 2) || (mode->hsync_start % 2) ||
+                   (mode->hsync_end % 2) || (mode->htotal % 2))
+                       return -EINVAL;
+       }
+
+       /* 4096x2160@60 is not reliable without overclocking core */
+       if (mode->hdisplay > 3840 && mode->vdisplay >= 2160 &&
+           drm_mode_vrefresh(mode) >= 50 &&
+           !vc4->hvs->vc5_hdmi_enable_4096by2160)
                return -EINVAL;
 
        /*
@@ -1110,24 +1876,14 @@ static int vc4_hdmi_encoder_atomic_check(struct drm_encoder *encoder,
                pixel_rate = mode->clock * 1000;
        }
 
-       if (conn_state->max_bpc == 12) {
-               pixel_rate = pixel_rate * 150;
-               do_div(pixel_rate, 100);
-       } else if (conn_state->max_bpc == 10) {
-               pixel_rate = pixel_rate * 125;
-               do_div(pixel_rate, 100);
-       }
-
-       if (mode->flags & DRM_MODE_FLAG_DBLCLK)
-               pixel_rate = pixel_rate * 2;
-
-       if (pixel_rate > vc4_hdmi->variant->max_pixel_clock)
-               return -EINVAL;
-
-       if (vc4_hdmi->disable_4kp60 && (pixel_rate > HDMI_14_MAX_TMDS_CLK))
-               return -EINVAL;
+       ret = vc4_hdmi_encoder_compute_config(vc4_hdmi, vc4_state, mode);
+       if (ret)
+               return ret;
 
-       vc4_state->pixel_rate = pixel_rate;
+       /* vc4_hdmi_encoder_compute_config may have changed output_bpc and/or output_format */
+       if (vc4_state->output_bpc != old_vc4_state->output_bpc ||
+           vc4_state->output_format != old_vc4_state->output_format)
+               crtc_state->mode_changed = true;
 
        return 0;
 }
@@ -1137,6 +1893,8 @@ vc4_hdmi_encoder_mode_valid(struct drm_encoder *encoder,
                            const struct drm_display_mode *mode)
 {
        struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
+       const struct drm_connector *connector = &vc4_hdmi->connector;
+       struct vc4_dev *vc4 = to_vc4_dev(connector->dev);
 
        if (vc4_hdmi->variant->unsupported_odd_h_timings &&
            !(mode->flags & DRM_MODE_FLAG_DBLCLK) &&
@@ -1144,20 +1902,18 @@ vc4_hdmi_encoder_mode_valid(struct drm_encoder *encoder,
             (mode->hsync_end % 2) || (mode->htotal % 2)))
                return MODE_H_ILLEGAL;
 
-       if ((mode->clock * 1000) > vc4_hdmi->variant->max_pixel_clock)
-               return MODE_CLOCK_HIGH;
-
-       if (vc4_hdmi->disable_4kp60 && vc4_hdmi_mode_needs_scrambling(mode))
+       if (mode->hdisplay > 3840 && mode->vdisplay >= 2160 &&
+           drm_mode_vrefresh(mode) >= 50 &&
+           !vc4->hvs->vc5_hdmi_enable_4096by2160)
                return MODE_CLOCK_HIGH;
 
-       return MODE_OK;
+       return vc4_hdmi_encoder_clock_valid(vc4_hdmi, mode->clock * 1000);
 }
 
 static const struct drm_encoder_helper_funcs vc4_hdmi_encoder_helper_funcs = {
        .atomic_check = vc4_hdmi_encoder_atomic_check,
+       .atomic_mode_set = vc4_hdmi_encoder_atomic_mode_set,
        .mode_valid = vc4_hdmi_encoder_mode_valid,
-       .disable = vc4_hdmi_encoder_disable,
-       .enable = vc4_hdmi_encoder_enable,
 };
 
 static u32 vc4_hdmi_channel_map(struct vc4_hdmi *vc4_hdmi, u32 channel_mask)
@@ -1184,11 +1940,24 @@ static u32 vc5_hdmi_channel_map(struct vc4_hdmi *vc4_hdmi, u32 channel_mask)
        return channel_map;
 }
 
+static bool vc5_hdmi_hp_detect(struct vc4_hdmi *vc4_hdmi)
+{
+       unsigned long flags;
+       u32 hotplug;
+
+       spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
+       hotplug = HDMI_READ(HDMI_HOTPLUG);
+       spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
+
+       return !!(hotplug & VC4_HDMI_HOTPLUG_CONNECTED);
+}
+
 /* HDMI audio codec callbacks */
 static void vc4_hdmi_audio_set_mai_clock(struct vc4_hdmi *vc4_hdmi,
                                         unsigned int samplerate)
 {
        u32 hsm_clock = clk_get_rate(vc4_hdmi->audio_clock);
+       unsigned long flags;
        unsigned long n, m;
 
        rational_best_approximation(hsm_clock, samplerate,
@@ -1198,19 +1967,22 @@ static void vc4_hdmi_audio_set_mai_clock(struct vc4_hdmi *vc4_hdmi,
                                     VC4_HD_MAI_SMP_M_SHIFT) + 1,
                                    &n, &m);
 
+       spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
        HDMI_WRITE(HDMI_MAI_SMP,
                   VC4_SET_FIELD(n, VC4_HD_MAI_SMP_N) |
                   VC4_SET_FIELD(m - 1, VC4_HD_MAI_SMP_M));
+       spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
 }
 
 static void vc4_hdmi_set_n_cts(struct vc4_hdmi *vc4_hdmi, unsigned int samplerate)
 {
-       struct drm_encoder *encoder = &vc4_hdmi->encoder.base.base;
-       struct drm_crtc *crtc = encoder->crtc;
-       const struct drm_display_mode *mode = &crtc->state->adjusted_mode;
+       const struct drm_display_mode *mode = &vc4_hdmi->saved_adjusted_mode;
        u32 n, cts;
        u64 tmp;
 
+       lockdep_assert_held(&vc4_hdmi->mutex);
+       lockdep_assert_held(&vc4_hdmi->hw_lock);
+
        n = 128 * samplerate / 1000;
        tmp = (u64)(mode->clock * 1000) * n;
        do_div(tmp, 128 * samplerate);
@@ -1236,31 +2008,48 @@ static inline struct vc4_hdmi *dai_to_hdmi(struct snd_soc_dai *dai)
        return snd_soc_card_get_drvdata(card);
 }
 
-static int vc4_hdmi_audio_startup(struct device *dev, void *data)
+static bool vc4_hdmi_audio_can_stream(struct vc4_hdmi *vc4_hdmi)
 {
-       struct vc4_hdmi *vc4_hdmi = dev_get_drvdata(dev);
-       struct drm_encoder *encoder = &vc4_hdmi->encoder.base.base;
+       lockdep_assert_held(&vc4_hdmi->mutex);
 
        /*
-        * If the HDMI encoder hasn't probed, or the encoder is
-        * currently in DVI mode, treat the codec dai as missing.
+        * If the encoder is currently in DVI mode, treat the codec DAI
+        * as missing.
         */
-       if (!encoder->crtc || !(HDMI_READ(HDMI_RAM_PACKET_CONFIG) &
-                               VC4_HDMI_RAM_PACKET_ENABLE))
+       if (!vc4_hdmi->encoder.hdmi_monitor)
+               return false;
+
+       return true;
+}
+
+static int vc4_hdmi_audio_startup(struct device *dev, void *data)
+{
+       struct vc4_hdmi *vc4_hdmi = dev_get_drvdata(dev);
+       unsigned long flags;
+
+       mutex_lock(&vc4_hdmi->mutex);
+
+       if (!vc4_hdmi_audio_can_stream(vc4_hdmi)) {
+               mutex_unlock(&vc4_hdmi->mutex);
                return -ENODEV;
+       }
 
        vc4_hdmi->audio.streaming = true;
 
+       spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
        HDMI_WRITE(HDMI_MAI_CTL,
                   VC4_HD_MAI_CTL_RESET |
                   VC4_HD_MAI_CTL_FLUSH |
                   VC4_HD_MAI_CTL_DLATE |
                   VC4_HD_MAI_CTL_ERRORE |
                   VC4_HD_MAI_CTL_ERRORF);
+       spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
 
        if (vc4_hdmi->variant->phy_rng_enable)
                vc4_hdmi->variant->phy_rng_enable(vc4_hdmi);
 
+       mutex_unlock(&vc4_hdmi->mutex);
+
        return 0;
 }
 
@@ -1268,32 +2057,48 @@ static void vc4_hdmi_audio_reset(struct vc4_hdmi *vc4_hdmi)
 {
        struct drm_encoder *encoder = &vc4_hdmi->encoder.base.base;
        struct device *dev = &vc4_hdmi->pdev->dev;
+       unsigned long flags;
        int ret;
 
+       lockdep_assert_held(&vc4_hdmi->mutex);
+
        vc4_hdmi->audio.streaming = false;
        ret = vc4_hdmi_stop_packet(encoder, HDMI_INFOFRAME_TYPE_AUDIO, false);
        if (ret)
                dev_err(dev, "Failed to stop audio infoframe: %d\n", ret);
 
+       spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
+
        HDMI_WRITE(HDMI_MAI_CTL, VC4_HD_MAI_CTL_RESET);
        HDMI_WRITE(HDMI_MAI_CTL, VC4_HD_MAI_CTL_ERRORF);
        HDMI_WRITE(HDMI_MAI_CTL, VC4_HD_MAI_CTL_FLUSH);
+
+       spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
 }
 
 static void vc4_hdmi_audio_shutdown(struct device *dev, void *data)
 {
        struct vc4_hdmi *vc4_hdmi = dev_get_drvdata(dev);
+       unsigned long flags;
+
+       mutex_lock(&vc4_hdmi->mutex);
+
+       spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
 
        HDMI_WRITE(HDMI_MAI_CTL,
                   VC4_HD_MAI_CTL_DLATE |
                   VC4_HD_MAI_CTL_ERRORE |
                   VC4_HD_MAI_CTL_ERRORF);
 
+       spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
+
        if (vc4_hdmi->variant->phy_rng_disable)
                vc4_hdmi->variant->phy_rng_disable(vc4_hdmi);
 
        vc4_hdmi->audio.streaming = false;
        vc4_hdmi_audio_reset(vc4_hdmi);
+
+       mutex_unlock(&vc4_hdmi->mutex);
 }
 
 static int sample_rate_to_mai_fmt(int samplerate)
@@ -1343,6 +2148,7 @@ static int vc4_hdmi_audio_prepare(struct device *dev, void *data,
        struct drm_encoder *encoder = &vc4_hdmi->encoder.base.base;
        unsigned int sample_rate = params->sample_rate;
        unsigned int channels = params->channels;
+       unsigned long flags;
        u32 audio_packet_config, channel_mask;
        u32 channel_map;
        u32 mai_audio_format;
@@ -1351,14 +2157,22 @@ static int vc4_hdmi_audio_prepare(struct device *dev, void *data,
        dev_dbg(dev, "%s: %u Hz, %d bit, %d channels\n", __func__,
                sample_rate, params->sample_width, channels);
 
+       mutex_lock(&vc4_hdmi->mutex);
+
+       if (!vc4_hdmi_audio_can_stream(vc4_hdmi)) {
+               mutex_unlock(&vc4_hdmi->mutex);
+               return -EINVAL;
+       }
+
+       vc4_hdmi_audio_set_mai_clock(vc4_hdmi, sample_rate);
+
+       spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
        HDMI_WRITE(HDMI_MAI_CTL,
                   VC4_SET_FIELD(channels, VC4_HD_MAI_CTL_CHNUM) |
                   VC4_HD_MAI_CTL_WHOLSMP |
                   VC4_HD_MAI_CTL_CHALIGN |
                   VC4_HD_MAI_CTL_ENABLE);
 
-       vc4_hdmi_audio_set_mai_clock(vc4_hdmi, sample_rate);
-
        mai_sample_rate = sample_rate_to_mai_fmt(sample_rate);
        if (params->iec.status[0] & IEC958_AES0_NONAUDIO &&
            params->channels == 8)
@@ -1383,10 +2197,10 @@ static int vc4_hdmi_audio_prepare(struct device *dev, void *data,
 
        /* Set the MAI threshold */
        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(0x10, VC4_HD_MAI_THR_DREQHIGH) |
-                  VC4_SET_FIELD(0x10, VC4_HD_MAI_THR_DREQLOW));
+                  VC4_SET_FIELD(0x08, VC4_HD_MAI_THR_PANICHIGH) |
+                  VC4_SET_FIELD(0x08, VC4_HD_MAI_THR_PANICLOW) |
+                  VC4_SET_FIELD(0x06, VC4_HD_MAI_THR_DREQHIGH) |
+                  VC4_SET_FIELD(0x08, VC4_HD_MAI_THR_DREQLOW));
 
        HDMI_WRITE(HDMI_MAI_CONFIG,
                   VC4_HDMI_MAI_CONFIG_BIT_REVERSE |
@@ -1396,10 +2210,16 @@ static int vc4_hdmi_audio_prepare(struct device *dev, void *data,
        channel_map = vc4_hdmi->variant->channel_map(vc4_hdmi, channel_mask);
        HDMI_WRITE(HDMI_MAI_CHANNEL_MAP, channel_map);
        HDMI_WRITE(HDMI_AUDIO_PACKET_CONFIG, audio_packet_config);
+
        vc4_hdmi_set_n_cts(vc4_hdmi, sample_rate);
 
+       spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
+
        memcpy(&vc4_hdmi->audio.infoframe, &params->cea, sizeof(params->cea));
-       vc4_hdmi_set_audio_infoframe(encoder);
+       if (vc4_hdmi->output_enabled)
+               vc4_hdmi_set_audio_infoframe(encoder);
+
+       mutex_unlock(&vc4_hdmi->mutex);
 
        return 0;
 }
@@ -1443,7 +2263,9 @@ static int vc4_hdmi_audio_get_eld(struct device *dev, void *data,
        struct vc4_hdmi *vc4_hdmi = dev_get_drvdata(dev);
        struct drm_connector *connector = &vc4_hdmi->connector;
 
+       mutex_lock(&vc4_hdmi->mutex);
        memcpy(buf, connector->eld, min(sizeof(connector->eld), len));
+       mutex_unlock(&vc4_hdmi->mutex);
 
        return 0;
 }
@@ -1472,10 +2294,12 @@ static int vc4_hdmi_audio_init(struct vc4_hdmi *vc4_hdmi)
        const __be32 *addr;
        int index;
        int ret;
+       int len;
 
-       if (!of_find_property(dev->of_node, "dmas", NULL)) {
+       if (!of_find_property(dev->of_node, "dmas", &len) ||
+           len == 0) {
                dev_warn(dev,
-                        "'dmas' DT property is missing, no HDMI audio\n");
+                        "'dmas' DT property is missing or empty, no HDMI audio\n");
                return 0;
        }
 
@@ -1572,10 +2396,11 @@ static void vc4_hdmi_audio_exit(struct vc4_hdmi *vc4_hdmi)
 static irqreturn_t vc4_hdmi_hpd_irq_thread(int irq, void *priv)
 {
        struct vc4_hdmi *vc4_hdmi = priv;
-       struct drm_device *dev = vc4_hdmi->connector.dev;
+       struct drm_connector *connector = &vc4_hdmi->connector;
+       struct drm_device *dev = connector->dev;
 
        if (dev && dev->registered)
-               drm_kms_helper_hotplug_event(dev);
+               drm_connector_helper_hpd_irq_event(connector);
 
        return IRQ_HANDLED;
 }
@@ -1671,6 +2496,8 @@ static void vc4_cec_read_msg(struct vc4_hdmi *vc4_hdmi, u32 cntrl1)
        struct cec_msg *msg = &vc4_hdmi->cec_rx_msg;
        unsigned int i;
 
+       lockdep_assert_held(&vc4_hdmi->hw_lock);
+
        msg->len = 1 + ((cntrl1 & VC4_HDMI_CEC_REC_WRD_CNT_MASK) >>
                                        VC4_HDMI_CEC_REC_WRD_CNT_SHIFT);
 
@@ -1689,11 +2516,12 @@ static void vc4_cec_read_msg(struct vc4_hdmi *vc4_hdmi, u32 cntrl1)
        }
 }
 
-static irqreturn_t vc4_cec_irq_handler_tx_bare(int irq, void *priv)
+static irqreturn_t vc4_cec_irq_handler_tx_bare_locked(struct vc4_hdmi *vc4_hdmi)
 {
-       struct vc4_hdmi *vc4_hdmi = priv;
        u32 cntrl1;
 
+       lockdep_assert_held(&vc4_hdmi->hw_lock);
+
        cntrl1 = HDMI_READ(HDMI_CEC_CNTRL_1);
        vc4_hdmi->cec_tx_ok = cntrl1 & VC4_HDMI_CEC_TX_STATUS_GOOD;
        cntrl1 &= ~VC4_HDMI_CEC_START_XMIT_BEGIN;
@@ -1702,11 +2530,24 @@ static irqreturn_t vc4_cec_irq_handler_tx_bare(int irq, void *priv)
        return IRQ_WAKE_THREAD;
 }
 
-static irqreturn_t vc4_cec_irq_handler_rx_bare(int irq, void *priv)
+static irqreturn_t vc4_cec_irq_handler_tx_bare(int irq, void *priv)
 {
        struct vc4_hdmi *vc4_hdmi = priv;
+       irqreturn_t ret;
+
+       spin_lock(&vc4_hdmi->hw_lock);
+       ret = vc4_cec_irq_handler_tx_bare_locked(vc4_hdmi);
+       spin_unlock(&vc4_hdmi->hw_lock);
+
+       return ret;
+}
+
+static irqreturn_t vc4_cec_irq_handler_rx_bare_locked(struct vc4_hdmi *vc4_hdmi)
+{
        u32 cntrl1;
 
+       lockdep_assert_held(&vc4_hdmi->hw_lock);
+
        vc4_hdmi->cec_rx_msg.len = 0;
        cntrl1 = HDMI_READ(HDMI_CEC_CNTRL_1);
        vc4_cec_read_msg(vc4_hdmi, cntrl1);
@@ -1719,6 +2560,18 @@ static irqreturn_t vc4_cec_irq_handler_rx_bare(int irq, void *priv)
        return IRQ_WAKE_THREAD;
 }
 
+static irqreturn_t vc4_cec_irq_handler_rx_bare(int irq, void *priv)
+{
+       struct vc4_hdmi *vc4_hdmi = priv;
+       irqreturn_t ret;
+
+       spin_lock(&vc4_hdmi->hw_lock);
+       ret = vc4_cec_irq_handler_rx_bare_locked(vc4_hdmi);
+       spin_unlock(&vc4_hdmi->hw_lock);
+
+       return ret;
+}
+
 static irqreturn_t vc4_cec_irq_handler(int irq, void *priv)
 {
        struct vc4_hdmi *vc4_hdmi = priv;
@@ -1729,69 +2582,142 @@ static irqreturn_t vc4_cec_irq_handler(int irq, void *priv)
        if (!(stat & VC4_HDMI_CPU_CEC))
                return IRQ_NONE;
 
+       spin_lock(&vc4_hdmi->hw_lock);
        cntrl5 = HDMI_READ(HDMI_CEC_CNTRL_5);
        vc4_hdmi->cec_irq_was_rx = cntrl5 & VC4_HDMI_CEC_RX_CEC_INT;
        if (vc4_hdmi->cec_irq_was_rx)
-               ret = vc4_cec_irq_handler_rx_bare(irq, priv);
+               ret = vc4_cec_irq_handler_rx_bare_locked(vc4_hdmi);
        else
-               ret = vc4_cec_irq_handler_tx_bare(irq, priv);
+               ret = vc4_cec_irq_handler_tx_bare_locked(vc4_hdmi);
 
        HDMI_WRITE(HDMI_CEC_CPU_CLEAR, VC4_HDMI_CPU_CEC);
+       spin_unlock(&vc4_hdmi->hw_lock);
+
        return ret;
 }
 
-static int vc4_hdmi_cec_adap_enable(struct cec_adapter *adap, bool enable)
+static int vc4_hdmi_cec_enable(struct cec_adapter *adap)
 {
        struct vc4_hdmi *vc4_hdmi = cec_get_drvdata(adap);
        /* clock period in microseconds */
        const u32 usecs = 1000000 / CEC_CLOCK_FREQ;
-       u32 val = HDMI_READ(HDMI_CEC_CNTRL_5);
+       unsigned long flags;
+       u32 val;
+       int ret;
+
+       /*
+        * NOTE: This function should really take vc4_hdmi->mutex, but doing so
+        * results in a reentrancy since cec_s_phys_addr_from_edid() called in
+        * .detect or .get_modes might call .adap_enable, which leads to this
+        * function being called with that mutex held.
+        *
+        * Concurrency is not an issue for the moment since we don't share any
+        * state with KMS, so we can ignore the lock for now, but we need to
+        * keep it in mind if we were to change that assumption.
+        */
+
+       ret = pm_runtime_resume_and_get(&vc4_hdmi->pdev->dev);
+       if (ret)
+               return ret;
 
+       spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
+
+       val = HDMI_READ(HDMI_CEC_CNTRL_5);
        val &= ~(VC4_HDMI_CEC_TX_SW_RESET | VC4_HDMI_CEC_RX_SW_RESET |
                 VC4_HDMI_CEC_CNT_TO_4700_US_MASK |
                 VC4_HDMI_CEC_CNT_TO_4500_US_MASK);
        val |= ((4700 / usecs) << VC4_HDMI_CEC_CNT_TO_4700_US_SHIFT) |
               ((4500 / usecs) << VC4_HDMI_CEC_CNT_TO_4500_US_SHIFT);
 
-       if (enable) {
-               HDMI_WRITE(HDMI_CEC_CNTRL_5, val |
-                          VC4_HDMI_CEC_TX_SW_RESET | VC4_HDMI_CEC_RX_SW_RESET);
-               HDMI_WRITE(HDMI_CEC_CNTRL_5, val);
-               HDMI_WRITE(HDMI_CEC_CNTRL_2,
-                          ((1500 / usecs) << VC4_HDMI_CEC_CNT_TO_1500_US_SHIFT) |
-                          ((1300 / usecs) << VC4_HDMI_CEC_CNT_TO_1300_US_SHIFT) |
-                          ((800 / usecs) << VC4_HDMI_CEC_CNT_TO_800_US_SHIFT) |
-                          ((600 / usecs) << VC4_HDMI_CEC_CNT_TO_600_US_SHIFT) |
-                          ((400 / usecs) << VC4_HDMI_CEC_CNT_TO_400_US_SHIFT));
-               HDMI_WRITE(HDMI_CEC_CNTRL_3,
-                          ((2750 / usecs) << VC4_HDMI_CEC_CNT_TO_2750_US_SHIFT) |
-                          ((2400 / usecs) << VC4_HDMI_CEC_CNT_TO_2400_US_SHIFT) |
-                          ((2050 / usecs) << VC4_HDMI_CEC_CNT_TO_2050_US_SHIFT) |
-                          ((1700 / usecs) << VC4_HDMI_CEC_CNT_TO_1700_US_SHIFT));
-               HDMI_WRITE(HDMI_CEC_CNTRL_4,
-                          ((4300 / usecs) << VC4_HDMI_CEC_CNT_TO_4300_US_SHIFT) |
-                          ((3900 / usecs) << VC4_HDMI_CEC_CNT_TO_3900_US_SHIFT) |
-                          ((3600 / usecs) << VC4_HDMI_CEC_CNT_TO_3600_US_SHIFT) |
-                          ((3500 / usecs) << VC4_HDMI_CEC_CNT_TO_3500_US_SHIFT));
-
-               if (!vc4_hdmi->variant->external_irq_controller)
-                       HDMI_WRITE(HDMI_CEC_CPU_MASK_CLEAR, VC4_HDMI_CPU_CEC);
-       } else {
-               if (!vc4_hdmi->variant->external_irq_controller)
-                       HDMI_WRITE(HDMI_CEC_CPU_MASK_SET, VC4_HDMI_CPU_CEC);
-               HDMI_WRITE(HDMI_CEC_CNTRL_5, val |
-                          VC4_HDMI_CEC_TX_SW_RESET | VC4_HDMI_CEC_RX_SW_RESET);
-       }
+       HDMI_WRITE(HDMI_CEC_CNTRL_5, val |
+                  VC4_HDMI_CEC_TX_SW_RESET | VC4_HDMI_CEC_RX_SW_RESET);
+       HDMI_WRITE(HDMI_CEC_CNTRL_5, val);
+       HDMI_WRITE(HDMI_CEC_CNTRL_2,
+                  ((1500 / usecs) << VC4_HDMI_CEC_CNT_TO_1500_US_SHIFT) |
+                  ((1300 / usecs) << VC4_HDMI_CEC_CNT_TO_1300_US_SHIFT) |
+                  ((800 / usecs) << VC4_HDMI_CEC_CNT_TO_800_US_SHIFT) |
+                  ((600 / usecs) << VC4_HDMI_CEC_CNT_TO_600_US_SHIFT) |
+                  ((400 / usecs) << VC4_HDMI_CEC_CNT_TO_400_US_SHIFT));
+       HDMI_WRITE(HDMI_CEC_CNTRL_3,
+                  ((2750 / usecs) << VC4_HDMI_CEC_CNT_TO_2750_US_SHIFT) |
+                  ((2400 / usecs) << VC4_HDMI_CEC_CNT_TO_2400_US_SHIFT) |
+                  ((2050 / usecs) << VC4_HDMI_CEC_CNT_TO_2050_US_SHIFT) |
+                  ((1700 / usecs) << VC4_HDMI_CEC_CNT_TO_1700_US_SHIFT));
+       HDMI_WRITE(HDMI_CEC_CNTRL_4,
+                  ((4300 / usecs) << VC4_HDMI_CEC_CNT_TO_4300_US_SHIFT) |
+                  ((3900 / usecs) << VC4_HDMI_CEC_CNT_TO_3900_US_SHIFT) |
+                  ((3600 / usecs) << VC4_HDMI_CEC_CNT_TO_3600_US_SHIFT) |
+                  ((3500 / usecs) << VC4_HDMI_CEC_CNT_TO_3500_US_SHIFT));
+
+       if (!vc4_hdmi->variant->external_irq_controller)
+               HDMI_WRITE(HDMI_CEC_CPU_MASK_CLEAR, VC4_HDMI_CPU_CEC);
+
+       spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
+
+       return 0;
+}
+
+static int vc4_hdmi_cec_disable(struct cec_adapter *adap)
+{
+       struct vc4_hdmi *vc4_hdmi = cec_get_drvdata(adap);
+       unsigned long flags;
+
+       /*
+        * NOTE: This function should really take vc4_hdmi->mutex, but doing so
+        * results in a reentrancy since cec_s_phys_addr_from_edid() called in
+        * .detect or .get_modes might call .adap_enable, which leads to this
+        * function being called with that mutex held.
+        *
+        * Concurrency is not an issue for the moment since we don't share any
+        * state with KMS, so we can ignore the lock for now, but we need to
+        * keep it in mind if we were to change that assumption.
+        */
+
+       spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
+
+       if (!vc4_hdmi->variant->external_irq_controller)
+               HDMI_WRITE(HDMI_CEC_CPU_MASK_SET, VC4_HDMI_CPU_CEC);
+
+       HDMI_WRITE(HDMI_CEC_CNTRL_5, HDMI_READ(HDMI_CEC_CNTRL_5) |
+                  VC4_HDMI_CEC_TX_SW_RESET | VC4_HDMI_CEC_RX_SW_RESET);
+
+       spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
+
+       pm_runtime_put(&vc4_hdmi->pdev->dev);
+
        return 0;
 }
 
+static int vc4_hdmi_cec_adap_enable(struct cec_adapter *adap, bool enable)
+{
+       if (enable)
+               return vc4_hdmi_cec_enable(adap);
+       else
+               return vc4_hdmi_cec_disable(adap);
+}
+
 static int vc4_hdmi_cec_adap_log_addr(struct cec_adapter *adap, u8 log_addr)
 {
        struct vc4_hdmi *vc4_hdmi = cec_get_drvdata(adap);
+       unsigned long flags;
+
+       /*
+        * NOTE: This function should really take vc4_hdmi->mutex, but doing so
+        * results in a reentrancy since cec_s_phys_addr_from_edid() called in
+        * .detect or .get_modes might call .adap_enable, which leads to this
+        * function being called with that mutex held.
+        *
+        * Concurrency is not an issue for the moment since we don't share any
+        * state with KMS, so we can ignore the lock for now, but we need to
+        * keep it in mind if we were to change that assumption.
+        */
 
+       spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
        HDMI_WRITE(HDMI_CEC_CNTRL_1,
                   (HDMI_READ(HDMI_CEC_CNTRL_1) & ~VC4_HDMI_CEC_ADDR_MASK) |
                   (log_addr & 0xf) << VC4_HDMI_CEC_ADDR_SHIFT);
+       spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
+
        return 0;
 }
 
@@ -1800,14 +2726,28 @@ static int vc4_hdmi_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
 {
        struct vc4_hdmi *vc4_hdmi = cec_get_drvdata(adap);
        struct drm_device *dev = vc4_hdmi->connector.dev;
+       unsigned long flags;
        u32 val;
        unsigned int i;
 
+       /*
+        * NOTE: This function should really take vc4_hdmi->mutex, but doing so
+        * results in a reentrancy since cec_s_phys_addr_from_edid() called in
+        * .detect or .get_modes might call .adap_enable, which leads to this
+        * function being called with that mutex held.
+        *
+        * Concurrency is not an issue for the moment since we don't share any
+        * state with KMS, so we can ignore the lock for now, but we need to
+        * keep it in mind if we were to change that assumption.
+        */
+
        if (msg->len > 16) {
                drm_err(dev, "Attempting to transmit too much data (%d)\n", msg->len);
                return -ENOMEM;
        }
 
+       spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
+
        for (i = 0; i < msg->len; i += 4)
                HDMI_WRITE(HDMI_CEC_TX_DATA_1 + (i >> 2),
                           (msg->msg[i]) |
@@ -1823,6 +2763,9 @@ static int vc4_hdmi_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
        val |= VC4_HDMI_CEC_START_XMIT_BEGIN;
 
        HDMI_WRITE(HDMI_CEC_CNTRL_1, val);
+
+       spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
+
        return 0;
 }
 
@@ -1837,7 +2780,7 @@ static int vc4_hdmi_cec_init(struct vc4_hdmi *vc4_hdmi)
        struct cec_connector_info conn_info;
        struct platform_device *pdev = vc4_hdmi->pdev;
        struct device *dev = &pdev->dev;
-       u32 value;
+       unsigned long flags;
        int ret;
 
        if (!of_find_property(dev->of_node, "interrupts", NULL)) {
@@ -1856,13 +2799,6 @@ static int vc4_hdmi_cec_init(struct vc4_hdmi *vc4_hdmi)
        cec_fill_conn_info_from_drm(&conn_info, &vc4_hdmi->connector);
        cec_s_conn_info(vc4_hdmi->cec_adap, &conn_info);
 
-       value = HDMI_READ(HDMI_CEC_CNTRL_1);
-       /* Set the logical address to Unregistered */
-       value |= VC4_HDMI_CEC_ADDR_MASK;
-       HDMI_WRITE(HDMI_CEC_CNTRL_1, value);
-
-       vc4_hdmi_cec_update_clk_div(vc4_hdmi);
-
        if (vc4_hdmi->variant->external_irq_controller) {
                ret = request_threaded_irq(platform_get_irq_byname(pdev, "cec-rx"),
                                           vc4_cec_irq_handler_rx_bare,
@@ -1878,7 +2814,9 @@ static int vc4_hdmi_cec_init(struct vc4_hdmi *vc4_hdmi)
                if (ret)
                        goto err_remove_cec_rx_handler;
        } else {
+               spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
                HDMI_WRITE(HDMI_CEC_CPU_MASK_SET, 0xffffffff);
+               spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
 
                ret = request_threaded_irq(platform_get_irq(pdev, 0),
                                           vc4_cec_irq_handler,
@@ -1923,6 +2861,29 @@ static void vc4_hdmi_cec_exit(struct vc4_hdmi *vc4_hdmi)
 
        cec_unregister_adapter(vc4_hdmi->cec_adap);
 }
+
+static int vc4_hdmi_cec_resume(struct vc4_hdmi *vc4_hdmi)
+{
+       unsigned long flags;
+       u32 value;
+
+       spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
+       value = HDMI_READ(HDMI_CEC_CNTRL_1);
+       /* Set the logical address to Unregistered */
+       value |= VC4_HDMI_CEC_ADDR_MASK;
+       HDMI_WRITE(HDMI_CEC_CNTRL_1, value);
+       spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
+
+       vc4_hdmi_cec_update_clk_div(vc4_hdmi);
+
+       if (!vc4_hdmi->variant->external_irq_controller) {
+               spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
+               HDMI_WRITE(HDMI_CEC_CPU_MASK_SET, 0xffffffff);
+               spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
+       }
+
+       return 0;
+}
 #else
 static int vc4_hdmi_cec_init(struct vc4_hdmi *vc4_hdmi)
 {
@@ -1931,6 +2892,10 @@ static int vc4_hdmi_cec_init(struct vc4_hdmi *vc4_hdmi)
 
 static void vc4_hdmi_cec_exit(struct vc4_hdmi *vc4_hdmi) {};
 
+static int vc4_hdmi_cec_resume(struct vc4_hdmi *vc4_hdmi)
+{
+       return 0;
+}
 #endif
 
 static int vc4_hdmi_build_regset(struct vc4_hdmi *vc4_hdmi,
@@ -2015,6 +2980,7 @@ static int vc5_hdmi_init_resources(struct vc4_hdmi *vc4_hdmi)
        struct platform_device *pdev = vc4_hdmi->pdev;
        struct device *dev = &pdev->dev;
        struct resource *res;
+       int ret;
 
        res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "hdmi");
        if (!res)
@@ -2111,6 +3077,38 @@ static int vc5_hdmi_init_resources(struct vc4_hdmi *vc4_hdmi)
                return PTR_ERR(vc4_hdmi->reset);
        }
 
+       ret = vc4_hdmi_build_regset(vc4_hdmi, &vc4_hdmi->hdmi_regset, VC4_HDMI);
+       if (ret)
+               return ret;
+
+       ret = vc4_hdmi_build_regset(vc4_hdmi, &vc4_hdmi->hd_regset, VC4_HD);
+       if (ret)
+               return ret;
+
+       ret = vc4_hdmi_build_regset(vc4_hdmi, &vc4_hdmi->cec_regset, VC5_CEC);
+       if (ret)
+               return ret;
+
+       ret = vc4_hdmi_build_regset(vc4_hdmi, &vc4_hdmi->csc_regset, VC5_CSC);
+       if (ret)
+               return ret;
+
+       ret = vc4_hdmi_build_regset(vc4_hdmi, &vc4_hdmi->dvp_regset, VC5_DVP);
+       if (ret)
+               return ret;
+
+       ret = vc4_hdmi_build_regset(vc4_hdmi, &vc4_hdmi->phy_regset, VC5_PHY);
+       if (ret)
+               return ret;
+
+       ret = vc4_hdmi_build_regset(vc4_hdmi, &vc4_hdmi->ram_regset, VC5_RAM);
+       if (ret)
+               return ret;
+
+       ret = vc4_hdmi_build_regset(vc4_hdmi, &vc4_hdmi->rm_regset, VC5_RM);
+       if (ret)
+               return ret;
+
        return 0;
 }
 
@@ -2132,6 +3130,15 @@ static int vc4_hdmi_runtime_resume(struct device *dev)
        if (ret)
                return ret;
 
+       if (vc4_hdmi->variant->reset)
+               vc4_hdmi->variant->reset(vc4_hdmi);
+
+       ret = vc4_hdmi_cec_resume(vc4_hdmi);
+       if (ret) {
+               clk_disable_unprepare(vc4_hdmi->hsm_clock);
+               return ret;
+       }
+
        return 0;
 }
 
@@ -2148,6 +3155,8 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data)
        vc4_hdmi = devm_kzalloc(dev, sizeof(*vc4_hdmi), GFP_KERNEL);
        if (!vc4_hdmi)
                return -ENOMEM;
+       mutex_init(&vc4_hdmi->mutex);
+       spin_lock_init(&vc4_hdmi->hw_lock);
        INIT_DELAYED_WORK(&vc4_hdmi->scrambling_work, vc4_hdmi_scrambling_wq);
 
        dev_set_drvdata(dev, vc4_hdmi);
@@ -2161,6 +3170,15 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data)
        vc4_hdmi->pdev = pdev;
        vc4_hdmi->variant = variant;
 
+       /*
+        * Since we don't know the state of the controller and its
+        * display (if any), let's assume it's always enabled.
+        * vc4_hdmi_disable_scrambling() will thus run at boot, make
+        * sure it's disabled, and avoid any inconsistency.
+        */
+       if (variant->max_pixel_clock > HDMI_14_MAX_TMDS_CLK)
+               vc4_hdmi->scdc_enabled = true;
+
        ret = variant->init_resources(vc4_hdmi);
        if (ret)
                return ret;
@@ -2190,14 +3208,6 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data)
        vc4_hdmi->disable_wifi_frequencies =
                of_property_read_bool(dev->of_node, "wifi-2.4ghz-coexistence");
 
-       if (variant->max_pixel_clock == 600000000) {
-               struct vc4_dev *vc4 = to_vc4_dev(drm);
-               long max_rate = clk_round_rate(vc4->hvs->core_clk, 550000000);
-
-               if (max_rate < 550000000)
-                       vc4_hdmi->disable_4kp60 = true;
-       }
-
        /*
         * If we boot without any cable connected to the HDMI connector,
         * the firmware will skip the HSM initialization and leave it
@@ -2211,20 +3221,11 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data)
        if (ret)
                goto err_put_ddc;
 
-       /*
-        * We need to have the device powered up at this point to call
-        * our reset hook and for the CEC init.
-        */
-       ret = vc4_hdmi_runtime_resume(dev);
-       if (ret)
-               goto err_put_ddc;
-
-       pm_runtime_get_noresume(dev);
-       pm_runtime_set_active(dev);
        pm_runtime_enable(dev);
 
-       if (vc4_hdmi->variant->reset)
-               vc4_hdmi->variant->reset(vc4_hdmi);
+       ret = pm_runtime_resume_and_get(dev);
+       if (ret)
+               goto err_put_ddc;
 
        if ((of_device_is_compatible(dev->of_node, "brcm,bcm2711-hdmi0") ||
             of_device_is_compatible(dev->of_node, "brcm,bcm2711-hdmi1")) &&
@@ -2357,7 +3358,7 @@ static const struct vc4_hdmi_variant bcm2711_hdmi0_variant = {
        .encoder_type           = VC4_ENCODER_TYPE_HDMI0,
        .debugfs_name           = "hdmi0_regs",
        .card_name              = "vc4-hdmi-0",
-       .max_pixel_clock        = HDMI_14_MAX_TMDS_CLK,
+       .max_pixel_clock        = 600000000,
        .registers              = vc5_hdmi_hdmi0_fields,
        .num_registers          = ARRAY_SIZE(vc5_hdmi_hdmi0_fields),
        .phy_lane_mapping       = {
@@ -2379,6 +3380,7 @@ static const struct vc4_hdmi_variant bcm2711_hdmi0_variant = {
        .phy_rng_disable        = vc5_hdmi_phy_rng_disable,
        .channel_map            = vc5_hdmi_channel_map,
        .supports_hdr           = true,
+       .hp_detect              = vc5_hdmi_hp_detect,
 };
 
 static const struct vc4_hdmi_variant bcm2711_hdmi1_variant = {
@@ -2407,6 +3409,7 @@ static const struct vc4_hdmi_variant bcm2711_hdmi1_variant = {
        .phy_rng_disable        = vc5_hdmi_phy_rng_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[] = {
index c0492da..53dd116 100644 (file)
@@ -12,7 +12,6 @@
 struct vc4_hdmi_encoder {
        struct vc4_encoder base;
        bool hdmi_monitor;
-       bool limited_rgb_range;
 };
 
 static inline struct vc4_hdmi_encoder *
@@ -77,7 +76,9 @@ struct vc4_hdmi_variant {
        void (*reset)(struct vc4_hdmi *vc4_hdmi);
 
        /* Callback to enable / disable the CSC */
-       void (*csc_setup)(struct vc4_hdmi *vc4_hdmi, bool enable);
+       void (*csc_setup)(struct vc4_hdmi *vc4_hdmi,
+                         struct drm_connector_state *state,
+                         const struct drm_display_mode *mode);
 
        /* Callback to configure the video timings in the HDMI block */
        void (*set_timings)(struct vc4_hdmi *vc4_hdmi,
@@ -102,6 +103,9 @@ struct vc4_hdmi_variant {
 
        /* Enables HDR metadata */
        bool supports_hdr;
+
+       /* Callback for hardware specific hotplug detect */
+       bool (*hp_detect)(struct vc4_hdmi *vc4_hdmi);
 };
 
 /* HDMI audio information */
@@ -117,6 +121,13 @@ struct vc4_hdmi_audio {
        bool streaming;
 };
 
+enum vc4_hdmi_output_format {
+       VC4_HDMI_OUTPUT_RGB,
+       VC4_HDMI_OUTPUT_YUV422,
+       VC4_HDMI_OUTPUT_YUV444,
+       VC4_HDMI_OUTPUT_YUV420,
+};
+
 /* General HDMI hardware state. */
 struct vc4_hdmi {
        struct vc4_hdmi_audio audio;
@@ -129,6 +140,8 @@ struct vc4_hdmi {
 
        struct delayed_work scrambling_work;
 
+       struct drm_property *broadcast_rgb_property;
+
        struct i2c_adapter *ddc;
        void __iomem *hdmicore_regs;
        void __iomem *hd_regs;
@@ -156,14 +169,6 @@ struct vc4_hdmi {
         */
        bool disable_wifi_frequencies;
 
-       /*
-        * Even if HDMI0 on the RPi4 can output modes requiring a pixel
-        * rate higher than 297MHz, it needs some adjustments in the
-        * config.txt file to be able to do so and thus won't always be
-        * available.
-        */
-       bool disable_4kp60;
-
        struct cec_adapter *cec_adap;
        struct cec_msg cec_rx_msg;
        bool cec_tx_ok;
@@ -179,6 +184,69 @@ struct vc4_hdmi {
 
        struct debugfs_regset32 hdmi_regset;
        struct debugfs_regset32 hd_regset;
+
+       /**
+        * @hw_lock: Spinlock protecting device register access.
+        */
+       spinlock_t hw_lock;
+
+       /**
+        * @mutex: Mutex protecting the driver access across multiple
+        * frameworks (KMS, ALSA).
+        *
+        * NOTE: While supported, CEC has been left out since
+        * cec_s_phys_addr_from_edid() might call .adap_enable and lead to a
+        * reentrancy issue between .get_modes (or .detect) and .adap_enable.
+        * Since we don't share any state between the CEC hooks and KMS', it's
+        * not a big deal. The only trouble might come from updating the CEC
+        * clock divider which might be affected by a modeset, but CEC should
+        * be resilient to that.
+        */
+       struct mutex mutex;
+
+       /**
+        * @saved_adjusted_mode: Copy of @drm_crtc_state.adjusted_mode
+        * for use by ALSA hooks and interrupt handlers. Protected by @mutex.
+        */
+       struct drm_display_mode saved_adjusted_mode;
+
+       /**
+        * @output_enabled: Is the HDMI controller currently active?
+        * Protected by @mutex.
+        */
+       bool output_enabled;
+
+       /**
+        * @scdc_enabled: Is the HDMI controller currently running with
+        * the scrambler on? Protected by @mutex.
+        */
+       bool scdc_enabled;
+
+       /**
+        * @output_bpc: Copy of @vc4_connector_state.output_bpc for use
+        * outside of KMS hooks. Protected by @mutex.
+        */
+       unsigned int output_bpc;
+
+       /**
+        * @output_format: Copy of @vc4_connector_state.output_format
+        * for use outside of KMS hooks. Protected by @mutex.
+        */
+       enum vc4_hdmi_output_format output_format;
+
+       /**
+        * @broadcast_rgb: Copy of @vc4_connector_state.broadcast_rgb
+        * for use outside of KMS hooks. Protected by @mutex.
+        */
+       int broadcast_rgb;
+
+       /* VC5 debugfs regset */
+       struct debugfs_regset32 cec_regset;
+       struct debugfs_regset32 csc_regset;
+       struct debugfs_regset32 dvp_regset;
+       struct debugfs_regset32 phy_regset;
+       struct debugfs_regset32 ram_regset;
+       struct debugfs_regset32 rm_regset;
 };
 
 static inline struct vc4_hdmi *
@@ -198,6 +266,9 @@ encoder_to_vc4_hdmi(struct drm_encoder *encoder)
 struct vc4_hdmi_connector_state {
        struct drm_connector_state      base;
        unsigned long long              pixel_rate;
+       unsigned int                    output_bpc;
+       enum vc4_hdmi_output_format     output_format;
+       int                             broadcast_rgb;
 };
 
 static inline struct vc4_hdmi_connector_state *
@@ -206,6 +277,12 @@ conn_state_to_vc4_hdmi_conn_state(struct drm_connector_state *conn_state)
        return container_of(conn_state, struct vc4_hdmi_connector_state, base);
 }
 
+static inline const struct vc4_hdmi_connector_state *
+const_conn_state_to_vc4_hdmi_conn_state(const struct drm_connector_state *conn_state)
+{
+       return container_of(conn_state, struct vc4_hdmi_connector_state, base);
+}
+
 void vc4_hdmi_phy_init(struct vc4_hdmi *vc4_hdmi,
                       struct vc4_hdmi_connector_state *vc4_conn_state);
 void vc4_hdmi_phy_disable(struct vc4_hdmi *vc4_hdmi);
index 3653548..62148f0 100644 (file)
 void vc4_hdmi_phy_init(struct vc4_hdmi *vc4_hdmi,
                       struct vc4_hdmi_connector_state *conn_state)
 {
+       unsigned long flags;
+
        /* PHY should be in reset, like
         * vc4_hdmi_encoder_disable() does.
         */
 
+       spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
+
        HDMI_WRITE(HDMI_TX_PHY_RESET_CTL, 0xf << 16);
        HDMI_WRITE(HDMI_TX_PHY_RESET_CTL, 0);
+
+       spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
 }
 
 void vc4_hdmi_phy_disable(struct vc4_hdmi *vc4_hdmi)
 {
+       unsigned long flags;
+
+       spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
        HDMI_WRITE(HDMI_TX_PHY_RESET_CTL, 0xf << 16);
+       spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
 }
 
 void vc4_hdmi_phy_rng_enable(struct vc4_hdmi *vc4_hdmi)
 {
+       unsigned long flags;
+
+       spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
        HDMI_WRITE(HDMI_TX_PHY_CTL_0,
                   HDMI_READ(HDMI_TX_PHY_CTL_0) &
                   ~VC4_HDMI_TX_PHY_RNG_PWRDN);
+       spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
 }
 
 void vc4_hdmi_phy_rng_disable(struct vc4_hdmi *vc4_hdmi)
 {
+       unsigned long flags;
+
+       spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
        HDMI_WRITE(HDMI_TX_PHY_CTL_0,
                   HDMI_READ(HDMI_TX_PHY_CTL_0) |
                   VC4_HDMI_TX_PHY_RNG_PWRDN);
+       spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
 }
 
 static unsigned long long
@@ -336,6 +354,8 @@ phy_get_channel_settings(enum vc4_hdmi_phy_channel chan,
 
 static void vc5_hdmi_reset_phy(struct vc4_hdmi *vc4_hdmi)
 {
+       lockdep_assert_held(&vc4_hdmi->hw_lock);
+
        HDMI_WRITE(HDMI_TX_PHY_RESET_CTL, 0x0f);
        HDMI_WRITE(HDMI_TX_PHY_POWERDOWN_CTL, BIT(10));
 }
@@ -348,10 +368,13 @@ void vc5_hdmi_phy_init(struct vc4_hdmi *vc4_hdmi,
        unsigned long long pixel_freq = conn_state->pixel_rate;
        unsigned long long vco_freq;
        unsigned char word_sel;
+       unsigned long flags;
        u8 vco_sel, vco_div;
 
        vco_freq = phy_get_vco_freq(pixel_freq, &vco_sel, &vco_div);
 
+       spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
+
        vc5_hdmi_reset_phy(vc4_hdmi);
 
        HDMI_WRITE(HDMI_TX_PHY_POWERDOWN_CTL,
@@ -501,23 +524,37 @@ void vc5_hdmi_phy_init(struct vc4_hdmi *vc4_hdmi,
                   HDMI_READ(HDMI_TX_PHY_RESET_CTL) |
                   VC4_HDMI_TX_PHY_RESET_CTL_PLL_RESETB |
                   VC4_HDMI_TX_PHY_RESET_CTL_PLLDIV_RESETB);
+
+       spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
 }
 
 void vc5_hdmi_phy_disable(struct vc4_hdmi *vc4_hdmi)
 {
+       unsigned long flags;
+
+       spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
        vc5_hdmi_reset_phy(vc4_hdmi);
+       spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
 }
 
 void vc5_hdmi_phy_rng_enable(struct vc4_hdmi *vc4_hdmi)
 {
+       unsigned long flags;
+
+       spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
        HDMI_WRITE(HDMI_TX_PHY_POWERDOWN_CTL,
                   HDMI_READ(HDMI_TX_PHY_POWERDOWN_CTL) &
                   ~VC4_HDMI_TX_PHY_POWERDOWN_CTL_RNDGEN_PWRDN);
+       spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
 }
 
 void vc5_hdmi_phy_rng_disable(struct vc4_hdmi *vc4_hdmi)
 {
+       unsigned long flags;
+
+       spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
        HDMI_WRITE(HDMI_TX_PHY_POWERDOWN_CTL,
                   HDMI_READ(HDMI_TX_PHY_POWERDOWN_CTL) |
                   VC4_HDMI_TX_PHY_POWERDOWN_CTL_RNDGEN_PWRDN);
+       spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
 }
index 19d2fdc..48db438 100644 (file)
@@ -1,6 +1,8 @@
 #ifndef _VC4_HDMI_REGS_H_
 #define _VC4_HDMI_REGS_H_
 
+#include <linux/pm_runtime.h>
+
 #include "vc4_hdmi.h"
 
 #define VC4_HDMI_PACKET_STRIDE                 0x24
@@ -52,6 +54,7 @@ enum vc4_hdmi_field {
        HDMI_CSC_24_23,
        HDMI_CSC_32_31,
        HDMI_CSC_34_33,
+       HDMI_CSC_CHANNEL_CTL,
        HDMI_CSC_CTL,
 
        /*
@@ -117,12 +120,24 @@ enum vc4_hdmi_field {
        HDMI_TX_PHY_POWERDOWN_CTL,
        HDMI_TX_PHY_RESET_CTL,
        HDMI_TX_PHY_TMDS_CLK_WORD_SEL,
+       HDMI_VEC_INTERFACE_CFG,
        HDMI_VEC_INTERFACE_XBAR,
        HDMI_VERTA0,
        HDMI_VERTA1,
        HDMI_VERTB0,
        HDMI_VERTB1,
        HDMI_VID_CTL,
+       HDMI_MISC_CONTROL,
+       HDMI_FORMAT_DET_1,
+       HDMI_FORMAT_DET_2,
+       HDMI_FORMAT_DET_3,
+       HDMI_FORMAT_DET_4,
+       HDMI_FORMAT_DET_5,
+       HDMI_FORMAT_DET_6,
+       HDMI_FORMAT_DET_7,
+       HDMI_FORMAT_DET_8,
+       HDMI_FORMAT_DET_9,
+       HDMI_FORMAT_DET_10,
 };
 
 struct vc4_hdmi_register {
@@ -185,6 +200,7 @@ static const struct vc4_hdmi_register __maybe_unused vc4_hdmi_fields[] = {
        VC4_HDMI_REG(HDMI_VERTB0, 0x00d0),
        VC4_HDMI_REG(HDMI_VERTA1, 0x00d4),
        VC4_HDMI_REG(HDMI_VERTB1, 0x00d8),
+       VC4_HDMI_REG(HDMI_MISC_CONTROL, 0x00e4),
        VC4_HDMI_REG(HDMI_CEC_CNTRL_1, 0x00e8),
        VC4_HDMI_REG(HDMI_CEC_CNTRL_2, 0x00ec),
        VC4_HDMI_REG(HDMI_CEC_CNTRL_3, 0x00f0),
@@ -233,8 +249,19 @@ static const struct vc4_hdmi_register __maybe_unused vc5_hdmi_hdmi0_fields[] = {
        VC4_HDMI_REG(HDMI_VERTB0, 0x0f0),
        VC4_HDMI_REG(HDMI_VERTA1, 0x0f4),
        VC4_HDMI_REG(HDMI_VERTB1, 0x0f8),
+       VC4_HDMI_REG(HDMI_MISC_CONTROL, 0x100),
        VC4_HDMI_REG(HDMI_MAI_CHANNEL_MAP, 0x09c),
        VC4_HDMI_REG(HDMI_MAI_CONFIG, 0x0a0),
+       VC4_HDMI_REG(HDMI_FORMAT_DET_1, 0x134),
+       VC4_HDMI_REG(HDMI_FORMAT_DET_2, 0x138),
+       VC4_HDMI_REG(HDMI_FORMAT_DET_3, 0x13c),
+       VC4_HDMI_REG(HDMI_FORMAT_DET_4, 0x140),
+       VC4_HDMI_REG(HDMI_FORMAT_DET_5, 0x144),
+       VC4_HDMI_REG(HDMI_FORMAT_DET_6, 0x148),
+       VC4_HDMI_REG(HDMI_FORMAT_DET_7, 0x14c),
+       VC4_HDMI_REG(HDMI_FORMAT_DET_8, 0x150),
+       VC4_HDMI_REG(HDMI_FORMAT_DET_9, 0x154),
+       VC4_HDMI_REG(HDMI_FORMAT_DET_10, 0x158),
        VC4_HDMI_REG(HDMI_DEEP_COLOR_CONFIG_1, 0x170),
        VC4_HDMI_REG(HDMI_GCP_CONFIG, 0x178),
        VC4_HDMI_REG(HDMI_GCP_WORD_1, 0x17c),
@@ -242,6 +269,7 @@ static const struct vc4_hdmi_register __maybe_unused vc5_hdmi_hdmi0_fields[] = {
        VC4_HDMI_REG(HDMI_SCRAMBLER_CTL, 0x1c4),
 
        VC5_DVP_REG(HDMI_CLOCK_STOP, 0x0bc),
+       VC5_DVP_REG(HDMI_VEC_INTERFACE_CFG, 0x0ec),
        VC5_DVP_REG(HDMI_VEC_INTERFACE_XBAR, 0x0f0),
 
        VC5_PHY_REG(HDMI_TX_PHY_RESET_CTL, 0x000),
@@ -287,6 +315,7 @@ static const struct vc4_hdmi_register __maybe_unused vc5_hdmi_hdmi0_fields[] = {
        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 vc5_hdmi_hdmi1_fields[] = {
@@ -313,8 +342,19 @@ static const struct vc4_hdmi_register __maybe_unused vc5_hdmi_hdmi1_fields[] = {
        VC4_HDMI_REG(HDMI_VERTB0, 0x0f0),
        VC4_HDMI_REG(HDMI_VERTA1, 0x0f4),
        VC4_HDMI_REG(HDMI_VERTB1, 0x0f8),
+       VC4_HDMI_REG(HDMI_MISC_CONTROL, 0x100),
        VC4_HDMI_REG(HDMI_MAI_CHANNEL_MAP, 0x09c),
        VC4_HDMI_REG(HDMI_MAI_CONFIG, 0x0a0),
+       VC4_HDMI_REG(HDMI_FORMAT_DET_1, 0x134),
+       VC4_HDMI_REG(HDMI_FORMAT_DET_2, 0x138),
+       VC4_HDMI_REG(HDMI_FORMAT_DET_3, 0x13c),
+       VC4_HDMI_REG(HDMI_FORMAT_DET_4, 0x140),
+       VC4_HDMI_REG(HDMI_FORMAT_DET_5, 0x144),
+       VC4_HDMI_REG(HDMI_FORMAT_DET_6, 0x148),
+       VC4_HDMI_REG(HDMI_FORMAT_DET_7, 0x14c),
+       VC4_HDMI_REG(HDMI_FORMAT_DET_8, 0x150),
+       VC4_HDMI_REG(HDMI_FORMAT_DET_9, 0x154),
+       VC4_HDMI_REG(HDMI_FORMAT_DET_10, 0x158),
        VC4_HDMI_REG(HDMI_DEEP_COLOR_CONFIG_1, 0x170),
        VC4_HDMI_REG(HDMI_GCP_CONFIG, 0x178),
        VC4_HDMI_REG(HDMI_GCP_WORD_1, 0x17c),
@@ -322,6 +362,7 @@ static const struct vc4_hdmi_register __maybe_unused vc5_hdmi_hdmi1_fields[] = {
        VC4_HDMI_REG(HDMI_SCRAMBLER_CTL, 0x1c4),
 
        VC5_DVP_REG(HDMI_CLOCK_STOP, 0x0bc),
+       VC5_DVP_REG(HDMI_VEC_INTERFACE_CFG, 0x0ec),
        VC5_DVP_REG(HDMI_VEC_INTERFACE_XBAR, 0x0f0),
 
        VC5_PHY_REG(HDMI_TX_PHY_RESET_CTL, 0x000),
@@ -367,6 +408,7 @@ static const struct vc4_hdmi_register __maybe_unused vc5_hdmi_hdmi1_fields[] = {
        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
@@ -412,6 +454,8 @@ static inline u32 vc4_hdmi_read(struct vc4_hdmi *hdmi,
        const struct vc4_hdmi_variant *variant = hdmi->variant;
        void __iomem *base;
 
+       WARN_ON(pm_runtime_status_suspended(&hdmi->pdev->dev));
+
        if (reg >= variant->num_registers) {
                dev_warn(&hdmi->pdev->dev,
                         "Invalid register ID %u\n", reg);
@@ -438,6 +482,10 @@ static inline void vc4_hdmi_write(struct vc4_hdmi *hdmi,
        const struct vc4_hdmi_variant *variant = hdmi->variant;
        void __iomem *base;
 
+       lockdep_assert_held(&hdmi->hw_lock);
+
+       WARN_ON(pm_runtime_status_suspended(&hdmi->pdev->dev));
+
        if (reg >= variant->num_registers) {
                dev_warn(&hdmi->pdev->dev,
                         "Invalid register ID %u\n", reg);
index 9d88bfb..b0acdff 100644 (file)
@@ -64,22 +64,21 @@ static const struct debugfs_reg32 hvs_regs[] = {
        VC4_REG32(SCALER_OLEDCOEF2),
 };
 
-void vc4_hvs_dump_state(struct drm_device *dev)
+void vc4_hvs_dump_state(struct vc4_hvs *hvs)
 {
-       struct vc4_dev *vc4 = to_vc4_dev(dev);
-       struct drm_printer p = drm_info_printer(&vc4->hvs->pdev->dev);
+       struct drm_printer p = drm_info_printer(&hvs->pdev->dev);
        int i;
 
-       drm_print_regset32(&p, &vc4->hvs->regset);
+       drm_print_regset32(&p, &hvs->regset);
 
        DRM_INFO("HVS ctx:\n");
        for (i = 0; i < 64; i += 4) {
                DRM_INFO("0x%08x (%s): 0x%08x 0x%08x 0x%08x 0x%08x\n",
                         i * 4, i < HVS_BOOTLOADER_DLIST_END ? "B" : "D",
-                        readl((u32 __iomem *)vc4->hvs->dlist + i + 0),
-                        readl((u32 __iomem *)vc4->hvs->dlist + i + 1),
-                        readl((u32 __iomem *)vc4->hvs->dlist + i + 2),
-                        readl((u32 __iomem *)vc4->hvs->dlist + i + 3));
+                        readl((u32 __iomem *)hvs->dlist + i + 0),
+                        readl((u32 __iomem *)hvs->dlist + i + 1),
+                        readl((u32 __iomem *)hvs->dlist + i + 2),
+                        readl((u32 __iomem *)hvs->dlist + i + 3));
        }
 }
 
@@ -95,6 +94,125 @@ static int vc4_hvs_debugfs_underrun(struct seq_file *m, void *data)
        return 0;
 }
 
+static int vc4_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 next_entry_start = 0;
+       unsigned int i, j;
+       u32 dlist_word, dispstat;
+
+       for (i = 0; i < SCALER_CHANNELS_COUNT; i++) {
+               dispstat = VC4_GET_FIELD(HVS_READ(SCALER_DISPSTATX(i)),
+                                        SCALER_DISPSTATX_MODE);
+               if (dispstat == SCALER_DISPSTATX_MODE_DISABLED ||
+                   dispstat == SCALER_DISPSTATX_MODE_EOF) {
+                       drm_printf(&p, "HVS chan %u disabled\n", i);
+                       continue;
+               }
+
+               drm_printf(&p, "HVS chan %u:\n", i);
+
+               for (j = HVS_READ(SCALER_DISPLISTX(i)); j < 256; j++) {
+                       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 vc5_hvs_debugfs_gamma(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 i, chan;
+       u32 dispstat, dispbkgndx;
+
+       for (chan = 0; chan < SCALER_CHANNELS_COUNT; chan++) {
+               u32 x_c, grad;
+               u32 offset = SCALER5_DSPGAMMA_START +
+                       chan * SCALER5_DSPGAMMA_CHAN_OFFSET;
+
+               dispstat = VC4_GET_FIELD(HVS_READ(SCALER_DISPSTATX(chan)),
+                                        SCALER_DISPSTATX_MODE);
+               if (dispstat == SCALER_DISPSTATX_MODE_DISABLED ||
+                   dispstat == SCALER_DISPSTATX_MODE_EOF) {
+                       drm_printf(&p, "HVS channel %u: Channel disabled\n", chan);
+                       continue;
+               }
+
+               dispbkgndx = HVS_READ(SCALER_DISPBKGNDX(chan));
+               if (!(dispbkgndx & SCALER_DISPBKGND_GAMMA)) {
+                       drm_printf(&p, "HVS channel %u: Gamma disabled\n", chan);
+                       continue;
+               }
+
+               drm_printf(&p, "HVS channel %u:\n", chan);
+               drm_printf(&p, "  red:\n");
+               for (i = 0; i < SCALER5_DSPGAMMA_NUM_POINTS; i++, offset += 8) {
+                       x_c = HVS_READ(offset);
+                       grad = HVS_READ(offset + 4);
+                       drm_printf(&p, "  %08x %08x - x %u, c %u, grad %u\n",
+                                  x_c, grad,
+                                  VC4_GET_FIELD(x_c, SCALER5_DSPGAMMA_OFF_X),
+                                  VC4_GET_FIELD(x_c, SCALER5_DSPGAMMA_OFF_C),
+                                  grad);
+               }
+               drm_printf(&p, "  green:\n");
+               for (i = 0; i < SCALER5_DSPGAMMA_NUM_POINTS; i++, offset += 8) {
+                       x_c = HVS_READ(offset);
+                       grad = HVS_READ(offset + 4);
+                       drm_printf(&p, "  %08x %08x - x %u, c %u, grad %u\n",
+                                  x_c, grad,
+                                  VC4_GET_FIELD(x_c, SCALER5_DSPGAMMA_OFF_X),
+                                  VC4_GET_FIELD(x_c, SCALER5_DSPGAMMA_OFF_C),
+                                  grad);
+               }
+               drm_printf(&p, "  blue:\n");
+               for (i = 0; i < SCALER5_DSPGAMMA_NUM_POINTS; i++, offset += 8) {
+                       x_c = HVS_READ(offset);
+                       grad = HVS_READ(offset + 4);
+                       drm_printf(&p, "  %08x %08x - x %u, c %u, grad %u\n",
+                                  x_c, grad,
+                                  VC4_GET_FIELD(x_c, SCALER5_DSPGAMMA_OFF_X),
+                                  VC4_GET_FIELD(x_c, SCALER5_DSPGAMMA_OFF_C),
+                                  grad);
+               }
+
+               /* Alpha only valid on channel 2 */
+               if (chan != 2)
+                       continue;
+
+               drm_printf(&p, "  alpha:\n");
+               for (i = 0; i < SCALER5_DSPGAMMA_NUM_POINTS; i++, offset += 8) {
+                       x_c = HVS_READ(offset);
+                       grad = HVS_READ(offset + 4);
+                       drm_printf(&p, "  %08x %08x - x %u, c %u, grad %u\n",
+                                  x_c, grad,
+                                  VC4_GET_FIELD(x_c, SCALER5_DSPGAMMA_OFF_X),
+                                  VC4_GET_FIELD(x_c, SCALER5_DSPGAMMA_OFF_C),
+                                  grad);
+               }
+       }
+       return 0;
+}
+
 /* The filter kernel is composed of dwords each containing 3 9-bit
  * signed integers packed next to each other.
  */
@@ -157,11 +275,10 @@ static int vc4_hvs_upload_linear_kernel(struct vc4_hvs *hvs,
        return 0;
 }
 
-static void vc4_hvs_lut_load(struct drm_crtc *crtc)
+static void vc4_hvs_lut_load(struct vc4_hvs *hvs,
+                            struct vc4_crtc *vc4_crtc)
 {
-       struct drm_device *dev = crtc->dev;
-       struct vc4_dev *vc4 = to_vc4_dev(dev);
-       struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
+       struct drm_crtc *crtc = &vc4_crtc->base;
        struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc->state);
        u32 i;
 
@@ -181,11 +298,12 @@ static void vc4_hvs_lut_load(struct drm_crtc *crtc)
                HVS_WRITE(SCALER_GAMDATA, vc4_crtc->lut_b[i]);
 }
 
-static void vc4_hvs_update_gamma_lut(struct drm_crtc *crtc)
+static void vc4_hvs_update_gamma_lut(struct vc4_hvs *hvs,
+                                    struct vc4_crtc *vc4_crtc)
 {
-       struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
-       struct drm_color_lut *lut = crtc->state->gamma_lut->data;
-       u32 length = drm_color_lut_size(crtc->state->gamma_lut);
+       struct drm_crtc_state *crtc_state = vc4_crtc->base.state;
+       struct drm_color_lut *lut = crtc_state->gamma_lut->data;
+       u32 length = drm_color_lut_size(crtc_state->gamma_lut);
        u32 i;
 
        for (i = 0; i < length; i++) {
@@ -194,12 +312,85 @@ static void vc4_hvs_update_gamma_lut(struct drm_crtc *crtc)
                vc4_crtc->lut_b[i] = drm_color_lut_extract(lut[i].blue, 8);
        }
 
-       vc4_hvs_lut_load(crtc);
+       vc4_hvs_lut_load(hvs, vc4_crtc);
 }
 
-u8 vc4_hvs_get_fifo_frame_count(struct drm_device *dev, unsigned int fifo)
+static void vc5_hvs_write_gamma_entry(struct vc4_hvs *hvs,
+                                     u32 offset,
+                                     struct vc5_gamma_entry *gamma)
+{
+       HVS_WRITE(offset, gamma->x_c_terms);
+       HVS_WRITE(offset + 4, gamma->grad_term);
+}
+
+static void vc5_hvs_lut_load(struct vc4_hvs *hvs,
+                            struct vc4_crtc *vc4_crtc)
+{
+       struct drm_crtc_state *crtc_state = vc4_crtc->base.state;
+       struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc_state);
+       u32 i;
+       u32 offset = SCALER5_DSPGAMMA_START +
+               vc4_state->assigned_channel * SCALER5_DSPGAMMA_CHAN_OFFSET;
+
+       for (i = 0; i < SCALER5_DSPGAMMA_NUM_POINTS; i++, offset += 8)
+               vc5_hvs_write_gamma_entry(hvs, offset, &vc4_crtc->pwl_r[i]);
+       for (i = 0; i < SCALER5_DSPGAMMA_NUM_POINTS; i++, offset += 8)
+               vc5_hvs_write_gamma_entry(hvs, offset, &vc4_crtc->pwl_g[i]);
+       for (i = 0; i < SCALER5_DSPGAMMA_NUM_POINTS; i++, offset += 8)
+               vc5_hvs_write_gamma_entry(hvs, offset, &vc4_crtc->pwl_b[i]);
+
+       if (vc4_state->assigned_channel == 2) {
+               /* Alpha only valid on channel 2 */
+               for (i = 0; i < SCALER5_DSPGAMMA_NUM_POINTS; i++, offset += 8)
+                       vc5_hvs_write_gamma_entry(hvs, offset, &vc4_crtc->pwl_a[i]);
+       }
+}
+
+static void vc5_hvs_update_gamma_lut(struct vc4_hvs *hvs,
+                                    struct vc4_crtc *vc4_crtc)
+{
+       struct drm_crtc *crtc = &vc4_crtc->base;
+       struct drm_color_lut *lut = crtc->state->gamma_lut->data;
+       unsigned int step, i;
+       u32 start, end;
+
+#define VC5_HVS_UPDATE_GAMMA_ENTRY_FROM_LUT(pwl, chan)                 \
+       start = drm_color_lut_extract(lut[i * step].chan, 12);          \
+       end = drm_color_lut_extract(lut[(i + 1) * step - 1].chan, 12);  \
+                                                                       \
+       /* Negative gradients not permitted by the hardware, so         \
+        * flatten such points out.                                     \
+        */                                                             \
+       if (end < start)                                                \
+               end = start;                                            \
+                                                                       \
+       /* Assume 12bit pipeline.                                       \
+        * X evenly spread over full range (12 bit).                    \
+        * C as U12.4 format.                                           \
+        * Gradient as U4.8 format.                                     \
+       */                                                              \
+       vc4_crtc->pwl[i] =                                              \
+               VC5_HVS_SET_GAMMA_ENTRY(i << 8, start << 4,             \
+                               ((end - start) << 4) / (step - 1))
+
+       /* HVS5 has a 16 point piecewise linear function for each colour
+        * channel (including alpha on channel 2) on each display channel.
+        *
+        * Currently take a crude subsample of the gamma LUT, but this could
+        * be improved to implement curve fitting.
+        */
+       step = crtc->gamma_size / SCALER5_DSPGAMMA_NUM_POINTS;
+       for (i = 0; i < SCALER5_DSPGAMMA_NUM_POINTS; i++) {
+               VC5_HVS_UPDATE_GAMMA_ENTRY_FROM_LUT(pwl_r, red);
+               VC5_HVS_UPDATE_GAMMA_ENTRY_FROM_LUT(pwl_g, green);
+               VC5_HVS_UPDATE_GAMMA_ENTRY_FROM_LUT(pwl_b, blue);
+       }
+
+       vc5_hvs_lut_load(hvs, vc4_crtc);
+}
+
+u8 vc4_hvs_get_fifo_frame_count(struct vc4_hvs *hvs, unsigned int fifo)
 {
-       struct vc4_dev *vc4 = to_vc4_dev(dev);
        u8 field = 0;
 
        switch (fifo) {
@@ -220,13 +411,13 @@ u8 vc4_hvs_get_fifo_frame_count(struct drm_device *dev, unsigned int fifo)
        return field;
 }
 
-int vc4_hvs_get_fifo_from_output(struct drm_device *dev, unsigned int output)
+int vc4_hvs_get_fifo_from_output(struct vc4_hvs *hvs, unsigned int output)
 {
-       struct vc4_dev *vc4 = to_vc4_dev(dev);
+       struct vc4_dev *vc4 = hvs->vc4;
        u32 reg;
        int ret;
 
-       if (!vc4->hvs->hvs5)
+       if (!vc4->is_vc5)
                return output;
 
        switch (output) {
@@ -273,9 +464,11 @@ int vc4_hvs_get_fifo_from_output(struct drm_device *dev, unsigned int output)
        }
 }
 
-static int vc4_hvs_init_channel(struct vc4_dev *vc4, struct drm_crtc *crtc,
+static int vc4_hvs_init_channel(struct vc4_hvs *hvs, struct drm_crtc *crtc,
                                struct drm_display_mode *mode, bool oneshot)
 {
+       struct vc4_dev *vc4 = hvs->vc4;
+       struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
        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;
@@ -293,7 +486,7 @@ static int vc4_hvs_init_channel(struct vc4_dev *vc4, struct drm_crtc *crtc,
         */
        dispctrl = SCALER_DISPCTRLX_ENABLE;
 
-       if (!vc4->hvs->hvs5)
+       if (!vc4->is_vc5)
                dispctrl |= VC4_SET_FIELD(mode->hdisplay,
                                          SCALER_DISPCTRLX_WIDTH) |
                            VC4_SET_FIELD(mode->vdisplay,
@@ -312,23 +505,27 @@ static int vc4_hvs_init_channel(struct vc4_dev *vc4, struct drm_crtc *crtc,
        dispbkgndx &= ~SCALER_DISPBKGND_GAMMA;
        dispbkgndx &= ~SCALER_DISPBKGND_INTERLACE;
 
+       if (crtc->state->gamma_lut)
+               /* Enable gamma on if required */
+               dispbkgndx |= SCALER_DISPBKGND_GAMMA;
+
        HVS_WRITE(SCALER_DISPBKGNDX(chan), dispbkgndx |
                  SCALER_DISPBKGND_AUTOHS |
-                 ((!vc4->hvs->hvs5) ? SCALER_DISPBKGND_GAMMA : 0) |
                  (interlace ? SCALER_DISPBKGND_INTERLACE : 0));
 
        /* Reload the LUT, since the SRAMs would have been disabled if
         * all CRTCs had SCALER_DISPBKGND_GAMMA unset at once.
         */
-       vc4_hvs_lut_load(crtc);
+       if (!vc4->is_vc5)
+               vc4_hvs_lut_load(hvs, vc4_crtc);
+       else
+               vc5_hvs_lut_load(hvs, vc4_crtc);
 
        return 0;
 }
 
-void vc4_hvs_stop_channel(struct drm_device *dev, unsigned int chan)
+void vc4_hvs_stop_channel(struct vc4_hvs *hvs, unsigned int chan)
 {
-       struct vc4_dev *vc4 = to_vc4_dev(dev);
-
        if (HVS_READ(SCALER_DISPCTRLX(chan)) & SCALER_DISPCTRLX_ENABLE)
                return;
 
@@ -349,6 +546,46 @@ void vc4_hvs_stop_channel(struct drm_device *dev, unsigned int chan)
                     SCALER_DISPSTATX_EMPTY);
 }
 
+static int vc4_hvs_gamma_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 drm_connector_state *conn_state;
+       struct drm_connector *connector;
+       struct drm_device *dev = crtc->dev;
+       struct vc4_dev *vc4 = to_vc4_dev(dev);
+
+       if (!vc4->is_vc5)
+               return 0;
+
+       if (!crtc_state->color_mgmt_changed)
+               return 0;
+
+       if (crtc_state->gamma_lut) {
+               unsigned int len = drm_color_lut_size(crtc_state->gamma_lut);
+
+               if (len != crtc->gamma_size) {
+                       DRM_DEBUG_KMS("Invalid LUT size; got %u, expected %u\n",
+                                     len, crtc->gamma_size);
+                       return -EINVAL;
+               }
+       }
+
+       connector = vc4_get_crtc_connector(crtc, crtc_state);
+       if (!connector)
+               return -EINVAL;
+
+       if (!(connector->connector_type == DRM_MODE_CONNECTOR_HDMIA))
+               return 0;
+
+       conn_state = drm_atomic_get_connector_state(state, connector);
+       if (!conn_state)
+               return -EINVAL;
+
+       crtc_state->mode_changed = true;
+       return 0;
+}
+
 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);
@@ -379,13 +616,23 @@ int vc4_hvs_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state)
        if (ret)
                return ret;
 
-       return 0;
+       return vc4_hvs_gamma_check(crtc, state);
 }
 
-static void vc4_hvs_update_dlist(struct drm_crtc *crtc)
+static void vc4_hvs_install_dlist(struct drm_crtc *crtc)
 {
        struct drm_device *dev = crtc->dev;
        struct vc4_dev *vc4 = to_vc4_dev(dev);
+       struct vc4_hvs *hvs = vc4->hvs;
+       struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc->state);
+
+       HVS_WRITE(SCALER_DISPLISTX(vc4_state->assigned_channel),
+                 vc4_state->mm.start);
+}
+
+static void vc4_hvs_update_dlist(struct drm_crtc *crtc)
+{
+       struct drm_device *dev = crtc->dev;
        struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
        struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc->state);
        unsigned long flags;
@@ -402,13 +649,7 @@ static void vc4_hvs_update_dlist(struct drm_crtc *crtc)
                        crtc->state->event = NULL;
                }
 
-               HVS_WRITE(SCALER_DISPLISTX(vc4_state->assigned_channel),
-                         vc4_state->mm.start);
-
                spin_unlock_irqrestore(&dev->event_lock, flags);
-       } else {
-               HVS_WRITE(SCALER_DISPLISTX(vc4_state->assigned_channel),
-                         vc4_state->mm.start);
        }
 
        spin_lock_irqsave(&vc4_crtc->irq_lock, flags);
@@ -437,19 +678,21 @@ void vc4_hvs_atomic_enable(struct drm_crtc *crtc,
        struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
        bool oneshot = vc4_crtc->feeds_txp;
 
+       vc4_hvs_install_dlist(crtc);
        vc4_hvs_update_dlist(crtc);
-       vc4_hvs_init_channel(vc4, crtc, mode, oneshot);
+       vc4_hvs_init_channel(vc4->hvs, crtc, mode, oneshot);
 }
 
 void vc4_hvs_atomic_disable(struct drm_crtc *crtc,
                            struct drm_atomic_state *state)
 {
        struct drm_device *dev = crtc->dev;
+       struct vc4_dev *vc4 = to_vc4_dev(dev);
        struct drm_crtc_state *old_state = drm_atomic_get_old_crtc_state(state, crtc);
        struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(old_state);
        unsigned int chan = vc4_state->assigned_channel;
 
-       vc4_hvs_stop_channel(dev, chan);
+       vc4_hvs_stop_channel(vc4->hvs, chan);
 }
 
 void vc4_hvs_atomic_flush(struct drm_crtc *crtc,
@@ -459,37 +702,56 @@ void vc4_hvs_atomic_flush(struct drm_crtc *crtc,
                                                                         crtc);
        struct drm_device *dev = crtc->dev;
        struct vc4_dev *vc4 = to_vc4_dev(dev);
+       struct vc4_hvs *hvs = vc4->hvs;
+       struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
        struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc->state);
+       unsigned int channel = vc4_state->assigned_channel;
        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;
+       unsigned int zpos = 0;
+       bool found = false;
+
+       if (vc4_state->assigned_channel == VC4_HVS_CHANNEL_DISABLED)
+               return;
 
        if (debug_dump_regs) {
                DRM_INFO("CRTC %d HVS before:\n", drm_crtc_index(crtc));
-               vc4_hvs_dump_state(dev);
+               vc4_hvs_dump_state(hvs);
        }
 
        /* Copy all the active planes' dlist contents to the hardware dlist. */
-       drm_atomic_crtc_for_each_plane(plane, crtc) {
-               /* Is this the first active plane? */
-               if (dlist_next == dlist_start) {
-                       /* We need to enable background fill when a plane
-                        * could be alpha blending from the background, i.e.
-                        * where no other plane is underneath. It suffices to
-                        * consider the first active plane here since we set
-                        * needs_bg_fill such that either the first plane
-                        * already needs it or all planes on top blend from
-                        * the first or a lower plane.
-                        */
-                       vc4_plane_state = to_vc4_plane_state(plane->state);
-                       enable_bg_fill = vc4_plane_state->needs_bg_fill;
+       do {
+               found = false;
+
+               drm_atomic_crtc_for_each_plane(plane, crtc) {
+                       if (plane->state->normalized_zpos != zpos)
+                               continue;
+
+                       /* Is this the first active plane? */
+                       if (dlist_next == dlist_start) {
+                               /* We need to enable background fill when a plane
+                                * could be alpha blending from the background, i.e.
+                                * where no other plane is underneath. It suffices to
+                                * consider the first active plane here since we set
+                                * needs_bg_fill such that either the first plane
+                                * already needs it or all planes on top blend from
+                                * the first or a lower plane.
+                                */
+                               vc4_plane_state = to_vc4_plane_state(plane->state);
+                               enable_bg_fill = vc4_plane_state->needs_bg_fill;
+                       }
+
+                       dlist_next += vc4_plane_write_dlist(plane, dlist_next);
+
+                       found = true;
                }
 
-               dlist_next += vc4_plane_write_dlist(plane, dlist_next);
-       }
+               zpos++;
+       } while (found);
 
        writel(SCALER_CTL0_END, dlist_next);
        dlist_next++;
@@ -500,8 +762,8 @@ void vc4_hvs_atomic_flush(struct drm_crtc *crtc,
                /* This sets a black background color fill, as is the case
                 * with other DRM drivers.
                 */
-               HVS_WRITE(SCALER_DISPBKGNDX(vc4_state->assigned_channel),
-                         HVS_READ(SCALER_DISPBKGNDX(vc4_state->assigned_channel)) |
+               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
@@ -511,34 +773,46 @@ void vc4_hvs_atomic_flush(struct drm_crtc *crtc,
         * If the CRTC is being disabled, there's no point in updating this
         * information.
         */
-       if (crtc->state->active && old_state->active)
+       if (crtc->state->active && old_state->active) {
+               vc4_hvs_install_dlist(crtc);
                vc4_hvs_update_dlist(crtc);
+       }
 
        if (crtc->state->color_mgmt_changed) {
-               u32 dispbkgndx = HVS_READ(SCALER_DISPBKGNDX(vc4_state->assigned_channel));
+               u32 dispbkgndx = HVS_READ(SCALER_DISPBKGNDX(channel));
 
                if (crtc->state->gamma_lut) {
-                       vc4_hvs_update_gamma_lut(crtc);
-                       dispbkgndx |= SCALER_DISPBKGND_GAMMA;
+                       if (!vc4->is_vc5) {
+                               vc4_hvs_update_gamma_lut(hvs, vc4_crtc);
+                               dispbkgndx |= SCALER_DISPBKGND_GAMMA;
+                       } else {
+                               vc5_hvs_update_gamma_lut(hvs, vc4_crtc);
+                       }
                } else {
                        /* Unsetting DISPBKGND_GAMMA skips the gamma lut step
                         * in hardware, which is the same as a linear lut that
                         * DRM expects us to use in absence of a user lut.
+                        *
+                        * Do NOT change state dynamically for hvs5 as it
+                        * inserts a delay in the pipeline that will cause
+                        * stalls if enabled/disabled whilst running. The other
+                        * should already be disabling/enabling the pipeline
+                        * when gamma changes.
                         */
-                       dispbkgndx &= ~SCALER_DISPBKGND_GAMMA;
+                       if (!vc4->is_vc5)
+                               dispbkgndx &= ~SCALER_DISPBKGND_GAMMA;
                }
-               HVS_WRITE(SCALER_DISPBKGNDX(vc4_state->assigned_channel), dispbkgndx);
+               HVS_WRITE(SCALER_DISPBKGNDX(channel), dispbkgndx);
        }
 
        if (debug_dump_regs) {
                DRM_INFO("CRTC %d HVS after:\n", drm_crtc_index(crtc));
-               vc4_hvs_dump_state(dev);
+               vc4_hvs_dump_state(hvs);
        }
 }
 
-void vc4_hvs_mask_underrun(struct drm_device *dev, int channel)
+void vc4_hvs_mask_underrun(struct vc4_hvs *hvs, int channel)
 {
-       struct vc4_dev *vc4 = to_vc4_dev(dev);
        u32 dispctrl = HVS_READ(SCALER_DISPCTRL);
 
        dispctrl &= ~SCALER_DISPCTRL_DSPEISLUR(channel);
@@ -546,9 +820,8 @@ void vc4_hvs_mask_underrun(struct drm_device *dev, int channel)
        HVS_WRITE(SCALER_DISPCTRL, dispctrl);
 }
 
-void vc4_hvs_unmask_underrun(struct drm_device *dev, int channel)
+void vc4_hvs_unmask_underrun(struct vc4_hvs *hvs, int channel)
 {
-       struct vc4_dev *vc4 = to_vc4_dev(dev);
        u32 dispctrl = HVS_READ(SCALER_DISPCTRL);
 
        dispctrl |= SCALER_DISPCTRL_DSPEISLUR(channel);
@@ -570,6 +843,7 @@ static irqreturn_t vc4_hvs_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;
        irqreturn_t irqret = IRQ_NONE;
        int channel;
        u32 control;
@@ -582,7 +856,7 @@ static irqreturn_t vc4_hvs_irq_handler(int irq, void *data)
                /* Interrupt masking is not always honored, so check it here. */
                if (status & SCALER_DISPSTAT_EUFLOW(channel) &&
                    control & SCALER_DISPCTRL_DSPEISLUR(channel)) {
-                       vc4_hvs_mask_underrun(dev, channel);
+                       vc4_hvs_mask_underrun(hvs, channel);
                        vc4_hvs_report_underrun(dev);
 
                        irqret = IRQ_HANDLED;
@@ -611,11 +885,9 @@ static int vc4_hvs_bind(struct device *dev, struct device *master, void *data)
        if (!hvs)
                return -ENOMEM;
 
+       hvs->vc4 = vc4;
        hvs->pdev = pdev;
 
-       if (of_device_is_compatible(pdev->dev.of_node, "brcm,bcm2711-hvs"))
-               hvs->hvs5 = true;
-
        hvs->regs = vc4_ioremap_regs(pdev, 0);
        if (IS_ERR(hvs->regs))
                return PTR_ERR(hvs->regs);
@@ -624,13 +896,24 @@ static int vc4_hvs_bind(struct device *dev, struct device *master, void *data)
        hvs->regset.regs = hvs_regs;
        hvs->regset.nregs = ARRAY_SIZE(hvs_regs);
 
-       if (hvs->hvs5) {
+       if (vc4->is_vc5) {
+               unsigned long min_rate;
+               unsigned long max_rate;
+
                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);
                }
 
+               max_rate = clk_get_max_rate(hvs->core_clk);
+               if (max_rate >= 550000000)
+                       hvs->vc5_hdmi_enable_scrambling = true;
+
+               min_rate = clk_get_min_rate(hvs->core_clk);
+               if (min_rate >= 600000000)
+                       hvs->vc5_hdmi_enable_4096by2160 = true;
+
                ret = clk_prepare_enable(hvs->core_clk);
                if (ret) {
                        dev_err(&pdev->dev, "Couldn't enable the core clock\n");
@@ -638,7 +921,7 @@ static int vc4_hvs_bind(struct device *dev, struct device *master, void *data)
                }
        }
 
-       if (!hvs->hvs5)
+       if (!vc4->is_vc5)
                hvs->dlist = hvs->regs + SCALER_DLIST_START;
        else
                hvs->dlist = hvs->regs + SCALER5_DLIST_START;
@@ -659,7 +942,7 @@ static int vc4_hvs_bind(struct device *dev, struct device *master, void *data)
         * between planes when they don't overlap on the screen, but
         * for now we just allocate globally.
         */
-       if (!hvs->hvs5)
+       if (!vc4->is_vc5)
                /* 48k words of 2x12-bit pixels */
                drm_mm_init(&hvs->lbm_mm, 0, 48 * 1024);
        else
@@ -728,6 +1011,11 @@ static int vc4_hvs_bind(struct device *dev, struct device *master, void *data)
        vc4_debugfs_add_regset32(drm, "hvs_regs", &hvs->regset);
        vc4_debugfs_add_file(drm, "hvs_underrun", vc4_hvs_debugfs_underrun,
                             NULL);
+       vc4_debugfs_add_file(drm, "hvs_dlists", vc4_hvs_debugfs_dlist,
+                            NULL);
+       if (vc4->is_vc5)
+               vc4_debugfs_add_file(drm, "hvs_gamma", vc5_hvs_debugfs_gamma,
+                                    NULL);
 
        return 0;
 }
index 20fa8e3..6001a7a 100644 (file)
@@ -260,6 +260,9 @@ vc4_irq_enable(struct drm_device *dev)
 {
        struct vc4_dev *vc4 = to_vc4_dev(dev);
 
+       if (WARN_ON_ONCE(vc4->is_vc5))
+               return;
+
        if (!vc4->v3d)
                return;
 
@@ -274,6 +277,9 @@ vc4_irq_disable(struct drm_device *dev)
 {
        struct vc4_dev *vc4 = to_vc4_dev(dev);
 
+       if (WARN_ON_ONCE(vc4->is_vc5))
+               return;
+
        if (!vc4->v3d)
                return;
 
@@ -291,8 +297,12 @@ vc4_irq_disable(struct drm_device *dev)
 
 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->is_vc5))
+               return -ENODEV;
+
        if (irq == IRQ_NOTCONNECTED)
                return -ENOTCONN;
 
@@ -311,6 +321,9 @@ void vc4_irq_uninstall(struct drm_device *dev)
 {
        struct vc4_dev *vc4 = to_vc4_dev(dev);
 
+       if (WARN_ON_ONCE(vc4->is_vc5))
+               return;
+
        vc4_irq_disable(dev);
        free_irq(vc4->irq, dev);
 }
@@ -321,6 +334,9 @@ void vc4_irq_reset(struct drm_device *dev)
        struct vc4_dev *vc4 = to_vc4_dev(dev);
        unsigned long irqflags;
 
+       if (WARN_ON_ONCE(vc4->is_vc5))
+               return;
+
        /* Acknowledge any stale IRQs. */
        V3D_WRITE(V3D_INTCTL, V3D_DRIVER_IRQS);
 
index 6030d4a..b4541d1 100644 (file)
@@ -39,9 +39,11 @@ static struct vc4_ctm_state *to_vc4_ctm_state(struct drm_private_state *priv)
 
 struct vc4_hvs_state {
        struct drm_private_state base;
+       unsigned long core_clock_rate;
 
        struct {
                unsigned in_use: 1;
+               unsigned long fifo_load;
                struct drm_crtc_commit *pending_commit;
        } fifo_state[HVS_NUM_CHANNELS];
 };
@@ -155,9 +157,13 @@ static u16 vc4_ctm_s31_32_to_s0_9(u64 in)
 static void
 vc4_ctm_commit(struct vc4_dev *vc4, struct drm_atomic_state *state)
 {
+       struct vc4_hvs *hvs = vc4->hvs;
        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;
+
        if (ctm_state->fifo) {
                HVS_WRITE(SCALER_OLEDCOEF2,
                          VC4_SET_FIELD(vc4_ctm_s31_32_to_s0_9(ctm->matrix[0]),
@@ -228,6 +234,7 @@ vc4_hvs_get_global_state(struct drm_atomic_state *state)
 static void vc4_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;
@@ -268,6 +275,7 @@ static void vc4_hvs_pv_muxing_commit(struct vc4_dev *vc4,
 static void vc5_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 char mux;
@@ -277,13 +285,17 @@ static void vc5_hvs_pv_muxing_commit(struct vc4_dev *vc4,
        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);
+               unsigned int channel = vc4_state->assigned_channel;
 
                if (!vc4_state->update_muxing)
                        continue;
 
                switch (vc4_crtc->data->hvs_output) {
                case 2:
-                       mux = (vc4_state->assigned_channel == 2) ? 0 : 1;
+                       WARN_ON(VC4_GET_FIELD(HVS_READ(SCALER_DISPCTRL),
+                                             SCALER_DISPCTRL_DSP3_MUX) == channel);
+
+                       mux = (channel == 2) ? 0 : 1;
                        reg = HVS_READ(SCALER_DISPECTRL);
                        HVS_WRITE(SCALER_DISPECTRL,
                                  (reg & ~SCALER_DISPECTRL_DSP2_MUX_MASK) |
@@ -291,10 +303,10 @@ static void vc5_hvs_pv_muxing_commit(struct vc4_dev *vc4,
                        break;
 
                case 3:
-                       if (vc4_state->assigned_channel == VC4_HVS_CHANNEL_DISABLED)
+                       if (channel == VC4_HVS_CHANNEL_DISABLED)
                                mux = 3;
                        else
-                               mux = vc4_state->assigned_channel;
+                               mux = channel;
 
                        reg = HVS_READ(SCALER_DISPCTRL);
                        HVS_WRITE(SCALER_DISPCTRL,
@@ -303,10 +315,10 @@ static void vc5_hvs_pv_muxing_commit(struct vc4_dev *vc4,
                        break;
 
                case 4:
-                       if (vc4_state->assigned_channel == VC4_HVS_CHANNEL_DISABLED)
+                       if (channel == VC4_HVS_CHANNEL_DISABLED)
                                mux = 3;
                        else
-                               mux = vc4_state->assigned_channel;
+                               mux = channel;
 
                        reg = HVS_READ(SCALER_DISPEOLN);
                        HVS_WRITE(SCALER_DISPEOLN,
@@ -316,10 +328,10 @@ static void vc5_hvs_pv_muxing_commit(struct vc4_dev *vc4,
                        break;
 
                case 5:
-                       if (vc4_state->assigned_channel == VC4_HVS_CHANNEL_DISABLED)
+                       if (channel == VC4_HVS_CHANNEL_DISABLED)
                                mux = 3;
                        else
-                               mux = vc4_state->assigned_channel;
+                               mux = channel;
 
                        reg = HVS_READ(SCALER_DISPDITHER);
                        HVS_WRITE(SCALER_DISPDITHER,
@@ -339,25 +351,31 @@ static void vc4_atomic_commit_tail(struct drm_atomic_state *state)
        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 long max_clock_rate = hvs ? clk_get_max_rate(hvs->core_clk) : 0;
        unsigned int channel;
        int i;
 
+       old_hvs_state = vc4_hvs_get_old_global_state(state);
+       if (WARN_ON(IS_ERR(old_hvs_state)))
+               return;
+
+       new_hvs_state = vc4_hvs_get_new_global_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 (!new_crtc_state->commit)
+               if (!new_crtc_state->commit || vc4->firmware_kms)
                        continue;
 
                vc4_crtc_state = to_vc4_crtc_state(new_crtc_state);
-               vc4_hvs_mask_underrun(dev, vc4_crtc_state->assigned_channel);
+               vc4_hvs_mask_underrun(hvs, vc4_crtc_state->assigned_channel);
        }
 
-       old_hvs_state = vc4_hvs_get_old_global_state(state);
-       if (IS_ERR(old_hvs_state))
-               return;
-
        for (channel = 0; channel < HVS_NUM_CHANNELS; channel++) {
                struct drm_crtc_commit *commit;
                int ret;
@@ -377,17 +395,25 @@ static void vc4_atomic_commit_tail(struct drm_atomic_state *state)
                old_hvs_state->fifo_state[channel].pending_commit = NULL;
        }
 
-       if (vc4->hvs->hvs5)
-               clk_set_min_rate(hvs->core_clk, 500000000);
+       if (vc4->is_vc5 && !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,
+                                                 500000000, max_clock_rate);
+
+               WARN_ON(clk_set_min_rate(hvs->core_clk, core_rate));
+       }
 
        drm_atomic_helper_commit_modeset_disables(dev, state);
 
        vc4_ctm_commit(vc4, state);
 
-       if (vc4->hvs->hvs5)
-               vc5_hvs_pv_muxing_commit(vc4, state);
-       else
-               vc4_hvs_pv_muxing_commit(vc4, state);
+       if (!vc4->firmware_kms) {
+               if (vc4->is_vc5)
+                       vc5_hvs_pv_muxing_commit(vc4, state);
+               else
+                       vc4_hvs_pv_muxing_commit(vc4, state);
+       }
 
        drm_atomic_helper_commit_planes(dev, state, 0);
 
@@ -401,17 +427,37 @@ static void vc4_atomic_commit_tail(struct drm_atomic_state *state)
 
        drm_atomic_helper_cleanup_planes(dev, state);
 
-       if (vc4->hvs->hvs5)
-               clk_set_min_rate(hvs->core_clk, 0);
+       if (vc4->is_vc5 && !vc4->firmware_kms) {
+               unsigned long core_rate = min_t(unsigned long,
+                                              max_clock_rate,
+                                              new_hvs_state->core_clock_rate);
+
+               drm_dbg(dev, "Running the core clock at %lu Hz\n", core_rate);
+
+               WARN_ON(clk_set_min_rate(hvs->core_clk, core_rate));
+
+               drm_dbg(dev, "Core clock actual rate: %lu Hz\n",
+                       clk_get_rate(hvs->core_clk));
+       }
 }
 
 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);
@@ -439,8 +485,12 @@ static struct drm_framebuffer *vc4_fb_create(struct drm_device *dev,
                                             struct drm_file *file_priv,
                                             const struct drm_mode_fb_cmd2 *mode_cmd)
 {
+       struct vc4_dev *vc4 = to_vc4_dev(dev);
        struct drm_mode_fb_cmd2 mode_cmd_local;
 
+       if (WARN_ON_ONCE(vc4->is_vc5))
+               return ERR_PTR(-ENODEV);
+
        /* If the user didn't specify a modifier, use the
         * vc4_set_tiling_ioctl() state for the BO.
         */
@@ -554,9 +604,6 @@ static int vc4_load_tracker_atomic_check(struct drm_atomic_state *state)
        struct drm_plane *plane;
        int i;
 
-       if (!vc4->load_tracker_available)
-               return 0;
-
        priv_state = drm_atomic_get_private_obj_state(state,
                                                      &vc4->load_tracker);
        if (IS_ERR(priv_state))
@@ -631,9 +678,6 @@ static void vc4_load_tracker_obj_fini(struct drm_device *dev, void *unused)
 {
        struct vc4_dev *vc4 = to_vc4_dev(dev);
 
-       if (!vc4->load_tracker_available)
-               return;
-
        drm_atomic_private_obj_fini(&vc4->load_tracker);
 }
 
@@ -641,9 +685,6 @@ static int vc4_load_tracker_obj_init(struct vc4_dev *vc4)
 {
        struct vc4_load_tracker_state *load_state;
 
-       if (!vc4->load_tracker_available)
-               return 0;
-
        load_state = kzalloc(sizeof(*load_state), GFP_KERNEL);
        if (!load_state)
                return -ENOMEM;
@@ -668,11 +709,13 @@ vc4_hvs_channels_duplicate_state(struct drm_private_obj *obj)
 
        __drm_atomic_helper_private_obj_duplicate_state(obj, &state->base);
 
-
        for (i = 0; i < HVS_NUM_CHANNELS; i++) {
                state->fifo_state[i].in_use = old_state->fifo_state[i].in_use;
+               state->fifo_state[i].fifo_load = old_state->fifo_state[i].fifo_load;
        }
 
+       state->core_clock_rate = old_state->core_clock_rate;
+
        return &state->base;
 }
 
@@ -752,6 +795,7 @@ static int vc4_hvs_channels_obj_init(struct vc4_dev *vc4)
 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_state *old_crtc_state, *new_crtc_state;
        struct drm_crtc *crtc;
@@ -775,9 +819,21 @@ static int vc4_pv_muxing_atomic_check(struct drm_device *dev,
                unsigned int matching_channels;
                unsigned int channel;
 
+               if (vc4->firmware_kms)
+                       continue;
+
+               drm_dbg(dev, "%s: Trying to find a channel.\n", crtc->name);
+
                /* Nothing to do here, let's skip it */
-               if (old_crtc_state->enable == new_crtc_state->enable)
+               if (old_crtc_state->enable == new_crtc_state->enable) {
+                       if (new_crtc_state->enable)
+                               drm_dbg(dev, "%s: Already enabled, reusing channel %d.\n",
+                                       crtc->name, new_vc4_crtc_state->assigned_channel);
+                       else
+                               drm_dbg(dev, "%s: Disabled, ignoring.\n", crtc->name);
+
                        continue;
+               }
 
                /* Muxing will need to be modified, mark it as such */
                new_vc4_crtc_state->update_muxing = true;
@@ -785,6 +841,10 @@ static int vc4_pv_muxing_atomic_check(struct drm_device *dev,
                /* If we're disabling our CRTC, we put back our channel */
                if (!new_crtc_state->enable) {
                        channel = old_vc4_crtc_state->assigned_channel;
+
+                       drm_dbg(dev, "%s: Disabling, Freeing channel %d\n",
+                               crtc->name, channel);
+
                        hvs_new_state->fifo_state[channel].in_use = false;
                        new_vc4_crtc_state->assigned_channel = VC4_HVS_CHANNEL_DISABLED;
                        continue;
@@ -819,6 +879,8 @@ static int vc4_pv_muxing_atomic_check(struct drm_device *dev,
                        return -EINVAL;
 
                channel = ffs(matching_channels) - 1;
+
+               drm_dbg(dev, "Assigned HVS channel %d to CRTC %s\n", channel, crtc->name);
                new_vc4_crtc_state->assigned_channel = channel;
                unassigned_channels &= ~BIT(channel);
                hvs_new_state->fifo_state[channel].in_use = true;
@@ -828,6 +890,78 @@ static int vc4_pv_muxing_atomic_check(struct drm_device *dev,
 }
 
 static int
+vc4_core_clock_atomic_check(struct drm_atomic_state *state)
+{
+       struct vc4_dev *vc4 = to_vc4_dev(state->dev);
+       struct drm_private_state *priv_state;
+       struct vc4_hvs_state *hvs_new_state;
+       struct vc4_load_tracker_state *load_state;
+       struct drm_crtc_state *old_crtc_state, *new_crtc_state;
+       struct drm_crtc *crtc;
+       unsigned int num_outputs;
+       unsigned long pixel_rate;
+       unsigned long cob_rate;
+       unsigned int i;
+
+       priv_state = drm_atomic_get_private_obj_state(state,
+                                                     &vc4->load_tracker);
+       if (IS_ERR(priv_state))
+               return PTR_ERR(priv_state);
+
+       load_state = to_vc4_load_tracker_state(priv_state);
+
+       hvs_new_state = vc4_hvs_get_global_state(state);
+       if (IS_ERR(hvs_new_state))
+               return PTR_ERR(hvs_new_state);
+
+       for_each_oldnew_crtc_in_state(state, crtc,
+                                     old_crtc_state,
+                                     new_crtc_state,
+                                     i) {
+               if (old_crtc_state->active) {
+                       struct vc4_crtc_state *old_vc4_state =
+                               to_vc4_crtc_state(old_crtc_state);
+                       unsigned int channel = old_vc4_state->assigned_channel;
+
+                       hvs_new_state->fifo_state[channel].fifo_load = 0;
+               }
+
+               if (new_crtc_state->active) {
+                       struct vc4_crtc_state *new_vc4_state =
+                               to_vc4_crtc_state(new_crtc_state);
+                       unsigned int channel = new_vc4_state->assigned_channel;
+
+                       hvs_new_state->fifo_state[channel].fifo_load =
+                               new_vc4_state->hvs_load;
+               }
+       }
+
+       cob_rate = 0;
+       num_outputs = 0;
+       for (i = 0; i < HVS_NUM_CHANNELS; i++) {
+               if (!hvs_new_state->fifo_state[i].in_use)
+                       continue;
+
+               num_outputs++;
+               cob_rate = max_t(unsigned long,
+                                hvs_new_state->fifo_state[i].fifo_load,
+                                cob_rate);
+       }
+
+       pixel_rate = load_state->hvs_load;
+       if (num_outputs > 1) {
+               pixel_rate = (pixel_rate * 40) / 100;
+       } else {
+               pixel_rate = (pixel_rate * 60) / 100;
+       }
+
+       hvs_new_state->core_clock_rate = max(cob_rate, pixel_rate);
+
+       return 0;
+}
+
+
+static int
 vc4_atomic_check(struct drm_device *dev, struct drm_atomic_state *state)
 {
        int ret;
@@ -844,7 +978,11 @@ vc4_atomic_check(struct drm_device *dev, struct drm_atomic_state *state)
        if (ret)
                return ret;
 
-       return vc4_load_tracker_atomic_check(state);
+       ret = vc4_load_tracker_atomic_check(state);
+       if (ret)
+               return ret;
+
+       return vc4_core_clock_atomic_check(state);
 }
 
 static struct drm_mode_config_helper_funcs vc4_mode_config_helpers = {
@@ -858,16 +996,23 @@ static const struct drm_mode_config_funcs vc4_mode_funcs = {
        .fb_create = vc4_fb_create,
 };
 
+static const struct drm_mode_config_funcs vc5_mode_funcs = {
+       .atomic_check = vc4_atomic_check,
+       .atomic_commit = drm_atomic_helper_commit,
+       .fb_create = drm_gem_fb_create,
+};
+
 int vc4_kms_load(struct drm_device *dev)
 {
        struct vc4_dev *vc4 = to_vc4_dev(dev);
-       bool is_vc5 = of_device_is_compatible(dev->dev->of_node,
-                                             "brcm,bcm2711-vc5");
        int ret;
 
-       if (!is_vc5) {
-               vc4->load_tracker_available = true;
-
+       /*
+        * The limits enforced by the load tracker aren't relevant for
+        * the BCM2711, but the load tracker computations are used for
+        * the core clock rate calculation.
+        */
+       if (!vc4->is_vc5) {
                /* Start with the load tracker enabled. Can be
                 * disabled through the debugfs load_tracker file.
                 */
@@ -883,7 +1028,7 @@ int vc4_kms_load(struct drm_device *dev)
                return ret;
        }
 
-       if (is_vc5) {
+       if (vc4->is_vc5) {
                dev->mode_config.max_width = 7680;
                dev->mode_config.max_height = 7680;
        } else {
@@ -891,10 +1036,11 @@ int vc4_kms_load(struct drm_device *dev)
                dev->mode_config.max_height = 2048;
        }
 
-       dev->mode_config.funcs = &vc4_mode_funcs;
+       dev->mode_config.funcs = vc4->is_vc5 ? &vc5_mode_funcs : &vc4_mode_funcs;
        dev->mode_config.helper_private = &vc4_mode_config_helpers;
        dev->mode_config.preferred_depth = 24;
        dev->mode_config.async_page_flip = true;
+       dev->mode_config.normalize_zpos = true;
 
        ret = vc4_ctm_obj_init(vc4);
        if (ret)
index 18abc06..c7f5adb 100644 (file)
 
 void vc4_perfmon_get(struct vc4_perfmon *perfmon)
 {
+       struct vc4_dev *vc4 = perfmon->dev;
+
+       if (WARN_ON_ONCE(vc4->is_vc5))
+               return;
+
        if (perfmon)
                refcount_inc(&perfmon->refcnt);
 }
 
 void vc4_perfmon_put(struct vc4_perfmon *perfmon)
 {
-       if (perfmon && refcount_dec_and_test(&perfmon->refcnt))
+       struct vc4_dev *vc4;
+
+       if (!perfmon)
+               return;
+
+       vc4 = perfmon->dev;
+       if (WARN_ON_ONCE(vc4->is_vc5))
+               return;
+
+       if (refcount_dec_and_test(&perfmon->refcnt))
                kfree(perfmon);
 }
 
@@ -32,6 +46,9 @@ void vc4_perfmon_start(struct vc4_dev *vc4, struct vc4_perfmon *perfmon)
        unsigned int i;
        u32 mask;
 
+       if (WARN_ON_ONCE(vc4->is_vc5))
+               return;
+
        if (WARN_ON_ONCE(!perfmon || vc4->active_perfmon))
                return;
 
@@ -49,6 +66,9 @@ void vc4_perfmon_stop(struct vc4_dev *vc4, struct vc4_perfmon *perfmon,
 {
        unsigned int i;
 
+       if (WARN_ON_ONCE(vc4->is_vc5))
+               return;
+
        if (WARN_ON_ONCE(!vc4->active_perfmon ||
                         perfmon != vc4->active_perfmon))
                return;
@@ -64,8 +84,12 @@ void vc4_perfmon_stop(struct vc4_dev *vc4, struct vc4_perfmon *perfmon,
 
 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->is_vc5))
+               return NULL;
+
        mutex_lock(&vc4file->perfmon.lock);
        perfmon = idr_find(&vc4file->perfmon.idr, id);
        vc4_perfmon_get(perfmon);
@@ -76,8 +100,14 @@ struct vc4_perfmon *vc4_perfmon_find(struct vc4_file *vc4file, int id)
 
 void vc4_perfmon_open_file(struct vc4_file *vc4file)
 {
+       struct vc4_dev *vc4 = vc4file->dev;
+
+       if (WARN_ON_ONCE(vc4->is_vc5))
+               return;
+
        mutex_init(&vc4file->perfmon.lock);
        idr_init_base(&vc4file->perfmon.idr, VC4_PERFMONID_MIN);
+       vc4file->dev = vc4;
 }
 
 static int vc4_perfmon_idr_del(int id, void *elem, void *data)
@@ -91,6 +121,11 @@ static int vc4_perfmon_idr_del(int id, void *elem, void *data)
 
 void vc4_perfmon_close_file(struct vc4_file *vc4file)
 {
+       struct vc4_dev *vc4 = vc4file->dev;
+
+       if (WARN_ON_ONCE(vc4->is_vc5))
+               return;
+
        mutex_lock(&vc4file->perfmon.lock);
        idr_for_each(&vc4file->perfmon.idr, vc4_perfmon_idr_del, NULL);
        idr_destroy(&vc4file->perfmon.idr);
@@ -107,6 +142,9 @@ int vc4_perfmon_create_ioctl(struct drm_device *dev, void *data,
        unsigned int i;
        int ret;
 
+       if (WARN_ON_ONCE(vc4->is_vc5))
+               return -ENODEV;
+
        if (!vc4->v3d) {
                DRM_DEBUG("Creating perfmon no VC4 V3D probed\n");
                return -ENODEV;
@@ -127,6 +165,7 @@ int vc4_perfmon_create_ioctl(struct drm_device *dev, void *data,
                          GFP_KERNEL);
        if (!perfmon)
                return -ENOMEM;
+       perfmon->dev = vc4;
 
        for (i = 0; i < req->ncounters; i++)
                perfmon->events[i] = req->events[i];
@@ -157,6 +196,9 @@ 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->is_vc5))
+               return -ENODEV;
+
        if (!vc4->v3d) {
                DRM_DEBUG("Destroying perfmon no VC4 V3D probed\n");
                return -ENODEV;
@@ -182,6 +224,9 @@ int vc4_perfmon_get_values_ioctl(struct drm_device *dev, void *data,
        struct vc4_perfmon *perfmon;
        int ret;
 
+       if (WARN_ON_ONCE(vc4->is_vc5))
+               return -ENODEV;
+
        if (!vc4->v3d) {
                DRM_DEBUG("Getting perfmon no VC4 V3D probed\n");
                return -ENODEV;
index 19161b6..849385f 100644 (file)
@@ -33,6 +33,7 @@ static const struct hvs_format {
        u32 hvs; /* HVS_FORMAT_* */
        u32 pixel_order;
        u32 pixel_order_hvs5;
+       bool hvs5_only;
 } hvs_formats[] = {
        {
                .drm = DRM_FORMAT_XRGB8888,
@@ -62,71 +63,175 @@ static const struct hvs_format {
                .drm = DRM_FORMAT_RGB565,
                .hvs = HVS_PIXEL_FORMAT_RGB565,
                .pixel_order = HVS_PIXEL_ORDER_XRGB,
+               .pixel_order_hvs5 = HVS_PIXEL_ORDER_XRGB,
        },
        {
                .drm = DRM_FORMAT_BGR565,
                .hvs = HVS_PIXEL_FORMAT_RGB565,
                .pixel_order = HVS_PIXEL_ORDER_XBGR,
+               .pixel_order_hvs5 = HVS_PIXEL_ORDER_XBGR,
        },
        {
                .drm = DRM_FORMAT_ARGB1555,
                .hvs = HVS_PIXEL_FORMAT_RGBA5551,
                .pixel_order = HVS_PIXEL_ORDER_ABGR,
+               .pixel_order_hvs5 = HVS_PIXEL_ORDER_ARGB,
        },
        {
                .drm = DRM_FORMAT_XRGB1555,
                .hvs = HVS_PIXEL_FORMAT_RGBA5551,
                .pixel_order = HVS_PIXEL_ORDER_ABGR,
+               .pixel_order_hvs5 = HVS_PIXEL_ORDER_ARGB,
        },
        {
                .drm = DRM_FORMAT_RGB888,
                .hvs = HVS_PIXEL_FORMAT_RGB888,
                .pixel_order = HVS_PIXEL_ORDER_XRGB,
+               .pixel_order_hvs5 = HVS_PIXEL_ORDER_XRGB,
        },
        {
                .drm = DRM_FORMAT_BGR888,
                .hvs = HVS_PIXEL_FORMAT_RGB888,
                .pixel_order = HVS_PIXEL_ORDER_XBGR,
+               .pixel_order_hvs5 = HVS_PIXEL_ORDER_XBGR,
        },
        {
                .drm = DRM_FORMAT_YUV422,
                .hvs = HVS_PIXEL_FORMAT_YCBCR_YUV422_3PLANE,
                .pixel_order = HVS_PIXEL_ORDER_XYCBCR,
+               .pixel_order_hvs5 = HVS_PIXEL_ORDER_XYCBCR,
        },
        {
                .drm = DRM_FORMAT_YVU422,
                .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,
                .pixel_order = HVS_PIXEL_ORDER_XYCBCR,
+               .pixel_order_hvs5 = HVS_PIXEL_ORDER_XYCBCR,
        },
        {
                .drm = DRM_FORMAT_YVU420,
                .hvs = HVS_PIXEL_FORMAT_YCBCR_YUV420_3PLANE,
                .pixel_order = HVS_PIXEL_ORDER_XYCRCB,
+               .pixel_order_hvs5 = HVS_PIXEL_ORDER_XYCRCB,
        },
        {
                .drm = DRM_FORMAT_NV12,
                .hvs = HVS_PIXEL_FORMAT_YCBCR_YUV420_2PLANE,
                .pixel_order = HVS_PIXEL_ORDER_XYCBCR,
+               .pixel_order_hvs5 = HVS_PIXEL_ORDER_XYCBCR,
        },
        {
                .drm = DRM_FORMAT_NV21,
                .hvs = HVS_PIXEL_FORMAT_YCBCR_YUV420_2PLANE,
                .pixel_order = HVS_PIXEL_ORDER_XYCRCB,
+               .pixel_order_hvs5 = HVS_PIXEL_ORDER_XYCRCB,
        },
        {
                .drm = DRM_FORMAT_NV16,
                .hvs = HVS_PIXEL_FORMAT_YCBCR_YUV422_2PLANE,
                .pixel_order = HVS_PIXEL_ORDER_XYCBCR,
+               .pixel_order_hvs5 = HVS_PIXEL_ORDER_XYCBCR,
        },
        {
                .drm = DRM_FORMAT_NV61,
                .hvs = HVS_PIXEL_FORMAT_YCBCR_YUV422_2PLANE,
                .pixel_order = HVS_PIXEL_ORDER_XYCRCB,
+               .pixel_order_hvs5 = HVS_PIXEL_ORDER_XYCRCB,
+       },
+       {
+               .drm = DRM_FORMAT_P030,
+               .hvs = HVS_PIXEL_FORMAT_YCBCR_10BIT,
+               .pixel_order_hvs5 = HVS_PIXEL_ORDER_XYCBCR,
+               .hvs5_only = true,
+       },
+       {
+               .drm = DRM_FORMAT_XRGB2101010,
+               .hvs = HVS_PIXEL_FORMAT_RGBA1010102,
+               .pixel_order_hvs5 = HVS_PIXEL_ORDER_ARGB,
+               .hvs5_only = true,
+       },
+       {
+               .drm = DRM_FORMAT_ARGB2101010,
+               .hvs = HVS_PIXEL_FORMAT_RGBA1010102,
+               .pixel_order_hvs5 = HVS_PIXEL_ORDER_ARGB,
+               .hvs5_only = true,
+       },
+       {
+               .drm = DRM_FORMAT_ABGR2101010,
+               .hvs = HVS_PIXEL_FORMAT_RGBA1010102,
+               .pixel_order_hvs5 = HVS_PIXEL_ORDER_ABGR,
+               .hvs5_only = true,
+       },
+       {
+               .drm = DRM_FORMAT_XBGR2101010,
+               .hvs = HVS_PIXEL_FORMAT_RGBA1010102,
+               .pixel_order_hvs5 = HVS_PIXEL_ORDER_ABGR,
+               .hvs5_only = true,
+       },
+       {
+               .drm = DRM_FORMAT_RGB332,
+               .hvs = HVS_PIXEL_FORMAT_RGB332,
+               .pixel_order = HVS_PIXEL_ORDER_ARGB,
+               .pixel_order_hvs5 = HVS_PIXEL_ORDER_ARGB,
+       },
+       {
+               .drm = DRM_FORMAT_BGR233,
+               .hvs = HVS_PIXEL_FORMAT_RGB332,
+               .pixel_order = HVS_PIXEL_ORDER_ABGR,
+               .pixel_order_hvs5 = HVS_PIXEL_ORDER_ABGR,
+       },
+       {
+               .drm = DRM_FORMAT_XRGB4444,
+               .hvs = HVS_PIXEL_FORMAT_RGBA4444,
+               .pixel_order = HVS_PIXEL_ORDER_ABGR,
+               .pixel_order_hvs5 = HVS_PIXEL_ORDER_ARGB,
+       },
+       {
+               .drm = DRM_FORMAT_ARGB4444,
+               .hvs = HVS_PIXEL_FORMAT_RGBA4444,
+               .pixel_order = HVS_PIXEL_ORDER_ABGR,
+               .pixel_order_hvs5 = HVS_PIXEL_ORDER_ARGB,
+       },
+       {
+               .drm = DRM_FORMAT_XBGR4444,
+               .hvs = HVS_PIXEL_FORMAT_RGBA4444,
+               .pixel_order = HVS_PIXEL_ORDER_ARGB,
+               .pixel_order_hvs5 = HVS_PIXEL_ORDER_ABGR,
+       },
+       {
+               .drm = DRM_FORMAT_ABGR4444,
+               .hvs = HVS_PIXEL_FORMAT_RGBA4444,
+               .pixel_order = HVS_PIXEL_ORDER_ARGB,
+               .pixel_order_hvs5 = HVS_PIXEL_ORDER_ABGR,
+       },
+       {
+               .drm = DRM_FORMAT_BGRX4444,
+               .hvs = HVS_PIXEL_FORMAT_RGBA4444,
+               .pixel_order = HVS_PIXEL_ORDER_RGBA,
+               .pixel_order_hvs5 = HVS_PIXEL_ORDER_BGRA,
+       },
+       {
+               .drm = DRM_FORMAT_BGRA4444,
+               .hvs = HVS_PIXEL_FORMAT_RGBA4444,
+               .pixel_order = HVS_PIXEL_ORDER_RGBA,
+               .pixel_order_hvs5 = HVS_PIXEL_ORDER_BGRA,
+       },
+       {
+               .drm = DRM_FORMAT_RGBX4444,
+               .hvs = HVS_PIXEL_FORMAT_RGBA4444,
+               .pixel_order = HVS_PIXEL_ORDER_BGRA,
+               .pixel_order_hvs5 = HVS_PIXEL_ORDER_RGBA,
+       },
+       {
+               .drm = DRM_FORMAT_RGBA4444,
+               .hvs = HVS_PIXEL_FORMAT_RGBA4444,
+               .pixel_order = HVS_PIXEL_ORDER_BGRA,
+               .pixel_order_hvs5 = HVS_PIXEL_ORDER_RGBA,
        },
 };
 
@@ -144,9 +249,9 @@ 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 (3 * dst >= 2 * (src >> 16))
                return VC4_SCALING_PPF;
        else
                return VC4_SCALING_TPZ;
@@ -303,16 +408,16 @@ static int vc4_plane_margins_adj(struct drm_plane_state *pstate)
                                               adjhdisplay,
                                               crtc_state->mode.hdisplay);
        vc4_pstate->crtc_x += left;
-       if (vc4_pstate->crtc_x > crtc_state->mode.hdisplay - left)
-               vc4_pstate->crtc_x = crtc_state->mode.hdisplay - left;
+       if (vc4_pstate->crtc_x > crtc_state->mode.hdisplay - right)
+               vc4_pstate->crtc_x = crtc_state->mode.hdisplay - right;
 
        adjvdisplay = crtc_state->mode.vdisplay - (top + bottom);
        vc4_pstate->crtc_y = DIV_ROUND_CLOSEST(vc4_pstate->crtc_y *
                                               adjvdisplay,
                                               crtc_state->mode.vdisplay);
        vc4_pstate->crtc_y += top;
-       if (vc4_pstate->crtc_y > crtc_state->mode.vdisplay - top)
-               vc4_pstate->crtc_y = crtc_state->mode.vdisplay - top;
+       if (vc4_pstate->crtc_y > crtc_state->mode.vdisplay - bottom)
+               vc4_pstate->crtc_y = crtc_state->mode.vdisplay - bottom;
 
        vc4_pstate->crtc_w = DIV_ROUND_CLOSEST(vc4_pstate->crtc_w *
                                               adjhdisplay,
@@ -332,7 +437,6 @@ 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_cma_object *bo = drm_fb_cma_get_gem_obj(fb, 0);
-       u32 subpixel_src_mask = (1 << 16) - 1;
        int num_planes = fb->format->num_planes;
        struct drm_crtc_state *crtc_state;
        u32 h_subsample = fb->format->hsub;
@@ -354,18 +458,10 @@ static int vc4_plane_setup_clipping_and_scaling(struct drm_plane_state *state)
        for (i = 0; i < num_planes; i++)
                vc4_state->offsets[i] = bo->paddr + fb->offsets[i];
 
-       /* We don't support subpixel source positioning for scaling. */
-       if ((state->src.x1 & subpixel_src_mask) ||
-           (state->src.x2 & subpixel_src_mask) ||
-           (state->src.y1 & subpixel_src_mask) ||
-           (state->src.y2 & subpixel_src_mask)) {
-               return -EINVAL;
-       }
-
-       vc4_state->src_x = state->src.x1 >> 16;
-       vc4_state->src_y = state->src.y1 >> 16;
-       vc4_state->src_w[0] = (state->src.x2 - state->src.x1) >> 16;
-       vc4_state->src_h[0] = (state->src.y2 - state->src.y1) >> 16;
+       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;
@@ -418,7 +514,7 @@ static void vc4_write_tpz(struct vc4_plane_state *vc4_state, u32 src, u32 dst)
 {
        u32 scale, recip;
 
-       scale = (1 << 16) * src / dst;
+       scale = src / dst;
 
        /* The specs note that while the reciprocal would be defined
         * as (1<<32)/scale, ~0 is close enough.
@@ -432,14 +528,48 @@ static void vc4_write_tpz(struct vc4_plane_state *vc4_state, u32 src, u32 dst)
                        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)
 {
-       u32 scale = (1 << 16) * src / dst;
+       u32 scale = src / dst;
+       s32 offset, offset2;
+       s32 phase;
+
+       /* 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 consitent with YUV's luma scaling.
+                  we lose 1bit 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,
                        SCALER_PPF_AGC |
                        VC4_SET_FIELD(scale, SCALER_PPF_SCALE) |
-                       VC4_SET_FIELD(0, SCALER_PPF_IPHASE));
+                       VC4_SET_FIELD(phase, SCALER_PPF_IPHASE));
 }
 
 static u32 vc4_lbm_size(struct drm_plane_state *state)
@@ -464,7 +594,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)
@@ -482,10 +612,10 @@ static u32 vc4_lbm_size(struct drm_plane_state *state)
        }
 
        /* Align it to 64 or 128 (hvs5) bytes */
-       lbm = roundup(lbm, vc4->hvs->hvs5 ? 128 : 64);
+       lbm = roundup(lbm, vc4->is_vc5 ? 128 : 64);
 
        /* Each "word" of the LBM memory contains 2 or 4 (hvs5) pixels */
-       lbm /= vc4->hvs->hvs5 ? 4 : 2;
+       lbm /= vc4->is_vc5 ? 4 : 2;
 
        return lbm;
 }
@@ -498,13 +628,15 @@ static void vc4_write_scaling_parameters(struct drm_plane_state *state,
        /* 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_state->src_w[channel], vc4_state->crtc_w, vc4_state->src_x, channel,
+                             state->chroma_siting_h);
        }
 
        /* 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_state->src_h[channel], vc4_state->crtc_h, vc4_state->src_y, channel,
+                             state->chroma_siting_v);
                vc4_dlist_write(vc4_state, 0xc0c0c0c0);
        }
 
@@ -529,11 +661,6 @@ static void vc4_plane_calc_load(struct drm_plane_state *state)
        struct vc4_plane_state *vc4_state;
        struct drm_crtc_state *crtc_state;
        unsigned int vscale_factor;
-       struct vc4_dev *vc4;
-
-       vc4 = to_vc4_dev(state->plane->dev);
-       if (!vc4->load_tracker_available)
-               return;
 
        vc4_state = to_vc4_plane_state(state);
        crtc_state = drm_atomic_get_existing_crtc_state(state->state,
@@ -560,7 +687,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
@@ -569,11 +697,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;
        }
 
@@ -606,7 +734,7 @@ static int vc4_plane_allocate_lbm(struct drm_plane_state *state)
                ret = drm_mm_insert_node_generic(&vc4->hvs->lbm_mm,
                                                 &vc4_state->lbm,
                                                 lbm_size,
-                                                vc4->hvs->hvs5 ? 64 : 32,
+                                                vc4->is_vc5 ? 64 : 32,
                                                 0, 0);
                spin_unlock_irqrestore(&vc4->hvs->mm_lock, irqflags);
 
@@ -621,6 +749,93 @@ static int vc4_plane_allocate_lbm(struct drm_plane_state *state)
        return 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
+ * each supported colorspace.
+ */
+static const u32 colorspace_coeffs[2][DRM_COLOR_ENCODING_MAX][3] = {
+       {
+               /* Limited range */
+               {
+                       /* BT601 */
+                       SCALER_CSC0_ITR_R_601_5,
+                       SCALER_CSC1_ITR_R_601_5,
+                       SCALER_CSC2_ITR_R_601_5,
+               }, {
+                       /* BT709 */
+                       SCALER_CSC0_ITR_R_709_3,
+                       SCALER_CSC1_ITR_R_709_3,
+                       SCALER_CSC2_ITR_R_709_3,
+               }, {
+                       /* BT2020 */
+                       SCALER_CSC0_ITR_R_2020,
+                       SCALER_CSC1_ITR_R_2020,
+                       SCALER_CSC2_ITR_R_2020,
+               }
+       }, {
+               /* Full range */
+               {
+                       /* JFIF */
+                       SCALER_CSC0_JPEG_JFIF,
+                       SCALER_CSC1_JPEG_JFIF,
+                       SCALER_CSC2_JPEG_JFIF,
+               }, {
+                       /* BT709 */
+                       SCALER_CSC0_ITR_R_709_3_FR,
+                       SCALER_CSC1_ITR_R_709_3_FR,
+                       SCALER_CSC2_ITR_R_709_3_FR,
+               }, {
+                       /* BT2020 */
+                       SCALER_CSC0_ITR_R_2020_FR,
+                       SCALER_CSC1_ITR_R_2020_FR,
+                       SCALER_CSC2_ITR_R_2020_FR,
+               }
+       }
+};
+
+static u32 vc4_hvs4_get_alpha_blend_mode(struct drm_plane_state *state)
+{
+       if (!state->fb->format->has_alpha)
+               return VC4_SET_FIELD(SCALER_POS2_ALPHA_MODE_FIXED,
+                                    SCALER_POS2_ALPHA_MODE);
+
+       switch (state->pixel_blend_mode) {
+       case DRM_MODE_BLEND_PIXEL_NONE:
+               return VC4_SET_FIELD(SCALER_POS2_ALPHA_MODE_FIXED,
+                                    SCALER_POS2_ALPHA_MODE);
+       default:
+       case DRM_MODE_BLEND_PREMULTI:
+               return VC4_SET_FIELD(SCALER_POS2_ALPHA_MODE_PIPELINE,
+                                    SCALER_POS2_ALPHA_MODE) |
+                       SCALER_POS2_ALPHA_PREMULT;
+       case DRM_MODE_BLEND_COVERAGE:
+               return VC4_SET_FIELD(SCALER_POS2_ALPHA_MODE_PIPELINE,
+                                    SCALER_POS2_ALPHA_MODE);
+       }
+}
+
+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);
+
+       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);
+       }
+}
+
 /* Writes out a full display list for an active plane to the plane's
  * private dlist state.
  */
@@ -639,7 +854,8 @@ 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;
        int ret, i;
@@ -651,6 +867,9 @@ static int vc4_plane_mode_set(struct drm_plane *plane,
        if (ret)
                return ret;
 
+       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
@@ -671,9 +890,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:
@@ -688,7 +909,7 @@ static int vc4_plane_mode_set(struct drm_plane *plane,
                                                 (i ? v_subsample : 1) *
                                                 fb->pitches[i];
 
-                       vc4_state->offsets[i] += vc4_state->src_x /
+                       vc4_state->offsets[i] += src_x /
                                                 (i ? h_subsample : 1) *
                                                 fb->format->cpp[i];
                }
@@ -711,7 +932,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
@@ -721,7 +942,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
@@ -767,47 +988,90 @@ static int vc4_plane_mode_set(struct drm_plane *plane,
        case DRM_FORMAT_MOD_BROADCOM_SAND128:
        case DRM_FORMAT_MOD_BROADCOM_SAND256: {
                uint32_t param = fourcc_mod_broadcom_param(fb->modifier);
-               u32 tile_w, tile, x_off, pix_per_tile;
-
-               hvs_format = HVS_PIXEL_FORMAT_H264;
-
-               switch (base_format_mod) {
-               case DRM_FORMAT_MOD_BROADCOM_SAND64:
-                       tiling = SCALER_CTL0_TILING_64B;
-                       tile_w = 64;
-                       break;
-               case DRM_FORMAT_MOD_BROADCOM_SAND128:
-                       tiling = SCALER_CTL0_TILING_128B;
-                       tile_w = 128;
-                       break;
-               case DRM_FORMAT_MOD_BROADCOM_SAND256:
-                       tiling = SCALER_CTL0_TILING_256B_OR_T;
-                       tile_w = 256;
-                       break;
-               default:
-                       break;
-               }
 
                if (param > SCALER_TILE_HEIGHT_MASK) {
-                       DRM_DEBUG_KMS("SAND height too large (%d)\n", param);
+                       DRM_DEBUG_KMS("SAND height too large (%d)\n",
+                                     param);
                        return -EINVAL;
                }
 
-               pix_per_tile = tile_w / fb->format->cpp[0];
-               tile = vc4_state->src_x / pix_per_tile;
-               x_off = vc4_state->src_x % pix_per_tile;
+               if (fb->format->format == DRM_FORMAT_P030) {
+                       hvs_format = HVS_PIXEL_FORMAT_YCBCR_10BIT;
+                       tiling = SCALER_CTL0_TILING_128B;
+               } else {
+                       hvs_format = HVS_PIXEL_FORMAT_H264;
+
+                       switch (base_format_mod) {
+                       case DRM_FORMAT_MOD_BROADCOM_SAND64:
+                               tiling = SCALER_CTL0_TILING_64B;
+                               break;
+                       case DRM_FORMAT_MOD_BROADCOM_SAND128:
+                               tiling = SCALER_CTL0_TILING_128B;
+                               break;
+                       case DRM_FORMAT_MOD_BROADCOM_SAND256:
+                               tiling = SCALER_CTL0_TILING_256B_OR_T;
+                               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_SAND64:
+                                       tile_w = 64;
+                                       break;
+                               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;
+
                        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 ? h_subsample : 1) *
-                                                fb->format->cpp[i];
+                       vc4_state->offsets[i] += x_off & ~(i ? 1 : 0);
                }
 
                pitch0 = VC4_SET_FIELD(param, SCALER_TILE_HEIGHT);
@@ -820,6 +1084,24 @@ 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++;
+
        /* 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.
@@ -827,7 +1109,7 @@ static int vc4_plane_mode_set(struct drm_plane *plane,
        mix_plane_alpha = state->alpha != DRM_BLEND_ALPHA_OPAQUE &&
                          fb->format->has_alpha;
 
-       if (!vc4->hvs->hvs5) {
+       if (!vc4->is_vc5) {
        /* Control word */
                vc4_dlist_write(vc4_state,
                                SCALER_CTL0_VALID |
@@ -860,31 +1142,19 @@ static int vc4_plane_mode_set(struct drm_plane *plane,
                /* Position Word 2: Source Image Size, Alpha */
                vc4_state->pos2_offset = vc4_state->dlist_count;
                vc4_dlist_write(vc4_state,
-                               VC4_SET_FIELD(fb->format->has_alpha ?
-                                             SCALER_POS2_ALPHA_MODE_PIPELINE :
-                                             SCALER_POS2_ALPHA_MODE_FIXED,
-                                             SCALER_POS2_ALPHA_MODE) |
                                (mix_plane_alpha ? SCALER_POS2_ALPHA_MIX : 0) |
-                               (fb->format->has_alpha ?
-                                               SCALER_POS2_ALPHA_PREMULT : 0) |
-                               VC4_SET_FIELD(vc4_state->src_w[0],
-                                             SCALER_POS2_WIDTH) |
-                               VC4_SET_FIELD(vc4_state->src_h[0],
-                                             SCALER_POS2_HEIGHT));
+                               vc4_hvs4_get_alpha_blend_mode(state) |
+                               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);
 
        } else {
-               u32 hvs_pixel_order = format->pixel_order;
-
-               if (format->pixel_order_hvs5)
-                       hvs_pixel_order = format->pixel_order_hvs5;
-
                /* Control word */
                vc4_dlist_write(vc4_state,
                                SCALER_CTL0_VALID |
-                               (hvs_pixel_order << SCALER_CTL0_ORDER_SHIFT) |
+                               (format->pixel_order_hvs5 << SCALER_CTL0_ORDER_SHIFT) |
                                (hvs_format << SCALER_CTL0_PIXEL_FORMAT_SHIFT) |
                                VC4_SET_FIELD(tiling, SCALER_CTL0_TILING) |
                                (vc4_state->is_unity ?
@@ -911,14 +1181,9 @@ static int vc4_plane_mode_set(struct drm_plane *plane,
                vc4_dlist_write(vc4_state,
                                VC4_SET_FIELD(state->alpha >> 4,
                                              SCALER5_CTL2_ALPHA) |
-                               (fb->format->has_alpha ?
-                                       SCALER5_CTL2_ALPHA_PREMULT : 0) |
+                               vc4_hvs5_get_alpha_blend_mode(state) |
                                (mix_plane_alpha ?
-                                       SCALER5_CTL2_ALPHA_MIX : 0) |
-                               VC4_SET_FIELD(fb->format->has_alpha ?
-                                     SCALER5_CTL2_ALPHA_MODE_PIPELINE :
-                                     SCALER5_CTL2_ALPHA_MODE_FIXED,
-                                     SCALER5_CTL2_ALPHA_MODE)
+                                       SCALER5_CTL2_ALPHA_MIX : 0)
                               );
 
                /* Position Word 1: Scaled Image Dimensions. */
@@ -933,10 +1198,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);
@@ -960,7 +1223,8 @@ static int vc4_plane_mode_set(struct drm_plane *plane,
 
        /* Pitch word 1/2 */
        for (i = 1; i < num_planes; i++) {
-               if (hvs_format != HVS_PIXEL_FORMAT_H264) {
+               if (hvs_format != HVS_PIXEL_FORMAT_H264 &&
+                   hvs_format != HVS_PIXEL_FORMAT_YCBCR_10BIT) {
                        vc4_dlist_write(vc4_state,
                                        VC4_SET_FIELD(fb->pitches[i],
                                                      SCALER_SRC_PITCH));
@@ -971,9 +1235,20 @@ static int vc4_plane_mode_set(struct drm_plane *plane,
 
        /* Colorspace conversion words */
        if (vc4_state->is_yuv) {
-               vc4_dlist_write(vc4_state, SCALER_CSC0_ITR_R_601_5);
-               vc4_dlist_write(vc4_state, SCALER_CSC1_ITR_R_601_5);
-               vc4_dlist_write(vc4_state, SCALER_CSC2_ITR_R_601_5);
+               enum drm_color_encoding color_encoding = state->color_encoding;
+               enum drm_color_range color_range = state->color_range;
+               const u32 *ccm;
+
+               if (color_encoding >= DRM_COLOR_ENCODING_MAX)
+                       color_encoding = DRM_COLOR_YCBCR_BT601;
+               if (color_range >= DRM_COLOR_RANGE_MAX)
+                       color_range = DRM_COLOR_YCBCR_LIMITED_RANGE;
+
+               ccm = colorspace_coeffs[color_range][color_encoding];
+
+               vc4_dlist_write(vc4_state, ccm[0]);
+               vc4_dlist_write(vc4_state, ccm[1]);
+               vc4_dlist_write(vc4_state, ccm[2]);
        }
 
        vc4_state->lbm_offset = 0;
@@ -1219,6 +1494,10 @@ static int vc4_plane_atomic_async_check(struct drm_plane *plane,
 
        old_vc4_state = to_vc4_plane_state(plane->state);
        new_vc4_state = to_vc4_plane_state(new_plane_state);
+
+       if (!new_vc4_state->hw_dlist)
+               return -EINVAL;
+
        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 ||
@@ -1288,6 +1567,13 @@ static const struct drm_plane_helper_funcs vc4_plane_helper_funcs = {
        .atomic_async_update = vc4_plane_atomic_async_update,
 };
 
+static const struct drm_plane_helper_funcs vc5_plane_helper_funcs = {
+       .atomic_check = vc4_plane_atomic_check,
+       .atomic_update = vc4_plane_atomic_update,
+       .atomic_async_check = vc4_plane_atomic_async_check,
+       .atomic_async_update = vc4_plane_atomic_async_update,
+};
+
 static bool vc4_format_mod_supported(struct drm_plane *plane,
                                     uint32_t format,
                                     uint64_t modifier)
@@ -1320,10 +1606,27 @@ static bool vc4_format_mod_supported(struct drm_plane *plane,
                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_RGBX1010102:
        case DRM_FORMAT_BGRX1010102:
        case DRM_FORMAT_RGBA1010102:
        case DRM_FORMAT_BGRA1010102:
+       case DRM_FORMAT_XRGB4444:
+       case DRM_FORMAT_ARGB4444:
+       case DRM_FORMAT_XBGR4444:
+       case DRM_FORMAT_ABGR4444:
+       case DRM_FORMAT_RGBX4444:
+       case DRM_FORMAT_RGBA4444:
+       case DRM_FORMAT_BGRX4444:
+       case DRM_FORMAT_BGRA4444:
+       case DRM_FORMAT_RGB332:
+       case DRM_FORMAT_BGR233:
        case DRM_FORMAT_YUV422:
        case DRM_FORMAT_YVU422:
        case DRM_FORMAT_YUV420:
@@ -1349,9 +1652,11 @@ static const struct drm_plane_funcs vc4_plane_funcs = {
 struct drm_plane *vc4_plane_init(struct drm_device *dev,
                                 enum drm_plane_type type)
 {
+       struct vc4_dev *vc4 = to_vc4_dev(dev);
        struct drm_plane *plane = NULL;
        struct vc4_plane *vc4_plane;
        u32 formats[ARRAY_SIZE(hvs_formats)];
+       int num_formats = 0;
        int ret = 0;
        unsigned i;
        static const uint64_t modifiers[] = {
@@ -1368,29 +1673,56 @@ struct drm_plane *vc4_plane_init(struct drm_device *dev,
        if (!vc4_plane)
                return ERR_PTR(-ENOMEM);
 
-       for (i = 0; i < ARRAY_SIZE(hvs_formats); i++)
-               formats[i] = hvs_formats[i].drm;
+       for (i = 0; i < ARRAY_SIZE(hvs_formats); i++) {
+               if (!hvs_formats[i].hvs5_only || vc4->is_vc5) {
+                       formats[num_formats] = hvs_formats[i].drm;
+                       num_formats++;
+               }
+       }
 
        plane = &vc4_plane->base;
        ret = drm_universal_plane_init(dev, plane, 0,
                                       &vc4_plane_funcs,
-                                      formats, ARRAY_SIZE(formats),
+                                      formats, num_formats,
                                       modifiers, type, NULL);
        if (ret)
                return ERR_PTR(ret);
 
-       drm_plane_helper_add(plane, &vc4_plane_helper_funcs);
+       if (vc4->is_vc5)
+               drm_plane_helper_add(plane, &vc5_plane_helper_funcs);
+       else
+               drm_plane_helper_add(plane, &vc4_plane_helper_funcs);
 
        drm_plane_create_alpha_property(plane);
+       drm_plane_create_blend_mode_property(plane,
+                                            BIT(DRM_MODE_BLEND_PIXEL_NONE) |
+                                            BIT(DRM_MODE_BLEND_PREMULTI) |
+                                            BIT(DRM_MODE_BLEND_COVERAGE));
        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);
+
+       drm_plane_create_chroma_siting_properties(plane, 0, 0);
+
+       if (type == DRM_PLANE_TYPE_PRIMARY)
+               drm_plane_create_zpos_immutable_property(plane, 0);
+
        return plane;
 }
 
+#define VC4_NUM_OVERLAY_PLANES 16
+
 int vc4_plane_create_additional_planes(struct drm_device *drm)
 {
        struct drm_plane *cursor_plane;
@@ -1406,7 +1738,7 @@ int vc4_plane_create_additional_planes(struct drm_device *drm)
         * modest number of planes to expose, that should hopefully
         * still cover any sane usecase.
         */
-       for (i = 0; i < 16; i++) {
+       for (i = 0; i < VC4_NUM_OVERLAY_PLANES; i++) {
                struct drm_plane *plane =
                        vc4_plane_init(drm, DRM_PLANE_TYPE_OVERLAY);
 
@@ -1415,17 +1747,28 @@ int vc4_plane_create_additional_planes(struct drm_device *drm)
 
                plane->possible_crtcs =
                        GENMASK(drm->mode_config.num_crtc - 1, 0);
+
+               /* 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);
        }
 
        drm_for_each_crtc(crtc, drm) {
                /* Set up the legacy cursor after overlay initialization,
-                * since we overlay planes on the CRTC in the order they were
-                * initialized.
+                * since the zpos fallback is that planes are rendered by plane
+                * ID order, and that then puts the cursor on top.
                 */
                cursor_plane = vc4_plane_init(drm, DRM_PLANE_TYPE_CURSOR);
                if (!IS_ERR(cursor_plane)) {
                        cursor_plane->possible_crtcs = drm_crtc_mask(crtc);
                        crtc->cursor = cursor_plane;
+
+                       drm_plane_create_zpos_property(cursor_plane,
+                                                      VC4_NUM_OVERLAY_PLANES + 1,
+                                                      1,
+                                                      VC4_NUM_OVERLAY_PLANES + 1);
                }
        }
 
index 8ac2f08..82b0fda 100644 (file)
 #define SCALER_DLIST_START                      0x00002000
 #define SCALER_DLIST_SIZE                       0x00004000
 
+/* Gamma PWL for each channel. 16 points for each of 4 colour channels (alpha
+ * only on channel 2). 8 bytes per entry, offsets first, then gradient:
+ *   Y = GRAD * X + C
+ *
+ * Values for X and C are left justified, and vary depending on the width of
+ * the HVS channel:
+ *    8-bit pipeline: X uses [31:24], C is U8.8 format, and GRAD is U4.8.
+ *   12-bit pipeline: X uses [31:20], C is U12.4 format, and GRAD is U4.8.
+ *
+ * The 3 HVS channels start at 0x400 offsets (ie chan 1 starts at 0x2400, and
+ * chan 2 at 0x2800).
+ */
+#define SCALER5_DSPGAMMA_NUM_POINTS            16
+#define SCALER5_DSPGAMMA_START                 0x00002000
+#define SCALER5_DSPGAMMA_CHAN_OFFSET           0x400
+# define SCALER5_DSPGAMMA_OFF_X_MASK           VC4_MASK(31, 20)
+# define SCALER5_DSPGAMMA_OFF_X_SHIFT          20
+# define SCALER5_DSPGAMMA_OFF_C_MASK           VC4_MASK(15, 0)
+# define SCALER5_DSPGAMMA_OFF_C_SHIFT          0
+# define SCALER5_DSPGAMMA_GRAD_MASK            VC4_MASK(11, 0)
+# define SCALER5_DSPGAMMA_GRAD_SHIFT           0
+
 #define SCALER5_DLIST_START                    0x00004000
 
 # define VC4_HDMI_SW_RESET_FORMAT_DETECT       BIT(1)
@@ -782,8 +804,27 @@ enum {
 # define VC4_HD_CSC_CTL_RGB2YCC                        BIT(1)
 # define VC4_HD_CSC_CTL_ENABLE                 BIT(0)
 
+# define VC5_MT_CP_CSC_CTL_USE_444_TO_422      BIT(6)
+# define VC5_MT_CP_CSC_CTL_FILTER_MODE_444_TO_422_MASK \
+                                               VC4_MASK(5, 4)
+# define VC5_MT_CP_CSC_CTL_FILTER_MODE_444_TO_422_STANDARD \
+                                               3
+# define VC5_MT_CP_CSC_CTL_USE_RNG_SUPPRESSION BIT(3)
+# define VC5_MT_CP_CSC_CTL_ENABLE              BIT(2)
+# define VC5_MT_CP_CSC_CTL_MODE_MASK           VC4_MASK(1, 0)
+
+# define VC5_MT_CP_CHANNEL_CTL_OUTPUT_REMAP_MASK \
+                                               VC4_MASK(7, 6)
+# define VC5_MT_CP_CHANNEL_CTL_OUTPUT_REMAP_LEGACY_STYLE \
+                                               2
+
 # define VC4_DVP_HT_CLOCK_STOP_PIXEL           BIT(1)
 
+# define VC5_DVP_HT_VEC_INTERFACE_CFG_SEL_422_MASK \
+                                               VC4_MASK(3, 2)
+# define VC5_DVP_HT_VEC_INTERFACE_CFG_SEL_422_FORMAT_422_LEGACY \
+                                               2
+
 /* HVS display list information. */
 #define HVS_BOOTLOADER_DLIST_END                32
 
@@ -816,16 +857,19 @@ enum hvs_pixel_format {
 /* Note: the LSB is the rightmost character shown.  Only valid for
  * HVS_PIXEL_FORMAT_RGB8888, not RGB888.
  */
+/* For modes 332, 4444, 555, 5551, 6666, 8888, 10:10:10:2 */
 #define HVS_PIXEL_ORDER_RGBA                   0
 #define HVS_PIXEL_ORDER_BGRA                   1
 #define HVS_PIXEL_ORDER_ARGB                   2
 #define HVS_PIXEL_ORDER_ABGR                   3
 
+/* For modes 666 and 888 (4 & 5) */
 #define HVS_PIXEL_ORDER_XBRG                   0
 #define HVS_PIXEL_ORDER_XRBG                   1
 #define HVS_PIXEL_ORDER_XRGB                   2
 #define HVS_PIXEL_ORDER_XBGR                   3
 
+/* For YCbCr modes (8-12, and 17) */
 #define HVS_PIXEL_ORDER_XYCBCR                 0
 #define HVS_PIXEL_ORDER_XYCRCB                 1
 #define HVS_PIXEL_ORDER_YXCBCR                 2
@@ -983,7 +1027,10 @@ enum hvs_pixel_format {
 #define SCALER_CSC0_COEF_CR_OFS_SHIFT          0
 #define SCALER_CSC0_ITR_R_601_5                        0x00f00000
 #define SCALER_CSC0_ITR_R_709_3                        0x00f00000
+#define SCALER_CSC0_ITR_R_2020                 0x00f00000
 #define SCALER_CSC0_JPEG_JFIF                  0x00000000
+#define SCALER_CSC0_ITR_R_709_3_FR             0x00000000
+#define SCALER_CSC0_ITR_R_2020_FR              0x00000000
 
 /* S2.8 contribution of Cb to Green */
 #define SCALER_CSC1_COEF_CB_GRN_MASK           VC4_MASK(31, 22)
@@ -998,8 +1045,11 @@ enum hvs_pixel_format {
 #define SCALER_CSC1_COEF_CR_BLU_MASK           VC4_MASK(1, 0)
 #define SCALER_CSC1_COEF_CR_BLU_SHIFT          0
 #define SCALER_CSC1_ITR_R_601_5                        0xe73304a8
-#define SCALER_CSC1_ITR_R_709_3                        0xf2b784a8
-#define SCALER_CSC1_JPEG_JFIF                  0xea34a400
+#define SCALER_CSC1_ITR_R_709_3                        0xf27784a8
+#define SCALER_CSC1_ITR_R_2020                 0xf43594a8
+#define SCALER_CSC1_JPEG_JFIF                  0xea349400
+#define SCALER_CSC1_ITR_R_709_3_FR             0xf4388400
+#define SCALER_CSC1_ITR_R_2020_FR              0xf5b6d400
 
 /* S2.8 contribution of Cb to Red */
 #define SCALER_CSC2_COEF_CB_RED_MASK           VC4_MASK(29, 20)
@@ -1010,9 +1060,12 @@ enum hvs_pixel_format {
 /* S2.8 contribution of Cb to Blue */
 #define SCALER_CSC2_COEF_CB_BLU_MASK           VC4_MASK(19, 10)
 #define SCALER_CSC2_COEF_CB_BLU_SHIFT          10
-#define SCALER_CSC2_ITR_R_601_5                        0x00066204
-#define SCALER_CSC2_ITR_R_709_3                        0x00072a1c
-#define SCALER_CSC2_JPEG_JFIF                  0x000599c5
+#define SCALER_CSC2_ITR_R_601_5                        0x00066604
+#define SCALER_CSC2_ITR_R_709_3                        0x00072e1d
+#define SCALER_CSC2_ITR_R_2020                 0x0006b624
+#define SCALER_CSC2_JPEG_JFIF                  0x00059dc6
+#define SCALER_CSC2_ITR_R_709_3_FR             0x00064ddb
+#define SCALER_CSC2_ITR_R_2020_FR              0x0005e5e2
 
 #define SCALER_TPZ0_VERT_RECALC                        BIT(31)
 #define SCALER_TPZ0_SCALE_MASK                 VC4_MASK(28, 8)
index 3c918ee..f6b7dc3 100644 (file)
@@ -593,11 +593,15 @@ vc4_rcl_render_config_surface_setup(struct vc4_exec_info *exec,
 
 int vc4_get_rcl(struct drm_device *dev, struct vc4_exec_info *exec)
 {
+       struct vc4_dev *vc4 = to_vc4_dev(dev);
        struct vc4_rcl_setup setup = {0};
        struct drm_vc4_submit_cl *args = exec->args;
        bool has_bin = args->bin_cl_size != 0;
        int ret;
 
+       if (WARN_ON_ONCE(vc4->is_vc5))
+               return -ENODEV;
+
        if (args->min_x_tile > args->max_x_tile ||
            args->min_y_tile > args->max_y_tile) {
                DRM_DEBUG("Bad render tile set (%d,%d)-(%d,%d)\n",
index 7bb3067..cc714dc 100644 (file)
@@ -127,6 +127,9 @@ 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->is_vc5))
+               return -ENODEV;
+
        mutex_lock(&vc4->power_lock);
        if (vc4->power_refcount++ == 0) {
                int ret = pm_runtime_get_sync(&vc4->v3d->pdev->dev);
@@ -145,6 +148,9 @@ vc4_v3d_pm_get(struct vc4_dev *vc4)
 void
 vc4_v3d_pm_put(struct vc4_dev *vc4)
 {
+       if (WARN_ON_ONCE(vc4->is_vc5))
+               return;
+
        mutex_lock(&vc4->power_lock);
        if (--vc4->power_refcount == 0) {
                pm_runtime_mark_last_busy(&vc4->v3d->pdev->dev);
@@ -172,6 +178,9 @@ int vc4_v3d_get_bin_slot(struct vc4_dev *vc4)
        uint64_t seqno = 0;
        struct vc4_exec_info *exec;
 
+       if (WARN_ON_ONCE(vc4->is_vc5))
+               return -ENODEV;
+
 try_again:
        spin_lock_irqsave(&vc4->job_lock, irqflags);
        slot = ffs(~vc4->bin_alloc_used);
@@ -316,6 +325,9 @@ int vc4_v3d_bin_bo_get(struct vc4_dev *vc4, bool *used)
 {
        int ret = 0;
 
+       if (WARN_ON_ONCE(vc4->is_vc5))
+               return -ENODEV;
+
        mutex_lock(&vc4->bin_bo_lock);
 
        if (used && *used)
@@ -348,6 +360,9 @@ static void bin_bo_release(struct kref *ref)
 
 void vc4_v3d_bin_bo_put(struct vc4_dev *vc4)
 {
+       if (WARN_ON_ONCE(vc4->is_vc5))
+               return;
+
        mutex_lock(&vc4->bin_bo_lock);
        kref_put(&vc4->bin_bo_kref, bin_bo_release);
        mutex_unlock(&vc4->bin_bo_lock);
index eec76af..833eb62 100644 (file)
@@ -105,9 +105,13 @@ size_is_lt(uint32_t width, uint32_t height, int cpp)
 struct drm_gem_cma_object *
 vc4_use_bo(struct vc4_exec_info *exec, uint32_t hindex)
 {
+       struct vc4_dev *vc4 = exec->dev;
        struct drm_gem_cma_object *obj;
        struct vc4_bo *bo;
 
+       if (WARN_ON_ONCE(vc4->is_vc5))
+               return NULL;
+
        if (hindex >= exec->bo_count) {
                DRM_DEBUG("BO index %d greater than BO count %d\n",
                          hindex, exec->bo_count);
@@ -160,10 +164,14 @@ vc4_check_tex_size(struct vc4_exec_info *exec, struct drm_gem_cma_object *fbo,
                   uint32_t offset, uint8_t tiling_format,
                   uint32_t width, uint32_t height, uint8_t cpp)
 {
+       struct vc4_dev *vc4 = exec->dev;
        uint32_t aligned_width, aligned_height, stride, size;
        uint32_t utile_w = utile_width(cpp);
        uint32_t utile_h = utile_height(cpp);
 
+       if (WARN_ON_ONCE(vc4->is_vc5))
+               return -ENODEV;
+
        /* The shaded vertex format stores signed 12.4 fixed point
         * (-2048,2047) offsets from the viewport center, so we should
         * never have a render target larger than 4096.  The texture
@@ -482,10 +490,14 @@ vc4_validate_bin_cl(struct drm_device *dev,
                    void *unvalidated,
                    struct vc4_exec_info *exec)
 {
+       struct vc4_dev *vc4 = to_vc4_dev(dev);
        uint32_t len = exec->args->bin_cl_size;
        uint32_t dst_offset = 0;
        uint32_t src_offset = 0;
 
+       if (WARN_ON_ONCE(vc4->is_vc5))
+               return -ENODEV;
+
        while (src_offset < len) {
                void *dst_pkt = validated + dst_offset;
                void *src_pkt = unvalidated + src_offset;
@@ -926,9 +938,13 @@ int
 vc4_validate_shader_recs(struct drm_device *dev,
                         struct vc4_exec_info *exec)
 {
+       struct vc4_dev *vc4 = to_vc4_dev(dev);
        uint32_t i;
        int ret = 0;
 
+       if (WARN_ON_ONCE(vc4->is_vc5))
+               return -ENODEV;
+
        for (i = 0; i < exec->shader_state_count; i++) {
                ret = validate_gl_shader_rec(dev, exec, &exec->shader_state[i]);
                if (ret)
index 7cf82b0..e315aeb 100644 (file)
@@ -778,6 +778,7 @@ vc4_handle_branch_target(struct vc4_shader_validation_state *validation_state)
 struct vc4_validated_shader_info *
 vc4_validate_shader(struct drm_gem_cma_object *shader_obj)
 {
+       struct vc4_dev *vc4 = to_vc4_dev(shader_obj->base.dev);
        bool found_shader_end = false;
        int shader_end_ip = 0;
        uint32_t last_thread_switch_ip = -3;
@@ -785,6 +786,9 @@ vc4_validate_shader(struct drm_gem_cma_object *shader_obj)
        struct vc4_validated_shader_info *validated_shader = NULL;
        struct vc4_shader_validation_state validation_state;
 
+       if (WARN_ON_ONCE(vc4->is_vc5))
+               return NULL;
+
        memset(&validation_state, 0, sizeof(validation_state));
        validation_state.shader = shader_obj->vaddr;
        validation_state.max_ip = shader_obj->base.size / sizeof(uint64_t);
index 11fc3d6..d263fda 100644 (file)
@@ -45,6 +45,7 @@
 #define VEC_CONFIG0_YDEL(x)            ((x) << 26)
 #define VEC_CONFIG0_CDEL_MASK          GENMASK(25, 24)
 #define VEC_CONFIG0_CDEL(x)            ((x) << 24)
+#define VEC_CONFIG0_SECAM_STD          BIT(21)
 #define VEC_CONFIG0_PBPR_FIL           BIT(18)
 #define VEC_CONFIG0_CHROMA_GAIN_MASK   GENMASK(17, 16)
 #define VEC_CONFIG0_CHROMA_GAIN_UNITY  (0 << 16)
 #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
 #define VEC_CONFIG0_PAL_N_STD          3
 
 #define VEC_SCHPH                      0x108
 #define VEC_SOFT_RESET                 0x10c
 #define VEC_CLMP0_START                        0x144
 #define VEC_CLMP0_END                  0x148
+
+/*
+ * These set the color subcarrier frequency
+ * if VEC_CONFIG1_CUSTOM_FREQ is enabled.
+ *
+ * VEC_FREQ1_0 contains the most significant 16-bit half-word,
+ * VEC_FREQ3_2 contains the least significant 16-bit half-word.
+ * 0x80000000 seems to be equivalent to the pixel clock
+ * (which itself is the VEC clock divided by 8).
+ *
+ * Reference values (with the default pixel clock of 13.5 MHz):
+ *
+ * NTSC  (3579545.[45] Hz)     - 0x21F07C1F
+ * PAL   (4433618.75 Hz)       - 0x2A098ACB
+ * PAL-M (3575611.[888111] Hz) - 0x21E6EFE3
+ * PAL-N (3582056.25 Hz)       - 0x21F69446
+ *
+ * NOTE: For SECAM, it is used as the Dr center frequency,
+ * regardless of whether VEC_CONFIG1_CUSTOM_FREQ is enabled or not;
+ * that is specified as 4406250 Hz, which corresponds to 0x29C71C72.
+ */
 #define VEC_FREQ3_2                    0x180
 #define VEC_FREQ1_0                    0x184
 
 
 #define VEC_INTERRUPT_CONTROL          0x190
 #define VEC_INTERRUPT_STATUS           0x194
+
+/*
+ * Db center frequency for SECAM; the clock for this is the same as for
+ * VEC_FREQ3_2/VEC_FREQ1_0, which is used for Dr center frequency.
+ *
+ * This is specified as 4250000 Hz, which corresponds to 0x284BDA13.
+ * That is also the default value, so no need to set it explicitly.
+ */
 #define VEC_FCW_SECAM_B                        0x198
 #define VEC_SECAM_GAIN_VAL             0x19c
 
 #define VEC_DAC_MISC_DAC_RST_N         BIT(0)
 
 
+static char *vc4_vec_tv_norm;
+
 struct vc4_vec_variant {
        u32 dac_config;
 };
@@ -170,8 +203,6 @@ struct vc4_vec {
 
        struct clk *clock;
 
-       const struct vc4_vec_tv_mode *tv_mode;
-
        struct debugfs_regset32 regset;
 };
 
@@ -205,13 +236,20 @@ struct vc4_vec_connector {
 enum vc4_vec_tv_mode_id {
        VC4_VEC_TV_MODE_NTSC,
        VC4_VEC_TV_MODE_NTSC_J,
+       VC4_VEC_TV_MODE_NTSC_443,
        VC4_VEC_TV_MODE_PAL,
        VC4_VEC_TV_MODE_PAL_M,
+       VC4_VEC_TV_MODE_PAL_N,
+       VC4_VEC_TV_MODE_PAL60,
+       VC4_VEC_TV_MODE_SECAM,
 };
 
 struct vc4_vec_tv_mode {
-       const struct drm_display_mode *mode;
-       void (*mode_set)(struct vc4_vec *vec);
+       const struct drm_display_mode *interlaced_mode;
+       const struct drm_display_mode *progressive_mode;
+       u32 config0;
+       u32 config1;
+       u32 custom_freq;
 };
 
 static const struct debugfs_reg32 vec_regs[] = {
@@ -241,66 +279,126 @@ static const struct debugfs_reg32 vec_regs[] = {
        VC4_REG32(VEC_DAC_MISC),
 };
 
-static void vc4_vec_ntsc_mode_set(struct vc4_vec *vec)
-{
-       VEC_WRITE(VEC_CONFIG0, VEC_CONFIG0_NTSC_STD | VEC_CONFIG0_PDEN);
-       VEC_WRITE(VEC_CONFIG1, VEC_CONFIG1_C_CVBS_CVBS);
-}
-
-static void vc4_vec_ntsc_j_mode_set(struct vc4_vec *vec)
-{
-       VEC_WRITE(VEC_CONFIG0, VEC_CONFIG0_NTSC_STD);
-       VEC_WRITE(VEC_CONFIG1, VEC_CONFIG1_C_CVBS_CVBS);
-}
-
-static const struct drm_display_mode ntsc_mode = {
-       DRM_MODE("720x480", DRM_MODE_TYPE_DRIVER, 13500,
+static const struct drm_display_mode drm_mode_480i = {
+       DRM_MODE("720x480i", DRM_MODE_TYPE_DRIVER, 13500,
                 720, 720 + 14, 720 + 14 + 64, 720 + 14 + 64 + 60, 0,
-                480, 480 + 3, 480 + 3 + 3, 480 + 3 + 3 + 16, 0,
+                480, 480 + 7, 480 + 7 + 6, 525, 0,
                 DRM_MODE_FLAG_INTERLACE)
 };
 
-static void vc4_vec_pal_mode_set(struct vc4_vec *vec)
-{
-       VEC_WRITE(VEC_CONFIG0, VEC_CONFIG0_PAL_BDGHI_STD);
-       VEC_WRITE(VEC_CONFIG1, VEC_CONFIG1_C_CVBS_CVBS);
-}
-
-static void vc4_vec_pal_m_mode_set(struct vc4_vec *vec)
-{
-       VEC_WRITE(VEC_CONFIG0, VEC_CONFIG0_PAL_BDGHI_STD);
-       VEC_WRITE(VEC_CONFIG1,
-                 VEC_CONFIG1_C_CVBS_CVBS | VEC_CONFIG1_CUSTOM_FREQ);
-       VEC_WRITE(VEC_FREQ3_2, 0x223b);
-       VEC_WRITE(VEC_FREQ1_0, 0x61d1);
-}
+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 pal_mode = {
-       DRM_MODE("720x576", DRM_MODE_TYPE_DRIVER, 13500,
+static const struct drm_display_mode drm_mode_576i = {
+       DRM_MODE("720x576i", DRM_MODE_TYPE_DRIVER, 13500,
                 720, 720 + 20, 720 + 20 + 64, 720 + 20 + 64 + 60, 0,
-                576, 576 + 2, 576 + 2 + 3, 576 + 2 + 3 + 20, 0,
+                576, 576 + 4, 576 + 4 + 6, 625, 0,
                 DRM_MODE_FLAG_INTERLACE)
 };
 
+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[] = {
        [VC4_VEC_TV_MODE_NTSC] = {
-               .mode = &ntsc_mode,
-               .mode_set = vc4_vec_ntsc_mode_set,
+               .interlaced_mode = &drm_mode_480i,
+               .progressive_mode = &drm_mode_240p,
+               .config0 = VEC_CONFIG0_NTSC_STD | VEC_CONFIG0_PDEN,
+               .config1 = VEC_CONFIG1_C_CVBS_CVBS,
        },
        [VC4_VEC_TV_MODE_NTSC_J] = {
-               .mode = &ntsc_mode,
-               .mode_set = vc4_vec_ntsc_j_mode_set,
+               .interlaced_mode = &drm_mode_480i,
+               .progressive_mode = &drm_mode_240p,
+               .config0 = VEC_CONFIG0_NTSC_STD,
+               .config1 = VEC_CONFIG1_C_CVBS_CVBS,
+       },
+       [VC4_VEC_TV_MODE_NTSC_443] = {
+               /* NTSC with PAL chroma frequency */
+               .interlaced_mode = &drm_mode_480i,
+               .progressive_mode = &drm_mode_240p,
+               .config0 = VEC_CONFIG0_NTSC_STD,
+               .config1 = VEC_CONFIG1_C_CVBS_CVBS | VEC_CONFIG1_CUSTOM_FREQ,
+               .custom_freq = 0x2a098acb,
        },
        [VC4_VEC_TV_MODE_PAL] = {
-               .mode = &pal_mode,
-               .mode_set = vc4_vec_pal_mode_set,
+               .interlaced_mode = &drm_mode_576i,
+               .progressive_mode = &drm_mode_288p,
+               .config0 = VEC_CONFIG0_PAL_BDGHI_STD,
+               .config1 = VEC_CONFIG1_C_CVBS_CVBS,
        },
        [VC4_VEC_TV_MODE_PAL_M] = {
-               .mode = &pal_mode,
-               .mode_set = vc4_vec_pal_m_mode_set,
+               .interlaced_mode = &drm_mode_480i,
+               .progressive_mode = &drm_mode_240p,
+               .config0 = VEC_CONFIG0_PAL_M_STD,
+               .config1 = VEC_CONFIG1_C_CVBS_CVBS,
+       },
+       [VC4_VEC_TV_MODE_PAL_N] = {
+               .interlaced_mode = &drm_mode_576i,
+               .progressive_mode = &drm_mode_288p,
+               .config0 = VEC_CONFIG0_PAL_N_STD,
+               .config1 = VEC_CONFIG1_C_CVBS_CVBS,
        },
+       [VC4_VEC_TV_MODE_PAL60] = {
+               /* PAL-M with chroma frequency of regular PAL */
+               .interlaced_mode = &drm_mode_480i,
+               .progressive_mode = &drm_mode_240p,
+               .config0 = VEC_CONFIG0_PAL_M_STD,
+               .config1 = VEC_CONFIG1_C_CVBS_CVBS | VEC_CONFIG1_CUSTOM_FREQ,
+               .custom_freq = 0x2a098acb,
+       },
+       [VC4_VEC_TV_MODE_SECAM] = {
+               .interlaced_mode = &drm_mode_576i,
+               .progressive_mode = &drm_mode_288p,
+               .config0 = VEC_CONFIG0_SECAM_STD,
+               .config1 = VEC_CONFIG1_C_CVBS_CVBS,
+               .custom_freq = 0x29c71c72,
+       },
+};
+
+static const char * const tv_mode_names[] = {
+       [VC4_VEC_TV_MODE_NTSC] = "NTSC",
+       [VC4_VEC_TV_MODE_NTSC_J] = "NTSC-J",
+       [VC4_VEC_TV_MODE_NTSC_443] = "NTSC-443",
+       [VC4_VEC_TV_MODE_PAL] = "PAL",
+       [VC4_VEC_TV_MODE_PAL_M] = "PAL-M",
+       [VC4_VEC_TV_MODE_PAL_N] = "PAL-N",
+       [VC4_VEC_TV_MODE_PAL60] = "PAL60",
+       [VC4_VEC_TV_MODE_SECAM] = "SECAM",
 };
 
+enum vc4_vec_tv_mode_id
+vc4_vec_get_default_mode(struct drm_connector *connector)
+{
+       int i;
+
+       if (vc4_vec_tv_norm) {
+               for (i = 0; i < ARRAY_SIZE(tv_mode_names); i++)
+                       if (strcmp(vc4_vec_tv_norm, tv_mode_names[i]) == 0)
+                               return (enum vc4_vec_tv_mode_id) i;
+       } 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 VC4_VEC_TV_MODE_PAL;
+       }
+
+       /* in all other cases, default to NTSC */
+       return VC4_VEC_TV_MODE_NTSC;
+}
+
 static enum drm_connector_status
 vc4_vec_connector_detect(struct drm_connector *connector, bool force)
 {
@@ -316,31 +414,74 @@ static void vc4_vec_connector_destroy(struct drm_connector *connector)
 static int vc4_vec_connector_get_modes(struct drm_connector *connector)
 {
        struct drm_connector_state *state = connector->state;
-       struct drm_display_mode *mode;
-
-       mode = drm_mode_duplicate(connector->dev,
-                                 vc4_vec_tv_modes[state->tv.mode].mode);
-       if (!mode) {
+       struct drm_display_mode *interlaced_mode, *progressive_mode;
+
+       interlaced_mode =
+               drm_mode_duplicate(connector->dev,
+                                  vc4_vec_tv_modes[state->tv.mode].interlaced_mode);
+       progressive_mode =
+               drm_mode_duplicate(connector->dev,
+                                  vc4_vec_tv_modes[state->tv.mode].progressive_mode);
+       if (!interlaced_mode || !progressive_mode) {
                DRM_ERROR("Failed to create a new display mode\n");
+               drm_mode_destroy(connector->dev, interlaced_mode);
+               drm_mode_destroy(connector->dev, progressive_mode);
                return -ENOMEM;
        }
 
-       drm_mode_probed_add(connector, mode);
+       if (connector->cmdline_mode.specified &&
+           connector->cmdline_mode.refresh_specified &&
+           !connector->cmdline_mode.interlace)
+               /* progressive mode set at boot, let's make it preferred */
+               progressive_mode->type |= DRM_MODE_TYPE_PREFERRED;
+       else
+               /* otherwise, interlaced mode is preferred */
+               interlaced_mode->type |= DRM_MODE_TYPE_PREFERRED;
+
+       drm_mode_probed_add(connector, interlaced_mode);
+       drm_mode_probed_add(connector, progressive_mode);
 
        return 1;
 }
 
+static void vc4_vec_connector_reset(struct drm_connector *connector)
+{
+       drm_atomic_helper_connector_reset(connector);
+       /* preserve TV standard */
+       if (connector->state)
+               connector->state->tv.mode = vc4_vec_get_default_mode(connector);
+}
+
+static int vc4_vec_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_funcs vc4_vec_connector_funcs = {
        .detect = vc4_vec_connector_detect,
        .fill_modes = drm_helper_probe_single_connector_modes,
        .destroy = vc4_vec_connector_destroy,
-       .reset = drm_atomic_helper_connector_reset,
+       .reset = vc4_vec_connector_reset,
        .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
        .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
 };
 
 static const struct drm_connector_helper_funcs vc4_vec_connector_helper_funcs = {
        .get_modes = vc4_vec_connector_get_modes,
+       .atomic_check = vc4_vec_connector_atomic_check,
 };
 
 static struct drm_connector *vc4_vec_connector_init(struct drm_device *dev,
@@ -366,8 +507,7 @@ static struct drm_connector *vc4_vec_connector_init(struct drm_device *dev,
 
        drm_object_attach_property(&connector->base,
                                   dev->mode_config.tv_mode_property,
-                                  VC4_VEC_TV_MODE_NTSC);
-       vec->tv_mode = &vc4_vec_tv_modes[VC4_VEC_TV_MODE_NTSC];
+                                  vc4_vec_get_default_mode(connector));
 
        drm_connector_attach_encoder(connector, vec->encoder);
 
@@ -400,6 +540,7 @@ static void vc4_vec_encoder_enable(struct drm_encoder *encoder)
 {
        struct vc4_vec_encoder *vc4_vec_encoder = to_vc4_vec_encoder(encoder);
        struct vc4_vec *vec = vc4_vec_encoder->vec;
+       unsigned int tv_mode = vec->connector->state->tv.mode;
        int ret;
 
        ret = pm_runtime_get_sync(&vec->pdev->dev);
@@ -448,14 +589,25 @@ static void vc4_vec_encoder_enable(struct drm_encoder *encoder)
        VEC_WRITE(VEC_CLMP0_START, 0xac);
        VEC_WRITE(VEC_CLMP0_END, 0xec);
        VEC_WRITE(VEC_CONFIG2,
-                 VEC_CONFIG2_UV_DIG_DIS | VEC_CONFIG2_RGB_DIG_DIS);
+                 VEC_CONFIG2_UV_DIG_DIS |
+                 VEC_CONFIG2_RGB_DIG_DIS |
+                 ((encoder->crtc->state->adjusted_mode.flags &
+                   DRM_MODE_FLAG_INTERLACE) ? 0 : VEC_CONFIG2_PROG_SCAN));
        VEC_WRITE(VEC_CONFIG3, VEC_CONFIG3_HORIZ_LEN_STD);
        VEC_WRITE(VEC_DAC_CONFIG, vec->variant->dac_config);
 
        /* Mask all interrupts. */
        VEC_WRITE(VEC_MASK0, 0);
 
-       vec->tv_mode->mode_set(vec);
+       VEC_WRITE(VEC_CONFIG0, vc4_vec_tv_modes[tv_mode].config0);
+       VEC_WRITE(VEC_CONFIG1, vc4_vec_tv_modes[tv_mode].config1);
+       if (vc4_vec_tv_modes[tv_mode].custom_freq != 0) {
+               VEC_WRITE(VEC_FREQ3_2,
+                         (vc4_vec_tv_modes[tv_mode].custom_freq >> 16) &
+                         0xffff);
+               VEC_WRITE(VEC_FREQ1_0,
+                         vc4_vec_tv_modes[tv_mode].custom_freq & 0xffff);
+       }
 
        VEC_WRITE(VEC_DAC_MISC,
                  VEC_DAC_MISC_VID_ACT | VEC_DAC_MISC_DAC_RST_N);
@@ -463,34 +615,80 @@ static void vc4_vec_encoder_enable(struct drm_encoder *encoder)
 }
 
 
-static bool vc4_vec_encoder_mode_fixup(struct drm_encoder *encoder,
-                                      const struct drm_display_mode *mode,
-                                      struct drm_display_mode *adjusted_mode)
-{
-       return true;
-}
-
-static void vc4_vec_encoder_atomic_mode_set(struct drm_encoder *encoder,
-                                       struct drm_crtc_state *crtc_state,
-                                       struct drm_connector_state *conn_state)
-{
-       struct vc4_vec_encoder *vc4_vec_encoder = to_vc4_vec_encoder(encoder);
-       struct vc4_vec *vec = vc4_vec_encoder->vec;
-
-       vec->tv_mode = &vc4_vec_tv_modes[conn_state->tv.mode];
-}
-
 static int vc4_vec_encoder_atomic_check(struct drm_encoder *encoder,
                                        struct drm_crtc_state *crtc_state,
                                        struct drm_connector_state *conn_state)
 {
-       const struct vc4_vec_tv_mode *vec_mode;
-
-       vec_mode = &vc4_vec_tv_modes[conn_state->tv.mode];
+       const struct drm_display_mode *reference_mode =
+               vc4_vec_tv_modes[conn_state->tv.mode].interlaced_mode;
+
+       if (crtc_state->adjusted_mode.crtc_clock != reference_mode->clock ||
+           crtc_state->adjusted_mode.crtc_htotal != reference_mode->htotal ||
+           crtc_state->adjusted_mode.crtc_hdisplay % 4 != 0 ||
+           crtc_state->adjusted_mode.crtc_hsync_end -
+                   crtc_state->adjusted_mode.crtc_hsync_start < 1)
+               return -EINVAL;
 
-       if (conn_state->crtc &&
-           !drm_mode_equal(vec_mode->mode, &crtc_state->adjusted_mode))
+       switch (reference_mode->vtotal) {
+       case 525:
+               if (crtc_state->adjusted_mode.crtc_vdisplay < 1 ||
+                   crtc_state->adjusted_mode.crtc_vdisplay > 253 ||
+                   crtc_state->adjusted_mode.crtc_vsync_start -
+                           crtc_state->adjusted_mode.crtc_vdisplay < 1 ||
+                   crtc_state->adjusted_mode.crtc_vsync_end -
+                           crtc_state->adjusted_mode.crtc_vsync_start != 3 ||
+                   crtc_state->adjusted_mode.crtc_vtotal -
+                           crtc_state->adjusted_mode.crtc_vsync_end < 4 ||
+                   crtc_state->adjusted_mode.crtc_vtotal > 262)
+                       return -EINVAL;
+
+               if ((crtc_state->adjusted_mode.flags &
+                    DRM_MODE_FLAG_INTERLACE) &&
+                   (crtc_state->adjusted_mode.vdisplay % 2 != 0 ||
+                    crtc_state->adjusted_mode.vsync_start % 2 != 1 ||
+                    crtc_state->adjusted_mode.vsync_end % 2 != 1 ||
+                    crtc_state->adjusted_mode.vtotal % 2 != 1))
+                       return -EINVAL;
+
+               /* progressive mode is hard-wired to 262 total lines */
+               if (!(crtc_state->adjusted_mode.flags &
+                     DRM_MODE_FLAG_INTERLACE) &&
+                   crtc_state->adjusted_mode.crtc_vtotal != 262)
+                       return -EINVAL;
+
+               break;
+
+       case 625:
+               if (crtc_state->adjusted_mode.crtc_vdisplay < 1 ||
+                   crtc_state->adjusted_mode.crtc_vdisplay > 305 ||
+                   crtc_state->adjusted_mode.crtc_vsync_start -
+                           crtc_state->adjusted_mode.crtc_vdisplay < 1 ||
+                   crtc_state->adjusted_mode.crtc_vsync_end -
+                           crtc_state->adjusted_mode.crtc_vsync_start != 3 ||
+                   crtc_state->adjusted_mode.crtc_vtotal -
+                           crtc_state->adjusted_mode.crtc_vsync_end < 2 ||
+                   crtc_state->adjusted_mode.crtc_vtotal > 312)
+                       return -EINVAL;
+
+               if ((crtc_state->adjusted_mode.flags &
+                    DRM_MODE_FLAG_INTERLACE) &&
+                   (crtc_state->adjusted_mode.vdisplay % 2 != 0 ||
+                    crtc_state->adjusted_mode.vsync_start % 2 != 0 ||
+                    crtc_state->adjusted_mode.vsync_end % 2 != 0 ||
+                    crtc_state->adjusted_mode.vtotal % 2 != 1))
+                       return -EINVAL;
+
+               /* progressive mode is hard-wired to 312 total lines */
+               if (!(crtc_state->adjusted_mode.flags &
+                     DRM_MODE_FLAG_INTERLACE) &&
+                   crtc_state->adjusted_mode.crtc_vtotal != 312)
+                       return -EINVAL;
+
+               break;
+
+       default:
                return -EINVAL;
+       }
 
        return 0;
 }
@@ -498,9 +696,7 @@ static int vc4_vec_encoder_atomic_check(struct drm_encoder *encoder,
 static const struct drm_encoder_helper_funcs vc4_vec_encoder_helper_funcs = {
        .disable = vc4_vec_encoder_disable,
        .enable = vc4_vec_encoder_enable,
-       .mode_fixup = vc4_vec_encoder_mode_fixup,
        .atomic_check = vc4_vec_encoder_atomic_check,
-       .atomic_mode_set = vc4_vec_encoder_atomic_mode_set,
 };
 
 static const struct vc4_vec_variant bcm2835_vec_variant = {
@@ -521,13 +717,6 @@ static const struct of_device_id vc4_vec_dt_match[] = {
        { /* sentinel */ },
 };
 
-static const char * const tv_mode_names[] = {
-       [VC4_VEC_TV_MODE_NTSC] = "NTSC",
-       [VC4_VEC_TV_MODE_NTSC_J] = "NTSC-J",
-       [VC4_VEC_TV_MODE_PAL] = "PAL",
-       [VC4_VEC_TV_MODE_PAL_M] = "PAL-M",
-};
-
 static int vc4_vec_bind(struct device *dev, struct device *master, void *data)
 {
        struct platform_device *pdev = to_platform_device(dev);
@@ -636,3 +825,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 (file)
index 0000000..e8d2b4b
--- /dev/null
@@ -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,
+};
index 42b5b05..b6f8f84 100644 (file)
 #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
 
 #define USB_VENDOR_ID_XAT      0x2505
 #define USB_DEVICE_ID_XAT_CSR  0x0220
 
+#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
index 544d119..4b59759 100644 (file)
@@ -41,6 +41,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 },
@@ -195,6 +196,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 }
 };
index 2dcaf31..3541229 100644 (file)
@@ -45,7 +45,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");
 
@@ -1112,7 +1112,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:
@@ -1124,6 +1126,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)) {
index 51f1caa..09effe7 100644 (file)
@@ -1684,6 +1684,19 @@ config SENSORS_EMC2103
          This driver can also be built as a module. If so, the module
          will be called emc2103.
 
+config SENSORS_EMC2305
+       tristate "Microchip EMC2305 and compatible EMC2301/2/3"
+       depends on I2C
+       imply THERMAL
+       help
+         If you say yes here you get support for the Microchip EMC2305
+         fan controller chips.
+         The Microchip EMC2305 is a fan controller for up to 5 fans.
+         Fan rotation speeds are reported in RPM.
+
+         This driver can also be built as a module.  If so, the module
+         will be called emc2305.
+
 config SENSORS_EMC6W201
        tristate "SMSC EMC6W201"
        depends on I2C
index 1629402..62a3a76 100644 (file)
@@ -67,6 +67,7 @@ obj-$(CONFIG_SENSORS_DS620)   += ds620.o
 obj-$(CONFIG_SENSORS_DS1621)   += ds1621.o
 obj-$(CONFIG_SENSORS_EMC1403)  += emc1403.o
 obj-$(CONFIG_SENSORS_EMC2103)  += emc2103.o
+obj-$(CONFIG_SENSORS_EMC2305)  += emc2305.o
 obj-$(CONFIG_SENSORS_EMC6W201) += emc6w201.o
 obj-$(CONFIG_SENSORS_F71805F)  += f71805f.o
 obj-$(CONFIG_SENSORS_F71882FG) += f71882fg.o
diff --git a/drivers/hwmon/emc2305.c b/drivers/hwmon/emc2305.c
new file mode 100644 (file)
index 0000000..c78d672
--- /dev/null
@@ -0,0 +1,643 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Hardware monitoring driver for Microchip EMC2305 fan controller
+ *
+ * Copyright (C) 2022 Nvidia Technologies Ltd and Delta Networks, Inc.
+ */
+
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/thermal.h>
+#include <linux/version.h>
+
+static const unsigned short
+emc2305_normal_i2c[] = { 0x27, 0x2c, 0x2d, 0x2e, 0x2f, 0x4c, 0x4d, I2C_CLIENT_END };
+
+#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_VENDOR                 0x5d
+#define EMC2305_REG_PRODUCT_ID         0xfd
+#define EMC2305_TACH_REGS_UNUSE_BITS   3
+#define EMC2305_TACH_CNT_MULTIPLIER    1       /* Set by FAN_CFG RNGx bits */
+#define EMC2305_PWM_MAX                        5
+#define EMC2305_PWM_CHNL_CMN           0
+
+#define EMC2305_PWM_DUTY2STATE(duty, max_state, pwm_max) \
+       (DIV_ROUND_CLOSEST((duty) * (max_state), (pwm_max)))
+#define EMC2305_PWM_STATE2DUTY(state, max_state, pwm_max) \
+       (DIV_ROUND_CLOSEST((state) * (pwm_max), (max_state)))
+
+/* Factor by equations [2] and [3] from data sheet; valid for fans where the number of edges
+ * equal (poles * 2 + 1).
+ */
+#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))
+
+enum emc230x_product_id {
+       EMC2305 = 0x34,
+       EMC2303 = 0x35,
+       EMC2302 = 0x36,
+       EMC2301 = 0x37,
+};
+
+static const struct i2c_device_id emc2305_ids[] = {
+       { "emc2305", 0 },
+       { "emc2303", 0 },
+       { "emc2302", 0 },
+       { "emc2301", 0 },
+       { }
+};
+MODULE_DEVICE_TABLE(i2c, emc2305_ids);
+
+static const struct of_device_id emc2305_dt_ids[] = {
+       { .compatible = "microchip,emc2305" },
+       { .compatible = "microchip,emc2301" },
+       { }
+};
+MODULE_DEVICE_TABLE(of, emc2305_dt_ids);
+
+/**
+ * @cdev: cooling device;
+ * @curr_state: cooling current state;
+ * @last_hwmon_state: last cooling state updated by hwmon subsystem;
+ * @last_thermal_state: last cooling state updated by thermal subsystem;
+ *
+ * The 'last_hwmon_state' and 'last_thermal_state' fields are provided to support fan low limit
+ * speed feature. The purpose of this feature is to provides ability to limit fan speed
+ * according to some system wise considerations, like absence of some replaceable units (PSU or
+ * line cards), high system ambient temperature, unreliable transceivers temperature sensing or
+ * some other factors which indirectly impacts system's airflow
+ * Fan low limit feature is supported through 'hwmon' interface: 'hwmon' 'pwm' attribute is
+ * used for setting low limit for fan speed in case 'thermal' subsystem is configured in
+ * kernel. In this case setting fan speed through 'hwmon' will never let the 'thermal'
+ * subsystem to select a lower duty cycle than the duty cycle selected with the 'pwm'
+ * attribute.
+ * From other side, fan speed is to be updated in hardware through 'pwm' only in case the
+ * requested fan speed is above last speed set by 'thermal' subsystem, otherwise requested fan
+ * speed will be just stored with no PWM update.
+ */
+struct emc2305_cdev_data {
+       struct thermal_cooling_device *cdev;
+       unsigned int cur_state;
+       unsigned long last_hwmon_state;
+       unsigned long last_thermal_state;
+};
+
+/**
+ * @client: i2c client;
+ * @hwmon_dev: hwmon device;
+ * @max_state: maximum cooling state of the cooling device;
+ * @pwm_max: maximum PWM;
+ * @pwm_min: minimum PWM;
+ * @pwm_channel: maximum number of PWM channels;
+ * @cdev_data: array of cooling devices data;
+ */
+struct emc2305_data {
+       struct i2c_client *client;
+       struct device *hwmon_dev;
+       u8 max_state;
+       u8 pwm_max;
+       u8 pwm_min;
+       u8 pwm_num;
+       u8 pwm_channel;
+       struct emc2305_cdev_data cdev_data[EMC2305_PWM_MAX];
+};
+
+static char *emc2305_fan_name[] = {
+       "emc2305_fan",
+       "emc2305_fan1",
+       "emc2305_fan2",
+       "emc2305_fan3",
+       "emc2305_fan4",
+       "emc2305_fan5",
+};
+
+static int emc2305_get_max_channel(struct emc2305_data *data)
+{
+       if (data->pwm_channel == EMC2305_PWM_CHNL_CMN)
+               return data->pwm_num;
+       else
+               return data->pwm_channel;
+}
+
+static int emc2305_get_cdev_idx(struct thermal_cooling_device *cdev)
+{
+       struct emc2305_data *data = cdev->devdata;
+       size_t len = strlen(cdev->type);
+       int ret;
+
+       if (len <= 0)
+               return -EINVAL;
+
+       /* Retuns index of cooling device 0..4 in case of separate PWM setting.
+        * Zero index is used in case of one common PWM setting.
+        * If the mode is set as EMC2305_PWM_CHNL_CMN, all PWMs are to be bound
+        * to the common thermal zone and should work at the same speed
+        * to perform cooling for the same thermal junction.
+        * Otherwise, return specific channel that will be used in bound
+        * related PWM to the thermal zone.
+        */
+       if (data->pwm_channel == EMC2305_PWM_CHNL_CMN)
+               return 0;
+
+       ret = cdev->type[len - 1];
+       switch (ret) {
+       case '1' ... '5':
+               return ret - '1';
+       default:
+               break;
+       }
+       return -EINVAL;
+}
+
+static int emc2305_get_cur_state(struct thermal_cooling_device *cdev, unsigned long *state)
+{
+       int cdev_idx;
+       struct emc2305_data *data = cdev->devdata;
+
+       cdev_idx = emc2305_get_cdev_idx(cdev);
+       if (cdev_idx < 0)
+               return cdev_idx;
+
+       *state = data->cdev_data[cdev_idx].cur_state;
+       return 0;
+}
+
+static int emc2305_get_max_state(struct thermal_cooling_device *cdev, unsigned long *state)
+{
+       struct emc2305_data *data = cdev->devdata;
+       *state = data->max_state;
+       return 0;
+}
+
+static int emc2305_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state)
+{
+       int cdev_idx;
+       struct emc2305_data *data = cdev->devdata;
+       struct i2c_client *client = data->client;
+       u8 val, i;
+
+       if (state > data->max_state)
+               return -EINVAL;
+
+       cdev_idx =  emc2305_get_cdev_idx(cdev);
+       if (cdev_idx < 0)
+               return cdev_idx;
+
+       /* Save thermal state. */
+       data->cdev_data[cdev_idx].last_thermal_state = state;
+       state = max_t(unsigned long, state, data->cdev_data[cdev_idx].last_hwmon_state);
+
+       val = EMC2305_PWM_STATE2DUTY(state, data->max_state, data->pwm_max);
+
+       if (val > EMC2305_FAN_MAX)
+               return -EINVAL;
+
+       data->cdev_data[cdev_idx].cur_state = state;
+       if (data->pwm_channel == EMC2305_PWM_CHNL_CMN)
+       /* Set the same PWM value in all channels
+        * if common PWM channel is used.
+        */
+               for (i = 0; i < data->pwm_num; i++)
+                       i2c_smbus_write_byte_data(client, EMC2305_REG_FAN_DRIVE(i), val);
+       else
+               i2c_smbus_write_byte_data(client, EMC2305_REG_FAN_DRIVE(cdev_idx), val);
+
+       return 0;
+}
+
+static const struct thermal_cooling_device_ops emc2305_cooling_ops = {
+       .get_max_state = emc2305_get_max_state,
+       .get_cur_state = emc2305_get_cur_state,
+       .set_cur_state = emc2305_set_cur_state,
+};
+
+static int emc2305_show_fault(struct device *dev, int channel)
+{
+       struct emc2305_data *data = dev_get_drvdata(dev);
+       struct i2c_client *client = data->client;
+       int status_reg;
+
+       status_reg = i2c_smbus_read_byte_data(client, EMC2305_REG_DRIVE_FAIL_STATUS);
+       return status_reg & (1 << channel) ? 1 : 0;
+}
+
+static int emc2305_show_fan(struct device *dev, int channel)
+{
+       struct emc2305_data *data = dev_get_drvdata(dev);
+       struct i2c_client *client = data->client;
+       int ret;
+
+       ret = i2c_smbus_read_word_swapped(client, EMC2305_REG_FAN_TACH(channel));
+       if (ret < 0)
+               return ret;
+
+       ret = ret >> EMC2305_TACH_REGS_UNUSE_BITS;
+       return EMC2305_RPM_FACTOR * EMC2305_TACH_CNT_MULTIPLIER / (ret > 0 ? ret : 1);
+}
+
+static int emc2305_show_pwm(struct device *dev, int channel)
+{
+       struct emc2305_data *data = dev_get_drvdata(dev);
+       struct i2c_client *client = data->client;
+
+       return i2c_smbus_read_byte_data(client, EMC2305_REG_FAN_DRIVE(channel));
+}
+
+static int emc2305_set_pwm(struct device *dev, long val, int channel)
+{
+       struct emc2305_data *data = dev_get_drvdata(dev);
+       struct i2c_client *client = data->client;
+
+       if (val < data->pwm_min || val > data->pwm_max)
+               return -EINVAL;
+
+       i2c_smbus_write_byte_data(client, EMC2305_REG_FAN_DRIVE(channel), val);
+       data->cdev_data[channel].cur_state = EMC2305_PWM_DUTY2STATE(val, data->max_state,
+                                                                   data->pwm_max);
+       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;
+
+       /* OF parameters are optional - overwrite default setting
+        * if some of them are provided.
+        */
+
+       if (of_find_property(np, "emc2305,cooling-levels", NULL)) {
+               ret = of_property_read_u8(np, "emc2305,cooling-levels", &data->max_state);
+               if (ret)
+                       return ret;
+       }
+
+       if (of_find_property(np, "emc2305,pwm-max", NULL)) {
+               ret = of_property_read_u8(np, "emc2305,pwm-max", &data->pwm_max);
+               if (ret)
+                       return ret;
+       }
+
+       if (of_find_property(np, "emc2305,pwm-min", NULL)) {
+               ret = of_property_read_u8(np, "emc2305,pwm-min", &data->pwm_min);
+               if (ret)
+                       return ret;
+       }
+
+       /* Not defined or 0 means one thermal zone over all cooling devices.
+        * Otherwise - separated thermal zones for each PWM channel.
+        */
+       if (of_find_property(np, "emc2305,pwm-channel", NULL)) {
+               ret = of_property_read_u8(np, "emc2305,pwm-channel", &data->pwm_channel);
+               if (ret)
+                       return ret;
+       }
+
+       return ret;
+}
+
+static int emc2305_set_single_tz(struct device *dev, int idx)
+{
+       struct emc2305_data *data = dev_get_drvdata(dev);
+       long pwm = data->pwm_max;
+       int cdev_idx;
+
+       cdev_idx = (idx) ? idx - 1 : 0;
+
+       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]);
+               return PTR_ERR(data->cdev_data[cdev_idx].cdev);
+       }
+       emc2305_set_pwm(dev, pwm, cdev_idx);
+       data->cdev_data[cdev_idx].cur_state = data->max_state;
+       /* Set minimal PWM speed. */
+       data->cdev_data[cdev_idx].last_hwmon_state = EMC2305_PWM_DUTY2STATE(data->pwm_min,
+                                                                           data->max_state,
+                                                                           data->pwm_max);
+       return 0;
+}
+
+static void emc2305_unset_tz(struct device *dev)
+{
+       struct emc2305_data *data = dev_get_drvdata(dev);
+       int i;
+
+       /* Unregister cooling device in case they have been registered by
+        * thermal_cooling_device_unregister(). No need for clean-up flow in case they
+        * have been registered by devm_thermal_of_cooling_device_register()
+        */
+       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 int emc2305_set_tz(struct device *dev)
+{
+       struct emc2305_data *data = dev_get_drvdata(dev);
+       int i, ret;
+
+       if (data->pwm_channel == EMC2305_PWM_CHNL_CMN)
+               return emc2305_set_single_tz(dev, 0);
+
+       for (i = 0; i < data->pwm_channel; i++) {
+               ret = emc2305_set_single_tz(dev, i + 1);
+               if (ret)
+                       goto thermal_cooling_device_register_fail;
+       }
+       return 0;
+
+thermal_cooling_device_register_fail:
+       emc2305_unset_tz(dev);
+       return ret;
+}
+
+static umode_t
+emc2305_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr, int channel)
+{
+       int max_channel = emc2305_get_max_channel((struct emc2305_data *)data);
+
+       /* Don't show channels which are not physically connected. */
+       if ((channel + 1) > max_channel)
+               return 0;
+       switch (type) {
+       case hwmon_fan:
+               switch (attr) {
+               case hwmon_fan_input:
+                       return 0444;
+               case hwmon_fan_fault:
+                       return 0444;
+               default:
+                       break;
+               }
+               break;
+       case hwmon_pwm:
+               switch (attr) {
+               case hwmon_pwm_input:
+                       return 0644;
+               default:
+                       break;
+               }
+               break;
+       default:
+               break;
+       }
+
+       return 0;
+};
+
+static int
+emc2305_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long val)
+{
+       struct emc2305_data *data = dev_get_drvdata(dev);
+
+       switch (type) {
+       case hwmon_pwm:
+               switch (attr) {
+               case hwmon_pwm_input:
+                       /* If thermal is configured - handle PWM limit setting. */
+                       if (IS_REACHABLE(CONFIG_THERMAL)) {
+                               data->cdev_data[channel].last_hwmon_state =
+                                       EMC2305_PWM_DUTY2STATE(val, data->max_state, data->pwm_max);
+                               /* Update PWM only in case requested state is not less than the
+                                * last thermal state.
+                                */
+                               if (data->cdev_data[channel].last_hwmon_state >=
+                                   data->cdev_data[channel].last_thermal_state)
+                                       return emc2305_set_cur_state(data->cdev_data[channel].cdev,
+                                                       data->cdev_data[channel].last_hwmon_state);
+                               return 0;
+                       }
+                       return emc2305_set_pwm(dev, val, channel);
+               default:
+                       break;
+               }
+               break;
+       default:
+               break;
+       }
+
+       return -EOPNOTSUPP;
+};
+
+static int
+emc2305_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val)
+{
+       int ret;
+
+       switch (type) {
+       case hwmon_fan:
+               switch (attr) {
+               case hwmon_fan_input:
+                       ret = emc2305_show_fan(dev, channel);
+                       if (ret < 0)
+                               return ret;
+                       *val = ret;
+                       return 0;
+               case hwmon_fan_fault:
+                       ret = emc2305_show_fault(dev, channel);
+                       if (ret < 0)
+                               return ret;
+                       *val = ret;
+                       return 0;
+               default:
+                       break;
+               }
+               break;
+       case hwmon_pwm:
+               switch (attr) {
+               case hwmon_pwm_input:
+                       ret = emc2305_show_pwm(dev, channel);
+                       if (ret < 0)
+                               return ret;
+                       *val = ret;
+                       return 0;
+               default:
+                       break;
+               }
+               break;
+       default:
+               break;
+       }
+
+       return -EOPNOTSUPP;
+};
+
+static const struct hwmon_ops emc2305_ops = {
+       .is_visible = emc2305_is_visible,
+       .read = emc2305_read,
+       .write = emc2305_write,
+};
+
+static const struct hwmon_channel_info *emc2305_info[] = {
+       HWMON_CHANNEL_INFO(fan,
+                          HWMON_F_INPUT | HWMON_F_FAULT,
+                          HWMON_F_INPUT | HWMON_F_FAULT,
+                          HWMON_F_INPUT | HWMON_F_FAULT,
+                          HWMON_F_INPUT | HWMON_F_FAULT,
+                          HWMON_F_INPUT | HWMON_F_FAULT),
+       HWMON_CHANNEL_INFO(pwm,
+                          HWMON_PWM_INPUT,
+                          HWMON_PWM_INPUT,
+                          HWMON_PWM_INPUT,
+                          HWMON_PWM_INPUT,
+                          HWMON_PWM_INPUT),
+       NULL
+};
+
+static const struct hwmon_chip_info emc2305_chip_info = {
+       .ops = &emc2305_ops,
+       .info = emc2305_info,
+};
+
+static int emc2305_identify(struct device *dev)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct emc2305_data *data = i2c_get_clientdata(client);
+       int ret;
+
+       ret = i2c_smbus_read_byte_data(client, EMC2305_REG_PRODUCT_ID);
+       if (ret < 0)
+               return ret;
+
+       switch (ret) {
+       case EMC2305:
+               data->pwm_num = 5;
+               break;
+       case EMC2303:
+               data->pwm_num = 3;
+               break;
+       case EMC2302:
+               data->pwm_num = 2;
+               break;
+       case EMC2301:
+               data->pwm_num = 1;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int emc2305_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+       struct i2c_adapter *adapter = client->adapter;
+       struct device *dev = &client->dev;
+       struct emc2305_data *data;
+       int vendor;
+       int ret;
+       int i;
+
+       if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA))
+               return -ENODEV;
+
+       vendor = i2c_smbus_read_byte_data(client, EMC2305_REG_VENDOR);
+       if (vendor != EMC2305_VENDOR)
+               return -ENODEV;
+
+       data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+       if (!data)
+               return -ENOMEM;
+
+       i2c_set_clientdata(client, data);
+       data->client = client;
+
+       ret = emc2305_identify(dev);
+       if (ret)
+               return ret;
+
+       data->max_state = EMC2305_FAN_MAX_STATE;
+       data->pwm_max = EMC2305_FAN_MAX;
+       data->pwm_min = EMC2305_FAN_MIN;
+       data->pwm_channel = EMC2305_PWM_CHNL_CMN;
+       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,
+                                                              &emc2305_chip_info, NULL);
+       if (IS_ERR(data->hwmon_dev))
+               return PTR_ERR(data->hwmon_dev);
+
+       if (IS_REACHABLE(CONFIG_THERMAL)) {
+               ret = emc2305_set_tz(dev);
+               if (ret != 0)
+                       return ret;
+       }
+
+       for (i = 0; i < data->pwm_num; i++) {
+               u8 val;
+
+               i2c_smbus_write_byte_data(client, EMC2305_REG_FAN_MIN_DRIVE(i),
+                                         data->pwm_min);
+
+               val = i2c_smbus_read_byte_data(client, EMC2305_REG_FAN_CFG(i));
+               /* Set RNGx so minRPM=500 */
+               val &= ~0x60;
+               i2c_smbus_write_byte_data(client, EMC2305_REG_FAN_CFG(i), val);
+       }
+
+       /* 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;
+}
+
+static int emc2305_remove(struct i2c_client *client)
+{
+       struct device *dev = &client->dev;
+
+       if (IS_REACHABLE(CONFIG_THERMAL))
+               emc2305_unset_tz(dev);
+       return 0;
+}
+
+static struct i2c_driver emc2305_driver = {
+       .class  = I2C_CLASS_HWMON,
+       .driver = {
+               .name = "emc2305",
+               .of_match_table = emc2305_dt_ids,
+       },
+       .probe    = emc2305_probe,
+       .remove   = emc2305_remove,
+       .id_table = emc2305_ids,
+       .address_list = emc2305_normal_i2c,
+};
+
+module_i2c_driver(emc2305_driver);
+
+MODULE_AUTHOR("Nvidia");
+MODULE_DESCRIPTION("Microchip EMC2305 fan controller driver");
+MODULE_LICENSE("GPL");
index fea4034..9bebb32 100644 (file)
@@ -9,6 +9,25 @@ menu "I2C Hardware Bus support"
 comment "PC SMBus host controller drivers"
        depends on PCI
 
+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
index 1336b04..95fe417 100644 (file)
@@ -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 (file)
index 0000000..962f2e5
--- /dev/null
@@ -0,0 +1,512 @@
+/*
+ * 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 | I2C_CLASS_DDC;
+       adap->algo = &bcm2708_i2c_algorithm;
+       adap->algo_data = bi;
+       adap->dev.parent = &pdev->dev;
+       adap->nr = pdev->id;
+       strlcpy(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_DDC;
+               break;
+       case 2:
+               adap->class = I2C_CLASS_DDC;
+               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 int 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);
+
+       return 0;
+}
+
+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);
index f72c657..7408e2c 100644 (file)
 #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,6 +382,7 @@ 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) {
@@ -349,6 +449,13 @@ static int bcm2835_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[],
        unsigned long time_left;
        int 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,
@@ -367,6 +474,10 @@ static int bcm2835_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[],
 
        bcm2835_i2c_finish_transfer(i2c_dev);
 
+       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);
@@ -377,7 +488,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;
index 7a048ab..08ad370 100644 (file)
@@ -445,7 +445,9 @@ static int i2c_gpio_probe(struct platform_device *pdev)
        adap->dev.parent = dev;
        adap->dev.of_node = np;
 
-       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;
index 3b23078..4836531 100644 (file)
@@ -399,4 +399,12 @@ config JOYSTICK_N64
          Say Y here if you want enable support for the four
          built-in controller ports on the Nintendo 64 console.
 
+config JOYSTICK_RPISENSE
+       tristate "Raspberry Pi Sense HAT joystick"
+       depends on GPIOLIB && INPUT
+       select MFD_RPISENSE_CORE
+
+       help
+         This is the joystick driver for the Raspberry Pi Sense HAT
+
 endif
index 5174b8a..d401dac 100644 (file)
@@ -39,3 +39,4 @@ obj-$(CONFIG_JOYSTICK_WARRIOR)                += warrior.o
 obj-$(CONFIG_JOYSTICK_WALKERA0701)     += walkera0701.o
 obj-$(CONFIG_JOYSTICK_XPAD)            += xpad.o
 obj-$(CONFIG_JOYSTICK_ZHENHUA)         += zhenhua.o
+obj-$(CONFIG_JOYSTICK_RPISENSE)                += rpisense-js.o
diff --git a/drivers/input/joystick/rpisense-js.c b/drivers/input/joystick/rpisense-js.c
new file mode 100644 (file)
index 0000000..6a41676
--- /dev/null
@@ -0,0 +1,153 @@
+/*
+ * Raspberry Pi Sense HAT joystick 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/mfd/rpisense/joystick.h>
+#include <linux/mfd/rpisense/core.h>
+
+static struct rpisense *rpisense;
+static unsigned char keymap[5] = {KEY_DOWN, KEY_RIGHT, KEY_UP, KEY_ENTER, KEY_LEFT,};
+
+static void keys_work_fn(struct work_struct *work)
+{
+       int i;
+       static s32 prev_keys;
+       struct rpisense_js *rpisense_js = &rpisense->joystick;
+       s32 keys = rpisense_reg_read(rpisense, RPISENSE_KEYS);
+       s32 changes = keys ^ prev_keys;
+
+       prev_keys = keys;
+       for (i = 0; i < 5; i++) {
+               if (changes & 1) {
+                       input_report_key(rpisense_js->keys_dev,
+                                        keymap[i], keys & 1);
+               }
+               changes >>= 1;
+               keys >>= 1;
+       }
+       input_sync(rpisense_js->keys_dev);
+}
+
+static irqreturn_t keys_irq_handler(int irq, void *pdev)
+{
+       struct rpisense_js *rpisense_js = &rpisense->joystick;
+
+       schedule_work(&rpisense_js->keys_work_s);
+       return IRQ_HANDLED;
+}
+
+static int rpisense_js_probe(struct platform_device *pdev)
+{
+       int ret;
+       int i;
+       struct rpisense_js *rpisense_js;
+
+       rpisense = rpisense_get_dev();
+       rpisense_js = &rpisense->joystick;
+
+       INIT_WORK(&rpisense_js->keys_work_s, keys_work_fn);
+
+       rpisense_js->keys_dev = input_allocate_device();
+       if (!rpisense_js->keys_dev) {
+               dev_err(&pdev->dev, "Could not allocate input device.\n");
+               return -ENOMEM;
+       }
+
+       rpisense_js->keys_dev->evbit[0] = BIT_MASK(EV_KEY);
+       for (i = 0; i < ARRAY_SIZE(keymap); i++) {
+               set_bit(keymap[i],
+                       rpisense_js->keys_dev->keybit);
+       }
+
+       rpisense_js->keys_dev->name = "Raspberry Pi Sense HAT Joystick";
+       rpisense_js->keys_dev->phys = "rpi-sense-joy/input0";
+       rpisense_js->keys_dev->id.bustype = BUS_I2C;
+       rpisense_js->keys_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
+       rpisense_js->keys_dev->keycode = keymap;
+       rpisense_js->keys_dev->keycodesize = sizeof(unsigned char);
+       rpisense_js->keys_dev->keycodemax = ARRAY_SIZE(keymap);
+
+       ret = input_register_device(rpisense_js->keys_dev);
+       if (ret) {
+               dev_err(&pdev->dev, "Could not register input device.\n");
+               goto err_keys_alloc;
+       }
+
+       ret = gpiod_direction_input(rpisense_js->keys_desc);
+       if (ret) {
+               dev_err(&pdev->dev, "Could not set keys-int direction.\n");
+               goto err_keys_reg;
+       }
+
+       rpisense_js->keys_irq = gpiod_to_irq(rpisense_js->keys_desc);
+       if (rpisense_js->keys_irq < 0) {
+               dev_err(&pdev->dev, "Could not determine keys-int IRQ.\n");
+               ret = rpisense_js->keys_irq;
+               goto err_keys_reg;
+       }
+
+       ret = devm_request_irq(&pdev->dev, rpisense_js->keys_irq,
+                              keys_irq_handler, IRQF_TRIGGER_RISING,
+                              "keys", &pdev->dev);
+       if (ret) {
+               dev_err(&pdev->dev, "IRQ request failed.\n");
+               goto err_keys_reg;
+       }
+       return 0;
+err_keys_reg:
+       input_unregister_device(rpisense_js->keys_dev);
+err_keys_alloc:
+       input_free_device(rpisense_js->keys_dev);
+       return ret;
+}
+
+static int rpisense_js_remove(struct platform_device *pdev)
+{
+       struct rpisense_js *rpisense_js = &rpisense->joystick;
+
+       input_unregister_device(rpisense_js->keys_dev);
+       input_free_device(rpisense_js->keys_dev);
+       return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id rpisense_js_id[] = {
+       { .compatible = "rpi,rpi-sense-js" },
+       { },
+};
+MODULE_DEVICE_TABLE(of, rpisense_js_id);
+#endif
+
+static struct platform_device_id rpisense_js_device_id[] = {
+       { .name = "rpi-sense-js" },
+       { },
+};
+MODULE_DEVICE_TABLE(platform, rpisense_js_device_id);
+
+static struct platform_driver rpisense_js_driver = {
+       .probe = rpisense_js_probe,
+       .remove = rpisense_js_remove,
+       .driver = {
+               .name = "rpi-sense-js",
+               .owner = THIS_MODULE,
+       },
+};
+
+module_platform_driver(rpisense_js_driver);
+
+MODULE_DESCRIPTION("Raspberry Pi Sense HAT joystick driver");
+MODULE_AUTHOR("Serge Schneider <serge@raspberrypi.org>");
+MODULE_LICENSE("GPL");
index bb2e1cb..719a5ad 100644 (file)
@@ -72,6 +72,8 @@
 #define EDT_DEFAULT_NUM_X              1024
 #define EDT_DEFAULT_NUM_Y              1024
 
+#define POLL_INTERVAL_MS               17      /* 17ms = 60fps */
+
 enum edt_pmode {
        EDT_PMODE_NOT_SUPPORTED,
        EDT_PMODE_HIBERNATE,
@@ -125,11 +127,15 @@ struct edt_ft5x06_ts_data {
        int offset_y;
        int report_rate;
        int max_support_points;
+       unsigned int known_ids;
 
        char name[EDT_NAME_LEN];
 
        struct edt_reg_addr reg_addr;
        enum edt_ver version;
+
+       struct timer_list timer;
+       struct work_struct work_i2c_poll;
 };
 
 struct edt_i2c_chip_data {
@@ -196,6 +202,10 @@ static irqreturn_t edt_ft5x06_ts_isr(int irq, void *dev_id)
        int i, type, x, y, id;
        int offset, tplen, datalen, crclen;
        int error;
+       unsigned int active_ids = 0, known_ids = tsdata->known_ids;
+       long released_ids;
+       int b = 0;
+       unsigned int num_points;
 
        switch (tsdata->version) {
        case EDT_M06:
@@ -243,9 +253,15 @@ static irqreturn_t edt_ft5x06_ts_isr(int irq, void *dev_id)
 
                if (!edt_ft5x06_ts_check_crc(tsdata, rdbuf, datalen))
                        goto out;
+               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);
        }
 
-       for (i = 0; i < tsdata->max_support_points; i++) {
+       for (i = 0; i < num_points; i++) {
                u8 *buf = &rdbuf[i * tplen + offset];
 
                type = buf[0] >> 6;
@@ -267,10 +283,25 @@ 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);
@@ -279,6 +310,22 @@ out:
        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);
+}
+
 static int edt_ft5x06_register_write(struct edt_ft5x06_ts_data *tsdata,
                                     u8 addr, u8 value)
 {
@@ -1234,17 +1281,27 @@ static int edt_ft5x06_ts_probe(struct i2c_client *client,
 
        i2c_set_clientdata(client, tsdata);
 
-       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 {
+               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(POLL_INTERVAL_MS);
+               add_timer(&tsdata->timer);
        }
 
        error = devm_device_add_group(&client->dev, &edt_ft5x06_attr_group);
@@ -1270,6 +1327,10 @@ static int 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);
 
        return 0;
index adc1556..7ed1b1f 100644 (file)
 #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)
 
 #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 +91,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,20 +99,76 @@ 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
+void bcm2836_arm_irqchip_spin_gpu_irq(void);
+
+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
+       .irq_unmask = armctrl_unmask_irq,
+#ifdef CONFIG_ARM64
+       .irq_ack    = armctrl_ack_irq
+#endif
 };
 
 static int armctrl_xlate(struct irq_domain *d, struct device_node *ctrlr,
@@ -135,15 +201,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);
 
@@ -174,6 +241,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);
 
@@ -186,6 +255,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;
 }
 
index 501facd..f9da3f2 100644 (file)
@@ -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;
 }
@@ -323,6 +347,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,
index 092eb59..9e53270 100644 (file)
@@ -47,8 +47,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);
@@ -62,6 +69,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)
 {
@@ -90,6 +104,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)
index 1f1d572..9fbdf5f 100644 (file)
@@ -114,6 +114,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
@@ -153,4 +160,15 @@ config LEDS_TRIGGER_TTY
 
          When build as a module this driver will be called ledtrig-tty.
 
+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
index 25c4db9..8bcb576 100644 (file)
@@ -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_AUDIO)       += ledtrig-audio.o
 obj-$(CONFIG_LEDS_TRIGGER_TTY)         += ledtrig-tty.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 (file)
index 0000000..1a52107
--- /dev/null
@@ -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 (file)
index 0000000..8a974a3
--- /dev/null
@@ -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");
index 86b7ce3..f0c2ad3 100644 (file)
 #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;
@@ -145,7 +148,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, 0, dev_name(dev), mbox);
        if (ret) {
                dev_err(dev, "Failed to register a mailbox IRQ handler: %d\n",
@@ -194,7 +197,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");
index 033b0c8..8f06b89 100644 (file)
@@ -2144,12 +2144,12 @@ static int __find_plane_by_offset(struct vb2_queue *q, unsigned long off,
        return -EINVAL;
 }
 
-int vb2_core_expbuf(struct vb2_queue *q, int *fd, unsigned int type,
-               unsigned int index, unsigned int plane, unsigned int flags)
+int vb2_core_expbuf_dmabuf(struct vb2_queue *q, unsigned int type,
+                          unsigned int index, unsigned int plane,
+                          unsigned int flags, struct dma_buf **dmabuf)
 {
        struct vb2_buffer *vb = NULL;
        struct vb2_plane *vb_plane;
-       int ret;
        struct dma_buf *dbuf;
 
        if (q->memory != VB2_MEMORY_MMAP) {
@@ -2201,6 +2201,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,
+                   unsigned int index, unsigned int plane, unsigned int flags)
+{
+       struct dma_buf *dbuf;
+       int ret;
+
+       ret = vb2_core_expbuf_dmabuf(q, type, index, 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",
index 6157e73..d14dd06 100644 (file)
@@ -730,6 +730,28 @@ config VIDEO_APTINA_PLL
 config VIDEO_CCS_PLL
        tristate
 
+config VIDEO_ARDUCAM_64MP
+       tristate "Arducam 64MP sensor support"
+       depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
+       depends on MEDIA_CAMERA_SUPPORT
+       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_V4L2 && VIDEO_V4L2_SUBDEV_API
+       depends on MEDIA_CAMERA_SUPPORT
+       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"
        depends on I2C && VIDEO_V4L2
@@ -817,6 +839,29 @@ config VIDEO_IMX290
          To compile this driver as a module, choose M here: the
          module will be called imx290.
 
+config VIDEO_IMX296
+       tristate "Sony IMX296 sensor support"
+       depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
+       depends on MEDIA_CAMERA_SUPPORT
+       select V4L2_FWNODE
+       help
+         This is a Video4Linux2 sensor driver for the Sony
+         IMX296 camera.
+
+         To compile this driver as a module, choose M here: the
+         module will be called imx296.
+
+config VIDEO_IMX477
+       tristate "Sony IMX477 sensor support"
+       depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
+       depends on MEDIA_CAMERA_SUPPORT
+       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_IMX319
        tristate "Sony IMX319 sensor support"
        depends on I2C && VIDEO_V4L2
@@ -883,6 +928,17 @@ config VIDEO_IMX412
          To compile this driver as a module, choose M here: the
          module will be called imx412.
 
+config VIDEO_IMX519
+       tristate "Arducam IMX519 sensor support"
+       depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
+       depends on MEDIA_CAMERA_SUPPORT
+       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_OV02A10
        tristate "OmniVision OV02A10 sensor support"
        depends on VIDEO_V4L2 && I2C
@@ -896,6 +952,17 @@ config VIDEO_OV02A10
          To compile this driver as a module, choose M here: the
          module will be called ov02a10.
 
+config VIDEO_OV2311
+       tristate "OmniVision OV2311 sensor support"
+       depends on I2C && VIDEO_V4L2
+       depends on MEDIA_CAMERA_SUPPORT
+       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"
        depends on VIDEO_V4L2 && I2C
@@ -1153,6 +1220,17 @@ config VIDEO_OV9640
          This is a Video4Linux2 sensor driver for the OmniVision
          OV9640 camera sensor.
 
+config VIDEO_OV9281
+       tristate "OmniVision OV9281 sensor support"
+       depends on I2C && VIDEO_V4L2
+       depends on MEDIA_CAMERA_SUPPORT
+       help
+         This is a Video4Linux2 sensor-level driver for the OmniVision
+         OV9281 camera.
+
+         To compile this driver as a module, choose M here: the
+         module will be called ov9281.
+
 config VIDEO_OV9650
        tristate "OmniVision OV9650/OV9652 sensor support"
        depends on I2C && VIDEO_V4L2
@@ -1187,6 +1265,18 @@ config VIDEO_OV13858
          This is a Video4Linux2 sensor driver for the OmniVision
          OV13858 camera.
 
+config VIDEO_IRS1125
+       tristate "Infineon IRS1125 sensor support"
+       depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
+       depends on MEDIA_CAMERA_SUPPORT
+       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_VS6624
        tristate "ST VS6624 sensor support"
        depends on VIDEO_V4L2 && I2C
@@ -1395,6 +1485,13 @@ endmenu
 menu "Lens drivers"
        visible if MEDIA_CAMERA_SUPPORT
 
+config VIDEO_AD5398
+       tristate "AD5398 lens voice coil support"
+       depends on GPIOLIB && I2C && VIDEO_V4L2
+       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_V4L2
index 83268f2..253f330 100644 (file)
@@ -7,6 +7,8 @@ obj-$(CONFIG_VIDEO_ET8EK8)      += et8ek8/
 obj-$(CONFIG_VIDEO_CX25840) += cx25840/
 obj-$(CONFIG_VIDEO_M5MOLS)     += m5mols/
 
+obj-$(CONFIG_VIDEO_ARDUCAM_64MP) += arducam_64mp.o
+obj-$(CONFIG_VIDEO_ARDUCAM_PIVARIETY) += arducam-pivariety.o
 obj-$(CONFIG_VIDEO_APTINA_PLL) += aptina-pll.o
 obj-$(CONFIG_VIDEO_TVAUDIO) += tvaudio.o
 obj-$(CONFIG_VIDEO_TDA7432) += tda7432.o
@@ -21,6 +23,7 @@ obj-$(CONFIG_VIDEO_SAA717X) += saa717x.o
 obj-$(CONFIG_VIDEO_SAA7127) += saa7127.o
 obj-$(CONFIG_VIDEO_SAA7185) += saa7185.o
 obj-$(CONFIG_VIDEO_SAA6752HS) += saa6752hs.o
+obj-$(CONFIG_VIDEO_AD5398)  += ad5398_vcm.o
 obj-$(CONFIG_VIDEO_AD5820)  += ad5820.o
 obj-$(CONFIG_VIDEO_AK7375)  += ak7375.o
 obj-$(CONFIG_VIDEO_DW9714)  += dw9714.o
@@ -65,6 +68,7 @@ obj-$(CONFIG_VIDEO_SONY_BTF_MPX) += sony-btf-mpx.o
 obj-$(CONFIG_VIDEO_UPD64031A) += upd64031a.o
 obj-$(CONFIG_VIDEO_UPD64083) += upd64083.o
 obj-$(CONFIG_VIDEO_OV02A10) += ov02a10.o
+obj-$(CONFIG_VIDEO_OV2311) += ov2311.o
 obj-$(CONFIG_VIDEO_OV2640) += ov2640.o
 obj-$(CONFIG_VIDEO_OV2680) += ov2680.o
 obj-$(CONFIG_VIDEO_OV2685) += ov2685.o
@@ -84,11 +88,13 @@ obj-$(CONFIG_VIDEO_OV772X) += ov772x.o
 obj-$(CONFIG_VIDEO_OV7740) += ov7740.o
 obj-$(CONFIG_VIDEO_OV8856) += ov8856.o
 obj-$(CONFIG_VIDEO_OV8865) += ov8865.o
+obj-$(CONFIG_VIDEO_OV9281) += ov9281.o
 obj-$(CONFIG_VIDEO_OV9282) += ov9282.o
 obj-$(CONFIG_VIDEO_OV9640) += ov9640.o
 obj-$(CONFIG_VIDEO_OV9650) += ov9650.o
 obj-$(CONFIG_VIDEO_OV9734) += ov9734.o
 obj-$(CONFIG_VIDEO_OV13858) += ov13858.o
+obj-$(CONFIG_VIDEO_IRS1125) += irs1125.o
 obj-$(CONFIG_VIDEO_MT9M001) += mt9m001.o
 obj-$(CONFIG_VIDEO_MT9M032) += mt9m032.o
 obj-$(CONFIG_VIDEO_MT9M111) += mt9m111.o
@@ -123,11 +129,14 @@ obj-$(CONFIG_VIDEO_IMX219)        += imx219.o
 obj-$(CONFIG_VIDEO_IMX258)     += imx258.o
 obj-$(CONFIG_VIDEO_IMX274)     += imx274.o
 obj-$(CONFIG_VIDEO_IMX290)     += imx290.o
+obj-$(CONFIG_VIDEO_IMX296)     += imx296.o
+obj-$(CONFIG_VIDEO_IMX477)     += imx477.o
 obj-$(CONFIG_VIDEO_IMX319)     += imx319.o
 obj-$(CONFIG_VIDEO_IMX334)     += imx334.o
 obj-$(CONFIG_VIDEO_IMX335)     += imx335.o
 obj-$(CONFIG_VIDEO_IMX355)     += imx355.o
 obj-$(CONFIG_VIDEO_IMX412)     += imx412.o
+obj-$(CONFIG_VIDEO_IMX519)     += imx519.o
 obj-$(CONFIG_VIDEO_MAX9286)    += max9286.o
 obj-$(CONFIG_VIDEO_MAX9271_LIB)        += max9271.o
 obj-$(CONFIG_VIDEO_RDACM20)    += rdacm20.o
diff --git a/drivers/media/i2c/ad5398_vcm.c b/drivers/media/i2c/ad5398_vcm.c
new file mode 100644 (file)
index 0000000..102c314
--- /dev/null
@@ -0,0 +1,342 @@
+// 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,
+                       const struct i2c_device_id *devid)
+{
+       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 int 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);
+       return 0;
+}
+
+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");
index d9a99fc..725d52f 100644 (file)
@@ -1280,6 +1280,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),
@@ -1291,6 +1292,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);
 
@@ -1338,6 +1340,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 (file)
index 0000000..3d2af2d
--- /dev/null
@@ -0,0 +1,1469 @@
+// 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_fwnode_bus_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 *)&reg_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_get_try_format(sd, 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_get_try_crop(&pivariety->sd, 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);
+       const u32 mask = V4L2_MBUS_CSI2_LANE_MASK;
+
+       if (pivariety->lanes > pivariety->bus.num_data_lanes)
+               return -EINVAL;
+
+       cfg->type = V4L2_MBUS_CSI2_DPHY;
+       cfg->flags = (pivariety->lanes << __ffs(mask)) & mask;
+
+       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";
+       default:
+               return NULL;
+       }
+}
+
+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;
+
+       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:
+       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,
+                          const struct i2c_device_id *id)
+{
+       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 int 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);
+
+       return 0;
+}
+
+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 (file)
index 0000000..b2b8cbc
--- /dev/null
@@ -0,0 +1,107 @@
+/* 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)
+
+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 (file)
index 0000000..42b777b
--- /dev/null
@@ -0,0 +1,2469 @@
+// 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 <asm/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 16384
+#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 struct arducam_64mp_reg mode_common_regs[] = {
+       {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},
+};
+
+/* 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},
+};
+
+/* 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},
+};
+
+/* 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},
+};
+
+/* 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, 0x00},
+       {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, 0x01},
+       {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},
+};
+
+/* 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},
+};
+
+/* 720p 120fps mode */
+static const struct arducam_64mp_reg mode_1280x720_regs[] = {
+       {0x0342, 0x1d},
+       {0x0343, 0xc4},
+       {0x0340, 0x03},
+       {0x0341, 0xd8},
+       {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},
+};
+
+/* 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 = 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 = 0x1dc4,
+               .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_get_try_format(sd, fh->state, IMAGE_PAD);
+       struct v4l2_mbus_framefmt *try_fmt_meta =
+               v4l2_subdev_get_try_format(sd, 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_get_try_crop(sd, 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_get_try_format(&arducam_64mp->sd, 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_get_try_format(sd, 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_get_try_format(sd, 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_get_try_crop(&arducam_64mp->sd, 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;
+       unsigned int i;
+       int ret;
+       u8 test_pattern_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);
+
+       /*
+        * 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 int 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);
+
+       return 0;
+}
+
+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_new = 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");
index e10af3f..4339ddc 100644 (file)
@@ -55,7 +55,7 @@
 #define IMX219_VTS_30FPS_640x480       0x06e3
 #define IMX219_VTS_MAX                 0xffff
 
-#define IMX219_VBLANK_MIN              4
+#define IMX219_VBLANK_MIN              32
 
 /*Frame Length Line*/
 #define IMX219_FLL_MIN                 0x08a6
 #define IMX219_PIXEL_ARRAY_WIDTH       3280U
 #define IMX219_PIXEL_ARRAY_HEIGHT      2464U
 
+/* Embedded metadata stream structure */
+#define IMX219_EMBEDDED_LINE_WIDTH 16384
+#define IMX219_NUM_EMBEDDED_LINES 1
+
+enum pad_types {
+       IMAGE_PAD,
+       METADATA_PAD,
+       NUM_PADS
+};
+
 struct imx219_reg {
        u16 address;
        u8 val;
@@ -143,6 +153,9 @@ struct imx219_mode {
 
        /* Default register values */
        struct imx219_reg_list reg_list;
+
+       /* Relative pixel clock rate factor for the mode. */
+       unsigned int rate_factor;
 };
 
 /*
@@ -485,6 +498,7 @@ static const struct imx219_mode supported_modes[] = {
                        .num_of_regs = ARRAY_SIZE(mode_3280x2464_regs),
                        .regs = mode_3280x2464_regs,
                },
+               .rate_factor = 1,
        },
        {
                /* 1080P 30fps cropped */
@@ -501,6 +515,7 @@ static const struct imx219_mode supported_modes[] = {
                        .num_of_regs = ARRAY_SIZE(mode_1920_1080_regs),
                        .regs = mode_1920_1080_regs,
                },
+               .rate_factor = 1,
        },
        {
                /* 2x2 binned 30fps mode */
@@ -517,6 +532,7 @@ static const struct imx219_mode supported_modes[] = {
                        .num_of_regs = ARRAY_SIZE(mode_1640_1232_regs),
                        .regs = mode_1640_1232_regs,
                },
+               .rate_factor = 1,
        },
        {
                /* 640x480 30fps mode */
@@ -533,12 +549,17 @@ static const struct imx219_mode supported_modes[] = {
                        .num_of_regs = ARRAY_SIZE(mode_640_480_regs),
                        .regs = mode_640_480_regs,
                },
+               /*
+                * This mode uses a special 2x2 binning that doubles the
+                * the internal pixel clock rate.
+                */
+               .rate_factor = 2,
        },
 };
 
 struct imx219 {
        struct v4l2_subdev sd;
-       struct media_pad pad;
+       struct media_pad pad[NUM_PADS];
 
        struct v4l2_mbus_framefmt fmt;
 
@@ -674,7 +695,7 @@ static void imx219_set_default_format(struct imx219 *imx219)
 
        fmt = &imx219->fmt;
        fmt->code = MEDIA_BUS_FMT_SRGGB10_1X10;
-       fmt->colorspace = V4L2_COLORSPACE_SRGB;
+       fmt->colorspace = V4L2_COLORSPACE_RAW;
        fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace);
        fmt->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true,
                                                          fmt->colorspace,
@@ -688,18 +709,26 @@ static void imx219_set_default_format(struct imx219 *imx219)
 static int imx219_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
 {
        struct imx219 *imx219 = to_imx219(sd);
-       struct v4l2_mbus_framefmt *try_fmt =
-               v4l2_subdev_get_try_format(sd, fh->state, 0);
+       struct v4l2_mbus_framefmt *try_fmt_img =
+               v4l2_subdev_get_try_format(sd, fh->state, IMAGE_PAD);
+       struct v4l2_mbus_framefmt *try_fmt_meta =
+               v4l2_subdev_get_try_format(sd, fh->state, METADATA_PAD);
        struct v4l2_rect *try_crop;
 
        mutex_lock(&imx219->mutex);
 
-       /* Initialize try_fmt */
-       try_fmt->width = supported_modes[0].width;
-       try_fmt->height = supported_modes[0].height;
-       try_fmt->code = imx219_get_format_code(imx219,
-                                              MEDIA_BUS_FMT_SRGGB10_1X10);
-       try_fmt->field = V4L2_FIELD_NONE;
+       /* 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 = imx219_get_format_code(imx219,
+                                                  MEDIA_BUS_FMT_SRGGB10_1X10);
+       try_fmt_img->field = V4L2_FIELD_NONE;
+
+       /* Initialize try_fmt for the embedded metadata pad */
+       try_fmt_meta->width = IMX219_EMBEDDED_LINE_WIDTH;
+       try_fmt_meta->height = IMX219_NUM_EMBEDDED_LINES;
+       try_fmt_meta->code = MEDIA_BUS_FMT_SENSOR_DATA;
+       try_fmt_meta->field = V4L2_FIELD_NONE;
 
        /* Initialize try_crop rectangle. */
        try_crop = v4l2_subdev_get_try_crop(sd, fh->state, 0);
@@ -747,7 +776,8 @@ static int imx219_set_ctrl(struct v4l2_ctrl *ctrl)
                break;
        case V4L2_CID_EXPOSURE:
                ret = imx219_write_reg(imx219, IMX219_REG_EXPOSURE,
-                                      IMX219_REG_VALUE_16BIT, ctrl->val);
+                                      IMX219_REG_VALUE_16BIT,
+                                      ctrl->val / imx219->mode->rate_factor);
                break;
        case V4L2_CID_DIGITAL_GAIN:
                ret = imx219_write_reg(imx219, IMX219_REG_DIGITAL_GAIN,
@@ -767,7 +797,8 @@ static int imx219_set_ctrl(struct v4l2_ctrl *ctrl)
        case V4L2_CID_VBLANK:
                ret = imx219_write_reg(imx219, IMX219_REG_VTS,
                                       IMX219_REG_VALUE_16BIT,
-                                      imx219->mode->height + ctrl->val);
+                                      (imx219->mode->height + ctrl->val) /
+                                               imx219->mode->rate_factor);
                break;
        case V4L2_CID_TEST_PATTERN_RED:
                ret = imx219_write_reg(imx219, IMX219_REG_TESTP_RED,
@@ -808,12 +839,21 @@ static int imx219_enum_mbus_code(struct v4l2_subdev *sd,
 {
        struct imx219 *imx219 = to_imx219(sd);
 
-       if (code->index >= (ARRAY_SIZE(codes) / 4))
+       if (code->pad >= NUM_PADS)
                return -EINVAL;
 
-       mutex_lock(&imx219->mutex);
-       code->code = imx219_get_format_code(imx219, codes[code->index * 4]);
-       mutex_unlock(&imx219->mutex);
+       if (code->pad == IMAGE_PAD) {
+               if (code->index >= (ARRAY_SIZE(codes) / 4))
+                       return -EINVAL;
+
+               code->code = imx219_get_format_code(imx219,
+                                                   codes[code->index * 4]);
+       } else {
+               if (code->index > 0)
+                       return -EINVAL;
+
+               code->code = MEDIA_BUS_FMT_SENSOR_DATA;
+       }
 
        return 0;
 }
@@ -823,28 +863,37 @@ static int imx219_enum_frame_size(struct v4l2_subdev *sd,
                                  struct v4l2_subdev_frame_size_enum *fse)
 {
        struct imx219 *imx219 = to_imx219(sd);
-       u32 code;
 
-       if (fse->index >= ARRAY_SIZE(supported_modes))
+       if (fse->pad >= NUM_PADS)
                return -EINVAL;
 
-       mutex_lock(&imx219->mutex);
-       code = imx219_get_format_code(imx219, fse->code);
-       mutex_unlock(&imx219->mutex);
-       if (fse->code != code)
-               return -EINVAL;
+       if (fse->pad == IMAGE_PAD) {
+               if (fse->index >= ARRAY_SIZE(supported_modes))
+                       return -EINVAL;
+
+               if (fse->code != imx219_get_format_code(imx219, fse->code))
+                       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;
+               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 = IMX219_EMBEDDED_LINE_WIDTH;
+               fse->max_width = fse->min_width;
+               fse->min_height = IMX219_NUM_EMBEDDED_LINES;
+               fse->max_height = fse->min_height;
+       }
 
        return 0;
 }
 
 static void imx219_reset_colorspace(struct v4l2_mbus_framefmt *fmt)
 {
-       fmt->colorspace = V4L2_COLORSPACE_SRGB;
+       fmt->colorspace = V4L2_COLORSPACE_RAW;
        fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace);
        fmt->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true,
                                                          fmt->colorspace,
@@ -852,9 +901,9 @@ static void imx219_reset_colorspace(struct v4l2_mbus_framefmt *fmt)
        fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace);
 }
 
-static void imx219_update_pad_format(struct imx219 *imx219,
-                                    const struct imx219_mode *mode,
-                                    struct v4l2_subdev_format *fmt)
+static void imx219_update_image_pad_format(struct imx219 *imx219,
+                                          const struct imx219_mode *mode,
+                                          struct v4l2_subdev_format *fmt)
 {
        fmt->format.width = mode->width;
        fmt->format.height = mode->height;
@@ -862,21 +911,39 @@ static void imx219_update_pad_format(struct imx219 *imx219,
        imx219_reset_colorspace(&fmt->format);
 }
 
+static void imx219_update_metadata_pad_format(struct v4l2_subdev_format *fmt)
+{
+       fmt->format.width = IMX219_EMBEDDED_LINE_WIDTH;
+       fmt->format.height = IMX219_NUM_EMBEDDED_LINES;
+       fmt->format.code = MEDIA_BUS_FMT_SENSOR_DATA;
+       fmt->format.field = V4L2_FIELD_NONE;
+}
+
 static int __imx219_get_pad_format(struct imx219 *imx219,
                                   struct v4l2_subdev_state *sd_state,
                                   struct v4l2_subdev_format *fmt)
 {
+       if (fmt->pad >= NUM_PADS)
+               return -EINVAL;
+
        if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
                struct v4l2_mbus_framefmt *try_fmt =
                        v4l2_subdev_get_try_format(&imx219->sd, sd_state,
                                                   fmt->pad);
                /* update the code which could change due to vflip or hflip: */
-               try_fmt->code = imx219_get_format_code(imx219, try_fmt->code);
+               try_fmt->code = fmt->pad == IMAGE_PAD ?
+                               imx219_get_format_code(imx219, try_fmt->code) :
+                               MEDIA_BUS_FMT_SENSOR_DATA;
                fmt->format = *try_fmt;
        } else {
-               imx219_update_pad_format(imx219, imx219->mode, fmt);
-               fmt->format.code = imx219_get_format_code(imx219,
-                                                         imx219->fmt.code);
+               if (fmt->pad == IMAGE_PAD) {
+                       imx219_update_image_pad_format(imx219, imx219->mode,
+                                                      fmt);
+                       fmt->format.code = imx219_get_format_code(imx219,
+                                                             imx219->fmt.code);
+               } else {
+                       imx219_update_metadata_pad_format(fmt);
+               }
        }
 
        return 0;
@@ -903,54 +970,83 @@ static int imx219_set_pad_format(struct v4l2_subdev *sd,
        struct imx219 *imx219 = to_imx219(sd);
        const struct imx219_mode *mode;
        struct v4l2_mbus_framefmt *framefmt;
-       int exposure_max, exposure_def, hblank;
+       int exposure_max, exposure_def, hblank, pixel_rate;
        unsigned int i;
 
-       mutex_lock(&imx219->mutex);
-
-       for (i = 0; i < ARRAY_SIZE(codes); i++)
-               if (codes[i] == fmt->format.code)
-                       break;
-       if (i >= ARRAY_SIZE(codes))
-               i = 0;
+       if (fmt->pad >= NUM_PADS)
+               return -EINVAL;
 
-       /* Bayer order varies with flips */
-       fmt->format.code = imx219_get_format_code(imx219, codes[i]);
+       mutex_lock(&imx219->mutex);
 
-       mode = v4l2_find_nearest_size(supported_modes,
-                                     ARRAY_SIZE(supported_modes),
-                                     width, height,
-                                     fmt->format.width, fmt->format.height);
-       imx219_update_pad_format(imx219, mode, fmt);
-       if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
-               framefmt = v4l2_subdev_get_try_format(sd, sd_state, fmt->pad);
-               *framefmt = fmt->format;
-       } else if (imx219->mode != mode ||
-                  imx219->fmt.code != fmt->format.code) {
-               imx219->fmt = fmt->format;
-               imx219->mode = mode;
-               /* Update limits and set FPS to default */
-               __v4l2_ctrl_modify_range(imx219->vblank, IMX219_VBLANK_MIN,
-                                        IMX219_VTS_MAX - mode->height, 1,
-                                        mode->vts_def - mode->height);
-               __v4l2_ctrl_s_ctrl(imx219->vblank,
-                                  mode->vts_def - mode->height);
-               /* Update max exposure while meeting expected vblanking */
-               exposure_max = mode->vts_def - 4;
-               exposure_def = (exposure_max < IMX219_EXPOSURE_DEFAULT) ?
-                       exposure_max : IMX219_EXPOSURE_DEFAULT;
-               __v4l2_ctrl_modify_range(imx219->exposure,
-                                        imx219->exposure->minimum,
-                                        exposure_max, imx219->exposure->step,
-                                        exposure_def);
-               /*
-                * Currently PPL is fixed to IMX219_PPL_DEFAULT, so hblank
-                * depends on mode->width only, and is not changeble in any
-                * way other than changing the mode.
-                */
-               hblank = IMX219_PPL_DEFAULT - mode->width;
-               __v4l2_ctrl_modify_range(imx219->hblank, hblank, hblank, 1,
-                                        hblank);
+       if (fmt->pad == IMAGE_PAD) {
+               for (i = 0; i < ARRAY_SIZE(codes); i++)
+                       if (codes[i] == fmt->format.code)
+                               break;
+               if (i >= ARRAY_SIZE(codes))
+                       i = 0;
+
+               /* Bayer order varies with flips */
+               fmt->format.code = imx219_get_format_code(imx219, codes[i]);
+
+               mode = v4l2_find_nearest_size(supported_modes,
+                                             ARRAY_SIZE(supported_modes),
+                                             width, height,
+                                             fmt->format.width,
+                                             fmt->format.height);
+               imx219_update_image_pad_format(imx219, mode, fmt);
+               if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+                       framefmt = v4l2_subdev_get_try_format(sd, sd_state,
+                                                             fmt->pad);
+                       *framefmt = fmt->format;
+               } else if (imx219->mode != mode ||
+                       imx219->fmt.code != fmt->format.code) {
+                       imx219->fmt = fmt->format;
+                       imx219->mode = mode;
+                       /* Update limits and set FPS to default */
+                       __v4l2_ctrl_modify_range(imx219->vblank,
+                                                IMX219_VBLANK_MIN,
+                                                IMX219_VTS_MAX - mode->height,
+                                                1,
+                                                mode->vts_def - mode->height);
+                       __v4l2_ctrl_s_ctrl(imx219->vblank,
+                                          mode->vts_def - mode->height);
+                       /*
+                        * Update max exposure while meeting
+                        * expected vblanking
+                        */
+                       exposure_max = mode->vts_def - 4;
+                       exposure_def =
+                               (exposure_max < IMX219_EXPOSURE_DEFAULT) ?
+                                       exposure_max : IMX219_EXPOSURE_DEFAULT;
+                       __v4l2_ctrl_modify_range(imx219->exposure,
+                                                imx219->exposure->minimum,
+                                                exposure_max,
+                                                imx219->exposure->step,
+                                                exposure_def);
+                       /*
+                        * Currently PPL is fixed to IMX219_PPL_DEFAULT, so
+                        * hblank depends on mode->width only, and is not
+                        * changeble in any way other than changing the mode.
+                        */
+                       hblank = IMX219_PPL_DEFAULT - mode->width;
+                       __v4l2_ctrl_modify_range(imx219->hblank, hblank, hblank,
+                                                1, hblank);
+
+                       /* Scale the pixel rate based on the mode specific factor */
+                       pixel_rate =
+                               IMX219_PIXEL_RATE * imx219->mode->rate_factor;
+                       __v4l2_ctrl_modify_range(imx219->pixel_rate, pixel_rate,
+                                                pixel_rate, 1, pixel_rate);
+               }
+       } else {
+               if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+                       framefmt = v4l2_subdev_get_try_format(sd, sd_state,
+                                                             fmt->pad);
+                       *framefmt = fmt->format;
+               } else {
+                       /* Only one embedded data mode is supported */
+                       imx219_update_metadata_pad_format(fmt);
+               }
        }
 
        mutex_unlock(&imx219->mutex);
@@ -1037,9 +1133,11 @@ static int imx219_start_streaming(struct imx219 *imx219)
        const struct imx219_reg_list *reg_list;
        int ret;
 
-       ret = pm_runtime_resume_and_get(&client->dev);
-       if (ret < 0)
+       ret = pm_runtime_get_sync(&client->dev);
+       if (ret < 0) {
+               pm_runtime_put_noidle(&client->dev);
                return ret;
+       }
 
        /* Apply default values of current mode */
        reg_list = &imx219->mode->reg_list;
@@ -1133,21 +1231,22 @@ err_unlock:
 /* Power/clock management functions */
 static int imx219_power_on(struct device *dev)
 {
-       struct v4l2_subdev *sd = dev_get_drvdata(dev);
+       struct i2c_client *client = to_i2c_client(dev);
+       struct v4l2_subdev *sd = i2c_get_clientdata(client);
        struct imx219 *imx219 = to_imx219(sd);
        int ret;
 
        ret = regulator_bulk_enable(IMX219_NUM_SUPPLIES,
                                    imx219->supplies);
        if (ret) {
-               dev_err(dev, "%s: failed to enable regulators\n",
+               dev_err(&client->dev, "%s: failed to enable regulators\n",
                        __func__);
                return ret;
        }
 
        ret = clk_prepare_enable(imx219->xclk);
        if (ret) {
-               dev_err(dev, "%s: failed to enable clock\n",
+               dev_err(&client->dev, "%s: failed to enable clock\n",
                        __func__);
                goto reg_off;
        }
@@ -1166,7 +1265,8 @@ reg_off:
 
 static int imx219_power_off(struct device *dev)
 {
-       struct v4l2_subdev *sd = dev_get_drvdata(dev);
+       struct i2c_client *client = to_i2c_client(dev);
+       struct v4l2_subdev *sd = i2c_get_clientdata(client);
        struct imx219 *imx219 = to_imx219(sd);
 
        gpiod_set_value_cansleep(imx219->reset_gpio, 0);
@@ -1178,7 +1278,8 @@ static int imx219_power_off(struct device *dev)
 
 static int __maybe_unused imx219_suspend(struct device *dev)
 {
-       struct v4l2_subdev *sd = dev_get_drvdata(dev);
+       struct i2c_client *client = to_i2c_client(dev);
+       struct v4l2_subdev *sd = i2c_get_clientdata(client);
        struct imx219 *imx219 = to_imx219(sd);
 
        if (imx219->streaming)
@@ -1189,7 +1290,8 @@ static int __maybe_unused imx219_suspend(struct device *dev)
 
 static int __maybe_unused imx219_resume(struct device *dev)
 {
-       struct v4l2_subdev *sd = dev_get_drvdata(dev);
+       struct i2c_client *client = to_i2c_client(dev);
+       struct v4l2_subdev *sd = i2c_get_clientdata(client);
        struct imx219 *imx219 = to_imx219(sd);
        int ret;
 
@@ -1279,7 +1381,7 @@ static int imx219_init_controls(struct imx219 *imx219)
        struct v4l2_ctrl_handler *ctrl_hdlr;
        unsigned int height = imx219->mode->height;
        struct v4l2_fwnode_device_properties props;
-       int exposure_max, exposure_def, hblank;
+       int exposure_max, exposure_def, hblank, pixel_rate;
        int i, ret;
 
        ctrl_hdlr = &imx219->ctrl_handler;
@@ -1291,11 +1393,11 @@ static int imx219_init_controls(struct imx219 *imx219)
        ctrl_hdlr->lock = &imx219->mutex;
 
        /* By default, PIXEL_RATE is read only */
+       pixel_rate = IMX219_PIXEL_RATE * imx219->mode->rate_factor;
        imx219->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &imx219_ctrl_ops,
                                               V4L2_CID_PIXEL_RATE,
-                                              IMX219_PIXEL_RATE,
-                                              IMX219_PIXEL_RATE, 1,
-                                              IMX219_PIXEL_RATE);
+                                              pixel_rate, pixel_rate,
+                                              1, pixel_rate);
 
        imx219->link_freq =
                v4l2_ctrl_new_int_menu(ctrl_hdlr, &imx219_ctrl_ops,
@@ -1525,13 +1627,14 @@ static int imx219_probe(struct i2c_client *client)
                            V4L2_SUBDEV_FL_HAS_EVENTS;
        imx219->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
 
-       /* Initialize source pad */
-       imx219->pad.flags = MEDIA_PAD_FL_SOURCE;
+       /* Initialize source pads */
+       imx219->pad[IMAGE_PAD].flags = MEDIA_PAD_FL_SOURCE;
+       imx219->pad[METADATA_PAD].flags = MEDIA_PAD_FL_SOURCE;
 
        /* Initialize default format */
        imx219_set_default_format(imx219);
 
-       ret = media_entity_pads_init(&imx219->sd.entity, 1, &imx219->pad);
+       ret = media_entity_pads_init(&imx219->sd.entity, NUM_PADS, imx219->pad);
        if (ret) {
                dev_err(dev, "failed to init entity pads: %d\n", ret);
                goto error_handler_free;
index c249507..55f8257 100644 (file)
@@ -7,8 +7,10 @@
 #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-fwnode.h>
 #include <asm/unaligned.h>
 
 #define IMX258_REG_VALUE_08BIT         1
@@ -18,6 +20,8 @@
 #define IMX258_MODE_STANDBY            0x00
 #define IMX258_MODE_STREAMING          0x01
 
+#define IMX258_REG_RESET               0x0103
+
 /* Chip ID */
 #define IMX258_REG_CHIP_ID             0x0016
 #define IMX258_CHIP_ID                 0x0258
 #define IMX258_VTS_30FPS               0x0c50
 #define IMX258_VTS_30FPS_2K            0x0638
 #define IMX258_VTS_30FPS_VGA           0x034c
-#define IMX258_VTS_MAX                 0xffff
+#define IMX258_VTS_MAX                 65525
 
-/*Frame Length Line*/
-#define IMX258_FLL_MIN                 0x08a6
-#define IMX258_FLL_MAX                 0xffff
-#define IMX258_FLL_STEP                        1
-#define IMX258_FLL_DEFAULT             0x0c98
-
-/* HBLANK control - read only */
-#define IMX258_PPL_DEFAULT             5352
+#define IMX258_REG_VTS                 0x0340
 
 /* Exposure control */
 #define IMX258_REG_EXPOSURE            0x0202
+#define IMX258_EXPOSURE_OFFSET         10
 #define IMX258_EXPOSURE_MIN            4
 #define IMX258_EXPOSURE_STEP           1
 #define IMX258_EXPOSURE_DEFAULT                0x640
-#define IMX258_EXPOSURE_MAX            65535
+#define IMX258_EXPOSURE_MAX            (IMX258_VTS_MAX - IMX258_EXPOSURE_OFFSET)
+
+/* HBLANK control - read only */
+#define IMX258_PPL_DEFAULT             5352
 
 /* Analog gain control */
 #define IMX258_REG_ANALOG_GAIN         0x0204
 #define IMX258_HDR_RATIO_STEP          1
 #define IMX258_HDR_RATIO_DEFAULT       0x0
 
+/* Long exposure multiplier */
+#define IMX258_LONG_EXP_SHIFT_MAX      7
+#define IMX258_LONG_EXP_SHIFT_REG      0x3002
+
 /* Test Pattern Control */
 #define IMX258_REG_TEST_PATTERN                0x0600
 
+#define IMX258_CLK_BLANK_STOP          0x4040
+
 /* Orientation */
 #define REG_MIRROR_FLIP_CONTROL                0x0101
-#define REG_CONFIG_MIRROR_FLIP         0x03
+#define REG_CONFIG_MIRROR_HFLIP                0x01
+#define REG_CONFIG_MIRROR_VFLIP                0x02
 #define REG_CONFIG_FLIP_TEST_PATTERN   0x02
 
-/* Input clock frequency in Hz */
-#define IMX258_INPUT_CLOCK_FREQ                19200000
+/* IMX258 native and active pixel array size. */
+#define IMX258_NATIVE_WIDTH            4224U
+#define IMX258_NATIVE_HEIGHT           3192U
+#define IMX258_PIXEL_ARRAY_LEFT                8U
+#define IMX258_PIXEL_ARRAY_TOP         16U
+#define IMX258_PIXEL_ARRAY_WIDTH       4208U
+#define IMX258_PIXEL_ARRAY_HEIGHT      3120U
 
 struct imx258_reg {
        u16 address;
@@ -91,12 +104,22 @@ struct imx258_reg_list {
        const struct imx258_reg *regs;
 };
 
+struct imx258_link_cfg {
+       unsigned int lf_to_pix_rate_factor;
+       struct imx258_reg_list reg_list;
+};
+
+#define IMX258_LANE_CONFIGS    2
+#define IMX258_2_LANE_MODE     0
+#define IMX258_4_LANE_MODE     1
+
 /* Link frequency config */
 struct imx258_link_freq_config {
+       u64 link_frequency;
        u32 pixels_per_line;
 
-       /* PLL registers for this link frequency */
-       struct imx258_reg_list reg_list;
+       /* Configuration for this link frequency / num lanes selection */
+       struct imx258_link_cfg link_cfg[IMX258_LANE_CONFIGS];
 };
 
 /* Mode : resolution and related config&values */
@@ -114,10 +137,37 @@ struct imx258_mode {
        u32 link_freq_index;
        /* Default register values */
        struct imx258_reg_list reg_list;
+
+       /* Analog crop rectangle. */
+       struct v4l2_rect crop;
+};
+
+/* 4208x3120 needs 1267Mbps/lane, 4 lanes. Use that rate on 2 lanes as well */
+static const struct imx258_reg mipi_1267mbps_19_2mhz_2l[] = {
+       { 0x0136, 0x13 },
+       { 0x0137, 0x33 },
+       { 0x0301, 0x0A },
+       { 0x0303, 0x02 },
+       { 0x0305, 0x03 },
+       { 0x0306, 0x00 },
+       { 0x0307, 0xC6 },
+       { 0x0309, 0x0A },
+       { 0x030B, 0x01 },
+       { 0x030D, 0x02 },
+       { 0x030E, 0x00 },
+       { 0x030F, 0xD8 },
+       { 0x0310, 0x00 },
+
+       { 0x0114, 0x01 },
+       { 0x0820, 0x09 },
+       { 0x0821, 0xa6 },
+       { 0x0822, 0x66 },
+       { 0x0823, 0x66 },
 };
 
-/* 4208x3118 needs 1267Mbps/lane, 4 lanes */
-static const struct imx258_reg mipi_data_rate_1267mbps[] = {
+static const struct imx258_reg mipi_1267mbps_19_2mhz_4l[] = {
+       { 0x0136, 0x13 },
+       { 0x0137, 0x33 },
        { 0x0301, 0x05 },
        { 0x0303, 0x02 },
        { 0x0305, 0x03 },
@@ -129,13 +179,61 @@ static const struct imx258_reg mipi_data_rate_1267mbps[] = {
        { 0x030E, 0x00 },
        { 0x030F, 0xD8 },
        { 0x0310, 0x00 },
+
+       { 0x0114, 0x03 },
+       { 0x0820, 0x13 },
+       { 0x0821, 0x4C },
+       { 0x0822, 0xCC },
+       { 0x0823, 0xCC },
+};
+
+static const struct imx258_reg mipi_1272mbps_24mhz_2l[] = {
+       { 0x0136, 0x18 },
+       { 0x0137, 0x00 },
+       { 0x0301, 0x0a },
+       { 0x0303, 0x02 },
+       { 0x0305, 0x04 },
+       { 0x0306, 0x00 },
+       { 0x0307, 0xD4 },
+       { 0x0309, 0x0A },
+       { 0x030B, 0x01 },
+       { 0x030D, 0x02 },
+       { 0x030E, 0x00 },
+       { 0x030F, 0xD8 },
+       { 0x0310, 0x00 },
+
+       { 0x0114, 0x01 },
        { 0x0820, 0x13 },
        { 0x0821, 0x4C },
        { 0x0822, 0xCC },
        { 0x0823, 0xCC },
 };
 
-static const struct imx258_reg mipi_data_rate_640mbps[] = {
+static const struct imx258_reg mipi_1272mbps_24mhz_4l[] = {
+       { 0x0136, 0x18 },
+       { 0x0137, 0x00 },
+       { 0x0301, 0x05 },
+       { 0x0303, 0x02 },
+       { 0x0305, 0x04 },
+       { 0x0306, 0x00 },
+       { 0x0307, 0xD4 },
+       { 0x0309, 0x0A },
+       { 0x030B, 0x01 },
+       { 0x030D, 0x02 },
+       { 0x030E, 0x00 },
+       { 0x030F, 0xD8 },
+       { 0x0310, 0x00 },
+
+       { 0x0114, 0x03 },
+       { 0x0820, 0x13 },
+       { 0x0821, 0xE0 },
+       { 0x0822, 0x00 },
+       { 0x0823, 0x00 },
+};
+
+static const struct imx258_reg mipi_640mbps_19_2mhz_2l[] = {
+       { 0x0136, 0x13 },
+       { 0x0137, 0x33 },
        { 0x0301, 0x05 },
        { 0x0303, 0x02 },
        { 0x0305, 0x03 },
@@ -147,18 +245,82 @@ static const struct imx258_reg mipi_data_rate_640mbps[] = {
        { 0x030E, 0x00 },
        { 0x030F, 0xD8 },
        { 0x0310, 0x00 },
-       { 0x0820, 0x0A },
+
+       { 0x0114, 0x01 },
+       { 0x0820, 0x05 },
        { 0x0821, 0x00 },
        { 0x0822, 0x00 },
        { 0x0823, 0x00 },
 };
 
-static const struct imx258_reg mode_4208x3118_regs[] = {
+static const struct imx258_reg mipi_640mbps_19_2mhz_4l[] = {
        { 0x0136, 0x13 },
        { 0x0137, 0x33 },
+       { 0x0301, 0x05 },
+       { 0x0303, 0x02 },
+       { 0x0305, 0x03 },
+       { 0x0306, 0x00 },
+       { 0x0307, 0x64 },
+       { 0x0309, 0x0A },
+       { 0x030B, 0x01 },
+       { 0x030D, 0x02 },
+       { 0x030E, 0x00 },
+       { 0x030F, 0xD8 },
+       { 0x0310, 0x00 },
+
+       { 0x0114, 0x03 },
+       { 0x0820, 0x0A },
+       { 0x0821, 0x00 },
+       { 0x0822, 0x00 },
+       { 0x0823, 0x00 },
+};
+
+static const struct imx258_reg mipi_642mbps_24mhz_2l[] = {
+       { 0x0136, 0x18 },
+       { 0x0137, 0x00 },
+       { 0x0301, 0x05 },
+       { 0x0303, 0x02 },
+       { 0x0305, 0x04 },
+       { 0x0306, 0x00 },
+       { 0x0307, 0x6B },
+       { 0x0309, 0x0A },
+       { 0x030B, 0x01 },
+       { 0x030D, 0x02 },
+       { 0x030E, 0x00 },
+       { 0x030F, 0xD8 },
+       { 0x0310, 0x00 },
+
+       { 0x0114, 0x01 },
+       { 0x0820, 0x0A },
+       { 0x0821, 0x00 },
+       { 0x0822, 0x00 },
+       { 0x0823, 0x00 },
+};
+
+static const struct imx258_reg mipi_642mbps_24mhz_4l[] = {
+       { 0x0136, 0x18 },
+       { 0x0137, 0x00 },
+       { 0x0301, 0x05 },
+       { 0x0303, 0x02 },
+       { 0x0305, 0x04 },
+       { 0x0306, 0x00 },
+       { 0x0307, 0x6B },
+       { 0x0309, 0x0A },
+       { 0x030B, 0x01 },
+       { 0x030D, 0x02 },
+       { 0x030E, 0x00 },
+       { 0x030F, 0xD8 },
+       { 0x0310, 0x00 },
+
+       { 0x0114, 0x03 },
+       { 0x0820, 0x0A },
+       { 0x0821, 0x00 },
+       { 0x0822, 0x00 },
+       { 0x0823, 0x00 },
+};
+
+static const struct imx258_reg mode_4208x3120_regs[] = {
        { 0x3051, 0x00 },
-       { 0x3052, 0x00 },
-       { 0x4E21, 0x14 },
        { 0x6B11, 0xCF },
        { 0x7FF0, 0x08 },
        { 0x7FF1, 0x0F },
@@ -181,7 +343,6 @@ static const struct imx258_reg mode_4208x3118_regs[] = {
        { 0x7FA8, 0x03 },
        { 0x7FA9, 0xFE },
        { 0x7B24, 0x81 },
-       { 0x7B25, 0x00 },
        { 0x6564, 0x07 },
        { 0x6B0D, 0x41 },
        { 0x653D, 0x04 },
@@ -203,11 +364,8 @@ static const struct imx258_reg mode_4208x3118_regs[] = {
        { 0x5F05, 0xED },
        { 0x0112, 0x0A },
        { 0x0113, 0x0A },
-       { 0x0114, 0x03 },
        { 0x0342, 0x14 },
        { 0x0343, 0xE8 },
-       { 0x0340, 0x0C },
-       { 0x0341, 0x50 },
        { 0x0344, 0x00 },
        { 0x0345, 0x00 },
        { 0x0346, 0x00 },
@@ -215,7 +373,7 @@ static const struct imx258_reg mode_4208x3118_regs[] = {
        { 0x0348, 0x10 },
        { 0x0349, 0x6F },
        { 0x034A, 0x0C },
-       { 0x034B, 0x2E },
+       { 0x034B, 0x2F },
        { 0x0381, 0x01 },
        { 0x0383, 0x01 },
        { 0x0385, 0x01 },
@@ -241,9 +399,7 @@ static const struct imx258_reg mode_4208x3118_regs[] = {
        { 0x034D, 0x70 },
        { 0x034E, 0x0C },
        { 0x034F, 0x30 },
-       { 0x0350, 0x01 },
-       { 0x0202, 0x0C },
-       { 0x0203, 0x46 },
+       { 0x0350, 0x00 },
        { 0x0204, 0x00 },
        { 0x0205, 0x00 },
        { 0x020E, 0x01 },
@@ -273,11 +429,7 @@ static const struct imx258_reg mode_4208x3118_regs[] = {
 };
 
 static const struct imx258_reg mode_2104_1560_regs[] = {
-       { 0x0136, 0x13 },
-       { 0x0137, 0x33 },
        { 0x3051, 0x00 },
-       { 0x3052, 0x00 },
-       { 0x4E21, 0x14 },
        { 0x6B11, 0xCF },
        { 0x7FF0, 0x08 },
        { 0x7FF1, 0x0F },
@@ -300,7 +452,6 @@ static const struct imx258_reg mode_2104_1560_regs[] = {
        { 0x7FA8, 0x03 },
        { 0x7FA9, 0xFE },
        { 0x7B24, 0x81 },
-       { 0x7B25, 0x00 },
        { 0x6564, 0x07 },
        { 0x6B0D, 0x41 },
        { 0x653D, 0x04 },
@@ -322,11 +473,8 @@ static const struct imx258_reg mode_2104_1560_regs[] = {
        { 0x5F05, 0xED },
        { 0x0112, 0x0A },
        { 0x0113, 0x0A },
-       { 0x0114, 0x03 },
        { 0x0342, 0x14 },
        { 0x0343, 0xE8 },
-       { 0x0340, 0x06 },
-       { 0x0341, 0x38 },
        { 0x0344, 0x00 },
        { 0x0345, 0x00 },
        { 0x0346, 0x00 },
@@ -334,7 +482,7 @@ static const struct imx258_reg mode_2104_1560_regs[] = {
        { 0x0348, 0x10 },
        { 0x0349, 0x6F },
        { 0x034A, 0x0C },
-       { 0x034B, 0x2E },
+       { 0x034B, 0x2F },
        { 0x0381, 0x01 },
        { 0x0383, 0x01 },
        { 0x0385, 0x01 },
@@ -345,11 +493,11 @@ static const struct imx258_reg mode_2104_1560_regs[] = {
        { 0x0404, 0x00 },
        { 0x0405, 0x20 },
        { 0x0408, 0x00 },
-       { 0x0409, 0x02 },
+       { 0x0409, 0x00 },
        { 0x040A, 0x00 },
        { 0x040B, 0x00 },
        { 0x040C, 0x10 },
-       { 0x040D, 0x6A },
+       { 0x040D, 0x70 },
        { 0x040E, 0x06 },
        { 0x040F, 0x18 },
        { 0x3038, 0x00 },
@@ -360,9 +508,7 @@ static const struct imx258_reg mode_2104_1560_regs[] = {
        { 0x034D, 0x38 },
        { 0x034E, 0x06 },
        { 0x034F, 0x18 },
-       { 0x0350, 0x01 },
-       { 0x0202, 0x06 },
-       { 0x0203, 0x2E },
+       { 0x0350, 0x00 },
        { 0x0204, 0x00 },
        { 0x0205, 0x00 },
        { 0x020E, 0x01 },
@@ -392,11 +538,7 @@ static const struct imx258_reg mode_2104_1560_regs[] = {
 };
 
 static const struct imx258_reg mode_1048_780_regs[] = {
-       { 0x0136, 0x13 },
-       { 0x0137, 0x33 },
        { 0x3051, 0x00 },
-       { 0x3052, 0x00 },
-       { 0x4E21, 0x14 },
        { 0x6B11, 0xCF },
        { 0x7FF0, 0x08 },
        { 0x7FF1, 0x0F },
@@ -419,7 +561,6 @@ static const struct imx258_reg mode_1048_780_regs[] = {
        { 0x7FA8, 0x03 },
        { 0x7FA9, 0xFE },
        { 0x7B24, 0x81 },
-       { 0x7B25, 0x00 },
        { 0x6564, 0x07 },
        { 0x6B0D, 0x41 },
        { 0x653D, 0x04 },
@@ -441,11 +582,8 @@ static const struct imx258_reg mode_1048_780_regs[] = {
        { 0x5F05, 0xED },
        { 0x0112, 0x0A },
        { 0x0113, 0x0A },
-       { 0x0114, 0x03 },
        { 0x0342, 0x14 },
        { 0x0343, 0xE8 },
-       { 0x0340, 0x03 },
-       { 0x0341, 0x4C },
        { 0x0344, 0x00 },
        { 0x0345, 0x00 },
        { 0x0346, 0x00 },
@@ -453,7 +591,7 @@ static const struct imx258_reg mode_1048_780_regs[] = {
        { 0x0348, 0x10 },
        { 0x0349, 0x6F },
        { 0x034A, 0x0C },
-       { 0x034B, 0x2E },
+       { 0x034B, 0x2F },
        { 0x0381, 0x01 },
        { 0x0383, 0x01 },
        { 0x0385, 0x01 },
@@ -464,11 +602,11 @@ static const struct imx258_reg mode_1048_780_regs[] = {
        { 0x0404, 0x00 },
        { 0x0405, 0x40 },
        { 0x0408, 0x00 },
-       { 0x0409, 0x06 },
+       { 0x0409, 0x00 },
        { 0x040A, 0x00 },
        { 0x040B, 0x00 },
        { 0x040C, 0x10 },
-       { 0x040D, 0x64 },
+       { 0x040D, 0x70 },
        { 0x040E, 0x03 },
        { 0x040F, 0x0C },
        { 0x3038, 0x00 },
@@ -479,9 +617,7 @@ static const struct imx258_reg mode_1048_780_regs[] = {
        { 0x034D, 0x18 },
        { 0x034E, 0x03 },
        { 0x034F, 0x0C },
-       { 0x0350, 0x01 },
-       { 0x0202, 0x03 },
-       { 0x0203, 0x42 },
+       { 0x0350, 0x00 },
        { 0x0204, 0x00 },
        { 0x0205, 0x00 },
        { 0x020E, 0x01 },
@@ -510,6 +646,49 @@ static const struct imx258_reg mode_1048_780_regs[] = {
        { 0x0220, 0x00 },
 };
 
+struct imx258_variant_cfg {
+       const struct imx258_reg *regs;
+       unsigned int num_regs;
+};
+
+static const struct imx258_reg imx258_cfg_regs[] = {
+       { 0x3052, 0x00 },
+       { 0x4E21, 0x14 },
+       { 0x7B25, 0x00 },
+};
+
+static const struct imx258_variant_cfg imx258_cfg = {
+       .regs = imx258_cfg_regs,
+       .num_regs = ARRAY_SIZE(imx258_cfg_regs),
+};
+
+static const struct imx258_reg imx258_pdaf_cfg_regs[] = {
+       { 0x3052, 0x01 },
+       { 0x4E21, 0x10 },
+       { 0x7B25, 0x01 },
+};
+
+static const struct imx258_variant_cfg imx258_pdaf_cfg = {
+       .regs = imx258_pdaf_cfg_regs,
+       .num_regs = ARRAY_SIZE(imx258_pdaf_cfg_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 imx258_test_pattern_menu[] = {
        "Disabled",
        "Solid Colour",
@@ -518,9 +697,15 @@ static const char * const imx258_test_pattern_menu[] = {
        "Pseudorandom Sequence (PN9)",
 };
 
-/* Configurations for supported link frequencies */
-#define IMX258_LINK_FREQ_634MHZ        633600000ULL
-#define IMX258_LINK_FREQ_320MHZ        320000000ULL
+/* regulator supplies */
+static const char * const imx258_supply_name[] = {
+       /* Supplies can be enabled in any order */
+       "vana",  /* Analog (2.8V) supply */
+       "vdig",  /* Digital Core (1.05V) supply */
+       "vif",  /* IF (1.8V) supply */
+};
+
+#define IMX258_NUM_SUPPLIES ARRAY_SIZE(imx258_supply_name)
 
 enum {
        IMX258_LINK_FREQ_1267MBPS,
@@ -528,37 +713,103 @@ enum {
 };
 
 /*
- * pixel_rate = link_freq * data-rate * nr_of_lanes / bits_per_sample
- * data rate => double data rate; number of lanes => 4; bits per pixel => 10
+ * Pixel rate does not necessarily relate to link frequency on this sensor as
+ * there is a FIFO between the pixel array pipeline and the MIPI serializer.
+ * The recommendation from Sony is that the pixel array is always run with a
+ * line length of 5352 pixels, which means that there is a large amount of
+ * blanking time for the 1048x780 mode. There is no need to replicate this
+ * blanking on the CSI2 bus, and the configuration of register 0x0301 allows the
+ * divider to be altered.
+ *
+ * The actual factor between link frequency and pixel rate is in the
+ * imx258_link_cfg, so use this to convert between the two.
+ * bits per pixel being 10, and D-PHY being DDR is assumed by this function, so
+ * the value is only the combination of number of lanes and pixel clock divider.
  */
-static u64 link_freq_to_pixel_rate(u64 f)
+static u64 link_freq_to_pixel_rate(u64 f, const struct imx258_link_cfg *link_cfg)
 {
-       f *= 2 * 4;
+       f *= 2 * link_cfg->lf_to_pix_rate_factor;
        do_div(f, 10);
 
        return f;
 }
 
 /* Menu items for LINK_FREQ V4L2 control */
-static const s64 link_freq_menu_items[] = {
+/* Configurations for supported link frequencies */
+#define IMX258_LINK_FREQ_634MHZ        633600000ULL
+#define IMX258_LINK_FREQ_320MHZ        320000000ULL
+
+static const s64 link_freq_menu_items_19_2[] = {
        IMX258_LINK_FREQ_634MHZ,
        IMX258_LINK_FREQ_320MHZ,
 };
 
+/* Configurations for supported link frequencies */
+#define IMX258_LINK_FREQ_636MHZ        636000000ULL
+#define IMX258_LINK_FREQ_321MHZ        321000000ULL
+
+static const s64 link_freq_menu_items_24[] = {
+       IMX258_LINK_FREQ_636MHZ,
+       IMX258_LINK_FREQ_321MHZ,
+};
+
+#define REGS(_list) { .num_of_regs = ARRAY_SIZE(_list), .regs = _list, }
+
 /* Link frequency configs */
-static const struct imx258_link_freq_config link_freq_configs[] = {
+static const struct imx258_link_freq_config link_freq_configs_19_2[] = {
        [IMX258_LINK_FREQ_1267MBPS] = {
                .pixels_per_line = IMX258_PPL_DEFAULT,
-               .reg_list = {
-                       .num_of_regs = ARRAY_SIZE(mipi_data_rate_1267mbps),
-                       .regs = mipi_data_rate_1267mbps,
+               .link_cfg = {
+                       [IMX258_2_LANE_MODE] = {
+                               .lf_to_pix_rate_factor = 2 * 2,
+                               .reg_list = REGS(mipi_1267mbps_19_2mhz_2l),
+                       },
+                       [IMX258_4_LANE_MODE] = {
+                               .lf_to_pix_rate_factor = 4,
+                               .reg_list = REGS(mipi_1267mbps_19_2mhz_4l),
+                       },
                }
        },
        [IMX258_LINK_FREQ_640MBPS] = {
                .pixels_per_line = IMX258_PPL_DEFAULT,
-               .reg_list = {
-                       .num_of_regs = ARRAY_SIZE(mipi_data_rate_640mbps),
-                       .regs = mipi_data_rate_640mbps,
+               .link_cfg = {
+                       [IMX258_2_LANE_MODE] = {
+                               .lf_to_pix_rate_factor = 2,
+                               .reg_list = REGS(mipi_640mbps_19_2mhz_2l),
+                       },
+                       [IMX258_4_LANE_MODE] = {
+                               .lf_to_pix_rate_factor = 4,
+                               .reg_list = REGS(mipi_640mbps_19_2mhz_4l),
+                       },
+               }
+       },
+};
+
+static const struct imx258_link_freq_config link_freq_configs_24[] = {
+       [IMX258_LINK_FREQ_1267MBPS] = {
+               .pixels_per_line = IMX258_PPL_DEFAULT,
+               .link_cfg = {
+                       [IMX258_2_LANE_MODE] = {
+                               .lf_to_pix_rate_factor = 2,
+                               .reg_list = REGS(mipi_1272mbps_24mhz_2l),
+                       },
+                       [IMX258_4_LANE_MODE] = {
+                               .lf_to_pix_rate_factor = 4,
+                               .reg_list = REGS(mipi_1272mbps_24mhz_4l),
+                       },
+               }
+       },
+       [IMX258_LINK_FREQ_640MBPS] = {
+               .pixels_per_line = IMX258_PPL_DEFAULT,
+               .link_cfg = {
+                       [IMX258_2_LANE_MODE] = {
+                               .lf_to_pix_rate_factor = 2 * 2,
+                               .reg_list = REGS(mipi_642mbps_24mhz_2l),
+                       },
+                       [IMX258_4_LANE_MODE] = {
+                               .lf_to_pix_rate_factor = 4,
+                               .reg_list = REGS(mipi_642mbps_24mhz_4l),
+                       },
                }
        },
 };
@@ -567,14 +818,20 @@ static const struct imx258_link_freq_config link_freq_configs[] = {
 static const struct imx258_mode supported_modes[] = {
        {
                .width = 4208,
-               .height = 3118,
+               .height = 3120,
                .vts_def = IMX258_VTS_30FPS,
                .vts_min = IMX258_VTS_30FPS,
                .reg_list = {
-                       .num_of_regs = ARRAY_SIZE(mode_4208x3118_regs),
-                       .regs = mode_4208x3118_regs,
+                       .num_of_regs = ARRAY_SIZE(mode_4208x3120_regs),
+                       .regs = mode_4208x3120_regs,
                },
                .link_freq_index = IMX258_LINK_FREQ_1267MBPS,
+               .crop = {
+                       .left = IMX258_PIXEL_ARRAY_LEFT,
+                       .top = IMX258_PIXEL_ARRAY_TOP,
+                       .width = 4208,
+                       .height = 3120,
+               },
        },
        {
                .width = 2104,
@@ -586,6 +843,12 @@ static const struct imx258_mode supported_modes[] = {
                        .regs = mode_2104_1560_regs,
                },
                .link_freq_index = IMX258_LINK_FREQ_640MBPS,
+               .crop = {
+                       .left = IMX258_PIXEL_ARRAY_LEFT,
+                       .top = IMX258_PIXEL_ARRAY_TOP,
+                       .width = 4208,
+                       .height = 3120,
+               },
        },
        {
                .width = 1048,
@@ -597,6 +860,12 @@ static const struct imx258_mode supported_modes[] = {
                        .regs = mode_1048_780_regs,
                },
                .link_freq_index = IMX258_LINK_FREQ_640MBPS,
+               .crop = {
+                       .left = IMX258_PIXEL_ARRAY_LEFT,
+                       .top = IMX258_PIXEL_ARRAY_TOP,
+                       .width = 4208,
+                       .height = 3120,
+               },
        },
 };
 
@@ -604,6 +873,8 @@ struct imx258 {
        struct v4l2_subdev sd;
        struct media_pad pad;
 
+       const struct imx258_variant_cfg *variant_cfg;
+
        struct v4l2_ctrl_handler ctrl_handler;
        /* V4L2 Controls */
        struct v4l2_ctrl *link_freq;
@@ -611,10 +882,19 @@ struct imx258 {
        struct v4l2_ctrl *vblank;
        struct v4l2_ctrl *hblank;
        struct v4l2_ctrl *exposure;
+       struct v4l2_ctrl *hflip;
+       struct v4l2_ctrl *vflip;
+       /* Current long exposure factor in use. Set through V4L2_CID_VBLANK */
+       unsigned int long_exp_shift;
 
        /* Current mode */
        const struct imx258_mode *cur_mode;
 
+       const struct imx258_link_freq_config *link_freq_configs;
+       const s64 *link_freq_menu_items;
+       unsigned int lane_mode_idx;
+       unsigned int csi2_flags;
+
        /*
         * Mutex for serialized access:
         * Protect sensor module set pad format and start/stop streaming safely.
@@ -625,6 +905,7 @@ struct imx258 {
        bool streaming;
 
        struct clk *clk;
+       struct regulator_bulk_data supplies[IMX258_NUM_SUPPLIES];
 };
 
 static inline struct imx258 *to_imx258(struct v4l2_subdev *_sd)
@@ -706,18 +987,39 @@ static int imx258_write_regs(struct imx258 *imx258,
        return 0;
 }
 
+/* Get bayer order based on flip setting. */
+static u32 imx258_get_format_code(struct imx258 *imx258)
+{
+       unsigned int i;
+
+       lockdep_assert_held(&imx258->mutex);
+
+       i = (imx258->vflip->val ? 2 : 0) |
+           (imx258->hflip->val ? 1 : 0);
+
+       return codes[i];
+}
 /* Open sub-device */
 static int imx258_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
 {
+       struct imx258 *imx258 = to_imx258(sd);
        struct v4l2_mbus_framefmt *try_fmt =
                v4l2_subdev_get_try_format(sd, fh->state, 0);
+       struct v4l2_rect *try_crop;
 
        /* Initialize try_fmt */
        try_fmt->width = supported_modes[0].width;
        try_fmt->height = supported_modes[0].height;
-       try_fmt->code = MEDIA_BUS_FMT_SGRBG10_1X10;
+       try_fmt->code = imx258_get_format_code(imx258);
        try_fmt->field = V4L2_FIELD_NONE;
 
+       /* Initialize try_crop */
+       try_crop = v4l2_subdev_get_try_crop(sd, fh->state, 0);
+       try_crop->left = IMX258_PIXEL_ARRAY_LEFT;
+       try_crop->top = IMX258_PIXEL_ARRAY_TOP;
+       try_crop->width = IMX258_PIXEL_ARRAY_WIDTH;
+       try_crop->height = IMX258_PIXEL_ARRAY_HEIGHT;
+
        return 0;
 }
 
@@ -748,6 +1050,39 @@ static int imx258_update_digital_gain(struct imx258 *imx258, u32 len, u32 val)
        return 0;
 }
 
+static void imx258_adjust_exposure_range(struct imx258 *imx258)
+{
+       int exposure_max, exposure_def;
+
+       /* Honour the VBLANK limits when setting exposure. */
+       exposure_max = imx258->cur_mode->height + imx258->vblank->val -
+                      IMX258_EXPOSURE_OFFSET;
+       exposure_def = min(exposure_max, imx258->exposure->val);
+       __v4l2_ctrl_modify_range(imx258->exposure, imx258->exposure->minimum,
+                                exposure_max, imx258->exposure->step,
+                                exposure_def);
+}
+
+static int imx258_set_frame_length(struct imx258 *imx258, unsigned int val)
+{
+       int ret;
+
+       imx258->long_exp_shift = 0;
+
+       while (val > IMX258_VTS_MAX) {
+               imx258->long_exp_shift++;
+               val >>= 1;
+       }
+
+       ret = imx258_write_reg(imx258, IMX258_REG_VTS,
+                              IMX258_REG_VALUE_16BIT, val);
+       if (ret)
+               return ret;
+
+       return imx258_write_reg(imx258, IMX258_LONG_EXP_SHIFT_REG,
+                               IMX258_REG_VALUE_08BIT, imx258->long_exp_shift);
+}
+
 static int imx258_set_ctrl(struct v4l2_ctrl *ctrl)
 {
        struct imx258 *imx258 =
@@ -756,6 +1091,13 @@ static int imx258_set_ctrl(struct v4l2_ctrl *ctrl)
        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)
+               imx258_adjust_exposure_range(imx258);
+
+       /*
         * Applying V4L2 control value only happens
         * when power is up for streaming
         */
@@ -771,7 +1113,7 @@ static int imx258_set_ctrl(struct v4l2_ctrl *ctrl)
        case V4L2_CID_EXPOSURE:
                ret = imx258_write_reg(imx258, IMX258_REG_EXPOSURE,
                                IMX258_REG_VALUE_16BIT,
-                               ctrl->val);
+                               ctrl->val >> imx258->long_exp_shift);
                break;
        case V4L2_CID_DIGITAL_GAIN:
                ret = imx258_update_digital_gain(imx258, IMX258_REG_VALUE_16BIT,
@@ -781,10 +1123,6 @@ static int imx258_set_ctrl(struct v4l2_ctrl *ctrl)
                ret = imx258_write_reg(imx258, IMX258_REG_TEST_PATTERN,
                                IMX258_REG_VALUE_16BIT,
                                ctrl->val);
-               ret = imx258_write_reg(imx258, REG_MIRROR_FLIP_CONTROL,
-                               IMX258_REG_VALUE_08BIT,
-                               !ctrl->val ? REG_CONFIG_MIRROR_FLIP :
-                               REG_CONFIG_FLIP_TEST_PATTERN);
                break;
        case V4L2_CID_WIDE_DYNAMIC_RANGE:
                if (!ctrl->val) {
@@ -802,6 +1140,19 @@ static int imx258_set_ctrl(struct v4l2_ctrl *ctrl)
                                               BIT(IMX258_HDR_RATIO_MAX));
                }
                break;
+       case V4L2_CID_VBLANK:
+               ret = imx258_set_frame_length(imx258,
+                                             imx258->cur_mode->height + ctrl->val);
+               break;
+       case V4L2_CID_VFLIP:
+       case V4L2_CID_HFLIP:
+               ret = imx258_write_reg(imx258, REG_MIRROR_FLIP_CONTROL,
+                                      IMX258_REG_VALUE_08BIT,
+                                      (imx258->hflip->val ?
+                                       REG_CONFIG_MIRROR_HFLIP : 0) |
+                                      (imx258->vflip->val ?
+                                       REG_CONFIG_MIRROR_VFLIP : 0));
+               break;
        default:
                dev_info(&client->dev,
                         "ctrl(id:0x%x,val:0x%x) is not handled\n",
@@ -823,11 +1174,13 @@ static int imx258_enum_mbus_code(struct v4l2_subdev *sd,
                                  struct v4l2_subdev_state *sd_state,
                                  struct v4l2_subdev_mbus_code_enum *code)
 {
-       /* Only one bayer order(GRBG) is supported */
+       struct imx258 *imx258 = to_imx258(sd);
+
+       /* Only one bayer format (10 bit) is supported */
        if (code->index > 0)
                return -EINVAL;
 
-       code->code = MEDIA_BUS_FMT_SGRBG10_1X10;
+       code->code = imx258_get_format_code(imx258);
 
        return 0;
 }
@@ -836,10 +1189,11 @@ static int imx258_enum_frame_size(struct v4l2_subdev *sd,
                                  struct v4l2_subdev_state *sd_state,
                                  struct v4l2_subdev_frame_size_enum *fse)
 {
+       struct imx258 *imx258 = to_imx258(sd);
        if (fse->index >= ARRAY_SIZE(supported_modes))
                return -EINVAL;
 
-       if (fse->code != MEDIA_BUS_FMT_SGRBG10_1X10)
+       if (fse->code != imx258_get_format_code(imx258))
                return -EINVAL;
 
        fse->min_width = supported_modes[fse->index].width;
@@ -850,12 +1204,13 @@ static int imx258_enum_frame_size(struct v4l2_subdev *sd,
        return 0;
 }
 
-static void imx258_update_pad_format(const struct imx258_mode *mode,
+static void imx258_update_pad_format(struct imx258 *imx258,
+                                    const struct imx258_mode *mode,
                                     struct v4l2_subdev_format *fmt)
 {
        fmt->format.width = mode->width;
        fmt->format.height = mode->height;
-       fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10;
+       fmt->format.code = imx258_get_format_code(imx258);
        fmt->format.field = V4L2_FIELD_NONE;
 }
 
@@ -868,7 +1223,7 @@ static int __imx258_get_pad_format(struct imx258 *imx258,
                                                          sd_state,
                                                          fmt->pad);
        else
-               imx258_update_pad_format(imx258->cur_mode, fmt);
+               imx258_update_pad_format(imx258, imx258->cur_mode, fmt);
 
        return 0;
 }
@@ -892,8 +1247,10 @@ static int imx258_set_pad_format(struct v4l2_subdev *sd,
                                 struct v4l2_subdev_format *fmt)
 {
        struct imx258 *imx258 = to_imx258(sd);
-       const struct imx258_mode *mode;
+       const struct imx258_link_freq_config *link_freq_cfgs;
+       const struct imx258_link_cfg *link_cfg;
        struct v4l2_mbus_framefmt *framefmt;
+       const struct imx258_mode *mode;
        s32 vblank_def;
        s32 vblank_min;
        s64 h_blank;
@@ -902,13 +1259,12 @@ static int imx258_set_pad_format(struct v4l2_subdev *sd,
 
        mutex_lock(&imx258->mutex);
 
-       /* Only one raw bayer(GBRG) order is supported */
-       fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10;
+       fmt->format.code = imx258_get_format_code(imx258);
 
        mode = v4l2_find_nearest_size(supported_modes,
                ARRAY_SIZE(supported_modes), width, height,
                fmt->format.width, fmt->format.height);
-       imx258_update_pad_format(mode, fmt);
+       imx258_update_pad_format(imx258, mode, fmt);
        if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
                framefmt = v4l2_subdev_get_try_format(sd, sd_state, fmt->pad);
                *framefmt = fmt->format;
@@ -916,9 +1272,14 @@ static int imx258_set_pad_format(struct v4l2_subdev *sd,
                imx258->cur_mode = mode;
                __v4l2_ctrl_s_ctrl(imx258->link_freq, mode->link_freq_index);
 
-               link_freq = link_freq_menu_items[mode->link_freq_index];
-               pixel_rate = link_freq_to_pixel_rate(link_freq);
-               __v4l2_ctrl_s_ctrl_int64(imx258->pixel_rate, pixel_rate);
+               link_freq = imx258->link_freq_menu_items[mode->link_freq_index];
+               link_freq_cfgs =
+                       &imx258->link_freq_configs[mode->link_freq_index];
+
+               link_cfg = &link_freq_cfgs->link_cfg[imx258->lane_mode_idx];
+               pixel_rate = link_freq_to_pixel_rate(link_freq, link_cfg);
+               __v4l2_ctrl_modify_range(imx258->pixel_rate, pixel_rate,
+                                        pixel_rate, 1, pixel_rate);
                /* Update limits and set FPS to default */
                vblank_def = imx258->cur_mode->vts_def -
                             imx258->cur_mode->height;
@@ -926,11 +1287,12 @@ static int imx258_set_pad_format(struct v4l2_subdev *sd,
                             imx258->cur_mode->height;
                __v4l2_ctrl_modify_range(
                        imx258->vblank, vblank_min,
-                       IMX258_VTS_MAX - imx258->cur_mode->height, 1,
-                       vblank_def);
+                       ((1 << IMX258_LONG_EXP_SHIFT_MAX) * IMX258_VTS_MAX) -
+                                               imx258->cur_mode->height,
+                       1, vblank_def);
                __v4l2_ctrl_s_ctrl(imx258->vblank, vblank_def);
                h_blank =
-                       link_freq_configs[mode->link_freq_index].pixels_per_line
+                       imx258->link_freq_configs[mode->link_freq_index].pixels_per_line
                         - imx258->cur_mode->width;
                __v4l2_ctrl_modify_range(imx258->hblank, h_blank,
                                         h_blank, 1, h_blank);
@@ -941,36 +1303,107 @@ static int imx258_set_pad_format(struct v4l2_subdev *sd,
        return 0;
 }
 
+static const struct v4l2_rect *
+__imx258_get_pad_crop(struct imx258 *imx258,
+                     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_get_try_crop(&imx258->sd, sd_state, pad);
+       case V4L2_SUBDEV_FORMAT_ACTIVE:
+               return &imx258->cur_mode->crop;
+       }
+
+       return NULL;
+}
+
+static int imx258_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 imx258 *imx258 = to_imx258(sd);
+
+               mutex_lock(&imx258->mutex);
+               sel->r = *__imx258_get_pad_crop(imx258, sd_state, sel->pad,
+                                               sel->which);
+               mutex_unlock(&imx258->mutex);
+
+               return 0;
+       }
+
+       case V4L2_SEL_TGT_NATIVE_SIZE:
+               sel->r.left = 0;
+               sel->r.top = 0;
+               sel->r.width = IMX258_NATIVE_WIDTH;
+               sel->r.height = IMX258_NATIVE_HEIGHT;
+
+               return 0;
+
+       case V4L2_SEL_TGT_CROP_DEFAULT:
+       case V4L2_SEL_TGT_CROP_BOUNDS:
+               sel->r.left = IMX258_PIXEL_ARRAY_LEFT;
+               sel->r.top = IMX258_PIXEL_ARRAY_TOP;
+               sel->r.width = IMX258_PIXEL_ARRAY_WIDTH;
+               sel->r.height = IMX258_PIXEL_ARRAY_HEIGHT;
+
+               return 0;
+       }
+
+       return -EINVAL;
+}
+
 /* Start streaming */
 static int imx258_start_streaming(struct imx258 *imx258)
 {
        struct i2c_client *client = v4l2_get_subdevdata(&imx258->sd);
        const struct imx258_reg_list *reg_list;
+       const struct imx258_link_freq_config *link_freq_cfg;
        int ret, link_freq_index;
 
+       ret = imx258_write_reg(imx258, IMX258_REG_RESET, IMX258_REG_VALUE_08BIT,
+                              0x01);
+       if (ret) {
+               dev_err(&client->dev, "%s failed to reset sensor\n", __func__);
+               return ret;
+       }
+       usleep_range(10000, 15000);
+
        /* Setup PLL */
        link_freq_index = imx258->cur_mode->link_freq_index;
-       reg_list = &link_freq_configs[link_freq_index].reg_list;
+       link_freq_cfg = &imx258->link_freq_configs[link_freq_index];
+
+       reg_list = &link_freq_cfg->link_cfg[imx258->lane_mode_idx].reg_list;
        ret = imx258_write_regs(imx258, reg_list->regs, reg_list->num_of_regs);
        if (ret) {
                dev_err(&client->dev, "%s failed to set plls\n", __func__);
                return ret;
        }
 
-       /* Apply default values of current mode */
-       reg_list = &imx258->cur_mode->reg_list;
-       ret = imx258_write_regs(imx258, reg_list->regs, reg_list->num_of_regs);
+       ret = imx258_write_regs(imx258, imx258->variant_cfg->regs,
+                               imx258->variant_cfg->num_regs);
        if (ret) {
-               dev_err(&client->dev, "%s failed to set mode\n", __func__);
+               dev_err(&client->dev, "%s failed to set variant config\n",
+                       __func__);
                return ret;
        }
 
-       /* Set Orientation be 180 degree */
-       ret = imx258_write_reg(imx258, REG_MIRROR_FLIP_CONTROL,
-                              IMX258_REG_VALUE_08BIT, REG_CONFIG_MIRROR_FLIP);
+       ret = imx258_write_reg(imx258, IMX258_CLK_BLANK_STOP,
+                              IMX258_REG_VALUE_08BIT,
+                              imx258->csi2_flags & V4L2_MBUS_CSI2_CONTINUOUS_CLOCK ?
+                              0 : 1);
        if (ret) {
-               dev_err(&client->dev, "%s failed to set orientation\n",
-                       __func__);
+               dev_err(&client->dev, "%s failed to set clock lane mode\n", __func__);
+               return ret;
+       }
+
+       /* Apply default values of current mode */
+       reg_list = &imx258->cur_mode->reg_list;
+       ret = imx258_write_regs(imx258, reg_list->regs, reg_list->num_of_regs);
+       if (ret) {
+               dev_err(&client->dev, "%s failed to set mode\n", __func__);
                return ret;
        }
 
@@ -1010,9 +1443,19 @@ static int imx258_power_on(struct device *dev)
        struct imx258 *imx258 = to_imx258(sd);
        int ret;
 
+       ret = regulator_bulk_enable(IMX258_NUM_SUPPLIES,
+                                   imx258->supplies);
+       if (ret) {
+               dev_err(dev, "%s: failed to enable regulators\n",
+                       __func__);
+               return ret;
+       }
+
        ret = clk_prepare_enable(imx258->clk);
-       if (ret)
+       if (ret) {
                dev_err(dev, "failed to enable clock\n");
+               regulator_bulk_disable(IMX258_NUM_SUPPLIES, imx258->supplies);
+       }
 
        return ret;
 }
@@ -1023,6 +1466,7 @@ static int imx258_power_off(struct device *dev)
        struct imx258 *imx258 = to_imx258(sd);
 
        clk_disable_unprepare(imx258->clk);
+       regulator_bulk_disable(IMX258_NUM_SUPPLIES, imx258->supplies);
 
        return 0;
 }
@@ -1133,6 +1577,7 @@ static const struct v4l2_subdev_pad_ops imx258_pad_ops = {
        .get_fmt = imx258_get_pad_format,
        .set_fmt = imx258_set_pad_format,
        .enum_frame_size = imx258_enum_frame_size,
+       .get_selection = imx258_get_selection,
 };
 
 static const struct v4l2_subdev_ops imx258_subdev_ops = {
@@ -1148,15 +1593,17 @@ static const struct v4l2_subdev_internal_ops imx258_internal_ops = {
 static int imx258_init_controls(struct imx258 *imx258)
 {
        struct i2c_client *client = v4l2_get_subdevdata(&imx258->sd);
+       const struct imx258_link_freq_config *link_freq_cfgs;
+       struct v4l2_fwnode_device_properties props;
        struct v4l2_ctrl_handler *ctrl_hdlr;
+       const struct imx258_link_cfg *link_cfg;
        s64 vblank_def;
        s64 vblank_min;
-       s64 pixel_rate_min;
-       s64 pixel_rate_max;
+       s64 pixel_rate;
        int ret;
 
        ctrl_hdlr = &imx258->ctrl_handler;
-       ret = v4l2_ctrl_handler_init(ctrl_hdlr, 8);
+       ret = v4l2_ctrl_handler_init(ctrl_hdlr, 12);
        if (ret)
                return ret;
 
@@ -1165,20 +1612,23 @@ static int imx258_init_controls(struct imx258 *imx258)
        imx258->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr,
                                &imx258_ctrl_ops,
                                V4L2_CID_LINK_FREQ,
-                               ARRAY_SIZE(link_freq_menu_items) - 1,
+                               ARRAY_SIZE(link_freq_menu_items_19_2) - 1,
                                0,
-                               link_freq_menu_items);
+                               imx258->link_freq_menu_items);
 
        if (imx258->link_freq)
                imx258->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
 
-       pixel_rate_max = link_freq_to_pixel_rate(link_freq_menu_items[0]);
-       pixel_rate_min = link_freq_to_pixel_rate(link_freq_menu_items[1]);
+       link_freq_cfgs = &imx258->link_freq_configs[0];
+       link_cfg = link_freq_cfgs[imx258->lane_mode_idx].link_cfg;
+       pixel_rate = link_freq_to_pixel_rate(imx258->link_freq_menu_items[0],
+                                            link_cfg);
+
        /* By default, PIXEL_RATE is read only */
        imx258->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &imx258_ctrl_ops,
                                V4L2_CID_PIXEL_RATE,
-                               pixel_rate_min, pixel_rate_max,
-                               1, pixel_rate_max);
+                               pixel_rate, pixel_rate,
+                               1, pixel_rate);
 
 
        vblank_def = imx258->cur_mode->vts_def - imx258->cur_mode->height;
@@ -1189,9 +1639,6 @@ static int imx258_init_controls(struct imx258 *imx258)
                                IMX258_VTS_MAX - imx258->cur_mode->height, 1,
                                vblank_def);
 
-       if (imx258->vblank)
-               imx258->vblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
-
        imx258->hblank = v4l2_ctrl_new_std(
                                ctrl_hdlr, &imx258_ctrl_ops, V4L2_CID_HBLANK,
                                IMX258_PPL_DEFAULT - imx258->cur_mode->width,
@@ -1225,6 +1672,25 @@ static int imx258_init_controls(struct imx258 *imx258)
                                ARRAY_SIZE(imx258_test_pattern_menu) - 1,
                                0, 0, imx258_test_pattern_menu);
 
+       ret = v4l2_fwnode_device_parse(&client->dev, &props);
+       if (ret)
+               goto error;
+       ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &imx258_ctrl_ops,
+                                             &props);
+       if (ret)
+               goto error;
+
+       imx258->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx258_ctrl_ops,
+                                         V4L2_CID_HFLIP, 0, 1, 1,
+                                         props.rotation == 180 ? 1 : 0);
+       if (imx258->hflip)
+               imx258->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT;
+
+       imx258->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx258_ctrl_ops,
+                                         V4L2_CID_VFLIP, 0, 1, 1,
+                                         props.rotation == 180 ? 1 : 0);
+       if (imx258->vflip)
+               imx258->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT;
        if (ctrl_hdlr->error) {
                ret = ctrl_hdlr->error;
                dev_err(&client->dev, "%s control init failed (%d)\n",
@@ -1249,9 +1715,33 @@ static void imx258_free_controls(struct imx258 *imx258)
        mutex_destroy(&imx258->mutex);
 }
 
+static int imx258_get_regulators(struct imx258 *imx258,
+                                struct i2c_client *client)
+{
+       unsigned int i;
+
+       for (i = 0; i < IMX258_NUM_SUPPLIES; i++)
+               imx258->supplies[i].supply = imx258_supply_name[i];
+
+       return devm_regulator_bulk_get(&client->dev,
+                                      IMX258_NUM_SUPPLIES,
+                                      imx258->supplies);
+}
+
+static const struct of_device_id imx258_dt_ids[] = {
+       { .compatible = "sony,imx258", .data = &imx258_cfg },
+       { .compatible = "sony,imx258-pdaf", .data = &imx258_pdaf_cfg },
+       { /* sentinel */ }
+};
+
 static int imx258_probe(struct i2c_client *client)
 {
        struct imx258 *imx258;
+       struct fwnode_handle *endpoint;
+       struct v4l2_fwnode_endpoint ep = {
+               .bus_type = V4L2_MBUS_CSI2_DPHY
+       };
+       const struct of_device_id *match;
        int ret;
        u32 val = 0;
 
@@ -1259,6 +1749,10 @@ static int imx258_probe(struct i2c_client *client)
        if (!imx258)
                return -ENOMEM;
 
+       ret = imx258_get_regulators(imx258, client);
+       if (ret)
+               return ret;
+
        imx258->clk = devm_clk_get_optional(&client->dev, NULL);
        if (IS_ERR(imx258->clk))
                return dev_err_probe(&client->dev, PTR_ERR(imx258->clk),
@@ -1268,21 +1762,67 @@ static int imx258_probe(struct i2c_client *client)
                        "no clock provided, using clock-frequency property\n");
 
                device_property_read_u32(&client->dev, "clock-frequency", &val);
+       } else if (IS_ERR(imx258->clk)) {
+               return dev_err_probe(&client->dev, PTR_ERR(imx258->clk),
+                                    "error getting clock\n");
        } else {
                val = clk_get_rate(imx258->clk);
        }
-       if (val != IMX258_INPUT_CLOCK_FREQ) {
-               dev_err(&client->dev, "input clock frequency not supported\n");
+
+       switch (val) {
+       case 19200000:
+               imx258->link_freq_configs = link_freq_configs_19_2;
+               imx258->link_freq_menu_items = link_freq_menu_items_19_2;
+               break;
+       case 24000000:
+               imx258->link_freq_configs = link_freq_configs_24;
+               imx258->link_freq_menu_items = link_freq_menu_items_24;
+               break;
+       default:
+               dev_err(&client->dev, "input clock frequency of %u not supported\n",
+                       val);
                return -EINVAL;
        }
 
-       /*
-        * Check that the device is mounted upside down. The driver only
-        * supports a single pixel order right now.
-        */
-       ret = device_property_read_u32(&client->dev, "rotation", &val);
-       if (ret || val != 180)
+       endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(&client->dev), NULL);
+       if (!endpoint) {
+               dev_err(&client->dev, "Endpoint node not found\n");
                return -EINVAL;
+       }
+
+       ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep);
+       fwnode_handle_put(endpoint);
+       if (ret == -ENXIO) {
+               dev_err(&client->dev, "Unsupported bus type, should be CSI2\n");
+               goto error_endpoint_poweron;
+       } else if (ret) {
+               dev_err(&client->dev, "Parsing endpoint node failed\n");
+               goto error_endpoint_poweron;
+       }
+
+       /* Get number of data lanes */
+       switch (ep.bus.mipi_csi2.num_data_lanes) {
+       case 2:
+               imx258->lane_mode_idx = IMX258_2_LANE_MODE;
+               break;
+       case 4:
+               imx258->lane_mode_idx = IMX258_4_LANE_MODE;
+               break;
+       default:
+               dev_err(&client->dev, "Invalid data lanes: %u\n",
+                       ep.bus.mipi_csi2.num_data_lanes);
+               ret = -EINVAL;
+               goto error_endpoint_poweron;
+       }
+
+       imx258->csi2_flags = ep.bus.mipi_csi2.flags;
+
+       match = i2c_of_match_device(imx258_dt_ids, client);
+       if (!match || !match->data)
+               imx258->variant_cfg = &imx258_cfg;
+       else
+               imx258->variant_cfg =
+                       (const struct imx258_variant_cfg *)match->data;
 
        /* Initialize subdev */
        v4l2_i2c_subdev_init(&imx258->sd, client, &imx258_subdev_ops);
@@ -1290,7 +1830,7 @@ static int imx258_probe(struct i2c_client *client)
        /* Will be powered off via pm_runtime_idle */
        ret = imx258_power_on(&client->dev);
        if (ret)
-               return ret;
+               goto error_endpoint_poweron;
 
        /* Check module identity */
        ret = imx258_identify_module(imx258);
@@ -1335,6 +1875,9 @@ error_handler_free:
 error_identify:
        imx258_power_off(&client->dev);
 
+error_endpoint_poweron:
+       v4l2_fwnode_endpoint_free(&ep);
+
        return ret;
 }
 
@@ -1369,10 +1912,6 @@ static const struct acpi_device_id imx258_acpi_ids[] = {
 MODULE_DEVICE_TABLE(acpi, imx258_acpi_ids);
 #endif
 
-static const struct of_device_id imx258_dt_ids[] = {
-       { .compatible = "sony,imx258" },
-       { /* sentinel */ }
-};
 MODULE_DEVICE_TABLE(of, imx258_dt_ids);
 
 static struct i2c_driver imx258_i2c_driver = {
index bf7a6c3..c5abef4 100644 (file)
@@ -1,6 +1,16 @@
 // SPDX-License-Identifier: GPL-2.0
 /*
- * Sony IMX290 CMOS Image Sensor Driver
+ * Sony IMX462 / IMX290 / IMX327 CMOS Image Sensor Driver
+ *
+ * The IMX462, IMX290,and IMX327 are very similar 1920x1080 1/2.8 CMOS image
+ * sensors.
+ * IMX327 can support up to 60fps with 10 or 12bit readout.
+ * IMX290 adds support for 120fps, but only 10bit and when connected over 4
+ * CSI-2 lanes.
+ * IMX462 adds support for 120fps in both 10 and 12bit readout modes.
+ *
+ * The modules don't appear to have a mechanism to identify whether the mono or
+ * colour variant is connected, therefore it is done via compatible string.
  *
  * Copyright (C) 2019 FRAMOS GmbH.
  *
@@ -13,6 +23,7 @@
 #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/regmap.h>
 #include <linux/regulator/consumer.h>
 #include <media/v4l2-fwnode.h>
 #include <media/v4l2-subdev.h>
 
+enum imx290_clk_index {
+       CLK_37_125,
+       CLK_74_25,
+};
+
 #define IMX290_STANDBY 0x3000
 #define IMX290_REGHOLD 0x3001
 #define IMX290_XMSTA 0x3002
+#define IMX290_FLIP_WINMODE 0x3007
 #define IMX290_FR_FDG_SEL 0x3009
 #define IMX290_BLKLEVEL_LOW 0x300a
 #define IMX290_BLKLEVEL_HIGH 0x300b
 #define IMX290_GAIN 0x3014
+#define IMX290_VMAX_LOW 0x3018
+#define IMX290_VMAX_MAX 0x3fff
 #define IMX290_HMAX_LOW 0x301c
 #define IMX290_HMAX_HIGH 0x301d
+#define IMX290_HMAX_MIN 2200 /* Min of 2200 pixels = 60fps */
+#define IMX290_HMAX_MAX 0xffff
+
+#define IMX290_EXPOSURE_MIN 1
+#define IMX290_EXPOSURE_STEP 1
+#define IMX290_EXPOSURE_LOW 0x3020
 #define IMX290_PGCTRL 0x308c
 #define IMX290_PHY_LANE_NUM 0x3407
 #define IMX290_CSI_LANE_MODE 0x3443
 #define IMX290_PGCTRL_THRU BIT(1)
 #define IMX290_PGCTRL_MODE(n) ((n) << 4)
 
+#define IMX290_NATIVE_WIDTH            1945U
+#define IMX290_NATIVE_HEIGHT           1109U
+#define IMX290_PIXEL_ARRAY_LEFT                4U
+#define IMX290_PIXEL_ARRAY_TOP         12U
+#define IMX290_PIXEL_ARRAY_WIDTH       1937U
+#define IMX290_PIXEL_ARRAY_HEIGHT      1097U
+
 static const char * const imx290_supply_name[] = {
        "vdda",
        "vddd",
@@ -56,18 +88,31 @@ struct imx290_mode {
        u32 width;
        u32 height;
        u32 hmax;
+       u32 vmax;
        u8 link_freq_index;
+       struct v4l2_rect crop;
+
+       const struct imx290_regval *mode_data;
+       u32 mode_data_size;
+       const struct imx290_regval *lane_data;
+       u32 lane_data_size;
 
-       const struct imx290_regval *data;
-       u32 data_size;
+
+       /* Clock setup can vary. Index as enum imx290_clk_index */
+       const struct imx290_regval *clk_data[2];
+       u32 clk_size;
 };
 
 struct imx290 {
        struct device *dev;
        struct clk *xclk;
+       u32 xclk_freq;
        struct regmap *regmap;
        u8 nlanes;
        u8 bpp;
+       u16 hmax_min;
+
+       const struct imx290_pixfmt *formats;
 
        struct v4l2_subdev sd;
        struct media_pad pad;
@@ -80,6 +125,11 @@ struct imx290 {
        struct v4l2_ctrl_handler ctrls;
        struct v4l2_ctrl *link_freq;
        struct v4l2_ctrl *pixel_rate;
+       struct v4l2_ctrl *hblank;
+       struct v4l2_ctrl *vblank;
+       struct v4l2_ctrl *hflip;
+       struct v4l2_ctrl *vflip;
+       struct v4l2_ctrl *exposure;
 
        struct mutex lock;
 };
@@ -89,11 +139,18 @@ struct imx290_pixfmt {
        u8 bpp;
 };
 
-static const struct imx290_pixfmt imx290_formats[] = {
+#define IMX290_NUM_FORMATS 2
+
+static const struct imx290_pixfmt imx290_colour_formats[IMX290_NUM_FORMATS] = {
        { MEDIA_BUS_FMT_SRGGB10_1X10, 10 },
        { MEDIA_BUS_FMT_SRGGB12_1X12, 12 },
 };
 
+static const struct imx290_pixfmt imx290_mono_formats[IMX290_NUM_FORMATS] = {
+       { MEDIA_BUS_FMT_Y10_1X10, 10 },
+       { MEDIA_BUS_FMT_Y12_1X12, 12 },
+};
+
 static const struct regmap_config imx290_regmap_config = {
        .reg_bits = 16,
        .val_bits = 8,
@@ -113,11 +170,7 @@ static const char * const imx290_test_pattern_menu[] = {
 
 static const struct imx290_regval imx290_global_init_settings[] = {
        { 0x3007, 0x00 },
-       { 0x3018, 0x65 },
-       { 0x3019, 0x04 },
        { 0x301a, 0x00 },
-       { 0x3444, 0x20 },
-       { 0x3445, 0x25 },
        { 0x303a, 0x0c },
        { 0x3040, 0x00 },
        { 0x3041, 0x00 },
@@ -171,8 +224,33 @@ static const struct imx290_regval imx290_global_init_settings[] = {
        { 0x33b3, 0x04 },
 };
 
-static const struct imx290_regval imx290_1080p_settings[] = {
+static const struct imx290_regval imx290_37_125mhz_clock_1080p[] = {
+       { 0x305c, 0x18 },
+       { 0x305d, 0x03 },
+       { 0x305e, 0x20 },
+       { 0x305f, 0x01 },
+       { 0x315e, 0x1a },
+       { 0x3164, 0x1a },
+       { 0x3444, 0x20 },
+       { 0x3445, 0x25 },
+       { 0x3480, 0x49 },
+};
+
+static const struct imx290_regval imx290_74_250mhz_clock_1080p[] = {
+       { 0x305c, 0x0c },
+       { 0x305d, 0x03 },
+       { 0x305e, 0x10 },
+       { 0x305f, 0x01 },
+       { 0x315e, 0x1b },
+       { 0x3164, 0x1b },
+       { 0x3444, 0x40 },
+       { 0x3445, 0x4a },
+       { 0x3480, 0x92 },
+};
+
+static const struct imx290_regval imx290_1080p_common_settings[] = {
        /* mode settings */
+       { IMX290_FR_FDG_SEL, 0x01 },
        { 0x3007, 0x00 },
        { 0x303a, 0x0c },
        { 0x3414, 0x0a },
@@ -182,15 +260,36 @@ static const struct imx290_regval imx290_1080p_settings[] = {
        { 0x3419, 0x04 },
        { 0x3012, 0x64 },
        { 0x3013, 0x00 },
-       { 0x305c, 0x18 },
-       { 0x305d, 0x03 },
-       { 0x305e, 0x20 },
-       { 0x305f, 0x01 },
-       { 0x315e, 0x1a },
-       { 0x3164, 0x1a },
-       { 0x3480, 0x49 },
+};
+
+static const struct imx290_regval imx290_1080p_2lane_settings[] = {
+       { 0x3405, 0x00 },
        /* data rate settings */
+       { IMX290_PHY_LANE_NUM, 0x01 },
+       { IMX290_CSI_LANE_MODE, 0x01 },
+       { 0x3446, 0x77 },
+       { 0x3447, 0x00 },
+       { 0x3448, 0x67 },
+       { 0x3449, 0x00 },
+       { 0x344a, 0x47 },
+       { 0x344b, 0x00 },
+       { 0x344c, 0x37 },
+       { 0x344d, 0x00 },
+       { 0x344e, 0x3f },
+       { 0x344f, 0x00 },
+       { 0x3450, 0xff },
+       { 0x3451, 0x00 },
+       { 0x3452, 0x3f },
+       { 0x3453, 0x00 },
+       { 0x3454, 0x37 },
+       { 0x3455, 0x00 },
+};
+
+static const struct imx290_regval imx290_1080p_4lane_settings[] = {
        { 0x3405, 0x10 },
+       /* data rate settings */
+       { IMX290_PHY_LANE_NUM, 0x03 },
+       { IMX290_CSI_LANE_MODE, 0x03 },
        { 0x3446, 0x57 },
        { 0x3447, 0x00 },
        { 0x3448, 0x37 },
@@ -209,8 +308,33 @@ static const struct imx290_regval imx290_1080p_settings[] = {
        { 0x3455, 0x00 },
 };
 
-static const struct imx290_regval imx290_720p_settings[] = {
+static const struct imx290_regval imx290_37_125mhz_clock_720p[] = {
+       { 0x305c, 0x20 },
+       { 0x305d, 0x00 },
+       { 0x305e, 0x20 },
+       { 0x305f, 0x01 },
+       { 0x315e, 0x1a },
+       { 0x3164, 0x1a },
+       { 0x3444, 0x20 },
+       { 0x3445, 0x25 },
+       { 0x3480, 0x49 },
+};
+
+static const struct imx290_regval imx290_74_250mhz_clock_720p[] = {
+       { 0x305c, 0x10 },
+       { 0x305d, 0x00 },
+       { 0x305e, 0x10 },
+       { 0x305f, 0x01 },
+       { 0x315e, 0x1b },
+       { 0x3164, 0x1b },
+       { 0x3444, 0x40 },
+       { 0x3445, 0x4a },
+       { 0x3480, 0x92 },
+};
+
+static const struct imx290_regval imx290_720p_common_settings[] = {
        /* mode settings */
+       { IMX290_FR_FDG_SEL, 0x01 },
        { 0x3007, 0x10 },
        { 0x303a, 0x06 },
        { 0x3414, 0x04 },
@@ -220,15 +344,36 @@ static const struct imx290_regval imx290_720p_settings[] = {
        { 0x3419, 0x02 },
        { 0x3012, 0x64 },
        { 0x3013, 0x00 },
-       { 0x305c, 0x20 },
-       { 0x305d, 0x00 },
-       { 0x305e, 0x20 },
-       { 0x305f, 0x01 },
-       { 0x315e, 0x1a },
-       { 0x3164, 0x1a },
-       { 0x3480, 0x49 },
+};
+
+static const struct imx290_regval imx290_720p_2lane_settings[] = {
+       { 0x3405, 0x00 },
+       { IMX290_PHY_LANE_NUM, 0x01 },
+       { IMX290_CSI_LANE_MODE, 0x01 },
        /* data rate settings */
+       { 0x3446, 0x67 },
+       { 0x3447, 0x00 },
+       { 0x3448, 0x57 },
+       { 0x3449, 0x00 },
+       { 0x344a, 0x2f },
+       { 0x344b, 0x00 },
+       { 0x344c, 0x27 },
+       { 0x344d, 0x00 },
+       { 0x344e, 0x2f },
+       { 0x344f, 0x00 },
+       { 0x3450, 0xbf },
+       { 0x3451, 0x00 },
+       { 0x3452, 0x2f },
+       { 0x3453, 0x00 },
+       { 0x3454, 0x27 },
+       { 0x3455, 0x00 },
+};
+
+static const struct imx290_regval imx290_720p_4lane_settings[] = {
        { 0x3405, 0x10 },
+       { IMX290_PHY_LANE_NUM, 0x03 },
+       { IMX290_CSI_LANE_MODE, 0x03 },
+       /* data rate settings */
        { 0x3446, 0x4f },
        { 0x3447, 0x00 },
        { 0x3448, 0x2f },
@@ -308,18 +453,46 @@ static const struct imx290_mode imx290_modes_2lanes[] = {
        {
                .width = 1920,
                .height = 1080,
-               .hmax = 0x1130,
+               .hmax = 0x0898,
+               .vmax = 0x0465,
                .link_freq_index = FREQ_INDEX_1080P,
-               .data = imx290_1080p_settings,
-               .data_size = ARRAY_SIZE(imx290_1080p_settings),
+               .crop = {
+                       .left = 4 + 8,
+                       .top = 12 + 8,
+                       .width = 1920,
+                       .height = 1080,
+               },
+               .mode_data = imx290_1080p_common_settings,
+               .mode_data_size = ARRAY_SIZE(imx290_1080p_common_settings),
+               .lane_data = imx290_1080p_2lane_settings,
+               .lane_data_size = ARRAY_SIZE(imx290_1080p_2lane_settings),
+               .clk_data = {
+                       [CLK_37_125] = imx290_37_125mhz_clock_1080p,
+                       [CLK_74_25] = imx290_74_250mhz_clock_1080p,
+               },
+               .clk_size = ARRAY_SIZE(imx290_37_125mhz_clock_1080p),
        },
        {
                .width = 1280,
                .height = 720,
-               .hmax = 0x19c8,
+               .hmax = 0x0ce4,
+               .vmax = 0x02ee,
                .link_freq_index = FREQ_INDEX_720P,
-               .data = imx290_720p_settings,
-               .data_size = ARRAY_SIZE(imx290_720p_settings),
+               .crop = {
+                       .left = 4 + 8 + 320,
+                       .top = 12 + 8 + 180,
+                       .width = 1280,
+                       .height = 720,
+               },
+               .mode_data = imx290_720p_common_settings,
+               .mode_data_size = ARRAY_SIZE(imx290_720p_common_settings),
+               .lane_data = imx290_720p_2lane_settings,
+               .lane_data_size = ARRAY_SIZE(imx290_720p_2lane_settings),
+               .clk_data = {
+                       [CLK_37_125] = imx290_37_125mhz_clock_720p,
+                       [CLK_74_25] = imx290_74_250mhz_clock_720p,
+               },
+               .clk_size = ARRAY_SIZE(imx290_37_125mhz_clock_720p),
        },
 };
 
@@ -328,17 +501,45 @@ static const struct imx290_mode imx290_modes_4lanes[] = {
                .width = 1920,
                .height = 1080,
                .hmax = 0x0898,
+               .vmax = 0x0465,
                .link_freq_index = FREQ_INDEX_1080P,
-               .data = imx290_1080p_settings,
-               .data_size = ARRAY_SIZE(imx290_1080p_settings),
+               .crop = {
+                       .left = 4 + 8,
+                       .top = 12 + 8,
+                       .width = 1920,
+                       .height = 1080,
+               },
+               .mode_data = imx290_1080p_common_settings,
+               .mode_data_size = ARRAY_SIZE(imx290_1080p_common_settings),
+               .lane_data = imx290_1080p_4lane_settings,
+               .lane_data_size = ARRAY_SIZE(imx290_1080p_4lane_settings),
+               .clk_data = {
+                       [CLK_37_125] = imx290_37_125mhz_clock_1080p,
+                       [CLK_74_25] = imx290_74_250mhz_clock_1080p,
+               },
+               .clk_size = ARRAY_SIZE(imx290_37_125mhz_clock_1080p),
        },
        {
                .width = 1280,
                .height = 720,
                .hmax = 0x0ce4,
+               .vmax = 0x02ee,
                .link_freq_index = FREQ_INDEX_720P,
-               .data = imx290_720p_settings,
-               .data_size = ARRAY_SIZE(imx290_720p_settings),
+               .crop = {
+                       .left = 4 + 8 + 320,
+                       .top = 12 + 8 + 180,
+                       .width = 1280,
+                       .height = 720,
+               },
+               .mode_data = imx290_720p_common_settings,
+               .mode_data_size = ARRAY_SIZE(imx290_720p_common_settings),
+               .lane_data = imx290_720p_4lane_settings,
+               .lane_data_size = ARRAY_SIZE(imx290_720p_4lane_settings),
+               .clk_data = {
+                       [CLK_37_125] = imx290_37_125mhz_clock_720p,
+                       [CLK_74_25] = imx290_74_250mhz_clock_720p,
+               },
+               .clk_size = ARRAY_SIZE(imx290_37_125mhz_clock_720p),
        },
 };
 
@@ -452,6 +653,64 @@ static int imx290_set_gain(struct imx290 *imx290, u32 value)
        return ret;
 }
 
+static int imx290_set_exposure(struct imx290 *imx290, u32 value)
+{
+       u32 exposure = (imx290->current_mode->height + imx290->vblank->val) -
+                                               value - 1;
+       int ret;
+
+       ret = imx290_write_buffered_reg(imx290, IMX290_EXPOSURE_LOW, 3,
+                                       exposure);
+       if (ret)
+               dev_err(imx290->dev, "Unable to write exposure\n");
+
+       return ret;
+}
+
+static int imx290_set_hmax(struct imx290 *imx290, u32 val)
+{
+       u32 hmax = val + imx290->current_mode->width;
+       int ret;
+
+       ret = imx290_write_buffered_reg(imx290, IMX290_HMAX_LOW, 2,
+                                       hmax);
+       if (ret)
+               dev_err(imx290->dev, "Error setting HMAX register\n");
+
+       return ret;
+}
+
+static int imx290_set_vmax(struct imx290 *imx290, u32 val)
+{
+       u32 vmax = val + imx290->current_mode->height;
+       int ret;
+
+       ret = imx290_write_buffered_reg(imx290, IMX290_VMAX_LOW, 3,
+                                       vmax);
+       if (ret)
+               dev_err(imx290->dev, "Unable to write vmax\n");
+
+       /*
+        * Changing vblank changes the allowed range for exposure.
+        * We don't supply the current exposure as default here as it
+        * may lie outside the new range. We will reset it just below.
+        */
+       __v4l2_ctrl_modify_range(imx290->exposure,
+                                IMX290_EXPOSURE_MIN,
+                                vmax - 2,
+                                IMX290_EXPOSURE_STEP,
+                                vmax - 2);
+
+       /*
+        * Becuse of the way exposure works for this sensor, updating
+        * vblank causes the effective exposure to change, so we must
+        * set it back to the "new" correct value.
+        */
+       imx290_set_exposure(imx290, imx290->exposure->val);
+
+       return ret;
+}
+
 /* Stop streaming */
 static int imx290_stop_streaming(struct imx290 *imx290)
 {
@@ -471,15 +730,35 @@ static int imx290_set_ctrl(struct v4l2_ctrl *ctrl)
        struct imx290 *imx290 = container_of(ctrl->handler,
                                             struct imx290, ctrls);
        int ret = 0;
+       u8 val;
 
        /* V4L2 controls values will be applied only when power is already up */
        if (!pm_runtime_get_if_in_use(imx290->dev))
                return 0;
 
        switch (ctrl->id) {
-       case V4L2_CID_GAIN:
+       case V4L2_CID_ANALOGUE_GAIN:
                ret = imx290_set_gain(imx290, ctrl->val);
                break;
+       case V4L2_CID_EXPOSURE:
+               ret = imx290_set_exposure(imx290, ctrl->val);
+               break;
+       case V4L2_CID_HBLANK:
+               ret = imx290_set_hmax(imx290, ctrl->val);
+               break;
+       case V4L2_CID_VBLANK:
+               ret = imx290_set_vmax(imx290, ctrl->val);
+               break;
+       case V4L2_CID_HFLIP:
+       case V4L2_CID_VFLIP:
+               /* WINMODE is in bits [6:4], so need to read-modify-write */
+               ret = imx290_read_reg(imx290, IMX290_FLIP_WINMODE, &val);
+               if (ret)
+                       break;
+               val &= ~0x03;
+               val |= imx290->vflip->val | (imx290->hflip->val << 1);
+               ret = imx290_write_reg(imx290, IMX290_FLIP_WINMODE, val);
+               break;
        case V4L2_CID_TEST_PATTERN:
                if (ctrl->val) {
                        imx290_write_reg(imx290, IMX290_BLKLEVEL_LOW, 0x00);
@@ -519,10 +798,12 @@ static int imx290_enum_mbus_code(struct v4l2_subdev *sd,
                                 struct v4l2_subdev_state *sd_state,
                                 struct v4l2_subdev_mbus_code_enum *code)
 {
-       if (code->index >= ARRAY_SIZE(imx290_formats))
+       const struct imx290 *imx290 = to_imx290(sd);
+
+       if (code->index >= IMX290_NUM_FORMATS)
                return -EINVAL;
 
-       code->code = imx290_formats[code->index].code;
+       code->code = imx290->formats[code->index].code;
 
        return 0;
 }
@@ -534,8 +815,8 @@ static int imx290_enum_frame_size(struct v4l2_subdev *sd,
        const struct imx290 *imx290 = to_imx290(sd);
        const struct imx290_mode *imx290_modes = imx290_modes_ptr(imx290);
 
-       if ((fse->code != imx290_formats[0].code) &&
-           (fse->code != imx290_formats[1].code))
+       if (fse->code != imx290->formats[0].code &&
+           fse->code != imx290->formats[1].code)
                return -EINVAL;
 
        if (fse->index >= imx290_modes_num(imx290))
@@ -576,23 +857,9 @@ static inline u8 imx290_get_link_freq_index(struct imx290 *imx290)
        return imx290->current_mode->link_freq_index;
 }
 
-static s64 imx290_get_link_freq(struct imx290 *imx290)
-{
-       u8 index = imx290_get_link_freq_index(imx290);
-
-       return *(imx290_link_freqs_ptr(imx290) + index);
-}
-
 static u64 imx290_calc_pixel_rate(struct imx290 *imx290)
 {
-       s64 link_freq = imx290_get_link_freq(imx290);
-       u8 nlanes = imx290->nlanes;
-       u64 pixel_rate;
-
-       /* pixel rate = link_freq * 2 * nr_of_lanes / bits_per_sample */
-       pixel_rate = link_freq * 2 * nlanes;
-       do_div(pixel_rate, imx290->bpp);
-       return pixel_rate;
+       return 148500000;
 }
 
 static int imx290_set_fmt(struct v4l2_subdev *sd,
@@ -613,22 +880,30 @@ static int imx290_set_fmt(struct v4l2_subdev *sd,
        fmt->format.width = mode->width;
        fmt->format.height = mode->height;
 
-       for (i = 0; i < ARRAY_SIZE(imx290_formats); i++)
-               if (imx290_formats[i].code == fmt->format.code)
+       for (i = 0; i < IMX290_NUM_FORMATS; i++)
+               if (imx290->formats[i].code == fmt->format.code)
                        break;
 
-       if (i >= ARRAY_SIZE(imx290_formats))
+       if (i >= IMX290_NUM_FORMATS)
                i = 0;
 
-       fmt->format.code = imx290_formats[i].code;
+       fmt->format.code = imx290->formats[i].code;
        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) {
                format = v4l2_subdev_get_try_format(sd, sd_state, fmt->pad);
        } else {
                format = &imx290->current_format;
                imx290->current_mode = mode;
-               imx290->bpp = imx290_formats[i].bpp;
+               imx290->bpp = imx290->formats[i].bpp;
 
                if (imx290->link_freq)
                        __v4l2_ctrl_s_ctrl(imx290->link_freq,
@@ -636,6 +911,30 @@ static int imx290_set_fmt(struct v4l2_subdev *sd,
                if (imx290->pixel_rate)
                        __v4l2_ctrl_s_ctrl_int64(imx290->pixel_rate,
                                                 imx290_calc_pixel_rate(imx290));
+
+               if (imx290->hblank) {
+                       __v4l2_ctrl_modify_range(imx290->hblank,
+                                                imx290->hmax_min - mode->width,
+                                                IMX290_HMAX_MAX - mode->width,
+                                                1, mode->hmax - mode->width);
+                       __v4l2_ctrl_s_ctrl(imx290->hblank,
+                                          mode->hmax - mode->width);
+               }
+               if (imx290->vblank) {
+                       __v4l2_ctrl_modify_range(imx290->vblank,
+                                                mode->vmax - mode->height,
+                                                IMX290_VMAX_MAX - mode->height,
+                                                1,
+                                                mode->vmax - mode->height);
+                       __v4l2_ctrl_s_ctrl(imx290->vblank,
+                                          mode->vmax - mode->height);
+               }
+               if (imx290->exposure)
+                       __v4l2_ctrl_modify_range(imx290->exposure,
+                                                IMX290_EXPOSURE_MIN,
+                                                mode->vmax - 2,
+                                                IMX290_EXPOSURE_STEP,
+                                                mode->vmax - 2);
        }
 
        *format = fmt->format;
@@ -665,6 +964,7 @@ static int imx290_write_current_format(struct imx290 *imx290)
 
        switch (imx290->current_format.code) {
        case MEDIA_BUS_FMT_SRGGB10_1X10:
+       case MEDIA_BUS_FMT_Y10_1X10:
                ret = imx290_set_register_array(imx290, imx290_10bit_settings,
                                                ARRAY_SIZE(
                                                        imx290_10bit_settings));
@@ -674,6 +974,7 @@ static int imx290_write_current_format(struct imx290 *imx290)
                }
                break;
        case MEDIA_BUS_FMT_SRGGB12_1X12:
+       case MEDIA_BUS_FMT_Y12_1X12:
                ret = imx290_set_register_array(imx290, imx290_12bit_settings,
                                                ARRAY_SIZE(
                                                        imx290_12bit_settings));
@@ -690,28 +991,63 @@ static int imx290_write_current_format(struct imx290 *imx290)
        return 0;
 }
 
-static int imx290_set_hmax(struct imx290 *imx290, u32 val)
+static const struct v4l2_rect *
+__imx290_get_pad_crop(struct imx290 *imx290,
+                     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_get_try_crop(&imx290->sd, sd_state, pad);
+       case V4L2_SUBDEV_FORMAT_ACTIVE:
+               return &imx290->current_mode->crop;
+       }
 
-       ret = imx290_write_reg(imx290, IMX290_HMAX_LOW, (val & 0xff));
-       if (ret) {
-               dev_err(imx290->dev, "Error setting HMAX register\n");
-               return ret;
+       return NULL;
+}
+
+static int imx290_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 imx290 *imx290 = to_imx290(sd);
+
+               mutex_lock(&imx290->lock);
+               sel->r = *__imx290_get_pad_crop(imx290, sd_state, sel->pad,
+                                               sel->which);
+               mutex_unlock(&imx290->lock);
+
+               return 0;
        }
 
-       ret = imx290_write_reg(imx290, IMX290_HMAX_HIGH, ((val >> 8) & 0xff));
-       if (ret) {
-               dev_err(imx290->dev, "Error setting HMAX register\n");
-               return ret;
+       case V4L2_SEL_TGT_NATIVE_SIZE:
+               sel->r.top = 0;
+               sel->r.left = 0;
+               sel->r.width = IMX290_NATIVE_WIDTH;
+               sel->r.height = IMX290_NATIVE_HEIGHT;
+
+               return 0;
+
+       case V4L2_SEL_TGT_CROP_DEFAULT:
+       case V4L2_SEL_TGT_CROP_BOUNDS:
+               sel->r.top = IMX290_PIXEL_ARRAY_TOP;
+               sel->r.left = IMX290_PIXEL_ARRAY_LEFT;
+               sel->r.width = IMX290_PIXEL_ARRAY_WIDTH;
+               sel->r.height = IMX290_PIXEL_ARRAY_HEIGHT;
+
+               return 0;
        }
 
-       return 0;
+       return -EINVAL;
 }
 
 /* Start streaming */
 static int imx290_start_streaming(struct imx290 *imx290)
 {
+       enum imx290_clk_index clk_idx = imx290->xclk_freq == 37125000 ?
+                                       CLK_37_125 : CLK_74_25;
        int ret;
 
        /* Set init register settings */
@@ -723,6 +1059,14 @@ static int imx290_start_streaming(struct imx290 *imx290)
                return ret;
        }
 
+       ret = imx290_set_register_array(imx290,
+                                       imx290->current_mode->clk_data[clk_idx],
+                                       imx290->current_mode->clk_size);
+       if (ret < 0) {
+               dev_err(imx290->dev, "Could not set clock registers\n");
+               return ret;
+       }
+
        /* Apply the register values related to current frame format */
        ret = imx290_write_current_format(imx290);
        if (ret < 0) {
@@ -731,15 +1075,22 @@ static int imx290_start_streaming(struct imx290 *imx290)
        }
 
        /* Apply default values of current mode */
-       ret = imx290_set_register_array(imx290, imx290->current_mode->data,
-                                       imx290->current_mode->data_size);
+       ret = imx290_set_register_array(imx290,
+                                       imx290->current_mode->mode_data,
+                                       imx290->current_mode->mode_data_size);
        if (ret < 0) {
                dev_err(imx290->dev, "Could not set current mode\n");
                return ret;
        }
-       ret = imx290_set_hmax(imx290, imx290->current_mode->hmax);
-       if (ret < 0)
+
+       /* Apply lane config registers of current mode */
+       ret = imx290_set_register_array(imx290,
+                                       imx290->current_mode->lane_data,
+                                       imx290->current_mode->lane_data_size);
+       if (ret < 0) {
+               dev_err(imx290->dev, "Could not set current mode\n");
                return ret;
+       }
 
        /* Apply customized values from user */
        ret = v4l2_ctrl_handler_setup(imx290->sd.ctrl_handler);
@@ -778,6 +1129,9 @@ static int imx290_set_stream(struct v4l2_subdev *sd, int enable)
                imx290_stop_streaming(imx290);
                pm_runtime_put(imx290->dev);
        }
+       /* vflip and hflip cannot change during streaming */
+       __v4l2_ctrl_grab(imx290->vflip, enable);
+       __v4l2_ctrl_grab(imx290->hflip, enable);
 
 unlock_and_return:
 
@@ -795,49 +1149,6 @@ static int imx290_get_regulators(struct device *dev, struct imx290 *imx290)
                                       imx290->supplies);
 }
 
-static int imx290_set_data_lanes(struct imx290 *imx290)
-{
-       int ret = 0, laneval, frsel;
-
-       switch (imx290->nlanes) {
-       case 2:
-               laneval = 0x01;
-               frsel = 0x02;
-               break;
-       case 4:
-               laneval = 0x03;
-               frsel = 0x01;
-               break;
-       default:
-               /*
-                * We should never hit this since the data lane count is
-                * validated in probe itself
-                */
-               dev_err(imx290->dev, "Lane configuration not supported\n");
-               ret = -EINVAL;
-               goto exit;
-       }
-
-       ret = imx290_write_reg(imx290, IMX290_PHY_LANE_NUM, laneval);
-       if (ret) {
-               dev_err(imx290->dev, "Error setting Physical Lane number register\n");
-               goto exit;
-       }
-
-       ret = imx290_write_reg(imx290, IMX290_CSI_LANE_MODE, laneval);
-       if (ret) {
-               dev_err(imx290->dev, "Error setting CSI Lane mode register\n");
-               goto exit;
-       }
-
-       ret = imx290_write_reg(imx290, IMX290_FR_FDG_SEL, frsel);
-       if (ret)
-               dev_err(imx290->dev, "Error setting FR/FDG SEL register\n");
-
-exit:
-       return ret;
-}
-
 static int imx290_power_on(struct device *dev)
 {
        struct v4l2_subdev *sd = dev_get_drvdata(dev);
@@ -861,9 +1172,6 @@ static int imx290_power_on(struct device *dev)
        gpiod_set_value_cansleep(imx290->rst_gpio, 0);
        usleep_range(30000, 31000);
 
-       /* Set data lane count */
-       imx290_set_data_lanes(imx290);
-
        return 0;
 }
 
@@ -893,6 +1201,7 @@ static const struct v4l2_subdev_pad_ops imx290_pad_ops = {
        .enum_frame_size = imx290_enum_frame_size,
        .get_fmt = imx290_get_fmt,
        .set_fmt = imx290_set_fmt,
+       .get_selection = imx290_get_selection,
 };
 
 static const struct v4l2_subdev_ops imx290_subdev_ops = {
@@ -926,16 +1235,35 @@ static s64 imx290_check_link_freqs(const struct imx290 *imx290,
        return 0;
 }
 
+static const struct of_device_id imx290_of_match[] = {
+       /*
+        * imx327 supports 1080p60 at 10 and 12bit.
+        * imx290 adds 10bit 1080p120.
+        * imx462 adds 10 and 12bit 1080p120.
+        * This driver currently maxes out at 1080p60, which is supported by all
+        * of them, but add the compatible strings for future implementation.
+        */
+       { .compatible = "sony,imx327", .data = imx290_colour_formats },
+       { .compatible = "sony,imx327-mono", .data = imx290_mono_formats },
+       { .compatible = "sony,imx290", .data = imx290_colour_formats },
+       { .compatible = "sony,imx290-mono", .data = imx290_mono_formats },
+       { .compatible = "sony,imx462", .data = imx290_colour_formats },
+       { .compatible = "sony,imx462-mono", .data = imx290_mono_formats },
+       { /* sentinel */ }
+};
+
 static int imx290_probe(struct i2c_client *client)
 {
+       struct v4l2_fwnode_device_properties props;
        struct device *dev = &client->dev;
        struct fwnode_handle *endpoint;
        /* Only CSI2 is supported for now: */
        struct v4l2_fwnode_endpoint ep = {
                .bus_type = V4L2_MBUS_CSI2_DPHY
        };
+       const struct of_device_id *match;
+       const struct imx290_mode *mode;
        struct imx290 *imx290;
-       u32 xclk_freq;
        s64 fq;
        int ret;
 
@@ -950,6 +1278,11 @@ static int imx290_probe(struct i2c_client *client)
                return -ENODEV;
        }
 
+       match = of_match_device(imx290_of_match, dev);
+       if (!match)
+               return -ENODEV;
+       imx290->formats = (const struct imx290_pixfmt *)match->data;
+
        endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL);
        if (!endpoint) {
                dev_err(dev, "Endpoint node not found\n");
@@ -973,6 +1306,7 @@ static int imx290_probe(struct i2c_client *client)
                ret = -EINVAL;
                goto free_err;
        }
+       imx290->hmax_min = IMX290_HMAX_MIN;
 
        dev_dbg(dev, "Using %u data lanes\n", imx290->nlanes);
 
@@ -999,21 +1333,21 @@ static int imx290_probe(struct i2c_client *client)
        }
 
        ret = fwnode_property_read_u32(dev_fwnode(dev), "clock-frequency",
-                                      &xclk_freq);
+                                      &imx290->xclk_freq);
        if (ret) {
                dev_err(dev, "Could not get xclk frequency\n");
                goto free_err;
        }
 
        /* external clock must be 37.125 MHz */
-       if (xclk_freq != 37125000) {
+       if (imx290->xclk_freq != 37125000 && imx290->xclk_freq != 74250000) {
                dev_err(dev, "External clock frequency %u is not supported\n",
-                       xclk_freq);
+                       imx290->xclk_freq);
                ret = -EINVAL;
                goto free_err;
        }
 
-       ret = clk_set_rate(imx290->xclk, xclk_freq);
+       ret = clk_set_rate(imx290->xclk, imx290->xclk_freq);
        if (ret) {
                dev_err(dev, "Could not set xclk frequency\n");
                goto free_err;
@@ -1042,10 +1376,35 @@ static int imx290_probe(struct i2c_client *client)
         */
        imx290_entity_init_cfg(&imx290->sd, NULL);
 
-       v4l2_ctrl_handler_init(&imx290->ctrls, 4);
+       v4l2_ctrl_handler_init(&imx290->ctrls, 11);
 
        v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops,
-                         V4L2_CID_GAIN, 0, 72, 1, 0);
+                         V4L2_CID_ANALOGUE_GAIN, 0, 100, 1, 0);
+
+       mode = imx290->current_mode;
+       imx290->hblank = v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops,
+                                          V4L2_CID_HBLANK,
+                                          imx290->hmax_min - mode->width,
+                                          IMX290_HMAX_MAX - mode->width, 1,
+                                          mode->hmax - mode->width);
+
+       imx290->vblank = v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops,
+                                          V4L2_CID_VBLANK,
+                                          mode->vmax - mode->height,
+                                          IMX290_VMAX_MAX - mode->height, 1,
+                                          mode->vmax - mode->height);
+
+       imx290->exposure = v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops,
+                                            V4L2_CID_EXPOSURE,
+                                            IMX290_EXPOSURE_MIN,
+                                            mode->vmax - 2,
+                                            IMX290_EXPOSURE_STEP,
+                                            mode->vmax - 2);
+
+       imx290->hflip = v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops,
+                                         V4L2_CID_HFLIP, 0, 1, 1, 0);
+       imx290->vflip = v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops,
+                                         V4L2_CID_VFLIP, 0, 1, 1, 0);
 
        imx290->link_freq =
                v4l2_ctrl_new_int_menu(&imx290->ctrls, &imx290_ctrl_ops,
@@ -1065,6 +1424,15 @@ static int imx290_probe(struct i2c_client *client)
                                     ARRAY_SIZE(imx290_test_pattern_menu) - 1,
                                     0, 0, imx290_test_pattern_menu);
 
+       ret = v4l2_fwnode_device_parse(&client->dev, &props);
+       if (ret)
+               goto free_ctrl;
+
+       ret = v4l2_ctrl_new_fwnode_properties(&imx290->ctrls, &imx290_ctrl_ops,
+                                             &props);
+       if (ret)
+               goto free_ctrl;
+
        imx290->sd.ctrl_handler = &imx290->ctrls;
 
        if (imx290->ctrls.error) {
@@ -1087,6 +1455,9 @@ static int imx290_probe(struct i2c_client *client)
                goto free_ctrl;
        }
 
+       /* Initialize the frame format (this also sets imx290->current_mode) */
+       imx290_entity_init_cfg(&imx290->sd, NULL);
+
        ret = v4l2_async_register_subdev(&imx290->sd);
        if (ret < 0) {
                dev_err(dev, "Could not register v4l2 device\n");
@@ -1138,10 +1509,6 @@ static int imx290_remove(struct i2c_client *client)
        return 0;
 }
 
-static const struct of_device_id imx290_of_match[] = {
-       { .compatible = "sony,imx290" },
-       { /* sentinel */ }
-};
 MODULE_DEVICE_TABLE(of, imx290_of_match);
 
 static struct i2c_driver imx290_i2c_driver = {
diff --git a/drivers/media/i2c/imx296.c b/drivers/media/i2c/imx296.c
new file mode 100644 (file)
index 0000000..c1e71ae
--- /dev/null
@@ -0,0 +1,1295 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for IMX296 CMOS Image Sensor from Sony
+ *
+ * Copyright 2019 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <linux/videodev2.h>
+
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-subdev.h>
+
+#define IMX296_PIXEL_ARRAY_WIDTH                       1456
+#define IMX296_PIXEL_ARRAY_HEIGHT                      1088
+
+#define IMX296_REG_8BIT(n)                             ((1 << 16) | (n))
+#define IMX296_REG_16BIT(n)                            ((2 << 16) | (n))
+#define IMX296_REG_24BIT(n)                            ((3 << 16) | (n))
+#define IMX296_REG_SIZE_SHIFT                          16
+#define IMX296_REG_ADDR_MASK                           0xffff
+
+#define IMX296_CTRL00                                  IMX296_REG_8BIT(0x3000)
+#define IMX296_CTRL00_STANDBY                          BIT(0)
+#define IMX296_CTRL08                                  IMX296_REG_8BIT(0x3008)
+#define IMX296_CTRL08_REGHOLD                          BIT(0)
+#define IMX296_CTRL0A                                  IMX296_REG_8BIT(0x300a)
+#define IMX296_CTRL0A_XMSTA                            BIT(0)
+#define IMX296_CTRL0B                                  IMX296_REG_8BIT(0x300b)
+#define IMX296_CTRL0B_TRIGEN                           BIT(0)
+#define IMX296_CTRL0D                                  IMX296_REG_8BIT(0x300d)
+#define IMX296_CTRL0D_WINMODE_ALL                      (0 << 0)
+#define IMX296_CTRL0D_WINMODE_FD_BINNING               (2 << 0)
+#define IMX296_CTRL0D_HADD_ON_BINNING                  BIT(5)
+#define IMX296_CTRL0D_SAT_CNT                          BIT(6)
+#define IMX296_CTRL0E                                  IMX296_REG_8BIT(0x300e)
+#define IMX296_CTRL0E_VREVERSE                         BIT(0)
+#define IMX296_CTRL0E_HREVERSE                         BIT(1)
+#define IMX296_VMAX                                    IMX296_REG_24BIT(0x3010)
+#define IMX296_HMAX                                    IMX296_REG_16BIT(0x3014)
+#define IMX296_TMDCTRL                                 IMX296_REG_8BIT(0x301d)
+#define IMX296_TMDCTRL_LATCH                           BIT(0)
+#define IMX296_TMDOUT                                  IMX296_REG_16BIT(0x301e)
+#define IMX296_TMDOUT_MASK                             0x3ff
+#define IMX296_WDSEL                                   IMX296_REG_8BIT(0x3021)
+#define IMX296_WDSEL_NORMAL                            (0 << 0)
+#define IMX296_WDSEL_MULTI_2                           (1 << 0)
+#define IMX296_WDSEL_MULTI_4                           (3 << 0)
+#define IMX296_BLKLEVELAUTO                            IMX296_REG_8BIT(0x3022)
+#define IMX296_BLKLEVELAUTO_ON                         0x01
+#define IMX296_BLKLEVELAUTO_OFF                                0xf0
+#define IMX296_SST                                     IMX296_REG_8BIT(0x3024)
+#define IMX296_SST_EN                                  BIT(0)
+#define IMX296_CTRLTOUT                                        IMX296_REG_8BIT(0x3026)
+#define IMX296_CTRLTOUT_TOUT1SEL_LOW                   (0 << 0)
+#define IMX296_CTRLTOUT_TOUT1SEL_PULSE                 (3 << 0)
+#define IMX296_CTRLTOUT_TOUT2SEL_LOW                   (0 << 2)
+#define IMX296_CTRLTOUT_TOUT2SEL_PULSE                 (3 << 2)
+#define IMX296_CTRLTRIG                                        IMX296_REG_8BIT(0x3029)
+#define IMX296_CTRLTRIG_TOUT1_SEL_LOW                  (0 << 0)
+#define IMX296_CTRLTRIG_TOUT1_SEL_PULSE1               (1 << 0)
+#define IMX296_CTRLTRIG_TOUT2_SEL_LOW                  (0 << 4)
+#define IMX296_CTRLTRIG_TOUT2_SEL_PULSE2               (2 << 4)
+#define IMX296_SYNCSEL                                 IMX296_REG_8BIT(0x3036)
+#define IMX296_SYNCSEL_NORMAL                          0xc0
+#define IMX296_SYNCSEL_HIZ                             0xf0
+#define IMX296_PULSE1                                  IMX296_REG_8BIT(0x306d)
+#define IMX296_PULSE1_EN_NOR                           BIT(0)
+#define IMX296_PULSE1_EN_TRIG                          BIT(1)
+#define IMX296_PULSE1_POL_HIGH                         (0 << 2)
+#define IMX296_PULSE1_POL_LOW                          (1 << 2)
+#define IMX296_PULSE1_UP                               IMX296_REG_24BIT(0x3070)
+#define IMX296_PULSE1_DN                               IMX296_REG_24BIT(0x3074)
+#define IMX296_PULSE2                                  IMX296_REG_8BIT(0x3079)
+#define IMX296_PULSE2_EN_NOR                           BIT(0)
+#define IMX296_PULSE2_EN_TRIG                          BIT(1)
+#define IMX296_PULSE2_POL_HIGH                         (0 << 2)
+#define IMX296_PULSE2_POL_LOW                          (1 << 2)
+#define IMX296_PULSE2_UP                               IMX296_REG_24BIT(0x307c)
+#define IMX296_PULSE2_DN                               IMX296_REG_24BIT(0x3080)
+#define IMX296_INCKSEL(n)                              IMX296_REG_8BIT(0x3089 + (n))
+#define IMX296_SHS1                                    IMX296_REG_24BIT(0x308d)
+#define IMX296_SHS2                                    IMX296_REG_24BIT(0x3090)
+#define IMX296_SHS3                                    IMX296_REG_24BIT(0x3094)
+#define IMX296_SHS4                                    IMX296_REG_24BIT(0x3098)
+#define IMX296_VBLANKLP                                        IMX296_REG_8BIT(0x309c)
+#define IMX296_VBLANKLP_NORMAL                         0x04
+#define IMX296_VBLANKLP_LOW_POWER                      0x2c
+#define IMX296_EXP_CNT                                 IMX296_REG_8BIT(0x30a3)
+#define IMX296_EXP_CNT_RESET                           BIT(0)
+#define IMX296_EXP_MAX                                 IMX296_REG_16BIT(0x30a6)
+#define IMX296_VINT                                    IMX296_REG_8BIT(0x30aa)
+#define IMX296_VINT_EN                                 BIT(0)
+#define IMX296_LOWLAGTRG                               IMX296_REG_8BIT(0x30ae)
+#define IMX296_LOWLAGTRG_FAST                          BIT(0)
+#define IMX296_I2CCTRL                                 IMX296_REG_8BIT(0x30ef)
+#define IMX296_I2CCTRL_I2CACKEN                                BIT(0)
+
+#define IMX296_SENSOR_INFO                             IMX296_REG_16BIT(0x3148)
+#define IMX296_SENSOR_INFO_MONO                                BIT(15)
+#define IMX296_S_SHSA                                  IMX296_REG_16BIT(0x31ca)
+#define IMX296_S_SHSB                                  IMX296_REG_16BIT(0x31d2)
+/*
+ * Registers 0x31c8 to 0x31cd, 0x31d0 to 0x31d5, 0x31e2, 0x31e3, 0x31ea and
+ * 0x31eb are related to exposure mode but otherwise not documented.
+ */
+
+#define IMX296_GAINCTRL                                        IMX296_REG_8BIT(0x3200)
+#define IMX296_GAINCTRL_WD_GAIN_MODE_NORMAL            0x01
+#define IMX296_GAINCTRL_WD_GAIN_MODE_MULTI             0x41
+#define IMX296_GAIN                                    IMX296_REG_16BIT(0x3204)
+#define IMX296_GAIN_MIN                                        0
+#define IMX296_GAIN_MAX                                        480
+#define IMX296_GAIN1                                   IMX296_REG_16BIT(0x3208)
+#define IMX296_GAIN2                                   IMX296_REG_16BIT(0x320c)
+#define IMX296_GAIN3                                   IMX296_REG_16BIT(0x3210)
+#define IMX296_GAINDLY                                 IMX296_REG_8BIT(0x3212)
+#define IMX296_GAINDLY_NONE                            0x08
+#define IMX296_GAINDLY_1FRAME                          0x09
+#define IMX296_PGCTRL                                  IMX296_REG_8BIT(0x3238)
+#define IMX296_PGCTRL_REGEN                            BIT(0)
+#define IMX296_PGCTRL_THRU                             BIT(1)
+#define IMX296_PGCTRL_CLKEN                            BIT(2)
+#define IMX296_PGCTRL_MODE(n)                          ((n) << 3)
+#define IMX296_PGHPOS                                  IMX296_REG_16BIT(0x3239)
+#define IMX296_PGVPOS                                  IMX296_REG_16BIT(0x323c)
+#define IMX296_PGHPSTEP                                        IMX296_REG_8BIT(0x323e)
+#define IMX296_PGVPSTEP                                        IMX296_REG_8BIT(0x323f)
+#define IMX296_PGHPNUM                                 IMX296_REG_8BIT(0x3240)
+#define IMX296_PGVPNUM                                 IMX296_REG_8BIT(0x3241)
+#define IMX296_PGDATA1                                 IMX296_REG_16BIT(0x3244)
+#define IMX296_PGDATA2                                 IMX296_REG_16BIT(0x3246)
+#define IMX296_PGHGSTEP                                        IMX296_REG_8BIT(0x3249)
+#define IMX296_BLKLEVEL                                        IMX296_REG_16BIT(0x3254)
+
+#define IMX296_FID0_ROI                                        IMX296_REG_8BIT(0x3300)
+#define IMX296_FID0_ROIH1ON                            BIT(0)
+#define IMX296_FID0_ROIV1ON                            BIT(1)
+#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_ROIWV1                             IMX296_REG_16BIT(0x3316)
+#define IMX296_FID0_ROIWV1_MIN                         4
+
+#define IMX296_CM_HSST_STARTTMG                                IMX296_REG_16BIT(0x4018)
+#define IMX296_CM_HSST_ENDTMG                          IMX296_REG_16BIT(0x401a)
+#define IMX296_DA_HSST_STARTTMG                                IMX296_REG_16BIT(0x404d)
+#define IMX296_DA_HSST_ENDTMG                          IMX296_REG_16BIT(0x4050)
+#define IMX296_LM_HSST_STARTTMG                                IMX296_REG_16BIT(0x4094)
+#define IMX296_LM_HSST_ENDTMG                          IMX296_REG_16BIT(0x4096)
+#define IMX296_SST_SIEASTA1_SET                                IMX296_REG_8BIT(0x40c9)
+#define IMX296_SST_SIEASTA1PRE_1U                      IMX296_REG_16BIT(0x40cc)
+#define IMX296_SST_SIEASTA1PRE_1D                      IMX296_REG_16BIT(0x40ce)
+#define IMX296_SST_SIEASTA1PRE_2U                      IMX296_REG_16BIT(0x40d0)
+#define IMX296_SST_SIEASTA1PRE_2D                      IMX296_REG_16BIT(0x40d2)
+#define IMX296_HSST                                    IMX296_REG_8BIT(0x40dc)
+#define IMX296_HSST_EN                                 BIT(2)
+
+#define IMX296_CKREQSEL                                        IMX296_REG_8BIT(0x4101)
+#define IMX296_CKREQSEL_HS                             BIT(2)
+#define IMX296_GTTABLENUM                              IMX296_REG_8BIT(0x4114)
+#define IMX296_CTRL418C                                        IMX296_REG_8BIT(0x418c)
+
+#define IMX296_STANDBY_DELAY           1500
+#define IMX296_STREAM_ON_DELAY         2000
+#define IMX296_STREAM_OFF_DELAY                2000
+
+struct imx296_clk_params {
+       unsigned int freq;
+       u8 incksel[4];
+       u8 ctrl418c;
+};
+
+static const struct imx296_clk_params imx296_clk_params[] = {
+       { 37125000, { 0x80, 0x0b, 0x80, 0x08 }, 116 },
+       { 54000000, { 0xb0, 0x0f, 0xb0, 0x0c }, 168 },
+       { 74250000, { 0x80, 0x0f, 0x80, 0x0c }, 232 },
+};
+
+static const char * const imx296_supply_names[] = {
+       "dvdd",
+       "ovdd",
+       "avdd",
+};
+
+struct imx296 {
+       struct device *dev;
+       struct clk *clk;
+       struct regulator_bulk_data supplies[ARRAY_SIZE(imx296_supply_names)];
+       struct gpio_desc *reset;
+       struct regmap *regmap;
+
+       const struct imx296_clk_params *clk_params;
+       bool mono;
+
+       bool streaming;                 /* Protected by ctrls.lock */
+
+       struct v4l2_subdev subdev;
+       struct media_pad pad;
+
+       struct v4l2_ctrl_handler ctrls;
+       struct v4l2_ctrl *hblank;
+       struct v4l2_ctrl *vblank;
+       struct v4l2_ctrl *exposure;
+       struct v4l2_ctrl *vflip;
+       struct v4l2_ctrl *hflip;
+
+       struct mutex lock;              /* Protects format and crop */
+       struct v4l2_mbus_framefmt format;
+       struct v4l2_rect crop;
+};
+
+static inline struct imx296 *to_imx296(struct v4l2_subdev *sd)
+{
+       return container_of(sd, struct imx296, subdev);
+}
+
+static int imx296_read(struct imx296 *sensor, u32 addr)
+{
+       u8 data[3] = { 0, 0, 0 };
+       int ret;
+
+       ret = regmap_raw_read(sensor->regmap, addr & IMX296_REG_ADDR_MASK, data,
+                             (addr >> IMX296_REG_SIZE_SHIFT) & 3);
+       if (ret < 0)
+               return ret;
+
+       return (data[2] << 16) | (data[1] << 8) | data[0];
+}
+
+static int imx296_write(struct imx296 *sensor, u32 addr, u32 value, int *err)
+{
+       u8 data[3] = { value & 0xff, (value >> 8) & 0xff, value >> 16 };
+       int ret;
+
+       if (err && *err)
+               return *err;
+
+       ret = regmap_raw_write(sensor->regmap, addr & IMX296_REG_ADDR_MASK,
+                              data, (addr >> IMX296_REG_SIZE_SHIFT) & 3);
+       if (ret < 0) {
+               dev_err(sensor->dev, "%u-bit write to 0x%04x failed: %d\n",
+                       ((addr >> IMX296_REG_SIZE_SHIFT) & 3) * 8,
+                       addr & IMX296_REG_ADDR_MASK, ret);
+               if (err)
+                       *err = ret;
+       }
+
+       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(struct imx296 *sensor)
+{
+       unsigned int i = 0;
+
+       lockdep_assert_held(&sensor->lock);
+
+       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];
+}
+
+/* -----------------------------------------------------------------------------
+ * Controls
+ */
+
+static const char * const imx296_test_pattern_menu[] = {
+       "Disabled",
+       "Multiple Pixels",
+       "Sequence 1",
+       "Sequence 2",
+       "Gradient",
+       "Row",
+       "Column",
+       "Cross",
+       "Stripe",
+       "Checks",
+};
+
+static const s64 imx296_link_freq_menu[] = {
+       1188000000ULL,
+};
+
+static void imx296_adjust_exposure_range(struct imx296 *sensor,
+                                        struct v4l2_ctrl *ctrl)
+{
+       int exposure_max, exposure_def;
+
+       /* Honour the new VBLANK limits when setting exposure */
+       exposure_max = (sensor->format.height + sensor->vblank->val) - 4;
+       exposure_def = min(exposure_max, sensor->exposure->val);
+
+       __v4l2_ctrl_modify_range(sensor->exposure, sensor->exposure->minimum,
+                                exposure_max, sensor->exposure->step,
+                                exposure_def);
+}
+
+static int imx296_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+       struct imx296 *sensor = container_of(ctrl->handler, struct imx296, ctrls);
+       unsigned int vmax;
+       int ret = 0;
+
+       if (ctrl->id == V4L2_CID_VBLANK) {
+               /*
+                * This control may change the limits of usable exposure, so
+                * check and adjust if necessary.
+                */
+               imx296_adjust_exposure_range(sensor, ctrl);
+       }
+
+       if (!sensor->streaming)
+               return 0;
+
+       switch (ctrl->id) {
+       case V4L2_CID_EXPOSURE:
+               /* Clamp the exposure value to VMAX. */
+               vmax = sensor->format.height + sensor->vblank->cur.val;
+               ctrl->val = min_t(int, ctrl->val, vmax);
+               imx296_write(sensor, IMX296_SHS1, vmax - ctrl->val, &ret);
+               break;
+
+       case V4L2_CID_ANALOGUE_GAIN:
+               imx296_write(sensor, IMX296_GAIN, ctrl->val, &ret);
+               break;
+
+       case V4L2_CID_VBLANK:
+               imx296_write(sensor, IMX296_VMAX,
+                            sensor->format.height + ctrl->val, &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);
+                       imx296_write(sensor, IMX296_PGVPOS, 8, &ret);
+                       imx296_write(sensor, IMX296_PGHPSTEP, 8, &ret);
+                       imx296_write(sensor, IMX296_PGVPSTEP, 8, &ret);
+                       imx296_write(sensor, IMX296_PGHPNUM, 100, &ret);
+                       imx296_write(sensor, IMX296_PGVPNUM, 100, &ret);
+                       imx296_write(sensor, IMX296_PGDATA1, 0x300, &ret);
+                       imx296_write(sensor, IMX296_PGDATA2, 0x100, &ret);
+                       imx296_write(sensor, IMX296_PGHGSTEP, 0, &ret);
+                       imx296_write(sensor, IMX296_BLKLEVEL, 0, &ret);
+                       imx296_write(sensor, IMX296_BLKLEVELAUTO,
+                                    IMX296_BLKLEVELAUTO_OFF, &ret);
+                       imx296_write(sensor, IMX296_PGCTRL,
+                                    IMX296_PGCTRL_REGEN |
+                                    IMX296_PGCTRL_CLKEN |
+                                    IMX296_PGCTRL_MODE(ctrl->val - 1), &ret);
+               } else {
+                       imx296_write(sensor, IMX296_PGCTRL,
+                                    IMX296_PGCTRL_CLKEN, &ret);
+                       imx296_write(sensor, IMX296_BLKLEVEL, 0x3c, &ret);
+                       imx296_write(sensor, IMX296_BLKLEVELAUTO,
+                                    IMX296_BLKLEVELAUTO_ON, &ret);
+               }
+               break;
+
+       default:
+               ret = -EINVAL;
+               break;
+       }
+
+       return ret;
+}
+
+static const struct v4l2_ctrl_ops imx296_ctrl_ops = {
+       .s_ctrl = imx296_s_ctrl,
+};
+
+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);
+       if (ret < 0)
+               return ret;
+
+       v4l2_ctrl_handler_init(&sensor->ctrls, 12);
+
+       sensor->exposure = v4l2_ctrl_new_std(&sensor->ctrls, &imx296_ctrl_ops,
+                                          V4L2_CID_EXPOSURE, 1, 1048575, 1,
+                                          1104);
+       v4l2_ctrl_new_std(&sensor->ctrls, &imx296_ctrl_ops,
+                         V4L2_CID_ANALOGUE_GAIN, IMX296_GAIN_MIN,
+                         IMX296_GAIN_MAX, 1, IMX296_GAIN_MIN);
+
+       sensor->hflip = v4l2_ctrl_new_std(&sensor->ctrls, &imx296_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, &imx296_ctrl_ops,
+                                         V4L2_CID_VFLIP, 0, 1, 1, 0);
+       if (sensor->vflip)
+               sensor->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT;
+
+       /*
+        * 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->vblank = v4l2_ctrl_new_std(&sensor->ctrls, &imx296_ctrl_ops,
+                                          V4L2_CID_VBLANK, 30,
+                                          1048575 - IMX296_PIXEL_ARRAY_HEIGHT,
+                                          1, 30);
+       /*
+        * The sensor calculates the MIPI timings internally to achieve a bit
+        * rate between 1122 and 1198 Mbps. The exact value is unfortunately not
+        * reported, at least according to the documentation. Report a nominal
+        * rate of 1188 Mbps as that is used by the datasheet in multiple
+        * examples.
+        */
+       v4l2_ctrl_new_std(&sensor->ctrls, NULL, V4L2_CID_PIXEL_RATE,
+                         1122000000 / 10, 1198000000 / 10, 1, 1188000000 / 10);
+       v4l2_ctrl_new_std_menu_items(&sensor->ctrls, &imx296_ctrl_ops,
+                                    V4L2_CID_TEST_PATTERN,
+                                    ARRAY_SIZE(imx296_test_pattern_menu) - 1,
+                                    0, 0, imx296_test_pattern_menu);
+
+       v4l2_ctrl_new_int_menu(&sensor->ctrls, NULL, V4L2_CID_LINK_FREQ,
+                              ARRAY_SIZE(imx296_link_freq_menu) - 1, 0,
+                              imx296_link_freq_menu);
+
+       v4l2_ctrl_new_fwnode_properties(&sensor->ctrls, &imx296_ctrl_ops,
+                                       &props);
+
+       if (sensor->ctrls.error) {
+               dev_err(sensor->dev, "failed to add controls (%d)\n",
+                       sensor->ctrls.error);
+               v4l2_ctrl_handler_free(&sensor->ctrls);
+               return sensor->ctrls.error;
+       }
+
+       sensor->subdev.ctrl_handler = &sensor->ctrls;
+
+       return 0;
+}
+
+/* -----------------------------------------------------------------------------
+ * V4L2 Subdev Operations
+ */
+
+/*
+ * This table is extracted from vendor data that is entirely undocumented. The
+ * first register write is required to activate the CSI-2 output. The other
+ * entries may or may not be optional?
+ */
+static const struct {
+       unsigned int reg;
+       unsigned int value;
+} imx296_init_table[] = {
+       { IMX296_REG_8BIT(0x3005), 0xf0 },
+       { IMX296_REG_8BIT(0x309e), 0x04 },
+       { IMX296_REG_8BIT(0x30a0), 0x04 },
+       { IMX296_REG_8BIT(0x30a1), 0x3c },
+       { IMX296_REG_8BIT(0x30a4), 0x5f },
+       { IMX296_REG_8BIT(0x30a8), 0x91 },
+       { IMX296_REG_8BIT(0x30ac), 0x28 },
+       { IMX296_REG_8BIT(0x30af), 0x09 },
+       { IMX296_REG_8BIT(0x30df), 0x00 },
+       { IMX296_REG_8BIT(0x3165), 0x00 },
+       { IMX296_REG_8BIT(0x3169), 0x10 },
+       { IMX296_REG_8BIT(0x316a), 0x02 },
+       { IMX296_REG_8BIT(0x31c8), 0xf3 },      /* Exposure-related */
+       { IMX296_REG_8BIT(0x31d0), 0xf4 },      /* Exposure-related */
+       { IMX296_REG_8BIT(0x321a), 0x00 },
+       { IMX296_REG_8BIT(0x3226), 0x02 },
+       { IMX296_REG_8BIT(0x3256), 0x01 },
+       { IMX296_REG_8BIT(0x3541), 0x72 },
+       { IMX296_REG_8BIT(0x3516), 0x77 },
+       { IMX296_REG_8BIT(0x350b), 0x7f },
+       { IMX296_REG_8BIT(0x3758), 0xa3 },
+       { IMX296_REG_8BIT(0x3759), 0x00 },
+       { IMX296_REG_8BIT(0x375a), 0x85 },
+       { IMX296_REG_8BIT(0x375b), 0x00 },
+       { IMX296_REG_8BIT(0x3832), 0xf5 },
+       { IMX296_REG_8BIT(0x3833), 0x00 },
+       { IMX296_REG_8BIT(0x38a2), 0xf6 },
+       { IMX296_REG_8BIT(0x38a3), 0x00 },
+       { IMX296_REG_8BIT(0x3a00), 0x80 },
+       { IMX296_REG_8BIT(0x3d48), 0xa3 },
+       { IMX296_REG_8BIT(0x3d49), 0x00 },
+       { IMX296_REG_8BIT(0x3d4a), 0x85 },
+       { IMX296_REG_8BIT(0x3d4b), 0x00 },
+       { IMX296_REG_8BIT(0x400e), 0x58 },
+       { IMX296_REG_8BIT(0x4014), 0x1c },
+       { IMX296_REG_8BIT(0x4041), 0x2a },
+       { IMX296_REG_8BIT(0x40a2), 0x06 },
+       { IMX296_REG_8BIT(0x40c1), 0xf6 },
+       { IMX296_REG_8BIT(0x40c7), 0x0f },
+       { IMX296_REG_8BIT(0x40c8), 0x00 },
+       { IMX296_REG_8BIT(0x4174), 0x00 },
+};
+
+static int imx296_setup(struct imx296 *sensor)
+{
+       const struct v4l2_mbus_framefmt *format = &sensor->format;
+       const struct v4l2_rect *crop = &sensor->crop;
+       unsigned int i;
+       int ret = 0;
+
+       for (i = 0; i < ARRAY_SIZE(imx296_init_table); ++i)
+               imx296_write(sensor, imx296_init_table[i].reg,
+                            imx296_init_table[i].value, &ret);
+
+       if (crop->width != IMX296_PIXEL_ARRAY_WIDTH ||
+           crop->height != IMX296_PIXEL_ARRAY_HEIGHT) {
+               imx296_write(sensor, IMX296_FID0_ROI,
+                            IMX296_FID0_ROIH1ON | IMX296_FID0_ROIV1ON, &ret);
+               imx296_write(sensor, IMX296_FID0_ROIPH1, crop->left, &ret);
+               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);
+       } else {
+               imx296_write(sensor, IMX296_FID0_ROI, 0, &ret);
+       }
+
+       imx296_write(sensor, IMX296_CTRL0D,
+                    (crop->width != format->width ?
+                     IMX296_CTRL0D_HADD_ON_BINNING : 0) |
+                    (crop->height != format->height ?
+                     IMX296_CTRL0D_WINMODE_FD_BINNING : 0),
+                    &ret);
+
+       /*
+        * HMAX and VMAX configure horizontal and vertical blanking by
+        * specifying the total line time and frame time respectively. The line
+        * time is specified in operational clock units (which appears to be the
+        * output of an internal PLL, fixed at 74.25 MHz regardless of the
+        * exernal clock frequency), while the frame time is specified as a
+        * number of lines.
+        *
+        * In the vertical direction the sensor outputs the following:
+        *
+        * - one line for the FS packet
+        * - two lines of embedded data (DT 0x12)
+        * - six null lines (DT 0x10)
+        * - four lines of vertical effective optical black (DT 0x37)
+        * - 8 to 1088 lines of active image data (RAW10, DT 0x2b)
+        * - one line for the FE packet
+        * - 16 or more lines of vertical blanking
+        */
+       imx296_write(sensor, IMX296_HMAX, 1100, &ret);
+       imx296_write(sensor, IMX296_VMAX,
+                    format->height + sensor->vblank->cur.val, &ret);
+
+       for (i = 0; i < ARRAY_SIZE(sensor->clk_params->incksel); ++i)
+               imx296_write(sensor, IMX296_INCKSEL(i),
+                            sensor->clk_params->incksel[i], &ret);
+       imx296_write(sensor, IMX296_GTTABLENUM, 0xc5, &ret);
+       imx296_write(sensor, IMX296_CTRL418C, sensor->clk_params->ctrl418c,
+                    &ret);
+
+       imx296_write(sensor, IMX296_GAINDLY, IMX296_GAINDLY_1FRAME, &ret);
+       imx296_write(sensor, IMX296_BLKLEVEL, 0x03c, &ret);
+
+       if (ret < 0)
+               return ret;
+
+       return __v4l2_ctrl_handler_setup(&sensor->ctrls);
+}
+
+static int imx296_stream_on(struct imx296 *sensor)
+{
+       int ret = 0;
+
+       imx296_write(sensor, IMX296_CTRL00, 0, &ret);
+       usleep_range(IMX296_STREAM_ON_DELAY, 2*IMX296_STREAM_ON_DELAY);
+       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;
+}
+
+static int imx296_stream_off(struct imx296 *sensor)
+{
+       int ret = 0;
+
+       imx296_write(sensor, IMX296_CTRL0A, IMX296_CTRL0A_XMSTA, &ret);
+       usleep_range(IMX296_STREAM_OFF_DELAY, 2*IMX296_STREAM_OFF_DELAY);
+       imx296_write(sensor, IMX296_CTRL00, IMX296_CTRL00_STANDBY, &ret);
+
+       __v4l2_ctrl_grab(sensor->vflip, 0);
+       __v4l2_ctrl_grab(sensor->hflip, 0);
+
+       return ret;
+}
+
+static int imx296_s_stream(struct v4l2_subdev *sd, int enable)
+{
+       struct imx296 *sensor = to_imx296(sd);
+       int ret;
+
+       if (!enable) {
+               ret = imx296_stream_off(sensor);
+
+               pm_runtime_mark_last_busy(sensor->dev);
+               pm_runtime_put_autosuspend(sensor->dev);
+
+               mutex_lock(sensor->ctrls.lock);
+               sensor->streaming = false;
+               mutex_unlock(sensor->ctrls.lock);
+
+               return ret;
+       }
+
+       mutex_lock(sensor->ctrls.lock);
+
+       ret = pm_runtime_get_sync(sensor->dev);
+       if (ret < 0)
+               goto done;
+
+       ret = imx296_setup(sensor);
+       if (ret < 0)
+               goto done;
+
+       /*
+        * Set streaming to true to ensure __v4l2_ctrl_handler_setup() will set
+        * the controls. The flag is reset to false further down if an error
+        * occurs.
+        */
+       sensor->streaming = true;
+
+       ret = __v4l2_ctrl_handler_setup(&sensor->ctrls);
+       if (ret < 0)
+               goto done;
+
+       ret = imx296_stream_on(sensor);
+
+done:
+       if (ret < 0) {
+               /*
+                * In case of error, turn the power off synchronously as the
+                * device likely has no other chance to recover.
+                */
+               pm_runtime_put_sync(sensor->dev);
+               sensor->streaming = false;
+       }
+
+       mutex_unlock(sensor->ctrls.lock);
+
+       return ret;
+}
+
+static int imx296_enum_mbus_code(struct v4l2_subdev *sd,
+                                struct v4l2_subdev_state *state,
+                                struct v4l2_subdev_mbus_code_enum *code)
+{
+       struct imx296 *sensor = to_imx296(sd);
+
+       if (code->index != 0)
+               return -EINVAL;
+
+       code->code = imx296_mbus_code(sensor);
+
+       return 0;
+}
+
+static int imx296_enum_frame_size(struct v4l2_subdev *sd,
+                                 struct v4l2_subdev_state *state,
+                                 struct v4l2_subdev_frame_size_enum *fse)
+{
+       struct imx296 *sensor = to_imx296(sd);
+
+       if (fse->index >= 1 || fse->code != imx296_mbus_code(sensor))
+               return -EINVAL;
+
+       fse->min_width = IMX296_PIXEL_ARRAY_WIDTH / (fse->index + 1);
+       fse->max_width = fse->min_width;
+       fse->min_height = IMX296_PIXEL_ARRAY_HEIGHT / (fse->index + 1);
+       fse->max_height = fse->min_height;
+
+       return 0;
+}
+
+static struct v4l2_mbus_framefmt *
+imx296_get_pad_format(struct imx296 *sensor, struct v4l2_subdev_state *state,
+                     unsigned int pad, u32 which)
+{
+       switch (which) {
+       case V4L2_SUBDEV_FORMAT_TRY:
+               return v4l2_subdev_get_try_format(&sensor->subdev, state, pad);
+       case V4L2_SUBDEV_FORMAT_ACTIVE:
+               return &sensor->format;
+       default:
+               return NULL;
+       }
+}
+
+static struct v4l2_rect *
+imx296_get_pad_crop(struct imx296 *sensor, struct v4l2_subdev_state *state,
+                   unsigned int pad, u32 which)
+{
+       switch (which) {
+       case V4L2_SUBDEV_FORMAT_TRY:
+               return v4l2_subdev_get_try_crop(&sensor->subdev, state, pad);
+       case V4L2_SUBDEV_FORMAT_ACTIVE:
+               return &sensor->crop;
+       default:
+               return NULL;
+       }
+}
+
+static int imx296_get_format(struct v4l2_subdev *sd,
+                            struct v4l2_subdev_state *state,
+                            struct v4l2_subdev_format *fmt)
+{
+       struct imx296 *sensor = to_imx296(sd);
+
+       mutex_lock(&sensor->lock);
+       fmt->format = *imx296_get_pad_format(sensor, state, fmt->pad,
+                                            fmt->which);
+       mutex_unlock(&sensor->lock);
+
+       return 0;
+}
+
+static int imx296_set_format(struct v4l2_subdev *sd,
+                            struct v4l2_subdev_state *state,
+                            struct v4l2_subdev_format *fmt)
+{
+       struct imx296 *sensor = to_imx296(sd);
+       struct v4l2_mbus_framefmt *format;
+       struct v4l2_rect *crop;
+
+       crop = imx296_get_pad_crop(sensor, state, fmt->pad, fmt->which);
+       format = imx296_get_pad_format(sensor, state, fmt->pad, fmt->which);
+
+       mutex_lock(&sensor->lock);
+
+       /*
+        * 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->code = imx296_mbus_code(sensor);
+       format->field = V4L2_FIELD_NONE;
+       format->colorspace = V4L2_COLORSPACE_RAW;
+       format->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
+       format->quantization = V4L2_QUANTIZATION_FULL_RANGE;
+       format->xfer_func = V4L2_XFER_FUNC_NONE;
+
+       fmt->format = *format;
+
+       mutex_unlock(&sensor->lock);
+
+       return 0;
+}
+
+static int imx296_get_selection(struct v4l2_subdev *sd,
+                               struct v4l2_subdev_state *state,
+                               struct v4l2_subdev_selection *sel)
+{
+       struct imx296 *sensor = to_imx296(sd);
+
+       switch (sel->target) {
+       case V4L2_SEL_TGT_CROP:
+               mutex_lock(&sensor->lock);
+               sel->r = *imx296_get_pad_crop(sensor, state, sel->pad,
+                                             sel->which);
+               mutex_unlock(&sensor->lock);
+               break;
+
+       case V4L2_SEL_TGT_CROP_DEFAULT:
+       case V4L2_SEL_TGT_CROP_BOUNDS:
+       case V4L2_SEL_TGT_NATIVE_SIZE:
+               sel->r.left = 0;
+               sel->r.top = 0;
+               sel->r.width = IMX296_PIXEL_ARRAY_WIDTH;
+               sel->r.height = IMX296_PIXEL_ARRAY_HEIGHT;
+               break;
+
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int imx296_set_selection(struct v4l2_subdev *sd,
+                               struct v4l2_subdev_state *state,
+                               struct v4l2_subdev_selection *sel)
+{
+       struct imx296 *sensor = to_imx296(sd);
+       struct v4l2_mbus_framefmt *format;
+       struct v4l2_rect *crop;
+       struct v4l2_rect rect;
+
+       if (sel->target != V4L2_SEL_TGT_CROP)
+               return -EINVAL;
+
+       /*
+        * Clamp the crop rectangle boundaries and align them to a multiple of 4
+        * pixels to satisfy hardware requirements.
+        */
+       rect.left = clamp(ALIGN(sel->r.left, 4), 0,
+                         IMX296_PIXEL_ARRAY_WIDTH - IMX296_FID0_ROIWH1_MIN);
+       rect.top = clamp(ALIGN(sel->r.top, 4), 0,
+                         IMX296_PIXEL_ARRAY_HEIGHT - IMX296_FID0_ROIWV1_MIN);
+       rect.width = clamp_t(unsigned int, ALIGN(sel->r.width, 4),
+                            IMX296_FID0_ROIWH1_MIN, IMX296_PIXEL_ARRAY_WIDTH);
+       rect.height = clamp_t(unsigned int, ALIGN(sel->r.height, 4),
+                             IMX296_FID0_ROIWV1_MIN, IMX296_PIXEL_ARRAY_HEIGHT);
+
+       rect.width = min_t(unsigned int, rect.width,
+                          IMX296_PIXEL_ARRAY_WIDTH - rect.left);
+       rect.height = min_t(unsigned int, rect.height,
+                           IMX296_PIXEL_ARRAY_HEIGHT - rect.top);
+
+       crop = imx296_get_pad_crop(sensor, state, sel->pad, sel->which);
+
+       mutex_lock(&sensor->lock);
+
+       if (rect.width != crop->width || rect.height != crop->height) {
+               /*
+                * Reset the output image size if the crop rectangle size has
+                * been modified.
+                */
+               format = imx296_get_pad_format(sensor, state, sel->pad,
+                                              sel->which);
+               format->width = rect.width;
+               format->height = rect.height;
+       }
+
+       *crop = rect;
+       sel->r = rect;
+
+       mutex_unlock(&sensor->lock);
+
+       return 0;
+}
+
+static int imx296_init_cfg(struct v4l2_subdev *sd,
+                          struct v4l2_subdev_state *state)
+{
+       struct v4l2_subdev_selection sel = {
+               .target = V4L2_SEL_TGT_CROP,
+               .which = state ? V4L2_SUBDEV_FORMAT_TRY
+                      : V4L2_SUBDEV_FORMAT_ACTIVE,
+               .r.width = IMX296_PIXEL_ARRAY_WIDTH,
+               .r.height = IMX296_PIXEL_ARRAY_HEIGHT,
+       };
+       struct v4l2_subdev_format format = {
+               .which = state ? V4L2_SUBDEV_FORMAT_TRY
+                      : V4L2_SUBDEV_FORMAT_ACTIVE,
+               .format = {
+                       .width = IMX296_PIXEL_ARRAY_WIDTH,
+                       .height = IMX296_PIXEL_ARRAY_HEIGHT,
+               },
+       };
+
+       imx296_set_selection(sd, state, &sel);
+       imx296_set_format(sd, state, &format);
+
+       return 0;
+}
+
+static const struct v4l2_subdev_video_ops imx296_subdev_video_ops = {
+       .s_stream = imx296_s_stream,
+};
+
+static const struct v4l2_subdev_pad_ops imx296_subdev_pad_ops = {
+       .enum_mbus_code = imx296_enum_mbus_code,
+       .enum_frame_size = imx296_enum_frame_size,
+       .get_fmt = imx296_get_format,
+       .set_fmt = imx296_set_format,
+       .get_selection = imx296_get_selection,
+       .set_selection = imx296_set_selection,
+       .init_cfg = imx296_init_cfg,
+};
+
+static const struct v4l2_subdev_ops imx296_subdev_ops = {
+       .video = &imx296_subdev_video_ops,
+       .pad = &imx296_subdev_pad_ops,
+};
+
+/* -----------------------------------------------------------------------------
+ * Power management
+ */
+
+static int imx296_power_on(struct imx296 *sensor)
+{
+       int ret;
+
+       ret = regulator_bulk_enable(ARRAY_SIZE(sensor->supplies),
+                                   sensor->supplies);
+       if (ret < 0)
+               return ret;
+
+       udelay(1);
+
+       ret = gpiod_direction_output(sensor->reset, 0);
+       if (ret < 0)
+               goto err_supply;
+
+       udelay(1);
+
+       ret = clk_prepare_enable(sensor->clk);
+       if (ret < 0)
+               goto err_reset;
+
+       /*
+        * The documentation doesn't explicitly say how much time is required
+        * after providing a clock and before starting I2C communication. It
+        * mentions a delay of 20µs in 4-wire mode, but tests showed that a
+        * delay of 100µs resulted in I2C communication failures, while 500µs
+        * seems to be enough. Be conservative.
+        */
+       usleep_range(1000, 2000);
+
+       return 0;
+
+err_reset:
+       gpiod_direction_output(sensor->reset, 1);
+err_supply:
+       regulator_bulk_disable(ARRAY_SIZE(sensor->supplies), sensor->supplies);
+       return ret;
+}
+
+static void imx296_power_off(struct imx296 *sensor)
+{
+       clk_disable_unprepare(sensor->clk);
+       gpiod_direction_output(sensor->reset, 1);
+       regulator_bulk_disable(ARRAY_SIZE(sensor->supplies), sensor->supplies);
+}
+
+static int __maybe_unused imx296_runtime_resume(struct device *dev)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct v4l2_subdev *subdev = i2c_get_clientdata(client);
+       struct imx296 *sensor = to_imx296(subdev);
+
+       return imx296_power_on(sensor);
+}
+
+static int __maybe_unused imx296_runtime_suspend(struct device *dev)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct v4l2_subdev *subdev = i2c_get_clientdata(client);
+       struct imx296 *sensor = to_imx296(subdev);
+
+       imx296_power_off(sensor);
+
+       return 0;
+}
+
+static const struct dev_pm_ops imx296_pm_ops = {
+       SET_RUNTIME_PM_OPS(imx296_runtime_suspend, imx296_runtime_resume, NULL)
+};
+
+/* -----------------------------------------------------------------------------
+ * Probe & Remove
+ */
+
+static int imx296_read_temperature(struct imx296 *sensor, int *temp)
+{
+       int tmdout;
+       int ret;
+
+       ret = imx296_write(sensor, IMX296_TMDCTRL, IMX296_TMDCTRL_LATCH, NULL);
+       if (ret < 0)
+               return ret;
+
+       tmdout = imx296_read(sensor, IMX296_TMDOUT) & IMX296_TMDOUT_MASK;
+       if (tmdout < 0)
+               return tmdout;
+
+       /* T(°C) = 246.312 - 0.304 * TMDOUT */;
+       *temp = 246312 - 304 * tmdout;
+
+       return imx296_write(sensor, IMX296_TMDCTRL, 0, NULL);
+}
+
+static int imx296_identify_model(struct imx296 *sensor)
+{
+       unsigned int model;
+       int temp = 0;
+       int ret;
+
+       /*
+        * While most registers can be read when the sensor is in standby, this
+        * is not the case of the sensor info register :-(
+        */
+       ret = imx296_write(sensor, IMX296_CTRL00, 0, NULL);
+       if (ret < 0) {
+               dev_err(sensor->dev,
+                       "failed to get sensor out of standby (%d)\n", ret);
+               return ret;
+       }
+
+       usleep_range(IMX296_STANDBY_DELAY, 2*IMX296_STANDBY_DELAY);
+
+       ret = imx296_read(sensor, IMX296_SENSOR_INFO);
+       if (ret < 0) {
+               dev_err(sensor->dev, "failed to read sensor information (%d)\n",
+                       ret);
+               goto done;
+       }
+
+       model = (ret >> 6) & 0x1ff;
+
+       switch (model) {
+       case 296:
+               sensor->mono = ret & IMX296_SENSOR_INFO_MONO;
+               break;
+       /*
+        * The IMX297 seems to share features with the IMX296, it may be
+        * possible to support it in the same driver.
+        */
+       case 297:
+       default:
+               dev_err(sensor->dev, "invalid device model 0x%04x\n", ret);
+               ret = -ENODEV;
+               goto done;
+       }
+
+       ret = imx296_read_temperature(sensor, &temp);
+       if (ret < 0)
+               goto done;
+
+       dev_info(sensor->dev, "found IMX%u%s (%u.%uC)\n", model,
+                sensor->mono ? "LL" : "LQ", temp / 1000, (temp / 100) % 10);
+
+done:
+       imx296_write(sensor, IMX296_CTRL00, IMX296_CTRL00_STANDBY, NULL);
+       return ret;
+}
+
+static const struct regmap_config imx296_regmap_config = {
+       .reg_bits = 16,
+       .val_bits = 8,
+
+       .wr_table = &(const struct regmap_access_table) {
+               .no_ranges = (const struct regmap_range[]) {
+                       {
+                               .range_min = IMX296_SENSOR_INFO & 0xffff,
+                               .range_max = (IMX296_SENSOR_INFO & 0xffff) + 1,
+                       },
+               },
+               .n_no_ranges = 1,
+       },
+};
+
+static int imx296_probe(struct i2c_client *client)
+{
+       struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+       unsigned long clk_rate;
+       struct imx296 *sensor;
+       unsigned int i;
+       int ret;
+
+       if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
+               dev_warn(&adapter->dev,
+                        "I2C-Adapter doesn't support I2C_FUNC_SMBUS_BYTE\n");
+               return -EIO;
+       }
+
+       sensor = devm_kzalloc(&client->dev, sizeof(*sensor), GFP_KERNEL);
+       if (!sensor)
+               return -ENOMEM;
+
+       sensor->dev = &client->dev;
+
+       mutex_init(&sensor->lock);
+
+       /* Acquire resources. */
+       for (i = 0; i < ARRAY_SIZE(sensor->supplies); ++i)
+               sensor->supplies[i].supply = imx296_supply_names[i];
+
+       ret = devm_regulator_bulk_get(sensor->dev, ARRAY_SIZE(sensor->supplies),
+                                     sensor->supplies);
+       if (ret) {
+               dev_err_probe(sensor->dev, ret, "failed to get supplies\n");
+               goto err_mutex;
+       }
+
+       sensor->reset = devm_gpiod_get_optional(sensor->dev, "reset",
+                                               GPIOD_OUT_HIGH);
+       if (IS_ERR(sensor->reset)) {
+               ret = PTR_ERR(sensor->reset);
+               dev_err_probe(sensor->dev, ret, "failed to get reset GPIO\n");
+               goto err_mutex;
+       }
+
+       sensor->clk = devm_clk_get(sensor->dev, "inck");
+       if (IS_ERR(sensor->clk)) {
+               ret = PTR_ERR(sensor->clk);
+               dev_err_probe(sensor->dev, ret, "failed to get clock\n");
+               goto err_mutex;
+       }
+
+       clk_rate = clk_get_rate(sensor->clk);
+       for (i = 0; i < ARRAY_SIZE(imx296_clk_params); ++i) {
+               if (clk_rate == imx296_clk_params[i].freq) {
+                       sensor->clk_params = &imx296_clk_params[i];
+                       break;
+               }
+       }
+
+       if (!sensor->clk_params) {
+               dev_err(sensor->dev, "unsupported clock rate %lu\n", clk_rate);
+               ret = -EINVAL;
+               goto err_mutex;
+       }
+
+       sensor->regmap = devm_regmap_init_i2c(client, &imx296_regmap_config);
+       if (IS_ERR(sensor->regmap)) {
+               ret = PTR_ERR(sensor->regmap);
+               goto err_mutex;
+       }
+
+       /*
+        * Enable power management. The driver supports runtime PM, but needs to
+        * work when runtime PM is disabled in the kernel. To that end, power
+        * the sensor on manually here, identify it, and fully initialize it.
+        */
+       ret = imx296_power_on(sensor);
+       if (ret < 0)
+               goto err_mutex;
+
+       ret = imx296_identify_model(sensor);
+       if (ret < 0)
+               goto err_power;
+
+       /* Initialize the V4L2 subdev, controls and media entity. */
+       v4l2_i2c_subdev_init(&sensor->subdev, client, &imx296_subdev_ops);
+
+       ret = imx296_ctrls_init(sensor);
+       if (ret < 0)
+               goto err_power;
+
+       sensor->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+       sensor->pad.flags = MEDIA_PAD_FL_SOURCE;
+       sensor->subdev.entity.function = MEDIA_ENT_F_CAM_SENSOR;
+       ret = media_entity_pads_init(&sensor->subdev.entity, 1, &sensor->pad);
+       if (ret < 0)
+               goto err_ctrls;
+
+       imx296_init_cfg(&sensor->subdev, NULL);
+
+       /*
+        * Enable runtime PM. As the device has been powered manually, mark it
+        * as active, and increase the usage count without resuming the device.
+        */
+       pm_runtime_set_active(sensor->dev);
+       pm_runtime_get_noresume(sensor->dev);
+       pm_runtime_enable(sensor->dev);
+
+       ret = v4l2_async_register_subdev(&sensor->subdev);
+       if (ret < 0)
+               goto err_pm;
+
+       /*
+        * Finally, enable autosuspend and decrease the usage count. The device
+        * will get suspended after the autosuspend delay, turning the power
+        * off.
+        */
+       pm_runtime_set_autosuspend_delay(sensor->dev, 1000);
+       pm_runtime_use_autosuspend(sensor->dev);
+       pm_runtime_put_autosuspend(sensor->dev);
+
+       return 0;
+
+err_pm:
+       pm_runtime_disable(sensor->dev);
+       pm_runtime_put_noidle(sensor->dev);
+       media_entity_cleanup(&sensor->subdev.entity);
+err_ctrls:
+       v4l2_ctrl_handler_free(&sensor->ctrls);
+err_power:
+       imx296_power_off(sensor);
+err_mutex:
+       mutex_destroy(&sensor->lock);
+       return ret;
+}
+
+static int imx296_remove(struct i2c_client *client)
+{
+       struct v4l2_subdev *subdev = i2c_get_clientdata(client);
+       struct imx296 *sensor = to_imx296(subdev);
+
+       v4l2_async_unregister_subdev(subdev);
+       media_entity_cleanup(&subdev->entity);
+       v4l2_ctrl_handler_free(&sensor->ctrls);
+
+       /*
+        * Disable runtime PM. In case runtime PM is disabled in the kernel,
+        * make sure to turn power off manually.
+        */
+       pm_runtime_disable(sensor->dev);
+       if (!pm_runtime_status_suspended(sensor->dev))
+               imx296_power_off(sensor);
+       pm_runtime_set_suspended(sensor->dev);
+
+       mutex_destroy(&sensor->lock);
+
+       return 0;
+}
+
+static const struct of_device_id imx296_of_match[] = {
+       { .compatible = "sony,imx296" },
+       { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, imx296_of_match);
+
+static struct i2c_driver imx296_i2c_driver = {
+       .driver = {
+               .of_match_table = imx296_of_match,
+               .name = "imx296",
+               .pm = &imx296_pm_ops
+       },
+       .probe_new = imx296_probe,
+       .remove = imx296_remove,
+};
+
+module_i2c_driver(imx296_i2c_driver);
+
+MODULE_DESCRIPTION("Sony IMX296 Camera driver");
+MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/media/i2c/imx477.c b/drivers/media/i2c/imx477.c
new file mode 100644 (file)
index 0000000..53e0656
--- /dev/null
@@ -0,0 +1,2312 @@
+// 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 <asm/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
+
+/* 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;
+};
+
+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},
+       {0x030e, 0x00},
+       {0x030f, 0x96},
+       {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, 0x12},
+       {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, 0x01},
+       {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},
+       {0x030e, 0x00},
+       {0x030f, 0x96},
+       {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, 0x12},
+       {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, 0x01},
+       {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},
+       {0x030e, 0x00},
+       {0x030f, 0x96},
+       {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},
+       {0x030e, 0x00},
+       {0x030f, 0x96},
+       {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 *exposure;
+       struct v4l2_ctrl *vflip;
+       struct v4l2_ctrl *hflip;
+       struct v4l2_ctrl *vblank;
+       struct v4l2_ctrl *hblank;
+
+       /* Current mode */
+       const struct imx477_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;
+
+       /* 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_get_try_format(sd, fh->state, IMAGE_PAD);
+       struct v4l2_mbus_framefmt *try_fmt_meta =
+               v4l2_subdev_get_try_format(sd, 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_get_try_crop(sd, 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;
+       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_get_try_format(&imx477->sd, 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;
+       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);
+
+       /*
+        * 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(imx477->hblank, hblank, hblank, 1, hblank);
+}
+
+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_get_try_format(sd, sd_state,
+                                                             fmt->pad);
+                       *framefmt = fmt->format;
+               } else {
+                       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_get_try_format(sd, 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_get_try_crop(&imx477->sd, 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;
+       const struct imx477_reg_list *extra_regs;
+       int ret;
+
+       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) {
+                       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);
+
+       /* Set vsync trigger mode */
+       if (trigger_mode != 0) {
+               /* trigger_mode == 1 for source, 2 for sink */
+               const u32 val = (trigger_mode == 1) ? 1 : 0;
+
+               imx477_write_reg(imx477, IMX477_REG_MC_MODE,
+                                IMX477_REG_VALUE_08BIT, 1);
+               imx477_write_reg(imx477, IMX477_REG_MS_SEL,
+                                IMX477_REG_VALUE_08BIT, val);
+               imx477_write_reg(imx477, IMX477_REG_XVS_IO_CTRL,
+                                IMX477_REG_VALUE_08BIT, val);
+               imx477_write_reg(imx477, IMX477_REG_EXTOUT_EN,
+                                IMX477_REG_VALUE_08BIT, val);
+       }
+
+       /* Apply customized values from user */
+       ret =  __v4l2_ctrl_handler_setup(imx477->sd.ctrl_handler);
+       if (ret)
+               return ret;
+
+       /* 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__);
+}
+
+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);
+
+       /*
+        * 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);
+
+       /* HBLANK is read-only for now, but does change with mode. */
+       if (imx477->hblank)
+               imx477->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+       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;
+
+       /* Setup exposure and frame/line length limits. */
+       imx477_set_framing_limits(imx477);
+
+       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 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] != IMX477_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 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;
+
+       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))
+               return -EINVAL;
+
+       /* 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 int 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);
+
+       return 0;
+}
+
+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_new = 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/imx519.c b/drivers/media/i2c/imx519.c
new file mode 100644 (file)
index 0000000..fa09cfc
--- /dev/null
@@ -0,0 +1,2092 @@
+// 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 <asm/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       493500000
+
+/* Pixel rate is fixed at 686MHz for all the modes */
+#define IMX519_PIXEL_RATE              686000000
+
+/* 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            1
+#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 16384
+#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 struct imx519_reg mode_common_regs[] = {
+       {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},
+};
+
+/* 16 mpix 10fps */
+static const struct imx519_reg mode_4656x3496_regs[] = {
+       {0x0111, 0x02},
+       {0x0112, 0x0a},
+       {0x0113, 0x0a},
+       {0x0114, 0x01},
+       {0x0342, 0x42},
+       {0x0343, 0x00},
+       {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, 0x04},
+       {0x0306, 0x01},
+       {0x0307, 0x57},
+       {0x0309, 0x0a},
+       {0x030b, 0x02},
+       {0x030d, 0x04},
+       {0x030e, 0x01},
+       {0x030f, 0x49},
+       {0x0310, 0x01},
+       {0x0820, 0x07},
+       {0x0821, 0xb6},
+       {0x0822, 0x00},
+       {0x0823, 0x00},
+       {0x3e20, 0x01},
+       {0x3e37, 0x00},
+       {0x3e3b, 0x00},
+       {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, 0x38},
+       {0x0343, 0x70},
+       {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, 0x04},
+       {0x0306, 0x01},
+       {0x0307, 0x57},
+       {0x0309, 0x0a},
+       {0x030b, 0x02},
+       {0x030d, 0x04},
+       {0x030e, 0x01},
+       {0x030f, 0x49},
+       {0x0310, 0x01},
+       {0x0820, 0x07},
+       {0x0821, 0xb6},
+       {0x0822, 0x00},
+       {0x0823, 0x00},
+       {0x3e20, 0x01},
+       {0x3e37, 0x00},
+       {0x3e3b, 0x00},
+       {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, 0x24},
+       {0x0343, 0x12},
+       {0x0340, 0x09},
+       {0x0341, 0xac},
+       {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, 0x01},
+       {0x3f4d, 0x01},
+       {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, 0x04},
+       {0x0306, 0x01},
+       {0x0307, 0x57},
+       {0x0309, 0x0a},
+       {0x030b, 0x02},
+       {0x030d, 0x04},
+       {0x030e, 0x01},
+       {0x030f, 0x49},
+       {0x0310, 0x01},
+       {0x0820, 0x07},
+       {0x0821, 0xb6},
+       {0x0822, 0x00},
+       {0x0823, 0x00},
+       {0x3e20, 0x01},
+       {0x3e37, 0x00},
+       {0x3e3b, 0x00},
+       {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, 0x25},
+       {0x0343, 0xd9},
+       {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, 0x01},
+       {0x3f4d, 0x01},
+       {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, 0x04},
+       {0x0306, 0x01},
+       {0x0307, 0x57},
+       {0x0309, 0x0a},
+       {0x030b, 0x02},
+       {0x030d, 0x04},
+       {0x030e, 0x01},
+       {0x030f, 0x49},
+       {0x0310, 0x01},
+       {0x0820, 0x07},
+       {0x0821, 0xb6},
+       {0x0822, 0x00},
+       {0x0823, 0x00},
+       {0x3e20, 0x01},
+       {0x3e37, 0x00},
+       {0x3e3b, 0x00},
+       {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, 0x1b},
+       {0x0343, 0x3b},
+       {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, 0x01},
+       {0x3f4d, 0x01},
+       {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, 0x04},
+       {0x0306, 0x01},
+       {0x0307, 0x57},
+       {0x0309, 0x0a},
+       {0x030b, 0x02},
+       {0x030d, 0x04},
+       {0x030e, 0x01},
+       {0x030f, 0x49},
+       {0x0310, 0x01},
+       {0x0820, 0x07},
+       {0x0821, 0xb6},
+       {0x0822, 0x00},
+       {0x0823, 0x00},
+       {0x3e20, 0x01},
+       {0x3e37, 0x00},
+       {0x3e3b, 0x00},
+       {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 = 0x4200,
+               .crop = {
+                       .left = IMX519_PIXEL_ARRAY_LEFT,
+                       .top = IMX519_PIXEL_ARRAY_TOP,
+                       .width = 4656,
+                       .height = 3496,
+               },
+               .timeperframe_min = {
+                       .numerator = 100,
+                       .denominator = 1000
+               },
+               .timeperframe_default = {
+                       .numerator = 100,
+                       .denominator = 1000
+               },
+               .reg_list = {
+                       .num_of_regs = ARRAY_SIZE(mode_4656x3496_regs),
+                       .regs = mode_4656x3496_regs,
+               }
+       },
+       {
+               .width = 3840,
+               .height = 2160,
+               .line_length_pix = 0x3870,
+               .crop = {
+                       .left = IMX519_PIXEL_ARRAY_LEFT + 408,
+                       .top = IMX519_PIXEL_ARRAY_TOP + 672,
+                       .width = 3840,
+                       .height = 2160,
+               },
+               .timeperframe_min = {
+                       .numerator = 100,
+                       .denominator = 2100
+               },
+               .timeperframe_default = {
+                       .numerator = 100,
+                       .denominator = 2100
+               },
+               .reg_list = {
+                       .num_of_regs = ARRAY_SIZE(mode_3840x2160_regs),
+                       .regs = mode_3840x2160_regs,
+               }
+       },
+       {
+               .width = 2328,
+               .height = 1748,
+               .line_length_pix = 0x2412,
+               .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 = 0x25D9,
+               .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 = 0x1B3B,
+               .crop = {
+                       .left = IMX519_PIXEL_ARRAY_LEFT + 1048,
+                       .top = IMX519_PIXEL_ARRAY_TOP + 1042,
+                       .width = 2560,
+                       .height = 1440,
+               },
+               .timeperframe_min = {
+                       .numerator = 100,
+                       .denominator = 12000
+               },
+               .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 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_get_try_format(sd, fh->state, IMAGE_PAD);
+       struct v4l2_mbus_framefmt *try_fmt_meta =
+               v4l2_subdev_get_try_format(sd, 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_get_try_crop(sd, 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_get_try_format(&imx519->sd, 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_get_try_format(sd, 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_get_try_format(sd, 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_get_try_crop(&imx519->sd, 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;
+       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);
+
+       /*
+        * 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 int 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);
+
+       return 0;
+}
+
+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_new = 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/irs1125.c b/drivers/media/i2c/irs1125.c
new file mode 100644 (file)
index 0000000..4543556
--- /dev/null
@@ -0,0 +1,1200 @@
+// 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_get_try_format(sd, 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,
+                        const struct i2c_device_id *id)
+{
+       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 int 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);
+
+       return 0;
+}
+
+#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 (file)
index 0000000..96d6761
--- /dev/null
@@ -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 (file)
index 0000000..a6d68e7
--- /dev/null
@@ -0,0 +1,1181 @@
+// 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 *)&reg_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_get_try_format(sd, 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_get_try_format(sd, 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_get_try_crop(&ov2311->subdev, 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_get_try_format(sd, 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;
+       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,
+                       const struct i2c_device_id *id)
+{
+       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 int 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);
+
+       return 0;
+}
+
+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");
index d346d18..befdb53 100644 (file)
@@ -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>
@@ -54,6 +55,8 @@
 #define OV5647_REG_GAIN_LO             0x350b
 #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
 #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_EXPOSURE_MIN            4
 #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)
+
 struct regval_list {
        u16 addr;
        u8 data;
@@ -101,6 +113,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;
@@ -108,6 +121,8 @@ struct ov5647 {
        struct v4l2_ctrl                *hblank;
        struct v4l2_ctrl                *vblank;
        struct v4l2_ctrl                *exposure;
+       struct v4l2_ctrl                *hflip;
+       struct v4l2_ctrl                *vflip;
        bool                            streaming;
 };
 
@@ -136,7 +151,7 @@ static struct regval_list ov5647_2592x1944_10bpp[] = {
        {0x3036, 0x69},
        {0x303c, 0x11},
        {0x3106, 0xf5},
-       {0x3821, 0x06},
+       {0x3821, 0x00},
        {0x3820, 0x00},
        {0x3827, 0xec},
        {0x370c, 0x03},
@@ -225,7 +240,7 @@ static struct regval_list ov5647_1080p30_10bpp[] = {
        {0x3036, 0x62},
        {0x303c, 0x11},
        {0x3106, 0xf5},
-       {0x3821, 0x06},
+       {0x3821, 0x00},
        {0x3820, 0x00},
        {0x3827, 0xec},
        {0x370c, 0x03},
@@ -389,7 +404,7 @@ static struct regval_list ov5647_2x2binned_10bpp[] = {
        {0x4800, 0x24},
        {0x3503, 0x03},
        {0x3820, 0x41},
-       {0x3821, 0x07},
+       {0x3821, 0x01},
        {0x350a, 0x00},
        {0x350b, 0x10},
        {0x3500, 0x00},
@@ -405,7 +420,7 @@ static struct regval_list ov5647_640x480_10bpp[] = {
        {0x3035, 0x11},
        {0x3036, 0x46},
        {0x303c, 0x11},
-       {0x3821, 0x07},
+       {0x3821, 0x01},
        {0x3820, 0x41},
        {0x370c, 0x03},
        {0x3612, 0x59},
@@ -494,7 +509,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
@@ -515,7 +530,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
@@ -536,7 +551,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
@@ -557,7 +572,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
@@ -587,7 +602,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;
@@ -603,10 +624,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;
@@ -619,17 +647,30 @@ static int ov5647_read(struct v4l2_subdev *sd, u16 reg, u8 *val)
        int ret;
 
        ret = i2c_master_send(client, data_w, 2);
-       if (ret < 0) {
+       /*
+        * A negative return code, or sending the wrong number of bytes, both
+        * count as an error.
+        */
+       if (ret != 2) {
                dev_dbg(&client->dev, "%s: i2c write error, reg: %x\n",
                        __func__, reg);
+               if (ret >= 0)
+                       ret = -EINVAL;
                return ret;
        }
 
        ret = i2c_master_recv(client, val, 1);
-       if (ret < 0) {
+       /*
+        * The only return value indicating success is 1. Anything else, even
+        * a non-negative value, indicates something went wrong.
+        */
+       if (ret == 1) {
+               ret = 0;
+       } else {
                dev_dbg(&client->dev, "%s: i2c read error, reg: %x\n",
                                __func__, reg);
-               return ret;
+               if (ret >= 0)
+                       ret = -EINVAL;
        }
 
        return 0;
@@ -757,6 +798,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);
@@ -788,6 +835,7 @@ error_clk_disable:
        clk_disable_unprepare(sensor->xclk);
 error_pwdn:
        gpiod_set_value_cansleep(sensor->pwdn, 1);
+       regulator_bulk_disable(OV5647_NUM_SUPPLIES, sensor->supplies);
 
        return ret;
 }
@@ -817,6 +865,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;
 }
@@ -853,6 +902,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 *
@@ -918,6 +969,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)
@@ -925,7 +995,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;
 }
@@ -936,7 +1006,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;
 
@@ -969,6 +1039,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;
@@ -1016,6 +1088,8 @@ static int ov5647_set_pad_fmt(struct v4l2_subdev *sd,
                                         exposure_def);
        }
        *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;
@@ -1191,6 +1265,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, &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,
@@ -1249,6 +1342,14 @@ static int ov5647_s_ctrl(struct v4l2_ctrl *ctrl)
                /* 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",
@@ -1265,10 +1366,23 @@ 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, 8);
 
@@ -1314,6 +1428,21 @@ static int ov5647_init_controls(struct ov5647 *sensor)
                                           sensor->mode->vts -
                                           sensor->mode->format.height);
 
+       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;
+
+       v4l2_fwnode_device_parse(dev, &props);
+
+       v4l2_ctrl_new_fwnode_properties(&sensor->ctrls, &ov5647_ctrl_ops,
+                                       &props);
+
        if (sensor->ctrls.error)
                goto handler_free;
 
@@ -1396,11 +1525,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;
 
@@ -1423,7 +1558,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;
 
index ebb299f..adc8e19 100644 (file)
@@ -14,6 +14,8 @@
 #include <linux/i2c.h>
 #include <linux/init.h>
 #include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/pm_runtime.h>
 #include <linux/regulator/consumer.h>
 #include <linux/slab.h>
 #include <linux/types.h>
 #define OV7251_AEC_EXPO_2              0x3502
 #define OV7251_AEC_AGC_ADJ_0           0x350a
 #define OV7251_AEC_AGC_ADJ_1           0x350b
+/* Exposure must be at least 20 lines shorter than VTS */
+#define OV7251_EXPOSURE_OFFSET         20
+ /* HTS is registers 0x380c and 0x380d */
+#define OV7251_HTS                     0x3a0
+#define OV7251_VTS_HIGH                        0x380e
+#define OV7251_VTS_LOW                 0x380f
+#define OV7251_VTS_MIN_OFFSET          92
+#define OV7251_VTS_MAX                 0x7fff
 #define OV7251_TIMING_FORMAT1          0x3820
 #define OV7251_TIMING_FORMAT1_VFLIP    BIT(2)
 #define OV7251_TIMING_FORMAT2          0x3821
 #define OV7251_TIMING_FORMAT2_MIRROR   BIT(2)
 #define OV7251_PRE_ISP_00              0x5e00
 #define OV7251_PRE_ISP_00_TEST_PATTERN BIT(7)
+#define OV7251_PLL1_PRE_DIV_REG                0x30b4
+#define OV7251_PLL1_MULT_REG           0x30b3
+#define OV7251_PLL1_DIVIDER_REG                0x30b1
+#define OV7251_PLL1_PIX_DIV_REG                0x30b0
+#define OV7251_PLL1_MIPI_DIV_REG       0x30b5
+#define OV7251_PLL2_PRE_DIV_REG                0x3098
+#define OV7251_PLL2_MULT_REG           0x3099
+#define OV7251_PLL2_DIVIDER_REG                0x309d
+#define OV7251_PLL2_SYS_DIV_REG                0x309a
+#define OV7251_PLL2_ADC_DIV_REG                0x309b
+
+/*
+ * OV7251 native and active pixel array size.
+ * Datasheet not available to confirm these values, so assume there are no
+ * border pixels.
+ */
+#define OV7251_NATIVE_WIDTH            640U
+#define OV7251_NATIVE_HEIGHT           480U
+#define OV7251_PIXEL_ARRAY_LEFT                0U
+#define OV7251_PIXEL_ARRAY_TOP         0U
+#define OV7251_PIXEL_ARRAY_WIDTH       640U
+#define OV7251_PIXEL_ARRAY_HEIGHT      480U
+
+#define OV7251_PIXEL_CLOCK 48000000
 
 struct reg_value {
        u16 reg;
        u8 val;
 };
 
+struct ov7251_frame_ival_info {
+       u16 vts;
+       struct v4l2_fract timeperframe;
+};
+
 struct ov7251_mode_info {
        u32 width;
        u32 height;
        const struct reg_value *data;
        u32 data_size;
-       u32 pixel_clock;
-       u32 link_freq;
-       u16 exposure_max;
        u16 exposure_def;
-       struct v4l2_fract timeperframe;
+};
+
+struct ov7251_pll1_config {
+       unsigned int pre_div;
+       unsigned int mult;
+       unsigned int div;
+       unsigned int pix_div;
+       unsigned int mipi_div;
+};
+
+struct ov7251_pll2_config {
+       unsigned int pre_div;
+       unsigned int mult;
+       unsigned int div;
+       unsigned int sys_div;
+       unsigned int adc_div;
+};
+
+struct ov7251_pll_configs {
+       const struct ov7251_pll1_config *pll1;
+       const struct ov7251_pll2_config *pll2;
 };
 
 struct ov7251 {
@@ -70,17 +126,19 @@ struct ov7251 {
        struct clk *xclk;
        u32 xclk_freq;
 
+       const struct ov7251_pll_configs *pll_configs;
+
        struct regulator *io_regulator;
        struct regulator *core_regulator;
        struct regulator *analog_regulator;
 
        const struct ov7251_mode_info *current_mode;
+       const struct ov7251_frame_ival_info *current_ival;
 
        struct v4l2_ctrl_handler ctrls;
-       struct v4l2_ctrl *pixel_clock;
-       struct v4l2_ctrl *link_freq;
        struct v4l2_ctrl *exposure;
-       struct v4l2_ctrl *gain;
+       struct v4l2_ctrl *hblank;
+       struct v4l2_ctrl *vblank;
 
        /* Cached register values */
        u8 aec_pk_manual;
@@ -99,288 +157,65 @@ static inline struct ov7251 *to_ov7251(struct v4l2_subdev *sd)
        return container_of(sd, struct ov7251, sd);
 }
 
-static const struct reg_value ov7251_global_init_setting[] = {
-       { 0x0103, 0x01 },
-       { 0x303b, 0x02 },
+enum xclk_rate {
+       OV7251_19_2_MHZ,
+       OV7251_24_MHZ,
+       OV7251_NUM_SUPPORTED_RATES
 };
 
-static const struct reg_value ov7251_setting_vga_30fps[] = {
-       { 0x3005, 0x00 },
-       { 0x3012, 0xc0 },
-       { 0x3013, 0xd2 },
-       { 0x3014, 0x04 },
-       { 0x3016, 0xf0 },
-       { 0x3017, 0xf0 },
-       { 0x3018, 0xf0 },
-       { 0x301a, 0xf0 },
-       { 0x301b, 0xf0 },
-       { 0x301c, 0xf0 },
-       { 0x3023, 0x05 },
-       { 0x3037, 0xf0 },
-       { 0x3098, 0x04 }, /* pll2 pre divider */
-       { 0x3099, 0x28 }, /* pll2 multiplier */
-       { 0x309a, 0x05 }, /* pll2 sys divider */
-       { 0x309b, 0x04 }, /* pll2 adc divider */
-       { 0x309d, 0x00 }, /* pll2 divider */
-       { 0x30b0, 0x0a }, /* pll1 pix divider */
-       { 0x30b1, 0x01 }, /* pll1 divider */
-       { 0x30b3, 0x64 }, /* pll1 multiplier */
-       { 0x30b4, 0x03 }, /* pll1 pre divider */
-       { 0x30b5, 0x05 }, /* pll1 mipi divider */
-       { 0x3106, 0xda },
-       { 0x3503, 0x07 },
-       { 0x3509, 0x10 },
-       { 0x3600, 0x1c },
-       { 0x3602, 0x62 },
-       { 0x3620, 0xb7 },
-       { 0x3622, 0x04 },
-       { 0x3626, 0x21 },
-       { 0x3627, 0x30 },
-       { 0x3630, 0x44 },
-       { 0x3631, 0x35 },
-       { 0x3634, 0x60 },
-       { 0x3636, 0x00 },
-       { 0x3662, 0x01 },
-       { 0x3663, 0x70 },
-       { 0x3664, 0x50 },
-       { 0x3666, 0x0a },
-       { 0x3669, 0x1a },
-       { 0x366a, 0x00 },
-       { 0x366b, 0x50 },
-       { 0x3673, 0x01 },
-       { 0x3674, 0xff },
-       { 0x3675, 0x03 },
-       { 0x3705, 0xc1 },
-       { 0x3709, 0x40 },
-       { 0x373c, 0x08 },
-       { 0x3742, 0x00 },
-       { 0x3757, 0xb3 },
-       { 0x3788, 0x00 },
-       { 0x37a8, 0x01 },
-       { 0x37a9, 0xc0 },
-       { 0x3800, 0x00 },
-       { 0x3801, 0x04 },
-       { 0x3802, 0x00 },
-       { 0x3803, 0x04 },
-       { 0x3804, 0x02 },
-       { 0x3805, 0x8b },
-       { 0x3806, 0x01 },
-       { 0x3807, 0xeb },
-       { 0x3808, 0x02 }, /* width high */
-       { 0x3809, 0x80 }, /* width low */
-       { 0x380a, 0x01 }, /* height high */
-       { 0x380b, 0xe0 }, /* height low */
-       { 0x380c, 0x03 }, /* total horiz timing high */
-       { 0x380d, 0xa0 }, /* total horiz timing low */
-       { 0x380e, 0x06 }, /* total vertical timing high */
-       { 0x380f, 0xbc }, /* total vertical timing low */
-       { 0x3810, 0x00 },
-       { 0x3811, 0x04 },
-       { 0x3812, 0x00 },
-       { 0x3813, 0x05 },
-       { 0x3814, 0x11 },
-       { 0x3815, 0x11 },
-       { 0x3820, 0x40 },
-       { 0x3821, 0x00 },
-       { 0x382f, 0x0e },
-       { 0x3832, 0x00 },
-       { 0x3833, 0x05 },
-       { 0x3834, 0x00 },
-       { 0x3835, 0x0c },
-       { 0x3837, 0x00 },
-       { 0x3b80, 0x00 },
-       { 0x3b81, 0xa5 },
-       { 0x3b82, 0x10 },
-       { 0x3b83, 0x00 },
-       { 0x3b84, 0x08 },
-       { 0x3b85, 0x00 },
-       { 0x3b86, 0x01 },
-       { 0x3b87, 0x00 },
-       { 0x3b88, 0x00 },
-       { 0x3b89, 0x00 },
-       { 0x3b8a, 0x00 },
-       { 0x3b8b, 0x05 },
-       { 0x3b8c, 0x00 },
-       { 0x3b8d, 0x00 },
-       { 0x3b8e, 0x00 },
-       { 0x3b8f, 0x1a },
-       { 0x3b94, 0x05 },
-       { 0x3b95, 0xf2 },
-       { 0x3b96, 0x40 },
-       { 0x3c00, 0x89 },
-       { 0x3c01, 0x63 },
-       { 0x3c02, 0x01 },
-       { 0x3c03, 0x00 },
-       { 0x3c04, 0x00 },
-       { 0x3c05, 0x03 },
-       { 0x3c06, 0x00 },
-       { 0x3c07, 0x06 },
-       { 0x3c0c, 0x01 },
-       { 0x3c0d, 0xd0 },
-       { 0x3c0e, 0x02 },
-       { 0x3c0f, 0x0a },
-       { 0x4001, 0x42 },
-       { 0x4004, 0x04 },
-       { 0x4005, 0x00 },
-       { 0x404e, 0x01 },
-       { 0x4300, 0xff },
-       { 0x4301, 0x00 },
-       { 0x4315, 0x00 },
-       { 0x4501, 0x48 },
-       { 0x4600, 0x00 },
-       { 0x4601, 0x4e },
-       { 0x4801, 0x0f },
-       { 0x4806, 0x0f },
-       { 0x4819, 0xaa },
-       { 0x4823, 0x3e },
-       { 0x4837, 0x19 },
-       { 0x4a0d, 0x00 },
-       { 0x4a47, 0x7f },
-       { 0x4a49, 0xf0 },
-       { 0x4a4b, 0x30 },
-       { 0x5000, 0x85 },
-       { 0x5001, 0x80 },
+static const struct ov7251_pll1_config ov7251_pll1_config_19_2_mhz = {
+       .pre_div = 0x03,
+       .mult = 0x4b,
+       .div = 0x01,
+       .pix_div = 0x0a,
+       .mipi_div = 0x05
 };
 
-static const struct reg_value ov7251_setting_vga_60fps[] = {
-       { 0x3005, 0x00 },
-       { 0x3012, 0xc0 },
-       { 0x3013, 0xd2 },
-       { 0x3014, 0x04 },
-       { 0x3016, 0x10 },
-       { 0x3017, 0x00 },
-       { 0x3018, 0x00 },
-       { 0x301a, 0x00 },
-       { 0x301b, 0x00 },
-       { 0x301c, 0x00 },
-       { 0x3023, 0x05 },
-       { 0x3037, 0xf0 },
-       { 0x3098, 0x04 }, /* pll2 pre divider */
-       { 0x3099, 0x28 }, /* pll2 multiplier */
-       { 0x309a, 0x05 }, /* pll2 sys divider */
-       { 0x309b, 0x04 }, /* pll2 adc divider */
-       { 0x309d, 0x00 }, /* pll2 divider */
-       { 0x30b0, 0x0a }, /* pll1 pix divider */
-       { 0x30b1, 0x01 }, /* pll1 divider */
-       { 0x30b3, 0x64 }, /* pll1 multiplier */
-       { 0x30b4, 0x03 }, /* pll1 pre divider */
-       { 0x30b5, 0x05 }, /* pll1 mipi divider */
-       { 0x3106, 0xda },
-       { 0x3503, 0x07 },
-       { 0x3509, 0x10 },
-       { 0x3600, 0x1c },
-       { 0x3602, 0x62 },
-       { 0x3620, 0xb7 },
-       { 0x3622, 0x04 },
-       { 0x3626, 0x21 },
-       { 0x3627, 0x30 },
-       { 0x3630, 0x44 },
-       { 0x3631, 0x35 },
-       { 0x3634, 0x60 },
-       { 0x3636, 0x00 },
-       { 0x3662, 0x01 },
-       { 0x3663, 0x70 },
-       { 0x3664, 0x50 },
-       { 0x3666, 0x0a },
-       { 0x3669, 0x1a },
-       { 0x366a, 0x00 },
-       { 0x366b, 0x50 },
-       { 0x3673, 0x01 },
-       { 0x3674, 0xff },
-       { 0x3675, 0x03 },
-       { 0x3705, 0xc1 },
-       { 0x3709, 0x40 },
-       { 0x373c, 0x08 },
-       { 0x3742, 0x00 },
-       { 0x3757, 0xb3 },
-       { 0x3788, 0x00 },
-       { 0x37a8, 0x01 },
-       { 0x37a9, 0xc0 },
-       { 0x3800, 0x00 },
-       { 0x3801, 0x04 },
-       { 0x3802, 0x00 },
-       { 0x3803, 0x04 },
-       { 0x3804, 0x02 },
-       { 0x3805, 0x8b },
-       { 0x3806, 0x01 },
-       { 0x3807, 0xeb },
-       { 0x3808, 0x02 }, /* width high */
-       { 0x3809, 0x80 }, /* width low */
-       { 0x380a, 0x01 }, /* height high */
-       { 0x380b, 0xe0 }, /* height low */
-       { 0x380c, 0x03 }, /* total horiz timing high */
-       { 0x380d, 0xa0 }, /* total horiz timing low */
-       { 0x380e, 0x03 }, /* total vertical timing high */
-       { 0x380f, 0x5c }, /* total vertical timing low */
-       { 0x3810, 0x00 },
-       { 0x3811, 0x04 },
-       { 0x3812, 0x00 },
-       { 0x3813, 0x05 },
-       { 0x3814, 0x11 },
-       { 0x3815, 0x11 },
-       { 0x3820, 0x40 },
-       { 0x3821, 0x00 },
-       { 0x382f, 0x0e },
-       { 0x3832, 0x00 },
-       { 0x3833, 0x05 },
-       { 0x3834, 0x00 },
-       { 0x3835, 0x0c },
-       { 0x3837, 0x00 },
-       { 0x3b80, 0x00 },
-       { 0x3b81, 0xa5 },
-       { 0x3b82, 0x10 },
-       { 0x3b83, 0x00 },
-       { 0x3b84, 0x08 },
-       { 0x3b85, 0x00 },
-       { 0x3b86, 0x01 },
-       { 0x3b87, 0x00 },
-       { 0x3b88, 0x00 },
-       { 0x3b89, 0x00 },
-       { 0x3b8a, 0x00 },
-       { 0x3b8b, 0x05 },
-       { 0x3b8c, 0x00 },
-       { 0x3b8d, 0x00 },
-       { 0x3b8e, 0x00 },
-       { 0x3b8f, 0x1a },
-       { 0x3b94, 0x05 },
-       { 0x3b95, 0xf2 },
-       { 0x3b96, 0x40 },
-       { 0x3c00, 0x89 },
-       { 0x3c01, 0x63 },
-       { 0x3c02, 0x01 },
-       { 0x3c03, 0x00 },
-       { 0x3c04, 0x00 },
-       { 0x3c05, 0x03 },
-       { 0x3c06, 0x00 },
-       { 0x3c07, 0x06 },
-       { 0x3c0c, 0x01 },
-       { 0x3c0d, 0xd0 },
-       { 0x3c0e, 0x02 },
-       { 0x3c0f, 0x0a },
-       { 0x4001, 0x42 },
-       { 0x4004, 0x04 },
-       { 0x4005, 0x00 },
-       { 0x404e, 0x01 },
-       { 0x4300, 0xff },
-       { 0x4301, 0x00 },
-       { 0x4315, 0x00 },
-       { 0x4501, 0x48 },
-       { 0x4600, 0x00 },
-       { 0x4601, 0x4e },
-       { 0x4801, 0x0f },
-       { 0x4806, 0x0f },
-       { 0x4819, 0xaa },
-       { 0x4823, 0x3e },
-       { 0x4837, 0x19 },
-       { 0x4a0d, 0x00 },
-       { 0x4a47, 0x7f },
-       { 0x4a49, 0xf0 },
-       { 0x4a4b, 0x30 },
-       { 0x5000, 0x85 },
-       { 0x5001, 0x80 },
+static const struct ov7251_pll1_config ov7251_pll1_config_24_mhz = {
+       .pre_div = 0x03,
+       .mult = 0x64,
+       .div = 0x01,
+       .pix_div = 0x0a,
+       .mipi_div = 0x05
+};
+
+static const struct ov7251_pll2_config ov7251_pll2_config_19_2_mhz = {
+       .pre_div = 0x04,
+       .mult = 0x32,
+       .div = 0x00,
+       .sys_div = 0x05,
+       .adc_div = 0x04
+};
+
+static const struct ov7251_pll2_config ov7251_pll2_config_24_mhz = {
+       .pre_div = 0x04,
+       .mult = 0x28,
+       .div = 0x00,
+       .sys_div = 0x05,
+       .adc_div = 0x04
+};
+
+static const struct ov7251_pll_configs ov7251_pll_configs_19_2_mhz = {
+       .pll1 = &ov7251_pll1_config_19_2_mhz,
+       .pll2 = &ov7251_pll2_config_19_2_mhz
+};
+
+static const struct ov7251_pll_configs ov7251_pll_configs_24_mhz = {
+       .pll1 = &ov7251_pll1_config_24_mhz,
+       .pll2 = &ov7251_pll2_config_24_mhz
+};
+
+static const struct ov7251_pll_configs *ov7251_pll_configs[] = {
+       [OV7251_19_2_MHZ] = &ov7251_pll_configs_19_2_mhz,
+       [OV7251_24_MHZ] = &ov7251_pll_configs_24_mhz
+};
+
+static const struct reg_value ov7251_global_init_setting[] = {
+       { 0x0103, 0x01 },
+       { 0x303b, 0x02 },
 };
 
-static const struct reg_value ov7251_setting_vga_90fps[] = {
+static const struct reg_value ov7251_setting_vga[] = {
        { 0x3005, 0x00 },
        { 0x3012, 0xc0 },
        { 0x3013, 0xd2 },
@@ -393,16 +228,6 @@ static const struct reg_value ov7251_setting_vga_90fps[] = {
        { 0x301c, 0x00 },
        { 0x3023, 0x05 },
        { 0x3037, 0xf0 },
-       { 0x3098, 0x04 }, /* pll2 pre divider */
-       { 0x3099, 0x28 }, /* pll2 multiplier */
-       { 0x309a, 0x05 }, /* pll2 sys divider */
-       { 0x309b, 0x04 }, /* pll2 adc divider */
-       { 0x309d, 0x00 }, /* pll2 divider */
-       { 0x30b0, 0x0a }, /* pll1 pix divider */
-       { 0x30b1, 0x01 }, /* pll1 divider */
-       { 0x30b3, 0x64 }, /* pll1 multiplier */
-       { 0x30b4, 0x03 }, /* pll1 pre divider */
-       { 0x30b5, 0x05 }, /* pll1 mipi divider */
        { 0x3106, 0xda },
        { 0x3503, 0x07 },
        { 0x3509, 0x10 },
@@ -448,8 +273,6 @@ static const struct reg_value ov7251_setting_vga_90fps[] = {
        { 0x380b, 0xe0 }, /* height low */
        { 0x380c, 0x03 }, /* total horiz timing high */
        { 0x380d, 0xa0 }, /* total horiz timing low */
-       { 0x380e, 0x02 }, /* total vertical timing high */
-       { 0x380f, 0x3c }, /* total vertical timing low */
        { 0x3810, 0x00 },
        { 0x3811, 0x04 },
        { 0x3812, 0x00 },
@@ -518,48 +341,32 @@ static const struct reg_value ov7251_setting_vga_90fps[] = {
        { 0x5001, 0x80 },
 };
 
+static const unsigned long supported_xclk_rates[] = {
+       [OV7251_19_2_MHZ] = 19200000,
+       [OV7251_24_MHZ] = 24000000,
+};
+
 static const s64 link_freq[] = {
        240000000,
 };
 
-static const struct ov7251_mode_info ov7251_mode_info_data[] = {
+static const struct ov7251_frame_ival_info ov7251_frame_ival_info_data[] = {
        {
-               .width = 640,
-               .height = 480,
-               .data = ov7251_setting_vga_30fps,
-               .data_size = ARRAY_SIZE(ov7251_setting_vga_30fps),
-               .pixel_clock = 48000000,
-               .link_freq = 0, /* an index in link_freq[] */
-               .exposure_max = 1704,
-               .exposure_def = 504,
+               .vts = 0x6bc,
                .timeperframe = {
                        .numerator = 100,
                        .denominator = 3000
                }
        },
        {
-               .width = 640,
-               .height = 480,
-               .data = ov7251_setting_vga_60fps,
-               .data_size = ARRAY_SIZE(ov7251_setting_vga_60fps),
-               .pixel_clock = 48000000,
-               .link_freq = 0, /* an index in link_freq[] */
-               .exposure_max = 840,
-               .exposure_def = 504,
+               .vts = 0x35c,
                .timeperframe = {
                        .numerator = 100,
                        .denominator = 6014
                }
        },
        {
-               .width = 640,
-               .height = 480,
-               .data = ov7251_setting_vga_90fps,
-               .data_size = ARRAY_SIZE(ov7251_setting_vga_90fps),
-               .pixel_clock = 48000000,
-               .link_freq = 0, /* an index in link_freq[] */
-               .exposure_max = 552,
-               .exposure_def = 504,
+               .vts = 0x23c,
                .timeperframe = {
                        .numerator = 100,
                        .denominator = 9043
@@ -567,6 +374,16 @@ static const struct ov7251_mode_info ov7251_mode_info_data[] = {
        },
 };
 
+static const struct ov7251_mode_info ov7251_mode_info_data[] = {
+       {
+               .width = 640,
+               .height = 480,
+               .data = ov7251_setting_vga,
+               .data_size = ARRAY_SIZE(ov7251_setting_vga),
+               .exposure_def = 504,
+       },
+};
+
 static int ov7251_regulators_enable(struct ov7251 *ov7251)
 {
        int ret;
@@ -691,6 +508,63 @@ static int ov7251_read_reg(struct ov7251 *ov7251, u16 reg, u8 *val)
        return 0;
 }
 
+static int ov7251_pll_configure(struct ov7251 *ov7251)
+{
+       const struct ov7251_pll_configs *configs;
+       int ret;
+
+       configs = ov7251->pll_configs;
+
+       ret = ov7251_write_reg(ov7251, OV7251_PLL1_PRE_DIV_REG,
+                              configs->pll1->pre_div);
+       if (ret < 0)
+               return ret;
+
+       ret = ov7251_write_reg(ov7251, OV7251_PLL1_MULT_REG,
+                              configs->pll1->mult);
+       if (ret < 0)
+               return ret;
+       ret = ov7251_write_reg(ov7251, OV7251_PLL1_DIVIDER_REG,
+                              configs->pll1->div);
+       if (ret < 0)
+               return ret;
+
+       ret = ov7251_write_reg(ov7251, OV7251_PLL1_PIX_DIV_REG,
+                              configs->pll1->pix_div);
+       if (ret < 0)
+               return ret;
+
+       ret = ov7251_write_reg(ov7251, OV7251_PLL1_MIPI_DIV_REG,
+                              configs->pll1->mipi_div);
+       if (ret < 0)
+               return ret;
+
+       ret = ov7251_write_reg(ov7251, OV7251_PLL2_PRE_DIV_REG,
+                              configs->pll2->pre_div);
+       if (ret < 0)
+               return ret;
+
+       ret = ov7251_write_reg(ov7251, OV7251_PLL2_MULT_REG,
+                              configs->pll2->mult);
+       if (ret < 0)
+               return ret;
+
+       ret = ov7251_write_reg(ov7251, OV7251_PLL2_DIVIDER_REG,
+                              configs->pll2->div);
+       if (ret < 0)
+               return ret;
+
+       ret = ov7251_write_reg(ov7251, OV7251_PLL2_SYS_DIV_REG,
+                              configs->pll2->sys_div);
+       if (ret < 0)
+               return ret;
+
+       ret = ov7251_write_reg(ov7251, OV7251_PLL2_ADC_DIV_REG,
+                              configs->pll2->adc_div);
+
+       return ret;
+}
+
 static int ov7251_set_exposure(struct ov7251 *ov7251, s32 exposure)
 {
        u16 reg;
@@ -765,41 +639,22 @@ static void ov7251_set_power_off(struct ov7251 *ov7251)
        ov7251_regulators_disable(ov7251);
 }
 
-static int ov7251_s_power(struct v4l2_subdev *sd, int on)
+static int __maybe_unused ov7251_sensor_suspend(struct device *dev)
 {
+       struct v4l2_subdev *sd = dev_get_drvdata(dev);
        struct ov7251 *ov7251 = to_ov7251(sd);
-       int ret = 0;
-
-       mutex_lock(&ov7251->lock);
-
-       /* If the power state is not modified - no work to do. */
-       if (ov7251->power_on == !!on)
-               goto exit;
-
-       if (on) {
-               ret = ov7251_set_power_on(ov7251);
-               if (ret < 0)
-                       goto exit;
 
-               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 init registers\n");
-                       ov7251_set_power_off(ov7251);
-                       goto exit;
-               }
+       ov7251_set_power_off(ov7251);
 
-               ov7251->power_on = true;
-       } else {
-               ov7251_set_power_off(ov7251);
-               ov7251->power_on = false;
-       }
+       return 0;
+}
 
-exit:
-       mutex_unlock(&ov7251->lock);
+static int __maybe_unused ov7251_sensor_resume(struct device *dev)
+{
+       struct v4l2_subdev *sd = dev_get_drvdata(dev);
+       struct ov7251 *ov7251 = to_ov7251(sd);
 
-       return ret;
+       return ov7251_set_power_on(ov7251);
 }
 
 static int ov7251_set_hflip(struct ov7251 *ov7251, s32 value)
@@ -836,6 +691,19 @@ static int ov7251_set_vflip(struct ov7251 *ov7251, s32 value)
        return ret;
 }
 
+static int ov7251_set_vblank(struct ov7251 *ov7251, s32 value)
+{
+       u16 reg;
+       u8 val[2];
+
+       reg = OV7251_VTS_HIGH;
+       value += ov7251->current_mode->height;
+       val[0] = (value & 0xff00) >> 8; /* goes to OV7251_VTS_HIGH */
+       val[1] = value & 0xff;          /* goes to OV7251_VTS_LOW */
+
+       return ov7251_write_seq_regs(ov7251, reg, val, 2);
+}
+
 static int ov7251_set_test_pattern(struct ov7251 *ov7251, s32 value)
 {
        u8 val = ov7251->pre_isp_00;
@@ -862,18 +730,29 @@ static int ov7251_s_ctrl(struct v4l2_ctrl *ctrl)
 {
        struct ov7251 *ov7251 = container_of(ctrl->handler,
                                             struct ov7251, ctrls);
+       s64 max;
        int ret;
 
        /* v4l2_ctrl_lock() locks our mutex */
+       switch (ctrl->id) {
+       case V4L2_CID_VBLANK:
+               /* Update max exposure while meeting expected vblanking */
+               max = ov7251->current_mode->height + ctrl->val - OV7251_EXPOSURE_OFFSET;
+               __v4l2_ctrl_modify_range(ov7251->exposure,
+                                        ov7251->exposure->minimum, max,
+                                        ov7251->exposure->step,
+                                        ov7251->exposure->default_value);
+               break;
+       }
 
-       if (!ov7251->power_on)
+       if (!pm_runtime_get_if_in_use(ov7251->dev))
                return 0;
 
        switch (ctrl->id) {
        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:
@@ -885,11 +764,16 @@ static int ov7251_s_ctrl(struct v4l2_ctrl *ctrl)
        case V4L2_CID_VFLIP:
                ret = ov7251_set_vflip(ov7251, ctrl->val);
                break;
+       case V4L2_CID_VBLANK:
+               ret = ov7251_set_vblank(ov7251, ctrl->val);
+               break;
        default:
                ret = -EINVAL;
                break;
        }
 
+       pm_runtime_put(ov7251->dev);
+
        return ret;
 }
 
@@ -934,13 +818,13 @@ static int ov7251_enum_frame_ival(struct v4l2_subdev *subdev,
        unsigned int index = fie->index;
        unsigned int i;
 
-       for (i = 0; i < ARRAY_SIZE(ov7251_mode_info_data); i++) {
-               if (fie->width != ov7251_mode_info_data[i].width ||
-                   fie->height != ov7251_mode_info_data[i].height)
+       for (i = 0; i < ARRAY_SIZE(ov7251_frame_ival_info_data); i++) {
+               if (fie->width != ov7251_mode_info_data[0].width ||
+                   fie->height != ov7251_mode_info_data[0].height)
                        continue;
 
                if (index-- == 0) {
-                       fie->interval = ov7251_mode_info_data[i].timeperframe;
+                       fie->interval = ov7251_frame_ival_info_data[i].timeperframe;
                        return 0;
                }
        }
@@ -999,23 +883,18 @@ static inline u32 avg_fps(const struct v4l2_fract *t)
        return (t->denominator + (t->numerator >> 1)) / t->numerator;
 }
 
-static const struct ov7251_mode_info *
-ov7251_find_mode_by_ival(struct ov7251 *ov7251, struct v4l2_fract *timeperframe)
+static const struct ov7251_frame_ival_info *
+ov7251_find_frame_ival_by_ival(struct ov7251 *ov7251, struct v4l2_fract *timeperframe)
 {
-       const struct ov7251_mode_info *mode = ov7251->current_mode;
        unsigned int fps_req = avg_fps(timeperframe);
        unsigned int max_dist_match = (unsigned int) -1;
        unsigned int i, n = 0;
 
-       for (i = 0; i < ARRAY_SIZE(ov7251_mode_info_data); i++) {
+       for (i = 0; i < ARRAY_SIZE(ov7251_frame_ival_info_data); i++) {
                unsigned int dist;
                unsigned int fps_tmp;
 
-               if (mode->width != ov7251_mode_info_data[i].width ||
-                   mode->height != ov7251_mode_info_data[i].height)
-                       continue;
-
-               fps_tmp = avg_fps(&ov7251_mode_info_data[i].timeperframe);
+               fps_tmp = avg_fps(&ov7251_frame_ival_info_data[i].timeperframe);
 
                dist = abs(fps_req - fps_tmp);
 
@@ -1025,7 +904,7 @@ ov7251_find_mode_by_ival(struct ov7251 *ov7251, struct v4l2_fract *timeperframe)
                }
        }
 
-       return &ov7251_mode_info_data[n];
+       return &ov7251_frame_ival_info_data[n];
 }
 
 static int ov7251_set_format(struct v4l2_subdev *sd,
@@ -1036,6 +915,7 @@ static int ov7251_set_format(struct v4l2_subdev *sd,
        struct v4l2_mbus_framefmt *__format;
        struct v4l2_rect *__crop;
        const struct ov7251_mode_info *new_mode;
+       s64 h_blank;
        int ret = 0;
 
        mutex_lock(&ov7251->lock);
@@ -1052,18 +932,14 @@ static int ov7251_set_format(struct v4l2_subdev *sd,
        __crop->height = new_mode->height;
 
        if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
-               ret = __v4l2_ctrl_s_ctrl_int64(ov7251->pixel_clock,
-                                              new_mode->pixel_clock);
-               if (ret < 0)
-                       goto exit;
-
-               ret = __v4l2_ctrl_s_ctrl(ov7251->link_freq,
-                                        new_mode->link_freq);
-               if (ret < 0)
-                       goto exit;
-
-               ret = __v4l2_ctrl_modify_range(ov7251->exposure,
-                                              1, new_mode->exposure_max,
+               h_blank = OV7251_HTS - new_mode->width;
+               __v4l2_ctrl_modify_range(ov7251->hblank, h_blank,
+                                        h_blank, 1, h_blank);
+               __v4l2_ctrl_s_ctrl(ov7251->hblank, h_blank);
+
+               ret = __v4l2_ctrl_modify_range(ov7251->exposure, 1,
+                                              ov7251->current_ival->vts -
+                                                       OV7251_EXPOSURE_OFFSET,
                                               1, new_mode->exposure_def);
                if (ret < 0)
                        goto exit;
@@ -1073,10 +949,6 @@ static int ov7251_set_format(struct v4l2_subdev *sd,
                if (ret < 0)
                        goto exit;
 
-               ret = __v4l2_ctrl_s_ctrl(ov7251->gain, 16);
-               if (ret < 0)
-                       goto exit;
-
                ov7251->current_mode = new_mode;
        }
 
@@ -1123,15 +995,34 @@ static int ov7251_get_selection(struct v4l2_subdev *sd,
 {
        struct ov7251 *ov7251 = to_ov7251(sd);
 
-       if (sel->target != V4L2_SEL_TGT_CROP)
-               return -EINVAL;
+       switch (sel->target) {
+       case V4L2_SEL_TGT_CROP:
+               mutex_lock(&ov7251->lock);
+               sel->r = *__ov7251_get_pad_crop(ov7251, sd_state, sel->pad,
+                                               sel->which);
+               mutex_unlock(&ov7251->lock);
 
-       mutex_lock(&ov7251->lock);
-       sel->r = *__ov7251_get_pad_crop(ov7251, sd_state, sel->pad,
-                                       sel->which);
-       mutex_unlock(&ov7251->lock);
+               return 0;
 
-       return 0;
+       case V4L2_SEL_TGT_NATIVE_SIZE:
+               sel->r.top = 0;
+               sel->r.left = 0;
+               sel->r.width = OV7251_NATIVE_WIDTH;
+               sel->r.height = OV7251_NATIVE_HEIGHT;
+
+               return 0;
+
+       case V4L2_SEL_TGT_CROP_DEFAULT:
+       case V4L2_SEL_TGT_CROP_BOUNDS:
+               sel->r.top = OV7251_PIXEL_ARRAY_TOP;
+               sel->r.left = OV7251_PIXEL_ARRAY_LEFT;
+               sel->r.width = OV7251_PIXEL_ARRAY_WIDTH;
+               sel->r.height = OV7251_PIXEL_ARRAY_HEIGHT;
+
+               return 0;
+       }
+
+       return -EINVAL;
 }
 
 static int ov7251_s_stream(struct v4l2_subdev *subdev, int enable)
@@ -1142,6 +1033,24 @@ static int ov7251_s_stream(struct v4l2_subdev *subdev, int enable)
        mutex_lock(&ov7251->lock);
 
        if (enable) {
+               ret = pm_runtime_get_sync(ov7251->dev);
+               if (ret < 0)
+                       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");
+                       goto err_power_down;
+               }
+
                ret = ov7251_set_register_array(ov7251,
                                        ov7251->current_mode->data,
                                        ov7251->current_mode->data_size);
@@ -1149,23 +1058,29 @@ static int ov7251_s_stream(struct v4l2_subdev *subdev, int enable)
                        dev_err(ov7251->dev, "could not set mode %dx%d\n",
                                ov7251->current_mode->width,
                                ov7251->current_mode->height);
-                       goto exit;
+                       goto err_power_down;
                }
                ret = __v4l2_ctrl_handler_setup(&ov7251->ctrls);
                if (ret < 0) {
                        dev_err(ov7251->dev, "could not sync v4l2 controls\n");
-                       goto exit;
+                       goto err_power_down;
                }
                ret = ov7251_write_reg(ov7251, OV7251_SC_MODE_SELECT,
                                       OV7251_SC_MODE_SELECT_STREAMING);
+               if (ret)
+                       goto err_power_down;
        } else {
                ret = ov7251_write_reg(ov7251, OV7251_SC_MODE_SELECT,
                                       OV7251_SC_MODE_SELECT_SW_STANDBY);
+               pm_runtime_put(ov7251->dev);
        }
 
-exit:
        mutex_unlock(&ov7251->lock);
+       return ret;
 
+err_power_down:
+       pm_runtime_put_noidle(ov7251->dev);
+       mutex_unlock(&ov7251->lock);
        return ret;
 }
 
@@ -1175,7 +1090,7 @@ static int ov7251_get_frame_interval(struct v4l2_subdev *subdev,
        struct ov7251 *ov7251 = to_ov7251(subdev);
 
        mutex_lock(&ov7251->lock);
-       fi->interval = ov7251->current_mode->timeperframe;
+       fi->interval = ov7251->current_ival->timeperframe;
        mutex_unlock(&ov7251->lock);
 
        return 0;
@@ -1185,42 +1100,30 @@ static int ov7251_set_frame_interval(struct v4l2_subdev *subdev,
                                     struct v4l2_subdev_frame_interval *fi)
 {
        struct ov7251 *ov7251 = to_ov7251(subdev);
-       const struct ov7251_mode_info *new_mode;
+       const struct ov7251_frame_ival_info *new_ival;
        int ret = 0;
 
        mutex_lock(&ov7251->lock);
-       new_mode = ov7251_find_mode_by_ival(ov7251, &fi->interval);
-
-       if (new_mode != ov7251->current_mode) {
-               ret = __v4l2_ctrl_s_ctrl_int64(ov7251->pixel_clock,
-                                              new_mode->pixel_clock);
-               if (ret < 0)
-                       goto exit;
-
-               ret = __v4l2_ctrl_s_ctrl(ov7251->link_freq,
-                                        new_mode->link_freq);
-               if (ret < 0)
-                       goto exit;
-
-               ret = __v4l2_ctrl_modify_range(ov7251->exposure,
-                                              1, new_mode->exposure_max,
-                                              1, new_mode->exposure_def);
-               if (ret < 0)
-                       goto exit;
+       new_ival = ov7251_find_frame_ival_by_ival(ov7251, &fi->interval);
 
-               ret = __v4l2_ctrl_s_ctrl(ov7251->exposure,
-                                        new_mode->exposure_def);
+       if (new_ival != ov7251->current_ival) {
+               ret = __v4l2_ctrl_modify_range(ov7251->exposure, 1,
+                                              new_ival->vts -
+                                                       OV7251_EXPOSURE_OFFSET,
+                                              1, ov7251->exposure->val);
                if (ret < 0)
                        goto exit;
 
-               ret = __v4l2_ctrl_s_ctrl(ov7251->gain, 16);
+               ret = __v4l2_ctrl_s_ctrl(ov7251->vblank,
+                                        new_ival->vts -
+                                               ov7251->current_mode->height);
                if (ret < 0)
                        goto exit;
 
-               ov7251->current_mode = new_mode;
+               ov7251->current_ival = new_ival;
        }
 
-       fi->interval = ov7251->current_mode->timeperframe;
+       fi->interval = ov7251->current_ival->timeperframe;
 
 exit:
        mutex_unlock(&ov7251->lock);
@@ -1228,10 +1131,6 @@ exit:
        return ret;
 }
 
-static const struct v4l2_subdev_core_ops ov7251_core_ops = {
-       .s_power = ov7251_s_power,
-};
-
 static const struct v4l2_subdev_video_ops ov7251_video_ops = {
        .s_stream = ov7251_s_stream,
        .g_frame_interval = ov7251_get_frame_interval,
@@ -1249,18 +1148,117 @@ static const struct v4l2_subdev_pad_ops ov7251_subdev_pad_ops = {
 };
 
 static const struct v4l2_subdev_ops ov7251_subdev_ops = {
-       .core = &ov7251_core_ops,
        .video = &ov7251_video_ops,
        .pad = &ov7251_subdev_pad_ops,
 };
 
+static int ov7251_check_hwcfg(struct ov7251 *ov7251)
+{
+       struct fwnode_handle *fwnode = dev_fwnode(ov7251->dev);
+       struct v4l2_fwnode_endpoint bus_cfg = {
+               .bus_type = V4L2_MBUS_CSI2_DPHY,
+       };
+       struct fwnode_handle *endpoint;
+       bool freq_found;
+       int i, j;
+       int ret;
+
+       endpoint = fwnode_graph_get_next_endpoint(fwnode, NULL);
+       if (!endpoint)
+               return -EPROBE_DEFER; /* could be provided by cio2-bridge */
+
+       ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, &bus_cfg);
+       fwnode_handle_put(endpoint);
+       if (ret)
+               return dev_err_probe(ov7251->dev, ret,
+                                    "parsing endpoint node failed\n");
+
+       if (bus_cfg.bus_type != V4L2_MBUS_CSI2_DPHY) {
+               ret = -EINVAL;
+               dev_err(ov7251->dev, "invalid bus type (%u), must be (%u)\n",
+                       bus_cfg.bus_type, V4L2_MBUS_CSI2_DPHY);
+               goto out_free_bus_cfg;
+       }
+
+       if (bus_cfg.bus.mipi_csi2.num_data_lanes != 1) {
+               dev_err(ov7251->dev, "only a 1-lane CSI2 config is supported");
+               ret = -EINVAL;
+               goto out_free_bus_cfg;
+       }
+
+       if (!bus_cfg.nr_of_link_frequencies) {
+               dev_err(ov7251->dev, "no link frequencies defined\n");
+               ret = -EINVAL;
+               goto out_free_bus_cfg;
+       }
+
+       freq_found = false;
+       for (i = 0; i < bus_cfg.nr_of_link_frequencies; i++) {
+               for (j = 0; j < ARRAY_SIZE(link_freq); j++)
+                       if (bus_cfg.link_frequencies[i] == link_freq[j]) {
+                               freq_found = true;
+                               break;
+                       }
+
+               if (freq_found)
+                       break;
+       }
+
+       if (i == bus_cfg.nr_of_link_frequencies) {
+               dev_err(ov7251->dev, "no supported link freq found\n");
+               ret = -EINVAL;
+               goto out_free_bus_cfg;
+       }
+
+out_free_bus_cfg:
+       v4l2_fwnode_endpoint_free(&bus_cfg);
+
+       return ret;
+}
+
+static int ov7251_detect_chip(struct ov7251 *ov7251)
+{
+       u8 chip_id_high, chip_id_low, chip_rev;
+       int ret;
+
+       ret = ov7251_read_reg(ov7251, OV7251_CHIP_ID_HIGH, &chip_id_high);
+       if (ret < 0 || chip_id_high != OV7251_CHIP_ID_HIGH_BYTE)
+               return dev_err_probe(ov7251->dev, -ENODEV,
+                                    "could not read ID high\n");
+
+       ret = ov7251_read_reg(ov7251, OV7251_CHIP_ID_LOW, &chip_id_low);
+       if (ret < 0 || chip_id_low != OV7251_CHIP_ID_LOW_BYTE)
+               return dev_err_probe(ov7251->dev, -ENODEV,
+                                    "could not read ID low\n");
+
+       ret = ov7251_read_reg(ov7251, OV7251_SC_GP_IO_IN1, &chip_rev);
+       if (ret < 0)
+               return dev_err_probe(ov7251->dev, -ENODEV,
+                                    "could not read revision\n");
+       chip_rev >>= 4;
+
+       dev_info(ov7251->dev,
+                "OV7251 revision %x (%s) detected at address 0x%02x\n",
+                chip_rev,
+                chip_rev == 0x4 ? "1A / 1B" :
+                chip_rev == 0x5 ? "1C / 1D" :
+                chip_rev == 0x6 ? "1E" :
+                chip_rev == 0x7 ? "1F" : "unknown",
+                ov7251->i2c_client->addr);
+
+       return 0;
+}
+
 static int ov7251_probe(struct i2c_client *client)
 {
+       struct v4l2_fwnode_device_properties props;
        struct device *dev = &client->dev;
-       struct fwnode_handle *endpoint;
+       struct v4l2_ctrl *ctrl;
        struct ov7251 *ov7251;
-       u8 chip_id_high, chip_id_low, chip_rev;
+       unsigned int rate = 0;
+       u32 h_blank, v_blank, v_blank_max;
        int ret;
+       int i;
 
        ov7251 = devm_kzalloc(dev, sizeof(struct ov7251), GFP_KERNEL);
        if (!ov7251)
@@ -1269,51 +1267,45 @@ static int ov7251_probe(struct i2c_client *client)
        ov7251->i2c_client = client;
        ov7251->dev = dev;
 
-       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, &ov7251->ep);
-       fwnode_handle_put(endpoint);
-       if (ret < 0) {
-               dev_err(dev, "parsing endpoint node failed\n");
+       ret = ov7251_check_hwcfg(ov7251);
+       if (ret)
                return ret;
-       }
-
-       if (ov7251->ep.bus_type != V4L2_MBUS_CSI2_DPHY) {
-               dev_err(dev, "invalid bus type (%u), must be CSI2 (%u)\n",
-                       ov7251->ep.bus_type, V4L2_MBUS_CSI2_DPHY);
-               return -EINVAL;
-       }
 
        /* get system clock (xclk) */
-       ov7251->xclk = devm_clk_get(dev, "xclk");
+       ov7251->xclk = devm_clk_get(dev, NULL);
        if (IS_ERR(ov7251->xclk)) {
                dev_err(dev, "could not get xclk");
                return PTR_ERR(ov7251->xclk);
        }
 
+       /*
+        * We could have either a 24MHz or 19.2MHz clock rate from either dt or
+        * ACPI. We also need to support the IPU3 case which will have both an
+        * external clock AND a clock-frequency property.
+        */
        ret = fwnode_property_read_u32(dev_fwnode(dev), "clock-frequency",
-                                      &ov7251->xclk_freq);
-       if (ret) {
-               dev_err(dev, "could not get xclk frequency\n");
-               return ret;
+                                      &rate);
+       if (!ret && ov7251->xclk) {
+               ret = clk_set_rate(ov7251->xclk, rate);
+               if (ret)
+                       return dev_err_probe(dev, ret,
+                                            "failed to set clock rate\n");
+       } else if (ret && !ov7251->xclk) {
+               return dev_err_probe(dev, ret, "invalid clock config\n");
        }
 
-       /* external clock must be 24MHz, allow 1% tolerance */
-       if (ov7251->xclk_freq < 23760000 || ov7251->xclk_freq > 24240000) {
-               dev_err(dev, "external clock frequency %u is not supported\n",
-                       ov7251->xclk_freq);
-               return -EINVAL;
-       }
+       ov7251->xclk_freq = rate ? rate : clk_get_rate(ov7251->xclk);
 
-       ret = clk_set_rate(ov7251->xclk, ov7251->xclk_freq);
-       if (ret) {
-               dev_err(dev, "could not set xclk frequency\n");
-               return ret;
-       }
+       for (i = 0; i < ARRAY_SIZE(supported_xclk_rates); i++)
+               if (ov7251->xclk_freq == supported_xclk_rates[i])
+                       break;
+
+       if (i == ARRAY_SIZE(supported_xclk_rates))
+               return dev_err_probe(dev, -EINVAL,
+                                    "clock rate %u Hz is unsupported\n",
+                                    ov7251->xclk_freq);
+
+       ov7251->pll_configs = ov7251_pll_configs[i];
 
        ov7251->io_regulator = devm_regulator_get(dev, "vdddo");
        if (IS_ERR(ov7251->io_regulator)) {
@@ -1333,7 +1325,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);
@@ -1341,7 +1334,10 @@ static int ov7251_probe(struct i2c_client *client)
 
        mutex_init(&ov7251->lock);
 
-       v4l2_ctrl_handler_init(&ov7251->ctrls, 7);
+       ov7251->current_mode = &ov7251_mode_info_data[0];
+       ov7251->current_ival = &ov7251_frame_ival_info_data[0];
+
+       v4l2_ctrl_handler_init(&ov7251->ctrls, 11);
        ov7251->ctrls.lock = &ov7251->lock;
 
        v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops,
@@ -1350,23 +1346,34 @@ static int ov7251_probe(struct i2c_client *client)
                          V4L2_CID_VFLIP, 0, 1, 1, 0);
        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_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops,
+                         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,
                                     0, 0, ov7251_test_pattern_menu);
-       ov7251->pixel_clock = v4l2_ctrl_new_std(&ov7251->ctrls,
-                                               &ov7251_ctrl_ops,
-                                               V4L2_CID_PIXEL_RATE,
-                                               1, INT_MAX, 1, 1);
-       ov7251->link_freq = v4l2_ctrl_new_int_menu(&ov7251->ctrls,
-                                                  &ov7251_ctrl_ops,
-                                                  V4L2_CID_LINK_FREQ,
-                                                  ARRAY_SIZE(link_freq) - 1,
-                                                  0, link_freq);
-       if (ov7251->link_freq)
-               ov7251->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+       v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops,
+                         V4L2_CID_PIXEL_RATE, OV7251_PIXEL_CLOCK,
+                         OV7251_PIXEL_CLOCK, 1, OV7251_PIXEL_CLOCK);
+       ctrl = v4l2_ctrl_new_int_menu(&ov7251->ctrls, &ov7251_ctrl_ops,
+                                     V4L2_CID_LINK_FREQ,
+                                     ARRAY_SIZE(link_freq) - 1,
+                                     0, link_freq);
+       if (ctrl)
+               ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+       h_blank = OV7251_HTS - ov7251->current_mode->width;
+       ov7251->hblank = v4l2_ctrl_new_std(&ov7251->ctrls, NULL,
+                                          V4L2_CID_HBLANK, h_blank,
+                                          h_blank, 1, h_blank);
+       if (ov7251->hblank)
+               ov7251->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+       v_blank = ov7251->current_ival->vts - ov7251->current_mode->height;
+       v_blank_max = OV7251_VTS_MAX - ov7251->current_mode->width;
+       ov7251->vblank = v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops,
+                                          V4L2_CID_VBLANK,
+                                          OV7251_VTS_MIN_OFFSET,
+                                          v_blank_max, 1, v_blank);
 
        ov7251->sd.ctrl_handler = &ov7251->ctrls;
 
@@ -1377,6 +1384,15 @@ static int ov7251_probe(struct i2c_client *client)
                goto free_ctrl;
        }
 
+       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.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
        ov7251->pad.flags = MEDIA_PAD_FL_SOURCE;
@@ -1389,47 +1405,24 @@ static int ov7251_probe(struct i2c_client *client)
                goto free_ctrl;
        }
 
-       ret = ov7251_s_power(&ov7251->sd, true);
-       if (ret < 0) {
-               dev_err(dev, "could not power up OV7251\n");
+       ret = ov7251_set_power_on(ov7251);
+       if (ret)
                goto free_entity;
-       }
-
-       ret = ov7251_read_reg(ov7251, OV7251_CHIP_ID_HIGH, &chip_id_high);
-       if (ret < 0 || chip_id_high != OV7251_CHIP_ID_HIGH_BYTE) {
-               dev_err(dev, "could not read ID high\n");
-               ret = -ENODEV;
-               goto power_down;
-       }
-       ret = ov7251_read_reg(ov7251, OV7251_CHIP_ID_LOW, &chip_id_low);
-       if (ret < 0 || chip_id_low != OV7251_CHIP_ID_LOW_BYTE) {
-               dev_err(dev, "could not read ID low\n");
-               ret = -ENODEV;
-               goto power_down;
-       }
 
-       ret = ov7251_read_reg(ov7251, OV7251_SC_GP_IO_IN1, &chip_rev);
-       if (ret < 0) {
-               dev_err(dev, "could not read revision\n");
-               ret = -ENODEV;
+       ret = ov7251_detect_chip(ov7251);
+       if (ret)
                goto power_down;
-       }
-       chip_rev >>= 4;
 
-       dev_info(dev, "OV7251 revision %x (%s) detected at address 0x%02x\n",
-                chip_rev,
-                chip_rev == 0x4 ? "1A / 1B" :
-                chip_rev == 0x5 ? "1C / 1D" :
-                chip_rev == 0x6 ? "1E" :
-                chip_rev == 0x7 ? "1F" : "unknown",
-                client->addr);
+       pm_runtime_set_active(&client->dev);
+       pm_runtime_get_noresume(&client->dev);
+       pm_runtime_enable(&client->dev);
 
        ret = ov7251_read_reg(ov7251, OV7251_PRE_ISP_00,
                              &ov7251->pre_isp_00);
        if (ret < 0) {
                dev_err(dev, "could not read test pattern value\n");
                ret = -ENODEV;
-               goto power_down;
+               goto err_pm_runtime;
        }
 
        ret = ov7251_read_reg(ov7251, OV7251_TIMING_FORMAT1,
@@ -1437,7 +1430,7 @@ static int ov7251_probe(struct i2c_client *client)
        if (ret < 0) {
                dev_err(dev, "could not read vflip value\n");
                ret = -ENODEV;
-               goto power_down;
+               goto err_pm_runtime;
        }
 
        ret = ov7251_read_reg(ov7251, OV7251_TIMING_FORMAT2,
@@ -1445,10 +1438,12 @@ static int ov7251_probe(struct i2c_client *client)
        if (ret < 0) {
                dev_err(dev, "could not read hflip value\n");
                ret = -ENODEV;
-               goto power_down;
+               goto err_pm_runtime;
        }
 
-       ov7251_s_power(&ov7251->sd, false);
+       pm_runtime_set_autosuspend_delay(&client->dev, 1000);
+       pm_runtime_use_autosuspend(&client->dev);
+       pm_runtime_put_autosuspend(&client->dev);
 
        ret = v4l2_async_register_subdev(&ov7251->sd);
        if (ret < 0) {
@@ -1460,8 +1455,11 @@ static int ov7251_probe(struct i2c_client *client)
 
        return 0;
 
+err_pm_runtime:
+       pm_runtime_disable(ov7251->dev);
+       pm_runtime_put_noidle(ov7251->dev);
 power_down:
-       ov7251_s_power(&ov7251->sd, false);
+       ov7251_set_power_off(ov7251);
 free_entity:
        media_entity_cleanup(&ov7251->sd.entity);
 free_ctrl:
@@ -1481,19 +1479,36 @@ static int ov7251_remove(struct i2c_client *client)
        v4l2_ctrl_handler_free(&ov7251->ctrls);
        mutex_destroy(&ov7251->lock);
 
+       pm_runtime_disable(ov7251->dev);
+       if (!pm_runtime_status_suspended(ov7251->dev))
+               ov7251_set_power_off(ov7251);
+       pm_runtime_set_suspended(ov7251->dev);
+
        return 0;
 }
 
+static const struct dev_pm_ops ov7251_pm_ops = {
+       SET_RUNTIME_PM_OPS(ov7251_sensor_suspend, ov7251_sensor_resume, NULL)
+};
+
 static const struct of_device_id ov7251_of_match[] = {
        { .compatible = "ovti,ov7251" },
        { /* sentinel */ }
 };
 MODULE_DEVICE_TABLE(of, ov7251_of_match);
 
+static const struct acpi_device_id ov7251_acpi_match[] = {
+       { "INT347E" },
+       { }
+};
+MODULE_DEVICE_TABLE(acpi, ov7251_acpi_match);
+
 static struct i2c_driver ov7251_i2c_driver = {
        .driver = {
                .of_match_table = ov7251_of_match,
+               .acpi_match_table = ov7251_acpi_match,
                .name  = "ov7251",
+               .pm = &ov7251_pm_ops,
        },
        .probe_new  = ov7251_probe,
        .remove = ov7251_remove,
diff --git a/drivers/media/i2c/ov9281.c b/drivers/media/i2c/ov9281.c
new file mode 100644 (file)
index 0000000..552b658
--- /dev/null
@@ -0,0 +1,1297 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Omnivision OV9281 1280x800 global shutter image sensor driver
+ *
+ * This driver has been taken from
+ * https://github.com/rockchip-linux/kernel/blob/develop-4.4/drivers/media/i2c/ov9281.c
+ * cleaned up, made to compile against mainline kernels instead of the Rockchip
+ * vendor kernel, and the relevant controls added to work with libcamera.
+ *
+ * Copyright (C) 2017 Fuzhou Rockchip Electronics Co., Ltd.
+ * V0.0X01.0X02 fix mclk issue when probe multiple camera.
+ * V0.0X01.0X03 add enum_frame_interval function.
+ */
+
+#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 <linux/sysfs.h>
+#include <linux/slab.h>
+#include <media/media-entity.h>
+#include <media/v4l2-async.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-subdev.h>
+
+#define OV9281_LINK_FREQ_400MHZ                400000000
+#define OV9281_LANES                   2
+
+/* pixel rate = link frequency * 2 * lanes / BITS_PER_SAMPLE */
+#define OV9281_PIXEL_RATE_10BIT                (OV9281_LINK_FREQ_400MHZ * 2 * \
+                                        OV9281_LANES / 10)
+#define OV9281_PIXEL_RATE_8BIT         (OV9281_LINK_FREQ_400MHZ * 2 * \
+                                        OV9281_LANES / 8)
+#define OV9281_XVCLK_FREQ              24000000
+
+#define CHIP_ID                                0x9281
+#define OV9281_REG_CHIP_ID             0x300a
+
+#define OV9281_REG_TIMING_FORMAT_1             0x3820
+#define OV9281_REG_TIMING_FORMAT_2             0x3821
+#define OV9281_FLIP_BIT                                BIT(2)
+
+#define OV9281_REG_CTRL_MODE           0x0100
+#define OV9281_MODE_SW_STANDBY         0x0
+#define OV9281_MODE_STREAMING          BIT(0)
+
+#define OV9281_REG_EXPOSURE            0x3500
+#define        OV9281_EXPOSURE_MIN             4
+#define        OV9281_EXPOSURE_STEP            1
+/*
+ * Number of lines less than frame length (VTS) that exposure must be.
+ * Datasheet states 25, although empirically 5 appears to work.
+ */
+#define OV9281_EXPOSURE_OFFSET         25
+
+#define OV9281_REG_GAIN_H              0x3508
+#define OV9281_REG_GAIN_L              0x3509
+#define OV9281_GAIN_H_MASK             0x07
+#define OV9281_GAIN_H_SHIFT            8
+#define OV9281_GAIN_L_MASK             0xff
+#define OV9281_GAIN_MIN                        0x10
+#define OV9281_GAIN_MAX                        0xf8
+#define OV9281_GAIN_STEP               1
+#define OV9281_GAIN_DEFAULT            0x10
+
+#define OV9281_REG_TEST_PATTERN                0x5e00
+#define OV9281_TEST_PATTERN_ENABLE     0x80
+#define OV9281_TEST_PATTERN_DISABLE    0x0
+
+#define OV9281_REG_VTS                 0x380e
+#define OV9281_VTS_MAX                 0x7fff
+
+/*
+ * OV9281 native and active pixel array size.
+ * Datasheet not available to confirm these values, so assume there are no
+ * border pixels.
+ */
+#define OV9281_NATIVE_WIDTH            1280U
+#define OV9281_NATIVE_HEIGHT           800U
+#define OV9281_PIXEL_ARRAY_LEFT                0U
+#define OV9281_PIXEL_ARRAY_TOP         0U
+#define OV9281_PIXEL_ARRAY_WIDTH       1280U
+#define OV9281_PIXEL_ARRAY_HEIGHT      800U
+
+#define REG_NULL                       0xFFFF
+
+#define OV9281_REG_VALUE_08BIT         1
+#define OV9281_REG_VALUE_16BIT         2
+#define OV9281_REG_VALUE_24BIT         3
+
+#define OV9281_NAME                    "ov9281"
+
+static const char * const ov9281_supply_names[] = {
+       "avdd",         /* Analog power */
+       "dovdd",        /* Digital I/O power */
+       "dvdd",         /* Digital core power */
+};
+
+#define OV9281_NUM_SUPPLIES ARRAY_SIZE(ov9281_supply_names)
+
+struct regval {
+       u16 addr;
+       u8 val;
+};
+
+struct ov9281_mode {
+       u32 width;
+       u32 height;
+       u32 hts_def;
+       u32 vts_def;
+       u32 exp_def;
+       struct v4l2_rect crop;
+       const struct regval *reg_list;
+};
+
+struct ov9281 {
+       struct i2c_client       *client;
+       struct clk              *xvclk;
+       struct gpio_desc        *reset_gpio;
+       struct gpio_desc        *pwdn_gpio;
+       struct regulator_bulk_data supplies[OV9281_NUM_SUPPLIES];
+
+       struct v4l2_subdev      subdev;
+       struct media_pad        pad;
+       struct v4l2_ctrl_handler ctrl_handler;
+       struct v4l2_ctrl        *exposure;
+       struct v4l2_ctrl        *anal_gain;
+       struct v4l2_ctrl        *digi_gain;
+       struct v4l2_ctrl        *hblank;
+       struct v4l2_ctrl        *vblank;
+       struct v4l2_ctrl        *hflip;
+       struct v4l2_ctrl        *vflip;
+       struct v4l2_ctrl        *pixel_rate;
+       struct v4l2_ctrl        *test_pattern;
+       struct mutex            mutex;
+       bool                    streaming;
+       bool                    power_on;
+       const struct ov9281_mode *cur_mode;
+       u32                     code;
+};
+
+#define to_ov9281(sd) container_of(sd, struct ov9281, subdev)
+
+/*
+ * Xclk 24Mhz
+ * max_framerate 120fps for 10 bit, 144fps for 8 bit.
+ * mipi_datarate per lane 800Mbps
+ */
+static const struct regval ov9281_common_regs[] = {
+       {0x0103, 0x01},
+       {0x0302, 0x32},
+       {0x030e, 0x02},
+       {0x3001, 0x00},
+       {0x3004, 0x00},
+       {0x3005, 0x00},
+       {0x3006, 0x04},
+       {0x3011, 0x0a},
+       {0x3013, 0x18},
+       {0x3022, 0x01},
+       {0x3023, 0x00},
+       {0x302c, 0x00},
+       {0x302f, 0x00},
+       {0x3030, 0x04},
+       {0x3039, 0x32},
+       {0x303a, 0x00},
+       {0x303f, 0x01},
+       {0x3500, 0x00},
+       {0x3501, 0x2a},
+       {0x3502, 0x90},
+       {0x3503, 0x08},
+       {0x3505, 0x8c},
+       {0x3507, 0x03},
+       {0x3508, 0x00},
+       {0x3509, 0x10},
+       {0x3610, 0x80},
+       {0x3611, 0xa0},
+       {0x3620, 0x6f},
+       {0x3632, 0x56},
+       {0x3633, 0x78},
+       {0x3666, 0x00},
+       {0x366f, 0x5a},
+       {0x3680, 0x84},
+       {0x3712, 0x80},
+       {0x372d, 0x22},
+       {0x3731, 0x80},
+       {0x3732, 0x30},
+       {0x377d, 0x22},
+       {0x3788, 0x02},
+       {0x3789, 0xa4},
+       {0x378a, 0x00},
+       {0x378b, 0x4a},
+       {0x3799, 0x20},
+       {0x3881, 0x42},
+       {0x38b1, 0x00},
+       {0x3920, 0xff},
+       {0x4010, 0x40},
+       {0x4043, 0x40},
+       {0x4307, 0x30},
+       {0x4317, 0x00},
+       {0x4501, 0x00},
+       {0x450a, 0x08},
+       {0x4601, 0x04},
+       {0x470f, 0x00},
+       {0x4f07, 0x00},
+       {0x4800, 0x00},
+       {0x5000, 0x9f},
+       {0x5001, 0x00},
+       {0x5e00, 0x00},
+       {0x5d00, 0x07},
+       {0x5d01, 0x00},
+       {REG_NULL, 0x00},
+};
+
+static const struct regval ov9281_1280x800_regs[] = {
+       {0x3778, 0x00},
+       {0x3800, 0x00},
+       {0x3801, 0x00},
+       {0x3802, 0x00},
+       {0x3803, 0x00},
+       {0x3804, 0x05},
+       {0x3805, 0x0f},
+       {0x3806, 0x03},
+       {0x3807, 0x2f},
+       {0x3808, 0x05},
+       {0x3809, 0x00},
+       {0x380a, 0x03},
+       {0x380b, 0x20},
+       {0x380c, 0x02},
+       {0x380d, 0xd8},
+       {0x380e, 0x03},
+       {0x380f, 0x8e},
+       {0x3810, 0x00},
+       {0x3811, 0x08},
+       {0x3812, 0x00},
+       {0x3813, 0x08},
+       {0x3814, 0x11},
+       {0x3815, 0x11},
+       {0x3820, 0x40},
+       {0x3821, 0x00},
+       {0x4003, 0x40},
+       {0x4008, 0x04},
+       {0x4009, 0x0b},
+       {0x400c, 0x00},
+       {0x400d, 0x07},
+       {0x4507, 0x00},
+       {0x4509, 0x00},
+       {REG_NULL, 0x00},
+};
+
+static const struct regval ov9281_1280x720_regs[] = {
+       {0x3778, 0x00},
+       {0x3800, 0x00},
+       {0x3801, 0x00},
+       {0x3802, 0x00},
+       {0x3803, 0x28},
+       {0x3804, 0x05},
+       {0x3805, 0x0f},
+       {0x3806, 0x03},
+       {0x3807, 0x07},
+       {0x3808, 0x05},
+       {0x3809, 0x00},
+       {0x380a, 0x02},
+       {0x380b, 0xd0},
+       {0x380c, 0x02},
+       {0x380d, 0xd8},
+       {0x380e, 0x03},
+       {0x380f, 0x8e},
+       {0x3810, 0x00},
+       {0x3811, 0x08},
+       {0x3812, 0x00},
+       {0x3813, 0x08},
+       {0x3814, 0x11},
+       {0x3815, 0x11},
+       {0x3820, 0x40},
+       {0x3821, 0x00},
+       {0x4003, 0x40},
+       {0x4008, 0x04},
+       {0x4009, 0x0b},
+       {0x400c, 0x00},
+       {0x400d, 0x07},
+       {0x4507, 0x00},
+       {0x4509, 0x00},
+       {REG_NULL, 0x00},
+};
+
+static const struct regval ov9281_640x400_regs[] = {
+       {0x3778, 0x10},
+       {0x3800, 0x00},
+       {0x3801, 0x00},
+       {0x3802, 0x00},
+       {0x3803, 0x00},
+       {0x3804, 0x05},
+       {0x3805, 0x0f},
+       {0x3806, 0x03},
+       {0x3807, 0x2f},
+       {0x3808, 0x02},
+       {0x3809, 0x80},
+       {0x380a, 0x01},
+       {0x380b, 0x90},
+       {0x380c, 0x02},
+       {0x380d, 0xd8},
+       {0x380e, 0x02},
+       {0x380f, 0x08},
+       {0x3810, 0x00},
+       {0x3811, 0x04},
+       {0x3812, 0x00},
+       {0x3813, 0x04},
+       {0x3814, 0x31},
+       {0x3815, 0x22},
+       {0x3820, 0x60},
+       {0x3821, 0x01},
+       {0x4008, 0x02},
+       {0x4009, 0x05},
+       {0x400c, 0x00},
+       {0x400d, 0x03},
+       {0x4507, 0x03},
+       {0x4509, 0x80},
+       {REG_NULL, 0x00},
+};
+
+static const struct regval op_10bit[] = {
+       {0x030d, 0x50},
+       {0x3662, 0x05},
+       {REG_NULL, 0x00},
+};
+
+static const struct regval op_8bit[] = {
+       {0x030d, 0x60},
+       {0x3662, 0x07},
+       {REG_NULL, 0x00},
+};
+
+static const struct ov9281_mode supported_modes[] = {
+       {
+               .width = 1280,
+               .height = 800,
+               .exp_def = 0x0320,
+               .hts_def = 0x05b0,      /* 0x2d8*2 */
+               .vts_def = 0x038e,
+               .crop = {
+                       .left = 0,
+                       .top = 0,
+                       .width = 1280,
+                       .height = 800
+               },
+               .reg_list = ov9281_1280x800_regs,
+       },
+       {
+               .width = 1280,
+               .height = 720,
+               .exp_def = 0x0320,
+               .hts_def = 0x05b0,
+               .vts_def = 761,
+               .crop = {
+                       .left = 0,
+                       .top = 40,
+                       .width = 1280,
+                       .height = 720
+               },
+               .reg_list = ov9281_1280x720_regs,
+       },
+       {
+               .width = 640,
+               .height = 400,
+               .exp_def = 0x0320,
+               .hts_def = 0x05b0,
+               .vts_def = 422,
+               .crop = {
+                       .left = 0,
+                       .top = 0,
+                       .width = 1280,
+                       .height = 800
+               },
+               .reg_list = ov9281_640x400_regs,
+       },
+};
+
+static const s64 link_freq_menu_items[] = {
+       OV9281_LINK_FREQ_400MHZ
+};
+
+static const char * const ov9281_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 ov9281_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 ov9281_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 = ov9281_write_reg(client, regs[i].addr,
+                                      OV9281_REG_VALUE_08BIT, regs[i].val);
+
+       return ret;
+}
+
+/* Read registers up to 4 at a time */
+static int ov9281_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 *)&reg_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 ov9281_get_reso_dist(const struct ov9281_mode *mode,
+                               struct v4l2_mbus_framefmt *framefmt)
+{
+       return abs(mode->width - framefmt->width) +
+              abs(mode->height - framefmt->height);
+}
+
+static const struct ov9281_mode *
+ov9281_find_best_fit(struct v4l2_subdev_format *fmt)
+{
+       struct v4l2_mbus_framefmt *framefmt = &fmt->format;
+       int dist;
+       int cur_best_fit = 0;
+       int cur_best_fit_dist = -1;
+       unsigned int i;
+
+       for (i = 0; i < ARRAY_SIZE(supported_modes); i++) {
+               dist = ov9281_get_reso_dist(&supported_modes[i], framefmt);
+               if (cur_best_fit_dist == -1 || dist < cur_best_fit_dist) {
+                       cur_best_fit_dist = dist;
+                       cur_best_fit = i;
+               }
+       }
+
+       return &supported_modes[cur_best_fit];
+}
+
+static int ov9281_set_fmt(struct v4l2_subdev *sd,
+                         struct v4l2_subdev_state *sd_state,
+                         struct v4l2_subdev_format *fmt)
+{
+       struct ov9281 *ov9281 = to_ov9281(sd);
+       const struct ov9281_mode *mode;
+       s64 h_blank, vblank_def, pixel_rate;
+
+       mutex_lock(&ov9281->mutex);
+
+       mode = ov9281_find_best_fit(fmt);
+       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_get_try_format(sd, sd_state, fmt->pad) = fmt->format;
+       } else {
+               ov9281->cur_mode = mode;
+               ov9281->code = fmt->format.code;
+               h_blank = mode->hts_def - mode->width;
+               __v4l2_ctrl_modify_range(ov9281->hblank, h_blank,
+                                        h_blank, 1, h_blank);
+               __v4l2_ctrl_s_ctrl(ov9281->hblank, h_blank);
+               vblank_def = mode->vts_def - mode->height;
+               __v4l2_ctrl_modify_range(ov9281->vblank, vblank_def,
+                                        OV9281_VTS_MAX - mode->height,
+                                        1, vblank_def);
+               __v4l2_ctrl_s_ctrl(ov9281->vblank, vblank_def);
+
+               pixel_rate = (fmt->format.code == MEDIA_BUS_FMT_Y10_1X10) ?
+                       OV9281_PIXEL_RATE_10BIT : OV9281_PIXEL_RATE_8BIT;
+               __v4l2_ctrl_modify_range(ov9281->pixel_rate, pixel_rate,
+                                        pixel_rate, 1, pixel_rate);
+       }
+
+       mutex_unlock(&ov9281->mutex);
+
+       return 0;
+}
+
+static int ov9281_get_fmt(struct v4l2_subdev *sd,
+                         struct v4l2_subdev_state *sd_state,
+                         struct v4l2_subdev_format *fmt)
+{
+       struct ov9281 *ov9281 = to_ov9281(sd);
+       const struct ov9281_mode *mode = ov9281->cur_mode;
+
+       mutex_lock(&ov9281->mutex);
+       if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+               fmt->format = *v4l2_subdev_get_try_format(sd, sd_state,
+                                                         fmt->pad);
+       } else {
+               fmt->format.width = mode->width;
+               fmt->format.height = mode->height;
+               fmt->format.code = ov9281->code;
+               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);
+       }
+       mutex_unlock(&ov9281->mutex);
+
+       return 0;
+}
+
+static int ov9281_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 ov9281_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 ov9281_enable_test_pattern(struct ov9281 *ov9281, u32 pattern)
+{
+       u32 val;
+
+       if (pattern)
+               val = (pattern - 1) | OV9281_TEST_PATTERN_ENABLE;
+       else
+               val = OV9281_TEST_PATTERN_DISABLE;
+
+       return ov9281_write_reg(ov9281->client, OV9281_REG_TEST_PATTERN,
+                               OV9281_REG_VALUE_08BIT, val);
+}
+
+static int ov9281_set_ctrl_hflip(struct ov9281 *ov9281, int value)
+{
+       u32 current_val;
+       int ret = ov9281_read_reg(ov9281->client, OV9281_REG_TIMING_FORMAT_2,
+                                       OV9281_REG_VALUE_08BIT, &current_val);
+       if (!ret) {
+               if (value)
+                       current_val |= OV9281_FLIP_BIT;
+               else
+                       current_val &= ~OV9281_FLIP_BIT;
+               return ov9281_write_reg(ov9281->client,
+                                               OV9281_REG_TIMING_FORMAT_2,
+                                               OV9281_REG_VALUE_08BIT,
+                                               current_val);
+       }
+       return ret;
+}
+
+static int ov9281_set_ctrl_vflip(struct ov9281 *ov9281, int value)
+{
+       u32 current_val;
+       int ret = ov9281_read_reg(ov9281->client, OV9281_REG_TIMING_FORMAT_1,
+                                       OV9281_REG_VALUE_08BIT, &current_val);
+       if (!ret) {
+               if (value)
+                       current_val |= OV9281_FLIP_BIT;
+               else
+                       current_val &= ~OV9281_FLIP_BIT;
+               return ov9281_write_reg(ov9281->client,
+                                               OV9281_REG_TIMING_FORMAT_1,
+                                               OV9281_REG_VALUE_08BIT,
+                                               current_val);
+       }
+       return ret;
+}
+
+static const struct v4l2_rect *
+__ov9281_get_pad_crop(struct ov9281 *ov9281,
+                     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_get_try_crop(&ov9281->subdev, sd_state,
+                                               pad);
+       case V4L2_SUBDEV_FORMAT_ACTIVE:
+               return &ov9281->cur_mode->crop;
+       }
+
+       return NULL;
+}
+
+static int ov9281_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 ov9281 *ov9281 = to_ov9281(sd);
+
+               mutex_lock(&ov9281->mutex);
+               sel->r = *__ov9281_get_pad_crop(ov9281, sd_state, sel->pad,
+                                               sel->which);
+               mutex_unlock(&ov9281->mutex);
+
+               return 0;
+       }
+
+       case V4L2_SEL_TGT_NATIVE_SIZE:
+               sel->r.top = 0;
+               sel->r.left = 0;
+               sel->r.width = OV9281_NATIVE_WIDTH;
+               sel->r.height = OV9281_NATIVE_HEIGHT;
+
+               return 0;
+
+       case V4L2_SEL_TGT_CROP_DEFAULT:
+       case V4L2_SEL_TGT_CROP_BOUNDS:
+               sel->r.top = OV9281_PIXEL_ARRAY_TOP;
+               sel->r.left = OV9281_PIXEL_ARRAY_LEFT;
+               sel->r.width = OV9281_PIXEL_ARRAY_WIDTH;
+               sel->r.height = OV9281_PIXEL_ARRAY_HEIGHT;
+
+               return 0;
+       }
+
+       return -EINVAL;
+}
+
+static int __ov9281_start_stream(struct ov9281 *ov9281)
+{
+       int ret;
+
+       ret = ov9281_write_array(ov9281->client, ov9281_common_regs);
+       if (ret)
+               return ret;
+
+       ret = ov9281_write_array(ov9281->client, ov9281->cur_mode->reg_list);
+       if (ret)
+               return ret;
+
+       if (ov9281->code == MEDIA_BUS_FMT_Y10_1X10)
+               ret = ov9281_write_array(ov9281->client, op_10bit);
+       else
+               ret = ov9281_write_array(ov9281->client, op_8bit);
+       if (ret)
+               return ret;
+
+       /* In case these controls are set before streaming */
+       mutex_unlock(&ov9281->mutex);
+       ret = v4l2_ctrl_handler_setup(&ov9281->ctrl_handler);
+       mutex_lock(&ov9281->mutex);
+       if (ret)
+               return ret;
+
+       return ov9281_write_reg(ov9281->client, OV9281_REG_CTRL_MODE,
+                               OV9281_REG_VALUE_08BIT, OV9281_MODE_STREAMING);
+}
+
+static int __ov9281_stop_stream(struct ov9281 *ov9281)
+{
+       return ov9281_write_reg(ov9281->client, OV9281_REG_CTRL_MODE,
+                               OV9281_REG_VALUE_08BIT, OV9281_MODE_SW_STANDBY);
+}
+
+static int ov9281_s_stream(struct v4l2_subdev *sd, int on)
+{
+       struct ov9281 *ov9281 = to_ov9281(sd);
+       struct i2c_client *client = ov9281->client;
+       int ret = 0;
+
+       mutex_lock(&ov9281->mutex);
+       on = !!on;
+       if (on == ov9281->streaming)
+               goto unlock_and_return;
+
+       if (on) {
+               ret = pm_runtime_get_sync(&client->dev);
+               if (ret < 0) {
+                       pm_runtime_put_noidle(&client->dev);
+                       goto unlock_and_return;
+               }
+
+               ret = __ov9281_start_stream(ov9281);
+               if (ret) {
+                       v4l2_err(sd, "start stream failed while write regs\n");
+                       pm_runtime_put(&client->dev);
+                       goto unlock_and_return;
+               }
+       } else {
+               __ov9281_stop_stream(ov9281);
+               pm_runtime_put(&client->dev);
+       }
+
+       ov9281->streaming = on;
+
+unlock_and_return:
+       mutex_unlock(&ov9281->mutex);
+
+       return ret;
+}
+
+static int ov9281_s_power(struct v4l2_subdev *sd, int on)
+{
+       struct ov9281 *ov9281 = to_ov9281(sd);
+       struct i2c_client *client = ov9281->client;
+       int ret = 0;
+
+       mutex_lock(&ov9281->mutex);
+
+       /* If the power state is not modified - no work to do. */
+       if (ov9281->power_on == !!on)
+               goto unlock_and_return;
+
+       if (on) {
+               ret = pm_runtime_get_sync(&client->dev);
+               if (ret < 0) {
+                       pm_runtime_put_noidle(&client->dev);
+                       goto unlock_and_return;
+               }
+               ov9281->power_on = true;
+       } else {
+               pm_runtime_put(&client->dev);
+               ov9281->power_on = false;
+       }
+
+unlock_and_return:
+       mutex_unlock(&ov9281->mutex);
+
+       return ret;
+}
+
+/* Calculate the delay in us by clock rate and clock cycles */
+static inline u32 ov9281_cal_delay(u32 cycles)
+{
+       return DIV_ROUND_UP(cycles, OV9281_XVCLK_FREQ / 1000 / 1000);
+}
+
+static int __ov9281_power_on(struct ov9281 *ov9281)
+{
+       int ret;
+       u32 delay_us;
+       struct device *dev = &ov9281->client->dev;
+
+       ret = clk_set_rate(ov9281->xvclk, OV9281_XVCLK_FREQ);
+       if (ret < 0)
+               dev_warn(dev, "Failed to set xvclk rate (24MHz)\n");
+       if (clk_get_rate(ov9281->xvclk) != OV9281_XVCLK_FREQ)
+               dev_warn(dev, "xvclk mismatched, modes are based on 24MHz - rate is %lu\n",
+                        clk_get_rate(ov9281->xvclk));
+
+       ret = clk_prepare_enable(ov9281->xvclk);
+       if (ret < 0) {
+               dev_err(dev, "Failed to enable xvclk\n");
+               return ret;
+       }
+
+       if (!IS_ERR(ov9281->reset_gpio))
+               gpiod_set_value_cansleep(ov9281->reset_gpio, 0);
+
+       ret = regulator_bulk_enable(OV9281_NUM_SUPPLIES, ov9281->supplies);
+       if (ret < 0) {
+               dev_err(dev, "Failed to enable regulators\n");
+               goto disable_clk;
+       }
+
+       if (!IS_ERR(ov9281->reset_gpio))
+               gpiod_set_value_cansleep(ov9281->reset_gpio, 1);
+
+       usleep_range(500, 1000);
+       if (!IS_ERR(ov9281->pwdn_gpio))
+               gpiod_set_value_cansleep(ov9281->pwdn_gpio, 1);
+
+       /* 8192 cycles prior to first SCCB transaction */
+       delay_us = ov9281_cal_delay(8192);
+       usleep_range(delay_us, delay_us * 2);
+
+       return 0;
+
+disable_clk:
+       clk_disable_unprepare(ov9281->xvclk);
+
+       return ret;
+}
+
+static void __ov9281_power_off(struct ov9281 *ov9281)
+{
+       if (!IS_ERR(ov9281->pwdn_gpio))
+               gpiod_set_value_cansleep(ov9281->pwdn_gpio, 0);
+       clk_disable_unprepare(ov9281->xvclk);
+       if (!IS_ERR(ov9281->reset_gpio))
+               gpiod_set_value_cansleep(ov9281->reset_gpio, 0);
+       regulator_bulk_disable(OV9281_NUM_SUPPLIES, ov9281->supplies);
+}
+
+static int ov9281_runtime_resume(struct device *dev)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct v4l2_subdev *sd = i2c_get_clientdata(client);
+       struct ov9281 *ov9281 = to_ov9281(sd);
+
+       return __ov9281_power_on(ov9281);
+}
+
+static int ov9281_runtime_suspend(struct device *dev)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct v4l2_subdev *sd = i2c_get_clientdata(client);
+       struct ov9281 *ov9281 = to_ov9281(sd);
+
+       __ov9281_power_off(ov9281);
+
+       return 0;
+}
+
+static int ov9281_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
+{
+       struct ov9281 *ov9281 = to_ov9281(sd);
+       struct v4l2_mbus_framefmt *try_fmt =
+                               v4l2_subdev_get_try_format(sd, fh->state, 0);
+       const struct ov9281_mode *def_mode = &supported_modes[0];
+
+       mutex_lock(&ov9281->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(&ov9281->mutex);
+       /* No crop or compose */
+
+       return 0;
+}
+
+static const struct dev_pm_ops ov9281_pm_ops = {
+       SET_RUNTIME_PM_OPS(ov9281_runtime_suspend,
+                          ov9281_runtime_resume, NULL)
+};
+
+static const struct v4l2_subdev_internal_ops ov9281_internal_ops = {
+       .open = ov9281_open,
+};
+
+static const struct v4l2_subdev_core_ops ov9281_core_ops = {
+       .s_power = ov9281_s_power,
+};
+
+static const struct v4l2_subdev_video_ops ov9281_video_ops = {
+       .s_stream = ov9281_s_stream,
+};
+
+static const struct v4l2_subdev_pad_ops ov9281_pad_ops = {
+       .enum_mbus_code = ov9281_enum_mbus_code,
+       .enum_frame_size = ov9281_enum_frame_sizes,
+       .get_fmt = ov9281_get_fmt,
+       .set_fmt = ov9281_set_fmt,
+       .get_selection = ov9281_get_selection,
+};
+
+static const struct v4l2_subdev_ops ov9281_subdev_ops = {
+       .core   = &ov9281_core_ops,
+       .video  = &ov9281_video_ops,
+       .pad    = &ov9281_pad_ops,
+};
+
+static int ov9281_set_ctrl(struct v4l2_ctrl *ctrl)
+{
+       struct ov9281 *ov9281 = container_of(ctrl->handler,
+                                            struct ov9281, ctrl_handler);
+       struct i2c_client *client = ov9281->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 = ov9281->cur_mode->height + ctrl->val - OV9281_EXPOSURE_OFFSET;
+               __v4l2_ctrl_modify_range(ov9281->exposure,
+                                        ov9281->exposure->minimum, max,
+                                        ov9281->exposure->step,
+                                        ov9281->cur_mode->vts_def);
+               break;
+       }
+
+       if (pm_runtime_get(&client->dev) <= 0)
+               return 0;
+
+       switch (ctrl->id) {
+       case V4L2_CID_HFLIP:
+               ret = ov9281_set_ctrl_hflip(ov9281, ctrl->val);
+               break;
+       case V4L2_CID_VFLIP:
+               ret = ov9281_set_ctrl_vflip(ov9281, ctrl->val);
+               break;
+       case V4L2_CID_EXPOSURE:
+               /* 4 least significant bits of expsoure are fractional part */
+               ret = ov9281_write_reg(ov9281->client, OV9281_REG_EXPOSURE,
+                                      OV9281_REG_VALUE_24BIT, ctrl->val << 4);
+               break;
+       case V4L2_CID_ANALOGUE_GAIN:
+               ret = ov9281_write_reg(ov9281->client, OV9281_REG_GAIN_H,
+                                      OV9281_REG_VALUE_08BIT,
+                                      (ctrl->val >> OV9281_GAIN_H_SHIFT) &
+                                                       OV9281_GAIN_H_MASK);
+               ret |= ov9281_write_reg(ov9281->client, OV9281_REG_GAIN_L,
+                                      OV9281_REG_VALUE_08BIT,
+                                      ctrl->val & OV9281_GAIN_L_MASK);
+               break;
+       case V4L2_CID_VBLANK:
+               ret = ov9281_write_reg(ov9281->client, OV9281_REG_VTS,
+                                      OV9281_REG_VALUE_16BIT,
+                                      ctrl->val + ov9281->cur_mode->height);
+               break;
+       case V4L2_CID_TEST_PATTERN:
+               ret = ov9281_enable_test_pattern(ov9281, ctrl->val);
+               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 ov9281_ctrl_ops = {
+       .s_ctrl = ov9281_set_ctrl,
+};
+
+static int ov9281_initialize_controls(struct ov9281 *ov9281)
+{
+       struct v4l2_fwnode_device_properties props;
+       const struct ov9281_mode *mode;
+       struct v4l2_ctrl_handler *handler;
+       struct v4l2_ctrl *ctrl;
+       s64 exposure_max, vblank_def;
+       u32 h_blank;
+       int ret;
+
+       handler = &ov9281->ctrl_handler;
+       mode = ov9281->cur_mode;
+       ret = v4l2_ctrl_handler_init(handler, 11);
+       if (ret)
+               return ret;
+       handler->lock = &ov9281->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;
+
+       ov9281->pixel_rate = v4l2_ctrl_new_std(handler, NULL,
+                                              V4L2_CID_PIXEL_RATE,
+                                              OV9281_PIXEL_RATE_10BIT,
+                                              OV9281_PIXEL_RATE_10BIT, 1,
+                                              OV9281_PIXEL_RATE_10BIT);
+
+       h_blank = mode->hts_def - mode->width;
+       ov9281->hblank = v4l2_ctrl_new_std(handler, NULL, V4L2_CID_HBLANK,
+                                          h_blank, h_blank, 1, h_blank);
+       if (ov9281->hblank)
+               ov9281->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+       vblank_def = mode->vts_def - mode->height;
+       ov9281->vblank = v4l2_ctrl_new_std(handler, &ov9281_ctrl_ops,
+                                          V4L2_CID_VBLANK, vblank_def,
+                                          OV9281_VTS_MAX - mode->height, 1,
+                                          vblank_def);
+
+       exposure_max = mode->vts_def - OV9281_EXPOSURE_OFFSET;
+       ov9281->exposure = v4l2_ctrl_new_std(handler, &ov9281_ctrl_ops,
+                                            V4L2_CID_EXPOSURE,
+                                            OV9281_EXPOSURE_MIN, exposure_max,
+                                            OV9281_EXPOSURE_STEP,
+                                            mode->exp_def);
+
+       ov9281->anal_gain = v4l2_ctrl_new_std(handler, &ov9281_ctrl_ops,
+                                             V4L2_CID_ANALOGUE_GAIN,
+                                             OV9281_GAIN_MIN, OV9281_GAIN_MAX,
+                                             OV9281_GAIN_STEP,
+                                             OV9281_GAIN_DEFAULT);
+
+       ov9281->vflip = v4l2_ctrl_new_std(handler, &ov9281_ctrl_ops,
+                                         V4L2_CID_VFLIP,
+                                               0, 1, 1, 0);
+
+       ov9281->hflip = v4l2_ctrl_new_std(handler, &ov9281_ctrl_ops,
+                                         V4L2_CID_HFLIP,
+                                               0, 1, 1, 0);
+
+       ov9281->test_pattern =
+               v4l2_ctrl_new_std_menu_items(handler, &ov9281_ctrl_ops,
+                                            V4L2_CID_TEST_PATTERN,
+                                            ARRAY_SIZE(ov9281_test_pattern_menu) - 1,
+                                            0, 0, ov9281_test_pattern_menu);
+
+       if (handler->error) {
+               ret = handler->error;
+               dev_err(&ov9281->client->dev,
+                       "Failed to init controls(%d)\n", ret);
+               goto err_free_handler;
+       }
+
+       ret = v4l2_fwnode_device_parse(&ov9281->client->dev, &props);
+       if (ret)
+               goto err_free_handler;
+
+       ret = v4l2_ctrl_new_fwnode_properties(handler, &ov9281_ctrl_ops,
+                                             &props);
+       if (ret)
+               goto err_free_handler;
+
+       ov9281->subdev.ctrl_handler = handler;
+
+       return 0;
+
+err_free_handler:
+       v4l2_ctrl_handler_free(handler);
+
+       return ret;
+}
+
+static int ov9281_check_sensor_id(struct ov9281 *ov9281,
+                                 struct i2c_client *client)
+{
+       struct device *dev = &ov9281->client->dev;
+       u32 id = 0, id_msb = 0;
+       int ret;
+
+       ret = ov9281_read_reg(client, OV9281_REG_CHIP_ID + 1,
+                             OV9281_REG_VALUE_08BIT, &id);
+       if (!ret)
+               ret = ov9281_read_reg(client, OV9281_REG_CHIP_ID,
+                                     OV9281_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 ov9281_configure_regulators(struct ov9281 *ov9281)
+{
+       unsigned int i;
+
+       for (i = 0; i < OV9281_NUM_SUPPLIES; i++)
+               ov9281->supplies[i].supply = ov9281_supply_names[i];
+
+       return devm_regulator_bulk_get(&ov9281->client->dev,
+                                      OV9281_NUM_SUPPLIES,
+                                      ov9281->supplies);
+}
+
+static int ov9281_probe(struct i2c_client *client,
+                       const struct i2c_device_id *id)
+{
+       struct device *dev = &client->dev;
+       struct ov9281 *ov9281;
+       struct v4l2_subdev *sd;
+       int ret;
+
+       ov9281 = devm_kzalloc(dev, sizeof(*ov9281), GFP_KERNEL);
+       if (!ov9281)
+               return -ENOMEM;
+
+       ov9281->client = client;
+       ov9281->cur_mode = &supported_modes[0];
+
+       ov9281->xvclk = devm_clk_get(dev, "xvclk");
+       if (IS_ERR(ov9281->xvclk)) {
+               dev_err(dev, "Failed to get xvclk\n");
+               return -EINVAL;
+       }
+
+       ov9281->reset_gpio = devm_gpiod_get_optional(dev, "reset",
+                                                    GPIOD_OUT_LOW);
+       if (IS_ERR(ov9281->reset_gpio))
+               dev_warn(dev, "Failed to get reset-gpios\n");
+
+       ov9281->pwdn_gpio = devm_gpiod_get_optional(dev, "pwdn", GPIOD_OUT_LOW);
+       if (IS_ERR(ov9281->pwdn_gpio))
+               dev_warn(dev, "Failed to get pwdn-gpios\n");
+
+       ret = ov9281_configure_regulators(ov9281);
+       if (ret) {
+               dev_err(dev, "Failed to get power regulators\n");
+               return ret;
+       }
+
+       mutex_init(&ov9281->mutex);
+
+       sd = &ov9281->subdev;
+       v4l2_i2c_subdev_init(sd, client, &ov9281_subdev_ops);
+       ret = ov9281_initialize_controls(ov9281);
+       if (ret)
+               goto err_destroy_mutex;
+
+       ret = __ov9281_power_on(ov9281);
+       if (ret)
+               goto err_free_handler;
+
+       ret = ov9281_check_sensor_id(ov9281, client);
+       if (ret)
+               goto err_power_off;
+
+       sd->internal_ops = &ov9281_internal_ops;
+       sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+
+       ov9281->pad.flags = MEDIA_PAD_FL_SOURCE;
+       sd->entity.function = MEDIA_ENT_F_CAM_SENSOR;
+       ret = media_entity_pads_init(&sd->entity, 1, &ov9281->pad);
+       if (ret < 0)
+               goto err_power_off;
+
+       ret = v4l2_async_register_subdev_sensor(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:
+       __ov9281_power_off(ov9281);
+err_free_handler:
+       v4l2_ctrl_handler_free(&ov9281->ctrl_handler);
+err_destroy_mutex:
+       mutex_destroy(&ov9281->mutex);
+
+       return ret;
+}
+
+static int ov9281_remove(struct i2c_client *client)
+{
+       struct v4l2_subdev *sd = i2c_get_clientdata(client);
+       struct ov9281 *ov9281 = to_ov9281(sd);
+
+       v4l2_async_unregister_subdev(sd);
+       media_entity_cleanup(&sd->entity);
+       v4l2_ctrl_handler_free(&ov9281->ctrl_handler);
+       mutex_destroy(&ov9281->mutex);
+
+       pm_runtime_disable(&client->dev);
+       if (!pm_runtime_status_suspended(&client->dev))
+               __ov9281_power_off(ov9281);
+       pm_runtime_set_suspended(&client->dev);
+
+       return 0;
+}
+
+static const struct of_device_id ov9281_of_match[] = {
+       { .compatible = "ovti,ov9281" },
+       {},
+};
+MODULE_DEVICE_TABLE(of, ov9281_of_match);
+
+static const struct i2c_device_id ov9281_match_id[] = {
+       { "ovti,ov9281", 0 },
+       { },
+};
+
+static struct i2c_driver ov9281_i2c_driver = {
+       .driver = {
+               .name = OV9281_NAME,
+               .pm = &ov9281_pm_ops,
+               .of_match_table = of_match_ptr(ov9281_of_match),
+       },
+       .probe          = &ov9281_probe,
+       .remove         = &ov9281_remove,
+       .id_table       = ov9281_match_id,
+};
+
+static int __init sensor_mod_init(void)
+{
+       return i2c_add_driver(&ov9281_i2c_driver);
+}
+
+static void __exit sensor_mod_exit(void)
+{
+       i2c_del_driver(&ov9281_i2c_driver);
+}
+
+device_initcall_sync(sensor_mod_init);
+module_exit(sensor_mod_exit);
+
+MODULE_DESCRIPTION("OmniVision ov9281 sensor driver");
+MODULE_LICENSE("GPL v2");
index 3205cd8..e3de5b2 100644 (file)
@@ -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\n",
                                __func__, reg, client->addr);
        }
+       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);
@@ -1609,11 +1626,20 @@ static int tc358743_get_mbus_config(struct v4l2_subdev *sd,
                                    struct v4l2_mbus_config *cfg)
 {
        struct tc358743_state *state = to_state(sd);
+       const u32 mask = V4L2_MBUS_CSI2_LANE_MASK;
+
+       if (state->csi_lanes_in_use > state->bus.num_data_lanes)
+               return -EINVAL;
 
        cfg->type = V4L2_MBUS_CSI2_DPHY;
+       cfg->flags = (state->csi_lanes_in_use << __ffs(mask)) & mask;
+
+       /* In DT mode, only report the number of active lanes */
+       if (sd->dev->of_node)
+               return 0;
 
-       /* Support for non-continuous CSI-2 clock is missing in the driver */
-       cfg->flags = V4L2_MBUS_CSI2_CONTINUOUS_CLOCK;
+       /* Support for non-continuous CSI-2 clock is missing in pdate mode */
+       cfg->flags |= V4L2_MBUS_CSI2_CONTINUOUS_CLOCK;
 
        switch (state->csi_lanes_in_use) {
        case 1:
@@ -1665,12 +1691,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;
@@ -1680,23 +1717,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;
 }
@@ -1710,19 +1731,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;
 
@@ -1950,7 +1966,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.
@@ -1970,6 +1986,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) {
@@ -1983,23 +2000,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);
@@ -2038,6 +2074,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))
@@ -2056,6 +2093,7 @@ static int tc358743_probe(struct i2c_client *client)
        if (pdata) {
                state->pdata = *pdata;
                state->bus.flags = V4L2_MBUS_CSI2_CONTINUOUS_CLOCK;
+               state->bus.num_data_lanes = 4;
        } else {
                err = tc358743_probe_of(state);
                if (err == -ENODEV)
@@ -2069,7 +2107,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;
index f40f419..7bf2c73 100644 (file)
@@ -60,6 +60,20 @@ static inline const char *intf_type(struct media_interface *intf)
        }
 };
 
+static inline const char *link_type_name(struct media_link *link)
+{
+       switch (link->flags & MEDIA_LNK_FL_LINK_TYPE) {
+       case MEDIA_LNK_FL_DATA_LINK:
+               return "data";
+       case MEDIA_LNK_FL_INTERFACE_LINK:
+               return "interface";
+       case MEDIA_LNK_FL_ANCILLARY_LINK:
+               return "ancillary";
+       default:
+               return "unknown";
+       }
+}
+
 __must_check int __media_entity_enum_init(struct media_entity_enum *ent_enum,
                                          int idx_max)
 {
@@ -107,9 +121,7 @@ static void dev_dbg_obj(const char *event_name,  struct media_gobj *gobj)
 
                dev_dbg(gobj->mdev->dev,
                        "%s id %u: %s link id %u ==> id %u\n",
-                       event_name, media_id(gobj),
-                       media_type(link->gobj0) == MEDIA_GRAPH_PAD ?
-                               "data" : "interface",
+                       event_name, media_id(gobj), link_type_name(link),
                        media_id(link->gobj0),
                        media_id(link->gobj1));
                break;
@@ -313,6 +325,12 @@ static void media_graph_walk_iter(struct media_graph *graph)
 
        link = list_entry(link_top(graph), typeof(*link), list);
 
+       /* If the link is not a data link, don't follow it */
+       if ((link->flags & MEDIA_LNK_FL_LINK_TYPE) != MEDIA_LNK_FL_DATA_LINK) {
+               link_top(graph) = link_top(graph)->next;
+               return;
+       }
+
        /* The link is not enabled so we do not follow. */
        if (!(link->flags & MEDIA_LNK_FL_ENABLED)) {
                link_top(graph) = link_top(graph)->next;
@@ -1032,3 +1050,25 @@ void media_remove_intf_links(struct media_interface *intf)
        mutex_unlock(&mdev->graph_mutex);
 }
 EXPORT_SYMBOL_GPL(media_remove_intf_links);
+
+struct media_link *media_create_ancillary_link(struct media_entity *primary,
+                                              struct media_entity *ancillary)
+{
+       struct media_link *link;
+
+       link = media_add_link(&primary->links);
+       if (!link)
+               return ERR_PTR(-ENOMEM);
+
+       link->gobj0 = &primary->graph_obj;
+       link->gobj1 = &ancillary->graph_obj;
+       link->flags = MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED |
+                     MEDIA_LNK_FL_ANCILLARY_LINK;
+
+       /* Initialize graph object embedded in the new link */
+       media_gobj_create(primary->graph_obj.mdev, MEDIA_GRAPH_LINK,
+                         &link->graph_obj);
+
+       return link;
+}
+EXPORT_SYMBOL_GPL(media_create_ancillary_link);
index addb8f2..ee5b77a 100644 (file)
@@ -505,3 +505,38 @@ unlock:
                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);
index 80321e0..9263d3d 100644 (file)
@@ -170,6 +170,7 @@ source "drivers/media/platform/am437x/Kconfig"
 source "drivers/media/platform/xilinx/Kconfig"
 source "drivers/media/platform/rcar-vin/Kconfig"
 source "drivers/media/platform/atmel/Kconfig"
+source "drivers/media/platform/bcm2835/Kconfig"
 source "drivers/media/platform/sunxi/Kconfig"
 
 config VIDEO_TI_CAL
index 73ce083..9d52476 100644 (file)
@@ -83,6 +83,8 @@ obj-$(CONFIG_VIDEO_QCOM_CAMSS)                += qcom/camss/
 
 obj-$(CONFIG_VIDEO_QCOM_VENUS)         += qcom/venus/
 
+obj-y                                  += bcm2835/
+
 obj-y                                  += sunxi/
 
 obj-$(CONFIG_VIDEO_MESON_GE2D)         += meson/ge2d/
diff --git a/drivers/media/platform/bcm2835/Kconfig b/drivers/media/platform/bcm2835/Kconfig
new file mode 100644 (file)
index 0000000..bd13701
--- /dev/null
@@ -0,0 +1,21 @@
+# Broadcom VideoCore4 V4L2 camera support
+
+config VIDEO_BCM2835_UNICAM
+       tristate "Broadcom BCM283x/BCM271x Unicam video capture driver"
+       depends on VIDEO_V4L2
+       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 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 (file)
index 0000000..a98aba0
--- /dev/null
@@ -0,0 +1,3 @@
+# Makefile for BCM2835 Unicam driver
+
+obj-$(CONFIG_VIDEO_BCM2835_UNICAM) += bcm2835-unicam.o
diff --git a/drivers/media/platform/bcm2835/bcm2835-unicam.c b/drivers/media/platform/bcm2835/bcm2835-unicam.c
new file mode 100644 (file)
index 0000000..cad7f01
--- /dev/null
@@ -0,0 +1,3470 @@
+// 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/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/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>
+#define v4l2_async_notifier_add_subdev __v4l2_async_notifier_add_subdev
+
+#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. Can be any size really, but the DMA
+ * allocation works in units of page sizes.
+ */
+#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         = 0x1e,
+               .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         = 0x1e,
+               .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         = 0x1e,
+               .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         = 0x1e,
+               .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         = 0x1e,
+               .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         = 0x1e,
+               .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         = 0x1e,
+               .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         = 0x1e,
+               .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         = 0x22,
+               .valid_colorspaces = MASK_CS_SRGB,
+       }, {
+               .fourcc         = V4L2_PIX_FMT_RGB565X, /* rrrrrggg gggbbbbb */
+               .code           = MEDIA_BUS_FMT_RGB565_2X8_BE,
+               .depth          = 16,
+               .csi_dt         = 0x22,
+               .valid_colorspaces = MASK_CS_SRGB,
+       }, {
+               .fourcc         = V4L2_PIX_FMT_RGB555, /* gggbbbbb arrrrrgg */
+               .code           = MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE,
+               .depth          = 16,
+               .csi_dt         = 0x21,
+               .valid_colorspaces = MASK_CS_SRGB,
+       }, {
+               .fourcc         = V4L2_PIX_FMT_RGB555X, /* arrrrrgg gggbbbbb */
+               .code           = MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE,
+               .depth          = 16,
+               .csi_dt         = 0x21,
+               .valid_colorspaces = MASK_CS_SRGB,
+       }, {
+               .fourcc         = V4L2_PIX_FMT_RGB24, /* rgb */
+               .code           = MEDIA_BUS_FMT_RGB888_1X24,
+               .depth          = 24,
+               .csi_dt         = 0x24,
+               .valid_colorspaces = MASK_CS_SRGB,
+       }, {
+               .fourcc         = V4L2_PIX_FMT_BGR24, /* bgr */
+               .code           = MEDIA_BUS_FMT_BGR888_1X24,
+               .depth          = 24,
+               .csi_dt         = 0x24,
+               .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         = 0x2a,
+               .valid_colorspaces = MASK_CS_RAW,
+       }, {
+               .fourcc         = V4L2_PIX_FMT_SGBRG8,
+               .code           = MEDIA_BUS_FMT_SGBRG8_1X8,
+               .depth          = 8,
+               .csi_dt         = 0x2a,
+               .valid_colorspaces = MASK_CS_RAW,
+       }, {
+               .fourcc         = V4L2_PIX_FMT_SGRBG8,
+               .code           = MEDIA_BUS_FMT_SGRBG8_1X8,
+               .depth          = 8,
+               .csi_dt         = 0x2a,
+               .valid_colorspaces = MASK_CS_RAW,
+       }, {
+               .fourcc         = V4L2_PIX_FMT_SRGGB8,
+               .code           = MEDIA_BUS_FMT_SRGGB8_1X8,
+               .depth          = 8,
+               .csi_dt         = 0x2a,
+               .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         = 0x2b,
+               .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         = 0x2b,
+               .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         = 0x2b,
+               .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         = 0x2b,
+               .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         = 0x2c,
+               .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         = 0x2c,
+               .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         = 0x2c,
+               .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         = 0x2c,
+               .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         = 0x2d,
+               .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         = 0x2d,
+               .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         = 0x2d,
+               .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         = 0x2d,
+               .valid_colorspaces = MASK_CS_RAW,
+       }, {
+       /*
+        * 16 bit Bayer formats could be supported, but there is no CSI2
+        * data_type defined for raw 16, and no sensors that produce it at
+        * present.
+        */
+
+       /* Greyscale formats */
+               .fourcc         = V4L2_PIX_FMT_GREY,
+               .code           = MEDIA_BUS_FMT_Y8_1X8,
+               .depth          = 8,
+               .csi_dt         = 0x2a,
+               .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         = 0x2b,
+               .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         = 0x2c,
+               .valid_colorspaces = MASK_CS_RAW,
+       }, {
+               .fourcc         = V4L2_PIX_FMT_Y14P,
+               .repacked_fourcc = V4L2_PIX_FMT_Y14,
+               .code           = MEDIA_BUS_FMT_Y14_1X14,
+               .depth          = 14,
+               .csi_dt         = 0x2d,
+               .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_subdev 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;
+
+       /* parent device */
+       struct platform_device *pdev;
+       /* subdevice async Notifier */
+       struct v4l2_async_notifier notifier;
+       unsigned int sequence;
+
+       /* 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, DUMMY_BUF_SIZE,
+                          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) {
+               /*
+                * 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;
+                       } else {
+                               node->cur_frm = node->next_frm;
+                       }
+               }
+               unicam->sequence++;
+       }
+
+       if (ista & UNICAM_FSI) {
+               /*
+                * Timestamp is to be when the first data byte was captured,
+                * aka frame start.
+                */
+               ts = ktime_get_ns();
+               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);
+       }
+
+       /*
+        * 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, video, 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, &current_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, video, g_dv_timings, 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, video, g_dv_timings,
+                              &current_timings);
+
+       if (ret < 0)
+               return ret;
+
+       if (v4l2_match_dv_timings(timings, &current_timings, 0, false))
+               return 0;
+
+       if (vb2_is_busy(&node->buffer_queue))
+               return -EBUSY;
+
+       ret = v4l2_subdev_call(dev->sensor, video, s_dv_timings, 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, video, query_dv_timings, 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_pad(link->sink->entity->pads)) {
+               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 (vq->num_buffers + *nbuffers < 3)
+               *nbuffers = 3 - vq->num_buffers;
+
+       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_CONTINUOUS_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_CONTINUOUS_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(&node->video_dev.entity, &node->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.flags & V4L2_MBUS_CSI2_LANE_MASK) >>
+                                       __ffs(V4L2_MBUS_CSI2_LANE_MASK);
+               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);
+       }
+
+       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:
+       media_pipeline_stop(&node->video_dev.entity);
+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);
+
+               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,
+                                  DUMMY_BUF_SIZE, 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_subdev *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_free_state(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_buffers_needed = 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, video, 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, video,
+                                       g_frame_interval))
+                       v4l2_disable_ioctl(&node->video_dev, VIDIOC_G_PARM);
+               if (pad_id == METADATA_PAD ||
+                   !v4l2_subdev_has_op(unicam->sensor, video,
+                                       s_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)
+{
+       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_alloc_state(unicam->sensor);
+       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_notifier_init(&dev->notifier);
+       dev->notifier.ops = &unicam_async_ops;
+
+       dev->asd.match_type = V4L2_ASYNC_MATCH_FWNODE;
+       dev->asd.match.fwnode = fwnode_graph_get_remote_endpoint(of_fwnode_handle(ep_node));
+       ret = v4l2_async_notifier_add_subdev(&dev->notifier, &dev->asd);
+       if (ret) {
+               unicam_err(dev, "Error adding subdevice: %d\n", ret);
+               goto cleanup_exit;
+       }
+
+       ret = v4l2_async_notifier_register(&dev->v4l2_dev, &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;
+       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;
+       }
+
+       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 int unicam_remove(struct platform_device *pdev)
+{
+       struct unicam_device *unicam = platform_get_drvdata(pdev);
+
+       unicam_dbg(2, unicam, "%s\n", __func__);
+
+       v4l2_async_notifier_unregister(&unicam->notifier);
+       v4l2_device_unregister(&unicam->v4l2_dev);
+       media_device_unregister(&unicam->mdev);
+       unregister_nodes(unicam);
+
+       pm_runtime_disable(&pdev->dev);
+
+       return 0;
+}
+
+static const struct of_device_id unicam_of_match[] = {
+       { .compatible = "brcm,bcm2835-unicam", },
+       { /* 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 (file)
index 0000000..ae059a1
--- /dev/null
@@ -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
index d596bc0..6ef44cf 100644 (file)
@@ -214,6 +214,12 @@ rkisp1_isp_get_pad_fmt(struct rkisp1_isp *isp,
        struct v4l2_subdev_state state = {
                .pads = isp->pad_cfg
                };
+       struct v4l2_subdev_state state = {
+               .pads = &state
+               };
+       struct v4l2_subdev_state state = {
+               .pads = &state
+               };
        if (which == V4L2_SUBDEV_FORMAT_TRY)
                return v4l2_subdev_get_try_format(&isp->sd, sd_state, pad);
        else
@@ -228,6 +234,12 @@ rkisp1_isp_get_pad_crop(struct rkisp1_isp *isp,
        struct v4l2_subdev_state state = {
                .pads = isp->pad_cfg
                };
+       struct v4l2_subdev_state state = {
+               .pads = &state
+               };
+       struct v4l2_subdev_state state = {
+               .pads = &state
+               };
        if (which == V4L2_SUBDEV_FORMAT_TRY)
                return v4l2_subdev_get_try_crop(&isp->sd, sd_state, pad);
        else
@@ -1053,6 +1065,12 @@ int rkisp1_isp_register(struct rkisp1_device *rkisp1)
        struct v4l2_subdev_state state = {
                .pads = rkisp1->isp.pad_cfg
                };
+       struct v4l2_subdev_state state = {
+               .pads = &state
+               };
+       struct v4l2_subdev_state state = {
+               .pads = &state
+               };
        struct rkisp1_isp *isp = &rkisp1->isp;
        struct media_pad *pads = isp->pads;
        struct v4l2_subdev *sd = &isp->sd;
index 2070f4b..1b9f62c 100644 (file)
@@ -186,6 +186,12 @@ rkisp1_rsz_get_pad_fmt(struct rkisp1_resizer *rsz,
        struct v4l2_subdev_state state = {
                .pads = rsz->pad_cfg
                };
+       struct v4l2_subdev_state state = {
+               .pads = &state
+               };
+       struct v4l2_subdev_state state = {
+               .pads = &state
+               };
        if (which == V4L2_SUBDEV_FORMAT_TRY)
                return v4l2_subdev_get_try_format(&rsz->sd, sd_state, pad);
        else
@@ -200,6 +206,12 @@ rkisp1_rsz_get_pad_crop(struct rkisp1_resizer *rsz,
        struct v4l2_subdev_state state = {
                .pads = rsz->pad_cfg
                };
+       struct v4l2_subdev_state state = {
+               .pads = &state
+               };
+       struct v4l2_subdev_state state = {
+               .pads = &state
+               };
        if (which == V4L2_SUBDEV_FORMAT_TRY)
                return v4l2_subdev_get_try_crop(&rsz->sd, sd_state, pad);
        else
@@ -791,6 +803,12 @@ static int rkisp1_rsz_register(struct rkisp1_resizer *rsz)
        struct v4l2_subdev_state state = {
                .pads = rsz->pad_cfg
                };
+       struct v4l2_subdev_state state = {
+               .pads = &state
+               };
+       struct v4l2_subdev_state state = {
+               .pads = &state
+               };
        static const char * const dev_names[] = {
                RKISP1_RSZ_MP_DEV_NAME,
                RKISP1_RSZ_SP_DEV_NAME
index 905005e..dd03e62 100644 (file)
 #include <media/v4l2-mc.h>
 #include <media/v4l2-subdev.h>
 
+struct video_mux_asd {
+       struct v4l2_async_subdev base;
+       unsigned int port;
+};
+
+static inline struct video_mux_asd *to_video_mux_asd(struct v4l2_async_subdev *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 v4l2_mbus_framefmt *format_mbus;
+       struct video_mux_pad_cfg *cfg;
        struct mux_control *mux;
        struct mutex lock;
        int active;
@@ -330,10 +347,37 @@ static int video_mux_init_cfg(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);
+       const u32 mask = V4L2_MBUS_CSI2_LANE_MASK;
+       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->flags = (vmux->cfg[vmux->active].num_lanes << __ffs(mask)) & mask;
+
+       /* Support for non-continuous CSI-2 clock is missing in pdate mode */
+       if (vmux->cfg[vmux->active].non_continuous)
+               cfg->flags |= V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK;
+       else
+               cfg->flags |= V4L2_MBUS_CSI2_CONTINUOUS_CLOCK;
+
+       return 0;
+};
+
 static const struct v4l2_subdev_pad_ops video_mux_pad_ops = {
        .init_cfg = video_mux_init_cfg,
        .get_fmt = video_mux_get_format,
        .set_fmt = video_mux_set_format,
+       .get_mbus_config = video_mux_get_mbus_config,
 };
 
 static const struct v4l2_subdev_ops video_mux_subdev_ops = {
@@ -346,6 +390,9 @@ static int video_mux_notify_bound(struct v4l2_async_notifier *notifier,
                                  struct v4l2_async_subdev *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);
 }
@@ -363,7 +410,7 @@ static int video_mux_async_register(struct video_mux *vmux,
        v4l2_async_notifier_init(&vmux->notifier);
 
        for (i = 0; i < num_input_pads; i++) {
-               struct v4l2_async_subdev *asd;
+               struct video_mux_asd *asd;
                struct fwnode_handle *ep, *remote_ep;
 
                ep = fwnode_graph_get_endpoint_by_id(
@@ -381,7 +428,8 @@ static int video_mux_async_register(struct video_mux *vmux,
                fwnode_handle_put(remote_ep);
 
                asd = v4l2_async_notifier_add_fwnode_remote_subdev(
-                       &vmux->notifier, ep, struct v4l2_async_subdev);
+                       &vmux->notifier, ep, struct video_mux_asd);
+               asd->port = i;
 
                fwnode_handle_put(ep);
 
@@ -407,6 +455,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;
@@ -461,10 +512,26 @@ static int video_mux_probe(struct platform_device *pdev)
        if (!vmux->format_mbus)
                return -ENOMEM;
 
+       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;
                vmux->format_mbus[i] = video_mux_format_mbus_default;
+
+               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;
index 857ef4a..deae75e 100644 (file)
@@ -25,6 +25,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.
index 795a012..adb1028 100644 (file)
@@ -1944,6 +1944,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,
index cd9e78c..84a1278 100644 (file)
@@ -275,6 +275,24 @@ v4l2_async_notifier_try_complete(struct v4l2_async_notifier *notifier)
 static int
 v4l2_async_notifier_try_all_subdevs(struct v4l2_async_notifier *notifier);
 
+static int v4l2_async_create_ancillary_links(struct v4l2_async_notifier *n,
+                                            struct v4l2_subdev *sd)
+{
+       struct media_link *link = NULL;
+
+#if IS_ENABLED(CONFIG_MEDIA_CONTROLLER)
+
+       if (sd->entity.function != MEDIA_ENT_F_LENS &&
+           sd->entity.function != MEDIA_ENT_F_FLASH)
+               return 0;
+
+       link = media_create_ancillary_link(&n->sd->entity, &sd->entity);
+
+#endif
+
+       return IS_ERR(link) ? PTR_ERR(link) : 0;
+}
+
 static int v4l2_async_match_notify(struct v4l2_async_notifier *notifier,
                                   struct v4l2_device *v4l2_dev,
                                   struct v4l2_subdev *sd,
@@ -293,6 +311,19 @@ static int v4l2_async_match_notify(struct v4l2_async_notifier *notifier,
                return ret;
        }
 
+       /*
+        * Depending of the function of the entities involved, we may want to
+        * create links between them (for example between a sensor and its lens
+        * or between a sensor's source pad and the connected device's sink
+        * pad).
+        */
+       ret = v4l2_async_create_ancillary_links(notifier, sd);
+       if (ret) {
+               v4l2_async_notifier_call_unbind(notifier, sd, asd);
+               v4l2_device_unregister_subdev(sd);
+               return ret;
+       }
+
        /* Remove from the waiting list */
        list_del(&asd->list);
        sd->asd = asd;
index db9baa0..50d012b 100644 (file)
@@ -97,29 +97,47 @@ static int def_to_user(struct v4l2_ext_control *c, struct v4l2_ctrl *ctrl)
        return ptr_to_user(c, ctrl, ctrl->p_new);
 }
 
-/* Helper function: copy the caller-provider value to the given control value */
-static int user_to_ptr(struct v4l2_ext_control *c,
-                      struct v4l2_ctrl *ctrl,
-                      union v4l2_ctrl_ptr ptr)
+/* Helper function: copy the caller-provider value as the new control value */
+static int user_to_new(struct v4l2_ext_control *c, struct v4l2_ctrl *ctrl)
 {
        int ret;
        u32 size;
 
-       ctrl->is_new = 1;
+       ctrl->is_new = 0;
+       if (ctrl->is_dyn_array &&
+           c->size > ctrl->p_dyn_alloc_elems * ctrl->elem_size) {
+               void *old = ctrl->p_dyn;
+               void *tmp = kvzalloc(2 * c->size, GFP_KERNEL);
+
+               if (!tmp)
+                       return -ENOMEM;
+               memcpy(tmp, ctrl->p_new.p, ctrl->elems * ctrl->elem_size);
+               memcpy(tmp + c->size, ctrl->p_cur.p, ctrl->elems * ctrl->elem_size);
+               ctrl->p_new.p = tmp;
+               ctrl->p_cur.p = tmp + c->size;
+               ctrl->p_dyn = tmp;
+               ctrl->p_dyn_alloc_elems = c->size / ctrl->elem_size;
+               kvfree(old);
+       }
+
        if (ctrl->is_ptr && !ctrl->is_string) {
+               unsigned int elems = c->size / ctrl->elem_size;
                unsigned int idx;
 
-               ret = copy_from_user(ptr.p, c->ptr, c->size) ? -EFAULT : 0;
-               if (ret || !ctrl->is_array)
-                       return ret;
-               for (idx = c->size / ctrl->elem_size; idx < ctrl->elems; idx++)
-                       ctrl->type_ops->init(ctrl, idx, ptr);
+               if (copy_from_user(ctrl->p_new.p, c->ptr, c->size))
+                       return -EFAULT;
+               ctrl->is_new = 1;
+               if (ctrl->is_dyn_array)
+                       ctrl->new_elems = elems;
+               else if (ctrl->is_array)
+                       for (idx = elems; idx < ctrl->elems; idx++)
+                               ctrl->type_ops->init(ctrl, idx, ctrl->p_new);
                return 0;
        }
 
        switch (ctrl->type) {
        case V4L2_CTRL_TYPE_INTEGER64:
-               *ptr.p_s64 = c->value64;
+               *ctrl->p_new.p_s64 = c->value64;
                break;
        case V4L2_CTRL_TYPE_STRING:
                size = c->size;
@@ -127,32 +145,27 @@ static int user_to_ptr(struct v4l2_ext_control *c,
                        return -ERANGE;
                if (size > ctrl->maximum + 1)
                        size = ctrl->maximum + 1;
-               ret = copy_from_user(ptr.p_char, c->string, size) ? -EFAULT : 0;
+               ret = copy_from_user(ctrl->p_new.p_char, c->string, size) ? -EFAULT : 0;
                if (!ret) {
-                       char last = ptr.p_char[size - 1];
+                       char last = ctrl->p_new.p_char[size - 1];
 
-                       ptr.p_char[size - 1] = 0;
+                       ctrl->p_new.p_char[size - 1] = 0;
                        /*
                         * If the string was longer than ctrl->maximum,
                         * then return an error.
                         */
-                       if (strlen(ptr.p_char) == ctrl->maximum && last)
+                       if (strlen(ctrl->p_new.p_char) == ctrl->maximum && last)
                                return -ERANGE;
                }
                return ret;
        default:
-               *ptr.p_s32 = c->value;
+               *ctrl->p_new.p_s32 = c->value;
                break;
        }
+       ctrl->is_new = 1;
        return 0;
 }
 
-/* Helper function: copy the caller-provider value as the new control value */
-static int user_to_new(struct v4l2_ext_control *c, struct v4l2_ctrl *ctrl)
-{
-       return user_to_ptr(c, ctrl, ctrl->p_new);
-}
-
 /*
  * VIDIOC_G/TRY/S_EXT_CTRLS implementation
  */
@@ -254,7 +267,31 @@ static int prepare_ext_ctrls(struct v4l2_ctrl_handler *hdl,
                        have_clusters = true;
                if (ctrl->cluster[0] != ctrl)
                        ref = find_ref_lock(hdl, ctrl->cluster[0]->id);
-               if (ctrl->is_ptr && !ctrl->is_string) {
+               if (ctrl->is_dyn_array) {
+                       unsigned int max_size = ctrl->dims[0] * ctrl->elem_size;
+                       unsigned int tot_size = ctrl->elem_size;
+
+                       if (cs->which == V4L2_CTRL_WHICH_REQUEST_VAL)
+                               tot_size *= ref->p_req_elems;
+                       else
+                               tot_size *= ctrl->elems;
+
+                       c->size = ctrl->elem_size * (c->size / ctrl->elem_size);
+                       if (get) {
+                               if (c->size < tot_size) {
+                                       c->size = tot_size;
+                                       return -ENOSPC;
+                               }
+                               c->size = tot_size;
+                       } else {
+                               if (c->size > max_size) {
+                                       c->size = max_size;
+                                       return -ENOSPC;
+                               }
+                               if (!c->size)
+                                       return -EFAULT;
+                       }
+               } else if (ctrl->is_ptr && !ctrl->is_string) {
                        unsigned int tot_size = ctrl->elems * ctrl->elem_size;
 
                        if (c->size < tot_size) {
@@ -346,7 +383,7 @@ static int class_check(struct v4l2_ctrl_handler *hdl, u32 which)
  *
  * Note that v4l2_g_ext_ctrls_common() with 'which' set to
  * V4L2_CTRL_WHICH_REQUEST_VAL is only called if the request was
- * completed, and in that case valid_p_req is true for all controls.
+ * completed, and in that case p_req_valid is true for all controls.
  */
 int v4l2_g_ext_ctrls_common(struct v4l2_ctrl_handler *hdl,
                            struct v4l2_ext_controls *cs,
@@ -430,7 +467,9 @@ int v4l2_g_ext_ctrls_common(struct v4l2_ctrl_handler *hdl,
 
                        if (is_default)
                                ret = def_to_user(cs->controls + idx, ref->ctrl);
-                       else if (is_request && ref->valid_p_req)
+                       else if (is_request && ref->p_req_dyn_enomem)
+                               ret = -ENOMEM;
+                       else if (is_request && ref->p_req_valid)
                                ret = req_to_user(cs->controls + idx, ref);
                        else if (is_volatile)
                                ret = new_to_user(cs->controls + idx, ref->ctrl);
@@ -457,6 +496,17 @@ int v4l2_g_ext_ctrls(struct v4l2_ctrl_handler *hdl, struct video_device *vdev,
 }
 EXPORT_SYMBOL(v4l2_g_ext_ctrls);
 
+/* Validate a new control */
+static int validate_new(const struct v4l2_ctrl *ctrl, union v4l2_ctrl_ptr p_new)
+{
+       unsigned int idx;
+       int err = 0;
+
+       for (idx = 0; !err && idx < ctrl->new_elems; idx++)
+               err = ctrl->type_ops->validate(ctrl, idx, p_new);
+       return err;
+}
+
 /* Validate controls. */
 static int validate_ctrls(struct v4l2_ext_controls *cs,
                          struct v4l2_ctrl_helper *helpers,
@@ -872,6 +922,9 @@ int __v4l2_ctrl_s_ctrl_compound(struct v4l2_ctrl *ctrl,
        /* It's a driver bug if this happens. */
        if (WARN_ON(ctrl->type != type))
                return -EINVAL;
+       /* Setting dynamic arrays is not (yet?) supported. */
+       if (WARN_ON(ctrl->is_dyn_array))
+               return -EINVAL;
        memcpy(ctrl->p_new.p, p, ctrl->elems * ctrl->elem_size);
        return set_ctrl(NULL, ctrl, 0);
 }
index 45a76f4..4cc1266 100644 (file)
@@ -627,6 +627,9 @@ static int std_validate_compound(const struct v4l2_ctrl *ctrl, u32 idx,
                zero_padding(*p_hevc_pps);
                break;
 
+       case V4L2_CTRL_TYPE_HEVC_SCALING_MATRIX:
+               break;
+
        case V4L2_CTRL_TYPE_HEVC_DECODE_PARAMS:
                p_hevc_decode_params = p;
 
@@ -806,11 +809,12 @@ EXPORT_SYMBOL(v4l2_ctrl_notify);
 
 /* Copy the one value to another. */
 static void ptr_to_ptr(struct v4l2_ctrl *ctrl,
-                      union v4l2_ctrl_ptr from, union v4l2_ctrl_ptr to)
+                      union v4l2_ctrl_ptr from, union v4l2_ctrl_ptr to,
+                      unsigned int elems)
 {
        if (ctrl == NULL)
                return;
-       memcpy(to.p, from.p_const, ctrl->elems * ctrl->elem_size);
+       memcpy(to.p, from.p_const, elems * ctrl->elem_size);
 }
 
 /* Copy the new value to the current value. */
@@ -823,8 +827,11 @@ void new_to_cur(struct v4l2_fh *fh, struct v4l2_ctrl *ctrl, u32 ch_flags)
 
        /* has_changed is set by cluster_changed */
        changed = ctrl->has_changed;
-       if (changed)
-               ptr_to_ptr(ctrl, ctrl->p_new, ctrl->p_cur);
+       if (changed) {
+               if (ctrl->is_dyn_array)
+                       ctrl->elems = ctrl->new_elems;
+               ptr_to_ptr(ctrl, ctrl->p_new, ctrl->p_cur, ctrl->elems);
+       }
 
        if (ch_flags & V4L2_EVENT_CTRL_CH_FLAGS) {
                /* Note: CH_FLAGS is only set for auto clusters. */
@@ -854,36 +861,122 @@ void cur_to_new(struct v4l2_ctrl *ctrl)
 {
        if (ctrl == NULL)
                return;
-       ptr_to_ptr(ctrl, ctrl->p_cur, ctrl->p_new);
+       if (ctrl->is_dyn_array)
+               ctrl->new_elems = ctrl->elems;
+       ptr_to_ptr(ctrl, ctrl->p_cur, ctrl->p_new, ctrl->new_elems);
+}
+
+static bool req_alloc_dyn_array(struct v4l2_ctrl_ref *ref, u32 elems)
+{
+       void *tmp;
+
+       if (elems < ref->p_req_dyn_alloc_elems)
+               return true;
+
+       tmp = kvmalloc(elems * ref->ctrl->elem_size, GFP_KERNEL);
+
+       if (!tmp) {
+               ref->p_req_dyn_enomem = true;
+               return false;
+       }
+       ref->p_req_dyn_enomem = false;
+       kvfree(ref->p_req.p);
+       ref->p_req.p = tmp;
+       ref->p_req_dyn_alloc_elems = elems;
+       return true;
 }
 
 /* Copy the new value to the request value */
 void new_to_req(struct v4l2_ctrl_ref *ref)
 {
+       struct v4l2_ctrl *ctrl;
+
        if (!ref)
                return;
-       ptr_to_ptr(ref->ctrl, ref->ctrl->p_new, ref->p_req);
-       ref->valid_p_req = true;
+
+       ctrl = ref->ctrl;
+       if (ctrl->is_dyn_array && !req_alloc_dyn_array(ref, ctrl->new_elems))
+               return;
+
+       ref->p_req_elems = ctrl->new_elems;
+       ptr_to_ptr(ctrl, ctrl->p_new, ref->p_req, ref->p_req_elems);
+       ref->p_req_valid = true;
 }
 
 /* Copy the current value to the request value */
 void cur_to_req(struct v4l2_ctrl_ref *ref)
 {
+       struct v4l2_ctrl *ctrl;
+
        if (!ref)
                return;
-       ptr_to_ptr(ref->ctrl, ref->ctrl->p_cur, ref->p_req);
-       ref->valid_p_req = true;
+
+       ctrl = ref->ctrl;
+       if (ctrl->is_dyn_array && !req_alloc_dyn_array(ref, ctrl->elems))
+               return;
+
+       ref->p_req_elems = ctrl->elems;
+       ptr_to_ptr(ctrl, ctrl->p_cur, ref->p_req, ctrl->elems);
+       ref->p_req_valid = true;
 }
 
 /* Copy the request value to the new value */
-void req_to_new(struct v4l2_ctrl_ref *ref)
+int req_to_new(struct v4l2_ctrl_ref *ref)
 {
+       struct v4l2_ctrl *ctrl;
+
        if (!ref)
-               return;
-       if (ref->valid_p_req)
-               ptr_to_ptr(ref->ctrl, ref->p_req, ref->ctrl->p_new);
-       else
-               ptr_to_ptr(ref->ctrl, ref->ctrl->p_cur, ref->ctrl->p_new);
+               return 0;
+
+       ctrl = ref->ctrl;
+
+       /*
+        * This control was never set in the request, so just use the current
+        * value.
+        */
+       if (!ref->p_req_valid) {
+               if (ctrl->is_dyn_array)
+                       ctrl->new_elems = ctrl->elems;
+               ptr_to_ptr(ctrl, ctrl->p_cur, ctrl->p_new, ctrl->new_elems);
+               return 0;
+       }
+
+       /* Not a dynamic array, so just copy the request value */
+       if (!ctrl->is_dyn_array) {
+               ptr_to_ptr(ctrl, ref->p_req, ctrl->p_new, ctrl->new_elems);
+               return 0;
+       }
+
+       /* Sanity check, should never happen */
+       if (WARN_ON(!ref->p_req_dyn_alloc_elems))
+               return -ENOMEM;
+
+       /*
+        * Check if the number of elements in the request is more than the
+        * elements in ctrl->p_dyn. If so, attempt to realloc ctrl->p_dyn.
+        * Note that p_dyn is allocated with twice the number of elements
+        * in the dynamic array since it has to store both the current and
+        * new value of such a control.
+        */
+       if (ref->p_req_elems > ctrl->p_dyn_alloc_elems) {
+               unsigned int sz = ref->p_req_elems * ctrl->elem_size;
+               void *old = ctrl->p_dyn;
+               void *tmp = kvzalloc(2 * sz, GFP_KERNEL);
+
+               if (!tmp)
+                       return -ENOMEM;
+               memcpy(tmp, ctrl->p_new.p, ctrl->elems * ctrl->elem_size);
+               memcpy(tmp + sz, ctrl->p_cur.p, ctrl->elems * ctrl->elem_size);
+               ctrl->p_new.p = tmp;
+               ctrl->p_cur.p = tmp + sz;
+               ctrl->p_dyn = tmp;
+               ctrl->p_dyn_alloc_elems = ref->p_req_elems;
+               kvfree(old);
+       }
+
+       ctrl->new_elems = ref->p_req_elems;
+       ptr_to_ptr(ctrl, ref->p_req, ctrl->p_new, ctrl->new_elems);
+       return 0;
 }
 
 /* Control range checking */
@@ -925,17 +1018,6 @@ int check_range(enum v4l2_ctrl_type type,
        }
 }
 
-/* Validate a new control */
-int validate_new(const struct v4l2_ctrl *ctrl, union v4l2_ctrl_ptr p_new)
-{
-       unsigned idx;
-       int err = 0;
-
-       for (idx = 0; !err && idx < ctrl->elems; idx++)
-               err = ctrl->type_ops->validate(ctrl, idx, p_new);
-       return err;
-}
-
 /* Set the handler's error code if it wasn't set earlier already */
 static inline int handler_set_err(struct v4l2_ctrl_handler *hdl, int err)
 {
@@ -980,6 +1062,8 @@ void v4l2_ctrl_handler_free(struct v4l2_ctrl_handler *hdl)
        /* Free all nodes */
        list_for_each_entry_safe(ref, next_ref, &hdl->ctrl_refs, node) {
                list_del(&ref->node);
+               if (ref->p_req_dyn_alloc_elems)
+                       kvfree(ref->p_req.p);
                kfree(ref);
        }
        /* Free all controls owned by the handler */
@@ -987,6 +1071,7 @@ void v4l2_ctrl_handler_free(struct v4l2_ctrl_handler *hdl)
                list_del(&ctrl->node);
                list_for_each_entry_safe(sev, next_sev, &ctrl->ev_subs, node)
                        list_del(&sev->node);
+               kvfree(ctrl->p_dyn);
                kvfree(ctrl);
        }
        kvfree(hdl->buckets);
@@ -1102,7 +1187,7 @@ int handler_new_ref(struct v4l2_ctrl_handler *hdl,
        if (hdl->error)
                return hdl->error;
 
-       if (allocate_req)
+       if (allocate_req && !ctrl->is_dyn_array)
                size_extra_req = ctrl->elems * ctrl->elem_size;
        new_ref = kzalloc(sizeof(*new_ref) + size_extra_req, GFP_KERNEL);
        if (!new_ref)
@@ -1250,6 +1335,9 @@ static struct v4l2_ctrl *v4l2_ctrl_new(struct v4l2_ctrl_handler *hdl,
        case V4L2_CTRL_TYPE_HEVC_SLICE_PARAMS:
                elem_size = sizeof(struct v4l2_ctrl_hevc_slice_params);
                break;
+       case V4L2_CTRL_TYPE_HEVC_SCALING_MATRIX:
+               elem_size = sizeof(struct v4l2_ctrl_hevc_scaling_matrix);
+               break;
        case V4L2_CTRL_TYPE_HEVC_DECODE_PARAMS:
                elem_size = sizeof(struct v4l2_ctrl_hevc_decode_params);
                break;
@@ -1267,7 +1355,6 @@ static struct v4l2_ctrl *v4l2_ctrl_new(struct v4l2_ctrl_handler *hdl,
                        elem_size = sizeof(s32);
                break;
        }
-       tot_ctrl_size = elem_size * elems;
 
        /* Sanity checks */
        if (id == 0 || name == NULL || !elem_size ||
@@ -1288,17 +1375,33 @@ static struct v4l2_ctrl *v4l2_ctrl_new(struct v4l2_ctrl_handler *hdl,
                handler_set_err(hdl, -EINVAL);
                return NULL;
        }
+       if (flags & V4L2_CTRL_FLAG_DYNAMIC_ARRAY) {
+               /*
+                * For now only support this for one-dimensional arrays only.
+                *
+                * This can be relaxed in the future, but this will
+                * require more effort.
+                */
+               if (nr_of_dims != 1) {
+                       handler_set_err(hdl, -EINVAL);
+                       return NULL;
+               }
+               /* Start with just 1 element */
+               elems = 1;
+       }
 
+       tot_ctrl_size = elem_size * elems;
        sz_extra = 0;
        if (type == V4L2_CTRL_TYPE_BUTTON)
                flags |= V4L2_CTRL_FLAG_WRITE_ONLY |
                        V4L2_CTRL_FLAG_EXECUTE_ON_WRITE;
        else if (type == V4L2_CTRL_TYPE_CTRL_CLASS)
                flags |= V4L2_CTRL_FLAG_READ_ONLY;
-       else if (type == V4L2_CTRL_TYPE_INTEGER64 ||
-                type == V4L2_CTRL_TYPE_STRING ||
-                type >= V4L2_CTRL_COMPOUND_TYPES ||
-                is_array)
+       else if (!(flags & V4L2_CTRL_FLAG_DYNAMIC_ARRAY) &&
+                (type == V4L2_CTRL_TYPE_INTEGER64 ||
+                 type == V4L2_CTRL_TYPE_STRING ||
+                 type >= V4L2_CTRL_COMPOUND_TYPES ||
+                 is_array))
                sz_extra += 2 * tot_ctrl_size;
 
        if (type >= V4L2_CTRL_COMPOUND_TYPES && p_def.p_const)
@@ -1327,7 +1430,9 @@ static struct v4l2_ctrl *v4l2_ctrl_new(struct v4l2_ctrl_handler *hdl,
        ctrl->is_ptr = is_array || type >= V4L2_CTRL_COMPOUND_TYPES || ctrl->is_string;
        ctrl->is_int = !ctrl->is_ptr && type != V4L2_CTRL_TYPE_INTEGER64;
        ctrl->is_array = is_array;
+       ctrl->is_dyn_array = !!(flags & V4L2_CTRL_FLAG_DYNAMIC_ARRAY);
        ctrl->elems = elems;
+       ctrl->new_elems = elems;
        ctrl->nr_of_dims = nr_of_dims;
        if (nr_of_dims)
                memcpy(ctrl->dims, dims, nr_of_dims * sizeof(dims[0]));
@@ -1340,6 +1445,16 @@ static struct v4l2_ctrl *v4l2_ctrl_new(struct v4l2_ctrl_handler *hdl,
        ctrl->cur.val = ctrl->val = def;
        data = &ctrl[1];
 
+       if (ctrl->is_dyn_array) {
+               ctrl->p_dyn_alloc_elems = elems;
+               ctrl->p_dyn = kvzalloc(2 * elems * elem_size, GFP_KERNEL);
+               if (!ctrl->p_dyn) {
+                       kvfree(ctrl);
+                       return NULL;
+               }
+               data = ctrl->p_dyn;
+       }
+
        if (!ctrl->is_int) {
                ctrl->p_new.p = data;
                ctrl->p_cur.p = data + tot_ctrl_size;
@@ -1349,7 +1464,10 @@ static struct v4l2_ctrl *v4l2_ctrl_new(struct v4l2_ctrl_handler *hdl,
        }
 
        if (type >= V4L2_CTRL_COMPOUND_TYPES && p_def.p_const) {
-               ctrl->p_def.p = ctrl->p_cur.p + tot_ctrl_size;
+               if (ctrl->is_dyn_array)
+                       ctrl->p_def.p = &ctrl[1];
+               else
+                       ctrl->p_def.p = ctrl->p_cur.p + tot_ctrl_size;
                memcpy(ctrl->p_def.p, p_def.p_const, elem_size);
        }
 
@@ -1359,6 +1477,7 @@ static struct v4l2_ctrl *v4l2_ctrl_new(struct v4l2_ctrl_handler *hdl,
        }
 
        if (handler_new_ref(hdl, ctrl, NULL, false, false)) {
+               kvfree(ctrl->p_dyn);
                kvfree(ctrl);
                return NULL;
        }
@@ -1696,6 +1815,9 @@ static int cluster_changed(struct v4l2_ctrl *master)
                        continue;
                }
 
+               if (ctrl->elems != ctrl->new_elems)
+                       ctrl_changed = true;
+
                for (idx = 0; !ctrl_changed && idx < ctrl->elems; idx++)
                        ctrl_changed = !ctrl->type_ops->equal(ctrl, idx,
                                ctrl->p_cur, ctrl->p_new);
index 421300e..b73358f 100644 (file)
@@ -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[] = {
@@ -997,6 +998,7 @@ const char *v4l2_ctrl_get_name(u32 id)
        case V4L2_CID_MPEG_VIDEO_HEVC_SPS:                      return "HEVC Sequence Parameter Set";
        case V4L2_CID_MPEG_VIDEO_HEVC_PPS:                      return "HEVC Picture Parameter Set";
        case V4L2_CID_MPEG_VIDEO_HEVC_SLICE_PARAMS:             return "HEVC Slice Parameters";
+       case V4L2_CID_MPEG_VIDEO_HEVC_SCALING_MATRIX:           return "HEVC Scaling Matrix";
        case V4L2_CID_MPEG_VIDEO_HEVC_DECODE_PARAMS:            return "HEVC Decode Parameters";
        case V4L2_CID_MPEG_VIDEO_HEVC_DECODE_MODE:              return "HEVC Decode Mode";
        case V4L2_CID_MPEG_VIDEO_HEVC_START_CODE:               return "HEVC Start Code";
@@ -1107,6 +1109,7 @@ const char *v4l2_ctrl_get_name(u32 id)
        case V4L2_CID_TEST_PATTERN_GREENR:      return "Green (Red) Pixel Value";
        case V4L2_CID_TEST_PATTERN_BLUE:        return "Blue Pixel Value";
        case V4L2_CID_TEST_PATTERN_GREENB:      return "Green (Blue) Pixel Value";
+       case V4L2_CID_NOTIFY_GAINS:             return "Notify Gains";
 
        /* Image processing controls */
        /* Keep the order of the 'case's the same as in v4l2-controls.h! */
@@ -1490,6 +1493,9 @@ void v4l2_ctrl_fill(u32 id, const char **name, enum v4l2_ctrl_type *type,
        case V4L2_CID_MPEG_VIDEO_HEVC_SLICE_PARAMS:
                *type = V4L2_CTRL_TYPE_HEVC_SLICE_PARAMS;
                break;
+       case V4L2_CID_MPEG_VIDEO_HEVC_SCALING_MATRIX:
+               *type = V4L2_CTRL_TYPE_HEVC_SCALING_MATRIX;
+               break;
        case V4L2_CID_MPEG_VIDEO_HEVC_DECODE_PARAMS:
                *type = V4L2_CTRL_TYPE_HEVC_DECODE_PARAMS;
                break;
index d4bf2c7..aba6176 100644 (file)
@@ -57,10 +57,9 @@ void cur_to_new(struct v4l2_ctrl *ctrl);
 void cur_to_req(struct v4l2_ctrl_ref *ref);
 void new_to_cur(struct v4l2_fh *fh, struct v4l2_ctrl *ctrl, u32 ch_flags);
 void new_to_req(struct v4l2_ctrl_ref *ref);
-void req_to_new(struct v4l2_ctrl_ref *ref);
+int req_to_new(struct v4l2_ctrl_ref *ref);
 void send_initial_event(struct v4l2_fh *fh, struct v4l2_ctrl *ctrl);
 void send_event(struct v4l2_fh *fh, struct v4l2_ctrl *ctrl, u32 changes);
-int validate_new(const struct v4l2_ctrl *ctrl, union v4l2_ctrl_ptr p_new);
 int handler_new_ref(struct v4l2_ctrl_handler *hdl,
                    struct v4l2_ctrl *ctrl,
                    struct v4l2_ctrl_ref **ctrl_ref,
index 7d098f2..c637049 100644 (file)
@@ -143,7 +143,7 @@ v4l2_ctrl_request_hdl_ctrl_find(struct v4l2_ctrl_handler *hdl, u32 id)
 {
        struct v4l2_ctrl_ref *ref = find_ref_lock(hdl, id);
 
-       return (ref && ref->valid_p_req) ? ref->ctrl : NULL;
+       return (ref && ref->p_req_valid) ? ref->ctrl : NULL;
 }
 EXPORT_SYMBOL_GPL(v4l2_ctrl_request_hdl_ctrl_find);
 
@@ -373,7 +373,7 @@ void v4l2_ctrl_request_complete(struct media_request *req,
                        v4l2_ctrl_unlock(master);
                        continue;
                }
-               if (ref->valid_p_req)
+               if (ref->p_req_valid)
                        continue;
 
                /* Copy the current control value into the request */
@@ -442,7 +442,7 @@ int v4l2_ctrl_request_setup(struct media_request *req,
                                struct v4l2_ctrl_ref *r =
                                        find_ref(hdl, master->cluster[i]->id);
 
-                               if (r->valid_p_req) {
+                               if (r->p_req_valid) {
                                        have_new_data = true;
                                        break;
                                }
@@ -458,7 +458,11 @@ int v4l2_ctrl_request_setup(struct media_request *req,
                                struct v4l2_ctrl_ref *r =
                                        find_ref(hdl, master->cluster[i]->id);
 
-                               req_to_new(r);
+                               ret = req_to_new(r);
+                               if (ret) {
+                                       v4l2_ctrl_unlock(master);
+                                       goto error;
+                               }
                                master->cluster[i]->is_new = 1;
                                r->req_done = true;
                        }
@@ -490,6 +494,7 @@ int v4l2_ctrl_request_setup(struct media_request *req,
                        break;
        }
 
+error:
        media_request_object_put(obj);
        return ret;
 }
index 7c596a8..91d1a8d 100644 (file)
@@ -1265,6 +1265,8 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt)
        case V4L2_PIX_FMT_Y16_BE:       descr = "16-bit Greyscale BE"; break;
        case V4L2_PIX_FMT_Y10BPACK:     descr = "10-bit Greyscale (Packed)"; break;
        case V4L2_PIX_FMT_Y10P:         descr = "10-bit Greyscale (MIPI Packed)"; break;
+       case V4L2_PIX_FMT_Y12P:         descr = "12-bit Greyscale (MIPI Packed)"; break;
+       case V4L2_PIX_FMT_Y14P:         descr = "14-bit Greyscale (MIPI Packed)"; break;
        case V4L2_PIX_FMT_Y8I:          descr = "Interleaved 8-bit Greyscale"; break;
        case V4L2_PIX_FMT_Y12I:         descr = "Interleaved 12-bit Greyscale"; break;
        case V4L2_PIX_FMT_Z16:          descr = "16-bit Depth"; break;
@@ -1308,6 +1310,8 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt)
        case V4L2_PIX_FMT_NV61M:        descr = "Y/CrCb 4:2:2 (N-C)"; break;
        case V4L2_PIX_FMT_NV12MT:       descr = "Y/CbCr 4:2:0 (64x32 MB, N-C)"; break;
        case V4L2_PIX_FMT_NV12MT_16X16: descr = "Y/CbCr 4:2:0 (16x16 MB, 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;
@@ -1387,6 +1391,8 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt)
        case V4L2_META_FMT_VIVID:       descr = "Vivid Metadata"; break;
        case V4L2_META_FMT_RK_ISP1_PARAMS:      descr = "Rockchip ISP1 3A Parameters"; break;
        case V4L2_META_FMT_RK_ISP1_STAT_3A:     descr = "Rockchip ISP1 3A Statistics"; 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;
 
        default:
                /* Compressed formats */
index 3de683b..af012cf 100644 (file)
@@ -301,9 +301,10 @@ static void __v4l2_m2m_try_queue(struct v4l2_m2m_dev *m2m_dev,
 
        dprintk("Trying to schedule a job for m2m_ctx: %p\n", m2m_ctx);
 
-       if (!m2m_ctx->out_q_ctx.q.streaming
-           || !m2m_ctx->cap_q_ctx.q.streaming) {
-               dprintk("Streaming needs to be on for both queues\n");
+       if (!(m2m_ctx->out_q_ctx.q.streaming &&
+             m2m_ctx->cap_q_ctx.q.streaming) &&
+           !(m2m_ctx->out_q_ctx.buffered && m2m_ctx->out_q_ctx.q.streaming)) {
+               dprintk("Streaming needs to be on for both queues, or buffered and OUTPUT streaming\n");
                return;
        }
 
@@ -491,8 +492,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);
index d2f3452..24e376b 100644 (file)
@@ -11,6 +11,14 @@ config MFD_CORE
        select IRQ_DOMAIN
        default n
 
+config MFD_RPISENSE_CORE
+       tristate "Raspberry Pi Sense HAT core functions"
+       depends on I2C
+       select MFD_CORE
+       help
+         This is the core driver for the Raspberry Pi Sense HAT. This provides
+         the necessary functions to communicate with the hardware.
+
 config MFD_CS5535
        tristate "AMD CS5535 and CS5536 southbridge core functions"
        select MFD_CORE
@@ -1085,6 +1093,16 @@ config MFD_SPMI_PMIC
          Say M here if you want to include support for the SPMI PMIC
          series as a module.  The module will be called "qcom-spmi-pmic".
 
+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
index 2ba6646..0a30247 100644 (file)
@@ -266,6 +266,7 @@ obj-$(CONFIG_MFD_STMFX)     += stmfx.o
 obj-$(CONFIG_MFD_KHADAS_MCU)   += khadas-mcu.o
 obj-$(CONFIG_MFD_ACER_A500_EC) += acer-ec-a500.o
 obj-$(CONFIG_MFD_QCOM_PM8008)  += qcom-pm8008.o
+obj-$(CONFIG_MFD_RPISENSE_CORE)        += rpisense-core.o
 
 obj-$(CONFIG_SGI_MFD_IOC3)     += ioc3.o
 obj-$(CONFIG_MFD_SIMPLE_MFD_I2C)       += simple-mfd-i2c.o
index 42fe67f..f66f92f 100644 (file)
@@ -50,6 +50,17 @@ static int bcm2835_pm_probe(struct platform_device *pdev)
        if (ret)
                return ret;
 
+       /* Map the RPiVid ASB regs if present. */
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
+       if (res) {
+               pm->rpivid_asb = devm_ioremap_resource(dev, res);
+               if (IS_ERR(pm->rpivid_asb)) {
+                       dev_err(dev, "Failed to map RPiVid ASB: %ld\n",
+                               PTR_ERR(pm->rpivid_asb));
+                       return PTR_ERR(pm->rpivid_asb);
+               }
+       }
+
        /* We'll use the presence of the AXI ASB regs in the
         * bcm2835-pm binding as the key for whether we can reference
         * the full PM register range and support power domains.
diff --git a/drivers/mfd/rpisense-core.c b/drivers/mfd/rpisense-core.c
new file mode 100644 (file)
index 0000000..6cfd63e
--- /dev/null
@@ -0,0 +1,165 @@
+/*
+ * Raspberry Pi Sense HAT core 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.
+ *
+ *  This driver is based on wm8350 implementation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/rpisense/core.h>
+#include <linux/slab.h>
+
+static struct rpisense *rpisense;
+
+static void rpisense_client_dev_register(struct rpisense *rpisense,
+                                        const char *name,
+                                        struct platform_device **pdev)
+{
+       int ret;
+
+       *pdev = platform_device_alloc(name, -1);
+       if (*pdev == NULL) {
+               dev_err(rpisense->dev, "Failed to allocate %s\n", name);
+               return;
+       }
+
+       (*pdev)->dev.parent = rpisense->dev;
+       platform_set_drvdata(*pdev, rpisense);
+       ret = platform_device_add(*pdev);
+       if (ret != 0) {
+               dev_err(rpisense->dev, "Failed to register %s: %d\n",
+                       name, ret);
+               platform_device_put(*pdev);
+               *pdev = NULL;
+       }
+}
+
+static int rpisense_probe(struct i2c_client *i2c,
+                              const struct i2c_device_id *id)
+{
+       int ret;
+       struct rpisense_js *rpisense_js;
+
+       rpisense = devm_kzalloc(&i2c->dev, sizeof(struct rpisense), GFP_KERNEL);
+       if (rpisense == NULL)
+               return -ENOMEM;
+
+       i2c_set_clientdata(i2c, rpisense);
+       rpisense->dev = &i2c->dev;
+       rpisense->i2c_client = i2c;
+
+       ret = rpisense_reg_read(rpisense, RPISENSE_WAI);
+       if (ret > 0) {
+               if (ret != 's')
+                       return -EINVAL;
+       } else {
+               return ret;
+       }
+       ret = rpisense_reg_read(rpisense, RPISENSE_VER);
+       if (ret < 0)
+               return ret;
+
+       dev_info(rpisense->dev,
+                "Raspberry Pi Sense HAT firmware version %i\n", ret);
+
+       rpisense_js = &rpisense->joystick;
+       rpisense_js->keys_desc = devm_gpiod_get(&i2c->dev,
+                                               "keys-int", GPIOD_IN);
+       if (IS_ERR(rpisense_js->keys_desc)) {
+               dev_warn(&i2c->dev, "Failed to get keys-int descriptor.\n");
+               rpisense_js->keys_desc = gpio_to_desc(23);
+               if (rpisense_js->keys_desc == NULL) {
+                       dev_err(&i2c->dev, "GPIO23 fallback failed.\n");
+                       return PTR_ERR(rpisense_js->keys_desc);
+               }
+       }
+       rpisense_client_dev_register(rpisense, "rpi-sense-js",
+                                    &(rpisense->joystick.pdev));
+       rpisense_client_dev_register(rpisense, "rpi-sense-fb",
+                                    &(rpisense->framebuffer.pdev));
+
+       return 0;
+}
+
+static int rpisense_remove(struct i2c_client *i2c)
+{
+       struct rpisense *rpisense = i2c_get_clientdata(i2c);
+
+       platform_device_unregister(rpisense->joystick.pdev);
+       return 0;
+}
+
+struct rpisense *rpisense_get_dev(void)
+{
+       return rpisense;
+}
+EXPORT_SYMBOL_GPL(rpisense_get_dev);
+
+s32 rpisense_reg_read(struct rpisense *rpisense, int reg)
+{
+       int ret = i2c_smbus_read_byte_data(rpisense->i2c_client, reg);
+
+       if (ret < 0)
+               dev_err(rpisense->dev, "Read from reg %d failed\n", reg);
+       /* Due to the BCM270x I2C clock stretching bug, some values
+        * may have MSB set. Clear it to avoid incorrect values.
+        * */
+       return ret & 0x7F;
+}
+EXPORT_SYMBOL_GPL(rpisense_reg_read);
+
+int rpisense_block_write(struct rpisense *rpisense, const char *buf, int count)
+{
+       int ret = i2c_master_send(rpisense->i2c_client, buf, count);
+
+       if (ret < 0)
+               dev_err(rpisense->dev, "Block write failed\n");
+       return ret;
+}
+EXPORT_SYMBOL_GPL(rpisense_block_write);
+
+static const struct i2c_device_id rpisense_i2c_id[] = {
+       { "rpi-sense", 0 },
+       { }
+};
+MODULE_DEVICE_TABLE(i2c, rpisense_i2c_id);
+
+#ifdef CONFIG_OF
+static const struct of_device_id rpisense_core_id[] = {
+       { .compatible = "rpi,rpi-sense" },
+       { },
+};
+MODULE_DEVICE_TABLE(of, rpisense_core_id);
+#endif
+
+
+static struct i2c_driver rpisense_driver = {
+       .driver = {
+                  .name = "rpi-sense",
+                  .owner = THIS_MODULE,
+       },
+       .probe = rpisense_probe,
+       .remove = rpisense_remove,
+       .id_table = rpisense_i2c_id,
+};
+
+module_i2c_driver(rpisense_driver);
+
+MODULE_DESCRIPTION("Raspberry Pi Sense HAT core driver");
+MODULE_AUTHOR("Serge Schneider <serge@raspberrypi.org>");
+MODULE_LICENSE("GPL");
+
index 5153669..16eb2a3 100644 (file)
@@ -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 = &regmap_config_16r_8v,
+};
+
 static int simple_mfd_i2c_probe(struct i2c_client *i2c)
 {
        const struct simple_mfd_data *simple_mfd_data;
@@ -64,6 +73,7 @@ static int simple_mfd_i2c_probe(struct i2c_client *i2c)
 
 static const struct of_device_id simple_mfd_i2c_of_match[] = {
        { .compatible = "kontron,sl28cpld" },
+       { .compatible = "raspberrypi,poe-core", &rpi_poe_core },
        {}
 };
 MODULE_DEVICE_TABLE(of, simple_mfd_i2c_of_match);
index 0f5a49f..6063314 100644 (file)
@@ -9,6 +9,14 @@ 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 AD525X_DPOT
        tristate "Analog Devices Digital Potentiometers"
        depends on (I2C || SPI) && SYSFS
index a086197..e0188c3 100644 (file)
@@ -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/
diff --git a/drivers/misc/bcm2835_smi.c b/drivers/misc/bcm2835_smi.c
new file mode 100644 (file)
index 0000000..831b703
--- /dev/null
@@ -0,0 +1,955 @@
+/**
+ * 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;
+       dma_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;
+       const __be32 *addr;
+
+       /* 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);
+
+       ioresource = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       inst->smi_regs_ptr = devm_ioremap_resource(dev, ioresource);
+       if (IS_ERR(inst->smi_regs_ptr)) {
+               err = PTR_ERR(inst->smi_regs_ptr);
+               goto err;
+       }
+       addr = of_get_address(node, 0, NULL, NULL);
+       inst->smi_regs_busaddr = be32_to_cpu(*addr);
+
+       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 int 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");
+       return 0;
+}
+
+/****************************************************************************
+*
+*   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>");
index a196116..7818437 100644 (file)
@@ -171,6 +171,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,
@@ -1863,7 +1870,11 @@ static void mmc_blk_mq_rw_recovery(struct mmc_queue *mq, struct request *req)
        }
 
        /* FIXME: Missing single sector read for large sector size */
-       if (!mmc_large_sector(card) && rq_data_dir(req) == READ &&
+       /*
+        * XXX: don't do single-sector reads, as it leaks a SG DMA
+        * mapping when reusing the still-pending req.
+        */
+       if (0 && !mmc_large_sector(card) && rq_data_dir(req) == READ &&
            brq->data.blocks > 1) {
                /* Read one sector at a time */
                mmc_blk_read_single(mq, req);
@@ -2923,6 +2934,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.
@@ -2930,7 +2943,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);
@@ -2945,6 +2967,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;
index 368f104..c835074 100644 (file)
@@ -1801,7 +1801,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;
 }
index d68e6e5..e41bc00 100644 (file)
@@ -99,6 +99,14 @@ static const struct mmc_fixup __maybe_unused mmc_blk_fixups[] = {
        MMC_FIXUP("V10016", CID_MANFID_KINGSTON, CID_OEMID_ANY, add_quirk_mmc,
                  MMC_QUIRK_TRIM_BROKEN),
 
+       /*
+        *  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),
+
        END_FIXUP
 };
 
index ccc148c..76c74a1 100644 (file)
@@ -5,6 +5,45 @@
 
 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
+       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
index 14004cc..03c20de 100644 (file)
@@ -23,6 +23,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 (file)
index 0000000..cdc1318
--- /dev/null
@@ -0,0 +1,1576 @@
+/*
+ * 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);
+}
+
+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);
+}
+
+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;
+               cfg.slave_id = 11;              /* DREQ channel */
+
+               /* 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;
+       if (swiotlb_max_segment())
+               mmc->max_req_size = (1 << IO_TLB_SHIFT) * IO_TLB_SEGSIZE;
+       else
+               mmc->max_req_size = 524288;
+       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;
+       const __be32 *addr;
+       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);
+
+       iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       host->ioaddr = devm_ioremap_resource(dev, iomem);
+       if (IS_ERR(host->ioaddr)) {
+               ret = PTR_ERR(host->ioaddr);
+               goto err;
+       }
+
+       addr = of_get_address(node, 0, NULL, NULL);
+       if (!addr) {
+               dev_err(dev, "could not get DMA-register address\n");
+               ret = -ENODEV;
+               goto err;
+       }
+       host->bus_addr = be32_to_cpup(addr);
+       pr_debug(" - ioaddr %lx, iomem->start %lx, bus_addr %lx\n",
+                (unsigned long)host->ioaddr,
+                (unsigned long)iomem->start,
+                (unsigned long)host->bus_addr);
+
+#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) {
+               mmc_of_parse(mmc);
+
+               /* 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 int 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);
+
+       return 0;
+}
+
+
+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 (file)
index 0000000..2c41240
--- /dev/null
@@ -0,0 +1,2208 @@
+/*
+ * 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"
+
+#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, u32 bus_to_phys)
+{
+       spin_lock_init(&log_lock);
+       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);
+               timer_base = ioremap(bus_to_phys + 0x7e003000, SZ_4K);
+               if (!timer_base)
+                       pr_err("sdhost: failed to remap timer\n");
+       }
+       else
+               pr_err("sdhost: failed to allocate log buf\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);
+}
+
+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);
+}
+
+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);
+}
+
+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;
+}
+
+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,
+       .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));
+       }
+
+       mmc_request_done(host->mmc, mrq);
+       log_event("TSK>", mrq, 0);
+}
+
+int bcm2835_sdhost_add_host(struct bcm2835_host *host)
+{
+       struct mmc_host *mmc;
+       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;
+                       cfg.slave_id = 13;              /* DREQ channel */
+
+                       /* 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 = 524288;
+       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;
+       }
+
+       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;
+
+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;
+       const __be32 *addr;
+       u32 msg[3];
+       int na;
+       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);
+
+       iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       host->ioaddr = devm_ioremap_resource(dev, iomem);
+       if (IS_ERR(host->ioaddr)) {
+               ret = PTR_ERR(host->ioaddr);
+               goto err;
+       }
+
+       na = of_n_addr_cells(node);
+       addr = of_get_address(node, 0, NULL, NULL);
+       if (!addr) {
+               dev_err(dev, "could not get DMA-register address\n");
+               return -ENODEV;
+       }
+       host->bus_addr = (phys_addr_t)of_read_number(addr, na);
+       pr_debug(" - ioaddr %lx, iomem->start %lx, bus_addr %lx\n",
+                (unsigned long)host->ioaddr,
+                (unsigned long)iomem->start,
+                (unsigned long)host->bus_addr);
+
+       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, iomem->start - host->bus_addr);
+
+       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);
+
+       ret = bcm2835_sdhost_add_host(host);
+       if (ret)
+               goto err;
+
+       platform_set_drvdata(pdev, host);
+
+       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 int 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");
+       return 0;
+}
+
+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");
index 032bf85..499046a 100644 (file)
@@ -207,6 +207,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,
index 7728f26..05a251d 100644 (file)
@@ -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
 
@@ -3143,7 +3143,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_dumpregs(host);
 
@@ -3165,7 +3165,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_dumpregs(host);
 
index ea13917..9a90637 100644 (file)
@@ -67,6 +67,9 @@
 
 /* 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 inline void bcmgenet_writel(u32 value, void __iomem *offset)
 {
@@ -2439,6 +2442,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 */
        bcmgenet_umac_writel(priv, CMD_SW_RESET, UMAC_CMD);
        udelay(2);
@@ -2608,7 +2616,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);
@@ -3249,7 +3257,7 @@ static void bcmgenet_get_hw_addr(struct bcmgenet_priv *priv,
 }
 
 /* Returns a reusable dma control register value */
-static u32 bcmgenet_dma_disable(struct bcmgenet_priv *priv)
+static u32 bcmgenet_dma_disable(struct bcmgenet_priv *priv, bool flush_rx)
 {
        unsigned int i;
        u32 reg;
@@ -3274,6 +3282,14 @@ static u32 bcmgenet_dma_disable(struct bcmgenet_priv *priv)
        udelay(10);
        bcmgenet_umac_writel(priv, 0, UMAC_TX_FLUSH);
 
+       if (flush_rx) {
+           reg = bcmgenet_rbuf_ctrl_get(priv);
+           bcmgenet_rbuf_ctrl_set(priv, reg | BIT(0));
+           udelay(10);
+           bcmgenet_rbuf_ctrl_set(priv, reg);
+           udelay(10);
+       }
+
        return dma_ctrl;
 }
 
@@ -3337,8 +3353,8 @@ static int bcmgenet_open(struct net_device *dev)
 
        bcmgenet_set_hw_addr(priv, dev->dev_addr);
 
-       /* Disable RX/TX DMA and flush TX queues */
-       dma_ctrl = bcmgenet_dma_disable(priv);
+       /* Disable RX/TX DMA and flush TX and RX queues */
+       dma_ctrl = bcmgenet_dma_disable(priv, true);
 
        /* Reinitialize TDMA and RDMA and SW housekeeping */
        ret = bcmgenet_init_dma(priv);
@@ -4075,9 +4091,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);
@@ -4193,7 +4212,7 @@ static int bcmgenet_resume(struct device *d)
                        bcmgenet_hfb_create_rxnfc_filter(priv, rule);
 
        /* Disable RX/TX DMA and flush TX queues */
-       dma_ctrl = bcmgenet_dma_disable(priv);
+       dma_ctrl = bcmgenet_dma_disable(priv, false);
 
        /* Reinitialize TDMA and RDMA and SW housekeeping */
        ret = bcmgenet_init_dma(priv);
index 0a6d91b..5e653f2 100644 (file)
@@ -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
index 89d16c5..817cd00 100644 (file)
@@ -293,6 +293,8 @@ int bcmgenet_mii_probe(struct net_device *dev)
        /* Communicate the integrated PHY revision */
        if (priv->internal_phy)
                phy_flags = priv->gphy_rev;
+       else
+               phy_flags = PHY_BRCM_AUTO_PWRDWN_ENABLE;
 
        /* Initialize link state variables that bcmgenet_mii_setup() uses */
        priv->old_link = -1;
index 902495a..cf1b579 100644 (file)
@@ -97,6 +97,8 @@ config AX88796B_PHY
 config BROADCOM_PHY
        tristate "Broadcom 54XX PHYs"
        select BCM_NET_PHYLIB
+       select BCM_NET_PHYPTP if NETWORK_PHY_TIMESTAMPING
+       depends on PTP_1588_CLOCK_OPTIONAL
        help
          Currently supports the BCM5411, BCM5421, BCM5461, BCM54616S, BCM5464,
          BCM5481, BCM54810 and BCM5482 PHYs.
@@ -153,6 +155,9 @@ config BCM_CYGNUS_PHY
 config BCM_NET_PHYLIB
        tristate
 
+config BCM_NET_PHYPTP
+       tristate
+
 config CICADA_PHY
        tristate "Cicada PHYs"
        help
index b2728d0..bb21d42 100644 (file)
@@ -46,6 +46,7 @@ obj-$(CONFIG_BCM84881_PHY)    += bcm84881.o
 obj-$(CONFIG_BCM87XX_PHY)      += bcm87xx.o
 obj-$(CONFIG_BCM_CYGNUS_PHY)   += bcm-cygnus.o
 obj-$(CONFIG_BCM_NET_PHYLIB)   += bcm-phy-lib.o
+obj-$(CONFIG_BCM_NET_PHYPTP)   += bcm-phy-ptp.o
 obj-$(CONFIG_BROADCOM_PHY)     += broadcom.o
 obj-$(CONFIG_CICADA_PHY)       += cicada.o
 obj-$(CONFIG_CORTINA_PHY)      += cortina.o
index c3842f8..9902fb1 100644 (file)
@@ -87,4 +87,23 @@ int bcm_phy_cable_test_start_rdb(struct phy_device *phydev);
 int bcm_phy_cable_test_start(struct phy_device *phydev);
 int bcm_phy_cable_test_get_status(struct phy_device *phydev, bool *finished);
 
+#if IS_ENABLED(CONFIG_BCM_NET_PHYPTP)
+struct bcm_ptp_private *bcm_ptp_probe(struct phy_device *phydev);
+void bcm_ptp_config_init(struct phy_device *phydev);
+void bcm_ptp_stop(struct bcm_ptp_private *priv);
+#else
+static inline struct bcm_ptp_private *bcm_ptp_probe(struct phy_device *phydev)
+{
+       return NULL;
+}
+
+static inline void bcm_ptp_config_init(struct phy_device *phydev)
+{
+}
+
+static inline void bcm_ptp_stop(struct bcm_ptp_private *priv)
+{
+}
+#endif
+
 #endif /* _LINUX_BCM_PHY_LIB_H */
diff --git a/drivers/net/phy/bcm-phy-ptp.c b/drivers/net/phy/bcm-phy-ptp.c
new file mode 100644 (file)
index 0000000..08a1824
--- /dev/null
@@ -0,0 +1,947 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2022 Meta Platforms Inc.
+ * Copyright (C) 2022 Jonathan Lemon <jonathan.lemon@gmail.com>
+ */
+
+#include <asm/unaligned.h>
+#include <linux/mii.h>
+#include <linux/phy.h>
+#include <linux/ptp_classify.h>
+#include <linux/ptp_clock_kernel.h>
+#include <linux/net_tstamp.h>
+#include <linux/netdevice.h>
+#include <linux/workqueue.h>
+
+#include "bcm-phy-lib.h"
+
+/* IEEE 1588 Expansion registers */
+#define SLICE_CTRL             0x0810
+#define  SLICE_TX_EN                   BIT(0)
+#define  SLICE_RX_EN                   BIT(8)
+#define TX_EVENT_MODE          0x0811
+#define  MODE_TX_UPDATE_CF             BIT(0)
+#define  MODE_TX_REPLACE_TS_CF         BIT(1)
+#define  MODE_TX_REPLACE_TS            GENMASK(1, 0)
+#define RX_EVENT_MODE          0x0819
+#define  MODE_RX_UPDATE_CF             BIT(0)
+#define  MODE_RX_INSERT_TS_48          BIT(1)
+#define  MODE_RX_INSERT_TS_64          GENMASK(1, 0)
+
+#define MODE_EVT_SHIFT_SYNC            0
+#define MODE_EVT_SHIFT_DELAY_REQ       2
+#define MODE_EVT_SHIFT_PDELAY_REQ      4
+#define MODE_EVT_SHIFT_PDELAY_RESP     6
+
+#define MODE_SEL_SHIFT_PORT            0
+#define MODE_SEL_SHIFT_CPU             8
+
+#define RX_MODE_SEL(sel, evt, act) \
+       (((MODE_RX_##act) << (MODE_EVT_SHIFT_##evt)) << (MODE_SEL_SHIFT_##sel))
+
+#define TX_MODE_SEL(sel, evt, act) \
+       (((MODE_TX_##act) << (MODE_EVT_SHIFT_##evt)) << (MODE_SEL_SHIFT_##sel))
+
+/* needs global TS capture first */
+#define TX_TS_CAPTURE          0x0821
+#define  TX_TS_CAP_EN                  BIT(0)
+#define RX_TS_CAPTURE          0x0822
+#define  RX_TS_CAP_EN                  BIT(0)
+
+#define TIME_CODE_0            0x0854
+#define TIME_CODE_1            0x0855
+#define TIME_CODE_2            0x0856
+#define TIME_CODE_3            0x0857
+#define TIME_CODE_4            0x0858
+
+#define DPLL_SELECT            0x085b
+#define  DPLL_HB_MODE2                 BIT(6)
+
+#define SHADOW_CTRL            0x085c
+#define SHADOW_LOAD            0x085d
+#define  TIME_CODE_LOAD                        BIT(10)
+#define  SYNC_OUT_LOAD                 BIT(9)
+#define  NCO_TIME_LOAD                 BIT(7)
+#define  FREQ_LOAD                     BIT(6)
+#define INTR_MASK              0x085e
+#define INTR_STATUS            0x085f
+#define  INTC_FSYNC                    BIT(0)
+#define  INTC_SOP                      BIT(1)
+
+#define NCO_FREQ_LSB           0x0873
+#define NCO_FREQ_MSB           0x0874
+
+#define NCO_TIME_0             0x0875
+#define NCO_TIME_1             0x0876
+#define NCO_TIME_2_CTRL                0x0877
+#define  FREQ_MDIO_SEL                 BIT(14)
+
+#define SYNC_OUT_0             0x0878
+#define SYNC_OUT_1             0x0879
+#define SYNC_OUT_2             0x087a
+
+#define SYNC_IN_DIVIDER                0x087b
+
+#define SYNOUT_TS_0            0x087c
+#define SYNOUT_TS_1            0x087d
+#define SYNOUT_TS_2            0x087e
+
+#define NSE_CTRL               0x087f
+#define  NSE_GMODE_EN                  GENMASK(15, 14)
+#define  NSE_CAPTURE_EN                        BIT(13)
+#define  NSE_INIT                      BIT(12)
+#define  NSE_CPU_FRAMESYNC             BIT(5)
+#define  NSE_SYNC1_FRAMESYNC           BIT(3)
+#define  NSE_FRAMESYNC_MASK            GENMASK(5, 2)
+#define  NSE_PEROUT_EN                 BIT(1)
+#define  NSE_ONESHOT_EN                        BIT(0)
+#define  NSE_SYNC_OUT_MASK             GENMASK(1, 0)
+
+#define TS_READ_CTRL           0x0885
+#define  TS_READ_START                 BIT(0)
+#define  TS_READ_END                   BIT(1)
+
+#define HB_REG_0               0x0886
+#define HB_REG_1               0x0887
+#define HB_REG_2               0x0888
+#define HB_REG_3               0x08ec
+#define HB_REG_4               0x08ed
+#define HB_STAT_CTRL           0x088e
+#define  HB_READ_START                 BIT(10)
+#define  HB_READ_END                   BIT(11)
+#define  HB_READ_MASK                  GENMASK(11, 10)
+
+#define TS_REG_0               0x0889
+#define TS_REG_1               0x088a
+#define TS_REG_2               0x088b
+#define TS_REG_3               0x08c4
+
+#define TS_INFO_0              0x088c
+#define TS_INFO_1              0x088d
+
+#define TIMECODE_CTRL          0x08c3
+#define  TX_TIMECODE_SEL               GENMASK(7, 0)
+#define  RX_TIMECODE_SEL               GENMASK(15, 8)
+
+#define TIME_SYNC              0x0ff5
+#define  TIME_SYNC_EN                  BIT(0)
+
+struct bcm_ptp_private {
+       struct phy_device *phydev;
+       struct mii_timestamper mii_ts;
+       struct ptp_clock *ptp_clock;
+       struct ptp_clock_info ptp_info;
+       struct ptp_pin_desc pin;
+       struct mutex mutex;
+       struct sk_buff_head tx_queue;
+       int tx_type;
+       bool hwts_rx;
+       u16 nse_ctrl;
+       bool pin_active;
+       struct delayed_work pin_work;
+};
+
+struct bcm_ptp_skb_cb {
+       unsigned long timeout;
+       u16 seq_id;
+       u8 msgtype;
+       bool discard;
+};
+
+struct bcm_ptp_capture {
+       ktime_t hwtstamp;
+       u16 seq_id;
+       u8 msgtype;
+       bool tx_dir;
+};
+
+#define BCM_SKB_CB(skb)                ((struct bcm_ptp_skb_cb *)(skb)->cb)
+#define SKB_TS_TIMEOUT         10                      /* jiffies */
+
+#define BCM_MAX_PULSE_8NS      ((1U << 9) - 1)
+#define BCM_MAX_PERIOD_8NS     ((1U << 30) - 1)
+
+#define BRCM_PHY_MODEL(phydev) \
+       ((phydev)->drv->phy_id & (phydev)->drv->phy_id_mask)
+
+static struct bcm_ptp_private *mii2priv(struct mii_timestamper *mii_ts)
+{
+       return container_of(mii_ts, struct bcm_ptp_private, mii_ts);
+}
+
+static struct bcm_ptp_private *ptp2priv(struct ptp_clock_info *info)
+{
+       return container_of(info, struct bcm_ptp_private, ptp_info);
+}
+
+static void bcm_ptp_get_framesync_ts(struct phy_device *phydev,
+                                    struct timespec64 *ts)
+{
+       u16 hb[4];
+
+       bcm_phy_write_exp(phydev, HB_STAT_CTRL, HB_READ_START);
+
+       hb[0] = bcm_phy_read_exp(phydev, HB_REG_0);
+       hb[1] = bcm_phy_read_exp(phydev, HB_REG_1);
+       hb[2] = bcm_phy_read_exp(phydev, HB_REG_2);
+       hb[3] = bcm_phy_read_exp(phydev, HB_REG_3);
+
+       bcm_phy_write_exp(phydev, HB_STAT_CTRL, HB_READ_END);
+       bcm_phy_write_exp(phydev, HB_STAT_CTRL, 0);
+
+       ts->tv_sec = (hb[3] << 16) | hb[2];
+       ts->tv_nsec = (hb[1] << 16) | hb[0];
+}
+
+static u16 bcm_ptp_framesync_disable(struct phy_device *phydev, u16 orig_ctrl)
+{
+       u16 ctrl = orig_ctrl & ~(NSE_FRAMESYNC_MASK | NSE_CAPTURE_EN);
+
+       bcm_phy_write_exp(phydev, NSE_CTRL, ctrl);
+
+       return ctrl;
+}
+
+static void bcm_ptp_framesync_restore(struct phy_device *phydev, u16 orig_ctrl)
+{
+       if (orig_ctrl & NSE_FRAMESYNC_MASK)
+               bcm_phy_write_exp(phydev, NSE_CTRL, orig_ctrl);
+}
+
+static void bcm_ptp_framesync(struct phy_device *phydev, u16 ctrl)
+{
+       /* trigger framesync - must have 0->1 transition. */
+       bcm_phy_write_exp(phydev, NSE_CTRL, ctrl | NSE_CPU_FRAMESYNC);
+}
+
+static int bcm_ptp_framesync_ts(struct phy_device *phydev,
+                               struct ptp_system_timestamp *sts,
+                               struct timespec64 *ts,
+                               u16 orig_ctrl)
+{
+       u16 ctrl, reg;
+       int i;
+
+       ctrl = bcm_ptp_framesync_disable(phydev, orig_ctrl);
+
+       ptp_read_system_prets(sts);
+
+       /* trigger framesync + capture */
+       bcm_ptp_framesync(phydev, ctrl | NSE_CAPTURE_EN);
+
+       ptp_read_system_postts(sts);
+
+       /* poll for FSYNC interrupt from TS capture */
+       for (i = 0; i < 10; i++) {
+               reg = bcm_phy_read_exp(phydev, INTR_STATUS);
+               if (reg & INTC_FSYNC) {
+                       bcm_ptp_get_framesync_ts(phydev, ts);
+                       break;
+               }
+       }
+
+       bcm_ptp_framesync_restore(phydev, orig_ctrl);
+
+       return reg & INTC_FSYNC ? 0 : -ETIMEDOUT;
+}
+
+static int bcm_ptp_gettimex(struct ptp_clock_info *info,
+                           struct timespec64 *ts,
+                           struct ptp_system_timestamp *sts)
+{
+       struct bcm_ptp_private *priv = ptp2priv(info);
+       int err;
+
+       mutex_lock(&priv->mutex);
+       err = bcm_ptp_framesync_ts(priv->phydev, sts, ts, priv->nse_ctrl);
+       mutex_unlock(&priv->mutex);
+
+       return err;
+}
+
+static int bcm_ptp_settime_locked(struct bcm_ptp_private *priv,
+                                 const struct timespec64 *ts)
+{
+       struct phy_device *phydev = priv->phydev;
+       u16 ctrl;
+       u64 ns;
+
+       ctrl = bcm_ptp_framesync_disable(phydev, priv->nse_ctrl);
+
+       /* set up time code */
+       bcm_phy_write_exp(phydev, TIME_CODE_0, ts->tv_nsec);
+       bcm_phy_write_exp(phydev, TIME_CODE_1, ts->tv_nsec >> 16);
+       bcm_phy_write_exp(phydev, TIME_CODE_2, ts->tv_sec);
+       bcm_phy_write_exp(phydev, TIME_CODE_3, ts->tv_sec >> 16);
+       bcm_phy_write_exp(phydev, TIME_CODE_4, ts->tv_sec >> 32);
+
+       /* set NCO counter to match */
+       ns = timespec64_to_ns(ts);
+       bcm_phy_write_exp(phydev, NCO_TIME_0, ns >> 4);
+       bcm_phy_write_exp(phydev, NCO_TIME_1, ns >> 20);
+       bcm_phy_write_exp(phydev, NCO_TIME_2_CTRL, (ns >> 36) & 0xfff);
+
+       /* set up load on next frame sync (auto-clears due to NSE_INIT) */
+       bcm_phy_write_exp(phydev, SHADOW_LOAD, TIME_CODE_LOAD | NCO_TIME_LOAD);
+
+       /* must have NSE_INIT in order to write time code */
+       bcm_ptp_framesync(phydev, ctrl | NSE_INIT);
+
+       bcm_ptp_framesync_restore(phydev, priv->nse_ctrl);
+
+       return 0;
+}
+
+static int bcm_ptp_settime(struct ptp_clock_info *info,
+                          const struct timespec64 *ts)
+{
+       struct bcm_ptp_private *priv = ptp2priv(info);
+       int err;
+
+       mutex_lock(&priv->mutex);
+       err = bcm_ptp_settime_locked(priv, ts);
+       mutex_unlock(&priv->mutex);
+
+       return err;
+}
+
+static int bcm_ptp_adjtime_locked(struct bcm_ptp_private *priv,
+                                 s64 delta_ns)
+{
+       struct timespec64 ts;
+       int err;
+       s64 ns;
+
+       err = bcm_ptp_framesync_ts(priv->phydev, NULL, &ts, priv->nse_ctrl);
+       if (!err) {
+               ns = timespec64_to_ns(&ts) + delta_ns;
+               ts = ns_to_timespec64(ns);
+               err = bcm_ptp_settime_locked(priv, &ts);
+       }
+       return err;
+}
+
+static int bcm_ptp_adjtime(struct ptp_clock_info *info, s64 delta_ns)
+{
+       struct bcm_ptp_private *priv = ptp2priv(info);
+       int err;
+
+       mutex_lock(&priv->mutex);
+       err = bcm_ptp_adjtime_locked(priv, delta_ns);
+       mutex_unlock(&priv->mutex);
+
+       return err;
+}
+
+/* A 125Mhz clock should adjust 8ns per pulse.
+ * The frequency adjustment base is 0x8000 0000, or 8*2^28.
+ *
+ * Frequency adjustment is
+ * adj = scaled_ppm * 8*2^28 / (10^6 * 2^16)
+ *   which simplifies to:
+ * adj = scaled_ppm * 2^9 / 5^6
+ */
+static int bcm_ptp_adjfine(struct ptp_clock_info *info, long scaled_ppm)
+{
+       struct bcm_ptp_private *priv = ptp2priv(info);
+       int neg_adj = 0;
+       u32 diff, freq;
+       u16 ctrl;
+       u64 adj;
+
+       if (scaled_ppm < 0) {
+               neg_adj = 1;
+               scaled_ppm = -scaled_ppm;
+       }
+
+       adj = scaled_ppm << 9;
+       diff = div_u64(adj, 15625);
+       freq = (8 << 28) + (neg_adj ? -diff : diff);
+
+       mutex_lock(&priv->mutex);
+
+       ctrl = bcm_ptp_framesync_disable(priv->phydev, priv->nse_ctrl);
+
+       bcm_phy_write_exp(priv->phydev, NCO_FREQ_LSB, freq);
+       bcm_phy_write_exp(priv->phydev, NCO_FREQ_MSB, freq >> 16);
+
+       bcm_phy_write_exp(priv->phydev, NCO_TIME_2_CTRL, FREQ_MDIO_SEL);
+
+       /* load on next framesync */
+       bcm_phy_write_exp(priv->phydev, SHADOW_LOAD, FREQ_LOAD);
+
+       bcm_ptp_framesync(priv->phydev, ctrl);
+
+       /* clear load */
+       bcm_phy_write_exp(priv->phydev, SHADOW_LOAD, 0);
+
+       bcm_ptp_framesync_restore(priv->phydev, priv->nse_ctrl);
+
+       mutex_unlock(&priv->mutex);
+
+       return 0;
+}
+
+static bool bcm_ptp_rxtstamp(struct mii_timestamper *mii_ts,
+                            struct sk_buff *skb, int type)
+{
+       struct bcm_ptp_private *priv = mii2priv(mii_ts);
+       struct skb_shared_hwtstamps *hwts;
+       struct ptp_header *header;
+       u32 sec, nsec;
+       u8 *data;
+       int off;
+
+       if (!priv->hwts_rx)
+               return false;
+
+       header = ptp_parse_header(skb, type);
+       if (!header)
+               return false;
+
+       data = (u8 *)(header + 1);
+       sec = get_unaligned_be32(data);
+       nsec = get_unaligned_be32(data + 4);
+
+       hwts = skb_hwtstamps(skb);
+       hwts->hwtstamp = ktime_set(sec, nsec);
+
+       off = data - skb->data + 8;
+       if (off < skb->len) {
+               memmove(data, data + 8, skb->len - off);
+               __pskb_trim(skb, skb->len - 8);
+       }
+
+       return false;
+}
+
+static bool bcm_ptp_get_tstamp(struct bcm_ptp_private *priv,
+                              struct bcm_ptp_capture *capts)
+{
+       struct phy_device *phydev = priv->phydev;
+       u16 ts[4], reg;
+       u32 sec, nsec;
+
+       mutex_lock(&priv->mutex);
+
+       reg = bcm_phy_read_exp(phydev, INTR_STATUS);
+       if ((reg & INTC_SOP) == 0) {
+               mutex_unlock(&priv->mutex);
+               return false;
+       }
+
+       bcm_phy_write_exp(phydev, TS_READ_CTRL, TS_READ_START);
+
+       ts[0] = bcm_phy_read_exp(phydev, TS_REG_0);
+       ts[1] = bcm_phy_read_exp(phydev, TS_REG_1);
+       ts[2] = bcm_phy_read_exp(phydev, TS_REG_2);
+       ts[3] = bcm_phy_read_exp(phydev, TS_REG_3);
+
+       /* not in be32 format for some reason */
+       capts->seq_id = bcm_phy_read_exp(priv->phydev, TS_INFO_0);
+
+       reg = bcm_phy_read_exp(phydev, TS_INFO_1);
+       capts->msgtype = reg >> 12;
+       capts->tx_dir = !!(reg & BIT(11));
+
+       bcm_phy_write_exp(phydev, TS_READ_CTRL, TS_READ_END);
+       bcm_phy_write_exp(phydev, TS_READ_CTRL, 0);
+
+       mutex_unlock(&priv->mutex);
+
+       sec = (ts[3] << 16) | ts[2];
+       nsec = (ts[1] << 16) | ts[0];
+       capts->hwtstamp = ktime_set(sec, nsec);
+
+       return true;
+}
+
+static void bcm_ptp_match_tstamp(struct bcm_ptp_private *priv,
+                                struct bcm_ptp_capture *capts)
+{
+       struct skb_shared_hwtstamps hwts;
+       struct sk_buff *skb, *ts_skb;
+       unsigned long flags;
+       bool first = false;
+
+       ts_skb = NULL;
+       spin_lock_irqsave(&priv->tx_queue.lock, flags);
+       skb_queue_walk(&priv->tx_queue, skb) {
+               if (BCM_SKB_CB(skb)->seq_id == capts->seq_id &&
+                   BCM_SKB_CB(skb)->msgtype == capts->msgtype) {
+                       first = skb_queue_is_first(&priv->tx_queue, skb);
+                       __skb_unlink(skb, &priv->tx_queue);
+                       ts_skb = skb;
+                       break;
+               }
+       }
+       spin_unlock_irqrestore(&priv->tx_queue.lock, flags);
+
+       /* TX captures one-step packets, discard them if needed. */
+       if (ts_skb) {
+               if (BCM_SKB_CB(ts_skb)->discard) {
+                       kfree_skb(ts_skb);
+               } else {
+                       memset(&hwts, 0, sizeof(hwts));
+                       hwts.hwtstamp = capts->hwtstamp;
+                       skb_complete_tx_timestamp(ts_skb, &hwts);
+               }
+       }
+
+       /* not first match, try and expire entries */
+       if (!first) {
+               while ((skb = skb_dequeue(&priv->tx_queue))) {
+                       if (!time_after(jiffies, BCM_SKB_CB(skb)->timeout)) {
+                               skb_queue_head(&priv->tx_queue, skb);
+                               break;
+                       }
+                       kfree_skb(skb);
+               }
+       }
+}
+
+static long bcm_ptp_do_aux_work(struct ptp_clock_info *info)
+{
+       struct bcm_ptp_private *priv = ptp2priv(info);
+       struct bcm_ptp_capture capts;
+       bool reschedule = false;
+
+       while (!skb_queue_empty_lockless(&priv->tx_queue)) {
+               if (!bcm_ptp_get_tstamp(priv, &capts)) {
+                       reschedule = true;
+                       break;
+               }
+               bcm_ptp_match_tstamp(priv, &capts);
+       }
+
+       return reschedule ? 1 : -1;
+}
+
+static int bcm_ptp_cancel_func(struct bcm_ptp_private *priv)
+{
+       if (!priv->pin_active)
+               return 0;
+
+       priv->pin_active = false;
+
+       priv->nse_ctrl &= ~(NSE_SYNC_OUT_MASK | NSE_SYNC1_FRAMESYNC |
+                           NSE_CAPTURE_EN);
+       bcm_phy_write_exp(priv->phydev, NSE_CTRL, priv->nse_ctrl);
+
+       cancel_delayed_work_sync(&priv->pin_work);
+
+       return 0;
+}
+
+static void bcm_ptp_perout_work(struct work_struct *pin_work)
+{
+       struct bcm_ptp_private *priv =
+               container_of(pin_work, struct bcm_ptp_private, pin_work.work);
+       struct phy_device *phydev = priv->phydev;
+       struct timespec64 ts;
+       u64 ns, next;
+       u16 ctrl;
+
+       mutex_lock(&priv->mutex);
+
+       /* no longer running */
+       if (!priv->pin_active) {
+               mutex_unlock(&priv->mutex);
+               return;
+       }
+
+       bcm_ptp_framesync_ts(phydev, NULL, &ts, priv->nse_ctrl);
+
+       /* this is 1PPS only */
+       next = NSEC_PER_SEC - ts.tv_nsec;
+       ts.tv_sec += next < NSEC_PER_MSEC ? 2 : 1;
+       ts.tv_nsec = 0;
+
+       ns = timespec64_to_ns(&ts);
+
+       /* force 0->1 transition for ONESHOT */
+       ctrl = bcm_ptp_framesync_disable(phydev,
+                                        priv->nse_ctrl & ~NSE_ONESHOT_EN);
+
+       bcm_phy_write_exp(phydev, SYNOUT_TS_0, ns & 0xfff0);
+       bcm_phy_write_exp(phydev, SYNOUT_TS_1, ns >> 16);
+       bcm_phy_write_exp(phydev, SYNOUT_TS_2, ns >> 32);
+
+       /* load values on next framesync */
+       bcm_phy_write_exp(phydev, SHADOW_LOAD, SYNC_OUT_LOAD);
+
+       bcm_ptp_framesync(phydev, ctrl | NSE_ONESHOT_EN | NSE_INIT);
+
+       priv->nse_ctrl |= NSE_ONESHOT_EN;
+       bcm_ptp_framesync_restore(phydev, priv->nse_ctrl);
+
+       mutex_unlock(&priv->mutex);
+
+       next = next + NSEC_PER_MSEC;
+       schedule_delayed_work(&priv->pin_work, nsecs_to_jiffies(next));
+}
+
+static int bcm_ptp_perout_locked(struct bcm_ptp_private *priv,
+                                struct ptp_perout_request *req, int on)
+{
+       struct phy_device *phydev = priv->phydev;
+       u64 period, pulse;
+       u16 val;
+
+       if (!on)
+               return bcm_ptp_cancel_func(priv);
+
+       /* 1PPS */
+       if (req->period.sec != 1 || req->period.nsec != 0)
+               return -EINVAL;
+
+       period = BCM_MAX_PERIOD_8NS;    /* write nonzero value */
+
+       if (req->flags & PTP_PEROUT_PHASE)
+               return -EOPNOTSUPP;
+
+       if (req->flags & PTP_PEROUT_DUTY_CYCLE)
+               pulse = ktime_to_ns(ktime_set(req->on.sec, req->on.nsec));
+       else
+               pulse = (u64)BCM_MAX_PULSE_8NS << 3;
+
+       /* convert to 8ns units */
+       pulse >>= 3;
+
+       if (!pulse || pulse > period || pulse > BCM_MAX_PULSE_8NS)
+               return -EINVAL;
+
+       bcm_phy_write_exp(phydev, SYNC_OUT_0, period);
+
+       val = ((pulse & 0x3) << 14) | ((period >> 16) & 0x3fff);
+       bcm_phy_write_exp(phydev, SYNC_OUT_1, val);
+
+       val = ((pulse >> 2) & 0x7f) | (pulse << 7);
+       bcm_phy_write_exp(phydev, SYNC_OUT_2, val);
+
+       if (priv->pin_active)
+               cancel_delayed_work_sync(&priv->pin_work);
+
+       priv->pin_active = true;
+       INIT_DELAYED_WORK(&priv->pin_work, bcm_ptp_perout_work);
+       schedule_delayed_work(&priv->pin_work, 0);
+
+       return 0;
+}
+
+static void bcm_ptp_extts_work(struct work_struct *pin_work)
+{
+       struct bcm_ptp_private *priv =
+               container_of(pin_work, struct bcm_ptp_private, pin_work.work);
+       struct phy_device *phydev = priv->phydev;
+       struct ptp_clock_event event;
+       struct timespec64 ts;
+       u16 reg;
+
+       mutex_lock(&priv->mutex);
+
+       /* no longer running */
+       if (!priv->pin_active) {
+               mutex_unlock(&priv->mutex);
+               return;
+       }
+
+       reg = bcm_phy_read_exp(phydev, INTR_STATUS);
+       if ((reg & INTC_FSYNC) == 0)
+               goto out;
+
+       bcm_ptp_get_framesync_ts(phydev, &ts);
+
+       event.index = 0;
+       event.type = PTP_CLOCK_EXTTS;
+       event.timestamp = timespec64_to_ns(&ts);
+       ptp_clock_event(priv->ptp_clock, &event);
+
+out:
+       mutex_unlock(&priv->mutex);
+       schedule_delayed_work(&priv->pin_work, HZ / 4);
+}
+
+static int bcm_ptp_extts_locked(struct bcm_ptp_private *priv, int on)
+{
+       struct phy_device *phydev = priv->phydev;
+
+       if (!on)
+               return bcm_ptp_cancel_func(priv);
+
+       if (priv->pin_active)
+               cancel_delayed_work_sync(&priv->pin_work);
+
+       bcm_ptp_framesync_disable(phydev, priv->nse_ctrl);
+
+       priv->nse_ctrl |= NSE_SYNC1_FRAMESYNC | NSE_CAPTURE_EN;
+
+       bcm_ptp_framesync_restore(phydev, priv->nse_ctrl);
+
+       priv->pin_active = true;
+       INIT_DELAYED_WORK(&priv->pin_work, bcm_ptp_extts_work);
+       schedule_delayed_work(&priv->pin_work, 0);
+
+       return 0;
+}
+
+static int bcm_ptp_enable(struct ptp_clock_info *info,
+                         struct ptp_clock_request *rq, int on)
+{
+       struct bcm_ptp_private *priv = ptp2priv(info);
+       int err = -EBUSY;
+
+       mutex_lock(&priv->mutex);
+
+       switch (rq->type) {
+       case PTP_CLK_REQ_PEROUT:
+               if (priv->pin.func == PTP_PF_PEROUT)
+                       err = bcm_ptp_perout_locked(priv, &rq->perout, on);
+               break;
+       case PTP_CLK_REQ_EXTTS:
+               if (priv->pin.func == PTP_PF_EXTTS)
+                       err = bcm_ptp_extts_locked(priv, on);
+               break;
+       default:
+               err = -EOPNOTSUPP;
+               break;
+       }
+
+       mutex_unlock(&priv->mutex);
+
+       return err;
+}
+
+static int bcm_ptp_verify(struct ptp_clock_info *info, unsigned int pin,
+                         enum ptp_pin_function func, unsigned int chan)
+{
+       switch (func) {
+       case PTP_PF_NONE:
+       case PTP_PF_EXTTS:
+       case PTP_PF_PEROUT:
+               break;
+       default:
+               return -EOPNOTSUPP;
+       }
+       return 0;
+}
+
+static const struct ptp_clock_info bcm_ptp_clock_info = {
+       .owner          = THIS_MODULE,
+       .name           = KBUILD_MODNAME,
+       .max_adj        = 100000000,
+       .gettimex64     = bcm_ptp_gettimex,
+       .settime64      = bcm_ptp_settime,
+       .adjtime        = bcm_ptp_adjtime,
+       .adjfine        = bcm_ptp_adjfine,
+       .enable         = bcm_ptp_enable,
+       .verify         = bcm_ptp_verify,
+       .do_aux_work    = bcm_ptp_do_aux_work,
+       .n_pins         = 1,
+       .n_per_out      = 1,
+       .n_ext_ts       = 1,
+};
+
+static void bcm_ptp_txtstamp(struct mii_timestamper *mii_ts,
+                            struct sk_buff *skb, int type)
+{
+       struct bcm_ptp_private *priv = mii2priv(mii_ts);
+       struct ptp_header *hdr;
+       bool discard = false;
+       int msgtype;
+
+       hdr = ptp_parse_header(skb, type);
+       if (!hdr)
+               goto out;
+       msgtype = ptp_get_msgtype(hdr, type);
+
+       switch (priv->tx_type) {
+       case HWTSTAMP_TX_ONESTEP_P2P:
+               if (msgtype == PTP_MSGTYPE_PDELAY_RESP)
+                       discard = true;
+               fallthrough;
+       case HWTSTAMP_TX_ONESTEP_SYNC:
+               if (msgtype == PTP_MSGTYPE_SYNC)
+                       discard = true;
+               fallthrough;
+       case HWTSTAMP_TX_ON:
+               BCM_SKB_CB(skb)->timeout = jiffies + SKB_TS_TIMEOUT;
+               BCM_SKB_CB(skb)->seq_id = be16_to_cpu(hdr->sequence_id);
+               BCM_SKB_CB(skb)->msgtype = msgtype;
+               BCM_SKB_CB(skb)->discard = discard;
+               skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
+               skb_queue_tail(&priv->tx_queue, skb);
+               ptp_schedule_worker(priv->ptp_clock, 0);
+               return;
+       default:
+               break;
+       }
+
+out:
+       kfree_skb(skb);
+}
+
+static int bcm_ptp_hwtstamp(struct mii_timestamper *mii_ts,
+                           struct ifreq *ifr)
+{
+       struct bcm_ptp_private *priv = mii2priv(mii_ts);
+       struct hwtstamp_config cfg;
+       u16 mode, ctrl;
+
+       if (copy_from_user(&cfg, ifr->ifr_data, sizeof(cfg)))
+               return -EFAULT;
+
+       switch (cfg.rx_filter) {
+       case HWTSTAMP_FILTER_NONE:
+               priv->hwts_rx = false;
+               break;
+       case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
+       case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:
+       case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:
+       case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
+       case HWTSTAMP_FILTER_PTP_V2_L2_SYNC:
+       case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ:
+       case HWTSTAMP_FILTER_PTP_V2_EVENT:
+       case HWTSTAMP_FILTER_PTP_V2_SYNC:
+       case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:
+               cfg.rx_filter = HWTSTAMP_FILTER_PTP_V2_EVENT;
+               priv->hwts_rx = true;
+               break;
+       default:
+               return -ERANGE;
+       }
+
+       priv->tx_type = cfg.tx_type;
+
+       ctrl  = priv->hwts_rx ? SLICE_RX_EN : 0;
+       ctrl |= priv->tx_type != HWTSTAMP_TX_OFF ? SLICE_TX_EN : 0;
+
+       mode = TX_MODE_SEL(PORT, SYNC, REPLACE_TS) |
+              TX_MODE_SEL(PORT, DELAY_REQ, REPLACE_TS) |
+              TX_MODE_SEL(PORT, PDELAY_REQ, REPLACE_TS) |
+              TX_MODE_SEL(PORT, PDELAY_RESP, REPLACE_TS);
+
+       bcm_phy_write_exp(priv->phydev, TX_EVENT_MODE, mode);
+
+       mode = RX_MODE_SEL(PORT, SYNC, INSERT_TS_64) |
+              RX_MODE_SEL(PORT, DELAY_REQ, INSERT_TS_64) |
+              RX_MODE_SEL(PORT, PDELAY_REQ, INSERT_TS_64) |
+              RX_MODE_SEL(PORT, PDELAY_RESP, INSERT_TS_64);
+
+       bcm_phy_write_exp(priv->phydev, RX_EVENT_MODE, mode);
+
+       bcm_phy_write_exp(priv->phydev, SLICE_CTRL, ctrl);
+
+       if (ctrl & SLICE_TX_EN)
+               bcm_phy_write_exp(priv->phydev, TX_TS_CAPTURE, TX_TS_CAP_EN);
+       else
+               ptp_cancel_worker_sync(priv->ptp_clock);
+
+       /* purge existing data */
+       skb_queue_purge(&priv->tx_queue);
+
+       return copy_to_user(ifr->ifr_data, &cfg, sizeof(cfg)) ? -EFAULT : 0;
+}
+
+static int bcm_ptp_ts_info(struct mii_timestamper *mii_ts,
+                          struct ethtool_ts_info *ts_info)
+{
+       struct bcm_ptp_private *priv = mii2priv(mii_ts);
+
+       ts_info->phc_index = ptp_clock_index(priv->ptp_clock);
+       ts_info->so_timestamping =
+               SOF_TIMESTAMPING_TX_HARDWARE |
+               SOF_TIMESTAMPING_RX_HARDWARE |
+               SOF_TIMESTAMPING_RAW_HARDWARE;
+       ts_info->tx_types =
+               BIT(HWTSTAMP_TX_ON) |
+               BIT(HWTSTAMP_TX_OFF) |
+               BIT(HWTSTAMP_TX_ONESTEP_SYNC) |
+               BIT(HWTSTAMP_TX_ONESTEP_P2P);
+       ts_info->rx_filters =
+               BIT(HWTSTAMP_FILTER_NONE) |
+               BIT(HWTSTAMP_FILTER_PTP_V2_EVENT);
+
+       return 0;
+}
+
+void bcm_ptp_stop(struct bcm_ptp_private *priv)
+{
+       ptp_cancel_worker_sync(priv->ptp_clock);
+       bcm_ptp_cancel_func(priv);
+}
+EXPORT_SYMBOL_GPL(bcm_ptp_stop);
+
+void bcm_ptp_config_init(struct phy_device *phydev)
+{
+       /* init network sync engine */
+       bcm_phy_write_exp(phydev, NSE_CTRL, NSE_GMODE_EN | NSE_INIT);
+
+       /* enable time sync (TX/RX SOP capture) */
+       bcm_phy_write_exp(phydev, TIME_SYNC, TIME_SYNC_EN);
+
+       /* use sec.nsec heartbeat capture */
+       bcm_phy_write_exp(phydev, DPLL_SELECT, DPLL_HB_MODE2);
+
+       /* use 64 bit timecode for TX */
+       bcm_phy_write_exp(phydev, TIMECODE_CTRL, TX_TIMECODE_SEL);
+
+       /* always allow FREQ_LOAD on framesync */
+       bcm_phy_write_exp(phydev, SHADOW_CTRL, FREQ_LOAD);
+
+       bcm_phy_write_exp(phydev, SYNC_IN_DIVIDER, 1);
+}
+EXPORT_SYMBOL_GPL(bcm_ptp_config_init);
+
+static void bcm_ptp_init(struct bcm_ptp_private *priv)
+{
+       priv->nse_ctrl = NSE_GMODE_EN;
+
+       mutex_init(&priv->mutex);
+       skb_queue_head_init(&priv->tx_queue);
+
+       priv->mii_ts.rxtstamp = bcm_ptp_rxtstamp;
+       priv->mii_ts.txtstamp = bcm_ptp_txtstamp;
+       priv->mii_ts.hwtstamp = bcm_ptp_hwtstamp;
+       priv->mii_ts.ts_info = bcm_ptp_ts_info;
+
+       priv->phydev->mii_ts = &priv->mii_ts;
+}
+
+struct bcm_ptp_private *bcm_ptp_probe(struct phy_device *phydev)
+{
+       struct bcm_ptp_private *priv;
+       struct ptp_clock *clock;
+
+       switch (BRCM_PHY_MODEL(phydev)) {
+       case PHY_ID_BCM54210E:
+#ifdef PHY_ID_BCM54213PE
+       case PHY_ID_BCM54213PE:
+#endif
+               break;
+       default:
+               return NULL;
+       }
+
+       priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL);
+       if (!priv)
+               return ERR_PTR(-ENOMEM);
+
+       priv->ptp_info = bcm_ptp_clock_info;
+
+       snprintf(priv->pin.name, sizeof(priv->pin.name), "SYNC_OUT");
+       priv->ptp_info.pin_config = &priv->pin;
+
+       clock = ptp_clock_register(&priv->ptp_info, &phydev->mdio.dev);
+       if (IS_ERR(clock))
+               return ERR_CAST(clock);
+       priv->ptp_clock = clock;
+
+       priv->phydev = phydev;
+       bcm_ptp_init(priv);
+
+       return priv;
+}
+EXPORT_SYMBOL_GPL(bcm_ptp_probe);
+
+MODULE_LICENSE("GPL");
index b330efb..3c4035d 100644 (file)
@@ -27,6 +27,11 @@ MODULE_DESCRIPTION("Broadcom PHY driver");
 MODULE_AUTHOR("Maciej W. Rozycki");
 MODULE_LICENSE("GPL");
 
+struct bcm54xx_phy_priv {
+       u64     *stats;
+       struct bcm_ptp_private *ptp;
+};
+
 static int bcm54xx_config_clock_delay(struct phy_device *phydev)
 {
        int rc, val;
@@ -83,6 +88,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;
@@ -254,7 +264,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);
@@ -313,9 +324,28 @@ static void bcm54xx_adjust_rxrefclk(struct phy_device *phydev)
                bcm_phy_write_shadow(phydev, BCM54XX_SHD_APD, val);
 }
 
+static void bcm54xx_ptp_stop(struct phy_device *phydev)
+{
+       struct bcm54xx_phy_priv *priv = phydev->priv;
+
+       if (priv->ptp)
+               bcm_ptp_stop(priv->ptp);
+}
+
+static void bcm54xx_ptp_config_init(struct phy_device *phydev)
+{
+       struct bcm54xx_phy_priv *priv = phydev->priv;
+
+       if (priv->ptp)
+               bcm_ptp_config_init(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)
@@ -353,6 +383,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;
@@ -371,10 +404,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.
@@ -385,18 +418,62 @@ static int bcm54xx_config_init(struct phy_device *phydev)
                bcm_phy_write_shadow(phydev, BCM5482_SHD_LEDS1, val);
 
                val = BCM_LED_MULTICOLOR_IN_PHASE |
-                       BCM5482_SHD_LEDS1_LED1(BCM_LED_MULTICOLOR_LINK_ACT) |
-                       BCM5482_SHD_LEDS1_LED3(BCM_LED_MULTICOLOR_LINK_ACT);
+                       BCM5482_SHD_LEDS1_LED1(led_modes[0]) |
+                       BCM5482_SHD_LEDS1_LED3(led_modes[1]);
                bcm_phy_write_exp(phydev, BCM_EXP_MULTICOLOR, val);
        }
 
+       bcm54xx_ptp_config_init(phydev);
+
        return 0;
 }
 
+static int bcm54xx_iddq_set(struct phy_device *phydev, bool enable)
+{
+       int ret = 0;
+
+       if (!(phydev->dev_flags & PHY_BRCM_IDDQ_SUSPEND))
+               return ret;
+
+       ret = bcm_phy_read_exp(phydev, BCM54XX_TOP_MISC_IDDQ_CTRL);
+       if (ret < 0)
+               goto out;
+
+       if (enable)
+               ret |= BCM54XX_TOP_MISC_IDDQ_SR | BCM54XX_TOP_MISC_IDDQ_LP;
+       else
+               ret &= ~(BCM54XX_TOP_MISC_IDDQ_SR | BCM54XX_TOP_MISC_IDDQ_LP);
+
+       ret = bcm_phy_write_exp(phydev, BCM54XX_TOP_MISC_IDDQ_CTRL, ret);
+out:
+       return ret;
+}
+
+static int bcm54xx_suspend(struct phy_device *phydev)
+{
+       int ret;
+
+       bcm54xx_ptp_stop(phydev);
+
+       /* We cannot use a read/modify/write here otherwise the PHY gets into
+        * a bad state where its LEDs keep flashing, thus defeating the purpose
+        * of low power mode.
+        */
+       ret = phy_write(phydev, MII_BMCR, BMCR_PDOWN);
+       if (ret < 0)
+               return ret;
+
+       return bcm54xx_iddq_set(phydev, true);
+}
+
 static int bcm54xx_resume(struct phy_device *phydev)
 {
        int ret;
 
+       ret = bcm54xx_iddq_set(phydev, false);
+       if (ret < 0)
+               return ret;
+
        /* Writes to register other than BMCR would be ignored
         * unless we clear the PDOWN bit first
         */
@@ -409,6 +486,15 @@ static int bcm54xx_resume(struct phy_device *phydev)
         */
        fsleep(40);
 
+       /* Issue a soft reset after clearing the power down bit
+        * and before doing any other configuration.
+        */
+       if (phydev->dev_flags & PHY_BRCM_IDDQ_SUSPEND) {
+               ret = genphy_soft_reset(phydev);
+               if (ret < 0)
+                       return ret;
+       }
+
        return bcm54xx_config_init(phydev);
 }
 
@@ -692,10 +778,6 @@ static irqreturn_t brcm_fet_handle_interrupt(struct phy_device *phydev)
        return IRQ_HANDLED;
 }
 
-struct bcm54xx_phy_priv {
-       u64     *stats;
-};
-
 static int bcm54xx_phy_probe(struct phy_device *phydev)
 {
        struct bcm54xx_phy_priv *priv;
@@ -712,6 +794,10 @@ static int bcm54xx_phy_probe(struct phy_device *phydev)
        if (!priv->stats)
                return -ENOMEM;
 
+       priv->ptp = bcm_ptp_probe(phydev);
+       if (IS_ERR(priv->ptp))
+               return PTR_ERR(priv->ptp);
+
        return 0;
 }
 
@@ -723,6 +809,36 @@ static void bcm54xx_get_stats(struct phy_device *phydev,
        bcm_phy_get_stats(phydev, priv->stats, stats, data);
 }
 
+static void bcm54xx_link_change_notify(struct phy_device *phydev)
+{
+       u16 mask = MII_BCM54XX_EXP_EXP08_EARLY_DAC_WAKE |
+                  MII_BCM54XX_EXP_EXP08_FORCE_DAC_WAKE;
+       int ret;
+
+       if (phydev->state != PHY_RUNNING)
+               return;
+
+       /* Don't change the DAC wake settings if auto power down
+        * is not requested.
+        */
+       if (!(phydev->dev_flags & PHY_BRCM_AUTO_PWRDWN_ENABLE))
+               return;
+
+       ret = bcm_phy_read_exp(phydev, MII_BCM54XX_EXP_EXP08);
+       if (ret < 0)
+               return;
+
+       /* Enable/disable 10BaseT auto and forced early DAC wake depending
+        * on the negotiated speed, those settings should only be done
+        * for 10Mbits/sec.
+        */
+       if (phydev->speed == SPEED_10)
+               ret |= mask;
+       else
+               ret &= ~mask;
+       bcm_phy_write_exp(phydev, MII_BCM54XX_EXP_EXP08, ret);
+}
+
 static struct phy_driver broadcom_drivers[] = {
 {
        .phy_id         = PHY_ID_BCM5411,
@@ -736,6 +852,7 @@ static struct phy_driver broadcom_drivers[] = {
        .config_init    = bcm54xx_config_init,
        .config_intr    = bcm_phy_config_intr,
        .handle_interrupt = bcm_phy_handle_interrupt,
+       .link_change_notify     = bcm54xx_link_change_notify,
 }, {
        .phy_id         = PHY_ID_BCM5421,
        .phy_id_mask    = 0xfffffff0,
@@ -748,9 +865,10 @@ static struct phy_driver broadcom_drivers[] = {
        .config_init    = bcm54xx_config_init,
        .config_intr    = bcm_phy_config_intr,
        .handle_interrupt = bcm_phy_handle_interrupt,
+       .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 */
        .get_sset_count = bcm_phy_get_sset_count,
@@ -760,6 +878,22 @@ static struct phy_driver broadcom_drivers[] = {
        .config_init    = bcm54xx_config_init,
        .config_intr    = bcm_phy_config_intr,
        .handle_interrupt = bcm_phy_handle_interrupt,
+       .link_change_notify     = bcm54xx_link_change_notify,
+       .suspend        = bcm54xx_suspend,
+       .resume         = bcm54xx_resume,
+}, {
+       .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,
@@ -772,6 +906,7 @@ static struct phy_driver broadcom_drivers[] = {
        .config_init    = bcm54xx_config_init,
        .config_intr    = bcm_phy_config_intr,
        .handle_interrupt = bcm_phy_handle_interrupt,
+       .link_change_notify     = bcm54xx_link_change_notify,
 }, {
        .phy_id         = PHY_ID_BCM54612E,
        .phy_id_mask    = 0xfffffff0,
@@ -784,6 +919,7 @@ static struct phy_driver broadcom_drivers[] = {
        .config_init    = bcm54xx_config_init,
        .config_intr    = bcm_phy_config_intr,
        .handle_interrupt = bcm_phy_handle_interrupt,
+       .link_change_notify     = bcm54xx_link_change_notify,
 }, {
        .phy_id         = PHY_ID_BCM54616S,
        .phy_id_mask    = 0xfffffff0,
@@ -796,6 +932,7 @@ static struct phy_driver broadcom_drivers[] = {
        .handle_interrupt = bcm_phy_handle_interrupt,
        .read_status    = bcm54616s_read_status,
        .probe          = bcm54616s_probe,
+       .link_change_notify     = bcm54xx_link_change_notify,
 }, {
        .phy_id         = PHY_ID_BCM5464,
        .phy_id_mask    = 0xfffffff0,
@@ -810,6 +947,7 @@ static struct phy_driver broadcom_drivers[] = {
        .handle_interrupt = bcm_phy_handle_interrupt,
        .suspend        = genphy_suspend,
        .resume         = genphy_resume,
+       .link_change_notify     = bcm54xx_link_change_notify,
 }, {
        .phy_id         = PHY_ID_BCM5481,
        .phy_id_mask    = 0xfffffff0,
@@ -823,6 +961,7 @@ static struct phy_driver broadcom_drivers[] = {
        .config_aneg    = bcm5481_config_aneg,
        .config_intr    = bcm_phy_config_intr,
        .handle_interrupt = bcm_phy_handle_interrupt,
+       .link_change_notify     = bcm54xx_link_change_notify,
 }, {
        .phy_id         = PHY_ID_BCM54810,
        .phy_id_mask    = 0xfffffff0,
@@ -836,8 +975,9 @@ static struct phy_driver broadcom_drivers[] = {
        .config_aneg    = bcm5481_config_aneg,
        .config_intr    = bcm_phy_config_intr,
        .handle_interrupt = bcm_phy_handle_interrupt,
-       .suspend        = genphy_suspend,
+       .suspend        = bcm54xx_suspend,
        .resume         = bcm54xx_resume,
+       .link_change_notify     = bcm54xx_link_change_notify,
 }, {
        .phy_id         = PHY_ID_BCM54811,
        .phy_id_mask    = 0xfffffff0,
@@ -851,8 +991,9 @@ static struct phy_driver broadcom_drivers[] = {
        .config_aneg    = bcm5481_config_aneg,
        .config_intr    = bcm_phy_config_intr,
        .handle_interrupt = bcm_phy_handle_interrupt,
-       .suspend        = genphy_suspend,
+       .suspend        = bcm54xx_suspend,
        .resume         = bcm54xx_resume,
+       .link_change_notify     = bcm54xx_link_change_notify,
 }, {
        .phy_id         = PHY_ID_BCM5482,
        .phy_id_mask    = 0xfffffff0,
@@ -865,6 +1006,7 @@ static struct phy_driver broadcom_drivers[] = {
        .config_init    = bcm54xx_config_init,
        .config_intr    = bcm_phy_config_intr,
        .handle_interrupt = bcm_phy_handle_interrupt,
+       .link_change_notify     = bcm54xx_link_change_notify,
 }, {
        .phy_id         = PHY_ID_BCM50610,
        .phy_id_mask    = 0xfffffff0,
@@ -877,6 +1019,9 @@ static struct phy_driver broadcom_drivers[] = {
        .config_init    = bcm54xx_config_init,
        .config_intr    = bcm_phy_config_intr,
        .handle_interrupt = bcm_phy_handle_interrupt,
+       .link_change_notify     = bcm54xx_link_change_notify,
+       .suspend        = bcm54xx_suspend,
+       .resume         = bcm54xx_resume,
 }, {
        .phy_id         = PHY_ID_BCM50610M,
        .phy_id_mask    = 0xfffffff0,
@@ -889,6 +1034,9 @@ static struct phy_driver broadcom_drivers[] = {
        .config_init    = bcm54xx_config_init,
        .config_intr    = bcm_phy_config_intr,
        .handle_interrupt = bcm_phy_handle_interrupt,
+       .link_change_notify     = bcm54xx_link_change_notify,
+       .suspend        = bcm54xx_suspend,
+       .resume         = bcm54xx_resume,
 }, {
        .phy_id         = PHY_ID_BCM57780,
        .phy_id_mask    = 0xfffffff0,
@@ -901,6 +1049,7 @@ static struct phy_driver broadcom_drivers[] = {
        .config_init    = bcm54xx_config_init,
        .config_intr    = bcm_phy_config_intr,
        .handle_interrupt = bcm_phy_handle_interrupt,
+       .link_change_notify     = bcm54xx_link_change_notify,
 }, {
        .phy_id         = PHY_ID_BCMAC131,
        .phy_id_mask    = 0xfffffff0,
@@ -927,6 +1076,7 @@ static struct phy_driver broadcom_drivers[] = {
        .get_strings    = bcm_phy_get_strings,
        .get_stats      = bcm54xx_get_stats,
        .probe          = bcm54xx_phy_probe,
+       .link_change_notify     = bcm54xx_link_change_notify,
 }, {
        .phy_id         = PHY_ID_BCM53125,
        .phy_id_mask    = 0xfffffff0,
@@ -940,6 +1090,7 @@ static struct phy_driver broadcom_drivers[] = {
        .config_init    = bcm54xx_config_init,
        .config_intr    = bcm_phy_config_intr,
        .handle_interrupt = bcm_phy_handle_interrupt,
+       .link_change_notify     = bcm54xx_link_change_notify,
 }, {
        .phy_id         = PHY_ID_BCM89610,
        .phy_id_mask    = 0xfffffff0,
@@ -952,6 +1103,7 @@ static struct phy_driver broadcom_drivers[] = {
        .config_init    = bcm54xx_config_init,
        .config_intr    = bcm_phy_config_intr,
        .handle_interrupt = bcm_phy_handle_interrupt,
+       .link_change_notify     = bcm54xx_link_change_notify,
 } };
 
 module_phy_driver(broadcom_drivers);
@@ -959,7 +1111,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 },
index 9f1f2b6..4703a98 100644 (file)
@@ -233,6 +233,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);
@@ -262,6 +263,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);
index d8cac02..92fa774 100644 (file)
@@ -218,6 +218,8 @@ static int lan87xx_read_status(struct phy_device *phydev)
        int err = genphy_read_status(phydev);
 
        if (!phydev->link && priv->energy_enable) {
+               int energy_detected;
+
                /* Disable EDPD to wake up PHY */
                int rc = phy_read(phydev, MII_LAN83C185_CTRL_STATUS);
                if (rc < 0)
@@ -228,12 +230,12 @@ static int lan87xx_read_status(struct phy_device *phydev)
                if (rc < 0)
                        return rc;
 
-               /* Wait max 640 ms to detect energy and the timeout is not
+               /* Wait max 1500 ms to detect energy and the timeout is not
                 * an actual error.
                 */
                read_poll_timeout(phy_read, rc,
                                  rc & MII_LAN83C185_ENERGYON || rc < 0,
-                                 10000, 640000, true, phydev,
+                                 150000, 1500000, true, phydev,
                                  MII_LAN83C185_CTRL_STATUS);
                if (rc < 0)
                        return rc;
@@ -243,10 +245,16 @@ static int lan87xx_read_status(struct phy_device *phydev)
                if (rc < 0)
                        return rc;
 
+               energy_detected = !!(rc & MII_LAN83C185_ENERGYON);
+
                rc = phy_write(phydev, MII_LAN83C185_CTRL_STATUS,
                               rc | MII_LAN83C185_EDPWRDOWN);
                if (rc < 0)
                        return rc;
+
+               /* Save CPU and power by deferring the next poll */
+               if (!energy_detected)
+                       msleep(2000);
        }
 
        return err;
index 3e1a83a..2ccda40 100644 (file)
@@ -445,6 +445,20 @@ static int msg_level = -1;
 module_param(msg_level, int, 0);
 MODULE_PARM_DESC(msg_level, "Override default message level");
 
+/* 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;
@@ -1260,6 +1274,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;
@@ -1771,6 +1788,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)
@@ -2268,6 +2286,22 @@ 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);
 
+       if (of_property_read_bool(phydev->mdio.dev.of_node,
+                                 "microchip,eee-enabled")) {
+               struct ethtool_eee edata;
+               memset(&edata, 0, sizeof(edata));
+               edata.cmd = ETHTOOL_SEEE;
+               edata.advertised = ADVERTISED_1000baseT_Full |
+                                  ADVERTISED_100baseT_Full;
+               edata.eee_enabled = true;
+               edata.tx_lpi_enabled = true;
+               if (of_property_read_u32(dev->udev->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);
+       }
+
        if (phydev->mdio.dev.of_node) {
                u32 reg;
                int len;
@@ -2716,6 +2750,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)
@@ -2797,6 +2836,10 @@ static int lan78xx_reset(struct lan78xx_net *dev)
 
        buf |= HW_CFG_MEF_;
 
+       /* 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;
@@ -2895,6 +2938,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;
@@ -3260,8 +3306,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;
@@ -3479,7 +3531,7 @@ static int rx_submit(struct lan78xx_net *dev, struct urb *urb, gfp_t flags)
        size_t size = dev->rx_urb_size;
        int ret = 0;
 
-       skb = netdev_alloc_skb_ip_align(dev->net, size);
+       skb = netdev_alloc_skb(dev->net, size);
        if (!skb) {
                usb_free_urb(urb);
                return -ENOMEM;
@@ -4105,7 +4157,13 @@ static int lan78xx_probe(struct usb_interface *intf,
        netdev->max_mtu = MAX_SINGLE_PACKET_SIZE;
        netif_set_gso_max_size(netdev, MAX_SINGLE_PACKET_SIZE - MAX_HEADER);
 
-       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, 0);
        buf = kmalloc(maxp, GFP_KERNEL);
        if (buf) {
index 4e39e43..64ad4f1 100644 (file)
@@ -50,6 +50,7 @@
 #define SUSPEND_SUSPEND3               (0x08)
 #define SUSPEND_ALLMODES               (SUSPEND_SUSPEND0 | SUSPEND_SUSPEND1 | \
                                         SUSPEND_SUSPEND2 | SUSPEND_SUSPEND3)
+#define MAC_ADDR_LEN                    (6)
 
 struct smsc95xx_priv {
        u32 mac_cr;
@@ -67,6 +68,18 @@ static bool turbo_mode = true;
 module_param(turbo_mode, bool, 0644);
 MODULE_PARM_DESC(turbo_mode, "Enable multiple frames per Rx transaction");
 
+static bool truesize_mode = false;
+module_param(truesize_mode, bool, 0644);
+MODULE_PARM_DESC(truesize_mode, "Report larger truesize value");
+
+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, int in_pm)
 {
@@ -765,6 +778,53 @@ 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_is_macaddr_param(struct usbnet *dev, u8 *dev_mac)
+{
+       int i, j, got_num, num;
+       u8 mtbl[MAC_ADDR_LEN];
+
+       if (macaddr[0] == ':')
+               return 0;
+
+       i = 0;
+       j = 0;
+       num = 0;
+       got_num = 0;
+       while (j < MAC_ADDR_LEN) {
+               if (macaddr[i] && macaddr[i] != ':') {
+                       got_num++;
+                       if ('0' <= macaddr[i] && macaddr[i] <= '9')
+                               num = num * 16 + macaddr[i] - '0';
+                       else if ('A' <= macaddr[i] && macaddr[i] <= 'F')
+                               num = num * 16 + 10 + macaddr[i] - 'A';
+                       else if ('a' <= macaddr[i] && macaddr[i] <= 'f')
+                               num = num * 16 + 10 + macaddr[i] - 'a';
+                       else
+                               break;
+                       i++;
+               } else if (got_num == 2) {
+                       mtbl[j++] = (u8) num;
+                       num = 0;
+                       got_num = 0;
+                       i++;
+               } else {
+                       break;
+               }
+       }
+
+       if (j == MAC_ADDR_LEN) {
+               netif_dbg(dev, ifup, dev->net, "Overriding MAC address with: "
+               "%02x:%02x:%02x:%02x:%02x:%02x\n", mtbl[0], mtbl[1], mtbl[2],
+                                               mtbl[3], mtbl[4], mtbl[5]);
+               for (i = 0; i < MAC_ADDR_LEN; i++)
+                       dev_mac[i] = mtbl[i];
+               return 1;
+       } else {
+               return 0;
+       }
+}
+
 static void smsc95xx_init_mac_address(struct usbnet *dev)
 {
        /* maybe the boot loader passed the MAC address in devicetree */
@@ -787,6 +847,10 @@ static void smsc95xx_init_mac_address(struct usbnet *dev)
                }
        }
 
+       /* Check module parameters */
+       if (smsc95xx_is_macaddr_param(dev, dev->net->dev_addr))
+               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");
@@ -913,13 +977,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",
@@ -1838,7 +1902,8 @@ static int smsc95xx_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
                                if (dev->net->features & NETIF_F_RXCSUM)
                                        smsc95xx_rx_csum_offload(skb);
                                skb_trim(skb, skb->len - 4); /* remove fcs */
-                               skb->truesize = size + sizeof(struct sk_buff);
+                               if (truesize_mode)
+                                       skb->truesize = size + sizeof(struct sk_buff);
 
                                return 1;
                        }
@@ -1856,7 +1921,8 @@ static int smsc95xx_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
                        if (dev->net->features & NETIF_F_RXCSUM)
                                smsc95xx_rx_csum_offload(ax_skb);
                        skb_trim(ax_skb, ax_skb->len - 4); /* remove fcs */
-                       ax_skb->truesize = size + sizeof(struct sk_buff);
+                       if (truesize_mode)
+                               ax_skb->truesize = size + sizeof(struct sk_buff);
 
                        usbnet_skb_return(dev, ax_skb);
                }
index 3f5da3b..ba3c58c 100644 (file)
@@ -78,7 +78,7 @@ struct brcmf_bus_ops {
        size_t (*get_ramsize)(struct device *dev);
        int (*get_memdump)(struct device *dev, void *data, size_t len);
        int (*get_fwname)(struct device *dev, const char *ext,
-                         unsigned char *fw_name);
+                         unsigned char *fw_name, bool board_specific);
        void (*debugfs_create)(struct device *dev);
        int (*reset)(struct device *dev);
 };
@@ -223,7 +223,14 @@ static inline
 int brcmf_bus_get_fwname(struct brcmf_bus *bus, const char *ext,
                         unsigned char *fw_name)
 {
-       return bus->ops->get_fwname(bus->dev, ext, fw_name);
+       return bus->ops->get_fwname(bus->dev, ext, fw_name, false);
+}
+
+static inline
+int brcmf_bus_get_board_fwname(struct brcmf_bus *bus, const char *ext,
+                              unsigned char *fw_name)
+{
+       return bus->ops->get_fwname(bus->dev, ext, fw_name, true);
 }
 
 static inline
index 9db12ff..94ab63d 100644 (file)
@@ -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>
@@ -2953,7 +2954,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) {
@@ -2963,6 +2964,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)
@@ -7463,18 +7465,23 @@ static s32 brcmf_translate_country_code(struct brcmf_pub *drvr, char alpha2[2],
        s32 found_index;
        int i;
 
-       country_codes = drvr->settings->country_codes;
-       if (!country_codes) {
-               brcmf_dbg(TRACE, "No country codes configured for device\n");
-               return -EINVAL;
-       }
-
        if ((alpha2[0] == ccreq->country_abbrev[0]) &&
            (alpha2[1] == ccreq->country_abbrev[1])) {
                brcmf_dbg(TRACE, "Country code already set\n");
                return -EAGAIN;
        }
 
+       country_codes = drvr->settings->country_codes;
+       if (!country_codes) {
+               brcmf_dbg(TRACE, "No country codes configured for device, using ISO3166 code and 0 rev\n");
+               memset(ccreq, 0, sizeof(*ccreq));
+               ccreq->country_abbrev[0] = alpha2[0];
+               ccreq->country_abbrev[1] = alpha2[1];
+               ccreq->ccode[0] = alpha2[0];
+               ccreq->ccode[1] = alpha2[1];
+               return 0;
+       }
+
        found_index = -1;
        for (i = 0; i < country_codes->table_size; i++) {
                cc = &country_codes->table[i];
@@ -7508,31 +7515,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;
 
index e3758bd..9047a08 100644 (file)
@@ -134,13 +134,23 @@ static int brcmf_c_process_clm_blob(struct brcmf_if *ifp)
        brcmf_dbg(TRACE, "Enter\n");
 
        memset(clm_name, 0, sizeof(clm_name));
-       err = brcmf_bus_get_fwname(bus, ".clm_blob", clm_name);
+       err = brcmf_bus_get_board_fwname(bus, ".clm_blob", clm_name);
        if (err) {
                bphy_err(drvr, "get CLM blob file name failed (%d)\n", err);
                return err;
        }
 
-       err = firmware_request_nowarn(&clm, clm_name, bus->dev);
+       if (clm_name[0])
+               err = firmware_request_nowarn(&clm, clm_name, bus->dev);
+       if (err || !clm_name[0]) {
+               err = brcmf_bus_get_fwname(bus, ".clm_blob", clm_name);
+               if (err) {
+                       bphy_err(drvr, "get CLM blob file name failed (%d)\n", err);
+                       return err;
+               }
+
+               err = firmware_request_nowarn(&clm, clm_name, bus->dev);
+       }
        if (err) {
                brcmf_info("no clm_blob available (err=%d), device may have limited channels available\n",
                           err);
index 9bb5f70..ca49700 100644 (file)
@@ -67,7 +67,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, ...);
index dcbe55b..c549720 100644 (file)
@@ -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"
@@ -30,6 +31,8 @@ enum nvram_parser_state {
        END
 };
 
+char saved_ccode[2] = {};
+
 /**
  * struct nvram_parser - internal info for parser.
  *
@@ -543,10 +546,26 @@ 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);
+       }
 
        if (free_bcm47xx_nvram)
                bcm47xx_nvram_release_contents(data);
@@ -628,7 +647,7 @@ static int brcmf_fw_request_firmware(const struct firmware **fw,
                if (!alt_path)
                        goto fallback;
 
-               ret = request_firmware(fw, alt_path, fwctx->dev);
+               ret = firmware_request_nowarn(fw, alt_path, fwctx->dev);
                kfree(alt_path);
                if (ret == 0)
                        return ret;
index 2f7bc3a..c2d6b8a 100644 (file)
@@ -10,6 +10,7 @@
 #include "debug.h"
 #include "core.h"
 #include "common.h"
+#include "firmware.h"
 #include "of.h"
 
 static int brcmf_of_get_country_codes(struct device *dev,
@@ -118,3 +119,38 @@ void brcmf_of_probe(struct device *dev, enum brcmf_bus_type bus_type,
        sdio->oob_irq_nr = irq;
        sdio->oob_irq_flags = irqf;
 }
+
+struct brcmf_firmware_mapping *
+brcmf_of_fwnames(struct device *dev, u32 *fwname_count)
+{
+       struct device_node *np = dev->of_node;
+       struct brcmf_firmware_mapping *fwnames;
+       struct device_node *map_np, *fw_np;
+       int of_count;
+       int count = 0;
+
+       map_np = of_get_child_by_name(np, "firmwares");
+       of_count = of_get_child_count(map_np);
+       if (!of_count)
+               return NULL;
+
+       fwnames = devm_kcalloc(dev, of_count,
+                              sizeof(struct brcmf_firmware_mapping),
+                              GFP_KERNEL);
+
+       for_each_child_of_node(map_np, fw_np)
+       {
+               struct brcmf_firmware_mapping *cur = &fwnames[count];
+
+               if (of_property_read_u32(fw_np, "chipid", &cur->chipid) ||
+                   of_property_read_u32(fw_np, "revmask", &cur->revmask))
+                       continue;
+               cur->fw_base = of_get_property(fw_np, "fw_base", NULL);
+               if (cur->fw_base)
+                       count++;
+       }
+
+       *fwname_count = count;
+
+       return count ? fwnames : NULL;
+}
index 10bf522..5b39a39 100644 (file)
@@ -5,9 +5,16 @@
 #ifdef CONFIG_OF
 void brcmf_of_probe(struct device *dev, enum brcmf_bus_type bus_type,
                    struct brcmf_mp_device *settings);
+struct brcmf_firmware_mapping *
+brcmf_of_fwnames(struct device *dev, u32 *map_count);
 #else
 static void brcmf_of_probe(struct device *dev, enum brcmf_bus_type bus_type,
                           struct brcmf_mp_device *settings)
 {
 }
+static struct brcmf_firmware_mapping *
+brcmf_of_fwnames(struct device *dev, u32 *map_count)
+{
+       return NULL;
+}
 #endif /* CONFIG_OF */
index 3ff4997..d9c45c4 100644 (file)
@@ -1381,7 +1381,8 @@ static int brcmf_pcie_get_memdump(struct device *dev, void *data, size_t len)
 }
 
 static
-int brcmf_pcie_get_fwname(struct device *dev, const char *ext, u8 *fw_name)
+int brcmf_pcie_get_fwname(struct device *dev, const char *ext, u8 *fw_name,
+                         bool board_specific)
 {
        struct brcmf_bus *bus_if = dev_get_drvdata(dev);
        struct brcmf_fw_request *fwreq;
@@ -1389,6 +1390,10 @@ int brcmf_pcie_get_fwname(struct device *dev, const char *ext, u8 *fw_name)
                { ext, fw_name },
        };
 
+       if (board_specific) {
+               fw_name[0] = 0;
+               return 0;
+       }
        fwreq = brcmf_fw_alloc_request(bus_if->chip, bus_if->chiprev,
                                       brcmf_pcie_fwnames,
                                       ARRAY_SIZE(brcmf_pcie_fwnames),
index f7961b2..67b0ad9 100644 (file)
@@ -35,6 +35,7 @@
 #include "core.h"
 #include "common.h"
 #include "bcdc.h"
+#include "of.h"
 
 #define DCMD_RESP_TIMEOUT      msecs_to_jiffies(2500)
 #define CTL_DONE_TIMEOUT       msecs_to_jiffies(2500)
@@ -611,6 +612,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");
@@ -633,7 +635,7 @@ MODULE_FIRMWARE(BRCMF_FW_DEFAULT_PATH "brcmfmac*-sdio.*.txt");
 /* per-board firmware binaries */
 MODULE_FIRMWARE(BRCMF_FW_DEFAULT_PATH "brcmfmac*-sdio.*.bin");
 
-static const struct brcmf_firmware_mapping brcmf_sdio_fwnames[] = {
+static const struct brcmf_firmware_mapping sdio_fwnames[] = {
        BRCMF_FW_ENTRY(BRCM_CC_43143_CHIP_ID, 0xFFFFFFFF, 43143),
        BRCMF_FW_ENTRY(BRCM_CC_43241_CHIP_ID, 0x0000001F, 43241B0),
        BRCMF_FW_ENTRY(BRCM_CC_43241_CHIP_ID, 0x00000020, 43241B4),
@@ -642,7 +644,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),
@@ -659,6 +661,9 @@ static const struct brcmf_firmware_mapping brcmf_sdio_fwnames[] = {
        BRCMF_FW_ENTRY(CY_CC_43752_CHIP_ID, 0xFFFFFFFF, 43752)
 };
 
+static const struct brcmf_firmware_mapping *brcmf_sdio_fwnames;
+static u32 brcmf_sdio_fwnames_count;
+
 #define TXCTL_CREDITS  2
 
 static void pkt_align(struct sk_buff *p, int len, int align)
@@ -4130,17 +4135,27 @@ brcmf_sdio_watchdog(struct timer_list *t)
 }
 
 static
-int brcmf_sdio_get_fwname(struct device *dev, const char *ext, u8 *fw_name)
+int brcmf_sdio_get_fwname(struct device *dev, const char *ext, u8 *fw_name,
+                         bool board_specific)
 {
        struct brcmf_bus *bus_if = dev_get_drvdata(dev);
+       struct brcmf_sdio_dev *sdiodev = bus_if->bus_priv.sdio;
        struct brcmf_fw_request *fwreq;
+       u8 board_ext[BRCMF_FW_NAME_LEN];
        struct brcmf_fw_name fwnames[] = {
                { ext, fw_name },
        };
 
+       if (board_specific) {
+               strlcpy(board_ext, ".", BRCMF_FW_NAME_LEN);
+               strlcat(board_ext, sdiodev->settings->board_type,
+                       BRCMF_FW_NAME_LEN);
+               strlcat(board_ext, ext, BRCMF_FW_NAME_LEN);
+               fwnames[0].extension = board_ext;
+       }
        fwreq = brcmf_fw_alloc_request(bus_if->chip, bus_if->chiprev,
                                       brcmf_sdio_fwnames,
-                                      ARRAY_SIZE(brcmf_sdio_fwnames),
+                                      brcmf_sdio_fwnames_count,
                                       fwnames, ARRAY_SIZE(fwnames));
        if (!fwreq)
                return -ENOMEM;
@@ -4196,6 +4211,9 @@ static const struct brcmf_bus_ops brcmf_sdio_bus_ops = {
 #define BRCMF_SDIO_FW_CODE     0
 #define BRCMF_SDIO_FW_NVRAM    1
 
+static struct brcmf_fw_request *
+brcmf_sdio_prepare_fw_request(struct brcmf_sdio *bus);
+
 static void brcmf_sdio_firmware_callback(struct device *dev, int err,
                                         struct brcmf_fw_request *fwreq)
 {
@@ -4211,6 +4229,22 @@ static void brcmf_sdio_firmware_callback(struct device *dev, int err,
 
        brcmf_dbg(TRACE, "Enter: dev=%s, err=%d\n", dev_name(dev), err);
 
+       if (err && brcmf_sdio_fwnames != sdio_fwnames) {
+               /* Try again with the standard firmware names */
+               brcmf_sdio_fwnames = sdio_fwnames;
+               brcmf_sdio_fwnames_count = ARRAY_SIZE(sdio_fwnames);
+               kfree(fwreq);
+               fwreq = brcmf_sdio_prepare_fw_request(bus);
+               if (!fwreq) {
+                       err = -ENOMEM;
+                       goto fail;
+               }
+               err = brcmf_fw_get_firmwares(dev, fwreq,
+                                            brcmf_sdio_firmware_callback);
+               if (!err)
+                       return;
+       }
+
        if (err)
                goto fail;
 
@@ -4419,7 +4453,7 @@ brcmf_sdio_prepare_fw_request(struct brcmf_sdio *bus)
 
        fwreq = brcmf_fw_alloc_request(bus->ci->chip, bus->ci->chiprev,
                                       brcmf_sdio_fwnames,
-                                      ARRAY_SIZE(brcmf_sdio_fwnames),
+                                      brcmf_sdio_fwnames_count,
                                       fwnames, ARRAY_SIZE(fwnames));
        if (!fwreq)
                return NULL;
@@ -4437,6 +4471,9 @@ struct brcmf_sdio *brcmf_sdio_probe(struct brcmf_sdio_dev *sdiodev)
        struct brcmf_sdio *bus;
        struct workqueue_struct *wq;
        struct brcmf_fw_request *fwreq;
+       struct brcmf_firmware_mapping *of_fwnames, *fwnames = NULL;
+       const int fwname_size = sizeof(struct brcmf_firmware_mapping);
+       u32 of_fw_count;
 
        brcmf_dbg(TRACE, "Enter\n");
 
@@ -4519,6 +4556,23 @@ struct brcmf_sdio *brcmf_sdio_probe(struct brcmf_sdio_dev *sdiodev)
 
        brcmf_dbg(INFO, "completed!!\n");
 
+       brcmf_sdio_fwnames = sdio_fwnames;
+       brcmf_sdio_fwnames_count = ARRAY_SIZE(sdio_fwnames);
+       of_fwnames = brcmf_of_fwnames(sdiodev->dev, &of_fw_count);
+       if (of_fwnames)
+               fwnames = devm_kcalloc(sdiodev->dev,
+                                      of_fw_count + brcmf_sdio_fwnames_count,
+                                      fwname_size, GFP_KERNEL);
+
+       if (fwnames) {
+               /* The array is scanned in order, so overrides come first */
+               memcpy(fwnames, of_fwnames, of_fw_count * fwname_size);
+               memcpy(fwnames + of_fw_count, sdio_fwnames,
+                      brcmf_sdio_fwnames_count * fwname_size);
+               brcmf_sdio_fwnames = fwnames;
+               brcmf_sdio_fwnames_count += of_fw_count;
+       }
+
        fwreq = brcmf_sdio_prepare_fw_request(bus);
        if (!fwreq) {
                ret = -ENOMEM;
index 9fb68c2..9bf2dbd 100644 (file)
@@ -1155,7 +1155,8 @@ error:
 }
 
 static
-int brcmf_usb_get_fwname(struct device *dev, const char *ext, u8 *fw_name)
+int brcmf_usb_get_fwname(struct device *dev, const char *ext, u8 *fw_name,
+                        bool board_specific)
 {
        struct brcmf_bus *bus = dev_get_drvdata(dev);
        struct brcmf_fw_request *fwreq;
@@ -1163,6 +1164,10 @@ int brcmf_usb_get_fwname(struct device *dev, const char *ext, u8 *fw_name)
                { ext, fw_name },
        };
 
+       if (board_specific) {
+               fw_name[0] = 0;
+               return 0;
+       }
        fwreq = brcmf_fw_alloc_request(bus->chip, bus->chiprev,
                                       brcmf_usb_fwnames,
                                       ARRAY_SIZE(brcmf_usb_fwnames),
index 80b5fd4..14c2ce9 100644 (file)
@@ -94,4 +94,11 @@ 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
index e0360a4..90c92ce 100644 (file)
@@ -1,6 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0
 obj-y = base.o device.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
 obj-$(CONFIG_OF_EARLY_FLATTREE) += fdt_address.o
diff --git a/drivers/of/configfs.c b/drivers/of/configfs.c
new file mode 100644 (file)
index 0000000..ac04301
--- /dev/null
@@ -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);
+       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,
+};
+
+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;
+}
+
+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);
+       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);
index d118712..be471ef 100644 (file)
@@ -243,6 +243,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)
index cc30215..44f0ea6 100644 (file)
                PCIE_MISC_CPU_2_PCIE_MEM_WIN0_LIMIT_HI + ((win) * 8)
 
 #define PCIE_MISC_HARD_PCIE_HARD_DEBUG                                 0x4204
-#define  PCIE_MISC_HARD_PCIE_HARD_DEBUG_CLKREQ_DEBUG_ENABLE_MASK       0x2
-#define  PCIE_MISC_HARD_PCIE_HARD_DEBUG_SERDES_IDDQ_MASK               0x08000000
+#define  PCIE_MISC_HARD_PCIE_HARD_DEBUG_CLKREQ_DEBUG_ENABLE_MASK       BIT(1)
+#define  PCIE_MISC_HARD_PCIE_HARD_DEBUG_CLKREQ_L1SS_ENABLE_MASK                BIT(21)
+#define  PCIE_MISC_HARD_PCIE_HARD_DEBUG_SERDES_IDDQ_MASK               BIT(27)
 
 
 #define PCIE_INTR2_CPU_BASE            0x4300
@@ -283,6 +284,7 @@ struct brcm_pcie {
        struct clk              *clk;
        struct device_node      *np;
        bool                    ssc;
+       bool                    l1ss;
        int                     gen;
        u64                     msi_target_addr;
        struct brcm_msi         *msi;
@@ -469,7 +471,8 @@ static struct irq_chip brcm_msi_irq_chip = {
 
 static struct msi_domain_info brcm_msi_domain_info = {
        /* Multi MSI is supported by the controller, but not by this driver */
-       .flags  = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS),
+       .flags  = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS |
+                  MSI_FLAG_PCI_MSIX),
        .chip   = &brcm_msi_irq_chip,
 };
 
@@ -877,6 +880,8 @@ static int brcm_pcie_setup(struct brcm_pcie *pcie)
 
        /* Reset the bridge */
        pcie->bridge_sw_init_set(pcie, 1);
+       pcie->perst_set(pcie, 1);
+
        usleep_range(100, 200);
 
        /* Take the bridge out of reset */
@@ -1033,12 +1038,25 @@ static int brcm_pcie_setup(struct brcm_pcie *pcie)
                PCIE_RC_CFG_VENDOR_VENDOR_SPECIFIC_REG1_ENDIAN_MODE_BAR2_MASK);
        writel(tmp, base + PCIE_RC_CFG_VENDOR_VENDOR_SPECIFIC_REG1);
 
-       /*
-        * Refclk from RC should be gated with CLKREQ# input when ASPM L0s,L1
-        * is enabled => setting the CLKREQ_DEBUG_ENABLE field to 1.
-        */
        tmp = readl(base + PCIE_MISC_HARD_PCIE_HARD_DEBUG);
-       tmp |= PCIE_MISC_HARD_PCIE_HARD_DEBUG_CLKREQ_DEBUG_ENABLE_MASK;
+       if (pcie->l1ss) {
+               /*
+                * Enable CLKREQ# signalling include L1 Substate control of
+                * the CLKREQ# signal and the external reference clock buffer.
+                * meet requirement for Endpoints that require CLKREQ#
+                * assertion to clock active within 400ns.
+                */
+               tmp &= ~PCIE_MISC_HARD_PCIE_HARD_DEBUG_CLKREQ_DEBUG_ENABLE_MASK;
+               tmp |= PCIE_MISC_HARD_PCIE_HARD_DEBUG_CLKREQ_L1SS_ENABLE_MASK;
+       } else {
+               /*
+                * Refclk from RC should be gated with CLKREQ# input when
+                * ASPM L0s,L1 is enabled => setting the CLKREQ_DEBUG_ENABLE
+                * field to 1.
+                */
+               tmp &= ~PCIE_MISC_HARD_PCIE_HARD_DEBUG_CLKREQ_L1SS_ENABLE_MASK;
+               tmp |= PCIE_MISC_HARD_PCIE_HARD_DEBUG_CLKREQ_DEBUG_ENABLE_MASK;
+       }
        writel(tmp, base + PCIE_MISC_HARD_PCIE_HARD_DEBUG);
 
        return 0;
@@ -1267,6 +1285,7 @@ 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");
 
        ret = clk_prepare_enable(pcie->clk);
        if (ret) {
index 77522e5..25b7423 100644 (file)
@@ -137,6 +137,14 @@ config ARM_DMC620_PMU
          Support for PMU events monitoring on the ARM DMC-620 memory
          controller.
 
+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"
 
 endmenu
index 5260b11..7ca0b48 100644 (file)
@@ -14,3 +14,4 @@ obj-$(CONFIG_THUNDERX2_PMU) += thunderx2_pmu.o
 obj-$(CONFIG_XGENE_PMU) += xgene_pmu.o
 obj-$(CONFIG_ARM_SPE_PMU) += arm_spe_pmu.o
 obj-$(CONFIG_ARM_DMC620_PMU) += arm_dmc620_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 (file)
index 0000000..5ae2bda
--- /dev/null
@@ -0,0 +1,637 @@
+/*
+ * 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 9
+
+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;
+               };
+       };
+};
+
+
+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;
+
+       /* 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);
+
+/* 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;
+
+const static 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"
+};
+
+const int num_bus_filters = ARRAY_SIZE(bus_filter_strings);
+
+const static 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"
+};
+
+const int num_system_buses = ARRAY_SIZE(system_bus_string);
+
+const static 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"
+};
+
+const int num_vpu_buses = ARRAY_SIZE(vpu_bus_string);
+
+const static 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 9 results, plus the overheads of start address and
+                * length So 11 u32 to define
+                */
+               u32 tmp[11];
+               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);
+       }
+
+       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]);
+
+       if (idx < 0 || idx > NUM_MONITORS)
+               idx = 0;
+
+       num_buses = idx == SYSTEM_MONITOR ? num_system_buses : 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, num_bus_filters);
+
+               cnt = snprintf(p, buff_size,
+                              "\nMonitoring transactions from %s only\n",
+                              bus_filter_strings[filt]);
+               p += cnt;
+               buff_size -= cnt;
+       }
+
+       cnt = snprintf(p, buff_size, "     Bus   |    Atrans    Atwait      AMax    Wtrans    Wtwait      WMax    Rtrans    Rtwait      RMax\n"
+                                    "======================================================================================================\n");
+
+       if (cnt >= buff_size)
+               goto done;
+
+       p += cnt;
+       buff_size -= cnt;
+
+       for (i = 0; i < num_buses; i++) {
+               if (mon->bus_enabled & (1 << i)) {
+#define DIVIDER (1024)
+                       typeof(mon->results[0]) *res = &(mon->results[i]);
+
+                       cnt = snprintf(p, buff_size,
+                                       "%10s | %8uK %8uK %8uK %8uK %8uK %8uK %8uK %8uK %8uK\n",
+                                       idx == SYSTEM_MONITOR ?
+                                               system_bus_string[i] :
+                                               vpu_bus_string[i],
+                                       res->atrans/DIVIDER,
+                                       res->atwait/DIVIDER,
+                                       res->amax/DIVIDER,
+                                       res->wtrans/DIVIDER,
+                                       res->wtwait/DIVIDER,
+                                       res->wmax/DIVIDER,
+                                       res->rtrans/DIVIDER,
+                                       res->rtwait/DIVIDER,
+                                       res->rmax/DIVIDER
+                                       );
+                       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;
+
+       /* 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 int rpi_axiperf_remove(struct platform_device *dev)
+{
+       int ret = 0;
+
+       kthread_stop(state->monitor_thread);
+
+       debugfs_remove_recursive(state->root_folder);
+       state->root_folder = NULL;
+
+       return ret;
+}
+
+static const struct of_device_id rpi_axiperf_match[] = {
+       {
+               .compatible = "brcm,bcm2835-axiperf",
+       },
+       {},
+};
+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");
+
index a293899..822e37f 100644 (file)
@@ -378,7 +378,7 @@ static const struct gpio_chip bcm2835_gpio_chip = {
        .get = bcm2835_gpio_get,
        .set = bcm2835_gpio_set,
        .set_config = gpiochip_generic_config,
-       .base = -1,
+       .base = 0,
        .ngpio = BCM2835_NUM_GPIOS,
        .can_sleep = false,
        .of_gpio_ranges_fallback = bcm2835_of_gpio_ranges_fallback,
@@ -395,7 +395,7 @@ static const struct gpio_chip bcm2711_gpio_chip = {
        .get = bcm2835_gpio_get,
        .set = bcm2835_gpio_set,
        .set_config = gpiochip_generic_config,
-       .base = -1,
+       .base = 0,
        .ngpio = BCM2711_NUM_GPIOS,
        .can_sleep = false,
        .of_gpio_ranges_fallback = bcm2835_of_gpio_ranges_fallback,
@@ -500,7 +500,7 @@ static void bcm2835_gpio_irq_config(struct bcm2835_pinctrl *pc,
        }
 }
 
-static void bcm2835_gpio_irq_enable(struct irq_data *data)
+static void bcm2835_gpio_irq_unmask(struct irq_data *data)
 {
        struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
        struct bcm2835_pinctrl *pc = gpiochip_get_data(chip);
@@ -515,7 +515,7 @@ static void bcm2835_gpio_irq_enable(struct irq_data *data)
        raw_spin_unlock_irqrestore(&pc->irq_lock[bank], flags);
 }
 
-static void bcm2835_gpio_irq_disable(struct irq_data *data)
+static void bcm2835_gpio_irq_mask(struct irq_data *data)
 {
        struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
        struct bcm2835_pinctrl *pc = gpiochip_get_data(chip);
@@ -688,12 +688,10 @@ static int bcm2835_gpio_irq_set_wake(struct irq_data *data, unsigned int on)
 
 static struct irq_chip bcm2835_gpio_irq_chip = {
        .name = MODULE_NAME,
-       .irq_enable = bcm2835_gpio_irq_enable,
-       .irq_disable = bcm2835_gpio_irq_disable,
        .irq_set_type = bcm2835_gpio_irq_set_type,
        .irq_ack = bcm2835_gpio_irq_ack,
-       .irq_mask = bcm2835_gpio_irq_disable,
-       .irq_unmask = bcm2835_gpio_irq_enable,
+       .irq_mask = bcm2835_gpio_irq_mask,
+       .irq_unmask = bcm2835_gpio_irq_unmask,
        .irq_set_wake = bcm2835_gpio_irq_set_wake,
        .flags = IRQCHIP_MASK_ON_SUSPEND,
 };
@@ -910,9 +908,12 @@ static int bcm2835_pmx_free(struct pinctrl_dev *pctldev,
                unsigned offset)
 {
        struct bcm2835_pinctrl *pc = pinctrl_dev_get_drvdata(pctldev);
+       enum bcm2835_fsel fsel = bcm2835_pinctrl_fsel_get(pc, offset);
+
+       /* Return non-GPIOs to GPIO_IN */
+       if (fsel != BCM2835_FSEL_GPIO_IN && fsel != BCM2835_FSEL_GPIO_OUT)
+               bcm2835_pinctrl_fsel_set(pc, offset, BCM2835_FSEL_GPIO_IN);
 
-       /* disable by setting to GPIO_IN */
-       bcm2835_pinctrl_fsel_set(pc, offset, BCM2835_FSEL_GPIO_IN);
        return 0;
 }
 
@@ -954,10 +955,7 @@ static void bcm2835_pmx_gpio_disable_free(struct pinctrl_dev *pctldev,
                struct pinctrl_gpio_range *range,
                unsigned offset)
 {
-       struct bcm2835_pinctrl *pc = pinctrl_dev_get_drvdata(pctldev);
-
-       /* disable by setting to GPIO_IN */
-       bcm2835_pinctrl_fsel_set(pc, offset, BCM2835_FSEL_GPIO_IN);
+       (void)bcm2835_pmx_free(pctldev, offset);
 }
 
 static int bcm2835_pmx_gpio_set_direction(struct pinctrl_dev *pctldev,
@@ -1339,7 +1337,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;
index 1c5af2f..81e7acb 100644 (file)
@@ -24,6 +24,7 @@ static struct gpio_desc *reset_gpio;
 static u32 timeout = DEFAULT_TIMEOUT_MS;
 static u32 active_delay = 100;
 static u32 inactive_delay = 100;
+static void (*old_power_off)(void);
 
 static void gpio_poweroff_do_poweroff(void)
 {
@@ -43,6 +44,9 @@ static void gpio_poweroff_do_poweroff(void)
        /* give it some time */
        mdelay(timeout);
 
+       if (old_power_off)
+               old_power_off();
+
        WARN_ON(1);
 }
 
@@ -50,9 +54,12 @@ static int gpio_poweroff_probe(struct platform_device *pdev)
 {
        bool input = false;
        enum gpiod_flags flags;
+       bool force = false;
+       bool export = false;
 
        /* If a pm_power_off function has already been added, leave it alone */
-       if (pm_power_off != NULL) {
+       force = of_property_read_bool(pdev->dev.of_node, "force");
+       if (!force && (pm_power_off != NULL)) {
                dev_err(&pdev->dev,
                        "%s: pm_power_off function already registered\n",
                       __func__);
@@ -74,6 +81,13 @@ static int gpio_poweroff_probe(struct platform_device *pdev)
        if (IS_ERR(reset_gpio))
                return PTR_ERR(reset_gpio);
 
+       export = of_property_read_bool(pdev->dev.of_node, "export");
+       if (export) {
+               gpiod_export(reset_gpio, false);
+               gpiod_export_link(&pdev->dev, "poweroff-gpio", reset_gpio);
+       }
+
+       old_power_off = pm_power_off;
        pm_power_off = &gpio_poweroff_do_poweroff;
        return 0;
 }
@@ -81,7 +95,9 @@ static int gpio_poweroff_probe(struct platform_device *pdev)
 static int gpio_poweroff_remove(struct platform_device *pdev)
 {
        if (pm_power_off == &gpio_poweroff_do_poweroff)
-               pm_power_off = NULL;
+               pm_power_off = old_power_off;
+
+       gpiod_unexport(reset_gpio);
 
        return 0;
 }
index fcc7534..986c941 100644 (file)
@@ -28,6 +28,12 @@ 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 PDA_POWER
        tristate "Generic PDA/phone power driver"
index 4e55a11..44923a1 100644 (file)
@@ -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_PDA_POWER)                += pda_power.o
 obj-$(CONFIG_APM_POWER)                += apm_power.o
 obj-$(CONFIG_AXP20X_POWER)     += axp20x_usb_power.o
diff --git a/drivers/power/supply/rpi_poe_power.c b/drivers/power/supply/rpi_poe_power.c
new file mode 100644 (file)
index 0000000..e96f98c
--- /dev/null
@@ -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");
index 2f4b11b..b10b435 100644 (file)
@@ -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),
index c877de3..1d9ab15 100644 (file)
@@ -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>
 
 struct raspberrypi_pwm {
        struct rpi_firmware *firmware;
+
+       struct regmap *regmap;
+       u32 offset;
+
        struct pwm_chip chip;
        unsigned int duty_cycle;
 };
@@ -43,7 +48,7 @@ struct raspberrypi_pwm *raspberrypi_pwm_from_chip(struct pwm_chip *chip)
        return container_of(chip, struct raspberrypi_pwm, 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 = {
@@ -52,17 +57,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 = {
@@ -70,16 +77,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 void raspberrypi_pwm_get_state(struct pwm_chip *chip,
@@ -117,7 +125,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(chip->dev, "Failed to set duty cycle: %pe\n",
@@ -144,29 +152,35 @@ 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");
-
        rpipwm = devm_kzalloc(&pdev->dev, sizeof(*rpipwm), GFP_KERNEL);
        if (!rpipwm)
                return -ENOMEM;
 
-       rpipwm->firmware = firmware;
+       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);
+
+               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;
+       }
+
        rpipwm->chip.dev = dev;
        rpipwm->chip.ops = &raspberrypi_pwm_ops;
        rpipwm->chip.base = -1;
        rpipwm->chip.npwm = RASPBERRYPI_FIRMWARE_PWM_NUM;
 
-       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 +192,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);
index 991b473..e3decc4 100644 (file)
@@ -8,6 +8,7 @@
 #include <linux/backlight.h>
 #include <linux/err.h>
 #include <linux/gpio.h>
+#include <linux/gpio/driver.h>
 #include <linux/i2c.h>
 #include <linux/init.h>
 #include <linux/interrupt.h>
 /* I2C registers of the Atmel microcontroller. */
 #define REG_ID         0x80
 #define REG_PORTA      0x81
-#define REG_PORTA_HF   BIT(2)
-#define REG_PORTA_VF   BIT(3)
 #define REG_PORTB      0x82
+#define REG_PORTC      0x83
 #define REG_POWERON    0x85
 #define REG_PWM                0x86
+#define REG_ADDR_L     0x8c
+#define REG_ADDR_H     0x8d
+#define REG_WRITE_DATA_H       0x90
+#define REG_WRITE_DATA_L       0x91
+
+#define PA_LCD_DITHB           BIT(0)
+#define PA_LCD_MODE            BIT(1)
+#define PA_LCD_LR              BIT(2)
+#define PA_LCD_UD              BIT(3)
+
+#define PB_BRIDGE_PWRDNX_N     BIT(0)
+#define PB_LCD_VCC_N           BIT(1)
+#define PB_LCD_MAIN            BIT(7)
+
+#define PC_LED_EN              BIT(0)
+#define PC_RST_TP_N            BIT(1)
+#define PC_RST_LCD_N           BIT(2)
+#define PC_RST_BRIDGE_N                BIT(3)
+
+enum gpio_signals {
+       RST_BRIDGE_N,   /* TC358762 bridge reset */
+       RST_TP_N,       /* Touch controller reset */
+       NUM_GPIO
+};
+
+struct gpio_signal_mappings {
+       unsigned int reg;
+       unsigned int mask;
+};
+
+static const struct gpio_signal_mappings mappings[NUM_GPIO] = {
+       [RST_BRIDGE_N] = { REG_PORTC, PC_RST_BRIDGE_N | PC_RST_LCD_N  },
+       [RST_TP_N] = { REG_PORTC, PC_RST_TP_N },
+};
+
+struct attiny_lcd {
+       /* lock to serialise overall accesses to the Atmel */
+       struct mutex    lock;
+       struct regmap   *regmap;
+       bool gpio_states[NUM_GPIO];
+       u8 port_states[3];
+
+       struct gpio_chip gc;
+};
 
 static const struct regmap_config attiny_regmap_config = {
        .reg_bits = 8,
        .val_bits = 8,
-       .max_register = REG_PWM,
+       .disable_locking = 1,
+       .max_register = REG_WRITE_DATA_L,
        .cache_type = REGCACHE_NONE,
 };
 
-static int attiny_lcd_power_enable(struct regulator_dev *rdev)
+static int attiny_set_port_state(struct attiny_lcd *state, int reg, u8 val)
 {
-       unsigned int data;
-       int ret, i;
+       state->port_states[reg - REG_PORTA] = val;
+       return regmap_write(state->regmap, reg, val);
+};
 
-       regmap_write(rdev->regmap, REG_POWERON, 1);
-       msleep(80);
+static u8 attiny_get_port_state(struct attiny_lcd *state, int reg)
+{
+       return state->port_states[reg - REG_PORTA];
+};
 
-       /* Wait for nPWRDWN to go low to indicate poweron is done. */
-       for (i = 0; i < 20; i++) {
-               ret = regmap_read(rdev->regmap, REG_PORTB, &data);
-               if (!ret) {
-                       if (data & BIT(0))
-                               break;
-               }
-               usleep_range(10000, 12000);
-       }
-       usleep_range(10000, 12000);
+static int attiny_lcd_power_enable(struct regulator_dev *rdev)
+{
+       struct attiny_lcd *state = rdev_get_drvdata(rdev);
+
+       mutex_lock(&state->lock);
 
-       if (ret)
-               pr_err("%s: regmap_read_poll_timeout failed %d\n", __func__, ret);
+       /* Ensure bridge, and tp stay in reset */
+       attiny_set_port_state(state, REG_PORTC, 0);
+       usleep_range(5000, 10000);
 
        /* Default to the same orientation as the closed source
         * firmware used for the panel.  Runtime rotation
         * configuration will be supported using VC4's plane
         * orientation bits.
         */
-       regmap_write(rdev->regmap, REG_PORTA, BIT(2));
+       attiny_set_port_state(state, REG_PORTA, PA_LCD_LR);
+       usleep_range(5000, 10000);
+       /* Main regulator on, and power to the panel (LCD_VCC_N) */
+       attiny_set_port_state(state, REG_PORTB, PB_LCD_MAIN);
+       usleep_range(5000, 10000);
+       /* Bring controllers out of reset */
+       attiny_set_port_state(state, REG_PORTC, PC_LED_EN);
+
+       msleep(80);
+
+       mutex_unlock(&state->lock);
 
        return 0;
 }
 
 static int attiny_lcd_power_disable(struct regulator_dev *rdev)
 {
+       struct attiny_lcd *state = rdev_get_drvdata(rdev);
+
+       mutex_lock(&state->lock);
+
        regmap_write(rdev->regmap, REG_PWM, 0);
-       regmap_write(rdev->regmap, REG_POWERON, 0);
+       usleep_range(5000, 10000);
+
+       attiny_set_port_state(state, REG_PORTA, 0);
+       usleep_range(5000, 10000);
+       attiny_set_port_state(state, REG_PORTB, PB_LCD_VCC_N);
+       usleep_range(5000, 10000);
+       attiny_set_port_state(state, REG_PORTC, 0);
        msleep(30);
+
+       mutex_unlock(&state->lock);
+
        return 0;
 }
 
 static int attiny_lcd_power_is_enabled(struct regulator_dev *rdev)
 {
-       unsigned int data;
-       int ret, i;
-
-       for (i = 0; i < 10; i++) {
-               ret = regmap_read(rdev->regmap, REG_POWERON, &data);
-               if (!ret)
-                       break;
-               usleep_range(10000, 12000);
-       }
-       if (ret < 0)
-               return ret;
-
-       if (!(data & BIT(0)))
-               return 0;
+       struct attiny_lcd *state = rdev_get_drvdata(rdev);
 
-       for (i = 0; i < 10; i++) {
-               ret = regmap_read(rdev->regmap, REG_PORTB, &data);
-               if (!ret)
-                       break;
-               usleep_range(10000, 12000);
-       }
-
-       if (ret < 0)
-               return ret;
-
-       return data & BIT(0);
+       return state->port_states[REG_PORTC - REG_PORTA] & PC_RST_BRIDGE_N;
 }
 
 static const struct regulator_init_data attiny_regulator_default = {
@@ -125,10 +169,13 @@ static const struct regulator_desc attiny_regulator = {
 
 static int attiny_update_status(struct backlight_device *bl)
 {
-       struct regmap *regmap = bl_get_data(bl);
+       struct attiny_lcd *state = bl_get_data(bl);
+       struct regmap *regmap = state->regmap;
        int brightness = bl->props.brightness;
        int ret, i;
 
+       mutex_lock(&state->lock);
+
        if (bl->props.power != FB_BLANK_UNBLANK ||
            bl->props.fb_blank != FB_BLANK_UNBLANK)
                brightness = 0;
@@ -139,30 +186,86 @@ static int attiny_update_status(struct backlight_device *bl)
                        break;
        }
 
+       mutex_unlock(&state->lock);
+
        return ret;
 }
 
-static int attiny_get_brightness(struct backlight_device *bl)
+static const struct backlight_ops attiny_bl = {
+       .update_status  = attiny_update_status,
+};
+
+static int attiny_gpio_get_direction(struct gpio_chip *gc, unsigned int off)
+{
+       return GPIO_LINE_DIRECTION_OUT;
+}
+
+static void attiny_gpio_set(struct gpio_chip *gc, unsigned int off, int val)
 {
-       struct regmap *regmap = bl_get_data(bl);
-       int ret, brightness, i;
+       struct attiny_lcd *state = gpiochip_get_data(gc);
+       u8 last_val;
 
-       for (i = 0; i < 10; i++) {
-               ret = regmap_read(regmap, REG_PWM, &brightness);
-               if (!ret)
-                       break;
-       }
+       if (off >= NUM_GPIO)
+               return;
+
+       mutex_lock(&state->lock);
 
-       if (ret)
-               return ret;
+       last_val = attiny_get_port_state(state, mappings[off].reg);
+       if (val)
+               last_val |= mappings[off].mask;
+       else
+               last_val &= ~mappings[off].mask;
 
-       return brightness;
+       attiny_set_port_state(state, mappings[off].reg, last_val);
+
+       if (off == RST_BRIDGE_N && val) {
+               usleep_range(5000, 8000);
+               regmap_write(state->regmap, REG_ADDR_H, 0x04);
+               usleep_range(5000, 8000);
+               regmap_write(state->regmap, REG_ADDR_L, 0x7c);
+               usleep_range(5000, 8000);
+               regmap_write(state->regmap, REG_WRITE_DATA_H, 0x00);
+               usleep_range(5000, 8000);
+               regmap_write(state->regmap, REG_WRITE_DATA_L, 0x00);
+
+               msleep(100);
+       }
+
+       mutex_unlock(&state->lock);
 }
 
-static const struct backlight_ops attiny_bl = {
-       .update_status  = attiny_update_status,
-       .get_brightness = attiny_get_brightness,
-};
+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
@@ -174,22 +277,30 @@ static int attiny_i2c_probe(struct i2c_client *i2c,
        struct regulator_config config = { };
        struct backlight_device *bl;
        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);
+       if (!state)
+               return -ENOMEM;
+
+       mutex_init(&state->lock);
+       i2c_set_clientdata(i2c, state);
+
        regmap = devm_regmap_init_i2c(i2c, &attiny_regmap_config);
        if (IS_ERR(regmap)) {
                ret = PTR_ERR(regmap);
                dev_err(&i2c->dev, "Failed to allocate register map: %d\n",
                        ret);
-               return ret;
+               goto error;
        }
 
-       ret = regmap_read(regmap, REG_ID, &data);
+       ret = attiny_i2c_read(i2c, REG_ID, &data);
        if (ret < 0) {
                dev_err(&i2c->dev, "Failed to read REG_ID reg: %d\n", ret);
-               return ret;
+               goto error;
        }
 
        switch (data) {
@@ -198,34 +309,73 @@ static int attiny_i2c_probe(struct i2c_client *i2c,
                break;
        default:
                dev_err(&i2c->dev, "Unknown Atmel firmware revision: 0x%02x\n", data);
-               return -ENODEV;
+               ret = -ENODEV;
+               goto error;
        }
 
        regmap_write(regmap, REG_POWERON, 0);
        msleep(30);
+       regmap_write(regmap, REG_PWM, 0);
 
        config.dev = &i2c->dev;
        config.regmap = regmap;
        config.of_node = i2c->dev.of_node;
        config.init_data = &attiny_regulator_default;
+       config.driver_data = state;
 
        rdev = devm_regulator_register(&i2c->dev, &attiny_regulator, &config);
        if (IS_ERR(rdev)) {
                dev_err(&i2c->dev, "Failed to register ATTINY regulator\n");
-               return PTR_ERR(rdev);
+               ret = PTR_ERR(rdev);
+               goto error;
        }
 
        props.type = BACKLIGHT_RAW;
        props.max_brightness = 0xff;
-       bl = devm_backlight_device_register(&i2c->dev,
-                                           "7inch-touchscreen-panel-bl",
-                                           &i2c->dev, regmap, &attiny_bl,
+
+       state->regmap = regmap;
+
+       bl = devm_backlight_device_register(&i2c->dev, dev_name(&i2c->dev),
+                                           &i2c->dev, state, &attiny_bl,
                                            &props);
-       if (IS_ERR(bl))
-               return PTR_ERR(bl);
+       if (IS_ERR(bl)) {
+               ret = PTR_ERR(bl);
+               goto error;
+       }
 
        bl->props.brightness = 0xff;
 
+       state->gc.parent = &i2c->dev;
+       state->gc.label = i2c->name;
+       state->gc.owner = THIS_MODULE;
+       state->gc.of_node = i2c->dev.of_node;
+       state->gc.base = -1;
+       state->gc.ngpio = NUM_GPIO;
+
+       state->gc.set = attiny_gpio_set;
+       state->gc.get_direction = attiny_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;
+       }
+
+       return 0;
+
+error:
+       mutex_destroy(&state->lock);
+
+       return ret;
+}
+
+static int attiny_i2c_remove(struct i2c_client *client)
+{
+       struct attiny_lcd *state = i2c_get_clientdata(client);
+
+       mutex_destroy(&state->lock);
+
        return 0;
 }
 
@@ -241,6 +391,7 @@ static struct i2c_driver attiny_regulator_driver = {
                .of_match_table = of_match_ptr(attiny_dt_ids),
        },
        .probe = attiny_i2c_probe,
+       .remove = attiny_i2c_remove,
 };
 
 module_i2c_driver(attiny_regulator_driver);
index 7473e6c..4b986b3 100644 (file)
@@ -473,3 +473,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");
index 14da4ab..ea75b71 100644 (file)
@@ -34,6 +34,7 @@
 #define PCF85063_REG_CTRL1             0x00 /* status */
 #define PCF85063_REG_CTRL1_CAP_SEL     BIT(0)
 #define PCF85063_REG_CTRL1_STOP                BIT(5)
+#define PCF85063_REG_CTRL1_EXT_TEST    BIT(7)
 
 #define PCF85063_REG_CTRL2             0x01
 #define PCF85063_CTRL2_AF              BIT(6)
@@ -117,6 +118,7 @@ static int pcf85063_rtc_set_time(struct device *dev, struct rtc_time *tm)
         * reset state until all time/date registers are written
         */
        rc = regmap_update_bits(pcf85063->regmap, PCF85063_REG_CTRL1,
+                               PCF85063_REG_CTRL1_EXT_TEST |
                                PCF85063_REG_CTRL1_STOP,
                                PCF85063_REG_CTRL1_STOP);
        if (rc)
index 8b6fb20..ad2231f 100644 (file)
@@ -242,8 +242,28 @@ static int pcf8523_rtc_read_time(struct device *dev, struct rtc_time *tm)
        if (err < 0)
                return err;
 
-       if (regs[0] & PCF8523_SECONDS_OS)
-               return -EINVAL;
+       if (regs[0] & 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[0] &= ~PCF8523_SECONDS_OS;
+
+               err = pcf8523_write(client, PCF8523_REG_SECONDS, regs[0]);
+               if (err < 0)
+                       return err;
+
+               err = pcf8523_read(client, PCF8523_REG_SECONDS, &regs[0]);
+               if (err < 0)
+                       return err;
+
+               if (regs[0] & PCF8523_SECONDS_OS)
+                       return -EAGAIN;
+       }
 
        tm->tm_sec = bcd2bin(regs[0] & 0x7f);
        tm->tm_min = bcd2bin(regs[1] & 0x7f);
index 12c8073..757c0ff 100644 (file)
@@ -80,6 +80,7 @@
 
 #define RV3028_BACKUP_TCE              BIT(5)
 #define RV3028_BACKUP_TCR_MASK         GENMASK(1,0)
+#define RV3028_BACKUP_BSM_MASK         0x0C
 
 #define OFFSET_STEP_PPT                        953674
 
@@ -789,6 +790,7 @@ static int rv3028_probe(struct i2c_client *client)
        struct rv3028_data *rv3028;
        int ret, status;
        u32 ohms;
+       u8 bsm;
        struct nvmem_config nvmem_cfg = {
                .name = "rv3028_nvram",
                .word_size = 1,
@@ -855,6 +857,21 @@ static int rv3028_probe(struct i2c_client *client)
        if (ret)
                return ret;
 
+       /* setup backup switchover mode */
+       if (!device_property_read_u8(&client->dev, "backup-switchover-mode",
+                                    &bsm))  {
+               if (bsm <= 3) {
+                       ret = regmap_update_bits(rv3028->regmap, RV3028_BACKUP,
+                               RV3028_BACKUP_BSM_MASK,
+                               (bsm & 0x03) << 2);
+
+                       if (ret)
+                               return ret;
+               } else {
+                       dev_warn(&client->dev, "invalid backup switchover mode value\n");
+               }
+       }
+
        /* setup trickle charger */
        if (!device_property_read_u32(&client->dev, "trickle-resistor-ohms",
                                      &ohms)) {
index 24f92a6..a6a705e 100644 (file)
@@ -17,6 +17,7 @@ config RASPBERRYPI_POWER
        bool "Raspberry Pi power domain driver"
        depends on ARCH_BCM2835 || (COMPILE_TEST && OF)
        depends on RASPBERRYPI_FIRMWARE=y
+       depends on PM
        select PM_GENERIC_DOMAINS if PM
        help
          This enables support for the RPi power domains which can be enabled
index 1e0041e..6059210 100644 (file)
@@ -143,6 +143,8 @@ struct bcm2835_power {
        /* AXI Async bridge registers. */
        void __iomem            *asb;
 
+       bool is_2711;
+
        struct genpd_onecell_data pd_xlate;
        struct bcm2835_power_domain domains[BCM2835_POWER_DOMAIN_COUNT];
        struct reset_controller_dev reset;
@@ -192,6 +194,10 @@ static int bcm2835_power_power_off(struct bcm2835_power_domain *pd, u32 pm_reg)
 {
        struct bcm2835_power *power = pd->power;
 
+       /* 2711 has no power domains above the reset controller. */
+       if (power->is_2711)
+               return 0;
+
        /* Enable functional isolation */
        PM_WRITE(pm_reg, PM_READ(pm_reg) & ~PM_ISFUNC);
 
@@ -213,6 +219,10 @@ static int bcm2835_power_power_on(struct bcm2835_power_domain *pd, u32 pm_reg)
        int inrush;
        bool powok;
 
+       /* 2711 has no power domains above the reset controller. */
+       if (power->is_2711)
+               return 0;
+
        /* If it was already powered on by the fw, leave it that way. */
        if (PM_READ(pm_reg) & PM_POWUP)
                return 0;
@@ -627,6 +637,18 @@ static int bcm2835_power_probe(struct platform_device *pdev)
        power->base = pm->base;
        power->asb = pm->asb;
 
+       /* 2711 hack: the new RPiVid ASB took over V3D, which is our
+        * only consumer of this driver so far.  The old ASB seems to
+        * still be present with ISP and H264 bits but no V3D, but I
+        * don't know if that's real or not.  The V3D is in the same
+        * place in the new ASB as the old one, so just poke the new
+        * one for now.
+        */
+       if (pm->rpivid_asb) {
+               power->asb = pm->rpivid_asb;
+               power->is_2711 = true;
+       }
+
        id = ASB_READ(ASB_AXI_BRDG_ID);
        if (id != 0x62726467 /* "BRDG" */) {
                dev_err(dev, "ASB register ID returned 0x%08x\n", id);
index 775c0bf..0f9b027 100644 (file)
@@ -372,6 +372,10 @@ static irqreturn_t bcm2835_spi_interrupt(int irq, void *dev_id)
        struct bcm2835_spi *bs = dev_id;
        u32 cs = bcm2835_rd(bs, BCM2835_SPI_CS);
 
+       /* Bail out early if interrupts are not enabled */
+       if (!(cs & BCM2835_SPI_CS_INTR))
+               return IRQ_NONE;
+
        /*
         * An interrupt is signaled either if DONE is set (TX FIFO empty)
         * or if RXR is set (RX FIFO >= ¾ full).
@@ -1051,6 +1055,16 @@ static int bcm2835_spi_transfer_one(struct spi_controller *ctlr,
        unsigned long hz_per_byte, byte_limit;
        u32 cs = slv->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;
 
@@ -1365,7 +1379,8 @@ static int bcm2835_spi_probe(struct platform_device *pdev)
        bcm2835_wr(bs, BCM2835_SPI_CS,
                   BCM2835_SPI_CS_CLEAR_RX | BCM2835_SPI_CS_CLEAR_TX);
 
-       err = devm_request_irq(&pdev->dev, bs->irq, bcm2835_spi_interrupt, 0,
+       err = devm_request_irq(&pdev->dev, bs->irq, bcm2835_spi_interrupt,
+                              IRQF_SHARED,
                               dev_name(&pdev->dev), bs);
        if (err) {
                dev_err(&pdev->dev, "could not request IRQ: %d\n", err);
index 0584f4d..6890a14 100644 (file)
@@ -35,6 +35,7 @@ struct spi_gpio {
        struct gpio_desc                *sck;
        struct gpio_desc                *miso;
        struct gpio_desc                *mosi;
+       bool                            sck_idle_input;
        struct gpio_desc                **cs_gpios;
 };
 
@@ -201,8 +202,12 @@ 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 */
        if (spi_gpio->cs_gpios) {
@@ -211,6 +216,9 @@ static void spi_gpio_chipselect(struct spi_device *spi, int is_active)
                /* SPI chip selects are normally active-low */
                gpiod_set_value_cansleep(cs, (spi->mode & SPI_CS_HIGH) ? is_active : !is_active);
        }
+
+       if (spi_gpio->sck_idle_input && !is_active)
+               gpiod_direction_input(spi_gpio->sck);
 }
 
 static int spi_gpio_setup(struct spi_device *spi)
@@ -289,6 +297,7 @@ 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);
 }
index d0bbf8f..c5f1e4c 100644 (file)
@@ -3469,6 +3469,7 @@ static int __spi_validate_bits_per_word(struct spi_controller *ctlr,
  */
 int spi_setup(struct spi_device *spi)
 {
+       struct spi_controller *ctlr = spi->controller;
        unsigned        bad_bits, ugly_bits;
        int             status;
 
@@ -3490,6 +3491,14 @@ int spi_setup(struct spi_device *spi)
                (SPI_TX_DUAL | SPI_TX_QUAD | SPI_TX_OCTAL |
                 SPI_RX_DUAL | SPI_RX_QUAD | SPI_RX_OCTAL)))
                return -EINVAL;
+
+       if (ctlr->use_gpio_descriptors && ctlr->cs_gpiods &&
+           ctlr->cs_gpiods[spi->chip_select] && !(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
         * SPI_CS_WORD has a fallback software implementation,
index 1bd73e3..3696662 100644 (file)
@@ -402,7 +402,6 @@ spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
                else
                        retval = get_user(tmp, (u32 __user *)arg);
                if (retval == 0) {
-                       struct spi_controller *ctlr = spi->controller;
                        u32     save = spi->mode;
 
                        if (tmp & ~SPI_MODE_MASK) {
@@ -410,10 +409,6 @@ spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
                                break;
                        }
 
-                       if (ctlr->use_gpio_descriptors && ctlr->cs_gpiods &&
-                           ctlr->cs_gpiods[spi->chip_select])
-                               tmp |= SPI_CS_HIGH;
-
                        tmp |= spi->mode & ~SPI_MODE_MASK;
                        spi->mode = (u16)tmp;
                        retval = spi_setup(spi);
@@ -674,6 +669,7 @@ static const struct file_operations spidev_fops = {
 static struct class *spidev_class;
 
 static const struct spi_device_id spidev_spi_ids[] = {
+       { .name = "spidev" },
        { .name = "dh2228fv" },
        { .name = "ltc2488" },
        { .name = "sx1301" },
@@ -751,7 +747,7 @@ static int spidev_probe(struct spi_device *spi)
         * compatible string, it is a Linux implementation thing
         * rather than a description of the hardware.
         */
-       WARN(spi->dev.of_node &&
+       WARN(0 && spi->dev.of_node &&
             of_device_is_compatible(spi->dev.of_node, "spidev"),
             "%pOF: buggy DT: spidev listed directly in DT\n", spi->dev.of_node);
 
index 9670a89..1a32196 100644 (file)
 #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);
+int variant_adafruit18(struct fbtft_display *display)
+{
+       display->gamma = ADAFRUIT18_GAMMA;
+       return 0;
+}
+
+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");
index 861a154..3bcd9ee 100644 (file)
@@ -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);
+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);
index 1690358..4c4c8e0 100644 (file)
@@ -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>
 
@@ -1184,6 +1186,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
  *
@@ -1193,12 +1196,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)
@@ -1214,6 +1220,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);
index 06afaa9..c6ec045 100644 (file)
@@ -251,7 +251,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);
 int fbtft_remove_common(struct device *dev, struct fb_info *info);
 
 /* fbtft-io.c */
@@ -272,11 +273,13 @@ 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_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);                    \
+       return fbtft_probe_common(_display, spi, NULL, dt_ids);            \
 }                                                                          \
                                                                           \
 static int fbtft_driver_remove_spi(struct spi_device *spi)                 \
@@ -288,7 +291,7 @@ static int fbtft_driver_remove_spi(struct spi_device *spi)                 \
                                                                           \
 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 int fbtft_driver_remove_pdev(struct platform_device *pdev)          \
@@ -298,8 +301,16 @@ static int fbtft_driver_remove_pdev(struct platform_device *pdev)          \
        return fbtft_remove_common(&pdev->dev, info);                      \
 }                                                                          \
                                                                           \
-static const struct of_device_id dt_ids[] = {                              \
-       { .compatible = _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)                         \
+                                                                          \
        {},                                                                \
 };                                                                         \
                                                                           \
@@ -347,6 +358,11 @@ static void __exit fbtft_driver_module_exit(void)                          \
 module_init(fbtft_driver_module_init);                                     \
 module_exit(fbtft_driver_module_exit);
 
+#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 */
index e3aaae9..d2128cd 100644 (file)
@@ -32,6 +32,8 @@ source "drivers/staging/media/omap4iss/Kconfig"
 
 source "drivers/staging/media/rkvdec/Kconfig"
 
+source "drivers/staging/media/rpivid/Kconfig"
+
 source "drivers/staging/media/sunxi/Kconfig"
 
 source "drivers/staging/media/tegra-vde/Kconfig"
index 5b5afc5..4d61af6 100644 (file)
@@ -4,6 +4,7 @@ obj-$(CONFIG_VIDEO_IMX_MEDIA)   += imx/
 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_SUNXI)      += sunxi/
 obj-$(CONFIG_VIDEO_TEGRA)      += tegra-video/
 obj-$(CONFIG_TEGRA_VDE)                += tegra-vde/
diff --git a/drivers/staging/media/rpivid/Kconfig b/drivers/staging/media/rpivid/Kconfig
new file mode 100644 (file)
index 0000000..304c3ed
--- /dev/null
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0
+
+config VIDEO_RPIVID
+       tristate "Rpi H265 driver"
+       depends on VIDEO_DEV && VIDEO_V4L2
+       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 (file)
index 0000000..9902570
--- /dev/null
@@ -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 (file)
index 0000000..f78e5de
--- /dev/null
@@ -0,0 +1,457 @@
+// 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_MPEG_VIDEO_HEVC_SPS,
+                       .ops    = &rpivid_hevc_sps_ctrl_ops,
+               },
+               .required       = true,
+       },
+       {
+               .cfg = {
+                       .id     = V4L2_CID_MPEG_VIDEO_HEVC_PPS,
+                       .ops    = &rpivid_hevc_pps_ctrl_ops,
+               },
+               .required       = true,
+       },
+       {
+               .cfg = {
+                       .id = V4L2_CID_MPEG_VIDEO_HEVC_SCALING_MATRIX,
+               },
+               .required       = false,
+       },
+       {
+               .cfg = {
+                       .id     = V4L2_CID_MPEG_VIDEO_HEVC_DECODE_PARAMS,
+               },
+               .required       = true,
+       },
+       {
+               .cfg = {
+                       .name   = "Slice param array",
+                       .id     = V4L2_CID_MPEG_VIDEO_HEVC_SLICE_PARAMS,
+                       .type   = V4L2_CTRL_TYPE_HEVC_SLICE_PARAMS,
+                       .flags  = V4L2_CTRL_FLAG_DYNAMIC_ARRAY,
+                       .dims   = { 0x1000 },
+               },
+               .required       = true,
+       },
+       {
+               .cfg = {
+                       .id     = V4L2_CID_MPEG_VIDEO_HEVC_DECODE_MODE,
+                       .max    = V4L2_MPEG_VIDEO_HEVC_DECODE_MODE_SLICE_BASED,
+                       .def    = V4L2_MPEG_VIDEO_HEVC_DECODE_MODE_SLICE_BASED,
+               },
+               .required       = false,
+       },
+       {
+               .cfg = {
+                       .id     = V4L2_CID_MPEG_VIDEO_HEVC_START_CODE,
+                       .max    = V4L2_MPEG_VIDEO_HEVC_START_CODE_NONE,
+                       .def    = V4L2_MPEG_VIDEO_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);
+
+       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 int 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);
+
+       return 0;
+}
+
+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 (file)
index 0000000..dbe496a
--- /dev/null
@@ -0,0 +1,202 @@
+/* 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;
+
+       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 (file)
index 0000000..e05f668
--- /dev/null
@@ -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_MPEG_VIDEO_HEVC_SPS);
+               run.h265.pps =
+                       rpivid_find_control_data(ctx,
+                                                V4L2_CID_MPEG_VIDEO_HEVC_PPS);
+               run.h265.dec =
+                       rpivid_find_control_data(ctx,
+                                                V4L2_CID_MPEG_VIDEO_HEVC_DECODE_PARAMS);
+
+               ctrl = rpivid_find_ctrl(ctx,
+                                       V4L2_CID_MPEG_VIDEO_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_MPEG_VIDEO_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 (file)
index 0000000..8f15bb6
--- /dev/null
@@ -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 (file)
index 0000000..834e34a
--- /dev/null
@@ -0,0 +1,2710 @@
+// 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_bit_offset / 8 + 1;
+       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 unsigned int cur_poc)
+{
+       unsigned int i;
+
+       for (i = 0; i < n; ++i) {
+               // Compare mod 2^16
+               // We only get u16 pocs & 8.3.1 says
+               // "The bitstream shall not contain data that result in values
+               //  of DiffPicOrderCnt( picA, picB ) used in the decoding
+               //  process that are not in the range of −2^15 to 2^15 − 1,
+               //  inclusive."
+               if (((cur_poc - dpb[idx[i]].pic_order_cnt[0]) & 0x8000) != 0)
+                       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].rps ==
+                                       V4L2_HEVC_DPB_ENTRY_RPS_LT_CURR ?
+                                                (1 << 4) : 0) |
+                                 (weighted_pred_flag ? (3 << 5) : 0));
+                       msg_slice(de, dec->dpb[dpb_no].pic_order_cnt[0]);
+
+                       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].rps ==
+                                        V4L2_HEVC_DPB_ENTRY_RPS_LT_CURR ?
+                                                (1 << 4) : 0) |
+                                       (weighted_pred_flag ? (3 << 5) : 0));
+                       msg_slice(de, dec->dpb[dpb_no].pic_order_cnt[0]);
+                       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);
+               /* FALLTHRU */
+               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 (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_bit_offset >= sh->bit_size ||
+                   sh->bit_size - sh->data_bit_offset < 8) {
+                       v4l2_warn(&dev->v4l2_dev,
+                                 "Bit size %d < Bit offset %d + 8\n",
+                                 sh->bit_size, sh->data_bit_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) {
+               int buffer_index =
+                       vb2_find_timestamp(vq, dec->dpb[i].timestamp, 0);
+               struct vb2_buffer *buf = buffer_index < 0 ?
+                                       NULL :
+                                       vb2_get_buffer(vq, buffer_index);
+
+               if (!buf) {
+                       v4l2_warn(&dev->v4l2_dev,
+                                 "Missing DPB ent %d, timestamp=%lld, index=%d\n",
+                                 i, (long long)dec->dpb[i].timestamp,
+                                 buffer_index);
+                       continue;
+               }
+
+               if (s->use_aux) {
+                       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))
+                       goto fail;
+               if (gptr_alloc(dev, ctx->coeff_bufs + i, coeff_alloc,
+                              DMA_ATTR_NO_KERNEL_MAPPING))
+                       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 (file)
index 0000000..e7d1793
--- /dev/null
@@ -0,0 +1,366 @@
+// 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 "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 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);
+
+       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 (file)
index 0000000..ec73a23
--- /dev/null
@@ -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 (file)
index 0000000..bab3ed3
--- /dev/null
@@ -0,0 +1,699 @@
+// 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 inline int is_sps_set(const struct v4l2_ctrl_hevc_sps * const sps)
+{
+       return sps && sps->pic_width_in_luma_samples != 0;
+}
+
+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_MPEG_VIDEO_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_MPEG_VIDEO_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_MPEG_VIDEO_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)
+{
+       long max_hevc_clock;
+       int rv;
+
+       max_hevc_clock = clk_get_max_rate(dev->clock);
+
+       rv = clk_set_min_rate(dev->clock, max_hevc_clock);
+       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_buffers_needed = 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 (file)
index 0000000..e22cc0e
--- /dev/null
@@ -0,0 +1,33 @@
+/* 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;
+};
+
+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
index cb7c824..0927c93 100644 (file)
@@ -43,6 +43,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
index 1fd191e..a0c4f71 100644 (file)
@@ -14,6 +14,9 @@ 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/
 
 ccflags-y += -I $(srctree)/$(src)/include
 
index c250fbe..ccda115 100644 (file)
@@ -6,13 +6,14 @@
 #include <linux/init.h>
 #include <linux/slab.h>
 #include <linux/module.h>
+#include <linux/of.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;
 static bool enable_compat_alsa = true;
-static int num_channels = MAX_SUBSTREAMS;
 
 module_param(enable_hdmi, bool, 0444);
 MODULE_PARM_DESC(enable_hdmi, "Enables HDMI virtual audio device");
@@ -21,8 +22,6 @@ MODULE_PARM_DESC(enable_headphones, "Enables Headphones virtual audio device");
 module_param(enable_compat_alsa, bool, 0444);
 MODULE_PARM_DESC(enable_compat_alsa,
                 "Enables ALSA compatibility virtual audio device");
-module_param(num_channels, int, 0644);
-MODULE_PARM_DESC(num_channels, "Number of audio channels (default: 8)");
 
 static void bcm2835_devm_free_vchi_ctx(struct device *dev, void *res)
 {
@@ -81,7 +80,11 @@ static int bcm2835_audio_alsa_newpcm(struct bcm2835_chip *chip,
        if (err)
                return err;
 
-       err = snd_bcm2835_new_pcm(chip, "bcm2835 IEC958/HDMI", 1, 0, 1, true);
+       err = snd_bcm2835_new_pcm(chip, "bcm2835 IEC958/HDMI", 1, AUDIO_DEST_HDMI0, 1, true);
+       if (err)
+               return err;
+
+       err = snd_bcm2835_new_pcm(chip, "bcm2835 IEC958/HDMI1", 2, AUDIO_DEST_HDMI1, 1, true);
        if (err)
                return err;
 
@@ -108,17 +111,30 @@ static struct bcm2835_audio_driver bcm2835_audio_alsa = {
        .newctl = snd_bcm2835_new_ctl,
 };
 
-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",
-       .longname  = "bcm2835 HDMI",
+       .shortname = "bcm2835 HDMI 1",
+       .longname  = "bcm2835 HDMI 1",
        .minchannels = 1,
        .newpcm = bcm2835_audio_simple_newpcm,
        .newctl = snd_bcm2835_new_hdmi_ctl,
-       .route = AUDIO_DEST_HDMI
+       .route = AUDIO_DEST_HDMI0
+};
+
+static struct bcm2835_audio_driver bcm2835_audio_hdmi1 = {
+       .driver = {
+               .name = "bcm2835_hdmi",
+               .owner = THIS_MODULE,
+       },
+       .shortname = "bcm2835 HDMI 2",
+       .longname  = "bcm2835 HDMI 2",
+       .minchannels = 1,
+       .newpcm = bcm2835_audio_simple_newpcm,
+       .newctl = snd_bcm2835_new_hdmi_ctl,
+       .route = AUDIO_DEST_HDMI1
 };
 
 static struct bcm2835_audio_driver bcm2835_audio_headphones = {
@@ -145,8 +161,12 @@ static struct bcm2835_audio_drivers children_devices[] = {
                .is_enabled = &enable_compat_alsa,
        },
        {
-               .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,
@@ -293,22 +313,93 @@ 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;
+       u32 num_displays, i, display_id;
+       int ret;
+
+       firmware_node = of_parse_phandle(dev->of_node, "brcm,firmware", 0);
+       firmware = rpi_firmware_get(firmware_node);
+
+       if (!firmware)
+               return;
+
+       of_node_put(firmware_node);
+
+       ret = rpi_firmware_property(firmware,
+                                   RPI_FIRMWARE_FRAMEBUFFER_GET_NUM_DISPLAYS,
+                                   &num_displays, sizeof(u32));
+
+       if (ret)
+               return;
+
+       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) {
+                       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;
+       }
+}
+
 static int snd_bcm2835_alsa_probe(struct platform_device *pdev)
 {
        struct device *dev = &pdev->dev;
+       u32 numchans;
        int err;
 
-       if (num_channels <= 0 || num_channels > MAX_SUBSTREAMS) {
-               num_channels = MAX_SUBSTREAMS;
-               dev_warn(dev, "Illegal num_channels value, will use %u\n",
-                        num_channels);
+       err = of_property_read_u32(dev->of_node, "brcm,pwm-channels",
+                                  &numchans);
+       if (err) {
+               dev_err(dev, "Failed to get DT property 'brcm,pwm-channels'");
+               return err;
+       }
+
+       if (numchans == 0 || numchans > MAX_SUBSTREAMS) {
+               numchans = MAX_SUBSTREAMS;
+               dev_warn(dev,
+                        "Illegal 'brcm,pwm-channels' value, will use %u\n",
+                        numchans);
+       }
+
+       if (!enable_compat_alsa) {
+               // In this mode, enable analog output by default
+               u32 disable_headphones = 0;
+
+               if (!of_property_read_bool(dev->of_node, "brcm,disable-hdmi"))
+                       set_hdmi_enables(dev);
+
+               of_property_read_u32(dev->of_node,
+                                    "brcm,disable-headphones",
+                                    &disable_headphones);
+               enable_headphones = !disable_headphones;
+       } else {
+               enable_hdmi0 = enable_hdmi;
        }
 
        err = bcm2835_devm_add_vchi_ctx(dev);
        if (err)
                return err;
 
-       err = snd_add_child_devices(dev, num_channels);
+       err = snd_add_child_devices(dev, numchans);
        if (err)
                return err;
 
@@ -330,6 +421,12 @@ static int snd_bcm2835_alsa_resume(struct platform_device *pdev)
 
 #endif
 
+static const struct of_device_id snd_bcm2835_of_match_table[] = {
+       { .compatible = "brcm,bcm2835-audio",},
+       {},
+};
+MODULE_DEVICE_TABLE(of, snd_bcm2835_of_match_table);
+
 static struct platform_driver bcm2835_alsa_driver = {
        .probe = snd_bcm2835_alsa_probe,
 #ifdef CONFIG_PM
@@ -338,6 +435,7 @@ static struct platform_driver bcm2835_alsa_driver = {
 #endif
        .driver = {
                .name = "bcm2835_audio",
+               .of_match_table = snd_bcm2835_of_match_table,
        },
 };
 module_platform_driver(bcm2835_alsa_driver);
index 51066ac..b42a2fb 100644 (file)
@@ -33,7 +33,9 @@ enum {
 enum snd_bcm2835_route {
        AUDIO_DEST_AUTO = 0,
        AUDIO_DEST_HEADPHONES = 1,
-       AUDIO_DEST_HDMI = 2,
+       AUDIO_DEST_HDMI = 2,  // for backwards compatibility.
+       AUDIO_DEST_HDMI0 = 2,
+       AUDIO_DEST_HDMI1 = 3,
        AUDIO_DEST_MAX,
 };
 
index 1b184d5..0a8721e 100644 (file)
@@ -178,7 +178,7 @@ static struct mmal_fmt formats[] = {
                .ybbp = 1,
                .remove_padding = 1,
        }, {
-               .fourcc = V4L2_PIX_FMT_BGR32,
+               .fourcc = V4L2_PIX_FMT_BGRX32,
                .mmal = MMAL_ENCODING_BGRA,
                .depth = 32,
                .mmal_component = COMP_CAMERA,
@@ -1453,6 +1453,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,
@@ -1734,6 +1735,12 @@ static int mmal_init(struct bm2835_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 = bm2835_mmal_set_all_camera_controls(dev);
        if (ret < 0) {
@@ -1928,7 +1935,7 @@ static int bcm2835_mmal_probe(struct platform_device *pdev)
                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 = &bm2835_mmal_video_qops;
index 75524ad..fdbcc35 100644 (file)
@@ -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,
index b096a12..b1b02fb 100644 (file)
@@ -474,6 +474,10 @@ static int ctrl_set_awb_mode(struct bm2835_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,
@@ -705,6 +709,8 @@ static int ctrl_set_video_encode_profile_level(struct bm2835_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:
@@ -770,6 +776,17 @@ static int ctrl_set_video_encode_profile_level(struct bm2835_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;
@@ -1052,8 +1069,8 @@ static const struct bm2835_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,
@@ -1220,8 +1237,10 @@ static const struct bm2835_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,
@@ -1251,6 +1270,39 @@ static const struct bm2835_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 bm2835_mmal_set_all_camera_controls(struct bm2835_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 (file)
index 0000000..c104be9
--- /dev/null
@@ -0,0 +1,11 @@
+config VIDEO_CODEC_BCM2835
+       tristate "BCM2835 Video codec support"
+       depends on MEDIA_SUPPORT && MEDIA_CONTROLLER
+       depends on VIDEO_V4L2 && (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 (file)
index 0000000..7fb908d
--- /dev/null
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0
+bcm2835-codec-objs := bcm2835-v4l2-codec.o
+
+obj-$(CONFIG_VIDEO_CODEC_BCM2835) += bcm2835-codec.o
+
+ccflags-y += \
+       -I$(srctree)/drivers/staging/vc04_services \
+       -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 (file)
index 0000000..bc27a04
--- /dev/null
@@ -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 (file)
index 0000000..ca063a2
--- /dev/null
@@ -0,0 +1,3841 @@
+// 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/platform_device.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 "vchiq-mmal/mmal-encodings.h"
+#include "vchiq-mmal/mmal-msg.h"
+#include "vchiq-mmal/mmal-parameters.h"
+#include "vchiq-mmal/mmal-vchiq.h"
+
+/*
+ * 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          1920
+#define MAX_H          1920
+#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     = { 32, 32, 32, 32, 32 },
+               .flags                  = 0,
+               .mmal_fmt               = MMAL_ENCODING_YUYV,
+               .size_multiplier_x2     = 2,
+       }, {
+               .fourcc                 = V4L2_PIX_FMT_UYVY,
+               .depth                  = 16,
+               .bytesperline_align     = { 32, 32, 32, 32, 32 },
+               .flags                  = 0,
+               .mmal_fmt               = MMAL_ENCODING_UYVY,
+               .size_multiplier_x2     = 2,
+       }, {
+               .fourcc                 = V4L2_PIX_FMT_YVYU,
+               .depth                  = 16,
+               .bytesperline_align     = { 32, 32, 32, 32, 32 },
+               .flags                  = 0,
+               .mmal_fmt               = MMAL_ENCODING_YVYU,
+               .size_multiplier_x2     = 2,
+       }, {
+               .fourcc                 = V4L2_PIX_FMT_VYUY,
+               .depth                  = 16,
+               .bytesperline_align     = { 32, 32, 32, 32, 32 },
+               .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     = { 32, 32, 32, 32, 32 },
+               .flags                  = 0,
+               .mmal_fmt               = MMAL_ENCODING_BGRA,
+               .size_multiplier_x2     = 2,
+       }, {
+               .fourcc                 = V4L2_PIX_FMT_RGBA32,
+               .depth                  = 32,
+               .bytesperline_align     = { 32, 32, 32, 32 },
+               .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 platform_device *pdev;
+
+       /* 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];
+
+       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 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 platform_device *pdev;
+       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.num = ctx->framerate_num;
+               port->es.video.frame_rate.den = 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.num = ctx->framerate_num;
+                       port->es.video.frame_rate.den = 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.num;
+       q_data->aspect_ratio.denominator = format->es.video.par.den;
+
+       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 > MAX_W)
+               f->fmt.pix_mp.width = MAX_W;
+       if (f->fmt.pix_mp.height > MAX_H)
+               f->fmt.pix_mp.height = 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:
+               break;
+       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:
+               break;
+       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);
+
+       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;
+
+       return 0;
+}
+
+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,
+                                           &param,
+                                           &param_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,
+                                           &param,
+                                           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;
+
+       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:
+               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_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\n");
+               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)
+{
+       struct bcm2835_codec_ctx *ctx = file2ctx(file);
+
+       if (ctx->dev->role != ENCODE && ctx->dev->role != ENCODE_IMAGE)
+               return -EINVAL;
+
+       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_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 = MAX_W;
+       fsize->stepwise.step_width = 2;
+       fsize->stepwise.min_height = MIN_H;
+       fsize->stepwise.max_height = 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,
+                                             &params,
+                                             sizeof(params));
+
+       } 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,
+                                                     &param, 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,
+                                                     &param, 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,
+                                                     &param, 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;
+       /* Add one buffer to take an EOS */
+       port->current_buffer.num = *nbuffers + 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_err(&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->index, 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->pdev->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->pdev->dev;
+       dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+       dst_vq->lock = &ctx->dev->dev_mutex;
+
+       return vb2_queue_init(dst_vq);
+}
+
+/*
+ * 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, 11);
+
+               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);
+               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);
+
+               v4l2_ctrl_new_std(hdl, &bcm2835_codec_ctrl_ops,
+                                 V4L2_CID_MIN_BUFFERS_FOR_CAPTURE,
+                                 1, 1, 1, 1);
+               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);
+
+       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,
+                                           &param_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->pdev->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,
+                                           &param_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->pdev->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 platform_device *pdev = drv->pdev;
+       struct bcm2835_codec_dev *dev;
+       struct video_device *vfd;
+       int function;
+       int video_nr;
+       int ret;
+
+       dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
+       if (!dev)
+               return -ENOMEM;
+
+       dev->pdev = pdev;
+
+       dev->role = role;
+
+       ret = vchiq_mmal_init(&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(&pdev->dev, &dev->v4l2_dev);
+       if (ret)
+               goto vchiq_finalise;
+
+       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_ENCODER_CMD);
+               v4l2_disable_ioctl(vfd, VIDIOC_TRY_ENCODER_CMD);
+               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;
+               break;
+       case DEINTERLACE:
+               v4l2_disable_ioctl(vfd, VIDIOC_ENCODER_CMD);
+               v4l2_disable_ioctl(vfd, VIDIOC_TRY_ENCODER_CMD);
+               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 platform_device *pdev)
+{
+       struct bcm2835_codec_driver *drv;
+       struct media_device *mdev;
+       int ret = 0;
+
+       drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL);
+       if (!drv)
+               return -ENOMEM;
+
+       drv->pdev = pdev;
+       mdev = &drv->mdev;
+       mdev->dev = &pdev->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), "platform:%s",
+                pdev->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;
+
+       platform_set_drvdata(pdev, 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 int bcm2835_codec_remove(struct platform_device *pdev)
+{
+       struct bcm2835_codec_driver *drv = platform_get_drvdata(pdev);
+
+       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);
+
+       return 0;
+}
+
+static struct platform_driver bcm2835_v4l2_codec_driver = {
+       .probe = bcm2835_codec_probe,
+       .remove = bcm2835_codec_remove,
+       .driver = {
+                  .name = "bcm2835-codec",
+                  .owner = THIS_MODULE,
+                  },
+};
+
+module_platform_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("platform:bcm2835-codec");
diff --git a/drivers/staging/vc04_services/bcm2835-isp/Kconfig b/drivers/staging/vc04_services/bcm2835-isp/Kconfig
new file mode 100644 (file)
index 0000000..71b14ac
--- /dev/null
@@ -0,0 +1,14 @@
+config VIDEO_ISP_BCM2835
+       tristate "BCM2835 ISP support"
+       depends on MEDIA_SUPPORT
+       depends on VIDEO_V4L2 && (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 (file)
index 0000000..42d3081
--- /dev/null
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0
+bcm2835-isp-objs := bcm2835-v4l2-isp.o
+
+obj-$(CONFIG_VIDEO_ISP_BCM2835) += bcm2835-isp.o
+
+ccflags-y += \
+       -I$(srctree)/drivers/staging/vc04_services \
+       -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 (file)
index 0000000..1726057
--- /dev/null
@@ -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 (file)
index 0000000..5ab232f
--- /dev/null
@@ -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 (file)
index 0000000..b674992
--- /dev/null
@@ -0,0 +1,1814 @@
+// 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 <linux/platform_device.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 "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
+
+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 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 mmal_parameter_rational digital_gain = {
+               .num = gain,
+               .den = 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->index, 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) {
+               struct bcm2835_isp_lens_shading ls;
+               /*
+                * 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(&ls, 0, sizeof(ls));
+               /* Must set a valid grid size for the FW */
+               ls.grid_cell_size = 16;
+               set_isp_param(&dev->node[0],
+                             MMAL_PARAMETER_LENS_SHADING_OVERRIDE,
+                             &ls, sizeof(ls));
+
+               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 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;
+               struct mmal_parameter_lens_shading_v2 ls;
+               struct dma_buf *dmabuf;
+               void *vcsm_handle;
+
+               v4l2_ls = (struct bcm2835_isp_lens_shading *)ctrl->p_new.p_u8;
+               /*
+                * 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(&ls, v4l2_ls, sizeof(ls));
+
+               dmabuf = dma_buf_get(v4l2_ls->dmabuf);
+               if (IS_ERR_OR_NULL(dmabuf))
+                       return -EINVAL;
+
+               ret = vc_sm_cma_import_dmabuf(dmabuf, &vcsm_handle);
+               if (ret) {
+                       dma_buf_put(dmabuf);
+                       return -EINVAL;
+               }
+
+               ls.mem_handle_table = vc_sm_cma_int_handle(vcsm_handle);
+               if (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,
+                                           &ls,
+                                           sizeof(ls));
+               else
+                       ret = -EINVAL;
+
+               vc_sm_cma_free(vcsm_handle);
+               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);
+
+               f->fmt.pix.bytesperline = get_bytesperline(f->fmt.pix.width,
+                                                          fmt);
+               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, &param_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 platform_device *pdev,
+                                     struct bcm2835_isp_dev **dev_int,
+                                     unsigned int instance)
+{
+       struct bcm2835_isp_dev *dev;
+       unsigned int i;
+       int ret;
+
+       dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
+       if (!dev)
+               return -ENOMEM;
+
+       *dev_int = dev;
+       dev->dev = &pdev->dev;
+
+       ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev);
+       if (ret)
+               return ret;
+
+       ret = vchiq_mmal_init(&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 int bcm2835_isp_remove(struct platform_device *pdev)
+{
+       struct bcm2835_isp_dev **bcm2835_isp_instances;
+       unsigned int i;
+
+       bcm2835_isp_instances = platform_get_drvdata(pdev);
+       for (i = 0; i < BCM2835_ISP_NUM_INSTANCES; i++) {
+               if (bcm2835_isp_instances[i])
+                       bcm2835_isp_remove_instance(bcm2835_isp_instances[i]);
+       }
+
+       return 0;
+}
+
+static int bcm2835_isp_probe(struct platform_device *pdev)
+{
+       struct bcm2835_isp_dev **bcm2835_isp_instances;
+       unsigned int i;
+       int ret;
+
+       bcm2835_isp_instances = devm_kzalloc(&pdev->dev,
+                                            sizeof(bcm2835_isp_instances) *
+                                                     BCM2835_ISP_NUM_INSTANCES,
+                                            GFP_KERNEL);
+       if (!bcm2835_isp_instances)
+               return -ENOMEM;
+
+       platform_set_drvdata(pdev, bcm2835_isp_instances);
+
+       for (i = 0; i < BCM2835_ISP_NUM_INSTANCES; i++) {
+               ret = bcm2835_isp_probe_instance(pdev,
+                                                &bcm2835_isp_instances[i], i);
+               if (ret)
+                       goto error;
+       }
+
+       dev_info(&pdev->dev, "Loaded V4L2 %s\n", BCM2835_ISP_NAME);
+       return 0;
+
+error:
+       bcm2835_isp_remove(pdev);
+
+       return ret;
+}
+
+static struct platform_driver bcm2835_isp_pdrv = {
+       .probe = bcm2835_isp_probe,
+       .remove = bcm2835_isp_remove,
+       .driver = {
+                       .name = BCM2835_ISP_NAME,
+                 },
+};
+
+module_platform_driver(bcm2835_isp_pdrv);
+
+MODULE_DESCRIPTION("BCM2835 ISP driver");
+MODULE_AUTHOR("Naushir Patuck <naush@raspberrypi.com>");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("1.0");
+MODULE_ALIAS("platform: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 (file)
index 0000000..107460a
--- /dev/null
@@ -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 */
index 099359f..3e9bd2a 100644 (file)
@@ -22,6 +22,7 @@
 #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>
@@ -51,6 +52,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 "."
 /* Run time control of log level, based on KERN_XXX level. */
 int vchiq_arm_log_level = VCHIQ_LOG_DEFAULT;
 int vchiq_susp_log_level = VCHIQ_LOG_ERROR;
+module_param_named(arm_log_level, vchiq_arm_log_level, int, 0644);
+module_param_named(susp_log_level, vchiq_susp_log_level, int, 0644);
+module_param_named(core_log_level, vchiq_core_log_level, int, 0644);
+module_param_named(core_msg_log_level, vchiq_core_msg_log_level, int, 0644);
+module_param_named(sync_log_level, vchiq_sync_log_level, int, 0644);
 
 DEFINE_SPINLOCK(msg_queue_spinlock);
 struct vchiq_state g_state;
 
 static struct platform_device *bcm2835_camera;
 static struct platform_device *bcm2835_audio;
+static struct platform_device *bcm2835_codec;
+static struct platform_device *vcsm_cma;
+static struct platform_device *bcm2835_isp;
 
 static struct vchiq_drvdata bcm2835_drvdata = {
        .cache_line_size = 32,
@@ -76,6 +87,11 @@ static struct vchiq_drvdata bcm2836_drvdata = {
        .cache_line_size = 64,
 };
 
+static struct vchiq_drvdata bcm2711_drvdata = {
+       .cache_line_size = 64,
+       .use_36bit_addrs = true,
+};
+
 struct vchiq_2835_state {
        int inited;
        struct vchiq_arm_state arm_state;
@@ -85,6 +101,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;
@@ -105,11 +122,14 @@ static void __iomem *g_regs;
  * of 32.
  */
 static unsigned int g_cache_line_size = 32;
+static struct dma_pool *g_dma_pool;
+static unsigned int g_use_36bit_addrs = 0;
 static unsigned int g_fragments_size;
 static char *g_fragments_base;
 static char *g_free_fragments;
 static struct semaphore g_free_fragments_sema;
 static struct device *g_dev;
+static struct device *g_dma_dev;
 
 static DEFINE_SEMAPHORE(g_free_fragments_mutex);
 
@@ -139,15 +159,21 @@ static void
 cleanup_pagelistinfo(struct vchiq_pagelist_info *pagelistinfo)
 {
        if (pagelistinfo->scatterlist_mapped) {
-               dma_unmap_sg(g_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(g_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(g_dev, pagelistinfo->pagelist_buffer_size,
+                                 pagelistinfo->pagelist,
+                                 pagelistinfo->dma_addr);
+       }
 }
 
 /* There is a potential problem with partial cache lines (pages?)
@@ -168,6 +194,7 @@ create_pagelist(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;
@@ -197,8 +224,16 @@ create_pagelist(char *buf, char __user *ubuf,
        /* Allocate enough storage to hold the page pointers and the page
         * list
         */
-       pagelist = dma_alloc_coherent(g_dev, pagelist_size, &dma_addr,
-                                     GFP_KERNEL);
+       if (pagelist_size > VCHIQ_DMA_POOL_SIZE) {
+               pagelist = dma_alloc_coherent(g_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;
+       }
 
        vchiq_log_trace(vchiq_arm_log_level, "%s - %pK", __func__, pagelist);
 
@@ -219,6 +254,7 @@ create_pagelist(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;
@@ -288,7 +324,7 @@ create_pagelist(char *buf, char __user *ubuf,
                count -= len;
        }
 
-       dma_buffers = dma_map_sg(g_dev,
+       dma_buffers = dma_map_sg(g_dma_dev,
                                 scatterlist,
                                 num_pages,
                                 pagelistinfo->dma_dir);
@@ -302,25 +338,60 @@ create_pagelist(char *buf, char __user *ubuf,
 
        /* Combine adjacent blocks for performance */
        k = 0;
-       for_each_sg(scatterlist, sg, dma_buffers, i) {
-               u32 len = sg_dma_len(sg);
-               u32 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 (k > 0 &&
-                   ((addrs[k - 1] & PAGE_MASK) +
-                    (((addrs[k - 1] & ~PAGE_MASK) + 1) << PAGE_SHIFT))
-                   == (addr & PAGE_MASK))
-                       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) {
+                       u32 len = sg_dma_len(sg);
+                       u64 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) {
+                       u32 len = sg_dma_len(sg);
+                       u32 addr = sg_dma_address(sg);
+                       u32 new_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));
+                       if (k > 0 &&
+                           ((addrs[k - 1] & PAGE_MASK) +
+                            (((addrs[k - 1] & ~PAGE_MASK) + 1) << PAGE_SHIFT))
+                           == (addr & PAGE_MASK))
+                               addrs[k - 1] += new_pages;
+                       else
+                               addrs[k++] = (addr & PAGE_MASK) | (new_pages - 1);
+               }
        }
 
        /* Partial cache lines (fragments) require special measures */
@@ -364,7 +435,7 @@ free_pagelist(struct vchiq_pagelist_info *pagelistinfo,
         * NOTE: dma_unmap_sg must be called before the
         * cpu can touch any of the data/pages.
         */
-       dma_unmap_sg(g_dev, pagelistinfo->scatterlist,
+       dma_unmap_sg(g_dma_dev, pagelistinfo->scatterlist,
                     pagelistinfo->num_pages, pagelistinfo->dma_dir);
        pagelistinfo->scatterlist_mapped = 0;
 
@@ -422,6 +493,7 @@ free_pagelist(struct vchiq_pagelist_info *pagelistinfo,
 int vchiq_platform_init(struct platform_device *pdev, struct vchiq_state *state)
 {
        struct device *dev = &pdev->dev;
+       struct device *dma_dev = NULL;
        struct vchiq_drvdata *drvdata = platform_get_drvdata(pdev);
        struct rpi_firmware *fw = drvdata->fw;
        struct vchiq_slot_zero *vchiq_slot_zero;
@@ -443,6 +515,24 @@ int vchiq_platform_init(struct platform_device *pdev, struct vchiq_state *state)
        g_cache_line_size = drvdata->cache_line_size;
        g_fragments_size = 2 * g_cache_line_size;
 
+       if (drvdata->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(g_fragments_size * MAX_FRAGMENTS);
@@ -455,13 +545,14 @@ int vchiq_platform_init(struct platform_device *pdev, struct vchiq_state *state)
        }
 
        WARN_ON(((unsigned long)slot_mem & (PAGE_SIZE - 1)) != 0);
+       channelbase = slot_phys;
 
        vchiq_slot_zero = vchiq_init_slots(slot_mem, slot_mem_size);
        if (!vchiq_slot_zero)
                return -EINVAL;
 
        vchiq_slot_zero->platform_data[VCHIQ_PLATFORM_FRAGMENTS_OFFSET_IDX] =
-               (int)slot_phys + slot_mem_size;
+               channelbase + slot_mem_size;
        vchiq_slot_zero->platform_data[VCHIQ_PLATFORM_FRAGMENTS_COUNT_IDX] =
                MAX_FRAGMENTS;
 
@@ -495,7 +586,6 @@ int vchiq_platform_init(struct platform_device *pdev, struct vchiq_state *state)
        }
 
        /* Send the base address of the slots to VideoCore */
-       channelbase = slot_phys;
        err = rpi_firmware_property(fw, RPI_FIRMWARE_VCHIQ_INIT,
                                    &channelbase, sizeof(channelbase));
        if (err || channelbase) {
@@ -504,6 +594,15 @@ int vchiq_platform_init(struct platform_device *pdev, struct vchiq_state *state)
        }
 
        g_dev = dev;
+       g_dma_dev = dma_dev ?: dev;
+       g_dma_pool = dmam_pool_create("vchiq_scatter_pool", dev,
+                                     VCHIQ_DMA_POOL_SIZE, g_cache_line_size,
+                                     0);
+       if (!g_dma_pool) {
+               dev_err(dev, "failed to create dma pool");
+               return -ENOMEM;
+       }
+
        vchiq_log_info(vchiq_arm_log_level,
                "vchiq_init - done (slots %pK, phys %pad)",
                vchiq_slot_zero, &slot_phys);
@@ -1765,6 +1864,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_drvdata },
        { .compatible = "brcm,bcm2836-vchiq", .data = &bcm2836_drvdata },
+       { .compatible = "brcm,bcm2711-vchiq", .data = &bcm2711_drvdata },
        {},
 };
 MODULE_DEVICE_TABLE(of, vchiq_of_match);
@@ -1774,6 +1874,7 @@ vchiq_register_child(struct platform_device *pdev, const char *name)
 {
        struct platform_device_info pdevinfo;
        struct platform_device *child;
+       struct device_node *np;
 
        memset(&pdevinfo, 0, sizeof(pdevinfo));
 
@@ -1782,12 +1883,32 @@ vchiq_register_child(struct platform_device *pdev, const char *name)
        pdevinfo.id = PLATFORM_DEVID_NONE;
        pdevinfo.dma_mask = DMA_BIT_MASK(32);
 
+       np = of_get_child_by_name(pdev->dev.of_node, name);
+
+       /* Skip the child if it is explicitly disabled */
+       if (np && !of_device_is_available(np))
+               return NULL;
+
        child = platform_device_register_full(&pdevinfo);
        if (IS_ERR(child)) {
                dev_warn(&pdev->dev, "%s not registered\n", name);
                child = NULL;
        }
 
+       child->dev.of_node = np;
+
+       /*
+        * We want the dma-ranges etc to be copied from the parent VCHIQ device
+        * to be passed on to the children without a node of their own.
+        */
+       if (!np)
+               np = pdev->dev.of_node;
+
+       of_dma_configure(&child->dev, np, true);
+
+       if (np != pdev->dev.of_node)
+               of_node_put(np);
+
        return child;
 }
 
@@ -1838,8 +1959,11 @@ static int vchiq_probe(struct platform_device *pdev)
                goto error_exit;
        }
 
+       vcsm_cma = vchiq_register_child(pdev, "vcsm-cma");
+       bcm2835_codec = vchiq_register_child(pdev, "bcm2835-codec");
        bcm2835_camera = vchiq_register_child(pdev, "bcm2835-camera");
        bcm2835_audio = vchiq_register_child(pdev, "bcm2835_audio");
+       bcm2835_isp = vchiq_register_child(pdev, "bcm2835-isp");
 
        return 0;
 
@@ -1851,8 +1975,11 @@ error_exit:
 
 static int vchiq_remove(struct platform_device *pdev)
 {
+       platform_device_unregister(bcm2835_isp);
        platform_device_unregister(bcm2835_audio);
        platform_device_unregister(bcm2835_camera);
+       platform_device_unregister(bcm2835_codec);
+       platform_device_unregister(vcsm_cma);
        vchiq_debugfs_deinit();
        vchiq_deregister_chrdev();
 
index e8e39a1..b7653e8 100644 (file)
@@ -61,6 +61,7 @@ struct vchiq_arm_state {
 
 struct vchiq_drvdata {
        const unsigned int cache_line_size;
+       const bool use_36bit_addrs;
        struct rpi_firmware *fw;
 };
 
diff --git a/drivers/staging/vc04_services/vc-sm-cma/Kconfig b/drivers/staging/vc04_services/vc-sm-cma/Kconfig
new file mode 100644 (file)
index 0000000..d812021
--- /dev/null
@@ -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 (file)
index 0000000..c92a577
--- /dev/null
@@ -0,0 +1,12 @@
+ccflags-y += \
+       -I$(srctree)/$(src)/../ \
+       -I$(srctree)/$(src)/../interface/vchiq_arm\
+       -I$(srctree)/$(src)/../include
+
+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 (file)
index 0000000..ac9b5f8
--- /dev/null
@@ -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 (file)
index 0000000..268f97e
--- /dev/null
@@ -0,0 +1,1714 @@
+// 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/platform_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 "vchiq_connected.h"
+#include "vc_sm_cma_vchi.h"
+
+#include "vc_sm.h"
+#include "vc_sm_knl.h"
+#include <linux/broadcom/vc_sm_cma_ioctl.h>
+
+/* ---- 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 platform_device *pdev;
+
+       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 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)
+{
+       return idr_find(&sm_state->kernelid_map, handle);
+}
+
+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->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->pdev->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 mutex *lock = &attachment->dmabuf->lock;
+       struct sg_table *table;
+
+       mutex_lock(lock);
+       pr_debug("%s attachment %p\n", __func__, attachment);
+       table = &a->sg_table;
+
+       /* return previously mapped sg table */
+       if (a->dma_dir == direction) {
+               mutex_unlock(lock);
+               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");
+               mutex_unlock(lock);
+               return ERR_PTR(-EIO);
+       }
+
+       a->dma_dir = direction;
+       mutex_unlock(lock);
+
+       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);
+
+       mutex_lock(&buf->lock);
+
+       /* now map it to userspace */
+       vma->vm_pgoff = 0;
+
+       ret = dma_mmap_coherent(&sm_state->pdev->dev, vma, buf->cookie,
+                               buf->dma_addr, buf->size);
+
+       if (ret) {
+               pr_err("Remapping memory failed, error: %d\n", ret);
+               return ret;
+       }
+
+       vma->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP;
+
+       mutex_unlock(&buf->lock);
+
+       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 = 0;
+
+       /* 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. */
+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->pdev->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.\n",
+                __func__, import.name, import.type, &dma_addr, import.size);
+
+       /* 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 = 1;
+       buffer->import.dma_buf = dma_buf;
+
+       buffer->import.attach = attach;
+       buffer->import.sgt = sgt;
+       buffer->dma_addr = dma_addr;
+       buffer->in_use = 1;
+       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->pdev->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->pdev->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.
+ */
+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->pdev->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->pdev->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->pdev->dev, buffer->size,
+                                         buffer->cookie, buffer->dma_addr);
+               kfree(buffer);
+       }
+       return ret;
+}
+
+#ifndef CONFIG_ARM64
+/* Converts VCSM_CACHE_OP_* to an operating function. */
+static void (*cache_op_to_func(const unsigned int cache_op))
+                                               (const void*, const void*)
+{
+       switch (cache_op) {
+       case VC_SM_CACHE_OP_NOP:
+               return NULL;
+
+       case VC_SM_CACHE_OP_INV:
+               return dmac_inv_range;
+       case VC_SM_CACHE_OP_CLEAN:
+               return dmac_clean_range;
+       case VC_SM_CACHE_OP_FLUSH:
+               return dmac_flush_range;
+
+       default:
+               pr_err("[%s]: Invalid cache_op: 0x%08x\n", __func__, cache_op);
+               return NULL;
+       }
+}
+
+/*
+ * Clean/invalid/flush cache of which buffer is already pinned (i.e. accessed).
+ */
+static int clean_invalid_contig_2d(const void __user *addr,
+                                  const size_t block_count,
+                                  const size_t block_size,
+                                  const size_t stride,
+                                  const unsigned int cache_op)
+{
+       size_t i;
+       void (*op_fn)(const void *start, const void *end);
+
+       if (!block_size) {
+               pr_err("[%s]: size cannot be 0\n", __func__);
+               return -EINVAL;
+       }
+
+       op_fn = cache_op_to_func(cache_op);
+       if (!op_fn)
+               return -EINVAL;
+
+       for (i = 0; i < block_count; i ++, addr += stride)
+               op_fn(addr, addr + block_size);
+
+       return 0;
+}
+
+static int vc_sm_cma_clean_invalid2(unsigned int cmdnr, unsigned long arg)
+{
+       struct vc_sm_cma_ioctl_clean_invalid2 ioparam;
+       struct vc_sm_cma_ioctl_clean_invalid_block *block = NULL;
+       int i, ret = 0;
+
+       /* Get parameter data. */
+       if (copy_from_user(&ioparam, (void *)arg, sizeof(ioparam))) {
+               pr_err("[%s]: failed to copy-from-user header for cmd %x\n",
+                      __func__, cmdnr);
+               return -EFAULT;
+       }
+       block = kmalloc(ioparam.op_count * sizeof(*block), GFP_KERNEL);
+       if (!block)
+               return -EFAULT;
+
+       if (copy_from_user(block, (void *)(arg + sizeof(ioparam)),
+                          ioparam.op_count * sizeof(*block)) != 0) {
+               pr_err("[%s]: failed to copy-from-user payload for cmd %x\n",
+                      __func__, cmdnr);
+               ret = -EFAULT;
+               goto out;
+       }
+
+       for (i = 0; i < ioparam.op_count; i++) {
+               const struct vc_sm_cma_ioctl_clean_invalid_block * const op =
+                                                               block + i;
+
+               if (op->invalidate_mode == VC_SM_CACHE_OP_NOP)
+                       continue;
+
+               ret = clean_invalid_contig_2d((void __user *)op->start_address,
+                                             op->block_count, op->block_size,
+                                             op->inter_block_stride,
+                                             op->invalidate_mode);
+               if (ret)
+                       break;
+       }
+out:
+       kfree(block);
+
+       return ret;
+}
+#endif
+
+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;
+       }
+
+#ifndef CONFIG_ARM64
+       /*
+        * Flush/Invalidate the cache for a given mapping.
+        * Blocks must be pinned (i.e. accessed) before this call.
+        */
+       case VC_SM_CMA_CMD_CLEAN_INVALID2:
+               ret = vc_sm_cma_clean_invalid2(cmdnr, arg);
+               break;
+#endif
+
+       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 vchiq_instance *vchiq_instance;
+       struct vc_sm_version version;
+       struct vc_sm_result_t version_result;
+
+       pr_info("[%s]: start\n", __func__);
+
+       /*
+        * Initialize and create a VCHI connection for the shared memory service
+        * running on videocore.
+        */
+       ret = vchiq_initialise(&vchiq_instance);
+       if (ret) {
+               pr_err("[%s]: failed to initialise VCHI instance (ret=%d)\n",
+                      __func__, ret);
+
+               return;
+       }
+
+       ret = vchiq_connect(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(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->sm_handle);
+}
+
+/* Driver loading. */
+static int bcm2835_vc_sm_cma_probe(struct platform_device *pdev)
+{
+       pr_info("%s: Videocore shared memory driver\n", __func__);
+
+       sm_state = devm_kzalloc(&pdev->dev, sizeof(*sm_state), GFP_KERNEL);
+       if (!sm_state)
+               return -ENOMEM;
+       sm_state->pdev = pdev;
+       mutex_init(&sm_state->map_lock);
+
+       spin_lock_init(&sm_state->kernelid_map_lock);
+       idr_init_base(&sm_state->kernelid_map, 1);
+
+       pdev->dev.dma_parms = devm_kzalloc(&pdev->dev,
+                                          sizeof(*pdev->dev.dma_parms),
+                                          GFP_KERNEL);
+       /* dma_set_max_seg_size checks if dma_parms is NULL. */
+       dma_set_max_seg_size(&pdev->dev, 0x3FFFFFFF);
+
+       vchiq_add_connected_callback(vc_sm_connected_init);
+       return 0;
+}
+
+/* Driver unloading. */
+static int bcm2835_vc_sm_cma_remove(struct platform_device *pdev)
+{
+       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->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__);
+       return 0;
+}
+
+/* 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 platform_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_platform_driver(bcm2835_vcsm_cma_driver);
+
+MODULE_AUTHOR("Dave Stevenson");
+MODULE_DESCRIPTION("VideoCore CMA Shared Memory Driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform: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 (file)
index 0000000..f1c7b95
--- /dev/null
@@ -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];
+
+       int in_use:1;   /* Kernel is still using this resource */
+       int 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 (file)
index 0000000..dc0829b
--- /dev/null
@@ -0,0 +1,505 @@
+// 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;
+
+};
+
+/* ---- Private Variables ------------------------------------------------ */
+
+/* ---- Private Function Prototypes -------------------------------------- */
+
+/* ---- Private Functions ------------------------------------------------ */
+static int
+bcm2835_vchi_msg_queue(unsigned int handle,
+                      void *data,
+                      unsigned int size)
+{
+       return vchiq_queue_kernel_message(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->service_handle[0]);
+               svc_use = 0;
+
+               if (wait_for_completion_interruptible(&instance->io_cmplt))
+                       continue;
+               vchiq_use_service(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->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->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->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 enum vchiq_status vc_sm_cma_vchi_callback(enum vchiq_reason reason,
+                                                struct vchiq_header *header,
+                                                unsigned int handle, void *userdata)
+{
+       struct sm_instance *instance = vchiq_get_service_userdata(handle);
+
+       switch (reason) {
+       case VCHIQ_MESSAGE_AVAILABLE:
+               vchiq_msg_queue_push(handle, header);
+               complete(&instance->io_cmplt);
+               break;
+
+       case VCHIQ_SERVICE_CLOSED:
+               pr_info("%s: service CLOSED!!", __func__);
+               break;
+
+       default:
+               break;
+       }
+
+       return VCHIQ_SUCCESS;
+}
+
+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);
+       }
+
+       /* 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, &params,
+                                           &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(instance->service_handle[i]);
+       }
+       kfree(instance);
+err_null:
+       pr_debug("%s: FAILED", __func__);
+       return NULL;
+}
+
+int vc_sm_cma_vchi_stop(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(instance->service_handle[i]);
+               vchiq_close_service(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 (file)
index 0000000..ed881c5
--- /dev/null
@@ -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 <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 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 (file)
index 0000000..4e63540
--- /dev/null
@@ -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 (file)
index 0000000..988fdd9
--- /dev/null
@@ -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__ */
index c99525a..5df9198 100644 (file)
@@ -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.
index 5bd7410..72e5a03 100644 (file)
@@ -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;
index 2be9941..59f7ad3 100644 (file)
  */
 #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 */
index a118efd..e0c331e 100644 (file)
@@ -51,6 +51,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 */
index b636e88..883b77f 100644 (file)
@@ -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 */
 
index a1e39b1..a1a5e6c 100644 (file)
@@ -221,6 +221,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,
 };
 
 struct mmal_parameter_rational {
@@ -313,6 +373,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 {
@@ -339,6 +400,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 {
@@ -580,7 +644,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 */
@@ -711,6 +817,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 {
@@ -749,7 +892,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 mmal_parameter_rational 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 mmal_parameter_rational 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 mmal_parameter_rational slope;
+       struct mmal_parameter_rational strength;
+};
+
+struct mmal_parameter_sharpen {
+       u32 enabled;
+       struct mmal_parameter_rational threshold;
+       struct mmal_parameter_rational strength;
+       struct mmal_parameter_rational 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
index 76d3f03..8719460 100644 (file)
 
 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
 
+#include <linux/completion.h>
 #include <linux/errno.h>
 #include <linux/kernel.h>
+#include <linux/mm.h>
 #include <linux/module.h>
 #include <linux/mutex.h>
-#include <linux/mm.h>
-#include <linux/slab.h>
-#include <linux/completion.h>
-#include <linux/vmalloc.h>
 #include <linux/raspberrypi/vchiq.h>
-#include <media/videobuf2-vmalloc.h>
+#include <linux/vmalloc.h>
+#include <media/videobuf2-v4l2.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
@@ -143,6 +155,8 @@ struct mmal_msg_context {
                        /* Presentation and Decode timestamps */
                        s64 pts;
                        s64 dts;
+                       /* MMAL buffer command flag */
+                       u32 cmd;
 
                        int status;     /* context status */
 
@@ -233,18 +247,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
@@ -266,13 +268,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
@@ -350,6 +357,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);
@@ -384,7 +392,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) {
@@ -423,14 +432,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,
@@ -452,6 +474,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)
@@ -459,8 +578,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;
@@ -485,6 +604,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 &
@@ -714,42 +849,43 @@ 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",
-                    port->es.video.width, port->es.video.height,
-                    port->es.video.color_space);
-
-               pr_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",
-                        port->es.video.frame_rate.num,
-                        port->es.video.frame_rate.den,
-                        port->es.video.par.num, port->es.video.par.den);
+               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_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_dbg_lvl(3, debug, "           : framerate %d/%d  aspect %d/%d\n",
+                          port->es.video.frame_rate.num,
+                          port->es.video.frame_rate.den,
+                          port->es.video.par.num, port->es.video.par.den);
        }
 }
 
@@ -780,7 +916,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);
@@ -823,8 +959,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->service_handle, rmsg_handle);
@@ -914,13 +1050,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->service_handle, rmsg_handle);
 
@@ -965,9 +1101,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->service_handle, rmsg_handle);
@@ -1136,10 +1272,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->service_handle, rmsg_handle);
@@ -1183,11 +1318,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->service_handle, rmsg_handle);
@@ -1226,9 +1361,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->service_handle, rmsg_handle);
@@ -1286,8 +1421,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->service_handle, rmsg_handle);
@@ -1332,6 +1468,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);
                        }
@@ -1363,6 +1500,8 @@ static int port_enable(struct vchiq_mmal_instance *instance,
 
        port->enabled = 1;
 
+       atomic_set(&port->buffers_with_vpu, 0);
+
        if (port->buffer_cb) {
                /* send buffer headers to videocore */
                hdr_count = 1;
@@ -1428,6 +1567,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);
@@ -1540,7 +1682,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;
        }
 
@@ -1558,14 +1700,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;
        }
 
@@ -1574,9 +1716,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;
@@ -1596,6 +1738,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. */
@@ -1629,10 +1797,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
  *
  */
@@ -1682,6 +1914,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;
@@ -1692,6 +1925,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++) {
@@ -1703,6 +1937,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++) {
@@ -1714,6 +1949,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;
@@ -1724,6 +1960,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 = 0;
@@ -1751,6 +1988,8 @@ int vchiq_mmal_component_finalise(struct vchiq_mmal_instance *instance,
 
        component->in_use = 0;
 
+       release_all_event_contexts(component);
+
        mutex_unlock(&instance->vchiq_mutex);
 
        return ret;
@@ -1775,7 +2014,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);
 
index 1dc81ec..247521f 100644 (file)
@@ -49,6 +49,7 @@ typedef void (*vchiq_mmal_buffer_cb)(
 
 struct vchiq_mmal_port {
        u32 enabled:1;
+       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 {
index e9bef5c..c6ff8d1 100644 (file)
@@ -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;
        }
 
index 12acb12..629f02f 100644 (file)
@@ -24,7 +24,7 @@
  *       for this trip point
  *    d. if the trend is THERMAL_TREND_DROP_FULL, use lower limit
  *       for this trip point
- * 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
@@ -115,7 +115,7 @@ static void update_passive_instance(struct thermal_zone_device *tz,
 
 static void thermal_zone_trip_update(struct thermal_zone_device *tz, int trip)
 {
-       int trip_temp;
+       int trip_temp, hyst_temp;
        enum thermal_trip_type trip_type;
        enum thermal_trend trend;
        struct thermal_instance *instance;
@@ -125,15 +125,19 @@ static void thermal_zone_trip_update(struct thermal_zone_device *tz, int trip)
        tz->ops->get_trip_temp(tz, trip, &trip_temp);
        tz->ops->get_trip_type(tz, trip, &trip_type);
 
-       trend = get_tz_trend(tz, trip);
-
-       if (tz->temperature >= trip_temp) {
-               throttle = true;
-               trace_thermal_zone_trip(tz, trip, trip_type);
+       tz->ops->get_trip_temp(tz, trip, &trip_temp);
+       hyst_temp = trip_temp;
+       if (tz->ops->get_trip_hyst) {
+               tz->ops->get_trip_hyst(tz, trip, &hyst_temp);
+               hyst_temp = trip_temp - hyst_temp;
        }
+       tz->ops->get_trip_type(tz, trip, &trip_type);
 
-       dev_dbg(&tz->device, "Trip%d[type=%d,temp=%d]:trend=%d,throttle=%d\n",
-                               trip, trip_type, trip_temp, trend, throttle);
+       trend = get_tz_trend(tz, trip);
+
+       dev_dbg(&tz->device,
+               "Trip%d[type=%d,temp=%d,hyst=%d]:trend=%d,throttle=%d\n",
+               trip, trip_type, trip_temp, hyst_temp, trend, throttle);
 
        mutex_lock(&tz->lock);
 
@@ -142,6 +146,18 @@ static void thermal_zone_trip_update(struct thermal_zone_device *tz, int trip)
                        continue;
 
                old_target = instance->target;
+               throttle = false;
+               /*
+                * Lower the mitigation only if the temperature
+                * goes below the hysteresis temperature.
+                */
+               if (tz->temperature >= trip_temp ||
+                  (tz->temperature >= hyst_temp &&
+                  old_target == instance->upper)) {
+                       throttle = true;
+                       trace_thermal_zone_trip(tz, trip, trip_type);
+               }
+
                instance->target = get_target_state(instance, trend, throttle);
                dev_dbg(&instance->cdev->device, "old_target=%d, target=%d\n",
                                        old_target, (int)instance->target);
index fd95860..fc36e59 100644 (file)
@@ -148,6 +148,13 @@ static int bcm2835aux_serial_probe(struct platform_device *pdev)
         */
        up.port.uartclk = clk_get_rate(data->clk) * 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) {
index 300a8bb..740389f 100644 (file)
@@ -1432,6 +1432,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;
@@ -1492,6 +1493,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 (!pl011_tx_char(uap, xmit->buf[xmit->tail], from_irq))
                        break;
 
@@ -1727,6 +1732,23 @@ static void pl011_put_poll_char(struct uart_port *port,
 
 #endif /* CONFIG_CONSOLE_POLL */
 
+unsigned long pl011_clk_round(unsigned long clk)
+{
+       unsigned long scaler;
+
+       /*
+        * If increasing a clock by less than 0.1% changes it
+        * from ..999.. to ..000.., round up.
+        */
+       scaler = 1;
+       while (scaler * 100000 < clk)
+               scaler *= 10;
+       if ((clk + scaler - 1)/scaler % 1000 == 0)
+               clk = (clk/scaler + 1) * scaler;
+
+       return clk;
+}
+
 static int pl011_hwinit(struct uart_port *port)
 {
        struct uart_amba_port *uap =
@@ -1743,7 +1765,7 @@ static int pl011_hwinit(struct uart_port *port)
        if (retval)
                return retval;
 
-       uap->port.uartclk = clk_get_rate(uap->clk);
+       uap->port.uartclk = pl011_clk_round(clk_get_rate(uap->clk));
 
        /* Clear pending error and receive interrupts */
        pl011_write(UART011_OEIS | UART011_BEIS | UART011_PEIS |
@@ -2440,7 +2462,7 @@ static int pl011_console_setup(struct console *co, char *options)
                        plat->init();
        }
 
-       uap->port.uartclk = clk_get_rate(uap->clk);
+       uap->port.uartclk = pl011_clk_round(clk_get_rate(uap->clk));
 
        if (uap->vendor->fixed_options) {
                baud = uap->fixed_baud;
@@ -2657,6 +2679,7 @@ static struct uart_driver amba_reg = {
        .cons                   = AMBA_CONSOLE,
 };
 
+#if 0
 static int pl011_probe_dt_alias(int index, struct device *dev)
 {
        struct device_node *np;
@@ -2688,6 +2711,7 @@ static int pl011_probe_dt_alias(int index, struct device *dev)
 
        return ret;
 }
+#endif
 
 /* unregisters the driver also if no more ports are left */
 static void pl011_unregister_port(struct uart_amba_port *uap)
@@ -2744,7 +2768,12 @@ static int pl011_setup_port(struct device *dev, struct uart_amba_port *uap,
        if (IS_ERR(base))
                return PTR_ERR(base);
 
+       /* Don't use DT serial<n> aliases - it causes the device to
+          be renumbered to ttyAMA1 if it is the second serial port in the
+          system, even though the other one is ttyS0. The 8250 driver
+          doesn't use this logic, so always remains ttyS0.
        index = pl011_probe_dt_alias(index, dev);
+       */
 
        uap->old_cr = 0;
        uap->port.dev = dev;
@@ -2810,6 +2839,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);
index 0ab7880..e30d7d3 100644 (file)
@@ -523,8 +523,9 @@ static int sc16is7xx_set_baud(struct uart_port *port, int baud)
 
        /* Enable enhanced features */
        regcache_cache_bypass(s->regmap, true);
-       sc16is7xx_port_write(port, SC16IS7XX_EFR_REG,
-                            SC16IS7XX_EFR_ENABLE_BIT);
+       sc16is7xx_port_update(port, SC16IS7XX_EFR_REG,
+                             SC16IS7XX_EFR_ENABLE_BIT,
+                             SC16IS7XX_EFR_ENABLE_BIT);
        regcache_cache_bypass(s->regmap, false);
 
        /* Put LCR back to the normal mode */
@@ -696,6 +697,8 @@ static bool sc16is7xx_port_irq(struct sc16is7xx_port *s, int portno)
                        rxlen = sc16is7xx_port_read(port, SC16IS7XX_RXLVL_REG);
                        if (rxlen)
                                sc16is7xx_handle_rx(port, rxlen, iir);
+                       else
+                               return false;
                        break;
                case SC16IS7XX_IIR_THRI_SRC:
                        sc16is7xx_handle_tx(port);
@@ -840,7 +843,7 @@ static unsigned int sc16is7xx_get_mctrl(struct uart_port *port)
        /* DCD and DSR are not wired and CTS/RTS is handled automatically
         * so just indicate DSR and CAR asserted
         */
-       return TIOCM_DSR | TIOCM_CAR;
+       return TIOCM_DSR | TIOCM_CAR | TIOCM_RI | TIOCM_CTS;
 }
 
 static void sc16is7xx_set_mctrl(struct uart_port *port, unsigned int mctrl)
@@ -927,14 +930,19 @@ static void sc16is7xx_set_termios(struct uart_port *port,
        regcache_cache_bypass(s->regmap, true);
        sc16is7xx_port_write(port, SC16IS7XX_XON1_REG, termios->c_cc[VSTART]);
        sc16is7xx_port_write(port, SC16IS7XX_XOFF1_REG, termios->c_cc[VSTOP]);
-       if (termios->c_cflag & CRTSCTS)
+       if (termios->c_cflag & CRTSCTS) {
                flow |= SC16IS7XX_EFR_AUTOCTS_BIT |
                        SC16IS7XX_EFR_AUTORTS_BIT;
+               port->status |= UPSTAT_AUTOCTS;
+       };
        if (termios->c_iflag & IXON)
                flow |= SC16IS7XX_EFR_SWFLOW3_BIT;
        if (termios->c_iflag & IXOFF)
                flow |= SC16IS7XX_EFR_SWFLOW1_BIT;
 
+       /* Always set enable enhanced */
+       flow |= SC16IS7XX_EFR_ENABLE_BIT;
+
        sc16is7xx_port_write(port, SC16IS7XX_EFR_REG, flow);
        regcache_cache_bypass(s->regmap, false);
 
index 643edf5..0e6efcd 100644 (file)
@@ -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/
index 26f9fb9..fe8c7a8 100644 (file)
@@ -190,6 +190,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;
 }
index 9e28d71..c32babf 100644 (file)
@@ -1969,6 +1969,16 @@ reset:
        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,
index ac6c5cc..75aefad 100644 (file)
@@ -5582,7 +5582,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);
index 4d59d92..e804599 100644 (file)
@@ -1268,6 +1268,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
@@ -2140,6 +2155,85 @@ free_interfaces:
        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);
index db67df2..b16e528 100644 (file)
 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/gadget/file_storage.c b/drivers/usb/gadget/file_storage.c
new file mode 100644 (file)
index 0000000..a896d73
--- /dev/null
@@ -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, &current->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);
index c4736d1..b325ebb 100644 (file)
@@ -730,6 +730,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 && (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
index 171de4d..073f314 100644 (file)
@@ -77,6 +77,7 @@ obj-$(CONFIG_USB_SL811_HCD)   += sl811-hcd.o
 obj-$(CONFIG_USB_SL811_CS)     += sl811_cs.o
 obj-$(CONFIG_USB_U132_HCD)     += u132-hcd.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 (file)
index 0000000..f10d466
--- /dev/null
@@ -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 (file)
index 0000000..45db991
--- /dev/null
@@ -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 (file)
index 0000000..0cef7b4
--- /dev/null
@@ -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 (file)
index 0000000..f6839f9
--- /dev/null
@@ -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 (file)
index 0000000..89aa887
--- /dev/null
@@ -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 (file)
index 0000000..5ec2ae2
--- /dev/null
@@ -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 (file)
index 0000000..f86e6f2
--- /dev/null
@@ -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 (file)
index 0000000..6dd04b5
--- /dev/null
@@ -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 (file)
index 0000000..1460afa
--- /dev/null
@@ -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 <asm/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 (file)
index 0000000..49b07e1
--- /dev/null
@@ -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 (file)
index 0000000..3b03532
--- /dev/null
@@ -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 (file)
index 0000000..26fcddc
--- /dev/null
@@ -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 (file)
index 0000000..2b429a3
--- /dev/null
@@ -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 (file)
index 0000000..25c1cc0
--- /dev/null
@@ -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 (file)
index 0000000..4ce560d
--- /dev/null
@@ -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 (file)
index 0000000..ad645ff
--- /dev/null
@@ -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 (file)
index 0000000..2004538
--- /dev/null
@@ -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 (file)
index 0000000..64f00c2
--- /dev/null
@@ -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 (file)
index 0000000..8b3772a
--- /dev/null
@@ -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(&notifier->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, &notifier->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(&notifier->observers)) {
+               observer_t *o;
+
+               DWC_ERROR("Notifier %p has active observers when removing\n", notifier->object);
+               DWC_CIRCLEQ_FOREACH(o, &notifier->observers, list_entry) {
+                       DWC_DEBUGC("    %p watching %s\n", o->observer, o->notification);
+               }
+
+               DWC_ASSERT(DWC_CIRCLEQ_EMPTY(&notifier->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(&notifier->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, &notifier->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 (file)
index 0000000..4a8cdfe
--- /dev/null
@@ -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 (file)
index 0000000..9a86d29
--- /dev/null
@@ -0,0 +1,1276 @@
+/* =========================================================================
+ * $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>
+# include <stdarg.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 (file)
index 0000000..b1cedb1
--- /dev/null
@@ -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 (file)
index 0000000..7ea65a6
--- /dev/null
@@ -0,0 +1,85 @@
+#
+# 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
+
+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 (file)
index 0000000..712b057
--- /dev/null
@@ -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 (file)
index 0000000..f827102
--- /dev/null
@@ -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 <asm/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 (file)
index 0000000..7770e20
--- /dev/null
@@ -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 (file)
index 0000000..ce0618d
--- /dev/null
@@ -0,0 +1,854 @@
+/* ==========================================================================
+ * $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
+ */
+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;
+}
+
+/**
+ * Function is called to write ADP registers
+ */
+void dwc_otg_adp_modify_reg(dwc_otg_core_if_t * core_if, uint32_t clr,
+                           uint32_t set)
+{
+       dwc_otg_adp_write_reg(core_if,
+                             (dwc_otg_adp_read_reg(core_if) & (~clr)) | set);
+}
+
+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.
+ */
+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.
+ */
+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.
+ */
+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_otg_adp_modify_reg(core_if, adpctl.d32, 0);
+       //dwc_otg_adp_write_reg(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 (file)
index 0000000..4110b25
--- /dev/null
@@ -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 (file)
index 0000000..2f8ea77
--- /dev/null
@@ -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 (file)
index 0000000..e10b67f
--- /dev/null
@@ -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 (file)
index 0000000..bbb3d32
--- /dev/null
@@ -0,0 +1,1876 @@
+/* ==========================================================================
+ * 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)
+
+extern dwc_otg_pcd_ep_t *get_ep_by_addr(dwc_otg_pcd_t * pcd, u16 wIndex);
+
+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, &regval, 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 (file)
index 0000000..55fd337
--- /dev/null
@@ -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 (file)
index 0000000..14300ad
--- /dev/null
@@ -0,0 +1,7146 @@
+/* ==========================================================================
+ * $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"
+
+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;
+}
+
+/** Saves GINTMSK register before setting the msk bits. */
+int dwc_otg_save_gintmsk_reg(dwc_otg_core_if_t * core_if)
+{
+       struct dwc_otg_global_regs_backup *gr;
+
+       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->gintmsk_local = DWC_READ_REG32(&core_if->core_global_regs->gintmsk);
+
+       DWC_DEBUGPL(DBG_ANY,"=============Backing GINTMSK registers============\n");
+       DWC_DEBUGPL(DBG_ANY, "Backed up gintmsk   = %08x\n", gr->gintmsk_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);
+
+}
+
+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;
+}
+
+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;
+}
+
+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;
+}
+
+int32_t dwc_otg_get_param_rx_thr_length(dwc_otg_core_if_t * core_if)
+{
+       return core_if->core_params->rx_thr_length;
+}
+
+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.
+ */
+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 (file)
index 0000000..79dbf83
--- /dev/null
@@ -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 (file)
index 0000000..e1c1e38
--- /dev/null
@@ -0,0 +1,1601 @@
+/* ==========================================================================
+ * $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.
+ */
+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.
+ */
+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.
+ */
+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.
+ */
+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.
+ */
+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 restore command after Hibernation
+ * was completed by the core. */
+int32_t dwc_otg_handle_restore_done_intr(dwc_otg_core_if_t * core_if)
+{
+       pcgcctl_data_t pcgcctl;
+       DWC_DEBUGPL(DBG_ANY, "++Restore Done Interrupt++\n");
+
+       //TODO De-assert restore signal. 8.a
+       pcgcctl.d32 = DWC_READ_REG32(core_if->pcgcctl);
+       if (pcgcctl.b.restoremode == 1) {
+               gintmsk_data_t gintmsk = {.d32 = 0 };
+               /*
+                * If restore mode is Remote Wakeup,
+                * unmask Remote Wakeup interrupt.
+                */
+               gintmsk.b.wkupintr = 1;
+               DWC_MODIFY_REG32(&core_if->core_global_regs->gintmsk,
+                                0, gintmsk.d32);
+       }
+
+       return 1;
+}
+
+/**
+ * This interrupt indicates that a device has been disconnected from
+ * the root port.
+ */
+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.
+ */
+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 (file)
index 0000000..4138fd1
--- /dev/null
@@ -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 (file)
index 0000000..ccc24e0
--- /dev/null
@@ -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 (file)
index 0000000..cb576e5
--- /dev/null
@@ -0,0 +1,1772 @@
+/* ==========================================================================
+ * $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"
+
+#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";
+
+
+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 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 int 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
+    );
+
+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
+    );
+
+extern void dwc_otg_adp_start(dwc_otg_core_if_t * core_if, uint8_t is_host);
+
+/*-------------------------------------------------------------------------*/
+/* 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 int 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__);
+                return REM_RETVAL(-ENOMEM);
+       }
+#ifndef DWC_DEVICE_ONLY
+       if (otg_dev->hcd) {
+               hcd_remove(_dev);
+       } else {
+               DWC_DEBUGPL(DBG_ANY, "%s: otg_dev->hcd NULL!\n", __func__);
+                return REM_RETVAL(-EINVAL);
+       }
+#endif
+
+#ifndef DWC_HOST_ONLY
+       if (otg_dev->pcd) {
+               pcd_remove(_dev);
+       } else {
+               DWC_DEBUGPL(DBG_ANY, "%s: otg_dev->pcd NULL!\n", __func__);
+                return REM_RETVAL(-EINVAL);
+       }
+#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__);
+               return REM_RETVAL(-ENXIO);
+       }
+
+       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__);
+               return REM_RETVAL(-ENXIO);
+       }
+
+       /*
+        * 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
+        return REM_RETVAL(0);
+}
+
+/**
+ * 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 (file)
index 0000000..6a8be63
--- /dev/null
@@ -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 (file)
index 0000000..f644acb
--- /dev/null
@@ -0,0 +1,1433 @@
+/*
+ * 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 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_blob *blob =
+               (struct fiq_dma_blob *)(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 = (u32)(uintptr_t)&blob->channel[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_blob *blob =
+               (struct fiq_dma_blob *)(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 = (u32)(uintptr_t)blob->channel[n].index[i].buf;
+       //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 (file)
index 0000000..86b4aaf
--- /dev/null
@@ -0,0 +1,399 @@
+/*
+ * 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_dma_blob {
+       struct fiq_dma_channel channel[0];
+} __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_blob *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[0];
+};
+
+#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 (file)
index 0000000..ffa8d21
--- /dev/null
@@ -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 (file)
index 0000000..e42d8ca
--- /dev/null
@@ -0,0 +1,4363 @@
+
+/* ==========================================================================
+ * $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 */
+
+
+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.
+ */
+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.
+ */
+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);
+}
+
+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_blob *blob = 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(&blob->channel[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
+
+               for (i = 0; i < num_channels; i++) {
+                       hcd->fiq_state->channel[i].fsm = FIQ_PASSTHROUGH;
+               }
+               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 (hc->ep_is_in && wLength < 4)
+                               ptr = hc->xfer_buff;
+
+                       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.
+ */
+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_blob *blob;
+       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.
+                */
+               blob = (struct fiq_dma_blob *)
+                       (uintptr_t)hcd->fiq_state->dma_base;
+               st->hcdma_copy.d32 =(u32)(uintptr_t)
+                       blob->channel[hc->hc_num].index[0].buf;
+
+               /* 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 */
+                       blob = 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(&blob->channel[hc->hc_num].index[i].buf[0], ptr, frame_length);
+                                               st->dma_info.slot_len[i] = frame_length;
+                                               ptr += frame_length;
+                                       } else {
+                                               dwc_memcpy(&blob->channel[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.
+                        */
+                       blob = (struct fiq_dma_blob *)
+                               (uintptr_t)hcd->fiq_state->dma_base;
+                       st->hcdma_copy.d32 = (u32)(uintptr_t)
+                               blob->channel[hc->hc_num].index[0].buf;
+
+                       /* 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.
+ */
+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
+
+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.
+ */
+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 = ((unsigned long) hc->xfer_buff & 0xFFFFFFFF);
+                       }
+               }
+       } else {
+               if (hc->align_buff) {
+                       st->hcdma_copy.d32 = hc->align_buff;
+               } else {
+                       st->hcdma_copy.d32 = ((unsigned long) hc->xfer_buff & 0xFFFFFFFF);
+               }
+       }
+       /* 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 = ((unsigned long) hc->xfer_buff & 0xFFFFFFFF);
+               }
+       }
+       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;
+                       }
+               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 (file)
index 0000000..5ed8dcc
--- /dev/null
@@ -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[0];
+};
+
+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_blob *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 (file)
index 0000000..2fd96e5
--- /dev/null
@@ -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.
+ */
+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 1
+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;
+}
+
+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 (file)
index 0000000..a384db5
--- /dev/null
@@ -0,0 +1,421 @@
+/* ==========================================================================
+ * $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);
+
+/** @} */
+
+#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 (file)
index 0000000..9d49b2b
--- /dev/null
@@ -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;
+               }
+
+       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
+ */
+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.
+ *
+ */
+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_blob *blob = 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, &blob->channel[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.
+ */
+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 (file)
index 0000000..2ee2745
--- /dev/null
@@ -0,0 +1,1087 @@
+
+/* ==========================================================================
+ * $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);
+}
+
+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(&regs,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(&regs);
+       set_fiq_regs(&regs);
+#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, usb_pipeout(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;
+        void **ref_ep_hcpriv = &ep->hcpriv;
+       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, ref_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,
+                                                  !(usb_pipein(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,
+                                               /*(dwc_otg_qh_t **)*/
+                                               ref_ep_hcpriv, 1);
+               if (0 == retval) {
+                       if (alloc_bandwidth) {
+                               allocate_bus_bandwidth(hcd,
+                                               dwc_otg_hcd_get_ep_bandwidth(
+                                                       dwc_otg_hcd, *ref_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 (file)
index 0000000..f51fad1
--- /dev/null
@@ -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
+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 (file)
index 0000000..7a77977
--- /dev/null
@@ -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 <asm/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 (file)
index 0000000..2ffd4f1
--- /dev/null
@@ -0,0 +1,2725 @@
+/* ==========================================================================
+ * $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.
+ */
+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.
+ */
+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
+ *
+ */
+extern void start_next_request(dwc_otg_pcd_ep_t * ep);
+
+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.
+ */
+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 (file)
index 0000000..a70ebd0
--- /dev/null
@@ -0,0 +1,273 @@
+/* ==========================================================================
+ * $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;
+}
+
+//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 (file)
index 0000000..4c1d591
--- /dev/null
@@ -0,0 +1,361 @@
+/* ==========================================================================
+ * $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);
+
+/******************************************************************************/
+
+/** @} */
+
+#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 (file)
index 0000000..76e94b4
--- /dev/null
@@ -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.
+ */
+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
+ */
+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.
+ */
+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 ...
+ */
+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 ...
+ */
+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.
+ */
+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.
+ */
+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.
+ */
+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.
+ */
+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.
+ */
+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
+ */
+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
+ */
+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
+ */
+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.
+ */
+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.
+ *
+ */
+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.
+ *
+ */
+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 (file)
index 0000000..a5ed8e8
--- /dev/null
@@ -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.
+ */
+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 (file)
index 0000000..8e0e7b5
--- /dev/null
@@ -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 (file)
index 0000000..fc45375
--- /dev/null
@@ -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 (file)
index 0000000..85e55fd
--- /dev/null
@@ -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 (file)
index 0000000..dc3820d
--- /dev/null
@@ -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 (file)
index 0000000..cdc9963
--- /dev/null
@@ -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;
index b398d3f..6df995c 100644 (file)
@@ -98,6 +98,7 @@ static void xhci_free_segments_for_ring(struct xhci_hcd *xhci,
  */
 static void xhci_link_segments(struct xhci_segment *prev,
                               struct xhci_segment *next,
+                              unsigned int trbs_per_seg,
                               enum xhci_ring_type type, bool chain_links)
 {
        u32 val;
@@ -106,16 +107,16 @@ static void xhci_link_segments(struct xhci_segment *prev,
                return;
        prev->next = next;
        if (type != TYPE_EVENT) {
-               prev->trbs[TRBS_PER_SEGMENT-1].link.segment_ptr =
+               prev->trbs[trbs_per_seg - 1].link.segment_ptr =
                        cpu_to_le64(next->dma);
 
                /* Set the last TRB in the segment to have a TRB type ID of Link TRB */
-               val = le32_to_cpu(prev->trbs[TRBS_PER_SEGMENT-1].link.control);
+               val = le32_to_cpu(prev->trbs[trbs_per_seg - 1].link.control);
                val &= ~TRB_TYPE_BITMASK;
                val |= TRB_TYPE(TRB_LINK);
                if (chain_links)
                        val |= TRB_CHAIN;
-               prev->trbs[TRBS_PER_SEGMENT-1].link.control = cpu_to_le32(val);
+               prev->trbs[trbs_per_seg - 1].link.control = cpu_to_le32(val);
        }
 }
 
@@ -139,15 +140,17 @@ static void xhci_link_rings(struct xhci_hcd *xhci, struct xhci_ring *ring,
                          (xhci->quirks & XHCI_AMD_0x96_HOST)));
 
        next = ring->enq_seg->next;
-       xhci_link_segments(ring->enq_seg, first, ring->type, chain_links);
-       xhci_link_segments(last, next, ring->type, chain_links);
+       xhci_link_segments(ring->enq_seg, first, ring->trbs_per_seg,
+                          ring->type, chain_links);
+       xhci_link_segments(last, next, ring->trbs_per_seg,
+                          ring->type, chain_links);
        ring->num_segs += num_segs;
-       ring->num_trbs_free += (TRBS_PER_SEGMENT - 1) * num_segs;
+       ring->num_trbs_free += (ring->trbs_per_seg - 1) * num_segs;
 
        if (ring->type != TYPE_EVENT && ring->enq_seg == ring->last_seg) {
-               ring->last_seg->trbs[TRBS_PER_SEGMENT-1].link.control
+               ring->last_seg->trbs[ring->trbs_per_seg - 1].link.control
                        &= ~cpu_to_le32(LINK_TOGGLE);
-               last->trbs[TRBS_PER_SEGMENT-1].link.control
+               last->trbs[ring->trbs_per_seg - 1].link.control
                        |= cpu_to_le32(LINK_TOGGLE);
                ring->last_seg = last;
        }
@@ -314,14 +317,15 @@ void xhci_initialize_ring_info(struct xhci_ring *ring,
         * Each segment has a link TRB, and leave an extra TRB for SW
         * accounting purpose
         */
-       ring->num_trbs_free = ring->num_segs * (TRBS_PER_SEGMENT - 1) - 1;
+       ring->num_trbs_free = ring->num_segs * (ring->trbs_per_seg - 1) - 1;
 }
 
 /* Allocate segments and link them for a ring */
 static int xhci_alloc_segments_for_ring(struct xhci_hcd *xhci,
                struct xhci_segment **first, struct xhci_segment **last,
-               unsigned int num_segs, unsigned int cycle_state,
-               enum xhci_ring_type type, unsigned int max_packet, gfp_t flags)
+               unsigned int num_segs, unsigned int trbs_per_seg,
+               unsigned int cycle_state, enum xhci_ring_type type,
+               unsigned int max_packet, gfp_t flags)
 {
        struct xhci_segment *prev;
        bool chain_links;
@@ -350,12 +354,12 @@ static int xhci_alloc_segments_for_ring(struct xhci_hcd *xhci,
                        }
                        return -ENOMEM;
                }
-               xhci_link_segments(prev, next, type, chain_links);
+               xhci_link_segments(prev, next, trbs_per_seg, type, chain_links);
 
                prev = next;
                num_segs--;
        }
-       xhci_link_segments(prev, *first, type, chain_links);
+       xhci_link_segments(prev, *first, trbs_per_seg, type, chain_links);
        *last = prev;
 
        return 0;
@@ -387,16 +391,28 @@ struct xhci_ring *xhci_ring_alloc(struct xhci_hcd *xhci,
        if (num_segs == 0)
                return ring;
 
+       ring->trbs_per_seg = TRBS_PER_SEGMENT;
+       /*
+        * The Via VL805 has a bug where cache readahead will fetch off the end
+        * of a page if the Link TRB of a transfer ring is in the last 4 slots.
+        * Where there are consecutive physical pages containing ring segments,
+        * this can cause a desync between the controller's view of a ring
+        * and the host.
+        */
+       if (xhci->quirks & XHCI_VLI_TRB_CACHE_BUG &&
+           type != TYPE_EVENT && type != TYPE_COMMAND)
+               ring->trbs_per_seg -= 4;
+
        ret = xhci_alloc_segments_for_ring(xhci, &ring->first_seg,
-                       &ring->last_seg, num_segs, cycle_state, type,
-                       max_packet, flags);
+                       &ring->last_seg, num_segs, ring->trbs_per_seg,
+                       cycle_state, type, max_packet, flags);
        if (ret)
                goto fail;
 
        /* Only event ring does not use link TRB */
        if (type != TYPE_EVENT) {
                /* See section 4.9.2.1 and 6.4.4.1 */
-               ring->last_seg->trbs[TRBS_PER_SEGMENT - 1].link.control |=
+               ring->last_seg->trbs[ring->trbs_per_seg - 1].link.control |=
                        cpu_to_le32(LINK_TOGGLE);
        }
        xhci_initialize_ring_info(ring, cycle_state);
@@ -429,16 +445,15 @@ int xhci_ring_expansion(struct xhci_hcd *xhci, struct xhci_ring *ring,
        unsigned int            num_segs_needed;
        int                     ret;
 
-       num_segs_needed = (num_trbs + (TRBS_PER_SEGMENT - 1) - 1) /
-                               (TRBS_PER_SEGMENT - 1);
-
+       num_segs_needed = (num_trbs + (ring->trbs_per_seg - 1) - 1) /
+                               (ring->trbs_per_seg - 1);
        /* Allocate number of segments we needed, or double the ring size */
        num_segs = ring->num_segs > num_segs_needed ?
                        ring->num_segs : num_segs_needed;
 
        ret = xhci_alloc_segments_for_ring(xhci, &first, &last,
-                       num_segs, ring->cycle_state, ring->type,
-                       ring->bounce_buf_len, flags);
+                       num_segs, ring->trbs_per_seg, ring->cycle_state,
+                       ring->type, ring->bounce_buf_len, flags);
        if (ret)
                return -ENOMEM;
 
@@ -1811,7 +1826,7 @@ int xhci_alloc_erst(struct xhci_hcd *xhci,
        for (val = 0; val < evt_ring->num_segs; val++) {
                entry = &erst->entries[val];
                entry->seg_addr = cpu_to_le64(seg->dma);
-               entry->seg_size = cpu_to_le32(TRBS_PER_SEGMENT);
+               entry->seg_size = cpu_to_le32(evt_ring->trbs_per_seg);
                entry->rsvd = 0;
                seg = seg->next;
        }
@@ -2511,9 +2526,11 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags)
         * Event ring setup: Allocate a normal ring, but also setup
         * the event ring segment table (ERST).  Section 4.9.3.
         */
+       val2 = 1 << HCS_ERST_MAX(xhci->hcs_params2);
+       val2 = min_t(unsigned int, ERST_MAX_SEGS, val2);
        xhci_dbg_trace(xhci, trace_xhci_dbg_init, "// Allocating event ring");
-       xhci->event_ring = xhci_ring_alloc(xhci, ERST_NUM_SEGS, 1, TYPE_EVENT,
-                                       0, flags);
+       xhci->event_ring = xhci_ring_alloc(xhci, val2, 1, TYPE_EVENT,
+                                          0, flags);
        if (!xhci->event_ring)
                goto fail;
        if (xhci_check_trb_in_td_math(xhci) < 0)
@@ -2526,7 +2543,7 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags)
        /* set ERST count with the number of entries in the segment table */
        val = readl(&xhci->ir_set->erst_size);
        val &= ERST_SIZE_MASK;
-       val |= ERST_NUM_SEGS;
+       val |= val2;
        xhci_dbg_trace(xhci, trace_xhci_dbg_init,
                        "// Write ERST size = %i to ir_set 0 (some bits preserved)",
                        val);
index 352626f..96a80d5 100644 (file)
@@ -304,6 +304,9 @@ static void xhci_pci_quirks(struct device *dev, struct xhci_hcd *xhci)
        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_VLI_TRB_CACHE_BUG;
+               xhci->quirks |= XHCI_VLI_SS_BULK_OUT_BUG;
        }
 
        if (pdev->vendor == PCI_VENDOR_ID_ASMEDIA &&
index f970799..6b4cb26 100644 (file)
@@ -90,15 +90,16 @@ static bool trb_is_link(union xhci_trb *trb)
        return TRB_TYPE_LINK_LE32(trb->link.control);
 }
 
-static bool last_trb_on_seg(struct xhci_segment *seg, union xhci_trb *trb)
+static bool last_trb_on_seg(struct xhci_segment *seg,
+                           unsigned int trbs_per_seg, union xhci_trb *trb)
 {
-       return trb == &seg->trbs[TRBS_PER_SEGMENT - 1];
+       return trb == &seg->trbs[trbs_per_seg - 1];
 }
 
 static bool last_trb_on_ring(struct xhci_ring *ring,
                        struct xhci_segment *seg, union xhci_trb *trb)
 {
-       return last_trb_on_seg(seg, trb) && (seg->next == ring->first_seg);
+       return last_trb_on_seg(seg, ring->trbs_per_seg, trb) && (seg->next == ring->first_seg);
 }
 
 static bool link_trb_toggles_cycle(union xhci_trb *trb)
@@ -161,7 +162,8 @@ void inc_deq(struct xhci_hcd *xhci, struct xhci_ring *ring)
 
        /* event ring doesn't have link trbs, check for last trb */
        if (ring->type == TYPE_EVENT) {
-               if (!last_trb_on_seg(ring->deq_seg, ring->dequeue)) {
+               if (!last_trb_on_seg(ring->deq_seg, ring->trbs_per_seg,
+                                    ring->dequeue)) {
                        ring->dequeue++;
                        goto out;
                }
@@ -174,7 +176,8 @@ void inc_deq(struct xhci_hcd *xhci, struct xhci_ring *ring)
 
        /* All other rings have link trbs */
        if (!trb_is_link(ring->dequeue)) {
-               if (last_trb_on_seg(ring->deq_seg, ring->dequeue)) {
+               if (last_trb_on_seg(ring->deq_seg, ring->trbs_per_seg,
+                   ring->dequeue)) {
                        xhci_warn(xhci, "Missing link TRB at end of segment\n");
                } else {
                        ring->dequeue++;
@@ -225,7 +228,7 @@ static void inc_enq(struct xhci_hcd *xhci, struct xhci_ring *ring,
        if (!trb_is_link(ring->enqueue))
                ring->num_trbs_free--;
 
-       if (last_trb_on_seg(ring->enq_seg, ring->enqueue)) {
+       if (last_trb_on_seg(ring->enq_seg, ring->trbs_per_seg, ring->enqueue)) {
                xhci_err(xhci, "Tried to move enqueue past ring segment\n");
                return;
        }
@@ -289,6 +292,12 @@ static inline int room_on_ring(struct xhci_hcd *xhci, struct xhci_ring *ring,
                return 0;
 
        if (ring->type != TYPE_COMMAND && ring->type != TYPE_EVENT) {
+               /*
+                * If the ring has a single segment the dequeue segment
+                * never changes, so don't use it as measure of free space.
+                */
+               if (ring->num_segs == 1)
+                       return ring->num_trbs_free >= num_trbs;
                num_trbs_in_deq_seg = ring->dequeue - ring->deq_seg->trbs;
                if (ring->num_trbs_free < num_trbs + num_trbs_in_deq_seg)
                        return 0;
@@ -667,6 +676,15 @@ static int xhci_move_dequeue_past_td(struct xhci_hcd *xhci,
        } while (!cycle_found || !td_last_trb_found);
 
 deq_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);
@@ -677,9 +695,9 @@ deq_found:
        }
 
        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 */
@@ -3138,7 +3156,7 @@ irqreturn_t xhci_irq(struct usb_hcd *hcd)
         * that clears the EHB.
         */
        while (xhci_handle_event(xhci) > 0) {
-               if (event_loop++ < TRBS_PER_SEGMENT / 2)
+               if (event_loop++ < xhci->event_ring->trbs_per_seg / 2)
                        continue;
                xhci_update_erst_dequeue(xhci, event_ring_deq);
                event_ring_deq = xhci->event_ring->dequeue;
@@ -3280,7 +3298,8 @@ static int prepare_ring(struct xhci_hcd *xhci, struct xhci_ring *ep_ring,
                }
        }
 
-       if (last_trb_on_seg(ep_ring->enq_seg, ep_ring->enqueue)) {
+       if (last_trb_on_seg(ep_ring->enq_seg, ep_ring->trbs_per_seg,
+           ep_ring->enqueue)) {
                xhci_warn(xhci, "Missing link TRB at end of ring segment\n");
                return -EINVAL;
        }
@@ -3589,14 +3608,15 @@ int xhci_queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
        unsigned int num_trbs;
        unsigned int start_cycle, num_sgs = 0;
        unsigned int enqd_len, block_len, trb_buff_len, full_len;
-       int sent_len, ret;
-       u32 field, length_field, remainder;
+       int sent_len, ret, vli_bulk_quirk = 0;
+       u32 field, length_field, remainder, maxpacket;
        u64 addr, send_addr;
 
        ring = xhci_urb_to_transfer_ring(xhci, urb);
        if (!ring)
                return -EINVAL;
 
+       maxpacket = usb_endpoint_maxp(&urb->ep->desc);
        full_len = urb->transfer_buffer_length;
        /* If we have scatter/gather list, we use it. */
        if (urb->num_sgs && !(urb->transfer_flags & URB_DMA_MAP_SINGLE)) {
@@ -3633,6 +3653,12 @@ int xhci_queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
        start_cycle = ring->cycle_state;
        send_addr = addr;
 
+       if (xhci->quirks & XHCI_VLI_SS_BULK_OUT_BUG &&
+           usb_endpoint_is_bulk_out(&urb->ep->desc)
+           && urb->dev->speed >= USB_SPEED_SUPER) {
+               vli_bulk_quirk = 1;
+       }
+
        /* Queue the TRBs, even if they are zero-length */
        for (enqd_len = 0; first_trb || enqd_len < full_len;
                        enqd_len += trb_buff_len) {
@@ -3645,6 +3671,11 @@ int xhci_queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
                if (enqd_len + trb_buff_len > full_len)
                        trb_buff_len = full_len - enqd_len;
 
+               if (vli_bulk_quirk && trb_buff_len > maxpacket) {
+                       /* SS bulk wMaxPacket is 1024B */
+                       remainder = trb_buff_len & (maxpacket - 1);
+                       trb_buff_len -= remainder;
+               }
                /* Don't change the cycle bit of the first TRB until later */
                if (first_trb) {
                        first_trb = false;
index 94fe7d6..b72d408 100644 (file)
@@ -877,8 +877,8 @@ static void xhci_clear_command_ring(struct xhci_hcd *xhci)
        seg = ring->deq_seg;
        do {
                memset(seg->trbs, 0,
-                       sizeof(union xhci_trb) * (TRBS_PER_SEGMENT - 1));
-               seg->trbs[TRBS_PER_SEGMENT - 1].link.control &=
+                       sizeof(union xhci_trb) * (ring->trbs_per_seg - 1));
+               seg->trbs[ring->trbs_per_seg - 1].link.control &=
                        cpu_to_le32(~TRB_CYCLE);
                seg = seg->next;
        } while (seg != ring->deq_seg);
@@ -889,7 +889,7 @@ static void xhci_clear_command_ring(struct xhci_hcd *xhci)
        ring->enq_seg = ring->deq_seg;
        ring->enqueue = ring->dequeue;
 
-       ring->num_trbs_free = ring->num_segs * (TRBS_PER_SEGMENT - 1) - 1;
+       ring->num_trbs_free = ring->num_segs * (ring->trbs_per_seg - 1) - 1;
        /*
         * Ring is now zeroed, so the HW should look for change of ownership
         * when the cycle bit is set to 1.
@@ -1618,6 +1618,109 @@ command_cleanup:
 }
 
 /*
+ * 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 = 0;
+
+       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
  */
@@ -5443,6 +5546,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,
index 79fa34f..1b5b036 100644 (file)
@@ -1635,6 +1635,7 @@ struct xhci_ring {
        unsigned int            num_trbs_free;
        unsigned int            num_trbs_free_temp;
        unsigned int            bounce_buf_len;
+       unsigned int            trbs_per_seg;
        enum xhci_ring_type     type;
        bool                    last_td_was_short;
        struct radix_tree_root  *trb_address_map;
@@ -1673,8 +1674,8 @@ struct urb_priv {
  * Each segment table entry is 4*32bits long.  1K seems like an ok size:
  * (1K bytes * 8bytes/bit) / (4*32 bits) = 64 segment entries in the table,
  * meaning 64 ring segments.
- * Initial allocated size of the ERST, in number of entries */
-#define        ERST_NUM_SEGS   1
+ * Maximum number of segments in the ERST */
+#define        ERST_MAX_SEGS   8
 /* Poll every 60 seconds */
 #define        POLL_TIMEOUT    60
 /* Stop endpoint command timeout (secs) for URB cancellation watchdog timer */
@@ -1903,6 +1904,9 @@ struct xhci_hcd {
 #define XHCI_NO_SOFT_RETRY     BIT_ULL(40)
 #define XHCI_BROKEN_D3COLD     BIT_ULL(41)
 #define XHCI_EP_CTX_BROKEN_DCS BIT_ULL(42)
+#define XHCI_AVOID_DQ_ON_LINK  BIT_ULL(43)
+#define XHCI_VLI_TRB_CACHE_BUG BIT_ULL(44)
+#define XHCI_VLI_SS_BULK_OUT_BUG       BIT_ULL(45)
 
        unsigned int            num_active_eps;
        unsigned int            limit_active_eps;
index e32694c..853ae0b 100644 (file)
@@ -248,6 +248,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
index cae2c83..30a17f4 100644 (file)
@@ -49,6 +49,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/rpi_backlight.c b/drivers/video/backlight/rpi_backlight.c
new file mode 100644 (file)
index 0000000..14a0d9b
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * 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.fb_blank != 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");
index 6ed5e60..e7f1a83 100644 (file)
@@ -219,6 +219,20 @@ config FB_TILEBLITTING
 comment "Frame buffer hardware drivers"
        depends on FB
 
+config FB_BCM2708
+       tristate "BCM2708 framebuffer support"
+       depends on FB && RASPBERRYPI_FIRMWARE
+       select FB_CFB_FILLRECT
+       select FB_CFB_COPYAREA
+       select FB_CFB_IMAGEBLIT
+       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
@@ -2238,6 +2252,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
+       select MFD_RPISENSE_CORE
+       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"
index 477b962..5a478be 100644 (file)
@@ -11,6 +11,7 @@ obj-$(CONFIG_FB_MACMODES)      += macmodes.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
@@ -129,6 +130,7 @@ obj-$(CONFIG_FB_MX3)                  += mx3fb.o
 obj-$(CONFIG_FB_DA8XX)           += da8xx-fb.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 (file)
index 0000000..365c5b9
--- /dev/null
@@ -0,0 +1,1274 @@
+/*
+ *  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(&param32, (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, &param);
+               break;
+       }
+       default:
+               ret = bcm2708_ioctl(info, cmd, arg);
+               break;
+       }
+       return ret;
+}
+#endif
+
+static void bcm2708_fb_fillrect(struct fb_info *info,
+                               const struct fb_fillrect *rect)
+{
+       cfb_fillrect(info, rect);
+}
+
+/* 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 void bcm2708_fb_imageblit(struct fb_info *info,
+                                const struct fb_image *image)
+{
+       cfb_imageblit(info, image);
+}
+
+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_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 = bcm2708_fb_fillrect,
+       .fb_copyarea = bcm2708_fb_copyarea,
+       .fb_imageblit = bcm2708_fb_imageblit,
+       .fb_pan_display = bcm2708_fb_pan_display,
+       .fb_ioctl = bcm2708_ioctl,
+#ifdef CONFIG_COMPAT
+       .fb_compat_ioctl = bcm2708_compat_ioctl,
+#endif
+};
+
+static int bcm2708_fb_register(struct bcm2708_fb *fb)
+{
+       int ret;
+
+       fb->fb.fbops = &bcm2708_fb_ops;
+       fb->fb.flags = FBINFO_FLAG_DEFAULT | 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 int 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);
+
+       return 0;
+}
+
+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");
index a2bb276..436494f 100644 (file)
  *
  *  Also need to add code to deal with cards endians that are different than
  *  the native cpu endians. I also need to deal with MSB position in the word.
+ *  Modified by Harm Hanemaaijer (fgenfb@yahoo.com) 2013:
+ *  - Provide optimized versions of fast_imageblit for 16 and 32bpp that are
+ *    significantly faster than the previous implementation.
+ *  - Simplify the fast/slow_imageblit selection code, avoiding integer
+ *    divides.
  */
 #include <linux/module.h>
 #include <linux/string.h>
@@ -262,6 +267,133 @@ static inline void fast_imageblit(const struct fb_image *image, struct fb_info *
        }
 }      
        
+/*
+ * Optimized fast_imageblit for bpp == 16. ppw = 2, bit_mask = 3 folded
+ * into the code, main loop unrolled.
+ */
+
+static inline void fast_imageblit16(const struct fb_image *image,
+                                   struct fb_info *p, u8 __iomem * dst1,
+                                   u32 fgcolor, u32 bgcolor)
+{
+       u32 fgx = fgcolor, bgx = bgcolor;
+       u32 spitch = (image->width + 7) / 8;
+       u32 end_mask, eorx;
+       const char *s = image->data, *src;
+       u32 __iomem *dst;
+       const u32 *tab = NULL;
+       int i, j, k;
+
+       tab = fb_be_math(p) ? cfb_tab16_be : cfb_tab16_le;
+
+       fgx <<= 16;
+       bgx <<= 16;
+       fgx |= fgcolor;
+       bgx |= bgcolor;
+
+       eorx = fgx ^ bgx;
+       k = image->width / 2;
+
+       for (i = image->height; i--;) {
+               dst = (u32 __iomem *) dst1;
+               src = s;
+
+               j = k;
+               while (j >= 4) {
+                       u8 bits = *src;
+                       end_mask = tab[(bits >> 6) & 3];
+                       FB_WRITEL((end_mask & eorx) ^ bgx, dst++);
+                       end_mask = tab[(bits >> 4) & 3];
+                       FB_WRITEL((end_mask & eorx) ^ bgx, dst++);
+                       end_mask = tab[(bits >> 2) & 3];
+                       FB_WRITEL((end_mask & eorx) ^ bgx, dst++);
+                       end_mask = tab[bits & 3];
+                       FB_WRITEL((end_mask & eorx) ^ bgx, dst++);
+                       src++;
+                       j -= 4;
+               }
+               if (j != 0) {
+                       u8 bits = *src;
+                       end_mask = tab[(bits >> 6) & 3];
+                       FB_WRITEL((end_mask & eorx) ^ bgx, dst++);
+                       if (j >= 2) {
+                               end_mask = tab[(bits >> 4) & 3];
+                               FB_WRITEL((end_mask & eorx) ^ bgx, dst++);
+                               if (j == 3) {
+                                       end_mask = tab[(bits >> 2) & 3];
+                                       FB_WRITEL((end_mask & eorx) ^ bgx, dst);
+                               }
+                       }
+               }
+               dst1 += p->fix.line_length;
+               s += spitch;
+       }
+}
+
+/*
+ * Optimized fast_imageblit for bpp == 32. ppw = 1, bit_mask = 1 folded
+ * into the code, main loop unrolled.
+ */
+
+static inline void fast_imageblit32(const struct fb_image *image,
+                                   struct fb_info *p, u8 __iomem * dst1,
+                                   u32 fgcolor, u32 bgcolor)
+{
+       u32 fgx = fgcolor, bgx = bgcolor;
+       u32 spitch = (image->width + 7) / 8;
+       u32 end_mask, eorx;
+       const char *s = image->data, *src;
+       u32 __iomem *dst;
+       const u32 *tab = NULL;
+       int i, j, k;
+
+       tab = cfb_tab32;
+
+       eorx = fgx ^ bgx;
+       k = image->width;
+
+       for (i = image->height; i--;) {
+               dst = (u32 __iomem *) dst1;
+               src = s;
+
+               j = k;
+               while (j >= 8) {
+                       u8 bits = *src;
+                       end_mask = tab[(bits >> 7) & 1];
+                       FB_WRITEL((end_mask & eorx) ^ bgx, dst++);
+                       end_mask = tab[(bits >> 6) & 1];
+                       FB_WRITEL((end_mask & eorx) ^ bgx, dst++);
+                       end_mask = tab[(bits >> 5) & 1];
+                       FB_WRITEL((end_mask & eorx) ^ bgx, dst++);
+                       end_mask = tab[(bits >> 4) & 1];
+                       FB_WRITEL((end_mask & eorx) ^ bgx, dst++);
+                       end_mask = tab[(bits >> 3) & 1];
+                       FB_WRITEL((end_mask & eorx) ^ bgx, dst++);
+                       end_mask = tab[(bits >> 2) & 1];
+                       FB_WRITEL((end_mask & eorx) ^ bgx, dst++);
+                       end_mask = tab[(bits >> 1) & 1];
+                       FB_WRITEL((end_mask & eorx) ^ bgx, dst++);
+                       end_mask = tab[bits & 1];
+                       FB_WRITEL((end_mask & eorx) ^ bgx, dst++);
+                       src++;
+                       j -= 8;
+               }
+               if (j != 0) {
+                       u32 bits = (u32) * src;
+                       while (j > 1) {
+                               end_mask = tab[(bits >> 7) & 1];
+                               FB_WRITEL((end_mask & eorx) ^ bgx, dst++);
+                               bits <<= 1;
+                               j--;
+                       }
+                       end_mask = tab[(bits >> 7) & 1];
+                       FB_WRITEL((end_mask & eorx) ^ bgx, dst);
+               }
+               dst1 += p->fix.line_length;
+               s += spitch;
+       }
+}
+
 void cfb_imageblit(struct fb_info *p, const struct fb_image *image)
 {
        u32 fgcolor, bgcolor, start_index, bitstart, pitch_index = 0;
@@ -294,11 +426,21 @@ void cfb_imageblit(struct fb_info *p, const struct fb_image *image)
                        bgcolor = image->bg_color;
                }       
                
-               if (32 % bpp == 0 && !start_index && !pitch_index && 
-                   ((width & (32/bpp-1)) == 0) &&
-                   bpp >= 8 && bpp <= 32)                      
-                       fast_imageblit(image, p, dst1, fgcolor, bgcolor);
-               else 
+               if (!start_index && !pitch_index) {
+                       if (bpp == 32)
+                               fast_imageblit32(image, p, dst1, fgcolor,
+                                                bgcolor);
+                       else if (bpp == 16 && (width & 1) == 0)
+                               fast_imageblit16(image, p, dst1, fgcolor,
+                                                bgcolor);
+                       else if (bpp == 8 && (width & 3) == 0)
+                               fast_imageblit(image, p, dst1, fgcolor,
+                                              bgcolor);
+                       else
+                               slow_imageblit(image, p, dst1, fgcolor,
+                                              bgcolor,
+                                              start_index, pitch_index);
+               } else
                        slow_imageblit(image, p, dst1, fgcolor, bgcolor,
                                        start_index, pitch_index);
        } else
index 528c87f..96d271f 100644 (file)
@@ -26,7 +26,6 @@
 #include <linux/init.h>
 #include <linux/linux_logo.h>
 #include <linux/proc_fs.h>
-#include <linux/platform_device.h>
 #include <linux/seq_file.h>
 #include <linux/console.h>
 #include <linux/kmod.h>
@@ -1097,6 +1096,30 @@ fb_blank(struct fb_info *info, int blank)
 }
 EXPORT_SYMBOL(fb_blank);
 
+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)
 {
@@ -1105,6 +1128,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;
 
@@ -1182,6 +1206,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(&copy, argp, sizeof(copy)))
+                               return -EFAULT;
+                       ret = fb_copyarea_user(info, &copy);
+                       break;
+               }
+               fallthrough;
        default:
                lock_fb_info(info);
                fb = info->fbops;
@@ -1321,6 +1354,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:
@@ -1571,43 +1605,18 @@ static void do_remove_conflicting_framebuffers(struct apertures_struct *a,
        /* check all firmware fbs and kick off if the base addr overlaps */
        for_each_registered_fb(i) {
                struct apertures_struct *gen_aper;
-               struct device *device;
 
                if (!(registered_fb[i]->flags & FBINFO_MISC_FIRMWARE))
                        continue;
 
                gen_aper = registered_fb[i]->apertures;
-               device = registered_fb[i]->device;
                if (fb_do_apertures_overlap(gen_aper, a) ||
                        (primary && gen_aper && gen_aper->count &&
                         gen_aper->ranges[0].base == VGA_FB_PHYS)) {
 
                        printk(KERN_INFO "fb%d: switching to %s from %s\n",
                               i, name, registered_fb[i]->fix.id);
-
-                       /*
-                        * If we kick-out a firmware driver, we also want to remove
-                        * the underlying platform device, such as simple-framebuffer,
-                        * VESA, EFI, etc. A native driver will then be able to
-                        * allocate the memory range.
-                        *
-                        * If it's not a platform device, at least print a warning. A
-                        * fix would add code to remove the device from the system.
-                        */
-                       if (!device) {
-                               /* TODO: Represent each OF framebuffer as its own
-                                * device in the device hierarchy. For now, offb
-                                * doesn't have such a device, so unregister the
-                                * framebuffer as before without warning.
-                                */
-                               do_unregister_framebuffer(registered_fb[i]);
-                       } else if (dev_is_platform(device)) {
-                               registered_fb[i]->forced_out = true;
-                               platform_device_unregister(to_platform_device(device));
-                       } else {
-                               pr_warn("fb%d: cannot remove device\n", i);
-                               do_unregister_framebuffer(registered_fb[i]);
-                       }
+                       do_unregister_framebuffer(registered_fb[i]);
                }
        }
 }
@@ -1945,13 +1954,9 @@ EXPORT_SYMBOL(register_framebuffer);
 void
 unregister_framebuffer(struct fb_info *fb_info)
 {
-       bool forced_out = fb_info->forced_out;
-
-       if (!forced_out)
-               mutex_lock(&registration_lock);
+       mutex_lock(&registration_lock);
        do_unregister_framebuffer(fb_info);
-       if (!forced_out)
-               mutex_unlock(&registration_lock);
+       mutex_unlock(&registration_lock);
 }
 EXPORT_SYMBOL(unregister_framebuffer);
 
diff --git a/drivers/video/fbdev/rpisense-fb.c b/drivers/video/fbdev/rpisense-fb.c
new file mode 100644 (file)
index 0000000..325977d
--- /dev/null
@@ -0,0 +1,296 @@
+/*
+ * 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/mfd/rpisense/framebuffer.h>
+#include <linux/mfd/rpisense/core.h>
+
+static bool lowlight;
+module_param(lowlight, bool, 0);
+MODULE_PARM_DESC(lowlight, "Reduce LED matrix brightness to one third");
+
+static struct rpisense *rpisense;
+
+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;
+
+       vmem_work[0] = 0;
+       for (j = 0; j < 8; j++) {
+               for (i = 0; i < 8; i++) {
+                       vmem_work[(j * 24) + i + 1] =
+                               gamma[(mem[(j * 8) + i] >> 11) & 0x1F];
+                       vmem_work[(j * 24) + (i + 8) + 1] =
+                               gamma[(mem[(j * 8) + i] >> 6) & 0x1F];
+                       vmem_work[(j * 24) + (i + 16) + 1] =
+                               gamma[(mem[(j * 8) + i]) & 0x1F];
+               }
+       }
+       rpisense_block_write(rpisense, vmem_work, 193);
+}
+
+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,
+};
+
+static int rpisense_fb_probe(struct platform_device *pdev)
+{
+       struct fb_info *info;
+       int ret = -ENOMEM;
+       struct rpisense_fb *rpisense_fb;
+
+       rpisense = rpisense_get_dev();
+       rpisense_fb = &rpisense->framebuffer;
+
+       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;
+
+       info = framebuffer_alloc(0, &pdev->dev);
+       if (!info) {
+               dev_err(&pdev->dev, "Could not allocate framebuffer.\n");
+               goto err_malloc;
+       }
+       rpisense_fb->info = info;
+
+       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_FLAG_DEFAULT | 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 int rpisense_fb_remove(struct platform_device *pdev)
+{
+       struct rpisense_fb *rpisense_fb = &rpisense->framebuffer;
+       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);
+       }
+
+       return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id rpisense_fb_id[] = {
+       { .compatible = "rpi,rpi-sense-fb" },
+       { },
+};
+MODULE_DEVICE_TABLE(of, rpisense_fb_id);
+#endif
+
+static struct platform_device_id rpisense_fb_device_id[] = {
+       { .name = "rpi-sense-fb" },
+       { },
+};
+MODULE_DEVICE_TABLE(platform, rpisense_fb_device_id);
+
+static struct platform_driver rpisense_fb_driver = {
+       .probe = rpisense_fb_probe,
+       .remove = rpisense_fb_remove,
+       .driver = {
+               .name = "rpi-sense-fb",
+               .owner = THIS_MODULE,
+       },
+};
+
+module_platform_driver(rpisense_fb_driver);
+
+MODULE_DESCRIPTION("Raspberry Pi Sense HAT framebuffer driver");
+MODULE_AUTHOR("Serge Schneider <serge@raspberrypi.org>");
+MODULE_LICENSE("GPL");
+
index 3c14e43..7626beb 100644 (file)
 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
index d4632aa..4832cd8 100644 (file)
@@ -30,7 +30,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(pdata->gpiod, 1);
+                       gpiod_direction_output_raw(pdata->gpiod, 1);
                        msleep(pdata->pullup_duration);
                        /*
                         * This will simply set the line as input since we are doing
index 9490717..e8d151d 100644 (file)
 #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)
@@ -97,9 +91,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);
@@ -117,7 +126,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;
 }
@@ -152,19 +169,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)
index ad78bdd..49a095e 100644 (file)
@@ -1000,7 +1000,7 @@ static inline int should_optimize_scan(struct ext4_allocation_context *ac)
                return 0;
        if (ac->ac_criteria >= 2)
                return 0;
-       if (!ext4_test_inode_flag(ac->ac_inode, EXT4_INODE_EXTENTS))
+       if (ext4_test_inode_flag(ac->ac_inode, EXT4_INODE_EXTENTS))
                return 0;
        return 1;
 }
index 46bdfa4..e146610 100644 (file)
@@ -762,6 +762,14 @@ struct drm_bridge {
         */
        bool interlace_allowed;
        /**
+        * @pre_enable_upstream_first: The bridge requires that the upstream
+        * bridge @pre_enable function is called before its @pre_enable,
+        * and conversely for post_disable. This is most frequently a
+        * requirement for DSI devices which need the host to be initialised
+        * before the peripheral.
+        */
+       bool pre_enable_upstream_first;
+       /**
         * @ddc: Associated I2C adapter for DDC access, if any.
         */
        struct i2c_adapter *ddc;
index 81c2984..e2573d0 100644 (file)
@@ -93,6 +93,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
  *
index c24559f..1be502b 100644 (file)
@@ -401,8 +401,8 @@ drm_hdmi_vendor_infoframe_from_display_mode(struct hdmi_vendor_infoframe *frame,
                                            const struct drm_display_mode *mode);
 
 void
-drm_hdmi_avi_infoframe_colorspace(struct hdmi_avi_infoframe *frame,
-                                 const struct drm_connector_state *conn_state);
+drm_hdmi_avi_infoframe_colorimetry(struct hdmi_avi_infoframe *frame,
+                                  const struct drm_connector_state *conn_state);
 
 void
 drm_hdmi_avi_infoframe_bars(struct hdmi_avi_infoframe *frame,
index 05e1949..0981f80 100644 (file)
@@ -130,6 +130,14 @@ struct mipi_dbi_dev {
         * @dbi: MIPI DBI interface
         */
        struct mipi_dbi dbi;
+
+       /**
+        * @driver_private: Driver private data.
+        *                  Necessary for drivers with private data since devm_drm_dev_alloc()
+        *                  can't allocate structures that embed a structure which then again
+        *                  embeds drm_device.
+        */
+       void *driver_private;
 };
 
 static inline struct mipi_dbi_dev *drm_to_mipi_dbi_dev(struct drm_device *drm)
index af7ba80..eea8d86 100644 (file)
@@ -113,29 +113,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)
 
 enum mipi_dsi_pixel_format {
index c34a3e8..912f1e4 100644 (file)
@@ -98,6 +98,10 @@ struct drm_object_properties {
         * Hence atomic drivers should not use drm_object_property_set_value()
         * and drm_object_property_get_value() on mutable objects, i.e. those
         * without the DRM_MODE_PROP_IMMUTABLE flag set.
+        *
+        * For atomic drivers the default value of properties is stored in this
+        * array, so drm_object_property_get_default_value can be used to
+        * retrieve it.
         */
        uint64_t values[DRM_OBJECT_MAX_PROPERTY];
 };
@@ -126,6 +130,9 @@ int drm_object_property_set_value(struct drm_mode_object *obj,
 int drm_object_property_get_value(struct drm_mode_object *obj,
                                  struct drm_property *property,
                                  uint64_t *value);
+int drm_object_property_get_default_value(struct drm_mode_object *obj,
+                                         struct drm_property *property,
+                                         uint64_t *val);
 
 void drm_object_attach_property(struct drm_mode_object *obj,
                                struct drm_property *property,
index 29ba4ad..2fa6b2c 100644 (file)
@@ -466,6 +466,8 @@ void drm_bus_flags_from_videomode(const struct videomode *vm, u32 *bus_flags);
 int of_get_drm_display_mode(struct device_node *np,
                            struct drm_display_mode *dmode, u32 *bus_flags,
                            int index);
+int of_get_drm_panel_display_mode(struct device_node *np,
+                                 struct drm_display_mode *dmode, u32 *bus_flags);
 #else
 static inline int of_get_drm_display_mode(struct device_node *np,
                                          struct drm_display_mode *dmode,
@@ -473,6 +475,12 @@ static inline int of_get_drm_display_mode(struct device_node *np,
 {
        return -EINVAL;
 }
+
+static inline int of_get_drm_panel_display_mode(struct device_node *np,
+                                               struct drm_display_mode *dmode, u32 *bus_flags)
+{
+       return -EINVAL;
+}
 #endif
 
 void drm_mode_set_name(struct drm_display_mode *mode);
index 4602f83..99eb628 100644 (file)
@@ -24,6 +24,7 @@
 #ifndef __DRM_PANEL_H__
 #define __DRM_PANEL_H__
 
+#include <drm/drm_connector.h>
 #include <linux/err.h>
 #include <linux/errno.h>
 #include <linux/list.h>
@@ -35,8 +36,6 @@ struct drm_device;
 struct drm_panel;
 struct display_timing;
 
-enum drm_panel_orientation;
-
 /**
  * struct drm_panel_funcs - perform operations on a given panel
  *
@@ -166,11 +165,29 @@ struct drm_panel {
        int connector_type;
 
        /**
+        * @orientation:
+        *
+        * Panel orientation at initialisation. This is used to initialise the
+        * drm_connector property for panel orientation.
+        */
+       enum drm_panel_orientation orientation;
+
+       /**
         * @list:
         *
         * Panel entry in registry.
         */
        struct list_head list;
+
+       /**
+        * @prepare_upstream_first:
+        *
+        * The upstream controller should be prepared first, before the prepare
+        * for the panel is called. This is largely required for DSI panels
+        * where the DSI host controller should be initialised to LP-11 before
+        * the panel is powered up.
+        */
+       bool prepare_upstream_first;
 };
 
 void drm_panel_init(struct drm_panel *panel, struct device *dev,
index fed97e3..ef61f7a 100644 (file)
@@ -180,6 +180,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:
         *
         * Blob representing damage (area in plane framebuffer that changed
@@ -750,6 +768,24 @@ struct drm_plane {
         * scaling.
         */
        struct drm_property *scaling_filter_property;
+
+       /**
+        * @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)
index 8d3ed28..04c5756 100644 (file)
@@ -18,6 +18,7 @@ int drm_helper_probe_detect(struct drm_connector *connector,
 void drm_kms_helper_poll_init(struct drm_device *dev);
 void drm_kms_helper_poll_fini(struct drm_device *dev);
 bool drm_helper_hpd_irq_event(struct drm_device *dev);
+bool drm_connector_helper_hpd_irq_event(struct drm_connector *connector);
 void drm_kms_helper_hotplug_event(struct drm_device *dev);
 
 void drm_kms_helper_poll_disable(struct drm_device *dev);
diff --git a/include/dt-bindings/gpio/gpio-fsm.h b/include/dt-bindings/gpio/gpio-fsm.h
new file mode 100644 (file)
index 0000000..eb40cfd
--- /dev/null
@@ -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
index c2c2147..2585e80 100644 (file)
@@ -22,6 +22,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
@@ -66,6 +67,7 @@
 #define PHY_BRCM_CLEAR_RGMII_MODE      0x00000004
 #define PHY_BRCM_DIS_TXCRXC_NOENRGY    0x00000008
 #define PHY_BRCM_EN_MASTER_MODE                0x00000010
+#define PHY_BRCM_IDDQ_SUSPEND          0x000000220
 
 /* Broadcom BCM7xxx specific workarounds */
 #define PHY_BRCM_7XXX_REV(x)           (((x) >> 8) & 0xff)
@@ -83,6 +85,7 @@
 
 #define MII_BCM54XX_EXP_DATA   0x15    /* Expansion register data */
 #define MII_BCM54XX_EXP_SEL    0x17    /* Expansion register select */
+#define MII_BCM54XX_EXP_SEL_TOP        0x0d00  /* TOP_MISC expansion register select */
 #define MII_BCM54XX_EXP_SEL_SSD        0x0e00  /* Secondary SerDes select */
 #define MII_BCM54XX_EXP_SEL_ER 0x0f00  /* Expansion register select */
 #define MII_BCM54XX_EXP_SEL_ETC        0x0d00  /* Expansion register spare + 2k mem */
 #define MII_BCM54XX_EXP_EXP08                  0x0F08
 #define  MII_BCM54XX_EXP_EXP08_RJCT_2MHZ       0x0001
 #define  MII_BCM54XX_EXP_EXP08_EARLY_DAC_WAKE  0x0200
+#define  MII_BCM54XX_EXP_EXP08_FORCE_DAC_WAKE  0x0100
 #define MII_BCM54XX_EXP_EXP75                  0x0f75
 #define  MII_BCM54XX_EXP_EXP75_VDACCTRL                0x003c
 #define  MII_BCM54XX_EXP_EXP75_CM_OSC          0x0001
 #define MII_BCM54XX_EXP_EXP97                  0x0f97
 #define  MII_BCM54XX_EXP_EXP97_MYST            0x0c0c
 
+/* Top-MISC expansion registers */
+#define BCM54XX_TOP_MISC_IDDQ_CTRL             (MII_BCM54XX_EXP_SEL_TOP + 0x06)
+#define BCM54XX_TOP_MISC_IDDQ_LP               (1 << 0)
+#define BCM54XX_TOP_MISC_IDDQ_SD               (1 << 2)
+#define BCM54XX_TOP_MISC_IDDQ_SR               (1 << 3)
+
 /*
  * BCM5482: Secondary SerDes registers
  */
diff --git a/include/linux/broadcom/bcm2835_smi.h b/include/linux/broadcom/bcm2835_smi.h
new file mode 100644 (file)
index 0000000..ee3a75e
--- /dev/null
@@ -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 (file)
index 0000000..3c70792
--- /dev/null
@@ -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 */
index f59c875..402aef4 100644 (file)
@@ -42,6 +42,8 @@ struct dentry;
  * struct clk_rate_request - Structure encoding the clk constraints that
  * a clock user might require.
  *
+ * Should be initialized by calling clk_hw_init_rate_request().
+ *
  * @rate:              Requested clock rate. This field will be adjusted by
  *                     clock drivers according to hardware capabilities.
  * @min_rate:          Minimum rate imposed by clk users.
@@ -60,6 +62,10 @@ struct clk_rate_request {
        struct clk_hw *best_parent_hw;
 };
 
+void clk_hw_init_rate_request(struct clk_hw * const hw,
+                             struct clk_rate_request *req,
+                             unsigned long rate);
+
 /**
  * struct clk_duty - Struture encoding the duty cycle ratio of a clock
  *
index 266e8de..39710b8 100644 (file)
@@ -714,6 +714,17 @@ bool clk_has_parent(struct clk *clk, struct clk *parent);
 int clk_set_rate_range(struct clk *clk, unsigned long min, unsigned long max);
 
 /**
+ * clk_get_rate_range - returns the clock rate range for a clock source
+ * @clk: clock source
+ * @min: Pointer to the variable that will hold the minimum
+ * @max: Pointer to the variable that will hold the maximum
+ *
+ * Fills the @min and @max variables with the minimum and maximum that
+ * the clock source can reach.
+ */
+void clk_get_rate_range(struct clk *clk, unsigned long *min, unsigned long *max);
+
+/**
  * clk_set_min_rate - set a minimum clock rate for a clock source
  * @clk: clock source
  * @rate: desired minimum clock rate in Hz, inclusive
@@ -744,8 +755,9 @@ int clk_set_parent(struct clk *clk, struct clk *parent);
  * clk_get_parent - get the parent clock source for this clock
  * @clk: clock source
  *
- * Returns struct clk corresponding to parent clock source, or
- * valid IS_ERR() condition containing errno.
+ * Returns struct clk corresponding to parent clock source, a NULL
+ * pointer if it doesn't have a parent, or a valid IS_ERR() condition
+ * containing errno.
  */
 struct clk *clk_get_parent(struct clk *clk);
 
@@ -908,6 +920,16 @@ static inline int clk_set_rate_range(struct clk *clk, unsigned long min,
        return 0;
 }
 
+static inline void clk_get_rate_range(struct clk *clk, unsigned long *min,
+                                     unsigned long *max)
+{
+       if (!min || !max)
+               return;
+
+       *min = 0;
+       *max = ULONG_MAX;
+}
+
 static inline int clk_set_min_rate(struct clk *clk, unsigned long rate)
 {
        return 0;
@@ -987,6 +1009,55 @@ static inline void clk_bulk_disable_unprepare(int num_clks,
 }
 
 /**
+ * clk_drop_range - Reset any range set on that clock
+ * @clk: clock source
+ *
+ * Returns success (0) or negative errno.
+ */
+static inline int clk_drop_range(struct clk *clk)
+{
+       return clk_set_rate_range(clk, 0, ULONG_MAX);
+}
+
+/**
+ * clk_get_min_rate - returns the minimum clock rate for a clock source
+ * @clk: clock source
+ *
+ * Returns either the minimum clock rate in Hz that clock source can
+ * reach, or 0 on error.
+ */
+static inline unsigned long clk_get_min_rate(struct clk *clk)
+{
+       unsigned long min, max;
+
+       if (!clk)
+               return 0;
+
+       clk_get_rate_range(clk, &min, &max);
+
+       return min;
+}
+
+/**
+ * clk_get_max_rate - returns the maximum clock rate for a clock source
+ * @clk: clock source
+ *
+ * Returns either the maximum clock rate in Hz that clock source can
+ * reach, or 0 on error.
+ */
+static inline unsigned long clk_get_max_rate(struct clk *clk)
+{
+       unsigned long min, max;
+
+       if (!clk)
+               return 0;
+
+       clk_get_rate_range(clk, &min, &max);
+
+       return max;
+}
+
+/**
  * clk_get_optional - lookup and obtain a reference to an optional clock
  *                   producer.
  * @dev: device for clock "consumer"
index 3d7306c..02f362c 100644 (file)
@@ -502,7 +502,6 @@ struct fb_info {
        } *apertures;
 
        bool skip_vt_switch; /* no VT switch on suspend/resume required */
-       bool forced_out; /* set when being removed by another driver */
 };
 
 static inline struct apertures_struct *alloc_apertures(unsigned int max_num) {
index a0b730b..cb9a65b 100644 (file)
@@ -85,6 +85,9 @@ struct led_classdev {
 #define LED_BRIGHT_HW_CHANGED  BIT(21)
 #define LED_RETAIN_AT_SHUTDOWN BIT(22)
 #define LED_INIT_DEFAULT_TRIGGER BIT(23)
+       /* 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;
index ed37dc4..f70a810 100644 (file)
@@ -9,6 +9,7 @@ struct bcm2835_pm {
        struct device *dev;
        void __iomem *base;
        void __iomem *asb;
+       void __iomem *rpivid_asb;
 };
 
 #endif /* BCM2835_MFD_PM_H */
diff --git a/include/linux/mfd/rpisense/core.h b/include/linux/mfd/rpisense/core.h
new file mode 100644 (file)
index 0000000..4856aa3
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Raspberry Pi Sense HAT core 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_MFD_RPISENSE_CORE_H_
+#define __LINUX_MFD_RPISENSE_CORE_H_
+
+#include <linux/mfd/rpisense/joystick.h>
+#include <linux/mfd/rpisense/framebuffer.h>
+
+/*
+ * Register values.
+ */
+#define RPISENSE_FB                    0x00
+#define RPISENSE_WAI                   0xF0
+#define RPISENSE_VER                   0xF1
+#define RPISENSE_KEYS                  0xF2
+#define RPISENSE_EE_WP                 0xF3
+
+#define RPISENSE_ID                    's'
+
+struct rpisense {
+       struct device *dev;
+       struct i2c_client *i2c_client;
+
+       /* Client devices */
+       struct rpisense_js joystick;
+       struct rpisense_fb framebuffer;
+};
+
+struct rpisense *rpisense_get_dev(void);
+s32 rpisense_reg_read(struct rpisense *rpisense, int reg);
+int rpisense_reg_write(struct rpisense *rpisense, int reg, u16 val);
+int rpisense_block_write(struct rpisense *rpisense, const char *buf, int count);
+
+#endif
diff --git a/include/linux/mfd/rpisense/framebuffer.h b/include/linux/mfd/rpisense/framebuffer.h
new file mode 100644 (file)
index 0000000..2ba95d7
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * 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_
+
+#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 platform_device *pdev;
+       struct fb_info *info;
+};
+
+#endif
diff --git a/include/linux/mfd/rpisense/joystick.h b/include/linux/mfd/rpisense/joystick.h
new file mode 100644 (file)
index 0000000..56196dc
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * Raspberry Pi Sense HAT joystick 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_JOYSTICK_H_
+#define __LINUX_RPISENSE_JOYSTICK_H_
+
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/gpio/consumer.h>
+#include <linux/platform_device.h>
+
+struct rpisense;
+
+struct rpisense_js {
+       struct platform_device *pdev;
+       struct input_dev *keys_dev;
+       struct gpio_desc *keys_desc;
+       struct work_struct keys_work_s;
+       int keys_irq;
+};
+
+
+#endif
index 517288d..626c450 100644 (file)
 /* 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)
index 37f9758..fca1b21 100644 (file)
@@ -293,6 +293,8 @@ struct mmc_card {
 #define MMC_QUIRK_TRIM_BROKEN  (1<<12)         /* Skip trim */
 #define MMC_QUIRK_BROKEN_HPI   (1<<13)         /* Disable broken HPI support */
 
+#define MMC_QUIRK_ERASE_BROKEN (1<<31)         /* Skip erase */
+
        bool                    reenable_cmdq;  /* Re-enable Command Queue */
 
        unsigned int            erase_size;     /* erase size in sectors */
diff --git a/include/linux/platform_data/dma-bcm2708.h b/include/linux/platform_data/dma-bcm2708.h
new file mode 100644 (file)
index 0000000..6ca874d
--- /dev/null
@@ -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 */
index 7ccaa76..77d9a69 100644 (file)
@@ -1840,6 +1840,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);
index 2c1fc92..2a0d491 100644 (file)
@@ -384,6 +384,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);
                /* Returns the hardware-chosen device address */
        int     (*address_device)(struct usb_hcd *, struct usb_device *udev);
                /* prepares the hardware to send commands to the device */
@@ -448,6 +453,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,
index 781371b..7cbbbf0 100644 (file)
@@ -19,6 +19,7 @@
 #define V4L2_CID_MPEG_VIDEO_HEVC_SPS           (V4L2_CID_CODEC_BASE + 1008)
 #define V4L2_CID_MPEG_VIDEO_HEVC_PPS           (V4L2_CID_CODEC_BASE + 1009)
 #define V4L2_CID_MPEG_VIDEO_HEVC_SLICE_PARAMS  (V4L2_CID_CODEC_BASE + 1010)
+#define V4L2_CID_MPEG_VIDEO_HEVC_SCALING_MATRIX        (V4L2_CID_CODEC_BASE + 1011)
 #define V4L2_CID_MPEG_VIDEO_HEVC_DECODE_PARAMS (V4L2_CID_CODEC_BASE + 1012)
 #define V4L2_CID_MPEG_VIDEO_HEVC_DECODE_MODE   (V4L2_CID_CODEC_BASE + 1015)
 #define V4L2_CID_MPEG_VIDEO_HEVC_START_CODE    (V4L2_CID_CODEC_BASE + 1016)
@@ -27,6 +28,7 @@
 #define V4L2_CTRL_TYPE_HEVC_SPS 0x0120
 #define V4L2_CTRL_TYPE_HEVC_PPS 0x0121
 #define V4L2_CTRL_TYPE_HEVC_SLICE_PARAMS 0x0122
+#define V4L2_CTRL_TYPE_HEVC_SCALING_MATRIX 0x0123
 #define V4L2_CTRL_TYPE_HEVC_DECODE_PARAMS 0x0124
 
 enum v4l2_mpeg_video_hevc_decode_mode {
@@ -171,6 +173,10 @@ struct v4l2_ctrl_hevc_slice_params {
        __u32   bit_size;
        __u32   data_bit_offset;
 
+       /* ISO/IEC 23008-2, ITU-T Rec. H.265: General slice segment header */
+       __u32   slice_segment_addr;
+       __u32   num_entry_point_offsets;
+
        /* ISO/IEC 23008-2, ITU-T Rec. H.265: NAL unit header */
        __u8    nal_unit_type;
        __u8    nuh_temporal_id_plus1;
@@ -196,11 +202,12 @@ struct v4l2_ctrl_hevc_slice_params {
        __u8    pic_struct;
 
        /* ISO/IEC 23008-2, ITU-T Rec. H.265: General slice segment header */
-       __u32   slice_segment_addr;
        __u8    ref_idx_l0[V4L2_HEVC_DPB_ENTRIES_NUM_MAX];
        __u8    ref_idx_l1[V4L2_HEVC_DPB_ENTRIES_NUM_MAX];
 
-       __u8    padding;
+       __u8    padding[5];
+
+       __u32   entry_point_offset_minus1[256];
 
        /* ISO/IEC 23008-2, ITU-T Rec. H.265: Weighted prediction parameter */
        struct v4l2_hevc_pred_weight_table pred_weight_table;
@@ -238,4 +245,13 @@ struct v4l2_ctrl_hevc_decode_params {
  */
 #define V4L2_CID_HANTRO_HEVC_SLICE_HEADER_SKIP (V4L2_CID_CODEC_HANTRO_BASE + 0)
 
+struct v4l2_ctrl_hevc_scaling_matrix {
+       __u8    scaling_list_4x4[6][16];
+       __u8    scaling_list_8x8[6][64];
+       __u8    scaling_list_16x16[6][64];
+       __u8    scaling_list_32x32[2][64];
+       __u8    scaling_list_dc_coef_16x16[6];
+       __u8    scaling_list_dc_coef_32x32[2];
+};
+
 #endif
index 09737b4..72c7cd8 100644 (file)
@@ -1107,4 +1107,23 @@ void media_remove_intf_links(struct media_interface *intf);
        (((entity)->ops && (entity)->ops->operation) ?                  \
         (entity)->ops->operation((entity) , ##args) : -ENOIOCTLCMD)
 
+/**
+ * media_create_ancillary_link() - create an ancillary link between two
+ *                                instances of &media_entity
+ *
+ * @primary:   pointer to the primary &media_entity
+ * @ancillary: pointer to the ancillary &media_entity
+ *
+ * Create an ancillary link between two entities, indicating that they
+ * represent two connected pieces of hardware that form a single logical unit.
+ * A typical example is a camera lens controller being linked to the sensor that
+ * it is supporting.
+ *
+ * The function sets both MEDIA_LNK_FL_ENABLED and MEDIA_LNK_FL_IMMUTABLE for
+ * the new link.
+ */
+struct media_link *
+media_create_ancillary_link(struct media_entity *primary,
+                           struct media_entity *ancillary);
+
 #endif
index 3cd25a2..0de5c2c 100644 (file)
@@ -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)
 {
index 575b59f..ebd9cef 100644 (file)
@@ -181,6 +181,8 @@ typedef void (*v4l2_ctrl_notify_fnc)(struct v4l2_ctrl *ctrl, void *priv);
  *             and/or has type %V4L2_CTRL_TYPE_STRING. In other words, &struct
  *             v4l2_ext_control uses field p to point to the data.
  * @is_array: If set, then this control contains an N-dimensional array.
+ * @is_dyn_array: If set, then this control contains a dynamically sized 1-dimensional array.
+ *             If this is set, then @is_array is also set.
  * @has_volatiles: If set, then one or more members of the cluster are volatile.
  *             Drivers should never touch this flag.
  * @call_notify: If set, then call the handler's notify function whenever the
@@ -201,6 +203,9 @@ typedef void (*v4l2_ctrl_notify_fnc)(struct v4l2_ctrl *ctrl, void *priv);
  * @step:      The control's step value for non-menu controls.
  * @elems:     The number of elements in the N-dimensional array.
  * @elem_size: The size in bytes of the control.
+ * @new_elems: The number of elements in p_new. This is the same as @elems,
+ *             except for dynamic arrays. In that case it is in the range of
+ *             1 to @p_dyn_alloc_elems.
  * @dims:      The size of each dimension.
  * @nr_of_dims:The number of dimensions in @dims.
  * @menu_skip_mask: The control's skip mask for menu controls. This makes it
@@ -219,15 +224,21 @@ typedef void (*v4l2_ctrl_notify_fnc)(struct v4l2_ctrl *ctrl, void *priv);
  *             :math:`ceil(\frac{maximum - minimum}{step}) + 1`.
  *             Used only if the @type is %V4L2_CTRL_TYPE_INTEGER_MENU.
  * @flags:     The control's flags.
- * @cur:       Structure to store the current value.
- * @cur.val:   The control's current value, if the @type is represented via
- *             a u32 integer (see &enum v4l2_ctrl_type).
- * @val:       The control's new s32 value.
  * @priv:      The control's private pointer. For use by the driver. It is
  *             untouched by the control framework. Note that this pointer is
  *             not freed when the control is deleted. Should this be needed
  *             then a new internal bitfield can be added to tell the framework
  *             to free this pointer.
+ * @p_dyn:     Pointer to the dynamically allocated array. Only valid if
+ *             @is_dyn_array is true.
+ * @p_dyn_alloc_elems: The number of elements in the dynamically allocated
+ *             array for both the cur and new values. So @p_dyn is actually
+ *             sized for 2 * @p_dyn_alloc_elems * @elem_size. Only valid if
+ *             @is_dyn_array is true.
+ * @cur:       Structure to store the current value.
+ * @cur.val:   The control's current value, if the @type is represented via
+ *             a u32 integer (see &enum v4l2_ctrl_type).
+ * @val:       The control's new s32 value.
  * @p_def:     The control's default value represented via a union which
  *             provides a standard way of accessing control types
  *             through a pointer (for compound controls only).
@@ -256,6 +267,7 @@ struct v4l2_ctrl {
        unsigned int is_string:1;
        unsigned int is_ptr:1;
        unsigned int is_array:1;
+       unsigned int is_dyn_array:1;
        unsigned int has_volatiles:1;
        unsigned int call_notify:1;
        unsigned int manual_mode_value:8;
@@ -268,6 +280,7 @@ struct v4l2_ctrl {
        s64 minimum, maximum, default_value;
        u32 elems;
        u32 elem_size;
+       u32 new_elems;
        u32 dims[V4L2_CTRL_MAX_DIMS];
        u32 nr_of_dims;
        union {
@@ -280,6 +293,8 @@ struct v4l2_ctrl {
        };
        unsigned long flags;
        void *priv;
+       void *p_dyn;
+       u32 p_dyn_alloc_elems;
        s32 val;
        struct {
                s32 val;
@@ -305,12 +320,22 @@ struct v4l2_ctrl {
  *             the control has been applied. This prevents applying controls
  *             from a cluster with multiple controls twice (when the first
  *             control of a cluster is applied, they all are).
- * @valid_p_req: If set, then p_req contains the control value for the request.
+ * @p_req_valid: If set, then p_req contains the control value for the request.
+ * @p_req_dyn_enomem: If set, then p_req is invalid since allocating space for
+ *             a dynamic array failed. Attempting to read this value shall
+ *             result in ENOMEM. Only valid if ctrl->is_dyn_array is true.
+ * @p_req_dyn_alloc_elems: The number of elements allocated for the dynamic
+ *             array. Only valid if @p_req_valid and ctrl->is_dyn_array are
+ *             true.
+ * @p_req_elems: The number of elements in @p_req. This is the same as
+ *             ctrl->elems, except for dynamic arrays. In that case it is in
+ *             the range of 1 to @p_req_dyn_alloc_elems. Only valid if
+ *             @p_req_valid is true.
  * @p_req:     If the control handler containing this control reference
  *             is bound to a media request, then this points to the
  *             value of the control that must be applied when the request
  *             is executed, or to the value of the control at the time
- *             that the request was completed. If @valid_p_req is false,
+ *             that the request was completed. If @p_req_valid is false,
  *             then this control was never set for this request and the
  *             control will not be updated when this request is applied.
  *
@@ -325,7 +350,10 @@ struct v4l2_ctrl_ref {
        struct v4l2_ctrl_helper *helper;
        bool from_other_dev;
        bool req_done;
-       bool valid_p_req;
+       bool p_req_valid;
+       bool p_req_dyn_enomem;
+       u32 p_req_dyn_alloc_elems;
+       u32 p_req_elems;
        union v4l2_ctrl_ptr p_req;
 };
 
index 841e190..80a6418 100644 (file)
                                         V4L2_MBUS_CSI2_CHANNEL_1 | \
                                         V4L2_MBUS_CSI2_CHANNEL_2 | \
                                         V4L2_MBUS_CSI2_CHANNEL_3)
+/*
+ * Number of lanes in use, 0 == use all available lanes (default)
+ *
+ * This is a temporary fix for devices that need to reduce the number of active
+ * lanes for certain modes, until g_mbus_config() can be replaced with a better
+ * solution.
+ */
+#define V4L2_MBUS_CSI2_LANE_MASK                (0xf << 10)
 
 /**
  * enum v4l2_mbus_type - media bus type
index 3b5986c..4b72d0e 100644 (file)
@@ -906,6 +906,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,
+                          unsigned int index, 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.
  * @fd:                pointer to the file descriptor associated with DMABUF
index 73ad784..dd3bbc7 100644 (file)
@@ -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,8 +92,12 @@ 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,
 
        /* Dispmanx TAGS */
        RPI_FIRMWARE_FRAMEBUFFER_ALLOCATE =                   0x00040001,
@@ -104,9 +111,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,
@@ -115,26 +129,39 @@ 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,
 };
 
+#define GET_DISPLAY_SETTINGS_PAYLOAD_SIZE 64
+
 #if IS_ENABLED(CONFIG_RASPBERRYPI_FIRMWARE)
 int rpi_firmware_property(struct rpi_firmware *fw,
                          u32 tag, void *data, size_t len);
index 808c73c..57094b5 100644 (file)
@@ -325,6 +325,13 @@ extern "C" {
 #define DRM_FORMAT_Q401                fourcc_code('Q', '4', '0', '1')
 
 /*
+ * 2 plane YCbCr MSB aligned, 3 pixels packed into 4 bytes.
+ * index 0 = Y plane, [31:0] x:Y2:Y1:Y0 2:10:10:10 little endian
+ * index 1 = Cr:Cb plane, [63:0] x:Cr2:Cb2:Cr1:x:Cb1:Cr0:Cb0 [2:10:10:10:2:10:10:10] little endian
+ */
+#define DRM_FORMAT_P030                fourcc_code('P', '0', '3', '0') /* 2x2 subsampled Cr:Cb plane 10 bits per channel packed */
+
+/*
  * 3 plane YCbCr
  * index 0: Y plane, [7:0] Y
  * index 1: Cb plane, [7:0] Cb
@@ -842,6 +849,10 @@ drm_fourcc_canonicalize_nvidia_format_mod(__u64 modifier)
  * and UV.  Some SAND-using hardware stores UV in a separate tiled
  * image from Y to reduce the column height, which is not supported
  * with these modifiers.
+ *
+ * The DRM_FORMAT_MOD_BROADCOM_SAND128_COL_HEIGHT modifier is also
+ * supported for DRM_FORMAT_P030 where the columns remain as 128 bytes
+ * wide, but as this is a 10 bpp format that translates to 96 pixels.
  */
 
 #define DRM_FORMAT_MOD_BROADCOM_SAND32_COL_HEIGHT(v) \
diff --git a/include/uapi/linux/bcm2835-isp.h b/include/uapi/linux/bcm2835-isp.h
new file mode 100644 (file)
index 0000000..c50e3ca
--- /dev/null
@@ -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_ */
index 4c14e8b..3c6f12b 100644 (file)
 #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 */
@@ -348,6 +354,12 @@ struct fb_copyarea {
        __u32 sy;
 };
 
+struct fb_dmacopy {
+       void *dst;
+       __u32 src;
+       __u32 length;
+};
+
 struct fb_fillrect {
        __u32 dx;       /* screen-relative */
        __u32 dy;
index 0dfc11e..bd33edf 100644 (file)
 
 #define MEDIA_BUS_FMT_FIXED                    0x0001
 
-/* RGB - next is       0x101e */
+/* RGB - next is       0x1021 */
 #define MEDIA_BUS_FMT_RGB444_1X12              0x1016
 #define MEDIA_BUS_FMT_RGB444_2X8_PADHI_BE      0x1001
 #define MEDIA_BUS_FMT_RGB444_2X8_PADHI_LE      0x1002
 #define MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE      0x1003
 #define MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE      0x1004
 #define MEDIA_BUS_FMT_RGB565_1X16              0x1017
+#define MEDIA_BUS_FMT_RGB565_1X24_CPADHI       0x1020
 #define MEDIA_BUS_FMT_BGR565_2X8_BE            0x1005
 #define MEDIA_BUS_FMT_BGR565_2X8_LE            0x1006
 #define MEDIA_BUS_FMT_RGB565_2X8_BE            0x1007
 #define MEDIA_BUS_FMT_RGB565_2X8_LE            0x1008
+#define MEDIA_BUS_FMT_BGR666_1X18              0x101f
 #define MEDIA_BUS_FMT_RGB666_1X18              0x1009
 #define MEDIA_BUS_FMT_RBG888_1X24              0x100e
+#define MEDIA_BUS_FMT_BGR666_1X24_CPADHI       0x101e
 #define MEDIA_BUS_FMT_RGB666_1X24_CPADHI       0x1015
 #define MEDIA_BUS_FMT_RGB666_1X7X3_SPWG                0x1010
 #define MEDIA_BUS_FMT_BGR888_1X24              0x1013
  */
 #define MEDIA_BUS_FMT_METADATA_FIXED           0x7001
 
+/* Sensor ancillary metadata formats - next is 0x7002 */
+#define MEDIA_BUS_FMT_SENSOR_DATA              0x7002
+
 #endif /* __LINUX_MEDIA_BUS_FORMAT_H */
index 200fa84..afbae72 100644 (file)
@@ -226,6 +226,7 @@ struct media_pad_desc {
 #define MEDIA_LNK_FL_LINK_TYPE                 (0xf << 28)
 #  define MEDIA_LNK_FL_DATA_LINK               (0 << 28)
 #  define MEDIA_LNK_FL_INTERFACE_LINK          (1 << 28)
+#  define MEDIA_LNK_FL_ANCILLARY_LINK          (2 << 28)
 
 struct media_link_desc {
        struct media_pad_desc source;
index 5532b5f..df3a844 100644 (file)
@@ -212,6 +212,10 @@ enum v4l2_colorfx {
  */
 #define V4L2_CID_USER_CCS_BASE                 (V4L2_CID_USER_BASE + 0x10f0)
 
+/* 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)
+
 /* MPEG-class control IDs */
 /* The MPEG controls are applicable to all codec controls
  * and the 'MPEG' part of the define is historical */
@@ -926,6 +930,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)
@@ -1118,6 +1123,7 @@ enum v4l2_jpeg_chroma_subsampling {
 #define V4L2_CID_TEST_PATTERN_BLUE             (V4L2_CID_IMAGE_SOURCE_CLASS_BASE + 6)
 #define V4L2_CID_TEST_PATTERN_GREENB           (V4L2_CID_IMAGE_SOURCE_CLASS_BASE + 7)
 #define V4L2_CID_UNIT_CELL_SIZE                        (V4L2_CID_IMAGE_SOURCE_CLASS_BASE + 8)
+#define V4L2_CID_NOTIFY_GAINS                  (V4L2_CID_IMAGE_SOURCE_CLASS_BASE + 9)
 
 
 /* Image processing controls */
index 9260791..272761a 100644 (file)
        ((__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
  */
@@ -569,6 +574,8 @@ struct v4l2_pix_format {
 /* Grey bit-packed formats */
 #define V4L2_PIX_FMT_Y10BPACK    v4l2_fourcc('Y', '1', '0', 'B') /* 10  Greyscale bit-packed */
 #define V4L2_PIX_FMT_Y10P    v4l2_fourcc('Y', '1', '0', 'P') /* 10  Greyscale, MIPI RAW10 packed */
+#define V4L2_PIX_FMT_Y12P    v4l2_fourcc('Y', '1', '2', 'P') /* 12  Greyscale, MIPI RAW12 packed */
+#define V4L2_PIX_FMT_Y14P    v4l2_fourcc('Y', '1', '4', 'P') /* 14  Greyscale, MIPI RAW12 packed */
 
 /* Palette formats */
 #define V4L2_PIX_FMT_PAL8    v4l2_fourcc('P', 'A', 'L', '8') /*  8  8-bit palette */
@@ -737,6 +744,10 @@ struct v4l2_pix_format {
 #define V4L2_PIX_FMT_SUNXI_TILED_NV12 v4l2_fourcc('S', 'T', '1', '2') /* Sunxi Tiled NV12 Format */
 #define V4L2_PIX_FMT_CNF4     v4l2_fourcc('C', 'N', 'F', '4') /* Intel 4-bit packed depth confidence information */
 #define V4L2_PIX_FMT_HI240    v4l2_fourcc('H', 'I', '2', '4') /* BTTV 8-bit dithered RGB */
+#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 bayer 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 */
@@ -766,6 +777,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 */
@@ -1869,6 +1882,7 @@ struct v4l2_querymenu {
 #define V4L2_CTRL_FLAG_HAS_PAYLOAD     0x0100
 #define V4L2_CTRL_FLAG_EXECUTE_ON_WRITE        0x0200
 #define V4L2_CTRL_FLAG_MODIFY_LAYOUT   0x0400
+#define V4L2_CTRL_FLAG_DYNAMIC_ARRAY   0x0800
 
 /*  Query flags, to be ORed with the control ID */
 #define V4L2_CTRL_FLAG_NEXT_CTRL       0x80000000
index e7c3b0e..1a64a49 100644 (file)
@@ -5833,6 +5833,9 @@ int __init cgroup_init_early(void)
        return 0;
 }
 
+static u16 cgroup_enable_mask __initdata;
+static int __init cgroup_disable(char *str);
+
 /**
  * cgroup_init - cgroup initialization
  *
@@ -5871,6 +5874,12 @@ int __init cgroup_init(void)
 
        mutex_unlock(&cgroup_mutex);
 
+       /*
+        * Apply an implicit disable, knowing that an explicit enable will
+        * prevent if from doing anything.
+        */
+       cgroup_disable("memory");
+
        for_each_subsys(ss, ssid) {
                if (ss->early_init) {
                        struct cgroup_subsys_state *css =
@@ -6455,6 +6464,10 @@ static int __init cgroup_disable(char *str)
                            strcmp(token, ss->legacy_name))
                                continue;
 
+                       /* An explicit cgroup_enable overrides a disable */
+                       if (cgroup_enable_mask & (1 << i))
+                               continue;
+
                        static_branch_disable(cgroup_subsys_enabled_key[i]);
                        pr_info("Disabling %s control group subsystem\n",
                                ss->name);
@@ -6473,6 +6486,31 @@ 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;
+
+                       cgroup_enable_mask |= 1 << i;
+                       static_branch_enable(cgroup_subsys_enabled_key[i]);
+                       pr_info("Enabling %s control group subsystem\n",
+                               ss->name);
+               }
+       }
+       return 1;
+}
+__setup("cgroup_enable=", cgroup_enable);
+
 void __init __weak enable_debug_cgroup(void) { }
 
 static int __init enable_cgroup_debug(char *str)
index 20e10e4..d40758e 100644 (file)
@@ -188,6 +188,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;
index a0b7afa..2f6d67a 100644 (file)
@@ -190,6 +190,27 @@ EXPORT_SYMBOL(init_on_alloc);
 DEFINE_STATIC_KEY_MAYBE(CONFIG_INIT_ON_FREE_DEFAULT_ON, init_on_free);
 EXPORT_SYMBOL(init_on_free);
 
+#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);
+
 static bool _init_on_alloc_enabled_early __read_mostly
                                = IS_ENABLED(CONFIG_INIT_ON_ALLOC_DEFAULT_ON);
 static int __init early_init_on_alloc(char *buf)
@@ -2980,12 +3001,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)
                                goto out;
index 7944e3e..4719af5 100644 (file)
@@ -648,8 +648,9 @@ error:
        return NULL;
 }
 
-static __init struct zswap_pool *__zswap_pool_create_fallback(void)
+static bool zswap_try_pool_create(void)
 {
+       struct zswap_pool *pool;
        bool has_comp, has_zpool;
 
        has_comp = crypto_has_acomp(zswap_compressor, 0, 0);
@@ -685,9 +686,21 @@ static __init struct zswap_pool *__zswap_pool_create_fallback(void)
        }
 
        if (!has_comp || !has_zpool)
-               return NULL;
+               return false;
+
+       pool = zswap_pool_create(zswap_zpool_type, zswap_compressor);
 
-       return zswap_pool_create(zswap_zpool_type, zswap_compressor);
+       if (pool) {
+               pr_info("loaded using pool %s/%s\n", pool->tfm_name,
+                       zpool_get_type(pool->zpool));
+               list_add(&pool->list, &zswap_pools);
+               zswap_has_pool = true;
+       } else {
+               pr_err("pool creation failed\n");
+               zswap_enabled = false;
+       }
+
+       return zswap_enabled;
 }
 
 static void zswap_pool_destroy(struct zswap_pool *pool)
@@ -860,16 +873,19 @@ static int zswap_zpool_param_set(const char *val,
 static int zswap_enabled_param_set(const char *val,
                                   const struct kernel_param *kp)
 {
+       int ret;
+
        if (zswap_init_failed) {
                pr_err("can't enable, initialization failed\n");
                return -ENODEV;
        }
-       if (!zswap_has_pool && zswap_init_started) {
-               pr_err("can't enable, no pool configured\n");
-               return -ENODEV;
-       }
 
-       return param_set_bool(val, kp);
+       ret = param_set_bool(val, kp);
+       if (!ret && zswap_enabled && zswap_init_started && !zswap_has_pool)
+               if (!zswap_try_pool_create())
+                       ret = -ENODEV;
+
+       return ret;
 }
 
 /*********************************
@@ -1436,7 +1452,6 @@ static int __init zswap_debugfs_init(void)
 **********************************/
 static int __init init_zswap(void)
 {
-       struct zswap_pool *pool;
        int ret;
 
        zswap_init_started = true;
@@ -1460,29 +1475,19 @@ static int __init init_zswap(void)
        if (ret)
                goto hp_fail;
 
-       pool = __zswap_pool_create_fallback();
-       if (pool) {
-               pr_info("loaded using pool %s/%s\n", pool->tfm_name,
-                       zpool_get_type(pool->zpool));
-               list_add(&pool->list, &zswap_pools);
-               zswap_has_pool = true;
-       } else {
-               pr_err("pool creation failed\n");
-               zswap_enabled = false;
-       }
-
        shrink_wq = create_workqueue("zswap-shrink");
        if (!shrink_wq)
-               goto fallback_fail;
+               goto hp_fail;
 
        frontswap_register_ops(&zswap_frontswap_ops);
        if (zswap_debugfs_init())
                pr_warn("debugfs initialization failed\n");
+
+       if (zswap_enabled)
+               zswap_try_pool_create();
+
        return 0;
 
-fallback_fail:
-       if (pool)
-               zswap_pool_destroy(pool);
 hp_fail:
        cpuhp_remove_state(CPUHP_MM_ZSWP_MEM_PREPARE);
 dstmem_fail:
index 11f853d..6f555b3 100644 (file)
@@ -883,16 +883,9 @@ static int tk_request(struct l2cap_conn *conn, u8 remote_oob, u8 auth,
            hcon->io_capability == HCI_IO_NO_INPUT_OUTPUT)
                smp->method = JUST_WORKS;
 
-       /* If Just Works, Continue with Zero TK and ask user-space for
-        * confirmation */
+       /* If Just Works, Continue with Zero TK */
        if (smp->method == JUST_WORKS) {
-               ret = mgmt_user_confirm_request(hcon->hdev, &hcon->dst,
-                                               hcon->type,
-                                               hcon->dst_type,
-                                               passkey, 1);
-               if (ret)
-                       return ret;
-               set_bit(SMP_FLAG_WAIT_USER, &smp->flags);
+               set_bit(SMP_FLAG_TK_VALID, &smp->flags);
                return 0;
        }
 
@@ -2207,7 +2200,7 @@ mackey_and_ltk:
        if (err)
                return SMP_UNSPECIFIED;
 
-       if (smp->method == REQ_OOB) {
+       if (smp->method == JUST_WORKS || smp->method == REQ_OOB) {
                if (hcon->out) {
                        sc_dhkey_check(smp);
                        SMP_ALLOW_CMD(smp, SMP_CMD_DHKEY_CHECK);
@@ -2222,9 +2215,6 @@ mackey_and_ltk:
        confirm_hint = 0;
 
 confirm:
-       if (smp->method == JUST_WORKS)
-               confirm_hint = 1;
-
        err = mgmt_user_confirm_request(hcon->hdev, &hcon->dst, hcon->type,
                                        hcon->dst_type, passkey, confirm_hint);
        if (err)
index 190d781..84c46c0 100644 (file)
@@ -18,9 +18,10 @@ include $(srctree)/scripts/Kbuild.include
 include $(src)/Makefile
 
 dtbs    := $(addprefix $(dst)/, $(dtb-y) $(if $(CONFIG_OF_ALL_DTBS),$(dtb-)))
+dtbos   := $(addprefix $(dst)/, $(dtbo-y) $(if $(CONFIG_OF_ALL_DTBS),$(dtb-)))
 subdirs := $(addprefix $(obj)/, $(subdir-y) $(subdir-m))
 
-__dtbs_install: $(dtbs) $(subdirs)
+__dtbs_install: $(dtbs) $(dtbos) $(subdirs)
        @:
 
 quiet_cmd_dtb_install = INSTALL $@
index 0a8a468..fa25e95 100644 (file)
@@ -300,6 +300,7 @@ DTC_FLAGS += -Wno-interrupt_provider
 ifeq ($(findstring 1,$(KBUILD_EXTRA_WARN)),)
 DTC_FLAGS += -Wno-unit_address_vs_reg \
        -Wno-unit_address_format \
+       -Wno-gpios_property \
        -Wno-avoid_unnecessary_addr_size \
        -Wno-alias_paths \
        -Wno-graph_child_address \
@@ -373,6 +374,24 @@ endef
 $(obj)/%.dt.yaml: $(src)/%.dts $(DTC) $(DT_TMP_SCHEMA) FORCE
        $(call if_changed_rule,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 \
+               -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
index 4218057..c865ad6 100644 (file)
@@ -26,3 +26,308 @@ 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"
+         depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+         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"
+       depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+       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_DAC
+        tristate "Support for HifiBerry DAC"
+        depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+        select SND_SOC_PCM5102A
+        select SND_RPI_SIMPLE_SOUNDCARD
+        help
+         Say Y or M if you want to add support for HifiBerry DAC.
+
+config SND_BCM2708_SOC_HIFIBERRY_DACPLUS
+        tristate "Support for HifiBerry DAC+"
+        depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+        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"
+        depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+        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"
+        depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+        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"
+        depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+        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"
+        depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+       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"
+        depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+        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"
+        depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+        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"
+         depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+         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"
+        depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+        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"
+        depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+        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"
+       depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+       select SND_SOC_WM8731
+       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"
+       depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+       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"
+       depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+       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"
+       depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+       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"
+       depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+       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"
+       depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+       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"
+       depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+       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"
+        depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+        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"
+       depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+       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"
+       depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+       select SND_SOC_WM8731
+       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"
+       depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+       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"
+       depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+       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"
+       depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+       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"
+        depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+        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"
+       depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+       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"
+       depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+       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"
+       depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+       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"
+       depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+       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"
+       depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+       select SND_SOC_PCM512x_I2C
+       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 SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+       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"
+       depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+       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 SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+       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"
+       depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+       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"
+       depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+       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"
+       depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
+       select SND_SOC_TLV320AIC3X_I2C
+       help
+         Say Y or M if you want to add support for tlv320aic3x add-on
index 7c2d789..46d1ece 100644 (file)
@@ -12,4 +12,73 @@ obj-$(CONFIG_SND_SOC_CYGNUS) += snd-soc-cygnus.o
 # BCM63XX Platform Support
 snd-soc-63xx-objs := 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-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_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 (file)
index 0000000..22564e8
--- /dev/null
@@ -0,0 +1,456 @@
+/*
+ * 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 = asoc_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 = asoc_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 = asoc_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_physical_width(params_format(params));
+
+       if (snd_soc_allo_boss_master) {
+               struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
+
+               snd_allo_boss_set_sclk(component,
+                       params_rate(params));
+
+               ret = snd_allo_boss_update_rate_den(
+                       substream, params);
+               if (ret)
+                       return ret;
+       }
+
+       ret = snd_soc_dai_set_bclk_ratio(asoc_rtd_to_cpu(rtd, 0), channels * width);
+       if (ret)
+               return ret;
+       ret = snd_soc_dai_set_bclk_ratio(asoc_rtd_to_codec(rtd, 0), channels * width);
+       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 = asoc_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 = asoc_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 int snd_allo_boss_remove(struct platform_device *pdev)
+{
+       snd_allo_boss_gpio_mute(&snd_allo_boss);
+       return 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 (file)
index 0000000..5ad7f16
--- /dev/null
@@ -0,0 +1,1133 @@
+/*
+ * 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, &reg);
+       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->den & CS43130_SP_M_LSB_DATA_MASK) >>
+                               CS43130_SP_M_LSB_DATA_SHIFT);
+               regmap_write(cs43130->regmap, CS43130_ASP_DEN_2,
+                               (clk_gen->den & CS43130_SP_M_MSB_DATA_MASK) >>
+                               CS43130_SP_M_MSB_DATA_SHIFT);
+               regmap_write(cs43130->regmap, CS43130_ASP_NUM_1,
+                               (clk_gen->num & CS43130_SP_N_LSB_DATA_MASK) >>
+                               CS43130_SP_N_LSB_DATA_SHIFT);
+               regmap_write(cs43130->regmap, CS43130_ASP_NUM_2,
+                               (clk_gen->num & 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->den & CS43130_SP_M_LSB_DATA_MASK) >>
+                               CS43130_SP_M_LSB_DATA_SHIFT);
+               regmap_write(cs43130->regmap, CS43130_XSP_DEN_2,
+                               (clk_gen->den & CS43130_SP_M_MSB_DATA_MASK) >>
+                               CS43130_SP_M_MSB_DATA_SHIFT);
+               regmap_write(cs43130->regmap, CS43130_XSP_NUM_1,
+                               (clk_gen->num & CS43130_SP_N_LSB_DATA_MASK) >>
+                               CS43130_SP_N_LSB_DATA_SHIFT);
+               regmap_write(cs43130->regmap, CS43130_XSP_NUM_2,
+                               (clk_gen->num & 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,
+       .non_legacy_dai_naming  = 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,
+                            const struct i2c_device_id *id)
+{
+       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, &reg);
+       devid = (reg & 0xFF) << 12;
+       ret = regmap_read(cs43130->regmap, CS43130_DEVID_CD, &reg);
+       devid |= (reg & 0xFF) << 4;
+       ret = regmap_read(cs43130->regmap, CS43130_DEVID_E, &reg);
+       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 int allo_cs43130_component_remove(struct i2c_client *i2c)
+{
+       snd_soc_unregister_component(&i2c->dev);
+       return 0;
+}
+
+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 (file)
index 0000000..b0aebd4
--- /dev/null
@@ -0,0 +1,388 @@
+/*
+ * 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;
+}
+
+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,
+                            const struct i2c_device_id *id)
+{
+       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 int allo_katana_component_remove(struct i2c_client *i2c)
+{
+       snd_soc_unregister_component(&i2c->dev);
+       return 0;
+}
+
+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 (file)
index 0000000..3efc898
--- /dev/null
@@ -0,0 +1,1063 @@
+/*
+ * 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(asoc_rtd_to_codec(rtd, 0)->component,
+                               PCM512x_MUTE, P_DAC_UNMUTE);
+               snd_soc_component_write(asoc_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(asoc_rtd_to_codec(rtd, 0)->component,
+                               PCM512x_MUTE, P_DAC_UNMUTE);
+               snd_soc_component_write(asoc_rtd_to_codec(rtd, 1)->component,
+                               PCM512x_MUTE, P_DAC_UNMUTE);
+       }
+
+       for (dac = 0; dac < rtd->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(asoc_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(asoc_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(asoc_rtd_to_codec(rtd, 0)->component,
+                               PCM512x_MUTE, P_DAC_RIGHT_MUTE);
+               snd_soc_component_write(asoc_rtd_to_codec(rtd, 1)->component,
+                               PCM512x_MUTE, P_DAC_LEFT_MUTE);
+               snd_soc_component_write(asoc_rtd_to_codec(rtd, 0)->component,
+                               PCM512x_DIGITAL_VOLUME_3, 0xff);
+               snd_soc_component_write(asoc_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(asoc_rtd_to_codec(rtd, 0)->component,
+                                               PCM512x_DIGITAL_VOLUME_2);
+               right_val = snd_soc_component_read(asoc_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(asoc_rtd_to_codec(rtd, 0)->component,
+                               PCM512x_DIGITAL_VOLUME_3, left_val);
+               snd_soc_component_write(asoc_rtd_to_codec(rtd, 1)->component,
+                               PCM512x_DIGITAL_VOLUME_2, right_val);
+               snd_soc_component_write(asoc_rtd_to_codec(rtd, 0)->component,
+                               PCM512x_MUTE, P_DAC_UNMUTE);
+               snd_soc_component_write(asoc_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(asoc_rtd_to_codec(rtd, 0)->component,
+                                               PCM512x_DIGITAL_VOLUME_2);
+               right_val = snd_soc_component_read(asoc_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(asoc_rtd_to_codec(rtd, 0)->component,
+                               PCM512x_DIGITAL_VOLUME_3, left_val);
+               snd_soc_component_write(asoc_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(asoc_rtd_to_codec(rtd, 1)->component,
+                       PCM512x_DIGITAL_VOLUME_3);
+       if (glb_ptr->dual_mode != 1) {
+               left_val = snd_soc_component_read(asoc_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]);
+
+       if (digital_gain_0db_limit) {
+               ret = snd_soc_limit_volume(card, "Subwoofer Playback Volume",
+                                       207);
+               if (ret < 0)
+                       dev_warn(card->dev, "Failed to set volume limit: %d\n",
+                               ret);
+       }
+
+       // 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(asoc_rtd_to_codec(rtd, 1)->component,
+                               PCM512x_DIGITAL_VOLUME_2, (~left_val));
+               if (ret < 0)
+                       return ret;
+
+               ret = snd_soc_component_write(asoc_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(asoc_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(asoc_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(asoc_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(asoc_rtd_to_codec(rtd, 1)->component,
+                               PCM512x_DIGITAL_VOLUME_3);
+       } else {
+               right_val = snd_soc_component_read(asoc_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 (digital_gain_0db_limit) {
+               ret = snd_soc_limit_volume(card, "Master Playback Volume",
+                                       207);
+               if (ret < 0)
+                       dev_warn(card->dev, "Failed to set volume limit: %d\n",
+                               ret);
+       }
+
+       if (glb_ptr->dual_mode == 1) { //in Dual Mono Mode
+
+               ret = snd_soc_component_write(asoc_rtd_to_codec(rtd, 0)->component,
+                               PCM512x_DIGITAL_VOLUME_2, (~left_val));
+               if (ret < 0)
+                       return ret;
+
+               ret = snd_soc_component_write(asoc_rtd_to_codec(rtd, 1)->component,
+                               PCM512x_DIGITAL_VOLUME_3, (~right_val));
+               if (ret < 0)
+                       return ret;
+
+       } else {
+
+               ret = snd_soc_component_write(asoc_rtd_to_codec(rtd, 0)->component,
+                               PCM512x_DIGITAL_VOLUME_2, (~left_val));
+               if (ret < 0)
+                       return ret;
+
+               ret = snd_soc_component_write(asoc_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(asoc_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(asoc_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(asoc_rtd_to_codec(rtd, 0)->component, PCM512x_MUTE,
+                               ~((left_val & 0x01)<<4));
+               if (ret < 0)
+                       return ret;
+               ret = snd_soc_component_write(asoc_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(asoc_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(asoc_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(asoc_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);
+
+       if (digital_gain_0db_limit) {
+               int ret;
+
+               //Set volume limit on both dacs
+               for (i = 0; i < ARRAY_SIZE(codec_ctl_pfx); i++) {
+                       char cname[256];
+
+                       sprintf(cname, "%s %s", codec_ctl_pfx[i], codec_ctl_name[0]);
+                       ret = snd_soc_limit_volume(card, cname, 207);
+                       if (ret < 0)
+                               dev_warn(card->dev, "Failed to set volume limit: %d\n",
+                                       ret);
+               }
+       }
+
+       // 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) {
+                               dev_err(rtd->card->dev, "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 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 = asoc_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(asoc_rtd_to_codec(rtd, dac)->component,
+                                       PCM512x_RATE_DET_4);
+
+               if (val & 0x40) {
+                       snd_soc_component_write(asoc_rtd_to_codec(rtd, dac)->component,
+                                       PCM512x_PLL_REF,
+                                       PCM512x_SREF_BCK);
+
+                       dev_info(asoc_rtd_to_codec(rtd, dac)->component->dev,
+                               "Setting BCLK as input clock & Enable PLL\n");
+               } else {
+                       snd_soc_component_write(asoc_rtd_to_codec(rtd, dac)->component,
+                                       PCM512x_PLL_EN,
+                                       0x00);
+
+                       snd_soc_component_write(asoc_rtd_to_codec(rtd, dac)->component,
+                                       PCM512x_PLL_REF,
+                                       PCM512x_SREF_SCK);
+
+                       dev_info(asoc_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);
+               if (!allo_piano_2_1_codecs[0].of_node) {
+                       dev_err(&pdev->dev,
+                               "Property 'audio-codec' missing or invalid\n");
+                       return -EINVAL;
+               }
+
+               allo_piano_2_1_codecs[1].of_node =
+                       of_parse_phandle(pdev->dev.of_node, "audio-codec", 1);
+               if (!allo_piano_2_1_codecs[1].of_node) {
+                       dev_err(&pdev->dev,
+                               "Property 'audio-codec' missing or invalid\n");
+                       return -EINVAL;
+               }
+
+               mute_gpio[0] = devm_gpiod_get_optional(&pdev->dev, "mute1",
+                                                       GPIOD_OUT_LOW);
+               if (IS_ERR(mute_gpio[0])) {
+                       ret = PTR_ERR(mute_gpio[0]);
+                       dev_err(&pdev->dev,
+                               "failed to get mute1 gpio6: %d\n", ret);
+                       return ret;
+               }
+
+               mute_gpio[1] = devm_gpiod_get_optional(&pdev->dev, "mute2",
+                                                       GPIOD_OUT_LOW);
+               if (IS_ERR(mute_gpio[1])) {
+                       ret = PTR_ERR(mute_gpio[1]);
+                       dev_err(&pdev->dev,
+                               "failed to get mute2 gpio25: %d\n", ret);
+                       return ret;
+               }
+
+               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) {
+                       dev_err(&pdev->dev,
+                               "snd_soc_register_card() failed: %d\n", ret);
+                       return ret;
+               }
+
+               if ((mute_gpio[0]) && (mute_gpio[1]))
+                       snd_allo_piano_gpio_mute(&snd_allo_piano_dac);
+
+               return 0;
+       }
+
+       return -EINVAL;
+}
+
+static int 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);
+       return 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 (file)
index 0000000..61640fb
--- /dev/null
@@ -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 (file)
index 0000000..127d496
--- /dev/null
@@ -0,0 +1,183 @@
+/*
+ * 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(asoc_rtd_to_codec(rtd, 0), 0, 24576000, 0);
+       if (ret)
+               return ret;
+
+       return snd_soc_dai_set_bclk_ratio(asoc_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",
+       .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 (file)
index 0000000..db507f3
--- /dev/null
@@ -0,0 +1,346 @@
+/*
+ * 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(asoc_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;
+       asoc_rtd_to_cpu(rtd, 0)->driver->playback.channels_min = 8;
+       asoc_rtd_to_cpu(rtd, 0)->driver->playback.channels_max = 8;
+       asoc_rtd_to_cpu(rtd, 0)->driver->capture.channels_min = 8;
+       asoc_rtd_to_cpu(rtd, 0)->driver->capture.channels_max = 8;
+       asoc_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;
+       asoc_rtd_to_cpu(rtd, 0)->driver->playback.channels_min = 2;
+       asoc_rtd_to_cpu(rtd, 0)->driver->playback.channels_max = 2;
+       asoc_rtd_to_cpu(rtd, 0)->driver->capture.channels_min = 2;
+       asoc_rtd_to_cpu(rtd, 0)->driver->capture.channels_max = 2;
+       asoc_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(asoc_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(asoc_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(asoc_rtd_to_codec(rtd, 0), 0, 49152000,
+                                                                       0);
+               break;
+       case 24000:
+               return snd_soc_dai_set_sysclk(asoc_rtd_to_codec(rtd, 0), 0, 49152000/2,
+                                                                       0);
+               break;
+       case 32000:
+       case 16000:
+               return snd_soc_dai_set_sysclk(asoc_rtd_to_codec(rtd, 0), 0, 49152000/3,
+                                                                       0);
+               break;
+       case 8000:
+               return snd_soc_dai_set_sysclk(asoc_rtd_to_codec(rtd, 0), 0, 49152000/6,
+                                                                       0);
+               break;
+       case 88200:
+       case 44100:
+               return snd_soc_dai_set_sysclk(asoc_rtd_to_codec(rtd, 0), 0, 45185400,
+                                                                       0);
+               break;
+       case 22050:
+               return snd_soc_dai_set_sysclk(asoc_rtd_to_codec(rtd, 0), 0, 45185400/2,
+                                                                       0);
+               break;
+       case 29400:
+       case 14700:
+               return snd_soc_dai_set_sysclk(asoc_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",
+       .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 (file)
index 0000000..ad33720
--- /dev/null
@@ -0,0 +1,189 @@
+/*
+ * 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 = asoc_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(asoc_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",
+       .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 (file)
index 0000000..b76d974
--- /dev/null
@@ -0,0 +1,248 @@
+/*
+ * 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 = asoc_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 = asoc_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 int audiosense_pi_card_remove(struct platform_device *pdev)
+{
+       struct snd_soc_card *card = platform_get_drvdata(pdev);
+
+       return 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/chipdip-dac.c b/sound/soc/bcm/chipdip-dac.c
new file mode 100644 (file)
index 0000000..6cb5369
--- /dev/null
@@ -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(asoc_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 (file)
index 0000000..55c2613
--- /dev/null
@@ -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 = asoc_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 = asoc_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 = asoc_rtd_to_codec(rtd, 0);
+       struct snd_soc_dai *cpu_dai = asoc_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 int snd_rpi_dacberry400_remove(struct platform_device *pdev)
+{
+       return 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 (file)
index 0000000..4649c6f
--- /dev/null
@@ -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 = asoc_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 = asoc_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 = asoc_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 = asoc_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 = asoc_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 = asoc_rtd_to_codec(rtd, 0);
+       struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
+       struct snd_soc_dai *cpu_dai = asoc_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 = asoc_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 (file)
index 0000000..ca48aef
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * 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",
+       .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 (file)
index 0000000..b19a06a
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * 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 = asoc_rtd_to_cpu(rtd, 0);
+
+       unsigned int sample_bits =
+               snd_pcm_format_physical_width(params_format(params));
+
+       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",
+       .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 (file)
index 0000000..ded4ba1
--- /dev/null
@@ -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 = asoc_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 = asoc_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 (file)
index 0000000..4d38818
--- /dev/null
@@ -0,0 +1,214 @@
+/*
+ * 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_active[SNDRV_PCM_STREAM_PLAYBACK]);
+       dev_dbg(dai->dev, "Capture Active  %d", dai->stream_active[SNDRV_PCM_STREAM_CAPTURE]);
+
+       switch (cmd) {
+       case SNDRV_PCM_TRIGGER_START:
+       case SNDRV_PCM_TRIGGER_RESUME:
+       case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+               if (dai->stream_active[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 (dai->stream_active[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 int voicehat_platform_remove(struct platform_device *pdev)
+{
+       snd_soc_unregister_component(&pdev->dev);
+       return 0;
+}
+
+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_dacplus.c b/sound/soc/bcm/hifiberry_dacplus.c
new file mode 100644 (file)
index 0000000..1d89854
--- /dev/null
@@ -0,0 +1,527 @@
+/*
+ * 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 struct gpio_desc *snd_mute_gpio;
+static struct gpio_desc *snd_reset_gpio;
+static struct snd_soc_card snd_rpi_hifiberry_dacplus;
+
+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 = asoc_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;
+
+               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 = asoc_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 = 32;
+
+       if (snd_rpi_hifiberry_is_dacpro) {
+               struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
+
+               width = snd_pcm_format_physical_width(params_format(params));
+
+               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(asoc_rtd_to_cpu(rtd, 0), channels * width);
+       if (ret)
+               return ret;
+       ret = snd_soc_dai_set_bclk_ratio(asoc_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 = asoc_rtd_to_codec(rtd, 0)->component;
+
+       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 = asoc_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 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 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;
+                       }
+               }
+       }
+
+       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 (file)
index 0000000..5fe6fd2
--- /dev/null
@@ -0,0 +1,398 @@
+/*
+ * 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 = asoc_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 = asoc_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 = 32;
+
+       if (snd_rpi_hifiberry_is_dacpro) {
+               struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
+
+               width = snd_pcm_format_physical_width(params_format(params));
+
+               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(asoc_rtd_to_cpu(rtd, 0), channels * width);
+       if (ret)
+               return ret;
+       ret = snd_soc_dai_set_bclk_ratio(asoc_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 = asoc_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 = asoc_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 (file)
index 0000000..517a70f
--- /dev/null
@@ -0,0 +1,605 @@
+/*
+ * 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"
+
+#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 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]),
+};
+
+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 = asoc_rtd_to_codec(rtd, 0)->component;
+       struct snd_soc_component *adc = asoc_rtd_to_codec(rtd, 1)->component;
+       struct snd_soc_dai_driver *adc_driver = asoc_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(asoc_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(asoc_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(asoc_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 = asoc_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 = 32;
+       struct snd_soc_component *dac = asoc_rtd_to_codec(rtd, 0)->component;
+       struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0);
+       struct snd_soc_dai_driver *drv = dai->driver;
+       const struct snd_soc_dai_ops *ops = drv->ops;
+
+       if (snd_rpi_hifiberry_is_dacpro) {
+               width = snd_pcm_format_physical_width(params_format(params));
+
+               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(asoc_rtd_to_cpu(rtd, 0), channels * width);
+       if (ret)
+               return ret;
+       ret = snd_soc_dai_set_bclk_ratio(asoc_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 = asoc_rtd_to_codec(rtd, 0)->component;
+       struct snd_soc_component *adc = asoc_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 = asoc_rtd_to_codec(rtd, 0)->component;
+       struct snd_soc_component *adc = asoc_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 (file)
index 0000000..e5919f8
--- /dev/null
@@ -0,0 +1,90 @@
+// 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 int dacplusdsp_platform_remove(struct platform_device *pdev)
+{
+       snd_soc_unregister_component(&pdev->dev);
+       return 0;
+}
+
+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 (file)
index 0000000..7e7e514
--- /dev/null
@@ -0,0 +1,238 @@
+// 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 = asoc_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 = asoc_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 int snd_rpi_hifiberry_dacplushd_remove(struct platform_device *pdev)
+{
+       if (IS_ERR(reset_gpio))
+               return -EINVAL;
+
+       /* put DAC into RESET and release GPIO */
+       gpiod_set_value(reset_gpio, 0);
+       gpiod_put(reset_gpio);
+
+       return 0;
+}
+
+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 (file)
index 0000000..6809232
--- /dev/null
@@ -0,0 +1,158 @@
+/*
+ * 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 = asoc_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 = asoc_rtd_to_cpu(rtd, 0);
+       int bclk_ratio;
+
+       bclk_ratio = snd_pcm_format_physical_width(
+                       params_format(params)) * 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 int snd_rpi_i_sabre_q2m_remove(struct platform_device *pdev)
+{
+       return 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 (file)
index 0000000..ea431ae
--- /dev/null
@@ -0,0 +1,274 @@
+/*
+ * 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 = asoc_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 = asoc_rtd_to_codec(rtd, 0);
+       struct snd_soc_dai *cpu_dai = asoc_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);
+
+       /* Set bclk ratio to align with codec's BCLK rate */
+       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;
+       }
+
+       /* 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;
+       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;
+               return 0;
+       case 44100:
+       case 88200:
+               pll_out = DA7213_PLL_FREQ_OUT_90316800;
+               return 0;
+       default:
+               dev_err(rtd->dev,"Unsupported samplerate %d\n", samplerate);
+               return -EINVAL;
+       }
+}
+
+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 int snd_rpi_iqaudio_codec_remove(struct platform_device *pdev)
+{
+       return 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 (file)
index 0000000..62f64c8
--- /dev/null
@@ -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 = asoc_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 int snd_rpi_iqaudio_dac_remove(struct platform_device *pdev)
+{
+       snd_rpi_iqaudio_gpio_mute(&snd_rpi_iqaudio_dac);
+
+       return 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 (file)
index 0000000..471eceb
--- /dev/null
@@ -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 = asoc_rtd_to_codec(rtd, 0)->component;
+       struct snd_soc_component *dac = asoc_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 = asoc_rtd_to_codec(rtd, 0);
+       struct snd_soc_component *digi = asoc_rtd_to_codec(rtd, 0)->component;
+       struct snd_soc_dai *cpu_dai = asoc_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 = asoc_rtd_to_codec(rtd, 0)->component;
+       struct snd_soc_component *dac = asoc_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 = asoc_rtd_to_codec(rtd, 0)->component;
+       struct snd_soc_component *dac = asoc_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 int snd_rpi_justboom_both_remove(struct platform_device *pdev)
+{
+       return 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 (file)
index 0000000..f9fbdc9
--- /dev/null
@@ -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 = asoc_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 = asoc_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 = asoc_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 (file)
index 0000000..ae699fb
--- /dev/null
@@ -0,0 +1,283 @@
+// 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] = asoc_rtd_to_codec(rtd, 0)->component;
+       dac[1] = asoc_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] = asoc_rtd_to_codec(rtd, 0)->component;
+       dac[1] = asoc_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 = asoc_rtd_to_cpu(rtd, 0);
+       unsigned int sample_bits;
+
+       sample_bits = snd_pcm_format_physical_width(params_format(params));
+       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 int 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);
+       return 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 (file)
index 0000000..629dde0
--- /dev/null
@@ -0,0 +1,1240 @@
+/*
+ * Pisound Linux kernel module.
+ * Copyright (C) 2016-2020  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 struct snd_rawmidi_ops pisnd_output_ops = {
+       .open = pisnd_output_open,
+       .close = pisnd_output_close,
+       .trigger = pisnd_output_trigger,
+       .drain = pisnd_output_drain,
+};
+
+static 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 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 = 150000;
+       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));
+
+       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 struct gpio_desc *button;
+
+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 = asoc_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_physical_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 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);
+
+       button = gpiod_get_index(dev, "button", 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);
+
+       gpiod_export(button, false);
+
+       return 0;
+}
+
+static int pisnd_uninit_gpio(void)
+{
+       int i;
+
+       struct gpio_desc **gpios[] = {
+               &osr0, &osr1, &osr2, &reset, &button,
+       };
+
+       gpiod_unexport(button);
+
+       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 int 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();
+
+       return 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 (file)
index 0000000..227a528
--- /dev/null
@@ -0,0 +1,1025 @@
+/*
+ * 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 =
+               asoc_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 =
+               asoc_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 =
+               asoc_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 =
+               asoc_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 =
+               asoc_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 =
+               asoc_rtd_to_codec(wm5102_runtime, 0)->component;
+
+       int ret = 0;
+       unsigned int clk_freq;
+
+       if (dapm->dev != asoc_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 =
+               asoc_rtd_to_codec(wm5102_runtime, 0)->component;
+
+       if (dapm->dev != asoc_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 = asoc_rtd_to_cpu(rtd, 0);
+       struct snd_soc_component *wm5102_component = asoc_rtd_to_codec(rtd, 0)->component;
+       struct snd_soc_dai *wm8804_dai = asoc_rtd_to_codec(get_wm8804_runtime(card), 0);
+
+       int ret;
+
+       unsigned int width = snd_pcm_format_physical_width(
+               params_format(params));
+       unsigned int rate = params_rate(params);
+       unsigned int clk_freq = calc_sysclk(rate);
+
+       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(asoc_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 = asoc_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 = asoc_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 = asoc_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,
+               .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(
+               asoc_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(
+               asoc_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 (file)
index 0000000..9a5cf91
--- /dev/null
@@ -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 = asoc_rtd_to_codec(rtd, 0);
+       struct snd_soc_dai *cpu_dai = asoc_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 (file)
index 0000000..fd1e429
--- /dev/null
@@ -0,0 +1,466 @@
+// 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 = asoc_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 = asoc_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 = asoc_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. More complex drivers could just replace
+        * the hw_params routine.
+        */
+       sample_bits = snd_pcm_format_physical_width(params_format(params));
+       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,
+};
+
+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 = asoc_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_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(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",
+               .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-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 = "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 (file)
index 0000000..835d0f9
--- /dev/null
@@ -0,0 +1,410 @@
+// 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 <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;
+
+/* 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 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 = asoc_rtd_to_codec(rtd, 0);
+       struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
+       struct snd_soc_dai *cpu_dai = asoc_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,
+};
+
+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,
+};
+
+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 },
+       {},
+};
+
+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");
index d59a7e9..2bf001d 100644 (file)
@@ -102,6 +102,7 @@ config SND_SOC_ALL_CODECS
        imply SND_SOC_ICS43432
        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
@@ -109,6 +110,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
@@ -146,6 +148,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
@@ -217,6 +220,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
@@ -358,12 +362,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
 
@@ -873,6 +877,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
@@ -1187,6 +1198,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
@@ -1437,6 +1452,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
@@ -1943,4 +1961,8 @@ config SND_SOC_LPASS_TX_MACRO
        select REGMAP_MMIO
        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
index 8dcea2c..58493a2 100644 (file)
@@ -99,6 +99,7 @@ snd-soc-hdac-hda-objs := hdac_hda.o
 snd-soc-ics43432-objs := ics43432.o
 snd-soc-inno-rk3036-objs := inno_rk3036.o
 snd-soc-isabelle-objs := isabelle.o
+snd-soc-i-sabre-codec-objs := i-sabre-codec.o
 snd-soc-jz4740-codec-objs := jz4740.o
 snd-soc-jz4725b-codec-objs := jz4725b.o
 snd-soc-jz4760-codec-objs := jz4760.o
@@ -111,6 +112,7 @@ snd-soc-lpass-rx-macro-objs := lpass-rx-macro.o
 snd-soc-lpass-tx-macro-objs := lpass-tx-macro.o
 snd-soc-lpass-wsa-macro-objs := lpass-wsa-macro.o
 snd-soc-lpass-va-macro-objs := lpass-va-macro.o
+snd-soc-ma120x0p-objs := ma120x0p.o
 snd-soc-madera-objs := madera.o
 snd-soc-max9759-objs := max9759.o
 snd-soc-max9768-objs := max9768.o
@@ -154,6 +156,7 @@ snd-soc-pcm179x-spi-objs := pcm179x-spi.o
 snd-soc-pcm186x-objs := pcm186x.o
 snd-soc-pcm186x-i2c-objs := pcm186x-i2c.o
 snd-soc-pcm186x-spi-objs := pcm186x-spi.o
+snd-soc-pcm1794a-objs := pcm1794a.o
 snd-soc-pcm3008-objs := pcm3008.o
 snd-soc-pcm3060-objs := pcm3060.o
 snd-soc-pcm3060-i2c-objs := pcm3060-i2c.o
@@ -231,6 +234,7 @@ snd-soc-tas6424-objs := tas6424.o
 snd-soc-tda7419-objs := tda7419.o
 snd-soc-tas2770-objs := tas2770.o
 snd-soc-tfa9879-objs := tfa9879.o
+snd-soc-tas5713-objs := tas5713.o
 snd-soc-tfa989x-objs := tfa989x.o
 snd-soc-tlv320aic23-objs := tlv320aic23.o
 snd-soc-tlv320aic23-i2c-objs := tlv320aic23-i2c.o
@@ -430,6 +434,7 @@ obj-$(CONFIG_SND_SOC_HDAC_HDA) += snd-soc-hdac-hda.o
 obj-$(CONFIG_SND_SOC_ICS43432) += snd-soc-ics43432.o
 obj-$(CONFIG_SND_SOC_INNO_RK3036)      += snd-soc-inno-rk3036.o
 obj-$(CONFIG_SND_SOC_ISABELLE) += snd-soc-isabelle.o
+obj-$(CONFIG_SND_SOC_I_SABRE_CODEC)    += snd-soc-i-sabre-codec.o
 obj-$(CONFIG_SND_SOC_JZ4740_CODEC)     += snd-soc-jz4740-codec.o
 obj-$(CONFIG_SND_SOC_JZ4725B_CODEC)    += snd-soc-jz4725b-codec.o
 obj-$(CONFIG_SND_SOC_JZ4760_CODEC)      += snd-soc-jz4760-codec.o
@@ -438,6 +443,7 @@ obj-$(CONFIG_SND_SOC_L3)    += snd-soc-l3.o
 obj-$(CONFIG_SND_SOC_LM4857)   += snd-soc-lm4857.o
 obj-$(CONFIG_SND_SOC_LM49453)   += snd-soc-lm49453.o
 obj-$(CONFIG_SND_SOC_LOCHNAGAR_SC)     += snd-soc-lochnagar-sc.o
+obj-$(CONFIG_SND_SOC_MA120X0P)   += snd-soc-ma120x0p.o
 obj-$(CONFIG_SND_SOC_MADERA)   += snd-soc-madera.o
 obj-$(CONFIG_SND_SOC_MAX9759)  += snd-soc-max9759.o
 obj-$(CONFIG_SND_SOC_MAX9768)  += snd-soc-max9768.o
@@ -492,6 +498,7 @@ obj-$(CONFIG_SND_SOC_PCM5102A)      += snd-soc-pcm5102a.o
 obj-$(CONFIG_SND_SOC_PCM512x)  += snd-soc-pcm512x.o
 obj-$(CONFIG_SND_SOC_PCM512x_I2C)      += snd-soc-pcm512x-i2c.o
 obj-$(CONFIG_SND_SOC_PCM512x_SPI)      += snd-soc-pcm512x-spi.o
+obj-$(CONFIG_SND_SOC_PCM1794A) += snd-soc-pcm1794a.o
 obj-$(CONFIG_SND_SOC_RK3328)   += snd-soc-rk3328.o
 obj-$(CONFIG_SND_SOC_RK817)    += snd-soc-rk817.o
 obj-$(CONFIG_SND_SOC_RL6231)   += snd-soc-rl6231.o
@@ -558,6 +565,7 @@ obj-$(CONFIG_SND_SOC_TAS5720)       += snd-soc-tas5720.o
 obj-$(CONFIG_SND_SOC_TAS6424)  += snd-soc-tas6424.o
 obj-$(CONFIG_SND_SOC_TDA7419)  += snd-soc-tda7419.o
 obj-$(CONFIG_SND_SOC_TAS2770) += snd-soc-tas2770.o
+obj-$(CONFIG_SND_SOC_TAS5713)  += snd-soc-tas5713.o
 obj-$(CONFIG_SND_SOC_TFA9879)  += snd-soc-tfa9879.o
 obj-$(CONFIG_SND_SOC_TFA989X)  += snd-soc-tfa989x.o
 obj-$(CONFIG_SND_SOC_TLV320AIC23)      += snd-soc-tlv320aic23.o
index 0214e3a..b8b15b8 100644 (file)
@@ -45,11 +45,18 @@ static 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,
index 5d6ef66..7d84031 100644 (file)
@@ -517,8 +517,10 @@ const struct of_device_id cs42xx8_of_match[] = {
        { .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)
 {
diff --git a/sound/soc/codecs/i-sabre-codec.c b/sound/soc/codecs/i-sabre-codec.c
new file mode 100644 (file)
index 0000000..714e1f5
--- /dev/null
@@ -0,0 +1,392 @@
+/*
+ * 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, const struct i2c_device_id *id)
+{
+       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 int i_sabre_codec_i2c_remove(struct i2c_client *i2c)
+{
+       i_sabre_codec_remove(&i2c->dev);
+
+       return 0;
+}
+
+
+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 (file)
index 0000000..9cac5a2
--- /dev/null
@@ -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 (file)
index 0000000..ac812e8
--- /dev/null
@@ -0,0 +1,1384 @@
+// 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, -5000, 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, 0x4a, 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,
+       .non_legacy_dai_naming  = 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,
+       const struct i2c_device_id *id)
+{
+       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 int 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);
+
+       return 0;
+}
+
+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 (file)
index 0000000..36b5b6c
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * 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 int pcm1794a_remove(struct platform_device *pdev)
+{
+       snd_soc_unregister_component(&pdev->dev);
+       return 0;
+}
+
+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");
index 60dee41..33ed3bb 100644 (file)
@@ -536,7 +536,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 = {
diff --git a/sound/soc/codecs/tas5713.c b/sound/soc/codecs/tas5713.c
new file mode 100644 (file)
index 0000000..53acd2b
--- /dev/null
@@ -0,0 +1,363 @@
+/*
+ * 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,
+                           const struct i2c_device_id *id)
+{
+       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 int tas5713_i2c_remove(struct i2c_client *i2c)
+{
+       snd_soc_unregister_component(&i2c->dev);
+       i2c_set_clientdata(i2c, NULL);
+
+       kfree(priv_data);
+
+       return 0;
+}
+
+
+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 (file)
index 0000000..8f019e0
--- /dev/null
@@ -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 */
index 5da7628..4ed5645 100644 (file)
@@ -1017,7 +1017,7 @@ int snd_soc_add_pcm_runtime(struct snd_soc_card *card,
        for_each_link_cpus(dai_link, i, cpu) {
                asoc_rtd_to_cpu(rtd, i) = snd_soc_find_dai(cpu);
                if (!asoc_rtd_to_cpu(rtd, i)) {
-                       dev_info(card->dev, "ASoC: CPU DAI %s not registered\n",
+                       dev_info(card->dev, "ASoC: CPU DAI %s not registered - will retry\n",
                                 cpu->dai_name);
                        goto _err_defer;
                }
@@ -1028,7 +1028,7 @@ int snd_soc_add_pcm_runtime(struct snd_soc_card *card,
        for_each_link_codecs(dai_link, i, codec) {
                asoc_rtd_to_codec(rtd, i) = snd_soc_find_dai(codec);
                if (!asoc_rtd_to_codec(rtd, i)) {
-                       dev_info(card->dev, "ASoC: CODEC DAI %s not registered\n",
+                       dev_info(card->dev, "ASoC: CODEC DAI %s not registered- will retry\n",
                                 codec->dai_name);
                        goto _err_defer;
                }
@@ -1231,7 +1231,7 @@ found:
  * Returns 0 on success, otherwise a negative error code.
  */
 int snd_soc_runtime_set_dai_fmt(struct snd_soc_pcm_runtime *rtd,
-                               unsigned int dai_fmt)
+       unsigned int dai_fmt)
 {
        struct snd_soc_dai *cpu_dai;
        struct snd_soc_dai *codec_dai;
@@ -1240,7 +1240,15 @@ int snd_soc_runtime_set_dai_fmt(struct snd_soc_pcm_runtime *rtd,
        int ret;
 
        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;
        }
@@ -1249,8 +1257,21 @@ int snd_soc_runtime_set_dai_fmt(struct snd_soc_pcm_runtime *rtd,
         * Flip the polarity for the "CPU" end of a CODEC<->CODEC link
         * the component which has non_legacy_dai_naming is Codec
         */
-       inv_dai_fmt = snd_soc_daifmt_clock_provider_fliped(dai_fmt);
-
+       inv_dai_fmt = dai_fmt & ~SND_SOC_DAIFMT_MASTER_MASK;
+       switch (dai_fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+       case SND_SOC_DAIFMT_CBM_CFM:
+               inv_dai_fmt |= SND_SOC_DAIFMT_CBS_CFS;
+               break;
+       case SND_SOC_DAIFMT_CBM_CFS:
+               inv_dai_fmt |= SND_SOC_DAIFMT_CBS_CFM;
+               break;
+       case SND_SOC_DAIFMT_CBS_CFM:
+               inv_dai_fmt |= SND_SOC_DAIFMT_CBM_CFS;
+               break;
+       case SND_SOC_DAIFMT_CBS_CFS:
+               inv_dai_fmt |= SND_SOC_DAIFMT_CBM_CFM;
+               break;
+       }
        for_each_rtd_cpu_dais(rtd, i, cpu_dai) {
                unsigned int fmt = dai_fmt;
 
index 1764b93..fbbe330 100644 (file)
@@ -817,8 +817,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
index 968d90c..a203e15 100644 (file)
@@ -1921,6 +1921,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = {
                   QUIRK_FLAG_GENERIC_IMPLICIT_FB),
        DEVICE_FLG(0x2b53, 0x0031, /* Fiero SC-01 (firmware v1.1.0) */
                   QUIRK_FLAG_GENERIC_IMPLICIT_FB),
+       DEVICE_FLG(0x09da, 0x2695, /* A4Tech FHD 1080p webcam */
+                  QUIRK_FLAG_DISABLE_AUTOSUSPEND | QUIRK_FLAG_GET_SAMPLE_RATE),
 
        /* Vendor matches */
        VENDOR_FLG(0x045e, /* MS Lifecam */